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