git: f1d2f22b34ab - stable/13 - pca954x: driver for PCA954x / TCA954x I2C switches

From: Andriy Gapon <avg_at_FreeBSD.org>
Date: Wed, 24 Nov 2021 14:22:10 UTC
The branch stable/13 has been updated by avg:

URL: https://cgit.FreeBSD.org/src/commit/?id=f1d2f22b34ab6c7216fcc980c57b60d5ef8f21d2

commit f1d2f22b34ab6c7216fcc980c57b60d5ef8f21d2
Author:     Andriy Gapon <avg@FreeBSD.org>
AuthorDate: 2020-08-18 09:16:28 +0000
Commit:     Andriy Gapon <avg@FreeBSD.org>
CommitDate: 2021-11-24 14:19:09 +0000

    pca954x: driver for PCA954x / TCA954x I2C switches
    
    At the moment only PCA9548A is supported and has been tested.
    
    (cherry picked from commit c0525ab1d1ce69ab3d589e95733caedb04e0dcbd)
---
 share/man/man4/Makefile              |   1 +
 share/man/man4/pca954x.4             | 119 +++++++++++++++++++
 sys/conf/files                       |   1 +
 sys/dev/iicbus/mux/pca954x.c         | 214 +++++++++++++++++++++++++++++++++++
 sys/modules/i2c/mux/Makefile         |   1 +
 sys/modules/i2c/mux/pca954x/Makefile |  20 ++++
 6 files changed, 356 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index e8c0a5651ff8..be66bf02e9f7 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -422,6 +422,7 @@ MAN=	aac.4 \
 	owc.4 \
 	${_padlock.4} \
 	pass.4 \
+	pca954x.4 \
 	pccard.4 \
 	pccbb.4 \
 	pcf.4 \
diff --git a/share/man/man4/pca954x.4 b/share/man/man4/pca954x.4
new file mode 100644
index 000000000000..d845740f9b6c
--- /dev/null
+++ b/share/man/man4/pca954x.4
@@ -0,0 +1,119 @@
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+.\"
+.\" Copyright (c) 2020 Andriy Gapon <avg@FreeBSD.org>
+.\"
+.\" 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$
+.\"
+.Dd November 13, 2021
+.Dt PCA954X 4
+.Os
+.Sh NAME
+.Nm pca954x
+.Nd driver for PCA9548A I2C switch
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device pca954x"
+.Cd "device iicmux"
+.Cd "device iicbus"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+pca954x_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver supports the PCA9548A I2C bus switch and compatible chips such as
+TCA9548A.
+It automatically connects an upstream I2C bus to one of several downstream
+buses as needed when slave devices on the downstream buses initiate I/O.
+More information on the automatic switching behavior is available in
+.Xr iicmux 4 .
+.Sh FDT CONFIGURATION
+On an
+.Xr FDT 4
+based system, an
+.Nm
+device node is defined as a child node of its upstream I2C bus.
+The children of the
+.Nm
+node are additional I2C buses, which will have their own I2C slave
+devices described in their child nodes.
+.Pp
+The
+.Nm
+driver attaches to nodes where the
+.Va compatible
+property is set to one of
+.Bl -bullet
+.It
+.Qq nxp,pca9548
+.El
+.Pp
+The
+.Nm
+driver supports the following optional properties in addition to the standard
+I2C mux properties:
+.Bl -tag -width i2c-mux-idle-disconnect
+.It Va i2c-mux-idle-disconnect
+if defined, forces the switch to disconnect all children in idle state.
+.El
+.Sh HINTS CONFIGURATION
+On a
+.Xr device.hints 5
+based system, these values are configurable for
+.Nm :
+.Bl -tag -width hint.pca954x.<unit>.chip_type
+.It Va hint.pca954x.<unit>.at
+The upstream
+.Xr iicbus 4
+the
+.Nm
+instance is attached to.
+.It Va hint.pca954x.<unit>.chip_type
+The type of the chip.
+At present, only
+.Qq pca9548
+is supported.
+.El
+.Pp
+When configured via hints, the driver automatically adds an
+.Xr iicbus 4
+instance for every downstream bus supported by the chip.
+There is currently no way to indicate used versus unused channels.
+.Sh SEE ALSO
+.Xr iicbus 4 ,
+.Xr iicmux 4
+.Sh HISTORY
+The
+.Nm
+driver and this manual page was written by
+.An Andriy Gapon Aq Mt avg@FreeBSD.org .
diff --git a/sys/conf/files b/sys/conf/files
index 5051a38b2997..3dc1caf8b53a 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1884,6 +1884,7 @@ dev/iicbus/mux/iicmux.c		optional iicmux
 dev/iicbus/mux/iicmux_if.m	optional iicmux
 dev/iicbus/mux/iic_gpiomux.c	optional iic_gpiomux fdt
 dev/iicbus/mux/ltc430x.c	optional ltc430x
+dev/iicbus/mux/pca954x.c	optional pca954x
 dev/iicbus/nxprtc.c		optional nxprtc | pcf8563
 dev/iicbus/ofw_iicbus.c		optional fdt iicbus
 dev/iicbus/pcf8574.c		optional pcf8574
diff --git a/sys/dev/iicbus/mux/pca954x.c b/sys/dev/iicbus/mux/pca954x.c
new file mode 100644
index 000000000000..44c81539691e
--- /dev/null
+++ b/sys/dev/iicbus/mux/pca954x.c
@@ -0,0 +1,214 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) Andriy Gapon
+ *
+ * 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.
+ *
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_platform.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/systm.h>
+
+#ifdef FDT
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/openfirm.h>
+#endif
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+#include "iicbus_if.h"
+#include "iicmux_if.h"
+#include <dev/iicbus/mux/iicmux.h>
+
+struct pca954x_descr {
+	const char 	*partname;
+	const char	*description;
+	int		numchannels;
+};
+
+static struct pca954x_descr pca9548_descr = {
+	.partname = "pca9548",
+	.description = "PCA9548A I2C Mux",
+	.numchannels = 8,
+};
+
+#ifdef FDT
+static struct ofw_compat_data compat_data[] = {
+	{ "nxp,pca9548", (uintptr_t)&pca9548_descr },
+	{ NULL, 0 },
+};
+#else
+static struct pca954x_descr *part_descrs[] = {
+	&pca9548_descr,
+};
+#endif
+
+struct pca954x_softc {
+	struct iicmux_softc mux;
+	uint8_t addr;
+	bool idle_disconnect;
+};
+
+static int
+pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
+{
+	struct iic_msg msg;
+	struct pca954x_softc *sc = device_get_softc(dev);
+	uint8_t busbits;
+	int error;
+
+	/*
+	 * The iicmux caller ensures busidx is between 0 and the number of buses
+	 * we passed to iicmux_init_softc(), no need for validation here.  If
+	 * the fdt data has the idle_disconnect property we idle the bus by
+	 * selecting no downstream buses, otherwise we just leave the current
+	 * bus active.
+	 */
+	if (busidx == IICMUX_SELECT_IDLE) {
+		if (sc->idle_disconnect)
+			busbits = 0;
+		else
+			return (0);
+	} else {
+		busbits = 1u << busidx;
+	}
+
+	msg.slave = sc->addr;
+	msg.flags = IIC_M_WR;
+	msg.len = 1;
+	msg.buf = &busbits;
+	error = iicbus_transfer(dev, &msg, 1);
+	return (error);
+}
+
+static const struct pca954x_descr *
+pca954x_find_chip(device_t dev)
+{
+#ifdef FDT
+	const struct ofw_compat_data *compat;
+
+	compat = ofw_bus_search_compatible(dev, compat_data);
+	if (compat == NULL)
+		return (NULL);
+	return ((const struct pca954x_descr *)compat->ocd_data);
+#else
+	const char *type;
+	int i;
+
+	if (resource_string_value(device_get_name(dev), device_get_unit(dev),
+	    "chip_type", &type) == 0) {
+		for (i = 0; i < nitems(part_descrs) - 1; ++i) {
+			if (strcasecmp(type, part_descrs[i]->partname) == 0)
+				return (part_descrs[i]);
+		}
+	}
+	return (NULL);
+#endif
+}
+
+static int
+pca954x_probe(device_t dev)
+{
+	const struct pca954x_descr *descr;
+
+	descr = pca954x_find_chip(dev);
+	if (descr == NULL)
+		return (ENXIO);
+
+	device_set_desc(dev, descr->description);
+	return (BUS_PROBE_DEFAULT);
+}
+
+static int
+pca954x_attach(device_t dev)
+{
+#ifdef FDT
+	phandle_t node;
+#endif
+	struct pca954x_softc *sc;
+	const struct pca954x_descr *descr;
+	int err;
+
+	sc = device_get_softc(dev);
+	sc->addr = iicbus_get_addr(dev);
+#ifdef FDT
+	node = ofw_bus_get_node(dev);
+	sc->idle_disconnect = OF_hasprop(node, "i2c-mux-idle-disconnect");
+#endif
+
+	descr = pca954x_find_chip(dev);
+	err = iicmux_attach(dev, device_get_parent(dev), descr->numchannels);
+	if (err == 0)
+                bus_generic_attach(dev);
+	return (err);
+}
+
+static int
+pca954x_detach(device_t dev)
+{
+	int err;
+
+	err = iicmux_detach(dev);
+	return (err);
+}
+
+static device_method_t pca954x_methods[] = {
+	/* device methods */
+	DEVMETHOD(device_probe,			pca954x_probe),
+	DEVMETHOD(device_attach,		pca954x_attach),
+	DEVMETHOD(device_detach,		pca954x_detach),
+
+	/* iicmux methods */
+	DEVMETHOD(iicmux_bus_select,		pca954x_bus_select),
+
+	DEVMETHOD_END
+};
+
+static devclass_t pca954x_devclass;
+
+DEFINE_CLASS_1(pca9548, pca954x_driver, pca954x_methods,
+    sizeof(struct pca954x_softc), iicmux_driver);
+DRIVER_MODULE(pca9548, iicbus, pca954x_driver, pca954x_devclass, 0, 0);
+
+#ifdef FDT
+DRIVER_MODULE(ofw_iicbus, pca9548, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0);
+#else
+DRIVER_MODULE(iicbus, pca9548, iicbus_driver, iicbus_devclass, 0, 0);
+#endif
+
+MODULE_DEPEND(pca9548, iicmux, 1, 1, 1);
+MODULE_DEPEND(pca9548, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
+MODULE_VERSION(pca9548, 1);
+
+#ifdef FDT
+IICBUS_FDT_PNP_INFO(compat_data);
+#endif
diff --git a/sys/modules/i2c/mux/Makefile b/sys/modules/i2c/mux/Makefile
index 8e228a7a7fef..a972a00a3b3a 100644
--- a/sys/modules/i2c/mux/Makefile
+++ b/sys/modules/i2c/mux/Makefile
@@ -3,6 +3,7 @@
 SUBDIR = \
 	iicmux \
 	ltc430x \
+	pca954x \
 
 .if !empty(OPT_FDT)
 SUBDIR+= iic_gpiomux
diff --git a/sys/modules/i2c/mux/pca954x/Makefile b/sys/modules/i2c/mux/pca954x/Makefile
new file mode 100644
index 000000000000..622b327204c5
--- /dev/null
+++ b/sys/modules/i2c/mux/pca954x/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+.PATH:		${SRCTOP}/sys/dev/iicbus/mux
+
+KMOD=	pca954x
+SRCS=	pca954x.c
+
+SRCS+=	\
+	bus_if.h \
+	device_if.h \
+	iicbus_if.h \
+	iicmux_if.h \
+	opt_platform.h \
+
+.if !empty(OPT_FDT)
+SRCS+=	ofw_bus_if.h
+.endif
+
+
+.include <bsd.kmod.mk>