Index: sys/dev/amdgpio/amdgpio.h =================================================================== --- sys/dev/amdgpio/amdgpio.h +++ sys/dev/amdgpio/amdgpio.h @@ -43,6 +43,13 @@ #define AMD_GPIO_PINS_PER_BANK 64 #define AMD_GPIO_PINS_MAX 256 /* 4 banks * 64 pins */ +#define AMD_GPIO_PINS_PER_INTR_BIT 4 +#define AMD_GPIO_NUM_INTR_BITS 46 +#define AMD_GPIO_RESERVED_INTR_BIT 15 /* pins 60 - 63 */ +#define AMD_GPIO_INTR_MASK \ + ((((uint64_t)1 << AMD_GPIO_NUM_INTR_BITS) - 1) & \ + ~(1u << AMD_GPIO_RESERVED_INTR_BIT)) + /* Number of pins in each bank */ #define AMD_GPIO_PINS_BANK0 63 #define AMD_GPIO_PINS_BANK1 64 @@ -52,15 +59,18 @@ AMD_GPIO_PINS_BANK1 + \ AMD_GPIO_PINS_BANK2 + \ AMD_GPIO_PINS_BANK3) -#define AMDGPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT) +#define AMDGPIO_DEFAULT_CAPS \ + (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) +#define AMDGPIO_INTR_CAPS GPIO_INTR_MASK /* Register related macros */ #define AMDGPIO_PIN_REGISTER(pin) (pin * 4) #define WAKE_INT_MASTER_REG 0xfc #define EOI_MASK (1 << 29) -#define WAKE_INT_STATUS_REG0 0x2f8 -#define WAKE_INT_STATUS_REG1 0x2fc +#define INTR_EN_MASK (1 << 30) +#define INT_STATUS_REG0 0x2f8 +#define INT_STATUS_REG1 0x2fc /* Bit definition of 32 bits of each pin register */ #define DB_TMR_OUT_OFF 0 @@ -302,7 +312,7 @@ /* Macros for driver mutex locking */ #define AMDGPIO_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ - "amdgpio", MTX_SPIN) + "amdgpio", MTX_SPIN | MTX_RECURSE) #define AMDGPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define AMDGPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define AMDGPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) @@ -321,6 +331,10 @@ struct resource *sc_res[AMD_GPIO_NUM_PIN_BANK + 1]; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; + int sc_intr_en_count; + int sc_intr_rid; + struct resource *sc_intr_res; + void *sc_intr_handle; struct gpio_pin sc_gpio_pins[AMD_GPIO_PINS_MAX]; const struct pin_info *sc_pin_info; const struct amd_pingroup *sc_groups; Index: sys/dev/amdgpio/amdgpio.c =================================================================== --- sys/dev/amdgpio/amdgpio.c +++ sys/dev/amdgpio/amdgpio.c @@ -145,9 +145,7 @@ if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); - /* Set a very simple name */ - snprintf(name, GPIOMAXNAME, "%s", sc->sc_gpio_pins[pin].gp_name); - name[GPIOMAXNAME - 1] = '\0'; + strlcpy(name, sc->sc_gpio_pins[pin].gp_name, GPIOMAXNAME); dprintf("pin %d name %s\n", pin, name); @@ -198,7 +196,7 @@ amdgpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct amdgpio_softc *sc; - uint32_t reg, val, allowed; + uint32_t reg, val; sc = device_get_softc(dev); @@ -206,36 +204,47 @@ if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); - allowed = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; + /* Only supported flags are allowed. */ + if (flags & ~AMDGPIO_DEFAULT_CAPS) + return (EINVAL); - /* - * Only directtion flag allowed - */ - if (flags & ~allowed) + /* Either input our output must be selected. */ + if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == 0) return (EINVAL); - /* - * Not both directions simultaneously - */ - if ((flags & allowed) == allowed) + /* Not both directions simultaneously. */ + if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) == + (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) return (EINVAL); - /* Set the GPIO mode and state */ + /* No pull-up and pull-down at the same time. */ + if ((flags & (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) == + (GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)) + return (EINVAL); + AMDGPIO_LOCK(sc); reg = AMDGPIO_PIN_REGISTER(pin); val = amdgpio_read_4(sc, reg); - if (flags & GPIO_PIN_INPUT) { + if (flags & GPIO_PIN_INPUT) val &= ~BIT(OUTPUT_ENABLE_OFF); - sc->sc_gpio_pins[pin].gp_flags = GPIO_PIN_INPUT; - } else { + else val |= BIT(OUTPUT_ENABLE_OFF); - sc->sc_gpio_pins[pin].gp_flags = GPIO_PIN_OUTPUT; - } + if (flags & GPIO_PIN_PULLUP) + val |= BIT(PULL_UP_ENABLE_OFF); + else + val &= ~BIT(PULL_UP_ENABLE_OFF); + if (flags & GPIO_PIN_PULLDOWN) + val |= BIT(PULL_DOWN_ENABLE_OFF); + else + val &= ~BIT(PULL_DOWN_ENABLE_OFF); + amdgpio_write_4(sc, reg, val); + sc->sc_gpio_pins[pin].gp_flags = flags; + dprintf("pin %d flags 0x%x val 0x%x gp_flags 0x%x\n", pin, flags, val, sc->sc_gpio_pins[pin].gp_flags); @@ -346,7 +355,232 @@ return (0); } +static bool +amdgpio_valid_intr_pin(struct amdgpio_softc *sc, int pin) +{ + if (!amdgpio_valid_pin(sc, pin)) + return (false); + if ((sc->sc_gpio_pins[pin].gp_caps & GPIO_INTR_MASK) == GPIO_INTR_NONE) + return (false); + return (true); +} + +static void +amdgpio_pin_config_intr(device_t dev, uint32_t pin, uint32_t intr_mode) +{ + struct amdgpio_softc *sc; + uint32_t reg, val; + int trig, act; + + sc = device_get_softc(dev); + + dprintf("pin %d mode 0x%x\n", pin, mode); + KASSERT(amdgpio_valid_intr_pin(sc, pin), ("invalid pin")); + + /* XXX Linux also sets various debounce modes. */ + switch (intr_mode) { + case GPIO_INTR_EDGE_FALLING: + trig = EDGE_TRIGGER; + act = ACTIVE_LOW; + break; + case GPIO_INTR_EDGE_RISING: + trig = EDGE_TRIGGER; + act = ACTIVE_HIGH; + break; + case GPIO_INTR_EDGE_BOTH: + trig = EDGE_TRIGGER; + act = BOTH_EDGE; + break; + case GPIO_INTR_LEVEL_LOW: + trig = LEVEL_TRIGGER; + act = ACTIVE_LOW; + break; + case GPIO_INTR_LEVEL_HIGH: + trig = LEVEL_TRIGGER; + act = ACTIVE_HIGH; + break; + default: + KASSERT(0, ("invalid intr mode 0x%x", intr_mode)); + return; + } + + reg = AMDGPIO_PIN_REGISTER(pin); + + /* Set the GPIO mode and state */ + AMDGPIO_LOCK(sc); + val = amdgpio_read_4(sc, reg); + val &= ~(1 << LEVEL_TRIG_OFF); + val |= trig << LEVEL_TRIG_OFF; + val &= ~(3 << ACTIVE_LEVEL_OFF); + val |= act << ACTIVE_LEVEL_OFF; + amdgpio_write_4(sc, reg, val); + AMDGPIO_UNLOCK(sc); +} + +static void +amdgpio_pin_enable_intr(device_t dev, uint32_t pin) +{ + struct amdgpio_softc *sc; + uint32_t reg, val; + + sc = device_get_softc(dev); + + dprintf("pin %d enable intr\n", pin); + KASSERT(amdgpio_valid_intr_pin(sc, pin), ("invalid pin")); + + reg = AMDGPIO_PIN_REGISTER(pin); + + AMDGPIO_LOCK(sc); + val = amdgpio_read_4(sc, reg); + val |= BIT(INTERRUPT_STS_OFF); /* clear previous status as well */ + val |= BIT(INTERRUPT_ENABLE_OFF); + amdgpio_write_4(sc, reg, val); + sc->sc_intr_en_count++; + AMDGPIO_UNLOCK(sc); +} + +static void +amdgpio_pin_disable_intr(device_t dev, uint32_t pin) +{ + struct amdgpio_softc *sc; + uint32_t reg, val; + + sc = device_get_softc(dev); + + dprintf("pin %d disable intr\n", pin); + KASSERT(amdgpio_valid_intr_pin(sc, pin), ("invalid pin")); + + reg = AMDGPIO_PIN_REGISTER(pin); + + AMDGPIO_LOCK(sc); + val = amdgpio_read_4(sc, reg); + val &= ~BIT(INTERRUPT_ENABLE_OFF); + amdgpio_write_4(sc, reg, val); + sc->sc_intr_en_count--; + AMDGPIO_UNLOCK(sc); +} + +static void +amdgpio_pin_unmask_intr(device_t dev, uint32_t pin) +{ + struct amdgpio_softc *sc; + uint32_t reg, val; + + sc = device_get_softc(dev); + + dprintf("pin %d unmask intr\n", pin); + KASSERT(amdgpio_valid_intr_pin(sc, pin), ("invalid pin")); + + reg = AMDGPIO_PIN_REGISTER(pin); + + AMDGPIO_LOCK(sc); + val = amdgpio_read_4(sc, reg); + val |= BIT(INTERRUPT_MASK_OFF); + amdgpio_write_4(sc, reg, val); + AMDGPIO_UNLOCK(sc); +} + +static void +amdgpio_pin_mask_intr(device_t dev, uint32_t pin) +{ + struct amdgpio_softc *sc; + uint32_t reg, val; + + sc = device_get_softc(dev); + + dprintf("pin %d mask intr\n", pin); + KASSERT(amdgpio_valid_intr_pin(sc, pin), ("invalid pin")); + + reg = AMDGPIO_PIN_REGISTER(pin); + + AMDGPIO_LOCK(sc); + val = amdgpio_read_4(sc, reg); + val &= ~BIT(INTERRUPT_MASK_OFF); + val |= BIT(INTERRUPT_STS_OFF); /* clear status as well */ + amdgpio_write_4(sc, reg, val); + AMDGPIO_UNLOCK(sc); +} + +static void +amdgpio_pin_eoi(device_t dev, uint32_t pin) +{ + struct amdgpio_softc *sc; + uint32_t reg, val; + + sc = device_get_softc(dev); + + dprintf("pin %d eoi intr\n", pin); + KASSERT(amdgpio_valid_intr_pin(sc, pin), ("invalid pin")); + + reg = AMDGPIO_PIN_REGISTER(pin); + + AMDGPIO_LOCK(sc); + /* XXX Linux does EOI via WAKE_INT_MASTER_REG and EIO_MASK. */ + val = amdgpio_read_4(sc, reg); + val |= BIT(INTERRUPT_STS_OFF); + amdgpio_write_4(sc, reg, val); + AMDGPIO_UNLOCK(sc); +} + static int +amdgpio_intr_filter(void *arg) +{ + struct amdgpio_softc *sc = arg; + uint64_t status; + uint32_t status0, status1; + uint32_t reg, val; + int ret = FILTER_STRAY; + int i, j; + + AMDGPIO_LOCK(sc); + if (sc->sc_busdev == NULL) { + /* Too early interrupt ? */ + AMDGPIO_UNLOCK(sc); + return (ret); + } + + /* Merge into a single 46-bit status. */ + status0 = amdgpio_read_4(sc, INT_STATUS_REG0); + status1 = amdgpio_read_4(sc, INT_STATUS_REG1); + status = status1; + status <<= 32; + status |= status0; + status &= AMD_GPIO_INTR_MASK; + for (i = 0; i < AMD_GPIO_NUM_INTR_BITS; i++) { + if ((status & (1 << i)) == 0) + continue; + for (j = 0; j < AMD_GPIO_PINS_PER_INTR_BIT; j++) { + uint32_t pin; + + pin = i * AMD_GPIO_PINS_PER_INTR_BIT + j; + reg = AMDGPIO_PIN_REGISTER(pin); + val = amdgpio_read_4(sc, reg); + + /* + * XXX consider that WAKE_STS_OFF can be set but + * INTERRUPT_STS_OFF unset after a wakeup. + */ + if ((val & BIT(INTERRUPT_STS_OFF)) == 0) + continue; + + /* Ignore masked pins as well. */ + if ((val & BIT(INTERRUPT_MASK_OFF)) == 0) + continue; + gpiobus_handle_intr(sc->sc_busdev, pin); + ret = FILTER_HANDLED; + } + } + + /* Ready for more interrupts. */ + val = amdgpio_read_4(sc, WAKE_INT_MASTER_REG); + val |= EOI_MASK; + amdgpio_write_4(sc, WAKE_INT_MASTER_REG, val); + + AMDGPIO_UNLOCK(sc); + return (ret); +} + +static int amdgpio_probe(device_t dev) { static char *gpio_ids[] = { "AMD0030", "AMDI0030", NULL }; @@ -365,7 +599,9 @@ amdgpio_attach(device_t dev) { struct amdgpio_softc *sc; - int i, pin, bank; + device_t busdev; + int i, pin, bank, intrbit; + int err; sc = device_get_softc(dev); sc->sc_dev = dev; @@ -388,6 +624,23 @@ sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); + sc->sc_intr_rid = 0; + sc->sc_intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, + &sc->sc_intr_rid, RF_SHAREABLE | RF_ACTIVE); + if (sc->sc_intr_res != NULL) { + err = bus_setup_intr(dev, sc->sc_intr_res, INTR_TYPE_MISC, + amdgpio_intr_filter, NULL, sc, &sc->sc_intr_handle); + if (err != 0) { + device_printf(dev, "Unable to setup irq, error %d\n", + err); + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_intr_rid, + sc->sc_intr_res); + sc->sc_intr_res = NULL; + } else { + device_printf(dev, "pin interrupts supported\n"); + } + } + /* Initialize all possible pins to be Invalid */ for (i = 0; i < AMD_GPIO_PINS_MAX ; i++) { snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, @@ -400,25 +653,40 @@ /* Initialize only driver exposed pins with appropriate capabilities */ for (i = 0; i < AMD_GPIO_PINS_EXPOSED ; i++) { pin = kernzp_pins[i].pin_num; - bank = pin/AMD_GPIO_PINS_PER_BANK; - snprintf(sc->sc_gpio_pins[pin].gp_name, GPIOMAXNAME, "%s%d_%s", - AMD_GPIO_PREFIX, bank, kernzp_pins[i].pin_name); + bank = pin / AMD_GPIO_PINS_PER_BANK; + intrbit = pin / AMD_GPIO_PINS_PER_INTR_BIT; + snprintf(sc->sc_gpio_pins[pin].gp_name, GPIOMAXNAME, "%s", + kernzp_pins[i].pin_name); sc->sc_gpio_pins[pin].gp_pin = pin; sc->sc_gpio_pins[pin].gp_caps = AMDGPIO_DEFAULT_CAPS; + if (sc->sc_intr_handle != NULL && + intrbit < AMD_GPIO_NUM_INTR_BITS && + intrbit != AMD_GPIO_RESERVED_INTR_BIT) + sc->sc_gpio_pins[pin].gp_caps |= AMDGPIO_INTR_CAPS; sc->sc_gpio_pins[pin].gp_flags = amdgpio_is_pin_output(sc, pin) ? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT; } - sc->sc_busdev = gpiobus_attach_bus(dev); - if (sc->sc_busdev == NULL) { + busdev = gpiobus_attach_bus(dev); + if (busdev == NULL) { device_printf(dev, "could not attach gpiobus\n"); goto err_bus; } + AMDGPIO_LOCK(sc); + sc->sc_busdev = busdev; + AMDGPIO_UNLOCK(sc); + return (0); err_bus: + if (sc->sc_intr_handle) + bus_teardown_intr(dev, sc->sc_intr_res, sc->sc_intr_handle); + if (sc->sc_intr_res) { + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_intr_rid, + sc->sc_intr_res); + } bus_release_resources(dev, amdgpio_spec, sc->sc_res); err_rsrc: @@ -436,6 +704,12 @@ if (sc->sc_busdev) gpiobus_detach_bus(dev); + if (sc->sc_intr_handle) + bus_teardown_intr(dev, sc->sc_intr_res, sc->sc_intr_handle); + if (sc->sc_intr_res) { + bus_release_resource(dev, SYS_RES_IRQ, sc->sc_intr_rid, + sc->sc_intr_res); + } bus_release_resources(dev, amdgpio_spec, sc->sc_res); AMDGPIO_LOCK_DESTROY(sc); @@ -460,6 +734,13 @@ DEVMETHOD(gpio_pin_set, amdgpio_pin_set), DEVMETHOD(gpio_pin_toggle, amdgpio_pin_toggle), + /* GPIO interrupt controller interface. */ + DEVMETHOD(gpio_pin_config_intr, amdgpio_pin_config_intr), + DEVMETHOD(gpio_pin_enable_intr, amdgpio_pin_enable_intr), + DEVMETHOD(gpio_pin_disable_intr, amdgpio_pin_disable_intr), + DEVMETHOD(gpio_pin_mask_intr, amdgpio_pin_mask_intr), + DEVMETHOD(gpio_pin_unmask_intr, amdgpio_pin_unmask_intr), + DEVMETHOD(gpio_pin_eoi, amdgpio_pin_eoi), DEVMETHOD_END }; Index: sys/dev/gpio/gpio_if.m =================================================================== --- sys/dev/gpio/gpio_if.m +++ sys/dev/gpio/gpio_if.m @@ -186,3 +186,38 @@ uint32_t num_pins; uint32_t *pin_flags; } DEFAULT gpio_default_nosupport; + + +# +# Methods for interrupt handling on !INTRNG platforms. +# +METHOD void pin_config_intr { + device_t dev; + uint32_t pin_num; + uint32_t intr_mode; +}; + +METHOD void pin_enable_intr { + device_t dev; + uint32_t pin_num; +}; + +METHOD void pin_disable_intr { + device_t dev; + uint32_t pin_num; +}; + +METHOD void pin_eoi { + device_t dev; + uint32_t pin_num; +}; + +METHOD void pin_mask_intr { + device_t dev; + uint32_t pin_num; +}; + +METHOD void pin_unmask_intr { + device_t dev; + uint32_t pin_num; +}; Index: sys/dev/gpio/gpiobus.c =================================================================== --- sys/dev/gpio/gpiobus.c +++ sys/dev/gpio/gpiobus.c @@ -35,6 +35,13 @@ #include #ifdef INTRNG #include +#else +#include +#include +#include +#include +#include +#include #endif #include #include @@ -64,6 +71,11 @@ static int gpiobus_child_pnpinfo_str(device_t, device_t, char *, size_t); static device_t gpiobus_add_child(device_t, u_int, const char *, int); static void gpiobus_hinted_child(device_t, const char *, int); +#ifndef INTRNG +static void gpiobus_pic_init(struct gpiobus_softc *); +static void gpiobus_pic_destroy(struct gpiopic *); +static void gpiobus_pic_resume(struct gpiopic *); +#endif /* * GPIOBUS interface @@ -120,14 +132,6 @@ rman_set_virtual(res, gpio_data); return (res); } -#else -struct resource * -gpio_alloc_intr_resource(device_t consumer_dev, int *rid, u_int alloc_flags, - gpio_pin_t pin, uint32_t intr_mode) -{ - - return (NULL); -} #endif int @@ -341,20 +345,29 @@ sc = GPIOBUS_SOFTC(dev); sc->sc_busdev = dev; sc->sc_dev = device_get_parent(dev); - sc->sc_intr_rman.rm_type = RMAN_ARRAY; - sc->sc_intr_rman.rm_descr = "GPIO Interrupts"; - if (rman_init(&sc->sc_intr_rman) != 0 || - rman_manage_region(&sc->sc_intr_rman, 0, ~0) != 0) - panic("%s: failed to set up rman.", __func__); if (GPIO_PIN_MAX(sc->sc_dev, &sc->sc_npins) != 0) return (ENXIO); - KASSERT(sc->sc_npins >= 0, ("GPIO device with no pins")); /* Pins = GPIO_PIN_MAX() + 1 */ sc->sc_npins++; + sc->sc_intr_rman.rm_type = RMAN_ARRAY; + sc->sc_intr_rman.rm_descr = "GPIO Interrupts"; +#ifndef INTRNG + sc->sc_intr_rman.rm_start = 0; + sc->sc_intr_rman.rm_end = sc->sc_npins - 1; +#endif + if (rman_init(&sc->sc_intr_rman) != 0) + panic("%s: failed to set up rman.", __func__); +#ifdef INTRNG + if (rman_manage_region(&sc->sc_intr_rman, 0, ~0) != 0) +#else + if (rman_manage_region(&sc->sc_intr_rman, 0, sc->sc_npins - 1) != 0) +#endif + panic("%s: failed to set up rman.", __func__); + sc->sc_pins = malloc(sizeof(*sc->sc_pins) * sc->sc_npins, M_DEVBUF, M_NOWAIT | M_ZERO); if (sc->sc_pins == NULL) @@ -363,6 +376,9 @@ /* Initialize the bus lock. */ GPIOBUS_LOCK_INIT(sc); +#ifndef INTRNG + gpiobus_pic_init(sc); +#endif return (0); } @@ -459,6 +475,10 @@ GPIOBUS_PIN_SETNAME(dev, devi->pins[i], device_get_nameunit(child)); + /* Set pin as a potential interrupt resource for the child. */ + resource_list_add(&devi->rl, SYS_RES_IRQ, i, devi->pins[i], + devi->pins[i], 1); + } return (0); } @@ -581,13 +601,14 @@ { struct gpiobus_softc *sc; struct gpiobus_ivar *devi; + struct gpiopic *pic; device_t *devlist; int i, err, ndevs; sc = GPIOBUS_SOFTC(dev); + pic = sc->sc_pic; KASSERT(mtx_initialized(&sc->sc_mtx), ("gpiobus mutex not initialized")); - GPIOBUS_LOCK_DESTROY(sc); if ((err = bus_generic_detach(dev)) != 0) return (err); @@ -613,6 +634,10 @@ sc->sc_pins = NULL; } +#ifndef INTRNG + gpiobus_pic_destroy(pic); +#endif + GPIOBUS_LOCK_DESTROY(sc); return (0); } @@ -626,7 +651,10 @@ static int gpiobus_resume(device_t dev) { + struct gpiobus_softc *sc; + sc = GPIOBUS_SOFTC(dev); + gpiobus_pic_resume(sc->sc_pic); return (bus_generic_resume(dev)); } @@ -778,6 +806,16 @@ dprintf("%s: entry (%p, %p, %d, %d, %p, %ld)\n", __func__, dev, child, type, rid, (void *)(intptr_t)start, count); + + /* + * FIXME support indirect discendants and non-IRQ resources. + * The request should be forwarded upwards for those. + */ + if (device_get_parent(child) != dev) + return (EINVAL); + if (type != SYS_RES_IRQ) + return (EINVAL); + devi = GPIOBUS_IVAR(child); rle = resource_list_add(&devi->rl, type, rid, start, start + count - 1, count); @@ -842,8 +880,18 @@ return (0); } +/* + * For now, this method supports only requests for GPIO IRQ resources. + * The requesting device, a consumer, does not have to be a child or + * a descendant of the bus device. + * + * FIXME + * The method does not support non-IRQ resources or non-GPIO IRQ resources + * even for its children. This should be fixed as soon as there is + * a child driver needs access to system resources. + */ static struct resource * -gpiobus_alloc_resource(device_t bus, device_t child, int type, int *rid, +gpiobus_alloc_resource(device_t bus, device_t consumer, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct gpiobus_softc *sc; @@ -854,10 +902,10 @@ if (type != SYS_RES_IRQ) return (NULL); - isdefault = (RMAN_IS_DEFAULT_RANGE(start, end) && count == 1); - rle = NULL; + isdefault = RMAN_IS_DEFAULT_RANGE(start, end) && count == 1 && + device_get_parent(consumer) == bus;; if (isdefault) { - rl = BUS_GET_RESOURCE_LIST(bus, child); + rl = BUS_GET_RESOURCE_LIST(bus, consumer); if (rl == NULL) return (NULL); rle = resource_list_find(rl, type, *rid); @@ -871,12 +919,11 @@ } sc = device_get_softc(bus); rv = rman_reserve_resource(&sc->sc_intr_rman, start, end, count, flags, - child); + consumer); if (rv == NULL) return (NULL); rman_set_rid(rv, *rid); - if ((flags & RF_ACTIVE) != 0 && - bus_activate_resource(child, type, *rid, rv) != 0) { + if ((flags & RF_ACTIVE) != 0 && rman_activate_resource(rv) != 0) { rman_release_resource(rv); return (NULL); } @@ -885,13 +932,17 @@ } static int -gpiobus_release_resource(device_t bus __unused, device_t child, int type, +gpiobus_release_resource(device_t bus, device_t consumer, int type, int rid, struct resource *r) { + struct gpiobus_softc *sc; int error; + sc = device_get_softc(bus); + if (!rman_is_region_manager(r, &sc->sc_intr_rman)) + return (EINVAL); if (rman_get_flags(r) & RF_ACTIVE) { - error = bus_deactivate_resource(child, type, rid, r); + error = rman_deactivate_resource(r); if (error) return (error); } @@ -910,7 +961,7 @@ } static int -gpiobus_acquire_bus(device_t busdev, device_t child, int how) +gpiobus_acquire_bus(device_t busdev, device_t consumer, int how) { struct gpiobus_softc *sc; @@ -918,10 +969,10 @@ GPIOBUS_ASSERT_UNLOCKED(sc); GPIOBUS_LOCK(sc); if (sc->sc_owner != NULL) { - if (sc->sc_owner == child) + if (sc->sc_owner == consumer) panic("%s: %s still owns the bus.", device_get_nameunit(busdev), - device_get_nameunit(child)); + device_get_nameunit(consumer)); if (how == GPIOBUS_DONTWAIT) { GPIOBUS_UNLOCK(sc); return (EWOULDBLOCK); @@ -929,14 +980,14 @@ while (sc->sc_owner != NULL) mtx_sleep(sc, &sc->sc_mtx, 0, "gpiobuswait", 0); } - sc->sc_owner = child; + sc->sc_owner = consumer; GPIOBUS_UNLOCK(sc); return (0); } static void -gpiobus_release_bus(device_t busdev, device_t child) +gpiobus_release_bus(device_t busdev, device_t consumer) { struct gpiobus_softc *sc; @@ -946,11 +997,11 @@ if (sc->sc_owner == NULL) panic("%s: %s releasing unowned bus.", device_get_nameunit(busdev), - device_get_nameunit(child)); - if (sc->sc_owner != child) + device_get_nameunit(consumer)); + if (sc->sc_owner != consumer) panic("%s: %s trying to release bus owned by %s", device_get_nameunit(busdev), - device_get_nameunit(child), + device_get_nameunit(consumer), device_get_nameunit(sc->sc_owner)); sc->sc_owner = NULL; wakeup(sc); @@ -1076,6 +1127,519 @@ return (0); } +#ifndef INTRNG + +struct gpiopic; + +struct gpiopic_intsrc { + struct gpiopic *is_pic; + struct intr_event *is_event; + u_long *is_count; /* TODO expose to userland. */ + u_long *is_straycount; /* TODO expose to userland. */ + int is_handlers; + u_int is_pin; + u_int is_mode; + u_int is_enabled:1; + u_int is_masked:1; +}; + +struct gpiopic { + struct sx intrsrc_lock; + struct gpiobus_softc *sc; + struct gpiopic_intsrc *intr_srcs; + u_long *intr_counts; + +}; + +static int +sysctl_gpio_intrs(SYSCTL_HANDLER_ARGS) +{ + struct sbuf sbuf; + struct gpiopic *pic = arg1; + struct gpiopic_intsrc *isrc; + u_int i; + int error; + + error = sysctl_wire_old_buffer(req, 0); + if (error != 0) + return (error); + + sbuf_new_for_sysctl(&sbuf, NULL, 128, req); + sx_slock(&pic->intrsrc_lock); + for (i = 0; i < pic->sc->sc_npins; i++) { + isrc = &pic->intr_srcs[i]; + if (isrc->is_event == NULL) + continue; + sbuf_printf(&sbuf, "%s:%u (mode 0x%08x): %lu\n", + isrc->is_event->ie_fullname, + isrc->is_pin, + isrc->is_mode, + *isrc->is_count); + } + + sx_sunlock(&pic->intrsrc_lock); + error = sbuf_finish(&sbuf); + sbuf_delete(&sbuf); + return (error); +} + +static int +gpiopic_assign_cpu(void *arg, int cpu) +{ + /* + * This could be made to work if only a single pin is configured + * to be an interrupt source but not in general case. + * At this time there does not appear to be a case for binding + * GPIO interrupts, so not bothering with the only case that could + * be made to work. + */ + return (EOPNOTSUPP); +} + +static void +gpiopic_unmask_intr(void *arg) +{ + struct gpiopic_intsrc *gisrc = arg; + + gisrc->is_masked = 0; + GPIO_PIN_UNMASK_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin); +} + +static void +gpiopic_mask_intr(void *arg) +{ + struct gpiopic_intsrc *gisrc = arg; + + gisrc->is_masked = 1; + GPIO_PIN_MASK_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin); +} + +static void +gpiopic_eoi(void *arg) +{ + struct gpiopic_intsrc *gisrc = arg; + + GPIO_PIN_EOI(gisrc->is_pic->sc->sc_dev, gisrc->is_pin); +} + +static void +gpiopic_enable_intr(struct gpiopic_intsrc *gisrc) +{ + gisrc->is_enabled = 1; + GPIO_PIN_ENABLE_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin); +} + +static void +gpiopic_disable_intr(struct gpiopic_intsrc *gisrc) +{ + gisrc->is_enabled = 0; + GPIO_PIN_DISABLE_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin); +} + +static void +gpiopic_config_intr(struct gpiopic_intsrc *gisrc, uint32_t intr_mode) { + gisrc->is_mode = intr_mode; + GPIO_PIN_CONFIG_INTR(gisrc->is_pic->sc->sc_dev, gisrc->is_pin, + intr_mode); +} + +static int +gpiopic_check_intr_pin(struct gpiopic *gpiopic, uint32_t pin) +{ + struct gpiopic_intsrc *gisrc; + + if (gpiopic == NULL) + return (ENOENT); + if (pin >= gpiopic->sc->sc_npins) + return (ENOENT); + gisrc = &gpiopic->intr_srcs[pin]; + if (gisrc->is_event == NULL) + return (ENOENT); + return (0); +} + +static int +gpiopic_register_sources(struct gpiopic *gpiopic) +{ + char name[GPIOMAXNAME]; + struct gpiobus_softc *sc = gpiopic->sc; + struct gpiopic_intsrc *gisrc; + int i, count; + int err; + + count = 0; + for (i = 0; i < sc->sc_npins; i++) { + uint32_t pincaps; + + err = GPIO_PIN_GETCAPS(sc->sc_dev, i, &pincaps); + if (err != 0) + continue; + if ((pincaps & GPIO_INTR_MASK) == GPIO_INTR_NONE) + continue; + + gisrc = &gpiopic->intr_srcs[i]; + gisrc->is_pic = gpiopic; + gisrc->is_pin = i; + gisrc->is_count = &gpiopic->intr_counts[i * 2]; + gisrc->is_straycount = &gpiopic->intr_counts[i * 2 + 1]; + gisrc->is_enabled = 0; + gisrc->is_mode = GPIO_INTR_CONFORM; + + /* + * We use IE_BUS_PRIV to indicate that this interrupt event is + * completely private to this bus. It's managed by the bus and + * it is not visible to the global interrupt management. + * As a consequence, its number / vector is in the private + * space and is meaningless in the global space. + */ + (void)gpiobus_pin_getname(gpiopic->sc->sc_busdev, i, name); + err = intr_event_create(&gisrc->is_event, gisrc, IE_BUS_PRIV, + i, + gpiopic_mask_intr, /* pre_ithread */ + gpiopic_unmask_intr, /* post_ithread */ + gpiopic_eoi, /* post_filter */ + gpiopic_assign_cpu, + "%s", name); + if (err != 0) { + device_printf(sc->sc_busdev, "gpiopic failed to " + "create interrupt event for pin %u: %d\n", i, err); + continue; + } + count++; + } + return (count); +} + +static void +gpiobus_pic_init(struct gpiobus_softc *sc) +{ + + struct sysctl_ctx_list *ctx; + struct sysctl_oid *tree_node; + struct sysctl_oid_list *tree; + struct gpiopic *gpiopic; + int i, count; + int err; + + for (i = 0; i < sc->sc_npins; i++) { + uint32_t pincaps; + + err = GPIO_PIN_GETCAPS(sc->sc_dev, i, &pincaps); + if (err != 0) + continue; + if ((pincaps & GPIO_INTR_MASK) != GPIO_INTR_NONE) + break; + } + if (i == sc->sc_npins) + return; + + gpiopic = malloc(sizeof(struct gpiopic), M_DEVBUF, M_NOWAIT | M_ZERO); + if (gpiopic == NULL) { + device_printf(sc->sc_busdev, "gpiopic allocation failed\n"); + return; + } + + gpiopic->intr_srcs = malloc(sc->sc_npins * + sizeof(struct gpiopic_intsrc), M_DEVBUF, M_NOWAIT | M_ZERO); + if (gpiopic->intr_srcs == NULL) { + free(gpiopic, M_DEVBUF); + device_printf(sc->sc_busdev, "gpiopic allocation failed\n"); + return; + } + + gpiopic->intr_counts = malloc(2 * sc->sc_npins * + sizeof(*gpiopic->intr_counts), M_DEVBUF, M_NOWAIT | M_ZERO); + if (gpiopic->intr_counts == NULL) { + free(gpiopic->intr_srcs, M_DEVBUF); + free(gpiopic, M_DEVBUF); + device_printf(sc->sc_busdev, "gpiopic allocation failed\n"); + return; + } + + sx_init(&gpiopic->intrsrc_lock, "gpiopic lock"); + gpiopic->sc = sc; + count = gpiopic_register_sources(gpiopic); + sc->sc_pic = gpiopic; + device_printf(sc->sc_busdev, "initialized %d interrupt sources\n", + count); + + ctx = device_get_sysctl_ctx(sc->sc_busdev); + tree_node = device_get_sysctl_tree(sc->sc_busdev); + tree = SYSCTL_CHILDREN(tree_node); + SYSCTL_ADD_PROC(ctx, tree, OID_AUTO, "interrupts", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, + gpiopic, 0, sysctl_gpio_intrs, "A", + "interrupt:pin (current mode): count"); +} + +static void +gpiobus_pic_destroy(struct gpiopic *gpiopic) +{ + struct gpiopic_intsrc *gisrc; + int i; + + if (gpiopic == NULL) + return; + + sx_xlock(&gpiopic->intrsrc_lock); + for (i = 0; i < gpiopic->sc->sc_npins; i++) { + gisrc = &gpiopic->intr_srcs[i]; + if (gisrc->is_event == NULL) + continue; + /* XXX what to do if there is a busy event? */ + (void)intr_event_destroy(gisrc->is_event); + } + sx_xunlock(&gpiopic->intrsrc_lock); + + sx_destroy(&gpiopic->intrsrc_lock); + free(gpiopic->intr_counts, M_DEVBUF); + free(gpiopic->intr_srcs, M_DEVBUF); + free(gpiopic, M_DEVBUF); +} + +static void +gpiobus_pic_resume(struct gpiopic *gpiopic) +{ + struct gpiopic_intsrc *gisrc; + int i; + + if (gpiopic == NULL) + return; + + sx_xlock(&gpiopic->intrsrc_lock); + for (i = 0; i < gpiopic->sc->sc_npins; i++) { + gisrc = &gpiopic->intr_srcs[i]; + if (gisrc->is_event == NULL) + continue; + + /* Just in case. */ + gpiopic_disable_intr(gisrc); + + if (!gisrc->is_enabled) + continue; + + if (gisrc->is_mode != GPIO_INTR_CONFORM) + gpiopic_config_intr(gisrc, gisrc->is_mode); + if (gisrc->is_masked) + gpiopic_mask_intr(gisrc); + else + gpiopic_unmask_intr(gisrc); + gpiopic_enable_intr(gisrc); + } + sx_xunlock(&gpiopic->intrsrc_lock); +} + +#define GPIO_MAX_STRAY 10 + +void +gpiobus_handle_intr(device_t busdev, uint32_t pin) +{ + struct gpiobus_softc *sc; + struct gpiopic *gpiopic; + struct gpiopic_intsrc *gisrc; + struct intr_event *ie; + + sc = device_get_softc(busdev); + gpiopic = sc->sc_pic; + + KASSERT(pin < sc->sc_npins, + ("%s for unsupported pin %u", __func__, pin)); + gisrc = &gpiopic->intr_srcs[pin]; + ie = gisrc->is_event; + + (*gisrc->is_count)++; + + /* + * For stray interrupts, mask the source, bump the + * stray count, and log the condition. + */ + if (intr_event_handle(ie, curthread->td_intr_frame) != 0) { + gpiopic_mask_intr(gisrc); + gpiopic_eoi(gisrc); + (*gisrc->is_straycount)++; + if (*gisrc->is_straycount < GPIO_MAX_STRAY) { + log(LOG_ERR, "stray irq on pin %u\n", pin); + } else if (*gisrc->is_straycount == GPIO_MAX_STRAY) { + log(LOG_CRIT, "too many stray irq's on pin %u: " + "not logging anymore\n", pin); + } + } +} + +static int +gpiopic_add_handler(const char *name, struct gpiopic *gpiopic, uint32_t pin, + driver_filter_t filter, driver_intr_t handler, + void *arg, enum intr_type flags, void **cookiep) +{ + struct gpiopic_intsrc *gisrc; + int err; + + KASSERT(gpiopic_check_intr_pin(gpiopic, pin) == 0, + ("setup_intr for unsupported pin %u", pin)); + gisrc = &gpiopic->intr_srcs[pin]; + err = intr_event_add_handler(gisrc->is_event, + name, filter, handler, arg, + intr_priority(flags), flags, cookiep); + if (err == 0) { + sx_xlock(&gpiopic->intrsrc_lock); + gisrc->is_handlers++; + if (gisrc->is_handlers == 1) { + gpiopic_enable_intr(gisrc); + gpiopic_unmask_intr(gisrc); /* unmask source */ + } + sx_xunlock(&gpiopic->intrsrc_lock); + } + return (err); +} + +static int +gpiobus_setup_intr(device_t bus, device_t dev, struct resource *irq, + int flags, driver_filter_t filter, void (*ihand)(void *), + void *arg, void **cookiep) +{ + struct gpiobus_softc *sc; + int err; + + sc = device_get_softc(bus); + if (!rman_is_region_manager(irq, &sc->sc_intr_rman)) + return (EINVAL); + + *cookiep = NULL; + if ((rman_get_flags(irq) & RF_SHAREABLE) == 0) + flags |= INTR_EXCL; + + err = rman_activate_resource(irq); + if (err != 0) + return (err); + + err = gpiopic_add_handler(device_get_nameunit(dev), sc->sc_pic, + rman_get_start(irq), filter, ihand, arg, flags, cookiep); + return (err); +} + +static int +gpiopic_remove_handler(void *cookie) +{ + struct gpiopic *gpiopic; + struct gpiopic_intsrc *gisrc; + int err; + + gisrc = intr_handler_source(cookie); + if (gisrc == NULL) + return (EINVAL); + + gpiopic = gisrc->is_pic; + err = intr_event_remove_handler(cookie); + if (err == 0) { + sx_xlock(&gpiopic->intrsrc_lock); + gisrc->is_handlers--; + if (gisrc->is_handlers == 0) { + gpiopic_mask_intr(gisrc); /* mask source */ + gpiopic_disable_intr(gisrc); + } + sx_xunlock(&gpiopic->intrsrc_lock); + } + return (err); +} + +static int +gpiobus_teardown_intr(device_t bus, device_t dev, struct resource *r, + void *ih) +{ + struct gpiobus_softc *sc; + int err; + + sc = device_get_softc(bus); + if (!rman_is_region_manager(r, &sc->sc_intr_rman)) + return (EINVAL); + err = gpiopic_remove_handler(ih); + return (err); +} + +static int +gpiopic_describe(void *ih, const char *descr) +{ + struct gpiopic_intsrc *gisrc; + int err; + + gisrc = intr_handler_source(ih); + if (gisrc == NULL) + return (EINVAL); + err = intr_event_describe_handler(gisrc->is_event, ih, descr); + return (err); +} + +static int +gpiobus_describe_intr(device_t bus, device_t child, struct resource *irq, + void *cookie, const char *descr) +{ + struct gpiobus_softc *sc; + int err; + + sc = device_get_softc(bus); + if (!rman_is_region_manager(irq, &sc->sc_intr_rman)) + return (EINVAL); + err = gpiopic_describe(cookie, descr); + return (err); +} + +static int +gpiobus_config_intr(device_t dev, int irq, enum intr_trigger trig, + enum intr_polarity pol) +{ + return (EOPNOTSUPP); +} + +struct resource * +gpio_alloc_intr_resource(device_t consumer_dev, int *rid, u_int alloc_flags, + gpio_pin_t pin, uint32_t intr_mode) +{ + struct resource *res; + struct gpiobus_softc *sc; + struct gpiopic_intsrc *gisrc; + device_t busdev; + uint32_t caps; + int err; + + switch (intr_mode) { + case GPIO_INTR_EDGE_FALLING: + case GPIO_INTR_EDGE_RISING: + case GPIO_INTR_EDGE_BOTH: + case GPIO_INTR_LEVEL_LOW: + case GPIO_INTR_LEVEL_HIGH: + case GPIO_INTR_CONFORM: + break; + default: + return (NULL); + } + + err = GPIO_PIN_GETCAPS(pin->dev, pin->pin, &caps); + if (err != 0) + return (NULL); + if ((intr_mode & caps) == 0) + return (NULL); + + busdev = GPIO_GET_BUS(pin->dev); + sc = device_get_softc(busdev); + err = gpiopic_check_intr_pin(sc->sc_pic, pin->pin); + if (err != 0) { + device_printf(busdev, "PIC not set up or bad pin %u\n", + pin->pin); + return (NULL); + } + + KASSERT(sc->sc_pins[pin->pin].mapped, + ("%s: unmapped pin %u", __func__, pin->pin)); + res = BUS_ALLOC_RESOURCE(busdev, consumer_dev, SYS_RES_IRQ, rid, + pin->pin, pin->pin, 1, alloc_flags); + if (res != NULL && intr_mode != GPIO_INTR_CONFORM) { + gisrc = &sc->sc_pic->intr_srcs[pin->pin]; + gpiopic_config_intr(gisrc, intr_mode); + } + return (res); +} +#endif /* !INTRNG */ + static device_method_t gpiobus_methods[] = { /* Device interface */ DEVMETHOD(device_probe, gpiobus_probe), @@ -1086,9 +1650,16 @@ DEVMETHOD(device_resume, gpiobus_resume), /* Bus interface */ +#ifdef INTRNG DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_config_intr, bus_generic_config_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), +#else + DEVMETHOD(bus_setup_intr, gpiobus_setup_intr), + DEVMETHOD(bus_config_intr, gpiobus_config_intr), + DEVMETHOD(bus_describe_intr, gpiobus_describe_intr), + DEVMETHOD(bus_teardown_intr, gpiobus_teardown_intr), +#endif DEVMETHOD(bus_set_resource, gpiobus_set_resource), DEVMETHOD(bus_alloc_resource, gpiobus_alloc_resource), DEVMETHOD(bus_release_resource, gpiobus_release_resource), Index: sys/dev/gpio/gpiobusvar.h =================================================================== --- sys/dev/gpio/gpiobusvar.h +++ sys/dev/gpio/gpiobusvar.h @@ -84,10 +84,13 @@ }; #endif +struct gpiopic; + struct gpiobus_softc { struct mtx sc_mtx; /* bus mutex */ struct rman sc_intr_rman; /* isr resources */ + struct gpiopic *sc_pic; /* interrupt controller logic */ device_t sc_busdev; /* bus device */ device_t sc_owner; /* bus owner */ device_t sc_dev; /* driver device */ @@ -176,6 +179,7 @@ int gpio_check_flags(uint32_t, uint32_t); device_t gpiobus_attach_bus(device_t); int gpiobus_detach_bus(device_t); +void gpiobus_handle_intr(device_t, uint32_t); int gpiobus_init_softc(device_t); int gpiobus_alloc_ivars(struct gpiobus_ivar *); void gpiobus_free_ivars(struct gpiobus_ivar *); Index: sys/kern/kern_intr.c =================================================================== --- sys/kern/kern_intr.c +++ sys/kern/kern_intr.c @@ -261,8 +261,8 @@ struct intr_event *ie; va_list ap; - /* The only valid flag during creation is IE_SOFT. */ - if ((flags & ~IE_SOFT) != 0) + /* Check for internal flags. */ + if ((flags & ~(IE_SOFT | IE_BUS_PRIV)) != 0) return (EINVAL); ie = malloc(sizeof(struct intr_event), M_ITHREAD, M_WAITOK | M_ZERO); ie->ie_source = source; @@ -413,6 +413,7 @@ TAILQ_FOREACH(ie, &event_list, ie_list) if (ie->ie_irq == irq && (ie->ie_flags & IE_SOFT) == 0 && + (ie->ie_flags & IE_BUS_PRIV) == 0 && CK_SLIST_FIRST(&ie->ie_handlers) != NULL) break; mtx_unlock(&event_lock); Index: sys/sys/interrupt.h =================================================================== --- sys/sys/interrupt.h +++ sys/sys/interrupt.h @@ -132,6 +132,9 @@ /* Interrupt event flags kept in ie_flags. */ #define IE_SOFT 0x000001 /* Software interrupt. */ #define IE_ADDING_THREAD 0x000004 /* Currently building an ithread. */ +#define IE_BUS_PRIV 0x000008 /* Interrupt is handled at bus level, + invisible to nexus and MD interrupt + code. */ /* Flags to pass to swi_sched. */ #define SWI_FROMNMI 0x1