[PATCH 4/4] iio: adc: ad4691: add SPI offload support

Radu Sabau via B4 Relay posted 4 patches 1 month ago
There is a newer version of this series
[PATCH 4/4] iio: adc: ad4691: add SPI offload support
Posted by Radu Sabau via B4 Relay 1 month ago
From: Radu Sabau <radu.sabau@analog.com>

Add SPI offload support to the AD4691 family driver to enable
DMA-based RX stream acquisition. When an SPI offload is available,
the driver switches to a pre-built SPI message with 32-bit transfers
(4-byte frames aligned to DMA width) and registers a periodic
offload trigger for autonomous, CPU-independent sampling.

The offload path implements its own buffer setup ops
(ad4691_offload_buffer_postenable/predisable) that enable the
offload trigger and wire the DMAengine buffer, while the existing
software triggered buffer path is retained as a fallback for
non-offload configurations.

Offload channel specs use a 32-bit storage/repeat with a 16-bit
shift to extract ADC data from the MSBytes of each DMA word,
matching the wire format in Manual Mode where SDO outputs ADC data
directly without a command echo.

Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.

Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
 drivers/iio/adc/Kconfig  |   1 +
 drivers/iio/adc/ad4691.c | 541 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 531 insertions(+), 11 deletions(-)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index d498f16c0816..93f090e9a562 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -144,6 +144,7 @@ config AD4691
 	depends on SPI
 	select IIO_BUFFER
 	select IIO_TRIGGERED_BUFFER
+	select IIO_BUFFER_DMAENGINE
 	select REGMAP
 	help
 	  Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index ab48f336e46c..7ec0a2555a4b 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -8,6 +8,7 @@
 #include <linux/clk.h>
 #include <linux/delay.h>
 #include <linux/device.h>
+#include <linux/dmaengine.h>
 #include <linux/err.h>
 #include <linux/gpio/consumer.h>
 #include <linux/hrtimer.h>
@@ -21,11 +22,15 @@
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
 #include <linux/util_macros.h>
 #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>
 #include <linux/iio/iio.h>
 
 #include <linux/iio/trigger.h>
@@ -49,6 +54,7 @@
  */
 #define AD4691_MANUAL_MODE_STD_FREQ(x, y)	((y) / (36 * ((x) + 1)))
 #define AD4691_BITS_PER_XFER			24
+#define AD4691_OFFLOAD_BITS_PER_WORD		32
 #define AD4691_CNV_DUTY_CYCLE_NS		380
 #define AD4691_MAX_CONV_PERIOD_US		800
 
@@ -253,6 +259,43 @@ static const struct iio_chan_spec ad4693_manual_channels[] = {
 	AD4691_CHANNEL(7, 7, 16, 24, 8)
 };
 
+/*
+ * Manual mode offload channels.
+ *
+ * Transfer format: 4-byte SPI frame to match 32-bit DMA width.
+ * In Manual Mode there is no command echo - SDO outputs ADC data directly.
+ * 16-bit ADC data in 2 MSBytes, shift=16 for extraction.
+ */
+static const struct iio_chan_spec ad4691_manual_offload_channels[] = {
+	AD4691_CHANNEL(0, 0, 16, 32, 16),
+	AD4691_CHANNEL(1, 1, 16, 32, 16),
+	AD4691_CHANNEL(2, 2, 16, 32, 16),
+	AD4691_CHANNEL(3, 3, 16, 32, 16),
+	AD4691_CHANNEL(4, 4, 16, 32, 16),
+	AD4691_CHANNEL(5, 5, 16, 32, 16),
+	AD4691_CHANNEL(6, 6, 16, 32, 16),
+	AD4691_CHANNEL(7, 7, 16, 32, 16),
+	AD4691_CHANNEL(8, 8, 16, 32, 16),
+	AD4691_CHANNEL(9, 9, 16, 32, 16),
+	AD4691_CHANNEL(10, 10, 16, 32, 16),
+	AD4691_CHANNEL(11, 11, 16, 32, 16),
+	AD4691_CHANNEL(12, 12, 16, 32, 16),
+	AD4691_CHANNEL(13, 13, 16, 32, 16),
+	AD4691_CHANNEL(14, 14, 16, 32, 16),
+	AD4691_CHANNEL(15, 15, 16, 32, 16)
+};
+
+static const struct iio_chan_spec ad4693_manual_offload_channels[] = {
+	AD4691_CHANNEL(0, 0, 16, 32, 16),
+	AD4691_CHANNEL(1, 1, 16, 32, 16),
+	AD4691_CHANNEL(2, 2, 16, 32, 16),
+	AD4691_CHANNEL(3, 3, 16, 32, 16),
+	AD4691_CHANNEL(4, 4, 16, 32, 16),
+	AD4691_CHANNEL(5, 5, 16, 32, 16),
+	AD4691_CHANNEL(6, 6, 16, 32, 16),
+	AD4691_CHANNEL(7, 7, 16, 32, 16)
+};
+
 static const struct ad4691_chip_info ad4691_chips[] = {
 	[AD4691_ID_AD4691] = {
 		.channels = ad4691_channels,
@@ -310,6 +353,17 @@ struct ad4691_state {
 	struct hrtimer			sampling_timer;
 	ktime_t				sampling_period;
 
+	struct spi_offload		*offload;
+	struct spi_offload_trigger	*offload_trigger;
+	struct spi_offload_trigger	*offload_trigger_periodic;
+	u64				offload_trigger_hz;
+	struct spi_message		offload_msg;
+	/* Max 16 channels * 2 transfers (cmd + data) + 1 state reset + 1 conv start */
+	struct spi_transfer		offload_xfer[34];
+	/* TX commands for manual and accumulator modes */
+	u32				offload_tx_cmd[17];
+	u32				offload_tx_reset;
+	u32				offload_tx_conv_stop;
 	/* DMA (thus cache coherency maintenance) may require the
 	 * transfer buffers to live in their own cache lines.
 	 * Make the buffer large enough for one 24 bit sample and one 64 bit
@@ -324,6 +378,65 @@ struct ad4691_state {
 	} scan __aligned(IIO_DMA_MINALIGN);
 };
 
+static const struct spi_offload_config ad4691_offload_config = {
+	.capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+			    SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
+static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigger,
+					 enum spi_offload_trigger_type type,
+					 u64 *args, u32 nargs)
+{
+	if (type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+		return false;
+
+	/*
+	 * Requires 2 args:
+	 * args[0] is the trigger event (BUSY or DATA_READY).
+	 * args[1] is the GPIO pin number (only GP0 supported).
+	 */
+	if (nargs != 2)
+		return false;
+
+	if (args[0] != AD4691_TRIGGER_EVENT_BUSY &&
+	    args[0] != AD4691_TRIGGER_EVENT_DATA_READY)
+		return false;
+
+	if (args[1] != AD4691_TRIGGER_PIN_GP0)
+		return false;
+
+	return true;
+}
+
+static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
+					  enum spi_offload_trigger_type type,
+					  u64 *args, u32 nargs)
+{
+	/*
+	 * GP0 is configured as DATA_READY or BUSY in ad4691_config()
+	 * based on the ADC mode. No additional configuration needed here.
+	 */
+	if (nargs != 2)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int ad4691_offload_trigger_validate(struct spi_offload_trigger *trigger,
+					   struct spi_offload_trigger_config *config)
+{
+	if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+		return -EINVAL;
+
+	return 0;
+}
+
+static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops = {
+	.match = ad4691_offload_trigger_match,
+	.request = ad4691_offload_trigger_request,
+	.validate = ad4691_offload_trigger_validate,
+};
+
 static void ad4691_disable_regulators(void *data)
 {
 	struct ad4691_state *st = data;
@@ -560,6 +673,9 @@ static int ad4691_get_sampling_freq(struct ad4691_state *st)
 
 	switch (st->adc_mode) {
 	case AD4691_MANUAL_MODE:
+		/* Offload uses periodic trigger, non-offload uses hrtimer */
+		if (st->offload)
+			return st->offload_trigger_hz;
 		return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
 					     ktime_to_ns(st->sampling_period));
 	case AD4691_CNV_CLOCK_MODE:
@@ -600,6 +716,7 @@ static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
  * ad4691_cnv_burst_period_ns - Compute the CNV_BURST_MODE PWM period.
  * @st: Driver state.
  * @n_active: Number of active channels.
+ * @is_offload: True for the SPI offload path, false for the triggered buffer path.
  *
  * The period must cover the full conversion time tOSC*(n_active+1) plus
  * the SPI transfer time for reading the accumulator results and issuing
@@ -608,7 +725,7 @@ static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
  * Return: Period in nanoseconds.
  */
 static u64 ad4691_cnv_burst_period_ns(struct ad4691_state *st,
-				      int n_active)
+				      int n_active, bool is_offload)
 {
 	unsigned int osc_idx = AD4691_OSC_1MHZ;
 	u64 osc_freq, conv_time_ns, spi_bits, spi_time_ns;
@@ -620,7 +737,20 @@ static u64 ad4691_cnv_burst_period_ns(struct ad4691_state *st,
 	osc_freq = ad4691_int_osc_val[osc_idx];
 	conv_time_ns = div64_u64((u64)(n_active + 1) * NSEC_PER_SEC, osc_freq);
 
-	spi_bits = (u64)n_active * 32 + 24;
+	if (is_offload) {
+		/*
+		 * Offload SPI sequence per trigger: n_active AVG_IN reads
+		 * (4 B each) + STATE_RESET (4 B).
+		 */
+		spi_bits = (u64)(n_active + 1) * 32;
+	} else {
+		/*
+		 * Non-offload sequence per trigger: n_active AVG_IN reads
+		 * (4 B: 2 cmd + 2 data each) + STATE_RESET (3 B).
+		 */
+		spi_bits = (u64)n_active * 32 + 24;
+	}
+
 	spi_time_ns = div64_u64(spi_bits * NSEC_PER_SEC, st->spi->max_speed_hz);
 
 	/* 50% margin on SPI time absorbs OS scheduling jitter. */
@@ -662,7 +792,9 @@ static int ad4691_pwm_get(struct spi_device *spi, struct ad4691_state *st)
 		 * transfer time. Use worst-case channel count here; the period
 		 * is refined at buffer enable time when the active count is known.
 		 */
-		u64 period_ns = ad4691_cnv_burst_period_ns(st, st->chip->num_channels);
+		u64 period_ns = ad4691_cnv_burst_period_ns(st,
+							    st->chip->num_channels,
+							    false);
 		int pwm_freq = (int)max(1ULL, div64_u64(NSEC_PER_SEC, period_ns));
 
 		return __ad4691_set_sampling_freq(st, pwm_freq);
@@ -683,6 +815,26 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
 	mutex_lock(&st->lock);
 	switch (st->adc_mode) {
 	case AD4691_MANUAL_MODE:
+		/* For offload mode, validate and store frequency for periodic trigger */
+		if (st->offload) {
+			struct spi_offload_trigger_config config = {
+				.type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+				.periodic = {
+					.frequency_hz = freq,
+				},
+			};
+
+			ret = spi_offload_trigger_validate(st->offload_trigger_periodic,
+							   &config);
+			if (ret)
+				goto exit;
+
+			st->offload_trigger_hz = config.periodic.frequency_hz;
+			ret = 0;
+			goto exit;
+		}
+
+		/* Non-offload: update hrtimer sampling period */
 		if (!freq || freq > st->chip->max_rate) {
 			ret = -EINVAL;
 			goto exit;
@@ -719,7 +871,9 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
 		 * count. The exact period is refined at buffer enable time when
 		 * the active channel count is known.
 		 */
-		period_ns = ad4691_cnv_burst_period_ns(st, st->chip->num_channels);
+		period_ns = ad4691_cnv_burst_period_ns(st,
+							st->chip->num_channels,
+							false);
 		pwm_freq = (int)max(1ULL, div64_u64(NSEC_PER_SEC, period_ns));
 		ret = __ad4691_set_sampling_freq(st, pwm_freq);
 
@@ -839,6 +993,13 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
 
 			/*
 			 * Wait for conversion to complete using a timed delay.
+			 * An interrupt-driven approach is not used for single-shot
+			 * reads: the DATA_READY IRQ is registered only in the
+			 * triggered buffer path, and offload configurations route
+			 * DATA_READY to the SPI engine, not to a CPU interrupt.
+			 * Using usleep_range keeps the driver simple and correct
+			 * across all configurations.
+			 *
 			 * CNV_CLOCK_MODE conversion time is bounded by
 			 * AD4691_MAX_CONV_PERIOD_US. All other modes are driven by
 			 * the internal oscillator; two oscillator periods cover a
@@ -1072,6 +1233,294 @@ static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
 	.postdisable = &ad4691_buffer_postdisable,
 };
 
+static int ad4691_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct spi_offload_trigger_config config = { };
+	struct spi_offload_trigger *trigger;
+	struct spi_transfer *xfer = st->offload_xfer;
+	int ret, num_xfers = 0;
+	int active_chans[16];
+	unsigned int bit;
+	int n_active = 0;
+	int i;
+
+	memset(xfer, 0, sizeof(st->offload_xfer));
+
+	/* Collect active channels in scan order */
+	iio_for_each_active_channel(indio_dev, bit)
+		active_chans[n_active++] = bit;
+
+	/*
+	 * MANUAL_MODE uses a periodic (PWM) trigger and reads directly from the ADC.
+	 * All other modes use the DATA_READY trigger and read from accumulators.
+	 */
+	if (st->adc_mode == AD4691_MANUAL_MODE) {
+		config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
+		config.periodic.frequency_hz = st->offload_trigger_hz;
+		trigger = st->offload_trigger_periodic;
+		if (!trigger)
+			return -EINVAL;
+	} else {
+		if (st->adc_mode != AD4691_AUTONOMOUS_MODE)
+			ret = regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
+					   AD4691_DATA_READY);
+		else
+			ret = regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
+					   AD4691_ADC_BUSY);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+					   AD4691_STATE_RESET_ALL);
+		if (ret)
+			return ret;
+
+		/* Configure accumulator masks - 0 = enabled, 1 = masked */
+		ret = regmap_write(st->regmap, AD4691_ACC_MASK1_REG,
+				       ~(*indio_dev->active_scan_mask) & 0xFF);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(st->regmap, AD4691_ACC_MASK2_REG,
+				       ~(*indio_dev->active_scan_mask >> 8) & 0xFF);
+		if (ret)
+			return ret;
+
+		/* Configure sequencer with active channels */
+		ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+				       *indio_dev->active_scan_mask);
+		if (ret)
+			return ret;
+
+		/* Configure accumulator count limit for each active channel */
+		iio_for_each_active_channel(indio_dev, bit) {
+			ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
+					       AD4691_ACC_COUNT_VAL);
+			if (ret)
+				return ret;
+		}
+
+		config.type = SPI_OFFLOAD_TRIGGER_DATA_READY;
+		trigger = st->offload_trigger;
+	}
+
+	switch (st->adc_mode) {
+	case AD4691_CNV_CLOCK_MODE:
+	case AD4691_CNV_BURST_MODE:
+	case AD4691_AUTONOMOUS_MODE:
+	case AD4691_SPI_BURST_MODE:
+		/*
+		 * AUTONOMOUS mode: must stop conversion before reading.
+		 * Sequence: CONV_STOP -> read accumulators -> STATE_RESET -> CONV_START
+		 */
+		if (st->adc_mode == AD4691_AUTONOMOUS_MODE) {
+			/*
+			 * With bits_per_word=32, SPI engine reads native u32
+			 * and transmits MSB first. No byte-swap needed.
+			 */
+			st->offload_tx_conv_stop = (AD4691_OSC_EN_REG >> 8) << 24 |
+				(AD4691_OSC_EN_REG & 0xFF) << 16 |
+				0x00 << 8;  /* CONV_START = 0 to stop */
+			xfer[num_xfers].tx_buf = &st->offload_tx_conv_stop;
+			xfer[num_xfers].len = 4;
+			xfer[num_xfers].bits_per_word = 32;
+			xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+			xfer[num_xfers].cs_change = 1;
+			num_xfers++;
+		}
+
+		/*
+		 * Single transfer per channel: 2-byte cmd + 2-byte data = 4 bytes
+		 * (one 32-bit SPI Engine DMA word).
+		 *
+		 * AVG_IN registers are used instead of ACC_IN. See the
+		 * AD4691_OFFLOAD_CHANNEL macro for a detailed explanation.
+		 *
+		 * RX word layout (big-endian): [cmd_hi, cmd_lo, d_hi, d_lo]
+		 */
+		for (i = 0; i < n_active; i++) {
+			unsigned int reg;
+			int ch = active_chans[i];
+
+			reg = AD4691_AVG_IN(ch);
+			st->offload_tx_cmd[ch] =
+				((reg >> 8) | 0x80) << 24 |
+				(reg & 0xFF) << 16;
+			xfer[num_xfers].tx_buf = &st->offload_tx_cmd[ch];
+			xfer[num_xfers].len = 4;
+			xfer[num_xfers].bits_per_word = 32;
+			xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+			xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+			xfer[num_xfers].cs_change = 1;
+			num_xfers++;
+		}
+
+		/*
+		 * State reset: clear accumulator so DATA_READY can fire again.
+		 *
+		 * With bits_per_word=32, SPI engine reads native u32
+		 * and transmits MSB first. No byte-swap needed.
+		 *
+		 * The device uses address-descending mode when streaming, so
+		 * the 4th byte is written to the OSC_EN register. In AUTONOMOUS
+		 * and SPI_BURST modes we want OSC_EN re-asserted, therefore set
+		 * the 4th byte to 0x01 for those modes.
+		 */
+		if (st->adc_mode == AD4691_AUTONOMOUS_MODE ||
+		    st->adc_mode == AD4691_SPI_BURST_MODE) {
+			st->offload_tx_reset = ((AD4691_STATE_RESET_REG >> 8) << 24) |
+				       ((AD4691_STATE_RESET_REG & 0xFF) << 16) |
+				       (0x01 << 8) | 0x01;
+		} else {
+			st->offload_tx_reset = ((AD4691_STATE_RESET_REG >> 8) << 24) |
+				       ((AD4691_STATE_RESET_REG & 0xFF) << 16) |
+				       (0x01 << 8);
+		}
+
+		xfer[num_xfers].tx_buf = &st->offload_tx_reset;
+		xfer[num_xfers].len = 4;
+		xfer[num_xfers].bits_per_word = 32;
+		xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+		xfer[num_xfers].cs_change = 0;
+		num_xfers++;
+
+		break;
+
+	case AD4691_MANUAL_MODE:
+		/*
+		 * Manual mode with CNV tied to CS: Each CS toggle triggers a
+		 * conversion AND reads the previous conversion result (pipeline).
+		 */
+		for (i = 0; i < n_active; i++) {
+			st->offload_tx_cmd[num_xfers] = AD4691_ADC_CHAN(active_chans[i]) << 24;
+			xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
+			xfer[num_xfers].len = 4;
+			xfer[num_xfers].bits_per_word = 32;
+			xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+			xfer[num_xfers].cs_change = 1;
+			xfer[num_xfers].cs_change_delay.value = 1000;
+			xfer[num_xfers].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+			/* First transfer RX is garbage - don't capture it */
+			if (num_xfers)
+				xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+			num_xfers++;
+		}
+
+		/* Final NOOP to flush pipeline and get last channel's data */
+		st->offload_tx_cmd[num_xfers] = AD4691_NOOP << 24;
+		xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
+		xfer[num_xfers].len = 4;
+		xfer[num_xfers].bits_per_word = 32;
+		xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+		xfer[num_xfers].cs_change = 0;
+		xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+		num_xfers++;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (num_xfers == 0)
+		return -EINVAL;
+
+	/*
+	 * For MANUAL_MODE, validate that the trigger frequency is low enough
+	 * for all SPI transfers to complete. Each transfer is 32 bits.
+	 * Add 50% margin for CS setup/hold and other overhead.
+	 */
+	if (st->adc_mode == AD4691_MANUAL_MODE) {
+		u64 min_period_ns;
+		u64 trigger_period_ns;
+
+		/* Time for all transfers in nanoseconds, with 50% overhead margin */
+		min_period_ns = div64_u64((u64)num_xfers * AD4691_OFFLOAD_BITS_PER_WORD *
+					  NSEC_PER_SEC * 3,
+					  st->spi->max_speed_hz * 2);
+
+		trigger_period_ns = div64_u64(NSEC_PER_SEC, st->offload_trigger_hz);
+
+		if (trigger_period_ns < min_period_ns)
+			return -EINVAL;
+	}
+
+	spi_message_init_with_transfers(&st->offload_msg, xfer, num_xfers);
+	st->offload_msg.offload = st->offload;
+
+	ret = spi_optimize_message(st->spi, &st->offload_msg);
+	if (ret)
+		return ret;
+
+	/*
+	 * Start conversions before enabling the trigger for all non-MANUAL modes.
+	 * If the trigger is enabled first, the SPI engine blocks waiting for
+	 * DATA_READY, and any subsequent SPI write times out.
+	 *
+	 * MANUAL_MODE: CNV is tied to CS; conversion starts with each transfer.
+	 * CNV_BURST_MODE: cnv_period updated above; PWM starts conversions.
+	 * AUTONOMOUS_MODE: OSC_EN=1 written here; DATA_READY fires when
+	 * accumulation completes and triggers the SPI engine offload sequence.
+	 */
+	if (st->adc_mode != AD4691_MANUAL_MODE) {
+		if (st->adc_mode == AD4691_CNV_BURST_MODE)
+			st->cnv_period =
+				ad4691_cnv_burst_period_ns(st, n_active, true);
+		ret = ad4691_sampling_enable(st, true);
+		if (ret)
+			goto err_unoptimize_message;
+	}
+
+	ret = spi_offload_trigger_enable(st->offload, trigger, &config);
+	if (ret)
+		goto err_sampling_disable;
+
+	return 0;
+
+err_sampling_disable:
+	ad4691_sampling_enable(st, false);
+err_unoptimize_message:
+	spi_unoptimize_message(&st->offload_msg);
+	return ret;
+}
+
+static int ad4691_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ad4691_state *st = iio_priv(indio_dev);
+	struct spi_offload_trigger *trigger;
+	int ret = 0, tmp;
+
+	trigger = (st->adc_mode == AD4691_MANUAL_MODE) ?
+		  st->offload_trigger_periodic : st->offload_trigger;
+
+	spi_offload_trigger_disable(st->offload, trigger);
+	spi_unoptimize_message(&st->offload_msg);
+
+	/* Stop conversions and reset sequencer state (not needed for MANUAL_MODE) */
+	if (st->adc_mode != AD4691_MANUAL_MODE) {
+		tmp = ad4691_sampling_enable(st, false);
+		if (!ret)
+			ret = tmp;
+
+		tmp = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+				   AD4691_SEQ_ALL_CHANNELS_OFF);
+		if (!ret)
+			ret = tmp;
+
+		tmp = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+				   AD4691_STATE_RESET_ALL);
+		if (!ret)
+			ret = tmp;
+	}
+
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_offload_buffer_setup_ops = {
+	.postenable = &ad4691_offload_buffer_postenable,
+	.predisable = &ad4691_offload_buffer_predisable,
+};
+
 static irqreturn_t ad4691_irq(int irq, void *private)
 {
 	struct iio_dev *indio_dev = private;
@@ -1353,10 +1802,17 @@ static void ad4691_setup_channels(struct iio_dev *indio_dev,
 				  struct ad4691_state *st)
 {
 	if (st->adc_mode == AD4691_MANUAL_MODE) {
-		if (st->chip->num_channels == 8)
-			indio_dev->channels = ad4693_manual_channels;
-		else
-			indio_dev->channels = ad4691_manual_channels;
+		if (st->offload) {
+			if (st->chip->num_channels == 8)
+				indio_dev->channels = ad4693_manual_offload_channels;
+			else
+				indio_dev->channels = ad4691_manual_offload_channels;
+		} else {
+			if (st->chip->num_channels == 8)
+				indio_dev->channels = ad4693_manual_channels;
+			else
+				indio_dev->channels = ad4691_manual_channels;
+		}
 	} else {
 		indio_dev->channels = st->chip->channels;
 	}
@@ -1364,6 +1820,54 @@ static void ad4691_setup_channels(struct iio_dev *indio_dev,
 	indio_dev->num_channels = st->chip->num_channels;
 }
 
+static int ad4691_setup_offload(struct iio_dev *indio_dev,
+				struct ad4691_state *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct dma_chan *rx_dma;
+	int ret;
+
+	if (st->adc_mode == AD4691_MANUAL_MODE) {
+		st->offload_trigger_periodic = devm_spi_offload_trigger_get(dev,
+				st->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
+		if (IS_ERR(st->offload_trigger_periodic))
+			return dev_err_probe(dev,
+				PTR_ERR(st->offload_trigger_periodic),
+				"failed to get periodic offload trigger\n");
+
+		st->offload_trigger_hz = AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
+								      st->spi->max_speed_hz);
+	} else {
+		struct spi_offload_trigger_info trigger_info = {
+			.fwnode = dev_fwnode(dev),
+			.ops = &ad4691_offload_trigger_ops,
+			.priv = st,
+		};
+
+		ret = devm_spi_offload_trigger_register(dev, &trigger_info);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "failed to register offload trigger\n");
+
+		st->offload_trigger = devm_spi_offload_trigger_get(dev,
+				st->offload, SPI_OFFLOAD_TRIGGER_DATA_READY);
+		if (IS_ERR(st->offload_trigger))
+			return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+					     "failed to get offload trigger\n");
+	}
+
+	rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
+	if (IS_ERR(rx_dma))
+		return dev_err_probe(dev, PTR_ERR(rx_dma),
+				     "failed to get offload RX DMA\n");
+
+	indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_HARDWARE;
+	indio_dev->setup_ops = &ad4691_offload_buffer_setup_ops;
+
+	return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
+			rx_dma, IIO_BUFFER_DIRECTION_IN);
+}
+
 static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
 					 struct ad4691_state *st)
 {
@@ -1455,6 +1959,14 @@ static int ad4691_probe(struct spi_device *spi)
 		return dev_err_probe(dev, PTR_ERR(st->regmap),
 				     "Failed to initialize regmap\n");
 
+	st->offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
+	if (IS_ERR(st->offload)) {
+		if (PTR_ERR(st->offload) != -ENODEV)
+			return dev_err_probe(dev, PTR_ERR(st->offload),
+					     "failed to get SPI offload\n");
+		st->offload = NULL;
+	}
+
 	st->chip = spi_get_device_match_data(spi);
 	if (!st->chip) {
 		st->chip = (void *)spi_get_device_id(spi)->driver_data;
@@ -1481,9 +1993,15 @@ static int ad4691_probe(struct spi_device *spi)
 
 	ad4691_setup_channels(indio_dev, st);
 
-	ret = ad4691_setup_triggered_buffer(indio_dev, st);
-	if (ret)
-		return ret;
+	if (st->offload) {
+		ret = ad4691_setup_offload(indio_dev, st);
+		if (ret)
+			return ret;
+	} else {
+		ret = ad4691_setup_triggered_buffer(indio_dev, st);
+		if (ret)
+			return ret;
+	}
 
 	return devm_iio_device_register(dev, indio_dev);
 }
@@ -1510,3 +2028,4 @@ module_spi_driver(ad4691_driver);
 MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
 MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMA_BUFFER");

-- 
2.43.0
Re: [PATCH 4/4] iio: adc: ad4691: add SPI offload support
Posted by Jonathan Cameron 1 month ago
On Thu, 05 Mar 2026 14:23:30 +0200
Radu Sabau via B4 Relay <devnull+radu.sabau.analog.com@kernel.org> wrote:

> From: Radu Sabau <radu.sabau@analog.com>
> 
> Add SPI offload support to the AD4691 family driver to enable
> DMA-based RX stream acquisition. When an SPI offload is available,
> the driver switches to a pre-built SPI message with 32-bit transfers
> (4-byte frames aligned to DMA width) and registers a periodic
> offload trigger for autonomous, CPU-independent sampling.
> 
> The offload path implements its own buffer setup ops
> (ad4691_offload_buffer_postenable/predisable) that enable the
> offload trigger and wire the DMAengine buffer, while the existing
> software triggered buffer path is retained as a fallback for
> non-offload configurations.
> 
> Offload channel specs use a 32-bit storage/repeat with a 16-bit
> shift to extract ADC data from the MSBytes of each DMA word,
> matching the wire format in Manual Mode where SDO outputs ADC data
> directly without a command echo.
> 
> Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
> 
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>

Just a few really quick comments as I'm out of time for today.

J

> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index ab48f336e46c..7ec0a2555a4b 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -8,6 +8,7 @@
>  #include <linux/clk.h>
>  #include <linux/delay.h>
>  #include <linux/device.h>
> +#include <linux/dmaengine.h>
>  #include <linux/err.h>
>  #include <linux/gpio/consumer.h>
>  #include <linux/hrtimer.h>
> @@ -21,11 +22,15 @@
>  #include <linux/regmap.h>
>  #include <linux/regulator/consumer.h>
>  #include <linux/spi/spi.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/provider.h>

This is a provider and a consumer?

>  #include <linux/util_macros.h>
>  #include <linux/units.h>
>  #include <linux/unaligned.h>

> @@ -719,7 +871,9 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
>  		 * count. The exact period is refined at buffer enable time when
>  		 * the active channel count is known.
>  		 */
> -		period_ns = ad4691_cnv_burst_period_ns(st, st->chip->num_channels);
> +		period_ns = ad4691_cnv_burst_period_ns(st,
> +							st->chip->num_channels,
Check for reformatting like occurred for 1st two lines here and try and
tweak earlier patches to reduce the churn.
> +							false);

> +
> +static int ad4691_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct ad4691_state *st = iio_priv(indio_dev);
> +	struct spi_offload_trigger *trigger;
> +	int ret = 0, tmp;
> +
> +	trigger = (st->adc_mode == AD4691_MANUAL_MODE) ?
> +		  st->offload_trigger_periodic : st->offload_trigger;
> +
> +	spi_offload_trigger_disable(st->offload, trigger);
> +	spi_unoptimize_message(&st->offload_msg);
> +
> +	/* Stop conversions and reset sequencer state (not needed for MANUAL_MODE) */
> +	if (st->adc_mode != AD4691_MANUAL_MODE) {
> +		tmp = ad4691_sampling_enable(st, false);
> +		if (!ret)
> +			ret = tmp;
> +
> +		tmp = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> +				   AD4691_SEQ_ALL_CHANNELS_OFF);
> +		if (!ret)

If that failed, all bets are off.  May be better to just return the error.

> +			ret = tmp;
> +
> +		tmp = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> +				   AD4691_STATE_RESET_ALL);
> +		if (!ret)
> +			ret = tmp;
> +	}
> +
> +	return ret;
> +}

>  static irqreturn_t ad4691_irq(int irq, void *private)
>  {
>  	struct iio_dev *indio_dev = private;
> @@ -1353,10 +1802,17 @@ static void ad4691_setup_channels(struct iio_dev *indio_dev,
>  				  struct ad4691_state *st)
>  {
>  	if (st->adc_mode == AD4691_MANUAL_MODE) {
> -		if (st->chip->num_channels == 8)
> -			indio_dev->channels = ad4693_manual_channels;
> -		else
> -			indio_dev->channels = ad4691_manual_channels;
> +		if (st->offload) {

Add more pointers to channel arrays to the chip_info structures and just look them
up from there.

> +			if (st->chip->num_channels == 8)
> +				indio_dev->channels = ad4693_manual_offload_channels;
> +			else
> +				indio_dev->channels = ad4691_manual_offload_channels;
> +		} else {
> +			if (st->chip->num_channels == 8)
> +				indio_dev->channels = ad4693_manual_channels;
> +			else
> +				indio_dev->channels = ad4691_manual_channels;
> +		}