1c0525ab1SAndriy Gapon /*- 2c0525ab1SAndriy Gapon * SPDX-License-Identifier: BSD-2-Clause 3c0525ab1SAndriy Gapon * 497dbd377SBjoern A. Zeeb * Copyright (c) 2019 Ian Lepore <ian@freebsd.org> 597dbd377SBjoern A. Zeeb * Copyright (c) 2020-2021 Andriy Gapon 6c81df1c1SBjoern A. Zeeb * Copyright (c) 2022-2024 Bjoern A. Zeeb 7c0525ab1SAndriy Gapon * 8c0525ab1SAndriy Gapon * Redistribution and use in source and binary forms, with or without 9c0525ab1SAndriy Gapon * modification, are permitted provided that the following conditions 10c0525ab1SAndriy Gapon * are met: 11c0525ab1SAndriy Gapon * 1. Redistributions of source code must retain the above copyright 1297dbd377SBjoern A. Zeeb * notice, this list of conditions and the following disclaimer. 13c0525ab1SAndriy Gapon * 2. Redistributions in binary form must reproduce the above copyright 14c0525ab1SAndriy Gapon * notice, this list of conditions and the following disclaimer in the 15c0525ab1SAndriy Gapon * documentation and/or other materials provided with the distribution. 16c0525ab1SAndriy Gapon * 17c0525ab1SAndriy Gapon * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18c0525ab1SAndriy Gapon * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19c0525ab1SAndriy Gapon * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 2097dbd377SBjoern A. Zeeb * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 2197dbd377SBjoern A. Zeeb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22c0525ab1SAndriy Gapon * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23c0525ab1SAndriy Gapon * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24c0525ab1SAndriy Gapon * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25c0525ab1SAndriy Gapon * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26c0525ab1SAndriy Gapon * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27c0525ab1SAndriy Gapon * SUCH DAMAGE. 28c0525ab1SAndriy Gapon */ 29c0525ab1SAndriy Gapon 30c0525ab1SAndriy Gapon #include <sys/cdefs.h> 31c0525ab1SAndriy Gapon #include "opt_platform.h" 32c0525ab1SAndriy Gapon 33c0525ab1SAndriy Gapon #include <sys/param.h> 34c0525ab1SAndriy Gapon #include <sys/bus.h> 35c0525ab1SAndriy Gapon #include <sys/kernel.h> 36c0525ab1SAndriy Gapon #include <sys/module.h> 37c0525ab1SAndriy Gapon #include <sys/systm.h> 38c0525ab1SAndriy Gapon 39c0525ab1SAndriy Gapon #ifdef FDT 40c0525ab1SAndriy Gapon #include <dev/ofw/ofw_bus.h> 41c0525ab1SAndriy Gapon #include <dev/ofw/ofw_bus_subr.h> 42c0525ab1SAndriy Gapon #include <dev/ofw/openfirm.h> 43c0525ab1SAndriy Gapon #endif 44c0525ab1SAndriy Gapon 45c0525ab1SAndriy Gapon #include <dev/iicbus/iicbus.h> 46c0525ab1SAndriy Gapon #include <dev/iicbus/iiconf.h> 47c0525ab1SAndriy Gapon #include "iicbus_if.h" 48c0525ab1SAndriy Gapon #include "iicmux_if.h" 49c0525ab1SAndriy Gapon #include <dev/iicbus/mux/iicmux.h> 50c0525ab1SAndriy Gapon 5197dbd377SBjoern A. Zeeb enum pca954x_type { 5297dbd377SBjoern A. Zeeb PCA954X_MUX, 5397dbd377SBjoern A. Zeeb PCA954X_SW, 5497dbd377SBjoern A. Zeeb }; 5597dbd377SBjoern A. Zeeb 56c0525ab1SAndriy Gapon struct pca954x_descr { 57c0525ab1SAndriy Gapon const char *partname; 58c0525ab1SAndriy Gapon const char *description; 5997dbd377SBjoern A. Zeeb enum pca954x_type type; 6097dbd377SBjoern A. Zeeb uint8_t numchannels; 6197dbd377SBjoern A. Zeeb uint8_t enable; 6297dbd377SBjoern A. Zeeb }; 6397dbd377SBjoern A. Zeeb 6497dbd377SBjoern A. Zeeb static struct pca954x_descr pca9540_descr = { 6597dbd377SBjoern A. Zeeb .partname = "pca9540", 6697dbd377SBjoern A. Zeeb .description = "PCA9540B I2C Mux", 6797dbd377SBjoern A. Zeeb .type = PCA954X_MUX, 6897dbd377SBjoern A. Zeeb .numchannels = 2, 6997dbd377SBjoern A. Zeeb .enable = 0x04, 7097dbd377SBjoern A. Zeeb }; 7197dbd377SBjoern A. Zeeb 72c81df1c1SBjoern A. Zeeb static struct pca954x_descr pca9546_descr = { 73c81df1c1SBjoern A. Zeeb .partname = "pca9546", 74c81df1c1SBjoern A. Zeeb .description = "PCA9546 I2C Switch", 75c81df1c1SBjoern A. Zeeb .type = PCA954X_SW, 76c81df1c1SBjoern A. Zeeb .numchannels = 4, 77c81df1c1SBjoern A. Zeeb }; 78c81df1c1SBjoern A. Zeeb 7997dbd377SBjoern A. Zeeb static struct pca954x_descr pca9547_descr = { 8097dbd377SBjoern A. Zeeb .partname = "pca9547", 8197dbd377SBjoern A. Zeeb .description = "PCA9547 I2C Mux", 8297dbd377SBjoern A. Zeeb .type = PCA954X_MUX, 8397dbd377SBjoern A. Zeeb .numchannels = 8, 8497dbd377SBjoern A. Zeeb .enable = 0x08, 85c0525ab1SAndriy Gapon }; 86c0525ab1SAndriy Gapon 87c0525ab1SAndriy Gapon static struct pca954x_descr pca9548_descr = { 88c0525ab1SAndriy Gapon .partname = "pca9548", 8997dbd377SBjoern A. Zeeb .description = "PCA9548A I2C Switch", 9097dbd377SBjoern A. Zeeb .type = PCA954X_SW, 91c0525ab1SAndriy Gapon .numchannels = 8, 92c0525ab1SAndriy Gapon }; 93c0525ab1SAndriy Gapon 94c0525ab1SAndriy Gapon #ifdef FDT 95c0525ab1SAndriy Gapon static struct ofw_compat_data compat_data[] = { 9697dbd377SBjoern A. Zeeb { "nxp,pca9540", (uintptr_t)&pca9540_descr }, 97c81df1c1SBjoern A. Zeeb { "nxp,pca9546", (uintptr_t)&pca9546_descr }, 9897dbd377SBjoern A. Zeeb { "nxp,pca9547", (uintptr_t)&pca9547_descr }, 99c0525ab1SAndriy Gapon { "nxp,pca9548", (uintptr_t)&pca9548_descr }, 100c0525ab1SAndriy Gapon { NULL, 0 }, 101c0525ab1SAndriy Gapon }; 102c0525ab1SAndriy Gapon #else 103c0525ab1SAndriy Gapon static struct pca954x_descr *part_descrs[] = { 10497dbd377SBjoern A. Zeeb &pca9540_descr, 105c81df1c1SBjoern A. Zeeb &pca9546_descr, 10697dbd377SBjoern A. Zeeb &pca9547_descr, 107c0525ab1SAndriy Gapon &pca9548_descr, 108c0525ab1SAndriy Gapon }; 109c0525ab1SAndriy Gapon #endif 110c0525ab1SAndriy Gapon 111c0525ab1SAndriy Gapon struct pca954x_softc { 112c0525ab1SAndriy Gapon struct iicmux_softc mux; 11397dbd377SBjoern A. Zeeb const struct pca954x_descr *descr; 114c0525ab1SAndriy Gapon uint8_t addr; 115c0525ab1SAndriy Gapon bool idle_disconnect; 116c0525ab1SAndriy Gapon }; 117c0525ab1SAndriy Gapon 118c0525ab1SAndriy Gapon static int 119c0525ab1SAndriy Gapon pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) 120c0525ab1SAndriy Gapon { 12197dbd377SBjoern A. Zeeb struct pca954x_softc *sc; 122c0525ab1SAndriy Gapon struct iic_msg msg; 123c0525ab1SAndriy Gapon int error; 12497dbd377SBjoern A. Zeeb uint8_t busbits; 12597dbd377SBjoern A. Zeeb 12697dbd377SBjoern A. Zeeb sc = device_get_softc(dev); 127c0525ab1SAndriy Gapon 128c0525ab1SAndriy Gapon /* 129c0525ab1SAndriy Gapon * The iicmux caller ensures busidx is between 0 and the number of buses 130c0525ab1SAndriy Gapon * we passed to iicmux_init_softc(), no need for validation here. If 131c0525ab1SAndriy Gapon * the fdt data has the idle_disconnect property we idle the bus by 132c0525ab1SAndriy Gapon * selecting no downstream buses, otherwise we just leave the current 133c0525ab1SAndriy Gapon * bus active. 134c0525ab1SAndriy Gapon */ 135c0525ab1SAndriy Gapon if (busidx == IICMUX_SELECT_IDLE) { 136c0525ab1SAndriy Gapon if (sc->idle_disconnect) 137c0525ab1SAndriy Gapon busbits = 0; 138c0525ab1SAndriy Gapon else 139c0525ab1SAndriy Gapon return (0); 14097dbd377SBjoern A. Zeeb } else if (sc->descr->type == PCA954X_MUX) { 14197dbd377SBjoern A. Zeeb uint8_t en; 14297dbd377SBjoern A. Zeeb 14397dbd377SBjoern A. Zeeb en = sc->descr->enable; 14497dbd377SBjoern A. Zeeb KASSERT(en > 0 && powerof2(en), ("%s: %s enable %#x " 14597dbd377SBjoern A. Zeeb "invalid\n", __func__, sc->descr->partname, en)); 14697dbd377SBjoern A. Zeeb busbits = en | (busidx & (en - 1)); 14797dbd377SBjoern A. Zeeb } else if (sc->descr->type == PCA954X_SW) { 148c0525ab1SAndriy Gapon busbits = 1u << busidx; 14997dbd377SBjoern A. Zeeb } else { 15097dbd377SBjoern A. Zeeb panic("%s: %s: unsupported type %d\n", 15197dbd377SBjoern A. Zeeb __func__, sc->descr->partname, sc->descr->type); 152c0525ab1SAndriy Gapon } 153c0525ab1SAndriy Gapon 154c0525ab1SAndriy Gapon msg.slave = sc->addr; 155c0525ab1SAndriy Gapon msg.flags = IIC_M_WR; 156c0525ab1SAndriy Gapon msg.len = 1; 157c0525ab1SAndriy Gapon msg.buf = &busbits; 158c0525ab1SAndriy Gapon error = iicbus_transfer(dev, &msg, 1); 159c0525ab1SAndriy Gapon return (error); 160c0525ab1SAndriy Gapon } 161c0525ab1SAndriy Gapon 162c0525ab1SAndriy Gapon static const struct pca954x_descr * 163c0525ab1SAndriy Gapon pca954x_find_chip(device_t dev) 164c0525ab1SAndriy Gapon { 165c0525ab1SAndriy Gapon #ifdef FDT 166c0525ab1SAndriy Gapon const struct ofw_compat_data *compat; 167c0525ab1SAndriy Gapon 16897dbd377SBjoern A. Zeeb if (!ofw_bus_status_okay(dev)) 16997dbd377SBjoern A. Zeeb return (NULL); 17097dbd377SBjoern A. Zeeb 171c0525ab1SAndriy Gapon compat = ofw_bus_search_compatible(dev, compat_data); 172c0525ab1SAndriy Gapon if (compat == NULL) 173c0525ab1SAndriy Gapon return (NULL); 174c0525ab1SAndriy Gapon return ((const struct pca954x_descr *)compat->ocd_data); 175c0525ab1SAndriy Gapon #else 176c0525ab1SAndriy Gapon const char *type; 177c0525ab1SAndriy Gapon int i; 178c0525ab1SAndriy Gapon 179c0525ab1SAndriy Gapon if (resource_string_value(device_get_name(dev), device_get_unit(dev), 180c0525ab1SAndriy Gapon "chip_type", &type) == 0) { 181c0525ab1SAndriy Gapon for (i = 0; i < nitems(part_descrs) - 1; ++i) { 182c0525ab1SAndriy Gapon if (strcasecmp(type, part_descrs[i]->partname) == 0) 183c0525ab1SAndriy Gapon return (part_descrs[i]); 184c0525ab1SAndriy Gapon } 185c0525ab1SAndriy Gapon } 186c0525ab1SAndriy Gapon return (NULL); 187c0525ab1SAndriy Gapon #endif 188c0525ab1SAndriy Gapon } 189c0525ab1SAndriy Gapon 190c0525ab1SAndriy Gapon static int 191c0525ab1SAndriy Gapon pca954x_probe(device_t dev) 192c0525ab1SAndriy Gapon { 193c0525ab1SAndriy Gapon const struct pca954x_descr *descr; 194c0525ab1SAndriy Gapon 195c0525ab1SAndriy Gapon descr = pca954x_find_chip(dev); 196c0525ab1SAndriy Gapon if (descr == NULL) 197c0525ab1SAndriy Gapon return (ENXIO); 198c0525ab1SAndriy Gapon 199c0525ab1SAndriy Gapon device_set_desc(dev, descr->description); 200c0525ab1SAndriy Gapon return (BUS_PROBE_DEFAULT); 201c0525ab1SAndriy Gapon } 202c0525ab1SAndriy Gapon 203c0525ab1SAndriy Gapon static int 204c0525ab1SAndriy Gapon pca954x_attach(device_t dev) 205c0525ab1SAndriy Gapon { 206c0525ab1SAndriy Gapon struct pca954x_softc *sc; 207c0525ab1SAndriy Gapon const struct pca954x_descr *descr; 20897dbd377SBjoern A. Zeeb int error; 209c0525ab1SAndriy Gapon 210c0525ab1SAndriy Gapon sc = device_get_softc(dev); 211c0525ab1SAndriy Gapon sc->addr = iicbus_get_addr(dev); 21297dbd377SBjoern A. Zeeb sc->idle_disconnect = device_has_property(dev, "i2c-mux-idle-disconnect"); 213c0525ab1SAndriy Gapon 21497dbd377SBjoern A. Zeeb sc->descr = descr = pca954x_find_chip(dev); 21597dbd377SBjoern A. Zeeb error = iicmux_attach(dev, device_get_parent(dev), descr->numchannels); 21697dbd377SBjoern A. Zeeb if (error == 0) 217*18250ec6SJohn Baldwin bus_attach_children(dev); 21897dbd377SBjoern A. Zeeb 21997dbd377SBjoern A. Zeeb return (error); 220c0525ab1SAndriy Gapon } 221c0525ab1SAndriy Gapon 222c0525ab1SAndriy Gapon static int 223c0525ab1SAndriy Gapon pca954x_detach(device_t dev) 224c0525ab1SAndriy Gapon { 22597dbd377SBjoern A. Zeeb int error; 226c0525ab1SAndriy Gapon 22797dbd377SBjoern A. Zeeb error = iicmux_detach(dev); 22897dbd377SBjoern A. Zeeb return (error); 229c0525ab1SAndriy Gapon } 230c0525ab1SAndriy Gapon 231c0525ab1SAndriy Gapon static device_method_t pca954x_methods[] = { 232c0525ab1SAndriy Gapon /* device methods */ 233c0525ab1SAndriy Gapon DEVMETHOD(device_probe, pca954x_probe), 234c0525ab1SAndriy Gapon DEVMETHOD(device_attach, pca954x_attach), 235c0525ab1SAndriy Gapon DEVMETHOD(device_detach, pca954x_detach), 236c0525ab1SAndriy Gapon 237c0525ab1SAndriy Gapon /* iicmux methods */ 238c0525ab1SAndriy Gapon DEVMETHOD(iicmux_bus_select, pca954x_bus_select), 239c0525ab1SAndriy Gapon 240c0525ab1SAndriy Gapon DEVMETHOD_END 241c0525ab1SAndriy Gapon }; 242c0525ab1SAndriy Gapon 24397dbd377SBjoern A. Zeeb DEFINE_CLASS_1(pca954x, pca954x_driver, pca954x_methods, 244c0525ab1SAndriy Gapon sizeof(struct pca954x_softc), iicmux_driver); 24597dbd377SBjoern A. Zeeb DRIVER_MODULE(pca954x, iicbus, pca954x_driver, 0, 0); 246c0525ab1SAndriy Gapon 247c0525ab1SAndriy Gapon #ifdef FDT 24897dbd377SBjoern A. Zeeb DRIVER_MODULE(ofw_iicbus, pca954x, ofw_iicbus_driver, 0, 0); 249c0525ab1SAndriy Gapon #else 25097dbd377SBjoern A. Zeeb DRIVER_MODULE(iicbus, pca954x, iicbus_driver, 0, 0); 251c0525ab1SAndriy Gapon #endif 252c0525ab1SAndriy Gapon 25397dbd377SBjoern A. Zeeb MODULE_DEPEND(pca954x, iicmux, 1, 1, 1); 25497dbd377SBjoern A. Zeeb MODULE_DEPEND(pca954x, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 25597dbd377SBjoern A. Zeeb MODULE_VERSION(pca954x, 1); 256c0525ab1SAndriy Gapon 257c0525ab1SAndriy Gapon #ifdef FDT 258c0525ab1SAndriy Gapon IICBUS_FDT_PNP_INFO(compat_data); 259c0525ab1SAndriy Gapon #endif 260