Module Name: src Committed By: jdc Date: Thu Oct 29 06:55:51 UTC 2020
Modified Files: src/sys/dev/i2c: files.i2c Added Files: src/sys/dev/i2c: pcf8574.c Log Message: Add a driver for the PCF8574 I/O expander, used as a GPIO in some sparc64 hardware. The driver currently handles pins configured as LED or INDICATOR and adds them to the LED and sysmon_envsys subsystems, respectively. To generate a diff of this commit: cvs rdiff -u -r1.111 -r1.112 src/sys/dev/i2c/files.i2c cvs rdiff -u -r0 -r1.1 src/sys/dev/i2c/pcf8574.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/dev/i2c/files.i2c diff -u src/sys/dev/i2c/files.i2c:1.111 src/sys/dev/i2c/files.i2c:1.112 --- src/sys/dev/i2c/files.i2c:1.111 Fri Apr 24 12:38:31 2020 +++ src/sys/dev/i2c/files.i2c Thu Oct 29 06:55:51 2020 @@ -1,4 +1,4 @@ -# $NetBSD: files.i2c,v 1.111 2020/04/24 12:38:31 macallan Exp $ +# $NetBSD: files.i2c,v 1.112 2020/10/29 06:55:51 jdc Exp $ obsolete defflag opt_i2cbus.h I2C_SCAN define i2cbus { } @@ -395,3 +395,8 @@ file dev/i2c/cwfg.c cwfg device pcagpio: leds attach pcagpio at iic file dev/i2c/pcagpio.c pcagpio + +# Philips PCF8574 IO expander +device pcf8574io: leds, sysmon_envsys +attach pcf8574io at iic +file dev/i2c/pcf8574.c pcf8574io Added files: Index: src/sys/dev/i2c/pcf8574.c diff -u /dev/null src/sys/dev/i2c/pcf8574.c:1.1 --- /dev/null Thu Oct 29 06:55:51 2020 +++ src/sys/dev/i2c/pcf8574.c Thu Oct 29 06:55:51 2020 @@ -0,0 +1,328 @@ +/*- + * Copyright (c) 2020 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Julian Coleman. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * A driver for Philips Semiconductor (NXP) PCF8574/PCF857A GPIO's. + * Uses device properties to connect pins to the appropriate subsystem. + */ + +#include <sys/cdefs.h> +__KERNEL_RCSID(0, "$NetBSD: pcf8574.c,v 1.1 2020/10/29 06:55:51 jdc Exp $"); + +#include <sys/param.h> +#include <sys/systm.h> +#include <sys/device.h> +#include <sys/kernel.h> + +#include <dev/sysmon/sysmonvar.h> + +#include <dev/i2c/i2cvar.h> +#include <dev/led.h> + +#ifdef PCF8574_DEBUG +#define DPRINTF printf +#else +#define DPRINTF if (0) printf +#endif + +struct pcf8574_led { + void *cookie; + uint8_t mask, v_on, v_off; +}; + +#define PCF8574_NPINS 8 +struct pcf8574_softc { + device_t sc_dev; + i2c_tag_t sc_tag; + i2c_addr_t sc_addr; + uint8_t sc_state; + uint8_t sc_mask; + + int sc_nleds; + struct pcf8574_led sc_leds[PCF8574_NPINS]; + + struct sysmon_envsys *sc_sme; + envsys_data_t sc_sensor[PCF8574_NPINS]; + int sc_pin_sensor[PCF8574_NPINS]; + int sc_pin_active[PCF8574_NPINS]; + +#ifdef PCF8574_DEBUG + callout_t sc_timer; +#endif +}; + +static int pcf8574_match(device_t, cfdata_t, void *); +static void pcf8574_attach(device_t, device_t, void *); +static int pcf8574_detach(device_t, int); + +static int pcf8574_read(struct pcf8574_softc *sc, uint8_t *val); +static int pcf8574_write(struct pcf8574_softc *sc, uint8_t val); +static void pcf8574_attach_led( + struct pcf8574_softc *, char *, int, int, int); +void pcf8574_refresh(struct sysmon_envsys *, envsys_data_t *); +int pcf8574_get_led(void *); +void pcf8574_set_led(void *, int); + +#ifdef PCF8574_DEBUG +static void pcf8574_timeout(void *); +#endif + +CFATTACH_DECL_NEW(pcf8574io, sizeof(struct pcf8574_softc), + pcf8574_match, pcf8574_attach, pcf8574_detach, NULL); + +static const struct device_compatible_entry compat_data[] = { + { "i2c-pcf8574", 0 }, + { NULL, 0 } +}; + +static int +pcf8574_match(device_t parent, cfdata_t cf, void *aux) +{ + struct i2c_attach_args *ia = aux; + int match_result; + + if (iic_use_direct_match(ia, cf, compat_data, &match_result)) + return match_result; + + /* We don't support indirect matches */ + return 0; +} + +static void +pcf8574_attach(device_t parent, device_t self, void *aux) +{ + struct pcf8574_softc *sc = device_private(self); + struct i2c_attach_args *ia = aux; + prop_dictionary_t dict = device_properties(self); + prop_array_t pins; + prop_dictionary_t pin; + int i, num, def, envc = 0; + char name[32]; + const char *nptr = NULL, *spptr; + bool ok = TRUE, act, sysmon = FALSE; + + sc->sc_tag = ia->ia_tag; + sc->sc_addr = ia->ia_addr; + sc->sc_dev = self; + + /* + * The PCF8574 requires input pins to be written with the value 1, + * and then read. Assume that all pins are input initially. + * We'll use the mask when we write, because we have to write a + * value for every pin, and we don't want to change input pins. + */ + sc->sc_mask = 0xff; + + pcf8574_read(sc, &sc->sc_state); + +#ifdef PCF8574_DEBUG + aprint_normal(": GPIO: state = 0x%02x\n", sc->sc_state); + + callout_init(&sc->sc_timer, CALLOUT_MPSAFE); + callout_reset(&sc->sc_timer, hz*30, pcf8574_timeout, sc); +#else + aprint_normal(": GPIO\n"); +#endif + + pins = prop_dictionary_get(dict, "pins"); + if (pins == NULL) + return; + + for (i = 0; i < prop_array_count(pins); i++) { + pin = prop_array_get(pins, i); + ok &= prop_dictionary_get_cstring_nocopy(pin, "name", &nptr); + ok &= prop_dictionary_get_uint32(pin, "pin", &num); + ok &= prop_dictionary_get_bool(pin, "active_high", &act); + /* optional default state */ + def = -1; + prop_dictionary_get_int32(pin, "default_state", &def); + if (!ok) + continue; + /* Extract pin type from the name */ + spptr = strstr(nptr, " "); + if (spptr == NULL) + continue; + spptr += 1; + strncpy(name, spptr, 31); + sc->sc_pin_active[i] = act; + if (!strncmp(nptr, "LED ", 4)) { + sc->sc_mask &= ~(1 << num); + pcf8574_attach_led(sc, name, num, act, def); + } + if (!strncmp(nptr, "INDICATOR ", 4)) { + if (!sysmon) { + sc->sc_sme = sysmon_envsys_create(); + sysmon = TRUE; + } + /* envsys sensor # to pin # mapping */ + sc->sc_pin_sensor[envc] = num; + sc->sc_sensor[i].state = ENVSYS_SINVALID; + sc->sc_sensor[i].units = ENVSYS_INDICATOR; + strlcpy(sc->sc_sensor[i].desc, name, + sizeof(sc->sc_sensor[i].desc)); + if (sysmon_envsys_sensor_attach(sc->sc_sme, + &sc->sc_sensor[i])) { + sysmon_envsys_destroy(sc->sc_sme); + aprint_error_dev(self, + "unable to attach pin %d at sysmon\n", i); + return; + } + DPRINTF("%s: added indicator: pin %d sensor %d (%s)\n", + device_xname(sc->sc_dev), num, envc, name); + envc++; + } + } + + if (sysmon) { + sc->sc_sme->sme_name = device_xname(self); + sc->sc_sme->sme_cookie = sc; + sc->sc_sme->sme_refresh = pcf8574_refresh; + if (sysmon_envsys_register(sc->sc_sme)) { + aprint_error_dev(self, + "unable to register with sysmon\n"); + sysmon_envsys_destroy(sc->sc_sme); + return; + } + } +} + +static int +pcf8574_detach(device_t self, int flags) +{ +#ifdef PCF8574_DEBUG + struct pcf8574_softc *sc = device_private(self); + + callout_halt(&sc->sc_timer, NULL); + callout_destroy(&sc->sc_timer); +#endif + + return 0; +} + +static int +pcf8574_read(struct pcf8574_softc *sc, uint8_t *val) +{ + int err = 0; + + if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) + return err; + err = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, + sc->sc_addr, NULL, 0, val, 1, 0); + iic_release_bus(sc->sc_tag, 0); + return err; +} + +static int +pcf8574_write(struct pcf8574_softc *sc, uint8_t val) +{ + int err = 0; + + if ((err = iic_acquire_bus(sc->sc_tag, 0)) != 0) + return err; + err = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, + &val, 1, NULL, 0, 0); + iic_release_bus(sc->sc_tag, 0); + return err; +} + +static void +pcf8574_attach_led(struct pcf8574_softc *sc, char *n, int pin, int act, int def) +{ + struct pcf8574_led *l; + + l = &sc->sc_leds[sc->sc_nleds]; + l->cookie = sc; + l->mask = 1 << pin; + l->v_on = act ? l->mask : 0; + l->v_off = act ? 0 : l->mask; + led_attach(n, l, pcf8574_get_led, pcf8574_set_led); + if (def != -1) + pcf8574_set_led(l, def); + DPRINTF("%s: attached LED: %02x %02x %02x def %d\n", + device_xname(sc->sc_dev), l->mask, l->v_on, l->v_off, def); + sc->sc_nleds++; +} + +void +pcf8574_refresh(struct sysmon_envsys *sme, envsys_data_t *edata) +{ + struct pcf8574_softc *sc = sme->sme_cookie; + int pin = sc->sc_pin_sensor[edata->sensor]; + int act = sc->sc_pin_active[pin]; + + pcf8574_read(sc, &sc->sc_state); + if (act) + edata->value_cur = sc->sc_state & 1 << pin ? TRUE : FALSE; + else + edata->value_cur = sc->sc_state & 1 << pin ? FALSE : TRUE; + edata->state = ENVSYS_SVALID; +} + +int +pcf8574_get_led(void *cookie) +{ + struct pcf8574_led *l = cookie; + struct pcf8574_softc *sc = l->cookie; + + return ((sc->sc_state & l->mask) == l->v_on); +} + +void +pcf8574_set_led(void *cookie, int val) +{ + struct pcf8574_led *l = cookie; + struct pcf8574_softc *sc = l->cookie; + uint32_t newstate; + + newstate = sc->sc_state & ~l->mask; + newstate |= val ? l->v_on : l->v_off; + DPRINTF("%s: set LED: %02x -> %02x, %02x %02x %02x\n", + device_xname(sc->sc_dev), sc->sc_state, newstate, l->mask, l->v_on, l->v_off); + if (newstate != sc->sc_state) { + pcf8574_write(sc, newstate | sc->sc_mask); + pcf8574_read(sc, &sc->sc_state); + } +} + +#ifdef PCF8574_DEBUG +static void +pcf8574_timeout(void *v) +{ + struct pcf8574_softc *sc = v; + uint8_t val; + + pcf8574_read(sc, &val); + if (val != sc->sc_state) + aprint_normal_dev(sc->sc_dev, + "status change: 0x%02x > 0x%02x\n", sc->sc_state, val); + sc->sc_state = val; + + callout_reset(&sc->sc_timer, hz*60, pcf8574_timeout, sc); +} +#endif