pcf8591: driver for adc/dac with i2c interface

This commit is contained in:
Andriy Gapon 2020-08-17 13:05:11 +03:00
parent 200bc58953
commit a60b304697
6 changed files with 413 additions and 0 deletions

View File

@ -420,6 +420,7 @@ MAN= aac.4 \
pccard.4 \
pccbb.4 \
pcf.4 \
pcf8591.4 \
${_pchtherm.4} \
pci.4 \
pcib.4 \

122
share/man/man4/pcf8591.4 Normal file
View File

@ -0,0 +1,122 @@
.\"
.\" 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: head/share/man/man4/cp2112.4 364144 2020-08-12 09:42:05Z avg $
.\"
.Dd November 6, 2021
.Dt PCF8591 4
.Os
.Sh NAME
.Nm pcf8591
.Nd driver for the PCF8591 8-bit A/D and D/A converter
.Sh SYNOPSIS
To compile this driver into the kernel,
place the following lines in your
kernel configuration file:
.Bd -ragged -offset indent
.Cd "device pcf8591"
.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
pcf8591_load="YES"
.Ed
.Sh DESCRIPTION
The
.Nm
driver supports reading four inputs and setting one output over I2C.
The hardware supports configuring the input lines as:
.Bl -bullet
.It
four single-ended inputs
.It
three differential inputs (one input line is shared between all three inputs)
.It
two single-ended inputs and one differential input
.It
two differential inputs.
.El
.Pp
The
.Nm
driver reports data via
.Xr sysctl 8
entries in the device's node in the
.Xr sysctl 8
tree:
.Bl -tag -width inputs.%d
.It Va inputs.%d
The input level of the corresponding input in steps between 0 and 255.
Absolute voltage depends on an actual reference voltage.
.El
.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,pcf8591".
.It Va reg
The I2C address of
.Nm .
It should be in the range from 0x40 to 0x4f (7-bit).
.El
.Pp
The DTS part for a
.Nm
device usually looks like:
.Bd -literal
/ {
...
pcf8591adc {
compatible = "nxp,pcf8591";
reg = <0x48>;
};
};
.Ed
.Sh SEE ALSO
.Xr fdt 4 ,
.Xr iicbus 4 ,
.Xr sysctl 8
.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 changing the input configuration.
All input lines are configured as single-ended inputs.
.Pp
The
.Nm
driver does not support setting the output.
It is always disabled (tri-state).

View File

@ -1855,6 +1855,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/pcf8591.c optional pcf8591
dev/iicbus/rtc8583.c optional rtc8583
dev/iicbus/rtc/rx8803.c optional rx8803 iicbus fdt
dev/iicbus/s35390a.c optional s35390a

271
sys/dev/iicbus/pcf8591.c Normal file
View File

@ -0,0 +1,271 @@
/*-
* 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.
*
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <machine/bus.h>
#include <dev/iicbus/iicbus.h>
#include <dev/iicbus/iiconf.h>
#ifdef FDT
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#endif
/*
* Driver for PCF8591 I2C 8-bit ADC and DAC.
*/
#define CTRL_CH_SELECT_MASK 0x03
#define CTRL_AUTOINC_EN 0x04
#define CTRL_CH_CONFIG_MASK 0x30
#define CTRL_CH_CONFIG_4_SINGLE 0x00
#define CTRL_CH_CONFIG_3_DIFF 0x10
#define CTRL_CH_CONFIG_2_SINGLE_1_DIFF 0x20
#define CTRL_CH_CONFIG_2_DIFF 0x30
#define CTRL_OUTPUT_EN 0x40
struct pcf8591_softc {
device_t sc_dev;
int sc_ch_count;
uint8_t sc_addr;
uint8_t sc_cfg;
uint8_t sc_output;
};
#ifdef FDT
static struct ofw_compat_data compat_data[] = {
{ "nxp,pcf8591", true },
{ NULL, false }
};
#endif
static int
pcf8591_set_config(device_t dev)
{
struct iic_msg msg;
uint8_t data[2];
struct pcf8591_softc *sc;
int error;
sc = device_get_softc(dev);
data[0] = sc->sc_cfg;
data[1] = sc->sc_output;
msg.slave = sc->sc_addr;
msg.flags = IIC_M_WR;
msg.len = nitems(data);
msg.buf = data;
error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT);
return (error);
}
static int
pcf8591_get_reading(device_t dev, uint8_t *reading)
{
struct iic_msg msg;
struct pcf8591_softc *sc;
int error;
sc = device_get_softc(dev);
msg.slave = sc->sc_addr;
msg.flags = IIC_M_RD;
msg.len = 1;
msg.buf = reading;
error = iicbus_transfer_excl(dev, &msg, 1, IIC_INTRWAIT);
return (error);
}
static int
pcf8591_select_channel(device_t dev, int channel)
{
struct pcf8591_softc *sc;
int error;
uint8_t unused;
sc = device_get_softc(dev);
if (channel >= sc->sc_ch_count)
return (EINVAL);
sc->sc_cfg &= ~CTRL_CH_SELECT_MASK;
sc->sc_cfg += channel;
error = pcf8591_set_config(dev);
if (error != 0)
return (error);
/*
* The next read is still for the old channel,
* so do it and discard.
*/
error = pcf8591_get_reading(dev, &unused);
return (error);
}
static int
pcf8591_channel_sysctl(SYSCTL_HANDLER_ARGS)
{
struct pcf8591_softc *sc;
device_t dev;
int error, channel, val;
uint8_t reading;
dev = arg1;
channel = arg2;
sc = device_get_softc(dev);
if (req->oldptr != NULL) {
error = pcf8591_select_channel(dev, channel);
if (error != 0)
return (EIO);
error = pcf8591_get_reading(dev, &reading);
if (error != 0)
return (EIO);
val = reading;
}
error = sysctl_handle_int(oidp, &val, 0, req);
return (error);
}
static void
pcf8591_start(void *arg)
{
device_t dev;
struct pcf8591_softc *sc;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree_node;
struct sysctl_oid_list *tree;
struct sysctl_oid *inputs_node;
struct sysctl_oid_list *inputs;
int error;
sc = arg;
dev = sc->sc_dev;
/*
* Set initial -- and, for the time being, fixed -- configuration.
* Channel auto-incrementi is disabled, although it could be more
* performant and precise for bulk channel queries.
* The inputs are configured as four single channels.
* The output is disabled.
*/
sc->sc_cfg = 0;
sc->sc_output = 0;
sc->sc_ch_count = 4;
error = pcf8591_set_config(dev);
ctx = device_get_sysctl_ctx(dev);
tree_node = device_get_sysctl_tree(dev);
tree = SYSCTL_CHILDREN(tree_node);
inputs_node = SYSCTL_ADD_NODE(ctx, tree, OID_AUTO, "inputs",
CTLTYPE_NODE, NULL, "Input channels");
inputs = SYSCTL_CHILDREN(inputs_node);
for (int i = 0; i < sc->sc_ch_count; i++) {
char buf[4];
snprintf(buf, sizeof(buf), "%d", i);
SYSCTL_ADD_PROC(ctx, inputs, OID_AUTO, buf,
CTLTYPE_INT | CTLFLAG_RD, dev, i,
pcf8591_channel_sysctl, "I", "Input level from 0 to 255 "
"(relative to Vref)");
}
}
static int
pcf8591_probe(device_t dev)
{
#ifdef FDT
if (!ofw_bus_status_okay(dev))
return (ENXIO);
#endif
device_set_desc(dev, "PCF8591 8-bit ADC / DAC");
#ifdef FDT
if (ofw_bus_search_compatible(dev, compat_data)->ocd_data)
return (BUS_PROBE_GENERIC);
#endif
return (BUS_PROBE_NOWILDCARD);
}
static int
pcf8591_attach(device_t dev)
{
struct pcf8591_softc *sc;
sc = device_get_softc(dev);
sc->sc_dev = dev;
sc->sc_addr = iicbus_get_addr(dev);
/*
* We have to wait until interrupts are enabled. Usually I2C read
* and write only works when the interrupts are available.
*/
config_intrhook_oneshot(pcf8591_start, sc);
return (0);
}
static int
pcf8591_detach(device_t dev)
{
return (0);
}
static device_method_t pcf8591_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, pcf8591_probe),
DEVMETHOD(device_attach, pcf8591_attach),
DEVMETHOD(device_detach, pcf8591_detach),
DEVMETHOD_END
};
static driver_t pcf8591_driver = {
"pcf8591",
pcf8591_methods,
sizeof(struct pcf8591_softc)
};
static devclass_t pcf8591_devclass;
DRIVER_MODULE(pcf8591, iicbus, pcf8591_driver, pcf8591_devclass, 0, 0);
MODULE_DEPEND(pcf8591, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
MODULE_VERSION(pcf8591, 1);
#ifdef FDT
IICBUS_FDT_PNP_INFO(compat_data);
#endif

View File

@ -20,6 +20,7 @@ SUBDIR = \
jedec_dimm \
mux \
nxprtc \
pcf8591 \
rtc8583 \
s35390a \
smb \

View File

@ -0,0 +1,17 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/iicbus
KMOD= pcf8591
SRCS= pcf8591.c
SRCS+= \
bus_if.h \
device_if.h \
iicbus_if.h \
opt_platform.h
.if !empty(OPT_FDT)
SRCS+= ofw_bus_if.h
.endif
.include <bsd.kmod.mk>