* [PATCH v2 0/8] Add SPI offload support to AD4030
@ 2025-09-18 17:37 Marcelo Schmitt
2025-09-18 17:37 ` [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels Marcelo Schmitt
` (7 more replies)
0 siblings, 8 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:37 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1
Hi,
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.
The patches to the SPI subsystem were submitted in a separate patch series
titled 'Add SPI offload trigger offset'.
Except for a couple of comments on the SPI offload patch, I believe I have
applied all suggestions to v1.
Change log v1 -> v2
[IIO Docs]
- Swapped PWM numbering.
- Expanded double PWM description and capture zone description.
[device tree]
- Dropped pwm-names since only one PWM signal is directly requested by the ADC.
- Use pattern to specify devices that require gain related properties.
- Disallow gain related properties for devices that don't come with embedded PGA.
- Documented VDDH and VDD_FDA supplies for ADAQ4216 and ADAQ4224.
- Updated PGA gain constants.
[IIO]
- Dropped all clock-modes and DDR related stuff for now as those will require
further changes to the SPI subsystem or to SPI controller drivers.
- Update the modes register with proper output data mode bits when sample
averaging (oversampling_ratio) is set.
- Lock on device state mutex before updating oversampling and sampling frequency.
- Made sampling_frequency shared by all channels.
- Better checking the requested sampling frequency is valid.
- Adjusted to SPI offload data capture preparation and stop procedures.
- Error out if try to get/set sampling frequency without offload trigger.
- Depend on PWM so build always succeed.
- Drop unmatched/unbalanced call to iio_device_release_direct().
- No longer shadowing error codes.
- Using BIT macro to make list of averaging options more readable.
- Updated PGA gain constants.
- De-duplicate 'ret == -EINVAL' check in PGA setup.
- Dropped redundant call to ad4030_set_pga_gain() on PGA GPIO setup.
- Better state struct field placement to avoid holes in data.
- Many minor readability and code style improvements.
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.
Link to v1: https://lore.kernel.org/linux-iio/cover.1756511030.git.marcelo.schmitt@analog.com/
Best regards,
Marcelo
Marcelo Schmitt (8):
iio: adc: ad4030: Fix _scale value for common-mode channels
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: Use BIT macro to improve code readability
iio: adc: ad4030: Add SPI offload support
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 | 71 +-
Documentation/iio/ad4030.rst | 35 +
drivers/iio/adc/Kconfig | 3 +
drivers/iio/adc/ad4030.c | 718 ++++++++++++++++--
4 files changed, 774 insertions(+), 53 deletions(-)
base-commit: 561285d048053fec8a3d6d1e3ddc60df11c393a0
--
2.50.1
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
@ 2025-09-18 17:37 ` Marcelo Schmitt
2025-09-18 19:32 ` David Lechner
2025-09-18 17:38 ` [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
` (6 subsequent siblings)
7 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:37 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1
Previously, the driver always used the amount of precision bits of
differential input channels to provide the scale to mV. Though,
differential and common-mode voltage channels have different amount of
precision bits and the correct number of precision bits must be considered
to get to a proper mV scale factor for each one. Use channel specific
number of precision bits to provide the correct scale value for each
channel.
Fixes: de67f28abe58 ("iio: adc: ad4030: check scan_type for error")
Fixes: 949abd1ca5a4 ("iio: adc: ad4030: add averaging support")
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
drivers/iio/adc/ad4030.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 1bc2f9a22470..d8bee6a4215a 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -385,7 +385,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);
--
2.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
2025-09-18 17:37 ` [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels Marcelo Schmitt
@ 2025-09-18 17:38 ` Marcelo Schmitt
2025-09-18 19:39 ` David Lechner
2025-09-19 17:33 ` Conor Dooley
2025-09-18 17:38 ` [PATCH v2 3/8] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
` (5 subsequent siblings)
7 siblings, 2 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:38 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, 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>
---
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.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 3/8] Documentation: iio: ad4030: Add double PWM SPI offload doc
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
2025-09-18 17:37 ` [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels Marcelo Schmitt
2025-09-18 17:38 ` [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
@ 2025-09-18 17:38 ` Marcelo Schmitt
2025-09-18 19:50 ` David Lechner
2025-09-18 17:38 ` [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
` (4 subsequent siblings)
7 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:38 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1
Document double PWM setup SPI offload wiring schema.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v1 -> v2
- Swapped PWM numbering.
- Expanded double PWM description and capture zone description.
Documentation/iio/ad4030.rst | 35 +++++++++++++++++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/Documentation/iio/ad4030.rst b/Documentation/iio/ad4030.rst
index b57424b650a8..9501d3fee9bb 100644
--- a/Documentation/iio/ad4030.rst
+++ b/Documentation/iio/ad4030.rst
@@ -92,6 +92,41 @@ 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 |
+ | | +--| PWM0 |
+ | | | |
+ | | +--| PWM1 |
+ | | | +-------------+
+ | | +->| 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. For AD4030 and similar ADCs, there are two
+possible data transfer zones for sample N. One of them (zone 1) starts after the
+data conversion for sample N is complete while the other one (zone 2) starts 9.8
+nanoseconds after the rising edge of CNV for sample N + 1.
+
+The configuration depicted in the above ASCII art is intended to perform data
+transfer in zone 2. To achieve high sample rates while meeting ADC timing
+requirements, an offset is added between the rising edges of PWM0 and PWM1 to
+delay the SPI transfer until 9.8 nanoseconds after CNV rising edge. This
+requires a specialized PWM controller that can provide such an offset.
+
SPI Clock mode
--------------
--
2.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
` (2 preceding siblings ...)
2025-09-18 17:38 ` [PATCH v2 3/8] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
@ 2025-09-18 17:38 ` Marcelo Schmitt
2025-09-18 19:51 ` David Lechner
2025-09-19 17:34 ` Conor Dooley
2025-09-18 17:38 ` [PATCH v2 5/8] iio: adc: ad4030: Use BIT macro to improve code readability Marcelo Schmitt
` (3 subsequent siblings)
7 siblings, 2 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:38 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, 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>
---
Change log v1 -> v2
- Dropped pwm-names since only one PWM signal is directly requested by the ADC.
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.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 5/8] iio: adc: ad4030: Use BIT macro to improve code readability
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
` (3 preceding siblings ...)
2025-09-18 17:38 ` [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
@ 2025-09-18 17:38 ` Marcelo Schmitt
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
` (2 subsequent siblings)
7 siblings, 0 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:38 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1, Andy Shevchenko
Use BIT macro to make the list of average modes more readable.
Suggested-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Link: https://lore.kernel.org/linux-iio/CAHp75Vfu-C3Hd0ZXTj4rxEgRe_O84cfo6jiRCPFxZJnYrvROWQ@mail.gmail.com/
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
drivers/iio/adc/ad4030.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index d8bee6a4215a..aa0e27321869 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -233,9 +233,11 @@ struct ad4030_state {
}
static const int ad4030_average_modes[] = {
- 1, 2, 4, 8, 16, 32, 64, 128,
- 256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
- 65536,
+ BIT(0), /* No averaging/oversampling */
+ BIT(1), BIT(2), BIT(3), BIT(4), /* 2 to 16 */
+ BIT(5), BIT(6), BIT(7), BIT(8), /* 32 to 256 */
+ BIT(9), BIT(10), BIT(11), BIT(12), /* 512 to 4096 */
+ BIT(13), BIT(14), BIT(15), BIT(16), /* 8192 to 65536 */
};
static int ad4030_enter_config_mode(struct ad4030_state *st)
--
2.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
` (4 preceding siblings ...)
2025-09-18 17:38 ` [PATCH v2 5/8] iio: adc: ad4030: Use BIT macro to improve code readability Marcelo Schmitt
@ 2025-09-18 17:39 ` Marcelo Schmitt
2025-09-19 8:21 ` Nuno Sá
` (3 more replies)
2025-09-18 17:39 ` [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
2025-09-18 17:39 ` [PATCH v2 8/8] iio: adc: ad4030: Add support for " Marcelo Schmitt
7 siblings, 4 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:39 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1, Sergiu Cuciurean,
Trevor Gamblin, Axel Haslam
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 to enable data capture at maximum sample rates.
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 for SPI offload support is based on work from Sergiu Cuciurean,
Nuno Sa, Axel Haslam, and Trevor Gamblin. Thus, this patch comes with many
co-developed-by tags. I also draw inspiration from other drivers supporting SPI
offload, many of them written by David Lechner.
Change log v1 -> v2
- Dropped all clock-modes and DDR related stuff for now as those will require
further changes to the SPI subsystem or to SPI controller drivers.
- Update the modes register with proper output data mode bits when sample
averaging (oversampling_ratio) is set.
- Lock on device state mutex before updating oversampling and sampling frequency.
- Made sampling_frequency shared by all channels.
- Better checking the requested sampling frequency is valid.
- Adjusted to SPI offload data capture preparation and stop procedures.
- Error out if try to get/set sampling frequency without offload trigger.
- Depend on PWM so build always succeed.
- Drop unmatched/unbalanced call to iio_device_release_direct().
- No longer shadowing error codes.
Suggestions to v1 that I did not comply to:
[SPI]
> I would be tempted to put the loop check here [in drivers/spi/spi-offload-trigger-pwm.c]:
>
> 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.
Not sure implementing the PWM trigger phase approximation/rounding/setup within
spi-offload-trigger-pwm is actually desirable. The PWM phase
approximation/rounding/setup done in AD4030 iterates over the configuration of a
second PWM (the PWM connected to the CNV pin). I haven't seen any other device
that would use such double PWM setup schema so pushing an additional argument to
spi_offload_trigger_pwm_validate() doesn't seem worth it.
[IIO]
> Why using slower speed for offload?
Looks like it's the same max speed for both register access and data sample.
So, just reusing the existing define for the max transfer speed.
drivers/iio/adc/Kconfig | 3 +
drivers/iio/adc/ad4030.c | 485 +++++++++++++++++++++++++++++++++++----
2 files changed, 445 insertions(+), 43 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 58a14e6833f6..2a44fcaccf54 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -60,8 +60,11 @@ config AD4030
tristate "Analog Devices AD4030 ADC Driver"
depends on SPI
depends on GPIOLIB
+ depends on PWM
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 aa0e27321869..52805c779934 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -14,15 +14,25 @@
*/
#include <linux/bitfield.h>
+#include <linux/cleanup.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/limits.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/minmax.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>
+#include <linux/types.h>
#define AD4030_REG_INTERFACE_CONFIG_A 0x00
#define AD4030_REG_INTERFACE_CONFIG_A_SW_RESET (BIT(0) | BIT(7))
@@ -111,6 +121,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,
@@ -136,11 +148,13 @@ 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 {
@@ -153,6 +167,15 @@ struct ad4030_state {
int offset_avail[3];
unsigned int avg_log2;
enum ad4030_out_mode mode;
+ struct mutex lock; /* Protect read-modify-write and multi write sequences */
+ /* Offload sampling */
+ 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;
+ struct pwm_device *cnv_trigger;
+ struct pwm_waveform cnv_wf;
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -209,8 +232,9 @@ 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 = \
+ (_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) | \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_shared_by_all_available = \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
@@ -232,6 +256,12 @@ 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[] = {
BIT(0), /* No averaging/oversampling */
BIT(1), BIT(2), BIT(3), BIT(4), /* 2 to 16 */
@@ -240,6 +270,11 @@ static const int ad4030_average_modes[] = {
BIT(13), BIT(14), BIT(15), BIT(16), /* 8192 to 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;
@@ -453,6 +488,106 @@ static int ad4030_get_chan_calibbias(struct iio_dev *indio_dev,
}
}
+static void ad4030_get_sampling_freq(struct ad4030_state *st, int *freq)
+{
+ struct spi_offload_trigger_config *config = &st->offload_trigger_config;
+
+ /*
+ * Conversion data is fetched from the device when the offload transfer
+ * is triggered. Thus, provide the SPI offload trigger frequency as the
+ * sampling frequency.
+ */
+ *freq = config->periodic.frequency_hz;
+}
+
+static int __ad4030_set_sampling_freq(struct ad4030_state *st,
+ unsigned int freq, unsigned int avg_log2)
+{
+ struct spi_offload_trigger_config *config = &st->offload_trigger_config;
+ struct pwm_waveform cnv_wf = { };
+ u64 target = AD4030_TCNVH_NS;
+ u64 offload_period_ns;
+ u64 offload_offset_ns;
+ int ret;
+
+ /*
+ * When averaging/oversampling over N samples, we fire the offload
+ * trigger once at every N pulses of the CNV signal. Conversely, the CNV
+ * signal needs to be N times faster than the offload trigger. Take that
+ * into account to correctly re-evaluate both the PWM waveform connected
+ * to CNV and the SPI offload trigger.
+ */
+ if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
+ freq <<= avg_log2;
+
+ cnv_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 {
+ cnv_wf.duty_length_ns = target;
+ ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
+ if (ret)
+ return ret;
+ target += AD4030_TCNVH_NS;
+ } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
+
+ if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
+ return -EINVAL;
+
+ offload_period_ns = cnv_wf.period_length_ns;
+ if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
+ offload_period_ns <<= avg_log2;
+
+ 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 += AD4030_TQUIET_CNV_DELAY_NS;
+ } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
+
+ st->cnv_wf = cnv_wf;
+
+ return 0;
+}
+
+static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, int freq)
+{
+ struct ad4030_state *st = iio_priv(indio_dev);
+
+ /*
+ * We have no control over the sampling frequency without SPI offload
+ * triggering.
+ */
+ if (!st->offload_trigger)
+ return -ENODEV;
+
+ if (!in_range(freq, 1, st->chip->max_sample_rate_hz))
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+ return __ad4030_set_sampling_freq(st, freq, st->avg_log2);
+}
+
static int ad4030_set_chan_calibscale(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int gain_int,
@@ -507,27 +642,6 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev,
st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
}
-static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
-{
- struct ad4030_state *st = iio_priv(dev);
- unsigned int avg_log2 = ilog2(avg_val);
- unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
- int ret;
-
- if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
- return -EINVAL;
-
- ret = regmap_write(st->regmap, AD4030_REG_AVG,
- AD4030_REG_AVG_MASK_AVG_SYNC |
- FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL, avg_log2));
- if (ret)
- return ret;
-
- st->avg_log2 = avg_log2;
-
- return 0;
-}
-
static bool ad4030_is_common_byte_asked(struct ad4030_state *st,
unsigned int mask)
{
@@ -536,11 +650,10 @@ static bool ad4030_is_common_byte_asked(struct ad4030_state *st,
AD4030_DUAL_COMMON_BYTE_CHANNELS_MASK);
}
-static int ad4030_set_mode(struct iio_dev *indio_dev, unsigned long mask)
+static int ad4030_set_mode(struct ad4030_state *st, unsigned long mask,
+ unsigned int avg_log2)
{
- struct ad4030_state *st = iio_priv(indio_dev);
-
- if (st->avg_log2 > 0) {
+ if (avg_log2 > 0) {
st->mode = AD4030_OUT_DATA_MD_30_AVERAGED_DIFF;
} else if (ad4030_is_common_byte_asked(st, mask)) {
switch (st->chip->precision_bits) {
@@ -564,6 +677,50 @@ static int ad4030_set_mode(struct iio_dev *indio_dev, unsigned long mask)
st->mode);
}
+static int ad4030_set_avg_frame_len(struct iio_dev *dev, unsigned long mask, int avg_val)
+{
+ struct ad4030_state *st = iio_priv(dev);
+ unsigned int avg_log2 = ilog2(avg_val);
+ unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
+ int freq;
+ int ret;
+
+ if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+ ret = ad4030_set_mode(st, mask, avg_log2);
+ if (ret)
+ return ret;
+
+ if (st->offload_trigger) {
+ /*
+ * The sample averaging and sampling frequency configurations
+ * are mutually dependent one from another. That's because the
+ * effective data sample rate is fCNV / 2^N, where N is the
+ * number of samples being averaged.
+ *
+ * When SPI offload is supported and we have control over the
+ * sample rate, the conversion start signal (CNV) and the SPI
+ * offload trigger frequencies must be re-evaluated so data is
+ * fetched only after 'avg_val' conversions.
+ */
+ ad4030_get_sampling_freq(st, &freq);
+ ret = __ad4030_set_sampling_freq(st, freq, avg_log2);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(st->regmap, AD4030_REG_AVG,
+ AD4030_REG_AVG_MASK_AVG_SYNC |
+ FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL, avg_log2));
+ if (ret)
+ return ret;
+
+ st->avg_log2 = avg_log2;
+ return 0;
+}
+
/*
* Descramble 2 32bits numbers out of a 64bits. The bits are interleaved:
* 1 bit for first number, 1 bit for the second, and so on...
@@ -672,7 +829,7 @@ static int ad4030_single_conversion(struct iio_dev *indio_dev,
struct ad4030_state *st = iio_priv(indio_dev);
int ret;
- ret = ad4030_set_mode(indio_dev, BIT(chan->scan_index));
+ ret = ad4030_set_mode(st, BIT(chan->scan_index), st->avg_log2);
if (ret)
return ret;
@@ -769,6 +926,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 (!st->offload_trigger)
+ return -ENODEV;
+
+ ad4030_get_sampling_freq(st, val);
+ return IIO_VAL_INT;
+
default:
return -EINVAL;
}
@@ -807,7 +971,10 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
return ad4030_set_chan_calibbias(indio_dev, chan, val);
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
- return ad4030_set_avg_frame_len(indio_dev, val);
+ return ad4030_set_avg_frame_len(indio_dev, BIT(chan->scan_index), val);
+
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad4030_set_sampling_freq(indio_dev, val);
default:
return -EINVAL;
@@ -869,7 +1036,9 @@ static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev,
static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
const unsigned long *scan_mask)
{
- return ad4030_set_mode(indio_dev, *scan_mask);
+ struct ad4030_state *st = iio_priv(indio_dev);
+
+ return ad4030_set_mode(st, *scan_mask, st->avg_log2);
}
static const struct iio_info ad4030_iio_info = {
@@ -898,6 +1067,88 @@ static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = {
.validate_scan_mask = ad4030_validate_scan_mask,
};
+static void ad4030_prepare_offload_msg(struct iio_dev *indio_dev)
+{
+ struct ad4030_state *st = iio_priv(indio_dev);
+ u8 offload_bpw;
+
+ if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
+ offload_bpw = 32;
+ else
+ offload_bpw = st->chip->precision_bits;
+
+ st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
+ st->offload_xfer.bits_per_word = roundup_pow_of_two(offload_bpw);
+ st->offload_xfer.len = spi_bpw_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_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;
+
+ ad4030_prepare_offload_msg(indio_dev);
+ st->offload_msg.offload = st->offload;
+ ret = spi_optimize_message(st->spi, &st->offload_msg);
+ if (ret)
+ goto out_reset_mode;
+
+ ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_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->cnv_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_err(&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;
+
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+
+ pwm_disable(st->cnv_trigger);
+
+ spi_unoptimize_message(&st->offload_msg);
+
+ /* reenter register configuration mode */
+ ret = ad4030_enter_config_mode(st);
+ if (ret)
+ dev_err(&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,
+ .validate_scan_mask = ad4030_validate_scan_mask,
+};
+
static int ad4030_regulators_get(struct ad4030_state *st)
{
struct device *dev = &st->spi->dev;
@@ -967,6 +1218,24 @@ 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->cnv_trigger = devm_pwm_get(dev, NULL);
+ if (IS_ERR(st->cnv_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_trigger),
+ "Failed to get CNV PWM\n");
+
+ /*
+ * Preemptively disable the PWM, since we only want to enable it with
+ * the buffer.
+ */
+ pwm_disable(st->cnv_trigger);
+
+ return 0;
+}
+
static int ad4030_config(struct ad4030_state *st)
{
int ret;
@@ -994,6 +1263,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;
@@ -1018,6 +1312,10 @@ static int ad4030_probe(struct spi_device *spi)
if (!st->chip)
return -EINVAL;
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
ret = ad4030_regulators_get(st);
if (ret)
return ret;
@@ -1045,24 +1343,57 @@ 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 is 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,
+ st->avg_log2);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to set offload samp freq\n");
+ }
return devm_iio_device_register(dev, indio_dev);
}
@@ -1100,6 +1431,23 @@ static const struct iio_scan_type ad4030_24_scan_types[] = {
},
};
+static const struct iio_scan_type ad4030_24_offload_scan_types[] = {
+ [AD4030_SCAN_TYPE_NORMAL] = {
+ .sign = 's',
+ .storagebits = 32,
+ .realbits = 24,
+ .shift = 0,
+ .endianness = IIO_CPU,
+ },
+ [AD4030_SCAN_TYPE_AVG] = {
+ .sign = 's',
+ .storagebits = 32,
+ .realbits = 30,
+ .shift = 2,
+ .endianness = IIO_CPU,
+ },
+};
+
static const struct iio_scan_type ad4030_16_scan_types[] = {
[AD4030_SCAN_TYPE_NORMAL] = {
.sign = 's',
@@ -1117,6 +1465,23 @@ static const struct iio_scan_type ad4030_16_scan_types[] = {
}
};
+static const struct iio_scan_type ad4030_16_offload_scan_types[] = {
+ [AD4030_SCAN_TYPE_NORMAL] = {
+ .sign = 's',
+ .storagebits = 32,
+ .realbits = 16,
+ .shift = 0,
+ .endianness = IIO_CPU,
+ },
+ [AD4030_SCAN_TYPE_AVG] = {
+ .sign = 's',
+ .storagebits = 32,
+ .realbits = 30,
+ .shift = 2,
+ .endianness = IIO_CPU,
+ },
+};
+
static const struct ad4030_chip_info ad4030_24_chip_info = {
.name = "ad4030-24",
.available_masks = ad4030_channel_masks,
@@ -1125,10 +1490,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_offload_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 * HZ_PER_MHZ,
};
static const struct ad4030_chip_info ad4630_16_chip_info = {
@@ -1141,10 +1511,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_offload_scan_types),
+ AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_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 * HZ_PER_MHZ,
};
static const struct ad4030_chip_info ad4630_24_chip_info = {
@@ -1157,10 +1534,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_offload_scan_types),
+ AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_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 * HZ_PER_MHZ,
};
static const struct ad4030_chip_info ad4632_16_chip_info = {
@@ -1173,10 +1557,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_offload_scan_types),
+ AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_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 * HZ_PER_KHZ,
};
static const struct ad4030_chip_info ad4632_24_chip_info = {
@@ -1189,10 +1580,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_offload_scan_types),
+ AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_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 * HZ_PER_KHZ,
};
static const struct spi_device_id ad4030_id_table[] = {
@@ -1228,3 +1626,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.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
` (5 preceding siblings ...)
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
@ 2025-09-18 17:39 ` Marcelo Schmitt
2025-09-19 17:36 ` Conor Dooley
2025-09-18 17:39 ` [PATCH v2 8/8] iio: adc: ad4030: Add support for " Marcelo Schmitt
7 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:39 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1
ADAQ4216 and ADAQ4224 are similar to AD4030 except that 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 configurations.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v1 -> v2
- Use pattern to specify devices that require gain related properties.
- Disallow gain related properties for devices that don't come with embedded PGA.
- Documented VDDH and VDD_FDA supplies for ADAQ4216 and ADAQ4224.
- Updated PGA gain constants.
.../bindings/iio/adc/adi,ad4030.yaml | 65 +++++++++++++++++--
1 file changed, 60 insertions(+), 5 deletions(-)
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
index 564b6f67a96e..bd43c617ae11 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
@@ -54,6 +58,14 @@ properties:
description:
Internal buffered Reference. Used when ref-supply is not connected.
+ vddh-supply:
+ description:
+ PGIA Positive Power Supply.
+
+ vdd-fda-supply:
+ description:
+ FDA Positive Power Supply.
+
cnv-gpios:
description:
The Convert Input (CNV). It initiates the sampling conversions.
@@ -64,6 +76,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 555 (A1=0, A0=1)
+ Gain 2222 (A1=1, A0=0)
+ Gain 6666 (A1=1, A0=1)
+ If defined, pga-gpios must be absent.
+ enum: [333, 555, 2222, 6666]
+
pwms:
description: PWM signal connected to the CNV pin.
maxItems: 1
@@ -86,11 +119,33 @@ required:
- vio-supply
- cnv-gpios
-oneOf:
- - required:
- - ref-supply
- - required:
- - refin-supply
+allOf:
+ - oneOf:
+ - required:
+ - ref-supply
+ - required:
+ - refin-supply
+ # ADAQ devices require a gain property to indicate how hardware PGA is set
+ - if:
+ properties:
+ compatible:
+ contains:
+ pattern: ^adi,adaq
+ then:
+ allOf:
+ - required: [vddh-supply, vdd-fda-supply]
+ properties:
+ ref-supply: false
+ - oneOf:
+ - required:
+ - adi,pga-value
+ - required:
+ - pga-gpios
+ else:
+ properties:
+ adi,pga-value: false
+ pga-gpios: false
+
unevaluatedProperties: false
--
2.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v2 8/8] iio: adc: ad4030: Add support for ADAQ4216 and ADAQ4224
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
` (6 preceding siblings ...)
2025-09-18 17:39 ` [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
@ 2025-09-18 17:39 ` Marcelo Schmitt
7 siblings, 0 replies; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-18 17:39 UTC (permalink / raw)
To: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, 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>
---
Change log v1 -> v2
- Updated PGA gain constants.
- Dropped redundant call to ad4030_set_pga_gain() on PGA GPIO setup.
- Better state struct field placement to avoid holes in data.
drivers/iio/adc/ad4030.c | 229 ++++++++++++++++++++++++++++++++++++++-
1 file changed, 225 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
index 52805c779934..f5d369099f37 100644
--- a/drivers/iio/adc/ad4030.c
+++ b/drivers/iio/adc/ad4030.c
@@ -47,6 +47,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
@@ -124,6 +126,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_PGA_GAIN_MAX_NANO (NANO * 2 / 3)
+
enum ad4030_out_mode {
AD4030_OUT_DATA_MD_DIFF,
AD4030_OUT_DATA_MD_16_DIFF_8_COM,
@@ -144,6 +150,23 @@ enum {
AD4030_SCAN_TYPE_AVG,
};
+/*
+ * Gains computed as fractions of 1000 so they can be expressed by integers.
+ */
+static const int ad4030_hw_gains[] = {
+ MILLI / 3, /* 333 */
+ (5 * MILLI / 9), /* 555 */
+ (20 * MILLI / 9), /* 2222 */
+ (20 * MILLI / 3), /* 6666 */
+};
+
+static const int ad4030_hw_gains_frac[][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;
@@ -151,6 +174,7 @@ struct ad4030_chip_info {
const struct iio_chan_spec offload_channels[AD4030_MAX_IIO_CHANNEL_NB];
u8 grade;
u8 precision_bits;
+ bool has_pga;
/* Number of hardware channels */
int num_voltage_inputs;
unsigned int tcyc_ns;
@@ -175,7 +199,11 @@ struct ad4030_state {
struct spi_offload_trigger *offload_trigger;
struct spi_offload_trigger_config offload_trigger_config;
struct pwm_device *cnv_trigger;
+ size_t scale_avail_size;
struct pwm_waveform cnv_wf;
+ unsigned int scale_avail[ARRAY_SIZE(ad4030_hw_gains)][2];
+ struct gpio_descs *pga_gpios;
+ unsigned int pga_index;
/*
* DMA (thus cache coherency maintenance) requires the transfer buffers
@@ -232,7 +260,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 = \
(_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) | \
BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
@@ -243,6 +271,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, \
@@ -257,10 +286,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_average_modes[] = {
BIT(0), /* No averaging/oversampling */
@@ -414,6 +449,65 @@ 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, int_part, fract_part, 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.
+ */
+ int_part = div_u64_rem(((u64)range * MICRO) >> mag_bits, NANO, &fract_part);
+ st->scale_avail[i][0] = int_part;
+ st->scale_avail[i][1] = fract_part;
+ }
+}
+
+static int ad4030_set_pga_gain(struct ad4030_state *st)
+{
+ DECLARE_BITMAP(bitmap, ADAQ4616_PGA_PINS) = { };
+
+ bitmap_write(bitmap, st->pga_index, 0, ADAQ4616_PGA_PINS);
+
+ return gpiod_multi_set_value_cansleep(st->pga_gpios, bitmap);
+}
+
+static int ad4030_set_pga(struct iio_dev *indio_dev, int gain_int, int gain_fract)
+{
+ struct ad4030_state *st = iio_priv(indio_dev);
+ unsigned int mag_bits = st->chip->precision_bits - 1;
+ u64 gain_nano, tmp;
+
+ if (!st->pga_gpios)
+ return -EINVAL;
+
+ gain_nano = gain_int * NANO + gain_fract;
+
+ if (!in_range(gain_nano, 1, ADAQ4616_PGA_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,
@@ -433,7 +527,14 @@ static int ad4030_get_chan_scale(struct iio_dev *indio_dev,
*val2 = 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->has_pga)
+ 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,
@@ -901,6 +1002,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->scale_avail_size == 1)
+ *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;
}
@@ -976,6 +1086,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, val, val2);
+
default:
return -EINVAL;
}
@@ -997,6 +1110,17 @@ static int ad4030_write_raw(struct iio_dev *indio_dev,
return ret;
}
+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;
+ }
+}
+
static int ad4030_reg_access(struct iio_dev *indio_dev, unsigned int reg,
unsigned int writeval, unsigned int *readval)
{
@@ -1045,6 +1169,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,
@@ -1288,6 +1413,50 @@ 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 == -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 != 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 0;
+ } else if (ret != 0) {
+ return dev_err_probe(dev, ret, "Failed to get PGA value.\n");
+ }
+
+ /* 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;
@@ -1334,6 +1503,14 @@ static int ad4030_probe(struct spi_device *spi)
if (ret)
return ret;
+ if (st->chip->has_pga) {
+ 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;
@@ -1593,12 +1770,54 @@ static const struct ad4030_chip_info ad4632_24_chip_info = {
.max_sample_rate_hz = 500 * HZ_PER_KHZ,
};
+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_offload_scan_types),
+ AD4030_CHAN_CMO(1, 0),
+ },
+ .grade = AD4030_REG_CHIP_GRADE_ADAQ4216_GRADE,
+ .precision_bits = 16,
+ .has_pga = true,
+ .num_voltage_inputs = 1,
+ .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+ .max_sample_rate_hz = 2 * HZ_PER_MHZ,
+};
+
+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_offload_scan_types),
+ AD4030_CHAN_CMO(1, 0),
+ },
+ .grade = AD4030_REG_CHIP_GRADE_ADAQ4224_GRADE,
+ .precision_bits = 24,
+ .has_pga = true,
+ .num_voltage_inputs = 1,
+ .tcyc_ns = AD4030_TCYC_ADJUSTED_NS,
+ .max_sample_rate_hz = 2 * HZ_PER_MHZ,
+};
+
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);
@@ -1609,6 +1828,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.50.1
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels
2025-09-18 17:37 ` [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels Marcelo Schmitt
@ 2025-09-18 19:32 ` David Lechner
2025-09-20 10:36 ` Jonathan Cameron
0 siblings, 1 reply; 30+ messages in thread
From: David Lechner @ 2025-09-18 19:32 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
On 9/18/25 12:37 PM, Marcelo Schmitt wrote:
> Previously, the driver always used the amount of precision bits of
> differential input channels to provide the scale to mV. Though,
> differential and common-mode voltage channels have different amount of
> precision bits and the correct number of precision bits must be considered
> to get to a proper mV scale factor for each one. Use channel specific
> number of precision bits to provide the correct scale value for each
> channel.
>
> Fixes: de67f28abe58 ("iio: adc: ad4030: check scan_type for error")
> Fixes: 949abd1ca5a4 ("iio: adc: ad4030: add averaging support")
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
Reviewed-by: David Lechner <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
2025-09-18 17:38 ` [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
@ 2025-09-18 19:39 ` David Lechner
2025-09-19 17:29 ` Conor Dooley
2025-09-19 17:33 ` Conor Dooley
1 sibling, 1 reply; 30+ messages in thread
From: David Lechner @ 2025-09-18 19:39 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
On 9/18/25 12:38 PM, Marcelo Schmitt wrote:
> 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>
> ---
> 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#
I think this is already referenced for all child nodes of a SPI
controller because of pattern matching of:
patternProperties:
"^.*@[0-9a-f]+$":
type: object
$ref: spi-peripheral-props.yaml
in Documentation/devicetree/bindings/spi/spi-controller.yaml
So perhaps not strictly necessary?
Would be curious to know if there is some difference.
> +
> properties:
> compatible:
> enum:
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 3/8] Documentation: iio: ad4030: Add double PWM SPI offload doc
2025-09-18 17:38 ` [PATCH v2 3/8] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
@ 2025-09-18 19:50 ` David Lechner
0 siblings, 0 replies; 30+ messages in thread
From: David Lechner @ 2025-09-18 19:50 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
Could use docs: instead of Documentation: in the subject to
make it a bit shorter. Seems common enough.
On 9/18/25 12:38 PM, Marcelo Schmitt wrote:
> Document double PWM setup SPI offload wiring schema.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v1 -> v2
> - Swapped PWM numbering.
> - Expanded double PWM description and capture zone description.
>
> Documentation/iio/ad4030.rst | 35 +++++++++++++++++++++++++++++++++++
> 1 file changed, 35 insertions(+)
>
> diff --git a/Documentation/iio/ad4030.rst b/Documentation/iio/ad4030.rst
> index b57424b650a8..9501d3fee9bb 100644
> --- a/Documentation/iio/ad4030.rst
> +++ b/Documentation/iio/ad4030.rst
> @@ -92,6 +92,41 @@ 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 |
> + | | +--| PWM0 |
> + | | | |
> + | | +--| PWM1 |
> + | | | +-------------+
> + | | +->| 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. For AD4030 and similar ADCs, there are two
> +possible data transfer zones for sample N. One of them (zone 1) starts after the
> +data conversion for sample N is complete while the other one (zone 2) starts 9.8
> +nanoseconds after the rising edge of CNV for sample N + 1.
> +
> +The configuration depicted in the above ASCII art is intended to perform data
Could say "diagram" instead of "ASCII art" if you want to be more formal.
> +transfer in zone 2. To achieve high sample rates while meeting ADC timing
> +requirements, an offset is added between the rising edges of PWM0 and PWM1 to
> +delay the SPI transfer until 9.8 nanoseconds after CNV rising edge. This
> +requires a specialized PWM controller that can provide such an offset.
> +
Could add a link to the HDL project as an example of such hardware.
> SPI Clock mode
> --------------
>
Good enough as it is.
Reviewed-by: David Lechner <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM
2025-09-18 17:38 ` [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
@ 2025-09-18 19:51 ` David Lechner
2025-09-19 17:34 ` Conor Dooley
1 sibling, 0 replies; 30+ messages in thread
From: David Lechner @ 2025-09-18 19:51 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
On 9/18/25 12:38 PM, Marcelo Schmitt wrote:
> 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>
> ---
Reviewed-by: David Lechner <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
@ 2025-09-19 8:21 ` Nuno Sá
2025-09-20 9:42 ` Jonathan Cameron
` (2 subsequent siblings)
3 siblings, 0 replies; 30+ messages in thread
From: Nuno Sá @ 2025-09-19 8:21 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1, Sergiu Cuciurean,
Trevor Gamblin, Axel Haslam
On Thu, 2025-09-18 at 14:39 -0300, 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 to enable data capture at maximum sample rates.
>
> 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 for SPI offload support is based on work from Sergiu
> Cuciurean,
> Nuno Sa, Axel Haslam, and Trevor Gamblin. Thus, this patch comes with many
> co-developed-by tags. I also draw inspiration from other drivers supporting
> SPI
> offload, many of them written by David Lechner.
As far as I'm concerned, you can drop me (and I guess Sergiu) of the list. The
support I added was for the legacy offload support we had in ADI tree which was
very different from what we have today. Ditto for the PWM waveform API. So I
don't think it makes sense for me to take credit for this one :)
- Nuno Sá
>
> Change log v1 -> v2
> - Dropped all clock-modes and DDR related stuff for now as those will require
> further changes to the SPI subsystem or to SPI controller drivers.
> - Update the modes register with proper output data mode bits when sample
> averaging (oversampling_ratio) is set.
> - Lock on device state mutex before updating oversampling and sampling
> frequency.
> - Made sampling_frequency shared by all channels.
> - Better checking the requested sampling frequency is valid.
> - Adjusted to SPI offload data capture preparation and stop procedures.
> - Error out if try to get/set sampling frequency without offload trigger.
> - Depend on PWM so build always succeed.
> - Drop unmatched/unbalanced call to iio_device_release_direct().
> - No longer shadowing error codes.
>
> Suggestions to v1 that I did not comply to:
> [SPI]
> > I would be tempted to put the loop check here [in drivers/spi/spi-offload-
> > trigger-pwm.c]:
> >
> > 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.
>
> Not sure implementing the PWM trigger phase approximation/rounding/setup
> within
> spi-offload-trigger-pwm is actually desirable. The PWM phase
> approximation/rounding/setup done in AD4030 iterates over the configuration of
> a
> second PWM (the PWM connected to the CNV pin). I haven't seen any other device
> that would use such double PWM setup schema so pushing an additional argument
> to
> spi_offload_trigger_pwm_validate() doesn't seem worth it.
>
> [IIO]
> > Why using slower speed for offload?
> Looks like it's the same max speed for both register access and data sample.
> So, just reusing the existing define for the max transfer speed.
>
> drivers/iio/adc/Kconfig | 3 +
> drivers/iio/adc/ad4030.c | 485 +++++++++++++++++++++++++++++++++++----
> 2 files changed, 445 insertions(+), 43 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 58a14e6833f6..2a44fcaccf54 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -60,8 +60,11 @@ config AD4030
> tristate "Analog Devices AD4030 ADC Driver"
> depends on SPI
> depends on GPIOLIB
> + depends on PWM
> 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 aa0e27321869..52805c779934 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -14,15 +14,25 @@
> */
>
> #include <linux/bitfield.h>
> +#include <linux/cleanup.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/limits.h>
> +#include <linux/log2.h>
> +#include <linux/math64.h>
> +#include <linux/minmax.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>
> +#include <linux/types.h>
>
> #define AD4030_REG_INTERFACE_CONFIG_A 0x00
> #define AD4030_REG_INTERFACE_CONFIG_A_SW_RESET (BIT(0) | BIT(7))
> @@ -111,6 +121,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,
> @@ -136,11 +148,13 @@ 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 {
> @@ -153,6 +167,15 @@ struct ad4030_state {
> int offset_avail[3];
> unsigned int avg_log2;
> enum ad4030_out_mode mode;
> + struct mutex lock; /* Protect read-modify-write and multi write
> sequences */
> + /* Offload sampling */
> + 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;
> + struct pwm_device *cnv_trigger;
> + struct pwm_waveform cnv_wf;
>
> /*
> * DMA (thus cache coherency maintenance) requires the transfer
> buffers
> @@ -209,8 +232,9 @@ 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 = \
> + (_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0)
> | \
> BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> .info_mask_shared_by_all_available = \
> BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> @@ -232,6 +256,12 @@ 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[] = {
> BIT(0), /* No
> averaging/oversampling */
> BIT(1), BIT(2), BIT(3), BIT(4), /* 2 to 16 */
> @@ -240,6 +270,11 @@ static const int ad4030_average_modes[] = {
> BIT(13), BIT(14), BIT(15), BIT(16), /* 8192 to 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;
> @@ -453,6 +488,106 @@ static int ad4030_get_chan_calibbias(struct iio_dev
> *indio_dev,
> }
> }
>
> +static void ad4030_get_sampling_freq(struct ad4030_state *st, int *freq)
> +{
> + struct spi_offload_trigger_config *config = &st-
> >offload_trigger_config;
> +
> + /*
> + * Conversion data is fetched from the device when the offload
> transfer
> + * is triggered. Thus, provide the SPI offload trigger frequency as
> the
> + * sampling frequency.
> + */
> + *freq = config->periodic.frequency_hz;
> +}
> +
> +static int __ad4030_set_sampling_freq(struct ad4030_state *st,
> + unsigned int freq, unsigned int
> avg_log2)
> +{
> + struct spi_offload_trigger_config *config = &st-
> >offload_trigger_config;
> + struct pwm_waveform cnv_wf = { };
> + u64 target = AD4030_TCNVH_NS;
> + u64 offload_period_ns;
> + u64 offload_offset_ns;
> + int ret;
> +
> + /*
> + * When averaging/oversampling over N samples, we fire the offload
> + * trigger once at every N pulses of the CNV signal. Conversely, the
> CNV
> + * signal needs to be N times faster than the offload trigger. Take
> that
> + * into account to correctly re-evaluate both the PWM waveform
> connected
> + * to CNV and the SPI offload trigger.
> + */
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
> + freq <<= avg_log2;
> +
> + cnv_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 {
> + cnv_wf.duty_length_ns = target;
> + ret = pwm_round_waveform_might_sleep(st->cnv_trigger,
> &cnv_wf);
> + if (ret)
> + return ret;
> + target += AD4030_TCNVH_NS;
> + } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
> +
> + if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
> + return -EINVAL;
> +
> + offload_period_ns = cnv_wf.period_length_ns;
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
> + offload_period_ns <<= avg_log2;
> +
> + 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 += AD4030_TQUIET_CNV_DELAY_NS;
> + } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
> +
> + st->cnv_wf = cnv_wf;
> +
> + return 0;
> +}
> +
> +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> +
> + /*
> + * We have no control over the sampling frequency without SPI offload
> + * triggering.
> + */
> + if (!st->offload_trigger)
> + return -ENODEV;
> +
> + if (!in_range(freq, 1, st->chip->max_sample_rate_hz))
> + return -EINVAL;
> +
> + guard(mutex)(&st->lock);
> + return __ad4030_set_sampling_freq(st, freq, st->avg_log2);
> +}
> +
> static int ad4030_set_chan_calibscale(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int gain_int,
> @@ -507,27 +642,6 @@ static int ad4030_set_chan_calibbias(struct iio_dev
> *indio_dev,
> st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
> }
>
> -static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
> -{
> - struct ad4030_state *st = iio_priv(dev);
> - unsigned int avg_log2 = ilog2(avg_val);
> - unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
> - int ret;
> -
> - if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
> - return -EINVAL;
> -
> - ret = regmap_write(st->regmap, AD4030_REG_AVG,
> - AD4030_REG_AVG_MASK_AVG_SYNC |
> - FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL,
> avg_log2));
> - if (ret)
> - return ret;
> -
> - st->avg_log2 = avg_log2;
> -
> - return 0;
> -}
> -
> static bool ad4030_is_common_byte_asked(struct ad4030_state *st,
> unsigned int mask)
> {
> @@ -536,11 +650,10 @@ static bool ad4030_is_common_byte_asked(struct
> ad4030_state *st,
> AD4030_DUAL_COMMON_BYTE_CHANNELS_MASK);
> }
>
> -static int ad4030_set_mode(struct iio_dev *indio_dev, unsigned long mask)
> +static int ad4030_set_mode(struct ad4030_state *st, unsigned long mask,
> + unsigned int avg_log2)
> {
> - struct ad4030_state *st = iio_priv(indio_dev);
> -
> - if (st->avg_log2 > 0) {
> + if (avg_log2 > 0) {
> st->mode = AD4030_OUT_DATA_MD_30_AVERAGED_DIFF;
> } else if (ad4030_is_common_byte_asked(st, mask)) {
> switch (st->chip->precision_bits) {
> @@ -564,6 +677,50 @@ static int ad4030_set_mode(struct iio_dev *indio_dev,
> unsigned long mask)
> st->mode);
> }
>
> +static int ad4030_set_avg_frame_len(struct iio_dev *dev, unsigned long mask,
> int avg_val)
> +{
> + struct ad4030_state *st = iio_priv(dev);
> + unsigned int avg_log2 = ilog2(avg_val);
> + unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
> + int freq;
> + int ret;
> +
> + if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
> + return -EINVAL;
> +
> + guard(mutex)(&st->lock);
> + ret = ad4030_set_mode(st, mask, avg_log2);
> + if (ret)
> + return ret;
> +
> + if (st->offload_trigger) {
> + /*
> + * The sample averaging and sampling frequency configurations
> + * are mutually dependent one from another. That's because
> the
> + * effective data sample rate is fCNV / 2^N, where N is the
> + * number of samples being averaged.
> + *
> + * When SPI offload is supported and we have control over the
> + * sample rate, the conversion start signal (CNV) and the SPI
> + * offload trigger frequencies must be re-evaluated so data
> is
> + * fetched only after 'avg_val' conversions.
> + */
> + ad4030_get_sampling_freq(st, &freq);
> + ret = __ad4030_set_sampling_freq(st, freq, avg_log2);
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_write(st->regmap, AD4030_REG_AVG,
> + AD4030_REG_AVG_MASK_AVG_SYNC |
> + FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL,
> avg_log2));
> + if (ret)
> + return ret;
> +
> + st->avg_log2 = avg_log2;
> + return 0;
> +}
> +
> /*
> * Descramble 2 32bits numbers out of a 64bits. The bits are interleaved:
> * 1 bit for first number, 1 bit for the second, and so on...
> @@ -672,7 +829,7 @@ static int ad4030_single_conversion(struct iio_dev
> *indio_dev,
> struct ad4030_state *st = iio_priv(indio_dev);
> int ret;
>
> - ret = ad4030_set_mode(indio_dev, BIT(chan->scan_index));
> + ret = ad4030_set_mode(st, BIT(chan->scan_index), st->avg_log2);
> if (ret)
> return ret;
>
> @@ -769,6 +926,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 (!st->offload_trigger)
> + return -ENODEV;
> +
> + ad4030_get_sampling_freq(st, val);
> + return IIO_VAL_INT;
> +
> default:
> return -EINVAL;
> }
> @@ -807,7 +971,10 @@ static int ad4030_write_raw_dispatch(struct iio_dev
> *indio_dev,
> return ad4030_set_chan_calibbias(indio_dev, chan, val);
>
> case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> - return ad4030_set_avg_frame_len(indio_dev, val);
> + return ad4030_set_avg_frame_len(indio_dev, BIT(chan-
> >scan_index), val);
> +
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return ad4030_set_sampling_freq(indio_dev, val);
>
> default:
> return -EINVAL;
> @@ -869,7 +1036,9 @@ static int ad4030_get_current_scan_type(const struct
> iio_dev *indio_dev,
> static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
> const unsigned long *scan_mask)
> {
> - return ad4030_set_mode(indio_dev, *scan_mask);
> + struct ad4030_state *st = iio_priv(indio_dev);
> +
> + return ad4030_set_mode(st, *scan_mask, st->avg_log2);
> }
>
> static const struct iio_info ad4030_iio_info = {
> @@ -898,6 +1067,88 @@ static const struct iio_buffer_setup_ops
> ad4030_buffer_setup_ops = {
> .validate_scan_mask = ad4030_validate_scan_mask,
> };
>
> +static void ad4030_prepare_offload_msg(struct iio_dev *indio_dev)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> + u8 offload_bpw;
> +
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
> + offload_bpw = 32;
> + else
> + offload_bpw = st->chip->precision_bits;
> +
> + st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
> + st->offload_xfer.bits_per_word = roundup_pow_of_two(offload_bpw);
> + st->offload_xfer.len = spi_bpw_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_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;
> +
> + ad4030_prepare_offload_msg(indio_dev);
> + st->offload_msg.offload = st->offload;
> + ret = spi_optimize_message(st->spi, &st->offload_msg);
> + if (ret)
> + goto out_reset_mode;
> +
> + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_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->cnv_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_err(&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;
> +
> + spi_offload_trigger_disable(st->offload, st->offload_trigger);
> +
> + pwm_disable(st->cnv_trigger);
> +
> + spi_unoptimize_message(&st->offload_msg);
> +
> + /* reenter register configuration mode */
> + ret = ad4030_enter_config_mode(st);
> + if (ret)
> + dev_err(&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,
> + .validate_scan_mask = ad4030_validate_scan_mask,
> +};
> +
> static int ad4030_regulators_get(struct ad4030_state *st)
> {
> struct device *dev = &st->spi->dev;
> @@ -967,6 +1218,24 @@ 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->cnv_trigger = devm_pwm_get(dev, NULL);
> + if (IS_ERR(st->cnv_trigger))
> + return dev_err_probe(dev, PTR_ERR(st->cnv_trigger),
> + "Failed to get CNV PWM\n");
> +
> + /*
> + * Preemptively disable the PWM, since we only want to enable it with
> + * the buffer.
> + */
> + pwm_disable(st->cnv_trigger);
> +
> + return 0;
> +}
> +
> static int ad4030_config(struct ad4030_state *st)
> {
> int ret;
> @@ -994,6 +1263,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;
> @@ -1018,6 +1312,10 @@ static int ad4030_probe(struct spi_device *spi)
> if (!st->chip)
> return -EINVAL;
>
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> +
> ret = ad4030_regulators_get(st);
> if (ret)
> return ret;
> @@ -1045,24 +1343,57 @@ 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 is 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,
> + st->avg_log2);
> + if (ret)
> + return dev_err_probe(&spi->dev, ret,
> + "Failed to set offload samp
> freq\n");
> + }
>
> return devm_iio_device_register(dev, indio_dev);
> }
> @@ -1100,6 +1431,23 @@ static const struct iio_scan_type
> ad4030_24_scan_types[] = {
> },
> };
>
> +static const struct iio_scan_type ad4030_24_offload_scan_types[] = {
> + [AD4030_SCAN_TYPE_NORMAL] = {
> + .sign = 's',
> + .storagebits = 32,
> + .realbits = 24,
> + .shift = 0,
> + .endianness = IIO_CPU,
> + },
> + [AD4030_SCAN_TYPE_AVG] = {
> + .sign = 's',
> + .storagebits = 32,
> + .realbits = 30,
> + .shift = 2,
> + .endianness = IIO_CPU,
> + },
> +};
> +
> static const struct iio_scan_type ad4030_16_scan_types[] = {
> [AD4030_SCAN_TYPE_NORMAL] = {
> .sign = 's',
> @@ -1117,6 +1465,23 @@ static const struct iio_scan_type
> ad4030_16_scan_types[] = {
> }
> };
>
> +static const struct iio_scan_type ad4030_16_offload_scan_types[] = {
> + [AD4030_SCAN_TYPE_NORMAL] = {
> + .sign = 's',
> + .storagebits = 32,
> + .realbits = 16,
> + .shift = 0,
> + .endianness = IIO_CPU,
> + },
> + [AD4030_SCAN_TYPE_AVG] = {
> + .sign = 's',
> + .storagebits = 32,
> + .realbits = 30,
> + .shift = 2,
> + .endianness = IIO_CPU,
> + },
> +};
> +
> static const struct ad4030_chip_info ad4030_24_chip_info = {
> .name = "ad4030-24",
> .available_masks = ad4030_channel_masks,
> @@ -1125,10 +1490,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_offload_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 * HZ_PER_MHZ,
> };
>
> static const struct ad4030_chip_info ad4630_16_chip_info = {
> @@ -1141,10 +1511,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_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_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 * HZ_PER_MHZ,
> };
>
> static const struct ad4030_chip_info ad4630_24_chip_info = {
> @@ -1157,10 +1534,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_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_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 * HZ_PER_MHZ,
> };
>
> static const struct ad4030_chip_info ad4632_16_chip_info = {
> @@ -1173,10 +1557,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_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_16_offload_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 * HZ_PER_KHZ,
> };
>
> static const struct ad4030_chip_info ad4632_24_chip_info = {
> @@ -1189,10 +1580,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_offload_scan_types),
> + AD4030_OFFLOAD_CHAN_DIFF(1, ad4030_24_offload_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 * HZ_PER_KHZ,
> };
>
> static const struct spi_device_id ad4030_id_table[] = {
> @@ -1228,3 +1626,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");
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
2025-09-18 19:39 ` David Lechner
@ 2025-09-19 17:29 ` Conor Dooley
2025-09-19 19:53 ` Marcelo Schmitt
0 siblings, 1 reply; 30+ messages in thread
From: Conor Dooley @ 2025-09-19 17:29 UTC (permalink / raw)
To: David Lechner
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel, jic23, michael.hennerich, nuno.sa, eblanc, andy,
robh, krzk+dt, conor+dt, corbet, marcelo.schmitt1
[-- Attachment #1: Type: text/plain, Size: 1890 bytes --]
On Thu, Sep 18, 2025 at 02:39:01PM -0500, David Lechner wrote:
> On 9/18/25 12:38 PM, Marcelo Schmitt wrote:
> > 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>
> > ---
> > 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#
>
> I think this is already referenced for all child nodes of a SPI
> controller because of pattern matching of:
>
> patternProperties:
> "^.*@[0-9a-f]+$":
> type: object
> $ref: spi-peripheral-props.yaml
>
> in Documentation/devicetree/bindings/spi/spi-controller.yaml
>
> So perhaps not strictly necessary?
>
> Would be curious to know if there is some difference.
I think it's good form if you're actually referencing the properties. I
don't know if it actually makes a difference in the end result of
dtbs_check but it may in terms of making sure properties in this binding
are properly typed when it is tested against. In this case, it appears
you're only looking at uint32 properties so it mightn't have any impact.
Rob would know for sure.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
2025-09-18 17:38 ` [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
2025-09-18 19:39 ` David Lechner
@ 2025-09-19 17:33 ` Conor Dooley
1 sibling, 0 replies; 30+ messages in thread
From: Conor Dooley @ 2025-09-19 17:33 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel, jic23,
michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
[-- Attachment #1: Type: text/plain, Size: 52 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM
2025-09-18 17:38 ` [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
2025-09-18 19:51 ` David Lechner
@ 2025-09-19 17:34 ` Conor Dooley
1 sibling, 0 replies; 30+ messages in thread
From: Conor Dooley @ 2025-09-19 17:34 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel, jic23,
michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
[-- Attachment #1: Type: text/plain, Size: 52 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
2025-09-18 17:39 ` [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
@ 2025-09-19 17:36 ` Conor Dooley
2025-09-19 21:12 ` Marcelo Schmitt
0 siblings, 1 reply; 30+ messages in thread
From: Conor Dooley @ 2025-09-19 17:36 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel, jic23,
michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1
[-- Attachment #1: Type: text/plain, Size: 4428 bytes --]
On Thu, Sep 18, 2025 at 02:39:29PM -0300, Marcelo Schmitt wrote:
> ADAQ4216 and ADAQ4224 are similar to AD4030 except that 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 configurations.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
> Change log v1 -> v2
> - Use pattern to specify devices that require gain related properties.
> - Disallow gain related properties for devices that don't come with embedded PGA.
> - Documented VDDH and VDD_FDA supplies for ADAQ4216 and ADAQ4224.
> - Updated PGA gain constants.
>
> .../bindings/iio/adc/adi,ad4030.yaml | 65 +++++++++++++++++--
> 1 file changed, 60 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml
> index 564b6f67a96e..bd43c617ae11 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
> @@ -54,6 +58,14 @@ properties:
> description:
> Internal buffered Reference. Used when ref-supply is not connected.
>
> + vddh-supply:
> + description:
> + PGIA Positive Power Supply.
> +
> + vdd-fda-supply:
> + description:
> + FDA Positive Power Supply.
> +
> cnv-gpios:
> description:
> The Convert Input (CNV). It initiates the sampling conversions.
> @@ -64,6 +76,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
How come this is "value" rather than "gain"?
> + 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 555 (A1=0, A0=1)
> + Gain 2222 (A1=1, A0=0)
> + Gain 6666 (A1=1, A0=1)
> + If defined, pga-gpios must be absent.
> + enum: [333, 555, 2222, 6666]
> +
> pwms:
> description: PWM signal connected to the CNV pin.
> maxItems: 1
> @@ -86,11 +119,33 @@ required:
> - vio-supply
> - cnv-gpios
>
> -oneOf:
> - - required:
> - - ref-supply
> - - required:
> - - refin-supply
> +allOf:
> + - oneOf:
> + - required:
> + - ref-supply
> + - required:
> + - refin-supply
> + # ADAQ devices require a gain property to indicate how hardware PGA is set
> + - if:
> + properties:
> + compatible:
> + contains:
> + pattern: ^adi,adaq
> + then:
> + allOf:
> + - required: [vddh-supply, vdd-fda-supply]
> + properties:
> + ref-supply: false
> + - oneOf:
> + - required:
> + - adi,pga-value
> + - required:
> + - pga-gpios
> + else:
> + properties:
> + adi,pga-value: false
> + pga-gpios: false
> +
>
> unevaluatedProperties: false
>
> --
> 2.50.1
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
2025-09-19 17:29 ` Conor Dooley
@ 2025-09-19 19:53 ` Marcelo Schmitt
2025-09-20 9:33 ` Jonathan Cameron
0 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-19 19:53 UTC (permalink / raw)
To: Conor Dooley
Cc: David Lechner, Marcelo Schmitt, linux-iio, devicetree, linux-doc,
linux-spi, linux-kernel, jic23, michael.hennerich, nuno.sa,
eblanc, andy, robh, krzk+dt, conor+dt, corbet
On 09/19, Conor Dooley wrote:
> On Thu, Sep 18, 2025 at 02:39:01PM -0500, David Lechner wrote:
> > On 9/18/25 12:38 PM, Marcelo Schmitt wrote:
> > > 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>
> > > ---
> > > Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml | 2 ++
...
> > > @@ -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#
> >
> > I think this is already referenced for all child nodes of a SPI
> > controller because of pattern matching of:
> >
> > patternProperties:
> > "^.*@[0-9a-f]+$":
> > type: object
> > $ref: spi-peripheral-props.yaml
> >
> > in Documentation/devicetree/bindings/spi/spi-controller.yaml
> >
> > So perhaps not strictly necessary?
> >
> > Would be curious to know if there is some difference.
>
> I think it's good form if you're actually referencing the properties. I
> don't know if it actually makes a difference in the end result of
> dtbs_check but it may in terms of making sure properties in this binding
> are properly typed when it is tested against. In this case, it appears
> you're only looking at uint32 properties so it mightn't have any impact.
> Rob would know for sure.
>
There's no difference, at least on dt_binding_check output.
Initial idea was to allow using properties from spi-peripheral-props.yaml, but
they are already available through the pattern in spi-controller.yaml.
If the noise doesn't worth it, I don't mind dropping this patch.
Thanks,
Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
2025-09-19 17:36 ` Conor Dooley
@ 2025-09-19 21:12 ` Marcelo Schmitt
2025-09-21 22:20 ` Conor Dooley
0 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-19 21:12 UTC (permalink / raw)
To: Conor Dooley
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel, jic23, michael.hennerich, nuno.sa, eblanc, dlechner,
andy, robh, krzk+dt, conor+dt, corbet
On 09/19, Conor Dooley wrote:
> On Thu, Sep 18, 2025 at 02:39:29PM -0300, Marcelo Schmitt wrote:
> > ADAQ4216 and ADAQ4224 are similar to AD4030 except that 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 configurations.
> >
> > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> > ---
> > Change log v1 -> v2
> > - Use pattern to specify devices that require gain related properties.
> > - Disallow gain related properties for devices that don't come with embedded PGA.
> > - Documented VDDH and VDD_FDA supplies for ADAQ4216 and ADAQ4224.
> > - Updated PGA gain constants.
> >
> > .../bindings/iio/adc/adi,ad4030.yaml | 65 +++++++++++++++++--
> > 1 file changed, 60 insertions(+), 5 deletions(-)
> >
...
> >
> > + 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
>
> How come this is "value" rather than "gain"?
Because, for this one, I drew inspiration from ad7191 bindings [1] in the hopes
of avoiding creating new properties or using discontinued/deprecated
nomenclature [2].
The thing is, we now have ADC chips coming with PGA circuitry in front of ADC
inputs. Those PGAs are usually set/configured through hardware connections
(e.g. dedicated GPIOs or pin-strapped) and have been described in dt-bindings.
Though, since these added PGAs don't follow a pattern with respect to the
provided gain, different properties began to appear. ad7380 and ad4000 use
adi,gain-milli to describe PGA gain [3, 4], ad7191 uses adi,pga-value and,
more recently, adaq7768-1 has been proposed with adi,aaf-gain-bp [5].
adaq7768-1 is arguably a slightly different case since the signal gain stems
from an anti-aliasing filter, but it nevertheless results in signal attenuation
much like some PGAs.
I personally like the -milli (or even -permille) nomenclature because 4 digits
have been more than enough to describe the gains (at least so far). Though, I
acknowledge the base points suffix (-bp) which is documented in
property-units.yaml [6]. The only thing I don't like much about -bp for
describing PGA gain is that PGA gains are often described in terms of unitless
scale factors, while bp implies the value to be described as a percent.
Anyways, whatever property name is chosen, it will probably be better settle to
something rather than arguing about property names each time a new ADC comes
with an integrated PGA.
[1] Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml
[2] https://lore.kernel.org/linux-iio/510f6efb-ada3-4848-ac8e-16fa5d1b5284@kernel.org/
[3] Documentation/devicetree/bindings/iio/adc/adi,ad7380.yaml
[4] Documentation/devicetree/bindings/iio/adc/adi,ad4000.yaml
[5] https://lore.kernel.org/linux-iio/46842d4cf2c1149bd64188f94c60ce5e4f3b2beb.1757001160.git.Jonathan.Santos@analog.com/
[6] https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml
>
> > + 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 555 (A1=0, A0=1)
> > + Gain 2222 (A1=1, A0=0)
> > + Gain 6666 (A1=1, A0=1)
> > + If defined, pga-gpios must be absent.
> > + enum: [333, 555, 2222, 6666]
> > +
Thanks,
Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props
2025-09-19 19:53 ` Marcelo Schmitt
@ 2025-09-20 9:33 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-09-20 9:33 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: Conor Dooley, David Lechner, Marcelo Schmitt, linux-iio,
devicetree, linux-doc, linux-spi, linux-kernel, michael.hennerich,
nuno.sa, eblanc, andy, robh, krzk+dt, conor+dt, corbet
On Fri, 19 Sep 2025 16:53:09 -0300
Marcelo Schmitt <marcelo.schmitt1@gmail.com> wrote:
> On 09/19, Conor Dooley wrote:
> > On Thu, Sep 18, 2025 at 02:39:01PM -0500, David Lechner wrote:
> > > On 9/18/25 12:38 PM, Marcelo Schmitt wrote:
> > > > 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>
> > > > ---
> > > > Documentation/devicetree/bindings/iio/adc/adi,ad4030.yaml | 2 ++
> ...
> > > > @@ -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#
> > >
> > > I think this is already referenced for all child nodes of a SPI
> > > controller because of pattern matching of:
> > >
> > > patternProperties:
> > > "^.*@[0-9a-f]+$":
> > > type: object
> > > $ref: spi-peripheral-props.yaml
> > >
> > > in Documentation/devicetree/bindings/spi/spi-controller.yaml
> > >
> > > So perhaps not strictly necessary?
> > >
> > > Would be curious to know if there is some difference.
> >
> > I think it's good form if you're actually referencing the properties. I
> > don't know if it actually makes a difference in the end result of
> > dtbs_check but it may in terms of making sure properties in this binding
> > are properly typed when it is tested against. In this case, it appears
> > you're only looking at uint32 properties so it mightn't have any impact.
> > Rob would know for sure.
> >
>
> There's no difference, at least on dt_binding_check output.
> Initial idea was to allow using properties from spi-peripheral-props.yaml, but
> they are already available through the pattern in spi-controller.yaml.
> If the noise doesn't worth it, I don't mind dropping this patch.
>
I agree with Conor. Having this here might not be strictly necessary but it
helps a reader of this binding know that those properties are relevant.
So I think keep it.
Jonathan
> Thanks,
> Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
2025-09-19 8:21 ` Nuno Sá
@ 2025-09-20 9:42 ` Jonathan Cameron
2025-09-20 14:43 ` David Lechner
2025-09-22 20:54 ` David Lechner
2025-09-24 20:23 ` kernel test robot
3 siblings, 1 reply; 30+ messages in thread
From: Jonathan Cameron @ 2025-09-20 9:42 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel,
michael.hennerich, nuno.sa, eblanc, dlechner, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1, Sergiu Cuciurean,
Trevor Gamblin, Axel Haslam
On Thu, 18 Sep 2025 14:39:10 -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 to enable data capture at maximum sample rates.
>
> 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 for SPI offload support is based on work from Sergiu Cuciurean,
> Nuno Sa, Axel Haslam, and Trevor Gamblin. Thus, this patch comes with many
> co-developed-by tags. I also draw inspiration from other drivers supporting SPI
> offload, many of them written by David Lechner.
>
> Change log v1 -> v2
> - Dropped all clock-modes and DDR related stuff for now as those will require
> further changes to the SPI subsystem or to SPI controller drivers.
> - Update the modes register with proper output data mode bits when sample
> averaging (oversampling_ratio) is set.
> - Lock on device state mutex before updating oversampling and sampling frequency.
> - Made sampling_frequency shared by all channels.
> - Better checking the requested sampling frequency is valid.
> - Adjusted to SPI offload data capture preparation and stop procedures.
> - Error out if try to get/set sampling frequency without offload trigger.
> - Depend on PWM so build always succeed.
> - Drop unmatched/unbalanced call to iio_device_release_direct().
> - No longer shadowing error codes.
>
> Suggestions to v1 that I did not comply to:
> [SPI]
> > I would be tempted to put the loop check here [in drivers/spi/spi-offload-trigger-pwm.c]:
> >
> > 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.
>
> Not sure implementing the PWM trigger phase approximation/rounding/setup within
> spi-offload-trigger-pwm is actually desirable. The PWM phase
> approximation/rounding/setup done in AD4030 iterates over the configuration of a
> second PWM (the PWM connected to the CNV pin). I haven't seen any other device
> that would use such double PWM setup schema so pushing an additional argument to
> spi_offload_trigger_pwm_validate() doesn't seem worth it.
>
> [IIO]
> > Why using slower speed for offload?
> Looks like it's the same max speed for both register access and data sample.
> So, just reusing the existing define for the max transfer speed.
>
> drivers/iio/adc/Kconfig | 3 +
> drivers/iio/adc/ad4030.c | 485 +++++++++++++++++++++++++++++++++++----
> 2 files changed, 445 insertions(+), 43 deletions(-)
Hi Marcelo
Just one thing I noticed today. If nothing else comes up I can fix that
up whilst applying. However, this will benefit from review from others
+ the IIO tree is effectively closed for this cycle so we have lots of time
to tidy up any remaining stuff.
Thanks,
Jonathan
> diff --git a/drivers/iio/adc/ad4030.c b/drivers/iio/adc/ad4030.c
> index aa0e27321869..52805c779934 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> +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;
> +
> + ad4030_prepare_offload_msg(indio_dev);
> + st->offload_msg.offload = st->offload;
> + ret = spi_optimize_message(st->spi, &st->offload_msg);
> + if (ret)
> + goto out_reset_mode;
> +
> + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_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->cnv_trigger);
> +out_unoptimize:
> + spi_unoptimize_message(&st->offload_msg);
> +out_reset_mode:
> + /* reenter register configuration mode */
> + ret = ad4030_enter_config_mode(st);
This blows away the original error. I'd do something like
if (ad40303_enter_config_mode(st))
dev_err(...)
return ret;
so we preserve whatever went wrong first.
> + if (ret)
> + dev_err(&st->spi->dev,
> + "couldn't reenter register configuration mode\n");
> + return ret;
> +}
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels
2025-09-18 19:32 ` David Lechner
@ 2025-09-20 10:36 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-09-20 10:36 UTC (permalink / raw)
To: David Lechner
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel, michael.hennerich, nuno.sa, eblanc, andy, robh,
krzk+dt, conor+dt, corbet, marcelo.schmitt1
On Thu, 18 Sep 2025 14:32:23 -0500
David Lechner <dlechner@baylibre.com> wrote:
> On 9/18/25 12:37 PM, Marcelo Schmitt wrote:
> > Previously, the driver always used the amount of precision bits of
> > differential input channels to provide the scale to mV. Though,
> > differential and common-mode voltage channels have different amount of
> > precision bits and the correct number of precision bits must be considered
> > to get to a proper mV scale factor for each one. Use channel specific
> > number of precision bits to provide the correct scale value for each
> > channel.
> >
> > Fixes: de67f28abe58 ("iio: adc: ad4030: check scan_type for error")
> > Fixes: 949abd1ca5a4 ("iio: adc: ad4030: add averaging support")
> > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> > ---
> Reviewed-by: David Lechner <dlechner@baylibre.com>
>
I'll queue this one up now, but not push the branch out yet as it will
make a mess of linux-next (due to ordering of trees).
Jonathan
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-20 9:42 ` Jonathan Cameron
@ 2025-09-20 14:43 ` David Lechner
0 siblings, 0 replies; 30+ messages in thread
From: David Lechner @ 2025-09-20 14:43 UTC (permalink / raw)
To: Jonathan Cameron, Marcelo Schmitt
Cc: linux-iio, devicetree, linux-doc, linux-spi, linux-kernel,
michael.hennerich, nuno.sa, eblanc, andy, robh, krzk+dt, conor+dt,
corbet, marcelo.schmitt1, Sergiu Cuciurean, Trevor Gamblin,
Axel Haslam
On 9/20/25 4:42 AM, Jonathan Cameron wrote:
> On Thu, 18 Sep 2025 14:39:10 -0300
> Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
>
...
>
> Just one thing I noticed today. If nothing else comes up I can fix that
> up whilst applying. However, this will benefit from review from others
> + the IIO tree is effectively closed for this cycle so we have lots of time
> to tidy up any remaining stuff.
>
FYI, I have some comments on this patch I am working on but will be some
time next week before I send it since I would also like to actually test
this since I have the hardware.
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
2025-09-19 21:12 ` Marcelo Schmitt
@ 2025-09-21 22:20 ` Conor Dooley
2025-09-27 15:33 ` Jonathan Cameron
0 siblings, 1 reply; 30+ messages in thread
From: Conor Dooley @ 2025-09-21 22:20 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel, jic23, michael.hennerich, nuno.sa, eblanc, dlechner,
andy, robh, krzk+dt, conor+dt, corbet
[-- Attachment #1: Type: text/plain, Size: 5144 bytes --]
On Fri, Sep 19, 2025 at 06:12:05PM -0300, Marcelo Schmitt wrote:
> On 09/19, Conor Dooley wrote:
> > On Thu, Sep 18, 2025 at 02:39:29PM -0300, Marcelo Schmitt wrote:
> > > ADAQ4216 and ADAQ4224 are similar to AD4030 except that 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 configurations.
> > >
> > > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> > > ---
> > > Change log v1 -> v2
> > > - Use pattern to specify devices that require gain related properties.
> > > - Disallow gain related properties for devices that don't come with embedded PGA.
> > > - Documented VDDH and VDD_FDA supplies for ADAQ4216 and ADAQ4224.
> > > - Updated PGA gain constants.
> > >
> > > .../bindings/iio/adc/adi,ad4030.yaml | 65 +++++++++++++++++--
> > > 1 file changed, 60 insertions(+), 5 deletions(-)
> > >
> ...
> > >
> > > + 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
> >
> > How come this is "value" rather than "gain"?
>
> Because, for this one, I drew inspiration from ad7191 bindings [1] in the hopes
> of avoiding creating new properties or using discontinued/deprecated
> nomenclature [2].
>
> The thing is, we now have ADC chips coming with PGA circuitry in front of ADC
> inputs. Those PGAs are usually set/configured through hardware connections
> (e.g. dedicated GPIOs or pin-strapped) and have been described in dt-bindings.
> Though, since these added PGAs don't follow a pattern with respect to the
> provided gain, different properties began to appear. ad7380 and ad4000 use
> adi,gain-milli to describe PGA gain [3, 4], ad7191 uses adi,pga-value and,
> more recently, adaq7768-1 has been proposed with adi,aaf-gain-bp [5].
> adaq7768-1 is arguably a slightly different case since the signal gain stems
> from an anti-aliasing filter, but it nevertheless results in signal attenuation
> much like some PGAs.
>
> I personally like the -milli (or even -permille) nomenclature because 4 digits
> have been more than enough to describe the gains (at least so far). Though, I
> acknowledge the base points suffix (-bp) which is documented in
> property-units.yaml [6]. The only thing I don't like much about -bp for
> describing PGA gain is that PGA gains are often described in terms of unitless
> scale factors, while bp implies the value to be described as a percent.
>
> Anyways, whatever property name is chosen, it will probably be better settle to
> something rather than arguing about property names each time a new ADC comes
> with an integrated PGA.
If PGA gains are common, then ye it would make sense to have a standard
property. I guess one of the problems with doing so is that there isn't
a standard/common binding for adcs themselves, so without making one
it'd involve reviewers pushing people to the standard one. I suppose the
current adc.yaml could be made into adc-channel.yaml and adc.yaml
repurposed. I bet there are more properties than just PGA gain that
could go there.
My personal objection to "pga-value" is that it doesn't communicate by
itself what aspect of the pga it actually controls. I don't really care
what "unit" qualifier is used that much or if one is used at all. That's
more of a thing for yourself and other IIO developers to handle.
Part of me is bothered though that all these gains are not in dB! But
I'd imagine there are not really any ADCs where the registers don't
deal in unitless gain and using dB would be nothing more than an
additional headache for software developers.
> [1] Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml
> [2] https://lore.kernel.org/linux-iio/510f6efb-ada3-4848-ac8e-16fa5d1b5284@kernel.org/
> [3] Documentation/devicetree/bindings/iio/adc/adi,ad7380.yaml
> [4] Documentation/devicetree/bindings/iio/adc/adi,ad4000.yaml
> [5] https://lore.kernel.org/linux-iio/46842d4cf2c1149bd64188f94c60ce5e4f3b2beb.1757001160.git.Jonathan.Santos@analog.com/
> [6] https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml
>
> >
> > > + 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 555 (A1=0, A0=1)
> > > + Gain 2222 (A1=1, A0=0)
> > > + Gain 6666 (A1=1, A0=1)
> > > + If defined, pga-gpios must be absent.
> > > + enum: [333, 555, 2222, 6666]
> > > +
>
> Thanks,
> Marcelo
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
2025-09-19 8:21 ` Nuno Sá
2025-09-20 9:42 ` Jonathan Cameron
@ 2025-09-22 20:54 ` David Lechner
2025-09-23 15:27 ` Marcelo Schmitt
2025-09-24 20:23 ` kernel test robot
3 siblings, 1 reply; 30+ messages in thread
From: David Lechner @ 2025-09-22 20:54 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: jic23, michael.hennerich, nuno.sa, eblanc, andy, robh, krzk+dt,
conor+dt, corbet, marcelo.schmitt1, Sergiu Cuciurean,
Trevor Gamblin, Axel Haslam
On 9/18/25 12:39 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 to enable data capture at maximum sample rates.
I tried testing this with AD4630-24 but didn't have luck in actually
capturing data. I'm 100% sure the problem is with the FPGA. And the
evaluation board doesn't have any place to attach a logic analyzer for
debugging. That means that I wasn't able to reliabably test this code
yet. But I don't expect my problems to be solved any time soon, so I
don't want to let that hold up progress. I would have really liked to
have been able to see the actual timings over the wire to make sure
we got all of that correct.
>
> 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 for SPI offload support is based on work from Sergiu Cuciurean,
> Nuno Sa, Axel Haslam, and Trevor Gamblin. Thus, this patch comes with many
> co-developed-by tags. I also draw inspiration from other drivers supporting SPI
> offload, many of them written by David Lechner.
>
> Change log v1 -> v2
> - Dropped all clock-modes and DDR related stuff for now as those will require
> further changes to the SPI subsystem or to SPI controller drivers.
> - Update the modes register with proper output data mode bits when sample
> averaging (oversampling_ratio) is set.
> - Lock on device state mutex before updating oversampling and sampling frequency.
> - Made sampling_frequency shared by all channels.
> - Better checking the requested sampling frequency is valid.
> - Adjusted to SPI offload data capture preparation and stop procedures.
> - Error out if try to get/set sampling frequency without offload trigger.
> - Depend on PWM so build always succeed.
> - Drop unmatched/unbalanced call to iio_device_release_direct().
> - No longer shadowing error codes.
>
> Suggestions to v1 that I did not comply to:
> [SPI]
>> I would be tempted to put the loop check here [in drivers/spi/spi-offload-trigger-pwm.c]:
>>
>> 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.
>
> Not sure implementing the PWM trigger phase approximation/rounding/setup within
> spi-offload-trigger-pwm is actually desirable. The PWM phase
> approximation/rounding/setup done in AD4030 iterates over the configuration of a
> second PWM (the PWM connected to the CNV pin). I haven't seen any other device
> that would use such double PWM setup schema so pushing an additional argument to
> spi_offload_trigger_pwm_validate() doesn't seem worth it.
I think you are right that spi_offload_trigger_pwm_validate() is not the
correct place for this. If we end up repeating this code a lot, we can
make a helper function for it in a place that makes sense at that time.
>
> [IIO]
>> Why using slower speed for offload?
> Looks like it's the same max speed for both register access and data sample.
> So, just reusing the existing define for the max transfer speed.
I don't follow. The "REG" in AD4030_SPI_MAX_REG_XFER_SPEED stands for
"register". The actual max speed for reading sample data should be coming
from the devicetree since it is faster and depends on the wiring and VIO
voltage. It could be as much as 102 MHz.
Unrelated to this series, I still think 80 MHz is faster that it needs
to be for AD4030_SPI_MAX_REG_XFER_SPEED. It is fine to do them slower,
e.g. at 10 MHz to reduce the risk of errors and also makes it easier to
debug using a logic analyzer.
>
> drivers/iio/adc/Kconfig | 3 +
> drivers/iio/adc/ad4030.c | 485 +++++++++++++++++++++++++++++++++++----
> 2 files changed, 445 insertions(+), 43 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 58a14e6833f6..2a44fcaccf54 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -60,8 +60,11 @@ config AD4030
> tristate "Analog Devices AD4030 ADC Driver"
> depends on SPI
> depends on GPIOLIB
> + depends on PWM
PWM is currently only required when using offload, so not a strict depedency.
> 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 aa0e27321869..52805c779934 100644
> --- a/drivers/iio/adc/ad4030.c
> +++ b/drivers/iio/adc/ad4030.c
> @@ -14,15 +14,25 @@
> */
>
> #include <linux/bitfield.h>
> +#include <linux/cleanup.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/limits.h>
> +#include <linux/log2.h>
> +#include <linux/math64.h>
> +#include <linux/minmax.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>
> +#include <linux/types.h>
>
> #define AD4030_REG_INTERFACE_CONFIG_A 0x00
> #define AD4030_REG_INTERFACE_CONFIG_A_SW_RESET (BIT(0) | BIT(7))
> @@ -111,6 +121,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,
> @@ -136,11 +148,13 @@ 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 {
> @@ -153,6 +167,15 @@ struct ad4030_state {
> int offset_avail[3];
> unsigned int avg_log2;
> enum ad4030_out_mode mode;
> + struct mutex lock; /* Protect read-modify-write and multi write sequences */
> + /* Offload sampling */
> + 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;
> + struct pwm_device *cnv_trigger;
> + struct pwm_waveform cnv_wf;
>
> /*
> * DMA (thus cache coherency maintenance) requires the transfer buffers
> @@ -209,8 +232,9 @@ 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 = \
> + (_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0) | \
> BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> .info_mask_shared_by_all_available = \
> BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> @@ -232,6 +256,12 @@ 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[] = {
> BIT(0), /* No averaging/oversampling */
> BIT(1), BIT(2), BIT(3), BIT(4), /* 2 to 16 */
> @@ -240,6 +270,11 @@ static const int ad4030_average_modes[] = {
> BIT(13), BIT(14), BIT(15), BIT(16), /* 8192 to 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;
> @@ -453,6 +488,106 @@ static int ad4030_get_chan_calibbias(struct iio_dev *indio_dev,
> }
> }
>
> +static void ad4030_get_sampling_freq(struct ad4030_state *st, int *freq)
> +{
> + struct spi_offload_trigger_config *config = &st->offload_trigger_config;
> +
> + /*
> + * Conversion data is fetched from the device when the offload transfer
> + * is triggered. Thus, provide the SPI offload trigger frequency as the
> + * sampling frequency.
> + */
> + *freq = config->periodic.frequency_hz;
> +}
> +
> +static int __ad4030_set_sampling_freq(struct ad4030_state *st,
> + unsigned int freq, unsigned int avg_log2)
This has more to do with the converstion trigger frequency (when CNV is
toggled) that it does with sample rate (when we read sample data). When
oversampling is in use, the conversion rate is higher than the sample rate.
So I would be tempted to call this function ad4030_update_conversion_rate().
> +{
> + struct spi_offload_trigger_config *config = &st->offload_trigger_config;
> + struct pwm_waveform cnv_wf = { };
> + u64 target = AD4030_TCNVH_NS;
> + u64 offload_period_ns;
> + u64 offload_offset_ns;
> + int ret;
> +
> + /*
> + * When averaging/oversampling over N samples, we fire the offload
> + * trigger once at every N pulses of the CNV signal. Conversely, the CNV
> + * signal needs to be N times faster than the offload trigger. Take that
> + * into account to correctly re-evaluate both the PWM waveform connected
> + * to CNV and the SPI offload trigger.
> + */
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
I think we could drop the condition here. If oversampling is disabled (oversampling
ratio == 1) then avg_log2 should be 0. st->mode may be left in a different state
from a single conversion, so can't be relied upon to decide if oversampling is
enabled or not.
> + freq <<= avg_log2;
> +
> + cnv_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 {
> + cnv_wf.duty_length_ns = target;
> + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
> + if (ret)
> + return ret;
> + target += AD4030_TCNVH_NS;
> + } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
> +
> + if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
> + return -EINVAL;
I hit this error during testing with the default max_sample_rate_hz assigned
in probe. We could have a loop for this too to try to get the closest valid
period rather than erroring if the exact value isn't available.
> +
> + offload_period_ns = cnv_wf.period_length_ns;
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
Same as above about dropping this condition.
> + offload_period_ns <<= avg_log2;
> +
> + 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 += AD4030_TQUIET_CNV_DELAY_NS;
> + } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
> +
> + st->cnv_wf = cnv_wf;
> +
> + return 0;
> +}
> +
> +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> +
> + /*
> + * We have no control over the sampling frequency without SPI offload
> + * triggering.
> + */
> + if (!st->offload_trigger)
> + return -ENODEV;
> +
> + if (!in_range(freq, 1, st->chip->max_sample_rate_hz))
> + return -EINVAL;
> +
> + guard(mutex)(&st->lock);
Why not iio_device_claim_direct() instead of a new lock? We wouldn't
want to change the sampling frequency during a buffered read anyway.
This driver already uses iio_device_claim_direct() to protect other
register access.
> + return __ad4030_set_sampling_freq(st, freq, st->avg_log2);
> +}
> +
> static int ad4030_set_chan_calibscale(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> int gain_int,
> @@ -507,27 +642,6 @@ static int ad4030_set_chan_calibbias(struct iio_dev *indio_dev,
> st->tx_data, AD4030_REG_OFFSET_BYTES_NB);
> }
>
> -static int ad4030_set_avg_frame_len(struct iio_dev *dev, int avg_val)
> -{
> - struct ad4030_state *st = iio_priv(dev);
> - unsigned int avg_log2 = ilog2(avg_val);
> - unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
> - int ret;
> -
> - if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
> - return -EINVAL;
> -
> - ret = regmap_write(st->regmap, AD4030_REG_AVG,
> - AD4030_REG_AVG_MASK_AVG_SYNC |
> - FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL, avg_log2));
> - if (ret)
> - return ret;
> -
> - st->avg_log2 = avg_log2;
> -
> - return 0;
> -}
> -
> static bool ad4030_is_common_byte_asked(struct ad4030_state *st,
> unsigned int mask)
> {
> @@ -536,11 +650,10 @@ static bool ad4030_is_common_byte_asked(struct ad4030_state *st,
> AD4030_DUAL_COMMON_BYTE_CHANNELS_MASK);
> }
>
> -static int ad4030_set_mode(struct iio_dev *indio_dev, unsigned long mask)
> +static int ad4030_set_mode(struct ad4030_state *st, unsigned long mask,
> + unsigned int avg_log2)
> {
> - struct ad4030_state *st = iio_priv(indio_dev);
> -
> - if (st->avg_log2 > 0) {
> + if (avg_log2 > 0) {
> st->mode = AD4030_OUT_DATA_MD_30_AVERAGED_DIFF;
> } else if (ad4030_is_common_byte_asked(st, mask)) {
> switch (st->chip->precision_bits) {
> @@ -564,6 +677,50 @@ static int ad4030_set_mode(struct iio_dev *indio_dev, unsigned long mask)
> st->mode);
> }
>
> +static int ad4030_set_avg_frame_len(struct iio_dev *dev, unsigned long mask, int avg_val)
> +{
> + struct ad4030_state *st = iio_priv(dev);
> + unsigned int avg_log2 = ilog2(avg_val);
> + unsigned int last_avg_idx = ARRAY_SIZE(ad4030_average_modes) - 1;
> + int freq;
> + int ret;
> +
> + if (avg_val < 0 || avg_val > ad4030_average_modes[last_avg_idx])
> + return -EINVAL;
> +
> + guard(mutex)(&st->lock);
> + ret = ad4030_set_mode(st, mask, avg_log2);
Not sure it makes sense to set the mode here. The mode gets set again anyway
(possibly with a different mask) just before doing a single conversion or when
starting a buffered read. So this doesn't really have any useful effect AFAICT.
> + if (ret)
> + return ret;
> +
> + if (st->offload_trigger) {
> + /*
> + * The sample averaging and sampling frequency configurations
> + * are mutually dependent one from another. That's because the
> + * effective data sample rate is fCNV / 2^N, where N is the
> + * number of samples being averaged.
> + *
> + * When SPI offload is supported and we have control over the
> + * sample rate, the conversion start signal (CNV) and the SPI
> + * offload trigger frequencies must be re-evaluated so data is
> + * fetched only after 'avg_val' conversions.
> + */
> + ad4030_get_sampling_freq(st, &freq);
> + ret = __ad4030_set_sampling_freq(st, freq, avg_log2);
This looks a bit strange to "get" the sample rate and then "set" it to the
same value we just got. This is what inspired the suggestion to reanme
__ad4030_set_sampling_freq().
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_write(st->regmap, AD4030_REG_AVG,
> + AD4030_REG_AVG_MASK_AVG_SYNC |
> + FIELD_PREP(AD4030_REG_AVG_MASK_AVG_VAL, avg_log2));
> + if (ret)
> + return ret;
> +
> + st->avg_log2 = avg_log2;
> + return 0;
> +}
> +
> /*
> * Descramble 2 32bits numbers out of a 64bits. The bits are interleaved:
> * 1 bit for first number, 1 bit for the second, and so on...
> @@ -672,7 +829,7 @@ static int ad4030_single_conversion(struct iio_dev *indio_dev,
> struct ad4030_state *st = iio_priv(indio_dev);
> int ret;
>
> - ret = ad4030_set_mode(indio_dev, BIT(chan->scan_index));
> + ret = ad4030_set_mode(st, BIT(chan->scan_index), st->avg_log2);
> if (ret)
> return ret;
>
> @@ -769,6 +926,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 (!st->offload_trigger)
> + return -ENODEV;
> +
> + ad4030_get_sampling_freq(st, val);
> + return IIO_VAL_INT;
> +
> default:
> return -EINVAL;
> }
> @@ -807,7 +971,10 @@ static int ad4030_write_raw_dispatch(struct iio_dev *indio_dev,
> return ad4030_set_chan_calibbias(indio_dev, chan, val);
>
> case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> - return ad4030_set_avg_frame_len(indio_dev, val);
> + return ad4030_set_avg_frame_len(indio_dev, BIT(chan->scan_index), val);
> +
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return ad4030_set_sampling_freq(indio_dev, val);
>
> default:
> return -EINVAL;
> @@ -869,7 +1036,9 @@ static int ad4030_get_current_scan_type(const struct iio_dev *indio_dev,
> static int ad4030_update_scan_mode(struct iio_dev *indio_dev,
> const unsigned long *scan_mask)
> {
> - return ad4030_set_mode(indio_dev, *scan_mask);
> + struct ad4030_state *st = iio_priv(indio_dev);
> +
> + return ad4030_set_mode(st, *scan_mask, st->avg_log2);
> }
>
> static const struct iio_info ad4030_iio_info = {
> @@ -898,6 +1067,88 @@ static const struct iio_buffer_setup_ops ad4030_buffer_setup_ops = {
> .validate_scan_mask = ad4030_validate_scan_mask,
> };
>
> +static void ad4030_prepare_offload_msg(struct iio_dev *indio_dev)
> +{
> + struct ad4030_state *st = iio_priv(indio_dev);
> + u8 offload_bpw;
> +
> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
> + offload_bpw = 32;
> + else
> + offload_bpw = st->chip->precision_bits;
> +
> + st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
As mentioned at the beginning, drop this line and let it use the max
speed from the devicetree.
> + st->offload_xfer.bits_per_word = roundup_pow_of_two(offload_bpw);
Why roundup_pow_of_two()? The SPI controller can do 24 bits per word.
And if we are reading both a 24-bit value and the common mode voltage,
this would cause both to be read in 1 word.
Speaking of which, I think this will need a possible second xfer with
bpw=8 if we want to read the common mode voltage.
Or, if the intention was to not allow it, we need different scan masks.
But I don't see a reason why we could not allow it.
Or, if this is making a assumptions about extra hardware being present
to move bits around between reading them over the SPI bus and pushing the
values to DMA, then there should be some comments about that. More on that
below.
> + st->offload_xfer.len = spi_bpw_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_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;
> +
> + ad4030_prepare_offload_msg(indio_dev);
> + st->offload_msg.offload = st->offload;
> + ret = spi_optimize_message(st->spi, &st->offload_msg);
> + if (ret)
> + goto out_reset_mode;
> +
> + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_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->cnv_trigger);
> +out_unoptimize:
> + spi_unoptimize_message(&st->offload_msg);
> +out_reset_mode:
> + /* reenter register configuration mode */
> + ret = ad4030_enter_config_mode(st);
This overwrites the original error in `ret` and could result in the function
returning sucessfully when it should have returned an error. To fix, we can
introduce a ret2 variable for checking the return value while unwinding after
an error (and should print ret2 in the dev_err(), otherwise that info gets
lost).
> + if (ret)
> + dev_err(&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;
> +
> + spi_offload_trigger_disable(st->offload, st->offload_trigger);
> +
> + pwm_disable(st->cnv_trigger);
> +
> + spi_unoptimize_message(&st->offload_msg);
> +
> + /* reenter register configuration mode */
> + ret = ad4030_enter_config_mode(st);
> + if (ret)
> + dev_err(&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,
> + .validate_scan_mask = ad4030_validate_scan_mask,
> +};
> +
> static int ad4030_regulators_get(struct ad4030_state *st)
> {
> struct device *dev = &st->spi->dev;
> @@ -967,6 +1218,24 @@ 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->cnv_trigger = devm_pwm_get(dev, NULL);
> + if (IS_ERR(st->cnv_trigger))
> + return dev_err_probe(dev, PTR_ERR(st->cnv_trigger),
> + "Failed to get CNV PWM\n");
> +
> + /*
> + * Preemptively disable the PWM, since we only want to enable it with
> + * the buffer.
> + */
> + pwm_disable(st->cnv_trigger);
> +
> + return 0;
> +}
> +
> static int ad4030_config(struct ad4030_state *st)
> {
> int ret;
> @@ -994,6 +1263,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;
If we want to be really strict/generic here, we should not be allowing
chips with num_voltage_inputs == 2 and a single SPI bus/deserializer (i.e.
channel data is interleaved). In this case, extra hardware is required
to do the de-interleaving (i.e. the spi_axis_reorder IP block).
We could take the easy way out and just always assume that is there.
In that case, we should makes some comments here about such assumptions.
Or we could actually describe it properly in the devicetree and check
for that here. This came up during the discussions when I was upstreaming
SPI offload support. It would look something like this...
In the devicetree, instead of having the DMA connected to the SPI controller,
we now have a separate IP block with it's own node between them.
/* spi_axis_reorder IP block */
reorder: offload-stream-sink@4000000 {
compatible = "adi,axi-spi-reorder";
reg = <0x4000000 0x1000>;
clocks = <&spi_clk>;
dmas = <&adc_dma>;
};
spi@5000000 {
compatible = "adi,axi-spi-engine-1.00.a
reg = <0x4000000 0x1000>;
clocks = <&clkc 15>, <&spi_clk>;
clock-name "s_axi_aclk", "spi_clk";
trigger-sources = <&pwm_trigger>;
offload-streams = <&reorder>;
offload-stream-names = "offload0-rx";
...
};
Then here in the driver, we would need a different (non-existing)
API to get the DMA from this offload-stream rather than calling
devm_spi_offload_rx_stream_request_dma_chan(). Or extend the SPI
controller to handle that.
Or 3rd option: If easy way is not acceptable and "right way" is too much
work, we could just return error here for num_voltage_inputs == 2 until
we add support for SPI controllers with two buses/deserializers.
> +
> + 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;
> @@ -1018,6 +1312,10 @@ static int ad4030_probe(struct spi_device *spi)
> if (!st->chip)
> return -EINVAL;
>
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> +
> ret = ad4030_regulators_get(st);
> if (ret)
> return ret;
> @@ -1045,24 +1343,57 @@ 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 is 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;
It looks like scan_masks are the same in both cases, so we could leave that
outside of the if statement.
> + 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,
Max sample rate probably isn't the best choice for a default since it only works
under very specific conditions. In another driver, we picked a rate that would
still work even if we maxed out the oversampling ratio and had only one serial
line. As mentioned above, the driver failed to probe for me because of this and
I had to change the inital rate.
> + st->avg_log2);
> + if (ret)
> + return dev_err_probe(&spi->dev, ret,
> + "Failed to set offload samp freq\n");
> + }
>
> return devm_iio_device_register(dev, indio_dev);
> }
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-22 20:54 ` David Lechner
@ 2025-09-23 15:27 ` Marcelo Schmitt
2025-09-23 16:03 ` David Lechner
0 siblings, 1 reply; 30+ messages in thread
From: Marcelo Schmitt @ 2025-09-23 15:27 UTC (permalink / raw)
To: David Lechner
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel, jic23, michael.hennerich, nuno.sa, eblanc, andy,
robh, krzk+dt, conor+dt, corbet, Sergiu Cuciurean, Trevor Gamblin,
Axel Haslam
Hi David, thanks for the insightful review.
On 09/22, David Lechner wrote:
> On 9/18/25 12:39 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 to enable data capture at maximum sample rates.
>
> I tried testing this with AD4630-24 but didn't have luck in actually
> capturing data. I'm 100% sure the problem is with the FPGA. And the
> evaluation board doesn't have any place to attach a logic analyzer for
> debugging. That means that I wasn't able to reliabably test this code
> yet. But I don't expect my problems to be solved any time soon, so I
> don't want to let that hold up progress. I would have really liked to
> have been able to see the actual timings over the wire to make sure
> we got all of that correct.
>
Even if you hook up probes to the SPI lines, you might not be able to logic
analyze the transfers at frequencies like 100 MHz or even at 80 MHz unless you
have a very fast logic analyzer or oscilloscope. To debug these signals we
usually change the HDL verilog to add ILA debug cores to record the signals on
the FPGA. I'll see if I can get or build the project with those ILA cores set.
Another thing is getting the correct combination of HDL + device tree because
we have a few possible HDL build configurations (for number of lanes, clock mode,
DDR/DTR, and capture zone) and the device tree must be coherent with what runs
on the FPGA. I'll send you some of boot files I was using during my tests.
> > ---
...
> > [IIO]
> >> Why using slower speed for offload?
> > Looks like it's the same max speed for both register access and data sample.
> > So, just reusing the existing define for the max transfer speed.
>
> I don't follow. The "REG" in AD4030_SPI_MAX_REG_XFER_SPEED stands for
> "register". The actual max speed for reading sample data should be coming
> from the devicetree since it is faster and depends on the wiring and VIO
> voltage. It could be as much as 102 MHz.
>
I have finally I noticed the SPI compatible mode timings. Sure, will adapt to
use faster sample rate when possible.
> Unrelated to this series, I still think 80 MHz is faster that it needs
> to be for AD4030_SPI_MAX_REG_XFER_SPEED. It is fine to do them slower,
> e.g. at 10 MHz to reduce the risk of errors and also makes it easier to
> debug using a logic analyzer.
Sure, will do that.
>
> >
> > drivers/iio/adc/Kconfig | 3 +
> > drivers/iio/adc/ad4030.c | 485 +++++++++++++++++++++++++++++++++++----
> > 2 files changed, 445 insertions(+), 43 deletions(-)
> >
> > diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> > index 58a14e6833f6..2a44fcaccf54 100644
...
> > + cnv_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 {
> > + cnv_wf.duty_length_ns = target;
> > + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
> > + if (ret)
> > + return ret;
> > + target += AD4030_TCNVH_NS;
> > + } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
> > +
> > + if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
> > + return -EINVAL;
>
> I hit this error during testing with the default max_sample_rate_hz assigned
> in probe. We could have a loop for this too to try to get the closest valid
> period rather than erroring if the exact value isn't available.
>
Yes, this makes sense. Though, looping to try to get a suitable period wouldn't
potentially also change the duty_length we settled above?
> > +
> > + offload_period_ns = cnv_wf.period_length_ns;
> > + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
>
...
> > +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> > +{
> > + struct ad4030_state *st = iio_priv(indio_dev);
> > +
> > + /*
> > + * We have no control over the sampling frequency without SPI offload
> > + * triggering.
> > + */
> > + if (!st->offload_trigger)
> > + return -ENODEV;
> > +
> > + if (!in_range(freq, 1, st->chip->max_sample_rate_hz))
> > + return -EINVAL;
> > +
> > + guard(mutex)(&st->lock);
>
> Why not iio_device_claim_direct() instead of a new lock? We wouldn't
> want to change the sampling frequency during a buffered read anyway.
> This driver already uses iio_device_claim_direct() to protect other
> register access.
The new lock is to protect concurrent updates of the oversampling and sampling
frequency. Since, oversampling and the sampling frequency properties are
mutually dependent one from another, a simultaneous write to those attributes
could lead to an invalid oversamp + samp freq configuration.
>
> > + return __ad4030_set_sampling_freq(st, freq, st->avg_log2);
> > +}
> > +
...
> > +static void ad4030_prepare_offload_msg(struct iio_dev *indio_dev)
> > +{
> > + struct ad4030_state *st = iio_priv(indio_dev);
> > + u8 offload_bpw;
> > +
> > + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
> > + offload_bpw = 32;
> > + else
> > + offload_bpw = st->chip->precision_bits;
> > +
>
> > + st->offload_xfer.speed_hz = AD4030_SPI_MAX_REG_XFER_SPEED;
>
> As mentioned at the beginning, drop this line and let it use the max
> speed from the devicetree.
>
> > + st->offload_xfer.bits_per_word = roundup_pow_of_two(offload_bpw);
>
> Why roundup_pow_of_two()? The SPI controller can do 24 bits per word.
> And if we are reading both a 24-bit value and the common mode voltage,
> this would cause both to be read in 1 word.
>
> Speaking of which, I think this will need a possible second xfer with
> bpw=8 if we want to read the common mode voltage.
>
> Or, if the intention was to not allow it, we need different scan masks.
> But I don't see a reason why we could not allow it.
>
Nothing says we couldn't support offloading transfers with
differential + common-mode data, at least in theory. So, I didn't felt like it
should be prevented. Though, offloading differential + common-mode data is
a configuration I couldn't really test with ADAQ4216 because the HDL is ... peculiar.
> Or, if this is making a assumptions about extra hardware being present
> to move bits around between reading them over the SPI bus and pushing the
> values to DMA, then there should be some comments about that. More on that
> below.
>
> > + st->offload_xfer.len = spi_bpw_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_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;
>
> If we want to be really strict/generic here, we should not be allowing
> chips with num_voltage_inputs == 2 and a single SPI bus/deserializer (i.e.
> channel data is interleaved). In this case, extra hardware is required
> to do the de-interleaving (i.e. the spi_axis_reorder IP block).
By channel data is interleaved you mean data from both channels going out on
SDO0 (LANE_MD == 0b11)? In that case, yes, I think so. Only the ADC driver would
know about data being interleaved and it would not be able to descramble it when
data gets pushed up through DMA.
>
> We could take the easy way out and just always assume that is there.
> In that case, we should makes some comments here about such assumptions.
>
> Or we could actually describe it properly in the devicetree and check
> for that here. This came up during the discussions when I was upstreaming
> SPI offload support. It would look something like this...
>
> In the devicetree, instead of having the DMA connected to the SPI controller,
> we now have a separate IP block with it's own node between them.
>
> /* spi_axis_reorder IP block */
> reorder: offload-stream-sink@4000000 {
> compatible = "adi,axi-spi-reorder";
> reg = <0x4000000 0x1000>;
> clocks = <&spi_clk>;
> dmas = <&adc_dma>;
> };
>
> spi@5000000 {
> compatible = "adi,axi-spi-engine-1.00.a
> reg = <0x4000000 0x1000>;
> clocks = <&clkc 15>, <&spi_clk>;
> clock-name "s_axi_aclk", "spi_clk";
>
> trigger-sources = <&pwm_trigger>;
> offload-streams = <&reorder>;
> offload-stream-names = "offload0-rx";
>
> ...
> };
>
> Then here in the driver, we would need a different (non-existing)
> API to get the DMA from this offload-stream rather than calling
> devm_spi_offload_rx_stream_request_dma_chan(). Or extend the SPI
> controller to handle that.
>
> Or 3rd option: If easy way is not acceptable and "right way" is too much
> work, we could just return error here for num_voltage_inputs == 2 until
> we add support for SPI controllers with two buses/deserializers.
>
3rd option sounds more reasonable for the moment.
I think this might, alternatively, be supported as something associated with
spi-rx/tx-bus-width dt property. The question we seem to be trying to answer is,
how data coming from a multi-line bus is expected to be delivered?
> > +
> > + 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);
> > +}
> > +
Thanks,
Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-23 15:27 ` Marcelo Schmitt
@ 2025-09-23 16:03 ` David Lechner
0 siblings, 0 replies; 30+ messages in thread
From: David Lechner @ 2025-09-23 16:03 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel, jic23, michael.hennerich, nuno.sa, eblanc, andy,
robh, krzk+dt, conor+dt, corbet, Sergiu Cuciurean, Trevor Gamblin,
Axel Haslam
On 9/23/25 10:27 AM, Marcelo Schmitt wrote:
> Hi David, thanks for the insightful review.
>
> On 09/22, David Lechner wrote:
>> On 9/18/25 12:39 PM, Marcelo Schmitt wrote:
...
>>> + cnv_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 {
>>> + cnv_wf.duty_length_ns = target;
>>> + ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
>>> + if (ret)
>>> + return ret;
>>> + target += AD4030_TCNVH_NS;
>>> + } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
>>> +
>>> + if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
>>> + return -EINVAL;
>>
>> I hit this error during testing with the default max_sample_rate_hz assigned
>> in probe. We could have a loop for this too to try to get the closest valid
>> period rather than erroring if the exact value isn't available.
>>
> Yes, this makes sense. Though, looping to try to get a suitable period wouldn't
> potentially also change the duty_length we settled above?
I didn't think too hard about it or debug too deep. So it might be fine the
way it is. We'll just want to make sure that when testing with a 2 MSPS part
that we can get the max sample rate without error. The ZedBoard has some funny
rounding due to clocks being divided by 3, so it could just be a case of
having to to put in 1.998 MHz to actually get 2 MHz or something like
that because of the lack of accuracy due to rounding.
>
>>> +
>>> + offload_period_ns = cnv_wf.period_length_ns;
>>> + if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
>>
> ...
>>> +static int ad4030_set_sampling_freq(struct iio_dev *indio_dev, int freq)
>>> +{
>>> + struct ad4030_state *st = iio_priv(indio_dev);
>>> +
>>> + /*
>>> + * We have no control over the sampling frequency without SPI offload
>>> + * triggering.
>>> + */
>>> + if (!st->offload_trigger)
>>> + return -ENODEV;
>>> +
>>> + if (!in_range(freq, 1, st->chip->max_sample_rate_hz))
>>> + return -EINVAL;
>>> +
>>> + guard(mutex)(&st->lock);
>>
>> Why not iio_device_claim_direct() instead of a new lock? We wouldn't
>> want to change the sampling frequency during a buffered read anyway.
>> This driver already uses iio_device_claim_direct() to protect other
>> register access.
>
> The new lock is to protect concurrent updates of the oversampling and sampling
> frequency. Since, oversampling and the sampling frequency properties are
> mutually dependent one from another, a simultaneous write to those attributes
> could lead to an invalid oversamp + samp freq configuration.
I understand the need for the protection. And using iio_device_claim_direct()
seems like it could do the job without the need for an additional lock.
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
` (2 preceding siblings ...)
2025-09-22 20:54 ` David Lechner
@ 2025-09-24 20:23 ` kernel test robot
3 siblings, 0 replies; 30+ messages in thread
From: kernel test robot @ 2025-09-24 20:23 UTC (permalink / raw)
To: Marcelo Schmitt, linux-iio, devicetree, linux-doc, linux-spi,
linux-kernel
Cc: llvm, oe-kbuild-all, jic23, michael.hennerich, nuno.sa, eblanc,
dlechner, andy, robh, krzk+dt, conor+dt, corbet, marcelo.schmitt1,
Sergiu Cuciurean, Trevor Gamblin, Axel Haslam
Hi Marcelo,
kernel test robot noticed the following build errors:
[auto build test ERROR on 561285d048053fec8a3d6d1e3ddc60df11c393a0]
url: https://github.com/intel-lab-lkp/linux/commits/Marcelo-Schmitt/iio-adc-ad4030-Fix-_scale-value-for-common-mode-channels/20250919-014323
base: 561285d048053fec8a3d6d1e3ddc60df11c393a0
patch link: https://lore.kernel.org/r/da55c0ed6fe895dc84e79c8b64e5923a4851e58f.1758214628.git.marcelo.schmitt%40analog.com
patch subject: [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support
config: x86_64-randconfig-077-20250922 (https://download.01.org/0day-ci/archive/20250925/202509250425.p1Sm9XA1-lkp@intel.com/config)
compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250925/202509250425.p1Sm9XA1-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/202509250425.p1Sm9XA1-lkp@intel.com/
All errors (new ones prefixed by >>):
>> drivers/iio/adc/ad4030.c:561:20: error: no member named 'offset_ns' in 'struct spi_offload_trigger_periodic'
561 | config->periodic.offset_ns = offload_offset_ns;
| ~~~~~~~~~~~~~~~~ ^
drivers/iio/adc/ad4030.c:566:28: error: no member named 'offset_ns' in 'struct spi_offload_trigger_periodic'
566 | } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
| ~~~~~~~~~~~~~~~~ ^
2 errors generated.
vim +561 drivers/iio/adc/ad4030.c
502
503 static int __ad4030_set_sampling_freq(struct ad4030_state *st,
504 unsigned int freq, unsigned int avg_log2)
505 {
506 struct spi_offload_trigger_config *config = &st->offload_trigger_config;
507 struct pwm_waveform cnv_wf = { };
508 u64 target = AD4030_TCNVH_NS;
509 u64 offload_period_ns;
510 u64 offload_offset_ns;
511 int ret;
512
513 /*
514 * When averaging/oversampling over N samples, we fire the offload
515 * trigger once at every N pulses of the CNV signal. Conversely, the CNV
516 * signal needs to be N times faster than the offload trigger. Take that
517 * into account to correctly re-evaluate both the PWM waveform connected
518 * to CNV and the SPI offload trigger.
519 */
520 if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
521 freq <<= avg_log2;
522
523 cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq);
524 /*
525 * The datasheet lists a minimum time of 9.8 ns, but no maximum. If the
526 * rounded PWM's value is less than 10, increase the target value by 10
527 * and attempt to round the waveform again, until the value is at least
528 * 10 ns. Use a separate variable to represent the target in case the
529 * rounding is severe enough to keep putting the first few results under
530 * the minimum 10ns condition checked by the while loop.
531 */
532 do {
533 cnv_wf.duty_length_ns = target;
534 ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
535 if (ret)
536 return ret;
537 target += AD4030_TCNVH_NS;
538 } while (cnv_wf.duty_length_ns < AD4030_TCNVH_NS);
539
540 if (!in_range(cnv_wf.period_length_ns, AD4030_TCYC_NS, INT_MAX))
541 return -EINVAL;
542
543 offload_period_ns = cnv_wf.period_length_ns;
544 if (st->mode == AD4030_OUT_DATA_MD_30_AVERAGED_DIFF)
545 offload_period_ns <<= avg_log2;
546
547 config->periodic.frequency_hz = DIV_ROUND_UP_ULL(NSEC_PER_SEC,
548 offload_period_ns);
549
550 /*
551 * The hardware does the capture on zone 2 (when SPI trigger PWM
552 * is used). This means that the SPI trigger signal should happen at
553 * tsync + tquiet_con_delay being tsync the conversion signal period
554 * and tquiet_con_delay 9.8ns. Hence set the PWM phase accordingly.
555 *
556 * The PWM waveform API only supports nanosecond resolution right now,
557 * so round this setting to the closest available value.
558 */
559 offload_offset_ns = AD4030_TQUIET_CNV_DELAY_NS;
560 do {
> 561 config->periodic.offset_ns = offload_offset_ns;
562 ret = spi_offload_trigger_validate(st->offload_trigger, config);
563 if (ret)
564 return ret;
565 offload_offset_ns += AD4030_TQUIET_CNV_DELAY_NS;
566 } while (config->periodic.offset_ns < AD4030_TQUIET_CNV_DELAY_NS);
567
568 st->cnv_wf = cnv_wf;
569
570 return 0;
571 }
572
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224
2025-09-21 22:20 ` Conor Dooley
@ 2025-09-27 15:33 ` Jonathan Cameron
0 siblings, 0 replies; 30+ messages in thread
From: Jonathan Cameron @ 2025-09-27 15:33 UTC (permalink / raw)
To: Conor Dooley
Cc: Marcelo Schmitt, Marcelo Schmitt, linux-iio, devicetree,
linux-doc, linux-spi, linux-kernel, michael.hennerich, nuno.sa,
eblanc, dlechner, andy, robh, krzk+dt, conor+dt, corbet
On Sun, 21 Sep 2025 23:20:01 +0100
Conor Dooley <conor@kernel.org> wrote:
> On Fri, Sep 19, 2025 at 06:12:05PM -0300, Marcelo Schmitt wrote:
> > On 09/19, Conor Dooley wrote:
> > > On Thu, Sep 18, 2025 at 02:39:29PM -0300, Marcelo Schmitt wrote:
> > > > ADAQ4216 and ADAQ4224 are similar to AD4030 except that 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 configurations.
> > > >
> > > > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> > > > ---
> > > > Change log v1 -> v2
> > > > - Use pattern to specify devices that require gain related properties.
> > > > - Disallow gain related properties for devices that don't come with embedded PGA.
> > > > - Documented VDDH and VDD_FDA supplies for ADAQ4216 and ADAQ4224.
> > > > - Updated PGA gain constants.
> > > >
> > > > .../bindings/iio/adc/adi,ad4030.yaml | 65 +++++++++++++++++--
> > > > 1 file changed, 60 insertions(+), 5 deletions(-)
> > > >
> > ...
> > > >
> > > > + 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
> > >
> > > How come this is "value" rather than "gain"?
> >
> > Because, for this one, I drew inspiration from ad7191 bindings [1] in the hopes
> > of avoiding creating new properties or using discontinued/deprecated
> > nomenclature [2].
> >
> > The thing is, we now have ADC chips coming with PGA circuitry in front of ADC
> > inputs. Those PGAs are usually set/configured through hardware connections
> > (e.g. dedicated GPIOs or pin-strapped) and have been described in dt-bindings.
> > Though, since these added PGAs don't follow a pattern with respect to the
> > provided gain, different properties began to appear. ad7380 and ad4000 use
> > adi,gain-milli to describe PGA gain [3, 4], ad7191 uses adi,pga-value and,
> > more recently, adaq7768-1 has been proposed with adi,aaf-gain-bp [5].
> > adaq7768-1 is arguably a slightly different case since the signal gain stems
> > from an anti-aliasing filter, but it nevertheless results in signal attenuation
> > much like some PGAs.
> >
> > I personally like the -milli (or even -permille) nomenclature because 4 digits
> > have been more than enough to describe the gains (at least so far). Though, I
> > acknowledge the base points suffix (-bp) which is documented in
> > property-units.yaml [6]. The only thing I don't like much about -bp for
> > describing PGA gain is that PGA gains are often described in terms of unitless
> > scale factors, while bp implies the value to be described as a percent.
> >
> > Anyways, whatever property name is chosen, it will probably be better settle to
> > something rather than arguing about property names each time a new ADC comes
> > with an integrated PGA.
>
> If PGA gains are common, then ye it would make sense to have a standard
> property. I guess one of the problems with doing so is that there isn't
> a standard/common binding for adcs themselves, so without making one
> it'd involve reviewers pushing people to the standard one. I suppose the
> current adc.yaml could be made into adc-channel.yaml and adc.yaml
> repurposed. I bet there are more properties than just PGA gain that
> could go there.
>
> My personal objection to "pga-value" is that it doesn't communicate by
> itself what aspect of the pga it actually controls. I don't really care
> what "unit" qualifier is used that much or if one is used at all. That's
> more of a thing for yourself and other IIO developers to handle.
>
> Part of me is bothered though that all these gains are not in dB! But
> I'd imagine there are not really any ADCs where the registers don't
> deal in unitless gain and using dB would be nothing more than an
> additional headache for software developers.
To me this problem isn't really about PGAs at all. What it is really
about is cases where a pin on a chip is either tied to a gpio or pin strapped.
Can we provide a solution at that layer?
i.e. A way to say this GPIO input is tied high so you can't control it
but you can still read what it's current value is. Maybe there is already
a clean way to do this.
Jonathan
>
> > [1] Documentation/devicetree/bindings/iio/adc/adi,ad7191.yaml
> > [2] https://lore.kernel.org/linux-iio/510f6efb-ada3-4848-ac8e-16fa5d1b5284@kernel.org/
> > [3] Documentation/devicetree/bindings/iio/adc/adi,ad7380.yaml
> > [4] Documentation/devicetree/bindings/iio/adc/adi,ad4000.yaml
> > [5] https://lore.kernel.org/linux-iio/46842d4cf2c1149bd64188f94c60ce5e4f3b2beb.1757001160.git.Jonathan.Santos@analog.com/
> > [6] https://github.com/devicetree-org/dt-schema/blob/main/dtschema/schemas/property-units.yaml
> >
> > >
> > > > + 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 555 (A1=0, A0=1)
> > > > + Gain 2222 (A1=1, A0=0)
> > > > + Gain 6666 (A1=1, A0=1)
> > > > + If defined, pga-gpios must be absent.
> > > > + enum: [333, 555, 2222, 6666]
> > > > +
> >
> > Thanks,
> > Marcelo
^ permalink raw reply [flat|nested] 30+ messages in thread
end of thread, other threads:[~2025-09-27 15:33 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-18 17:37 [PATCH v2 0/8] Add SPI offload support to AD4030 Marcelo Schmitt
2025-09-18 17:37 ` [PATCH v2 1/8] iio: adc: ad4030: Fix _scale value for common-mode channels Marcelo Schmitt
2025-09-18 19:32 ` David Lechner
2025-09-20 10:36 ` Jonathan Cameron
2025-09-18 17:38 ` [PATCH v2 2/8] dt-bindings: iio: adc: adi,ad4030: Reference spi-peripheral-props Marcelo Schmitt
2025-09-18 19:39 ` David Lechner
2025-09-19 17:29 ` Conor Dooley
2025-09-19 19:53 ` Marcelo Schmitt
2025-09-20 9:33 ` Jonathan Cameron
2025-09-19 17:33 ` Conor Dooley
2025-09-18 17:38 ` [PATCH v2 3/8] Documentation: iio: ad4030: Add double PWM SPI offload doc Marcelo Schmitt
2025-09-18 19:50 ` David Lechner
2025-09-18 17:38 ` [PATCH v2 4/8] dt-bindings: iio: adc: adi,ad4030: Add PWM Marcelo Schmitt
2025-09-18 19:51 ` David Lechner
2025-09-19 17:34 ` Conor Dooley
2025-09-18 17:38 ` [PATCH v2 5/8] iio: adc: ad4030: Use BIT macro to improve code readability Marcelo Schmitt
2025-09-18 17:39 ` [PATCH v2 6/8] iio: adc: ad4030: Add SPI offload support Marcelo Schmitt
2025-09-19 8:21 ` Nuno Sá
2025-09-20 9:42 ` Jonathan Cameron
2025-09-20 14:43 ` David Lechner
2025-09-22 20:54 ` David Lechner
2025-09-23 15:27 ` Marcelo Schmitt
2025-09-23 16:03 ` David Lechner
2025-09-24 20:23 ` kernel test robot
2025-09-18 17:39 ` [PATCH v2 7/8] dt-bindings: iio: adc: adi,ad4030: Add ADAQ4216 and ADAQ4224 Marcelo Schmitt
2025-09-19 17:36 ` Conor Dooley
2025-09-19 21:12 ` Marcelo Schmitt
2025-09-21 22:20 ` Conor Dooley
2025-09-27 15:33 ` Jonathan Cameron
2025-09-18 17:39 ` [PATCH v2 8/8] iio: adc: ad4030: Add support for " 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).