summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/Makefile3
-rw-r--r--share/man/man4/iic_gpiomux.488
-rw-r--r--share/man/man4/iicmux.4148
-rw-r--r--share/man/man4/ltc430x.4112
-rw-r--r--sys/conf/NOTES5
-rw-r--r--sys/conf/files4
-rw-r--r--sys/dev/iicbus/iiconf.c13
-rw-r--r--sys/dev/iicbus/iiconf.h19
-rw-r--r--sys/dev/iicbus/mux/iic_gpiomux.c249
-rw-r--r--sys/dev/iicbus/mux/iicmux.c387
-rw-r--r--sys/dev/iicbus/mux/iicmux.h78
-rw-r--r--sys/dev/iicbus/mux/iicmux_if.m49
-rw-r--r--sys/dev/iicbus/mux/ltc430x.c221
-rw-r--r--sys/modules/i2c/Makefile1
-rw-r--r--sys/modules/i2c/mux/Makefile11
-rw-r--r--sys/modules/i2c/mux/iic_gpiomux/Makefile20
-rw-r--r--sys/modules/i2c/mux/iicmux/Makefile19
-rw-r--r--sys/modules/i2c/mux/ltc430x/Makefile20
18 files changed, 1445 insertions, 2 deletions
diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 6a609ae50460..82394156d1d5 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -204,8 +204,10 @@ MAN= aac.4 \
ig4.4 \
igmp.4 \
iic.4 \
+ iic_gpiomux.4 \
iicbb.4 \
iicbus.4 \
+ iicmux.4 \
iicsmb.4 \
iir.4 \
${_imcsmb.4} \
@@ -258,6 +260,7 @@ MAN= aac.4 \
lp.4 \
lpbb.4 \
lpt.4 \
+ ltc430x.4 \
mac.4 \
mac_biba.4 \
mac_bsdextended.4 \
diff --git a/share/man/man4/iic_gpiomux.4 b/share/man/man4/iic_gpiomux.4
new file mode 100644
index 000000000000..16d15fbcfd3a
--- /dev/null
+++ b/share/man/man4/iic_gpiomux.4
@@ -0,0 +1,88 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2019 Ian Lepore <ian@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 January 1, 2020
+.Dt IIC_GPIOMUX 4
+.Os
+.Sh NAME
+.Nm iic_gpiomux
+.Nd driver for I2C mux hardware controlled via GPIO
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device iic_gpiomux"
+.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
+iic_gpiomux_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver supports any type of I2C bus multiplexer (mux) hardware that
+is controlled by manipulating the state of one or more GPIO pins.
+It automatically connects an upstream I2C bus to one of the 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 .
+.Pp
+.Sh FDT CONFIGURATION
+On an
+.Xr fdt 4
+based system, an
+.Nm
+device node may be defined as a child node of any arbitrary bus
+in the FDT data.
+The
+.Va i2c-parent
+property indicates the connection to the 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 conforms to the standard
+.Bk -words
+.Li i2c/i2c-mux-gpio.txt
+.Ek
+bindings document.
+.Sh SEE ALSO
+.Xr iicbus 4 ,
+.Xr iicmux 4 ,
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 13.0 .
diff --git a/share/man/man4/iicmux.4 b/share/man/man4/iicmux.4
new file mode 100644
index 000000000000..b505fcf43ac5
--- /dev/null
+++ b/share/man/man4/iicmux.4
@@ -0,0 +1,148 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2019 Ian Lepore <ian@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 January 1, 2020
+.Dt IICMUX 4
+.Os
+.Sh NAME
+.Nm iicmux
+.Nd I2C bus mulitiplexer framework
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device iicmux"
+.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
+iicmux_load="YES"
+.Ed
+.Pp
+Note that it is usually not necessary to explicitly load the
+driver module, as it will be loaded automatically along with
+the driver for the specific mux hardware in use.
+.Sh DESCRIPTION
+The
+.Nm
+framework provides support code to help implement drivers for various
+I2C bus multiplexer (mux) hardware.
+.Nm
+is not a standalone driver,
+it is a collection of support functions and driver methods which are
+used by individual mux hardware drivers.
+It will be loaded automatically when needed by a mux hardware driver.
+This manual page provides an overview of the I2C mux framework and its
+behavior.
+.Pp
+Generally speaking, an I2C mux is connected to an upstream I2C bus, and to
+one or more downstream I2C buses, and it can be commanded to connect
+any one of the downstream buses to the upstream bus.
+Some hardware may be able to connect multiple downstream buses at the
+same time, but that concept is not supported by
+.Nm .
+.Pp
+The
+.Nm
+framework operates automatically when I2C slave devices initiate I/O.
+It does not require (or even allow for) any external control to select
+the active downstream bus.
+.Pp
+When there is no I/O in progress, the mux is said to be in the
+.Dq idle
+state.
+Some mux hardware has the ability to disconnect all downstream buses
+when in an idle state.
+Other hardware must always have one of the downstream buses connected.
+Individual mux hardware drivers typically provide a way to select which
+downstream bus (if any) should be connected while in the idle state.
+In the absence of such configuration, whichever downstream bus was
+last used remains connected to the upstream bus.
+.Pp
+When an I2C slave device on a bus downstream of a mux initiates I/O,
+it first requests exclusive use of the bus by calling
+.Fn iicbus_request_bus .
+This request is communicated to the bus's parent, which is the
+.Nm
+framework
+mux driver.
+Once exclusive bus ownership is obtained, the mux driver
+connects the upstream I2C bus to the downstream bus which hosts the
+slave device that requested bus ownership.
+The mux hardware maintains that upstream-to-downstream connection until
+the slave device calls
+.Fn iicbus_release_bus .
+Before releasing ownership, the mux driver returns the mux hardware to
+the idle state.
+.Sh FDT CONFIGURATION
+On an
+.Xr fdt 4
+based system, an I2C mux device node is defined as a child node of its
+upstream I2C bus when the mux device is an I2C slave itself.
+It may be defined as a child node of any other bus or device in the
+system when it is not an I2C slave, in which case the
+.Va i2c-parent
+property indicates which upstream bus the mux is attached to.
+In either case, the children of the mux node are additional I2C buses, which
+will have one or more I2C slave devices described in their child nodes.
+.Pp
+Drivers using the
+.Nm
+framework conform to the standard
+.Bk -words
+.Li i2c/i2c-mux.txt
+.Ek
+bindings document.
+.Sh HINTS CONFIGURATION
+On a
+.Xr device.hints 5
+based system, these values are configurable for
+.Nm
+framework drivers :
+.Bl -tag -width indent
+.It Va hint.<driver>.<unit>.at
+The upstream
+.Xr iicbus 4
+the
+.Nm
+instance is attached to.
+.El
+.Pp
+When configured via hints, the driver automatically adds an iicbus
+instance for every downstream bus supported by the chip.
+There is currently no way to indicate used versus unused downstream buses.
+.Sh SEE ALSO
+.Xr iicbus 4 ,
+.Sh HISTORY
+The
+.Nm
+framework first appeared in
+.Fx 13.0 .
diff --git a/share/man/man4/ltc430x.4 b/share/man/man4/ltc430x.4
new file mode 100644
index 000000000000..eec672e354c9
--- /dev/null
+++ b/share/man/man4/ltc430x.4
@@ -0,0 +1,112 @@
+.\"-
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2019 Ian Lepore <ian@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 January 1, 2020
+.Dt LTC430X 4
+.Os
+.Sh NAME
+.Nm ltc430x
+.Nd driver for LTC4305 and LTC4306 I2C mux chips
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following line in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device ltc430x"
+.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
+ltc430x_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver supports the LTC4305 and LTC4306 I2C bus multiplexer (mux) chips.
+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 conforms to the standard
+.Bk -words
+.Li i2c/i2c-mux-ltc4306.txt
+.Ek
+bindings document, except that the following optional properties
+are not currently supported and will be ignored if present:
+.Bl -bullet -compact -inset -offset indent
+.It
+enable-gpios
+.It
+gpio-controller
+.It
+#gpio-cells
+.It
+ltc,downstream-accelerators-enable
+.It
+ltc,upstream-accelerators-enable
+.El
+.Sh HINTS CONFIGURATION
+On a
+.Xr device.hints 5
+based system, these values are configurable for
+.Nm :
+.Bl -tag -width indent
+.It Va hint.ltc430x.<unit>.at
+The upstream
+.Xr iicbus 4
+the
+.Nm
+instance is attached to.
+.El
+.Pp
+When configured via hints, the driver automatically adds an iicbus
+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 first appeared in
+.Fx 13.0 .
diff --git a/sys/conf/NOTES b/sys/conf/NOTES
index 19a528218404..338b9503405c 100644
--- a/sys/conf/NOTES
+++ b/sys/conf/NOTES
@@ -2329,6 +2329,11 @@ device iic # userland access to i2c slave devices via ioctl(8)
device iicsmb # smb over i2c bridge
device iicoc # OpenCores I2C controller support
+# I2C bus multiplexer (mux) devices
+device iicmux # i2c mux core driver
+device iic_gpiomux # i2c mux hardware controlled via gpio pins
+device ltc430x # LTC4305 and LTC4306 i2c mux chips
+
# I2C peripheral devices
#
device ad7418 # Analog Devices temp and voltage sensor
diff --git a/sys/conf/files b/sys/conf/files
index bb1614fca784..8eca48091d85 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1801,6 +1801,10 @@ dev/iicbus/iicoc_fdt.c optional iicoc fdt
dev/iicbus/iicoc_pci.c optional iicoc pci
dev/iicbus/isl12xx.c optional isl12xx
dev/iicbus/lm75.c optional lm75
+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/nxprtc.c optional nxprtc | pcf8563
dev/iicbus/ofw_iicbus.c optional fdt iicbus
dev/iicbus/rtc8583.c optional rtc8583
diff --git a/sys/dev/iicbus/iiconf.c b/sys/dev/iicbus/iiconf.c
index 052de6150671..6f24119660ab 100644
--- a/sys/dev/iicbus/iiconf.c
+++ b/sys/dev/iicbus/iiconf.c
@@ -137,6 +137,7 @@ iicbus_poll(struct iicbus_softc *sc, int how)
int
iicbus_request_bus(device_t bus, device_t dev, int how)
{
+ struct iic_reqbus_data reqdata;
struct iicbus_softc *sc = (struct iicbus_softc *)device_get_softc(bus);
int error = 0;
@@ -175,8 +176,11 @@ iicbus_request_bus(device_t bus, device_t dev, int how)
*/
IICBUS_UNLOCK(sc);
/* Ask the underlying layers if the request is ok */
+ reqdata.dev = dev;
+ reqdata.bus = bus;
+ reqdata.flags = how | IIC_REQBUS_DEV;
error = IICBUS_CALLBACK(device_get_parent(bus),
- IIC_REQUEST_BUS, (caddr_t)&how);
+ IIC_REQUEST_BUS, (caddr_t)&reqdata);
IICBUS_LOCK(sc);
if (error != 0) {
@@ -201,6 +205,7 @@ iicbus_request_bus(device_t bus, device_t dev, int how)
int
iicbus_release_bus(device_t bus, device_t dev)
{
+ struct iic_reqbus_data reqdata;
struct iicbus_softc *sc = (struct iicbus_softc *)device_get_softc(bus);
IICBUS_LOCK(sc);
@@ -213,7 +218,11 @@ iicbus_release_bus(device_t bus, device_t dev)
if (--sc->owncount == 0) {
/* Drop the lock while informing the low-level driver. */
IICBUS_UNLOCK(sc);
- IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS, NULL);
+ reqdata.dev = dev;
+ reqdata.bus = bus;
+ reqdata.flags = IIC_REQBUS_DEV;
+ IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS,
+ (caddr_t)&reqdata);
IICBUS_LOCK(sc);
sc->owner = NULL;
wakeup_one(sc);
diff --git a/sys/dev/iicbus/iiconf.h b/sys/dev/iicbus/iiconf.h
index dd85c2a0fc32..b6a9c3b54fcf 100644
--- a/sys/dev/iicbus/iiconf.h
+++ b/sys/dev/iicbus/iiconf.h
@@ -47,6 +47,25 @@
#define IIC_INTR 0x2
#define IIC_INTRWAIT (IIC_INTR | IIC_WAIT)
#define IIC_RECURSIVE 0x4
+#define IIC_REQBUS_DEV 0x8 /* See struct iic_reqbus_data, below. */
+
+/*
+ * The original iicbus->bridge callback api took a pointer to an int containing
+ * flags. The new api allows a pointer to this struct, with IIC_REQBUS_DEV set
+ * in the flags to let the implementation know the pointer is actually to this
+ * struct which has the flags word first, followed by the device_t of the
+ * requesting bus and device.
+ *
+ * Note that the requesting device may not be a i2c slave device which is a
+ * child of the requested bus -- it may be a mux device which is electrically
+ * part of the bus hierarchy, but whose driver belongs to some other bus
+ * hierarchy such as gpio.
+ */
+struct iic_reqbus_data {
+ int flags; /* Flags from the set defined above. */
+ device_t bus; /* The iicbus being requested. */
+ device_t dev; /* The device requesting the bus. */
+};
/*
* i2c modes
diff --git a/sys/dev/iicbus/mux/iic_gpiomux.c b/sys/dev/iicbus/mux/iic_gpiomux.c
new file mode 100644
index 000000000000..d18d85dde7f7
--- /dev/null
+++ b/sys/dev/iicbus/mux/iic_gpiomux.c
@@ -0,0 +1,249 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Ian Lepore <ian@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.
+ */
+
+/*
+ * Driver for i2c bus muxes controlled by one or more gpio pins.
+ *
+ * This driver has #ifdef FDT sections in it, as if it supports both fdt and
+ * hinted attachment, but there is currently no support for hinted attachment.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_platform.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/gpio.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/systm.h>
+
+#include <dev/gpio/gpiobusvar.h>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/mux/iicmux.h>
+
+#ifdef FDT
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/openfirm.h>
+
+static struct ofw_compat_data compat_data[] = {
+ {"i2c-mux-gpio", true},
+ {NULL, false}
+};
+OFWBUS_PNP_INFO(compat_data);
+SIMPLEBUS_PNP_INFO(compat_data);
+#endif /* FDT */
+
+#include <dev/iicbus/iiconf.h>
+#include "iicmux.h"
+#include "iicmux_if.h"
+
+struct gpiomux_softc {
+ struct iicmux_softc mux;
+ int idleidx;
+ int numpins;
+ gpio_pin_t pins[IICMUX_MAX_BUSES];
+};
+
+#define IDLE_NOOP (-1) /* When asked to idle the bus, do nothing. */
+
+static int
+gpiomux_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
+{
+ struct gpiomux_softc *sc = device_get_softc(dev);
+ int i;
+
+ /*
+ * The iicmux caller ensures busidx is between 0 and the number of buses
+ * we passed to iicmux_init_softc(), no need for validation here. The
+ * bits in the index number are transcribed to the state of the pins,
+ * except when we're asked to idle the bus. In that case, we transcribe
+ * sc->idleidx to the pins, unless that is IDLE_NOOP (leave the current
+ * bus selected), in which case we just bail.
+ */
+ if (busidx == IICMUX_SELECT_IDLE) {
+ if (sc->idleidx == IDLE_NOOP)
+ return (0);
+ busidx = sc->idleidx;
+ }
+
+ for (i = 0; i < sc->numpins; ++i)
+ gpio_pin_set_active(sc->pins[i], busidx & (1u << i));
+
+ return (0);
+}
+
+static int
+gpiomux_probe(device_t dev)
+{
+ int rv;
+
+ rv = ENXIO;
+
+#ifdef FDT
+ if (ofw_bus_status_okay(dev) &&
+ ofw_bus_search_compatible(dev, compat_data)->ocd_data)
+ rv = BUS_PROBE_DEFAULT;
+#endif
+
+ device_set_desc(dev, "I2C GPIO Mux");
+
+ return (rv);
+}
+
+static int
+gpiomux_attach(device_t dev)
+{
+ struct gpiomux_softc *sc = device_get_softc(dev);
+ ssize_t len;
+ device_t busdev;
+ int err, i, idlebits, numchannels;
+ pcell_t propval;
+ phandle_t node;
+
+ node = ofw_bus_get_node(dev);
+
+ /*
+ * Locate the gpio pin(s) that control the mux hardware. There can be
+ * multiple pins, but there must be at least one.
+ */
+ for (i = 0; ; ++i) {
+ err = gpio_pin_get_by_ofw_propidx(dev, node, "mux-gpios", i,
+ &sc->pins[i]);
+ if (err != 0) {
+ break;
+ }
+ }
+ sc->numpins = i;
+ if (sc->numpins == 0) {
+ device_printf(dev, "cannot acquire pins listed in mux-gpios\n");
+ return ((err == 0) ? ENXIO : err);
+ }
+ numchannels = 1u << sc->numpins;
+ if (numchannels > IICMUX_MAX_BUSES) {
+ device_printf(dev, "too many mux-gpios pins for max %d buses\n",
+ IICMUX_MAX_BUSES);
+ return (EINVAL);
+ }
+
+ /*
+ * We don't have a parent/child relationship to the upstream bus, we
+ * have to locate it via the i2c-parent property. Explicitly tell the
+ * user which upstream we're associated with, since the normal attach
+ * message is going to mention only our actual parent.
+ */
+ len = OF_getencprop(node, "i2c-parent", &propval, sizeof(propval));
+ if (len != sizeof(propval)) {
+ device_printf(dev, "cannot obtain i2c-parent property\n");
+ return (ENXIO);
+ }
+ busdev = OF_device_from_xref((phandle_t)propval);
+ if (busdev == NULL) {
+ device_printf(dev,
+ "cannot find device referenced by i2c-parent property\n");
+ return (ENXIO);
+ }
+ device_printf(dev, "upstream bus is %s\n", device_get_nameunit(busdev));
+
+ /*
+ * If there is an idle-state property, that is the value we set the pins
+ * to when the bus is idle, otherwise idling the bus is a no-op
+ * (whichever bus was last accessed remains active).
+ */
+ len = OF_getencprop(node, "idle-state", &propval, sizeof(propval));
+ if (len == sizeof(propval)) {
+ if ((int)propval >= numchannels) {
+ device_printf(dev,
+ "idle-state property %d exceeds channel count\n",
+ propval);
+ }
+ sc->idleidx = (int)propval;
+ idlebits = sc->idleidx;
+ } else {
+ sc->idleidx = IDLE_NOOP;
+ idlebits = 0;
+ }
+
+ /* Preset the mux to the idle state to get things started. */
+ for (i = 0; i < sc->numpins; ++i) {
+ gpio_pin_setflags(sc->pins[i], GPIO_PIN_OUTPUT);
+ gpio_pin_set_active(sc->pins[i], idlebits & (1u << i));
+ }
+
+ /* Init the core driver, have it add our child downstream buses. */
+ if ((err = iicmux_attach(dev, busdev, numchannels)) == 0)
+ bus_generic_attach(dev);
+
+ return (err);
+}
+
+static int
+gpiomux_detach(device_t dev)
+{
+ struct gpiomux_softc *sc = device_get_softc(dev);
+ int err, i;
+
+ if ((err = iicmux_detach(dev)) != 0)
+ return (err);
+
+ for (i = 0; i < sc->numpins; ++i)
+ gpio_pin_release(sc->pins[i]);
+
+ return (0);
+}
+
+static device_method_t gpiomux_methods[] = {
+ /* device methods */
+ DEVMETHOD(device_probe, gpiomux_probe),
+ DEVMETHOD(device_attach, gpiomux_attach),
+ DEVMETHOD(device_detach, gpiomux_detach),
+
+ /* iicmux methods */
+ DEVMETHOD(iicmux_bus_select, gpiomux_bus_select),
+
+ DEVMETHOD_END
+};
+
+static devclass_t gpiomux_devclass;
+
+DEFINE_CLASS_1(iic_gpiomux, iic_gpiomux_driver, gpiomux_methods,
+ sizeof(struct gpiomux_softc), iicmux_driver);
+DRIVER_MODULE(iic_gpiomux, simplebus, iic_gpiomux_driver, gpiomux_devclass, 0, 0);
+DRIVER_MODULE(iic_gpiomux, ofw_simplebus, iic_gpiomux_driver, gpiomux_devclass, 0, 0);
+
+#ifdef FDT
+DRIVER_MODULE(ofw_iicbus, iic_gpiomux, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0);
+#else
+DRIVER_MODULE(iicbus, iic_gpiomux, iicbus_driver, iicbus_devclass, 0, 0);
+#endif
+
+MODULE_DEPEND(iic_gpiomux, iicmux, 1, 1, 1);
+MODULE_DEPEND(iic_gpiomux, iicbus, 1, 1, 1);
diff --git a/sys/dev/iicbus/mux/iicmux.c b/sys/dev/iicbus/mux/iicmux.c
new file mode 100644
index 000000000000..b64a0ef2e16b
--- /dev/null
+++ b/sys/dev/iicbus/mux/iicmux.c
@@ -0,0 +1,387 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Ian Lepore <ian@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.
+ */
+
+#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/sysctl.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/iiconf.h>
+#include "iicbus_if.h"
+#include "iicmux_if.h"
+#include "iicmux.h"
+
+/*------------------------------------------------------------------------------
+ * iicbus methods, called by the iicbus functions in iiconf.c.
+ *
+ * All these functions return an IIC adapter-layer error code (because we are
+ * pretending to be a host bridge/i2c controller). Standard errno values
+ * returned from these must be encoded using iic2errno().
+ *----------------------------------------------------------------------------*/
+
+static int
+iicmux_callback(device_t dev, int index, caddr_t data)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+ struct iic_reqbus_data *rd;
+ int err, i;
+
+ /* If it's not one of the operations we know about, bail early. */
+ if (index != IIC_REQUEST_BUS && index != IIC_RELEASE_BUS)
+ return (iic2errno(EOPNOTSUPP));
+
+ /*
+ * Ensure that the data passed to us includes the device_t of the child
+ * bus and device. If missing, someone bypassed iicbus_request_bus()
+ * and called this method directly using the old calling standard. If
+ * present, find the index of the child bus that called us.
+ */
+ rd = (struct iic_reqbus_data *)data;
+ if (!(rd->flags & IIC_REQBUS_DEV))
+ return (iic2errno(EINVAL));
+
+ for (i = 0; i <= sc->maxbus && sc->childdevs[i] != rd->bus; ++i)
+ continue;
+ if (i > sc->maxbus)
+ return (iic2errno(ENOENT));
+
+ /*
+ * If the operation is a release it "cannot fail". Idle the downstream
+ * bus, then release exclusive use of the upstream bus, and we're done.
+ */
+ if (index == IIC_RELEASE_BUS) {
+ if (sc->debugmux > 0) {
+ device_printf(dev, "idle the bus for %s on bus %s\n",
+ device_get_nameunit(rd->dev),
+ device_get_nameunit(rd->bus));
+ }
+ IICMUX_BUS_SELECT(dev, IICMUX_SELECT_IDLE, rd);
+ iicbus_release_bus(sc->busdev, dev);
+ return (IIC_NOERR);
+ }
+
+ if (sc->debugmux > 0) {
+ device_printf(dev, "select bus idx %d for %s on bus %s\n", i,
+ device_get_nameunit(rd->dev), device_get_nameunit(rd->bus));
+ }
+
+ /*
+ * The operation is a request for exclusive use. First we have to
+ * request exclusive use of our upstream bus. If multiple slave devices
+ * from our different child buses attempt to do IO at the same time,
+ * this is what ensures that they don't switch the bus out from under
+ * each other. The first one in proceeds and others wait here (or get an
+ * EWOULDBLOCK return if they're using IIC_DONTWAIT).
+ */
+ if ((err = iicbus_request_bus(sc->busdev, dev, rd->flags)) != 0)
+ return (err); /* Already an IIC error code. */
+
+ /*
+ * Now that we own exclusive use of the upstream bus, connect it to the
+ * downstream bus where the request came from.
+ */
+ if ((err = IICMUX_BUS_SELECT(dev, i, rd)) != 0)
+ iicbus_release_bus(sc->busdev, dev);
+
+ return (err);
+}
+
+static u_int
+iicmux_get_frequency(device_t dev, u_char speed)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (IICBUS_GET_FREQUENCY(sc->busdev, speed));
+}
+
+#ifdef FDT
+static phandle_t
+iicmux_get_node(device_t dev, device_t child)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+ int i;
+
+ for (i = 0; i <= sc->maxbus; ++i) {
+ if (sc->childdevs[i] == child)
+ return (sc->childnodes[i]);
+ }
+ return (0); /* null handle */
+}
+#endif
+
+static int
+iicmux_intr(device_t dev, int event, char *buf)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ /* XXX iicbus_intr() in iiconf.c should return status. */
+
+ iicbus_intr(sc->busdev, event, buf);
+ return (0);
+}
+
+static int
+iicmux_read(device_t dev, char *buf, int len, int *bytes, int last, int delay)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_read(sc->busdev, buf, len, bytes, last, delay));
+}
+
+static int
+iicmux_repeated_start(device_t dev, u_char slave, int timeout)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_repeated_start(sc->busdev, slave, timeout));
+}
+
+static int
+iicmux_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_reset(sc->busdev, speed, addr, oldaddr));
+}
+
+static int
+iicmux_start(device_t dev, u_char slave, int timeout)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_start(sc->busdev, slave, timeout));
+}
+
+static int
+iicmux_stop(device_t dev)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_stop(sc->busdev));
+}
+
+static int
+iicmux_transfer( device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_transfer(sc->busdev, msgs, nmsgs));
+}
+
+static int
+iicmux_write(device_t dev, const char *buf, int len, int *bytes, int timeout)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ return (iicbus_write(sc->busdev, buf, len, bytes, timeout));
+}
+
+/*------------------------------------------------------------------------------
+ * iicmux helper functions, called by hardware-specific drivers.
+ * All these functions return a standard errno value.
+ *----------------------------------------------------------------------------*/
+
+int
+iicmux_add_child(device_t dev, device_t child, int busidx)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+
+ KASSERT(busidx < sc->numbuses,
+ ("iicmux_add_child: bus idx %d too big", busidx));
+ KASSERT(sc->childdevs[busidx] == NULL,
+ ("iicmux_add_child: bus idx %d already added", busidx));
+
+ sc->childdevs[busidx] = child;
+ if (sc->maxbus < busidx)
+ sc->maxbus = busidx;
+
+ return (0);
+}
+
+int
+iicmux_attach(device_t dev, device_t busdev, int numbuses)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+ int i, numadded;
+
+ /*
+ * Init the softc...
+ */
+ KASSERT(numbuses <= IICMUX_MAX_BUSES,
+ ("iicmux_attach: numbuses %d exceeds max %d\n",
+ numbuses, IICMUX_MAX_BUSES));
+
+ sc->dev = dev;
+ sc->busdev = busdev;
+ sc->numbuses = numbuses;
+
+ SYSCTL_ADD_UINT(device_get_sysctl_ctx(sc->dev),
+ SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO,
+ "debugmux", CTLFLAG_RWTUN, &sc->debugmux, 0, "debug mux operations");
+
+ /*
+ * Add children...
+ */
+ numadded = 0;
+
+#ifdef FDT
+ phandle_t child, node, parent;
+ pcell_t idx;
+
+ /*
+ * Find our FDT node. Child nodes within our node will become our
+ * iicbus children.
+ */
+ if((node = ofw_bus_get_node(sc->dev)) == 0) {
+ device_printf(sc->dev, "cannot find FDT node\n");
+ return (ENOENT);
+ }
+
+ /*
+ * First we have to see if there is a child node named "i2c-mux". If it
+ * exists, all children of that node are buses, else all children of the
+ * device node are buses.
+ */
+ if ((parent = ofw_bus_find_child(node, "i2c-mux")) == 0)
+ parent = node;
+
+ /*
+ * Attach the children represented in the device tree.
+ */
+ for (child = OF_child(parent); child != 0; child = OF_peer(child)) {
+ if (OF_getencprop(child, "reg", &idx, sizeof(idx)) == -1) {
+ device_printf(dev,
+ "child bus missing required 'reg' property\n");
+ continue;
+ }
+ if (idx >= sc->numbuses) {
+ device_printf(dev,
+ "child bus 'reg' property %d exceeds the number "
+ "of buses supported by the device (%d)\n",
+ idx, sc->numbuses);
+ continue;
+ }
+ sc->childdevs[idx] = device_add_child(sc->dev, "iicbus", -1);
+ sc->childnodes[idx] = child;
+ if (sc->maxbus < idx)
+ sc->maxbus = idx;
+ ++numadded;
+ }
+#endif /* FDT */
+
+ /*
+ * If we configured anything using FDT data, we're done. Otherwise add
+ * an iicbus child for every downstream bus supported by the mux chip.
+ */
+ if (numadded > 0)
+ return (0);
+
+ for (i = 0; i < sc->numbuses; ++i) {
+ sc->childdevs[i] = device_add_child(sc->dev, "iicbus", -1);
+ if (sc->maxbus < i)
+ sc->maxbus = i;
+ }
+
+ return (0);
+}
+
+int
+iicmux_detach(device_t dev)
+{
+ struct iicmux_softc *sc = device_get_softc(dev);
+ int err, i;
+
+ /* Delete only the children we added in iicmux_add* functions. */
+ for (i = 0; i <= sc->maxbus; ++i) {
+ if (sc->childdevs[i] == NULL)
+ continue;
+ if ((err = device_delete_child(dev, sc->childdevs[i])) != 0)
+ return (err);
+ sc->childdevs[i] = NULL;
+ }
+
+ return (0);
+}
+
+static device_method_t iicmux_methods [] = {
+ /* iicbus_if methods */
+ DEVMETHOD(iicbus_intr, iicmux_intr),
+ DEVMETHOD(iicbus_callback, iicmux_callback),
+ DEVMETHOD(iicbus_repeated_start, iicmux_repeated_start),
+ DEVMETHOD(iicbus_start, iicmux_start),
+ DEVMETHOD(iicbus_stop, iicmux_stop),
+ DEVMETHOD(iicbus_read, iicmux_read),
+ DEVMETHOD(iicbus_write, iicmux_write),
+ DEVMETHOD(iicbus_reset, iicmux_reset),
+ DEVMETHOD(iicbus_transfer, iicmux_transfer),
+ DEVMETHOD(iicbus_get_frequency, iicmux_get_frequency),
+
+#ifdef FDT
+ /* ofwbus_if methods */
+ DEVMETHOD(ofw_bus_get_node, iicmux_get_node),
+#endif
+
+ DEVMETHOD_END
+};
+
+static int
+iicmux_modevent(module_t mod, int type, void *unused)
+{
+ switch (type) {
+ case MOD_LOAD:
+ return 0;
+ case MOD_UNLOAD:
+ return 0;
+ }
+ return EINVAL;
+}
+
+static moduledata_t iicmux_mod = {
+ "iicmux",
+ iicmux_modevent,
+ 0
+};
+
+DEFINE_CLASS_0(iicmux, iicmux_driver, iicmux_methods,
+ sizeof(struct iicmux_softc));
+
+DECLARE_MODULE(iicmux, iicmux_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
+MODULE_VERSION(iicmux, 1);
+
+MODULE_DEPEND(iicmux, iicbus, 1, 1, 1);
diff --git a/sys/dev/iicbus/mux/iicmux.h b/sys/dev/iicbus/mux/iicmux.h
new file mode 100644
index 000000000000..510078dd539d
--- /dev/null
+++ b/sys/dev/iicbus/mux/iicmux.h
@@ -0,0 +1,78 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Ian Lepore <ian@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$
+ */
+
+#ifndef __IICMUX_H
+#define __IICMUX_H
+
+#define IICMUX_MAX_BUSES 16 /* More than any available mux chip. */
+
+/*
+ * IICMUX_SELECT_IDLE instructs the mux hardware driver to do whatever it is
+ * configured to do when the downstream buses are idle. Hardware has varying
+ * capabilities; it may disconnect all downstream buses, or connect a specific
+ * bus, or just leave whatever bus was last used connected. Hardware which is
+ * capable of various choices will have some mechanism to configure the choice
+ * which is handled outside of the iicmux framework.
+ */
+#define IICMUX_SELECT_IDLE (-1)
+
+/*
+ * The iicmux softc; chip drivers should embed one of these as the first member
+ * variable of their own softc struct, and must call iicmux_attach() to
+ * initialize it before calling any other iicmux functions.
+ */
+struct iicmux_softc {
+ device_t busdev; /* Upstream i2c bus (may not be our parent). */
+ device_t dev; /* Ourself. */
+ int maxbus; /* Index of highest populated busdevs slot. */
+ int numbuses; /* Number of buses supported by the chip. */
+ int debugmux; /* Write debug messages when > 0. */
+ device_t childdevs[IICMUX_MAX_BUSES]; /* Child bus instances. */
+#ifdef FDT
+ phandle_t childnodes[IICMUX_MAX_BUSES]; /* Child bus fdt nodes. */
+#endif
+};
+
+DECLARE_CLASS(iicmux_driver);
+
+/*
+ * Helpers to call from attach/detach functions of chip-specific drivers.
+ *
+ * The iicmux_attach() function initializes the core driver's portion of the
+ * softc, and creates child iicbus instances for any children it can identify
+ * using hints and FDT data. If a chip driver does its own device_add_child()
+ * calls to add other downstream buses that participate in the mux switching, it
+ * must call iicmux_add_child() to inform the core driver of the downstream
+ * busidx<->device_t relationship.
+ */
+int iicmux_add_child(device_t dev, device_t child, int busidx);
+int iicmux_attach(device_t dev, device_t busdev, int numbuses);
+int iicmux_detach(device_t dev);
+
+#endif
diff --git a/sys/dev/iicbus/mux/iicmux_if.m b/sys/dev/iicbus/mux/iicmux_if.m
new file mode 100644
index 000000000000..3d89da863748
--- /dev/null
+++ b/sys/dev/iicbus/mux/iicmux_if.m
@@ -0,0 +1,49 @@
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2019 Ian Lepore <ian@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$
+#
+
+#include <sys/bus.h>
+
+INTERFACE iicmux;
+
+HEADER {
+ struct iic_reqbus_data;
+}
+
+#
+# iicmux_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
+# Do whatever the mux hardware requires to connect the downstream
+# bus selected by busidx to the upstream bus. This must return
+# an IIC adapter-layer error code; standard errno values returned
+# from this must be encoded using iic2errno().
+#
+METHOD int bus_select {
+ device_t dev;
+ int busidx;
+ struct iic_reqbus_data *rd;
+};
diff --git a/sys/dev/iicbus/mux/ltc430x.c b/sys/dev/iicbus/mux/ltc430x.c
new file mode 100644
index 000000000000..627f8a21321e
--- /dev/null
+++ b/sys/dev/iicbus/mux/ltc430x.c
@@ -0,0 +1,221 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 Ian Lepore <ian@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.
+ */
+
+#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>
+
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+#include "iicbus_if.h"
+#include "iicmux_if.h"
+
+static struct chip_info {
+ const char *partname;
+ const char *description;
+ int numchannels;
+} chip_infos[] = {
+ {"ltc4305", "LTC4305 I2C Mux", 2},
+ {"ltc4306", "LTC4306 I2C Mux", 4},
+};
+#define CHIP_NONE (-1)
+#define CHIP_LTC4305 0
+#define CHIP_LTC4306 1
+
+#ifdef FDT
+#include <dev/ofw/ofw_bus.h>
+#include <dev/ofw/ofw_bus_subr.h>
+#include <dev/ofw/openfirm.h>
+
+static struct ofw_compat_data compat_data[] = {
+ {"lltc,ltc4305", CHIP_LTC4305},
+ {"lltc,ltc4306", CHIP_LTC4306},
+ {NULL, CHIP_NONE}
+};
+IICBUS_FDT_PNP_INFO(compat_data);
+#endif
+
+#include <dev/iicbus/mux/iicmux.h>
+
+struct ltc430x_softc {
+ struct iicmux_softc mux;
+ bool idle_disconnect;
+};
+
+/*
+ * The datasheet doesn't name the registers, it calls them control register 0-3.
+ */
+#define LTC430X_CTLREG_0 0
+#define LTC430X_CTLREG_1 1
+#define LTC430X_CTLREG_2 2
+#define LTC430X_CTLREG_3 3
+
+static int
+ltc430x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
+{
+ struct ltc430x_softc *sc = device_get_softc(dev);
+ uint8_t busbits;
+
+ /*
+ * 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. The upper bits of control register 3 activate the
+ * downstream buses; bit 7 is the first bus, bit 6 the second, etc.
+ */
+ if (busidx == IICMUX_SELECT_IDLE) {
+ if (sc->idle_disconnect)
+ busbits = 0;
+ else
+ return (0);
+ } else {
+ busbits = 1u << (7 - busidx);
+ }
+
+ /*
+ * We have to add the IIC_RECURSIVE flag because the iicmux core has
+ * already reserved the bus for us, and iicdev_writeto() is going to try
+ * to reserve it again, which is allowed with the recursive flag.
+ */
+ return (iicdev_writeto(dev, LTC430X_CTLREG_3, &busbits, sizeof(busbits),
+ rd->flags | IIC_RECURSIVE));
+}
+
+static int
+ltc430x_find_chiptype(device_t dev)
+{
+#ifdef FDT
+ return (ofw_bus_search_compatible(dev, compat_data)->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(chip_infos); ++i) {
+ if (strcasecmp(type, chip_infos[i].partname) == 0) {
+ return (i);
+ }
+ }
+ }
+ return (CHIP_NONE);
+#endif
+}
+
+static int
+ltc430x_probe(device_t dev)
+{
+ int type;
+
+#ifdef FDT
+ if (!ofw_bus_status_okay(dev))
+ return (ENXIO);
+#endif
+
+ type = ltc430x_find_chiptype(dev);
+ if (type == CHIP_NONE)
+ return (ENXIO);
+
+ device_set_desc(dev, chip_infos[type].description);
+
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+ltc430x_attach(device_t dev)
+{
+ struct ltc430x_softc *sc __unused;
+ int chip, err, numchan;
+
+ sc = device_get_softc(dev);
+
+#ifdef FDT
+ phandle_t node;
+
+ node = ofw_bus_get_node(dev);
+ sc->idle_disconnect = OF_hasprop(node, "i2c-mux-idle-disconnect");
+#endif
+
+ /* We found the chip type when probing, so now it "can't fail". */
+ if ((chip = ltc430x_find_chiptype(dev)) == CHIP_NONE) {
+ device_printf(dev, "impossible: can't identify chip type\n");
+ return (ENXIO);
+ }
+ numchan = chip_infos[chip].numchannels;
+
+ if ((err = iicmux_attach(dev, device_get_parent(dev), numchan)) == 0)
+ bus_generic_attach(dev);
+
+ return (err);
+}
+
+static int
+ltc430x_detach(device_t dev)
+{
+ int err;
+
+ if ((err = iicmux_detach(dev)) != 0)
+ return (err);
+
+ return (0);
+}
+
+static device_method_t ltc430x_methods[] = {
+ /* device methods */
+ DEVMETHOD(device_probe, ltc430x_probe),
+ DEVMETHOD(device_attach, ltc430x_attach),
+ DEVMETHOD(device_detach, ltc430x_detach),
+
+ /* iicmux methods */
+ DEVMETHOD(iicmux_bus_select, ltc430x_bus_select),
+
+ DEVMETHOD_END
+};
+
+static devclass_t ltc430x_devclass;
+
+DEFINE_CLASS_1(ltc430x, ltc430x_driver, ltc430x_methods,
+ sizeof(struct ltc430x_softc), iicmux_driver);
+DRIVER_MODULE(ltc430x, iicbus, ltc430x_driver, ltc430x_devclass, 0, 0);
+
+#ifdef FDT
+DRIVER_MODULE(ofw_iicbus, ltc430x, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0);
+#else
+DRIVER_MODULE(iicbus, ltc430x, iicbus_driver, iicbus_devclass, 0, 0);
+#endif
+
+MODULE_DEPEND(ltc430x, iicmux, 1, 1, 1);
+MODULE_DEPEND(ltc430x, iicbus, 1, 1, 1);
+
diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile
index aec95c7eb3e4..d17f96ba9fbb 100644
--- a/sys/modules/i2c/Makefile
+++ b/sys/modules/i2c/Makefile
@@ -17,6 +17,7 @@ SUBDIR = \
isl \
isl12xx \
jedec_dimm \
+ mux \
nxprtc \
rtc8583 \
s35390a \
diff --git a/sys/modules/i2c/mux/Makefile b/sys/modules/i2c/mux/Makefile
new file mode 100644
index 000000000000..8e228a7a7fef
--- /dev/null
+++ b/sys/modules/i2c/mux/Makefile
@@ -0,0 +1,11 @@
+# $FreeBSD$
+
+SUBDIR = \
+ iicmux \
+ ltc430x \
+
+.if !empty(OPT_FDT)
+SUBDIR+= iic_gpiomux
+.endif
+
+.include <bsd.subdir.mk>
diff --git a/sys/modules/i2c/mux/iic_gpiomux/Makefile b/sys/modules/i2c/mux/iic_gpiomux/Makefile
new file mode 100644
index 000000000000..f1ec74efd4cb
--- /dev/null
+++ b/sys/modules/i2c/mux/iic_gpiomux/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus/mux
+
+KMOD= iic_gpiomux
+SRCS= iic_gpiomux.c
+
+SRCS+= \
+ bus_if.h \
+ device_if.h \
+ gpio_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>
diff --git a/sys/modules/i2c/mux/iicmux/Makefile b/sys/modules/i2c/mux/iicmux/Makefile
new file mode 100644
index 000000000000..1d642bf3e5ae
--- /dev/null
+++ b/sys/modules/i2c/mux/iicmux/Makefile
@@ -0,0 +1,19 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus/mux
+
+KMOD= iicmux
+SRCS= iicmux.c iicmux_if.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>
diff --git a/sys/modules/i2c/mux/ltc430x/Makefile b/sys/modules/i2c/mux/ltc430x/Makefile
new file mode 100644
index 000000000000..9d04982226e4
--- /dev/null
+++ b/sys/modules/i2c/mux/ltc430x/Makefile
@@ -0,0 +1,20 @@
+# $FreeBSD$
+
+.PATH: ${SRCTOP}/sys/dev/iicbus/mux
+
+KMOD= ltc430x
+SRCS= ltc430x.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>