ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
that scales the analog input signal prior to it reaching the ADC. The PGA
is controlled through a pair of pins (A0 and A1) whose state define the
gain that is applied to the input signal.
Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
through the IIO device channel scale available interface and enable control
of the PGA through the channel scale interface.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
drivers/iio/adc/ad4030.c | 239 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 235 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 37ba00097efe..32157b3a0420 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -21,6 +21,7 @@
#include <linux/iio/trigger_consumer.h>
#include <linux/iio/triggered_buffer.h>
#include <linux/log2.h>
+#include <linux/minmax.h>
#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
@@ -42,6 +43,8 @@
#define AD4030_REG_CHIP_GRADE_AD4630_24_GRADE 0x00
#define AD4030_REG_CHIP_GRADE_AD4632_16_GRADE 0x05
#define AD4030_REG_CHIP_GRADE_AD4632_24_GRADE 0x02
+#define AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE 0x1E
+#define AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE 0x1C
#define AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE GENMASK(7, 3)
#define AD4030_REG_SCRATCH_PAD 0x0A
#define AD4030_REG_SPI_REVISION 0x0B
@@ -121,6 +124,10 @@
/* Datasheet says 9.8ns, so use the closest integer value */
#define AD4030_TQUIET_CNV_DELAY_NS 10
+/* HARDWARE_GAIN */
+#define ADAQ4616_PGA_PINS 2
+#define ADAQ4616_GAIN_MAX_NANO 6666666667
+
enum ad4030_out_mode {
AD4030_OUT_DATA_MD_DIFF,
AD4030_OUT_DATA_MD_16_DIFF_8_COM,
@@ -149,6 +156,20 @@ enum {
AD4030_OFFLOAD_SCAN_TYPE_AVG,
};
+/*
+ * Gains computed as fractions of 1000 so they can be expressed by integers.
+ */
+static const int ad4030_hw_gains[] = {
+ 333, 556, 2222, 6667,
+};
+
+static const int ad4030_hw_gains_frac[4][2] = {
+ { 1, 3 }, /* 1/3 gain */
+ { 5, 9 }, /* 5/9 gain */
+ { 20, 9 }, /* 20/9 gain */
+ { 20, 3 }, /* 20/3 gain */
+};
+
struct ad4030_chip_info {
const char *name;
const unsigned long *available_masks;
@@ -160,6 +181,7 @@ struct ad4030_chip_info {
int num_voltage_inputs;
unsigned int tcyc_ns;
unsigned int max_sample_rate_hz;
+ unsigned int num_pga_pins;
};
struct ad4030_state {
@@ -183,6 +205,10 @@ struct ad4030_state {
struct spi_offload *offload;
struct spi_offload_trigger *offload_trigger;
struct spi_offload_trigger_config offload_trigger_config;
+ struct gpio_descs *pga_gpios;
+ int pga_index;
+ unsigned int scale_avail[ARRAY_SIZE(ad4030_hw_gains)][2];
+ size_t scale_avail_size;
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -239,7 +265,7 @@ struct ad4030_state {
* - voltage0-voltage1
* - voltage2-voltage3
*/
-#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload) { \
+#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload, _pga) { \
.info_mask_shared_by_all = \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_shared_by_all_available = \
@@ -250,6 +276,7 @@ struct ad4030_state {
BIT(IIO_CHAN_INFO_CALIBBIAS) | \
BIT(IIO_CHAN_INFO_RAW), \
.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
+ (_pga ? BIT(IIO_CHAN_INFO_SCALE) : 0) | \
BIT(IIO_CHAN_INFO_CALIBSCALE), \
.type = IIO_VOLTAGE, \
.indexed = 1, \
@@ -264,10 +291,16 @@ struct ad4030_state {
}
#define AD4030_CHAN_DIFF(_idx, _scan_type) \
- __AD4030_CHAN_DIFF(_idx, _scan_type, 0)
+ __AD4030_CHAN_DIFF(_idx, _scan_type, 0, 0)
#define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type) \
- __AD4030_CHAN_DIFF(_idx, _scan_type, 1)
+ __AD4030_CHAN_DIFF(_idx, _scan_type, 1, 0)
+
+#define ADAQ4216_CHAN_DIFF(_idx, _scan_type) \
+ __AD4030_CHAN_DIFF(_idx, _scan_type, 0, 1)
+
+#define ADAQ4216_OFFLOAD_CHAN_DIFF(_idx, _scan_type) \
+ __AD4030_CHAN_DIFF(_idx, _scan_type, 1, 1)
static const int ad4030_rx_bus_width[] = {
1, 2, 4, 8,
@@ -429,6 +462,74 @@ static const struct regmap_config ad4030_regmap_config = {
.max_register = AD4030_REG_DIG_ERR,
};
+static void ad4030_fill_scale_avail(struct ad4030_state *st)
+{
+ unsigned int mag_bits, tmp0, tmp1, i;
+ u64 range;
+
+ /*
+ * The maximum precision of differential channels is retrieved from the
+ * chip properties. The output code of differential channels is in two's
+ * complement format (i.e. signed), so the MSB is the sign bit and only
+ * (precision_bits - 1) bits express voltage magnitude.
+ */
+ mag_bits = st->chip->precision_bits - 1;
+
+ for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
+ range = mult_frac(st->vref_uv, ad4030_hw_gains_frac[i][1],
+ ad4030_hw_gains_frac[i][0]);
+ /*
+ * If range were in mV, we would multiply it by NANO below.
+ * Though, range is in µV so multiply it by MICRO only so the
+ * result after right shift and division scales output codes to
+ * millivolts.
+ */
+ tmp0 = div_u64_rem(((u64)range * MICRO) >> mag_bits, NANO, &tmp1);
+ st->scale_avail[i][0] = tmp0; /* Integer part */
+ st->scale_avail[i][1] = tmp1; /* Fractional part */
+ }
+}
+
+static int ad4030_set_pga_gain(struct ad4030_state *st)
+{
+ DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { };
+
+ bitmap_write(bitmap, st->pga_index, 0, 2);
+
+ return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap);
+}
+
+static int ad4030_set_pga(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int gain_int,
+ int gain_fract)
+{
+ struct ad4030_state *st = iio_priv(indio_dev);
+ const struct iio_scan_type *scan_type;
+ unsigned int mag_bits;
+ u64 gain_nano, tmp;
+
+ if (!st->pga_gpios)
+ return -EINVAL;
+
+ scan_type = iio_get_current_scan_type(indio_dev, chan);
+ if (scan_type->sign == 's')
+ mag_bits = st->chip->precision_bits - 1;
+ else
+ mag_bits = st->chip->precision_bits;
+
+ gain_nano = gain_int * NANO + gain_fract;
+
+ if (!in_range(gain_nano, 0, ADAQ4616_GAIN_MAX_NANO))
+ return -EINVAL;
+
+ tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
+ gain_nano = DIV_ROUND_CLOSEST_ULL(st->vref_uv, tmp);
+ st->pga_index = find_closest(gain_nano, ad4030_hw_gains,
+ ARRAY_SIZE(ad4030_hw_gains));
+
+ return ad4030_set_pga_gain(st);
+}
+
static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val,
@@ -455,7 +556,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
*val2 = scan_type->realbits == 30 ? st->chip->precision_bits
: scan_type->realbits;
- return IIO_VAL_FRACTIONAL_LOG2;
+ /* The LSB of the 8-bit common-mode data is always vref/256. */
+ if (scan_type->realbits == 8 || !st->chip->num_pga_pins)
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ *val = st->scale_avail[st->pga_index][0];
+ *val2 = st->scale_avail[st->pga_index][1];
+
+ return IIO_VAL_INT_PLUS_NANO;
}
static int ad4030_get_chan_calibscale(struct iio_dev *indio_dev,
@@ -654,6 +762,19 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev,
st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
}
+static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_INT_PLUS_NANO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ return -EINVAL;
+}
+
static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
{
struct ad4030_state *st = iio_priv(dev);
@@ -891,6 +1012,15 @@ static int ad4030_read_avail(struct iio_dev *indio_dev,
*length = ARRAY_SIZE(ad4030_average_modes);
return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_SCALE:
+ if (!st->pga_gpios)
+ *vals = (int *)st->scale_avail[st->pga_index];
+ else
+ *vals = (int *)st->scale_avail;
+ *length = st->scale_avail_size * 2; /* print int and nano part */
+ *type = IIO_VAL_INT_PLUS_NANO;
+ return IIO_AVAIL_LIST;
+
default:
return -EINVAL;
}
@@ -966,6 +1096,9 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_SAMP_FREQ:
return ad4030_set_sampling_freq(indio_dev, val);
+ case IIO_CHAN_INFO_SCALE:
+ return ad4030_set_pga(indio_dev, chan, val, val2);
+
default:
return -EINVAL;
}
@@ -1037,6 +1170,7 @@ static const struct iio_info ad4030_iio_info = {
.read_avail = ad4030_read_avail,
.read_raw = ad4030_read_raw,
.write_raw = ad4030_write_raw,
+ .write_raw_get_fmt = &ad4030_write_raw_get_fmt,
.debugfs_reg_access = ad4030_reg_access,
.read_label = ad4030_read_label,
.get_current_scan_type = ad4030_get_current_scan_type,
@@ -1318,6 +1452,51 @@ static int ad4030_spi_offload_setup(struct iio_dev *indio_dev,
IIO_BUFFER_DIRECTION_IN);
}
+static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
+ struct ad4030_state *st)
+{
+ unsigned int i;
+ int pga_value;
+ int ret;
+
+ ret = device_property_read_u32(dev, "adi,pga-value", &pga_value);
+ if (ret && ret != -EINVAL)
+ return dev_err_probe(dev, ret, "Failed to get PGA value.\n");
+
+ if (ret == -EINVAL) {
+ /* Setup GPIOs for PGA control */
+ st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW);
+ if (IS_ERR(st->pga_gpios))
+ return dev_err_probe(dev, PTR_ERR(st->pga_gpios),
+ "Failed to get PGA gpios.\n");
+
+ if (st->pga_gpios->ndescs != 2)
+ return dev_err_probe(dev, -EINVAL,
+ "Expected 2 GPIOs for PGA control.\n");
+
+ st->scale_avail_size = ARRAY_SIZE(ad4030_hw_gains);
+ st->pga_index = 0;
+ return ad4030_set_pga_gain(st);
+ }
+
+ /* Set ADC driver to handle pin-strapped PGA pins setup */
+ for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
+ if (pga_value != ad4030_hw_gains[i])
+ continue;
+
+ st->pga_index = i;
+ break;
+ }
+ if (i == ARRAY_SIZE(ad4030_hw_gains))
+ return dev_err_probe(dev, -EINVAL, "Invalid PGA value: %d.\n",
+ pga_value);
+
+ st->scale_avail_size = 1;
+ st->pga_gpios = NULL;
+
+ return 0;
+}
+
static int ad4030_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
@@ -1360,6 +1539,14 @@ static int ad4030_probe(struct spi_device *spi)
if (ret)
return ret;
+ if (st->chip->num_pga_pins > 0) {
+ ret = ad4030_setup_pga(dev, indio_dev, st);
+ if (ret)
+ return ret;
+
+ ad4030_fill_scale_avail(st);
+ }
+
ret = ad4030_config(st);
if (ret)
return ret;
@@ -1611,12 +1798,54 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
.max_sample_rate_hz = 500 * KILO,
};
+static const struct ad4030_chip_info adaq4216_chip_info = {
+ .name = "adaq4216",
+ .available_masks = ad4030_channel_masks,
+ .channels = {
+ ADAQ4216_CHAN_DIFF(0, ad4030_16_scan_types),
+ AD4030_CHAN_CMO(1, 0),
+ IIO_CHAN_SOFT_TIMESTAMP(2),
+ },
+ .offload_channels = {
+ ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_16_scan_types),
+ AD4030_CHAN_CMO(1, 0),
+ },
+ .grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE,
+ .precision_bits = 16,
+ .num_voltage_inputs = 1,
+ .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+ .max_sample_rate_hz = 2 * MEGA,
+ .num_pga_pins = ADAQ4616_PGA_PINS,
+};
+
+static const struct ad4030_chip_info adaq4224_chip_info = {
+ .name = "adaq4224",
+ .available_masks = ad4030_channel_masks,
+ .channels = {
+ ADAQ4216_CHAN_DIFF(0, ad4030_24_scan_types),
+ AD4030_CHAN_CMO(1, 0),
+ IIO_CHAN_SOFT_TIMESTAMP(2),
+ },
+ .offload_channels = {
+ ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
+ AD4030_CHAN_CMO(1, 0),
+ },
+ .grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE,
+ .precision_bits = 24,
+ .num_voltage_inputs = 1,
+ .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+ .max_sample_rate_hz = 2 * MEGA,
+ .num_pga_pins = ADAQ4616_PGA_PINS,
+};
+
static const struct spi_device_id ad4030_id_table[] = {
{ "ad4030-24", (kernel_ulong_t)&ad4030_24_chip_info },
{ "ad4630-16", (kernel_ulong_t)&ad4630_16_chip_info },
{ "ad4630-24", (kernel_ulong_t)&ad4630_24_chip_info },
{ "ad4632-16", (kernel_ulong_t)&ad4632_16_chip_info },
{ "ad4632-24", (kernel_ulong_t)&ad4632_24_chip_info },
+ { "adaq4216", (kernel_ulong_t)&adaq4216_chip_info },
+ { "adaq4224", (kernel_ulong_t)&adaq4224_chip_info },
{ }
};
MODULE_DEVICE_TABLE(spi, ad4030_id_table);
@@ -1627,6 +1856,8 @@ static const struct of_device_id ad4030_of_match[] = {
{ .compatible = "adi,ad4630-24", .data = &ad4630_24_chip_info },
{ .compatible = "adi,ad4632-16", .data = &ad4632_16_chip_info },
{ .compatible = "adi,ad4632-24", .data = &ad4632_24_chip_info },
+ { .compatible = "adi,adaq4216", .data = &adaq4216_chip_info },
+ { .compatible = "adi,adaq4224", .data = &adaq4224_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, ad4030_of_match);
--
2.39.2
Hi Marcelo,
kernel test robot noticed the following build warnings:
url: https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/iio-adc-ad4030-Fix-_scale-for-when-oversampling-is-enabled/20250830-084901
base: 91812d3843409c235f336f32f1c37ddc790f1e03
patch link: https://lore.kernel.org/r/006ac88a667ce0d2c751946b562af83d0f27a44f.1756511030.git.marcelo.schmitt%40analog.com
patch subject: [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
config: x86_64-randconfig-161-20250831 (https://download.01.org/0day-ci/archive/20250831/202508310754.Y4V0Iq26-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202508310754.Y4V0Iq26-lkp@intel.com/
smatch warnings:
drivers/iio/adc/ad4030.c:515 ad4030_set_pga() error: 'scan_type' dereferencing possible ERR_PTR()
vim +/scan_type +515 drivers/iio/adc/ad4030.c
8017880dd8ca3e Marcelo Schmitt 2025-08-29 502 static int ad4030_set_pga(struct iio_dev *indio_dev,
8017880dd8ca3e Marcelo Schmitt 2025-08-29 503 struct iio_chan_spec const *chan, int gain_int,
8017880dd8ca3e Marcelo Schmitt 2025-08-29 504 int gain_fract)
8017880dd8ca3e Marcelo Schmitt 2025-08-29 505 {
8017880dd8ca3e Marcelo Schmitt 2025-08-29 506 struct ad4030_state *st = iio_priv(indio_dev);
8017880dd8ca3e Marcelo Schmitt 2025-08-29 507 const struct iio_scan_type *scan_type;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 508 unsigned int mag_bits;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 509 u64 gain_nano, tmp;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 510
8017880dd8ca3e Marcelo Schmitt 2025-08-29 511 if (!st->pga_gpios)
8017880dd8ca3e Marcelo Schmitt 2025-08-29 512 return -EINVAL;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 513
8017880dd8ca3e Marcelo Schmitt 2025-08-29 514 scan_type = iio_get_current_scan_type(indio_dev, chan);
if (IS_ERR(scan_type))
return PTR_ERR(scan_type);
8017880dd8ca3e Marcelo Schmitt 2025-08-29 @515 if (scan_type->sign == 's')
8017880dd8ca3e Marcelo Schmitt 2025-08-29 516 mag_bits = st->chip->precision_bits - 1;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 517 else
8017880dd8ca3e Marcelo Schmitt 2025-08-29 518 mag_bits = st->chip->precision_bits;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 519
8017880dd8ca3e Marcelo Schmitt 2025-08-29 520 gain_nano = gain_int * NANO + gain_fract;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 521
8017880dd8ca3e Marcelo Schmitt 2025-08-29 522 if (!in_range(gain_nano, 0, ADAQ4616_GAIN_MAX_NANO))
8017880dd8ca3e Marcelo Schmitt 2025-08-29 523 return -EINVAL;
8017880dd8ca3e Marcelo Schmitt 2025-08-29 524
8017880dd8ca3e Marcelo Schmitt 2025-08-29 525 tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
8017880dd8ca3e Marcelo Schmitt 2025-08-29 526 gain_nano = DIV_ROUND_CLOSEST_ULL(st->vref_uv, tmp);
8017880dd8ca3e Marcelo Schmitt 2025-08-29 527 st->pga_index = find_closest(gain_nano, ad4030_hw_gains,
8017880dd8ca3e Marcelo Schmitt 2025-08-29 528 ARRAY_SIZE(ad4030_hw_gains));
8017880dd8ca3e Marcelo Schmitt 2025-08-29 529
8017880dd8ca3e Marcelo Schmitt 2025-08-29 530 return ad4030_set_pga_gain(st);
8017880dd8ca3e Marcelo Schmitt 2025-08-29 531 }
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
On 8/29/25 7:45 PM, Marcelo Schmitt wrote:
> ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
> that scales the analog input signal prior to it reaching the ADC. The PGA
> is controlled through a pair of pins (A0 and A1) whose state define the
> gain that is applied to the input signal.
>
> Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
> through the IIO device channel scale available interface and enable control
> of the PGA through the channel scale interface.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> drivers/iio/adc/ad4030.c | 239 ++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 235 insertions(+), 4 deletions(-)
>
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index 37ba00097efe..32157b3a0420 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -21,6 +21,7 @@
> #include <linux/iio/trigger_consumer.h>
> #include <linux/iio/triggered_buffer.h>
> #include <linux/log2.h>
> +#include <linux/minmax.h>
> #include <linux/pwm.h>
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> @@ -42,6 +43,8 @@
> #define AD4030_REG_CHIP_GRADE_AD4630_24_GRADE 0x00
> #define AD4030_REG_CHIP_GRADE_AD4632_16_GRADE 0x05
> #define AD4030_REG_CHIP_GRADE_AD4632_24_GRADE 0x02
> +#define AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE 0x1E
> +#define AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE 0x1C
> #define AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE GENMASK(7, 3)
> #define AD4030_REG_SCRATCH_PAD 0x0A
> #define AD4030_REG_SPI_REVISION 0x0B
> @@ -121,6 +124,10 @@
> /* Datasheet says 9.8ns, so use the closest integer value */
> #define AD4030_TQUIET_CNV_DELAY_NS 10
>
> +/* HARDWARE_GAIN */
> +#define ADAQ4616_PGA_PINS 2
> +#define ADAQ4616_GAIN_MAX_NANO 6666666667
> +
> enum ad4030_out_mode {
> AD4030_OUT_DATA_MD_DIFF,
> AD4030_OUT_DATA_MD_16_DIFF_8_COM,
> @@ -149,6 +156,20 @@ enum {
> AD4030_OFFLOAD_SCAN_TYPE_AVG,
> };
>
> +/*
> + * Gains computed as fractions of 1000 so they can be expressed by integers.
> + */
> +static const int ad4030_hw_gains[] = {
> + 333, 556, 2222, 6667,
> +};
> +
> +static const int ad4030_hw_gains_frac[4][2] = {
> + { 1, 3 }, /* 1/3 gain */
> + { 5, 9 }, /* 5/9 gain */
> + { 20, 9 }, /* 20/9 gain */
> + { 20, 3 }, /* 20/3 gain */
> +};
> +
> struct ad4030_chip_info {
> const char *name;
> const unsigned long *available_masks;
> @@ -160,6 +181,7 @@ struct ad4030_chip_info {
> int num_voltage_inputs;
> unsigned int tcyc_ns;
> unsigned int max_sample_rate_hz;
> + unsigned int num_pga_pins;
This is only usesd for boolean checks, so perhaps:
bool has_pga;
> };
>
> struct ad4030_state {
> @@ -183,6 +205,10 @@ struct ad4030_state {
> struct spi_offload *offload;
> struct spi_offload_trigger *offload_trigger;
> struct spi_offload_trigger_config offload_trigger_config;
> + struct gpio_descs *pga_gpios;
> + int pga_index;
> + unsigned int scale_avail[ARRAY_SIZE(ad4030_hw_gains)][2];
> + size_t scale_avail_size;
>
> /*
> * DMA (thus cache coherency maintenance) requires the transfer buffers
> @@ -239,7 +265,7 @@ struct ad4030_state {
> * - voltage0-voltage1
> * - voltage2-voltage3
> */
> -#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload) { \
> +#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload, _pga) { \
> .info_mask_shared_by_all = \
> BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> .info_mask_shared_by_all_available = \
> @@ -250,6 +276,7 @@ struct ad4030_state {
> BIT(IIO_CHAN_INFO_CALIBBIAS) | \
> BIT(IIO_CHAN_INFO_RAW), \
> .info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) | \
> + (_pga ? BIT(IIO_CHAN_INFO_SCALE) : 0) | \
> BIT(IIO_CHAN_INFO_CALIBSCALE), \
> .type = IIO_VOLTAGE, \
> .indexed = 1, \
> @@ -264,10 +291,16 @@ struct ad4030_state {
> }
>
> #define AD4030_CHAN_DIFF(_idx, _scan_type) \
> - __AD4030_CHAN_DIFF(_idx, _scan_type, 0)
> + __AD4030_CHAN_DIFF(_idx, _scan_type, 0, 0)
>
> #define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type) \
> - __AD4030_CHAN_DIFF(_idx, _scan_type, 1)
> + __AD4030_CHAN_DIFF(_idx, _scan_type, 1, 0)
> +
> +#define ADAQ4216_CHAN_DIFF(_idx, _scan_type) \
> + __AD4030_CHAN_DIFF(_idx, _scan_type, 0, 1)
> +
> +#define ADAQ4216_OFFLOAD_CHAN_DIFF(_idx, _scan_type) \
> + __AD4030_CHAN_DIFF(_idx, _scan_type, 1, 1)
>
> static const int ad4030_rx_bus_width[] = {
> 1, 2, 4, 8,
> @@ -429,6 +462,74 @@ static const struct regmap_config ad4030_regmap_config = {
> .max_register = AD4030_REG_DIG_ERR,
> };
>
> +static void ad4030_fill_scale_avail(struct ad4030_state *st)
> +{
> + unsigned int mag_bits, tmp0, tmp1, i;
> + u64 range;
> +
> + /*
> + * The maximum precision of differential channels is retrieved from the
> + * chip properties. The output code of differential channels is in two's
> + * complement format (i.e. signed), so the MSB is the sign bit and only
> + * (precision_bits - 1) bits express voltage magnitude.
> + */
> + mag_bits = st->chip->precision_bits - 1;
Seems odd that function below checks for if (scan_type->sign == 's') but this
doesn't.
> +
> + for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
> + range = mult_frac(st->vref_uv, ad4030_hw_gains_frac[i][1],
> + ad4030_hw_gains_frac[i][0]);
> + /*
> + * If range were in mV, we would multiply it by NANO below.
> + * Though, range is in µV so multiply it by MICRO only so the
> + * result after right shift and division scales output codes to
> + * millivolts.
> + */
> + tmp0 = div_u64_rem(((u64)range * MICRO) >> mag_bits, NANO, &tmp1);
> + st->scale_avail[i][0] = tmp0; /* Integer part */
> + st->scale_avail[i][1] = tmp1; /* Fractional part */
Could just give the variables meaningful names and avoid the comments.
> + }
> +}
> +
> +static int ad4030_set_pga_gain(struct ad4030_state *st)
> +{
> + DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { };
> +
> + bitmap_write(bitmap, st->pga_index, 0, 2);
Use ADAQ4616_PGA_PINS here instead of 2?
> +
> + return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap);
> +}
> +
> +static int ad4030_set_pga(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int gain_int,
> + int gain_fract)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> + const struct iio_scan_type *scan_type;
> + unsigned int mag_bits;
> + u64 gain_nano, tmp;
> +
> + if (!st->pga_gpios)
> + return -EINVAL;
> +
> + scan_type = iio_get_current_scan_type(indio_dev, chan);
Need to check for error.
> + if (scan_type->sign == 's')
> + mag_bits = st->chip->precision_bits - 1;
> + else
> + mag_bits = st->chip->precision_bits;
> +
> + gain_nano = gain_int * NANO + gain_fract;
> +
> + if (!in_range(gain_nano, 0, ADAQ4616_GAIN_MAX_NANO))
> + return -EINVAL;
> +
> + tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
> + gain_nano = DIV_ROUND_CLOSEST_ULL(st->vref_uv, tmp);
> + st->pga_index = find_closest(gain_nano, ad4030_hw_gains,
> + ARRAY_SIZE(ad4030_hw_gains));
> +
> + return ad4030_set_pga_gain(st);
> +}
> +
> static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int *val,
> @@ -455,7 +556,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
> *val2 = scan_type->realbits == 30 ? st->chip->precision_bits
> : scan_type->realbits;
>
> - return IIO_VAL_FRACTIONAL_LOG2;
> + /* The LSB of the 8-bit common-mode data is always vref/256. */
> + if (scan_type->realbits == 8 || !st->chip->num_pga_pins)
`if` statement should be earlier so that we don't set *val, *val2
twice.
> + return IIO_VAL_FRACTIONAL_LOG2;
> +
> + *val = st->scale_avail[st->pga_index][0];
> + *val2 = st->scale_avail[st->pga_index][1];
> +
> + return IIO_VAL_INT_PLUS_NANO;
> }
>
> static int ad4030_get_chan_calibscale(struct iio_dev *indio_dev,
> @@ -654,6 +762,19 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev,
> st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
> }
>
> +static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + return IIO_VAL_INT_PLUS_NANO;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +
> + return -EINVAL;
Unreachable code.
> +}
> +
> static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
> {
> struct ad4030_state *st = iio_priv(dev);
> @@ -891,6 +1012,15 @@ static int ad4030_read_avail(struct iio_dev *indio_dev,
> *length = ARRAY_SIZE(ad4030_average_modes);
> return IIO_AVAIL_LIST;
>
> + case IIO_CHAN_INFO_SCALE:
> + if (!st->pga_gpios)
if (st->scale_avail_size == 1)
would make more sense here.
> + *vals = (int *)st->scale_avail[st->pga_index];
> + else
> + *vals = (int *)st->scale_avail;
> + *length = st->scale_avail_size * 2; /* print int and nano part */
> + *type = IIO_VAL_INT_PLUS_NANO;
> + return IIO_AVAIL_LIST;
> +
> default:
> return -EINVAL;
> }
> @@ -966,6 +1096,9 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
> case IIO_CHAN_INFO_SAMP_FREQ:
> return ad4030_set_sampling_freq(indio_dev, val);
>
> + case IIO_CHAN_INFO_SCALE:
> + return ad4030_set_pga(indio_dev, chan, val, val2);
> +
> default:
> return -EINVAL;
> }
> @@ -1037,6 +1170,7 @@ static const struct iio_info ad4030_iio_info = {
> .read_avail = ad4030_read_avail,
> .read_raw = ad4030_read_raw,
> .write_raw = ad4030_write_raw,
> + .write_raw_get_fmt = &ad4030_write_raw_get_fmt,
> .debugfs_reg_access = ad4030_reg_access,
> .read_label = ad4030_read_label,
> .get_current_scan_type = ad4030_get_current_scan_type,
> @@ -1318,6 +1452,51 @@ static int ad4030_spi_offload_setup(struct iio_dev *indio_dev,
> IIO_BUFFER_DIRECTION_IN);
> }
>
> +static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
> + struct ad4030_state *st)
> +{
> + unsigned int i;
> + int pga_value;
> + int ret;
> +
> + ret = device_property_read_u32(dev, "adi,pga-value", &pga_value);
> + if (ret && ret != -EINVAL)
> + return dev_err_probe(dev, ret, "Failed to get PGA value.\n");
> +
> + if (ret == -EINVAL) {
> + /* Setup GPIOs for PGA control */
> + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW);
> + if (IS_ERR(st->pga_gpios))
> + return dev_err_probe(dev, PTR_ERR(st->pga_gpios),
> + "Failed to get PGA gpios.\n");
> +
> + if (st->pga_gpios->ndescs != 2)
s/2/ADAQ4616_PGA_PINS/?
> + return dev_err_probe(dev, -EINVAL,
> + "Expected 2 GPIOs for PGA control.\n");
> +
> + st->scale_avail_size = ARRAY_SIZE(ad4030_hw_gains);
> + st->pga_index = 0;
> + return ad4030_set_pga_gain(st);
We already intialized the array to GPIO_OUT_LOW, so isn't calling
ad4030_set_pga_gain() here with index of 0 redundant?
> + }
> +
> + /* Set ADC driver to handle pin-strapped PGA pins setup */
> + for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
> + if (pga_value != ad4030_hw_gains[i])
> + continue;
> +
> + st->pga_index = i;
> + break;
> + }
> + if (i == ARRAY_SIZE(ad4030_hw_gains))
> + return dev_err_probe(dev, -EINVAL, "Invalid PGA value: %d.\n",
> + pga_value);
> +
> + st->scale_avail_size = 1;
> + st->pga_gpios = NULL;
This seems reduandant.
> +
> + return 0;
> +}
> +
> static int ad4030_probe(struct spi_device *spi)
> {
> struct device *dev = &spi->dev;
> @@ -1360,6 +1539,14 @@ static int ad4030_probe(struct spi_device *spi)
> if (ret)
> return ret;
>
> + if (st->chip->num_pga_pins > 0) {
> + ret = ad4030_setup_pga(dev, indio_dev, st);
> + if (ret)
> + return ret;
> +
> + ad4030_fill_scale_avail(st);
> + }
> +
> ret = ad4030_config(st);
> if (ret)
> return ret;
> @@ -1611,12 +1798,54 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
> .max_sample_rate_hz = 500 * KILO,
> };
>
> +static const struct ad4030_chip_info adaq4216_chip_info = {
> + .name = "adaq4216",
> + .available_masks = ad4030_channel_masks,
> + .channels = {
> + ADAQ4216_CHAN_DIFF(0, ad4030_16_scan_types),
> + AD4030_CHAN_CMO(1, 0),
> + IIO_CHAN_SOFT_TIMESTAMP(2),
> + },
> + .offload_channels = {
> + ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_16_scan_types),
> + AD4030_CHAN_CMO(1, 0),
> + },
> + .grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE,
> + .precision_bits = 16,
> + .num_voltage_inputs = 1,
> + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 2 * MEGA,
> + .num_pga_pins = ADAQ4616_PGA_PINS,
> +};
> +
> +static const struct ad4030_chip_info adaq4224_chip_info = {
> + .name = "adaq4224",
> + .available_masks = ad4030_channel_masks,
> + .channels = {
> + ADAQ4216_CHAN_DIFF(0, ad4030_24_scan_types),
> + AD4030_CHAN_CMO(1, 0),
> + IIO_CHAN_SOFT_TIMESTAMP(2),
> + },
> + .offload_channels = {
> + ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
> + AD4030_CHAN_CMO(1, 0),
> + },
> + .grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE,
> + .precision_bits = 24,
> + .num_voltage_inputs = 1,
> + .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> + .max_sample_rate_hz = 2 * MEGA,
> + .num_pga_pins = ADAQ4616_PGA_PINS,
> +};
> +
> static const struct spi_device_id ad4030_id_table[] = {
> { "ad4030-24", (kernel_ulong_t)&ad4030_24_chip_info },
> { "ad4630-16", (kernel_ulong_t)&ad4630_16_chip_info },
> { "ad4630-24", (kernel_ulong_t)&ad4630_24_chip_info },
> { "ad4632-16", (kernel_ulong_t)&ad4632_16_chip_info },
> { "ad4632-24", (kernel_ulong_t)&ad4632_24_chip_info },
> + { "adaq4216", (kernel_ulong_t)&adaq4216_chip_info },
> + { "adaq4224", (kernel_ulong_t)&adaq4224_chip_info },
> { }
> };
> MODULE_DEVICE_TABLE(spi, ad4030_id_table);
> @@ -1627,6 +1856,8 @@ static const struct of_device_id ad4030_of_match[] = {
> { .compatible = "adi,ad4630-24", .data = &ad4630_24_chip_info },
> { .compatible = "adi,ad4632-16", .data = &ad4632_16_chip_info },
> { .compatible = "adi,ad4632-24", .data = &ad4632_24_chip_info },
> + { .compatible = "adi,adaq4216", .data = &adaq4216_chip_info },
> + { .compatible = "adi,adaq4224", .data = &adaq4224_chip_info },
> { }
> };
> MODULE_DEVICE_TABLE(of, ad4030_of_match);
On Sat, Aug 30, 2025 at 3:46 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
> that scales the analog input signal prior to it reaching the ADC. The PGA
> is controlled through a pair of pins (A0 and A1) whose state define the
> gain that is applied to the input signal.
>
> Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
> through the IIO device channel scale available interface and enable control
> of the PGA through the channel scale interface.
...
> /* Datasheet says 9.8ns, so use the closest integer value */
> #define AD4030_TQUIET_CNV_DELAY_NS 10
You already used that in one of the previous patches, can you move
there this one and use instead of magic += 10?
> +/* HARDWARE_GAIN */
> +#define ADAQ4616_PGA_PINS 2
> +#define ADAQ4616_GAIN_MAX_NANO 6666666667
Can we use calculus instead (people can't count properly after 3 :-)?
Something like this
(NANO * 2 / 3) // whoever in the above it's 20 and this puzzles me how
something with _NANO can be so big :-)
...
> +/*
> + * Gains computed as fractions of 1000 so they can be expressed by integers.
> + */
> +static const int ad4030_hw_gains[] = {
> + 333, 556, 2222, 6667,
Again, instead of comment (or in addition to) this can be written as
1000 / 3, 5000 / 9, 20000 / 9, 20000 / 3,
Let the compiler do its job.
> +};
> +
> +static const int ad4030_hw_gains_frac[4][2] = {
Drop 4
> + { 1, 3 }, /* 1/3 gain */
> + { 5, 9 }, /* 5/9 gain */
> + { 20, 9 }, /* 20/9 gain */
> + { 20, 3 }, /* 20/3 gain */
> +};
...
> +static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + return IIO_VAL_INT_PLUS_NANO;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> + return -EINVAL;
What's the point of this return?
> +}
...
> +static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
> + struct ad4030_state *st)
> +{
> + unsigned int i;
> + int pga_value;
> + int ret;
> +
> + ret = device_property_read_u32(dev, "adi,pga-value", &pga_value);
> + if (ret && ret != -EINVAL)
> + return dev_err_probe(dev, ret, "Failed to get PGA value.\n"
> +
> + if (ret == -EINVAL) {
This can be done differently, i.e. check for EINVAL first and in
'else' branch check for other ret != 0. This will deduplicate the
EINVAL check.
> + /* Setup GPIOs for PGA control */
> + st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW);
> + if (IS_ERR(st->pga_gpios))
> + return dev_err_probe(dev, PTR_ERR(st->pga_gpios),
> + "Failed to get PGA gpios.\n");
> +
> + if (st->pga_gpios->ndescs != 2)
> + return dev_err_probe(dev, -EINVAL,
> + "Expected 2 GPIOs for PGA control.\n");
> +
> + st->scale_avail_size = ARRAY_SIZE(ad4030_hw_gains);
> + st->pga_index = 0;
> + return ad4030_set_pga_gain(st);
> + }
...
> + .max_sample_rate_hz = 2 * MEGA,
HZ_PER_MHZ
...
> + .max_sample_rate_hz = 2 * MEGA,
Ditto.
--
With Best Regards,
Andy Shevchenko
Hi Andy, thank you for your review. On 08/30, Andy Shevchenko wrote: > On Sat, Aug 30, 2025 at 3:46 AM Marcelo Schmitt > <marcelo.schmitt@analog.com> wrote: > > > > ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry > > that scales the analog input signal prior to it reaching the ADC. The PGA > > is controlled through a pair of pins (A0 and A1) whose state define the > > gain that is applied to the input signal. > > > > Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options > > through the IIO device channel scale available interface and enable control > > of the PGA through the channel scale interface. > ... > > > +/* HARDWARE_GAIN */ > > +#define ADAQ4616_PGA_PINS 2 > > +#define ADAQ4616_GAIN_MAX_NANO 6666666667 > > Can we use calculus instead (people can't count properly after 3 :-)? > Something like this > > (NANO * 2 / 3) // whoever in the above it's 20 and this puzzles me how > something with _NANO can be so big :-) > Yeah, the max gain could be expressed in MILLI, but maybe I'll just do 20000 / 9 (or equivalent) as you suggested below and drop ADAQ4616_GAIN_MAX_NANO. I'll also comply with your suggestions to this and other patches. Thanks, Marcelo
© 2016 - 2026 Red Hat, Inc.