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