root/sys/dev/gpio/pl061.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * 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/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)
{
        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)
{
        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;
        }

        /* Mask all interrupts. They will be unmasked as needed later */
        bus_write_1(sc->sc_mem_res, PL061_INTMASK, 0);

        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;
                }
        }

        mtx_init(&sc->sc_mtx, device_get_nameunit(dev), "pl061", MTX_SPIN);

        if (sc->sc_xref != 0 && !intr_pic_register(dev, sc->sc_xref)) {
                device_printf(dev, "couldn't register PIC\n");
                PL061_LOCK_DESTROY(sc);
                goto free_isrc;
        }

        sc->sc_busdev = gpiobus_add_bus(dev);
        if (sc->sc_busdev == NULL) {
                device_printf(dev, "couldn't attach gpio bus\n");
                PL061_LOCK_DESTROY(sc);
                goto free_isrc;
        }

        bus_attach_children(dev);
        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_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_hdlr);
        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),

        /* Bus interface */
        DEVMETHOD(bus_setup_intr,       bus_generic_setup_intr),
        DEVMETHOD(bus_teardown_intr,    bus_generic_teardown_intr),

        /* 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(gpio, pl061_driver, pl061_methods, sizeof(struct pl061_softc));