pcf8574: driver for 8-pin quasi-bidirectional GPIO over I2C
MFC after: 2 weeks
This commit is contained in:
parent
832503063e
commit
6354154ef5
@ -421,6 +421,7 @@ MAN= aac.4 \
|
||||
pccard.4 \
|
||||
pccbb.4 \
|
||||
pcf.4 \
|
||||
pcf8574.4 \
|
||||
pcf8591.4 \
|
||||
${_pchtherm.4} \
|
||||
pci.4 \
|
||||
|
98
share/man/man4/pcf8574.4
Normal file
98
share/man/man4/pcf8574.4
Normal file
@ -0,0 +1,98 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
||||
.\"
|
||||
.\" Copyright (c) 2020 Andriy Gapon <avg@FreeBSD.org>
|
||||
.\"
|
||||
.\" 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.
|
@ -1856,6 +1856,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
|
||||
|
425
sys/dev/iicbus/gpio/pcf8574.c
Normal file
425
sys/dev/iicbus/gpio/pcf8574.c
Normal file
@ -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 <sys/cdefs.h>
|
||||
__FBSDID("$FreeBSD$");
|
||||
|
||||
#include "opt_platform.h"
|
||||
|
||||
#include <sys/param.h>
|
||||
#include <sys/bus.h>
|
||||
#include <sys/gpio.h>
|
||||
#include <sys/kernel.h>
|
||||
#include <sys/module.h>
|
||||
#include <sys/systm.h>
|
||||
#include <sys/sx.h>
|
||||
|
||||
#ifdef FDT
|
||||
#include <dev/ofw/openfirm.h>
|
||||
#include <dev/ofw/ofw_bus.h>
|
||||
#include <dev/ofw/ofw_bus_subr.h>
|
||||
#endif
|
||||
|
||||
#include <dev/iicbus/iicbus.h>
|
||||
#include <dev/iicbus/iiconf.h>
|
||||
|
||||
#include <dev/gpio/gpiobusvar.h>
|
||||
|
||||
#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
|
@ -21,6 +21,7 @@ SUBDIR = \
|
||||
max44009 \
|
||||
mux \
|
||||
nxprtc \
|
||||
pcf8574 \
|
||||
pcf8591 \
|
||||
rtc8583 \
|
||||
s35390a \
|
||||
|
18
sys/modules/i2c/pcf8574/Makefile
Normal file
18
sys/modules/i2c/pcf8574/Makefile
Normal file
@ -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 <bsd.kmod.mk>
|
Loading…
Reference in New Issue
Block a user