1 1.11 thorpej /* $NetBSD: pcai2cmux.c,v 1.11 2025/09/17 13:56:40 thorpej Exp $ */ 2 1.1 thorpej 3 1.1 thorpej /*- 4 1.1 thorpej * Copyright (c) 2020 The NetBSD Foundation, Inc. 5 1.1 thorpej * All rights reserved. 6 1.1 thorpej * 7 1.1 thorpej * This code is derived from software contributed to The NetBSD Foundation 8 1.1 thorpej * by Jason R. Thorpe. 9 1.1 thorpej * 10 1.1 thorpej * Redistribution and use in source and binary forms, with or without 11 1.1 thorpej * modification, are permitted provided that the following conditions 12 1.1 thorpej * are met: 13 1.1 thorpej * 1. Redistributions of source code must retain the above copyright 14 1.1 thorpej * notice, this list of conditions and the following disclaimer. 15 1.1 thorpej * 2. Redistributions in binary form must reproduce the above copyright 16 1.1 thorpej * notice, this list of conditions and the following disclaimer in the 17 1.1 thorpej * documentation and/or other materials provided with the distribution. 18 1.1 thorpej * 19 1.1 thorpej * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 1.1 thorpej * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 1.1 thorpej * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 1.1 thorpej * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 1.1 thorpej * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 1.1 thorpej * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 1.1 thorpej * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 1.1 thorpej * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 1.1 thorpej * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 1.1 thorpej * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 1.1 thorpej * POSSIBILITY OF SUCH DAMAGE. 30 1.1 thorpej */ 31 1.1 thorpej 32 1.6 jmcneill #if defined(__i386__) || defined(__amd64__) || defined(__aarch64__) 33 1.6 jmcneill #include "acpica.h" 34 1.6 jmcneill #endif 35 1.6 jmcneill 36 1.1 thorpej #include <sys/cdefs.h> 37 1.11 thorpej __KERNEL_RCSID(0, "$NetBSD: pcai2cmux.c,v 1.11 2025/09/17 13:56:40 thorpej Exp $"); 38 1.1 thorpej 39 1.1 thorpej /* 40 1.1 thorpej * Driver for NXP PCA954x / PCA984x I2C switches and multiplexers. 41 1.1 thorpej * 42 1.1 thorpej * There are two flavors of this device: 43 1.1 thorpej * 44 1.1 thorpej * - Multiplexers, which connect the upstream bus to one downstream bus 45 1.1 thorpej * at a time. 46 1.1 thorpej * 47 1.1 thorpej * - Switches, which can connect the upstream bus to one or more downstream 48 1.1 thorpej * busses at a time (which is useful when using an all-call address for 49 1.1 thorpej * a large array of PCA9685 LED controllers, for example). 50 1.1 thorpej * 51 1.1 thorpej * Alas, the device tree bindings don't have anything specifically for 52 1.1 thorpej * switches, so we treat the switch variants as basic multiplexers, 53 1.1 thorpej * only enabling one downstream bus at a time. 54 1.1 thorpej * 55 1.1 thorpej * Note that some versions of these chips also have interrupt mux 56 1.1 thorpej * capability. XXX We do not support this yet. 57 1.1 thorpej */ 58 1.1 thorpej 59 1.1 thorpej #include <sys/param.h> 60 1.1 thorpej #include <sys/systm.h> 61 1.1 thorpej #include <sys/device.h> 62 1.1 thorpej 63 1.1 thorpej #include <dev/fdt/fdtvar.h> 64 1.1 thorpej #include <dev/i2c/i2cmuxvar.h> 65 1.1 thorpej 66 1.6 jmcneill #if NACPICA > 0 67 1.6 jmcneill #include <dev/acpi/acpivar.h> 68 1.6 jmcneill #endif 69 1.6 jmcneill 70 1.1 thorpej /* There are a maximum of 8 busses supported. */ 71 1.1 thorpej #define PCAIICMUX_MAX_BUSSES 8 72 1.1 thorpej 73 1.1 thorpej struct pcaiicmux_type { 74 1.1 thorpej unsigned int nchannels; /* # of downstream channels */ 75 1.1 thorpej uint8_t enable_bit; /* if 0, chip is switch type */ 76 1.1 thorpej }; 77 1.1 thorpej 78 1.1 thorpej static const struct pcaiicmux_type mux2_type = { 79 1.1 thorpej .nchannels = 2, 80 1.1 thorpej .enable_bit = __BIT(2), 81 1.1 thorpej }; 82 1.1 thorpej 83 1.1 thorpej static const struct pcaiicmux_type switch2_type = { 84 1.1 thorpej .nchannels = 2, 85 1.1 thorpej .enable_bit = 0, 86 1.1 thorpej }; 87 1.1 thorpej 88 1.1 thorpej static const struct pcaiicmux_type mux4_type = { 89 1.1 thorpej .nchannels = 4, 90 1.1 thorpej .enable_bit = __BIT(2), 91 1.1 thorpej }; 92 1.1 thorpej 93 1.1 thorpej static const struct pcaiicmux_type switch4_type = { 94 1.1 thorpej .nchannels = 4, 95 1.1 thorpej .enable_bit = 0, 96 1.1 thorpej }; 97 1.1 thorpej 98 1.1 thorpej static const struct pcaiicmux_type mux8_type = { 99 1.1 thorpej .nchannels = 8, 100 1.1 thorpej .enable_bit = __BIT(3), 101 1.1 thorpej }; 102 1.1 thorpej 103 1.1 thorpej static const struct pcaiicmux_type switch8_type = { 104 1.1 thorpej .nchannels = 8, 105 1.1 thorpej .enable_bit = 0, 106 1.1 thorpej }; 107 1.1 thorpej 108 1.1 thorpej static const struct device_compatible_entry compat_data[] = { 109 1.1 thorpej /* PCA9540 - 2 channel i2c mux */ 110 1.1 thorpej { .compat = "nxp,pca9540", 111 1.2 thorpej .data = &mux2_type }, 112 1.1 thorpej 113 1.1 thorpej /* PCA9542 - 2 channel i2c mux with interrupts */ 114 1.1 thorpej { .compat = "nxp,pca9542", 115 1.2 thorpej .data = &mux2_type }, 116 1.1 thorpej 117 1.1 thorpej /* PCA9543 - 2 channel i2c switch with interrupts */ 118 1.1 thorpej { .compat = "nxp,pca9543", 119 1.2 thorpej .data = &switch2_type }, 120 1.1 thorpej 121 1.1 thorpej /* PCA9544 - 4 channel i2c mux with interrupts */ 122 1.1 thorpej { .compat = "nxp,pca9544", 123 1.2 thorpej .data = &mux4_type }, 124 1.1 thorpej 125 1.1 thorpej /* PCA9545 - 4 channel i2c switch with interrupts */ 126 1.1 thorpej { .compat = "nxp,pca9545", 127 1.2 thorpej .data = &switch4_type }, 128 1.1 thorpej 129 1.1 thorpej /* PCA9546 - 4 channel i2c switch */ 130 1.1 thorpej { .compat = "nxp,pca9546", 131 1.2 thorpej .data = &switch4_type }, 132 1.1 thorpej 133 1.1 thorpej /* PCA9547 - 8 channel i2c mux */ 134 1.1 thorpej { .compat = "nxp,pca9547", 135 1.2 thorpej .data = &mux8_type }, 136 1.7 thorpej { .compat = "NXP0002", 137 1.7 thorpej .data = &mux8_type }, 138 1.1 thorpej 139 1.1 thorpej /* PCA9548 - 8 channel i2c switch */ 140 1.1 thorpej { .compat = "nxp,pca9548", 141 1.2 thorpej .data = &switch8_type }, 142 1.1 thorpej 143 1.1 thorpej /* PCA9846 - 4 channel i2c switch */ 144 1.1 thorpej { .compat = "nxp,pca9846", 145 1.2 thorpej .data = &switch4_type }, 146 1.1 thorpej 147 1.1 thorpej /* PCA9847 - 8 channel i2c mux */ 148 1.1 thorpej { .compat = "nxp,pca9847", 149 1.2 thorpej .data = &mux8_type }, 150 1.1 thorpej 151 1.1 thorpej /* PCA9848 - 8 channel i2c switch */ 152 1.1 thorpej { .compat = "nxp,pca9848", 153 1.2 thorpej .data = &switch8_type }, 154 1.1 thorpej 155 1.1 thorpej /* PCA9849 - 4 channel i2c mux */ 156 1.1 thorpej { .compat = "nxp,pca9849", 157 1.2 thorpej .data = &mux4_type }, 158 1.1 thorpej 159 1.8 thorpej DEVICE_COMPAT_EOL 160 1.1 thorpej }; 161 1.1 thorpej 162 1.1 thorpej struct pcaiicmux_softc { 163 1.1 thorpej struct iicmux_softc sc_iicmux; 164 1.1 thorpej 165 1.1 thorpej i2c_addr_t sc_addr; 166 1.1 thorpej int sc_cur_value; 167 1.1 thorpej 168 1.1 thorpej const struct pcaiicmux_type *sc_type; 169 1.1 thorpej struct fdtbus_gpio_pin *sc_reset_gpio; 170 1.1 thorpej 171 1.1 thorpej bool sc_idle_disconnect; 172 1.1 thorpej 173 1.1 thorpej struct pcaiicmux_bus_info { 174 1.1 thorpej uint8_t enable_value; 175 1.1 thorpej } sc_bus_info[PCAIICMUX_MAX_BUSSES]; 176 1.1 thorpej }; 177 1.1 thorpej 178 1.1 thorpej static int 179 1.1 thorpej pcaiicmux_write(struct pcaiicmux_softc * const sc, uint8_t const val, 180 1.1 thorpej int const flags) 181 1.1 thorpej { 182 1.1 thorpej if ((int)val == sc->sc_cur_value) { 183 1.1 thorpej return 0; 184 1.1 thorpej } 185 1.1 thorpej sc->sc_cur_value = (int)val; 186 1.1 thorpej 187 1.1 thorpej int const error = 188 1.1 thorpej iic_smbus_send_byte(sc->sc_iicmux.sc_i2c_parent, sc->sc_addr, val, 189 1.1 thorpej flags & ~I2C_F_SPEED); 190 1.1 thorpej if (error) { 191 1.1 thorpej sc->sc_cur_value = -1; 192 1.1 thorpej } 193 1.1 thorpej 194 1.1 thorpej return error; 195 1.1 thorpej } 196 1.1 thorpej 197 1.1 thorpej /*****************************************************************************/ 198 1.1 thorpej 199 1.1 thorpej static void * 200 1.1 thorpej pcaiicmux_get_mux_info(struct iicmux_softc * const iicmux) 201 1.1 thorpej { 202 1.1 thorpej return container_of(iicmux, struct pcaiicmux_softc, sc_iicmux); 203 1.1 thorpej } 204 1.1 thorpej 205 1.1 thorpej static void * 206 1.1 thorpej pcaiicmux_get_bus_info(struct iicmux_bus * const bus) 207 1.1 thorpej { 208 1.1 thorpej struct iicmux_softc * const iicmux = bus->mux; 209 1.1 thorpej struct pcaiicmux_softc * const sc = iicmux->sc_mux_data; 210 1.1 thorpej bus_addr_t addr; 211 1.1 thorpej int error; 212 1.1 thorpej 213 1.1 thorpej if (bus->busidx >= sc->sc_type->nchannels) { 214 1.1 thorpej aprint_error_dev(iicmux->sc_dev, 215 1.1 thorpej "device tree error: bus index %d out of range\n", 216 1.1 thorpej bus->busidx); 217 1.1 thorpej return NULL; 218 1.1 thorpej } 219 1.1 thorpej 220 1.1 thorpej struct pcaiicmux_bus_info * const bus_info = 221 1.1 thorpej &sc->sc_bus_info[bus->busidx]; 222 1.1 thorpej 223 1.10 thorpej switch (devhandle_type(bus->devhandle)) { 224 1.10 thorpej case DEVHANDLE_TYPE_OF: 225 1.10 thorpej error = fdtbus_get_reg(devhandle_to_of(bus->devhandle), 226 1.10 thorpej 0, &addr, NULL); 227 1.6 jmcneill if (error) { 228 1.6 jmcneill aprint_error_dev(iicmux->sc_dev, 229 1.6 jmcneill "unable to get reg property for bus %d\n", 230 1.6 jmcneill bus->busidx); 231 1.6 jmcneill return NULL; 232 1.6 jmcneill } 233 1.6 jmcneill break; 234 1.6 jmcneill #if NACPICA > 0 235 1.10 thorpej case DEVHANDLE_TYPE_ACPI: { 236 1.6 jmcneill ACPI_INTEGER val; 237 1.6 jmcneill ACPI_STATUS rv; 238 1.10 thorpej rv = acpi_eval_integer(devhandle_to_acpi(bus->devhandle), 239 1.10 thorpej "_ADR", &val); 240 1.6 jmcneill if (ACPI_FAILURE(rv)) { 241 1.6 jmcneill aprint_error_dev(iicmux->sc_dev, 242 1.6 jmcneill "unable to evaluate _ADR for bus %d: %s\n", 243 1.6 jmcneill bus->busidx, AcpiFormatException(rv)); 244 1.6 jmcneill return NULL; 245 1.6 jmcneill } 246 1.6 jmcneill addr = (bus_addr_t)val; 247 1.6 jmcneill } break; 248 1.6 jmcneill #endif 249 1.6 jmcneill default: 250 1.6 jmcneill aprint_error_dev(iicmux->sc_dev, "unsupported handle type\n"); 251 1.1 thorpej return NULL; 252 1.1 thorpej } 253 1.1 thorpej 254 1.1 thorpej if (addr >= sc->sc_type->nchannels) { 255 1.1 thorpej aprint_error_dev(iicmux->sc_dev, 256 1.1 thorpej "device tree error: reg property %llu out of range\n", 257 1.1 thorpej (unsigned long long)addr); 258 1.1 thorpej return NULL; 259 1.1 thorpej } 260 1.1 thorpej 261 1.1 thorpej /* 262 1.1 thorpej * If it's a mux type, the enable value is the channel number 263 1.1 thorpej * (from the reg property) OR'd with the enable bit. 264 1.1 thorpej * 265 1.1 thorpej * If it's a switch type, the enable value is 1 << channel number 266 1.1 thorpej * (from the reg property). 267 1.1 thorpej */ 268 1.1 thorpej if (sc->sc_type->enable_bit) { 269 1.1 thorpej bus_info->enable_value = 270 1.1 thorpej (uint8_t)addr | sc->sc_type->enable_bit; 271 1.1 thorpej } else { 272 1.1 thorpej bus_info->enable_value = 1 << addr; 273 1.1 thorpej } 274 1.1 thorpej 275 1.1 thorpej return bus_info; 276 1.1 thorpej } 277 1.1 thorpej 278 1.1 thorpej static int 279 1.1 thorpej pcaiicmux_acquire_bus(struct iicmux_bus * const bus, int const flags) 280 1.1 thorpej { 281 1.1 thorpej struct pcaiicmux_softc * const sc = bus->mux->sc_mux_data; 282 1.1 thorpej struct pcaiicmux_bus_info * const bus_info = bus->bus_data; 283 1.9 thorpej int error; 284 1.1 thorpej 285 1.9 thorpej error = pcaiicmux_write(sc, bus_info->enable_value, flags); 286 1.9 thorpej if (error) { 287 1.9 thorpej printf("%s: %s: pcaiicmux_write failed (error = %d)\n", 288 1.9 thorpej device_xname(sc->sc_iicmux.sc_dev), __func__, error); 289 1.9 thorpej } 290 1.9 thorpej return error; 291 1.1 thorpej } 292 1.1 thorpej 293 1.1 thorpej static void 294 1.1 thorpej pcaiicmux_release_bus(struct iicmux_bus * const bus, int const flags) 295 1.1 thorpej { 296 1.1 thorpej struct pcaiicmux_softc * const sc = bus->mux->sc_mux_data; 297 1.1 thorpej 298 1.1 thorpej if (sc->sc_idle_disconnect) { 299 1.9 thorpej int error; 300 1.9 thorpej 301 1.9 thorpej error = pcaiicmux_write(sc, 0, flags); 302 1.9 thorpej if (error) { 303 1.9 thorpej printf("%s: %s: pcaiicmux_write failed (error = %d)\n", 304 1.9 thorpej device_xname(sc->sc_iicmux.sc_dev), __func__, 305 1.9 thorpej error); 306 1.9 thorpej } 307 1.1 thorpej } 308 1.1 thorpej } 309 1.1 thorpej 310 1.1 thorpej static const struct iicmux_config pcaiicmux_config = { 311 1.1 thorpej .desc = "PCA954x", 312 1.1 thorpej .get_mux_info = pcaiicmux_get_mux_info, 313 1.1 thorpej .get_bus_info = pcaiicmux_get_bus_info, 314 1.1 thorpej .acquire_bus = pcaiicmux_acquire_bus, 315 1.1 thorpej .release_bus = pcaiicmux_release_bus, 316 1.1 thorpej }; 317 1.1 thorpej 318 1.1 thorpej /*****************************************************************************/ 319 1.1 thorpej 320 1.1 thorpej static const struct pcaiicmux_type * 321 1.1 thorpej pcaiicmux_type_by_compat(const struct i2c_attach_args * const ia) 322 1.1 thorpej { 323 1.1 thorpej const struct pcaiicmux_type *type = NULL; 324 1.1 thorpej const struct device_compatible_entry *dce; 325 1.1 thorpej 326 1.3 thorpej if ((dce = iic_compatible_lookup(ia, compat_data)) != NULL) 327 1.2 thorpej type = dce->data; 328 1.1 thorpej 329 1.1 thorpej return type; 330 1.1 thorpej } 331 1.1 thorpej 332 1.1 thorpej static int 333 1.1 thorpej pcaiicmux_match(device_t parent, cfdata_t cf, void *aux) 334 1.1 thorpej { 335 1.1 thorpej struct i2c_attach_args * const ia = aux; 336 1.1 thorpej int match_result; 337 1.1 thorpej 338 1.1 thorpej if (iic_use_direct_match(ia, cf, compat_data, &match_result)) { 339 1.1 thorpej return match_result; 340 1.1 thorpej } 341 1.4 jmcneill 342 1.1 thorpej /* This device is direct-config only. */ 343 1.1 thorpej 344 1.1 thorpej return 0; 345 1.1 thorpej } 346 1.1 thorpej 347 1.1 thorpej static void 348 1.1 thorpej pcaiicmux_attach(device_t parent, device_t self, void *aux) 349 1.1 thorpej { 350 1.1 thorpej struct pcaiicmux_softc * const sc = device_private(self); 351 1.1 thorpej struct i2c_attach_args * const ia = aux; 352 1.11 thorpej devhandle_t devhandle = device_handle(self); 353 1.1 thorpej int error; 354 1.1 thorpej 355 1.1 thorpej sc->sc_iicmux.sc_dev = self; 356 1.1 thorpej sc->sc_iicmux.sc_config = &pcaiicmux_config; 357 1.1 thorpej sc->sc_iicmux.sc_i2c_parent = ia->ia_tag; 358 1.1 thorpej sc->sc_addr = ia->ia_addr; 359 1.1 thorpej 360 1.1 thorpej sc->sc_type = pcaiicmux_type_by_compat(ia); 361 1.1 thorpej KASSERT(sc->sc_type != NULL); 362 1.1 thorpej 363 1.1 thorpej aprint_naive("\n"); 364 1.1 thorpej aprint_normal(": PCA954x I2C %s\n", 365 1.1 thorpej sc->sc_type->enable_bit ? "mux" : "switch"); 366 1.1 thorpej 367 1.11 thorpej if (devhandle_type(devhandle) == DEVHANDLE_TYPE_OF) { 368 1.11 thorpej const int phandle = devhandle_to_of(devhandle); 369 1.6 jmcneill if (of_hasprop(phandle, "i2c-mux-idle-disconnect")) { 370 1.6 jmcneill sc->sc_idle_disconnect = true; 371 1.6 jmcneill } 372 1.6 jmcneill 373 1.6 jmcneill /* Reset the mux if a reset GPIO is specified. */ 374 1.6 jmcneill sc->sc_reset_gpio = fdtbus_gpio_acquire(phandle, "reset-gpios", 375 1.6 jmcneill GPIO_PIN_OUTPUT); 376 1.6 jmcneill if (sc->sc_reset_gpio) { 377 1.6 jmcneill fdtbus_gpio_write(sc->sc_reset_gpio, 1); 378 1.6 jmcneill delay(10); 379 1.6 jmcneill fdtbus_gpio_write(sc->sc_reset_gpio, 0); 380 1.6 jmcneill delay(10); 381 1.6 jmcneill } 382 1.1 thorpej } 383 1.1 thorpej 384 1.1 thorpej /* Force the mux into a disconnected state. */ 385 1.1 thorpej sc->sc_cur_value = -1; 386 1.1 thorpej error = iic_acquire_bus(ia->ia_tag, 0); 387 1.1 thorpej if (error) { 388 1.9 thorpej aprint_error_dev(self, 389 1.9 thorpej "failed to acquire I2C bus (error = %d)\n", error); 390 1.1 thorpej return; 391 1.1 thorpej } 392 1.1 thorpej error = pcaiicmux_write(sc, 0, 0); 393 1.1 thorpej iic_release_bus(ia->ia_tag, 0); 394 1.1 thorpej if (error) { 395 1.1 thorpej aprint_error_dev(self, 396 1.9 thorpej "failed to set mux to disconnected state (error = %d)\n", 397 1.9 thorpej error); 398 1.1 thorpej return; 399 1.1 thorpej } 400 1.1 thorpej 401 1.1 thorpej iicmux_attach(&sc->sc_iicmux); 402 1.1 thorpej } 403 1.1 thorpej 404 1.1 thorpej CFATTACH_DECL_NEW(pcaiicmux, sizeof(struct pcaiicmux_softc), 405 1.1 thorpej pcaiicmux_match, pcaiicmux_attach, NULL, NULL); 406