1 /* $NetBSD: pcagpio.c,v 1.16 2026/09/03 07:55:10 jdc Exp $ */ 2 3 /*- 4 * Copyright (c) 2020 Michael Lorenz 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 /* 30 * a driver for Philips Semiconductor PCA9555 GPIO controllers 31 */ 32 33 #include <sys/cdefs.h> 34 __KERNEL_RCSID(0, "$NetBSD: pcagpio.c,v 1.16 2026/09/03 07:55:10 jdc Exp $"); 35 36 #include <sys/param.h> 37 #include <sys/systm.h> 38 #include <sys/device.h> 39 #include <sys/conf.h> 40 #include <sys/bus.h> 41 42 #include <dev/sysmon/sysmonvar.h> 43 44 #include <dev/i2c/i2cvar.h> 45 #include <dev/led.h> 46 47 #ifdef PCAGPIO_DEBUG 48 #define DPRINTF printf 49 #else 50 #define DPRINTF if (0) printf 51 #endif 52 53 /* commands */ 54 #define PCAGPIO_INPUT 0x00 /* line status */ 55 #define PCAGPIO_OUTPUT 0x01 /* output status */ 56 #define PCAGPIO_REVERT 0x02 /* revert input if set */ 57 #define PCAGPIO_CONFIG 0x03 /* input if set, output if not */ 58 59 static int pcagpio_match(device_t, cfdata_t, void *); 60 static void pcagpio_attach(device_t, device_t, void *); 61 static int pcagpio_detach(device_t, int); 62 63 /* we can only pass one cookie to led_attach() but we need several values... */ 64 struct pcagpio_led { 65 void *cookie; 66 struct led_device *led; 67 uint32_t mask, v_on, v_off; 68 }; 69 70 struct pcagpio_pin { 71 int pin_sensor; 72 int pin_active; 73 int pin_default; 74 char pin_desc[ENVSYS_DESCLEN]; 75 }; 76 77 struct pcagpio_softc { 78 device_t sc_dev; 79 i2c_tag_t sc_i2c; 80 i2c_addr_t sc_addr; 81 82 int sc_is_16bit; 83 uint32_t sc_state, sc_dir, sc_in; 84 struct pcagpio_led sc_leds[16]; 85 struct pcagpio_pin sc_pins[16]; 86 int sc_nleds; 87 88 struct sysmon_envsys *sc_sme; 89 envsys_data_t sc_sensor[16]; 90 }; 91 92 93 static void pcagpio_writereg(struct pcagpio_softc *, int, uint32_t); 94 static uint32_t pcagpio_readreg(struct pcagpio_softc *, int); 95 static void pcagpio_attach_led( 96 struct pcagpio_softc *, char *, int, int, int); 97 static int pcagpio_attach_sysmon( 98 struct pcagpio_softc *, char *, int, int, int, int); 99 void pcagpio_refresh(struct sysmon_envsys *, envsys_data_t *); 100 static int pcagpio_get(void *); 101 static void pcagpio_set(void *, int); 102 103 CFATTACH_DECL_NEW(pcagpio, sizeof(struct pcagpio_softc), 104 pcagpio_match, pcagpio_attach, pcagpio_detach, NULL); 105 106 static const struct device_compatible_entry compat_data[] = { 107 { .compat = "i2c-pca9555", .value = 1 }, 108 { .compat = "pca9555", .value = 1 }, 109 { .compat = "i2c-pca9556", .value = 0 }, 110 { .compat = "pca9556", .value = 0 }, 111 DEVICE_COMPAT_EOL 112 }; 113 114 static int 115 pcagpio_match(device_t parent, cfdata_t match, void *aux) 116 { 117 struct i2c_attach_args *ia = aux; 118 int match_result; 119 120 if (iic_use_direct_match(ia, match, compat_data, &match_result)) 121 return match_result; 122 123 return 0; 124 } 125 126 #ifdef PCAGPIO_DEBUG 127 static void 128 printdir(const char* name, uint32_t val, uint32_t mask, char letter) 129 { 130 char flags[17], bits[17]; 131 uint32_t bit = 0x8000; 132 int i; 133 134 val &= mask; 135 for (i = 0; i < 16; i++) { 136 flags[i] = (mask & bit) ? letter : '-'; 137 bits[i] = (val & bit) ? 'X' : ' '; 138 bit = bit >> 1; 139 } 140 flags[16] = 0; 141 bits[16] = 0; 142 printf("%s: dir: %s\n", name, flags); 143 printf("%s: lvl: %s\n", name, bits); 144 } 145 #endif 146 147 static void 148 pcagpio_attach(device_t parent, device_t self, void *aux) 149 { 150 struct pcagpio_softc *sc = device_private(self); 151 struct i2c_attach_args *ia = aux; 152 const struct device_compatible_entry *dce; 153 prop_dictionary_t dict = device_properties(self); 154 prop_array_t pins; 155 prop_dictionary_t pin; 156 int envc = 0; 157 158 sc->sc_dev = self; 159 sc->sc_i2c = ia->ia_tag; 160 sc->sc_addr = ia->ia_addr; 161 sc->sc_nleds = 0; 162 sc->sc_sme = NULL; 163 164 aprint_naive("\n"); 165 sc->sc_is_16bit = 0; 166 if ((dce = iic_compatible_lookup(ia, compat_data)) != NULL) 167 sc->sc_is_16bit = dce->value; 168 169 aprint_normal(": %s\n", sc->sc_is_16bit ? "PCA9555" : "PCA9556"); 170 171 sc->sc_state = pcagpio_readreg(sc, PCAGPIO_OUTPUT); 172 173 #ifdef PCAGPIO_DEBUG 174 uint32_t in, out; 175 sc->sc_dir = pcagpio_readreg(sc, PCAGPIO_CONFIG); 176 sc->sc_in = pcagpio_readreg(sc, PCAGPIO_INPUT); 177 in = sc-> sc_in; 178 out = sc->sc_state; 179 180 out &= ~sc->sc_dir; 181 in &= sc->sc_dir; 182 183 printdir(device_xname(sc->sc_dev), in, sc->sc_dir, 'I'); 184 printdir(device_xname(sc->sc_dev), out, ~sc->sc_dir, 'O'); 185 186 #endif 187 188 pins = prop_dictionary_get(dict, "pins"); 189 if (pins != NULL) { 190 int i, num, def; 191 char name[32]; 192 const char *spptr, *nptr; 193 bool ok = TRUE, act; 194 195 for (i = 0; i < prop_array_count(pins); i++) { 196 nptr = NULL; 197 pin = prop_array_get(pins, i); 198 ok &= prop_dictionary_get_string(pin, "name", 199 &nptr); 200 ok &= prop_dictionary_get_uint32(pin, "pin", &num); 201 ok &= prop_dictionary_get_bool( pin, "active_high", 202 &act); 203 /* optional default state */ 204 def = -1; 205 prop_dictionary_get_int32(pin, "default_state", &def); 206 if (!ok) 207 continue; 208 /* Extract pin type from the name */ 209 spptr = strstr(nptr, " "); 210 if (spptr == NULL) 211 continue; 212 spptr += 1; 213 strncpy(name, spptr, 31); 214 215 216 /* LED's with an optional default state to set. */ 217 if (!strncmp(nptr, "LED ", 4)) 218 pcagpio_attach_led(sc, name, num, act, def); 219 220 /* Indicator: status is either true, or false. */ 221 if (!strncmp(nptr, "INDICATOR ", 10)) { 222 if (pcagpio_attach_sysmon(sc, 223 name, envc, num, act, -1)) 224 return; 225 envc++; 226 } 227 228 /* 229 * Alert: 230 * - status is either true, or false 231 * - set envsys state to critical if a default is set 232 * and status != default 233 * - display a message if the status changes 234 */ 235 if (!strncmp(nptr, "ALERT ", 6)) { 236 if (pcagpio_attach_sysmon(sc, 237 name, envc, num, act, -1)) 238 return; 239 envc++; 240 } 241 } 242 } 243 244 if (sc->sc_sme != NULL) { 245 sc->sc_sme->sme_name = device_xname(self); 246 sc->sc_sme->sme_cookie = sc; 247 sc->sc_sme->sme_refresh = pcagpio_refresh; 248 if (sysmon_envsys_register(sc->sc_sme)) { 249 aprint_error_dev(self, 250 "unable to register with sysmon\n"); 251 sysmon_envsys_destroy(sc->sc_sme); 252 sc->sc_sme = NULL; 253 return; 254 } 255 } 256 } 257 258 static int 259 pcagpio_detach(device_t self, int flags) 260 { 261 struct pcagpio_softc *sc = device_private(self); 262 int i; 263 264 for (i = 0; i < sc->sc_nleds; i++) 265 led_detach(sc->sc_leds[i].led); 266 267 if (sc->sc_sme != NULL) { 268 sysmon_envsys_unregister(sc->sc_sme); 269 sc->sc_sme = NULL; 270 } 271 272 return 0; 273 } 274 275 static void 276 pcagpio_writereg(struct pcagpio_softc *sc, int reg, uint32_t val) 277 { 278 uint8_t cmd; 279 280 iic_acquire_bus(sc->sc_i2c, 0); 281 if (sc->sc_is_16bit) { 282 uint16_t creg; 283 cmd = reg << 1; 284 creg = htole16(val); 285 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 286 sc->sc_addr, &cmd, 1, &creg, 2, 0); 287 } else { 288 uint8_t creg; 289 cmd = reg; 290 creg = (uint8_t)val; 291 iic_exec(sc->sc_i2c, I2C_OP_WRITE_WITH_STOP, 292 sc->sc_addr, &cmd, 1, &creg, 1, 0); 293 } 294 if (reg == PCAGPIO_OUTPUT) sc->sc_state = val; 295 iic_release_bus(sc->sc_i2c, 0); 296 } 297 298 static uint32_t pcagpio_readreg(struct pcagpio_softc *sc, int reg) 299 { 300 uint8_t cmd; 301 uint32_t ret; 302 303 iic_acquire_bus(sc->sc_i2c, 0); 304 if (sc->sc_is_16bit) { 305 uint16_t creg; 306 cmd = reg << 1; 307 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 308 sc->sc_addr, &cmd, 1, &creg, 2, 0); 309 ret = le16toh(creg); 310 } else { 311 uint8_t creg; 312 cmd = reg; 313 iic_exec(sc->sc_i2c, I2C_OP_READ_WITH_STOP, 314 sc->sc_addr, &cmd, 1, &creg, 1, 0); 315 ret = creg; 316 } 317 iic_release_bus(sc->sc_i2c, 0); 318 return ret; 319 } 320 321 static void 322 pcagpio_attach_led(struct pcagpio_softc *sc, char *n, int pin, int act, int def) 323 { 324 struct pcagpio_led *l; 325 326 l = &sc->sc_leds[sc->sc_nleds]; 327 l->cookie = sc; 328 l->mask = 1 << pin; 329 l->v_on = act ? l->mask : 0; 330 l->v_off = act ? 0 : l->mask; 331 l->led = led_attach(n, l, pcagpio_get, pcagpio_set); 332 if (def != -1) pcagpio_set(l, def); 333 DPRINTF("%s: %04x %04x %04x def %d\n", 334 __func__, l->mask, l->v_on, l->v_off, def); 335 sc->sc_nleds++; 336 } 337 338 static int 339 pcagpio_attach_sysmon(struct pcagpio_softc *sc, char *name, int envc, int pin, 340 int act, int def) 341 { 342 int ret; 343 344 if (sc->sc_sme == NULL) { 345 sc->sc_sme = sysmon_envsys_create(); 346 sc->sc_sme->sme_events_timeout = 0; 347 } 348 349 strlcpy(sc->sc_pins[pin].pin_desc, name, 350 sizeof(sc->sc_pins[pin].pin_desc)); 351 /* envsys sensor # to pin # mapping */ 352 sc->sc_pins[envc].pin_sensor = pin; 353 sc->sc_sensor[envc].state = ENVSYS_SINVALID; 354 sc->sc_sensor[envc].units = ENVSYS_INDICATOR; 355 if (def == -1) 356 sc->sc_pins[envc].pin_default = def; 357 else { 358 sc->sc_pins[envc].pin_default = def << pin; 359 sc->sc_sensor[envc].flags = ENVSYS_FMONCRITICAL; 360 } 361 strlcpy(sc->sc_sensor[envc].desc, name, 362 sizeof(sc->sc_sensor[envc].desc)); 363 ret = sysmon_envsys_sensor_attach(sc->sc_sme, &sc->sc_sensor[envc]); 364 if (ret) { 365 sysmon_envsys_destroy(sc->sc_sme); 366 sc->sc_sme = NULL; 367 aprint_error_dev(sc->sc_dev, 368 "unable to attach pin %d at sysmon\n", pin); 369 return ret; 370 } 371 DPRINTF("%s: added sysmon: pin %d sensor %d (%s)\n", 372 device_xname(sc->sc_dev), pin, envc, name); 373 return 0; 374 } 375 376 void 377 pcagpio_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) 378 { 379 struct pcagpio_softc *sc = sme->sme_cookie; 380 int pin = sc->sc_pins[edata->sensor].pin_sensor; 381 int act = sc->sc_pins[edata->sensor].pin_active; 382 int def = sc->sc_pins[edata->sensor].pin_default; 383 u_int32_t prev_state = sc->sc_in; 384 385 sc->sc_in = pcagpio_readreg(sc, PCAGPIO_INPUT); 386 if (act) 387 edata->value_cur = sc->sc_in & 1 << pin ? TRUE : FALSE; 388 else 389 edata->value_cur = sc->sc_in & 1 << pin ? FALSE : TRUE; 390 391 /* Is this an alert pin with a default value? */ 392 if (def != -1 && (sc->sc_in & (1 << pin)) != def) 393 edata->state = ENVSYS_SCRITICAL; 394 else 395 edata->state = ENVSYS_SVALID; 396 397 if (sc->sc_state != prev_state) { 398 DPRINTF("%s: (refresh) status change: 0x%02x > 0x%02x\n", 399 device_xname(sc->sc_dev), prev_state, sc->sc_state); 400 } 401 } 402 403 static int 404 pcagpio_get(void *cookie) 405 { 406 struct pcagpio_led *l = cookie; 407 struct pcagpio_softc *sc = l->cookie; 408 409 return ((sc->sc_state & l->mask) == l->v_on); 410 } 411 412 static void 413 pcagpio_set(void *cookie, int val) 414 { 415 struct pcagpio_led *l = cookie; 416 struct pcagpio_softc *sc = l->cookie; 417 uint32_t newstate; 418 419 newstate = sc->sc_state & ~l->mask; 420 newstate |= val ? l->v_on : l->v_off; 421 DPRINTF("%s: %04x -> %04x, %04x %04x %04x\n", __func__, 422 sc->sc_state, newstate, l->mask, l->v_on, l->v_off); 423 if (newstate != sc->sc_state) 424 pcagpio_writereg(sc, PCAGPIO_OUTPUT, newstate); 425 } 426