From: Dumitru Ceclan <dumitru.ceclan@analog.com>
Add support for AD4111/AD4112/AD4114/AD4115/AD4116.
The AD411X family encompasses a series of low power, low noise, 24-bit,
sigma-delta analog-to-digital converters that offer a versatile range of
specifications.
This family of ADCs integrates an analog front end suitable for processing
both fully differential and single-ended, bipolar voltage inputs
addressing a wide array of industrial and instrumentation requirements.
- All ADCs have inputs with a precision voltage divider with a division
ratio of 10.
- AD4116 has 5 low level inputs without a voltage divider.
- AD4111 and AD4112 support current inputs (0 mA to 20 mA) using a 50ohm
shunt resistor.
Signed-off-by: Dumitru Ceclan <dumitru.ceclan@analog.com>
---
drivers/iio/adc/ad7173.c | 327 ++++++++++++++++++++++++++++++++++++++++++-----
1 file changed, 297 insertions(+), 30 deletions(-)
diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c
index 106a50dbabd4..328685ce25e0 100644
--- a/drivers/iio/adc/ad7173.c
+++ b/drivers/iio/adc/ad7173.c
@@ -1,8 +1,9 @@
// SPDX-License-Identifier: GPL-2.0+
/*
- * AD717x family SPI ADC driver
+ * AD717x and AD411x family SPI ADC driver
*
* Supported devices:
+ * AD4111/AD4112/AD4114/AD4115/AD4116
* AD7172-2/AD7172-4/AD7173-8/AD7175-2
* AD7175-8/AD7176-2/AD7177-2
*
@@ -75,7 +76,9 @@
#define AD7176_ID 0x0c90
#define AD7175_2_ID 0x0cd0
#define AD7172_4_ID 0x2050
-#define AD7173_ID 0x30d0
+#define AD7173_AD4111_AD4112_AD4114_ID 0x30d0
+#define AD4116_ID 0x34d0
+#define AD4115_ID 0x38d0
#define AD7175_8_ID 0x3cd0
#define AD7177_ID 0x4fd0
#define AD7173_ID_MASK GENMASK(15, 4)
@@ -106,6 +109,7 @@
#define AD7173_GPO12_DATA(x) BIT((x) + 0)
#define AD7173_GPO23_DATA(x) BIT((x) + 4)
+#define AD4111_GPO01_DATA(x) BIT((x) + 6)
#define AD7173_GPO_DATA(x) ((x) < 2 ? AD7173_GPO12_DATA(x) : AD7173_GPO23_DATA(x))
#define AD7173_INTERFACE_DATA_STAT BIT(6)
@@ -124,11 +128,20 @@
#define AD7173_VOLTAGE_INT_REF_uV 2500000
#define AD7173_TEMP_SENSIIVITY_uV_per_C 477
#define AD7177_ODR_START_VALUE 0x07
+#define AD4111_SHUNT_RESISTOR_OHM 50
+#define AD4111_DIVIDER_RATIO 10
+#define AD411X_VCOM_INPUT 0X10
+#define AD4111_CURRENT_CHAN_CUTOFF 16
#define AD7173_FILTER_ODR0_MASK GENMASK(5, 0)
#define AD7173_MAX_CONFIGS 8
enum ad7173_ids {
+ ID_AD4111,
+ ID_AD4112,
+ ID_AD4114,
+ ID_AD4115,
+ ID_AD4116,
ID_AD7172_2,
ID_AD7172_4,
ID_AD7173_8,
@@ -138,22 +151,43 @@ enum ad7173_ids {
ID_AD7177_2,
};
+enum ad4111_current_channels {
+ AD4111_CURRENT_IN0P_IN0N,
+ AD4111_CURRENT_IN1P_IN1N,
+ AD4111_CURRENT_IN2P_IN2N,
+ AD4111_CURRENT_IN3P_IN3N,
+};
+
+enum ad7173_channel_types {
+ AD7173_CHAN_SINGLE_ENDED,
+ AD7173_CHAN_DIFFERENTIAL,
+};
+
struct ad7173_device_info {
const unsigned int *sinc5_data_rates;
unsigned int num_sinc5_data_rates;
unsigned int odr_start_value;
+ /*
+ * AD4116 has both inputs with a volage divider and without.
+ * These inputs cannot be mixed in the channel configuration.
+ * Does not include the VCOM input.
+ */
+ unsigned int num_voltage_inputs_with_divider;
unsigned int num_channels;
unsigned int num_configs;
- unsigned int num_inputs;
+ unsigned int num_voltage_inputs;
unsigned int clock;
unsigned int id;
char *name;
+ bool has_current_inputs;
+ bool has_vcom_input;
bool has_temp;
/* ((AVDD1 − AVSS)/5) */
bool has_common_input;
bool has_input_buf;
bool has_int_ref;
bool has_ref2;
+ bool higher_gpio_bits;
u8 num_gpios;
};
@@ -195,6 +229,24 @@ struct ad7173_state {
#endif
};
+static unsigned int ad4115_sinc5_data_rates[] = {
+ 24845000, 24845000, 20725000, 20725000, /* 0-3 */
+ 15564000, 13841000, 10390000, 10390000, /* 4-7 */
+ 4994000, 2499000, 1000000, 500000, /* 8-11 */
+ 395500, 200000, 100000, 59890, /* 12-15 */
+ 49920, 20000, 16660, 10000, /* 16-19 */
+ 5000, 2500, 2500, /* 20-22 */
+};
+
+static unsigned int ad4116_sinc5_data_rates[] = {
+ 12422360, 12422360, 12422360, 12422360, /* 0-3 */
+ 10362690, 10362690, 7782100, 6290530, /* 4-7 */
+ 5194800, 2496900, 1007600, 499900, /* 8-11 */
+ 390600, 200300, 100000, 59750, /* 12-15 */
+ 49840, 20000, 16650, 10000, /* 16-19 */
+ 5000, 2500, 1250, /* 20-22 */
+};
+
static const unsigned int ad7173_sinc5_data_rates[] = {
6211000, 6211000, 6211000, 6211000, 6211000, 6211000, 5181000, 4444000, /* 0-7 */
3115000, 2597000, 1007000, 503800, 381000, 200300, 100500, 59520, /* 8-15 */
@@ -210,14 +262,109 @@ static const unsigned int ad7175_sinc5_data_rates[] = {
5000, /* 20 */
};
+static unsigned int ad4111_current_channel_config[] = {
+ [AD4111_CURRENT_IN0P_IN0N] = 0x1E8,
+ [AD4111_CURRENT_IN1P_IN1N] = 0x1C9,
+ [AD4111_CURRENT_IN2P_IN2N] = 0x1AA,
+ [AD4111_CURRENT_IN3P_IN3N] = 0x18B,
+};
+
static const struct ad7173_device_info ad7173_device_info[] = {
+ [ID_AD4111] = {
+ .name = "ad4111",
+ .id = AD7173_AD4111_AD4112_AD4114_ID,
+ .num_voltage_inputs_with_divider = 8,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_voltage_inputs = 8,
+ .num_gpios = 2,
+ .higher_gpio_bits = true,
+ .has_temp = true,
+ .has_vcom_input = true,
+ .has_input_buf = true,
+ .has_current_inputs = true,
+ .has_int_ref = true,
+ .clock = 2 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7173_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
+ },
+ [ID_AD4112] = {
+ .name = "ad4112",
+ .id = AD7173_AD4111_AD4112_AD4114_ID,
+ .num_voltage_inputs_with_divider = 8,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_voltage_inputs = 8,
+ .num_gpios = 2,
+ .higher_gpio_bits = true,
+ .has_vcom_input = true,
+ .has_temp = true,
+ .has_input_buf = true,
+ .has_current_inputs = true,
+ .has_int_ref = true,
+ .clock = 2 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7173_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
+ },
+ [ID_AD4114] = {
+ .name = "ad4114",
+ .id = AD7173_AD4111_AD4112_AD4114_ID,
+ .num_voltage_inputs_with_divider = 16,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_voltage_inputs = 16,
+ .num_gpios = 4,
+ .higher_gpio_bits = true,
+ .has_vcom_input = true,
+ .has_temp = true,
+ .has_input_buf = true,
+ .has_int_ref = true,
+ .clock = 2 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad7173_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
+ },
+ [ID_AD4115] = {
+ .name = "ad4115",
+ .id = AD4115_ID,
+ .num_voltage_inputs_with_divider = 16,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_voltage_inputs = 16,
+ .num_gpios = 4,
+ .higher_gpio_bits = true,
+ .has_vcom_input = true,
+ .has_temp = true,
+ .has_input_buf = true,
+ .has_int_ref = true,
+ .clock = 8 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad4115_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad4115_sinc5_data_rates),
+ },
+ [ID_AD4116] = {
+ .name = "ad4116",
+ .id = AD4116_ID,
+ .num_voltage_inputs_with_divider = 11,
+ .num_channels = 16,
+ .num_configs = 8,
+ .num_voltage_inputs = 16,
+ .num_gpios = 4,
+ .higher_gpio_bits = true,
+ .has_vcom_input = true,
+ .has_temp = true,
+ .has_input_buf = true,
+ .has_int_ref = true,
+ .clock = 4 * HZ_PER_MHZ,
+ .sinc5_data_rates = ad4116_sinc5_data_rates,
+ .num_sinc5_data_rates = ARRAY_SIZE(ad4116_sinc5_data_rates),
+ },
[ID_AD7172_2] = {
.name = "ad7172-2",
.id = AD7172_2_ID,
- .num_inputs = 5,
+ .num_voltage_inputs = 5,
.num_channels = 4,
.num_configs = 4,
.num_gpios = 2,
+ .higher_gpio_bits = false,
.has_temp = true,
.has_input_buf = true,
.has_int_ref = true,
@@ -229,10 +376,11 @@ static const struct ad7173_device_info ad7173_device_info[] = {
[ID_AD7172_4] = {
.name = "ad7172-4",
.id = AD7172_4_ID,
- .num_inputs = 9,
+ .num_voltage_inputs = 9,
.num_channels = 8,
.num_configs = 8,
.num_gpios = 4,
+ .higher_gpio_bits = false,
.has_temp = false,
.has_input_buf = true,
.has_ref2 = true,
@@ -243,11 +391,12 @@ static const struct ad7173_device_info ad7173_device_info[] = {
},
[ID_AD7173_8] = {
.name = "ad7173-8",
- .id = AD7173_ID,
- .num_inputs = 17,
+ .id = AD7173_AD4111_AD4112_AD4114_ID,
+ .num_voltage_inputs = 17,
.num_channels = 16,
.num_configs = 8,
.num_gpios = 4,
+ .higher_gpio_bits = false,
.has_temp = true,
.has_input_buf = true,
.has_int_ref = true,
@@ -260,10 +409,11 @@ static const struct ad7173_device_info ad7173_device_info[] = {
[ID_AD7175_2] = {
.name = "ad7175-2",
.id = AD7175_2_ID,
- .num_inputs = 5,
+ .num_voltage_inputs = 5,
.num_channels = 4,
.num_configs = 4,
.num_gpios = 2,
+ .higher_gpio_bits = false,
.has_temp = true,
.has_input_buf = true,
.has_int_ref = true,
@@ -275,10 +425,11 @@ static const struct ad7173_device_info ad7173_device_info[] = {
[ID_AD7175_8] = {
.name = "ad7175-8",
.id = AD7175_8_ID,
- .num_inputs = 17,
+ .num_voltage_inputs = 17,
.num_channels = 16,
.num_configs = 8,
.num_gpios = 4,
+ .higher_gpio_bits = false,
.has_temp = true,
.has_input_buf = true,
.has_int_ref = true,
@@ -291,10 +442,11 @@ static const struct ad7173_device_info ad7173_device_info[] = {
[ID_AD7176_2] = {
.name = "ad7176-2",
.id = AD7176_ID,
- .num_inputs = 5,
+ .num_voltage_inputs = 5,
.num_channels = 4,
.num_configs = 4,
.num_gpios = 2,
+ .higher_gpio_bits = false,
.has_temp = false,
.has_input_buf = false,
.has_int_ref = true,
@@ -306,10 +458,11 @@ static const struct ad7173_device_info ad7173_device_info[] = {
[ID_AD7177_2] = {
.name = "ad7177-2",
.id = AD7177_ID,
- .num_inputs = 5,
+ .num_voltage_inputs = 5,
.num_channels = 4,
.num_configs = 4,
.num_gpios = 2,
+ .higher_gpio_bits = false,
.has_temp = true,
.has_input_buf = true,
.has_int_ref = true,
@@ -328,6 +481,11 @@ static const char *const ad7173_ref_sel_str[] = {
[AD7173_SETUP_REF_SEL_AVDD1_AVSS] = "avdd",
};
+static const char *const ad7173_channel_types[] = {
+ [AD7173_CHAN_SINGLE_ENDED] = "single-ended",
+ [AD7173_CHAN_DIFFERENTIAL] = "differential",
+};
+
static const char *const ad7173_clk_sel[] = {
"ext-clk", "xtal"
};
@@ -360,6 +518,15 @@ static int ad7173_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
return 0;
}
+static int ad4111_mask_xlate(struct gpio_regmap *gpio, unsigned int base,
+ unsigned int offset, unsigned int *reg,
+ unsigned int *mask)
+{
+ *mask = AD4111_GPO01_DATA(offset);
+ *reg = base;
+ return 0;
+}
+
static void ad7173_gpio_disable(void *data)
{
struct ad7173_state *st = data;
@@ -392,7 +559,10 @@ static int ad7173_gpio_init(struct ad7173_state *st)
gpio_regmap.regmap = st->reg_gpiocon_regmap;
gpio_regmap.ngpio = st->info->num_gpios;
gpio_regmap.reg_set_base = AD7173_REG_GPIO;
- gpio_regmap.reg_mask_xlate = ad7173_mask_xlate;
+ if (st->info->higher_gpio_bits)
+ gpio_regmap.reg_mask_xlate = ad4111_mask_xlate;
+ else
+ gpio_regmap.reg_mask_xlate = ad7173_mask_xlate;
st->gpio_regmap = devm_gpio_regmap_register(dev, &gpio_regmap);
ret = PTR_ERR_OR_ZERO(st->gpio_regmap);
@@ -688,18 +858,33 @@ static int ad7173_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- if (chan->type == IIO_TEMP) {
+
+ switch (chan->type) {
+ case IIO_TEMP:
temp = AD7173_VOLTAGE_INT_REF_uV * MILLI;
temp /= AD7173_TEMP_SENSIIVITY_uV_per_C;
*val = temp;
*val2 = chan->scan_type.realbits;
- } else {
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_VOLTAGE:
*val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel);
*val2 = chan->scan_type.realbits - !!(ch->cfg.bipolar);
+
+ if (chan->channel < st->info->num_voltage_inputs_with_divider)
+ *val *= AD4111_DIVIDER_RATIO;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CURRENT:
+ *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel);
+ *val /= AD4111_SHUNT_RESISTOR_OHM;
+ *val2 = chan->scan_type.realbits - (ch->cfg.bipolar ? 1 : 0);
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
}
- return IIO_VAL_FRACTIONAL_LOG2;
case IIO_CHAN_INFO_OFFSET:
- if (chan->type == IIO_TEMP) {
+
+ switch (chan->type) {
+ case IIO_TEMP:
/* 0 Kelvin -> raw sample */
temp = -ABSOLUTE_ZERO_MILLICELSIUS;
temp *= AD7173_TEMP_SENSIIVITY_uV_per_C;
@@ -708,10 +893,14 @@ static int ad7173_read_raw(struct iio_dev *indio_dev,
AD7173_VOLTAGE_INT_REF_uV *
MILLI);
*val = -temp;
- } else {
+ return IIO_VAL_INT;
+ case IIO_VOLTAGE:
+ case IIO_CURRENT:
*val = -BIT(chan->scan_type.realbits - 1);
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
}
- return IIO_VAL_INT;
case IIO_CHAN_INFO_SAMP_FREQ:
reg = st->channels[chan->address].cfg.odr;
@@ -919,13 +1108,34 @@ static int ad7173_register_clk_provider(struct iio_dev *indio_dev)
&st->int_clk_hw);
}
+static int ad4111_validate_current_ain(struct ad7173_state *st,
+ unsigned int ain[2])
+{
+ struct device *dev = &st->sd.spi->dev;
+
+ if (!st->info->has_current_inputs)
+ return dev_err_probe(dev, -EINVAL,
+ "Model %s does not support current channels\n",
+ st->info->name);
+
+ if (ain[0] >= ARRAY_SIZE(ad4111_current_channel_config))
+ return dev_err_probe(dev, -EINVAL,
+ "For current channels single-channel must be <[0-3]>\n");
+
+ return 0;
+}
+
static int ad7173_validate_voltage_ain_inputs(struct ad7173_state *st,
unsigned int ain[2])
{
struct device *dev = &st->sd.spi->dev;
+ bool ain_selects_normal_input[] = {
+ ain[0] < st->info->num_voltage_inputs,
+ ain[1] < st->info->num_voltage_inputs
+ };
for (int i = 0; i < 2; i++) {
- if (ain[i] < st->info->num_inputs)
+ if (ain_selects_normal_input[i])
continue;
if (ain[i] == AD7173_AIN_REF_POS || ain[i] == AD7173_AIN_REF_NEG)
@@ -936,11 +1146,27 @@ static int ad7173_validate_voltage_ain_inputs(struct ad7173_state *st,
st->info->has_common_input)
continue;
+ if (st->info->has_vcom_input && ain[i] == AD411X_VCOM_INPUT) {
+ if (ain_selects_normal_input[(i + 1) % 2] &&
+ ain[(i + 1) % 2] >= st->info->num_voltage_inputs_with_divider)
+ return dev_err_probe(dev, -EINVAL,
+ "VCOM must be paired with inputs having divider.\n");
+
+ continue;
+ }
+
return dev_err_probe(dev, -EINVAL,
"Input pin number out of range for pair (%d %d).\n",
ain[0], ain[1]);
}
+ if ((ain_selects_normal_input[0] && ain_selects_normal_input[1]) &&
+ ((ain[0] >= st->info->num_voltage_inputs_with_divider) !=
+ (ain[1] >= st->info->num_voltage_inputs_with_divider)))
+ return dev_err_probe(dev, -EINVAL,
+ "Both inputs must either have a voltage divider or not have: (%d %d).\n",
+ ain[0], ain[1]);
+
return 0;
}
@@ -972,7 +1198,7 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
struct device *dev = indio_dev->dev.parent;
struct iio_chan_spec *chan_arr, *chan;
unsigned int ain[2], chan_index = 0;
- int ref_sel, ret, num_channels;
+ int ref_sel, ret, is_current_chan, num_channels;
num_channels = device_get_child_node_count(dev);
@@ -1022,12 +1248,23 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
chan_st_priv = &chans_st_arr[chan_index];
ret = fwnode_property_read_u32_array(child, "diff-channels",
ain, ARRAY_SIZE(ain));
- if (ret)
- return ret;
+ if (ret) {
+ ret = fwnode_property_read_u32_array(child, "single-channel",
+ ain, 1);
+ if (ret)
+ return ret;
- ret = ad7173_validate_voltage_ain_inputs(st, ain);
- if (ret)
- return ret;
+ ret = ad4111_validate_current_ain(st, ain);
+ if (ret)
+ return ret;
+ is_current_chan = true;
+ ain[1] = 0;
+ } else {
+ ret = ad7173_validate_voltage_ain_inputs(st, ain);
+ if (ret)
+ return ret;
+ is_current_chan = false;
+ }
ret = fwnode_property_match_property_string(child,
"adi,reference-select",
@@ -1051,17 +1288,34 @@ static int ad7173_fw_parse_channel_config(struct iio_dev *indio_dev)
chan->scan_index = chan_index;
chan->channel = ain[0];
chan->channel2 = ain[1];
- chan->differential = true;
-
- chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]);
chan_st_priv->chan_reg = chan_index;
chan_st_priv->cfg.input_buf = st->info->has_input_buf;
chan_st_priv->cfg.odr = 0;
-
chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child, "bipolar");
+
if (chan_st_priv->cfg.bipolar)
chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
+ ret = fwnode_property_match_property_string(child,
+ "adi,channel-type",
+ ad7173_channel_types,
+ ARRAY_SIZE(ad7173_channel_types));
+ chan->differential = (ret < 0 || ret == AD7173_CHAN_DIFFERENTIAL)
+ ? 1 : 0;
+
+
+ if (is_current_chan) {
+ chan->type = IIO_CURRENT;
+ chan->differential = false;
+ ain[1] = FIELD_GET(AD7173_CH_SETUP_AINNEG_MASK,
+ ad4111_current_channel_config[ain[0]]);
+ ain[0] = FIELD_GET(AD7173_CH_SETUP_AINPOS_MASK,
+ ad4111_current_channel_config[ain[0]]);
+ } else {
+ chan_st_priv->cfg.input_buf = st->info->has_input_buf;
+ }
+ chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]);
+
chan_index++;
}
return 0;
@@ -1188,6 +1442,14 @@ static int ad7173_probe(struct spi_device *spi)
}
static const struct of_device_id ad7173_of_match[] = {
+ { .compatible = "ad4111",
+ .data = &ad7173_device_info[ID_AD4111]},
+ { .compatible = "ad4112",
+ .data = &ad7173_device_info[ID_AD4112]},
+ { .compatible = "ad4114",
+ .data = &ad7173_device_info[ID_AD4114]},
+ { .compatible = "ad4115",
+ .data = &ad7173_device_info[ID_AD4115]},
{ .compatible = "adi,ad7172-2",
.data = &ad7173_device_info[ID_AD7172_2]},
{ .compatible = "adi,ad7172-4",
@@ -1207,6 +1469,11 @@ static const struct of_device_id ad7173_of_match[] = {
MODULE_DEVICE_TABLE(of, ad7173_of_match);
static const struct spi_device_id ad7173_id_table[] = {
+ { "ad4111", (kernel_ulong_t)&ad7173_device_info[ID_AD4111]},
+ { "ad4112", (kernel_ulong_t)&ad7173_device_info[ID_AD4112]},
+ { "ad4114", (kernel_ulong_t)&ad7173_device_info[ID_AD4114]},
+ { "ad4115", (kernel_ulong_t)&ad7173_device_info[ID_AD4115]},
+ { "ad4116", (kernel_ulong_t)&ad7173_device_info[ID_AD4116]},
{ "ad7172-2", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_2]},
{ "ad7172-4", (kernel_ulong_t)&ad7173_device_info[ID_AD7172_4]},
{ "ad7173-8", (kernel_ulong_t)&ad7173_device_info[ID_AD7173_8]},
@@ -1231,5 +1498,5 @@ module_spi_driver(ad7173_driver);
MODULE_IMPORT_NS(IIO_AD_SIGMA_DELTA);
MODULE_AUTHOR("Lars-Peter Clausen <lars@metafo.de>");
MODULE_AUTHOR("Dumitru Ceclan <dumitru.ceclan@analog.com>");
-MODULE_DESCRIPTION("Analog Devices AD7172/AD7173/AD7175/AD7176 ADC driver");
+MODULE_DESCRIPTION("Analog Devices AD7173 and similar ADC driver");
MODULE_LICENSE("GPL");
--
2.43.0
On Mon, 2024-05-27 at 20:02 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Dumitru Ceclan <dumitru.ceclan@analog.com>
>
> Add support for AD4111/AD4112/AD4114/AD4115/AD4116.
>
> The AD411X family encompasses a series of low power, low noise, 24-bit,
> sigma-delta analog-to-digital converters that offer a versatile range of
> specifications.
>
> This family of ADCs integrates an analog front end suitable for processing
> both fully differential and single-ended, bipolar voltage inputs
> addressing a wide array of industrial and instrumentation requirements.
>
> - All ADCs have inputs with a precision voltage divider with a division
> ratio of 10.
> - AD4116 has 5 low level inputs without a voltage divider.
> - AD4111 and AD4112 support current inputs (0 mA to 20 mA) using a 50ohm
> shunt resistor.
>
> Signed-off-by: Dumitru Ceclan <dumitru.ceclan@analog.com>
> ---
> drivers/iio/adc/ad7173.c | 327 ++++++++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 297 insertions(+), 30 deletions(-)
>
> diff --git a/drivers/iio/adc/ad7173.c b/drivers/iio/adc/ad7173.c
> index 106a50dbabd4..328685ce25e0 100644
> --- a/drivers/iio/adc/ad7173.c
> +++ b/drivers/iio/adc/ad7173.c
> @@ -1,8 +1,9 @@
> // SPDX-License-Identifier: GPL-2.0+
> /*
> - * AD717x family SPI ADC driver
> + * AD717x and AD411x family SPI ADC driver
> *
> * Supported devices:
> + * AD4111/AD4112/AD4114/AD4115/AD4116
> * AD7172-2/AD7172-4/AD7173-8/AD7175-2
> * AD7175-8/AD7176-2/AD7177-2
> *
> @@ -75,7 +76,9 @@
> #define AD7176_ID 0x0c90
> #define AD7175_2_ID 0x0cd0
> #define AD7172_4_ID 0x2050
> -#define AD7173_ID 0x30d0
> +#define AD7173_AD4111_AD4112_AD4114_ID 0x30d0
I would definitely rename this :). Would even prefer to have separate defines all
defined by AD7173_ID.
> +#define AD4116_ID 0x34d0
> +#define AD4115_ID 0x38d0
> #define AD7175_8_ID 0x3cd0
> #define AD7177_ID 0x4fd0
> #define AD7173_ID_MASK GENMASK(15, 4)
> @@ -106,6 +109,7 @@
>
> #define AD7173_GPO12_DATA(x) BIT((x) + 0)
> #define AD7173_GPO23_DATA(x) BIT((x) + 4)
> +#define AD4111_GPO01_DATA(x) BIT((x) + 6)
> #define AD7173_GPO_DATA(x) ((x) < 2 ? AD7173_GPO12_DATA(x) :
> AD7173_GPO23_DATA(x))
>
> #define AD7173_INTERFACE_DATA_STAT BIT(6)
> @@ -124,11 +128,20 @@
> #define AD7173_VOLTAGE_INT_REF_uV 2500000
> #define AD7173_TEMP_SENSIIVITY_uV_per_C 477
> #define AD7177_ODR_START_VALUE 0x07
> +#define AD4111_SHUNT_RESISTOR_OHM 50
> +#define AD4111_DIVIDER_RATIO 10
> +#define AD411X_VCOM_INPUT 0X10
> +#define AD4111_CURRENT_CHAN_CUTOFF 16
>
> #define AD7173_FILTER_ODR0_MASK GENMASK(5, 0)
> #define AD7173_MAX_CONFIGS 8
>
> enum ad7173_ids {
> + ID_AD4111,
> + ID_AD4112,
> + ID_AD4114,
> + ID_AD4115,
> + ID_AD4116,
> ID_AD7172_2,
> ID_AD7172_4,
> ID_AD7173_8,
> @@ -138,22 +151,43 @@ enum ad7173_ids {
> ID_AD7177_2,
> };
>
> +enum ad4111_current_channels {
> + AD4111_CURRENT_IN0P_IN0N,
> + AD4111_CURRENT_IN1P_IN1N,
> + AD4111_CURRENT_IN2P_IN2N,
> + AD4111_CURRENT_IN3P_IN3N,
> +};
> +
> +enum ad7173_channel_types {
> + AD7173_CHAN_SINGLE_ENDED,
> + AD7173_CHAN_DIFFERENTIAL,
> +};
> +
> struct ad7173_device_info {
> const unsigned int *sinc5_data_rates;
> unsigned int num_sinc5_data_rates;
> unsigned int odr_start_value;
> + /*
> + * AD4116 has both inputs with a volage divider and without.
s/volage/voltage
> + * These inputs cannot be mixed in the channel configuration.
> + * Does not include the VCOM input.
> + */
> + unsigned int num_voltage_inputs_with_divider;
nit: maybe num_voltage_in_div?
> unsigned int num_channels;
> unsigned int num_configs;
> - unsigned int num_inputs;
> + unsigned int num_voltage_inputs;
nit: maybe num_voltage_in?
> unsigned int clock;
> unsigned int id;
> char *name;
> + bool has_current_inputs;
> + bool has_vcom_input;
> bool has_temp;
> /* ((AVDD1 − AVSS)/5) */
> bool has_common_input;
> bool has_input_buf;
> bool has_int_ref;
> bool has_ref2;
> + bool higher_gpio_bits;
> u8 num_gpios;
> };
>
> @@ -195,6 +229,24 @@ struct ad7173_state {
> #endif
> };
>
> +static unsigned int ad4115_sinc5_data_rates[] = {
> + 24845000, 24845000, 20725000, 20725000, /* 0-3 */
> + 15564000, 13841000, 10390000, 10390000, /* 4-7 */
> + 4994000, 2499000, 1000000, 500000, /* 8-11 */
> + 395500, 200000, 100000, 59890, /* 12-15 */
> + 49920, 20000, 16660, 10000, /* 16-19 */
> + 5000, 2500, 2500, /* 20-22 */
> +};
> +
> +static unsigned int ad4116_sinc5_data_rates[] = {
> + 12422360, 12422360, 12422360, 12422360, /* 0-3 */
> + 10362690, 10362690, 7782100, 6290530, /* 4-7 */
> + 5194800, 2496900, 1007600, 499900, /* 8-11 */
> + 390600, 200300, 100000, 59750, /* 12-15 */
> + 49840, 20000, 16650, 10000, /* 16-19 */
> + 5000, 2500, 1250, /* 20-22 */
> +};
> +
> static const unsigned int ad7173_sinc5_data_rates[] = {
> 6211000, 6211000, 6211000, 6211000, 6211000, 6211000, 5181000,
> 4444000, /* 0-7 */
> 3115000, 2597000, 1007000, 503800, 381000, 200300, 100500,
> 59520, /* 8-15 */
> @@ -210,14 +262,109 @@ static const unsigned int ad7175_sinc5_data_rates[] = {
> 5000, /* 20 */
> };
>
> +static unsigned int ad4111_current_channel_config[] = {
> + [AD4111_CURRENT_IN0P_IN0N] = 0x1E8,
> + [AD4111_CURRENT_IN1P_IN1N] = 0x1C9,
> + [AD4111_CURRENT_IN2P_IN2N] = 0x1AA,
> + [AD4111_CURRENT_IN3P_IN3N] = 0x18B,
> +};
> +
> static const struct ad7173_device_info ad7173_device_info[] = {
> + [ID_AD4111] = {
> + .name = "ad4111",
> + .id = AD7173_AD4111_AD4112_AD4114_ID,
> + .num_voltage_inputs_with_divider = 8,
> + .num_channels = 16,
> + .num_configs = 8,
> + .num_voltage_inputs = 8,
> + .num_gpios = 2,
> + .higher_gpio_bits = true,
> + .has_temp = true,
> + .has_vcom_input = true,
> + .has_input_buf = true,
> + .has_current_inputs = true,
> + .has_int_ref = true,
> + .clock = 2 * HZ_PER_MHZ,
> + .sinc5_data_rates = ad7173_sinc5_data_rates,
> + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
> + },
At some point it would be nice to drop the ad7173_device_info array...
...
>
> @@ -688,18 +858,33 @@ static int ad7173_read_raw(struct iio_dev *indio_dev,
>
> return IIO_VAL_INT;
> case IIO_CHAN_INFO_SCALE:
> - if (chan->type == IIO_TEMP) {
> +
> + switch (chan->type) {
> + case IIO_TEMP:
> temp = AD7173_VOLTAGE_INT_REF_uV * MILLI;
> temp /= AD7173_TEMP_SENSIIVITY_uV_per_C;
> *val = temp;
> *val2 = chan->scan_type.realbits;
> - } else {
> + return IIO_VAL_FRACTIONAL_LOG2;
> + case IIO_VOLTAGE:
> *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel);
> *val2 = chan->scan_type.realbits - !!(ch->cfg.bipolar);
> +
> + if (chan->channel < st->info-
> >num_voltage_inputs_with_divider)
> + *val *= AD4111_DIVIDER_RATIO;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + case IIO_CURRENT:
> + *val = ad7173_get_ref_voltage_milli(st, ch->cfg.ref_sel);
> + *val /= AD4111_SHUNT_RESISTOR_OHM;
> + *val2 = chan->scan_type.realbits - (ch->cfg.bipolar ? 1 :
> 0);
Can bipolar have any other value than 0 or 1? Just subtract it directly...
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> }
> - return IIO_VAL_FRACTIONAL_LOG2;
> case IIO_CHAN_INFO_OFFSET:
> - if (chan->type == IIO_TEMP) {
> +
> + switch (chan->type) {
> + case IIO_TEMP:
> /* 0 Kelvin -> raw sample */
> temp = -ABSOLUTE_ZERO_MILLICELSIUS;
> temp *= AD7173_TEMP_SENSIIVITY_uV_per_C;
> @@ -708,10 +893,14 @@ static int ad7173_read_raw(struct iio_dev *indio_dev,
> AD7173_VOLTAGE_INT_REF_uV *
> MILLI);
> *val = -temp;
> - } else {
> + return IIO_VAL_INT;
> + case IIO_VOLTAGE:
> + case IIO_CURRENT:
> *val = -BIT(chan->scan_type.realbits - 1);
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> }
> - return IIO_VAL_INT;
> case IIO_CHAN_INFO_SAMP_FREQ:
> reg = st->channels[chan->address].cfg.odr;
>
> @@ -919,13 +1108,34 @@ static int ad7173_register_clk_provider(struct iio_dev
> *indio_dev)
> &st->int_clk_hw);
> }
>
> +static int ad4111_validate_current_ain(struct ad7173_state *st,
> + unsigned int ain[2])
Hmm, pass by reference... Should also be const AFAICT.
...
>
> @@ -1022,12 +1248,23 @@ static int ad7173_fw_parse_channel_config(struct iio_dev
> *indio_dev)
> chan_st_priv = &chans_st_arr[chan_index];
> ret = fwnode_property_read_u32_array(child, "diff-channels",
> ain, ARRAY_SIZE(ain));
> - if (ret)
> - return ret;
> + if (ret) {
> + ret = fwnode_property_read_u32_array(child, "single-
> channel",
> + ain, 1);
> + if (ret)
> + return ret;
>
> - ret = ad7173_validate_voltage_ain_inputs(st, ain);
> - if (ret)
> - return ret;
> + ret = ad4111_validate_current_ain(st, ain);
> + if (ret)
> + return ret;
> + is_current_chan = true;
> + ain[1] = 0;
> + } else {
> + ret = ad7173_validate_voltage_ain_inputs(st, ain);
> + if (ret)
> + return ret;
> + is_current_chan = false;
> + }
>
> ret = fwnode_property_match_property_string(child,
> "adi,reference-
> select",
> @@ -1051,17 +1288,34 @@ static int ad7173_fw_parse_channel_config(struct iio_dev
> *indio_dev)
> chan->scan_index = chan_index;
> chan->channel = ain[0];
> chan->channel2 = ain[1];
> - chan->differential = true;
> -
> - chan_st_priv->ain = AD7173_CH_ADDRESS(ain[0], ain[1]);
> chan_st_priv->chan_reg = chan_index;
> chan_st_priv->cfg.input_buf = st->info->has_input_buf;
> chan_st_priv->cfg.odr = 0;
> -
> chan_st_priv->cfg.bipolar = fwnode_property_read_bool(child,
> "bipolar");
> +
> if (chan_st_priv->cfg.bipolar)
> chan->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
>
> + ret = fwnode_property_match_property_string(child,
> + "adi,channel-type",
> + ad7173_channel_types,
> +
> ARRAY_SIZE(ad7173_channel_types));
> + chan->differential = (ret < 0 || ret == AD7173_CHAN_DIFFERENTIAL)
> + ? 1 : 0;
I don't think we should treat 'ret < 0' has a differential channel. Any reason for
it? For me, it's just an invalid property value given by the user...
- Nuno Sá
On 29/05/2024 15:46, Nuno Sá wrote:
> On Mon, 2024-05-27 at 20:02 +0300, Dumitru Ceclan via B4 Relay wrote:
>> From: Dumitru Ceclan <dumitru.ceclan@analog.com>
...
>> static const struct ad7173_device_info ad7173_device_info[] = {
>> + [ID_AD4111] = {
>> + .name = "ad4111",
>> + .id = AD7173_AD4111_AD4112_AD4114_ID,
>> + .num_voltage_inputs_with_divider = 8,
>> + .num_channels = 16,
>> + .num_configs = 8,
>> + .num_voltage_inputs = 8,
>> + .num_gpios = 2,
>> + .higher_gpio_bits = true,
>> + .has_temp = true,
>> + .has_vcom_input = true,
>> + .has_input_buf = true,
>> + .has_current_inputs = true,
>> + .has_int_ref = true,
>> + .clock = 2 * HZ_PER_MHZ,
>> + .sinc5_data_rates = ad7173_sinc5_data_rates,
>> + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
>> + },
>
> At some point it would be nice to drop the ad7173_device_info array...
>
What are good alternatives to this?
...
>> + ret = fwnode_property_match_property_string(child,
>> + "adi,channel-type",
>> + ad7173_channel_types,
>> +
>> ARRAY_SIZE(ad7173_channel_types));
>> + chan->differential = (ret < 0 || ret == AD7173_CHAN_DIFFERENTIAL)
>> + ? 1 : 0;
>
> I don't think we should treat 'ret < 0' has a differential channel. Any reason for
> it? For me, it's just an invalid property value given by the user...
>
Yes, as that would be the default value if it's missing or invalid
> - Nuno Sá
>
On 5/29/24 9:03 AM, Ceclan, Dumitru wrote:
> On 29/05/2024 15:46, Nuno Sá wrote:
>> On Mon, 2024-05-27 at 20:02 +0300, Dumitru Ceclan via B4 Relay wrote:
>>> From: Dumitru Ceclan <dumitru.ceclan@analog.com>
>
> ...
>
>>> static const struct ad7173_device_info ad7173_device_info[] = {
>>> + [ID_AD4111] = {
>>> + .name = "ad4111",
>>> + .id = AD7173_AD4111_AD4112_AD4114_ID,
>>> + .num_voltage_inputs_with_divider = 8,
>>> + .num_channels = 16,
>>> + .num_configs = 8,
>>> + .num_voltage_inputs = 8,
>>> + .num_gpios = 2,
>>> + .higher_gpio_bits = true,
>>> + .has_temp = true,
>>> + .has_vcom_input = true,
>>> + .has_input_buf = true,
>>> + .has_current_inputs = true,
>>> + .has_int_ref = true,
>>> + .clock = 2 * HZ_PER_MHZ,
>>> + .sinc5_data_rates = ad7173_sinc5_data_rates,
>>> + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
>>> + },
>>
>> At some point it would be nice to drop the ad7173_device_info array...
>>
> What are good alternatives to this?
Drivers like ad7091r8 have individual static struct ad7091r_init_info
instead of putting them all in an array. I like doing it that
way because it makes less code to read compared to having the
array.
It would let us get rid of enum ad7173_ids, have one level less
indent on each static const struct ad7173_device_info and
{ .compatible = "adi,ad7172-2", .data = &ad7173_device_info },
would now fit on one line since we no longer need the array
index.
On Wed, 2024-05-29 at 15:59 -0500, David Lechner wrote:
> On 5/29/24 9:03 AM, Ceclan, Dumitru wrote:
> > On 29/05/2024 15:46, Nuno Sá wrote:
> > > On Mon, 2024-05-27 at 20:02 +0300, Dumitru Ceclan via B4 Relay wrote:
> > > > From: Dumitru Ceclan <dumitru.ceclan@analog.com>
> >
> > ...
> >
> > > > static const struct ad7173_device_info ad7173_device_info[] = {
> > > > + [ID_AD4111] = {
> > > > + .name = "ad4111",
> > > > + .id = AD7173_AD4111_AD4112_AD4114_ID,
> > > > + .num_voltage_inputs_with_divider = 8,
> > > > + .num_channels = 16,
> > > > + .num_configs = 8,
> > > > + .num_voltage_inputs = 8,
> > > > + .num_gpios = 2,
> > > > + .higher_gpio_bits = true,
> > > > + .has_temp = true,
> > > > + .has_vcom_input = true,
> > > > + .has_input_buf = true,
> > > > + .has_current_inputs = true,
> > > > + .has_int_ref = true,
> > > > + .clock = 2 * HZ_PER_MHZ,
> > > > + .sinc5_data_rates = ad7173_sinc5_data_rates,
> > > > + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
> > > > + },
> > >
> > > At some point it would be nice to drop the ad7173_device_info array...
> > >
> > What are good alternatives to this?
>
> Drivers like ad7091r8 have individual static struct ad7091r_init_info
> instead of putting them all in an array. I like doing it that
> way because it makes less code to read compared to having the
> array.
>
> It would let us get rid of enum ad7173_ids, have one level less
> indent on each static const struct ad7173_device_info and
>
> { .compatible = "adi,ad7172-2", .data = &ad7173_device_info },
>
> would now fit on one line since we no longer need the array
> index.
>
Exactly... But up to you to do it now or defer it to a later patch.
- Nuno Sá
On Thu, 30 May 2024 08:19:57 +0200
Nuno Sá <noname.nuno@gmail.com> wrote:
> On Wed, 2024-05-29 at 15:59 -0500, David Lechner wrote:
> > On 5/29/24 9:03 AM, Ceclan, Dumitru wrote:
> > > On 29/05/2024 15:46, Nuno Sá wrote:
> > > > On Mon, 2024-05-27 at 20:02 +0300, Dumitru Ceclan via B4 Relay wrote:
> > > > > From: Dumitru Ceclan <dumitru.ceclan@analog.com>
> > >
> > > ...
> > >
> > > > > static const struct ad7173_device_info ad7173_device_info[] = {
> > > > > + [ID_AD4111] = {
> > > > > + .name = "ad4111",
> > > > > + .id = AD7173_AD4111_AD4112_AD4114_ID,
> > > > > + .num_voltage_inputs_with_divider = 8,
> > > > > + .num_channels = 16,
> > > > > + .num_configs = 8,
> > > > > + .num_voltage_inputs = 8,
> > > > > + .num_gpios = 2,
> > > > > + .higher_gpio_bits = true,
> > > > > + .has_temp = true,
> > > > > + .has_vcom_input = true,
> > > > > + .has_input_buf = true,
> > > > > + .has_current_inputs = true,
> > > > > + .has_int_ref = true,
> > > > > + .clock = 2 * HZ_PER_MHZ,
> > > > > + .sinc5_data_rates = ad7173_sinc5_data_rates,
> > > > > + .num_sinc5_data_rates = ARRAY_SIZE(ad7173_sinc5_data_rates),
> > > > > + },
> > > >
> > > > At some point it would be nice to drop the ad7173_device_info array...
> > > >
> > > What are good alternatives to this?
> >
> > Drivers like ad7091r8 have individual static struct ad7091r_init_info
> > instead of putting them all in an array. I like doing it that
> > way because it makes less code to read compared to having the
> > array.
> >
> > It would let us get rid of enum ad7173_ids, have one level less
> > indent on each static const struct ad7173_device_info and
> >
> > { .compatible = "adi,ad7172-2", .data = &ad7173_device_info },
> >
> > would now fit on one line since we no longer need the array
> > index.
> >
>
> Exactly... But up to you to do it now or defer it to a later patch.
>
Agreed - the array pattern for this is not a good idea as it
also encourages people to assign meaning to the enum values leaving
stuff expressed as code that should be a flag or value inside these
device_info structures.
Some of those where mistakes of a younger me ;(
Jonathan
> - Nuno Sá
© 2016 - 2026 Red Hat, Inc.