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