pcf8574.c revision 1.3 1 /* $NetBSD: pcf8574.c,v 1.3 2020/12/05 14:48:09 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.3 2020/12/05 14:48:09 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 1;
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