===================================================================
@@ -0,0 +1,553 @@
+/*
+ * i.MX SoC RTC driver
+ *
+ * Copyright (c) 2008 Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ *
+ * This file may be distributed under the terms of the GNU General
+ * Public License, version 2.
+ *
+ * Hardware bugs/workarounds:
+ * 1. When alarm seconds is set to odd number false interrupt is generated one
+ * second before the real one. Workaround is to compare if actual seconds
+ * is equal to alarm seconds to be sure this is real alarm interrupt. This
+ * bug and workaround is described in MX1/L erratum.
+ * 2. 2Hz flag in ISR doesn't generate interrupt. It is needed to generate
+ * RTC 2Hz periodic interrupts. Workaround is to enable 4Hz sampling
+ * interrupt and check if 2Hz ISR flag is set.
+ * 3. SWR (software reset) bit in RTC control register doesn't reset time/date
+ * registers. Workaround is to reset them one by one manually.
+ * 4. Not really a bug, but... There is no registers for year and month.
+ * So we have to calculate month from the days register (value 0-511).
+ * And it is not possible to have years here... so we will have to handle
+ * this to platform specific code through set_year() and get_year() functions
+ * passed by the platform_data.
+ */
+
+#include <linux/module.h>
+#include <linux/rtc.h>
+#include <linux/io.h>
+#include <linux/platform_device.h>
+#include <mach/rtc.h>
+
+/*
+ * RTC registers
+ */
+
+#define RCCTL 0x10 /* RTC Control Register */
+#define RTCISR 0x14 /* RTC Interrupt Status Register */
+#define RTCIENR 0x18 /* RTC Interrupt Enable Register */
+#define SECONDS 0x04 /* RTC Seconds Counter Register */
+#define HOURMIN 0x00 /* RTC Hours and Minutes Counter Register */
+#define DAYR 0x20 /* RTC Days Counter Register */
+#define ALRM_SEC 0x0C /* RTC Seconds Alarm Register */
+#define ALRM_HM 0x08 /* RTC Hours and Minutes Alarm Register */
+#define DAYALARM 0x24 /* RTC Day Alarm Register */
+
+#define RCCTL_EN (1 << 7)
+#define RCCTL_XTL_32KHZ (1 << 5)
+
+#define RTCI_SAM(x) (1 << ((x) + 8))
+#define RTCI_2HZ (1 << 7)
+#define RTCI_1HZ (1 << 4)
+#define RTCI_ALM (1 << 2)
+
+/* See Hardware bugs/workarounds No.2 for details */
+#define RTC_IRQ_MASK 0x007F
+#define RTC_SAM_IRQ_MASK 0xFF80
+
+#define MIN_MASK 0x3f
+#define HOUR_MASK 0x1f
+#define HOUR_SHIFT 8
+#define DAY_MAX 511 /* we have 9bit register for days */
+
+#define DRV_NAME "rtc-imx"
+
+struct imx_priv {
+ struct rtc_device *rtc;
+ void __iomem *ioaddr;
+ int irq;
+ int sam_irq;
+ size_t size;
+ unsigned long baseaddr;
+ struct imxrtc_platform_data *pdata;
+ int year;
+ int f_32khz;
+ int per_frq;
+ unsigned int save_irq;
+};
+
+static void imx_rtc_int_set(struct imx_priv *rtc, unsigned int rtc_int)
+{
+ unsigned int temp;
+
+ temp = __raw_readl(rtc->ioaddr + RTCIENR);
+ __raw_writel(temp | rtc_int, rtc->ioaddr + RTCIENR);
+}
+
+static void imx_rtc_int_clear(struct imx_priv *rtc, unsigned int rtc_int)
+{
+ unsigned int temp;
+
+ temp = __raw_readl(rtc->ioaddr + RTCIENR);
+ __raw_writel(temp & ~rtc_int, rtc->ioaddr + RTCIENR);
+}
+
+static irqreturn_t imx_rtc_per_irq(int irq, void *data)
+{
+ struct imx_priv *priv = data;
+ unsigned int status;
+
+ /* we serve only interrupts belonging to this irq handler */
+ status = __raw_readl(priv->ioaddr + RTCISR) & RTC_SAM_IRQ_MASK;
+
+ /* clear irq status register */
+ __raw_writel(status, priv->ioaddr + RTCISR);
+
+ dev_dbg(&priv->rtc->dev, "Sampling irq status: 0x%x\n", status);
+
+ if (unlikely(!status))
+ return IRQ_NONE;
+
+ /* See Hardware bugs/workarounds No.2 for details */
+ if ((__raw_readl(priv->ioaddr + RTCIENR) & RTCI_2HZ) &&
+ (status & RTCI_SAM(0)) && !(status & RTCI_2HZ))
+ return IRQ_HANDLED;
+
+ rtc_update_irq(priv->rtc, 1, RTC_IRQF | RTC_PF);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t imx_rtc_irq(int irq, void *data)
+{
+ struct imx_priv *priv = data;
+ unsigned long events = 0;
+ unsigned long number = 0;
+ unsigned int status;
+ irqreturn_t ret = IRQ_NONE;
+
+ /* we serve only interrupts belonging to this irq handler */
+ status = __raw_readl(priv->ioaddr + RTCISR) & RTC_IRQ_MASK;
+
+ /* clear irq status register */
+ __raw_writel(status, priv->ioaddr + RTCISR);
+
+ dev_dbg(&priv->rtc->dev, "RTC irq status: 0x%x\n", status);
+
+ if (status & RTCI_ALM) {
+ /* See Hardware bugs/workarounds No.1 for details */
+ if (__raw_readl(priv->ioaddr + SECONDS) ==
+ __raw_readl(priv->ioaddr + ALRM_SEC)) {
+ events |= RTC_AF | RTC_IRQF;
+ number++;
+ /*
+ * disable irq, because it will be triggered again after
+ * 512 days. Alarm is single-shot event.
+ */
+ imx_rtc_int_clear(priv, RTCI_ALM);
+ } else {
+ ret = IRQ_HANDLED;
+ }
+ }
+
+ if (status & RTCI_1HZ) {
+ events |= RTC_IRQF | RTC_UF;
+ number++;
+ }
+
+ if (events) {
+ rtc_update_irq(priv->rtc, number, events);
+ ret = IRQ_HANDLED;
+ }
+
+ return ret;
+}
+
+static int imx_rtc_read_time(struct device *dev, struct rtc_time *tm)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+ int temp;
+
+ tm->tm_sec = __raw_readl(priv->ioaddr + SECONDS);
+ temp = __raw_readl(priv->ioaddr + HOURMIN);
+ tm->tm_min = temp & MIN_MASK;
+ tm->tm_hour = (temp >> HOUR_SHIFT) & HOUR_MASK;
+ tm->tm_yday = __raw_readl(priv->ioaddr + DAYR);
+ /*
+ * Maybe our day counter leaped over the year?
+ * See Hardware bugs/workarounds No.4 for details
+ */
+ temp = rtc_year_days(1, 12, priv->year);
+ if (tm->tm_yday > temp) {
+ priv->year++;
+ if (priv->pdata && priv->pdata->set_year)
+ priv->pdata->set_year(dev, priv->year);
+ tm->tm_yday -= temp;
+ /* Write new days value back to register */
+ __raw_writel(tm->tm_yday, priv->ioaddr + DAYR);
+ }
+ tm->tm_year = priv->year;
+ /* calculate month and month day from year days */
+ tm->tm_mon = 0;
+ tm->tm_mday = tm->tm_yday;
+ temp = rtc_month_days(0, tm->tm_year);
+ while (tm->tm_mday > temp) {
+ tm->tm_mon++;
+ tm->tm_mday -= temp;
+ temp = rtc_month_days(tm->tm_mon, tm->tm_year);
+ }
+ /* first day in month is 1 */
+ tm->tm_mday++;
+
+ return 0;
+}
+
+static int imx_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+ int temp;
+
+ __raw_writel(tm->tm_sec, priv->ioaddr + SECONDS);
+ temp = tm->tm_min | (tm->tm_hour << HOUR_SHIFT);
+ __raw_writel(temp, priv->ioaddr + HOURMIN);
+ temp = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+ __raw_writel(temp, priv->ioaddr + DAYR);
+ priv->year = tm->tm_year;
+ /* See Hardware bugs/workarounds No.4 for details */
+ if (priv->pdata && priv->pdata->set_year)
+ priv->pdata->set_year(dev, priv->year);
+
+ return 0;
+}
+
+static int imx_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alm->time;
+ int temp;
+
+ tm->tm_sec = __raw_readl(priv->ioaddr + ALRM_SEC);
+ temp = __raw_readl(priv->ioaddr + ALRM_HM);
+ tm->tm_min = temp & MIN_MASK;
+ tm->tm_hour = (temp >> HOUR_SHIFT) & HOUR_MASK;
+ tm->tm_yday = __raw_readl(priv->ioaddr + DAYALARM);
+ tm->tm_year = priv->year;
+ /* Maybe our day counter leaped over the year? */
+ temp = rtc_year_days(1, 12, priv->year);
+ if (tm->tm_yday > temp) {
+ tm->tm_year++;
+ tm->tm_yday -= temp;
+ }
+ /* calculate month and month day from year days */
+ tm->tm_mon = 0;
+ tm->tm_mday = tm->tm_yday;
+ temp = rtc_month_days(0, tm->tm_year);
+ while (tm->tm_mday > temp) {
+ tm->tm_mon++;
+ tm->tm_mday -= temp;
+ temp = rtc_month_days(tm->tm_mon, tm->tm_year);
+ }
+ /* first day in month is 1 */
+ tm->tm_mday++;
+
+ alm->enabled = !!(__raw_readl(priv->ioaddr + RTCIENR) & RTCI_ALM);
+
+ return 0;
+}
+
+static int imx_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alm)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+ struct rtc_time *tm = &alm->time;
+ int temp = 0;
+
+ if (tm->tm_year != priv->year) {
+ /* This RTC doesn't support alarms longer than 2 years */
+ if (tm->tm_year - priv->year > 1)
+ return -EINVAL;
+ temp = rtc_year_days(1, 12, priv->year);
+ }
+ temp += rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
+ /* check if we will fit in days register */
+ if (temp > DAY_MAX)
+ return -EINVAL;
+ __raw_writel(temp, priv->ioaddr + DAYALARM);
+ __raw_writel(tm->tm_sec, priv->ioaddr + ALRM_SEC);
+ temp = tm->tm_min | (tm->tm_hour << HOUR_SHIFT);
+ __raw_writel(temp, priv->ioaddr + ALRM_HM);
+
+ if (alm->enabled)
+ imx_rtc_int_set(priv, RTCI_ALM);
+ else
+ imx_rtc_int_clear(priv, RTCI_ALM);
+
+ return 0;
+}
+
+static int imx_rtc_set_per_state(struct device *dev, int enabled)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+
+ /* periodic interrupts are not supported if crystal is 32kHz */
+ if (priv->f_32khz)
+ return -ENXIO;
+
+ if (enabled) {
+ /* See Hardware bugs/workarounds No.2 for details */
+ if (priv->per_frq == 2)
+ imx_rtc_int_set(priv, RTCI_2HZ | RTCI_SAM(0));
+ else
+ imx_rtc_int_set(priv, RTCI_SAM(ilog2(priv->per_frq) - 2));
+ } else {
+ imx_rtc_int_clear(priv, RTCI_SAM(7) | RTCI_SAM(6) |
+ RTCI_SAM(5) | RTCI_SAM(4) |
+ RTCI_SAM(3) | RTCI_SAM(2) |
+ RTCI_SAM(1) | RTCI_SAM(0) | RTCI_2HZ);
+ }
+
+ return 0;
+}
+
+static int imx_rtc_set_per_freq(struct device *dev, int freq)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+
+ /* periodic interrupts are not supported if crystal is 32kHz */
+ if (priv->f_32khz)
+ return -ENXIO;
+
+ if (freq > 512)
+ return -EINVAL;
+
+ priv->per_frq = freq;
+
+ /* reinitialize irq for new frequency if periodic irq was enabled */
+ if (__raw_readl(priv->ioaddr + RTCIENR) & (RTCI_SAM(7) | RTCI_SAM(6) |
+ RTCI_SAM(5) | RTCI_SAM(4) | RTCI_SAM(3) | RTCI_SAM(2) |
+ RTCI_SAM(1) | RTCI_SAM(0) | RTCI_2HZ)) {
+ imx_rtc_set_per_state(dev, 0);
+ imx_rtc_set_per_state(dev, 1);
+ }
+
+ return 0;
+}
+
+static int
+imx_rtc_ioctl(struct device *dev, unsigned int cmd, unsigned long arg)
+{
+ struct imx_priv *priv = dev_get_drvdata(dev);
+
+ switch (cmd) {
+ /* AIE = Alarm Interrupt Enable */
+ case RTC_AIE_OFF:
+ imx_rtc_int_clear(priv, RTCI_ALM);
+ break;
+ case RTC_AIE_ON:
+ imx_rtc_int_set(priv, RTCI_ALM);
+ break;
+ /* UIE = Update Interrupt Enable (1/second) */
+ case RTC_UIE_OFF:
+ imx_rtc_int_clear(priv, RTCI_1HZ);
+ break;
+ case RTC_UIE_ON:
+ imx_rtc_int_set(priv, RTCI_1HZ);
+ break;
+ default:
+ return -ENOIOCTLCMD;
+ }
+
+ return 0;
+}
+
+static const struct rtc_class_ops imx_rtc_ops = {
+ .read_time = imx_rtc_read_time,
+ .set_time = imx_rtc_set_time,
+ .read_alarm = imx_rtc_read_alarm,
+ .set_alarm = imx_rtc_set_alarm,
+ .irq_set_state = imx_rtc_set_per_state,
+ .irq_set_freq = imx_rtc_set_per_freq,
+ .ioctl = imx_rtc_ioctl,
+};
+
+#ifdef CONFIG_PM
+static int imx_rtc_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct imx_priv *priv = platform_get_drvdata(pdev);
+
+ priv->save_irq = __raw_readl(priv->ioaddr + RTCIENR);
+
+ if (device_may_wakeup(&pdev->dev) && (priv->save_irq & RTCI_ALM)) {
+ priv->save_irq &= ~RTCI_ALM;
+ enable_irq_wake(priv->irq);
+ }
+
+ imx_rtc_int_clear(priv, priv->save_irq);
+
+ return 0;
+}
+
+static int imx_rtc_resume(struct platform_device *pdev)
+{
+ struct imx_priv *priv = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(&pdev->dev) && (priv->save_irq & RTCI_ALM))
+ disable_irq_wake(priv->irq);
+
+ imx_rtc_int_set(priv, priv->save_irq);
+
+ return 0;
+}
+#else
+#define imx_rtc_suspend NULL
+#define imx_rtc_resume NULL
+#endif
+
+static int __init imx_rtc_probe(struct platform_device *pdev)
+{
+ struct rtc_device *rtc;
+ struct resource *res;
+ int irq;
+ struct imx_priv *priv;
+ struct rtc_time tm;
+ int ret = 0;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ priv = kzalloc(sizeof *priv, GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ priv->sam_irq = -1;
+ priv->size = resource_size(res);
+ if (!request_mem_region(res->start, priv->size, pdev->name)) {
+ ret = -EBUSY;
+ goto out;
+ }
+ priv->baseaddr = res->start;
+ priv->irq = irq;
+ priv->ioaddr = ioremap(priv->baseaddr, priv->size);
+ if (!priv->ioaddr) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ rtc = rtc_device_register(pdev->name, &pdev->dev,
+ &imx_rtc_ops, THIS_MODULE);
+ if (IS_ERR(rtc)) {
+ ret = PTR_ERR(rtc);
+ goto out;
+ }
+ priv->rtc = rtc;
+ platform_set_drvdata(pdev, priv);
+
+ /* request irq */
+ ret = request_irq(priv->irq, imx_rtc_irq, 0, DRV_NAME, priv);
+ if (ret)
+ goto out;
+
+ priv->pdata = pdev->dev.platform_data;
+
+ priv->year = 70;
+
+ if (!priv->pdata) {
+ dev_warn(&pdev->dev, "No platform data provided\n");
+ priv->f_32khz = 0;
+ } else {
+ priv->f_32khz = priv->pdata->freq_is_32khz;
+ /* See Hardware bugs/workarounds No.4 for details */
+ if (priv->pdata->get_year)
+ priv->year = priv->pdata->get_year(&pdev->dev);
+ }
+
+ /* Reset the RTC if it has invalid values */
+ imx_rtc_read_time(&pdev->dev, &tm);
+ if (rtc_valid_tm(&tm)) {
+ dev_dbg(&pdev->dev, "Invalid time. Resetting RTC.\n");
+ /* See Hardware bugs/workarounds No.3 for details */
+ __raw_writel(0, priv->ioaddr + SECONDS);
+ __raw_writel(0, priv->ioaddr + HOURMIN);
+ __raw_writel(0, priv->ioaddr + DAYR);
+ }
+
+ /* Initialize RTC */
+ if (priv->f_32khz) {
+ __raw_writel(RCCTL_EN | RCCTL_XTL_32KHZ, priv->ioaddr + RCCTL);
+ } else {
+ __raw_writel(RCCTL_EN, priv->ioaddr + RCCTL);
+ priv->sam_irq = platform_get_irq(pdev, 1);
+ if (priv->sam_irq >= 0)
+ if (request_irq(priv->sam_irq, imx_rtc_per_irq, 0,
+ DRV_NAME, priv))
+ priv->sam_irq = -1;
+ /*
+ * hack to disable periodic irq if we failed to
+ * register RTC sampling interrupt.
+ */
+ if (priv->sam_irq < 0) {
+ dev_warn(&pdev->dev, "Failed to register i.MX RTC "
+ "sampling interrupt\n");
+ priv->f_32khz = 1;
+ }
+ }
+
+ return 0;
+
+out:
+ if (priv->sam_irq >= 0)
+ free_irq(priv->sam_irq, priv);
+ if (priv->rtc)
+ rtc_device_unregister(priv->rtc);
+ if (priv->ioaddr)
+ iounmap(priv->ioaddr);
+ if (priv->baseaddr)
+ release_mem_region(priv->baseaddr, priv->size);
+ kfree(priv);
+ return ret;
+}
+
+static int __exit imx_rtc_remove(struct platform_device *pdev)
+{
+ struct imx_priv *priv = platform_get_drvdata(pdev);
+
+ if (priv->sam_irq >= 0)
+ free_irq(priv->sam_irq, priv);
+ free_irq(priv->irq, priv);
+ rtc_device_unregister(priv->rtc);
+ iounmap(priv->ioaddr);
+ release_mem_region(priv->baseaddr, priv->size);
+ kfree(priv);
+ return 0;
+}
+
+static struct platform_driver imx_rtc_platform_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .owner = THIS_MODULE,
+ },
+ .remove = __exit_p(imx_rtc_remove),
+ .suspend = imx_rtc_suspend,
+ .resume = imx_rtc_resume,
+};
+
+static int __init imx_rtc_init(void)
+{
+ return platform_driver_probe(&imx_rtc_platform_driver, imx_rtc_probe);
+}
+
+static void __exit imx_rtc_exit(void)
+{
+ platform_driver_unregister(&imx_rtc_platform_driver);
+}
+
+module_init(imx_rtc_init);
+module_exit(imx_rtc_exit);
+
+MODULE_AUTHOR("Paulius Zaleckas <paulius.zaleckas@teltonika.lt>");
+MODULE_DESCRIPTION("i.MX SoC RTC driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
===================================================================
@@ -736,4 +736,14 @@ config RTC_DRV_MV
This driver can also be built as a module. If so, the module
will be called rtc-mv.
+config RTC_DRV_IMX
+ tristate "i.MX On-Chip RTC"
+ depends on ARCH_MX1
+ help
+ If you say yes here you will get support for the
+ i.MX On-Chip Real Time Clock.
+
+ This driver can also be built as a module. If so, the module
+ will be called rtc-imx.
+
endif # RTC_CLASS
===================================================================
@@ -38,6 +38,7 @@ obj-$(CONFIG_RTC_DRV_DS1742) += rtc-ds17
obj-$(CONFIG_RTC_DRV_DS3234) += rtc-ds3234.o
obj-$(CONFIG_RTC_DRV_EP93XX) += rtc-ep93xx.o
obj-$(CONFIG_RTC_DRV_FM3130) += rtc-fm3130.o
+obj-$(CONFIG_RTC_DRV_IMX) += rtc-imx.o
obj-$(CONFIG_RTC_DRV_ISL1208) += rtc-isl1208.o
obj-$(CONFIG_RTC_DRV_M41T80) += rtc-m41t80.o
obj-$(CONFIG_RTC_DRV_M41T94) += rtc-m41t94.o
===================================================================
@@ -0,0 +1,19 @@
+/*
+ * rtc.h - i.MX RTC driver header file
+ *
+ * Copyright (c) 2008, Paulius Zaleckas <paulius.zaleckas@teltonika.lt>
+ *
+ * This file is released under the GPLv2
+ */
+
+#ifndef __ASM_ARCH_RTC_H_
+#define __ASM_ARCH_RTC_H_
+
+struct imxrtc_platform_data {
+ void (*set_year)(struct device *, int);
+ int (*get_year)(struct device *);
+
+ int freq_is_32khz;
+};
+
+#endif /* __ASM_ARCH_RTC_H_ */