Author: mw
Date: Mon May 25 15:31:43 2020
New Revision: 361462
URL: https://svnweb.freebsd.org/changeset/base/361462

Log:
  Add TCA6416 GPIO expander support.
  
  Add basic TCA6416 GPIO expander support over I2C bus. The driver handles
  enabling and disabling pins, setting pin mode to IN and OUT and
  toggling the pins. External interrupts are not supported.
  
  Submitted by: Dawid Gorecki <d...@semihalf.com>
  Reviewed by: manu, mmel
  Obtained from: Semihalf
  Sponsored by: Alstom Group
  Differential Revision: https://reviews.freebsd.org/D24363

Added:
  head/sys/dev/iicbus/gpio/
  head/sys/dev/iicbus/gpio/tca6416.c   (contents, props changed)
  head/sys/modules/i2c/tca6416/
  head/sys/modules/i2c/tca6416/Makefile   (contents, props changed)
Modified:
  head/sys/conf/files
  head/sys/modules/i2c/Makefile

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files Mon May 25 15:21:38 2020        (r361461)
+++ head/sys/conf/files Mon May 25 15:31:43 2020        (r361462)
@@ -1806,6 +1806,7 @@ dev/iicbus/ofw_iicbus.c           optional fdt iicbus
 dev/iicbus/rtc8583.c           optional rtc8583
 dev/iicbus/s35390a.c           optional s35390a
 dev/iicbus/sy8106a.c           optional sy8106a ext_resources fdt
+dev/iicbus/gpio/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

Added: head/sys/dev/iicbus/gpio/tca6416.c
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/dev/iicbus/gpio/tca6416.c  Mon May 25 15:31:43 2020        
(r361462)
@@ -0,0 +1,534 @@
+/*-
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/sysctl.h>
+
+#include <machine/bus.h>
+
+#include <dev/ofw/openfirm.h>
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#include <dev/gpio/gpiobusvar.h>
+
+#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 | GPIO_PIN_INVIN)
+
+#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;
+};
+
+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 = {
+       "gpio",
+       tca6416_methods,
+       sizeof(struct tca6416_softc)
+};
+
+static devclass_t tca6416_devclass;
+
+DRIVER_MODULE(tca6416, iicbus, tca6416_driver, tca6416_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 = &reg;
+
+       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 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;
+
+       sc = device_get_softc(dev);
+       sc->dev = dev;
+       sc->addr = iicbus_get_addr(dev);
+
+       mtx_init(&sc->mtx, "tca6416 gpio", "gpio", MTX_DEF);
+
+       sc->busdev = gpiobus_attach_bus(dev);
+       if (sc->busdev == NULL) {
+               device_printf(dev, "Could not create busdev child\n");
+               return (ENXIO);
+       }
+
+#ifdef DEBUG
+       tca6416_regdump_setup(dev);
+#endif
+
+       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;
+
+       reg_addr = TCA6416_REG_ADDR(pin, POLARITY_INV_REG);
+
+       error = tca6416_read(dev, reg_addr, &val);
+       if (error != 0)
+               return (error);
+
+       if (val & (1 << reg_bit))
+               *pflags |= GPIO_PIN_INVIN;
+
+       return (0);
+}
+
+static int
+tca6416_pin_setflags(device_t dev, uint32_t pin, uint32_t flags)
+{
+       uint8_t reg_addr, inv_reg_addr, reg_bit, val, inv_val;
+       struct tca6416_softc *sc;
+       int error;
+
+       sc = device_get_softc(dev);
+
+       if (pin >= NUM_PINS)
+               return (EINVAL);
+
+       reg_addr = TCA6416_REG_ADDR(pin, CONF_REG);
+       inv_reg_addr = TCA6416_REG_ADDR(pin, POLARITY_INV_REG);
+       reg_bit = TCA6416_BIT_FROM_PIN(pin);
+
+       mtx_lock(&sc->mtx);
+
+       error = tca6416_read(dev, reg_addr, &val);
+       if (error != 0)
+               goto fail;
+       error = tca6416_read(dev, inv_reg_addr, &inv_val);
+       if (error != 0)
+               goto fail;
+
+       if (flags & GPIO_PIN_INPUT)
+               val |= (1 << reg_bit);
+       else if (flags & GPIO_PIN_OUTPUT)
+               val &= ~(1 << reg_bit);
+
+       if (flags & GPIO_PIN_INVIN)
+               inv_val |= (1 << reg_bit);
+       else
+               inv_val &= ~(1 << reg_bit);
+
+       error = tca6416_write(dev, reg_addr, val);
+       if (error != 0)
+               goto fail;
+       error = tca6416_write(dev, inv_reg_addr, val);
+
+fail:
+       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, &reg_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, &reg_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, &reg_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, &regval);
+       if (error != 0) {
+               return (error);
+       }
+
+       len = snprintf(buf, 5, "0x%02x", regval);
+
+       error = sysctl_handle_string(oidp, buf, len, req);
+
+       return (error);
+}
+#endif

Modified: head/sys/modules/i2c/Makefile
==============================================================================
--- head/sys/modules/i2c/Makefile       Mon May 25 15:21:38 2020        
(r361461)
+++ head/sys/modules/i2c/Makefile       Mon May 25 15:31:43 2020        
(r361462)
@@ -24,4 +24,8 @@ SUBDIR = \
        smb \
        smbus \
 
+.if !empty(OPT_FDT)
+SUBDIR += tca6416
+.endif
+
 .include <bsd.subdir.mk>

Added: head/sys/modules/i2c/tca6416/Makefile
==============================================================================
--- /dev/null   00:00:00 1970   (empty, because file is newly added)
+++ head/sys/modules/i2c/tca6416/Makefile       Mon May 25 15:31:43 2020        
(r361462)
@@ -0,0 +1,7 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus/gpio/
+KMOD   = tca6416
+SRCS   = tca6416.c opt_platform.h gpio_if.h
+
+.include <bsd.kmod.mk>
_______________________________________________
svn-src-all@freebsd.org mailing list
https://lists.freebsd.org/mailman/listinfo/svn-src-all
To unsubscribe, send any mail to "svn-src-all-unsubscr...@freebsd.org"

Reply via email to