From nobody Mon Apr 6 09:20:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D5465396B90; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; cv=none; b=PdT5YHMwOie6591Na7Aw1z5cobCqHU5pIyLHi5CIZKUymxVJDKavZDa4PeY1TkmyjvCGNMD9PFYcIcUrxbTDJEcLNy88BLxFbPCXoGdWkaIj0epdw5kwOqfogr6h+vgozUkclAhhR6DiYSjHF0lczMMDHd2UpiOb8TZC6lBDtMY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; c=relaxed/simple; bh=hDGWwkaa+HeYi8FXZB5lS0GMVvEhKWw0eGojvNtK8pQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=g5yG9y+Ws6WEC8sR6QOEgk0uK7yrFaQQ5bb1tHMj6kId/OUB62T40bxl/y66Elu9H3oiFRX2B8jBdzVsiAqQ1sgapMrFYKScu2audmbyDTVPxYRmrsmQ5m3LdZ7CGLzivp15hPvxnGBFXJQufeZgIySoQ4YCO6b/gpQh7UsRcmA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ifQd6qlQ; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ifQd6qlQ" Received: by smtp.kernel.org (Postfix) with ESMTPS id 78181C2BCAF; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774004639; bh=hDGWwkaa+HeYi8FXZB5lS0GMVvEhKWw0eGojvNtK8pQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ifQd6qlQTAfFj5t/IbHZVsRhaxDKIE3M8Xh7s3aVB3o3ke31QM4lheeR/DEObEEJx mc7ZA41k5ZpzMzflvYr0wMogFxkMahgiegpf/Fe2pykL3rOzkaJTq75qFPEdL0P+fQ f24I1w4T9HRXkklU1qhpRySgxxn+rPup0zCCRrf3aWINSCyIlOnOh2jSZZQ/4OqBgr OHeQoz62T0lFbaNca8VlgVVWU/DkJinVI1NqNT7w/J0bTqJfKRhBalqpJFKcmQ7fgW GEZFfE1FnYv1FUof8RGCi9Qw8DpX7fOMnl+UMvgzybT9jLRwjp9KLlTliKw0pBe3jR KmVyN3wfXGcgw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 67EE7108B8F3; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Fri, 20 Mar 2026 13:03:55 +0200 Subject: [PATCH v4 1/4] dt-bindings: iio: adc: add AD4691 family Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260320-ad4692-multichannel-sar-adc-driver-v4-1-052c1050507a@analog.com> References: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> In-Reply-To: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> To: Lars-Peter Clausen , Michael Hennerich , Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Liam Girdwood , Mark Brown , Linus Walleij , Bartosz Golaszewski , Philipp Zabel Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774004636; l=6905; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=7k2O/4139eTFZPpsKABwqAvS5Z2dOIjHJmh/+f2Utno=; b=lrFcBCjOdXPGR+Bz2gwk2OmdYGjGUHSKVoK9Hq9H5tauzlTVMH6+fioNyQ2kZYRq9d1fAP5cS VDi70vHG+9KBFA7nSrPLYqnRcHY4Ughig9xp27dzIx7hyTHnETubeGV X-Developer-Key: i=radu.sabau@analog.com; a=ed25519; pk=lDPQHgn9jTdt0vo58Na9lLxLaE2mb330if71Cn+EvFU= X-Endpoint-Received: by B4 Relay for radu.sabau@analog.com/20260220 with auth_id=642 X-Original-From: Radu Sabau Reply-To: radu.sabau@analog.com From: Radu Sabau Add DT bindings for the Analog Devices AD4691 family of multichannel SAR ADCs (AD4691, AD4692, AD4693, AD4694). The binding describes the hardware connections: - Power domains: avdd-supply (required), vio-supply, ref-supply or refin-supply (external reference; the REFIN path enables the internal reference buffer), and an optional ldo-in-supply, that if absent, means the on-chip internal LDO will be used. - Optional PWM on the CNV pin selects CNV Burst Mode; when absent, Manual Mode is assumed with CNV tied to SPI CS. - An optional reset GPIO (reset-gpios) for hardware reset. - Up to four GP pins (gp0..gp3) usable as interrupt sources, identified in firmware via interrupt-names "gp0".."gp3". - gpio-controller with #gpio-cells =3D <2> for GP pin GPIO usage. - #trigger-source-cells =3D <1>: one cell selecting the GP pin number (0-3) used as the SPI offload trigger source. Two binding examples are provided: CNV Burst Mode with SPI offload (DMA data acquisition driven by DATA_READY on a GP pin), and Manual Mode for CPU-driven triggered-buffer or single-shot capture. Signed-off-by: Radu Sabau --- .../devicetree/bindings/iio/adc/adi,ad4691.yaml | 173 +++++++++++++++++= ++++ MAINTAINERS | 7 + 2 files changed, 180 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml b/Do= cumentation/devicetree/bindings/iio/adc/adi,ad4691.yaml new file mode 100644 index 000000000000..def9f32c78af --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml @@ -0,0 +1,173 @@ +# 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 + +description: | + The AD4691 family are high-speed, low-power, multichannel successive + approximation register (SAR) analog-to-digital converters (ADCs) with + an SPI-compatible serial interface. The ADC supports CNV Burst Mode, + where an external PWM drives the CNV pin, and Manual Mode, where CNV + is directly tied to the SPI chip-select. + + Datasheets: + * https://www.analog.com/en/products/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 + + avdd-supply: + description: Analog power supply (4.5V to 5.5V). + + ldo-in-supply: + description: LDO input supply. When absent, the internal LDO is used. + + vio-supply: + description: I/O voltage supply (1.71V to 1.89V or VDD). + + ref-supply: + description: External reference voltage supply (2.4V to 5.25V). + + refin-supply: + description: Internal reference buffer input supply. + + reset-gpios: + description: + GPIO line controlling the hardware reset pin (active-low). + maxItems: 1 + + pwms: + description: + PWM connected to the CNV pin. When present, selects CNV Burst Mode w= here + the PWM drives the conversion rate. When absent, Manual Mode is used + (CNV tied to SPI CS). + maxItems: 1 + + interrupts: + description: + Interrupt lines connected to the ADC GP pins. Each GP pin can be + physically wired to an interrupt-capable input on the SoC. + maxItems: 4 + + interrupt-names: + description: Names of the interrupt lines, matching the GP pin names. + minItems: 1 + maxItems: 4 + items: + - const: gp0 + - const: gp1 + - const: gp2 + - const: gp3 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + '#trigger-source-cells': + description: + This node can act as a trigger source. The single cell in a consumer + reference specifies the GP pin number (0-3) used as the trigger outp= ut. + const: 1 + +required: + - compatible + - reg + - avdd-supply + - vio-supply + +allOf: + # ref-supply and refin-supply are mutually exclusive, one is required + - oneOf: + - required: + - ref-supply + - required: + - refin-supply + + # CNV Burst Mode (pwms present) without SPI offload requires a DRDY inte= rrupt. + # Offload configurations expose '#trigger-source-cells' instead. + - if: + required: + - pwms + not: + required: + - '#trigger-source-cells' + then: + required: + - interrupts + +unevaluatedProperties: false + +examples: + - | + #include + /* AD4692 in CNV Burst Mode with SPI offload */ + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + adc@0 { + compatible =3D "adi,ad4692"; + reg =3D <0>; + spi-cpol; + spi-cpha; + spi-max-frequency =3D <40000000>; + + avdd-supply =3D <&avdd_supply>; + vio-supply =3D <&vio_supply>; + ref-supply =3D <&ref_5v>; + + reset-gpios =3D <&gpio0 15 GPIO_ACTIVE_LOW>; + + pwms =3D <&pwm_gen 0 0>; + + #trigger-source-cells =3D <1>; + }; + }; + + - | + #include + /* AD4692 in Manual Mode (CNV tied to SPI CS) */ + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + + adc@0 { + compatible =3D "adi,ad4692"; + reg =3D <0>; + spi-cpol; + spi-cpha; + spi-max-frequency =3D <31250000>; + + avdd-supply =3D <&avdd_supply>; + vio-supply =3D <&vio_supply>; + refin-supply =3D <&refin_supply>; + + reset-gpios =3D <&gpio0 15 GPIO_ACTIVE_LOW>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 61bf550fd37c..438ca850fa1c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1484,6 +1484,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4170-4.yaml F: drivers/iio/adc/ad4170-4.c =20 +ANALOG DEVICES INC AD4691 DRIVER +M: Radu Sabau +L: linux-iio@vger.kernel.org +S: Supported +W: https://ez.analog.com/linux-software-drivers +F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml + ANALOG DEVICES INC AD4695 DRIVER M: Michael Hennerich M: Nuno S=C3=A1 --=20 2.43.0 From nobody Mon Apr 6 09:20:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D54EF396D1B; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; cv=none; b=QbMeeMd50ShTRuPl/vuOOeU+UajrY1/4K7wFRt0G0KWyb0zwdZhbGch2fnIjn7F3b3xXLiBJb0wXnGg5BngvCaNGsOVtY1wWM93vI6C9sGmgK0kiSTJrXTZ6ZXyhcM4euym6MXB1a9Wzf4Uh3ufdsqdb22AWU29D3sE8RGgyNGA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; c=relaxed/simple; bh=UEt4cgKb+fqRIc82mlHIlgHeHxZW00eNlutc8pqhALk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=qvFbXpG4H8Vd0ppUlcv/wLHqzM7+eLzgq4MiqffkqlhvsRbk3IVFLE7RfcORRUxwpsbMXlY+jUY8LmK8zmZqHIeJV32admP0HmiuV5x+CbvzvBx3bm1FDS04je0CvmLS1E8A8v+/DtVMcCIwPJNuGMeEva1Nk7Hjo/iVZH6Oj9A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=e6c/NrI/; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="e6c/NrI/" Received: by smtp.kernel.org (Postfix) with ESMTPS id 8947BC2BC9E; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774004639; bh=UEt4cgKb+fqRIc82mlHIlgHeHxZW00eNlutc8pqhALk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=e6c/NrI/9UavR0XoGHLRbgHUbf4nL540gZ7mcAnJ1TQ70MV75jU4hT5cNMcpWtlYN 4CtBd6a8q1EqgaJ86ywYxp46LpgM6GuiBn+36h9WASH9gy6+iok/OmdzeLiXcoCxIK Aq2T0FnkZtn8JKJsx7tGA5jQDGMp2I9Wwl3JF3sXyH8TTJAmStsh26aaw914wIhhJU 7aomCuYIP6wYE2r7gXWDSgSo1Rt04FlWYwX0VffiZKwsUPiqYu5k0fso0kOewN169q JEXdLsiBdDdTGSiT2h/G5ZNvcZFmRF8UAbd0ueuwsfh6RHrx7xJY9yIPobY/5pU1F1 iKlgGGhm8SAtA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7AE77108B8F7; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Fri, 20 Mar 2026 13:03:56 +0200 Subject: [PATCH v4 2/4] iio: adc: ad4691: add initial driver for AD4691 family Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260320-ad4692-multichannel-sar-adc-driver-v4-2-052c1050507a@analog.com> References: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> In-Reply-To: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> To: Lars-Peter Clausen , Michael Hennerich , Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Liam Girdwood , Mark Brown , Linus Walleij , Bartosz Golaszewski , Philipp Zabel Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774004636; l=22997; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=/Xv9b93N272xCWH6GxA2WNmVVNOWyUWJwB+yFhBOMzo=; b=asjNzlmZA0vNpPNfIUXjrB+9YDHZwVPj3Ot5634CP+eKGcp942HMBJbOX3D282R3Bj7J2bj7p LTVQoEEJECmDdj6r8059368WiyyOE8ljCCE+bL6pdfggH0WX8xZ5/QL X-Developer-Key: i=radu.sabau@analog.com; a=ed25519; pk=lDPQHgn9jTdt0vo58Na9lLxLaE2mb330if71Cn+EvFU= X-Endpoint-Received: by B4 Relay for radu.sabau@analog.com/20260220 with auth_id=642 X-Original-From: Radu Sabau Reply-To: radu.sabau@analog.com From: Radu Sabau Add support for the Analog Devices AD4691 family of high-speed, low-power multichannel SAR ADCs: AD4691 (16-ch, 500 kSPS), AD4692 (16-ch, 1 MSPS), AD4693 (8-ch, 500 kSPS) and AD4694 (8-ch, 1 MSPS). The driver implements a custom regmap layer over raw SPI to handle the device's mixed 1/2/3/4-byte register widths and uses the standard IIO read_raw/write_raw interface for single-channel reads. The chip idles in Autonomous Mode so that single-shot read_raw can use the internal oscillator without disturbing the hardware configuration. Three voltage supply domains are managed: avdd (required), vio, and a reference supply on either the REF pin (ref-supply, external buffer) or the REFIN pin (refin-supply, uses the on-chip reference buffer; REFBUF_EN is set accordingly). Hardware reset is performed via the reset controller framework; a software reset through SPI_CONFIG_A is used as fallback when no hardware reset is available. Accumulator channel masking for single-shot reads uses ACC_MASK_REG via an ADDR_DESCENDING SPI write, which covers both mask bytes in a single 16-bit transfer. Signed-off-by: Radu Sabau --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 11 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4691.c | 686 +++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 699 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 438ca850fa1c..24e4502b8292 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1490,6 +1490,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml +F: drivers/iio/adc/ad4691.c =20 ANALOG DEVICES INC AD4695 DRIVER M: Michael Hennerich 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. =20 +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) +=3D ad4080.o obj-$(CONFIG_AD4130) +=3D ad4130.o obj-$(CONFIG_AD4134) +=3D ad4134.o obj-$(CONFIG_AD4170_4) +=3D ad4170-4.o +obj-$(CONFIG_AD4691) +=3D ad4691.o obj-$(CONFIG_AD4695) +=3D ad4695.o obj-$(CONFIG_AD4851) +=3D ad4851.o obj-$(CONFIG_AD7091R) +=3D ad7091r-base.o diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c new file mode 100644 index 000000000000..5e02eb44ca44 --- /dev/null +++ b/drivers/iio/adc/ad4691.c @@ -0,0 +1,686 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2024-2026 Analog Devices, Inc. + * Author: Radu Sabau + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define AD4691_VREF_uV_MIN 2400000 +#define AD4691_VREF_uV_MAX 5250000 +#define AD4691_VREF_2P5_uV_MAX 2750000 +#define AD4691_VREF_3P0_uV_MAX 3250000 +#define AD4691_VREF_3P3_uV_MAX 3750000 +#define AD4691_VREF_4P096_uV_MAX 4500000 + +#define AD4691_SPI_CONFIG_A_REG 0x000 +#define AD4691_SW_RESET (BIT(7) | BIT(0)) + +#define AD4691_STATUS_REG 0x014 +#define AD4691_CLAMP_STATUS1_REG 0x01A +#define AD4691_CLAMP_STATUS2_REG 0x01B +#define AD4691_DEVICE_SETUP 0x020 +#define AD4691_LDO_EN BIT(4) +#define AD4691_REF_CTRL 0x021 +#define AD4691_REF_CTRL_MASK GENMASK(4, 2) +#define AD4691_REFBUF_EN BIT(0) +#define AD4691_OSC_FREQ_REG 0x023 +#define AD4691_OSC_FREQ_MASK GENMASK(3, 0) +#define AD4691_STD_SEQ_CONFIG 0x025 +#define AD4691_SPARE_CONTROL 0x02A + +#define AD4691_OSC_EN_REG 0x180 +#define AD4691_STATE_RESET_REG 0x181 +#define AD4691_STATE_RESET_ALL 0x01 +#define AD4691_ADC_SETUP 0x182 +#define AD4691_AUTONOMOUS_MODE_VAL 0x02 +/* + * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a + * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte. + */ +#define AD4691_ACC_MASK_REG 0x185 +#define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n)) +#define AD4691_GPIO_MODE1_REG 0x196 +#define AD4691_GPIO_MODE2_REG 0x197 +#define AD4691_GPIO_READ 0x1A0 +#define AD4691_ACC_STATUS_FULL1_REG 0x1B0 +#define AD4691_ACC_STATUS_FULL2_REG 0x1B1 +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2 +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3 +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4 +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n)) +#define AD4691_AVG_IN(n) (0x201 + (2 * (n))) +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n))) +#define AD4691_ACC_IN(n) (0x252 + (3 * (n))) +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n))) + +enum ad4691_ref_ctrl { + AD4691_VREF_2P5 =3D 0, + AD4691_VREF_3P0 =3D 1, + AD4691_VREF_3P3 =3D 2, + AD4691_VREF_4P096 =3D 3, + AD4691_VREF_5P0 =3D 4, +}; + +struct ad4691_chip_info { + const struct iio_chan_spec *channels; + const char *name; + unsigned int num_channels; + unsigned int max_rate; +}; + +#define AD4691_CHANNEL(ch) \ + { \ + .type =3D IIO_VOLTAGE, \ + .indexed =3D 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available =3D \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_shared_by_all =3D BIT(IIO_CHAN_INFO_SCALE), \ + .channel =3D ch, \ + .scan_index =3D ch, \ + .scan_type =3D { \ + .sign =3D 'u', \ + .realbits =3D 16, \ + .storagebits =3D 16, \ + .shift =3D 0, \ + }, \ + } + +static const struct iio_chan_spec ad4691_channels[] =3D { + AD4691_CHANNEL(0), + AD4691_CHANNEL(1), + AD4691_CHANNEL(2), + AD4691_CHANNEL(3), + AD4691_CHANNEL(4), + AD4691_CHANNEL(5), + AD4691_CHANNEL(6), + AD4691_CHANNEL(7), + AD4691_CHANNEL(8), + AD4691_CHANNEL(9), + AD4691_CHANNEL(10), + AD4691_CHANNEL(11), + AD4691_CHANNEL(12), + AD4691_CHANNEL(13), + AD4691_CHANNEL(14), + AD4691_CHANNEL(15), +}; + +static const struct iio_chan_spec ad4693_channels[] =3D { + AD4691_CHANNEL(0), + AD4691_CHANNEL(1), + AD4691_CHANNEL(2), + AD4691_CHANNEL(3), + AD4691_CHANNEL(4), + AD4691_CHANNEL(5), + AD4691_CHANNEL(6), + AD4691_CHANNEL(7), +}; + +/* + * Internal oscillator frequency table. Index is the OSC_FREQ_REG[3:0] val= ue. + * Index 0 (1 MHz) is only valid for AD4692/AD4694; AD4691/AD4693 support + * up to 500 kHz and use index 1 as their highest valid rate. + */ +static const unsigned int ad4691_osc_freqs[] =3D { + 1000000, /* 0x0: 1 MHz */ + 500000, /* 0x1: 500 kHz */ + 400000, /* 0x2: 400 kHz */ + 250000, /* 0x3: 250 kHz */ + 200000, /* 0x4: 200 kHz */ + 167000, /* 0x5: 167 kHz */ + 133000, /* 0x6: 133 kHz */ + 125000, /* 0x7: 125 kHz */ + 100000, /* 0x8: 100 kHz */ + 50000, /* 0x9: 50 kHz */ + 25000, /* 0xA: 25 kHz */ + 12500, /* 0xB: 12.5 kHz */ + 10000, /* 0xC: 10 kHz */ + 5000, /* 0xD: 5 kHz */ + 2500, /* 0xE: 2.5 kHz */ + 1250, /* 0xF: 1.25 kHz */ +}; + +static const struct ad4691_chip_info ad4691_chip_info =3D { + .channels =3D ad4691_channels, + .name =3D "ad4691", + .num_channels =3D ARRAY_SIZE(ad4691_channels), + .max_rate =3D 500 * HZ_PER_KHZ, +}; + +static const struct ad4691_chip_info ad4692_chip_info =3D { + .channels =3D ad4691_channels, + .name =3D "ad4692", + .num_channels =3D ARRAY_SIZE(ad4691_channels), + .max_rate =3D 1 * HZ_PER_MHZ, +}; + +static const struct ad4691_chip_info ad4693_chip_info =3D { + .channels =3D ad4693_channels, + .name =3D "ad4693", + .num_channels =3D ARRAY_SIZE(ad4693_channels), + .max_rate =3D 500 * HZ_PER_KHZ, +}; + +static const struct ad4691_chip_info ad4694_chip_info =3D { + .channels =3D ad4693_channels, + .name =3D "ad4694", + .num_channels =3D ARRAY_SIZE(ad4693_channels), + .max_rate =3D 1 * HZ_PER_MHZ, +}; + +struct ad4691_state { + const struct ad4691_chip_info *info; + struct regmap *regmap; + int vref_uV; + bool refbuf_en; + bool ldo_en; + /* + * Synchronize access to members of the driver state, and ensure + * atomicity of consecutive SPI operations. + */ + struct mutex lock; +}; + +static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *= val) +{ + struct spi_device *spi =3D 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 =3D spi_write_then_read(spi, tx, 2, rx, 1); + if (ret) + return ret; + *val =3D rx[0]; + return 0; + case AD4691_STD_SEQ_CONFIG: + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15): + ret =3D spi_write_then_read(spi, tx, 2, rx, 2); + if (ret) + return ret; + *val =3D get_unaligned_be16(rx); + return 0; + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15): + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15): + ret =3D spi_write_then_read(spi, tx, 2, rx, 3); + if (ret) + return ret; + *val =3D get_unaligned_be24(rx); + return 0; + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15): + ret =3D spi_write_then_read(spi, tx, 2, rx, 4); + if (ret) + return ret; + *val =3D get_unaligned_be32(rx); + return 0; + default: + return -EINVAL; + } +} + +static int ad4691_reg_write(void *context, unsigned int reg, unsigned int = val) +{ + struct spi_device *spi =3D context; + u8 tx[4]; + + put_unaligned_be16(reg, tx); + + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1: + case AD4691_ACC_MASK_REG + 1 ... AD4691_GPIO_MODE2_REG: + if (val > 0xFF) + return -EINVAL; + tx[2] =3D val; + return spi_write_then_read(spi, tx, 3, NULL, 0); + case AD4691_ACC_MASK_REG: + case AD4691_STD_SEQ_CONFIG: + if (val > 0xFFFF) + return -EINVAL; + put_unaligned_be16(val, &tx[2]); + return spi_write_then_read(spi, tx, 4, NULL, 0); + default: + return -EINVAL; + } +} + +static bool ad4691_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case AD4691_STATUS_REG: + case AD4691_CLAMP_STATUS1_REG: + case AD4691_CLAMP_STATUS2_REG: + case AD4691_GPIO_READ: + case AD4691_ACC_STATUS_FULL1_REG ... AD4691_ACC_STATUS_SAT2_REG: + case AD4691_ACC_SAT_OVR_REG(0) ... AD4691_ACC_SAT_OVR_REG(15): + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15): + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15): + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15): + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15): + return true; + default: + return false; + } +} + +static bool ad4691_readable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_SPARE_CONTROL ... AD4691_ACC_SAT_OVR_REG(15): + case AD4691_STD_SEQ_CONFIG: + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15): + case AD4691_AVG_STS_IN(0) ... AD4691_AVG_STS_IN(15): + case AD4691_ACC_IN(0) ... AD4691_ACC_IN(15): + case AD4691_ACC_STS_DATA(0) ... AD4691_ACC_STS_DATA(15): + return true; + default: + return false; + } +} + +static bool ad4691_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_STD_SEQ_CONFIG: + case AD4691_SPARE_CONTROL ... AD4691_GPIO_MODE2_REG: + return true; + default: + return false; + } +} + +static const struct regmap_config ad4691_regmap_config =3D { + .reg_bits =3D 16, + .val_bits =3D 32, + .reg_read =3D ad4691_reg_read, + .reg_write =3D ad4691_reg_write, + .volatile_reg =3D ad4691_volatile_reg, + .readable_reg =3D ad4691_readable_reg, + .writeable_reg =3D ad4691_writeable_reg, + .max_register =3D AD4691_ACC_STS_DATA(15), + .cache_type =3D REGCACHE_MAPLE, +}; + +static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val) +{ + unsigned int reg_val; + int ret; + + ret =3D regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val); + if (ret) + return ret; + + *val =3D ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]; + return IIO_VAL_INT; +} + +static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int start =3D (st->info->max_rate =3D=3D HZ_PER_MHZ) ? 0 : 1; + unsigned int i; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + for (i =3D start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) { + if ((int)ad4691_osc_freqs[i] =3D=3D freq) + return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG, + AD4691_OSC_FREQ_MASK, i); + } + + return -EINVAL; +} + +static int ad4691_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int start =3D (st->info->max_rate =3D=3D HZ_PER_MHZ) ? 0 : 1; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals =3D (const int *)&ad4691_osc_freqs[start]; + *type =3D IIO_VAL_INT; + *length =3D ARRAY_SIZE(ad4691_osc_freqs) - start; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4691_single_shot_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + + /* + * Use AUTONOMOUS mode for single-shot reads. The chip always + * operates in AUTONOMOUS mode in this driver revision. + */ + ret =3D regmap_write(st->regmap, AD4691_STATE_RESET_REG, + AD4691_STATE_RESET_ALL); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + BIT(chan->channel)); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, + (u16)~BIT(chan->channel)); + if (ret) + return ret; + + ret =3D regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_OSC_EN_REG, 1); + if (ret) + return ret; + + /* + * Wait for at least 2 internal oscillator periods for the + * conversion to complete. + */ + fsleep(DIV_ROUND_UP(2 * USEC_PER_SEC, + ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, + reg_val)])); + + ret =3D regmap_write(st->regmap, AD4691_OSC_EN_REG, 0); + if (ret) + return ret; + + ret =3D regmap_read(st->regmap, AD4691_AVG_IN(chan->channel), ®_val); + if (ret) + return ret; + + *val =3D reg_val; + + ret =3D regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RES= ET_ALL); + if (ret) + return ret; + + return IIO_VAL_INT; +} + +static int ad4691_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long info) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + + switch (info) { + case IIO_CHAN_INFO_RAW: { + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + return ad4691_single_shot_read(indio_dev, chan, val); + } + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4691_get_sampling_freq(st, val); + case IIO_CHAN_INFO_SCALE: + *val =3D st->vref_uV / (MICRO / MILLI); + *val2 =3D 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 =3D 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 =3D { + .read_raw =3D &ad4691_read_raw, + .write_raw =3D &ad4691_write_raw, + .read_avail =3D &ad4691_read_avail, + .debugfs_reg_access =3D &ad4691_reg_access, +}; + +static int ad4691_regulator_setup(struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + int ret; + + ret =3D devm_regulator_get_enable(dev, "avdd"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable AVDD\n"); + + ret =3D devm_regulator_get_enable(dev, "ldo-in"); + if (ret && ret !=3D -ENODEV) + return dev_err_probe(dev, ret, "Failed to get and enable LDO-IN\n"); + st->ldo_en =3D (ret =3D=3D -ENODEV); + + ret =3D devm_regulator_get_enable(dev, "vio"); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable VIO\n"); + + st->vref_uV =3D devm_regulator_get_enable_read_voltage(dev, "ref"); + if (st->vref_uV >=3D 0) { + st->refbuf_en =3D false; + } else if (st->vref_uV =3D=3D -ENODEV) { + st->vref_uV =3D devm_regulator_get_enable_read_voltage(dev, "refin"); + st->refbuf_en =3D true; + } + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to get reference supply\n"); + + if (st->vref_uV < AD4691_VREF_uV_MIN || st->vref_uV > AD4691_VREF_uV_MAX) + return dev_err_probe(dev, -EINVAL, + "vref(%d) must be in the range [%u...%u]\n", + st->vref_uV, AD4691_VREF_uV_MIN, + AD4691_VREF_uV_MAX); + + return 0; +} + +static int ad4691_reset(struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct reset_control *rst; + + rst =3D devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(rst)) + return dev_err_probe(dev, PTR_ERR(rst), "Failed to get reset\n"); + + if (!rst) + /* No hardware reset available, fall back to software reset. */ + return regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG, + AD4691_SW_RESET); + + reset_control_assert(rst); + /* Reset delay required. See datasheet Table 5. */ + fsleep(300); + reset_control_deassert(rst); + + return 0; +} + +static int ad4691_config(struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + enum ad4691_ref_ctrl ref_val; + int ret; + + switch (st->vref_uV) { + case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX: + ref_val =3D AD4691_VREF_2P5; + break; + case AD4691_VREF_2P5_uV_MAX + 1 ... AD4691_VREF_3P0_uV_MAX: + ref_val =3D AD4691_VREF_3P0; + break; + case AD4691_VREF_3P0_uV_MAX + 1 ... AD4691_VREF_3P3_uV_MAX: + ref_val =3D AD4691_VREF_3P3; + break; + case AD4691_VREF_3P3_uV_MAX + 1 ... AD4691_VREF_4P096_uV_MAX: + ref_val =3D AD4691_VREF_4P096; + break; + case AD4691_VREF_4P096_uV_MAX + 1 ... AD4691_VREF_uV_MAX: + ref_val =3D AD4691_VREF_5P0; + break; + default: + return dev_err_probe(dev, -EINVAL, + "Unsupported vref voltage: %d uV\n", + st->vref_uV); + } + + ret =3D regmap_write(st->regmap, AD4691_REF_CTRL, + FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val) | + (st->refbuf_en ? AD4691_REFBUF_EN : 0)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n"); + + ret =3D regmap_write(st->regmap, AD4691_DEVICE_SETUP, + st->ldo_en ? AD4691_LDO_EN : 0); + if (ret) + return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n"); + + /* + * Set the internal oscillator to the highest valid rate for this chip. + * Index 0 (1 MHz) is valid only for AD4692/AD4694; AD4691/AD4693 start + * at index 1 (500 kHz). + */ + ret =3D regmap_write(st->regmap, AD4691_OSC_FREQ_REG, + (st->info->max_rate =3D=3D HZ_PER_MHZ) ? 0 : 1); + if (ret) + return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n"); + + /* Device always operates in AUTONOMOUS mode. */ + return regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_= VAL); +} + +static int ad4691_probe(struct spi_device *spi) +{ + struct device *dev =3D &spi->dev; + struct iio_dev *indio_dev; + struct ad4691_state *st; + int ret; + + indio_dev =3D devm_iio_device_alloc(&spi->dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st =3D iio_priv(indio_dev); + st->info =3D spi_get_device_match_data(spi); + + ret =3D devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + st->regmap =3D devm_regmap_init(dev, NULL, spi, &ad4691_regmap_config); + if (IS_ERR(st->regmap)) + return dev_err_probe(dev, PTR_ERR(st->regmap), + "Failed to initialize regmap\n"); + + ret =3D ad4691_regulator_setup(st); + if (ret) + return ret; + + ret =3D ad4691_reset(st); + if (ret) + return ret; + + ret =3D ad4691_config(st); + if (ret) + return ret; + + indio_dev->name =3D st->info->name; + indio_dev->info =3D &ad4691_info; + indio_dev->modes =3D INDIO_DIRECT_MODE; + + indio_dev->channels =3D st->info->channels; + indio_dev->num_channels =3D st->info->num_channels; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct of_device_id ad4691_of_match[] =3D { + { .compatible =3D "adi,ad4691", .data =3D &ad4691_chip_info }, + { .compatible =3D "adi,ad4692", .data =3D &ad4692_chip_info }, + { .compatible =3D "adi,ad4693", .data =3D &ad4693_chip_info }, + { .compatible =3D "adi,ad4694", .data =3D &ad4694_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(of, ad4691_of_match); + +static const struct spi_device_id ad4691_id[] =3D { + { "ad4691", (kernel_ulong_t)&ad4691_chip_info }, + { "ad4692", (kernel_ulong_t)&ad4692_chip_info }, + { "ad4693", (kernel_ulong_t)&ad4693_chip_info }, + { "ad4694", (kernel_ulong_t)&ad4694_chip_info }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad4691_id); + +static struct spi_driver ad4691_driver =3D { + .driver =3D { + .name =3D "ad4691", + .of_match_table =3D ad4691_of_match, + }, + .probe =3D ad4691_probe, + .id_table =3D ad4691_id, +}; +module_spi_driver(ad4691_driver); + +MODULE_AUTHOR("Radu Sabau "); +MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Mon Apr 6 09:20:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D53EB395DB5; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; cv=none; b=t9wLLaR/xA3rE337lQuCG9A17qlWgAd7/ER4/L7ofjqLoXIeuBA1wYUTuhxHZPmpAvrxdgua754uZU9WvpVMQWMEhn9fPxZX/Pq1ckWdL3RtAT0iQ8/K5qdkG3r1+A2UcQZtfD0O7PwXcpPSin2uwShin+N1LVsMnLtJpHtduSw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; c=relaxed/simple; bh=EwoCFBJ5cyStyDXFYeoZWtaltfXA3XNeJLdoeTIZRBI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=g5gqrHBWcYaZG8j8NBnQE+EGz/LeIAZ+2I3GgySchCMe6ZfO0AcylnG9YQj0X55mvhK+Om9MqHtW7DOwFcKJFHHnU5KS+gGnz61a9KdlSTl1kbw1T9e6dL/aPG85lFNQsNy5KcuLnoJRlYivd8hZPtmhdxrdIpm8gW8cAIBt8cA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=EXoou8fk; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="EXoou8fk" Received: by smtp.kernel.org (Postfix) with ESMTPS id 96662C2BCB5; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774004639; bh=EwoCFBJ5cyStyDXFYeoZWtaltfXA3XNeJLdoeTIZRBI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=EXoou8fkA7iol83YT61O3sWOseOqCg1NHIRcgj15SefjqM9OA7vd77E5DNWr0SijU 5WtfgYePPghXNnVeCDRUaESvUSVskDhZ3Jn/2Zj9odJML/PBTYmO6GWbkJQHz+2faU wtTUYmU4YWfnXByw+sENk5JEF5kYBq9HTpDtcjkY5hkkBPZIHd+9iVkdLPGk64z1b0 +ggNphKgopNSLz9tXIhn2bn0YsVLYGdQ9Rt91cEpSNGFJDun1dGq4t/SKSUf8a7SUF F1eH0C2pm5AaVxycY7Xbnqc/R8hm6X+bMvoWtKqqex2JSjMk2fhLKMWOGubxL8YNZc slCAePUS9YLog== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8B9E9108B8F5; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Fri, 20 Mar 2026 13:03:57 +0200 Subject: [PATCH v4 3/4] iio: adc: ad4691: add triggered buffer support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260320-ad4692-multichannel-sar-adc-driver-v4-3-052c1050507a@analog.com> References: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> In-Reply-To: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> To: Lars-Peter Clausen , Michael Hennerich , Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Liam Girdwood , Mark Brown , Linus Walleij , Bartosz Golaszewski , Philipp Zabel Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774004636; l=25341; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=V0ridDLzIVUpiT+dRc8jjxqO3UbSh6vnB8zfwRF82rk=; b=mlRqH4z1AlC7JgVKpEEfEjH9VVob+TElgMOV+JrnRvWEpMrfs3TpQCLNp0ees3DzRvqP7Bvsj dusr2RBADfvBXUJo9mFtkCoA3pHmXMQ55I74c7uxNOjYh+bNPMVLRpZ X-Developer-Key: i=radu.sabau@analog.com; a=ed25519; pk=lDPQHgn9jTdt0vo58Na9lLxLaE2mb330if71Cn+EvFU= X-Endpoint-Received: by B4 Relay for radu.sabau@analog.com/20260220 with auth_id=642 X-Original-From: Radu Sabau Reply-To: radu.sabau@analog.com From: Radu Sabau Add buffered capture support using the IIO triggered buffer framework. CNV Burst Mode: the GP pin identified by interrupt-names in the device tree is configured as DATA_READY output. The IRQ handler stops conversions and fires the IIO trigger; the trigger handler executes a pre-built SPI message that reads all active channels from the AVG_IN accumulator registers and then resets accumulator state and restarts conversions for the next cycle. Manual Mode: CNV is tied to SPI CS so each transfer simultaneously reads the previous result and starts the next conversion (pipelined N+1 scheme). At preenable time a pre-built, optimised SPI message of N+1 transfers is constructed (N channel reads plus one NOOP to drain the pipeline). The trigger handler executes the message in a single spi_sync() call and collects the results. An external trigger (e.g. iio-trig-hrtimer) is required to drive the trigger at the desired sample rate. Both modes share the same trigger handler and push a complete scan =E2=80= =94 one u16 slot per channel at its scan_index position, followed by a timestamp =E2=80=94 to the IIO buffer via iio_push_to_buffers_with_ts(). The CNV Burst Mode sampling frequency (PWM period) is exposed as a buffer-level attribute via IIO_DEVICE_ATTR. Signed-off-by: Radu Sabau --- drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/ad4691.c | 584 +++++++++++++++++++++++++++++++++++++++++++= ++-- 2 files changed, 571 insertions(+), 15 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 5e02eb44ca44..db776de32846 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -9,9 +9,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include #include @@ -19,7 +22,12 @@ #include #include =20 +#include #include +#include +#include +#include +#include =20 #define AD4691_VREF_uV_MIN 2400000 #define AD4691_VREF_uV_MAX 5250000 @@ -28,6 +36,8 @@ #define AD4691_VREF_3P3_uV_MAX 3750000 #define AD4691_VREF_4P096_uV_MAX 4500000 =20 +#define AD4691_CNV_DUTY_CYCLE_NS 380 + #define AD4691_SPI_CONFIG_A_REG 0x000 #define AD4691_SW_RESET (BIT(7) | BIT(0)) =20 @@ -35,6 +45,7 @@ #define AD4691_CLAMP_STATUS1_REG 0x01A #define AD4691_CLAMP_STATUS2_REG 0x01B #define AD4691_DEVICE_SETUP 0x020 +#define AD4691_MANUAL_MODE BIT(2) #define AD4691_LDO_EN BIT(4) #define AD4691_REF_CTRL 0x021 #define AD4691_REF_CTRL_MASK GENMASK(4, 2) @@ -42,21 +53,29 @@ #define AD4691_OSC_FREQ_REG 0x023 #define AD4691_OSC_FREQ_MASK GENMASK(3, 0) #define AD4691_STD_SEQ_CONFIG 0x025 +#define AD4691_SEQ_ALL_CHANNELS_OFF 0x00 #define AD4691_SPARE_CONTROL 0x02A =20 +#define AD4691_NOOP 0x00 +#define AD4691_ADC_CHAN(ch) ((0x10 + (ch)) << 3) + #define AD4691_OSC_EN_REG 0x180 #define AD4691_STATE_RESET_REG 0x181 #define AD4691_STATE_RESET_ALL 0x01 #define AD4691_ADC_SETUP 0x182 -#define AD4691_AUTONOMOUS_MODE_VAL 0x02 +#define AD4691_CNV_BURST_MODE 0x01 +#define AD4691_AUTONOMOUS_MODE 0x02 /* * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte. */ #define AD4691_ACC_MASK_REG 0x185 #define AD4691_ACC_COUNT_LIMIT(n) (0x186 + (n)) +#define AD4691_ACC_COUNT_VAL 0x1 #define AD4691_GPIO_MODE1_REG 0x196 #define AD4691_GPIO_MODE2_REG 0x197 +#define AD4691_GP_MODE_MASK GENMASK(3, 0) +#define AD4691_GP_MODE_DATA_READY 0x06 #define AD4691_GPIO_READ 0x1A0 #define AD4691_ACC_STATUS_FULL1_REG 0x1B0 #define AD4691_ACC_STATUS_FULL2_REG 0x1B1 @@ -121,6 +140,7 @@ static const struct iio_chan_spec ad4691_channels[] =3D= { AD4691_CHANNEL(13), AD4691_CHANNEL(14), AD4691_CHANNEL(15), + IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 static const struct iio_chan_spec ad4693_channels[] =3D { @@ -132,6 +152,7 @@ static const struct iio_chan_spec ad4693_channels[] =3D= { AD4691_CHANNEL(5), AD4691_CHANNEL(6), AD4691_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 /* @@ -189,16 +210,63 @@ static const struct ad4691_chip_info ad4694_chip_info= =3D { struct ad4691_state { const struct ad4691_chip_info *info; struct regmap *regmap; + + struct pwm_device *conv_trigger; + struct iio_trigger *trig; + int irq; + + bool manual_mode; + int vref_uV; bool refbuf_en; bool ldo_en; + u32 cnv_period_ns; /* * Synchronize access to members of the driver state, and ensure * atomicity of consecutive SPI operations. */ struct mutex lock; + /* + * Per-buffer-enabl ree lifetimesources: + * Manual Mode - a pre-built SPI message that clocks out N+1 + * transfers in one go. + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N + * transfers in one go. + */ + void *scan_devm_group; + struct spi_message scan_msg; + struct spi_transfer *scan_xfers; + __be16 *scan_tx; + __be16 *scan_rx; + /* Scan buffer: one slot per channel (u16) plus timestamp */ + struct { + u16 vals[16]; + s64 ts __aligned(8); + } scan __aligned(IIO_DMA_MINALIGN); }; =20 +/* + * Configure the given GP pin (0-3) as DATA_READY output. + * GP0/GP1 =E2=86=92 GPIO_MODE1_REG, GP2/GP3 =E2=86=92 GPIO_MODE2_REG. + * Even pins occupy bits [3:0], odd pins bits [7:4]. + */ +static int ad4691_gpio_setup(struct ad4691_state *st, unsigned int gp_num) +{ + unsigned int shift =3D 4 * (gp_num % 2); + + return regmap_update_bits(st->regmap, + AD4691_GPIO_MODE1_REG + gp_num / 2, + AD4691_GP_MODE_MASK << shift, + AD4691_GP_MODE_DATA_READY << shift); +} + +static void ad4691_disable_pwm(void *data) +{ + struct pwm_state state =3D { .enabled =3D false }; + + pwm_apply_might_sleep(data, &state); +} + static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *= val) { struct spi_device *spi =3D context; @@ -341,14 +409,16 @@ static int ad4691_get_sampling_freq(struct ad4691_sta= te *st, int *val) static int ad4691_set_sampling_freq(struct iio_dev *indio_dev, int freq) { struct ad4691_state *st =3D iio_priv(indio_dev); - unsigned int start =3D (st->info->max_rate =3D=3D HZ_PER_MHZ) ? 0 : 1; unsigned int i; =20 + if (freq > st->info->max_rate) + return -EINVAL; + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; =20 - for (i =3D start; i < ARRAY_SIZE(ad4691_osc_freqs); i++) { + for (i =3D 0; i < ARRAY_SIZE(ad4691_osc_freqs); i++) { if ((int)ad4691_osc_freqs[i] =3D=3D freq) return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG, AD4691_OSC_FREQ_MASK, i); @@ -363,7 +433,10 @@ static int ad4691_read_avail(struct iio_dev *indio_dev, int *length, long mask) { struct ad4691_state *st =3D iio_priv(indio_dev); - unsigned int start =3D (st->info->max_rate =3D=3D HZ_PER_MHZ) ? 0 : 1; + unsigned int start; + + /* Skip frequencies that exceed this chip's maximum rate. */ + start =3D (ad4691_osc_freqs[0] > st->info->max_rate) ? 1 : 0; =20 switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: @@ -386,8 +459,7 @@ static int ad4691_single_shot_read(struct iio_dev *indi= o_dev, guard(mutex)(&st->lock); =20 /* - * Use AUTONOMOUS mode for single-shot reads. The chip always - * operates in AUTONOMOUS mode in this driver revision. + * Use AUTONOMOUS mode for single-shot reads. */ ret =3D regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RESET_ALL); @@ -417,8 +489,7 @@ static int ad4691_single_shot_read(struct iio_dev *indi= o_dev, * conversion to complete. */ fsleep(DIV_ROUND_UP(2 * USEC_PER_SEC, - ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, - reg_val)])); + ad4691_osc_freqs[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)])); =20 ret =3D regmap_write(st->regmap, AD4691_OSC_EN_REG, 0); if (ret) @@ -488,6 +559,374 @@ static int ad4691_reg_access(struct iio_dev *indio_de= v, unsigned int reg, return regmap_write(st->regmap, reg, writeval); } =20 +static int ad4691_set_pwm_freq(struct ad4691_state *st, int freq) +{ + if (!freq) + return -EINVAL; + + st->cnv_period_ns =3D DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq); + return 0; +} + +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable) +{ + struct pwm_state conv_state =3D { }; + + conv_state.period =3D st->cnv_period_ns; + conv_state.duty_cycle =3D AD4691_CNV_DUTY_CYCLE_NS; + conv_state.polarity =3D PWM_POLARITY_NORMAL; + conv_state.enabled =3D enable; + + return pwm_apply_might_sleep(st->conv_trigger, &conv_state); +} + +/* + * ad4691_enter_conversion_mode - Switch the chip to its buffer conversion= mode. + * + * Configures the ADC hardware registers for the mode selected at probe + * (CNV_BURST or MANUAL). Called from buffer preenable before starting + * sampling. The chip is in AUTONOMOUS mode during idle (for read_raw). + */ +static int ad4691_enter_conversion_mode(struct ad4691_state *st) +{ + int ret; + + if (st->manual_mode) + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_MANUAL_MODE, AD4691_MANUAL_MODE); + + ret =3D regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_CNV_BURST_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4691_STATE_RESET_REG, + AD4691_STATE_RESET_ALL); +} + +/* + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode. + * + * Called from buffer postdisable to restore the chip to the + * idle state used by read_raw. Clears the sequencer and resets state. + */ +static int ad4691_exit_conversion_mode(struct ad4691_state *st) +{ + if (st->manual_mode) + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_MANUAL_MODE, 0); + + return regmap_write(st->regmap, AD4691_ADC_SETUP, + AD4691_AUTONOMOUS_MODE); +} + +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + struct spi_device *spi =3D to_spi_device(dev); + unsigned int n_active =3D hweight_long(*indio_dev->active_scan_mask); + unsigned int n_xfers =3D n_active + 1; + unsigned int k, i; + int ret; + + st->scan_devm_group =3D devres_open_group(dev, NULL, GFP_KERNEL); + if (!st->scan_devm_group) + return -ENOMEM; + + st->scan_xfers =3D devm_kcalloc(dev, n_xfers, sizeof(*st->scan_xfers), + GFP_KERNEL); + st->scan_tx =3D devm_kcalloc(dev, n_xfers, sizeof(*st->scan_tx), + GFP_KERNEL); + st->scan_rx =3D devm_kcalloc(dev, n_xfers, sizeof(*st->scan_rx), + GFP_KERNEL); + if (!st->scan_xfers || !st->scan_tx || !st->scan_rx) { + devres_release_group(dev, st->scan_devm_group); + return -ENOMEM; + } + + spi_message_init(&st->scan_msg); + + k =3D 0; + iio_for_each_active_channel(indio_dev, i) { + st->scan_tx[k] =3D cpu_to_be16(AD4691_ADC_CHAN(i)); + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[k].rx_buf =3D &st->scan_rx[k]; + st->scan_xfers[k].len =3D sizeof(__be16); + st->scan_xfers[k].cs_change =3D 1; + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + k++; + } + + /* Final NOOP transfer to retrieve last channel's result. */ + st->scan_tx[k] =3D cpu_to_be16(AD4691_NOOP); + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[k].rx_buf =3D &st->scan_rx[k]; + st->scan_xfers[k].len =3D sizeof(__be16); + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + + devres_close_group(dev, st->scan_devm_group); + + st->scan_msg.spi =3D spi; + + ret =3D spi_optimize_message(spi, &st->scan_msg); + if (ret) { + devres_release_group(dev, st->scan_devm_group); + return ret; + } + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) { + spi_unoptimize_message(&st->scan_msg); + devres_release_group(dev, st->scan_devm_group); + return ret; + } + + return 0; +} + +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + int ret; + + ret =3D ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + devres_release_group(dev, st->scan_devm_group); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_manual_buffer_setup_ops = =3D { + .preenable =3D &ad4691_manual_buffer_preenable, + .postdisable =3D &ad4691_manual_buffer_postdisable, +}; + +static int ad4691_cnv_burst_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + struct spi_device *spi =3D to_spi_device(dev); + unsigned int n_active =3D hweight_long(*indio_dev->active_scan_mask); + unsigned int bit, k, i; + int ret; + + st->scan_devm_group =3D devres_open_group(dev, NULL, GFP_KERNEL); + if (!st->scan_devm_group) + return -ENOMEM; + + st->scan_xfers =3D devm_kcalloc(dev, 2 * n_active, sizeof(*st->scan_xfers= ), + GFP_KERNEL); + st->scan_tx =3D devm_kcalloc(dev, n_active, sizeof(*st->scan_tx), + GFP_KERNEL); + st->scan_rx =3D devm_kcalloc(dev, n_active, sizeof(*st->scan_rx), + GFP_KERNEL); + if (!st->scan_xfers || !st->scan_tx || !st->scan_rx) { + devres_release_group(dev, st->scan_devm_group); + return -ENOMEM; + } + + spi_message_init(&st->scan_msg); + + /* + * Each AVG_IN read needs two transfers: a 2-byte address write phase + * followed by a 2-byte data read phase. CS toggles between channels + * (cs_change=3D1 on the read phase of all but the last channel). + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, i) { + st->scan_tx[k] =3D cpu_to_be16(0x8000 | AD4691_AVG_IN(i)); + st->scan_xfers[2 * k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[2 * k].len =3D sizeof(__be16); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + st->scan_xfers[2 * k + 1].rx_buf =3D &st->scan_rx[k]; + st->scan_xfers[2 * k + 1].len =3D sizeof(__be16); + if (k < n_active - 1) + st->scan_xfers[2 * k + 1].cs_change =3D 1; + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg); + k++; + } + + devres_close_group(dev, st->scan_devm_group); + + st->scan_msg.spi =3D spi; + + ret =3D spi_optimize_message(spi, &st->scan_msg); + if (ret) { + devres_release_group(dev, st->scan_devm_group); + return ret; + } + + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, + (u16)~(*indio_dev->active_scan_mask)); + if (ret) + goto err; + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + *indio_dev->active_scan_mask); + if (ret) + goto err; + + iio_for_each_active_channel(indio_dev, bit) { + ret =3D regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit), + AD4691_ACC_COUNT_VAL); + if (ret) + goto err; + } + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + goto err; + + ret =3D ad4691_sampling_enable(st, true); + if (ret) + goto err; + + enable_irq(st->irq); + return 0; +err: + spi_unoptimize_message(&st->scan_msg); + devres_release_group(dev, st->scan_devm_group); + return ret; +} + +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + int ret; + + disable_irq(st->irq); + + ret =3D ad4691_sampling_enable(st, false); + if (ret) + return ret; + + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, + AD4691_SEQ_ALL_CHANNELS_OFF); + if (ret) + return ret; + + ret =3D ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + devres_release_group(dev, st->scan_devm_group); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops= =3D { + .preenable =3D &ad4691_cnv_burst_buffer_preenable, + .postdisable =3D &ad4691_cnv_burst_buffer_postdisable, +}; + +static ssize_t sampling_frequency_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct ad4691_state *st =3D iio_priv(indio_dev); + + if (st->manual_mode) + return -ENODEV; + + return sysfs_emit(buf, "%u\n", (u32)(NSEC_PER_SEC / st->cnv_period_ns)); +} + +static ssize_t sampling_frequency_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); + struct ad4691_state *st =3D iio_priv(indio_dev); + int freq, ret; + + if (st->manual_mode) + return -ENODEV; + + ret =3D kstrtoint(buf, 10, &freq); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + ret =3D ad4691_set_pwm_freq(st, freq); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR(sampling_frequency, 0644, + sampling_frequency_show, + sampling_frequency_store, 0); + +static const struct iio_dev_attr *ad4691_buffer_attrs[] =3D { + &iio_dev_attr_sampling_frequency, + NULL, +}; + +static irqreturn_t ad4691_irq(int irq, void *private) +{ + struct iio_dev *indio_dev =3D private; + struct ad4691_state *st =3D iio_priv(indio_dev); + + /* + * GPx has asserted: stop conversions before reading so the + * accumulator does not continue sampling while the trigger handler + * processes the data. Then fire the IIO trigger to push the sample + * to the buffer. + */ + ad4691_sampling_enable(st, false); + iio_trigger_poll(st->trig); + + return IRQ_HANDLED; +} + +static const struct iio_trigger_ops ad4691_trigger_ops =3D { + .validate_device =3D iio_trigger_validate_own_device, +}; + +static irqreturn_t ad4691_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf =3D p; + struct iio_dev *indio_dev =3D pf->indio_dev; + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int i, k =3D 0; + int ret; + + guard(mutex)(&st->lock); + + ret =3D spi_sync(st->scan_msg.spi, &st->scan_msg); + if (ret) + goto done; + + if (st->manual_mode) { + iio_for_each_active_channel(indio_dev, i) { + st->scan.vals[i] =3D be16_to_cpu(st->scan_rx[k + 1]); + k++; + } + } else { + iio_for_each_active_channel(indio_dev, i) { + st->scan.vals[i] =3D be16_to_cpu(st->scan_rx[k]); + k++; + } + + ret =3D regmap_write(st->regmap, AD4691_STATE_RESET_REG, + AD4691_STATE_RESET_ALL); + if (ret) + goto done; + + ret =3D ad4691_sampling_enable(st, true); + if (ret) + goto done; + } + + iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan), + pf->timestamp); + +done: + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + static const struct iio_info ad4691_info =3D { .read_raw =3D &ad4691_read_raw, .write_raw =3D &ad4691_write_raw, @@ -495,6 +934,25 @@ static const struct iio_info ad4691_info =3D { .debugfs_reg_access =3D &ad4691_reg_access, }; =20 +static int ad4691_pwm_setup(struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + int ret; + + st->conv_trigger =3D devm_pwm_get(dev, "cnv"); + if (IS_ERR(st->conv_trigger)) + return dev_err_probe(dev, PTR_ERR(st->conv_trigger), + "Failed to get cnv pwm\n"); + + ret =3D devm_add_action_or_reset(dev, ad4691_disable_pwm, + st->conv_trigger); + if (ret) + return dev_err_probe(dev, ret, + "Failed to register PWM disable action\n"); + + return ad4691_set_pwm_freq(st, st->info->max_rate); +} + static int ad4691_regulator_setup(struct ad4691_state *st) { struct device *dev =3D regmap_get_device(st->regmap); @@ -555,12 +1013,30 @@ static int ad4691_reset(struct ad4691_state *st) return 0; } =20 -static int ad4691_config(struct ad4691_state *st) +static int ad4691_config(struct ad4691_state *st, u32 max_speed_hz) { struct device *dev =3D regmap_get_device(st->regmap); enum ad4691_ref_ctrl ref_val; + const char *irq_name; + unsigned int gp_num; int ret; =20 + /* + * Determine buffer conversion mode from DT: if a PWM is provided it + * drives the CNV pin (CNV_BURST_MODE); otherwise CNV is tied to CS + * and each SPI transfer triggers a conversion (MANUAL_MODE). + * Both modes idle in AUTONOMOUS mode so that read_raw can use the + * internal oscillator without disturbing the hardware configuration. + */ + if (device_property_present(dev, "pwms")) { + st->manual_mode =3D false; + ret =3D ad4691_pwm_setup(st); + if (ret) + return ret; + } else { + st->manual_mode =3D true; + } + switch (st->vref_uV) { case AD4691_VREF_uV_MIN ... AD4691_VREF_2P5_uV_MAX: ref_val =3D AD4691_VREF_2P5; @@ -595,17 +1071,91 @@ static int ad4691_config(struct ad4691_state *st) return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n"); =20 /* - * Set the internal oscillator to the highest valid rate for this chip. - * Index 0 (1 MHz) is valid only for AD4692/AD4694; AD4691/AD4693 start - * at index 1 (500 kHz). + * Set the internal oscillator to the highest rate this chip supports. + * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those + * chips start at index 1 (500 kHz). */ ret =3D regmap_write(st->regmap, AD4691_OSC_FREQ_REG, - (st->info->max_rate =3D=3D HZ_PER_MHZ) ? 0 : 1); + (ad4691_osc_freqs[0] > st->info->max_rate) ? 1 : 0); if (ret) return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n"); =20 /* Device always operates in AUTONOMOUS mode. */ - return regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE_= VAL); + ret =3D regmap_write(st->regmap, AD4691_ADC_SETUP, AD4691_AUTONOMOUS_MODE= ); + if (ret) + return dev_err_probe(dev, ret, "Failed to write ADC_SETUp\n"); + + if (st->manual_mode) + return 0; + + ret =3D device_property_read_string_array(dev, "interrupt-names", + &irq_name, 1); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to read interrupt-names\n"); + + if (strncmp(irq_name, "gp", 2) !=3D 0 || + kstrtouint(irq_name + 2, 10, &gp_num) || gp_num > 3) + return dev_err_probe(dev, -EINVAL, + "Invalid interrupt name '%s'\n", irq_name); + + return ad4691_gpio_setup(st, gp_num); +} + +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev, + struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + int irq, ret; + + st->trig =3D devm_iio_trigger_alloc(dev, "%s-dev%d", + indio_dev->name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + + st->trig->ops =3D &ad4691_trigger_ops; + iio_trigger_set_drvdata(st->trig, st); + + ret =3D devm_iio_trigger_register(dev, st->trig); + if (ret) + return dev_err_probe(dev, ret, "IIO trigger register failed\n"); + + indio_dev->trig =3D iio_trigger_get(st->trig); + + if (!st->manual_mode) { + /* + * GP0 asserts at end-of-conversion. The IRQ handler stops + * conversions and fires the IIO trigger so the trigger handler + * can read and push the sample to the buffer. The IRQ is kept + * disabled until the buffer is enabled. + */ + irq =3D fwnode_irq_get(dev_fwnode(dev), 0); + if (irq < 0) + return dev_err_probe(dev, irq, + "failed to get GP interrupt\n"); + + st->irq =3D irq; + + ret =3D devm_request_threaded_irq(dev, irq, NULL, + &ad4691_irq, + IRQF_ONESHOT | IRQF_NO_AUTOEN, + indio_dev->name, indio_dev); + if (ret) + return ret; + + return devm_iio_triggered_buffer_setup_ext(dev, indio_dev, + &iio_pollfunc_store_time, + &ad4691_trigger_handler, + IIO_BUFFER_DIRECTION_IN, + &ad4691_cnv_burst_buffer_setup_ops, + ad4691_buffer_attrs); + } + + return devm_iio_triggered_buffer_setup(dev, indio_dev, + &iio_pollfunc_store_time, + &ad4691_trigger_handler, + &ad4691_manual_buffer_setup_ops); } =20 static int ad4691_probe(struct spi_device *spi) @@ -639,7 +1189,7 @@ static int ad4691_probe(struct spi_device *spi) if (ret) return ret; =20 - ret =3D ad4691_config(st); + ret =3D ad4691_config(st, spi->max_speed_hz); if (ret) return ret; =20 @@ -650,6 +1200,10 @@ static int ad4691_probe(struct spi_device *spi) indio_dev->channels =3D st->info->channels; indio_dev->num_channels =3D st->info->num_channels; =20 + ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } =20 --=20 2.43.0 From nobody Mon Apr 6 09:20:35 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id D8257396D1E; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; cv=none; b=MXxjsc8D2YpcslVdHGDl2cj7qN6AAdSfL8u2sbWMHG9xbl73bge/UYIHeWxE+uH+kX8WKdyKCHxuUPq8FjRw0H+BghDgkJMprD77QsxgATHxWebI8GoG9ZpD9XRxbGJBFZq4uPvrmqZdKlahN4NNHbFJbiURZH+Wx2HBVtRZOmA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1774004639; c=relaxed/simple; bh=d0/ZPw94aFPTAExgWJwi5bY5yasqhO+dLDXzto1Lo0I=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=UNWTHGioKJgZ2cF6aClFdRrwIHBFq1fzJgI7zs5uJj05iJlccZW8jp+Kix73zFkdUbK/98yZFOwCq9ZK4LQIUSKq3z9uCwPDDb31Kuir8xXnvmQglnOwiCNkbds16Sg71Nh3Dq9Bhvk2xTm2IuvU/+GSJ+BQ2XykxBvI4fo54a4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=uO1t5oGH; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="uO1t5oGH" Received: by smtp.kernel.org (Postfix) with ESMTPS id A686EC2BCC9; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1774004639; bh=d0/ZPw94aFPTAExgWJwi5bY5yasqhO+dLDXzto1Lo0I=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=uO1t5oGHTLwEEpWQTiqm5IgBe2Wm6sxyN09lfz/SccPgTzXAM+teVqBI2Zum64m6l z/+/o8PgslLWDCVP2+ju5YgiWRFblvtnpU+UeiDNzTj5oJdpAgbAPBNdu1MjFzmh9Y kCpzRSRnHHEo4rwCFfwe8GgyTbziMDhYjIkhbgk4Ql/vr1IHm92KQ541gcYX5dlcio /Pv/qno0BlHX7FszpzY3/29FoBNokDZfMDjA7z+Ine+cF3f0Ingc6fH7DXJzhFEMug GXAF/3gK0r2fSX67LDmMN/RbO1SSbnZlWtjAIbgJkJn+K6GKWyIM2G/Fh4Rilflh3Z UBIxFUowS3DqA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 9CA5C108B8F3; Fri, 20 Mar 2026 11:03:59 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Fri, 20 Mar 2026 13:03:58 +0200 Subject: [PATCH v4 4/4] iio: adc: ad4691: add SPI offload support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260320-ad4692-multichannel-sar-adc-driver-v4-4-052c1050507a@analog.com> References: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> In-Reply-To: <20260320-ad4692-multichannel-sar-adc-driver-v4-0-052c1050507a@analog.com> To: Lars-Peter Clausen , Michael Hennerich , Jonathan Cameron , David Lechner , =?utf-8?q?Nuno_S=C3=A1?= , Andy Shevchenko , Rob Herring , Krzysztof Kozlowski , Conor Dooley , =?utf-8?q?Uwe_Kleine-K=C3=B6nig?= , Liam Girdwood , Mark Brown , Linus Walleij , Bartosz Golaszewski , Philipp Zabel Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1774004636; l=23384; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=CcNob1GhOWpVfV4i05s0F2XBUXGCckdQbVMCSNH+1Go=; b=SU5pnU+A0aTeL95IAt11cS9Hto61a/gS2bAyFQPWD9pTz9yx/9zTQKalqarf/6KK2pfIE3613 eqtsffDXqGaC/EHonsjqb0Y7nnTyMxvAfNW06j5iD8S9LbvtQkBh6YV X-Developer-Key: i=radu.sabau@analog.com; a=ed25519; pk=lDPQHgn9jTdt0vo58Na9lLxLaE2mb330if71Cn+EvFU= X-Endpoint-Received: by B4 Relay for radu.sabau@analog.com/20260220 with auth_id=642 X-Original-From: Radu Sabau Reply-To: radu.sabau@analog.com From: Radu Sabau Add SPI offload support to enable DMA-based, CPU-independent data acquisition using the SPI Engine offload framework. When an SPI offload is available (devm_spi_offload_get() succeeds), the driver registers a DMA engine IIO buffer and uses dedicated buffer setup operations. If no offload is available the existing software triggered buffer path is used unchanged. Both CNV Burst Mode and Manual Mode support offload, but use different trigger mechanisms: CNV Burst Mode: the SPI Engine is triggered by the ADC's DATA_READY signal on the GP pin specified by the trigger-source consumer reference in the device tree (one cell =3D GP pin number 0-3). For this mode the driver acts as both an SPI offload consumer (DMA RX stream, message optimization) and a trigger source provider: it registers the GP/DATA_READY output via devm_spi_offload_trigger_register() so the offload framework can match the '#trigger-source-cells' phandle and automatically fire the SPI Engine DMA transfer at end-of-conversion. Manual Mode: the SPI Engine is triggered by a periodic trigger at the configured sampling frequency. The pre-built SPI message uses the pipelined CNV-on-CS protocol: N+1 4-byte transfers are issued for N active channels (the first result is discarded as garbage from the pipeline flush) and the remaining N results are captured by DMA. All offload transfers use 32-bit frames (bits_per_word=3D32, len=3D4) for DMA word alignment. This patch promotes the channel scan_type from storagebits=3D16 (triggered-buffer path) to storagebits=3D32 to match the DMA word size; the triggered-buffer paths are updated to the same layout for consistency. CNV Burst Mode channel data arrives in the lower 16 bits of the 32-bit word (shift=3D0); Manual Mode data arrives in the upper 16 bits (shift=3D16), matching the 4-byte SPI transfer layout [data_hi, data_lo, 0, 0]. A separate ad4691_manual_channels[] array encodes the shift=3D16 scan type for manual mode. Kconfig gains a dependency on IIO_BUFFER_DMAENGINE. Signed-off-by: Radu Sabau --- drivers/iio/adc/Kconfig | 1 + drivers/iio/adc/ad4691.c | 470 +++++++++++++++++++++++++++++++++++++++++++= ---- 2 files changed, 435 insertions(+), 36 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 db776de32846..5e0fe993c17d 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -19,10 +20,14 @@ #include #include #include +#include +#include #include #include =20 #include +#include +#include #include #include #include @@ -37,6 +42,7 @@ #define AD4691_VREF_4P096_uV_MAX 4500000 =20 #define AD4691_CNV_DUTY_CYCLE_NS 380 +#define AD4691_CNV_HIGH_TIME_NS 430 =20 #define AD4691_SPI_CONFIG_A_REG 0x000 #define AD4691_SW_RESET (BIT(7) | BIT(0)) @@ -89,6 +95,12 @@ #define AD4691_ACC_IN(n) (0x252 + (3 * (n))) #define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n))) =20 +/* SPI offload 32-bit word field masks (transmitted MSB first) */ +#define AD4691_OFFLOAD_BITS_PER_WORD 32 +#define AD4691_MSG_ADDR_HI GENMASK(31, 24) +#define AD4691_MSG_ADDR_LO GENMASK(23, 16) +#define AD4691_MSG_DATA GENMASK(15, 8) + enum ad4691_ref_ctrl { AD4691_VREF_2P5 =3D 0, AD4691_VREF_3P0 =3D 1, @@ -99,12 +111,22 @@ enum ad4691_ref_ctrl { =20 struct ad4691_chip_info { const struct iio_chan_spec *channels; + const struct iio_chan_spec *manual_channels; const char *name; unsigned int num_channels; unsigned int max_rate; }; =20 -#define AD4691_CHANNEL(ch) \ +/* + * 16-bit ADC data is stored in 32-bit slots to match the SPI offload DMA + * word size (32 bits per transfer). The shift reflects the data position + * within the 32-bit word: + * CNV_BURST: RX =3D [dummy, dummy, data_hi, data_lo] -> shift =3D 0 + * MANUAL: RX =3D [data_hi, data_lo, dummy, dummy] -> shift =3D 16 + * The triggered-buffer paths store data in the same position for consiste= ncy. + * Do not "fix" storagebits to 16. + */ +#define AD4691_CHANNEL(ch, _shift) \ { \ .type =3D IIO_VOLTAGE, \ .indexed =3D 1, \ @@ -118,40 +140,72 @@ struct ad4691_chip_info { .scan_type =3D { \ .sign =3D 'u', \ .realbits =3D 16, \ - .storagebits =3D 16, \ - .shift =3D 0, \ + .storagebits =3D 32, \ + .shift =3D _shift, \ }, \ } =20 static const struct iio_chan_spec ad4691_channels[] =3D { - AD4691_CHANNEL(0), - AD4691_CHANNEL(1), - AD4691_CHANNEL(2), - AD4691_CHANNEL(3), - AD4691_CHANNEL(4), - AD4691_CHANNEL(5), - AD4691_CHANNEL(6), - AD4691_CHANNEL(7), - AD4691_CHANNEL(8), - AD4691_CHANNEL(9), - AD4691_CHANNEL(10), - AD4691_CHANNEL(11), - AD4691_CHANNEL(12), - AD4691_CHANNEL(13), - AD4691_CHANNEL(14), - AD4691_CHANNEL(15), + AD4691_CHANNEL(0, 0), + AD4691_CHANNEL(1, 0), + AD4691_CHANNEL(2, 0), + AD4691_CHANNEL(3, 0), + AD4691_CHANNEL(4, 0), + AD4691_CHANNEL(5, 0), + AD4691_CHANNEL(6, 0), + AD4691_CHANNEL(7, 0), + AD4691_CHANNEL(8, 0), + AD4691_CHANNEL(9, 0), + AD4691_CHANNEL(10, 0), + AD4691_CHANNEL(11, 0), + AD4691_CHANNEL(12, 0), + AD4691_CHANNEL(13, 0), + AD4691_CHANNEL(14, 0), + AD4691_CHANNEL(15, 0), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4691_manual_channels[] =3D { + AD4691_CHANNEL(0, 16), + AD4691_CHANNEL(1, 16), + AD4691_CHANNEL(2, 16), + AD4691_CHANNEL(3, 16), + AD4691_CHANNEL(4, 16), + AD4691_CHANNEL(5, 16), + AD4691_CHANNEL(6, 16), + AD4691_CHANNEL(7, 16), + AD4691_CHANNEL(8, 16), + AD4691_CHANNEL(9, 16), + AD4691_CHANNEL(10, 16), + AD4691_CHANNEL(11, 16), + AD4691_CHANNEL(12, 16), + AD4691_CHANNEL(13, 16), + AD4691_CHANNEL(14, 16), + AD4691_CHANNEL(15, 16), IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 static const struct iio_chan_spec ad4693_channels[] =3D { - AD4691_CHANNEL(0), - AD4691_CHANNEL(1), - AD4691_CHANNEL(2), - AD4691_CHANNEL(3), - AD4691_CHANNEL(4), - AD4691_CHANNEL(5), - AD4691_CHANNEL(6), - AD4691_CHANNEL(7), + AD4691_CHANNEL(0, 0), + AD4691_CHANNEL(1, 0), + AD4691_CHANNEL(2, 0), + AD4691_CHANNEL(3, 0), + AD4691_CHANNEL(4, 0), + AD4691_CHANNEL(5, 0), + AD4691_CHANNEL(6, 0), + AD4691_CHANNEL(7, 0), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4693_manual_channels[] =3D { + AD4691_CHANNEL(0, 16), + AD4691_CHANNEL(1, 16), + AD4691_CHANNEL(2, 16), + AD4691_CHANNEL(3, 16), + AD4691_CHANNEL(4, 16), + AD4691_CHANNEL(5, 16), + AD4691_CHANNEL(6, 16), + AD4691_CHANNEL(7, 16), IIO_CHAN_SOFT_TIMESTAMP(16), }; =20 @@ -181,6 +235,7 @@ static const unsigned int ad4691_osc_freqs[] =3D { =20 static const struct ad4691_chip_info ad4691_chip_info =3D { .channels =3D ad4691_channels, + .manual_channels =3D ad4691_manual_channels, .name =3D "ad4691", .num_channels =3D ARRAY_SIZE(ad4691_channels), .max_rate =3D 500 * HZ_PER_KHZ, @@ -188,6 +243,7 @@ static const struct ad4691_chip_info ad4691_chip_info = =3D { =20 static const struct ad4691_chip_info ad4692_chip_info =3D { .channels =3D ad4691_channels, + .manual_channels =3D ad4691_manual_channels, .name =3D "ad4692", .num_channels =3D ARRAY_SIZE(ad4691_channels), .max_rate =3D 1 * HZ_PER_MHZ, @@ -195,6 +251,7 @@ static const struct ad4691_chip_info ad4692_chip_info = =3D { =20 static const struct ad4691_chip_info ad4693_chip_info =3D { .channels =3D ad4693_channels, + .manual_channels =3D ad4693_manual_channels, .name =3D "ad4693", .num_channels =3D ARRAY_SIZE(ad4693_channels), .max_rate =3D 500 * HZ_PER_KHZ, @@ -202,6 +259,7 @@ static const struct ad4691_chip_info ad4693_chip_info = =3D { =20 static const struct ad4691_chip_info ad4694_chip_info =3D { .channels =3D ad4693_channels, + .manual_channels =3D ad4693_manual_channels, .name =3D "ad4694", .num_channels =3D ARRAY_SIZE(ad4693_channels), .max_rate =3D 1 * HZ_PER_MHZ, @@ -227,9 +285,9 @@ struct ad4691_state { */ struct mutex lock; /* - * Per-buffer-enabl ree lifetimesources: - * Manual Mode - a pre-built SPI message that clocks out N+1 - * transfers in one go. + * Per-buffer-enable lifetime resources (triggered-buffer paths): + * Manual Mode - a pre-built SPI message that clocks out N+1 + * transfers in one go. * CNV Burst Mode - a pre-built SPI message that clocks out 2*N * transfers in one go. */ @@ -238,9 +296,19 @@ struct ad4691_state { struct spi_transfer *scan_xfers; __be16 *scan_tx; __be16 *scan_rx; - /* Scan buffer: one slot per channel (u16) plus timestamp */ + /* SPI offload DMA path resources */ + struct spi_offload *offload; + /* SPI offload trigger - periodic (MANUAL) or DATA_READY (CNV_BURST) */ + struct spi_offload_trigger *offload_trigger; + u64 offload_trigger_hz; + struct spi_message offload_msg; + /* Max 16 channel xfers + 1 state-reset or NOOP */ + struct spi_transfer offload_xfer[17]; + u32 offload_tx_cmd[17]; + u32 offload_tx_reset; + /* Scan buffer: one slot per channel (u32) plus timestamp */ struct { - u16 vals[16]; + u32 vals[16]; s64 ts __aligned(8); } scan __aligned(IIO_DMA_MINALIGN); }; @@ -260,6 +328,46 @@ static int ad4691_gpio_setup(struct ad4691_state *st, = unsigned int gp_num) AD4691_GP_MODE_DATA_READY << shift); } =20 +static const struct spi_offload_config ad4691_offload_config =3D { + .capability_flags =3D SPI_OFFLOAD_CAP_TRIGGER | + SPI_OFFLOAD_CAP_RX_STREAM_DMA, +}; + +static bool ad4691_offload_trigger_match(struct spi_offload_trigger *trigg= er, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + return type =3D=3D SPI_OFFLOAD_TRIGGER_DATA_READY && + nargs =3D=3D 1 && args[0] <=3D 3; +} + +static int ad4691_offload_trigger_request(struct spi_offload_trigger *trig= ger, + enum spi_offload_trigger_type type, + u64 *args, u32 nargs) +{ + struct ad4691_state *st =3D spi_offload_trigger_get_priv(trigger); + + if (nargs !=3D 1) + return -EINVAL; + + return ad4691_gpio_setup(st, (unsigned int)args[0]); +} + +static int ad4691_offload_trigger_validate(struct spi_offload_trigger *tri= gger, + struct spi_offload_trigger_config *config) +{ + if (config->type !=3D SPI_OFFLOAD_TRIGGER_DATA_READY) + return -EINVAL; + + return 0; +} + +static const struct spi_offload_trigger_ops ad4691_offload_trigger_ops =3D= { + .match =3D ad4691_offload_trigger_match, + .request =3D ad4691_offload_trigger_request, + .validate =3D ad4691_offload_trigger_validate, +}; + static void ad4691_disable_pwm(void *data) { struct pwm_state state =3D { .enabled =3D false }; @@ -817,6 +925,206 @@ static const struct iio_buffer_setup_ops ad4691_cnv_b= urst_buffer_setup_ops =3D { .postdisable =3D &ad4691_cnv_burst_buffer_postdisable, }; =20 +static int ad4691_manual_offload_buffer_postenable(struct iio_dev *indio_d= ev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + struct spi_device *spi =3D to_spi_device(dev); + struct spi_offload_trigger_config config =3D { + .type =3D SPI_OFFLOAD_TRIGGER_PERIODIC, + }; + unsigned int bit, k; + int ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->offload_xfer, 0, sizeof(st->offload_xfer)); + + /* + * N+1 transfers for N channels. Each CS-low period triggers + * a conversion AND returns the previous result (pipelined). + * TX: [AD4691_ADC_CHAN(n), 0x00, 0x00, 0x00] + * RX: [data_hi, data_lo, 0x00, 0x00] (shift=3D16) + * Transfer 0 RX is garbage; transfers 1..N carry real data. + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + st->offload_tx_cmd[k] =3D + cpu_to_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, + AD4691_ADC_CHAN(bit))); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_cmd[k]; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->offload_xfer[k].cs_change =3D 1; + st->offload_xfer[k].cs_change_delay.value =3D AD4691_CNV_HIGH_TIME_NS; + st->offload_xfer[k].cs_change_delay.unit =3D SPI_DELAY_UNIT_NSECS; + /* First transfer RX is garbage =E2=80=94 skip it. */ + if (k > 0) + st->offload_xfer[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + } + + /* Final NOOP to flush pipeline and capture last channel. */ + st->offload_tx_cmd[k] =3D + cpu_to_be32(FIELD_PREP(AD4691_MSG_ADDR_HI, AD4691_NOOP)); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_cmd[k]; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->offload_xfer[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + + spi_message_init_with_transfers(&st->offload_msg, st->offload_xfer, k); + st->offload_msg.offload =3D st->offload; + + ret =3D spi_optimize_message(spi, &st->offload_msg); + if (ret) + goto err_exit_conversion; + + config.periodic.frequency_hz =3D st->offload_trigger_hz; + ret =3D spi_offload_trigger_enable(st->offload, st->offload_trigger, &con= fig); + if (ret) + goto err_unoptimize; + + return 0; + +err_unoptimize: + spi_unoptimize_message(&st->offload_msg); +err_exit_conversion: + ad4691_exit_conversion_mode(st); + return ret; +} + +static int ad4691_manual_offload_buffer_predisable(struct iio_dev *indio_d= ev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->offload_msg); + + return ad4691_exit_conversion_mode(st); +} + +static const struct iio_buffer_setup_ops ad4691_manual_offload_buffer_setu= p_ops =3D { + .postenable =3D &ad4691_manual_offload_buffer_postenable, + .predisable =3D &ad4691_manual_offload_buffer_predisable, +}; + +static int ad4691_cnv_burst_offload_buffer_postenable(struct iio_dev *indi= o_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + struct device *dev =3D regmap_get_device(st->regmap); + struct spi_device *spi =3D to_spi_device(dev); + struct spi_offload_trigger_config config =3D { + .type =3D SPI_OFFLOAD_TRIGGER_DATA_READY, + }; + unsigned int n_active =3D hweight_long(*indio_dev->active_scan_mask); + unsigned int bit, k; + int ret; + + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, + (u16)~(*indio_dev->active_scan_mask)); + if (ret) + return ret; + + ret =3D 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 =3D regmap_write(st->regmap, AD4691_ACC_COUNT_LIMIT(bit), + AD4691_ACC_COUNT_VAL); + if (ret) + return ret; + } + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->offload_xfer, 0, sizeof(st->offload_xfer)); + + /* + * N transfers to read N AVG_IN registers plus one state-reset + * transfer (no RX) to re-arm DATA_READY. + * TX: [reg_hi | 0x80, reg_lo, 0x00, 0x00] + * RX: [0x00, 0x00, data_hi, data_lo] (shift=3D0) + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + unsigned int reg =3D AD4691_AVG_IN(bit); + + st->offload_tx_cmd[k] =3D + cpu_to_be32(((reg >> 8 | 0x80) << 24) | + ((reg & 0xFF) << 16)); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_cmd[k]; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + st->offload_xfer[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + if (k < n_active - 1) + st->offload_xfer[k].cs_change =3D 1; + k++; + } + + /* State reset to re-arm DATA_READY for the next scan. */ + st->offload_tx_reset =3D + cpu_to_be32(((AD4691_STATE_RESET_REG >> 8) << 24) | + ((AD4691_STATE_RESET_REG & 0xFF) << 16) | + (AD4691_STATE_RESET_ALL << 8)); + st->offload_xfer[k].tx_buf =3D &st->offload_tx_reset; + st->offload_xfer[k].len =3D sizeof(u32); + st->offload_xfer[k].bits_per_word =3D AD4691_OFFLOAD_BITS_PER_WORD; + k++; + + spi_message_init_with_transfers(&st->offload_msg, st->offload_xfer, k); + st->offload_msg.offload =3D st->offload; + + ret =3D spi_optimize_message(spi, &st->offload_msg); + if (ret) + goto err_exit_conversion; + + ret =3D ad4691_sampling_enable(st, true); + if (ret) + goto err_unoptimize; + + ret =3D spi_offload_trigger_enable(st->offload, st->offload_trigger, &con= fig); + if (ret) + goto err_sampling_disable; + + return 0; + +err_sampling_disable: + ad4691_sampling_enable(st, false); +err_unoptimize: + spi_unoptimize_message(&st->offload_msg); +err_exit_conversion: + ad4691_exit_conversion_mode(st); + return ret; +} + +static int ad4691_cnv_burst_offload_buffer_predisable(struct iio_dev *indi= o_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + spi_offload_trigger_disable(st->offload, st->offload_trigger); + + ret =3D ad4691_sampling_enable(st, false); + if (ret) + return ret; + + spi_unoptimize_message(&st->offload_msg); + + return ad4691_exit_conversion_mode(st); +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_offload_buffer_s= etup_ops =3D { + .postenable =3D &ad4691_cnv_burst_offload_buffer_postenable, + .predisable =3D &ad4691_cnv_burst_offload_buffer_predisable, +}; + static ssize_t sampling_frequency_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -824,6 +1132,9 @@ static ssize_t sampling_frequency_show(struct device *= dev, struct iio_dev *indio_dev =3D dev_to_iio_dev(dev); struct ad4691_state *st =3D iio_priv(indio_dev); =20 + if (st->manual_mode && st->offload) + return sysfs_emit(buf, "%llu\n", st->offload_trigger_hz); + if (st->manual_mode) return -ENODEV; =20 @@ -838,7 +1149,7 @@ static ssize_t sampling_frequency_store(struct device = *dev, struct ad4691_state *st =3D iio_priv(indio_dev); int freq, ret; =20 - if (st->manual_mode) + if (st->manual_mode && !st->offload) return -ENODEV; =20 ret =3D kstrtoint(buf, 10, &freq); @@ -847,6 +1158,20 @@ static ssize_t sampling_frequency_store(struct device= *dev, =20 guard(mutex)(&st->lock); =20 + if (st->manual_mode) { + struct spi_offload_trigger_config config =3D { + .type =3D SPI_OFFLOAD_TRIGGER_PERIODIC, + .periodic =3D { .frequency_hz =3D freq }, + }; + + ret =3D spi_offload_trigger_validate(st->offload_trigger, &config); + if (ret) + return ret; + + st->offload_trigger_hz =3D config.periodic.frequency_hz; + return len; + } + ret =3D ad4691_set_pwm_freq(st, freq); if (ret) return ret; @@ -900,7 +1225,7 @@ static irqreturn_t ad4691_trigger_handler(int irq, voi= d *p) =20 if (st->manual_mode) { iio_for_each_active_channel(indio_dev, i) { - st->scan.vals[i] =3D be16_to_cpu(st->scan_rx[k + 1]); + st->scan.vals[i] =3D (u32)be16_to_cpu(st->scan_rx[k + 1]) << 16; k++; } } else { @@ -1088,6 +1413,15 @@ static int ad4691_config(struct ad4691_state *st, u3= 2 max_speed_hz) if (st->manual_mode) return 0; =20 + /* + * In the offload CNV Burst path the GP pin is supplied by the trigger + * consumer via #trigger-source-cells; gpio_setup is called from + * ad4691_offload_trigger_request() instead. For the non-offload path + * derive the pin from the first interrupt-names entry (e.g. "gp0"). + */ + if (device_property_present(dev, "#trigger-source-cells")) + return 0; + ret =3D device_property_read_string_array(dev, "interrupt-names", &irq_name, 1); if (ret < 0) @@ -1158,6 +1492,56 @@ static int ad4691_setup_triggered_buffer(struct iio_= dev *indio_dev, &ad4691_manual_buffer_setup_ops); } =20 +static int ad4691_setup_offload(struct iio_dev *indio_dev, + struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct dma_chan *rx_dma; + int ret; + + if (st->manual_mode) { + st->offload_trigger =3D + devm_spi_offload_trigger_get(dev, st->offload, + SPI_OFFLOAD_TRIGGER_PERIODIC); + if (IS_ERR(st->offload_trigger)) + return dev_err_probe(dev, PTR_ERR(st->offload_trigger), + "Failed to get periodic offload trigger\n"); + + st->offload_trigger_hz =3D st->info->max_rate; + } else { + struct spi_offload_trigger_info trigger_info =3D { + .fwnode =3D dev_fwnode(dev), + .ops =3D &ad4691_offload_trigger_ops, + .priv =3D st, + }; + + ret =3D 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 =3D + 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 DATA_READY offload trigger\n"); + } + + rx_dma =3D 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 channel\n"); + + if (st->manual_mode) + indio_dev->setup_ops =3D &ad4691_manual_offload_buffer_setup_ops; + else + indio_dev->setup_ops =3D &ad4691_cnv_burst_offload_buffer_setup_ops; + + return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma, + IIO_BUFFER_DIRECTION_IN); +} + static int ad4691_probe(struct spi_device *spi) { struct device *dev =3D &spi->dev; @@ -1193,14 +1577,27 @@ static int ad4691_probe(struct spi_device *spi) if (ret) return ret; =20 + st->offload =3D devm_spi_offload_get(dev, spi, &ad4691_offload_config); + ret =3D PTR_ERR_OR_ZERO(st->offload); + if (ret =3D=3D -ENODEV) + st->offload =3D NULL; + else if (ret) + return dev_err_probe(dev, ret, "Failed to get SPI offload\n"); + indio_dev->name =3D st->info->name; indio_dev->info =3D &ad4691_info; indio_dev->modes =3D INDIO_DIRECT_MODE; =20 - indio_dev->channels =3D st->info->channels; + if (st->manual_mode) + indio_dev->channels =3D st->info->manual_channels; + else + indio_dev->channels =3D st->info->channels; indio_dev->num_channels =3D st->info->num_channels; =20 - ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + if (st->offload) + ret =3D ad4691_setup_offload(indio_dev, st); + else + ret =3D ad4691_setup_triggered_buffer(indio_dev, st); if (ret) return ret; =20 @@ -1238,3 +1635,4 @@ module_spi_driver(ad4691_driver); MODULE_AUTHOR("Radu Sabau "); MODULE_DESCRIPTION("Analog Devices AD4691 Family ADC Driver"); MODULE_LICENSE("GPL"); +MODULE_IMPORT_NS("IIO_DMA_BUFFER"); --=20 2.43.0