[PATCH v4 5/5] iio: adc: ad4080: add support for AD4880 dual-channel ADC

From: Antoniu Miclaus

Date: Mon Feb 23 2026 - 11:27:29 EST


Add support for the AD4880, a dual-channel 20-bit 40MSPS SAR ADC with
integrated fully differential amplifiers (FDA).

The AD4880 has two independent ADC channels, each with its own SPI
configuration interface. The driver uses spi_new_ancillary_device() to
create an additional SPI device for the second channel, allowing both
channels to share the same SPI bus with different chip selects.

Reviewed-by: David Lechner <dlechner@xxxxxxxxxxxx>
Reviewed-by: Nuno Sá <nuno.sa@xxxxxxxxxx>
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@xxxxxxxxxx>
---
Changes in v4:
- Use spi_get_chipselect() instead of device_property_read_u32_array()
for ancillary device chip select lookup
- Add Reviewed-by from Nuno Sá

drivers/iio/adc/ad4080.c | 243 ++++++++++++++++++++++++++++++---------
1 file changed, 191 insertions(+), 52 deletions(-)

diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
index 7cf3b6ed7940..2f52ad7b4be0 100644
--- a/drivers/iio/adc/ad4080.c
+++ b/drivers/iio/adc/ad4080.c
@@ -16,6 +16,7 @@
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
@@ -131,6 +132,9 @@
#define AD4084_CHIP_ID 0x0054
#define AD4086_CHIP_ID 0x0056
#define AD4087_CHIP_ID 0x0057
+#define AD4880_CHIP_ID 0x0750
+
+#define AD4080_MAX_CHANNELS 2

#define AD4080_LVDS_CNV_CLK_CNT_MAX 7

@@ -176,8 +180,9 @@ struct ad4080_chip_info {
};

struct ad4080_state {
- struct regmap *regmap;
- struct iio_backend *back;
+ struct spi_device *spi[AD4080_MAX_CHANNELS];
+ struct regmap *regmap[AD4080_MAX_CHANNELS];
+ struct iio_backend *back[AD4080_MAX_CHANNELS];
const struct ad4080_chip_info *info;
/*
* Synchronize access to members the of driver state, and ensure
@@ -203,10 +208,11 @@ static int ad4080_reg_access(struct iio_dev *indio_dev, unsigned int reg,
{
struct ad4080_state *st = iio_priv(indio_dev);

+ /* Use channel 0 regmap for debugfs access */
if (readval)
- return regmap_read(st->regmap, reg, readval);
+ return regmap_read(st->regmap[0], reg, readval);

- return regmap_write(st->regmap, reg, writeval);
+ return regmap_write(st->regmap[0], reg, writeval);
}

static int ad4080_get_scale(struct ad4080_state *st, int *val, int *val2)
@@ -227,8 +233,9 @@ static unsigned int ad4080_get_dec_rate(struct iio_dev *dev,
struct ad4080_state *st = iio_priv(dev);
int ret;
unsigned int data;
+ unsigned int ch = chan->channel;

- ret = regmap_read(st->regmap, AD4080_REG_FILTER_CONFIG, &data);
+ ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data);
if (ret)
return ret;

@@ -240,13 +247,14 @@ static int ad4080_set_dec_rate(struct iio_dev *dev,
unsigned int mode)
{
struct ad4080_state *st = iio_priv(dev);
+ unsigned int ch = chan->channel;

guard(mutex)(&st->lock);

if ((st->filter_type >= SINC_5 && mode >= 512) || mode < 2)
return -EINVAL;

- return regmap_update_bits(st->regmap, AD4080_REG_FILTER_CONFIG,
+ return regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG,
AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK,
FIELD_PREP(AD4080_FILTER_CONFIG_SINC_DEC_RATE_MSK,
(ilog2(mode) - 1)));
@@ -304,23 +312,23 @@ static int ad4080_write_raw(struct iio_dev *indio_dev,
}
}

-static int ad4080_lvds_sync_write(struct ad4080_state *st)
+static int ad4080_lvds_sync_write(struct ad4080_state *st, unsigned int ch)
{
- struct device *dev = regmap_get_device(st->regmap);
+ struct device *dev = regmap_get_device(st->regmap[ch]);
int ret;

- ret = regmap_set_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
+ ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN);
if (ret)
return ret;

- ret = iio_backend_interface_data_align(st->back, 10000);
+ ret = iio_backend_interface_data_align(st->back[ch], 10000);
if (ret)
return dev_err_probe(dev, ret,
"Data alignment process failed\n");

dev_dbg(dev, "Success: Pattern correct and Locked!\n");
- return regmap_clear_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
+ return regmap_clear_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
AD4080_ADC_DATA_INTF_CONFIG_A_INTF_CHK_EN);
}

@@ -329,9 +337,10 @@ static int ad4080_get_filter_type(struct iio_dev *dev,
{
struct ad4080_state *st = iio_priv(dev);
unsigned int data;
+ unsigned int ch = chan->channel;
int ret;

- ret = regmap_read(st->regmap, AD4080_REG_FILTER_CONFIG, &data);
+ ret = regmap_read(st->regmap[ch], AD4080_REG_FILTER_CONFIG, &data);
if (ret)
return ret;

@@ -343,6 +352,7 @@ static int ad4080_set_filter_type(struct iio_dev *dev,
unsigned int mode)
{
struct ad4080_state *st = iio_priv(dev);
+ unsigned int ch = chan->channel;
int dec_rate;
int ret;

@@ -355,11 +365,11 @@ static int ad4080_set_filter_type(struct iio_dev *dev,
if (mode >= SINC_5 && dec_rate >= 512)
return -EINVAL;

- ret = iio_backend_filter_type_set(st->back, mode);
+ ret = iio_backend_filter_type_set(st->back[ch], mode);
if (ret)
return ret;

- ret = regmap_update_bits(st->regmap, AD4080_REG_FILTER_CONFIG,
+ ret = regmap_update_bits(st->regmap[ch], AD4080_REG_FILTER_CONFIG,
AD4080_FILTER_CONFIG_FILTER_SEL_MSK,
FIELD_PREP(AD4080_FILTER_CONFIG_FILTER_SEL_MSK,
mode));
@@ -399,6 +409,29 @@ static int ad4080_read_avail(struct iio_dev *indio_dev,
}
}

+static int ad4880_update_scan_mode(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ad4080_state *st = iio_priv(indio_dev);
+ unsigned int ch;
+ int ret;
+
+ for (ch = 0; ch < st->info->num_channels; ch++) {
+ /*
+ * Each backend has a single channel (channel 0 from the
+ * backend's perspective), so always use channel index 0.
+ */
+ if (test_bit(ch, scan_mask))
+ ret = iio_backend_chan_enable(st->back[ch], 0);
+ else
+ ret = iio_backend_chan_disable(st->back[ch], 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
static const struct iio_info ad4080_iio_info = {
.debugfs_reg_access = ad4080_reg_access,
.read_raw = ad4080_read_raw,
@@ -406,6 +439,19 @@ static const struct iio_info ad4080_iio_info = {
.read_avail = ad4080_read_avail,
};

+/*
+ * AD4880 needs update_scan_mode to enable/disable individual backend channels.
+ * Single-channel devices don't need this as their backends may not implement
+ * chan_enable/chan_disable operations.
+ */
+static const struct iio_info ad4880_iio_info = {
+ .debugfs_reg_access = ad4080_reg_access,
+ .read_raw = ad4080_read_raw,
+ .write_raw = ad4080_write_raw,
+ .read_avail = ad4080_read_avail,
+ .update_scan_mode = ad4880_update_scan_mode,
+};
+
static const struct iio_enum ad4080_filter_type_enum = {
.items = ad4080_filter_type_iio_enum,
.num_items = ARRAY_SIZE(ad4080_filter_type_iio_enum),
@@ -420,17 +466,51 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
{ }
};

-#define AD4080_CHANNEL_DEFINE(bits, storage) { \
+/*
+ * AD4880 needs per-channel filter configuration since each channel has
+ * its own independent ADC with separate SPI interface.
+ */
+static struct iio_chan_spec_ext_info ad4880_ext_info[] = {
+ IIO_ENUM("filter_type", IIO_SEPARATE, &ad4080_filter_type_enum),
+ IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE,
+ &ad4080_filter_type_enum),
+ { }
+};
+
+#define AD4080_CHANNEL_DEFINE(bits, storage, idx) { \
.type = IIO_VOLTAGE, \
.indexed = 1, \
- .channel = 0, \
+ .channel = (idx), \
.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_shared_by_all_available = \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.ext_info = ad4080_ext_info, \
- .scan_index = 0, \
+ .scan_index = (idx), \
+ .scan_type = { \
+ .sign = 's', \
+ .realbits = (bits), \
+ .storagebits = (storage), \
+ }, \
+}
+
+/*
+ * AD4880 has per-channel attributes (filter_type, oversampling_ratio,
+ * sampling_frequency) since each channel has its own independent ADC
+ * with separate SPI configuration interface.
+ */
+#define AD4880_CHANNEL_DEFINE(bits, storage, idx) { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .channel = (idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
+ .ext_info = ad4880_ext_info, \
+ .scan_index = (idx), \
.scan_type = { \
.sign = 's', \
.realbits = (bits), \
@@ -438,17 +518,22 @@ static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
}, \
}

-static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32);
+static const struct iio_chan_spec ad4080_channel = AD4080_CHANNEL_DEFINE(20, 32, 0);
+
+static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32, 0);

-static const struct iio_chan_spec ad4081_channel = AD4080_CHANNEL_DEFINE(20, 32);
+static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16, 0);

-static const struct iio_chan_spec ad4083_channel = AD4080_CHANNEL_DEFINE(16, 16);
+static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16, 0);

-static const struct iio_chan_spec ad4084_channel = AD4080_CHANNEL_DEFINE(16, 16);
+static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16, 0);

-static const struct iio_chan_spec ad4086_channel = AD4080_CHANNEL_DEFINE(14, 16);
+static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16, 0);

-static const struct iio_chan_spec ad4087_channel = AD4080_CHANNEL_DEFINE(14, 16);
+static const struct iio_chan_spec ad4880_channels[] = {
+ AD4880_CHANNEL_DEFINE(20, 32, 0),
+ AD4880_CHANNEL_DEFINE(20, 32, 1),
+};

static const struct ad4080_chip_info ad4080_chip_info = {
.name = "ad4080",
@@ -510,25 +595,34 @@ static const struct ad4080_chip_info ad4087_chip_info = {
.lvds_cnv_clk_cnt_max = 1,
};

-static int ad4080_setup(struct iio_dev *indio_dev)
+static const struct ad4080_chip_info ad4880_chip_info = {
+ .name = "ad4880",
+ .product_id = AD4880_CHIP_ID,
+ .scale_table = ad4080_scale_table,
+ .num_scales = ARRAY_SIZE(ad4080_scale_table),
+ .num_channels = 2,
+ .channels = ad4880_channels,
+ .lvds_cnv_clk_cnt_max = AD4080_LVDS_CNV_CLK_CNT_MAX,
+};
+
+static int ad4080_setup_channel(struct ad4080_state *st, unsigned int ch)
{
- struct ad4080_state *st = iio_priv(indio_dev);
- struct device *dev = regmap_get_device(st->regmap);
+ struct device *dev = regmap_get_device(st->regmap[ch]);
__le16 id_le;
u16 id;
int ret;

- ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
+ ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
AD4080_INTERFACE_CONFIG_A_SW_RESET);
if (ret)
return ret;

- ret = regmap_write(st->regmap, AD4080_REG_INTERFACE_CONFIG_A,
+ ret = regmap_write(st->regmap[ch], AD4080_REG_INTERFACE_CONFIG_A,
AD4080_INTERFACE_CONFIG_A_SDO_ENABLE);
if (ret)
return ret;

- ret = regmap_bulk_read(st->regmap, AD4080_REG_PRODUCT_ID_L, &id_le,
+ ret = regmap_bulk_read(st->regmap[ch], AD4080_REG_PRODUCT_ID_L, &id_le,
sizeof(id_le));
if (ret)
return ret;
@@ -537,18 +631,18 @@ static int ad4080_setup(struct iio_dev *indio_dev)
if (id != st->info->product_id)
dev_info(dev, "Unrecognized CHIP_ID 0x%X\n", id);

- ret = regmap_set_bits(st->regmap, AD4080_REG_GPIO_CONFIG_A,
+ ret = regmap_set_bits(st->regmap[ch], AD4080_REG_GPIO_CONFIG_A,
AD4080_GPIO_CONFIG_A_GPO_1_EN);
if (ret)
return ret;

- ret = regmap_write(st->regmap, AD4080_REG_GPIO_CONFIG_B,
+ ret = regmap_write(st->regmap[ch], AD4080_REG_GPIO_CONFIG_B,
FIELD_PREP(AD4080_GPIO_CONFIG_B_GPIO_1_SEL_MSK,
AD4080_GPIO_CONFIG_B_GPIO_FILTER_RES_RDY));
if (ret)
return ret;

- ret = iio_backend_num_lanes_set(st->back, st->num_lanes);
+ ret = iio_backend_num_lanes_set(st->back[ch], st->num_lanes);
if (ret)
return ret;

@@ -556,7 +650,7 @@ static int ad4080_setup(struct iio_dev *indio_dev)
return 0;

/* Set maximum LVDS Data Transfer Latency */
- ret = regmap_update_bits(st->regmap,
+ ret = regmap_update_bits(st->regmap[ch],
AD4080_REG_ADC_DATA_INTF_CONFIG_B,
AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
FIELD_PREP(AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_CLK_CNT_MSK,
@@ -565,24 +659,39 @@ static int ad4080_setup(struct iio_dev *indio_dev)
return ret;

if (st->num_lanes > 1) {
- ret = regmap_set_bits(st->regmap, AD4080_REG_ADC_DATA_INTF_CONFIG_A,
+ ret = regmap_set_bits(st->regmap[ch], AD4080_REG_ADC_DATA_INTF_CONFIG_A,
AD4080_ADC_DATA_INTF_CONFIG_A_SPI_LVDS_LANES);
if (ret)
return ret;
}

- ret = regmap_set_bits(st->regmap,
+ ret = regmap_set_bits(st->regmap[ch],
AD4080_REG_ADC_DATA_INTF_CONFIG_B,
AD4080_ADC_DATA_INTF_CONFIG_B_LVDS_CNV_EN);
if (ret)
return ret;

- return ad4080_lvds_sync_write(st);
+ return ad4080_lvds_sync_write(st, ch);
+}
+
+static int ad4080_setup(struct iio_dev *indio_dev)
+{
+ struct ad4080_state *st = iio_priv(indio_dev);
+ unsigned int ch;
+ int ret;
+
+ for (ch = 0; ch < st->info->num_channels; ch++) {
+ ret = ad4080_setup_channel(st, ch);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}

static int ad4080_properties_parse(struct ad4080_state *st)
{
- struct device *dev = regmap_get_device(st->regmap);
+ struct device *dev = regmap_get_device(st->regmap[0]);

st->lvds_cnv_en = device_property_read_bool(dev, "adi,lvds-cnv-enable");

@@ -602,6 +711,7 @@ static int ad4080_probe(struct spi_device *spi)
struct device *dev = &spi->dev;
struct ad4080_state *st;
struct clk *clk;
+ unsigned int ch;
int ret;

indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
@@ -610,6 +720,10 @@ static int ad4080_probe(struct spi_device *spi)

st = iio_priv(indio_dev);

+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -ENODEV;
+
ret = devm_regulator_bulk_get_enable(dev,
ARRAY_SIZE(ad4080_power_supplies),
ad4080_power_supplies);
@@ -617,13 +731,27 @@ static int ad4080_probe(struct spi_device *spi)
return dev_err_probe(dev, ret,
"failed to get and enable supplies\n");

- st->regmap = devm_regmap_init_spi(spi, &ad4080_regmap_config);
- if (IS_ERR(st->regmap))
- return PTR_ERR(st->regmap);
-
- st->info = spi_get_device_match_data(spi);
- if (!st->info)
- return -ENODEV;
+ /* Setup primary SPI device (channel 0) */
+ st->spi[0] = spi;
+ st->regmap[0] = devm_regmap_init_spi(spi, &ad4080_regmap_config);
+ if (IS_ERR(st->regmap[0]))
+ return PTR_ERR(st->regmap[0]);
+
+ /* Setup ancillary SPI devices for additional channels */
+ if (st->info->num_channels > 1) {
+ for (int i = 1; i < st->info->num_channels; i++) {
+ st->spi[i] = devm_spi_new_ancillary_device(spi,
+ spi_get_chipselect(spi, i));
+ if (IS_ERR(st->spi[i]))
+ return dev_err_probe(dev, PTR_ERR(st->spi[i]),
+ "failed to register ancillary device\n");
+
+ st->regmap[i] = devm_regmap_init_spi(st->spi[i],
+ &ad4080_regmap_config);
+ if (IS_ERR(st->regmap[i]))
+ return PTR_ERR(st->regmap[i]);
+ }
+ }

ret = devm_mutex_init(dev, &st->lock);
if (ret)
@@ -632,7 +760,8 @@ static int ad4080_probe(struct spi_device *spi)
indio_dev->name = st->info->name;
indio_dev->channels = st->info->channels;
indio_dev->num_channels = st->info->num_channels;
- indio_dev->info = &ad4080_iio_info;
+ indio_dev->info = st->info->num_channels > 1 ?
+ &ad4880_iio_info : &ad4080_iio_info;

ret = ad4080_properties_parse(st);
if (ret)
@@ -644,15 +773,23 @@ static int ad4080_probe(struct spi_device *spi)

st->clk_rate = clk_get_rate(clk);

- st->back = devm_iio_backend_get(dev, NULL);
- if (IS_ERR(st->back))
- return PTR_ERR(st->back);
+ /* Get backends for all channels */
+ for (ch = 0; ch < st->info->num_channels; ch++) {
+ st->back[ch] = devm_iio_backend_get_by_index(dev, ch);
+ if (IS_ERR(st->back[ch]))
+ return PTR_ERR(st->back[ch]);

- ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
- if (ret)
- return ret;
+ ret = devm_iio_backend_enable(dev, st->back[ch]);
+ if (ret)
+ return ret;
+ }

- ret = devm_iio_backend_enable(dev, st->back);
+ /*
+ * Request buffer from the first backend only. For multi-channel
+ * devices (e.g., AD4880), all backends share a single IIO buffer
+ * as data from all ADC channels is interleaved into one stream.
+ */
+ ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev);
if (ret)
return ret;

@@ -670,6 +807,7 @@ static const struct spi_device_id ad4080_id[] = {
{ "ad4084", (kernel_ulong_t)&ad4084_chip_info },
{ "ad4086", (kernel_ulong_t)&ad4086_chip_info },
{ "ad4087", (kernel_ulong_t)&ad4087_chip_info },
+ { "ad4880", (kernel_ulong_t)&ad4880_chip_info },
{ }
};
MODULE_DEVICE_TABLE(spi, ad4080_id);
@@ -681,6 +819,7 @@ static const struct of_device_id ad4080_of_match[] = {
{ .compatible = "adi,ad4084", &ad4084_chip_info },
{ .compatible = "adi,ad4086", &ad4086_chip_info },
{ .compatible = "adi,ad4087", &ad4087_chip_info },
+ { .compatible = "adi,ad4880", &ad4880_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, ad4080_of_match);
--
2.43.0