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 */

Reply via email to