iio: adc: ti-ads112c14: add measurement channel support

Add support for parsing devicetree properties for measurement channels
and doing direct reads on these.

There are quite a lot of conditions that have to be met for each
measurement to be made, so quite a bit of state and algorithms are
required to handle it.

Channels are created dynamically since the number of possibilities is
unreasonably large.

Signed-off-by: David Lechner (TI) <dlechner@baylibre.com>
Signed-off-by: Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
This commit is contained in:
David Lechner (TI) 2026-07-14 18:21:30 -05:00 committed by Jonathan Cameron
parent aa372a9abc
commit 36c12dbda8

View File

@ -12,9 +12,11 @@
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/device/devres.h>
#include <linux/i2c.h>
#include <linux/iio/iio.h>
#include <linux/math64.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
@ -77,6 +79,7 @@
#define ADS112C14_REG_MUX_CFG 0x07
#define ADS112C14_MUX_CFG_AINP GENMASK(7, 4)
#define ADS112C14_MUX_CFG_AINN GENMASK(3, 0)
#define ADS112C14_MUX_CFG_AIN_GND 8
#define ADS112C14_REG_GAIN_CFG 0x08
#define ADS112C14_GAIN_CFG_SPARE BIT(7)
@ -130,6 +133,20 @@
#define ADS112C14_INT_REF0_mV 1250
#define ADS112C14_INT_REF1_mV 2500
enum {
ADS112C14_VREF_SOURCE_INTERNAL_2_5V,
ADS112C14_VREF_SOURCE_INTERNAL_1_25V,
ADS112C14_VREF_SOURCE_EXTERNAL,
ADS112C14_VREF_SOURCE_AVDD,
};
static const char * const ads112c14_vref_source_names[] = {
[ADS112C14_VREF_SOURCE_INTERNAL_2_5V] = "internal-2.5v",
[ADS112C14_VREF_SOURCE_INTERNAL_1_25V] = "internal-1.25v",
[ADS112C14_VREF_SOURCE_EXTERNAL] = "external",
[ADS112C14_VREF_SOURCE_AVDD] = "avdd",
};
/*
* Available gains as tenths (e.g. value 5 == 0.5 gain). Indexes correspond to
* ADS112C14_GAIN_CFG_GAIN values.
@ -204,11 +221,33 @@ static const struct iio_chan_spec ads112c14_sys_mon_channels[] = {
},
};
struct ads112c14_measurement {
const char *label;
u32 vref_source;
u8 iunit;
u8 idac1_mag;
u8 idac2_mag;
u8 idac1_mux;
u8 idac2_mux;
u8 iadc_count;
u8 gain_val;
bool global_chop;
bool bipolar;
int scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
};
struct ads112c14_data {
const struct ads112c14_chip_info *chip_info;
struct regmap *regmap;
/* Synchronizes access to register value fields. */
struct mutex lock;
u32 avdd_uV;
u32 ext_ref_uV;
bool refp_is_avdd;
bool refn_is_gnd;
u32 ext_ref_ohms;
struct ads112c14_measurement *measurements;
u32 num_measurements;
u8 sys_mon_chan_short_gain_val;
int sys_mon_chan_short_scale_available[ARRAY_SIZE(ads112c14_pga_gains_x10)][2];
};
@ -263,12 +302,112 @@ static const struct regmap_config ads112c14_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
static int ads112c14_prepare_measurement_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
struct ads112c14_measurement *measurement = &data->measurements[chan->scan_index];
u32 refp_buf_en, refn_buf_en, ref_val, ref_sel;
int ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_MUX_CFG,
ADS112C14_MUX_CFG_AINP | ADS112C14_MUX_CFG_AINN,
FIELD_PREP(ADS112C14_MUX_CFG_AINP, chan->channel) |
FIELD_PREP(ADS112C14_MUX_CFG_AINN, chan->channel2));
if (ret)
return ret;
ret = regmap_assign_bits(data->regmap, ADS112C14_REG_DIGITAL_CFG,
ADS112C14_DIGITAL_CFG_CODING,
!measurement->bipolar);
if (ret)
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_GAIN_CFG,
ADS112C14_GAIN_CFG_SYS_MON |
ADS112C14_GAIN_CFG_GAIN,
FIELD_PREP(ADS112C14_GAIN_CFG_SYS_MON, 0) |
FIELD_PREP(ADS112C14_GAIN_CFG_GAIN,
measurement->gain_val));
if (ret)
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_IDAC_MAG_CFG,
ADS112C14_IDAC_MAG_CFG_I2MAG |
ADS112C14_IDAC_MAG_CFG_I1MAG,
FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I2MAG,
measurement->idac2_mag) |
FIELD_PREP(ADS112C14_IDAC_MAG_CFG_I1MAG,
measurement->idac1_mag));
if (ret)
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_IDAC_MUX_CFG,
ADS112C14_IDAC_MUX_CFG_IUNIT |
ADS112C14_IDAC_MUX_CFG_I2MUX |
ADS112C14_IDAC_MUX_CFG_I1MUX,
FIELD_PREP(ADS112C14_IDAC_MUX_CFG_IUNIT,
measurement->iunit) |
FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I2MUX,
measurement->idac2_mux) |
FIELD_PREP(ADS112C14_IDAC_MUX_CFG_I1MUX,
measurement->idac1_mux));
if (ret)
return ret;
ret = regmap_update_bits(data->regmap, ADS112C14_REG_DATA_RATE_CFG,
ADS112C14_DATA_RATE_CFG_GC_EN,
FIELD_PREP(ADS112C14_DATA_RATE_CFG_GC_EN,
measurement->global_chop));
if (ret)
return ret;
refp_buf_en = !data->refp_is_avdd &&
measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
refn_buf_en = !data->refn_is_gnd &&
measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL;
ref_val = measurement->vref_source == ADS112C14_VREF_SOURCE_INTERNAL_2_5V ?
ADS112C14_REFERENCE_CFG_REF_VAL_2_5V :
ADS112C14_REFERENCE_CFG_REF_VAL_1_25V;
switch (measurement->vref_source) {
case ADS112C14_VREF_SOURCE_AVDD:
ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_AVDD;
break;
case ADS112C14_VREF_SOURCE_EXTERNAL:
ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_EXTERNAL;
break;
default:
ref_sel = ADS112C14_REFERENCE_CFG_REF_SEL_INTERNAL;
break;
}
return regmap_update_bits(data->regmap, ADS112C14_REG_REFERENCE_CFG,
ADS112C14_REFERENCE_CFG_REFP_BUF_EN |
ADS112C14_REFERENCE_CFG_REFN_BUF_EN |
ADS112C14_REFERENCE_CFG_REF_VAL |
ADS112C14_REFERENCE_CFG_REF_SEL,
FIELD_PREP(ADS112C14_REFERENCE_CFG_REFP_BUF_EN,
refp_buf_en) |
FIELD_PREP(ADS112C14_REFERENCE_CFG_REFN_BUF_EN,
refn_buf_en) |
FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_VAL,
ref_val) |
FIELD_PREP(ADS112C14_REFERENCE_CFG_REF_SEL,
ref_sel));
}
static int ads112c14_prepare_sys_mon_channel(struct ads112c14_data *data,
const struct iio_chan_spec *chan)
{
u32 gain_val;
int ret;
/*
* NB: IDAC registers are left as-is in case they are generating current
* needed for the external reference measurement.
*/
/*
* All SYS_MON channels use GAIN of 1 to keep it simple. Other than
* the internal short channel, where it is useful in practice.
@ -326,8 +465,9 @@ static int ads112c14_single_conversion(struct ads112c14_data *data,
guard(mutex)(&data->lock);
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
/* Not implemented yet. */
return -EINVAL;
ret = ads112c14_prepare_measurement_channel(data, chan);
if (ret)
return ret;
} else {
ret = ads112c14_prepare_sys_mon_channel(data, chan);
if (ret)
@ -360,6 +500,7 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
int *val, int *val2, long mask)
{
struct ads112c14_data *data = iio_priv(indio_dev);
struct ads112c14_measurement *measurement = NULL;
const int *scale_avail;
u32 vref_uV, fsr_bits;
@ -367,8 +508,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
vref_uV = ADS112C14_INT_REF1_mV * (MICRO / MILLI);
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
/* Not implemented yet. */
return -EINVAL;
measurement = &data->measurements[chan->scan_index];
fsr_bits = data->chip_info->resolution_bits - measurement->bipolar;
} else {
/* All SYS_MON channels are using signed coding. */
fsr_bits = data->chip_info->resolution_bits - 1;
@ -398,7 +539,8 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
*val = sign_extend32(*val, fsr_bits);
if (!measurement || measurement->bipolar)
*val = sign_extend32(*val, fsr_bits);
return IIO_VAL_INT;
}
@ -410,6 +552,16 @@ static int ads112c14_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_FRACTIONAL_LOG2;
}
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
guard(mutex)(&data->lock);
scale_avail = measurement->scale_available[measurement->gain_val];
*val = scale_avail[0];
*val2 = scale_avail[1];
return IIO_VAL_DECIMAL64_PICO;
}
if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
u8 idx;
@ -463,6 +615,18 @@ static int ads112c14_read_avail(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_SCALE:
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
struct ads112c14_measurement *measurement;
guard(mutex)(&data->lock);
measurement = &data->measurements[chan->scan_index];
*vals = (const int *)measurement->scale_available;
*length = 2 * ARRAY_SIZE(measurement->scale_available);
*type = IIO_VAL_DECIMAL64_PICO;
return IIO_AVAIL_LIST;
}
if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
guard(mutex)(&data->lock);
@ -490,7 +654,13 @@ static int ads112c14_write_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_SCALE: {
guard(mutex)(&data->lock);
if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
struct ads112c14_measurement *measurement;
measurement = &data->measurements[chan->scan_index];
scale_avail = measurement->scale_available;
gain_val = &measurement->gain_val;
} else if (chan->channel == ADS112C14_SYS_MON_CHANNEL_SHORT) {
scale_avail = data->sys_mon_chan_short_scale_available;
gain_val = &data->sys_mon_chan_short_gain_val;
} else {
@ -527,8 +697,20 @@ static int ads112c14_write_raw_get_fmt(struct iio_dev *indio_dev,
static int ads112c14_read_label(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, char *label)
{
struct ads112c14_data *data = iio_priv(indio_dev);
const char *label_source;
/* measurement channels */
if (chan->channel < ADS112C14_SYS_MON_CHANNEL_BASE) {
struct ads112c14_measurement *measurement;
measurement = &data->measurements[chan->scan_index];
if (!measurement->label)
return -EINVAL;
return sysfs_emit(label, "%s\n", measurement->label);
}
/* System monitor channels. */
switch (chan->channel) {
case ADS112C14_SYS_MON_CHANNEL_TEMP:
@ -561,6 +743,215 @@ static const struct iio_info ads112c14_info = {
.read_label = ads112c14_read_label,
};
static int ads112c14_populate_idac_mag(u32 current_nA, u8 *idac_mag)
{
u32 current_uA = current_nA / (NANO / MICRO);
/* Convert microamps to IMAG bits */
if (current_uA == 1)
*idac_mag = 1;
else if (in_range(current_uA, 10, 100) && current_uA % 10 == 0)
*idac_mag = current_uA / 10 + 1;
else
return dev_err_probe(NULL, -EINVAL,
"invalid excitation-current-nanoamp value\n");
return 0;
}
static int ads112c14_parse_channels(struct iio_dev *indio_dev,
bool *need_avdd_ref, bool *need_ext_ref)
{
struct ads112c14_data *data = iio_priv(indio_dev);
struct device *dev = indio_dev->dev.parent;
struct iio_chan_spec *channels;
u32 num_child_nodes, i, pair[2];
int ret;
*need_avdd_ref = false;
*need_ext_ref = false;
num_child_nodes = device_get_named_child_node_count(dev, "channel");
data->measurements = devm_kcalloc(dev, num_child_nodes,
sizeof(*data->measurements), GFP_KERNEL);
if (!data->measurements)
return -ENOMEM;
channels = devm_kcalloc(dev, num_child_nodes +
ARRAY_SIZE(ads112c14_sys_mon_channels),
sizeof(*channels), GFP_KERNEL);
if (!channels)
return -ENOMEM;
i = 0;
device_for_each_named_child_node_scoped(dev, child, "channel") {
struct ads112c14_measurement *measurement = &data->measurements[i];
struct iio_chan_spec *spec = &channels[i];
spec->indexed = 1;
spec->scan_index = i;
measurement->gain_val = 1;
if (fwnode_property_present(child, "label")) {
ret = fwnode_property_read_string(child, "label", &measurement->label);
if (ret)
return dev_err_probe(dev, ret,
"failed to read label property\n");
}
if (fwnode_property_present(child, "single-channel")) {
ret = fwnode_property_read_u32(child, "single-channel",
&pair[0]);
if (ret)
return dev_err_probe(dev, ret,
"failed to read single-channel property\n");
if (pair[0] >= 8)
return dev_err_probe(dev, -EINVAL,
"single-channel value must be between 0 and 7\n");
spec->channel = pair[0];
/*
* NB: channel2 is unused by iio core code in this case.
* Let's us avoid special case for negative input mux
* for single-ended channels when taking measurements.
*/
spec->channel2 = ADS112C14_MUX_CFG_AIN_GND;
} else if (fwnode_property_present(child, "diff-channels")) {
ret = fwnode_property_read_u32_array(child, "diff-channels",
pair, ARRAY_SIZE(pair));
if (ret)
return dev_err_probe(dev, ret,
"failed to read diff-channels property\n");
if (pair[0] >= 8 || pair[1] >= 8)
return dev_err_probe(dev, -EINVAL,
"diff-channels values must be between 0 and 7\n");
spec->differential = 1;
spec->channel = pair[0];
spec->channel2 = pair[1];
} else {
return dev_err_probe(dev, -EINVAL,
"channel node missing channel type property\n");
}
if (fwnode_property_present(child, "excitation-channels")) {
ret = fwnode_property_count_u32(child, "excitation-channels");
if (ret < 0)
return dev_err_probe(dev, ret,
"failed to read excitation-channels property\n");
if (ret < 1 || ret > 2)
return dev_err_probe(dev, -EINVAL,
"excitation-channels property must have 1 or 2 values\n");
measurement->iadc_count = ret;
pair[1] = 0;
ret = fwnode_property_read_u32_array(child, "excitation-channels",
pair, measurement->iadc_count);
if (ret)
return dev_err_probe(dev, ret,
"failed to read excitation-channels property\n");
if (pair[0] >= 8 || pair[1] >= 8)
return dev_err_probe(dev, -EINVAL,
"excitation-channels values must be between 0 and 7\n");
measurement->idac1_mux = pair[0];
measurement->idac2_mux = measurement->iadc_count > 1 ? pair[1] : 0;
ret = fwnode_property_read_u32_array(child, "excitation-current-nanoamp",
pair, measurement->iadc_count);
if (ret)
return dev_err_probe(dev, ret,
"failed to read excitation-current-nanoamp property\n");
if (pair[0] <= 100 * (NANO / MICRO) &&
(measurement->iadc_count == 1 || pair[1] <= 100 * (NANO / MICRO))) {
/*
* If both values are 100µA or less, then we can
* use IUNIT = 1µA for better precision.
*/
ret = ads112c14_populate_idac_mag(pair[0],
&measurement->idac1_mag);
if (ret)
return ret;
if (measurement->iadc_count > 1) {
ret = ads112c14_populate_idac_mag(pair[1],
&measurement->idac2_mag);
if (ret)
return ret;
}
} else {
/*
* Otherwise, IUINT is 10µA (flag set) and so
* IxMAG is 1/10 of the actual current.
*/
measurement->iunit = 1;
ret = ads112c14_populate_idac_mag(pair[0] / 10,
&measurement->idac1_mag);
if (ret)
return ret;
if (measurement->iadc_count > 1) {
ret = ads112c14_populate_idac_mag(pair[1] / 10,
&measurement->idac2_mag);
if (ret)
return ret;
}
}
}
measurement->bipolar = fwnode_property_read_bool(child, "bipolar");
measurement->global_chop = fwnode_property_read_bool(child,
"input-chopping");
if (fwnode_property_present(child, "reference-sources")) {
ret = fwnode_property_match_property_string(child,
"reference-sources", ads112c14_vref_source_names,
ARRAY_SIZE(ads112c14_vref_source_names));
if (ret < 0)
return dev_err_probe(dev, ret,
"invalid reference-sources value\n");
measurement->vref_source = ret;
}
if (measurement->vref_source == ADS112C14_VREF_SOURCE_AVDD)
*need_avdd_ref = true;
if (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL)
*need_ext_ref = true;
spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE);
spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SCALE);
/*
* If reference source is resistor rather than voltage supply,
* then the measurement is effectively a resistance measurement.
*/
spec->type = (measurement->vref_source == ADS112C14_VREF_SOURCE_EXTERNAL &&
data->ext_ref_ohms) ? IIO_RESISTANCE : IIO_VOLTAGE;
if (spec->type == IIO_RESISTANCE)
spec->differential = 0;
i++;
}
data->num_measurements = i;
memcpy(channels + i, ads112c14_sys_mon_channels, sizeof(ads112c14_sys_mon_channels));
indio_dev->channels = channels;
indio_dev->num_channels = i + ARRAY_SIZE(ads112c14_sys_mon_channels);
return 0;
}
static void ads112c14_populate_scale_available(int (*scale_avail)[2],
u32 full_scale, u32 fsr_bits)
{
@ -580,6 +971,33 @@ static void ads112c14_populate_tables(struct ads112c14_data *data)
{
u32 full_scale, fsr_bits;
for (u32 i = 0; i < data->num_measurements; i++) {
struct ads112c14_measurement *measurement = &data->measurements[i];
switch (measurement->vref_source) {
case ADS112C14_VREF_SOURCE_EXTERNAL:
if (data->ext_ref_ohms)
full_scale = data->ext_ref_ohms;
else
full_scale = data->ext_ref_uV / (MICRO / MILLI);
break;
case ADS112C14_VREF_SOURCE_AVDD:
full_scale = data->avdd_uV / (MICRO / MILLI);
break;
case ADS112C14_VREF_SOURCE_INTERNAL_1_25V:
full_scale = ADS112C14_INT_REF0_mV;
break;
default:
full_scale = ADS112C14_INT_REF1_mV;
break;
}
fsr_bits = data->chip_info->resolution_bits - measurement->bipolar;
ads112c14_populate_scale_available(measurement->scale_available,
full_scale, fsr_bits);
}
/* For now, assuming all sys_mon channels are using 2.5V reference. */
full_scale = ADS112C14_INT_REF1_mV;
fsr_bits = data->chip_info->resolution_bits - 1;
@ -594,6 +1012,9 @@ static int ads112c14_probe(struct i2c_client *client)
const struct ads112c14_chip_info *info;
struct iio_dev *indio_dev;
struct ads112c14_data *data;
bool need_avdd_ref, need_ext_ref;
u32 refp_uV = 0;
u32 refn_uV = 0;
u32 reg_val;
int ret;
@ -612,13 +1033,76 @@ static int ads112c14_probe(struct i2c_client *client)
if (ret)
return ret;
if (device_property_present(dev, "ti,refp-refn-resistor-ohms")) {
ret = device_property_read_u32(dev, "ti,refp-refn-resistor-ohms",
&data->ext_ref_ohms);
if (ret)
return dev_err_probe(dev, ret,
"failed to read ti,refp-refn-resistor-ohms property\n");
}
ret = ads112c14_parse_channels(indio_dev, &need_avdd_ref, &need_ext_ref);
if (ret)
return ret;
ret = devm_regulator_get_enable(dev, "dvdd");
if (ret)
return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
ret = devm_regulator_get_enable(dev, "avdd");
if (ret)
return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
if (need_avdd_ref) {
ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
if (ret < 0)
return dev_err_probe(dev, ret, "failed to get avdd voltage\n");
data->avdd_uV = ret;
} else {
ret = devm_regulator_get_enable(dev, "avdd");
if (ret)
return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
}
if (device_property_present(dev, "refp-supply")) {
ret = devm_regulator_get_enable_read_voltage(dev, "refp");
if (ret < 0)
return dev_err_probe(dev, ret, "failed to get refp voltage\n");
refp_uV = ret;
struct fwnode_handle *refp_fwnode __free(fwnode_handle) =
fwnode_find_reference(dev->fwnode, "refp-supply", 0);
if (IS_ERR(refp_fwnode))
return dev_err_probe(dev, PTR_ERR(refp_fwnode),
"failed to get refp fwnode\n");
struct fwnode_handle *avdd_fwnode __free(fwnode_handle) =
fwnode_find_reference(dev->fwnode, "avdd-supply", 0);
if (IS_ERR(avdd_fwnode))
return dev_err_probe(dev, PTR_ERR(avdd_fwnode),
"failed to get avdd fwnode\n");
/* REFP buffer should not be enabled when connected to AVDD */
data->refp_is_avdd = refp_fwnode == avdd_fwnode;
}
if (device_property_present(dev, "refn-supply")) {
ret = devm_regulator_get_enable_read_voltage(dev, "refn");
if (ret < 0)
return dev_err_probe(dev, ret, "failed to get refn voltage\n");
refn_uV = ret;
} else {
data->refn_is_gnd = true;
}
data->ext_ref_uV = refp_uV - refn_uV;
if (data->ext_ref_uV && data->ext_ref_ohms)
return dev_err_probe(dev, -EINVAL,
"ti,refp-refn-resistor-ohms property should not be present when refp-supply or refn-supply is present\n");
if (need_ext_ref && !data->ext_ref_uV && !data->ext_ref_ohms)
return dev_err_probe(dev, -EINVAL,
"external reference measurements require either refp-supply or ti,refp-refn-resistor-ohms property\n");
/* It takes some time for the internal reference to stabilize. */
fsleep(10 * USEC_PER_MSEC);
@ -680,8 +1164,6 @@ static int ads112c14_probe(struct i2c_client *client)
indio_dev->name = info->name;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = ads112c14_sys_mon_channels;
indio_dev->num_channels = ARRAY_SIZE(ads112c14_sys_mon_channels);
indio_dev->info = &ads112c14_info;
return devm_iio_device_register(dev, indio_dev);