1422d05daSIan Lepore /*- 2422d05daSIan Lepore * SPDX-License-Identifier: BSD-2-Clause 3422d05daSIan Lepore * 4422d05daSIan Lepore * Copyright (c) 2019 Ian Lepore <ian@freebsd.org> 5422d05daSIan Lepore * 6422d05daSIan Lepore * Redistribution and use in source and binary forms, with or without 7422d05daSIan Lepore * modification, are permitted provided that the following conditions 8422d05daSIan Lepore * are met: 9422d05daSIan Lepore * 1. Redistributions of source code must retain the above copyright 10422d05daSIan Lepore * notice, this list of conditions and the following disclaimer. 11422d05daSIan Lepore * 2. Redistributions in binary form must reproduce the above copyright 12422d05daSIan Lepore * notice, this list of conditions and the following disclaimer in the 13422d05daSIan Lepore * documentation and/or other materials provided with the distribution. 14422d05daSIan Lepore * 15422d05daSIan Lepore * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16422d05daSIan Lepore * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17422d05daSIan Lepore * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18422d05daSIan Lepore * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19422d05daSIan Lepore * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20422d05daSIan Lepore * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21422d05daSIan Lepore * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22422d05daSIan Lepore * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23422d05daSIan Lepore * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24422d05daSIan Lepore * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25422d05daSIan Lepore * SUCH DAMAGE. 26422d05daSIan Lepore */ 27422d05daSIan Lepore 28422d05daSIan Lepore /* 29422d05daSIan Lepore * Driver for i2c bus muxes controlled by one or more gpio pins. 30422d05daSIan Lepore * 31422d05daSIan Lepore * This driver has #ifdef FDT sections in it, as if it supports both fdt and 32422d05daSIan Lepore * hinted attachment, but there is currently no support for hinted attachment. 33422d05daSIan Lepore */ 34422d05daSIan Lepore 35422d05daSIan Lepore #include <sys/cdefs.h> 36422d05daSIan Lepore __FBSDID("$FreeBSD$"); 37422d05daSIan Lepore 38422d05daSIan Lepore #include "opt_platform.h" 39422d05daSIan Lepore 40422d05daSIan Lepore #include <sys/param.h> 41422d05daSIan Lepore #include <sys/bus.h> 42422d05daSIan Lepore #include <sys/gpio.h> 43422d05daSIan Lepore #include <sys/kernel.h> 44422d05daSIan Lepore #include <sys/module.h> 45422d05daSIan Lepore #include <sys/systm.h> 46422d05daSIan Lepore 47422d05daSIan Lepore #include <dev/gpio/gpiobusvar.h> 48422d05daSIan Lepore 49422d05daSIan Lepore #include <dev/iicbus/iicbus.h> 50422d05daSIan Lepore #include <dev/iicbus/mux/iicmux.h> 51422d05daSIan Lepore 52422d05daSIan Lepore #ifdef FDT 53422d05daSIan Lepore #include <dev/ofw/ofw_bus.h> 54422d05daSIan Lepore #include <dev/ofw/ofw_bus_subr.h> 55422d05daSIan Lepore #include <dev/ofw/openfirm.h> 56422d05daSIan Lepore 57422d05daSIan Lepore static struct ofw_compat_data compat_data[] = { 58422d05daSIan Lepore {"i2c-mux-gpio", true}, 59422d05daSIan Lepore {NULL, false} 60422d05daSIan Lepore }; 61519b64e2SMark Johnston OFWBUS_PNP_INFO(compat_data); 62519b64e2SMark Johnston SIMPLEBUS_PNP_INFO(compat_data); 63422d05daSIan Lepore #endif /* FDT */ 64422d05daSIan Lepore 65422d05daSIan Lepore #include <dev/iicbus/iiconf.h> 66422d05daSIan Lepore #include "iicmux.h" 67422d05daSIan Lepore #include "iicmux_if.h" 68422d05daSIan Lepore 69422d05daSIan Lepore struct gpiomux_softc { 70422d05daSIan Lepore struct iicmux_softc mux; 71422d05daSIan Lepore int idleidx; 72422d05daSIan Lepore int numpins; 73422d05daSIan Lepore gpio_pin_t pins[IICMUX_MAX_BUSES]; 74422d05daSIan Lepore }; 75422d05daSIan Lepore 76422d05daSIan Lepore #define IDLE_NOOP (-1) /* When asked to idle the bus, do nothing. */ 77422d05daSIan Lepore 78422d05daSIan Lepore static int 79422d05daSIan Lepore gpiomux_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd) 80422d05daSIan Lepore { 81422d05daSIan Lepore struct gpiomux_softc *sc = device_get_softc(dev); 82422d05daSIan Lepore int i; 83422d05daSIan Lepore 84422d05daSIan Lepore /* 85422d05daSIan Lepore * The iicmux caller ensures busidx is between 0 and the number of buses 86422d05daSIan Lepore * we passed to iicmux_init_softc(), no need for validation here. The 87422d05daSIan Lepore * bits in the index number are transcribed to the state of the pins, 88422d05daSIan Lepore * except when we're asked to idle the bus. In that case, we transcribe 89422d05daSIan Lepore * sc->idleidx to the pins, unless that is IDLE_NOOP (leave the current 90422d05daSIan Lepore * bus selected), in which case we just bail. 91422d05daSIan Lepore */ 92422d05daSIan Lepore if (busidx == IICMUX_SELECT_IDLE) { 93422d05daSIan Lepore if (sc->idleidx == IDLE_NOOP) 94422d05daSIan Lepore return (0); 95422d05daSIan Lepore busidx = sc->idleidx; 96422d05daSIan Lepore } 97422d05daSIan Lepore 98422d05daSIan Lepore for (i = 0; i < sc->numpins; ++i) 99422d05daSIan Lepore gpio_pin_set_active(sc->pins[i], busidx & (1u << i)); 100422d05daSIan Lepore 101422d05daSIan Lepore return (0); 102422d05daSIan Lepore } 103422d05daSIan Lepore 104422d05daSIan Lepore static int 105422d05daSIan Lepore gpiomux_probe(device_t dev) 106422d05daSIan Lepore { 107422d05daSIan Lepore int rv; 108422d05daSIan Lepore 109422d05daSIan Lepore rv = ENXIO; 110422d05daSIan Lepore 111422d05daSIan Lepore #ifdef FDT 112422d05daSIan Lepore if (ofw_bus_status_okay(dev) && 113422d05daSIan Lepore ofw_bus_search_compatible(dev, compat_data)->ocd_data) 114422d05daSIan Lepore rv = BUS_PROBE_DEFAULT; 115422d05daSIan Lepore #endif 116422d05daSIan Lepore 117422d05daSIan Lepore device_set_desc(dev, "I2C GPIO Mux"); 118422d05daSIan Lepore 119422d05daSIan Lepore return (rv); 120422d05daSIan Lepore } 121422d05daSIan Lepore 122d9b35549SIan Lepore static void 123d9b35549SIan Lepore gpiomux_release_pins(struct gpiomux_softc *sc) 124d9b35549SIan Lepore { 125d9b35549SIan Lepore int i; 126d9b35549SIan Lepore 127d9b35549SIan Lepore for (i = 0; i < sc->numpins; ++i) 128d9b35549SIan Lepore gpio_pin_release(sc->pins[i]); 129d9b35549SIan Lepore } 130d9b35549SIan Lepore 131422d05daSIan Lepore static int 132422d05daSIan Lepore gpiomux_attach(device_t dev) 133422d05daSIan Lepore { 134422d05daSIan Lepore struct gpiomux_softc *sc = device_get_softc(dev); 135422d05daSIan Lepore ssize_t len; 136422d05daSIan Lepore device_t busdev; 137422d05daSIan Lepore int err, i, idlebits, numchannels; 138422d05daSIan Lepore pcell_t propval; 139422d05daSIan Lepore phandle_t node; 140422d05daSIan Lepore 141422d05daSIan Lepore node = ofw_bus_get_node(dev); 142422d05daSIan Lepore 143422d05daSIan Lepore /* 144422d05daSIan Lepore * Locate the gpio pin(s) that control the mux hardware. There can be 145422d05daSIan Lepore * multiple pins, but there must be at least one. 146422d05daSIan Lepore */ 147422d05daSIan Lepore for (i = 0; ; ++i) { 148422d05daSIan Lepore err = gpio_pin_get_by_ofw_propidx(dev, node, "mux-gpios", i, 149422d05daSIan Lepore &sc->pins[i]); 150422d05daSIan Lepore if (err != 0) { 151422d05daSIan Lepore break; 152422d05daSIan Lepore } 153422d05daSIan Lepore } 154422d05daSIan Lepore sc->numpins = i; 155422d05daSIan Lepore if (sc->numpins == 0) { 156422d05daSIan Lepore device_printf(dev, "cannot acquire pins listed in mux-gpios\n"); 157d9b35549SIan Lepore if (err == 0) 158d9b35549SIan Lepore err = ENXIO; 159d9b35549SIan Lepore goto errexit; 160422d05daSIan Lepore } 161422d05daSIan Lepore numchannels = 1u << sc->numpins; 162422d05daSIan Lepore if (numchannels > IICMUX_MAX_BUSES) { 163422d05daSIan Lepore device_printf(dev, "too many mux-gpios pins for max %d buses\n", 164422d05daSIan Lepore IICMUX_MAX_BUSES); 165d9b35549SIan Lepore err = EINVAL; 166d9b35549SIan Lepore goto errexit; 167422d05daSIan Lepore } 168422d05daSIan Lepore 169422d05daSIan Lepore /* 170422d05daSIan Lepore * We don't have a parent/child relationship to the upstream bus, we 171422d05daSIan Lepore * have to locate it via the i2c-parent property. Explicitly tell the 172422d05daSIan Lepore * user which upstream we're associated with, since the normal attach 173422d05daSIan Lepore * message is going to mention only our actual parent. 174422d05daSIan Lepore */ 175422d05daSIan Lepore len = OF_getencprop(node, "i2c-parent", &propval, sizeof(propval)); 176422d05daSIan Lepore if (len != sizeof(propval)) { 177422d05daSIan Lepore device_printf(dev, "cannot obtain i2c-parent property\n"); 178d9b35549SIan Lepore err = ENXIO; 179d9b35549SIan Lepore goto errexit; 180422d05daSIan Lepore } 181422d05daSIan Lepore busdev = OF_device_from_xref((phandle_t)propval); 182422d05daSIan Lepore if (busdev == NULL) { 183422d05daSIan Lepore device_printf(dev, 184422d05daSIan Lepore "cannot find device referenced by i2c-parent property\n"); 185d9b35549SIan Lepore err = ENXIO; 186d9b35549SIan Lepore goto errexit; 187422d05daSIan Lepore } 188422d05daSIan Lepore device_printf(dev, "upstream bus is %s\n", device_get_nameunit(busdev)); 189422d05daSIan Lepore 190422d05daSIan Lepore /* 191422d05daSIan Lepore * If there is an idle-state property, that is the value we set the pins 192422d05daSIan Lepore * to when the bus is idle, otherwise idling the bus is a no-op 193422d05daSIan Lepore * (whichever bus was last accessed remains active). 194422d05daSIan Lepore */ 195422d05daSIan Lepore len = OF_getencprop(node, "idle-state", &propval, sizeof(propval)); 196422d05daSIan Lepore if (len == sizeof(propval)) { 197422d05daSIan Lepore if ((int)propval >= numchannels) { 198422d05daSIan Lepore device_printf(dev, 199422d05daSIan Lepore "idle-state property %d exceeds channel count\n", 200422d05daSIan Lepore propval); 201422d05daSIan Lepore } 202422d05daSIan Lepore sc->idleidx = (int)propval; 203422d05daSIan Lepore idlebits = sc->idleidx; 204422d05daSIan Lepore } else { 205422d05daSIan Lepore sc->idleidx = IDLE_NOOP; 206422d05daSIan Lepore idlebits = 0; 207422d05daSIan Lepore } 208422d05daSIan Lepore 209422d05daSIan Lepore /* Preset the mux to the idle state to get things started. */ 210422d05daSIan Lepore for (i = 0; i < sc->numpins; ++i) { 211422d05daSIan Lepore gpio_pin_setflags(sc->pins[i], GPIO_PIN_OUTPUT); 212422d05daSIan Lepore gpio_pin_set_active(sc->pins[i], idlebits & (1u << i)); 213422d05daSIan Lepore } 214422d05daSIan Lepore 215422d05daSIan Lepore /* Init the core driver, have it add our child downstream buses. */ 216422d05daSIan Lepore if ((err = iicmux_attach(dev, busdev, numchannels)) == 0) 217422d05daSIan Lepore bus_generic_attach(dev); 218422d05daSIan Lepore 219d9b35549SIan Lepore errexit: 220d9b35549SIan Lepore 221d9b35549SIan Lepore if (err != 0) 222d9b35549SIan Lepore gpiomux_release_pins(sc); 223d9b35549SIan Lepore 224422d05daSIan Lepore return (err); 225422d05daSIan Lepore } 226422d05daSIan Lepore 227422d05daSIan Lepore static int 228422d05daSIan Lepore gpiomux_detach(device_t dev) 229422d05daSIan Lepore { 230422d05daSIan Lepore struct gpiomux_softc *sc = device_get_softc(dev); 231d9b35549SIan Lepore int err; 232422d05daSIan Lepore 233422d05daSIan Lepore if ((err = iicmux_detach(dev)) != 0) 234422d05daSIan Lepore return (err); 235422d05daSIan Lepore 236d9b35549SIan Lepore gpiomux_release_pins(sc); 237422d05daSIan Lepore 238422d05daSIan Lepore return (0); 239422d05daSIan Lepore } 240422d05daSIan Lepore 241422d05daSIan Lepore static device_method_t gpiomux_methods[] = { 242422d05daSIan Lepore /* device methods */ 243422d05daSIan Lepore DEVMETHOD(device_probe, gpiomux_probe), 244422d05daSIan Lepore DEVMETHOD(device_attach, gpiomux_attach), 245422d05daSIan Lepore DEVMETHOD(device_detach, gpiomux_detach), 246422d05daSIan Lepore 247422d05daSIan Lepore /* iicmux methods */ 248422d05daSIan Lepore DEVMETHOD(iicmux_bus_select, gpiomux_bus_select), 249422d05daSIan Lepore 250422d05daSIan Lepore DEVMETHOD_END 251422d05daSIan Lepore }; 252422d05daSIan Lepore 253422d05daSIan Lepore static devclass_t gpiomux_devclass; 254422d05daSIan Lepore 255422d05daSIan Lepore DEFINE_CLASS_1(iic_gpiomux, iic_gpiomux_driver, gpiomux_methods, 256422d05daSIan Lepore sizeof(struct gpiomux_softc), iicmux_driver); 257422d05daSIan Lepore DRIVER_MODULE(iic_gpiomux, simplebus, iic_gpiomux_driver, gpiomux_devclass, 0, 0); 258422d05daSIan Lepore DRIVER_MODULE(iic_gpiomux, ofw_simplebus, iic_gpiomux_driver, gpiomux_devclass, 0, 0); 259422d05daSIan Lepore 260422d05daSIan Lepore #ifdef FDT 261422d05daSIan Lepore DRIVER_MODULE(ofw_iicbus, iic_gpiomux, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0); 262422d05daSIan Lepore #else 263*676ea8e1SJohn Baldwin DRIVER_MODULE(iicbus, iic_gpiomux, iicbus_driver, 0, 0); 264422d05daSIan Lepore #endif 265422d05daSIan Lepore 266422d05daSIan Lepore MODULE_DEPEND(iic_gpiomux, iicmux, 1, 1, 1); 267422d05daSIan Lepore MODULE_DEPEND(iic_gpiomux, iicbus, 1, 1, 1); 268