xref: /freebsd/sys/dev/iicbus/mux/pca954x.c (revision cc68614da8232d8baaca0ae0d0dd8f890f06623e)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) Andriy Gapon
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 FOR
19  * 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 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include "opt_platform.h"
33 
34 #include <sys/param.h>
35 #include <sys/bus.h>
36 #include <sys/kernel.h>
37 #include <sys/module.h>
38 #include <sys/systm.h>
39 
40 #ifdef FDT
41 #include <dev/ofw/ofw_bus.h>
42 #include <dev/ofw/ofw_bus_subr.h>
43 #include <dev/ofw/openfirm.h>
44 #endif
45 
46 #include <dev/iicbus/iicbus.h>
47 #include <dev/iicbus/iiconf.h>
48 #include "iicbus_if.h"
49 #include "iicmux_if.h"
50 #include <dev/iicbus/mux/iicmux.h>
51 
52 struct pca954x_descr {
53 	const char 	*partname;
54 	const char	*description;
55 	int		numchannels;
56 };
57 
58 static struct pca954x_descr pca9548_descr = {
59 	.partname = "pca9548",
60 	.description = "PCA9548A I2C Mux",
61 	.numchannels = 8,
62 };
63 
64 #ifdef FDT
65 static struct ofw_compat_data compat_data[] = {
66 	{ "nxp,pca9548", (uintptr_t)&pca9548_descr },
67 	{ NULL, 0 },
68 };
69 #else
70 static struct pca954x_descr *part_descrs[] = {
71 	&pca9548_descr,
72 };
73 #endif
74 
75 struct pca954x_softc {
76 	struct iicmux_softc mux;
77 	uint8_t addr;
78 	bool idle_disconnect;
79 };
80 
81 static int
82 pca954x_bus_select(device_t dev, int busidx, struct iic_reqbus_data *rd)
83 {
84 	struct iic_msg msg;
85 	struct pca954x_softc *sc = device_get_softc(dev);
86 	uint8_t busbits;
87 	int error;
88 
89 	/*
90 	 * The iicmux caller ensures busidx is between 0 and the number of buses
91 	 * we passed to iicmux_init_softc(), no need for validation here.  If
92 	 * the fdt data has the idle_disconnect property we idle the bus by
93 	 * selecting no downstream buses, otherwise we just leave the current
94 	 * bus active.
95 	 */
96 	if (busidx == IICMUX_SELECT_IDLE) {
97 		if (sc->idle_disconnect)
98 			busbits = 0;
99 		else
100 			return (0);
101 	} else {
102 		busbits = 1u << busidx;
103 	}
104 
105 	msg.slave = sc->addr;
106 	msg.flags = IIC_M_WR;
107 	msg.len = 1;
108 	msg.buf = &busbits;
109 	error = iicbus_transfer(dev, &msg, 1);
110 	return (error);
111 }
112 
113 static const struct pca954x_descr *
114 pca954x_find_chip(device_t dev)
115 {
116 #ifdef FDT
117 	const struct ofw_compat_data *compat;
118 
119 	compat = ofw_bus_search_compatible(dev, compat_data);
120 	if (compat == NULL)
121 		return (NULL);
122 	return ((const struct pca954x_descr *)compat->ocd_data);
123 #else
124 	const char *type;
125 	int i;
126 
127 	if (resource_string_value(device_get_name(dev), device_get_unit(dev),
128 	    "chip_type", &type) == 0) {
129 		for (i = 0; i < nitems(part_descrs) - 1; ++i) {
130 			if (strcasecmp(type, part_descrs[i]->partname) == 0)
131 				return (part_descrs[i]);
132 		}
133 	}
134 	return (NULL);
135 #endif
136 }
137 
138 static int
139 pca954x_probe(device_t dev)
140 {
141 	const struct pca954x_descr *descr;
142 
143 	descr = pca954x_find_chip(dev);
144 	if (descr == NULL)
145 		return (ENXIO);
146 
147 	device_set_desc(dev, descr->description);
148 	return (BUS_PROBE_DEFAULT);
149 }
150 
151 static int
152 pca954x_attach(device_t dev)
153 {
154 #ifdef FDT
155 	phandle_t node;
156 #endif
157 	struct pca954x_softc *sc;
158 	const struct pca954x_descr *descr;
159 	int err;
160 
161 	sc = device_get_softc(dev);
162 	sc->addr = iicbus_get_addr(dev);
163 #ifdef FDT
164 	node = ofw_bus_get_node(dev);
165 	sc->idle_disconnect = OF_hasprop(node, "i2c-mux-idle-disconnect");
166 #endif
167 
168 	descr = pca954x_find_chip(dev);
169 	err = iicmux_attach(dev, device_get_parent(dev), descr->numchannels);
170 	if (err == 0)
171                 bus_generic_attach(dev);
172 	return (err);
173 }
174 
175 static int
176 pca954x_detach(device_t dev)
177 {
178 	int err;
179 
180 	err = iicmux_detach(dev);
181 	return (err);
182 }
183 
184 static device_method_t pca954x_methods[] = {
185 	/* device methods */
186 	DEVMETHOD(device_probe,			pca954x_probe),
187 	DEVMETHOD(device_attach,		pca954x_attach),
188 	DEVMETHOD(device_detach,		pca954x_detach),
189 
190 	/* iicmux methods */
191 	DEVMETHOD(iicmux_bus_select,		pca954x_bus_select),
192 
193 	DEVMETHOD_END
194 };
195 
196 static devclass_t pca954x_devclass;
197 
198 DEFINE_CLASS_1(pca9548, pca954x_driver, pca954x_methods,
199     sizeof(struct pca954x_softc), iicmux_driver);
200 DRIVER_MODULE(pca9548, iicbus, pca954x_driver, pca954x_devclass, 0, 0);
201 
202 #ifdef FDT
203 DRIVER_MODULE(ofw_iicbus, pca9548, ofw_iicbus_driver, ofw_iicbus_devclass, 0, 0);
204 #else
205 DRIVER_MODULE(iicbus, pca9548, iicbus_driver, iicbus_devclass, 0, 0);
206 #endif
207 
208 MODULE_DEPEND(pca9548, iicmux, 1, 1, 1);
209 MODULE_DEPEND(pca9548, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
210 MODULE_VERSION(pca9548, 1);
211 
212 #ifdef FDT
213 IICBUS_FDT_PNP_INFO(compat_data);
214 #endif
215