git: 027a58aab2ce - main - qoriq_gpio: Implement interrupt controller functionality

From: Wojciech Macek <wma_at_FreeBSD.org>
Date: Fri, 29 Oct 2021 08:31:44 UTC
The branch main has been updated by wma:

URL: https://cgit.FreeBSD.org/src/commit/?id=027a58aab2cee5589a3a639afb77ecbb607f8fee

commit 027a58aab2cee5589a3a639afb77ecbb607f8fee
Author:     Kornel Duleba <mindal@semihalf.com>
AuthorDate: 2021-09-28 15:09:41 +0000
Commit:     Wojciech Macek <wma@FreeBSD.org>
CommitDate: 2021-10-29 08:08:26 +0000

    qoriq_gpio: Implement interrupt controller functionality
    
    The pic_* interface was used.
    Only edge interrupts are supported by this controller.
    Driver mutex had to be converted to a spin lock so that it can
    be used in the interrupt filter context.
    Two types of intr_map_data are supported - INTR_MAP_DATA_GPIO and
    INTR_MAP_DATA_FDT. This way interrupts can be allocated using the
    userspace gpio interrupt allocation method, as well as directly from
    simplebus. The latter can be used by devices that have its irq routed
    to a GPIO pin.
    
    Obtained from: Semihalf
    Sponsored by: Alstom Group
    Differential revision: https://reviews.freebsd.org/D32587
---
 sys/dev/gpio/qoriq_gpio.c | 352 ++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 339 insertions(+), 13 deletions(-)

diff --git a/sys/dev/gpio/qoriq_gpio.c b/sys/dev/gpio/qoriq_gpio.c
index dc4813e07b8e..0a78adbecb0f 100644
--- a/sys/dev/gpio/qoriq_gpio.c
+++ b/sys/dev/gpio/qoriq_gpio.c
@@ -38,6 +38,7 @@ __FBSDID("$FreeBSD$");
 #include <sys/kernel.h>
 #include <sys/module.h>
 #include <sys/mutex.h>
+#include <sys/proc.h>
 #include <sys/rman.h>
 #include <sys/gpio.h>
 
@@ -49,19 +50,25 @@ __FBSDID("$FreeBSD$");
 #include <dev/ofw/ofw_bus.h>
 #include <dev/ofw/ofw_bus_subr.h>
 
+#include <dt-bindings/interrupt-controller/irq.h>
+
 #include "gpio_if.h"
+#include "pic_if.h"
 
+#define	BIT(x)		(1 << (x))
 #define MAXPIN		(31)
 
 #define VALID_PIN(u)	((u) >= 0 && (u) <= MAXPIN)
 #define DEFAULT_CAPS	(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \
-			 GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)
+			 GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL | \
+			 GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH | \
+			 GPIO_PIN_PULLUP)
 
-#define GPIO_LOCK(sc)			mtx_lock(&(sc)->sc_mtx)
-#define	GPIO_UNLOCK(sc)		mtx_unlock(&(sc)->sc_mtx)
+#define	GPIO_LOCK(sc)	mtx_lock_spin(&(sc)->sc_mtx)
+#define	GPIO_UNLOCK(sc)	mtx_unlock_spin(&(sc)->sc_mtx)
 #define GPIO_LOCK_INIT(sc) \
 	mtx_init(&(sc)->sc_mtx, device_get_nameunit((sc)->dev),	\
-	    "gpio", MTX_DEF)
+	    "gpio", MTX_SPIN)
 #define GPIO_LOCK_DESTROY(_sc)	mtx_destroy(&_sc->sc_mtx);
 
 #define	GPIO_GPDIR	0x0
@@ -72,12 +79,21 @@ __FBSDID("$FreeBSD$");
 #define	GPIO_GPICR	0x14
 #define	GPIO_GPIBE	0x18
 
+struct qoriq_gpio_irqsrc {
+	struct intr_irqsrc	isrc;
+	int			pin;
+};
+
 struct qoriq_gpio_softc {
 	device_t	dev;
 	device_t	busdev;
 	struct mtx	sc_mtx;
 	struct resource *sc_mem;	/* Memory resource */
+	struct resource	*sc_intr;
+	void		*intr_cookie;
 	struct gpio_pin	 sc_pins[MAXPIN + 1];
+	struct qoriq_gpio_irqsrc sc_isrcs[MAXPIN + 1];
+	struct intr_map_data_gpio gdata;
 };
 
 static device_t
@@ -260,6 +276,254 @@ qoriq_gpio_pin_toggle(device_t dev, uint32_t pin)
 	return (0);
 }
 
+static void
+qoriq_gpio_set_intr(struct qoriq_gpio_softc *sc, int pin, bool enable)
+{
+	uint32_t reg;
+
+	reg = bus_read_4(sc->sc_mem, GPIO_GPIMR);
+	if (enable)
+		reg |= BIT(31 - pin);
+	else
+		reg &= ~BIT(31 - pin);
+	bus_write_4(sc->sc_mem, GPIO_GPIMR, reg);
+}
+
+static void
+qoriq_gpio_ack_intr(struct qoriq_gpio_softc *sc, int pin)
+{
+	uint32_t reg;
+
+	reg = BIT(31 - pin);
+	bus_write_4(sc->sc_mem, GPIO_GPIER, reg);
+}
+
+static int
+qoriq_gpio_intr(void *arg)
+{
+	struct qoriq_gpio_softc *sc;
+	struct trapframe *tf;
+	uint32_t status;
+	int pin;
+
+	sc = (struct qoriq_gpio_softc *)arg;
+	tf = curthread->td_intr_frame;
+
+	status = bus_read_4(sc->sc_mem, GPIO_GPIER);
+	status &= bus_read_4(sc->sc_mem, GPIO_GPIMR);
+	while (status != 0) {
+		pin = ffs(status) - 1;
+		status &= ~BIT(pin);
+		pin = 31 - pin;
+
+		if (intr_isrc_dispatch(&sc->sc_isrcs[pin].isrc, tf) != 0) {
+			GPIO_LOCK(sc);
+			qoriq_gpio_set_intr(sc, pin, false);
+			qoriq_gpio_ack_intr(sc, pin);
+			GPIO_UNLOCK(sc);
+			device_printf(sc->dev,
+			    "Masking spurious pin interrupt %d\n",
+			    pin);
+		}
+	}
+
+	return (FILTER_HANDLED);
+}
+
+static void
+qoriq_gpio_disable_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct qoriq_gpio_softc *sc;
+	struct qoriq_gpio_irqsrc *qisrc;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	GPIO_LOCK(sc);
+	qoriq_gpio_set_intr(sc, qisrc->pin, false);
+	GPIO_UNLOCK(sc);
+}
+
+static void
+qoriq_gpio_enable_intr(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct qoriq_gpio_softc *sc;
+	struct qoriq_gpio_irqsrc *qisrc;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	GPIO_LOCK(sc);
+	qoriq_gpio_set_intr(sc, qisrc->pin, true);
+	GPIO_UNLOCK(sc);
+}
+
+static struct intr_map_data_gpio*
+qoriq_gpio_convert_map_data(struct qoriq_gpio_softc *sc, struct intr_map_data *data)
+{
+	struct intr_map_data_gpio *gdata;
+	struct intr_map_data_fdt *daf;
+
+	switch (data->type) {
+	case INTR_MAP_DATA_GPIO:
+		gdata = (struct intr_map_data_gpio *)data;
+		break;
+	case INTR_MAP_DATA_FDT:
+		daf = (struct intr_map_data_fdt *)data;
+		if (daf->ncells != 2)
+			return (NULL);
+
+		gdata = &sc->gdata;
+		gdata->gpio_pin_num = daf->cells[0];
+		switch (daf->cells[1]) {
+		case IRQ_TYPE_LEVEL_LOW:
+			gdata->gpio_intr_mode = GPIO_INTR_LEVEL_LOW;
+			break;
+		case IRQ_TYPE_LEVEL_HIGH:
+			gdata->gpio_intr_mode = GPIO_INTR_LEVEL_HIGH;
+			break;
+		case IRQ_TYPE_EDGE_RISING:
+			gdata->gpio_intr_mode = GPIO_INTR_EDGE_RISING;
+			break;
+		case IRQ_TYPE_EDGE_FALLING:
+			gdata->gpio_intr_mode = GPIO_INTR_EDGE_FALLING;
+			break;
+		case IRQ_TYPE_EDGE_BOTH:
+			gdata->gpio_intr_mode = GPIO_INTR_EDGE_BOTH;
+			break;
+		default:
+			return (NULL);
+		}
+		break;
+	default:
+		return (NULL);
+	}
+
+	return (gdata);
+}
+
+
+static int
+qoriq_gpio_map_intr(device_t dev, struct intr_map_data *data,
+    struct intr_irqsrc **isrcp)
+{
+	struct qoriq_gpio_softc *sc;
+	struct intr_map_data_gpio *gdata;
+	int pin;
+
+	sc = device_get_softc(dev);
+
+	gdata = qoriq_gpio_convert_map_data(sc, data);
+	if (gdata == NULL)
+		return (EINVAL);
+
+	pin = gdata->gpio_pin_num;
+	if (pin > MAXPIN)
+		return (EINVAL);
+
+	*isrcp = &sc->sc_isrcs[pin].isrc;
+	return (0);
+}
+
+static int
+qoriq_gpio_setup_intr(device_t dev, struct intr_irqsrc *isrc,
+    struct resource *res, struct intr_map_data *data)
+{
+	struct qoriq_gpio_softc *sc;
+	struct intr_map_data_gpio *gdata;
+	struct qoriq_gpio_irqsrc *qisrc;
+	bool falling;
+	uint32_t reg;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	gdata = qoriq_gpio_convert_map_data(sc, data);
+	if (gdata == NULL)
+		return (EINVAL);
+
+	if (gdata->gpio_intr_mode & GPIO_INTR_EDGE_BOTH)
+		falling = false;
+	else if (gdata->gpio_intr_mode & GPIO_INTR_EDGE_FALLING)
+		falling = true;
+	else
+		return (EOPNOTSUPP);
+
+	GPIO_LOCK(sc);
+	reg = bus_read_4(sc->sc_mem, GPIO_GPICR);
+	if (falling)
+		reg |= BIT(31 - qisrc->pin);
+	else
+		reg &= ~BIT(31 - qisrc->pin);
+	bus_write_4(sc->sc_mem, GPIO_GPICR, reg);
+	GPIO_UNLOCK(sc);
+
+	return (0);
+}
+
+static int
+qoriq_gpio_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
+    struct resource *res, struct intr_map_data *data)
+{
+	struct qoriq_gpio_softc *sc;
+	struct qoriq_gpio_irqsrc *qisrc;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	if (isrc->isrc_handlers > 0)
+		return (0);
+
+	GPIO_LOCK(sc);
+	qoriq_gpio_set_intr(sc, qisrc->pin, false);
+	GPIO_UNLOCK(sc);
+	return (0);
+}
+
+static void
+qoriq_gpio_post_filter(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct qoriq_gpio_softc *sc;
+	struct qoriq_gpio_irqsrc *qisrc;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	GPIO_LOCK(sc);
+	qoriq_gpio_ack_intr(sc, qisrc->pin);
+	GPIO_UNLOCK(sc);
+}
+
+
+static void
+qoriq_gpio_post_ithread(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct qoriq_gpio_softc *sc;
+	struct qoriq_gpio_irqsrc *qisrc;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	GPIO_LOCK(sc);
+	qoriq_gpio_ack_intr(sc, qisrc->pin);
+	qoriq_gpio_set_intr(sc, qisrc->pin, true);
+	GPIO_UNLOCK(sc);
+}
+
+static void
+qoriq_gpio_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
+{
+	struct qoriq_gpio_softc *sc;
+	struct qoriq_gpio_irqsrc *qisrc;
+
+	sc = device_get_softc(dev);
+	qisrc = (struct qoriq_gpio_irqsrc *)isrc;
+
+	GPIO_LOCK(sc);
+	qoriq_gpio_set_intr(sc, qisrc->pin, false);
+	GPIO_UNLOCK(sc);
+}
+
 static struct ofw_compat_data gpio_matches[] = {
     {"fsl,qoriq-gpio", 1},
     {"fsl,pq3-gpio", 1},
@@ -385,7 +649,9 @@ static int
 qoriq_gpio_attach(device_t dev)
 {
 	struct qoriq_gpio_softc *sc = device_get_softc(dev);
-	int i, rid;
+	int i, rid, error;
+	const char *name;
+	intptr_t xref;
 
 	sc->dev = dev;
 
@@ -397,17 +663,46 @@ qoriq_gpio_attach(device_t dev)
 		     SYS_RES_MEMORY, &rid, RF_ACTIVE);
 	if (sc->sc_mem == NULL) {
 		device_printf(dev, "Can't allocate memory for device output port");
-		qoriq_gpio_detach(dev);
-		return (ENOMEM);
+		error = ENOMEM;
+		goto fail;
+	}
+
+	rid = 0;
+	sc->sc_intr = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid,
+	    RF_ACTIVE | RF_SHAREABLE);
+	if (sc->sc_intr == NULL) {
+		device_printf(dev, "Can't allocate interrupt resource.\n");
+		error = ENOMEM;
+		goto fail;
+	}
+
+	error = bus_setup_intr(dev, sc->sc_intr, INTR_TYPE_MISC | INTR_MPSAFE,
+	    qoriq_gpio_intr, NULL, sc, &sc->intr_cookie);
+	if (error != 0) {
+		device_printf(dev, "Failed to setup interrupt.\n");
+		goto fail;
 	}
 
-	for (i = 0; i <= MAXPIN; i++)
+	name = device_get_nameunit(dev);
+	for (i = 0; i <= MAXPIN; i++) {
 		sc->sc_pins[i].gp_caps = DEFAULT_CAPS;
+		sc->sc_isrcs[i].pin = i;
+		error = intr_isrc_register(&sc->sc_isrcs[i].isrc,
+		    dev, 0, "%s,%u", name, i);
+		if (error != 0)
+			goto fail;
+	}
+
+	xref = OF_xref_from_node(ofw_bus_get_node(dev));
+	if (intr_pic_register(dev, xref) == NULL) {
+		error = ENXIO;
+		goto fail;
+	}
 
 	sc->busdev = gpiobus_attach_bus(dev);
 	if (sc->busdev == NULL) {
-		qoriq_gpio_detach(dev);
-		return (ENOMEM);
+		error = ENXIO;
+		goto fail;
 	}
 	/*
 	 * Enable the GPIO Input Buffer for all GPIOs.
@@ -419,7 +714,13 @@ qoriq_gpio_attach(device_t dev)
 
 	OF_device_register_xref(OF_xref_from_node(ofw_bus_get_node(dev)), dev);
 
+	bus_write_4(sc->sc_mem, GPIO_GPIER, 0xffffffff);
+	bus_write_4(sc->sc_mem, GPIO_GPIMR, 0);
+
 	return (0);
+fail:
+	qoriq_gpio_detach(dev);
+	return (error);
 }
 
 static int
@@ -435,6 +736,13 @@ qoriq_gpio_detach(device_t dev)
 				     rman_get_rid(sc->sc_mem), sc->sc_mem);
 	}
 
+	if (sc->intr_cookie != NULL)
+		bus_teardown_intr(dev, sc->sc_intr, sc->intr_cookie);
+
+	if (sc->sc_intr != NULL)
+		bus_release_resource(dev, SYS_RES_IRQ,
+		    rman_get_rid(sc->sc_intr), sc->sc_intr);
+
 	GPIO_LOCK_DESTROY(sc);
 
 	return (0);
@@ -446,6 +754,11 @@ static device_method_t qoriq_gpio_methods[] = {
 	DEVMETHOD(device_attach, 	qoriq_gpio_attach),
 	DEVMETHOD(device_detach, 	qoriq_gpio_detach),
 
+	/* Bus interface */
+	DEVMETHOD(bus_setup_intr,		bus_generic_setup_intr),
+	DEVMETHOD(bus_activate_resource,	bus_generic_activate_resource),
+	DEVMETHOD(bus_deactivate_resource,	bus_generic_deactivate_resource),
+
 	/* GPIO protocol */
 	DEVMETHOD(gpio_get_bus, 	qoriq_gpio_get_bus),
 	DEVMETHOD(gpio_pin_max, 	qoriq_gpio_pin_max),
@@ -461,6 +774,16 @@ static device_method_t qoriq_gpio_methods[] = {
 	DEVMETHOD(gpio_pin_access_32,	qoriq_gpio_pin_access_32),
 	DEVMETHOD(gpio_pin_config_32,	qoriq_gpio_pin_config_32),
 
+	/* Interrupt controller */
+	DEVMETHOD(pic_disable_intr,	qoriq_gpio_disable_intr),
+	DEVMETHOD(pic_enable_intr,	qoriq_gpio_enable_intr),
+	DEVMETHOD(pic_map_intr,		qoriq_gpio_map_intr),
+	DEVMETHOD(pic_setup_intr,	qoriq_gpio_setup_intr),
+	DEVMETHOD(pic_teardown_intr,	qoriq_gpio_teardown_intr),
+	DEVMETHOD(pic_post_filter,	qoriq_gpio_post_filter),
+	DEVMETHOD(pic_post_ithread,	qoriq_gpio_post_ithread),
+	DEVMETHOD(pic_pre_ithread,	qoriq_gpio_pre_ithread),
+
 	DEVMETHOD_END
 };
 
@@ -471,6 +794,9 @@ static driver_t qoriq_gpio_driver = {
 };
 static devclass_t qoriq_gpio_devclass;
 
-EARLY_DRIVER_MODULE(qoriq_gpio, simplebus, qoriq_gpio_driver,
-    qoriq_gpio_devclass, NULL, NULL,
-    BUS_PASS_RESOURCE + BUS_PASS_ORDER_MIDDLE);
+/*
+ * This needs to be loaded after interrupts are available and
+ * before consumers need it.
+ */
+EARLY_DRIVER_MODULE(qoriq_gpio, simplebus, qoriq_gpio_driver, qoriq_gpio_devclass,
+    NULL, NULL, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);