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
This commit is contained in:
Luiz Otavio O Souza 2015-02-04 18:15:28 +00:00
parent e74d6e2a12
commit 12471cec7c

View File

@ -32,6 +32,7 @@ __FBSDID("$FreeBSD$");
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/gpio.h>
#include <sys/interrupt.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
@ -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)
@ -656,6 +675,40 @@ bcm_gpio_get_reserved_pins(struct bcm_gpio_softc *sc)
return (0);
}
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)
{
@ -670,6 +723,39 @@ bcm_gpio_probe(device_t dev)
return (BUS_PROBE_DEFAULT);
}
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)
{
@ -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),