xref: /openbsd-src/sys/dev/i2c/pca9548.c (revision 505ee9ea3b177e2387d907a91ca7da069f3f14d8)
1 /*	$OpenBSD: pca9548.c,v 1.1 2020/06/18 18:05:00 kettenis 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 PCA9548_NUM_CHANNELS	8
32 
33 struct pcamux_bus {
34 	struct pcamux_softc	*pb_sc;
35 	int			pb_channel;
36 	struct i2c_controller	pb_ic;
37 	struct i2c_bus		pb_ib;
38 };
39 
40 struct pcamux_softc {
41 	struct device		sc_dev;
42 	i2c_tag_t		sc_tag;
43 	i2c_addr_t		sc_addr;
44 
45 	int			sc_node;
46 	int			sc_channel;
47 	struct pcamux_bus	sc_bus[PCA9548_NUM_CHANNELS];
48 	struct rwlock		sc_lock;
49 };
50 
51 int	pcamux_match(struct device *, void *, void *);
52 void	pcamux_attach(struct device *, struct device *, void *);
53 
54 struct cfattach pcamux_ca = {
55 	sizeof(struct pcamux_softc), pcamux_match, pcamux_attach
56 };
57 
58 struct cfdriver pcamux_cd = {
59 	NULL, "pcamux", DV_DULL
60 };
61 
62 int	pcamux_acquire_bus(void *, int);
63 void	pcamux_release_bus(void *, int);
64 int	pcamux_exec(void *, i2c_op_t, i2c_addr_t, const void *, size_t,
65 	    void *, size_t, int);
66 void	pcamux_bus_scan(struct device *, struct i2cbus_attach_args *, void *);
67 
68 int
69 pcamux_match(struct device *parent, void *match, void *aux)
70 {
71 	struct i2c_attach_args *ia = aux;
72 
73 	if (strcmp(ia->ia_name, "nxp,pca9548") == 0)
74 		return (1);
75 	return (0);
76 }
77 
78 void
79 pcamux_attach(struct device *parent, struct device *self, void *aux)
80 {
81 	struct pcamux_softc *sc = (struct pcamux_softc *)self;
82 	struct i2c_attach_args *ia = aux;
83 	int node = *(int *)ia->ia_cookie;
84 
85 	sc->sc_tag = ia->ia_tag;
86 	sc->sc_addr = ia->ia_addr;
87 
88 	sc->sc_node = node;
89 	sc->sc_channel = -1;	/* unknown */
90 	rw_init(&sc->sc_lock, sc->sc_dev.dv_xname);
91 
92 	printf("\n");
93 
94 	for (node = OF_child(node); node; node = OF_peer(node)) {
95 		struct i2cbus_attach_args iba;
96 		struct pcamux_bus *pb;
97 		uint32_t channel;
98 
99 		channel = OF_getpropint(node, "reg", -1);
100 		if (channel >= PCA9548_NUM_CHANNELS)
101 			continue;
102 
103 		pb = &sc->sc_bus[channel];
104 		pb->pb_sc = sc;
105 		pb->pb_channel = channel;
106 		pb->pb_ic.ic_cookie = pb;
107 		pb->pb_ic.ic_acquire_bus = pcamux_acquire_bus;
108 		pb->pb_ic.ic_release_bus = pcamux_release_bus;
109 		pb->pb_ic.ic_exec = pcamux_exec;
110 
111 		/* Configure the child busses. */
112 		memset(&iba, 0, sizeof(iba));
113 		iba.iba_name = "iic";
114 		iba.iba_tag = &pb->pb_ic;
115 		iba.iba_bus_scan = pcamux_bus_scan;
116 		iba.iba_bus_scan_arg = &sc->sc_node;
117 
118 		config_found(&sc->sc_dev, &iba, iicbus_print);
119 
120 		pb->pb_ib.ib_node = node;
121 		pb->pb_ib.ib_ic = &pb->pb_ic;
122 		i2c_register(&pb->pb_ib);
123 	}
124 }
125 
126 int
127 pcamux_set_channel(struct pcamux_softc *sc, int channel, int flags)
128 {
129 	uint8_t data;
130 	int error;
131 
132 	if (channel < -1 || channel >= PCA9548_NUM_CHANNELS)
133 		return ENXIO;
134 
135 	if (sc->sc_channel == channel)
136 		return 0;
137 
138 	data = (channel == -1) ? 0 : (1 << channel);
139 	error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP,
140 	    sc->sc_addr, NULL, 0, &data, sizeof data, flags);
141 
142 	return error;
143 }
144 
145 int
146 pcamux_acquire_bus(void *cookie, int flags)
147 {
148 	struct pcamux_bus *pb = cookie;
149 	struct pcamux_softc *sc = pb->pb_sc;
150 	int rwflags = RW_WRITE;
151 	int error;
152 
153 	if (flags & I2C_F_POLL)
154 		rwflags |= RW_NOSLEEP;
155 
156 	error = rw_enter(&sc->sc_lock, rwflags);
157 	if (error)
158 		return error;
159 
160 	/* Acquire parent bus. */
161 	error = iic_acquire_bus(sc->sc_tag, flags);
162 	if (error) {
163 		rw_exit_write(&sc->sc_lock);
164 		return error;
165 	}
166 
167 	error = pcamux_set_channel(sc, pb->pb_channel, flags);
168 	if (error) {
169 		iic_release_bus(sc->sc_tag, flags);
170 		rw_exit_write(&sc->sc_lock);
171 		return error;
172 	}
173 
174 	return 0;
175 }
176 
177 void
178 pcamux_release_bus(void *cookie, int flags)
179 {
180 	struct pcamux_bus *pb = cookie;
181 	struct pcamux_softc *sc = pb->pb_sc;
182 
183 	/* Release parent bus. */
184 	iic_release_bus(sc->sc_tag, flags);
185 	rw_exit_write(&sc->sc_lock);
186 }
187 
188 int
189 pcamux_exec(void *cookie, i2c_op_t op, i2c_addr_t addr, const void *cmd,
190     size_t cmdlen, void *buf, size_t buflen, int flags)
191 {
192 	struct pcamux_bus *pb = cookie;
193 	struct pcamux_softc *sc = pb->pb_sc;
194 
195 	rw_assert_wrlock(&sc->sc_lock);
196 
197 	/* Issue the transaction on the parent bus. */
198 	return iic_exec(sc->sc_tag, op, addr, cmd, cmdlen, buf, buflen, flags);
199 }
200 
201 void
202 pcamux_bus_scan(struct device *self, struct i2cbus_attach_args *iba, void *arg)
203 {
204 	int iba_node = *(int *)arg;
205 	struct i2c_attach_args ia;
206 	char name[32];
207 	uint32_t reg[1];
208 	int node;
209 
210 	for (node = OF_child(iba_node); node; node = OF_peer(node)) {
211 		memset(name, 0, sizeof(name));
212 		memset(reg, 0, sizeof(reg));
213 
214 		if (OF_getprop(node, "compatible", name, sizeof(name)) == -1)
215 			continue;
216 		if (name[0] == '\0')
217 			continue;
218 
219 		if (OF_getprop(node, "reg", &reg, sizeof(reg)) != sizeof(reg))
220 			continue;
221 
222 		memset(&ia, 0, sizeof(ia));
223 		ia.ia_tag = iba->iba_tag;
224 		ia.ia_addr = bemtoh32(&reg[0]);
225 		ia.ia_name = name;
226 		ia.ia_cookie = &node;
227 		config_found(self, &ia, iic_print);
228 	}
229 }
230