#include <sys/cdefs.h>
#include "opt_platform.h"
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/endian.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/sx.h>
#include <sys/sysctl.h>
#ifdef FDT
#include <dev/ofw/ofw_bus.h>
#include <dev/ofw/ofw_bus_subr.h>
#endif
#include <dev/iicbus/iiconf.h>
#include <dev/iicbus/iicbus.h>
#include "iicbus_if.h"
#define ADS111x_CONV 0
#define ADS111x_CONF 1
#define ADS111x_CONF_OS_SHIFT 15
#define ADS111x_CONF_MUX_SHIFT 12
#define ADS111x_CONF_GAIN_SHIFT 9
#define ADS111x_CONF_MODE_SHIFT 8
#define ADS111x_CONF_RATE_SHIFT 5
#define ADS111x_CONF_COMP_DISABLE 3
#define ADS111x_LOTHRESH 2
#define ADS111x_HITHRESH 3
#define ADS111x_CONF_MEASURE (1u << ADS111x_CONF_OS_SHIFT)
#define ADS111x_CONF_IDLE (1u << ADS111x_CONF_OS_SHIFT)
#define ADS111x_CONF_DEFAULT \
((1 << ADS111x_CONF_MODE_SHIFT) | ADS111x_CONF_COMP_DISABLE)
#define ADS111x_CONF_USERMASK 0x001f
#define DEFAULT_GAINIDX 2
#define DEFAULT_RATEIDX 4
static const u_int fixedranges[8] = {
2048000, 2048000, 2048000, 2048000, 2048000, 2048000, 2048000, 2048000,
};
static const u_int gainranges[8] = {
6144000, 4096000, 2048000, 1024000, 512000, 256000, 256000, 256000,
};
#define ADS101x_RANGEDIV ((1 << 15) - 15)
#define ADS111x_RANGEDIV ((1 << 15) - 1)
static const u_int rates101x[8] = {128, 250, 490, 920, 1600, 2400, 3300, 3300};
static const u_int rates111x[8] = { 8, 16, 32, 64, 128, 250, 475, 860};
struct ads111x_channel {
u_int gainidx;
u_int rateidx;
bool configured;
};
#define ADS111x_MAX_CHANNELS 8
struct ads111x_chipinfo {
const char *name;
const u_int *rangetab;
const u_int *ratetab;
u_int numchan;
int rangediv;
};
static struct ads111x_chipinfo ads111x_chip_infos[] = {
{ "ADS1013", fixedranges, rates101x, 1, ADS101x_RANGEDIV },
{ "ADS1014", gainranges, rates101x, 1, ADS101x_RANGEDIV },
{ "ADS1015", gainranges, rates101x, 8, ADS101x_RANGEDIV },
{ "ADS1113", fixedranges, rates111x, 1, ADS111x_RANGEDIV },
{ "ADS1114", gainranges, rates111x, 1, ADS111x_RANGEDIV },
{ "ADS1115", gainranges, rates111x, 8, ADS111x_RANGEDIV },
};
#ifdef FDT
static struct ofw_compat_data compat_data[] = {
{"ti,ads1013", (uintptr_t)&ads111x_chip_infos[0]},
{"ti,ads1014", (uintptr_t)&ads111x_chip_infos[1]},
{"ti,ads1015", (uintptr_t)&ads111x_chip_infos[2]},
{"ti,ads1113", (uintptr_t)&ads111x_chip_infos[3]},
{"ti,ads1114", (uintptr_t)&ads111x_chip_infos[4]},
{"ti,ads1115", (uintptr_t)&ads111x_chip_infos[5]},
{NULL, (uintptr_t)NULL},
};
IICBUS_FDT_PNP_INFO(compat_data);
#endif
struct ads111x_softc {
device_t dev;
struct sx lock;
int addr;
int cfgword;
const struct ads111x_chipinfo
*chipinfo;
struct ads111x_channel
channels[ADS111x_MAX_CHANNELS];
};
static int
ads111x_write_2(struct ads111x_softc *sc, int reg, int val)
{
uint8_t data[3];
struct iic_msg msgs[1];
uint8_t slaveaddr;
slaveaddr = iicbus_get_addr(sc->dev);
data[0] = reg;
be16enc(&data[1], val);
msgs[0].slave = slaveaddr;
msgs[0].flags = IIC_M_WR;
msgs[0].len = sizeof(data);
msgs[0].buf = data;
return (iicbus_transfer_excl(sc->dev, msgs, nitems(msgs), IIC_WAIT));
}
static int
ads111x_read_2(struct ads111x_softc *sc, int reg, int *val)
{
int err;
uint8_t data[2];
err = iic2errno(iicdev_readfrom(sc->dev, reg, data, 2, IIC_WAIT));
if (err == 0)
*val = (int16_t)be16dec(data);
return (err);
}
static int
ads111x_sample_voltage(struct ads111x_softc *sc, int channum, int *voltage)
{
struct ads111x_channel *chan;
int err, cfgword, convword, rate, retries, waitns;
int64_t fsrange;
chan = &sc->channels[channum];
cfgword = sc->cfgword |
(1 << ADS111x_CONF_OS_SHIFT) |
(channum << ADS111x_CONF_MUX_SHIFT) |
(chan->gainidx << ADS111x_CONF_GAIN_SHIFT) |
(chan->rateidx << ADS111x_CONF_RATE_SHIFT);
if ((err = ads111x_write_2(sc, ADS111x_CONF, cfgword)) != 0)
return (err);
rate = sc->chipinfo->ratetab[chan->rateidx];
waitns = (1000000000 + rate - 1) / rate;
err = pause_sbt("ads111x", nstosbt(waitns), 0, C_PREL(2));
if (err != 0 && err != EWOULDBLOCK)
return (err);
for (retries = 5; ; --retries) {
if (retries == 0)
return (EWOULDBLOCK);
if ((err = ads111x_read_2(sc, ADS111x_CONF, &cfgword)) != 0)
return (err);
if (cfgword & ADS111x_CONF_IDLE)
break;
pause_sbt("ads111x", nstosbt(waitns / 20), 0, C_PREL(2));
}
if ((err = ads111x_read_2(sc, ADS111x_CONV, &convword)) != 0)
return (err);
fsrange = sc->chipinfo->rangetab[chan->gainidx];
*voltage = (int)((convword * fsrange ) / sc->chipinfo->rangediv);
return (err);
}
static int
ads111x_sysctl_gainidx(SYSCTL_HANDLER_ARGS)
{
struct ads111x_softc *sc;
int chan, err, gainidx;
sc = arg1;
chan = arg2;
gainidx = sc->channels[chan].gainidx;
err = sysctl_handle_int(oidp, &gainidx, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
if (gainidx < 0 || gainidx > 7)
return (EINVAL);
sx_xlock(&sc->lock);
sc->channels[chan].gainidx = gainidx;
sx_xunlock(&sc->lock);
return (err);
}
static int
ads111x_sysctl_rateidx(SYSCTL_HANDLER_ARGS)
{
struct ads111x_softc *sc;
int chan, err, rateidx;
sc = arg1;
chan = arg2;
rateidx = sc->channels[chan].rateidx;
err = sysctl_handle_int(oidp, &rateidx, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
if (rateidx < 0 || rateidx > 7)
return (EINVAL);
sx_xlock(&sc->lock);
sc->channels[chan].rateidx = rateidx;
sx_xunlock(&sc->lock);
return (err);
}
static int
ads111x_sysctl_voltage(SYSCTL_HANDLER_ARGS)
{
struct ads111x_softc *sc;
int chan, err, voltage;
sc = arg1;
chan = arg2;
if (req->oldptr != NULL) {
sx_xlock(&sc->lock);
err = ads111x_sample_voltage(sc, chan, &voltage);
sx_xunlock(&sc->lock);
if (err != 0) {
device_printf(sc->dev,
"conversion read failed, error %d\n", err);
return (err);
}
}
err = sysctl_handle_int(oidp, &voltage, 0, req);
return (err);
}
static int
ads111x_sysctl_config(SYSCTL_HANDLER_ARGS)
{
struct ads111x_softc *sc;
int config, err;
sc = arg1;
config = sc->cfgword & ADS111x_CONF_USERMASK;
err = sysctl_handle_int(oidp, &config, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
sx_xlock(&sc->lock);
sc->cfgword = config & ADS111x_CONF_USERMASK;
err = ads111x_write_2(sc, ADS111x_CONF, sc->cfgword);
sx_xunlock(&sc->lock);
return (err);
}
static int
ads111x_sysctl_lothresh(SYSCTL_HANDLER_ARGS)
{
struct ads111x_softc *sc;
int thresh, err;
sc = arg1;
if ((err = ads111x_read_2(sc, ADS111x_LOTHRESH, &thresh)) != 0)
return (err);
err = sysctl_handle_int(oidp, &thresh, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
sx_xlock(&sc->lock);
err = ads111x_write_2(sc, ADS111x_CONF, thresh);
sx_xunlock(&sc->lock);
return (err);
}
static int
ads111x_sysctl_hithresh(SYSCTL_HANDLER_ARGS)
{
struct ads111x_softc *sc;
int thresh, err;
sc = arg1;
if ((err = ads111x_read_2(sc, ADS111x_HITHRESH, &thresh)) != 0)
return (err);
err = sysctl_handle_int(oidp, &thresh, 0, req);
if (err != 0 || req->newptr == NULL)
return (err);
sx_xlock(&sc->lock);
err = ads111x_write_2(sc, ADS111x_CONF, thresh);
sx_xunlock(&sc->lock);
return (err);
}
static void
ads111x_setup_channel(struct ads111x_softc *sc, int chan, int gainidx, int rateidx)
{
struct ads111x_channel *c;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *chantree, *devtree;
char chanstr[4];
c = &sc->channels[chan];
c->gainidx = gainidx;
c->rateidx = rateidx;
if (c->configured)
return;
ctx = device_get_sysctl_ctx(sc->dev);
devtree = device_get_sysctl_tree(sc->dev);
snprintf(chanstr, sizeof(chanstr), "%d", chan);
chantree = SYSCTL_ADD_NODE(ctx, SYSCTL_CHILDREN(devtree), OID_AUTO,
chanstr, CTLFLAG_RD | CTLFLAG_MPSAFE, NULL, "channel data");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO,
"gain_index", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
sc, chan, ads111x_sysctl_gainidx, "I",
"programmable gain amp setting, 0-7");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO,
"rate_index", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE,
sc, chan, ads111x_sysctl_rateidx, "I", "sample rate setting, 0-7");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO,
"voltage",
CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_SKIP | CTLFLAG_MPSAFE, sc,
chan, ads111x_sysctl_voltage, "I", "sampled voltage in microvolts");
c->configured = true;
}
static void
ads111x_add_channels(struct ads111x_softc *sc)
{
const char *name;
uint32_t chan, gainidx, num_added, rateidx, unit;
bool found;
#ifdef FDT
phandle_t child, node;
num_added = 0;
node = ofw_bus_get_node(sc->dev);
for (child = OF_child(node); child != 0; child = OF_peer(child)) {
if (OF_getencprop(child, "reg", &chan, sizeof(chan)) == -1)
continue;
if (chan >= ADS111x_MAX_CHANNELS)
continue;
gainidx = DEFAULT_GAINIDX;
rateidx = DEFAULT_RATEIDX;
OF_getencprop(child, "ti,gain", &gainidx, sizeof(gainidx));
OF_getencprop(child, "ti,datarate", &rateidx, sizeof(rateidx));
ads111x_setup_channel(sc, chan, gainidx, rateidx);
++num_added;
}
#else
num_added = 0;
#endif
name = device_get_name(sc->dev);
unit = device_get_unit(sc->dev);
for (chan = 0; chan < sc->chipinfo->numchan; ++chan) {
char resname[16];
found = false;
gainidx = DEFAULT_GAINIDX;
rateidx = DEFAULT_RATEIDX;
snprintf(resname, sizeof(resname), "%d.gain_index", chan);
if (resource_int_value(name, unit, resname, &gainidx) == 0)
found = true;
snprintf(resname, sizeof(resname), "%d.rate_index", chan);
if (resource_int_value(name, unit, resname, &rateidx) == 0)
found = true;
if (found) {
ads111x_setup_channel(sc, chan, gainidx, rateidx);
++num_added;
}
}
if (num_added > 0)
return;
for (chan = 0; chan < sc->chipinfo->numchan; ++chan) {
gainidx = DEFAULT_GAINIDX;
rateidx = DEFAULT_RATEIDX;
ads111x_setup_channel(sc, chan, gainidx, rateidx);
}
}
static const struct ads111x_chipinfo *
ads111x_find_chipinfo(device_t dev)
{
const struct ads111x_chipinfo *info;
const char *chiptype;
int i;
#ifdef FDT
if (ofw_bus_status_okay(dev)) {
info = (struct ads111x_chipinfo*)
ofw_bus_search_compatible(dev, compat_data)->ocd_data;
if (info != NULL)
return (info);
}
#endif
chiptype = NULL;
resource_string_value(device_get_name(dev), device_get_unit(dev),
"type", &chiptype);
if (chiptype != NULL) {
for (i = 0; i < nitems(ads111x_chip_infos); ++i) {
info = &ads111x_chip_infos[i];
if (strcasecmp(chiptype, info->name) == 0)
return (info);
}
}
return (NULL);
}
static int
ads111x_probe(device_t dev)
{
const struct ads111x_chipinfo *info;
info = ads111x_find_chipinfo(dev);
if (info != NULL) {
device_set_desc(dev, info->name);
#ifdef FDT
return (BUS_PROBE_DEFAULT);
#else
return (BUS_PROBE_NOWILDCARD);
#endif
}
return (ENXIO);
}
static int
ads111x_attach(device_t dev)
{
struct ads111x_softc *sc;
struct sysctl_ctx_list *ctx;
struct sysctl_oid *tree;
int err;
sc = device_get_softc(dev);
sc->dev = dev;
sc->addr = iicbus_get_addr(dev);
sc->cfgword = ADS111x_CONF_DEFAULT;
sc->chipinfo = ads111x_find_chipinfo(sc->dev);
if (sc->chipinfo == NULL) {
device_printf(dev,
"cannot get chipinfo (but it worked during probe)");
return (ENXIO);
}
if ((err = ads111x_write_2(sc, ADS111x_CONF, sc->cfgword)) != 0) {
device_printf(dev, "cannot write chip config register\n");
return (err);
}
sx_init(&sc->lock, "ads111x");
ctx = device_get_sysctl_ctx(dev);
tree = device_get_sysctl_tree(dev);
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"config", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, 0,
ads111x_sysctl_config, "I", "configuration register word");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"lo_thresh", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, 0,
ads111x_sysctl_lothresh, "I", "comparator low threshold");
SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO,
"hi_thresh", CTLTYPE_INT | CTLFLAG_RWTUN | CTLFLAG_MPSAFE, sc, 0,
ads111x_sysctl_hithresh, "I", "comparator high threshold");
ads111x_add_channels(sc);
return (0);
}
static int
ads111x_detach(device_t dev)
{
struct ads111x_softc *sc;
sc = device_get_softc(dev);
sx_destroy(&sc->lock);
return (0);
}
static device_method_t ads111x_methods[] = {
DEVMETHOD(device_probe, ads111x_probe),
DEVMETHOD(device_attach, ads111x_attach),
DEVMETHOD(device_detach, ads111x_detach),
DEVMETHOD_END,
};
static driver_t ads111x_driver = {
"ads111x",
ads111x_methods,
sizeof(struct ads111x_softc),
};
DRIVER_MODULE(ads111x, iicbus, ads111x_driver, NULL, NULL);
MODULE_VERSION(ads111x, 1);
MODULE_DEPEND(ads111x, iicbus, 1, 1, 1);