xref: /freebsd-src/sys/dev/iicbus/mux/pca954x.c (revision 18250ec6c089c0c50cbd9fd87d78e03ff89916df)
1c0525ab1SAndriy Gapon /*-
2c0525ab1SAndriy Gapon  * SPDX-License-Identifier: BSD-2-Clause
3c0525ab1SAndriy Gapon  *
497dbd377SBjoern A. Zeeb  * Copyright (c) 2019 Ian Lepore <ian@freebsd.org>
597dbd377SBjoern A. Zeeb  * Copyright (c) 2020-2021 Andriy Gapon
6c81df1c1SBjoern A. Zeeb  * Copyright (c) 2022-2024 Bjoern A. Zeeb
7c0525ab1SAndriy Gapon  *
8c0525ab1SAndriy Gapon  * Redistribution and use in source and binary forms, with or without
9c0525ab1SAndriy Gapon  * modification, are permitted provided that the following conditions
10c0525ab1SAndriy Gapon  * are met:
11c0525ab1SAndriy Gapon  * 1. Redistributions of source code must retain the above copyright
1297dbd377SBjoern A. Zeeb  *    notice, this list of conditions and the following disclaimer.
13c0525ab1SAndriy Gapon  * 2. Redistributions in binary form must reproduce the above copyright
14c0525ab1SAndriy Gapon  *    notice, this list of conditions and the following disclaimer in the
15c0525ab1SAndriy Gapon  *    documentation and/or other materials provided with the distribution.
16c0525ab1SAndriy Gapon  *
17c0525ab1SAndriy Gapon  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18c0525ab1SAndriy Gapon  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19c0525ab1SAndriy Gapon  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2097dbd377SBjoern A. Zeeb  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2197dbd377SBjoern A. Zeeb  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22c0525ab1SAndriy Gapon  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23c0525ab1SAndriy Gapon  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24c0525ab1SAndriy Gapon  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25c0525ab1SAndriy Gapon  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26c0525ab1SAndriy Gapon  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27c0525ab1SAndriy Gapon  * SUCH DAMAGE.
28c0525ab1SAndriy Gapon  */
29c0525ab1SAndriy Gapon 
30c0525ab1SAndriy Gapon #include <sys/cdefs.h>
31c0525ab1SAndriy Gapon #include "opt_platform.h"
32c0525ab1SAndriy Gapon 
33c0525ab1SAndriy Gapon #include <sys/param.h>
34c0525ab1SAndriy Gapon #include <sys/bus.h>
35c0525ab1SAndriy Gapon #include <sys/kernel.h>
36c0525ab1SAndriy Gapon #include <sys/module.h>
37c0525ab1SAndriy Gapon #include <sys/systm.h>
38c0525ab1SAndriy Gapon 
39c0525ab1SAndriy Gapon #ifdef FDT
40c0525ab1SAndriy Gapon #include <dev/ofw/ofw_bus.h>
41c0525ab1SAndriy Gapon #include <dev/ofw/ofw_bus_subr.h>
42c0525ab1SAndriy Gapon #include <dev/ofw/openfirm.h>
43c0525ab1SAndriy Gapon #endif
44c0525ab1SAndriy Gapon 
45c0525ab1SAndriy Gapon #include <dev/iicbus/iicbus.h>
46c0525ab1SAndriy Gapon #include <dev/iicbus/iiconf.h>
47c0525ab1SAndriy Gapon #include "iicbus_if.h"
48c0525ab1SAndriy Gapon #include "iicmux_if.h"
49c0525ab1SAndriy Gapon #include <dev/iicbus/mux/iicmux.h>
50c0525ab1SAndriy Gapon 
5197dbd377SBjoern A. Zeeb enum pca954x_type {
5297dbd377SBjoern A. Zeeb 	PCA954X_MUX,
5397dbd377SBjoern A. Zeeb 	PCA954X_SW,
5497dbd377SBjoern A. Zeeb };
5597dbd377SBjoern A. Zeeb 
56c0525ab1SAndriy Gapon struct pca954x_descr {
57c0525ab1SAndriy Gapon 	const char 		*partname;
58c0525ab1SAndriy Gapon 	const char		*description;
5997dbd377SBjoern A. Zeeb 	enum pca954x_type	type;
6097dbd377SBjoern A. Zeeb 	uint8_t			numchannels;
6197dbd377SBjoern A. Zeeb 	uint8_t			enable;
6297dbd377SBjoern A. Zeeb };
6397dbd377SBjoern A. Zeeb 
6497dbd377SBjoern A. Zeeb static struct pca954x_descr pca9540_descr = {
6597dbd377SBjoern A. Zeeb 	.partname = "pca9540",
6697dbd377SBjoern A. Zeeb 	.description = "PCA9540B I2C Mux",
6797dbd377SBjoern A. Zeeb 	.type = PCA954X_MUX,
6897dbd377SBjoern A. Zeeb 	.numchannels = 2,
6997dbd377SBjoern A. Zeeb 	.enable = 0x04,
7097dbd377SBjoern A. Zeeb };
7197dbd377SBjoern A. Zeeb 
72c81df1c1SBjoern A. Zeeb static struct pca954x_descr pca9546_descr = {
73c81df1c1SBjoern A. Zeeb 	.partname = "pca9546",
74c81df1c1SBjoern A. Zeeb 	.description = "PCA9546 I2C Switch",
75c81df1c1SBjoern A. Zeeb 	.type = PCA954X_SW,
76c81df1c1SBjoern A. Zeeb 	.numchannels = 4,
77c81df1c1SBjoern A. Zeeb };
78c81df1c1SBjoern A. Zeeb 
7997dbd377SBjoern A. Zeeb static struct pca954x_descr pca9547_descr = {
8097dbd377SBjoern A. Zeeb 	.partname = "pca9547",
8197dbd377SBjoern A. Zeeb 	.description = "PCA9547 I2C Mux",
8297dbd377SBjoern A. Zeeb 	.type = PCA954X_MUX,
8397dbd377SBjoern A. Zeeb 	.numchannels = 8,
8497dbd377SBjoern A. Zeeb 	.enable = 0x08,
85c0525ab1SAndriy Gapon };
86c0525ab1SAndriy Gapon 
87c0525ab1SAndriy Gapon static struct pca954x_descr pca9548_descr = {
88c0525ab1SAndriy Gapon 	.partname = "pca9548",
8997dbd377SBjoern A. Zeeb 	.description = "PCA9548A I2C Switch",
9097dbd377SBjoern A. Zeeb 	.type = PCA954X_SW,
91c0525ab1SAndriy Gapon 	.numchannels = 8,
92c0525ab1SAndriy Gapon };
93c0525ab1SAndriy Gapon 
94c0525ab1SAndriy Gapon #ifdef FDT
95c0525ab1SAndriy Gapon static struct ofw_compat_data compat_data[] = {
9697dbd377SBjoern A. Zeeb 	{ "nxp,pca9540", (uintptr_t)&pca9540_descr },
97c81df1c1SBjoern A. Zeeb 	{ "nxp,pca9546", (uintptr_t)&pca9546_descr },
9897dbd377SBjoern A. Zeeb 	{ "nxp,pca9547", (uintptr_t)&pca9547_descr },
99c0525ab1SAndriy Gapon 	{ "nxp,pca9548", (uintptr_t)&pca9548_descr },
100c0525ab1SAndriy Gapon 	{ NULL, 0 },
101c0525ab1SAndriy Gapon };
102c0525ab1SAndriy Gapon #else
103c0525ab1SAndriy Gapon static struct pca954x_descr *part_descrs[] = {
10497dbd377SBjoern A. Zeeb 	&pca9540_descr,
105c81df1c1SBjoern A. Zeeb 	&pca9546_descr,
10697dbd377SBjoern A. Zeeb 	&pca9547_descr,
107c0525ab1SAndriy Gapon 	&pca9548_descr,
108c0525ab1SAndriy Gapon };
109c0525ab1SAndriy Gapon #endif
110c0525ab1SAndriy Gapon 
111c0525ab1SAndriy Gapon struct pca954x_softc {
112c0525ab1SAndriy Gapon 	struct iicmux_softc mux;
11397dbd377SBjoern A. Zeeb 	const struct pca954x_descr *descr;
114c0525ab1SAndriy Gapon 	uint8_t addr;
115c0525ab1SAndriy Gapon 	bool idle_disconnect;
116c0525ab1SAndriy Gapon };
117c0525ab1SAndriy Gapon 
118c0525ab1SAndriy Gapon static int
119c0525ab1SAndriy Gapon pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
120c0525ab1SAndriy Gapon {
12197dbd377SBjoern A. Zeeb 	struct pca954x_softc *sc;
122c0525ab1SAndriy Gapon 	struct iic_msg msg;
123c0525ab1SAndriy Gapon 	int error;
12497dbd377SBjoern A. Zeeb 	uint8_t busbits;
12597dbd377SBjoern A. Zeeb 
12697dbd377SBjoern A. Zeeb 	sc = device_get_softc(dev);
127c0525ab1SAndriy Gapon 
128c0525ab1SAndriy Gapon 	/*
129c0525ab1SAndriy Gapon 	 * The iicmux caller ensures busidx is between 0 and the number of buses
130c0525ab1SAndriy Gapon 	 * we passed to iicmux_init_softc(), no need for validation here.  If
131c0525ab1SAndriy Gapon 	 * the fdt data has the idle_disconnect property we idle the bus by
132c0525ab1SAndriy Gapon 	 * selecting no downstream buses, otherwise we just leave the current
133c0525ab1SAndriy Gapon 	 * bus active.
134c0525ab1SAndriy Gapon 	 */
135c0525ab1SAndriy Gapon 	if (busidx == IICMUX_SELECT_IDLE) {
136c0525ab1SAndriy Gapon 		if (sc->idle_disconnect)
137c0525ab1SAndriy Gapon 			busbits = 0;
138c0525ab1SAndriy Gapon 		else
139c0525ab1SAndriy Gapon 			return (0);
14097dbd377SBjoern A. Zeeb 	} else if (sc->descr->type == PCA954X_MUX) {
14197dbd377SBjoern A. Zeeb 		uint8_t en;
14297dbd377SBjoern A. Zeeb 
14397dbd377SBjoern A. Zeeb 		en = sc->descr->enable;
14497dbd377SBjoern A. Zeeb 		KASSERT(en > 0 && powerof2(en), ("%s: %s enable %#x "
14597dbd377SBjoern A. Zeeb 		    "invalid\n", __func__, sc->descr->partname, en));
14697dbd377SBjoern A. Zeeb 		busbits = en | (busidx & (en - 1));
14797dbd377SBjoern A. Zeeb 	} else if (sc->descr->type == PCA954X_SW) {
148c0525ab1SAndriy Gapon 		busbits = 1u << busidx;
14997dbd377SBjoern A. Zeeb 	} else {
15097dbd377SBjoern A. Zeeb 		panic("%s: %s: unsupported type %d\n",
15197dbd377SBjoern A. Zeeb 		    __func__, sc->descr->partname, sc->descr->type);
152c0525ab1SAndriy Gapon 	}
153c0525ab1SAndriy Gapon 
154c0525ab1SAndriy Gapon 	msg.slave = sc->addr;
155c0525ab1SAndriy Gapon 	msg.flags = IIC_M_WR;
156c0525ab1SAndriy Gapon 	msg.len = 1;
157c0525ab1SAndriy Gapon 	msg.buf = &busbits;
158c0525ab1SAndriy Gapon 	error = iicbus_transfer(dev, &msg, 1);
159c0525ab1SAndriy Gapon 	return (error);
160c0525ab1SAndriy Gapon }
161c0525ab1SAndriy Gapon 
162c0525ab1SAndriy Gapon static const struct pca954x_descr *
163c0525ab1SAndriy Gapon pca954x_find_chip(device_t dev)
164c0525ab1SAndriy Gapon {
165c0525ab1SAndriy Gapon #ifdef FDT
166c0525ab1SAndriy Gapon 	const struct ofw_compat_data *compat;
167c0525ab1SAndriy Gapon 
16897dbd377SBjoern A. Zeeb 	if (!ofw_bus_status_okay(dev))
16997dbd377SBjoern A. Zeeb 		return (NULL);
17097dbd377SBjoern A. Zeeb 
171c0525ab1SAndriy Gapon 	compat = ofw_bus_search_compatible(dev, compat_data);
172c0525ab1SAndriy Gapon 	if (compat == NULL)
173c0525ab1SAndriy Gapon 		return (NULL);
174c0525ab1SAndriy Gapon 	return ((const struct pca954x_descr *)compat->ocd_data);
175c0525ab1SAndriy Gapon #else
176c0525ab1SAndriy Gapon 	const char *type;
177c0525ab1SAndriy Gapon 	int i;
178c0525ab1SAndriy Gapon 
179c0525ab1SAndriy Gapon 	if (resource_string_value(device_get_name(dev), device_get_unit(dev),
180c0525ab1SAndriy Gapon 	    "chip_type", &type) == 0) {
181c0525ab1SAndriy Gapon 		for (i = 0; i < nitems(part_descrs) - 1; ++i) {
182c0525ab1SAndriy Gapon 			if (strcasecmp(type, part_descrs[i]->partname) == 0)
183c0525ab1SAndriy Gapon 				return (part_descrs[i]);
184c0525ab1SAndriy Gapon 		}
185c0525ab1SAndriy Gapon 	}
186c0525ab1SAndriy Gapon 	return (NULL);
187c0525ab1SAndriy Gapon #endif
188c0525ab1SAndriy Gapon }
189c0525ab1SAndriy Gapon 
190c0525ab1SAndriy Gapon static int
191c0525ab1SAndriy Gapon pca954x_probe(device_t dev)
192c0525ab1SAndriy Gapon {
193c0525ab1SAndriy Gapon 	const struct pca954x_descr *descr;
194c0525ab1SAndriy Gapon 
195c0525ab1SAndriy Gapon 	descr = pca954x_find_chip(dev);
196c0525ab1SAndriy Gapon 	if (descr == NULL)
197c0525ab1SAndriy Gapon 		return (ENXIO);
198c0525ab1SAndriy Gapon 
199c0525ab1SAndriy Gapon 	device_set_desc(dev, descr->description);
200c0525ab1SAndriy Gapon 	return (BUS_PROBE_DEFAULT);
201c0525ab1SAndriy Gapon }
202c0525ab1SAndriy Gapon 
203c0525ab1SAndriy Gapon static int
204c0525ab1SAndriy Gapon pca954x_attach(device_t dev)
205c0525ab1SAndriy Gapon {
206c0525ab1SAndriy Gapon 	struct pca954x_softc *sc;
207c0525ab1SAndriy Gapon 	const struct pca954x_descr *descr;
20897dbd377SBjoern A. Zeeb 	int error;
209c0525ab1SAndriy Gapon 
210c0525ab1SAndriy Gapon 	sc = device_get_softc(dev);
211c0525ab1SAndriy Gapon 	sc->addr = iicbus_get_addr(dev);
21297dbd377SBjoern A. Zeeb 	sc->idle_disconnect = device_has_property(dev, "i2c-mux-idle-disconnect");
213c0525ab1SAndriy Gapon 
21497dbd377SBjoern A. Zeeb 	sc->descr = descr = pca954x_find_chip(dev);
21597dbd377SBjoern A. Zeeb 	error = iicmux_attach(dev, device_get_parent(dev), descr->numchannels);
21697dbd377SBjoern A. Zeeb 	if (error == 0)
217*18250ec6SJohn Baldwin                 bus_attach_children(dev);
21897dbd377SBjoern A. Zeeb 
21997dbd377SBjoern A. Zeeb 	return (error);
220c0525ab1SAndriy Gapon }
221c0525ab1SAndriy Gapon 
222c0525ab1SAndriy Gapon static int
223c0525ab1SAndriy Gapon pca954x_detach(device_t dev)
224c0525ab1SAndriy Gapon {
22597dbd377SBjoern A. Zeeb 	int error;
226c0525ab1SAndriy Gapon 
22797dbd377SBjoern A. Zeeb 	error = iicmux_detach(dev);
22897dbd377SBjoern A. Zeeb 	return (error);
229c0525ab1SAndriy Gapon }
230c0525ab1SAndriy Gapon 
231c0525ab1SAndriy Gapon static device_method_t pca954x_methods[] = {
232c0525ab1SAndriy Gapon 	/* device methods */
233c0525ab1SAndriy Gapon 	DEVMETHOD(device_probe,			pca954x_probe),
234c0525ab1SAndriy Gapon 	DEVMETHOD(device_attach,		pca954x_attach),
235c0525ab1SAndriy Gapon 	DEVMETHOD(device_detach,		pca954x_detach),
236c0525ab1SAndriy Gapon 
237c0525ab1SAndriy Gapon 	/* iicmux methods */
238c0525ab1SAndriy Gapon 	DEVMETHOD(iicmux_bus_select,		pca954x_bus_select),
239c0525ab1SAndriy Gapon 
240c0525ab1SAndriy Gapon 	DEVMETHOD_END
241c0525ab1SAndriy Gapon };
242c0525ab1SAndriy Gapon 
24397dbd377SBjoern A. Zeeb DEFINE_CLASS_1(pca954x, pca954x_driver, pca954x_methods,
244c0525ab1SAndriy Gapon     sizeof(struct pca954x_softc), iicmux_driver);
24597dbd377SBjoern A. Zeeb DRIVER_MODULE(pca954x, iicbus, pca954x_driver, 0, 0);
246c0525ab1SAndriy Gapon 
247c0525ab1SAndriy Gapon #ifdef FDT
24897dbd377SBjoern A. Zeeb DRIVER_MODULE(ofw_iicbus, pca954x, ofw_iicbus_driver, 0, 0);
249c0525ab1SAndriy Gapon #else
25097dbd377SBjoern A. Zeeb DRIVER_MODULE(iicbus, pca954x, iicbus_driver, 0, 0);
251c0525ab1SAndriy Gapon #endif
252c0525ab1SAndriy Gapon 
25397dbd377SBjoern A. Zeeb MODULE_DEPEND(pca954x, iicmux, 1, 1, 1);
25497dbd377SBjoern A. Zeeb MODULE_DEPEND(pca954x, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
25597dbd377SBjoern A. Zeeb MODULE_VERSION(pca954x, 1);
256c0525ab1SAndriy Gapon 
257c0525ab1SAndriy Gapon #ifdef FDT
258c0525ab1SAndriy Gapon IICBUS_FDT_PNP_INFO(compat_data);
259c0525ab1SAndriy Gapon #endif
260