]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/rtc/rtc-at91rm9200.c
Merge tag 'sunxi-dt-for-3.11-2' of git://github.com/mripard/linux into next/dt
[karo-tx-linux.git] / drivers / rtc / rtc-at91rm9200.c
index 9592d08f3b1100b9805eef87d2b23062d8868800..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>
@@ -43,6 +44,7 @@
 #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;
@@ -50,20 +52,55 @@ 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)
 {
-       return at91_rtc_read(AT91_RTC_IMR);
+       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;
 }
 
 /*
@@ -272,11 +309,18 @@ static irqreturn_t at91_rtc_interrupt(int irq, void *dev_id)
 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 */
        }