From nobody Mon May 25 02:57:45 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 29EAC3F4DC5; Tue, 19 May 2026 12:20:42 +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=1779193242; cv=none; b=bltsr6qhcG7cYcy6zTTgJ/zsXpdtG9OAZBqjG1VgGaHe8OyCY8OEk5NIuvMnTnBA/a6pwldTTOvewt0b4NVcy6T+0fxAjwXOmxOb94S5BHCC3VdwEqZWSLGB+1c8oP2YG04xv4IKOxXTr3CIVAqr1Zfx3PS6kEAIGdFgtl+GSxU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779193242; c=relaxed/simple; bh=8av/kJ9kA0sa1b8pBI5r56i7v9Q1hZvi5vRA9smgQjU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=s8l2iRsqSGFcBHhBrCZM4UgPjUlVWve1Uo9NRZblaj9sMXcHT6xBC5wxmDXJF0T5z4vRzhEtVy2O7yAX+hoJkffLIsMg7NoaoszEYorD28ZUmktDjRSbH0IyUdjHftQVxzkmM6U5Dl8VgHFeH/zkAqsoJt37sbt6Pqs11mOSlE4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=XQWXcyD0; 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="XQWXcyD0" Received: by smtp.kernel.org (Postfix) with ESMTPS id D2D87C2BCC6; Tue, 19 May 2026 12:20:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779193241; bh=8av/kJ9kA0sa1b8pBI5r56i7v9Q1hZvi5vRA9smgQjU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=XQWXcyD0L8rWeTkVN5JmIMhwTosjB/3bgRJE1c0tV9uD0qWwAOVNAbkXUzC+yHtY4 phixY3eXrmcC8yqU8bD7MGeXENkqvUttOy2d3LkzKQuL0gVU7X1FM6VkYxMF4Ce1Sh 62Xda/ca0WGCSa6sJ5K0xU7Gsb9Yy+zCYmmnGDK6tyalIh+K1pxUBtY2+9RiLDWv9R 7IM1HCobecQ/p9f+4RBmAyR4km8izamrHOZY99ue0ang651B1gu0K7GjOAKBJhDEwu 64YgvklFe7GL4gMdqI8TkCg8Wj6UXT3HMxeDnX6/UWjPS6LI0gkCOaxguK8NM6DPlf k+Y7hHToxtE1g== 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 C26A8CD4F5B; Tue, 19 May 2026 12:20:41 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Tue, 19 May 2026 15:20:22 +0300 Subject: [PATCH v12 1/6] 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: <20260519-ad4692-multichannel-sar-adc-driver-v12-1-5b335162aa51@analog.com> References: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@analog.com> In-Reply-To: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@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 , Jonathan Corbet , Shuah Khan Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org, Radu Sabau , Conor Dooley X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779193239; l=7809; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=zmIoPMOYWOcJrqI//dWqHawVi5T3cjP8oiFlmAKz5cM=; b=eLeAGfPufSTaRYvK09a6oiPuMKxvUURSN+JvMr4RsmwHQgifNDhdYU+qRuZwy/op70x/d+7xj gJnUwnV0VTcCmzQbZS5bRhT2WUEODzLPnEku3MCFZoEJo7LvU9MfGo/ 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). Digital core VDD is supplied either externally via vdd-supply, or generated by the on-chip LDO fed from ldo-in-supply; the two are mutually exclusive and one must be present. - 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. The four variants are not compatible with each other: AD4691/AD4692 have 16 analog input channels while AD4693/AD4694 have 8, and AD4691/AD4693 top out at 500 kSPS while AD4692/AD4694 reach 1 MSPS. These differences in channel count and maximum sample rate require distinct compatible strings so the driver can select the correct channel configuration and rate limits. Acked-by: Conor Dooley Signed-off-by: Radu Sabau --- .../devicetree/bindings/iio/adc/adi,ad4691.yaml | 180 +++++++++++++++++= ++++ MAINTAINERS | 7 + 2 files changed, 187 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..af28a0c1cfa9 --- /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 + +description: | + The AD4691 family are high-speed, low-power, multichannel successive + approximation register (SAR) analog-to-digital converters (ADCs) with + an SPI-compatible serial interface. The ADC supports CNV Burst Mode, + where an external PWM drives the CNV pin, and Manual Mode, where CNV + is directly tied to the SPI chip-select. + + Datasheets: + * https://www.analog.com/en/products/ad4691.html + * https://www.analog.com/en/products/ad4692.html + * https://www.analog.com/en/products/ad4693.html + * https://www.analog.com/en/products/ad4694.html + +$ref: /schemas/spi/spi-peripheral-props.yaml# + +properties: + compatible: + enum: + - adi,ad4691 + - adi,ad4692 + - adi,ad4693 + - adi,ad4694 + + reg: + maxItems: 1 + + spi-max-frequency: + maximum: 40000000 + + spi-cpol: true + spi-cpha: true + + avdd-supply: + description: Analog power supply (4.5V to 5.5V). + + vdd-supply: + description: + External 1.8V digital core supply. When present, the internal LDO is + disabled (LDO_EN =3D 0). Mutually exclusive with ldo-in-supply. + + ldo-in-supply: + description: + LDO input supply (2.4V to 5.5V). When present and vdd-supply is abse= nt, + the internal LDO generates 1.8V VDD from this input (LDO_EN =3D 1). + Mutually exclusive with vdd-supply. + + 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: + enum: + - gp0 + - gp1 + - gp2 + - gp3 + + gpio-controller: true + + '#gpio-cells': + const: 2 + + '#trigger-source-cells': + description: + This node can act as a trigger source. The single cell in a consumer + reference specifies the GP pin number (0-3) used as the trigger outp= ut. + const: 1 + +required: + - compatible + - reg + - avdd-supply + - vio-supply + +allOf: + # vdd-supply and ldo-in-supply are mutually exclusive, one is required: + # either an external 1.8V VDD is provided or the internal LDO is fed from + # ldo-in-supply to generate VDD. + - oneOf: + - required: + - vdd-supply + - required: + - ldo-in-supply + # ref-supply and refin-supply are mutually exclusive, one is required + - oneOf: + - required: + - ref-supply + - required: + - refin-supply + +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>; + ldo-in-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>; + ldo-in-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 c2c6d79275c6..7d31c38921e8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1482,6 +1482,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 May 25 02:57:45 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 29F644D8D90; Tue, 19 May 2026 12:20:42 +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=1779193242; cv=none; b=KLh7OjiKoghLOgTcmSyOqkbIthju2Q5qN0UCcGUpYIfoXrvFXQSIsyXwbxNa0Z0M+LqugdUUYeJxS6H2PzxEE/w1FTvqdEd2ZFiA6jpMzvCymTacM8oiX84YJNPr1hsqaUx9qzv9N1J4IYi/CnBq4SyUeNZ9c6V+qgheFIauQsU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779193242; c=relaxed/simple; bh=wNkEWNjnFvNKYFEz+fcepREO9mdGbvKVXEuxR5OrLKM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=AupgpvmOHda9ykM/7n7suUJNT2gdmHLYLeiMP1XIONDpT9YVmVzeDeN9Ib5X9fkcqeBslS7UpuQUIFvOHU97+jQF93OQuEHvG1+eWArVCPYYQYTMKUwkM5Vb4vzcST2hu48VK7xYTVHh9SUFHuuRrv/l/Sxvj7fJOAdkCDNgI5A= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=hdVBnKpZ; 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="hdVBnKpZ" Received: by smtp.kernel.org (Postfix) with ESMTPS id E07CAC2BCF6; Tue, 19 May 2026 12:20:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779193242; bh=wNkEWNjnFvNKYFEz+fcepREO9mdGbvKVXEuxR5OrLKM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=hdVBnKpZrFr7LebzZywBRXnoVWz04FKbWZrhjoDlr847DqKs3pDu/UNBrxPwZgkYV nzKq1NlwXx1QiHJGVuIgxb5+idhAfryAp8uBOd6qN+3P0o+ceH9A2Dvol590Zjn0da L0EtNc5UsMjE4JGtQxkpyeHN00jgGho7kJvsIV4Oihs6PUDIn98szzoeSi6WopKApD ZBZ2FIuPZeKQhqgyCUe1UWrJUoaY63WJJrfIZTDc2CSbwFesaw8RhyRbkz8nvjL0lF WQnaR+qiTOnZBrIKi8SvH5qFQfM6m/hLeo/Y/xx+m+dJZt8uYDniQPHBLEr+xqfC7h Tjiz4SH17hZQQ== 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 D4446CD5BAA; Tue, 19 May 2026 12:20:41 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Tue, 19 May 2026 15:20:23 +0300 Subject: [PATCH v12 2/6] 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: <20260519-ad4692-multichannel-sar-adc-driver-v12-2-5b335162aa51@analog.com> References: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@analog.com> In-Reply-To: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@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 , Jonathan Corbet , Shuah Khan Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779193239; l=27214; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=gDkEQLWoER1TtqBGVDJYpgGZR+Zm5FdCv+DCMwQ6FZo=; b=QPAHuKZB3zvMzF5uRcy3ZRIrNI6BSitiFupBvvUICApXGPQPRLm7DLujBxPB9nM5v58/7MAr2 7yUfGd0ACISB+HbeDd8I1d3I4ssEQcRy+UYQlmP2OqSvtLZhwWDEJlz 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 by asserting the reset-gpios GPIO line for at least 300 =C2=B5s then deasserting it; a software reset via SPI_CONFIG_A is used as fallback when no reset GPIO is provided. 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. IIO_CHAN_INFO_SAMP_FREQ is exposed as info_mask_separate. The oscillator is shared hardware =E2=80=94 writing any channel's sampling_frequency attri= bute sets it for all others =E2=80=94 but per-channel attributes are used throug= hout the series to avoid an ABI change when per-channel oversampling ratios are introduced in a later commit, at which point the effective output rate (osc_freq / osr[N]) becomes genuinely per-channel. Reviewed-by: David Lechner Signed-off-by: Radu Sabau --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 12 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ad4691.c | 796 +++++++++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 810 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 7d31c38921e8..020c1ffae31b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1488,6 +1488,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 a9dedbb8eb46..c9aca0d08e41 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -144,6 +144,18 @@ 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 + depends on REGULATOR || COMPILE_TEST + 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 097357d146ba..707dd708912f 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..2d58df862142 --- /dev/null +++ b/drivers/iio/adc/ad4691.c @@ -0,0 +1,796 @@ +// 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 +#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_CNV_DUTY_CYCLE_NS 380 +#define AD4691_CNV_HIGH_TIME_NS 430 + +#define AD4691_SPI_CONFIG_A_REG 0x000 +#define AD4691_SW_RESET (BIT(7) | BIT(0)) + +#define AD4691_STATUS_REG 0x014 +#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) +#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_SEQ_ALL_CHANNELS_OFF 0x00 +#define AD4691_SPARE_CONTROL 0x02A + +#define AD4691_MAX_CHANNELS 16 + +#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 BIT(0) +#define AD4691_ADC_SETUP 0x182 +#define AD4691_ADC_MODE_MASK GENMASK(1, 0) +#define AD4691_CNV_BURST_MODE 0x01 +#define AD4691_AUTONOMOUS_MODE 0x02 +/* + * ACC_MASK_REG covers both mask bytes via ADDR_DESCENDING SPI: writing a + * 16-bit BE value to 0x185 auto-decrements to 0x184 for the second byte. + */ +#define AD4691_ACC_MASK_REG 0x185 +#define AD4691_ACC_DEPTH_IN(n) (0x186 + (n)) +#define AD4691_GPIO_MODE1_REG 0x196 +#define AD4691_GPIO_MODE2_REG 0x197 +#define AD4691_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 +#define AD4691_ACC_STATUS_OVERRUN1_REG 0x1B2 +#define AD4691_ACC_STATUS_OVERRUN2_REG 0x1B3 +#define AD4691_ACC_STATUS_SAT1_REG 0x1B4 +#define AD4691_ACC_STATUS_SAT2_REG 0x1BE +#define AD4691_ACC_SAT_OVR_REG(n) (0x1C0 + (n)) +#define AD4691_AVG_IN(n) (0x201 + (2 * (n))) +#define AD4691_AVG_STS_IN(n) (0x222 + (3 * (n))) +#define AD4691_ACC_IN(n) (0x252 + (3 * (n))) +#define AD4691_ACC_STS_DATA(n) (0x283 + (4 * (n))) + + +static const char * const ad4691_supplies[] =3D { "avdd", "vio" }; + +enum ad4691_ref_ctrl { + AD4691_VREF_2P5, + AD4691_VREF_3P0, + AD4691_VREF_3P3, + AD4691_VREF_4P096, + AD4691_VREF_5P0 +}; + +struct ad4691_channel_info { + const struct iio_chan_spec *channels __counted_by_ptr(num_channels); + unsigned int num_channels; +}; + +struct ad4691_chip_info { + const char *name; + unsigned int max_rate; + const struct ad4691_channel_info *sw_info; +}; + +#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_type =3D { \ + .realbits =3D 16, \ + }, \ + } + +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), +}; + +static const struct ad4691_channel_info ad4691_sw_info =3D { + .channels =3D ad4691_channels, + .num_channels =3D ARRAY_SIZE(ad4691_channels), +}; + +static const struct ad4691_channel_info ad4693_sw_info =3D { + .channels =3D ad4693_channels, + .num_channels =3D ARRAY_SIZE(ad4693_channels), +}; + +/* + * 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 int ad4691_osc_freqs_Hz[] =3D { + [0x0] =3D 1000000, + [0x1] =3D 500000, + [0x2] =3D 400000, + [0x3] =3D 250000, + [0x4] =3D 200000, + [0x5] =3D 167000, + [0x6] =3D 133000, + [0x7] =3D 125000, + [0x8] =3D 100000, + [0x9] =3D 50000, + [0xA] =3D 25000, + [0xB] =3D 12500, + [0xC] =3D 10000, + [0xD] =3D 5000, + [0xE] =3D 2500, + [0xF] =3D 1250, +}; + +static const struct ad4691_chip_info ad4691_chip_info =3D { + .name =3D "ad4691", + .max_rate =3D 500 * HZ_PER_KHZ, + .sw_info =3D &ad4691_sw_info, +}; + +static const struct ad4691_chip_info ad4692_chip_info =3D { + .name =3D "ad4692", + .max_rate =3D 1 * HZ_PER_MHZ, + .sw_info =3D &ad4691_sw_info, +}; + +static const struct ad4691_chip_info ad4693_chip_info =3D { + .name =3D "ad4693", + .max_rate =3D 500 * HZ_PER_KHZ, + .sw_info =3D &ad4693_sw_info, +}; + +static const struct ad4691_chip_info ad4694_chip_info =3D { + .name =3D "ad4694", + .max_rate =3D 1 * HZ_PER_MHZ, + .sw_info =3D &ad4693_sw_info, +}; + +struct ad4691_state { + const struct ad4691_chip_info *info; + struct regmap *regmap; + struct spi_device *spi; + + 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; + + /* Set bit 15 to mark the operation as READ. */ + put_unaligned_be16(0x8000 | reg, tx); + + switch (reg) { + case 0 ... AD4691_OSC_FREQ_REG: + case AD4691_SPARE_CONTROL ... AD4691_ACC_MASK_REG - 1: + case AD4691_ACC_MASK_REG + 1 ... AD4691_ACC_SAT_OVR_REG(15): + ret =3D spi_write_then_read(spi, tx, sizeof(tx), rx, 1); + if (ret) + return ret; + *val =3D rx[0]; + return 0; + case AD4691_ACC_MASK_REG: + case AD4691_STD_SEQ_CONFIG: + case AD4691_AVG_IN(0) ... AD4691_AVG_IN(15): + ret =3D spi_write_then_read(spi, tx, sizeof(tx), 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, sizeof(tx), 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, sizeof(tx), 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 > U8_MAX) + 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 > U16_MAX) + 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): + return true; + default: + break; + } + + /* + * Multi-byte registers have non-unit strides; only accept base + * addresses to prevent debugfs from triggering reads that cross + * register boundaries. + */ + if (reg >=3D AD4691_AVG_IN(0) && reg <=3D AD4691_AVG_IN(15)) + return (reg - AD4691_AVG_IN(0)) % 2 =3D=3D 0; + if (reg >=3D AD4691_AVG_STS_IN(0) && reg <=3D AD4691_AVG_STS_IN(15)) + return (reg - AD4691_AVG_STS_IN(0)) % 3 =3D=3D 0; + if (reg >=3D AD4691_ACC_IN(0) && reg <=3D AD4691_ACC_IN(15)) + return (reg - AD4691_ACC_IN(0)) % 3 =3D=3D 0; + if (reg >=3D AD4691_ACC_STS_DATA(0) && reg <=3D AD4691_ACC_STS_DATA(15)) + return (reg - AD4691_ACC_STS_DATA(0)) % 4 =3D=3D 0; + + 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: + return true; + default: + break; + } + + /* Multi-byte registers: only accept base addresses (see volatile_reg). */ + if (reg >=3D AD4691_AVG_IN(0) && reg <=3D AD4691_AVG_IN(15)) + return (reg - AD4691_AVG_IN(0)) % 2 =3D=3D 0; + if (reg >=3D AD4691_AVG_STS_IN(0) && reg <=3D AD4691_AVG_STS_IN(15)) + return (reg - AD4691_AVG_STS_IN(0)) % 3 =3D=3D 0; + if (reg >=3D AD4691_ACC_IN(0) && reg <=3D AD4691_ACC_IN(15)) + return (reg - AD4691_ACC_IN(0)) % 3 =3D=3D 0; + if (reg >=3D AD4691_ACC_STS_DATA(0) && reg <=3D AD4691_ACC_STS_DATA(15)) + return (reg - AD4691_ACC_STS_DATA(0)) % 4 =3D=3D 0; + + 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, +}; + +/* + * Index 0 in ad4691_osc_freqs_Hz is 1 MHz =E2=80=94 valid only for AD4692= /AD4694 + * (max_rate =3D=3D 1 MHz). AD4691/AD4693 cap at 500 kHz so their valid ra= nge + * starts at index 1. + */ +static unsigned int ad4691_samp_freq_start(const struct ad4691_chip_info *= info) +{ + return (info->max_rate =3D=3D 1 * HZ_PER_MHZ) ? 0 : 1; +} + +static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val) +{ + unsigned int reg_val; + int ret; + + guard(mutex)(&st->lock); + + ret =3D regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val); + if (ret) + return ret; + + *val =3D ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]; + return IIO_VAL_INT; +} + +static int ad4691_set_sampling_freq(struct ad4691_state *st, int freq) +{ + unsigned int start =3D ad4691_samp_freq_start(st->info); + + guard(mutex)(&st->lock); + + for (unsigned int i =3D start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) { + if (ad4691_osc_freqs_Hz[i] !=3D freq) + continue; + return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG, + AD4691_OSC_FREQ_MASK, i); + } + + return -EINVAL; +} + +static int ad4691_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + const int **vals, int *type, + int *length, long mask) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int start =3D ad4691_samp_freq_start(st->info); + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *vals =3D &ad4691_osc_freqs_Hz[start]; + *type =3D IIO_VAL_INT; + *length =3D ARRAY_SIZE(ad4691_osc_freqs_Hz) - start; + return IIO_AVAIL_LIST; + default: + return -EINVAL; + } +} + +static int ad4691_single_shot_read(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int reg_val, osc_idx, period_us; + int ret; + + guard(mutex)(&st->lock); + + /* Use AUTONOMOUS mode for single-shot reads. */ + ret =3D regmap_write(st->regmap, AD4691_STATE_RESET_REG, AD4691_STATE_RES= ET_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, + ~BIT(chan->channel) & GENMASK(15, 0)); + 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; + + osc_idx =3D FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val); + /* Wait 2 oscillator periods for the conversion to complete. */ + period_us =3D DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_id= x]); + fsleep(period_us); + + 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) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + return ad4691_set_sampling_freq(st, 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_bulk_get_enable(dev, ARRAY_SIZE(ad4691_supplies), + ad4691_supplies); + if (ret) + return dev_err_probe(dev, ret, "Failed to get and enable supplies\n"); + + /* + * vdd-supply and ldo-in-supply are mutually exclusive: + * vdd-supply present =E2=86=92 external 1.8V VDD; disable internal LD= O. + * vdd-supply absent =E2=86=92 enable internal LDO fed from ldo-in-su= pply. + * Having both simultaneously is strongly inadvisable per the datasheet. + */ + if (device_property_present(dev, "vdd-supply")) { + ret =3D devm_regulator_get_enable(dev, "vdd"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable VDD\n"); + } else if (device_property_present(dev, "ldo-in-supply")) { + ret =3D devm_regulator_get_enable(dev, "ldo-in"); + if (ret) + return dev_err_probe(dev, ret, + "Failed to get and enable LDO-IN\n"); + st->ldo_en =3D true; + } else { + return dev_err_probe(dev, -EINVAL, + "missing one of vdd-supply, ldo-in-supply\n"); + } + + if (device_property_present(dev, "ref-supply")) { + st->vref_uV =3D devm_regulator_get_enable_read_voltage(dev, "ref"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to get REF supply voltage\n"); + } else if (device_property_present(dev, "refin-supply")) { + st->vref_uV =3D devm_regulator_get_enable_read_voltage(dev, "refin"); + if (st->vref_uV < 0) + return dev_err_probe(dev, st->vref_uV, + "Failed to get REFIN supply voltage\n"); + st->refbuf_en =3D true; + } else { + return dev_err_probe(dev, -EINVAL, + "missing one of ref-supply, refin-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; + int ret; + + 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) { + /* + * Assert the reset line to guarantee a clean reset pulse on + * every probe, including driver reloads where the line may + * already be deasserted (reset_control_put() does not + * re-assert on release). tRESETL (minimum pulse width) =3D 10 ns + * (Table 5); kernel function-call overhead alone exceeds this, + * so no explicit delay is needed between assert and deassert. + */ + reset_control_assert(rst); + ret =3D reset_control_deassert(rst); + if (ret) + return ret; + /* + * Wait tHWR =3D 300 =C2=B5s (Table 5) for the device to complete its + * internal reset sequence before accepting SPI commands. + */ + fsleep(300); + return 0; + } + + /* No hardware reset available, fall back to software reset. */ + ret =3D regmap_write(st->regmap, AD4691_SPI_CONFIG_A_REG, AD4691_SW_RESET= ); + if (ret) + return ret; + /* + * Wait tSWR =3D 300 =C2=B5s (Table 5) for the device to complete its + * internal reset sequence before accepting SPI commands. + */ + fsleep(300); + 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; + unsigned int 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); + } + + val =3D FIELD_PREP(AD4691_REF_CTRL_MASK, ref_val); + if (st->refbuf_en) + val |=3D AD4691_REFBUF_EN; + + ret =3D regmap_write(st->regmap, AD4691_REF_CTRL, val); + if (ret) + return dev_err_probe(dev, ret, "Failed to write REF_CTRL\n"); + + ret =3D regmap_assign_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_LDO_EN, st->ldo_en); + if (ret) + return dev_err_probe(dev, ret, "Failed to write DEVICE_SETUP\n"); + + /* + * Set the internal oscillator to the highest rate this chip supports. + * Index 0 (1 MHz) exceeds the 500 kHz max of AD4691/AD4693, so those + * chips start at index 1 (500 kHz). + */ + ret =3D regmap_write(st->regmap, AD4691_OSC_FREQ_REG, + ad4691_samp_freq_start(st->info)); + if (ret) + return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n"); + + ret =3D regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE); + if (ret) + return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n"); + + return 0; +} + +static int ad4691_probe(struct spi_device *spi) +{ + struct device *dev =3D &spi->dev; + struct iio_dev *indio_dev; + struct ad4691_state *st; + int ret; + + indio_dev =3D devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st =3D iio_priv(indio_dev); + st->spi =3D spi; + st->info =3D spi_get_device_match_data(spi); + if (!st->info) + return -ENODEV; + + 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->sw_info->channels; + indio_dev->num_channels =3D st->info->sw_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 { + { .name =3D "ad4691", .driver_data =3D (kernel_ulong_t)&ad4691_chip_info = }, + { .name =3D "ad4692", .driver_data =3D (kernel_ulong_t)&ad4692_chip_info = }, + { .name =3D "ad4693", .driver_data =3D (kernel_ulong_t)&ad4693_chip_info = }, + { .name =3D "ad4694", .driver_data =3D (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 May 25 02:57:45 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 3A9014D90C3; Tue, 19 May 2026 12:20:42 +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=1779193242; cv=none; b=p7Mwsv3nDFvNCWO4WoPgbHrDiKFwiatTUV5z3hTeWLd1axnnkK4DFC8orrPm9NI1tcX3hrX+tckSRxwmys4m15Idfh9jZ/6BL0Scq9acaViVCQI9YcQrTmPz8Pd9NwXyeaQ31mMhayZ2GpZ9TZ4xBORdn8GbHdIYBHAOeVxp5Vo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779193242; c=relaxed/simple; bh=1sqdMC5KUycdQqikbv57LRxcX/hPNyCXqOBF7DRJyP0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=MeiEdsskA2NLDmQngAPnYHoYqadkhkOZxDpzku/uovSUZtjyhbhtvt/ytPbyhkc7v7i6ULtkuZP1YSwdx/RCqGdkCFXE59MpdFfPgfpeEDS2OI74S8f3Lq1u+bNakJvNAE/oNobdj8F3Pr5ijPsCtOAbK/ouBDd27CmTbfz138E= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=RoNuQYJ3; 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="RoNuQYJ3" Received: by smtp.kernel.org (Postfix) with ESMTPS id F22E0C2BCB8; Tue, 19 May 2026 12:20:41 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779193242; bh=1sqdMC5KUycdQqikbv57LRxcX/hPNyCXqOBF7DRJyP0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=RoNuQYJ3O0zvMWJgzcdGqrYUEyVs66nVagIuzG16cBt82zuFEu/i+lMtXDK3K+UW3 2Gs3sRXZk+4+KHTbJQZISX+godvdg90km2+L6tx7bL/196P3XS6Pn50FI2tNz/jEL8 32ftWF3JE37QExDjOVJaWJX/YjtsQdzXFaAj0f4Y5OvUH8OsWdsdjoFI/an8yqgtQq 62wlXpCBmT/qLfYSiLTDiTJFIj8QnaYkhHz9CZhU/Qx+xAQtZYK+qwPzk/bzTvBCCw 8PwCc7lTCiXPCkKj+DVNBC8vS7rnVBZi1JL7nyRIjmLF7otGIsSbEKhJsFUoNFlwgn LA+SKCpfG5EVA== 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 E6B63CD5BAB; Tue, 19 May 2026 12:20:41 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Tue, 19 May 2026 15:20:24 +0300 Subject: [PATCH v12 3/6] 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: <20260519-ad4692-multichannel-sar-adc-driver-v12-3-5b335162aa51@analog.com> References: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@analog.com> In-Reply-To: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@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 , Jonathan Corbet , Shuah Khan Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779193239; l=23572; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=xlaG8TkZZk50Mh7JwX9ZMPBUVGLUpyjdQDiBAVwW4O0=; b=CU0tUgEThC9a+nGNq7XXWriM3ld9ahtmtaEWOtTsxY38fNNJYUwdorKCG3u5E967mAw46gxl5 4080Ahg12XqCIw2OPSekhM7QiAyholzicm0Uv37wvqYKJTlm4S9K1sr 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 big-endian 16-bit (__be16) slot per active channel, densely packed in scan_index order, followed by a timestamp. 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 | 559 +++++++++++++++++++++++++++++++++++++++++++= +++- 2 files changed, 557 insertions(+), 4 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index c9aca0d08e41..1d7afde108c0 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -148,6 +148,8 @@ config AD4691 tristate "Analog Devices AD4691 Family ADC Driver" depends on SPI depends on REGULATOR || COMPILE_TEST + 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 2d58df862142..ed60ed5b488c 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -11,19 +11,29 @@ #include #include #include +#include +#include #include #include #include #include +#include +#include #include #include #include +#include #include #include #include #include =20 +#include #include +#include +#include +#include +#include =20 #define AD4691_VREF_uV_MIN 2400000 #define AD4691_VREF_uV_MAX 5250000 @@ -120,8 +130,12 @@ struct ad4691_chip_info { 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 { \ + .format =3D 'u', \ .realbits =3D 16, \ + .storagebits =3D 16, \ + .endianness =3D IIO_BE, \ }, \ } =20 @@ -142,6 +156,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 { @@ -153,6 +168,7 @@ static const struct iio_chan_spec ad4693_channels[] =3D= { AD4691_CHANNEL(5), AD4691_CHANNEL(6), AD4691_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), }; =20 static const struct ad4691_channel_info ad4691_sw_info =3D { @@ -189,6 +205,8 @@ static const int ad4691_osc_freqs_Hz[] =3D { [0xF] =3D 1250, }; =20 +static const char * const ad4691_gp_names[] =3D { "gp0", "gp1", "gp2", "gp= 3" }; + static const struct ad4691_chip_info ad4691_chip_info =3D { .name =3D "ad4691", .max_rate =3D 500 * HZ_PER_KHZ, @@ -218,8 +236,13 @@ struct ad4691_state { struct regmap *regmap; struct spi_device *spi; =20 + struct pwm_device *conv_trigger; + int irq; int vref_uV; + u32 cnv_period_ns; =20 + bool manual_mode; + bool irq_enabled; bool refbuf_en; bool ldo_en; /* @@ -227,8 +250,56 @@ struct ad4691_state { * atomicity of consecutive SPI operations. */ struct mutex lock; + /* + * Per-buffer-enable lifetime resources: + * Manual Mode - a pre-built SPI message that clocks out N+1 + * transfers in one go. + * CNV Burst Mode - a pre-built SPI message that clocks out 2*N + * transfers in one go. + */ + struct spi_message scan_msg; + /* + * max 16 + 1 NOOP (manual) or 2*16 + 1 state-reset (CNV burst). + */ + struct spi_transfer scan_xfers[34]; + /* + * CNV burst: 16 AVG_IN addresses =3D 16. Manual: 16 channel cmds + + * 1 NOOP =3D 17. Stored as native u16; put_unaligned_be16() fills each + * slot so the SPI controller (bits_per_word=3D8) sends bytes MSB-first. + */ + u16 scan_tx[17] __aligned(IIO_DMA_MINALIGN); + /* + * CNV burst state-reset: 4-byte write [addr_hi, addr_lo, + * STATE_RESET_ALL, OSC_EN=3D1]. CS is asserted throughout, so + * ADDR_DESCENDING writes byte[3]=3D1 to OSC_EN_REG (0x180) as a + * deliberate side-write, keeping the oscillator enabled. Shared + * with the offload path (mutually exclusive at probe). + */ + u8 scan_tx_reset[4] __aligned(IIO_DMA_MINALIGN); + /* + * Scan buffer: one BE16 slot per active channel, plus timestamp. + * DMA-aligned because scan_xfers point rx_buf directly into vals[]. + */ + IIO_DECLARE_DMA_BUFFER_WITH_TS(__be16, vals, 16); }; =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 bit_off =3D gp_num % 2; + unsigned int reg_off =3D gp_num / 2; + unsigned int shift =3D 4 * bit_off; + + return regmap_update_bits(st->regmap, + AD4691_GPIO_MODE1_REG + reg_off, + AD4691_GP_MODE_MASK << shift, + AD4691_GP_MODE_DATA_READY << shift); +} + static int ad4691_reg_read(void *context, unsigned int reg, unsigned int *= val) { struct spi_device *spi =3D context; @@ -548,13 +619,397 @@ static int ad4691_reg_access(struct iio_dev *indio_d= ev, unsigned int reg, return regmap_write(st->regmap, reg, writeval); } =20 -static const struct iio_info ad4691_info =3D { +static int ad4691_set_pwm_freq(struct ad4691_state *st, unsigned int freq) +{ + if (!freq) + return -EINVAL; + + st->cnv_period_ns =3D DIV_ROUND_UP(NSEC_PER_SEC, freq); + return 0; +} + +static int ad4691_sampling_enable(struct ad4691_state *st, bool enable) +{ + struct pwm_state conv_state =3D { + .period =3D st->cnv_period_ns, + .duty_cycle =3D AD4691_CNV_DUTY_CYCLE_NS, + .polarity =3D PWM_POLARITY_NORMAL, + .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_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE); + if (ret) + return ret; + + return regmap_write(st->regmap, AD4691_STATE_RESET_REG, + AD4691_STATE_RESET_ALL); +} + +/* + * ad4691_exit_conversion_mode - Return the chip to AUTONOMOUS mode. + * + * Called from buffer postdisable to restore the chip to the + * idle state used by read_raw. Clears the sequencer and resets state. + */ +static int ad4691_exit_conversion_mode(struct ad4691_state *st) +{ + if (st->manual_mode) + return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, + AD4691_MANUAL_MODE, 0); + + return regmap_update_bits(st->regmap, AD4691_ADC_SETUP, + AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE); +} + +static int ad4691_manual_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + unsigned int k, i; + int ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + spi_message_init(&st->scan_msg); + + k =3D 0; + iio_for_each_active_channel(indio_dev, i) { + /* + * Channel-select command occupies the first (high) byte of the + * 16-bit DIN frame; the second byte is a don't-care zero pad. + * put_unaligned_be16() writes [cmd, 0x00] in memory so the + * SPI controller sends the command byte first on the wire. + */ + put_unaligned_be16((u16)(AD4691_ADC_CHAN(i) << 8), &st->scan_tx[k]); + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; + /* + * The pipeline means xfer[0] receives the residual from the + * previous sequence, not a valid sample. Discard it (rx_buf=3DNULL) + * to avoid aliasing vals[0] across two concurrent DMA mappings. + * xfer[1] (or the NOOP when only one channel is active) writes + * the real ch[0] result to vals[0]. Subsequent transfers write + * into vals[k-1] so each result lands at the next dense slot. + */ + st->scan_xfers[k].rx_buf =3D (k =3D=3D 0) ? NULL : &st->vals[k - 1]; + st->scan_xfers[k].len =3D sizeof(*st->scan_tx); + st->scan_xfers[k].cs_change =3D 1; + st->scan_xfers[k].cs_change_delay.value =3D AD4691_CNV_HIGH_TIME_NS; + st->scan_xfers[k].cs_change_delay.unit =3D SPI_DELAY_UNIT_NSECS; + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + k++; + } + + /* Final NOOP transfer retrieves the last channel's result. */ + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; /* scan_tx[k] =3D=3D 0 =3D= =3D NOOP */ + st->scan_xfers[k].rx_buf =3D &st->vals[k - 1]; + st->scan_xfers[k].len =3D sizeof(*st->scan_tx); + spi_message_add_tail(&st->scan_xfers[k], &st->scan_msg); + + ret =3D spi_optimize_message(st->spi, &st->scan_msg); + if (ret) + return ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) { + spi_unoptimize_message(&st->scan_msg); + return ret; + } + + return 0; +} + +static int ad4691_manual_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + ret =3D ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + 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); + unsigned int acc_mask, std_seq_config; + unsigned int k, i; + int ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + 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) { + put_unaligned_be16(0x8000 | AD4691_AVG_IN(i), &st->scan_tx[k]); + st->scan_xfers[2 * k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[2 * k].len =3D sizeof(*st->scan_tx); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + st->scan_xfers[2 * k + 1].rx_buf =3D &st->vals[k]; + st->scan_xfers[2 * k + 1].len =3D sizeof(*st->scan_tx); + st->scan_xfers[2 * k + 1].cs_change =3D 1; + spi_message_add_tail(&st->scan_xfers[2 * k + 1], &st->scan_msg); + k++; + } + + /* + * Append a 4-byte state-reset transfer [addr_hi, addr_lo, + * STATE_RESET_ALL, OSC_EN=3D1]. CS is asserted throughout, so + * ADDR_DESCENDING writes byte[3]=3D1 to OSC_EN_REG (0x180) as a + * deliberate side-write, keeping the oscillator enabled. + * STATE_RESET_ALL starts the next burst; the hardware does not + * accumulate new conversions until after a STATE_RESET pulse, so + * no in-progress data is lost. No cs_change here =E2=80=94 CS must + * deassert normally at end of message to frame the next command. + */ + put_unaligned_be16(AD4691_STATE_RESET_REG, st->scan_tx_reset); + st->scan_tx_reset[2] =3D AD4691_STATE_RESET_ALL; + st->scan_tx_reset[3] =3D 1; + st->scan_xfers[2 * k].tx_buf =3D st->scan_tx_reset; + st->scan_xfers[2 * k].len =3D sizeof(st->scan_tx_reset); + spi_message_add_tail(&st->scan_xfers[2 * k], &st->scan_msg); + + ret =3D spi_optimize_message(st->spi, &st->scan_msg); + if (ret) + return ret; + + std_seq_config =3D bitmap_read(indio_dev->active_scan_mask, 0, + iio_get_masklength(indio_dev)) & GENMASK(15, 0); + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, std_seq_config); + if (ret) + goto err_unoptimize; + + acc_mask =3D ~std_seq_config & GENMASK(15, 0); + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask); + if (ret) + goto err_unoptimize; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + goto err_unoptimize; + + return 0; + +err_unoptimize: + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static int ad4691_cnv_burst_buffer_postenable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + /* + * Start the PWM and unmask the IRQ here in postenable, not in + * preenable. The IIO core attaches the trigger poll function between + * preenable and postenable; enabling sampling or unmasking the IRQ + * before that point risks a DATA_READY assertion landing before the + * poll function is registered. iio_trigger_poll() would drop the + * event, disable_irq_nosync() would fire, and enable_irq() would + * never be called, leaving the IRQ permanently masked. + */ + ret =3D ad4691_sampling_enable(st, true); + if (ret) + return ret; + + enable_irq(st->irq); + st->irq_enabled =3D true; + return 0; +} + +static int ad4691_cnv_burst_buffer_predisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + + if (st->irq_enabled) { + disable_irq(st->irq); + st->irq_enabled =3D false; + } + return ad4691_sampling_enable(st, false); +} + +static int ad4691_cnv_burst_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + int ret; + + ret =3D ad4691_exit_conversion_mode(st); + spi_unoptimize_message(&st->scan_msg); + return ret; +} + +static const struct iio_buffer_setup_ops ad4691_cnv_burst_buffer_setup_ops= =3D { + .preenable =3D ad4691_cnv_burst_buffer_preenable, + .postenable =3D ad4691_cnv_burst_buffer_postenable, + .predisable =3D ad4691_cnv_burst_buffer_predisable, + .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); + + return sysfs_emit(buf, "%lu\n", 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); + unsigned int freq; + int ret; + + ret =3D kstrtouint(buf, 10, &freq); + if (ret) + return ret; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ret =3D ad4691_set_pwm_freq(st, freq); + if (ret) + return ret; + + return len; +} + +static IIO_DEVICE_ATTR_RW(sampling_frequency, 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); + + /* + * Disable the IRQ before calling iio_trigger_poll(). The IRQ is + * re-enabled via the trigger .reenable callback, which the IIO core + * calls inside iio_trigger_notify_done() once use_count reaches zero. + * Re-enabling here (before notify_done) would race: a DATA_READY + * between enable_irq() and notify_done() calls iio_trigger_poll() + * while use_count > 0, dropping the event and permanently masking + * the IRQ. + */ + disable_irq_nosync(st->irq); + iio_trigger_poll(indio_dev->trig); + + return IRQ_HANDLED; +} + +static void ad4691_trigger_reenable(struct iio_trigger *trig) +{ + struct ad4691_state *st =3D iio_trigger_get_drvdata(trig); + + enable_irq(st->irq); +} + +static const struct iio_trigger_ops ad4691_trigger_ops =3D { + .reenable =3D ad4691_trigger_reenable, + .validate_device =3D iio_trigger_validate_own_device, +}; + +static void ad4691_read_scan(struct iio_dev *indio_dev, s64 ts) +{ + struct ad4691_state *st =3D iio_priv(indio_dev); + + guard(mutex)(&st->lock); + + spi_sync(st->spi, &st->scan_msg); + + /* + * rx_buf pointers in scan_xfers point directly into scan.vals, so no + * copy is needed. The scan_msg already includes a STATE_RESET at the + * end (appended in preenable), so no explicit reset is needed here. + */ + iio_push_to_buffers_with_ts(indio_dev, st->vals, sizeof(st->vals), ts); +} + +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; + + ad4691_read_scan(indio_dev, pf->timestamp); + iio_trigger_notify_done(indio_dev->trig); + return IRQ_HANDLED; +} + +/* + * CNV burst mode: only allow our own trigger (driven by DATA_READY IRQ). + * Manual mode: external triggers (e.g. iio-trig-hrtimer) must be allowed + * because manual mode has no DATA_READY IRQ to fire the internal trigger. + * iio_trigger_ops.validate_device =3D iio_trigger_validate_own_device is + * correct in both modes =E2=80=94 it prevents other devices from hijackin= g our + * internal trigger; the distinction here is only for iio_info.validate_tr= igger. + */ +static const struct iio_info ad4691_cnv_burst_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, + .validate_trigger =3D iio_validate_own_trigger, +}; + +static const struct iio_info ad4691_manual_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, }; =20 +static int ad4691_pwm_setup(struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + + 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"); + + 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); @@ -662,6 +1117,22 @@ static int ad4691_config(struct ad4691_state *st) unsigned int val; 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; @@ -715,6 +1186,86 @@ static int ad4691_config(struct ad4691_state *st) return 0; } =20 +static int ad4691_setup_triggered_buffer(struct iio_dev *indio_dev, + struct ad4691_state *st) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct iio_trigger *trig; + unsigned int i; + int irq, ret; + + indio_dev->channels =3D st->info->sw_info->channels; + indio_dev->num_channels =3D st->info->sw_info->num_channels; + indio_dev->info =3D st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_b= urst_info; + + /* + * Manual mode relies on an external trigger (e.g. iio-trig-hrtimer); + * no internal trigger is needed or registered. + */ + if (st->manual_mode) + return devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ad4691_trigger_handler, + &ad4691_manual_buffer_setup_ops); + + /* + * CNV burst mode: allocate an internal trigger driven by the + * DATA_READY IRQ on the GP pin. + */ + trig =3D devm_iio_trigger_alloc(dev, "%s-dev%d", indio_dev->name, + iio_device_id(indio_dev)); + if (!trig) + return -ENOMEM; + + trig->ops =3D &ad4691_trigger_ops; + iio_trigger_set_drvdata(trig, st); + + ret =3D devm_iio_trigger_register(dev, trig); + if (ret) + return dev_err_probe(dev, ret, "IIO trigger register failed\n"); + + indio_dev->trig =3D iio_trigger_get(trig); + + /* + * The GP pin named in interrupt-names asserts at end-of-conversion. + * The IRQ handler 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 -ENXIO; + for (i =3D 0; i < ARRAY_SIZE(ad4691_gp_names); i++) { + irq =3D fwnode_irq_get_byname(dev_fwnode(dev), + ad4691_gp_names[i]); + if (irq > 0 || irq =3D=3D -EPROBE_DEFER) + break; + } + if (irq < 0) + return dev_err_probe(dev, irq, "failed to get GP interrupt\n"); + + st->irq =3D irq; + + ret =3D ad4691_gpio_setup(st, i); + if (ret) + return ret; + + /* + * The handler only calls disable_irq_nosync() and iio_trigger_poll(), + * both safe in hardirq context, so register as a hard IRQ handler. + * IRQF_NO_AUTOEN keeps it disabled until the buffer is enabled. + */ + ret =3D devm_request_irq(dev, irq, ad4691_irq, 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); +} + static int ad4691_probe(struct spi_device *spi) { struct device *dev =3D &spi->dev; @@ -754,11 +1305,11 @@ static int ad4691_probe(struct spi_device *spi) return ret; =20 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->sw_info->channels; - indio_dev->num_channels =3D st->info->sw_info->num_channels; + ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + if (ret) + return ret; =20 return devm_iio_device_register(dev, indio_dev); } --=20 2.43.0 From nobody Mon May 25 02:57:45 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 4444C4D90CF; Tue, 19 May 2026 12:20:42 +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=1779193242; cv=none; b=YWv7bMIyPFyahYjzIl+sOjO4qk81uiisjZC4PAfU7ThdRbOcim7h2IA0kcAKl1+fF6bfrcKPdQin4/s6n4i/yTx3WNAGuhZIjNFx6aR6fmyhBB6pkhyQmG55NRK+2IPZgkFJeaHQPPWNztSCdlMjrzKaKSu0Hqfc0zKyyt0t3mY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779193242; c=relaxed/simple; bh=0jFSbQs8PwiBHVcWBrHgOhkrZcGVjLbXKWC3iYSrHw8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=hrfIBcqAs9Uc+ZQ+jvTDFhQ3eE6C8VBJJK6ZI78DlRXb4jEYv4HxVFD1dJ5VJxhfAWURb5IBoiIGQfNoQDPq+ZyHq5CrRV+PJXAdTU4IGTkFNkgvuHR8F0Q8+R3YqyphXWBmIlspvNp+xi49/Ivmf3mOQcfdrM8rdBfBoRvPxfY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=aePAJcln; 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="aePAJcln" Received: by smtp.kernel.org (Postfix) with ESMTPS id 14751C32782; Tue, 19 May 2026 12:20:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779193242; bh=0jFSbQs8PwiBHVcWBrHgOhkrZcGVjLbXKWC3iYSrHw8=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=aePAJcln5YWjFVU2hu6mVlWTaKdDCqnZymtF/HNAuh6TS0pwEjTLSsAMZeqGD3SoK 1Bl3UHHjDS+pEp2HfzvdY6KEpSzN3+CfN0XRbTBpijBKJb0Jasxf9OcJp6ChV9KpkL wsK3WVWmyCnTrF3za5xI4l4icf67y+OxFi+EFXvQduhSyccTAeyLy6y7koWOELivzy rojgv2ve8MTEtTOroNMt3VPkLerYj7QGBR3+1MJoY8kgDxQd45M279iC+xlxijYguA pStWV6c2AiKyPY0EO6c9E+wN5mQL1WgCefPdx5mFkPkxzVcGpN9P9lJ+OVGYKVNDEM eR9CBjkQV2Z3w== 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 08391CD5BA4; Tue, 19 May 2026 12:20:42 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Tue, 19 May 2026 15:20:25 +0300 Subject: [PATCH v12 4/6] 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: <20260519-ad4692-multichannel-sar-adc-driver-v12-4-5b335162aa51@analog.com> References: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@analog.com> In-Reply-To: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@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 , Jonathan Corbet , Shuah Khan Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779193239; l=23170; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=+Fl84Iub3ubfav79EFYXz27Lp20lswktFmOtdmM1I4Y=; b=vDDxnJ5YYjG9l/OzP1CrQGChuItGIPjxyuXVM9zxK2chzaiTanEbPKMmc7L515tvTiDIG0SY+ kn2Qp82JsmwBm4QodwyiflhlUATQYqJlE9y5IjqGl+U9XPSIHji81Bt 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 16-bit 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 16-bit frames (bits_per_word=3D16, len=3D2). The SPI Engine assembles received bits into native 16-bit words before DMA, so offload samples land in CPU-native byte order (IIO_CPU). Dedicated channel arrays (AD4691_OFFLOAD_CHANNEL) reflect this: they omit IIO_BE and carry no soft timestamp (DMA delivers data directly to userspace). The software triggered-buffer path retains its IIO_BE channels because bits_per_word=3D8 causes SPI to deliver bytes MSB-first into memory, making the on-disk layout big-endian. Both paths use storagebits=3D16 as transfers are 16 bits wide in both cases. IIO_BUFFER_DMAENGINE is selected because the offload path uses devm_iio_dmaengine_buffer_setup_with_handle() to allocate and attach the DMA RX buffer to the IIO device. Signed-off-by: Radu Sabau --- drivers/iio/adc/Kconfig | 2 + drivers/iio/adc/ad4691.c | 444 +++++++++++++++++++++++++++++++++++++++++++= +++- 2 files changed, 443 insertions(+), 3 deletions(-) diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index 1d7afde108c0..4d12eeb08020 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -149,8 +149,10 @@ config AD4691 depends on SPI depends on REGULATOR || COMPILE_TEST select IIO_BUFFER + select IIO_BUFFER_DMAENGINE select IIO_TRIGGERED_BUFFER select REGMAP + select SPI_OFFLOAD help Say yes here to build support for Analog Devices AD4691 Family MuxSAR SPI analog to digital converters (ADC). diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c index ed60ed5b488c..a6588292f3c1 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -24,11 +25,15 @@ #include #include #include +#include +#include #include #include #include =20 #include +#include +#include #include #include #include @@ -44,6 +49,11 @@ =20 #define AD4691_CNV_DUTY_CYCLE_NS 380 #define AD4691_CNV_HIGH_TIME_NS 430 +/* + * Conservative default for the manual offload periodic trigger. Low enough + * to work safely out of the box across all OSR and channel count combinat= ions. + */ +#define AD4691_OFFLOAD_INITIAL_TRIGGER_HZ (100 * HZ_PER_KHZ) =20 #define AD4691_SPI_CONFIG_A_REG 0x000 #define AD4691_SW_RESET (BIT(7) | BIT(0)) @@ -118,6 +128,7 @@ struct ad4691_chip_info { const char *name; unsigned int max_rate; const struct ad4691_channel_info *sw_info; + const struct ad4691_channel_info *offload_info; }; =20 #define AD4691_CHANNEL(ch) \ @@ -139,6 +150,30 @@ struct ad4691_chip_info { }, \ } =20 +/* + * Offload path (bits_per_word=3D16): the SPI Engine assembles received + * bits into native 16-bit words before DMA, so samples are in + * CPU-native byte order (IIO_CPU). storagebits=3D16 matches the 16-bit + * DMA word size. + */ +#define AD4691_OFFLOAD_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 { \ + .format =3D 'u', \ + .realbits =3D 16, \ + .storagebits =3D 16, \ + }, \ + } + static const struct iio_chan_spec ad4691_channels[] =3D { AD4691_CHANNEL(0), AD4691_CHANNEL(1), @@ -171,6 +206,40 @@ static const struct iio_chan_spec ad4693_channels[] = =3D { IIO_CHAN_SOFT_TIMESTAMP(8), }; =20 +/* + * Offload channel arrays: no IIO_CHAN_SOFT_TIMESTAMP because DMA delivers + * data directly to userspace without a software timestamp. + */ +static const struct iio_chan_spec ad4691_offload_channels[] =3D { + AD4691_OFFLOAD_CHANNEL(0), + AD4691_OFFLOAD_CHANNEL(1), + AD4691_OFFLOAD_CHANNEL(2), + AD4691_OFFLOAD_CHANNEL(3), + AD4691_OFFLOAD_CHANNEL(4), + AD4691_OFFLOAD_CHANNEL(5), + AD4691_OFFLOAD_CHANNEL(6), + AD4691_OFFLOAD_CHANNEL(7), + AD4691_OFFLOAD_CHANNEL(8), + AD4691_OFFLOAD_CHANNEL(9), + AD4691_OFFLOAD_CHANNEL(10), + AD4691_OFFLOAD_CHANNEL(11), + AD4691_OFFLOAD_CHANNEL(12), + AD4691_OFFLOAD_CHANNEL(13), + AD4691_OFFLOAD_CHANNEL(14), + AD4691_OFFLOAD_CHANNEL(15), +}; + +static const struct iio_chan_spec ad4693_offload_channels[] =3D { + AD4691_OFFLOAD_CHANNEL(0), + AD4691_OFFLOAD_CHANNEL(1), + AD4691_OFFLOAD_CHANNEL(2), + AD4691_OFFLOAD_CHANNEL(3), + AD4691_OFFLOAD_CHANNEL(4), + AD4691_OFFLOAD_CHANNEL(5), + AD4691_OFFLOAD_CHANNEL(6), + AD4691_OFFLOAD_CHANNEL(7), +}; + static const struct ad4691_channel_info ad4691_sw_info =3D { .channels =3D ad4691_channels, .num_channels =3D ARRAY_SIZE(ad4691_channels), @@ -181,6 +250,16 @@ static const struct ad4691_channel_info ad4693_sw_info= =3D { .num_channels =3D ARRAY_SIZE(ad4693_channels), }; =20 +static const struct ad4691_channel_info ad4691_offload_info =3D { + .channels =3D ad4691_offload_channels, + .num_channels =3D ARRAY_SIZE(ad4691_offload_channels), +}; + +static const struct ad4691_channel_info ad4693_offload_info =3D { + .channels =3D ad4693_offload_channels, + .num_channels =3D ARRAY_SIZE(ad4693_offload_channels), +}; + /* * 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 @@ -211,24 +290,28 @@ static const struct ad4691_chip_info ad4691_chip_info= =3D { .name =3D "ad4691", .max_rate =3D 500 * HZ_PER_KHZ, .sw_info =3D &ad4691_sw_info, + .offload_info =3D &ad4691_offload_info, }; =20 static const struct ad4691_chip_info ad4692_chip_info =3D { .name =3D "ad4692", .max_rate =3D 1 * HZ_PER_MHZ, .sw_info =3D &ad4691_sw_info, + .offload_info =3D &ad4691_offload_info, }; =20 static const struct ad4691_chip_info ad4693_chip_info =3D { .name =3D "ad4693", .max_rate =3D 500 * HZ_PER_KHZ, .sw_info =3D &ad4693_sw_info, + .offload_info =3D &ad4693_offload_info, }; =20 static const struct ad4691_chip_info ad4694_chip_info =3D { .name =3D "ad4694", .max_rate =3D 1 * HZ_PER_MHZ, .sw_info =3D &ad4693_sw_info, + .offload_info =3D &ad4693_offload_info, }; =20 struct ad4691_state { @@ -250,6 +333,10 @@ struct ad4691_state { * atomicity of consecutive SPI operations. */ struct mutex lock; + /* NULL when no SPI offload hardware is present. */ + struct spi_offload *offload; + struct spi_offload_trigger *offload_trigger; + u64 trigger_hz; /* * Per-buffer-enable lifetime resources: * Manual Mode - a pre-built SPI message that clocks out N+1 @@ -264,8 +351,11 @@ struct ad4691_state { struct spi_transfer scan_xfers[34]; /* * CNV burst: 16 AVG_IN addresses =3D 16. Manual: 16 channel cmds + - * 1 NOOP =3D 17. Stored as native u16; put_unaligned_be16() fills each - * slot so the SPI controller (bits_per_word=3D8) sends bytes MSB-first. + * 1 NOOP =3D 17. Stored as native u16. The non-offload path fills slots + * with put_unaligned_be16() (bits_per_word=3D8, bytes go out in memory + * order). The offload path assigns native values directly + * (bits_per_word=3Dbpw, SPI reads each slot as a native 16-bit word and + * shifts it out MSB-first). */ u16 scan_tx[17] __aligned(IIO_DMA_MINALIGN); /* @@ -300,6 +390,45 @@ 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 && ar= gs[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 || args[0] > 3) + return -EINVAL; + + return ad4691_gpio_setup(st, 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 int ad4691_reg_read(void *context, unsigned int reg, unsigned int *= val) { struct spi_device *spi =3D context; @@ -876,6 +1005,218 @@ static const struct iio_buffer_setup_ops ad4691_cnv_= burst_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 bpw =3D indio_dev->channels[0].scan_type.realbits; + unsigned int bit, k; + int ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + /* + * 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] + * RX: [data_hi, data_lo] (storagebits=3D16, shift=3D0) + * Transfer 0 RX is garbage; transfers 1..N carry real data. + * scan_tx is reused for TX commands (mutually exclusive with the + * non-offload triggered-buffer path). + * + * bits_per_word=3Dbpw: the SPI controller reads tx_buf as a native + * 16-bit word and shifts it out MSB-first. Store the exact 16-bit + * value we want on the wire as a plain native u16 =E2=80=94 no endianness + * macro =E2=80=94 so the wire bytes are correct on both LE and BE hosts. + * The channel-select command is a single byte; shift it to the MSB + * position so SPI sends it first, with a zero pad in the LSB. + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + st->scan_tx[k] =3D AD4691_ADC_CHAN(bit) << 8; + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[k].len =3D sizeof(*st->scan_tx); + st->scan_xfers[k].bits_per_word =3D bpw; + st->scan_xfers[k].cs_change =3D 1; + st->scan_xfers[k].cs_change_delay.value =3D AD4691_CNV_HIGH_TIME_NS; + st->scan_xfers[k].cs_change_delay.unit =3D SPI_DELAY_UNIT_NSECS; + /* First transfer RX is garbage =E2=80=94 skip it. */ + if (k > 0) + st->scan_xfers[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + } + + /* Final NOOP transfer retrieves the last channel's result. */ + st->scan_xfers[k].tx_buf =3D &st->scan_tx[k]; /* scan_tx[k] =3D=3D 0 =3D= =3D NOOP */ + st->scan_xfers[k].len =3D sizeof(*st->scan_tx); + st->scan_xfers[k].bits_per_word =3D bpw; + st->scan_xfers[k].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + k++; + + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, k); + st->scan_msg.offload =3D st->offload; + + ret =3D spi_optimize_message(spi, &st->scan_msg); + if (ret) + goto err_exit_conversion; + + config.periodic.frequency_hz =3D st->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->scan_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->scan_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 bpw =3D indio_dev->channels[0].scan_type.realbits; + unsigned int acc_mask, std_seq_config; + unsigned int bit, k; + int ret; + + std_seq_config =3D bitmap_read(indio_dev->active_scan_mask, 0, + iio_get_masklength(indio_dev)) & GENMASK(15, 0); + ret =3D regmap_write(st->regmap, AD4691_STD_SEQ_CONFIG, std_seq_config); + if (ret) + return ret; + + acc_mask =3D ~std_seq_config & GENMASK(15, 0); + ret =3D regmap_write(st->regmap, AD4691_ACC_MASK_REG, acc_mask); + if (ret) + return ret; + + ret =3D ad4691_enter_conversion_mode(st); + if (ret) + return ret; + + memset(st->scan_xfers, 0, sizeof(st->scan_xfers)); + memset(st->scan_tx, 0, sizeof(st->scan_tx)); + + /* + * Each AVG_IN register read uses two transfers: + * TX: [reg_hi | 0x80, reg_lo] (address phase, CS stays asserted) + * RX: [data_hi, data_lo] (bpw-wide data phase, storagebits=3D16) + * Both TX and RX use bits_per_word=3Dbpw: the SPI controller reads tx_buf + * as a native 16-bit word and shifts it out MSB-first. Store the exact + * 16-bit wire value as a plain native u16 =E2=80=94 no endianness macro = =E2=80=94 so the + * wire bytes are correct on both LE and BE hosts. The read-address + * (0x8000 | reg) is already the 16-bit value we want on the wire. + * scan_tx is reused for TX addresses (mutually exclusive with the + * non-offload triggered-buffer path). + */ + k =3D 0; + iio_for_each_active_channel(indio_dev, bit) { + st->scan_tx[k] =3D 0x8000 | AD4691_AVG_IN(bit); + + /* TX: address phase, CS stays asserted into data phase */ + st->scan_xfers[2 * k].tx_buf =3D &st->scan_tx[k]; + st->scan_xfers[2 * k].len =3D sizeof(*st->scan_tx); + st->scan_xfers[2 * k].bits_per_word =3D bpw; + + /* RX: data phase, CS toggles after to delimit the next register op */ + st->scan_xfers[2 * k + 1].len =3D sizeof(*st->scan_tx); + st->scan_xfers[2 * k + 1].bits_per_word =3D bpw; + st->scan_xfers[2 * k + 1].offload_flags =3D SPI_OFFLOAD_XFER_RX_STREAM; + st->scan_xfers[2 * k + 1].cs_change =3D 1; + k++; + } + + /* + * State reset: single 4-byte write [addr_hi, addr_lo, STATE_RESET_ALL, + * OSC_EN=3D1]. ADDR_DESCENDING writes byte[3]=3D1 to OSC_EN_REG (0x180) = as + * a deliberate side-write, keeping the oscillator enabled. + * scan_tx_reset is shared with the non-offload path (len=3D4 here vs + * len=3D3 there) since the two paths are mutually exclusive at probe. + */ + put_unaligned_be16(AD4691_STATE_RESET_REG, st->scan_tx_reset); + st->scan_tx_reset[2] =3D AD4691_STATE_RESET_ALL; + st->scan_tx_reset[3] =3D 1; + st->scan_xfers[2 * k].tx_buf =3D st->scan_tx_reset; + st->scan_xfers[2 * k].len =3D sizeof(st->scan_tx_reset); + /* + * 4-byte u8 buffer assembled with put_unaligned_be16(); leave + * bits_per_word at the default (8) so bytes go out in memory order. + */ + + spi_message_init_with_transfers(&st->scan_msg, st->scan_xfers, 2 * k + 1); + st->scan_msg.offload =3D st->offload; + + ret =3D spi_optimize_message(spi, &st->scan_msg); + if (ret) + goto err_exit_conversion; + + ret =3D spi_offload_trigger_enable(st->offload, st->offload_trigger, &con= fig); + if (ret) + goto err_unoptimize; + + ret =3D ad4691_sampling_enable(st, true); + if (ret) + goto err_disable_trigger; + + return 0; + +err_disable_trigger: + spi_offload_trigger_disable(st->offload, st->offload_trigger); +err_unoptimize: + spi_unoptimize_message(&st->scan_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); + + ad4691_sampling_enable(st, false); + spi_offload_trigger_disable(st->offload, st->offload_trigger); + spi_unoptimize_message(&st->scan_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) @@ -883,6 +1224,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", READ_ONCE(st->trigger_hz)); + return sysfs_emit(buf, "%lu\n", NSEC_PER_SEC / st->cnv_period_ns); } =20 @@ -903,6 +1247,20 @@ static ssize_t sampling_frequency_store(struct device= *dev, if (IIO_DEV_ACQUIRE_FAILED(claim)) return -EBUSY; =20 + if (st->manual_mode && st->offload) { + 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; + + WRITE_ONCE(st->trigger_hz, config.periodic.frequency_hz); + return len; + } + ret =3D ad4691_set_pwm_freq(st, freq); if (ret) return ret; @@ -1266,9 +1624,77 @@ static int ad4691_setup_triggered_buffer(struct iio_= dev *indio_dev, ad4691_buffer_attrs); } =20 +static int ad4691_setup_offload(struct iio_dev *indio_dev, + struct ad4691_state *st, + struct spi_offload *spi_offload) +{ + struct device *dev =3D regmap_get_device(st->regmap); + struct dma_chan *rx_dma; + int ret; + + st->offload =3D spi_offload; + + indio_dev->channels =3D st->info->offload_info->channels; + indio_dev->num_channels =3D st->info->offload_info->num_channels; + /* + * Offload path uses DMA directly; no IIO trigger is involved, so + * external triggers are not restricted (no validate_trigger). + */ + indio_dev->info =3D &ad4691_manual_info; + + 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->trigger_hz =3D AD4691_OFFLOAD_INITIAL_TRIGGER_HZ; + } 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; + + ret =3D devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dm= a, + IIO_BUFFER_DIRECTION_IN); + if (ret) + return ret; + + indio_dev->buffer->attrs =3D ad4691_buffer_attrs; + + return 0; +} + static int ad4691_probe(struct spi_device *spi) { struct device *dev =3D &spi->dev; + struct spi_offload *spi_offload; struct iio_dev *indio_dev; struct ad4691_state *st; int ret; @@ -1304,10 +1730,20 @@ static int ad4691_probe(struct spi_device *spi) if (ret) return ret; =20 + spi_offload =3D devm_spi_offload_get(dev, spi, &ad4691_offload_config); + ret =3D PTR_ERR_OR_ZERO(spi_offload); + if (ret =3D=3D -ENODEV) + spi_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->modes =3D INDIO_DIRECT_MODE; =20 - ret =3D ad4691_setup_triggered_buffer(indio_dev, st); + if (spi_offload) + ret =3D ad4691_setup_offload(indio_dev, st, spi_offload); + else + ret =3D ad4691_setup_triggered_buffer(indio_dev, st); if (ret) return ret; =20 @@ -1345,3 +1781,5 @@ 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"); +MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER"); --=20 2.43.0 From nobody Mon May 25 02:57:45 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 624BF4DB541; Tue, 19 May 2026 12:20:42 +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=1779193242; cv=none; b=U2qQLFsbUi+K/+A9ihAbvkoDAAjuKiqT66jPEVVUEzD4pmEdgwIdkqIbuwpP+hZ8lM3UyBRtzPozvz2RMhS8k9b1mOvNhE8uxm5Smrb17ldyJ40zTdRvKrB0epukSCtcPzaNN3If0IFklOtIyp1V937+RjhFqmyDdDY//v9hzlU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779193242; c=relaxed/simple; bh=6noF5P/h8PpsGt9HM8XCEGsF4ZlL3qsz4OLuprLJ3f0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=okTFkmaafH2eAPdwVQsronOojRvtd5LQ53/fevYqFZiUey4uW8z3BR73G0mzrTQRSHTTVKBAlLjIL3USxp2xeqVpuJTWfW5gaA1UkMThk/ENLfoYF8uVymImd9sZR5PI3ulAwOZXuvGUVq5BAjozAP3t5CfhnzJIdT+n3ngIYII= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=l38QYUxg; 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="l38QYUxg" Received: by smtp.kernel.org (Postfix) with ESMTPS id 28044C4AF10; Tue, 19 May 2026 12:20:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779193242; bh=6noF5P/h8PpsGt9HM8XCEGsF4ZlL3qsz4OLuprLJ3f0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=l38QYUxgPvc6+NcV8NULwRkeGg5/obmB5+fqiGQ7AhoKu7gvTt7GYhZngY48IZuBS p1Yj9XFlLbtIMgCO4f+o8X3QDxHb/OJObNQYLQRaaUoSI8y+CPLn9Qpqq4OyY3OqVi NS7q277whcY7JkGrEnIAz018AARDIKn0NhdoqN+7bZSgfd7PX71/rEfVwqjV7JqDL2 mX7mhXOYKLvGGpvxQ3WHV5UEN3kRdPbyhE68koEw2GwRTiltQk9h8wFQhZH3aa9upv Z/7jtv3+MUAkEr+oWSnkTjea5joSdYTMnJC1BmUuOVRUwljokmkrgLylGep3YaCm7B u+ANjpThn112A== 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 1EC16CD5BA6; Tue, 19 May 2026 12:20:42 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Tue, 19 May 2026 15:20:26 +0300 Subject: [PATCH v12 5/6] iio: adc: ad4691: add oversampling 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: <20260519-ad4692-multichannel-sar-adc-driver-v12-5-5b335162aa51@analog.com> References: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@analog.com> In-Reply-To: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@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 , Jonathan Corbet , Shuah Khan Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779193239; l=21801; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=71Iz+nSCkLyeM58C7db0FPqoKl0Fh4GsWZwpG21oKtY=; b=iXZ9ls4hKqJlnvrxUT2UjaoMcvqGso7mgpV4pnvtOzjxuvjqZsC7xJ/Fo3gw3JINu13jeMN0I DGzmlasiUGND8f0r6nRL4rtiO6lKroTfkdK93TTG3PDpebkSeow5o2k 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 per-channel oversampling ratio (OSR) support for CNV burst mode. The accumulator depth register (ACC_DEPTH_IN) is programmed with the selected OSR at buffer enable time and before each single-shot read. Supported OSR values: 1, 2, 4, 8, 16, 32. Introduce AD4691_MANUAL_CHANNEL() for manual mode channels, which do not expose the oversampling_ratio attribute since OSR is not applicable in that mode. A separate manual_channels array is added to struct ad4691_channel_info and selected at probe time. in_voltageN_sampling_frequency represents the effective output rate for channel N, defined as osc_freq / osr[N]. The chip has one internal oscillator shared by all channels; each channel independently accumulates osr[N] oscillator cycles before producing a result. Writing sampling_frequency computes needed_osc =3D freq * osr[N] and snaps down to the largest oscillator table entry that satisfies both osc <=3D needed_osc and osc % osr[N] =3D=3D 0, guaranteeing an exact integer read-back. The result is stored in target_osc_freq_Hz and written to OSC_FREQ_REG at buffer enable and single-shot time, so sampling_frequency and oversampling_ratio can be set in any order. in_voltageN_sampling_frequency_available is precomputed at probe for each OSR value, listing only oscillator table entries that divide evenly by that OSR, expressed as effective rates (osc_freq / osr[N]). The list becomes sparser as OSR increases, capping at max_rate / osr[N]. read_avail picks the precomputed list for the channel's current OSR, making the returned pointer stable and race-free. Writing oversampling_ratio stores the new OSR for that channel and snaps target_osc_freq_Hz to the largest oscillator table entry that is both <=3D old_effective_rate * new_osr and evenly divisible by new_osr. This preserves an integer read-back of in_voltageN_sampling_frequency after the OSR change while keeping the oscillator as close as possible to the previous effective rate. OSR defaults to 1 (no accumulation) for all channels. Signed-off-by: Radu Sabau --- drivers/iio/adc/ad4691.c | 388 ++++++++++++++++++++++++++++++++++++++++++-= ---- 1 file changed, 351 insertions(+), 37 deletions(-) diff --git a/drivers/iio/adc/ad4691.c b/drivers/iio/adc/ad4691.c index a6588292f3c1..4b32f50d2176 100644 --- a/drivers/iio/adc/ad4691.c +++ b/drivers/iio/adc/ad4691.c @@ -121,6 +121,7 @@ enum ad4691_ref_ctrl { =20 struct ad4691_channel_info { const struct iio_chan_spec *channels __counted_by_ptr(num_channels); + const struct iio_chan_spec *manual_channels __counted_by_ptr(num_channels= ); unsigned int num_channels; }; =20 @@ -131,12 +132,39 @@ struct ad4691_chip_info { const struct ad4691_channel_info *offload_info; }; =20 +/* CNV burst mode channel =E2=80=94 exposes oversampling ratio. */ #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 =3D BIT(IIO_CHAN_INFO_RAW) | \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available =3D \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) | \ + 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 { \ + .format =3D 'u', \ + .realbits =3D 16, \ + .storagebits =3D 16, \ + .endianness =3D IIO_BE, \ + }, \ + } + +/* + * Manual mode channel =E2=80=94 no oversampling ratio attribute. OSR is n= ot + * supported in manual mode; ACC_DEPTH_IN is not configured during manual + * buffer enable. + */ +#define AD4691_MANUAL_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), \ @@ -155,8 +183,33 @@ struct ad4691_chip_info { * bits into native 16-bit words before DMA, so samples are in * CPU-native byte order (IIO_CPU). storagebits=3D16 matches the 16-bit * DMA word size. + * + * CNV burst offload configures ACC_DEPTH_IN per channel, so the + * oversampling_ratio attribute is exposed. Manual offload does not; + * use AD4691_OFFLOAD_MANUAL_CHANNEL for that path. */ #define AD4691_OFFLOAD_CHANNEL(ch) \ + { \ + .type =3D IIO_VOLTAGE, \ + .indexed =3D 1, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_RAW) \ + | BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) \ + | BIT(IIO_CHAN_INFO_SAMP_FREQ), \ + .info_mask_separate_available =3D \ + BIT(IIO_CHAN_INFO_OVERSAMPLING_RATIO) \ + | 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 { \ + .format =3D 'u', \ + .realbits =3D 16, \ + .storagebits =3D 16, \ + }, \ + } + +/* Manual offload =E2=80=94 same IIO_CPU layout but no oversampling_ratio = attribute. */ +#define AD4691_OFFLOAD_MANUAL_CHANNEL(ch) \ { \ .type =3D IIO_VOLTAGE, \ .indexed =3D 1, \ @@ -240,23 +293,91 @@ static const struct iio_chan_spec ad4693_offload_chan= nels[] =3D { AD4691_OFFLOAD_CHANNEL(7), }; =20 +static const struct iio_chan_spec ad4691_manual_channels[] =3D { + AD4691_MANUAL_CHANNEL(0), + AD4691_MANUAL_CHANNEL(1), + AD4691_MANUAL_CHANNEL(2), + AD4691_MANUAL_CHANNEL(3), + AD4691_MANUAL_CHANNEL(4), + AD4691_MANUAL_CHANNEL(5), + AD4691_MANUAL_CHANNEL(6), + AD4691_MANUAL_CHANNEL(7), + AD4691_MANUAL_CHANNEL(8), + AD4691_MANUAL_CHANNEL(9), + AD4691_MANUAL_CHANNEL(10), + AD4691_MANUAL_CHANNEL(11), + AD4691_MANUAL_CHANNEL(12), + AD4691_MANUAL_CHANNEL(13), + AD4691_MANUAL_CHANNEL(14), + AD4691_MANUAL_CHANNEL(15), + IIO_CHAN_SOFT_TIMESTAMP(16), +}; + +static const struct iio_chan_spec ad4693_manual_channels[] =3D { + AD4691_MANUAL_CHANNEL(0), + AD4691_MANUAL_CHANNEL(1), + AD4691_MANUAL_CHANNEL(2), + AD4691_MANUAL_CHANNEL(3), + AD4691_MANUAL_CHANNEL(4), + AD4691_MANUAL_CHANNEL(5), + AD4691_MANUAL_CHANNEL(6), + AD4691_MANUAL_CHANNEL(7), + IIO_CHAN_SOFT_TIMESTAMP(8), +}; + +static const struct iio_chan_spec ad4691_offload_manual_channels[] =3D { + AD4691_OFFLOAD_MANUAL_CHANNEL(0), + AD4691_OFFLOAD_MANUAL_CHANNEL(1), + AD4691_OFFLOAD_MANUAL_CHANNEL(2), + AD4691_OFFLOAD_MANUAL_CHANNEL(3), + AD4691_OFFLOAD_MANUAL_CHANNEL(4), + AD4691_OFFLOAD_MANUAL_CHANNEL(5), + AD4691_OFFLOAD_MANUAL_CHANNEL(6), + AD4691_OFFLOAD_MANUAL_CHANNEL(7), + AD4691_OFFLOAD_MANUAL_CHANNEL(8), + AD4691_OFFLOAD_MANUAL_CHANNEL(9), + AD4691_OFFLOAD_MANUAL_CHANNEL(10), + AD4691_OFFLOAD_MANUAL_CHANNEL(11), + AD4691_OFFLOAD_MANUAL_CHANNEL(12), + AD4691_OFFLOAD_MANUAL_CHANNEL(13), + AD4691_OFFLOAD_MANUAL_CHANNEL(14), + AD4691_OFFLOAD_MANUAL_CHANNEL(15), +}; + +static const struct iio_chan_spec ad4693_offload_manual_channels[] =3D { + AD4691_OFFLOAD_MANUAL_CHANNEL(0), + AD4691_OFFLOAD_MANUAL_CHANNEL(1), + AD4691_OFFLOAD_MANUAL_CHANNEL(2), + AD4691_OFFLOAD_MANUAL_CHANNEL(3), + AD4691_OFFLOAD_MANUAL_CHANNEL(4), + AD4691_OFFLOAD_MANUAL_CHANNEL(5), + AD4691_OFFLOAD_MANUAL_CHANNEL(6), + AD4691_OFFLOAD_MANUAL_CHANNEL(7), +}; + +static const int ad4691_oversampling_ratios[] =3D { 1, 2, 4, 8, 16, 32 }; + static const struct ad4691_channel_info ad4691_sw_info =3D { .channels =3D ad4691_channels, + .manual_channels =3D ad4691_manual_channels, .num_channels =3D ARRAY_SIZE(ad4691_channels), }; =20 static const struct ad4691_channel_info ad4693_sw_info =3D { .channels =3D ad4693_channels, + .manual_channels =3D ad4693_manual_channels, .num_channels =3D ARRAY_SIZE(ad4693_channels), }; =20 static const struct ad4691_channel_info ad4691_offload_info =3D { .channels =3D ad4691_offload_channels, + .manual_channels =3D ad4691_offload_manual_channels, .num_channels =3D ARRAY_SIZE(ad4691_offload_channels), }; =20 static const struct ad4691_channel_info ad4693_offload_info =3D { .channels =3D ad4693_offload_channels, + .manual_channels =3D ad4693_offload_manual_channels, .num_channels =3D ARRAY_SIZE(ad4693_offload_channels), }; =20 @@ -323,6 +444,25 @@ struct ad4691_state { int irq; int vref_uV; u32 cnv_period_ns; + /* + * Snapped oscillator frequency (Hz) shared by all channels. Set when + * sampling_frequency or oversampling_ratio is written; written to + * OSC_FREQ_REG at buffer enable and single-shot time so both attributes + * can be set in any order. Reading in_voltageN_sampling_frequency + * returns target_osc_freq_Hz / osr[N] =E2=80=94 the effective rate for t= hat + * channel given its oversampling ratio. + */ + u32 target_osc_freq_Hz; + /* Per-channel oversampling ratio; always 1 in manual mode. */ + u8 osr[AD4691_MAX_CHANNELS]; + /* + * Precomputed effective-rate lists, one row per entry in + * ad4691_oversampling_ratios[]. Populated at probe; read_avail picks + * the row for the channel's current OSR. The tables are stable after + * probe so returning a pointer into them from read_avail is race-free. + */ + int samp_freq_avail[ARRAY_SIZE(ad4691_oversampling_ratios)][ARRAY_SIZE(ad= 4691_osc_freqs_Hz)]; + int samp_freq_avail_len[ARRAY_SIZE(ad4691_oversampling_ratios)]; =20 bool manual_mode; bool irq_enabled; @@ -588,50 +728,131 @@ static unsigned int ad4691_samp_freq_start(const str= uct ad4691_chip_info *info) return (info->max_rate =3D=3D 1 * HZ_PER_MHZ) ? 0 : 1; } =20 -static int ad4691_get_sampling_freq(struct ad4691_state *st, int *val) -{ - unsigned int reg_val; - int ret; - - guard(mutex)(&st->lock); - - ret =3D regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val); - if (ret) - return ret; - - *val =3D ad4691_osc_freqs_Hz[FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val)]; - return IIO_VAL_INT; -} - -static int ad4691_set_sampling_freq(struct ad4691_state *st, int freq) +/* + * Find the largest oscillator table entry that is both <=3D needed_osc and + * evenly divisible by osr (guaranteeing an integer effective rate on + * read-back). Returns 0 if no such entry exists in the chip's valid range. + */ +static unsigned int ad4691_find_osc_freq(struct ad4691_state *st, + unsigned int needed_osc, + unsigned int osr) { unsigned int start =3D ad4691_samp_freq_start(st->info); =20 - guard(mutex)(&st->lock); - for (unsigned int i =3D start; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) { - if (ad4691_osc_freqs_Hz[i] !=3D freq) + if ((unsigned int)ad4691_osc_freqs_Hz[i] > needed_osc) continue; - return regmap_update_bits(st->regmap, AD4691_OSC_FREQ_REG, - AD4691_OSC_FREQ_MASK, i); + if (ad4691_osc_freqs_Hz[i] % osr) + continue; + return ad4691_osc_freqs_Hz[i]; } + return 0; +} =20 +/* Write target_osc_freq_Hz to OSC_FREQ_REG. Called at use time. */ +static int ad4691_write_osc_freq(struct ad4691_state *st) +{ + for (unsigned int i =3D 0; i < ARRAY_SIZE(ad4691_osc_freqs_Hz); i++) { + if (ad4691_osc_freqs_Hz[i] =3D=3D st->target_osc_freq_Hz) + return regmap_write(st->regmap, AD4691_OSC_FREQ_REG, i); + } return -EINVAL; } =20 +/* Return the index of osr in ad4691_oversampling_ratios[], defaulting to = 0. */ +static unsigned int ad4691_osr_index(unsigned int osr) +{ + for (unsigned int i =3D 0; i < ARRAY_SIZE(ad4691_oversampling_ratios) - 1= ; i++) { + if ((unsigned int)ad4691_oversampling_ratios[i] =3D=3D osr) + return i; + } + return ARRAY_SIZE(ad4691_oversampling_ratios) - 1; +} + +/* + * Precompute samp_freq_avail[][]: for each OSR value, list the oscillator + * table entries that divide evenly by that OSR, expressed as effective ra= tes + * (osc_freq / osr). Called once at probe after st->info is set. + */ +static void ad4691_precompute_samp_freq_avail(struct ad4691_state *st) +{ + unsigned int start =3D ad4691_samp_freq_start(st->info); + + for (unsigned int i =3D 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i+= +) { + unsigned int osr =3D ad4691_oversampling_ratios[i]; + int n =3D 0; + + for (unsigned int j =3D start; j < ARRAY_SIZE(ad4691_osc_freqs_Hz); j++)= { + if (ad4691_osc_freqs_Hz[j] % osr) + continue; + st->samp_freq_avail[i][n++] =3D ad4691_osc_freqs_Hz[j] / osr; + } + st->samp_freq_avail_len[i] =3D n; + } +} + +static int ad4691_get_sampling_freq(struct ad4691_state *st, u8 osr, int *= val) +{ + *val =3D st->target_osc_freq_Hz / osr; + return IIO_VAL_INT; +} + +static int ad4691_set_sampling_freq(struct ad4691_state *st, + struct iio_chan_spec const *chan, int freq) +{ + unsigned int osr, found; + + /* + * Read osr under st->lock: osr[chan] and target_osc_freq_Hz are + * modified together under the lock; reading after acquiring it ensures + * we see a consistent snapshot with no concurrent write racing us. + */ + guard(mutex)(&st->lock); + osr =3D st->osr[chan->channel]; + + if (freq <=3D 0 || (unsigned int)freq > st->info->max_rate / osr) + return -EINVAL; + + found =3D ad4691_find_osc_freq(st, (unsigned int)freq * osr, osr); + if (!found) + return -EINVAL; + + /* + * Store the snapped oscillator frequency; OSC_FREQ_REG is written at + * buffer enable and single-shot time so that sampling_frequency and + * oversampling_ratio can be set in any order. + */ + st->target_osc_freq_Hz =3D found; + return 0; +} + 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 ad4691_samp_freq_start(st->info); =20 switch (mask) { - case IIO_CHAN_INFO_SAMP_FREQ: - *vals =3D &ad4691_osc_freqs_Hz[start]; + case IIO_CHAN_INFO_SAMP_FREQ: { + unsigned int osr_idx; + + /* + * The precomputed tables are stable after probe; only the + * channel's current OSR needs to be read under the lock to + * pick the right row atomically. + */ + guard(mutex)(&st->lock); + osr_idx =3D ad4691_osr_index(st->osr[chan->channel]); + *vals =3D st->samp_freq_avail[osr_idx]; *type =3D IIO_VAL_INT; - *length =3D ARRAY_SIZE(ad4691_osc_freqs_Hz) - start; + *length =3D st->samp_freq_avail_len[osr_idx]; + return IIO_AVAIL_LIST; + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: + *vals =3D ad4691_oversampling_ratios; + *type =3D IIO_VAL_INT; + *length =3D ARRAY_SIZE(ad4691_oversampling_ratios); return IIO_AVAIL_LIST; default: return -EINVAL; @@ -642,7 +863,7 @@ static int ad4691_single_shot_read(struct iio_dev *indi= o_dev, struct iio_chan_spec const *chan, int *val) { struct ad4691_state *st =3D iio_priv(indio_dev); - unsigned int reg_val, osc_idx, period_us; + unsigned int reg_val, period_us; int ret; =20 guard(mutex)(&st->lock); @@ -662,7 +883,12 @@ static int ad4691_single_shot_read(struct iio_dev *ind= io_dev, if (ret) return ret; =20 - ret =3D regmap_read(st->regmap, AD4691_OSC_FREQ_REG, ®_val); + ret =3D regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(chan->channel), + st->osr[chan->channel]); + if (ret) + return ret; + + ret =3D ad4691_write_osc_freq(st); if (ret) return ret; =20 @@ -670,9 +896,12 @@ static int ad4691_single_shot_read(struct iio_dev *ind= io_dev, if (ret) return ret; =20 - osc_idx =3D FIELD_GET(AD4691_OSC_FREQ_MASK, reg_val); - /* Wait 2 oscillator periods for the conversion to complete. */ - period_us =3D DIV_ROUND_UP(2UL * USEC_PER_SEC, ad4691_osc_freqs_Hz[osc_id= x]); + /* + * Wait osr + 1 oscillator periods: osr for accumulation, +1 for the + * pipeline margin (one extra period ensures the final result is ready). + */ + period_us =3D DIV_ROUND_UP((st->osr[chan->channel] + 1) * USEC_PER_SEC, + st->target_osc_freq_Hz); fsleep(period_us); =20 ret =3D regmap_write(st->regmap, AD4691_OSC_EN_REG, 0); @@ -706,8 +935,21 @@ static int ad4691_read_raw(struct iio_dev *indio_dev, =20 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_SAMP_FREQ: { + /* + * Read target_osc_freq_Hz and osr[chan] under st->lock to get a + * consistent snapshot: write_raw for SAMP_FREQ or OSR modifies + * both fields under the lock, so a concurrent read without the + * lock could observe a new oscillator frequency with the old OSR. + */ + guard(mutex)(&st->lock); + return ad4691_get_sampling_freq(st, st->osr[chan->channel], val); + } + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + guard(mutex)(&st->lock); + *val =3D st->osr[chan->channel]; + return IIO_VAL_INT; + } case IIO_CHAN_INFO_SCALE: *val =3D st->vref_uV / (MICRO / MILLI); *val2 =3D chan->scan_type.realbits; @@ -729,7 +971,40 @@ static int ad4691_write_raw(struct iio_dev *indio_dev, =20 switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: - return ad4691_set_sampling_freq(st, val); + return ad4691_set_sampling_freq(st, chan, val); + case IIO_CHAN_INFO_OVERSAMPLING_RATIO: { + unsigned int old_effective, found; + bool valid =3D false; + + for (unsigned int i =3D 0; i < ARRAY_SIZE(ad4691_oversampling_ratios); i= ++) { + if (ad4691_oversampling_ratios[i] =3D=3D val) { + valid =3D true; + break; + } + } + if (!valid) + return -EINVAL; + + /* + * Hold st->lock while computing the new oscillator frequency + * and updating both target_osc_freq_Hz and osr[chan] atomically: + * read_raw for SAMP_FREQ reads both fields under the lock and + * must see a consistent pair (new osc =E2=86=94 new osr). + * + * Snap target_osc_freq_Hz to the largest table entry that is + * both <=3D old_effective * new_osr and evenly divisible by + * new_osr, preserving an integer read-back of + * in_voltageN_sampling_frequency after the OSR change. + */ + guard(mutex)(&st->lock); + old_effective =3D st->target_osc_freq_Hz / st->osr[chan->channel]; + found =3D ad4691_find_osc_freq(st, old_effective * (unsigned int)val, va= l); + if (!found) + return -EINVAL; + st->target_osc_freq_Hz =3D found; + st->osr[chan->channel] =3D val; + return 0; + } default: return -EINVAL; } @@ -784,6 +1059,10 @@ static int ad4691_enter_conversion_mode(struct ad4691= _state *st) return regmap_update_bits(st->regmap, AD4691_DEVICE_SETUP, AD4691_MANUAL_MODE, AD4691_MANUAL_MODE); =20 + ret =3D ad4691_write_osc_freq(st); + if (ret) + return ret; + ret =3D regmap_update_bits(st->regmap, AD4691_ADC_SETUP, AD4691_ADC_MODE_MASK, AD4691_CNV_BURST_MODE); if (ret) @@ -943,6 +1222,12 @@ static int ad4691_cnv_burst_buffer_preenable(struct i= io_dev *indio_dev) if (ret) goto err_unoptimize; =20 + iio_for_each_active_channel(indio_dev, i) { + ret =3D regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(i), st->osr[i]); + if (ret) + goto err_unoptimize; + } + ret =3D ad4691_enter_conversion_mode(st); if (ret) goto err_unoptimize; @@ -1122,6 +1407,12 @@ static int ad4691_cnv_burst_offload_buffer_postenabl= e(struct iio_dev *indio_dev) if (ret) return ret; =20 + iio_for_each_active_channel(indio_dev, bit) { + ret =3D regmap_write(st->regmap, AD4691_ACC_DEPTH_IN(bit), st->osr[bit]); + if (ret) + return ret; + } + ret =3D ad4691_enter_conversion_mode(st); if (ret) return ret; @@ -1536,11 +1827,15 @@ static int ad4691_config(struct ad4691_state *st) if (ret) return dev_err_probe(dev, ret, "Failed to write OSC_FREQ\n"); =20 + st->target_osc_freq_Hz =3D ad4691_osc_freqs_Hz[ad4691_samp_freq_start(st-= >info)]; + ret =3D regmap_update_bits(st->regmap, AD4691_ADC_SETUP, AD4691_ADC_MODE_MASK, AD4691_AUTONOMOUS_MODE); if (ret) return dev_err_probe(dev, ret, "Failed to write ADC_SETUP\n"); =20 + ad4691_precompute_samp_freq_avail(st); + return 0; } =20 @@ -1552,7 +1847,14 @@ static int ad4691_setup_triggered_buffer(struct iio_= dev *indio_dev, unsigned int i; int irq, ret; =20 - indio_dev->channels =3D st->info->sw_info->channels; + /* + * Manual mode exposes channels without the oversampling_ratio attribute + * because ACC_DEPTH_IN is not configured in manual mode. + */ + if (st->manual_mode) + indio_dev->channels =3D st->info->sw_info->manual_channels; + else + indio_dev->channels =3D st->info->sw_info->channels; indio_dev->num_channels =3D st->info->sw_info->num_channels; indio_dev->info =3D st->manual_mode ? &ad4691_manual_info : &ad4691_cnv_b= urst_info; =20 @@ -1634,7 +1936,18 @@ static int ad4691_setup_offload(struct iio_dev *indi= o_dev, =20 st->offload =3D spi_offload; =20 - indio_dev->channels =3D st->info->offload_info->channels; + /* + * CNV burst offload exposes oversampling_ratio (ACC_DEPTH_IN is + * configured per channel at buffer enable). Manual offload does not + * configure ACC_DEPTH_IN, so it uses a separate channel array + * without the oversampling_ratio attribute. Both paths use IIO_CPU + * (no .endianness annotation) because bits_per_word=3D16 causes the + * SPI Engine to produce native 16-bit DMA words. + */ + if (st->manual_mode) + indio_dev->channels =3D st->info->offload_info->manual_channels; + else + indio_dev->channels =3D st->info->offload_info->channels; indio_dev->num_channels =3D st->info->offload_info->num_channels; /* * Offload path uses DMA directly; no IIO trigger is involved, so @@ -1708,6 +2021,7 @@ static int ad4691_probe(struct spi_device *spi) st->info =3D spi_get_device_match_data(spi); if (!st->info) return -ENODEV; + memset(st->osr, 1, sizeof(st->osr)); =20 ret =3D devm_mutex_init(dev, &st->lock); if (ret) --=20 2.43.0 From nobody Mon May 25 02:57:45 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 7804F4DB559; Tue, 19 May 2026 12:20:42 +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=1779193242; cv=none; b=oV3rC9yTvcbWc/RGEhtJbFEjDDe/mIssI2AvJv0euGwoNJXrKq/l0eHAqFPazG5P5DHfE5uNkFWdaSovC3N9wCbHIoPpAAAeMgu0qDoNwLpFDMvaFGf+M8Nc/ruJM8AYBmRZWXJuvUNt5GUtXN0zQrhKYHqVP7EVtk2DXJkEdyg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779193242; c=relaxed/simple; bh=SPfamFp3YZ8OchMonJrLDO7yU7xsNqbzcTrUfCuqmDA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=DfonpHLKT03BzzdhS39lI26bkAOhItjtEF5cmsM4wP87U0LwiyjuOtDFGbZqGQHk9oXnBlEDtP3M8s1s+aenzoGAdirjN8MnD/V7QpSvjcu0KVVLFRBrytdr3Q5drV75b1/V/bYvoBSFSfPWgs1rNoniHWW5qjhk0irxDoYQlFI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=c0MVaVz4; 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="c0MVaVz4" Received: by smtp.kernel.org (Postfix) with ESMTPS id 3E672C2BD01; Tue, 19 May 2026 12:20:42 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779193242; bh=SPfamFp3YZ8OchMonJrLDO7yU7xsNqbzcTrUfCuqmDA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=c0MVaVz4ggX5PiunyqcOJeQHPRCxtobnZsiwwAujU+h6ARSuZrQ2OC8c1Q92sydwN dlIhJy5Pl5wzOj8nBrwG/owT3M8V1nPgH/GNq2PBq1E0p7mDON6ilnnIclY8YzfpvP kNMP23cE9VJL3AD5MLoClqYERHDd3hfRn9gY83xEcDyqGp8B5uQLe/QqVhVfr6sSp3 vJHSHY9ePxTBohSj6UPqoF7P99es5/jCX0RQTLQjLIY2TZAtXsi7tsWXSkxCgZjlKJ ypjYxCtOsiCapoNYTwELUs7qEL3lkIFTDtezT0iaC4LqmfSlFvQTcKKA6Uji//x+SW poHSQubMfaNjA== 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 31C5DCD4F5B; Tue, 19 May 2026 12:20:42 +0000 (UTC) From: Radu Sabau via B4 Relay Date: Tue, 19 May 2026 15:20:27 +0300 Subject: [PATCH v12 6/6] docs: iio: adc: ad4691: add driver documentation 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: <20260519-ad4692-multichannel-sar-adc-driver-v12-6-5b335162aa51@analog.com> References: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@analog.com> In-Reply-To: <20260519-ad4692-multichannel-sar-adc-driver-v12-0-5b335162aa51@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 , Jonathan Corbet , Shuah Khan Cc: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-pwm@vger.kernel.org, linux-gpio@vger.kernel.org, linux-doc@vger.kernel.org, Radu Sabau X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779193239; l=10118; i=radu.sabau@analog.com; s=20260220; h=from:subject:message-id; bh=ziKrV/hpGVkRKcYXOfs3HQoVIa+3tfQmISbmKkj1m1g=; b=tedYBSfrAytfjH5TvAfYi+HZzuI5aj+KZ4LnUJGnd+vV2GhVbTjr81lG7PSQ2J5iRKWpo7Kwe v4uVg9iIOKABCnK7trpYVJQeDjlFfi0T3a+aCAMg9sVkC6TG1DlQEpH 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 RST documentation for the AD4691 family ADC driver covering supported devices, IIO channels, operating modes, oversampling, reference voltage, LDO supply, reset, GP pins, SPI offload support, and buffer data format. Signed-off-by: Radu Sabau --- Documentation/iio/ad4691.rst | 226 +++++++++++++++++++++++++++++++++++++++= ++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 228 insertions(+) diff --git a/Documentation/iio/ad4691.rst b/Documentation/iio/ad4691.rst new file mode 100644 index 000000000000..5ec77846e317 --- /dev/null +++ b/Documentation/iio/ad4691.rst @@ -0,0 +1,226 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +AD4691 driver +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +ADC driver for Analog Devices Inc. AD4691 family of multichannel SAR ADCs. +The module name is ``ad4691``. + + +Supported devices +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The following chips are supported by this driver: + +* `AD4691 `_ =E2=80=94 16-= channel, 500 kSPS +* `AD4692 `_ =E2=80=94 16-= channel, 1 MSPS +* `AD4693 `_ =E2=80=94 8-c= hannel, 500 kSPS +* `AD4694 `_ =E2=80=94 8-c= hannel, 1 MSPS + + +IIO channels +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Each physical ADC input maps to one IIO voltage channel. The AD4691 and AD= 4692 +expose 16 channels (``voltage0`` through ``voltage15``); the AD4693 and AD= 4694 +expose 8 channels (``voltage0`` through ``voltage7``). + +All channels share a common scale (``in_voltage_scale``), derived from the +reference voltage. Each channel independently exposes: + +* ``in_voltageN_raw`` =E2=80=94 single-shot ADC result +* ``in_voltageN_sampling_frequency`` =E2=80=94 per-channel effective outpu= t rate, + defined as the internal oscillator frequency divided by the channel's + oversampling ratio. Writing this attribute selects the nearest achievable + rate for the current OSR; the value read back reflects the actual rate a= fter + snapping to the closest valid oscillator entry. +* ``in_voltageN_sampling_frequency_available`` =E2=80=94 list of achievabl= e effective + rates for the channel's current oversampling ratio. The list updates + dynamically when the oversampling ratio changes. + +The following attributes are only available in CNV Burst Mode: + +* ``in_voltageN_oversampling_ratio`` =E2=80=94 per-channel hardware oversa= mpling depth; + see `Oversampling`_ below. +* ``in_voltageN_oversampling_ratio_available`` =E2=80=94 valid ratios: 1, = 2, 4, 8, 16, + 32. + + +Operating modes +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The driver supports two operating modes, selected automatically from the +device tree at probe time. + +Manual Mode +----------- + +Selected when no ``pwms`` property is present in the device tree. The CNV = pin +is tied to the SPI chip-select: every CS assertion triggers a conversion a= nd +returns the previous result. A user-defined IIO trigger (e.g. hrtimer trig= ger) +drives the buffer. + +Oversampling is not supported in Manual Mode. + +CNV Burst Mode +-------------- + +Selected when a ``pwms`` property is present in the device tree. A PWM dri= ves +the CNV pin at the configured conversion rate. A GP pin wired to the SoC a= nd +declared in the device tree signals DATA_READY at the end of each burst, +triggering a readout of all active channel results into the IIO buffer. + +The buffer output rate is controlled by the ``sampling_frequency`` attribu= te +on the IIO buffer. In practice the PWM rate should be set low enough to al= low +the SPI readout to complete before the next conversion burst begins. + +Autonomous Mode (idle / single-shot) +------------------------------------- + +When the IIO buffer is disabled, ``in_voltageN_raw`` reads perform a single +conversion on the requested channel using the internal oscillator. The +oscillator is started and stopped around each read to save power. + + +Oversampling +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +In CNV Burst Mode each channel has an independent hardware accumulator that +averages a configurable number of successive conversions. The result is al= ways +a 16-bit mean, so the buffer data type (shown in ``buffer0/in_voltageN_typ= e``) +is unaffected by the oversampling ratio. Valid ratios are 1, 2, 4, 8, 16 a= nd +32; the default is 1 (no averaging). Oversampling is not supported in Manu= al +Mode. + +.. code-block:: bash + + # Set oversampling ratio to 16 on channel 0 + echo 16 > /sys/bus/iio/devices/iio:device0/in_voltage0_oversampling_ra= tio + + # Read the resulting effective sampling frequency + cat /sys/bus/iio/devices/iio:device0/in_voltage0_sampling_frequency + +Writing ``oversampling_ratio`` stores the new depth for that channel and +snaps the internal oscillator to the largest valid table entry that is both +less than or equal to ``old_effective_rate =C3=97 new_osr`` and evenly div= isible +by ``new_osr``. This preserves an integer read-back of +``in_voltageN_sampling_frequency`` after the change and keeps the oscillat= or +as close as possible to the previous effective rate. + +All channels share one internal oscillator. Writing ``sampling_frequency``= for +any channel updates the oscillator and therefore affects the effective rate +read back from all other channels. + + +Reference voltage +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The driver supports two reference configurations, mutually exclusive: + +* **External reference** (``ref-supply``): a voltage between 2.4 V and 5.2= 5 V + supplied externally. +* **Buffered internal reference** (``refin-supply``): an internal reference + buffer is enabled by the driver. + +Exactly one of ``ref-supply`` or ``refin-supply`` must be present in the +device tree. The reference voltage determines the full-scale range reported +via ``in_voltage_scale``. + + +LDO supply +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The chip contains an internal LDO that powers part of the analog front-end. +The supply configuration is mutually exclusive: + +* **External VDD** (``vdd-supply``): an external 1.8 V supply is used dire= ctly; + the internal LDO is disabled. +* **Internal LDO** (``ldo-in-supply``): the internal LDO is enabled and fed + from the ``ldo-in`` regulator. Use this when no external 1.8 V VDD is pr= esent. + +Exactly one of ``vdd-supply`` or ``ldo-in-supply`` must be provided. + + +Reset +=3D=3D=3D=3D=3D + +The driver supports two reset mechanisms: + +* **Hardware reset** (``reset-gpios`` in device tree): the GPIO line is + asserted for at least 300 =C2=B5s then deasserted at probe. +* **Software reset** (fallback when ``reset-gpios`` is absent): written + automatically at probe. + + +GP pins and interrupts +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The chip exposes up to four general-purpose (GP) pins. In CNV Burst Mode +(non-offload), one GP pin must be wired to an interrupt-capable SoC input = and +declared in the device tree using the ``interrupts`` and ``interrupt-names= `` +properties. The ``interrupt-names`` value identifies which GP pin is used +(``"gp0"`` through ``"gp3"``). + +Example device tree fragment:: + + adc@0 { + compatible =3D "adi,ad4692"; + ... + interrupt-parent =3D <&gpio0>; + interrupts =3D <17 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names =3D "gp0"; + }; + + +SPI offload support +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +When a SPI offload engine (e.g. the AXI SPI Engine) is present, the driver +uses DMA-backed transfers for CPU-independent, high-throughput data captur= e. +SPI offload is detected automatically at probe; if no offload hardware is +available the driver falls back to the software triggered-buffer path. + +Two SPI offload sub-modes exist: + +CNV Burst offload +----------------- + +Used when a ``pwms`` property is present and SPI offload is available. The= PWM +drives CNV at the configured rate; on DATA_READY the offload engine reads = all +active channel results and streams them directly to the IIO DMA buffer wit= h no +CPU involvement. The GP pin used as DATA_READY trigger is supplied by the +trigger-source consumer at buffer enable time; no ``interrupt-names`` entr= y is +required. + +Manual offload +-------------- + +Used when no ``pwms`` property is present and SPI offload is available. A +periodic SPI offload trigger controls the conversion rate and the offload = engine +streams results directly to the IIO DMA buffer. + +The ``sampling_frequency`` attribute on the IIO buffer controls the trigger +rate (in Hz). The initial rate is 100 kHz. + +Oversampling is not supported in Manual Mode. + + +Buffer data format +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The sample format in the IIO buffer depends on whether SPI offload is in u= se. + +Software triggered-buffer path (no SPI offload) +------------------------------------------------ + +Each active channel occupies one 16-bit big-endian slot (``storagebits=3D1= 6``, +``endianness=3Dbe``). Active channels are packed densely in scan-index ord= er, +followed by a 64-bit software timestamp appended by the IIO core. + +SPI offload path +---------------- + +Each active channel occupies one 16-bit CPU-native slot (``storagebits=3D1= 6``, +``endianness=3Dcpu``). The SPI offload engine streams 16-bit words directl= y from +the SPI Engine into the DMA buffer; no software timestamp is appended. diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst index ba3e609c6a13..007e0a1fcc5a 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -23,6 +23,7 @@ Industrial I/O Kernel Drivers ad4000 ad4030 ad4062 + ad4691 ad4695 ad7191 ad7380 diff --git a/MAINTAINERS b/MAINTAINERS index 020c1ffae31b..3fbac296b667 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1488,6 +1488,7 @@ L: linux-iio@vger.kernel.org S: Supported W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/adc/adi,ad4691.yaml +F: Documentation/iio/ad4691.rst F: drivers/iio/adc/ad4691.c =20 ANALOG DEVICES INC AD4695 DRIVER --=20 2.43.0