Source-Changes-HG archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

[src/trunk]: src/sys/arch/arm/broadcom Add support for interrupts on GPIO pin...



details:   https://anonhg.NetBSD.org/src/rev/392f61ea0b08
branches:  trunk
changeset: 322827:392f61ea0b08
user:      thorpej <thorpej%NetBSD.org@localhost>
date:      Sat May 19 14:02:10 2018 +0000

description:
Add support for interrupts on GPIO pins.  We support both FDT-driven
interrupt registration as well as the new GPIO interrupt interface.

Based on initial work by Brad Spencer.
PR kern/51676

diffstat:

 sys/arch/arm/broadcom/bcm2835_gpio.c |  434 +++++++++++++++++++++++++++++++++-
 1 files changed, 420 insertions(+), 14 deletions(-)

diffs (truncated from 516 to 300 lines):

diff -r ecba3853c234 -r 392f61ea0b08 sys/arch/arm/broadcom/bcm2835_gpio.c
--- a/sys/arch/arm/broadcom/bcm2835_gpio.c      Sat May 19 13:59:06 2018 +0000
+++ b/sys/arch/arm/broadcom/bcm2835_gpio.c      Sat May 19 14:02:10 2018 +0000
@@ -1,4 +1,4 @@
-/*     $NetBSD: bcm2835_gpio.c,v 1.6 2017/12/10 21:38:26 skrll Exp $   */
+/*     $NetBSD: bcm2835_gpio.c,v 1.7 2018/05/19 14:02:10 thorpej Exp $ */
 
 /*-
  * Copyright (c) 2013, 2014, 2017 The NetBSD Foundation, Inc.
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: bcm2835_gpio.c,v 1.6 2017/12/10 21:38:26 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: bcm2835_gpio.c,v 1.7 2018/05/19 14:02:10 thorpej Exp $");
 
 /*
  * Driver for BCM2835 GPIO
@@ -46,6 +46,7 @@
 #include <sys/intr.h>
 #include <sys/kernel.h>
 #include <sys/kmem.h>
+#include <sys/proc.h>
 #include <sys/gpio.h>
 
 #include <sys/bitops.h>
@@ -66,6 +67,29 @@
 
 #define        BCMGPIO_MAXPINS 54
 
+struct bcmgpio_eint {
+        int                    (*eint_func)(void *);
+        void                   *eint_arg;
+        int                    eint_flags;
+        int                    eint_bank;
+        int                    eint_num;
+};
+
+#define        BCMGPIO_INTR_POS_EDGE   0x01
+#define        BCMGPIO_INTR_NEG_EDGE   0x02
+#define        BCMGPIO_INTR_HIGH_LEVEL 0x04
+#define        BCMGPIO_INTR_LOW_LEVEL  0x08
+#define        BCMGPIO_INTR_MPSAFE     0x10
+
+struct bcmgpio_softc;
+struct bcmgpio_bank {
+       struct bcmgpio_softc    *sc_bcm;
+       void                    *sc_ih;
+       struct bcmgpio_eint     sc_eint[32];
+       int                     sc_bankno;
+};
+#define        BCMGPIO_NBANKS  2
+
 struct bcmgpio_softc {
        device_t                sc_dev;
        bus_space_tag_t         sc_iot;
@@ -74,6 +98,9 @@
 
        kmutex_t                sc_lock;
        gpio_pin_t              sc_gpio_pins[BCMGPIO_MAXPINS];
+
+       /* For interrupt support. */
+       struct bcmgpio_bank     sc_banks[BCMGPIO_NBANKS];
 };
 
 struct bcmgpio_pin {
@@ -90,6 +117,13 @@
 static void    bcm2835gpio_gpio_pin_write(void *, int, int);
 static void    bcm2835gpio_gpio_pin_ctl(void *, int, int);
 
+static void *  bcmgpio_gpio_intr_establish(void *, int, int, int,
+                                           int (*)(void *), void *);
+static void    bcmgpio_gpio_intr_disestablish(void *, void *);
+static bool    bcmgpio_gpio_intrstr(void *, int, int, char *, size_t);
+
+static int     bcmgpio_intr(void *);
+
 u_int          bcm283x_pin_getfunc(const struct bcmgpio_softc * const, u_int);
 void           bcm283x_pin_setfunc(const struct bcmgpio_softc * const, u_int,
                    u_int);
@@ -110,6 +144,17 @@
        .write = bcmgpio_fdt_write
 };
 
+static void *  bcmgpio_fdt_intr_establish(device_t, u_int *, int, int,
+                   int (*func)(void *), void *);
+static void    bcmgpio_fdt_intr_disestablish(device_t, void *);
+static bool    bcmgpio_fdt_intrstr(device_t, u_int *, char *, size_t);
+
+static struct fdtbus_interrupt_controller_func bcmgpio_fdt_intrfuncs = {
+       .establish = bcmgpio_fdt_intr_establish,
+       .disestablish = bcmgpio_fdt_intr_disestablish,
+       .intrstr = bcmgpio_fdt_intrstr,
+};
+
 CFATTACH_DECL_NEW(bcmgpio, sizeof(struct bcmgpio_softc),
     bcmgpio_match, bcmgpio_attach, NULL, NULL);
 
@@ -208,6 +253,7 @@
        u_int func;
        int error;
        int pin;
+       int bank;
 
        const int phandle = faa->faa_phandle;
        if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
@@ -238,10 +284,18 @@
 
                if (func == BCM2835_GPIO_IN ||
                    func == BCM2835_GPIO_OUT) {
+                       /* XXX TRISTATE?  Really? */
                        sc->sc_gpio_pins[pin].pin_caps = GPIO_PIN_INPUT |
                                GPIO_PIN_OUTPUT |
                                GPIO_PIN_PUSHPULL | GPIO_PIN_TRISTATE |
                                GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN;
+                       sc->sc_gpio_pins[pin].pin_intrcaps =
+                               GPIO_INTR_POS_EDGE |
+                               GPIO_INTR_NEG_EDGE |
+                               GPIO_INTR_DOUBLE_EDGE |
+                               GPIO_INTR_HIGH_LEVEL |
+                               GPIO_INTR_LOW_LEVEL |
+                               GPIO_INTR_MPSAFE;
                        /* read initial state */
                        sc->sc_gpio_pins[pin].pin_state =
                                bcm2835gpio_gpio_pin_read(sc, pin);
@@ -253,19 +307,33 @@
                }
        }
 
-       /* create controller tag */
-       sc->sc_gpio_gc.gp_cookie = sc;
-       sc->sc_gpio_gc.gp_pin_read = bcm2835gpio_gpio_pin_read;
-       sc->sc_gpio_gc.gp_pin_write = bcm2835gpio_gpio_pin_write;
-       sc->sc_gpio_gc.gp_pin_ctl = bcm2835gpio_gpio_pin_ctl;
+       /* Initialize interrupts. */
+       for (bank = 0; bank < BCMGPIO_NBANKS; bank++) {
+               char intrstr[128];
+
+               if (!fdtbus_intr_str(phandle, bank, intrstr, sizeof(intrstr))) {
+                       aprint_error_dev(self, "failed to decode interrupt\n");
+                       continue;
+               }
 
-       gba.gba_gc = &sc->sc_gpio_gc;
-       for (pin = 0; pin < BCMGPIO_MAXPINS;) {
-               const int npins = MIN(BCMGPIO_MAXPINS - pin, 32);
-               gba.gba_pins = &sc->sc_gpio_pins[pin];
-               gba.gba_npins = npins;
-               config_found_ia(self, "gpiobus", &gba, gpiobus_print);
-               pin += npins;
+               sc->sc_banks[bank].sc_bankno = bank;
+               sc->sc_banks[bank].sc_bcm = sc;
+               sc->sc_banks[bank].sc_ih =
+                   fdtbus_intr_establish(phandle, bank, IPL_VM,
+                                         FDT_INTR_MPSAFE,
+                                         bcmgpio_intr, &sc->sc_banks[bank]);
+               if (sc->sc_banks[bank].sc_ih) {
+                       aprint_normal_dev(self,
+                           "pins %d..%d interrupting on %s\n",
+                           bank * 32,
+                           MIN((bank * 32) + 31, BCMGPIO_MAXPINS),
+                           intrstr);
+               } else {
+                       aprint_normal_dev(self,
+                           "failed to establish interrupt for pins %d..%d\n",
+                           bank * 32,
+                           MIN((bank * 32) + 31, BCMGPIO_MAXPINS));
+               }
        }
 
        fdtbus_register_gpio_controller(self, faa->faa_phandle, &bcmgpio_funcs);
@@ -278,6 +346,344 @@
        }
 
        fdtbus_pinctrl_configure();
+
+       fdtbus_register_interrupt_controller(self, phandle,
+           &bcmgpio_fdt_intrfuncs);
+
+       /* create controller tag */
+       sc->sc_gpio_gc.gp_cookie = sc;
+       sc->sc_gpio_gc.gp_pin_read = bcm2835gpio_gpio_pin_read;
+       sc->sc_gpio_gc.gp_pin_write = bcm2835gpio_gpio_pin_write;
+       sc->sc_gpio_gc.gp_pin_ctl = bcm2835gpio_gpio_pin_ctl;
+       sc->sc_gpio_gc.gp_intr_establish = bcmgpio_gpio_intr_establish;
+       sc->sc_gpio_gc.gp_intr_disestablish = bcmgpio_gpio_intr_disestablish;
+       sc->sc_gpio_gc.gp_intr_str = bcmgpio_gpio_intrstr;
+
+       gba.gba_gc = &sc->sc_gpio_gc;
+       gba.gba_pins = &sc->sc_gpio_pins[0];
+       gba.gba_npins = BCMGPIO_MAXPINS;
+       (void) config_found_ia(self, "gpiobus", &gba, gpiobus_print);
+}
+
+/* GPIO interrupt support functions */
+
+static int
+bcmgpio_intr(void *arg)
+{
+       struct bcmgpio_bank * const b = arg;
+       struct bcmgpio_softc * const sc = b->sc_bcm;
+       struct bcmgpio_eint *eint;
+       uint32_t status, pending, bit;
+       uint32_t clear_level;
+       int (*func)(void *);
+       int rv = 0;
+
+       for (;;) {
+               status = pending = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+                   BCM2835_GPIO_GPEDS(b->sc_bankno));
+               if (status == 0)
+                       break;
+
+               /*
+                * This will clear the indicator for any pending
+                * edge-triggered pins, but level-triggered pins
+                * will still be indicated until the pin is
+                * de-asserted.  We'll have to clear level-triggered
+                * indicators below.
+                */
+               bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+                   BCM2835_GPIO_GPEDS(b->sc_bankno), status);
+               clear_level = 0;
+
+               while ((bit = ffs32(pending)) != 0) {
+                       pending &= ~__BIT(bit - 1);
+                       eint = &b->sc_eint[bit - 1];
+                       if ((func = eint->eint_func) == NULL)
+                               continue;
+                       if (eint->eint_flags & (BCMGPIO_INTR_HIGH_LEVEL |
+                                               BCMGPIO_INTR_LOW_LEVEL))
+                               clear_level |= __BIT(bit - 1);
+                       const bool mpsafe =
+                           (eint->eint_flags & BCMGPIO_INTR_MPSAFE) != 0;
+                       if (!mpsafe)
+                               KERNEL_LOCK(1, curlwp);
+                       rv |= (*func)(eint->eint_arg);
+                       if (!mpsafe)
+                               KERNEL_UNLOCK_ONE(curlwp);
+               }
+               
+               /*
+                * Now that all of the handlers have been called,
+                * we can clear the indicators for any level-triggered
+                * pins.
+                */
+               if (clear_level)
+                       bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+                           BCM2835_GPIO_GPEDS(b->sc_bankno), clear_level);
+       }
+
+       return (rv);
+}
+
+static void *
+bmcgpio_intr_enable(struct bcmgpio_softc *sc, int (*func)(void *), void *arg,
+                   int bank, int pin, int flags)
+{
+       struct bcmgpio_eint *eint;
+       uint32_t mask, enabled_ren, enabled_fen, enabled_hen, enabled_len;
+       int has_edge = flags & (BCMGPIO_INTR_POS_EDGE|BCMGPIO_INTR_NEG_EDGE);
+       int has_level = flags &
+           (BCMGPIO_INTR_HIGH_LEVEL|BCMGPIO_INTR_LOW_LEVEL);
+
+       if (bank < 0 || bank >= BCMGPIO_NBANKS)
+               return NULL;
+       if (pin < 0 || pin >= 32)
+               return (NULL);
+
+       /* Must specify a mode. */
+       if (!has_edge && !has_level)
+               return (NULL);
+
+       /* Can't have HIGH and LOW together. */
+       if (has_level == (BCMGPIO_INTR_HIGH_LEVEL|BCMGPIO_INTR_LOW_LEVEL))
+               return (NULL);
+       
+       /* Can't have EDGE and LEVEL together. */
+       if (has_edge && has_level)
+               return (NULL);
+
+       eint = &sc->sc_banks[bank].sc_eint[pin];
+
+       mask = __BIT(pin);
+
+       mutex_enter(&sc->sc_lock);
+
+       if (eint->eint_func != NULL) {
+               mutex_exit(&sc->sc_lock);
+               return (NULL);  /* in use */
+       }
+
+       eint->eint_func = func;
+       eint->eint_arg = arg;
+       eint->eint_flags = flags;
+       eint->eint_bank = bank;
+       eint->eint_num = pin;
+
+       enabled_ren = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+                                      BCM2835_GPIO_GPREN(bank));



Home | Main Index | Thread Index | Old Index