1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 Ian Lepore <ian@freebsd.org>
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
19 * FOR 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 * Driver for i2c bus muxes controlled by one or more gpio pins.
30 *
31 * This driver has #ifdef FDT sections in it, as if it supports both fdt and
32 * hinted attachment, but there is currently no support for hinted attachment.
33 */
34
35 #include <sys/cdefs.h>
36 #include "opt_platform.h"
37
38 #include <sys/param.h>
39 #include <sys/bus.h>
40 #include <sys/gpio.h>
41 #include <sys/kernel.h>
42 #include <sys/module.h>
43 #include <sys/systm.h>
44
45 #include <dev/gpio/gpiobusvar.h>
46
47 #include <dev/iicbus/iicbus.h>
48 #include <dev/iicbus/mux/iicmux.h>
49
50 #ifdef FDT
51 #include <dev/ofw/ofw_bus.h>
52 #include <dev/ofw/ofw_bus_subr.h>
53 #include <dev/ofw/openfirm.h>
54
55 static struct ofw_compat_data compat_data[] = {
56 {"i2c-mux-gpio", true},
57 {NULL, false}
58 };
59 OFWBUS_PNP_INFO(compat_data);
60 SIMPLEBUS_PNP_INFO(compat_data);
61 #endif /* FDT */
62
63 #include <dev/iicbus/iiconf.h>
64 #include "iicmux.h"
65 #include "iicmux_if.h"
66
67 struct gpiomux_softc {
68 struct iicmux_softc mux;
69 int idleidx;
70 int numpins;
71 gpio_pin_t pins[IICMUX_MAX_BUSES];
72 };
73
74 #define IDLE_NOOP (-1) /* When asked to idle the bus, do nothing. */
75
76 static int
gpiomux_bus_select(device_t dev,int busidx,struct iic_reqbus_data * rd)77 gpiomux_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
78 {
79 struct gpiomux_softc *sc = device_get_softc(dev);
80 int i;
81
82 /*
83 * The iicmux caller ensures busidx is between 0 and the number of buses
84 * we passed to iicmux_init_softc(), no need for validation here. The
85 * bits in the index number are transcribed to the state of the pins,
86 * except when we're asked to idle the bus. In that case, we transcribe
87 * sc->idleidx to the pins, unless that is IDLE_NOOP (leave the current
88 * bus selected), in which case we just bail.
89 */
90 if (busidx == IICMUX_SELECT_IDLE) {
91 if (sc->idleidx == IDLE_NOOP)
92 return (0);
93 busidx = sc->idleidx;
94 }
95
96 for (i = 0; i < sc->numpins; ++i)
97 gpio_pin_set_active(sc->pins[i], busidx & (1u << i));
98
99 return (0);
100 }
101
102 static int
gpiomux_probe(device_t dev)103 gpiomux_probe(device_t dev)
104 {
105 int rv;
106
107 rv = ENXIO;
108
109 #ifdef FDT
110 if (ofw_bus_status_okay(dev) &&
111 ofw_bus_search_compatible(dev, compat_data)->ocd_data)
112 rv = BUS_PROBE_DEFAULT;
113 #endif
114
115 device_set_desc(dev, "I2C GPIO Mux");
116
117 return (rv);
118 }
119
120 static void
gpiomux_release_pins(struct gpiomux_softc * sc)121 gpiomux_release_pins(struct gpiomux_softc *sc)
122 {
123 int i;
124
125 for (i = 0; i < sc->numpins; ++i)
126 gpio_pin_release(sc->pins[i]);
127 }
128
129 static int
gpiomux_attach(device_t dev)130 gpiomux_attach(device_t dev)
131 {
132 struct gpiomux_softc *sc = device_get_softc(dev);
133 ssize_t len;
134 device_t busdev;
135 int err, i, idlebits, numchannels;
136 pcell_t propval;
137 phandle_t node;
138
139 node = ofw_bus_get_node(dev);
140
141 /*
142 * Locate the gpio pin(s) that control the mux hardware. There can be
143 * multiple pins, but there must be at least one.
144 */
145 for (i = 0; ; ++i) {
146 err = gpio_pin_get_by_ofw_propidx(dev, node, "mux-gpios", i,
147 &sc->pins[i]);
148 if (err != 0) {
149 break;
150 }
151 }
152 sc->numpins = i;
153 if (sc->numpins == 0) {
154 device_printf(dev, "cannot acquire pins listed in mux-gpios\n");
155 if (err == 0)
156 err = ENXIO;
157 goto errexit;
158 }
159 numchannels = 1u << sc->numpins;
160 if (numchannels > IICMUX_MAX_BUSES) {
161 device_printf(dev, "too many mux-gpios pins for max %d buses\n",
162 IICMUX_MAX_BUSES);
163 err = EINVAL;
164 goto errexit;
165 }
166
167 /*
168 * We don't have a parent/child relationship to the upstream bus, we
169 * have to locate it via the i2c-parent property. Explicitly tell the
170 * user which upstream we're associated with, since the normal attach
171 * message is going to mention only our actual parent.
172 */
173 len = OF_getencprop(node, "i2c-parent", &propval, sizeof(propval));
174 if (len != sizeof(propval)) {
175 device_printf(dev, "cannot obtain i2c-parent property\n");
176 err = ENXIO;
177 goto errexit;
178 }
179 busdev = OF_device_from_xref((phandle_t)propval);
180 if (busdev == NULL) {
181 device_printf(dev,
182 "cannot find device referenced by i2c-parent property\n");
183 err = ENXIO;
184 goto errexit;
185 }
186 device_printf(dev, "upstream bus is %s\n", device_get_nameunit(busdev));
187
188 /*
189 * If there is an idle-state property, that is the value we set the pins
190 * to when the bus is idle, otherwise idling the bus is a no-op
191 * (whichever bus was last accessed remains active).
192 */
193 len = OF_getencprop(node, "idle-state", &propval, sizeof(propval));
194 if (len == sizeof(propval)) {
195 if ((int)propval >= numchannels) {
196 device_printf(dev,
197 "idle-state property %d exceeds channel count\n",
198 propval);
199 }
200 sc->idleidx = (int)propval;
201 idlebits = sc->idleidx;
202 } else {
203 sc->idleidx = IDLE_NOOP;
204 idlebits = 0;
205 }
206
207 /* Preset the mux to the idle state to get things started. */
208 for (i = 0; i < sc->numpins; ++i) {
209 gpio_pin_setflags(sc->pins[i], GPIO_PIN_OUTPUT);
210 gpio_pin_set_active(sc->pins[i], idlebits & (1u << i));
211 }
212
213 /* Init the core driver, have it add our child downstream buses. */
214 if ((err = iicmux_attach(dev, busdev, numchannels)) == 0)
215 bus_attach_children(dev);
216
217 errexit:
218
219 if (err != 0)
220 gpiomux_release_pins(sc);
221
222 return (err);
223 }
224
225 static int
gpiomux_detach(device_t dev)226 gpiomux_detach(device_t dev)
227 {
228 struct gpiomux_softc *sc = device_get_softc(dev);
229 int err;
230
231 if ((err = iicmux_detach(dev)) != 0)
232 return (err);
233
234 gpiomux_release_pins(sc);
235
236 return (0);
237 }
238
239 static device_method_t gpiomux_methods[] = {
240 /* device methods */
241 DEVMETHOD(device_probe, gpiomux_probe),
242 DEVMETHOD(device_attach, gpiomux_attach),
243 DEVMETHOD(device_detach, gpiomux_detach),
244
245 /* iicmux methods */
246 DEVMETHOD(iicmux_bus_select, gpiomux_bus_select),
247
248 DEVMETHOD_END
249 };
250
251 DEFINE_CLASS_1(iic_gpiomux, iic_gpiomux_driver, gpiomux_methods,
252 sizeof(struct gpiomux_softc), iicmux_driver);
253 DRIVER_MODULE(iic_gpiomux, simplebus, iic_gpiomux_driver, 0, 0);
254 DRIVER_MODULE(iic_gpiomux, ofw_simplebus, iic_gpiomux_driver, 0, 0);
255
256 #ifdef FDT
257 DRIVER_MODULE(ofw_iicbus, iic_gpiomux, ofw_iicbus_driver, 0, 0);
258 #else
259 DRIVER_MODULE(iicbus, iic_gpiomux, iicbus_driver, 0, 0);
260 #endif
261
262 MODULE_DEPEND(iic_gpiomux, iicmux, 1, 1, 1);
263 MODULE_DEPEND(iic_gpiomux, iicbus, 1, 1, 1);
264