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