xref: /netbsd-src/sys/dev/i2c/pcagpio.c (revision e6c7e151de239c49d2e38720a061ed9d1fa99309)
1 /* $NetBSD: pcagpio.c,v 1.3 2020/02/02 06:43:14 macallan Exp $ */
2 
3 /*-
4  * Copyright (c) 2020 Michael Lorenz
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /*
30  * a driver for Philips Semiconductor PCA9555 GPIO controllers
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: pcagpio.c,v 1.3 2020/02/02 06:43:14 macallan Exp $");
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/device.h>
39 #include <sys/conf.h>
40 #include <sys/bus.h>
41 
42 #include <dev/i2c/i2cvar.h>
43 #include <dev/led.h>
44 
45 #ifdef PCAGPIO_DEBUG
46 #define DPRINTF printf
47 #else
48 #define DPRINTF if (0) printf
49 #endif
50 
51 /* commands */
52 #define PCAGPIO_INPUT	0x00	/* line status */
53 #define PCAGPIO_OUTPUT	0x01	/* output status */
54 #define PCAGPIO_REVERT	0x02	/* revert input if set */
55 #define PCAGPIO_CONFIG	0x03	/* input if set, output if not */
56 
57 static int	pcagpio_match(device_t, cfdata_t, void *);
58 static void	pcagpio_attach(device_t, device_t, void *);
59 
60 /* we can only pass one cookie to led_attach() but we need several values... */
61 struct pcagpio_led {
62 	void *cookie;
63 	uint32_t mask, v_on, v_off;
64 };
65 
66 struct pcagpio_softc {
67 	device_t	sc_dev;
68 	i2c_tag_t	sc_i2c;
69 	i2c_addr_t	sc_addr;
70 
71 	int		sc_is_16bit;
72 	uint32_t	sc_state;
73 	struct pcagpio_led sc_leds[16];
74 	int		sc_nleds;
75 };
76 
77 
78 static void 	pcagpio_writereg(struct pcagpio_softc *, int, uint32_t);
79 static uint32_t pcagpio_readreg(struct pcagpio_softc *, int);
80 static void	pcagpio_attach_led(
81 			struct pcagpio_softc *, char *, int, int, int);
82 static int	pcagpio_get(void *);
83 static void	pcagpio_set(void *, int);
84 
85 CFATTACH_DECL_NEW(pcagpio, sizeof(struct pcagpio_softc),
86     pcagpio_match, pcagpio_attach, NULL, NULL);
87 
88 static const struct device_compatible_entry compat_data[] = {
89 	{ "i2c-pca9555",	1 },
90 	{ "pca9555",		1 },
91 	{ "i2c-pca9556",	0 },
92 	{ "pca9556",		0 },
93 	{ NULL,			0 }
94 };
95 
96 static int
97 pcagpio_match(device_t parent, cfdata_t match, void *aux)
98 {
99 	struct i2c_attach_args *ia = aux;
100 	int match_result;
101 
102 	if (iic_use_direct_match(ia, match, compat_data, &match_result))
103 		return match_result;
104 
105 	return 0;
106 }
107 
108 #ifdef PCAGPIO_DEBUG
109 static void
110 printdir(uint32_t val, uint32_t mask, char letter)
111 {
112 	char flags[17], bits[17];
113 	uint32_t bit = 0x8000;
114 	int i;
115 
116 	val &= mask;
117 	for (i = 0; i < 16; i++) {
118 		flags[i] = (mask & bit) ? letter : '-';
119 		bits[i] = (val & bit) ? 'X' : ' ';
120 		bit = bit >> 1;
121 	}
122 	flags[16] = 0;
123 	bits[16] = 0;
124 	printf("dir: %s\n", flags);
125 	printf("lvl: %s\n", bits);
126 }
127 #endif
128 
129 static void
130 pcagpio_attach(device_t parent, device_t self, void *aux)
131 {
132 	struct pcagpio_softc *sc = device_private(self);
133 	struct i2c_attach_args *ia = aux;
134 	const struct device_compatible_entry *dce;
135 	prop_dictionary_t dict = device_properties(self);
136 	prop_array_t pins;
137 	prop_dictionary_t pin;
138 
139 	sc->sc_dev = self;
140 	sc->sc_i2c = ia->ia_tag;
141 	sc->sc_addr = ia->ia_addr;
142 	sc->sc_nleds = 0;
143 
144 	aprint_naive("\n");
145 	sc->sc_is_16bit = 0;
146 	if (iic_compatible_match(ia, compat_data, &dce))
147 		sc->sc_is_16bit = dce->data;
148 
149 	aprint_normal(": %s\n", sc->sc_is_16bit ? "PCA9555" : "PCA9556");
150 
151 	sc->sc_state = pcagpio_readreg(sc, PCAGPIO_OUTPUT);
152 
153 #ifdef PCAGPIO_DEBUG
154 	uint32_t dir, in, out;
155 	dir = pcagpio_readreg(sc, PCAGPIO_CONFIG);
156 	in = pcagpio_readreg(sc, PCAGPIO_INPUT);
157 	out = sc->sc_state;
158 
159 	out &= ~dir;
160 	in &= dir;
161 
162 	printdir(in, dir, 'I');
163 	printdir(out, ~dir, 'O');
164 #endif
165 
166 	pins = prop_dictionary_get(dict, "pins");
167 	if (pins != NULL) {
168 		int i, num, def;
169 		char name[32];
170 		const char *nptr;
171 		bool ok = TRUE, act;
172 
173 		for (i = 0; i < prop_array_count(pins); i++) {
174 			nptr = NULL;
175 			pin = prop_array_get(pins, i);
176 			ok &= prop_dictionary_get_cstring_nocopy(pin, "name",
177 			    &nptr);
178 			strncpy(name, nptr, 31);
179 			ok &= prop_dictionary_get_uint32(pin, "pin", &num);
180 			ok &= prop_dictionary_get_bool(
181 			    pin, "active_high", &act);
182 			/* optional default state */
183 			def = -1;
184 			prop_dictionary_get_int32(pin, "default_state", &def);
185 			if (ok) {
186 				pcagpio_attach_led(sc, name, num, act, def);
187 			}
188 		}
189 	}
190 }
191 
192 static void
193 pcagpio_writereg(struct pcagpio_softc *sc, int reg, uint32_t val)
194 {
195 	uint8_t cmd;
196 
197 	iic_acquire_bus(sc->sc_i2c, 0);
198 	if (sc->sc_is_16bit) {
199 		uint16_t creg;
200 		cmd = reg << 1;
201 		creg = htole16(val);
202 		iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
203 		    sc->sc_addr, &cmd, 1, &creg, 2, 0);
204 	} else {
205 		uint8_t creg;
206 		cmd = reg;
207 		creg = (uint8_t)val;
208 		iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP,
209 		    sc->sc_addr, &cmd, 1, &creg, 1, 0);
210 	}
211 	if (reg == PCAGPIO_OUTPUT) sc->sc_state = val;
212 	iic_release_bus(sc->sc_i2c, 0);
213 }
214 
215 static uint32_t pcagpio_readreg(struct pcagpio_softc *sc, int reg)
216 {
217 	uint8_t cmd;
218 	uint32_t ret;
219 
220 	iic_acquire_bus(sc->sc_i2c, 0);
221 	if (sc->sc_is_16bit) {
222 		uint16_t creg;
223 		cmd = reg << 1;
224 		iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
225 		    sc->sc_addr, &cmd, 1, &creg, 2, 0);
226 		ret = le16toh(creg);
227 	} else {
228 		uint8_t creg;
229 		cmd = reg;
230 		iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP,
231 		    sc->sc_addr, &cmd, 1, &creg, 1, 0);
232 		ret = creg;
233 	}
234 	iic_release_bus(sc->sc_i2c, 0);
235 	return ret;
236 }
237 
238 static void
239 pcagpio_attach_led(struct pcagpio_softc *sc, char *n, int pin, int act, int def)
240 {
241 	struct pcagpio_led *l;
242 
243 	l = &sc->sc_leds[sc->sc_nleds];
244 	l->cookie = sc;
245 	l->mask = 1 << pin;
246 	l->v_on = act ? l->mask : 0;
247 	l->v_off = act ? 0 : l->mask;
248 	led_attach(n, l, pcagpio_get, pcagpio_set);
249 	if (def != -1) pcagpio_set(l, def);
250 	DPRINTF("%s: %04x %04x %04x def %d\n",
251 	    __func__, l->mask, l->v_on, l->v_off, def);
252 	sc->sc_nleds++;
253 }
254 
255 static int
256 pcagpio_get(void *cookie)
257 {
258 	struct pcagpio_led *l = cookie;
259 	struct pcagpio_softc *sc = l->cookie;
260 
261 	return ((sc->sc_state & l->mask) == l->v_on);
262 }
263 
264 static void
265 pcagpio_set(void *cookie, int val)
266 {
267 	struct pcagpio_led *l = cookie;
268 	struct pcagpio_softc *sc = l->cookie;
269 	uint32_t newstate;
270 
271 	newstate = sc->sc_state & ~l->mask;
272 	newstate |= val ? l->v_on : l->v_off;
273 	DPRINTF("%s: %04x -> %04x, %04x %04x %04x\n", __func__,
274 	    sc->sc_state, newstate, l->mask, l->v_on, l->v_off);
275 	if (newstate != sc->sc_state)
276 		pcagpio_writereg(sc, PCAGPIO_OUTPUT, newstate);
277 }
278