pcf8574.c revision 1.3 1 1.3 jdc /* $NetBSD: pcf8574.c,v 1.3 2020/12/05 14:48:09 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.3 jdc __KERNEL_RCSID(0, "$NetBSD: pcf8574.c,v 1.3 2020/12/05 14:48:09 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.1 jdc
47 1.1 jdc #include <dev/i2c/i2cvar.h>
48 1.1 jdc #include <dev/led.h>
49 1.1 jdc
50 1.1 jdc #ifdef PCF8574_DEBUG
51 1.1 jdc #define DPRINTF printf
52 1.1 jdc #else
53 1.1 jdc #define DPRINTF if (0) printf
54 1.1 jdc #endif
55 1.1 jdc
56 1.1 jdc struct pcf8574_led {
57 1.1 jdc void *cookie;
58 1.2 jdc struct led_device *led;
59 1.1 jdc uint8_t mask, v_on, v_off;
60 1.1 jdc };
61 1.1 jdc
62 1.1 jdc #define PCF8574_NPINS 8
63 1.1 jdc struct pcf8574_softc {
64 1.1 jdc device_t sc_dev;
65 1.1 jdc i2c_tag_t sc_tag;
66 1.1 jdc i2c_addr_t sc_addr;
67 1.1 jdc uint8_t sc_state;
68 1.1 jdc uint8_t sc_mask;
69 1.1 jdc
70 1.1 jdc int sc_nleds;
71 1.1 jdc struct pcf8574_led sc_leds[PCF8574_NPINS];
72 1.1 jdc
73 1.1 jdc struct sysmon_envsys *sc_sme;
74 1.1 jdc envsys_data_t sc_sensor[PCF8574_NPINS];
75 1.1 jdc int sc_pin_sensor[PCF8574_NPINS];
76 1.1 jdc int sc_pin_active[PCF8574_NPINS];
77 1.1 jdc
78 1.1 jdc #ifdef PCF8574_DEBUG
79 1.1 jdc callout_t sc_timer;
80 1.1 jdc #endif
81 1.1 jdc };
82 1.1 jdc
83 1.1 jdc static int pcf8574_match(device_t, cfdata_t, void *);
84 1.1 jdc static void pcf8574_attach(device_t, device_t, void *);
85 1.1 jdc static int pcf8574_detach(device_t, int);
86 1.1 jdc
87 1.1 jdc static int pcf8574_read(struct pcf8574_softc *sc, uint8_t *val);
88 1.1 jdc static int pcf8574_write(struct pcf8574_softc *sc, uint8_t val);
89 1.1 jdc static void pcf8574_attach_led(
90 1.1 jdc struct pcf8574_softc *, char *, int, int, int);
91 1.1 jdc void pcf8574_refresh(struct sysmon_envsys *, envsys_data_t *);
92 1.1 jdc int pcf8574_get_led(void *);
93 1.1 jdc void pcf8574_set_led(void *, int);
94 1.1 jdc
95 1.1 jdc #ifdef PCF8574_DEBUG
96 1.1 jdc static void pcf8574_timeout(void *);
97 1.1 jdc #endif
98 1.1 jdc
99 1.1 jdc CFATTACH_DECL_NEW(pcf8574io, sizeof(struct pcf8574_softc),
100 1.1 jdc pcf8574_match, pcf8574_attach, pcf8574_detach, NULL);
101 1.1 jdc
102 1.1 jdc static const struct device_compatible_entry compat_data[] = {
103 1.1 jdc { "i2c-pcf8574", 0 },
104 1.1 jdc { NULL, 0 }
105 1.1 jdc };
106 1.1 jdc
107 1.1 jdc static int
108 1.1 jdc pcf8574_match(device_t parent, cfdata_t cf, void *aux)
109 1.1 jdc {
110 1.1 jdc struct i2c_attach_args *ia = aux;
111 1.3 jdc struct pcf8574_softc sc;
112 1.1 jdc int match_result;
113 1.1 jdc
114 1.3 jdc if (!iic_use_direct_match(ia, cf, compat_data, &match_result))
115 1.3 jdc return 0;
116 1.3 jdc
117 1.3 jdc /* Try a read so that we don't match on optional components */
118 1.3 jdc if (match_result) {
119 1.3 jdc sc.sc_tag = ia->ia_tag;
120 1.3 jdc sc.sc_addr = ia->ia_addr;
121 1.3 jdc if (pcf8574_read(&sc, &sc.sc_state))
122 1.3 jdc return 0;
123 1.3 jdc else
124 1.3 jdc return 1;
125 1.3 jdc }
126 1.1 jdc
127 1.1 jdc /* We don't support indirect matches */
128 1.1 jdc return 0;
129 1.1 jdc }
130 1.1 jdc
131 1.1 jdc static void
132 1.1 jdc pcf8574_attach(device_t parent, device_t self, void *aux)
133 1.1 jdc {
134 1.1 jdc struct pcf8574_softc *sc = device_private(self);
135 1.1 jdc struct i2c_attach_args *ia = aux;
136 1.1 jdc prop_dictionary_t dict = device_properties(self);
137 1.1 jdc prop_array_t pins;
138 1.1 jdc prop_dictionary_t pin;
139 1.1 jdc int i, num, def, envc = 0;
140 1.1 jdc char name[32];
141 1.1 jdc const char *nptr = NULL, *spptr;
142 1.1 jdc bool ok = TRUE, act, sysmon = FALSE;
143 1.1 jdc
144 1.1 jdc sc->sc_tag = ia->ia_tag;
145 1.1 jdc sc->sc_addr = ia->ia_addr;
146 1.1 jdc sc->sc_dev = self;
147 1.1 jdc
148 1.1 jdc /*
149 1.1 jdc * The PCF8574 requires input pins to be written with the value 1,
150 1.1 jdc * and then read. Assume that all pins are input initially.
151 1.1 jdc * We'll use the mask when we write, because we have to write a
152 1.1 jdc * value for every pin, and we don't want to change input pins.
153 1.1 jdc */
154 1.1 jdc sc->sc_mask = 0xff;
155 1.1 jdc
156 1.1 jdc pcf8574_read(sc, &sc->sc_state);
157 1.1 jdc
158 1.1 jdc #ifdef PCF8574_DEBUG
159 1.1 jdc aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state);
160 1.1 jdc
161 1.1 jdc callout_init(&sc->sc_timer, CALLOUT_MPSAFE);
162 1.1 jdc callout_reset(&sc->sc_timer, hz*30, pcf8574_timeout, sc);
163 1.1 jdc #else
164 1.1 jdc aprint_normal(": GPIO\n");
165 1.1 jdc #endif
166 1.1 jdc
167 1.1 jdc pins = prop_dictionary_get(dict, "pins");
168 1.1 jdc if (pins == NULL)
169 1.1 jdc return;
170 1.1 jdc
171 1.1 jdc for (i = 0; i < prop_array_count(pins); i++) {
172 1.1 jdc pin = prop_array_get(pins, i);
173 1.1 jdc ok &= prop_dictionary_get_cstring_nocopy(pin, "name", &nptr);
174 1.1 jdc ok &= prop_dictionary_get_uint32(pin, "pin", &num);
175 1.1 jdc ok &= prop_dictionary_get_bool(pin, "active_high", &act);
176 1.1 jdc /* optional default state */
177 1.1 jdc def = -1;
178 1.1 jdc prop_dictionary_get_int32(pin, "default_state", &def);
179 1.1 jdc if (!ok)
180 1.1 jdc continue;
181 1.1 jdc /* Extract pin type from the name */
182 1.1 jdc spptr = strstr(nptr, " ");
183 1.1 jdc if (spptr == NULL)
184 1.1 jdc continue;
185 1.1 jdc spptr += 1;
186 1.1 jdc strncpy(name, spptr, 31);
187 1.1 jdc sc->sc_pin_active[i] = act;
188 1.1 jdc if (!strncmp(nptr, "LED ", 4)) {
189 1.1 jdc sc->sc_mask &= ~(1 << num);
190 1.1 jdc pcf8574_attach_led(sc, name, num, act, def);
191 1.1 jdc }
192 1.1 jdc if (!strncmp(nptr, "INDICATOR ", 4)) {
193 1.1 jdc if (!sysmon) {
194 1.1 jdc sc->sc_sme = sysmon_envsys_create();
195 1.1 jdc sysmon = TRUE;
196 1.1 jdc }
197 1.1 jdc /* envsys sensor # to pin # mapping */
198 1.1 jdc sc->sc_pin_sensor[envc] = num;
199 1.1 jdc sc->sc_sensor[i].state = ENVSYS_SINVALID;
200 1.1 jdc sc->sc_sensor[i].units = ENVSYS_INDICATOR;
201 1.1 jdc strlcpy(sc->sc_sensor[i].desc, name,
202 1.1 jdc sizeof(sc->sc_sensor[i].desc));
203 1.1 jdc if (sysmon_envsys_sensor_attach(sc->sc_sme,
204 1.1 jdc &sc->sc_sensor[i])) {
205 1.1 jdc sysmon_envsys_destroy(sc->sc_sme);
206 1.3 jdc sc->sc_sme = NULL;
207 1.1 jdc aprint_error_dev(self,
208 1.1 jdc "unable to attach pin %d at sysmon\n", i);
209 1.1 jdc return;
210 1.1 jdc }
211 1.1 jdc DPRINTF("%s: added indicator: pin %d sensor %d (%s)\n",
212 1.1 jdc device_xname(sc->sc_dev), num, envc, name);
213 1.1 jdc envc++;
214 1.1 jdc }
215 1.1 jdc }
216 1.1 jdc
217 1.1 jdc if (sysmon) {
218 1.1 jdc sc->sc_sme->sme_name = device_xname(self);
219 1.1 jdc sc->sc_sme->sme_cookie = sc;
220 1.1 jdc sc->sc_sme->sme_refresh = pcf8574_refresh;
221 1.1 jdc if (sysmon_envsys_register(sc->sc_sme)) {
222 1.1 jdc aprint_error_dev(self,
223 1.1 jdc "unable to register with sysmon\n");
224 1.1 jdc sysmon_envsys_destroy(sc->sc_sme);
225 1.3 jdc sc->sc_sme = NULL;
226 1.1 jdc return;
227 1.1 jdc }
228 1.1 jdc }
229 1.1 jdc }
230 1.1 jdc
231 1.1 jdc static int
232 1.1 jdc pcf8574_detach(device_t self, int flags)
233 1.1 jdc {
234 1.1 jdc struct pcf8574_softc *sc = device_private(self);
235 1.2 jdc int i;
236 1.2 jdc
237 1.3 jdc if (sc->sc_sme != NULL) {
238 1.2 jdc sysmon_envsys_unregister(sc->sc_sme);
239 1.3 jdc sc->sc_sme = NULL;
240 1.3 jdc }
241 1.2 jdc
242 1.2 jdc for (i = 0; i < sc->sc_nleds; i++)
243 1.2 jdc led_detach(sc->sc_leds[i].led);
244 1.1 jdc
245 1.2 jdc #ifdef PCF8574_DEBUG
246 1.1 jdc callout_halt(&sc->sc_timer, NULL);
247 1.1 jdc callout_destroy(&sc->sc_timer);
248 1.1 jdc #endif
249 1.1 jdc return 0;
250 1.1 jdc }
251 1.1 jdc
252 1.1 jdc static int
253 1.1 jdc pcf8574_read(struct pcf8574_softc *sc, uint8_t *val)
254 1.1 jdc {
255 1.1 jdc int err = 0;
256 1.1 jdc
257 1.1 jdc if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0)
258 1.1 jdc return err;
259 1.1 jdc err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP,
260 1.1 jdc sc->sc_addr, NULL, 0, val, 1, 0);
261 1.1 jdc iic_release_bus(sc->sc_tag, 0);
262 1.1 jdc return err;
263 1.1 jdc }
264 1.1 jdc
265 1.1 jdc static int
266 1.1 jdc pcf8574_write(struct pcf8574_softc *sc, uint8_t val)
267 1.1 jdc {
268 1.1 jdc int err = 0;
269 1.1 jdc
270 1.1 jdc if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0)
271 1.1 jdc return err;
272 1.1 jdc err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr,
273 1.1 jdc &val, 1, NULL, 0, 0);
274 1.1 jdc iic_release_bus(sc->sc_tag, 0);
275 1.1 jdc return err;
276 1.1 jdc }
277 1.1 jdc
278 1.1 jdc static void
279 1.1 jdc pcf8574_attach_led(struct pcf8574_softc *sc, char *n, int pin, int act, int def)
280 1.1 jdc {
281 1.1 jdc struct pcf8574_led *l;
282 1.1 jdc
283 1.1 jdc l = &sc->sc_leds[sc->sc_nleds];
284 1.1 jdc l->cookie = sc;
285 1.1 jdc l->mask = 1 << pin;
286 1.1 jdc l->v_on = act ? l->mask : 0;
287 1.1 jdc l->v_off = act ? 0 : l->mask;
288 1.1 jdc led_attach(n, l, pcf8574_get_led, pcf8574_set_led);
289 1.1 jdc if (def != -1)
290 1.1 jdc pcf8574_set_led(l, def);
291 1.1 jdc DPRINTF("%s: attached LED: %02x %02x %02x def %d\n",
292 1.1 jdc device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def);
293 1.1 jdc sc->sc_nleds++;
294 1.1 jdc }
295 1.1 jdc
296 1.1 jdc void
297 1.1 jdc pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata)
298 1.1 jdc {
299 1.1 jdc struct pcf8574_softc *sc = sme->sme_cookie;
300 1.1 jdc int pin = sc->sc_pin_sensor[edata->sensor];
301 1.1 jdc int act = sc->sc_pin_active[pin];
302 1.1 jdc
303 1.1 jdc pcf8574_read(sc, &sc->sc_state);
304 1.1 jdc if (act)
305 1.1 jdc edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE;
306 1.1 jdc else
307 1.1 jdc edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE;
308 1.1 jdc edata->state = ENVSYS_SVALID;
309 1.1 jdc }
310 1.1 jdc
311 1.1 jdc int
312 1.1 jdc pcf8574_get_led(void *cookie)
313 1.1 jdc {
314 1.1 jdc struct pcf8574_led *l = cookie;
315 1.1 jdc struct pcf8574_softc *sc = l->cookie;
316 1.1 jdc
317 1.1 jdc return ((sc->sc_state & l->mask) == l->v_on);
318 1.1 jdc }
319 1.1 jdc
320 1.1 jdc void
321 1.1 jdc pcf8574_set_led(void *cookie, int val)
322 1.1 jdc {
323 1.1 jdc struct pcf8574_led *l = cookie;
324 1.1 jdc struct pcf8574_softc *sc = l->cookie;
325 1.1 jdc uint32_t newstate;
326 1.1 jdc
327 1.1 jdc newstate = sc->sc_state & ~l->mask;
328 1.1 jdc newstate |= val ? l->v_on : l->v_off;
329 1.1 jdc DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n",
330 1.1 jdc device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off);
331 1.1 jdc if (newstate != sc->sc_state) {
332 1.1 jdc pcf8574_write(sc, newstate | sc->sc_mask);
333 1.1 jdc pcf8574_read(sc, &sc->sc_state);
334 1.1 jdc }
335 1.1 jdc }
336 1.1 jdc
337 1.1 jdc #ifdef PCF8574_DEBUG
338 1.1 jdc static void
339 1.1 jdc pcf8574_timeout(void *v)
340 1.1 jdc {
341 1.1 jdc struct pcf8574_softc *sc = v;
342 1.1 jdc uint8_t val;
343 1.1 jdc
344 1.1 jdc pcf8574_read(sc, &val);
345 1.1 jdc if (val != sc->sc_state)
346 1.1 jdc aprint_normal_dev(sc->sc_dev,
347 1.1 jdc "status change: 0x%02x > 0x%02x\n", sc->sc_state, val);
348 1.1 jdc sc->sc_state = val;
349 1.1 jdc
350 1.1 jdc callout_reset(&sc->sc_timer, hz*60, pcf8574_timeout, sc);
351 1.1 jdc }
352 1.1 jdc #endif
353