155514eacf
This change consists of two parts. First, nctgpio now supports hardware access via an I/O port window if it's configured by firmware. For instance, PC Engines firmware v4.10.0.2 does that. This is faster than going through the Super I/O configuration registers. Second, nctgpio now caches values of bits that it controls. For example, the driver does not need to access the hardware to determine if a pin is an output or an input, or a state of an output. Also, the driver makes use of the fact that the hardware preserves an output state of a pin accross a switch to the input mode and back. With this change I am able to use the 1-Wire bus over nctgpio whereas previously the driver introduced too much latency to be compliant with the relatively strict protocol timings. superio0: <Nuvoton NCT5104D/NCT6102D/NCT6106D (rev. B+)> at port 0x2e-0x2f on isa0 gpio1: <Nuvoton GPIO controller> at GPIO ldn 0x07 on superio0 pcib0: allocated type 4 (0x220-0x226) for rid 0 of gpio1 gpiobus1: <GPIO bus> on gpio1 owc0: <GPIO attached one-wire bus> at pin 4 on gpiobus1 ow0: <1 Wire Bus> on owc0 ow0: romid 28:b2:9e:45:92:10:02:34: no driver ow_temp0: <Advanced One Wire Temperature> romid 28:b2:9e:45:92:10:02:34 on ow0 MFC after: 4 weeks
791 lines
18 KiB
C
791 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)
|
|
};
|
|
|
|
static devclass_t nct_devclass;
|
|
|
|
DRIVER_MODULE(nctgpio, superio, nct_driver, nct_devclass, NULL, NULL);
|
|
MODULE_DEPEND(nctgpio, gpiobus, 1, 1, 1);
|
|
MODULE_DEPEND(nctgpio, superio, 1, 1, 1);
|
|
MODULE_VERSION(nctgpio, 1);
|
|
|