* [PATCH v5 01/10] dt-bindings: adc: add excitation-current-chopping property
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 6:52 ` [PATCH v5 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
` (8 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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, Conor Dooley
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>
Acked-by: Conor Dooley <conor.dooley@microchip.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] 16+ messages in thread* [PATCH v5 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-10-10 6:52 ` [PATCH v5 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 7:02 ` sashiko-bot
2026-10-10 6:52 ` [PATCH v5 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
` (7 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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.
Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../devicetree/bindings/iio/adc/ti,ads1262.yaml | 381 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 387 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..d9957aa509d8
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
@@ -0,0 +1,381 @@
+# 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:
+ description:
+ External crystal or clock wired to the XTAL/CLKIN pins. The device
+ automatically detects and switches to it. If not connected, the internal
+ oscillator is used.
+ maxItems: 1
+
+ '#io-channel-cells':
+ const: 3
+ description: |
+ The first cell selects between ADC1 (1) and ADC2 (2). The two following
+ cells select the positive and negative inputs respectively:
+ 0-9: AIN0-AIN9
+ 10: AINCOM
+ 11: Temperature sensor monitor
+ 12: Analog power supply monitor
+ 13: Digital power supply monitor
+ 14: TDAC test signal
+ Monitor inputs (11-14) must be selected as both the positive and negative
+ input.
+
+ '#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:
+ patternProperties:
+ "^channel@[0-9a-f]+$":
+ properties:
+ reference-sources:
+ minItems: 3
+ maxItems: 3
+ default: [internal-p, internal-n, internal]
+ else:
+ patternProperties:
+ "^channel@[0-9a-f]+$":
+ 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>;
+ refp3-supply = <&refp3>;
+ refn3-supply = <&refn3>;
+ #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>;
+ reference-sources = "refp3", "refn3";
+ };
+ };
+ };
+
+ - |
+ #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 3a2dfd1a4fd8..b9ad0ee57233 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27321,6 +27321,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] 16+ messages in thread* Re: [PATCH v5 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-10-10 6:52 ` [PATCH v5 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-10-10 7:02 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-10 7:02 UTC (permalink / raw)
To: Kurt Borja; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] dt-bindings: missing mandatory required properties for GPIO controller
--
commit 71f9ec7fa4f648264b425d77eb77849a06771e4a
Author: Kurt Borja <kuurtb@gmail.com>
dt-bindings: iio: adc: support the TI ADS126x ADC family
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).
> 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 0000000000000..d9957aa509d8c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
[ ... ]
> + '#gpio-cells':
> + const: 2
> +
> + gpio-controller: true
> +
> + regulators:
> + type: object
[ ... ]
> +dependencies:
> + interrupts: [interrupt-names]
> + interrupts-extended: [interrupt-names]
> +
> +required:
> + - compatible
> + - reg
> + - spi-cpha
> + - avdd-supply
> + - dvdd-supply
> + - '#address-cells'
> + - '#size-cells'
[Severity: Medium]
Since this hardware adds provider capabilities by defining gpio-controller and
#gpio-cells, shouldn't these properties be explicitly included in the required
list?
When hardware gains new provider capabilities like a GPIO controller, the
dt-bindings subsystem guidelines state that the corresponding properties should
be mandatory. Omitting them from the required list allows incomplete device
tree nodes to silently pass schema validation, which could cause drivers or
dependent subsystems to fail at runtime.
> +
> +allOf:
> + - $ref: /schemas/spi/spi-peripheral-props.yaml#
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-ads126x-v5-0-49e331cb2fed@gmail.com?part=2
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 03/10] iio: adc: add the ti-ads1262 driver
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-10-10 6:52 ` [PATCH v5 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
2026-10-10 6:52 ` [PATCH v5 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 7:05 ` sashiko-bot
2026-10-10 6:52 ` [PATCH v5 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
` (6 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 | 927 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 940 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index b9ad0ee57233..fe828fccc8d7 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27326,6 +27326,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 a802d8ee4dd9..06aa84a3f145 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1900,6 +1900,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 9e82c21dbd37..fee580bdd3b2 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -161,6 +161,7 @@ obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
obj-$(CONFIG_TI_ADS112C04) += ti-ads112c04.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..a54f78e88012
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -0,0 +1,927 @@
+// 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/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_US 65536
+
+#define ADS1262_NOMINAL_CLK_RATE_HZ 7372800
+
+#define ADS1262_FW_CHANNEL_COUNT 16
+#define ADS1262_MON_CHANNEL_COUNT 4
+#define ADS1262_REGMAP_WRITE_SZ 8
+
+#define ADS1262_ADC1_RESOLUTION 32
+
+struct ads1262_chip_info {
+ const char *name;
+};
+
+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_hz;
+};
+
+static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
+ {
+ .type = IIO_TEMP,
+ .channel = ADS1262_INPMUX_TEMP,
+ .channel2 = ADS1262_INPMUX_TEMP,
+ .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,
+ .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,
+ .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,
+ .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_US);
+
+ ret = gpiod_set_value_cansleep(reset_gpiod, 0);
+ if (ret)
+ return ret;
+
+ fsleep(ADS1262_POWER_TRANS_US);
+ } 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_HZ, st->clk_rate_hz);
+ ret = wait_for_completion_interruptible_timeout(&st->drdy,
+ msecs_to_jiffies(max_lat_ms));
+ if (ret < 0)
+ return ret;
+ if (ret == 0)
+ 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)
+{
+ if (iiospec->nargs != 3)
+ return -EINVAL;
+
+ /* REVISIT: the auxiliary ADC (ADC2) is currently not supported */
+ if (iiospec->args[0] != 1)
+ return -EINVAL;
+
+ for (unsigned int i = 0; i < indio_dev->num_channels; i++) {
+ if (indio_dev->channels[i].channel == iiospec->args[1] &&
+ indio_dev->channels[i].channel2 == iiospec->args[2])
+ 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 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_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;
+ 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;
+
+ i = 0;
+ device_for_each_named_child_node_scoped(dev, node, "channel") {
+ struct iio_chan_spec *spec = &chan_specs[i];
+
+ ret = ads1262_parse_channel_node(st, spec, node);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_u32(node, "reg", ®);
+ if (ret)
+ return dev_err_probe(dev, ret, "%pfwP: failed to read reg\n",
+ node);
+
+ 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_supplies_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_US);
+
+ return 0;
+}
+
+static int ads1262_spi_probe(struct spi_device *spi)
+{
+ const struct ads1262_chip_info *info;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ads1262 *st;
+ unsigned long rate;
+ struct clk *clk;
+ int irq;
+ int ret;
+
+ info = spi_get_device_match_data(spi);
+ if (!info)
+ return -ENODATA;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ indio_dev->name = info->name;
+ 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_supplies_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_hz = rate ?: ADS1262_NOMINAL_CLK_RATE_HZ;
+
+ 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");
+
+ /*
+ * 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 ads1262_chip_info ads1262_chip_info = {
+ .name = "ads1262",
+};
+
+static const struct of_device_id ads1262_of_match[] = {
+ { .compatible = "ti,ads1262", .data = &ads1262_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads1262_of_match);
+
+static const struct spi_device_id ads1262_spi_match[] = {
+ { .name = "ads1262", .driver_data = (kernel_ulong_t)&ads1262_chip_info },
+ { }
+};
+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] 16+ messages in thread* Re: [PATCH v5 03/10] iio: adc: add the ti-ads1262 driver
2026-10-10 6:52 ` [PATCH v5 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-10-10 7:05 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-10 7:05 UTC (permalink / raw)
To: Kurt Borja; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] iio: adc: ti-ads1262: lockdep splat on regmap debugfs access
- [Medium] iio: adc: ti-ads1262: stale DRDY on interrupted channel read causes corrupted data
--
commit 5387f009404bbcc26fabb71c5f2ad0b5ba669607
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: add the ti-ads1262 driver
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.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 0000000000000..a54f78e88012c
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> +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;
[Severity: Medium]
If ads1262_wait_for_conversion() is interrupted by a signal and returns
-ERESTARTSYS, does this leave the conversion running in the background?
If so, a stale DRDY interrupt from that background conversion might fire on
the next read exactly in the window between reinit_completion() and the
hardware processing the new START1 command. This would cause the wait to
complete prematurely and return corrupted or stale data to user space.
Is there a way to drain the hardware state or cancel the conversion when
the wait is interrupted?
[ ... ]
> +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);
[Severity: Medium]
Will this lockdep assertion fail when the regmap is accessed via debugfs?
Although the driver manually acquires st->xfer_lock before invoking regmap
updates, it does not wire this lock into the regmap core's configuration
(via custom lock/unlock callbacks, or by setting disable_locking = true).
As a result, the regmap core manages its own internal mutex and debugfs
accesses will bypass st->xfer_lock, calling into the bus callbacks directly
and triggering this assertion.
> +
> + /*
> + * 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);
[Severity: Medium]
Similarly, will this assertion fail on regmap debugfs writes for the same
reasons?
> +
> + /*
> + * 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);
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-ads126x-v5-0-49e331cb2fed@gmail.com?part=3
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (2 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 7:07 ` sashiko-bot
2026-10-10 6:52 ` [PATCH v5 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
` (5 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 a54f78e88012..7a6fa009ddad 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -24,6 +24,7 @@
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
+#include <linux/units.h>
#include <asm/byteorder.h>
@@ -98,6 +99,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)
@@ -165,6 +167,7 @@
#define ADS1262_POWER_TRANS_US 65536
#define ADS1262_NOMINAL_CLK_RATE_HZ 7372800
+#define ADS1262_MODULATOR_DIV 8
#define ADS1262_FW_CHANNEL_COUNT 16
#define ADS1262_MON_CHANNEL_COUNT 4
@@ -176,16 +179,25 @@ struct ads1262_chip_info {
const char *name;
};
+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 __counted_by_ptr(num_channels);
/* protects concurrent SPI transfers */
struct mutex xfer_lock;
/* protects channel state */
struct mutex chan_lock;
struct completion drdy;
unsigned long clk_rate_hz;
+ int sampling_freq_table[ADS1262_DR_COUNT][2];
+ int sampling_freq_fir[ADS1262_DR_16_6_SPS + 1][2];
};
static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
@@ -199,7 +211,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,
@@ -212,7 +226,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,
@@ -225,7 +241,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,
@@ -239,10 +257,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),
},
};
+#define ads1262_default_channel ((struct ads1262_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);
@@ -367,9 +441,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,
@@ -421,6 +504,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;
@@ -433,6 +518,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;
}
@@ -472,6 +634,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,
};
@@ -678,6 +842,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_hz, 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)
@@ -744,9 +951,18 @@ 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;
+
i = 0;
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];
+
+ *chan = ads1262_default_channel;
ret = ads1262_parse_channel_node(st, spec, node);
if (ret)
@@ -767,7 +983,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++;
}
@@ -776,6 +994,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] = ads1262_default_channel;
chan_specs[i].scan_index = i;
i++;
}
@@ -875,6 +1094,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return dev_err_probe(dev, ret, "failed to configure device\n");
+ 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] 16+ messages in thread* Re: [PATCH v5 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-10-10 6:52 ` [PATCH v5 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-10-10 7:07 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-10 7:07 UTC (permalink / raw)
To: Kurt Borja; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] iio: adc: ti-ads1262: out-of-bounds access on st->channels for timestamp channel
--
commit 7618e3d1bfd0f75640c3a335d284574aab75861b
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support per-channel sampling frequency
This commit adds per-channel sampling frequency support to the
ti-ads1262 driver. It also assigns the "available" attribute
per-channel in preparation for per-filter availability support.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index a54f78e88012c..7a6fa009ddadc 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -436,11 +513,19 @@ 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;
> +
[Severity: High]
Does this cause an out-of-bounds array access if this function is called for
the soft timestamp channel?
Because st->channels is allocated without space for the timestamp channel,
the timestamp channel is assigned a scan_index equal to st->num_channels,
which is one element past the end of the array.
When triggered buffer sampling is introduced in the follow-up patches,
iio_for_each_active_channel() iterates over all bits in the active scan mask,
including the soft timestamp channel if enabled by userspace.
If the timestamp channel is passed to the preparation functions and reaches
here, it looks like this will read out-of-bounds to get chan->data_rate,
and then send corrupted configuration commands to the hardware via
regmap_update_bits().
> 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,
[ ... ]
> @@ -744,9 +951,18 @@ 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;
> +
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-ads126x-v5-0-49e331cb2fed@gmail.com?part=4
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 05/10] iio: adc: ti-ads1262: support per-channel reference and gain
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (3 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 6:52 ` [PATCH v5 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
` (4 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 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 | 479 ++++++++++++++++++++++++++++++++++++++++---
1 file changed, 454 insertions(+), 25 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 7a6fa009ddad..98d147f120c4 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>
@@ -17,12 +18,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>
@@ -82,6 +85,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
@@ -122,7 +126,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)
@@ -130,7 +134,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)
@@ -168,9 +182,13 @@
#define ADS1262_NOMINAL_CLK_RATE_HZ 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_ADC1_RESOLUTION 32
@@ -182,6 +200,13 @@ struct ads1262_chip_info {
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 {
@@ -196,8 +221,14 @@ struct ads1262 {
struct mutex chan_lock;
struct completion drdy;
unsigned long clk_rate_hz;
+ bool bipolar_supply;
+ int avdd_uV;
+ int avss_uV;
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 struct iio_chan_spec ads1262_monitor_chan_specs[] = {
@@ -212,8 +243,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,
@@ -227,8 +261,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,
@@ -242,8 +278,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,
@@ -258,8 +296,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),
},
};
@@ -306,6 +346,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)
{
@@ -317,6 +375,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);
@@ -447,17 +511,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)
@@ -506,6 +586,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;
@@ -518,6 +599,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);
@@ -540,6 +649,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;
@@ -572,6 +687,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:
@@ -613,6 +738,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)
{
@@ -636,6 +772,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,
};
@@ -876,20 +1013,241 @@ 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;
+
+ switch (chan->ref_p) {
+ case ADS1262_RMUXP_INTERNAL:
+ if (chan->ref_n == ADS1262_RMUXN_INTERNAL) {
+ refp_uV = ADS1262_INTERNAL_REFERENCE_uV;
+ break;
+ }
+
+ /* The negative internal reference is AVSS */
+ refp_uV = st->avss_uV + 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:
+ if (!st->avdd_uV)
+ return dev_err_probe(dev, -EINVAL,
+ "channel@%lu: avdd voltage unknown\n",
+ spec->address);
+ refp_uV = st->avdd_uV;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ switch (chan->ref_n) {
+ case ADS1262_RMUXN_INTERNAL:
+ if (chan->ref_p == ADS1262_RMUXP_INTERNAL) {
+ refn_uV = 0;
+ break;
+ }
+ fallthrough;
+ case ADS1262_RMUXN_AVSS:
+ refn_uV = st->avss_uV;
+ 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@%lu: reference voltage below 0.9V\n",
+ spec->address);
+
+ 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;
@@ -928,6 +1286,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;
}
@@ -964,7 +1354,7 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
*chan = ads1262_default_channel;
- ret = ads1262_parse_channel_node(st, spec, node);
+ ret = ads1262_parse_channel_node(st, spec, chan, node);
if (ret)
return ret;
@@ -973,7 +1363,7 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
return dev_err_probe(dev, ret, "%pfwP: failed to read reg\n",
node);
- spec->type = IIO_VOLTAGE;
+ spec->type = chan->is_resistance ? IIO_RESISTANCE : IIO_VOLTAGE;
spec->indexed = true;
spec->scan_index = i;
spec->address = reg;
@@ -984,8 +1374,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++;
}
@@ -1008,22 +1400,59 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
return 0;
}
+static void ads1262_regulator_disable(void *data)
+{
+ struct regulator *supply = data;
+
+ regulator_disable(supply);
+}
+
static int ads1262_supplies_setup(struct ads1262 *st)
{
struct device *dev = &st->spi->dev;
+ struct regulator *avdd;
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)
+ avdd = devm_regulator_get(dev, "avdd");
+ ret = PTR_ERR_OR_ZERO(avdd);
+ if (ret)
return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
- ret = devm_regulator_get_enable_optional(dev, "avss");
+ ret = regulator_enable(avdd);
+ if (ret)
+ return ret;
+
+ ret = devm_add_action_or_reset(dev, ads1262_regulator_disable, avdd);
+ if (ret)
+ return ret;
+
+ ret = regulator_get_voltage(avdd);
+ if (ret > 0)
+ st->avdd_uV = 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.
+ */
+ ret = devm_regulator_get_enable_read_voltage(dev, "avss");
if (ret < 0 && ret != -ENODEV)
- return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+ return dev_err_probe(dev, ret, "failed to read AVSS voltage\n");
+
+ if (ret > 0) {
+ st->avss_uV = -ret;
+ st->bipolar_supply = true;
+ }
fsleep(ADS1262_POWER_TRANS_US);
--
2.55.0
^ permalink raw reply related [flat|nested] 16+ messages in thread* [PATCH v5 06/10] iio: adc: ti-ads1262: support input chopping
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (4 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 6:52 ` [PATCH v5 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
` (3 subsequent siblings)
9 siblings, 0 replies; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 98d147f120c4..938e26075302 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -205,6 +205,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];
};
@@ -456,7 +457,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;
@@ -464,7 +465,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.
@@ -512,7 +513,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;
@@ -553,13 +556,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;
@@ -569,7 +585,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;
@@ -1316,6 +1332,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] 16+ messages in thread* [PATCH v5 07/10] iio: adc: ti-ads1262: support excitation currents
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (5 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 7:09 ` sashiko-bot
2026-10-10 6:52 ` [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
` (2 subsequent siblings)
9 siblings, 1 reply; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 | 90 ++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 87 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 938e26075302..49d4bf206be0 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -200,12 +200,15 @@ struct ads1262_chip_info {
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];
};
@@ -307,6 +310,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
#define ads1262_default_channel ((struct ads1262_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[] = {
@@ -365,6 +372,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)
{
@@ -514,8 +536,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;
@@ -536,6 +560,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,
@@ -570,7 +610,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;
@@ -1264,8 +1304,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]);
@@ -1332,7 +1372,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] 16+ messages in thread* Re: [PATCH v5 07/10] iio: adc: ti-ads1262: support excitation currents
2026-10-10 6:52 ` [PATCH v5 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-10-10 7:09 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-10 7:09 UTC (permalink / raw)
To: Kurt Borja; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] iio: adc: ti-ads1262: Build failure in min() macro due to mixed signedness
--
commit f745e114f602a4dc9a511ee58a8c5092e66748e8
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support excitation currents
This commit adds support for the two IDAC excitation current sources on the
TI ADS1262 ADC. It allows routing IDAC outputs to pins and configuring their
magnitudes via device tree properties.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 938e260753027..49d4bf206be07 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -1332,7 +1372,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)));
[Severity: Low]
Does this introduce a compile-time regression due to strict signed/unsigned
type checking in the min() macro?
The fwnode_property_count_u32() function returns a signed int which is
stored in the count variable, while ARRAY_SIZE() evaluates to an unsigned
size_t. The kernel's min() macro enforces that both arguments have
compatible signedness, which triggers a build-breaking assertion here.
Could min_t(size_t, count, ARRAY_SIZE(pins)) or similar be used here
instead?
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "%pfwP: failed to read excitation-channels\n", node);
[ ... ]
> + mags[0] = 0;
> + mags[1] = 0;
> + ret = fwnode_property_read_u32_array(node, "excitation-current-nanoamp",
> + mags, min(count, ARRAY_SIZE(mags)));
[Severity: Low]
Does this also trigger the same min() macro signedness build regression as
above, since count is signed and ARRAY_SIZE(mags) is unsigned?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-ads126x-v5-0-49e331cb2fed@gmail.com?part=7
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (6 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 7:09 ` sashiko-bot
2026-10-10 6:52 ` [PATCH v5 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-10-10 6:52 ` [PATCH v5 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
9 siblings, 1 reply; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 | 299 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 301 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 06aa84a3f145..6d5df9c041ac 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1904,6 +1904,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 49d4bf206be0..0606e712feec 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -32,6 +32,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
@@ -190,6 +193,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_ADC1_RESOLUTION 32
@@ -216,6 +220,7 @@ struct ads1262_channel {
struct ads1262 {
struct spi_device *spi;
struct regmap *regmap;
+ struct iio_trigger *trig;
struct gpio_desc *start_gpiod;
size_t num_channels;
struct ads1262_channel *channels __counted_by_ptr(num_channels);
@@ -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_hz;
bool bipolar_supply;
int avdd_uV;
@@ -233,6 +240,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 struct iio_chan_spec ads1262_monitor_chan_specs[] = {
@@ -833,10 +845,278 @@ 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);
+ struct device *dev = &st->spi->dev;
+
+ if (indio_dev->trig == st->trig) {
+ dev_err_once(dev, "the DRDY trigger doesn't support multiple channel sampling\n");
+ return -EINVAL;
+ }
+
+ /*
+ * 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 const struct iio_buffer_setup_ops ads1262_buffer_ops = {
+ .postenable = ads1262_buffer_postenable,
+ .predisable = ads1262_buffer_predisable,
+};
+
+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;
@@ -1585,6 +1865,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)
@@ -1628,6 +1911,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] 16+ messages in thread* Re: [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling
2026-10-10 6:52 ` [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-10-10 7:09 ` sashiko-bot
0 siblings, 0 replies; 16+ messages in thread
From: sashiko-bot @ 2026-10-10 7:09 UTC (permalink / raw)
To: Kurt Borja; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] spi: DMA API violation due to cacheline sharing between tx and rx buffers
- [High] iio: Out-of-bounds array access on soft timestamp channel in trigger handlers
--
commit 498de2a7a709d846f73f3af6fbc4fac307b5368e
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support triggered buffer sampling
Add triggered buffer support and a data-ready (DRDY) hardware trigger.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 49d4bf206be07..0606e712feec8 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -233,6 +240,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];
^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
Does this code risk a DMA API violation on non-coherent architectures?
The compiler places rx immediately after tx at offset 13. While tx has the
__aligned(IIO_DMA_MINALIGN) attribute, its size (13 bytes) is not padded
to the cacheline boundary, causing rx to share the same cacheline as tx.
During SPI transfers, both buffers are mapped for DMA concurrently.
Unmapping rx (which invalidates the CPU cache for that line) can silently
destroy adjacent CPU writes or unflushed data in tx, leading to data
corruption and triggering CONFIG_DMA_API_DEBUG warnings.
Should rx also be explicitly marked with __aligned(IIO_DMA_MINALIGN)?
[ ... ]
> +static void ads1262_channel_prep_tx(struct ads1262 *st,
> + const struct iio_chan_spec *spec)
> +{
> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
[ ... ]
> +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]);
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: High]
Can this code read out-of-bounds when chan is the soft timestamp channel?
When triggered buffer support is set up, the IIO core automatically
appends a software timestamp channel to the indio_dev->channels array
and reserves a bit for it in the active scan mask.
The loop over iio_for_each_active_channel() does not skip the IIO_TIMESTAMP
channel. When ads1262_channel_prep_tx() is called for the timestamp channel,
it accesses st->channels[spec->scan_index]. Because the timestamp channel's
scan_index is numerically greater than the hardware channels, it indexes
out-of-bounds of the driver's allocated st->channels array.
The driver then sends configuration data read from this out-of-bounds memory
to the ADC and blocks on wait_for_completion_interruptible_timeout(). Since
the ADC was improperly configured, the expected DRDY interrupt may never
arrive, causing a timeout.
Does this same issue exist in ads1262_buffer_postenable_one() if only the
timestamp channel is enabled?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261010-ads126x-v5-0-49e331cb2fed@gmail.com?part=8
^ permalink raw reply [flat|nested] 16+ messages in thread
* [PATCH v5 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (7 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
2026-10-10 6:52 ` [PATCH v5 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
9 siblings, 0 replies; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 | 120 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 121 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 6d5df9c041ac..1d05ba6e9886 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1904,6 +1904,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 0606e712feec..5bb41b70353f 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -24,6 +24,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>
@@ -1122,6 +1123,121 @@ 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);
+
+ if (!st->avdd_uV)
+ return -EINVAL;
+
+ return DIV_ROUND_CLOSEST(st->avdd_uV + st->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 int ads1262_refout_get_voltage(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ /* The internal reference 2.5 V is measured above AVSS */
+ return st->avss_uV + ADS1262_INTERNAL_REFERENCE_uV;
+}
+
+static const struct regulator_ops ads1262_refout_regulator_ops = {
+ .is_enabled = ads1262_refout_is_enabled,
+ .get_voltage = ads1262_refout_get_voltage,
+};
+
+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",
+ .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;
@@ -1907,6 +2023,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return dev_err_probe(dev, ret, "failed to configure device\n");
+ 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] 16+ messages in thread* [PATCH v5 10/10] iio: adc: ti-ads1262: support common mode supplies
2026-10-10 6:52 [PATCH v5 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (8 preceding siblings ...)
2026-10-10 6:52 ` [PATCH v5 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
@ 2026-10-10 6:52 ` Kurt Borja
9 siblings, 0 replies; 16+ messages in thread
From: Kurt Borja @ 2026-10-10 6:52 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 5bb41b70353f..66a7fedfefde 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -241,6 +241,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);
@@ -690,6 +691,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;
@@ -1123,6 +1131,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);
@@ -1876,6 +1906,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++;
}
@@ -2027,6 +2060,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] 16+ messages in thread