Index: sys/conf/files =================================================================== --- sys/conf/files +++ sys/conf/files @@ -1806,6 +1806,7 @@ dev/iicbus/rtc8583.c optional rtc8583 dev/iicbus/s35390a.c optional s35390a dev/iicbus/sy8106a.c optional sy8106a ext_resources fdt +dev/iicbus/tca6416.c optional tca6416 fdt dev/iir/iir.c optional iir dev/iir/iir_ctrl.c optional iir dev/iir/iir_pci.c optional iir pci Index: sys/dev/iicbus/tca6416.c =================================================================== --- /dev/null +++ sys/dev/iicbus/tca6416.c @@ -0,0 +1,595 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Alstom Group. + * Copyright (c) 2020 Semihalf. + * + * 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. + */ + +/* + * Driver for TI TCA6416 I2C GPIO expander module. + * + * This driver only supports basic functionality + * (interrupt handling and polarity inversion were omitted). + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include + +#include "gpio_if.h" + +/* Base addresses of registers. LSB omitted. */ +#define IN_PORT_REG 0x00 +#define OUT_PORT_REG 0x02 +#define POLARITY_INV_REG 0x04 +#define CONF_REG 0x06 + +#define NUM_PINS 16 +#define PINS_PER_REG 8 +#define PIN_CAPS \ + (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT \ + | GPIO_PIN_PUSHPULL) + +#ifdef DEBUG +#define dbg_dev_printf(dev, fmt, args...) \ + device_printf(dev, fmt, ##args) +#else +#define dbg_dev_printf(dev, fmt, args...) +#endif + +#define TCA6416_BIT_FROM_PIN(pin) (pin % PINS_PER_REG) +#define TCA6416_REG_ADDR(pin, baseaddr) (baseaddr | (pin / PINS_PER_REG)) + +struct tca6416_softc { + device_t dev; + device_t busdev; + struct mtx mtx; + uint32_t addr; + + struct intr_config_hook intr_hook; +}; + +static int tca6416_read(device_t, uint8_t, uint8_t*); +static int tca6416_write(device_t, uint8_t, uint8_t); +static int tca6416_probe(device_t); +static int tca6416_attach(device_t); +static int tca6416_detach(device_t); +static device_t tca6416_get_bus(device_t); +static int tca6416_pin_max(device_t, int*); +static int tca6416_pin_getcaps(device_t, uint32_t, uint32_t*); +static int tca6416_pin_getflags(device_t, uint32_t, uint32_t*); +static int tca6416_pin_setflags(device_t, uint32_t, uint32_t); +static int tca6416_pin_getname(device_t, uint32_t, char*); +static int tca6416_pin_get(device_t, uint32_t, unsigned int*); +static int tca6416_pin_set(device_t, uint32_t, unsigned int); +static int tca6416_pin_toggle(device_t, uint32_t); +#ifdef DEBUG +static void tca6416_regdump_setup(device_t dev); +static int tca6416_regdump_sysctl(SYSCTL_HANDLER_ARGS); +#endif + +static device_method_t tca6416_methods[] = { + DEVMETHOD(device_probe, tca6416_probe), + DEVMETHOD(device_attach, tca6416_attach), + DEVMETHOD(device_detach, tca6416_detach), + + /* GPIO methods */ + DEVMETHOD(gpio_get_bus, tca6416_get_bus), + DEVMETHOD(gpio_pin_max, tca6416_pin_max), + DEVMETHOD(gpio_pin_getcaps, tca6416_pin_getcaps), + DEVMETHOD(gpio_pin_getflags, tca6416_pin_getflags), + DEVMETHOD(gpio_pin_setflags, tca6416_pin_setflags), + DEVMETHOD(gpio_pin_getname, tca6416_pin_getname), + DEVMETHOD(gpio_pin_get, tca6416_pin_get), + DEVMETHOD(gpio_pin_set, tca6416_pin_set), + DEVMETHOD(gpio_pin_toggle, tca6416_pin_toggle), + + DEVMETHOD_END +}; + +static driver_t tca6416_driver = { + "tca6416", + tca6416_methods, + sizeof(struct tca6416_softc) +}; + +static devclass_t tca6416_devclass; + +extern devclass_t gpioc_devclass; +extern driver_t gpioc_driver; + +DRIVER_MODULE(tca6416, iicbus, tca6416_driver, tca6416_devclass, 0, 0); +DRIVER_MODULE(gpioc, tca6416, gpioc_driver, gpioc_devclass, 0, 0); +extern devclass_t ofwgpiobus_devclass; +extern driver_t ofw_gpiobus_driver; +DRIVER_MODULE(ofw_gpiobus, tca6416, ofw_gpiobus_driver, + ofwgpiobus_devclass, 0, 0); +extern devclass_t gpiobus_devclass; +extern driver_t gpiobus_driver; +DRIVER_MODULE(gpiobus, tca6416, gpiobus_driver, gpiobus_devclass, 0, 0); +MODULE_VERSION(tca6416, 1); + +static struct ofw_compat_data compat_data[] = { + {"ti,tca6416", 1}, + {0,0} +}; + +static int +tca6416_read(device_t dev, uint8_t reg, uint8_t *data) +{ + struct iic_msg msgs[2]; + struct tca6416_softc *sc; + int error; + + sc = device_get_softc(dev); + if (data == NULL) + return (EINVAL); + + msgs[0].slave = sc->addr; + msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP; + msgs[0].len = 1; + msgs[0].buf = ® + + msgs[1].slave = sc->addr; + msgs[1].flags = IIC_M_RD; + msgs[1].len = 1; + msgs[1].buf = data; + + error = iicbus_transfer_excl(dev, msgs, 2, IIC_WAIT); + return (iic2errno(error)); +} + +static int +tca6416_write(device_t dev, uint8_t reg, uint8_t val) +{ + struct iic_msg msg; + struct tca6416_softc *sc; + int error; + uint8_t buffer[2] = {reg, val}; + + sc = device_get_softc(dev); + + msg.slave = sc->addr; + msg.flags = IIC_M_WR; + msg.len = 2; + msg.buf = buffer; + + error = iicbus_transfer_excl(dev, &msg, 1, IIC_WAIT); + return (iic2errno(error)); +} + +static void +tca6416_conf(void *arg) +{ + device_t dev; + struct tca6416_softc *sc; + int error = 0; + uint8_t regval; + + dev = (device_t)arg; + sc = device_get_softc(dev); + + config_intrhook_disestablish(&sc->intr_hook); + + mtx_lock(&sc->mtx); + + error = tca6416_write(dev, POLARITY_INV_REG, 0x00); + if (error != 0) { + device_printf(dev, "Could not write to device.\n"); + mtx_unlock(&sc->mtx); + return; + } + error = tca6416_write(dev, POLARITY_INV_REG | 1, 0x00); + if (error != 0) { + device_printf(dev, "Could not write to device\n"); + mtx_unlock(&sc->mtx); + return; + } + + /* + * Read from registers to confirm that the device + * can establish communication in both ways. + */ + error = tca6416_read(dev, POLARITY_INV_REG, ®val); + if (error || regval != 0x00) { + device_printf(dev, "Error while reading from device\n"); + mtx_unlock(&sc->mtx); + return; + } + error = tca6416_read(dev, POLARITY_INV_REG | 1, ®val); + if (error || regval != 0x00) { + device_printf(dev, "Error while reading from device\n"); + mtx_unlock(&sc->mtx); + return; + } + + mtx_unlock(&sc->mtx); + + sc->busdev = gpiobus_attach_bus(dev); + if (!sc->busdev){ + device_printf(dev, "Could not create busdev child.\n"); + } + +#ifdef DEBUG + tca6416_regdump_setup(dev); +#endif + +} + +static int +tca6416_probe(device_t dev) +{ + + if (!ofw_bus_status_okay(dev)) + return (ENXIO); + + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); + + device_set_desc(dev, "TCA6416 I/O expander"); + return (BUS_PROBE_DEFAULT); +} + +static int +tca6416_attach(device_t dev) +{ + struct tca6416_softc *sc; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->addr = iicbus_get_addr(dev); + + mtx_init(&sc->mtx, "tca6416 gpio", "gpio", MTX_DEF); + + /* + * Since many i2c controllers do not operate properly + * before the interrupts start, the rest of configuration + * is executed after interrupts are enabled. + */ + sc->intr_hook.ich_func = tca6416_conf; + sc->intr_hook.ich_arg = dev; + + error = config_intrhook_establish(&sc->intr_hook); + if (error != 0) { + mtx_destroy(&sc->mtx); + return (ENOMEM); + } + + return (0); +} + +static int +tca6416_detach(device_t dev) +{ + struct tca6416_softc *sc; + + sc = device_get_softc(dev); + + if (sc->busdev != NULL) + gpiobus_detach_bus(sc->busdev); + + mtx_destroy(&sc->mtx); + + return (0); +} + +static device_t +tca6416_get_bus(device_t dev) +{ + struct tca6416_softc *sc; + + sc = device_get_softc(dev); + + return (sc->busdev); +} + +static int +tca6416_pin_max(device_t dev __unused, int *maxpin) +{ + + if (maxpin == NULL) + return (EINVAL); + + *maxpin = NUM_PINS - 1; + return (0); +} + +static int +tca6416_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) +{ + + if (pin >= NUM_PINS || caps == NULL) + return (EINVAL); + + *caps = PIN_CAPS; + return (0); +} + +static int +tca6416_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) +{ + struct tca6416_softc *sc; + int error; + uint8_t reg_addr, reg_bit, val; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS || pflags == NULL) + return (EINVAL); + + reg_addr = TCA6416_REG_ADDR(pin, CONF_REG); + reg_bit = TCA6416_BIT_FROM_PIN(pin); + + error = tca6416_read(dev, reg_addr, &val); + if (error != 0) + return (error); + + *pflags = (val & (1 << reg_bit)) + ? GPIO_PIN_INPUT : GPIO_PIN_OUTPUT; + + return (0); +} + +static int +tca6416_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) +{ + struct tca6416_softc *sc; + uint8_t reg_addr, reg_bit, val; + int error; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + + reg_addr = TCA6416_REG_ADDR(pin, CONF_REG); + reg_bit = TCA6416_BIT_FROM_PIN(pin); + + mtx_lock(&sc->mtx); + + error = tca6416_read(dev, reg_addr, &val); + if (error != 0) { + mtx_unlock(&sc->mtx); + return (error); + } + + if (flags == GPIO_PIN_INPUT) { + val |= (1 << reg_bit); + } else if (flags == GPIO_PIN_OUTPUT) { + val &= ~(1 << reg_bit); + } else { + mtx_unlock(&sc->mtx); + return (EOPNOTSUPP); + } + + error = tca6416_write(dev, reg_addr, val); + + mtx_unlock(&sc->mtx); + + return (error); +} + +static int +tca6416_pin_getname(device_t dev, uint32_t pin, char *name) +{ + + if (pin >= NUM_PINS || name == NULL) + return (EINVAL); + + snprintf(name, GPIOMAXNAME, "gpio_P%d%d", pin / PINS_PER_REG, + pin % PINS_PER_REG); + + return (0); +} + +static int +tca6416_pin_get(device_t dev, uint32_t pin, unsigned int *pval) +{ + struct tca6416_softc *sc; + uint8_t reg_bit, reg_addr, reg_pvalue; + int error; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS || pval == NULL) + return (EINVAL); + + reg_bit = TCA6416_BIT_FROM_PIN(pin); + reg_addr = TCA6416_REG_ADDR(pin, IN_PORT_REG); + + dbg_dev_printf(dev, "Reading pin %u pvalue.", pin); + + error = tca6416_read(dev, reg_addr, ®_pvalue); + if (error != 0) + return (error); + + *pval = reg_pvalue & (1 << reg_bit) ? 1 : 0; + + return (0); +} + +static int +tca6416_pin_set(device_t dev, uint32_t pin, unsigned int val) +{ + struct tca6416_softc *sc; + uint8_t reg_addr, reg_bit, reg_value; + int error; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + + reg_addr = TCA6416_REG_ADDR(pin , OUT_PORT_REG); + reg_bit = TCA6416_BIT_FROM_PIN(pin); + + dbg_dev_printf(dev, "Setting pin: %u to %u\n", pin, val); + + mtx_lock(&sc->mtx); + + error = tca6416_read(dev, reg_addr, ®_value); + if (error != 0) { + dbg_dev_printf(dev, "Failed to read from register.\n"); + mtx_unlock(&sc->mtx); + return (error); + } + + if (val != 0) + reg_value |= (1 << reg_bit); + else + reg_value &= ~(1 << reg_bit); + + + error = tca6416_write(dev, reg_addr, reg_value); + if (error != 0) { + dbg_dev_printf(dev, "Could not write to register.\n"); + mtx_unlock(&sc->mtx); + return (error); + } + + mtx_unlock(&sc->mtx); + + return (0); +} + +static int +tca6416_pin_toggle(device_t dev, uint32_t pin) +{ + struct tca6416_softc *sc; + int error; + uint8_t reg_addr, reg_bit, reg_value; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + + reg_addr = TCA6416_REG_ADDR(pin, OUT_PORT_REG); + reg_bit = TCA6416_BIT_FROM_PIN(pin); + + dbg_dev_printf(dev, "Toggling pin: %d\n", pin); + + mtx_lock(&sc->mtx); + + error = tca6416_read(dev, reg_addr, ®_value); + if (error != 0) { + mtx_unlock(&sc->mtx); + dbg_dev_printf(dev, "Cannot read from register.\n"); + return (error); + } + + reg_value ^= (1 << reg_bit); + + error = tca6416_write(dev, reg_addr, reg_value); + if (error != 0) + dbg_dev_printf(dev, "Cannot write to register.\n"); + + mtx_unlock(&sc->mtx); + + return (error); +} + +#ifdef DEBUG +static void +tca6416_regdump_setup(device_t dev) +{ + struct sysctl_ctx_list *ctx; + struct sysctl_oid *node; + + ctx = device_get_sysctl_ctx(dev); + node = device_get_sysctl_tree(dev); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_1", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, IN_PORT_REG, + tca6416_regdump_sysctl, "A", "Input port 1"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "in_reg_2", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, + IN_PORT_REG | 1, tca6416_regdump_sysctl, "A", "Input port 2"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_1", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, OUT_PORT_REG, + tca6416_regdump_sysctl, "A", "Output port 1"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "out_reg_2", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, OUT_PORT_REG + | 1, tca6416_regdump_sysctl, "A", "Output port 2"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_1", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, + POLARITY_INV_REG, tca6416_regdump_sysctl, "A", "Polarity inv 1"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "pol_inv_2", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, + POLARITY_INV_REG | 1, tca6416_regdump_sysctl, "A", + "Polarity inv 2"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_1", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, + CONF_REG, tca6416_regdump_sysctl, "A", "Configuration 1"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(node), OID_AUTO, "conf_reg_2", + CTLTYPE_STRING | CTLFLAG_RD | CTLFLAG_MPSAFE, dev, + CONF_REG | 1, tca6416_regdump_sysctl, "A", "Configuration 2"); +} + +static int +tca6416_regdump_sysctl(SYSCTL_HANDLER_ARGS) +{ + device_t dev; + char buf[5]; + struct tca6416_softc *sc; + int len, error; + uint8_t reg, regval; + + dev = (device_t)arg1; + reg = (uint8_t)arg2; + sc = device_get_softc(dev); + + + error = tca6416_read(dev, reg, ®val); + if (error != 0) { + return (error); + } + + len = snprintf(buf, 5, "0x%02x", regval); + + error = sysctl_handle_string(oidp, buf, len, req); + + return (error); +} +#endif Index: sys/modules/i2c/Makefile =================================================================== --- sys/modules/i2c/Makefile +++ sys/modules/i2c/Makefile @@ -24,4 +24,8 @@ smb \ smbus \ +.if !empty(OPT_FDT) +SUBDIR += tca6416 +.endif + .include Index: sys/modules/i2c/tca6416/Makefile =================================================================== --- /dev/null +++ sys/modules/i2c/tca6416/Makefile @@ -0,0 +1,7 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/iicbus +KMOD = tca6416 +SRCS = tca6416.c opt_platform.h gpio_if.h + +.include