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.
Key changes:
- Add AD4880 chip info with 2 channels
- Extend state structure to support arrays of regmaps and backends
- Refactor setup into per-channel function
- Add adi,aux-spi-cs property for secondary channel chip select
- Add channel index parameter to channel macro for scan_index support
- Make all IIO attributes per-channel (filter_type, oversampling_ratio,
sampling_frequency) for independent channel configuration
Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
---
drivers/iio/adc/ad4080.c | 236 ++++++++++++++++++++++++++++-----------
1 file changed, 172 insertions(+), 64 deletions(-)
diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
index 7cf3b6ed7940..e588ff23a7a5 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
@@ -187,7 +192,7 @@ struct ad4080_state {
unsigned int num_lanes;
unsigned int dec_rate;
unsigned long clk_rate;
- enum ad4080_filter_type filter_type;
+ enum ad4080_filter_type filter_type[AD4080_MAX_CHANNELS];
bool lvds_cnv_en;
};
@@ -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)
+ if ((st->filter_type[ch] >= 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)));
@@ -257,6 +265,7 @@ static int ad4080_read_raw(struct iio_dev *indio_dev,
int *val, int *val2, long m)
{
struct ad4080_state *st = iio_priv(indio_dev);
+ unsigned int ch = chan->channel;
int dec_rate;
switch (m) {
@@ -266,15 +275,15 @@ static int ad4080_read_raw(struct iio_dev *indio_dev,
dec_rate = ad4080_get_dec_rate(indio_dev, chan);
if (dec_rate < 0)
return dec_rate;
- if (st->filter_type == SINC_5_COMP)
+ if (st->filter_type[ch] == SINC_5_COMP)
dec_rate *= 2;
- if (st->filter_type)
+ if (st->filter_type[ch])
*val = DIV_ROUND_CLOSEST(st->clk_rate, dec_rate);
else
*val = st->clk_rate;
return IIO_VAL_INT;
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
- if (st->filter_type == FILTER_NONE) {
+ if (st->filter_type[ch] == FILTER_NONE) {
*val = 1;
} else {
*val = ad4080_get_dec_rate(indio_dev, chan);
@@ -292,10 +301,11 @@ static int ad4080_write_raw(struct iio_dev *indio_dev,
int val, int val2, long mask)
{
struct ad4080_state *st = iio_priv(indio_dev);
+ unsigned int ch = chan->channel;
switch (mask) {
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
- if (st->filter_type == FILTER_NONE && val > 1)
+ if (st->filter_type[ch] == FILTER_NONE && val > 1)
return -EINVAL;
return ad4080_set_dec_rate(indio_dev, chan, val);
@@ -304,23 +314,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 +339,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 +354,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,18 +367,18 @@ 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));
if (ret)
return ret;
- st->filter_type = mode;
+ st->filter_type[ch] = mode;
return 0;
}
@@ -377,17 +389,18 @@ static int ad4080_read_avail(struct iio_dev *indio_dev,
long mask)
{
struct ad4080_state *st = iio_priv(indio_dev);
+ unsigned int ch = chan->channel;
switch (mask) {
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
- switch (st->filter_type) {
+ switch (st->filter_type[ch]) {
case FILTER_NONE:
*vals = ad4080_dec_rate_none;
*length = ARRAY_SIZE(ad4080_dec_rate_none);
break;
default:
*vals = ad4080_dec_rate_avail;
- *length = st->filter_type >= SINC_5 ?
+ *length = st->filter_type[ch] >= SINC_5 ?
(ARRAY_SIZE(ad4080_dec_rate_avail) - 2) :
ARRAY_SIZE(ad4080_dec_rate_avail);
break;
@@ -399,11 +412,35 @@ static int ad4080_read_avail(struct iio_dev *indio_dev,
}
}
+static int ad4080_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,
.write_raw = ad4080_write_raw,
.read_avail = ad4080_read_avail,
+ .update_scan_mode = ad4080_update_scan_mode,
};
static const struct iio_enum ad4080_filter_type_enum = {
@@ -414,23 +451,23 @@ static const struct iio_enum ad4080_filter_type_enum = {
};
static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
- IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ad4080_filter_type_enum),
- IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL,
+ 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) { \
+#define AD4080_CHANNEL_DEFINE(bits, storage, idx) { \
.type = IIO_VOLTAGE, \
.indexed = 1, \
- .channel = 0, \
- .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE), \
- .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
+ .channel = (idx), \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) | \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
- .info_mask_shared_by_all_available = \
+ .info_mask_separate_available = \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.ext_info = ad4080_ext_info, \
- .scan_index = 0, \
+ .scan_index = (idx), \
.scan_type = { \
.sign = 's', \
.realbits = (bits), \
@@ -438,17 +475,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[] = {
+ AD4080_CHANNEL_DEFINE(20, 32, 0),
+ AD4080_CHANNEL_DEFINE(20, 32, 1),
+};
static const struct ad4080_chip_info ad4080_chip_info = {
.name = "ad4080",
@@ -510,25 +552,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 +588,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 +607,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 +616,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");
@@ -596,12 +662,19 @@ static int ad4080_properties_parse(struct ad4080_state *st)
return 0;
}
+static void ad4080_unregister_ancillary(void *data)
+{
+ spi_unregister_device(data);
+}
+
static int ad4080_probe(struct spi_device *spi)
{
+ static const char * const backend_names[] = { "0", "1" };
struct iio_dev *indio_dev;
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 +683,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 +694,35 @@ 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);
+ /* 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]);
- st->info = spi_get_device_match_data(spi);
- if (!st->info)
- return -ENODEV;
+ /* Setup ancillary SPI device for additional channel (AD4880) */
+ if (st->info->num_channels > 1) {
+ u32 aux_cs;
+
+ ret = device_property_read_u32(dev, "adi,aux-spi-cs", &aux_cs);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "missing adi,aux-spi-cs for multi-channel device\n");
+
+ st->spi[1] = spi_new_ancillary_device(spi, aux_cs);
+ if (IS_ERR(st->spi[1]))
+ return PTR_ERR(st->spi[1]);
+
+ ret = devm_add_action_or_reset(dev, ad4080_unregister_ancillary,
+ st->spi[1]);
+ if (ret)
+ return ret;
+
+ st->regmap[1] = devm_regmap_init_spi(st->spi[1],
+ &ad4080_regmap_config);
+ if (IS_ERR(st->regmap[1]))
+ return PTR_ERR(st->regmap[1]);
+ }
ret = devm_mutex_init(dev, &st->lock);
if (ret)
@@ -644,15 +743,22 @@ 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++) {
+ if (st->info->num_channels > 1)
+ st->back[ch] = devm_iio_backend_get(dev, backend_names[ch]);
+ else
+ st->back[ch] = devm_iio_backend_get(dev, NULL);
- ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
- if (ret)
- return ret;
+ if (IS_ERR(st->back[ch]))
+ return PTR_ERR(st->back[ch]);
+
+ ret = devm_iio_backend_enable(dev, st->back[ch]);
+ if (ret)
+ return ret;
+ }
- ret = devm_iio_backend_enable(dev, st->back);
+ ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev);
if (ret)
return ret;
@@ -670,6 +776,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 +788,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
On Thu, 29 Jan 2026 17:27:30 +0200
Antoniu Miclaus <antoniu.miclaus@analog.com> wrote:
> 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.
>
> Key changes:
> - Add AD4880 chip info with 2 channels
> - Extend state structure to support arrays of regmaps and backends
> - Refactor setup into per-channel function
> - Add adi,aux-spi-cs property for secondary channel chip select
> - Add channel index parameter to channel macro for scan_index support
> - Make all IIO attributes per-channel (filter_type, oversampling_ratio,
> sampling_frequency) for independent channel configuration
>
> Signed-off-by: Antoniu Miclaus <antoniu.miclaus@analog.com>
Hi Antoniu
A few additional comments from me to add to those of the other reviewers.
Jonathan
> ---
> drivers/iio/adc/ad4080.c | 236 ++++++++++++++++++++++++++++-----------
> 1 file changed, 172 insertions(+), 64 deletions(-)
>
> diff --git a/drivers/iio/adc/ad4080.c b/drivers/iio/adc/ad4080.c
> index 7cf3b6ed7940..e588ff23a7a5 100644
> --- a/drivers/iio/adc/ad4080.c
> +++ b/drivers/iio/adc/ad4080.c
> static int ad4080_probe(struct spi_device *spi)
> {
> + static const char * const backend_names[] = { "0", "1" };
> struct iio_dev *indio_dev;
> 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 +683,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 +694,35 @@ 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);
> + /* 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]);
>
> - st->info = spi_get_device_match_data(spi);
> - if (!st->info)
> - return -ENODEV;
> + /* Setup ancillary SPI device for additional channel (AD4880) */
> + if (st->info->num_channels > 1) {
This hard codes assumption that it is 2. So perhaps better to check for
that explicitly rather than simply > 1.
Maybe turn this into an appropriate loop so you don't need to care that
it's 2 or bigger.
> + u32 aux_cs;
> +
> + ret = device_property_read_u32(dev, "adi,aux-spi-cs", &aux_cs);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "missing adi,aux-spi-cs for multi-channel device\n");
> +
> + st->spi[1] = spi_new_ancillary_device(spi, aux_cs);
> + if (IS_ERR(st->spi[1]))
> + return PTR_ERR(st->spi[1]);
> +
> + ret = devm_add_action_or_reset(dev, ad4080_unregister_ancillary,
> + st->spi[1]);
> + if (ret)
> + return ret;
> +
> + st->regmap[1] = devm_regmap_init_spi(st->spi[1],
> + &ad4080_regmap_config);
> + if (IS_ERR(st->regmap[1]))
> + return PTR_ERR(st->regmap[1]);
> + }
>
> ret = devm_mutex_init(dev, &st->lock);
> if (ret)
> @@ -644,15 +743,22 @@ 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++) {
> + if (st->info->num_channels > 1)
> + st->back[ch] = devm_iio_backend_get(dev, backend_names[ch]);
> + else
> + st->back[ch] = devm_iio_backend_get(dev, NULL);
To me, it makes sense to always use names for multi channel devices and only
fall back to this if there is only one supported channel.
Something like
char *name = NULL;
if (st->info->num_channels != 1)
name = backend_names[ch];
st->back[ch] = devm_iio_backend_get(dev, name);
>
> - ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
> - if (ret)
> - return ret;
> + if (IS_ERR(st->back[ch]))
> + return PTR_ERR(st->back[ch]);
> +
> + ret = devm_iio_backend_enable(dev, st->back[ch]);
This changes the ordering so we now enable it before requesting the buffer.
That may well be fine, but I'd kind of prefer that to be made clear. Perhaps
with a precursor patch reorganizing that order where you can talk about why
it is fine to do so.
> + if (ret)
> + return ret;
> + }
>
> - ret = devm_iio_backend_enable(dev, st->back);
> + ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev);
Add a comment on why requesting only the first buffer is enough.
> if (ret)
> return ret;
>
On 1/29/26 9:27 AM, Antoniu Miclaus wrote:
> 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.
>
...
> +static int ad4080_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;
Previously, the single-channel chips didn't call backend channel
enable/disable. It this going to cause problems for that? I.e. the
function isn't implemented in the backend?
> + }
> +
> + return 0;
> +}
> +
> static const struct iio_info ad4080_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 = ad4080_update_scan_mode,
> };
>
> static const struct iio_enum ad4080_filter_type_enum = {
> @@ -414,23 +451,23 @@ static const struct iio_enum ad4080_filter_type_enum = {
> };
>
> static struct iio_chan_spec_ext_info ad4080_ext_info[] = {
> - IIO_ENUM("filter_type", IIO_SHARED_BY_ALL, &ad4080_filter_type_enum),
> - IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_ALL,
> + IIO_ENUM("filter_type", IIO_SEPARATE, &ad4080_filter_type_enum),
> + IIO_ENUM_AVAILABLE("filter_type", IIO_SEPARATE,
> &ad4080_filter_type_enum),
This is a breaking ABI change, so could be problamatic. We probably need to
keep the old info[] for the single channle chips and make a new info[] for
the multi-channels chips to avoid breaking things.
> { }
> };
>
...
> @@ -617,13 +694,35 @@ 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);
> + /* 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]);
>
> - st->info = spi_get_device_match_data(spi);
> - if (!st->info)
> - return -ENODEV;
> + /* Setup ancillary SPI device for additional channel (AD4880) */
> + if (st->info->num_channels > 1) {
> + u32 aux_cs;
> +
> + ret = device_property_read_u32(dev, "adi,aux-spi-cs", &aux_cs);
As in the DT bindings comment, this sould be looking up the value at index 1
of the reg property rather than a custom property.
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "missing adi,aux-spi-cs for multi-channel device\n");
> +
> + st->spi[1] = spi_new_ancillary_device(spi, aux_cs);
> + if (IS_ERR(st->spi[1]))
> + return PTR_ERR(st->spi[1]);
> +
> + ret = devm_add_action_or_reset(dev, ad4080_unregister_ancillary,
> + st->spi[1]);
> + if (ret)
> + return ret;
> +
> + st->regmap[1] = devm_regmap_init_spi(st->spi[1],
> + &ad4080_regmap_config);
> + if (IS_ERR(st->regmap[1]))
> + return PTR_ERR(st->regmap[1]);
> + }
>
> ret = devm_mutex_init(dev, &st->lock);
> if (ret)
> @@ -644,15 +743,22 @@ 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++) {
> + if (st->info->num_channels > 1)
> + st->back[ch] = devm_iio_backend_get(dev, backend_names[ch]);
It looks like we missed io-backend-names from the DT bindings. In this case
though, going by index instead of name would make more sense. So we could add
a devm_iio_backend_get_by_index() function instead of modifying the DT bindings.
Then we wouldn't need the if statement here.
> + else
> + st->back[ch] = devm_iio_backend_get(dev, NULL);
>
> - ret = devm_iio_backend_request_buffer(dev, st->back, indio_dev);
> - if (ret)
> - return ret;
> + if (IS_ERR(st->back[ch]))
> + return PTR_ERR(st->back[ch]);
> +
> + ret = devm_iio_backend_enable(dev, st->back[ch]);
> + if (ret)
> + return ret;
> + }
>
> - ret = devm_iio_backend_enable(dev, st->back);
> + ret = devm_iio_backend_request_buffer(dev, st->back[0], indio_dev);
If this is correct, it could use a comment explaining why only one
backend has the buffer even if there are two backends.
> if (ret)
> return ret;
>
> @@ -670,6 +776,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 +788,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);
On Thu, Jan 29, 2026 at 05:27:30PM +0200, Antoniu Miclaus wrote: > 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. > Key changes: > - Add AD4880 chip info with 2 channels > - Extend state structure to support arrays of regmaps and backends > - Refactor setup into per-channel function > - Add adi,aux-spi-cs property for secondary channel chip select > - Add channel index parameter to channel macro for scan_index support > - Make all IIO attributes per-channel (filter_type, oversampling_ratio, > sampling_frequency) for independent channel configuration Can something be split as prerequisite? Overall, making struct regmap *map = ...->...[ch]; will help to make many lines of code easier to read. -- With Best Regards, Andy Shevchenko
© 2016 - 2026 Red Hat, Inc.