From c625d62f0e60556b51446397ecbe54a7bd47c98c Mon Sep 17 00:00:00 2001 From: kettenis Date: Sun, 21 Nov 2021 11:02:21 +0000 Subject: [PATCH] Add iicmux(4), a driver that switches between I2C busses connected to a single I2C controller by using the pin muxing facilities of an SoC. ok visa@ --- sys/dev/fdt/files.fdt | 6 +- sys/dev/fdt/iicmux.c | 271 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 sys/dev/fdt/iicmux.c diff --git a/sys/dev/fdt/files.fdt b/sys/dev/fdt/files.fdt index f93b3fae98b..cfc14e7b7ec 100644 --- a/sys/dev/fdt/files.fdt +++ b/sys/dev/fdt/files.fdt @@ -1,8 +1,12 @@ -# $OpenBSD: files.fdt,v 1.159 2021/11/09 16:16:11 kn Exp $ +# $OpenBSD: files.fdt,v 1.160 2021/11/21 11:02:21 kettenis Exp $ # # Config file and device description for machine-independent FDT code. # Included by ports that need it. +device iicmux: i2cbus +attach iicmux at fdt +file dev/fdt/iicmux.c iicmux + device pinctrl attach pinctrl at fdt file dev/fdt/pinctrl.c pinctrl diff --git a/sys/dev/fdt/iicmux.c b/sys/dev/fdt/iicmux.c new file mode 100644 index 00000000000..e564a366f37 --- /dev/null +++ b/sys/dev/fdt/iicmux.c @@ -0,0 +1,271 @@ +/* $OpenBSD: iicmux.c,v 1.1 2021/11/21 11:02:21 kettenis Exp $ */ +/* + * Copyright (c) 2021 Mark Kettenis + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include + +#include +#include + +#define _I2C_PRIVATE +#include + +#include +#include +#include + +#define IICMUX_MAX_CHANNELS 4 + +struct iicmux_bus { + struct iicmux_softc *pb_sc; + int pb_node; + int pb_channel; + struct i2c_controller pb_ic; + struct i2c_bus pb_ib; + struct device *pb_iic; +}; + +struct iicmux_softc { + struct device sc_dev; + i2c_tag_t sc_tag; + + int sc_node; + int sc_channel; + int sc_nchannel; + struct iicmux_bus sc_bus[IICMUX_MAX_CHANNELS]; + const char *sc_busnames[IICMUX_MAX_CHANNELS]; + struct rwlock sc_lock; + + int sc_enable; +}; + +int iicmux_match(struct device *, void *, void *); +void iicmux_attach(struct device *, struct device *, void *); + +struct cfattach iicmux_ca = { + sizeof(struct iicmux_softc), iicmux_match, iicmux_attach +}; + +struct cfdriver iicmux_cd = { + NULL, "iicmux", DV_DULL +}; + +int iicmux_acquire_bus(void *, int); +void iicmux_release_bus(void *, int); +int iicmux_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t, + void *, size_t, int); +void iicmux_bus_scan(struct device *, struct i2cbus_attach_args *, void *); + +int +iicmux_match(struct device *parent, void *match, void *aux) +{ + struct fdt_attach_args *faa = aux; + + return OF_is_compatible(faa->fa_node, "i2c-mux-pinctrl"); +} + +void +iicmux_attach(struct device *parent, struct device *self, void *aux) +{ + struct iicmux_softc *sc = (struct iicmux_softc *)self; + struct fdt_attach_args *faa = aux; + int node = faa->fa_node; + uint32_t phandle; + char *names; + char *name; + char *end; + int len; + + sc->sc_channel = -1; /* unknown */ + rw_init(&sc->sc_lock, sc->sc_dev.dv_xname); + + phandle = OF_getpropint(node, "i2c-parent", 0); + sc->sc_tag = i2c_byphandle(phandle); + if (sc->sc_tag == NULL) { + printf(": can't find parent bus\n"); + return; + } + + len = OF_getproplen(node, "pinctrl-names"); + if (len <= 0) { + printf(": no channels\n"); + return; + } + + names = malloc(len, M_DEVBUF, M_WAITOK); + OF_getprop(node, "pinctrl-names", names, len); + end = names + len; + name = names; + while (name < end) { + if (strcmp(name, "idle") == 0) + break; + + if (sc->sc_nchannel >= IICMUX_MAX_CHANNELS) { + printf(": too many channels\n"); + free(names, M_DEVBUF, len); + return; + } + + sc->sc_busnames[sc->sc_nchannel] = name; + name += strlen(name) + 1; + sc->sc_nchannel++; + } + + printf("\n"); + + sc->sc_node = node; + for (node = OF_child(node); node; node = OF_peer(node)) { + struct i2cbus_attach_args iba; + struct iicmux_bus *pb; + uint32_t channel; + + channel = OF_getpropint(node, "reg", -1); + if (channel >= sc->sc_nchannel) + continue; + + pb = &sc->sc_bus[channel]; + pb->pb_sc = sc; + pb->pb_node = node; + pb->pb_channel = channel; + pb->pb_ic.ic_cookie = pb; + pb->pb_ic.ic_acquire_bus = iicmux_acquire_bus; + pb->pb_ic.ic_release_bus = iicmux_release_bus; + pb->pb_ic.ic_exec = iicmux_exec; + + /* Configure the child busses. */ + memset(&iba, 0, sizeof(iba)); + iba.iba_name = "iic"; + iba.iba_tag = &pb->pb_ic; + iba.iba_bus_scan = iicmux_bus_scan; + iba.iba_bus_scan_arg = &pb->pb_node; + + config_found(&sc->sc_dev, &iba, iicbus_print); + + pb->pb_ib.ib_node = node; + pb->pb_ib.ib_ic = &pb->pb_ic; + i2c_register(&pb->pb_ib); + } +} + +int +iicmux_set_channel(struct iicmux_softc *sc, int channel, int flags) +{ + int error; + + if (channel < -1 || channel >= sc->sc_nchannel) + return ENXIO; + + if (sc->sc_channel == channel) + return 0; + + error = pinctrl_byname(sc->sc_node, sc->sc_busnames[channel]); + if (error == 0) + sc->sc_channel = channel; + + return error ? EIO : 0; +} + +int +iicmux_acquire_bus(void *cookie, int flags) +{ + struct iicmux_bus *pb = cookie; + struct iicmux_softc *sc = pb->pb_sc; + int rwflags = RW_WRITE; + int error; + + if (flags & I2C_F_POLL) + rwflags |= RW_NOSLEEP; + + error = rw_enter(&sc->sc_lock, rwflags); + if (error) + return error; + + /* Acquire parent bus. */ + error = iic_acquire_bus(sc->sc_tag, flags); + if (error) { + rw_exit_write(&sc->sc_lock); + return error; + } + + error = iicmux_set_channel(sc, pb->pb_channel, flags); + if (error) { + iic_release_bus(sc->sc_tag, flags); + rw_exit_write(&sc->sc_lock); + return error; + } + + return 0; +} + +void +iicmux_release_bus(void *cookie, int flags) +{ + struct iicmux_bus *pb = cookie; + struct iicmux_softc *sc = pb->pb_sc; + + if (pinctrl_byname(sc->sc_node, "idle") == 0) + sc->sc_channel = -1; + + /* Release parent bus. */ + iic_release_bus(sc->sc_tag, flags); + rw_exit_write(&sc->sc_lock); +} + +int +iicmux_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd, + size_t cmdlen, void *buf, size_t buflen, int flags) +{ + struct iicmux_bus *pb = cookie; + struct iicmux_softc *sc = pb->pb_sc; + + rw_assert_wrlock(&sc->sc_lock); + + /* Issue the transaction on the parent bus. */ + return iic_exec(sc->sc_tag, op, addr, cmd, cmdlen, buf, buflen, flags); +} + +void +iicmux_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg) +{ + int iba_node = *(int *)arg; + struct i2c_attach_args ia; + char name[32]; + uint32_t reg[1]; + int node; + + for (node = OF_child(iba_node); node; node = OF_peer(node)) { + memset(name, 0, sizeof(name)); + memset(reg, 0, sizeof(reg)); + + if (OF_getprop(node, "compatible", name, sizeof(name)) == -1) + continue; + if (name[0] == '\0') + continue; + + if (OF_getprop(node, "reg", ®, sizeof(reg)) != sizeof(reg)) + continue; + + memset(&ia, 0, sizeof(ia)); + ia.ia_tag = iba->iba_tag; + ia.ia_addr = bemtoh32(®[0]); + ia.ia_name = name; + ia.ia_cookie = &node; + config_found(self, &ia, iic_print); + } +} -- 2.20.1