Re: [PATCH v3 5/6] iio: adc: ad7173: Add support for AD411x devices

From: Nuno Sá
Date: Wed May 29 2024 - 08:46:30 EST


On Mon, 2024-05-27 at 20:02 +0300, Dumitru Ceclan via B4 Relay wrote:
> From: Dumitru Ceclan <dumitru.ceclan@xxxxxxxxxx>
>
> 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@xxxxxxxxxx>
> ---
>  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á