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
Cache object: c18ec09ddf267064c07e8085ef27eb62
|