Author: loos
Date: Wed Feb  4 18:15:28 2015
New Revision: 278215
URL: https://svnweb.freebsd.org/changeset/base/278215

Log:
  Add GPIO interrupt support for BCM2835 (Raspberry pi).
  
  With this commit any of the GPIO pins can now be programmed to act as an
  interrupt source for GPIO devices (i.e. limited to devices directly
  attached to gpiobus - at least for now).
  
  Differential Revision:        https://reviews.freebsd.org/D1000

Modified:
  head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c

Modified: head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c
==============================================================================
--- head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c        Wed Feb  4 17:23:02 
2015        (r278214)
+++ head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c        Wed Feb  4 18:15:28 
2015        (r278215)
@@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/systm.h>
 #include <sys/bus.h>
 #include <sys/gpio.h>
+#include <sys/interrupt.h>
 #include <sys/kernel.h>
 #include <sys/lock.h>
 #include <sys/module.h>
@@ -57,6 +58,7 @@ __FBSDID("$FreeBSD$");
 
 #define        BCM_GPIO_IRQS           4
 #define        BCM_GPIO_PINS           54
+#define        BCM_GPIO_PINS_PER_BANK  32
 #define        BCM_GPIO_DEFAULT_CAPS   (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT |     
\
     GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)
 
@@ -81,12 +83,15 @@ struct bcm_gpio_softc {
        struct resource *       sc_res[BCM_GPIO_IRQS + 1];
        bus_space_tag_t         sc_bst;
        bus_space_handle_t      sc_bsh;
-       void *                  sc_intrhand;
+       void *                  sc_intrhand[BCM_GPIO_IRQS];
        int                     sc_gpio_npins;
        int                     sc_ro_npins;
        int                     sc_ro_pins[BCM_GPIO_PINS];
        struct gpio_pin         sc_gpio_pins[BCM_GPIO_PINS];
+       struct intr_event *     sc_events[BCM_GPIO_PINS];
        struct bcm_gpio_sysctl  sc_sysctl[BCM_GPIO_PINS];
+       enum intr_trigger       sc_irq_trigger[BCM_GPIO_PINS];
+       enum intr_polarity      sc_irq_polarity[BCM_GPIO_PINS];
 };
 
 enum bcm_gpio_pud {
@@ -95,21 +100,35 @@ enum bcm_gpio_pud {
        BCM_GPIO_PULLUP,
 };
 
-#define        BCM_GPIO_LOCK(_sc)      mtx_lock(&_sc->sc_mtx)
-#define        BCM_GPIO_UNLOCK(_sc)    mtx_unlock(&_sc->sc_mtx)
-#define        BCM_GPIO_LOCK_ASSERT(_sc)       mtx_assert(&_sc->sc_mtx, 
MA_OWNED)
-
-#define        BCM_GPIO_GPFSEL(_bank)  0x00 + _bank * 4
-#define        BCM_GPIO_GPSET(_bank)   0x1c + _bank * 4
-#define        BCM_GPIO_GPCLR(_bank)   0x28 + _bank * 4
-#define        BCM_GPIO_GPLEV(_bank)   0x34 + _bank * 4
-#define        BCM_GPIO_GPPUD(_bank)   0x94
-#define        BCM_GPIO_GPPUDCLK(_bank)        0x98 + _bank * 4
-
+#define        BCM_GPIO_LOCK(_sc)      mtx_lock_spin(&(_sc)->sc_mtx)
+#define        BCM_GPIO_UNLOCK(_sc)    mtx_unlock_spin(&(_sc)->sc_mtx)
+#define        BCM_GPIO_LOCK_ASSERT(_sc)       mtx_assert(&(_sc)->sc_mtx, 
MA_OWNED)
 #define        BCM_GPIO_WRITE(_sc, _off, _val)         \
-    bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val)
+    bus_space_write_4((_sc)->sc_bst, (_sc)->sc_bsh, _off, _val)
 #define        BCM_GPIO_READ(_sc, _off)                \
-    bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off)
+    bus_space_read_4((_sc)->sc_bst, (_sc)->sc_bsh, _off)
+#define        BCM_GPIO_CLEAR_BITS(_sc, _off, _bits)   \
+    BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) & ~(_bits))
+#define        BCM_GPIO_SET_BITS(_sc, _off, _bits)     \
+    BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) | _bits)
+#define        BCM_GPIO_BANK(a)        (a / BCM_GPIO_PINS_PER_BANK)
+#define        BCM_GPIO_MASK(a)        (1U << (a % BCM_GPIO_PINS_PER_BANK))
+
+#define        BCM_GPIO_GPFSEL(_bank)  (0x00 + _bank * 4)      /* Function 
Select */
+#define        BCM_GPIO_GPSET(_bank)   (0x1c + _bank * 4)      /* Pin Out Set 
*/
+#define        BCM_GPIO_GPCLR(_bank)   (0x28 + _bank * 4)      /* Pin Out 
Clear */
+#define        BCM_GPIO_GPLEV(_bank)   (0x34 + _bank * 4)      /* Pin Level */
+#define        BCM_GPIO_GPEDS(_bank)   (0x40 + _bank * 4)      /* Event Status 
*/
+#define        BCM_GPIO_GPREN(_bank)   (0x4c + _bank * 4)      /* Rising Edge 
irq */
+#define        BCM_GPIO_GPFEN(_bank)   (0x58 + _bank * 4)      /* Falling Edge 
irq */
+#define        BCM_GPIO_GPHEN(_bank)   (0x64 + _bank * 4)      /* High Level 
irq */
+#define        BCM_GPIO_GPLEN(_bank)   (0x70 + _bank * 4)      /* Low Level 
irq */
+#define        BCM_GPIO_GPAREN(_bank)  (0x7c + _bank * 4)      /* Async Rising 
Edge */
+#define        BCM_GPIO_GPAFEN(_bank)  (0x88 + _bank * 4)      /* Async 
Falling Egde */
+#define        BCM_GPIO_GPPUD(_bank)   (0x94)                  /* Pin Pull 
up/down */
+#define        BCM_GPIO_GPPUDCLK(_bank) (0x98 + _bank * 4)     /* Pin Pull up 
clock */
+
+static struct bcm_gpio_softc *bcm_gpio_sc = NULL;
 
 static int
 bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin)
@@ -657,6 +676,40 @@ bcm_gpio_get_reserved_pins(struct bcm_gp
 }
 
 static int
+bcm_gpio_intr(void *arg)
+{
+       int bank_last, irq;
+       struct bcm_gpio_softc *sc;
+       struct intr_event *event;
+       uint32_t bank, mask, reg;
+
+       sc = (struct bcm_gpio_softc *)arg;
+       reg = 0;
+       bank_last = -1;
+       for (irq = 0; irq < BCM_GPIO_PINS; irq++) {
+               bank = BCM_GPIO_BANK(irq);
+               mask = BCM_GPIO_MASK(irq);
+               if (bank != bank_last) {
+                       reg = BCM_GPIO_READ(sc, BCM_GPIO_GPEDS(bank));
+                       bank_last = bank;
+               }
+               if (reg & mask) {
+                       event = sc->sc_events[irq];
+                       if (event != NULL && !TAILQ_EMPTY(&event->ie_handlers))
+                               intr_event_handle(event, NULL);
+                       else {
+                               device_printf(sc->sc_dev, "Stray IRQ %d\n",
+                                   irq);
+                       }
+                       /* Clear the Status bit by writing '1' to it. */
+                       BCM_GPIO_WRITE(sc, BCM_GPIO_GPEDS(bank), mask);
+               }
+       }
+
+       return (FILTER_HANDLED);
+}
+
+static int
 bcm_gpio_probe(device_t dev)
 {
 
@@ -671,6 +724,39 @@ bcm_gpio_probe(device_t dev)
 }
 
 static int
+bcm_gpio_intr_attach(device_t dev)
+{
+       struct bcm_gpio_softc *sc;
+       int i;
+
+       sc = device_get_softc(dev);
+       for (i = 0; i < BCM_GPIO_IRQS; i++) {
+               if (bus_setup_intr(dev, sc->sc_res[i + 1],
+                   INTR_TYPE_MISC | INTR_MPSAFE, bcm_gpio_intr,
+                   NULL, sc, &sc->sc_intrhand[i]) != 0) {
+                       return (-1);
+               }
+       }
+
+       return (0);
+}
+
+static void
+bcm_gpio_intr_detach(device_t dev)
+{
+       struct bcm_gpio_softc *sc;
+       int i;
+
+       sc = device_get_softc(dev);
+       for (i = 0; i < BCM_GPIO_IRQS; i++) {
+               if (sc->sc_intrhand[i]) {
+                       bus_teardown_intr(dev, sc->sc_res[i + 1],
+                           sc->sc_intrhand[i]);
+               }
+       }
+}
+
+static int
 bcm_gpio_attach(device_t dev)
 {
        int i, j;
@@ -678,30 +764,34 @@ bcm_gpio_attach(device_t dev)
        struct bcm_gpio_softc *sc;
        uint32_t func;
 
-       sc = device_get_softc(dev);
-       sc->sc_dev = dev;
-       mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_DEF);
+       if (bcm_gpio_sc != NULL)
+               return (ENXIO);
+
+       bcm_gpio_sc = sc = device_get_softc(dev);
+       sc->sc_dev = dev;
+       mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_SPIN);
        if (bus_alloc_resources(dev, bcm_gpio_res_spec, sc->sc_res) != 0) {
                device_printf(dev, "cannot allocate resources\n");
                goto fail;
        }
        sc->sc_bst = rman_get_bustag(sc->sc_res[0]);
        sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]);
-
+       /* Setup the GPIO interrupt handler. */
+       if (bcm_gpio_intr_attach(dev)) {
+               device_printf(dev, "unable to setup the gpio irq handler\n");
+               goto fail;
+       }
        /* Find our node. */
        gpio = ofw_bus_get_node(sc->sc_dev);
-
        if (!OF_hasprop(gpio, "gpio-controller"))
                /* Node is not a GPIO controller. */
                goto fail;
-
        /*
         * Find the read-only pins.  These are pins we never touch or bad
         * things could happen.
         */
        if (bcm_gpio_get_reserved_pins(sc) == -1)
                goto fail;
-
        /* Initialize the software controlled pins. */
        for (i = 0, j = 0; j < BCM_GPIO_PINS; j++) {
                snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME,
@@ -710,6 +800,9 @@ bcm_gpio_attach(device_t dev)
                sc->sc_gpio_pins[i].gp_pin = j;
                sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS;
                sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func);
+               /* The default is active-low interrupts. */
+               sc->sc_irq_trigger[i] = INTR_TRIGGER_LEVEL;
+               sc->sc_irq_polarity[i] = INTR_POLARITY_LOW;
                i++;
        }
        sc->sc_gpio_npins = i;
@@ -721,6 +814,7 @@ bcm_gpio_attach(device_t dev)
        return (0);
 
 fail:
+       bcm_gpio_intr_detach(dev);
        bus_release_resources(dev, bcm_gpio_res_spec, sc->sc_res);
        mtx_destroy(&sc->sc_mtx);
 
@@ -734,6 +828,177 @@ bcm_gpio_detach(device_t dev)
        return (EBUSY);
 }
 
+static uint32_t
+bcm_gpio_intr_reg(struct bcm_gpio_softc *sc, unsigned int irq, uint32_t bank)
+{
+
+       if (irq > BCM_GPIO_PINS)
+               return (0);
+       if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_LEVEL) {
+               if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW)
+                       return (BCM_GPIO_GPLEN(bank));
+               else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH)
+                       return (BCM_GPIO_GPHEN(bank));
+       } else if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_EDGE) {
+               if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW)
+                       return (BCM_GPIO_GPFEN(bank));
+               else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH)
+                       return (BCM_GPIO_GPREN(bank));
+       }
+
+       return (0);
+}
+
+static void
+bcm_gpio_mask_irq(void *source)
+{
+       uint32_t bank, mask, reg;
+       unsigned int irq;
+
+       irq = (unsigned int)source;
+       if (irq > BCM_GPIO_PINS)
+               return;
+       if (bcm_gpio_pin_is_ro(bcm_gpio_sc, irq))
+               return;
+       bank = BCM_GPIO_BANK(irq);
+       mask = BCM_GPIO_MASK(irq);
+       BCM_GPIO_LOCK(bcm_gpio_sc);
+       reg = bcm_gpio_intr_reg(bcm_gpio_sc, irq, bank);
+       if (reg != 0)
+               BCM_GPIO_CLEAR_BITS(bcm_gpio_sc, reg, mask);
+       BCM_GPIO_UNLOCK(bcm_gpio_sc);
+}
+
+static void
+bcm_gpio_unmask_irq(void *source)
+{
+       uint32_t bank, mask, reg;
+       unsigned int irq;
+
+       irq = (unsigned int)source;
+       if (irq > BCM_GPIO_PINS)
+               return;
+       if (bcm_gpio_pin_is_ro(bcm_gpio_sc, irq))
+               return;
+       bank = BCM_GPIO_BANK(irq);
+       mask = BCM_GPIO_MASK(irq);
+       BCM_GPIO_LOCK(bcm_gpio_sc);
+       reg = bcm_gpio_intr_reg(bcm_gpio_sc, irq, bank);
+       if (reg != 0)
+               BCM_GPIO_SET_BITS(bcm_gpio_sc, reg, mask);
+       BCM_GPIO_UNLOCK(bcm_gpio_sc);
+}
+
+static int
+bcm_gpio_activate_resource(device_t bus, device_t child, int type, int rid,
+       struct resource *res)
+{
+       int pin;
+
+       if (type != SYS_RES_IRQ)
+               return (ENXIO);
+       /* Unmask the interrupt. */
+       pin = rman_get_start(res);
+       bcm_gpio_unmask_irq((void *)pin);
+
+       return (0);
+}
+
+static int
+bcm_gpio_deactivate_resource(device_t bus, device_t child, int type, int rid,
+       struct resource *res)
+{
+       int pin;
+
+       if (type != SYS_RES_IRQ)
+               return (ENXIO);
+       /* Mask the interrupt. */
+       pin = rman_get_start(res);
+       bcm_gpio_mask_irq((void *)pin);
+
+       return (0);
+}
+
+static int
+bcm_gpio_config_intr(device_t dev, int irq, enum intr_trigger trig,
+       enum intr_polarity pol)
+{
+       int bank;
+       struct bcm_gpio_softc *sc;
+       uint32_t mask, oldreg, reg;
+
+       if (irq > BCM_GPIO_PINS)
+               return (EINVAL);
+       /* There is no standard trigger or polarity. */
+       if (trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM)
+               return (EINVAL);
+       sc = device_get_softc(dev);
+       if (bcm_gpio_pin_is_ro(sc, irq))
+               return (EINVAL);
+       bank = BCM_GPIO_BANK(irq);
+       mask = BCM_GPIO_MASK(irq);
+       BCM_GPIO_LOCK(sc);
+       oldreg = bcm_gpio_intr_reg(sc, irq, bank);
+       sc->sc_irq_trigger[irq] = trig;
+       sc->sc_irq_polarity[irq] = pol;
+       reg = bcm_gpio_intr_reg(sc, irq, bank);
+       if (reg != 0)
+               BCM_GPIO_SET_BITS(sc, reg, mask);
+       if (reg != oldreg && oldreg != 0)
+               BCM_GPIO_CLEAR_BITS(sc, oldreg, mask);
+       BCM_GPIO_UNLOCK(sc);
+
+       return (0);
+}
+
+static int
+bcm_gpio_setup_intr(device_t bus, device_t child, struct resource *ires,
+       int flags, driver_filter_t *filt, driver_intr_t *handler,
+       void *arg, void **cookiep)
+{
+       struct bcm_gpio_softc *sc;
+       struct intr_event *event;
+       int pin, error;
+
+       sc = device_get_softc(bus);
+       pin = rman_get_start(ires);
+       if (pin > BCM_GPIO_PINS)
+               panic("%s: bad pin %d", __func__, pin);
+       event = sc->sc_events[pin];
+       if (event == NULL) {
+               error = intr_event_create(&event, (void *)pin, 0, pin, 
+                   bcm_gpio_mask_irq, bcm_gpio_unmask_irq, NULL, NULL,
+                   "gpio%d pin%d:", device_get_unit(bus), pin);
+               if (error != 0)
+                       return (error);
+               sc->sc_events[pin] = event;
+       }
+       intr_event_add_handler(event, device_get_nameunit(child), filt,
+           handler, arg, intr_priority(flags), flags, cookiep);
+
+       return (0);
+}
+
+static int
+bcm_gpio_teardown_intr(device_t dev, device_t child, struct resource *ires,
+       void *cookie)
+{
+       struct bcm_gpio_softc *sc;
+       int pin, err;
+
+       sc = device_get_softc(dev);
+       pin = rman_get_start(ires);
+       if (pin > BCM_GPIO_PINS)
+               panic("%s: bad pin %d", __func__, pin);
+       if (sc->sc_events[pin] == NULL)
+               panic("Trying to teardown unoccupied IRQ");
+       err = intr_event_remove_handler(cookie);
+       if (!err)
+               sc->sc_events[pin] = NULL;
+
+       return (err);
+}
+
 static phandle_t
 bcm_gpio_get_node(device_t bus, device_t dev)
 {
@@ -759,6 +1024,13 @@ static device_method_t bcm_gpio_methods[
        DEVMETHOD(gpio_pin_set,         bcm_gpio_pin_set),
        DEVMETHOD(gpio_pin_toggle,      bcm_gpio_pin_toggle),
 
+       /* Bus interface */
+       DEVMETHOD(bus_activate_resource,        bcm_gpio_activate_resource),
+       DEVMETHOD(bus_deactivate_resource,      bcm_gpio_deactivate_resource),
+       DEVMETHOD(bus_config_intr,      bcm_gpio_config_intr),
+       DEVMETHOD(bus_setup_intr,       bcm_gpio_setup_intr),
+       DEVMETHOD(bus_teardown_intr,    bcm_gpio_teardown_intr),
+
        /* ofw_bus interface */
        DEVMETHOD(ofw_bus_get_node,     bcm_gpio_get_node),
 
_______________________________________________
svn-src-head@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/svn-src-head
To unsubscribe, send any mail to "svn-src-head-unsubscr...@freebsd.org"

Reply via email to