#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/gpio.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/ofw/ofw_bus.h>
#include <arm/broadcom/bcm2835/bcm2835_firmware.h>
#include <arm/broadcom/bcm2835/bcm2835_vcbus.h>
#include "gpio_if.h"
#define RPI_VIRT_GPIO_PINS 2
struct rpi_virt_gpio_softc {
device_t busdev;
device_t firmware;
struct mtx sc_mtx;
void *vaddr;
vm_paddr_t paddr;
struct gpio_pin gpio_pins[RPI_VIRT_GPIO_PINS];
uint32_t state[RPI_VIRT_GPIO_PINS];
};
#define RPI_VIRT_GPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx)
#define RPI_VIRT_GPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx)
static struct ofw_compat_data compat_data[] = {
{"brcm,bcm2835-virtgpio", 1},
{NULL, 0}
};
static device_t
rpi_virt_gpio_get_bus(device_t dev)
{
struct rpi_virt_gpio_softc *sc;
sc = device_get_softc(dev);
return (sc->busdev);
}
static int
rpi_virt_gpio_pin_max(device_t dev, int *maxpin)
{
*maxpin = RPI_VIRT_GPIO_PINS - 1;
return (0);
}
static int
rpi_virt_gpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps)
{
if (pin >= RPI_VIRT_GPIO_PINS)
return (EINVAL);
*caps = GPIO_PIN_OUTPUT;
return (0);
}
static int
rpi_virt_gpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags)
{
if (pin >= RPI_VIRT_GPIO_PINS)
return (EINVAL);
*flags = GPIO_PIN_OUTPUT;
return (0);
}
static int
rpi_virt_gpio_pin_set(device_t dev, uint32_t pin, uint32_t value)
{
struct rpi_virt_gpio_softc *sc;
uint32_t *ptr;
uint16_t on, off;
if (pin >= RPI_VIRT_GPIO_PINS)
return (EINVAL);
sc = device_get_softc(dev);
RPI_VIRT_GPIO_LOCK(sc);
on = (uint16_t)(sc->state[pin] >> 16);
off = (uint16_t)sc->state[pin];
if (bootverbose)
device_printf(dev, "on: %hu, off: %hu, now: %d -> %u\n",
on, off, on - off, value);
if ((value > 0 && on - off != 0) || (value == 0 && on - off == 0)) {
RPI_VIRT_GPIO_UNLOCK(sc);
return (0);
}
if (value > 0)
++on;
else
++off;
sc->state[pin] = (on << 16 | off);
ptr = (uint32_t *)sc->vaddr;
ptr[pin] = sc->state[pin];
RPI_VIRT_GPIO_UNLOCK(sc);
return (0);
}
static int
rpi_virt_gpio_pin_get(device_t dev, uint32_t pin, uint32_t *val)
{
struct rpi_virt_gpio_softc *sc;
uint32_t *ptr, v;
if (pin >= RPI_VIRT_GPIO_PINS)
return (EINVAL);
sc = device_get_softc(dev);
ptr = (uint32_t *)sc->vaddr;
RPI_VIRT_GPIO_LOCK(sc);
v = ptr[pin];
RPI_VIRT_GPIO_UNLOCK(sc);
*val = ((uint16_t)(v >> 16) - (uint16_t)v) == 0 ? 0 : 1;
return (0);
}
static int
rpi_virt_gpio_pin_toggle(device_t dev, uint32_t pin)
{
int rv;
unsigned int val;
if (pin >= RPI_VIRT_GPIO_PINS)
return (EINVAL);
rv = rpi_virt_gpio_pin_get(dev, pin, &val);
if (rv != 0)
return (rv);
rv = rpi_virt_gpio_pin_set(dev, pin, val == 0 ? 1 : 0);
return (rv);
}
static int
rpi_virt_gpio_probe(device_t dev)
{
device_t firmware;
phandle_t gpio;
union msg_gpiovirtbuf cfg;
int rv;
if (ofw_bus_status_okay(dev) == 0)
return (ENXIO);
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0)
return (ENXIO);
gpio = ofw_bus_get_node(dev);
if (OF_hasprop(gpio, "gpio-controller") == 0)
return (ENXIO);
firmware = device_get_parent(dev);
rv = bcm2835_firmware_property(firmware,
BCM2835_FIRMWARE_TAG_GET_GPIOVIRTBUF, &cfg, sizeof(cfg));
if (rv != 0)
return (ENXIO);
device_set_desc(dev, "Raspberry Pi Virtual GPIO controller");
return (BUS_PROBE_DEFAULT);
}
static int
rpi_virt_gpio_attach(device_t dev)
{
struct rpi_virt_gpio_softc *sc;
union msg_gpiovirtbuf cfg;
int i, rv;
sc = device_get_softc(dev);
sc->firmware = device_get_parent(dev);
mtx_init(&sc->sc_mtx, "Raspberry Pi virtgpio", NULL, MTX_SPIN);
sc->vaddr = contigmalloc(
PAGE_SIZE,
M_DEVBUF, M_ZERO,
0, BCM2838_PERIPH_MAXADDR,
PAGE_SIZE, 0);
if (sc->vaddr == NULL) {
device_printf(dev, "Failed to allocate memory.\n");
return ENOMEM;
}
sc->paddr = vtophys(sc->vaddr);
pmap_change_attr((vm_offset_t)sc->vaddr, PAGE_SIZE,
VM_MEMATTR_UNCACHEABLE);
if (bootverbose)
device_printf(dev,
"KVA alloc'd: virtual: %p, phys: %#jx\n",
sc->vaddr, (uintmax_t)sc->paddr);
cfg.req.addr = (uint32_t)sc->paddr;
rv = bcm2835_firmware_property(sc->firmware,
BCM2835_FIRMWARE_TAG_SET_GPIOVIRTBUF, &cfg, sizeof(cfg));
if (bootverbose)
device_printf(dev, "rv: %d, addr: 0x%x\n", rv, cfg.resp.addr);
if (rv != 0 || cfg.resp.addr != 0)
goto fail;
for (i = 0; i < RPI_VIRT_GPIO_PINS; i++) {
sc->gpio_pins[i].gp_pin = i;
sc->gpio_pins[i].gp_caps = sc->gpio_pins[i].gp_flags
= GPIO_PIN_OUTPUT;
}
sc->busdev = gpiobus_add_bus(dev);
if (sc->busdev == NULL)
goto fail;
bus_attach_children(dev);
return (0);
fail:
free(sc->vaddr, M_DEVBUF);
mtx_destroy(&sc->sc_mtx);
return (ENXIO);
}
static int
rpi_virt_gpio_detach(device_t dev)
{
return (EBUSY);
}
static device_method_t rpi_virt_gpio_methods[] = {
DEVMETHOD(device_probe, rpi_virt_gpio_probe),
DEVMETHOD(device_attach, rpi_virt_gpio_attach),
DEVMETHOD(device_detach, rpi_virt_gpio_detach),
DEVMETHOD(gpio_get_bus, rpi_virt_gpio_get_bus),
DEVMETHOD(gpio_pin_max, rpi_virt_gpio_pin_max),
DEVMETHOD(gpio_pin_getcaps, rpi_virt_gpio_pin_getcaps),
DEVMETHOD(gpio_pin_getflags, rpi_virt_gpio_pin_getflags),
DEVMETHOD(gpio_pin_set, rpi_virt_gpio_pin_set),
DEVMETHOD(gpio_pin_get, rpi_virt_gpio_pin_get),
DEVMETHOD(gpio_pin_toggle, rpi_virt_gpio_pin_toggle),
DEVMETHOD_END
};
static driver_t rpi_virt_gpio_driver = {
"gpio",
rpi_virt_gpio_methods,
sizeof(struct rpi_virt_gpio_softc),
};
EARLY_DRIVER_MODULE(rpi_virt_gpio, bcm2835_firmware, rpi_virt_gpio_driver,
0, 0, BUS_PASS_INTERRUPT + BUS_PASS_ORDER_LATE);