Index: sys/arm/broadcom/bcm2835/bcm2835_gpio.c =================================================================== --- sys/arm/broadcom/bcm2835/bcm2835_gpio.c +++ sys/arm/broadcom/bcm2835/bcm2835_gpio.c @@ -39,6 +39,7 @@ #include #include #include +#include #include #include @@ -48,6 +49,7 @@ #include #include +#include #include #include @@ -62,10 +64,20 @@ #define dprintf(fmt, args...) #endif +#define BCM_GPIO_IRQS 4 #define BCM_GPIO_PINS 54 +#define BCM_GPIO_PINS_PER_BANK 32 #define BCM_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \ GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN) +static struct resource_spec bcm_gpio_irq_spec[] = { + { SYS_RES_IRQ, 0, RF_ACTIVE }, + { SYS_RES_IRQ, 1, RF_ACTIVE }, + { SYS_RES_IRQ, 2, RF_ACTIVE }, + { SYS_RES_IRQ, 3, RF_ACTIVE }, + { -1, 0, 0 } +}; + struct bcm_gpio_sysctl { struct bcm_gpio_softc *sc; uint32_t pin; @@ -75,15 +87,18 @@ device_t sc_dev; struct mtx sc_mtx; struct resource * sc_mem_res; - struct resource * sc_irq_res; + struct resource * sc_irq_res[BCM_GPIO_IRQS]; bus_space_tag_t sc_bst; bus_space_handle_t sc_bsh; - void * sc_intrhand; + void * sc_intrhand[BCM_GPIO_IRQS]; int sc_gpio_npins; int sc_ro_npins; int sc_ro_pins[BCM_GPIO_PINS]; struct gpio_pin sc_gpio_pins[BCM_GPIO_PINS]; + struct intr_event * sc_events[BCM_GPIO_PINS]; struct bcm_gpio_sysctl sc_sysctl[BCM_GPIO_PINS]; + enum intr_trigger sc_irq_trigger[BCM_GPIO_PINS]; + enum intr_polarity sc_irq_polarity[BCM_GPIO_PINS]; }; enum bcm_gpio_pud { @@ -92,21 +107,39 @@ BCM_GPIO_PULLUP, }; -#define BCM_GPIO_LOCK(_sc) mtx_lock(&_sc->sc_mtx) -#define BCM_GPIO_UNLOCK(_sc) mtx_unlock(&_sc->sc_mtx) +#define BCM_GPIO_LOCK(_sc) mtx_lock_spin(&_sc->sc_mtx) +#define BCM_GPIO_UNLOCK(_sc) mtx_unlock_spin(&_sc->sc_mtx) #define BCM_GPIO_LOCK_ASSERT(_sc) mtx_assert(&_sc->sc_mtx, MA_OWNED) -#define BCM_GPIO_GPFSEL(_bank) 0x00 + _bank * 4 -#define BCM_GPIO_GPSET(_bank) 0x1c + _bank * 4 -#define BCM_GPIO_GPCLR(_bank) 0x28 + _bank * 4 -#define BCM_GPIO_GPLEV(_bank) 0x34 + _bank * 4 -#define BCM_GPIO_GPPUD(_bank) 0x94 -#define BCM_GPIO_GPPUDCLK(_bank) 0x98 + _bank * 4 +#define BCM_GPIO_GPFSEL(_bank) (0x00 + _bank * 4) /* Function Select */ +#define BCM_GPIO_GPSET(_bank) (0x1c + _bank * 4) /* Pin Out Set */ +#define BCM_GPIO_GPCLR(_bank) (0x28 + _bank * 4) /* Pin Out Clear */ +#define BCM_GPIO_GPLEV(_bank) (0x34 + _bank * 4) /* Pin Level */ +#define BCM_GPIO_GPEDS(_bank) (0x40 + _bank * 4) /* Event Status */ +#define BCM_GPIO_GPREN(_bank) (0x4c + _bank * 4) /* Rising Edge isr */ +#define BCM_GPIO_GPFEN(_bank) (0x58 + _bank * 4) /* Falling Edge isr */ +#define BCM_GPIO_GPHEN(_bank) (0x64 + _bank * 4) /* High Level isr */ +#define BCM_GPIO_GPLEN(_bank) (0x70 + _bank * 4) /* Low Level isr */ +#define BCM_GPIO_GPAREN(_bank) (0x7c + _bank * 4) /* Async Rising Edge */ +#define BCM_GPIO_GPAFEN(_bank) (0x88 + _bank * 4) /* Async Falling Egde */ +#define BCM_GPIO_GPPUD(_bank) (0x94) /* Pin Pull up/down */ +#define BCM_GPIO_GPPUDCLK(_bank) (0x98 + _bank * 4) /* Pin Pull up clock */ #define BCM_GPIO_WRITE(_sc, _off, _val) \ bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val) #define BCM_GPIO_READ(_sc, _off) \ bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off) +#define BCM_GPIO_CLEAR_BITS(_sc, _off, _bits) \ + BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) & ~(_bits)) +#define BCM_GPIO_SET_BITS(_sc, _off, _bits) \ + BCM_GPIO_WRITE(_sc, _off, BCM_GPIO_READ(_sc, _off) | _bits) + +#define BCM_GPIO_BANK(a) (a / BCM_GPIO_PINS_PER_BANK) +#define BCM_GPIO_MASK(a) (1U << (a % BCM_GPIO_PINS_PER_BANK)) + +static int bcm_gpio_intr(void *); + +static struct bcm_gpio_softc *bcm_gpio_sc = NULL; static int bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin) @@ -686,16 +719,52 @@ } static int +bcm_gpio_intr_attach(device_t dev) +{ + struct bcm_gpio_softc *sc; + int i; + + sc = device_get_softc(dev); + for (i = 0; i < BCM_GPIO_IRQS; i++) { + if (bus_setup_intr(dev, sc->sc_irq_res[i], INTR_TYPE_MISC, + bcm_gpio_intr, NULL, sc, &sc->sc_intrhand[i]) != 0) { + return (-1); + } + } + + return (0); +} + +static void +bcm_gpio_intr_detach(device_t dev) +{ + struct bcm_gpio_softc *sc; + int i; + + sc = device_get_softc(dev); + for (i = 0; i < BCM_GPIO_IRQS; i++) { + if (sc->sc_intrhand[i]) { + bus_teardown_intr(dev, sc->sc_irq_res[i], + sc->sc_intrhand[i]); + } + } +} + +static int bcm_gpio_attach(device_t dev) { - struct bcm_gpio_softc *sc = device_get_softc(dev); + struct bcm_gpio_softc *sc; uint32_t func; int i, j, rid; phandle_t gpio; + if (bcm_gpio_sc != NULL) + return (ENXIO); + + bcm_gpio_sc = sc = device_get_softc(dev); sc->sc_dev = dev; - mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_DEF); + mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_SPIN); rid = 0; sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, @@ -708,15 +777,19 @@ sc->sc_bst = rman_get_bustag(sc->sc_mem_res); sc->sc_bsh = rman_get_bushandle(sc->sc_mem_res); - rid = 0; - sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, - RF_ACTIVE); - if (!sc->sc_irq_res) { + /* Request the IRQ resources. */ + if (bus_alloc_resources(dev, bcm_gpio_irq_spec, sc->sc_irq_res)) { bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); - device_printf(dev, "cannot allocate interrupt\n"); + device_printf(dev, "cannot allocate irq resources\n"); return (ENXIO); } + /* Setup the GPIO interrupt handler. */ + if (bcm_gpio_intr_attach(dev)) { + device_printf(dev, "unable to setup the gpio irq handler\n"); + goto fail; + } + /* Find our node. */ gpio = ofw_bus_get_node(sc->sc_dev); @@ -741,6 +814,9 @@ sc->sc_gpio_pins[i].gp_pin = j; sc->sc_gpio_pins[i].gp_caps = BCM_GPIO_DEFAULT_CAPS; sc->sc_gpio_pins[i].gp_flags = bcm_gpio_func_flag(func); + /* The default is active-low interrupts. */ + sc->sc_irq_trigger[i] = INTR_TRIGGER_LEVEL; + sc->sc_irq_polarity[i] = INTR_POLARITY_LOW; i++; } sc->sc_gpio_npins = i; @@ -749,13 +825,16 @@ device_add_child(dev, "gpioc", device_get_unit(dev)); device_add_child(dev, "gpiobus", device_get_unit(dev)); + return (bus_generic_attach(dev)); fail: + bcm_gpio_intr_detach(dev); if (sc->sc_irq_res) - bus_release_resource(dev, SYS_RES_IRQ, 0, sc->sc_irq_res); + bus_release_resources(dev, bcm_gpio_irq_spec, sc->sc_irq_res); if (sc->sc_mem_res) bus_release_resource(dev, SYS_RES_MEMORY, 0, sc->sc_mem_res); + return (ENXIO); } @@ -766,6 +845,234 @@ return (EBUSY); } +static uint32_t +bcm_gpio_intr_reg(struct bcm_gpio_softc *sc, unsigned int irq, uint32_t bank) +{ + + if (irq > BCM_GPIO_PINS) + return (0); + + if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_LEVEL) { + if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW) + return (BCM_GPIO_GPLEN(bank)); + else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH) + return (BCM_GPIO_GPHEN(bank)); + } else if (sc->sc_irq_trigger[irq] == INTR_TRIGGER_EDGE) { + if (sc->sc_irq_polarity[irq] == INTR_POLARITY_LOW) + return (BCM_GPIO_GPFEN(bank)); + else if (sc->sc_irq_polarity[irq] == INTR_POLARITY_HIGH) + return (BCM_GPIO_GPREN(bank)); + } + + return (0); +} + +static void +bcm_gpio_mask_irq(void *source) +{ + uint32_t bank, mask, reg; + unsigned int irq; + + irq = (unsigned int)source; + if (irq > BCM_GPIO_PINS) + return; + if (bcm_gpio_pin_is_ro(bcm_gpio_sc, irq)) + return; + + bank = BCM_GPIO_BANK(irq); + mask = BCM_GPIO_MASK(irq); + + BCM_GPIO_LOCK(bcm_gpio_sc); + reg = bcm_gpio_intr_reg(bcm_gpio_sc, irq, bank); + if (reg != 0) + BCM_GPIO_CLEAR_BITS(bcm_gpio_sc, reg, mask); + BCM_GPIO_UNLOCK(bcm_gpio_sc); +} + +static void +bcm_gpio_unmask_irq(void *source) +{ + uint32_t bank, mask, reg; + unsigned int irq; + + irq = (unsigned int)source; + if (irq > BCM_GPIO_PINS) + return; + if (bcm_gpio_pin_is_ro(bcm_gpio_sc, irq)) + return; + + bank = BCM_GPIO_BANK(irq); + mask = BCM_GPIO_MASK(irq); + + BCM_GPIO_LOCK(bcm_gpio_sc); + reg = bcm_gpio_intr_reg(bcm_gpio_sc, irq, bank); + if (reg != 0) + BCM_GPIO_SET_BITS(bcm_gpio_sc, reg, mask); + BCM_GPIO_UNLOCK(bcm_gpio_sc); +} + +static int +bcm_gpio_activate_resource(device_t bus, device_t child, int type, int rid, + struct resource *res) +{ + int pin; + + if (type != SYS_RES_IRQ) + return (ENXIO); + + /* Unmask the interrupt. */ + pin = rman_get_start(res); + bcm_gpio_unmask_irq((void *)pin); + + return (0); +} + +static int +bcm_gpio_deactivate_resource(device_t bus, device_t child, int type, int rid, + struct resource *res) +{ + int pin; + + if (type != SYS_RES_IRQ) + return (ENXIO); + + /* Mask the interrupt. */ + pin = rman_get_start(res); + bcm_gpio_mask_irq((void *)pin); + + return (0); +} + +static int +bcm_gpio_config_intr(device_t dev, int irq, enum intr_trigger trig, + enum intr_polarity pol) +{ + int bank; + struct bcm_gpio_softc *sc; + uint32_t mask, oldreg, reg; + + if (irq > BCM_GPIO_PINS) + return (EINVAL); + + /* There is no standard trigger or polarity. */ + if (trig == INTR_TRIGGER_CONFORM || pol == INTR_POLARITY_CONFORM) + return (EINVAL); + + sc = device_get_softc(dev); + if (bcm_gpio_pin_is_ro(sc, irq)) + return (EINVAL); + + bank = BCM_GPIO_BANK(irq); + mask = BCM_GPIO_MASK(irq); + + BCM_GPIO_LOCK(sc); + oldreg = bcm_gpio_intr_reg(sc, irq, bank); + sc->sc_irq_trigger[irq] = trig; + sc->sc_irq_polarity[irq] = pol; + reg = bcm_gpio_intr_reg(sc, irq, bank); + if (reg != 0) + BCM_GPIO_SET_BITS(sc, reg, mask); + if (oldreg != 0) + BCM_GPIO_CLEAR_BITS(sc, oldreg, mask); + BCM_GPIO_UNLOCK(sc); + + return (0); +} + +static int +bcm_gpio_setup_intr(device_t bus, device_t child, struct resource *ires, + int flags, driver_filter_t *filt, driver_intr_t *handler, + void *arg, void **cookiep) +{ + struct bcm_gpio_softc *sc; + struct intr_event *event; + int pin, error; + + sc = device_get_softc(bus); + pin = rman_get_start(ires); + + if (pin > BCM_GPIO_PINS) + panic("%s: bad pin %d", __func__, pin); + + event = sc->sc_events[pin]; + if (event == NULL) { + error = intr_event_create(&event, (void *)pin, 0, pin, + bcm_gpio_mask_irq, bcm_gpio_unmask_irq, NULL, NULL, + "gpio%d pin%d:", device_get_unit(bus), pin); + + if (error != 0) + return (error); + + sc->sc_events[pin] = event; + } + + intr_event_add_handler(event, device_get_nameunit(child), filt, + handler, arg, intr_priority(flags), flags, cookiep); + + return (0); +} + +static int +bcm_gpio_teardown_intr(device_t dev, device_t child, struct resource *ires, + void *cookie) +{ + struct bcm_gpio_softc *sc; + int pin, err; + + sc = device_get_softc(dev); + pin = rman_get_start(ires); + if (pin > BCM_GPIO_PINS) + panic("%s: bad pin %d", __func__, pin); + + if (sc->sc_events[pin] == NULL) + panic("Trying to teardown unoccupied IRQ"); + + err = intr_event_remove_handler(cookie); + if (!err) + sc->sc_events[pin] = NULL; + + return (err); +} + +static int +bcm_gpio_intr(void *arg) +{ + int bank_last, irq; + struct bcm_gpio_softc *sc; + struct intr_event *event; + uint32_t bank, mask, reg; + + sc = (struct bcm_gpio_softc *)arg; + + bank_last = -1; + for (irq = 0; irq < BCM_GPIO_PINS; irq++) { + + bank = BCM_GPIO_BANK(irq); + mask = BCM_GPIO_MASK(irq); + + if (bank != bank_last) { + reg = BCM_GPIO_READ(sc, BCM_GPIO_GPEDS(bank)); + bank_last = bank; + } + + if (reg & mask) { + + event = sc->sc_events[irq]; + if (event != NULL && !TAILQ_EMPTY(&event->ie_handlers)) + intr_event_handle(event, NULL); + else { + device_printf(sc->sc_dev, "Stray IRQ %d\n", + irq); + } + + /* Clear the Status bit by writing '1' to it. */ + BCM_GPIO_WRITE(sc, BCM_GPIO_GPEDS(bank), mask); + } + } + + return (FILTER_HANDLED); +} + static phandle_t bcm_gpio_get_node(device_t bus, device_t dev) { @@ -790,6 +1097,13 @@ DEVMETHOD(gpio_pin_set, bcm_gpio_pin_set), DEVMETHOD(gpio_pin_toggle, bcm_gpio_pin_toggle), + /* Bus interface */ + DEVMETHOD(bus_activate_resource, bcm_gpio_activate_resource), + DEVMETHOD(bus_deactivate_resource, bcm_gpio_deactivate_resource), + DEVMETHOD(bus_config_intr, bcm_gpio_config_intr), + DEVMETHOD(bus_setup_intr, bcm_gpio_setup_intr), + DEVMETHOD(bus_teardown_intr, bcm_gpio_teardown_intr), + /* ofw_bus interface */ DEVMETHOD(ofw_bus_get_node, bcm_gpio_get_node),