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