1 /* $NetBSD: pcf8574.c,v 1.10 2021/06/21 03:12:54 christos 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.10 2021/06/21 03:12:54 christos 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 #include <dev/sysmon/sysmon_taskq.h> 47 48 #include <dev/i2c/i2cvar.h> 49 #include <dev/led.h> 50 51 #ifdef PCF8574_DEBUG 52 #define DPRINTF printf 53 #else 54 #define DPRINTF if (0) printf 55 #endif 56 57 struct pcf8574_led { 58 void *cookie; 59 struct led_device *led; 60 uint8_t mask, v_on, v_off; 61 }; 62 63 struct pcf8574_pin { 64 int pin_sensor; 65 int pin_active; 66 char pin_desc[ENVSYS_DESCLEN]; 67 }; 68 69 #define PCF8574_NPINS 8 70 struct pcf8574_softc { 71 device_t sc_dev; 72 i2c_tag_t sc_tag; 73 i2c_addr_t sc_addr; 74 uint8_t sc_state; 75 uint8_t sc_mask; 76 77 uint8_t sc_alert_mask; 78 #define PCF8574_DEFAULT_TIMER 60 79 int sc_callout_time; 80 callout_t sc_timer; 81 82 int sc_nleds; 83 struct pcf8574_led sc_leds[PCF8574_NPINS]; 84 struct pcf8574_pin sc_pins[PCF8574_NPINS]; 85 86 struct sysmon_envsys *sc_sme; 87 envsys_data_t sc_sensor[PCF8574_NPINS]; 88 }; 89 90 static int pcf8574_match(device_t, cfdata_t, void *); 91 static void pcf8574_attach(device_t, device_t, void *); 92 static int pcf8574_detach(device_t, int); 93 94 static int pcf8574_read(struct pcf8574_softc *sc, uint8_t *); 95 static int pcf8574_write(struct pcf8574_softc *sc, uint8_t); 96 static void pcf8574_attach_led( 97 struct pcf8574_softc *, char *, int, int, int); 98 static int pcf8574_attach_sysmon( 99 struct pcf8574_softc *, char *, int, int, int); 100 void pcf8574_refresh(struct sysmon_envsys *, envsys_data_t *); 101 int pcf8574_get_led(void *); 102 void pcf8574_set_led(void *, int); 103 static void pcf8574_timeout(void *); 104 static void pcf8574_check(void *); 105 static void pcf8574_check_alert(struct pcf8574_softc *, uint8_t, uint8_t); 106 107 CFATTACH_DECL_NEW(pcf8574io, sizeof(struct pcf8574_softc), 108 pcf8574_match, pcf8574_attach, pcf8574_detach, NULL); 109 110 static const struct device_compatible_entry compat_data[] = { 111 { .compat = "i2c-pcf8574" }, 112 DEVICE_COMPAT_EOL 113 }; 114 115 static int 116 pcf8574_match(device_t parent, cfdata_t cf, void *aux) 117 { 118 struct i2c_attach_args *ia = aux; 119 int match_result; 120 121 if (iic_use_direct_match(ia, cf, compat_data, &match_result)) 122 return match_result; 123 124 /* We don't support indirect matches */ 125 return 0; 126 } 127 128 static void 129 pcf8574_attach(device_t parent, device_t self, void *aux) 130 { 131 struct pcf8574_softc *sc = device_private(self); 132 struct i2c_attach_args *ia = aux; 133 prop_dictionary_t dict = device_properties(self); 134 prop_array_t pins; 135 prop_dictionary_t pin; 136 int i, num, def, envc = 0; 137 char name[32]; 138 const char *nptr = NULL, *spptr; 139 bool ok = TRUE, act; 140 141 sc->sc_tag = ia->ia_tag; 142 sc->sc_addr = ia->ia_addr; 143 sc->sc_dev = self; 144 145 sc->sc_sme = NULL; 146 #ifdef PCF8574_DEBUG 147 sc->sc_callout_time = 60; /* watch for changes when debugging */ 148 #else 149 sc->sc_callout_time = 0; 150 #endif 151 152 /* 153 * The PCF8574 requires input pins to be written with the value 1, 154 * and then read. Assume that all pins are input initially. 155 * We'll use the mask when we write, because we have to write a 156 * value for every pin, and we don't want to change input pins. 157 */ 158 sc->sc_mask = 0xff; 159 160 /* Try a read, and fail if this component isn't present */ 161 if (pcf8574_read(sc, &sc->sc_state)) { 162 aprint_normal(": read failed\n"); 163 return; 164 } 165 166 #ifdef PCF8574_DEBUG 167 aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state); 168 #else 169 aprint_normal(": GPIO\n"); 170 #endif 171 172 pins = prop_dictionary_get(dict, "pins"); 173 if (pins == NULL) 174 return; 175 176 for (i = 0; i < prop_array_count(pins); i++) { 177 pin = prop_array_get(pins, i); 178 ok &= prop_dictionary_get_string(pin, "name", &nptr); 179 ok &= prop_dictionary_get_uint32(pin, "pin", &num); 180 ok &= prop_dictionary_get_bool(pin, "active_high", &act); 181 /* optional default state */ 182 def = -1; 183 prop_dictionary_get_int32(pin, "default_state", &def); 184 if (!ok) 185 continue; 186 /* Extract pin type from the name */ 187 spptr = strstr(nptr, " "); 188 if (spptr == NULL) 189 continue; 190 spptr += 1; 191 strncpy(name, spptr, 31); 192 sc->sc_pins[i].pin_active = act; 193 if (!strncmp(nptr, "LED ", 4)) { 194 sc->sc_mask &= ~(1 << num); 195 pcf8574_attach_led(sc, name, num, act, def); 196 } 197 if (!strncmp(nptr, "INDICATOR ", 10)) { 198 if (pcf8574_attach_sysmon(sc, name, envc, num, act)) 199 return; 200 envc++; 201 } 202 if (!strncmp(nptr, "ALERT ", 6)) { 203 u_int64_t alert_time; 204 205 if (pcf8574_attach_sysmon(sc, name, envc, num, act)) 206 return; 207 208 /* Adjust our timeout if the alert times out. */ 209 if (def != -1) 210 alert_time = def / 2; 211 else 212 alert_time = PCF8574_DEFAULT_TIMER; 213 214 if (sc->sc_callout_time == 0 || 215 alert_time < sc->sc_callout_time) 216 sc->sc_callout_time = alert_time; 217 218 sc->sc_alert_mask |= (1 << num); 219 envc++; 220 } 221 } 222 223 if (sc->sc_sme != NULL) { 224 sc->sc_sme->sme_name = device_xname(self); 225 sc->sc_sme->sme_cookie = sc; 226 sc->sc_sme->sme_refresh = pcf8574_refresh; 227 if (sysmon_envsys_register(sc->sc_sme)) { 228 aprint_error_dev(self, 229 "unable to register with sysmon\n"); 230 sysmon_envsys_destroy(sc->sc_sme); 231 sc->sc_sme = NULL; 232 return; 233 } 234 } 235 236 if (sc->sc_callout_time) { 237 callout_init(&sc->sc_timer, CALLOUT_MPSAFE); 238 callout_reset(&sc->sc_timer, hz * sc->sc_callout_time, 239 pcf8574_timeout, sc); 240 } 241 } 242 243 static int 244 pcf8574_detach(device_t self, int flags) 245 { 246 struct pcf8574_softc *sc = device_private(self); 247 int i; 248 249 if (sc->sc_callout_time) { 250 callout_halt(&sc->sc_timer, NULL); 251 callout_destroy(&sc->sc_timer); 252 sc->sc_callout_time = 0; 253 } 254 255 if (sc->sc_sme != NULL) { 256 sysmon_envsys_unregister(sc->sc_sme); 257 sc->sc_sme = NULL; 258 } 259 260 for (i = 0; i < sc->sc_nleds; i++) 261 led_detach(sc->sc_leds[i].led); 262 263 return 0; 264 } 265 266 static int 267 pcf8574_read(struct pcf8574_softc *sc, uint8_t *val) 268 { 269 int err = 0; 270 271 if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) 272 return err; 273 err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, 274 sc->sc_addr, NULL, 0, val, 1, 0); 275 iic_release_bus(sc->sc_tag, 0); 276 return err; 277 } 278 279 static int 280 pcf8574_write(struct pcf8574_softc *sc, uint8_t val) 281 { 282 int err = 0; 283 284 if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) 285 return err; 286 err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, 287 &val, 1, NULL, 0, 0); 288 iic_release_bus(sc->sc_tag, 0); 289 return err; 290 } 291 292 static void 293 pcf8574_attach_led(struct pcf8574_softc *sc, char *name, int pin, int act, 294 int def) 295 { 296 struct pcf8574_led *l; 297 298 l = &sc->sc_leds[sc->sc_nleds]; 299 l->cookie = sc; 300 l->mask = 1 << pin; 301 l->v_on = act ? l->mask : 0; 302 l->v_off = act ? 0 : l->mask; 303 led_attach(name, l, pcf8574_get_led, pcf8574_set_led); 304 if (def != -1) 305 pcf8574_set_led(l, def); 306 DPRINTF("%s: attached LED: %02x %02x %02x def %d\n", 307 device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def); 308 sc->sc_nleds++; 309 } 310 311 static int 312 pcf8574_attach_sysmon(struct pcf8574_softc *sc, char *name, int envc, int pin, 313 int act) 314 { 315 int ret; 316 317 if (sc->sc_sme == NULL) { 318 sc->sc_sme = sysmon_envsys_create(); 319 sc->sc_sme->sme_events_timeout = 0; 320 } 321 322 strlcpy(sc->sc_pins[pin].pin_desc, name, 323 sizeof(sc->sc_pins[pin].pin_desc)); 324 /* envsys sensor # to pin # mapping */ 325 sc->sc_pins[envc].pin_sensor = pin; 326 sc->sc_sensor[envc].state = ENVSYS_SINVALID; 327 sc->sc_sensor[envc].units = ENVSYS_INDICATOR; 328 strlcpy(sc->sc_sensor[envc].desc, name, 329 sizeof(sc->sc_sensor[envc].desc)); 330 ret = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[envc]); 331 if (ret) { 332 sysmon_envsys_destroy(sc->sc_sme); 333 sc->sc_sme = NULL; 334 aprint_error_dev(sc->sc_dev, 335 "unable to attach pin %d at sysmon\n", pin); 336 return ret; 337 } 338 DPRINTF("%s: added sysmon: pin %d sensor %d (%s)\n", 339 device_xname(sc->sc_dev), pin, envc, name); 340 return 0; 341 } 342 343 void 344 pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 345 { 346 struct pcf8574_softc *sc = sme->sme_cookie; 347 int pin = sc->sc_pins[edata->sensor].pin_sensor; 348 int act = sc->sc_pins[pin].pin_active; 349 u_int8_t prev_state = sc->sc_state; 350 351 pcf8574_read(sc, &sc->sc_state); 352 if (act) 353 edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE; 354 else 355 edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE; 356 edata->state = ENVSYS_SVALID; 357 358 /* We read all the pins, so check for alerts on any pin now */ 359 if (sc->sc_state != prev_state) { 360 DPRINTF("%s: (refresh) status change: 0x%02x > 0x%02x\n", 361 device_xname(sc->sc_dev), prev_state, sc->sc_state); 362 pcf8574_check_alert(sc, prev_state, sc->sc_state); 363 } 364 } 365 366 int 367 pcf8574_get_led(void *cookie) 368 { 369 struct pcf8574_led *l = cookie; 370 struct pcf8574_softc *sc = l->cookie; 371 372 return ((sc->sc_state & l->mask) == l->v_on); 373 } 374 375 void 376 pcf8574_set_led(void *cookie, int val) 377 { 378 struct pcf8574_led *l = cookie; 379 struct pcf8574_softc *sc = l->cookie; 380 uint32_t newstate; 381 382 newstate = sc->sc_state & ~l->mask; 383 newstate |= val ? l->v_on : l->v_off; 384 DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n", 385 device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off); 386 if (newstate != sc->sc_state) { 387 pcf8574_write(sc, newstate | sc->sc_mask); 388 pcf8574_read(sc, &sc->sc_state); 389 } 390 } 391 392 static void 393 pcf8574_timeout(void *v) 394 { 395 struct pcf8574_softc *sc = v; 396 397 sysmon_task_queue_sched(0, pcf8574_check, sc); 398 callout_reset(&sc->sc_timer, hz * sc->sc_callout_time, 399 pcf8574_timeout, sc); 400 } 401 402 static void 403 pcf8574_check(void *v) 404 { 405 struct pcf8574_softc *sc = v; 406 uint8_t prev_state = sc->sc_state; 407 408 pcf8574_read(sc, &sc->sc_state); 409 if (sc->sc_state != prev_state) { 410 DPRINTF("%s: (check) status change: 0x%02x > 0x%02x\n", 411 device_xname(sc->sc_dev), prev_state, sc->sc_state); 412 pcf8574_check_alert(sc, prev_state, sc->sc_state); 413 } 414 } 415 416 417 static void 418 pcf8574_check_alert(struct pcf8574_softc *sc, uint8_t prev_state, 419 uint8_t curr_state) 420 { 421 int i; 422 uint8_t pin_chg; 423 424 for (i = 0; i < PCF8574_NPINS; i++) { 425 pin_chg = (sc->sc_state & 1 << i) ^ (prev_state & 1 << i); 426 if (pin_chg & sc->sc_alert_mask) { 427 if (sc->sc_pins[i].pin_active) 428 printf("%s: Alert: %s = %s\n", 429 device_xname(sc->sc_dev), 430 sc->sc_pins[i].pin_desc, 431 sc->sc_state & 1 << i ? "True" : "False"); 432 else 433 printf("%s: Alert: %s = %s\n", 434 device_xname(sc->sc_dev), 435 sc->sc_pins[i].pin_desc, 436 sc->sc_state & 1 << i ? "False" : "True"); 437 } 438 } 439 } 440