Module Name: src
Committed By: thorpej
Date: Sat May 19 14:02:10 UTC 2018
Modified Files:
src/sys/arch/arm/broadcom: bcm2835_gpio.c
Log Message:
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
To generate a diff of this commit:
cvs rdiff -u -r1.6 -r1.7 src/sys/arch/arm/broadcom/bcm2835_gpio.c
Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.
Modified files:
Index: src/sys/arch/arm/broadcom/bcm2835_gpio.c
diff -u src/sys/arch/arm/broadcom/bcm2835_gpio.c:1.6 src/sys/arch/arm/broadcom/bcm2835_gpio.c:1.7
--- src/sys/arch/arm/broadcom/bcm2835_gpio.c:1.6 Sun Dec 10 21:38:26 2017
+++ src/sys/arch/arm/broadcom/bcm2835_gpio.c Sat May 19 14:02:10 2018
@@ -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 @@ __KERNEL_RCSID(0, "$NetBSD: bcm2835_gpio
#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 @@ int bcm2835gpiodebug = 3;
#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 @@ struct bcmgpio_softc {
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 int bcm2835gpio_gpio_pin_read(voi
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 @@ static struct fdtbus_gpio_controller_fun
.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 @@ bcmgpio_attach(device_t parent, device_t
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 @@ bcmgpio_attach(device_t parent, device_t
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 @@ bcmgpio_attach(device_t parent, device_t
}
}
- /* 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];
- 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;
+ if (!fdtbus_intr_str(phandle, bank, intrstr, sizeof(intrstr))) {
+ aprint_error_dev(self, "failed to decode interrupt\n");
+ continue;
+ }
+
+ 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 @@ bcmgpio_attach(device_t parent, device_t
}
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));
+ enabled_fen = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPFEN(bank));
+ enabled_hen = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPHEN(bank));
+ enabled_len = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPLEN(bank));
+
+ enabled_ren &= ~mask;
+ enabled_fen &= ~mask;
+ enabled_hen &= ~mask;
+ enabled_len &= ~mask;
+
+ if (flags & BCMGPIO_INTR_POS_EDGE)
+ enabled_ren |= mask;
+ if (flags & BCMGPIO_INTR_NEG_EDGE)
+ enabled_fen |= mask;
+ if (flags & BCMGPIO_INTR_HIGH_LEVEL)
+ enabled_hen |= mask;
+ if (flags & BCMGPIO_INTR_LOW_LEVEL)
+ enabled_len |= mask;
+
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPREN(bank), enabled_ren);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPFEN(bank), enabled_fen);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPHEN(bank), enabled_hen);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPLEN(bank), enabled_len);
+
+ mutex_exit(&sc->sc_lock);
+ return (eint);
+}
+
+static void
+bcmgpio_intr_disable(struct bcmgpio_softc *sc, struct bcmgpio_eint *eint)
+{
+ uint32_t mask, enabled_ren, enabled_fen, enabled_hen, enabled_len;
+ int bank = eint->eint_bank;
+
+ mask = __BIT(eint->eint_num);
+
+ KASSERT(eint->eint_func != NULL);
+
+ mutex_enter(&sc->sc_lock);
+
+ enabled_ren = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPREN(bank));
+ enabled_fen = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPFEN(bank));
+ enabled_hen = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPHEN(bank));
+ enabled_len = bus_space_read_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPLEN(bank));
+
+ enabled_ren &= ~mask;
+ enabled_fen &= ~mask;
+ enabled_hen &= ~mask;
+ enabled_len &= ~mask;
+
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPREN(bank), enabled_ren);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPFEN(bank), enabled_fen);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPHEN(bank), enabled_hen);
+ bus_space_write_4(sc->sc_iot, sc->sc_ioh,
+ BCM2835_GPIO_GPLEN(bank), enabled_len);
+
+ eint->eint_func = NULL;
+ eint->eint_arg = NULL;
+ eint->eint_flags = 0;
+
+ mutex_exit(&sc->sc_lock);
+}
+
+static void *
+bcmgpio_fdt_intr_establish(device_t dev, u_int *specifier, int ipl, int flags,
+ int (*func)(void *), void *arg)
+{
+ struct bcmgpio_softc * const sc = device_private(dev);
+ int eint_flags = (flags & FDT_INTR_MPSAFE) ? BCMGPIO_INTR_MPSAFE : 0;
+
+ if (ipl != IPL_VM) {
+ aprint_error_dev(dev, "%s: wrong IPL %d (expected %d)\n",
+ __func__, ipl, IPL_VM);
+ return (NULL);
+ }
+
+ /* 1st cell is the bank */
+ /* 2nd cell is the pin */
+ /* 3rd cell is flags */
+ const u_int bank = be32toh(specifier[0]);
+ const u_int pin = be32toh(specifier[1]);
+ const u_int type = be32toh(specifier[2]) & 0xf;
+
+ switch (type) {
+ case 0x1:
+ eint_flags |= BCMGPIO_INTR_POS_EDGE;
+ break;
+ case 0x2:
+ eint_flags |= BCMGPIO_INTR_NEG_EDGE;
+ break;
+ case 0x3:
+ eint_flags |= BCMGPIO_INTR_POS_EDGE | BCMGPIO_INTR_NEG_EDGE;
+ break;
+ case 0x4:
+ eint_flags |= BCMGPIO_INTR_HIGH_LEVEL;
+ break;
+ case 0x8:
+ eint_flags |= BCMGPIO_INTR_LOW_LEVEL;
+ break;
+ default:
+ aprint_error_dev(dev, "%s: unsupported irq type 0x%x\n",
+ __func__, type);
+ return (NULL);
+ }
+
+ return (bmcgpio_intr_enable(sc, func, arg, bank, pin, eint_flags));
+}
+
+static void
+bcmgpio_fdt_intr_disestablish(device_t dev, void *ih)
+{
+ struct bcmgpio_softc * const sc = device_private(dev);
+ struct bcmgpio_eint * const eint = ih;
+
+ bcmgpio_intr_disable(sc, eint);
+}
+
+static void *
+bcmgpio_gpio_intr_establish(void *vsc, int pin, int ipl, int irqmode,
+ int (*func)(void *), void *arg)
+{
+ struct bcmgpio_softc * const sc = vsc;
+ int eint_flags = (irqmode & GPIO_INTR_MPSAFE) ? BCMGPIO_INTR_MPSAFE : 0;
+ int bank = pin / 32;
+ int type = irqmode & GPIO_INTR_MODE_MASK;
+
+ pin %= 32;
+
+ if (ipl != IPL_VM) {
+ aprint_error_dev(sc->sc_dev, "%s: wrong IPL %d (expected %d)\n",
+ __func__, ipl, IPL_VM);
+ return (NULL);
+ }
+
+ switch (type) {
+ case GPIO_INTR_POS_EDGE:
+ eint_flags |= BCMGPIO_INTR_POS_EDGE;
+ break;
+ case GPIO_INTR_NEG_EDGE:
+ eint_flags |= BCMGPIO_INTR_NEG_EDGE;
+ break;
+ case GPIO_INTR_DOUBLE_EDGE:
+ eint_flags |= BCMGPIO_INTR_POS_EDGE | BCMGPIO_INTR_NEG_EDGE;
+ break;
+ case GPIO_INTR_HIGH_LEVEL:
+ eint_flags |= BCMGPIO_INTR_HIGH_LEVEL;
+ break;
+ case GPIO_INTR_LOW_LEVEL:
+ eint_flags |= BCMGPIO_INTR_LOW_LEVEL;
+ break;
+ default:
+ aprint_error_dev(sc->sc_dev, "%s: unsupported irq type 0x%x\n",
+ __func__, type);
+ return (NULL);
+ }
+
+ return (bmcgpio_intr_enable(sc, func, arg, bank, pin, eint_flags));
+}
+
+static void
+bcmgpio_gpio_intr_disestablish(void *vsc, void *ih)
+{
+ struct bcmgpio_softc * const sc = vsc;
+ struct bcmgpio_eint * const eint = ih;
+
+ bcmgpio_intr_disable(sc, eint);
+}
+
+static bool
+bcmgpio_gpio_intrstr(void *vsc, int pin, int irqmode, char *buf, size_t buflen)
+{
+
+ if (pin < 0 || pin >= BCMGPIO_MAXPINS)
+ return (false);
+
+ snprintf(buf, buflen, "GPIO %d", pin);
+
+ return (true);
+}
+
+static bool
+bcmgpio_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen)
+{
+
+ /* 1st cell is the bank */
+ /* 2nd cell is the pin */
+ /* 3rd cell is flags */
+ if (!specifier)
+ return (false);
+ const u_int bank = be32toh(specifier[0]);
+ const u_int pin = be32toh(specifier[1]);
+
+ if (bank >= BCMGPIO_NBANKS)
+ return (false);
+ if (pin >= 32)
+ return (false);
+
+ snprintf(buf, buflen, "GPIO %u", (bank * 32) + pin);
+
+ return (true);
}
/* GPIO support functions */