diff --git a/share/man/man4/ads111x.4 b/share/man/man4/ads111x.4 new file mode 100644 index 000000000000..01f1351235c9 --- /dev/null +++ b/share/man/man4/ads111x.4 @@ -0,0 +1,240 @@ +.\" +.\" Copyright (c) 2019 Ian Lepore +.\" +.\" 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 ``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 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 August 5, 2019 +.Dt ADS1115 4 +.Os +.Sh NAME +.Nm ads1115 +.Nd driver for ADS101x and ADS111x i2c analog to digital converters +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device ads1115" +.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 +ads1115_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for the ADS101x/ADS111x family of analog +to digital converter (ADC) devices. +The supported devices are all similar to each other, varying in +features such as resolution and number of input channels. +The devices offer a number of configuration options which can be +set via hints, FDT data, and +.Xr sysctl 8 . +.Pp +.Xr Sysctl 8 +provides access to the voltage measurements made by the device. +Each time the +.Va dev.ads1115...voltage +variable is accessed for a given channel, the driver switches the +chip's internal mux to choose the right input pins for that channel, +directs it to make a single measurement, and returns the measured value +in microvolts. +The amount of time required to make the measurement is a function +of the sampling rate configured for the device. +While device is directed to make a single measurement, it still averages +the input values for the same amount of time as it would to emit one +sample if it were in continuous mode. +For example, if the sample rate were configured as 125 samples per +second, a single measurement would require 8 milliseconds. +.Pp +For devices that support multiple input pins, the device datasheet +describes mux settings to control how those pins are interpeted when +making either single-ended or differential measurements. +There are eight possible ways to combine the inputs from the four pins. +The +.Nm +driver models that by creating a separate output channel for each of +the eight combinations. +To make a measurement on a given pin or pair of pins, you simply access +the voltage variable for the channel number that corresponds the mux +setting number (0 through 7) shown in the datasheet. +When the driver is configured with hints or FDT data, it creates +sysctl variables for just the channels specified in the config data. +When there is no channel config data, it creates all eight possible +channels so that you can access whichever one(s) you need. +.Pp +For devices that include an +.Va alert +output pin, the +.Nm +driver does not directly support the pin in terms of sensing or +acting on changes in the pin state. +However, you may connect the pin to a gpio input or fan controller +or other external device, and use the driver's sysctl variables to +configure behavior and threshold values for the pin. +The driver avoids perturbing your settings as it does other +manipulations to the config register. +.Sh SYSCTL VARIABLES +Sysctl variables are used to access the voltage measurements, and to +change the configuration of the channels. +All writeable variables may also be set as +.Xr loader 8 +tunables. +Channel numbers in these sysctl variables range from 0 through 7. +.Bl -tag -width indent +.It Va dev.ads1115..config +Provides access to the configuration register bits that control the +alert pin configuration. +Other bits which are controlled by the driver are masked out, and +cannot be viewed or changed using this variable. +.It Va dev.ads1115..lo_thresh +Sets the low threshold for activating the alert pin. +.It Va dev.ads1115..hi_thresh +Sets the high threshold for activating the alert pin. +.It Va dev.ads1115...rate_index +Sets the sample rate for the channel. +The device datasheet documents eight available sample rates, chosen +by setting a value of 0 through 7 into the corresponding control +register bits. +This variable sets the value used for those bits when making a +measurement on the given channel. +.Pp +Because measurements are always made in single-shot mode, think of +this variable as controlling the averaging time for a single sample; +the time to make a measurement is 1 / samplerate. +.It Va dev.ads1115...gain_index +Sets the programmable gain amplifier for the channel on devices +which have an internal amplifier. +The device datasheet documents eight available gain values, chosen +by setting a value of 0 through 7 into the corresponding control +register bits. +This variable sets the value used for those bits when making a +measurement on the given channel. +.It Va dev.ads1115...voltage +Reading this variable causes the device to make a measurement on +the corresponding input pin(s) and return the voltage in microvolts. +.Pp +Note that this variable does not appear when you list multiple +sysctl variables -- you must access it specifically by name, because +accessing it triggers device I/O. +.El +.Sh HARDWARE +The +.Nm +driver provides support for the following devices: +.Pp +.Bl -column -compact -offset indent "XXXXXXXX" "XXXXXXXX" +.It ADS1013 Ta ADS1113 +.It ADS1014 Ta ADS1114 +.It ADS1015 Ta ADS1115 +.El +.Sh FDT CONFIGURATION +On an +.Xr fdt 4 +based system, the +.Nm +device is defined as a slave device subnode +of the i2c bus controller node. +All properties documented in the +.Va ads1115.txt +bindings document can be used with the +.Nm +device. +.Pp +The following properties are required in the +.Nm +device subnode: +.Bl -tag -width indent +.It Va compatible +One of the following: +.Bl -column -compact -offset indent ".Dq ti,ads1013" ".Dq ti,ads1113" +.It Dq ti,ads1013 Ta Dq ti,ads1113 +.It Dq ti,ads1014 Ta Dq ti,ads1114 +.It Dq ti,ads1015 Ta Dq ti,ads1115 +.El +.It Va reg +I2c slave address of device. +.El +.Pp +Specific channels can be configured by adding child nodes to the +.Nm +node, as described in the standard ads1115.txt bindings document. +If no channels are configured, sysctl variables will be created +for all possible channels supported by the device type, otherwise +only the specified channels are created. +.Ss Example including channel configuration +.Bd -unfilled -offset indent +adc@48 { + compatible = "ti,ads1115"; + reg = <0x48>; + status = "okay"; + #address-cells = <1>; + #size-cells = <0>; + + channel@6 { + reg = <6>; + ti,gain = <3>; + ti,datarate = <4>; + }; + channel@7 { + reg = <7>; + ti,gain = <1>; + ti,datarate = <7>; + }; +}; +.Ed +.Sh HINTS CONFIGURATION +On a +.Xr device.hints 5 +based system, such as +.Li MIPS , +these values are configurable for +.Nm : +.Bl -tag -width indent +.It Va hint.ads1115..at +The iicbus instance the +.Nm +instance is attached to. +.It Va hint.ads1115...gain_index +The amplifier gain, as described above for the sysctl variable +.Va dev.ads1115...gain_index . +.It Va hint.ads1115...rate_index +The sample rate, as described above for the sysctl variable +.Va dev.ads1115...rate_index . +.El +.Pp +If no channels are configured, sysctl variables will be created +for all possible channels supported by the device type, otherwise +only the specified channels are created. +.Sh SEE ALSO +.Xr fdt 4 , +.Xr sysctl 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0 . diff --git a/sys/conf/NOTES b/sys/conf/NOTES index d9180700e5c1..938a2e7c8670 100644 --- a/sys/conf/NOTES +++ b/sys/conf/NOTES @@ -2422,6 +2422,7 @@ device iicoc # OpenCores I2C controller support # I2C peripheral devices # device ad7418 # Analog Devices temp and voltage sensor +device ads111x # Texas Instruments ADS101x and ADS111x ADCs device ds1307 # Dallas DS1307 RTC and compatible device ds13rtc # All Dallas/Maxim ds13xx chips device ds1672 # Dallas DS1672 RTC diff --git a/sys/conf/files b/sys/conf/files index 1e009a121d79..00815a6143c7 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1761,6 +1761,7 @@ dev/ida/ida.c optional ida dev/ida/ida_disk.c optional ida dev/ida/ida_pci.c optional ida pci dev/iicbus/ad7418.c optional ad7418 +dev/iicbus/ads111x.c optional ads111x dev/iicbus/ds1307.c optional ds1307 dev/iicbus/ds13rtc.c optional ds13rtc | ds133x | ds1374 dev/iicbus/ds1672.c optional ds1672 diff --git a/sys/dev/iicbus/ads111x.c b/sys/dev/iicbus/ads111x.c new file mode 100644 index 000000000000..22ebfa2059b3 --- /dev/null +++ b/sys/dev/iicbus/ads111x.c @@ -0,0 +1,582 @@ +/*- + * Copyright (c) 2019 Ian Lepore. + * + * 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 ``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 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 Texas Instruments ADS101x and ADS111x family i2c ADC chips. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_platform.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef FDT +#include +#include +#endif + +#include +#include + +#include "iicbus_if.h" + +/* + * Chip registers, bit definitions, shifting and masking values. + */ +#define ADS111x_CONV 0 /* Reg 0: Latest sample (ro) */ + +#define ADS111x_CONF 1 /* Reg 1: Config (rw) */ +#define ADS111x_CONF_OS_SHIFT 15 /* Operational state */ +#define ADS111x_CONF_MUX_SHIFT 12 /* Input mux setting */ +#define ADS111x_CONF_GAIN_SHIFT 9 /* Programmable gain amp */ +#define ADS111x_CONF_MODE_SHIFT 8 /* Operational mode */ +#define ADS111x_CONF_RATE_SHIFT 5 /* Sample rate */ + +#define ADS111x_LOTHRESH 2 /* Compare lo threshold (rw) */ + +#define ADS111x_HITHRESH 3 /* Compare hi threshold (rw) */ + +/* + * On config write, the operational-state bit starts a measurement, on read it + * indicates when the measurement process is complete/idle. + */ +#define ADS111x_CONF_MEASURE (1u << ADS111x_CONF_OS_SHIFT) +#define ADS111x_CONF_IDLE (1u << ADS111x_CONF_OS_SHIFT) + +/* + * The default values for config items that are not per-channel. Mostly, this + * turns off the comparator on chips that have that feature, because this driver + * doesn't support it directly. However, the user is allowed to enable the + * comparator and we'll leave it alone if they do. That allows them connect the + * alert pin to something and use the feature without any help from this driver. + */ +#define ADS111x_CONF_DEFAULT (1 << ADS111x_CONF_MODE_SHIFT) +#define ADS111x_CONF_USERMASK 0x001f + +/* + * Per-channel defaults. The chip only has one control register, and we load + * per-channel values into it every time we make a measurement on that channel. + * These are the default values for the control register from the datasheet, for + * values we maintain on a per-channel basis. + */ +#define DEFAULT_GAINIDX 2 +#define DEFAULT_RATEIDX 4 + +/* + * Full-scale ranges for each available amplifier setting, in microvolts. The + * ADS1x13 chips are fixed-range, the other chips contain a programmable gain + * amplifier, and the full scale range is based on the amplifier setting. + */ +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, +}; + +/* The highest value for the ADS101x chip is 0x7ff0, for ADS111x it's 0x7fff. */ +#define ADS101x_RANGEDIV ((1 << 15) - 15) +#define ADS111x_RANGEDIV ((1 << 15) - 1) + +/* Samples per second; varies based on chip type. */ +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; /* Amplifier (full-scale range) config index */ + u_int rateidx; /* Samples per second config index */ + bool configured; /* Channel has been 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[2]; + + be16enc(data, val); + + return (iic2errno(iicdev_writeto(sc->dev, reg, data, 2, 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, waitns; + int64_t fsrange; + + chan = &sc->channels[channum]; + + /* Ask the chip to do a one-shot measurement of the given channel. */ + 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); + + /* + * Calculate how long it will take to make the measurement at the + * current sampling rate (round up), and sleep at least that long. + */ + 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); + +#if 0 + /* + * Sanity-check that the measurement is complete. Not enabled by + * default because checking wastes 200-800us just in moving the status + * command and result across the i2c bus, which could double the time it + * takes to get one measurement. Unlike most i2c slaves, this device + * does not auto-increment the register number on reads, so we can't + * read both status and measurement in one operation. + */ + if ((err = ads111x_read_2(sc, ADS111x_CONF, &cfgword)) != 0) + return (err); + if (!(cfgword & ADS111x_CONF_IDLE)) + return (EIO); +#endif + + /* Retrieve the sample and convert it to microvolts. */ + 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 setting up the channel for the first time, create channel's + * sysctl entries. We might have already configured the channel if + * config data for it exists in both FDT and hints. + */ + + 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, NULL, "channel data"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(chantree), OID_AUTO, + "gain_index", CTLTYPE_INT | CTLFLAG_RWTUN, 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, 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, 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; + + /* Configure any channels that have FDT data. */ + 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 + + /* Configure any channels that have hint data. */ + name = device_get_name(sc->dev); + unit = device_get_unit(sc->dev); + for (chan = 0; chan < sc->chipinfo->numchan; ++chan) { + found = false; + gainidx = DEFAULT_GAINIDX; + rateidx = DEFAULT_RATEIDX; + if (resource_int_value(name, unit, "gain_index", &gainidx) == 0) + found = true; + if (resource_int_value(name, unit, "rate_index", &gainidx) == 0) + found = true; + if (found) { + ads111x_setup_channel(sc, chan, gainidx, rateidx); + ++num_added; + } + } + + /* If any channels were configured via FDT or hints, we're done. */ + if (num_added > 0) + return; + + /* + * No channel config; add all possible channels using default values, + * and let the user configure the ones they want on the fly via sysctl. + */ + 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 + + /* For hinted devices, we must be told the chip type. */ + 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); + return (BUS_PROBE_DEFAULT); + } + + 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); + } + + /* Set the default chip config. */ + if ((err = ads111x_write_2(sc, ADS111x_CONF, sc->cfgword)) != 0) { + device_printf(dev, "cannot write chip config register\n"); + return (err); + } + + /* Add the sysctl handler to set the chip configuration register. */ + 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, sc, 0, + ads111x_sysctl_config, "I", "configuration register word"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "lo_thresh", CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0, + ads111x_sysctl_lothresh, "I", "comparator low threshold"); + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "hi_thresh", CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0, + ads111x_sysctl_hithresh, "I", "comparator high threshold"); + + /* Set up channels based on metadata or default config. */ + ads111x_add_channels(sc); + + sx_init(&sc->lock, "ads111x"); + + 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), +}; +static devclass_t ads111x_devclass; + +DRIVER_MODULE(ads111x, iicbus, ads111x_driver, ads111x_devclass, NULL, NULL); +MODULE_VERSION(ads111x, 1); +MODULE_DEPEND(ads111x, iicbus, 1, 1, 1); diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile index 2ff819816098..aec95c7eb3e4 100644 --- a/sys/modules/i2c/Makefile +++ b/sys/modules/i2c/Makefile @@ -1,6 +1,7 @@ # $FreeBSD$ SUBDIR = \ + ads111x \ controllers \ cyapa \ ds1307 \ diff --git a/sys/modules/i2c/ads111x/Makefile b/sys/modules/i2c/ads111x/Makefile new file mode 100644 index 000000000000..fe77a688ea44 --- /dev/null +++ b/sys/modules/i2c/ads111x/Makefile @@ -0,0 +1,15 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/iicbus + +KMOD= ads111x +SRCS= ads111x.c + +SRCS+= \ + bus_if.h \ + device_if.h \ + iicbus_if.h \ + ofw_bus_if.h \ + opt_platform.h \ + +.include