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 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 pca9547_descr = { 73 .partname = "pca9547", 74 .description = "PCA9547 I2C Mux", 75 .type = PCA954X_MUX, 76 .numchannels = 8, 77 .enable = 0x08, 78 }; 79 80 static struct pca954x_descr pca9548_descr = { 81 .partname = "pca9548", 82 .description = "PCA9548A I2C Switch", 83 .type = PCA954X_SW, 84 .numchannels = 8, 85 }; 86 87 #ifdef FDT 88 static struct ofw_compat_data compat_data[] = { 89 { "nxp,pca9540", (uintptr_t)&pca9540_descr }, 90 { "nxp,pca9547", (uintptr_t)&pca9547_descr }, 91 { "nxp,pca9548", (uintptr_t)&pca9548_descr }, 92 { NULL, 0 }, 93 }; 94 #else 95 static struct pca954x_descr *part_descrs[] = { 96 &pca9540_descr, 97 &pca9547_descr, 98 &pca9548_descr, 99 }; 100 #endif 101 102 struct pca954x_softc { 103 struct iicmux_softc mux; 104 const struct pca954x_descr *descr; 105 uint8_t addr; 106 bool idle_disconnect; 107 }; 108 109 static int 110 pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) 111 { 112 struct pca954x_softc *sc; 113 struct iic_msg msg; 114 int error; 115 uint8_t busbits; 116 117 sc = device_get_softc(dev); 118 119 /* 120 * The iicmux caller ensures busidx is between 0 and the number of buses 121 * we passed to iicmux_init_softc(), no need for validation here. If 122 * the fdt data has the idle_disconnect property we idle the bus by 123 * selecting no downstream buses, otherwise we just leave the current 124 * bus active. 125 */ 126 if (busidx == IICMUX_SELECT_IDLE) { 127 if (sc->idle_disconnect) 128 busbits = 0; 129 else 130 return (0); 131 } else if (sc->descr->type == PCA954X_MUX) { 132 uint8_t en; 133 134 en = sc->descr->enable; 135 KASSERT(en > 0 && powerof2(en), ("%s: %s enable %#x " 136 "invalid\n", __func__, sc->descr->partname, en)); 137 busbits = en | (busidx & (en - 1)); 138 } else if (sc->descr->type == PCA954X_SW) { 139 busbits = 1u << busidx; 140 } else { 141 panic("%s: %s: unsupported type %d\n", 142 __func__, sc->descr->partname, sc->descr->type); 143 } 144 145 msg.slave = sc->addr; 146 msg.flags = IIC_M_WR; 147 msg.len = 1; 148 msg.buf = &busbits; 149 error = iicbus_transfer(dev, &msg, 1); 150 return (error); 151 } 152 153 static const struct pca954x_descr * 154 pca954x_find_chip(device_t dev) 155 { 156 #ifdef FDT 157 const struct ofw_compat_data *compat; 158 159 if (!ofw_bus_status_okay(dev)) 160 return (NULL); 161 162 compat = ofw_bus_search_compatible(dev, compat_data); 163 if (compat == NULL) 164 return (NULL); 165 return ((const struct pca954x_descr *)compat->ocd_data); 166 #else 167 const char *type; 168 int i; 169 170 if (resource_string_value(device_get_name(dev), device_get_unit(dev), 171 "chip_type", &type) == 0) { 172 for (i = 0; i < nitems(part_descrs) - 1; ++i) { 173 if (strcasecmp(type, part_descrs[i]->partname) == 0) 174 return (part_descrs[i]); 175 } 176 } 177 return (NULL); 178 #endif 179 } 180 181 static int 182 pca954x_probe(device_t dev) 183 { 184 const struct pca954x_descr *descr; 185 186 descr = pca954x_find_chip(dev); 187 if (descr == NULL) 188 return (ENXIO); 189 190 device_set_desc(dev, descr->description); 191 return (BUS_PROBE_DEFAULT); 192 } 193 194 static int 195 pca954x_attach(device_t dev) 196 { 197 struct pca954x_softc *sc; 198 const struct pca954x_descr *descr; 199 int error; 200 201 sc = device_get_softc(dev); 202 sc->addr = iicbus_get_addr(dev); 203 sc->idle_disconnect = device_has_property(dev, "i2c-mux-idle-disconnect"); 204 205 sc->descr = descr = pca954x_find_chip(dev); 206 error = iicmux_attach(dev, device_get_parent(dev), descr->numchannels); 207 if (error == 0) 208 bus_generic_attach(dev); 209 210 return (error); 211 } 212 213 static int 214 pca954x_detach(device_t dev) 215 { 216 int error; 217 218 error = iicmux_detach(dev); 219 return (error); 220 } 221 222 static device_method_t pca954x_methods[] = { 223 /* device methods */ 224 DEVMETHOD(device_probe, pca954x_probe), 225 DEVMETHOD(device_attach, pca954x_attach), 226 DEVMETHOD(device_detach, pca954x_detach), 227 228 /* iicmux methods */ 229 DEVMETHOD(iicmux_bus_select, pca954x_bus_select), 230 231 DEVMETHOD_END 232 }; 233 234 DEFINE_CLASS_1(pca954x, pca954x_driver, pca954x_methods, 235 sizeof(struct pca954x_softc), iicmux_driver); 236 DRIVER_MODULE(pca954x, iicbus, pca954x_driver, 0, 0); 237 238 #ifdef FDT 239 DRIVER_MODULE(ofw_iicbus, pca954x, ofw_iicbus_driver, 0, 0); 240 #else 241 DRIVER_MODULE(iicbus, pca954x, iicbus_driver, 0, 0); 242 #endif 243 244 MODULE_DEPEND(pca954x, iicmux, 1, 1, 1); 245 MODULE_DEPEND(pca954x, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 246 MODULE_VERSION(pca954x, 1); 247 248 #ifdef FDT 249 IICBUS_FDT_PNP_INFO(compat_data); 250 #endif 251