Home | History | Annotate | Line # | Download | only in i2c
      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