Merge branch 'merge' of git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / drivers / rtc / rtc-at91rm9200.c
index 0eab77b22340019693aa165c8987b65bd170977f..f296f3f7db9bb72574396182f3a8dfd115e16786 100644 (file)
@@ -25,6 +25,7 @@
 #include <linux/rtc.h>
 #include <linux/bcd.h>
 #include <linux/interrupt.h>
+#include <linux/spinlock.h>
 #include <linux/ioctl.h>
 #include <linux/completion.h>
 #include <linux/io.h>
 
 #define AT91_RTC_EPOCH         1900UL  /* just like arch/arm/common/rtctime.c */
 
+struct at91_rtc_config {
+       bool use_shadow_imr;
+};
+
+static const struct at91_rtc_config *at91_rtc_config;
 static DECLARE_COMPLETION(at91_rtc_updated);
 static unsigned int at91_alarm_year = AT91_RTC_EPOCH;
 static void __iomem *at91_rtc_regs;
 static int irq;
+static DEFINE_SPINLOCK(at91_rtc_lock);
+static u32 at91_rtc_shadow_imr;
+
+static void at91_rtc_write_ier(u32 mask)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&at91_rtc_lock, flags);
+       at91_rtc_shadow_imr |= mask;
+       at91_rtc_write(AT91_RTC_IER, mask);
+       spin_unlock_irqrestore(&at91_rtc_lock, flags);
+}
+
+static void at91_rtc_write_idr(u32 mask)
+{
+       unsigned long flags;
+
+       spin_lock_irqsave(&at91_rtc_lock, flags);
+       at91_rtc_write(AT91_RTC_IDR, mask);
+       /*
+        * Register read back (of any RTC-register) needed to make sure
+        * IDR-register write has reached the peripheral before updating
+        * shadow mask.
+        *
+        * Note that there is still a possibility that the mask is updated
+        * before interrupts have actually been disabled in hardware. The only
+        * way to be certain would be to poll the IMR-register, which is is
+        * the very register we are trying to emulate. The register read back
+        * is a reasonable heuristic.
+        */
+       at91_rtc_read(AT91_RTC_SR);
+       at91_rtc_shadow_imr &= ~mask;
+       spin_unlock_irqrestore(&at91_rtc_lock, flags);
+}
+
+static u32 at91_rtc_read_imr(void)
+{
+       unsigned long flags;
+       u32 mask;
+
+       if (at91_rtc_config->use_shadow_imr) {
+               spin_lock_irqsave(&at91_rtc_lock, flags);
+               mask = at91_rtc_shadow_imr;
+               spin_unlock_irqrestore(&at91_rtc_lock, flags);
+       } else {
+               mask = at91_rtc_read(AT91_RTC_IMR);
+       }
+
+       return mask;
+}
 
 /*
  * Decode time/date into rtc_time structure
@@ -110,9 +166,9 @@ static int at91_rtc_settime(struct device *dev, struct rtc_time *tm)
        cr = at91_rtc_read(AT91_RTC_CR);
        at91_rtc_write(AT91_RTC_CR, cr | AT91_RTC_UPDCAL | AT91_RTC_UPDTIM);
 
-       at91_rtc_write(AT91_RTC_IER, AT91_RTC_ACKUPD);
+       at91_rtc_write_ier(AT91_RTC_ACKUPD);
        wait_for_completion(&at91_rtc_updated); /* wait for ACKUPD interrupt */
-       at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD);
+       at91_rtc_write_idr(AT91_RTC_ACKUPD);
 
        at91_rtc_write(AT91_RTC_TIMR,
                          bin2bcd(tm->tm_sec) << 0
@@ -144,7 +200,7 @@ static int at91_rtc_readalarm(struct device *dev, struct rtc_wkalrm *alrm)
        tm->tm_yday = rtc_year_days(tm->tm_mday, tm->tm_mon, tm->tm_year);
        tm->tm_year = at91_alarm_year - 1900;
 
-       alrm->enabled = (at91_rtc_read(AT91_RTC_IMR) & AT91_RTC_ALARM)
+       alrm->enabled = (at91_rtc_read_imr() & AT91_RTC_ALARM)
                        ? 1 : 0;
 
        dev_dbg(dev, "%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __func__,
@@ -169,7 +225,7 @@ static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
        tm.tm_min = alrm->time.tm_min;
        tm.tm_sec = alrm->time.tm_sec;
 
-       at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ALARM);
+       at91_rtc_write_idr(AT91_RTC_ALARM);
        at91_rtc_write(AT91_RTC_TIMALR,
                  bin2bcd(tm.tm_sec) << 0
                | bin2bcd(tm.tm_min) << 8
@@ -182,7 +238,7 @@ static int at91_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm)
 
        if (alrm->enabled) {
                at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM);
-               at91_rtc_write(AT91_RTC_IER, AT91_RTC_ALARM);
+               at91_rtc_write_ier(AT91_RTC_ALARM);
        }
 
        dev_dbg(dev, "%s(): %4d-%02d-%02d %02d:%02d:%02d\n", __func__,
@@ -198,9 +254,9 @@ static int at91_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
 
        if (enabled) {
                at91_rtc_write(AT91_RTC_SCCR, AT91_RTC_ALARM);
-               at91_rtc_write(AT91_RTC_IER, AT91_RTC_ALARM);
+               at91_rtc_write_ier(AT91_RTC_ALARM);
        } else
-               at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ALARM);
+               at91_rtc_write_idr(AT91_RTC_ALARM);
 
        return 0;
 }
@@ -209,7 +265,7 @@ static int at91_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled)
  */
 static int at91_rtc_proc(struct device *dev, struct seq_file *seq)
 {
-       unsigned long imr = at91_rtc_read(AT91_RTC_IMR);
+       unsigned long imr = at91_rtc_read_imr();
 
        seq_printf(seq, "update_IRQ\t: %s\n",
                        (imr & AT91_RTC_ACKUPD) ? "yes" : "no");
@@ -229,7 +285,7 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
        unsigned int rtsr;
        unsigned long events = 0;
 
-       rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read(AT91_RTC_IMR);
+       rtsr = at91_rtc_read(AT91_RTC_SR) & at91_rtc_read_imr();
        if (rtsr) {             /* this interrupt is shared!  Is it ours? */
                if (rtsr & AT91_RTC_ALARM)
                        events |= (RTC_AF | RTC_IRQF);
@@ -250,6 +306,43 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
        return IRQ_NONE;                /* not handled */
 }
 
+static const struct at91_rtc_config at91rm9200_config = {
+};
+
+static const struct at91_rtc_config at91sam9x5_config = {
+       .use_shadow_imr = true,
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id at91_rtc_dt_ids[] = {
+       {
+               .compatible = "atmel,at91rm9200-rtc",
+               .data = &at91rm9200_config,
+       }, {
+               .compatible = "atmel,at91sam9x5-rtc",
+               .data = &at91sam9x5_config,
+       }, {
+               /* sentinel */
+       }
+};
+MODULE_DEVICE_TABLE(of, at91_rtc_dt_ids);
+#endif
+
+static const struct at91_rtc_config *
+at91_rtc_get_config(struct platform_device *pdev)
+{
+       const struct of_device_id *match;
+
+       if (pdev->dev.of_node) {
+               match = of_match_node(at91_rtc_dt_ids, pdev->dev.of_node);
+               if (!match)
+                       return NULL;
+               return (const struct at91_rtc_config *)match->data;
+       }
+
+       return &at91rm9200_config;
+}
+
 static const struct rtc_class_ops at91_rtc_ops = {
        .read_time      = at91_rtc_readtime,
        .set_time       = at91_rtc_settime,
@@ -268,6 +361,10 @@ static int __init at91_rtc_probe(struct platform_device *pdev)
        struct resource *regs;
        int ret = 0;
 
+       at91_rtc_config = at91_rtc_get_config(pdev);
+       if (!at91_rtc_config)
+               return -ENODEV;
+
        regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!regs) {
                dev_err(&pdev->dev, "no mmio resource defined\n");
@@ -290,7 +387,7 @@ static int __init at91_rtc_probe(struct platform_device *pdev)
        at91_rtc_write(AT91_RTC_MR, 0);         /* 24 hour mode */
 
        /* Disable all interrupts */
-       at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM |
+       at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM |
                                        AT91_RTC_SECEV | AT91_RTC_TIMEV |
                                        AT91_RTC_CALEV);
 
@@ -335,7 +432,7 @@ static int __exit at91_rtc_remove(struct platform_device *pdev)
        struct rtc_device *rtc = platform_get_drvdata(pdev);
 
        /* Disable all interrupts */
-       at91_rtc_write(AT91_RTC_IDR, AT91_RTC_ACKUPD | AT91_RTC_ALARM |
+       at91_rtc_write_idr(AT91_RTC_ACKUPD | AT91_RTC_ALARM |
                                        AT91_RTC_SECEV | AT91_RTC_TIMEV |
                                        AT91_RTC_CALEV);
        free_irq(irq, pdev);
@@ -358,13 +455,13 @@ static int at91_rtc_suspend(struct device *dev)
        /* this IRQ is shared with DBGU and other hardware which isn't
         * necessarily doing PM like we are...
         */
-       at91_rtc_imr = at91_rtc_read(AT91_RTC_IMR)
+       at91_rtc_imr = at91_rtc_read_imr()
                        & (AT91_RTC_ALARM|AT91_RTC_SECEV);
        if (at91_rtc_imr) {
                if (device_may_wakeup(dev))
                        enable_irq_wake(irq);
                else
-                       at91_rtc_write(AT91_RTC_IDR, at91_rtc_imr);
+                       at91_rtc_write_idr(at91_rtc_imr);
        }
        return 0;
 }
@@ -375,7 +472,7 @@ static int at91_rtc_resume(struct device *dev)
                if (device_may_wakeup(dev))
                        disable_irq_wake(irq);
                else
-                       at91_rtc_write(AT91_RTC_IER, at91_rtc_imr);
+                       at91_rtc_write_ier(at91_rtc_imr);
        }
        return 0;
 }
@@ -383,12 +480,6 @@ static int at91_rtc_resume(struct device *dev)
 
 static SIMPLE_DEV_PM_OPS(at91_rtc_pm_ops, at91_rtc_suspend, at91_rtc_resume);
 
-static const struct of_device_id at91_rtc_dt_ids[] = {
-       { .compatible = "atmel,at91rm9200-rtc" },
-       { /* sentinel */ }
-};
-MODULE_DEVICE_TABLE(of, at91_rtc_dt_ids);
-
 static struct platform_driver at91_rtc_driver = {
        .remove         = __exit_p(at91_rtc_remove),
        .driver         = {