/*- * Copyright (c) 2016 Daniel Wyatt * 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 #include #include #include #include #include #include #include #include #include #include #include #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\n", b, pin_num, group, bit); else NCT_VERBOSE_PRINTF(sc->dev, "read %d from output pin %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, 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);