Module Name: src Committed By: jmcneill Date: Sun Nov 3 11:34:40 UTC 2019
Modified Files: src/sys/arch/arm/ti: ti_gpio.c Log Message: Add support for GPIO interrupts and fix reading the state of output pins. To generate a diff of this commit: cvs rdiff -u -r1.2 -r1.3 src/sys/arch/arm/ti/ti_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/ti/ti_gpio.c diff -u src/sys/arch/arm/ti/ti_gpio.c:1.2 src/sys/arch/arm/ti/ti_gpio.c:1.3 --- src/sys/arch/arm/ti/ti_gpio.c:1.2 Tue Oct 29 22:19:13 2019 +++ src/sys/arch/arm/ti/ti_gpio.c Sun Nov 3 11:34:40 2019 @@ -1,4 +1,4 @@ -/* $NetBSD: ti_gpio.c,v 1.2 2019/10/29 22:19:13 jmcneill Exp $ */ +/* $NetBSD: ti_gpio.c,v 1.3 2019/11/03 11:34:40 jmcneill Exp $ */ /*- * Copyright (c) 2019 Jared McNeill <jmcne...@invisible.ca> @@ -27,7 +27,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ti_gpio.c,v 1.2 2019/10/29 22:19:13 jmcneill Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ti_gpio.c,v 1.3 2019/11/03 11:34:40 jmcneill Exp $"); #include <sys/param.h> #include <sys/bus.h> @@ -44,27 +44,87 @@ __KERNEL_RCSID(0, "$NetBSD: ti_gpio.c,v #include <arm/ti/ti_prcm.h> -#define GPIO_OE 0x34 -#define GPIO_DATAIN 0x38 -#define GPIO_CLEARDATAOUT 0x90 -#define GPIO_SETDATAOUT 0x94 +#define TI_GPIO_NPINS 32 + +enum ti_gpio_type { + TI_GPIO_OMAP3, + TI_GPIO_OMAP4, + TI_NGPIO +}; + +enum { + GPIO_IRQSTATUS1, + GPIO_IRQENABLE1, /* OMAP3 */ + GPIO_IRQENABLE1_SET, /* OMAP4 */ + GPIO_IRQENABLE1_CLR, /* OMAP4 */ + GPIO_OE, + GPIO_DATAIN, + GPIO_DATAOUT, + GPIO_LEVELDETECT0, + GPIO_LEVELDETECT1, + GPIO_RISINGDETECT, + GPIO_FALLINGDETECT, + GPIO_CLEARDATAOUT, + GPIO_SETDATAOUT, + GPIO_NREG +}; + +static const u_int ti_gpio_regmap[TI_NGPIO][GPIO_NREG] = { + [TI_GPIO_OMAP3] = { + [GPIO_IRQSTATUS1] = 0x18, + [GPIO_IRQENABLE1] = 0x1c, + [GPIO_OE] = 0x34, + [GPIO_DATAIN] = 0x38, + [GPIO_DATAOUT] = 0x3c, + [GPIO_LEVELDETECT0] = 0x40, + [GPIO_LEVELDETECT1] = 0x44, + [GPIO_RISINGDETECT] = 0x48, + [GPIO_FALLINGDETECT] = 0x4c, + [GPIO_CLEARDATAOUT] = 0x90, + [GPIO_SETDATAOUT] = 0x94, + }, + [TI_GPIO_OMAP4] = { + [GPIO_IRQSTATUS1] = 0x2c, + [GPIO_IRQENABLE1_SET] = 0x34, + [GPIO_IRQENABLE1_CLR] = 0x38, + [GPIO_OE] = 0x134, + [GPIO_DATAIN] = 0x138, + [GPIO_DATAOUT] = 0x13c, + [GPIO_LEVELDETECT0] = 0x140, + [GPIO_LEVELDETECT1] = 0x144, + [GPIO_RISINGDETECT] = 0x148, + [GPIO_FALLINGDETECT] = 0x14c, + [GPIO_CLEARDATAOUT] = 0x190, + [GPIO_SETDATAOUT] = 0x194, + }, +}; static const struct of_compat_data compat_data[] = { - /* compatible reg offset */ - { "ti,omap3-gpio", 0x0 }, - { "ti,omap4-gpio", 0x100 }, + { "ti,omap3-gpio", TI_GPIO_OMAP3 }, + { "ti,omap4-gpio", TI_GPIO_OMAP4 }, { NULL } }; +struct ti_gpio_intr { + u_int intr_pin; + int (*intr_func)(void *); + void *intr_arg; + bool intr_mpsafe; +}; + struct ti_gpio_softc { device_t sc_dev; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; kmutex_t sc_lock; - bus_size_t sc_regoff; + enum ti_gpio_type sc_type; + const char *sc_modname; + void *sc_ih; struct gpio_chipset_tag sc_gp; - gpio_pin_t sc_pins[32]; + gpio_pin_t sc_pins[TI_GPIO_NPINS]; + bool sc_pinout[TI_GPIO_NPINS]; + struct ti_gpio_intr sc_intr[TI_GPIO_NPINS]; device_t sc_gpiodev; }; @@ -76,9 +136,9 @@ struct ti_gpio_pin { }; #define RD4(sc, reg) \ - bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg) + (sc)->sc_regoff) + bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, ti_gpio_regmap[(sc)->sc_type][(reg)]) #define WR4(sc, reg, val) \ - bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg) + (sc)->sc_regoff, (val)) + bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, ti_gpio_regmap[(sc)->sc_type][(reg)], (val)) static int ti_gpio_match(device_t, cfdata_t, void *); static void ti_gpio_attach(device_t, device_t, void *); @@ -100,6 +160,8 @@ ti_gpio_ctl(struct ti_gpio_softc *sc, u_ oe &= ~__BIT(pin); WR4(sc, GPIO_OE, oe); + sc->sc_pinout[pin] = (flags & GPIO_PIN_OUTPUT) != 0; + return 0; } @@ -162,7 +224,10 @@ ti_gpio_read(device_t dev, void *priv, b const uint32_t data_mask = __BIT(pin->pin_nr); /* No lock required for reads */ - data = RD4(sc, GPIO_DATAIN); + if (sc->sc_pinout[pin->pin_nr]) + data = RD4(sc, GPIO_DATAOUT); + else + data = RD4(sc, GPIO_DATAIN); val = __SHIFTOUT(data, data_mask); if (!raw && pin->pin_actlo) val = !val; @@ -195,6 +260,126 @@ static struct fdtbus_gpio_controller_fun .write = ti_gpio_write, }; +static void +ti_gpio_intr_disestablish(device_t dev, void *ih) +{ + struct ti_gpio_softc * const sc = device_private(dev); + struct ti_gpio_intr *intr = ih; + const u_int pin = intr->intr_pin; + const uint32_t pin_mask = __BIT(pin); + uint32_t val; + + /* Disable interrupts */ + if (sc->sc_type == TI_GPIO_OMAP3) { + val = RD4(sc, GPIO_IRQENABLE1); + WR4(sc, GPIO_IRQENABLE1, val & ~pin_mask); + } else { + WR4(sc, GPIO_IRQENABLE1_CLR, pin_mask); + } + + intr->intr_func = NULL; + intr->intr_arg = NULL; +} + +static void * +ti_gpio_intr_establish(device_t dev, u_int *specifier, int ipl, int flags, + int (*func)(void *), void *arg) +{ + struct ti_gpio_softc * const sc = device_private(dev); + uint32_t val; + + /* 1st cell is the pin */ + /* 2nd cell is flags */ + const u_int pin = be32toh(specifier[0]); + const u_int type = be32toh(specifier[2]) & 0xf; + + if (ipl != IPL_VM || pin >= __arraycount(sc->sc_pins)) + return NULL; + + /* + * Enabling both high and low level triggers will cause the GPIO + * controller to always assert the interrupt. + */ + if ((type & (FDT_INTR_TYPE_LOW_LEVEL|FDT_INTR_TYPE_HIGH_LEVEL)) == + (FDT_INTR_TYPE_LOW_LEVEL|FDT_INTR_TYPE_HIGH_LEVEL)) + return NULL; + + if (sc->sc_intr[pin].intr_func != NULL) + return NULL; + + /* Set pin as input */ + if (ti_gpio_ctl(sc, pin, GPIO_PIN_INPUT) != 0) + return NULL; + + sc->sc_intr[pin].intr_pin = pin; + sc->sc_intr[pin].intr_func = func; + sc->sc_intr[pin].intr_arg = arg; + sc->sc_intr[pin].intr_mpsafe = (flags & FDT_INTR_MPSAFE) != 0; + + const uint32_t pin_mask = __BIT(pin); + + /* Configure triggers */ + val = RD4(sc, GPIO_LEVELDETECT0); + if ((type & FDT_INTR_TYPE_LOW_LEVEL) != 0) + val |= pin_mask; + else + val &= ~pin_mask; + WR4(sc, GPIO_LEVELDETECT0, val); + + val = RD4(sc, GPIO_LEVELDETECT1); + if ((type & FDT_INTR_TYPE_HIGH_LEVEL) != 0) + val |= pin_mask; + else + val &= ~pin_mask; + WR4(sc, GPIO_LEVELDETECT1, val); + + val = RD4(sc, GPIO_RISINGDETECT); + if ((type & FDT_INTR_TYPE_POS_EDGE) != 0) + val |= pin_mask; + else + val &= ~pin_mask; + WR4(sc, GPIO_RISINGDETECT, val); + + val = RD4(sc, GPIO_FALLINGDETECT); + if ((type & FDT_INTR_TYPE_NEG_EDGE) != 0) + val |= pin_mask; + else + val &= ~pin_mask; + WR4(sc, GPIO_FALLINGDETECT, val); + + /* Enable interrupts */ + if (sc->sc_type == TI_GPIO_OMAP3) { + val = RD4(sc, GPIO_IRQENABLE1); + WR4(sc, GPIO_IRQENABLE1, val | pin_mask); + } else { + WR4(sc, GPIO_IRQENABLE1_SET, pin_mask); + } + + return &sc->sc_intr[pin]; +} + +static bool +ti_gpio_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen) +{ + struct ti_gpio_softc * const sc = device_private(dev); + + /* 1st cell is the pin */ + /* 2nd cell is flags */ + const u_int pin = be32toh(specifier[0]); + + if (pin >= __arraycount(sc->sc_pins)) + return false; + + snprintf(buf, buflen, "%s pin %d", sc->sc_modname, pin); + return true; +} + +static struct fdtbus_interrupt_controller_func ti_gpio_intrfuncs = { + .establish = ti_gpio_intr_establish, + .disestablish = ti_gpio_intr_disestablish, + .intrstr = ti_gpio_intrstr, +}; + static int ti_gpio_pin_read(void *priv, int pin) { @@ -263,6 +448,34 @@ ti_gpio_attach_ports(struct ti_gpio_soft } static int +ti_gpio_intr(void *priv) +{ + struct ti_gpio_softc * const sc = priv; + uint32_t status; + u_int bit; + int rv = 0; + + status = RD4(sc, GPIO_IRQSTATUS1); + WR4(sc, GPIO_IRQSTATUS1, status); + + while ((bit = ffs32(status)) != 0) { + const u_int pin = bit - 1; + const uint32_t pin_mask = __BIT(pin); + struct ti_gpio_intr *intr = &sc->sc_intr[pin]; + status &= ~pin_mask; + if (intr->intr_func == NULL) + continue; + if (!intr->intr_mpsafe) + KERNEL_LOCK(1, curlwp); + rv |= intr->intr_func(intr->intr_arg); + if (!intr->intr_mpsafe) + KERNEL_UNLOCK_ONE(curlwp); + } + + return rv; +} + +static int ti_gpio_match(device_t parent, cfdata_t cf, void *aux) { struct fdt_attach_args * const faa = aux; @@ -276,7 +489,7 @@ ti_gpio_attach(device_t parent, device_t struct ti_gpio_softc * const sc = device_private(self); struct fdt_attach_args * const faa = aux; const int phandle = faa->faa_phandle; - const char *modname; + char intrstr[128]; bus_addr_t addr; bus_size_t size; @@ -284,6 +497,10 @@ ti_gpio_attach(device_t parent, device_t aprint_error(": couldn't get registers\n"); return; } + if (!fdtbus_intr_str(phandle, 0, intrstr, sizeof(intrstr))) { + aprint_error(": couldn't decode interrupt\n"); + return; + } if (ti_prcm_enable_hwmod(phandle, 0) != 0) { aprint_error(": couldn't enable module\n"); return; @@ -295,17 +512,27 @@ ti_gpio_attach(device_t parent, device_t aprint_error(": couldn't map registers\n"); return; } - sc->sc_regoff = of_search_compatible(phandle, compat_data)->data; + sc->sc_type = of_search_compatible(phandle, compat_data)->data; mutex_init(&sc->sc_lock, MUTEX_DEFAULT, IPL_VM); - modname = fdtbus_get_string(phandle, "ti,hwmods"); - if (modname == NULL) - modname = fdtbus_get_string(OF_parent(phandle), "ti,hwmods"); + sc->sc_modname = fdtbus_get_string(phandle, "ti,hwmods"); + if (sc->sc_modname == NULL) + sc->sc_modname = fdtbus_get_string(OF_parent(phandle), "ti,hwmods"); aprint_naive("\n"); - aprint_normal(": GPIO (%s)\n", modname); + aprint_normal(": GPIO (%s)\n", sc->sc_modname); fdtbus_register_gpio_controller(self, phandle, &ti_gpio_funcs); ti_gpio_attach_ports(sc); + + sc->sc_ih = fdtbus_intr_establish(phandle, 0, IPL_VM, FDT_INTR_MPSAFE, + ti_gpio_intr, sc); + if (sc->sc_ih == NULL) { + aprint_error_dev(self, "failed to establish interrupt on %s\n", + intrstr); + return; + } + aprint_normal_dev(self, "interrupting on %s\n", intrstr); + fdtbus_register_interrupt_controller(self, phandle, &ti_gpio_intrfuncs); }