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