svn commit: r361462 - in head/sys: conf dev/iicbus/gpio modules/i2c modules/i2c/tca6416

Marcin Wojtas mw at FreeBSD.org
Mon May 25 15:31:44 UTC 2020


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 <dgr at 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 = ®
+
+	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>


More information about the svn-src-all mailing list