* [PATCH v2 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family
@ 2026-03-10 14:32 Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
` (3 more replies)
0 siblings, 4 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-10 14:32 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
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
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 Clock Mode: external PWM drives CNV independently of SPI;
DATA_READY on GP0 signals end of conversion
- Manual Mode: CNV tied to SPI CS; each SPI transfer reads
the previous conversion result and starts the
next (pipelined N+1 scheme)
A new driver is warranted rather than extending ad4695: the AD4691
data path uses an accumulator-register model — results are read from
AVG_IN registers, with ACC_MASK, ADC_SETUP, DEVICE_SETUP, and
GPIO_MODE registers controlling the sequencer — none of which exist
in AD4695. CNV Clock Mode (PWM drives CNV independently of SPI) and
Manual Mode (pipelined N+1 transfers) also have no equivalent in
AD4695's command-embedded single-cycle protocol.
The series is structured as follows:
1/4 - DT bindings (YAML schema + dt-bindings header) and
MAINTAINERS entry
2/4 - Initial driver: register map via custom regmap callbacks,
IIO read_raw/write_raw, both operating modes, single-channel
reads via internal oscillator (Autonomous Mode)
3/4 - Triggered buffer support: IRQ-driven (DATA_READY on GP0) for
CNV Clock Mode; hrtimer-based trigger for Manual Mode to
handle the pipelined N+1 SPI protocol
4/4 - SPI Engine offload support: DMA-backed high-throughput
capture path using the SPI offload subsystem
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 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 bindings for 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 | 180 +++
MAINTAINERS | 9 +
drivers/iio/adc/Kconfig | 14 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4691.c | 1506 ++++++++++++++++++++
include/dt-bindings/iio/adc/adi,ad4691.h | 13 +
6 files changed, 1723 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] 9+ messages in thread
* [PATCH v2 1/4] dt-bindings: iio: adc: add bindings for AD4691 family
2026-03-10 14:32 [PATCH v2 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
@ 2026-03-10 14:32 ` Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
` (2 subsequent siblings)
3 siblings, 0 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-10 14:32 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
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add DT bindings for the Analog Devices AD4691 family of multichannel
SAR ADCs (AD4691, AD4692, AD4693, AD4694).
The binding describes the hardware connections: an optional PWM on
the CNV pin selects CNV Clock Mode; when absent, Manual Mode is used
with CNV tied to SPI CS. GPIO pins, voltage supplies, and the
trigger-source interface for SPI Engine offload operation are also
described.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
.../devicetree/bindings/iio/adc/adi,ad4691.yaml | 180 +++++++++++++++++++++
MAINTAINERS | 8 +
include/dt-bindings/iio/adc/adi,ad4691.h | 13 ++
3 files changed, 201 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
new file mode 100644
index 000000000000..a9301e0ca851
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
@@ -0,0 +1,180 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ad4691.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD4691 Family Multichannel SAR ADCs
+
+maintainers:
+ - Radu Sabau <radu.sabau@analog.com>
+
+description: |
+ The AD4691 family are high-speed, low-power, multichannel successive
+ approximation register (SAR) analog-to-digital converters (ADCs) with
+ an SPI-compatible serial interface. The ADC supports CNV Clock Mode,
+ where an external PWM drives the CNV pin, and Manual Mode, where CNV
+ is directly tied to the SPI chip-select.
+
+ Datasheets:
+ * https://www.analog.com/en/products/ad4692.html
+ * https://www.analog.com/en/products/ad4691.html
+ * https://www.analog.com/en/products/ad4694.html
+ * https://www.analog.com/en/products/ad4693.html
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+ compatible:
+ enum:
+ - adi,ad4691
+ - adi,ad4692
+ - adi,ad4693
+ - adi,ad4694
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 40000000
+
+ spi-cpol: true
+ spi-cpha: true
+
+ vio-supply:
+ description: I/O voltage supply (1.71V to 1.89V or VDD).
+
+ vref-supply:
+ description: External reference voltage supply (2.4V to 5.25V).
+
+ vrefin-supply:
+ description: Internal reference buffer input supply.
+
+ reset-gpios:
+ description: GPIO connected to the RESET pin (active high).
+ maxItems: 1
+
+ clocks:
+ description: Reference clock for PWM timing in CNV Clock Mode.
+ maxItems: 1
+
+ pwms:
+ description:
+ PWM connected to the CNV pin. When present, selects CNV Clock Mode where
+ the PWM drives the conversion rate. When absent, Manual Mode is used
+ (CNV tied to SPI CS).
+ maxItems: 1
+
+ pwm-names:
+ items:
+ - const: cnv
+
+ interrupts:
+ description:
+ Interrupt line connected to the ADC GP0 pin. GP0 must be physically
+ wired to an interrupt-capable input on the SoC. The ADC asserts GP0 as
+ DATA_READY at end of conversion, used both for non-offload CNV Clock Mode
+ operation and for SPI Engine offload triggering via '#trigger-source-cells'.
+ Not used in Manual Mode, where CNV is tied to SPI CS and no DATA_READY
+ signal is generated.
+ maxItems: 1
+
+ '#trigger-source-cells':
+ description: |
+ For SPI Engine offload operation, this node acts as a trigger source.
+ Two cells are required:
+ - First cell: Trigger event type (0 = BUSY, 1 = DATA_READY)
+ - Second cell: GPIO pin number (only 0 = GP0 is supported)
+
+ Macros are available in dt-bindings/iio/adc/adi,ad4691.h:
+ AD4691_TRIGGER_EVENT_BUSY, AD4691_TRIGGER_EVENT_DATA_READY
+ AD4691_TRIGGER_PIN_GP0
+ const: 2
+
+required:
+ - compatible
+ - reg
+ - vio-supply
+ - reset-gpios
+
+allOf:
+ # vref-supply and vrefin-supply are mutually exclusive, one is required
+ - oneOf:
+ - required:
+ - vref-supply
+ - required:
+ - vrefin-supply
+
+ # CNV Clock Mode requires a reference clock.
+ - if:
+ required:
+ - pwms
+ then:
+ required:
+ - clocks
+
+ # CNV Clock Mode (pwms present) without SPI offload requires a DRDY interrupt.
+ # Offload configurations expose '#trigger-source-cells' instead.
+ - if:
+ required:
+ - pwms
+ not:
+ required:
+ - '#trigger-source-cells'
+ then:
+ required:
+ - interrupts
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ /* Example: AD4692 in CNV Clock Mode (pwms present) with standard SPI */
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ reg = <0>;
+ spi-cpol;
+ spi-cpha;
+ spi-max-frequency = <40000000>;
+
+ vio-supply = <&vio_supply>;
+ vref-supply = <&vref_5v>;
+
+ reset-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
+
+ clocks = <&ref_clk>;
+
+ pwms = <&pwm_gen 0 0>;
+ pwm-names = "cnv";
+
+ interrupts = <12 4>;
+ };
+ };
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+
+ /* Example: AD4692 in Manual Mode (no pwms) with SPI Engine offload */
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "adi,ad4692";
+ reg = <0>;
+ spi-cpol;
+ spi-cpha;
+ spi-max-frequency = <31250000>;
+
+ vio-supply = <&vio_supply>;
+ vrefin-supply = <&vrefin_supply>;
+
+ reset-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 61bf550fd37c..9994d107d88d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1484,6 +1484,14 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad4170-4.yaml
F: drivers/iio/adc/ad4170-4.c
+ANALOG DEVICES INC AD4691 DRIVER
+M: Radu Sabau <radu.sabau@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F: include/dt-bindings/iio/adc/adi,ad4691.h
+
ANALOG DEVICES INC AD4695 DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
M: Nuno Sá <nuno.sa@analog.com>
diff --git a/include/dt-bindings/iio/adc/adi,ad4691.h b/include/dt-bindings/iio/adc/adi,ad4691.h
new file mode 100644
index 000000000000..294b03974f48
--- /dev/null
+++ b/include/dt-bindings/iio/adc/adi,ad4691.h
@@ -0,0 +1,13 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) */
+
+#ifndef _DT_BINDINGS_ADI_AD4691_H
+#define _DT_BINDINGS_ADI_AD4691_H
+
+/* Trigger event types */
+#define AD4691_TRIGGER_EVENT_BUSY 0
+#define AD4691_TRIGGER_EVENT_DATA_READY 1
+
+/* Trigger GPIO pin selection */
+#define AD4691_TRIGGER_PIN_GP0 0
+
+#endif /* _DT_BINDINGS_ADI_AD4691_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-10 14:32 [PATCH v2 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
@ 2026-03-10 14:32 ` Radu Sabau via B4 Relay
2026-03-11 21:03 ` Andy Shevchenko
2026-03-13 10:31 ` Uwe Kleine-König
2026-03-10 14:32 ` [PATCH v2 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
3 siblings, 2 replies; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-10 14:32 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
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add support for the Analog Devices AD4691 family of high-speed,
low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
AD4694 (8-ch, 1 MSPS).
The driver implements a custom regmap layer over raw SPI to handle the
device's mixed 1/2/3/4-byte register widths and uses the standard IIO
read_raw/write_raw interface for single-channel reads.
Two buffered operating modes are supported, auto-detected from the
device tree:
- CNV Clock Mode: an external PWM drives the CNV pin; the sampling
rate is controlled via the PWM period. Requires a
reference clock and a DATA_READY interrupt.
- Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
a conversion and returns the previous result
(pipelined). No external clock or interrupt needed.
In both modes the chip idles in Autonomous Mode so that single-shot
read_raw can use the internal oscillator without disturbing the
hardware configuration.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ad4691.c | 773 +++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 786 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 9994d107d88d..5325f7d3b7f4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
+F: drivers/iio/adc/ad4691.c
F: include/dt-bindings/iio/adc/adi,ad4691.h
ANALOG DEVICES INC AD4695 DRIVER
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 60038ae8dfc4..3685a03aa8dc 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -139,6 +139,17 @@ config AD4170_4
To compile this driver as a module, choose M here: the module will be
called ad4170-4.
+config AD4691
+ tristate "Analog Devices AD4691 Family ADC Driver"
+ depends on SPI
+ select REGMAP
+ help
+ Say yes here to build support for Analog Devices AD4691 Family MuxSAR
+ SPI analog to digital converters (ADC).
+
+ To compile this driver as a module, choose M here: the module will be
+ called ad4691.
+
config AD4695
tristate "Analog Device AD4695 ADC Driver"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index c76550415ff1..4ac1ea09d773 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
obj-$(CONFIG_AD4130) += ad4130.o
obj-$(CONFIG_AD4134) += ad4134.o
obj-$(CONFIG_AD4170_4) += ad4170-4.o
+obj-$(CONFIG_AD4691) += ad4691.o
obj-$(CONFIG_AD4695) += ad4695.o
obj-$(CONFIG_AD4851) += ad4851.o
obj-$(CONFIG_AD7091R) += ad7091r-base.o
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
new file mode 100644
index 000000000000..528c37a9a383
--- /dev/null
+++ b/drivers/iio/adc/ad4691.c
@@ -0,0 +1,773 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2024-2026 Analog Devices, Inc.
+ * Author: Radu Sabau <radu.sabau@analog.com>
+ */
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/util_macros.h>
+#include <linux/units.h>
+#include <linux/unaligned.h>
+
+#include <linux/iio/iio.h>
+
+#include <dt-bindings/iio/adc/adi,ad4691.h>
+
+#define AD4691_VREF_MIN 2400000
+#define AD4691_VREF_MAX 5250000
+
+/*
+ * Default sampling frequency for MANUAL_MODE.
+ * Each sample needs (num_channels + 1) SPI transfers of 24 bits.
+ * The factor 36 = 24 * 3/2 folds in a 50% scheduling margin:
+ * freq = spi_hz / (24 * 3/2 * (num_channels + 1))
+ * = spi_hz / (36 * (num_channels + 1))
+ */
+#define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
+#define AD4691_BITS_PER_XFER 24
+#define AD4691_CNV_DUTY_CYCLE_NS 380
+#define AD4691_MAX_CONV_PERIOD_US 800
+
+#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
+#define AD4691_STATE_RESET_ALL 0x01
+
+#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
+
+#define AD4691_DEVICE_MANUAL 0x14
+#define AD4691_DEVICE_REGISTER 0x10
+#define AD4691_AUTONOMOUS_MODE_VAL 0x02
+
+#define AD4691_NOOP 0x00
+#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
+
+#define AD4691_STATUS_REG 0x14
+#define AD4691_CLAMP_STATUS1_REG 0x1A
+#define AD4691_CLAMP_STATUS2_REG 0x1B
+#define AD4691_DEVICE_SETUP 0x20
+#define AD4691_REF_CTRL 0x21
+#define AD4691_OSC_FREQ_REG 0x23
+#define AD4691_STD_SEQ_CONFIG 0x25
+#define AD4691_SPARE_CONTROL 0x2A
+
+#define AD4691_OSC_EN_REG 0x180
+#define AD4691_STATE_RESET_REG 0x181
+#define AD4691_ADC_SETUP 0x182
+#define AD4691_ACC_MASK1_REG 0x184
+#define AD4691_ACC_MASK2_REG 0x185
+#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
+#define AD4691_ACC_COUNT_VAL 0x3F
+#define AD4691_GPIO_MODE1_REG 0x196
+#define AD4691_GPIO_MODE2_REG 0x197
+#define AD4691_GPIO_READ 0x1A0
+#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
+#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
+#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
+#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
+#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
+#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
+#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
+#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
+#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
+#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
+#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
+
+enum ad4691_adc_mode {
+ AD4691_CNV_CLOCK_MODE,
+ AD4691_MANUAL_MODE,
+};
+
+enum ad4691_gpio_mode {
+ AD4691_ADC_BUSY = 4,
+ AD4691_DATA_READY = 6,
+};
+
+enum ad4691_ref_ctrl {
+ AD4691_VREF_2P5 = 0,
+ AD4691_VREF_3P0,
+ AD4691_VREF_3P3,
+ AD4691_VREF_4P096,
+ AD4691_VREF_5P0,
+};
+
+struct ad4691_chip_info {
+ const struct iio_chan_spec *channels;
+ const struct iio_chan_spec *manual_channels;
+ const char *name;
+ int num_channels;
+ int max_rate;
+};
+
+#define AD4691_CHANNEL(chan, index, real_bits, storage_bits, _shift) \
+ { \
+ .type = IIO_VOLTAGE, \
+ .indexed = 1, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+ .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) \
+ | BIT(IIO_CHAN_INFO_SCALE), \
+ .channel = chan, \
+ .scan_index = index, \
+ .scan_type = { \
+ .sign = 'u', \
+ .realbits = real_bits, \
+ .storagebits = storage_bits, \
+ .shift = _shift, \
+ }, \
+ }
+
+static const struct iio_chan_spec ad4691_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 32, 0),
+ AD4691_CHANNEL(1, 1, 16, 32, 0),
+ AD4691_CHANNEL(2, 2, 16, 32, 0),
+ AD4691_CHANNEL(3, 3, 16, 32, 0),
+ AD4691_CHANNEL(4, 4, 16, 32, 0),
+ AD4691_CHANNEL(5, 5, 16, 32, 0),
+ AD4691_CHANNEL(6, 6, 16, 32, 0),
+ AD4691_CHANNEL(7, 7, 16, 32, 0),
+ AD4691_CHANNEL(8, 8, 16, 32, 0),
+ AD4691_CHANNEL(9, 9, 16, 32, 0),
+ AD4691_CHANNEL(10, 10, 16, 32, 0),
+ AD4691_CHANNEL(11, 11, 16, 32, 0),
+ AD4691_CHANNEL(12, 12, 16, 32, 0),
+ AD4691_CHANNEL(13, 13, 16, 32, 0),
+ AD4691_CHANNEL(14, 14, 16, 32, 0),
+ AD4691_CHANNEL(15, 15, 16, 32, 0)
+};
+
+static const struct iio_chan_spec ad4693_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 32, 0),
+ AD4691_CHANNEL(1, 1, 16, 32, 0),
+ AD4691_CHANNEL(2, 2, 16, 32, 0),
+ AD4691_CHANNEL(3, 3, 16, 32, 0),
+ AD4691_CHANNEL(4, 4, 16, 32, 0),
+ AD4691_CHANNEL(5, 5, 16, 32, 0),
+ AD4691_CHANNEL(6, 6, 16, 32, 0),
+ AD4691_CHANNEL(7, 7, 16, 32, 0)
+};
+
+static const struct iio_chan_spec ad4691_manual_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 24, 8),
+ AD4691_CHANNEL(1, 1, 16, 24, 8),
+ AD4691_CHANNEL(2, 2, 16, 24, 8),
+ AD4691_CHANNEL(3, 3, 16, 24, 8),
+ AD4691_CHANNEL(4, 4, 16, 24, 8),
+ AD4691_CHANNEL(5, 5, 16, 24, 8),
+ AD4691_CHANNEL(6, 6, 16, 24, 8),
+ AD4691_CHANNEL(7, 7, 16, 24, 8),
+ AD4691_CHANNEL(8, 8, 16, 24, 8),
+ AD4691_CHANNEL(9, 9, 16, 24, 8),
+ AD4691_CHANNEL(10, 10, 16, 24, 8),
+ AD4691_CHANNEL(11, 11, 16, 24, 8),
+ AD4691_CHANNEL(12, 12, 16, 24, 8),
+ AD4691_CHANNEL(13, 13, 16, 24, 8),
+ AD4691_CHANNEL(14, 14, 16, 24, 8),
+ AD4691_CHANNEL(15, 15, 16, 24, 8)
+};
+
+static const struct iio_chan_spec ad4693_manual_channels[] = {
+ AD4691_CHANNEL(0, 0, 16, 24, 8),
+ AD4691_CHANNEL(1, 1, 16, 24, 8),
+ AD4691_CHANNEL(2, 2, 16, 24, 8),
+ AD4691_CHANNEL(3, 3, 16, 24, 8),
+ AD4691_CHANNEL(4, 4, 16, 24, 8),
+ AD4691_CHANNEL(5, 5, 16, 24, 8),
+ AD4691_CHANNEL(6, 6, 16, 24, 8),
+ AD4691_CHANNEL(7, 7, 16, 24, 8)
+};
+
+static const struct ad4691_chip_info ad4691_ad4691 = {
+ .channels = ad4691_channels,
+ .manual_channels = ad4691_manual_channels,
+ .name = "ad4691",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 500000,
+};
+
+static const struct ad4691_chip_info ad4691_ad4692 = {
+ .channels = ad4691_channels,
+ .manual_channels = ad4691_manual_channels,
+ .name = "ad4692",
+ .num_channels = ARRAY_SIZE(ad4691_channels),
+ .max_rate = 1000000,
+};
+
+static const struct ad4691_chip_info ad4691_ad4693 = {
+ .channels = ad4693_channels,
+ .manual_channels = ad4693_manual_channels,
+ .name = "ad4693",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 500000,
+};
+
+static const struct ad4691_chip_info ad4691_ad4694 = {
+ .channels = ad4693_channels,
+ .manual_channels = ad4693_manual_channels,
+ .name = "ad4694",
+ .num_channels = ARRAY_SIZE(ad4693_channels),
+ .max_rate = 1000000,
+};
+
+struct ad4691_state {
+ const struct ad4691_chip_info *chip;
+ struct spi_device *spi;
+ struct regmap *regmap;
+
+ unsigned long ref_clk_rate;
+ struct pwm_device *conv_trigger;
+
+ enum ad4691_adc_mode adc_mode;
+
+ int vref;
+ u64 cnv_period;
+ /*
+ * Synchronize access to members of the driver state, and ensure
+ * atomicity of consecutive SPI operations.
+ */
+ struct mutex lock;
+};
+
+static void ad4691_disable_pwm(void *data)
+{
+ struct pwm_device *pwm = data;
+ struct pwm_state state;
+
+ pwm_get_state(pwm, &state);
+ state.enabled = false;
+ pwm_apply_might_sleep(pwm, &state);
+}
+
+static int ad4691_regulator_get(struct ad4691_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = devm_regulator_get_enable(dev, "vio");
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
+
+ st->vref = devm_regulator_get_enable_read_voltage(dev, "vref");
+ if (st->vref == -ENODEV)
+ st->vref = devm_regulator_get_enable_read_voltage(dev, "vrefin");
+ if (st->vref < 0)
+ return dev_err_probe(dev, st->vref,
+ "Failed to get reference supply\n");
+
+ if (st->vref < AD4691_VREF_MIN || st->vref > AD4691_VREF_MAX)
+ return dev_err_probe(dev, -EINVAL, "vref(%d) must be under [%u %u]\n",
+ st->vref, AD4691_VREF_MIN, AD4691_VREF_MAX);
+
+ return 0;
+}
+
+static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
+{
+ struct ad4691_state *st = context;
+ u8 tx[2], rx[4];
+ int ret;
+
+ put_unaligned_be16(0x8000 | reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
+ ret = spi_write_then_read(st->spi, tx, 2, rx, 1);
+ if (!ret)
+ *val = rx[0];
+ return ret;
+ case AD4691_STD_SEQ_CONFIG:
+ case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
+ ret = spi_write_then_read(st->spi, tx, 2, rx, 2);
+ if (!ret)
+ *val = get_unaligned_be16(rx);
+ return ret;
+ 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(st->spi, tx, 2, rx, 3);
+ if (!ret)
+ *val = get_unaligned_be24(rx);
+ return ret;
+ case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
+ ret = spi_write_then_read(st->spi, tx, 2, rx, 4);
+ if (!ret)
+ *val = get_unaligned_be32(rx);
+ return ret;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
+{
+ struct ad4691_state *st = context;
+ u8 tx[4];
+
+ put_unaligned_be16(reg, tx);
+
+ switch (reg) {
+ case 0 ... AD4691_OSC_FREQ_REG:
+ case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
+ if (val > 0xFF)
+ return -EINVAL;
+ tx[2] = val;
+ return spi_write_then_read(st->spi, tx, 3, NULL, 0);
+ case AD4691_STD_SEQ_CONFIG:
+ if (val > 0xFFFF)
+ return -EINVAL;
+ put_unaligned_be16(val, &tx[2]);
+ return spi_write_then_read(st->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_RBTREE,
+};
+
+static int ad4691_get_sampling_freq(struct ad4691_state *st)
+{
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
+ ktime_to_ns(st->sampling_period));
+ }
+
+ return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
+ pwm_get_period(st->conv_trigger));
+}
+
+static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
+{
+ unsigned long long target, ref_clk_period_ns;
+ struct pwm_state cnv_state;
+
+ pwm_init_state(st->conv_trigger, &cnv_state);
+
+ freq = clamp(freq, 1, st->chip->max_rate);
+ target = DIV_ROUND_CLOSEST_ULL(st->ref_clk_rate, freq);
+ ref_clk_period_ns = DIV_ROUND_CLOSEST_ULL(NANO, st->ref_clk_rate);
+ st->cnv_period = ref_clk_period_ns * target;
+ cnv_state.period = ref_clk_period_ns * target;
+ cnv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
+ cnv_state.enabled = false;
+
+ return pwm_apply_might_sleep(st->conv_trigger, &cnv_state);
+}
+
+static int ad4691_pwm_get(struct spi_device *spi, struct ad4691_state *st)
+{
+ struct clk *ref_clk;
+ int ret;
+
+ ref_clk = devm_clk_get_enabled(&spi->dev, NULL);
+ if (IS_ERR(ref_clk))
+ return dev_err_probe(&spi->dev, PTR_ERR(ref_clk),
+ "Failed to get ref clock\n");
+
+ st->ref_clk_rate = clk_get_rate(ref_clk);
+
+ st->conv_trigger = devm_pwm_get(&spi->dev, "cnv");
+ if (IS_ERR(st->conv_trigger))
+ return dev_err_probe(&spi->dev, PTR_ERR(st->conv_trigger),
+ "Failed to get cnv pwm\n");
+
+ ret = devm_add_action_or_reset(&spi->dev, ad4691_disable_pwm,
+ st->conv_trigger);
+ if (ret)
+ return dev_err_probe(&spi->dev, ret,
+ "Failed to register PWM disable action\n");
+
+ return __ad4691_set_sampling_freq(st, st->chip->max_rate);
+}
+
+static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&st->lock);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ if (!freq || freq > st->chip->max_rate)
+ return -EINVAL;
+
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL
+ (NSEC_PER_SEC, freq));
+ return 0;
+ }
+
+ if (!st->conv_trigger)
+ return -ENODEV;
+
+ if (!freq || freq > st->chip->max_rate)
+ return -EINVAL;
+
+ return __ad4691_set_sampling_freq(st, freq);
+}
+
+static int ad4691_single_shot_read(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned long conv_us = DIV_ROUND_UP(2UL * USEC_PER_SEC,
+ st->chip->max_rate);
+ u16 mask = ~BIT(chan->channel);
+ unsigned int reg_val;
+ int ret;
+
+ /*
+ * Always use AUTONOMOUS mode for single-shot reads, regardless
+ * of the buffer mode (CNV_CLOCK or MANUAL). The chip is kept
+ * in AUTONOMOUS mode during idle; enter_conversion_mode() and
+ * exit_conversion_mode() handle the switch for buffer operation.
+ */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ BIT(chan->channel));
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK1_REG, mask & 0xFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK2_REG, (mask >> 8) & 0xFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 1);
+ if (ret)
+ return ret;
+
+ /*
+ * Wait for conversion to complete using a timed delay.
+ * A single read needs 2 internal oscillator periods.
+ * OSC_FREQ_REG is never modified by the driver, so the
+ * oscillator runs at reset-default speed. Use chip->max_rate
+ * as a conservative proxy: it is always <= the OSC frequency,
+ * so the computed delay is >= the actual conversion time.
+ */
+ fsleep(conv_us);
+
+ ret = regmap_write(st->regmap, AD4691_OSC_EN_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val);
+ if (ret)
+ return ret;
+
+ *val = reg_val;
+ regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+
+ return IIO_VAL_INT;
+}
+
+static int ad4691_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long info)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ switch (info) {
+ case IIO_CHAN_INFO_RAW: {
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ return ad4691_single_shot_read(indio_dev, chan, val);
+ }
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = ad4691_get_sampling_freq(st);
+ return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE:
+ *val = st->vref / 1000;
+ *val2 = chan->scan_type.realbits;
+ return IIO_VAL_FRACTIONAL_LOG2;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ad4691_set_sampling_freq(indio_dev, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->lock);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static const struct iio_info ad4691_info = {
+ .read_raw = &ad4691_read_raw,
+ .write_raw = &ad4691_write_raw,
+ .debugfs_reg_access = &ad4691_reg_access,
+};
+
+static int ad4691_gpio_setup(struct ad4691_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct gpio_desc *reset;
+
+ reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset))
+ return dev_err_probe(dev, PTR_ERR(reset),
+ "Failed to get reset GPIO\n");
+
+ /* Reset delay required. See datasheet Table 5. */
+ fsleep(300);
+ gpiod_set_value(reset, 0);
+
+ return 0;
+}
+
+static int ad4691_config(struct ad4691_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned int reg_val;
+ int ret;
+
+ /*
+ * Determine buffer conversion mode from DT: if a PWM is provided it
+ * drives the CNV pin (CNV_CLOCK_MODE); otherwise CNV is tied to CS
+ * and each SPI transfer triggers a conversion (MANUAL_MODE).
+ * Both modes idle in AUTONOMOUS mode so that read_raw can use the
+ * internal oscillator without disturbing the hardware configuration.
+ */
+ if (device_property_present(dev, "pwms")) {
+ st->adc_mode = AD4691_CNV_CLOCK_MODE;
+ ret = ad4691_pwm_get(st->spi, st);
+ if (ret)
+ return ret;
+ } else {
+ st->adc_mode = AD4691_MANUAL_MODE;
+ }
+
+ /* Perform a state reset on the channels at start-up. */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write state reset\n");
+
+ /* Clear STATUS register by reading from the STATUS register. */
+ ret = regmap_read(st->regmap, AD4691_STATUS_REG, ®_val);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read status register\n");
+
+ switch (st->vref) {
+ case AD4691_VREF_MIN ... 2750000:
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL,
+ FIELD_PREP(AD4691_REF_CTRL_MASK,
+ AD4691_VREF_2P5));
+ break;
+ case 2750001 ... 3250000:
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL,
+ FIELD_PREP(AD4691_REF_CTRL_MASK,
+ AD4691_VREF_3P0));
+ break;
+ case 3250001 ... 3750000:
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL,
+ FIELD_PREP(AD4691_REF_CTRL_MASK,
+ AD4691_VREF_3P3));
+ break;
+ case 3750001 ... 4500000:
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL,
+ FIELD_PREP(AD4691_REF_CTRL_MASK,
+ AD4691_VREF_4P096));
+ break;
+ case 4500001 ... AD4691_VREF_MAX:
+ ret = regmap_write(st->regmap, AD4691_REF_CTRL,
+ FIELD_PREP(AD4691_REF_CTRL_MASK,
+ AD4691_VREF_5P0));
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL,
+ "Unsupported vref voltage: %d uV\n",
+ st->vref);
+ }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n");
+
+ /* Both CNV_CLOCK and MANUAL devices start in AUTONOMOUS mode. */
+ ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n");
+
+ return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
+}
+
+static int ad4691_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ad4691_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ st->spi = spi;
+
+ st->regmap = devm_regmap_init(dev, NULL, st, &ad4691_regmap_config);
+ if (IS_ERR(st->regmap))
+ return dev_err_probe(dev, PTR_ERR(st->regmap),
+ "Failed to initialize regmap\n");
+
+ st->chip = spi_get_device_match_data(spi);
+ if (!st->chip)
+ return dev_err_probe(dev, -ENODEV, "Could not find chip info\n");
+
+ ret = ad4691_regulator_get(st);
+ if (ret)
+ return ret;
+
+ ret = ad4691_gpio_setup(st);
+ if (ret)
+ return ret;
+
+ ret = ad4691_config(st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->chip->name;
+ indio_dev->info = &ad4691_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ indio_dev->channels = (st->adc_mode == AD4691_MANUAL_MODE)
+ ? st->chip->manual_channels : st->chip->channels;
+ indio_dev->num_channels = st->chip->num_channels;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ad4691_of_match[] = {
+ { .compatible = "adi,ad4691", .data = &ad4691_ad4691 },
+ { .compatible = "adi,ad4692", .data = &ad4691_ad4692 },
+ { .compatible = "adi,ad4693", .data = &ad4691_ad4693 },
+ { .compatible = "adi,ad4694", .data = &ad4691_ad4694 },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ad4691_of_match);
+
+static const struct spi_device_id ad4691_id[] = {
+ { "ad4691", (kernel_ulong_t)&ad4691_ad4691 },
+ { "ad4692", (kernel_ulong_t)&ad4691_ad4692 },
+ { "ad4693", (kernel_ulong_t)&ad4691_ad4693 },
+ { "ad4694", (kernel_ulong_t)&ad4691_ad4694 },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ad4691_id);
+
+static struct spi_driver ad4691_driver = {
+ .driver = {
+ .name = "ad4691",
+ .of_match_table = ad4691_of_match,
+ },
+ .probe = ad4691_probe,
+ .id_table = ad4691_id,
+};
+module_spi_driver(ad4691_driver);
+
+MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
+MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-10 14:32 [PATCH v2 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
@ 2026-03-10 14:32 ` Radu Sabau via B4 Relay
2026-03-11 21:08 ` Andy Shevchenko
2026-03-10 14:32 ` [PATCH v2 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
3 siblings, 1 reply; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-10 14:32 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
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add buffered capture support using the IIO triggered buffer framework.
Both operating modes share a single IIO trigger and trigger handler.
The handler builds a complete scan — one u32 slot per channel at its
scan_index position, followed by a timestamp — and pushes it to the
IIO buffer in a single iio_push_to_buffers_with_ts() call.
For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
IRQ handler stops conversions and fires the IIO trigger; the trigger
handler reads accumulated results from the AVG_IN registers via regmap
and restarts conversions for the next cycle.
For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
so conversions are triggered by CS assertion rather than by a dedicated
pin. The standard iio-trig-hrtimer module is not used because the timer
period must be derived from the SPI clock rate and the number of active
channels: the pipelined protocol requires N+1 SPI transfers per scan
(the first result is garbage and is discarded), so the minimum period
depends on both the SPI speed and the live channel count at buffer
enable time. A driver-private hrtimer whose period is recomputed by
buffer_postenable is simpler and avoids requiring the user to configure
an external trigger with the correct hardware-derived period.
Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
channel slots in the scan buffer are uniformly sized regardless of the
SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ad4691.c | 405 ++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 382 insertions(+), 25 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 528c37a9a383..8b3caf0334ba 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
+#include <linux/hrtimer.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/math.h>
@@ -25,8 +26,13 @@
#include <linux/units.h>
#include <linux/unaligned.h>
+#include <linux/iio/buffer.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
+
#include <dt-bindings/iio/adc/adi,ad4691.h>
#define AD4691_VREF_MIN 2400000
@@ -71,7 +77,7 @@
#define AD4691_ACC_MASK1_REG 0x184
#define AD4691_ACC_MASK2_REG 0x185
#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
-#define AD4691_ACC_COUNT_VAL 0x3F
+#define AD4691_ACC_COUNT_VAL 0x01
#define AD4691_GPIO_MODE1_REG 0x196
#define AD4691_GPIO_MODE2_REG 0x197
#define AD4691_GPIO_READ 0x1A0
@@ -161,33 +167,33 @@ static const struct iio_chan_spec ad4693_channels[] = {
};
static const struct iio_chan_spec ad4691_manual_channels[] = {
- AD4691_CHANNEL(0, 0, 16, 24, 8),
- AD4691_CHANNEL(1, 1, 16, 24, 8),
- AD4691_CHANNEL(2, 2, 16, 24, 8),
- AD4691_CHANNEL(3, 3, 16, 24, 8),
- AD4691_CHANNEL(4, 4, 16, 24, 8),
- AD4691_CHANNEL(5, 5, 16, 24, 8),
- AD4691_CHANNEL(6, 6, 16, 24, 8),
- AD4691_CHANNEL(7, 7, 16, 24, 8),
- AD4691_CHANNEL(8, 8, 16, 24, 8),
- AD4691_CHANNEL(9, 9, 16, 24, 8),
- AD4691_CHANNEL(10, 10, 16, 24, 8),
- AD4691_CHANNEL(11, 11, 16, 24, 8),
- AD4691_CHANNEL(12, 12, 16, 24, 8),
- AD4691_CHANNEL(13, 13, 16, 24, 8),
- AD4691_CHANNEL(14, 14, 16, 24, 8),
- AD4691_CHANNEL(15, 15, 16, 24, 8)
+ AD4691_CHANNEL(0, 0, 16, 32, 8),
+ AD4691_CHANNEL(1, 1, 16, 32, 8),
+ AD4691_CHANNEL(2, 2, 16, 32, 8),
+ AD4691_CHANNEL(3, 3, 16, 32, 8),
+ AD4691_CHANNEL(4, 4, 16, 32, 8),
+ AD4691_CHANNEL(5, 5, 16, 32, 8),
+ AD4691_CHANNEL(6, 6, 16, 32, 8),
+ AD4691_CHANNEL(7, 7, 16, 32, 8),
+ AD4691_CHANNEL(8, 8, 16, 32, 8),
+ AD4691_CHANNEL(9, 9, 16, 32, 8),
+ AD4691_CHANNEL(10, 10, 16, 32, 8),
+ AD4691_CHANNEL(11, 11, 16, 32, 8),
+ AD4691_CHANNEL(12, 12, 16, 32, 8),
+ AD4691_CHANNEL(13, 13, 16, 32, 8),
+ AD4691_CHANNEL(14, 14, 16, 32, 8),
+ AD4691_CHANNEL(15, 15, 16, 32, 8)
};
static const struct iio_chan_spec ad4693_manual_channels[] = {
- AD4691_CHANNEL(0, 0, 16, 24, 8),
- AD4691_CHANNEL(1, 1, 16, 24, 8),
- AD4691_CHANNEL(2, 2, 16, 24, 8),
- AD4691_CHANNEL(3, 3, 16, 24, 8),
- AD4691_CHANNEL(4, 4, 16, 24, 8),
- AD4691_CHANNEL(5, 5, 16, 24, 8),
- AD4691_CHANNEL(6, 6, 16, 24, 8),
- AD4691_CHANNEL(7, 7, 16, 24, 8)
+ AD4691_CHANNEL(0, 0, 16, 32, 8),
+ AD4691_CHANNEL(1, 1, 16, 32, 8),
+ AD4691_CHANNEL(2, 2, 16, 32, 8),
+ AD4691_CHANNEL(3, 3, 16, 32, 8),
+ AD4691_CHANNEL(4, 4, 16, 32, 8),
+ AD4691_CHANNEL(5, 5, 16, 32, 8),
+ AD4691_CHANNEL(6, 6, 16, 32, 8),
+ AD4691_CHANNEL(7, 7, 16, 32, 8)
};
static const struct ad4691_chip_info ad4691_ad4691 = {
@@ -230,6 +236,8 @@ struct ad4691_state {
unsigned long ref_clk_rate;
struct pwm_device *conv_trigger;
+ struct iio_trigger *trig;
+
enum ad4691_adc_mode adc_mode;
int vref;
@@ -239,6 +247,22 @@ struct ad4691_state {
* atomicity of consecutive SPI operations.
*/
struct mutex lock;
+
+ /* hrtimer for MANUAL_MODE triggered buffer (non-offload) */
+ struct hrtimer sampling_timer;
+ ktime_t sampling_period;
+
+ /*
+ * DMA (thus cache coherency maintenance) may require the
+ * transfer buffers to live in their own cache lines.
+ */
+ unsigned char rx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)] __aligned(IIO_DMA_MINALIGN);
+ unsigned char tx_data[ALIGN(3, sizeof(s64)) + sizeof(s64)];
+ /* Scan buffer: one slot per channel (u32) plus timestamp */
+ struct {
+ u32 vals[16];
+ s64 ts __aligned(8);
+ } scan __aligned(IIO_DMA_MINALIGN);
};
static void ad4691_disable_pwm(void *data)
@@ -394,6 +418,27 @@ static const struct regmap_config ad4691_regmap_config = {
.cache_type = REGCACHE_RBTREE,
};
+static int ad4691_transfer(struct ad4691_state *st, int command,
+ unsigned int *val)
+{
+ struct spi_transfer xfer = {
+ .tx_buf = st->tx_data,
+ .rx_buf = st->rx_data,
+ .len = 3,
+ };
+ int ret;
+
+ memcpy(st->tx_data, &command, 3);
+
+ ret = spi_sync_transfer(st->spi, &xfer, 1);
+ if (ret)
+ return ret;
+
+ *val = get_unaligned_be24(st->rx_data);
+
+ return 0;
+}
+
static int ad4691_get_sampling_freq(struct ad4691_state *st)
{
if (st->adc_mode == AD4691_MANUAL_MODE) {
@@ -478,6 +523,18 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
return __ad4691_set_sampling_freq(st, freq);
}
+static int ad4691_sampling_enable(struct ad4691_state *st, bool enable)
+{
+ struct pwm_state conv_state = { };
+
+ conv_state.period = st->cnv_period;
+ conv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
+ conv_state.polarity = PWM_POLARITY_NORMAL;
+ conv_state.enabled = enable;
+
+ return pwm_apply_might_sleep(st->conv_trigger, &conv_state);
+}
+
static int ad4691_single_shot_read(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val)
{
@@ -592,6 +649,240 @@ static int ad4691_reg_access(struct iio_dev *indio_dev, unsigned int reg,
return regmap_write(st->regmap, reg, writeval);
}
+/*
+ * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion mode.
+ *
+ * Configures the ADC hardware registers for the mode selected at probe
+ * (CNV_CLOCK or MANUAL). Called from buffer postenable before starting
+ * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw).
+ */
+static int ad4691_enter_conversion_mode(struct ad4691_state *st)
+{
+ int ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE)
+ return regmap_write(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_DEVICE_MANUAL);
+
+ ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_CNV_CLOCK_MODE);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG,
+ AD4691_DATA_READY);
+}
+
+/*
+ * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode.
+ *
+ * Called from buffer postdisable/predisable to restore the chip to the
+ * idle state used by read_raw. Clears the sequencer and resets state.
+ */
+static int ad4691_exit_conversion_mode(struct ad4691_state *st)
+{
+ int ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ ret = regmap_write(st->regmap, AD4691_DEVICE_SETUP,
+ AD4691_DEVICE_REGISTER);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_VAL);
+ if (ret)
+ return ret;
+
+ /* Restore GP0 to ADC_BUSY for AUTONOMOUS idle (enter set it to DATA_READY) */
+ ret = regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ AD4691_SEQ_ALL_CHANNELS_OFF);
+ if (ret)
+ return ret;
+
+ return regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+}
+
+static int ad4691_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ int n_active = hweight_long(*indio_dev->active_scan_mask);
+ unsigned int bit;
+ int ret;
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ u64 min_period_ns;
+
+ /* N+1 transfers needed for N channels, with 50% overhead */
+ min_period_ns = div64_u64((u64)(n_active + 1) * AD4691_BITS_PER_XFER *
+ NSEC_PER_SEC * 3,
+ st->spi->max_speed_hz * 2);
+
+ if (ktime_to_ns(st->sampling_period) < min_period_ns) {
+ dev_err(&st->spi->dev,
+ "Sampling period %lld ns too short for %d channels. Min: %llu ns\n",
+ ktime_to_ns(st->sampling_period), n_active,
+ min_period_ns);
+ return -EINVAL;
+ }
+
+ hrtimer_start(&st->sampling_timer, st->sampling_period,
+ HRTIMER_MODE_REL);
+ return 0;
+ }
+
+ /* CNV_CLOCK_MODE: configure sequencer and start PWM */
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK1_REG,
+ ~(*indio_dev->active_scan_mask) & 0xFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK2_REG,
+ ~(*indio_dev->active_scan_mask >> 8) & 0xFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ *indio_dev->active_scan_mask);
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
+ AD4691_ACC_COUNT_VAL);
+ if (ret)
+ return ret;
+ }
+
+ return ad4691_sampling_enable(st, true);
+}
+
+static int ad4691_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE)
+ hrtimer_cancel_wait_running(&st->sampling_timer);
+ else
+ ad4691_sampling_enable(st, false);
+
+ return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
+ .postenable = &ad4691_buffer_postenable,
+ .postdisable = &ad4691_buffer_postdisable,
+};
+
+static irqreturn_t ad4691_irq(int irq, void *private)
+{
+ struct iio_dev *indio_dev = private;
+ struct ad4691_state *st = iio_priv(indio_dev);
+
+ /*
+ * DATA_READY has asserted: stop conversions before reading so the
+ * accumulator does not continue sampling while the trigger handler
+ * processes the data. Then fire the IIO trigger to push the sample
+ * to the buffer.
+ */
+ ad4691_sampling_enable(st, false);
+ iio_trigger_poll(st->trig);
+
+ return IRQ_HANDLED;
+}
+
+static enum hrtimer_restart ad4691_sampling_timer_handler(struct hrtimer *timer)
+{
+ struct ad4691_state *st = container_of(timer, struct ad4691_state,
+ sampling_timer);
+
+ iio_trigger_poll(st->trig);
+ hrtimer_forward_now(timer, st->sampling_period);
+
+ return HRTIMER_RESTART;
+}
+
+static const struct iio_trigger_ops ad4691_trigger_ops = {
+ .validate_device = iio_trigger_validate_own_device,
+};
+
+static irqreturn_t ad4691_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ad4691_state *st = iio_priv(indio_dev);
+ unsigned int val;
+ int ret, i;
+
+ mutex_lock(&st->lock);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ unsigned int prev_val;
+ int prev_chan = -1;
+
+ /*
+ * MANUAL_MODE with CNV tied to CS: each transfer triggers a
+ * conversion AND returns the previous conversion's result.
+ * First transfer returns garbage, so we do N+1 transfers for
+ * N channels. Collect all results into scan.vals[], then push
+ * the complete scan once.
+ */
+ iio_for_each_active_channel(indio_dev, i) {
+ ret = ad4691_transfer(st, AD4691_ADC_CHAN(i), &val);
+ if (ret)
+ goto done;
+
+ if (prev_chan >= 0)
+ st->scan.vals[prev_chan] = prev_val;
+ prev_val = val;
+ prev_chan = i;
+ }
+
+ /* Final NOOP transfer to retrieve last channel's result */
+ ret = ad4691_transfer(st, AD4691_NOOP, &val);
+ if (ret)
+ goto done;
+
+ st->scan.vals[prev_chan] = val;
+ } else {
+ for (i = 0; i < st->chip->num_channels; i++) {
+ if (BIT(i) & *indio_dev->active_scan_mask) {
+ ret = regmap_read(st->regmap, AD4691_AVG_IN(i), &val);
+ if (ret)
+ goto done;
+
+ st->scan.vals[i] = val;
+ }
+ }
+
+ regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
+
+ /* Restart conversions for the next trigger cycle. */
+ ad4691_sampling_enable(st, true);
+ }
+
+ iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+ pf->timestamp);
+
+done:
+ iio_trigger_notify_done(indio_dev->trig);
+ mutex_unlock(&st->lock);
+ return IRQ_HANDLED;
+}
+
static const struct iio_info ad4691_info = {
.read_raw = &ad4691_read_raw,
.write_raw = &ad4691_write_raw,
@@ -690,6 +981,66 @@ static int ad4691_config(struct ad4691_state *st)
return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
}
+static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
+ struct ad4691_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ int irq, ret;
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
+ indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return dev_err_probe(dev, -ENOMEM,
+ "Failed to allocate IIO trigger\n");
+
+ st->trig->ops = &ad4691_trigger_ops;
+ iio_trigger_set_drvdata(st->trig, st);
+
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return dev_err_probe(dev, ret, "IIO trigger register failed\n");
+
+ indio_dev->trig = iio_trigger_get(st->trig);
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /*
+ * No DATA_READY signal in MANUAL_MODE; CNV is tied to CS so
+ * conversions start with each SPI transfer. Use an hrtimer to
+ * schedule periodic reads.
+ */
+ hrtimer_setup(&st->sampling_timer, ad4691_sampling_timer_handler,
+ CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL(
+ NSEC_PER_SEC,
+ AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
+ st->spi->max_speed_hz)));
+ } else {
+ /*
+ * DATA_READY asserts at end-of-conversion. The IRQ handler
+ * stops conversions and fires the IIO trigger so the trigger
+ * handler can read and push the sample to the buffer.
+ */
+ irq = fwnode_irq_get(dev_fwnode(dev), 0);
+ if (irq <= 0)
+ return dev_err_probe(dev, irq ? irq : -ENOENT,
+ "failed to get DATA_READY interrupt\n");
+
+ ret = devm_request_threaded_irq(dev, irq, NULL,
+ &ad4691_irq,
+ IRQF_ONESHOT,
+ indio_dev->name, indio_dev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "request irq %d failed\n", irq);
+ }
+
+ return devm_iio_triggered_buffer_setup(dev, indio_dev,
+ &iio_pollfunc_store_time,
+ &ad4691_trigger_handler,
+ &ad4691_buffer_setup_ops);
+}
+
static int ad4691_probe(struct spi_device *spi)
{
struct device *dev = &spi->dev;
@@ -737,6 +1088,10 @@ static int ad4691_probe(struct spi_device *spi)
? st->chip->manual_channels : st->chip->channels;
indio_dev->num_channels = st->chip->num_channels;
+ ret = ad4691_setup_triggered_buffer(indio_dev, st);
+ if (ret)
+ return ret;
+
return devm_iio_device_register(dev, indio_dev);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH v2 4/4] iio: adc: ad4691: add SPI offload support
2026-03-10 14:32 [PATCH v2 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
` (2 preceding siblings ...)
2026-03-10 14:32 ` [PATCH v2 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
@ 2026-03-10 14:32 ` Radu Sabau via B4 Relay
2026-03-11 20:45 ` Andy Shevchenko
3 siblings, 1 reply; 9+ messages in thread
From: Radu Sabau via B4 Relay @ 2026-03-10 14:32 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
Cc: linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio,
Radu Sabau
From: Radu Sabau <radu.sabau@analog.com>
Add SPI offload support to enable DMA-based, CPU-independent data
acquisition using the SPI Engine offload framework.
When an SPI offload is available (devm_spi_offload_get() succeeds),
the driver registers a DMA engine IIO buffer and uses dedicated buffer
setup operations. If no offload is available the existing software
triggered buffer path is used unchanged.
Both CNV Clock Mode and Manual Mode support offload, but use different
trigger mechanisms:
CNV Clock Mode: the SPI Engine is triggered by the ADC's DATA_READY
signal on GP0. For this mode the driver acts as both an SPI offload
consumer (DMA RX stream, message optimization) and a trigger source
provider: it registers the GP0/DATA_READY output via
devm_spi_offload_trigger_register() so the offload framework can
match the '#trigger-source-cells' phandle from the device tree and
automatically fire the SPI Engine DMA transfer at end-of-conversion.
The pre-built SPI message reads all active channels from the AVG_IN
accumulator registers (2-byte address + 2-byte data per channel,
one 4-byte transfer each) followed by a state reset word to re-arm
the accumulator for the next cycle.
Manual Mode: the SPI Engine is triggered by a periodic trigger at
the configured sampling frequency. The pre-built SPI message uses
the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued
for N active channels (the first result is discarded as garbage from
the pipeline flush) and the remaining N results are captured by DMA.
All offload transfers use 32-bit frames (bits_per_word=32, len=4) for
DMA word alignment. In Manual Mode the 4-byte DMA word layout is
[dummy(8), data_hi(8), data_lo(8), extra(8)]; the channel scan type
storagebits=32, shift=8, realbits=16 correctly extracts the 16-bit
ADC result from the middle two bytes.
Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
Signed-off-by: Radu Sabau <radu.sabau@analog.com>
---
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ad4691.c | 398 +++++++++++++++++++++++++++++++++++++++++++++--
2 files changed, 389 insertions(+), 10 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index d498f16c0816..93f090e9a562 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -144,6 +144,7 @@ config AD4691
depends on SPI
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
+ select IIO_BUFFER_DMAENGINE
select REGMAP
help
Say yes here to build support for Analog Devices AD4691 Family MuxSAR
diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
index 8b3caf0334ba..2ed384cfc1b9 100644
--- a/drivers/iio/adc/ad4691.c
+++ b/drivers/iio/adc/ad4691.c
@@ -9,6 +9,7 @@
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
+#include <linux/dmaengine.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/hrtimer.h>
@@ -22,11 +23,15 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/provider.h>
#include <linux/util_macros.h>
#include <linux/units.h>
#include <linux/unaligned.h>
#include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dma.h>
+#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/iio.h>
#include <linux/iio/trigger.h>
@@ -47,6 +52,7 @@
*/
#define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
#define AD4691_BITS_PER_XFER 24
+#define AD4691_OFFLOAD_BITS_PER_WORD 32
#define AD4691_CNV_DUTY_CYCLE_NS 380
#define AD4691_MAX_CONV_PERIOD_US 800
@@ -252,6 +258,16 @@ struct ad4691_state {
struct hrtimer sampling_timer;
ktime_t sampling_period;
+ struct spi_offload *offload;
+ struct spi_offload_trigger *offload_trigger;
+ struct spi_offload_trigger *offload_trigger_periodic;
+ u64 offload_trigger_hz;
+ struct spi_message offload_msg;
+ /* Max 16 channel transfers + 1 state reset or NOOP */
+ struct spi_transfer offload_xfer[17];
+ /* TX commands for manual and accumulator modes */
+ u32 offload_tx_cmd[17];
+ u32 offload_tx_reset;
/*
* DMA (thus cache coherency maintenance) may require the
* transfer buffers to live in their own cache lines.
@@ -265,6 +281,65 @@ struct ad4691_state {
} scan __aligned(IIO_DMA_MINALIGN);
};
+static const struct spi_offload_config ad4691_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
+static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ if (type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+ return false;
+
+ /*
+ * Requires 2 args:
+ * args[0] is the trigger event (BUSY or DATA_READY).
+ * args[1] is the GPIO pin number (only GP0 supported).
+ */
+ if (nargs != 2)
+ return false;
+
+ if (args[0] != AD4691_TRIGGER_EVENT_BUSY &&
+ args[0] != AD4691_TRIGGER_EVENT_DATA_READY)
+ return false;
+
+ if (args[1] != AD4691_TRIGGER_PIN_GP0)
+ return false;
+
+ return true;
+}
+
+static int ad4691_offload_trigger_request(struct spi_offload_trigger *trigger,
+ enum spi_offload_trigger_type type,
+ u64 *args, u32 nargs)
+{
+ /*
+ * GP0 is configured as DATA_READY or BUSY in ad4691_config()
+ * based on the ADC mode. No additional configuration needed here.
+ */
+ if (nargs != 2)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ad4691_offload_trigger_validate(struct spi_offload_trigger *trigger,
+ struct spi_offload_trigger_config *config)
+{
+ if (config->type != SPI_OFFLOAD_TRIGGER_DATA_READY)
+ return -EINVAL;
+
+ return 0;
+}
+
+static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops = {
+ .match = ad4691_offload_trigger_match,
+ .request = ad4691_offload_trigger_request,
+ .validate = ad4691_offload_trigger_validate,
+};
+
static void ad4691_disable_pwm(void *data)
{
struct pwm_device *pwm = data;
@@ -442,6 +517,9 @@ static int ad4691_transfer(struct ad4691_state *st, int command,
static int ad4691_get_sampling_freq(struct ad4691_state *st)
{
if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /* Offload uses periodic trigger, non-offload uses hrtimer */
+ if (st->offload)
+ return st->offload_trigger_hz;
return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
ktime_to_ns(st->sampling_period));
}
@@ -497,6 +575,7 @@ static int ad4691_pwm_get(struct spi_device *spi, struct ad4691_state *st)
static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq)
{
struct ad4691_state *st = iio_priv(indio_dev);
+ int ret;
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
@@ -506,12 +585,31 @@ static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, unsigned int freq
guard(mutex)(&st->lock);
if (st->adc_mode == AD4691_MANUAL_MODE) {
- if (!freq || freq > st->chip->max_rate)
- return -EINVAL;
-
- st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL
- (NSEC_PER_SEC, freq));
- return 0;
+ /* For offload mode, validate and store frequency for periodic trigger */
+ if (st->offload) {
+ struct spi_offload_trigger_config config = {
+ .type = SPI_OFFLOAD_TRIGGER_PERIODIC,
+ .periodic = {
+ .frequency_hz = freq,
+ },
+ };
+
+ ret = spi_offload_trigger_validate(st->offload_trigger_periodic,
+ &config);
+ if (ret)
+ return ret;
+
+ st->offload_trigger_hz = config.periodic.frequency_hz;
+ return 0;
+ } else {
+ /* Non-offload: update hrtimer sampling period */
+ if (!freq || freq > st->chip->max_rate)
+ return -EINVAL;
+
+ st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL
+ (NSEC_PER_SEC, freq));
+ return 0;
+ }
}
if (!st->conv_trigger)
@@ -787,6 +885,224 @@ static const struct iio_buffer_setup_ops ad4691_buffer_setup_ops = {
.postdisable = &ad4691_buffer_postdisable,
};
+static int ad4691_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct spi_offload_trigger_config config = { };
+ struct spi_offload_trigger *trigger;
+ struct spi_transfer *xfer = st->offload_xfer;
+ int ret, num_xfers = 0;
+ int active_chans[16];
+ unsigned int bit;
+ int n_active = 0;
+ int i;
+
+ memset(xfer, 0, sizeof(st->offload_xfer));
+
+ /* Collect active channels in scan order */
+ iio_for_each_active_channel(indio_dev, bit)
+ active_chans[n_active++] = bit;
+
+ ret = ad4691_enter_conversion_mode(st);
+ if (ret)
+ return ret;
+
+ /*
+ * MANUAL_MODE uses a periodic (PWM) trigger and reads directly from
+ * the ADC. CNV_CLOCK_MODE uses the DATA_READY trigger and reads from
+ * accumulators.
+ */
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
+ config.periodic.frequency_hz = st->offload_trigger_hz;
+ trigger = st->offload_trigger_periodic;
+ if (!trigger)
+ return -EINVAL;
+ } else {
+ ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
+ AD4691_STATE_RESET_ALL);
+ if (ret)
+ return ret;
+
+ /* Configure accumulator masks - 0 = enabled, 1 = masked */
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK1_REG,
+ ~(*indio_dev->active_scan_mask) & 0xFF);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(st->regmap, AD4691_ACC_MASK2_REG,
+ ~(*indio_dev->active_scan_mask >> 8) & 0xFF);
+ if (ret)
+ return ret;
+
+ /* Configure sequencer with active channels */
+ ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
+ *indio_dev->active_scan_mask);
+ if (ret)
+ return ret;
+
+ iio_for_each_active_channel(indio_dev, bit) {
+ ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
+ AD4691_ACC_COUNT_VAL);
+ if (ret)
+ return ret;
+ }
+
+ config.type = SPI_OFFLOAD_TRIGGER_DATA_READY;
+ trigger = st->offload_trigger;
+ }
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ /*
+ * Manual mode with CNV tied to CS: Each CS toggle triggers a
+ * conversion AND reads the previous conversion result (pipeline).
+ */
+ for (i = 0; i < n_active; i++) {
+ st->offload_tx_cmd[num_xfers] = AD4691_ADC_CHAN(active_chans[i]) << 24;
+ xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+ xfer[num_xfers].cs_change = 1;
+ xfer[num_xfers].cs_change_delay.value = 1000;
+ xfer[num_xfers].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
+ /* First transfer RX is garbage - don't capture it */
+ if (num_xfers)
+ xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ num_xfers++;
+ }
+
+ /* Final NOOP to flush pipeline and get last channel's data */
+ st->offload_tx_cmd[num_xfers] = AD4691_NOOP << 24;
+ xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+ xfer[num_xfers].cs_change = 0;
+ xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ num_xfers++;
+ } else {
+ /*
+ * CNV_CLOCK_MODE: single transfer per channel (2-byte cmd +
+ * 2-byte data = 4 bytes, one 32-bit SPI Engine DMA word).
+ * AVG_IN registers are used; RX layout: [cmd_hi, cmd_lo, d_hi, d_lo]
+ */
+ for (i = 0; i < n_active; i++) {
+ unsigned int reg;
+ int ch = active_chans[i];
+
+ reg = AD4691_AVG_IN(ch);
+ st->offload_tx_cmd[ch] =
+ ((reg >> 8) | 0x80) << 24 |
+ (reg & 0xFF) << 16;
+ xfer[num_xfers].tx_buf = &st->offload_tx_cmd[ch];
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+ xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+ xfer[num_xfers].cs_change = 1;
+ num_xfers++;
+ }
+
+ /*
+ * State reset: clear accumulator so DATA_READY can fire again.
+ * With bits_per_word=32, SPI engine transmits MSB first.
+ */
+ st->offload_tx_reset = ((AD4691_STATE_RESET_REG >> 8) << 24) |
+ ((AD4691_STATE_RESET_REG & 0xFF) << 16) |
+ (0x01 << 8);
+
+ xfer[num_xfers].tx_buf = &st->offload_tx_reset;
+ xfer[num_xfers].len = 4;
+ xfer[num_xfers].bits_per_word = 32;
+ xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
+ xfer[num_xfers].cs_change = 0;
+ num_xfers++;
+ }
+
+ if (num_xfers == 0)
+ return -EINVAL;
+
+ /*
+ * For MANUAL_MODE, validate that the trigger frequency is low enough
+ * for all SPI transfers to complete. Each transfer is 32 bits.
+ * Add 50% margin for CS setup/hold and other overhead.
+ */
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ u64 min_period_ns;
+ u64 trigger_period_ns;
+
+ /* Time for all transfers in nanoseconds, with 50% overhead margin */
+ min_period_ns = div64_u64((u64)num_xfers * AD4691_OFFLOAD_BITS_PER_WORD *
+ NSEC_PER_SEC * 3,
+ st->spi->max_speed_hz * 2);
+
+ trigger_period_ns = div64_u64(NSEC_PER_SEC, st->offload_trigger_hz);
+
+ if (trigger_period_ns < min_period_ns)
+ return -EINVAL;
+ }
+
+ spi_message_init_with_transfers(&st->offload_msg, xfer, num_xfers);
+ st->offload_msg.offload = st->offload;
+
+ ret = spi_optimize_message(st->spi, &st->offload_msg);
+ if (ret)
+ return ret;
+
+ /*
+ * For CNV_CLOCK_MODE, start conversions before enabling the trigger.
+ * If the trigger is enabled first, the SPI engine blocks waiting for
+ * DATA_READY, and any subsequent SPI write times out.
+ *
+ * MANUAL_MODE: CNV is tied to CS; conversion starts with each transfer.
+ */
+ if (st->adc_mode == AD4691_CNV_CLOCK_MODE) {
+ ret = ad4691_sampling_enable(st, true);
+ if (ret)
+ goto err_unoptimize_message;
+ }
+
+ ret = spi_offload_trigger_enable(st->offload, trigger, &config);
+ if (ret)
+ goto err_sampling_disable;
+
+ return 0;
+
+err_sampling_disable:
+ if (st->adc_mode == AD4691_CNV_CLOCK_MODE)
+ ad4691_sampling_enable(st, false);
+err_unoptimize_message:
+ spi_unoptimize_message(&st->offload_msg);
+ return ret;
+}
+
+static int ad4691_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ad4691_state *st = iio_priv(indio_dev);
+ struct spi_offload_trigger *trigger;
+ int ret;
+
+ trigger = (st->adc_mode == AD4691_MANUAL_MODE) ?
+ st->offload_trigger_periodic : st->offload_trigger;
+
+ spi_offload_trigger_disable(st->offload, trigger);
+ spi_unoptimize_message(&st->offload_msg);
+
+ if (st->adc_mode == AD4691_CNV_CLOCK_MODE) {
+ ret = ad4691_sampling_enable(st, false);
+ if (ret)
+ return ret;
+ }
+
+ return ad4691_exit_conversion_mode(st);
+}
+
+static const struct iio_buffer_setup_ops ad4691_offload_buffer_setup_ops = {
+ .postenable = &ad4691_offload_buffer_postenable,
+ .predisable = &ad4691_offload_buffer_predisable,
+};
+
static irqreturn_t ad4691_irq(int irq, void *private)
{
struct iio_dev *indio_dev = private;
@@ -981,6 +1297,54 @@ static int ad4691_config(struct ad4691_state *st)
return regmap_write(st->regmap, AD4691_GPIO_MODE1_REG, AD4691_ADC_BUSY);
}
+static int ad4691_setup_offload(struct iio_dev *indio_dev,
+ struct ad4691_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct dma_chan *rx_dma;
+ int ret;
+
+ if (st->adc_mode == AD4691_MANUAL_MODE) {
+ st->offload_trigger_periodic = devm_spi_offload_trigger_get(dev,
+ st->offload, SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(st->offload_trigger_periodic))
+ return dev_err_probe(dev,
+ PTR_ERR(st->offload_trigger_periodic),
+ "failed to get periodic offload trigger\n");
+
+ st->offload_trigger_hz = AD4691_MANUAL_MODE_STD_FREQ(st->chip->num_channels,
+ st->spi->max_speed_hz);
+ } else {
+ struct spi_offload_trigger_info trigger_info = {
+ .fwnode = dev_fwnode(dev),
+ .ops = &ad4691_offload_trigger_ops,
+ .priv = st,
+ };
+
+ ret = devm_spi_offload_trigger_register(dev, &trigger_info);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to register offload trigger\n");
+
+ st->offload_trigger = devm_spi_offload_trigger_get(dev,
+ st->offload, SPI_OFFLOAD_TRIGGER_DATA_READY);
+ if (IS_ERR(st->offload_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+ "failed to get offload trigger\n");
+ }
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma),
+ "failed to get offload RX DMA\n");
+
+ indio_dev->modes = INDIO_DIRECT_MODE | INDIO_BUFFER_HARDWARE;
+ indio_dev->setup_ops = &ad4691_offload_buffer_setup_ops;
+
+ return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev,
+ rx_dma, IIO_BUFFER_DIRECTION_IN);
+}
+
static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev,
struct ad4691_state *st)
{
@@ -1064,6 +1428,14 @@ static int ad4691_probe(struct spi_device *spi)
return dev_err_probe(dev, PTR_ERR(st->regmap),
"Failed to initialize regmap\n");
+ st->offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
+ if (IS_ERR(st->offload)) {
+ if (PTR_ERR(st->offload) != -ENODEV)
+ return dev_err_probe(dev, PTR_ERR(st->offload),
+ "failed to get SPI offload\n");
+ st->offload = NULL;
+ }
+
st->chip = spi_get_device_match_data(spi);
if (!st->chip)
return dev_err_probe(dev, -ENODEV, "Could not find chip info\n");
@@ -1088,10 +1460,15 @@ static int ad4691_probe(struct spi_device *spi)
? st->chip->manual_channels : st->chip->channels;
indio_dev->num_channels = st->chip->num_channels;
- ret = ad4691_setup_triggered_buffer(indio_dev, st);
- if (ret)
- return ret;
-
+ if (st->offload) {
+ ret = ad4691_setup_offload(indio_dev, st);
+ if (ret)
+ return ret;
+ } else {
+ ret = ad4691_setup_triggered_buffer(indio_dev, st);
+ if (ret)
+ return ret;
+ }
return devm_iio_device_register(dev, indio_dev);
}
@@ -1126,3 +1503,4 @@ module_spi_driver(ad4691_driver);
MODULE_AUTHOR("Radu Sabau <radu.sabau@analog.com>");
MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMA_BUFFER");
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 4/4] iio: adc: ad4691: add SPI offload support
2026-03-10 14:32 ` [PATCH v2 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
@ 2026-03-11 20:45 ` Andy Shevchenko
0 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-03-11 20:45 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On Tue, Mar 10, 2026 at 04:32:25PM +0200, Radu Sabau via B4 Relay wrote:
> Add SPI offload support to enable DMA-based, CPU-independent data
> acquisition using the SPI Engine offload framework.
>
> When an SPI offload is available (devm_spi_offload_get() succeeds),
> the driver registers a DMA engine IIO buffer and uses dedicated buffer
> setup operations. If no offload is available the existing software
> triggered buffer path is used unchanged.
>
> Both CNV Clock Mode and Manual Mode support offload, but use different
> trigger mechanisms:
>
> CNV Clock Mode: the SPI Engine is triggered by the ADC's DATA_READY
> signal on GP0. For this mode the driver acts as both an SPI offload
> consumer (DMA RX stream, message optimization) and a trigger source
> provider: it registers the GP0/DATA_READY output via
> devm_spi_offload_trigger_register() so the offload framework can
> match the '#trigger-source-cells' phandle from the device tree and
> automatically fire the SPI Engine DMA transfer at end-of-conversion.
> The pre-built SPI message reads all active channels from the AVG_IN
> accumulator registers (2-byte address + 2-byte data per channel,
> one 4-byte transfer each) followed by a state reset word to re-arm
> the accumulator for the next cycle.
>
> Manual Mode: the SPI Engine is triggered by a periodic trigger at
> the configured sampling frequency. The pre-built SPI message uses
> the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued
> for N active channels (the first result is discarded as garbage from
> the pipeline flush) and the remaining N results are captured by DMA.
>
> All offload transfers use 32-bit frames (bits_per_word=32, len=4) for
> DMA word alignment. In Manual Mode the 4-byte DMA word layout is
> [dummy(8), data_hi(8), data_lo(8), extra(8)]; the channel scan type
> storagebits=32, shift=8, realbits=16 correctly extracts the 16-bit
> ADC result from the middle two bytes.
>
> Kconfig gains a dependency on IIO_BUFFER_DMAENGINE.
...
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL
> + (NSEC_PER_SEC, freq));
Bad indentation.
...
> +static int ad4691_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ad4691_state *st = iio_priv(indio_dev);
> + struct spi_offload_trigger_config config = { };
> + struct spi_offload_trigger *trigger;
> + struct spi_transfer *xfer = st->offload_xfer;
> + int ret, num_xfers = 0;
> + int active_chans[16];
> + unsigned int bit;
> + int n_active = 0;
> + int i;
> +
> + memset(xfer, 0, sizeof(st->offload_xfer));
> +
> + /* Collect active channels in scan order */
> + iio_for_each_active_channel(indio_dev, bit)
> + active_chans[n_active++] = bit;
> +
> + ret = ad4691_enter_conversion_mode(st);
> + if (ret)
> + return ret;
> +
> + /*
> + * MANUAL_MODE uses a periodic (PWM) trigger and reads directly from
> + * the ADC. CNV_CLOCK_MODE uses the DATA_READY trigger and reads from
> + * accumulators.
> + */
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
> + config.periodic.frequency_hz = st->offload_trigger_hz;
> + trigger = st->offload_trigger_periodic;
> + if (!trigger)
> + return -EINVAL;
> + } else {
> + ret = regmap_write(st->regmap, AD4691_STATE_RESET_REG,
> + AD4691_STATE_RESET_ALL);
> + if (ret)
> + return ret;
> + /* Configure accumulator masks - 0 = enabled, 1 = masked */
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK1_REG,
> + ~(*indio_dev->active_scan_mask) & 0xFF);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK2_REG,
> + ~(*indio_dev->active_scan_mask >> 8) & 0xFF);
> + if (ret)
> + return ret;
Why bulk write can't be used?
> + /* Configure sequencer with active channels */
> + ret = regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG,
> + *indio_dev->active_scan_mask);
> + if (ret)
> + return ret;
> +
> + iio_for_each_active_channel(indio_dev, bit) {
> + ret = regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit),
> + AD4691_ACC_COUNT_VAL);
> + if (ret)
> + return ret;
> + }
> +
> + config.type = SPI_OFFLOAD_TRIGGER_DATA_READY;
> + trigger = st->offload_trigger;
> + }
> +
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + /*
> + * Manual mode with CNV tied to CS: Each CS toggle triggers a
> + * conversion AND reads the previous conversion result (pipeline).
> + */
> + for (i = 0; i < n_active; i++) {
> + st->offload_tx_cmd[num_xfers] = AD4691_ADC_CHAN(active_chans[i]) << 24;
> + xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
> + xfer[num_xfers].cs_change = 1;
> + xfer[num_xfers].cs_change_delay.value = 1000;
> + xfer[num_xfers].cs_change_delay.unit = SPI_DELAY_UNIT_NSECS;
> + /* First transfer RX is garbage - don't capture it */
> + if (num_xfers)
> + xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + num_xfers++;
> + }
> +
> + /* Final NOOP to flush pipeline and get last channel's data */
> + st->offload_tx_cmd[num_xfers] = AD4691_NOOP << 24;
> + xfer[num_xfers].tx_buf = &st->offload_tx_cmd[num_xfers];
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
> + xfer[num_xfers].cs_change = 0;
> + xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + num_xfers++;
> + } else {
> + /*
> + * CNV_CLOCK_MODE: single transfer per channel (2-byte cmd +
> + * 2-byte data = 4 bytes, one 32-bit SPI Engine DMA word).
> + * AVG_IN registers are used; RX layout: [cmd_hi, cmd_lo, d_hi, d_lo]
> + */
> + for (i = 0; i < n_active; i++) {
> + unsigned int reg;
> + int ch = active_chans[i];
> +
> + reg = AD4691_AVG_IN(ch);
> + st->offload_tx_cmd[ch] =
> + ((reg >> 8) | 0x80) << 24 |
> + (reg & 0xFF) << 16;
Use proper put_unaligned() and friends.
> + xfer[num_xfers].tx_buf = &st->offload_tx_cmd[ch];
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
> + xfer[num_xfers].offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
> + xfer[num_xfers].cs_change = 1;
> + num_xfers++;
> + }
> +
> + /*
> + * State reset: clear accumulator so DATA_READY can fire again.
> + * With bits_per_word=32, SPI engine transmits MSB first.
> + */
> + st->offload_tx_reset = ((AD4691_STATE_RESET_REG >> 8) << 24) |
> + ((AD4691_STATE_RESET_REG & 0xFF) << 16) |
> + (0x01 << 8);
Ditto.
> +
> + xfer[num_xfers].tx_buf = &st->offload_tx_reset;
> + xfer[num_xfers].len = 4;
> + xfer[num_xfers].bits_per_word = 32;
> + xfer[num_xfers].speed_hz = st->spi->max_speed_hz;
> + xfer[num_xfers].cs_change = 0;
> + num_xfers++;
> + }
> +
> + if (num_xfers == 0)
> + return -EINVAL;
> +
> + /*
> + * For MANUAL_MODE, validate that the trigger frequency is low enough
> + * for all SPI transfers to complete. Each transfer is 32 bits.
> + * Add 50% margin for CS setup/hold and other overhead.
> + */
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + u64 min_period_ns;
> + u64 trigger_period_ns;
> +
> + /* Time for all transfers in nanoseconds, with 50% overhead margin */
> + min_period_ns = div64_u64((u64)num_xfers * AD4691_OFFLOAD_BITS_PER_WORD *
> + NSEC_PER_SEC * 3,
> + st->spi->max_speed_hz * 2);
> +
> + trigger_period_ns = div64_u64(NSEC_PER_SEC, st->offload_trigger_hz);
Why 64-bit division? The dividend is 32-bit value.
> + if (trigger_period_ns < min_period_ns)
> + return -EINVAL;
> + }
> +
> + spi_message_init_with_transfers(&st->offload_msg, xfer, num_xfers);
> + st->offload_msg.offload = st->offload;
> +
> + ret = spi_optimize_message(st->spi, &st->offload_msg);
> + if (ret)
> + return ret;
> +
> + /*
> + * For CNV_CLOCK_MODE, start conversions before enabling the trigger.
> + * If the trigger is enabled first, the SPI engine blocks waiting for
> + * DATA_READY, and any subsequent SPI write times out.
> + *
> + * MANUAL_MODE: CNV is tied to CS; conversion starts with each transfer.
> + */
> + if (st->adc_mode == AD4691_CNV_CLOCK_MODE) {
> + ret = ad4691_sampling_enable(st, true);
> + if (ret)
> + goto err_unoptimize_message;
> + }
> +
> + ret = spi_offload_trigger_enable(st->offload, trigger, &config);
> + if (ret)
> + goto err_sampling_disable;
> +
> + return 0;
> +
> +err_sampling_disable:
> + if (st->adc_mode == AD4691_CNV_CLOCK_MODE)
> + ad4691_sampling_enable(st, false);
> +err_unoptimize_message:
> + spi_unoptimize_message(&st->offload_msg);
> + return ret;
> +}
...
> + st->offload = devm_spi_offload_get(dev, spi, &ad4691_offload_config);
ret = PTR_ERR_OR_ZERO(...); ?
> + if (IS_ERR(st->offload)) {
> + if (PTR_ERR(st->offload) != -ENODEV)
> + return dev_err_probe(dev, PTR_ERR(st->offload),
> + "failed to get SPI offload\n");
> + st->offload = NULL;
> + }
if (ret == -ENODEV)
->offload = NULL;
else if (ret)
return dev_err_probe(...);
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-10 14:32 ` [PATCH v2 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
@ 2026-03-11 21:03 ` Andy Shevchenko
2026-03-13 10:31 ` Uwe Kleine-König
1 sibling, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-03-11 21:03 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On Tue, Mar 10, 2026 at 04:32:23PM +0200, Radu Sabau via B4 Relay wrote:
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> Two buffered operating modes are supported, auto-detected from the
> device tree:
>
> - CNV Clock Mode: an external PWM drives the CNV pin; the sampling
> rate is controlled via the PWM period. Requires a
> reference clock and a DATA_READY interrupt.
>
> - Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
> a conversion and returns the previous result
> (pipelined). No external clock or interrupt needed.
>
> In both modes the chip idles in Autonomous Mode so that single-shot
> read_raw can use the internal oscillator without disturbing the
> hardware configuration.
...
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
Just no.
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/util_macros.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>
...
> +#define AD4691_VREF_MIN 2400000
> +#define AD4691_VREF_MAX 5250000
What are the units? _uV ?
#define AD4691_VREF_uV_MIN 2400000
#define AD4691_VREF_uV_MAX 5250000
...
> +#define AD4691_NOOP 0x00
> +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
> +
> +#define AD4691_STATUS_REG 0x14
> +#define AD4691_CLAMP_STATUS1_REG 0x1A
> +#define AD4691_CLAMP_STATUS2_REG 0x1B
> +#define AD4691_DEVICE_SETUP 0x20
> +#define AD4691_REF_CTRL 0x21
> +#define AD4691_OSC_FREQ_REG 0x23
> +#define AD4691_STD_SEQ_CONFIG 0x25
> +#define AD4691_SPARE_CONTROL 0x2A
> +
> +#define AD4691_OSC_EN_REG 0x180
Make all register offsets equal in width, id est the above will be 0x023
and so on.
> +#define AD4691_STATE_RESET_REG 0x181
> +#define AD4691_ADC_SETUP 0x182
> +#define AD4691_ACC_MASK1_REG 0x184
> +#define AD4691_ACC_MASK2_REG 0x185
> +#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
> +#define AD4691_ACC_COUNT_VAL 0x3F
> +#define AD4691_GPIO_MODE1_REG 0x196
> +#define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GPIO_READ 0x1A0
> +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
> +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
> +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
> +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
> +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
> +#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
> +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
> +#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
...
> +enum ad4691_ref_ctrl {
> + AD4691_VREF_2P5 = 0,
> + AD4691_VREF_3P0,
> + AD4691_VREF_3P3,
> + AD4691_VREF_4P096,
> + AD4691_VREF_5P0,
Is it HW related? Make sure you have _all_ of that being assigned explicitly.
Otherwise drop the standard assignment (if it's Linux enum). Ditto rule of
thumb to *all* enums in the code here and in the future.
This can be written on your internal Wiki page for the internal review rounds.
> +};
...
> + int num_channels;
> + int max_rate;
Why are they signed?
...
> +static const struct ad4691_chip_info ad4691_ad4691 = {
> + .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> + .name = "ad4691",
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> + .max_rate = 500000,
500 * HZ_PER_KHZ
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4692 = {
> + .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> + .name = "ad4692",
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> + .max_rate = 1000000,
1 * HZ_PER_MHZ
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4693 = {
> + .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> + .name = "ad4693",
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> + .max_rate = 500000,
...and so on...
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4694 = {
> + .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> + .name = "ad4694",
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> + .max_rate = 1000000,
> +};
...
> +struct ad4691_state {
> + const struct ad4691_chip_info *chip;
> + struct spi_device *spi;
Why? regmap has reference to struct device. This should be enough.
> + struct regmap *regmap;
> +
> + unsigned long ref_clk_rate;
> + struct pwm_device *conv_trigger;
> +
> + enum ad4691_adc_mode adc_mode;
> +
> + int vref;
Units? _uV?
int vref_uV;
> + u64 cnv_period;
> + /*
> + * Synchronize access to members of the driver state, and ensure
> + * atomicity of consecutive SPI operations.
> + */
> + struct mutex lock;
> +};
...
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> + ret = spi_write_then_read(st->spi, tx, 2, rx, 1);
> + if (!ret)
> + *val = rx[0];
Regular pattern, please.
if (ret)
return ret;
...
return 0;
> + return ret;
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + ret = spi_write_then_read(st->spi, tx, 2, rx, 2);
> + if (!ret)
> + *val = get_unaligned_be16(rx);
> + return ret;
> + 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(st->spi, tx, 2, rx, 3);
> + if (!ret)
> + *val = get_unaligned_be24(rx);
> + return ret;
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + ret = spi_write_then_read(st->spi, tx, 2, rx, 4);
> + if (!ret)
> + *val = get_unaligned_be32(rx);
> + return ret;
> + default:
> + return -EINVAL;
> + }
> +}
...
> + .cache_type = REGCACHE_RBTREE,
Why not MAPLE?
...
> +static int ad4691_get_sampling_freq(struct ad4691_state *st)
> +{
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> + ktime_to_ns(st->sampling_period));
> + }
Unneeded {}, why 64-bit division?
> +
> + return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> + pwm_get_period(st->conv_trigger));
Ditto.
> +}
...
> +static int ad4691_pwm_get(struct spi_device *spi, struct ad4691_state *st)
Is spi used at all here? I do not see it. Why not drop it and take struct
device from regmap? Same question to the entire code.
...
> + st->sampling_period = ns_to_ktime(DIV_ROUND_CLOSEST_ULL
> + (NSEC_PER_SEC, freq));
Bad indentation.
...
> + if (!freq || freq > st->chip->max_rate)
> + return -EINVAL;
ERANGE ?
...
> + unsigned long conv_us = DIV_ROUND_UP(2UL * USEC_PER_SEC,
Why UL?
> + st->chip->max_rate);
Also it's harder to read. Split this assignment to be closer for its first
user.
...
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK1_REG, mask & 0xFF);
> + if (ret)
> + return ret;
> +
> + ret = regmap_write(st->regmap, AD4691_ACC_MASK2_REG, (mask >> 8) & 0xFF);
> + if (ret)
> + return ret;
Why not bulk write?
...
> +static int ad4691_gpio_setup(struct ad4691_state *st)
> +{
> + struct device *dev = &st->spi->dev;
> + struct gpio_desc *reset;
> +
> + reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(reset))
> + return dev_err_probe(dev, PTR_ERR(reset),
> + "Failed to get reset GPIO\n");
> +
> + /* Reset delay required. See datasheet Table 5. */
> + fsleep(300);
> + gpiod_set_value(reset, 0);
> +
> + return 0;
> +}
Use reset-gpio instead.
...
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK,
> + AD4691_VREF_3P0));
Make the second line a bit longer. Ditto for the rest.
> + break;
> + case 3250001 ... 3750000:
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK,
> + AD4691_VREF_3P3));
> + break;
> + case 3750001 ... 4500000:
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK,
> + AD4691_VREF_4P096));
> + break;
> + case 4500001 ... AD4691_VREF_MAX:
> + ret = regmap_write(st->regmap, AD4691_REF_CTRL,
> + FIELD_PREP(AD4691_REF_CTRL_MASK,
> + AD4691_VREF_5P0));
> + break;
It's all the repetitions of the same regmap_write. Prepare value instead of ret
inside the switch and call regmap_write() only once after it.
...
> +static int ad4691_probe(struct spi_device *spi)
> +{
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ad4691_state *st;
> + int ret;
> +
> + indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> +
> + st = iio_priv(indio_dev);
> + ret = devm_mutex_init(dev, &st->lock);
> + if (ret)
> + return ret;
> + st->spi = spi;
No need.
> + st->regmap = devm_regmap_init(dev, NULL, st, &ad4691_regmap_config);
> + if (IS_ERR(st->regmap))
> + return dev_err_probe(dev, PTR_ERR(st->regmap),
> + "Failed to initialize regmap\n");
> +
> + st->chip = spi_get_device_match_data(spi);
> + if (!st->chip)
> + return dev_err_probe(dev, -ENODEV, "Could not find chip info\n");
We agreed to avoid this dead code.
> + ret = ad4691_regulator_get(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_gpio_setup(st);
> + if (ret)
> + return ret;
> +
> + ret = ad4691_config(st);
> + if (ret)
> + return ret;
> +
> + indio_dev->name = st->chip->name;
> + indio_dev->info = &ad4691_info;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> +
> + indio_dev->channels = (st->adc_mode == AD4691_MANUAL_MODE)
> + ? st->chip->manual_channels : st->chip->channels;
Locate ? on the previous line
> + indio_dev->num_channels = st->chip->num_channels;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 3/4] iio: adc: ad4691: add triggered buffer support
2026-03-10 14:32 ` [PATCH v2 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
@ 2026-03-11 21:08 ` Andy Shevchenko
0 siblings, 0 replies; 9+ messages in thread
From: Andy Shevchenko @ 2026-03-11 21:08 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Uwe Kleine-König,
Liam Girdwood, Mark Brown, Linus Walleij, Bartosz Golaszewski,
linux-iio, devicetree, linux-kernel, linux-pwm, linux-gpio
On Tue, Mar 10, 2026 at 04:32:24PM +0200, Radu Sabau via B4 Relay wrote:
> Add buffered capture support using the IIO triggered buffer framework.
>
> Both operating modes share a single IIO trigger and trigger handler.
> The handler builds a complete scan — one u32 slot per channel at its
> scan_index position, followed by a timestamp — and pushes it to the
> IIO buffer in a single iio_push_to_buffers_with_ts() call.
>
> For CNV Clock Mode the GP0 pin is configured as DATA_READY output. The
> IRQ handler stops conversions and fires the IIO trigger; the trigger
> handler reads accumulated results from the AVG_IN registers via regmap
> and restarts conversions for the next cycle.
>
> For Manual Mode there is no DATA_READY signal; CNV is tied to SPI CS
> so conversions are triggered by CS assertion rather than by a dedicated
> pin. The standard iio-trig-hrtimer module is not used because the timer
> period must be derived from the SPI clock rate and the number of active
> channels: the pipelined protocol requires N+1 SPI transfers per scan
> (the first result is garbage and is discarded), so the minimum period
> depends on both the SPI speed and the live channel count at buffer
> enable time. A driver-private hrtimer whose period is recomputed by
> buffer_postenable is simpler and avoids requiring the user to configure
> an external trigger with the correct hardware-derived period.
>
> Manual mode channels use storagebits=32 (shift=8, realbits=16) so all
> channel slots in the scan buffer are uniformly sized regardless of the
> SPI wire format (24-bit transfer, 16-bit ADC data in bits[23:8]).
Many comments from previous patch are applicable here.
...
> +static irqreturn_t ad4691_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ad4691_state *st = iio_priv(indio_dev);
> + unsigned int val;
> + int ret, i;
Why is 'i' signed?
> + mutex_lock(&st->lock);
No guard()()?
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + unsigned int prev_val;
> + int prev_chan = -1;
> +
> + /*
> + * MANUAL_MODE with CNV tied to CS: each transfer triggers a
> + * conversion AND returns the previous conversion's result.
> + * First transfer returns garbage, so we do N+1 transfers for
> + * N channels. Collect all results into scan.vals[], then push
> + * the complete scan once.
> + */
> + iio_for_each_active_channel(indio_dev, i) {
> + ret = ad4691_transfer(st, AD4691_ADC_CHAN(i), &val);
> + if (ret)
> + goto done;
> +
> + if (prev_chan >= 0)
> + st->scan.vals[prev_chan] = prev_val;
> + prev_val = val;
> + prev_chan = i;
> + }
> +
> + /* Final NOOP transfer to retrieve last channel's result */
> + ret = ad4691_transfer(st, AD4691_NOOP, &val);
> + if (ret)
> + goto done;
> +
> + st->scan.vals[prev_chan] = val;
> + } else {
> + for (i = 0; i < st->chip->num_channels; i++) {
> + if (BIT(i) & *indio_dev->active_scan_mask) {
NIH for_each_set_bit().
> + ret = regmap_read(st->regmap, AD4691_AVG_IN(i), &val);
> + if (ret)
> + goto done;
> +
> + st->scan.vals[i] = val;
> + }
> + }
> +
> + regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL);
> +
> + /* Restart conversions for the next trigger cycle. */
> + ad4691_sampling_enable(st, true);
> + }
> +
> + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
> + pf->timestamp);
> +
> +done:
> + iio_trigger_notify_done(indio_dev->trig);
> + mutex_unlock(&st->lock);
> + return IRQ_HANDLED;
> +}
...
> + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d",
> + indio_dev->name,
> + iio_device_id(indio_dev));
> + if (!st->trig)
> + return dev_err_probe(dev, -ENOMEM,
> + "Failed to allocate IIO trigger\n");
No. Ask your senior colleagues why.
...
> + irq = fwnode_irq_get(dev_fwnode(dev), 0);
> + if (irq <= 0)
' = 0' ?!
> + return dev_err_probe(dev, irq ? irq : -ENOENT,
> + "failed to get DATA_READY interrupt\n");
This ugly ternary will gone.
...
> + ret = devm_request_threaded_irq(dev, irq, NULL,
> + &ad4691_irq,
> + IRQF_ONESHOT,
> + indio_dev->name, indio_dev);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "request irq %d failed\n", irq);
Also no. Similar reason as above.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 2/4] iio: adc: ad4691: add initial driver for AD4691 family
2026-03-10 14:32 ` [PATCH v2 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
2026-03-11 21:03 ` Andy Shevchenko
@ 2026-03-13 10:31 ` Uwe Kleine-König
1 sibling, 0 replies; 9+ messages in thread
From: Uwe Kleine-König @ 2026-03-13 10:31 UTC (permalink / raw)
To: radu.sabau
Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Nuno Sá, Andy Shevchenko, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Liam Girdwood, Mark Brown,
Linus Walleij, Bartosz Golaszewski, linux-iio, devicetree,
linux-kernel, linux-pwm, linux-gpio
[-- Attachment #1: Type: text/plain, Size: 18763 bytes --]
On Tue, Mar 10, 2026 at 04:32:23PM +0200, Radu Sabau via B4 Relay wrote:
> From: Radu Sabau <radu.sabau@analog.com>
>
> Add support for the Analog Devices AD4691 family of high-speed,
> low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS),
> AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and
> AD4694 (8-ch, 1 MSPS).
>
> The driver implements a custom regmap layer over raw SPI to handle the
> device's mixed 1/2/3/4-byte register widths and uses the standard IIO
> read_raw/write_raw interface for single-channel reads.
>
> Two buffered operating modes are supported, auto-detected from the
> device tree:
>
> - CNV Clock Mode: an external PWM drives the CNV pin; the sampling
> rate is controlled via the PWM period. Requires a
> reference clock and a DATA_READY interrupt.
>
> - Manual Mode: CNV is tied to SPI CS; each SPI transfer triggers
> a conversion and returns the previous result
> (pipelined). No external clock or interrupt needed.
>
> In both modes the chip idles in Autonomous Mode so that single-shot
> read_raw can use the internal oscillator without disturbing the
> hardware configuration.
>
> Signed-off-by: Radu Sabau <radu.sabau@analog.com>
> ---
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ad4691.c | 773 +++++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 786 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9994d107d88d..5325f7d3b7f4 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org
> S: Supported
> W: https://ez.analog.com/linux-software-drivers
> F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml
> +F: drivers/iio/adc/ad4691.c
> F: include/dt-bindings/iio/adc/adi,ad4691.h
>
> ANALOG DEVICES INC AD4695 DRIVER
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 60038ae8dfc4..3685a03aa8dc 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -139,6 +139,17 @@ config AD4170_4
> To compile this driver as a module, choose M here: the module will be
> called ad4170-4.
>
> +config AD4691
> + tristate "Analog Devices AD4691 Family ADC Driver"
> + depends on SPI
> + select REGMAP
> + help
> + Say yes here to build support for Analog Devices AD4691 Family MuxSAR
> + SPI analog to digital converters (ADC).
> +
> + To compile this driver as a module, choose M here: the module will be
> + called ad4691.
> +
> config AD4695
> tristate "Analog Device AD4695 ADC Driver"
> depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index c76550415ff1..4ac1ea09d773 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -16,6 +16,7 @@ obj-$(CONFIG_AD4080) += ad4080.o
> obj-$(CONFIG_AD4130) += ad4130.o
> obj-$(CONFIG_AD4134) += ad4134.o
> obj-$(CONFIG_AD4170_4) += ad4170-4.o
> +obj-$(CONFIG_AD4691) += ad4691.o
> obj-$(CONFIG_AD4695) += ad4695.o
> obj-$(CONFIG_AD4851) += ad4851.o
> obj-$(CONFIG_AD7091R) += ad7091r-base.o
> diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c
> new file mode 100644
> index 000000000000..528c37a9a383
> --- /dev/null
> +++ b/drivers/iio/adc/ad4691.c
> @@ -0,0 +1,773 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (C) 2024-2026 Analog Devices, Inc.
> + * Author: Radu Sabau <radu.sabau@analog.com>
> + */
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/kernel.h>
> +#include <linux/math.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/util_macros.h>
> +#include <linux/units.h>
> +#include <linux/unaligned.h>
> +
> +#include <linux/iio/iio.h>
> +
> +#include <dt-bindings/iio/adc/adi,ad4691.h>
> +
> +#define AD4691_VREF_MIN 2400000
> +#define AD4691_VREF_MAX 5250000
> +
> +/*
> + * Default sampling frequency for MANUAL_MODE.
> + * Each sample needs (num_channels + 1) SPI transfers of 24 bits.
> + * The factor 36 = 24 * 3/2 folds in a 50% scheduling margin:
> + * freq = spi_hz / (24 * 3/2 * (num_channels + 1))
> + * = spi_hz / (36 * (num_channels + 1))
> + */
> +#define AD4691_MANUAL_MODE_STD_FREQ(x, y) ((y) / (36 * ((x) + 1)))
> +#define AD4691_BITS_PER_XFER 24
> +#define AD4691_CNV_DUTY_CYCLE_NS 380
> +#define AD4691_MAX_CONV_PERIOD_US 800
> +
> +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00
> +#define AD4691_STATE_RESET_ALL 0x01
> +
> +#define AD4691_REF_CTRL_MASK GENMASK(4, 2)
> +
> +#define AD4691_DEVICE_MANUAL 0x14
> +#define AD4691_DEVICE_REGISTER 0x10
> +#define AD4691_AUTONOMOUS_MODE_VAL 0x02
> +
> +#define AD4691_NOOP 0x00
> +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3)
> +
> +#define AD4691_STATUS_REG 0x14
> +#define AD4691_CLAMP_STATUS1_REG 0x1A
> +#define AD4691_CLAMP_STATUS2_REG 0x1B
> +#define AD4691_DEVICE_SETUP 0x20
> +#define AD4691_REF_CTRL 0x21
> +#define AD4691_OSC_FREQ_REG 0x23
> +#define AD4691_STD_SEQ_CONFIG 0x25
> +#define AD4691_SPARE_CONTROL 0x2A
> +
> +#define AD4691_OSC_EN_REG 0x180
> +#define AD4691_STATE_RESET_REG 0x181
> +#define AD4691_ADC_SETUP 0x182
> +#define AD4691_ACC_MASK1_REG 0x184
> +#define AD4691_ACC_MASK2_REG 0x185
> +#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n))
> +#define AD4691_ACC_COUNT_VAL 0x3F
> +#define AD4691_GPIO_MODE1_REG 0x196
> +#define AD4691_GPIO_MODE2_REG 0x197
> +#define AD4691_GPIO_READ 0x1A0
> +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0
> +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1
> +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2
> +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3
> +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4
> +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE
> +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n))
> +#define AD4691_AVG_IN(n) (0x201 + (2 * (n)))
> +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n)))
> +#define AD4691_ACC_IN(n) (0x252 + (3 * (n)))
> +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n)))
> +
> +enum ad4691_adc_mode {
> + AD4691_CNV_CLOCK_MODE,
> + AD4691_MANUAL_MODE,
> +};
> +
> +enum ad4691_gpio_mode {
> + AD4691_ADC_BUSY = 4,
> + AD4691_DATA_READY = 6,
> +};
> +
> +enum ad4691_ref_ctrl {
> + AD4691_VREF_2P5 = 0,
> + AD4691_VREF_3P0,
> + AD4691_VREF_3P3,
> + AD4691_VREF_4P096,
> + AD4691_VREF_5P0,
> +};
> +
> +struct ad4691_chip_info {
> + const struct iio_chan_spec *channels;
> + const struct iio_chan_spec *manual_channels;
> + const char *name;
> + int num_channels;
> + int max_rate;
> +};
> +
> +#define AD4691_CHANNEL(chan, index, real_bits, storage_bits, _shift) \
> + { \
> + .type = IIO_VOLTAGE, \
> + .indexed = 1, \
> + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
> + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ) \
> + | BIT(IIO_CHAN_INFO_SCALE), \
> + .channel = chan, \
> + .scan_index = index, \
> + .scan_type = { \
> + .sign = 'u', \
> + .realbits = real_bits, \
> + .storagebits = storage_bits, \
> + .shift = _shift, \
> + }, \
> + }
> +
> +static const struct iio_chan_spec ad4691_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 32, 0),
> + AD4691_CHANNEL(1, 1, 16, 32, 0),
> + AD4691_CHANNEL(2, 2, 16, 32, 0),
> + AD4691_CHANNEL(3, 3, 16, 32, 0),
> + AD4691_CHANNEL(4, 4, 16, 32, 0),
> + AD4691_CHANNEL(5, 5, 16, 32, 0),
> + AD4691_CHANNEL(6, 6, 16, 32, 0),
> + AD4691_CHANNEL(7, 7, 16, 32, 0),
> + AD4691_CHANNEL(8, 8, 16, 32, 0),
> + AD4691_CHANNEL(9, 9, 16, 32, 0),
> + AD4691_CHANNEL(10, 10, 16, 32, 0),
> + AD4691_CHANNEL(11, 11, 16, 32, 0),
> + AD4691_CHANNEL(12, 12, 16, 32, 0),
> + AD4691_CHANNEL(13, 13, 16, 32, 0),
> + AD4691_CHANNEL(14, 14, 16, 32, 0),
> + AD4691_CHANNEL(15, 15, 16, 32, 0)
> +};
> +
> +static const struct iio_chan_spec ad4693_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 32, 0),
> + AD4691_CHANNEL(1, 1, 16, 32, 0),
> + AD4691_CHANNEL(2, 2, 16, 32, 0),
> + AD4691_CHANNEL(3, 3, 16, 32, 0),
> + AD4691_CHANNEL(4, 4, 16, 32, 0),
> + AD4691_CHANNEL(5, 5, 16, 32, 0),
> + AD4691_CHANNEL(6, 6, 16, 32, 0),
> + AD4691_CHANNEL(7, 7, 16, 32, 0)
> +};
> +
> +static const struct iio_chan_spec ad4691_manual_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 24, 8),
> + AD4691_CHANNEL(1, 1, 16, 24, 8),
> + AD4691_CHANNEL(2, 2, 16, 24, 8),
> + AD4691_CHANNEL(3, 3, 16, 24, 8),
> + AD4691_CHANNEL(4, 4, 16, 24, 8),
> + AD4691_CHANNEL(5, 5, 16, 24, 8),
> + AD4691_CHANNEL(6, 6, 16, 24, 8),
> + AD4691_CHANNEL(7, 7, 16, 24, 8),
> + AD4691_CHANNEL(8, 8, 16, 24, 8),
> + AD4691_CHANNEL(9, 9, 16, 24, 8),
> + AD4691_CHANNEL(10, 10, 16, 24, 8),
> + AD4691_CHANNEL(11, 11, 16, 24, 8),
> + AD4691_CHANNEL(12, 12, 16, 24, 8),
> + AD4691_CHANNEL(13, 13, 16, 24, 8),
> + AD4691_CHANNEL(14, 14, 16, 24, 8),
> + AD4691_CHANNEL(15, 15, 16, 24, 8)
> +};
> +
> +static const struct iio_chan_spec ad4693_manual_channels[] = {
> + AD4691_CHANNEL(0, 0, 16, 24, 8),
> + AD4691_CHANNEL(1, 1, 16, 24, 8),
> + AD4691_CHANNEL(2, 2, 16, 24, 8),
> + AD4691_CHANNEL(3, 3, 16, 24, 8),
> + AD4691_CHANNEL(4, 4, 16, 24, 8),
> + AD4691_CHANNEL(5, 5, 16, 24, 8),
> + AD4691_CHANNEL(6, 6, 16, 24, 8),
> + AD4691_CHANNEL(7, 7, 16, 24, 8)
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4691 = {
> + .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> + .name = "ad4691",
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> + .max_rate = 500000,
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4692 = {
> + .channels = ad4691_channels,
> + .manual_channels = ad4691_manual_channels,
> + .name = "ad4692",
> + .num_channels = ARRAY_SIZE(ad4691_channels),
> + .max_rate = 1000000,
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4693 = {
> + .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> + .name = "ad4693",
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> + .max_rate = 500000,
> +};
> +
> +static const struct ad4691_chip_info ad4691_ad4694 = {
> + .channels = ad4693_channels,
> + .manual_channels = ad4693_manual_channels,
> + .name = "ad4694",
> + .num_channels = ARRAY_SIZE(ad4693_channels),
> + .max_rate = 1000000,
> +};
> +
> +struct ad4691_state {
> + const struct ad4691_chip_info *chip;
> + struct spi_device *spi;
> + struct regmap *regmap;
> +
> + unsigned long ref_clk_rate;
> + struct pwm_device *conv_trigger;
> +
> + enum ad4691_adc_mode adc_mode;
> +
> + int vref;
> + u64 cnv_period;
> + /*
> + * Synchronize access to members of the driver state, and ensure
> + * atomicity of consecutive SPI operations.
> + */
> + struct mutex lock;
> +};
> +
> +static void ad4691_disable_pwm(void *data)
> +{
> + struct pwm_device *pwm = data;
> + struct pwm_state state;
> +
> + pwm_get_state(pwm, &state);
> + state.enabled = false;
> + pwm_apply_might_sleep(pwm, &state);
You can just do:
struct pwm_state state = { .enabled = false, };
pwm_apply_might_sleep(pwm, &state);
as there is no need to keep period and duty_cycle.
> +}
> +
> +static int ad4691_regulator_get(struct ad4691_state *st)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + ret = devm_regulator_get_enable(dev, "vio");
> + if (ret)
> + return dev_err_probe(dev, ret, "Failed to get and enable VIO\n");
> +
> + st->vref = devm_regulator_get_enable_read_voltage(dev, "vref");
> + if (st->vref == -ENODEV)
> + st->vref = devm_regulator_get_enable_read_voltage(dev, "vrefin");
> + if (st->vref < 0)
> + return dev_err_probe(dev, st->vref,
> + "Failed to get reference supply\n");
> +
> + if (st->vref < AD4691_VREF_MIN || st->vref > AD4691_VREF_MAX)
> + return dev_err_probe(dev, -EINVAL, "vref(%d) must be under [%u %u]\n",
> + st->vref, AD4691_VREF_MIN, AD4691_VREF_MAX);
> +
> + return 0;
> +}
> +
> +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *val)
> +{
> + struct ad4691_state *st = context;
> + u8 tx[2], rx[4];
> + int ret;
> +
> + put_unaligned_be16(0x8000 | reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15):
> + ret = spi_write_then_read(st->spi, tx, 2, rx, 1);
> + if (!ret)
> + *val = rx[0];
> + return ret;
> + case AD4691_STD_SEQ_CONFIG:
> + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15):
> + ret = spi_write_then_read(st->spi, tx, 2, rx, 2);
> + if (!ret)
> + *val = get_unaligned_be16(rx);
> + return ret;
> + 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(st->spi, tx, 2, rx, 3);
> + if (!ret)
> + *val = get_unaligned_be24(rx);
> + return ret;
> + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15):
> + ret = spi_write_then_read(st->spi, tx, 2, rx, 4);
> + if (!ret)
> + *val = get_unaligned_be32(rx);
> + return ret;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int val)
> +{
> + struct ad4691_state *st = context;
> + u8 tx[4];
> +
> + put_unaligned_be16(reg, tx);
> +
> + switch (reg) {
> + case 0 ... AD4691_OSC_FREQ_REG:
> + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG:
> + if (val > 0xFF)
> + return -EINVAL;
> + tx[2] = val;
> + return spi_write_then_read(st->spi, tx, 3, NULL, 0);
> + case AD4691_STD_SEQ_CONFIG:
> + if (val > 0xFFFF)
> + return -EINVAL;
> + put_unaligned_be16(val, &tx[2]);
> + return spi_write_then_read(st->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_RBTREE,
> +};
> +
> +static int ad4691_get_sampling_freq(struct ad4691_state *st)
> +{
> + if (st->adc_mode == AD4691_MANUAL_MODE) {
> + return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> + ktime_to_ns(st->sampling_period));
> + }
> +
> + return DIV_ROUND_CLOSEST_ULL(NSEC_PER_SEC,
> + pwm_get_period(st->conv_trigger));
Other iio drivers (I looked at ad4062.c) use down-rounding division to
provide the sampling frequency. I wonder if there should be some
consistency about that in the different drivers?
(But having said that, the ad4062.c calculation looks somewhat fishy, is
AD4062_TCONV_NS really relevant to the frequency?)
> +}
> +
> +static int __ad4691_set_sampling_freq(struct ad4691_state *st, int freq)
> +{
> + unsigned long long target, ref_clk_period_ns;
> + struct pwm_state cnv_state;
> +
> + pwm_init_state(st->conv_trigger, &cnv_state);
> +
> + freq = clamp(freq, 1, st->chip->max_rate);
> + target = DIV_ROUND_CLOSEST_ULL(st->ref_clk_rate, freq);
> + ref_clk_period_ns = DIV_ROUND_CLOSEST_ULL(NANO, st->ref_clk_rate);
> + st->cnv_period = ref_clk_period_ns * target;
> + cnv_state.period = ref_clk_period_ns * target;
You're losing precision here. Consider ref_clk_rate = 64000000, freq =
1234560. Then
cnv_state.period
= ref_clk_period_ns * target
= DIV_ROUND_CLOSEST_ULL(NANO, st->ref_clk_rate) * DIV_ROUND_CLOSEST_ULL(st->ref_clk_rate, freq)
= 16 * 52
= 832
The exact value is NANO / freq (as st->ref_clk_rate cancels out) which
is 810.0051840331778.
So this either needs a comment why the calculation is as it is without
canceling st->ref_clk_rate and two divisions instead of only one, or it
should be simplified.
Also I wonder if DIV_ROUND_CLOSEST_ULL(NANO, freq) is right. With freq =
793336 we get:
NANO / freq = 1260.499964706001
so 1260 is picked as period which results in an actual frequency of
NANO / 1260 = 793650.7936507936
and thus a delta of 314.79365079361014 Hz. With period = 1261 however we
get
NANO / 1261 = 793021.4115781126
which has a delta of 314.5884218873689 and thus is (maybe?) the better
choice? But maybe that is too theoretic as the underlying PWM hardware
probably cannot set 1260 ns or 1261 ns exactly anyhow and most probably
both result in the same HW setting (but if not, 1261 yields the better
setting).
> + cnv_state.duty_cycle = AD4691_CNV_DUTY_CYCLE_NS;
> + cnv_state.enabled = false;
> +
> + return pwm_apply_might_sleep(st->conv_trigger, &cnv_state);
> +}
Best regards
Uwe
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-03-13 10:31 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-10 14:32 [PATCH v2 0/4] iio: adc: ad4691: add driver for AD4691 multichannel SAR ADC family Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 1/4] dt-bindings: iio: adc: add bindings for AD4691 family Radu Sabau via B4 Relay
2026-03-10 14:32 ` [PATCH v2 2/4] iio: adc: ad4691: add initial driver " Radu Sabau via B4 Relay
2026-03-11 21:03 ` Andy Shevchenko
2026-03-13 10:31 ` Uwe Kleine-König
2026-03-10 14:32 ` [PATCH v2 3/4] iio: adc: ad4691: add triggered buffer support Radu Sabau via B4 Relay
2026-03-11 21:08 ` Andy Shevchenko
2026-03-10 14:32 ` [PATCH v2 4/4] iio: adc: ad4691: add SPI offload support Radu Sabau via B4 Relay
2026-03-11 20:45 ` Andy Shevchenko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox