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