Re: [PATCH v7 3/6] iio: adc: ad4691: add triggered buffer support
From: Jonathan Cameron
Date: Sun Apr 12 2026 - 13:51:35 EST
On Thu, 09 Apr 2026 18:28:24 +0300
Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@xxxxxxxxxx> wrote:
> From: Radu Sabau <radu.sabau@xxxxxxxxxx>
>
> Add buffered capture support using the IIO triggered buffer framework.
>
> CNV Burst Mode: the GP pin identified by interrupt-names in the device
> tree is configured as DATA_READY output. The IRQ handler stops
> conversions and fires the IIO trigger; the trigger handler executes a
> pre-built SPI message that reads all active channels from the AVG_IN
> accumulator registers and then resets accumulator state and restarts
> conversions for the next cycle.
>
> Manual Mode: CNV is tied to SPI CS so each transfer simultaneously
> reads the previous result and starts the next conversion (pipelined
> N+1 scheme). At preenable time a pre-built, optimised SPI message of
> N+1 transfers is constructed (N channel reads plus one NOOP to drain
> the pipeline). The trigger handler executes the message in a single
> spi_sync() call and collects the results. An external trigger (e.g.
> iio-trig-hrtimer) is required to drive the trigger at the desired
> sample rate.
>
> Both modes share the same trigger handler and push a complete scan —
> one u16 slot per channel at its scan_index position, followed by a
> timestamp — to the IIO buffer via iio_push_to_buffers_with_ts().
>
> The CNV Burst Mode sampling frequency (PWM period) is exposed as a
> buffer-level attribute via IIO_DEVICE_ATTR.
>
> Signed-off-by: Radu Sabau <radu.sabau@xxxxxxxxxx>
A couple of minor things inline.
Thanks,
Jonathan
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index 43bd408c3d11..3e5caa0972eb 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -5,15 +5,19 @@
> */
> #include <linux/array_size.h>
> #include <linux/bitfield.h>
> -#include <linux/bitops.h>
> +#include <linux/bitmap.h>
> #include <linux/cleanup.h>
> #include <linux/delay.h>
> #include <linux/dev_printk.h>
> #include <linux/device/devres.h>
> +#include <linux/dmaengine.h>
> #include <linux/err.h>
> +#include <linux/interrupt.h>
> #include <linux/math.h>
> #include <linux/module.h>
> #include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> #include <linux/reset.h>
> @@ -21,7 +25,14 @@
> #include <linux/units.h>
> #include <linux/unaligned.h>
>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/buffer-dma.h>
> +#include <linux/iio/buffer-dmaengine.h>
Not yet... Only bring these headers in when you need them.
So far this is just doing normal SPI stuff.
> #include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
> @@ -201,8 +245,45 @@ struct ad4691_state {
> * atomicity of consecutive SPI operations.
> */
> struct mutex lock;
> + /*
> + * Per-buffer-enable lifetime resources:
> + * Manual Mode - a pre-built SPI message that clocks out N+1
> + * transfers in one go.
> + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
> + * transfers in one go.
> + */
> + struct spi_message scan_msg;
> + /* max 16 + 1 NOOP (manual) or 2*16 + 2 (CNV burst). */
> + struct spi_transfer scan_xfers[34];
> + /*
> + * CNV burst: 16 AVG_IN addresses + state-reset address + state-reset
> + * value = 18. Manual: 16 channel cmds + 1 NOOP = 17.
> + */
> + __be16 scan_tx[18];
David raised this. As these aren't going through the regmap and are using
spi_transfers directly they need to be using DMA safe buffers.
> + /* Scan buffer: one BE16 slot per channel (rx'd directly), plus timestamp */
> + struct {
> + __be16 vals[16];
> + aligned_s64 ts;
> + } scan;
> };
> +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int n_active;
> + unsigned int n_xfers;
> + unsigned int prev_i, k, i;
> + bool first;
> + int ret;
> +
> + n_active = bitmap_weight(indio_dev->active_scan_mask, iio_get_masklength(indio_dev));
> + n_xfers = n_active + 1;
> +
> + memset(st->scan_xfers, 0, n_xfers * sizeof(st->scan_xfers[0]));
> + memset(st->scan_tx, 0, n_xfers * sizeof(st->scan_tx[0]));
> +
> + spi_message_init(&st->scan_msg);
> +
> + first = true;
> + prev_i = 0;
> + k = 0;
> + iio_for_each_active_channel(indio_dev, i) {
> + st->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> + /*
> + * The pipeline means xfer[0] receives the residual from the
> + * previous sequence, not a valid sample for channel i. Point
> + * it at vals[i] anyway; xfer[1] (or the NOOP when only one
> + * channel is active) will overwrite that slot with the real
> + * result, so no separate dummy buffer is needed.
> + */
> + if (first) {
> + st->scan_xfers[k].rx_buf = &st->scan.vals[i];
> + first = false;
> + } else {
> + st->scan_xfers[k].rx_buf = &st->scan.vals[prev_i];
> + }
> + st->scan_xfers[k].len = sizeof(__be16);
> + st->scan_xfers[k].cs_change = 1;
> + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> + prev_i = i;
> + k++;
> + }
> +
> + /* Final NOOP transfer retrieves the last channel's result. */
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k]; /* scan_tx[k] == 0 == NOOP */
> + st->scan_xfers[k].rx_buf = &st->scan.vals[prev_i];
> + st->scan_xfers[k].len = sizeof(__be16);
> + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> +
> + ret = spi_optimize_message(st->spi, &st->scan_msg);
> + if (ret)
See below. This matches my expectations for what to do if spi_optimize_message()
fails.
> + return ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret) {
> + spi_unoptimize_message(&st->scan_msg);
> + return ret;
> + }
> +
> + return 0;
> +}
> +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int n_active;
> + unsigned int k, i;
> + int ret;
> +
> + n_active = bitmap_weight(indio_dev->active_scan_mask, iio_get_masklength(indio_dev));
> +
> + memset(st->scan_xfers, 0, (2 * n_active + 2) * sizeof(st->scan_xfers[0]));
> + memset(st->scan_tx, 0, (n_active + 2) * sizeof(st->scan_tx[0]));
> +
> + spi_message_init(&st->scan_msg);
> +
> + /*
> + * Each AVG_IN read needs two transfers: a 2-byte address write phase
> + * followed by a 2-byte data read phase. CS toggles between channels
> + * (cs_change=1 on the read phase of all but the last channel).
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, i) {
> + st->scan_tx[k] = cpu_to_be16(0x8000 | AD4691_AVG_IN(i));
> + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[2 * k].len = sizeof(__be16);
> + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> + st->scan_xfers[2 * k + 1].rx_buf = &st->scan.vals[i];
> + st->scan_xfers[2 * k + 1].len = sizeof(__be16);
> + st->scan_xfers[2 * k + 1].cs_change = 1;
> + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
> + k++;
> + }
> +
> + st->scan_tx[k] = cpu_to_be16(AD4691_STATE_RESET_REG);
> + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[2 * k].len = sizeof(__be16);
> + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> + st->scan_tx[k + 1] = cpu_to_be16(AD4691_STATE_RESET_ALL << 8);
> + st->scan_xfers[2 * k + 1].tx_buf = &st->scan_tx[k + 1];
> + st->scan_xfers[2 * k + 1].len = sizeof(__be16);
> + st->scan_xfers[2 * k + 1].cs_change = 1;
> + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
> +
> + ret = spi_optimize_message(st->spi, &st->scan_msg);
> + if (ret)
> + goto err_unoptimize;
I'd expect spi_optimize_message() to be side effect free if it fails.
I took a quick look at the implementation and it looks like it is..
So probably just return ret; here
That matches with the other similar flow above.
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)));
> + if (ret)
> + goto err_unoptimize;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> + ~bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)) & GENMASK(15, 0));
> + if (ret)
> + goto err_unoptimize;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + goto err_unoptimize;
> +
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + goto err_exit_conv;
> +
> + enable_irq(st->irq);
> + return 0;
> +
> +err_exit_conv:
> + ad4691_exit_conversion_mode(st);
> +err_unoptimize:
> + spi_unoptimize_message(&st->scan_msg);
> + return ret;
> +}
> +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> + struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct iio_trigger *trig;
> + unsigned int i;
> + int irq, ret;
> +
> + trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> + indio_dev->name,
> + iio_device_id(indio_dev));
Might as well wrap this less.
> + if (!trig)
> + return -ENOMEM;
> +