Re: [PATCH v5 2/4] iio: adc: ad4691: add initial driver for AD4691 family
From: Andy Shevchenko
Date: Fri Mar 27 2026 - 07:43:59 EST
On Fri, Mar 27, 2026 at 01:07:58PM +0200, Radu Sabau via B4 Relay wrote:
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> The chip idles in Autonomous Mode so that single-shot read_raw can use
> the internal oscillator without disturbing the hardware configuration.
>
> Three voltage supply domains are managed: avdd (required), vio, and a
> reference supply on either the REF pin (ref-supply, external buffer)
> or the REFIN pin (refin-supply, uses the on-chip reference buffer;
> REFBUF_EN is set accordingly). Hardware reset is performed via
> the reset controller framework; a software reset through SPI_CONFIG_A
> is used as fallback when no hardware reset is available.
>
> Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
> an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
> 16-bit transfer.
...
+ array_size.h
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
Hmm... Is it used? Or perhaps you need only
dev_printk.h
device/devres.h
?
> +#include <linux/err.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/reset.h>
> +#include <linux/spi/spi.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>
...
> +/*
> + * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
> + * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
> + * up to 500 kHz and use index 1 as their highest valid rate.
> + */
> +static const int ad4691_osc_freqs[] = {
> + 1000000, /* 0x0: 1 MHz */
> + 500000, /* 0x1: 500 kHz */
> + 400000, /* 0x2: 400 kHz */
> + 250000, /* 0x3: 250 kHz */
> + 200000, /* 0x4: 200 kHz */
> + 167000, /* 0x5: 167 kHz */
> + 133000, /* 0x6: 133 kHz */
> + 125000, /* 0x7: 125 kHz */
> + 100000, /* 0x8: 100 kHz */
> + 50000, /* 0x9: 50 kHz */
> + 25000, /* 0xA: 25 kHz */
> + 12500, /* 0xB: 12.5 kHz */
> + 10000, /* 0xC: 10 kHz */
> + 5000, /* 0xD: 5 kHz */
> + 2500, /* 0xE: 2.5 kHz */
> + 1250, /* 0xF: 1.25 kHz */
Instead of comments, make the code self-commented and robust:
/* ...the top comment... */
static const int ad4691_osc_freqs_Hz[] = {
...
[0xD] = 5000,
[0xE] = 2500,
[0xF] = 1250,
};
I would even use unit multipliers in some cases, but it might make the whole
table inconsistent, dunno.
> +};
>From this it will be visible that the table is in Hz and each value is properly
indexed, even shuffling won't break the code.
...
> +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
> + unsigned int i;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> + for (i = start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) {
for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) {
> + if (ad4691_osc_freqs[i] != freq)
> + continue;
> + return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> + AD4691_OSC_FREQ_MASK, i);
> + }
> +
> + return -EINVAL;
> +}
...
> +static int ad4691_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type,
> + int *length, long mask)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
Yeah, in the table it's written as 1000000... But as I mentioned above, using
unit multipliers _there_ maybe not a good idea.
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *vals = &ad4691_osc_freqs[start];
> + *type = IIO_VAL_INT;
> + *length = ARRAY_SIZE(ad4691_osc_freqs) - start;
> + return IIO_AVAIL_LIST;
> + default:
> + return -EINVAL;
> + }
> +}
...
> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int reg_val;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> + /*
> + * Use AUTONOMOUS mode for single-shot reads.
> + */
One line?
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + BIT(chan->channel));
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> + (u16)~BIT(chan->channel));
Why do you need casting?
> + if (ret)
> + return ret;
> + ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * Wait for at least 2 internal oscillator periods for the
> + * conversion to complete.
> + */
> + fsleep(DIV_ROUND_UP(2 * USEC_PER_SEC, ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]));
Way too long line. Use temporary variables for that to make it easier to parse.
Also add a (short) comment on how the OSC periods are being calculated.
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
> + if (ret)
> + return ret;
> +
> + *val = reg_val;
> +
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + return IIO_VAL_INT;
> +}
...
> + ret = devm_regulator_get_enable(dev, "avdd");
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get and enable AVDD\n");
> +
> + ret = devm_regulator_get_enable(dev, "vio");
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
Can they be united to a bulk?
...
> +static int ad4691_reset(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct reset_control *rst;
> +
> + rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(rst))
> + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n");
> +
> + if (rst) {
> + /*
> + * The GPIO is already asserted by reset_gpio_probe (GPIOD_OUT_HIGH).
> + * Wait for the reset pulse width required by the chip. See datasheet Table 5.
Too long, try to wrap around 80, and drop unneeded (confusing?) details.
> + */
/*
* The GPIO is already asserted by reset_gpio_probe().
* Wait for the reset pulse width required by the chip.
* See datasheet Table 5.
*/
> + fsleep(300);
> + return reset_control_deassert(rst);
> + }
> +
> + /* No hardware reset available, fall back to software reset. */
> + return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
> + AD4691_SW_RESET);
> +}
...
> + ret = regmap_update_bits(st->regmap, AD4691_REF_CTRL,
> + AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN,
> + FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val) |
> + (st->refbuf_en ? AD4691_REFBUF_EN : 0));
With temporary variable it will become something like
/* ...Comment on what is this... */
val = FIELD_PREP(...);
FIELD_MODIFY(...);
ret = regmap_update_bits(st->regmap, AD4691_REF_CTRL,
AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN, val);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
...
> + /*
> + * Set the internal oscillator to the highest rate this chip supports.
> + * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
> + * chips start at index 1 (500 kHz).
> + */
> + ret = regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
> + AD4691_OSC_FREQ_MASK,
> + (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1);
_assign_bits?
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
--
With Best Regards,
Andy Shevchenko