xref: /netbsd-src/sys/dev/i2c/pcf8574.c (revision 9fd8799cb5ceb66c69f2eb1a6d26a1d587ba1f1e)
1 /* $NetBSD: pcf8574.c,v 1.4 2020/12/05 15:02:29 jdc Exp $ */
2 
3 /*-
4  * Copyright (c) 2020 The NetBSD Foundation, Inc.
5  * All rights reserved.
6  *
7  * This code is derived from software contributed to The NetBSD Foundation
8  * by Julian Coleman.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29  * POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*
33  * A driver for Philips Semiconductor (NXP) PCF8574/PCF857A GPIO's.
34  * Uses device properties to connect pins to the appropriate subsystem.
35  */
36 
37 #include <sys/cdefs.h>
38 __KERNEL_RCSID(0, "$NetBSD: pcf8574.c,v 1.4 2020/12/05 15:02:29 jdc Exp $");
39 
40 #include <sys/param.h>
41 #include <sys/systm.h>
42 #include <sys/device.h>
43 #include <sys/kernel.h>
44 
45 #include <dev/sysmon/sysmonvar.h>
46 
47 #include <dev/i2c/i2cvar.h>
48 #include <dev/led.h>
49 
50 #ifdef PCF8574_DEBUG
51 #define DPRINTF printf
52 #else
53 #define DPRINTF if (0) printf
54 #endif
55 
56 struct pcf8574_led {
57 	void *cookie;
58 	struct led_device *led;
59 	uint8_t mask, v_on, v_off;
60 };
61 
62 #define PCF8574_NPINS	8
63 struct pcf8574_softc {
64 	device_t	sc_dev;
65 	i2c_tag_t	sc_tag;
66 	i2c_addr_t	sc_addr;
67 	uint8_t		sc_state;
68 	uint8_t		sc_mask;
69 
70 	int		sc_nleds;
71 	struct pcf8574_led sc_leds[PCF8574_NPINS];
72 
73 	struct sysmon_envsys *sc_sme;
74 	envsys_data_t	sc_sensor[PCF8574_NPINS];
75 	int		sc_pin_sensor[PCF8574_NPINS];
76 	int		sc_pin_active[PCF8574_NPINS];
77 
78 #ifdef PCF8574_DEBUG
79 	callout_t	sc_timer;
80 #endif
81 };
82 
83 static int	pcf8574_match(device_t, cfdata_t, void *);
84 static void	pcf8574_attach(device_t, device_t, void *);
85 static int	pcf8574_detach(device_t, int);
86 
87 static int	pcf8574_read(struct pcf8574_softc *sc, uint8_t *val);
88 static int	pcf8574_write(struct pcf8574_softc *sc, uint8_t val);
89 static void	pcf8574_attach_led(
90 			struct pcf8574_softc *, char *, int, int, int);
91 void		pcf8574_refresh(struct sysmon_envsys *, envsys_data_t *);
92 int		pcf8574_get_led(void *);
93 void		pcf8574_set_led(void *, int);
94 
95 #ifdef PCF8574_DEBUG
96 static void pcf8574_timeout(void *);
97 #endif
98 
99 CFATTACH_DECL_NEW(pcf8574io, sizeof(struct pcf8574_softc),
100 	pcf8574_match, pcf8574_attach, pcf8574_detach, NULL);
101 
102 static const struct device_compatible_entry compat_data[] = {
103 	{ "i2c-pcf8574",		0 },
104 	{ NULL,				0 }
105 };
106 
107 static int
108 pcf8574_match(device_t parent, cfdata_t cf, void *aux)
109 {
110 	struct i2c_attach_args *ia = aux;
111 	struct pcf8574_softc sc;
112 	int match_result;
113 
114 	if (!iic_use_direct_match(ia, cf, compat_data, &match_result))
115 		return 0;
116 
117 	/* Try a read so that we don't match on optional components */
118 	if (match_result) {
119 		sc.sc_tag = ia->ia_tag;
120 		sc.sc_addr = ia->ia_addr;
121 		if (pcf8574_read(&sc, &sc.sc_state))
122 			return 0;
123 		else
124 			return match_result;
125 	}
126 
127 	/* We don't support indirect matches */
128 	return 0;
129 }
130 
131 static void
132 pcf8574_attach(device_t parent, device_t self, void *aux)
133 {
134 	struct pcf8574_softc *sc = device_private(self);
135 	struct i2c_attach_args *ia = aux;
136 	prop_dictionary_t dict = device_properties(self);
137 	prop_array_t pins;
138 	prop_dictionary_t pin;
139 	int i, num, def, envc = 0;
140 	char name[32];
141 	const char *nptr = NULL, *spptr;
142 	bool ok = TRUE, act, sysmon = FALSE;
143 
144 	sc->sc_tag = ia->ia_tag;
145 	sc->sc_addr = ia->ia_addr;
146 	sc->sc_dev = self;
147 
148 	/*
149 	 * The PCF8574 requires input pins to be written with the value 1,
150 	 * and then read.  Assume that all pins are input initially.
151 	 * We'll use the mask when we write, because we have to write a
152 	 * value for every pin, and we don't want to change input pins.
153 	 */
154 	sc->sc_mask = 0xff;
155 
156 	pcf8574_read(sc, &sc->sc_state);
157 
158 #ifdef PCF8574_DEBUG
159 	aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state);
160 
161 	callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
162 	callout_reset(&sc->sc_timer, hz*30, pcf8574_timeout, sc);
163 #else
164 	aprint_normal(": GPIO\n");
165 #endif
166 
167 	pins = prop_dictionary_get(dict, "pins");
168 	if (pins == NULL)
169 		return;
170 
171 	for (i = 0; i < prop_array_count(pins); i++) {
172 		pin = prop_array_get(pins, i);
173 		ok &= prop_dictionary_get_cstring_nocopy(pin, "name", &nptr);
174 		ok &= prop_dictionary_get_uint32(pin, "pin", &num);
175 		ok &= prop_dictionary_get_bool(pin, "active_high", &act);
176 		/* optional default state */
177 		def = -1;
178 		prop_dictionary_get_int32(pin, "default_state", &def);
179 		if (!ok)
180 			continue;
181 		/* Extract pin type from the name */
182 		spptr = strstr(nptr, " ");
183 		if (spptr == NULL)
184 			continue;
185 		spptr += 1;
186 		strncpy(name, spptr, 31);
187 		sc->sc_pin_active[i] = act;
188 		if (!strncmp(nptr, "LED ", 4)) {
189 			sc->sc_mask &= ~(1 << num);
190 			pcf8574_attach_led(sc, name, num, act, def);
191 		}
192 		if (!strncmp(nptr, "INDICATOR ", 4)) {
193 			if (!sysmon) {
194 				sc->sc_sme = sysmon_envsys_create();
195 				sysmon = TRUE;
196 			}
197 			/* envsys sensor # to pin # mapping */
198 			sc->sc_pin_sensor[envc] = num;
199 			sc->sc_sensor[i].state = ENVSYS_SINVALID;
200 			sc->sc_sensor[i].units = ENVSYS_INDICATOR;
201 			strlcpy(sc->sc_sensor[i].desc, name,
202 			    sizeof(sc->sc_sensor[i].desc));
203 			if (sysmon_envsys_sensor_attach(sc->sc_sme,
204 			    &sc->sc_sensor[i])) {
205 				sysmon_envsys_destroy(sc->sc_sme);
206 				sc->sc_sme = NULL;
207 				aprint_error_dev(self,
208 				    "unable to attach pin %d at sysmon\n", i);
209 				return;
210 			}
211 			DPRINTF("%s: added indicator: pin %d sensor %d (%s)\n",
212 			    device_xname(sc->sc_dev), num, envc, name);
213 			envc++;
214 		}
215 	}
216 
217 	if (sysmon) {
218 		sc->sc_sme->sme_name = device_xname(self);
219 		sc->sc_sme->sme_cookie = sc;
220 		sc->sc_sme->sme_refresh = pcf8574_refresh;
221 		if (sysmon_envsys_register(sc->sc_sme)) {
222 			aprint_error_dev(self,
223 			    "unable to register with sysmon\n");
224 			sysmon_envsys_destroy(sc->sc_sme);
225 			sc->sc_sme = NULL;
226 			return;
227 		}
228 	}
229 }
230 
231 static int
232 pcf8574_detach(device_t self, int flags)
233 {
234 	struct pcf8574_softc *sc = device_private(self);
235 	int i;
236 
237 	if (sc->sc_sme != NULL) {
238 		sysmon_envsys_unregister(sc->sc_sme);
239 		sc->sc_sme = NULL;
240 	}
241 
242 	for (i = 0; i < sc->sc_nleds; i++)
243 		led_detach(sc->sc_leds[i].led);
244 
245 #ifdef PCF8574_DEBUG
246 	callout_halt(&sc->sc_timer, NULL);
247 	callout_destroy(&sc->sc_timer);
248 #endif
249 	return 0;
250 }
251 
252 static int
253 pcf8574_read(struct pcf8574_softc *sc, uint8_t *val)
254 {
255 	int err = 0;
256 
257 	if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0)
258 		return err;
259 	err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
260 	    sc->sc_addr, NULL, 0, val, 1, 0);
261 	iic_release_bus(sc->sc_tag, 0);
262 	return err;
263 }
264 
265 static int
266 pcf8574_write(struct pcf8574_softc *sc, uint8_t val)
267 {
268 	int err = 0;
269 
270 	if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0)
271 		return err;
272 	err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
273 	    &val, 1, NULL, 0, 0);
274 	iic_release_bus(sc->sc_tag, 0);
275 	return err;
276 }
277 
278 static void
279 pcf8574_attach_led(struct pcf8574_softc *sc, char *n, int pin, int act, int def)
280 {
281 	struct pcf8574_led *l;
282 
283 	l = &sc->sc_leds[sc->sc_nleds];
284 	l->cookie = sc;
285 	l->mask = 1 << pin;
286 	l->v_on = act ? l->mask : 0;
287 	l->v_off = act ? 0 : l->mask;
288 	led_attach(n, l, pcf8574_get_led, pcf8574_set_led);
289 	if (def != -1)
290 		pcf8574_set_led(l, def);
291 	DPRINTF("%s: attached LED: %02x %02x %02x def %d\n",
292 	    device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def);
293 	sc->sc_nleds++;
294 }
295 
296 void
297 pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
298 {
299 	struct pcf8574_softc *sc = sme->sme_cookie;
300 	int pin = sc->sc_pin_sensor[edata->sensor];
301 	int act = sc->sc_pin_active[pin];
302 
303 	pcf8574_read(sc, &sc->sc_state);
304 	if (act)
305 		edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE;
306 	else
307 		edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE;
308 	edata->state = ENVSYS_SVALID;
309 }
310 
311 int
312 pcf8574_get_led(void *cookie)
313 {
314 	struct pcf8574_led *l = cookie;
315 	struct pcf8574_softc *sc = l->cookie;
316 
317 	return ((sc->sc_state & l->mask) == l->v_on);
318 }
319 
320 void
321 pcf8574_set_led(void *cookie, int val)
322 {
323 	struct pcf8574_led *l = cookie;
324 	struct pcf8574_softc *sc = l->cookie;
325 	uint32_t newstate;
326 
327 	newstate = sc->sc_state & ~l->mask;
328 	newstate |= val ? l->v_on : l->v_off;
329 	DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n",
330 	    device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off);
331 	if (newstate != sc->sc_state) {
332 		pcf8574_write(sc, newstate | sc->sc_mask);
333 		pcf8574_read(sc, &sc->sc_state);
334 	}
335 }
336 
337 #ifdef PCF8574_DEBUG
338 static void
339 pcf8574_timeout(void *v)
340 {
341 	struct pcf8574_softc *sc = v;
342 	uint8_t val;
343 
344 	pcf8574_read(sc, &val);
345 	if (val != sc->sc_state)
346 		aprint_normal_dev(sc->sc_dev,
347 		    "status change: 0x%02x > 0x%02x\n", sc->sc_state, val);
348 	sc->sc_state = val;
349 
350 	callout_reset(&sc->sc_timer, hz*60, pcf8574_timeout, sc);
351 }
352 #endif
353