aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--share/man/man4/ads111x.4240
-rw-r--r--sys/conf/NOTES1
-rw-r--r--sys/conf/files1
-rw-r--r--sys/dev/iicbus/ads111x.c582
-rw-r--r--sys/modules/i2c/Makefile1
-rw-r--r--sys/modules/i2c/ads111x/Makefile15
6 files changed, 840 insertions, 0 deletions
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 <ian@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 ``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.<unit>.<channel>.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.<unit>.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.<unit>.lo_thresh
+Sets the low threshold for activating the alert pin.
+.It Va dev.ads1115.<unit>.hi_thresh
+Sets the high threshold for activating the alert pin.
+.It Va dev.ads1115.<unit>.<channel>.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.<unit>.<channel>.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.<unit>.<channel>.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.<unit>.at
+The iicbus instance the
+.Nm
+instance is attached to.
+.It Va hint.ads1115.<unit>.<channel>.gain_index
+The amplifier gain, as described above for the sysctl variable
+.Va dev.ads1115.<unit>.<channel>.gain_index .
+.It Va hint.ads1115.<unit>.<channel>.rate_index
+The sample rate, as described above for the sysctl variable
+.Va dev.ads1115.<unit>.<channel>.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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#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"
+
+/*
+ * 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 <bsd.kmod.mk>