* [PATCH v6 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family
@ 2026-04-03 11:03 Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 1/4] dt-bindings: iio: adc: add AD4691 family Radu Sabau via B4 Relay
` (3 more replies)
0 siblings, 4 replies; 23+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-04-03 11:03 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, Radu Sabau
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 Burst Mode: external PWM drives CNV independently of SPI;
DATA_READY on a GP pin 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 Burst 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) 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 a GP pin
selected via interrupt-names) for CNV Burst Mode; external IIO
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
Datasheets:
https://www.analog.com/en/products/ad4691.html
https://www.analog.com/en/products/ad4692.html
https://www.analog.com/en/products/ad4693.html
https://www.analog.com/en/products/ad4694.html
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
Changes in v6:
- Replace device.h with dev_printk.h + device/devres.h; add array_size.h
- Rename osc_freqs[] → osc_freqs_Hz[] with explicit [0xN] index designators
- Move loop variable into for() declaration in set_sampling_freq
- Convert multi-line block comment to single-line in single_shot_read
- Replace (u16)~ cast with ~BIT() & GENMASK(15, 0) for ACC_MASK_REG write;
GENMASK(15, 0) is still needed, otherwise maximum value condition line
in reg_write() would fail.
- Extract osc_idx/period_us temporaries in single_shot_read; add comment
- Use devm_regulator_bulk_get_enable() for avdd + vio supplies
- Reformat reset_gpio_probe() comment; remove (GPIOD_OUT_HIGH) detail
- Extract REF_CTRL value into temporary before regmap_update_bits
- Use regmap_assign_bits for OSC_FREQ_REG in config
- Remove ad4691_free_scan_bufs NULL assignments; they are not checked.
- Replace indio_dev->masklength with iio_get_masklength() throughout
- Fix spi_optimize_message error path to use goto err in preenable
- Add iio_buffer_enabled() guard in sampling_frequency_store and
set_oversampling_ratio
- Move ad4691_gpio_setup call from ad4691_config into
setup_triggered_buffer after IRQ lookup; remove duplicate
fwnode_irq_get_byname loop
- Replace oversampling ratio search loop with is_power_of_2 + ilog2
- Link to v5: https://lore.kernel.org/r/20260327-ad4692-multichannel-sar-adc-driver-v5-0-11f789de47b8@analog.com
Changes in v5:
- Reorder datasheets numerically
- Fix interrupt-names: use enum with minItems/maxItems
- Remove if/then block requiring interrupts — driver detail, not hardware constraint
- Remove redundant .shift = 0 from channel macro
- Write max_rate comparison as 1 * HZ_PER_MHZ
- Invert set_sampling_freq loop to use continue
- Fix fsleep() line break; remove blank line in read_raw
- Reorder supply init: vio immediately after avdd
- Move comment rewrites and OSC_FREQ_REG condition into the base driver patch
- Add bit-15 READ comment in reg_read
- Rewrite ldo-in handling with cleaner if/else-if pattern
- Drop redundant refbuf_en = false; invert if (!rst) in reset
- Drop reset_control_assert() — GPIO already asserted at probe
- Use regmap_update_bits/assign_bits in config
- Remove tab-column alignment of state struct members
- Declare osc_freqs[] as const int, eliminating explicit casts
- Drop obvious AUTONOMOUS mode comment
- Rename ACC_COUNT_LIMIT → ACC_DEPTH_IN to match datasheet
- Use bitmap_weight()/bitmap_read() for active_scan_mask access;
add #include <linux/bitmap.h>
- Fix channel macro line-continuation tab alignment
- Use IIO_CHAN_SOFT_TIMESTAMP(8) for 8-channel variants
- Use aligned_s64 ts in scan struct
- Add comment explaining start-index removal in set_sampling_freq
- Remove trailing comma after NULL in buffer_attrs[]
- Add IRQF_NO_AUTOEN rationale comment
- Remove unreachable manual_mode guards in sampling_frequency_show/store
- Remove st->trig; use indio_dev->trig directly
- Move max_speed_hz param to the offload patch where it is used
- Use DIV_ROUND_UP for CNV period; use compound pwm_state initializer
- Move offload fields into a separately allocated sub-struct
- Build TX words via u8* byte-fill; fixes sparse __be32 warnings
- Add three scan types (NORMAL/OFFLOAD_CNV/OFFLOAD_MANUAL) with
get_current_scan_type; triggered buffer path uses storagebits=16
- Fix IIO_CHAN_INFO_SCALE: use iio_get_current_scan_type() for realbits
- Add MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER")
- Add Documentation/iio/ad4691.rst
- Link to v4: https://lore.kernel.org/r/20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com
Changes in v4:
- dt-bindings: add avdd-supply (required) and ldo-in-supply (optional);
rename vref-supply → ref-supply, vrefin-supply → refin-supply;
corrected reset-gpios polarity (active-high → active-low); remove
clocks and pwm-names; extend interrupts to up to 4 GP pins with
interrupt-names "gp0".."gp3"; reduce #trigger-source-cells to
const: 1 (GP pin number); add gpio-controller / #gpio-cells = <2>;
drop adi,ad4691.h header; update binding examples
- driver: rename CNV Clock Mode → CNV Burst Mode throughout
- driver: add avdd-supply (required) and ldo-in-supply; track ref vs.
refin supply for REFBUF_EN; set LDO_EN in DEVICE_SETUP when ldo-in
is present; add software reset fallback via SPI_CONFIG_A register
- driver: merge ACC_MASK1_REG / ACC_MASK2_REG into ACC_MASK_REG with
a single ADDR_DESCENDING 16-bit SPI write
- driver: remove clocks usage; set PWM rate directly without ref clock
- driver: rename chip info structs (ad4691_chip_info etc.); rename
*chip → *info in state struct; replace adc_mode enum with manual_mode
bool; replace ktime sampling_period with u32 cnv_period_ns
- driver: move IIO_CHAN_INFO_SAMP_FREQ to info_mask_separate with an
available list for the internal oscillator frequency
- driver: use regcache MAPLE instead of RBTREE
- triggered buffer: derive DATA_READY GP pin from interrupt-names in
firmware ("gp0".."gp3") instead of assuming GP0
- triggered buffer: use regmap_update_bits for DEVICE_SETUP mode toggle
to avoid clobbering LDO_EN when toggling MANUAL_MODE bit
- triggered buffer: split buffer setup ops into separate Manual and
CNV Burst variants (mirrors offload path structure)
- SPI offload: promote channel storagebits from 16 to 32 to match DMA
word size; introduce ad4691_manual_channels[] with shift=16 (data in
upper 16 bits of the 32-bit word); update triggered-buffer paths to
the same layout for consistency
- SPI offload: derive GP pin from trigger-source args[0] instead of
hardcoding GP0; split offload buffer setup ops per mode
- replace put_unaligned_be32() + FIELD_PREP() with cpu_to_be32() and
plain bit-shift ops for SPI offload message construction
- multiple reviewer-requested code style and correctness fixes
(Andy Shevchenko, Nuno Sá, Uwe Kleine-König, David Lechner)
- Link to v3: https://lore.kernel.org/r/20260313-ad4692-multichannel-sar-adc-driver-v3-0-b4d14d81a181@analog.com
Changes in v3:
- Replace GPIO reset handling with reset controller framework
- Replace two regmap_write() calls for ACC_MASK1/ACC_MASK2 with regmap_bulk_write()
- Move conv_us declaration closer to its first use
- Derive spi_device/dev from regmap instead of storing st->spi
- ad4691_trigger_handler(): use guard(mutex)() and iio_for_each_active_channel()
- ad4691_setup_triggered_buffer(): return -ENOMEM/-ENOENT directly instead of
wrapping in dev_err_probe(); fix fwnode_irq_get() check (irq <= 0 → irq < 0)
- Add GENMASK defines for SPI offload 32-bit message layout; replace manual
bit-shifts with put_unaligned_be32() + FIELD_PREP()
- Use DIV_ROUND_CLOSEST_ULL() instead of div64_u64()
- ad4691_set_sampling_freq(): fix indentation; drop unnecessary else after return
- ad4691_probe(): use PTR_ERR_OR_ZERO() for devm_spi_offload_get()
- Link to v2: https://lore.kernel.org/r/20260310-ad4692-multichannel-sar-adc-driver-v2-0-d9bb8aeb5e17@analog.com
Changes in v2:
- Drop adi,spi-mode DT property; operating mode now auto-detected
from pwms presence (CNV Clock Mode if present, Manual Mode if not)
- Reduce from 5 operating modes to 2 (CNV Clock Mode, Manual Mode);
Autonomous, SPI Burst and CNV Burst modes removed as user-selectable
modes; Autonomous Mode is now the internal idle/single-shot state
- Single-shot read_raw always uses internal oscillator (Autonomous
Mode), independent of the configured buffer mode
- Replace bulk regulator API with devm_regulator_get_enable() and
devm_regulator_get_enable_read_voltage()
- Use guard(mutex) and IIO_DEV_ACQUIRE_DIRECT_MODE scoped helpers
- Replace enum + indexed chip_info array with named chip_info structs
- Remove product_id field and hardware ID check from probe
- Factor IIO_CHAN_INFO_RAW body into ad4691_single_shot_read() helper
- Use fwnode_irq_get(dev_fwnode(dev), 0); drop interrupt-names from
DT binding
- Use devm_clk_get_enabled(dev, NULL); drop clock-names from DT
binding
- Use spi_write_then_read() for DMA-safe register writes
- Use put_unaligned_be16() for SPI header construction
- fsleep() instead of usleep_range() in single-shot path
- storagebits 24->32 for manual-mode channels (uniform DMA layout)
- Collect full scan into vals[16], single iio_push_to_buffers_with_ts()
- Use pf->timestamp instead of iio_get_time_ns() in trigger handler
- Remove IRQF_TRIGGER_FALLING (comes from firmware/DT)
- Fix offload xfer array size ([17]: N channels + 1 state reset)
- Drop third DT binding example per reviewer request
- Link to v1: https://lore.kernel.org/r/20260305-ad4692-multichannel-sar-adc-driver-v1-0-336229a8dcc7@analog.com
---
Radu Sabau (4):
dt-bindings: iio: adc: add AD4691 family
iio: adc: ad4691: add initial driver for AD4691 family
iio: adc: ad4691: add triggered buffer support
iio: adc: ad4691: add SPI offload support
.../devicetree/bindings/iio/adc/adi,ad4691.yaml | 162 ++
Documentation/iio/ad4691.rst | 259 +++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 9 +
drivers/iio/adc/Kconfig | 14 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4691.c | 1685 ++++++++++++++++++++
7 files changed, 2131 insertions(+)
---
base-commit: 11439c4635edd669ae435eec308f4ab8a0804808
change-id: 20260302-ad4692-multichannel-sar-adc-driver-78e4d44d24b2
Best regards,
--
Radu Sabau <radu.sabau@analog.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* [PATCH v6 1/4] dt-bindings: iio: adc: add AD4691 family
2026-04-03 11:03 [PATCH v6 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
@ 2026-04-03 11:03 ` Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 2/4] iio: adc: ad4691: add initial driver for " Radu Sabau via B4 Relay
` (2 subsequent siblings)
3 siblings, 0 replies; 23+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-04-03 11:03 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, 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:
- Power domains: avdd-supply (required), vio-supply, ref-supply or
refin-supply (external reference; the REFIN path enables the
internal reference buffer), and an optional ldo-in-supply, that if
absent, means the on-chip internal LDO will be used.
- Optional PWM on the CNV pin selects CNV Burst Mode; when absent,
Manual Mode is assumed with CNV tied to SPI CS.
- An optional reset GPIO (reset-gpios) for hardware reset.
- Up to four GP pins (gp0..gp3) usable as interrupt sources,
identified in firmware via interrupt-names "gp0".."gp3".
- gpio-controller with #gpio-cells = <2> for GP pin GPIO usage.
- #trigger-source-cells = <1>: one cell selecting the GP pin number
(0-3) used as the SPI offload trigger source.
Two binding examples are provided: CNV Burst Mode with SPI offload
(DMA data acquisition driven by DATA_READY on a GP pin), and Manual
Mode for CPU-driven triggered-buffer or single-shot capture.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
.../devicetree/bindings/iio/adc/adi,ad4691.yaml | 162 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 169 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..81d2ca4e0e22
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
@@ -0,0 +1,162 @@
+# 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 Burst 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/ad4691.html
+ * https://www.analog.com/en/products/ad4692.html
+ * https://www.analog.com/en/products/ad4693.html
+ * https://www.analog.com/en/products/ad4694.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
+
+ avdd-supply:
+ description: Analog power supply (4.5V to 5.5V).
+
+ ldo-in-supply:
+ description: LDO input supply. When absent, the internal LDO is used.
+
+ vio-supply:
+ description: I/O voltage supply (1.71V to 1.89V or VDD).
+
+ ref-supply:
+ description: External reference voltage supply (2.4V to 5.25V).
+
+ refin-supply:
+ description: Internal reference buffer input supply.
+
+ reset-gpios:
+ description:
+ GPIO line controlling the hardware reset pin (active-low).
+ maxItems: 1
+
+ pwms:
+ description:
+ PWM connected to the CNV pin. When present, selects CNV Burst Mode where
+ the PWM drives the conversion rate. When absent, Manual Mode is used
+ (CNV tied to SPI CS).
+ maxItems: 1
+
+ interrupts:
+ description:
+ Interrupt lines connected to the ADC GP pins. Each GP pin can be
+ physically wired to an interrupt-capable input on the SoC.
+ maxItems: 4
+
+ interrupt-names:
+ description: Names of the interrupt lines, matching the GP pin names.
+ minItems: 1
+ maxItems: 4
+ items:
+ enum:
+ - gp0
+ - gp1
+ - gp2
+ - gp3
+
+ gpio-controller: true
+
+ '#gpio-cells':
+ const: 2
+
+ '#trigger-source-cells':
+ description:
+ This node can act as a trigger source. The single cell in a consumer
+ reference specifies the GP pin number (0-3) used as the trigger output.
+ const: 1
+
+required:
+ - compatible
+ - reg
+ - avdd-supply
+ - vio-supply
+
+allOf:
+ # ref-supply and refin-supply are mutually exclusive, one is required
+ - oneOf:
+ - required:
+ - ref-supply
+ - required:
+ - refin-supply
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ /* AD4692 in CNV Burst Mode with SPI offload */
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ reg = <0>;
+ spi-cpol;
+ spi-cpha;
+ spi-max-frequency = <40000000>;
+
+ avdd-supply = <&avdd_supply>;
+ vio-supply = <&vio_supply>;
+ ref-supply = <&ref_5v>;
+
+ reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
+
+ pwms = <&pwm_gen 0 0>;
+
+ #trigger-source-cells = <1>;
+ };
+ };
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ /* AD4692 in Manual Mode (CNV tied to SPI CS) */
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ reg = <0>;
+ spi-cpol;
+ spi-cpha;
+ spi-max-frequency = <31250000>;
+
+ avdd-supply = <&avdd_supply>;
+ vio-supply = <&vio_supply>;
+ refin-supply = <&refin_supply>;
+
+ reset-gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c..438ca850fa1c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1484,6 +1484,13 @@ 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
+
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
M: Nuno Sá <nuno.sa@analog.com>
--
2.43.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* [PATCH v6 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-04-03 11:03 [PATCH v6 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 1/4] dt-bindings: iio: adc: add AD4691 family Radu Sabau via B4 Relay
@ 2026-04-03 11:03 ` Radu Sabau via B4 Relay
2026-04-04 14:25 ` David Lechner
2026-04-03 11:03 ` [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
3 siblings, 1 reply; 23+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-04-03 11:03 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, 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.
The chip idles in Autonomous Mode so that single-shot read_raw can use
the internal oscillator without disturbing the hardware configuration.
Three voltage supply domains are managed: avdd (required), vio, and a
reference supply on either the REF pin (ref-supply, external buffer)
or the REFIN pin (refin-supply, uses the on-chip reference buffer;
REFBUF_EN is set accordingly). Hardware reset is performed via
the reset controller framework; a software reset through SPI_CONFIG_A
is used as fallback when no hardware reset is available.
Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
16-bit transfer.
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 | 691 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 704 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 438ca850fa1c..24e4502b8292 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
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
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..43bd408c3d11
--- /dev/null
+++ b/drivers/iio/adc/ad4691.c
@@ -0,0 +1,691 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024-2026 Analog Devices, Inc.
+ * Author: Radu Sabau <radu.sabau@analog.com>
+ */
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/dev_printk.h>
+#include <linux/device/devres.h>
+#include <linux/err.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/reset.h>
+#include <linux/spi/spi.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+#define AD4691_VREF_uV_MIN 2400000
+#define AD4691_VREF_uV_MAX 5250000
+#define AD4691_VREF_2P5_uV_MAX 2750000
+#define AD4691_VREF_3P0_uV_MAX 3250000
+#define AD4691_VREF_3P3_uV_MAX 3750000
+#define AD4691_VREF_4P096_uV_MAX 4500000
+
+#define AD4691_SPI_CONFIG_A_REG 0x000
+#define AD4691_SW_RESET (BIT(7) | BIT(0))
+
+#define AD4691_STATUS_REG 0x014
+#define AD4691_CLAMP_STATUS1_REG 0x01A
+#define AD4691_CLAMP_STATUS2_REG 0x01B
+#define AD4691_DEVICE_SETUP 0x020
+#define AD4691_LDO_EN BIT(4)
+#define AD4691_REF_CTRL 0x021
+#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
+#define AD4691_REFBUF_EN BIT(0)
+#define AD4691_OSC_FREQ_REG 0x023
+#define AD4691_OSC_FREQ_MASK GENMASK(3, 0)
+#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_STATE_RESET_ALL 0x01
+#define AD4691_ADC_SETUP 0x182
+#define AD4691_ADC_MODE_MASK GENMASK(1, 0)
+#define AD4691_AUTONOMOUS_MODE 0x02
+/*
+ * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
+ * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte.
+ */
+#define AD4691_ACC_MASK_REG 0x185
+#define AD4691_ACC_DEPTH_IN(n) (0x186 + (n))
+#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)))
+
+static const char * const ad4691_supplies[] = { "avdd", "vio" };
+
+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 char *name;
+ unsigned int num_channels;
+ unsigned int max_rate;
+};
+
+#define AD4691_CHANNEL(ch) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_separate_available = \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = ch, \
+ .scan_index = ch, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = 16, \
+ .storagebits = 16, \
+ }, \
+ }
+
+static const struct iio_chan_spec ad4691_channels[] = {
+ AD4691_CHANNEL(0),
+ AD4691_CHANNEL(1),
+ AD4691_CHANNEL(2),
+ AD4691_CHANNEL(3),
+ AD4691_CHANNEL(4),
+ AD4691_CHANNEL(5),
+ AD4691_CHANNEL(6),
+ AD4691_CHANNEL(7),
+ AD4691_CHANNEL(8),
+ AD4691_CHANNEL(9),
+ AD4691_CHANNEL(10),
+ AD4691_CHANNEL(11),
+ AD4691_CHANNEL(12),
+ AD4691_CHANNEL(13),
+ AD4691_CHANNEL(14),
+ AD4691_CHANNEL(15),
+};
+
+static const struct iio_chan_spec ad4693_channels[] = {
+ AD4691_CHANNEL(0),
+ AD4691_CHANNEL(1),
+ AD4691_CHANNEL(2),
+ AD4691_CHANNEL(3),
+ AD4691_CHANNEL(4),
+ AD4691_CHANNEL(5),
+ AD4691_CHANNEL(6),
+ AD4691_CHANNEL(7),
+};
+
+/*
+ * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] value.
+ * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support
+ * up to 500 kHz and use index 1 as their highest valid rate.
+ */
+static const int ad4691_osc_freqs_Hz[] = {
+ [0x0] = 1000000,
+ [0x1] = 500000,
+ [0x2] = 400000,
+ [0x3] = 250000,
+ [0x4] = 200000,
+ [0x5] = 167000,
+ [0x6] = 133000,
+ [0x7] = 125000,
+ [0x8] = 100000,
+ [0x9] = 50000,
+ [0xA] = 25000,
+ [0xB] = 12500,
+ [0xC] = 10000,
+ [0xD] = 5000,
+ [0xE] = 2500,
+ [0xF] = 1250,
+};
+
+static const struct ad4691_chip_info ad4691_chip_info = {
+ .channels = ad4691_channels,
+ .name = "ad4691",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4692_chip_info = {
+ .channels = ad4691_channels,
+ .name = "ad4692",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 1 * HZ_PER_MHZ,
+};
+
+static const struct ad4691_chip_info ad4693_chip_info = {
+ .channels = ad4693_channels,
+ .name = "ad4693",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 500 * HZ_PER_KHZ,
+};
+
+static const struct ad4691_chip_info ad4694_chip_info = {
+ .channels = ad4693_channels,
+ .name = "ad4694",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 1 * HZ_PER_MHZ,
+};
+
+struct ad4691_state {
+ const struct ad4691_chip_info *info;
+ struct regmap *regmap;
+ int vref_uV;
+ bool refbuf_en;
+ bool ldo_en;
+ /*
+ * Synchronize access to members of the driver state, and ensure
+ * atomicity of consecutive SPI operations.
+ */
+ struct mutex lock;
+};
+
+static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct spi_device *spi = context;
+ u8 tx[2], rx[4];
+ int ret;
+
+ /* Set bit 15 to mark the operation as READ. */
+ 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 spi_device *spi = context;
+ u8 tx[4];
+
+ put_unaligned_be16(reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1:
+ case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG:
+ if (val > 0xFF)
+ return -EINVAL;
+ tx[2] = val;
+ return spi_write_then_read(spi, tx, 3, NULL, 0);
+ case AD4691_ACC_MASK_REG:
+ 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, int *val)
+{
+ unsigned int reg_val;
+ int ret;
+
+ ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
+ if (ret)
+ return ret;
+
+ *val = ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)];
+ return IIO_VAL_INT;
+}
+
+static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ for (unsigned int i = start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) {
+ if (ad4691_osc_freqs_Hz[i] != freq)
+ continue;
+ return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG,
+ AD4691_OSC_FREQ_MASK, i);
+ }
+
+ return -EINVAL;
+}
+
+static int ad4691_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type,
+ int *length, long mask)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int start = (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = &ad4691_osc_freqs_Hz[start];
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EINVAL;
+ }
+}
+
+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);
+ unsigned int reg_val, osc_idx, period_us;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ /* Use AUTONOMOUS mode for single-shot reads. */
+ 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_write(st->regmap, AD4691_ACC_MASK_REG,
+ ~BIT(chan->channel) & GENMASK(15, 0));
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
+ if (ret)
+ return ret;
+
+ osc_idx = FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val);
+ /* Wait 2 oscillator periods for the conversion to complete. */
+ period_us = DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_idx]);
+ fsleep(period_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;
+
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ 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:
+ return ad4691_get_sampling_freq(st, val);
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->vref_uV / (MICRO / MILLI);
+ *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,
+ .read_avail = &ad4691_read_avail,
+ .debugfs_reg_access = &ad4691_reg_access,
+};
+
+static int ad4691_regulator_setup(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ int ret;
+
+ ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(ad4691_supplies),
+ ad4691_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get and enable supplies\n");
+
+ ret = devm_regulator_get_enable(dev, "ldo-in");
+ if (ret == -ENODEV)
+ st->ldo_en = true;
+ else if (ret)
+ return dev_err_probe(dev, ret, "Failed to get and enable LDO-IN\n");
+
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
+ if (st->vref_uV == -ENODEV) {
+ st->vref_uV = devm_regulator_get_enable_read_voltage(dev, "refin");
+ st->refbuf_en = true;
+ }
+ 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 in the range [%u...%u]\n",
+ st->vref_uV, AD4691_VREF_uV_MIN,
+ AD4691_VREF_uV_MAX);
+
+ return 0;
+}
+
+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) {
+ /*
+ * The GPIO is already asserted by reset_gpio_probe().
+ * Wait for the reset pulse width required by the chip.
+ * See datasheet Table 5.
+ */
+ fsleep(300);
+ return reset_control_deassert(rst);
+ }
+
+ /* No hardware reset available, fall back to software reset. */
+ return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG,
+ AD4691_SW_RESET);
+}
+
+static int ad4691_config(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ enum ad4691_ref_ctrl ref_val;
+ unsigned int val;
+ int ret;
+
+ switch (st->vref_uV) {
+ case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
+ ref_val = AD4691_VREF_2P5;
+ break;
+ case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX:
+ ref_val = AD4691_VREF_3P0;
+ break;
+ case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX:
+ ref_val = AD4691_VREF_3P3;
+ break;
+ case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX:
+ ref_val = AD4691_VREF_4P096;
+ break;
+ case AD4691_VREF_4P096_uV_MAX + 1 ... 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);
+ }
+
+ val = FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val);
+ if (st->refbuf_en)
+ val |= AD4691_REFBUF_EN;
+
+ ret = regmap_update_bits(st->regmap, AD4691_REF_CTRL,
+ AD4691_REF_CTRL_MASK | AD4691_REFBUF_EN, val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
+
+ ret = regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_LDO_EN, st->ldo_en);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n");
+
+ /*
+ * Set the internal oscillator to the highest rate this chip supports.
+ * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those
+ * chips start at index 1 (500 kHz).
+ */
+ ret = regmap_assign_bits(st->regmap, AD4691_OSC_FREQ_REG,
+ AD4691_OSC_FREQ_MASK,
+ (st->info->max_rate == 1 * HZ_PER_MHZ) ? 0 : 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n");
+
+ ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+ AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
+
+ return 0;
+}
+
+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);
+ st->info = spi_get_device_match_data(spi);
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ st->regmap = devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "Failed to initialize regmap\n");
+
+ ret = ad4691_regulator_setup(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->info->name;
+ indio_dev->info = &ad4691_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ indio_dev->channels = st->info->channels;
+ indio_dev->num_channels = st->info->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4691_of_match[] = {
+ { .compatible = "adi,ad4691", .data = &ad4691_chip_info },
+ { .compatible = "adi,ad4692", .data = &ad4692_chip_info },
+ { .compatible = "adi,ad4693", .data = &ad4693_chip_info },
+ { .compatible = "adi,ad4694", .data = &ad4694_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad4691_of_match);
+
+static const struct spi_device_id ad4691_id[] = {
+ { "ad4691", (kernel_ulong_t)&ad4691_chip_info },
+ { "ad4692", (kernel_ulong_t)&ad4692_chip_info },
+ { "ad4693", (kernel_ulong_t)&ad4693_chip_info },
+ { "ad4694", (kernel_ulong_t)&ad4694_chip_info },
+ { }
+};
+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] 23+ messages in thread
* [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support
2026-04-03 11:03 [PATCH v6 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 1/4] dt-bindings: iio: adc: add AD4691 family Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 2/4] iio: adc: ad4691: add initial driver for " Radu Sabau via B4 Relay
@ 2026-04-03 11:03 ` Radu Sabau via B4 Relay
2026-04-04 15:12 ` David Lechner
2026-04-03 11:03 ` [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
3 siblings, 1 reply; 23+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-04-03 11:03 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add buffered capture support using the IIO triggered buffer framework.
CNV Burst Mode: the GP pin identified by interrupt-names in the device
tree is configured as DATA_READY output. The IRQ handler stops
conversions and fires the IIO trigger; the trigger handler executes a
pre-built SPI message that reads all active channels from the AVG_IN
accumulator registers and then resets accumulator state and restarts
conversions for the next cycle.
Manual Mode: CNV is tied to SPI CS so each transfer simultaneously
reads the previous result and starts the next conversion (pipelined
N+1 scheme). At preenable time a pre-built, optimised SPI message of
N+1 transfers is constructed (N channel reads plus one NOOP to drain
the pipeline). The trigger handler executes the message in a single
spi_sync() call and collects the results. An external trigger (e.g.
iio-trig-hrtimer) is required to drive the trigger at the desired
sample rate.
Both modes share the same trigger handler and push a complete scan —
one u16 slot per channel at its scan_index position, followed by a
timestamp — to the IIO buffer via iio_push_to_buffers_with_ts().
The CNV Burst Mode sampling frequency (PWM period) is exposed as a
buffer-level attribute via IIO_DEVICE_ATTR.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ad4691.c | 592 ++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 592 insertions(+), 2 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 43bd408c3d11..f2a7273e43b9 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -5,15 +5,19 @@
*/
#include <linux/array_size.h>
#include <linux/bitfield.h>
+#include <linux/bitmap.h>
#include <linux/bitops.h>
#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/device/devres.h>
#include <linux/err.h>
+#include <linux/interrupt.h>
#include <linux/math.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
@@ -21,7 +25,12 @@
#include <linux/units.h>
#include <linux/unaligned.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
#define AD4691_VREF_uV_MIN 2400000
#define AD4691_VREF_uV_MAX 5250000
@@ -30,6 +39,8 @@
#define AD4691_VREF_3P3_uV_MAX 3750000
#define AD4691_VREF_4P096_uV_MAX 4500000
+#define AD4691_CNV_DUTY_CYCLE_NS 380
+
#define AD4691_SPI_CONFIG_A_REG 0x000
#define AD4691_SW_RESET (BIT(7) | BIT(0))
@@ -37,6 +48,7 @@
#define AD4691_CLAMP_STATUS1_REG 0x01A
#define AD4691_CLAMP_STATUS2_REG 0x01B
#define AD4691_DEVICE_SETUP 0x020
+#define AD4691_MANUAL_MODE BIT(2)
#define AD4691_LDO_EN BIT(4)
#define AD4691_REF_CTRL 0x021
#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
@@ -44,13 +56,18 @@
#define AD4691_OSC_FREQ_REG 0x023
#define AD4691_OSC_FREQ_MASK GENMASK(3, 0)
#define AD4691_STD_SEQ_CONFIG 0x025
+#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
#define AD4691_SPARE_CONTROL 0x02A
+#define AD4691_NOOP 0x00
+#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
+
#define AD4691_OSC_EN_REG 0x180
#define AD4691_STATE_RESET_REG 0x181
#define AD4691_STATE_RESET_ALL 0x01
#define AD4691_ADC_SETUP 0x182
#define AD4691_ADC_MODE_MASK GENMASK(1, 0)
+#define AD4691_CNV_BURST_MODE 0x01
#define AD4691_AUTONOMOUS_MODE 0x02
/*
* ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
@@ -60,6 +77,8 @@
#define AD4691_ACC_DEPTH_IN(n) (0x186 + (n))
#define AD4691_GPIO_MODE1_REG 0x196
#define AD4691_GPIO_MODE2_REG 0x197
+#define AD4691_GP_MODE_MASK GENMASK(3, 0)
+#define AD4691_GP_MODE_DATA_READY 0x06
#define AD4691_GPIO_READ 0x1A0
#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
@@ -95,9 +114,11 @@ struct ad4691_chip_info {
.type = IIO_VOLTAGE, \
.indexed = 1, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
- | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ | BIT(IIO_CHAN_INFO_SAMP_FREQ) \
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_separate_available = \
- BIT(IIO_CHAN_INFO_SAMP_FREQ), \
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) \
+ | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
.channel = ch, \
.scan_index = ch, \
@@ -125,6 +146,7 @@ static const struct iio_chan_spec ad4691_channels[] = {
AD4691_CHANNEL(13),
AD4691_CHANNEL(14),
AD4691_CHANNEL(15),
+ IIO_CHAN_SOFT_TIMESTAMP(16),
};
static const struct iio_chan_spec ad4693_channels[] = {
@@ -136,6 +158,7 @@ static const struct iio_chan_spec ad4693_channels[] = {
AD4691_CHANNEL(5),
AD4691_CHANNEL(6),
AD4691_CHANNEL(7),
+ IIO_CHAN_SOFT_TIMESTAMP(8),
};
/*
@@ -162,6 +185,14 @@ static const int ad4691_osc_freqs_Hz[] = {
[0xF] = 1250,
};
+static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" };
+
+/*
+ * Valid ACC_DEPTH values where the effective divisor equals the count.
+ * From Table 13: ACC_DEPTH = 2^N yields right-shift = N, divisor = 2^N.
+ */
+static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
+
static const struct ad4691_chip_info ad4691_chip_info = {
.channels = ad4691_channels,
.name = "ad4691",
@@ -193,16 +224,55 @@ static const struct ad4691_chip_info ad4694_chip_info = {
struct ad4691_state {
const struct ad4691_chip_info *info;
struct regmap *regmap;
+
+ struct pwm_device *conv_trigger;
+ int irq;
+
+ bool manual_mode;
+
int vref_uV;
+ u8 osr[16];
bool refbuf_en;
bool ldo_en;
+ u32 cnv_period_ns;
/*
* Synchronize access to members of the driver state, and ensure
* atomicity of consecutive SPI operations.
*/
struct mutex lock;
+ /*
+ * Per-buffer-enable lifetime resources:
+ * Manual Mode - a pre-built SPI message that clocks out N+1
+ * transfers in one go.
+ * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
+ * transfers in one go.
+ */
+ struct spi_message scan_msg;
+ struct spi_transfer *scan_xfers;
+ __be16 *scan_tx;
+ __be16 *scan_rx;
+ /* Scan buffer: one slot per channel plus timestamp */
+ struct {
+ u16 vals[16];
+ aligned_s64 ts;
+ } scan __aligned(IIO_DMA_MINALIGN);
};
+/*
+ * Configure the given GP pin (0-3) as DATA_READY output.
+ * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.
+ * Even pins occupy bits [3:0], odd pins bits [7:4].
+ */
+static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
+{
+ unsigned int shift = 4 * (gp_num % 2);
+
+ return regmap_update_bits(st->regmap,
+ AD4691_GPIO_MODE1_REG + gp_num / 2,
+ AD4691_GP_MODE_MASK << shift,
+ AD4691_GP_MODE_DATA_READY << shift);
+}
+
static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct spi_device *spi = context;
@@ -362,6 +432,24 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
return -EINVAL;
}
+static int ad4691_set_oversampling_ratio(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int osr)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ if (osr < 1 || osr > 32 || !is_power_of_2(osr))
+ return -EINVAL;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ st->osr[chan->scan_index] = osr;
+ return regmap_write(st->regmap,
+ AD4691_ACC_DEPTH_IN(chan->scan_index), osr);
+}
+
static int ad4691_read_avail(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
const int **vals, int *type,
@@ -376,6 +464,11 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
*type = IIO_VAL_INT;
*length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
return IIO_AVAIL_LIST;
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *vals = ad4691_oversampling_ratios;
+ *type = IIO_VAL_INT;
+ *length = ARRAY_SIZE(ad4691_oversampling_ratios);
+ return IIO_AVAIL_LIST;
default:
return -EINVAL;
}
@@ -406,6 +499,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
if (ret)
return ret;
+ ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->scan_index),
+ st->osr[chan->scan_index]);
+ if (ret)
+ return ret;
+
ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
if (ret)
return ret;
@@ -452,6 +550,9 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
}
case IIO_CHAN_INFO_SAMP_FREQ:
return ad4691_get_sampling_freq(st, val);
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ *val = st->osr[chan->scan_index];
+ return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
*val = st->vref_uV / (MICRO / MILLI);
*val2 = chan->scan_type.realbits;
@@ -468,6 +569,8 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
switch (mask) {
case IIO_CHAN_INFO_SAMP_FREQ:
return ad4691_set_sampling_freq(indio_dev, val);
+ case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
+ return ad4691_set_oversampling_ratio(indio_dev, chan, val);
default:
return -EINVAL;
}
@@ -486,6 +589,385 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
return regmap_write(st->regmap, reg, writeval);
}
+static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq)
+{
+ if (!freq)
+ return -EINVAL;
+
+ st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);
+ return 0;
+}
+
+static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
+{
+ struct pwm_state conv_state = {
+ .period = st->cnv_period_ns,
+ .duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,
+ .polarity = PWM_POLARITY_NORMAL,
+ .enabled = enable,
+ };
+
+ return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
+}
+
+/*
+ * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
+ *
+ * Configures the ADC hardware registers for the mode selected at probe
+ * (CNV_BURST or MANUAL). Called from buffer preenable 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->manual_mode)
+ return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
+
+ ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+ AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+}
+
+/*
+ * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
+ *
+ * Called from buffer postdisable 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)
+{
+ if (st->manual_mode)
+ return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_MANUAL_MODE, 0);
+
+ return regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
+ AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
+}
+
+static void ad4691_free_scan_bufs(struct ad4691_state *st)
+{
+ kfree(st->scan_xfers);
+ kfree(st->scan_tx);
+ kfree(st->scan_rx);
+}
+
+static int ad4691_manual_buffer_preenable(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);
+ unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ unsigned int n_xfers = n_active + 1;
+ unsigned int k, i;
+ int ret;
+
+ st->scan_xfers = kcalloc(n_xfers, sizeof(*st->scan_xfers), GFP_KERNEL);
+ if (!st->scan_xfers)
+ return -ENOMEM;
+
+ st->scan_tx = kcalloc(n_xfers, sizeof(*st->scan_tx), GFP_KERNEL);
+ if (!st->scan_tx) {
+ kfree(st->scan_xfers);
+ return -ENOMEM;
+ }
+
+ st->scan_rx = kcalloc(n_xfers, sizeof(*st->scan_rx), GFP_KERNEL);
+ if (!st->scan_rx) {
+ kfree(st->scan_tx);
+ kfree(st->scan_xfers);
+ return -ENOMEM;
+ }
+
+ spi_message_init(&st->scan_msg);
+
+ k = 0;
+ iio_for_each_active_channel(indio_dev, i) {
+ st->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));
+ st->scan_xfers[k].tx_buf = &st->scan_tx[k];
+ st->scan_xfers[k].rx_buf = &st->scan_rx[k];
+ st->scan_xfers[k].len = sizeof(__be16);
+ st->scan_xfers[k].cs_change = 1;
+ spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
+ k++;
+ }
+
+ /* Final NOOP transfer to retrieve last channel's result. */
+ st->scan_tx[k] = cpu_to_be16(AD4691_NOOP);
+ st->scan_xfers[k].tx_buf = &st->scan_tx[k];
+ st->scan_xfers[k].rx_buf = &st->scan_rx[k];
+ st->scan_xfers[k].len = sizeof(__be16);
+ spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
+
+ st->scan_msg.spi = spi;
+
+ ret = spi_optimize_message(spi, &st->scan_msg);
+ if (ret) {
+ ad4691_free_scan_bufs(st);
+ return ret;
+ }
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret) {
+ spi_unoptimize_message(&st->scan_msg);
+ ad4691_free_scan_bufs(st);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = ad4691_exit_conversion_mode(st);
+ spi_unoptimize_message(&st->scan_msg);
+ ad4691_free_scan_bufs(st);
+ return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {
+ .preenable = &ad4691_manual_buffer_preenable,
+ .postdisable = &ad4691_manual_buffer_postdisable,
+};
+
+static int ad4691_cnv_burst_buffer_preenable(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);
+ unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ unsigned int bit, k, i;
+ int ret;
+
+ st->scan_xfers = kcalloc(2 * n_active, sizeof(*st->scan_xfers), GFP_KERNEL);
+ if (!st->scan_xfers)
+ return -ENOMEM;
+
+ st->scan_tx = kcalloc(n_active, sizeof(*st->scan_tx), GFP_KERNEL);
+ if (!st->scan_tx) {
+ kfree(st->scan_xfers);
+ return -ENOMEM;
+ }
+
+ st->scan_rx = kcalloc(n_active, sizeof(*st->scan_rx), GFP_KERNEL);
+ if (!st->scan_rx) {
+ kfree(st->scan_tx);
+ kfree(st->scan_xfers);
+ return -ENOMEM;
+ }
+
+ spi_message_init(&st->scan_msg);
+
+ /*
+ * Each AVG_IN read needs two transfers: a 2-byte address write phase
+ * followed by a 2-byte data read phase. CS toggles between channels
+ * (cs_change=1 on the read phase of all but the last channel).
+ */
+ k = 0;
+ iio_for_each_active_channel(indio_dev, i) {
+ st->scan_tx[k] = cpu_to_be16(0x8000 | AD4691_AVG_IN(i));
+ st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
+ st->scan_xfers[2 * k].len = sizeof(__be16);
+ spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
+ st->scan_xfers[2 * k + 1].rx_buf = &st->scan_rx[k];
+ st->scan_xfers[2 * k + 1].len = sizeof(__be16);
+ if (k < n_active - 1)
+ st->scan_xfers[2 * k + 1].cs_change = 1;
+ spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
+ k++;
+ }
+
+ st->scan_msg.spi = spi;
+
+ ret = spi_optimize_message(spi, &st->scan_msg);
+ if (ret)
+ goto err_free_bufs;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ bitmap_read(indio_dev->active_scan_mask, 0,
+ iio_get_masklength(indio_dev)));
+ if (ret)
+ goto err;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+ ~bitmap_read(indio_dev->active_scan_mask, 0,
+ iio_get_masklength(indio_dev)) & GENMASK(15, 0));
+ if (ret)
+ goto err;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
+ st->osr[bit]);
+ if (ret)
+ goto err;
+ }
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ goto err;
+
+ ret = ad4691_sampling_enable(st, true);
+ if (ret)
+ goto err;
+
+ enable_irq(st->irq);
+ return 0;
+err:
+ spi_unoptimize_message(&st->scan_msg);
+err_free_bufs:
+ ad4691_free_scan_bufs(st);
+ return ret;
+}
+
+static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ int ret;
+
+ disable_irq(st->irq);
+
+ ret = ad4691_sampling_enable(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ AD4691_SEQ_ALL_CHANNELS_OFF);
+ if (ret)
+ return ret;
+
+ ret = ad4691_exit_conversion_mode(st);
+ spi_unoptimize_message(&st->scan_msg);
+ ad4691_free_scan_bufs(st);
+ return ret;
+}
+
+static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
+ .preenable = &ad4691_cnv_burst_buffer_preenable,
+ .postdisable = &ad4691_cnv_burst_buffer_postdisable,
+};
+
+static ssize_t sampling_frequency_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
+}
+
+static ssize_t sampling_frequency_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+ struct ad4691_state *st = iio_priv(indio_dev);
+ int freq, ret;
+
+ ret = kstrtoint(buf, 10, &freq);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ if (iio_buffer_enabled(indio_dev))
+ return -EBUSY;
+
+ ret = ad4691_set_pwm_freq(st, freq);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static IIO_DEVICE_ATTR(sampling_frequency, 0644,
+ sampling_frequency_show,
+ sampling_frequency_store, 0);
+
+static const struct iio_dev_attr *ad4691_buffer_attrs[] = {
+ &iio_dev_attr_sampling_frequency,
+ NULL
+};
+
+static irqreturn_t ad4691_irq(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ /*
+ * GPx 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(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
+static const struct iio_trigger_ops ad4691_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int i, k = 0;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ ret = spi_sync(st->scan_msg.spi, &st->scan_msg);
+ if (ret)
+ return ret;
+
+ if (st->manual_mode) {
+ iio_for_each_active_channel(indio_dev, i) {
+ st->scan.vals[i] = be16_to_cpu(st->scan_rx[k + 1]);
+ k++;
+ }
+ } else {
+ iio_for_each_active_channel(indio_dev, i) {
+ st->scan.vals[i] = be16_to_cpu(st->scan_rx[k]);
+ k++;
+ }
+
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ ret = ad4691_sampling_enable(st, true);
+ if (ret)
+ return ret;
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+ timestamp);
+ return 0;
+}
+
+static irqreturn_t ad4691_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+
+ ad4691_read_scan(indio_dev, pf->timestamp);
+ 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,
@@ -493,6 +975,18 @@ static const struct iio_info ad4691_info = {
.debugfs_reg_access = &ad4691_reg_access,
};
+static int ad4691_pwm_setup(struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+
+ 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");
+
+ return ad4691_set_pwm_freq(st, st->info->max_rate);
+}
+
static int ad4691_regulator_setup(struct ad4691_state *st)
{
struct device *dev = regmap_get_device(st->regmap);
@@ -558,6 +1052,22 @@ static int ad4691_config(struct ad4691_state *st)
unsigned int val;
int ret;
+ /*
+ * Determine buffer conversion mode from DT: if a PWM is provided it
+ * drives the CNV pin (CNV_BURST_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->manual_mode = false;
+ ret = ad4691_pwm_setup(st);
+ if (ret)
+ return ret;
+ } else {
+ st->manual_mode = true;
+ }
+
switch (st->vref_uV) {
case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
ref_val = AD4691_VREF_2P5;
@@ -613,6 +1123,78 @@ static int ad4691_config(struct ad4691_state *st)
return 0;
}
+static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
+ struct ad4691_state *st)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct iio_trigger *trig;
+ unsigned int i;
+ int irq, ret;
+
+ trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!trig)
+ return -ENOMEM;
+
+ trig->ops = &ad4691_trigger_ops;
+ iio_trigger_set_drvdata(trig, st);
+
+ ret = devm_iio_trigger_register(dev, trig);
+ if (ret)
+ return dev_err_probe(dev, ret, "IIO trigger register failed\n");
+
+ indio_dev->trig = iio_trigger_get(trig);
+
+ if (!st->manual_mode) {
+ /*
+ * The GP pin named in interrupt-names 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.
+ * The IRQ is kept disabled until the buffer is enabled.
+ */
+ irq = -ENODEV;
+ for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {
+ irq = fwnode_irq_get_byname(dev_fwnode(dev),
+ ad4691_gp_names[i]);
+ if (irq > 0)
+ break;
+ }
+ if (irq <= 0)
+ return dev_err_probe(dev, irq < 0 ? irq : -ENODEV,
+ "failed to get GP interrupt\n");
+
+ st->irq = irq;
+
+ ret = ad4691_gpio_setup(st, i);
+ if (ret)
+ return ret;
+
+ /*
+ * IRQ is kept disabled until the buffer is enabled to prevent
+ * spurious DATA_READY events before the SPI message is set up.
+ */
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &ad4691_irq,
+ IRQF_ONESHOT | IRQF_NO_AUTOEN,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return ret;
+
+ return devm_iio_triggered_buffer_setup_ext(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad4691_trigger_handler,
+ IIO_BUFFER_DIRECTION_IN,
+ &ad4691_cnv_burst_buffer_setup_ops,
+ ad4691_buffer_attrs);
+ }
+
+ return devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad4691_trigger_handler,
+ &ad4691_manual_buffer_setup_ops);
+}
+
static int ad4691_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
@@ -627,6 +1209,8 @@ static int ad4691_probe(struct spi_device *spi)
st = iio_priv(indio_dev);
st->info = spi_get_device_match_data(spi);
+ memset(st->osr, 1, sizeof(st->osr));
+
ret = devm_mutex_init(dev, &st->lock);
if (ret)
return ret;
@@ -655,6 +1239,10 @@ static int ad4691_probe(struct spi_device *spi)
indio_dev->channels = st->info->channels;
indio_dev->num_channels = st->info->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] 23+ messages in thread
* [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-03 11:03 [PATCH v6 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
` (2 preceding siblings ...)
2026-04-03 11:03 ` [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
@ 2026-04-03 11:03 ` Radu Sabau via B4 Relay
2026-04-04 15:34 ` David Lechner
2026-04-04 15:57 ` David Lechner
3 siblings, 2 replies; 23+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-04-03 11:03 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc, 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 Burst Mode and Manual Mode support offload, but use different
trigger mechanisms:
CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY
signal on the GP pin specified by the trigger-source consumer reference
in the device tree (one cell = GP pin number 0-3). 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
GP/DATA_READY output via devm_spi_offload_trigger_register() so the
offload framework can match the '#trigger-source-cells' phandle and
automatically fire the SPI Engine DMA transfer at end-of-conversion.
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. This patch promotes the channel scan_type from
storagebits=16 (triggered-buffer path) to storagebits=32 to match the
DMA word size; the triggered-buffer paths are updated to the same layout
for consistency. CNV Burst Mode channel data arrives in the lower 16
bits of the 32-bit word (shift=0); Manual Mode data arrives in the upper
16 bits (shift=16), matching the 4-byte SPI transfer layout
[data_hi, data_lo, 0, 0]. A separate ad4691_manual_channels[] array
encodes the shift=16 scan type for manual mode.
Add driver documentation under Documentation/iio/ad4691.rst covering
operating modes, oversampling, reference voltage, SPI offload paths,
and buffer data layout; register in MAINTAINERS and index.rst
Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
Documentation/iio/ad4691.rst | 259 ++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad4691.c | 422 ++++++++++++++++++++++++++++++++++++++++++-
5 files changed, 676 insertions(+), 8 deletions(-)
diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst
new file mode 100644
index 000000000000..36f0c841605a
--- /dev/null
+++ b/Documentation/iio/ad4691.rst
@@ -0,0 +1,259 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=============
+AD4691 driver
+=============
+
+ADC driver for Analog Devices Inc. AD4691 family of multichannel SAR ADCs.
+The module name is ``ad4691``.
+
+
+Supported devices
+=================
+
+The following chips are supported by this driver:
+
+* `AD4691 <https://www.analog.com/en/products/ad4691.html>`_ — 16-channel, 500 kSPS
+* `AD4692 <https://www.analog.com/en/products/ad4692.html>`_ — 16-channel, 1 MSPS
+* `AD4693 <https://www.analog.com/en/products/ad4693.html>`_ — 8-channel, 500 kSPS
+* `AD4694 <https://www.analog.com/en/products/ad4694.html>`_ — 8-channel, 1 MSPS
+
+
+IIO channels
+============
+
+Each physical ADC input maps to one IIO voltage channel. The AD4691 and AD4692
+expose 16 channels (``voltage0`` through ``voltage15``); the AD4693 and AD4694
+expose 8 channels (``voltage0`` through ``voltage7``).
+
+All channels share a common scale (``in_voltage_scale``), derived from the
+reference voltage. Each channel independently exposes:
+
+* ``in_voltageN_raw`` — single-shot ADC result
+* ``in_voltageN_sampling_frequency`` — internal oscillator frequency used for
+ single-shot reads and CNV Burst Mode buffered captures
+* ``in_voltageN_sampling_frequency_available`` — list of valid oscillator
+ frequencies
+* ``in_voltageN_oversampling_ratio`` — per-channel hardware accumulation depth
+* ``in_voltageN_oversampling_ratio_available`` — list of valid ratios
+
+
+Operating modes
+===============
+
+The driver supports two operating modes, auto-detected from the device tree at
+probe time. Both modes transition to and from an internal Autonomous Mode idle
+state when the IIO buffer is enabled and disabled.
+
+Manual Mode
+-----------
+
+Selected when no ``pwms`` property is present in the device tree. The CNV pin
+is tied to the SPI chip-select: every CS assertion both triggers a new
+conversion and returns the result of the previous one (pipelined N+1 scheme).
+
+To read N channels the driver issues N+1 SPI transfers in a single optimised
+message:
+
+* Transfers 0 to N-1 each carry ``AD4691_ADC_CHAN(n)`` in the TX byte to
+ select the next channel; the RX byte of transfer ``k+1`` contains the result
+ of the channel selected in transfer ``k``.
+* Transfer N is a NOOP (0x00) to flush the last conversion result out of the
+ pipeline.
+
+The external IIO trigger (``pollfunc_store_time``) drives the trigger handler,
+which executes the pre-built SPI message and pushes the scan to the buffer.
+
+CNV Burst Mode
+--------------
+
+Selected when a ``pwms`` property is present in the device tree. The PWM drives
+the CNV pin independently of SPI at the configured conversion rate, and a GP
+pin (identified by ``interrupt-names``) asserts DATA_READY at end-of-burst to
+signal that the AVG_IN result registers are ready to be read.
+
+The IRQ handler stops the PWM, fires the IIO trigger, and the trigger handler
+reads all active ``AVG_IN(n)`` registers in a single optimised SPI message and
+pushes the scan to the buffer.
+
+The buffer sampling frequency (i.e. the PWM rate) is controlled by the
+``sampling_frequency`` attribute on the IIO buffer. Valid values span from the
+chip's minimum oscillator rate up to its maximum conversion rate
+(500 kSPS for AD4691/AD4693, 1 MSPS for AD4692/AD4694).
+
+Autonomous Mode (idle / single-shot)
+-------------------------------------
+
+The chip idles in Autonomous Mode whenever the IIO buffer is disabled. In this
+state, ``read_raw`` requests (``in_voltageN_raw``) use the internal oscillator
+to perform a single conversion on the requested channel and read back the
+result from the ``AVG_IN(N)`` register. The oscillator is started and stopped
+for each read to save power.
+
+
+Oversampling
+============
+
+Each channel has an independent hardware accumulator (ACC_DEPTH_IN) that
+averages a configurable number of successive conversions before DATA_READY
+asserts. The result is always returned as a 16-bit mean from the ``AVG_IN``
+register, so the IIO ``realbits`` and ``storagebits`` are unaffected by the
+oversampling ratio.
+
+Valid ratios are 1, 2, 4, 8, 16 and 32. The default is 1 (no averaging).
+
+.. code-block:: bash
+
+ # Set oversampling ratio to 16 on channel 0
+ echo 16 > /sys/bus/iio/devices/iio:device0/in_voltage0_oversampling_ratio
+
+When OSR > 1 the effective conversion rate for ``read_raw`` is reduced
+accordingly, since the driver waits for 2 × OSR oscillator periods before
+reading the result.
+
+
+Reference voltage
+=================
+
+The driver supports two reference configurations, mutually exclusive:
+
+* **External reference** (``ref-supply``): a voltage between 2.4 V and 5.25 V
+ supplied externally. The internal reference buffer is disabled.
+* **Buffered internal reference** (``refin-supply``): An internal reference
+ buffer is used. The driver enables ``REFBUF_EN`` in the REF_CTRL register
+ when this supply is used.
+
+Exactly one of ``ref-supply`` or ``refin-supply`` must be present in the
+device tree.
+
+The reference voltage determines the full-scale range:
+
+.. code-block::
+
+ full-scale = Vref / 2^16 (per LSB)
+
+
+LDO supply
+==========
+
+The chip contains an internal LDO that powers part of the analog front-end.
+The LDO input can be driven externally via the ``ldo-in-supply`` regulator. If
+that supply is absent, the driver enables the internal LDO path (``LDO_EN``
+bit in DEVICE_SETUP).
+
+
+Reset
+=====
+
+The driver supports two reset mechanisms:
+
+* **Hardware reset** (``reset-gpios`` in device tree): the GPIO is already
+ asserted at driver probe by the reset controller framework. The driver waits
+ for the required 300 µs reset pulse width and then deasserts.
+* **Software reset** (fallback when ``reset-gpios`` is absent): the driver
+ writes the software-reset pattern to the SPI_CONFIG_A register.
+
+
+GP pins and interrupts
+======================
+
+The chip exposes up to four general-purpose (GP) pins that can be configured as
+interrupt outputs. In CNV Burst Mode (non-offload), one GP pin must be wired to
+an interrupt-capable SoC input and declared in the device tree using the
+``interrupts`` and ``interrupt-names`` properties.
+
+The ``interrupt-names`` value identifies which GP pin is used (``"gp0"``
+through ``"gp3"``). The driver configures that pin as a DATA_READY output in
+the GPIO_MODE register.
+
+Example device tree fragment::
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ ...
+ interrupts = <17 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-parent = <&gpio0>;
+ interrupt-names = "gp0";
+ };
+
+
+SPI offload support
+===================
+
+When a SPI offload engine (e.g. the AXI SPI Engine) is present, the driver
+uses DMA-backed transfers for CPU-independent, high-throughput data capture.
+SPI offload is detected automatically at probe via ``devm_spi_offload_get()``;
+if no offload hardware is available the driver falls back to the software
+triggered-buffer path.
+
+Two SPI offload sub-modes exist, corresponding to the two operating modes:
+
+CNV Burst offload
+-----------------
+
+Used when a ``pwms`` property is present and SPI offload is available.
+
+The PWM drives CNV at the configured rate. On DATA_READY the SPI offload
+engine automatically executes a pre-built message that reads all active
+``AVG_IN`` registers and streams the data directly to an IIO DMA buffer with
+no CPU involvement. A final state-reset transfer re-arms DATA_READY for the
+next burst.
+
+The GP pin used as DATA_READY trigger is supplied by the trigger-source
+consumer (via ``#trigger-source-cells``) at buffer enable time; no
+``interrupt-names`` entry is required in this path.
+
+The buffer sampling frequency is controlled by the ``sampling_frequency``
+attribute on the IIO buffer (same as the non-offload CNV Burst path).
+
+Manual offload
+--------------
+
+Used when no ``pwms`` property is present and SPI offload is available.
+
+A periodic SPI offload trigger controls the conversion rate. On each trigger
+period, the SPI engine executes an N+1 transfer message (same pipelined scheme
+as software Manual Mode) and streams the data directly to the IIO DMA buffer.
+
+The ``sampling_frequency`` attribute on the IIO buffer controls the trigger
+rate (in Hz). The default is the chip's maximum conversion rate.
+
+
+Buffer data format
+==================
+
+The IIO buffer data format (``in_voltageN_type``) depends on the active path:
+
++-------------------------+-------------+-------------+-------+
+| Path | storagebits | realbits | shift |
++=========================+=============+=============+=======+
+| Triggered buffer | 16 | 16 | 0 |
++-------------------------+-------------+-------------+-------+
+| CNV Burst offload (DMA) | 32 | 16 | 0 |
++-------------------------+-------------+-------------+-------+
+| Manual offload (DMA) | 32 | 16 | 16 |
++-------------------------+-------------+-------------+-------+
+
+In the triggered-buffer path the driver unpacks the 16-bit result in software
+before pushing to the buffer, so ``storagebits`` is 16.
+
+In the DMA offload paths the DMA engine writes 32-bit words directly into the
+IIO DMA buffer:
+
+* **CNV Burst offload**: the SPI engine reads AVG_IN registers with a 2-byte
+ address phase followed by a 2-byte data phase; the 16-bit result lands in
+ the lower half of the 32-bit word (``shift=0``).
+* **Manual offload**: each 32-bit SPI word carries the channel byte in the
+ first byte; the 16-bit result is returned in the upper half of the 32-bit
+ word (``shift=16``).
+
+The ``in_voltageN_type`` sysfs attribute reflects the active scan type.
+
+
+Unimplemented features
+======================
+
+* GPIO controller functionality of the GP pins
+* Clamp status and overrange events
+* Raw accumulator (ACC_IN) and accumulator status registers
+* ADC_BUSY and overrun status interrupts
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6a13..007e0a1fcc5a 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -23,6 +23,7 @@ Industrial I/O Kernel Drivers
ad4000
ad4030
ad4062
+ ad4691
ad4695
ad7191
ad7380
diff --git a/MAINTAINERS b/MAINTAINERS
index 24e4502b8292..875ea2455d91 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: Documentation/iio/ad4691.rst
F: drivers/iio/adc/ad4691.c
ANALOG DEVICES INC AD4695 DRIVER
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 f2a7273e43b9..cc2138e47feb 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -11,6 +11,7 @@
#include <linux/delay.h>
#include <linux/dev_printk.h>
#include <linux/device/devres.h>
+#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/math.h>
@@ -22,10 +23,14 @@
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.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/sysfs.h>
#include <linux/iio/trigger.h>
@@ -40,6 +45,7 @@
#define AD4691_VREF_4P096_uV_MAX 4500000
#define AD4691_CNV_DUTY_CYCLE_NS 380
+#define AD4691_CNV_HIGH_TIME_NS 430
#define AD4691_SPI_CONFIG_A_REG 0x000
#define AD4691_SW_RESET (BIT(7) | BIT(0))
@@ -92,6 +98,8 @@
#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
+#define AD4691_OFFLOAD_BITS_PER_WORD 32
+
static const char * const ad4691_supplies[] = { "avdd", "vio" };
enum ad4691_ref_ctrl {
@@ -109,6 +117,31 @@ struct ad4691_chip_info {
unsigned int max_rate;
};
+enum {
+ AD4691_SCAN_TYPE_NORMAL, /* triggered buffer: storagebits=16, shift=0 */
+ AD4691_SCAN_TYPE_OFFLOAD_CNV, /* CNV burst offload: storagebits=32, shift=0 */
+ AD4691_SCAN_TYPE_OFFLOAD_MANUAL, /* manual offload: storagebits=32, shift=16 */
+};
+
+static const struct iio_scan_type ad4691_scan_types[] = {
+ [AD4691_SCAN_TYPE_NORMAL] = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 16,
+ },
+ [AD4691_SCAN_TYPE_OFFLOAD_CNV] = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 32,
+ },
+ [AD4691_SCAN_TYPE_OFFLOAD_MANUAL] = {
+ .sign = 'u',
+ .realbits = 16,
+ .storagebits = 32,
+ .shift = 16,
+ },
+};
+
#define AD4691_CHANNEL(ch) \
{ \
.type = IIO_VOLTAGE, \
@@ -122,11 +155,9 @@ struct ad4691_chip_info {
.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
.channel = ch, \
.scan_index = ch, \
- .scan_type = { \
- .sign = 'u', \
- .realbits = 16, \
- .storagebits = 16, \
- }, \
+ .has_ext_scan_type = 1, \
+ .ext_scan_type = ad4691_scan_types, \
+ .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types), \
}
static const struct iio_chan_spec ad4691_channels[] = {
@@ -221,6 +252,17 @@ static const struct ad4691_chip_info ad4694_chip_info = {
.max_rate = 1 * HZ_PER_MHZ,
};
+struct ad4691_offload_state {
+ struct spi_offload *spi;
+ struct spi_offload_trigger *trigger;
+ u64 trigger_hz;
+ struct spi_message msg;
+ /* Max 16 channel xfers + 1 state-reset or NOOP */
+ struct spi_transfer xfer[17];
+ u8 tx_cmd[17][4];
+ u8 tx_reset[4];
+};
+
struct ad4691_state {
const struct ad4691_chip_info *info;
struct regmap *regmap;
@@ -251,6 +293,8 @@ struct ad4691_state {
struct spi_transfer *scan_xfers;
__be16 *scan_tx;
__be16 *scan_rx;
+ /* NULL when no SPI offload hardware is present */
+ struct ad4691_offload_state *offload;
/* Scan buffer: one slot per channel plus timestamp */
struct {
u16 vals[16];
@@ -273,6 +317,46 @@ static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
AD4691_GP_MODE_DATA_READY << shift);
}
+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)
+{
+ return type == SPI_OFFLOAD_TRIGGER_DATA_READY &&
+ nargs == 1 && args[0] <= 3;
+}
+
+static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ struct ad4691_state *st = spi_offload_trigger_get_priv(trigger);
+
+ if (nargs != 1)
+ return -EINVAL;
+
+ return ad4691_gpio_setup(st, (unsigned int)args[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 int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
{
struct spi_device *spi = context;
@@ -553,10 +637,17 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
*val = st->osr[chan->scan_index];
return IIO_VAL_INT;
- case IIO_CHAN_INFO_SCALE:
+ case IIO_CHAN_INFO_SCALE: {
+ const struct iio_scan_type *scan_type;
+
+ scan_type = iio_get_current_scan_type(indio_dev, chan);
+ if (IS_ERR(scan_type))
+ return PTR_ERR(scan_type);
+
*val = st->vref_uV / (MICRO / MILLI);
- *val2 = chan->scan_type.realbits;
+ *val2 = scan_type->realbits;
return IIO_VAL_FRACTIONAL_LOG2;
+ }
default:
return -EINVAL;
}
@@ -856,6 +947,213 @@ static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
.postdisable = &ad4691_cnv_burst_buffer_postdisable,
};
+static int ad4691_manual_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct ad4691_offload_state *offload = st->offload;
+ struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ };
+ unsigned int bit, k;
+ int ret;
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ memset(offload->xfer, 0, sizeof(offload->xfer));
+
+ /*
+ * N+1 transfers for N channels. Each CS-low period triggers
+ * a conversion AND returns the previous result (pipelined).
+ * TX: [AD4691_ADC_CHAN(n), 0x00, 0x00, 0x00]
+ * RX: [data_hi, data_lo, 0x00, 0x00] (shift=16)
+ * Transfer 0 RX is garbage; transfers 1..N carry real data.
+ */
+ k = 0;
+ iio_for_each_active_channel(indio_dev, bit) {
+ offload->tx_cmd[k][0] = AD4691_ADC_CHAN(bit);
+ offload->xfer[k].tx_buf = offload->tx_cmd[k];
+ offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
+ offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+ offload->xfer[k].cs_change = 1;
+ offload->xfer[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS;
+ offload->xfer[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+ /* First transfer RX is garbage — skip it. */
+ if (k > 0)
+ offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ k++;
+ }
+
+ /* Final NOOP to flush pipeline and capture last channel. */
+ offload->tx_cmd[k][0] = AD4691_NOOP;
+ offload->xfer[k].tx_buf = offload->tx_cmd[k];
+ offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
+ offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+ offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ k++;
+
+ spi_message_init_with_transfers(&offload->msg, offload->xfer, k);
+ offload->msg.offload = offload->spi;
+
+ ret = spi_optimize_message(spi, &offload->msg);
+ if (ret)
+ goto err_exit_conversion;
+
+ config.periodic.frequency_hz = offload->trigger_hz;
+ ret = spi_offload_trigger_enable(offload->spi, offload->trigger, &config);
+ if (ret)
+ goto err_unoptimize;
+
+ return 0;
+
+err_unoptimize:
+ spi_unoptimize_message(&offload->msg);
+err_exit_conversion:
+ ad4691_exit_conversion_mode(st);
+ return ret;
+}
+
+static int ad4691_manual_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct ad4691_offload_state *offload = st->offload;
+
+ spi_offload_trigger_disable(offload->spi, offload->trigger);
+ spi_unoptimize_message(&offload->msg);
+
+ return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_manual_offload_buffer_setup_ops = {
+ .postenable = &ad4691_manual_offload_buffer_postenable,
+ .predisable = &ad4691_manual_offload_buffer_predisable,
+};
+
+static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct ad4691_offload_state *offload = st->offload;
+ struct device *dev = regmap_get_device(st->regmap);
+ struct spi_device *spi = to_spi_device(dev);
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_DATA_READY,
+ };
+ unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ unsigned int bit, k;
+ int ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ bitmap_read(indio_dev->active_scan_mask, 0,
+ iio_get_masklength(indio_dev)));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
+ ~bitmap_read(indio_dev->active_scan_mask, 0,
+ iio_get_masklength(indio_dev)) & GENMASK(15, 0));
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
+ st->osr[bit]);
+ if (ret)
+ return ret;
+ }
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ memset(offload->xfer, 0, sizeof(offload->xfer));
+
+ /*
+ * N transfers to read N AVG_IN registers plus one state-reset
+ * transfer (no RX) to re-arm DATA_READY.
+ * TX: [reg_hi | 0x80, reg_lo, 0x00, 0x00]
+ * RX: [0x00, 0x00, data_hi, data_lo] (shift=0)
+ */
+ k = 0;
+ iio_for_each_active_channel(indio_dev, bit) {
+ unsigned int reg = AD4691_AVG_IN(bit);
+
+ offload->tx_cmd[k][0] = (reg >> 8) | 0x80;
+ offload->tx_cmd[k][1] = reg & 0xFF;
+ offload->xfer[k].tx_buf = offload->tx_cmd[k];
+ offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
+ offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+ offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ if (k < n_active - 1)
+ offload->xfer[k].cs_change = 1;
+ k++;
+ }
+
+ /* State reset to re-arm DATA_READY for the next scan. */
+ offload->tx_reset[0] = AD4691_STATE_RESET_REG >> 8;
+ offload->tx_reset[1] = AD4691_STATE_RESET_REG & 0xFF;
+ offload->tx_reset[2] = AD4691_STATE_RESET_ALL;
+ offload->xfer[k].tx_buf = offload->tx_reset;
+ offload->xfer[k].len = sizeof(offload->tx_reset);
+ offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
+ k++;
+
+ spi_message_init_with_transfers(&offload->msg, offload->xfer, k);
+ offload->msg.offload = offload->spi;
+
+ ret = spi_optimize_message(spi, &offload->msg);
+ if (ret)
+ goto err_exit_conversion;
+
+ ret = ad4691_sampling_enable(st, true);
+ if (ret)
+ goto err_unoptimize;
+
+ ret = spi_offload_trigger_enable(offload->spi, offload->trigger, &config);
+ if (ret)
+ goto err_sampling_disable;
+
+ return 0;
+
+err_sampling_disable:
+ ad4691_sampling_enable(st, false);
+err_unoptimize:
+ spi_unoptimize_message(&offload->msg);
+err_exit_conversion:
+ ad4691_exit_conversion_mode(st);
+ return ret;
+}
+
+static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct ad4691_offload_state *offload = st->offload;
+ int ret;
+
+ spi_offload_trigger_disable(offload->spi, offload->trigger);
+
+ ret = ad4691_sampling_enable(st, false);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ AD4691_SEQ_ALL_CHANNELS_OFF);
+ if (ret)
+ return ret;
+
+ spi_unoptimize_message(&offload->msg);
+
+ return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_setup_ops = {
+ .postenable = &ad4691_cnv_burst_offload_buffer_postenable,
+ .predisable = &ad4691_cnv_burst_offload_buffer_predisable,
+};
+
static ssize_t sampling_frequency_show(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -863,6 +1161,9 @@ static ssize_t sampling_frequency_show(struct device *dev,
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct ad4691_state *st = iio_priv(indio_dev);
+ if (st->manual_mode && st->offload)
+ return sysfs_emit(buf, "%llu\n", st->offload->trigger_hz);
+
return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
}
@@ -883,6 +1184,20 @@ static ssize_t sampling_frequency_store(struct device *dev,
if (iio_buffer_enabled(indio_dev))
return -EBUSY;
+ if (st->manual_mode && 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, &config);
+ if (ret)
+ return ret;
+
+ st->offload->trigger_hz = config.periodic.frequency_hz;
+ return len;
+ }
+
ret = ad4691_set_pwm_freq(st, freq);
if (ret)
return ret;
@@ -968,10 +1283,23 @@ static irqreturn_t ad4691_trigger_handler(int irq, void *p)
return IRQ_HANDLED;
}
+static int ad4691_get_current_scan_type(const struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ if (!st->offload)
+ return AD4691_SCAN_TYPE_NORMAL;
+ if (st->manual_mode)
+ return AD4691_SCAN_TYPE_OFFLOAD_MANUAL;
+ return AD4691_SCAN_TYPE_OFFLOAD_CNV;
+}
+
static const struct iio_info ad4691_info = {
.read_raw = &ad4691_read_raw,
.write_raw = &ad4691_write_raw,
.read_avail = &ad4691_read_avail,
+ .get_current_scan_type = &ad4691_get_current_scan_type,
.debugfs_reg_access = &ad4691_reg_access,
};
@@ -1195,9 +1523,75 @@ static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
&ad4691_manual_buffer_setup_ops);
}
+static int ad4691_setup_offload(struct iio_dev *indio_dev,
+ struct ad4691_state *st,
+ struct spi_offload *spi_offload)
+{
+ struct device *dev = regmap_get_device(st->regmap);
+ struct ad4691_offload_state *offload;
+ struct dma_chan *rx_dma;
+ int ret;
+
+ offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
+ if (!offload)
+ return -ENOMEM;
+
+ offload->spi = spi_offload;
+ st->offload = offload;
+
+ if (st->manual_mode) {
+ offload->trigger =
+ devm_spi_offload_trigger_get(dev, offload->spi,
+ SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(offload->trigger))
+ return dev_err_probe(dev, PTR_ERR(offload->trigger),
+ "Failed to get periodic offload trigger\n");
+
+ offload->trigger_hz = st->info->max_rate;
+ } 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");
+
+ offload->trigger =
+ devm_spi_offload_trigger_get(dev, offload->spi,
+ SPI_OFFLOAD_TRIGGER_DATA_READY);
+ if (IS_ERR(offload->trigger))
+ return dev_err_probe(dev, PTR_ERR(offload->trigger),
+ "Failed to get DATA_READY offload trigger\n");
+ }
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, offload->spi);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma),
+ "Failed to get offload RX DMA channel\n");
+
+ if (st->manual_mode)
+ indio_dev->setup_ops = &ad4691_manual_offload_buffer_setup_ops;
+ else
+ indio_dev->setup_ops = &ad4691_cnv_burst_offload_buffer_setup_ops;
+
+ ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
+ IIO_BUFFER_DIRECTION_IN);
+ if (ret)
+ return ret;
+
+ indio_dev->buffer->attrs = ad4691_buffer_attrs;
+
+ return 0;
+}
+
static int ad4691_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
+ struct spi_offload *spi_offload;
struct iio_dev *indio_dev;
struct ad4691_state *st;
int ret;
@@ -1232,6 +1626,13 @@ static int ad4691_probe(struct spi_device *spi)
if (ret)
return ret;
+ spi_offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
+ ret = PTR_ERR_OR_ZERO(spi_offload);
+ if (ret == -ENODEV)
+ spi_offload = NULL;
+ else if (ret)
+ return dev_err_probe(dev, ret, "Failed to get SPI offload\n");
+
indio_dev->name = st->info->name;
indio_dev->info = &ad4691_info;
indio_dev->modes = INDIO_DIRECT_MODE;
@@ -1239,7 +1640,10 @@ static int ad4691_probe(struct spi_device *spi)
indio_dev->channels = st->info->channels;
indio_dev->num_channels = st->info->num_channels;
- ret = ad4691_setup_triggered_buffer(indio_dev, st);
+ if (spi_offload)
+ ret = ad4691_setup_offload(indio_dev, st, spi_offload);
+ else
+ ret = ad4691_setup_triggered_buffer(indio_dev, st);
if (ret)
return ret;
@@ -1277,3 +1681,5 @@ 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");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
--
2.43.0
^ permalink raw reply related [flat|nested] 23+ messages in thread
* Re: [PATCH v6 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-04-03 11:03 ` [PATCH v6 2/4] iio: adc: ad4691: add initial driver for " Radu Sabau via B4 Relay
@ 2026-04-04 14:25 ` David Lechner
0 siblings, 0 replies; 23+ messages in thread
From: David Lechner @ 2026-04-04 14:25 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc
On 4/3/26 6:03 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.
>
> The chip idles in Autonomous Mode so that single-shot read_raw can use
> the internal oscillator without disturbing the hardware configuration.
>
> Three voltage supply domains are managed: avdd (required), vio, and a
> reference supply on either the REF pin (ref-supply, external buffer)
> or the REFIN pin (refin-supply, uses the on-chip reference buffer;
> REFBUF_EN is set accordingly). Hardware reset is performed via
> the reset controller framework; a software reset through SPI_CONFIG_A
> is used as fallback when no hardware reset is available.
>
> Accumulator channel masking for single-shot reads uses ACC_MASK_REG via
> an ADDR_DESCENDING SPI write, which covers both mask bytes in a single
> 16-bit transfer.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
This patch looks in good shape. Although I wouldn't mind using
MEGA/MICRO, etc. more in numbers with more than 3 or 4 zeros.
Reviewed-by: David Lechner <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support
2026-04-03 11:03 ` [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
@ 2026-04-04 15:12 ` David Lechner
2026-04-05 8:57 ` Andy Shevchenko
2026-04-06 9:22 ` Sabau, Radu bogdan
0 siblings, 2 replies; 23+ messages in thread
From: David Lechner @ 2026-04-04 15:12 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc
On 4/3/26 6:03 AM, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add buffered capture support using the IIO triggered buffer framework.
>
> CNV Burst Mode: the GP pin identified by interrupt-names in the device
> tree is configured as DATA_READY output. The IRQ handler stops
> conversions and fires the IIO trigger; the trigger handler executes a
> pre-built SPI message that reads all active channels from the AVG_IN
> accumulator registers and then resets accumulator state and restarts
> conversions for the next cycle.
>
> Manual Mode: CNV is tied to SPI CS so each transfer simultaneously
> reads the previous result and starts the next conversion (pipelined
> N+1 scheme). At preenable time a pre-built, optimised SPI message of
> N+1 transfers is constructed (N channel reads plus one NOOP to drain
> the pipeline). The trigger handler executes the message in a single
> spi_sync() call and collects the results. An external trigger (e.g.
> iio-trig-hrtimer) is required to drive the trigger at the desired
> sample rate.
>
> Both modes share the same trigger handler and push a complete scan —
> one u16 slot per channel at its scan_index position, followed by a
> timestamp — to the IIO buffer via iio_push_to_buffers_with_ts().
>
> The CNV Burst Mode sampling frequency (PWM period) is exposed as a
> buffer-level attribute via IIO_DEVICE_ATTR.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> drivers/iio/adc/Kconfig | 2 +
> drivers/iio/adc/ad4691.c | 592 ++++++++++++++++++++++++++++++++++++++++++++++-
> 2 files changed, 592 insertions(+), 2 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 43bd408c3d11..f2a7273e43b9 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -5,15 +5,19 @@
> */
> #include <linux/array_size.h>
> #include <linux/bitfield.h>
> +#include <linux/bitmap.h>
> #include <linux/bitops.h>
> #include <linux/cleanup.h>
> #include <linux/delay.h>
> #include <linux/dev_printk.h>
> #include <linux/device/devres.h>
> #include <linux/err.h>
> +#include <linux/interrupt.h>
> #include <linux/math.h>
> #include <linux/module.h>
> #include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> #include <linux/reset.h>
> @@ -21,7 +25,12 @@
> #include <linux/units.h>
> #include <linux/unaligned.h>
>
> +#include <linux/iio/buffer.h>
> #include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/triggered_buffer.h>
> +#include <linux/iio/trigger_consumer.h>
>
> #define AD4691_VREF_uV_MIN 2400000
> #define AD4691_VREF_uV_MAX 5250000
> @@ -30,6 +39,8 @@
> #define AD4691_VREF_3P3_uV_MAX 3750000
> #define AD4691_VREF_4P096_uV_MAX 4500000
>
> +#define AD4691_CNV_DUTY_CYCLE_NS 380
> +
> #define AD4691_SPI_CONFIG_A_REG 0x000
> #define AD4691_SW_RESET (BIT(7) | BIT(0))
>
> @@ -37,6 +48,7 @@
> #define AD4691_CLAMP_STATUS1_REG 0x01A
> #define AD4691_CLAMP_STATUS2_REG 0x01B
> #define AD4691_DEVICE_SETUP 0x020
> +#define AD4691_MANUAL_MODE BIT(2)
> #define AD4691_LDO_EN BIT(4)
> #define AD4691_REF_CTRL 0x021
> #define AD4691_REF_CTRL_MASK GENMASK(4, 2)
> @@ -44,13 +56,18 @@
> #define AD4691_OSC_FREQ_REG 0x023
> #define AD4691_OSC_FREQ_MASK GENMASK(3, 0)
> #define AD4691_STD_SEQ_CONFIG 0x025
> +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
> #define AD4691_SPARE_CONTROL 0x02A
>
> +#define AD4691_NOOP 0x00
> +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
> +
> #define AD4691_OSC_EN_REG 0x180
> #define AD4691_STATE_RESET_REG 0x181
> #define AD4691_STATE_RESET_ALL 0x01
> #define AD4691_ADC_SETUP 0x182
> #define AD4691_ADC_MODE_MASK GENMASK(1, 0)
> +#define AD4691_CNV_BURST_MODE 0x01
> #define AD4691_AUTONOMOUS_MODE 0x02
> /*
> * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a
> @@ -60,6 +77,8 @@
> #define AD4691_ACC_DEPTH_IN(n) (0x186 + (n))
> #define AD4691_GPIO_MODE1_REG 0x196
> #define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GP_MODE_MASK GENMASK(3, 0)
> +#define AD4691_GP_MODE_DATA_READY 0x06
> #define AD4691_GPIO_READ 0x1A0
> #define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> #define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> @@ -95,9 +114,11 @@ struct ad4691_chip_info {
> .type = IIO_VOLTAGE, \
> .indexed = 1, \
> .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
> - | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> .info_mask_separate_available = \
> - BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> + BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
> .channel = ch, \
> .scan_index = ch, \
> @@ -125,6 +146,7 @@ static const struct iio_chan_spec ad4691_channels[] = {
> AD4691_CHANNEL(13),
> AD4691_CHANNEL(14),
> AD4691_CHANNEL(15),
> + IIO_CHAN_SOFT_TIMESTAMP(16),
> };
>
> static const struct iio_chan_spec ad4693_channels[] = {
> @@ -136,6 +158,7 @@ static const struct iio_chan_spec ad4693_channels[] = {
> AD4691_CHANNEL(5),
> AD4691_CHANNEL(6),
> AD4691_CHANNEL(7),
> + IIO_CHAN_SOFT_TIMESTAMP(8),
> };
>
> /*
> @@ -162,6 +185,14 @@ static const int ad4691_osc_freqs_Hz[] = {
> [0xF] = 1250,
> };
>
> +static const char * const ad4691_gp_names[] = { "gp0", "gp1", "gp2", "gp3" };
> +
> +/*
> + * Valid ACC_DEPTH values where the effective divisor equals the count.
> + * From Table 13: ACC_DEPTH = 2^N yields right-shift = N, divisor = 2^N.
> + */
> +static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
It would be nice to add oversampling in a separate commit as that is a
separate feature.
Oversampling also affects sampling frequency. When there isn't oversampling,
sample rate == conversion rate. However, with oversampling, sample rate ==
conversion rate / oversampling ratio (because each sample involves #OSR
conversions).
So more code will be required to make IIO_CHAN_INFO_SAMP_FREQ attributes
(both read/write_raw and read_avail) adjust the values based on the current
oversampling ratio.
> +
> static const struct ad4691_chip_info ad4691_chip_info = {
> .channels = ad4691_channels,
> .name = "ad4691",
> @@ -193,16 +224,55 @@ static const struct ad4691_chip_info ad4694_chip_info = {
> struct ad4691_state {
> const struct ad4691_chip_info *info;
> struct regmap *regmap;
> +
> + struct pwm_device *conv_trigger;
> + int irq;
> +
> + bool manual_mode;
> +
> int vref_uV;
> + u8 osr[16];
> bool refbuf_en;
> bool ldo_en;
> + u32 cnv_period_ns;
> /*
> * Synchronize access to members of the driver state, and ensure
> * atomicity of consecutive SPI operations.
> */
> struct mutex lock;
> + /*
> + * Per-buffer-enable lifetime resources:
> + * Manual Mode - a pre-built SPI message that clocks out N+1
> + * transfers in one go.
> + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
> + * transfers in one go.
> + */
> + struct spi_message scan_msg;
> + struct spi_transfer *scan_xfers;
> + __be16 *scan_tx;
> + __be16 *scan_rx;
Why not embed these arrays here? Then we don't have to deal with
alloc/free later.
> + /* Scan buffer: one slot per channel plus timestamp */
> + struct {
> + u16 vals[16];
> + aligned_s64 ts;
> + } scan __aligned(IIO_DMA_MINALIGN);
Better would be IIO_DECLARE_BUFFER_WITH_TS() since we don't always
use all vals.
Also, current usage doesn't need to be DMA-safe because scan_tx
is being used for the actual SPI xfer.
> };
>
> +/*
> + * Configure the given GP pin (0-3) as DATA_READY output.
> + * GP0/GP1 → GPIO_MODE1_REG, GP2/GP3 → GPIO_MODE2_REG.
> + * Even pins occupy bits [3:0], odd pins bits [7:4].
> + */
> +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
> +{
> + unsigned int shift = 4 * (gp_num % 2);
> +
> + return regmap_update_bits(st->regmap,
> + AD4691_GPIO_MODE1_REG + gp_num / 2,
> + AD4691_GP_MODE_MASK << shift,
> + AD4691_GP_MODE_DATA_READY << shift);
> +}
> +
> static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> {
> struct spi_device *spi = context;
> @@ -362,6 +432,24 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq)
> return -EINVAL;
> }
>
> +static int ad4691_set_oversampling_ratio(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int osr)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + if (osr < 1 || osr > 32 || !is_power_of_2(osr))
> + return -EINVAL;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + st->osr[chan->scan_index] = osr;
> + return regmap_write(st->regmap,
> + AD4691_ACC_DEPTH_IN(chan->scan_index), osr);
> +}
> +
> static int ad4691_read_avail(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan,
> const int **vals, int *type,
> @@ -376,6 +464,11 @@ static int ad4691_read_avail(struct iio_dev *indio_dev,
> *type = IIO_VAL_INT;
> *length = ARRAY_SIZE(ad4691_osc_freqs_Hz) - start;
> return IIO_AVAIL_LIST;
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> + *vals = ad4691_oversampling_ratios;
> + *type = IIO_VAL_INT;
> + *length = ARRAY_SIZE(ad4691_oversampling_ratios);
> + return IIO_AVAIL_LIST;
> default:
> return -EINVAL;
> }
> @@ -406,6 +499,11 @@ static int ad4691_single_shot_read(struct iio_dev *indio_dev,
> if (ret)
> return ret;
>
> + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->scan_index),
> + st->osr[chan->scan_index]);
> + if (ret)
> + return ret;
> +
> ret = regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val);
> if (ret)
> return ret;
> @@ -452,6 +550,9 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
> }
> case IIO_CHAN_INFO_SAMP_FREQ:
> return ad4691_get_sampling_freq(st, val);
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> + *val = st->osr[chan->scan_index];
> + return IIO_VAL_INT;
> case IIO_CHAN_INFO_SCALE:
> *val = st->vref_uV / (MICRO / MILLI);
> *val2 = chan->scan_type.realbits;
> @@ -468,6 +569,8 @@ static int ad4691_write_raw(struct iio_dev *indio_dev,
> switch (mask) {
> case IIO_CHAN_INFO_SAMP_FREQ:
> return ad4691_set_sampling_freq(indio_dev, val);
> + case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> + return ad4691_set_oversampling_ratio(indio_dev, chan, val);
> default:
> return -EINVAL;
> }
> @@ -486,6 +589,385 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> return regmap_write(st->regmap, reg, writeval);
> }
>
> +static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq)
> +{
> + if (!freq)
> + return -EINVAL;
> +
> + st->cnv_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, freq);
> + return 0;
> +}
> +
> +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
> +{
> + struct pwm_state conv_state = {
> + .period = st->cnv_period_ns,
> + .duty_cycle = AD4691_CNV_DUTY_CYCLE_NS,
> + .polarity = PWM_POLARITY_NORMAL,
> + .enabled = enable,
> + };
> +
> + return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
> +}
> +
> +/*
> + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
> + *
> + * Configures the ADC hardware registers for the mode selected at probe
> + * (CNV_BURST or MANUAL). Called from buffer preenable 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->manual_mode)
> + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_MANUAL_MODE, AD4691_MANUAL_MODE);
> +
> + ret = regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> + AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE);
> + if (ret)
> + return ret;
> +
> + return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> +}
> +
> +/*
> + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
> + *
> + * Called from buffer postdisable 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)
> +{
> + if (st->manual_mode)
> + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP,
> + AD4691_MANUAL_MODE, 0);
> +
> + return regmap_update_bits(st->regmap, AD4691_ADC_SETUP,
> + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE);
> +}
> +
> +static void ad4691_free_scan_bufs(struct ad4691_state *st)
> +{
> + kfree(st->scan_xfers);
> + kfree(st->scan_tx);
> + kfree(st->scan_rx);
> +}
> +
> +static int ad4691_manual_buffer_preenable(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);
> + unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> + unsigned int n_xfers = n_active + 1;
> + unsigned int k, i;
> + int ret;
> +
> + st->scan_xfers = kcalloc(n_xfers, sizeof(*st->scan_xfers), GFP_KERNEL);
Usually, we make st->scan_xfers a fixed array with the max number of possible
xfers. Then we don't have to deal with alloc/free.
> + if (!st->scan_xfers)
> + return -ENOMEM;
> +
> + st->scan_tx = kcalloc(n_xfers, sizeof(*st->scan_tx), GFP_KERNEL);
> + if (!st->scan_tx) {
> + kfree(st->scan_xfers);
> + return -ENOMEM;
> + }
> +
> + st->scan_rx = kcalloc(n_xfers, sizeof(*st->scan_rx), GFP_KERNEL);
> + if (!st->scan_rx) {
> + kfree(st->scan_tx);
> + kfree(st->scan_xfers);
> + return -ENOMEM;
> + }
> +
> + spi_message_init(&st->scan_msg);
> +
> + k = 0;
> + iio_for_each_active_channel(indio_dev, i) {
> + st->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[k].rx_buf = &st->scan_rx[k];
> + st->scan_xfers[k].len = sizeof(__be16);
> + st->scan_xfers[k].cs_change = 1;
> + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> + k++;
> + }
> +
> + /* Final NOOP transfer to retrieve last channel's result. */
> + st->scan_tx[k] = cpu_to_be16(AD4691_NOOP);
> + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[k].rx_buf = &st->scan_rx[k];
> + st->scan_xfers[k].len = sizeof(__be16);
> + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> +
> + st->scan_msg.spi = spi;
This isn't how the SPI framework is intended to be used. We should
have st->spi = spi in probe instead.
> +
> + ret = spi_optimize_message(spi, &st->scan_msg);
> + if (ret) {
> + ad4691_free_scan_bufs(st);
> + return ret;
> + }
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret) {
> + spi_unoptimize_message(&st->scan_msg);
> + ad4691_free_scan_bufs(st);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = ad4691_exit_conversion_mode(st);
> + spi_unoptimize_message(&st->scan_msg);
> + ad4691_free_scan_bufs(st);
> + return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = {
> + .preenable = &ad4691_manual_buffer_preenable,
> + .postdisable = &ad4691_manual_buffer_postdisable,
> +};
> +
> +static int ad4691_cnv_burst_buffer_preenable(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);
> + unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> + unsigned int bit, k, i;
> + int ret;
> +
> + st->scan_xfers = kcalloc(2 * n_active, sizeof(*st->scan_xfers), GFP_KERNEL);
> + if (!st->scan_xfers)
> + return -ENOMEM;
> +
> + st->scan_tx = kcalloc(n_active, sizeof(*st->scan_tx), GFP_KERNEL);
> + if (!st->scan_tx) {
> + kfree(st->scan_xfers);
> + return -ENOMEM;
> + }
> +
> + st->scan_rx = kcalloc(n_active, sizeof(*st->scan_rx), GFP_KERNEL);
> + if (!st->scan_rx) {
> + kfree(st->scan_tx);
> + kfree(st->scan_xfers);
> + return -ENOMEM;
> + }
> +
> + spi_message_init(&st->scan_msg);
> +
> + /*
> + * Each AVG_IN read needs two transfers: a 2-byte address write phase
> + * followed by a 2-byte data read phase. CS toggles between channels
> + * (cs_change=1 on the read phase of all but the last channel).
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, i) {
> + st->scan_tx[k] = cpu_to_be16(0x8000 | AD4691_AVG_IN(i));
> + st->scan_xfers[2 * k].tx_buf = &st->scan_tx[k];
> + st->scan_xfers[2 * k].len = sizeof(__be16);
> + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg);
> + st->scan_xfers[2 * k + 1].rx_buf = &st->scan_rx[k];
> + st->scan_xfers[2 * k + 1].len = sizeof(__be16);
> + if (k < n_active - 1)
> + st->scan_xfers[2 * k + 1].cs_change = 1;
> + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg);
> + k++;
> + }
> +
> + st->scan_msg.spi = spi;
> +
> + ret = spi_optimize_message(spi, &st->scan_msg);
> + if (ret)
> + goto err_free_bufs;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)));
> + if (ret)
> + goto err;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> + ~bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)) & GENMASK(15, 0));
> + if (ret)
> + goto err;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
> + st->osr[bit]);
> + if (ret)
> + goto err;
> + }
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + goto err;
> +
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + goto err;
Do we need to do something to exit conversion mode on error here?
> +
> + enable_irq(st->irq);
> + return 0;
> +err:
> + spi_unoptimize_message(&st->scan_msg);
> +err_free_bufs:
> + ad4691_free_scan_bufs(st);
> + return ret;
> +}
> +
> +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + disable_irq(st->irq);
> +
> + ret = ad4691_sampling_enable(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + AD4691_SEQ_ALL_CHANNELS_OFF);
> + if (ret)
> + return ret;
> +
This order of unwinding is not the exact reverse of how it was
set up. So either the order needs to be fixed or a comment added
explaining why this order is needed instead.
> + ret = ad4691_exit_conversion_mode(st);
> + spi_unoptimize_message(&st->scan_msg);
> + ad4691_free_scan_bufs(st);
> + return ret;
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
> + .preenable = &ad4691_cnv_burst_buffer_preenable,
> + .postdisable = &ad4691_cnv_burst_buffer_postdisable,
> +};
> +
> +static ssize_t sampling_frequency_show(struct device *dev,
> + struct device_attribute *attr,
> + char *buf)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
> +}
> +
> +static ssize_t sampling_frequency_store(struct device *dev,
> + struct device_attribute *attr,
> + const char *buf, size_t len)
> +{
> + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> + struct ad4691_state *st = iio_priv(indio_dev);
> + int freq, ret;
> +
> + ret = kstrtoint(buf, 10, &freq);
> + if (ret)
> + return ret;
> +
> + guard(mutex)(&st->lock);
> +
> + if (iio_buffer_enabled(indio_dev))
This should be using iio_device_claim_direct(), otherwise
it is racy.
> + return -EBUSY;
> +
> + ret = ad4691_set_pwm_freq(st, freq);
> + if (ret)
> + return ret;
> +
> + return len;
> +}
> +
> +static IIO_DEVICE_ATTR(sampling_frequency, 0644,
> + sampling_frequency_show,
> + sampling_frequency_store, 0);
> +
> +static const struct iio_dev_attr *ad4691_buffer_attrs[] = {
> + &iio_dev_attr_sampling_frequency,
> + NULL
> +};
> +
> +static irqreturn_t ad4691_irq(int irq, void *private)
> +{
> + struct iio_dev *indio_dev = private;
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + /*
> + * GPx has asserted: stop conversions before reading so the
Does this happen per-channel or only once per complete sequence?
> + * 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(indio_dev->trig);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static const struct iio_trigger_ops ad4691_trigger_ops = {
> + .validate_device = iio_trigger_validate_own_device,
> +};
> +
> +static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int i, k = 0;
> + int ret;
> +
> + guard(mutex)(&st->lock);
> +
> + ret = spi_sync(st->scan_msg.spi, &st->scan_msg);
> + if (ret)
> + return ret;
> +
> + if (st->manual_mode) {
> + iio_for_each_active_channel(indio_dev, i) {
> + st->scan.vals[i] = be16_to_cpu(st->scan_rx[k + 1]);
> + k++;
> + }
> + } else {
> + iio_for_each_active_channel(indio_dev, i) {
> + st->scan.vals[i] = be16_to_cpu(st->scan_rx[k]);
> + k++;
> + }
I suppose this is fine, but we usually try to avoid extra copiying and
byte swapping of bufferes like this if we can. It seems completly doable
in both modes. Manual mode will just one extra two-byte buffer for the
throw-away conversion on the first read xfer (or just write to the same
element twice).
> +
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + return ret;
> + }
> +
> + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> + timestamp);
> + return 0;
> +}
> +
> +static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> +
> + ad4691_read_scan(indio_dev, pf->timestamp);
> + 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,
> @@ -493,6 +975,18 @@ static const struct iio_info ad4691_info = {
> .debugfs_reg_access = &ad4691_reg_access,
> };
>
> +static int ad4691_pwm_setup(struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> +
> + 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");
> +
> + return ad4691_set_pwm_freq(st, st->info->max_rate);
> +}
> +
> static int ad4691_regulator_setup(struct ad4691_state *st)
> {
> struct device *dev = regmap_get_device(st->regmap);
> @@ -558,6 +1052,22 @@ static int ad4691_config(struct ad4691_state *st)
> unsigned int val;
> int ret;
>
> + /*
> + * Determine buffer conversion mode from DT: if a PWM is provided it
> + * drives the CNV pin (CNV_BURST_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->manual_mode = false;
> + ret = ad4691_pwm_setup(st);
> + if (ret)
> + return ret;
> + } else {
> + st->manual_mode = true;
> + }
> +
> switch (st->vref_uV) {
> case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX:
> ref_val = AD4691_VREF_2P5;
> @@ -613,6 +1123,78 @@ static int ad4691_config(struct ad4691_state *st)
> return 0;
> }
>
> +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> + struct ad4691_state *st)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct iio_trigger *trig;
> + unsigned int i;
> + int irq, ret;
> +
> + trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> + indio_dev->name,
> + iio_device_id(indio_dev));
> + if (!trig)
> + return -ENOMEM;
> +
> + trig->ops = &ad4691_trigger_ops;
> + iio_trigger_set_drvdata(trig, st);
> +
> + ret = devm_iio_trigger_register(dev, trig);
> + if (ret)
> + return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> +
> + indio_dev->trig = iio_trigger_get(trig);
> +
> + if (!st->manual_mode) {
I would invert the if since the other case is shorter.
> + /*
> + * The GP pin named in interrupt-names 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.
> + * The IRQ is kept disabled until the buffer is enabled.
> + */
> + irq = -ENODEV;
> + for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {
> + irq = fwnode_irq_get_byname(dev_fwnode(dev),
> + ad4691_gp_names[i]);
> + if (irq > 0)
> + break;
> + }
> + if (irq <= 0)
> + return dev_err_probe(dev, irq < 0 ? irq : -ENODEV,
> + "failed to get GP interrupt\n");
Usually we would usually just use spi->irq since it already
has been looked up. But I guess it is OK to do it like this.
> +
> + st->irq = irq;
> +
> + ret = ad4691_gpio_setup(st, i);
> + if (ret)
> + return ret;
> +
> + /*
> + * IRQ is kept disabled until the buffer is enabled to prevent
> + * spurious DATA_READY events before the SPI message is set up.
> + */
> + ret = devm_request_threaded_irq(dev, irq, NULL,
> + &ad4691_irq,
> + IRQF_ONESHOT | IRQF_NO_AUTOEN,
> + indio_dev->name, indio_dev);
> + if (ret)
> + return ret;
> +
> + return devm_iio_triggered_buffer_setup_ext(dev, indio_dev,
> + &iio_pollfunc_store_time,
> + &ad4691_trigger_handler,
> + IIO_BUFFER_DIRECTION_IN,
> + &ad4691_cnv_burst_buffer_setup_ops,
> + ad4691_buffer_attrs);
> + }
> +
> + return devm_iio_triggered_buffer_setup(dev, indio_dev,
> + &iio_pollfunc_store_time,
> + &ad4691_trigger_handler,
> + &ad4691_manual_buffer_setup_ops);
> +}
> +
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-03 11:03 ` [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
@ 2026-04-04 15:34 ` David Lechner
2026-04-06 9:34 ` Sabau, Radu bogdan
2026-04-04 15:57 ` David Lechner
1 sibling, 1 reply; 23+ messages in thread
From: David Lechner @ 2026-04-04 15:34 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc
On 4/3/26 6:03 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 Burst Mode and Manual Mode support offload, but use different
> trigger mechanisms:
>
> CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY
> signal on the GP pin specified by the trigger-source consumer reference
> in the device tree (one cell = GP pin number 0-3). 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
> GP/DATA_READY output via devm_spi_offload_trigger_register() so the
> offload framework can match the '#trigger-source-cells' phandle and
> automatically fire the SPI Engine DMA transfer at end-of-conversion.
>
> 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. This patch promotes the channel scan_type from
> storagebits=16 (triggered-buffer path) to storagebits=32 to match the
> DMA word size; the triggered-buffer paths are updated to the same layout
> for consistency. CNV Burst Mode channel data arrives in the lower 16
> bits of the 32-bit word (shift=0); Manual Mode data arrives in the upper
> 16 bits (shift=16), matching the 4-byte SPI transfer layout
> [data_hi, data_lo, 0, 0]. A separate ad4691_manual_channels[] array
> encodes the shift=16 scan type for manual mode.
>
> Add driver documentation under Documentation/iio/ad4691.rst covering
> operating modes, oversampling, reference voltage, SPI offload paths,
> and buffer data layout; register in MAINTAINERS and index.rst
Documentation should be separate patch. It covers more than just SPI
offload.
>
> Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> Documentation/iio/ad4691.rst | 259 ++++++++++++++++++++++++++
> Documentation/iio/index.rst | 1 +
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ad4691.c | 422 ++++++++++++++++++++++++++++++++++++++++++-
> 5 files changed, 676 insertions(+), 8 deletions(-)
>
> diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst
> new file mode 100644
> index 000000000000..36f0c841605a
> --- /dev/null
> +++ b/Documentation/iio/ad4691.rst
> @@ -0,0 +1,259 @@
> +.. SPDX-License-Identifier: GPL-2.0-only
> +
> +=============
> +AD4691 driver
> +=============
> +
> +ADC driver for Analog Devices Inc. AD4691 family of multichannel SAR ADCs.
> +The module name is ``ad4691``.
> +
> +
> +Supported devices
> +=================
> +
> +The following chips are supported by this driver:
> +
> +* `AD4691 <https://www.analog.com/en/products/ad4691.html>`_ — 16-channel, 500 kSPS
> +* `AD4692 <https://www.analog.com/en/products/ad4692.html>`_ — 16-channel, 1 MSPS
> +* `AD4693 <https://www.analog.com/en/products/ad4693.html>`_ — 8-channel, 500 kSPS
> +* `AD4694 <https://www.analog.com/en/products/ad4694.html>`_ — 8-channel, 1 MSPS
> +
> +
> +IIO channels
> +============
> +
> +Each physical ADC input maps to one IIO voltage channel. The AD4691 and AD4692
> +expose 16 channels (``voltage0`` through ``voltage15``); the AD4693 and AD4694
> +expose 8 channels (``voltage0`` through ``voltage7``).
> +
> +All channels share a common scale (``in_voltage_scale``), derived from the
> +reference voltage. Each channel independently exposes:
> +
> +* ``in_voltageN_raw`` — single-shot ADC result
> +* ``in_voltageN_sampling_frequency`` — internal oscillator frequency used for
As mentioned in another patch, sampling_frquency != osciallator frequency when
oversampling ratio != 1. So this needs to be changed to reflect that.
> + single-shot reads and CNV Burst Mode buffered captures
> +* ``in_voltageN_sampling_frequency_available`` — list of valid oscillator
> + frequencies
> +* ``in_voltageN_oversampling_ratio`` — per-channel hardware accumulation depth
> +* ``in_voltageN_oversampling_ratio_available`` — list of valid ratios
> +
> +
> +Operating modes
> +===============
> +
> +The driver supports two operating modes, auto-detected from the device tree at
> +probe time. Both modes transition to and from an internal Autonomous Mode idle
> +state when the IIO buffer is enabled and disabled.
> +
> +Manual Mode
> +-----------
> +
> +Selected when no ``pwms`` property is present in the device tree. The CNV pin
> +is tied to the SPI chip-select: every CS assertion both triggers a new
> +conversion and returns the result of the previous one (pipelined N+1 scheme).
> +
> +To read N channels the driver issues N+1 SPI transfers in a single optimised
> +message:
> +
> +* Transfers 0 to N-1 each carry ``AD4691_ADC_CHAN(n)`` in the TX byte to
> + select the next channel; the RX byte of transfer ``k+1`` contains the result
> + of the channel selected in transfer ``k``.
> +* Transfer N is a NOOP (0x00) to flush the last conversion result out of the
> + pipeline.
> +
> +The external IIO trigger (``pollfunc_store_time``) drives the trigger handler,
I'm not sure "external" is the best word to describe this. I would say a "user-
defined IIO triger (e.g. hrtimer trigger)".
> +which executes the pre-built SPI message and pushes the scan to the buffer.
> +
> +CNV Burst Mode
> +--------------
> +
> +Selected when a ``pwms`` property is present in the device tree. The PWM drives
> +the CNV pin independently of SPI at the configured conversion rate, and a GP
> +pin (identified by ``interrupt-names``) asserts DATA_READY at end-of-burst to
> +signal that the AVG_IN result registers are ready to be read.
> +
> +The IRQ handler stops the PWM, fires the IIO trigger, and the trigger handler
If we stop the PWM after an IRQ, then we don't get a consistent sample rate.
Ideally, we would leave the PWM running and just pick a rate slow enough that
there is plenty of time to read the data. Otherwise, this mode doesn't seem
particularly useful.
> +reads all active ``AVG_IN(n)`` registers in a single optimised SPI message and
> +pushes the scan to the buffer.
> +
> +The buffer sampling frequency (i.e. the PWM rate) is controlled by the
> +``sampling_frequency`` attribute on the IIO buffer. Valid values span from the
> +chip's minimum oscillator rate up to its maximum conversion rate
> +(500 kSPS for AD4691/AD4693, 1 MSPS for AD4692/AD4694).
Valid, but not usable without SPI offload.
> +
> +Autonomous Mode (idle / single-shot)
> +-------------------------------------
> +
> +The chip idles in Autonomous Mode whenever the IIO buffer is disabled. In this
> +state, ``read_raw`` requests (``in_voltageN_raw``) use the internal oscillator
> +to perform a single conversion on the requested channel and read back the
> +result from the ``AVG_IN(N)`` register. The oscillator is started and stopped
> +for each read to save power.
> +
> +
> +Oversampling
> +============
> +
> +Each channel has an independent hardware accumulator (ACC_DEPTH_IN) that
> +averages a configurable number of successive conversions before DATA_READY
> +asserts. The result is always returned as a 16-bit mean from the ``AVG_IN``
> +register, so the IIO ``realbits`` and ``storagebits`` are unaffected by the
> +oversampling ratio.
> +
> +Valid ratios are 1, 2, 4, 8, 16 and 32. The default is 1 (no averaging).
> +
> +.. code-block:: bash
> +
> + # Set oversampling ratio to 16 on channel 0
> + echo 16 > /sys/bus/iio/devices/iio:device0/in_voltage0_oversampling_ratio
> +
> +When OSR > 1 the effective conversion rate for ``read_raw`` is reduced
> +accordingly, since the driver waits for 2 × OSR oscillator periods before
> +reading the result.
> +
> +
> +Reference voltage
> +=================
> +
> +The driver supports two reference configurations, mutually exclusive:
> +
> +* **External reference** (``ref-supply``): a voltage between 2.4 V and 5.25 V
> + supplied externally. The internal reference buffer is disabled.
> +* **Buffered internal reference** (``refin-supply``): An internal reference
> + buffer is used. The driver enables ``REFBUF_EN`` in the REF_CTRL register
> + when this supply is used.
> +
> +Exactly one of ``ref-supply`` or ``refin-supply`` must be present in the
> +device tree.
> +
> +The reference voltage determines the full-scale range:
> +
> +.. code-block::
> +
> + full-scale = Vref / 2^16 (per LSB)
> +
> +
> +LDO supply
> +==========
> +
> +The chip contains an internal LDO that powers part of the analog front-end.
> +The LDO input can be driven externally via the ``ldo-in-supply`` regulator. If
> +that supply is absent, the driver enables the internal LDO path (``LDO_EN``
> +bit in DEVICE_SETUP).
> +
> +
> +Reset
> +=====
> +
> +The driver supports two reset mechanisms:
> +
> +* **Hardware reset** (``reset-gpios`` in device tree): the GPIO is already
> + asserted at driver probe by the reset controller framework. The driver waits
> + for the required 300 µs reset pulse width and then deasserts.
> +* **Software reset** (fallback when ``reset-gpios`` is absent): the driver
> + writes the software-reset pattern to the SPI_CONFIG_A register.
> +
> +
> +GP pins and interrupts
> +======================
> +
> +The chip exposes up to four general-purpose (GP) pins that can be configured as
> +interrupt outputs. In CNV Burst Mode (non-offload), one GP pin must be wired to
Or trigger sources.
> +an interrupt-capable SoC input and declared in the device tree using the
> +``interrupts`` and ``interrupt-names`` properties.
> +
> +The ``interrupt-names`` value identifies which GP pin is used (``"gp0"``
> +through ``"gp3"``). The driver configures that pin as a DATA_READY output in
> +the GPIO_MODE register.
> +
> +Example device tree fragment::
> +
> + adc@0 {
> + compatible = "adi,ad4692";
> + ...
> + interrupts = <17 IRQ_TYPE_LEVEL_HIGH>;
> + interrupt-parent = <&gpio0>;
> + interrupt-names = "gp0";
> + };
> +
> +
> +SPI offload support
> +===================
> +
> +When a SPI offload engine (e.g. the AXI SPI Engine) is present, the driver
> +uses DMA-backed transfers for CPU-independent, high-throughput data capture.
> +SPI offload is detected automatically at probe via ``devm_spi_offload_get()``;
> +if no offload hardware is available the driver falls back to the software
> +triggered-buffer path.
> +
> +Two SPI offload sub-modes exist, corresponding to the two operating modes:
> +
> +CNV Burst offload
> +-----------------
> +
> +Used when a ``pwms`` property is present and SPI offload is available.
> +
> +The PWM drives CNV at the configured rate. On DATA_READY the SPI offload
> +engine automatically executes a pre-built message that reads all active
> +``AVG_IN`` registers and streams the data directly to an IIO DMA buffer with
> +no CPU involvement. A final state-reset transfer re-arms DATA_READY for the
> +next burst.
> +
> +The GP pin used as DATA_READY trigger is supplied by the trigger-source
> +consumer (via ``#trigger-source-cells``) at buffer enable time; no
> +``interrupt-names`` entry is required in this path.
> +
> +The buffer sampling frequency is controlled by the ``sampling_frequency``
> +attribute on the IIO buffer (same as the non-offload CNV Burst path).
> +
> +Manual offload
> +--------------
> +
> +Used when no ``pwms`` property is present and SPI offload is available.
> +
> +A periodic SPI offload trigger controls the conversion rate. On each trigger
> +period, the SPI engine executes an N+1 transfer message (same pipelined scheme
How does this work with oversampling?
> +as software Manual Mode) and streams the data directly to the IIO DMA buffer.
> +
> +The ``sampling_frequency`` attribute on the IIO buffer controls the trigger
> +rate (in Hz). The default is the chip's maximum conversion rate.
> +
> +
> +Buffer data format
> +==================
> +
> +The IIO buffer data format (``in_voltageN_type``) depends on the active path:
> +
> ++-------------------------+-------------+-------------+-------+
> +| Path | storagebits | realbits | shift |
> ++=========================+=============+=============+=======+
> +| Triggered buffer | 16 | 16 | 0 |
> ++-------------------------+-------------+-------------+-------+
> +| CNV Burst offload (DMA) | 32 | 16 | 0 |
> ++-------------------------+-------------+-------------+-------+
> +| Manual offload (DMA) | 32 | 16 | 16 |
> ++-------------------------+-------------+-------------+-------+
> +
> +In the triggered-buffer path the driver unpacks the 16-bit result in software
> +before pushing to the buffer, so ``storagebits`` is 16.
> +
> +In the DMA offload paths the DMA engine writes 32-bit words directly into the
> +IIO DMA buffer:
> +
> +* **CNV Burst offload**: the SPI engine reads AVG_IN registers with a 2-byte
> + address phase followed by a 2-byte data phase; the 16-bit result lands in
> + the lower half of the 32-bit word (``shift=0``).
> +* **Manual offload**: each 32-bit SPI word carries the channel byte in the
> + first byte; the 16-bit result is returned in the upper half of the 32-bit
I would expect the "first" byte to be in the "upper half" of the 32-bits as
well. This layout could be explained better.
Also, since extra data has to be read in this mode, does this affect the max
conversion rate?
> + word (``shift=16``).
> +
> +The ``in_voltageN_type`` sysfs attribute reflects the active scan type.
> +
> +
> +Unimplemented features
> +======================
> +
> +* GPIO controller functionality of the GP pins
> +* Clamp status and overrange events
> +* Raw accumulator (ACC_IN) and accumulator status registers
> +* ADC_BUSY and overrun status interrupts
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-03 11:03 ` [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
2026-04-04 15:34 ` David Lechner
@ 2026-04-04 15:57 ` David Lechner
2026-04-06 10:39 ` Sabau, Radu bogdan
1 sibling, 1 reply; 23+ messages in thread
From: David Lechner @ 2026-04-04 15:57 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, Jonathan Corbet, Shuah Khan
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
linux-doc
On 4/3/26 6:03 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 Burst Mode and Manual Mode support offload, but use different
> trigger mechanisms:
>
> CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY
> signal on the GP pin specified by the trigger-source consumer reference
> in the device tree (one cell = GP pin number 0-3). 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
> GP/DATA_READY output via devm_spi_offload_trigger_register() so the
> offload framework can match the '#trigger-source-cells' phandle and
> automatically fire the SPI Engine DMA transfer at end-of-conversion.
>
> 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. This patch promotes the channel scan_type from
> storagebits=16 (triggered-buffer path) to storagebits=32 to match the
> DMA word size; the triggered-buffer paths are updated to the same layout
> for consistency. CNV Burst Mode channel data arrives in the lower 16
> bits of the 32-bit word (shift=0); Manual Mode data arrives in the upper
> 16 bits (shift=16), matching the 4-byte SPI transfer layout
> [data_hi, data_lo, 0, 0]. A separate ad4691_manual_channels[] array
> encodes the shift=16 scan type for manual mode.
>
> Add driver documentation under Documentation/iio/ad4691.rst covering
> operating modes, oversampling, reference voltage, SPI offload paths,
> and buffer data layout; register in MAINTAINERS and index.rst
>
> Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> Documentation/iio/ad4691.rst | 259 ++++++++++++++++++++++++++
> Documentation/iio/index.rst | 1 +
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ad4691.c | 422 ++++++++++++++++++++++++++++++++++++++++++-
> 5 files changed, 676 insertions(+), 8 deletions(-)
>
...
> ANALOG DEVICES INC AD4695 DRIVER
> 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 f2a7273e43b9..cc2138e47feb 100644
> --- a/drivers/iio/adc/ad4691.c
> +++ b/drivers/iio/adc/ad4691.c
> @@ -11,6 +11,7 @@
> #include <linux/delay.h>
> #include <linux/dev_printk.h>
> #include <linux/device/devres.h>
> +#include <linux/dmaengine.h>
> #include <linux/err.h>
> #include <linux/interrupt.h>
> #include <linux/math.h>
> @@ -22,10 +23,14 @@
> #include <linux/regulator/consumer.h>
> #include <linux/reset.h>
> #include <linux/spi/spi.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/provider.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/sysfs.h>
> #include <linux/iio/trigger.h>
> @@ -40,6 +45,7 @@
> #define AD4691_VREF_4P096_uV_MAX 4500000
>
> #define AD4691_CNV_DUTY_CYCLE_NS 380
> +#define AD4691_CNV_HIGH_TIME_NS 430
>
> #define AD4691_SPI_CONFIG_A_REG 0x000
> #define AD4691_SW_RESET (BIT(7) | BIT(0))
> @@ -92,6 +98,8 @@
> #define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> #define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
>
> +#define AD4691_OFFLOAD_BITS_PER_WORD 32
> +
> static const char * const ad4691_supplies[] = { "avdd", "vio" };
>
> enum ad4691_ref_ctrl {
> @@ -109,6 +117,31 @@ struct ad4691_chip_info {
> unsigned int max_rate;
> };
>
> +enum {
> + AD4691_SCAN_TYPE_NORMAL, /* triggered buffer: storagebits=16, shift=0 */
> + AD4691_SCAN_TYPE_OFFLOAD_CNV, /* CNV burst offload: storagebits=32, shift=0 */
> + AD4691_SCAN_TYPE_OFFLOAD_MANUAL, /* manual offload: storagebits=32, shift=16 */
> +};
> +
> +static const struct iio_scan_type ad4691_scan_types[] = {
> + [AD4691_SCAN_TYPE_NORMAL] = {
> + .sign = 'u',
> + .realbits = 16,
> + .storagebits = 16,
> + },
> + [AD4691_SCAN_TYPE_OFFLOAD_CNV] = {
> + .sign = 'u',
> + .realbits = 16,
> + .storagebits = 32,
> + },
> + [AD4691_SCAN_TYPE_OFFLOAD_MANUAL] = {
> + .sign = 'u',
> + .realbits = 16,
> + .storagebits = 32,
> + .shift = 16,
> + },
> +};
> +
> #define AD4691_CHANNEL(ch) \
> { \
> .type = IIO_VOLTAGE, \
> @@ -122,11 +155,9 @@ struct ad4691_chip_info {
> .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE), \
> .channel = ch, \
> .scan_index = ch, \
> - .scan_type = { \
> - .sign = 'u', \
> - .realbits = 16, \
> - .storagebits = 16, \
> - }, \
> + .has_ext_scan_type = 1, \
> + .ext_scan_type = ad4691_scan_types, \
> + .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types), \
Usually, we just make two separte ad4691_chip_info structs for offload
vs. not offload.
ext_scan_type is generally only used when the scan type can change
dynamically after probe.
> }
>
> static const struct iio_chan_spec ad4691_channels[] = {
> @@ -221,6 +252,17 @@ static const struct ad4691_chip_info ad4694_chip_info = {
> .max_rate = 1 * HZ_PER_MHZ,
> };
>
> +struct ad4691_offload_state {
> + struct spi_offload *spi;
> + struct spi_offload_trigger *trigger;
> + u64 trigger_hz;
> + struct spi_message msg;
> + /* Max 16 channel xfers + 1 state-reset or NOOP */
> + struct spi_transfer xfer[17];
> + u8 tx_cmd[17][4];
> + u8 tx_reset[4];
> +};
> +
> struct ad4691_state {
> const struct ad4691_chip_info *info;
> struct regmap *regmap;
> @@ -251,6 +293,8 @@ struct ad4691_state {
> struct spi_transfer *scan_xfers;
> __be16 *scan_tx;
> __be16 *scan_rx;
> + /* NULL when no SPI offload hardware is present */
> + struct ad4691_offload_state *offload;
> /* Scan buffer: one slot per channel plus timestamp */
> struct {
> u16 vals[16];
> @@ -273,6 +317,46 @@ static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
> AD4691_GP_MODE_DATA_READY << shift);
> }
>
> +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)
> +{
> + return type == SPI_OFFLOAD_TRIGGER_DATA_READY &&
> + nargs == 1 && args[0] <= 3;
> +}
> +
> +static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
> + enum spi_offload_trigger_type type,
> + u64 *args, u32 nargs)
> +{
> + struct ad4691_state *st = spi_offload_trigger_get_priv(trigger);
> +
> + if (nargs != 1)
> + return -EINVAL;
> +
> + return ad4691_gpio_setup(st, (unsigned int)args[0]);
Should be fine to leave out the cast here.
> +}
> +
> +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 int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> {
> struct spi_device *spi = context;
> @@ -553,10 +637,17 @@ static int ad4691_read_raw(struct iio_dev *indio_dev,
> case IIO_CHAN_INFO_OVERSAMPLING_RATIO:
> *val = st->osr[chan->scan_index];
> return IIO_VAL_INT;
> - case IIO_CHAN_INFO_SCALE:
> + case IIO_CHAN_INFO_SCALE: {
> + const struct iio_scan_type *scan_type;
> +
> + scan_type = iio_get_current_scan_type(indio_dev, chan);
> + if (IS_ERR(scan_type))
> + return PTR_ERR(scan_type);
> +
> *val = st->vref_uV / (MICRO / MILLI);
> - *val2 = chan->scan_type.realbits;
> + *val2 = scan_type->realbits;
> return IIO_VAL_FRACTIONAL_LOG2;
> + }
> default:
> return -EINVAL;
> }
> @@ -856,6 +947,213 @@ static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops = {
> .postdisable = &ad4691_cnv_burst_buffer_postdisable,
> };
>
> +static int ad4691_manual_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct ad4691_offload_state *offload = st->offload;
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
> + };
> + unsigned int bit, k;
> + int ret;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + memset(offload->xfer, 0, sizeof(offload->xfer));
> +
> + /*
> + * N+1 transfers for N channels. Each CS-low period triggers
> + * a conversion AND returns the previous result (pipelined).
> + * TX: [AD4691_ADC_CHAN(n), 0x00, 0x00, 0x00]
> + * RX: [data_hi, data_lo, 0x00, 0x00] (shift=16)
> + * Transfer 0 RX is garbage; transfers 1..N carry real data.
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, bit) {
> + offload->tx_cmd[k][0] = AD4691_ADC_CHAN(bit);
> + offload->xfer[k].tx_buf = offload->tx_cmd[k];
> + offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
> + offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
> + offload->xfer[k].cs_change = 1;
> + offload->xfer[k].cs_change_delay.value = AD4691_CNV_HIGH_TIME_NS;
> + offload->xfer[k].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> + /* First transfer RX is garbage — skip it. */
> + if (k > 0)
> + offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + k++;
> + }
> +
> + /* Final NOOP to flush pipeline and capture last channel. */
> + offload->tx_cmd[k][0] = AD4691_NOOP;
> + offload->xfer[k].tx_buf = offload->tx_cmd[k];
> + offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
> + offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
> + offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + k++;
> +
> + spi_message_init_with_transfers(&offload->msg, offload->xfer, k);
> + offload->msg.offload = offload->spi;
> +
> + ret = spi_optimize_message(spi, &offload->msg);
> + if (ret)
> + goto err_exit_conversion;
> +
> + config.periodic.frequency_hz = offload->trigger_hz;
> + ret = spi_offload_trigger_enable(offload->spi, offload->trigger, &config);
> + if (ret)
> + goto err_unoptimize;
> +
> + return 0;
> +
> +err_unoptimize:
> + spi_unoptimize_message(&offload->msg);
> +err_exit_conversion:
> + ad4691_exit_conversion_mode(st);
> + return ret;
> +}
> +
> +static int ad4691_manual_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct ad4691_offload_state *offload = st->offload;
> +
> + spi_offload_trigger_disable(offload->spi, offload->trigger);
> + spi_unoptimize_message(&offload->msg);
> +
> + return ad4691_exit_conversion_mode(st);
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_manual_offload_buffer_setup_ops = {
> + .postenable = &ad4691_manual_offload_buffer_postenable,
> + .predisable = &ad4691_manual_offload_buffer_predisable,
> +};
> +
> +static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct ad4691_offload_state *offload = st->offload;
> + struct device *dev = regmap_get_device(st->regmap);
> + struct spi_device *spi = to_spi_device(dev);
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_DATA_READY,
> + };
> + unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> + unsigned int bit, k;
> + int ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)));
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK_REG,
> + ~bitmap_read(indio_dev->active_scan_mask, 0,
> + iio_get_masklength(indio_dev)) & GENMASK(15, 0));
> + if (ret)
> + return ret;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + ret = regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit),
> + st->osr[bit]);
> + if (ret)
> + return ret;
> + }
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + memset(offload->xfer, 0, sizeof(offload->xfer));
> +
> + /*
> + * N transfers to read N AVG_IN registers plus one state-reset
> + * transfer (no RX) to re-arm DATA_READY.
> + * TX: [reg_hi | 0x80, reg_lo, 0x00, 0x00]
> + * RX: [0x00, 0x00, data_hi, data_lo] (shift=0)
> + */
> + k = 0;
> + iio_for_each_active_channel(indio_dev, bit) {
> + unsigned int reg = AD4691_AVG_IN(bit);
> +
> + offload->tx_cmd[k][0] = (reg >> 8) | 0x80;
> + offload->tx_cmd[k][1] = reg & 0xFF;
Can we use put_unaligned_be16()?
> + offload->xfer[k].tx_buf = offload->tx_cmd[k];
> + offload->xfer[k].len = sizeof(offload->tx_cmd[k]);
> + offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
> + offload->xfer[k].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + if (k < n_active - 1)
> + offload->xfer[k].cs_change = 1;
> + k++;
> + }
> +
> + /* State reset to re-arm DATA_READY for the next scan. */
> + offload->tx_reset[0] = AD4691_STATE_RESET_REG >> 8;
> + offload->tx_reset[1] = AD4691_STATE_RESET_REG & 0xFF;
ditto.
> + offload->tx_reset[2] = AD4691_STATE_RESET_ALL;
> + offload->xfer[k].tx_buf = offload->tx_reset;
> + offload->xfer[k].len = sizeof(offload->tx_reset);
> + offload->xfer[k].bits_per_word = AD4691_OFFLOAD_BITS_PER_WORD;
> + k++;
> +
> + spi_message_init_with_transfers(&offload->msg, offload->xfer, k);
> + offload->msg.offload = offload->spi;
> +
> + ret = spi_optimize_message(spi, &offload->msg);
> + if (ret)
> + goto err_exit_conversion;
> +
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + goto err_unoptimize;
> +
> + ret = spi_offload_trigger_enable(offload->spi, offload->trigger, &config);
> + if (ret)
> + goto err_sampling_disable;
> +
> + return 0;
> +
> +err_sampling_disable:
> + ad4691_sampling_enable(st, false);
> +err_unoptimize:
> + spi_unoptimize_message(&offload->msg);
> +err_exit_conversion:
> + ad4691_exit_conversion_mode(st);
> + return ret;
> +}
> +
> +static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct ad4691_offload_state *offload = st->offload;
> + int ret;
> +
> + spi_offload_trigger_disable(offload->spi, offload->trigger);
> +
> + ret = ad4691_sampling_enable(st, false);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + AD4691_SEQ_ALL_CHANNELS_OFF);
> + if (ret)
> + return ret;
> +
> + spi_unoptimize_message(&offload->msg);
> +
> + return ad4691_exit_conversion_mode(st);
> +}
> +
> +static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_setup_ops = {
> + .postenable = &ad4691_cnv_burst_offload_buffer_postenable,
> + .predisable = &ad4691_cnv_burst_offload_buffer_predisable,
> +};
> +
> static ssize_t sampling_frequency_show(struct device *dev,
> struct device_attribute *attr,
> char *buf)
> @@ -863,6 +1161,9 @@ static ssize_t sampling_frequency_show(struct device *dev,
> struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> struct ad4691_state *st = iio_priv(indio_dev);
>
> + if (st->manual_mode && st->offload)
> + return sysfs_emit(buf, "%llu\n", st->offload->trigger_hz);
> +
> return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
> }
>
> @@ -883,6 +1184,20 @@ static ssize_t sampling_frequency_store(struct device *dev,
> if (iio_buffer_enabled(indio_dev))
> return -EBUSY;
>
> + if (st->manual_mode && st->offload) {
> + struct spi_offload_trigger_config config = {
> + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
> + .periodic = { .frequency_hz = freq },
> + };
Same comment as other patches. This needs to account for oversampling ratio.
> +
> + ret = spi_offload_trigger_validate(st->offload->trigger, &config);
> + if (ret)
> + return ret;
> +
> + st->offload->trigger_hz = config.periodic.frequency_hz;
> + return len;
> + }
> +
> ret = ad4691_set_pwm_freq(st, freq);
> if (ret)
> return ret;
> @@ -968,10 +1283,23 @@ static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> return IRQ_HANDLED;
> }
>
> +static int ad4691_get_current_scan_type(const struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> +
> + if (!st->offload)
> + return AD4691_SCAN_TYPE_NORMAL;
> + if (st->manual_mode)
> + return AD4691_SCAN_TYPE_OFFLOAD_MANUAL;
> + return AD4691_SCAN_TYPE_OFFLOAD_CNV;
> +}
> +
> static const struct iio_info ad4691_info = {
> .read_raw = &ad4691_read_raw,
> .write_raw = &ad4691_write_raw,
> .read_avail = &ad4691_read_avail,
> + .get_current_scan_type = &ad4691_get_current_scan_type,
> .debugfs_reg_access = &ad4691_reg_access,
> };
>
> @@ -1195,9 +1523,75 @@ static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> &ad4691_manual_buffer_setup_ops);
> }
>
> +static int ad4691_setup_offload(struct iio_dev *indio_dev,
> + struct ad4691_state *st,
> + struct spi_offload *spi_offload)
> +{
> + struct device *dev = regmap_get_device(st->regmap);
> + struct ad4691_offload_state *offload;
> + struct dma_chan *rx_dma;
> + int ret;
> +
> + offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
> + if (!offload)
> + return -ENOMEM;
> +
> + offload->spi = spi_offload;
> + st->offload = offload;
> +
> + if (st->manual_mode) {
> + offload->trigger =
> + devm_spi_offload_trigger_get(dev, offload->spi,
> + SPI_OFFLOAD_TRIGGER_PERIODIC);
> + if (IS_ERR(offload->trigger))
> + return dev_err_probe(dev, PTR_ERR(offload->trigger),
> + "Failed to get periodic offload trigger\n");
> +
> + offload->trigger_hz = st->info->max_rate;
I think I mentioned this elsewhere, but can we really get max_rate in manual mode
due to the extra SPI overhead? Probably safer to start with a lower rate.
> + } 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");
> +
> + offload->trigger =
> + devm_spi_offload_trigger_get(dev, offload->spi,
> + SPI_OFFLOAD_TRIGGER_DATA_READY);
> + if (IS_ERR(offload->trigger))
> + return dev_err_probe(dev, PTR_ERR(offload->trigger),
> + "Failed to get DATA_READY offload trigger\n");
> + }
> +
> + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, offload->spi);
> + if (IS_ERR(rx_dma))
> + return dev_err_probe(dev, PTR_ERR(rx_dma),
> + "Failed to get offload RX DMA channel\n");
> +
> + if (st->manual_mode)
> + indio_dev->setup_ops = &ad4691_manual_offload_buffer_setup_ops;
> + else
> + indio_dev->setup_ops = &ad4691_cnv_burst_offload_buffer_setup_ops;
> +
> + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
> + IIO_BUFFER_DIRECTION_IN);
> + if (ret)
> + return ret;
> +
> + indio_dev->buffer->attrs = ad4691_buffer_attrs;
Should including ad4691_buffer_attrs depend on st->manual_mode?
I thought it was only used when PWM is connected to CNV.
> +
> + return 0;
> +}
> +
> static int ad4691_probe(struct spi_device *spi)
> {
> struct device *dev = &spi->dev;
> + struct spi_offload *spi_offload;
> struct iio_dev *indio_dev;
> struct ad4691_state *st;
> int ret;
> @@ -1232,6 +1626,13 @@ static int ad4691_probe(struct spi_device *spi)
> if (ret)
> return ret;
>
> + spi_offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
> + ret = PTR_ERR_OR_ZERO(spi_offload);
> + if (ret == -ENODEV)
> + spi_offload = NULL;
> + else if (ret)
> + return dev_err_probe(dev, ret, "Failed to get SPI offload\n");
> +
> indio_dev->name = st->info->name;
> indio_dev->info = &ad4691_info;
> indio_dev->modes = INDIO_DIRECT_MODE;
> @@ -1239,7 +1640,10 @@ static int ad4691_probe(struct spi_device *spi)
> indio_dev->channels = st->info->channels;
> indio_dev->num_channels = st->info->num_channels;
As mentioned earlier, we generally want separate channel structs
for SPI offload. These will also have different num_channels because
there is no timestamp channel in SPI offload.
>
> - ret = ad4691_setup_triggered_buffer(indio_dev, st);
> + if (spi_offload)
> + ret = ad4691_setup_offload(indio_dev, st, spi_offload);
> + else
> + ret = ad4691_setup_triggered_buffer(indio_dev, st);
> if (ret)
> return ret;
>
> @@ -1277,3 +1681,5 @@ 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");
> +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support
2026-04-04 15:12 ` David Lechner
@ 2026-04-05 8:57 ` Andy Shevchenko
2026-04-06 9:22 ` Sabau, Radu bogdan
1 sibling, 0 replies; 23+ messages in thread
From: Andy Shevchenko @ 2026-04-05 8:57 UTC (permalink / raw)
To: David Lechner
Cc: 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, Jonathan Corbet, Shuah Khan, linux-iio, devicetree,
linux-kernel, linux-pwm, linux-gpio, linux-doc
On Sat, Apr 04, 2026 at 10:12:04AM -0500, David Lechner wrote:
> On 4/3/26 6:03 AM, Radu Sabau via B4 Relay wrote:
> > Add buffered capture support using the IIO triggered buffer framework.
> >
> > CNV Burst Mode: the GP pin identified by interrupt-names in the device
> > tree is configured as DATA_READY output. The IRQ handler stops
> > conversions and fires the IIO trigger; the trigger handler executes a
> > pre-built SPI message that reads all active channels from the AVG_IN
> > accumulator registers and then resets accumulator state and restarts
> > conversions for the next cycle.
> >
> > Manual Mode: CNV is tied to SPI CS so each transfer simultaneously
> > reads the previous result and starts the next conversion (pipelined
> > N+1 scheme). At preenable time a pre-built, optimised SPI message of
> > N+1 transfers is constructed (N channel reads plus one NOOP to drain
> > the pipeline). The trigger handler executes the message in a single
> > spi_sync() call and collects the results. An external trigger (e.g.
> > iio-trig-hrtimer) is required to drive the trigger at the desired
> > sample rate.
> >
> > Both modes share the same trigger handler and push a complete scan —
> > one u16 slot per channel at its scan_index position, followed by a
> > timestamp — to the IIO buffer via iio_push_to_buffers_with_ts().
> >
> > The CNV Burst Mode sampling frequency (PWM period) is exposed as a
> > buffer-level attribute via IIO_DEVICE_ATTR.
Tried my best to avoid clashes with David's review.
...
> > #include <linux/array_size.h>
> > #include <linux/bitfield.h>
> > +#include <linux/bitmap.h>
> > #include <linux/bitops.h>
When bitmap.h is present, it implies bitops.h, hence the latter can be simply
replaced.
> > #include <linux/cleanup.h>
> > #include <linux/delay.h>
> > #include <linux/dev_printk.h>
> > #include <linux/device/devres.h>
> > #include <linux/err.h>
> > +#include <linux/interrupt.h>
> > #include <linux/math.h>
> > #include <linux/module.h>
> > #include <linux/mod_devicetable.h>
> > +#include <linux/property.h>
> > +#include <linux/pwm.h>
> > #include <linux/regmap.h>
> > #include <linux/regulator/consumer.h>
> > #include <linux/reset.h>
...
> > .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) \
> > - | BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > + | BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> > + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
> > .info_mask_separate_available = \
> > - BIT(IIO_CHAN_INFO_SAMP_FREQ), \
> > + BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> > + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO), \
You may reduce churn by squeezing a new ones in between existing ones.
Also consider use usual patter of placing the operator on the same line where
left operand is (currently it goes with the right operand).
...
> > struct ad4691_state {
Just to double check, when add a new field or fields into the data structure
check with `pahole` that the new members placed at the best or at least good
enough locations.
> > const struct ad4691_chip_info *info;
> > struct regmap *regmap;
> > +
> > + struct pwm_device *conv_trigger;
> > + int irq;
> > +
> > + bool manual_mode;
> > +
> > int vref_uV;
> > + u8 osr[16];
> > bool refbuf_en;
> > bool ldo_en;
> > + u32 cnv_period_ns;
> > /*
> > * Synchronize access to members of the driver state, and ensure
> > * atomicity of consecutive SPI operations.
> > */
> > struct mutex lock;
> > + /*
> > + * Per-buffer-enable lifetime resources:
> > + * Manual Mode - a pre-built SPI message that clocks out N+1
> > + * transfers in one go.
> > + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N
> > + * transfers in one go.
> > + */
> > + struct spi_message scan_msg;
> > + struct spi_transfer *scan_xfers;
> > + __be16 *scan_tx;
> > + __be16 *scan_rx;
>
> Why not embed these arrays here? Then we don't have to deal with
> alloc/free later.
>
> > + /* Scan buffer: one slot per channel plus timestamp */
> > + struct {
> > + u16 vals[16];
> > + aligned_s64 ts;
> > + } scan __aligned(IIO_DMA_MINALIGN);
>
> Better would be IIO_DECLARE_BUFFER_WITH_TS() since we don't always
> use all vals.
>
> Also, current usage doesn't need to be DMA-safe because scan_tx
> is being used for the actual SPI xfer.
>
> > };
...
> > +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num)
> > +{
> > + unsigned int shift = 4 * (gp_num % 2);
> > +
> > + return regmap_update_bits(st->regmap,
> > + AD4691_GPIO_MODE1_REG + gp_num / 2,
> > + AD4691_GP_MODE_MASK << shift,
> > + AD4691_GP_MODE_DATA_READY << shift);
Not sure if compiler will see % and / together, I would go with two more
temporary variables to make it clear to it:
... _bit_off = % 2;
... _reg_off = / 2;
The practical example is described, for example, here:
9b3cd5c7099f ("regmap: place foo / 8 and foo % 8 closer to each other").
> > +}
...
> > +static int ad4691_manual_buffer_preenable(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);
> > + unsigned int n_active = bitmap_weight(indio_dev->active_scan_mask,
> > + iio_get_masklength(indio_dev));
In such cases please split definition and assignment. Will take two lines, but
readability will be better.
> > + unsigned int n_xfers = n_active + 1;
> > + unsigned int k, i;
> > + int ret;
> > +
> > + st->scan_xfers = kcalloc(n_xfers, sizeof(*st->scan_xfers), GFP_KERNEL);
>
> Usually, we make st->scan_xfers a fixed array with the max number of possible
> xfers. Then we don't have to deal with alloc/free.
And please if it's still be required to allocate and possible use kzalloc_objs().
> > + if (!st->scan_xfers)
> > + return -ENOMEM;
> > +
> > + st->scan_tx = kcalloc(n_xfers, sizeof(*st->scan_tx), GFP_KERNEL);
> > + if (!st->scan_tx) {
> > + kfree(st->scan_xfers);
> > + return -ENOMEM;
> > + }
> > +
> > + st->scan_rx = kcalloc(n_xfers, sizeof(*st->scan_rx), GFP_KERNEL);
> > + if (!st->scan_rx) {
> > + kfree(st->scan_tx);
> > + kfree(st->scan_xfers);
> > + return -ENOMEM;
> > + }
> > +
> > + spi_message_init(&st->scan_msg);
> > +
> > + k = 0;
> > + iio_for_each_active_channel(indio_dev, i) {
> > + st->scan_tx[k] = cpu_to_be16(AD4691_ADC_CHAN(i));
> > + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> > + st->scan_xfers[k].rx_buf = &st->scan_rx[k];
> > + st->scan_xfers[k].len = sizeof(__be16);
> > + st->scan_xfers[k].cs_change = 1;
> > + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> > + k++;
> > + }
> > +
> > + /* Final NOOP transfer to retrieve last channel's result. */
> > + st->scan_tx[k] = cpu_to_be16(AD4691_NOOP);
> > + st->scan_xfers[k].tx_buf = &st->scan_tx[k];
> > + st->scan_xfers[k].rx_buf = &st->scan_rx[k];
> > + st->scan_xfers[k].len = sizeof(__be16);
> > + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg);
> > +
> > + st->scan_msg.spi = spi;
>
> This isn't how the SPI framework is intended to be used. We should
> have st->spi = spi in probe instead.
>
> > +
> > + ret = spi_optimize_message(spi, &st->scan_msg);
> > + if (ret) {
> > + ad4691_free_scan_bufs(st);
> > + return ret;
> > + }
> > +
> > + ret = ad4691_enter_conversion_mode(st);
> > + if (ret) {
> > + spi_unoptimize_message(&st->scan_msg);
> > + ad4691_free_scan_bufs(st);
> > + return ret;
> > + }
> > +
> > + return 0;
> > +}
...
> > +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev)
As per above.
...
> > +static ssize_t sampling_frequency_show(struct device *dev,
> > + struct device_attribute *attr,
> > + char *buf)
> > +{
> > + struct iio_dev *indio_dev = dev_to_iio_dev(dev);
> > + struct ad4691_state *st = iio_priv(indio_dev);
> > +
> > + return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns));
Why casting?
> > +}
...
> > +static IIO_DEVICE_ATTR(sampling_frequency, 0644,
> > + sampling_frequency_show,
> > + sampling_frequency_store, 0);
IIO_DEVICE_ATTR_RW().
...
> > +static int ad4691_read_scan(struct iio_dev *indio_dev, s64 timestamp)
> > +{
> > + struct ad4691_state *st = iio_priv(indio_dev);
> > + unsigned int i, k = 0;
> > + int ret;
> > +
> > + guard(mutex)(&st->lock);
> > +
> > + ret = spi_sync(st->scan_msg.spi, &st->scan_msg);
> > + if (ret)
> > + return ret;
> > +
> > + if (st->manual_mode) {
> > + iio_for_each_active_channel(indio_dev, i) {
> > + st->scan.vals[i] = be16_to_cpu(st->scan_rx[k + 1]);
> > + k++;
> > + }
> > + } else {
> > + iio_for_each_active_channel(indio_dev, i) {
> > + st->scan.vals[i] = be16_to_cpu(st->scan_rx[k]);
> > + k++;
> > + }
>
> I suppose this is fine, but we usually try to avoid extra copiying and
> byte swapping of bufferes like this if we can. It seems completly doable
> in both modes. Manual mode will just one extra two-byte buffer for the
> throw-away conversion on the first read xfer (or just write to the same
> element twice).
And in case it's still needed, we may introduce a helper in
include/linux/byteorder/generic.h calling it memcpy_to/from_be16().
> > + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> > + AD4691_STATE_RESET_ALL);
> > + if (ret)
> > + return ret;
> > +
> > + ret = ad4691_sampling_enable(st, true);
> > + if (ret)
> > + return ret;
> > + }
> > +
> > + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> > + timestamp);
> > + return 0;
> > +}
...
> > +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
> > + struct ad4691_state *st)
> > +{
> > + struct device *dev = regmap_get_device(st->regmap);
> > + struct iio_trigger *trig;
> > + unsigned int i;
> > + int irq, ret;
> > +
> > + trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> > + indio_dev->name,
> > + iio_device_id(indio_dev));
> > + if (!trig)
> > + return -ENOMEM;
> > +
> > + trig->ops = &ad4691_trigger_ops;
> > + iio_trigger_set_drvdata(trig, st);
> > +
> > + ret = devm_iio_trigger_register(dev, trig);
> > + if (ret)
> > + return dev_err_probe(dev, ret, "IIO trigger register failed\n");
> > +
> > + indio_dev->trig = iio_trigger_get(trig);
> > +
> > + if (!st->manual_mode) {
>
> I would invert the if since the other case is shorter.
+1.
> > + /*
> > + * The GP pin named in interrupt-names 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.
> > + * The IRQ is kept disabled until the buffer is enabled.
> > + */
> > + irq = -ENODEV;
> > + for (i = 0; i < ARRAY_SIZE(ad4691_gp_names); i++) {
> > + irq = fwnode_irq_get_byname(dev_fwnode(dev),
> > + ad4691_gp_names[i]);
> > + if (irq > 0)
> > + break;
> > + }
> > + if (irq <= 0)
> > + return dev_err_probe(dev, irq < 0 ? irq : -ENODEV,
> > + "failed to get GP interrupt\n");
>
> Usually we would usually just use spi->irq since it already
> has been looked up. But I guess it is OK to do it like this.
No, it's not. (Linux) IRQ shouldn't ever be 0, so this check is effectively a
dead code.
irq = -ENXIO; // Note, this is the error code used by core for
// IRQ not found.
...
if (irq < 0)
return dev_err_probe(dev, irq, "failed to get GP interrupt\n");
> > + st->irq = irq;
> > +
> > + ret = ad4691_gpio_setup(st, i);
> > + if (ret)
> > + return ret;
> > +
> > + /*
> > + * IRQ is kept disabled until the buffer is enabled to prevent
> > + * spurious DATA_READY events before the SPI message is set up.
> > + */
> > + ret = devm_request_threaded_irq(dev, irq, NULL,
> > + &ad4691_irq,
> > + IRQF_ONESHOT | IRQF_NO_AUTOEN,
> > + indio_dev->name, indio_dev);
> > + if (ret)
> > + return ret;
> > +
> > + return devm_iio_triggered_buffer_setup_ext(dev, indio_dev,
> > + &iio_pollfunc_store_time,
> > + &ad4691_trigger_handler,
> > + IIO_BUFFER_DIRECTION_IN,
> > + &ad4691_cnv_burst_buffer_setup_ops,
> > + ad4691_buffer_attrs);
> > + }
> > +
> > + return devm_iio_triggered_buffer_setup(dev, indio_dev,
> > + &iio_pollfunc_store_time,
> > + &ad4691_trigger_handler,
> > + &ad4691_manual_buffer_setup_ops);
> > +}
> > +
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support
2026-04-04 15:12 ` David Lechner
2026-04-05 8:57 ` Andy Shevchenko
@ 2026-04-06 9:22 ` Sabau, Radu bogdan
2026-04-06 13:39 ` David Lechner
1 sibling, 1 reply; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 9: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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, April 4, 2026 6:12 PM
...
> > +
> > +/*
> > + * Valid ACC_DEPTH values where the effective divisor equals the count.
> > + * From Table 13: ACC_DEPTH = 2^N yields right-shift = N, divisor = 2^N.
> > + */
> > +static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
>
> It would be nice to add oversampling in a separate commit as that is a
> separate feature.
Do you think this would be suitable after the offload commit?
>
> Oversampling also affects sampling frequency. When there isn't oversampling,
> sample rate == conversion rate. However, with oversampling, sample rate ==
> conversion rate / oversampling ratio (because each sample involves #OSR
> conversions).
>
> So more code will be required to make IIO_CHAN_INFO_SAMP_FREQ
> attributes
> (both read/write_raw and read_avail) adjust the values based on the current
> oversampling ratio.
I agree with this, the sampling frequency differs when oversampling is used.
Will add that in the next version.
>
> > +
> > static const struct ad4691_chip_info ad4691_chip_info = {
> > .channels = ad4691_channels,
...
> > +
> > + st->scan_msg.spi = spi;
>
> This isn't how the SPI framework is intended to be used. We should
> have st->spi = spi in probe instead.
You are right. The spi pointer was removed from struct in earlier versions
since the message wasn't pre-prepared back then, though now it is needed.
>
> > +
> > + ret = spi_optimize_message(spi, &st->scan_msg);
> > + if (ret) {
...
> > +};
> > +
> > +static irqreturn_t ad4691_irq(int irq, void *private)
> > +{
> > + struct iio_dev *indio_dev = private;
> > + struct ad4691_state *st = iio_priv(indio_dev);
> > +
> > + /*
> > + * GPx has asserted: stop conversions before reading so the
>
> Does this happen per-channel or only once per complete sequence?
>
This one happens per complete sequence.
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-04 15:34 ` David Lechner
@ 2026-04-06 9:34 ` Sabau, Radu bogdan
2026-04-06 13:44 ` David Lechner
0 siblings, 1 reply; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 9:34 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, April 4, 2026 6:34 PM
...
> >
> > Add driver documentation under Documentation/iio/ad4691.rst covering
> > operating modes, oversampling, reference voltage, SPI offload paths,
> > and buffer data layout; register in MAINTAINERS and index.rst
>
> Documentation should be separate patch. It covers more than just SPI
> offload.
>
Noted. Thanks for this.
> >
> > Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
> >
> > Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> > ---
...
> > +Selected when a ``pwms`` property is present in the device tree. The PWM
> drives
> > +the CNV pin independently of SPI at the configured conversion rate, and a
> GP
> > +pin (identified by ``interrupt-names``) asserts DATA_READY at end-of-burst
> to
> > +signal that the AVG_IN result registers are ready to be read.
> > +
> > +The IRQ handler stops the PWM, fires the IIO trigger, and the trigger
> handler
>
> If we stop the PWM after an IRQ, then we don't get a consistent sample rate.
> Ideally, we would leave the PWM running and just pick a rate slow enough
> that
> there is plenty of time to read the data. Otherwise, this mode doesn't seem
> particularly useful.
Should there also be a condition when setting the sampling frequency, that will
protect from setting too fast sample rates?
>
> > +reads all active ``AVG_IN(n)`` registers in a single optimised SPI message and
> > +pushes the scan to the buffer.
> > +
> > +The buffer sampling frequency (i.e. the PWM rate) is controlled by the
> > +``sampling_frequency`` attribute on the IIO buffer. Valid values span from
> the
> > +chip's minimum oscillator rate up to its maximum conversion rate
> > +(500 kSPS for AD4691/AD4693, 1 MSPS for AD4692/AD4694).
>
> Valid, but not usable without SPI offload.
The PWM is also be used with triggered-buffer in CNV Burst Mode.
>
> > +
> > +Autonomous Mode (idle / single-shot)
> > +-------------------------------------
> > +
...
> > +
> > +Manual offload
> > +--------------
> > +
> > +Used when no ``pwms`` property is present and SPI offload is available.
> > +
> > +A periodic SPI offload trigger controls the conversion rate. On each trigger
> > +period, the SPI engine executes an N+1 transfer message (same pipelined
> scheme
>
> How does this work with oversampling?
As per previous versions discussions, Manual Mode doesn't support oversampling,
since the chip only transfers the raw unfiltered 16-bit data without register interaction
in this mode.
>
> > +as software Manual Mode) and streams the data directly to the IIO DMA
> buffer.
...
> > +IIO DMA buffer:
> > +
> > +* **CNV Burst offload**: the SPI engine reads AVG_IN registers with a 2-
> byte
> > + address phase followed by a 2-byte data phase; the 16-bit result lands in
> > + the lower half of the 32-bit word (``shift=0``).
> > +* **Manual offload**: each 32-bit SPI word carries the channel byte in the
> > + first byte; the 16-bit result is returned in the upper half of the 32-bit
>
> I would expect the "first" byte to be in the "upper half" of the 32-bits as
> well. This layout could be explained better.
>
> Also, since extra data has to be read in this mode, does this affect the max
> conversion rate?
This is bad documentation on my part. "channel byte" isn't used anymore,
this is previous version behaviour. Right now, only 16-bits worth of actual
channel data are used.
>
> > + word (``shift=16``).
> > +
> > +The ``in_voltageN_type`` sysfs attribute reflects the active scan type.
> > +
> > +
> > +Unimplemented features
> > +======================
> > +
> > +* GPIO controller functionality of the GP pins
> > +* Clamp status and overrange events
> > +* Raw accumulator (ACC_IN) and accumulator status registers
> > +* ADC_BUSY and overrun status interrupts
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-04 15:57 ` David Lechner
@ 2026-04-06 10:39 ` Sabau, Radu bogdan
2026-04-06 11:08 ` Sabau, Radu bogdan
2026-04-06 13:53 ` David Lechner
0 siblings, 2 replies; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 10: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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Saturday, April 4, 2026 6:57 PM
...
> > +
> > #define AD4691_CHANNEL(ch)
> \
> > { \
> > .type = IIO_VOLTAGE, \
> > @@ -122,11 +155,9 @@ struct ad4691_chip_info {
> > .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),
> \
> > .channel = ch, \
> > .scan_index = ch, \
> > - .scan_type = { \
> > - .sign = 'u', \
> > - .realbits = 16, \
> > - .storagebits = 16, \
> > - }, \
> > + .has_ext_scan_type = 1,
> \
> > + .ext_scan_type = ad4691_scan_types, \
> > + .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types),
> \
>
> Usually, we just make two separte ad4691_chip_info structs for offload
> vs. not offload.
>
> ext_scan_type is generally only used when the scan type can change
> dynamically after probe.
>
So, just to be clear, you are saying I should have different chip_info structs
and change the triggered-buffer for offload ones if offload is present?
I am asking since offload has different scan types as well, and this would
mean 3 different chip_info structs for each chip -> total of 12 chip_info structs,
each with a different channel array, or perhaps there is a more compact way
to have this implemented.
I could make the channel arrays use the same macro and have the scan_type
reversed to storage and shift done as parameters.
Please let me know your thoughts on this.
> > }
> >
> > static const struct iio_chan_spec ad4691_channels[] = {
> > @@ -221,6 +252,17 @@ static const struct ad4691_chip_info
> ad4694_chip_info = {
> > .max_rate = 1 * HZ_PER_MHZ,
> > };
> >
...
> > }
> >
> > @@ -883,6 +1184,20 @@ static ssize_t sampling_frequency_store(struct
> device *dev,
> > if (iio_buffer_enabled(indio_dev))
> > return -EBUSY;
> >
> > + if (st->manual_mode && st->offload) {
> > + struct spi_offload_trigger_config config = {
> > + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
> > + .periodic = { .frequency_hz = freq },
> > + };
>
> Same comment as other patches. This needs to account for oversampling
> ratio.
>
I am thinking that since we would have different chip_info structs, manual
mode channels could omit the oversampling attribute, since it is not supported
by the chip on this mode.
> > +
> > + ret = spi_offload_trigger_validate(st->offload->trigger,
> &config);
> > + if (ret)
> > + return ret;
> > +
> > + st->offload->trigger_hz = config.periodic.frequency_hz;
> > + return len;
> > + }
> > +
> > ret = ad4691_set_pwm_freq(st, freq);
> > if (ret)
> > return ret;
> > @@ -968,10 +1283,23 @@ static irqreturn_t ad4691_trigger_handler(int
> irq, void *p)
> > return IRQ_HANDLED;
> > }
> >
> > +static int ad4691_get_current_scan_type(const struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan)
> > +{
> > + struct ad4691_state *st = iio_priv(indio_dev);
> > +
> > + if (!st->offload)
> > + return AD4691_SCAN_TYPE_NORMAL;
> > + if (st->manual_mode)
> > + return AD4691_SCAN_TYPE_OFFLOAD_MANUAL;
> > + return AD4691_SCAN_TYPE_OFFLOAD_CNV;
> > +}
> > +
> > static const struct iio_info ad4691_info = {
> > .read_raw = &ad4691_read_raw,
> > .write_raw = &ad4691_write_raw,
> > .read_avail = &ad4691_read_avail,
> > + .get_current_scan_type = &ad4691_get_current_scan_type,
> > .debugfs_reg_access = &ad4691_reg_access,
> > };
> >
> > @@ -1195,9 +1523,75 @@ static int ad4691_setup_triggered_buffer(struct
> iio_dev *indio_dev,
> >
> &ad4691_manual_buffer_setup_ops);
> > }
> >
> > +static int ad4691_setup_offload(struct iio_dev *indio_dev,
> > + struct ad4691_state *st,
> > + struct spi_offload *spi_offload)
> > +{
> > + struct device *dev = regmap_get_device(st->regmap);
> > + struct ad4691_offload_state *offload;
> > + struct dma_chan *rx_dma;
> > + int ret;
> > +
> > + offload = devm_kzalloc(dev, sizeof(*offload), GFP_KERNEL);
> > + if (!offload)
> > + return -ENOMEM;
> > +
> > + offload->spi = spi_offload;
> > + st->offload = offload;
> > +
> > + if (st->manual_mode) {
> > + offload->trigger =
> > + devm_spi_offload_trigger_get(dev, offload->spi,
> > +
> SPI_OFFLOAD_TRIGGER_PERIODIC);
> > + if (IS_ERR(offload->trigger))
> > + return dev_err_probe(dev, PTR_ERR(offload->trigger),
> > + "Failed to get periodic offload
> trigger\n");
> > +
> > + offload->trigger_hz = st->info->max_rate;
>
> I think I mentioned this elsewhere, but can we really get max_rate in manual
> mode
> due to the extra SPI overhead? Probably safer to start with a lower rate.
You are right a slower rate would be nicer, from my tests 311kHz worked perfect
with a 10MHz SPI frequency, but perhaps these numbers are a bit "odd".
How do you feel about 100kHz for a starting sample rate?
>
> > + } 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");
> > +
> > + offload->trigger =
> > + devm_spi_offload_trigger_get(dev, offload->spi,
> > +
> SPI_OFFLOAD_TRIGGER_DATA_READY);
> > + if (IS_ERR(offload->trigger))
> > + return dev_err_probe(dev, PTR_ERR(offload->trigger),
> > + "Failed to get DATA_READY offload
> trigger\n");
> > + }
> > +
> > + rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev,
> offload->spi);
> > + if (IS_ERR(rx_dma))
> > + return dev_err_probe(dev, PTR_ERR(rx_dma),
> > + "Failed to get offload RX DMA channel\n");
> > +
> > + if (st->manual_mode)
> > + indio_dev->setup_ops =
> &ad4691_manual_offload_buffer_setup_ops;
> > + else
> > + indio_dev->setup_ops =
> &ad4691_cnv_burst_offload_buffer_setup_ops;
> > +
> > + ret = devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
> rx_dma,
> > +
> IIO_BUFFER_DIRECTION_IN);
> > + if (ret)
> > + return ret;
> > +
> > + indio_dev->buffer->attrs = ad4691_buffer_attrs;
>
> Should including ad4691_buffer_attrs depend on st->manual_mode?
>
> I thought it was only used when PWM is connected to CNV.
>
For offload manual mode, I thought buffer sampling frequency should also be available,
since the offload trigger's frequency is accessible.
> > +
> > + return 0;
> > +}
> > +
> > static int ad4691_probe(struct spi_device *spi)
> > {
> > struct device *dev = &spi->dev;
> > + struct spi_offload *spi_offload;
> > struct iio_dev *indio_dev;
> > struct ad4691_state *st;
> > int ret;
> > @@ -1232,6 +1626,13 @@ static int ad4691_probe(struct spi_device *spi)
> > if (ret)
> > return ret;
> >
> > + spi_offload = devm_spi_offload_get(dev, spi,
> &ad4691_offload_config);
> > + ret = PTR_ERR_OR_ZERO(spi_offload);
> > + if (ret == -ENODEV)
> > + spi_offload = NULL;
> > + else if (ret)
> > + return dev_err_probe(dev, ret, "Failed to get SPI offload\n");
> > +
> > indio_dev->name = st->info->name;
> > indio_dev->info = &ad4691_info;
> > indio_dev->modes = INDIO_DIRECT_MODE;
> > @@ -1239,7 +1640,10 @@ static int ad4691_probe(struct spi_device *spi)
> > indio_dev->channels = st->info->channels;
> > indio_dev->num_channels = st->info->num_channels;
>
> As mentioned earlier, we generally want separate channel structs
> for SPI offload. These will also have different num_channels because
> there is no timestamp channel in SPI offload.
If different chip_info structs will be used, wouldn't they already have specific
channels attached to them?
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 10:39 ` Sabau, Radu bogdan
@ 2026-04-06 11:08 ` Sabau, Radu bogdan
2026-04-06 13:30 ` Sabau, Radu bogdan
2026-04-06 13:53 ` David Lechner
1 sibling, 1 reply; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 11:08 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: Sabau, Radu bogdan
> Sent: Monday, April 6, 2026 1:39 PM
...
> > > #define AD4691_CHANNEL(ch)
> > \
> > > { \
> > > .type = IIO_VOLTAGE, \
> > > @@ -122,11 +155,9 @@ struct ad4691_chip_info {
> > > .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),
> > \
> > > .channel = ch, \
> > > .scan_index = ch, \
> > > - .scan_type = { \
> > > - .sign = 'u', \
> > > - .realbits = 16, \
> > > - .storagebits = 16, \
> > > - }, \
> > > + .has_ext_scan_type = 1,
> > \
> > > + .ext_scan_type = ad4691_scan_types, \
> > > + .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types),
> > \
> >
> > Usually, we just make two separte ad4691_chip_info structs for offload
> > vs. not offload.
> >
> > ext_scan_type is generally only used when the scan type can change
> > dynamically after probe.
> >
>
> So, just to be clear, you are saying I should have different chip_info structs
> and change the triggered-buffer for offload ones if offload is present?
> I am asking since offload has different scan types as well, and this would
> mean 3 different chip_info structs for each chip -> total of 12 chip_info structs,
> each with a different channel array, or perhaps there is a more compact way
> to have this implemented.
> I could make the channel arrays use the same macro and have the scan_type
> reversed to storage and shift done as parameters.
>
I have given this a thought and I think this could be done in a more compact way:
1. Parametrize AD4691_CHANNEL to accept storagebits and shift, then define 4 channel
arrays:
- ad4691_channels[] - 16ch + timestamp (triggered-buffer path)
- ad4693_channels[] - 8ch + timestamp (triggered-buffer path)
- ad4691_offload_cnv_channels[] - 16 entries, storagebits=32, shift = 0
- ad4691_offload_manual_channels[] - 16 entries, storagebits=32, shift=16
The two offload arrays are shared across both chip families. Since num_channels
bound the interation in the IIO core, the 8ch chips simply use the first 8 entries of
the 16-entry offload arrays. Triggered-buffer path would need different channel
arrays since the timestamp index would be different, and offload doesn't use
timestamp.
2. chip_info could then stay at 2 structs, and have channels selected at probe for the
indio_dev, or have 4 chip info structs each having its own channels assigned, and only
num_channels could be changed at probe.
> Please let me know your thoughts on this.
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 11:08 ` Sabau, Radu bogdan
@ 2026-04-06 13:30 ` Sabau, Radu bogdan
2026-04-06 13:56 ` David Lechner
0 siblings, 1 reply; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 13:30 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: Sabau, Radu bogdan
> Sent: Monday, April 6, 2026 2:08 PM
>
> ...
>
> > > > #define AD4691_CHANNEL(ch)
> > > \
> > > > { \
> > > > .type = IIO_VOLTAGE, \
> > > > @@ -122,11 +155,9 @@ struct ad4691_chip_info {
> > > > .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),
> > > \
> > > > .channel = ch, \
> > > > .scan_index = ch, \
> > > > - .scan_type = { \
> > > > - .sign = 'u', \
> > > > - .realbits = 16, \
> > > > - .storagebits = 16, \
> > > > - }, \
> > > > + .has_ext_scan_type = 1,
> > > \
> > > > + .ext_scan_type = ad4691_scan_types, \
> > > > + .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types),
> > > \
> > >
> > > Usually, we just make two separte ad4691_chip_info structs for offload
> > > vs. not offload.
> > >
> > > ext_scan_type is generally only used when the scan type can change
> > > dynamically after probe.
> > >
> >
> > So, just to be clear, you are saying I should have different chip_info structs
> > and change the triggered-buffer for offload ones if offload is present?
> > I am asking since offload has different scan types as well, and this would
> > mean 3 different chip_info structs for each chip -> total of 12 chip_info
> structs,
> > each with a different channel array, or perhaps there is a more compact way
> > to have this implemented.
> > I could make the channel arrays use the same macro and have the scan_type
> > reversed to storage and shift done as parameters.
> >
>
> I have given this a thought and I think this could be done in a more compact
> way:
>
> 1. Parametrize AD4691_CHANNEL to accept storagebits and shift, then define
> 4 channel
> arrays:
>
> - ad4691_channels[] - 16ch + timestamp (triggered-buffer path)
> - ad4693_channels[] - 8ch + timestamp (triggered-buffer path)
> - ad4691_offload_cnv_channels[] - 16 entries, storagebits=32, shift =
> 0
> - ad4691_offload_manual_channels[] - 16 entries, storagebits=32,
> shift=16
>
> The two offload arrays are shared across both chip families. Since
> num_channels
> bound the interation in the IIO core, the 8ch chips simply use the first 8
> entries of
> the 16-entry offload arrays. Triggered-buffer path would need different
> channel
> arrays since the timestamp index would be different, and offload doesn't use
> timestamp.
>
> 2. chip_info could then stay at 2 structs, and have channels selected at probe
> for the
> indio_dev, or have 4 chip info structs each having its own channels assigned,
> and only
> num_channels could be changed at probe.
>
I also have to mention that the oversampling commit would then implement
AD4691_MANUAL_CHANNEL macro which would miss the OVERSAMPLING
infomask, and offload_manual_channels will be declared using it.
More than this, that commit would also add other ad4691_manual_channels[]
and ad4693_manual_channels[] arrays that would use that MACRO as well.
Then, chip_info would have ad4691/93_channels assigned to it by default,
and indio_dev->channels will later be assigned at probe, depending on the
mode and offload.
If different chip_info structs would be wanted still, then my best guess is
to have different info structures (perhaps new types) in chip_info by default.
Something like *sw_info and *offload_info.
Each one would contain all the pre-defined channel arrays in them
(channels and manual_channels) and so have ad4691_sw_info and ad4691_offload_info.
After so, chip_info will also contain besides these 2 info structures, num_channels and max_rate.
At probe indio_dev assignments will be made from the chip_info entirely.
What's your guys take on this? I am keen to hearing your thoughts about this.
Thanks,
Radu
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support
2026-04-06 9:22 ` Sabau, Radu bogdan
@ 2026-04-06 13:39 ` David Lechner
0 siblings, 0 replies; 23+ messages in thread
From: David Lechner @ 2026-04-06 13:39 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
On 4/6/26 4:22 AM, Sabau, Radu bogdan wrote:
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Saturday, April 4, 2026 6:12 PM
>
> ...
>
>>> +
>>> +/*
>>> + * Valid ACC_DEPTH values where the effective divisor equals the count.
>>> + * From Table 13: ACC_DEPTH = 2^N yields right-shift = N, divisor = 2^N.
>>> + */
>>> +static const int ad4691_oversampling_ratios[] = { 1, 2, 4, 8, 16, 32 };
>>
>> It would be nice to add oversampling in a separate commit as that is a
>> separate feature.
>
> Do you think this would be suitable after the offload commit?
>
Yes, I think that would be OK.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 9:34 ` Sabau, Radu bogdan
@ 2026-04-06 13:44 ` David Lechner
2026-04-06 14:16 ` Sabau, Radu bogdan
2026-04-07 12:38 ` Sabau, Radu bogdan
0 siblings, 2 replies; 23+ messages in thread
From: David Lechner @ 2026-04-06 13: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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
On 4/6/26 4:34 AM, Sabau, Radu bogdan wrote:
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Saturday, April 4, 2026 6:34 PM
>
> ...
>
>>> +Selected when a ``pwms`` property is present in the device tree. The PWM
>> drives
>>> +the CNV pin independently of SPI at the configured conversion rate, and a
>> GP
>>> +pin (identified by ``interrupt-names``) asserts DATA_READY at end-of-burst
>> to
>>> +signal that the AVG_IN result registers are ready to be read.
>>> +
>>> +The IRQ handler stops the PWM, fires the IIO trigger, and the trigger
>> handler
>>
>> If we stop the PWM after an IRQ, then we don't get a consistent sample rate.
>> Ideally, we would leave the PWM running and just pick a rate slow enough
>> that
>> there is plenty of time to read the data. Otherwise, this mode doesn't seem
>> particularly useful.
>
> Should there also be a condition when setting the sampling frequency, that will
> protect from setting too fast sample rates?
I haven't figured out a good way to do this since the real max rate
depends on a lot of different things and when not using offloading,
the time it takes to do SPI xfers is non-deterministic.
>>> +IIO DMA buffer:
>>> +
>>> +* **CNV Burst offload**: the SPI engine reads AVG_IN registers with a 2-
>> byte
>>> + address phase followed by a 2-byte data phase; the 16-bit result lands in
>>> + the lower half of the 32-bit word (``shift=0``).
>>> +* **Manual offload**: each 32-bit SPI word carries the channel byte in the
>>> + first byte; the 16-bit result is returned in the upper half of the 32-bit
>>
>> I would expect the "first" byte to be in the "upper half" of the 32-bits as
>> well. This layout could be explained better.
>>
>> Also, since extra data has to be read in this mode, does this affect the max
>> conversion rate?
>
> This is bad documentation on my part. "channel byte" isn't used anymore,
> this is previous version behaviour. Right now, only 16-bits worth of actual
> channel data are used.
>
Then why do we need the shift if there is no other data? Can't we rework
the SPI message so that there is no shift?
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 10:39 ` Sabau, Radu bogdan
2026-04-06 11:08 ` Sabau, Radu bogdan
@ 2026-04-06 13:53 ` David Lechner
1 sibling, 0 replies; 23+ messages in thread
From: David Lechner @ 2026-04-06 13:53 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
On 4/6/26 5:39 AM, Sabau, Radu bogdan wrote:
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Saturday, April 4, 2026 6:57 PM
>
> ...
>
>>> +
>>> #define AD4691_CHANNEL(ch)
>> \
>>> { \
>>> .type = IIO_VOLTAGE, \
>>> @@ -122,11 +155,9 @@ struct ad4691_chip_info {
>>> .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),
>> \
>>> .channel = ch, \
>>> .scan_index = ch, \
>>> - .scan_type = { \
>>> - .sign = 'u', \
>>> - .realbits = 16, \
>>> - .storagebits = 16, \
>>> - }, \
>>> + .has_ext_scan_type = 1,
>> \
>>> + .ext_scan_type = ad4691_scan_types, \
>>> + .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types),
>> \
>>
>> Usually, we just make two separte ad4691_chip_info structs for offload
>> vs. not offload.
>>
>> ext_scan_type is generally only used when the scan type can change
>> dynamically after probe.
>>
>
> So, just to be clear, you are saying I should have different chip_info structs
> and change the triggered-buffer for offload ones if offload is present?
> I am asking since offload has different scan types as well, and this would
> mean 3 different chip_info structs for each chip -> total of 12 chip_info structs,
> each with a different channel array, or perhaps there is a more compact way
> to have this implemented.
> I could make the channel arrays use the same macro and have the scan_type
> reversed to storage and shift done as parameters.
>
> Please let me know your thoughts on this.
If it gets too complex, we can dynamically create the chip info
struct during probe. But in general we prefer to statically define
them even if it gets a little verbose. Macros usually help here.
>>> }
>>>
>>> @@ -883,6 +1184,20 @@ static ssize_t sampling_frequency_store(struct
>> device *dev,
>>> if (iio_buffer_enabled(indio_dev))
>>> return -EBUSY;
>>>
>>> + if (st->manual_mode && st->offload) {
>>> + struct spi_offload_trigger_config config = {
>>> + .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
>>> + .periodic = { .frequency_hz = freq },
>>> + };
>>
>> Same comment as other patches. This needs to account for oversampling
>> ratio.
>>
>
> I am thinking that since we would have different chip_info structs, manual
> mode channels could omit the oversampling attribute, since it is not supported
> by the chip on this mode.
Yes, this would be ideal.
>> SPI_OFFLOAD_TRIGGER_PERIODIC);
>>> + if (IS_ERR(offload->trigger))
>>> + return dev_err_probe(dev, PTR_ERR(offload->trigger),
>>> + "Failed to get periodic offload
>> trigger\n");
>>> +
>>> + offload->trigger_hz = st->info->max_rate;
>>
>> I think I mentioned this elsewhere, but can we really get max_rate in manual
>> mode
>> due to the extra SPI overhead? Probably safer to start with a lower rate.
>
> You are right a slower rate would be nicer, from my tests 311kHz worked perfect
> with a 10MHz SPI frequency, but perhaps these numbers are a bit "odd".
>
> How do you feel about 100kHz for a starting sample rate?
Sounds reasonable.
>> IIO_BUFFER_DIRECTION_IN);
>>> + if (ret)
>>> + return ret;
>>> +
>>> + indio_dev->buffer->attrs = ad4691_buffer_attrs;
>>
>> Should including ad4691_buffer_attrs depend on st->manual_mode?
>>
>> I thought it was only used when PWM is connected to CNV.
>>
>
> For offload manual mode, I thought buffer sampling frequency should also be available,
> since the offload trigger's frequency is accessible.
Ah right. Not sure what I was thinking when I wrote that.
>
>>> +
>>> + return 0;
>>> +}
>>> +
>>> static int ad4691_probe(struct spi_device *spi)
>>> {
>>> struct device *dev = &spi->dev;
>>> + struct spi_offload *spi_offload;
>>> struct iio_dev *indio_dev;
>>> struct ad4691_state *st;
>>> int ret;
>>> @@ -1232,6 +1626,13 @@ static int ad4691_probe(struct spi_device *spi)
>>> if (ret)
>>> return ret;
>>>
>>> + spi_offload = devm_spi_offload_get(dev, spi,
>> &ad4691_offload_config);
>>> + ret = PTR_ERR_OR_ZERO(spi_offload);
>>> + if (ret == -ENODEV)
>>> + spi_offload = NULL;
>>> + else if (ret)
>>> + return dev_err_probe(dev, ret, "Failed to get SPI offload\n");
>>> +
>>> indio_dev->name = st->info->name;
>>> indio_dev->info = &ad4691_info;
>>> indio_dev->modes = INDIO_DIRECT_MODE;
>>> @@ -1239,7 +1640,10 @@ static int ad4691_probe(struct spi_device *spi)
>>> indio_dev->channels = st->info->channels;
>>> indio_dev->num_channels = st->info->num_channels;
>>
>> As mentioned earlier, we generally want separate channel structs
>> for SPI offload. These will also have different num_channels because
>> there is no timestamp channel in SPI offload.
>
> If different chip_info structs will be used, wouldn't they already have specific
> channels attached to them?
>
Yes.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 13:30 ` Sabau, Radu bogdan
@ 2026-04-06 13:56 ` David Lechner
2026-04-06 14:20 ` Sabau, Radu bogdan
0 siblings, 1 reply; 23+ messages in thread
From: David Lechner @ 2026-04-06 13:56 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
On 4/6/26 8:30 AM, Sabau, Radu bogdan wrote:
>
>
>> -----Original Message-----
>> From: Sabau, Radu bogdan
>> Sent: Monday, April 6, 2026 2:08 PM
>>
>> ...
>>
>>>>> #define AD4691_CHANNEL(ch)
>>>> \
>>>>> { \
>>>>> .type = IIO_VOLTAGE, \
>>>>> @@ -122,11 +155,9 @@ struct ad4691_chip_info {
>>>>> .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SCALE),
>>>> \
>>>>> .channel = ch, \
>>>>> .scan_index = ch, \
>>>>> - .scan_type = { \
>>>>> - .sign = 'u', \
>>>>> - .realbits = 16, \
>>>>> - .storagebits = 16, \
>>>>> - }, \
>>>>> + .has_ext_scan_type = 1,
>>>> \
>>>>> + .ext_scan_type = ad4691_scan_types, \
>>>>> + .num_ext_scan_type = ARRAY_SIZE(ad4691_scan_types),
>>>> \
>>>>
>>>> Usually, we just make two separte ad4691_chip_info structs for offload
>>>> vs. not offload.
>>>>
>>>> ext_scan_type is generally only used when the scan type can change
>>>> dynamically after probe.
>>>>
>>>
>>> So, just to be clear, you are saying I should have different chip_info structs
>>> and change the triggered-buffer for offload ones if offload is present?
>>> I am asking since offload has different scan types as well, and this would
>>> mean 3 different chip_info structs for each chip -> total of 12 chip_info
>> structs,
>>> each with a different channel array, or perhaps there is a more compact way
>>> to have this implemented.
>>> I could make the channel arrays use the same macro and have the scan_type
>>> reversed to storage and shift done as parameters.
>>>
>>
>> I have given this a thought and I think this could be done in a more compact
>> way:
>>
>> 1. Parametrize AD4691_CHANNEL to accept storagebits and shift, then define
>> 4 channel
>> arrays:
>>
>> - ad4691_channels[] - 16ch + timestamp (triggered-buffer path)
>> - ad4693_channels[] - 8ch + timestamp (triggered-buffer path)
>> - ad4691_offload_cnv_channels[] - 16 entries, storagebits=32, shift =
>> 0
>> - ad4691_offload_manual_channels[] - 16 entries, storagebits=32,
>> shift=16
>>
>> The two offload arrays are shared across both chip families. Since
>> num_channels
>> bound the interation in the IIO core, the 8ch chips simply use the first 8
>> entries of
>> the 16-entry offload arrays. Triggered-buffer path would need different
>> channel
>> arrays since the timestamp index would be different, and offload doesn't use
>> timestamp.
>>
>> 2. chip_info could then stay at 2 structs, and have channels selected at probe
>> for the
>> indio_dev, or have 4 chip info structs each having its own channels assigned,
>> and only
>> num_channels could be changed at probe.
>>
>
> I also have to mention that the oversampling commit would then implement
> AD4691_MANUAL_CHANNEL macro which would miss the OVERSAMPLING
> infomask, and offload_manual_channels will be declared using it.
> More than this, that commit would also add other ad4691_manual_channels[]
> and ad4693_manual_channels[] arrays that would use that MACRO as well.
>
> Then, chip_info would have ad4691/93_channels assigned to it by default,
> and indio_dev->channels will later be assigned at probe, depending on the
> mode and offload.
>
> If different chip_info structs would be wanted still, then my best guess is
> to have different info structures (perhaps new types) in chip_info by default.
> Something like *sw_info and *offload_info.
Yes, this is how I would do it too.
> Each one would contain all the pre-defined channel arrays in them
> (channels and manual_channels) and so have ad4691_sw_info and ad4691_offload_info.
> After so, chip_info will also contain besides these 2 info structures, num_channels and max_rate.
> At probe indio_dev assignments will be made from the chip_info entirely.
>
> What's your guys take on this? I am keen to hearing your thoughts about this.
>
> Thanks,
> Radu
>
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 13:44 ` David Lechner
@ 2026-04-06 14:16 ` Sabau, Radu bogdan
2026-04-06 15:02 ` David Lechner
2026-04-07 12:38 ` Sabau, Radu bogdan
1 sibling, 1 reply; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 14:16 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, April 6, 2026 4:44 PM
...
> >
> > This is bad documentation on my part. "channel byte" isn't used anymore,
> > this is previous version behaviour. Right now, only 16-bits worth of actual
> > channel data are used.
> >
> Then why do we need the shift if there is no other data? Can't we rework
> the SPI message so that there is no shift?
I thought the shift is needed since DMA size is 32 bits, and value comes on the
upper word 16 bits, not on the lower ones as for CNV Burst.
Manual Mode layout: TX [CMD_HI CMD_LO DUMMY DUMMY], RX [DATA_HI DATA_LO DUMMY DUMMY]
CNV Burst layout: TX [REG_HI REG_LO DUMMY DUMMY], RX [DUMMY DUMMY DATA_HI DATA_LO]
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 13:56 ` David Lechner
@ 2026-04-06 14:20 ` Sabau, Radu bogdan
0 siblings, 0 replies; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-06 14:20 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, April 6, 2026 4:57 PM
...
> >
> > I also have to mention that the oversampling commit would then implement
> > AD4691_MANUAL_CHANNEL macro which would miss the OVERSAMPLING
> > infomask, and offload_manual_channels will be declared using it.
> > More than this, that commit would also add other
> ad4691_manual_channels[]
> > and ad4693_manual_channels[] arrays that would use that MACRO as well.
> >
> > Then, chip_info would have ad4691/93_channels assigned to it by default,
> > and indio_dev->channels will later be assigned at probe, depending on the
> > mode and offload.
> >
> > If different chip_info structs would be wanted still, then my best guess is
> > to have different info structures (perhaps new types) in chip_info by default.
> > Something like *sw_info and *offload_info.
>
> Yes, this is how I would do it too.
>
Ok then, will have different chip infos and each one will have respective channels
to them. Thanks for this, too!
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 14:16 ` Sabau, Radu bogdan
@ 2026-04-06 15:02 ` David Lechner
0 siblings, 0 replies; 23+ messages in thread
From: David Lechner @ 2026-04-06 15:02 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
On 4/6/26 9:16 AM, Sabau, Radu bogdan wrote:
>
>
>> -----Original Message-----
>> From: David Lechner <dlechner@baylibre.com>
>> Sent: Monday, April 6, 2026 4:44 PM
>
> ...
>
>>>
>>> This is bad documentation on my part. "channel byte" isn't used anymore,
>>> this is previous version behaviour. Right now, only 16-bits worth of actual
>>> channel data are used.
>>>
>> Then why do we need the shift if there is no other data? Can't we rework
>> the SPI message so that there is no shift?
>
> I thought the shift is needed since DMA size is 32 bits, and value comes on the
> upper word 16 bits, not on the lower ones as for CNV Burst.
That should only happen if we are reading 32-bits instead of 16 bits.
We should be able to set up the SPI xfers so that we only read 16 bits.
>
> Manual Mode layout: TX [CMD_HI CMD_LO DUMMY DUMMY], RX [DATA_HI DATA_LO DUMMY DUMMY]
> CNV Burst layout: TX [REG_HI REG_LO DUMMY DUMMY], RX [DUMMY DUMMY DATA_HI DATA_LO]
This can be split in two xfers.
CNV Burst layout:
TX [REG_HI REG_LO]
RX [DATA_HI DATA_LO]
And we could even set bits_per_word to 16 so that the data is CPU
endian instead of big endian when doing SPI offloading.
^ permalink raw reply [flat|nested] 23+ messages in thread
* RE: [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support
2026-04-06 13:44 ` David Lechner
2026-04-06 14:16 ` Sabau, Radu bogdan
@ 2026-04-07 12:38 ` Sabau, Radu bogdan
1 sibling, 0 replies; 23+ messages in thread
From: Sabau, Radu bogdan @ 2026-04-07 12:38 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, Jonathan Corbet, Shuah Khan
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, linux-doc@vger.kernel.org
> -----Original Message-----
> From: David Lechner <dlechner@baylibre.com>
> Sent: Monday, April 6, 2026 4:44 PM
...
> >> If we stop the PWM after an IRQ, then we don't get a consistent sample
> rate.
> >> Ideally, we would leave the PWM running and just pick a rate slow enough
> >> that
> >> there is plenty of time to read the data. Otherwise, this mode doesn't seem
> >> particularly useful.
> >
> > Should there also be a condition when setting the sampling frequency, that
> will
> > protect from setting too fast sample rates?
>
> I haven't figured out a good way to do this since the real max rate
> depends on a lot of different things and when not using offloading,
> the time it takes to do SPI xfers is non-deterministic.
>
There is another question that pops in my mind. How would osr work for the buffer
sampling frequency? Since that controls the PWM period, and if say channels 0, 1, 2
are used with osr 1, 4, 8 respectively, then it would mean the PWM "required
frequency" would be something like :
osr_avg = (osr[0] + osr[1] + osr[2]) / 3;
actual_samp_freq = samp_freq / osr_avg;
The problem here being - beside what you already stated that the xfer is non-deterministic -
that the implementation could be fragile, since buffer_sampling frequency wouldn't really
know for sure which channels are enabled and so couldn't account for osr in a safe way.
The solution I propose is letting PWM run from buffer enable to disable, the whole time,
while only disabling/enabling the irq in the handler before and after spi transfer, to also
assure they are rightly happening. This way, the PWM runs at the desired rate, the only
thing here is that if the PWM is set to a too high of a frequency, some periods will be
missed, which is normal if you think about it.
For example : if the user uses a 1MHz SPI for some reason, and wants a PWM of 500kHz,
and enables 4 channels with maximum osr and maximum per-channel samp_freq.
tPWM = 1/500kHz = 2us;
tOSR_per_channel = 32 * tOSC = 32us;
-> tPWM is already too fast, but there would be more
tOSR_total = 4 * 32 = 128us;
tSPI_per_channel = 32 * tSPI = 32us (too say the least)
tSPI_total = 128us;
tSEQ_total = 256us (again, to say the least)
so the actual sampling freq is 256us in this case, but that's non-determinstic, since this
is nothing but a best case scenario, time would vary between 256-280us to say so in
reality.
I would then say so, setting a too high buffer sampling frequency (PWM frequency) implies
by itself that samples will be missed.
So then, PWM freq should be set by the user, to the desired value without capping of the
value since that capping depends on too much run-time variables set by the same user.
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-04-07 12:39 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-03 11:03 [PATCH v6 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 1/4] dt-bindings: iio: adc: add AD4691 family Radu Sabau via B4 Relay
2026-04-03 11:03 ` [PATCH v6 2/4] iio: adc: ad4691: add initial driver for " Radu Sabau via B4 Relay
2026-04-04 14:25 ` David Lechner
2026-04-03 11:03 ` [PATCH v6 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
2026-04-04 15:12 ` David Lechner
2026-04-05 8:57 ` Andy Shevchenko
2026-04-06 9:22 ` Sabau, Radu bogdan
2026-04-06 13:39 ` David Lechner
2026-04-03 11:03 ` [PATCH v6 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
2026-04-04 15:34 ` David Lechner
2026-04-06 9:34 ` Sabau, Radu bogdan
2026-04-06 13:44 ` David Lechner
2026-04-06 14:16 ` Sabau, Radu bogdan
2026-04-06 15:02 ` David Lechner
2026-04-07 12:38 ` Sabau, Radu bogdan
2026-04-04 15:57 ` David Lechner
2026-04-06 10:39 ` Sabau, Radu bogdan
2026-04-06 11:08 ` Sabau, Radu bogdan
2026-04-06 13:30 ` Sabau, Radu bogdan
2026-04-06 13:56 ` David Lechner
2026-04-06 14:20 ` Sabau, Radu bogdan
2026-04-06 13:53 ` David Lechner
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox