#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <sys/gpio.h>
#include <sys/ioccom.h>
#include <sys/filio.h>
#include <sys/fcntl.h>
#include <sys/sigio.h>
#include <sys/signalvar.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/uio.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#include <sys/module.h>
#include <dev/gpio/gpiobusvar.h>
#include "gpiobus_if.h"
#undef GPIOC_DEBUG
#ifdef GPIOC_DEBUG
#define dprintf printf
#define ddevice_printf device_printf
#else
#define dprintf(x, arg...)
#define ddevice_printf(dev, x, arg...)
#endif
struct gpioc_softc {
device_t sc_dev;
device_t sc_pdev;
struct cdev *sc_ctl_dev;
int sc_unit;
int sc_npins;
struct gpioc_pin_intr *sc_pin_intr;
};
struct gpioc_pin_intr {
struct gpioc_softc *sc;
gpio_pin_t pin;
uint32_t intr_mode;
bool config_locked;
int intr_rid;
struct resource *intr_res;
void *intr_cookie;
struct mtx mtx;
SLIST_HEAD(gpioc_privs_list, gpioc_privs) privs;
};
struct gpioc_cdevpriv {
struct gpioc_softc *sc;
struct selinfo selinfo;
bool async;
uint8_t report_option;
struct sigio *sigio;
struct mtx mtx;
struct gpioc_pin_event *events;
int numevents;
int evidx_head;
int evidx_tail;
SLIST_HEAD(gpioc_pins_list, gpioc_pins) pins;
};
struct gpioc_privs {
struct gpioc_cdevpriv *priv;
SLIST_ENTRY(gpioc_privs) next;
};
struct gpioc_pins {
struct gpioc_pin_intr *pin;
int eventcount;
int firstevent;
SLIST_ENTRY(gpioc_pins) next;
};
struct gpioc_pin_event {
struct gpioc_pins *privpin;
sbintime_t event_time;
bool event_pin_state;
};
static MALLOC_DEFINE(M_GPIOC, "gpioc", "gpioc device data");
static int gpioc_allocate_pin_intr(struct gpioc_softc*,
struct gpioc_pin_intr*, uint32_t, uint32_t);
static int gpioc_release_pin_intr(struct gpioc_softc*,
struct gpioc_pin_intr*);
static int gpioc_attach_priv_pin(struct gpioc_cdevpriv*,
struct gpioc_pin_intr*);
static int gpioc_detach_priv_pin(struct gpioc_cdevpriv*,
struct gpioc_pin_intr*);
static bool gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv*,
struct gpioc_pin_intr *intr_conf);
static uint32_t gpioc_get_intr_config(struct gpioc_softc*,
struct gpioc_cdevpriv*, uint32_t pin);
static int gpioc_set_intr_config(struct gpioc_softc*,
struct gpioc_cdevpriv*, uint32_t, uint32_t);
static void gpioc_interrupt_handler(void*);
static int gpioc_kqread(struct knote*, long);
static void gpioc_kqdetach(struct knote*);
static int gpioc_probe(device_t dev);
static int gpioc_attach(device_t dev);
static int gpioc_detach(device_t dev);
static void gpioc_cdevpriv_dtor(void*);
static d_open_t gpioc_open;
static d_read_t gpioc_read;
static d_ioctl_t gpioc_ioctl;
static d_poll_t gpioc_poll;
static d_kqfilter_t gpioc_kqfilter;
static struct cdevsw gpioc_cdevsw = {
.d_version = D_VERSION,
.d_open = gpioc_open,
.d_read = gpioc_read,
.d_ioctl = gpioc_ioctl,
.d_poll = gpioc_poll,
.d_kqfilter = gpioc_kqfilter,
.d_name = "gpioc",
};
static const struct filterops gpioc_read_filterops = {
.f_isfd = true,
.f_attach = NULL,
.f_detach = gpioc_kqdetach,
.f_event = gpioc_kqread,
.f_touch = NULL,
.f_copy = knote_triv_copy,
};
static struct gpioc_pin_event *
next_head_event(struct gpioc_cdevpriv *priv)
{
struct gpioc_pin_event *rv;
rv = &priv->events[priv->evidx_head++];
if (priv->evidx_head == priv->numevents)
priv->evidx_head = 0;
return (rv);
}
static struct gpioc_pin_event *
next_tail_event(struct gpioc_cdevpriv *priv)
{
struct gpioc_pin_event *rv;
rv = &priv->events[priv->evidx_tail++];
if (priv->evidx_tail == priv->numevents)
priv->evidx_tail = 0;
return (rv);
}
static size_t
number_of_events(struct gpioc_cdevpriv *priv)
{
if (priv->evidx_head >= priv->evidx_tail)
return (priv->evidx_head - priv->evidx_tail);
else
return (priv->numevents + priv->evidx_head - priv->evidx_tail);
}
static int
gpioc_allocate_pin_intr(struct gpioc_softc *sc,
struct gpioc_pin_intr *intr_conf, uint32_t pin, uint32_t flags)
{
int err;
intr_conf->config_locked = true;
mtx_unlock(&intr_conf->mtx);
MPASS(intr_conf->pin == NULL);
err = gpio_pin_get_by_bus_pinnum(sc->sc_pdev, pin, &intr_conf->pin);
if (err != 0)
goto error_exit;
intr_conf->intr_res = gpio_alloc_intr_resource(sc->sc_dev,
intr_conf->intr_rid, RF_ACTIVE, intr_conf->pin, flags);
if (intr_conf->intr_res == NULL) {
err = ENXIO;
goto error_pin;
}
err = bus_setup_intr(sc->sc_dev, intr_conf->intr_res,
INTR_TYPE_MISC | INTR_MPSAFE, NULL, gpioc_interrupt_handler,
intr_conf, &intr_conf->intr_cookie);
if (err != 0) {
bus_release_resource(sc->sc_dev, intr_conf->intr_res);
intr_conf->intr_res = NULL;
goto error_pin;
}
intr_conf->intr_mode = flags;
error_exit:
mtx_lock(&intr_conf->mtx);
intr_conf->config_locked = false;
wakeup(&intr_conf->config_locked);
return (err);
error_pin:
gpio_pin_release(intr_conf->pin);
intr_conf->pin = NULL;
goto error_exit;
}
static int
gpioc_release_pin_intr(struct gpioc_softc *sc, struct gpioc_pin_intr *intr_conf)
{
int err;
intr_conf->config_locked = true;
mtx_unlock(&intr_conf->mtx);
if (intr_conf->intr_cookie != NULL) {
err = bus_teardown_intr(sc->sc_dev, intr_conf->intr_res,
intr_conf->intr_cookie);
if (err != 0)
goto error_exit;
else
intr_conf->intr_cookie = NULL;
}
if (intr_conf->intr_res != NULL) {
err = bus_release_resource(sc->sc_dev, SYS_RES_IRQ,
intr_conf->intr_rid, intr_conf->intr_res);
if (err != 0)
goto error_exit;
else {
intr_conf->intr_rid = 0;
intr_conf->intr_res = NULL;
}
}
gpio_pin_release(intr_conf->pin);
intr_conf->pin = NULL;
intr_conf->intr_mode = 0;
err = 0;
error_exit:
mtx_lock(&intr_conf->mtx);
intr_conf->config_locked = false;
wakeup(&intr_conf->config_locked);
return (err);
}
static int
gpioc_attach_priv_pin(struct gpioc_cdevpriv *priv,
struct gpioc_pin_intr *intr_conf)
{
struct gpioc_privs *priv_link;
struct gpioc_pins *pin_link;
unsigned int consistency_a __diagused;
unsigned int consistency_b __diagused;
consistency_a = 0;
consistency_b = 0;
mtx_assert(&intr_conf->mtx, MA_OWNED);
mtx_lock(&priv->mtx);
SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
if (priv_link->priv == priv)
consistency_a++;
}
KASSERT(consistency_a <= 1,
("inconsistent links between pin config and cdevpriv"));
SLIST_FOREACH(pin_link, &priv->pins, next) {
if (pin_link->pin == intr_conf)
consistency_b++;
}
KASSERT(consistency_a == consistency_b,
("inconsistent links between pin config and cdevpriv"));
if (consistency_a == 1 && consistency_b == 1) {
mtx_unlock(&priv->mtx);
return (EEXIST);
}
priv_link = malloc(sizeof(struct gpioc_privs), M_GPIOC,
M_NOWAIT | M_ZERO);
if (priv_link == NULL)
{
mtx_unlock(&priv->mtx);
return (ENOMEM);
}
pin_link = malloc(sizeof(struct gpioc_pins), M_GPIOC,
M_NOWAIT | M_ZERO);
if (pin_link == NULL) {
mtx_unlock(&priv->mtx);
return (ENOMEM);
}
priv_link->priv = priv;
pin_link->pin = intr_conf;
SLIST_INSERT_HEAD(&intr_conf->privs, priv_link, next);
SLIST_INSERT_HEAD(&priv->pins, pin_link, next);
mtx_unlock(&priv->mtx);
return (0);
}
static int
gpioc_detach_priv_pin(struct gpioc_cdevpriv *priv,
struct gpioc_pin_intr *intr_conf)
{
struct gpioc_privs *priv_link, *priv_link_temp;
struct gpioc_pins *pin_link, *pin_link_temp;
unsigned int consistency_a __diagused;
unsigned int consistency_b __diagused;
consistency_a = 0;
consistency_b = 0;
mtx_assert(&intr_conf->mtx, MA_OWNED);
mtx_lock(&priv->mtx);
SLIST_FOREACH_SAFE(priv_link, &intr_conf->privs, next, priv_link_temp) {
if (priv_link->priv == priv) {
SLIST_REMOVE(&intr_conf->privs, priv_link, gpioc_privs,
next);
free(priv_link, M_GPIOC);
consistency_a++;
}
}
KASSERT(consistency_a <= 1,
("inconsistent links between pin config and cdevpriv"));
SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) {
if (pin_link->pin == intr_conf) {
if (pin_link->eventcount > 0) {
priv->evidx_head = priv->evidx_tail = 0;
}
SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next);
free(pin_link, M_GPIOC);
consistency_b++;
}
}
KASSERT(consistency_a == consistency_b,
("inconsistent links between pin config and cdevpriv"));
mtx_unlock(&priv->mtx);
return (0);
}
static bool
gpioc_intr_reconfig_allowed(struct gpioc_cdevpriv *priv,
struct gpioc_pin_intr *intr_conf)
{
struct gpioc_privs *priv_link;
mtx_assert(&intr_conf->mtx, MA_OWNED);
if (SLIST_EMPTY(&intr_conf->privs))
return (true);
SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
if (priv_link->priv != priv)
return (false);
}
return (true);
}
static uint32_t
gpioc_get_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv,
uint32_t pin)
{
struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin];
struct gpioc_privs *priv_link;
uint32_t flags;
flags = intr_conf->intr_mode;
if (flags == 0)
return (0);
mtx_lock(&intr_conf->mtx);
SLIST_FOREACH(priv_link, &intr_conf->privs, next) {
if (priv_link->priv == priv) {
flags |= GPIO_INTR_ATTACHED;
break;
}
}
mtx_unlock(&intr_conf->mtx);
return (flags);
}
static int
gpioc_set_intr_config(struct gpioc_softc *sc, struct gpioc_cdevpriv *priv,
uint32_t pin, uint32_t flags)
{
struct gpioc_pin_intr *intr_conf = &sc->sc_pin_intr[pin];
int res;
res = 0;
if (intr_conf->intr_mode == 0 && flags == 0) {
return (0);
}
mtx_lock(&intr_conf->mtx);
while (intr_conf->config_locked == true)
mtx_sleep(&intr_conf->config_locked, &intr_conf->mtx, 0,
"gpicfg", 0);
if (intr_conf->intr_mode == 0 && flags != 0) {
res = gpioc_allocate_pin_intr(sc, intr_conf, pin, flags);
if (res == 0)
res = gpioc_attach_priv_pin(priv, intr_conf);
if (res == EEXIST)
res = 0;
} else if (intr_conf->intr_mode == flags) {
res = gpioc_attach_priv_pin(priv, intr_conf);
if (res == EEXIST)
res = 0;
} else if (intr_conf->intr_mode != 0 && flags == 0) {
if (gpioc_intr_reconfig_allowed(priv, intr_conf)) {
res = gpioc_release_pin_intr(sc, intr_conf);
}
if (res == 0)
res = gpioc_detach_priv_pin(priv, intr_conf);
} else {
if (!gpioc_intr_reconfig_allowed(priv, intr_conf))
res = EBUSY;
else {
res = gpioc_release_pin_intr(sc, intr_conf);
if (res == 0)
res = gpioc_allocate_pin_intr(sc, intr_conf,
pin, flags);
if (res == 0)
res = gpioc_attach_priv_pin(priv, intr_conf);
if (res == EEXIST)
res = 0;
}
}
mtx_unlock(&intr_conf->mtx);
return (res);
}
static void
gpioc_interrupt_handler(void *arg)
{
struct gpioc_pin_intr *intr_conf;
struct gpioc_privs *privs;
sbintime_t evtime;
bool pin_state;
intr_conf = arg;
evtime = sbinuptime();
if (intr_conf->intr_mode & GPIO_INTR_EDGE_BOTH)
gpio_pin_is_active(intr_conf->pin, &pin_state);
else if (intr_conf->intr_mode & GPIO_INTR_EDGE_RISING)
pin_state = true;
else
pin_state = false;
mtx_lock(&intr_conf->mtx);
if (intr_conf->config_locked == true) {
ddevice_printf(sc->sc_dev, "Interrupt configuration in "
"progress. Discarding interrupt on pin %d.\n",
intr_conf->pin->pin);
mtx_unlock(&intr_conf->mtx);
return;
}
if (SLIST_EMPTY(&intr_conf->privs)) {
ddevice_printf(sc->sc_dev, "No file descriptor associated with "
"occurred interrupt on pin %d.\n", intr_conf->pin->pin);
mtx_unlock(&intr_conf->mtx);
return;
}
SLIST_FOREACH(privs, &intr_conf->privs, next) {
struct gpioc_cdevpriv *priv = privs->priv;
struct gpioc_pins *privpin;
struct gpioc_pin_event *event;
mtx_lock(&priv->mtx);
SLIST_FOREACH(privpin, &priv->pins, next) {
if (privpin->pin == intr_conf)
break;
}
if (privpin == NULL) {
ddevice_printf(sc->sc_dev, "Cannot find privpin\n");
mtx_unlock(&priv->mtx);
continue;
}
if (priv->report_option == GPIO_EVENT_REPORT_DETAIL) {
event = next_head_event(priv);
if (priv->evidx_head == priv->evidx_tail)
next_tail_event(priv);
} else {
if (privpin->eventcount > 0)
event = &priv->events[privpin->firstevent + 1];
else {
privpin->firstevent = priv->evidx_head;
event = next_head_event(priv);
event->privpin = privpin;
event->event_time = evtime;
event->event_pin_state = pin_state;
event = next_head_event(priv);
}
++privpin->eventcount;
}
event->privpin = privpin;
event->event_time = evtime;
event->event_pin_state = pin_state;
wakeup(priv);
selwakeup(&priv->selinfo);
KNOTE_LOCKED(&priv->selinfo.si_note, 0);
if (priv->async == true && priv->sigio != NULL)
pgsigio(&priv->sigio, SIGIO, 0);
mtx_unlock(&priv->mtx);
}
mtx_unlock(&intr_conf->mtx);
}
static int
gpioc_probe(device_t dev)
{
device_set_desc(dev, "GPIO controller");
return (0);
}
static int
gpioc_attach(device_t dev)
{
int err;
struct gpioc_softc *sc;
struct make_dev_args devargs;
sc = device_get_softc(dev);
sc->sc_dev = dev;
sc->sc_pdev = device_get_parent(dev);
sc->sc_unit = device_get_unit(dev);
sc->sc_npins = gpiobus_get_npins(dev);
sc->sc_pin_intr = malloc(sizeof(struct gpioc_pin_intr) * sc->sc_npins,
M_GPIOC, M_WAITOK | M_ZERO);
for (int i = 0; i < sc->sc_npins; i++) {
sc->sc_pin_intr[i].sc = sc;
mtx_init(&sc->sc_pin_intr[i].mtx, "gpioc pin", NULL, MTX_DEF);
SLIST_INIT(&sc->sc_pin_intr[i].privs);
}
make_dev_args_init(&devargs);
devargs.mda_devsw = &gpioc_cdevsw;
devargs.mda_uid = UID_ROOT;
devargs.mda_gid = GID_WHEEL;
devargs.mda_mode = 0600;
devargs.mda_si_drv1 = sc;
err = make_dev_s(&devargs, &sc->sc_ctl_dev, "gpioc%d", sc->sc_unit);
if (err != 0) {
device_printf(dev, "Failed to create gpioc%d", sc->sc_unit);
return (ENXIO);
}
return (0);
}
static int
gpioc_detach(device_t dev)
{
struct gpioc_softc *sc = device_get_softc(dev);
if (sc->sc_ctl_dev)
destroy_dev(sc->sc_ctl_dev);
for (int i = 0; i < sc->sc_npins; i++) {
mtx_destroy(&sc->sc_pin_intr[i].mtx);
MPASS(sc->sc_pin_intr[i].pin == NULL);
}
free(sc->sc_pin_intr, M_GPIOC);
return (0);
}
static void
gpioc_cdevpriv_dtor(void *data)
{
struct gpioc_cdevpriv *priv;
struct gpioc_privs *priv_link, *priv_link_temp;
struct gpioc_pins *pin_link, *pin_link_temp;
unsigned int consistency __diagused;
priv = data;
SLIST_FOREACH_SAFE(pin_link, &priv->pins, next, pin_link_temp) {
consistency = 0;
mtx_lock(&pin_link->pin->mtx);
while (pin_link->pin->config_locked == true)
mtx_sleep(&pin_link->pin->config_locked,
&pin_link->pin->mtx, 0, "gpicfg", 0);
SLIST_FOREACH_SAFE(priv_link, &pin_link->pin->privs, next,
priv_link_temp) {
if (priv_link->priv == priv) {
SLIST_REMOVE(&pin_link->pin->privs, priv_link,
gpioc_privs, next);
free(priv_link, M_GPIOC);
consistency++;
}
}
KASSERT(consistency == 1,
("inconsistent links between pin config and cdevpriv"));
if (gpioc_intr_reconfig_allowed(priv, pin_link->pin)) {
gpioc_release_pin_intr(priv->sc, pin_link->pin);
}
mtx_unlock(&pin_link->pin->mtx);
SLIST_REMOVE(&priv->pins, pin_link, gpioc_pins, next);
free(pin_link, M_GPIOC);
}
wakeup(&priv);
knlist_clear(&priv->selinfo.si_note, 0);
seldrain(&priv->selinfo);
knlist_destroy(&priv->selinfo.si_note);
funsetown(&priv->sigio);
mtx_destroy(&priv->mtx);
free(priv->events, M_GPIOC);
free(data, M_GPIOC);
}
static int
gpioc_open(struct cdev *dev, int oflags, int devtype, struct thread *td)
{
struct gpioc_cdevpriv *priv;
int err = 0;
priv = malloc(sizeof(*priv), M_GPIOC, M_WAITOK | M_ZERO);
priv->sc = dev->si_drv1;
mtx_init(&priv->mtx, "gpioc priv", NULL, MTX_DEF);
knlist_init_mtx(&priv->selinfo.si_note, &priv->mtx);
priv->async = false;
priv->report_option = GPIO_EVENT_REPORT_DETAIL;
priv->sigio = NULL;
priv->numevents = priv->sc->sc_npins * 2;
priv->events = malloc(priv->numevents * sizeof(struct gpioc_pin_event),
M_GPIOC, M_WAITOK | M_ZERO);
priv->evidx_head = priv->evidx_tail = 0;
SLIST_INIT(&priv->pins);
err = devfs_set_cdevpriv(priv, gpioc_cdevpriv_dtor);
if (err != 0)
gpioc_cdevpriv_dtor(priv);
return (err);
}
static int
gpioc_read(struct cdev *dev, struct uio *uio, int ioflag)
{
struct gpioc_cdevpriv *priv;
struct gpioc_pin_event *event;
union {
struct gpio_event_summary sum;
struct gpio_event_detail evt;
uint8_t data[1];
} recbuf;
size_t recsize;
int err;
if ((err = devfs_get_cdevpriv((void **)&priv)) != 0)
return (err);
if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY)
recsize = sizeof(struct gpio_event_summary);
else
recsize = sizeof(struct gpio_event_detail);
if (uio->uio_resid < recsize)
return (EINVAL);
mtx_lock(&priv->mtx);
while (priv->evidx_head == priv->evidx_tail) {
if (SLIST_EMPTY(&priv->pins)) {
err = ENXIO;
break;
} else if (ioflag & O_NONBLOCK) {
err = EWOULDBLOCK;
break;
} else {
err = mtx_sleep(priv, &priv->mtx, PCATCH, "gpintr", 0);
if (err != 0)
break;
}
}
while (err == 0 && uio->uio_resid >= recsize &&
priv->evidx_tail != priv->evidx_head) {
event = next_tail_event(priv);
if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY) {
recbuf.sum.gp_first_time = event->event_time;
recbuf.sum.gp_pin = event->privpin->pin->pin->pin;
recbuf.sum.gp_count = event->privpin->eventcount;
recbuf.sum.gp_first_state = event->event_pin_state;
event = next_tail_event(priv);
recbuf.sum.gp_last_time = event->event_time;
recbuf.sum.gp_last_state = event->event_pin_state;
event->privpin->eventcount = 0;
event->privpin->firstevent = 0;
} else {
recbuf.evt.gp_time = event->event_time;
recbuf.evt.gp_pin = event->privpin->pin->pin->pin;
recbuf.evt.gp_pinstate = event->event_pin_state;
}
mtx_unlock(&priv->mtx);
err = uiomove(recbuf.data, recsize, uio);
mtx_lock(&priv->mtx);
}
mtx_unlock(&priv->mtx);
return (err);
}
static int
gpioc_ioctl(struct cdev *cdev, u_long cmd, caddr_t arg, int fflag,
struct thread *td)
{
int max_pin, res;
struct gpioc_softc *sc = cdev->si_drv1;
struct gpioc_cdevpriv *priv;
struct gpio_pin pin;
struct gpio_req req;
struct gpio_access_32 *a32;
struct gpio_config_32 *c32;
struct gpio_event_config *evcfg;
struct gpioc_pin_event *tmp;
uint32_t caps, intrflags;
switch (cmd) {
case GPIOMAXPIN:
res = 0;
max_pin = sc->sc_npins - 1;
bcopy(&max_pin, arg, sizeof(max_pin));
break;
case GPIOGETCONFIG:
bcopy(arg, &pin, sizeof(pin));
dprintf("get config pin %d\n", pin.gp_pin);
res = GPIOBUS_PIN_GETFLAGS(sc->sc_pdev, sc->sc_dev, pin.gp_pin,
&pin.gp_flags);
if (res != 0)
break;
res = devfs_get_cdevpriv((void **)&priv);
if (res != 0)
break;
pin.gp_flags |= gpioc_get_intr_config(sc, priv,
pin.gp_pin);
res = GPIOBUS_PIN_GETCAPS(sc->sc_pdev, sc->sc_dev, pin.gp_pin,
&pin.gp_caps);
if (res != 0)
break;
res = GPIOBUS_PIN_GETNAME(sc->sc_pdev, pin.gp_pin, pin.gp_name);
if (res != 0)
break;
bcopy(&pin, arg, sizeof(pin));
break;
case GPIOSETCONFIG:
bcopy(arg, &pin, sizeof(pin));
dprintf("set config pin %d\n", pin.gp_pin);
res = devfs_get_cdevpriv((void **)&priv);
if (res != 0)
break;
res = GPIOBUS_PIN_GETCAPS(sc->sc_pdev, sc->sc_dev,
pin.gp_pin, &caps);
if (res != 0)
break;
res = gpio_check_flags(caps, pin.gp_flags);
if (res != 0)
break;
intrflags = pin.gp_flags & GPIO_INTR_MASK;
switch (intrflags) {
case GPIO_INTR_NONE:
break;
case GPIO_INTR_EDGE_RISING:
case GPIO_INTR_EDGE_FALLING:
case GPIO_INTR_EDGE_BOTH:
if ((intrflags & caps) == 0)
res = EOPNOTSUPP;
break;
default:
res = EINVAL;
break;
}
if (res != 0)
break;
res = GPIOBUS_PIN_SETFLAGS(sc->sc_pdev, sc->sc_dev, pin.gp_pin,
pin.gp_flags & ~GPIO_INTR_MASK);
if (res != 0)
break;
res = gpioc_set_intr_config(sc, priv, pin.gp_pin,
intrflags);
break;
case GPIOGET:
bcopy(arg, &req, sizeof(req));
res = GPIOBUS_PIN_GET(sc->sc_pdev, sc->sc_dev, req.gp_pin,
&req.gp_value);
if (res != 0)
break;
dprintf("read pin %d -> %d\n",
req.gp_pin, req.gp_value);
bcopy(&req, arg, sizeof(req));
break;
case GPIOSET:
bcopy(arg, &req, sizeof(req));
res = GPIOBUS_PIN_SET(sc->sc_pdev, sc->sc_dev, req.gp_pin,
req.gp_value);
dprintf("write pin %d -> %d\n",
req.gp_pin, req.gp_value);
break;
case GPIOTOGGLE:
bcopy(arg, &req, sizeof(req));
dprintf("toggle pin %d\n",
req.gp_pin);
res = GPIOBUS_PIN_TOGGLE(sc->sc_pdev, sc->sc_dev, req.gp_pin);
break;
case GPIOSETNAME:
bcopy(arg, &pin, sizeof(pin));
dprintf("set name on pin %d\n", pin.gp_pin);
res = GPIOBUS_PIN_SETNAME(sc->sc_pdev, pin.gp_pin,
pin.gp_name);
break;
case GPIOACCESS32:
a32 = (struct gpio_access_32 *)arg;
res = GPIOBUS_PIN_ACCESS_32(sc->sc_pdev, sc->sc_dev,
a32->first_pin, a32->clear_pins, a32->change_pins,
&a32->orig_pins);
break;
case GPIOCONFIG32:
c32 = (struct gpio_config_32 *)arg;
res = GPIOBUS_PIN_CONFIG_32(sc->sc_pdev, sc->sc_dev,
c32->first_pin, c32->num_pins, c32->pin_flags);
break;
case GPIOCONFIGEVENTS:
evcfg = (struct gpio_event_config *)arg;
res = devfs_get_cdevpriv((void **)&priv);
if (res != 0)
break;
if (evcfg->gp_report_type != GPIO_EVENT_REPORT_DETAIL &&
evcfg->gp_report_type != GPIO_EVENT_REPORT_SUMMARY) {
res = EINVAL;
break;
}
tmp = NULL;
if (evcfg->gp_report_type == GPIO_EVENT_REPORT_DETAIL &&
priv->numevents < evcfg->gp_fifo_size) {
tmp = malloc(evcfg->gp_fifo_size *
sizeof(struct gpioc_pin_event), M_GPIOC,
M_WAITOK | M_ZERO);
}
mtx_lock(&priv->mtx);
if (!SLIST_EMPTY(&priv->pins)) {
mtx_unlock(&priv->mtx);
free(tmp, M_GPIOC);
res = EINVAL;
break;
}
if (tmp != NULL) {
free(priv->events, M_GPIOC);
priv->events = tmp;
priv->numevents = evcfg->gp_fifo_size;
priv->evidx_head = priv->evidx_tail = 0;
}
priv->report_option = evcfg->gp_report_type;
mtx_unlock(&priv->mtx);
break;
case FIONBIO:
res = 0;
break;
case FIOASYNC:
res = devfs_get_cdevpriv((void **)&priv);
if (res == 0) {
if (*(int *)arg == FASYNC)
priv->async = true;
else
priv->async = false;
}
break;
case FIOGETOWN:
res = devfs_get_cdevpriv((void **)&priv);
if (res == 0)
*(int *)arg = fgetown(&priv->sigio);
break;
case FIOSETOWN:
res = devfs_get_cdevpriv((void **)&priv);
if (res == 0)
res = fsetown(*(int *)arg, &priv->sigio);
break;
default:
return (ENOTTY);
break;
}
return (res);
}
static int
gpioc_poll(struct cdev *dev, int events, struct thread *td)
{
struct gpioc_cdevpriv *priv;
int err;
int revents;
revents = 0;
err = devfs_get_cdevpriv((void **)&priv);
if (err != 0) {
revents = POLLERR;
return (revents);
}
if (SLIST_EMPTY(&priv->pins)) {
revents = POLLHUP;
return (revents);
}
if (events & (POLLIN | POLLRDNORM)) {
if (priv->evidx_head != priv->evidx_tail)
revents |= events & (POLLIN | POLLRDNORM);
else
selrecord(td, &priv->selinfo);
}
return (revents);
}
static int
gpioc_kqfilter(struct cdev *dev, struct knote *kn)
{
struct gpioc_cdevpriv *priv;
struct knlist *knlist;
int err;
err = devfs_get_cdevpriv((void **)&priv);
if (err != 0)
return err;
if (SLIST_EMPTY(&priv->pins))
return (ENXIO);
switch(kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &gpioc_read_filterops;
kn->kn_hook = (void *)priv;
break;
default:
return (EOPNOTSUPP);
}
knlist = &priv->selinfo.si_note;
knlist_add(knlist, kn, 0);
return (0);
}
static int
gpioc_kqread(struct knote *kn, long hint)
{
struct gpioc_cdevpriv *priv = kn->kn_hook;
size_t recsize;
if (SLIST_EMPTY(&priv->pins)) {
kn->kn_flags |= EV_EOF;
return (1);
} else {
if (priv->evidx_head != priv->evidx_tail) {
if (priv->report_option == GPIO_EVENT_REPORT_SUMMARY)
recsize = sizeof(struct gpio_event_summary);
else
recsize = sizeof(struct gpio_event_detail);
kn->kn_data = recsize * number_of_events(priv);
return (1);
}
}
return (0);
}
static void
gpioc_kqdetach(struct knote *kn)
{
struct gpioc_cdevpriv *priv = kn->kn_hook;
struct knlist *knlist = &priv->selinfo.si_note;
knlist_remove(knlist, kn, 0);
}
static device_method_t gpioc_methods[] = {
DEVMETHOD(device_probe, gpioc_probe),
DEVMETHOD(device_attach, gpioc_attach),
DEVMETHOD(device_detach, gpioc_detach),
DEVMETHOD_END
};
driver_t gpioc_driver = {
"gpioc",
gpioc_methods,
sizeof(struct gpioc_softc)
};
DRIVER_MODULE(gpioc, gpiobus, gpioc_driver, 0, 0);
MODULE_VERSION(gpioc, 1);