* [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-13 10:07 [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
@ 2026-03-13 10:07 ` Radu Sabau via B4 Relay
2026-03-14 9:41 ` Krzysztof Kozlowski
` (2 more replies)
2026-03-13 10:07 ` [PATCH v3 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
` (3 subsequent siblings)
4 siblings, 3 replies; 37+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-13 10:07 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add DT bindings for the Analog Devices AD4691 family of multichannel
SAR ADCs (AD4691, AD4692, AD4693, AD4694).
The binding describes the hardware connections: an optional PWM on
the CNV pin selects CNV Clock Mode; when absent, Manual Mode is used
with CNV tied to SPI CS. GPIO pins, voltage supplies, and the
trigger-source interface for SPI Engine offload operation are also
described.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
.../devicetree/bindings/iio/adc/adi,ad4691.yaml | 180 +++++++++++++++++++++
MAINTAINERS | 8 +
include/dt-bindings/iio/adc/adi,ad4691.h | 13 ++
3 files changed, 201 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
new file mode 100644
index 000000000000..a9301e0ca851
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
@@ -0,0 +1,180 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ad4691.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD4691 Family Multichannel SAR ADCs
+
+maintainers:
+ - Radu Sabau <radu.sabau@analog.com>
+
+description: |
+ The AD4691 family are high-speed, low-power, multichannel successive
+ approximation register (SAR) analog-to-digital converters (ADCs) with
+ an SPI-compatible serial interface. The ADC supports CNV Clock Mode,
+ where an external PWM drives the CNV pin, and Manual Mode, where CNV
+ is directly tied to the SPI chip-select.
+
+ Datasheets:
+ * https://www.analog.com/en/products/ad4692.html
+ * https://www.analog.com/en/products/ad4691.html
+ * https://www.analog.com/en/products/ad4694.html
+ * https://www.analog.com/en/products/ad4693.html
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - adi,ad4691
+ - adi,ad4692
+ - adi,ad4693
+ - adi,ad4694
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 40000000
+
+ spi-cpol: true
+ spi-cpha: true
+
+ vio-supply:
+ description: I/O voltage supply (1.71V to 1.89V or VDD).
+
+ vref-supply:
+ description: External reference voltage supply (2.4V to 5.25V).
+
+ vrefin-supply:
+ description: Internal reference buffer input supply.
+
+ reset-gpios:
+ description: GPIO connected to the RESET pin (active high).
+ maxItems: 1
+
+ clocks:
+ description: Reference clock for PWM timing in CNV Clock Mode.
+ maxItems: 1
+
+ pwms:
+ description:
+ PWM connected to the CNV pin. When present, selects CNV Clock Mode where
+ the PWM drives the conversion rate. When absent, Manual Mode is used
+ (CNV tied to SPI CS).
+ maxItems: 1
+
+ pwm-names:
+ items:
+ - const: cnv
+
+ interrupts:
+ description:
+ Interrupt line connected to the ADC GP0 pin. GP0 must be physically
+ wired to an interrupt-capable input on the SoC. The ADC asserts GP0 as
+ DATA_READY at end of conversion, used both for non-offload CNV Clock Mode
+ operation and for SPI Engine offload triggering via '#trigger-source-cells'.
+ Not used in Manual Mode, where CNV is tied to SPI CS and no DATA_READY
+ signal is generated.
+ maxItems: 1
+
+ '#trigger-source-cells':
+ description: |
+ For SPI Engine offload operation, this node acts as a trigger source.
+ Two cells are required:
+ - First cell: Trigger event type (0 = BUSY, 1 = DATA_READY)
+ - Second cell: GPIO pin number (only 0 = GP0 is supported)
+
+ Macros are available in dt-bindings/iio/adc/adi,ad4691.h:
+ AD4691_TRIGGER_EVENT_BUSY, AD4691_TRIGGER_EVENT_DATA_READY
+ AD4691_TRIGGER_PIN_GP0
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - vio-supply
+ - reset-gpios
+
+allOf:
+ # vref-supply and vrefin-supply are mutually exclusive, one is required
+ - oneOf:
+ - required:
+ - vref-supply
+ - required:
+ - vrefin-supply
+
+ # CNV Clock Mode requires a reference clock.
+ - if:
+ required:
+ - pwms
+ then:
+ required:
+ - clocks
+
+ # CNV Clock Mode (pwms present) without SPI offload requires a DRDY interrupt.
+ # Offload configurations expose '#trigger-source-cells' instead.
+ - if:
+ required:
+ - pwms
+ not:
+ required:
+ - '#trigger-source-cells'
+ then:
+ required:
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ /* Example: AD4692 in CNV Clock Mode (pwms present) with standard SPI */
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ reg = <0>;
+ spi-cpol;
+ spi-cpha;
+ spi-max-frequency = <40000000>;
+
+ vio-supply = <&vio_supply>;
+ vref-supply = <&vref_5v>;
+
+ reset-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
+
+ clocks = <&ref_clk>;
+
+ pwms = <&pwm_gen 0 0>;
+ pwm-names = "cnv";
+
+ interrupts = <12 4>;
+ };
+ };
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ /* Example: AD4692 in Manual Mode (no pwms) with SPI Engine offload */
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ reg = <0>;
+ spi-cpol;
+ spi-cpha;
+ spi-max-frequency = <31250000>;
+
+ vio-supply = <&vio_supply>;
+ vrefin-supply = <&vrefin_supply>;
+
+ reset-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c..9994d107d88d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1484,6 +1484,14 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad4170-4.yaml
F: drivers/iio/adc/ad4170-4.c
+ANALOG DEVICES INC AD4691 DRIVER
+M: Radu Sabau <radu.sabau@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F: include/dt-bindings/iio/adc/adi,ad4691.h
+
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
M: Nuno Sá <nuno.sa@analog.com>
diff --git a/include/dt-bindings/iio/adc/adi,ad4691.h b/include/dt-bindings/iio/adc/adi,ad4691.h
new file mode 100644
index 000000000000..294b03974f48
--- /dev/null
+++ b/include/dt-bindings/iio/adc/adi,ad4691.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef _DT_BINDINGS_ADI_AD4691_H
+#define _DT_BINDINGS_ADI_AD4691_H
+
+/* Trigger event types */
+#define AD4691_TRIGGER_EVENT_BUSY 0
+#define AD4691_TRIGGER_EVENT_DATA_READY 1
+
+/* Trigger GPIO pin selection */
+#define AD4691_TRIGGER_PIN_GP0 0
+
+#endif /* _DT_BINDINGS_ADI_AD4691_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-13 10:07 ` [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
@ 2026-03-14 9:41 ` Krzysztof Kozlowski
2026-03-16 11:55 ` Sabau, Radu bogdan
2026-03-14 15:29 ` David Lechner
2026-03-14 18:18 ` David Lechner
2 siblings, 1 reply; 37+ messages in thread
From: Krzysztof Kozlowski @ 2026-03-14 9:41 UTC (permalink / raw)
To: Radu Sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio, devicetree, linux-kernel, linux-pwm,
linux-gpio
On Fri, Mar 13, 2026 at 12:07:25PM +0200, Radu Sabau wrote:
> Add DT bindings for the Analog Devices AD4691 family of multichannel
> SAR ADCs (AD4691, AD4692, AD4693, AD4694).
subject, you did not implement entire feedback. Respond to all the
comments and implement them.
I finish the review here.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-14 9:41 ` Krzysztof Kozlowski
@ 2026-03-16 11:55 ` Sabau, Radu bogdan
0 siblings, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 11:55 UTC (permalink / raw)
To: Krzysztof Kozlowski
Cc: Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
David Lechner, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org
> -----Original Message-----
> From: Krzysztof Kozlowski <krzk@kernel.org>
> Sent: Saturday, March 14, 2026 11:42 AM
> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>
> Cc: Lars-Peter Clausen <lars@metafoo.de>; Hennerich, Michael
> <Michael.Hennerich@analog.com>; Jonathan Cameron <jic23@kernel.org>;
> David Lechner <dlechner@baylibre.com>; Sa, Nuno <Nuno.Sa@analog.com>;
> Andy Shevchenko <andy@kernel.org>; Rob Herring <robh@kernel.org>;
> Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
> <conor+dt@kernel.org>; Uwe Kleine-König <ukleinek@kernel.org>; Liam
> Girdwood <lgirdwood@gmail.com>; Mark Brown <broonie@kernel.org>; Linus
> Walleij <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>; Philipp
> Zabel <p.zabel@pengutronix.de>; linux-iio@vger.kernel.org;
> devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-
> pwm@vger.kernel.org; linux-gpio@vger.kernel.org
> Subject: Re: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691
> family
>
> [External]
>
> On Fri, Mar 13, 2026 at 12:07:25PM +0200, Radu Sabau wrote:
> > Add DT bindings for the Analog Devices AD4691 family of multichannel
> > SAR ADCs (AD4691, AD4692, AD4693, AD4694).
>
> subject, you did not implement entire feedback. Respond to all the
> comments and implement them.
>
> I finish the review here.
>
I see what you mean now, sorry for the confusion on my end.
I will implement the subject in the next version.
Best Regards,
Radu
> Best regards,
> Krzysztof
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-13 10:07 ` [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
2026-03-14 9:41 ` Krzysztof Kozlowski
@ 2026-03-14 15:29 ` David Lechner
2026-03-14 16:19 ` David Lechner
2026-03-16 12:39 ` Sabau, Radu bogdan
2026-03-14 18:18 ` David Lechner
2 siblings, 2 replies; 37+ messages in thread
From: David Lechner @ 2026-03-14 15:29 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add DT bindings for the Analog Devices AD4691 family of multichannel
> SAR ADCs (AD4691, AD4692, AD4693, AD4694).
>
> The binding describes the hardware connections: an optional PWM on
> the CNV pin selects CNV Clock Mode; when absent, Manual Mode is used
> with CNV tied to SPI CS. GPIO pins, voltage supplies, and the
> trigger-source interface for SPI Engine offload operation are also
> described.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> .../devicetree/bindings/iio/adc/adi,ad4691.yaml | 180 +++++++++++++++++++++
> MAINTAINERS | 8 +
> include/dt-bindings/iio/adc/adi,ad4691.h | 13 ++
> 3 files changed, 201 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> new file mode 100644
> index 000000000000..a9301e0ca851
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> @@ -0,0 +1,180 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/adi,ad4691.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices AD4691 Family Multichannel SAR ADCs
> +
> +maintainers:
> + - Radu Sabau <radu.sabau@analog.com>
> +
> +description: |
> + The AD4691 family are high-speed, low-power, multichannel successive
> + approximation register (SAR) analog-to-digital converters (ADCs) with
> + an SPI-compatible serial interface. The ADC supports CNV Clock Mode,
> + where an external PWM drives the CNV pin, and Manual Mode, where CNV
> + is directly tied to the SPI chip-select.
> +
> + Datasheets:
> + * https://www.analog.com/en/products/ad4692.html
> + * https://www.analog.com/en/products/ad4691.html
> + * https://www.analog.com/en/products/ad4694.html
> + * https://www.analog.com/en/products/ad4693.html
> +
> +$ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +properties:
> + compatible:
> + enum:
> + - adi,ad4691
> + - adi,ad4692
> + - adi,ad4693
> + - adi,ad4694
> +
> + reg:
> + maxItems: 1
> +
> + spi-max-frequency:
> + maximum: 40000000
> +
> + spi-cpol: true
> + spi-cpha: true
> +
> + vio-supply:
> + description: I/O voltage supply (1.71V to 1.89V or VDD).
> +
Missing avdd-supply and ldo-in-supply.
> + vref-supply:
> + description: External reference voltage supply (2.4V to 5.25V).
> +
> + vrefin-supply:
> + description: Internal reference buffer input supply.
> +
It always confuses me when "v" is added to the pin name. Generally, "VREF"
is an internal signal and "REF" is the actual pin name. So it makes more
sense to me to call these ref-supply and refin-supply.
> + reset-gpios:
> + description: GPIO connected to the RESET pin (active high).
Datasheet says "Active Low".
> + maxItems: 1
> +
> + clocks:
> + description: Reference clock for PWM timing in CNV Clock Mode.
> + maxItems: 1
I feel like I asked this already, but which pin is this clock connected to?
It sounds like it is the clock for the PWM, not the ADC. So it does not belong
here.
> +
> + pwms:
> + description:
> + PWM connected to the CNV pin. When present, selects CNV Clock Mode where
> + the PWM drives the conversion rate. When absent, Manual Mode is used
> + (CNV tied to SPI CS).
> + maxItems: 1
> +
> + pwm-names:
> + items:
> + - const: cnv
Usually, we don't have a name when there is only one.
> +
> + interrupts:
> + description:
> + Interrupt line connected to the ADC GP0 pin. GP0 must be physically
> + wired to an interrupt-capable input on the SoC. The ADC asserts GP0 as
> + DATA_READY at end of conversion, used both for non-offload CNV Clock Mode
> + operation and for SPI Engine offload triggering via '#trigger-source-cells'.
> + Not used in Manual Mode, where CNV is tied to SPI CS and no DATA_READY
> + signal is generated.
> + maxItems: 1
> +
> + '#trigger-source-cells':
> + description: |
> + For SPI Engine offload operation, this node acts as a trigger source.
I don't think we need to call out SPI offload here. In theory, this could
also also be used for synchronization with something else.
> + Two cells are required:
> + - First cell: Trigger event type (0 = BUSY, 1 = DATA_READY)
I'm wondering if we really need to specify the event type. For interrupts,
we we just specify the pin and not the function when the pin has more than
one possible function.
I know that we have done something like this on some of the previous SPI
offload devices. So maybe there was a good reason for it. Or maybe I just
had tunnel vision at the time.
I suggest we try implementing this with just one cell that specifies the
physical pin. In the driver, when SPI_OFFLOAD_TRIGGER_DATA_READY is
requested in the driver, we can use that to program the function of the
pin accordingly.
> + - Second cell: GPIO pin number (only 0 = GP0 is supported)
If GP0 is the only possible pin for an output, we should omit the cell. If
there are more possible pins, we should document them (even if the driver
doesn't support it).
> +
> + Macros are available in dt-bindings/iio/adc/adi,ad4691.h:
> + AD4691_TRIGGER_EVENT_BUSY, AD4691_TRIGGER_EVENT_DATA_READY
> + AD4691_TRIGGER_PIN_GP0
> + const: 2
> +
> +required:
> + - compatible
> + - reg
> + - vio-supply
> + - reset-gpios
> +
> +allOf:
> + # vref-supply and vrefin-supply are mutually exclusive, one is required
> + - oneOf:
> + - required:
> + - vref-supply
> + - required:
> + - vrefin-supply
> +
> + # CNV Clock Mode requires a reference clock.
> + - if:
> + required:
> + - pwms
> + then:
> + required:
> + - clocks
> +
> + # CNV Clock Mode (pwms present) without SPI offload requires a DRDY interrupt.
> + # Offload configurations expose '#trigger-source-cells' instead.
> + - if:
> + required:
> + - pwms
> + not:
> + required:
> + - '#trigger-source-cells'
> + then:
> + required:
> + - interrupts
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> +
> + /* Example: AD4692 in CNV Clock Mode (pwms present) with standard SPI */
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + adc@0 {
> + compatible = "adi,ad4692";
> + reg = <0>;
> + spi-cpol;
> + spi-cpha;
> + spi-max-frequency = <40000000>;
> +
> + vio-supply = <&vio_supply>;
> + vref-supply = <&vref_5v>;
> +
> + reset-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
I would expect reset to be active low to match the hardware.
> +
> + clocks = <&ref_clk>;
> +
> + pwms = <&pwm_gen 0 0>;
> + pwm-names = "cnv";
Should we also include the trigger in this example?
> +
> + interrupts = <12 4>;
> + };
> + };
> +
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> +
> + /* Example: AD4692 in Manual Mode (no pwms) with SPI Engine offload */
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + adc@0 {
> + compatible = "adi,ad4692";
> + reg = <0>;
> + spi-cpol;
> + spi-cpha;
> + spi-max-frequency = <31250000>;
> +
> + vio-supply = <&vio_supply>;
> + vrefin-supply = <&vrefin_supply>;
> +
> + reset-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
> + };
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 61bf550fd37c..9994d107d88d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1484,6 +1484,14 @@ W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/adc/adi,ad4170-4.yaml
> F: drivers/iio/adc/ad4170-4.c
>
> +ANALOG DEVICES INC AD4691 DRIVER
> +M: Radu Sabau <radu.sabau@analog.com>
> +L: linux-iio@vger.kernel.org
> +S: Supported
> +W: https://ez.analog.com/linux-software-drivers
> +F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> +F: include/dt-bindings/iio/adc/adi,ad4691.h
> +
> ANALOG DEVICES INC AD4695 DRIVER
> M: Michael Hennerich <michael.hennerich@analog.com>
> M: Nuno Sá <nuno.sa@analog.com>
> diff --git a/include/dt-bindings/iio/adc/adi,ad4691.h b/include/dt-bindings/iio/adc/adi,ad4691.h
> new file mode 100644
> index 000000000000..294b03974f48
> --- /dev/null
> +++ b/include/dt-bindings/iio/adc/adi,ad4691.h
> @@ -0,0 +1,13 @@
> +/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
> +
> +#ifndef _DT_BINDINGS_ADI_AD4691_H
> +#define _DT_BINDINGS_ADI_AD4691_H
> +
> +/* Trigger event types */
> +#define AD4691_TRIGGER_EVENT_BUSY 0
> +#define AD4691_TRIGGER_EVENT_DATA_READY 1
> +
> +/* Trigger GPIO pin selection */
> +#define AD4691_TRIGGER_PIN_GP0 0
Could probably do without a macro to give GP0/1/2/3 names since
it will be pretty obvious that 0 = GP0, etc.
> +
> +#endif /* _DT_BINDINGS_ADI_AD4691_H */
>
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-14 15:29 ` David Lechner
@ 2026-03-14 16:19 ` David Lechner
2026-03-16 12:39 ` Sabau, Radu bogdan
1 sibling, 0 replies; 37+ messages in thread
From: David Lechner @ 2026-03-14 16:19 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On 3/14/26 10:29 AM, David Lechner wrote:
> On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
>> From: Radu Sabau <radu.sabau@analog.com>
>>
>> Add DT bindings for the Analog Devices AD4691 family of multichannel
>> SAR ADCs (AD4691, AD4692, AD4693, AD4694).
>>
...
>> +
>> +properties:
More properties we can add: gpio-controller and #gpio-cells
It doesn't matter if the driver implements it or not. We want the
bindings to be as complete as possible and we know these are the
correct properties for ADCs with GPIOs.
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-14 15:29 ` David Lechner
2026-03-14 16:19 ` David Lechner
@ 2026-03-16 12:39 ` Sabau, Radu bogdan
2026-03-16 12:55 ` Sabau, Radu bogdan
2026-03-16 15:14 ` David Lechner
1 sibling, 2 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 12:39 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, March 14, 2026 5:30 PM
> On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
...
> > +
> > + clocks:
> > + description: Reference clock for PWM timing in CNV Clock Mode.
> > + maxItems: 1
>
> I feel like I asked this already, but which pin is this clock connected to?
> It sounds like it is the clock for the PWM, not the ADC. So it does not belong
> here.
>
The pin is connected to the CNV pin of the ADC, which in CNV Clock Mode
replaces the internal oscillator.
> > +
> > + pwms:
> > + description:
> > + PWM connected to the CNV pin. When present, selects CNV Clock Mode
...
> > + Two cells are required:
> > + - First cell: Trigger event type (0 = BUSY, 1 = DATA_READY)
>
> I'm wondering if we really need to specify the event type. For interrupts,
> we we just specify the pin and not the function when the pin has more than
> one possible function.
>
> I know that we have done something like this on some of the previous SPI
> offload devices. So maybe there was a good reason for it. Or maybe I just
> had tunnel vision at the time.
>
> I suggest we try implementing this with just one cell that specifies the
> physical pin. In the driver, when SPI_OFFLOAD_TRIGGER_DATA_READY is
> requested in the driver, we can use that to program the function of the
> pin accordingly.
I agree with this, since only DATA_READY will be used anyway as an interrupt
in CNV_CLOCK mode.
In fact, I am now thinking of removing ADC_BUSY entirely, since its used in
just two cases, which none of them perhaps make sense :
1. Manual Mode,where ADC_BUSY is selected for GPx, though is not used as
an interrupt or 'feedback' of anyway.
2. Autonomous Mode, where in theory it would be used to see when each
channel was sampled, but this mode is used for just once channel single
shot reading, so again, not actually used.
The implementation would see the enum removed and just initializing
the GPx pin used as DATA READY using a macro.
What are your thoughts on this?
>
> > + - Second cell: GPIO pin number (only 0 = GP0 is supported)
>
> If GP0 is the only possible pin for an output, we should omit the cell. If
> there are more possible pins, we should document them (even if the driver
> doesn't support it).
You are also right about this, other pins can be used as DATA_READY, and so
the DT should perhaps indicate which of those pins is actually used, so
that we know at probe (gpio_setup would make a comeback?) which
value should be written to the GPIO registers.
>
> > +
> > + Macros are available in dt-bindings/iio/adc/adi,ad4691.h:
> > + AD4691_TRIGGER_EVENT_BUSY,
...
> > +
> > + clocks = <&ref_clk>;
> > +
> > + pwms = <&pwm_gen 0 0>;
> > + pwm-names = "cnv";
>
> Should we also include the trigger in this example?
>
In this example, I would say this is needed since the CNV PWM is
not only starting the conversion on the ADC, but also controlling
the sampling rate, making custom sampling rates available in
comparison to the internal oscillator used by AUTONOMOUS.
> > +
> > + interrupts = <12 4>;
Best Regards,
Radu
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-16 12:39 ` Sabau, Radu bogdan
@ 2026-03-16 12:55 ` Sabau, Radu bogdan
2026-03-16 15:14 ` David Lechner
1 sibling, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 12:55 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: Sabau, Radu bogdan
> Sent: Monday, March 16, 2026 2:39 PM
...
> > > +
> > > + clocks:
> > > + description: Reference clock for PWM timing in CNV Clock Mode.
> > > + maxItems: 1
> >
> > I feel like I asked this already, but which pin is this clock connected to?
> > It sounds like it is the clock for the PWM, not the ADC. So it does not belong
> > here.
> >
>
> The pin is connected to the CNV pin of the ADC, which in CNV Clock Mode
> replaces the internal oscillator.
>
My bad here, you were referring to the clock, not the PWM, therefore you
are right, this should be removed from here.
> > > +
> > > + pwms:
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-16 12:39 ` Sabau, Radu bogdan
2026-03-16 12:55 ` Sabau, Radu bogdan
@ 2026-03-16 15:14 ` David Lechner
2026-03-16 15:47 ` Sabau, Radu bogdan
1 sibling, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-16 15:14 UTC (permalink / raw)
To: Sabau, Radu bogdan, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
On 3/16/26 7:39 AM, Sabau, Radu bogdan wrote:
>
>
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Saturday, March 14, 2026 5:30 PM
>> On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
>
> ...
>
>>> +
>>> + clocks:
>>> + description: Reference clock for PWM timing in CNV Clock Mode.
>>> + maxItems: 1
>>
>> I feel like I asked this already, but which pin is this clock connected to?
>> It sounds like it is the clock for the PWM, not the ADC. So it does not belong
>> here.
>>
>
> The pin is connected to the CNV pin of the ADC, which in CNV Clock Mode
> replaces the internal oscillator.
>
>>> +
>>> + pwms:
>>> + description:
>>> + PWM connected to the CNV pin. When present, selects CNV Clock Mode
>
> ...
>
>>> + Two cells are required:
>>> + - First cell: Trigger event type (0 = BUSY, 1 = DATA_READY)
>>
>> I'm wondering if we really need to specify the event type. For interrupts,
>> we we just specify the pin and not the function when the pin has more than
>> one possible function.
>>
>> I know that we have done something like this on some of the previous SPI
>> offload devices. So maybe there was a good reason for it. Or maybe I just
>> had tunnel vision at the time.
>>
>> I suggest we try implementing this with just one cell that specifies the
>> physical pin. In the driver, when SPI_OFFLOAD_TRIGGER_DATA_READY is
>> requested in the driver, we can use that to program the function of the
>> pin accordingly.
>
> I agree with this, since only DATA_READY will be used anyway as an interrupt
> in CNV_CLOCK mode.
> In fact, I am now thinking of removing ADC_BUSY entirely, since its used in
> just two cases, which none of them perhaps make sense :
>
> 1. Manual Mode,where ADC_BUSY is selected for GPx, though is not used as
> an interrupt or 'feedback' of anyway.
> 2. Autonomous Mode, where in theory it would be used to see when each
> channel was sampled, but this mode is used for just once channel single
> shot reading, so again, not actually used.
>
> The implementation would see the enum removed and just initializing
> the GPx pin used as DATA READY using a macro.
>
> What are your thoughts on this?
We should try to consider every reasonable possible wiring situation.
The only case I can think where the devicetree might need to know the
requested function in addition to which pin is if the pin is wired to
something not controlled by Linux. That is an odd enough situation though
that we could defer considering that. I think we could add support for such
a thing later if we needed to without breaking the existing bindings.
So hopefully I am thinking clearly enough about this to say, yes, we
should just go with #trigger-source-cells = <1>; where the cell is the
GP pin number.
>
>>
>>> + - Second cell: GPIO pin number (only 0 = GP0 is supported)
>>
>> If GP0 is the only possible pin for an output, we should omit the cell. If
>> there are more possible pins, we should document them (even if the driver
>> doesn't support it).
>
> You are also right about this, other pins can be used as DATA_READY, and so
> the DT should perhaps indicate which of those pins is actually used, so
> that we know at probe (gpio_setup would make a comeback?) which
> value should be written to the GPIO registers.
>
>>
>>> +
>>> + Macros are available in dt-bindings/iio/adc/adi,ad4691.h:
>>> + AD4691_TRIGGER_EVENT_BUSY,
>
> ...
>
>>> +
>>> + clocks = <&ref_clk>;
>>> +
>>> + pwms = <&pwm_gen 0 0>;
>>> + pwm-names = "cnv";
>>
>> Should we also include the trigger in this example?
>>
>
> In this example, I would say this is needed since the CNV PWM is
> not only starting the conversion on the ADC, but also controlling
> the sampling rate, making custom sampling rates available in
> comparison to the internal oscillator used by AUTONOMOUS.
The point was to have an example that shows SPI offload usage.
I assume this would be more common that PWM without SPI offload.
>
>>> +
>>> + interrupts = <12 4>;
>
> Best Regards,
> Radu
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-16 15:14 ` David Lechner
@ 2026-03-16 15:47 ` Sabau, Radu bogdan
0 siblings, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 15:47 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, March 16, 2026 5:14 PM
...
> > I agree with this, since only DATA_READY will be used anyway as an interrupt
> > in CNV_CLOCK mode.
> > In fact, I am now thinking of removing ADC_BUSY entirely, since its used in
> > just two cases, which none of them perhaps make sense :
> >
> > 1. Manual Mode,where ADC_BUSY is selected for GPx, though is not used as
> > an interrupt or 'feedback' of anyway.
> > 2. Autonomous Mode, where in theory it would be used to see when each
> > channel was sampled, but this mode is used for just once channel single
> > shot reading, so again, not actually used.
> >
> > The implementation would see the enum removed and just initializing
> > the GPx pin used as DATA READY using a macro.
> >
> > What are your thoughts on this?
>
>
> We should try to consider every reasonable possible wiring situation.
> The only case I can think where the devicetree might need to know the
> requested function in addition to which pin is if the pin is wired to
> something not controlled by Linux. That is an odd enough situation though
> that we could defer considering that. I think we could add support for such
> a thing later if we needed to without breaking the existing bindings.
>
> So hopefully I am thinking clearly enough about this to say, yes, we
> should just go with #trigger-source-cells = <1>; where the cell is the
> GP pin number.
>
I agree. This will be covered in the next version then.
> >
> >>
> >>> + - Second cell: GPIO pin number (only 0 = GP0 is supported)
...
> >
> > In this example, I would say this is needed since the CNV PWM is
> > not only starting the conversion on the ADC, but also controlling
> > the sampling rate, making custom sampling rates available in
> > comparison to the internal oscillator used by AUTONOMOUS.
>
> The point was to have an example that shows SPI offload usage.
> I assume this would be more common that PWM without SPI offload.
>
Indeed, this mode would be more commonly used with offload. However
the PWM would also be used although offload is not.
> >
> >>> +
> >>> + interrupts = <12 4>;
> >
> > Best Regards,
> > Radu
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-13 10:07 ` [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
2026-03-14 9:41 ` Krzysztof Kozlowski
2026-03-14 15:29 ` David Lechner
@ 2026-03-14 18:18 ` David Lechner
2 siblings, 0 replies; 37+ messages in thread
From: David Lechner @ 2026-03-14 18:18 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add DT bindings for the Analog Devices AD4691 family of multichannel
> SAR ADCs (AD4691, AD4692, AD4693, AD4694).
>
...
> + interrupts:
> + description:
> + Interrupt line connected to the ADC GP0 pin. GP0 must be physically
> + wired to an interrupt-capable input on the SoC. The ADC asserts GP0 as
> + DATA_READY at end of conversion, used both for non-offload CNV Clock Mode
> + operation and for SPI Engine offload triggering via '#trigger-source-cells'.
> + Not used in Manual Mode, where CNV is tied to SPI CS and no DATA_READY
> + signal is generated.
> + maxItems: 1
> +
Some chips have 4 GP pins, so there can be up to 4 interrupts.
Also, the DT bindings should not specify which event this is - it is
programmable. We should just say which pin is physically wired. So
interrupt-names should be "gp0", "gp1", "gp2", "gp3".
It will be up to the driver to decide how it wants to use these.
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-13 10:07 [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-03-13 10:07 ` [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
@ 2026-03-13 10:07 ` Radu Sabau via B4 Relay
2026-03-13 10:58 ` Andy Shevchenko
` (3 more replies)
2026-03-13 10:07 ` [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
` (2 subsequent siblings)
4 siblings, 4 replies; 37+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-13 10:07 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add support for the Analog Devices AD4691 family of high-speed,
low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
AD4694 (8-ch, 1 MSPS).
The driver implements a custom regmap layer over raw SPI to handle the
device's mixed 1/2/3/4-byte register widths and uses the standard IIO
read_raw/write_raw interface for single-channel reads.
Two buffered operating modes are supported, auto-detected from the
device tree:
- CNV Clock Mode: an external PWM drives the CNV pin; the sampling
rate is controlled via the PWM period. Requires a
reference clock and a DATA_READY interrupt.
- Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
a conversion and returns the previous result
(pipelined). No external clock or interrupt needed.
In both modes the chip idles in Autonomous Mode so that single-shot
read_raw can use the internal oscillator without disturbing the
hardware configuration.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4691.c | 772 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 785 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9994d107d88d..5325f7d3b7f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F: drivers/iio/adc/ad4691.c
F: include/dt-bindings/iio/adc/adi,ad4691.h
ANALOG DEVICES INC AD4695 DRIVER
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 60038ae8dfc4..3685a03aa8dc 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -139,6 +139,17 @@ config AD4170_4
To compile this driver as a module, choose M here: the module will be
called ad4170-4.
+config AD4691
+ tristate "Analog Devices AD4691 Family ADC Driver"
+ depends on SPI
+ select REGMAP
+ help
+ Say yes here to build support for Analog Devices AD4691 Family MuxSAR
+ SPI analog to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad4691.
+
config AD4695
tristate "Analog Device AD4695 ADC Driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index c76550415ff1..4ac1ea09d773 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
obj-$(CONFIG_AD4130) += ad4130.o
obj-$(CONFIG_AD4134) += ad4134.o
obj-$(CONFIG_AD4170_4) += ad4170-4.o
+obj-$(CONFIG_AD4691) += ad4691.o
obj-$(CONFIG_AD4695) += ad4695.o
obj-$(CONFIG_AD4851) += ad4851.o
obj-$(CONFIG_AD7091R) += ad7091r-base.o
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
new file mode 100644
index 000000000000..31eafa12bef8
--- /dev/null
+++ b/drivers/iio/adc/ad4691.c
@@ -0,0 +1,772 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024-2026 Analog Devices, Inc.
+ * Author: Radu Sabau <radu.sabau@analog.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/reset.h>
+#include <linux/interrupt.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/util_macros.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+#include <dt-bindings/iio/adc/adi,ad4691.h>
+
+#define AD4691_VREF_uV_MIN 2400000
+#define AD4691_VREF_uV_MAX 5250000
+
+/*
+ * Default sampling frequency for MANUAL_MODE.
+ * Each sample needs (num_channels + 1) SPI transfers of 24 bits.
+ * The factor 36 = 24 * 3/2 folds in a 50% scheduling margin:
+ * freq = spi_hz / (24 * 3/2 * (num_channels + 1))
+ * = spi_hz / (36 * (num_channels + 1))
+ */
+#define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
+#define AD4691_BITS_PER_XFER 24
+#define AD4691_CNV_DUTY_CYCLE_NS 380
+#define AD4691_MAX_CONV_PERIOD_US 800
+
+#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
+#define AD4691_STATE_RESET_ALL 0x01
+
+#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
+
+#define AD4691_DEVICE_MANUAL 0x14
+#define AD4691_DEVICE_REGISTER 0x10
+#define AD4691_AUTONOMOUS_MODE_VAL 0x02
+
+#define AD4691_NOOP 0x00
+#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
+
+#define AD4691_STATUS_REG 0x014
+#define AD4691_CLAMP_STATUS1_REG 0x01A
+#define AD4691_CLAMP_STATUS2_REG 0x01B
+#define AD4691_DEVICE_SETUP 0x020
+#define AD4691_REF_CTRL 0x021
+#define AD4691_OSC_FREQ_REG 0x023
+#define AD4691_STD_SEQ_CONFIG 0x025
+#define AD4691_SPARE_CONTROL 0x02A
+
+#define AD4691_OSC_EN_REG 0x180
+#define AD4691_STATE_RESET_REG 0x181
+#define AD4691_ADC_SETUP 0x182
+#define AD4691_ACC_MASK1_REG 0x184
+#define AD4691_ACC_MASK2_REG 0x185
+#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
+#define AD4691_ACC_COUNT_VAL 0x3F
+#define AD4691_GPIO_MODE1_REG 0x196
+#define AD4691_GPIO_MODE2_REG 0x197
+#define AD4691_GPIO_READ 0x1A0
+#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
+#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
+#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
+#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
+#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
+#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
+#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
+#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
+#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
+#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
+#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
+
+enum ad4691_adc_mode {
+ AD4691_CNV_CLOCK_MODE,
+ AD4691_MANUAL_MODE,
+};
+
+enum ad4691_gpio_mode {
+ AD4691_ADC_BUSY = 4,
+ AD4691_DATA_READY = 6,
+};
+
+enum ad4691_ref_ctrl {
+ AD4691_VREF_2P5 = 0,
+ AD4691_VREF_3P0 = 1,
+ AD4691_VREF_3P3 = 2,
+ AD4691_VREF_4P096 = 3,
+ AD4691_VREF_5P0 = 4,
+};
+
+struct ad4691_chip_info {
+ const struct iio_chan_spec *channels;
+ const struct iio_chan_spec *manual_channels;
+ const char *name;
+ unsigned int num_channels;
+ unsigned int max_rate;
+};
+
+#define AD4691_CHANNEL(chan, index, real_bits, storage_bits, _shift) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) \
+ | BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = chan, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = real_bits, \
+ .storagebits = storage_bits, \
+ .shift = _shift, \
+ }, \
+ }
+
+static const struct iio_chan_spec ad4691_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 32, 0),
+ AD4691_CHANNEL(1, 1, 16, 32, 0),
+ AD4691_CHANNEL(2, 2, 16, 32, 0),
+ AD4691_CHANNEL(3, 3, 16, 32, 0),
+ AD4691_CHANNEL(4, 4, 16, 32, 0),
+ AD4691_CHANNEL(5, 5, 16, 32, 0),
+ AD4691_CHANNEL(6, 6, 16, 32, 0),
+ AD4691_CHANNEL(7, 7, 16, 32, 0),
+ AD4691_CHANNEL(8, 8, 16, 32, 0),
+ AD4691_CHANNEL(9, 9, 16, 32, 0),
+ AD4691_CHANNEL(10, 10, 16, 32, 0),
+ AD4691_CHANNEL(11, 11, 16, 32, 0),
+ AD4691_CHANNEL(12, 12, 16, 32, 0),
+ AD4691_CHANNEL(13, 13, 16, 32, 0),
+ AD4691_CHANNEL(14, 14, 16, 32, 0),
+ AD4691_CHANNEL(15, 15, 16, 32, 0)
+};
+
+static const struct iio_chan_spec ad4693_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 32, 0),
+ AD4691_CHANNEL(1, 1, 16, 32, 0),
+ AD4691_CHANNEL(2, 2, 16, 32, 0),
+ AD4691_CHANNEL(3, 3, 16, 32, 0),
+ AD4691_CHANNEL(4, 4, 16, 32, 0),
+ AD4691_CHANNEL(5, 5, 16, 32, 0),
+ AD4691_CHANNEL(6, 6, 16, 32, 0),
+ AD4691_CHANNEL(7, 7, 16, 32, 0)
+};
+
+static const struct iio_chan_spec ad4691_manual_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 24, 8),
+ AD4691_CHANNEL(1, 1, 16, 24, 8),
+ AD4691_CHANNEL(2, 2, 16, 24, 8),
+ AD4691_CHANNEL(3, 3, 16, 24, 8),
+ AD4691_CHANNEL(4, 4, 16, 24, 8),
+ AD4691_CHANNEL(5, 5, 16, 24, 8),
+ AD4691_CHANNEL(6, 6, 16, 24, 8),
+ AD4691_CHANNEL(7, 7, 16, 24, 8),
+ AD4691_CHANNEL(8, 8, 16, 24, 8),
+ AD4691_CHANNEL(9, 9, 16, 24, 8),
+ AD4691_CHANNEL(10, 10, 16, 24, 8),
+ AD4691_CHANNEL(11, 11, 16, 24, 8),
+ AD4691_CHANNEL(12, 12, 16, 24, 8),
+ AD4691_CHANNEL(13, 13, 16, 24, 8),
+ AD4691_CHANNEL(14, 14, 16, 24, 8),
+ AD4691_CHANNEL(15, 15, 16, 24, 8)
+};
+
+static const struct iio_chan_spec ad4693_manual_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 24, 8),
+ AD4691_CHANNEL(1, 1, 16, 24, 8),
+ AD4691_CHANNEL(2, 2, 16, 24, 8),
+ AD4691_CHANNEL(3, 3, 16, 24, 8),
+ AD4691_CHANNEL(4, 4, 16, 24, 8),
+ AD4691_CHANNEL(5, 5, 16, 24, 8),
+ AD4691_CHANNEL(6, 6, 16, 24, 8),
+ AD4691_CHANNEL(7, 7, 16, 24, 8)
+};
+
+static const struct ad4691_chip_info ad4691_ad4691 = {
+ .channels = ad4691_channels,
+ .manual_channels = ad4691_manual_channels,
+ .name = "ad4691",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4691_ad4692 = {
+ .channels = ad4691_channels,
+ .manual_channels = ad4691_manual_channels,
+ .name = "ad4692",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 1 * HZ_PER_MHZ,
+};
+
+static const struct ad4691_chip_info ad4691_ad4693 = {
+ .channels = ad4693_channels,
+ .manual_channels = ad4693_manual_channels,
+ .name = "ad4693",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4691_ad4694 = {
+ .channels = ad4693_channels,
+ .manual_channels = ad4693_manual_channels,
+ .name = "ad4694",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 1 * HZ_PER_MHZ,
+};
+
+struct ad4691_state {
+ const struct ad4691_chip_info *chip;
+ struct regmap *regmap;
+
+ unsigned long ref_clk_rate;
+ struct pwm_device *conv_trigger;
+
+ enum ad4691_adc_mode adc_mode;
+
+ int vref_uV;
+ u64 cnv_period;
+ ktime_t sampling_period;
+ /*
+ * Synchronize access to members of the driver state, and ensure
+ * atomicity of consecutive SPI operations.
+ */
+ struct mutex lock;
+};
+
+static void ad4691_disable_pwm(void *data)
+{
+ struct pwm_device *pwm = data;
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+ state.enabled = false;
+ pwm_apply_might_sleep(pwm, &state);
+}
+
+static int ad4691_regulator_get(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ int ret;
+
+ ret = devm_regulator_get_enable(dev, "vio");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
+
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vref");
+ if (st->vref_uV == -ENODEV)
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vrefin");
+ if (st->vref_uV < 0)
+ return dev_err_probe(dev, st->vref_uV,
+ "Failed to get reference supply\n");
+
+ if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
+ return dev_err_probe(dev, -EINVAL, "vref(%d) must be under [%u %u]\n",
+ st->vref_uV, AD4691_VREF_uV_MIN, AD4691_VREF_uV_MAX);
+
+ return 0;
+}
+
+static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct ad4691_state *st = context;
+ struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
+ u8 tx[2], rx[4];
+ int ret;
+
+ put_unaligned_be16(0x8000 | reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+ ret = spi_write_then_read(spi, tx, 2, rx, 1);
+ if (ret)
+ return ret;
+ *val = rx[0];
+ return 0;
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ ret = spi_write_then_read(spi, tx, 2, rx, 2);
+ if (ret)
+ return ret;
+ *val = get_unaligned_be16(rx);
+ return 0;
+ case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+ case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+ ret = spi_write_then_read(spi, tx, 2, rx, 3);
+ if (ret)
+ return ret;
+ *val = get_unaligned_be24(rx);
+ return 0;
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ ret = spi_write_then_read(spi, tx, 2, rx, 4);
+ if (ret)
+ return ret;
+ *val = get_unaligned_be32(rx);
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct ad4691_state *st = context;
+ struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
+ u8 tx[4];
+
+ put_unaligned_be16(reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
+ if (val > 0xFF)
+ return -EINVAL;
+ tx[2] = val;
+ return spi_write_then_read(spi, tx, 3, NULL, 0);
+ case AD4691_STD_SEQ_CONFIG:
+ if (val > 0xFFFF)
+ return -EINVAL;
+ put_unaligned_be16(val, &tx[2]);
+ return spi_write_then_read(spi, tx, 4, NULL, 0);
+ default:
+ return -EINVAL;
+ }
+}
+
+static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case AD4691_STATUS_REG:
+ case AD4691_CLAMP_STATUS1_REG:
+ case AD4691_CLAMP_STATUS2_REG:
+ case AD4691_GPIO_READ:
+ case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
+ case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+ case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
+ case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config ad4691_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 32,
+ .reg_read = ad4691_reg_read,
+ .reg_write = ad4691_reg_write,
+ .volatile_reg = ad4691_volatile_reg,
+ .readable_reg = ad4691_readable_reg,
+ .writeable_reg = ad4691_writeable_reg,
+ .max_register = AD4691_ACC_STS_DATA(15),
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ad4691_get_sampling_freq(struct ad4691_state *st)
+{
+ if (st->adc_mode == AD4691_MANUAL_MODE)
+ return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
+ ktime_to_ns(st->sampling_period));
+
+ return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
+ pwm_get_period(st->conv_trigger));
+}
+
+static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
+{
+ unsigned long long target, ref_clk_period_ns;
+ struct pwm_state cnv_state;
+
+ pwm_init_state(st->conv_trigger, &cnv_state);
+
+ freq = clamp(freq, 1, st->chip->max_rate);
+ target = DIV_ROUND_CLOSEST_ULL(st->ref_clk_rate, freq);
+ ref_clk_period_ns = DIV_ROUND_CLOSEST_ULL(NANO, st->ref_clk_rate);
+ st->cnv_period = ref_clk_period_ns * target;
+ cnv_state.period = ref_clk_period_ns * target;
+ cnv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
+ cnv_state.enabled = false;
+
+ return pwm_apply_might_sleep(st->conv_trigger, &cnv_state);
+}
+
+static int ad4691_pwm_get(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct clk *ref_clk;
+ int ret;
+
+ ref_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(ref_clk))
+ return dev_err_probe(dev, PTR_ERR(ref_clk),
+ "Failed to get ref clock\n");
+
+ st->ref_clk_rate = clk_get_rate(ref_clk);
+
+ st->conv_trigger = devm_pwm_get(dev, "cnv");
+ if (IS_ERR(st->conv_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
+ "Failed to get cnv pwm\n");
+
+ ret = devm_add_action_or_reset(dev, ad4691_disable_pwm,
+ st->conv_trigger);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "Failed to register PWM disable action\n");
+
+ return __ad4691_set_sampling_freq(st, st->chip->max_rate);
+}
+
+static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&st->lock);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ if (!freq || freq > st->chip->max_rate)
+ return -ERANGE;
+
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC,
+ freq));
+ return 0;
+ }
+
+ if (!st->conv_trigger)
+ return -ENODEV;
+
+ if (!freq || freq > st->chip->max_rate)
+ return -ERANGE;
+
+ return __ad4691_set_sampling_freq(st, freq);
+}
+
+static int ad4691_single_shot_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ u16 mask = ~BIT(chan->channel);
+ u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
+ unsigned int reg_val;
+ int ret;
+
+ /*
+ * Always use AUTONOMOUS mode for single-shot reads, regardless
+ * of the buffer mode (CNV_CLOCK or MANUAL). The chip is kept
+ * in AUTONOMOUS mode during idle; enter_conversion_mode() and
+ * exit_conversion_mode() handle the switch for buffer operation.
+ */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ BIT(chan->channel));
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
+ if (ret)
+ return ret;
+
+ /*
+ * Wait for conversion to complete using a timed delay.
+ * A single read needs 2 internal oscillator periods.
+ * OSC_FREQ_REG is never modified by the driver, so the
+ * oscillator runs at reset-default speed. Use chip->max_rate
+ * as a conservative proxy: it is always <= the OSC frequency,
+ * so the computed delay is >= the actual conversion time.
+ */
+ unsigned long conv_us = DIV_ROUND_UP(2 * USEC_PER_SEC,
+ st->chip->max_rate);
+ fsleep(conv_us);
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
+ if (ret)
+ return ret;
+
+ *val = reg_val;
+ regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+
+ return IIO_VAL_INT;
+}
+
+static int ad4691_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long info)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ return ad4691_single_shot_read(indio_dev, chan, val);
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = ad4691_get_sampling_freq(st);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->vref_uV / 1000;
+ *val2 = chan->scan_type.realbits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad4691_set_sampling_freq(indio_dev, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_info ad4691_info = {
+ .read_raw = &ad4691_read_raw,
+ .write_raw = &ad4691_write_raw,
+ .debugfs_reg_access = &ad4691_reg_access,
+};
+
+static int ad4691_reset(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct reset_control *rst;
+
+ rst = devm_reset_control_get_optional_exclusive(dev, NULL);
+ if (IS_ERR(rst))
+ return dev_err_probe(dev, PTR_ERR(rst),
+ "Failed to get reset\n");
+
+ if (!rst)
+ return 0;
+
+ reset_control_assert(rst);
+ /* Reset delay required. See datasheet Table 5. */
+ fsleep(300);
+ reset_control_deassert(rst);
+
+ return 0;
+}
+
+static int ad4691_config(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ enum ad4691_ref_ctrl ref_val;
+ unsigned int reg_val;
+ int ret;
+
+ /*
+ * Determine buffer conversion mode from DT: if a PWM is provided it
+ * drives the CNV pin (CNV_CLOCK_MODE); otherwise CNV is tied to CS
+ * and each SPI transfer triggers a conversion (MANUAL_MODE).
+ * Both modes idle in AUTONOMOUS mode so that read_raw can use the
+ * internal oscillator without disturbing the hardware configuration.
+ */
+ if (device_property_present(dev, "pwms")) {
+ st->adc_mode = AD4691_CNV_CLOCK_MODE;
+ ret = ad4691_pwm_get(st);
+ if (ret)
+ return ret;
+ } else {
+ st->adc_mode = AD4691_MANUAL_MODE;
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
+ AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
+ to_spi_device(dev)->max_speed_hz)));
+ }
+
+ /* Perform a state reset on the channels at start-up. */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write state reset\n");
+
+ /* Clear STATUS register by reading from the STATUS register. */
+ ret = regmap_read(st->regmap, AD4691_STATUS_REG, ®_val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read status register\n");
+
+ switch (st->vref_uV) {
+ case AD4691_VREF_uV_MIN ... 2750000:
+ ref_val = AD4691_VREF_2P5;
+ break;
+ case 2750001 ... 3250000:
+ ref_val = AD4691_VREF_3P0;
+ break;
+ case 3250001 ... 3750000:
+ ref_val = AD4691_VREF_3P3;
+ break;
+ case 3750001 ... 4500000:
+ ref_val = AD4691_VREF_4P096;
+ break;
+ case 4500001 ... AD4691_VREF_uV_MAX:
+ ref_val = AD4691_VREF_5P0;
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Unsupported vref voltage: %d uV\n",
+ st->vref_uV);
+ }
+
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL,
+ FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val));
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
+
+ /* Both CNV_CLOCK and MANUAL devices start in AUTONOMOUS mode. */
+ ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
+
+ return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
+}
+
+static int ad4691_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad4691_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ st->regmap = devm_regmap_init(dev, NULL, st, &ad4691_regmap_config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "Failed to initialize regmap\n");
+
+ st->chip = spi_get_device_match_data(spi);
+
+ ret = ad4691_regulator_get(st);
+ if (ret)
+ return ret;
+
+ ret = ad4691_reset(st);
+ if (ret)
+ return ret;
+
+ ret = ad4691_config(st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->chip->name;
+ indio_dev->info = &ad4691_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ indio_dev->channels = (st->adc_mode == AD4691_MANUAL_MODE) ?
+ st->chip->manual_channels : st->chip->channels;
+ indio_dev->num_channels = st->chip->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4691_of_match[] = {
+ { .compatible = "adi,ad4691", .data = &ad4691_ad4691 },
+ { .compatible = "adi,ad4692", .data = &ad4691_ad4692 },
+ { .compatible = "adi,ad4693", .data = &ad4691_ad4693 },
+ { .compatible = "adi,ad4694", .data = &ad4691_ad4694 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad4691_of_match);
+
+static const struct spi_device_id ad4691_id[] = {
+ { "ad4691", (kernel_ulong_t)&ad4691_ad4691 },
+ { "ad4692", (kernel_ulong_t)&ad4691_ad4692 },
+ { "ad4693", (kernel_ulong_t)&ad4691_ad4693 },
+ { "ad4694", (kernel_ulong_t)&ad4691_ad4694 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad4691_id);
+
+static struct spi_driver ad4691_driver = {
+ .driver = {
+ .name = "ad4691",
+ .of_match_table = ad4691_of_match,
+ },
+ .probe = ad4691_probe,
+ .id_table = ad4691_id,
+};
+module_spi_driver(ad4691_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-13 10:07 ` [PATCH v3 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
@ 2026-03-13 10:58 ` Andy Shevchenko
2026-03-16 15:29 ` Sabau, Radu bogdan
2026-03-14 11:04 ` Nuno Sá
` (2 subsequent siblings)
3 siblings, 1 reply; 37+ messages in thread
From: Andy Shevchenko @ 2026-03-13 10:58 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio, devicetree, linux-kernel, linux-pwm,
linux-gpio
On Fri, Mar 13, 2026 at 12:07:26PM +0200, Radu Sabau via B4 Relay wrote:
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> Two buffered operating modes are supported, auto-detected from the
> device tree:
>
> - CNV Clock Mode: an external PWM drives the CNV pin; the sampling
> rate is controlled via the PWM period. Requires a
> reference clock and a DATA_READY interrupt.
>
> - Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
> a conversion and returns the previous result
> (pipelined). No external clock or interrupt needed.
>
> In both modes the chip idles in Autonomous Mode so that single-shot
> read_raw can use the internal oscillator without disturbing the
> hardware configuration.
...
> +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
Unused
...
> +#define AD4691_STATE_RESET_ALL 0x01
> +
> +#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
It's a bit better for navigating thru code if the register bit field
definitions are located after the respective register offset.
...
> +#define AD4691_DEVICE_MANUAL 0x14
> +#define AD4691_DEVICE_REGISTER 0x10
These two are unused.
...
> +#define AD4691_AUTONOMOUS_MODE_VAL 0x02
As per above (move closer to the respective offset definition).
...
> +#define AD4691_NOOP 0x00
> +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
Unused.
...
> +#define AD4691_STATUS_REG 0x014
> +#define AD4691_CLAMP_STATUS1_REG 0x01A
> +#define AD4691_CLAMP_STATUS2_REG 0x01B
> +#define AD4691_DEVICE_SETUP 0x020
> +#define AD4691_REF_CTRL 0x021
> +#define AD4691_OSC_FREQ_REG 0x023
> +#define AD4691_STD_SEQ_CONFIG 0x025
> +#define AD4691_SPARE_CONTROL 0x02A
> +
> +#define AD4691_OSC_EN_REG 0x180
> +#define AD4691_STATE_RESET_REG 0x181
> +#define AD4691_ADC_SETUP 0x182
> +#define AD4691_ACC_MASK1_REG 0x184
> +#define AD4691_ACC_MASK2_REG 0x185
> +#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
> +#define AD4691_ACC_COUNT_VAL 0x3F
Unused
> +#define AD4691_GPIO_MODE1_REG 0x196
> +#define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GPIO_READ 0x1A0
> +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
> +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
> +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
> +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
> +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
> +#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
> +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
> +#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
...
> +#define AD4691_CHANNEL(chan, index, real_bits, storage_bits, _shift) \
> + { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> + | BIT(IIO_CHAN_INFO_SCALE), \
Can be better formatting:
.info_mask_shared_by_all = \
BIT(IIO_CHAN_INFO_SAMP_FREQ) | \
BIT(IIO_CHAN_INFO_SCALE), \
> + .channel = chan, \
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 'u', \
> + .realbits = real_bits, \
> + .storagebits = storage_bits, \
> + .shift = _shift, \
> + }, \
> + }
...
> + u64 cnv_period;
Units?
...
> +static int ad4691_regulator_get(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + int ret;
> +
> + ret = devm_regulator_get_enable(dev, "vio");
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
> +
> + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vref");
> + if (st->vref_uV == -ENODEV)
> + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vrefin");
> + if (st->vref_uV < 0)
> + return dev_err_probe(dev, st->vref_uV,
> + "Failed to get reference supply\n");
> +
> + if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
> + return dev_err_probe(dev, -EINVAL, "vref(%d) must be under [%u %u]\n",
"...must be in the range [%u,%u]\n"
(or other possible delimiter instead of space: double or triple dots, dash).
> + st->vref_uV, AD4691_VREF_uV_MIN, AD4691_VREF_uV_MAX);
> +
> + return 0;
> +}
...
> +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> + struct ad4691_state *st = context;
> + struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
Make the context to be spi, st is not used here...
> + u8 tx[2], rx[4];
> + int ret;
> +
> + put_unaligned_be16(0x8000 | reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 1);
> + if (ret)
> + return ret;
> + *val = rx[0];
> + return 0;
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 2);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be16(rx);
> + return 0;
> + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
> + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 3);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be24(rx);
> + return 0;
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 4);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be32(rx);
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + struct ad4691_state *st = context;
> + struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
...neither here. Sorry if my previous comment was misleading in these cases.
> + u8 tx[4];
> +
> + put_unaligned_be16(reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
> + if (val > 0xFF)
> + return -EINVAL;
> + tx[2] = val;
> + return spi_write_then_read(spi, tx, 3, NULL, 0);
> + case AD4691_STD_SEQ_CONFIG:
> + if (val > 0xFFFF)
> + return -EINVAL;
> + put_unaligned_be16(val, &tx[2]);
> + return spi_write_then_read(spi, tx, 4, NULL, 0);
> + default:
> + return -EINVAL;
> + }
> +}
...
> +{
> + if (st->adc_mode == AD4691_MANUAL_MODE)
> + return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> + ktime_to_ns(st->sampling_period));
> + return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> + pwm_get_period(st->conv_trigger));
One line. It's fine to have ~83 character long lines if it increases
readability.
> +}
...
> +static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
> +{
> + unsigned long long target, ref_clk_period_ns;
> + struct pwm_state cnv_state;
> +
> + pwm_init_state(st->conv_trigger, &cnv_state);
> +
> + freq = clamp(freq, 1, st->chip->max_rate);
> + target = DIV_ROUND_CLOSEST_ULL(st->ref_clk_rate, freq);
> + ref_clk_period_ns = DIV_ROUND_CLOSEST_ULL(NANO, st->ref_clk_rate);
NANO --> NSEC_PER_SEC
Why _ULL?
> + st->cnv_period = ref_clk_period_ns * target;
> + cnv_state.period = ref_clk_period_ns * target;
> + cnv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
> + cnv_state.enabled = false;
> +
> + return pwm_apply_might_sleep(st->conv_trigger, &cnv_state);
> +}
...
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +
Redundant blank line.
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + guard(mutex)(&st->lock);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + if (!freq || freq > st->chip->max_rate)
> + return -ERANGE;
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> + freq));
Better indentation either to put all on one line or
st->sampling_period =
ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq));
> + return 0;
> + }
> +
> + if (!st->conv_trigger)
> + return -ENODEV;
> + if (!freq || freq > st->chip->max_rate)
in_range() ?
> + return -ERANGE;
> +
> + return __ad4691_set_sampling_freq(st, freq);
> +}
...
> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + u16 mask = ~BIT(chan->channel);
> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
This looks quite wrong. Is it for sure like two 32-bit stances per each mask
byte? If not, this should be __le16 acc_mask = cpu_to_le16(~BIT(...));
> + unsigned int reg_val;
> + int ret;
> +
> + /*
> + * Always use AUTONOMOUS mode for single-shot reads, regardless
> + * of the buffer mode (CNV_CLOCK or MANUAL). The chip is kept
> + * in AUTONOMOUS mode during idle; enter_conversion_mode() and
> + * exit_conversion_mode() handle the switch for buffer operation.
> + */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + BIT(chan->channel));
> + if (ret)
> + return ret;
> +
> + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
I believe this is not doing what you were expected to do in accordance with u32
above. Using sizeof() is a good pattern to show mistakes.
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * Wait for conversion to complete using a timed delay.
> + * A single read needs 2 internal oscillator periods.
> + * OSC_FREQ_REG is never modified by the driver, so the
> + * oscillator runs at reset-default speed. Use chip->max_rate
> + * as a conservative proxy: it is always <= the OSC frequency,
> + * so the computed delay is >= the actual conversion time.
> + */
> + unsigned long conv_us = DIV_ROUND_UP(2 * USEC_PER_SEC,
> + st->chip->max_rate);
No, we do not mix definitions and code (only a couple of exceptions, none of
which is applicable here).
> + fsleep(conv_us);
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
> + if (ret)
> + return ret;
> +
> + *val = reg_val;
> + regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
> + return IIO_VAL_INT;
> +}
...
> +static int ad4691_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long info)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + switch (info) {
> + case IIO_CHAN_INFO_RAW: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + return ad4691_single_shot_read(indio_dev, chan, val);
> + }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *val = ad4691_get_sampling_freq(st);
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + *val = st->vref_uV / 1000;
"1000" --> "(MICRO / MILLI)"
> + *val2 = chan->scan_type.realbits;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> + }
> +}
...
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct reset_control *rst;
> +
> + rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(rst))
> + return dev_err_probe(dev, PTR_ERR(rst),
> + "Failed to get reset\n");
It's one line (of 81 characters, which is fine in this case).
> + if (!rst)
> + return 0;
Is this required? I mean if reset APIs are NULL-aware, this will be just 300 µs
sleep.
> + reset_control_assert(rst);
> + /* Reset delay required. See datasheet Table 5. */
> + fsleep(300);
> + reset_control_deassert(rst);
> +
> + return 0;
> +}
...
> +static int ad4691_config(struct ad4691_state *st)
I missed the fact that 'spi' is used in one occasion here, perhaps just supply
the max_speed_hz instead of to_spi_device() ?
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + enum ad4691_ref_ctrl ref_val;
> + unsigned int reg_val;
> + int ret;
> +
> + /*
> + * Determine buffer conversion mode from DT: if a PWM is provided it
> + * drives the CNV pin (CNV_CLOCK_MODE); otherwise CNV is tied to CS
> + * and each SPI transfer triggers a conversion (MANUAL_MODE).
> + * Both modes idle in AUTONOMOUS mode so that read_raw can use the
> + * internal oscillator without disturbing the hardware configuration.
> + */
> + if (device_property_present(dev, "pwms")) {
> + st->adc_mode = AD4691_CNV_CLOCK_MODE;
> + ret = ad4691_pwm_get(st);
> + if (ret)
> + return ret;
> + } else {
> + st->adc_mode = AD4691_MANUAL_MODE;
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
Why _ULL? The dividend is 32-bit.
> + AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> + to_spi_device(dev)->max_speed_hz)));
> + }
> +
> + /* Perform a state reset on the channels at start-up. */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write state reset\n");
> +
> + /* Clear STATUS register by reading from the STATUS register. */
> + ret = regmap_read(st->regmap, AD4691_STATUS_REG, ®_val);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to read status register\n");
> +
> + switch (st->vref_uV) {
> + case AD4691_VREF_uV_MIN ... 2750000:
> + ref_val = AD4691_VREF_2P5;
> + break;
> + case 2750001 ... 3250000:
> + ref_val = AD4691_VREF_3P0;
> + break;
> + case 3250001 ... 3750000:
> + ref_val = AD4691_VREF_3P3;
> + break;
> + case 3750001 ... 4500000:
> + ref_val = AD4691_VREF_4P096;
> + break;
> + case 4500001 ... AD4691_VREF_uV_MAX:
> + ref_val = AD4691_VREF_5P0;
> + break;
> + default:
> + return dev_err_probe(dev, -EINVAL,
> + "Unsupported vref voltage: %d uV\n",
> + st->vref_uV);
> + }
> +
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val));
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
> +
> + /* Both CNV_CLOCK and MANUAL devices start in AUTONOMOUS mode. */
> + ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
> +
> + return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
> +}
...
> +static int ad4691_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ad4691_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> + st = iio_priv(indio_dev);
> + ret = devm_mutex_init(dev, &st->lock);
It's better to have like this
...
st = iio_priv(indio_dev);
st->chip = spi_get_device_match_data(spi);
ret = devm_mutex_init(dev, &st->lock);
...
> + if (ret)
> + return ret;
> +
> + st->regmap = devm_regmap_init(dev, NULL, st, &ad4691_regmap_config);
> + if (IS_ERR(st->regmap))
> + return dev_err_probe(dev, PTR_ERR(st->regmap),
> + "Failed to initialize regmap\n");
> +
> + st->chip = spi_get_device_match_data(spi);
> +
> + ret = ad4691_regulator_get(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_reset(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_config(st);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = st->chip->name;
> + indio_dev->info = &ad4691_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + indio_dev->channels = (st->adc_mode == AD4691_MANUAL_MODE) ?
> + st->chip->manual_channels : st->chip->channels;
> + indio_dev->num_channels = st->chip->num_channels;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-13 10:58 ` Andy Shevchenko
@ 2026-03-16 15:29 ` Sabau, Radu bogdan
2026-03-16 15:51 ` David Lechner
0 siblings, 1 reply; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 15:29 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
David Lechner, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org
> -----Original Message-----
> From: Andy Shevchenko <andriy.shevchenko@intel.com>
> Sent: Friday, March 13, 2026 12:58 PM
> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>
>
> > + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
>
> This looks quite wrong. Is it for sure like two 32-bit stances per each mask
> byte? If not, this should be __le16 acc_mask = cpu_to_le16(~BIT(...));
>
Hi Andy,
Each acc_mask has its own register, therefore the u32 acc_mask[2] is
intentional - since the regmap is configured with val_bits=32 - the 4-byte
stride matches what regmap reads. However, I understand how this
can be confusing for anyone reading the code, therefore I propose
two ways for this :
1. Keep regmap_bulk_write and add a comment above acc_mask explaining
why u32 is used, although these register values are 8 bits.
2. Switch to regmap_multi_reg_write, which takes explicit (reg, value) pairs
and sidesteps the ambiguity entirely.
Do you have a preference?
...
> > + if (!rst)
> > + return 0;
>
> Is this required? I mean if reset APIs are NULL-aware, this will be just 300 µs
> sleep.
>
You are right about this. Also, I will implement a software reset in this case
as per David's suggestion.
Thanks,
Radu
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-16 15:29 ` Sabau, Radu bogdan
@ 2026-03-16 15:51 ` David Lechner
2026-03-16 15:57 ` Sabau, Radu bogdan
0 siblings, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-16 15:51 UTC (permalink / raw)
To: Sabau, Radu bogdan, Andy Shevchenko
Cc: Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
Sa, Nuno, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Uwe Kleine-König, Liam Girdwood, Mark Brown,
Linus Walleij, Bartosz Golaszewski, Philipp Zabel,
linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
On 3/16/26 10:29 AM, Sabau, Radu bogdan wrote:
>
>
>> -----Original Message-----
>> From: Andy Shevchenko <andriy.shevchenko@intel.com>
>> Sent: Friday, March 13, 2026 12:58 PM
>> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>
>>
>>> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
>>
>> This looks quite wrong. Is it for sure like two 32-bit stances per each mask
>> byte? If not, this should be __le16 acc_mask = cpu_to_le16(~BIT(...));
>>
>
> Hi Andy,
>
> Each acc_mask has its own register, therefore the u32 acc_mask[2] is
> intentional - since the regmap is configured with val_bits=32 - the 4-byte
> stride matches what regmap reads. However, I understand how this
> can be confusing for anyone reading the code, therefore I propose
> two ways for this :
>
> 1. Keep regmap_bulk_write and add a comment above acc_mask explaining
> why u32 is used, although these register values are 8 bits.
> 2. Switch to regmap_multi_reg_write, which takes explicit (reg, value) pairs
> and sidesteps the ambiguity entirely.
>
> Do you have a preference?
Since we already have a custom read/write functions to handle different
register sizes and the chip can read more than one consecutive register
at once, can we just call this a single register and add a special case
to ad4691_reg_read/write() to handle it? Then we can just do a regular
regmap_read/write() functions to access it as a single 16-bit value.
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-16 15:51 ` David Lechner
@ 2026-03-16 15:57 ` Sabau, Radu bogdan
2026-03-16 16:13 ` Andy Shevchenko
0 siblings, 1 reply; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 15:57 UTC (permalink / raw)
To: David Lechner, Andy Shevchenko
Cc: Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
Sa, Nuno, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Uwe Kleine-König, Liam Girdwood, Mark Brown,
Linus Walleij, Bartosz Golaszewski, Philipp Zabel,
linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, March 16, 2026 5:51 PM
> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>; Andy Shevchenko
> <andriy.shevchenko@intel.com>
> Cc: Lars-Peter Clausen <lars@metafoo.de>; Hennerich, Michael
> <Michael.Hennerich@analog.com>; Jonathan Cameron <jic23@kernel.org>;
> Sa, Nuno <Nuno.Sa@analog.com>; Andy Shevchenko <andy@kernel.org>;
> Rob Herring <robh@kernel.org>; Krzysztof Kozlowski <krzk+dt@kernel.org>;
> Conor Dooley <conor+dt@kernel.org>; Uwe Kleine-König
> <ukleinek@kernel.org>; Liam Girdwood <lgirdwood@gmail.com>; Mark Brown
> <broonie@kernel.org>; Linus Walleij <linusw@kernel.org>; Bartosz
> Golaszewski <brgl@kernel.org>; Philipp Zabel <p.zabel@pengutronix.de>;
> linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-pwm@vger.kernel.org; linux-
> gpio@vger.kernel.org
> Subject: Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691
> family
>
> [External]
>
> On 3/16/26 10:29 AM, Sabau, Radu bogdan wrote:
> >
> >
> >> -----Original Message-----
> >> From: Andy Shevchenko <andriy.shevchenko@intel.com>
> >> Sent: Friday, March 13, 2026 12:58 PM
> >> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>
> >>
> >>> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
> >>
> >> This looks quite wrong. Is it for sure like two 32-bit stances per each mask
> >> byte? If not, this should be __le16 acc_mask = cpu_to_le16(~BIT(...));
> >>
> >
> > Hi Andy,
> >
> > Each acc_mask has its own register, therefore the u32 acc_mask[2] is
> > intentional - since the regmap is configured with val_bits=32 - the 4-byte
> > stride matches what regmap reads. However, I understand how this
> > can be confusing for anyone reading the code, therefore I propose
> > two ways for this :
> >
> > 1. Keep regmap_bulk_write and add a comment above acc_mask explaining
> > why u32 is used, although these register values are 8 bits.
> > 2. Switch to regmap_multi_reg_write, which takes explicit (reg, value) pairs
> > and sidesteps the ambiguity entirely.
> >
> > Do you have a preference?
>
> Since we already have a custom read/write functions to handle different
> register sizes and the chip can read more than one consecutive register
> at once, can we just call this a single register and add a special case
> to ad4691_reg_read/write() to handle it? Then we can just do a regular
> regmap_read/write() functions to access it as a single 16-bit value.
This sounds even better! I will have this in the next version!
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-16 15:57 ` Sabau, Radu bogdan
@ 2026-03-16 16:13 ` Andy Shevchenko
0 siblings, 0 replies; 37+ messages in thread
From: Andy Shevchenko @ 2026-03-16 16:13 UTC (permalink / raw)
To: Sabau, Radu bogdan
Cc: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org
On Mon, Mar 16, 2026 at 03:57:53PM +0000, Sabau, Radu bogdan wrote:
> > From: David Lechner <dlechner@baylibre.com>
> > Sent: Monday, March 16, 2026 5:51 PM
> > On 3/16/26 10:29 AM, Sabau, Radu bogdan wrote:
> > >> From: Andy Shevchenko <andriy.shevchenko@intel.com>
> > >> Sent: Friday, March 13, 2026 12:58 PM
> > >> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>
...
> > >>> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
> > >>
> > >> This looks quite wrong. Is it for sure like two 32-bit stances per each mask
> > >> byte? If not, this should be __le16 acc_mask = cpu_to_le16(~BIT(...));
> > >>
> > >
> > > Hi Andy,
> > >
> > > Each acc_mask has its own register, therefore the u32 acc_mask[2] is
> > > intentional - since the regmap is configured with val_bits=32 - the 4-byte
> > > stride matches what regmap reads. However, I understand how this
> > > can be confusing for anyone reading the code, therefore I propose
> > > two ways for this :
> > >
> > > 1. Keep regmap_bulk_write and add a comment above acc_mask explaining
> > > why u32 is used, although these register values are 8 bits.
> > > 2. Switch to regmap_multi_reg_write, which takes explicit (reg, value) pairs
> > > and sidesteps the ambiguity entirely.
> > >
> > > Do you have a preference?
> >
> > Since we already have a custom read/write functions to handle different
> > register sizes and the chip can read more than one consecutive register
> > at once, can we just call this a single register and add a special case
> > to ad4691_reg_read/write() to handle it? Then we can just do a regular
> > regmap_read/write() functions to access it as a single 16-bit value.
>
> This sounds even better! I will have this in the next version!
I also second David's suggestion, please go for it.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-13 10:07 ` [PATCH v3 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
2026-03-13 10:58 ` Andy Shevchenko
@ 2026-03-14 11:04 ` Nuno Sá
2026-03-16 14:29 ` Sabau, Radu bogdan
2026-03-16 16:00 ` Sabau, Radu bogdan
2026-03-14 16:36 ` David Lechner
2026-03-25 15:01 ` kernel test robot
3 siblings, 2 replies; 37+ messages in thread
From: Nuno Sá @ 2026-03-14 11:04 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Uwe Kleine-König, Liam Girdwood, Mark Brown, Linus Walleij,
Bartosz Golaszewski, Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
Hi Radu,
Some comments from me. Unfortunately, only have time for the first patch :)
On Fri, 2026-03-13 at 12:07 +0200, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> Two buffered operating modes are supported, auto-detected from the
> device tree:
>
> - CNV Clock Mode: an external PWM drives the CNV pin; the sampling
> rate is controlled via the PWM period. Requires a
> reference clock and a DATA_READY interrupt.
>
> - Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
> a conversion and returns the previous result
> (pipelined). No external clock or interrupt needed.
>
> In both modes the chip idles in Autonomous Mode so that single-shot
> read_raw can use the internal oscillator without disturbing the
> hardware configuration.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
...
>
> +
> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + u16 mask = ~BIT(chan->channel);
> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
> + unsigned int reg_val;
> + int ret;
> +
> + /*
> + * Always use AUTONOMOUS mode for single-shot reads, regardless
> + * of the buffer mode (CNV_CLOCK or MANUAL). The chip is kept
> + * in AUTONOMOUS mode during idle; enter_conversion_mode() and
> + * exit_conversion_mode() handle the switch for buffer operation.
> + */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + BIT(chan->channel));
> + if (ret)
> + return ret;
> +
> + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
> + if (ret)
> + return ret;
Not DMA safe... Not sure if things changed in regmap_bulk_write() but before it was
not guaranteed that a safe buffer was going to be used.
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * Wait for conversion to complete using a timed delay.
> + * A single read needs 2 internal oscillator periods.
> + * OSC_FREQ_REG is never modified by the driver, so the
> + * oscillator runs at reset-default speed. Use chip->max_rate
> + * as a conservative proxy: it is always <= the OSC frequency,
> + * so the computed delay is >= the actual conversion time.
> + */
> + unsigned long conv_us = DIV_ROUND_UP(2 * USEC_PER_SEC,
> + st->chip->max_rate);
> + fsleep(conv_us);
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
> + if (ret)
> + return ret;
> +
> + *val = reg_val;
> + regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
No error handling.
> + return IIO_VAL_INT;
> +}
> +
> +static int ad4691_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long info)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + switch (info) {
> + case IIO_CHAN_INFO_RAW: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
Side note. The above turned out to be a cool addition!
> + return ad4691_single_shot_read(indio_dev, chan, val);
> + }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *val = ad4691_get_sampling_freq(st);
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + *val = st->vref_uV / 1000;
> + *val2 = chan->scan_type.realbits;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return ad4691_set_sampling_freq(indio_dev, val);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> + unsigned int writeval, unsigned int *readval)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->lock);
> +
> + if (readval)
> + return regmap_read(st->regmap, reg, readval);
> +
> + return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info ad4691_info = {
> + .read_raw = &ad4691_read_raw,
> + .write_raw = &ad4691_write_raw,
> + .debugfs_reg_access = &ad4691_reg_access,
> +};
> +
> +static int ad4691_reset(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct reset_control *rst;
> +
> + rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(rst))
> + return dev_err_probe(dev, PTR_ERR(rst),
> + "Failed to get reset\n");
> +
> + if (!rst)
> + return 0;
> +
> + reset_control_assert(rst);
You can get the reset in the asserted state already.
> + /* Reset delay required. See datasheet Table 5. */
> + fsleep(300);
> + reset_control_deassert(rst);
> +
> + return 0;
> +}
> +
> +static int ad4691_config(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + enum ad4691_ref_ctrl ref_val;
> + unsigned int reg_val;
> + int ret;
> +
> + /*
> + * Determine buffer conversion mode from DT: if a PWM is provided it
> + * drives the CNV pin (CNV_CLOCK_MODE); otherwise CNV is tied to CS
> + * and each SPI transfer triggers a conversion (MANUAL_MODE).
> + * Both modes idle in AUTONOMOUS mode so that read_raw can use the
> + * internal oscillator without disturbing the hardware configuration.
> + */
> + if (device_property_present(dev, "pwms")) {
> + st->adc_mode = AD4691_CNV_CLOCK_MODE;
> + ret = ad4691_pwm_get(st);
> + if (ret)
> + return ret;
> + } else {
> + st->adc_mode = AD4691_MANUAL_MODE;
> + st->sampling_period =
> ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> + AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> + to_spi_device(dev)->max_speed_hz)));
> + }
> +
> + /* Perform a state reset on the channels at start-up. */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write state reset\n");
> +
> + /* Clear STATUS register by reading from the STATUS register. */
> + ret = regmap_read(st->regmap, AD4691_STATUS_REG, ®_val);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to read status
> register\n");
> +
> + switch (st->vref_uV) {
> + case AD4691_VREF_uV_MIN ... 2750000:
> + ref_val = AD4691_VREF_2P5;
> + break;
> + case 2750001 ... 3250000:
> + ref_val = AD4691_VREF_3P0;
> + break;
> + case 3250001 ... 3750000:
> + ref_val = AD4691_VREF_3P3;
> + break;
> + case 3750001 ... 4500000:
> + ref_val = AD4691_VREF_4P096;
> + break;
> + case 4500001 ... AD4691_VREF_uV_MAX:
> + ref_val = AD4691_VREF_5P0;
> + break;
> + default:
> + return dev_err_probe(dev, -EINVAL,
> + "Unsupported vref voltage: %d uV\n",
> + st->vref_uV);
> + }
> +
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val));
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
> +
> + /* Both CNV_CLOCK and MANUAL devices start in AUTONOMOUS mode. */
> + ret = regmap_write(st->regmap, AD4691_ADC_SETUP,
> AD4691_AUTONOMOUS_MODE_VAL);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
> +
> + return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
> +}
> +
> +static int ad4691_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ad4691_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> +
Agree with Andy here.
> + st->regmap = devm_regmap_init(dev, NULL, st, &ad4691_regmap_config);
> + if (IS_ERR(st->regmap))
> + return dev_err_probe(dev, PTR_ERR(st->regmap),
> + "Failed to initialize regmap\n");
> +
> + st->chip = spi_get_device_match_data(spi);
> +
> + ret = ad4691_regulator_get(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_reset(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_config(st);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = st->chip->name;
> + indio_dev->info = &ad4691_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + indio_dev->channels = (st->adc_mode == AD4691_MANUAL_MODE) ?
> + st->chip->manual_channels : st->chip->channels;
> + indio_dev->num_channels = st->chip->num_channels;
nit: I would prefer explicit if() else
- Nuno Sá
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-14 11:04 ` Nuno Sá
@ 2026-03-16 14:29 ` Sabau, Radu bogdan
2026-03-16 16:00 ` Sabau, Radu bogdan
1 sibling, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 14:29 UTC (permalink / raw)
To: Nuno Sá, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, David Lechner, Sa, Nuno, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Uwe Kleine-König, Liam Girdwood, Mark Brown, Linus Walleij,
Bartosz Golaszewski, Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: Nuno Sá <noname.nuno@gmail.com>
> Sent: Saturday, March 14, 2026 1:05 PM
> > + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG,
> acc_mask, 2);
> > + if (ret)
> > + return ret;
>
> Not DMA safe... Not sure if things changed in regmap_bulk_write() but before
> it was
> not guaranteed that a safe buffer was going to be used.
>
Hi Nuno!
What you are referring to here was my first thought too when implementing
bulk_write. However since we use custom .reg_write callbacksm, map->write
is NULL., therefore making regmap_bulk_write always fall into the individual
_regmap_write loop and calling our ad4691_reg_write -> spi_write_then_read
directly. So no DMA buffer allocation occurs on this path.
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-14 11:04 ` Nuno Sá
2026-03-16 14:29 ` Sabau, Radu bogdan
@ 2026-03-16 16:00 ` Sabau, Radu bogdan
1 sibling, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 16:00 UTC (permalink / raw)
To: Nuno Sá, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, David Lechner, Sa, Nuno, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Uwe Kleine-König, Liam Girdwood, Mark Brown, Linus Walleij,
Bartosz Golaszewski, Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: Nuno Sá <noname.nuno@gmail.com>
> Sent: Saturday, March 14, 2026 1:05 PM
...
> > +
> > + reset_control_assert(rst);
>
> You can get the reset in the asserted state already.
>
I'm not sure I fully follow your comment. Could you clarify what
you had in mind?
As far as I can tell, devm_reset_control_get_optional_exclusive()
doesn't guarantee the reset line is in any particular state upon
return - it depends on the system/bootloader. Keeping the
explicit reset_control_assert() before the delay makes the
sequence correct regardless of the initial hardware state,
and it's a no-op if the reset is already asserted.
If you are thinking of a specific API or pattern that gets the reset
handle in an asserted state, I am not really aware of it - but happy
to be corrected.
Thanks,
Radu
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-13 10:07 ` [PATCH v3 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
2026-03-13 10:58 ` Andy Shevchenko
2026-03-14 11:04 ` Nuno Sá
@ 2026-03-14 16:36 ` David Lechner
2026-03-16 13:01 ` Sabau, Radu bogdan
2026-03-25 15:01 ` kernel test robot
3 siblings, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-14 16:36 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> Two buffered operating modes are supported, auto-detected from the
> device tree:
>
> - CNV Clock Mode: an external PWM drives the CNV pin; the sampling
> rate is controlled via the PWM period. Requires a
> reference clock and a DATA_READY interrupt.
>
> - Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
> a conversion and returns the previous result
> (pipelined). No external clock or interrupt needed.
>
> In both modes the chip idles in Autonomous Mode so that single-shot
> read_raw can use the internal oscillator without disturbing the
> hardware configuration.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ad4691.c | 772 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 785 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9994d107d88d..5325f7d3b7f4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org
> S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> +F: drivers/iio/adc/ad4691.c
> F: include/dt-bindings/iio/adc/adi,ad4691.h
>
> ANALOG DEVICES INC AD4695 DRIVER
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 60038ae8dfc4..3685a03aa8dc 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -139,6 +139,17 @@ config AD4170_4
> To compile this driver as a module, choose M here: the module will be
> called ad4170-4.
>
> +config AD4691
> + tristate "Analog Devices AD4691 Family ADC Driver"
> + depends on SPI
> + select REGMAP
> + help
> + Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> + SPI analog to digital converters (ADC).
> +
> + To compile this driver as a module, choose M here: the module will be
> + called ad4691.
> +
> config AD4695
> tristate "Analog Device AD4695 ADC Driver"
> depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index c76550415ff1..4ac1ea09d773 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
> obj-$(CONFIG_AD4130) += ad4130.o
> obj-$(CONFIG_AD4134) += ad4134.o
> obj-$(CONFIG_AD4170_4) += ad4170-4.o
> +obj-$(CONFIG_AD4691) += ad4691.o
> obj-$(CONFIG_AD4695) += ad4695.o
> obj-$(CONFIG_AD4851) += ad4851.o
> obj-$(CONFIG_AD7091R) += ad7091r-base.o
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> new file mode 100644
> index 000000000000..31eafa12bef8
> --- /dev/null
> +++ b/drivers/iio/adc/ad4691.c
> @@ -0,0 +1,772 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2024-2026 Analog Devices, Inc.
> + * Author: Radu Sabau <radu.sabau@analog.com>
> + */
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/reset.h>
> +#include <linux/interrupt.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/util_macros.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>
> +
> +#include <linux/iio/iio.h>
> +
> +#include <dt-bindings/iio/adc/adi,ad4691.h>
> +
> +#define AD4691_VREF_uV_MIN 2400000
> +#define AD4691_VREF_uV_MAX 5250000
> +
> +/*
> + * Default sampling frequency for MANUAL_MODE.
> + * Each sample needs (num_channels + 1) SPI transfers of 24 bits.
> + * The factor 36 = 24 * 3/2 folds in a 50% scheduling margin:
> + * freq = spi_hz / (24 * 3/2 * (num_channels + 1))
> + * = spi_hz / (36 * (num_channels + 1))
> + */
> +#define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
> +#define AD4691_BITS_PER_XFER 24
> +#define AD4691_CNV_DUTY_CYCLE_NS 380
> +#define AD4691_MAX_CONV_PERIOD_US 800
> +
> +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
> +#define AD4691_STATE_RESET_ALL 0x01
> +
> +#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
> +
> +#define AD4691_DEVICE_MANUAL 0x14
> +#define AD4691_DEVICE_REGISTER 0x10
> +#define AD4691_AUTONOMOUS_MODE_VAL 0x02
> +
> +#define AD4691_NOOP 0x00
> +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
> +
> +#define AD4691_STATUS_REG 0x014
> +#define AD4691_CLAMP_STATUS1_REG 0x01A
> +#define AD4691_CLAMP_STATUS2_REG 0x01B
> +#define AD4691_DEVICE_SETUP 0x020
> +#define AD4691_REF_CTRL 0x021
> +#define AD4691_OSC_FREQ_REG 0x023
> +#define AD4691_STD_SEQ_CONFIG 0x025
> +#define AD4691_SPARE_CONTROL 0x02A
> +
> +#define AD4691_OSC_EN_REG 0x180
> +#define AD4691_STATE_RESET_REG 0x181
> +#define AD4691_ADC_SETUP 0x182
> +#define AD4691_ACC_MASK1_REG 0x184
> +#define AD4691_ACC_MASK2_REG 0x185
> +#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
> +#define AD4691_ACC_COUNT_VAL 0x3F
> +#define AD4691_GPIO_MODE1_REG 0x196
> +#define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GPIO_READ 0x1A0
> +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
> +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
> +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
> +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
> +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
> +#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
> +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
> +#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
> +
> +enum ad4691_adc_mode {
> + AD4691_CNV_CLOCK_MODE,
> + AD4691_MANUAL_MODE,
> +};
> +
> +enum ad4691_gpio_mode {
> + AD4691_ADC_BUSY = 4,
> + AD4691_DATA_READY = 6,
> +};
> +
> +enum ad4691_ref_ctrl {
> + AD4691_VREF_2P5 = 0,
> + AD4691_VREF_3P0 = 1,
> + AD4691_VREF_3P3 = 2,
> + AD4691_VREF_4P096 = 3,
> + AD4691_VREF_5P0 = 4,
> +};
> +
> +struct ad4691_chip_info {
> + const struct iio_chan_spec *channels;
> + const struct iio_chan_spec *manual_channels;
> + const char *name;
> + unsigned int num_channels;
> + unsigned int max_rate;
> +};
> +
> +#define AD4691_CHANNEL(chan, index, real_bits, storage_bits, _shift) \
> + { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> + | BIT(IIO_CHAN_INFO_SCALE), \
> + .channel = chan, \
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 'u', \
> + .realbits = real_bits, \
> + .storagebits = storage_bits, \
> + .shift = _shift, \
> + }, \
> + }
> +
> +static const struct iio_chan_spec ad4691_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 32, 0),
> + AD4691_CHANNEL(1, 1, 16, 32, 0),
> + AD4691_CHANNEL(2, 2, 16, 32, 0),
> + AD4691_CHANNEL(3, 3, 16, 32, 0),
> + AD4691_CHANNEL(4, 4, 16, 32, 0),
> + AD4691_CHANNEL(5, 5, 16, 32, 0),
> + AD4691_CHANNEL(6, 6, 16, 32, 0),
> + AD4691_CHANNEL(7, 7, 16, 32, 0),
> + AD4691_CHANNEL(8, 8, 16, 32, 0),
> + AD4691_CHANNEL(9, 9, 16, 32, 0),
> + AD4691_CHANNEL(10, 10, 16, 32, 0),
> + AD4691_CHANNEL(11, 11, 16, 32, 0),
> + AD4691_CHANNEL(12, 12, 16, 32, 0),
> + AD4691_CHANNEL(13, 13, 16, 32, 0),
> + AD4691_CHANNEL(14, 14, 16, 32, 0),
> + AD4691_CHANNEL(15, 15, 16, 32, 0)
> +};
> +
> +static const struct iio_chan_spec ad4693_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 32, 0),
> + AD4691_CHANNEL(1, 1, 16, 32, 0),
> + AD4691_CHANNEL(2, 2, 16, 32, 0),
> + AD4691_CHANNEL(3, 3, 16, 32, 0),
> + AD4691_CHANNEL(4, 4, 16, 32, 0),
> + AD4691_CHANNEL(5, 5, 16, 32, 0),
> + AD4691_CHANNEL(6, 6, 16, 32, 0),
> + AD4691_CHANNEL(7, 7, 16, 32, 0)
> +};
I would expect the 16-bits in 32-bits to be only for SPI offload and
therefore not introduced until the later patch that adds SPI offload
support. And we should include a comment on why it is needed so that
no one tries to "fix" it later (since normally we would use 16-bit
storage for 16-bit data).
> +
> +static const struct iio_chan_spec ad4691_manual_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 24, 8),
> + AD4691_CHANNEL(1, 1, 16, 24, 8),
> + AD4691_CHANNEL(2, 2, 16, 24, 8),
> + AD4691_CHANNEL(3, 3, 16, 24, 8),
> + AD4691_CHANNEL(4, 4, 16, 24, 8),
> + AD4691_CHANNEL(5, 5, 16, 24, 8),
> + AD4691_CHANNEL(6, 6, 16, 24, 8),
> + AD4691_CHANNEL(7, 7, 16, 24, 8),
> + AD4691_CHANNEL(8, 8, 16, 24, 8),
> + AD4691_CHANNEL(9, 9, 16, 24, 8),
> + AD4691_CHANNEL(10, 10, 16, 24, 8),
> + AD4691_CHANNEL(11, 11, 16, 24, 8),
> + AD4691_CHANNEL(12, 12, 16, 24, 8),
> + AD4691_CHANNEL(13, 13, 16, 24, 8),
> + AD4691_CHANNEL(14, 14, 16, 24, 8),
> + AD4691_CHANNEL(15, 15, 16, 24, 8)
> +};
Can't have 24-bit storage, but I guess that got fixed in a later patch.
> +
> +static const struct iio_chan_spec ad4693_manual_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 24, 8),
> + AD4691_CHANNEL(1, 1, 16, 24, 8),
> + AD4691_CHANNEL(2, 2, 16, 24, 8),
> + AD4691_CHANNEL(3, 3, 16, 24, 8),
> + AD4691_CHANNEL(4, 4, 16, 24, 8),
> + AD4691_CHANNEL(5, 5, 16, 24, 8),
> + AD4691_CHANNEL(6, 6, 16, 24, 8),
> + AD4691_CHANNEL(7, 7, 16, 24, 8)
> +};
channel and index is always the same, so we can combine that into
one argument.
> +
> +static const struct ad4691_chip_info ad4691_ad4691 = {
Usually, we name these like: ad4691_chip_info instead of ad4691_ad4691.
> + .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> + .name = "ad4691",
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> + .max_rate = 500 * HZ_PER_KHZ,
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4692 = {
And here would be ad4692_chip_info.
> + .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> + .name = "ad4692",
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> + .max_rate = 1 * HZ_PER_MHZ,
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4693 = {
> + .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> + .name = "ad4693",
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> + .max_rate = 500 * HZ_PER_KHZ,
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4694 = {
> + .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> + .name = "ad4694",
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> + .max_rate = 1 * HZ_PER_MHZ,
> +};
> +
> +struct ad4691_state {
> + const struct ad4691_chip_info *chip;
Usually, we would call this "info" rather than "chip".
> + struct regmap *regmap;
> +
> + unsigned long ref_clk_rate;
> + struct pwm_device *conv_trigger;
> +
> + enum ad4691_adc_mode adc_mode;
> +
> + int vref_uV;
> + u64 cnv_period;
Always include units in variable names when possible. Is this ns?
> + ktime_t sampling_period;
> + /*
> + * Synchronize access to members of the driver state, and ensure
> + * atomicity of consecutive SPI operations.
> + */
> + struct mutex lock;
> +};
> +
> +static void ad4691_disable_pwm(void *data)
> +{
> + struct pwm_device *pwm = data;
> + struct pwm_state state;
> +
> + pwm_get_state(pwm, &state);
> + state.enabled = false;
> + pwm_apply_might_sleep(pwm, &state);
See Uwe's comment from v2. This is why we always tell people to wait a week
before sending a new revision. It takes a while for all comments to come in.
> +}
> +
> +static int ad4691_regulator_get(struct ad4691_state *st)
Would be more logical to keep this close to probe function since it is
only used during probe.
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + int ret;
> +
As mentioned in the DT review, there are also avdd and ldo-in regulators that can
be handled here.
Later we may need to set DEVICE_SETUP:LDO_EN based on this info.
> + ret = devm_regulator_get_enable(dev, "vio");
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
> +
> + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vref");
> + if (st->vref_uV == -ENODEV)
> + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "vrefin");
Don't we need to keep track of REF vs. REFIN so that we can set REF_CTRL:REFBUF_EN
correctly later?
> + if (st->vref_uV < 0)
> + return dev_err_probe(dev, st->vref_uV,
> + "Failed to get reference supply\n");
> +
> + if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX)
> + return dev_err_probe(dev, -EINVAL, "vref(%d) must be under [%u %u]\n",
> + st->vref_uV, AD4691_VREF_uV_MIN, AD4691_VREF_uV_MAX);
> +
> + return 0;
> +}
> +
> +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> + struct ad4691_state *st = context;
> + struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
> + u8 tx[2], rx[4];
> + int ret;
> +
> + put_unaligned_be16(0x8000 | reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 1);
> + if (ret)
> + return ret;
> + *val = rx[0];
> + return 0;
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 2);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be16(rx);
> + return 0;
> + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
> + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 3);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be24(rx);
> + return 0;
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + ret = spi_write_then_read(spi, tx, 2, rx, 4);
> + if (ret)
> + return ret;
> + *val = get_unaligned_be32(rx);
> + return 0;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + struct ad4691_state *st = context;
> + struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
> + u8 tx[4];
> +
> + put_unaligned_be16(reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
> + if (val > 0xFF)
> + return -EINVAL;
> + tx[2] = val;
> + return spi_write_then_read(spi, tx, 3, NULL, 0);
> + case AD4691_STD_SEQ_CONFIG:
> + if (val > 0xFFFF)
> + return -EINVAL;
> + put_unaligned_be16(val, &tx[2]);
> + return spi_write_then_read(spi, tx, 4, NULL, 0);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static bool ad4691_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case AD4691_STATUS_REG:
> + case AD4691_CLAMP_STATUS1_REG:
> + case AD4691_CLAMP_STATUS2_REG:
> + case AD4691_GPIO_READ:
> + case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG:
> + case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15):
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
> + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool ad4691_readable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15):
> + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15):
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool ad4691_writeable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static const struct regmap_config ad4691_regmap_config = {
> + .reg_bits = 16,
> + .val_bits = 32,
> + .reg_read = ad4691_reg_read,
> + .reg_write = ad4691_reg_write,
> + .volatile_reg = ad4691_volatile_reg,
> + .readable_reg = ad4691_readable_reg,
> + .writeable_reg = ad4691_writeable_reg,
> + .max_register = AD4691_ACC_STS_DATA(15),
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +static int ad4691_get_sampling_freq(struct ad4691_state *st)
> +{
> + if (st->adc_mode == AD4691_MANUAL_MODE)
> + return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> + ktime_to_ns(st->sampling_period));
> +
> + return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> + pwm_get_period(st->conv_trigger));
> +}
> +
> +static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
> +{
> + unsigned long long target, ref_clk_period_ns;
> + struct pwm_state cnv_state;
> +
> + pwm_init_state(st->conv_trigger, &cnv_state);
> +
> + freq = clamp(freq, 1, st->chip->max_rate);
> + target = DIV_ROUND_CLOSEST_ULL(st->ref_clk_rate, freq);
> + ref_clk_period_ns = DIV_ROUND_CLOSEST_ULL(NANO, st->ref_clk_rate);
> + st->cnv_period = ref_clk_period_ns * target;
> + cnv_state.period = ref_clk_period_ns * target;
> + cnv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
> + cnv_state.enabled = false;
> +
> + return pwm_apply_might_sleep(st->conv_trigger, &cnv_state);
> +}
> +
> +static int ad4691_pwm_get(struct ad4691_state *st)
"get" is confusing name for a "setup" function.
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct clk *ref_clk;
> + int ret;
> +
> + ref_clk = devm_clk_get_enabled(dev, NULL);
> + if (IS_ERR(ref_clk))
> + return dev_err_probe(dev, PTR_ERR(ref_clk),
> + "Failed to get ref clock\n");
> +
> + st->ref_clk_rate = clk_get_rate(ref_clk);
This clock stuff was already done in probe.
Also, as mentioned in the DT review, since this clock isn't connected to
the ADC chip, it doesn't really make sense that it should be assigned to
the ADC. We should be able to just set the PWM rate to what we need without
caring what the the input clock to the PWM is.
If something else is needed to make it actually work, we need a detailed
explanation in the cover letter so that we can discuss it more.
> +
> + st->conv_trigger = devm_pwm_get(dev, "cnv");
> + if (IS_ERR(st->conv_trigger))
> + return dev_err_probe(dev, PTR_ERR(st->conv_trigger),
> + "Failed to get cnv pwm\n");
> +
> + ret = devm_add_action_or_reset(dev, ad4691_disable_pwm,
> + st->conv_trigger);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "Failed to register PWM disable action\n");
> +
> + return __ad4691_set_sampling_freq(st, st->chip->max_rate);
> +}
> +
> +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + guard(mutex)(&st->lock);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + if (!freq || freq > st->chip->max_rate)
> + return -ERANGE;
> +
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> + freq));
Why do we convert this to ktime when we just convert it back to ns later?
> + return 0;
> + }
> +
> + if (!st->conv_trigger)
> + return -ENODEV;
> +
> + if (!freq || freq > st->chip->max_rate)
> + return -ERANGE;
> +
> + return __ad4691_set_sampling_freq(st, freq);
> +}
> +
> +static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + u16 mask = ~BIT(chan->channel);
> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
> + unsigned int reg_val;
> + int ret;
No mutex lock here?
> +
> + /*
> + * Always use AUTONOMOUS mode for single-shot reads, regardless
> + * of the buffer mode (CNV_CLOCK or MANUAL). The chip is kept
> + * in AUTONOMOUS mode during idle; enter_conversion_mode() and
> + * exit_conversion_mode() handle the switch for buffer operation.
> + */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + BIT(chan->channel));
> + if (ret)
> + return ret;
> +
> + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * Wait for conversion to complete using a timed delay.
> + * A single read needs 2 internal oscillator periods.
> + * OSC_FREQ_REG is never modified by the driver, so the
> + * oscillator runs at reset-default speed. Use chip->max_rate
> + * as a conservative proxy: it is always <= the OSC frequency,
> + * so the computed delay is >= the actual conversion time.
> + */
> + unsigned long conv_us = DIV_ROUND_UP(2 * USEC_PER_SEC,
> + st->chip->max_rate);
> + fsleep(conv_us);
> +
> + ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
> + if (ret)
> + return ret;
> +
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
> + if (ret)
> + return ret;
> +
> + *val = reg_val;
> + regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
> + return IIO_VAL_INT;
> +}
> +
> +static int ad4691_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long info)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + switch (info) {
> + case IIO_CHAN_INFO_RAW: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> +
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + return ad4691_single_shot_read(indio_dev, chan, val);
> + }
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *val = ad4691_get_sampling_freq(st);
> + return IIO_VAL_INT;
> + case IIO_CHAN_INFO_SCALE:
> + *val = st->vref_uV / 1000;
> + *val2 = chan->scan_type.realbits;
> + return IIO_VAL_FRACTIONAL_LOG2;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_write_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + int val, int val2, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + return ad4691_set_sampling_freq(indio_dev, val);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> + unsigned int writeval, unsigned int *readval)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->lock);
> +
> + if (readval)
> + return regmap_read(st->regmap, reg, readval);
> +
> + return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info ad4691_info = {
> + .read_raw = &ad4691_read_raw,
> + .write_raw = &ad4691_write_raw,
> + .debugfs_reg_access = &ad4691_reg_access,
> +};
> +
> +static int ad4691_reset(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct reset_control *rst;
> +
> + rst = devm_reset_control_get_optional_exclusive(dev, NULL);
> + if (IS_ERR(rst))
> + return dev_err_probe(dev, PTR_ERR(rst),
> + "Failed to get reset\n");
> +
> + if (!rst)
Usually, we do a software reset here (see SPI_CONFIG_A register)
if hardware reset is not possible.
> + return 0;
> +
> + reset_control_assert(rst);
> + /* Reset delay required. See datasheet Table 5. */
> + fsleep(300);
> + reset_control_deassert(rst);
> +
> + return 0;
> +}
> +
> +static int ad4691_config(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + enum ad4691_ref_ctrl ref_val;
> + unsigned int reg_val;
> + int ret;
> +
> + /*
> + * Determine buffer conversion mode from DT: if a PWM is provided it
> + * drives the CNV pin (CNV_CLOCK_MODE); otherwise CNV is tied to CS
> + * and each SPI transfer triggers a conversion (MANUAL_MODE).
> + * Both modes idle in AUTONOMOUS mode so that read_raw can use the
> + * internal oscillator without disturbing the hardware configuration.
> + */
> + if (device_property_present(dev, "pwms")) {
> + st->adc_mode = AD4691_CNV_CLOCK_MODE;
> + ret = ad4691_pwm_get(st);
> + if (ret)
> + return ret;
> + } else {
> + st->adc_mode = AD4691_MANUAL_MODE;
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> + AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> + to_spi_device(dev)->max_speed_hz)));
> + }
> +
> + /* Perform a state reset on the channels at start-up. */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
This seems redundant since we already did a hardware (or software reset).
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write state reset\n");
> +
> + /* Clear STATUS register by reading from the STATUS register. */
> + ret = regmap_read(st->regmap, AD4691_STATUS_REG, ®_val);
Ditto.
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to read status register\n");
> +
> + switch (st->vref_uV) {
> + case AD4691_VREF_uV_MIN ... 2750000:
Strange to mix macro and non-macro for range.
> + ref_val = AD4691_VREF_2P5;
> + break;
> + case 2750001 ... 3250000:
> + ref_val = AD4691_VREF_3P0;
> + break;
> + case 3250001 ... 3750000:
> + ref_val = AD4691_VREF_3P3;
> + break;
> + case 3750001 ... 4500000:
> + ref_val = AD4691_VREF_4P096;
> + break;
> + case 4500001 ... AD4691_VREF_uV_MAX:
> + ref_val = AD4691_VREF_5P0;
> + break;
> + default:
> + return dev_err_probe(dev, -EINVAL,
> + "Unsupported vref voltage: %d uV\n",
> + st->vref_uV);
> + }
> +
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val));
As mentioned elsewhere, also expect to set REFBUF_EN here if needed.
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
> +
> + /* Both CNV_CLOCK and MANUAL devices start in AUTONOMOUS mode. */
> + ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
> +
> + return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
We should not be setting anything about the gpios without checking what the
devicetree says is wired up.
I would defer adding all of the pwm/CNV_CLOCK/samling_freqnecy/GP0 trigger
stuff to the patch that actually adds SPI offload support for it to logically
make sense.
> +}
> +
> +static int ad4691_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ad4691_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> +
> + st->regmap = devm_regmap_init(dev, NULL, st, &ad4691_regmap_config);
> + if (IS_ERR(st->regmap))
> + return dev_err_probe(dev, PTR_ERR(st->regmap),
> + "Failed to initialize regmap\n");
> +
> + st->chip = spi_get_device_match_data(spi);
> +
> + ret = ad4691_regulator_get(st);
I would call this ad4691_regulator_setup() or something similar to make it more
obvious this is an init function.
> + if (ret)
> + return ret;
> +
> + ret = ad4691_reset(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_config(st);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = st->chip->name;
> + indio_dev->info = &ad4691_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + indio_dev->channels = (st->adc_mode == AD4691_MANUAL_MODE) ?
> + st->chip->manual_channels : st->chip->channels;
> + indio_dev->num_channels = st->chip->num_channels;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
> +
> +static const struct of_device_id ad4691_of_match[] = {
> + { .compatible = "adi,ad4691", .data = &ad4691_ad4691 },
> + { .compatible = "adi,ad4692", .data = &ad4691_ad4692 },
> + { .compatible = "adi,ad4693", .data = &ad4691_ad4693 },
> + { .compatible = "adi,ad4694", .data = &ad4691_ad4694 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(of, ad4691_of_match);
> +
> +static const struct spi_device_id ad4691_id[] = {
> + { "ad4691", (kernel_ulong_t)&ad4691_ad4691 },
> + { "ad4692", (kernel_ulong_t)&ad4691_ad4692 },
> + { "ad4693", (kernel_ulong_t)&ad4691_ad4693 },
> + { "ad4694", (kernel_ulong_t)&ad4691_ad4694 },
> + { }
> +};
> +MODULE_DEVICE_TABLE(spi, ad4691_id);
> +
> +static struct spi_driver ad4691_driver = {
> + .driver = {
> + .name = "ad4691",
> + .of_match_table = ad4691_of_match,
> + },
> + .probe = ad4691_probe,
> + .id_table = ad4691_id,
> +};
> +module_spi_driver(ad4691_driver);
> +
> +MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
> +MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
> +MODULE_LICENSE("GPL");
>
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-14 16:36 ` David Lechner
@ 2026-03-16 13:01 ` Sabau, Radu bogdan
0 siblings, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 13:01 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, March 14, 2026 6:37 PM
> On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
> > From: Radu Sabau <radu.sabau@analog.com>
> >
...
> > +
> > +static const struct iio_chan_spec ad4691_manual_channels[] = {
> > + AD4691_CHANNEL(0, 0, 16, 24, 8),
> > + AD4691_CHANNEL(1, 1, 16, 24, 8),
> > + AD4691_CHANNEL(2, 2, 16, 24, 8),
> > + AD4691_CHANNEL(3, 3, 16, 24, 8),
> > + AD4691_CHANNEL(4, 4, 16, 24, 8),
> > + AD4691_CHANNEL(5, 5, 16, 24, 8),
> > + AD4691_CHANNEL(6, 6, 16, 24, 8),
> > + AD4691_CHANNEL(7, 7, 16, 24, 8),
> > + AD4691_CHANNEL(8, 8, 16, 24, 8),
> > + AD4691_CHANNEL(9, 9, 16, 24, 8),
> > + AD4691_CHANNEL(10, 10, 16, 24, 8),
> > + AD4691_CHANNEL(11, 11, 16, 24, 8),
> > + AD4691_CHANNEL(12, 12, 16, 24, 8),
> > + AD4691_CHANNEL(13, 13, 16, 24, 8),
> > + AD4691_CHANNEL(14, 14, 16, 24, 8),
> > + AD4691_CHANNEL(15, 15, 16, 24, 8)
> > +};
>
> Can't have 24-bit storage, but I guess that got fixed in a later patch.
>
Will have this changed here on the next version.
>
> > +
> > +static const struct iio_chan_spec ad4693_manual_channels[] = {
> > + AD4691_CHANNEL(0, 0, 16, 24, 8),
...
> > +
> > + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> > + FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val));
>
> As mentioned elsewhere, also expect to set REFBUF_EN here if needed.
You are also right about the REBUF_EN functionality, here and in probe.
The internal reference should be used if an external one is not present,
with the default reset value (no change to the ref_ctrl value in this case,
only to refbuf_en).
And if an external one is present, the reference voltage will be set
accordingly.
>
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
> > +
> > + /* Both CNV_CLOCK and MANUAL devices start in AUTONOMOUS
> mode. */
> > + ret = regmap_write(st->regmap, AD4691_ADC_SETUP,
> AD4691_AUTONOMOUS_MODE_VAL);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "Failed to write
> ADC_SETUP\n");
> > +
> > + return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
> AD4691_ADC_BUSY);
>
> We should not be setting anything about the gpios without checking what the
> devicetree says is wired up.
>
> I would defer adding all of the pwm/CNV_CLOCK/samling_freqnecy/GP0
> trigger
> stuff to the patch that actually adds SPI offload support for it to logically
> make sense.
>
I understand your point, but these are also used in triggered buffer mode
as well so perhaps they should be moved there.
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-13 10:07 ` [PATCH v3 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
` (2 preceding siblings ...)
2026-03-14 16:36 ` David Lechner
@ 2026-03-25 15:01 ` kernel test robot
3 siblings, 0 replies; 37+ messages in thread
From: kernel test robot @ 2026-03-25 15:01 UTC (permalink / raw)
To: Radu Sabau via B4 Relay, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Uwe Kleine-König, Liam Girdwood, Mark Brown, Linus Walleij,
Bartosz Golaszewski, Philipp Zabel
Cc: oe-kbuild-all, linux-iio, devicetree, linux-kernel, linux-pwm,
linux-gpio, Radu Sabau
Hi Radu,
kernel test robot noticed the following build errors:
[auto build test ERROR on 11439c4635edd669ae435eec308f4ab8a0804808]
url: https://github.com/intel-lab-lkp/linux/commits/Radu-Sabau-via-B4-Relay/dt-bindings-iio-adc-add-bindings-for-AD4691-family/20260314-040740
base: 11439c4635edd669ae435eec308f4ab8a0804808
patch link: https://lore.kernel.org/r/20260313-ad4692-multichannel-sar-adc-driver-v3-2-b4d14d81a181%40analog.com
patch subject: [PATCH v3 2/4] iio: adc: ad4691: add initial driver for AD4691 family
config: nios2-allmodconfig (https://download.01.org/0day-ci/archive/20260325/202603252241.8UAUrLG4-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 11.5.0
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260325/202603252241.8UAUrLG4-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/202603252241.8UAUrLG4-lkp@intel.com/
All errors (new ones prefixed by >>):
In file included from <command-line>:
drivers/iio/adc/ad4691.c: In function '__ad4691_set_sampling_freq':
>> include/linux/compiler_types.h:706:45: error: call to '__compiletime_assert_418' declared with attribute error: clamp(freq, 1, st->chip->max_rate) signedness error
706 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^
include/linux/compiler_types.h:687:25: note: in definition of macro '__compiletime_assert'
687 | prefix ## suffix(); \
| ^~~~~~
include/linux/compiler_types.h:706:9: note: in expansion of macro '_compiletime_assert'
706 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:39:37: note: in expansion of macro 'compiletime_assert'
39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/minmax.h:190:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
190 | BUILD_BUG_ON_MSG(!__types_ok3(uval, ulo, uhi), \
| ^~~~~~~~~~~~~~~~
include/linux/minmax.h:195:9: note: in expansion of macro '__clamp_once'
195 | __clamp_once(type, val, lo, hi, __UNIQUE_ID(v_), __UNIQUE_ID(l_), __UNIQUE_ID(h_))
| ^~~~~~~~~~~~
include/linux/minmax.h:206:28: note: in expansion of macro '__careful_clamp'
206 | #define clamp(val, lo, hi) __careful_clamp(auto, val, lo, hi)
| ^~~~~~~~~~~~~~~
drivers/iio/adc/ad4691.c:419:16: note: in expansion of macro 'clamp'
419 | freq = clamp(freq, 1, st->chip->max_rate);
| ^~~~~
--
In file included from <command-line>:
ad4691.c: In function '__ad4691_set_sampling_freq':
>> include/linux/compiler_types.h:706:45: error: call to '__compiletime_assert_418' declared with attribute error: clamp(freq, 1, st->chip->max_rate) signedness error
706 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^
include/linux/compiler_types.h:687:25: note: in definition of macro '__compiletime_assert'
687 | prefix ## suffix(); \
| ^~~~~~
include/linux/compiler_types.h:706:9: note: in expansion of macro '_compiletime_assert'
706 | _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
| ^~~~~~~~~~~~~~~~~~~
include/linux/build_bug.h:39:37: note: in expansion of macro 'compiletime_assert'
39 | #define BUILD_BUG_ON_MSG(cond, msg) compiletime_assert(!(cond), msg)
| ^~~~~~~~~~~~~~~~~~
include/linux/minmax.h:190:9: note: in expansion of macro 'BUILD_BUG_ON_MSG'
190 | BUILD_BUG_ON_MSG(!__types_ok3(uval, ulo, uhi), \
| ^~~~~~~~~~~~~~~~
include/linux/minmax.h:195:9: note: in expansion of macro '__clamp_once'
195 | __clamp_once(type, val, lo, hi, __UNIQUE_ID(v_), __UNIQUE_ID(l_), __UNIQUE_ID(h_))
| ^~~~~~~~~~~~
include/linux/minmax.h:206:28: note: in expansion of macro '__careful_clamp'
206 | #define clamp(val, lo, hi) __careful_clamp(auto, val, lo, hi)
| ^~~~~~~~~~~~~~~
ad4691.c:419:16: note: in expansion of macro 'clamp'
419 | freq = clamp(freq, 1, st->chip->max_rate);
| ^~~~~
vim +/__compiletime_assert_418 +706 include/linux/compiler_types.h
eb5c2d4b45e3d2d Will Deacon 2020-07-21 692
eb5c2d4b45e3d2d Will Deacon 2020-07-21 693 #define _compiletime_assert(condition, msg, prefix, suffix) \
eb5c2d4b45e3d2d Will Deacon 2020-07-21 694 __compiletime_assert(condition, msg, prefix, suffix)
eb5c2d4b45e3d2d Will Deacon 2020-07-21 695
eb5c2d4b45e3d2d Will Deacon 2020-07-21 696 /**
eb5c2d4b45e3d2d Will Deacon 2020-07-21 697 * compiletime_assert - break build and emit msg if condition is false
eb5c2d4b45e3d2d Will Deacon 2020-07-21 698 * @condition: a compile-time constant condition to check
eb5c2d4b45e3d2d Will Deacon 2020-07-21 699 * @msg: a message to emit if condition is false
eb5c2d4b45e3d2d Will Deacon 2020-07-21 700 *
eb5c2d4b45e3d2d Will Deacon 2020-07-21 701 * In tradition of POSIX assert, this macro will break the build if the
eb5c2d4b45e3d2d Will Deacon 2020-07-21 702 * supplied condition is *false*, emitting the supplied error message if the
eb5c2d4b45e3d2d Will Deacon 2020-07-21 703 * compiler has support to do so.
eb5c2d4b45e3d2d Will Deacon 2020-07-21 704 */
eb5c2d4b45e3d2d Will Deacon 2020-07-21 705 #define compiletime_assert(condition, msg) \
eb5c2d4b45e3d2d Will Deacon 2020-07-21 @706 _compiletime_assert(condition, msg, __compiletime_assert_, __COUNTER__)
eb5c2d4b45e3d2d Will Deacon 2020-07-21 707
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-13 10:07 [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-03-13 10:07 ` [PATCH v3 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
2026-03-13 10:07 ` [PATCH v3 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
@ 2026-03-13 10:07 ` Radu Sabau via B4 Relay
2026-03-13 11:13 ` Andy Shevchenko
2026-03-14 18:37 ` David Lechner
2026-03-13 10:07 ` [PATCH v3 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
2026-03-13 11:14 ` [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Andy Shevchenko
4 siblings, 2 replies; 37+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-13 10:07 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add buffered capture support using the IIO triggered buffer framework.
Both operating modes share a single IIO trigger and trigger handler.
The handler builds a complete scan — one u32 slot per channel at its
scan_index position, followed by a timestamp — and pushes it to the
IIO buffer in a single iio_push_to_buffers_with_ts() call.
For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
IRQ handler stops conversions and fires the IIO trigger; the trigger
handler reads accumulated results from the AVG_IN registers via regmap
and restarts conversions for the next cycle.
For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
so conversions are triggered by CS assertion rather than by a dedicated
pin. The standard iio-trig-hrtimer module is not used because the timer
period must be derived from the SPI clock rate and the number of active
channels: the pipelined protocol requires N+1 SPI transfers per scan
(the first result is garbage and is discarded), so the minimum period
depends on both the SPI speed and the live channel count at buffer
enable time. A driver-private hrtimer whose period is recomputed by
buffer_postenable is simpler and avoids requiring the user to configure
an external trigger with the correct hardware-derived period.
Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
channel slots in the scan buffer are uniformly sized regardless of the
SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ad4691.c | 402 ++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 378 insertions(+), 26 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 3685a03aa8dc..d498f16c0816 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -142,6 +142,8 @@ config AD4170_4
config AD4691
tristate "Analog Devices AD4691 Family ADC Driver"
depends on SPI
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
select REGMAP
help
Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index 31eafa12bef8..de2208395b21 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/reset.h>
+#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/math.h>
#include <linux/module.h>
@@ -24,8 +25,13 @@
#include <linux/units.h>
#include <linux/unaligned.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
#include <dt-bindings/iio/adc/adi,ad4691.h>
#define AD4691_VREF_uV_MIN 2400000
@@ -70,7 +76,7 @@
#define AD4691_ACC_MASK1_REG 0x184
#define AD4691_ACC_MASK2_REG 0x185
#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
-#define AD4691_ACC_COUNT_VAL 0x3F
+#define AD4691_ACC_COUNT_VAL 0x01
#define AD4691_GPIO_MODE1_REG 0x196
#define AD4691_GPIO_MODE2_REG 0x197
#define AD4691_GPIO_READ 0x1A0
@@ -160,33 +166,33 @@ static const struct iio_chan_spec ad4693_channels[] = {
};
static const struct iio_chan_spec ad4691_manual_channels[] = {
- AD4691_CHANNEL(0, 0, 16, 24, 8),
- AD4691_CHANNEL(1, 1, 16, 24, 8),
- AD4691_CHANNEL(2, 2, 16, 24, 8),
- AD4691_CHANNEL(3, 3, 16, 24, 8),
- AD4691_CHANNEL(4, 4, 16, 24, 8),
- AD4691_CHANNEL(5, 5, 16, 24, 8),
- AD4691_CHANNEL(6, 6, 16, 24, 8),
- AD4691_CHANNEL(7, 7, 16, 24, 8),
- AD4691_CHANNEL(8, 8, 16, 24, 8),
- AD4691_CHANNEL(9, 9, 16, 24, 8),
- AD4691_CHANNEL(10, 10, 16, 24, 8),
- AD4691_CHANNEL(11, 11, 16, 24, 8),
- AD4691_CHANNEL(12, 12, 16, 24, 8),
- AD4691_CHANNEL(13, 13, 16, 24, 8),
- AD4691_CHANNEL(14, 14, 16, 24, 8),
- AD4691_CHANNEL(15, 15, 16, 24, 8)
+ AD4691_CHANNEL(0, 0, 16, 32, 8),
+ AD4691_CHANNEL(1, 1, 16, 32, 8),
+ AD4691_CHANNEL(2, 2, 16, 32, 8),
+ AD4691_CHANNEL(3, 3, 16, 32, 8),
+ AD4691_CHANNEL(4, 4, 16, 32, 8),
+ AD4691_CHANNEL(5, 5, 16, 32, 8),
+ AD4691_CHANNEL(6, 6, 16, 32, 8),
+ AD4691_CHANNEL(7, 7, 16, 32, 8),
+ AD4691_CHANNEL(8, 8, 16, 32, 8),
+ AD4691_CHANNEL(9, 9, 16, 32, 8),
+ AD4691_CHANNEL(10, 10, 16, 32, 8),
+ AD4691_CHANNEL(11, 11, 16, 32, 8),
+ AD4691_CHANNEL(12, 12, 16, 32, 8),
+ AD4691_CHANNEL(13, 13, 16, 32, 8),
+ AD4691_CHANNEL(14, 14, 16, 32, 8),
+ AD4691_CHANNEL(15, 15, 16, 32, 8)
};
static const struct iio_chan_spec ad4693_manual_channels[] = {
- AD4691_CHANNEL(0, 0, 16, 24, 8),
- AD4691_CHANNEL(1, 1, 16, 24, 8),
- AD4691_CHANNEL(2, 2, 16, 24, 8),
- AD4691_CHANNEL(3, 3, 16, 24, 8),
- AD4691_CHANNEL(4, 4, 16, 24, 8),
- AD4691_CHANNEL(5, 5, 16, 24, 8),
- AD4691_CHANNEL(6, 6, 16, 24, 8),
- AD4691_CHANNEL(7, 7, 16, 24, 8)
+ AD4691_CHANNEL(0, 0, 16, 32, 8),
+ AD4691_CHANNEL(1, 1, 16, 32, 8),
+ AD4691_CHANNEL(2, 2, 16, 32, 8),
+ AD4691_CHANNEL(3, 3, 16, 32, 8),
+ AD4691_CHANNEL(4, 4, 16, 32, 8),
+ AD4691_CHANNEL(5, 5, 16, 32, 8),
+ AD4691_CHANNEL(6, 6, 16, 32, 8),
+ AD4691_CHANNEL(7, 7, 16, 32, 8)
};
static const struct ad4691_chip_info ad4691_ad4691 = {
@@ -228,6 +234,8 @@ struct ad4691_state {
unsigned long ref_clk_rate;
struct pwm_device *conv_trigger;
+ struct iio_trigger *trig;
+
enum ad4691_adc_mode adc_mode;
int vref_uV;
@@ -238,6 +246,21 @@ struct ad4691_state {
* atomicity of consecutive SPI operations.
*/
struct mutex lock;
+
+ /* hrtimer for MANUAL_MODE triggered buffer (non-offload) */
+ struct hrtimer sampling_timer;
+
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers to live in their own cache lines.
+ */
+ unsigned char rx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN);
+ unsigned char tx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)];
+ /* Scan buffer: one slot per channel (u32) plus timestamp */
+ struct {
+ u32 vals[16];
+ s64 ts __aligned(8);
+ } scan __aligned(IIO_DMA_MINALIGN);
};
static void ad4691_disable_pwm(void *data)
@@ -399,6 +422,28 @@ static const struct regmap_config ad4691_regmap_config = {
.cache_type = REGCACHE_MAPLE,
};
+static int ad4691_transfer(struct ad4691_state *st, int command,
+ unsigned int *val)
+{
+ struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
+ struct spi_transfer xfer = {
+ .tx_buf = st->tx_data,
+ .rx_buf = st->rx_data,
+ .len = 3,
+ };
+ int ret;
+
+ memcpy(st->tx_data, &command, 3);
+
+ ret = spi_sync_transfer(spi, &xfer, 1);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be24(st->rx_data);
+
+ return 0;
+}
+
static int ad4691_get_sampling_freq(struct ad4691_state *st)
{
if (st->adc_mode == AD4691_MANUAL_MODE)
@@ -483,6 +528,18 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
return __ad4691_set_sampling_freq(st, freq);
}
+static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
+{
+ struct pwm_state conv_state = { };
+
+ conv_state.period = st->cnv_period;
+ conv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
+ conv_state.polarity = PWM_POLARITY_NORMAL;
+ conv_state.enabled = enable;
+
+ return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
+}
+
static int ad4691_single_shot_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val)
{
@@ -594,6 +651,235 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
return regmap_write(st->regmap, reg, writeval);
}
+/*
+ * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
+ *
+ * Configures the ADC hardware registers for the mode selected at probe
+ * (CNV_CLOCK or MANUAL). Called from buffer postenable before starting
+ * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
+ */
+static int ad4691_enter_conversion_mode(struct ad4691_state *st)
+{
+ int ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE)
+ return regmap_write(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_DEVICE_MANUAL);
+
+ ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_CNV_CLOCK_MODE);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
+ AD4691_DATA_READY);
+}
+
+/*
+ * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
+ *
+ * Called from buffer postdisable/predisable to restore the chip to the
+ * idle state used by read_raw. Clears the sequencer and resets state.
+ */
+static int ad4691_exit_conversion_mode(struct ad4691_state *st)
+{
+ int ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ ret = regmap_write(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_DEVICE_REGISTER);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
+ if (ret)
+ return ret;
+
+ /* Restore GP0 to ADC_BUSY for AUTONOMOUS idle (enter set it to DATA_READY) */
+ ret = regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ AD4691_SEQ_ALL_CHANNELS_OFF);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+}
+
+static int ad4691_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
+ u16 mask = ~(*indio_dev->active_scan_mask);
+ u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
+ int n_active = hweight_long(*indio_dev->active_scan_mask);
+ unsigned int bit;
+ int ret;
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ u64 min_period_ns;
+
+ /* N+1 transfers needed for N channels, with 50% overhead */
+ min_period_ns = div64_u64((u64)(n_active + 1) * AD4691_BITS_PER_XFER *
+ NSEC_PER_SEC * 3,
+ spi->max_speed_hz * 2);
+
+ if (ktime_to_ns(st->sampling_period) < min_period_ns) {
+ dev_err(dev,
+ "Sampling period %lld ns too short for %d channels. Min: %llu ns\n",
+ ktime_to_ns(st->sampling_period), n_active,
+ min_period_ns);
+ return -EINVAL;
+ }
+
+ hrtimer_start(&st->sampling_timer, st->sampling_period,
+ HRTIMER_MODE_REL);
+ return 0;
+ }
+
+ /* CNV_CLOCK_MODE: configure sequencer and start PWM */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ *indio_dev->active_scan_mask);
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
+ AD4691_ACC_COUNT_VAL);
+ if (ret)
+ return ret;
+ }
+
+ return ad4691_sampling_enable(st, true);
+}
+
+static int ad4691_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE)
+ hrtimer_cancel_wait_running(&st->sampling_timer);
+ else
+ ad4691_sampling_enable(st, false);
+
+ return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
+ .postenable = &ad4691_buffer_postenable,
+ .postdisable = &ad4691_buffer_postdisable,
+};
+
+static irqreturn_t ad4691_irq(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ /*
+ * DATA_READY has asserted: stop conversions before reading so the
+ * accumulator does not continue sampling while the trigger handler
+ * processes the data. Then fire the IIO trigger to push the sample
+ * to the buffer.
+ */
+ ad4691_sampling_enable(st, false);
+ iio_trigger_poll(st->trig);
+
+ return IRQ_HANDLED;
+}
+
+static enum hrtimer_restart ad4691_sampling_timer_handler(struct hrtimer *timer)
+{
+ struct ad4691_state *st = container_of(timer, struct ad4691_state,
+ sampling_timer);
+
+ iio_trigger_poll(st->trig);
+ hrtimer_forward_now(timer, st->sampling_period);
+
+ return HRTIMER_RESTART;
+}
+
+static const struct iio_trigger_ops ad4691_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static irqreturn_t ad4691_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int val, i;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ unsigned int prev_val;
+ int prev_chan = -1;
+
+ /*
+ * MANUAL_MODE with CNV tied to CS: each transfer triggers a
+ * conversion AND returns the previous conversion's result.
+ * First transfer returns garbage, so we do N+1 transfers for
+ * N channels. Collect all results into scan.vals[], then push
+ * the complete scan once.
+ */
+ iio_for_each_active_channel(indio_dev, i) {
+ ret = ad4691_transfer(st, AD4691_ADC_CHAN(i), &val);
+ if (ret)
+ goto done;
+
+ if (prev_chan >= 0)
+ st->scan.vals[prev_chan] = prev_val;
+ prev_val = val;
+ prev_chan = i;
+ }
+
+ /* Final NOOP transfer to retrieve last channel's result */
+ ret = ad4691_transfer(st, AD4691_NOOP, &val);
+ if (ret)
+ goto done;
+
+ st->scan.vals[prev_chan] = val;
+ } else {
+ iio_for_each_active_channel(indio_dev, i) {
+ ret = regmap_read(st->regmap, AD4691_AVG_IN(i), &val);
+ if (ret)
+ goto done;
+
+ st->scan.vals[i] = val;
+ }
+
+ regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+
+ /* Restart conversions for the next trigger cycle. */
+ ad4691_sampling_enable(st, true);
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+ pf->timestamp);
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+ return IRQ_HANDLED;
+}
+
static const struct iio_info ad4691_info = {
.read_raw = &ad4691_read_raw,
.write_raw = &ad4691_write_raw,
@@ -624,6 +910,7 @@ static int ad4691_reset(struct ad4691_state *st)
static int ad4691_config(struct ad4691_state *st)
{
struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
enum ad4691_ref_ctrl ref_val;
unsigned int reg_val;
int ret;
@@ -644,7 +931,7 @@ static int ad4691_config(struct ad4691_state *st)
st->adc_mode = AD4691_MANUAL_MODE;
st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
- to_spi_device(dev)->max_speed_hz)));
+ spi->max_speed_hz)));
}
/* Perform a state reset on the channels at start-up. */
@@ -693,6 +980,65 @@ static int ad4691_config(struct ad4691_state *st)
return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
}
+static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
+ struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
+ int irq, ret;
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+
+ st->trig->ops = &ad4691_trigger_ops;
+ iio_trigger_set_drvdata(st->trig, st);
+
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return dev_err_probe(dev, ret, "IIO trigger register failed\n");
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /*
+ * No DATA_READY signal in MANUAL_MODE; CNV is tied to CS so
+ * conversions start with each SPI transfer. Use an hrtimer to
+ * schedule periodic reads.
+ */
+ hrtimer_setup(&st->sampling_timer, ad4691_sampling_timer_handler,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(
+ NSEC_PER_SEC,
+ AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
+ spi->max_speed_hz)));
+ } else {
+ /*
+ * DATA_READY asserts at end-of-conversion. The IRQ handler
+ * stops conversions and fires the IIO trigger so the trigger
+ * handler can read and push the sample to the buffer.
+ */
+ irq = fwnode_irq_get(dev_fwnode(dev), 0);
+ if (irq < 0)
+ return dev_err_probe(dev, irq,
+ "failed to get DATA_READY interrupt\n");
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &ad4691_irq,
+ IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+ }
+
+ return devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad4691_trigger_handler,
+ &ad4691_buffer_setup_ops);
+}
+
static int ad4691_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
@@ -736,6 +1082,10 @@ static int ad4691_probe(struct spi_device *spi)
st->chip->manual_channels : st->chip->channels;
indio_dev->num_channels = st->chip->num_channels;
+ ret = ad4691_setup_triggered_buffer(indio_dev, st);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-13 10:07 ` [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
@ 2026-03-13 11:13 ` Andy Shevchenko
2026-03-13 12:09 ` Sabau, Radu bogdan
2026-03-14 18:37 ` David Lechner
1 sibling, 1 reply; 37+ messages in thread
From: Andy Shevchenko @ 2026-03-13 11:13 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio, devicetree, linux-kernel, linux-pwm,
linux-gpio
On Fri, Mar 13, 2026 at 12:07:27PM +0200, Radu Sabau via B4 Relay wrote:
> Add buffered capture support using the IIO triggered buffer framework.
>
> Both operating modes share a single IIO trigger and trigger handler.
> The handler builds a complete scan — one u32 slot per channel at its
> scan_index position, followed by a timestamp — and pushes it to the
> IIO buffer in a single iio_push_to_buffers_with_ts() call.
>
> For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
> IRQ handler stops conversions and fires the IIO trigger; the trigger
> handler reads accumulated results from the AVG_IN registers via regmap
> and restarts conversions for the next cycle.
>
> For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
> so conversions are triggered by CS assertion rather than by a dedicated
> pin. The standard iio-trig-hrtimer module is not used because the timer
> period must be derived from the SPI clock rate and the number of active
> channels: the pipelined protocol requires N+1 SPI transfers per scan
> (the first result is garbage and is discarded), so the minimum period
> depends on both the SPI speed and the live channel count at buffer
> enable time. A driver-private hrtimer whose period is recomputed by
> buffer_postenable is simpler and avoids requiring the user to configure
> an external trigger with the correct hardware-derived period.
>
> Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
> channel slots in the scan buffer are uniformly sized regardless of the
> SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
...
> #include <linux/device.h>
> #include <linux/err.h>
> #include <linux/reset.h>
This is unordered.
> +#include <linux/hrtimer.h>
> #include <linux/interrupt.h>
> #include <linux/math.h>
> #include <linux/module.h>
...
> +#include <linux/iio/buffer.h>
> #include <linux/iio/iio.h>
>
Unneeded blank line.
> -#define AD4691_ACC_COUNT_VAL 0x3F
> +#define AD4691_ACC_COUNT_VAL 0x01
No ping-pong, and actually this was not used at all. So, make sure you add
constants when they are really started being used.
> #define AD4691_GPIO_MODE1_REG 0x196
> #define AD4691_GPIO_MODE2_REG 0x197
> #define AD4691_GPIO_READ 0x1A0
...
> static const struct iio_chan_spec ad4691_manual_channels[] = {
> - AD4691_CHANNEL(0, 0, 16, 24, 8),
> - AD4691_CHANNEL(1, 1, 16, 24, 8),
> - AD4691_CHANNEL(2, 2, 16, 24, 8),
> - AD4691_CHANNEL(3, 3, 16, 24, 8),
> - AD4691_CHANNEL(4, 4, 16, 24, 8),
> - AD4691_CHANNEL(5, 5, 16, 24, 8),
> - AD4691_CHANNEL(6, 6, 16, 24, 8),
> - AD4691_CHANNEL(7, 7, 16, 24, 8),
> - AD4691_CHANNEL(8, 8, 16, 24, 8),
> - AD4691_CHANNEL(9, 9, 16, 24, 8),
> - AD4691_CHANNEL(10, 10, 16, 24, 8),
> - AD4691_CHANNEL(11, 11, 16, 24, 8),
> - AD4691_CHANNEL(12, 12, 16, 24, 8),
> - AD4691_CHANNEL(13, 13, 16, 24, 8),
> - AD4691_CHANNEL(14, 14, 16, 24, 8),
> - AD4691_CHANNEL(15, 15, 16, 24, 8)
> + AD4691_CHANNEL(0, 0, 16, 32, 8),
> + AD4691_CHANNEL(1, 1, 16, 32, 8),
> + AD4691_CHANNEL(2, 2, 16, 32, 8),
> + AD4691_CHANNEL(3, 3, 16, 32, 8),
> + AD4691_CHANNEL(4, 4, 16, 32, 8),
> + AD4691_CHANNEL(5, 5, 16, 32, 8),
> + AD4691_CHANNEL(6, 6, 16, 32, 8),
> + AD4691_CHANNEL(7, 7, 16, 32, 8),
> + AD4691_CHANNEL(8, 8, 16, 32, 8),
> + AD4691_CHANNEL(9, 9, 16, 32, 8),
> + AD4691_CHANNEL(10, 10, 16, 32, 8),
> + AD4691_CHANNEL(11, 11, 16, 32, 8),
> + AD4691_CHANNEL(12, 12, 16, 32, 8),
> + AD4691_CHANNEL(13, 13, 16, 32, 8),
> + AD4691_CHANNEL(14, 14, 16, 32, 8),
> + AD4691_CHANNEL(15, 15, 16, 32, 8)
> };
>
> static const struct iio_chan_spec ad4693_manual_channels[] = {
> - AD4691_CHANNEL(0, 0, 16, 24, 8),
> - AD4691_CHANNEL(1, 1, 16, 24, 8),
> - AD4691_CHANNEL(2, 2, 16, 24, 8),
> - AD4691_CHANNEL(3, 3, 16, 24, 8),
> - AD4691_CHANNEL(4, 4, 16, 24, 8),
> - AD4691_CHANNEL(5, 5, 16, 24, 8),
> - AD4691_CHANNEL(6, 6, 16, 24, 8),
> - AD4691_CHANNEL(7, 7, 16, 24, 8)
> + AD4691_CHANNEL(0, 0, 16, 32, 8),
> + AD4691_CHANNEL(1, 1, 16, 32, 8),
> + AD4691_CHANNEL(2, 2, 16, 32, 8),
> + AD4691_CHANNEL(3, 3, 16, 32, 8),
> + AD4691_CHANNEL(4, 4, 16, 32, 8),
> + AD4691_CHANNEL(5, 5, 16, 32, 8),
> + AD4691_CHANNEL(6, 6, 16, 32, 8),
> + AD4691_CHANNEL(7, 7, 16, 32, 8)
> };
Hold on, you just introduced them in the first patch. Are those wrong?!
Please, fix this mess and make sure you have no - (minus) lines from
the previous patch(es).
...
> + /*
> + * DMA (thus cache coherency maintenance) may require the
> + * transfer buffers to live in their own cache lines.
> + */
> + unsigned char rx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN);
> + unsigned char tx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)];
Don't we have macro for this?
...
> +static int ad4691_transfer(struct ad4691_state *st, int command,
Signed 'command'? Why?
> + unsigned int *val)
...
> +static int ad4691_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + u16 mask = ~(*indio_dev->active_scan_mask);
> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
Same Q as per previous patch. This seems very wrong.
> + int n_active = hweight_long(*indio_dev->active_scan_mask);
Why signed?
> + unsigned int bit;
> + int ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + u64 min_period_ns;
> +
> + /* N+1 transfers needed for N channels, with 50% overhead */
> + min_period_ns = div64_u64((u64)(n_active + 1) * AD4691_BITS_PER_XFER *
> + NSEC_PER_SEC * 3,
> + spi->max_speed_hz * 2);
> +
> + if (ktime_to_ns(st->sampling_period) < min_period_ns) {
> + dev_err(dev,
> + "Sampling period %lld ns too short for %d channels. Min: %llu ns\n",
> + ktime_to_ns(st->sampling_period), n_active,
> + min_period_ns);
> + return -EINVAL;
> + }
> +
> + hrtimer_start(&st->sampling_timer, st->sampling_period,
> + HRTIMER_MODE_REL);
> + return 0;
> + }
> +
> + /* CNV_CLOCK_MODE: configure sequencer and start PWM */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
sizeof()? ARRAY_SIZE()? See comments per previous patch.
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + *indio_dev->active_scan_mask);
> + if (ret)
> + return ret;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
> + AD4691_ACC_COUNT_VAL);
> + if (ret)
> + return ret;
> + }
> +
> + return ad4691_sampling_enable(st, true);
> +}
...
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int val, i;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + unsigned int prev_val;
> + int prev_chan = -1;
> +
> + /*
> + * MANUAL_MODE with CNV tied to CS: each transfer triggers a
> + * conversion AND returns the previous conversion's result.
> + * First transfer returns garbage, so we do N+1 transfers for
> + * N channels. Collect all results into scan.vals[], then push
> + * the complete scan once.
> + */
> + iio_for_each_active_channel(indio_dev, i) {
> + ret = ad4691_transfer(st, AD4691_ADC_CHAN(i), &val);
> + if (ret)
> + goto done;
> +
> + if (prev_chan >= 0)
> + st->scan.vals[prev_chan] = prev_val;
> + prev_val = val;
> + prev_chan = i;
> + }
> +
> + /* Final NOOP transfer to retrieve last channel's result */
> + ret = ad4691_transfer(st, AD4691_NOOP, &val);
> + if (ret)
> + goto done;
> + st->scan.vals[prev_chan] = val;
How do you guarantee that prev_chan never ever be -1 here?
> + } else {
> + iio_for_each_active_channel(indio_dev, i) {
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(i), &val);
> + if (ret)
> + goto done;
> +
> + st->scan.vals[i] = val;
> + }
> +
> + regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
> + /* Restart conversions for the next trigger cycle. */
> + ad4691_sampling_enable(st, true);
> + }
> +
> + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> + pf->timestamp);
> +
> +done:
> + iio_trigger_notify_done(indio_dev->trig);
> + return IRQ_HANDLED;
> +}
...
> static int ad4691_config(struct ad4691_state *st)
> {
> struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> enum ad4691_ref_ctrl ref_val;
> unsigned int reg_val;
> int ret;
> st->adc_mode = AD4691_MANUAL_MODE;
> st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> - to_spi_device(dev)->max_speed_hz)));
> + spi->max_speed_hz)));
> }
Okay, this should be part of the previous patch.
> return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
> }
...
> +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> + struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + int irq, ret;
> +
> + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> + indio_dev->name,
> + iio_device_id(indio_dev));
It seems you ignored some of my comments. Please go back and read carefully
what I commented on previous version of the series.
> + if (!st->trig)
> + return -ENOMEM;
> +
> + st->trig->ops = &ad4691_trigger_ops;
> + iio_trigger_set_drvdata(st->trig, st);
> +
> + ret = devm_iio_trigger_register(dev, st->trig);
> + if (ret)
> + return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> +
> + indio_dev->trig = iio_trigger_get(st->trig);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + /*
> + * No DATA_READY signal in MANUAL_MODE; CNV is tied to CS so
> + * conversions start with each SPI transfer. Use an hrtimer to
> + * schedule periodic reads.
> + */
> + hrtimer_setup(&st->sampling_timer, ad4691_sampling_timer_handler,
> + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(
> + NSEC_PER_SEC,
> + AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> + spi->max_speed_hz)));
> + } else {
> + /*
> + * DATA_READY asserts at end-of-conversion. The IRQ handler
> + * stops conversions and fires the IIO trigger so the trigger
> + * handler can read and push the sample to the buffer.
> + */
> + irq = fwnode_irq_get(dev_fwnode(dev), 0);
> + if (irq < 0)
> + return dev_err_probe(dev, irq,
> + "failed to get DATA_READY interrupt\n");
> +
> + ret = devm_request_threaded_irq(dev, irq, NULL,
> + &ad4691_irq,
> + IRQF_ONESHOT,
> + indio_dev->name, indio_dev);
> + if (ret)
> + return ret;
> + }
> +
> + return devm_iio_triggered_buffer_setup(dev, indio_dev,
> + &iio_pollfunc_store_time,
> + &ad4691_trigger_handler,
> + &ad4691_buffer_setup_ops);
> +}
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-13 11:13 ` Andy Shevchenko
@ 2026-03-13 12:09 ` Sabau, Radu bogdan
2026-03-13 14:40 ` Andy Shevchenko
0 siblings, 1 reply; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-13 12:09 UTC (permalink / raw)
To: Andy Shevchenko
Cc: Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
David Lechner, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org
> -----Original Message-----
> From: Andy Shevchenko <andriy.shevchenko@intel.com>
> Sent: Friday, March 13, 2026 11:13 AM
> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>
> Cc: Lars-Peter Clausen <lars@metafoo.de>; Hennerich, Michael
> <Michael.Hennerich@analog.com>; Jonathan Cameron <jic23@kernel.org>;
> David Lechner <dlechner@baylibre.com>; Sa, Nuno <Nuno.Sa@analog.com>;
> Andy Shevchenko <andy@kernel.org>; Rob Herring <robh@kernel.org>;
> Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
> <conor+dt@kernel.org>; Uwe Kleine-König <ukleinek@kernel.org>; Liam
> Girdwood <lgirdwood@gmail.com>; Mark Brown <broonie@kernel.org>; Linus
> Walleij <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>; Philipp
> Zabel <p.zabel@pengutronix.de>; linux-iio@vger.kernel.org;
> devicetree@vger.kernel.org; linux-kernel@vger.kernel.org; linux-
> pwm@vger.kernel.org; linux-gpio@vger.kernel.org
> Subject: Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
>
> [External]
>
> On Fri, Mar 13, 2026 at 12:07:27PM +0200, Radu Sabau via B4 Relay wrote:
>
> > Add buffered capture support using the IIO triggered buffer framework.
> >
> > Both operating modes share a single IIO trigger and trigger handler.
> > The handler builds a complete scan — one u32 slot per channel at its
> > scan_index position, followed by a timestamp — and pushes it to the
> > IIO buffer in a single iio_push_to_buffers_with_ts() call.
> >
> > For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
> > IRQ handler stops conversions and fires the IIO trigger; the trigger
> > handler reads accumulated results from the AVG_IN registers via regmap
> > and restarts conversions for the next cycle.
> >
> > For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
> > so conversions are triggered by CS assertion rather than by a dedicated
> > pin. The standard iio-trig-hrtimer module is not used because the timer
> > period must be derived from the SPI clock rate and the number of active
> > channels: the pipelined protocol requires N+1 SPI transfers per scan
> > (the first result is garbage and is discarded), so the minimum period
> > depends on both the SPI speed and the live channel count at buffer
> > enable time. A driver-private hrtimer whose period is recomputed by
> > buffer_postenable is simpler and avoids requiring the user to configure
> > an external trigger with the correct hardware-derived period.
> >
> > Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
> > channel slots in the scan buffer are uniformly sized regardless of the
> > SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
>
> ...
>
> > #include <linux/device.h>
> > #include <linux/err.h>
>
> > #include <linux/reset.h>
>
> This is unordered.
>
> > +#include <linux/hrtimer.h>
> > #include <linux/interrupt.h>
> > #include <linux/math.h>
> > #include <linux/module.h>
>
> ...
>
> > +#include <linux/iio/buffer.h>
> > #include <linux/iio/iio.h>
>
> >
>
> Unneeded blank line.
>
> > -#define AD4691_ACC_COUNT_VAL 0x3F
> > +#define AD4691_ACC_COUNT_VAL 0x01
>
> No ping-pong, and actually this was not used at all. So, make sure you add
> constants when they are really started being used.
Hi Andy,
First of all, thank you again for you thorough review, it does help a lot
in improving this driver.
This value is being used in the buffer_postenable in order to make
sure we don't encounter oversampling, since Manual Mode doesn't
oversample, and per Jonathan's review, there is no reason to support
both oversampled and raw readings at the same time.
>
> > #define AD4691_GPIO_MODE1_REG 0x196
> > #define AD4691_GPIO_MODE2_REG 0x197
> > #define AD4691_GPIO_READ 0x1A0
>
> ...
>
> > static const struct iio_chan_spec ad4691_manual_channels[] = {
> > - AD4691_CHANNEL(0, 0, 16, 24, 8),
> > - AD4691_CHANNEL(1, 1, 16, 24, 8),
> > - AD4691_CHANNEL(2, 2, 16, 24, 8),
> > - AD4691_CHANNEL(3, 3, 16, 24, 8),
> > - AD4691_CHANNEL(4, 4, 16, 24, 8),
> > - AD4691_CHANNEL(5, 5, 16, 24, 8),
> > - AD4691_CHANNEL(6, 6, 16, 24, 8),
> > - AD4691_CHANNEL(7, 7, 16, 24, 8),
> > - AD4691_CHANNEL(8, 8, 16, 24, 8),
> > - AD4691_CHANNEL(9, 9, 16, 24, 8),
> > - AD4691_CHANNEL(10, 10, 16, 24, 8),
> > - AD4691_CHANNEL(11, 11, 16, 24, 8),
> > - AD4691_CHANNEL(12, 12, 16, 24, 8),
> > - AD4691_CHANNEL(13, 13, 16, 24, 8),
> > - AD4691_CHANNEL(14, 14, 16, 24, 8),
> > - AD4691_CHANNEL(15, 15, 16, 24, 8)
> > + AD4691_CHANNEL(0, 0, 16, 32, 8),
> > + AD4691_CHANNEL(1, 1, 16, 32, 8),
> > + AD4691_CHANNEL(2, 2, 16, 32, 8),
> > + AD4691_CHANNEL(3, 3, 16, 32, 8),
> > + AD4691_CHANNEL(4, 4, 16, 32, 8),
> > + AD4691_CHANNEL(5, 5, 16, 32, 8),
> > + AD4691_CHANNEL(6, 6, 16, 32, 8),
> > + AD4691_CHANNEL(7, 7, 16, 32, 8),
> > + AD4691_CHANNEL(8, 8, 16, 32, 8),
> > + AD4691_CHANNEL(9, 9, 16, 32, 8),
> > + AD4691_CHANNEL(10, 10, 16, 32, 8),
> > + AD4691_CHANNEL(11, 11, 16, 32, 8),
> > + AD4691_CHANNEL(12, 12, 16, 32, 8),
> > + AD4691_CHANNEL(13, 13, 16, 32, 8),
> > + AD4691_CHANNEL(14, 14, 16, 32, 8),
> > + AD4691_CHANNEL(15, 15, 16, 32, 8)
> > };
> >
> > static const struct iio_chan_spec ad4693_manual_channels[] = {
> > - AD4691_CHANNEL(0, 0, 16, 24, 8),
> > - AD4691_CHANNEL(1, 1, 16, 24, 8),
> > - AD4691_CHANNEL(2, 2, 16, 24, 8),
> > - AD4691_CHANNEL(3, 3, 16, 24, 8),
> > - AD4691_CHANNEL(4, 4, 16, 24, 8),
> > - AD4691_CHANNEL(5, 5, 16, 24, 8),
> > - AD4691_CHANNEL(6, 6, 16, 24, 8),
> > - AD4691_CHANNEL(7, 7, 16, 24, 8)
> > + AD4691_CHANNEL(0, 0, 16, 32, 8),
> > + AD4691_CHANNEL(1, 1, 16, 32, 8),
> > + AD4691_CHANNEL(2, 2, 16, 32, 8),
> > + AD4691_CHANNEL(3, 3, 16, 32, 8),
> > + AD4691_CHANNEL(4, 4, 16, 32, 8),
> > + AD4691_CHANNEL(5, 5, 16, 32, 8),
> > + AD4691_CHANNEL(6, 6, 16, 32, 8),
> > + AD4691_CHANNEL(7, 7, 16, 32, 8)
> > };
>
> Hold on, you just introduced them in the first patch. Are those wrong?!
> Please, fix this mess and make sure you have no - (minus) lines from
> the previous patch(es).
>
The short answer is yes, the first patch's manual channel's structure is wrong
since the buffer scan slot expects 32-bits, but I guess this change should have
happened in the previous commit since the channels are first configured there.
I am sorry for this, I'll make sure to fix this in the next version alongside the
other comments.
> ...
>
> > + /*
> > + * DMA (thus cache coherency maintenance) may require the
> > + * transfer buffers to live in their own cache lines.
> > + */
> > + unsigned char rx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)]
> __aligned(IIO_DMA_MINALIGN);
> > + unsigned char tx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)];
>
> Don't we have macro for this?
>
> ...
>
> > +static int ad4691_transfer(struct ad4691_state *st, int command,
>
> Signed 'command'? Why?
>
> > + unsigned int *val)
>
> ...
>
> > +static int ad4691_buffer_postenable(struct iio_dev *indio_dev)
> > +{
> > + struct ad4691_state *st = iio_priv(indio_dev);
> > + struct device *dev = regmap_get_device(st->regmap);
> > + struct spi_device *spi = to_spi_device(dev);
> > + u16 mask = ~(*indio_dev->active_scan_mask);
>
> > + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
>
> Same Q as per previous patch. This seems very wrong.
>
> > + int n_active = hweight_long(*indio_dev->active_scan_mask);
>
> Why signed?
>
> > + unsigned int bit;
> > + int ret;
> > +
> > + ret = ad4691_enter_conversion_mode(st);
> > + if (ret)
> > + return ret;
> > +
> > + if (st->adc_mode == AD4691_MANUAL_MODE) {
> > + u64 min_period_ns;
> > +
> > + /* N+1 transfers needed for N channels, with 50% overhead */
> > + min_period_ns = div64_u64((u64)(n_active + 1) *
> AD4691_BITS_PER_XFER *
> > + NSEC_PER_SEC * 3,
> > + spi->max_speed_hz * 2);
> > +
> > + if (ktime_to_ns(st->sampling_period) < min_period_ns) {
> > + dev_err(dev,
> > + "Sampling period %lld ns too short for %d
> channels. Min: %llu ns\n",
> > + ktime_to_ns(st->sampling_period), n_active,
> > + min_period_ns);
> > + return -EINVAL;
> > + }
> > +
> > + hrtimer_start(&st->sampling_timer, st->sampling_period,
> > + HRTIMER_MODE_REL);
> > + return 0;
> > + }
> > +
> > + /* CNV_CLOCK_MODE: configure sequencer and start PWM */
> > + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> > + AD4691_STATE_RESET_ALL);
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG,
> acc_mask, 2);
>
> sizeof()? ARRAY_SIZE()? See comments per previous patch.
>
> > + if (ret)
> > + return ret;
> > +
> > + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> > + *indio_dev->active_scan_mask);
> > + if (ret)
> > + return ret;
> > +
> > + iio_for_each_active_channel(indio_dev, bit) {
> > + ret = regmap_write(st->regmap,
> AD4691_ACC_COUNT_LIMIT(bit),
> > + AD4691_ACC_COUNT_VAL);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return ad4691_sampling_enable(st, true);
> > +}
>
> ...
>
> > +{
> > + struct iio_poll_func *pf = p;
> > + struct iio_dev *indio_dev = pf->indio_dev;
> > + struct ad4691_state *st = iio_priv(indio_dev);
> > + unsigned int val, i;
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + if (st->adc_mode == AD4691_MANUAL_MODE) {
> > + unsigned int prev_val;
> > + int prev_chan = -1;
> > +
> > + /*
> > + * MANUAL_MODE with CNV tied to CS: each transfer triggers
> a
> > + * conversion AND returns the previous conversion's result.
> > + * First transfer returns garbage, so we do N+1 transfers for
> > + * N channels. Collect all results into scan.vals[], then push
> > + * the complete scan once.
> > + */
> > + iio_for_each_active_channel(indio_dev, i) {
> > + ret = ad4691_transfer(st, AD4691_ADC_CHAN(i),
> &val);
> > + if (ret)
> > + goto done;
> > +
> > + if (prev_chan >= 0)
> > + st->scan.vals[prev_chan] = prev_val;
> > + prev_val = val;
> > + prev_chan = i;
> > + }
> > +
> > + /* Final NOOP transfer to retrieve last channel's result */
> > + ret = ad4691_transfer(st, AD4691_NOOP, &val);
> > + if (ret)
> > + goto done;
>
> > + st->scan.vals[prev_chan] = val;
>
> How do you guarantee that prev_chan never ever be -1 here?
>
> > + } else {
> > + iio_for_each_active_channel(indio_dev, i) {
> > + ret = regmap_read(st->regmap, AD4691_AVG_IN(i),
> &val);
> > + if (ret)
> > + goto done;
> > +
> > + st->scan.vals[i] = val;
> > + }
> > +
> > + regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> AD4691_STATE_RESET_ALL);
> > +
> > + /* Restart conversions for the next trigger cycle. */
> > + ad4691_sampling_enable(st, true);
> > + }
> > +
> > + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> > + pf->timestamp);
> > +
> > +done:
> > + iio_trigger_notify_done(indio_dev->trig);
> > + return IRQ_HANDLED;
> > +}
>
> ...
>
> > static int ad4691_config(struct ad4691_state *st)
> > {
> > struct device *dev = regmap_get_device(st->regmap);
> > + struct spi_device *spi = to_spi_device(dev);
> > enum ad4691_ref_ctrl ref_val;
> > unsigned int reg_val;
> > int ret;
>
> > st->adc_mode = AD4691_MANUAL_MODE;
> > st->sampling_period =
> ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> > AD4691_MANUAL_MODE_STD_FREQ(st->chip-
> >num_channels,
> > - to_spi_device(dev)->max_speed_hz)));
> > + spi->max_speed_hz)));
> > }
>
> Okay, this should be part of the previous patch.
>
> > return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
> AD4691_ADC_BUSY);
> > }
>
> ...
>
> > +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> > + struct ad4691_state *st)
> > +{
> > + struct device *dev = regmap_get_device(st->regmap);
> > + struct spi_device *spi = to_spi_device(dev);
> > + int irq, ret;
> > +
> > + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> > + indio_dev->name,
> > + iio_device_id(indio_dev));
>
> It seems you ignored some of my comments. Please go back and read carefully
> what I commented on previous version of the series.
I am very sorry for this. I may have misunderstood the comments, and thus
seem like I ignored them, but this wasn't my intention at all.
If you refer to the trigger_alloc comment, I did talk to my senior colleagues
and they referred to the fact -ENOMEM return should have been enough,
but perhaps this was wrong too. Could you please clarify?
Best Regards,
Radu
>
> > + if (!st->trig)
> > + return -ENOMEM;
> > +
> > + st->trig->ops = &ad4691_trigger_ops;
> > + iio_trigger_set_drvdata(st->trig, st);
> > +
> > + ret = devm_iio_trigger_register(dev, st->trig);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> > +
> > + indio_dev->trig = iio_trigger_get(st->trig);
> > +
> > + if (st->adc_mode == AD4691_MANUAL_MODE) {
> > + /*
> > + * No DATA_READY signal in MANUAL_MODE; CNV is tied to CS
> so
> > + * conversions start with each SPI transfer. Use an hrtimer to
> > + * schedule periodic reads.
> > + */
> > + hrtimer_setup(&st->sampling_timer,
> ad4691_sampling_timer_handler,
> > + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> > + st->sampling_period =
> ns_to_ktime(DIV_ROUND_CLOSEST_ULL(
> > + NSEC_PER_SEC,
> > + AD4691_MANUAL_MODE_STD_FREQ(st->chip-
> >num_channels,
> > + spi->max_speed_hz)));
> > + } else {
> > + /*
> > + * DATA_READY asserts at end-of-conversion. The IRQ handler
> > + * stops conversions and fires the IIO trigger so the trigger
> > + * handler can read and push the sample to the buffer.
> > + */
> > + irq = fwnode_irq_get(dev_fwnode(dev), 0);
> > + if (irq < 0)
> > + return dev_err_probe(dev, irq,
> > + "failed to get DATA_READY
> interrupt\n");
> > +
> > + ret = devm_request_threaded_irq(dev, irq, NULL,
> > + &ad4691_irq,
> > + IRQF_ONESHOT,
> > + indio_dev->name, indio_dev);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + return devm_iio_triggered_buffer_setup(dev, indio_dev,
> > + &iio_pollfunc_store_time,
> > + &ad4691_trigger_handler,
> > + &ad4691_buffer_setup_ops);
> > +}
>
> --
> With Best Regards,
> Andy Shevchenko
>
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-13 12:09 ` Sabau, Radu bogdan
@ 2026-03-13 14:40 ` Andy Shevchenko
0 siblings, 0 replies; 37+ messages in thread
From: Andy Shevchenko @ 2026-03-13 14:40 UTC (permalink / raw)
To: Sabau, Radu bogdan
Cc: Lars-Peter Clausen, Hennerich, Michael, Jonathan Cameron,
David Lechner, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio@vger.kernel.org,
devicetree@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org
On Fri, Mar 13, 2026 at 12:09:55PM +0000, Sabau, Radu bogdan wrote:
> > -----Original Message-----
> > From: Andy Shevchenko <andriy.shevchenko@intel.com>
> > Sent: Friday, March 13, 2026 11:13 AM
> > On Fri, Mar 13, 2026 at 12:07:27PM +0200, Radu Sabau via B4 Relay wrote:
...
> > > -#define AD4691_ACC_COUNT_VAL 0x3F
> > > +#define AD4691_ACC_COUNT_VAL 0x01
> >
> > No ping-pong, and actually this was not used at all. So, make sure you add
> > constants when they are really started being used.
>
> This value is being used in the buffer_postenable in order to make
> sure we don't encounter oversampling, since Manual Mode doesn't
> oversample, and per Jonathan's review, there is no reason to support
> both oversampled and raw readings at the same time.
Yes, but it wasn't used before this patch.
...
> > > +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> > > + struct ad4691_state *st)
> > > +{
> > > + struct device *dev = regmap_get_device(st->regmap);
> > > + struct spi_device *spi = to_spi_device(dev);
> > > + int irq, ret;
> > > +
> > > + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> > > + indio_dev->name,
> > > + iio_device_id(indio_dev));
> >
> > It seems you ignored some of my comments. Please go back and read carefully
> > what I commented on previous version of the series.
>
> I am very sorry for this. I may have misunderstood the comments, and thus
> seem like I ignored them, but this wasn't my intention at all.
When in such a situation, ask! A request is free and acceptable.
> If you refer to the trigger_alloc comment, I did talk to my senior colleagues
> and they referred to the fact -ENOMEM return should have been enough,
> but perhaps this was wrong too. Could you please clarify?
I'm talking about the room of the previous lines
st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name,
iio_device_id(indio_dev));
fits 80 limit, for example. OTOH this is less logical, perhaps your variant
is okay.
...
> Radu
When answering, remove the context you are agree with and not going to discuss.
I had to drop over 200 lines just "for fun" (no).
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-13 10:07 ` [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
2026-03-13 11:13 ` Andy Shevchenko
@ 2026-03-14 18:37 ` David Lechner
2026-03-16 13:22 ` Sabau, Radu bogdan
1 sibling, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-14 18:37 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add buffered capture support using the IIO triggered buffer framework.
>
> Both operating modes share a single IIO trigger and trigger handler.
> The handler builds a complete scan — one u32 slot per channel at its
> scan_index position, followed by a timestamp — and pushes it to the
> IIO buffer in a single iio_push_to_buffers_with_ts() call.
It would really help here to see some timing diagrams to know if we
are implementing this right.
For example, it isn't clear that in clocked mode if CNV triggers a
single conversion in the sequencer (i.e. IIO_SAMP_FREQ should be
info_mask_separate) or if it triggers the sequence (i.e. IIO_SAMP_FREQ
should be info_mask_shared_by_all).
>
> For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
> IRQ handler stops conversions and fires the IIO trigger; the trigger
> handler reads accumulated results from the AVG_IN registers via regmap
> and restarts conversions for the next cycle.
This seems OK, but I would kind of would expect that PWM as CNV to
only be used for SPI offloading and not without SPI offloading.
The ADC also has an internal oscillator, so it seems like it would
be more useful to use that as a conversion trigger rather than
requiring external hardware.
>
> For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
> so conversions are triggered by CS assertion rather than by a dedicated
> pin. The standard iio-trig-hrtimer module is not used because the timer
> period must be derived from the SPI clock rate and the number of active
> channels: the pipelined protocol requires N+1 SPI transfers per scan
> (the first result is garbage and is discarded), so the minimum period
> depends on both the SPI speed and the live channel count at buffer
> enable time. A driver-private hrtimer whose period is recomputed by
> buffer_postenable is simpler and avoids requiring the user to configure
> an external trigger with the correct hardware-derived period.
I'm not really following the argument here. It is quite normal that if
an hrtimer trigger is set too fast then samples will be missed. So I don't
see why we wouldn't be able to use it here. This is why we usually have
a timestamp channel, so we can know roughly when the conversion actually
took place.
>
> Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
> channel slots in the scan buffer are uniformly sized regardless of the
> SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
I also don't understand why we are including the status bits in manual
mode but not in CNV clock mode.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> drivers/iio/adc/Kconfig | 2 +
> drivers/iio/adc/ad4691.c | 402 ++++++++++++++++++++++++++++++++++++++++++++---
> 2 files changed, 378 insertions(+), 26 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 3685a03aa8dc..d498f16c0816 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -142,6 +142,8 @@ config AD4170_4
> config AD4691
> tristate "Analog Devices AD4691 Family ADC Driver"
> depends on SPI
> + select IIO_BUFFER
> + select IIO_TRIGGERED_BUFFER
> select REGMAP
> help
> Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index 31eafa12bef8..de2208395b21 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -11,6 +11,7 @@
> #include <linux/device.h>
> #include <linux/err.h>
> #include <linux/reset.h>
> +#include <linux/hrtimer.h>
> #include <linux/interrupt.h>
> #include <linux/math.h>
> #include <linux/module.h>
> @@ -24,8 +25,13 @@
> #include <linux/units.h>
> #include <linux/unaligned.h>
>
> +#include <linux/iio/buffer.h>
> #include <linux/iio/iio.h>
>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
> +
> #include <dt-bindings/iio/adc/adi,ad4691.h>
>
> #define AD4691_VREF_uV_MIN 2400000
> @@ -70,7 +76,7 @@
> #define AD4691_ACC_MASK1_REG 0x184
> #define AD4691_ACC_MASK2_REG 0x185
> #define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
> -#define AD4691_ACC_COUNT_VAL 0x3F
> +#define AD4691_ACC_COUNT_VAL 0x01
> #define AD4691_GPIO_MODE1_REG 0x196
> #define AD4691_GPIO_MODE2_REG 0x197
> #define AD4691_GPIO_READ 0x1A0
> @@ -160,33 +166,33 @@ static const struct iio_chan_spec ad4693_channels[] = {
> };
>
> static const struct iio_chan_spec ad4691_manual_channels[] = {
> - AD4691_CHANNEL(0, 0, 16, 24, 8),
> - AD4691_CHANNEL(1, 1, 16, 24, 8),
> - AD4691_CHANNEL(2, 2, 16, 24, 8),
> - AD4691_CHANNEL(3, 3, 16, 24, 8),
> - AD4691_CHANNEL(4, 4, 16, 24, 8),
> - AD4691_CHANNEL(5, 5, 16, 24, 8),
> - AD4691_CHANNEL(6, 6, 16, 24, 8),
> - AD4691_CHANNEL(7, 7, 16, 24, 8),
> - AD4691_CHANNEL(8, 8, 16, 24, 8),
> - AD4691_CHANNEL(9, 9, 16, 24, 8),
> - AD4691_CHANNEL(10, 10, 16, 24, 8),
> - AD4691_CHANNEL(11, 11, 16, 24, 8),
> - AD4691_CHANNEL(12, 12, 16, 24, 8),
> - AD4691_CHANNEL(13, 13, 16, 24, 8),
> - AD4691_CHANNEL(14, 14, 16, 24, 8),
> - AD4691_CHANNEL(15, 15, 16, 24, 8)
> + AD4691_CHANNEL(0, 0, 16, 32, 8),
> + AD4691_CHANNEL(1, 1, 16, 32, 8),
> + AD4691_CHANNEL(2, 2, 16, 32, 8),
> + AD4691_CHANNEL(3, 3, 16, 32, 8),
> + AD4691_CHANNEL(4, 4, 16, 32, 8),
> + AD4691_CHANNEL(5, 5, 16, 32, 8),
> + AD4691_CHANNEL(6, 6, 16, 32, 8),
> + AD4691_CHANNEL(7, 7, 16, 32, 8),
> + AD4691_CHANNEL(8, 8, 16, 32, 8),
> + AD4691_CHANNEL(9, 9, 16, 32, 8),
> + AD4691_CHANNEL(10, 10, 16, 32, 8),
> + AD4691_CHANNEL(11, 11, 16, 32, 8),
> + AD4691_CHANNEL(12, 12, 16, 32, 8),
> + AD4691_CHANNEL(13, 13, 16, 32, 8),
> + AD4691_CHANNEL(14, 14, 16, 32, 8),
> + AD4691_CHANNEL(15, 15, 16, 32, 8)
> };
>
> static const struct iio_chan_spec ad4693_manual_channels[] = {
> - AD4691_CHANNEL(0, 0, 16, 24, 8),
> - AD4691_CHANNEL(1, 1, 16, 24, 8),
> - AD4691_CHANNEL(2, 2, 16, 24, 8),
> - AD4691_CHANNEL(3, 3, 16, 24, 8),
> - AD4691_CHANNEL(4, 4, 16, 24, 8),
> - AD4691_CHANNEL(5, 5, 16, 24, 8),
> - AD4691_CHANNEL(6, 6, 16, 24, 8),
> - AD4691_CHANNEL(7, 7, 16, 24, 8)
> + AD4691_CHANNEL(0, 0, 16, 32, 8),
> + AD4691_CHANNEL(1, 1, 16, 32, 8),
> + AD4691_CHANNEL(2, 2, 16, 32, 8),
> + AD4691_CHANNEL(3, 3, 16, 32, 8),
> + AD4691_CHANNEL(4, 4, 16, 32, 8),
> + AD4691_CHANNEL(5, 5, 16, 32, 8),
> + AD4691_CHANNEL(6, 6, 16, 32, 8),
> + AD4691_CHANNEL(7, 7, 16, 32, 8)
> };
>
> static const struct ad4691_chip_info ad4691_ad4691 = {
> @@ -228,6 +234,8 @@ struct ad4691_state {
> unsigned long ref_clk_rate;
> struct pwm_device *conv_trigger;
>
> + struct iio_trigger *trig;
> +
> enum ad4691_adc_mode adc_mode;
>
> int vref_uV;
> @@ -238,6 +246,21 @@ struct ad4691_state {
> * atomicity of consecutive SPI operations.
> */
> struct mutex lock;
> +
> + /* hrtimer for MANUAL_MODE triggered buffer (non-offload) */
> + struct hrtimer sampling_timer;
> +
> + /*
> + * DMA (thus cache coherency maintenance) may require the
> + * transfer buffers to live in their own cache lines.
> + */
> + unsigned char rx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN);
> + unsigned char tx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)];
> + /* Scan buffer: one slot per channel (u32) plus timestamp */
> + struct {
> + u32 vals[16];
> + s64 ts __aligned(8);
> + } scan __aligned(IIO_DMA_MINALIGN);
> };
>
> static void ad4691_disable_pwm(void *data)
> @@ -399,6 +422,28 @@ static const struct regmap_config ad4691_regmap_config = {
> .cache_type = REGCACHE_MAPLE,
> };
>
> +static int ad4691_transfer(struct ad4691_state *st, int command,
> + unsigned int *val)
> +{
> + struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
> + struct spi_transfer xfer = {
> + .tx_buf = st->tx_data,
> + .rx_buf = st->rx_data,
> + .len = 3,
> + };
> + int ret;
> +
> + memcpy(st->tx_data, &command, 3);
> +
> + ret = spi_sync_transfer(spi, &xfer, 1);
> + if (ret)
> + return ret;
> +
> + *val = get_unaligned_be24(st->rx_data);
> +
> + return 0;
> +}
> +
> static int ad4691_get_sampling_freq(struct ad4691_state *st)
> {
> if (st->adc_mode == AD4691_MANUAL_MODE)
> @@ -483,6 +528,18 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
> return __ad4691_set_sampling_freq(st, freq);
> }
>
> +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
> +{
> + struct pwm_state conv_state = { };
> +
> + conv_state.period = st->cnv_period;
> + conv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
> + conv_state.polarity = PWM_POLARITY_NORMAL;
> + conv_state.enabled = enable;
> +
> + return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
> +}
> +
> static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan, int *val)
> {
> @@ -594,6 +651,235 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> return regmap_write(st->regmap, reg, writeval);
> }
>
> +/*
> + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
> + *
> + * Configures the ADC hardware registers for the mode selected at probe
> + * (CNV_CLOCK or MANUAL). Called from buffer postenable before starting
> + * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
> + */
> +static int ad4691_enter_conversion_mode(struct ad4691_state *st)
> +{
> + int ret;
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE)
> + return regmap_write(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_DEVICE_MANUAL);
> +
> + ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_CNV_CLOCK_MODE);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
> + AD4691_DATA_READY);
> +}
> +
> +/*
> + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
> + *
> + * Called from buffer postdisable/predisable to restore the chip to the
> + * idle state used by read_raw. Clears the sequencer and resets state.
> + */
> +static int ad4691_exit_conversion_mode(struct ad4691_state *st)
> +{
> + int ret;
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + ret = regmap_write(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_DEVICE_REGISTER);
> + if (ret)
> + return ret;
> + }
> +
> + ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
> + if (ret)
> + return ret;
> +
> + /* Restore GP0 to ADC_BUSY for AUTONOMOUS idle (enter set it to DATA_READY) */
> + ret = regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + AD4691_SEQ_ALL_CHANNELS_OFF);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> +}
> +
> +static int ad4691_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + u16 mask = ~(*indio_dev->active_scan_mask);
> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
> + int n_active = hweight_long(*indio_dev->active_scan_mask);
> + unsigned int bit;
> + int ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
We can just make two different struct iio_buffer_setup_ops so we don't have
to put these ifs in each function. The mode can't change at runtime.
> + u64 min_period_ns;
> +
> + /* N+1 transfers needed for N channels, with 50% overhead */
> + min_period_ns = div64_u64((u64)(n_active + 1) * AD4691_BITS_PER_XFER *
> + NSEC_PER_SEC * 3,
> + spi->max_speed_hz * 2);
> +
> + if (ktime_to_ns(st->sampling_period) < min_period_ns) {
> + dev_err(dev,
> + "Sampling period %lld ns too short for %d channels. Min: %llu ns\n",
> + ktime_to_ns(st->sampling_period), n_active,
> + min_period_ns);
> + return -EINVAL;
> + }
> +
> + hrtimer_start(&st->sampling_timer, st->sampling_period,
> + HRTIMER_MODE_REL);
> + return 0;
> + }
> +
> + /* CNV_CLOCK_MODE: configure sequencer and start PWM */
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG, acc_mask, 2);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + *indio_dev->active_scan_mask);
> + if (ret)
> + return ret;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
> + AD4691_ACC_COUNT_VAL);
> + if (ret)
> + return ret;
> + }
> +
> + return ad4691_sampling_enable(st, true);
> +}
> +
> +static int ad4691_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE)
> + hrtimer_cancel_wait_running(&st->sampling_timer);
> + else
> + ad4691_sampling_enable(st, false);
> +
> + return ad4691_exit_conversion_mode(st);
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
> + .postenable = &ad4691_buffer_postenable,
> + .postdisable = &ad4691_buffer_postdisable,
I feel like I said this already (maybe it was a different series).
These aren't symetric. It needs to be postenable/predisable or
preenable/postdiable. Otherwise error unwinding doesn't work correclty.
> +};
> +
> +static irqreturn_t ad4691_irq(int irq, void *private)
> +{
> + struct iio_dev *indio_dev = private;
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + /*
> + * DATA_READY has asserted: stop conversions before reading so the
> + * accumulator does not continue sampling while the trigger handler
> + * processes the data. Then fire the IIO trigger to push the sample
> + * to the buffer.
> + */
> + ad4691_sampling_enable(st, false);
> + iio_trigger_poll(st->trig);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static enum hrtimer_restart ad4691_sampling_timer_handler(struct hrtimer *timer)
> +{
> + struct ad4691_state *st = container_of(timer, struct ad4691_state,
> + sampling_timer);
> +
> + iio_trigger_poll(st->trig);
> + hrtimer_forward_now(timer, st->sampling_period);
> +
> + return HRTIMER_RESTART;
> +}
> +
> +static const struct iio_trigger_ops ad4691_trigger_ops = {
> + .validate_device = iio_trigger_validate_own_device,
> +};
> +
> +static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int val, i;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + unsigned int prev_val;
> + int prev_chan = -1;
> +
> + /*
> + * MANUAL_MODE with CNV tied to CS: each transfer triggers a
> + * conversion AND returns the previous conversion's result.
> + * First transfer returns garbage, so we do N+1 transfers for
> + * N channels. Collect all results into scan.vals[], then push
> + * the complete scan once.
> + */
> + iio_for_each_active_channel(indio_dev, i) {
> + ret = ad4691_transfer(st, AD4691_ADC_CHAN(i), &val);
It would be more efficient to set up a single SPI message (in buffer enable
callback) that reads all channels at once rather than doing multiple SPI
messages.
> + if (ret)
> + goto done;
> +
> + if (prev_chan >= 0)
> + st->scan.vals[prev_chan] = prev_val;
> + prev_val = val;
> + prev_chan = i;
> + }
> +
> + /* Final NOOP transfer to retrieve last channel's result */
> + ret = ad4691_transfer(st, AD4691_NOOP, &val);
> + if (ret)
> + goto done;
> +
> + st->scan.vals[prev_chan] = val;
> + } else {
> + iio_for_each_active_channel(indio_dev, i) {
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(i), &val);
Same here.
> + if (ret)
> + goto done;
> +
> + st->scan.vals[i] = val;
> + }
> +
> + regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
> + /* Restart conversions for the next trigger cycle. */
> + ad4691_sampling_enable(st, true);
> + }
> +
> + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> + pf->timestamp);
I don't recall seeing timestamp channels.
> +
> +done:
> + iio_trigger_notify_done(indio_dev->trig);
> + return IRQ_HANDLED;
> +}
> +
> static const struct iio_info ad4691_info = {
> .read_raw = &ad4691_read_raw,
> .write_raw = &ad4691_write_raw,
> @@ -624,6 +910,7 @@ static int ad4691_reset(struct ad4691_state *st)
> static int ad4691_config(struct ad4691_state *st)
> {
> struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> enum ad4691_ref_ctrl ref_val;
> unsigned int reg_val;
> int ret;
> @@ -644,7 +931,7 @@ static int ad4691_config(struct ad4691_state *st)
> st->adc_mode = AD4691_MANUAL_MODE;
> st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> - to_spi_device(dev)->max_speed_hz)));
> + spi->max_speed_hz)));
> }
>
> /* Perform a state reset on the channels at start-up. */
> @@ -693,6 +980,65 @@ static int ad4691_config(struct ad4691_state *st)
> return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
> }
>
> +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> + struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + int irq, ret;
> +
> + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> + indio_dev->name,
> + iio_device_id(indio_dev));
> + if (!st->trig)
> + return -ENOMEM;
> +
> + st->trig->ops = &ad4691_trigger_ops;
> + iio_trigger_set_drvdata(st->trig, st);
> +
> + ret = devm_iio_trigger_register(dev, st->trig);
> + if (ret)
> + return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> +
> + indio_dev->trig = iio_trigger_get(st->trig);
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + /*
> + * No DATA_READY signal in MANUAL_MODE; CNV is tied to CS so
> + * conversions start with each SPI transfer. Use an hrtimer to
> + * schedule periodic reads.
> + */
> + hrtimer_setup(&st->sampling_timer, ad4691_sampling_timer_handler,
> + CLOCK_MONOTONIC, HRTIMER_MODE_REL);
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(
> + NSEC_PER_SEC,
> + AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> + spi->max_speed_hz)));
> + } else {
> + /*
> + * DATA_READY asserts at end-of-conversion. The IRQ handler
> + * stops conversions and fires the IIO trigger so the trigger
> + * handler can read and push the sample to the buffer.
> + */
> + irq = fwnode_irq_get(dev_fwnode(dev), 0);
> + if (irq < 0)
> + return dev_err_probe(dev, irq,
> + "failed to get DATA_READY interrupt\n");
There is no such line as DATA_READY. This would be GP0.
> +
> + ret = devm_request_threaded_irq(dev, irq, NULL,
> + &ad4691_irq,
> + IRQF_ONESHOT,
> + indio_dev->name, indio_dev);
Not many drivers do this, but I think it is good practice to use the
IRQF_NO_AUTOEN flag in IIO and only enable the interrupt when it has
been fully configured during the buffer enable.
> + if (ret)
> + return ret;
> + }
> +
> + return devm_iio_triggered_buffer_setup(dev, indio_dev,
> + &iio_pollfunc_store_time,
> + &ad4691_trigger_handler,
> + &ad4691_buffer_setup_ops);
> +}
> +
> static int ad4691_probe(struct spi_device *spi)
> {
> struct device *dev = &spi->dev;
> @@ -736,6 +1082,10 @@ static int ad4691_probe(struct spi_device *spi)
> st->chip->manual_channels : st->chip->channels;
> indio_dev->num_channels = st->chip->num_channels;
>
> + ret = ad4691_setup_triggered_buffer(indio_dev, st);
> + if (ret)
> + return ret;
> +
> return devm_iio_device_register(dev, indio_dev);
> }
>
>
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-14 18:37 ` David Lechner
@ 2026-03-16 13:22 ` Sabau, Radu bogdan
2026-03-16 15:37 ` David Lechner
0 siblings, 1 reply; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 13:22 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, March 14, 2026 8:38 PM
...
> > Both operating modes share a single IIO trigger and trigger handler.
> > The handler builds a complete scan — one u32 slot per channel at its
> > scan_index position, followed by a timestamp — and pushes it to the
> > IIO buffer in a single iio_push_to_buffers_with_ts() call.
>
> It would really help here to see some timing diagrams to know if we
> are implementing this right.
>
> For example, it isn't clear that in clocked mode if CNV triggers a
> single conversion in the sequencer (i.e. IIO_SAMP_FREQ should be
> info_mask_separate) or if it triggers the sequence (i.e. IIO_SAMP_FREQ
> should be info_mask_shared_by_all).
>
The CNV triggers the sequence and IIO_SAMP_FREQ is info_mask_shared_by_all.
As per datasheet page 31 (Accumulator Section), when each accumulator
receives a sample, the ACC_COUNT is increased. In clocked mode we
are setting the ACC_COUNT limit to 1, therefore having one sample per
channel (no oversampling as discussed in previous versions). So each
period of the CNV PWM is respective to one sample of a channel.
> >
> > For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
> > IRQ handler stops conversions and fires the IIO trigger; the trigger
> > handler reads accumulated results from the AVG_IN registers via regmap
> > and restarts conversions for the next cycle.
>
> This seems OK, but I would kind of would expect that PWM as CNV to
> only be used for SPI offloading and not without SPI offloading.
>
> The ADC also has an internal oscillator, so it seems like it would
> be more useful to use that as a conversion trigger rather than
> requiring external hardware.
>
This CNV is used in triggered buffer mode as well, not only in offload.
In this mode, CNV replaces the internal oscillator so CNV is the
conversion trigger (offload or not), which also introduces the advantage
of having a more flexible sampling rate.
> >
> > For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
> > so conversions are triggered by CS assertion rather than by a dedicated
> > pin. The standard iio-trig-hrtimer module is not used because the timer
> > period must be derived from the SPI clock rate and the number of active
> > channels: the pipelined protocol requires N+1 SPI transfers per scan
> > (the first result is garbage and is discarded), so the minimum period
> > depends on both the SPI speed and the live channel count at buffer
> > enable time. A driver-private hrtimer whose period is recomputed by
> > buffer_postenable is simpler and avoids requiring the user to configure
> > an external trigger with the correct hardware-derived period.
>
> I'm not really following the argument here. It is quite normal that if
> an hrtimer trigger is set too fast then samples will be missed. So I don't
> see why we wouldn't be able to use it here. This is why we usually have
> a timestamp channel, so we can know roughly when the conversion actually
> took place.
>
My bad here in this case. I thought no samples were wanted missing in a case
like this. I will try and use iio-trig-hrtimer on the next version.
> >
> > Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
> > channel slots in the scan buffer are uniformly sized regardless of the
> > SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
>
> I also don't understand why we are including the status bits in manual
> mode but not in CNV clock mode.
>
In Manual Mode, status bits are received through SPI, because that's how
the hardware works. However, they are masked by the driver and thus not used.
> >
> > Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> > ---
> > drivers/iio/adc/Kconfig | 2 +
...
> > +
> > + /*
> > + * MANUAL_MODE with CNV tied to CS: each transfer triggers
> a
> > + * conversion AND returns the previous conversion's result.
> > + * First transfer returns garbage, so we do N+1 transfers for
> > + * N channels. Collect all results into scan.vals[], then push
> > + * the complete scan once.
> > + */
> > + iio_for_each_active_channel(indio_dev, i) {
> > + ret = ad4691_transfer(st, AD4691_ADC_CHAN(i),
> &val);
>
> It would be more efficient to set up a single SPI message (in buffer enable
> callback) that reads all channels at once rather than doing multiple SPI
> messages.
>
Similar to what offload does, of course. Thanks for this.
> > + if (ret)
> > + goto done;
^ permalink raw reply [flat|nested] 37+ messages in thread* Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-16 13:22 ` Sabau, Radu bogdan
@ 2026-03-16 15:37 ` David Lechner
2026-03-16 15:56 ` Sabau, Radu bogdan
0 siblings, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-16 15:37 UTC (permalink / raw)
To: Sabau, Radu bogdan, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
On 3/16/26 8:22 AM, Sabau, Radu bogdan wrote:
>
>
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Saturday, March 14, 2026 8:38 PM
>
> ...
>
>>> Both operating modes share a single IIO trigger and trigger handler.
>>> The handler builds a complete scan — one u32 slot per channel at its
>>> scan_index position, followed by a timestamp — and pushes it to the
>>> IIO buffer in a single iio_push_to_buffers_with_ts() call.
>>
>> It would really help here to see some timing diagrams to know if we
>> are implementing this right.
>>
>> For example, it isn't clear that in clocked mode if CNV triggers a
>> single conversion in the sequencer (i.e. IIO_SAMP_FREQ should be
>> info_mask_separate) or if it triggers the sequence (i.e. IIO_SAMP_FREQ
>> should be info_mask_shared_by_all).
>>
>
> The CNV triggers the sequence and IIO_SAMP_FREQ is info_mask_shared_by_all.
>
> As per datasheet page 31 (Accumulator Section), when each accumulator
> receives a sample, the ACC_COUNT is increased. In clocked mode we
> are setting the ACC_COUNT limit to 1, therefore having one sample per
> channel (no oversampling as discussed in previous versions). So each
> period of the CNV PWM is respective to one sample of a channel.
Assuming that "a" channel means "one" channel...
In this case then sampling_frequency should be per channel (separate).
A sampling_frequency that is shared_by_all means that each period of
CNV should trigger one sample each for _all_ channels. In other words,
the sampling frequency gives one complete set of samples for all enabled
channels pushed to the buffer.
>
>>>
>>> For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
>>> IRQ handler stops conversions and fires the IIO trigger; the trigger
>>> handler reads accumulated results from the AVG_IN registers via regmap
>>> and restarts conversions for the next cycle.
>>
>> This seems OK, but I would kind of would expect that PWM as CNV to
>> only be used for SPI offloading and not without SPI offloading.
>>
>> The ADC also has an internal oscillator, so it seems like it would
>> be more useful to use that as a conversion trigger rather than
>> requiring external hardware.
>>
>
> This CNV is used in triggered buffer mode as well, not only in offload.
> In this mode, CNV replaces the internal oscillator so CNV is the
> conversion trigger (offload or not), which also introduces the advantage
> of having a more flexible sampling rate.
Yes, I understand that. We just never did that for any other chip yet.
Usually, we would just use the internal oscillator on the chip instead
for this sort of thing. But if you have applications engineers telling
you that this is a setup they want to support, then we can do it.
>>>
>>> Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
>>> channel slots in the scan buffer are uniformly sized regardless of the
>>> SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
>>
>> I also don't understand why we are including the status bits in manual
>> mode but not in CNV clock mode.
>>
>
> In Manual Mode, status bits are received through SPI, because that's how
> the hardware works. However, they are masked by the driver and thus not used.
Usually there are registers to turn status on and off independently. If
there isn't it could be helpful to add some comments in the code to
remind us.
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-16 15:37 ` David Lechner
@ 2026-03-16 15:56 ` Sabau, Radu bogdan
2026-03-16 16:44 ` David Lechner
0 siblings, 1 reply; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 15:56 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, March 16, 2026 5:38 PM
> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>; Lars-Peter Clausen
> <lars@metafoo.de>; Hennerich, Michael <Michael.Hennerich@analog.com>;
> Jonathan Cameron <jic23@kernel.org>; Sa, Nuno <Nuno.Sa@analog.com>;
> Andy Shevchenko <andy@kernel.org>; Rob Herring <robh@kernel.org>;
> Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
> <conor+dt@kernel.org>; Uwe Kleine-König <ukleinek@kernel.org>; Liam
> Girdwood <lgirdwood@gmail.com>; Mark Brown <broonie@kernel.org>; Linus
> Walleij <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>; Philipp
> Zabel <p.zabel@pengutronix.de>
> Cc: linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-pwm@vger.kernel.org; linux-
> gpio@vger.kernel.org
> Subject: Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
>
> [External]
>
> On 3/16/26 8:22 AM, Sabau, Radu bogdan wrote:
> >
> >
> >> -----Original Message-----
> >> From: David Lechner <dlechner@baylibre.com>
> >> Sent: Saturday, March 14, 2026 8:38 PM
> >
> > ...
> >
> >>> Both operating modes share a single IIO trigger and trigger handler.
> >>> The handler builds a complete scan — one u32 slot per channel at its
> >>> scan_index position, followed by a timestamp — and pushes it to the
> >>> IIO buffer in a single iio_push_to_buffers_with_ts() call.
> >>
> >> It would really help here to see some timing diagrams to know if we
> >> are implementing this right.
> >>
> >> For example, it isn't clear that in clocked mode if CNV triggers a
> >> single conversion in the sequencer (i.e. IIO_SAMP_FREQ should be
> >> info_mask_separate) or if it triggers the sequence (i.e. IIO_SAMP_FREQ
> >> should be info_mask_shared_by_all).
> >>
> >
> > The CNV triggers the sequence and IIO_SAMP_FREQ is
> info_mask_shared_by_all.
> >
> > As per datasheet page 31 (Accumulator Section), when each accumulator
> > receives a sample, the ACC_COUNT is increased. In clocked mode we
> > are setting the ACC_COUNT limit to 1, therefore having one sample per
> > channel (no oversampling as discussed in previous versions). So each
> > period of the CNV PWM is respective to one sample of a channel.
>
> Assuming that "a" channel means "one" channel...
>
> In this case then sampling_frequency should be per channel (separate).
>
> A sampling_frequency that is shared_by_all means that each period of
> CNV should trigger one sample each for _all_ channels. In other words,
> the sampling frequency gives one complete set of samples for all enabled
> channels pushed to the buffer.
>
Oh, ok then, will have them separate. I assumed that since the PWM period
is constant with each pulse, then the sampling rate will be the same for
each channel, thus having them as shared_by_all, but I assume you are
right about this in this case, I will have them as separate in this case, the
update will happen in the previous patch upon next version.
> >
> >>>
> >>> For CNV Clock Mode the GP0 pin is configured as DATA_READY output.
> The
> >>> IRQ handler stops conversions and fires the IIO trigger; the trigger
> >>> handler reads accumulated results from the AVG_IN registers via regmap
> >>> and restarts conversions for the next cycle.
> >>
> >> This seems OK, but I would kind of would expect that PWM as CNV to
> >> only be used for SPI offloading and not without SPI offloading.
> >>
> >> The ADC also has an internal oscillator, so it seems like it would
> >> be more useful to use that as a conversion trigger rather than
> >> requiring external hardware.
> >>
> >
> > This CNV is used in triggered buffer mode as well, not only in offload.
> > In this mode, CNV replaces the internal oscillator so CNV is the
> > conversion trigger (offload or not), which also introduces the advantage
> > of having a more flexible sampling rate.
>
> Yes, I understand that. We just never did that for any other chip yet.
> Usually, we would just use the internal oscillator on the chip instead
> for this sort of thing. But if you have applications engineers telling
> you that this is a setup they want to support, then we can do it.
I see, and yes, this is something that needs support. Appreciate the feedback!
>
> >>>
> >>> Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
> >>> channel slots in the scan buffer are uniformly sized regardless of the
> >>> SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
> >>
> >> I also don't understand why we are including the status bits in manual
> >> mode but not in CNV clock mode.
> >>
> >
> > In Manual Mode, status bits are received through SPI, because that's how
> > the hardware works. However, they are masked by the driver and thus not
> used.
>
> Usually there are registers to turn status on and off independently. If
> there isn't it could be helpful to add some comments in the code to
> remind us.
You are right, there is a register to turn this off, and they are actually disabled
by default, thus 16 storage bits can be used. Thanks for this!
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-16 15:56 ` Sabau, Radu bogdan
@ 2026-03-16 16:44 ` David Lechner
2026-03-17 9:27 ` Sabau, Radu bogdan
0 siblings, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-16 16:44 UTC (permalink / raw)
To: Sabau, Radu bogdan, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
On 3/16/26 10:56 AM, Sabau, Radu bogdan wrote:
>
>
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Monday, March 16, 2026 5:38 PM
>> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>; Lars-Peter Clausen
>> <lars@metafoo.de>; Hennerich, Michael <Michael.Hennerich@analog.com>;
>> Jonathan Cameron <jic23@kernel.org>; Sa, Nuno <Nuno.Sa@analog.com>;
>> Andy Shevchenko <andy@kernel.org>; Rob Herring <robh@kernel.org>;
>> Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
>> <conor+dt@kernel.org>; Uwe Kleine-König <ukleinek@kernel.org>; Liam
>> Girdwood <lgirdwood@gmail.com>; Mark Brown <broonie@kernel.org>; Linus
>> Walleij <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>; Philipp
>> Zabel <p.zabel@pengutronix.de>
>> Cc: linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-
>> kernel@vger.kernel.org; linux-pwm@vger.kernel.org; linux-
>> gpio@vger.kernel.org
>> Subject: Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
>>
>> [External]
>>
>> On 3/16/26 8:22 AM, Sabau, Radu bogdan wrote:
>>>
>>>
>>>> -----Original Message-----
>>>> From: David Lechner <dlechner@baylibre.com>
>>>> Sent: Saturday, March 14, 2026 8:38 PM
>>>
>>> ...
>>>
>>>>> Both operating modes share a single IIO trigger and trigger handler.
>>>>> The handler builds a complete scan — one u32 slot per channel at its
>>>>> scan_index position, followed by a timestamp — and pushes it to the
>>>>> IIO buffer in a single iio_push_to_buffers_with_ts() call.
>>>>
>>>> It would really help here to see some timing diagrams to know if we
>>>> are implementing this right.
>>>>
>>>> For example, it isn't clear that in clocked mode if CNV triggers a
>>>> single conversion in the sequencer (i.e. IIO_SAMP_FREQ should be
>>>> info_mask_separate) or if it triggers the sequence (i.e. IIO_SAMP_FREQ
>>>> should be info_mask_shared_by_all).
>>>>
>>>
>>> The CNV triggers the sequence and IIO_SAMP_FREQ is
>> info_mask_shared_by_all.
>>>
>>> As per datasheet page 31 (Accumulator Section), when each accumulator
>>> receives a sample, the ACC_COUNT is increased. In clocked mode we
>>> are setting the ACC_COUNT limit to 1, therefore having one sample per
>>> channel (no oversampling as discussed in previous versions). So each
>>> period of the CNV PWM is respective to one sample of a channel.
>>
>> Assuming that "a" channel means "one" channel...
>>
>> In this case then sampling_frequency should be per channel (separate).
>>
>> A sampling_frequency that is shared_by_all means that each period of
>> CNV should trigger one sample each for _all_ channels. In other words,
>> the sampling frequency gives one complete set of samples for all enabled
>> channels pushed to the buffer.
>>
>
> Oh, ok then, will have them separate. I assumed that since the PWM period
> is constant with each pulse, then the sampling rate will be the same for
> each channel, thus having them as shared_by_all, but I assume you are
> right about this in this case, I will have them as separate in this case, the
> update will happen in the previous patch upon next version.
>
Does the sampling stop after one "burst" (reading each enabled channel once)?
If yes, then what controls when the next set of samples starts?
Looking at Figure 63 in the datasheet for CNV Clock mode, it looks like it
depends entirely on how long the SPI message takes. So the actual sample rate
is going to be quite random instead of the sum of each channel as the IIO ABI
says it should. It seems a waste of the PWM to do it this way since we end
up with a random sample rate.
It seems to me like the CNV Burst mode would actually be better suited to
how IIO usually does things. In this case, the PWM frequency would control
the effective sample rate (one PWM pulse triggers one complete set of
samples) and the internal oscillator controls triggering each individual
conversion.
In this setup, we would still have the info_mask_separate IIO_SAMP_FREQ,
but it would control the internal oscillator. Then we would have a separate
buffer0/sampling_frequency attribute that controlled the PWM frequency.
Then, as long as the PWM frequency was slow enough that the SPI message
can be done, it can make samples with almost no jitter. This is why I would
expect PWM to almost always be used with SPI offload though, otherwise
it has to be quite slow compared to what the chip is capable of.
I suppose the CNV Clock mode could also be made to work with the typical
IIO trigger so that we could control the actual sample rate. It just
wouldn't be as precise.
If you have some examples of how this chip should actually be used in the
real world, that could help pick what is the right thing to do here.
^ permalink raw reply [flat|nested] 37+ messages in thread
* RE: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-16 16:44 ` David Lechner
@ 2026-03-17 9:27 ` Sabau, Radu bogdan
0 siblings, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-17 9:27 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, March 16, 2026 6:45 PM
> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>; Lars-Peter Clausen
> <lars@metafoo.de>; Hennerich, Michael <Michael.Hennerich@analog.com>;
> Jonathan Cameron <jic23@kernel.org>; Sa, Nuno <Nuno.Sa@analog.com>;
> Andy Shevchenko <andy@kernel.org>; Rob Herring <robh@kernel.org>;
> Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
> <conor+dt@kernel.org>; Uwe Kleine-König <ukleinek@kernel.org>; Liam
> Girdwood <lgirdwood@gmail.com>; Mark Brown <broonie@kernel.org>; Linus
> Walleij <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>; Philipp
> Zabel <p.zabel@pengutronix.de>
> Cc: linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-
> kernel@vger.kernel.org; linux-pwm@vger.kernel.org; linux-
> gpio@vger.kernel.org
> Subject: Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
>
> [External]
>
> On 3/16/26 10:56 AM, Sabau, Radu bogdan wrote:
> >
> >
> >> -----Original Message-----
> >> From: David Lechner <dlechner@baylibre.com>
> >> Sent: Monday, March 16, 2026 5:38 PM
> >> To: Sabau, Radu bogdan <Radu.Sabau@analog.com>; Lars-Peter Clausen
> >> <lars@metafoo.de>; Hennerich, Michael
> <Michael.Hennerich@analog.com>;
> >> Jonathan Cameron <jic23@kernel.org>; Sa, Nuno <Nuno.Sa@analog.com>;
> >> Andy Shevchenko <andy@kernel.org>; Rob Herring <robh@kernel.org>;
> >> Krzysztof Kozlowski <krzk+dt@kernel.org>; Conor Dooley
> >> <conor+dt@kernel.org>; Uwe Kleine-König <ukleinek@kernel.org>; Liam
> >> Girdwood <lgirdwood@gmail.com>; Mark Brown <broonie@kernel.org>;
> Linus
> >> Walleij <linusw@kernel.org>; Bartosz Golaszewski <brgl@kernel.org>;
> Philipp
> >> Zabel <p.zabel@pengutronix.de>
> >> Cc: linux-iio@vger.kernel.org; devicetree@vger.kernel.org; linux-
> >> kernel@vger.kernel.org; linux-pwm@vger.kernel.org; linux-
> >> gpio@vger.kernel.org
> >> Subject: Re: [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support
> >>
> >> [External]
> >>
> >> On 3/16/26 8:22 AM, Sabau, Radu bogdan wrote:
> >>>
> >>>
> >>>> -----Original Message-----
> >>>> From: David Lechner <dlechner@baylibre.com>
> >>>> Sent: Saturday, March 14, 2026 8:38 PM
> >>>
> >>> ...
> >>>
> >>>>> Both operating modes share a single IIO trigger and trigger handler.
> >>>>> The handler builds a complete scan — one u32 slot per channel at its
> >>>>> scan_index position, followed by a timestamp — and pushes it to the
> >>>>> IIO buffer in a single iio_push_to_buffers_with_ts() call.
> >>>>
> >>>> It would really help here to see some timing diagrams to know if we
> >>>> are implementing this right.
> >>>>
> >>>> For example, it isn't clear that in clocked mode if CNV triggers a
> >>>> single conversion in the sequencer (i.e. IIO_SAMP_FREQ should be
> >>>> info_mask_separate) or if it triggers the sequence (i.e. IIO_SAMP_FREQ
> >>>> should be info_mask_shared_by_all).
> >>>>
> >>>
> >>> The CNV triggers the sequence and IIO_SAMP_FREQ is
> >> info_mask_shared_by_all.
> >>>
> >>> As per datasheet page 31 (Accumulator Section), when each accumulator
> >>> receives a sample, the ACC_COUNT is increased. In clocked mode we
> >>> are setting the ACC_COUNT limit to 1, therefore having one sample per
> >>> channel (no oversampling as discussed in previous versions). So each
> >>> period of the CNV PWM is respective to one sample of a channel.
> >>
> >> Assuming that "a" channel means "one" channel...
> >>
> >> In this case then sampling_frequency should be per channel (separate).
> >>
> >> A sampling_frequency that is shared_by_all means that each period of
> >> CNV should trigger one sample each for _all_ channels. In other words,
> >> the sampling frequency gives one complete set of samples for all enabled
> >> channels pushed to the buffer.
> >>
> >
> > Oh, ok then, will have them separate. I assumed that since the PWM period
> > is constant with each pulse, then the sampling rate will be the same for
> > each channel, thus having them as shared_by_all, but I assume you are
> > right about this in this case, I will have them as separate in this case, the
> > update will happen in the previous patch upon next version.
> >
> Does the sampling stop after one "burst" (reading each enabled channel
> once)?
>
> If yes, then what controls when the next set of samples starts?
>
> Looking at Figure 63 in the datasheet for CNV Clock mode, it looks like it
> depends entirely on how long the SPI message takes. So the actual sample rate
> is going to be quite random instead of the sum of each channel as the IIO ABI
> says it should. It seems a waste of the PWM to do it this way since we end
> up with a random sample rate.
>
> It seems to me like the CNV Burst mode would actually be better suited to
> how IIO usually does things. In this case, the PWM frequency would control
> the effective sample rate (one PWM pulse triggers one complete set of
> samples) and the internal oscillator controls triggering each individual
> conversion.
>
> In this setup, we would still have the info_mask_separate IIO_SAMP_FREQ,
> but it would control the internal oscillator. Then we would have a separate
> buffer0/sampling_frequency attribute that controlled the PWM frequency.
>
> Then, as long as the PWM frequency was slow enough that the SPI message
> can be done, it can make samples with almost no jitter. This is why I would
> expect PWM to almost always be used with SPI offload though, otherwise
> it has to be quite slow compared to what the chip is capable of.
>
> I suppose the CNV Clock mode could also be made to work with the typical
> IIO trigger so that we could control the actual sample rate. It just
> wouldn't be as precise.
>
>
>
> If you have some examples of how this chip should actually be used in the
> real world, that could help pick what is the right thing to do here.
You are indeed right. For CNV Clock Mode, the actual sample rate is
random and what is exposed as 'sample rate' is not actually according
to what the IIO ABI says, nor respecting to what already exists in the
kernel. I connected a Logic Analyzer and the GP0 interrupt is not actually
constant in this case, but for CNV Burst Mode it is (I have used first version
driver from this series).
Also, I think your suggestion that the sampling frequency of the channels
should refer to the internal oscillator and the sampling frequency of the
buffer to be the actual PWM frequency of CNV is a very good one.
This way CNV Burst Mode complies with the ABI and works as expected
with both typical IIO trigger and offload mode. More than this setting
the ACC_COUNT to 1 (as it is right now) also avoids oversampling although
CNV Burst is used, so I think switching from CNV Clock to CNV Burst would
be a really good mode. Thanks for this!
I will address the rest of the comments and also change the CNV Clock Mode
for CNV Burst Mode.
^ permalink raw reply [flat|nested] 37+ messages in thread
* [PATCH v3 4/4] iio: adc: ad4691: add SPI offload support
2026-03-13 10:07 [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
` (2 preceding siblings ...)
2026-03-13 10:07 ` [PATCH v3 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
@ 2026-03-13 10:07 ` Radu Sabau via B4 Relay
2026-03-14 19:37 ` David Lechner
2026-03-13 11:14 ` [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Andy Shevchenko
4 siblings, 1 reply; 37+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-13 10:07 UTC (permalink / raw)
To: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add SPI offload support to enable DMA-based, CPU-independent data
acquisition using the SPI Engine offload framework.
When an SPI offload is available (devm_spi_offload_get() succeeds),
the driver registers a DMA engine IIO buffer and uses dedicated buffer
setup operations. If no offload is available the existing software
triggered buffer path is used unchanged.
Both CNV Clock Mode and Manual Mode support offload, but use different
trigger mechanisms:
CNV Clock Mode: the SPI Engine is triggered by the ADC's DATA_READY
signal on GP0. For this mode the driver acts as both an SPI offload
consumer (DMA RX stream, message optimization) and a trigger source
provider: it registers the GP0/DATA_READY output via
devm_spi_offload_trigger_register() so the offload framework can
match the '#trigger-source-cells' phandle from the device tree and
automatically fire the SPI Engine DMA transfer at end-of-conversion.
The pre-built SPI message reads all active channels from the AVG_IN
accumulator registers (2-byte address + 2-byte data per channel,
one 4-byte transfer each) followed by a state reset word to re-arm
the accumulator for the next cycle.
Manual Mode: the SPI Engine is triggered by a periodic trigger at
the configured sampling frequency. The pre-built SPI message uses
the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued
for N active channels (the first result is discarded as garbage from
the pipeline flush) and the remaining N results are captured by DMA.
All offload transfers use 32-bit frames (bits_per_word=32, len=4) for
DMA word alignment. In Manual Mode the 4-byte DMA word layout is
[dummy(8), data_hi(8), data_lo(8), extra(8)]; the channel scan type
storagebits=32, shift=8, realbits=16 correctly extracts the 16-bit
ADC result from the middle two bytes.
Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad4691.c | 397 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 391 insertions(+), 7 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index d498f16c0816..93f090e9a562 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -144,6 +144,7 @@ config AD4691
depends on SPI
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
+ select IIO_BUFFER_DMAENGINE
select REGMAP
help
Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index de2208395b21..ad9eaa94727e 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -9,6 +9,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/reset.h>
#include <linux/hrtimer.h>
@@ -21,11 +22,15 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
#include <linux/util_macros.h>
#include <linux/units.h>
#include <linux/unaligned.h>
#include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dma.h>
+#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger.h>
@@ -46,6 +51,7 @@
*/
#define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
#define AD4691_BITS_PER_XFER 24
+#define AD4691_OFFLOAD_BITS_PER_WORD 32
#define AD4691_CNV_DUTY_CYCLE_NS 380
#define AD4691_MAX_CONV_PERIOD_US 800
@@ -92,6 +98,11 @@
#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
+/* SPI offload 32-bit message byte-field masks (MSB = first transmitted) */
+#define AD4691_MSG_ADDR_HI GENMASK(31, 24)
+#define AD4691_MSG_ADDR_LO GENMASK(23, 16)
+#define AD4691_MSG_DATA GENMASK(15, 8)
+
enum ad4691_adc_mode {
AD4691_CNV_CLOCK_MODE,
AD4691_MANUAL_MODE,
@@ -250,6 +261,16 @@ struct ad4691_state {
/* hrtimer for MANUAL_MODE triggered buffer (non-offload) */
struct hrtimer sampling_timer;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
+ struct spi_offload_trigger *offload_trigger_periodic;
+ u64 offload_trigger_hz;
+ struct spi_message offload_msg;
+ /* Max 16 channel transfers + 1 state reset or NOOP */
+ struct spi_transfer offload_xfer[17];
+ /* TX commands for manual and accumulator modes */
+ u32 offload_tx_cmd[17];
+ u32 offload_tx_reset;
/*
* DMA (thus cache coherency maintenance) may require the
* transfer buffers to live in their own cache lines.
@@ -263,6 +284,65 @@ struct ad4691_state {
} scan __aligned(IIO_DMA_MINALIGN);
};
+static const struct spi_offload_config ad4691_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
+static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ if (type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+ return false;
+
+ /*
+ * Requires 2 args:
+ * args[0] is the trigger event (BUSY or DATA_READY).
+ * args[1] is the GPIO pin number (only GP0 supported).
+ */
+ if (nargs != 2)
+ return false;
+
+ if (args[0] != AD4691_TRIGGER_EVENT_BUSY &&
+ args[0] != AD4691_TRIGGER_EVENT_DATA_READY)
+ return false;
+
+ if (args[1] != AD4691_TRIGGER_PIN_GP0)
+ return false;
+
+ return true;
+}
+
+static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ /*
+ * GP0 is configured as DATA_READY or BUSY in ad4691_config()
+ * based on the ADC mode. No additional configuration needed here.
+ */
+ if (nargs != 2)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ad4691_offload_trigger_validate(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops = {
+ .match = ad4691_offload_trigger_match,
+ .request = ad4691_offload_trigger_request,
+ .validate = ad4691_offload_trigger_validate,
+};
+
static void ad4691_disable_pwm(void *data)
{
struct pwm_device *pwm = data;
@@ -446,9 +526,13 @@ static int ad4691_transfer(struct ad4691_state *st, int command,
static int ad4691_get_sampling_freq(struct ad4691_state *st)
{
- if (st->adc_mode == AD4691_MANUAL_MODE)
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /* Offload uses periodic trigger, non-offload uses hrtimer */
+ if (st->offload)
+ return st->offload_trigger_hz;
return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
ktime_to_ns(st->sampling_period));
+ }
return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
pwm_get_period(st->conv_trigger));
@@ -502,6 +586,7 @@ static int ad4691_pwm_get(struct ad4691_state *st)
static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
{
struct ad4691_state *st = iio_priv(indio_dev);
+ int ret;
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
@@ -511,11 +596,29 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
guard(mutex)(&st->lock);
if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /* For offload mode, validate and store frequency for periodic trigger */
+ if (st->offload) {
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ .periodic = {
+ .frequency_hz = freq,
+ },
+ };
+
+ ret = spi_offload_trigger_validate(st->offload_trigger_periodic,
+ &config);
+ if (ret)
+ return ret;
+
+ st->offload_trigger_hz = config.periodic.frequency_hz;
+ return 0;
+ }
+
+ /* Non-offload: update hrtimer sampling period */
if (!freq || freq > st->chip->max_rate)
return -ERANGE;
- st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC,
- freq));
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq));
return 0;
}
@@ -787,6 +890,223 @@ static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
.postdisable = &ad4691_buffer_postdisable,
};
+static int ad4691_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
+ struct spi_offload_trigger_config config = { };
+ struct spi_offload_trigger *trigger;
+ struct spi_transfer *xfer = st->offload_xfer;
+ int ret, num_xfers = 0;
+ int active_chans[16];
+ unsigned int bit;
+ int n_active = 0;
+ int i;
+
+ memset(xfer, 0, sizeof(st->offload_xfer));
+
+ /* Collect active channels in scan order */
+ iio_for_each_active_channel(indio_dev, bit)
+ active_chans[n_active++] = bit;
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ /*
+ * MANUAL_MODE uses a periodic (PWM) trigger and reads directly from
+ * the ADC. CNV_CLOCK_MODE uses the DATA_READY trigger and reads from
+ * accumulators.
+ */
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
+ config.periodic.frequency_hz = st->offload_trigger_hz;
+ trigger = st->offload_trigger_periodic;
+ if (!trigger)
+ return -EINVAL;
+ } else {
+ u16 mask = ~(*indio_dev->active_scan_mask);
+ u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
+
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ /* Configure accumulator masks - 0 = enabled, 1 = masked */
+ ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG,
+ acc_mask, 2);
+ if (ret)
+ return ret;
+
+ /* Configure sequencer with active channels */
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ *indio_dev->active_scan_mask);
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
+ AD4691_ACC_COUNT_VAL);
+ if (ret)
+ return ret;
+ }
+
+ config.type = SPI_OFFLOAD_TRIGGER_DATA_READY;
+ trigger = st->offload_trigger;
+ }
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /*
+ * Manual mode with CNV tied to CS: Each CS toggle triggers a
+ * conversion AND reads the previous conversion result (pipeline).
+ */
+ for (i = 0; i < n_active; i++) {
+ put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI,
+ AD4691_ADC_CHAN(active_chans[i])),
+ &st->offload_tx_cmd[num_xfers]);
+ xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = spi->max_speed_hz;
+ xfer[num_xfers].cs_change = 1;
+ xfer[num_xfers].cs_change_delay.value = 1000;
+ xfer[num_xfers].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+ /* First transfer RX is garbage - don't capture it */
+ if (num_xfers)
+ xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ num_xfers++;
+ }
+
+ /* Final NOOP to flush pipeline and get last channel's data */
+ put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, AD4691_NOOP),
+ &st->offload_tx_cmd[num_xfers]);
+ xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = spi->max_speed_hz;
+ xfer[num_xfers].cs_change = 0;
+ xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ num_xfers++;
+ } else {
+ /*
+ * CNV_CLOCK_MODE: single transfer per channel (2-byte cmd +
+ * 2-byte data = 4 bytes, one 32-bit SPI Engine DMA word).
+ * AVG_IN registers are used; RX layout: [cmd_hi, cmd_lo, d_hi, d_lo]
+ */
+ for (i = 0; i < n_active; i++) {
+ unsigned int reg;
+ int ch = active_chans[i];
+
+ reg = AD4691_AVG_IN(ch);
+ put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, (reg >> 8) | 0x80) |
+ FIELD_PREP(AD4691_MSG_ADDR_LO, reg & 0xFF),
+ &st->offload_tx_cmd[ch]);
+ xfer[num_xfers].tx_buf = &st->offload_tx_cmd[ch];
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = spi->max_speed_hz;
+ xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ xfer[num_xfers].cs_change = 1;
+ num_xfers++;
+ }
+
+ /* State reset: clear accumulator so DATA_READY can fire again. */
+ put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, AD4691_STATE_RESET_REG >> 8) |
+ FIELD_PREP(AD4691_MSG_ADDR_LO, AD4691_STATE_RESET_REG & 0xFF) |
+ FIELD_PREP(AD4691_MSG_DATA, AD4691_STATE_RESET_ALL),
+ &st->offload_tx_reset);
+ xfer[num_xfers].tx_buf = &st->offload_tx_reset;
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = spi->max_speed_hz;
+ xfer[num_xfers].cs_change = 0;
+ num_xfers++;
+ }
+
+ if (num_xfers == 0)
+ return -EINVAL;
+
+ /*
+ * For MANUAL_MODE, validate that the trigger frequency is low enough
+ * for all SPI transfers to complete. Each transfer is 32 bits.
+ * Add 50% margin for CS setup/hold and other overhead.
+ */
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ u64 min_period_ns;
+ u64 trigger_period_ns;
+
+ /* Time for all transfers in nanoseconds, with 50% overhead margin */
+ min_period_ns = div64_u64((u64)num_xfers * AD4691_OFFLOAD_BITS_PER_WORD *
+ NSEC_PER_SEC * 3,
+ spi->max_speed_hz * 2);
+
+ trigger_period_ns = DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC, st->offload_trigger_hz);
+
+ if (trigger_period_ns < min_period_ns)
+ return -EINVAL;
+ }
+
+ spi_message_init_with_transfers(&st->offload_msg, xfer, num_xfers);
+ st->offload_msg.offload = st->offload;
+
+ ret = spi_optimize_message(spi, &st->offload_msg);
+ if (ret)
+ return ret;
+
+ /*
+ * For CNV_CLOCK_MODE, start conversions before enabling the trigger.
+ * If the trigger is enabled first, the SPI engine blocks waiting for
+ * DATA_READY, and any subsequent SPI write times out.
+ *
+ * MANUAL_MODE: CNV is tied to CS; conversion starts with each transfer.
+ */
+ if (st->adc_mode == AD4691_CNV_CLOCK_MODE) {
+ ret = ad4691_sampling_enable(st, true);
+ if (ret)
+ goto err_unoptimize_message;
+ }
+
+ ret = spi_offload_trigger_enable(st->offload, trigger, &config);
+ if (ret)
+ goto err_sampling_disable;
+
+ return 0;
+
+err_sampling_disable:
+ if (st->adc_mode == AD4691_CNV_CLOCK_MODE)
+ ad4691_sampling_enable(st, false);
+err_unoptimize_message:
+ spi_unoptimize_message(&st->offload_msg);
+ return ret;
+}
+
+static int ad4691_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct spi_offload_trigger *trigger;
+ int ret;
+
+ trigger = (st->adc_mode == AD4691_MANUAL_MODE) ?
+ st->offload_trigger_periodic : st->offload_trigger;
+
+ spi_offload_trigger_disable(st->offload, trigger);
+ spi_unoptimize_message(&st->offload_msg);
+
+ if (st->adc_mode == AD4691_CNV_CLOCK_MODE) {
+ ret = ad4691_sampling_enable(st, false);
+ if (ret)
+ return ret;
+ }
+
+ return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_offload_buffer_setup_ops = {
+ .postenable = &ad4691_offload_buffer_postenable,
+ .predisable = &ad4691_offload_buffer_predisable,
+};
+
static irqreturn_t ad4691_irq(int irq, void *private)
{
struct iio_dev *indio_dev = private;
@@ -980,6 +1300,55 @@ static int ad4691_config(struct ad4691_state *st)
return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
}
+static int ad4691_setup_offload(struct iio_dev *indio_dev,
+ struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
+ struct dma_chan *rx_dma;
+ int ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ st->offload_trigger_periodic = devm_spi_offload_trigger_get(dev,
+ st->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(st->offload_trigger_periodic))
+ return dev_err_probe(dev,
+ PTR_ERR(st->offload_trigger_periodic),
+ "failed to get periodic offload trigger\n");
+
+ st->offload_trigger_hz = AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
+ spi->max_speed_hz);
+ } else {
+ struct spi_offload_trigger_info trigger_info = {
+ .fwnode = dev_fwnode(dev),
+ .ops = &ad4691_offload_trigger_ops,
+ .priv = st,
+ };
+
+ ret = devm_spi_offload_trigger_register(dev, &trigger_info);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register offload trigger\n");
+
+ st->offload_trigger = devm_spi_offload_trigger_get(dev,
+ st->offload, SPI_OFFLOAD_TRIGGER_DATA_READY);
+ if (IS_ERR(st->offload_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+ "failed to get offload trigger\n");
+ }
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma),
+ "failed to get offload RX DMA\n");
+
+ indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_HARDWARE;
+ indio_dev->setup_ops = &ad4691_offload_buffer_setup_ops;
+
+ return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
+ rx_dma, IIO_BUFFER_DIRECTION_IN);
+}
+
static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
struct ad4691_state *st)
{
@@ -1060,6 +1429,14 @@ static int ad4691_probe(struct spi_device *spi)
return dev_err_probe(dev, PTR_ERR(st->regmap),
"Failed to initialize regmap\n");
+ st->offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
+ ret = PTR_ERR_OR_ZERO(st->offload);
+ if (ret == -ENODEV)
+ st->offload = NULL;
+ else if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get SPI offload\n");
+
st->chip = spi_get_device_match_data(spi);
ret = ad4691_regulator_get(st);
@@ -1082,10 +1459,15 @@ static int ad4691_probe(struct spi_device *spi)
st->chip->manual_channels : st->chip->channels;
indio_dev->num_channels = st->chip->num_channels;
- ret = ad4691_setup_triggered_buffer(indio_dev, st);
- if (ret)
- return ret;
-
+ if (st->offload) {
+ ret = ad4691_setup_offload(indio_dev, st);
+ if (ret)
+ return ret;
+ } else {
+ ret = ad4691_setup_triggered_buffer(indio_dev, st);
+ if (ret)
+ return ret;
+ }
return devm_iio_device_register(dev, indio_dev);
}
@@ -1120,3 +1502,4 @@ module_spi_driver(ad4691_driver);
MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMA_BUFFER");
--
2.43.0
^ permalink raw reply related [flat|nested] 37+ messages in thread* Re: [PATCH v3 4/4] iio: adc: ad4691: add SPI offload support
2026-03-13 10:07 ` [PATCH v3 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
@ 2026-03-14 19:37 ` David Lechner
2026-03-16 13:31 ` Sabau, Radu bogdan
0 siblings, 1 reply; 37+ messages in thread
From: David Lechner @ 2026-03-14 19:37 UTC (permalink / raw)
To: radu.sabau, Lars-Peter Clausen, Michael Hennerich,
Jonathan Cameron, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On 3/13/26 5:07 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add SPI offload support to enable DMA-based, CPU-independent data
> acquisition using the SPI Engine offload framework.
>
> When an SPI offload is available (devm_spi_offload_get() succeeds),
> the driver registers a DMA engine IIO buffer and uses dedicated buffer
> setup operations. If no offload is available the existing software
> triggered buffer path is used unchanged.
>
> Both CNV Clock Mode and Manual Mode support offload, but use different
> trigger mechanisms:
>
> CNV Clock Mode: the SPI Engine is triggered by the ADC's DATA_READY
> signal on GP0. For this mode the driver acts as both an SPI offload
> consumer (DMA RX stream, message optimization) and a trigger source
> provider: it registers the GP0/DATA_READY output via
> devm_spi_offload_trigger_register() so the offload framework can
> match the '#trigger-source-cells' phandle from the device tree and
> automatically fire the SPI Engine DMA transfer at end-of-conversion.
> The pre-built SPI message reads all active channels from the AVG_IN
> accumulator registers (2-byte address + 2-byte data per channel,
> one 4-byte transfer each) followed by a state reset word to re-arm
> the accumulator for the next cycle.
>
> Manual Mode: the SPI Engine is triggered by a periodic trigger at
> the configured sampling frequency. The pre-built SPI message uses
> the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued
> for N active channels (the first result is discarded as garbage from
> the pipeline flush) and the remaining N results are captured by DMA.
>
> All offload transfers use 32-bit frames (bits_per_word=32, len=4) for
> DMA word alignment. In Manual Mode the 4-byte DMA word layout is
> [dummy(8), data_hi(8), data_lo(8), extra(8)]; the channel scan type
> storagebits=32, shift=8, realbits=16 correctly extracts the 16-bit
> ADC result from the middle two bytes.
>
> Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ad4691.c | 397 ++++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 391 insertions(+), 7 deletions(-)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index d498f16c0816..93f090e9a562 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -144,6 +144,7 @@ config AD4691
> depends on SPI
> select IIO_BUFFER
> select IIO_TRIGGERED_BUFFER
> + select IIO_BUFFER_DMAENGINE
> select REGMAP
> help
> Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> index de2208395b21..ad9eaa94727e 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -9,6 +9,7 @@
> #include <linux/clk.h>
> #include <linux/delay.h>
> #include <linux/device.h>
> +#include <linux/dmaengine.h>
> #include <linux/err.h>
> #include <linux/reset.h>
> #include <linux/hrtimer.h>
> @@ -21,11 +22,15 @@
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> #include <linux/spi/spi.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/provider.h>
> #include <linux/util_macros.h>
> #include <linux/units.h>
> #include <linux/unaligned.h>
>
> #include <linux/iio/buffer.h>
> +#include <linux/iio/buffer-dma.h>
> +#include <linux/iio/buffer-dmaengine.h>
> #include <linux/iio/iio.h>
>
> #include <linux/iio/trigger.h>
> @@ -46,6 +51,7 @@
> */
> #define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
> #define AD4691_BITS_PER_XFER 24
> +#define AD4691_OFFLOAD_BITS_PER_WORD 32
> #define AD4691_CNV_DUTY_CYCLE_NS 380
> #define AD4691_MAX_CONV_PERIOD_US 800
>
> @@ -92,6 +98,11 @@
> #define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> #define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
>
> +/* SPI offload 32-bit message byte-field masks (MSB = first transmitted) */
> +#define AD4691_MSG_ADDR_HI GENMASK(31, 24)
> +#define AD4691_MSG_ADDR_LO GENMASK(23, 16)
> +#define AD4691_MSG_DATA GENMASK(15, 8)
> +
> enum ad4691_adc_mode {
> AD4691_CNV_CLOCK_MODE,
> AD4691_MANUAL_MODE,
> @@ -250,6 +261,16 @@ struct ad4691_state {
> /* hrtimer for MANUAL_MODE triggered buffer (non-offload) */
> struct hrtimer sampling_timer;
>
> + struct spi_offload *offload;
> + struct spi_offload_trigger *offload_trigger;
> + struct spi_offload_trigger *offload_trigger_periodic;
Aren't these mutually exclusive (depends on hardware wiring)?
It seems like we only need one trigger pointer because we never
have two at the same time.
> + u64 offload_trigger_hz;
> + struct spi_message offload_msg;
> + /* Max 16 channel transfers + 1 state reset or NOOP */
> + struct spi_transfer offload_xfer[17];
> + /* TX commands for manual and accumulator modes */
> + u32 offload_tx_cmd[17];
> + u32 offload_tx_reset;
> /*
> * DMA (thus cache coherency maintenance) may require the
> * transfer buffers to live in their own cache lines.
> @@ -263,6 +284,65 @@ struct ad4691_state {
> } scan __aligned(IIO_DMA_MINALIGN);
> };
>
> +static const struct spi_offload_config ad4691_offload_config = {
> + .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
> + SPI_OFFLOAD_CAP_RX_STREAM_DMA,
> +};
> +
> +static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigger,
> + enum spi_offload_trigger_type type,
> + u64 *args, u32 nargs)
> +{
> + if (type != SPI_OFFLOAD_TRIGGER_DATA_READY)
> + return false;
> +
> + /*
> + * Requires 2 args:
> + * args[0] is the trigger event (BUSY or DATA_READY).
> + * args[1] is the GPIO pin number (only GP0 supported).
> + */
> + if (nargs != 2)
> + return false;
> +
> + if (args[0] != AD4691_TRIGGER_EVENT_BUSY &&
> + args[0] != AD4691_TRIGGER_EVENT_DATA_READY)
What is the difference between BUSY and DATA_READY?
> + return false;
> +
> + if (args[1] != AD4691_TRIGGER_PIN_GP0)
> + return false;
> +
> + return true;
> +}
> +
> +static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
> + enum spi_offload_trigger_type type,
> + u64 *args, u32 nargs)
> +{
> + /*
> + * GP0 is configured as DATA_READY or BUSY in ad4691_config()
> + * based on the ADC mode. No additional configuration needed here.
> + */
> + if (nargs != 2)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static int ad4691_offload_trigger_validate(struct spi_offload_trigger *trigger,
> + struct spi_offload_trigger_config *config)
> +{
> + if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
> + return -EINVAL;
> +
> + return 0;
> +}
> +
> +static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops = {
> + .match = ad4691_offload_trigger_match,
> + .request = ad4691_offload_trigger_request,
> + .validate = ad4691_offload_trigger_validate,
> +};
> +
> static void ad4691_disable_pwm(void *data)
> {
> struct pwm_device *pwm = data;
> @@ -446,9 +526,13 @@ static int ad4691_transfer(struct ad4691_state *st, int command,
>
> static int ad4691_get_sampling_freq(struct ad4691_state *st)
> {
> - if (st->adc_mode == AD4691_MANUAL_MODE)
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + /* Offload uses periodic trigger, non-offload uses hrtimer */
> + if (st->offload)
> + return st->offload_trigger_hz;
> return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> ktime_to_ns(st->sampling_period));
> + }
>
> return DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> pwm_get_period(st->conv_trigger));
> @@ -502,6 +586,7 @@ static int ad4691_pwm_get(struct ad4691_state *st)
> static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
> {
> struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
>
> IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
>
> @@ -511,11 +596,29 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
> guard(mutex)(&st->lock);
>
> if (st->adc_mode == AD4691_MANUAL_MODE) {
> + /* For offload mode, validate and store frequency for periodic trigger */
> + if (st->offload) {
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
> + .periodic = {
> + .frequency_hz = freq,
> + },
> + };
> +
> + ret = spi_offload_trigger_validate(st->offload_trigger_periodic,
> + &config);
> + if (ret)
> + return ret;
> +
> + st->offload_trigger_hz = config.periodic.frequency_hz;
> + return 0;
> + }
> +
> + /* Non-offload: update hrtimer sampling period */
> if (!freq || freq > st->chip->max_rate)
> return -ERANGE;
>
> - st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC,
> - freq));
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq));
> return 0;
> }
>
> @@ -787,6 +890,223 @@ static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
> .postdisable = &ad4691_buffer_postdisable,
> };
>
> +static int ad4691_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct spi_device *spi = to_spi_device(regmap_get_device(st->regmap));
> + struct spi_offload_trigger_config config = { };
> + struct spi_offload_trigger *trigger;
> + struct spi_transfer *xfer = st->offload_xfer;
> + int ret, num_xfers = 0;
> + int active_chans[16];
> + unsigned int bit;
> + int n_active = 0;
> + int i;
> +
> + memset(xfer, 0, sizeof(st->offload_xfer));
> +
> + /* Collect active channels in scan order */
> + iio_for_each_active_channel(indio_dev, bit)
> + active_chans[n_active++] = bit;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + /*
> + * MANUAL_MODE uses a periodic (PWM) trigger and reads directly from
> + * the ADC. CNV_CLOCK_MODE uses the DATA_READY trigger and reads from
> + * accumulators.
> + */
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
> + config.periodic.frequency_hz = st->offload_trigger_hz;
> + trigger = st->offload_trigger_periodic;
> + if (!trigger)
> + return -EINVAL;
> + } else {
> + u16 mask = ~(*indio_dev->active_scan_mask);
> + u32 acc_mask[2] = { mask & 0xFF, mask >> 8 };
This is hard to grok. Probably should use bitfield helper and
cpu_to_be16().
> +
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + /* Configure accumulator masks - 0 = enabled, 1 = masked */
> + ret = regmap_bulk_write(st->regmap, AD4691_ACC_MASK1_REG,
> + acc_mask, 2);
> + if (ret)
> + return ret;
> +
> + /* Configure sequencer with active channels */
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + *indio_dev->active_scan_mask);
> + if (ret)
> + return ret;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
> + AD4691_ACC_COUNT_VAL);
> + if (ret)
> + return ret;
> + }
> +
> + config.type = SPI_OFFLOAD_TRIGGER_DATA_READY;
> + trigger = st->offload_trigger;
> + }
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + /*
> + * Manual mode with CNV tied to CS: Each CS toggle triggers a
> + * conversion AND reads the previous conversion result (pipeline).
> + */
> + for (i = 0; i < n_active; i++) {
> + put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI,
Nothing unaligned here.
> + AD4691_ADC_CHAN(active_chans[i])),
> + &st->offload_tx_cmd[num_xfers]);
> + xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = spi->max_speed_hz;
This should already be the default.
> + xfer[num_xfers].cs_change = 1;
> + xfer[num_xfers].cs_change_delay.value = 1000;
This needs an explantion of where the number comes from. I would expect 430 ns
based on max value of t_CONV from the datasheet.
> + xfer[num_xfers].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
If it really is 1 microsecond, we can change the units.
> + /* First transfer RX is garbage - don't capture it */
> + if (num_xfers)
Would make more sense as (i > 0)
> + xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + num_xfers++;
Why have 2nd variable instead of using i?
> + }
> +
> + /* Final NOOP to flush pipeline and get last channel's data */
> + put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, AD4691_NOOP),
> + &st->offload_tx_cmd[num_xfers]);
> + xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = spi->max_speed_hz;
> + xfer[num_xfers].cs_change = 0;
> + xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + num_xfers++;
> + } else {
> + /*
> + * CNV_CLOCK_MODE: single transfer per channel (2-byte cmd +
> + * 2-byte data = 4 bytes, one 32-bit SPI Engine DMA word).
> + * AVG_IN registers are used; RX layout: [cmd_hi, cmd_lo, d_hi, d_lo]
These comments are confusing. What it actually appears we are doing is
doing a 16-bit write and then a 16-bit read. I assume we are doing it
using 32-bit words for efficiny so that we only have 1/2 of the number
of xfers required to do it separately.
TX layout: [cmd_hi, cmd_lo, ignore, ignore]
RX layout: [ignore, ignore, data_hi, data_lo]
> + */
> + for (i = 0; i < n_active; i++) {
> + unsigned int reg;
> + int ch = active_chans[i];
> +
> + reg = AD4691_AVG_IN(ch);
> + put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, (reg >> 8) | 0x80) |
Mixing FIELD_PREP() and bit ops looks wrong.
> + FIELD_PREP(AD4691_MSG_ADDR_LO, reg & 0xFF),
> + &st->offload_tx_cmd[ch]);
> + xfer[num_xfers].tx_buf = &st->offload_tx_cmd[ch];
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = spi->max_speed_hz;
> + xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + xfer[num_xfers].cs_change = 1;
> + num_xfers++;
> + }
> +
> + /* State reset: clear accumulator so DATA_READY can fire again. */
> + put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, AD4691_STATE_RESET_REG >> 8) |
> + FIELD_PREP(AD4691_MSG_ADDR_LO, AD4691_STATE_RESET_REG & 0xFF) |
> + FIELD_PREP(AD4691_MSG_DATA, AD4691_STATE_RESET_ALL),
> + &st->offload_tx_reset);
> + xfer[num_xfers].tx_buf = &st->offload_tx_reset;
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = spi->max_speed_hz;
> + xfer[num_xfers].cs_change = 0;
> + num_xfers++;
> + }
> +
> + if (num_xfers == 0)
> + return -EINVAL;
> +
...
> +static int ad4691_setup_offload(struct iio_dev *indio_dev,
> + struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + struct dma_chan *rx_dma;
> + int ret;
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + st->offload_trigger_periodic = devm_spi_offload_trigger_get(dev,
> + st->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
> + if (IS_ERR(st->offload_trigger_periodic))
> + return dev_err_probe(dev,
> + PTR_ERR(st->offload_trigger_periodic),
> + "failed to get periodic offload trigger\n");
> +
> + st->offload_trigger_hz = AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
> + spi->max_speed_hz);
> + } else {
> + struct spi_offload_trigger_info trigger_info = {
> + .fwnode = dev_fwnode(dev),
> + .ops = &ad4691_offload_trigger_ops,
> + .priv = st,
> + };
> +
> + ret = devm_spi_offload_trigger_register(dev, &trigger_info);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to register offload trigger\n");
> +
> + st->offload_trigger = devm_spi_offload_trigger_get(dev,
> + st->offload, SPI_OFFLOAD_TRIGGER_DATA_READY);
> + if (IS_ERR(st->offload_trigger))
> + return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
> + "failed to get offload trigger\n");
> + }
> +
> + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
> + if (IS_ERR(rx_dma))
> + return dev_err_probe(dev, PTR_ERR(rx_dma),
> + "failed to get offload RX DMA\n");
> +
> + indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_HARDWARE;
We don't need to set INDIO_BUFFER_HARDWARE here,
devm_iio_dmaengine_buffer_setup_with_handle() does that already.
> + indio_dev->setup_ops = &ad4691_offload_buffer_setup_ops;
> +
> + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
> + rx_dma, IIO_BUFFER_DIRECTION_IN);
> +}
> +
^ permalink raw reply [flat|nested] 37+ messages in thread* RE: [PATCH v3 4/4] iio: adc: ad4691: add SPI offload support
2026-03-14 19:37 ` David Lechner
@ 2026-03-16 13:31 ` Sabau, Radu bogdan
0 siblings, 0 replies; 37+ messages in thread
From: Sabau, Radu bogdan @ 2026-03-16 13:31 UTC (permalink / raw)
To: David Lechner, Lars-Peter Clausen, Hennerich, Michael,
Jonathan Cameron, Sa, Nuno, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel
Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org,
linux-gpio@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, March 14, 2026 9:37 PM
...
> > +
> > + if (args[0] != AD4691_TRIGGER_EVENT_BUSY &&
> > + args[0] != AD4691_TRIGGER_EVENT_DATA_READY)
>
> What is the difference between BUSY and DATA_READY?
>
Perhaps BUSY won't be used at all, since indeed this question got me
thinking of its necessity.
> > + return false;
> > +
...
> > + xfer[num_xfers].cs_change = 1;
> > + xfer[num_xfers].cs_change_delay.value = 1000;
>
> This needs an explantion of where the number comes from. I would expect
> 430 ns
> based on max value of t_CONV from the datasheet.
>
You are right about this. I was playing with this value at testing and
Forgot to change it back, my bad...
> > + xfer[num_xfers].cs_change_delay.unit =
...
> > + } else {
> > + /*
> > + * CNV_CLOCK_MODE: single transfer per channel (2-byte cmd
> +
> > + * 2-byte data = 4 bytes, one 32-bit SPI Engine DMA word).
> > + * AVG_IN registers are used; RX layout: [cmd_hi, cmd_lo,
> d_hi, d_lo]
>
> These comments are confusing. What it actually appears we are doing is
> doing a 16-bit write and then a 16-bit read. I assume we are doing it
> using 32-bit words for efficiny so that we only have 1/2 of the number
> of xfers required to do it separately.
>
> TX layout: [cmd_hi, cmd_lo, ignore, ignore]
> RX layout: [ignore, ignore, data_hi, data_lo]
>
I will make sure to update the comment as you are saying here, since
TX and RX should be explained separately. Having them both as
'RX layout' is a mistake.
>
> > + */
> > + for (i = 0; i < n_active; i++) {
> > + unsigned int reg;
> > + int ch = active_chans[i];
> > +
> > + reg = AD4691_AVG_IN(ch);
> > +
> put_unaligned_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, (reg >> 8)
> | 0x80) |
>
> Mixing FIELD_PREP() and bit ops looks wrong.
Will stick to bit ops in this case, then.
^ permalink raw reply [flat|nested] 37+ messages in thread
* Re: [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family
2026-03-13 10:07 [PATCH v3 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
` (3 preceding siblings ...)
2026-03-13 10:07 ` [PATCH v3 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
@ 2026-03-13 11:14 ` Andy Shevchenko
4 siblings, 0 replies; 37+ messages in thread
From: Andy Shevchenko @ 2026-03-13 11:14 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
Philipp Zabel, linux-iio, devicetree, linux-kernel, linux-pwm,
linux-gpio
On Fri, Mar 13, 2026 at 12:07:24PM +0200, Radu Sabau via B4 Relay wrote:
> This series adds support for the Analog Devices AD4691 family of
> high-speed, low-power multichannel successive approximation register
> (SAR) ADCs with an SPI-compatible serial interface.
>
> The family includes:
> - AD4691: 16-channel, 500 kSPS
> - AD4692: 16-channel, 1 MSPS
> - AD4693: 8-channel, 500 kSPS
> - AD4694: 8-channel, 1 MSPS
>
> The devices support two operating modes, auto-detected from the device
> tree:
> - CNV Clock Mode: external PWM drives CNV independently of SPI;
> DATA_READY on GP0 signals end of conversion
> - Manual Mode: CNV tied to SPI CS; each SPI transfer reads
> the previous conversion result and starts the
> next (pipelined N+1 scheme)
>
> A new driver is warranted rather than extending ad4695: the AD4691
> data path uses an accumulator-register model — results are read from
> AVG_IN registers, with ACC_MASK, ADC_SETUP, DEVICE_SETUP, and
> GPIO_MODE registers controlling the sequencer — none of which exist
> in AD4695. CNV Clock Mode (PWM drives CNV independently of SPI) and
> Manual Mode (pipelined N+1 transfers) also have no equivalent in
> AD4695's command-embedded single-cycle protocol.
>
> The series is structured as follows:
> 1/4 - DT bindings (YAML schema + dt-bindings header) and
> MAINTAINERS entry
> 2/4 - Initial driver: register map via custom regmap callbacks,
> IIO read_raw/write_raw, both operating modes, single-channel
> reads via internal oscillator (Autonomous Mode)
> 3/4 - Triggered buffer support: IRQ-driven (DATA_READY on GP0) for
> CNV Clock Mode; hrtimer-based trigger for Manual Mode to
> handle the pipelined N+1 SPI protocol
> 4/4 - SPI Engine offload support: DMA-backed high-throughput
> capture path using the SPI offload subsystem
I stopped review where I stopped because I have a feeling that you ignored some
of my comments from the previous version. Why? What's going on?
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 37+ messages in thread