* [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 16:33 ` Conor Dooley
2026-08-28 6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
` (8 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Add a generic excitation-current-chopping property. This is a feature
seen frequently in ADCs that are designed to ratiometrically measure
resistive loads, such as RTDs and strain gauges. Enabling this can
reduce any error due to excitation current mismatch by swapping
excitation sources of alternate conversions. This can have side-effects
in timing and filter response, so it is not always desirable to enable
this feature in some applications. Therefore, it is best to make this a
property that can be enabled or disabled in the device tree.
Suggested-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
Documentation/devicetree/bindings/iio/adc/adc.yaml | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/adc.yaml b/Documentation/devicetree/bindings/iio/adc/adc.yaml
index b673eaa5d072..fe20aa8f1bfc 100644
--- a/Documentation/devicetree/bindings/iio/adc/adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adc.yaml
@@ -109,6 +109,14 @@ properties:
sources. The index in the array corresponds to the same index in the
excitation-channels array.
+ excitation-current-chopping:
+ type: boolean
+ description:
+ If set, the excitation current sources are swapped and averaged on every
+ other conversion to reduce current mismatch. This generally comes at the
+ expense of added settling time between conversions, so should be omitted
+ on systems with high-frequency signals or high data rate requirements.
+
burn-out-current-nanoamp:
description:
Burn-out current sources provide current to the channel's input pins for
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property
2026-08-28 6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
@ 2026-08-28 16:33 ` Conor Dooley
0 siblings, 0 replies; 14+ messages in thread
From: Conor Dooley @ 2026-08-28 16:33 UTC (permalink / raw)
To: Kurt Borja
Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-28 6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 16:39 ` Conor Dooley
2026-08-28 6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
` (7 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
integrated PGA, internal reference, excitation and burn-out current
sources for sensor biasing and diagnostics. The ADS1263 is compatible
with ADS1262, but includes a second auxiliary ADC (ADC2) to perform main
channel (ADC1) cross-checking measurements, system background
measurements, or temperature compensation of the primary sensor.
Both parts can configure per-channel voltage reference source,
excitation current sources (IDAC), plus input and IDAC chopping for
offset and IDAC mismatch cancellation. This lets the device drive and
ratiometrically measure RTDs and other resistive sensors.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../devicetree/bindings/iio/adc/ti,ads1262.yaml | 376 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 382 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
new file mode 100644
index 000000000000..7e26572388e4
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
@@ -0,0 +1,376 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TI ADS1262/ADS1263 analog to digital converter
+
+maintainers:
+ - Kurt Borja <kuurtb@gmail.com>
+
+description: |
+ The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an
+ integrated PGA, reference, and internal fault monitors. The ADS1263 integrates
+ an auxiliary, 24-bit, ΔΣ ADC intended for background measurements.
+
+ Datasheets:
+ - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf
+
+properties:
+ compatible:
+ oneOf:
+ - const: ti,ads1262
+ - items:
+ - const: ti,ads1263
+ - const: ti,ads1262
+
+ reg:
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ spi-max-frequency:
+ maximum: 8000000
+
+ spi-cpha: true
+
+ interrupts:
+ minItems: 1
+ maxItems: 2
+ description: Data ready (DRDY) interrupt lines.
+
+ interrupt-names:
+ description:
+ The data ready (DRDY) signal has two independent interrupt lines. A
+ dedicated DRDY pin and a pin shared with the serial interface MISO pin,
+ which is called DOUT/DRDY.
+ minItems: 1
+ maxItems: 2
+ items:
+ enum: [drdy, doutdrdy]
+
+ start-gpios:
+ description: Start conversion control.
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ dvdd-supply:
+ description: Digital power supply.
+
+ avdd-supply:
+ description:
+ Analog power supply. In bipolar supply configurations, the reported
+ voltage should be in reference to DGND.
+
+ avss-supply:
+ description:
+ Negative analog power supply for bipolar configurations. AVSS can only be
+ at or below the ground reference (DGND). If not described, AVSS is assumed
+ to be connected to ground (0V).
+
+ clocks:
+ maxItems: 1
+
+ '#io-channel-cells':
+ minimum: 1
+ maximum: 2
+ description: |
+ The first cell selects the channel by its reg. The second cell selects
+ between the main ADC (ADC1) and the auxiliary ADC (ADC2) as follows:
+ 0: ADC1
+ 1: ADC2
+
+ '#gpio-cells':
+ const: 2
+
+ gpio-controller: true
+
+ regulators:
+ type: object
+ description:
+ List of regulators provided by this chip.
+
+ properties:
+ vbias:
+ $ref: /schemas/regulator/regulator.yaml#
+ type: object
+ description:
+ Level-shift voltage output on the AINCOM pin. Its output is the
+ mid-voltage of the analog supply, (AVDD + AVSS) / 2, and is used to
+ shift floating sensors to within the ADC input range.
+ unevaluatedProperties: false
+
+ refout:
+ $ref: /schemas/regulator/regulator.yaml#
+ type: object
+ description:
+ Buffered internal voltage reference output on the REFOUT pin.
+ unevaluatedProperties: false
+
+ additionalProperties: false
+
+patternProperties:
+ "^ain([0-9]|com)-supply$":
+ description:
+ Common-mode voltage supply connected to AIN<N> or AINCOM.
+
+ "^refp[1-3]-supply$":
+ description:
+ Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
+ REFP3 (AIN4). If this regulator is not described and a channel selects
+ it as a reference source, then it's assumed to be connected to ground
+ (0V).
+
+ "^refn[1-3]-supply$":
+ description:
+ Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
+ REFN3 (AIN5). If this regulator is not described and a channel selects
+ it as a reference source, then it's assumed to be connected to ground
+ (0V).
+
+ "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
+ description:
+ Magnitude of the external reference resistor connected between REFP<N>
+ and REFN<M>. In ratiometric configurations, such as RTD measurements, the
+ IDAC excitation current returns through this resistor, generating the
+ reference voltage for the conversion.
+
+ "^channel@[0-9a-f]+$":
+ $ref: /schemas/iio/adc/adc.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ single-channel:
+ minimum: 0
+ maximum: 10
+
+ common-mode-channel:
+ minimum: 0
+ maximum: 10
+ default: 10
+
+ diff-channels:
+ items:
+ minimum: 0
+ maximum: 10
+
+ input-chopping: true
+
+ reference-sources:
+ minItems: 2
+ description:
+ Indicates the reference sources for this channel. The first and second
+ items are the positive (REFP) and negative (REFN) sources of the main
+ ADC (ADC1). The third item is the reference source of the secondary
+ ADC (ADC2) and must always have a positive differential voltage.
+ items:
+ - enum: [internal-p, refp1, refp2, refp3, avdd]
+ - enum: [internal-n, refn1, refn2, refn3, avss]
+ - enum: [internal, refp1-refn1, refp2-refn2, refp3-refn3, avdd-avss]
+
+ ti,reference-reversal:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Indicates that the ADC1 (this has no effect on ADC2) reference voltage
+ for this channel has negative polarity and thus should be internally
+ reversed.
+
+ excitation-channels:
+ minItems: 1
+ maxItems: 2
+ description:
+ Selects the pins for the IDAC sources from 0 (AIN0) to 10 (AINCOM).
+ The first value corresponds to IDAC1 and the second to IDAC2.
+ items:
+ minimum: 0
+ maximum: 10
+
+ excitation-current-nanoamp:
+ minItems: 1
+ maxItems: 2
+ description:
+ The first value corresponds to IDAC1 and the second to IDAC2.
+ items:
+ enum: [50000, 100000, 250000, 500000, 750000, 1000000, 1500000,
+ 2000000, 2500000, 3000000]
+
+ excitation-current-chopping: true
+
+ burn-out-current-nanoamp:
+ description:
+ Selects current magnitude for the sensor bias current source.
+ enum: [500, 2000, 10000, 50000, 200000]
+
+ ti,burn-out-resistor:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Instead of a fixed current, the sensor bias (burn-out) current source
+ can be pulled using an internal 10 MΩ resistor.
+
+ ti,burn-out-polarity:
+ $ref: /schemas/types.yaml#/definitions/string
+ description:
+ The sensor bias can be configured to either pull-up or pull-down mode.
+ In pull-up mode, the current flows into the positive input and flows
+ out of the negative input. In pull-down mode, the polarities are
+ reversed.
+ enum: [pull-up, pull-down]
+ default: pull-up
+
+ dependencies:
+ excitation-channels: [excitation-current-nanoamp]
+ excitation-current-nanoamp: [excitation-channels]
+ burn-out-current-nanoamp:
+ not:
+ required:
+ - ti,burn-out-resistor
+
+ required:
+ - reg
+
+ allOf:
+ - if:
+ properties:
+ excitation-channels:
+ maxItems: 1
+ required:
+ - excitation-channels
+ then:
+ properties:
+ excitation-current-nanoamp:
+ maxItems: 1
+ else:
+ properties:
+ excitation-current-nanoamp:
+ minItems: 2
+
+ oneOf:
+ - required: [single-channel]
+ - required: [diff-channels]
+
+dependencies:
+ interrupts: [interrupt-names]
+ interrupts-extended: [interrupt-names]
+
+required:
+ - compatible
+ - reg
+ - spi-cpha
+ - avdd-supply
+ - dvdd-supply
+ - '#address-cells'
+ - '#size-cells'
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: ti,ads1263
+ then:
+ properties:
+ '#io-channel-cells':
+ minimum: 1
+ maximum: 2
+ patternProperties:
+ "^channel@[0-9]+$":
+ properties:
+ reference-sources:
+ minItems: 3
+ maxItems: 3
+ default: [internal-p, internal-n, internal]
+ else:
+ properties:
+ '#io-channel-cells':
+ const: 1
+ patternProperties:
+ "^channel@[0-9]+$":
+ properties:
+ reference-sources:
+ minItems: 2
+ maxItems: 2
+ default: [internal-p, internal-n]
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "ti,ads1262";
+ reg = <0>;
+ spi-max-frequency = <8000000>;
+ spi-cpha;
+ avdd-supply = <&avdd>;
+ dvdd-supply = <&dvdd>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
+ interrupt-names = "drdy";
+
+ /* Typical common mode voltage configuration */
+ aincom-supply = <&ads1262_vbias>;
+
+ regulators {
+ ads1262_vbias: vbias { };
+ };
+
+ channel@0 {
+ reg = <0>;
+ single-channel = <0>;
+ /* The VBIAS is enabled on pin 10 (AINCOM) */
+ common-mode-channel = <10>;
+ };
+ };
+ };
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "ti,ads1263", "ti,ads1262";
+ reg = <0>;
+ spi-max-frequency = <8000000>;
+ spi-cpha;
+ avdd-supply = <&avdd>;
+ dvdd-supply = <&dvdd>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
+ interrupt-names = "drdy";
+
+ ti,refp2-refn2-resistor-ohms = <3520>;
+
+ channel@0 {
+ reg = <0>;
+ diff-channels = <4 5>;
+ reference-sources = "refp2", "refn2", "refp2-refn2";
+ excitation-channels = <1 6>;
+ excitation-current-nanoamp = <500000 500000>;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index f3be900e5b9d..7593e33c5346 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27019,6 +27019,12 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml
F: drivers/iio/adc/ti-ads1018.c
+TI ADS1262 ADC DRIVER
+M: Kurt Borja <kuurtb@gmail.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
+
TI ADS7924 ADC DRIVER
M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
L: linux-iio@vger.kernel.org
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-08-28 6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-08-28 16:39 ` Conor Dooley
0 siblings, 0 replies; 14+ messages in thread
From: Conor Dooley @ 2026-08-28 16:39 UTC (permalink / raw)
To: Kurt Borja
Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
devicetree, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 13996 bytes --]
On Fri, Aug 28, 2026 at 01:38:17AM -0500, Kurt Borja wrote:
> The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
> integrated PGA, internal reference, excitation and burn-out current
> sources for sensor biasing and diagnostics. The ADS1263 is compatible
> with ADS1262, but includes a second auxiliary ADC (ADC2) to perform main
> channel (ADC1) cross-checking measurements, system background
> measurements, or temperature compensation of the primary sensor.
>
> Both parts can configure per-channel voltage reference source,
> excitation current sources (IDAC), plus input and IDAC chopping for
> offset and IDAC mismatch cancellation. This lets the device drive and
> ratiometrically measure RTDs and other resistive sensors.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> .../devicetree/bindings/iio/adc/ti,ads1262.yaml | 376 +++++++++++++++++++++
> MAINTAINERS | 6 +
> 2 files changed, 382 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> new file mode 100644
> index 000000000000..7e26572388e4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> @@ -0,0 +1,376 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: TI ADS1262/ADS1263 analog to digital converter
> +
> +maintainers:
> + - Kurt Borja <kuurtb@gmail.com>
> +
> +description: |
> + The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an
> + integrated PGA, reference, and internal fault monitors. The ADS1263 integrates
> + an auxiliary, 24-bit, ΔΣ ADC intended for background measurements.
> +
> + Datasheets:
> + - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf
> +
> +properties:
> + compatible:
> + oneOf:
> + - const: ti,ads1262
> + - items:
> + - const: ti,ads1263
> + - const: ti,ads1262
> +
> + reg:
> + maxItems: 1
> +
> + '#address-cells':
> + const: 1
> +
> + '#size-cells':
> + const: 0
> +
> + spi-max-frequency:
> + maximum: 8000000
> +
> + spi-cpha: true
> +
> + interrupts:
> + minItems: 1
> + maxItems: 2
> + description: Data ready (DRDY) interrupt lines.
> +
> + interrupt-names:
> + description:
> + The data ready (DRDY) signal has two independent interrupt lines. A
> + dedicated DRDY pin and a pin shared with the serial interface MISO pin,
> + which is called DOUT/DRDY.
> + minItems: 1
> + maxItems: 2
> + items:
> + enum: [drdy, doutdrdy]
> +
> + start-gpios:
> + description: Start conversion control.
> + maxItems: 1
> +
> + reset-gpios:
> + maxItems: 1
> +
> + dvdd-supply:
> + description: Digital power supply.
> +
> + avdd-supply:
> + description:
> + Analog power supply. In bipolar supply configurations, the reported
> + voltage should be in reference to DGND.
> +
> + avss-supply:
> + description:
> + Negative analog power supply for bipolar configurations. AVSS can only be
> + at or below the ground reference (DGND). If not described, AVSS is assumed
> + to be connected to ground (0V).
> +
> + clocks:
> + maxItems: 1
clock doesn't appear mandatory, what does it do?
> +
> + '#io-channel-cells':
> + minimum: 1
> + maximum: 2
What's the use case for actually setting this as a single cell? Kinda
confused by it.
Otherwise, quite frankly, the binding is too complicated for me! I'll
have to leave it up to the IIO folks to cover whether what's being done
is sane.
Cheers,
Conor.
> + description: |
> + The first cell selects the channel by its reg. The second cell selects
> + between the main ADC (ADC1) and the auxiliary ADC (ADC2) as follows:
> + 0: ADC1
> + 1: ADC2
> +
> + '#gpio-cells':
> + const: 2
> +
> + gpio-controller: true
> +
> + regulators:
> + type: object
> + description:
> + List of regulators provided by this chip.
> +
> + properties:
> + vbias:
> + $ref: /schemas/regulator/regulator.yaml#
> + type: object
> + description:
> + Level-shift voltage output on the AINCOM pin. Its output is the
> + mid-voltage of the analog supply, (AVDD + AVSS) / 2, and is used to
> + shift floating sensors to within the ADC input range.
> + unevaluatedProperties: false
> +
> + refout:
> + $ref: /schemas/regulator/regulator.yaml#
> + type: object
> + description:
> + Buffered internal voltage reference output on the REFOUT pin.
> + unevaluatedProperties: false
> +
> + additionalProperties: false
> +
> +patternProperties:
> + "^ain([0-9]|com)-supply$":
> + description:
> + Common-mode voltage supply connected to AIN<N> or AINCOM.
> +
> + "^refp[1-3]-supply$":
> + description:
> + Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
> + REFP3 (AIN4). If this regulator is not described and a channel selects
> + it as a reference source, then it's assumed to be connected to ground
> + (0V).
> +
> + "^refn[1-3]-supply$":
> + description:
> + Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
> + REFN3 (AIN5). If this regulator is not described and a channel selects
> + it as a reference source, then it's assumed to be connected to ground
> + (0V).
> +
> + "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
> + description:
> + Magnitude of the external reference resistor connected between REFP<N>
> + and REFN<M>. In ratiometric configurations, such as RTD measurements, the
> + IDAC excitation current returns through this resistor, generating the
> + reference voltage for the conversion.
> +
> + "^channel@[0-9a-f]+$":
> + $ref: /schemas/iio/adc/adc.yaml#
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + maxItems: 1
> +
> + single-channel:
> + minimum: 0
> + maximum: 10
> +
> + common-mode-channel:
> + minimum: 0
> + maximum: 10
> + default: 10
> +
> + diff-channels:
> + items:
> + minimum: 0
> + maximum: 10
> +
> + input-chopping: true
> +
> + reference-sources:
> + minItems: 2
> + description:
> + Indicates the reference sources for this channel. The first and second
> + items are the positive (REFP) and negative (REFN) sources of the main
> + ADC (ADC1). The third item is the reference source of the secondary
> + ADC (ADC2) and must always have a positive differential voltage.
> + items:
> + - enum: [internal-p, refp1, refp2, refp3, avdd]
> + - enum: [internal-n, refn1, refn2, refn3, avss]
> + - enum: [internal, refp1-refn1, refp2-refn2, refp3-refn3, avdd-avss]
> +
> + ti,reference-reversal:
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Indicates that the ADC1 (this has no effect on ADC2) reference voltage
> + for this channel has negative polarity and thus should be internally
> + reversed.
> +
> + excitation-channels:
> + minItems: 1
> + maxItems: 2
> + description:
> + Selects the pins for the IDAC sources from 0 (AIN0) to 10 (AINCOM).
> + The first value corresponds to IDAC1 and the second to IDAC2.
> + items:
> + minimum: 0
> + maximum: 10
> +
> + excitation-current-nanoamp:
> + minItems: 1
> + maxItems: 2
> + description:
> + The first value corresponds to IDAC1 and the second to IDAC2.
> + items:
> + enum: [50000, 100000, 250000, 500000, 750000, 1000000, 1500000,
> + 2000000, 2500000, 3000000]
> +
> + excitation-current-chopping: true
> +
> + burn-out-current-nanoamp:
> + description:
> + Selects current magnitude for the sensor bias current source.
> + enum: [500, 2000, 10000, 50000, 200000]
> +
> + ti,burn-out-resistor:
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Instead of a fixed current, the sensor bias (burn-out) current source
> + can be pulled using an internal 10 MΩ resistor.
> +
> + ti,burn-out-polarity:
> + $ref: /schemas/types.yaml#/definitions/string
> + description:
> + The sensor bias can be configured to either pull-up or pull-down mode.
> + In pull-up mode, the current flows into the positive input and flows
> + out of the negative input. In pull-down mode, the polarities are
> + reversed.
> + enum: [pull-up, pull-down]
> + default: pull-up
> +
> + dependencies:
> + excitation-channels: [excitation-current-nanoamp]
> + excitation-current-nanoamp: [excitation-channels]
> + burn-out-current-nanoamp:
> + not:
> + required:
> + - ti,burn-out-resistor
> +
> + required:
> + - reg
> +
> + allOf:
> + - if:
> + properties:
> + excitation-channels:
> + maxItems: 1
> + required:
> + - excitation-channels
> + then:
> + properties:
> + excitation-current-nanoamp:
> + maxItems: 1
> + else:
> + properties:
> + excitation-current-nanoamp:
> + minItems: 2
> +
> + oneOf:
> + - required: [single-channel]
> + - required: [diff-channels]
> +
> +dependencies:
> + interrupts: [interrupt-names]
> + interrupts-extended: [interrupt-names]
> +
> +required:
> + - compatible
> + - reg
> + - spi-cpha
> + - avdd-supply
> + - dvdd-supply
> + - '#address-cells'
> + - '#size-cells'
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
> + - if:
> + properties:
> + compatible:
> + contains:
> + const: ti,ads1263
> + then:
> + properties:
> + '#io-channel-cells':
> + minimum: 1
> + maximum: 2
> + patternProperties:
> + "^channel@[0-9]+$":
> + properties:
> + reference-sources:
> + minItems: 3
> + maxItems: 3
> + default: [internal-p, internal-n, internal]
> + else:
> + properties:
> + '#io-channel-cells':
> + const: 1
> + patternProperties:
> + "^channel@[0-9]+$":
> + properties:
> + reference-sources:
> + minItems: 2
> + maxItems: 2
> + default: [internal-p, internal-n]
> +
> +unevaluatedProperties: false
> +
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + adc@0 {
> + compatible = "ti,ads1262";
> + reg = <0>;
> + spi-max-frequency = <8000000>;
> + spi-cpha;
> + avdd-supply = <&avdd>;
> + dvdd-supply = <&dvdd>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
> + interrupt-names = "drdy";
> +
> + /* Typical common mode voltage configuration */
> + aincom-supply = <&ads1262_vbias>;
> +
> + regulators {
> + ads1262_vbias: vbias { };
> + };
> +
> + channel@0 {
> + reg = <0>;
> + single-channel = <0>;
> + /* The VBIAS is enabled on pin 10 (AINCOM) */
> + common-mode-channel = <10>;
> + };
> + };
> + };
> +
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + adc@0 {
> + compatible = "ti,ads1263", "ti,ads1262";
> + reg = <0>;
> + spi-max-frequency = <8000000>;
> + spi-cpha;
> + avdd-supply = <&avdd>;
> + dvdd-supply = <&dvdd>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
> + interrupt-names = "drdy";
> +
> + ti,refp2-refn2-resistor-ohms = <3520>;
> +
> + channel@0 {
> + reg = <0>;
> + diff-channels = <4 5>;
> + reference-sources = "refp2", "refn2", "refp2-refn2";
> + excitation-channels = <1 6>;
> + excitation-current-nanoamp = <500000 500000>;
> + };
> + };
> + };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f3be900e5b9d..7593e33c5346 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27019,6 +27019,12 @@ S: Maintained
> F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml
> F: drivers/iio/adc/ti-ads1018.c
>
> +TI ADS1262 ADC DRIVER
> +M: Kurt Borja <kuurtb@gmail.com>
> +L: linux-iio@vger.kernel.org
> +S: Maintained
> +F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> +
> TI ADS7924 ADC DRIVER
> M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> L: linux-iio@vger.kernel.org
>
> --
> 2.55.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-28 6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
2026-08-28 6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 8:09 ` Andy Shevchenko
2026-08-28 6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
` (6 subsequent siblings)
9 siblings, 1 reply; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Add the ti-ads1262 driver with initial support for the primary ADC
(ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
and interoperability considerations were taken into account.
Various features such as accurate timeout delays, per-channel reference
sources, scale, offset, settling latency, excitation currents and
diagnostics are intentionally left out for future support.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads1262.c | 938 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 951 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 7593e33c5346..0607aa90a6b1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27024,6 +27024,7 @@ M: Kurt Borja <kuurtb@gmail.com>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
+F: drivers/iio/adc/ti-ads1262.c
TI ADS7924 ADC DRIVER
M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 38e212a6f789..e51ce42bce94 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1860,6 +1860,17 @@ config TI_ADS124S08
This driver can also be built as a module. If so, the module will be
called ti-ads124s08.
+config TI_ADS1262
+ tristate "Texas Instruments ADS1262"
+ depends on SPI
+ select REGMAP
+ help
+ If you say yes here you get support for Texas Instruments ADS1262 and
+ ADS1263 ADC chips.
+
+ This driver can also be built as a module. If so, the module will be
+ called ti-ads1262.
+
config TI_ADS1298
tristate "Texas Instruments ADS1298"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 9271a5e1ba01..989bed6fba3d 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
+obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o
obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
new file mode 100644
index 000000000000..d919144c23cb
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -0,0 +1,938 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments ADS1262 ADC driver
+ *
+ * Copyright (C) 2026 Kurt Borja <kuurtb@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/compiler_attributes.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/lockdep.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+
+#define ADS1262_OPCODE_NOP 0x00
+#define ADS1262_OPCODE_RESET 0x06
+#define ADS1262_OPCODE_START1 0x08
+#define ADS1262_OPCODE_STOP1 0x0A
+#define ADS1262_OPCODE_START2 0x0C
+#define ADS1262_OPCODE_STOP2 0x0E
+#define ADS1262_OPCODE_RDATA1 0x12
+#define ADS1262_OPCODE_RDATA2 0x14
+#define ADS1262_OPCODE_SYOCAL1 0x16
+#define ADS1262_OPCODE_SYGCAL1 0x17
+#define ADS1262_OPCODE_SFOCAL1 0x19
+#define ADS1262_OPCODE_SYOCAL2 0x1B
+#define ADS1262_OPCODE_SYGCAL2 0x1C
+#define ADS1262_OPCODE_SFOCAL2 0x1E
+#define ADS1262_OPCODE_RREG 0x20
+#define ADS1262_OPCODE_WREG 0x40
+
+#define ADS1262_ID_REG 0x00
+#define ADS1262_DEV_ID_MASK GENMASK(7, 5)
+#define ADS1262_DEV_ID 0
+#define ADS1263_DEV_ID 1
+#define ADS1262_REV_ID_MASK GENMASK(4, 0)
+
+#define ADS1262_POWER_REG 0x01
+#define ADS1262_POWER_RESET_MASK BIT(4)
+#define ADS1262_POWER_VBIAS_MASK BIT(1)
+#define ADS1262_POWER_INTREF_MASK BIT(0)
+
+#define ADS1262_INTERFACE_REG 0x02
+#define ADS1262_INTERFACE_TIMEOUT_MASK BIT(3)
+#define ADS1262_INTERFACE_STATUS_MASK BIT(2)
+#define ADS1262_INTERFACE_CRC_MASK GENMASK(1, 0)
+
+#define ADS1262_MODE0_REG 0x03
+#define ADS1262_MODE0_REFREV_MASK BIT(7)
+#define ADS1262_MODE0_RUNMODE_MASK BIT(6)
+#define ADS1262_RUNMODE_CONTINUOUS 0
+#define ADS1262_RUNMODE_PULSE 1
+#define ADS1262_MODE0_IDAC_CHOP_MASK BIT(5)
+#define ADS1262_MODE0_INPUT_CHOP_MASK BIT(4)
+#define ADS1262_MODE0_DELAY_MASK GENMASK(3, 0)
+
+#define ADS1262_MODE1_REG 0x04
+#define ADS1262_MODE1_FILTER_MASK GENMASK(7, 5)
+#define ADS1262_FILTER_FIR 4
+#define ADS1262_MODE1_SBADC_MASK BIT(4)
+#define ADS1262_MODE1_SBPOL_MASK BIT(3)
+#define ADS1262_MODE1_SBMAG_MASK GENMASK(2, 0)
+
+#define ADS1262_MODE2_REG 0x05
+#define ADS1262_MODE2_BYPASS_MASK BIT(7)
+#define ADS1262_MODE2_GAIN_MASK GENMASK(6, 4)
+#define ADS1262_MODE2_DR_MASK GENMASK(3, 0)
+#define ADS1262_DR_2_5_SPS 0
+#define ADS1262_DR_5_SPS 1
+#define ADS1262_DR_10_SPS 2
+#define ADS1262_DR_16_6_SPS 3
+#define ADS1262_DR_20_SPS 4
+#define ADS1262_DR_50_SPS 5
+#define ADS1262_DR_60_SPS 6
+#define ADS1262_DR_100_SPS 7
+#define ADS1262_DR_400_SPS 8
+#define ADS1262_DR_1200_SPS 9
+#define ADS1262_DR_2400_SPS 10
+#define ADS1262_DR_4800_SPS 11
+#define ADS1262_DR_7200_SPS 12
+#define ADS1262_DR_14400_SPS 13
+#define ADS1262_DR_19200_SPS 14
+#define ADS1262_DR_38400_SPS 15
+
+#define ADS1262_INPMUX_REG 0x06
+#define ADS1262_INPMUX_MUXP_MASK GENMASK(7, 4)
+#define ADS1262_INPMUX_MUXN_MASK GENMASK(3, 0)
+#define ADS1262_INPMUX_AIN1 1
+#define ADS1262_INPMUX_AINCOM 10
+#define ADS1262_INPMUX_TEMP 11
+#define ADS1262_INPMUX_AVDD 12
+#define ADS1262_INPMUX_DVDD 13
+#define ADS1262_INPMUX_TDAC 14
+#define ADS1262_INPMUX_FLOAT 15
+
+#define ADS1262_OFCAL0_REG 0x07
+#define ADS1262_OFCAL1_REG 0x08
+#define ADS1262_OFCAL2_REG 0x09
+#define ADS1262_FSCAL0_REG 0x0A
+#define ADS1262_FSCAL1_REG 0x0B
+#define ADS1262_FSCAL2_REG 0x0C
+
+#define ADS1262_IDACMUX_REG 0x0D
+#define ADS1262_IDACMUX_MUX2_MASK GENMASK(7, 4)
+#define ADS1262_IDACMUX_MUX1_MASK GENMASK(3, 0)
+#define ADS1262_IDACMUX_NO_CONN 0b1011
+
+#define ADS1262_IDACMAG_REG 0x0E
+#define ADS1262_IDACMAG_MAG2_MASK GENMASK(7, 4)
+#define ADS1262_IDACMAG_MAG1_MASK GENMASK(3, 0)
+
+#define ADS1262_REFMUX_REG 0x0F
+#define ADS1262_REFMUX_RMUXP_MASK GENMASK(5, 3)
+#define ADS1262_REFMUX_RMUXN_MASK GENMASK(2, 0)
+
+#define ADS1262_TDACP_REG 0x10
+#define ADS1262_TDACP_OUTP_MASK BIT(7)
+#define ADS1262_TDACP_MAGP_MASK GENMASK(4, 0)
+
+#define ADS1262_TDACN_REG 0x11
+#define ADS1262_TDACN_OUTN_MASK BIT(7)
+#define ADS1262_TDACN_MAGN_MASK GENMASK(4, 0)
+
+#define ADS1262_GPIOCON_REG 0x12
+#define ADS1262_GPIODIR_REG 0x13
+#define ADS1262_GPIODAT_REG 0x14
+
+#define ADS1262_ADC2CFG_REG 0x15
+#define ADS1262_ADC2CFG_DR2_MASK GENMASK(7, 6)
+#define ADS1262_ADC2CFG_REF2_MASK GENMASK(5, 3)
+#define ADS1262_ADC2CFG_GAIN2_MASK GENMASK(2, 0)
+
+#define ADS1262_ADC2MUX_REG 0x16
+#define ADS1262_ADC2MUX_MUXP2_MASK GENMASK(7, 4)
+#define ADS1262_ADC2MUX_MUXN2_MASK GENMASK(3, 0)
+
+#define ADS1262_ADC2OFC0_REG 0x17
+#define ADS1262_ADC2OFC1_REG 0x18
+#define ADS1262_ADC2FSC0_REG 0x19
+#define ADS1262_ADC2FSC1_REG 0x1A
+
+#define ADS1262_REG_COUNT (ADS1262_ADC2FSC1_REG + 1)
+
+/*
+ * The power transition timing requirement is 65536 clock cycles, at the minimum
+ * clock frequency this is 65536 microseconds.
+ */
+#define ADS1262_POWER_TRANS_USECS 65536
+
+#define ADS1262_NOMINAL_CLK_RATE 7372800
+
+#define ADS1262_FW_CHANNEL_COUNT 16
+#define ADS1262_MON_CHANNEL_COUNT 4
+#define ADS1262_REGMAP_WRITE_SZ 8
+#define ADS1262_MONITOR_ADDR_OFFSET 100
+
+#define ADS1262_ADC1_RESOLUTION 32
+
+struct ads1262 {
+ struct spi_device *spi;
+ struct regmap *regmap;
+ struct gpio_desc *start_gpiod;
+ /* protects concurrent SPI transfers */
+ struct mutex xfer_lock;
+ /* protects channel state */
+ struct mutex chan_lock;
+ struct completion drdy;
+ unsigned long clk_rate;
+ u8 dev_id;
+};
+
+static const char * const ads1262_device_id_to_name[] = {
+ [ADS1262_DEV_ID] = "ads1262",
+ [ADS1263_DEV_ID] = "ads1263",
+};
+
+static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
+ {
+ .type = IIO_TEMP,
+ .channel = ADS1262_INPMUX_TEMP,
+ .channel2 = ADS1262_INPMUX_TEMP,
+ .address = ADS1262_MONITOR_ADDR_OFFSET + 0,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_SIGNED_INT,
+ .realbits = ADS1262_ADC1_RESOLUTION,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .channel = ADS1262_INPMUX_AVDD,
+ .channel2 = ADS1262_INPMUX_AVDD,
+ .indexed = 1,
+ .address = ADS1262_MONITOR_ADDR_OFFSET + 1,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_SIGNED_INT,
+ .realbits = ADS1262_ADC1_RESOLUTION,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .channel = ADS1262_INPMUX_DVDD,
+ .channel2 = ADS1262_INPMUX_DVDD,
+ .indexed = 1,
+ .address = ADS1262_MONITOR_ADDR_OFFSET + 2,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_SIGNED_INT,
+ .realbits = ADS1262_ADC1_RESOLUTION,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+ {
+ .type = IIO_VOLTAGE,
+ .channel = ADS1262_INPMUX_TDAC,
+ .channel2 = ADS1262_INPMUX_TDAC,
+ .indexed = 1,
+ .differential = 1,
+ .address = ADS1262_MONITOR_ADDR_OFFSET + 3,
+ .scan_type = {
+ .format = IIO_SCAN_FORMAT_SIGNED_INT,
+ .realbits = ADS1262_ADC1_RESOLUTION,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ },
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ },
+};
+
+static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
+{
+ guard(mutex)(&st->xfer_lock);
+
+ return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
+}
+
+static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
+{
+ guard(mutex)(&st->xfer_lock);
+
+ return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
+}
+
+static int ads1262_dev_reset(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct gpio_desc *reset_gpiod;
+ int ret;
+
+ reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(reset_gpiod))
+ return dev_err_probe(dev, PTR_ERR(reset_gpiod),
+ "failed to get reset GPIO\n");
+
+ if (reset_gpiod) {
+ /*
+ * Wait a power transition cycle to ensure we are in a
+ * powered-off state after acquiring the RESET GPIO.
+ */
+ fsleep(ADS1262_POWER_TRANS_USECS);
+
+ ret = gpiod_set_value_cansleep(reset_gpiod, 0);
+ if (ret)
+ return ret;
+
+ fsleep(ADS1262_POWER_TRANS_USECS);
+ } else {
+ ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_RESET);
+ if (ret)
+ return ret;
+ /*
+ * The RESET timing requirement is 8 clock cycles, at the
+ * minimum clock rate this is 8 microseconds.
+ */
+ fsleep(8);
+ }
+
+ return 0;
+}
+
+static int ads1262_dev_start(struct ads1262 *st)
+{
+ int ret;
+
+ if (st->start_gpiod)
+ ret = gpiod_set_value_cansleep(st->start_gpiod, 1);
+ else
+ ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_START1);
+
+ return ret;
+}
+
+static int ads1262_dev_stop(struct ads1262 *st)
+{
+ int ret;
+
+ if (st->start_gpiod)
+ ret = gpiod_set_value_cansleep(st->start_gpiod, 0);
+ else
+ ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_STOP1);
+
+ return ret;
+}
+
+static int ads1262_dev_start_one(struct ads1262 *st)
+{
+ int ret;
+
+ ret = ads1262_dev_start(st);
+ if (ret)
+ return ret;
+
+ if (st->start_gpiod) {
+ /*
+ * The START pulse timing requirement is 4 clock cycles, at the
+ * minimum clock rate this is 4 microseconds.
+ */
+ fsleep(4);
+ return ads1262_dev_stop(st);
+ }
+
+ return 0;
+}
+
+static int ads1262_wait_for_conversion(struct ads1262 *st)
+{
+ u64 max_lat_ms;
+ long ret;
+
+ /*
+ * The first conversion latency is affected by the channel's data rate,
+ * filter, the configurable conversion delay and whether chop mode
+ * and/or IDAC rotation mode are enabled.
+ *
+ * The worst possible latency is calculated by taking the lowest data
+ * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
+ * (Table 9-13). Then we scale it by the actual clock rate and multiply
+ * by 4 to account for chop and IDAC rotation modes (Equation 20).
+ */
+ max_lat_ms = 4 * div_u64(1600ULL * ADS1262_NOMINAL_CLK_RATE, st->clk_rate);
+ ret = wait_for_completion_interruptible_timeout(&st->drdy,
+ msecs_to_jiffies(max_lat_ms));
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int ads1262_channel_enable(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ guard(mutex)(&st->xfer_lock);
+ guard(mutex)(&st->chan_lock);
+
+ return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+ ADS1262_INPMUX_MUXN_MASK |
+ ADS1262_INPMUX_MUXP_MASK,
+ FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
+ FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+}
+
+static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
+{
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_MODE0_RUNMODE_MASK,
+ FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode));
+}
+
+static int ads1262_channel_read(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *spec, __be32 *val)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
+ if (ret)
+ return ret;
+
+ ret = ads1262_channel_enable(st, spec);
+ if (ret)
+ return ret;
+
+ reinit_completion(&st->drdy);
+
+ ret = ads1262_dev_start_one(st);
+ if (ret)
+ return ret;
+
+ ret = ads1262_wait_for_conversion(st);
+ if (ret)
+ return ret;
+
+ return ads1262_dev_read_by_cmd(st, ADS1262_OPCODE_RDATA1, val);
+}
+
+static int ads1262_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ __be32 raw;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ads1262_channel_read(indio_dev, chan, &raw);
+ if (ret)
+ return ret;
+ *val = be32_to_cpu(raw);
+
+ return IIO_VAL_INT;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ if (readval)
+ return regmap_read(st->regmap, reg, readval);
+
+ return regmap_write(st->regmap, reg, writeval);
+}
+
+static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
+ const struct fwnode_reference_args *iiospec)
+{
+ /* REVISIT: the auxiliary ADC (ADC2) is currently not supported */
+ if (iiospec->nargs > 1 && iiospec->args[1])
+ return -EINVAL;
+
+ if (!iiospec->nargs)
+ return 0;
+
+ for (unsigned int i = 0; i < indio_dev->num_channels; i++) {
+ if (indio_dev->channels[i].address == iiospec->args[0])
+ return i;
+ }
+
+ return -EINVAL;
+}
+
+static const struct iio_info ads1262_iio_info = {
+ .read_raw = ads1262_read_raw,
+ .debugfs_reg_access = ads1262_debugfs_reg_access,
+ .fwnode_xlate = ads1262_fwnode_xlate,
+};
+
+static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
+{
+ struct ads1262 *st = dev_id;
+
+ complete(&st->drdy);
+
+ return IRQ_HANDLED;
+}
+
+static int ads1262_dev_configure(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ int id, ret;
+
+ ret = ads1262_dev_reset(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to reset device\n");
+
+ guard(mutex)(&st->xfer_lock);
+
+ ret = regmap_read(st->regmap, ADS1262_ID_REG, &id);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read device ID\n");
+
+ id = FIELD_GET(ADS1262_DEV_ID_MASK, id);
+ if (id > ADS1263_DEV_ID) {
+ dev_warn(dev, "unknown device ID %d\n", id);
+ id = ADS1262_DEV_ID;
+ }
+ st->dev_id = id;
+
+ ret = regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_RESET_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_INTREF_MASK);
+ if (ret)
+ return ret;
+
+ return regmap_clear_bits(st->regmap, ADS1262_INTERFACE_REG,
+ ADS1262_INTERFACE_STATUS_MASK |
+ ADS1262_INTERFACE_CRC_MASK);
+}
+
+static bool ads1262_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS1262_ID_REG ... ADS1262_ADC2FSC1_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ads1262_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS1262_POWER_REG ... ADS1262_ADC2FSC1_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ads1262_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS1262_POWER_REG:
+ case ADS1262_OFCAL0_REG ... ADS1262_FSCAL2_REG:
+ case ADS1262_GPIODAT_REG:
+ case ADS1262_ADC2OFC0_REG ... ADS1262_ADC2FSC1_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct reg_default ads1262_reg_defaults[] = {
+ {
+ .reg = ADS1262_INTERFACE_REG,
+ .def = FIELD_PREP_CONST(ADS1262_INTERFACE_STATUS_MASK, 1)
+ | FIELD_PREP_CONST(ADS1262_INTERFACE_CRC_MASK, 1)
+ },
+ {
+ .reg = ADS1262_MODE0_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_MODE1_REG,
+ .def = FIELD_PREP_CONST(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR)
+ },
+ {
+ .reg = ADS1262_MODE2_REG,
+ .def = FIELD_PREP_CONST(ADS1262_MODE2_DR_MASK, ADS1262_DR_20_SPS)
+ },
+ {
+ .reg = ADS1262_INPMUX_REG,
+ .def = FIELD_PREP_CONST(ADS1262_INPMUX_MUXN_MASK, ADS1262_INPMUX_AIN1)
+ },
+ {
+ .reg = ADS1262_IDACMUX_REG,
+ .def = FIELD_PREP_CONST(ADS1262_IDACMUX_MUX2_MASK, ADS1262_IDACMUX_NO_CONN)
+ | FIELD_PREP_CONST(ADS1262_IDACMUX_MUX1_MASK, ADS1262_IDACMUX_NO_CONN)
+ },
+ {
+ .reg = ADS1262_IDACMAG_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_REFMUX_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_TDACP_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_TDACN_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_GPIOCON_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_GPIODIR_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_ADC2CFG_REG,
+ .def = 0x00
+ },
+ {
+ .reg = ADS1262_ADC2MUX_REG,
+ .def = FIELD_PREP_CONST(ADS1262_ADC2MUX_MUXN2_MASK, ADS1262_INPMUX_AIN1)
+ },
+};
+
+static const struct regmap_config ads1262_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .writeable_reg = ads1262_writeable_reg,
+ .readable_reg = ads1262_readable_reg,
+ .volatile_reg = ads1262_volatile_reg,
+ .reg_defaults = ads1262_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
+ .max_register = ADS1262_ADC2FSC1_REG,
+ .can_sleep = true,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ads1262_regmap_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf, size_t val_size)
+{
+ struct ads1262 *st = context;
+ u8 tx[2];
+
+ lockdep_assert_held(&st->xfer_lock);
+
+ /*
+ * The register read operation uses a two byte command header followed
+ * by the register data:
+ *
+ * byte 0: RREG opcode | register address
+ * byte 1: number of registers to transfer, minus one
+ * byte 2..: register data
+ */
+ tx[0] = *(u8 *)reg_buf | ADS1262_OPCODE_RREG;
+ tx[1] = val_size - 1;
+
+ return spi_write_then_read(st->spi, tx, sizeof(tx), val_buf, val_size);
+}
+
+static int ads1262_regmap_gather_write(void *context, const void *reg_buf,
+ size_t reg_size, const void *val_buf,
+ size_t val_size)
+{
+ struct ads1262 *st = context;
+ u8 tx[ADS1262_REGMAP_WRITE_SZ + 2];
+
+ lockdep_assert_held(&st->xfer_lock);
+
+ /*
+ * The register write operation uses a two byte command header followed
+ * by the register data:
+ *
+ * byte 0: WREG opcode | register address
+ * byte 1: number of registers to transfer, minus one
+ * byte 2..: register data
+ */
+ tx[0] = *(u8 *)reg_buf | ADS1262_OPCODE_WREG;
+ tx[1] = val_size - 1;
+ memcpy(&tx[2], val_buf, val_size);
+
+ return spi_write_then_read(st->spi, tx, 2 + val_size, NULL, 0);
+}
+
+static int ads1262_regmap_write(void *context, const void *data, size_t count)
+{
+ return ads1262_regmap_gather_write(context, data, 1, data + 1,
+ count - 1);
+}
+
+static const struct regmap_bus ads1262_regmap_bus = {
+ .read = ads1262_regmap_read,
+ .gather_write = ads1262_regmap_gather_write,
+ .write = ads1262_regmap_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+ .max_raw_write = ADS1262_REGMAP_WRITE_SZ,
+};
+
+static int ads1262_parse_channel_node(struct ads1262 *st,
+ struct iio_chan_spec *spec,
+ struct fwnode_handle *node)
+{
+ struct device *dev = &st->spi->dev;
+ u32 pins[2];
+ int ret;
+
+ if (fwnode_property_present(node, "single-channel")) {
+ ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
+ if (ret)
+ return dev_err_probe(dev, ret, "%pfwP: failed to read single-channel\n",
+ node);
+
+ pins[1] = ADS1262_INPMUX_AINCOM;
+
+ if (fwnode_property_present(node, "common-mode-channel")) {
+ ret = fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "%pfwP: failed to read common-mode-channel\n",
+ node);
+ }
+ } else if (fwnode_property_present(node, "diff-channels")) {
+ ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
+ ARRAY_SIZE(pins));
+ if (ret)
+ return dev_err_probe(dev, ret, "%pfwP: failed to read diff-channels\n",
+ node);
+
+ spec->differential = true;
+ } else {
+ return dev_err_probe(dev, -EINVAL,
+ "%pfwP: one of single-channel or diff-channels is required\n",
+ node);
+ }
+
+ if (pins[0] > ADS1262_INPMUX_AINCOM || pins[1] > ADS1262_INPMUX_AINCOM)
+ return dev_err_probe(dev, -EINVAL, "%pfwP: input channels not in range\n", node);
+
+ spec->channel = pins[0];
+ spec->channel2 = pins[1];
+
+ return 0;
+}
+
+static int ads1262_parse_channels(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ struct iio_chan_spec *chan_specs;
+ unsigned int num_fw_channels, num_specs;
+ unsigned int i = 0;
+ u32 reg;
+ int ret;
+
+ num_fw_channels = device_get_named_child_node_count(dev, "channel");
+ if (num_fw_channels > ADS1262_FW_CHANNEL_COUNT)
+ return dev_err_probe(dev, -EINVAL, "too many channels\n");
+
+ /* Account for the monitor channels and timestamp */
+ num_specs = num_fw_channels + ADS1262_MON_CHANNEL_COUNT + 1;
+ chan_specs = devm_kcalloc(dev, num_specs, sizeof(*chan_specs), GFP_KERNEL);
+ if (!chan_specs)
+ return -ENOMEM;
+
+ device_for_each_named_child_node_scoped(dev, node, "channel") {
+ struct iio_chan_spec *spec = &chan_specs[i];
+
+ ret = fwnode_property_read_u32(node, "reg", ®);
+ if (ret)
+ return dev_err_probe(dev, ret, "%pfwP: failed to read channel reg\n", node);
+ if (reg >= ADS1262_MONITOR_ADDR_OFFSET)
+ return dev_err_probe(dev, -EINVAL, "%pfwP: reg out of range\n", node);
+
+ ret = ads1262_parse_channel_node(st, spec, node);
+ if (ret)
+ return ret;
+
+ spec->type = IIO_VOLTAGE;
+ spec->indexed = true;
+ spec->scan_index = i;
+ spec->address = reg;
+ spec->scan_type = (struct iio_scan_type) {
+ .format = IIO_SCAN_FORMAT_SIGNED_INT,
+ .realbits = ADS1262_ADC1_RESOLUTION,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ };
+ spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+
+ i++;
+ }
+
+ memcpy(&chan_specs[i], ads1262_monitor_chan_specs,
+ sizeof(ads1262_monitor_chan_specs));
+
+ for (unsigned int mon = 0; mon < ADS1262_MON_CHANNEL_COUNT; mon++) {
+ chan_specs[i].scan_index = i;
+ i++;
+ }
+
+ chan_specs[i] = IIO_CHAN_SOFT_TIMESTAMP(i);
+ i++;
+
+ indio_dev->channels = chan_specs;
+ indio_dev->num_channels = i;
+
+ return 0;
+}
+
+static int ads1262_supply_setup(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = devm_regulator_get_enable(dev, "dvdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
+
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+
+ ret = devm_regulator_get_enable_optional(dev, "avss");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+
+ fsleep(ADS1262_POWER_TRANS_USECS);
+
+ return 0;
+}
+
+static int ads1262_spi_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ads1262 *st;
+ unsigned long rate;
+ struct clk *clk;
+ int irq;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ads1262_iio_info;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+ init_completion(&st->drdy);
+
+ ret = devm_mutex_init(dev, &st->chan_lock);
+ if (ret)
+ return ret;
+ ret = devm_mutex_init(dev, &st->xfer_lock);
+ if (ret)
+ return ret;
+
+ ret = ads1262_parse_channels(indio_dev);
+ if (ret)
+ return ret;
+
+ ret = ads1262_supply_setup(st);
+ if (ret)
+ return ret;
+
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get external clock\n");
+
+ rate = clk_get_rate(clk);
+ if (clk && !rate)
+ return dev_err_probe(dev, -EINVAL, "failed to get clock rate\n");
+ st->clk_rate = rate ? rate : ADS1262_NOMINAL_CLK_RATE;
+
+ st->start_gpiod = devm_gpiod_get_optional(dev, "start", GPIOD_OUT_LOW);
+ if (IS_ERR(st->start_gpiod))
+ return dev_err_probe(dev, PTR_ERR(st->start_gpiod),
+ "failed to get start GPIO\n");
+
+ st->regmap = devm_regmap_init(dev, &ads1262_regmap_bus, st,
+ &ads1262_regmap_config);
+ if (IS_ERR(st->regmap))
+ return PTR_ERR(st->regmap);
+
+ ret = ads1262_dev_configure(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to configure device\n");
+
+ indio_dev->name = ads1262_device_id_to_name[st->dev_id];
+
+ /*
+ * REVISIT: This chip has software polling capabilities, which could be
+ * used to stop depending on the DRDY signal.
+ *
+ * Additionally, the MISO pin also can be used as a DRDY IRQ, in which
+ * case the interrupt would be named 'doutdrdy', but requires extra
+ * timing and synchronization considerations to be reliable.
+ */
+ irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
+ if (irq < 0)
+ return dev_err_probe(dev, irq,
+ "the 'drdy' IRQ is currently required for operation\n");
+
+ ret = devm_request_irq(dev, irq, ads1262_irq_handler, IRQF_NO_THREAD,
+ indio_dev->name, st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ads1262_of_match[] = {
+ { .compatible = "ti,ads1262" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads1262_of_match);
+
+static const struct spi_device_id ads1262_spi_match[] = {
+ { .name = "ads1262" },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ads1262_spi_match);
+
+static struct spi_driver ads1262_spi_driver = {
+ .driver = {
+ .name = "ads1262",
+ .of_match_table = ads1262_of_match,
+ },
+ .probe = ads1262_spi_probe,
+ .id_table = ads1262_spi_match,
+};
+module_spi_driver(ads1262_spi_driver);
+
+MODULE_DESCRIPTION("Texas Instruments ADS1262 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>");
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver
2026-08-28 6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-08-28 8:09 ` Andy Shevchenko
0 siblings, 0 replies; 14+ messages in thread
From: Andy Shevchenko @ 2026-08-28 8:09 UTC (permalink / raw)
To: Kurt Borja
Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
devicetree, linux-kernel
On Fri, Aug 28, 2026 at 01:38:18AM -0500, Kurt Borja wrote:
> Add the ti-ads1262 driver with initial support for the primary ADC
> (ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
> and interoperability considerations were taken into account.
>
> Various features such as accurate timeout delays, per-channel reference
> sources, scale, offset, settling latency, excitation currents and
> diagnostics are intentionally left out for future support.
...
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/compiler_attributes.h>
This one is covered by types.h.
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/lockdep.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
...
> +/*
> + * The power transition timing requirement is 65536 clock cycles, at the minimum
> + * clock frequency this is 65536 microseconds.
> + */
> +#define ADS1262_POWER_TRANS_USECS 65536
_US as a suffix is enough. It's how in plenty of cases we do in the Linux
kernel.
> +#define ADS1262_NOMINAL_CLK_RATE 7372800
And here perhaps _Hz? What is the unit for this value?
...
> +static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
> +}
> +
> +static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
> +}
How do these do not conflict or race with regmap SPI communication?
...
> +static int ads1262_dev_reset(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + struct gpio_desc *reset_gpiod;
> + int ret;
> +
> + reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> + if (IS_ERR(reset_gpiod))
> + return dev_err_probe(dev, PTR_ERR(reset_gpiod),
> + "failed to get reset GPIO\n");
> +
Unneeded blank line. But can you use reset-gpio driver instead?
> + if (reset_gpiod) {
> + /*
> + * Wait a power transition cycle to ensure we are in a
> + * powered-off state after acquiring the RESET GPIO.
> + */
> + fsleep(ADS1262_POWER_TRANS_USECS);
> +
> + ret = gpiod_set_value_cansleep(reset_gpiod, 0);
> + if (ret)
> + return ret;
> +
> + fsleep(ADS1262_POWER_TRANS_USECS);
> + } else {
> + ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_RESET);
> + if (ret)
> + return ret;
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds.
> + */
> + fsleep(8);
> + }
> +
> + return 0;
> +}
...
> +static int ads1262_wait_for_conversion(struct ads1262 *st)
> +{
> + u64 max_lat_ms;
> + long ret;
> +
> + /*
> + * The first conversion latency is affected by the channel's data rate,
> + * filter, the configurable conversion delay and whether chop mode
> + * and/or IDAC rotation mode are enabled.
> + *
> + * The worst possible latency is calculated by taking the lowest data
> + * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
> + * (Table 9-13). Then we scale it by the actual clock rate and multiply
> + * by 4 to account for chop and IDAC rotation modes (Equation 20).
> + */
> + max_lat_ms = 4 * div_u64(1600ULL * ADS1262_NOMINAL_CLK_RATE, st->clk_rate);
> + ret = wait_for_completion_interruptible_timeout(&st->drdy,
> + msecs_to_jiffies(max_lat_ms));
> + if (ret < 0)
> + return ret;
> + if (!ret)
In this case it's better to have number comparison as the semantics of 0 is
different to usual (success) code.
if (ret == 0)
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
...
> +static int ads1262_regmap_write(void *context, const void *data, size_t count)
> +{
> + return ads1262_regmap_gather_write(context, data, 1, data + 1,
> + count - 1);
I would go for a single line of 82 characters.
> +}
...
> +static int ads1262_parse_channel_node(struct ads1262 *st,
> + struct iio_chan_spec *spec,
> + struct fwnode_handle *node)
> +{
> + struct device *dev = &st->spi->dev;
> + u32 pins[2];
> + int ret;
Can this use the property names for 'single-channel' and 'diff-channels'?
This will deduplicate the same in a few places and reduce potential typos.
> + if (fwnode_property_present(node, "single-channel")) {
> + ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
> + if (ret)
> + return dev_err_probe(dev, ret, "%pfwP: failed to read single-channel\n",
> + node);
> +
> + pins[1] = ADS1262_INPMUX_AINCOM;
> +
> + if (fwnode_property_present(node, "common-mode-channel")) {
> + ret = fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "%pfwP: failed to read common-mode-channel\n",
> + node);
> + }
> + } else if (fwnode_property_present(node, "diff-channels")) {
> + ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
> + ARRAY_SIZE(pins));
> + if (ret)
> + return dev_err_probe(dev, ret, "%pfwP: failed to read diff-channels\n",
> + node);
> +
> + spec->differential = true;
> + } else {
> + return dev_err_probe(dev, -EINVAL,
> + "%pfwP: one of single-channel or diff-channels is required\n",
> + node);
> + }
> +
> + if (pins[0] > ADS1262_INPMUX_AINCOM || pins[1] > ADS1262_INPMUX_AINCOM)
> + return dev_err_probe(dev, -EINVAL, "%pfwP: input channels not in range\n", node);
> +
> + spec->channel = pins[0];
> + spec->channel2 = pins[1];
> +
> + return 0;
> +}
...
> +static int ads1262_parse_channels(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + struct iio_chan_spec *chan_specs;
> + unsigned int num_fw_channels, num_specs;
> + unsigned int i = 0;
Split assignment. Move it closer to the first user.
> + u32 reg;
> + int ret;
> +
> + num_fw_channels = device_get_named_child_node_count(dev, "channel");
> + if (num_fw_channels > ADS1262_FW_CHANNEL_COUNT)
> + return dev_err_probe(dev, -EINVAL, "too many channels\n");
> +
> + /* Account for the monitor channels and timestamp */
> + num_specs = num_fw_channels + ADS1262_MON_CHANNEL_COUNT + 1;
> + chan_specs = devm_kcalloc(dev, num_specs, sizeof(*chan_specs), GFP_KERNEL);
> + if (!chan_specs)
> + return -ENOMEM;
> +
> + device_for_each_named_child_node_scoped(dev, node, "channel") {
> + struct iio_chan_spec *spec = &chan_specs[i];
> +
> + ret = fwnode_property_read_u32(node, "reg", ®);
> + if (ret)
> + return dev_err_probe(dev, ret, "%pfwP: failed to read channel reg\n", node);
> + if (reg >= ADS1262_MONITOR_ADDR_OFFSET)
> + return dev_err_probe(dev, -EINVAL, "%pfwP: reg out of range\n", node);
> +
> + ret = ads1262_parse_channel_node(st, spec, node);
> + if (ret)
> + return ret;
> +
> + spec->type = IIO_VOLTAGE;
> + spec->indexed = true;
> + spec->scan_index = i;
> + spec->address = reg;
> + spec->scan_type = (struct iio_scan_type) {
> + .format = IIO_SCAN_FORMAT_SIGNED_INT,
> + .realbits = ADS1262_ADC1_RESOLUTION,
> + .storagebits = 32,
> + .endianness = IIO_BE,
> + };
> + spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> +
> + i++;
> + }
> +
> + memcpy(&chan_specs[i], ads1262_monitor_chan_specs,
> + sizeof(ads1262_monitor_chan_specs));
> +
> + for (unsigned int mon = 0; mon < ADS1262_MON_CHANNEL_COUNT; mon++) {
> + chan_specs[i].scan_index = i;
> + i++;
> + }
> +
> + chan_specs[i] = IIO_CHAN_SOFT_TIMESTAMP(i);
> + i++;
> +
> + indio_dev->channels = chan_specs;
> + indio_dev->num_channels = i;
> +
> + return 0;
> +}
...
> + st->clk_rate = rate ? rate : ADS1262_NOMINAL_CLK_RATE;
Can use Elvis.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (2 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 6:38 ` [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
` (5 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Add per-channel sampling frequency support. The "available" attribute is
assigned per-channel too, in order to eventually support per-filter
availability.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 233 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 228 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index d919144c23cb..86ea9b6232d8 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -25,6 +25,7 @@
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
+#include <linux/units.h>
#include <asm/byteorder.h>
@@ -99,6 +100,7 @@
#define ADS1262_DR_14400_SPS 13
#define ADS1262_DR_19200_SPS 14
#define ADS1262_DR_38400_SPS 15
+#define ADS1262_DR_COUNT (ADS1262_DR_38400_SPS + 1)
#define ADS1262_INPMUX_REG 0x06
#define ADS1262_INPMUX_MUXP_MASK GENMASK(7, 4)
@@ -166,6 +168,7 @@
#define ADS1262_POWER_TRANS_USECS 65536
#define ADS1262_NOMINAL_CLK_RATE 7372800
+#define ADS1262_MODULATOR_DIV 8
#define ADS1262_FW_CHANNEL_COUNT 16
#define ADS1262_MON_CHANNEL_COUNT 4
@@ -174,10 +177,17 @@
#define ADS1262_ADC1_RESOLUTION 32
+struct ads1262_channel {
+ u8 data_rate;
+ u8 filter;
+};
+
struct ads1262 {
struct spi_device *spi;
struct regmap *regmap;
struct gpio_desc *start_gpiod;
+ size_t num_channels;
+ struct ads1262_channel *channels;
/* protects concurrent SPI transfers */
struct mutex xfer_lock;
/* protects channel state */
@@ -185,6 +195,8 @@ struct ads1262 {
struct completion drdy;
unsigned long clk_rate;
u8 dev_id;
+ int sampling_freq_table[ADS1262_DR_COUNT][2];
+ int sampling_freq_fir[ADS1262_DR_16_6_SPS + 1][2];
};
static const char * const ads1262_device_id_to_name[] = {
@@ -204,7 +216,9 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.storagebits = 32,
.endianness = IIO_BE,
},
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_VOLTAGE,
@@ -218,7 +232,9 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.storagebits = 32,
.endianness = IIO_BE,
},
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_VOLTAGE,
@@ -232,7 +248,9 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.storagebits = 32,
.endianness = IIO_BE,
},
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
{
.type = IIO_VOLTAGE,
@@ -247,10 +265,66 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.storagebits = 32,
.endianness = IIO_BE,
},
- .info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
},
};
+static const struct ads1262_channel ads1262_default_channel = {
+ .data_rate = ADS1262_DR_20_SPS,
+ .filter = ADS1262_FILTER_FIR,
+};
+
+static const u32 ads1262_mod_decimator_a[] = {
+ [ADS1262_DR_2_5_SPS] = 64,
+ [ADS1262_DR_5_SPS] = 64,
+ [ADS1262_DR_10_SPS] = 64,
+ [ADS1262_DR_16_6_SPS] = 64,
+ [ADS1262_DR_20_SPS] = 64,
+ [ADS1262_DR_50_SPS] = 64,
+ [ADS1262_DR_60_SPS] = 64,
+ [ADS1262_DR_100_SPS] = 64,
+ [ADS1262_DR_400_SPS] = 64,
+ [ADS1262_DR_1200_SPS] = 64,
+ [ADS1262_DR_2400_SPS] = 64,
+ [ADS1262_DR_4800_SPS] = 64,
+ [ADS1262_DR_7200_SPS] = 64,
+ [ADS1262_DR_14400_SPS] = 64,
+ [ADS1262_DR_19200_SPS] = 48,
+ [ADS1262_DR_38400_SPS] = 24,
+};
+
+static const u32 ads1262_mod_decimator_b[] = {
+ [ADS1262_DR_2_5_SPS] = 5760,
+ [ADS1262_DR_5_SPS] = 2880,
+ [ADS1262_DR_10_SPS] = 1440,
+ [ADS1262_DR_16_6_SPS] = 864,
+ [ADS1262_DR_20_SPS] = 720,
+ [ADS1262_DR_50_SPS] = 288,
+ [ADS1262_DR_60_SPS] = 240,
+ [ADS1262_DR_100_SPS] = 144,
+ [ADS1262_DR_400_SPS] = 36,
+ [ADS1262_DR_1200_SPS] = 12,
+ [ADS1262_DR_2400_SPS] = 6,
+ [ADS1262_DR_4800_SPS] = 3,
+ [ADS1262_DR_7200_SPS] = 2,
+ [ADS1262_DR_14400_SPS] = 1,
+ [ADS1262_DR_19200_SPS] = 1,
+ [ADS1262_DR_38400_SPS] = 1,
+};
+
+static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
+ int val2)
+{
+ for (unsigned int i = 0; i < num_elements; i++) {
+ if (val == array[i][0] && val2 == array[i][1])
+ return i;
+ }
+
+ return -EINVAL;
+}
+
static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
{
guard(mutex)(&st->xfer_lock);
@@ -375,9 +449,18 @@ static int ads1262_wait_for_conversion(struct ads1262 *st)
static int ads1262_channel_enable(struct ads1262 *st,
const struct iio_chan_spec *spec)
{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ int ret;
+
guard(mutex)(&st->xfer_lock);
guard(mutex)(&st->chan_lock);
+ ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
+ ADS1262_MODE2_DR_MASK,
+ FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate));
+ if (ret)
+ return ret;
+
return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
ADS1262_INPMUX_MUXN_MASK |
ADS1262_INPMUX_MUXP_MASK,
@@ -429,6 +512,8 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
__be32 raw;
int ret;
@@ -441,6 +526,83 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&st->chan_lock);
+
+ *val = st->sampling_freq_table[chan_data->data_rate][0];
+ *val2 = st->sampling_freq_table[chan_data->data_rate][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ads1262_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, const int **vals,
+ int *type, int *length, long mask)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+
+ switch (chan_data->filter) {
+ case ADS1262_FILTER_FIR:
+ *vals = (const int *)st->sampling_freq_fir;
+ *length = ARRAY_SIZE(st->sampling_freq_fir) * 2;
+ return IIO_AVAIL_LIST;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ads1262_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int val,
+ int val2, long mask)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&st->chan_lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ switch (chan_data->filter) {
+ case ADS1262_FILTER_FIR:
+ ret = ads1262_find_two(st->sampling_freq_fir,
+ ARRAY_SIZE(st->sampling_freq_fir),
+ val, val2);
+ if (ret < 0)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = ads1262_find_two(st->sampling_freq_table,
+ ARRAY_SIZE(st->sampling_freq_table),
+ val, val2);
+ if (ret < 0)
+ return ret;
+
+ chan_data->data_rate = ret;
+
+ return 0;
+
default:
return -EOPNOTSUPP;
}
@@ -479,6 +641,8 @@ static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
static const struct iio_info ads1262_iio_info = {
.read_raw = ads1262_read_raw,
+ .read_avail = ads1262_read_avail,
+ .write_raw = ads1262_write_raw,
.debugfs_reg_access = ads1262_debugfs_reg_access,
.fwnode_xlate = ads1262_fwnode_xlate,
};
@@ -697,6 +861,49 @@ static const struct regmap_bus ads1262_regmap_bus = {
.max_raw_write = ADS1262_REGMAP_WRITE_SZ,
};
+static void ads1262_populate_samp_freqs(struct ads1262 *st)
+{
+ u64 freq_Hz, freq_uHz;
+ u32 freq_rem;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(st->sampling_freq_table); i++) {
+ freq_uHz = div_u64(mul_u32_u32(st->clk_rate, MICRO),
+ ADS1262_MODULATOR_DIV *
+ ads1262_mod_decimator_a[i] *
+ ads1262_mod_decimator_b[i]);
+ freq_Hz = div_u64_rem(freq_uHz, MICRO, &freq_rem);
+
+ st->sampling_freq_table[i][0] = (int)freq_Hz;
+ st->sampling_freq_table[i][1] = (int)freq_rem;
+ }
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(st->sampling_freq_fir); i++) {
+ /*
+ * The FIR filter does not support the 16.6 SPS data rate so it
+ * gets mapped to 20 SPS instead.
+ */
+ switch (i) {
+ case ADS1262_DR_2_5_SPS ... ADS1262_DR_10_SPS:
+ st->sampling_freq_fir[i][0] = st->sampling_freq_table[i][0];
+ st->sampling_freq_fir[i][1] = st->sampling_freq_table[i][1];
+ break;
+ case ADS1262_DR_16_6_SPS:
+ st->sampling_freq_fir[i][0] = st->sampling_freq_table[ADS1262_DR_20_SPS][0];
+ st->sampling_freq_fir[i][1] = st->sampling_freq_table[ADS1262_DR_20_SPS][1];
+ break;
+ }
+ }
+}
+
+static int ads1262_populate_tables(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+
+ ads1262_populate_samp_freqs(st);
+
+ return 0;
+}
+
static int ads1262_parse_channel_node(struct ads1262 *st,
struct iio_chan_spec *spec,
struct fwnode_handle *node)
@@ -763,8 +970,15 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
if (!chan_specs)
return -ENOMEM;
+ st->num_channels = num_fw_channels + ADS1262_MON_CHANNEL_COUNT;
+ st->channels = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels),
+ GFP_KERNEL);
+ if (!st->channels)
+ return -ENOMEM;
+
device_for_each_named_child_node_scoped(dev, node, "channel") {
struct iio_chan_spec *spec = &chan_specs[i];
+ struct ads1262_channel *chan = &st->channels[i];
ret = fwnode_property_read_u32(node, "reg", ®);
if (ret)
@@ -772,6 +986,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
if (reg >= ADS1262_MONITOR_ADDR_OFFSET)
return dev_err_probe(dev, -EINVAL, "%pfwP: reg out of range\n", node);
+ *chan = (struct ads1262_channel)ads1262_default_channel;
+
ret = ads1262_parse_channel_node(st, spec, node);
if (ret)
return ret;
@@ -786,7 +1002,9 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
.storagebits = 32,
.endianness = IIO_BE,
};
- spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ);
i++;
}
@@ -795,6 +1013,7 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
sizeof(ads1262_monitor_chan_specs));
for (unsigned int mon = 0; mon < ADS1262_MON_CHANNEL_COUNT; mon++) {
+ st->channels[i] = (struct ads1262_channel)ads1262_default_channel;
chan_specs[i].scan_index = i;
i++;
}
@@ -890,6 +1109,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
indio_dev->name = ads1262_device_id_to_name[st->dev_id];
+ ret = ads1262_populate_tables(indio_dev);
+ if (ret)
+ return ret;
+
/*
* REVISIT: This chip has software polling capabilities, which could be
* used to stop depending on the DRDY signal.
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (3 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 6:38 ` [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
` (4 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Allow each channel to select its voltage reference through the
"reference-sources" firmware property. Then, use the reference voltage
to calculate available scales. The PGA bypass handling is intentionally
left out until more information about on its usual applications is
gathered.
The ADS1262 allows single-ended supply configurations or bipolar supply
configurations. In single ended configurations both the analog and
digital rails share the same ground, i.e. AVSS = DGND = 0 V. In bipolar
supply configurations, AVSS can go below ground, e.g. AVSS = -2.5 V. If
AVSS is below ground, the ADC can achieve true bipolar measurements and
the external references can also have voltage levels below ground.
This is currently an issue because the regulator subsystem doesn't
support reading negative voltages. The ad4170-4 driver faces this
problem too and the same workaround is used in this case: assume every
regulator reports magnitudes (absolute values). If the chip has a
bipolar supply configuration, then assume positive references are above
ground (>= 0 V) and negative references are below ground (<= 0 V). This
is not a hardware constraint, but it is the most common wiring.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 493 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 468 insertions(+), 25 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 86ea9b6232d8..c87c0a66119a 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -8,6 +8,7 @@
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/bitmap.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/completion.h>
@@ -18,12 +19,14 @@
#include <linux/interrupt.h>
#include <linux/lockdep.h>
#include <linux/math64.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/string.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -83,6 +86,7 @@
#define ADS1262_MODE2_REG 0x05
#define ADS1262_MODE2_BYPASS_MASK BIT(7)
#define ADS1262_MODE2_GAIN_MASK GENMASK(6, 4)
+#define ADS1262_GAIN_COUNT 6
#define ADS1262_MODE2_DR_MASK GENMASK(3, 0)
#define ADS1262_DR_2_5_SPS 0
#define ADS1262_DR_5_SPS 1
@@ -123,7 +127,7 @@
#define ADS1262_IDACMUX_REG 0x0D
#define ADS1262_IDACMUX_MUX2_MASK GENMASK(7, 4)
#define ADS1262_IDACMUX_MUX1_MASK GENMASK(3, 0)
-#define ADS1262_IDACMUX_NO_CONN 0b1011
+#define ADS1262_IDACMUX_NO_CONN 11
#define ADS1262_IDACMAG_REG 0x0E
#define ADS1262_IDACMAG_MAG2_MASK GENMASK(7, 4)
@@ -131,7 +135,17 @@
#define ADS1262_REFMUX_REG 0x0F
#define ADS1262_REFMUX_RMUXP_MASK GENMASK(5, 3)
+#define ADS1262_RMUXP_INTERNAL 0
+#define ADS1262_RMUXP_REFP1 1
+#define ADS1262_RMUXP_REFP2 2
+#define ADS1262_RMUXP_REFP3 3
+#define ADS1262_RMUXP_AVDD 4
#define ADS1262_REFMUX_RMUXN_MASK GENMASK(2, 0)
+#define ADS1262_RMUXN_INTERNAL 0
+#define ADS1262_RMUXN_REFN1 1
+#define ADS1262_RMUXN_REFN2 2
+#define ADS1262_RMUXN_REFN3 3
+#define ADS1262_RMUXN_AVSS 4
#define ADS1262_TDACP_REG 0x10
#define ADS1262_TDACP_OUTP_MASK BIT(7)
@@ -169,9 +183,13 @@
#define ADS1262_NOMINAL_CLK_RATE 7372800
#define ADS1262_MODULATOR_DIV 8
+#define ADS1262_INTERNAL_REFERENCE_uV 2500000
+#define ADS1262_TEMP_SLOPE_uV_C 420ULL
+#define ADS1262_TEMP_ZERO_C 111900ULL
#define ADS1262_FW_CHANNEL_COUNT 16
#define ADS1262_MON_CHANNEL_COUNT 4
+#define ADS1262_EXT_REF_COUNT 3
#define ADS1262_REGMAP_WRITE_SZ 8
#define ADS1262_MONITOR_ADDR_OFFSET 100
@@ -180,12 +198,21 @@
struct ads1262_channel {
u8 data_rate;
u8 filter;
+ u8 gain;
+ u8 ref_p;
+ u8 ref_n;
+ bool ref_reversal;
+ bool is_resistance;
+ size_t num_scales;
+ int scales[ADS1262_GAIN_COUNT][2];
};
struct ads1262 {
struct spi_device *spi;
struct regmap *regmap;
struct gpio_desc *start_gpiod;
+ struct regulator *avdd_supply;
+ struct regulator *avss_supply;
size_t num_channels;
struct ads1262_channel *channels;
/* protects concurrent SPI transfers */
@@ -195,8 +222,12 @@ struct ads1262 {
struct completion drdy;
unsigned long clk_rate;
u8 dev_id;
+ bool bipolar_supply;
int sampling_freq_table[ADS1262_DR_COUNT][2];
int sampling_freq_fir[ADS1262_DR_16_6_SPS + 1][2];
+ u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
+ int refp_uV[ADS1262_EXT_REF_COUNT];
+ int refn_uV[ADS1262_EXT_REF_COUNT];
};
static const char * const ads1262_device_id_to_name[] = {
@@ -217,8 +248,11 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.endianness = IIO_BE,
},
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_OFFSET) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_VOLTAGE,
@@ -233,8 +267,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.endianness = IIO_BE,
},
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_VOLTAGE,
@@ -249,8 +285,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.endianness = IIO_BE,
},
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
},
{
.type = IIO_VOLTAGE,
@@ -266,8 +304,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
.endianness = IIO_BE,
},
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
- .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
+ .info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE),
},
};
@@ -314,6 +354,24 @@ static const u32 ads1262_mod_decimator_b[] = {
[ADS1262_DR_38400_SPS] = 1,
};
+static const char * const ads1262_ref_sources_pos[] = {
+ [ADS1262_RMUXP_INTERNAL] = "internal-p",
+ [ADS1262_RMUXP_REFP1] = "refp1",
+ [ADS1262_RMUXP_REFP2] = "refp2",
+ [ADS1262_RMUXP_REFP3] = "refp3",
+ [ADS1262_RMUXP_AVDD] = "avdd",
+ NULL
+};
+
+static const char * const ads1262_ref_sources_neg[] = {
+ [ADS1262_RMUXN_INTERNAL] = "internal-n",
+ [ADS1262_RMUXN_REFN1] = "refn1",
+ [ADS1262_RMUXN_REFN2] = "refn2",
+ [ADS1262_RMUXN_REFN3] = "refn3",
+ [ADS1262_RMUXN_AVSS] = "avss",
+ NULL
+};
+
static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
int val2)
{
@@ -325,6 +383,12 @@ static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
return -EINVAL;
}
+static bool ads1262_reference_is_external(int ref_p, int ref_n)
+{
+ return in_range(ref_p, ADS1262_RMUXP_REFP1, ADS1262_EXT_REF_COUNT) &&
+ in_range(ref_n, ADS1262_RMUXN_REFN1, ADS1262_EXT_REF_COUNT);
+}
+
static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
{
guard(mutex)(&st->xfer_lock);
@@ -455,17 +519,33 @@ static int ads1262_channel_enable(struct ads1262 *st,
guard(mutex)(&st->xfer_lock);
guard(mutex)(&st->chan_lock);
- ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
- ADS1262_MODE2_DR_MASK,
- FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate));
+ ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_MODE0_REFREV_MASK,
+ FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
if (ret)
return ret;
- return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
- ADS1262_INPMUX_MUXN_MASK |
- ADS1262_INPMUX_MUXP_MASK,
- FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
- FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+ ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
+ ADS1262_MODE2_DR_MASK |
+ ADS1262_MODE2_GAIN_MASK,
+ FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+ FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+ ADS1262_INPMUX_MUXN_MASK |
+ ADS1262_INPMUX_MUXP_MASK,
+ FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
+ FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
+ ADS1262_REFMUX_RMUXN_MASK |
+ ADS1262_REFMUX_RMUXP_MASK,
+ FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n) |
+ FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p));
}
static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
@@ -514,6 +594,7 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
{
struct ads1262 *st = iio_priv(indio_dev);
struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+ u64 scale, offset;
__be32 raw;
int ret;
@@ -526,6 +607,34 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE: {
+ guard(mutex)(&st->chan_lock);
+
+ *val = chan_data->scales[chan_data->gain][0];
+ *val2 = chan_data->scales[chan_data->gain][1];
+
+ return IIO_VAL_DECIMAL64_PICO;
+ }
+
+ case IIO_CHAN_INFO_OFFSET: {
+ guard(mutex)(&st->chan_lock);
+
+ scale = iio_val_s64_compose(chan_data->scales[chan_data->gain][0],
+ chan_data->scales[chan_data->gain][1]);
+
+ switch (chan->type) {
+ case IIO_TEMP:
+ offset = -mul_u64_u64_div_u64(ADS1262_TEMP_ZERO_C,
+ PICO * MILLIDEGREE_PER_DEGREE,
+ scale * ADS1262_TEMP_SLOPE_uV_C);
+ iio_val_s64_decompose(offset, val, val2);
+
+ return IIO_VAL_INT_64;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
case IIO_CHAN_INFO_SAMP_FREQ: {
guard(mutex)(&st->chan_lock);
@@ -548,6 +657,12 @@ static int ads1262_read_avail(struct iio_dev *indio_dev,
struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *type = IIO_VAL_DECIMAL64_PICO;
+ *vals = (const int *)chan_data->scales;
+ *length = chan_data->num_scales * 2;
+ return IIO_AVAIL_LIST;
+
case IIO_CHAN_INFO_SAMP_FREQ:
*type = IIO_VAL_INT_PLUS_MICRO;
@@ -580,6 +695,16 @@ static int ads1262_write_raw(struct iio_dev *indio_dev,
guard(mutex)(&st->chan_lock);
switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ ret = ads1262_find_two(chan_data->scales, chan_data->num_scales,
+ val, val2);
+ if (ret < 0)
+ return ret;
+
+ chan_data->gain = ret;
+
+ return 0;
+
case IIO_CHAN_INFO_SAMP_FREQ:
switch (chan_data->filter) {
case ADS1262_FILTER_FIR:
@@ -621,6 +746,17 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
return regmap_write(st->regmap, reg, writeval);
}
+static int ads1262_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
const struct fwnode_reference_args *iiospec)
{
@@ -643,6 +779,7 @@ static const struct iio_info ads1262_iio_info = {
.read_raw = ads1262_read_raw,
.read_avail = ads1262_read_avail,
.write_raw = ads1262_write_raw,
+ .write_raw_get_fmt = ads1262_write_raw_get_fmt,
.debugfs_reg_access = ads1262_debugfs_reg_access,
.fwnode_xlate = ads1262_fwnode_xlate,
};
@@ -895,20 +1032,246 @@ static void ads1262_populate_samp_freqs(struct ads1262 *st)
}
}
+static void ads1262_calculate_scales(int (*scales)[2], size_t num_scales,
+ u32 full_scale, u64 mult,
+ u32 resolution)
+{
+ unsigned int i;
+ s64 val;
+
+ /*
+ * Each scale in the table corresponds to a PGA gain configuration,
+ * which are given in powers of 2.
+ */
+ for (i = 0; i < num_scales; i++) {
+ val = mul_u64_u64_shr(full_scale, mult, resolution - 1 + i);
+ iio_val_s64_decompose(val, &scales[i][0], &scales[i][1]);
+ }
+}
+
+static int ads1262_populate_scales_resistance(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u32 full_scale;
+
+ if (!ads1262_reference_is_external(chan->ref_p, chan->ref_n))
+ return -EINVAL;
+
+ full_scale = st->rref_ohms[chan->ref_p - 1][chan->ref_n - 1];
+
+ chan->num_scales = ARRAY_SIZE(chan->scales);
+
+ ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+ PICO, ADS1262_ADC1_RESOLUTION);
+
+ return 0;
+}
+
+static int ads1262_populate_scales_temp(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u32 full_scale;
+ u64 mult;
+
+ full_scale = ADS1262_INTERNAL_REFERENCE_uV;
+ mult = PICO * MILLIDEGREE_PER_DEGREE / ADS1262_TEMP_SLOPE_uV_C;
+
+ chan->num_scales = 1;
+
+ ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+ mult, ADS1262_ADC1_RESOLUTION);
+
+ return 0;
+}
+
+static int ads1262_populate_scales_voltage(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct device *dev = &st->spi->dev;
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ int refp_uV, refn_uV;
+ u32 full_scale_uV;
+ u64 mult;
+ int ret;
+
+ switch (chan->ref_p) {
+ case ADS1262_RMUXP_INTERNAL:
+ ret = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "channel@%u: failed to get avss voltage\n",
+ spec->scan_index);
+
+ /* The internal reference negative is AVSS */
+ refp_uV = -ret + ADS1262_INTERNAL_REFERENCE_uV;
+ break;
+
+ case ADS1262_RMUXP_REFP1 ... ADS1262_RMUXP_REFP3:
+ refp_uV = st->refp_uV[chan->ref_p - 1];
+ break;
+
+ case ADS1262_RMUXP_AVDD:
+ ret = regulator_get_voltage(st->avdd_supply);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "channel@%u: failed to get avdd voltage\n",
+ spec->scan_index);
+ refp_uV = ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ switch (chan->ref_n) {
+ case ADS1262_RMUXN_INTERNAL:
+ case ADS1262_RMUXN_AVSS:
+ ret = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "channel@%u: failed to get avss voltage\n",
+ spec->scan_index);
+ refn_uV = -ret;
+ break;
+
+ case ADS1262_RMUXN_REFN1 ... ADS1262_RMUXN_REFN3:
+ refn_uV = st->refn_uV[chan->ref_n - 1];
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ full_scale_uV = abs(refp_uV - refn_uV);
+ if (full_scale_uV < 900000)
+ return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
+ spec->scan_index);
+
+ if (spec->channel >= ADS1262_INPMUX_AVDD &&
+ spec->channel <= ADS1262_INPMUX_DVDD) {
+ /* The power supply monitors are scaled down by a factor of 4 */
+ mult = 4;
+ chan->num_scales = 1;
+ } else {
+ mult = 1;
+ chan->num_scales = ARRAY_SIZE(chan->scales);
+ }
+
+ ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale_uV,
+ NANO * mult, ADS1262_ADC1_RESOLUTION);
+
+ return 0;
+}
+
+static int ads1262_parse_references(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned int i, j;
+ char name[sizeof("ti,refpN-refnM-resistor-ohms")];
+ u32 ohms;
+ int ret;
+
+ for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+ scnprintf(name, sizeof(name), "refp%u", i);
+ ret = devm_regulator_get_enable_read_voltage(dev, name);
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+ name);
+
+ st->refp_uV[i - 1] = ret == -ENODEV ? 0 : ret;
+ }
+
+ for (i = ADS1262_RMUXN_REFN1; i <= ADS1262_RMUXN_REFN3; i++) {
+ scnprintf(name, sizeof(name), "refn%u", i);
+ ret = devm_regulator_get_enable_read_voltage(dev, name);
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+ name);
+
+ /*
+ * REVISIT: Currently the regulator subsystem doesn't support
+ * reading negative voltages. If we have a bipolar supply
+ * configuration (AVSS < 0), then we are forced to assume that
+ * negative references are either 0V (no regulator) or below
+ * ground magnitudes.
+ */
+ if (st->bipolar_supply)
+ st->refn_uV[i - 1] = ret == -ENODEV ? 0 : -ret;
+ else
+ st->refn_uV[i - 1] = ret == -ENODEV ? 0 : ret;
+ }
+
+ for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+ for (j = ADS1262_RMUXN_REFN1; j <= ADS1262_RMUXN_REFN3; j++) {
+ scnprintf(name, sizeof(name),
+ "ti,refp%u-refn%u-resistor-ohms", i, j);
+
+ if (!device_property_present(dev, name))
+ continue;
+
+ ret = device_property_read_u32(dev, name, &ohms);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read reference resistor: %s\n",
+ name);
+ if (!ohms)
+ return dev_err_probe(dev, -EINVAL,
+ "reference resistor can't be 0 ohms: %s\n",
+ name);
+
+ st->rref_ohms[i - 1][j - 1] = ohms;
+ }
+ }
+
+ return 0;
+}
+
static int ads1262_populate_tables(struct iio_dev *indio_dev)
{
struct ads1262 *st = iio_priv(indio_dev);
+ const struct iio_chan_spec *spec;
+ int ret;
ads1262_populate_samp_freqs(st);
+ ret = ads1262_parse_references(st);
+ if (ret)
+ return ret;
+
+ for (unsigned int i = 0; i < st->num_channels; i++) {
+ spec = &indio_dev->channels[i];
+
+ switch (spec->type) {
+ case IIO_VOLTAGE:
+ ret = ads1262_populate_scales_voltage(st, spec);
+ if (ret)
+ return ret;
+ break;
+ case IIO_TEMP:
+ ret = ads1262_populate_scales_temp(st, spec);
+ if (ret)
+ return ret;
+ break;
+ case IIO_RESISTANCE:
+ ret = ads1262_populate_scales_resistance(st, spec);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
return 0;
}
static int ads1262_parse_channel_node(struct ads1262 *st,
struct iio_chan_spec *spec,
+ struct ads1262_channel *chan,
struct fwnode_handle *node)
{
struct device *dev = &st->spi->dev;
+ const char *sources[2];
+ char name[sizeof("ti,refpN-refnM-resistor-ohms")];
u32 pins[2];
int ret;
@@ -947,6 +1310,38 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
spec->channel = pins[0];
spec->channel2 = pins[1];
+ if (fwnode_property_present(node, "reference-sources")) {
+ ret = fwnode_property_read_string_array(node, "reference-sources",
+ sources, ARRAY_SIZE(sources));
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%pfwP: failed to read reference-sources\n",
+ node);
+ if (ret < 2)
+ return dev_err_probe(dev, -EINVAL, "%pfwP: missing reference-sources\n",
+ node);
+
+ ret = match_string(ads1262_ref_sources_pos, -1, sources[0]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%pfwP: invalid positive reference source\n",
+ node);
+ chan->ref_p = ret;
+
+ ret = match_string(ads1262_ref_sources_neg, -1, sources[1]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%pfwP: invalid negative reference source\n",
+ node);
+ chan->ref_n = ret;
+
+ if (ads1262_reference_is_external(chan->ref_p, chan->ref_n)) {
+ scnprintf(name, sizeof(name), "ti,refp%u-refn%u-resistor-ohms",
+ chan->ref_p, chan->ref_n);
+ if (device_property_present(dev, name))
+ chan->is_resistance = true;
+ }
+ }
+
+ chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
+
return 0;
}
@@ -988,11 +1383,11 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
*chan = (struct ads1262_channel)ads1262_default_channel;
- ret = ads1262_parse_channel_node(st, spec, node);
+ ret = ads1262_parse_channel_node(st, spec, chan, node);
if (ret)
return ret;
- spec->type = IIO_VOLTAGE;
+ spec->type = chan->is_resistance ? IIO_RESISTANCE : IIO_VOLTAGE;
spec->indexed = true;
spec->scan_index = i;
spec->address = reg;
@@ -1003,8 +1398,10 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
.endianness = IIO_BE,
};
spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ);
- spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE);
+ spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE);
i++;
}
@@ -1027,6 +1424,14 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
return 0;
}
+static void ads1262_regulator_disable(void *data)
+{
+ struct regulator *supply = data;
+
+ if (supply)
+ regulator_disable(supply);
+}
+
static int ads1262_supply_setup(struct ads1262 *st)
{
struct device *dev = &st->spi->dev;
@@ -1036,14 +1441,52 @@ static int ads1262_supply_setup(struct ads1262 *st)
if (ret)
return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
- ret = devm_regulator_get_enable(dev, "avdd");
- if (ret < 0)
+ st->avdd_supply = devm_regulator_get(dev, "avdd");
+ ret = PTR_ERR_OR_ZERO(st->avdd_supply);
+ if (ret)
return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
- ret = devm_regulator_get_enable_optional(dev, "avss");
- if (ret < 0 && ret != -ENODEV)
+ ret = regulator_enable(st->avdd_supply);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ads1262_regulator_disable,
+ st->avdd_supply);
+ if (ret)
+ return ret;
+
+ /*
+ * REVISIT: The AVSS supply has a minimum of -2.5V and maximum of 0V.
+ * Currently the regulator subsystem doesn't support negative voltages,
+ * so we assume the value returned here is actually the magnitude
+ * (absolute value).
+ *
+ * This limitation forces us to assume that, if we have a bipolar supply
+ * (AVSS < 0V), all negative references are below ground (REFN <= 0V)
+ * and positive references are above ground (REFP >= 0V), as this is the
+ * most common configuration.
+ */
+ st->avss_supply = devm_regulator_get_optional(dev, "avss");
+ ret = PTR_ERR_OR_ZERO(st->avss_supply);
+ if (ret && ret != -ENODEV)
return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+ if (ret != -ENODEV) {
+ ret = regulator_enable(st->avss_supply);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ads1262_regulator_disable,
+ st->avss_supply);
+ if (ret)
+ return ret;
+
+ st->bipolar_supply = true;
+ } else {
+ st->avss_supply = NULL;
+ st->bipolar_supply = false;
+ }
+
fsleep(ADS1262_POWER_TRANS_USECS);
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (4 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
` (3 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Enable per-channel input chopping via the "input-chopping" property.
As the device withholds the first conversion in chop mode, single reads
are taken in CONTINUOUS run mode with a brief start/stop pulse, as
recommended by the datasheet (Section 9.4.1.2).
Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index c87c0a66119a..4d38da092001 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -203,6 +203,7 @@ struct ads1262_channel {
u8 ref_n;
bool ref_reversal;
bool is_resistance;
+ bool input_chop;
size_t num_scales;
int scales[ADS1262_GAIN_COUNT][2];
};
@@ -464,7 +465,7 @@ static int ads1262_dev_stop(struct ads1262 *st)
return ret;
}
-static int ads1262_dev_start_one(struct ads1262 *st)
+static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode)
{
int ret;
@@ -472,7 +473,7 @@ static int ads1262_dev_start_one(struct ads1262 *st)
if (ret)
return ret;
- if (st->start_gpiod) {
+ if (runmode == ADS1262_RUNMODE_CONTINUOUS || st->start_gpiod) {
/*
* The START pulse timing requirement is 4 clock cycles, at the
* minimum clock rate this is 4 microseconds.
@@ -520,7 +521,9 @@ static int ads1262_channel_enable(struct ads1262 *st,
guard(mutex)(&st->chan_lock);
ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_MODE0_INPUT_CHOP_MASK |
ADS1262_MODE0_REFREV_MASK,
+ FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
if (ret)
return ret;
@@ -561,13 +564,26 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
const struct iio_chan_spec *spec, __be32 *val)
{
struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u8 runmode;
int ret;
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
+ /*
+ * When a channel has chop mode or IDAC rotation mode, the first
+ * conversion is always withheld so the datasheet suggests using the
+ * CONTINUOUS mode and briefly starting and stopping conversions to
+ * achieve the same effect (Section 9.4.1.2).
+ */
+ if (chan->input_chop)
+ runmode = ADS1262_RUNMODE_CONTINUOUS;
+ else
+ runmode = ADS1262_RUNMODE_PULSE;
+
+ ret = ads1262_set_runmode(st, runmode);
if (ret)
return ret;
@@ -577,7 +593,7 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
reinit_completion(&st->drdy);
- ret = ads1262_dev_start_one(st);
+ ret = ads1262_dev_start_one(st, runmode);
if (ret)
return ret;
@@ -1340,6 +1356,7 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
}
}
+ chan->input_chop = fwnode_property_read_bool(node, "input-chopping");
chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (5 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
` (2 subsequent siblings)
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Support the two IDAC excitation current sources. Each channel can route
its IDAC1/IDAC2 outputs to a pin via the "excitation-channels" property
and select a magnitude via "excitation-current-nanoamp".
Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 87 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 84 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 4d38da092001..04566d00d7cb 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -198,12 +198,15 @@
struct ads1262_channel {
u8 data_rate;
u8 filter;
+ u8 idac_mux[2];
+ u8 idac_mag[2];
u8 gain;
u8 ref_p;
u8 ref_n;
bool ref_reversal;
bool is_resistance;
bool input_chop;
+ bool idac_chop;
size_t num_scales;
int scales[ADS1262_GAIN_COUNT][2];
};
@@ -315,6 +318,7 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
static const struct ads1262_channel ads1262_default_channel = {
.data_rate = ADS1262_DR_20_SPS,
.filter = ADS1262_FILTER_FIR,
+ .idac_mux = { ADS1262_IDACMUX_NO_CONN, ADS1262_IDACMUX_NO_CONN },
};
static const u32 ads1262_mod_decimator_a[] = {
@@ -373,6 +377,21 @@ static const char * const ads1262_ref_sources_neg[] = {
NULL
};
+static const u32 ads1262_idac_mags_nA[] = {
+ 0, 50000, 100000, 250000, 500000, 750000, 1000000, 1500000, /* 0..7 */
+ 2000000, 2500000, 3000000 /* 8..10 */
+};
+
+static int ads1262_find_one(const u32 *array, size_t num_elements, u32 val)
+{
+ for (unsigned int i = 0; i < num_elements; i++) {
+ if (val == array[i])
+ return i;
+ }
+
+ return -EINVAL;
+}
+
static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
int val2)
{
@@ -522,8 +541,10 @@ static int ads1262_channel_enable(struct ads1262 *st,
ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
ADS1262_MODE0_INPUT_CHOP_MASK |
+ ADS1262_MODE0_IDAC_CHOP_MASK |
ADS1262_MODE0_REFREV_MASK,
FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+ FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
if (ret)
return ret;
@@ -544,6 +565,22 @@ static int ads1262_channel_enable(struct ads1262 *st,
if (ret)
return ret;
+ ret = regmap_update_bits(st->regmap, ADS1262_IDACMUX_REG,
+ ADS1262_IDACMUX_MUX1_MASK |
+ ADS1262_IDACMUX_MUX2_MASK,
+ FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+ FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]));
+ if (ret)
+ return ret;
+
+ ret = regmap_update_bits(st->regmap, ADS1262_IDACMAG_REG,
+ ADS1262_IDACMAG_MAG1_MASK |
+ ADS1262_IDACMAG_MAG2_MASK,
+ FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
+ FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]));
+ if (ret)
+ return ret;
+
return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
ADS1262_REFMUX_RMUXN_MASK |
ADS1262_REFMUX_RMUXP_MASK,
@@ -578,7 +615,7 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
* CONTINUOUS mode and briefly starting and stopping conversions to
* achieve the same effect (Section 9.4.1.2).
*/
- if (chan->input_chop)
+ if (chan->input_chop || chan->idac_chop)
runmode = ADS1262_RUNMODE_CONTINUOUS;
else
runmode = ADS1262_RUNMODE_PULSE;
@@ -1288,8 +1325,8 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
struct device *dev = &st->spi->dev;
const char *sources[2];
char name[sizeof("ti,refpN-refnM-resistor-ohms")];
- u32 pins[2];
- int ret;
+ u32 pins[2], mags[2];
+ int count, ret;
if (fwnode_property_present(node, "single-channel")) {
ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
@@ -1356,7 +1393,51 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
}
}
+ if (fwnode_property_present(node, "excitation-channels")) {
+ count = fwnode_property_count_u32(node, "excitation-channels");
+ if (count < 0)
+ return dev_err_probe(dev, count,
+ "%pfwP: failed to count excitation-channels\n", node);
+
+ pins[0] = ADS1262_IDACMUX_NO_CONN;
+ pins[1] = ADS1262_IDACMUX_NO_CONN;
+ ret = fwnode_property_read_u32_array(node, "excitation-channels",
+ pins, min(count, ARRAY_SIZE(pins)));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "%pfwP: failed to read excitation-channels\n", node);
+ if (pins[0] > ADS1262_IDACMUX_NO_CONN || pins[1] > ADS1262_IDACMUX_NO_CONN)
+ return dev_err_probe(dev, -EINVAL,
+ "%pfwP: excitation-channels not in range\n", node);
+ chan->idac_mux[0] = pins[0];
+ chan->idac_mux[1] = pins[1];
+
+ mags[0] = 0;
+ mags[1] = 0;
+ ret = fwnode_property_read_u32_array(node, "excitation-current-nanoamp",
+ mags, min(count, ARRAY_SIZE(mags)));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "%pfwP: failed to read excitation-current-nanoamp\n",
+ node);
+
+ ret = ads1262_find_one(ads1262_idac_mags_nA,
+ ARRAY_SIZE(ads1262_idac_mags_nA), mags[0]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "%pfwP: invalid excitation-current-nanoamp\n", node);
+ chan->idac_mag[0] = ret;
+
+ ret = ads1262_find_one(ads1262_idac_mags_nA,
+ ARRAY_SIZE(ads1262_idac_mags_nA), mags[1]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "%pfwP: invalid excitation-current-nanoamp\n", node);
+ chan->idac_mag[1] = ret;
+ }
+
chan->input_chop = fwnode_property_read_bool(node, "input-chopping");
+ chan->idac_chop = fwnode_property_read_bool(node, "excitation-current-chopping");
chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (6 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-08-28 6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Add triggered buffer support and a data-ready (DRDY) hardware trigger.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ti-ads1262.c | 305 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 307 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index e51ce42bce94..2b60ade01e90 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1864,6 +1864,8 @@ config TI_ADS1262
tristate "Texas Instruments ADS1262"
depends on SPI
select REGMAP
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
help
If you say yes here you get support for Texas Instruments ADS1262 and
ADS1263 ADC chips.
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 04566d00d7cb..3f6d34a08b29 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -33,6 +33,9 @@
#include <asm/byteorder.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
#define ADS1262_OPCODE_NOP 0x00
#define ADS1262_OPCODE_RESET 0x06
@@ -191,6 +194,7 @@
#define ADS1262_MON_CHANNEL_COUNT 4
#define ADS1262_EXT_REF_COUNT 3
#define ADS1262_REGMAP_WRITE_SZ 8
+#define ADS1262_SPI_XFER_SZ 13
#define ADS1262_MONITOR_ADDR_OFFSET 100
#define ADS1262_ADC1_RESOLUTION 32
@@ -214,6 +218,7 @@ struct ads1262_channel {
struct ads1262 {
struct spi_device *spi;
struct regmap *regmap;
+ struct iio_trigger *trig;
struct gpio_desc *start_gpiod;
struct regulator *avdd_supply;
struct regulator *avss_supply;
@@ -224,6 +229,8 @@ struct ads1262 {
/* protects channel state */
struct mutex chan_lock;
struct completion drdy;
+ struct spi_message msg;
+ struct spi_transfer xfer;
unsigned long clk_rate;
u8 dev_id;
bool bipolar_supply;
@@ -232,6 +239,11 @@ struct ads1262 {
u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
int refp_uV[ADS1262_EXT_REF_COUNT];
int refn_uV[ADS1262_EXT_REF_COUNT];
+ IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
+ ADS1262_FW_CHANNEL_COUNT +
+ ADS1262_MON_CHANNEL_COUNT);
+ u8 tx[ADS1262_SPI_XFER_SZ] __aligned(IIO_DMA_MINALIGN);
+ u8 rx[ADS1262_SPI_XFER_SZ];
};
static const char * const ads1262_device_id_to_name[] = {
@@ -837,10 +849,284 @@ static const struct iio_info ads1262_iio_info = {
.fwnode_xlate = ads1262_fwnode_xlate,
};
+static int ads1262_buffer_postenable_mult(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+
+ /*
+ * When multiple channels are selected, we use a single transfer to both
+ * enable channels, start and then read conversions with a full-duplex
+ * optimized method. The transfer buffer holds up to four contiguous
+ * commands: two register write commands, and start and stop commands if
+ * no START GPIO is provided.
+ *
+ * The buffer is arranged as follows:
+ *
+ * byte 0-1: write protocol header
+ * byte 2-5: MODE0, MODE1, MODE2, INPMUX register data
+ * byte 6-7: write protocol header
+ * byte 8-10: IDACMUX, IDACMAG, REFMUX register data
+ * byte 11: START1 command
+ * byte 12: STOP1 command
+ */
+ if (st->start_gpiod)
+ st->xfer.len = 11;
+ else
+ st->xfer.len = 13;
+
+ static_assert(13 <= ADS1262_SPI_XFER_SZ);
+
+ return spi_optimize_message(st->spi, &st->msg);
+}
+
+static int ads1262_buffer_postenable_one(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ unsigned long i;
+ int ret;
+
+ i = find_first_bit(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ ret = ads1262_channel_enable(st, &indio_dev->channels[i]);
+ if (ret)
+ return ret;
+
+ ret = ads1262_set_runmode(st, ADS1262_RUNMODE_CONTINUOUS);
+ if (ret)
+ return ret;
+
+ static_assert(5 <= ADS1262_SPI_XFER_SZ);
+
+ st->xfer.len = 5;
+ memset(st->tx, 0, st->xfer.len);
+ /*
+ * When only one channel is selected, we can't really avoid concurrent
+ * device activity from happening between the DRDY signal and data
+ * retrieval, thus we read by command. The transfer buffer holds the
+ * command (RDATA1) plus the 4 conversion bytes (5 bytes total).
+ */
+ st->tx[0] = ADS1262_OPCODE_RDATA1;
+
+ ret = spi_optimize_message(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ ret = ads1262_dev_start(st);
+ if (ret) {
+ spi_unoptimize_message(&st->msg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ads1262_buffer_postenable(struct iio_dev *indio_dev)
+{
+ int ret;
+
+ if (iio_validate_scan_mask_onehot(indio_dev,
+ indio_dev->active_scan_mask))
+ ret = ads1262_buffer_postenable_one(indio_dev);
+ else
+ ret = ads1262_buffer_postenable_mult(indio_dev);
+
+ return ret;
+}
+
+static int ads1262_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+
+ if (iio_validate_scan_mask_onehot(indio_dev,
+ indio_dev->active_scan_mask)) {
+ ads1262_dev_stop(st);
+ } else {
+ regcache_drop_region(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_INPMUX_REG);
+ regcache_drop_region(st->regmap, ADS1262_IDACMUX_REG,
+ ADS1262_REFMUX_REG);
+ }
+
+ spi_unoptimize_message(&st->msg);
+
+ return 0;
+}
+
+static bool ads1262_validate_scan_mask(struct iio_dev *indio_dev,
+ const unsigned long *scan_mask)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+
+ if (st->trig && indio_dev->trig == st->trig)
+ return iio_validate_scan_mask_onehot(indio_dev, scan_mask);
+
+ return true;
+}
+
+static const struct iio_buffer_setup_ops ads1262_buffer_ops = {
+ .postenable = ads1262_buffer_postenable,
+ .predisable = ads1262_buffer_predisable,
+ .validate_scan_mask = ads1262_validate_scan_mask,
+};
+
+static void ads1262_channel_prep_tx(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u8 runmode;
+
+ guard(mutex)(&st->chan_lock);
+
+ /*
+ * Input chopping and IDAC rotation modes require the continuous
+ * conversion mode.
+ *
+ * This condition only matters when we have an START GPIO, in which case
+ * the pulse mode is preferred for its predictability: one conversion
+ * per rising edge. Briefly pulsing the START GPIO (4 uS) should have
+ * the same effect almost every time, unless the pulse lasts more than
+ * ~208 uS, which should be rare even if the task is preempted.
+ *
+ * If we rely solely on conversion control commands, both modes are
+ * equivalent because START1 and STOP1 commands are send contiguously on
+ * the same transfer.
+ */
+ if (chan->input_chop || chan->idac_chop)
+ runmode = ADS1262_RUNMODE_CONTINUOUS;
+ else
+ runmode = ADS1262_RUNMODE_PULSE;
+
+ st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
+ st->tx[1] = ADS1262_INPMUX_REG - ADS1262_MODE0_REG;
+ st->tx[2] = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+ FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
+ FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode) |
+ FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
+ st->tx[3] = FIELD_PREP(ADS1262_MODE1_FILTER_MASK, chan->filter);
+ st->tx[4] = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+ FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
+ st->tx[5] = FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel) |
+ FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2);
+
+ st->tx[6] = ADS1262_IDACMUX_REG | ADS1262_OPCODE_WREG;
+ st->tx[7] = ADS1262_REFMUX_REG - ADS1262_IDACMUX_REG;
+ st->tx[8] = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+ FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]);
+ st->tx[9] = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
+ FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
+ st->tx[10] = FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p) |
+ FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n);
+
+ /*
+ * If we have an START GPIO, the transfer length is 11 so these last two
+ * bytes are ignored.
+ */
+ st->tx[11] = ADS1262_OPCODE_START1;
+ st->tx[12] = ADS1262_OPCODE_STOP1;
+}
+
+static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ unsigned int chan;
+ int i = -1;
+ int ret;
+
+ /*
+ * This routine enables and reads channels in a full-duplex fashion.
+ *
+ * When a channel is enabled, the previous conversion is clocked out of
+ * the shift data register on the same transfer (Section 9.4.7.1). This
+ * allows for low latency software sequencing but forbids any
+ * communication with the chip in-between or data corruption may occur,
+ * hence the need to take the xfer_lock for the whole operation.
+ */
+ guard(mutex)(&st->xfer_lock);
+
+ iio_for_each_active_channel(indio_dev, chan) {
+ ads1262_channel_prep_tx(st, &indio_dev->channels[chan]);
+
+ reinit_completion(&st->drdy);
+
+ ret = spi_sync(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ if (st->start_gpiod) {
+ gpiod_set_value_cansleep(st->start_gpiod, 1);
+ fsleep(4);
+ gpiod_set_value_cansleep(st->start_gpiod, 0);
+ }
+
+ if (i > -1)
+ memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));
+ i++;
+
+ ret = ads1262_wait_for_conversion(st);
+ if (ret)
+ return ret;
+ }
+
+ memset(st->tx, 0, st->xfer.len);
+ ret = spi_sync(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));
+
+ return 0;
+}
+
+static int ads1262_fill_buffer_one(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->xfer_lock);
+
+ ret = spi_sync(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ /* In command mode the conversion data is found at offset 1 */
+ memcpy(st->scan_buffer, &st->rx[1], sizeof(*st->scan_buffer));
+
+ return 0;
+}
+
+static irqreturn_t ads1262_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ads1262 *st = iio_priv(indio_dev);
+ s64 ts = pf->timestamp;
+ unsigned int weight;
+ int ret;
+
+ weight = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ if (weight == 1)
+ ret = ads1262_fill_buffer_one(indio_dev);
+ else
+ ret = ads1262_fill_buffer_mult(indio_dev);
+ if (ret)
+ goto out_notify_done;
+
+ iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer,
+ sizeof(st->scan_buffer), ts);
+
+out_notify_done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
{
struct ads1262 *st = dev_id;
+ iio_trigger_poll(st->trig);
complete(&st->drdy);
return IRQ_HANDLED;
@@ -1609,6 +1895,9 @@ static int ads1262_spi_probe(struct spi_device *spi)
st = iio_priv(indio_dev);
st->spi = spi;
init_completion(&st->drdy);
+ st->xfer.tx_buf = st->tx;
+ st->xfer.rx_buf = st->rx;
+ spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
ret = devm_mutex_init(dev, &st->chan_lock);
if (ret)
@@ -1654,6 +1943,22 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ads1262_trigger_handler,
+ &ads1262_buffer_ops);
+ if (ret)
+ return ret;
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", indio_dev->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+ iio_trigger_set_drvdata(st->trig, st);
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return ret;
+
/*
* REVISIT: This chip has software polling capabilities, which could be
* used to stop depending on the DRDY signal.
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (7 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
2026-08-28 6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Register the "refout" and "vbias" regulators to be able to use them as
common mode supplies.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ti-ads1262.c | 119 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 120 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 2b60ade01e90..4548fec1f86f 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1864,6 +1864,7 @@ config TI_ADS1262
tristate "Texas Instruments ADS1262"
depends on SPI
select REGMAP
+ select REGULATOR
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 3f6d34a08b29..4784142f35c9 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -25,6 +25,7 @@
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
#include <linux/spi/spi.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -1132,6 +1133,120 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static int ads1262_vbias_enable(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_set_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_vbias_disable(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_vbias_is_enabled(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_test_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_vbias_get_voltage(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+ int avdd_uV, avss_uV;
+
+ avdd_uV = regulator_get_voltage(st->avdd_supply);
+ if (avdd_uV < 0)
+ return avdd_uV;
+
+ avss_uV = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+ if (avss_uV < 0)
+ return avss_uV;
+
+ return DIV_ROUND_CLOSEST(avdd_uV - avss_uV, 2);
+}
+
+static const struct regulator_ops ads1262_vbias_regulator_ops = {
+ .enable = ads1262_vbias_enable,
+ .disable = ads1262_vbias_disable,
+ .is_enabled = ads1262_vbias_is_enabled,
+ .get_voltage = ads1262_vbias_get_voltage,
+};
+
+static int ads1262_refout_is_enabled(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_test_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_INTREF_MASK);
+}
+
+static const struct regulator_ops ads1262_refout_regulator_ops = {
+ .is_enabled = ads1262_refout_is_enabled,
+};
+
+static const struct regulator_desc ads1262_vbias_regulator_desc = {
+ .name = "vbias",
+ .of_match = "vbias",
+ .regulators_node = "regulators",
+ .supply_name = "avdd",
+ .ops = &ads1262_vbias_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static const struct regulator_desc ads1262_refout_regulator_desc = {
+ .name = "refout",
+ .of_match = "refout",
+ .regulators_node = "regulators",
+ .supply_name = "avdd",
+ .n_voltages = 1,
+ .fixed_uV = 2500000,
+ .ops = &ads1262_refout_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static int ads1262_register_regulators(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct regulator_config config = {
+ .dev = dev,
+ .driver_data = st,
+ };
+ struct regulator_dev *rdev;
+
+ struct fwnode_handle *reg_node __free(fwnode_handle) =
+ device_get_named_child_node(dev, "regulators");
+ if (!reg_node)
+ return 0;
+
+ rdev = devm_regulator_register(dev, &ads1262_refout_regulator_desc,
+ &config);
+ if (IS_ERR(rdev))
+ return PTR_ERR(rdev);
+
+ rdev = devm_regulator_register(dev, &ads1262_vbias_regulator_desc,
+ &config);
+
+ return PTR_ERR_OR_ZERO(rdev);
+}
+
static int ads1262_dev_configure(struct ads1262 *st)
{
struct device *dev = &st->spi->dev;
@@ -1939,6 +2054,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
indio_dev->name = ads1262_device_id_to_name[st->dev_id];
+ ret = ads1262_register_regulators(st);
+ if (ret)
+ return ret;
+
ret = ads1262_populate_tables(indio_dev);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies
2026-08-28 6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (8 preceding siblings ...)
2026-08-28 6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
@ 2026-08-28 6:38 ` Kurt Borja
9 siblings, 0 replies; 14+ messages in thread
From: Kurt Borja @ 2026-08-28 6:38 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel
Enable common mode regulators. The usual configuration is to have our
own 'vbias' regulator connected internally as common mode voltage on the
AINCOM pin.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 37 +++++++++++++++++++++++++++++++++++++
1 file changed, 37 insertions(+)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 4784142f35c9..c7ad1cf1a15d 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -240,6 +240,7 @@ struct ads1262 {
u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
int refp_uV[ADS1262_EXT_REF_COUNT];
int refn_uV[ADS1262_EXT_REF_COUNT];
+ int common_mode_uV[ADS1262_INPMUX_AINCOM + 1];
IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
ADS1262_FW_CHANNEL_COUNT +
ADS1262_MON_CHANNEL_COUNT);
@@ -695,6 +696,13 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
scale * ADS1262_TEMP_SLOPE_uV_C);
iio_val_s64_decompose(offset, val, val2);
+ return IIO_VAL_INT_64;
+
+ case IIO_VOLTAGE:
+ offset = mul_u64_u64_div_u64(st->common_mode_uV[chan->channel2],
+ NANO, scale);
+ iio_val_s64_decompose(offset, val, val2);
+
return IIO_VAL_INT_64;
default:
return -EOPNOTSUPP;
@@ -1133,6 +1141,28 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static int ads1262_parse_common_mode_supplies(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ char name[sizeof("aincom")];
+ int ret;
+
+ for (unsigned int i = 0; i <= ADS1262_INPMUX_AINCOM; i++) {
+ if (i < ADS1262_INPMUX_AINCOM)
+ scnprintf(name, sizeof(name), "ain%u", i);
+ else
+ scnprintf(name, sizeof(name), "aincom");
+
+ ret = devm_regulator_get_enable_read_voltage(dev, name);
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret,
+ "failed to get common mode supply: %s\n", name);
+ st->common_mode_uV[i] = ret != -ENODEV ? ret : 0;
+ }
+
+ return 0;
+}
+
static int ads1262_vbias_enable(struct regulator_dev *rdev)
{
struct ads1262 *st = rdev_get_drvdata(rdev);
@@ -1902,6 +1932,9 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
BIT(IIO_CHAN_INFO_SCALE);
+ if (!chan->is_resistance && !spec->differential)
+ spec->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
+
i++;
}
@@ -2058,6 +2091,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = ads1262_parse_common_mode_supplies(st);
+ if (ret)
+ return ret;
+
ret = ads1262_populate_tables(indio_dev);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 14+ messages in thread