xref: /freebsd/sys/dev/iicbus/mux/pca954x.c (revision 069ac184)
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 #include "opt_platform.h"
32 
33 #include <sys/param.h>
34 #include <sys/bus.h>
35 #include <sys/kernel.h>
36 #include <sys/module.h>
37 #include <sys/systm.h>
38 
39 #ifdef FDT
40 #include <dev/ofw/ofw_bus.h>
41 #include <dev/ofw/ofw_bus_subr.h>
42 #include <dev/ofw/openfirm.h>
43 #endif
44 
45 #include <dev/iicbus/iicbus.h>
46 #include <dev/iicbus/iiconf.h>
47 #include "iicbus_if.h"
48 #include "iicmux_if.h"
49 #include <dev/iicbus/mux/iicmux.h>
50 
51 enum pca954x_type {
52 	PCA954X_MUX,
53 	PCA954X_SW,
54 };
55 
56 struct pca954x_descr {
57 	const char 		*partname;
58 	const char		*description;
59 	enum pca954x_type	type;
60 	uint8_t			numchannels;
61 	uint8_t			enable;
62 };
63 
64 static struct pca954x_descr pca9540_descr = {
65 	.partname = "pca9540",
66 	.description = "PCA9540B I2C Mux",
67 	.type = PCA954X_MUX,
68 	.numchannels = 2,
69 	.enable = 0x04,
70 };
71 
72 static struct pca954x_descr pca9547_descr = {
73 	.partname = "pca9547",
74 	.description = "PCA9547 I2C Mux",
75 	.type = PCA954X_MUX,
76 	.numchannels = 8,
77 	.enable = 0x08,
78 };
79 
80 static struct pca954x_descr pca9548_descr = {
81 	.partname = "pca9548",
82 	.description = "PCA9548A I2C Switch",
83 	.type = PCA954X_SW,
84 	.numchannels = 8,
85 };
86 
87 #ifdef FDT
88 static struct ofw_compat_data compat_data[] = {
89 	{ "nxp,pca9540", (uintptr_t)&pca9540_descr },
90 	{ "nxp,pca9547", (uintptr_t)&pca9547_descr },
91 	{ "nxp,pca9548", (uintptr_t)&pca9548_descr },
92 	{ NULL, 0 },
93 };
94 #else
95 static struct pca954x_descr *part_descrs[] = {
96 	&pca9540_descr,
97 	&pca9547_descr,
98 	&pca9548_descr,
99 };
100 #endif
101 
102 struct pca954x_softc {
103 	struct iicmux_softc mux;
104 	const struct pca954x_descr *descr;
105 	uint8_t addr;
106 	bool idle_disconnect;
107 };
108 
109 static int
110 pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
111 {
112 	struct pca954x_softc *sc;
113 	struct iic_msg msg;
114 	int error;
115 	uint8_t busbits;
116 
117 	sc = device_get_softc(dev);
118 
119 	/*
120 	 * The iicmux caller ensures busidx is between 0 and the number of buses
121 	 * we passed to iicmux_init_softc(), no need for validation here.  If
122 	 * the fdt data has the idle_disconnect property we idle the bus by
123 	 * selecting no downstream buses, otherwise we just leave the current
124 	 * bus active.
125 	 */
126 	if (busidx == IICMUX_SELECT_IDLE) {
127 		if (sc->idle_disconnect)
128 			busbits = 0;
129 		else
130 			return (0);
131 	} else if (sc->descr->type == PCA954X_MUX) {
132 		uint8_t en;
133 
134 		en = sc->descr->enable;
135 		KASSERT(en > 0 && powerof2(en), ("%s: %s enable %#x "
136 		    "invalid\n", __func__, sc->descr->partname, en));
137 		busbits = en | (busidx & (en - 1));
138 	} else if (sc->descr->type == PCA954X_SW) {
139 		busbits = 1u << busidx;
140 	} else {
141 		panic("%s: %s: unsupported type %d\n",
142 		    __func__, sc->descr->partname, sc->descr->type);
143 	}
144 
145 	msg.slave = sc->addr;
146 	msg.flags = IIC_M_WR;
147 	msg.len = 1;
148 	msg.buf = &busbits;
149 	error = iicbus_transfer(dev, &msg, 1);
150 	return (error);
151 }
152 
153 static const struct pca954x_descr *
154 pca954x_find_chip(device_t dev)
155 {
156 #ifdef FDT
157 	const struct ofw_compat_data *compat;
158 
159 	if (!ofw_bus_status_okay(dev))
160 		return (NULL);
161 
162 	compat = ofw_bus_search_compatible(dev, compat_data);
163 	if (compat == NULL)
164 		return (NULL);
165 	return ((const struct pca954x_descr *)compat->ocd_data);
166 #else
167 	const char *type;
168 	int i;
169 
170 	if (resource_string_value(device_get_name(dev), device_get_unit(dev),
171 	    "chip_type", &type) == 0) {
172 		for (i = 0; i < nitems(part_descrs) - 1; ++i) {
173 			if (strcasecmp(type, part_descrs[i]->partname) == 0)
174 				return (part_descrs[i]);
175 		}
176 	}
177 	return (NULL);
178 #endif
179 }
180 
181 static int
182 pca954x_probe(device_t dev)
183 {
184 	const struct pca954x_descr *descr;
185 
186 	descr = pca954x_find_chip(dev);
187 	if (descr == NULL)
188 		return (ENXIO);
189 
190 	device_set_desc(dev, descr->description);
191 	return (BUS_PROBE_DEFAULT);
192 }
193 
194 static int
195 pca954x_attach(device_t dev)
196 {
197 	struct pca954x_softc *sc;
198 	const struct pca954x_descr *descr;
199 	int error;
200 
201 	sc = device_get_softc(dev);
202 	sc->addr = iicbus_get_addr(dev);
203 	sc->idle_disconnect = device_has_property(dev, "i2c-mux-idle-disconnect");
204 
205 	sc->descr = descr = pca954x_find_chip(dev);
206 	error = iicmux_attach(dev, device_get_parent(dev), descr->numchannels);
207 	if (error == 0)
208                 bus_generic_attach(dev);
209 
210 	return (error);
211 }
212 
213 static int
214 pca954x_detach(device_t dev)
215 {
216 	int error;
217 
218 	error = iicmux_detach(dev);
219 	return (error);
220 }
221 
222 static device_method_t pca954x_methods[] = {
223 	/* device methods */
224 	DEVMETHOD(device_probe,			pca954x_probe),
225 	DEVMETHOD(device_attach,		pca954x_attach),
226 	DEVMETHOD(device_detach,		pca954x_detach),
227 
228 	/* iicmux methods */
229 	DEVMETHOD(iicmux_bus_select,		pca954x_bus_select),
230 
231 	DEVMETHOD_END
232 };
233 
234 DEFINE_CLASS_1(pca954x, pca954x_driver, pca954x_methods,
235     sizeof(struct pca954x_softc), iicmux_driver);
236 DRIVER_MODULE(pca954x, iicbus, pca954x_driver, 0, 0);
237 
238 #ifdef FDT
239 DRIVER_MODULE(ofw_iicbus, pca954x, ofw_iicbus_driver, 0, 0);
240 #else
241 DRIVER_MODULE(iicbus, pca954x, iicbus_driver, 0, 0);
242 #endif
243 
244 MODULE_DEPEND(pca954x, iicmux, 1, 1, 1);
245 MODULE_DEPEND(pca954x, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
246 MODULE_VERSION(pca954x, 1);
247 
248 #ifdef FDT
249 IICBUS_FDT_PNP_INFO(compat_data);
250 #endif
251