xref: /openbsd-src/sys/dev/i2c/pca9548.c (revision 1a8dbaac879b9f3335ad7fb25429ce63ac1d6bac)
1 /*	$OpenBSD: pca9548.c,v 1.3 2020/09/29 13:59:22 patrick Exp $	*/
2 
3 /*
4  * Copyright (c) 2020 Mark Kettenis
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/param.h>
20 #include <sys/systm.h>
21 #include <sys/device.h>
22 
23 #include <machine/bus.h>
24 
25 #define _I2C_PRIVATE
26 #include <dev/i2c/i2cvar.h>
27 
28 #include <dev/ofw/openfirm.h>
29 #include <dev/ofw/ofw_misc.h>
30 
31 #define PCA9546_NUM_CHANNELS	4
32 #define PCA9548_NUM_CHANNELS	8
33 #define PCAMUX_MAX_CHANNELS	8
34 
35 struct pcamux_bus {
36 	struct pcamux_softc	*pb_sc;
37 	int			pb_node;
38 	int			pb_channel;
39 	struct i2c_controller	pb_ic;
40 	struct i2c_bus		pb_ib;
41 };
42 
43 struct pcamux_softc {
44 	struct device		sc_dev;
45 	i2c_tag_t		sc_tag;
46 	i2c_addr_t		sc_addr;
47 
48 	int			sc_node;
49 	int			sc_channel;
50 	int			sc_nchannel;
51 	struct pcamux_bus	sc_bus[PCAMUX_MAX_CHANNELS];
52 	struct rwlock		sc_lock;
53 };
54 
55 int	pcamux_match(struct device *, void *, void *);
56 void	pcamux_attach(struct device *, struct device *, void *);
57 
58 struct cfattach pcamux_ca = {
59 	sizeof(struct pcamux_softc), pcamux_match, pcamux_attach
60 };
61 
62 struct cfdriver pcamux_cd = {
63 	NULL, "pcamux", DV_DULL
64 };
65 
66 int	pcamux_acquire_bus(void *, int);
67 void	pcamux_release_bus(void *, int);
68 int	pcamux_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
69 	    void *, size_t, int);
70 void	pcamux_bus_scan(struct device *, struct i2cbus_attach_args *, void *);
71 
72 int
73 pcamux_match(struct device *parent, void *match, void *aux)
74 {
75 	struct i2c_attach_args *ia = aux;
76 
77 	if (strcmp(ia->ia_name, "nxp,pca9546") == 0 ||
78 	    strcmp(ia->ia_name, "nxp,pca9548") == 0)
79 		return (1);
80 	return (0);
81 }
82 
83 void
84 pcamux_attach(struct device *parent, struct device *self, void *aux)
85 {
86 	struct pcamux_softc *sc = (struct pcamux_softc *)self;
87 	struct i2c_attach_args *ia = aux;
88 	int node = *(int *)ia->ia_cookie;
89 
90 	sc->sc_tag = ia->ia_tag;
91 	sc->sc_addr = ia->ia_addr;
92 
93 	sc->sc_node = node;
94 	sc->sc_channel = -1;	/* unknown */
95 	rw_init(&sc->sc_lock, sc->sc_dev.dv_xname);
96 
97 	if (strcmp(ia->ia_name, "nxp,pca9546") == 0)
98 		sc->sc_nchannel = 4;
99 	else if (strcmp(ia->ia_name, "nxp,pca9548") == 0)
100 		sc->sc_nchannel = 8;
101 
102 	printf("\n");
103 
104 	for (node = OF_child(node); node; node = OF_peer(node)) {
105 		struct i2cbus_attach_args iba;
106 		struct pcamux_bus *pb;
107 		uint32_t channel;
108 
109 		channel = OF_getpropint(node, "reg", -1);
110 		if (channel >= sc->sc_nchannel)
111 			continue;
112 
113 		pb = &sc->sc_bus[channel];
114 		pb->pb_sc = sc;
115 		pb->pb_node = node;
116 		pb->pb_channel = channel;
117 		pb->pb_ic.ic_cookie = pb;
118 		pb->pb_ic.ic_acquire_bus = pcamux_acquire_bus;
119 		pb->pb_ic.ic_release_bus = pcamux_release_bus;
120 		pb->pb_ic.ic_exec = pcamux_exec;
121 
122 		/* Configure the child busses. */
123 		memset(&iba, 0, sizeof(iba));
124 		iba.iba_name = "iic";
125 		iba.iba_tag = &pb->pb_ic;
126 		iba.iba_bus_scan = pcamux_bus_scan;
127 		iba.iba_bus_scan_arg = &pb->pb_node;
128 
129 		config_found(&sc->sc_dev, &iba, iicbus_print);
130 
131 		pb->pb_ib.ib_node = node;
132 		pb->pb_ib.ib_ic = &pb->pb_ic;
133 		i2c_register(&pb->pb_ib);
134 	}
135 }
136 
137 int
138 pcamux_set_channel(struct pcamux_softc *sc, int channel, int flags)
139 {
140 	uint8_t data;
141 	int error;
142 
143 	if (channel < -1 || channel >= sc->sc_nchannel)
144 		return ENXIO;
145 
146 	if (sc->sc_channel == channel)
147 		return 0;
148 
149 	data = (channel == -1) ? 0 : (1 << channel);
150 	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
151 	    sc->sc_addr, NULL, 0, &data, sizeof data, flags);
152 
153 	return error;
154 }
155 
156 int
157 pcamux_acquire_bus(void *cookie, int flags)
158 {
159 	struct pcamux_bus *pb = cookie;
160 	struct pcamux_softc *sc = pb->pb_sc;
161 	int rwflags = RW_WRITE;
162 	int error;
163 
164 	if (flags & I2C_F_POLL)
165 		rwflags |= RW_NOSLEEP;
166 
167 	error = rw_enter(&sc->sc_lock, rwflags);
168 	if (error)
169 		return error;
170 
171 	/* Acquire parent bus. */
172 	error = iic_acquire_bus(sc->sc_tag, flags);
173 	if (error) {
174 		rw_exit_write(&sc->sc_lock);
175 		return error;
176 	}
177 
178 	error = pcamux_set_channel(sc, pb->pb_channel, flags);
179 	if (error) {
180 		iic_release_bus(sc->sc_tag, flags);
181 		rw_exit_write(&sc->sc_lock);
182 		return error;
183 	}
184 
185 	return 0;
186 }
187 
188 void
189 pcamux_release_bus(void *cookie, int flags)
190 {
191 	struct pcamux_bus *pb = cookie;
192 	struct pcamux_softc *sc = pb->pb_sc;
193 
194 	/* Release parent bus. */
195 	iic_release_bus(sc->sc_tag, flags);
196 	rw_exit_write(&sc->sc_lock);
197 }
198 
199 int
200 pcamux_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd,
201     size_t cmdlen, void *buf, size_t buflen, int flags)
202 {
203 	struct pcamux_bus *pb = cookie;
204 	struct pcamux_softc *sc = pb->pb_sc;
205 
206 	rw_assert_wrlock(&sc->sc_lock);
207 
208 	/* Issue the transaction on the parent bus. */
209 	return iic_exec(sc->sc_tag, op, addr, cmd, cmdlen, buf, buflen, flags);
210 }
211 
212 void
213 pcamux_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg)
214 {
215 	int iba_node = *(int *)arg;
216 	struct i2c_attach_args ia;
217 	char name[32];
218 	uint32_t reg[1];
219 	int node;
220 
221 	for (node = OF_child(iba_node); node; node = OF_peer(node)) {
222 		memset(name, 0, sizeof(name));
223 		memset(reg, 0, sizeof(reg));
224 
225 		if (OF_getprop(node, "compatible", name, sizeof(name)) == -1)
226 			continue;
227 		if (name[0] == '\0')
228 			continue;
229 
230 		if (OF_getprop(node, "reg", &reg, sizeof(reg)) != sizeof(reg))
231 			continue;
232 
233 		memset(&ia, 0, sizeof(ia));
234 		ia.ia_tag = iba->iba_tag;
235 		ia.ia_addr = bemtoh32(&reg[0]);
236 		ia.ia_name = name;
237 		ia.ia_cookie = &node;
238 		config_found(self, &ia, iic_print);
239 	}
240 }
241