freebsd-dev/sys/dev/nctgpio/nctgpio.c

789 lines
18 KiB
C

/*-
* Copyright (c) 2016 Daniel Wyatt <Daniel.Wyatt@gmail.com>
* 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.
*
* $FreeBSD$
*
*/
/*
* Nuvoton GPIO driver.
*
*/
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/eventhandler.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/gpio.h>
#include <machine/bus.h>
#include <dev/gpio/gpiobusvar.h>
#include <dev/superio/superio.h>
#include "gpio_if.h"
/* Logical Device Numbers. */
#define NCT_LDN_GPIO 0x07
#define NCT_LDN_GPIO_MODE 0x0f
/* Logical Device 7 */
#define NCT_LD7_GPIO0_IOR 0xe0
#define NCT_LD7_GPIO0_DAT 0xe1
#define NCT_LD7_GPIO0_INV 0xe2
#define NCT_LD7_GPIO0_DST 0xe3
#define NCT_LD7_GPIO1_IOR 0xe4
#define NCT_LD7_GPIO1_DAT 0xe5
#define NCT_LD7_GPIO1_INV 0xe6
#define NCT_LD7_GPIO1_DST 0xe7
/* Logical Device F */
#define NCT_LDF_GPIO0_OUTCFG 0xe0
#define NCT_LDF_GPIO1_OUTCFG 0xe1
/* Direct I/O port access. */
#define NCT_IO_GSR 0
#define NCT_IO_IOR 1
#define NCT_IO_DAT 2
#define NCT_IO_INV 3
#define NCT_MAX_PIN 15
#define NCT_IS_VALID_PIN(_p) ((_p) >= 0 && (_p) <= NCT_MAX_PIN)
#define NCT_PIN_BIT(_p) (1 << ((_p) & 7))
#define NCT_GPIO_CAPS (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT | \
GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL | \
GPIO_PIN_INVIN | GPIO_PIN_INVOUT)
/*
* Note that the values are important.
* They match actual register offsets.
*/
typedef enum {
REG_IOR = 0,
REG_DAT = 1,
REG_INV = 2,
} reg_t;
struct nct_softc {
device_t dev;
device_t dev_f;
device_t busdev;
struct mtx mtx;
struct resource *iores;
int iorid;
int curgrp;
struct {
/* direction, 1: pin is input */
uint8_t ior[2];
/* output value */
uint8_t out[2];
/* whether out is valid */
uint8_t out_known[2];
/* inversion, 1: pin is inverted */
uint8_t inv[2];
} cache;
struct gpio_pin pins[NCT_MAX_PIN + 1];
};
#define GPIO_LOCK_INIT(_sc) mtx_init(&(_sc)->mtx, \
device_get_nameunit(dev), NULL, MTX_DEF)
#define GPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->mtx)
#define GPIO_LOCK(_sc) mtx_lock(&(_sc)->mtx)
#define GPIO_UNLOCK(_sc) mtx_unlock(&(_sc)->mtx)
#define GPIO_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->mtx, MA_OWNED)
#define GPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->mtx, MA_NOTOWNED)
struct nuvoton_vendor_device_id {
uint16_t chip_id;
const char * descr;
} nct_devs[] = {
{
.chip_id = 0x1061,
.descr = "Nuvoton NCT5104D",
},
{
.chip_id = 0xc452,
.descr = "Nuvoton NCT5104D (PC-Engines APU)",
},
{
.chip_id = 0xc453,
.descr = "Nuvoton NCT5104D (PC-Engines APU3)",
},
};
static void
nct_io_set_group(struct nct_softc *sc, int group)
{
GPIO_ASSERT_LOCKED(sc);
if (group != sc->curgrp) {
bus_write_1(sc->iores, NCT_IO_GSR, group);
sc->curgrp = group;
}
}
static uint8_t
nct_io_read(struct nct_softc *sc, int group, uint8_t reg)
{
nct_io_set_group(sc, group);
return (bus_read_1(sc->iores, reg));
}
static void
nct_io_write(struct nct_softc *sc, int group, uint8_t reg, uint8_t val)
{
nct_io_set_group(sc, group);
return (bus_write_1(sc->iores, reg, val));
}
static uint8_t
nct_get_ioreg(struct nct_softc *sc, reg_t reg, int group)
{
uint8_t ioreg;
if (sc->iores != NULL)
ioreg = NCT_IO_IOR + reg;
else if (group == 0)
ioreg = NCT_LD7_GPIO0_IOR + reg;
else
ioreg = NCT_LD7_GPIO1_IOR + reg;
return (ioreg);
}
static uint8_t
nct_read_reg(struct nct_softc *sc, reg_t reg, int group)
{
uint8_t ioreg;
uint8_t val;
ioreg = nct_get_ioreg(sc, reg, group);
if (sc->iores != NULL)
val = nct_io_read(sc, group, ioreg);
else
val = superio_read(sc->dev, ioreg);
return (val);
}
#define GET_BIT(v, b) (((v) >> (b)) & 1)
static bool
nct_get_pin_reg(struct nct_softc *sc, reg_t reg, uint32_t pin_num)
{
uint8_t bit;
uint8_t group;
uint8_t val;
KASSERT(NCT_IS_VALID_PIN(pin_num), ("%s: invalid pin number %d",
__func__, pin_num));
group = pin_num >> 3;
bit = pin_num & 7;
val = nct_read_reg(sc, reg, group);
return (GET_BIT(val, bit));
}
static int
nct_get_pin_cache(struct nct_softc *sc, uint32_t pin_num, uint8_t *cache)
{
uint8_t bit;
uint8_t group;
uint8_t val;
KASSERT(NCT_IS_VALID_PIN(pin_num), ("%s: invalid pin number %d",
__func__, pin_num));
group = pin_num >> 3;
bit = pin_num & 7;
val = cache[group];
return (GET_BIT(val, bit));
}
static void
nct_write_reg(struct nct_softc *sc, reg_t reg, int group, uint8_t val)
{
uint8_t ioreg;
ioreg = nct_get_ioreg(sc, reg, group);
if (sc->iores != NULL)
nct_io_write(sc, group, ioreg, val);
else
superio_write(sc->dev, ioreg, val);
}
static void
nct_set_pin_reg(struct nct_softc *sc, reg_t reg, uint32_t pin_num, bool val)
{
uint8_t *cache;
uint8_t bit;
uint8_t bitval;
uint8_t group;
uint8_t mask;
KASSERT(NCT_IS_VALID_PIN(pin_num),
("%s: invalid pin number %d", __func__, pin_num));
KASSERT(reg == REG_IOR || reg == REG_INV,
("%s: unsupported register %d", __func__, reg));
group = pin_num >> 3;
bit = pin_num & 7;
mask = (uint8_t)1 << bit;
bitval = (uint8_t)val << bit;
if (reg == REG_IOR)
cache = &sc->cache.ior[group];
else
cache = &sc->cache.inv[group];
if ((*cache & mask) == bitval)
return;
*cache &= ~mask;
*cache |= bitval;
nct_write_reg(sc, reg, group, *cache);
}
/*
* Set a pin to input (val is true) or output (val is false) mode.
*/
static void
nct_set_pin_input(struct nct_softc *sc, uint32_t pin_num, bool val)
{
nct_set_pin_reg(sc, REG_IOR, pin_num, val);
}
/*
* Check whether a pin is configured as an input.
*/
static bool
nct_pin_is_input(struct nct_softc *sc, uint32_t pin_num)
{
return (nct_get_pin_cache(sc, pin_num, sc->cache.ior));
}
/*
* Set a pin to inverted (val is true) or normal (val is false) mode.
*/
static void
nct_set_pin_inverted(struct nct_softc *sc, uint32_t pin_num, bool val)
{
nct_set_pin_reg(sc, REG_INV, pin_num, val);
}
static bool
nct_pin_is_inverted(struct nct_softc *sc, uint32_t pin_num)
{
return (nct_get_pin_cache(sc, pin_num, sc->cache.inv));
}
/*
* Write a value to an output pin.
* NB: the hardware remembers last output value across switching from
* output mode to input mode and back.
* Writes to a pin in input mode are not allowed here as they cannot
* have any effect and would corrupt the output value cache.
*/
static void
nct_write_pin(struct nct_softc *sc, uint32_t pin_num, bool val)
{
uint8_t bit;
uint8_t group;
KASSERT(!nct_pin_is_input(sc, pin_num), ("attempt to write input pin"));
group = pin_num >> 3;
bit = pin_num & 7;
if (GET_BIT(sc->cache.out_known[group], bit) &&
GET_BIT(sc->cache.out[group], bit) == val) {
/* The pin is already in requested state. */
return;
}
sc->cache.out_known[group] |= 1 << bit;
if (val)
sc->cache.out[group] |= 1 << bit;
else
sc->cache.out[group] &= ~(1 << bit);
nct_write_reg(sc, REG_DAT, group, sc->cache.out[group]);
}
/*
* NB: state of an input pin cannot be cached, of course.
* For an output we can either take the value from the cache if it's valid
* or read the state from the hadrware and cache it.
*/
static bool
nct_read_pin(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t bit;
uint8_t group;
bool val;
if (nct_pin_is_input(sc, pin_num))
return (nct_get_pin_reg(sc, REG_DAT, pin_num));
group = pin_num >> 3;
bit = pin_num & 7;
if (GET_BIT(sc->cache.out_known[group], bit))
return (GET_BIT(sc->cache.out[group], bit));
val = nct_get_pin_reg(sc, REG_DAT, pin_num);
sc->cache.out_known[group] |= 1 << bit;
if (val)
sc->cache.out[group] |= 1 << bit;
else
sc->cache.out[group] &= ~(1 << bit);
return (val);
}
static uint8_t
nct_outcfg_addr(uint32_t pin_num)
{
KASSERT(NCT_IS_VALID_PIN(pin_num), ("%s: invalid pin number %d",
__func__, pin_num));
if ((pin_num >> 3) == 0)
return (NCT_LDF_GPIO0_OUTCFG);
else
return (NCT_LDF_GPIO1_OUTCFG);
}
/*
* NB: PP/OD can be configured only via configuration registers.
* Also, the registers are in a different logical device.
* So, this is a special case. No caching too.
*/
static void
nct_set_pin_opendrain(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t reg;
uint8_t outcfg;
reg = nct_outcfg_addr(pin_num);
outcfg = superio_read(sc->dev_f, reg);
outcfg |= NCT_PIN_BIT(pin_num);
superio_write(sc->dev_f, reg, outcfg);
}
static void
nct_set_pin_pushpull(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t reg;
uint8_t outcfg;
reg = nct_outcfg_addr(pin_num);
outcfg = superio_read(sc->dev_f, reg);
outcfg &= ~NCT_PIN_BIT(pin_num);
superio_write(sc->dev_f, reg, outcfg);
}
static bool
nct_pin_is_opendrain(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t reg;
uint8_t outcfg;
reg = nct_outcfg_addr(pin_num);
outcfg = superio_read(sc->dev_f, reg);
return (outcfg & NCT_PIN_BIT(pin_num));
}
static int
nct_probe(device_t dev)
{
int j;
uint16_t chipid;
if (superio_vendor(dev) != SUPERIO_VENDOR_NUVOTON)
return (ENXIO);
if (superio_get_type(dev) != SUPERIO_DEV_GPIO)
return (ENXIO);
/*
* There are several GPIO devices, we attach only to one of them
* and use the rest without attaching.
*/
if (superio_get_ldn(dev) != NCT_LDN_GPIO)
return (ENXIO);
chipid = superio_devid(dev);
for (j = 0; j < nitems(nct_devs); j++) {
if (chipid == nct_devs[j].chip_id) {
device_set_desc(dev, "Nuvoton GPIO controller");
return (BUS_PROBE_DEFAULT);
}
}
return (ENXIO);
}
static int
nct_attach(device_t dev)
{
struct nct_softc *sc;
device_t dev_8;
uint16_t iobase;
int err;
int i;
sc = device_get_softc(dev);
sc->dev = dev;
sc->dev_f = superio_find_dev(device_get_parent(dev), SUPERIO_DEV_GPIO,
NCT_LDN_GPIO_MODE);
if (sc->dev_f == NULL) {
device_printf(dev, "failed to find LDN F\n");
return (ENXIO);
}
/*
* As strange as it may seem, I/O port base is configured in the
* Logical Device 8 which is primarily used for WDT, but also plays
* a role in GPIO configuration.
*/
iobase = 0;
dev_8 = superio_find_dev(device_get_parent(dev), SUPERIO_DEV_WDT, 8);
if (dev_8 != NULL)
iobase = superio_get_iobase(dev_8);
if (iobase != 0 && iobase != 0xffff) {
sc->curgrp = -1;
sc->iorid = 0;
err = bus_set_resource(dev, SYS_RES_IOPORT, sc->iorid,
iobase, 7);
if (err == 0) {
sc->iores = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
&sc->iorid, RF_ACTIVE);
if (sc->iores == NULL) {
device_printf(dev, "can't map i/o space, "
"iobase=0x%04x\n", iobase);
}
} else {
device_printf(dev,
"failed to set io port resource at 0x%x\n", iobase);
}
}
/* Enable gpio0 and gpio1. */
superio_dev_enable(dev, 0x03);
GPIO_LOCK_INIT(sc);
GPIO_LOCK(sc);
sc->cache.inv[0] = nct_read_reg(sc, REG_INV, 0);
sc->cache.inv[1] = nct_read_reg(sc, REG_INV, 1);
sc->cache.ior[0] = nct_read_reg(sc, REG_IOR, 0);
sc->cache.ior[1] = nct_read_reg(sc, REG_IOR, 1);
/*
* Caching input values is meaningless as an input can be changed at any
* time by an external agent. But outputs are controlled by this
* driver, so it can cache their state. Also, the hardware remembers
* the output state of a pin when the pin is switched to input mode and
* then back to output mode. So, the cache stays valid.
* The only problem is with pins that are in input mode at the attach
* time. For them the output state is not known until it is set by the
* driver for the first time.
* 'out' and 'out_known' bits form a tri-state output cache:
* |-----+-----------+---------|
* | out | out_known | cache |
* |-----+-----------+---------|
* | X | 0 | invalid |
* | 0 | 1 | 0 |
* | 1 | 1 | 1 |
* |-----+-----------+---------|
*/
sc->cache.out[0] = nct_read_reg(sc, REG_DAT, 0);
sc->cache.out[1] = nct_read_reg(sc, REG_DAT, 1);
sc->cache.out_known[0] = ~sc->cache.ior[0];
sc->cache.out_known[1] = ~sc->cache.ior[1];
for (i = 0; i <= NCT_MAX_PIN; i++) {
struct gpio_pin *pin;
pin = &sc->pins[i];
pin->gp_pin = i;
pin->gp_caps = NCT_GPIO_CAPS;
pin->gp_flags = 0;
snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%02o", i);
pin->gp_name[GPIOMAXNAME - 1] = '\0';
if (nct_pin_is_input(sc, i))
pin->gp_flags |= GPIO_PIN_INPUT;
else
pin->gp_flags |= GPIO_PIN_OUTPUT;
if (nct_pin_is_opendrain(sc, i))
pin->gp_flags |= GPIO_PIN_OPENDRAIN;
else
pin->gp_flags |= GPIO_PIN_PUSHPULL;
if (nct_pin_is_inverted(sc, i))
pin->gp_flags |= (GPIO_PIN_INVIN | GPIO_PIN_INVOUT);
}
GPIO_UNLOCK(sc);
sc->busdev = gpiobus_attach_bus(dev);
if (sc->busdev == NULL) {
GPIO_LOCK_DESTROY(sc);
return (ENXIO);
}
return (0);
}
static int
nct_detach(device_t dev)
{
struct nct_softc *sc;
sc = device_get_softc(dev);
gpiobus_detach_bus(dev);
if (sc->iores != NULL)
bus_release_resource(dev, SYS_RES_IOPORT, sc->iorid, sc->iores);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK_DESTROY(sc);
return (0);
}
static device_t
nct_gpio_get_bus(device_t dev)
{
struct nct_softc *sc;
sc = device_get_softc(dev);
return (sc->busdev);
}
static int
nct_gpio_pin_max(device_t dev, int *npins)
{
*npins = NCT_MAX_PIN;
return (0);
}
static int
nct_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value)
{
struct nct_softc *sc;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_LOCK(sc);
if ((sc->pins[pin_num].gp_flags & GPIO_PIN_OUTPUT) == 0) {
GPIO_UNLOCK(sc);
return (EINVAL);
}
nct_write_pin(sc, pin_num, pin_value);
GPIO_UNLOCK(sc);
return (0);
}
static int
nct_gpio_pin_get(device_t dev, uint32_t pin_num, uint32_t *pin_value)
{
struct nct_softc *sc;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK(sc);
*pin_value = nct_read_pin(sc, pin_num);
GPIO_UNLOCK(sc);
return (0);
}
static int
nct_gpio_pin_toggle(device_t dev, uint32_t pin_num)
{
struct nct_softc *sc;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK(sc);
if ((sc->pins[pin_num].gp_flags & GPIO_PIN_OUTPUT) == 0) {
GPIO_UNLOCK(sc);
return (EINVAL);
}
if (nct_read_pin(sc, pin_num))
nct_write_pin(sc, pin_num, 0);
else
nct_write_pin(sc, pin_num, 1);
GPIO_UNLOCK(sc);
return (0);
}
static int
nct_gpio_pin_getcaps(device_t dev, uint32_t pin_num, uint32_t *caps)
{
struct nct_softc *sc;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK(sc);
*caps = sc->pins[pin_num].gp_caps;
GPIO_UNLOCK(sc);
return (0);
}
static int
nct_gpio_pin_getflags(device_t dev, uint32_t pin_num, uint32_t *flags)
{
struct nct_softc *sc;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK(sc);
*flags = sc->pins[pin_num].gp_flags;
GPIO_UNLOCK(sc);
return (0);
}
static int
nct_gpio_pin_getname(device_t dev, uint32_t pin_num, char *name)
{
struct nct_softc *sc;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK(sc);
memcpy(name, sc->pins[pin_num].gp_name, GPIOMAXNAME);
GPIO_UNLOCK(sc);
return (0);
}
static int
nct_gpio_pin_setflags(device_t dev, uint32_t pin_num, uint32_t flags)
{
struct nct_softc *sc;
struct gpio_pin *pin;
if (!NCT_IS_VALID_PIN(pin_num))
return (EINVAL);
sc = device_get_softc(dev);
pin = &sc->pins[pin_num];
if ((flags & pin->gp_caps) != flags)
return (EINVAL);
if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) ==
(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) {
return (EINVAL);
}
if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) ==
(GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) {
return (EINVAL);
}
if ((flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) ==
(GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) {
return (EINVAL);
}
GPIO_ASSERT_UNLOCKED(sc);
GPIO_LOCK(sc);
if ((flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT)) != 0) {
nct_set_pin_input(sc, pin_num, (flags & GPIO_PIN_INPUT) != 0);
pin->gp_flags &= ~(GPIO_PIN_INPUT | GPIO_PIN_OUTPUT);
pin->gp_flags |= flags & (GPIO_PIN_INPUT | GPIO_PIN_OUTPUT);
}
if ((flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) != 0) {
nct_set_pin_inverted(sc, pin_num,
(flags & GPIO_PIN_INVIN) != 0);
pin->gp_flags &= ~(GPIO_PIN_INVIN | GPIO_PIN_INVOUT);
pin->gp_flags |= flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT);
}
if ((flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL)) != 0) {
if (flags & GPIO_PIN_OPENDRAIN)
nct_set_pin_opendrain(sc, pin_num);
else
nct_set_pin_pushpull(sc, pin_num);
pin->gp_flags &= ~(GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL);
pin->gp_flags |=
flags & (GPIO_PIN_OPENDRAIN | GPIO_PIN_PUSHPULL);
}
GPIO_UNLOCK(sc);
return (0);
}
static device_method_t nct_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, nct_probe),
DEVMETHOD(device_attach, nct_attach),
DEVMETHOD(device_detach, nct_detach),
/* GPIO */
DEVMETHOD(gpio_get_bus, nct_gpio_get_bus),
DEVMETHOD(gpio_pin_max, nct_gpio_pin_max),
DEVMETHOD(gpio_pin_get, nct_gpio_pin_get),
DEVMETHOD(gpio_pin_set, nct_gpio_pin_set),
DEVMETHOD(gpio_pin_toggle, nct_gpio_pin_toggle),
DEVMETHOD(gpio_pin_getname, nct_gpio_pin_getname),
DEVMETHOD(gpio_pin_getcaps, nct_gpio_pin_getcaps),
DEVMETHOD(gpio_pin_getflags, nct_gpio_pin_getflags),
DEVMETHOD(gpio_pin_setflags, nct_gpio_pin_setflags),
DEVMETHOD_END
};
static driver_t nct_driver = {
"gpio",
nct_methods,
sizeof(struct nct_softc)
};
DRIVER_MODULE(nctgpio, superio, nct_driver, NULL, NULL);
MODULE_DEPEND(nctgpio, gpiobus, 1, 1, 1);
MODULE_DEPEND(nctgpio, superio, 1, 1, 1);
MODULE_VERSION(nctgpio, 1);