Author: ian
Date: Thu May 26 22:34:25 2016
New Revision: 300786
URL: https://svnweb.freebsd.org/changeset/base/300786

Log:
  Add support for triggering interrupts on both rising and falling edges.
  Also, EOI a gpio interrupt in the post_ithread routine before re-enabling.

Modified:
  head/sys/arm/freescale/imx/imx_gpio.c

Modified: head/sys/arm/freescale/imx/imx_gpio.c
==============================================================================
--- head/sys/arm/freescale/imx/imx_gpio.c       Thu May 26 22:13:40 2016        
(r300785)
+++ head/sys/arm/freescale/imx/imx_gpio.c       Thu May 26 22:34:25 2016        
(r300786)
@@ -84,6 +84,7 @@ __FBSDID("$FreeBSD$");
 #define                GPIO_ICR_COND_HIGH      1
 #define                GPIO_ICR_COND_RISE      2
 #define                GPIO_ICR_COND_FALL      3
+#define                GPIO_ICR_COND_MASK      0x3
 #define        IMX_GPIO_IMR_REG        0x014 /* Interrupt Mask Register */
 #define        IMX_GPIO_ISR_REG        0x018 /* Interrupt Status Register */
 #define        IMX_GPIO_EDGE_REG       0x01C /* Edge Detect Register */
@@ -91,7 +92,7 @@ __FBSDID("$FreeBSD$");
 #ifdef INTRNG
 #define        DEFAULT_CAPS    (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \
     GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | GPIO_INTR_EDGE_RISING | \
-    GPIO_INTR_EDGE_FALLING )
+    GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH)
 #else
 #define        DEFAULT_CAPS    (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)
 #endif
@@ -177,7 +178,8 @@ gpio_pic_map_fdt(struct imx51_gpio_softc
         *    2 = high-to-low edge triggered.
         *    4 = active high level-sensitive.
         *    8 = active low level-sensitive.
-         * We can do any single one of these modes, but nothing in combo.
+        * We can do any single one of these modes, and also edge low+high
+        * (i.e., trigger on both edges); other combinations are not supported.
         */
 
        if (daf->ncells != 2) {
@@ -197,6 +199,9 @@ gpio_pic_map_fdt(struct imx51_gpio_softc
        case 2:
                mode = GPIO_INTR_EDGE_FALLING;
                break;
+       case 3:
+               mode = GPIO_INTR_EDGE_BOTH;
+               break;
        case 4:
                mode = GPIO_INTR_LEVEL_HIGH;
                break;
@@ -219,7 +224,6 @@ gpio_pic_map_gpio(struct imx51_gpio_soft
     u_int *irqp, uint32_t *modep)
 {
        u_int irq;
-       uint32_t mode;
 
        irq = dag->gpio_pin_num;
        if (irq >= sc->gpio_npins) {
@@ -227,17 +231,22 @@ gpio_pic_map_gpio(struct imx51_gpio_soft
                return (EINVAL);
        }
 
-       mode = dag->gpio_intr_mode;
-       if (mode != GPIO_INTR_LEVEL_LOW && mode != GPIO_INTR_LEVEL_HIGH &&
-           mode != GPIO_INTR_EDGE_RISING && mode != GPIO_INTR_EDGE_FALLING) {
+       switch (dag->gpio_intr_mode) {
+       case GPIO_INTR_LEVEL_LOW:
+       case GPIO_INTR_LEVEL_HIGH:
+       case GPIO_INTR_EDGE_RISING:
+       case GPIO_INTR_EDGE_FALLING:
+       case GPIO_INTR_EDGE_BOTH:
+               break;
+       default:
                device_printf(sc->dev, "Unsupported interrupt mode 0x%8x\n",
-                   mode);
+                   dag->gpio_intr_mode);
                return (EINVAL);
        }
 
        *irqp = irq;
        if (modep != NULL)
-               *modep = mode;
+               *modep = dag->gpio_intr_mode;
        return (0);
 }
 
@@ -300,8 +309,8 @@ gpio_pic_setup_intr(device_t dev, struct
 {
        struct imx51_gpio_softc *sc;
        struct gpio_irqsrc *gi;
-       int error, icfg;
-       u_int irq, reg, shift, wrk;
+       int error;
+       u_int icfg, irq, reg, shift, wrk;
        uint32_t mode;
 
        if (data == NULL)
@@ -320,39 +329,52 @@ gpio_pic_setup_intr(device_t dev, struct
        /* Compare config if this is not first setup. */
        if (isrc->isrc_handlers != 0)
                return (gi->gi_mode == mode ? 0 : EINVAL);
-
        gi->gi_mode = mode;
-       switch (mode) {
-       case GPIO_INTR_LEVEL_LOW:
-               icfg = GPIO_ICR_COND_LOW;
-               break;
-       case GPIO_INTR_LEVEL_HIGH:
-               icfg = GPIO_ICR_COND_HIGH;
-               break;
-       case GPIO_INTR_EDGE_RISING:
-               icfg = GPIO_ICR_COND_RISE;
-               break;
-       case GPIO_INTR_EDGE_FALLING:
-               icfg = GPIO_ICR_COND_FALL;
-               break;
-       }
 
-       if (irq < 16) {
-               reg = IMX_GPIO_ICR1_REG;
-               shift = 2 * irq;
+       /*
+        * To interrupt on both edges we have to use the EDGE register.  The
+        * manual says it only exists for backwards compatibilty with older imx
+        * chips, but it's also the only way to configure interrupting on both
+        * edges.  If the EDGE bit is on, the corresponding ICRn bit is ignored.
+        */
+       mtx_lock_spin(&sc->sc_mtx);
+       if (mode == GPIO_INTR_EDGE_BOTH) {
+               SET4(sc, IMX_GPIO_EDGE_REG, (1u << irq));
        } else {
-               reg = IMX_GPIO_ICR2_REG;
-               shift = 2 * (irq - 16);
+               CLEAR4(sc, IMX_GPIO_EDGE_REG, (1u << irq));
+               switch (mode) {
+               default: 
+                       /* silence warnings; default can't actually happen. */
+                       /* FALLTHROUGH */
+               case GPIO_INTR_LEVEL_LOW:
+                       icfg = GPIO_ICR_COND_LOW;
+                       break;
+               case GPIO_INTR_LEVEL_HIGH:
+                       icfg = GPIO_ICR_COND_HIGH;
+                       break;
+               case GPIO_INTR_EDGE_RISING:
+                       icfg = GPIO_ICR_COND_RISE;
+                       break;
+               case GPIO_INTR_EDGE_FALLING:
+                       icfg = GPIO_ICR_COND_FALL;
+                       break;
+               }
+               if (irq < 16) {
+                       reg = IMX_GPIO_ICR1_REG;
+                       shift = 2 * irq;
+               } else {
+                       reg = IMX_GPIO_ICR2_REG;
+                       shift = 2 * (irq - 16);
+               }
+               wrk = READ4(sc, reg);
+               wrk &= ~(GPIO_ICR_COND_MASK << shift);
+               wrk |= icfg << shift;
+               WRITE4(sc, reg, wrk);
        }
-
-       mtx_lock_spin(&sc->sc_mtx);
-       CLEAR4(sc, IMX_GPIO_IMR_REG, (1U << irq));
-       WRITE4(sc, IMX_GPIO_ISR_REG, (1U << irq));
-       wrk = READ4(sc, reg);
-       wrk &= ~(0x03 << shift);
-       wrk |= icfg << shift;
-       WRITE4(sc, reg, wrk);
+       WRITE4(sc, IMX_GPIO_ISR_REG, (1u << irq));
+       SET4(sc, IMX_GPIO_IMR_REG, (1u << irq));
        mtx_unlock_spin(&sc->sc_mtx);
+
        return (0);
 }
 
@@ -407,15 +429,21 @@ gpio_pic_post_filter(device_t dev, struc
 static void
 gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
 {
+       struct imx51_gpio_softc *sc;
+       u_int irq;
+
+       sc = device_get_softc(dev);
+       irq = ((struct gpio_irqsrc *)isrc)->gi_irq;
 
        arm_irq_memory_barrier(0);
+       /* EOI.  W1C reg so no r-m-w, no locking needed. */
+       WRITE4(sc, IMX_GPIO_ISR_REG, (1U << irq));
        gpio_pic_enable_intr(dev, isrc);
 }
 
 static void
 gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
 {
-
        gpio_pic_disable_intr(dev, isrc);
 }
 
_______________________________________________
svn-src-head@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to