Devicetree
 help / color / mirror / Atom feed
* [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support
@ 2026-08-28  6:38 Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
                   ` (9 more replies)
  0 siblings, 10 replies; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Hi all,

This series introduces support for TI ADS1262 and ADS1263 ADCs [1].
These devices are very similar (if not the same), except ADS1263
includes a secondary auxiliary ADC.

I plan to add these features to the main driver soon:

  - Filters
  - Calibration (manual and automatic)
  - GPIO controller capabilities
  - Channel hot-reloading in buffer mode
  - SPI offload support (38400 SPS turns out to be too high for some
    systems)
  - Conversion delay support
  - The ti-ads1263-adc2 driver for ADC2

The auxiliary ADC operates almost completely independent of the main
ADC. The only consideration that has to be taken for interoperability is
when reading conversion data in direct mode (Datasheet 9.4.7.1), which
happens only in buffer mode, when multiple channels are enabled.

When reading data in direct mode, all SPI activity is forbidden between
the data-ready signal and the data retrieval. To achieve this a second
mutex called xfer_lock was introduced to block SPI activity on the
device.

This is one of the biggest drivers I've developed, so I hope the code
and the comments are self-explanatory. If not, please let me know so I
can clarify them.

As always, thanks for your reviews and help. Submitting upstream is
always a great learning experience :)

[1] https://www.ti.com/lit/ds/symlink/ads1263.pdf

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
v4:
  dt-bindings
  -----------

  - Added excitation-current-chopping property to adc.yaml. I decided to
    go for *current* instead of *channel* because the current source is
    what's actually being swapped in-between conversions. IMO this is
    more accurate and also matches the terminology used in ad4170-4's
    datasheet.

  - Remove monitor channels from devicetree as their configuration is
    usually fixed

  - Allow ADS1263 parts to also specify #io-channel-cells = 1 and be
    fully compatible with ADS1262. (Sashiko)

  - In interrupt-names description, explain 'drdy' and 'doutdrdy' are
    two independent physical interrupts which output the same DRDY
    signal. (Sashiko)

  - Clarify the ground assumption on refp and refn regulators

  - In the example, describe vbias as an empty object instead of adding
    a redundant regulator-name.

  ti-ads1262
  ----------

  - Multi-channel buffered sampling is now done in PULSE mode to avoid
    missing interrupts due to latency

  - Monitor channels are defined statically, as opossed to parsing them
    from devicetree

  - Stop using devicetree 'reg' as the channel's scan_index because
    there might be "disabled" channels.

  - Setup power supplies before enabling the clock

  - Get the RESET GPIOD locally in ads1262_dev_reset() to avoid having
    to store the pointer to it

  - Only register regulators if the "regulator" node is present in
    firmware

  - Add .is_enabled() callback to refout (Sashiko)

  - Removed include <linux/mod_devicetable.h>

  - Restrict the available sample rates to those compatible with the FIR
    filter until other filters are supported

  - There's a few conditions that require the voltage magnitude of the
    power supplies. Instead of keeping track of each individually, get a
    handle to the regulator and get the voltage on demand.

  - Add fwnode_xlate callback to prepare for future ADC2 support

  - Added validate_scan_mask callback because the DRDY trigger is not
    supported when multiple channels are enabled (although it would not
    be the end of the world, the buffer would just fail to fill)

  Misc:
    - Renamed ads1262_dev_cmd() to ads1262_dev_send_cmd()
    - Removed __aligned(IIO_DMA_MIN_ALIGN) from rx[] buffer
    - Add ADS1262_SPI_XFER_SZ macro and a couple of static_assert()
    - Removed sign_extend32() because a simple cast is enough
    - Replaced regmap_read_bypassed() with plain regmap_read() in
      debugfs callback
    - Use -ENODEV instead of -ENXIO in some error paths
    - Added macro for internal/nominal clock rate
    - Reword dt-bindings patch for more clarity
    - Reword main driver patch for more clarity and some development
      details
    - Move register field values to macros instead of enums
    - Refactor ads1262_find_two()
    - Use regmap_test_bits() in regulator callbacks
    - Improve style of reg defaults table
    - Regulators are now registered only if there is a "regulators" node
      on the device

v3: https://patch.msgid.link/20260807-ads126x-v3-0-f89925d72792@gmail.com
v2: https://patch.msgid.link/20260628-ads126x-v2-0-4b1b231325ba@gmail.com
v1: https://patch.msgid.link/20260612-ads126x-v1-0-894c788d03ed@gmail.com

---
Kurt Borja (10):
      dt-bindings: adc: add excitation-current-chopping property
      dt-bindings: iio: adc: support the TI ADS126x ADC family
      iio: adc: add the ti-ads1262 driver
      iio: adc: ti-ads1262: support per-channel sampling frequency
      iio: adc: ti-ads1262: support per-channel reference and gain
      iio: adc: ti-ads1262: support input chopping
      iio: adc: ti-ads1262: support excitation currents
      iio: adc: ti-ads1262: support triggered buffer sampling
      iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
      iio: adc: ti-ads1262: support common mode supplies

 Documentation/devicetree/bindings/iio/adc/adc.yaml |    8 +
 .../devicetree/bindings/iio/adc/ti,ads1262.yaml    |  376 ++++
 MAINTAINERS                                        |    7 +
 drivers/iio/adc/Kconfig                            |   14 +
 drivers/iio/adc/Makefile                           |    1 +
 drivers/iio/adc/ti-ads1262.c                       | 2163 ++++++++++++++++++++
 6 files changed, 2569 insertions(+)
---
base-commit: 22359083a9e74d538ce383d5c0ee30cc20182187
change-id: 20251129-ads126x-fb6107505cae

-- 
Thanks, 
 ~ Kurt


^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28 16:33   ` Conor Dooley
  2026-08-28  6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
                   ` (8 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Add a generic excitation-current-chopping property. This is a feature
seen frequently in ADCs that are designed to ratiometrically measure
resistive loads, such as RTDs and strain gauges. Enabling this can
reduce any error due to excitation current mismatch by swapping
excitation sources of alternate conversions. This can have side-effects
in timing and filter response, so it is not always desirable to enable
this feature in some applications. Therefore, it is best to make this a
property that can be enabled or disabled in the device tree.

Suggested-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 Documentation/devicetree/bindings/iio/adc/adc.yaml | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/adc.yaml b/Documentation/devicetree/bindings/iio/adc/adc.yaml
index b673eaa5d072..fe20aa8f1bfc 100644
--- a/Documentation/devicetree/bindings/iio/adc/adc.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adc.yaml
@@ -109,6 +109,14 @@ properties:
       sources. The index in the array corresponds to the same index in the
       excitation-channels array.
 
+  excitation-current-chopping:
+    type: boolean
+    description:
+      If set, the excitation current sources are swapped and averaged on every
+      other conversion to reduce current mismatch. This generally comes at the
+      expense of added settling time between conversions, so should be omitted
+      on systems with high-frequency signals or high data rate requirements.
+
   burn-out-current-nanoamp:
     description:
       Burn-out current sources provide current to the channel's input pins for

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:45   ` sashiko-bot
  2026-08-28 16:39   ` Conor Dooley
  2026-08-28  6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
                   ` (7 subsequent siblings)
  9 siblings, 2 replies; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
integrated PGA, internal reference, excitation and burn-out current
sources for sensor biasing and diagnostics. The ADS1263 is compatible
with ADS1262, but includes a second auxiliary ADC (ADC2) to perform main
channel (ADC1) cross-checking measurements, system background
measurements, or temperature compensation of the primary sensor.

Both parts can configure per-channel voltage reference source,
excitation current sources (IDAC), plus input and IDAC chopping for
offset and IDAC mismatch cancellation. This lets the device drive and
ratiometrically measure RTDs and other resistive sensors.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 .../devicetree/bindings/iio/adc/ti,ads1262.yaml    | 376 +++++++++++++++++++++
 MAINTAINERS                                        |   6 +
 2 files changed, 382 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
new file mode 100644
index 000000000000..7e26572388e4
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
@@ -0,0 +1,376 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TI ADS1262/ADS1263 analog to digital converter
+
+maintainers:
+  - Kurt Borja <kuurtb@gmail.com>
+
+description: |
+  The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an
+  integrated PGA, reference, and internal fault monitors. The ADS1263 integrates
+  an auxiliary, 24-bit, ΔΣ ADC intended for background measurements.
+
+  Datasheets:
+    - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf
+
+properties:
+  compatible:
+    oneOf:
+      - const: ti,ads1262
+      - items:
+          - const: ti,ads1263
+          - const: ti,ads1262
+
+  reg:
+    maxItems: 1
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  spi-max-frequency:
+    maximum: 8000000
+
+  spi-cpha: true
+
+  interrupts:
+    minItems: 1
+    maxItems: 2
+    description: Data ready (DRDY) interrupt lines.
+
+  interrupt-names:
+    description:
+      The data ready (DRDY) signal has two independent interrupt lines. A
+      dedicated DRDY pin and a pin shared with the serial interface MISO pin,
+      which is called DOUT/DRDY.
+    minItems: 1
+    maxItems: 2
+    items:
+      enum: [drdy, doutdrdy]
+
+  start-gpios:
+    description: Start conversion control.
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  dvdd-supply:
+    description: Digital power supply.
+
+  avdd-supply:
+    description:
+      Analog power supply. In bipolar supply configurations, the reported
+      voltage should be in reference to DGND.
+
+  avss-supply:
+    description:
+      Negative analog power supply for bipolar configurations. AVSS can only be
+      at or below the ground reference (DGND). If not described, AVSS is assumed
+      to be connected to ground (0V).
+
+  clocks:
+    maxItems: 1
+
+  '#io-channel-cells':
+    minimum: 1
+    maximum: 2
+    description: |
+      The first cell selects the channel by its reg. The second cell selects
+      between the main ADC (ADC1) and the auxiliary ADC (ADC2) as follows:
+        0: ADC1
+        1: ADC2
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+  regulators:
+    type: object
+    description:
+      List of regulators provided by this chip.
+
+    properties:
+      vbias:
+        $ref: /schemas/regulator/regulator.yaml#
+        type: object
+        description:
+          Level-shift voltage output on the AINCOM pin. Its output is the
+          mid-voltage of the analog supply, (AVDD + AVSS) / 2, and is used to
+          shift floating sensors to within the ADC input range.
+        unevaluatedProperties: false
+
+      refout:
+        $ref: /schemas/regulator/regulator.yaml#
+        type: object
+        description:
+          Buffered internal voltage reference output on the REFOUT pin.
+        unevaluatedProperties: false
+
+    additionalProperties: false
+
+patternProperties:
+  "^ain([0-9]|com)-supply$":
+    description:
+      Common-mode voltage supply connected to AIN<N> or AINCOM.
+
+  "^refp[1-3]-supply$":
+    description:
+      Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
+      REFP3 (AIN4). If this regulator is not described and a channel selects
+      it as a reference source, then it's assumed to be connected to ground
+      (0V).
+
+  "^refn[1-3]-supply$":
+    description:
+      Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
+      REFN3 (AIN5). If this regulator is not described and a channel selects
+      it as a reference source, then it's assumed to be connected to ground
+      (0V).
+
+  "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
+    description:
+      Magnitude of the external reference resistor connected between REFP<N>
+      and REFN<M>. In ratiometric configurations, such as RTD measurements, the
+      IDAC excitation current returns through this resistor, generating the
+      reference voltage for the conversion.
+
+  "^channel@[0-9a-f]+$":
+    $ref: /schemas/iio/adc/adc.yaml#
+    unevaluatedProperties: false
+
+    properties:
+      reg:
+        maxItems: 1
+
+      single-channel:
+        minimum: 0
+        maximum: 10
+
+      common-mode-channel:
+        minimum: 0
+        maximum: 10
+        default: 10
+
+      diff-channels:
+        items:
+          minimum: 0
+          maximum: 10
+
+      input-chopping: true
+
+      reference-sources:
+        minItems: 2
+        description:
+          Indicates the reference sources for this channel. The first and second
+          items are the positive (REFP) and negative (REFN) sources of the main
+          ADC (ADC1). The third item is the reference source of the secondary
+          ADC (ADC2) and must always have a positive differential voltage.
+        items:
+          - enum: [internal-p, refp1, refp2, refp3, avdd]
+          - enum: [internal-n, refn1, refn2, refn3, avss]
+          - enum: [internal, refp1-refn1, refp2-refn2, refp3-refn3, avdd-avss]
+
+      ti,reference-reversal:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          Indicates that the ADC1 (this has no effect on ADC2) reference voltage
+          for this channel has negative polarity and thus should be internally
+          reversed.
+
+      excitation-channels:
+        minItems: 1
+        maxItems: 2
+        description:
+          Selects the pins for the IDAC sources from 0 (AIN0) to 10 (AINCOM).
+          The first value corresponds to IDAC1 and the second to IDAC2.
+        items:
+          minimum: 0
+          maximum: 10
+
+      excitation-current-nanoamp:
+        minItems: 1
+        maxItems: 2
+        description:
+          The first value corresponds to IDAC1 and the second to IDAC2.
+        items:
+          enum: [50000, 100000, 250000, 500000, 750000, 1000000, 1500000,
+                 2000000, 2500000, 3000000]
+
+      excitation-current-chopping: true
+
+      burn-out-current-nanoamp:
+        description:
+          Selects current magnitude for the sensor bias current source.
+        enum: [500, 2000, 10000, 50000, 200000]
+
+      ti,burn-out-resistor:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          Instead of a fixed current, the sensor bias (burn-out) current source
+          can be pulled using an internal 10 MΩ resistor.
+
+      ti,burn-out-polarity:
+        $ref: /schemas/types.yaml#/definitions/string
+        description:
+          The sensor bias can be configured to either pull-up or pull-down mode.
+          In pull-up mode, the current flows into the positive input and flows
+          out of the negative input. In pull-down mode, the polarities are
+          reversed.
+        enum: [pull-up, pull-down]
+        default: pull-up
+
+    dependencies:
+      excitation-channels: [excitation-current-nanoamp]
+      excitation-current-nanoamp: [excitation-channels]
+      burn-out-current-nanoamp:
+        not:
+          required:
+            - ti,burn-out-resistor
+
+    required:
+      - reg
+
+    allOf:
+      - if:
+          properties:
+            excitation-channels:
+              maxItems: 1
+          required:
+            - excitation-channels
+        then:
+          properties:
+            excitation-current-nanoamp:
+              maxItems: 1
+        else:
+          properties:
+            excitation-current-nanoamp:
+              minItems: 2
+
+    oneOf:
+      - required: [single-channel]
+      - required: [diff-channels]
+
+dependencies:
+  interrupts: [interrupt-names]
+  interrupts-extended: [interrupt-names]
+
+required:
+  - compatible
+  - reg
+  - spi-cpha
+  - avdd-supply
+  - dvdd-supply
+  - '#address-cells'
+  - '#size-cells'
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: ti,ads1263
+    then:
+      properties:
+        '#io-channel-cells':
+          minimum: 1
+          maximum: 2
+      patternProperties:
+        "^channel@[0-9]+$":
+          properties:
+            reference-sources:
+              minItems: 3
+              maxItems: 3
+              default: [internal-p, internal-n, internal]
+    else:
+      properties:
+        '#io-channel-cells':
+          const: 1
+      patternProperties:
+        "^channel@[0-9]+$":
+          properties:
+            reference-sources:
+              minItems: 2
+              maxItems: 2
+              default: [internal-p, internal-n]
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "ti,ads1262";
+            reg = <0>;
+            spi-max-frequency = <8000000>;
+            spi-cpha;
+            avdd-supply = <&avdd>;
+            dvdd-supply = <&dvdd>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
+            interrupt-names = "drdy";
+
+            /* Typical common mode voltage configuration */
+            aincom-supply = <&ads1262_vbias>;
+
+            regulators {
+                ads1262_vbias: vbias { };
+            };
+
+            channel@0 {
+                reg = <0>;
+                single-channel = <0>;
+                /* The VBIAS is enabled on pin 10 (AINCOM) */
+                common-mode-channel = <10>;
+            };
+        };
+    };
+
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "ti,ads1263", "ti,ads1262";
+            reg = <0>;
+            spi-max-frequency = <8000000>;
+            spi-cpha;
+            avdd-supply = <&avdd>;
+            dvdd-supply = <&dvdd>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
+            interrupt-names = "drdy";
+
+            ti,refp2-refn2-resistor-ohms = <3520>;
+
+            channel@0 {
+                reg = <0>;
+                diff-channels = <4 5>;
+                reference-sources = "refp2", "refn2", "refp2-refn2";
+                excitation-channels = <1 6>;
+                excitation-current-nanoamp = <500000 500000>;
+            };
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index f3be900e5b9d..7593e33c5346 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27019,6 +27019,12 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml
 F:	drivers/iio/adc/ti-ads1018.c
 
+TI ADS1262 ADC DRIVER
+M:	Kurt Borja <kuurtb@gmail.com>
+L:	linux-iio@vger.kernel.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
+
 TI ADS7924 ADC DRIVER
 M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
 L:	linux-iio@vger.kernel.org

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:52   ` sashiko-bot
  2026-08-28  8:09   ` Andy Shevchenko
  2026-08-28  6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
                   ` (6 subsequent siblings)
  9 siblings, 2 replies; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Add the ti-ads1262 driver with initial support for the primary ADC
(ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
and interoperability considerations were taken into account.

Various features such as accurate timeout delays, per-channel reference
sources, scale, offset, settling latency, excitation currents and
diagnostics are intentionally left out for future support.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 MAINTAINERS                  |   1 +
 drivers/iio/adc/Kconfig      |  11 +
 drivers/iio/adc/Makefile     |   1 +
 drivers/iio/adc/ti-ads1262.c | 938 +++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 951 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 7593e33c5346..0607aa90a6b1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27024,6 +27024,7 @@ M:	Kurt Borja <kuurtb@gmail.com>
 L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
+F:	drivers/iio/adc/ti-ads1262.c
 
 TI ADS7924 ADC DRIVER
 M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 38e212a6f789..e51ce42bce94 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1860,6 +1860,17 @@ config TI_ADS124S08
 	  This driver can also be built as a module. If so, the module will be
 	  called ti-ads124s08.
 
+config TI_ADS1262
+	tristate "Texas Instruments ADS1262"
+	depends on SPI
+	select REGMAP
+	help
+	  If you say yes here you get support for Texas Instruments ADS1262 and
+	  ADS1263 ADC chips.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called ti-ads1262.
+
 config TI_ADS1298
 	tristate "Texas Instruments ADS1298"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 9271a5e1ba01..989bed6fba3d 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -158,6 +158,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
 obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
 obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
 obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
+obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o
 obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
 obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
 obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
new file mode 100644
index 000000000000..d919144c23cb
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -0,0 +1,938 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments ADS1262 ADC driver
+ *
+ * Copyright (C) 2026 Kurt Borja <kuurtb@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/compiler_attributes.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/lockdep.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+
+#define ADS1262_OPCODE_NOP			0x00
+#define ADS1262_OPCODE_RESET			0x06
+#define ADS1262_OPCODE_START1			0x08
+#define ADS1262_OPCODE_STOP1			0x0A
+#define ADS1262_OPCODE_START2			0x0C
+#define ADS1262_OPCODE_STOP2			0x0E
+#define ADS1262_OPCODE_RDATA1			0x12
+#define ADS1262_OPCODE_RDATA2			0x14
+#define ADS1262_OPCODE_SYOCAL1			0x16
+#define ADS1262_OPCODE_SYGCAL1			0x17
+#define ADS1262_OPCODE_SFOCAL1			0x19
+#define ADS1262_OPCODE_SYOCAL2			0x1B
+#define ADS1262_OPCODE_SYGCAL2			0x1C
+#define ADS1262_OPCODE_SFOCAL2			0x1E
+#define ADS1262_OPCODE_RREG			0x20
+#define ADS1262_OPCODE_WREG			0x40
+
+#define ADS1262_ID_REG				0x00
+#define   ADS1262_DEV_ID_MASK			GENMASK(7, 5)
+#define     ADS1262_DEV_ID			  0
+#define     ADS1263_DEV_ID			  1
+#define   ADS1262_REV_ID_MASK			GENMASK(4, 0)
+
+#define ADS1262_POWER_REG			0x01
+#define   ADS1262_POWER_RESET_MASK		BIT(4)
+#define   ADS1262_POWER_VBIAS_MASK		BIT(1)
+#define   ADS1262_POWER_INTREF_MASK		BIT(0)
+
+#define ADS1262_INTERFACE_REG			0x02
+#define   ADS1262_INTERFACE_TIMEOUT_MASK	BIT(3)
+#define   ADS1262_INTERFACE_STATUS_MASK		BIT(2)
+#define   ADS1262_INTERFACE_CRC_MASK		GENMASK(1, 0)
+
+#define ADS1262_MODE0_REG			0x03
+#define   ADS1262_MODE0_REFREV_MASK		BIT(7)
+#define   ADS1262_MODE0_RUNMODE_MASK		BIT(6)
+#define     ADS1262_RUNMODE_CONTINUOUS		  0
+#define     ADS1262_RUNMODE_PULSE		  1
+#define   ADS1262_MODE0_IDAC_CHOP_MASK		BIT(5)
+#define   ADS1262_MODE0_INPUT_CHOP_MASK		BIT(4)
+#define   ADS1262_MODE0_DELAY_MASK		GENMASK(3, 0)
+
+#define ADS1262_MODE1_REG			0x04
+#define   ADS1262_MODE1_FILTER_MASK		GENMASK(7, 5)
+#define     ADS1262_FILTER_FIR			  4
+#define   ADS1262_MODE1_SBADC_MASK		BIT(4)
+#define   ADS1262_MODE1_SBPOL_MASK		BIT(3)
+#define   ADS1262_MODE1_SBMAG_MASK		GENMASK(2, 0)
+
+#define ADS1262_MODE2_REG			0x05
+#define   ADS1262_MODE2_BYPASS_MASK		BIT(7)
+#define   ADS1262_MODE2_GAIN_MASK		GENMASK(6, 4)
+#define   ADS1262_MODE2_DR_MASK			GENMASK(3, 0)
+#define     ADS1262_DR_2_5_SPS			  0
+#define     ADS1262_DR_5_SPS			  1
+#define     ADS1262_DR_10_SPS			  2
+#define     ADS1262_DR_16_6_SPS			  3
+#define     ADS1262_DR_20_SPS			  4
+#define     ADS1262_DR_50_SPS			  5
+#define     ADS1262_DR_60_SPS			  6
+#define     ADS1262_DR_100_SPS			  7
+#define     ADS1262_DR_400_SPS			  8
+#define     ADS1262_DR_1200_SPS			  9
+#define     ADS1262_DR_2400_SPS			  10
+#define     ADS1262_DR_4800_SPS			  11
+#define     ADS1262_DR_7200_SPS			  12
+#define     ADS1262_DR_14400_SPS		  13
+#define     ADS1262_DR_19200_SPS		  14
+#define     ADS1262_DR_38400_SPS		  15
+
+#define ADS1262_INPMUX_REG			0x06
+#define   ADS1262_INPMUX_MUXP_MASK		GENMASK(7, 4)
+#define   ADS1262_INPMUX_MUXN_MASK		GENMASK(3, 0)
+#define     ADS1262_INPMUX_AIN1			  1
+#define     ADS1262_INPMUX_AINCOM		  10
+#define     ADS1262_INPMUX_TEMP			  11
+#define     ADS1262_INPMUX_AVDD			  12
+#define     ADS1262_INPMUX_DVDD			  13
+#define     ADS1262_INPMUX_TDAC			  14
+#define     ADS1262_INPMUX_FLOAT		  15
+
+#define ADS1262_OFCAL0_REG			0x07
+#define ADS1262_OFCAL1_REG			0x08
+#define ADS1262_OFCAL2_REG			0x09
+#define ADS1262_FSCAL0_REG			0x0A
+#define ADS1262_FSCAL1_REG			0x0B
+#define ADS1262_FSCAL2_REG			0x0C
+
+#define ADS1262_IDACMUX_REG			0x0D
+#define   ADS1262_IDACMUX_MUX2_MASK		GENMASK(7, 4)
+#define   ADS1262_IDACMUX_MUX1_MASK		GENMASK(3, 0)
+#define     ADS1262_IDACMUX_NO_CONN		0b1011
+
+#define ADS1262_IDACMAG_REG			0x0E
+#define   ADS1262_IDACMAG_MAG2_MASK		GENMASK(7, 4)
+#define   ADS1262_IDACMAG_MAG1_MASK		GENMASK(3, 0)
+
+#define ADS1262_REFMUX_REG			0x0F
+#define   ADS1262_REFMUX_RMUXP_MASK		GENMASK(5, 3)
+#define   ADS1262_REFMUX_RMUXN_MASK		GENMASK(2, 0)
+
+#define ADS1262_TDACP_REG			0x10
+#define   ADS1262_TDACP_OUTP_MASK		BIT(7)
+#define   ADS1262_TDACP_MAGP_MASK		GENMASK(4, 0)
+
+#define ADS1262_TDACN_REG			0x11
+#define   ADS1262_TDACN_OUTN_MASK		BIT(7)
+#define   ADS1262_TDACN_MAGN_MASK		GENMASK(4, 0)
+
+#define ADS1262_GPIOCON_REG			0x12
+#define ADS1262_GPIODIR_REG			0x13
+#define ADS1262_GPIODAT_REG			0x14
+
+#define ADS1262_ADC2CFG_REG			0x15
+#define   ADS1262_ADC2CFG_DR2_MASK		GENMASK(7, 6)
+#define   ADS1262_ADC2CFG_REF2_MASK		GENMASK(5, 3)
+#define   ADS1262_ADC2CFG_GAIN2_MASK		GENMASK(2, 0)
+
+#define ADS1262_ADC2MUX_REG			0x16
+#define   ADS1262_ADC2MUX_MUXP2_MASK		GENMASK(7, 4)
+#define   ADS1262_ADC2MUX_MUXN2_MASK		GENMASK(3, 0)
+
+#define ADS1262_ADC2OFC0_REG			0x17
+#define ADS1262_ADC2OFC1_REG			0x18
+#define ADS1262_ADC2FSC0_REG			0x19
+#define ADS1262_ADC2FSC1_REG			0x1A
+
+#define ADS1262_REG_COUNT			(ADS1262_ADC2FSC1_REG + 1)
+
+/*
+ * The power transition timing requirement is 65536 clock cycles, at the minimum
+ * clock frequency this is 65536 microseconds.
+ */
+#define ADS1262_POWER_TRANS_USECS		65536
+
+#define ADS1262_NOMINAL_CLK_RATE		7372800
+
+#define ADS1262_FW_CHANNEL_COUNT		16
+#define ADS1262_MON_CHANNEL_COUNT		4
+#define ADS1262_REGMAP_WRITE_SZ			8
+#define ADS1262_MONITOR_ADDR_OFFSET		100
+
+#define ADS1262_ADC1_RESOLUTION			32
+
+struct ads1262 {
+	struct spi_device *spi;
+	struct regmap *regmap;
+	struct gpio_desc *start_gpiod;
+	/* protects concurrent SPI transfers */
+	struct mutex xfer_lock;
+	/* protects channel state */
+	struct mutex chan_lock;
+	struct completion drdy;
+	unsigned long clk_rate;
+	u8 dev_id;
+};
+
+static const char * const ads1262_device_id_to_name[] = {
+	[ADS1262_DEV_ID] = "ads1262",
+	[ADS1263_DEV_ID] = "ads1263",
+};
+
+static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
+	{
+		.type = IIO_TEMP,
+		.channel = ADS1262_INPMUX_TEMP,
+		.channel2 = ADS1262_INPMUX_TEMP,
+		.address = ADS1262_MONITOR_ADDR_OFFSET + 0,
+		.scan_type = {
+			.format = IIO_SCAN_FORMAT_SIGNED_INT,
+			.realbits = ADS1262_ADC1_RESOLUTION,
+			.storagebits = 32,
+			.endianness = IIO_BE,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.channel = ADS1262_INPMUX_AVDD,
+		.channel2 = ADS1262_INPMUX_AVDD,
+		.indexed = 1,
+		.address = ADS1262_MONITOR_ADDR_OFFSET + 1,
+		.scan_type = {
+			.format = IIO_SCAN_FORMAT_SIGNED_INT,
+			.realbits = ADS1262_ADC1_RESOLUTION,
+			.storagebits = 32,
+			.endianness = IIO_BE,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.channel = ADS1262_INPMUX_DVDD,
+		.channel2 = ADS1262_INPMUX_DVDD,
+		.indexed = 1,
+		.address = ADS1262_MONITOR_ADDR_OFFSET + 2,
+		.scan_type = {
+			.format = IIO_SCAN_FORMAT_SIGNED_INT,
+			.realbits = ADS1262_ADC1_RESOLUTION,
+			.storagebits = 32,
+			.endianness = IIO_BE,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+	{
+		.type = IIO_VOLTAGE,
+		.channel = ADS1262_INPMUX_TDAC,
+		.channel2 = ADS1262_INPMUX_TDAC,
+		.indexed = 1,
+		.differential = 1,
+		.address = ADS1262_MONITOR_ADDR_OFFSET + 3,
+		.scan_type = {
+			.format = IIO_SCAN_FORMAT_SIGNED_INT,
+			.realbits = ADS1262_ADC1_RESOLUTION,
+			.storagebits = 32,
+			.endianness = IIO_BE,
+		},
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+	},
+};
+
+static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
+{
+	guard(mutex)(&st->xfer_lock);
+
+	return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
+}
+
+static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
+{
+	guard(mutex)(&st->xfer_lock);
+
+	return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
+}
+
+static int ads1262_dev_reset(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct gpio_desc *reset_gpiod;
+	int ret;
+
+	reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+	if (IS_ERR(reset_gpiod))
+		return dev_err_probe(dev, PTR_ERR(reset_gpiod),
+				     "failed to get reset GPIO\n");
+
+	if (reset_gpiod) {
+		/*
+		 * Wait a power transition cycle to ensure we are in a
+		 * powered-off state after acquiring the RESET GPIO.
+		 */
+		fsleep(ADS1262_POWER_TRANS_USECS);
+
+		ret = gpiod_set_value_cansleep(reset_gpiod, 0);
+		if (ret)
+			return ret;
+
+		fsleep(ADS1262_POWER_TRANS_USECS);
+	} else {
+		ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_RESET);
+		if (ret)
+			return ret;
+		/*
+		 * The RESET timing requirement is 8 clock cycles, at the
+		 * minimum clock rate this is 8 microseconds.
+		 */
+		fsleep(8);
+	}
+
+	return 0;
+}
+
+static int ads1262_dev_start(struct ads1262 *st)
+{
+	int ret;
+
+	if (st->start_gpiod)
+		ret = gpiod_set_value_cansleep(st->start_gpiod, 1);
+	else
+		ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_START1);
+
+	return ret;
+}
+
+static int ads1262_dev_stop(struct ads1262 *st)
+{
+	int ret;
+
+	if (st->start_gpiod)
+		ret = gpiod_set_value_cansleep(st->start_gpiod, 0);
+	else
+		ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_STOP1);
+
+	return ret;
+}
+
+static int ads1262_dev_start_one(struct ads1262 *st)
+{
+	int ret;
+
+	ret = ads1262_dev_start(st);
+	if (ret)
+		return ret;
+
+	if (st->start_gpiod) {
+		/*
+		 * The START pulse timing requirement is 4 clock cycles, at the
+		 * minimum clock rate this is 4 microseconds.
+		 */
+		fsleep(4);
+		return ads1262_dev_stop(st);
+	}
+
+	return 0;
+}
+
+static int ads1262_wait_for_conversion(struct ads1262 *st)
+{
+	u64 max_lat_ms;
+	long ret;
+
+	/*
+	 * The first conversion latency is affected by the channel's data rate,
+	 * filter, the configurable conversion delay and whether chop mode
+	 * and/or IDAC rotation mode are enabled.
+	 *
+	 * The worst possible latency is calculated by taking the lowest data
+	 * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
+	 * (Table 9-13). Then we scale it by the actual clock rate and multiply
+	 * by 4 to account for chop and IDAC rotation modes (Equation 20).
+	 */
+	max_lat_ms = 4 * div_u64(1600ULL * ADS1262_NOMINAL_CLK_RATE, st->clk_rate);
+	ret = wait_for_completion_interruptible_timeout(&st->drdy,
+							msecs_to_jiffies(max_lat_ms));
+	if (ret < 0)
+		return ret;
+	if (!ret)
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int ads1262_channel_enable(struct ads1262 *st,
+				  const struct iio_chan_spec *spec)
+{
+	guard(mutex)(&st->xfer_lock);
+	guard(mutex)(&st->chan_lock);
+
+	return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+				  ADS1262_INPMUX_MUXN_MASK |
+				  ADS1262_INPMUX_MUXP_MASK,
+				  FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
+				  FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+}
+
+static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
+{
+	guard(mutex)(&st->xfer_lock);
+
+	return regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+				  ADS1262_MODE0_RUNMODE_MASK,
+				  FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode));
+}
+
+static int ads1262_channel_read(struct iio_dev *indio_dev,
+				const struct iio_chan_spec *spec, __be32 *val)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	int ret;
+
+	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
+
+	ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
+	if (ret)
+		return ret;
+
+	ret = ads1262_channel_enable(st, spec);
+	if (ret)
+		return ret;
+
+	reinit_completion(&st->drdy);
+
+	ret = ads1262_dev_start_one(st);
+	if (ret)
+		return ret;
+
+	ret = ads1262_wait_for_conversion(st);
+	if (ret)
+		return ret;
+
+	return ads1262_dev_read_by_cmd(st, ADS1262_OPCODE_RDATA1, val);
+}
+
+static int ads1262_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	__be32 raw;
+	int ret;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = ads1262_channel_read(indio_dev, chan, &raw);
+		if (ret)
+			return ret;
+		*val = be32_to_cpu(raw);
+
+		return IIO_VAL_INT;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+				      unsigned int writeval, unsigned int *readval)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+
+	guard(mutex)(&st->xfer_lock);
+
+	if (readval)
+		return regmap_read(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
+				const struct fwnode_reference_args *iiospec)
+{
+	/* REVISIT: the auxiliary ADC (ADC2) is currently not supported */
+	if (iiospec->nargs > 1 && iiospec->args[1])
+		return -EINVAL;
+
+	if (!iiospec->nargs)
+		return 0;
+
+	for (unsigned int i = 0; i < indio_dev->num_channels; i++) {
+		if (indio_dev->channels[i].address == iiospec->args[0])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
+static const struct iio_info ads1262_iio_info = {
+	.read_raw = ads1262_read_raw,
+	.debugfs_reg_access = ads1262_debugfs_reg_access,
+	.fwnode_xlate = ads1262_fwnode_xlate,
+};
+
+static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
+{
+	struct ads1262 *st = dev_id;
+
+	complete(&st->drdy);
+
+	return IRQ_HANDLED;
+}
+
+static int ads1262_dev_configure(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	int id, ret;
+
+	ret = ads1262_dev_reset(st);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to reset device\n");
+
+	guard(mutex)(&st->xfer_lock);
+
+	ret = regmap_read(st->regmap, ADS1262_ID_REG, &id);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to read device ID\n");
+
+	id = FIELD_GET(ADS1262_DEV_ID_MASK, id);
+	if (id > ADS1263_DEV_ID) {
+		 dev_warn(dev, "unknown device ID %d\n", id);
+		 id = ADS1262_DEV_ID;
+	}
+	st->dev_id = id;
+
+	ret = regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
+				ADS1262_POWER_RESET_MASK);
+	if (ret)
+		return ret;
+
+	ret = regmap_set_bits(st->regmap, ADS1262_POWER_REG,
+			      ADS1262_POWER_INTREF_MASK);
+	if (ret)
+		return ret;
+
+	return regmap_clear_bits(st->regmap, ADS1262_INTERFACE_REG,
+				 ADS1262_INTERFACE_STATUS_MASK |
+				 ADS1262_INTERFACE_CRC_MASK);
+}
+
+static bool ads1262_readable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADS1262_ID_REG ... ADS1262_ADC2FSC1_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool ads1262_writeable_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADS1262_POWER_REG ... ADS1262_ADC2FSC1_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static bool ads1262_volatile_reg(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case ADS1262_POWER_REG:
+	case ADS1262_OFCAL0_REG ... ADS1262_FSCAL2_REG:
+	case ADS1262_GPIODAT_REG:
+	case ADS1262_ADC2OFC0_REG ... ADS1262_ADC2FSC1_REG:
+		return true;
+	default:
+		return false;
+	}
+}
+
+static const struct reg_default ads1262_reg_defaults[] = {
+	{
+		.reg = ADS1262_INTERFACE_REG,
+		.def = FIELD_PREP_CONST(ADS1262_INTERFACE_STATUS_MASK, 1)
+		     | FIELD_PREP_CONST(ADS1262_INTERFACE_CRC_MASK, 1)
+	},
+	{
+		.reg = ADS1262_MODE0_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_MODE1_REG,
+		.def = FIELD_PREP_CONST(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR)
+	},
+	{
+		.reg = ADS1262_MODE2_REG,
+		.def = FIELD_PREP_CONST(ADS1262_MODE2_DR_MASK, ADS1262_DR_20_SPS)
+	},
+	{
+		.reg = ADS1262_INPMUX_REG,
+		.def = FIELD_PREP_CONST(ADS1262_INPMUX_MUXN_MASK, ADS1262_INPMUX_AIN1)
+	},
+	{
+		.reg = ADS1262_IDACMUX_REG,
+		.def = FIELD_PREP_CONST(ADS1262_IDACMUX_MUX2_MASK, ADS1262_IDACMUX_NO_CONN)
+		     | FIELD_PREP_CONST(ADS1262_IDACMUX_MUX1_MASK, ADS1262_IDACMUX_NO_CONN)
+	},
+	{
+		.reg = ADS1262_IDACMAG_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_REFMUX_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_TDACP_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_TDACN_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_GPIOCON_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_GPIODIR_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_ADC2CFG_REG,
+		.def = 0x00
+	},
+	{
+		.reg = ADS1262_ADC2MUX_REG,
+		.def = FIELD_PREP_CONST(ADS1262_ADC2MUX_MUXN2_MASK, ADS1262_INPMUX_AIN1)
+	},
+};
+
+static const struct regmap_config ads1262_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.writeable_reg = ads1262_writeable_reg,
+	.readable_reg = ads1262_readable_reg,
+	.volatile_reg = ads1262_volatile_reg,
+	.reg_defaults = ads1262_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
+	.max_register = ADS1262_ADC2FSC1_REG,
+	.can_sleep = true,
+	.cache_type = REGCACHE_MAPLE,
+};
+
+static int ads1262_regmap_read(void *context, const void *reg_buf,
+			       size_t reg_size, void *val_buf, size_t val_size)
+{
+	struct ads1262 *st = context;
+	u8 tx[2];
+
+	lockdep_assert_held(&st->xfer_lock);
+
+	/*
+	 * The register read operation uses a two byte command header followed
+	 * by the register data:
+	 *
+	 *	byte 0:   RREG opcode | register address
+	 *	byte 1:   number of registers to transfer, minus one
+	 *	byte 2..: register data
+	 */
+	tx[0] = *(u8 *)reg_buf | ADS1262_OPCODE_RREG;
+	tx[1] = val_size - 1;
+
+	return spi_write_then_read(st->spi, tx, sizeof(tx), val_buf, val_size);
+}
+
+static int ads1262_regmap_gather_write(void *context, const void *reg_buf,
+				       size_t reg_size, const void *val_buf,
+				       size_t val_size)
+{
+	struct ads1262 *st = context;
+	u8 tx[ADS1262_REGMAP_WRITE_SZ + 2];
+
+	lockdep_assert_held(&st->xfer_lock);
+
+	/*
+	 * The register write operation uses a two byte command header followed
+	 * by the register data:
+	 *
+	 *	byte 0:   WREG opcode | register address
+	 *	byte 1:   number of registers to transfer, minus one
+	 *	byte 2..: register data
+	 */
+	tx[0] = *(u8 *)reg_buf | ADS1262_OPCODE_WREG;
+	tx[1] = val_size - 1;
+	memcpy(&tx[2], val_buf, val_size);
+
+	return spi_write_then_read(st->spi, tx, 2 + val_size, NULL, 0);
+}
+
+static int ads1262_regmap_write(void *context, const void *data, size_t count)
+{
+	return ads1262_regmap_gather_write(context, data, 1, data + 1,
+					   count - 1);
+}
+
+static const struct regmap_bus ads1262_regmap_bus = {
+	.read = ads1262_regmap_read,
+	.gather_write = ads1262_regmap_gather_write,
+	.write = ads1262_regmap_write,
+	.reg_format_endian_default = REGMAP_ENDIAN_BIG,
+	.val_format_endian_default = REGMAP_ENDIAN_BIG,
+	.max_raw_write = ADS1262_REGMAP_WRITE_SZ,
+};
+
+static int ads1262_parse_channel_node(struct ads1262 *st,
+				      struct iio_chan_spec *spec,
+				      struct fwnode_handle *node)
+{
+	struct device *dev = &st->spi->dev;
+	u32 pins[2];
+	int ret;
+
+	if (fwnode_property_present(node, "single-channel")) {
+		ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
+		if (ret)
+			return dev_err_probe(dev, ret, "%pfwP: failed to read single-channel\n",
+					     node);
+
+		pins[1] = ADS1262_INPMUX_AINCOM;
+
+		if (fwnode_property_present(node, "common-mode-channel")) {
+			ret = fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "%pfwP: failed to read common-mode-channel\n",
+						     node);
+		}
+	} else if (fwnode_property_present(node, "diff-channels")) {
+		ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
+						     ARRAY_SIZE(pins));
+		if (ret)
+			return dev_err_probe(dev, ret, "%pfwP: failed to read diff-channels\n",
+					     node);
+
+		spec->differential = true;
+	} else {
+		return dev_err_probe(dev, -EINVAL,
+				     "%pfwP: one of single-channel or diff-channels is required\n",
+				     node);
+	}
+
+	if (pins[0] > ADS1262_INPMUX_AINCOM || pins[1] > ADS1262_INPMUX_AINCOM)
+		return dev_err_probe(dev, -EINVAL, "%pfwP: input channels not in range\n", node);
+
+	spec->channel = pins[0];
+	spec->channel2 = pins[1];
+
+	return 0;
+}
+
+static int ads1262_parse_channels(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	struct iio_chan_spec *chan_specs;
+	unsigned int num_fw_channels, num_specs;
+	unsigned int i = 0;
+	u32 reg;
+	int ret;
+
+	num_fw_channels = device_get_named_child_node_count(dev, "channel");
+	if (num_fw_channels > ADS1262_FW_CHANNEL_COUNT)
+		return dev_err_probe(dev, -EINVAL, "too many channels\n");
+
+	/* Account for the monitor channels and timestamp */
+	num_specs = num_fw_channels + ADS1262_MON_CHANNEL_COUNT + 1;
+	chan_specs = devm_kcalloc(dev, num_specs, sizeof(*chan_specs), GFP_KERNEL);
+	if (!chan_specs)
+		return -ENOMEM;
+
+	device_for_each_named_child_node_scoped(dev, node, "channel") {
+		struct iio_chan_spec *spec = &chan_specs[i];
+
+		ret = fwnode_property_read_u32(node, "reg", &reg);
+		if (ret)
+			return dev_err_probe(dev, ret, "%pfwP: failed to read channel reg\n", node);
+		if (reg >= ADS1262_MONITOR_ADDR_OFFSET)
+			return dev_err_probe(dev, -EINVAL, "%pfwP: reg out of range\n", node);
+
+		ret = ads1262_parse_channel_node(st, spec, node);
+		if (ret)
+			return ret;
+
+		spec->type = IIO_VOLTAGE;
+		spec->indexed = true;
+		spec->scan_index = i;
+		spec->address = reg;
+		spec->scan_type = (struct iio_scan_type) {
+			.format = IIO_SCAN_FORMAT_SIGNED_INT,
+			.realbits = ADS1262_ADC1_RESOLUTION,
+			.storagebits = 32,
+			.endianness = IIO_BE,
+		};
+		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+
+		i++;
+	}
+
+	memcpy(&chan_specs[i], ads1262_monitor_chan_specs,
+	       sizeof(ads1262_monitor_chan_specs));
+
+	for (unsigned int mon = 0; mon < ADS1262_MON_CHANNEL_COUNT; mon++) {
+		chan_specs[i].scan_index = i;
+		i++;
+	}
+
+	chan_specs[i] = IIO_CHAN_SOFT_TIMESTAMP(i);
+	i++;
+
+	indio_dev->channels = chan_specs;
+	indio_dev->num_channels = i;
+
+	return 0;
+}
+
+static int ads1262_supply_setup(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	int ret;
+
+	ret = devm_regulator_get_enable(dev, "dvdd");
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
+
+	ret = devm_regulator_get_enable(dev, "avdd");
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+
+	ret = devm_regulator_get_enable_optional(dev, "avss");
+	if (ret < 0 && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+
+	fsleep(ADS1262_POWER_TRANS_USECS);
+
+	return 0;
+}
+
+static int ads1262_spi_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct iio_dev *indio_dev;
+	struct ads1262 *st;
+	unsigned long rate;
+	struct clk *clk;
+	int irq;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &ads1262_iio_info;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+	init_completion(&st->drdy);
+
+	ret = devm_mutex_init(dev, &st->chan_lock);
+	if (ret)
+		return ret;
+	ret = devm_mutex_init(dev, &st->xfer_lock);
+	if (ret)
+		return ret;
+
+	ret = ads1262_parse_channels(indio_dev);
+	if (ret)
+		return ret;
+
+	ret = ads1262_supply_setup(st);
+	if (ret)
+		return ret;
+
+	clk = devm_clk_get_optional_enabled(dev, NULL);
+	if (IS_ERR(clk))
+		return dev_err_probe(dev, PTR_ERR(clk), "failed to get external clock\n");
+
+	rate = clk_get_rate(clk);
+	if (clk && !rate)
+		return dev_err_probe(dev, -EINVAL, "failed to get clock rate\n");
+	st->clk_rate = rate ? rate : ADS1262_NOMINAL_CLK_RATE;
+
+	st->start_gpiod = devm_gpiod_get_optional(dev, "start", GPIOD_OUT_LOW);
+	if (IS_ERR(st->start_gpiod))
+		return dev_err_probe(dev, PTR_ERR(st->start_gpiod),
+				     "failed to get start GPIO\n");
+
+	st->regmap = devm_regmap_init(dev, &ads1262_regmap_bus, st,
+				      &ads1262_regmap_config);
+	if (IS_ERR(st->regmap))
+		return PTR_ERR(st->regmap);
+
+	ret = ads1262_dev_configure(st);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to configure device\n");
+
+	indio_dev->name = ads1262_device_id_to_name[st->dev_id];
+
+	/*
+	 * REVISIT: This chip has software polling capabilities, which could be
+	 * used to stop depending on the DRDY signal.
+	 *
+	 * Additionally, the MISO pin also can be used as a DRDY IRQ, in which
+	 * case the interrupt would be named 'doutdrdy', but requires extra
+	 * timing and synchronization considerations to be reliable.
+	 */
+	irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
+	if (irq < 0)
+		return dev_err_probe(dev, irq,
+				     "the 'drdy' IRQ is currently required for operation\n");
+
+	ret = devm_request_irq(dev, irq, ads1262_irq_handler, IRQF_NO_THREAD,
+			       indio_dev->name, st);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct of_device_id ads1262_of_match[] = {
+	{ .compatible = "ti,ads1262" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ads1262_of_match);
+
+static const struct spi_device_id ads1262_spi_match[] = {
+	{ .name = "ads1262" },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ads1262_spi_match);
+
+static struct spi_driver ads1262_spi_driver = {
+	.driver = {
+		.name = "ads1262",
+		.of_match_table = ads1262_of_match,
+	},
+	.probe = ads1262_spi_probe,
+	.id_table = ads1262_spi_match,
+};
+module_spi_driver(ads1262_spi_driver);
+
+MODULE_DESCRIPTION("Texas Instruments ADS1262 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>");

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (2 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  7:03   ` sashiko-bot
  2026-08-28  6:38 ` [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
                   ` (5 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Add per-channel sampling frequency support. The "available" attribute is
assigned per-channel too, in order to eventually support per-filter
availability.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/ti-ads1262.c | 233 ++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 228 insertions(+), 5 deletions(-)

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index d919144c23cb..86ea9b6232d8 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -25,6 +25,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
 #include <linux/types.h>
+#include <linux/units.h>
 
 #include <asm/byteorder.h>
 
@@ -99,6 +100,7 @@
 #define     ADS1262_DR_14400_SPS		  13
 #define     ADS1262_DR_19200_SPS		  14
 #define     ADS1262_DR_38400_SPS		  15
+#define     ADS1262_DR_COUNT			  (ADS1262_DR_38400_SPS + 1)
 
 #define ADS1262_INPMUX_REG			0x06
 #define   ADS1262_INPMUX_MUXP_MASK		GENMASK(7, 4)
@@ -166,6 +168,7 @@
 #define ADS1262_POWER_TRANS_USECS		65536
 
 #define ADS1262_NOMINAL_CLK_RATE		7372800
+#define ADS1262_MODULATOR_DIV			8
 
 #define ADS1262_FW_CHANNEL_COUNT		16
 #define ADS1262_MON_CHANNEL_COUNT		4
@@ -174,10 +177,17 @@
 
 #define ADS1262_ADC1_RESOLUTION			32
 
+struct ads1262_channel {
+	u8 data_rate;
+	u8 filter;
+};
+
 struct ads1262 {
 	struct spi_device *spi;
 	struct regmap *regmap;
 	struct gpio_desc *start_gpiod;
+	size_t num_channels;
+	struct ads1262_channel *channels;
 	/* protects concurrent SPI transfers */
 	struct mutex xfer_lock;
 	/* protects channel state */
@@ -185,6 +195,8 @@ struct ads1262 {
 	struct completion drdy;
 	unsigned long clk_rate;
 	u8 dev_id;
+	int sampling_freq_table[ADS1262_DR_COUNT][2];
+	int sampling_freq_fir[ADS1262_DR_16_6_SPS + 1][2];
 };
 
 static const char * const ads1262_device_id_to_name[] = {
@@ -204,7 +216,9 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.storagebits = 32,
 			.endianness = IIO_BE,
 		},
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -218,7 +232,9 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.storagebits = 32,
 			.endianness = IIO_BE,
 		},
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -232,7 +248,9 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.storagebits = 32,
 			.endianness = IIO_BE,
 		},
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -247,10 +265,66 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.storagebits = 32,
 			.endianness = IIO_BE,
 		},
-		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW),
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
 	},
 };
 
+static const struct ads1262_channel ads1262_default_channel = {
+	.data_rate = ADS1262_DR_20_SPS,
+	.filter = ADS1262_FILTER_FIR,
+};
+
+static const u32 ads1262_mod_decimator_a[] = {
+	[ADS1262_DR_2_5_SPS]	= 64,
+	[ADS1262_DR_5_SPS]	= 64,
+	[ADS1262_DR_10_SPS]	= 64,
+	[ADS1262_DR_16_6_SPS]	= 64,
+	[ADS1262_DR_20_SPS]	= 64,
+	[ADS1262_DR_50_SPS]	= 64,
+	[ADS1262_DR_60_SPS]	= 64,
+	[ADS1262_DR_100_SPS]	= 64,
+	[ADS1262_DR_400_SPS]	= 64,
+	[ADS1262_DR_1200_SPS]	= 64,
+	[ADS1262_DR_2400_SPS]	= 64,
+	[ADS1262_DR_4800_SPS]	= 64,
+	[ADS1262_DR_7200_SPS]	= 64,
+	[ADS1262_DR_14400_SPS]	= 64,
+	[ADS1262_DR_19200_SPS]	= 48,
+	[ADS1262_DR_38400_SPS]	= 24,
+};
+
+static const u32 ads1262_mod_decimator_b[] = {
+	[ADS1262_DR_2_5_SPS]	= 5760,
+	[ADS1262_DR_5_SPS]	= 2880,
+	[ADS1262_DR_10_SPS]	= 1440,
+	[ADS1262_DR_16_6_SPS]	= 864,
+	[ADS1262_DR_20_SPS]	= 720,
+	[ADS1262_DR_50_SPS]	= 288,
+	[ADS1262_DR_60_SPS]	= 240,
+	[ADS1262_DR_100_SPS]	= 144,
+	[ADS1262_DR_400_SPS]	= 36,
+	[ADS1262_DR_1200_SPS]	= 12,
+	[ADS1262_DR_2400_SPS]	= 6,
+	[ADS1262_DR_4800_SPS]	= 3,
+	[ADS1262_DR_7200_SPS]	= 2,
+	[ADS1262_DR_14400_SPS]	= 1,
+	[ADS1262_DR_19200_SPS]	= 1,
+	[ADS1262_DR_38400_SPS]	= 1,
+};
+
+static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
+			    int val2)
+{
+	for (unsigned int i = 0; i < num_elements; i++) {
+		if (val == array[i][0] && val2 == array[i][1])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
 static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
 {
 	guard(mutex)(&st->xfer_lock);
@@ -375,9 +449,18 @@ static int ads1262_wait_for_conversion(struct ads1262 *st)
 static int ads1262_channel_enable(struct ads1262 *st,
 				  const struct iio_chan_spec *spec)
 {
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	int ret;
+
 	guard(mutex)(&st->xfer_lock);
 	guard(mutex)(&st->chan_lock);
 
+	ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
+				 ADS1262_MODE2_DR_MASK,
+				 FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate));
+	if (ret)
+		return ret;
+
 	return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
 				  ADS1262_INPMUX_MUXN_MASK |
 				  ADS1262_INPMUX_MUXP_MASK,
@@ -429,6 +512,8 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 			    struct iio_chan_spec const *chan, int *val,
 			    int *val2, long mask)
 {
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
 	__be32 raw;
 	int ret;
 
@@ -441,6 +526,83 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 
 		return IIO_VAL_INT;
 
+	case IIO_CHAN_INFO_SAMP_FREQ: {
+		guard(mutex)(&st->chan_lock);
+
+		*val = st->sampling_freq_table[chan_data->data_rate][0];
+		*val2 = st->sampling_freq_table[chan_data->data_rate][1];
+
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ads1262_read_avail(struct iio_dev *indio_dev,
+			      struct iio_chan_spec const *chan, const int **vals,
+			      int *type, int *length, long mask)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*type = IIO_VAL_INT_PLUS_MICRO;
+
+		switch (chan_data->filter) {
+		case ADS1262_FILTER_FIR:
+			*vals = (const int *)st->sampling_freq_fir;
+			*length = ARRAY_SIZE(st->sampling_freq_fir) * 2;
+			return IIO_AVAIL_LIST;
+		default:
+			return -EOPNOTSUPP;
+		}
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ads1262_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan, int val,
+			     int val2, long mask)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+	int ret;
+
+	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
+
+	guard(mutex)(&st->chan_lock);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		switch (chan_data->filter) {
+		case ADS1262_FILTER_FIR:
+			ret = ads1262_find_two(st->sampling_freq_fir,
+					       ARRAY_SIZE(st->sampling_freq_fir),
+					       val, val2);
+			if (ret < 0)
+				return ret;
+			break;
+		default:
+			return -EINVAL;
+		}
+
+		ret = ads1262_find_two(st->sampling_freq_table,
+				       ARRAY_SIZE(st->sampling_freq_table),
+				       val, val2);
+		if (ret < 0)
+			return ret;
+
+		chan_data->data_rate = ret;
+
+		return 0;
+
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -479,6 +641,8 @@ static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
 
 static const struct iio_info ads1262_iio_info = {
 	.read_raw = ads1262_read_raw,
+	.read_avail = ads1262_read_avail,
+	.write_raw = ads1262_write_raw,
 	.debugfs_reg_access = ads1262_debugfs_reg_access,
 	.fwnode_xlate = ads1262_fwnode_xlate,
 };
@@ -697,6 +861,49 @@ static const struct regmap_bus ads1262_regmap_bus = {
 	.max_raw_write = ADS1262_REGMAP_WRITE_SZ,
 };
 
+static void ads1262_populate_samp_freqs(struct ads1262 *st)
+{
+	u64 freq_Hz, freq_uHz;
+	u32 freq_rem;
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(st->sampling_freq_table); i++) {
+		freq_uHz = div_u64(mul_u32_u32(st->clk_rate, MICRO),
+				   ADS1262_MODULATOR_DIV *
+				   ads1262_mod_decimator_a[i] *
+				   ads1262_mod_decimator_b[i]);
+		freq_Hz = div_u64_rem(freq_uHz, MICRO, &freq_rem);
+
+		st->sampling_freq_table[i][0] = (int)freq_Hz;
+		st->sampling_freq_table[i][1] = (int)freq_rem;
+	}
+
+	for (unsigned int i = 0; i < ARRAY_SIZE(st->sampling_freq_fir); i++) {
+		/*
+		 * The FIR filter does not support the 16.6 SPS data rate so it
+		 * gets mapped to 20 SPS instead.
+		 */
+		switch (i) {
+		case ADS1262_DR_2_5_SPS ... ADS1262_DR_10_SPS:
+			st->sampling_freq_fir[i][0] = st->sampling_freq_table[i][0];
+			st->sampling_freq_fir[i][1] = st->sampling_freq_table[i][1];
+			break;
+		case ADS1262_DR_16_6_SPS:
+			st->sampling_freq_fir[i][0] = st->sampling_freq_table[ADS1262_DR_20_SPS][0];
+			st->sampling_freq_fir[i][1] = st->sampling_freq_table[ADS1262_DR_20_SPS][1];
+			break;
+		}
+	}
+}
+
+static int ads1262_populate_tables(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+
+	ads1262_populate_samp_freqs(st);
+
+	return 0;
+}
+
 static int ads1262_parse_channel_node(struct ads1262 *st,
 				      struct iio_chan_spec *spec,
 				      struct fwnode_handle *node)
@@ -763,8 +970,15 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 	if (!chan_specs)
 		return -ENOMEM;
 
+	st->num_channels = num_fw_channels + ADS1262_MON_CHANNEL_COUNT;
+	st->channels = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels),
+				    GFP_KERNEL);
+	if (!st->channels)
+		return -ENOMEM;
+
 	device_for_each_named_child_node_scoped(dev, node, "channel") {
 		struct iio_chan_spec *spec = &chan_specs[i];
+		struct ads1262_channel *chan = &st->channels[i];
 
 		ret = fwnode_property_read_u32(node, "reg", &reg);
 		if (ret)
@@ -772,6 +986,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 		if (reg >= ADS1262_MONITOR_ADDR_OFFSET)
 			return dev_err_probe(dev, -EINVAL, "%pfwP: reg out of range\n", node);
 
+		*chan = (struct ads1262_channel)ads1262_default_channel;
+
 		ret = ads1262_parse_channel_node(st, spec, node);
 		if (ret)
 			return ret;
@@ -786,7 +1002,9 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 			.storagebits = 32,
 			.endianness = IIO_BE,
 		};
-		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+					   BIT(IIO_CHAN_INFO_SAMP_FREQ);
+		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ);
 
 		i++;
 	}
@@ -795,6 +1013,7 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 	       sizeof(ads1262_monitor_chan_specs));
 
 	for (unsigned int mon = 0; mon < ADS1262_MON_CHANNEL_COUNT; mon++) {
+		st->channels[i] = (struct ads1262_channel)ads1262_default_channel;
 		chan_specs[i].scan_index = i;
 		i++;
 	}
@@ -890,6 +1109,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
 
 	indio_dev->name = ads1262_device_id_to_name[st->dev_id];
 
+	ret = ads1262_populate_tables(indio_dev);
+	if (ret)
+		return ret;
+
 	/*
 	 * REVISIT: This chip has software polling capabilities, which could be
 	 * used to stop depending on the DRDY signal.

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (3 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Allow each channel to select its voltage reference through the
"reference-sources" firmware property. Then, use the reference voltage
to calculate available scales. The PGA bypass handling is intentionally
left out until more information about on its usual applications is
gathered.

The ADS1262 allows single-ended supply configurations or bipolar supply
configurations. In single ended configurations both the analog and
digital rails share the same ground, i.e. AVSS = DGND = 0 V. In bipolar
supply configurations, AVSS can go below ground, e.g. AVSS = -2.5 V. If
AVSS is below ground, the ADC can achieve true bipolar measurements and
the external references can also have voltage levels below ground.

This is currently an issue because the regulator subsystem doesn't
support reading negative voltages. The ad4170-4 driver faces this
problem too and the same workaround is used in this case: assume every
regulator reports magnitudes (absolute values). If the chip has a
bipolar supply configuration, then assume positive references are above
ground (>= 0 V) and negative references are below ground (<= 0 V). This
is not a hardware constraint, but it is the most common wiring.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/ti-ads1262.c | 493 ++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 468 insertions(+), 25 deletions(-)

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 86ea9b6232d8..c87c0a66119a 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -8,6 +8,7 @@
 #include <linux/array_size.h>
 #include <linux/bitfield.h>
 #include <linux/bitops.h>
+#include <linux/bitmap.h>
 #include <linux/cleanup.h>
 #include <linux/clk.h>
 #include <linux/completion.h>
@@ -18,12 +19,14 @@
 #include <linux/interrupt.h>
 #include <linux/lockdep.h>
 #include <linux/math64.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
 #include <linux/spi/spi.h>
+#include <linux/string.h>
 #include <linux/types.h>
 #include <linux/units.h>
 
@@ -83,6 +86,7 @@
 #define ADS1262_MODE2_REG			0x05
 #define   ADS1262_MODE2_BYPASS_MASK		BIT(7)
 #define   ADS1262_MODE2_GAIN_MASK		GENMASK(6, 4)
+#define     ADS1262_GAIN_COUNT			6
 #define   ADS1262_MODE2_DR_MASK			GENMASK(3, 0)
 #define     ADS1262_DR_2_5_SPS			  0
 #define     ADS1262_DR_5_SPS			  1
@@ -123,7 +127,7 @@
 #define ADS1262_IDACMUX_REG			0x0D
 #define   ADS1262_IDACMUX_MUX2_MASK		GENMASK(7, 4)
 #define   ADS1262_IDACMUX_MUX1_MASK		GENMASK(3, 0)
-#define     ADS1262_IDACMUX_NO_CONN		0b1011
+#define     ADS1262_IDACMUX_NO_CONN		  11
 
 #define ADS1262_IDACMAG_REG			0x0E
 #define   ADS1262_IDACMAG_MAG2_MASK		GENMASK(7, 4)
@@ -131,7 +135,17 @@
 
 #define ADS1262_REFMUX_REG			0x0F
 #define   ADS1262_REFMUX_RMUXP_MASK		GENMASK(5, 3)
+#define     ADS1262_RMUXP_INTERNAL		  0
+#define     ADS1262_RMUXP_REFP1			  1
+#define     ADS1262_RMUXP_REFP2			  2
+#define     ADS1262_RMUXP_REFP3			  3
+#define     ADS1262_RMUXP_AVDD			  4
 #define   ADS1262_REFMUX_RMUXN_MASK		GENMASK(2, 0)
+#define     ADS1262_RMUXN_INTERNAL		  0
+#define     ADS1262_RMUXN_REFN1			  1
+#define     ADS1262_RMUXN_REFN2			  2
+#define     ADS1262_RMUXN_REFN3			  3
+#define     ADS1262_RMUXN_AVSS			  4
 
 #define ADS1262_TDACP_REG			0x10
 #define   ADS1262_TDACP_OUTP_MASK		BIT(7)
@@ -169,9 +183,13 @@
 
 #define ADS1262_NOMINAL_CLK_RATE		7372800
 #define ADS1262_MODULATOR_DIV			8
+#define ADS1262_INTERNAL_REFERENCE_uV		2500000
+#define ADS1262_TEMP_SLOPE_uV_C			420ULL
+#define ADS1262_TEMP_ZERO_C			111900ULL
 
 #define ADS1262_FW_CHANNEL_COUNT		16
 #define ADS1262_MON_CHANNEL_COUNT		4
+#define ADS1262_EXT_REF_COUNT			3
 #define ADS1262_REGMAP_WRITE_SZ			8
 #define ADS1262_MONITOR_ADDR_OFFSET		100
 
@@ -180,12 +198,21 @@
 struct ads1262_channel {
 	u8 data_rate;
 	u8 filter;
+	u8 gain;
+	u8 ref_p;
+	u8 ref_n;
+	bool ref_reversal;
+	bool is_resistance;
+	size_t num_scales;
+	int scales[ADS1262_GAIN_COUNT][2];
 };
 
 struct ads1262 {
 	struct spi_device *spi;
 	struct regmap *regmap;
 	struct gpio_desc *start_gpiod;
+	struct regulator *avdd_supply;
+	struct regulator *avss_supply;
 	size_t num_channels;
 	struct ads1262_channel *channels;
 	/* protects concurrent SPI transfers */
@@ -195,8 +222,12 @@ struct ads1262 {
 	struct completion drdy;
 	unsigned long clk_rate;
 	u8 dev_id;
+	bool bipolar_supply;
 	int sampling_freq_table[ADS1262_DR_COUNT][2];
 	int sampling_freq_fir[ADS1262_DR_16_6_SPS + 1][2];
+	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
+	int refp_uV[ADS1262_EXT_REF_COUNT];
+	int refn_uV[ADS1262_EXT_REF_COUNT];
 };
 
 static const char * const ads1262_device_id_to_name[] = {
@@ -217,8 +248,11 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_OFFSET) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -233,8 +267,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -249,8 +285,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 	{
 		.type = IIO_VOLTAGE,
@@ -266,8 +304,10 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 			.endianness = IIO_BE,
 		},
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
-		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ),
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						BIT(IIO_CHAN_INFO_SCALE),
 	},
 };
 
@@ -314,6 +354,24 @@ static const u32 ads1262_mod_decimator_b[] = {
 	[ADS1262_DR_38400_SPS]	= 1,
 };
 
+static const char * const ads1262_ref_sources_pos[] = {
+	[ADS1262_RMUXP_INTERNAL] = "internal-p",
+	[ADS1262_RMUXP_REFP1] = "refp1",
+	[ADS1262_RMUXP_REFP2] = "refp2",
+	[ADS1262_RMUXP_REFP3] = "refp3",
+	[ADS1262_RMUXP_AVDD] = "avdd",
+	NULL
+};
+
+static const char * const ads1262_ref_sources_neg[] = {
+	[ADS1262_RMUXN_INTERNAL] = "internal-n",
+	[ADS1262_RMUXN_REFN1] = "refn1",
+	[ADS1262_RMUXN_REFN2] = "refn2",
+	[ADS1262_RMUXN_REFN3] = "refn3",
+	[ADS1262_RMUXN_AVSS] = "avss",
+	NULL
+};
+
 static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
 			    int val2)
 {
@@ -325,6 +383,12 @@ static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
 	return -EINVAL;
 }
 
+static bool ads1262_reference_is_external(int ref_p, int ref_n)
+{
+	return in_range(ref_p, ADS1262_RMUXP_REFP1, ADS1262_EXT_REF_COUNT) &&
+	       in_range(ref_n, ADS1262_RMUXN_REFN1, ADS1262_EXT_REF_COUNT);
+}
+
 static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
 {
 	guard(mutex)(&st->xfer_lock);
@@ -455,17 +519,33 @@ static int ads1262_channel_enable(struct ads1262 *st,
 	guard(mutex)(&st->xfer_lock);
 	guard(mutex)(&st->chan_lock);
 
-	ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
-				 ADS1262_MODE2_DR_MASK,
-				 FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate));
+	ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+				 ADS1262_MODE0_REFREV_MASK,
+				 FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
 	if (ret)
 		return ret;
 
-	return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
-				  ADS1262_INPMUX_MUXN_MASK |
-				  ADS1262_INPMUX_MUXP_MASK,
-				  FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
-				  FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+	ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
+				 ADS1262_MODE2_DR_MASK |
+				 ADS1262_MODE2_GAIN_MASK,
+				 FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+				 FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+				 ADS1262_INPMUX_MUXN_MASK |
+				 ADS1262_INPMUX_MUXP_MASK,
+				 FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
+				 FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel));
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
+				  ADS1262_REFMUX_RMUXN_MASK |
+				  ADS1262_REFMUX_RMUXP_MASK,
+				  FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n) |
+				  FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p));
 }
 
 static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
@@ -514,6 +594,7 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 {
 	struct ads1262 *st = iio_priv(indio_dev);
 	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+	u64 scale, offset;
 	__be32 raw;
 	int ret;
 
@@ -526,6 +607,34 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 
 		return IIO_VAL_INT;
 
+	case IIO_CHAN_INFO_SCALE: {
+		guard(mutex)(&st->chan_lock);
+
+		*val = chan_data->scales[chan_data->gain][0];
+		*val2 = chan_data->scales[chan_data->gain][1];
+
+		return IIO_VAL_DECIMAL64_PICO;
+	}
+
+	case IIO_CHAN_INFO_OFFSET: {
+		guard(mutex)(&st->chan_lock);
+
+		scale = iio_val_s64_compose(chan_data->scales[chan_data->gain][0],
+					    chan_data->scales[chan_data->gain][1]);
+
+		switch (chan->type) {
+		case IIO_TEMP:
+			offset = -mul_u64_u64_div_u64(ADS1262_TEMP_ZERO_C,
+						      PICO * MILLIDEGREE_PER_DEGREE,
+						      scale * ADS1262_TEMP_SLOPE_uV_C);
+			iio_val_s64_decompose(offset, val, val2);
+
+			return IIO_VAL_INT_64;
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+
 	case IIO_CHAN_INFO_SAMP_FREQ: {
 		guard(mutex)(&st->chan_lock);
 
@@ -548,6 +657,12 @@ static int ads1262_read_avail(struct iio_dev *indio_dev,
 	struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
 
 	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		*type = IIO_VAL_DECIMAL64_PICO;
+		*vals = (const int *)chan_data->scales;
+		*length = chan_data->num_scales * 2;
+		return IIO_AVAIL_LIST;
+
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		*type = IIO_VAL_INT_PLUS_MICRO;
 
@@ -580,6 +695,16 @@ static int ads1262_write_raw(struct iio_dev *indio_dev,
 	guard(mutex)(&st->chan_lock);
 
 	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		ret = ads1262_find_two(chan_data->scales, chan_data->num_scales,
+				       val, val2);
+		if (ret < 0)
+			return ret;
+
+		chan_data->gain = ret;
+
+		return 0;
+
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		switch (chan_data->filter) {
 		case ADS1262_FILTER_FIR:
@@ -621,6 +746,17 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
 	return regmap_write(st->regmap, reg, writeval);
 }
 
+static int ads1262_write_raw_get_fmt(struct iio_dev *indio_dev,
+				     struct iio_chan_spec const *chan, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SCALE:
+		return IIO_VAL_DECIMAL64_PICO;
+	default:
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+}
+
 static int ads1262_fwnode_xlate(struct iio_dev *indio_dev,
 				const struct fwnode_reference_args *iiospec)
 {
@@ -643,6 +779,7 @@ static const struct iio_info ads1262_iio_info = {
 	.read_raw = ads1262_read_raw,
 	.read_avail = ads1262_read_avail,
 	.write_raw = ads1262_write_raw,
+	.write_raw_get_fmt = ads1262_write_raw_get_fmt,
 	.debugfs_reg_access = ads1262_debugfs_reg_access,
 	.fwnode_xlate = ads1262_fwnode_xlate,
 };
@@ -895,20 +1032,246 @@ static void ads1262_populate_samp_freqs(struct ads1262 *st)
 	}
 }
 
+static void ads1262_calculate_scales(int (*scales)[2], size_t num_scales,
+				     u32 full_scale, u64 mult,
+				     u32 resolution)
+{
+	unsigned int i;
+	s64 val;
+
+	/*
+	 * Each scale in the table corresponds to a PGA gain configuration,
+	 * which are given in powers of 2.
+	 */
+	for (i = 0; i < num_scales; i++) {
+		val = mul_u64_u64_shr(full_scale, mult, resolution - 1 + i);
+		iio_val_s64_decompose(val, &scales[i][0], &scales[i][1]);
+	}
+}
+
+static int ads1262_populate_scales_resistance(struct ads1262 *st,
+					      const struct iio_chan_spec *spec)
+{
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u32 full_scale;
+
+	if (!ads1262_reference_is_external(chan->ref_p, chan->ref_n))
+		return -EINVAL;
+
+	full_scale = st->rref_ohms[chan->ref_p - 1][chan->ref_n - 1];
+
+	chan->num_scales = ARRAY_SIZE(chan->scales);
+
+	ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+				 PICO, ADS1262_ADC1_RESOLUTION);
+
+	return 0;
+}
+
+static int ads1262_populate_scales_temp(struct ads1262 *st,
+					const struct iio_chan_spec *spec)
+{
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u32 full_scale;
+	u64 mult;
+
+	full_scale = ADS1262_INTERNAL_REFERENCE_uV;
+	mult = PICO * MILLIDEGREE_PER_DEGREE / ADS1262_TEMP_SLOPE_uV_C;
+
+	chan->num_scales = 1;
+
+	ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+				 mult, ADS1262_ADC1_RESOLUTION);
+
+	return 0;
+}
+
+static int ads1262_populate_scales_voltage(struct ads1262 *st,
+					   const struct iio_chan_spec *spec)
+{
+	struct device *dev = &st->spi->dev;
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	int refp_uV, refn_uV;
+	u32 full_scale_uV;
+	u64 mult;
+	int ret;
+
+	switch (chan->ref_p) {
+	case ADS1262_RMUXP_INTERNAL:
+		ret = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "channel@%u: failed to get avss voltage\n",
+					     spec->scan_index);
+
+		/* The internal reference negative is AVSS */
+		refp_uV = -ret + ADS1262_INTERNAL_REFERENCE_uV;
+		break;
+
+	case ADS1262_RMUXP_REFP1 ... ADS1262_RMUXP_REFP3:
+		refp_uV = st->refp_uV[chan->ref_p - 1];
+		break;
+
+	case ADS1262_RMUXP_AVDD:
+		ret = regulator_get_voltage(st->avdd_supply);
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "channel@%u: failed to get avdd voltage\n",
+					     spec->scan_index);
+		refp_uV = ret;
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	switch (chan->ref_n) {
+	case ADS1262_RMUXN_INTERNAL:
+	case ADS1262_RMUXN_AVSS:
+		ret = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "channel@%u: failed to get avss voltage\n",
+					     spec->scan_index);
+		refn_uV = -ret;
+		break;
+
+	case ADS1262_RMUXN_REFN1 ... ADS1262_RMUXN_REFN3:
+		refn_uV = st->refn_uV[chan->ref_n - 1];
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	full_scale_uV = abs(refp_uV - refn_uV);
+	if (full_scale_uV < 900000)
+		return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
+				     spec->scan_index);
+
+	if (spec->channel >= ADS1262_INPMUX_AVDD &&
+	    spec->channel <= ADS1262_INPMUX_DVDD) {
+		/* The power supply monitors are scaled down by a factor of 4 */
+		mult = 4;
+		chan->num_scales = 1;
+	} else {
+		mult = 1;
+		chan->num_scales = ARRAY_SIZE(chan->scales);
+	}
+
+	ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale_uV,
+				 NANO * mult, ADS1262_ADC1_RESOLUTION);
+
+	return 0;
+}
+
+static int ads1262_parse_references(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	unsigned int i, j;
+	char name[sizeof("ti,refpN-refnM-resistor-ohms")];
+	u32 ohms;
+	int ret;
+
+	for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+		scnprintf(name, sizeof(name), "refp%u", i);
+		ret = devm_regulator_get_enable_read_voltage(dev, name);
+		if (ret < 0 && ret != -ENODEV)
+			return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+					     name);
+
+		st->refp_uV[i - 1] = ret == -ENODEV ? 0 : ret;
+	}
+
+	for (i = ADS1262_RMUXN_REFN1; i <= ADS1262_RMUXN_REFN3; i++) {
+		scnprintf(name, sizeof(name), "refn%u", i);
+		ret = devm_regulator_get_enable_read_voltage(dev, name);
+		if (ret < 0 && ret != -ENODEV)
+			return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+					     name);
+
+		/*
+		 * REVISIT: Currently the regulator subsystem doesn't support
+		 * reading negative voltages. If we have a bipolar supply
+		 * configuration (AVSS < 0), then we are forced to assume that
+		 * negative references are either 0V (no regulator) or below
+		 * ground magnitudes.
+		 */
+		if (st->bipolar_supply)
+			st->refn_uV[i - 1] = ret == -ENODEV ? 0 : -ret;
+		else
+			st->refn_uV[i - 1] = ret == -ENODEV ? 0 : ret;
+	}
+
+	for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+		for (j = ADS1262_RMUXN_REFN1; j <= ADS1262_RMUXN_REFN3; j++) {
+			scnprintf(name, sizeof(name),
+				  "ti,refp%u-refn%u-resistor-ohms", i, j);
+
+			if (!device_property_present(dev, name))
+				continue;
+
+			ret = device_property_read_u32(dev, name, &ohms);
+			if (ret)
+				return dev_err_probe(dev, ret,
+						     "failed to read reference resistor: %s\n",
+						     name);
+			if (!ohms)
+				return dev_err_probe(dev, -EINVAL,
+						     "reference resistor can't be 0 ohms: %s\n",
+						     name);
+
+			st->rref_ohms[i - 1][j - 1] = ohms;
+		}
+	}
+
+	return 0;
+}
+
 static int ads1262_populate_tables(struct iio_dev *indio_dev)
 {
 	struct ads1262 *st = iio_priv(indio_dev);
+	const struct iio_chan_spec *spec;
+	int ret;
 
 	ads1262_populate_samp_freqs(st);
 
+	ret = ads1262_parse_references(st);
+	if (ret)
+		return ret;
+
+	for (unsigned int i = 0; i < st->num_channels; i++) {
+		spec = &indio_dev->channels[i];
+
+		switch (spec->type) {
+		case IIO_VOLTAGE:
+			ret = ads1262_populate_scales_voltage(st, spec);
+			if (ret)
+				return ret;
+			break;
+		case IIO_TEMP:
+			ret = ads1262_populate_scales_temp(st, spec);
+			if (ret)
+				return ret;
+			break;
+		case IIO_RESISTANCE:
+			ret = ads1262_populate_scales_resistance(st, spec);
+			if (ret)
+				return ret;
+			break;
+		default:
+			return -EOPNOTSUPP;
+		}
+	}
+
 	return 0;
 }
 
 static int ads1262_parse_channel_node(struct ads1262 *st,
 				      struct iio_chan_spec *spec,
+				      struct ads1262_channel *chan,
 				      struct fwnode_handle *node)
 {
 	struct device *dev = &st->spi->dev;
+	const char *sources[2];
+	char name[sizeof("ti,refpN-refnM-resistor-ohms")];
 	u32 pins[2];
 	int ret;
 
@@ -947,6 +1310,38 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
 	spec->channel = pins[0];
 	spec->channel2 = pins[1];
 
+	if (fwnode_property_present(node, "reference-sources")) {
+		ret = fwnode_property_read_string_array(node, "reference-sources",
+							sources, ARRAY_SIZE(sources));
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "%pfwP: failed to read reference-sources\n",
+					     node);
+		if (ret < 2)
+			return dev_err_probe(dev, -EINVAL, "%pfwP: missing reference-sources\n",
+					     node);
+
+		ret = match_string(ads1262_ref_sources_pos, -1, sources[0]);
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "%pfwP: invalid positive reference source\n",
+					     node);
+		chan->ref_p = ret;
+
+		ret = match_string(ads1262_ref_sources_neg, -1, sources[1]);
+		if (ret < 0)
+			return dev_err_probe(dev, ret, "%pfwP: invalid negative reference source\n",
+					     node);
+		chan->ref_n = ret;
+
+		if (ads1262_reference_is_external(chan->ref_p, chan->ref_n)) {
+			scnprintf(name, sizeof(name), "ti,refp%u-refn%u-resistor-ohms",
+				  chan->ref_p, chan->ref_n);
+			if (device_property_present(dev, name))
+				chan->is_resistance = true;
+		}
+	}
+
+	chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
+
 	return 0;
 }
 
@@ -988,11 +1383,11 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 
 		*chan = (struct ads1262_channel)ads1262_default_channel;
 
-		ret = ads1262_parse_channel_node(st, spec, node);
+		ret = ads1262_parse_channel_node(st, spec, chan, node);
 		if (ret)
 			return ret;
 
-		spec->type = IIO_VOLTAGE;
+		spec->type = chan->is_resistance ? IIO_RESISTANCE : IIO_VOLTAGE;
 		spec->indexed = true;
 		spec->scan_index = i;
 		spec->address = reg;
@@ -1003,8 +1398,10 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 			.endianness = IIO_BE,
 		};
 		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
-					   BIT(IIO_CHAN_INFO_SAMP_FREQ);
-		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ);
+					   BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+					   BIT(IIO_CHAN_INFO_SCALE);
+		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+						     BIT(IIO_CHAN_INFO_SCALE);
 
 		i++;
 	}
@@ -1027,6 +1424,14 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 	return 0;
 }
 
+static void ads1262_regulator_disable(void *data)
+{
+	struct regulator *supply = data;
+
+	if (supply)
+		regulator_disable(supply);
+}
+
 static int ads1262_supply_setup(struct ads1262 *st)
 {
 	struct device *dev = &st->spi->dev;
@@ -1036,14 +1441,52 @@ static int ads1262_supply_setup(struct ads1262 *st)
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
 
-	ret = devm_regulator_get_enable(dev, "avdd");
-	if (ret < 0)
+	st->avdd_supply = devm_regulator_get(dev, "avdd");
+	ret = PTR_ERR_OR_ZERO(st->avdd_supply);
+	if (ret)
 		return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
 
-	ret = devm_regulator_get_enable_optional(dev, "avss");
-	if (ret < 0 && ret != -ENODEV)
+	ret = regulator_enable(st->avdd_supply);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, ads1262_regulator_disable,
+				       st->avdd_supply);
+	if (ret)
+		return ret;
+
+	/*
+	 * REVISIT: The AVSS supply has a minimum of -2.5V and maximum of 0V.
+	 * Currently the regulator subsystem doesn't support negative voltages,
+	 * so we assume the value returned here is actually the magnitude
+	 * (absolute value).
+	 *
+	 * This limitation forces us to assume that, if we have a bipolar supply
+	 * (AVSS < 0V), all negative references are below ground (REFN <= 0V)
+	 * and positive references are above ground (REFP >= 0V), as this is the
+	 * most common configuration.
+	 */
+	st->avss_supply = devm_regulator_get_optional(dev, "avss");
+	ret = PTR_ERR_OR_ZERO(st->avss_supply);
+	if (ret && ret != -ENODEV)
 		return dev_err_probe(dev, ret, "failed to get avss regulator\n");
 
+	if (ret != -ENODEV) {
+		ret = regulator_enable(st->avss_supply);
+		if (ret)
+			return ret;
+
+		ret = devm_add_action_or_reset(dev, ads1262_regulator_disable,
+					       st->avss_supply);
+		if (ret)
+			return ret;
+
+		st->bipolar_supply = true;
+	} else  {
+		st->avss_supply = NULL;
+		st->bipolar_supply = false;
+	}
+
 	fsleep(ADS1262_POWER_TRANS_USECS);
 
 	return 0;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (4 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Enable per-channel input chopping via the "input-chopping" property.
As the device withholds the first conversion in chop mode, single reads
are taken in CONTINUOUS run mode with a brief start/stop pulse, as
recommended by the datasheet (Section 9.4.1.2).

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/ti-ads1262.c | 25 +++++++++++++++++++++----
 1 file changed, 21 insertions(+), 4 deletions(-)

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index c87c0a66119a..4d38da092001 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -203,6 +203,7 @@ struct ads1262_channel {
 	u8 ref_n;
 	bool ref_reversal;
 	bool is_resistance;
+	bool input_chop;
 	size_t num_scales;
 	int scales[ADS1262_GAIN_COUNT][2];
 };
@@ -464,7 +465,7 @@ static int ads1262_dev_stop(struct ads1262 *st)
 	return ret;
 }
 
-static int ads1262_dev_start_one(struct ads1262 *st)
+static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode)
 {
 	int ret;
 
@@ -472,7 +473,7 @@ static int ads1262_dev_start_one(struct ads1262 *st)
 	if (ret)
 		return ret;
 
-	if (st->start_gpiod) {
+	if (runmode == ADS1262_RUNMODE_CONTINUOUS || st->start_gpiod) {
 		/*
 		 * The START pulse timing requirement is 4 clock cycles, at the
 		 * minimum clock rate this is 4 microseconds.
@@ -520,7 +521,9 @@ static int ads1262_channel_enable(struct ads1262 *st,
 	guard(mutex)(&st->chan_lock);
 
 	ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+				 ADS1262_MODE0_INPUT_CHOP_MASK |
 				 ADS1262_MODE0_REFREV_MASK,
+				 FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
 				 FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
 	if (ret)
 		return ret;
@@ -561,13 +564,26 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
 				const struct iio_chan_spec *spec, __be32 *val)
 {
 	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u8 runmode;
 	int ret;
 
 	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
 	if (IIO_DEV_ACQUIRE_FAILED(claim))
 		return -EBUSY;
 
-	ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
+	/*
+	 * When a channel has chop mode or IDAC rotation mode, the first
+	 * conversion is always withheld so the datasheet suggests using the
+	 * CONTINUOUS mode and briefly starting and stopping conversions to
+	 * achieve the same effect (Section 9.4.1.2).
+	 */
+	if (chan->input_chop)
+		runmode = ADS1262_RUNMODE_CONTINUOUS;
+	else
+		runmode = ADS1262_RUNMODE_PULSE;
+
+	ret = ads1262_set_runmode(st, runmode);
 	if (ret)
 		return ret;
 
@@ -577,7 +593,7 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
 
 	reinit_completion(&st->drdy);
 
-	ret = ads1262_dev_start_one(st);
+	ret = ads1262_dev_start_one(st, runmode);
 	if (ret)
 		return ret;
 
@@ -1340,6 +1356,7 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
 		}
 	}
 
+	chan->input_chop = fwnode_property_read_bool(node, "input-chopping");
 	chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
 
 	return 0;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (5 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:57   ` sashiko-bot
  2026-08-28  6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
                   ` (2 subsequent siblings)
  9 siblings, 1 reply; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Support the two IDAC excitation current sources. Each channel can route
its IDAC1/IDAC2 outputs to a pin via the "excitation-channels" property
and select a magnitude via "excitation-current-nanoamp".

Reviewed-by: David Lechner <dlechner@baylibre.com>
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/ti-ads1262.c | 87 ++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 84 insertions(+), 3 deletions(-)

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 4d38da092001..04566d00d7cb 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -198,12 +198,15 @@
 struct ads1262_channel {
 	u8 data_rate;
 	u8 filter;
+	u8 idac_mux[2];
+	u8 idac_mag[2];
 	u8 gain;
 	u8 ref_p;
 	u8 ref_n;
 	bool ref_reversal;
 	bool is_resistance;
 	bool input_chop;
+	bool idac_chop;
 	size_t num_scales;
 	int scales[ADS1262_GAIN_COUNT][2];
 };
@@ -315,6 +318,7 @@ static const struct iio_chan_spec ads1262_monitor_chan_specs[] = {
 static const struct ads1262_channel ads1262_default_channel = {
 	.data_rate = ADS1262_DR_20_SPS,
 	.filter = ADS1262_FILTER_FIR,
+	.idac_mux = { ADS1262_IDACMUX_NO_CONN, ADS1262_IDACMUX_NO_CONN },
 };
 
 static const u32 ads1262_mod_decimator_a[] = {
@@ -373,6 +377,21 @@ static const char * const ads1262_ref_sources_neg[] = {
 	NULL
 };
 
+static const u32 ads1262_idac_mags_nA[] = {
+	0, 50000, 100000, 250000, 500000, 750000, 1000000, 1500000,	/* 0..7 */
+	2000000, 2500000, 3000000					/* 8..10 */
+};
+
+static int ads1262_find_one(const u32 *array, size_t num_elements, u32 val)
+{
+	for (unsigned int i = 0; i < num_elements; i++) {
+		if (val == array[i])
+			return i;
+	}
+
+	return -EINVAL;
+}
+
 static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
 			    int val2)
 {
@@ -522,8 +541,10 @@ static int ads1262_channel_enable(struct ads1262 *st,
 
 	ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
 				 ADS1262_MODE0_INPUT_CHOP_MASK |
+				 ADS1262_MODE0_IDAC_CHOP_MASK |
 				 ADS1262_MODE0_REFREV_MASK,
 				 FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+				 FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
 				 FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal));
 	if (ret)
 		return ret;
@@ -544,6 +565,22 @@ static int ads1262_channel_enable(struct ads1262 *st,
 	if (ret)
 		return ret;
 
+	ret = regmap_update_bits(st->regmap, ADS1262_IDACMUX_REG,
+				 ADS1262_IDACMUX_MUX1_MASK |
+				 ADS1262_IDACMUX_MUX2_MASK,
+				 FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+				 FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]));
+	if (ret)
+		return ret;
+
+	ret = regmap_update_bits(st->regmap, ADS1262_IDACMAG_REG,
+				 ADS1262_IDACMAG_MAG1_MASK |
+				 ADS1262_IDACMAG_MAG2_MASK,
+				 FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
+				 FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]));
+	if (ret)
+		return ret;
+
 	return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
 				  ADS1262_REFMUX_RMUXN_MASK |
 				  ADS1262_REFMUX_RMUXP_MASK,
@@ -578,7 +615,7 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
 	 * CONTINUOUS mode and briefly starting and stopping conversions to
 	 * achieve the same effect (Section 9.4.1.2).
 	 */
-	if (chan->input_chop)
+	if (chan->input_chop || chan->idac_chop)
 		runmode = ADS1262_RUNMODE_CONTINUOUS;
 	else
 		runmode = ADS1262_RUNMODE_PULSE;
@@ -1288,8 +1325,8 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
 	struct device *dev = &st->spi->dev;
 	const char *sources[2];
 	char name[sizeof("ti,refpN-refnM-resistor-ohms")];
-	u32 pins[2];
-	int ret;
+	u32 pins[2], mags[2];
+	int count, ret;
 
 	if (fwnode_property_present(node, "single-channel")) {
 		ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
@@ -1356,7 +1393,51 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
 		}
 	}
 
+	if (fwnode_property_present(node, "excitation-channels")) {
+		count = fwnode_property_count_u32(node, "excitation-channels");
+		if (count < 0)
+			return dev_err_probe(dev, count,
+					     "%pfwP: failed to count excitation-channels\n", node);
+
+		pins[0] = ADS1262_IDACMUX_NO_CONN;
+		pins[1] = ADS1262_IDACMUX_NO_CONN;
+		ret = fwnode_property_read_u32_array(node, "excitation-channels",
+						     pins, min(count, ARRAY_SIZE(pins)));
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "%pfwP: failed to read excitation-channels\n", node);
+		if (pins[0] > ADS1262_IDACMUX_NO_CONN || pins[1] > ADS1262_IDACMUX_NO_CONN)
+			return dev_err_probe(dev, -EINVAL,
+					     "%pfwP: excitation-channels not in range\n", node);
+		chan->idac_mux[0] = pins[0];
+		chan->idac_mux[1] = pins[1];
+
+		mags[0] = 0;
+		mags[1] = 0;
+		ret = fwnode_property_read_u32_array(node, "excitation-current-nanoamp",
+						     mags, min(count, ARRAY_SIZE(mags)));
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "%pfwP: failed to read excitation-current-nanoamp\n",
+					     node);
+
+		ret = ads1262_find_one(ads1262_idac_mags_nA,
+				       ARRAY_SIZE(ads1262_idac_mags_nA), mags[0]);
+		if (ret < 0)
+			return dev_err_probe(dev, ret,
+					     "%pfwP: invalid excitation-current-nanoamp\n", node);
+		chan->idac_mag[0] = ret;
+
+		ret = ads1262_find_one(ads1262_idac_mags_nA,
+				       ARRAY_SIZE(ads1262_idac_mags_nA), mags[1]);
+		if (ret < 0)
+			return dev_err_probe(dev, ret,
+					     "%pfwP: invalid excitation-current-nanoamp\n", node);
+		chan->idac_mag[1] = ret;
+	}
+
 	chan->input_chop = fwnode_property_read_bool(node, "input-chopping");
+	chan->idac_chop = fwnode_property_read_bool(node, "excitation-current-chopping");
 	chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
 
 	return 0;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (6 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:57   ` sashiko-bot
  2026-08-28  6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
  2026-08-28  6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
  9 siblings, 1 reply; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Add triggered buffer support and a data-ready (DRDY) hardware trigger.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/Kconfig      |   2 +
 drivers/iio/adc/ti-ads1262.c | 305 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 307 insertions(+)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index e51ce42bce94..2b60ade01e90 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1864,6 +1864,8 @@ config TI_ADS1262
 	tristate "Texas Instruments ADS1262"
 	depends on SPI
 	select REGMAP
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
 	help
 	  If you say yes here you get support for Texas Instruments ADS1262 and
 	  ADS1263 ADC chips.
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 04566d00d7cb..3f6d34a08b29 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -33,6 +33,9 @@
 #include <asm/byteorder.h>
 
 #include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
 
 #define ADS1262_OPCODE_NOP			0x00
 #define ADS1262_OPCODE_RESET			0x06
@@ -191,6 +194,7 @@
 #define ADS1262_MON_CHANNEL_COUNT		4
 #define ADS1262_EXT_REF_COUNT			3
 #define ADS1262_REGMAP_WRITE_SZ			8
+#define ADS1262_SPI_XFER_SZ			13
 #define ADS1262_MONITOR_ADDR_OFFSET		100
 
 #define ADS1262_ADC1_RESOLUTION			32
@@ -214,6 +218,7 @@ struct ads1262_channel {
 struct ads1262 {
 	struct spi_device *spi;
 	struct regmap *regmap;
+	struct iio_trigger *trig;
 	struct gpio_desc *start_gpiod;
 	struct regulator *avdd_supply;
 	struct regulator *avss_supply;
@@ -224,6 +229,8 @@ struct ads1262 {
 	/* protects channel state */
 	struct mutex chan_lock;
 	struct completion drdy;
+	struct spi_message msg;
+	struct spi_transfer xfer;
 	unsigned long clk_rate;
 	u8 dev_id;
 	bool bipolar_supply;
@@ -232,6 +239,11 @@ struct ads1262 {
 	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
 	int refp_uV[ADS1262_EXT_REF_COUNT];
 	int refn_uV[ADS1262_EXT_REF_COUNT];
+	IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
+				   ADS1262_FW_CHANNEL_COUNT +
+				   ADS1262_MON_CHANNEL_COUNT);
+	u8 tx[ADS1262_SPI_XFER_SZ] __aligned(IIO_DMA_MINALIGN);
+	u8 rx[ADS1262_SPI_XFER_SZ];
 };
 
 static const char * const ads1262_device_id_to_name[] = {
@@ -837,10 +849,284 @@ static const struct iio_info ads1262_iio_info = {
 	.fwnode_xlate = ads1262_fwnode_xlate,
 };
 
+static int ads1262_buffer_postenable_mult(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+
+	/*
+	 * When multiple channels are selected, we use a single transfer to both
+	 * enable channels, start and then read conversions with a full-duplex
+	 * optimized method. The transfer buffer holds up to four contiguous
+	 * commands: two register write commands, and start and stop commands if
+	 * no START GPIO is provided.
+	 *
+	 * The buffer is arranged as follows:
+	 *
+	 *     byte 0-1: write protocol header
+	 *     byte 2-5: MODE0, MODE1, MODE2, INPMUX register data
+	 *     byte 6-7: write protocol header
+	 *     byte 8-10: IDACMUX, IDACMAG, REFMUX register data
+	 *     byte 11: START1 command
+	 *     byte 12: STOP1 command
+	 */
+	if (st->start_gpiod)
+		st->xfer.len = 11;
+	else
+		st->xfer.len = 13;
+
+	static_assert(13 <= ADS1262_SPI_XFER_SZ);
+
+	return spi_optimize_message(st->spi, &st->msg);
+}
+
+static int ads1262_buffer_postenable_one(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	unsigned long i;
+	int ret;
+
+	i = find_first_bit(indio_dev->active_scan_mask,
+			   iio_get_masklength(indio_dev));
+	ret = ads1262_channel_enable(st, &indio_dev->channels[i]);
+	if (ret)
+		return ret;
+
+	ret = ads1262_set_runmode(st, ADS1262_RUNMODE_CONTINUOUS);
+	if (ret)
+		return ret;
+
+	static_assert(5 <= ADS1262_SPI_XFER_SZ);
+
+	st->xfer.len = 5;
+	memset(st->tx, 0, st->xfer.len);
+	/*
+	 * When only one channel is selected, we can't really avoid concurrent
+	 * device activity from happening between the DRDY signal and data
+	 * retrieval, thus we read by command. The transfer buffer holds the
+	 * command (RDATA1) plus the 4 conversion bytes (5 bytes total).
+	 */
+	st->tx[0] = ADS1262_OPCODE_RDATA1;
+
+	ret = spi_optimize_message(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	ret = ads1262_dev_start(st);
+	if (ret) {
+		spi_unoptimize_message(&st->msg);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int ads1262_buffer_postenable(struct iio_dev *indio_dev)
+{
+	int ret;
+
+	if (iio_validate_scan_mask_onehot(indio_dev,
+					  indio_dev->active_scan_mask))
+		ret = ads1262_buffer_postenable_one(indio_dev);
+	else
+		ret = ads1262_buffer_postenable_mult(indio_dev);
+
+	return ret;
+}
+
+static int ads1262_buffer_predisable(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+
+	if (iio_validate_scan_mask_onehot(indio_dev,
+					  indio_dev->active_scan_mask)) {
+		ads1262_dev_stop(st);
+	} else {
+		regcache_drop_region(st->regmap, ADS1262_MODE0_REG,
+				     ADS1262_INPMUX_REG);
+		regcache_drop_region(st->regmap, ADS1262_IDACMUX_REG,
+				     ADS1262_REFMUX_REG);
+	}
+
+	spi_unoptimize_message(&st->msg);
+
+	return 0;
+}
+
+static bool ads1262_validate_scan_mask(struct iio_dev *indio_dev,
+				       const unsigned long *scan_mask)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+
+	if (st->trig && indio_dev->trig == st->trig)
+		return iio_validate_scan_mask_onehot(indio_dev, scan_mask);
+
+	return true;
+}
+
+static const struct iio_buffer_setup_ops ads1262_buffer_ops = {
+	.postenable = ads1262_buffer_postenable,
+	.predisable = ads1262_buffer_predisable,
+	.validate_scan_mask = ads1262_validate_scan_mask,
+};
+
+static void ads1262_channel_prep_tx(struct ads1262 *st,
+				    const struct iio_chan_spec *spec)
+{
+	struct ads1262_channel *chan = &st->channels[spec->scan_index];
+	u8 runmode;
+
+	guard(mutex)(&st->chan_lock);
+
+	/*
+	 * Input chopping and IDAC rotation modes require the continuous
+	 * conversion mode.
+	 *
+	 * This condition only matters when we have an START GPIO, in which case
+	 * the pulse mode is preferred for its predictability: one conversion
+	 * per rising edge. Briefly pulsing the START GPIO (4 uS) should have
+	 * the same effect almost every time, unless the pulse lasts more than
+	 * ~208 uS, which should be rare even if the task is preempted.
+	 *
+	 * If we rely solely on conversion control commands, both modes are
+	 * equivalent because START1 and STOP1 commands are send contiguously on
+	 * the same transfer.
+	 */
+	if (chan->input_chop || chan->idac_chop)
+		runmode = ADS1262_RUNMODE_CONTINUOUS;
+	else
+		runmode = ADS1262_RUNMODE_PULSE;
+
+	st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
+	st->tx[1] = ADS1262_INPMUX_REG - ADS1262_MODE0_REG;
+	st->tx[2] = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+		    FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
+		    FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode) |
+		    FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
+	st->tx[3] = FIELD_PREP(ADS1262_MODE1_FILTER_MASK, chan->filter);
+	st->tx[4] = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+		    FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
+	st->tx[5] = FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel) |
+		    FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2);
+
+	st->tx[6] = ADS1262_IDACMUX_REG | ADS1262_OPCODE_WREG;
+	st->tx[7] = ADS1262_REFMUX_REG - ADS1262_IDACMUX_REG;
+	st->tx[8] = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+		    FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]);
+	st->tx[9] = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
+		    FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
+	st->tx[10] = FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p) |
+		     FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n);
+
+	/*
+	 * If we have an START GPIO, the transfer length is 11 so these last two
+	 * bytes are ignored.
+	 */
+	st->tx[11] = ADS1262_OPCODE_START1;
+	st->tx[12] = ADS1262_OPCODE_STOP1;
+}
+
+static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	unsigned int chan;
+	int i = -1;
+	int ret;
+
+	/*
+	 * This routine enables and reads channels in a full-duplex fashion.
+	 *
+	 * When a channel is enabled, the previous conversion is clocked out of
+	 * the shift data register on the same transfer (Section 9.4.7.1). This
+	 * allows for low latency software sequencing but forbids any
+	 * communication with the chip in-between or data corruption may occur,
+	 * hence the need to take the xfer_lock for the whole operation.
+	 */
+	guard(mutex)(&st->xfer_lock);
+
+	iio_for_each_active_channel(indio_dev, chan) {
+		ads1262_channel_prep_tx(st, &indio_dev->channels[chan]);
+
+		reinit_completion(&st->drdy);
+
+		ret = spi_sync(st->spi, &st->msg);
+		if (ret)
+			return ret;
+
+		if (st->start_gpiod) {
+			gpiod_set_value_cansleep(st->start_gpiod, 1);
+			fsleep(4);
+			gpiod_set_value_cansleep(st->start_gpiod, 0);
+		}
+
+		if (i > -1)
+			memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));
+		i++;
+
+		ret = ads1262_wait_for_conversion(st);
+		if (ret)
+			return ret;
+	}
+
+	memset(st->tx, 0, st->xfer.len);
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));
+
+	return 0;
+}
+
+static int ads1262_fill_buffer_one(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	int ret;
+
+	guard(mutex)(&st->xfer_lock);
+
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	/* In command mode the conversion data is found at offset 1 */
+	memcpy(st->scan_buffer, &st->rx[1], sizeof(*st->scan_buffer));
+
+	return 0;
+}
+
+static irqreturn_t ads1262_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ads1262 *st = iio_priv(indio_dev);
+	s64 ts = pf->timestamp;
+	unsigned int weight;
+	int ret;
+
+	weight = bitmap_weight(indio_dev->active_scan_mask,
+			       iio_get_masklength(indio_dev));
+
+	if (weight == 1)
+		ret = ads1262_fill_buffer_one(indio_dev);
+	else
+		ret = ads1262_fill_buffer_mult(indio_dev);
+	if (ret)
+		goto out_notify_done;
+
+	iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer,
+				    sizeof(st->scan_buffer), ts);
+
+out_notify_done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
 static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
 {
 	struct ads1262 *st = dev_id;
 
+	iio_trigger_poll(st->trig);
 	complete(&st->drdy);
 
 	return IRQ_HANDLED;
@@ -1609,6 +1895,9 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	st = iio_priv(indio_dev);
 	st->spi = spi;
 	init_completion(&st->drdy);
+	st->xfer.tx_buf = st->tx;
+	st->xfer.rx_buf = st->rx;
+	spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
 
 	ret = devm_mutex_init(dev, &st->chan_lock);
 	if (ret)
@@ -1654,6 +1943,22 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      ads1262_trigger_handler,
+					      &ads1262_buffer_ops);
+	if (ret)
+		return ret;
+
+	st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", indio_dev->name,
+					  iio_device_id(indio_dev));
+	if (!st->trig)
+		return -ENOMEM;
+	iio_trigger_set_drvdata(st->trig, st);
+	ret = devm_iio_trigger_register(dev, st->trig);
+	if (ret)
+		return ret;
+
 	/*
 	 * REVISIT: This chip has software polling capabilities, which could be
 	 * used to stop depending on the DRDY signal.

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (7 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  6:53   ` sashiko-bot
  2026-08-28  6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
  9 siblings, 1 reply; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Register the "refout" and "vbias" regulators to be able to use them as
common mode supplies.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/Kconfig      |   1 +
 drivers/iio/adc/ti-ads1262.c | 119 +++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 120 insertions(+)

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 2b60ade01e90..4548fec1f86f 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1864,6 +1864,7 @@ config TI_ADS1262
 	tristate "Texas Instruments ADS1262"
 	depends on SPI
 	select REGMAP
+	select REGULATOR
 	select IIO_BUFFER
 	select IIO_TRIGGERED_BUFFER
 	help
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 3f6d34a08b29..4784142f35c9 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -25,6 +25,7 @@
 #include <linux/property.h>
 #include <linux/regmap.h>
 #include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
 #include <linux/spi/spi.h>
 #include <linux/string.h>
 #include <linux/types.h>
@@ -1132,6 +1133,120 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+static int ads1262_vbias_enable(struct regulator_dev *rdev)
+{
+	struct ads1262 *st = rdev_get_drvdata(rdev);
+
+	guard(mutex)(&st->xfer_lock);
+
+	return regmap_set_bits(st->regmap, ADS1262_POWER_REG,
+			       ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_vbias_disable(struct regulator_dev *rdev)
+{
+	struct ads1262 *st = rdev_get_drvdata(rdev);
+
+	guard(mutex)(&st->xfer_lock);
+
+	return regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
+				 ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_vbias_is_enabled(struct regulator_dev *rdev)
+{
+	struct ads1262 *st = rdev_get_drvdata(rdev);
+
+	guard(mutex)(&st->xfer_lock);
+
+	return regmap_test_bits(st->regmap, ADS1262_POWER_REG,
+				ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_vbias_get_voltage(struct regulator_dev *rdev)
+{
+	struct ads1262 *st = rdev_get_drvdata(rdev);
+	int avdd_uV, avss_uV;
+
+	avdd_uV = regulator_get_voltage(st->avdd_supply);
+	if (avdd_uV < 0)
+		return avdd_uV;
+
+	avss_uV = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
+	if (avss_uV < 0)
+		return avss_uV;
+
+	return DIV_ROUND_CLOSEST(avdd_uV - avss_uV, 2);
+}
+
+static const struct regulator_ops ads1262_vbias_regulator_ops = {
+	.enable = ads1262_vbias_enable,
+	.disable = ads1262_vbias_disable,
+	.is_enabled = ads1262_vbias_is_enabled,
+	.get_voltage = ads1262_vbias_get_voltage,
+};
+
+static int ads1262_refout_is_enabled(struct regulator_dev *rdev)
+{
+	struct ads1262 *st = rdev_get_drvdata(rdev);
+
+	guard(mutex)(&st->xfer_lock);
+
+	return regmap_test_bits(st->regmap, ADS1262_POWER_REG,
+				ADS1262_POWER_INTREF_MASK);
+}
+
+static const struct regulator_ops ads1262_refout_regulator_ops = {
+	.is_enabled = ads1262_refout_is_enabled,
+};
+
+static const struct regulator_desc ads1262_vbias_regulator_desc = {
+	.name = "vbias",
+	.of_match = "vbias",
+	.regulators_node = "regulators",
+	.supply_name = "avdd",
+	.ops = &ads1262_vbias_regulator_ops,
+	.type = REGULATOR_VOLTAGE,
+	.owner = THIS_MODULE,
+};
+
+static const struct regulator_desc ads1262_refout_regulator_desc = {
+	.name = "refout",
+	.of_match = "refout",
+	.regulators_node = "regulators",
+	.supply_name = "avdd",
+	.n_voltages = 1,
+	.fixed_uV = 2500000,
+	.ops = &ads1262_refout_regulator_ops,
+	.type = REGULATOR_VOLTAGE,
+	.owner = THIS_MODULE,
+};
+
+static int ads1262_register_regulators(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct regulator_config config = {
+		.dev = dev,
+		.driver_data = st,
+	};
+	struct regulator_dev *rdev;
+
+	struct fwnode_handle *reg_node __free(fwnode_handle) =
+		device_get_named_child_node(dev, "regulators");
+	if (!reg_node)
+		return 0;
+
+	rdev = devm_regulator_register(dev, &ads1262_refout_regulator_desc,
+				       &config);
+	if (IS_ERR(rdev))
+		return PTR_ERR(rdev);
+
+	rdev = devm_regulator_register(dev, &ads1262_vbias_regulator_desc,
+				       &config);
+
+	return PTR_ERR_OR_ZERO(rdev);
+}
+
 static int ads1262_dev_configure(struct ads1262 *st)
 {
 	struct device *dev = &st->spi->dev;
@@ -1939,6 +2054,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
 
 	indio_dev->name = ads1262_device_id_to_name[st->dev_id];
 
+	ret = ads1262_register_regulators(st);
+	if (ret)
+		return ret;
+
 	ret = ads1262_populate_tables(indio_dev);
 	if (ret)
 		return ret;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies
  2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (8 preceding siblings ...)
  2026-08-28  6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
@ 2026-08-28  6:38 ` Kurt Borja
  2026-08-28  7:03   ` sashiko-bot
  9 siblings, 1 reply; 21+ messages in thread
From: Kurt Borja @ 2026-08-28  6:38 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, David Lechner
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel

Enable common mode regulators. The usual configuration is to have our
own 'vbias' regulator connected internally as common mode voltage on the
AINCOM pin.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 drivers/iio/adc/ti-ads1262.c | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 4784142f35c9..c7ad1cf1a15d 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -240,6 +240,7 @@ struct ads1262 {
 	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
 	int refp_uV[ADS1262_EXT_REF_COUNT];
 	int refn_uV[ADS1262_EXT_REF_COUNT];
+	int common_mode_uV[ADS1262_INPMUX_AINCOM + 1];
 	IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
 				   ADS1262_FW_CHANNEL_COUNT +
 				   ADS1262_MON_CHANNEL_COUNT);
@@ -695,6 +696,13 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 						      scale * ADS1262_TEMP_SLOPE_uV_C);
 			iio_val_s64_decompose(offset, val, val2);
 
+			return IIO_VAL_INT_64;
+
+		case IIO_VOLTAGE:
+			offset = mul_u64_u64_div_u64(st->common_mode_uV[chan->channel2],
+						     NANO, scale);
+			iio_val_s64_decompose(offset, val, val2);
+
 			return IIO_VAL_INT_64;
 		default:
 			return -EOPNOTSUPP;
@@ -1133,6 +1141,28 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
 	return IRQ_HANDLED;
 }
 
+static int ads1262_parse_common_mode_supplies(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	char name[sizeof("aincom")];
+	int ret;
+
+	for (unsigned int i = 0; i <= ADS1262_INPMUX_AINCOM; i++) {
+		if (i < ADS1262_INPMUX_AINCOM)
+			scnprintf(name, sizeof(name), "ain%u", i);
+		else
+			scnprintf(name, sizeof(name), "aincom");
+
+		ret = devm_regulator_get_enable_read_voltage(dev, name);
+		if (ret < 0 && ret != -ENODEV)
+			return dev_err_probe(dev, ret,
+					     "failed to get common mode supply: %s\n", name);
+		st->common_mode_uV[i] = ret != -ENODEV ? ret : 0;
+	}
+
+	return 0;
+}
+
 static int ads1262_vbias_enable(struct regulator_dev *rdev)
 {
 	struct ads1262 *st = rdev_get_drvdata(rdev);
@@ -1902,6 +1932,9 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
 		spec->info_mask_separate_available = BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 						     BIT(IIO_CHAN_INFO_SCALE);
 
+		if (!chan->is_resistance && !spec->differential)
+			spec->info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
+
 		i++;
 	}
 
@@ -2058,6 +2091,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	ret = ads1262_parse_common_mode_supplies(st);
+	if (ret)
+		return ret;
+
 	ret = ads1262_populate_tables(indio_dev);
 	if (ret)
 		return ret;

-- 
2.55.0


^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
  2026-08-28  6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-08-28  6:45   ` sashiko-bot
  2026-08-28 16:39   ` Conor Dooley
  1 sibling, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  6:45 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] The conditional blocks restricting `reference-sources` use an incorrect regex `^channel@[0-9]+$` which fails to match valid hexadecimal unit addresses like `channel@a`, silently bypassing schema constraints for higher channels.
- [Medium] The cell-count property '#io-channel-cells' lacks a 'const' constraint for the 'ti,ads1263' compatible string, violating subsystem guidelines.
--

commit ffd177f411725c6f5e66daebc6c552aaa2c40ee0
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 includes a
second auxiliary ADC for background measurements.

> 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..7e26572388e4f
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml

[ ... ]

> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,ads1263
> +    then:
> +      properties:
> +        '#io-channel-cells':
> +          minimum: 1
> +          maximum: 2

[Severity: Medium]
Does this violate the device tree bindings subsystem rule that requires
cell-count properties to have a strict `const` constraint?

Allowing a variable `#io-channel-cells` breaks the deterministic parsing
contract for IIO consumers, which typically rely on a fixed cell-count defined
by the provider variant.

> +      patternProperties:
> +        "^channel@[0-9]+$":

[Severity: Medium]
Will this regex fail to match valid hexadecimal unit addresses like
`channel@a`?

Device tree nodes for channel 10 (which uses the unit address `channel@a`)
will silently bypass the variant-specific length constraints for
`reference-sources`, allowing malformed configurations to be accepted without
warnings.

> +          properties:
> +            reference-sources:
> +              minItems: 3
> +              maxItems: 3
> +              default: [internal-p, internal-n, internal]
> +    else:
> +      properties:
> +        '#io-channel-cells':
> +          const: 1
> +      patternProperties:
> +        "^channel@[0-9]+$":

[Severity: Medium]
Does this regex also miss matching the 'a-f' range, bypassing
`reference-sources` constraints for higher channels on the `ti,ads1262`
variant?

> +          properties:
> +            reference-sources:
> +              minItems: 2
> +              maxItems: 2
> +              default: [internal-p, internal-n]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=2

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver
  2026-08-28  6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-08-28  6:52   ` sashiko-bot
  2026-08-28  8:09   ` Andy Shevchenko
  1 sibling, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  6:52 UTC (permalink / raw)
  To: Kurt Borja; +Cc: conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] Regmap debugfs accesses bypass the driver's custom xfer_lock, triggering lockdep splats and risking SPI message corruption.
- [Medium] Missing device stop on interrupted conversion causes a race condition and data desynchronization.
- [Low] Incompatible pointer type passed to `regmap_read`.
--

commit 6093db1ff45e9e897720bc6b278550ffbef67e85
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).
Various features such as accurate timeout delays, per-channel reference
sources, scale, offset, and diagnostics are intentionally left out for
future support.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> new file mode 100644
> --- /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]
Does this error path leave the hardware running if the wait is interrupted?

If ads1262_wait_for_conversion() returns an error (like -ERESTARTSYS from
a signal), the driver exits immediately without stopping the hardware. If a
subsequent read calls reinit_completion(&st->drdy), but the abandoned
hardware conversion finishes and asserts the DRDY interrupt right after,
could the new read prematurely fetch stale data from the old conversion?

Should this path ensure the hardware is stopped and synchronized with the
interrupt handler on timeout or interrupt?

[ ... ]

> +static int ads1262_dev_configure(struct ads1262 *st)
> +{
> +	struct device *dev = &st->spi->dev;
> +	int id, ret;
> +
> +	ret = ads1262_dev_reset(st);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to reset device\n");
> +
> +	guard(mutex)(&st->xfer_lock);
> +
> +	ret = regmap_read(st->regmap, ADS1262_ID_REG, &id);

[Severity: Low]
Is the type of id correct here?

The regmap_read() API expects an unsigned int * for its third argument, but
id is declared as an int. Could this trigger a -Wpointer-sign compiler
warning?

[ ... ]

> +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);

[Severity: Medium]
Will this lockdep assertion fail if the register is read via debugfs?

Because ads1262_regmap_config does not set disable_locking = true and does
not provide custom lock/unlock callbacks, regmap uses its own internal
mutex. When regmap's debugfs interface is read, the regmap core acquires
its internal mutex and calls the driver's ads1262_regmap_read() callback
without holding st->xfer_lock.

Could this bypass the xfer_lock, triggering the lockdep splat and allowing
debugfs SPI operations to interleave with driver-initiated SPI transactions?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=3

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
  2026-08-28  6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
@ 2026-08-28  6:53   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  6:53 UTC (permalink / raw)
  To: Kurt Borja; +Cc: devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Calling regulator_get_voltage() on a regulator's own supply from within its .get_voltage() callback causes a recursive locking self-deadlock.
--

commit ea8c096c3d3561a926dd5bc84b789c153ef1db68
Author: Kurt Borja <kuurtb@gmail.com>

iio: adc: ti-ads1262: support REFOUT and VBIAS regulators

This commit registers the "refout" and "vbias" regulators in the ti-ads1262
driver to enable their use as common mode supplies.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 3f6d34a08b296..4784142f35c9d 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c

[ ... ]

> @@ -1132,6 +1133,120 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
>  	return IRQ_HANDLED;
>  }
>  

[ ... ]

> +static int ads1262_vbias_get_voltage(struct regulator_dev *rdev)
> +{
> +	struct ads1262 *st = rdev_get_drvdata(rdev);
> +	int avdd_uV, avss_uV;
> +
> +	avdd_uV = regulator_get_voltage(st->avdd_supply);

[Severity: High]
Does this cause a recursive locking self-deadlock?

When a consumer or sysfs triggers a voltage query on vbias, the regulator
core calls regulator_lock_dependent(), which safely locks vbias and
recursively locks all of its supplies, including avdd.

The core then invokes vbias's .get_voltage callback, mapping to
ads1262_vbias_get_voltage(). Inside this callback, regulator_get_voltage()
acts as an external API: it initializes a new lock context and attempts
to lock avdd_supply again.

Because the current thread already holds the mutex for avdd, attempting
to lock it again with a new lock context results in a self-deadlock. A
thread attempting to read the vbias voltage will deadlock indefinitely,
causing a hang on that code path.

Could this approach use the unlocked internal API
regulator_get_voltage_rdev(rdev->supply->rdev) to get the supply's
voltage from within a regulator op instead?

> +	if (avdd_uV < 0)
> +		return avdd_uV;
> +
> +	avss_uV = st->avss_supply ? regulator_get_voltage(st->avss_supply) : 0;
> +	if (avss_uV < 0)
> +		return avss_uV;
> +
> +	return DIV_ROUND_CLOSEST(avdd_uV - avss_uV, 2);
> +}

[ ... ]

> +static const struct regulator_desc ads1262_vbias_regulator_desc = {
> +	.name = "vbias",
> +	.of_match = "vbias",
> +	.regulators_node = "regulators",
> +	.supply_name = "avdd",

[Severity: High]
This line establishes avdd as the upstream supply for vbias, which
instructs the regulator core that avdd is a strict dependency of vbias
and must be locked before dispatching operations to vbias ops. This is
what triggers the self-deadlock described above when
regulator_get_voltage() is called.

> +	.ops = &ads1262_vbias_regulator_ops,
> +	.type = REGULATOR_VOLTAGE,
> +	.owner = THIS_MODULE,
> +};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=9

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents
  2026-08-28  6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-08-28  6:57   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  6:57 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:
- [Low] Using `min()` with variables of different signedness (`int` and `size_t`) triggers a `BUILD_BUG_ON` compiler error from the kernel's `<linux/minmax.h>`.
--

commit b2756d4b7fc670f092613a6125bee27988772b27
Author: Kurt Borja <kuurtb@gmail.com>

iio: adc: ti-ads1262: support excitation currents

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".

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 4d38da092001b..04566d00d7cb8 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c

[ ... ]

> @@ -1356,7 +1393,51 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
>  		}
>  	}
>  
> +	if (fwnode_property_present(node, "excitation-channels")) {
> +		count = fwnode_property_count_u32(node, "excitation-channels");
> +		if (count < 0)
> +			return dev_err_probe(dev, count,
> +					     "%pfwP: failed to count excitation-channels\n", node);
> +
> +		pins[0] = ADS1262_IDACMUX_NO_CONN;
> +		pins[1] = ADS1262_IDACMUX_NO_CONN;
> +		ret = fwnode_property_read_u32_array(node, "excitation-channels",
> +						     pins, min(count, ARRAY_SIZE(pins)));
                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Low]
Does this code trigger a build failure from a BUILD_BUG_ON() in the min()
macro?

The min() macro enforces strict type checking. The count variable is a signed
int, while ARRAY_SIZE(pins) evaluates to an unsigned size_t. Would it be
better to use min_t(size_t, count, ARRAY_SIZE(pins)) here?

> +		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)));
                                                                   ^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: Low]
Does this min() macro invocation also trigger a build failure due to the same
mismatched types?

> +		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,

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=7

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling
  2026-08-28  6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-08-28  6:57   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  6:57 UTC (permalink / raw)
  To: Kurt Borja; +Cc: conor+dt, robh, devicetree

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The `rx` buffer in `struct ads1262` lacks the `__aligned(IIO_DMA_MINALIGN)` attribute, causing it to be placed at an unaligned memory offset and sharing a cacheline with the `tx` buffer.
- [High] The driver fails to filter out the IIO soft timestamp channel when iterating over `active_scan_mask`, treating it as a hardware channel. This leads to out-of-bounds array reads and writes, including a potential `FORTIFY_SOURCE` kernel panic.
- [High] The driver dynamically modifies a pre-optimized SPI transfer buffer without cache synchronization, breaking DMA coherency on architectures without hardware cache coherency.
--

commit 21b4fc84b4dd63fd1e3310ee7b41df30e8039785
Author: Kurt Borja <kuurtb@gmail.com>
Subject: 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 04566d00d7cb8..3f6d34a08b296 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -232,6 +239,11 @@ struct ads1262 {
>  	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
>  	int refp_uV[ADS1262_EXT_REF_COUNT];
>  	int refn_uV[ADS1262_EXT_REF_COUNT];
> +	IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
> +				   ADS1262_FW_CHANNEL_COUNT +
> +				   ADS1262_MON_CHANNEL_COUNT);
> +	u8 tx[ADS1262_SPI_XFER_SZ] __aligned(IIO_DMA_MINALIGN);
> +	u8 rx[ADS1262_SPI_XFER_SZ];

[Severity: High]
Does the rx buffer need the __aligned(IIO_DMA_MINALIGN) attribute as well?

Without it, rx might be placed at an unaligned memory offset and share a
cacheline with the tx buffer. Since they are mapped for DMA in opposite
directions (DMA_TO_DEVICE and DMA_FROM_DEVICE), mapping overlapping
cachelines can violate the DMA API and cause memory corruption on
non-cache-coherent architectures.

>  };
>  
>  static const char * const ads1262_device_id_to_name[] = {
[ ... ]
> @@ -837,10 +849,284 @@ static const struct iio_info ads1262_iio_info = {
[ ... ]
> +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]
Is it possible for this loop to iterate over the IIO soft timestamp channel?

If the timestamp channel is enabled and its bit is set in active_scan_mask,
iio_for_each_active_channel() will yield the timestamp channel's index.

This would cause ads1262_channel_prep_tx() to perform an out-of-bounds read
on the st->channels array since the timestamp channel is treated as a
hardware channel:

	struct ads1262_channel *chan = &st->channels[spec->scan_index];

Additionally, the extra loop iteration could cause the following memcpy to
write past the bounds of the scan_buffer array when all channels are active:

	memcpy(&st->scan_buffer[i], st->rx, sizeof(st->scan_buffer[i]));

> +
> +		reinit_completion(&st->drdy);
> +
> +		ret = spi_sync(st->spi, &st->msg);

[Severity: High]
Could this sequence break DMA coherency on architectures without hardware
cache coherency?

If the SPI message is permanently mapped for DMA in
ads1262_buffer_postenable_mult() via spi_optimize_message():

	return spi_optimize_message(st->spi, &st->msg);

And then on every trigger event, ads1262_channel_prep_tx() dynamically writes
new register commands directly into the st->tx buffer from the CPU:

	st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;

When ads1262_fill_buffer_mult() later calls spi_sync(), the message is
already marked as pre-optimized, so dma_sync_single_for_device() might
be skipped. Will the SPI controller fetch stale cached data instead of
the newly written configuration commands?

> +		if (ret)
> +			return ret;
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=8

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies
  2026-08-28  6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
@ 2026-08-28  7:03   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  7:03 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:
- [Medium] The `rx` buffer in `struct ads1262` lacks the `__aligned(IIO_DMA_MINALIGN)` attribute, causing it to share a cacheline with `tx`.
--

commit f75cefb819b551b39349e21259c13e70e46354c3
Author: Kurt Borja <kuurtb@gmail.com>
Subject: iio: adc: ti-ads1262: support common mode supplies

This commit enables common mode regulators for the ADS1262 ADC driver. It
adds support for parsing these supplies during probe and applying the common
mode voltage offsets to raw voltage readings.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 4784142f35c9d..c7ad1cf1a15d5 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -240,6 +240,7 @@ struct ads1262 {
>  	u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
>  	int refp_uV[ADS1262_EXT_REF_COUNT];
>  	int refn_uV[ADS1262_EXT_REF_COUNT];
> +	int common_mode_uV[ADS1262_INPMUX_AINCOM + 1];
>  	IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
>  				   ADS1262_FW_CHANNEL_COUNT +
>  				   ADS1262_MON_CHANNEL_COUNT);

[Severity: Medium]
Does the rx buffer at the end of this struct need an alignment attribute?

Looking at the layout of struct ads1262, the tx and rx buffers are defined
as:

    u8 tx[ADS1262_SPI_XFER_SZ] __aligned(IIO_DMA_MINALIGN);
    u8 rx[ADS1262_SPI_XFER_SZ];

Because rx lacks the __aligned(IIO_DMA_MINALIGN) attribute, it can share a
cacheline with the preceding tx buffer. C struct layout rules do not add
padding after an aligned array unless the subsequent member requires it.

Since these buffers are mapped separately by the SPI core (DMA_TO_DEVICE
and DMA_FROM_DEVICE respectively), could this violate the DMA API rule that
DMA buffers must not share cachelines with other separately mapped data? 
This seems like it would trigger a warning when CONFIG_DMA_API_DEBUG is
enabled during SPI transfers.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=10

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency
  2026-08-28  6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-08-28  7:03   ` sashiko-bot
  0 siblings, 0 replies; 21+ messages in thread
From: sashiko-bot @ 2026-08-28  7:03 UTC (permalink / raw)
  To: Kurt Borja; +Cc: robh, devicetree, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Out-of-bounds array read when accessing the soft timestamp channel.
--

commit dd8f25ef4ef94f53753566f6a544d3947f644a0f
Author: Kurt Borja <kuurtb@gmail.com>

iio: adc: ti-ads1262: support per-channel sampling frequency
    
This commit adds per-channel sampling frequency support by introducing a
channel-specific configuration array and assigning the available attributes
per-channel.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index d919144c23cb2..86ea9b6232d8f 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c

[ ... ]

> @@ -375,9 +449,18 @@ static int ads1262_wait_for_conversion(struct ads1262 *st)
>  static int ads1262_channel_enable(struct ads1262 *st,
>  				  const struct iio_chan_spec *spec)
>  {
> +	struct ads1262_channel *chan = &st->channels[spec->scan_index];
> +	int ret;
> +
>  	guard(mutex)(&st->xfer_lock);
>  	guard(mutex)(&st->chan_lock);

[Severity: High]
If a user enables the IIO timestamp channel (in_timestamp_en=1) alongside
other channels, could spec->scan_index exceed the allocated size of
st->channels here?

The IIO core includes the timestamp channel in indio_dev->active_scan_mask.
In the later commit "iio: adc: ti-ads1262: support triggered buffer sampling",
the driver iterates over active_scan_mask and passes each channel to this
function without skipping the timestamp channel.

Looking at how the array is allocated:

> @@ -763,8 +970,15 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
>  	if (!chan_specs)
>  		return -ENOMEM;
>  
> +	st->num_channels = num_fw_channels + ADS1262_MON_CHANNEL_COUNT;
> +	st->channels = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels),
> +				    GFP_KERNEL);
> +	if (!st->channels)
> +		return -ENOMEM;

Because st->channels is sized to strictly exclude the timestamp channel, the
timestamp channel's scan_index will equal st->num_channels.

Will this cause an out-of-bounds read when preparing the SPI transmission,
potentially sending garbage data to the ADC hardware and causing the driver
to hang waiting for an interrupt?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260828-ads126x-v4-0-1dc27e9c0260@gmail.com?part=4

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver
  2026-08-28  6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
  2026-08-28  6:52   ` sashiko-bot
@ 2026-08-28  8:09   ` Andy Shevchenko
  1 sibling, 0 replies; 21+ messages in thread
From: Andy Shevchenko @ 2026-08-28  8:09 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel

On Fri, Aug 28, 2026 at 01:38:18AM -0500, Kurt Borja wrote:
> Add the ti-ads1262 driver with initial support for the primary ADC
> (ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
> and interoperability considerations were taken into account.
> 
> Various features such as accurate timeout delays, per-channel reference
> sources, scale, offset, settling latency, excitation currents and
> diagnostics are intentionally left out for future support.

...

> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>

> +#include <linux/compiler_attributes.h>

This one is covered by types.h.

> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/lockdep.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>

...

> +/*
> + * The power transition timing requirement is 65536 clock cycles, at the minimum
> + * clock frequency this is 65536 microseconds.
> + */
> +#define ADS1262_POWER_TRANS_USECS		65536

_US as a suffix is enough. It's how in plenty of cases we do in the Linux
kernel.

> +#define ADS1262_NOMINAL_CLK_RATE		7372800

And here perhaps _Hz? What is the unit for this value?

...

> +static int ads1262_dev_send_cmd(struct ads1262 *st, u8 opcode)
> +{
> +	guard(mutex)(&st->xfer_lock);
> +
> +	return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
> +}
> +
> +static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
> +{
> +	guard(mutex)(&st->xfer_lock);
> +
> +	return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
> +}

How do these do not conflict or race with regmap SPI communication?

...

> +static int ads1262_dev_reset(struct ads1262 *st)
> +{
> +	struct device *dev = &st->spi->dev;
> +	struct gpio_desc *reset_gpiod;
> +	int ret;
> +
> +	reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
> +	if (IS_ERR(reset_gpiod))
> +		return dev_err_probe(dev, PTR_ERR(reset_gpiod),
> +				     "failed to get reset GPIO\n");

> +

Unneeded blank line. But can you use reset-gpio driver instead?

> +	if (reset_gpiod) {
> +		/*
> +		 * Wait a power transition cycle to ensure we are in a
> +		 * powered-off state after acquiring the RESET GPIO.
> +		 */
> +		fsleep(ADS1262_POWER_TRANS_USECS);
> +
> +		ret = gpiod_set_value_cansleep(reset_gpiod, 0);
> +		if (ret)
> +			return ret;
> +
> +		fsleep(ADS1262_POWER_TRANS_USECS);
> +	} else {
> +		ret = ads1262_dev_send_cmd(st, ADS1262_OPCODE_RESET);
> +		if (ret)
> +			return ret;
> +		/*
> +		 * The RESET timing requirement is 8 clock cycles, at the
> +		 * minimum clock rate this is 8 microseconds.
> +		 */
> +		fsleep(8);
> +	}
> +
> +	return 0;
> +}

...

> +static int ads1262_wait_for_conversion(struct ads1262 *st)
> +{
> +	u64 max_lat_ms;
> +	long ret;
> +
> +	/*
> +	 * The first conversion latency is affected by the channel's data rate,
> +	 * filter, the configurable conversion delay and whether chop mode
> +	 * and/or IDAC rotation mode are enabled.
> +	 *
> +	 * The worst possible latency is calculated by taking the lowest data
> +	 * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
> +	 * (Table 9-13). Then we scale it by the actual clock rate and multiply
> +	 * by 4 to account for chop and IDAC rotation modes (Equation 20).
> +	 */
> +	max_lat_ms = 4 * div_u64(1600ULL * ADS1262_NOMINAL_CLK_RATE, st->clk_rate);
> +	ret = wait_for_completion_interruptible_timeout(&st->drdy,
> +							msecs_to_jiffies(max_lat_ms));
> +	if (ret < 0)
> +		return ret;
> +	if (!ret)

In this case it's better to have number comparison as the semantics of 0 is
different to usual (success) code.

	if (ret == 0)

> +		return -ETIMEDOUT;
> +
> +	return 0;
> +}

...

> +static int ads1262_regmap_write(void *context, const void *data, size_t count)
> +{
> +	return ads1262_regmap_gather_write(context, data, 1, data + 1,
> +					   count - 1);

I would go for a single line of 82 characters.

> +}

...

> +static int ads1262_parse_channel_node(struct ads1262 *st,
> +				      struct iio_chan_spec *spec,
> +				      struct fwnode_handle *node)
> +{
> +	struct device *dev = &st->spi->dev;
> +	u32 pins[2];
> +	int ret;

Can this use the property names for 'single-channel' and 'diff-channels'?
This will deduplicate the same in a few places and reduce potential typos.

> +	if (fwnode_property_present(node, "single-channel")) {
> +		ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
> +		if (ret)
> +			return dev_err_probe(dev, ret, "%pfwP: failed to read single-channel\n",
> +					     node);
> +
> +		pins[1] = ADS1262_INPMUX_AINCOM;
> +
> +		if (fwnode_property_present(node, "common-mode-channel")) {
> +			ret = fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
> +			if (ret)
> +				return dev_err_probe(dev, ret,
> +						     "%pfwP: failed to read common-mode-channel\n",
> +						     node);
> +		}
> +	} else if (fwnode_property_present(node, "diff-channels")) {
> +		ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
> +						     ARRAY_SIZE(pins));
> +		if (ret)
> +			return dev_err_probe(dev, ret, "%pfwP: failed to read diff-channels\n",
> +					     node);
> +
> +		spec->differential = true;
> +	} else {
> +		return dev_err_probe(dev, -EINVAL,
> +				     "%pfwP: one of single-channel or diff-channels is required\n",
> +				     node);
> +	}
> +
> +	if (pins[0] > ADS1262_INPMUX_AINCOM || pins[1] > ADS1262_INPMUX_AINCOM)
> +		return dev_err_probe(dev, -EINVAL, "%pfwP: input channels not in range\n", node);
> +
> +	spec->channel = pins[0];
> +	spec->channel2 = pins[1];
> +
> +	return 0;
> +}

...

> +static int ads1262_parse_channels(struct iio_dev *indio_dev)
> +{
> +	struct ads1262 *st = iio_priv(indio_dev);
> +	struct device *dev = &st->spi->dev;
> +	struct iio_chan_spec *chan_specs;
> +	unsigned int num_fw_channels, num_specs;
> +	unsigned int i = 0;

Split assignment. Move it closer to the first user.

> +	u32 reg;
> +	int ret;
> +
> +	num_fw_channels = device_get_named_child_node_count(dev, "channel");
> +	if (num_fw_channels > ADS1262_FW_CHANNEL_COUNT)
> +		return dev_err_probe(dev, -EINVAL, "too many channels\n");
> +
> +	/* Account for the monitor channels and timestamp */
> +	num_specs = num_fw_channels + ADS1262_MON_CHANNEL_COUNT + 1;
> +	chan_specs = devm_kcalloc(dev, num_specs, sizeof(*chan_specs), GFP_KERNEL);
> +	if (!chan_specs)
> +		return -ENOMEM;
> +
> +	device_for_each_named_child_node_scoped(dev, node, "channel") {
> +		struct iio_chan_spec *spec = &chan_specs[i];
> +
> +		ret = fwnode_property_read_u32(node, "reg", &reg);
> +		if (ret)
> +			return dev_err_probe(dev, ret, "%pfwP: failed to read channel reg\n", node);
> +		if (reg >= ADS1262_MONITOR_ADDR_OFFSET)
> +			return dev_err_probe(dev, -EINVAL, "%pfwP: reg out of range\n", node);
> +
> +		ret = ads1262_parse_channel_node(st, spec, node);
> +		if (ret)
> +			return ret;
> +
> +		spec->type = IIO_VOLTAGE;
> +		spec->indexed = true;
> +		spec->scan_index = i;
> +		spec->address = reg;
> +		spec->scan_type = (struct iio_scan_type) {
> +			.format = IIO_SCAN_FORMAT_SIGNED_INT,
> +			.realbits = ADS1262_ADC1_RESOLUTION,
> +			.storagebits = 32,
> +			.endianness = IIO_BE,
> +		};
> +		spec->info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> +
> +		i++;
> +	}
> +
> +	memcpy(&chan_specs[i], ads1262_monitor_chan_specs,
> +	       sizeof(ads1262_monitor_chan_specs));
> +
> +	for (unsigned int mon = 0; mon < ADS1262_MON_CHANNEL_COUNT; mon++) {
> +		chan_specs[i].scan_index = i;
> +		i++;
> +	}
> +
> +	chan_specs[i] = IIO_CHAN_SOFT_TIMESTAMP(i);
> +	i++;
> +
> +	indio_dev->channels = chan_specs;
> +	indio_dev->num_channels = i;
> +
> +	return 0;
> +}

...

> +	st->clk_rate = rate ? rate : ADS1262_NOMINAL_CLK_RATE;

Can use Elvis.

-- 
With Best Regards,
Andy Shevchenko



^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property
  2026-08-28  6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
@ 2026-08-28 16:33   ` Conor Dooley
  0 siblings, 0 replies; 21+ messages in thread
From: Conor Dooley @ 2026-08-28 16:33 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 75 bytes --]

Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family
  2026-08-28  6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
  2026-08-28  6:45   ` sashiko-bot
@ 2026-08-28 16:39   ` Conor Dooley
  1 sibling, 0 replies; 21+ messages in thread
From: Conor Dooley @ 2026-08-28 16:39 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 13996 bytes --]

On Fri, Aug 28, 2026 at 01:38:17AM -0500, Kurt Borja wrote:
> The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
> integrated PGA, internal reference, excitation and burn-out current
> sources for sensor biasing and diagnostics. The ADS1263 is compatible
> with ADS1262, but includes a second auxiliary ADC (ADC2) to perform main
> channel (ADC1) cross-checking measurements, system background
> measurements, or temperature compensation of the primary sensor.
> 
> Both parts can configure per-channel voltage reference source,
> excitation current sources (IDAC), plus input and IDAC chopping for
> offset and IDAC mismatch cancellation. This lets the device drive and
> ratiometrically measure RTDs and other resistive sensors.
> 
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
>  .../devicetree/bindings/iio/adc/ti,ads1262.yaml    | 376 +++++++++++++++++++++
>  MAINTAINERS                                        |   6 +
>  2 files changed, 382 insertions(+)
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> new file mode 100644
> index 000000000000..7e26572388e4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> @@ -0,0 +1,376 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: TI ADS1262/ADS1263 analog to digital converter
> +
> +maintainers:
> +  - Kurt Borja <kuurtb@gmail.com>
> +
> +description: |
> +  The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an
> +  integrated PGA, reference, and internal fault monitors. The ADS1263 integrates
> +  an auxiliary, 24-bit, ΔΣ ADC intended for background measurements.
> +
> +  Datasheets:
> +    - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf
> +
> +properties:
> +  compatible:
> +    oneOf:
> +      - const: ti,ads1262
> +      - items:
> +          - const: ti,ads1263
> +          - const: ti,ads1262
> +
> +  reg:
> +    maxItems: 1
> +
> +  '#address-cells':
> +    const: 1
> +
> +  '#size-cells':
> +    const: 0
> +
> +  spi-max-frequency:
> +    maximum: 8000000
> +
> +  spi-cpha: true
> +
> +  interrupts:
> +    minItems: 1
> +    maxItems: 2
> +    description: Data ready (DRDY) interrupt lines.
> +
> +  interrupt-names:
> +    description:
> +      The data ready (DRDY) signal has two independent interrupt lines. A
> +      dedicated DRDY pin and a pin shared with the serial interface MISO pin,
> +      which is called DOUT/DRDY.
> +    minItems: 1
> +    maxItems: 2
> +    items:
> +      enum: [drdy, doutdrdy]
> +
> +  start-gpios:
> +    description: Start conversion control.
> +    maxItems: 1
> +
> +  reset-gpios:
> +    maxItems: 1
> +
> +  dvdd-supply:
> +    description: Digital power supply.
> +
> +  avdd-supply:
> +    description:
> +      Analog power supply. In bipolar supply configurations, the reported
> +      voltage should be in reference to DGND.
> +
> +  avss-supply:
> +    description:
> +      Negative analog power supply for bipolar configurations. AVSS can only be
> +      at or below the ground reference (DGND). If not described, AVSS is assumed
> +      to be connected to ground (0V).
> +
> +  clocks:
> +    maxItems: 1

clock doesn't appear mandatory, what does it do?

> +
> +  '#io-channel-cells':
> +    minimum: 1
> +    maximum: 2

What's the use case for actually setting this as a single cell? Kinda
confused by it.

Otherwise, quite frankly, the binding is too complicated for me! I'll
have to leave it up to the IIO folks to cover whether what's being done
is sane.

Cheers,
Conor.

> +    description: |
> +      The first cell selects the channel by its reg. The second cell selects
> +      between the main ADC (ADC1) and the auxiliary ADC (ADC2) as follows:
> +        0: ADC1
> +        1: ADC2
> +
> +  '#gpio-cells':
> +    const: 2
> +
> +  gpio-controller: true
> +
> +  regulators:
> +    type: object
> +    description:
> +      List of regulators provided by this chip.
> +
> +    properties:
> +      vbias:
> +        $ref: /schemas/regulator/regulator.yaml#
> +        type: object
> +        description:
> +          Level-shift voltage output on the AINCOM pin. Its output is the
> +          mid-voltage of the analog supply, (AVDD + AVSS) / 2, and is used to
> +          shift floating sensors to within the ADC input range.
> +        unevaluatedProperties: false
> +
> +      refout:
> +        $ref: /schemas/regulator/regulator.yaml#
> +        type: object
> +        description:
> +          Buffered internal voltage reference output on the REFOUT pin.
> +        unevaluatedProperties: false
> +
> +    additionalProperties: false
> +
> +patternProperties:
> +  "^ain([0-9]|com)-supply$":
> +    description:
> +      Common-mode voltage supply connected to AIN<N> or AINCOM.
> +
> +  "^refp[1-3]-supply$":
> +    description:
> +      Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
> +      REFP3 (AIN4). If this regulator is not described and a channel selects
> +      it as a reference source, then it's assumed to be connected to ground
> +      (0V).
> +
> +  "^refn[1-3]-supply$":
> +    description:
> +      Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
> +      REFN3 (AIN5). If this regulator is not described and a channel selects
> +      it as a reference source, then it's assumed to be connected to ground
> +      (0V).
> +
> +  "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
> +    description:
> +      Magnitude of the external reference resistor connected between REFP<N>
> +      and REFN<M>. In ratiometric configurations, such as RTD measurements, the
> +      IDAC excitation current returns through this resistor, generating the
> +      reference voltage for the conversion.
> +
> +  "^channel@[0-9a-f]+$":
> +    $ref: /schemas/iio/adc/adc.yaml#
> +    unevaluatedProperties: false
> +
> +    properties:
> +      reg:
> +        maxItems: 1
> +
> +      single-channel:
> +        minimum: 0
> +        maximum: 10
> +
> +      common-mode-channel:
> +        minimum: 0
> +        maximum: 10
> +        default: 10
> +
> +      diff-channels:
> +        items:
> +          minimum: 0
> +          maximum: 10
> +
> +      input-chopping: true
> +
> +      reference-sources:
> +        minItems: 2
> +        description:
> +          Indicates the reference sources for this channel. The first and second
> +          items are the positive (REFP) and negative (REFN) sources of the main
> +          ADC (ADC1). The third item is the reference source of the secondary
> +          ADC (ADC2) and must always have a positive differential voltage.
> +        items:
> +          - enum: [internal-p, refp1, refp2, refp3, avdd]
> +          - enum: [internal-n, refn1, refn2, refn3, avss]
> +          - enum: [internal, refp1-refn1, refp2-refn2, refp3-refn3, avdd-avss]
> +
> +      ti,reference-reversal:
> +        $ref: /schemas/types.yaml#/definitions/flag
> +        description:
> +          Indicates that the ADC1 (this has no effect on ADC2) reference voltage
> +          for this channel has negative polarity and thus should be internally
> +          reversed.
> +
> +      excitation-channels:
> +        minItems: 1
> +        maxItems: 2
> +        description:
> +          Selects the pins for the IDAC sources from 0 (AIN0) to 10 (AINCOM).
> +          The first value corresponds to IDAC1 and the second to IDAC2.
> +        items:
> +          minimum: 0
> +          maximum: 10
> +
> +      excitation-current-nanoamp:
> +        minItems: 1
> +        maxItems: 2
> +        description:
> +          The first value corresponds to IDAC1 and the second to IDAC2.
> +        items:
> +          enum: [50000, 100000, 250000, 500000, 750000, 1000000, 1500000,
> +                 2000000, 2500000, 3000000]
> +
> +      excitation-current-chopping: true
> +
> +      burn-out-current-nanoamp:
> +        description:
> +          Selects current magnitude for the sensor bias current source.
> +        enum: [500, 2000, 10000, 50000, 200000]
> +
> +      ti,burn-out-resistor:
> +        $ref: /schemas/types.yaml#/definitions/flag
> +        description:
> +          Instead of a fixed current, the sensor bias (burn-out) current source
> +          can be pulled using an internal 10 MΩ resistor.
> +
> +      ti,burn-out-polarity:
> +        $ref: /schemas/types.yaml#/definitions/string
> +        description:
> +          The sensor bias can be configured to either pull-up or pull-down mode.
> +          In pull-up mode, the current flows into the positive input and flows
> +          out of the negative input. In pull-down mode, the polarities are
> +          reversed.
> +        enum: [pull-up, pull-down]
> +        default: pull-up
> +
> +    dependencies:
> +      excitation-channels: [excitation-current-nanoamp]
> +      excitation-current-nanoamp: [excitation-channels]
> +      burn-out-current-nanoamp:
> +        not:
> +          required:
> +            - ti,burn-out-resistor
> +
> +    required:
> +      - reg
> +
> +    allOf:
> +      - if:
> +          properties:
> +            excitation-channels:
> +              maxItems: 1
> +          required:
> +            - excitation-channels
> +        then:
> +          properties:
> +            excitation-current-nanoamp:
> +              maxItems: 1
> +        else:
> +          properties:
> +            excitation-current-nanoamp:
> +              minItems: 2
> +
> +    oneOf:
> +      - required: [single-channel]
> +      - required: [diff-channels]
> +
> +dependencies:
> +  interrupts: [interrupt-names]
> +  interrupts-extended: [interrupt-names]
> +
> +required:
> +  - compatible
> +  - reg
> +  - spi-cpha
> +  - avdd-supply
> +  - dvdd-supply
> +  - '#address-cells'
> +  - '#size-cells'
> +
> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: ti,ads1263
> +    then:
> +      properties:
> +        '#io-channel-cells':
> +          minimum: 1
> +          maximum: 2
> +      patternProperties:
> +        "^channel@[0-9]+$":
> +          properties:
> +            reference-sources:
> +              minItems: 3
> +              maxItems: 3
> +              default: [internal-p, internal-n, internal]
> +    else:
> +      properties:
> +        '#io-channel-cells':
> +          const: 1
> +      patternProperties:
> +        "^channel@[0-9]+$":
> +          properties:
> +            reference-sources:
> +              minItems: 2
> +              maxItems: 2
> +              default: [internal-p, internal-n]
> +
> +unevaluatedProperties: false
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        adc@0 {
> +            compatible = "ti,ads1262";
> +            reg = <0>;
> +            spi-max-frequency = <8000000>;
> +            spi-cpha;
> +            avdd-supply = <&avdd>;
> +            dvdd-supply = <&dvdd>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
> +            interrupt-names = "drdy";
> +
> +            /* Typical common mode voltage configuration */
> +            aincom-supply = <&ads1262_vbias>;
> +
> +            regulators {
> +                ads1262_vbias: vbias { };
> +            };
> +
> +            channel@0 {
> +                reg = <0>;
> +                single-channel = <0>;
> +                /* The VBIAS is enabled on pin 10 (AINCOM) */
> +                common-mode-channel = <10>;
> +            };
> +        };
> +    };
> +
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        adc@0 {
> +            compatible = "ti,ads1263", "ti,ads1262";
> +            reg = <0>;
> +            spi-max-frequency = <8000000>;
> +            spi-cpha;
> +            avdd-supply = <&avdd>;
> +            dvdd-supply = <&dvdd>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
> +            interrupt-names = "drdy";
> +
> +            ti,refp2-refn2-resistor-ohms = <3520>;
> +
> +            channel@0 {
> +                reg = <0>;
> +                diff-channels = <4 5>;
> +                reference-sources = "refp2", "refn2", "refp2-refn2";
> +                excitation-channels = <1 6>;
> +                excitation-current-nanoamp = <500000 500000>;
> +            };
> +        };
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index f3be900e5b9d..7593e33c5346 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27019,6 +27019,12 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml
>  F:	drivers/iio/adc/ti-ads1018.c
>  
> +TI ADS1262 ADC DRIVER
> +M:	Kurt Borja <kuurtb@gmail.com>
> +L:	linux-iio@vger.kernel.org
> +S:	Maintained
> +F:	Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> +
>  TI ADS7924 ADC DRIVER
>  M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
>  L:	linux-iio@vger.kernel.org
> 
> -- 
> 2.55.0
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2026-08-28 16:40 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-28  6:38 [PATCH v4 00/10] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-28  6:38 ` [PATCH v4 01/10] dt-bindings: adc: add excitation-current-chopping property Kurt Borja
2026-08-28 16:33   ` Conor Dooley
2026-08-28  6:38 ` [PATCH v4 02/10] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-08-28  6:45   ` sashiko-bot
2026-08-28 16:39   ` Conor Dooley
2026-08-28  6:38 ` [PATCH v4 03/10] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-08-28  6:52   ` sashiko-bot
2026-08-28  8:09   ` Andy Shevchenko
2026-08-28  6:38 ` [PATCH v4 04/10] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
2026-08-28  7:03   ` sashiko-bot
2026-08-28  6:38 ` [PATCH v4 05/10] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
2026-08-28  6:38 ` [PATCH v4 06/10] iio: adc: ti-ads1262: support input chopping Kurt Borja
2026-08-28  6:38 ` [PATCH v4 07/10] iio: adc: ti-ads1262: support excitation currents Kurt Borja
2026-08-28  6:57   ` sashiko-bot
2026-08-28  6:38 ` [PATCH v4 08/10] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
2026-08-28  6:57   ` sashiko-bot
2026-08-28  6:38 ` [PATCH v4 09/10] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-08-28  6:53   ` sashiko-bot
2026-08-28  6:38 ` [PATCH v4 10/10] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
2026-08-28  7:03   ` sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox