#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/errno.h>
#include <sys/lock.h>
#include <sys/bus.h>
#include <sys/rman.h>
#include "opt_acpi.h"
#include "acpi.h"
#include <dev/acpica/acpivar.h>
#include <bus/pci/pcivar.h>
#include "gpio_intel_var.h"
#include "gpio_if.h"
static int gpio_intel_probe(device_t dev);
static int gpio_intel_attach(device_t dev);
static int gpio_intel_detach(device_t dev);
static int gpio_intel_alloc_intr(device_t dev, u_int pin, int trigger,
int polarity, int termination, void **cookiep);
static void gpio_intel_setup_intr(device_t dev, void *cookie, void *arg,
driver_intr_t *handler);
static void gpio_intel_teardown_intr(device_t dev, void *cookie);
static void gpio_intel_free_intr(device_t dev, void *cookie);
static int gpio_intel_alloc_io_pin(device_t dev, u_int pin, int flags,
void **cookiep);
static void gpio_intel_release_io_pin(device_t dev, void *cookie);
static int gpio_intel_read_pin(device_t dev, void *cookie);
static void gpio_intel_write_pin(device_t dev, void *cookie, int value);
static void gpio_intel_intr(void *arg);
static int gpio_intel_pin_exists(struct gpio_intel_softc *sc,
uint16_t pin);
static char *cherryview_ids[] = { "INT33FF", NULL };
static int
gpio_intel_probe(device_t dev)
{
if (acpi_disabled("gpio_intel") ||
ACPI_ID_PROBE(device_get_parent(dev), dev, cherryview_ids) == NULL)
return (ENXIO);
device_set_desc(dev, "Intel Cherry Trail GPIO Controller");
return (BUS_PROBE_DEFAULT);
}
static int
gpio_intel_attach(device_t dev)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
int error, i, rid;
lockinit(&sc->lk, "gpio_intel", 0, LK_CANRECURSE);
sc->dev = dev;
if (ACPI_ID_PROBE(device_get_parent(dev), dev, cherryview_ids)
!= NULL) {
error = gpio_cherryview_matchuid(sc);
if (error) {
error = ENXIO;
goto err;
}
} else {
error = ENXIO;
goto err;
}
for (i = 0; i < NELEM(sc->intrmaps); i++) {
sc->intrmaps[i].pin = -1;
}
for (i = 0; i < NELEM(sc->iomaps); i++) {
sc->iomaps[i].pin = -1;
}
rid = 0;
sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
&rid, RF_ACTIVE);
if (sc->mem_res == NULL) {
device_printf(dev, "unable to map registers");
error = ENXIO;
goto err;
}
rid = 0;
sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&rid, RF_ACTIVE);
if (sc->irq_res == NULL) {
device_printf(dev, "unable to map interrupt");
error = ENXIO;
goto err;
}
lockmgr(&sc->lk, LK_EXCLUSIVE);
error = bus_setup_intr(dev, sc->irq_res, INTR_MPSAFE,
gpio_intel_intr, sc, &sc->intrhand, NULL);
if (error)
device_printf(dev, "Can't setup IRQ\n");
pci_set_powerstate(dev, PCI_POWERSTATE_D0);
sc->fns->init(sc);
lockmgr(&sc->lk, LK_RELEASE);
device_add_child(dev, "gpio_acpi", -1);
bus_generic_attach(dev);
return (0);
err:
if (sc->irq_res) {
bus_release_resource(dev, SYS_RES_IRQ,
rman_get_rid(sc->irq_res), sc->irq_res);
sc->irq_res = NULL;
}
if (sc->mem_res) {
bus_release_resource(dev, SYS_RES_MEMORY,
rman_get_rid(sc->mem_res), sc->mem_res);
sc->mem_res = NULL;
}
return (error);
}
static int
gpio_intel_detach(device_t dev)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
int error;
error = bus_generic_detach(dev);
if (error)
return (error);
device_delete_children(dev);
bus_teardown_intr(dev, sc->irq_res, sc->intrhand);
if (sc->irq_res) {
bus_release_resource(dev, SYS_RES_IRQ,
rman_get_rid(sc->irq_res), sc->irq_res);
sc->irq_res = NULL;
}
if (sc->mem_res) {
bus_release_resource(dev, SYS_RES_MEMORY,
rman_get_rid(sc->mem_res), sc->mem_res);
sc->mem_res = NULL;
}
lockuninit(&sc->lk);
pci_set_powerstate(dev, PCI_POWERSTATE_D3);
return 0;
}
static int
gpio_intel_alloc_intr(device_t dev, u_int pin, int trigger, int polarity,
int termination, void **cookiep)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_intr_map *map = NULL;
int i, ret;
if (cookiep == NULL)
return (EINVAL);
lockmgr(&sc->lk, LK_EXCLUSIVE);
if (gpio_intel_pin_exists(sc, pin)) {
for (i = 0; i < NELEM(sc->intrmaps); i++) {
if (sc->intrmaps[i].pin == pin) {
lockmgr(&sc->lk, LK_RELEASE);
return (EBUSY);
}
}
ret = sc->fns->map_intr(sc, pin, trigger, polarity,
termination);
if (ret == 0) {
for (i = 0; i < NELEM(sc->intrmaps); i++) {
if (sc->intrmaps[i].pin == pin)
map = &sc->intrmaps[i];
}
if (map != NULL) {
*cookiep = map;
map->arg = NULL;
map->handler = NULL;
}
}
} else {
device_printf(sc->dev, "%s: Invalid pin %d\n", __func__, pin);
ret = ENOENT;
}
lockmgr(&sc->lk, LK_RELEASE);
return (ret);
}
static void
gpio_intel_free_intr(device_t dev, void *cookie)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_intr_map *map = (struct pin_intr_map *)cookie;
KKASSERT(gpio_intel_pin_exists(sc, map->pin));
lockmgr(&sc->lk, LK_EXCLUSIVE);
map->arg = NULL;
map->handler = NULL;
sc->fns->unmap_intr(sc, map);
lockmgr(&sc->lk, LK_RELEASE);
}
static void
gpio_intel_setup_intr(device_t dev, void *cookie, void *arg,
driver_intr_t *handler)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_intr_map *map = (struct pin_intr_map *)cookie;
KKASSERT(gpio_intel_pin_exists(sc, map->pin));
lockmgr(&sc->lk, LK_EXCLUSIVE);
map->arg = arg;
map->handler = handler;
sc->fns->enable_intr(sc, map);
lockmgr(&sc->lk, LK_RELEASE);
}
static void
gpio_intel_teardown_intr(device_t dev, void *cookie)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_intr_map *map = (struct pin_intr_map *)cookie;
KKASSERT(gpio_intel_pin_exists(sc, map->pin));
lockmgr(&sc->lk, LK_EXCLUSIVE);
sc->fns->disable_intr(sc, map);
map->arg = NULL;
map->handler = NULL;
lockmgr(&sc->lk, LK_RELEASE);
}
static int
gpio_intel_alloc_io_pin(device_t dev, u_int pin, int flags, void **cookiep)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_io_map *map = NULL;
int i, ret;
if (cookiep == NULL)
return (EINVAL);
if (!gpio_intel_pin_exists(sc, pin))
return (ENOENT);
lockmgr(&sc->lk, LK_EXCLUSIVE);
for (i = 0; i < NELEM(sc->iomaps); i++) {
if (sc->iomaps[i].pin == pin) {
lockmgr(&sc->lk, LK_RELEASE);
return (EBUSY);
}
}
for (i = 0; i < NELEM(sc->iomaps); i++) {
if (sc->iomaps[i].pin == -1) {
map = &sc->iomaps[i];
break;
}
}
if (map == NULL) {
ret = EBUSY;
} else if (sc->fns->check_io_pin(sc, pin, flags)) {
map->pin = pin;
map->flags = flags;
*cookiep = map;
ret = 0;
} else {
ret = EBUSY;
}
lockmgr(&sc->lk, LK_RELEASE);
return (ret);
}
static void
gpio_intel_release_io_pin(device_t dev, void *cookie)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_io_map *map = (struct pin_io_map *)cookie;
lockmgr(&sc->lk, LK_EXCLUSIVE);
map->pin = -1;
map->flags = 0;
lockmgr(&sc->lk, LK_RELEASE);
}
static int
gpio_intel_read_pin(device_t dev, void *cookie)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_io_map *map = (struct pin_io_map *)cookie;
int val;
KKASSERT(map->pin >= 0);
KKASSERT(gpio_intel_pin_exists(sc, map->pin));
lockmgr(&sc->lk, LK_EXCLUSIVE);
val = sc->fns->read_pin(sc, map->pin);
lockmgr(&sc->lk, LK_RELEASE);
return (val);
}
static void
gpio_intel_write_pin(device_t dev, void *cookie, int value)
{
struct gpio_intel_softc *sc = device_get_softc(dev);
struct pin_io_map *map = (struct pin_io_map *)cookie;
KKASSERT(map->pin >= 0);
KKASSERT(gpio_intel_pin_exists(sc, map->pin));
lockmgr(&sc->lk, LK_EXCLUSIVE);
sc->fns->write_pin(sc, map->pin, value);
lockmgr(&sc->lk, LK_RELEASE);
}
static void
gpio_intel_intr(void *arg)
{
struct gpio_intel_softc *sc = (struct gpio_intel_softc *)arg;
lockmgr(&sc->lk, LK_EXCLUSIVE);
sc->fns->intr(arg);
lockmgr(&sc->lk, LK_RELEASE);
}
static int
gpio_intel_pin_exists(struct gpio_intel_softc *sc, uint16_t pin)
{
struct pinrange *r;
for (r = sc->ranges; r->start != -1 && r->end != -1; r++) {
if (r->start <= (int)pin && r->end >= (int)pin)
return (TRUE);
}
return (FALSE);
}
static device_method_t gpio_intel_methods[] = {
DEVMETHOD(device_probe, gpio_intel_probe),
DEVMETHOD(device_attach, gpio_intel_attach),
DEVMETHOD(device_detach, gpio_intel_detach),
DEVMETHOD(gpio_alloc_intr, gpio_intel_alloc_intr),
DEVMETHOD(gpio_free_intr, gpio_intel_free_intr),
DEVMETHOD(gpio_setup_intr, gpio_intel_setup_intr),
DEVMETHOD(gpio_teardown_intr, gpio_intel_teardown_intr),
DEVMETHOD(gpio_alloc_io_pin, gpio_intel_alloc_io_pin),
DEVMETHOD(gpio_release_io_pin, gpio_intel_release_io_pin),
DEVMETHOD(gpio_read_pin, gpio_intel_read_pin),
DEVMETHOD(gpio_write_pin, gpio_intel_write_pin),
DEVMETHOD_END
};
static driver_t gpio_intel_driver = {
"gpio_intel",
gpio_intel_methods,
sizeof(struct gpio_intel_softc),
.gpri = KOBJ_GPRI_ACPI+2
};
static devclass_t gpio_intel_devclass;
DRIVER_MODULE(gpio_intel, acpi, gpio_intel_driver, gpio_intel_devclass,
NULL, NULL);
MODULE_DEPEND(gpio_intel, acpi, 1, 1, 1);
MODULE_DEPEND(gpio_intel, gpio_acpi, 1, 1, 1);
MODULE_VERSION(gpio_intel, 1);