freebsd-src/sys/dev/nctgpio/nctgpio.c
Warner Losh 95ee2897e9 sys: Remove $FreeBSD$: two-line .h pattern
Remove /^\s*\*\n \*\s+\$FreeBSD\$$\n/
2023-08-16 11:54:11 -06:00

1518 lines
37 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.
*
*/
/*
* 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"
#define NCT_PPOD_LDN 0xf /* LDN used to select Push-Pull/Open-Drain */
/* Direct access through GPIO register table */
#define NCT_IO_GSR 0 /* Group Select */
#define NCT_IO_IOR 1 /* I/O */
#define NCT_IO_DAT 2 /* Data */
#define NCT_IO_INV 3 /* Inversion */
#define NCT_IO_DST 4 /* Status */
#define NCT_MAX_GROUP 9
#define NCT_MAX_PIN 75
#define NCT_PIN_IS_VALID(_sc, _p) ((_p) < (_sc)->npins)
#define NCT_PIN_GROUP(_sc, _p) ((_sc)->pinmap[(_p)].group)
#define NCT_PIN_GRPNUM(_sc, _p) ((_sc)->pinmap[(_p)].grpnum)
#define NCT_PIN_BIT(_sc, _p) ((_sc)->pinmap[(_p)].bit)
#define NCT_PIN_BITMASK(_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)
#define NCT_PREFER_INDIRECT_CHANNEL 2
#define NCT_VERBOSE_PRINTF(dev, ...) \
do { \
if (__predict_false(bootverbose)) \
device_printf(dev, __VA_ARGS__); \
} while (0)
/*
* 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_gpio_group {
uint32_t caps;
uint8_t enable_ldn;
uint8_t enable_reg;
uint8_t enable_mask;
uint8_t data_ldn;
uint8_t iobase;
uint8_t ppod_reg; /* Push-Pull/Open-Drain */
uint8_t grpnum;
uint8_t pinbits[8];
uint8_t npins;
};
struct nct_softc {
device_t dev;
device_t busdev;
struct mtx mtx;
struct resource *iores;
int iorid;
int curgrp;
struct {
uint8_t ior[NCT_MAX_GROUP + 1]; /* direction, 1: input 0: output */
uint8_t out[NCT_MAX_GROUP + 1]; /* output value */
uint8_t out_known[NCT_MAX_GROUP + 1]; /* whether out is valid */
uint8_t inv[NCT_MAX_GROUP + 1]; /* inversion, 1: inverted */
} cache;
struct gpio_pin pins[NCT_MAX_PIN + 1];
struct nct_device *nctdevp;
int npins; /* Total number of pins */
/* Lookup tables */
struct {
struct nct_gpio_group *group;
uint8_t grpnum;
uint8_t bit;
} pinmap[NCT_MAX_PIN+1];
struct nct_gpio_group *grpmap[NCT_MAX_GROUP+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)
#define GET_BIT(v, b) (((v) >> (b)) & 1)
/*
* For most devices there are several GPIO devices, we attach only to one of
* them and use the rest without attaching.
*/
struct nct_device {
uint16_t devid;
int extid;
const char *descr;
int ngroups;
struct nct_gpio_group groups[NCT_MAX_GROUP + 1];
} nct_devices[] = {
{
.devid = 0xa025,
.descr = "GPIO on Winbond 83627DHG IC ver. 5",
.ngroups = 5,
.groups = {
{
.grpnum = 2,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x09,
.ppod_reg = 0xe0, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe3,
},
{
.grpnum = 3,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0,
},
{
.grpnum = 4,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
{
.grpnum = 5,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 6,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
},
},
{
.devid = 0x1061,
.descr = "GPIO on Nuvoton NCT5104D",
.ngroups = 2,
.groups = {
{
.grpnum = 0,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe0,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 1,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
},
},
{
.devid = 0xc452, /* FIXME Conflict with Nuvoton NCT6106D. See NetBSD's nct_match. */
.descr = "GPIO on Nuvoton NCT5104D (PC-Engines APU)",
.ngroups = 2,
.groups = {
{
.grpnum = 0,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe0,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 1,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
},
},
{
.devid = 0xc453,
.descr = "GPIO on Nuvoton NCT5104D (PC-Engines APU3)",
.ngroups = 2,
.groups = {
{
.grpnum = 0,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe0,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 1,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
},
},
{
.devid = 0xd42a,
.extid = 1,
.descr = "GPIO on Nuvoton NCT6796D-E",
.ngroups = 10,
.groups = {
{
.grpnum = 0,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x08,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x08,
.ppod_reg = 0xe0, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 1,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x08,
.enable_reg = 0x30,
.enable_mask = 0x80,
.data_ldn = 0x08,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0,
},
{
.grpnum = 2,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 3,
.pinbits = { 0, 1, 2, 3, 4, 5, 6 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 7,
.iobase = 0xe4,
},
{
.grpnum = 4,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0, /* FIXME Page 344 say "F0~F2, E8",
not "F0~F3". */
},
{
.grpnum = 5,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
{
.grpnum = 6,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
{
.grpnum = 7,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 8,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
{
.grpnum = 9,
.pinbits = { 0, 1, 2, 3 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 4,
.iobase = 0xe8,
},
},
},
{
.devid = 0xd42a,
.extid = 2,
.descr = "GPIO on Nuvoton NCT5585D",
.ngroups = 6,
.groups = {
{
.grpnum = 2,
.pinbits = { 0, 1, 2, 3, 4, 5, 6 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x09,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 7,
.iobase = 0xe0,
},
{
.grpnum = 3,
.pinbits = { 1, 2, 3 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x09,
.ppod_reg = 0xe2,
.caps = NCT_GPIO_CAPS,
.npins = 3,
.iobase = 0xe4,
},
{
.grpnum = 5,
.pinbits = { 0, 2, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x09,
.ppod_reg = 0xe4,
.caps = NCT_GPIO_CAPS,
.npins = 4,
.iobase = 0xf4,
},
{
.grpnum = 7,
.pinbits = { 4 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe6,
.caps = NCT_GPIO_CAPS,
.npins = 1,
.iobase = 0xe0,
},
{
.grpnum = 8,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x07,
.ppod_reg = 0xe7,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
{
.grpnum = 9,
.pinbits = { 0, 2 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x07,
.ppod_reg = 0xea,
.caps = NCT_GPIO_CAPS,
.npins = 2,
.iobase = 0xe8,
},
},
},
{
.devid = 0xc562,
.descr = "GPIO on Nuvoton NCT6779D",
.ngroups = 9,
.groups = {
{
.grpnum = 0,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x08,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x08,
.ppod_reg = 0xe0, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 1,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x08,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0,
},
{
.grpnum = 2,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 3,
.pinbits = { 0, 1, 2, 3, 4, 5, 6 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 7,
.iobase = 0xe4,
},
{
.grpnum = 4,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0,
},
{
.grpnum = 5,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x09,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
{
.grpnum = 6,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
{
.grpnum = 7,
.pinbits = { 0, 1, 2, 3, 4, 5, 6 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 7,
.iobase = 0xe0,
},
{
.grpnum = 8,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x07,
.ppod_reg = 0xe1, /* FIXME Need to check for this group. */
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
},
},
{
.devid = 0xd282,
.descr = "GPIO on Nuvoton NCT6112D/NCT6114D/NCT6116D",
.ngroups = 9,
.groups = {
{
.grpnum = 0,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x07,
.ppod_reg = 0xe0,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe0,
},
{
.grpnum = 1,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x02,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe4,
},
{
.grpnum = 2,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x04,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xe8,
},
{
.grpnum = 3,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x08,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xec,
},
{
.grpnum = 4,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x10,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0,
},
{
.grpnum = 5,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x20,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf4,
},
{
.grpnum = 6,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x40,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf8,
},
{
.grpnum = 7,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x07,
.enable_reg = 0x30,
.enable_mask = 0x80,
.data_ldn = 0x07,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xfc,
},
{
.grpnum = 8,
.pinbits = { 0, 1, 2, 3, 4, 5, 6, 7 },
.enable_ldn = 0x09,
.enable_reg = 0x30,
.enable_mask = 0x01,
.data_ldn = 0x09,
.ppod_reg = 0xe1,
.caps = NCT_GPIO_CAPS,
.npins = 8,
.iobase = 0xf0,
},
},
},
};
static const char *
io2str(uint8_t ioport)
{
switch (ioport) {
case NCT_IO_GSR: return ("grpsel");
case NCT_IO_IOR: return ("io");
case NCT_IO_DAT: return ("data");
case NCT_IO_INV: return ("inv");
case NCT_IO_DST: return ("status");
default: return ("?");
}
}
static void
nct_io_set_group(struct nct_softc *sc, uint8_t grpnum)
{
GPIO_ASSERT_LOCKED(sc);
if (grpnum == sc->curgrp)
return;
NCT_VERBOSE_PRINTF(sc->dev, "write %s 0x%x ioport %d\n",
io2str(NCT_IO_GSR), grpnum, NCT_IO_GSR);
bus_write_1(sc->iores, NCT_IO_GSR, grpnum);
sc->curgrp = grpnum;
}
static uint8_t
nct_io_read(struct nct_softc *sc, uint8_t grpnum, uint8_t reg)
{
uint8_t val;
nct_io_set_group(sc, grpnum);
val = bus_read_1(sc->iores, reg);
NCT_VERBOSE_PRINTF(sc->dev, "read %s 0x%x ioport %d\n",
io2str(reg), val, reg);
return (val);
}
static void
nct_io_write(struct nct_softc *sc, uint8_t grpnum, uint8_t reg, uint8_t val)
{
nct_io_set_group(sc, grpnum);
NCT_VERBOSE_PRINTF(sc->dev, "write %s 0x%x ioport %d\n",
io2str(reg), val, reg);
bus_write_1(sc->iores, reg, val);
}
static uint8_t
nct_get_ioreg(struct nct_softc *sc, reg_t reg, uint8_t grpnum)
{
uint8_t iobase;
if (sc->iores != NULL)
iobase = NCT_IO_IOR;
else
iobase = sc->grpmap[grpnum]->iobase;
return (iobase + reg);
}
static const char *
reg2str(reg_t reg)
{
switch (reg) {
case REG_IOR: return ("io");
case REG_DAT: return ("data");
case REG_INV: return ("inv");
default: return ("?");
}
}
static uint8_t
nct_read_reg(struct nct_softc *sc, reg_t reg, uint8_t grpnum)
{
struct nct_gpio_group *gp;
uint8_t ioreg;
uint8_t val;
ioreg = nct_get_ioreg(sc, reg, grpnum);
if (sc->iores != NULL)
return (nct_io_read(sc, grpnum, ioreg));
gp = sc->grpmap[grpnum];
val = superio_ldn_read(sc->dev, gp->data_ldn, ioreg);
NCT_VERBOSE_PRINTF(sc->dev, "read %s 0x%x from group GPIO%u ioreg 0x%x\n",
reg2str(reg), val, grpnum, ioreg);
return (val);
}
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_PIN_IS_VALID(sc, pin_num), ("%s: invalid pin number %d",
__func__, pin_num));
group = NCT_PIN_GRPNUM(sc, pin_num);
bit = NCT_PIN_BIT(sc, pin_num);
val = cache[group];
return (GET_BIT(val, bit));
}
static void
nct_write_reg(struct nct_softc *sc, reg_t reg, uint8_t grpnum, uint8_t val)
{
struct nct_gpio_group *gp;
uint8_t ioreg;
ioreg = nct_get_ioreg(sc, reg, grpnum);
if (sc->iores != NULL) {
nct_io_write(sc, grpnum, ioreg, val);
return;
}
gp = sc->grpmap[grpnum];
superio_ldn_write(sc->dev, gp->data_ldn, ioreg, val);
NCT_VERBOSE_PRINTF(sc->dev, "write %s 0x%x to group GPIO%u ioreg 0x%x\n",
reg2str(reg), val, grpnum, ioreg);
}
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_PIN_IS_VALID(sc, pin_num),
("%s: invalid pin number %d", __func__, pin_num));
KASSERT(reg == REG_IOR || reg == REG_INV,
("%s: unsupported register %d", __func__, reg));
group = NCT_PIN_GRPNUM(sc, pin_num);
bit = NCT_PIN_BIT(sc, pin_num);
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 = NCT_PIN_GRPNUM(sc, pin_num);
bit = NCT_PIN_BIT(sc, pin_num);
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]);
}
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;
bool b;
KASSERT(NCT_PIN_IS_VALID(sc, pin_num), ("%s: invalid pin number %d",
__func__, pin_num));
group = NCT_PIN_GRPNUM(sc, pin_num);
bit = NCT_PIN_BIT(sc, pin_num);
val = nct_read_reg(sc, reg, group);
b = GET_BIT(val, bit);
if (__predict_false(bootverbose)) {
if (nct_pin_is_input(sc, pin_num))
NCT_VERBOSE_PRINTF(sc->dev, "read %d from input pin %u<GPIO%u%u>\n",
b, pin_num, group, bit);
else
NCT_VERBOSE_PRINTF(sc->dev,
"read %d from output pin %u<GPIO%u%u>, cache miss\n",
b, pin_num, group, bit);
}
return (b);
}
/*
* 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 = NCT_PIN_GRPNUM(sc, pin_num);
bit = NCT_PIN_BIT(sc, pin_num);
if (GET_BIT(sc->cache.out_known[group], bit)) {
val = GET_BIT(sc->cache.out[group], bit);
NCT_VERBOSE_PRINTF(sc->dev,
"read %d from output pin %u<GPIO%u%u>, cache hit\n",
val, pin_num, group, bit);
return (val);
}
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);
}
/* FIXME Incorret for NCT5585D and probably other chips. */
static uint8_t
nct_ppod_reg(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t group = NCT_PIN_GRPNUM(sc, pin_num);
return (sc->grpmap[group]->ppod_reg);
}
/*
* 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_ppod_reg(sc, pin_num);
outcfg = superio_ldn_read(sc->dev, NCT_PPOD_LDN, reg);
outcfg |= NCT_PIN_BITMASK(pin_num);
superio_ldn_write(sc->dev, 0xf, reg, outcfg);
}
static void
nct_set_pin_pushpull(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t reg;
uint8_t outcfg;
reg = nct_ppod_reg(sc, pin_num);
outcfg = superio_ldn_read(sc->dev, NCT_PPOD_LDN, reg);
outcfg &= ~NCT_PIN_BITMASK(pin_num);
superio_ldn_write(sc->dev, 0xf, reg, outcfg);
}
static bool
nct_pin_is_opendrain(struct nct_softc *sc, uint32_t pin_num)
{
uint8_t reg;
uint8_t outcfg;
reg = nct_ppod_reg(sc, pin_num);
outcfg = superio_ldn_read(sc->dev, NCT_PPOD_LDN, reg);
return (outcfg & NCT_PIN_BITMASK(pin_num));
}
static struct nct_device *
nct_lookup_device(device_t dev)
{
struct nct_device *nctdevp;
uint16_t devid;
int i, extid;
devid = superio_devid(dev);
extid = superio_extid(dev);
for (i = 0, nctdevp = nct_devices; i < nitems(nct_devices); i++, nctdevp++) {
if (devid == nctdevp->devid && nctdevp->extid == extid)
return (nctdevp);
}
return (NULL);
}
static int
nct_probe(device_t dev)
{
struct nct_device *nctdevp;
uint8_t ldn;
ldn = superio_get_ldn(dev);
if (superio_vendor(dev) != SUPERIO_VENDOR_NUVOTON) {
NCT_VERBOSE_PRINTF(dev, "ldn 0x%x not a Nuvoton device\n", ldn);
return (ENXIO);
}
if (superio_get_type(dev) != SUPERIO_DEV_GPIO) {
NCT_VERBOSE_PRINTF(dev, "ldn 0x%x not a GPIO device\n", ldn);
return (ENXIO);
}
nctdevp = nct_lookup_device(dev);
if (nctdevp == NULL) {
NCT_VERBOSE_PRINTF(dev, "ldn 0x%x not supported\n", ldn);
return (ENXIO);
}
device_set_desc(dev, nctdevp->descr);
return (BUS_PROBE_DEFAULT);
}
static int
nct_attach(device_t dev)
{
struct nct_softc *sc;
struct nct_gpio_group *gp;
uint32_t pin_num;
uint8_t v;
int flags, i, g;
sc = device_get_softc(dev);
sc->dev = dev;
sc->nctdevp = nct_lookup_device(dev);
flags = 0;
(void)resource_int_value(device_get_name(dev), device_get_unit(dev), "flags", &flags);
if ((flags & NCT_PREFER_INDIRECT_CHANNEL) == 0) {
uint16_t iobase;
device_t dev_8;
/*
* 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) {
int err;
NCT_VERBOSE_PRINTF(dev, "iobase %#x\n", iobase);
sc->curgrp = -1;
sc->iorid = 0;
err = bus_set_resource(dev, SYS_RES_IOPORT, sc->iorid,
iobase, 7); /* FIXME NCT6796D-E have 8 registers according to table 18.3. */
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=%#x\n", iobase);
}
} else {
device_printf(dev,
"failed to set io port resource at %#x\n", iobase);
}
}
}
NCT_VERBOSE_PRINTF(dev, "iores %p %s channel\n",
sc->iores, (sc->iores ? "direct" : "indirect"));
/* Enable GPIO groups */
for (g = 0, gp = sc->nctdevp->groups; g < sc->nctdevp->ngroups; g++, gp++) {
NCT_VERBOSE_PRINTF(dev,
"GPIO%d: %d pins, enable with mask 0x%x via ldn 0x%x reg 0x%x\n",
gp->grpnum, gp->npins, gp->enable_mask, gp->enable_ldn,
gp->enable_reg);
v = superio_ldn_read(dev, gp->enable_ldn, gp->enable_reg);
v |= gp->enable_mask;
superio_ldn_write(dev, gp->enable_ldn, gp->enable_reg, v);
}
GPIO_LOCK_INIT(sc);
GPIO_LOCK(sc);
pin_num = 0;
sc->npins = 0;
for (g = 0, gp = sc->nctdevp->groups; g < sc->nctdevp->ngroups; g++, gp++) {
sc->grpmap[gp->grpnum] = gp;
/*
* 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.inv[gp->grpnum] = nct_read_reg(sc, REG_INV, gp->grpnum);
sc->cache.ior[gp->grpnum] = nct_read_reg(sc, REG_IOR, gp->grpnum);
sc->cache.out[gp->grpnum] = nct_read_reg(sc, REG_DAT, gp->grpnum);
sc->cache.out_known[gp->grpnum] = ~sc->cache.ior[gp->grpnum];
sc->npins += gp->npins;
for (i = 0; i < gp->npins; i++, pin_num++) {
struct gpio_pin *pin;
sc->pinmap[pin_num].group = gp;
sc->pinmap[pin_num].grpnum = gp->grpnum;
sc->pinmap[pin_num].bit = gp->pinbits[i];
pin = &sc->pins[pin_num];
pin->gp_pin = pin_num;
pin->gp_caps = gp->caps;
pin->gp_flags = 0;
snprintf(pin->gp_name, GPIOMAXNAME, "GPIO%u%u",
gp->grpnum, gp->pinbits[i]);
if (nct_pin_is_input(sc, pin_num))
pin->gp_flags |= GPIO_PIN_INPUT;
else
pin->gp_flags |= GPIO_PIN_OUTPUT;
if (nct_pin_is_opendrain(sc, pin_num))
pin->gp_flags |= GPIO_PIN_OPENDRAIN;
else
pin->gp_flags |= GPIO_PIN_PUSHPULL;
if (nct_pin_is_inverted(sc, pin_num))
pin->gp_flags |= (GPIO_PIN_INVIN | GPIO_PIN_INVOUT);
}
}
NCT_VERBOSE_PRINTF(dev, "%d pins available\n", sc->npins);
GPIO_UNLOCK(sc);
sc->busdev = gpiobus_attach_bus(dev);
if (sc->busdev == NULL) {
device_printf(dev, "failed to attach to gpiobus\n");
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 *maxpin)
{
struct nct_softc *sc;
sc = device_get_softc(dev);
*maxpin = sc->npins - 1;
return (0);
}
static int
nct_gpio_pin_set(device_t dev, uint32_t pin_num, uint32_t pin_value)
{
struct nct_softc *sc;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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;
sc = device_get_softc(dev);
if (!NCT_PIN_IS_VALID(sc, pin_num))
return (EINVAL);
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);
/* input or output */
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);
}
/* invert */
if ((flags & (GPIO_PIN_INVIN | GPIO_PIN_INVOUT)) != 0) {
nct_set_pin_inverted(sc, pin_num, 1);
pin->gp_flags |= (GPIO_PIN_INVIN | GPIO_PIN_INVOUT);
} else {
nct_set_pin_inverted(sc, pin_num, 0);
pin->gp_flags &= ~(GPIO_PIN_INVIN | GPIO_PIN_INVOUT);
}
/* Open drain or push pull */
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);