plat-nomadik: make GPIO interrupts work with cpuidle ApSleep
authorRabin Vincent <rabin.vincent@stericsson.com>
Thu, 10 Feb 2011 06:15:58 +0000 (11:45 +0530)
committerLinus Walleij <linus.walleij@linaro.org>
Mon, 14 Mar 2011 13:05:17 +0000 (14:05 +0100)
Enable wakeups by default for any GPIO interrupts and in the suspend/resume
path narrow this down to only the the real wakeup interrupts. This approach is
based on the assumption that cpuidle ApSleep will be entered more often than
system suspend.

Signed-off-by: Rabin Vincent <rabin.vincent@stericsson.com>
Reviewed-by: Srinidhi Kasagar <srinidhi.kasagar@stericsson.com>
[Fixup for genirq changes to struct irq_data on 2.6.38]
Signed-off-by: Linus Walleij <linus.walleij@linaro.org>
arch/arm/plat-nomadik/gpio.c
arch/arm/plat-nomadik/include/plat/gpio.h

index 45b1cf95e378f9eb62183c6cd3a6ffb3d2485009..70620426ee550fb4ecb8300e1ad144fd7d429623 100644 (file)
@@ -50,6 +50,10 @@ struct nmk_gpio_chip {
        /* Keep track of configured edges */
        u32 edge_rising;
        u32 edge_falling;
+       u32 real_wake;
+       u32 rwimsc;
+       u32 fwimsc;
+       u32 slpm;
 };
 
 static struct nmk_gpio_chip *
@@ -534,8 +538,20 @@ static void __nmk_gpio_irq_modify(struct nmk_gpio_chip *nmk_chip,
        }
 }
 
-static int nmk_gpio_irq_modify(struct irq_data *d, enum nmk_gpio_irq_type which,
-                              bool enable)
+static void __nmk_gpio_set_wake(struct nmk_gpio_chip *nmk_chip,
+                               int gpio, bool on)
+{
+#ifdef CONFIG_ARCH_U8500
+       if (cpu_is_u8500v2()) {
+               __nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base,
+                                   on ? NMK_GPIO_SLPM_WAKEUP_ENABLE
+                                      : NMK_GPIO_SLPM_WAKEUP_DISABLE);
+       }
+#endif
+       __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, on);
+}
+
+static int nmk_gpio_irq_maskunmask(struct irq_data *d, bool enable)
 {
        int gpio;
        struct nmk_gpio_chip *nmk_chip;
@@ -548,45 +564,55 @@ static int nmk_gpio_irq_modify(struct irq_data *d, enum nmk_gpio_irq_type which,
        if (!nmk_chip)
                return -EINVAL;
 
-       spin_lock_irqsave(&nmk_chip->lock, flags);
-       __nmk_gpio_irq_modify(nmk_chip, gpio, which, enable);
-       spin_unlock_irqrestore(&nmk_chip->lock, flags);
+       spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
+       spin_lock(&nmk_chip->lock);
+
+       __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, enable);
+
+       if (!(nmk_chip->real_wake & bitmask))
+               __nmk_gpio_set_wake(nmk_chip, gpio, enable);
+
+       spin_unlock(&nmk_chip->lock);
+       spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
 
        return 0;
 }
 
 static void nmk_gpio_irq_mask(struct irq_data *d)
 {
-       nmk_gpio_irq_modify(d, NORMAL, false);
+       nmk_gpio_irq_maskunmask(d, false);
 }
 
 static void nmk_gpio_irq_unmask(struct irq_data *d)
 {
-       nmk_gpio_irq_modify(d, NORMAL, true);
+       nmk_gpio_irq_maskunmask(d, true);
 }
 
 static int nmk_gpio_irq_set_wake(struct irq_data *d, unsigned int on)
 {
+       struct irq_desc *desc = irq_to_desc(d->irq);
+       bool enabled = !(desc->status & IRQ_DISABLED);
        struct nmk_gpio_chip *nmk_chip;
        unsigned long flags;
+       u32 bitmask;
        int gpio;
 
        gpio = NOMADIK_IRQ_TO_GPIO(d->irq);
        nmk_chip = irq_data_get_irq_chip_data(d);
        if (!nmk_chip)
                return -EINVAL;
+       bitmask = nmk_gpio_get_bitmask(gpio);
 
        spin_lock_irqsave(&nmk_gpio_slpm_lock, flags);
        spin_lock(&nmk_chip->lock);
 
-#ifdef CONFIG_ARCH_U8500
-       if (cpu_is_u8500v2()) {
-               __nmk_gpio_set_slpm(nmk_chip, gpio - nmk_chip->chip.base,
-                                   on ? NMK_GPIO_SLPM_WAKEUP_ENABLE
-                                      : NMK_GPIO_SLPM_WAKEUP_DISABLE);
-       }
-#endif
-       __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, on);
+       if (!enabled)
+               __nmk_gpio_set_wake(nmk_chip, gpio, on);
+
+       if (on)
+               nmk_chip->real_wake |= bitmask;
+       else
+               nmk_chip->real_wake &= ~bitmask;
 
        spin_unlock(&nmk_chip->lock);
        spin_unlock_irqrestore(&nmk_gpio_slpm_lock, flags);
@@ -620,7 +646,7 @@ static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type)
        if (enabled)
                __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, false);
 
-       if (wake)
+       if (enabled || wake)
                __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, false);
 
        nmk_chip->edge_rising &= ~bitmask;
@@ -634,7 +660,7 @@ static int nmk_gpio_irq_set_type(struct irq_data *d, unsigned int type)
        if (enabled)
                __nmk_gpio_irq_modify(nmk_chip, gpio, NORMAL, true);
 
-       if (wake)
+       if (enabled || wake)
                __nmk_gpio_irq_modify(nmk_chip, gpio, WAKE, true);
 
        spin_unlock_irqrestore(&nmk_chip->lock, flags);
@@ -870,6 +896,60 @@ static struct gpio_chip nmk_gpio_template = {
        .can_sleep              = 0,
 };
 
+/*
+ * Called from the suspend/resume path to only keep the real wakeup interrupts
+ * (those that have had set_irq_wake() called on them) as wakeup interrupts,
+ * and not the rest of the interrupts which we needed to have as wakeups for
+ * cpuidle.
+ *
+ * PM ops are not used since this needs to be done at the end, after all the
+ * other drivers are done with their suspend callbacks.
+ */
+void nmk_gpio_wakeups_suspend(void)
+{
+       int i;
+
+       for (i = 0; i < NUM_BANKS; i++) {
+               struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
+
+               if (!chip)
+                       break;
+
+               chip->rwimsc = readl(chip->addr + NMK_GPIO_RWIMSC);
+               chip->fwimsc = readl(chip->addr + NMK_GPIO_FWIMSC);
+
+               writel(chip->rwimsc & chip->real_wake,
+                      chip->addr + NMK_GPIO_RWIMSC);
+               writel(chip->fwimsc & chip->real_wake,
+                      chip->addr + NMK_GPIO_FWIMSC);
+
+               if (cpu_is_u8500v2()) {
+                       chip->slpm = readl(chip->addr + NMK_GPIO_SLPC);
+
+                       /* 0 -> wakeup enable */
+                       writel(~chip->real_wake, chip->addr + NMK_GPIO_SLPC);
+               }
+       }
+}
+
+void nmk_gpio_wakeups_resume(void)
+{
+       int i;
+
+       for (i = 0; i < NUM_BANKS; i++) {
+               struct nmk_gpio_chip *chip = nmk_gpio_chips[i];
+
+               if (!chip)
+                       break;
+
+               writel(chip->rwimsc, chip->addr + NMK_GPIO_RWIMSC);
+               writel(chip->fwimsc, chip->addr + NMK_GPIO_FWIMSC);
+
+               if (cpu_is_u8500v2())
+                       writel(chip->slpm, chip->addr + NMK_GPIO_SLPC);
+       }
+}
+
 static int __devinit nmk_gpio_probe(struct platform_device *dev)
 {
        struct nmk_gpio_platform_data *pdata = dev->dev.platform_data;
index e3a4837e86f49b0124de91a7d87cff2dd96697c6..1b9f6f0843d1227cf67f896511ac43467f62d617 100644 (file)
@@ -75,6 +75,9 @@ extern int nmk_gpio_set_pull(int gpio, enum nmk_gpio_pull pull);
 extern int nmk_gpio_set_mode(int gpio, int gpio_mode);
 extern int nmk_gpio_get_mode(int gpio);
 
+extern void nmk_gpio_wakeups_suspend(void);
+extern void nmk_gpio_wakeups_resume(void);
+
 /*
  * Platform data to register a block: only the initial gpio/irq number.
  */