From nobody Sun Nov 21 10:05:42 2021 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 28CDC189F38F; Sun, 21 Nov 2021 10:05:43 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4HxmHq0bS7z3srt; Sun, 21 Nov 2021 10:05:43 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id E9B10581C; Sun, 21 Nov 2021 10:05:42 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 1ALA5gox017388; Sun, 21 Nov 2021 10:05:42 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 1ALA5g7L017387; Sun, 21 Nov 2021 10:05:42 GMT (envelope-from git) Date: Sun, 21 Nov 2021 10:05:42 GMT Message-Id: <202111211005.1ALA5g7L017387@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Andriy Gapon Subject: git: ee82e15cd9fc - stable/13 - pcf8574: driver for 8-pin quasi-bidirectional GPIO over I2C List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: Sender: owner-dev-commits-src-branches@freebsd.org X-BeenThere: dev-commits-src-branches@freebsd.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: avg X-Git-Repository: src X-Git-Refname: refs/heads/stable/13 X-Git-Reftype: branch X-Git-Commit: ee82e15cd9fc8387c06295bc59839ab0cf2f8e10 Auto-Submitted: auto-generated X-ThisMailContainsUnwantedMimeParts: N The branch stable/13 has been updated by avg: URL: https://cgit.FreeBSD.org/src/commit/?id=ee82e15cd9fc8387c06295bc59839ab0cf2f8e10 commit ee82e15cd9fc8387c06295bc59839ab0cf2f8e10 Author: Andriy Gapon AuthorDate: 2020-10-01 09:48:56 +0000 Commit: Andriy Gapon CommitDate: 2021-11-21 09:59:34 +0000 pcf8574: driver for 8-pin quasi-bidirectional GPIO over I2C (cherry picked from commit 6354154ef520d359492badf0de9c8ae5d7080ae7) --- share/man/man4/Makefile | 1 + share/man/man4/pcf8574.4 | 98 +++++++++ sys/conf/files | 1 + sys/dev/iicbus/gpio/pcf8574.c | 425 +++++++++++++++++++++++++++++++++++++++ sys/modules/i2c/Makefile | 1 + sys/modules/i2c/pcf8574/Makefile | 18 ++ 6 files changed, 544 insertions(+) diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index f5aaa2b616a6..e8c0a5651ff8 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -425,6 +425,7 @@ MAN= aac.4 \ pccard.4 \ pccbb.4 \ pcf.4 \ + pcf8574.4 \ pcf8591.4 \ ${_pchtherm.4} \ pci.4 \ diff --git a/share/man/man4/pcf8574.4 b/share/man/man4/pcf8574.4 new file mode 100644 index 000000000000..9fdf71874063 --- /dev/null +++ b/share/man/man4/pcf8574.4 @@ -0,0 +1,98 @@ +.\" +.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD +.\" +.\" Copyright (c) 2020 Andriy Gapon +.\" +.\" 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 AUTHOR 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 AUTHOR 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. +.\" +.\" $FreeBSD$ +.\" +.Dd November 6, 2021 +.Dt PCF8574 4 +.Os +.Sh NAME +.Nm pcf8574 +.Nd driver for the PCF8574 8-bit I2C IO expander +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device pcf8574" +.Cd "device gpio" +.Cd "device iicbus" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +pcf8574_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides +.Xr gpiobus 4 +control over 8 GPIO pins. +The pins are quasi-bidirectional. +Only low output can be actively driven. +High output is equivalent to input. +.Pp +On an +.Xr FDT 4 +based system the following properties must be set: +.Bl -tag -width "compatible" +.It Va compatible +Must be set to "nxp,pcf8574". +.It Va reg +The I2C address of +.Nm . +.El +.Pp +The DTS part for a +.Nm +device usually looks like: +.Bd -literal +/ { + + ... + pcf8574@27 { + compatible = "nxp,pcf8574"; + reg = <0x27>; + }; +}; +.Ed +.Sh SEE ALSO +.Xr fdt 4 , +.Xr gpiobus 4 , +.Xr iicbus 4 +.Sh HISTORY +The +.Nm +driver and this manual page was written by +.An Andriy Gapon Aq Mt avg@FreeBSD.org . +.Sh BUGS +The +.Nm +driver does not support the input change interrupt +that the hardware provides. diff --git a/sys/conf/files b/sys/conf/files index a9f67bcf46f7..5051a38b2997 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1886,6 +1886,7 @@ dev/iicbus/mux/iic_gpiomux.c optional iic_gpiomux fdt dev/iicbus/mux/ltc430x.c optional ltc430x dev/iicbus/nxprtc.c optional nxprtc | pcf8563 dev/iicbus/ofw_iicbus.c optional fdt iicbus +dev/iicbus/pcf8574.c optional pcf8574 dev/iicbus/pcf8591.c optional pcf8591 dev/iicbus/rtc8583.c optional rtc8583 dev/iicbus/rtc/rx8803.c optional rx8803 iicbus fdt diff --git a/sys/dev/iicbus/gpio/pcf8574.c b/sys/dev/iicbus/gpio/pcf8574.c new file mode 100644 index 000000000000..6fa544ac3283 --- /dev/null +++ b/sys/dev/iicbus/gpio/pcf8574.c @@ -0,0 +1,425 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) Andriy Gapon + * + * 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 AUTHOR 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 AUTHOR 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. + * + */ + +/* + * Driver for PCF8574 / PCF8574A 8-bit I/O expander + * with quasi-bidirectional I/O. + * There is no separate mode / configuration register. + * Pins are set and queried via a single register. + * Because of that we have to maintain the state in the driver + * and assume that there is no outside meddling with the device. + * See the datasheet for details. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_platform.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef FDT +#include +#include +#include +#endif + +#include +#include + +#include + +#include "gpio_if.h" + +#define NUM_PINS 8 +#define PIN_CAPS (GPIO_PIN_OUTPUT | GPIO_PIN_INPUT) + +#define dbg_dev_printf(dev, fmt, args...) \ + if (bootverbose) device_printf(dev, fmt, ##args) + +struct pcf8574_softc { + device_t dev; + device_t busdev; + struct sx lock; + uint8_t addr; + uint8_t output_mask; + uint8_t output_state; +}; + +#ifdef FDT +static struct ofw_compat_data compat_data[] = { + { "nxp,pcf8574", 1 }, + { "nxp,pcf8574a", 1 }, + { NULL, 0 } +}; +#endif + +static int +pcf8574_read(struct pcf8574_softc *sc, uint8_t *val) +{ + struct iic_msg msg; + int error; + + msg.slave = sc->addr; + msg.flags = IIC_M_RD; + msg.len = 1; + msg.buf = val; + + error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT); + return (iic2errno(error)); +} + +static int +pcf8574_write(struct pcf8574_softc *sc, uint8_t val) +{ + struct iic_msg msg; + int error; + + msg.slave = sc->addr; + msg.flags = IIC_M_WR; + msg.len = 1; + msg.buf = &val; + + error = iicbus_transfer_excl(sc->dev, &msg, 1, IIC_WAIT); + return (iic2errno(error)); +} + +static int +pcf8574_probe(device_t dev) +{ + +#ifdef FDT + if (ofw_bus_search_compatible(dev, compat_data)->ocd_data == 0) + return (ENXIO); +#endif + device_set_desc(dev, "PCF8574 I/O expander"); + return (BUS_PROBE_DEFAULT); +} + +static int +pcf8574_attach(device_t dev) +{ + struct pcf8574_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->addr = iicbus_get_addr(dev); + + /* Treat everything as input because there is no way to tell. */ + sc->output_mask = 0; + sc->output_state = 0xff; + + /* Put the device to a safe, known state. */ + (void)pcf8574_write(sc, 0xff); + + sx_init(&sc->lock, "pcf8574"); + sc->busdev = gpiobus_attach_bus(dev); + if (sc->busdev == NULL) { + device_printf(dev, "Could not create busdev child\n"); + sx_destroy(&sc->lock); + return (ENXIO); + } + return (0); +} + +static int +pcf8574_detach(device_t dev) +{ + struct pcf8574_softc *sc; + + sc = device_get_softc(dev); + + if (sc->busdev != NULL) + gpiobus_detach_bus(sc->busdev); + + sx_destroy(&sc->lock); + return (0); +} + +static device_t +pcf8574_get_bus(device_t dev) +{ + struct pcf8574_softc *sc; + + sc = device_get_softc(dev); + return (sc->busdev); +} + +static int +pcf8574_pin_max(device_t dev __unused, int *maxpin) +{ + *maxpin = NUM_PINS - 1; + return (0); +} + +static int +pcf8574_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) +{ + + if (pin >= NUM_PINS) + return (EINVAL); + *caps = PIN_CAPS; + return (0); +} + +static int +pcf8574_pin_getflags(device_t dev, uint32_t pin, uint32_t *pflags) +{ + struct pcf8574_softc *sc; + uint8_t val, stale; + int error; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + + sx_xlock(&sc->lock); + error = pcf8574_read(sc, &val); + if (error != 0) { + dbg_dev_printf(dev, "failed to read from device: %d\n", + error); + sx_xunlock(&sc->lock); + return (error); + } + + /* + * Check for pins whose read value is one, but they are configured + * as outputs with low signal. This is an impossible combination, + * so change their view to be inputs. + */ + stale = val & sc->output_mask & ~sc->output_state; + sc->output_mask &= ~stale; + sc->output_state |= stale; + + if ((sc->output_mask & (1 << pin)) != 0) + *pflags = GPIO_PIN_OUTPUT; + else + *pflags = GPIO_PIN_INPUT; + sx_xunlock(&sc->lock); + + return (0); +} + +static int +pcf8574_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) +{ + struct pcf8574_softc *sc; + int error; + uint8_t val; + bool update_needed; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + if ((flags & ~PIN_CAPS) != 0) + return (EINVAL); + + sx_xlock(&sc->lock); + if ((flags & GPIO_PIN_OUTPUT) != 0) { + sc->output_mask |= 1 << pin; + update_needed = false; + } else if ((flags & GPIO_PIN_INPUT) != 0) { + sc->output_mask &= ~(1 << pin); + sc->output_state |= 1 << pin; + update_needed = true; + } else { + KASSERT(false, ("both input and output modes requested")); + update_needed = false; + } + + if (update_needed) { + val = sc->output_state | ~sc->output_mask; + error = pcf8574_write(sc, val); + if (error != 0) + dbg_dev_printf(dev, "failed to write to device: %d\n", + error); + } + sx_xunlock(&sc->lock); + + return (0); +} + +static int +pcf8574_pin_getname(device_t dev, uint32_t pin, char *name) +{ + + if (pin >= NUM_PINS) + return (EINVAL); + snprintf(name, GPIOMAXNAME, "P%d", pin); + return (0); +} + +static int +pcf8574_pin_get(device_t dev, uint32_t pin, unsigned int *on) +{ + struct pcf8574_softc *sc; + uint8_t val; + int error; + + sc = device_get_softc(dev); + + sx_xlock(&sc->lock); + if ((sc->output_mask & (1 << pin)) != 0) { + *on = (sc->output_state & (1 << pin)) != 0; + sx_xunlock(&sc->lock); + return (0); + } + + error = pcf8574_read(sc, &val); + if (error != 0) { + dbg_dev_printf(dev, "failed to read from device: %d\n", error); + sx_xunlock(&sc->lock); + return (error); + } + sx_xunlock(&sc->lock); + + *on = (val & (1 << pin)) != 0; + return (0); +} + +static int +pcf8574_pin_set(device_t dev, uint32_t pin, unsigned int on) +{ + struct pcf8574_softc *sc; + uint8_t val; + int error; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + + sx_xlock(&sc->lock); + + if ((sc->output_mask & (1 << pin)) == 0) { + sx_xunlock(&sc->lock); + return (EINVAL); + } + + /* + * Algorithm: + * - set all outputs to their recorded state; + * - set all inputs to the high state; + * - apply the requested change. + */ + val = sc->output_state | ~sc->output_mask; + val &= ~(1 << pin); + val |= (on != 0) << pin; + + error = pcf8574_write(sc, val); + if (error != 0) { + dbg_dev_printf(dev, "failed to write to device: %d\n", error); + sx_xunlock(&sc->lock); + return (error); + } + + /* + * NB: we can record anything as "output" state of input pins. + * By convention and for convenience it will be recorded as 1. + */ + sc->output_state = val; + sx_xunlock(&sc->lock); + return (0); +} + +static int +pcf8574_pin_toggle(device_t dev, uint32_t pin) +{ + struct pcf8574_softc *sc; + uint8_t val; + int error; + + sc = device_get_softc(dev); + + if (pin >= NUM_PINS) + return (EINVAL); + + sx_xlock(&sc->lock); + + if ((sc->output_mask & (1 << pin)) == 0) { + sx_xunlock(&sc->lock); + return (EINVAL); + } + + val = sc->output_state | ~sc->output_mask; + val ^= 1 << pin; + + error = pcf8574_write(sc, val); + if (error != 0) { + dbg_dev_printf(dev, "failed to write to device: %d\n", error); + sx_xunlock(&sc->lock); + return (error); + } + + sc->output_state = val; + sx_xunlock(&sc->lock); + return (0); +} + +static device_method_t pcf8574_methods[] = { + DEVMETHOD(device_probe, pcf8574_probe), + DEVMETHOD(device_attach, pcf8574_attach), + DEVMETHOD(device_detach, pcf8574_detach), + + /* GPIO methods */ + DEVMETHOD(gpio_get_bus, pcf8574_get_bus), + DEVMETHOD(gpio_pin_max, pcf8574_pin_max), + DEVMETHOD(gpio_pin_getcaps, pcf8574_pin_getcaps), + DEVMETHOD(gpio_pin_getflags, pcf8574_pin_getflags), + DEVMETHOD(gpio_pin_setflags, pcf8574_pin_setflags), + DEVMETHOD(gpio_pin_getname, pcf8574_pin_getname), + DEVMETHOD(gpio_pin_get, pcf8574_pin_get), + DEVMETHOD(gpio_pin_set, pcf8574_pin_set), + DEVMETHOD(gpio_pin_toggle, pcf8574_pin_toggle), + + DEVMETHOD_END +}; + +static driver_t pcf8574_driver = { + "gpio", + pcf8574_methods, + sizeof(struct pcf8574_softc) +}; + +static devclass_t pcf8574_devclass; + +DRIVER_MODULE(pcf8574, iicbus, pcf8574_driver, pcf8574_devclass, 0, 0); +MODULE_DEPEND(pcf8574, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); +MODULE_DEPEND(pcf8574, gpiobus, 1, 1, 1); +MODULE_VERSION(pcf8574, 1); +#ifdef FDT +IICBUS_FDT_PNP_INFO(compat_data); +#endif diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile index 483eb012bb95..9fea714975f7 100644 --- a/sys/modules/i2c/Makefile +++ b/sys/modules/i2c/Makefile @@ -21,6 +21,7 @@ SUBDIR = \ max44009 \ mux \ nxprtc \ + pcf8574 \ pcf8591 \ rtc8583 \ s35390a \ diff --git a/sys/modules/i2c/pcf8574/Makefile b/sys/modules/i2c/pcf8574/Makefile new file mode 100644 index 000000000000..7c4fe37297e4 --- /dev/null +++ b/sys/modules/i2c/pcf8574/Makefile @@ -0,0 +1,18 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/iicbus/gpio/ +KMOD = pcf8574 +SRCS = pcf8574.c + +SRCS+= \ + bus_if.h \ + device_if.h \ + gpio_if.h \ + iicbus_if.h \ + opt_platform.h \ + +.if !empty(OPT_FDT) +SRCS+= ofw_bus_if.h +.endif + +.include