freebsd-nq/sys/dev/gpio/pl061.c
Andrew Turner 365ed84f28 Use the correct variable to check which interrupt mode to use
In the PL061 driver we incorrectly used the mask rather than mode to find
how to configure the interrupt.

Sponsored by:	Innovate UK
2020-09-10 09:37:30 +00:00

581 lines
14 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2020 Amazon.com, Inc. or its affiliates.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/proc.h>
#include <sys/rman.h>
#include <sys/lock.h>
#include <sys/mutex.h>
#include <sys/gpio.h>
#include <sys/interrupt.h>
#include <machine/bus.h>
#include <machine/intr.h>
#include <machine/resource.h>
#include <dev/gpio/gpiobusvar.h>
#include "pl061.h"
#include "gpio_if.h"
#include "pic_if.h"
#define PL061_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx)
#define PL061_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx)
#define PL061_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx)
#define PL061_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED)
#define PL061_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED)
#if 0
#define dprintf(fmt, args...) do { \
printf(fmt, ##args); \
} while (0)
#else
#define dprintf(fmt, args...)
#endif
#define PL061_PIN_TO_ADDR(pin) (1 << (pin + 2))
#define PL061_DATA 0x3FC
#define PL061_DIR 0x400
#define PL061_INTSENSE 0x404
#define PL061_INTBOTHEDGES 0x408
#define PL061_INTEVENT 0x40C
#define PL061_INTMASK 0x410
#define PL061_RAWSTATUS 0x414
#define PL061_STATUS 0x418
#define PL061_INTCLR 0x41C
#define PL061_MODECTRL 0x420
#define PL061_ALLOWED_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | GPIO_INTR_EDGE_BOTH | \
GPIO_INTR_EDGE_RISING | GPIO_INTR_EDGE_FALLING | \
GPIO_INTR_LEVEL_HIGH | GPIO_INTR_LEVEL_LOW )
#define PIC_INTR_ISRC(sc, irq) (&(sc->sc_isrcs[irq].isrc))
static device_t
pl061_get_bus(device_t dev)
{
struct pl061_softc *sc;
sc = device_get_softc(dev);
return (sc->sc_busdev);
}
static int
pl061_pin_max(device_t dev, int *maxpin)
{
*maxpin = PL061_NUM_GPIO - 1;
return (0);
}
static int
pl061_pin_getname(device_t dev, uint32_t pin, char *name)
{
struct pl061_softc *sc;
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
snprintf(name, GPIOMAXNAME, "p%u", pin);
name[GPIOMAXNAME - 1] = '\0';
return (0);
}
static int
pl061_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
{
struct pl061_softc *sc;
uint8_t mask = 1 << pin;
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
PL061_LOCK(sc);
*flags = 0;
if (mask & bus_read_1(sc->sc_mem_res, PL061_DIR))
*flags |= GPIO_PIN_OUTPUT;
else
*flags |= GPIO_PIN_INPUT;
PL061_UNLOCK(sc);
return (0);
}
static int
pl061_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
{
struct pl061_softc *sc;
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
*caps = PL061_ALLOWED_CAPS;
return (0);
}
static void
mask_and_set(struct pl061_softc *sc, long a, uint8_t m, uint8_t b)
{
uint8_t tmp;
tmp = bus_read_1(sc->sc_mem_res, a);
tmp &= ~m;
tmp |= b;
bus_write_1(sc->sc_mem_res, a, tmp);
dprintf("%s: writing %#x to register %#lx\n", __func__, tmp, a);
}
static int
pl061_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
{
struct pl061_softc *sc;
uint8_t mask = 1 << pin;
const uint32_t in_out = (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT);
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
if (flags & ~PL061_ALLOWED_CAPS)
return (EINVAL);
/* can't be both input and output */
if ((flags & in_out) == in_out)
return (EINVAL);
PL061_LOCK(sc);
mask_and_set(sc, PL061_DIR, mask, flags & GPIO_PIN_OUTPUT ? mask : 0);
PL061_UNLOCK(sc);
return (0);
}
static int
pl061_pin_get(device_t dev, uint32_t pin, uint32_t *value)
{
struct pl061_softc *sc;
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
PL061_LOCK(sc);
if (bus_read_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin)))
*value = GPIO_PIN_HIGH;
else
*value = GPIO_PIN_LOW;
PL061_UNLOCK(sc);
return (0);
}
static int
pl061_pin_set(device_t dev, uint32_t pin, uint32_t value)
{
struct pl061_softc *sc;
uint8_t d = (value == GPIO_PIN_HIGH) ? 0xff : 0x00;
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
PL061_LOCK(sc);
bus_write_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin), d);
PL061_UNLOCK(sc);
return (0);
}
static int
pl061_pin_toggle(device_t dev, uint32_t pin)
{
struct pl061_softc *sc;
uint8_t d;
sc = device_get_softc(dev);
if (pin >= PL061_NUM_GPIO)
return (EINVAL);
PL061_LOCK(sc);
d = ~bus_read_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin));
bus_write_1(sc->sc_mem_res, PL061_PIN_TO_ADDR(pin), d);
PL061_UNLOCK(sc);
return (0);
}
static void
pl061_pic_disable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct pl061_softc *sc;
uint8_t mask;
sc = device_get_softc(dev);
mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq;
dprintf("%s: calling disable interrupt %#x\n", __func__, mask);
PL061_LOCK(sc);
mask_and_set(sc, PL061_INTMASK, mask, 0);
PL061_UNLOCK(sc);
}
static void
pl061_pic_enable_intr(device_t dev, struct intr_irqsrc *isrc)
{
struct pl061_softc *sc;
uint8_t mask;
sc = device_get_softc(dev);
mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq;
dprintf("%s: calling enable interrupt %#x\n", __func__, mask);
PL061_LOCK(sc);
mask_and_set(sc, PL061_INTMASK, mask, mask);
PL061_UNLOCK(sc);
}
static int
pl061_pic_map_intr(device_t dev, struct intr_map_data *data,
struct intr_irqsrc **isrcp)
{
struct pl061_softc *sc;
struct intr_map_data_gpio *gdata;
uint32_t irq;
sc = device_get_softc(dev);
if (data->type != INTR_MAP_DATA_GPIO)
return (ENOTSUP);
gdata = (struct intr_map_data_gpio *)data;
irq = gdata->gpio_pin_num;
if (irq >= PL061_NUM_GPIO) {
device_printf(dev, "invalid interrupt number %u\n", irq);
return (EINVAL);
}
dprintf("%s: calling map interrupt %u\n", __func__, irq);
*isrcp = PIC_INTR_ISRC(sc, irq);
return (0);
}
static int
pl061_pic_setup_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
struct pl061_softc *sc;
struct intr_map_data_gpio *gdata;
struct pl061_pin_irqsrc *irqsrc;
uint32_t mode;
uint8_t mask;
if (data == NULL)
return (ENOTSUP);
sc = device_get_softc(dev);
gdata = (struct intr_map_data_gpio *)data;
irqsrc = (struct pl061_pin_irqsrc *)isrc;
mode = gdata->gpio_intr_mode;
mask = 1 << gdata->gpio_pin_num;
dprintf("%s: calling setup interrupt %u mode %#x\n", __func__,
irqsrc->irq, mode);
if (irqsrc->irq != gdata->gpio_pin_num) {
dprintf("%s: interrupts don't match\n", __func__);
return (EINVAL);
}
if (isrc->isrc_handlers != 0) {
dprintf("%s: handler already attached\n", __func__);
return (irqsrc->mode == mode ? 0 : EINVAL);
}
irqsrc->mode = mode;
PL061_LOCK(sc);
if (mode & GPIO_INTR_EDGE_BOTH) {
mask_and_set(sc, PL061_INTBOTHEDGES, mask, mask);
mask_and_set(sc, PL061_INTSENSE, mask, 0);
} else if (mode & GPIO_INTR_EDGE_RISING) {
mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0);
mask_and_set(sc, PL061_INTSENSE, mask, 0);
mask_and_set(sc, PL061_INTEVENT, mask, mask);
} else if (mode & GPIO_INTR_EDGE_FALLING) {
mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0);
mask_and_set(sc, PL061_INTSENSE, mask, 0);
mask_and_set(sc, PL061_INTEVENT, mask, 0);
} else if (mode & GPIO_INTR_LEVEL_HIGH) {
mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0);
mask_and_set(sc, PL061_INTSENSE, mask, mask);
mask_and_set(sc, PL061_INTEVENT, mask, mask);
} else if (mode & GPIO_INTR_LEVEL_LOW) {
mask_and_set(sc, PL061_INTBOTHEDGES, mask, 0);
mask_and_set(sc, PL061_INTSENSE, mask, mask);
mask_and_set(sc, PL061_INTEVENT, mask, 0);
}
PL061_UNLOCK(sc);
return (0);
}
static int
pl061_pic_teardown_intr(device_t dev, struct intr_irqsrc *isrc,
struct resource *res, struct intr_map_data *data)
{
struct pl061_softc *sc;
struct pl061_pin_irqsrc *irqsrc;
uint8_t mask;
irqsrc = (struct pl061_pin_irqsrc *)isrc;
mask = 1 << irqsrc->irq;
dprintf("%s: calling teardown interrupt %#x\n", __func__, mask);
sc = device_get_softc(dev);
if (isrc->isrc_handlers == 0) {
irqsrc->mode = GPIO_INTR_CONFORM;
PL061_LOCK(sc);
mask_and_set(sc, PL061_INTMASK, mask, 0);
PL061_UNLOCK(sc);
}
return (0);
}
static void
pl061_pic_post_filter(device_t dev, struct intr_irqsrc *isrc)
{
struct pl061_softc *sc;
uint8_t mask;
sc = device_get_softc(dev);
mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq;
dprintf("%s: calling post filter %#x\n", __func__, mask);
bus_write_1(sc->sc_mem_res, PL061_INTCLR, mask);
}
static void
pl061_pic_post_ithread(device_t dev, struct intr_irqsrc *isrc)
{
struct pl061_softc *sc;
uint8_t mask;
sc = device_get_softc(dev);
mask = 1 << ((struct pl061_pin_irqsrc *)isrc)->irq;
dprintf("%s: calling post ithread %#x\n", __func__, mask);
bus_write_1(sc->sc_mem_res, PL061_INTCLR, mask);
pl061_pic_enable_intr(dev, isrc);
}
static void
pl061_pic_pre_ithread(device_t dev, struct intr_irqsrc *isrc)
{
pl061_pic_disable_intr(dev, isrc);
}
static int
pl061_intr(void *arg)
{
struct pl061_softc *sc;
struct trapframe *tf;
uint8_t status;
int pin;
sc = (struct pl061_softc *)arg;
tf = curthread->td_intr_frame;
status = bus_read_1(sc->sc_mem_res, PL061_STATUS);
while (status != 0) {
pin = ffs(status) - 1;
status &= ~(1 << pin);
if (intr_isrc_dispatch(PIC_INTR_ISRC(sc, pin), tf) != 0)
device_printf(sc->sc_dev, "spurious interrupt %d\n",
pin);
dprintf("got IRQ on %d\n", pin);
}
return (FILTER_HANDLED);
}
int
pl061_attach(device_t dev)
{
struct pl061_softc *sc;
int ret;
int irq;
const char *name;
sc = device_get_softc(dev);
sc->sc_dev = dev;
sc->sc_mem_rid = 0;
sc->sc_mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&sc->sc_mem_rid, RF_ACTIVE);
if (sc->sc_mem_res == NULL) {
device_printf(dev, "can't allocate memory resource\n");
return (ENXIO);
}
sc->sc_irq_rid = 0;
sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->sc_irq_rid, RF_ACTIVE);
if (sc->sc_irq_res == NULL) {
device_printf(dev, "can't allocate IRQ resource\n");
goto free_mem;
}
ret = bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE,
pl061_intr, NULL, sc, &sc->sc_irq_hdlr);
if (ret) {
device_printf(dev, "can't setup IRQ\n");
goto free_pic;
}
name = device_get_nameunit(dev);
for (irq = 0; irq < PL061_NUM_GPIO; irq++) {
if (bootverbose) {
device_printf(dev,
"trying to register pin %d name %s\n", irq, name);
}
sc->sc_isrcs[irq].irq = irq;
sc->sc_isrcs[irq].mode = GPIO_INTR_CONFORM;
ret = intr_isrc_register(PIC_INTR_ISRC(sc, irq), dev, 0,
"%s", name);
if (ret) {
device_printf(dev, "can't register isrc %d\n", ret);
goto free_isrc;
}
}
sc->sc_busdev = gpiobus_attach_bus(dev);
if (sc->sc_busdev == NULL) {
device_printf(dev, "couldn't attach gpio bus\n");
goto free_isrc;
}
mtx_init(&sc->sc_mtx, device_get_nameunit(dev), "pl061", MTX_SPIN);
return (0);
free_isrc:
/*
* XXX isrc_release_counters() not implemented
* for (irq = 0; irq < PL061_NUM_GPIO; irq++)
* intr_isrc_deregister(PIC_INTR_ISRC(sc, irq));
*/
bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid,
sc->sc_irq_res);
free_pic:
/*
* XXX intr_pic_deregister: not implemented
* intr_pic_deregister(dev, 0);
*/
free_mem:
bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
sc->sc_mem_res);
return (ENXIO);
}
int
pl061_detach(device_t dev)
{
struct pl061_softc *sc;
sc = device_get_softc(dev);
if (sc->sc_busdev)
gpiobus_detach_bus(dev);
if (sc->sc_irq_hdlr != NULL)
bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_hdlr);
if (sc->sc_irq_res != NULL)
bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid,
sc->sc_irq_res);
if (sc->sc_mem_res != NULL)
bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid,
sc->sc_mem_res);
PL061_LOCK_DESTROY(sc);
return (0);
}
static device_method_t pl061_methods[] = {
/* Device interface */
DEVMETHOD(device_attach, pl061_attach),
DEVMETHOD(device_detach, pl061_detach),
/* GPIO protocol */
DEVMETHOD(gpio_get_bus, pl061_get_bus),
DEVMETHOD(gpio_pin_max, pl061_pin_max),
DEVMETHOD(gpio_pin_getname, pl061_pin_getname),
DEVMETHOD(gpio_pin_getflags, pl061_pin_getflags),
DEVMETHOD(gpio_pin_getcaps, pl061_pin_getcaps),
DEVMETHOD(gpio_pin_setflags, pl061_pin_setflags),
DEVMETHOD(gpio_pin_get, pl061_pin_get),
DEVMETHOD(gpio_pin_set, pl061_pin_set),
DEVMETHOD(gpio_pin_toggle, pl061_pin_toggle),
/* Interrupt controller interface */
DEVMETHOD(pic_disable_intr, pl061_pic_disable_intr),
DEVMETHOD(pic_enable_intr, pl061_pic_enable_intr),
DEVMETHOD(pic_map_intr, pl061_pic_map_intr),
DEVMETHOD(pic_setup_intr, pl061_pic_setup_intr),
DEVMETHOD(pic_teardown_intr, pl061_pic_teardown_intr),
DEVMETHOD(pic_post_filter, pl061_pic_post_filter),
DEVMETHOD(pic_post_ithread, pl061_pic_post_ithread),
DEVMETHOD(pic_pre_ithread, pl061_pic_pre_ithread),
DEVMETHOD_END
};
DEFINE_CLASS_0(pl061, pl061_driver, pl061_methods, sizeof(struct pl061_softc));