]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
authorMarcelo Schmitt <marcelo.schmitt@analog.com>
Mon, 23 Feb 2026 17:10:23 +0000 (14:10 -0300)
committerJonathan Cameron <Jonathan.Cameron@huawei.com>
Sun, 1 Mar 2026 12:04:44 +0000 (12:04 +0000)
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>
Signed-off-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
drivers/iio/adc/ad4030.c

index 42b8cd88738278c8530ec1e7d5edea6f863ad6d8..9c5f19321e3b888189345760deb56ef69bce0ee8 100644 (file)
@@ -48,6 +48,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
 /* 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_PGA_GAIN_MAX_NANO     (NANO * 2 / 3)
+
 enum ad4030_out_mode {
        AD4030_OUT_DATA_MD_DIFF,
        AD4030_OUT_DATA_MD_16_DIFF_8_COM,
@@ -145,6 +151,23 @@ enum {
        AD4030_SCAN_TYPE_AVG,
 };
 
+/*
+ * Gains computed as fractions of 1000 so they can be expressed by integers.
+ */
+static const int adaq4216_hw_gains_vpv[] = {
+       1 * MILLI / 3,          /* 0.333 */
+       5 * MILLI / 9,          /* 0.555 */
+       20 * MILLI / 9,         /* 0.2222 */
+       20 * MILLI / 3,         /* 0.6666 */
+};
+
+static const int adaq4216_hw_gains_frac[][2] = {
+       { 1, 3 },  /* 1/3 V/V gain */
+       { 5, 9 },  /* 5/9 V/V gain */
+       { 20, 9 }, /* 20/9 V/V gain */
+       { 20, 3 }, /* 20/3 V/V gain */
+};
+
 struct ad4030_chip_info {
        const char *name;
        const unsigned long *available_masks;
@@ -152,6 +175,7 @@ struct ad4030_chip_info {
        const struct iio_chan_spec offload_channels[AD4030_MAX_IIO_CHANNEL_NB];
        u8 grade;
        u8 precision_bits;
+       bool has_pga;
        /* Number of hardware channels */
        int num_voltage_inputs;
        unsigned int tcyc_ns;
@@ -175,7 +199,11 @@ struct ad4030_state {
        struct spi_offload_trigger *offload_trigger;
        struct spi_offload_trigger_config offload_trigger_config;
        struct pwm_device *cnv_trigger;
+       size_t scale_avail_size;
        struct pwm_waveform cnv_wf;
+       unsigned int scale_avail[ARRAY_SIZE(adaq4216_hw_gains_vpv)][2];
+       struct gpio_descs *pga_gpios;
+       unsigned int pga_index;
 
        /*
         * DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -232,7 +260,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 =                                      \
                (_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) |         \
                BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),                  \
@@ -243,6 +271,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,                                                   \
@@ -257,10 +286,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)
 
 /*
  * AD4030 can average over 2^N samples, where N = 1, 2, 3, ..., 16.
@@ -418,6 +453,65 @@ 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, int_part, fract_part;
+       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 (unsigned int i = 0; i < ARRAY_SIZE(adaq4216_hw_gains_frac); i++) {
+               range = mult_frac(st->vref_uv, adaq4216_hw_gains_frac[i][1],
+                                 adaq4216_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.
+                */
+               int_part = div_u64_rem((range * MICRO) >> mag_bits, NANO, &fract_part);
+               st->scale_avail[i][0] = int_part;
+               st->scale_avail[i][1] = fract_part;
+       }
+}
+
+static int ad4030_set_pga_gain(struct ad4030_state *st)
+{
+       DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { };
+
+       bitmap_write(bitmap, st->pga_index, 0, ADAQ4616_PGA_PINS);
+
+       return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap);
+}
+
+static int ad4030_set_pga(struct iio_dev *indio_dev, int gain_int, int gain_fract)
+{
+       struct ad4030_state *st = iio_priv(indio_dev);
+       unsigned int mag_bits = st->chip->precision_bits - 1;
+       unsigned int tmp;
+       u64 gain_nano;
+
+       if (!st->pga_gpios)
+               return -EINVAL;
+
+       gain_nano = gain_int * NANO + gain_fract;
+       if (!in_range(gain_nano, 1, ADAQ4616_PGA_GAIN_MAX_NANO))
+               return -EINVAL;
+
+       tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
+       gain_nano = DIV_ROUND_CLOSEST(st->vref_uv, tmp);
+       st->pga_index = find_closest(gain_nano, adaq4216_hw_gains_vpv,
+                                    ARRAY_SIZE(adaq4216_hw_gains_vpv));
+
+       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,
@@ -430,6 +524,13 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
        if (IS_ERR(scan_type))
                return PTR_ERR(scan_type);
 
+       /* The LSB of the 8-bit common-mode data is always vref/256. */
+       if (st->chip->has_pga && scan_type->realbits != 8) {
+               *val = st->scale_avail[st->pga_index][0];
+               *val2 = st->scale_avail[st->pga_index][1];
+               return IIO_VAL_INT_PLUS_NANO;
+       }
+
        if (chan->differential)
                *val = (st->vref_uv * 2) / MILLI;
        else
@@ -898,6 +999,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->scale_avail_size == 1)
+                       *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;
        }
@@ -970,6 +1080,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, val, val2);
+
        default:
                return -EINVAL;
        }
@@ -991,6 +1104,17 @@ static int ad4030_write_raw(struct iio_dev *indio_dev,
        return ret;
 }
 
+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;
+       }
+}
+
 static int ad4030_reg_access(struct iio_dev *indio_dev, unsigned int reg,
                             unsigned int writeval, unsigned int *readval)
 {
@@ -1037,6 +1161,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,
@@ -1278,6 +1403,26 @@ 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)
+{
+       /* 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 != ADAQ4616_PGA_PINS)
+               return dev_err_probe(dev, -EINVAL,
+                                    "Expected %d GPIOs for PGA control.\n",
+                                    ADAQ4616_PGA_PINS);
+
+       st->scale_avail_size = ARRAY_SIZE(adaq4216_hw_gains_vpv);
+       st->pga_index = 0;
+
+       return 0;
+}
+
 static int ad4030_probe(struct spi_device *spi)
 {
        struct device *dev = &spi->dev;
@@ -1320,6 +1465,14 @@ static int ad4030_probe(struct spi_device *spi)
        if (ret)
                return ret;
 
+       if (st->chip->has_pga) {
+               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;
@@ -1571,12 +1724,52 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
        .max_sample_rate_hz = 500 * HZ_PER_KHZ,
 };
 
+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_offload_scan_types),
+       },
+       .grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE,
+       .precision_bits = 16,
+       .has_pga = true,
+       .num_voltage_inputs = 1,
+       .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+       .max_sample_rate_hz = 2 * HZ_PER_MHZ,
+};
+
+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_offload_scan_types),
+       },
+       .grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE,
+       .precision_bits = 24,
+       .has_pga = true,
+       .num_voltage_inputs = 1,
+       .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+       .max_sample_rate_hz = 2 * HZ_PER_MHZ,
+};
+
 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);
@@ -1587,6 +1780,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);