Message ID | 1227194815-16200-1-git-send-email-anemo@mba.ocn.ne.jp |
---|---|
State | Superseded, archived |
Headers | show |
On Fri, 21 Nov 2008 00:26:54 +0900 Atsushi Nemoto <anemo@mba.ocn.ne.jp> wrote: > > Add support for RTC in TX4939 SoC. > > Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp> nack, please see below. > --- > drivers/rtc/Kconfig | 7 + > drivers/rtc/Makefile | 1 + > drivers/rtc/rtc-tx4939.c | 338 ++++++++++++++++++++++++++++++++++++++++++++++ > 3 files changed, 346 insertions(+), 0 deletions(-) > create mode 100644 drivers/rtc/rtc-tx4939.c > > diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig > index 123092d..80da08f 100644 > --- a/drivers/rtc/Kconfig > +++ b/drivers/rtc/Kconfig > @@ -679,4 +679,11 @@ config RTC_DRV_STARFIRE > If you say Y here you will get support for the RTC found on > Starfire systems. > > +config RTC_DRV_TX4939 > + tristate "TX4939 SoC" > + depends on SOC_TX4939 > + help > + Driver for the internal RTC (Realtime Clock) module found on > + Toshiba TX4939 SoC. > + > endif # RTC_CLASS > diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile > index 6e79c91..84dbffd 100644 > --- a/drivers/rtc/Makefile > +++ b/drivers/rtc/Makefile > @@ -66,6 +66,7 @@ obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o > obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o > obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o > obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl4030.o > +obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o > obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o > obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o > obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o > diff --git a/drivers/rtc/rtc-tx4939.c b/drivers/rtc/rtc-tx4939.c > new file mode 100644 > index 0000000..405790c > --- /dev/null > +++ b/drivers/rtc/rtc-tx4939.c > @@ -0,0 +1,338 @@ > +/* > + * TX4939 internal RTC driver > + * Based on RBTX49xx patch from CELF patch archive. > + * > + * This file is subject to the terms and conditions of the GNU General Public > + * License. See the file "COPYING" in the main directory of this archive > + * for more details. > + * > + * (C) Copyright TOSHIBA CORPORATION 2005-2007 > + */ > +#include <linux/rtc.h> > +#include <linux/platform_device.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <asm/txx9/tx4939.h> > + > +struct tx4939rtc_plat_data { > + struct rtc_device *rtc; > + struct tx4939_rtc_reg __iomem *rtcreg; > + spinlock_t lock; > +}; is the additional lock necessary? > +atic struct tx4939rtc_plat_data *get_tx4939rtc_plat_data(struct device *dev) > +{ > + return platform_get_drvdata(to_platform_device(dev)); > +} > + > +static int tx4939_rtc_cmd(struct tx4939_rtc_reg __iomem *rtcreg, int cmd) > +{ > + int i = 0; > + > + __raw_writel(cmd, &rtcreg->ctl); > + /* This might take 30us (next 32.768KHz clock) */ > + while (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_BUSY) { > + /* timeout on approx. 100us (@ GBUS200MHz) */ > + if (i++ > 200 * 100) > + return -EBUSY; > + cpu_relax(); > + } > + return 0; > +} > + > +static int tx4939_rtc_set_time(struct device *dev, struct rtc_time *tm) > +{ > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + int i, ret; > + unsigned long sec; > + unsigned char buf[6]; > + > + rtc_tm_to_time(tm, &sec); > + buf[0] = 0; > + buf[1] = 0; > + buf[2] = sec; > + buf[3] = sec >> 8; > + buf[4] = sec >> 16; > + buf[5] = sec >> 24; > + spin_lock_irq(&pdata->lock); > + __raw_writel(0, &rtcreg->adr); > + for (i = 0; i < 6; i++) > + __raw_writel(buf[i], &rtcreg->dat); > + ret = tx4939_rtc_cmd(rtcreg, > + TX4939_RTCCTL_COMMAND_SETTIME | > + (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); > + spin_unlock_irq(&pdata->lock); > + return ret; > +} it would be better to implement set_mmss instead of set_time > + > +static int tx4939_rtc_read_time(struct device *dev, struct rtc_time *tm) > +{ > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + int i, ret; > + unsigned long sec; > + unsigned char buf[6]; > + > + spin_lock_irq(&pdata->lock); > + ret = tx4939_rtc_cmd(rtcreg, > + TX4939_RTCCTL_COMMAND_GETTIME | > + (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); > + if (ret) { > + spin_unlock_irq(&pdata->lock); > + return ret; > + } > + __raw_writel(2, &rtcreg->adr); > + for (i = 2; i < 6; i++) > + buf[i] = __raw_readl(&rtcreg->dat); > + spin_unlock_irq(&pdata->lock); > + sec = (buf[5] << 24) | (buf[4] << 16) | (buf[3] << 8) | buf[2]; > + rtc_time_to_tm(sec, tm); > + return 0; this shouldn't lead to problems but an return rtc_valid_tm would be preferable > +} > + > +static int tx4939_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) > +{ > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + int i, ret; > + unsigned long sec; > + unsigned char buf[6]; > + > + if (alrm->time.tm_sec < 0 || > + alrm->time.tm_min < 0 || > + alrm->time.tm_hour < 0 || > + alrm->time.tm_mday < 0 || > + alrm->time.tm_mon < 0 || > + alrm->time.tm_year < 0) > + return -EINVAL; > + rtc_tm_to_time(&alrm->time, &sec); > + buf[0] = 0; > + buf[1] = 0; > + buf[2] = sec; > + buf[3] = sec >> 8; > + buf[4] = sec >> 16; > + buf[5] = sec >> 24; > + spin_lock_irq(&pdata->lock); > + __raw_writel(0, &rtcreg->adr); > + for (i = 0; i < 6; i++) > + __raw_writel(buf[i], &rtcreg->dat); > + ret = tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_SETALARM | > + (alrm->enabled ? TX4939_RTCCTL_ALME : 0)); > + spin_unlock_irq(&pdata->lock); > + return ret; > +} > + > +static int tx4939_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) > +{ > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + int i, ret; > + unsigned long sec; > + unsigned char buf[6]; > + u32 ctl; > + > + spin_lock_irq(&pdata->lock); > + ret = tx4939_rtc_cmd(rtcreg, > + TX4939_RTCCTL_COMMAND_GETALARM | > + (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); > + if (ret) { > + spin_unlock_irq(&pdata->lock); > + return ret; > + } > + __raw_writel(2, &rtcreg->adr); > + for (i = 2; i < 6; i++) > + buf[i] = __raw_readl(&rtcreg->dat); > + ctl = __raw_readl(&rtcreg->ctl); > + alrm->enabled = (ctl & TX4939_RTCCTL_ALME) ? 1 : 0; > + alrm->pending = (ctl & TX4939_RTCCTL_ALMD) ? 1 : 0; > + spin_unlock_irq(&pdata->lock); > + sec = (buf[5] << 24) | (buf[4] << 16) | (buf[3] << 8) | buf[2]; > + rtc_time_to_tm(sec, &alrm->time); > + return 0; > +} > + > +static irqreturn_t tx4939_rtc_interrupt(int irq, void *dev_id) > +{ > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev_id); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + unsigned long events = RTC_IRQF; > + > + spin_lock(&pdata->lock); > + if (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALMD) { > + events |= RTC_AF; > + tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP); > + } > + spin_unlock(&pdata->lock); > + rtc_update_irq(pdata->rtc, 1, events); > + return IRQ_HANDLED; > +} > + > +static int tx4939_rtc_ioctl(struct device *dev, > + unsigned int cmd, unsigned long arg) > +{ > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + struct rtc_time tm; > + struct rtc_wkalrm alarm; > + void __user *uarg = (void __user *)arg; > + > + switch (cmd) { > + case RTC_ALM_SET: > + if (copy_from_user(&alarm.time, uarg, sizeof(tm))) > + return -EFAULT; > + alarm.enabled = 0; > + alarm.pending = 0; > + /* keep all date/time in alarm.time */ > + return rtc_set_alarm(pdata->rtc, &alarm); the rtc subsystem maps ALM_SET to set_alarm, you don't need it. > + case RTC_AIE_OFF: > + spin_lock_irq(&pdata->lock); > + tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP); > + spin_unlock_irq(&pdata->lock); > + break; > + case RTC_AIE_ON: > + spin_lock_irq(&pdata->lock); > + tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP | > + TX4939_RTCCTL_ALME); > + spin_unlock_irq(&pdata->lock); > + break; AIE_ON an OFF are mapped to alarm_irq_enable, please see the latest patches on the rtc mailing list or here http://patchwork.ozlabs.org/patch/9676/ > + default: > + return -ENOIOCTLCMD; > + } > + return 0; > +} > + > +static const struct rtc_class_ops tx4939_rtc_ops = { > + .read_time = tx4939_rtc_read_time, > + .set_time = tx4939_rtc_set_time, > + .read_alarm = tx4939_rtc_read_alarm, > + .set_alarm = tx4939_rtc_set_alarm, > + .ioctl = tx4939_rtc_ioctl, > +}; > + > +static ssize_t tx4939_rtc_nvram_read(struct kobject *kobj, > + struct bin_attribute *bin_attr, > + char *buf, loff_t pos, size_t size) > +{ > + struct device *dev = container_of(kobj, struct device, kobj); > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + ssize_t count; > + > + spin_lock_irq(&pdata->lock); > + for (count = 0; size > 0 && pos < TX4939_RTC_REG_RAMSIZE; > + count++, size--) { > + __raw_writel(pos++, &rtcreg->adr); > + *buf++ = __raw_readl(&rtcreg->dat); > + } > + spin_unlock_irq(&pdata->lock); > + return count; > +} > + > +static ssize_t tx4939_rtc_nvram_write(struct kobject *kobj, > + struct bin_attribute *bin_attr, > + char *buf, loff_t pos, size_t size) > +{ > + struct device *dev = container_of(kobj, struct device, kobj); > + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); > + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; > + ssize_t count; > + > + spin_lock_irq(&pdata->lock); > + for (count = 0; size > 0 && pos < TX4939_RTC_REG_RAMSIZE; > + count++, size--) { > + __raw_writel(pos++, &rtcreg->adr); > + __raw_writel(*buf++, &rtcreg->dat); > + } > + spin_unlock_irq(&pdata->lock); > + return count; > +} > + > +static struct bin_attribute tx4939_rtc_nvram_attr = { > + .attr = { > + .name = "nvram", > + .mode = S_IRUGO | S_IWUSR, > + }, > + .size = TX4939_RTC_REG_RAMSIZE, > + .read = tx4939_rtc_nvram_read, > + .write = tx4939_rtc_nvram_write, > +}; > + > +static int __init tx4939_rtc_probe(struct platform_device *pdev) > +{ > + struct rtc_device *rtc; > + struct tx4939rtc_plat_data *pdata; > + struct resource *res; > + int irq, ret; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + if (!res) > + return -ENODEV; > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) > + return -ENODEV; > + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); > + if (!pdata) > + return -ENOMEM; > + > + if (!devm_request_mem_region(&pdev->dev, res->start, > + res->end - res->start + 1, pdev->name)) > + return -EBUSY; use resource_size() > + pdata->rtcreg = devm_ioremap(&pdev->dev, res->start, > + res->end - res->start + 1); > + if (!pdata->rtcreg) > + return -EBUSY; > + > + spin_lock_init(&pdata->lock); > + tx4939_rtc_cmd(pdata->rtcreg, TX4939_RTCCTL_COMMAND_NOP); > + if (devm_request_irq(&pdev->dev, irq, tx4939_rtc_interrupt, > + IRQF_DISABLED | IRQF_SHARED, > + pdev->name, &pdev->dev) < 0) > + return -EBUSY; > + rtc = rtc_device_register(pdev->name, &pdev->dev, > + &tx4939_rtc_ops, THIS_MODULE); > + if (IS_ERR(rtc)) > + return PTR_ERR(rtc); > + pdata->rtc = rtc; > + platform_set_drvdata(pdev, pdata); it's better to set those pointers as early as possible > + ret = sysfs_create_bin_file(&pdev->dev.kobj, &tx4939_rtc_nvram_attr); > + if (ret) > + rtc_device_unregister(rtc); > + return ret; > +} > + > +static int __exit tx4939_rtc_remove(struct platform_device *pdev) > +{ > + struct tx4939rtc_plat_data *pdata = platform_get_drvdata(pdev); > + struct rtc_device *rtc = pdata->rtc; > + > + sysfs_remove_bin_file(&pdev->dev.kobj, &tx4939_rtc_nvram_attr); > + rtc_device_unregister(rtc); > + platform_set_drvdata(pdev, NULL); > + return 0; > +} > + > +static struct platform_driver tx4939_rtc_driver = { > + .remove = __exit_p(tx4939_rtc_remove), > + .driver = { > + .name = "tx4939rtc", > + .owner = THIS_MODULE, > + }, > +}; > + > +static int __init tx4939rtc_init(void) > +{ > + return platform_driver_probe(&tx4939_rtc_driver, tx4939_rtc_probe); > +} > + > +static void __exit tx4939rtc_exit(void) > +{ > + platform_driver_unregister(&tx4939_rtc_driver); > +} > + > +module_init(tx4939rtc_init); > +module_exit(tx4939rtc_exit); > + > +MODULE_DESCRIPTION("TX4939 internal RTC driver"); > +MODULE_LICENSE("GPL"); > +MODULE_ALIAS("platform:tx4939rtc"); MODULE_AUTHOR please
On Thu, 20 Nov 2008 16:45:33 +0100, Alessandro Zummo <alessandro.zummo@towertech.it> wrote: > > +struct tx4939rtc_plat_data { > > + struct rtc_device *rtc; > > + struct tx4939_rtc_reg __iomem *rtcreg; > > + spinlock_t lock; > > +}; > > is the additional lock necessary? It is used to protect sysfs nvram access from rtc ops. Hm, can I use rtc->ops_lock in sysfs read/write routine to achieve it? I will address all other issues. Thanks. --- Atsushi Nemoto --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to "rtc-linux". Membership options at http://groups.google.com/group/rtc-linux . Please read http://groups.google.com/group/rtc-linux/web/checklist before submitting a driver. -~----------~----~----~----~------~----~------~--~---
On Fri, 21 Nov 2008 01:17:44 +0900 (JST) Atsushi Nemoto <anemo@mba.ocn.ne.jp> wrote: > It is used to protect sysfs nvram access from rtc ops. > > Hm, can I use rtc->ops_lock in sysfs read/write routine to achieve it? it should be doable, rtc-ds1305 uses it > I will address all other issues. Thanks. thanks!
On Thu, 20 Nov 2008 17:21:38 +0100, Alessandro Zummo <alessandro.zummo@towertech.it> wrote: > > It is used to protect sysfs nvram access from rtc ops. > > > > Hm, can I use rtc->ops_lock in sysfs read/write routine to achieve it? > > it should be doable, rtc-ds1305 uses it But ops_lock is mutex, so it cannot protect from an interrupt handler. I will try rtc->irq_lock. --- Atsushi Nemoto --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to "rtc-linux". Membership options at http://groups.google.com/group/rtc-linux . Please read http://groups.google.com/group/rtc-linux/web/checklist before submitting a driver. -~----------~----~----~----~------~----~------~--~---
diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig index 123092d..80da08f 100644 --- a/drivers/rtc/Kconfig +++ b/drivers/rtc/Kconfig @@ -679,4 +679,11 @@ config RTC_DRV_STARFIRE If you say Y here you will get support for the RTC found on Starfire systems. +config RTC_DRV_TX4939 + tristate "TX4939 SoC" + depends on SOC_TX4939 + help + Driver for the internal RTC (Realtime Clock) module found on + Toshiba TX4939 SoC. + endif # RTC_CLASS diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile index 6e79c91..84dbffd 100644 --- a/drivers/rtc/Makefile +++ b/drivers/rtc/Makefile @@ -66,6 +66,7 @@ obj-$(CONFIG_RTC_DRV_SH) += rtc-sh.o obj-$(CONFIG_RTC_DRV_STK17TA8) += rtc-stk17ta8.o obj-$(CONFIG_RTC_DRV_TEST) += rtc-test.o obj-$(CONFIG_RTC_DRV_TWL4030) += rtc-twl4030.o +obj-$(CONFIG_RTC_DRV_TX4939) += rtc-tx4939.o obj-$(CONFIG_RTC_DRV_V3020) += rtc-v3020.o obj-$(CONFIG_RTC_DRV_VR41XX) += rtc-vr41xx.o obj-$(CONFIG_RTC_DRV_WM8350) += rtc-wm8350.o diff --git a/drivers/rtc/rtc-tx4939.c b/drivers/rtc/rtc-tx4939.c new file mode 100644 index 0000000..405790c --- /dev/null +++ b/drivers/rtc/rtc-tx4939.c @@ -0,0 +1,338 @@ +/* + * TX4939 internal RTC driver + * Based on RBTX49xx patch from CELF patch archive. + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + * + * (C) Copyright TOSHIBA CORPORATION 2005-2007 + */ +#include <linux/rtc.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <asm/txx9/tx4939.h> + +struct tx4939rtc_plat_data { + struct rtc_device *rtc; + struct tx4939_rtc_reg __iomem *rtcreg; + spinlock_t lock; +}; + +static struct tx4939rtc_plat_data *get_tx4939rtc_plat_data(struct device *dev) +{ + return platform_get_drvdata(to_platform_device(dev)); +} + +static int tx4939_rtc_cmd(struct tx4939_rtc_reg __iomem *rtcreg, int cmd) +{ + int i = 0; + + __raw_writel(cmd, &rtcreg->ctl); + /* This might take 30us (next 32.768KHz clock) */ + while (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_BUSY) { + /* timeout on approx. 100us (@ GBUS200MHz) */ + if (i++ > 200 * 100) + return -EBUSY; + cpu_relax(); + } + return 0; +} + +static int tx4939_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + int i, ret; + unsigned long sec; + unsigned char buf[6]; + + rtc_tm_to_time(tm, &sec); + buf[0] = 0; + buf[1] = 0; + buf[2] = sec; + buf[3] = sec >> 8; + buf[4] = sec >> 16; + buf[5] = sec >> 24; + spin_lock_irq(&pdata->lock); + __raw_writel(0, &rtcreg->adr); + for (i = 0; i < 6; i++) + __raw_writel(buf[i], &rtcreg->dat); + ret = tx4939_rtc_cmd(rtcreg, + TX4939_RTCCTL_COMMAND_SETTIME | + (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); + spin_unlock_irq(&pdata->lock); + return ret; +} + +static int tx4939_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + int i, ret; + unsigned long sec; + unsigned char buf[6]; + + spin_lock_irq(&pdata->lock); + ret = tx4939_rtc_cmd(rtcreg, + TX4939_RTCCTL_COMMAND_GETTIME | + (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); + if (ret) { + spin_unlock_irq(&pdata->lock); + return ret; + } + __raw_writel(2, &rtcreg->adr); + for (i = 2; i < 6; i++) + buf[i] = __raw_readl(&rtcreg->dat); + spin_unlock_irq(&pdata->lock); + sec = (buf[5] << 24) | (buf[4] << 16) | (buf[3] << 8) | buf[2]; + rtc_time_to_tm(sec, tm); + return 0; +} + +static int tx4939_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + int i, ret; + unsigned long sec; + unsigned char buf[6]; + + if (alrm->time.tm_sec < 0 || + alrm->time.tm_min < 0 || + alrm->time.tm_hour < 0 || + alrm->time.tm_mday < 0 || + alrm->time.tm_mon < 0 || + alrm->time.tm_year < 0) + return -EINVAL; + rtc_tm_to_time(&alrm->time, &sec); + buf[0] = 0; + buf[1] = 0; + buf[2] = sec; + buf[3] = sec >> 8; + buf[4] = sec >> 16; + buf[5] = sec >> 24; + spin_lock_irq(&pdata->lock); + __raw_writel(0, &rtcreg->adr); + for (i = 0; i < 6; i++) + __raw_writel(buf[i], &rtcreg->dat); + ret = tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_SETALARM | + (alrm->enabled ? TX4939_RTCCTL_ALME : 0)); + spin_unlock_irq(&pdata->lock); + return ret; +} + +static int tx4939_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm) +{ + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + int i, ret; + unsigned long sec; + unsigned char buf[6]; + u32 ctl; + + spin_lock_irq(&pdata->lock); + ret = tx4939_rtc_cmd(rtcreg, + TX4939_RTCCTL_COMMAND_GETALARM | + (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALME)); + if (ret) { + spin_unlock_irq(&pdata->lock); + return ret; + } + __raw_writel(2, &rtcreg->adr); + for (i = 2; i < 6; i++) + buf[i] = __raw_readl(&rtcreg->dat); + ctl = __raw_readl(&rtcreg->ctl); + alrm->enabled = (ctl & TX4939_RTCCTL_ALME) ? 1 : 0; + alrm->pending = (ctl & TX4939_RTCCTL_ALMD) ? 1 : 0; + spin_unlock_irq(&pdata->lock); + sec = (buf[5] << 24) | (buf[4] << 16) | (buf[3] << 8) | buf[2]; + rtc_time_to_tm(sec, &alrm->time); + return 0; +} + +static irqreturn_t tx4939_rtc_interrupt(int irq, void *dev_id) +{ + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev_id); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + unsigned long events = RTC_IRQF; + + spin_lock(&pdata->lock); + if (__raw_readl(&rtcreg->ctl) & TX4939_RTCCTL_ALMD) { + events |= RTC_AF; + tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP); + } + spin_unlock(&pdata->lock); + rtc_update_irq(pdata->rtc, 1, events); + return IRQ_HANDLED; +} + +static int tx4939_rtc_ioctl(struct device *dev, + unsigned int cmd, unsigned long arg) +{ + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + struct rtc_time tm; + struct rtc_wkalrm alarm; + void __user *uarg = (void __user *)arg; + + switch (cmd) { + case RTC_ALM_SET: + if (copy_from_user(&alarm.time, uarg, sizeof(tm))) + return -EFAULT; + alarm.enabled = 0; + alarm.pending = 0; + /* keep all date/time in alarm.time */ + return rtc_set_alarm(pdata->rtc, &alarm); + case RTC_AIE_OFF: + spin_lock_irq(&pdata->lock); + tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP); + spin_unlock_irq(&pdata->lock); + break; + case RTC_AIE_ON: + spin_lock_irq(&pdata->lock); + tx4939_rtc_cmd(rtcreg, TX4939_RTCCTL_COMMAND_NOP | + TX4939_RTCCTL_ALME); + spin_unlock_irq(&pdata->lock); + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static const struct rtc_class_ops tx4939_rtc_ops = { + .read_time = tx4939_rtc_read_time, + .set_time = tx4939_rtc_set_time, + .read_alarm = tx4939_rtc_read_alarm, + .set_alarm = tx4939_rtc_set_alarm, + .ioctl = tx4939_rtc_ioctl, +}; + +static ssize_t tx4939_rtc_nvram_read(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t size) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + ssize_t count; + + spin_lock_irq(&pdata->lock); + for (count = 0; size > 0 && pos < TX4939_RTC_REG_RAMSIZE; + count++, size--) { + __raw_writel(pos++, &rtcreg->adr); + *buf++ = __raw_readl(&rtcreg->dat); + } + spin_unlock_irq(&pdata->lock); + return count; +} + +static ssize_t tx4939_rtc_nvram_write(struct kobject *kobj, + struct bin_attribute *bin_attr, + char *buf, loff_t pos, size_t size) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct tx4939rtc_plat_data *pdata = get_tx4939rtc_plat_data(dev); + struct tx4939_rtc_reg __iomem *rtcreg = pdata->rtcreg; + ssize_t count; + + spin_lock_irq(&pdata->lock); + for (count = 0; size > 0 && pos < TX4939_RTC_REG_RAMSIZE; + count++, size--) { + __raw_writel(pos++, &rtcreg->adr); + __raw_writel(*buf++, &rtcreg->dat); + } + spin_unlock_irq(&pdata->lock); + return count; +} + +static struct bin_attribute tx4939_rtc_nvram_attr = { + .attr = { + .name = "nvram", + .mode = S_IRUGO | S_IWUSR, + }, + .size = TX4939_RTC_REG_RAMSIZE, + .read = tx4939_rtc_nvram_read, + .write = tx4939_rtc_nvram_write, +}; + +static int __init tx4939_rtc_probe(struct platform_device *pdev) +{ + struct rtc_device *rtc; + struct tx4939rtc_plat_data *pdata; + struct resource *res; + int irq, ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return -ENODEV; + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + if (!devm_request_mem_region(&pdev->dev, res->start, + res->end - res->start + 1, pdev->name)) + return -EBUSY; + pdata->rtcreg = devm_ioremap(&pdev->dev, res->start, + res->end - res->start + 1); + if (!pdata->rtcreg) + return -EBUSY; + + spin_lock_init(&pdata->lock); + tx4939_rtc_cmd(pdata->rtcreg, TX4939_RTCCTL_COMMAND_NOP); + if (devm_request_irq(&pdev->dev, irq, tx4939_rtc_interrupt, + IRQF_DISABLED | IRQF_SHARED, + pdev->name, &pdev->dev) < 0) + return -EBUSY; + rtc = rtc_device_register(pdev->name, &pdev->dev, + &tx4939_rtc_ops, THIS_MODULE); + if (IS_ERR(rtc)) + return PTR_ERR(rtc); + pdata->rtc = rtc; + platform_set_drvdata(pdev, pdata); + ret = sysfs_create_bin_file(&pdev->dev.kobj, &tx4939_rtc_nvram_attr); + if (ret) + rtc_device_unregister(rtc); + return ret; +} + +static int __exit tx4939_rtc_remove(struct platform_device *pdev) +{ + struct tx4939rtc_plat_data *pdata = platform_get_drvdata(pdev); + struct rtc_device *rtc = pdata->rtc; + + sysfs_remove_bin_file(&pdev->dev.kobj, &tx4939_rtc_nvram_attr); + rtc_device_unregister(rtc); + platform_set_drvdata(pdev, NULL); + return 0; +} + +static struct platform_driver tx4939_rtc_driver = { + .remove = __exit_p(tx4939_rtc_remove), + .driver = { + .name = "tx4939rtc", + .owner = THIS_MODULE, + }, +}; + +static int __init tx4939rtc_init(void) +{ + return platform_driver_probe(&tx4939_rtc_driver, tx4939_rtc_probe); +} + +static void __exit tx4939rtc_exit(void) +{ + platform_driver_unregister(&tx4939_rtc_driver); +} + +module_init(tx4939rtc_init); +module_exit(tx4939rtc_exit); + +MODULE_DESCRIPTION("TX4939 internal RTC driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:tx4939rtc");
Add support for RTC in TX4939 SoC. Signed-off-by: Atsushi Nemoto <anemo@mba.ocn.ne.jp> --- drivers/rtc/Kconfig | 7 + drivers/rtc/Makefile | 1 + drivers/rtc/rtc-tx4939.c | 338 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 346 insertions(+), 0 deletions(-) create mode 100644 drivers/rtc/rtc-tx4939.c