omap: gpio: Simultaneously requested rising and falling edge
authorCory Maccarrone <darkstar6262@gmail.com>
Fri, 8 Jan 2010 18:29:04 +0000 (10:29 -0800)
committerTony Lindgren <tony@atomide.com>
Fri, 8 Jan 2010 18:29:04 +0000 (10:29 -0800)
Some chips, namely any OMAP1 chips using METHOD_MPUIO,
OMAP15xx and OMAP7xx, cannot be setup to respond to on-chip GPIO
interrupts in both rising and falling edge directions -- they can
only respond to one direction or the other, depending on how the
ICR is configured.

Additionally, current code forces rising edge detection if both
flags are specified:

        if (trigger & IRQ_TYPE_EDGE_RISING)
                l |= 1 << gpio;
        else if (trigger & IRQ_TYPE_EDGE_FALLING)
                l &= ~(1 << gpio);
        else
                goto bad;

This change implements a toggle function that will modify the ICR
to flip the direction of interrupt for IRQs that are requested with
both rising and falling flags.  The toggle function is not called
for chips and GPIOs it does not apply to through the use of a flip_mask
that's added on a per-bank basis.  The mask is only set for those
GPIOs where a toggle is necessary.  Edge detection starts out the
same as above with FALLING mode first.

The toggle happens on EACH interrupt; without it, we have the
following sequence of actions on GPIO transition:

  ICR    GPIO               Result
  0x1    0 -> 1 (rising)    Interrupt
  0x1    1 -> 0 (falling)   No interrupt

  (set ICR to 0x0 manually)
  0x0    0 -> 1 (rising)    No interrupt
  0x0    1 -> 0 (falling)   Interrupt

That is, with the ICR set to 1 for a gpio, only rising edge interrupts
are caught, and with it set to 0, only falling edge interrupts are
caught.  If we add in the toggle, we get this:

  ICR    GPIO               Result
  0x1    0 -> 1 (rising)    Interrupt (ICR set to 0x0)
  0x0    1 -> 0 (falling)   Interrupt (ICR set to 0x1)
  0x1    0 -> 1 ...

so, both rising and falling are caught, per the request for both
(IRQ_TYPE_EDGE_RISING | IRQ_TYPE_EDGE_FALLING).

Signed-off-by: Cory Maccarrone <darkstar6262@gmail.com>
Acked-by: Kevin Hilman <khilman@deeprootsystems.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
arch/arm/plat-omap/gpio.c

index 04846811d0aaf83696e2b6c641785d902af7f1c5..f8dfa8d5e605a081efbe6268185cd4265903d968 100644 (file)
@@ -192,6 +192,7 @@ struct gpio_bank {
        u32 saved_risingdetect;
 #endif
        u32 level_mask;
+       u32 toggle_mask;
        spinlock_t lock;
        struct gpio_chip chip;
        struct clk *dbck;
@@ -749,6 +750,44 @@ static inline void set_24xx_gpio_triggering(struct gpio_bank *bank, int gpio,
 }
 #endif
 
+/*
+ * This only applies to chips that can't do both rising and falling edge
+ * detection at once.  For all other chips, this function is a noop.
+ */
+static void _toggle_gpio_edge_triggering(struct gpio_bank *bank, int gpio)
+{
+       void __iomem *reg = bank->base;
+       u32 l = 0;
+
+       switch (bank->method) {
+#ifdef CONFIG_ARCH_OMAP1
+       case METHOD_MPUIO:
+               reg += OMAP_MPUIO_GPIO_INT_EDGE;
+               break;
+#endif
+#ifdef CONFIG_ARCH_OMAP15XX
+       case METHOD_GPIO_1510:
+               reg += OMAP1510_GPIO_INT_CONTROL;
+               break;
+#endif
+#if defined(CONFIG_ARCH_OMAP730) || defined(CONFIG_ARCH_OMAP850)
+       case METHOD_GPIO_7XX:
+               reg += OMAP7XX_GPIO_INT_CONTROL;
+               break;
+#endif
+       default:
+               return;
+       }
+
+       l = __raw_readl(reg);
+       if ((l >> gpio) & 1)
+               l &= ~(1 << gpio);
+       else
+               l |= 1 << gpio;
+
+       __raw_writel(l, reg);
+}
+
 static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger)
 {
        void __iomem *reg = bank->base;
@@ -759,6 +798,8 @@ static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger)
        case METHOD_MPUIO:
                reg += OMAP_MPUIO_GPIO_INT_EDGE;
                l = __raw_readl(reg);
+               if (trigger & IRQ_TYPE_EDGE_BOTH)
+                       bank->toggle_mask |= 1 << gpio;
                if (trigger & IRQ_TYPE_EDGE_RISING)
                        l |= 1 << gpio;
                else if (trigger & IRQ_TYPE_EDGE_FALLING)
@@ -771,6 +812,8 @@ static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger)
        case METHOD_GPIO_1510:
                reg += OMAP1510_GPIO_INT_CONTROL;
                l = __raw_readl(reg);
+               if (trigger & IRQ_TYPE_EDGE_BOTH)
+                       bank->toggle_mask |= 1 << gpio;
                if (trigger & IRQ_TYPE_EDGE_RISING)
                        l |= 1 << gpio;
                else if (trigger & IRQ_TYPE_EDGE_FALLING)
@@ -803,6 +846,8 @@ static int _set_gpio_triggering(struct gpio_bank *bank, int gpio, int trigger)
        case METHOD_GPIO_7XX:
                reg += OMAP7XX_GPIO_INT_CONTROL;
                l = __raw_readl(reg);
+               if (trigger & IRQ_TYPE_EDGE_BOTH)
+                       bank->toggle_mask |= 1 << gpio;
                if (trigger & IRQ_TYPE_EDGE_RISING)
                        l |= 1 << gpio;
                else if (trigger & IRQ_TYPE_EDGE_FALLING)
@@ -1217,7 +1262,7 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
 {
        void __iomem *isr_reg = NULL;
        u32 isr;
-       unsigned int gpio_irq;
+       unsigned int gpio_irq, gpio_index;
        struct gpio_bank *bank;
        u32 retrigger = 0;
        int unmasked = 0;
@@ -1284,9 +1329,23 @@ static void gpio_irq_handler(unsigned int irq, struct irq_desc *desc)
 
                gpio_irq = bank->virtual_irq_start;
                for (; isr != 0; isr >>= 1, gpio_irq++) {
+                       gpio_index = get_gpio_index(irq_to_gpio(gpio_irq));
+
                        if (!(isr & 1))
                                continue;
 
+#ifdef CONFIG_ARCH_OMAP1
+                       /*
+                        * Some chips can't respond to both rising and falling
+                        * at the same time.  If this irq was requested with
+                        * both flags, we need to flip the ICR data for the IRQ
+                        * to respond to the IRQ for the opposite direction.
+                        * This will be indicated in the bank toggle_mask.
+                        */
+                       if (bank->toggle_mask & (1 << gpio_index))
+                               _toggle_gpio_edge_triggering(bank, gpio_index);
+#endif
+
                        generic_handle_irq(gpio_irq);
                }
        }