arm: allwinner: Add GPIO Interrupt support

Not all pins in Allwinner have interrupts support so we rely
on the padconf data to add the proper caps when pin_getcaps is called.
The pin is switch to the specific "eint" function during setup_intr and
switched back to its old function in teardown_intr.
Only INTR_MAP_DATA_GPIO is supported for now.

MFC after:	1 month
This commit is contained in:
Emmanuel Vadot 2020-01-19 19:51:20 +00:00
parent 2bebe88556
commit 0fe5379c6a
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=356891

View File

@ -41,6 +41,7 @@ __FBSDID("$FreeBSD$");
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <sys/proc.h>
#include <machine/bus.h>
#include <machine/resource.h>
@ -61,10 +62,17 @@ __FBSDID("$FreeBSD$");
#include "opt_soc.h"
#endif
#ifdef INTRNG
#include "pic_if.h"
#endif
#include "gpio_if.h"
#define AW_GPIO_DEFAULT_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \
GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN)
GPIO_PIN_PULLUP | GPIO_PIN_PULLDOWN);
#define AW_GPIO_INTR_CAPS (GPIO_INTR_LEVEL_LOW | GPIO_INTR_LEVEL_HIGH | \
GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING | GPIO_INTR_EDGE_BOTH)
#define AW_GPIO_NONE 0
#define AW_GPIO_PULLUP 1
@ -249,17 +257,45 @@ struct clk_list {
clk_t clk;
};
#ifdef INTRNG
struct gpio_irqsrc {
struct intr_irqsrc isrc;
u_int irq;
uint32_t mode;
uint32_t pin;
uint32_t bank;
uint32_t intnum;
uint32_t intfunc;
uint32_t oldfunc;
bool enabled;
};
#endif
#define AW_GPIO_MEMRES 0
#define AW_GPIO_IRQRES 1
#define AW_GPIO_RESSZ 2
struct aw_gpio_softc {
device_t sc_dev;
device_t sc_busdev;
struct resource * sc_res[AW_GPIO_RESSZ];
struct mtx sc_mtx;
struct resource * sc_mem_res;
struct resource * sc_irq_res;
bus_space_tag_t sc_bst;
bus_space_handle_t sc_bsh;
void * sc_intrhand;
struct aw_gpio_conf *conf;
TAILQ_HEAD(, clk_list) clk_list;
#ifdef INTRNG
struct gpio_irqsrc *gpio_pic_irqsrc;
int nirqs;
#endif
};
static struct resource_spec aw_gpio_res_spec[] = {
{ SYS_RES_MEMORY, 0, RF_ACTIVE },
{ SYS_RES_IRQ, 0, RF_ACTIVE | RF_SHAREABLE },
{ -1, 0, 0 }
};
#define AW_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx)
@ -271,14 +307,18 @@ struct aw_gpio_softc {
#define AW_GPIO_GP_DRV(_bank, _idx) 0x14 + ((_bank) * 0x24) + ((_idx) << 2)
#define AW_GPIO_GP_PUL(_bank, _idx) 0x1c + ((_bank) * 0x24) + ((_idx) << 2)
#define AW_GPIO_GP_INT_CFG0 0x200
#define AW_GPIO_GP_INT_CFG1 0x204
#define AW_GPIO_GP_INT_CFG2 0x208
#define AW_GPIO_GP_INT_CFG3 0x20c
#define AW_GPIO_GP_INT_BASE(_bank) (0x200 + 0x20 * _bank)
#define AW_GPIO_GP_INT_CTL 0x210
#define AW_GPIO_GP_INT_STA 0x214
#define AW_GPIO_GP_INT_DEB 0x218
#define AW_GPIO_GP_INT_CFG(_bank, _pin) (AW_GPIO_GP_INT_BASE(_bank) + (0x4 * ((_pin) / 8)))
#define AW_GPIO_GP_INT_CTL(_bank) (AW_GPIO_GP_INT_BASE(_bank) + 0x10)
#define AW_GPIO_GP_INT_STA(_bank) (AW_GPIO_GP_INT_BASE(_bank) + 0x14)
#define AW_GPIO_GP_INT_DEB(_bank) (AW_GPIO_GP_INT_BASE(_bank) + 0x18)
#define AW_GPIO_INT_EDGE_POSITIVE 0x0
#define AW_GPIO_INT_EDGE_NEGATIVE 0x1
#define AW_GPIO_INT_LEVEL_HIGH 0x2
#define AW_GPIO_INT_LEVEL_LOW 0x3
#define AW_GPIO_INT_EDGE_BOTH 0x4
static char *aw_gpio_parse_function(phandle_t node);
static const char **aw_gpio_parse_pins(phandle_t node, int *pins_nb);
@ -290,10 +330,16 @@ static int aw_gpio_pin_set(device_t dev, uint32_t pin, unsigned int value);
static int aw_gpio_pin_get_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int *value);
static int aw_gpio_pin_set_locked(struct aw_gpio_softc *sc, uint32_t pin, unsigned int value);
static void aw_gpio_intr(void *arg);
static void aw_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc);
static void aw_gpio_pic_disable_intr_locked(struct aw_gpio_softc *sc, struct intr_irqsrc *isrc);
static void aw_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc);
static int aw_gpio_register_isrcs(struct aw_gpio_softc *sc);
#define AW_GPIO_WRITE(_sc, _off, _val) \
bus_space_write_4(_sc->sc_bst, _sc->sc_bsh, _off, _val)
bus_write_4((_sc)->sc_res[AW_GPIO_MEMRES], _off, _val)
#define AW_GPIO_READ(_sc, _off) \
bus_space_read_4(_sc->sc_bst, _sc->sc_bsh, _off)
bus_read_4((_sc)->sc_res[AW_GPIO_MEMRES], _off)
static uint32_t
aw_gpio_get_function(struct aw_gpio_softc *sc, uint32_t pin)
@ -492,6 +538,8 @@ aw_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
return (EINVAL);
*caps = AW_GPIO_DEFAULT_CAPS;
if (sc->conf->padconf->pins[pin].eint_func != 0)
*caps |= AW_GPIO_INTR_CAPS;
return (0);
}
@ -806,6 +854,27 @@ aw_gpio_pin_config_32(device_t dev, uint32_t first_pin, uint32_t num_pins,
return (err);
}
static int
aw_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells,
pcell_t *gpios, uint32_t *pin, uint32_t *flags)
{
struct aw_gpio_softc *sc;
int i;
sc = device_get_softc(bus);
/* The GPIO pins are mapped as: <gpio-phandle bank pin flags>. */
for (i = 0; i < sc->conf->padconf->npins; i++)
if (sc->conf->padconf->pins[i].port == gpios[0] &&
sc->conf->padconf->pins[i].pin == gpios[1]) {
*pin = i;
break;
}
*flags = gpios[gcells - 1];
return (0);
}
static int
aw_find_pinnum_by_name(struct aw_gpio_softc *sc, const char *pinname)
{
@ -938,7 +1007,7 @@ aw_gpio_probe(device_t dev)
static int
aw_gpio_attach(device_t dev)
{
int rid, error;
int error;
phandle_t gpio;
struct aw_gpio_softc *sc;
struct clk_list *clkp, *clkp_tmp;
@ -951,22 +1020,15 @@ aw_gpio_attach(device_t dev)
mtx_init(&sc->sc_mtx, "aw gpio", "gpio", MTX_SPIN);
rid = 0;
sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid,
RF_ACTIVE);
if (!sc->sc_mem_res) {
device_printf(dev, "cannot allocate memory window\n");
goto fail;
if (bus_alloc_resources(dev, aw_gpio_res_spec, sc->sc_res) != 0) {
device_printf(dev, "cannot allocate device resources\n");
return (ENXIO);
}
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) {
device_printf(dev, "cannot allocate interrupt\n");
if (bus_setup_intr(dev, sc->sc_res[AW_GPIO_IRQRES],
INTR_TYPE_CLK | INTR_MPSAFE, NULL, aw_gpio_intr, sc,
&sc->sc_intrhand)) {
device_printf(dev, "cannot setup interrupt handler\n");
goto fail;
}
@ -1009,6 +1071,11 @@ aw_gpio_attach(device_t dev)
goto fail;
}
#ifdef INTRNG
aw_gpio_register_isrcs(sc);
intr_pic_register(dev, OF_xref_from_node(ofw_bus_get_node(dev)));
#endif
sc->sc_busdev = gpiobus_attach_bus(dev);
if (sc->sc_busdev == NULL)
goto fail;
@ -1062,6 +1129,314 @@ aw_gpio_detach(device_t dev)
return (EBUSY);
}
static void
aw_gpio_intr(void *arg)
{
struct aw_gpio_softc *sc;
struct intr_irqsrc *isrc;
uint32_t reg;
int irq;
sc = (struct aw_gpio_softc *)arg;
AW_GPIO_LOCK(sc);
for (irq = 0; irq < sc->nirqs; irq++) {
if (!sc->gpio_pic_irqsrc[irq].enabled)
continue;
reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_STA(sc->gpio_pic_irqsrc[irq].bank));
if (!(reg & (1 << sc->gpio_pic_irqsrc[irq].intnum)))
continue;
isrc = &sc->gpio_pic_irqsrc[irq].isrc;
if (intr_isrc_dispatch(isrc, curthread->td_intr_frame) != 0) {
aw_gpio_pic_disable_intr_locked(sc, isrc);
aw_gpio_pic_post_filter(sc->sc_dev, isrc);
device_printf(sc->sc_dev, "Stray irq %u disabled\n", irq);
}
}
AW_GPIO_UNLOCK(sc);
}
/*
* Interrupts support
*/
static int
aw_gpio_register_isrcs(struct aw_gpio_softc *sc)
{
const char *name;
int nirqs;
int pin;
int err;
name = device_get_nameunit(sc->sc_dev);
for (nirqs = 0, pin = 0; pin < sc->conf->padconf->npins; pin++) {
if (sc->conf->padconf->pins[pin].eint_func == 0)
continue;
nirqs++;
}
sc->gpio_pic_irqsrc = malloc(sizeof(*sc->gpio_pic_irqsrc) * nirqs,
M_DEVBUF, M_WAITOK | M_ZERO);
for (nirqs = 0, pin = 0; pin < sc->conf->padconf->npins; pin++) {
if (sc->conf->padconf->pins[pin].eint_func == 0)
continue;
sc->gpio_pic_irqsrc[nirqs].pin = pin;
sc->gpio_pic_irqsrc[nirqs].bank = sc->conf->padconf->pins[pin].eint_bank;
sc->gpio_pic_irqsrc[nirqs].intnum = sc->conf->padconf->pins[pin].eint_num;
sc->gpio_pic_irqsrc[nirqs].intfunc = sc->conf->padconf->pins[pin].eint_func;
sc->gpio_pic_irqsrc[nirqs].irq = nirqs;
sc->gpio_pic_irqsrc[nirqs].mode = GPIO_INTR_CONFORM;
err = intr_isrc_register(&sc->gpio_pic_irqsrc[nirqs].isrc,
sc->sc_dev, 0, "%s,%s", name,
sc->conf->padconf->pins[pin].functions[sc->conf->padconf->pins[pin].eint_func]);
if (err) {
device_printf(sc->sc_dev, "intr_isrs_register failed for irq %d\n", nirqs);
}
nirqs++;
}
sc->nirqs = nirqs;
return (0);
}
static void
aw_gpio_pic_disable_intr_locked(struct aw_gpio_softc *sc, struct intr_irqsrc *isrc)
{
u_int irq;
uint32_t reg;
AW_GPIO_LOCK_ASSERT(sc);
irq = ((struct gpio_irqsrc *)isrc)->irq;
reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank));
reg &= ~(1 << sc->gpio_pic_irqsrc[irq].intnum);
AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank), reg);
sc->gpio_pic_irqsrc[irq].enabled = false;
}
static void
aw_gpio_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct aw_gpio_softc *sc;
sc = device_get_softc(dev);
AW_GPIO_LOCK(sc);
aw_gpio_pic_disable_intr_locked(sc, isrc);
AW_GPIO_UNLOCK(sc);
}
static void
aw_gpio_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct aw_gpio_softc *sc;
u_int irq;
uint32_t reg;
sc = device_get_softc(dev);
irq = ((struct gpio_irqsrc *)isrc)->irq;
AW_GPIO_LOCK(sc);
reg = AW_GPIO_READ(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank));
reg |= 1 << sc->gpio_pic_irqsrc[irq].intnum;
AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_CTL(sc->gpio_pic_irqsrc[irq].bank), reg);
AW_GPIO_UNLOCK(sc);
sc->gpio_pic_irqsrc[irq].enabled = true;
}
static int
aw_gpio_pic_map_gpio(struct aw_gpio_softc *sc, struct intr_map_data_gpio *dag,
u_int *irqp, u_int *mode)
{
u_int irq;
int pin;
irq = dag->gpio_pin_num;
for (pin = 0; pin < sc->nirqs; pin++)
if (sc->gpio_pic_irqsrc[pin].pin == irq)
break;
if (pin == sc->nirqs) {
device_printf(sc->sc_dev, "Invalid interrupt number %u\n", irq);
return (EINVAL);
}
switch (dag->gpio_intr_mode) {
case GPIO_INTR_LEVEL_LOW:
case GPIO_INTR_LEVEL_HIGH:
case GPIO_INTR_EDGE_RISING:
case GPIO_INTR_EDGE_FALLING:
case GPIO_INTR_EDGE_BOTH:
break;
default:
device_printf(sc->sc_dev, "Unsupported interrupt mode 0x%8x\n",
dag->gpio_intr_mode);
return (EINVAL);
}
*irqp = pin;
if (mode != NULL)
*mode = dag->gpio_intr_mode;
return (0);
}
static int
aw_gpio_pic_map_intr(device_t dev, struct intr_map_data *data,
struct intr_irqsrc **isrcp)
{
struct aw_gpio_softc *sc;
u_int irq;
int err;
sc = device_get_softc(dev);
switch (data->type) {
case INTR_MAP_DATA_GPIO:
err = aw_gpio_pic_map_gpio(sc,
(struct intr_map_data_gpio *)data,
&irq, NULL);
break;
default:
return (ENOTSUP);
};
if (err == 0)
*isrcp = &sc->gpio_pic_irqsrc[irq].isrc;
return (0);
}
static int
aw_gpio_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
struct aw_gpio_softc *sc;
struct gpio_irqsrc *gi;
uint32_t irqcfg;
uint32_t pinidx, reg;
u_int irq, mode;
int err;
sc = device_get_softc(dev);
gi = (struct gpio_irqsrc *)isrc;
switch (data->type) {
case INTR_MAP_DATA_GPIO:
err = aw_gpio_pic_map_gpio(sc,
(struct intr_map_data_gpio *)data,
&irq, &mode);
break;
default:
return (ENOTSUP);
};
pinidx = (sc->gpio_pic_irqsrc[irq].intnum % 8) * 4;
AW_GPIO_LOCK(sc);
switch (mode) {
case GPIO_INTR_LEVEL_LOW:
irqcfg = AW_GPIO_INT_LEVEL_LOW << pinidx;
break;
case GPIO_INTR_LEVEL_HIGH:
irqcfg = AW_GPIO_INT_LEVEL_HIGH << pinidx;
break;
case GPIO_INTR_EDGE_RISING:
irqcfg = AW_GPIO_INT_EDGE_POSITIVE << pinidx;
break;
case GPIO_INTR_EDGE_FALLING:
irqcfg = AW_GPIO_INT_EDGE_NEGATIVE << pinidx;
break;
case GPIO_INTR_EDGE_BOTH:
irqcfg = AW_GPIO_INT_EDGE_BOTH << pinidx;
break;
}
/* Switch the pin to interrupt mode */
sc->gpio_pic_irqsrc[irq].oldfunc = aw_gpio_get_function(sc,
sc->gpio_pic_irqsrc[irq].pin);
aw_gpio_set_function(sc, sc->gpio_pic_irqsrc[irq].pin,
sc->gpio_pic_irqsrc[irq].intfunc);
/* Write interrupt mode */
reg = AW_GPIO_READ(sc,
AW_GPIO_GP_INT_CFG(sc->gpio_pic_irqsrc[irq].bank,
sc->gpio_pic_irqsrc[irq].intnum));
reg &= ~(0xF << pinidx);
reg |= irqcfg;
AW_GPIO_WRITE(sc,
AW_GPIO_GP_INT_CFG(sc->gpio_pic_irqsrc[irq].bank,
sc->gpio_pic_irqsrc[irq].intnum),
reg);
AW_GPIO_UNLOCK(sc);
return (0);
}
static int
aw_gpio_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
struct aw_gpio_softc *sc;
struct gpio_irqsrc *gi;
sc = device_get_softc(dev);
gi = (struct gpio_irqsrc *)isrc;
/* Switch back the pin to it's original function */
AW_GPIO_LOCK(sc);
aw_gpio_set_function(sc, gi->pin, gi->oldfunc);
AW_GPIO_UNLOCK(sc);
return (0);
}
static void
aw_gpio_pic_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
struct aw_gpio_softc *sc;
struct gpio_irqsrc *gi;
sc = device_get_softc(dev);
gi = (struct gpio_irqsrc *)isrc;
arm_irq_memory_barrier(0);
AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_STA(gi->bank), 1 << gi->intnum);
}
static void
aw_gpio_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
struct aw_gpio_softc *sc;
struct gpio_irqsrc *gi;
sc = device_get_softc(dev);
gi = (struct gpio_irqsrc *)isrc;
arm_irq_memory_barrier(0);
AW_GPIO_WRITE(sc, AW_GPIO_GP_INT_STA(gi->bank), 1 << gi->intnum);
aw_gpio_pic_enable_intr(dev, isrc);
}
static void
aw_gpio_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
struct aw_gpio_softc *sc;
sc = device_get_softc(dev);
aw_gpio_pic_disable_intr_locked(sc, isrc);
}
/*
* OFWBUS Interface
*/
static phandle_t
aw_gpio_get_node(device_t dev, device_t bus)
{
@ -1070,33 +1445,24 @@ aw_gpio_get_node(device_t dev, device_t bus)
return (ofw_bus_get_node(dev));
}
static int
aw_gpio_map_gpios(device_t bus, phandle_t dev, phandle_t gparent, int gcells,
pcell_t *gpios, uint32_t *pin, uint32_t *flags)
{
struct aw_gpio_softc *sc;
int i;
sc = device_get_softc(bus);
/* The GPIO pins are mapped as: <gpio-phandle bank pin flags>. */
for (i = 0; i < sc->conf->padconf->npins; i++)
if (sc->conf->padconf->pins[i].port == gpios[0] &&
sc->conf->padconf->pins[i].pin == gpios[1]) {
*pin = i;
break;
}
*flags = gpios[gcells - 1];
return (0);
}
static device_method_t aw_gpio_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, aw_gpio_probe),
DEVMETHOD(device_attach, aw_gpio_attach),
DEVMETHOD(device_detach, aw_gpio_detach),
#ifdef INTRNG
/* Interrupt controller interface */
DEVMETHOD(pic_disable_intr, aw_gpio_pic_disable_intr),
DEVMETHOD(pic_enable_intr, aw_gpio_pic_enable_intr),
DEVMETHOD(pic_map_intr, aw_gpio_pic_map_intr),
DEVMETHOD(pic_setup_intr, aw_gpio_pic_setup_intr),
DEVMETHOD(pic_teardown_intr, aw_gpio_pic_teardown_intr),
DEVMETHOD(pic_post_filter, aw_gpio_pic_post_filter),
DEVMETHOD(pic_post_ithread, aw_gpio_pic_post_ithread),
DEVMETHOD(pic_pre_ithread, aw_gpio_pic_pre_ithread),
#endif
/* GPIO protocol */
DEVMETHOD(gpio_get_bus, aw_gpio_get_bus),
DEVMETHOD(gpio_pin_max, aw_gpio_pin_max),