Implement interrupt controller functionality in the i.MX6 GPIO
authorpatrick <patrick@openbsd.org>
Sat, 6 Aug 2016 18:12:13 +0000 (18:12 +0000)
committerpatrick <patrick@openbsd.org>
Sat, 6 Aug 2016 18:12:13 +0000 (18:12 +0000)
driver.  This allows us to use to hook up the Ethernet interrupt
on the Nitrogen6x, SabreLite and WandBoard like it's described
in the device tree.

ok kettenis@

sys/arch/armv7/imx/imxgpio.c
sys/arch/armv7/imx/imxgpiovar.h

index 7ace8f5..995a4e2 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: imxgpio.c,v 1.10 2016/07/27 11:45:02 patrick Exp $ */
+/* $OpenBSD: imxgpio.c,v 1.11 2016/08/06 18:12:13 patrick Exp $ */
 /*
  * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org>
  * Copyright (c) 2012-2013 Patrick Wildt <patrick@blueri.se>
@@ -53,21 +53,25 @@ struct intrhand {
        void *ih_arg;                   /* arg for handler */
        int ih_ipl;                     /* IPL_* */
        int ih_irq;                     /* IRQ number */
-       int ih_gpio;                    /* gpio pin */
+       int ih_level;                   /* GPIO level */
        struct evcount  ih_count;
        char *ih_name;
+       void *ih_sc;
 };
 
 struct imxgpio_softc {
        struct device           sc_dev;
        bus_space_tag_t         sc_iot;
        bus_space_handle_t      sc_ioh;
+       int                     sc_node;
+
        void                    *sc_ih_h;
        void                    *sc_ih_l;
-       int                     sc_max_il;
-       int                     sc_min_il;
+       int                     sc_ipl;
        int                     sc_irq;
        struct intrhand         *sc_handlers[GPIO_NUM_PINS];
+       struct interrupt_controller sc_ic;
+
        unsigned int (*sc_get_bit)(struct imxgpio_softc *sc,
            unsigned int gpio);
        void (*sc_set_bit)(struct imxgpio_softc *sc,
@@ -89,6 +93,12 @@ void imxgpio_config_pin(void *, uint32_t *, int);
 int imxgpio_get_pin(void *, uint32_t *);
 void imxgpio_set_pin(void *, uint32_t *, int);
 
+int imxgpio_intr(void *);
+void *imxgpio_intr_establish(void *, int *, int, int (*)(void *),
+    void *, char *);
+void imxgpio_intr_disestablish(void *);
+void imxgpio_recalc_ipl(struct imxgpio_softc *);
+
 unsigned int imxgpio_v6_get_bit(struct imxgpio_softc *, unsigned int);
 void imxgpio_v6_set_bit(struct imxgpio_softc *, unsigned int);
 void imxgpio_v6_clear_bit(struct imxgpio_softc *, unsigned int);
@@ -121,6 +131,7 @@ imxgpio_attach(struct device *parent, struct device *self, void *aux)
        if (faa->fa_nreg < 1)
                return;
 
+       sc->sc_node = faa->fa_node;
        sc->sc_iot = faa->fa_iot;
        if (bus_space_map(sc->sc_iot, faa->fa_reg[0].addr,
            faa->fa_reg[0].size, 0, &sc->sc_ioh))
@@ -138,9 +149,19 @@ imxgpio_attach(struct device *parent, struct device *self, void *aux)
        sc->sc_clear_bit = imxgpio_v6_clear_bit;
        sc->sc_set_dir = imxgpio_v6_set_dir;
 
+       sc->sc_ipl = IPL_NONE;
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, GPIO_IMR, 0);
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, GPIO_ISR, ~0);
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, GPIO_EDGE_SEL, 0);
+
+       sc->sc_ic.ic_node = faa->fa_node;
+       sc->sc_ic.ic_cookie = sc;
+       sc->sc_ic.ic_establish = imxgpio_intr_establish;
+       sc->sc_ic.ic_disestablish = imxgpio_intr_disestablish;
+       arm_intr_register_fdt(&sc->sc_ic);
+
        printf("\n");
 
-       /* XXX - IRQ */
        /* XXX - SYSCONFIG */
        /* XXX - CTRL */
        /* XXX - DEBOUNCE */
@@ -298,3 +319,183 @@ imxgpio_v6_get_dir(struct imxgpio_softc *sc, unsigned int gpio)
        splx(s);
        return val;
 }
+
+int
+imxgpio_intr(void *cookie)
+{
+       struct imxgpio_softc    *sc = (struct imxgpio_softc *)cookie;
+       struct intrhand         *ih;
+       uint32_t                 status, pending, mask;
+       int                      pin, s;
+
+       status = bus_space_read_4(sc->sc_iot, sc->sc_ioh, GPIO_ISR);
+       mask = bus_space_read_4(sc->sc_iot, sc->sc_ioh, GPIO_IMR);
+
+       status &= mask;
+       pending = status;
+
+       while (pending) {
+               pin = ffs(pending) - 1;
+
+               if ((ih = sc->sc_handlers[pin]) != NULL) {
+                       s = splraise(ih->ih_ipl);
+                       if (ih->ih_func(ih->ih_arg))
+                               ih->ih_count.ec_count++;
+                       splx(s);
+               }
+
+               pending &= ~(1 << pin);
+       }
+
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, GPIO_ISR, status);
+
+       return 1;
+}
+
+void *
+imxgpio_intr_establish(void *cookie, int *cells, int ipl,
+    int (*func)(void *), void *arg, char *name)
+{
+       struct imxgpio_softc    *sc = (struct imxgpio_softc *)cookie;
+       struct intrhand         *ih;
+       int                      psw, val, reg, shift;
+       int                      irqno = cells[0];
+       int                      level = cells[1];
+
+       if (irqno < 0 || irqno >= GPIO_NUM_PINS)
+               panic("%s: bogus irqnumber %d: %s", __func__,
+                    irqno, name);
+
+       if (sc->sc_handlers[irqno] != NULL)
+               panic("%s: irqnumber %d reused: %s", __func__,
+                    irqno, name);
+
+       ih = malloc(sizeof(*ih), M_DEVBUF, M_WAITOK);
+       ih->ih_func = func;
+       ih->ih_arg = arg;
+       ih->ih_ipl = ipl;
+       ih->ih_irq = irqno;
+       ih->ih_name = name;
+       ih->ih_level = level;
+       ih->ih_sc = sc;
+
+       psw = disable_interrupts(PSR_I);
+
+       sc->sc_handlers[irqno] = ih;
+
+       if (name != NULL)
+               evcount_attach(&ih->ih_count, name, &ih->ih_irq);
+
+#ifdef DEBUG_INTC
+       printf("%s: irq %d ipl %d [%s]\n", __func__, ih->ih_irq, ih->ih_ipl,
+           ih->ih_name);
+#endif
+
+       imxgpio_recalc_ipl(sc);
+
+       switch (level) {
+               case 1: /* rising */
+                       val = 2;
+                       break;
+               case 2: /* falling */
+                       val = 3;
+                       break;
+               case 4: /* high */
+                       val = 1;
+                       break;
+               case 8: /* low */
+                       val = 0;
+                       break;
+               default:
+                       panic("%s: unsupported trigger type", __func__);
+       }
+
+       if (irqno < 16) {
+               reg = GPIO_ICR1;
+               shift = irqno << 1;
+       } else {
+               reg = GPIO_ICR2;
+               shift = (irqno - 16) << 1;
+       }
+
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg,
+           bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg) & ~(0x3 << shift));
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, reg,
+           bus_space_read_4(sc->sc_iot, sc->sc_ioh, reg) | val << shift);
+
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, GPIO_IMR,
+           bus_space_read_4(sc->sc_iot, sc->sc_ioh, GPIO_IMR) | 1 << irqno);
+
+       restore_interrupts(psw);
+       return (ih);
+}
+
+void
+imxgpio_intr_disestablish(void *cookie)
+{
+       struct intrhand         *ih = cookie;
+       struct imxgpio_softc    *sc = ih->ih_sc;
+       uint32_t                 mask;
+       int                      psw;
+
+       psw = disable_interrupts(PSR_I);
+
+#ifdef DEBUG_INTC
+       printf("%s: irq %d ipl %d [%s]\n", __func__, ih->ih_irq, ih->ih_ipl,
+           ih->ih_name);
+#endif
+
+       mask = bus_space_read_4(sc->sc_iot, sc->sc_ioh, GPIO_IMR);
+       mask &= ~(1 << ih->ih_irq);
+       bus_space_write_4(sc->sc_iot, sc->sc_ioh, GPIO_IMR, mask);
+
+       sc->sc_handlers[ih->ih_irq] = NULL;
+       if (ih->ih_name != NULL)
+               evcount_detach(&ih->ih_count);
+       free(ih, M_DEVBUF, sizeof(*ih));
+
+       imxgpio_recalc_ipl(sc);
+
+       restore_interrupts(psw);
+}
+
+void
+imxgpio_recalc_ipl(struct imxgpio_softc *sc)
+{
+       struct intrhand         *ih;
+       int                      pin;
+       int                      max = IPL_NONE;
+       int                      min = IPL_HIGH;
+
+       for (pin = 0; pin < GPIO_NUM_PINS; pin++) {
+               ih = sc->sc_handlers[pin];
+               if (ih == NULL)
+                       continue;
+
+               if (ih->ih_ipl > max)
+                       max = ih->ih_ipl;
+
+               if (ih->ih_ipl < min)
+                       min = ih->ih_ipl;
+       }
+
+       if (max == IPL_NONE)
+               min = IPL_NONE;
+
+       if (sc->sc_ipl != max) {
+               sc->sc_ipl = max;
+
+               if (sc->sc_ih_l != NULL)
+                       arm_intr_disestablish_fdt(sc->sc_ih_l);
+
+               if (sc->sc_ih_h != NULL)
+                       arm_intr_disestablish_fdt(sc->sc_ih_h);
+
+               if (sc->sc_ipl != IPL_NONE) {
+                       sc->sc_ih_l = arm_intr_establish_fdt_idx(sc->sc_node, 0,
+                           sc->sc_ipl, imxgpio_intr, sc, sc->sc_dev.dv_xname);
+                       sc->sc_ih_h = arm_intr_establish_fdt_idx(sc->sc_node, 1,
+                           sc->sc_ipl, imxgpio_intr, sc, sc->sc_dev.dv_xname);
+               }
+       }
+}
index 1451624..b9b59aa 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: imxgpiovar.h,v 1.1 2013/09/06 20:45:53 patrick Exp $ */
+/* $OpenBSD: imxgpiovar.h,v 1.2 2016/08/06 18:12:13 patrick Exp $ */
 /*
  * Copyright (c) 2007,2009 Dale Rahn <drahn@openbsd.org>
  * Copyright (c) 2012-2013 Patrick Wildt <patrick@blueri.se>
@@ -29,13 +29,4 @@ void imxgpio_set_bit(unsigned int gpio);
 void imxgpio_clear_bit(unsigned int gpio);
 void imxgpio_set_dir(unsigned int gpio, unsigned int dir);
 
-/* interrupts */
-void imxgpio_clear_intr(unsigned int gpio);
-void imxgpio_intr_mask(unsigned int gpio);
-void imxgpio_intr_unmask(unsigned int gpio);
-void imxgpio_intr_level(unsigned int gpio, unsigned int level);
-void *imxgpio_intr_establish(unsigned int gpio, int level, int spl,
-    int (*func)(void *), void *arg, char *name);
-void imxgpio_intr_disestablish(void *cookie);
-
 #endif /* IMXGPIOVAR_H */