linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 00/15] Add SPI offload support to AD4030
@ 2025-08-30  0:39 Marcelo Schmitt
  2025-08-30  0:40 ` [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled Marcelo Schmitt
                   ` (15 more replies)
  0 siblings, 16 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:39 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

Hello,

This patch series add support for high sample rate data acquisition with AD4030
and similar devices. The last couple patches in the series add support for
ADAQ4216 and ADAQ4224 which are similar to AD4030, but have a PGA in front of
the ADC input.

Most of the code is for SPI offload support is based on work from Sergiu
Cuciurean, Nuno Sa, Axel Haslam, and Trevor Gamblin. Thus, the SPI offload and
related patches come with many co-developed-by tags. I also draw inspiration
from other drivers supporting SPI offload, many of them written by David Lechner.

The patches to the SPI subsystem are from Axel Haslam and I only signed them to
indicate I'm moving them forward.

The patches with updates to device tree are introduced before the patches that
use each specific change.

ADAQ PGA device tree doc and driver handling was inspired on AD7191 dt-binding
and driver.

The code was tested on a remote setup with ADAQ4216 connected to a ZedBoard
running Linux kernel 6.17.0-rc1 built from IIO tree testing branch.


Axel Haslam (2):
  spi: offload: types: add offset parameter
  spi: spi-offload-trigger-pwm: Use duty offset

Marcelo Schmitt (13):
  iio: adc: ad4030: Fix _scale for when oversampling is enabled
  dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
  Documentation: iio: ad4030: Add double PWM SPI offload doc
  dt-bindings: iio: adc: adi,ad4030: Add PWM
  iio: adc: ad4030: Add SPI offload support
  dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width
    option
  iio: adc: ad4030: Support multiple data lanes per channel
  dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode
  iio: adc: ad4030: Add clock mode option parse and setup
  dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate
  iio: adc: ad4030: Enable dual data rate
  dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
  iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224

 .../bindings/iio/adc/adi,ad4030.yaml          |  86 ++-
 Documentation/iio/ad4030.rst                  |  29 +
 drivers/iio/adc/Kconfig                       |   2 +
 drivers/iio/adc/ad4030.c                      | 704 +++++++++++++++++-
 drivers/spi/spi-offload-trigger-pwm.c         |   5 +-
 include/linux/spi/offload/types.h             |   1 +
 6 files changed, 792 insertions(+), 35 deletions(-)


base-commit: 91812d3843409c235f336f32f1c37ddc790f1e03
-- 
2.39.2


^ permalink raw reply	[flat|nested] 43+ messages in thread

* [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
@ 2025-08-30  0:40 ` Marcelo Schmitt
  2025-08-30  5:00   ` Andy Shevchenko
  2025-08-30 18:43   ` Jonathan Cameron
  2025-08-30  0:40 ` [PATCH 02/15] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
                   ` (14 subsequent siblings)
  15 siblings, 2 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:40 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

Previously, the AD4030 driver was using the number of scan realbits for the
voltage channel to derive the scale to millivolts. Though, when sample
averaging is enabled (oversampling_ratio > 1), the number of scan realbits
for the channel is set to 30 and doesn't match the amount of conversion
precision bits. Due to that, the calculated channel scale did not correctly
scale raw sample data to millivolt units in those cases. Use chip specific
precision bits to derive the correct channel _scale on every and all
channel configuration.

Fixes: dc78e71d7c15 ("iio: adc: ad4030: remove some duplicate code")
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
This was probalby buggy since 
commit 949abd1ca5a4 ("iio: adc: ad4030: add averaging support")
but I decided to set the fixes tag with dc78e71d7c15 because this patch will
not apply cleanly over 949abd1ca5a4.

 drivers/iio/adc/ad4030.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 1bc2f9a22470..82784593f976 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -394,7 +394,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
 	else
 		*val = st->vref_uv / MILLI;
 
-	*val2 = scan_type->realbits;
+	/*
+	 * Even though the sample data comes in a 30-bit chunk when the ADC
+	 * is averaging samples, the conversion precision is still 16-bit or
+	 * 24-bit depending on the device. Thus, instead of scan_type->realbits,
+	 * use chip specific precision bits to derive the correct scale to mV.
+	 */
+	*val2 = scan_type->realbits == 30 ? st->chip->precision_bits
+					  : scan_type->realbits;
 
 	return IIO_VAL_FRACTIONAL_LOG2;
 }
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 02/15] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
  2025-08-30  0:40 ` [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled Marcelo Schmitt
@ 2025-08-30  0:40 ` Marcelo Schmitt
  2025-08-30  0:41 ` [PATCH 03/15] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:40 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

AD4030 and similar devices all connect to the system as SPI peripherals.
Reference spi-peripheral-props so common SPI peripheral can be used from
ad4030 dt-binding.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Not sure if it is worth applying this patch since it doesn't seem to cause any
practical effect to the binding. Though, sending it in case it might be worth it.

 Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index 54e7349317b7..a8fee4062d0e 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -20,6 +20,8 @@ description: |
   * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4630-24_ad4632-24.pdf
   * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4630-16-4632-16.pdf
 
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
 properties:
   compatible:
     enum:
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 03/15] Documentation: iio: ad4030: Add double PWM SPI offload doc
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
  2025-08-30  0:40 ` [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled Marcelo Schmitt
  2025-08-30  0:40 ` [PATCH 02/15] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
@ 2025-08-30  0:41 ` Marcelo Schmitt
  2025-08-30 16:49   ` David Lechner
  2025-08-30  0:41 ` [PATCH 04/15] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
                   ` (12 subsequent siblings)
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:41 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

Document double PWM setup SPI offload wiring schema.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 Documentation/iio/ad4030.rst | 29 +++++++++++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/Documentation/iio/ad4030.rst b/Documentation/iio/ad4030.rst
index b57424b650a8..dc3ac253ef66 100644
--- a/Documentation/iio/ad4030.rst
+++ b/Documentation/iio/ad4030.rst
@@ -92,6 +92,35 @@ Interleaved mode
 In this mode, both channels conversion results are bit interleaved one SDO line.
 As such the wiring is the same as `One lane mode`_.
 
+SPI offload wiring
+^^^^^^^^^^^^^^^^^^
+
+.. code-block::
+
+    +-------------+         +-------------+
+    |         CNV |<-----+--| GPIO        |
+    |             |      +--| PWM1        |
+    |             |         |             |
+    |             |      +--| PWM0        |
+    |             |      |  +-------------+
+    |             |      +->| TRIGGER     |
+    |          CS |<--------| CS          |
+    |             |         |             |
+    |     ADC     |         |     SPI     |
+    |             |         |             |
+    |         SDI |<--------| SDO         |
+    |         SDO |-------->| SDI         |
+    |        SCLK |<--------| SCLK        |
+    +-------------+         +-------------+
+
+In this mode, both the ``cnv-gpios`` and a ``pwms`` properties are required.
+The ``pwms`` property specifies the PWM that is connected to the ADC CNV pin.
+The SPI offload will have a ``trigger-sources`` property to indicate the SPI
+offload (PWM) trigger source. The IIO device driver synchronizes the PWMs to do
+ADC transfer zone 2 data capture.
+
+.. seealso:: `SPI offload support`_
+
 SPI Clock mode
 --------------
 
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 04/15] dt-bindings: iio: adc: adi,ad4030: Add PWM
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (2 preceding siblings ...)
  2025-08-30  0:41 ` [PATCH 03/15] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
@ 2025-08-30  0:41 ` Marcelo Schmitt
  2025-08-30  0:42 ` [PATCH 05/15] spi: offload: types: add offset parameter Marcelo Schmitt
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:41 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

In setups designed for high speed data rate capture, a PWM is used to
generate the CNV signal that issues data captures from the ADC. Document
the use of a PWM for AD4030 and similar devices.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index a8fee4062d0e..564b6f67a96e 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -64,6 +64,10 @@ properties:
       The Reset Input (/RST). Used for asynchronous device reset.
     maxItems: 1
 
+  pwms:
+    description: PWM signal connected to the CNV pin.
+    maxItems: 1
+
   interrupts:
     description:
       The BUSY pin is used to signal that the conversions results are available
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 05/15] spi: offload: types: add offset parameter
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (3 preceding siblings ...)
  2025-08-30  0:41 ` [PATCH 04/15] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
@ 2025-08-30  0:42 ` Marcelo Schmitt
  2025-08-30  5:01   ` Andy Shevchenko
  2025-08-30  0:42 ` [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset Marcelo Schmitt
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:42 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: Axel Haslam, jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner,
	andy, corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, marcelo.schmitt1

From: Axel Haslam <ahaslam@baylibre.com>

Add an offset parameter that can be passed in the periodic trigger.
This is useful for example when adc drivers implement a separate periodic
signal to trigger conversion and need offload to read the result with
some delay.

Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 include/linux/spi/offload/types.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/linux/spi/offload/types.h b/include/linux/spi/offload/types.h
index 6f7892347871..0170fd1f42e5 100644
--- a/include/linux/spi/offload/types.h
+++ b/include/linux/spi/offload/types.h
@@ -59,6 +59,7 @@ enum spi_offload_trigger_type {
 
 struct spi_offload_trigger_periodic {
 	u64 frequency_hz;
+	u64 offset_ns;
 };
 
 struct spi_offload_trigger_config {
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (4 preceding siblings ...)
  2025-08-30  0:42 ` [PATCH 05/15] spi: offload: types: add offset parameter Marcelo Schmitt
@ 2025-08-30  0:42 ` Marcelo Schmitt
  2025-08-30  5:02   ` Andy Shevchenko
  2025-08-30 16:41   ` David Lechner
  2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
                   ` (9 subsequent siblings)
  15 siblings, 2 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:42 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: Axel Haslam, jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner,
	andy, corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, marcelo.schmitt1

From: Axel Haslam <ahaslam@baylibre.com>

Pass the duty offset to the waveform pwm.

Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 drivers/spi/spi-offload-trigger-pwm.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/spi/spi-offload-trigger-pwm.c b/drivers/spi/spi-offload-trigger-pwm.c
index 805ed41560df..8413aeb3689d 100644
--- a/drivers/spi/spi-offload-trigger-pwm.c
+++ b/drivers/spi/spi-offload-trigger-pwm.c
@@ -51,13 +51,13 @@ static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger *trigger,
 	wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
 	/* REVISIT: 50% duty-cycle for now - may add config parameter later */
 	wf.duty_length_ns = wf.period_length_ns / 2;
-
+	wf.duty_offset_ns = periodic->offset_ns;
 	ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
 	if (ret < 0)
 		return ret;
 
 	periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns);
-
+	periodic->offset_ns = wf.duty_offset_ns;
 	return 0;
 }
 
@@ -77,6 +77,7 @@ static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger,
 	wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
 	/* REVISIT: 50% duty-cycle for now - may add config parameter later */
 	wf.duty_length_ns = wf.period_length_ns / 2;
+	wf.duty_offset_ns = periodic->offset_ns;
 
 	return pwm_set_waveform_might_sleep(st->pwm, &wf, false);
 }
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (5 preceding siblings ...)
  2025-08-30  0:42 ` [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset Marcelo Schmitt
@ 2025-08-30  0:42 ` Marcelo Schmitt
  2025-08-30  7:36   ` Andy Shevchenko
                     ` (3 more replies)
  2025-08-30  0:43 ` [PATCH 08/15] dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width option Marcelo Schmitt
                   ` (8 subsequent siblings)
  15 siblings, 4 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:42 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, tgamblin,
	marcelo.schmitt1

AD4030 and similar ADCs can capture data at sample rates up to 2 mega
samples per second (MSPS). Not all SPI controllers are able to achieve
such high throughputs and even when the controller is fast enough to run
transfers at the required speed, it may be costly to the CPU to handle
transfer data at such high sample rates.  Add SPI offload support for
AD4030 and similar ADCs so to enable ADC data capture at maximum sample
rates.

Cc: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Cc: Nuno Sa <nuno.sa@analog.com>
Cc: Trevor Gamblin <tgamblin@baylibre.com>
Cc: Axel Haslam <ahaslam@baylibre.com>
Cc: David Lechner <dlechner@baylibre.com>
Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Co-developed-by: Nuno Sa <nuno.sa@analog.com>
Signed-off-by: Nuno Sa <nuno.sa@analog.com>
Co-developed-by: Trevor Gamblin <tgamblin@baylibre.com>
Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
Co-developed-by: Axel Haslam <ahaslam@baylibre.com>
Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Most of the code in this patch is based on work from Sergiu Cuciurean, Nuno Sa,
Axel Haslam, and Trevor Gamblin, hence the many co-developed-by tags. I also
draw inspiration from other drivers supporting SPI offload, many of them written
by David Lechner.

 drivers/iio/adc/Kconfig  |   2 +
 drivers/iio/adc/ad4030.c | 400 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 378 insertions(+), 24 deletions(-)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 6de2abad0197..7cfbc07e7f77 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -49,6 +49,8 @@ config AD4030
 	depends on GPIOLIB
 	select REGMAP
 	select IIO_BUFFER
+	select IIO_BUFFER_DMA
+	select IIO_BUFFER_DMAENGINE
 	select IIO_TRIGGERED_BUFFER
 	help
 	  Say yes here to build support for Analog Devices AD4030 and AD4630 high speed
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 82784593f976..68f76432dbfd 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -15,11 +15,15 @@
 
 #include <linux/bitfield.h>
 #include <linux/clk.h>
+#include <linux/dmaengine.h>
+#include <linux/iio/buffer-dmaengine.h>
 #include <linux/iio/iio.h>
 #include <linux/iio/trigger_consumer.h>
 #include <linux/iio/triggered_buffer.h>
+#include <linux/pwm.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
+#include <linux/spi/offload/consumer.h>
 #include <linux/spi/spi.h>
 #include <linux/unaligned.h>
 #include <linux/units.h>
@@ -111,6 +115,8 @@
 #define AD4632_TCYC_NS			2000
 #define AD4632_TCYC_ADJUSTED_NS		(AD4632_TCYC_NS - AD4030_TCNVL_NS)
 #define AD4030_TRESET_COM_DELAY_MS	750
+/* Datasheet says 9.8ns, so use the closest integer value */
+#define AD4030_TQUIET_CNV_DELAY_NS	10
 
 enum ad4030_out_mode {
 	AD4030_OUT_DATA_MD_DIFF,
@@ -120,7 +126,7 @@ enum ad4030_out_mode {
 	AD4030_OUT_DATA_MD_32_PATTERN,
 };
 
-enum {
+enum ad4030_lane_mode {
 	AD4030_LANE_MD_1_PER_CH,
 	AD4030_LANE_MD_2_PER_CH,
 	AD4030_LANE_MD_4_PER_CH,
@@ -130,17 +136,21 @@ enum {
 enum {
 	AD4030_SCAN_TYPE_NORMAL,
 	AD4030_SCAN_TYPE_AVG,
+	AD4030_OFFLOAD_SCAN_TYPE_NORMAL,
+	AD4030_OFFLOAD_SCAN_TYPE_AVG,
 };
 
 struct ad4030_chip_info {
 	const char *name;
 	const unsigned long *available_masks;
 	const struct iio_chan_spec channels[AD4030_MAX_IIO_CHANNEL_NB];
+	const struct iio_chan_spec offload_channels[AD4030_MAX_IIO_CHANNEL_NB];
 	u8 grade;
 	u8 precision_bits;
 	/* Number of hardware channels */
 	int num_voltage_inputs;
 	unsigned int tcyc_ns;
+	unsigned int max_sample_rate_hz;
 };
 
 struct ad4030_state {
@@ -148,11 +158,20 @@ struct ad4030_state {
 	struct regmap *regmap;
 	const struct ad4030_chip_info *chip;
 	struct gpio_desc *cnv_gpio;
+	struct pwm_device *conv_trigger;
+	struct pwm_waveform conv_wf;
 	int vref_uv;
 	int vio_uv;
 	int offset_avail[3];
 	unsigned int avg_log2;
 	enum ad4030_out_mode mode;
+	enum ad4030_lane_mode lane_mode;
+	/* offload sampling spi message */
+	struct spi_transfer offload_xfer;
+	struct spi_message offload_msg;
+	struct spi_offload *offload;
+	struct spi_offload_trigger *offload_trigger;
+	struct spi_offload_trigger_config offload_trigger_config;
 
 	/*
 	 * DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -209,12 +228,13 @@ struct ad4030_state {
  * - voltage0-voltage1
  * - voltage2-voltage3
  */
-#define AD4030_CHAN_DIFF(_idx, _scan_type) {				\
+#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload) {		\
 	.info_mask_shared_by_all =					\
 		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),			\
 	.info_mask_shared_by_all_available =				\
 		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),			\
 	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |		\
+		(_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) |		\
 		BIT(IIO_CHAN_INFO_CALIBSCALE) |				\
 		BIT(IIO_CHAN_INFO_CALIBBIAS) |				\
 		BIT(IIO_CHAN_INFO_RAW),					\
@@ -232,12 +252,23 @@ struct ad4030_state {
 	.num_ext_scan_type = ARRAY_SIZE(_scan_type),			\
 }
 
+#define AD4030_CHAN_DIFF(_idx, _scan_type)				\
+	__AD4030_CHAN_DIFF(_idx, _scan_type, 0)
+
+#define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
+	__AD4030_CHAN_DIFF(_idx, _scan_type, 1)
+
 static const int ad4030_average_modes[] = {
 	1, 2, 4, 8, 16, 32, 64, 128,
 	256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
 	65536,
 };
 
+static const struct spi_offload_config ad4030_offload_config = {
+	.capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+			    SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
 static int ad4030_enter_config_mode(struct ad4030_state *st)
 {
 	st->tx_data[0] = AD4030_REG_ACCESS;
@@ -385,7 +416,7 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
 	struct ad4030_state *st = iio_priv(indio_dev);
 	const struct iio_scan_type *scan_type;
 
-	scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels);
+	scan_type = iio_get_current_scan_type(indio_dev, chan);
 	if (IS_ERR(scan_type))
 		return PTR_ERR(scan_type);
 
@@ -458,6 +489,96 @@ static int ad4030_get_chan_calibbias(struct iio_dev *indio_dev,
 	}
 }
 
+static void ad4030_get_sampling_freq(const struct ad4030_state *st, int *freq)
+{
+	*freq = DIV_ROUND_CLOSEST_ULL(NANO, st->conv_wf.period_length_ns);
+}
+
+static int __ad4030_set_sampling_freq(struct ad4030_state *st, unsigned int freq)
+{
+	struct spi_offload_trigger_config *config = &st->offload_trigger_config;
+	struct pwm_waveform conv_wf = { };
+	u64 offload_period_ns;
+	u64 offload_offset_ns;
+	u32 mode;
+	int ret;
+	u64 target = AD4030_TCNVH_NS;
+
+	conv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
+	/*
+	 * The datasheet lists a minimum time of 9.8 ns, but no maximum. If the
+	 * rounded PWM's value is less than 10, increase the target value by 10
+	 * and attempt to round the waveform again, until the value is at least
+	 * 10 ns. Use a separate variable to represent the target in case the
+	 * rounding is severe enough to keep putting the first few results under
+	 * the minimum 10ns condition checked by the while loop.
+	 */
+	do {
+		conv_wf.duty_length_ns = target;
+		ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
+		if (ret)
+			return ret;
+		target += 10;
+	} while (conv_wf.duty_length_ns < 10);
+
+	offload_period_ns = conv_wf.period_length_ns;
+
+	ret = regmap_read(st->regmap, AD4030_REG_MODES, &mode);
+	if (ret)
+		return ret;
+	if (FIELD_GET(AD4030_REG_MODES_MASK_OUT_DATA_MODE, mode) == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF) {
+		u32 avg;
+
+		ret = regmap_read(st->regmap, AD4030_REG_AVG, &avg);
+		if (ret)
+			return ret;
+
+		offload_period_ns <<= FIELD_GET(AD4030_REG_AVG_MASK_AVG_VAL, avg);
+	}
+
+	config->periodic.frequency_hz =  DIV_ROUND_UP_ULL(NSEC_PER_SEC,
+							  offload_period_ns);
+
+	/*
+	 * The hardware does the capture on zone 2 (when spi trigger PWM
+	 * is used). This means that the spi trigger signal should happen at
+	 * tsync + tquiet_con_delay being tsync the conversion signal period
+	 * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
+	 *
+	 * The PWM waveform API only supports nanosecond resolution right now,
+	 * so round this setting to the closest available value.
+	 */
+	offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS;
+	do {
+		config->periodic.offset_ns = offload_offset_ns;
+		ret = spi_offload_trigger_validate(st->offload_trigger, config);
+		if (ret)
+			return ret;
+		offload_offset_ns += 10;
+
+	} while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
+
+	st->conv_wf = conv_wf;
+
+	return 0;
+}
+
+static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
+{
+	struct ad4030_state *st = iio_priv(indio_dev);
+	int ret;
+
+	if (PTR_ERR_OR_ZERO(st->offload))
+		return -EINVAL;
+
+	if (!freq || freq > st->chip->max_sample_rate_hz)
+		return -EINVAL;
+
+	ret = __ad4030_set_sampling_freq(st, freq);
+	iio_device_release_direct(indio_dev);
+
+	return ret;
+}
 static int ad4030_set_chan_calibscale(struct iio_dev *indio_dev,
 				      struct iio_chan_spec const *chan,
 				      int gain_int,
@@ -618,7 +739,7 @@ static int ad4030_conversion(struct iio_dev *indio_dev)
 	unsigned int i;
 	int ret;
 
-	scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels);
+	scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]);
 	if (IS_ERR(scan_type))
 		return PTR_ERR(scan_type);
 
@@ -774,6 +895,13 @@ static int ad4030_read_raw_dispatch(struct iio_dev *indio_dev,
 		*val = BIT(st->avg_log2);
 		return IIO_VAL_INT;
 
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		if (PTR_ERR_OR_ZERO(st->offload))
+			return -EINVAL;
+
+		ad4030_get_sampling_freq(st, val);
+		return IIO_VAL_INT;
+
 	default:
 		return -EINVAL;
 	}
@@ -814,6 +942,9 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
 	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
 		return ad4030_set_avg_frame_len(indio_dev, val);
 
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ad4030_set_sampling_freq(indio_dev, val);
+
 	default:
 		return -EINVAL;
 	}
@@ -868,7 +999,11 @@ static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev,
 {
 	struct ad4030_state *st = iio_priv(indio_dev);
 
-	return st->avg_log2 ? AD4030_SCAN_TYPE_AVG : AD4030_SCAN_TYPE_NORMAL;
+	if (PTR_ERR_OR_ZERO(st->offload))
+		return st->avg_log2 ? AD4030_SCAN_TYPE_AVG : AD4030_SCAN_TYPE_NORMAL;
+	else
+		return st->avg_log2 ? AD4030_OFFLOAD_SCAN_TYPE_AVG :
+				      AD4030_OFFLOAD_SCAN_TYPE_NORMAL;
 }
 
 static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
@@ -903,6 +1038,67 @@ static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = {
 	.validate_scan_mask = ad4030_validate_scan_mask,
 };
 
+static int ad4030_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+	struct ad4030_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = regmap_write(st->regmap, AD4030_REG_EXIT_CFG_MODE, BIT(0));
+	if (ret)
+		return ret;
+
+	st->offload_msg.offload = st->offload;
+	ret = spi_optimize_message(st->spi, &st->offload_msg);
+	if (ret < 0)
+		goto out_reset_mode;
+
+	ret = pwm_set_waveform_might_sleep(st->conv_trigger, &st->conv_wf, false);
+	if (ret)
+		goto out_unoptimize;
+
+	ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
+					 &st->offload_trigger_config);
+	if (ret)
+		goto out_pwm_disable;
+	return 0;
+out_pwm_disable:
+	pwm_disable(st->conv_trigger);
+out_unoptimize:
+	spi_unoptimize_message(&st->offload_msg);
+out_reset_mode:
+	/* reenter register configuration mode */
+	ret = ad4030_enter_config_mode(st);
+	if (ret)
+		dev_warn(&st->spi->dev,
+			 "couldn't reenter register configuration mode\n");
+	return ret;
+}
+
+static int ad4030_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ad4030_state *st = iio_priv(indio_dev);
+	int ret;
+
+	pwm_disable(st->conv_trigger);
+
+	spi_offload_trigger_disable(st->offload, st->offload_trigger);
+
+	spi_unoptimize_message(&st->offload_msg);
+
+	/* reenter register configuration mode */
+	ret = ad4030_enter_config_mode(st);
+	if (ret)
+		dev_warn(&st->spi->dev,
+			 "couldn't reenter register configuration mode\n");
+
+	return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4030_offload_buffer_setup_ops = {
+	.postenable = &ad4030_offload_buffer_postenable,
+	.predisable = &ad4030_offload_buffer_predisable,
+};
+
 static int ad4030_regulators_get(struct ad4030_state *st)
 {
 	struct device *dev = &st->spi->dev;
@@ -972,6 +1168,44 @@ static int ad4030_detect_chip_info(const struct ad4030_state *st)
 	return 0;
 }
 
+static int ad4030_pwm_get(struct ad4030_state *st)
+{
+	struct device *dev = &st->spi->dev;
+
+	st->conv_trigger = devm_pwm_get(dev, "cnv");
+	if (IS_ERR(st->conv_trigger))
+		return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
+				     "Failed to get cnv pwm\n");
+
+	/*
+	 * Preemptively disable the PWM, since we only want to enable it with
+	 * the buffer
+	 */
+	pwm_disable(st->conv_trigger);
+
+	return 0;
+}
+
+static void ad4030_prepare_offload_msg(struct ad4030_state *st)
+{
+	u8 data_width = st->chip->precision_bits;
+	u8 offload_bpw;
+
+	if (st->lane_mode == AD4030_LANE_MD_INTERLEAVED)
+		/*
+		 * This means all channels on 1 lane.
+		 */
+		offload_bpw = data_width * st->chip->num_voltage_inputs;
+	else
+		offload_bpw  = data_width;
+
+	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
+	st->offload_xfer.bits_per_word = offload_bpw;
+	st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(offload_bpw));
+	st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+	spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
+}
+
 static int ad4030_config(struct ad4030_state *st)
 {
 	int ret;
@@ -982,11 +1216,11 @@ static int ad4030_config(struct ad4030_state *st)
 	st->offset_avail[2] = BIT(st->chip->precision_bits - 1) - 1;
 
 	if (st->chip->num_voltage_inputs > 1)
-		reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE,
-				       AD4030_LANE_MD_INTERLEAVED);
+		st->lane_mode = AD4030_LANE_MD_INTERLEAVED;
 	else
-		reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE,
-				       AD4030_LANE_MD_1_PER_CH);
+		st->lane_mode = AD4030_LANE_MD_1_PER_CH;
+
+	reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE, st->lane_mode);
 
 	ret = regmap_write(st->regmap, AD4030_REG_MODES, reg_modes);
 	if (ret)
@@ -999,6 +1233,31 @@ static int ad4030_config(struct ad4030_state *st)
 	return 0;
 }
 
+static int ad4030_spi_offload_setup(struct iio_dev *indio_dev,
+				    struct ad4030_state *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct dma_chan *rx_dma;
+
+	indio_dev->setup_ops = &ad4030_offload_buffer_setup_ops;
+
+	st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload,
+							   SPI_OFFLOAD_TRIGGER_PERIODIC);
+	if (IS_ERR(st->offload_trigger))
+		return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+				     "failed to get offload trigger\n");
+
+	st->offload_trigger_config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
+
+	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");
+
+	return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
+							   IIO_BUFFER_DIRECTION_IN);
+}
+
 static int ad4030_probe(struct spi_device *spi)
 {
 	struct device *dev = &spi->dev;
@@ -1050,24 +1309,55 @@ static int ad4030_probe(struct spi_device *spi)
 		return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
 				     "Failed to get cnv gpio\n");
 
-	/*
-	 * One hardware channel is split in two software channels when using
-	 * common byte mode. Add one more channel for the timestamp.
-	 */
-	indio_dev->num_channels = 2 * st->chip->num_voltage_inputs + 1;
 	indio_dev->name = st->chip->name;
 	indio_dev->modes = INDIO_DIRECT_MODE;
 	indio_dev->info = &ad4030_iio_info;
-	indio_dev->channels = st->chip->channels;
-	indio_dev->available_scan_masks = st->chip->available_masks;
 
-	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
-					      iio_pollfunc_store_time,
-					      ad4030_trigger_handler,
-					      &ad4030_buffer_setup_ops);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Failed to setup triggered buffer\n");
+	st->offload = devm_spi_offload_get(dev, spi, &ad4030_offload_config);
+	ret = PTR_ERR_OR_ZERO(st->offload);
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to get offload\n");
+
+	/* Fall back to low speed usage when no SPI offload available. */
+	if (ret == -ENODEV) {
+		/*
+		 * One hardware channel is split in two software channels when
+		 * using common byte mode. Add one more channel for the timestamp.
+		 */
+		indio_dev->num_channels = 2 * st->chip->num_voltage_inputs + 1;
+		indio_dev->channels = st->chip->channels;
+		indio_dev->available_scan_masks = st->chip->available_masks;
+
+		ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+						      iio_pollfunc_store_time,
+						      ad4030_trigger_handler,
+						      &ad4030_buffer_setup_ops);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to setup triggered buffer\n");
+
+	} else {
+		/*
+		 * One hardware channel is split in two software channels when
+		 * using common byte mode. Offloaded SPI transfers can't support
+		 * software timestamp so no additional timestamp channel is added.
+		 */
+		indio_dev->num_channels = 2 * st->chip->num_voltage_inputs;
+		indio_dev->channels = st->chip->offload_channels;
+		indio_dev->available_scan_masks = st->chip->available_masks;
+		ret = ad4030_spi_offload_setup(indio_dev, st);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to setup SPI offload\n");
+
+		ret = ad4030_pwm_get(st);
+		if (ret)
+			return dev_err_probe(&spi->dev, ret,
+					     "Failed to get PWM: %d\n", ret);
+
+		ret = __ad4030_set_sampling_freq(st, st->chip->max_sample_rate_hz);
+		ad4030_prepare_offload_msg(st);
+	}
 
 	return devm_iio_device_register(dev, indio_dev);
 }
@@ -1103,6 +1393,20 @@ static const struct iio_scan_type ad4030_24_scan_types[] = {
 		.shift = 2,
 		.endianness = IIO_BE,
 	},
+	[AD4030_OFFLOAD_SCAN_TYPE_NORMAL] = {
+		.sign = 's',
+		.storagebits = 32,
+		.realbits = 24,
+		.shift = 0,
+		.endianness = IIO_CPU,
+	},
+	[AD4030_OFFLOAD_SCAN_TYPE_AVG] = {
+		.sign = 's',
+		.storagebits = 32,
+		.realbits = 30,
+		.shift = 2,
+		.endianness = IIO_CPU,
+	},
 };
 
 static const struct iio_scan_type ad4030_16_scan_types[] = {
@@ -1119,7 +1423,21 @@ static const struct iio_scan_type ad4030_16_scan_types[] = {
 		.realbits = 30,
 		.shift = 2,
 		.endianness = IIO_BE,
-	}
+	},
+	[AD4030_OFFLOAD_SCAN_TYPE_NORMAL] = {
+		.sign = 's',
+		.storagebits = 32,
+		.realbits = 16,
+		.shift = 0,
+		.endianness = IIO_CPU,
+	},
+	[AD4030_OFFLOAD_SCAN_TYPE_AVG] = {
+		.sign = 's',
+		.storagebits = 32,
+		.realbits = 30,
+		.shift = 2,
+		.endianness = IIO_CPU,
+	},
 };
 
 static const struct ad4030_chip_info ad4030_24_chip_info = {
@@ -1130,10 +1448,15 @@ static const struct ad4030_chip_info ad4030_24_chip_info = {
 		AD4030_CHAN_CMO(1, 0),
 		IIO_CHAN_SOFT_TIMESTAMP(2),
 	},
+	.offload_channels = {
+		AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
+		AD4030_CHAN_CMO(1, 0),
+	},
 	.grade = AD4030_REG_CHIP_GRADE_AD4030_24_GRADE,
 	.precision_bits = 24,
 	.num_voltage_inputs = 1,
 	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 2 * MEGA,
 };
 
 static const struct ad4030_chip_info ad4630_16_chip_info = {
@@ -1146,10 +1469,17 @@ static const struct ad4030_chip_info ad4630_16_chip_info = {
 		AD4030_CHAN_CMO(3, 1),
 		IIO_CHAN_SOFT_TIMESTAMP(4),
 	},
+	.offload_channels = {
+		AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_16_scan_types),
+		AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_scan_types),
+		AD4030_CHAN_CMO(2, 0),
+		AD4030_CHAN_CMO(3, 1),
+	},
 	.grade = AD4030_REG_CHIP_GRADE_AD4630_16_GRADE,
 	.precision_bits = 16,
 	.num_voltage_inputs = 2,
 	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 2 * MEGA,
 };
 
 static const struct ad4030_chip_info ad4630_24_chip_info = {
@@ -1162,10 +1492,17 @@ static const struct ad4030_chip_info ad4630_24_chip_info = {
 		AD4030_CHAN_CMO(3, 1),
 		IIO_CHAN_SOFT_TIMESTAMP(4),
 	},
+	.offload_channels = {
+		AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
+		AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_scan_types),
+		AD4030_CHAN_CMO(2, 0),
+		AD4030_CHAN_CMO(3, 1),
+	},
 	.grade = AD4030_REG_CHIP_GRADE_AD4630_24_GRADE,
 	.precision_bits = 24,
 	.num_voltage_inputs = 2,
 	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 2 * MEGA,
 };
 
 static const struct ad4030_chip_info ad4632_16_chip_info = {
@@ -1178,10 +1515,17 @@ static const struct ad4030_chip_info ad4632_16_chip_info = {
 		AD4030_CHAN_CMO(3, 1),
 		IIO_CHAN_SOFT_TIMESTAMP(4),
 	},
+	.offload_channels = {
+		AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_16_scan_types),
+		AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_scan_types),
+		AD4030_CHAN_CMO(2, 0),
+		AD4030_CHAN_CMO(3, 1),
+	},
 	.grade = AD4030_REG_CHIP_GRADE_AD4632_16_GRADE,
 	.precision_bits = 16,
 	.num_voltage_inputs = 2,
 	.tcyc_ns = AD4632_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 500 * KILO,
 };
 
 static const struct ad4030_chip_info ad4632_24_chip_info = {
@@ -1194,10 +1538,17 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
 		AD4030_CHAN_CMO(3, 1),
 		IIO_CHAN_SOFT_TIMESTAMP(4),
 	},
+	.offload_channels = {
+		AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
+		AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_scan_types),
+		AD4030_CHAN_CMO(2, 0),
+		AD4030_CHAN_CMO(3, 1),
+	},
 	.grade = AD4030_REG_CHIP_GRADE_AD4632_24_GRADE,
 	.precision_bits = 24,
 	.num_voltage_inputs = 2,
 	.tcyc_ns = AD4632_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 500 * KILO,
 };
 
 static const struct spi_device_id ad4030_id_table[] = {
@@ -1233,3 +1584,4 @@ module_spi_driver(ad4030_driver);
 MODULE_AUTHOR("Esteban Blanc <eblanc@baylibre.com>");
 MODULE_DESCRIPTION("Analog Devices AD4630 ADC family driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 08/15] dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width option
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (6 preceding siblings ...)
  2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
@ 2025-08-30  0:43 ` Marcelo Schmitt
  2025-08-30 17:01   ` David Lechner
  2025-08-30  0:43 ` [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel Marcelo Schmitt
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:43 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

AD4630 has two input channels and each of them can have it's data output in
4 dedicated lines, resulting in a total of 8 data lines used by the device.
Document the option that specifies the case where AD4630 and similar ADCs
provide data through 8 SPI lines.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index 564b6f67a96e..bee85087a7b2 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -39,7 +39,7 @@ properties:
     maximum: 102040816
 
   spi-rx-bus-width:
-    enum: [1, 2, 4]
+    enum: [1, 2, 4, 8]
 
   vdd-5v-supply: true
   vdd-1v8-supply: true
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (7 preceding siblings ...)
  2025-08-30  0:43 ` [PATCH 08/15] dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width option Marcelo Schmitt
@ 2025-08-30  0:43 ` Marcelo Schmitt
  2025-08-30  7:38   ` Andy Shevchenko
  2025-08-30 17:19   ` David Lechner
  2025-08-30  0:43 ` [PATCH 10/15] dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode Marcelo Schmitt
                   ` (6 subsequent siblings)
  15 siblings, 2 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:43 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, marcelo.schmitt1

AD4030 and similar chips can output ADC sample data through 1, 2, or 4
lines per channel. The number of SPI lines the device uses to output data
is specified in firmware. Parse SPI read bus width setting from firmware
and configure the device to use that amount of lines to output data.

Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 drivers/iio/adc/ad4030.c | 33 ++++++++++++++++++++++++++++++---
 1 file changed, 30 insertions(+), 3 deletions(-)

diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 68f76432dbfd..e6c1c9be1632 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -20,6 +20,7 @@
 #include <linux/iio/iio.h>
 #include <linux/iio/trigger_consumer.h>
 #include <linux/iio/triggered_buffer.h>
+#include <linux/log2.h>
 #include <linux/pwm.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
@@ -258,6 +259,10 @@ struct ad4030_state {
 #define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
 	__AD4030_CHAN_DIFF(_idx, _scan_type, 1)
 
+static const int ad4030_rx_bus_width[] = {
+	1, 2, 4, 8,
+};
+
 static const int ad4030_average_modes[] = {
 	1, 2, 4, 8, 16, 32, 64, 128,
 	256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
@@ -1197,7 +1202,7 @@ static void ad4030_prepare_offload_msg(struct ad4030_state *st)
 		 */
 		offload_bpw = data_width * st->chip->num_voltage_inputs;
 	else
-		offload_bpw  = data_width;
+		offload_bpw  = data_width / (1 << st->lane_mode);
 
 	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
 	st->offload_xfer.bits_per_word = offload_bpw;
@@ -1208,6 +1213,10 @@ static void ad4030_prepare_offload_msg(struct ad4030_state *st)
 
 static int ad4030_config(struct ad4030_state *st)
 {
+	struct device *dev = &st->spi->dev;
+	const char *propname;
+	u32 rx_bus_width;
+	unsigned int i;
 	int ret;
 	u8 reg_modes;
 
@@ -1215,10 +1224,28 @@ static int ad4030_config(struct ad4030_state *st)
 	st->offset_avail[1] = 1;
 	st->offset_avail[2] = BIT(st->chip->precision_bits - 1) - 1;
 
-	if (st->chip->num_voltage_inputs > 1)
+	/* Optional property specifying the number of lanes to read ADC data */
+	propname = "spi-rx-bus-width";
+	rx_bus_width = ad4030_rx_bus_width[0]; /* Default to 1 rx lane. */
+	device_property_read_u32(dev, propname, &rx_bus_width);
+	/* Check the rx bus width is valid */
+	for (i = 0; i < ARRAY_SIZE(ad4030_rx_bus_width); i++)
+		if (ad4030_rx_bus_width[i] == rx_bus_width)
+			break;
+
+	if (i >= ARRAY_SIZE(ad4030_rx_bus_width))
+		return dev_err_probe(dev, -EINVAL, "Invalid %s: %u\n",
+				     propname, rx_bus_width);
+
+	rx_bus_width = ad4030_rx_bus_width[i];
+
+	if (rx_bus_width == 8 && st->chip->num_voltage_inputs == 1)
+		return dev_err_probe(dev, -EINVAL, "1 channel with 8 lanes?\n");
+
+	if (rx_bus_width == 1 && st->chip->num_voltage_inputs > 1)
 		st->lane_mode = AD4030_LANE_MD_INTERLEAVED;
 	else
-		st->lane_mode = AD4030_LANE_MD_1_PER_CH;
+		st->lane_mode = ilog2(rx_bus_width / st->chip->num_voltage_inputs);
 
 	reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE, st->lane_mode);
 
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 10/15] dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (8 preceding siblings ...)
  2025-08-30  0:43 ` [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel Marcelo Schmitt
@ 2025-08-30  0:43 ` Marcelo Schmitt
  2025-08-30 18:02   ` David Lechner
  2025-08-30  0:44 ` [PATCH 11/15] iio: adc: ad4030: Add clock mode option parse and setup Marcelo Schmitt
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:43 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, marcelo.schmitt1

AD4030 and similar designs support three different options for the clock
that frames ADC output data. Each option implies a different hardware
configuration for reading ADC data. Document AD4030 clock mode options.

Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 .../devicetree/bindings/iio/adc/adi,ad4030.yaml      | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index bee85087a7b2..1e4e025b835f 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -78,6 +78,18 @@ properties:
   interrupt-names:
     const: busy
 
+  adi,clock-mode:
+    $ref: /schemas/types.yaml#/definitions/string
+    enum: [ spi, echo, host ]
+    default: spi
+    description:
+      Describes how the clock that frames ADC data output is setup.
+      spi  - Spi-compatible. Normal SPI operation clocking.
+      echo - Echo-clock. Synchronous clock echoing to ease timing requirements
+             when using isolation on the digital interface.
+      host - Host. The Host clock mode uses an internal oscillator to clock out
+             the data bits. In this mode, the spi controller is not driving SCLK.
+
 required:
   - compatible
   - reg
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 11/15] iio: adc: ad4030: Add clock mode option parse and setup
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (9 preceding siblings ...)
  2025-08-30  0:43 ` [PATCH 10/15] dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode Marcelo Schmitt
@ 2025-08-30  0:44 ` Marcelo Schmitt
  2025-08-30  7:42   ` Andy Shevchenko
  2025-08-30  0:44 ` [PATCH 12/15] dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate Marcelo Schmitt
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:44 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, marcelo.schmitt1

AD4030 series of ADCs support three different options for the clock that
frames data output. Since each clock option implies a different hardware
setup, the clock mode to use is specified in firmware. Read the designated
clock option from firmware and configure the device to work accordingly.

Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 drivers/iio/adc/ad4030.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index e6c1c9be1632..a5931056936a 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -74,6 +74,7 @@
 	(AD4030_REG_GAIN_X0_MSB + (AD4030_REG_GAIN_BYTES_NB * (ch)))
 #define AD4030_REG_MODES			0x20
 #define     AD4030_REG_MODES_MASK_OUT_DATA_MODE	GENMASK(2, 0)
+#define     AD4030_REG_MODES_MASK_CLOCK_MODE	GENMASK(5, 4)
 #define     AD4030_REG_MODES_MASK_LANE_MODE	GENMASK(7, 6)
 #define AD4030_REG_OSCILATOR			0x21
 #define AD4030_REG_IO				0x22
@@ -127,6 +128,12 @@ enum ad4030_out_mode {
 	AD4030_OUT_DATA_MD_32_PATTERN,
 };
 
+enum ad4030_clock_mode {
+	AD4030_SPI_CLOCK_MODE,
+	AD4030_ECHO_CLOCK_MODE,
+	AD4030_CLOCK_HOST_MODE,
+};
+
 enum ad4030_lane_mode {
 	AD4030_LANE_MD_1_PER_CH,
 	AD4030_LANE_MD_2_PER_CH,
@@ -167,6 +174,7 @@ struct ad4030_state {
 	unsigned int avg_log2;
 	enum ad4030_out_mode mode;
 	enum ad4030_lane_mode lane_mode;
+	enum ad4030_clock_mode clock_mode;
 	/* offload sampling spi message */
 	struct spi_transfer offload_xfer;
 	struct spi_message offload_msg;
@@ -263,6 +271,12 @@ static const int ad4030_rx_bus_width[] = {
 	1, 2, 4, 8,
 };
 
+static const char * const ad4030_clock_mode_str[] = {
+	[AD4030_SPI_CLOCK_MODE] = "spi",
+	[AD4030_ECHO_CLOCK_MODE] = "echo",
+	[AD4030_CLOCK_HOST_MODE] = "host",
+};
+
 static const int ad4030_average_modes[] = {
 	1, 2, 4, 8, 16, 32, 64, 128,
 	256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
@@ -1249,6 +1263,14 @@ static int ad4030_config(struct ad4030_state *st)
 
 	reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE, st->lane_mode);
 
+	/* Optional data clock mode */
+	ret = device_property_match_property_string(dev, "adi,clock-mode",
+						    ad4030_clock_mode_str,
+						    ARRAY_SIZE(ad4030_clock_mode_str));
+	/* Default to SPI clock mode. */
+	reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_CLOCK_MODE,
+				ret >= 0 ? ret : AD4030_SPI_CLOCK_MODE);
+
 	ret = regmap_write(st->regmap, AD4030_REG_MODES, reg_modes);
 	if (ret)
 		return ret;
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 12/15] dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (10 preceding siblings ...)
  2025-08-30  0:44 ` [PATCH 11/15] iio: adc: ad4030: Add clock mode option parse and setup Marcelo Schmitt
@ 2025-08-30  0:44 ` Marcelo Schmitt
  2025-08-30 17:27   ` David Lechner
  2025-08-30  0:45 ` [PATCH 13/15] iio: adc: ad4030: Enable dual data rate Marcelo Schmitt
                   ` (3 subsequent siblings)
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:44 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, marcelo.schmitt1

On echo and host clock modes, AD4030 and similar devices can do two data
bit transitions per clock cycle per active lane. Document how to specify
dual data rate (DDR) feature for AD4030 series devices in device tree.

Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 .../bindings/iio/adc/adi,ad4030.yaml          | 27 +++++++++++++++----
 1 file changed, 22 insertions(+), 5 deletions(-)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index 1e4e025b835f..9adb60629631 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -90,6 +90,13 @@ properties:
       host - Host. The Host clock mode uses an internal oscillator to clock out
              the data bits. In this mode, the spi controller is not driving SCLK.
 
+  adi,dual-data-rate:
+    description:
+      Enable dual data rate (DDR) in which two bits (per active lane) are
+      transmitted in one clock cycle. This can reduce the serial clock to
+      10 MHz while operating at a sample rate of 2 MSPS.
+    type: boolean
+
 required:
   - compatible
   - reg
@@ -98,11 +105,21 @@ required:
   - vio-supply
   - cnv-gpios
 
-oneOf:
-  - required:
-      - ref-supply
-  - required:
-      - refin-supply
+allOf:
+  - oneOf:
+      - required:
+          - ref-supply
+      - required:
+          - refin-supply
+  # DDR is available only for echo clock mode and host clock mode.
+  - if:
+      properties:
+        adi,clock-mode:
+          contains:
+            const: spi
+    then:
+      properties:
+        adi,dual-data-rate: false
 
 unevaluatedProperties: false
 
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 13/15] iio: adc: ad4030: Enable dual data rate
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (11 preceding siblings ...)
  2025-08-30  0:44 ` [PATCH 12/15] dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate Marcelo Schmitt
@ 2025-08-30  0:45 ` Marcelo Schmitt
  2025-08-30  7:46   ` Andy Shevchenko
  2025-08-30 17:33   ` David Lechner
  2025-08-30  0:45 ` [PATCH 14/15] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
                   ` (2 subsequent siblings)
  15 siblings, 2 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:45 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, marcelo.schmitt1

Set AD4030 series device to do two data bit transitions per clock cycle per
active lane when specified by firmware. The dual data rate (DDR) feature is
available only for host clock mode and echo clock mode.

Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 drivers/iio/adc/ad4030.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index a5931056936a..37ba00097efe 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -74,6 +74,7 @@
 	(AD4030_REG_GAIN_X0_MSB + (AD4030_REG_GAIN_BYTES_NB * (ch)))
 #define AD4030_REG_MODES			0x20
 #define     AD4030_REG_MODES_MASK_OUT_DATA_MODE	GENMASK(2, 0)
+#define     AD4030_REG_MODES_MASK_DDR_MODE	BIT(3)
 #define     AD4030_REG_MODES_MASK_CLOCK_MODE	GENMASK(5, 4)
 #define     AD4030_REG_MODES_MASK_LANE_MODE	GENMASK(7, 6)
 #define AD4030_REG_OSCILATOR			0x21
@@ -175,6 +176,7 @@ struct ad4030_state {
 	enum ad4030_out_mode mode;
 	enum ad4030_lane_mode lane_mode;
 	enum ad4030_clock_mode clock_mode;
+	bool ddr;
 	/* offload sampling spi message */
 	struct spi_transfer offload_xfer;
 	struct spi_message offload_msg;
@@ -1218,6 +1220,9 @@ static void ad4030_prepare_offload_msg(struct ad4030_state *st)
 	else
 		offload_bpw  = data_width / (1 << st->lane_mode);
 
+	if (st->ddr)
+		offload_bpw  /= 2;
+
 	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
 	st->offload_xfer.bits_per_word = offload_bpw;
 	st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(offload_bpw));
@@ -1271,6 +1276,12 @@ static int ad4030_config(struct ad4030_state *st)
 	reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_CLOCK_MODE,
 				ret >= 0 ? ret : AD4030_SPI_CLOCK_MODE);
 
+	/* DDR is only valid for echo clock and host clock modes */
+	if (ret == AD4030_ECHO_CLOCK_MODE || ret == AD4030_CLOCK_HOST_MODE) {
+		st->ddr = device_property_read_bool(dev, "adi,dual-data-rate");
+		reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_DDR_MODE, st->ddr);
+	}
+
 	ret = regmap_write(st->regmap, AD4030_REG_MODES, reg_modes);
 	if (ret)
 		return ret;
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 14/15] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (12 preceding siblings ...)
  2025-08-30  0:45 ` [PATCH 13/15] iio: adc: ad4030: Enable dual data rate Marcelo Schmitt
@ 2025-08-30  0:45 ` Marcelo Schmitt
  2025-08-30 18:45   ` David Lechner
  2025-08-30  0:45 ` [PATCH 15/15] iio: adc: ad4030: Add support for " Marcelo Schmitt
  2025-08-30  2:48 ` [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
  15 siblings, 1 reply; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:45 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

ADAQ4216 and ADAQ4224 are similar to AD4030 except ADAQ devices have a PGA
(programmable gain amplifier) that scales the input signal prior to it
reaching the ADC inputs. The PGA is controlled through a couple of pins (A0
and A1) that set one of four possible signal gain.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
The PGA doc was inspired on ad7191 dt-binding and uses the same properies (but
with different values) to describe the hardware.

 .../bindings/iio/adc/adi,ad4030.yaml          | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index 9adb60629631..36fd2aa51922 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
@@ -19,6 +19,8 @@ description: |
   * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4030-24-4032-24.pdf
   * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4630-24_ad4632-24.pdf
   * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4630-16-4632-16.pdf
+  * https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4216.pdf
+  * https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4224.pdf
 
 $ref: /schemas/spi/spi-peripheral-props.yaml#
 
@@ -31,6 +33,8 @@ properties:
       - adi,ad4630-24
       - adi,ad4632-16
       - adi,ad4632-24
+      - adi,adaq4216
+      - adi,adaq4224
 
   reg:
     maxItems: 1
@@ -64,6 +68,27 @@ properties:
       The Reset Input (/RST). Used for asynchronous device reset.
     maxItems: 1
 
+  pga-gpios:
+    description:
+      A0 and A1 pins for gain selection. For devices that have PGA configuration
+      input pins, pga-gpios should be defined if adi,gain-milli is absent.
+    minItems: 2
+    maxItems: 2
+
+  adi,pga-value:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Should be present if PGA control inputs are pin-strapped. The values
+      specify the gain per mille. For example, 333 means the input signal is
+      scaled by a 0.333 factor (i.e. attenuated to one third of it's original
+      magnitude). Possible values:
+      Gain 333 (A1=0, A0=0)
+      Gain 556 (A1=0, A0=1)
+      Gain 2222 (A1=1, A0=0)
+      Gain 6667 (A1=1, A0=1)
+      If defined, pga-gpios must be absent.
+    enum: [333, 556, 2222, 6667]
+
   pwms:
     description: PWM signal connected to the CNV pin.
     maxItems: 1
@@ -120,6 +145,20 @@ allOf:
     then:
       properties:
         adi,dual-data-rate: false
+  # ADAQ devices require a gain property to indicate how hardware PGA is set
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - adi,adaq4216
+              - adi,adaq4224
+    then:
+      oneOf:
+        - required:
+            - adi,pga-value
+        - required:
+            - pga-gpios
 
 unevaluatedProperties: false
 
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (13 preceding siblings ...)
  2025-08-30  0:45 ` [PATCH 14/15] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
@ 2025-08-30  0:45 ` Marcelo Schmitt
  2025-08-30  7:57   ` Andy Shevchenko
                     ` (2 more replies)
  2025-08-30  2:48 ` [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
  15 siblings, 3 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  0:45 UTC (permalink / raw)
  To: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet,
	robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, marcelo.schmitt1

ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
that scales the analog input signal prior to it reaching the ADC. The PGA
is controlled through a pair of pins (A0 and A1) whose state define the
gain that is applied to the input signal.

Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
through the IIO device channel scale available interface and enable control
of the PGA through the channel scale interface.

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 drivers/iio/adc/ad4030.c | 239 ++++++++++++++++++++++++++++++++++++++-
 1 file changed, 235 insertions(+), 4 deletions(-)

diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 37ba00097efe..32157b3a0420 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -21,6 +21,7 @@
 #include <linux/iio/trigger_consumer.h>
 #include <linux/iio/triggered_buffer.h>
 #include <linux/log2.h>
+#include <linux/minmax.h>
 #include <linux/pwm.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
@@ -42,6 +43,8 @@
 #define     AD4030_REG_CHIP_GRADE_AD4630_24_GRADE	0x00
 #define     AD4030_REG_CHIP_GRADE_AD4632_16_GRADE	0x05
 #define     AD4030_REG_CHIP_GRADE_AD4632_24_GRADE	0x02
+#define     AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE	0x1E
+#define     AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE	0x1C
 #define     AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE	GENMASK(7, 3)
 #define AD4030_REG_SCRATCH_PAD			0x0A
 #define AD4030_REG_SPI_REVISION			0x0B
@@ -121,6 +124,10 @@
 /* Datasheet says 9.8ns, so use the closest integer value */
 #define AD4030_TQUIET_CNV_DELAY_NS	10
 
+/* HARDWARE_GAIN */
+#define ADAQ4616_PGA_PINS		2
+#define ADAQ4616_GAIN_MAX_NANO		6666666667
+
 enum ad4030_out_mode {
 	AD4030_OUT_DATA_MD_DIFF,
 	AD4030_OUT_DATA_MD_16_DIFF_8_COM,
@@ -149,6 +156,20 @@ enum {
 	AD4030_OFFLOAD_SCAN_TYPE_AVG,
 };
 
+/*
+ * Gains computed as fractions of 1000 so they can be expressed by integers.
+ */
+static const int ad4030_hw_gains[] = {
+	333, 556, 2222, 6667,
+};
+
+static const int ad4030_hw_gains_frac[4][2] = {
+	{ 1, 3 },  /* 1/3 gain */
+	{ 5, 9 },  /* 5/9 gain */
+	{ 20, 9 }, /* 20/9 gain */
+	{ 20, 3 }, /* 20/3 gain */
+};
+
 struct ad4030_chip_info {
 	const char *name;
 	const unsigned long *available_masks;
@@ -160,6 +181,7 @@ struct ad4030_chip_info {
 	int num_voltage_inputs;
 	unsigned int tcyc_ns;
 	unsigned int max_sample_rate_hz;
+	unsigned int num_pga_pins;
 };
 
 struct ad4030_state {
@@ -183,6 +205,10 @@ struct ad4030_state {
 	struct spi_offload *offload;
 	struct spi_offload_trigger *offload_trigger;
 	struct spi_offload_trigger_config offload_trigger_config;
+	struct gpio_descs *pga_gpios;
+	int pga_index;
+	unsigned int scale_avail[ARRAY_SIZE(ad4030_hw_gains)][2];
+	size_t scale_avail_size;
 
 	/*
 	 * DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -239,7 +265,7 @@ struct ad4030_state {
  * - voltage0-voltage1
  * - voltage2-voltage3
  */
-#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload) {		\
+#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload, _pga) {		\
 	.info_mask_shared_by_all =					\
 		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),			\
 	.info_mask_shared_by_all_available =				\
@@ -250,6 +276,7 @@ struct ad4030_state {
 		BIT(IIO_CHAN_INFO_CALIBBIAS) |				\
 		BIT(IIO_CHAN_INFO_RAW),					\
 	.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) |	\
+		(_pga ? BIT(IIO_CHAN_INFO_SCALE) : 0) |			\
 		BIT(IIO_CHAN_INFO_CALIBSCALE),				\
 	.type = IIO_VOLTAGE,						\
 	.indexed = 1,							\
@@ -264,10 +291,16 @@ struct ad4030_state {
 }
 
 #define AD4030_CHAN_DIFF(_idx, _scan_type)				\
-	__AD4030_CHAN_DIFF(_idx, _scan_type, 0)
+	__AD4030_CHAN_DIFF(_idx, _scan_type, 0, 0)
 
 #define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
-	__AD4030_CHAN_DIFF(_idx, _scan_type, 1)
+	__AD4030_CHAN_DIFF(_idx, _scan_type, 1, 0)
+
+#define ADAQ4216_CHAN_DIFF(_idx, _scan_type)				\
+	__AD4030_CHAN_DIFF(_idx, _scan_type, 0, 1)
+
+#define ADAQ4216_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
+	__AD4030_CHAN_DIFF(_idx, _scan_type, 1, 1)
 
 static const int ad4030_rx_bus_width[] = {
 	1, 2, 4, 8,
@@ -429,6 +462,74 @@ static const struct regmap_config ad4030_regmap_config = {
 	.max_register = AD4030_REG_DIG_ERR,
 };
 
+static void ad4030_fill_scale_avail(struct ad4030_state *st)
+{
+	unsigned int mag_bits, tmp0, tmp1, i;
+	u64 range;
+
+	/*
+	 * The maximum precision of differential channels is retrieved from the
+	 * chip properties. The output code of differential channels is in two's
+	 * complement format (i.e. signed), so the MSB is the sign bit and only
+	 * (precision_bits - 1) bits express voltage magnitude.
+	 */
+	mag_bits = st->chip->precision_bits - 1;
+
+	for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
+		range = mult_frac(st->vref_uv, ad4030_hw_gains_frac[i][1],
+				  ad4030_hw_gains_frac[i][0]);
+		/*
+		 * If range were in mV, we would multiply it by NANO below.
+		 * Though, range is in µV so multiply it by MICRO only so the
+		 * result after right shift and division scales output codes to
+		 * millivolts.
+		 */
+		tmp0 = div_u64_rem(((u64)range * MICRO) >> mag_bits, NANO, &tmp1);
+		st->scale_avail[i][0] = tmp0; /* Integer part */
+		st->scale_avail[i][1] = tmp1; /* Fractional part */
+	}
+}
+
+static int ad4030_set_pga_gain(struct ad4030_state *st)
+{
+	DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { };
+
+	bitmap_write(bitmap, st->pga_index, 0, 2);
+
+	return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap);
+}
+
+static int ad4030_set_pga(struct iio_dev *indio_dev,
+			  struct iio_chan_spec const *chan, int gain_int,
+			  int gain_fract)
+{
+	struct ad4030_state *st = iio_priv(indio_dev);
+	const struct iio_scan_type *scan_type;
+	unsigned int mag_bits;
+	u64 gain_nano, tmp;
+
+	if (!st->pga_gpios)
+		return -EINVAL;
+
+	scan_type = iio_get_current_scan_type(indio_dev, chan);
+	if (scan_type->sign == 's')
+		mag_bits = st->chip->precision_bits - 1;
+	else
+		mag_bits = st->chip->precision_bits;
+
+	gain_nano = gain_int * NANO + gain_fract;
+
+	if (!in_range(gain_nano, 0, ADAQ4616_GAIN_MAX_NANO))
+		return -EINVAL;
+
+	tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
+	gain_nano = DIV_ROUND_CLOSEST_ULL(st->vref_uv, tmp);
+	st->pga_index = find_closest(gain_nano, ad4030_hw_gains,
+				     ARRAY_SIZE(ad4030_hw_gains));
+
+	return ad4030_set_pga_gain(st);
+}
+
 static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
 				 struct iio_chan_spec const *chan,
 				 int *val,
@@ -455,7 +556,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
 	*val2 = scan_type->realbits == 30 ? st->chip->precision_bits
 					  : scan_type->realbits;
 
-	return IIO_VAL_FRACTIONAL_LOG2;
+	/* The LSB of the 8-bit common-mode data is always vref/256. */
+	if (scan_type->realbits == 8 || !st->chip->num_pga_pins)
+		return IIO_VAL_FRACTIONAL_LOG2;
+
+	*val = st->scale_avail[st->pga_index][0];
+	*val2 = st->scale_avail[st->pga_index][1];
+
+	return IIO_VAL_INT_PLUS_NANO;
 }
 
 static int ad4030_get_chan_calibscale(struct iio_dev *indio_dev,
@@ -654,6 +762,19 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev,
 				 st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
 }
 
+static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev,
+				    struct iio_chan_spec const *chan, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+
+	return -EINVAL;
+}
+
 static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
 {
 	struct ad4030_state *st = iio_priv(dev);
@@ -891,6 +1012,15 @@ static int ad4030_read_avail(struct iio_dev *indio_dev,
 		*length = ARRAY_SIZE(ad4030_average_modes);
 		return IIO_AVAIL_LIST;
 
+	case IIO_CHAN_INFO_SCALE:
+		if (!st->pga_gpios)
+			*vals = (int *)st->scale_avail[st->pga_index];
+		else
+			*vals = (int *)st->scale_avail;
+		*length = st->scale_avail_size * 2; /* print int and nano part */
+		*type = IIO_VAL_INT_PLUS_NANO;
+		return IIO_AVAIL_LIST;
+
 	default:
 		return -EINVAL;
 	}
@@ -966,6 +1096,9 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		return ad4030_set_sampling_freq(indio_dev, val);
 
+	case IIO_CHAN_INFO_SCALE:
+		return ad4030_set_pga(indio_dev, chan, val, val2);
+
 	default:
 		return -EINVAL;
 	}
@@ -1037,6 +1170,7 @@ static const struct iio_info ad4030_iio_info = {
 	.read_avail = ad4030_read_avail,
 	.read_raw = ad4030_read_raw,
 	.write_raw = ad4030_write_raw,
+	.write_raw_get_fmt = &ad4030_write_raw_get_fmt,
 	.debugfs_reg_access = ad4030_reg_access,
 	.read_label = ad4030_read_label,
 	.get_current_scan_type = ad4030_get_current_scan_type,
@@ -1318,6 +1452,51 @@ static int ad4030_spi_offload_setup(struct iio_dev *indio_dev,
 							   IIO_BUFFER_DIRECTION_IN);
 }
 
+static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
+			    struct ad4030_state *st)
+{
+	unsigned int i;
+	int pga_value;
+	int ret;
+
+	ret = device_property_read_u32(dev, "adi,pga-value", &pga_value);
+	if (ret && ret != -EINVAL)
+		return dev_err_probe(dev, ret, "Failed to get PGA value.\n");
+
+	if (ret == -EINVAL) {
+		/* Setup GPIOs for PGA control */
+		st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW);
+		if (IS_ERR(st->pga_gpios))
+			return dev_err_probe(dev, PTR_ERR(st->pga_gpios),
+					     "Failed to get PGA gpios.\n");
+
+		if (st->pga_gpios->ndescs != 2)
+			return dev_err_probe(dev, -EINVAL,
+					     "Expected 2 GPIOs for PGA control.\n");
+
+		st->scale_avail_size = ARRAY_SIZE(ad4030_hw_gains);
+		st->pga_index = 0;
+		return ad4030_set_pga_gain(st);
+	}
+
+	/* Set ADC driver to handle pin-strapped PGA pins setup */
+	for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
+		if (pga_value != ad4030_hw_gains[i])
+			continue;
+
+		st->pga_index = i;
+		break;
+	}
+	if (i == ARRAY_SIZE(ad4030_hw_gains))
+		return dev_err_probe(dev, -EINVAL, "Invalid PGA value: %d.\n",
+				     pga_value);
+
+	st->scale_avail_size = 1;
+	st->pga_gpios = NULL;
+
+	return 0;
+}
+
 static int ad4030_probe(struct spi_device *spi)
 {
 	struct device *dev = &spi->dev;
@@ -1360,6 +1539,14 @@ static int ad4030_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	if (st->chip->num_pga_pins > 0) {
+		ret = ad4030_setup_pga(dev, indio_dev, st);
+		if (ret)
+			return ret;
+
+		ad4030_fill_scale_avail(st);
+	}
+
 	ret = ad4030_config(st);
 	if (ret)
 		return ret;
@@ -1611,12 +1798,54 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
 	.max_sample_rate_hz = 500 * KILO,
 };
 
+static const struct ad4030_chip_info adaq4216_chip_info = {
+	.name = "adaq4216",
+	.available_masks = ad4030_channel_masks,
+	.channels = {
+		ADAQ4216_CHAN_DIFF(0, ad4030_16_scan_types),
+		AD4030_CHAN_CMO(1, 0),
+		IIO_CHAN_SOFT_TIMESTAMP(2),
+	},
+	.offload_channels = {
+		ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_16_scan_types),
+		AD4030_CHAN_CMO(1, 0),
+	},
+	.grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE,
+	.precision_bits = 16,
+	.num_voltage_inputs = 1,
+	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 2 * MEGA,
+	.num_pga_pins = ADAQ4616_PGA_PINS,
+};
+
+static const struct ad4030_chip_info adaq4224_chip_info = {
+	.name = "adaq4224",
+	.available_masks = ad4030_channel_masks,
+	.channels = {
+		ADAQ4216_CHAN_DIFF(0, ad4030_24_scan_types),
+		AD4030_CHAN_CMO(1, 0),
+		IIO_CHAN_SOFT_TIMESTAMP(2),
+	},
+	.offload_channels = {
+		ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
+		AD4030_CHAN_CMO(1, 0),
+	},
+	.grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE,
+	.precision_bits = 24,
+	.num_voltage_inputs = 1,
+	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+	.max_sample_rate_hz = 2 * MEGA,
+	.num_pga_pins = ADAQ4616_PGA_PINS,
+};
+
 static const struct spi_device_id ad4030_id_table[] = {
 	{ "ad4030-24", (kernel_ulong_t)&ad4030_24_chip_info },
 	{ "ad4630-16", (kernel_ulong_t)&ad4630_16_chip_info },
 	{ "ad4630-24", (kernel_ulong_t)&ad4630_24_chip_info },
 	{ "ad4632-16", (kernel_ulong_t)&ad4632_16_chip_info },
 	{ "ad4632-24", (kernel_ulong_t)&ad4632_24_chip_info },
+	{ "adaq4216", (kernel_ulong_t)&adaq4216_chip_info },
+	{ "adaq4224", (kernel_ulong_t)&adaq4224_chip_info },
 	{ }
 };
 MODULE_DEVICE_TABLE(spi, ad4030_id_table);
@@ -1627,6 +1856,8 @@ static const struct of_device_id ad4030_of_match[] = {
 	{ .compatible = "adi,ad4630-24", .data = &ad4630_24_chip_info },
 	{ .compatible = "adi,ad4632-16", .data = &ad4632_16_chip_info },
 	{ .compatible = "adi,ad4632-24", .data = &ad4632_24_chip_info },
+	{ .compatible = "adi,adaq4216", .data = &adaq4216_chip_info },
+	{ .compatible = "adi,adaq4224", .data = &adaq4224_chip_info },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, ad4030_of_match);
-- 
2.39.2


^ permalink raw reply related	[flat|nested] 43+ messages in thread

* Re: [PATCH 00/15] Add SPI offload support to AD4030
  2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
                   ` (14 preceding siblings ...)
  2025-08-30  0:45 ` [PATCH 15/15] iio: adc: ad4030: Add support for " Marcelo Schmitt
@ 2025-08-30  2:48 ` Marcelo Schmitt
  15 siblings, 0 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-08-30  2:48 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam

...
> 
> The patches to the SPI subsystem are from Axel Haslam and I only signed them to
> indicate I'm moving them forward.
> 
Realized I should have based the SPI patches on top of SPI for-next or SPI
for-6.17 and probably sent them on a separate patch set.
I'll do so in v2, but will first leave time for v1 review.
The changes to the SPI subsystem are not extensive and the patches apply cleanly
on to of SPI for-next, nevertheless.

> 
> Axel Haslam (2):
>   spi: offload: types: add offset parameter
>   spi: spi-offload-trigger-pwm: Use duty offset
> 
...
>  .../bindings/iio/adc/adi,ad4030.yaml          |  86 ++-
>  Documentation/iio/ad4030.rst                  |  29 +
>  drivers/iio/adc/Kconfig                       |   2 +
>  drivers/iio/adc/ad4030.c                      | 704 +++++++++++++++++-
>  drivers/spi/spi-offload-trigger-pwm.c         |   5 +-
>  include/linux/spi/offload/types.h             |   1 +
>  6 files changed, 792 insertions(+), 35 deletions(-)
> 
> 
> base-commit: 91812d3843409c235f336f32f1c37ddc790f1e03
> -- 
> 2.39.2
> 

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled
  2025-08-30  0:40 ` [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled Marcelo Schmitt
@ 2025-08-30  5:00   ` Andy Shevchenko
  2025-08-30 18:43   ` Jonathan Cameron
  1 sibling, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  5:00 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:40 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> Previously, the AD4030 driver was using the number of scan realbits for the
> voltage channel to derive the scale to millivolts. Though, when sample
> averaging is enabled (oversampling_ratio > 1), the number of scan realbits
> for the channel is set to 30 and doesn't match the amount of conversion
> precision bits. Due to that, the calculated channel scale did not correctly
> scale raw sample data to millivolt units in those cases. Use chip specific
> precision bits to derive the correct channel _scale on every and all
> channel configuration.
>
> Fixes: dc78e71d7c15 ("iio: adc: ad4030: remove some duplicate code")
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> This was probalby buggy since
> commit 949abd1ca5a4 ("iio: adc: ad4030: add averaging support")
> but I decided to set the fixes tag with dc78e71d7c15 because this patch will
> not apply cleanly over 949abd1ca5a4.

FWIW, you may add a few Fixes tags: The original one, and the one(s)
which changed
 drastically the code. In any case for the small conflicts we don't
care, just put the correct Fixes and if needed to backport, one
provides an updated patch specifically for backporting.


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 05/15] spi: offload: types: add offset parameter
  2025-08-30  0:42 ` [PATCH 05/15] spi: offload: types: add offset parameter Marcelo Schmitt
@ 2025-08-30  5:01   ` Andy Shevchenko
  0 siblings, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  5:01 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi,
	Axel Haslam, jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner,
	andy, corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:42 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> From: Axel Haslam <ahaslam@baylibre.com>
>
> Add an offset parameter that can be passed in the periodic trigger.
> This is useful for example when adc drivers implement a separate periodic

ADC

> signal to trigger conversion and need offload to read the result with
> some delay.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset
  2025-08-30  0:42 ` [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset Marcelo Schmitt
@ 2025-08-30  5:02   ` Andy Shevchenko
  2025-08-30 16:41   ` David Lechner
  1 sibling, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  5:02 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi,
	Axel Haslam, jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner,
	andy, corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:42 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> Pass the duty offset to the waveform pwm.

...

>         wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
>         /* REVISIT: 50% duty-cycle for now - may add config parameter later */
>         wf.duty_length_ns = wf.period_length_ns / 2;
> -

Stray - line

> +       wf.duty_offset_ns = periodic->offset_ns;
>         ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
>         if (ret < 0)
>                 return ret;
>
>         periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns);
> -

Ditto.

> +       periodic->offset_ns = wf.duty_offset_ns;
>         return 0;
>  }
>
> @@ -77,6 +77,7 @@ static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger,
>         wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
>         /* REVISIT: 50% duty-cycle for now - may add config parameter later */
>         wf.duty_length_ns = wf.period_length_ns / 2;
> +       wf.duty_offset_ns = periodic->offset_ns;

>

Especially as it seems that the pattern is to have a blank line before
last return statements.

>         return pwm_set_waveform_might_sleep(st->pwm, &wf, false);
>  }


-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
  2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
@ 2025-08-30  7:36   ` Andy Shevchenko
  2025-08-30 12:08   ` kernel test robot
                     ` (2 subsequent siblings)
  3 siblings, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  7:36 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, tgamblin, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:43 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> AD4030 and similar ADCs can capture data at sample rates up to 2 mega
> samples per second (MSPS). Not all SPI controllers are able to achieve
> such high throughputs and even when the controller is fast enough to run
> transfers at the required speed, it may be costly to the CPU to handle
> transfer data at such high sample rates.  Add SPI offload support for
> AD4030 and similar ADCs so to enable ADC data capture at maximum sample

Either add a comma after ADCs or drop 'so' word.

> rates.

> Cc: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Cc: Nuno Sa <nuno.sa@analog.com>
> Cc: Trevor Gamblin <tgamblin@baylibre.com>
> Cc: Axel Haslam <ahaslam@baylibre.com>
> Cc: David Lechner <dlechner@baylibre.com>

First of all, please keep Cc:s just after the '---' line, which will
have the same effect for email and make the commit message less noisy.
Second, don't put Cc for the people that you already have other tags
for.
Here I found at least 3 people that are repeated in the given specific
tags below. By default the tools (git send-email) converts all tags to
the Cc automatically.

> Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Co-developed-by: Nuno Sa <nuno.sa@analog.com>
> Signed-off-by: Nuno Sa <nuno.sa@analog.com>
> Co-developed-by: Trevor Gamblin <tgamblin@baylibre.com>
> Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
> Co-developed-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>

...

> -enum {
> +enum ad4030_lane_mode {

Sounds like a candidate for a separate change, but I haven't checked
how big this part is, so perhaps it's fine just to do it here.

...

>  static const int ad4030_average_modes[] = {
>         1, 2, 4, 8, 16, 32, 64, 128,
>         256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
>         65536,

Side note, this looks like the list of bits, and can be optimised to use BIT().

>  };

...

> +       /*
> +        * The hardware does the capture on zone 2 (when spi trigger PWM
> +        * is used). This means that the spi trigger signal should happen at
> +        * tsync + tquiet_con_delay being tsync the conversion signal period
> +        * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
> +        *
> +        * The PWM waveform API only supports nanosecond resolution right now,
> +        * so round this setting to the closest available value.
> +        */
> +       offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS;
> +       do {
> +               config->periodic.offset_ns = offload_offset_ns;
> +               ret = spi_offload_trigger_validate(st->offload_trigger, config);
> +               if (ret)
> +                       return ret;
> +               offload_offset_ns += 10;

> +

Unneeded blank line.

> +       } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);

...

> +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
> +{
> +       struct ad4030_state *st = iio_priv(indio_dev);
> +       int ret;
> +
> +       if (PTR_ERR_OR_ZERO(st->offload))
> +               return -EINVAL;

Why shadow the actual error code?

> +       if (!freq || freq > st->chip->max_sample_rate_hz)
> +               return -EINVAL;

in_range() ?

> +       ret = __ad4030_set_sampling_freq(st, freq);
> +       iio_device_release_direct(indio_dev);
> +
> +       return ret;
> +}

...

> +       case IIO_CHAN_INFO_SAMP_FREQ:
> +               if (PTR_ERR_OR_ZERO(st->offload))
> +                       return -EINVAL;

Shadowing an actual error code needs a good justification.

> +               ad4030_get_sampling_freq(st, val);
> +               return IIO_VAL_INT;


...

> +       st->offload_msg.offload = st->offload;
> +       ret = spi_optimize_message(st->spi, &st->offload_msg);
> +       if (ret < 0)

Why ' < 0'? Is it capable of returning positive values? If so, what
are their meanings?

> +               goto out_reset_mode;

...

> +       /*
> +        * Preemptively disable the PWM, since we only want to enable it with
> +        * the buffer

Missing period.

> +        */

...

> +static void ad4030_prepare_offload_msg(struct ad4030_state *st)
> +{
> +       u8 data_width = st->chip->precision_bits;
> +       u8 offload_bpw;
> +
> +       if (st->lane_mode == AD4030_LANE_MD_INTERLEAVED)

> +               /*
> +                * This means all channels on 1 lane.
> +                */

This is a one line comment. Why 3 LoCs?

> +               offload_bpw = data_width * st->chip->num_voltage_inputs;
> +       else
> +               offload_bpw  = data_width;
> +
> +       st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
> +       st->offload_xfer.bits_per_word = offload_bpw;
> +       st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(offload_bpw));
> +       st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> +       spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
> +}

...

> +       /* Fall back to low speed usage when no SPI offload available. */

is available

And choose one style for one line comments and use it everywhere.

...

> +       if (ret == -ENODEV) {
> +               /*
> +                * One hardware channel is split in two software channels when
> +                * using common byte mode. Add one more channel for the timestamp.
> +                */
> +               indio_dev->num_channels = 2 * st->chip->num_voltage_inputs + 1;
> +               indio_dev->channels = st->chip->channels;
> +               indio_dev->available_scan_masks = st->chip->available_masks;
> +
> +               ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> +                                                     iio_pollfunc_store_time,
> +                                                     ad4030_trigger_handler,
> +                                                     &ad4030_buffer_setup_ops);
> +               if (ret)
> +                       return dev_err_probe(dev, ret,
> +                                            "Failed to setup triggered buffer\n");

> +

Stray blank line.

> +       } else {
> +               /*
> +                * One hardware channel is split in two software channels when
> +                * using common byte mode. Offloaded SPI transfers can't support
> +                * software timestamp so no additional timestamp channel is added.
> +                */
> +               indio_dev->num_channels = 2 * st->chip->num_voltage_inputs;
> +               indio_dev->channels = st->chip->offload_channels;
> +               indio_dev->available_scan_masks = st->chip->available_masks;
> +               ret = ad4030_spi_offload_setup(indio_dev, st);
> +               if (ret)
> +                       return dev_err_probe(dev, ret,
> +                                            "Failed to setup SPI offload\n");
> +
> +               ret = ad4030_pwm_get(st);
> +               if (ret)
> +                       return dev_err_probe(&spi->dev, ret,
> +                                            "Failed to get PWM: %d\n", ret);
> +
> +               ret = __ad4030_set_sampling_freq(st, st->chip->max_sample_rate_hz);
> +               ad4030_prepare_offload_msg(st);
> +       }

...

> -       }
> +       },

You see, this is the point I always make about leaving trailing commas
in the non-terminator entries.
(It's just a good example I can't help comment on this just for others
to point out again on this)

...

> +       .max_sample_rate_hz = 2 * MEGA,

HZ_PER_MHZ


...

> +       .max_sample_rate_hz = 2 * MEGA,

Ditto.

...

> +       .max_sample_rate_hz = 2 * MEGA,

Ditto.

...

> +       .max_sample_rate_hz = 500 * KILO,

HZ_PER_KHZ

...

> +       .max_sample_rate_hz = 500 * KILO,

Ditto.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel
  2025-08-30  0:43 ` [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel Marcelo Schmitt
@ 2025-08-30  7:38   ` Andy Shevchenko
  2025-08-30 17:19   ` David Lechner
  1 sibling, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  7:38 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:43 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> AD4030 and similar chips can output ADC sample data through 1, 2, or 4
> lines per channel. The number of SPI lines the device uses to output data
> is specified in firmware. Parse SPI read bus width setting from firmware
> and configure the device to use that amount of lines to output data.

...

> -               offload_bpw  = data_width;
> +               offload_bpw  = data_width / (1 << st->lane_mode);

This is interesting. What's the difference you see to the use of the
right shift?
Also note, in case of lane_mode == 31 (yeah, I understand that here
it's not the case) this is UB in accordance with C standard.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 11/15] iio: adc: ad4030: Add clock mode option parse and setup
  2025-08-30  0:44 ` [PATCH 11/15] iio: adc: ad4030: Add clock mode option parse and setup Marcelo Schmitt
@ 2025-08-30  7:42   ` Andy Shevchenko
  0 siblings, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  7:42 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:44 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> AD4030 series of ADCs support three different options for the clock that
> frames data output. Since each clock option implies a different hardware
> setup, the clock mode to use is specified in firmware. Read the designated
> clock option from firmware and configure the device to work accordingly.

...

> +       ret = device_property_match_property_string(dev, "adi,clock-mode",
> +                                                   ad4030_clock_mode_str,
> +                                                   ARRAY_SIZE(ad4030_clock_mode_str));
> +       /* Default to SPI clock mode. */
> +       reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_CLOCK_MODE,
> +                               ret >= 0 ? ret : AD4030_SPI_CLOCK_MODE);

FIELD_MODIFY() ?

Also, I would rather put it as proper if

if (ret >= 0)
  FIELD_MODIFY(...)
else
  FIELD_MODIFY(...)

> +

I would not add this blank line as these are coupled.

>         ret = regmap_write(st->regmap, AD4030_REG_MODES, reg_modes);
>         if (ret)
>                 return ret;

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 13/15] iio: adc: ad4030: Enable dual data rate
  2025-08-30  0:45 ` [PATCH 13/15] iio: adc: ad4030: Enable dual data rate Marcelo Schmitt
@ 2025-08-30  7:46   ` Andy Shevchenko
  2025-08-30 17:33   ` David Lechner
  1 sibling, 0 replies; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  7:46 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:45 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> Set AD4030 series device to do two data bit transitions per clock cycle per
> active lane when specified by firmware. The dual data rate (DDR) feature is
> available only for host clock mode and echo clock mode.

...

>  struct ad4030_state {

>         enum ad4030_out_mode mode;
>         enum ad4030_lane_mode lane_mode;
>         enum ad4030_clock_mode clock_mode;
> +       bool ddr;

I believe you run `pahole` each time you modify the data type like this.

>         /* offload sampling spi message */
>         struct spi_transfer offload_xfer;
>         struct spi_message offload_msg;

...

>         else
>                 offload_bpw  = data_width / (1 << st->lane_mode);

With the previous comment WRT right shift...

> +       if (st->ddr)
> +               offload_bpw  /= 2;

...this also can use right shift, but I understand that 2 is more
explicit to show the point of DDR (as "double").

...

> +       /* DDR is only valid for echo clock and host clock modes */
> +       if (ret == AD4030_ECHO_CLOCK_MODE || ret == AD4030_CLOCK_HOST_MODE) {
> +               st->ddr = device_property_read_bool(dev, "adi,dual-data-rate");
> +               reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_DDR_MODE, st->ddr);

FIELD_MODIFY()?

> +       }

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
  2025-08-30  0:45 ` [PATCH 15/15] iio: adc: ad4030: Add support for " Marcelo Schmitt
@ 2025-08-30  7:57   ` Andy Shevchenko
  2025-09-02 15:22     ` Marcelo Schmitt
  2025-08-30 19:17   ` David Lechner
  2025-09-01 11:47   ` Dan Carpenter
  2 siblings, 1 reply; 43+ messages in thread
From: Andy Shevchenko @ 2025-08-30  7:57 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi, jic23,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On Sat, Aug 30, 2025 at 3:46 AM Marcelo Schmitt
<marcelo.schmitt@analog.com> wrote:
>
> ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
> that scales the analog input signal prior to it reaching the ADC. The PGA
> is controlled through a pair of pins (A0 and A1) whose state define the
> gain that is applied to the input signal.
>
> Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
> through the IIO device channel scale available interface and enable control
> of the PGA through the channel scale interface.

...

>  /* Datasheet says 9.8ns, so use the closest integer value */
>  #define AD4030_TQUIET_CNV_DELAY_NS     10

You already used that in one of the previous patches, can you move
there this one and use instead of magic += 10?


> +/* HARDWARE_GAIN */
> +#define ADAQ4616_PGA_PINS              2
> +#define ADAQ4616_GAIN_MAX_NANO         6666666667

Can we use calculus instead (people can't count properly after 3 :-)?
Something like this

(NANO * 2 / 3) // whoever in the above it's 20 and this puzzles me how
something with _NANO can be so big :-)

...

> +/*
> + * Gains computed as fractions of 1000 so they can be expressed by integers.
> + */
> +static const int ad4030_hw_gains[] = {
> +       333, 556, 2222, 6667,

Again, instead of comment (or in addition to) this can be written as

1000 / 3, 5000 / 9, 20000 / 9, 20000 / 3,

Let the compiler do its job.

> +};
> +
> +static const int ad4030_hw_gains_frac[4][2] = {

Drop 4

> +       { 1, 3 },  /* 1/3 gain */
> +       { 5, 9 },  /* 5/9 gain */
> +       { 20, 9 }, /* 20/9 gain */
> +       { 20, 3 }, /* 20/3 gain */
> +};

...

> +static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev,
> +                                   struct iio_chan_spec const *chan, long mask)
> +{
> +       switch (mask) {
> +       case IIO_CHAN_INFO_SCALE:
> +               return IIO_VAL_INT_PLUS_NANO;
> +       default:
> +               return IIO_VAL_INT_PLUS_MICRO;
> +       }

> +       return -EINVAL;

What's the point of this return?

> +}

...

> +static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
> +                           struct ad4030_state *st)
> +{
> +       unsigned int i;
> +       int pga_value;
> +       int ret;
> +
> +       ret = device_property_read_u32(dev, "adi,pga-value", &pga_value);
> +       if (ret && ret != -EINVAL)
> +               return dev_err_probe(dev, ret, "Failed to get PGA value.\n"
> +
> +       if (ret == -EINVAL) {

This can be done differently, i.e. check for EINVAL first and in
'else' branch check for other ret != 0. This will deduplicate the
EINVAL check.

> +               /* Setup GPIOs for PGA control */
> +               st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW);
> +               if (IS_ERR(st->pga_gpios))
> +                       return dev_err_probe(dev, PTR_ERR(st->pga_gpios),
> +                                            "Failed to get PGA gpios.\n");
> +
> +               if (st->pga_gpios->ndescs != 2)
> +                       return dev_err_probe(dev, -EINVAL,
> +                                            "Expected 2 GPIOs for PGA control.\n");
> +
> +               st->scale_avail_size = ARRAY_SIZE(ad4030_hw_gains);
> +               st->pga_index = 0;
> +               return ad4030_set_pga_gain(st);
> +       }

...

> +       .max_sample_rate_hz = 2 * MEGA,

HZ_PER_MHZ

...

> +       .max_sample_rate_hz = 2 * MEGA,

Ditto.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
  2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
  2025-08-30  7:36   ` Andy Shevchenko
@ 2025-08-30 12:08   ` kernel test robot
  2025-08-30 19:11   ` Jonathan Cameron
  2025-08-30 20:14   ` David Lechner
  3 siblings, 0 replies; 43+ messages in thread
From: kernel test robot @ 2025-08-30 12:08 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: oe-kbuild-all, jic23, Michael.Hennerich, nuno.sa, eblanc,
	dlechner, andy, corbet, robh, krzk+dt, conor+dt, broonie,
	Jonathan.Cameron, andriy.shevchenko, ahaslam, sergiu.cuciurean,
	tgamblin, marcelo.schmitt1

Hi Marcelo,

kernel test robot noticed the following build errors:

[auto build test ERROR on 91812d3843409c235f336f32f1c37ddc790f1e03]

url:    https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/iio-adc-ad4030-Fix-_scale-for-when-oversampling-is-enabled/20250830-084901
base:   91812d3843409c235f336f32f1c37ddc790f1e03
patch link:    https://lore.kernel.org/r/0d9f377295635d977e0767de9db96d0a6ad06de0.1756511030.git.marcelo.schmitt%40analog.com
patch subject: [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
config: x86_64-buildonly-randconfig-005-20250830 (https://download.01.org/0day-ci/archive/20250830/202508301919.3TZbcu20-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250830/202508301919.3TZbcu20-lkp@intel.com/reproduce)

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202508301919.3TZbcu20-lkp@intel.com/

All errors (new ones prefixed by >>):

   drivers/iio/adc/ad4030.c: In function '__ad4030_set_sampling_freq':
>> drivers/iio/adc/ad4030.c:518:23: error: implicit declaration of function 'pwm_round_waveform_might_sleep' [-Werror=implicit-function-declaration]
     518 |                 ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
         |                       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   drivers/iio/adc/ad4030.c: In function 'ad4030_offload_buffer_postenable':
>> drivers/iio/adc/ad4030.c:1055:15: error: implicit declaration of function 'pwm_set_waveform_might_sleep' [-Werror=implicit-function-declaration]
    1055 |         ret = pwm_set_waveform_might_sleep(st->conv_trigger, &st->conv_wf, false);
         |               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~
   cc1: some warnings being treated as errors


vim +/pwm_round_waveform_might_sleep +518 drivers/iio/adc/ad4030.c

   496	
   497	static int __ad4030_set_sampling_freq(struct ad4030_state *st, unsigned int freq)
   498	{
   499		struct spi_offload_trigger_config *config = &st->offload_trigger_config;
   500		struct pwm_waveform conv_wf = { };
   501		u64 offload_period_ns;
   502		u64 offload_offset_ns;
   503		u32 mode;
   504		int ret;
   505		u64 target = AD4030_TCNVH_NS;
   506	
   507		conv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
   508		/*
   509		 * The datasheet lists a minimum time of 9.8 ns, but no maximum. If the
   510		 * rounded PWM's value is less than 10, increase the target value by 10
   511		 * and attempt to round the waveform again, until the value is at least
   512		 * 10 ns. Use a separate variable to represent the target in case the
   513		 * rounding is severe enough to keep putting the first few results under
   514		 * the minimum 10ns condition checked by the while loop.
   515		 */
   516		do {
   517			conv_wf.duty_length_ns = target;
 > 518			ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
   519			if (ret)
   520				return ret;
   521			target += 10;
   522		} while (conv_wf.duty_length_ns < 10);
   523	
   524		offload_period_ns = conv_wf.period_length_ns;
   525	
   526		ret = regmap_read(st->regmap, AD4030_REG_MODES, &mode);
   527		if (ret)
   528			return ret;
   529		if (FIELD_GET(AD4030_REG_MODES_MASK_OUT_DATA_MODE, mode) == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF) {
   530			u32 avg;
   531	
   532			ret = regmap_read(st->regmap, AD4030_REG_AVG, &avg);
   533			if (ret)
   534				return ret;
   535	
   536			offload_period_ns <<= FIELD_GET(AD4030_REG_AVG_MASK_AVG_VAL, avg);
   537		}
   538	
   539		config->periodic.frequency_hz =  DIV_ROUND_UP_ULL(NSEC_PER_SEC,
   540								  offload_period_ns);
   541	
   542		/*
   543		 * The hardware does the capture on zone 2 (when spi trigger PWM
   544		 * is used). This means that the spi trigger signal should happen at
   545		 * tsync + tquiet_con_delay being tsync the conversion signal period
   546		 * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
   547		 *
   548		 * The PWM waveform API only supports nanosecond resolution right now,
   549		 * so round this setting to the closest available value.
   550		 */
   551		offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS;
   552		do {
   553			config->periodic.offset_ns = offload_offset_ns;
   554			ret = spi_offload_trigger_validate(st->offload_trigger, config);
   555			if (ret)
   556				return ret;
   557			offload_offset_ns += 10;
   558	
   559		} while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
   560	
   561		st->conv_wf = conv_wf;
   562	
   563		return 0;
   564	}
   565	

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset
  2025-08-30  0:42 ` [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset Marcelo Schmitt
  2025-08-30  5:02   ` Andy Shevchenko
@ 2025-08-30 16:41   ` David Lechner
  1 sibling, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 16:41 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi, Uwe Kleine-König
  Cc: Axel Haslam, jic23, Michael.Hennerich, nuno.sa, eblanc, andy,
	corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, marcelo.schmitt1

On 8/29/25 7:42 PM, Marcelo Schmitt wrote:
> From: Axel Haslam <ahaslam@baylibre.com>
> 
> Pass the duty offset to the waveform pwm.
> 
> Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  drivers/spi/spi-offload-trigger-pwm.c | 5 +++--
>  1 file changed, 3 insertions(+), 2 deletions(-)
> 
> diff --git a/drivers/spi/spi-offload-trigger-pwm.c b/drivers/spi/spi-offload-trigger-pwm.c
> index 805ed41560df..8413aeb3689d 100644
> --- a/drivers/spi/spi-offload-trigger-pwm.c
> +++ b/drivers/spi/spi-offload-trigger-pwm.c
> @@ -51,13 +51,13 @@ static int spi_offload_trigger_pwm_validate(struct spi_offload_trigger *trigger,
>  	wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
>  	/* REVISIT: 50% duty-cycle for now - may add config parameter later */
>  	wf.duty_length_ns = wf.period_length_ns / 2;
> -
> +	wf.duty_offset_ns = periodic->offset_ns;

I would be tempted to put the loop check here:

	offload_offset_ns = periodic->offset_ns;

	do {
		wf.offset_ns = offload_offset_ns;
		ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
		if (ret)
			return ret;
		offload_offset_ns += 10;

	} while (wf.offset_ns < periodic->offset_ns);

	wf.duty_offset_ns = periodic->offset_ns;

instead of in the ADC driver so that all future callers don't have to
repeat this.

Also cc: Uwe in case he has any better suggestions on how to avoid
repeating such verbose validations by all uses of the PWM waveform
APIs.


>  	ret = pwm_round_waveform_might_sleep(st->pwm, &wf);
>  	if (ret < 0)
>  		return ret;
>  
>  	periodic->frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC, wf.period_length_ns);
> -
> +	periodic->offset_ns = wf.duty_offset_ns;
>  	return 0;
>  }
>  
> @@ -77,6 +77,7 @@ static int spi_offload_trigger_pwm_enable(struct spi_offload_trigger *trigger,
>  	wf.period_length_ns = DIV_ROUND_UP_ULL(NSEC_PER_SEC, periodic->frequency_hz);
>  	/* REVISIT: 50% duty-cycle for now - may add config parameter later */
>  	wf.duty_length_ns = wf.period_length_ns / 2;
> +	wf.duty_offset_ns = periodic->offset_ns;
>  
>  	return pwm_set_waveform_might_sleep(st->pwm, &wf, false);
>  }


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 03/15] Documentation: iio: ad4030: Add double PWM SPI offload doc
  2025-08-30  0:41 ` [PATCH 03/15] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
@ 2025-08-30 16:49   ` David Lechner
  0 siblings, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 16:49 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On 8/29/25 7:41 PM, Marcelo Schmitt wrote:
> Document double PWM setup SPI offload wiring schema.
> 
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  Documentation/iio/ad4030.rst | 29 +++++++++++++++++++++++++++++
>  1 file changed, 29 insertions(+)
> 
> diff --git a/Documentation/iio/ad4030.rst b/Documentation/iio/ad4030.rst
> index b57424b650a8..dc3ac253ef66 100644
> --- a/Documentation/iio/ad4030.rst
> +++ b/Documentation/iio/ad4030.rst
> @@ -92,6 +92,35 @@ Interleaved mode
>  In this mode, both channels conversion results are bit interleaved one SDO line.
>  As such the wiring is the same as `One lane mode`_.
>  
> +SPI offload wiring
> +^^^^^^^^^^^^^^^^^^
> +
> +.. code-block::
> +
> +    +-------------+         +-------------+
> +    |         CNV |<-----+--| GPIO        |
> +    |             |      +--| PWM1        |

Would be more logical to swap the PWM numbers since CNV
is triggered first.

> +    |             |         |             |
> +    |             |      +--| PWM0        |
> +    |             |      |  +-------------+
> +    |             |      +->| TRIGGER     |
> +    |          CS |<--------| CS          |
> +    |             |         |             |
> +    |     ADC     |         |     SPI     |
> +    |             |         |             |
> +    |         SDI |<--------| SDO         |
> +    |         SDO |-------->| SDI         |
> +    |        SCLK |<--------| SCLK        |
> +    +-------------+         +-------------+
> +
> +In this mode, both the ``cnv-gpios`` and a ``pwms`` properties are required.
> +The ``pwms`` property specifies the PWM that is connected to the ADC CNV pin.
> +The SPI offload will have a ``trigger-sources`` property to indicate the SPI
> +offload (PWM) trigger source. The IIO device driver synchronizes the PWMs to do

suggest to add something like:

with an offset between the rising edge of PWM0 and PWM1 to delay the SPI
transfer until some time after the conversion. This requires a specialized
PWM controller that can provide such an offset.

> +ADC transfer zone 2 data capture.

What is "zone 2"?

> +
> +.. seealso:: `SPI offload support`_

There is no section that this links to. Add the section or delete this.

> +
>  SPI Clock mode
>  --------------
>  


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 08/15] dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width option
  2025-08-30  0:43 ` [PATCH 08/15] dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width option Marcelo Schmitt
@ 2025-08-30 17:01   ` David Lechner
  0 siblings, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 17:01 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On 8/29/25 7:43 PM, Marcelo Schmitt wrote:
> AD4630 has two input channels and each of them can have it's data output in
> 4 dedicated lines, resulting in a total of 8 data lines used by the device.
> Document the option that specifies the case where AD4630 and similar ADCs
> provide data through 8 SPI lines.

It is true that there are 8 serial data out lines, but not all lines
work the same way. spi-rx-bus-width describes the quad-spi-like lines
for sending multiple bits of one word from one channel at the same time.

The second set of 4 lines is for sending data from a separate channel
at the same time, so it doesn't work with an octal SPI without scrambling
data between to two channels.

Instead, I am proposing that we consider the second channel (the 2nd set
of 4 lines) as a second SPI bus on the same SPI controller. I am currently
working on implementing this in the AXI SPI Engine and the AD7380 driver.
There is still some HDL work to finalize for this before I can submit it
to the mailing lists though. 

It will use the bindings proposed in [1] rather than spi-rx-bus-width.
So we should drop this patch and wait for the other work to be done
before supporting reading both channels at the same time on AD4630.

[1]: https://lore.kernel.org/linux-spi/20250616220054.3968946-1-sean.anderson@linux.dev/

> 
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> index 564b6f67a96e..bee85087a7b2 100644
> --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> @@ -39,7 +39,7 @@ properties:
>      maximum: 102040816
>  
>    spi-rx-bus-width:
> -    enum: [1, 2, 4]
> +    enum: [1, 2, 4, 8]
>  
>    vdd-5v-supply: true
>    vdd-1v8-supply: true


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel
  2025-08-30  0:43 ` [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel Marcelo Schmitt
  2025-08-30  7:38   ` Andy Shevchenko
@ 2025-08-30 17:19   ` David Lechner
  1 sibling, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 17:19 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On 8/29/25 7:43 PM, Marcelo Schmitt wrote:
> AD4030 and similar chips can output ADC sample data through 1, 2, or 4
> lines per channel. The number of SPI lines the device uses to output data
> is specified in firmware. Parse SPI read bus width setting from firmware
> and configure the device to use that amount of lines to output data.
> 
> Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  drivers/iio/adc/ad4030.c | 33 ++++++++++++++++++++++++++++++---
>  1 file changed, 30 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index 68f76432dbfd..e6c1c9be1632 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -20,6 +20,7 @@
>  #include <linux/iio/iio.h>
>  #include <linux/iio/trigger_consumer.h>
>  #include <linux/iio/triggered_buffer.h>
> +#include <linux/log2.h>
>  #include <linux/pwm.h>
>  #include <linux/regmap.h>
>  #include <linux/regulator/consumer.h>
> @@ -258,6 +259,10 @@ struct ad4030_state {
>  #define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
>  	__AD4030_CHAN_DIFF(_idx, _scan_type, 1)
>  
> +static const int ad4030_rx_bus_width[] = {
> +	1, 2, 4, 8,
> +};
> +
>  static const int ad4030_average_modes[] = {
>  	1, 2, 4, 8, 16, 32, 64, 128,
>  	256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
> @@ -1197,7 +1202,7 @@ static void ad4030_prepare_offload_msg(struct ad4030_state *st)
>  		 */
>  		offload_bpw = data_width * st->chip->num_voltage_inputs;
>  	else
> -		offload_bpw  = data_width;
> +		offload_bpw  = data_width / (1 << st->lane_mode);

To make proper use of 2 or 4 lines for a single channel, we should be
using the SPI APIs correctly and set rx_nbits in struct spi_transfer
instead of providing an inaccurate bits per word.

>  
>  	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
>  	st->offload_xfer.bits_per_word = offload_bpw;
> @@ -1208,6 +1213,10 @@ static void ad4030_prepare_offload_msg(struct ad4030_state *st)
>  
>  static int ad4030_config(struct ad4030_state *st)
>  {
> +	struct device *dev = &st->spi->dev;
> +	const char *propname;
> +	u32 rx_bus_width;
> +	unsigned int i;
>  	int ret;
>  	u8 reg_modes;
>  
> @@ -1215,10 +1224,28 @@ static int ad4030_config(struct ad4030_state *st)
>  	st->offset_avail[1] = 1;
>  	st->offset_avail[2] = BIT(st->chip->precision_bits - 1) - 1;
>  
> -	if (st->chip->num_voltage_inputs > 1)
> +	/* Optional property specifying the number of lanes to read ADC data */
> +	propname = "spi-rx-bus-width";

This property is already handled by the core SPI code and will set
spi->mode flags SPI_RX_DUAL, SPI_RX_QUAD or SPI_RX_OCTAL. So we don't
need to read the property again.

> +	rx_bus_width = ad4030_rx_bus_width[0]; /* Default to 1 rx lane. */
> +	device_property_read_u32(dev, propname, &rx_bus_width);
> +	/* Check the rx bus width is valid */
> +	for (i = 0; i < ARRAY_SIZE(ad4030_rx_bus_width); i++)
> +		if (ad4030_rx_bus_width[i] == rx_bus_width)
> +			break;
> +
> +	if (i >= ARRAY_SIZE(ad4030_rx_bus_width))
> +		return dev_err_probe(dev, -EINVAL, "Invalid %s: %u\n",
> +				     propname, rx_bus_width);
> +
> +	rx_bus_width = ad4030_rx_bus_width[i];
> +
> +	if (rx_bus_width == 8 && st->chip->num_voltage_inputs == 1)
> +		return dev_err_probe(dev, -EINVAL, "1 channel with 8 lanes?\n");

As mentioned in the dt-bindings patch review, we really should consider
the 2 channel case separate as 2 SPI buses rather than 8 lines on a
single bus. 

Only using "spi-rx-bus-width" also has the shortcoming that if we specify
4, we don't know if is it 4 lines on 1 channel or is it 2 lines each on 2
channels? There is no way to tell from just that information.

> +
> +	if (rx_bus_width == 1 && st->chip->num_voltage_inputs > 1)
>  		st->lane_mode = AD4030_LANE_MD_INTERLEAVED;
>  	else
> -		st->lane_mode = AD4030_LANE_MD_1_PER_CH;
> +		st->lane_mode = ilog2(rx_bus_width / st->chip->num_voltage_inputs);
>  
>  	reg_modes = FIELD_PREP(AD4030_REG_MODES_MASK_LANE_MODE, st->lane_mode);
>  


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 12/15] dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate
  2025-08-30  0:44 ` [PATCH 12/15] dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate Marcelo Schmitt
@ 2025-08-30 17:27   ` David Lechner
  0 siblings, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 17:27 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On 8/29/25 7:44 PM, Marcelo Schmitt wrote:
> On echo and host clock modes, AD4030 and similar devices can do two data
> bit transitions per clock cycle per active lane. Document how to specify
> dual data rate (DDR) feature for AD4030 series devices in device tree.
> 
I don't think this needs to be in the devicetree. Dual data rate doesn't
depend on wiring, it only depends on if the SPI controller supports it
or not. The core SPI code in Linux already has dtr_caps for SPI controllers
to indicate that they have DDR support. So an ADC driver can just check
this flag to see if the controller supports it. No devicetree flags required.


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 13/15] iio: adc: ad4030: Enable dual data rate
  2025-08-30  0:45 ` [PATCH 13/15] iio: adc: ad4030: Enable dual data rate Marcelo Schmitt
  2025-08-30  7:46   ` Andy Shevchenko
@ 2025-08-30 17:33   ` David Lechner
  1 sibling, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 17:33 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On 8/29/25 7:45 PM, Marcelo Schmitt wrote:
> Set AD4030 series device to do two data bit transitions per clock cycle per
> active lane when specified by firmware. The dual data rate (DDR) feature is
> available only for host clock mode and echo clock mode.
> 
> Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  drivers/iio/adc/ad4030.c | 11 +++++++++++
>  1 file changed, 11 insertions(+)
> 
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index a5931056936a..37ba00097efe 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -74,6 +74,7 @@
>  	(AD4030_REG_GAIN_X0_MSB + (AD4030_REG_GAIN_BYTES_NB * (ch)))
>  #define AD4030_REG_MODES			0x20
>  #define     AD4030_REG_MODES_MASK_OUT_DATA_MODE	GENMASK(2, 0)
> +#define     AD4030_REG_MODES_MASK_DDR_MODE	BIT(3)
>  #define     AD4030_REG_MODES_MASK_CLOCK_MODE	GENMASK(5, 4)
>  #define     AD4030_REG_MODES_MASK_LANE_MODE	GENMASK(7, 6)
>  #define AD4030_REG_OSCILATOR			0x21
> @@ -175,6 +176,7 @@ struct ad4030_state {
>  	enum ad4030_out_mode mode;
>  	enum ad4030_lane_mode lane_mode;
>  	enum ad4030_clock_mode clock_mode;
> +	bool ddr;
>  	/* offload sampling spi message */
>  	struct spi_transfer offload_xfer;
>  	struct spi_message offload_msg;
> @@ -1218,6 +1220,9 @@ static void ad4030_prepare_offload_msg(struct ad4030_state *st)
>  	else
>  		offload_bpw  = data_width / (1 << st->lane_mode);
>  
> +	if (st->ddr)
> +		offload_bpw  /= 2;
> +

There is already an existing dtr_mode flag in struct spi_transfer. We should
be using that instead of providing an inaccurate bits per word value.

>  	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
>  	st->offload_xfer.bits_per_word = offload_bpw;
>  	st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(offload_bpw));
> @@ -1271,6 +1276,12 @@ static int ad4030_config(struct ad4030_state *st)
>  	reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_CLOCK_MODE,
>  				ret >= 0 ? ret : AD4030_SPI_CLOCK_MODE);
>  
> +	/* DDR is only valid for echo clock and host clock modes */
> +	if (ret == AD4030_ECHO_CLOCK_MODE || ret == AD4030_CLOCK_HOST_MODE) {
> +		st->ddr = device_property_read_bool(dev, "adi,dual-data-rate");

As mentioned in the dt-bindings patch review, we can already get this info
from the spi controller via dtr_caps. 

> +		reg_modes |= FIELD_PREP(AD4030_REG_MODES_MASK_DDR_MODE, st->ddr);
> +	}
> +
>  	ret = regmap_write(st->regmap, AD4030_REG_MODES, reg_modes);
>  	if (ret)
>  		return ret;

We will need a separate patch to add support for dtr_caps and dtr_mode to the
axi-spi-engine driver. And likely some HDL work for that as well. So I would
suggest splitting this out into a separate series.

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 10/15] dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode
  2025-08-30  0:43 ` [PATCH 10/15] dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode Marcelo Schmitt
@ 2025-08-30 18:02   ` David Lechner
  0 siblings, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 18:02 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, marcelo.schmitt1

On 8/29/25 7:43 PM, Marcelo Schmitt wrote:
> AD4030 and similar designs support three different options for the clock
> that frames ADC output data. Each option implies a different hardware
> configuration for reading ADC data. Document AD4030 clock mode options.
> 
> Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  .../devicetree/bindings/iio/adc/adi,ad4030.yaml      | 12 ++++++++++++
>  1 file changed, 12 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> index bee85087a7b2..1e4e025b835f 100644
> --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> @@ -78,6 +78,18 @@ properties:
>    interrupt-names:
>      const: busy
>  
> +  adi,clock-mode:
> +    $ref: /schemas/types.yaml#/definitions/string
> +    enum: [ spi, echo, host ]
> +    default: spi
> +    description:
> +      Describes how the clock that frames ADC data output is setup.
> +      spi  - Spi-compatible. Normal SPI operation clocking.
> +      echo - Echo-clock. Synchronous clock echoing to ease timing requirements
> +             when using isolation on the digital interface.
> +      host - Host. The Host clock mode uses an internal oscillator to clock out
> +             the data bits. In this mode, the spi controller is not driving SCLK.
> +
>  required:
>    - compatible
>    - reg

I think this would make sense as a common property in spi-peripheral-props.yaml
as this is something that is not specific to just this ADC and also requires
a supporting SPI controller with the matching wiring.

I would also tweak the names and descriptions a bit to describe how it is wired
rather than how it is used.

  spi-sclk-source:
    enum: [ controller, echo, peripheral ]
    default: controller
    description: |
      Indicates how the SCLK is wired.
      controller: The SCLK line is driven by the controller (typical SPI bus).
      echo: The SCLK line is driven by the controller and the peripheral echos
        the clock back to an input on the controller on a second line.
      peripheral: The SCLK line from the controller is not connected to the
        peripheral and an independent clock output driven by the peripheral is
        connected to an input on the controller.



^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled
  2025-08-30  0:40 ` [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled Marcelo Schmitt
  2025-08-30  5:00   ` Andy Shevchenko
@ 2025-08-30 18:43   ` Jonathan Cameron
  2025-08-30 18:48     ` David Lechner
  2025-09-02 13:18     ` Marcelo Schmitt
  1 sibling, 2 replies; 43+ messages in thread
From: Jonathan Cameron @ 2025-08-30 18:43 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On Fri, 29 Aug 2025 21:40:24 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:

> Previously, the AD4030 driver was using the number of scan realbits for the
> voltage channel to derive the scale to millivolts. Though, when sample
> averaging is enabled (oversampling_ratio > 1), the number of scan realbits
> for the channel is set to 30 and doesn't match the amount of conversion
> precision bits. Due to that, the calculated channel scale did not correctly
> scale raw sample data to millivolt units in those cases. Use chip specific
> precision bits to derive the correct channel _scale on every and all
> channel configuration.
> 
> Fixes: dc78e71d7c15 ("iio: adc: ad4030: remove some duplicate code")
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>

Hi Marcelo

I was assuming that when this said 'averaging' it actually meant
summing (there is a note about using the upper precision bits to get the same
scaling which is what we'd expect it were simply summing over X samples).

So given that we don't divide back down to get the original scaling I'm
not following how this works.

E.g. If we 'averaged' just 2 values of 3 then we'd go from a value of 3 to
one of 6.  Therefore I'd expect the scale to halve as each lsb represents
half the voltage it did when we weren't averaging those 2 samples.

I think that is what we'd see with the current code, so my reasoning is
clearly wrong, but why?

Jonathan

> ---
> This was probalby buggy since 
> commit 949abd1ca5a4 ("iio: adc: ad4030: add averaging support")
> but I decided to set the fixes tag with dc78e71d7c15 because this patch will
> not apply cleanly over 949abd1ca5a4.
> 
>  drivers/iio/adc/ad4030.c | 9 ++++++++-
>  1 file changed, 8 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index 1bc2f9a22470..82784593f976 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -394,7 +394,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
>  	else
>  		*val = st->vref_uv / MILLI;
>  
> -	*val2 = scan_type->realbits;
> +	/*
> +	 * Even though the sample data comes in a 30-bit chunk when the ADC
> +	 * is averaging samples, the conversion precision is still 16-bit or
> +	 * 24-bit depending on the device. Thus, instead of scan_type->realbits,
> +	 * use chip specific precision bits to derive the correct scale to mV.
> +	 */
> +	*val2 = scan_type->realbits == 30 ? st->chip->precision_bits
> +					  : scan_type->realbits;
>  
>  	return IIO_VAL_FRACTIONAL_LOG2;
>  }


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 14/15] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
  2025-08-30  0:45 ` [PATCH 14/15] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
@ 2025-08-30 18:45   ` David Lechner
  0 siblings, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 18:45 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On 8/29/25 7:45 PM, Marcelo Schmitt wrote:
> ADAQ4216 and ADAQ4224 are similar to AD4030 except ADAQ devices have a PGA
> (programmable gain amplifier) that scales the input signal prior to it
> reaching the ADC inputs. The PGA is controlled through a couple of pins (A0
> and A1) that set one of four possible signal gain.
> 
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> The PGA doc was inspired on ad7191 dt-binding and uses the same properies (but
> with different values) to describe the hardware.
> 
>  .../bindings/iio/adc/adi,ad4030.yaml          | 39 +++++++++++++++++++
>  1 file changed, 39 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> index 9adb60629631..36fd2aa51922 100644
> --- a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> @@ -19,6 +19,8 @@ description: |
>    * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4030-24-4032-24.pdf
>    * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4630-24_ad4632-24.pdf
>    * https://www.analog.com/media/en/technical-documentation/data-sheets/ad4630-16-4632-16.pdf
> +  * https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4216.pdf
> +  * https://www.analog.com/media/en/technical-documentation/data-sheets/adaq4224.pdf
>  
>  $ref: /schemas/spi/spi-peripheral-props.yaml#
>  
> @@ -31,6 +33,8 @@ properties:
>        - adi,ad4630-24
>        - adi,ad4632-16
>        - adi,ad4632-24
> +      - adi,adaq4216
> +      - adi,adaq4224
>  
>    reg:
>      maxItems: 1
> @@ -64,6 +68,27 @@ properties:
>        The Reset Input (/RST). Used for asynchronous device reset.
>      maxItems: 1
>  
> +  pga-gpios:
> +    description:
> +      A0 and A1 pins for gain selection. For devices that have PGA configuration
> +      input pins, pga-gpios should be defined if adi,gain-milli is absent.
> +    minItems: 2
> +    maxItems: 2
> +
> +  adi,pga-value:
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    description: |
> +      Should be present if PGA control inputs are pin-strapped. The values
> +      specify the gain per mille. For example, 333 means the input signal is
> +      scaled by a 0.333 factor (i.e. attenuated to one third of it's original
> +      magnitude). Possible values:
> +      Gain 333 (A1=0, A0=0)
> +      Gain 556 (A1=0, A0=1)
> +      Gain 2222 (A1=1, A0=0)
> +      Gain 6667 (A1=1, A0=1)
> +      If defined, pga-gpios must be absent.
> +    enum: [333, 556, 2222, 6667]
> +

It looks like these chips have some different power supplies
as well. E.g. V_DDH, VDD_FDA, VSS_FDA, VLDO. And there is only
REFIN, no REF.

>    pwms:
>      description: PWM signal connected to the CNV pin.
>      maxItems: 1
> @@ -120,6 +145,20 @@ allOf:
>      then:
>        properties:
>          adi,dual-data-rate: false
> +  # ADAQ devices require a gain property to indicate how hardware PGA is set
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,adaq4216
> +              - adi,adaq4224

Could use pattern instead:

		pattern: ^adi,adaq

> +    then:
> +      oneOf:
> +        - required:
> +            - adi,pga-value
> +        - required:
> +            - pga-gpios

  	else:
	  adi,pga-value: false
	  pga-gpios: false

>  
>  unevaluatedProperties: false
>  


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled
  2025-08-30 18:43   ` Jonathan Cameron
@ 2025-08-30 18:48     ` David Lechner
  2025-09-02 13:18     ` Marcelo Schmitt
  1 sibling, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 18:48 UTC (permalink / raw)
  To: Jonathan Cameron, Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi,
	Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh, krzk+dt,
	conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko, ahaslam,
	marcelo.schmitt1

On 8/30/25 1:43 PM, Jonathan Cameron wrote:
> On Fri, 29 Aug 2025 21:40:24 -0300
> Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> 
>> Previously, the AD4030 driver was using the number of scan realbits for the
>> voltage channel to derive the scale to millivolts. Though, when sample
>> averaging is enabled (oversampling_ratio > 1), the number of scan realbits
>> for the channel is set to 30 and doesn't match the amount of conversion
>> precision bits. Due to that, the calculated channel scale did not correctly
>> scale raw sample data to millivolt units in those cases. Use chip specific
>> precision bits to derive the correct channel _scale on every and all
>> channel configuration.
>>
>> Fixes: dc78e71d7c15 ("iio: adc: ad4030: remove some duplicate code")
>> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> 
> Hi Marcelo
> 
> I was assuming that when this said 'averaging' it actually meant
> summing (there is a note about using the upper precision bits to get the same
> scaling which is what we'd expect it were simply summing over X samples).
> 
> So given that we don't divide back down to get the original scaling I'm
> not following how this works.

I had the same feeling. I have some hardware I can test later this week.


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
  2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
  2025-08-30  7:36   ` Andy Shevchenko
  2025-08-30 12:08   ` kernel test robot
@ 2025-08-30 19:11   ` Jonathan Cameron
  2025-08-30 20:14   ` David Lechner
  3 siblings, 0 replies; 43+ messages in thread
From: Jonathan Cameron @ 2025-08-30 19:11 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, linux-kernel, linux-doc, devicetree, linux-spi,
	Michael.Hennerich, nuno.sa, eblanc, dlechner, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, tgamblin, marcelo.schmitt1

On Fri, 29 Aug 2025 21:42:50 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:

> AD4030 and similar ADCs can capture data at sample rates up to 2 mega
> samples per second (MSPS). Not all SPI controllers are able to achieve
> such high throughputs and even when the controller is fast enough to run
> transfers at the required speed, it may be costly to the CPU to handle
> transfer data at such high sample rates.  Add SPI offload support for
> AD4030 and similar ADCs so to enable ADC data capture at maximum sample
> rates.
> 
> Cc: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Cc: Nuno Sa <nuno.sa@analog.com>
> Cc: Trevor Gamblin <tgamblin@baylibre.com>
> Cc: Axel Haslam <ahaslam@baylibre.com>
> Cc: David Lechner <dlechner@baylibre.com>
> Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Co-developed-by: Nuno Sa <nuno.sa@analog.com>
> Signed-off-by: Nuno Sa <nuno.sa@analog.com>
> Co-developed-by: Trevor Gamblin <tgamblin@baylibre.com>
> Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
> Co-developed-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Most of the code in this patch is based on work from Sergiu Cuciurean, Nuno Sa,
> Axel Haslam, and Trevor Gamblin, hence the many co-developed-by tags. I also
> draw inspiration from other drivers supporting SPI offload, many of them written
> by David Lechner.

A few things inline. 

> +
> +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
> +{
> +	struct ad4030_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (PTR_ERR_OR_ZERO(st->offload))
> +		return -EINVAL;
> +
> +	if (!freq || freq > st->chip->max_sample_rate_hz)
> +		return -EINVAL;
> +
> +	ret = __ad4030_set_sampling_freq(st, freq);
> +	iio_device_release_direct(indio_dev);
Where is the claim?
> +
> +	return ret;
> +}


>  
>  static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
> @@ -903,6 +1038,67 @@ static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = {
>  	.validate_scan_mask = ad4030_validate_scan_mask,
>  };
>  
> +static int ad4030_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4030_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = regmap_write(st->regmap, AD4030_REG_EXIT_CFG_MODE, BIT(0));
> +	if (ret)
> +		return ret;
> +
> +	st->offload_msg.offload = st->offload;
> +	ret = spi_optimize_message(st->spi, &st->offload_msg);
> +	if (ret < 0)
> +		goto out_reset_mode;
> +
> +	ret = pwm_set_waveform_might_sleep(st->conv_trigger, &st->conv_wf, false);
> +	if (ret)
> +		goto out_unoptimize;
> +
> +	ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
> +					 &st->offload_trigger_config);
> +	if (ret)
> +		goto out_pwm_disable;
Blank line here.

> +	return 0;
Blank line here.

> +out_pwm_disable:
> +	pwm_disable(st->conv_trigger);
> +out_unoptimize:
> +	spi_unoptimize_message(&st->offload_msg);
> +out_reset_mode:
> +	/* reenter register configuration mode */
> +	ret = ad4030_enter_config_mode(st);
> +	if (ret)
> +		dev_warn(&st->spi->dev,
> +			 "couldn't reenter register configuration mode\n");
> +	return ret;
> +}

> +
> +static void ad4030_prepare_offload_msg(struct ad4030_state *st)
> +{
> +	u8 data_width = st->chip->precision_bits;
> +	u8 offload_bpw;
> +
> +	if (st->lane_mode == AD4030_LANE_MD_INTERLEAVED)
> +		/*
> +		 * This means all channels on 1 lane.
> +		 */

Single line comment looks like enough here.

> +		offload_bpw = data_width * st->chip->num_voltage_inputs;
> +	else
> +		offload_bpw  = data_width;
> +
> +	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
> +	st->offload_xfer.bits_per_word = offload_bpw;
> +	st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(offload_bpw));
> +	st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> +	spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
> +}


> @@ -1103,6 +1393,20 @@ static const struct iio_scan_type ad4030_24_scan_types[] = {
>  		.shift = 2,
>  		.endianness = IIO_BE,
>  	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_NORMAL] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 24,
> +		.shift = 0,
> +		.endianness = IIO_CPU,
> +	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_AVG] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 30,
> +		.shift = 2,
> +		.endianness = IIO_CPU,
> +	},
>  };
>  
>  static const struct iio_scan_type ad4030_16_scan_types[] = {
> @@ -1119,7 +1423,21 @@ static const struct iio_scan_type ad4030_16_scan_types[] = {
>  		.realbits = 30,
>  		.shift = 2,
>  		.endianness = IIO_BE,
> -	}
> +	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_NORMAL] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 16,
> +		.shift = 0,
> +		.endianness = IIO_CPU,
> +	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_AVG] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 30,
> +		.shift = 2,
> +		.endianness = IIO_CPU,
> +	},
>  };
>  
>  static const struct ad4030_chip_info ad4030_24_chip_info = {
> @@ -1130,10 +1448,15 @@ static const struct ad4030_chip_info ad4030_24_chip_info = {
>  		AD4030_CHAN_CMO(1, 0),
>  		IIO_CHAN_SOFT_TIMESTAMP(2),
>  	},
> +	.offload_channels = {
> +		AD4030_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),

This array still has the non offload cases.  Do they make sense?

> +		AD4030_CHAN_CMO(1, 0),
> +	},
>  	.grade = AD4030_REG_CHIP_GRADE_AD4030_24_GRADE,
>  	.precision_bits = 24,
>  	.num_voltage_inputs = 1,
>  	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> +	.max_sample_rate_hz = 2 * MEGA,
>  };

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
  2025-08-30  0:45 ` [PATCH 15/15] iio: adc: ad4030: Add support for " Marcelo Schmitt
  2025-08-30  7:57   ` Andy Shevchenko
@ 2025-08-30 19:17   ` David Lechner
  2025-09-01 11:47   ` Dan Carpenter
  2 siblings, 0 replies; 43+ messages in thread
From: David Lechner @ 2025-08-30 19:17 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, marcelo.schmitt1

On 8/29/25 7:45 PM, Marcelo Schmitt wrote:
> ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
> that scales the analog input signal prior to it reaching the ADC. The PGA
> is controlled through a pair of pins (A0 and A1) whose state define the
> gain that is applied to the input signal.
> 
> Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
> through the IIO device channel scale available interface and enable control
> of the PGA through the channel scale interface.
> 
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  drivers/iio/adc/ad4030.c | 239 ++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 235 insertions(+), 4 deletions(-)
> 
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index 37ba00097efe..32157b3a0420 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -21,6 +21,7 @@
>  #include <linux/iio/trigger_consumer.h>
>  #include <linux/iio/triggered_buffer.h>
>  #include <linux/log2.h>
> +#include <linux/minmax.h>
>  #include <linux/pwm.h>
>  #include <linux/regmap.h>
>  #include <linux/regulator/consumer.h>
> @@ -42,6 +43,8 @@
>  #define     AD4030_REG_CHIP_GRADE_AD4630_24_GRADE	0x00
>  #define     AD4030_REG_CHIP_GRADE_AD4632_16_GRADE	0x05
>  #define     AD4030_REG_CHIP_GRADE_AD4632_24_GRADE	0x02
> +#define     AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE	0x1E
> +#define     AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE	0x1C
>  #define     AD4030_REG_CHIP_GRADE_MASK_CHIP_GRADE	GENMASK(7, 3)
>  #define AD4030_REG_SCRATCH_PAD			0x0A
>  #define AD4030_REG_SPI_REVISION			0x0B
> @@ -121,6 +124,10 @@
>  /* Datasheet says 9.8ns, so use the closest integer value */
>  #define AD4030_TQUIET_CNV_DELAY_NS	10
>  
> +/* HARDWARE_GAIN */
> +#define ADAQ4616_PGA_PINS		2
> +#define ADAQ4616_GAIN_MAX_NANO		6666666667
> +
>  enum ad4030_out_mode {
>  	AD4030_OUT_DATA_MD_DIFF,
>  	AD4030_OUT_DATA_MD_16_DIFF_8_COM,
> @@ -149,6 +156,20 @@ enum {
>  	AD4030_OFFLOAD_SCAN_TYPE_AVG,
>  };
>  
> +/*
> + * Gains computed as fractions of 1000 so they can be expressed by integers.
> + */
> +static const int ad4030_hw_gains[] = {
> +	333, 556, 2222, 6667,
> +};
> +
> +static const int ad4030_hw_gains_frac[4][2] = {
> +	{ 1, 3 },  /* 1/3 gain */
> +	{ 5, 9 },  /* 5/9 gain */
> +	{ 20, 9 }, /* 20/9 gain */
> +	{ 20, 3 }, /* 20/3 gain */
> +};
> +
>  struct ad4030_chip_info {
>  	const char *name;
>  	const unsigned long *available_masks;
> @@ -160,6 +181,7 @@ struct ad4030_chip_info {
>  	int num_voltage_inputs;
>  	unsigned int tcyc_ns;
>  	unsigned int max_sample_rate_hz;
> +	unsigned int num_pga_pins;

This is only usesd for boolean checks, so perhaps:

	bool has_pga;

>  };
>  
>  struct ad4030_state {
> @@ -183,6 +205,10 @@ struct ad4030_state {
>  	struct spi_offload *offload;
>  	struct spi_offload_trigger *offload_trigger;
>  	struct spi_offload_trigger_config offload_trigger_config;
> +	struct gpio_descs *pga_gpios;
> +	int pga_index;
> +	unsigned int scale_avail[ARRAY_SIZE(ad4030_hw_gains)][2];
> +	size_t scale_avail_size;
>  
>  	/*
>  	 * DMA (thus cache coherency maintenance) requires the transfer buffers
> @@ -239,7 +265,7 @@ struct ad4030_state {
>   * - voltage0-voltage1
>   * - voltage2-voltage3
>   */
> -#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload) {		\
> +#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload, _pga) {		\
>  	.info_mask_shared_by_all =					\
>  		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),			\
>  	.info_mask_shared_by_all_available =				\
> @@ -250,6 +276,7 @@ struct ad4030_state {
>  		BIT(IIO_CHAN_INFO_CALIBBIAS) |				\
>  		BIT(IIO_CHAN_INFO_RAW),					\
>  	.info_mask_separate_available = BIT(IIO_CHAN_INFO_CALIBBIAS) |	\
> +		(_pga ? BIT(IIO_CHAN_INFO_SCALE) : 0) |			\
>  		BIT(IIO_CHAN_INFO_CALIBSCALE),				\
>  	.type = IIO_VOLTAGE,						\
>  	.indexed = 1,							\
> @@ -264,10 +291,16 @@ struct ad4030_state {
>  }
>  
>  #define AD4030_CHAN_DIFF(_idx, _scan_type)				\
> -	__AD4030_CHAN_DIFF(_idx, _scan_type, 0)
> +	__AD4030_CHAN_DIFF(_idx, _scan_type, 0, 0)
>  
>  #define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
> -	__AD4030_CHAN_DIFF(_idx, _scan_type, 1)
> +	__AD4030_CHAN_DIFF(_idx, _scan_type, 1, 0)
> +
> +#define ADAQ4216_CHAN_DIFF(_idx, _scan_type)				\
> +	__AD4030_CHAN_DIFF(_idx, _scan_type, 0, 1)
> +
> +#define ADAQ4216_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
> +	__AD4030_CHAN_DIFF(_idx, _scan_type, 1, 1)
>  
>  static const int ad4030_rx_bus_width[] = {
>  	1, 2, 4, 8,
> @@ -429,6 +462,74 @@ static const struct regmap_config ad4030_regmap_config = {
>  	.max_register = AD4030_REG_DIG_ERR,
>  };
>  
> +static void ad4030_fill_scale_avail(struct ad4030_state *st)
> +{
> +	unsigned int mag_bits, tmp0, tmp1, i;
> +	u64 range;
> +
> +	/*
> +	 * The maximum precision of differential channels is retrieved from the
> +	 * chip properties. The output code of differential channels is in two's
> +	 * complement format (i.e. signed), so the MSB is the sign bit and only
> +	 * (precision_bits - 1) bits express voltage magnitude.
> +	 */
> +	mag_bits = st->chip->precision_bits - 1;

Seems odd that function below checks for if (scan_type->sign == 's') but this
doesn't.

> +
> +	for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
> +		range = mult_frac(st->vref_uv, ad4030_hw_gains_frac[i][1],
> +				  ad4030_hw_gains_frac[i][0]);
> +		/*
> +		 * If range were in mV, we would multiply it by NANO below.
> +		 * Though, range is in µV so multiply it by MICRO only so the
> +		 * result after right shift and division scales output codes to
> +		 * millivolts.
> +		 */
> +		tmp0 = div_u64_rem(((u64)range * MICRO) >> mag_bits, NANO, &tmp1);
> +		st->scale_avail[i][0] = tmp0; /* Integer part */
> +		st->scale_avail[i][1] = tmp1; /* Fractional part */

Could just give the variables meaningful names and avoid the comments.

> +	}
> +}
> +
> +static int ad4030_set_pga_gain(struct ad4030_state *st)
> +{
> +	DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { };
> +
> +	bitmap_write(bitmap, st->pga_index, 0, 2);

Use ADAQ4616_PGA_PINS here instead of 2?

> +
> +	return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap);
> +}
> +
> +static int ad4030_set_pga(struct iio_dev *indio_dev,
> +			  struct iio_chan_spec const *chan, int gain_int,
> +			  int gain_fract)
> +{
> +	struct ad4030_state *st = iio_priv(indio_dev);
> +	const struct iio_scan_type *scan_type;
> +	unsigned int mag_bits;
> +	u64 gain_nano, tmp;
> +
> +	if (!st->pga_gpios)
> +		return -EINVAL;
> +
> +	scan_type = iio_get_current_scan_type(indio_dev, chan);

Need to check for error.

> +	if (scan_type->sign == 's')
> +		mag_bits = st->chip->precision_bits - 1;
> +	else
> +		mag_bits = st->chip->precision_bits;
> +
> +	gain_nano = gain_int * NANO + gain_fract;
> +
> +	if (!in_range(gain_nano, 0, ADAQ4616_GAIN_MAX_NANO))
> +		return -EINVAL;
> +
> +	tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
> +	gain_nano = DIV_ROUND_CLOSEST_ULL(st->vref_uv, tmp);
> +	st->pga_index = find_closest(gain_nano, ad4030_hw_gains,
> +				     ARRAY_SIZE(ad4030_hw_gains));
> +
> +	return ad4030_set_pga_gain(st);
> +}
> +
>  static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
>  				 struct iio_chan_spec const *chan,
>  				 int *val,
> @@ -455,7 +556,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
>  	*val2 = scan_type->realbits == 30 ? st->chip->precision_bits
>  					  : scan_type->realbits;
>  
> -	return IIO_VAL_FRACTIONAL_LOG2;
> +	/* The LSB of the 8-bit common-mode data is always vref/256. */
> +	if (scan_type->realbits == 8 || !st->chip->num_pga_pins)

`if` statement should be earlier so that we don't set *val, *val2
twice.

> +		return IIO_VAL_FRACTIONAL_LOG2;
> +
> +	*val = st->scale_avail[st->pga_index][0];
> +	*val2 = st->scale_avail[st->pga_index][1];
> +
> +	return IIO_VAL_INT_PLUS_NANO;
>  }
>  
>  static int ad4030_get_chan_calibscale(struct iio_dev *indio_dev,
> @@ -654,6 +762,19 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev,
>  				 st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
>  }
>  
> +static int ad4030_write_raw_get_fmt(struct iio_dev *indio_dev,
> +				    struct iio_chan_spec const *chan, long mask)
> +{
> +	switch (mask) {
> +	case IIO_CHAN_INFO_SCALE:
> +		return IIO_VAL_INT_PLUS_NANO;
> +	default:
> +		return IIO_VAL_INT_PLUS_MICRO;
> +	}
> +
> +	return -EINVAL;

Unreachable code.

> +}
> +
>  static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
>  {
>  	struct ad4030_state *st = iio_priv(dev);
> @@ -891,6 +1012,15 @@ static int ad4030_read_avail(struct iio_dev *indio_dev,
>  		*length = ARRAY_SIZE(ad4030_average_modes);
>  		return IIO_AVAIL_LIST;
>  
> +	case IIO_CHAN_INFO_SCALE:
> +		if (!st->pga_gpios)

		if (st->scale_avail_size == 1)

would make more sense here.

> +			*vals = (int *)st->scale_avail[st->pga_index];
> +		else
> +			*vals = (int *)st->scale_avail;
> +		*length = st->scale_avail_size * 2; /* print int and nano part */
> +		*type = IIO_VAL_INT_PLUS_NANO;
> +		return IIO_AVAIL_LIST;
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -966,6 +1096,9 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
>  	case IIO_CHAN_INFO_SAMP_FREQ:
>  		return ad4030_set_sampling_freq(indio_dev, val);
>  
> +	case IIO_CHAN_INFO_SCALE:
> +		return ad4030_set_pga(indio_dev, chan, val, val2);
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -1037,6 +1170,7 @@ static const struct iio_info ad4030_iio_info = {
>  	.read_avail = ad4030_read_avail,
>  	.read_raw = ad4030_read_raw,
>  	.write_raw = ad4030_write_raw,
> +	.write_raw_get_fmt = &ad4030_write_raw_get_fmt,
>  	.debugfs_reg_access = ad4030_reg_access,
>  	.read_label = ad4030_read_label,
>  	.get_current_scan_type = ad4030_get_current_scan_type,
> @@ -1318,6 +1452,51 @@ static int ad4030_spi_offload_setup(struct iio_dev *indio_dev,
>  							   IIO_BUFFER_DIRECTION_IN);
>  }
>  
> +static int ad4030_setup_pga(struct device *dev, struct iio_dev *indio_dev,
> +			    struct ad4030_state *st)
> +{
> +	unsigned int i;
> +	int pga_value;
> +	int ret;
> +
> +	ret = device_property_read_u32(dev, "adi,pga-value", &pga_value);
> +	if (ret && ret != -EINVAL)
> +		return dev_err_probe(dev, ret, "Failed to get PGA value.\n");
> +
> +	if (ret == -EINVAL) {
> +		/* Setup GPIOs for PGA control */
> +		st->pga_gpios = devm_gpiod_get_array(dev, "pga", GPIOD_OUT_LOW);
> +		if (IS_ERR(st->pga_gpios))
> +			return dev_err_probe(dev, PTR_ERR(st->pga_gpios),
> +					     "Failed to get PGA gpios.\n");
> +
> +		if (st->pga_gpios->ndescs != 2)

s/2/ADAQ4616_PGA_PINS/?

> +			return dev_err_probe(dev, -EINVAL,
> +					     "Expected 2 GPIOs for PGA control.\n");
> +
> +		st->scale_avail_size = ARRAY_SIZE(ad4030_hw_gains);
> +		st->pga_index = 0;
> +		return ad4030_set_pga_gain(st);

We already intialized the array to GPIO_OUT_LOW, so isn't calling
ad4030_set_pga_gain() here with index of 0 redundant?

> +	}
> +
> +	/* Set ADC driver to handle pin-strapped PGA pins setup */
> +	for (i = 0; i < ARRAY_SIZE(ad4030_hw_gains); i++) {
> +		if (pga_value != ad4030_hw_gains[i])
> +			continue;
> +
> +		st->pga_index = i;
> +		break;
> +	}
> +	if (i == ARRAY_SIZE(ad4030_hw_gains))
> +		return dev_err_probe(dev, -EINVAL, "Invalid PGA value: %d.\n",
> +				     pga_value);
> +
> +	st->scale_avail_size = 1;
> +	st->pga_gpios = NULL;

This seems reduandant.

> +
> +	return 0;
> +}
> +
>  static int ad4030_probe(struct spi_device *spi)
>  {
>  	struct device *dev = &spi->dev;
> @@ -1360,6 +1539,14 @@ static int ad4030_probe(struct spi_device *spi)
>  	if (ret)
>  		return ret;
>  
> +	if (st->chip->num_pga_pins > 0) {
> +		ret = ad4030_setup_pga(dev, indio_dev, st);
> +		if (ret)
> +			return ret;
> +
> +		ad4030_fill_scale_avail(st);
> +	}
> +
>  	ret = ad4030_config(st);
>  	if (ret)
>  		return ret;
> @@ -1611,12 +1798,54 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
>  	.max_sample_rate_hz = 500 * KILO,
>  };
>  
> +static const struct ad4030_chip_info adaq4216_chip_info = {
> +	.name = "adaq4216",
> +	.available_masks = ad4030_channel_masks,
> +	.channels = {
> +		ADAQ4216_CHAN_DIFF(0, ad4030_16_scan_types),
> +		AD4030_CHAN_CMO(1, 0),
> +		IIO_CHAN_SOFT_TIMESTAMP(2),
> +	},
> +	.offload_channels = {
> +		ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_16_scan_types),
> +		AD4030_CHAN_CMO(1, 0),
> +	},
> +	.grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE,
> +	.precision_bits = 16,
> +	.num_voltage_inputs = 1,
> +	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> +	.max_sample_rate_hz = 2 * MEGA,
> +	.num_pga_pins = ADAQ4616_PGA_PINS,
> +};
> +
> +static const struct ad4030_chip_info adaq4224_chip_info = {
> +	.name = "adaq4224",
> +	.available_masks = ad4030_channel_masks,
> +	.channels = {
> +		ADAQ4216_CHAN_DIFF(0, ad4030_24_scan_types),
> +		AD4030_CHAN_CMO(1, 0),
> +		IIO_CHAN_SOFT_TIMESTAMP(2),
> +	},
> +	.offload_channels = {
> +		ADAQ4216_OFFLOAD_CHAN_DIFF(0, ad4030_24_scan_types),
> +		AD4030_CHAN_CMO(1, 0),
> +	},
> +	.grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE,
> +	.precision_bits = 24,
> +	.num_voltage_inputs = 1,
> +	.tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
> +	.max_sample_rate_hz = 2 * MEGA,
> +	.num_pga_pins = ADAQ4616_PGA_PINS,
> +};
> +
>  static const struct spi_device_id ad4030_id_table[] = {
>  	{ "ad4030-24", (kernel_ulong_t)&ad4030_24_chip_info },
>  	{ "ad4630-16", (kernel_ulong_t)&ad4630_16_chip_info },
>  	{ "ad4630-24", (kernel_ulong_t)&ad4630_24_chip_info },
>  	{ "ad4632-16", (kernel_ulong_t)&ad4632_16_chip_info },
>  	{ "ad4632-24", (kernel_ulong_t)&ad4632_24_chip_info },
> +	{ "adaq4216", (kernel_ulong_t)&adaq4216_chip_info },
> +	{ "adaq4224", (kernel_ulong_t)&adaq4224_chip_info },
>  	{ }
>  };
>  MODULE_DEVICE_TABLE(spi, ad4030_id_table);
> @@ -1627,6 +1856,8 @@ static const struct of_device_id ad4030_of_match[] = {
>  	{ .compatible = "adi,ad4630-24", .data = &ad4630_24_chip_info },
>  	{ .compatible = "adi,ad4632-16", .data = &ad4632_16_chip_info },
>  	{ .compatible = "adi,ad4632-24", .data = &ad4632_24_chip_info },
> +	{ .compatible = "adi,adaq4216", .data = &adaq4216_chip_info },
> +	{ .compatible = "adi,adaq4224", .data = &adaq4224_chip_info },
>  	{ }
>  };
>  MODULE_DEVICE_TABLE(of, ad4030_of_match);


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
  2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
                     ` (2 preceding siblings ...)
  2025-08-30 19:11   ` Jonathan Cameron
@ 2025-08-30 20:14   ` David Lechner
  2025-09-02 14:52     ` Marcelo Schmitt
  3 siblings, 1 reply; 43+ messages in thread
From: David Lechner @ 2025-08-30 20:14 UTC (permalink / raw)
  To: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi
  Cc: jic23, Michael.Hennerich, nuno.sa, eblanc, andy, corbet, robh,
	krzk+dt, conor+dt, broonie, Jonathan.Cameron, andriy.shevchenko,
	ahaslam, sergiu.cuciurean, tgamblin, marcelo.schmitt1

On 8/29/25 7:42 PM, Marcelo Schmitt wrote:
> AD4030 and similar ADCs can capture data at sample rates up to 2 mega
> samples per second (MSPS). Not all SPI controllers are able to achieve
> such high throughputs and even when the controller is fast enough to run
> transfers at the required speed, it may be costly to the CPU to handle
> transfer data at such high sample rates.  Add SPI offload support for
> AD4030 and similar ADCs so to enable ADC data capture at maximum sample
> rates.
> 
> Cc: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Cc: Nuno Sa <nuno.sa@analog.com>
> Cc: Trevor Gamblin <tgamblin@baylibre.com>
> Cc: Axel Haslam <ahaslam@baylibre.com>
> Cc: David Lechner <dlechner@baylibre.com>
> Co-developed-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Co-developed-by: Nuno Sa <nuno.sa@analog.com>
> Signed-off-by: Nuno Sa <nuno.sa@analog.com>
> Co-developed-by: Trevor Gamblin <tgamblin@baylibre.com>
> Signed-off-by: Trevor Gamblin <tgamblin@baylibre.com>
> Co-developed-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Axel Haslam <ahaslam@baylibre.com>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Most of the code in this patch is based on work from Sergiu Cuciurean, Nuno Sa,
> Axel Haslam, and Trevor Gamblin, hence the many co-developed-by tags. I also
> draw inspiration from other drivers supporting SPI offload, many of them written
> by David Lechner.
> 
>  drivers/iio/adc/Kconfig  |   2 +
>  drivers/iio/adc/ad4030.c | 400 ++++++++++++++++++++++++++++++++++++---
>  2 files changed, 378 insertions(+), 24 deletions(-)
> 
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 6de2abad0197..7cfbc07e7f77 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -49,6 +49,8 @@ config AD4030
>  	depends on GPIOLIB
>  	select REGMAP
>  	select IIO_BUFFER
> +	select IIO_BUFFER_DMA
> +	select IIO_BUFFER_DMAENGINE
>  	select IIO_TRIGGERED_BUFFER
>  	help
>  	  Say yes here to build support for Analog Devices AD4030 and AD4630 high speed
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index 82784593f976..68f76432dbfd 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -15,11 +15,15 @@
>  
>  #include <linux/bitfield.h>
>  #include <linux/clk.h>
> +#include <linux/dmaengine.h>
> +#include <linux/iio/buffer-dmaengine.h>
>  #include <linux/iio/iio.h>
>  #include <linux/iio/trigger_consumer.h>
>  #include <linux/iio/triggered_buffer.h>
> +#include <linux/pwm.h>
>  #include <linux/regmap.h>
>  #include <linux/regulator/consumer.h>
> +#include <linux/spi/offload/consumer.h>
>  #include <linux/spi/spi.h>
>  #include <linux/unaligned.h>
>  #include <linux/units.h>
> @@ -111,6 +115,8 @@
>  #define AD4632_TCYC_NS			2000
>  #define AD4632_TCYC_ADJUSTED_NS		(AD4632_TCYC_NS - AD4030_TCNVL_NS)
>  #define AD4030_TRESET_COM_DELAY_MS	750
> +/* Datasheet says 9.8ns, so use the closest integer value */
> +#define AD4030_TQUIET_CNV_DELAY_NS	10
>  
>  enum ad4030_out_mode {
>  	AD4030_OUT_DATA_MD_DIFF,
> @@ -120,7 +126,7 @@ enum ad4030_out_mode {
>  	AD4030_OUT_DATA_MD_32_PATTERN,
>  };
>  
> -enum {
> +enum ad4030_lane_mode {
>  	AD4030_LANE_MD_1_PER_CH,
>  	AD4030_LANE_MD_2_PER_CH,
>  	AD4030_LANE_MD_4_PER_CH,
> @@ -130,17 +136,21 @@ enum {
>  enum {
>  	AD4030_SCAN_TYPE_NORMAL,
>  	AD4030_SCAN_TYPE_AVG,
> +	AD4030_OFFLOAD_SCAN_TYPE_NORMAL,
> +	AD4030_OFFLOAD_SCAN_TYPE_AVG,
>  };
>  
>  struct ad4030_chip_info {
>  	const char *name;
>  	const unsigned long *available_masks;
>  	const struct iio_chan_spec channels[AD4030_MAX_IIO_CHANNEL_NB];
> +	const struct iio_chan_spec offload_channels[AD4030_MAX_IIO_CHANNEL_NB];
>  	u8 grade;
>  	u8 precision_bits;
>  	/* Number of hardware channels */
>  	int num_voltage_inputs;
>  	unsigned int tcyc_ns;
> +	unsigned int max_sample_rate_hz;
>  };
>  
>  struct ad4030_state {
> @@ -148,11 +158,20 @@ struct ad4030_state {
>  	struct regmap *regmap;
>  	const struct ad4030_chip_info *chip;
>  	struct gpio_desc *cnv_gpio;
> +	struct pwm_device *conv_trigger;
> +	struct pwm_waveform conv_wf;
>  	int vref_uv;
>  	int vio_uv;
>  	int offset_avail[3];
>  	unsigned int avg_log2;
>  	enum ad4030_out_mode mode;
> +	enum ad4030_lane_mode lane_mode;
> +	/* offload sampling spi message */
> +	struct spi_transfer offload_xfer;
> +	struct spi_message offload_msg;
> +	struct spi_offload *offload;
> +	struct spi_offload_trigger *offload_trigger;
> +	struct spi_offload_trigger_config offload_trigger_config;
>  
>  	/*
>  	 * DMA (thus cache coherency maintenance) requires the transfer buffers
> @@ -209,12 +228,13 @@ struct ad4030_state {
>   * - voltage0-voltage1
>   * - voltage2-voltage3
>   */
> -#define AD4030_CHAN_DIFF(_idx, _scan_type) {				\
> +#define __AD4030_CHAN_DIFF(_idx, _scan_type, _offload) {		\
>  	.info_mask_shared_by_all =					\
>  		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),			\
>  	.info_mask_shared_by_all_available =				\
>  		BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO),			\
>  	.info_mask_separate = BIT(IIO_CHAN_INFO_SCALE) |		\
> +		(_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) |		\

Sampling freqency should be shared_by_all, not separate. There is only
one conversion trigger and this is simultaneous sampling.

>  		BIT(IIO_CHAN_INFO_CALIBSCALE) |				\
>  		BIT(IIO_CHAN_INFO_CALIBBIAS) |				\
>  		BIT(IIO_CHAN_INFO_RAW),					\
> @@ -232,12 +252,23 @@ struct ad4030_state {
>  	.num_ext_scan_type = ARRAY_SIZE(_scan_type),			\
>  }
>  
> +#define AD4030_CHAN_DIFF(_idx, _scan_type)				\
> +	__AD4030_CHAN_DIFF(_idx, _scan_type, 0)
> +
> +#define AD4030_OFFLOAD_CHAN_DIFF(_idx, _scan_type)			\
> +	__AD4030_CHAN_DIFF(_idx, _scan_type, 1)
> +
>  static const int ad4030_average_modes[] = {
>  	1, 2, 4, 8, 16, 32, 64, 128,
>  	256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
>  	65536,
>  };
>  
> +static const struct spi_offload_config ad4030_offload_config = {
> +	.capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
> +			    SPI_OFFLOAD_CAP_RX_STREAM_DMA,
> +};
> +
>  static int ad4030_enter_config_mode(struct ad4030_state *st)
>  {
>  	st->tx_data[0] = AD4030_REG_ACCESS;
> @@ -385,7 +416,7 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
>  	struct ad4030_state *st = iio_priv(indio_dev);
>  	const struct iio_scan_type *scan_type;
>  
> -	scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels);
> +	scan_type = iio_get_current_scan_type(indio_dev, chan);

Is this actually a bug fix? Won't this change the scale of
the common mode voltage channels?

>  	if (IS_ERR(scan_type))
>  		return PTR_ERR(scan_type);
>  
> @@ -458,6 +489,96 @@ static int ad4030_get_chan_calibbias(struct iio_dev *indio_dev,
>  	}
>  }
>  
> +static void ad4030_get_sampling_freq(const struct ad4030_state *st, int *freq)
> +{
> +	*freq = DIV_ROUND_CLOSEST_ULL(NANO, st->conv_wf.period_length_ns);
> +}
> +
> +static int __ad4030_set_sampling_freq(struct ad4030_state *st, unsigned int freq)
> +{
> +	struct spi_offload_trigger_config *config = &st->offload_trigger_config;
> +	struct pwm_waveform conv_wf = { };
> +	u64 offload_period_ns;
> +	u64 offload_offset_ns;
> +	u32 mode;
> +	int ret;
> +	u64 target = AD4030_TCNVH_NS;
> +
> +	conv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
> +	/*
> +	 * The datasheet lists a minimum time of 9.8 ns, but no maximum. If the
> +	 * rounded PWM's value is less than 10, increase the target value by 10
> +	 * and attempt to round the waveform again, until the value is at least
> +	 * 10 ns. Use a separate variable to represent the target in case the
> +	 * rounding is severe enough to keep putting the first few results under
> +	 * the minimum 10ns condition checked by the while loop.
> +	 */
> +	do {
> +		conv_wf.duty_length_ns = target;
> +		ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
> +		if (ret)
> +			return ret;
> +		target += 10;
> +	} while (conv_wf.duty_length_ns < 10);
> +
> +	offload_period_ns = conv_wf.period_length_ns;
> +
> +	ret = regmap_read(st->regmap, AD4030_REG_MODES, &mode);
> +	if (ret)
> +		return ret;
> +	if (FIELD_GET(AD4030_REG_MODES_MASK_OUT_DATA_MODE, mode) == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF) {

Since this depends on the oversampling ration, we need to defer this
until we start a buffered read. Otherwise if someone sets sampling
frequency first and the changes the oversampling ratio later, then
the PWM period will not be correct.

Alternatly, we could update this both when sampling freqency and
when oversampling ratio are updated. This would allow returning an
error if the oversampling ratio is too big for the requested
sampling frequency.

> +		u32 avg;
> +
> +		ret = regmap_read(st->regmap, AD4030_REG_AVG, &avg);
> +		if (ret)
> +			return ret;
> +
> +		offload_period_ns <<= FIELD_GET(AD4030_REG_AVG_MASK_AVG_VAL, avg);
> +	}
> +
> +	config->periodic.frequency_hz =  DIV_ROUND_UP_ULL(NSEC_PER_SEC,
> +							  offload_period_ns);
> +
> +	/*
> +	 * The hardware does the capture on zone 2 (when spi trigger PWM
> +	 * is used). This means that the spi trigger signal should happen at
> +	 * tsync + tquiet_con_delay being tsync the conversion signal period
> +	 * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
> +	 *
> +	 * The PWM waveform API only supports nanosecond resolution right now,
> +	 * so round this setting to the closest available value.
> +	 */
> +	offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS;
> +	do {
> +		config->periodic.offset_ns = offload_offset_ns;
> +		ret = spi_offload_trigger_validate(st->offload_trigger, config);
> +		if (ret)
> +			return ret;
> +		offload_offset_ns += 10;
> +
> +	} while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
> +
> +	st->conv_wf = conv_wf;
> +
> +	return 0;
> +}
> +
> +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
> +{
> +	struct ad4030_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	if (PTR_ERR_OR_ZERO(st->offload))
> +		return -EINVAL;
> +
> +	if (!freq || freq > st->chip->max_sample_rate_hz)
> +		return -EINVAL;
> +
> +	ret = __ad4030_set_sampling_freq(st, freq);
> +	iio_device_release_direct(indio_dev);
> +
> +	return ret;
> +}
>  static int ad4030_set_chan_calibscale(struct iio_dev *indio_dev,
>  				      struct iio_chan_spec const *chan,
>  				      int gain_int,
> @@ -618,7 +739,7 @@ static int ad4030_conversion(struct iio_dev *indio_dev)
>  	unsigned int i;
>  	int ret;
>  
> -	scan_type = iio_get_current_scan_type(indio_dev, st->chip->channels);
> +	scan_type = iio_get_current_scan_type(indio_dev, &indio_dev->channels[0]);

Since a single conversion doesn't use offload, I don't think we want to change this.

>  	if (IS_ERR(scan_type))
>  		return PTR_ERR(scan_type);
>  
> @@ -774,6 +895,13 @@ static int ad4030_read_raw_dispatch(struct iio_dev *indio_dev,
>  		*val = BIT(st->avg_log2);
>  		return IIO_VAL_INT;
>  
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		if (PTR_ERR_OR_ZERO(st->offload))
> +			return -EINVAL;
> +
> +		ad4030_get_sampling_freq(st, val);
> +		return IIO_VAL_INT;
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -814,6 +942,9 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
>  	case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
>  		return ad4030_set_avg_frame_len(indio_dev, val);
>  
> +	case IIO_CHAN_INFO_SAMP_FREQ:

This casts val from signed to unsigned, so probably should check for val < 0 first.

> +		return ad4030_set_sampling_freq(indio_dev, val);
> +
>  	default:
>  		return -EINVAL;
>  	}
> @@ -868,7 +999,11 @@ static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev,
>  {
>  	struct ad4030_state *st = iio_priv(indio_dev);
>  
> -	return st->avg_log2 ? AD4030_SCAN_TYPE_AVG : AD4030_SCAN_TYPE_NORMAL;
> +	if (PTR_ERR_OR_ZERO(st->offload))
> +		return st->avg_log2 ? AD4030_SCAN_TYPE_AVG : AD4030_SCAN_TYPE_NORMAL;
> +	else
> +		return st->avg_log2 ? AD4030_OFFLOAD_SCAN_TYPE_AVG :
> +				      AD4030_OFFLOAD_SCAN_TYPE_NORMAL;
>  }
>  
>  static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
> @@ -903,6 +1038,67 @@ static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = {
>  	.validate_scan_mask = ad4030_validate_scan_mask,
>  };
>  
> +static int ad4030_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> +	struct ad4030_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	ret = regmap_write(st->regmap, AD4030_REG_EXIT_CFG_MODE, BIT(0));
> +	if (ret)
> +		return ret;
> +
> +	st->offload_msg.offload = st->offload;
> +	ret = spi_optimize_message(st->spi, &st->offload_msg);
> +	if (ret < 0)
> +		goto out_reset_mode;
> +
> +	ret = pwm_set_waveform_might_sleep(st->conv_trigger, &st->conv_wf, false);
> +	if (ret)
> +		goto out_unoptimize;
> +
> +	ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
> +					 &st->offload_trigger_config);
> +	if (ret)
> +		goto out_pwm_disable;
> +	return 0;
> +out_pwm_disable:
> +	pwm_disable(st->conv_trigger);
> +out_unoptimize:
> +	spi_unoptimize_message(&st->offload_msg);
> +out_reset_mode:
> +	/* reenter register configuration mode */
> +	ret = ad4030_enter_config_mode(st);
> +	if (ret)
> +		dev_warn(&st->spi->dev,

		dev_err()

would even be appropriate since we can't do anything else, the hardware
is broken.

> +			 "couldn't reenter register configuration mode\n");
> +	return ret;
> +}
> +
> +static int ad4030_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> +	struct ad4030_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	pwm_disable(st->conv_trigger);
> +
> +	spi_offload_trigger_disable(st->offload, st->offload_trigger);

Probably should disable offload first, otherwise, we could possibly get
bad data after stopping CNV trigger but before stopping offload trigger.

> +
> +	spi_unoptimize_message(&st->offload_msg);
> +
> +	/* reenter register configuration mode */
> +	ret = ad4030_enter_config_mode(st);
> +	if (ret)
> +		dev_warn(&st->spi->dev,
> +			 "couldn't reenter register configuration mode\n");
> +
> +	return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4030_offload_buffer_setup_ops = {
> +	.postenable = &ad4030_offload_buffer_postenable,
> +	.predisable = &ad4030_offload_buffer_predisable,
> +};
> +
>  static int ad4030_regulators_get(struct ad4030_state *st)
>  {
>  	struct device *dev = &st->spi->dev;
> @@ -972,6 +1168,44 @@ static int ad4030_detect_chip_info(const struct ad4030_state *st)
>  	return 0;
>  }
>  
> +static int ad4030_pwm_get(struct ad4030_state *st)
> +{
> +	struct device *dev = &st->spi->dev;
> +
> +	st->conv_trigger = devm_pwm_get(dev, "cnv");

The DT bindings didn't have pwm-names, so this should be NULL.

> +	if (IS_ERR(st->conv_trigger))
> +		return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
> +				     "Failed to get cnv pwm\n");
> +
> +	/*
> +	 * Preemptively disable the PWM, since we only want to enable it with
> +	 * the buffer
> +	 */
> +	pwm_disable(st->conv_trigger);
> +
> +	return 0;
> +}
> +
> +static void ad4030_prepare_offload_msg(struct ad4030_state *st)
> +{
> +	u8 data_width = st->chip->precision_bits;
> +	u8 offload_bpw;
> +
> +	if (st->lane_mode == AD4030_LANE_MD_INTERLEAVED)
> +		/*
> +		 * This means all channels on 1 lane.
> +		 */
> +		offload_bpw = data_width * st->chip->num_voltage_inputs;

This looks suspicious. I would suggest to just not support interleaved
with SPI offload.

If we do want to support it we will need to fix the HDL so that this is
not necessary. And likely we will need some kind of devicetree binding
to say that there is a descrambler between the SPI offload and the DMA
buffer so that we know that the data will actually come out correct.

> +	else
> +		offload_bpw  = data_width;
> +
> +	st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;

Why using slower speed for offload?

> +	st->offload_xfer.bits_per_word = offload_bpw;
> +	st->offload_xfer.len = roundup_pow_of_two(BITS_TO_BYTES(offload_bpw));

Thanks to Andy, we have spi_bpw_to_bytes() to use here.

> +	st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> +	spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);

This currently only handles the case where one channel is enabled and
oversampling is disabled. 

> +}
> +
>  static int ad4030_config(struct ad4030_state *st)
>  {
>  	int ret;

...

> @@ -1050,24 +1309,55 @@ static int ad4030_probe(struct spi_device *spi)
>  		return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
>  				     "Failed to get cnv gpio\n");
>  
> -	/*
> -	 * One hardware channel is split in two software channels when using
> -	 * common byte mode. Add one more channel for the timestamp.
> -	 */
> -	indio_dev->num_channels = 2 * st->chip->num_voltage_inputs + 1;
>  	indio_dev->name = st->chip->name;
>  	indio_dev->modes = INDIO_DIRECT_MODE;
>  	indio_dev->info = &ad4030_iio_info;
> -	indio_dev->channels = st->chip->channels;
> -	indio_dev->available_scan_masks = st->chip->available_masks;
>  
> -	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> -					      iio_pollfunc_store_time,
> -					      ad4030_trigger_handler,
> -					      &ad4030_buffer_setup_ops);
> -	if (ret)
> -		return dev_err_probe(dev, ret,
> -				     "Failed to setup triggered buffer\n");
> +	st->offload = devm_spi_offload_get(dev, spi, &ad4030_offload_config);
> +	ret = PTR_ERR_OR_ZERO(st->offload);
> +	if (ret && ret != -ENODEV)
> +		return dev_err_probe(dev, ret, "failed to get offload\n");
> +
> +	/* Fall back to low speed usage when no SPI offload available. */
> +	if (ret == -ENODEV) {
> +		/*
> +		 * One hardware channel is split in two software channels when
> +		 * using common byte mode. Add one more channel for the timestamp.
> +		 */
> +		indio_dev->num_channels = 2 * st->chip->num_voltage_inputs + 1;
> +		indio_dev->channels = st->chip->channels;
> +		indio_dev->available_scan_masks = st->chip->available_masks;
> +
> +		ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> +						      iio_pollfunc_store_time,
> +						      ad4030_trigger_handler,
> +						      &ad4030_buffer_setup_ops);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "Failed to setup triggered buffer\n");
> +
> +	} else {
> +		/*
> +		 * One hardware channel is split in two software channels when
> +		 * using common byte mode. Offloaded SPI transfers can't support
> +		 * software timestamp so no additional timestamp channel is added.
> +		 */
> +		indio_dev->num_channels = 2 * st->chip->num_voltage_inputs;
> +		indio_dev->channels = st->chip->offload_channels;
> +		indio_dev->available_scan_masks = st->chip->available_masks;

I don't think scan_masks works here since this uses a DMA buffer and we can't
demux any extra channels that were enabled. Instead, I think we need to implement
a .validate_scan callback for the offload case to make sure the enabled channels
are one of the allowed combinations.

> +		ret = ad4030_spi_offload_setup(indio_dev, st);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "Failed to setup SPI offload\n");
> +
> +		ret = ad4030_pwm_get(st);
> +		if (ret)
> +			return dev_err_probe(&spi->dev, ret,
> +					     "Failed to get PWM: %d\n", ret);
> +
> +		ret = __ad4030_set_sampling_freq(st, st->chip->max_sample_rate_hz);
> +		ad4030_prepare_offload_msg(st);

The message needs to change depending on the oversampling ratio and if
the common mode channel is enabled, so we can't do this in probe.

> +	}
>  
>  	return devm_iio_device_register(dev, indio_dev);
>  }
> @@ -1103,6 +1393,20 @@ static const struct iio_scan_type ad4030_24_scan_types[] = {
>  		.shift = 2,
>  		.endianness = IIO_BE,
>  	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_NORMAL] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 24,
> +		.shift = 0,
> +		.endianness = IIO_CPU,
> +	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_AVG] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 30,
> +		.shift = 2,
> +		.endianness = IIO_CPU,
> +	},
>  };
>  
>  static const struct iio_scan_type ad4030_16_scan_types[] = {
> @@ -1119,7 +1423,21 @@ static const struct iio_scan_type ad4030_16_scan_types[] = {
>  		.realbits = 30,
>  		.shift = 2,
>  		.endianness = IIO_BE,
> -	}
> +	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_NORMAL] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 16,
> +		.shift = 0,
> +		.endianness = IIO_CPU,
> +	},
> +	[AD4030_OFFLOAD_SCAN_TYPE_AVG] = {
> +		.sign = 's',
> +		.storagebits = 32,
> +		.realbits = 30,
> +		.shift = 2,
> +		.endianness = IIO_CPU,
> +	},
>  };

Instead of extending these arrays, I would just make 2 new arrays.



^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
  2025-08-30  0:45 ` [PATCH 15/15] iio: adc: ad4030: Add support for " Marcelo Schmitt
  2025-08-30  7:57   ` Andy Shevchenko
  2025-08-30 19:17   ` David Lechner
@ 2025-09-01 11:47   ` Dan Carpenter
  2 siblings, 0 replies; 43+ messages in thread
From: Dan Carpenter @ 2025-09-01 11:47 UTC (permalink / raw)
  To: oe-kbuild, Marcelo Schmitt, linux-iio, linux-kernel, linux-doc,
	devicetree, linux-spi
  Cc: lkp, oe-kbuild-all, jic23, Michael.Hennerich, nuno.sa, eblanc,
	dlechner, andy, corbet, robh, krzk+dt, conor+dt, broonie,
	Jonathan.Cameron, andriy.shevchenko, ahaslam, marcelo.schmitt1

Hi Marcelo,

kernel test robot noticed the following build warnings:

url:    https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/iio-adc-ad4030-Fix-_scale-for-when-oversampling-is-enabled/20250830-084901
base:   91812d3843409c235f336f32f1c37ddc790f1e03
patch link:    https://lore.kernel.org/r/006ac88a667ce0d2c751946b562af83d0f27a44f.1756511030.git.marcelo.schmitt%40analog.com
patch subject: [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
config: x86_64-randconfig-161-20250831 (https://download.01.org/0day-ci/archive/20250831/202508310754.Y4V0Iq26-lkp@intel.com/config)
compiler: gcc-12 (Debian 12.2.0-14+deb12u1) 12.2.0

If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Reported-by: Dan Carpenter <dan.carpenter@linaro.org>
| Closes: https://lore.kernel.org/r/202508310754.Y4V0Iq26-lkp@intel.com/

smatch warnings:
drivers/iio/adc/ad4030.c:515 ad4030_set_pga() error: 'scan_type' dereferencing possible ERR_PTR()

vim +/scan_type +515 drivers/iio/adc/ad4030.c

8017880dd8ca3e Marcelo Schmitt 2025-08-29  502  static int ad4030_set_pga(struct iio_dev *indio_dev,
8017880dd8ca3e Marcelo Schmitt 2025-08-29  503  			  struct iio_chan_spec const *chan, int gain_int,
8017880dd8ca3e Marcelo Schmitt 2025-08-29  504  			  int gain_fract)
8017880dd8ca3e Marcelo Schmitt 2025-08-29  505  {
8017880dd8ca3e Marcelo Schmitt 2025-08-29  506  	struct ad4030_state *st = iio_priv(indio_dev);
8017880dd8ca3e Marcelo Schmitt 2025-08-29  507  	const struct iio_scan_type *scan_type;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  508  	unsigned int mag_bits;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  509  	u64 gain_nano, tmp;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  510  
8017880dd8ca3e Marcelo Schmitt 2025-08-29  511  	if (!st->pga_gpios)
8017880dd8ca3e Marcelo Schmitt 2025-08-29  512  		return -EINVAL;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  513  
8017880dd8ca3e Marcelo Schmitt 2025-08-29  514  	scan_type = iio_get_current_scan_type(indio_dev, chan);

	if (IS_ERR(scan_type))
		return PTR_ERR(scan_type);

8017880dd8ca3e Marcelo Schmitt 2025-08-29 @515  	if (scan_type->sign == 's')
8017880dd8ca3e Marcelo Schmitt 2025-08-29  516  		mag_bits = st->chip->precision_bits - 1;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  517  	else
8017880dd8ca3e Marcelo Schmitt 2025-08-29  518  		mag_bits = st->chip->precision_bits;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  519  
8017880dd8ca3e Marcelo Schmitt 2025-08-29  520  	gain_nano = gain_int * NANO + gain_fract;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  521  
8017880dd8ca3e Marcelo Schmitt 2025-08-29  522  	if (!in_range(gain_nano, 0, ADAQ4616_GAIN_MAX_NANO))
8017880dd8ca3e Marcelo Schmitt 2025-08-29  523  		return -EINVAL;
8017880dd8ca3e Marcelo Schmitt 2025-08-29  524  
8017880dd8ca3e Marcelo Schmitt 2025-08-29  525  	tmp = DIV_ROUND_CLOSEST_ULL(gain_nano << mag_bits, NANO);
8017880dd8ca3e Marcelo Schmitt 2025-08-29  526  	gain_nano = DIV_ROUND_CLOSEST_ULL(st->vref_uv, tmp);
8017880dd8ca3e Marcelo Schmitt 2025-08-29  527  	st->pga_index = find_closest(gain_nano, ad4030_hw_gains,
8017880dd8ca3e Marcelo Schmitt 2025-08-29  528  				     ARRAY_SIZE(ad4030_hw_gains));
8017880dd8ca3e Marcelo Schmitt 2025-08-29  529  
8017880dd8ca3e Marcelo Schmitt 2025-08-29  530  	return ad4030_set_pga_gain(st);
8017880dd8ca3e Marcelo Schmitt 2025-08-29  531  }

-- 
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki


^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled
  2025-08-30 18:43   ` Jonathan Cameron
  2025-08-30 18:48     ` David Lechner
@ 2025-09-02 13:18     ` Marcelo Schmitt
  1 sibling, 0 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-09-02 13:18 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi, Michael.Hennerich, nuno.sa, eblanc, dlechner, andy,
	corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam

Hi Jonathan,

Thanks for having a look at this.
Comment inline.

On 08/30, Jonathan Cameron wrote:
> On Fri, 29 Aug 2025 21:40:24 -0300
> Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> 
> > Previously, the AD4030 driver was using the number of scan realbits for the
> > voltage channel to derive the scale to millivolts. Though, when sample
> > averaging is enabled (oversampling_ratio > 1), the number of scan realbits
> > for the channel is set to 30 and doesn't match the amount of conversion
> > precision bits. Due to that, the calculated channel scale did not correctly
> > scale raw sample data to millivolt units in those cases. Use chip specific
> > precision bits to derive the correct channel _scale on every and all
> > channel configuration.
> > 
> > Fixes: dc78e71d7c15 ("iio: adc: ad4030: remove some duplicate code")
> > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> 
> Hi Marcelo
> 
> I was assuming that when this said 'averaging' it actually meant
> summing (there is a note about using the upper precision bits to get the same
> scaling which is what we'd expect it were simply summing over X samples).
> 
> So given that we don't divide back down to get the original scaling I'm
> not following how this works.
> 
> E.g. If we 'averaged' just 2 values of 3 then we'd go from a value of 3 to
> one of 6.  Therefore I'd expect the scale to halve as each lsb represents
> half the voltage it did when we weren't averaging those 2 samples.

This makes sense and thank you for explaining it to me.
I did some more test and debugging on the remote setup and found out the device
was not correctly configured for averaging data on my tests for v1. I need to
tweak a few more things in the driver to get both device registers and spi
transfer configuration good for offload with averaging mode. I'll reply with
more details if I find something unexpected, or drop this patch on v2.

Thanks,
Marcelo

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 07/15] iio: adc: ad4030: Add SPI offload support
  2025-08-30 20:14   ` David Lechner
@ 2025-09-02 14:52     ` Marcelo Schmitt
  0 siblings, 0 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-09-02 14:52 UTC (permalink / raw)
  To: David Lechner
  Cc: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi, jic23, Michael.Hennerich, nuno.sa, eblanc, andy,
	corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam, sergiu.cuciurean, tgamblin

Hi David,

On 08/30, David Lechner wrote:
> On 8/29/25 7:42 PM, Marcelo Schmitt wrote:
> > AD4030 and similar ADCs can capture data at sample rates up to 2 mega
> > samples per second (MSPS). Not all SPI controllers are able to achieve
> > such high throughputs and even when the controller is fast enough to run
> > transfers at the required speed, it may be costly to the CPU to handle
> > transfer data at such high sample rates.  Add SPI offload support for
> > AD4030 and similar ADCs so to enable ADC data capture at maximum sample
> > rates.
> > 
...
> > +
> > +static int __ad4030_set_sampling_freq(struct ad4030_state *st, unsigned int freq)
> > +{
...
> > +	do {
> > +		conv_wf.duty_length_ns = target;
> > +		ret = pwm_round_waveform_might_sleep(st->conv_trigger, &conv_wf);
> > +		if (ret)
> > +			return ret;
> > +		target += 10;
> > +	} while (conv_wf.duty_length_ns < 10);
> > +
> > +	offload_period_ns = conv_wf.period_length_ns;
> > +
> > +	ret = regmap_read(st->regmap, AD4030_REG_MODES, &mode);
> > +	if (ret)
> > +		return ret;
> > +	if (FIELD_GET(AD4030_REG_MODES_MASK_OUT_DATA_MODE, mode) == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF) {
> 
> Since this depends on the oversampling ration, we need to defer this
> until we start a buffered read. Otherwise if someone sets sampling
> frequency first and the changes the oversampling ratio later, then
> the PWM period will not be correct.
> 

Yes, that's one of the problems I noticed when testing yesterday and probably
the reason why I initially thought the scale was buggy for averaging/oversampling.

> Alternatly, we could update this both when sampling freqency and
> when oversampling ratio are updated. This would allow returning an
> error if the oversampling ratio is too big for the requested
> sampling frequency.
> 

Sure, this sounds to be the best way of keeping track of the sampling frequency
and oversampling ratio combination. Otherwise, unadvised users (like me) could
set the oversampling ratio then run transfers without the driver being able to
update the CNV trigger waveform according to the number of samples to average.
The result of that is the device doesn't do oversampling (despite the
oversampling attribute value).

I also see your comments to this and other patches and I think I agree with them
all. Will re-spin to apply the requested changes and provide support for SPI
related stuff in a separate series.

Thanks,
Marcelo

^ permalink raw reply	[flat|nested] 43+ messages in thread

* Re: [PATCH 15/15] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
  2025-08-30  7:57   ` Andy Shevchenko
@ 2025-09-02 15:22     ` Marcelo Schmitt
  0 siblings, 0 replies; 43+ messages in thread
From: Marcelo Schmitt @ 2025-09-02 15:22 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Marcelo Schmitt, linux-iio, linux-kernel, linux-doc, devicetree,
	linux-spi, jic23, Michael.Hennerich, nuno.sa, eblanc, dlechner,
	andy, corbet, robh, krzk+dt, conor+dt, broonie, Jonathan.Cameron,
	andriy.shevchenko, ahaslam

Hi Andy, thank you for your review.

On 08/30, Andy Shevchenko wrote:
> On Sat, Aug 30, 2025 at 3:46 AM Marcelo Schmitt
> <marcelo.schmitt@analog.com> wrote:
> >
> > ADAQ4216 and ADAQ4224 are similar to AD4030, but feature a PGA circuitry
> > that scales the analog input signal prior to it reaching the ADC. The PGA
> > is controlled through a pair of pins (A0 and A1) whose state define the
> > gain that is applied to the input signal.
> >
> > Add support for ADAQ4216 and ADAQ4224. Provide a list of PGA options
> > through the IIO device channel scale available interface and enable control
> > of the PGA through the channel scale interface.
> 
...
> 
> > +/* HARDWARE_GAIN */
> > +#define ADAQ4616_PGA_PINS              2
> > +#define ADAQ4616_GAIN_MAX_NANO         6666666667
> 
> Can we use calculus instead (people can't count properly after 3 :-)?
> Something like this
> 
> (NANO * 2 / 3) // whoever in the above it's 20 and this puzzles me how
> something with _NANO can be so big :-)
> 
Yeah, the max gain could be expressed in MILLI, but maybe I'll just do 20000 / 9
(or equivalent) as you suggested below and drop ADAQ4616_GAIN_MAX_NANO.
I'll also comply with your suggestions to this and other patches.  

Thanks,
Marcelo

^ permalink raw reply	[flat|nested] 43+ messages in thread

end of thread, other threads:[~2025-09-02 15:21 UTC | newest]

Thread overview: 43+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-30  0:39 [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt
2025-08-30  0:40 ` [PATCH 01/15] iio: adc: ad4030: Fix _scale for when oversampling is enabled Marcelo Schmitt
2025-08-30  5:00   ` Andy Shevchenko
2025-08-30 18:43   ` Jonathan Cameron
2025-08-30 18:48     ` David Lechner
2025-09-02 13:18     ` Marcelo Schmitt
2025-08-30  0:40 ` [PATCH 02/15] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
2025-08-30  0:41 ` [PATCH 03/15] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
2025-08-30 16:49   ` David Lechner
2025-08-30  0:41 ` [PATCH 04/15] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
2025-08-30  0:42 ` [PATCH 05/15] spi: offload: types: add offset parameter Marcelo Schmitt
2025-08-30  5:01   ` Andy Shevchenko
2025-08-30  0:42 ` [PATCH 06/15] spi: spi-offload-trigger-pwm: Use duty offset Marcelo Schmitt
2025-08-30  5:02   ` Andy Shevchenko
2025-08-30 16:41   ` David Lechner
2025-08-30  0:42 ` [PATCH 07/15] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
2025-08-30  7:36   ` Andy Shevchenko
2025-08-30 12:08   ` kernel test robot
2025-08-30 19:11   ` Jonathan Cameron
2025-08-30 20:14   ` David Lechner
2025-09-02 14:52     ` Marcelo Schmitt
2025-08-30  0:43 ` [PATCH 08/15] dt-bindings: iio: adc: adi,ad4030: Add 4-lane per channel bus width option Marcelo Schmitt
2025-08-30 17:01   ` David Lechner
2025-08-30  0:43 ` [PATCH 09/15] iio: adc: ad4030: Support multiple data lanes per channel Marcelo Schmitt
2025-08-30  7:38   ` Andy Shevchenko
2025-08-30 17:19   ` David Lechner
2025-08-30  0:43 ` [PATCH 10/15] dt-bindings: iio: adc: adi,ad4030: Add adi,clock-mode Marcelo Schmitt
2025-08-30 18:02   ` David Lechner
2025-08-30  0:44 ` [PATCH 11/15] iio: adc: ad4030: Add clock mode option parse and setup Marcelo Schmitt
2025-08-30  7:42   ` Andy Shevchenko
2025-08-30  0:44 ` [PATCH 12/15] dt-bindings: iio: adc: adi,ad4030: Add adi,dual-data-rate Marcelo Schmitt
2025-08-30 17:27   ` David Lechner
2025-08-30  0:45 ` [PATCH 13/15] iio: adc: ad4030: Enable dual data rate Marcelo Schmitt
2025-08-30  7:46   ` Andy Shevchenko
2025-08-30 17:33   ` David Lechner
2025-08-30  0:45 ` [PATCH 14/15] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
2025-08-30 18:45   ` David Lechner
2025-08-30  0:45 ` [PATCH 15/15] iio: adc: ad4030: Add support for " Marcelo Schmitt
2025-08-30  7:57   ` Andy Shevchenko
2025-09-02 15:22     ` Marcelo Schmitt
2025-08-30 19:17   ` David Lechner
2025-09-01 11:47   ` Dan Carpenter
2025-08-30  2:48 ` [PATCH 00/15] Add SPI offload support to AD4030 Marcelo Schmitt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).