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