Date: Fri, 29 Oct 2021 08:31:44 GMT From: Wojciech Macek <wma@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 027a58aab2ce - main - qoriq_gpio: Implement interrupt controller functionality Message-ID: <202110290831.19T8Vi7W073872@gitrepo.freebsd.org>
next in thread | raw e-mail | index | archive | help
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);
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202110290831.19T8Vi7W073872>