1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2019 Ian Lepore <ian@freebsd.org> 5 * Copyright (c) 2020-2021 Andriy Gapon 6 * Copyright (c) 2022-2024 Bjoern A. Zeeb 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 #include <sys/cdefs.h> 31 #include "opt_platform.h" 32 33 #include <sys/param.h> 34 #include <sys/bus.h> 35 #include <sys/kernel.h> 36 #include <sys/module.h> 37 #include <sys/systm.h> 38 39 #ifdef FDT 40 #include <dev/ofw/ofw_bus.h> 41 #include <dev/ofw/ofw_bus_subr.h> 42 #include <dev/ofw/openfirm.h> 43 #endif 44 45 #include <dev/iicbus/iicbus.h> 46 #include <dev/iicbus/iiconf.h> 47 #include "iicbus_if.h" 48 #include "iicmux_if.h" 49 #include <dev/iicbus/mux/iicmux.h> 50 51 enum pca954x_type { 52 PCA954X_MUX, 53 PCA954X_SW, 54 }; 55 56 struct pca954x_descr { 57 const char *partname; 58 const char *description; 59 enum pca954x_type type; 60 uint8_t numchannels; 61 uint8_t enable; 62 }; 63 64 static struct pca954x_descr pca9540_descr = { 65 .partname = "pca9540", 66 .description = "PCA9540B I2C Mux", 67 .type = PCA954X_MUX, 68 .numchannels = 2, 69 .enable = 0x04, 70 }; 71 72 static struct pca954x_descr pca9546_descr = { 73 .partname = "pca9546", 74 .description = "PCA9546 I2C Switch", 75 .type = PCA954X_SW, 76 .numchannels = 4, 77 }; 78 79 static struct pca954x_descr pca9547_descr = { 80 .partname = "pca9547", 81 .description = "PCA9547 I2C Mux", 82 .type = PCA954X_MUX, 83 .numchannels = 8, 84 .enable = 0x08, 85 }; 86 87 static struct pca954x_descr pca9548_descr = { 88 .partname = "pca9548", 89 .description = "PCA9548A I2C Switch", 90 .type = PCA954X_SW, 91 .numchannels = 8, 92 }; 93 94 #ifdef FDT 95 static struct ofw_compat_data compat_data[] = { 96 { "nxp,pca9540", (uintptr_t)&pca9540_descr }, 97 { "nxp,pca9546", (uintptr_t)&pca9546_descr }, 98 { "nxp,pca9547", (uintptr_t)&pca9547_descr }, 99 { "nxp,pca9548", (uintptr_t)&pca9548_descr }, 100 { NULL, 0 }, 101 }; 102 #else 103 static struct pca954x_descr *part_descrs[] = { 104 &pca9540_descr, 105 &pca9546_descr, 106 &pca9547_descr, 107 &pca9548_descr, 108 }; 109 #endif 110 111 struct pca954x_softc { 112 struct iicmux_softc mux; 113 const struct pca954x_descr *descr; 114 uint8_t addr; 115 bool idle_disconnect; 116 }; 117 118 static int 119 pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) 120 { 121 struct pca954x_softc *sc; 122 struct iic_msg msg; 123 int error; 124 uint8_t busbits; 125 126 sc = device_get_softc(dev); 127 128 /* 129 * The iicmux caller ensures busidx is between 0 and the number of buses 130 * we passed to iicmux_init_softc(), no need for validation here. If 131 * the fdt data has the idle_disconnect property we idle the bus by 132 * selecting no downstream buses, otherwise we just leave the current 133 * bus active. 134 */ 135 if (busidx == IICMUX_SELECT_IDLE) { 136 if (sc->idle_disconnect) 137 busbits = 0; 138 else 139 return (0); 140 } else if (sc->descr->type == PCA954X_MUX) { 141 uint8_t en; 142 143 en = sc->descr->enable; 144 KASSERT(en > 0 && powerof2(en), ("%s: %s enable %#x " 145 "invalid\n", __func__, sc->descr->partname, en)); 146 busbits = en | (busidx & (en - 1)); 147 } else if (sc->descr->type == PCA954X_SW) { 148 busbits = 1u << busidx; 149 } else { 150 panic("%s: %s: unsupported type %d\n", 151 __func__, sc->descr->partname, sc->descr->type); 152 } 153 154 msg.slave = sc->addr; 155 msg.flags = IIC_M_WR; 156 msg.len = 1; 157 msg.buf = &busbits; 158 error = iicbus_transfer(dev, &msg, 1); 159 return (error); 160 } 161 162 static const struct pca954x_descr * 163 pca954x_find_chip(device_t dev) 164 { 165 #ifdef FDT 166 const struct ofw_compat_data *compat; 167 168 if (!ofw_bus_status_okay(dev)) 169 return (NULL); 170 171 compat = ofw_bus_search_compatible(dev, compat_data); 172 if (compat == NULL) 173 return (NULL); 174 return ((const struct pca954x_descr *)compat->ocd_data); 175 #else 176 const char *type; 177 int i; 178 179 if (resource_string_value(device_get_name(dev), device_get_unit(dev), 180 "chip_type", &type) == 0) { 181 for (i = 0; i < nitems(part_descrs) - 1; ++i) { 182 if (strcasecmp(type, part_descrs[i]->partname) == 0) 183 return (part_descrs[i]); 184 } 185 } 186 return (NULL); 187 #endif 188 } 189 190 static int 191 pca954x_probe(device_t dev) 192 { 193 const struct pca954x_descr *descr; 194 195 descr = pca954x_find_chip(dev); 196 if (descr == NULL) 197 return (ENXIO); 198 199 device_set_desc(dev, descr->description); 200 return (BUS_PROBE_DEFAULT); 201 } 202 203 static int 204 pca954x_attach(device_t dev) 205 { 206 struct pca954x_softc *sc; 207 const struct pca954x_descr *descr; 208 int error; 209 210 sc = device_get_softc(dev); 211 sc->addr = iicbus_get_addr(dev); 212 sc->idle_disconnect = device_has_property(dev, "i2c-mux-idle-disconnect"); 213 214 sc->descr = descr = pca954x_find_chip(dev); 215 error = iicmux_attach(dev, device_get_parent(dev), descr->numchannels); 216 if (error == 0) 217 bus_generic_attach(dev); 218 219 return (error); 220 } 221 222 static int 223 pca954x_detach(device_t dev) 224 { 225 int error; 226 227 error = iicmux_detach(dev); 228 return (error); 229 } 230 231 static device_method_t pca954x_methods[] = { 232 /* device methods */ 233 DEVMETHOD(device_probe, pca954x_probe), 234 DEVMETHOD(device_attach, pca954x_attach), 235 DEVMETHOD(device_detach, pca954x_detach), 236 237 /* iicmux methods */ 238 DEVMETHOD(iicmux_bus_select, pca954x_bus_select), 239 240 DEVMETHOD_END 241 }; 242 243 DEFINE_CLASS_1(pca954x, pca954x_driver, pca954x_methods, 244 sizeof(struct pca954x_softc), iicmux_driver); 245 DRIVER_MODULE(pca954x, iicbus, pca954x_driver, 0, 0); 246 247 #ifdef FDT 248 DRIVER_MODULE(ofw_iicbus, pca954x, ofw_iicbus_driver, 0, 0); 249 #else 250 DRIVER_MODULE(iicbus, pca954x, iicbus_driver, 0, 0); 251 #endif 252 253 MODULE_DEPEND(pca954x, iicmux, 1, 1, 1); 254 MODULE_DEPEND(pca954x, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 255 MODULE_VERSION(pca954x, 1); 256 257 #ifdef FDT 258 IICBUS_FDT_PNP_INFO(compat_data); 259 #endif 260