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