From owner-svn-src-head@FreeBSD.ORG Wed Feb 4 18:15:29 2015 Return-Path: Delivered-To: svn-src-head@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [8.8.178.115]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by hub.freebsd.org (Postfix) with ESMTPS id 3814FD28; Wed, 4 Feb 2015 18:15:29 +0000 (UTC) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:1900:2254:2068::e6a:0]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mx1.freebsd.org (Postfix) with ESMTPS id 2389BE42; Wed, 4 Feb 2015 18:15:29 +0000 (UTC) Received: from svn.freebsd.org ([127.0.1.70]) by svn.freebsd.org (8.14.9/8.14.9) with ESMTP id t14IFTf9075960; Wed, 4 Feb 2015 18:15:29 GMT (envelope-from loos@FreeBSD.org) Received: (from loos@localhost) by svn.freebsd.org (8.14.9/8.14.9/Submit) id t14IFSoj075959; Wed, 4 Feb 2015 18:15:28 GMT (envelope-from loos@FreeBSD.org) Message-Id: <201502041815.t14IFSoj075959@svn.freebsd.org> X-Authentication-Warning: svn.freebsd.org: loos set sender to loos@FreeBSD.org using -f From: Luiz Otavio O Souza Date: Wed, 4 Feb 2015 18:15:28 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org Subject: svn commit: r278215 - head/sys/arm/broadcom/bcm2835 X-SVN-Group: head MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-BeenThere: svn-src-head@freebsd.org X-Mailman-Version: 2.1.18-1 Precedence: list List-Id: SVN commit messages for the src tree for head/-current List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Wed, 04 Feb 2015 18:15:29 -0000 Author: loos Date: Wed Feb 4 18:15:28 2015 New Revision: 278215 URL: https://svnweb.freebsd.org/changeset/base/278215 Log: Add GPIO interrupt support for BCM2835 (Raspberry pi). With this commit any of the GPIO pins can now be programmed to act as an interrupt source for GPIO devices (i.e. limited to devices directly attached to gpiobus - at least for now). Differential Revision: https://reviews.freebsd.org/D1000 Modified: head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c Modified: head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c ============================================================================== --- head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c Wed Feb 4 17:23:02 2015 (r278214) +++ head/sys/arm/broadcom/bcm2835/bcm2835_gpio.c Wed Feb 4 18:15:28 2015 (r278215) @@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -57,6 +58,7 @@ __FBSDID("$FreeBSD$"); #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) @@ -81,12 +83,15 @@ struct bcm_gpio_softc { struct resource * sc_res[BCM_GPIO_IRQS + 1]; 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 { @@ -95,21 +100,35 @@ enum bcm_gpio_pud { 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_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_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_WRITE(_sc, _off, _val) \ - bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _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) + 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)) + +#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 irq */ +#define BCM_GPIO_GPFEN(_bank) (0x58 + _bank * 4) /* Falling Edge irq */ +#define BCM_GPIO_GPHEN(_bank) (0x64 + _bank * 4) /* High Level irq */ +#define BCM_GPIO_GPLEN(_bank) (0x70 + _bank * 4) /* Low Level irq */ +#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 */ + +static struct bcm_gpio_softc *bcm_gpio_sc = NULL; static int bcm_gpio_pin_is_ro(struct bcm_gpio_softc *sc, int pin) @@ -657,6 +676,40 @@ bcm_gpio_get_reserved_pins(struct bcm_gp } 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; + reg = 0; + 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 int bcm_gpio_probe(device_t dev) { @@ -671,6 +724,39 @@ bcm_gpio_probe(device_t dev) } 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_res[i + 1], + INTR_TYPE_MISC | INTR_MPSAFE, 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_res[i + 1], + sc->sc_intrhand[i]); + } + } +} + +static int bcm_gpio_attach(device_t dev) { int i, j; @@ -678,30 +764,34 @@ bcm_gpio_attach(device_t dev) struct bcm_gpio_softc *sc; uint32_t func; - sc = device_get_softc(dev); - sc->sc_dev = dev; - mtx_init(&sc->sc_mtx, "bcm gpio", "gpio", MTX_DEF); + 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_SPIN); if (bus_alloc_resources(dev, bcm_gpio_res_spec, sc->sc_res) != 0) { device_printf(dev, "cannot allocate resources\n"); goto fail; } sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); - + /* 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); - if (!OF_hasprop(gpio, "gpio-controller")) /* Node is not a GPIO controller. */ goto fail; - /* * Find the read-only pins. These are pins we never touch or bad * things could happen. */ if (bcm_gpio_get_reserved_pins(sc) == -1) goto fail; - /* Initialize the software controlled pins. */ for (i = 0, j = 0; j < BCM_GPIO_PINS; j++) { snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, @@ -710,6 +800,9 @@ bcm_gpio_attach(device_t dev) 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; @@ -721,6 +814,7 @@ bcm_gpio_attach(device_t dev) return (0); fail: + bcm_gpio_intr_detach(dev); bus_release_resources(dev, bcm_gpio_res_spec, sc->sc_res); mtx_destroy(&sc->sc_mtx); @@ -734,6 +828,177 @@ bcm_gpio_detach(device_t dev) 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 (reg != oldreg && 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 phandle_t bcm_gpio_get_node(device_t bus, device_t dev) { @@ -759,6 +1024,13 @@ static device_method_t bcm_gpio_methods[ 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),