Devicetree
 help / color / mirror / Atom feed
* [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support
@ 2026-06-12 22:46 Kurt Borja
  2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja
                   ` (5 more replies)
  0 siblings, 6 replies; 13+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
  To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel, linux-gpio, Kurt Borja,
	Jonathan Cameron

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.

The main ADC has quite a few features supported the main driver
(ti-ads1262), including:

  - Power management
  - IIO direct and buffer modes
  - Channel hot-reloading
  - Internal or external oscillator
  - Internal or external voltage reference
  - Filter configuration
  - Sensor bias configuration
  - IDAC configuration
  - Level-shift voltage configuration
  - Manual calibration support
  - GPIO controller capabilities

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

  - SPI offload support (38400 SPS turns out to be too high for some
    systems)
  - User triggered, automatic calibration (Datasheet 9.4.9)

Additionally, full support for the (less capable) auxiliary ADC is
introduced by the auxiliary ti-ads1263-adc2 driver included in this
series.

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-explainatory. 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>
---
Kurt Borja (5):
      dt-bindings: iio: adc: Add TI ADS126x ADC family
      iio: adc: Add ti-ads1262 driver
      iio: adc: ti-ads1262: Add GPIO controller support
      iio: adc: ti-ads1262: Add calibration support
      iio: adc: Add ti-ads1263-adc2 driver

 .../devicetree/bindings/iio/adc/ti,ads1262.yaml    |  308 +++
 .../bindings/iio/adc/ti,ads1263-adc2.yaml          |   49 +
 MAINTAINERS                                        |   10 +
 drivers/iio/adc/Kconfig                            |   26 +
 drivers/iio/adc/Makefile                           |    2 +
 drivers/iio/adc/ti-ads1262.c                       | 2180 ++++++++++++++++++++
 drivers/iio/adc/ti-ads1262.h                       |   39 +
 drivers/iio/adc/ti-ads1263-adc2.c                  |  470 +++++
 8 files changed, 3084 insertions(+)
---
base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e
change-id: 20251129-ads126x-fb6107505cae

-- 
Thanks, 
 ~ Kurt


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

* [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family
  2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
@ 2026-06-12 22:46 ` Kurt Borja
  2026-06-12 22:53   ` sashiko-bot
  2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
  To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel, linux-gpio, Kurt Borja,
	Jonathan Cameron

Add TI ADS1262 and TI ADS1263 ADC2 devicetree bindings documentation.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 .../devicetree/bindings/iio/adc/ti,ads1262.yaml    | 308 +++++++++++++++++++++
 .../bindings/iio/adc/ti,ads1263-adc2.yaml          |  49 ++++
 MAINTAINERS                                        |   7 +
 3 files changed, 364 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..1c4fde94f6c7
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
@@ -0,0 +1,308 @@
+# 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:
+    enum:
+      - ti,ads1262
+      - ti,ads1263
+
+  reg:
+    maxItems: 1
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  spi-max-frequency:
+    maximum: 8000000
+
+  spi-cpha: true
+
+  interrupts:
+    description: Data ready (DRDY) interrupt line.
+    maxItems: 1
+
+  start-gpios:
+    description: Start conversion control.
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  dvdd-supply:
+    description: Digital power supply.
+
+  avdd-supply:
+    description: Analog power supply.
+
+  vref-supply:
+    description: Optional external voltage reference.
+
+  ti,pos-refmux:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Selects the positive voltage reference input:
+      0: Internal 2.5 V reference
+      1: AIN0 pin
+      2: AIN2 pin
+      3: AIN4 pin
+      4: AVDD pin
+    minimum: 0
+    maximum: 4
+    default: 0
+
+  ti,neg-refmux:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Selects the negative voltage reference input:
+      0: Internal 2.5 V reference
+      1: AIN1 pin
+      2: AIN3 pin
+      3: AIN5 pin
+      4: AVSS pin
+    minimum: 0
+    maximum: 4
+    default: 0
+
+  ti,vbias:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description: Enables the level-shift voltage on the AINCOM pin.
+    default: false
+
+  ti,idac1-pin:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Selects the analog input pin to connect IDAC1:
+      0: AIN0
+      1: AIN1
+      2: AIN2
+      3: AIN3
+      4: AIN4
+      5: AIN5
+      6: AIN6
+      7: AIN7
+      8: AIN8
+      9: AIN9
+      10: AINCOM
+      11: No Connection
+    minimum: 0
+    maximum: 11
+    default: 11
+
+  ti,idac1-microamp:
+    description: Selects the current values of IDAC1.
+    enum: [0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000]
+    default: 0
+
+  ti,idac2-pin:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Selects the analog input pin to connect IDAC2:
+      0: AIN0
+      1: AIN1
+      2: AIN2
+      3: AIN3
+      4: AIN4
+      5: AIN5
+      6: AIN6
+      7: AIN7
+      8: AIN8
+      9: AIN9
+      10: AINCOM
+      11: No Connection
+    minimum: 0
+    maximum: 11
+    default: 11
+
+  ti,idac2-microamp:
+    description: Selects the current values of IDAC2.
+    enum: [0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000]
+    default: 0
+
+  clocks:
+    maxItems: 1
+
+  '#io-channel-cells':
+    const: 1
+
+  '#gpio-cells':
+    const: 2
+
+  gpio-controller: true
+
+  adc:
+    $ref: /schemas/iio/adc/ti,ads1263-adc2.yaml#
+
+required:
+  - compatible
+  - reg
+  - avdd-supply
+  - dvdd-supply
+  - '#address-cells'
+  - '#size-cells'
+
+unevaluatedProperties: false
+
+patternProperties:
+  "^channel@[0-9]+$":
+    $ref: /schemas/iio/adc/adc.yaml#
+    additionalProperties: false
+
+    properties:
+      reg:
+        maxItems: 1
+
+      diff-channels:
+        description: |
+          Selects the analog input configuration for this channel. The first
+          value is the positive input and the second is the negative input.
+          The following values are available:
+          0: AIN0 pin
+          1: AIN1 pin
+          2: AIN2 pin
+          3: AIN3 pin
+          4: AIN4 pin
+          5: AIN5 pin
+          6: AIN6 pin
+          7: AIN7 pin
+          8: AIN8 pin
+          9: AIN9 pin
+          10: AINCOM pin
+          11: Temperature sensor monitor
+          12: Analog power supply monitor
+          13: Digital power supply monitor
+          14: TDAC test signal
+          15: Float (open connection)
+        items:
+          minimum: 0
+          maximum: 15
+
+      ti,chop-mode:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          When enabled, the ADC performs two internal conversions to cancel the
+          input offset voltage. The first conversion is taken with normal input
+          polarity. The ADC reverses the internal input polarity for the second
+          conversion. The difference of the two conversions is computed to yield
+          the final corrected result with the offset voltage removed.
+        default: false
+
+      ti,idac-rotation-mode:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          The rotation mode automatically swaps the IDAC1 and IDAC2 connections
+          of alternate conversions. The ADC averages the alternate conversions
+          to eliminate IDAC mismatch.
+        default: false
+
+      ti,pga-bypass:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description: Bypass the Programmable Gain Amplifier (PGA).
+        default: false
+
+      ti,rev-vref-pol:
+        $ref: /schemas/types.yaml#/definitions/flag
+        description:
+          The reference polarity can be negative, but the ADC requires a
+          positive voltage reference. In this case, the reference
+          polarity-reversal switch changes the reference polarity from negative
+          to positive.
+        default: false
+
+      ti,sbias-connection:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description: |
+          Selects the sensor bias current source connection:
+          0: Sensor bias connected to ADC1 mux out
+          1: Sensor bias connected to ADC2 mux out
+        minimum: 0
+        maximum: 1
+        default: 0
+
+      ti,sbias-polarity:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description: |
+          Selects the sensor bias current source polarity:
+          0: Sensor bias pull-up
+          1: Sensor bias pull-down
+        minimum: 0
+        maximum: 1
+        default: 0
+
+      ti,sbias-magnitude:
+        $ref: /schemas/types.yaml#/definitions/uint32
+        description: |
+          Selects the sensor bias magnitude:
+          0: No sensor bias current or resistor
+          1: 0.5-uA sensor bias current
+          2: 2-uA sensor bias current
+          3: 10-uA sensor bias current
+          4: 50-uA sensor bias current
+          5: 200-uA sensor bias current
+          6: 10-Mohm resistor
+        minimum: 0
+        maximum: 6
+        default: 0
+
+    required:
+      - reg
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        adc@0 {
+            compatible = "ti,ads1263";
+            reg = <0>;
+            spi-max-frequency = <8000000>;
+            spi-cpha;
+            avdd-supply = <&avdd>;
+            dvdd-supply = <&dvdd>;
+            #address-cells = <1>;
+            #size-cells = <0>;
+
+            reset-gpios = <&gpio 18 GPIO_ACTIVE_LOW>;
+
+            /* AINP: 0 - AINN: AINCOM */
+            channel@0 {
+                reg = <0>;
+                diff-channels = <0x0 0xA>;
+            };
+
+            /* Temperature sensor monitor */
+            channel@1 {
+                reg = <1>;
+                diff-channels = <0xB 0xB>;
+            };
+
+            adc {
+                compatible = "ti,ads1263-adc2";
+            };
+        };
+    };
diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml
new file mode 100644
index 000000000000..9dd5577589b1
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads1263-adc2.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TI ADS1263 ADC2 analog-to-digital converter
+
+maintainers:
+  - Kurt Borja <kuurtb@gmail.com>
+
+description:
+  The ADS1263 includes an auxiliary, 24-bit, delta-sigma ADC (ADC2). ADC2
+  operation is independent of ADC1, with independent selections of input
+  channel, reference voltage, sample rate, and channel gain.
+
+properties:
+  compatible:
+    enum:
+      - ti,ads1263-adc2
+  vref-supply:
+    description: Optional external voltage reference.
+
+  ti,refmux:
+    $ref: /schemas/types.yaml#/definitions/uint32
+    description: |
+      Selects the positive voltage reference input:
+      0: Internal 2.5 V reference
+      1: AIN0-AIN1 pins
+      2: AIN2-AIN3 pins
+      3: AIN4-AIN5 pins
+      4: AVDD-AVSS pins
+    minimum: 0
+    maximum: 4
+    default: 0
+
+  '#address-cells':
+    const: 1
+
+  '#size-cells':
+    const: 0
+
+  '#io-channel-cells':
+    const: 1
+
+required:
+  - compatible
+
+additionalProperties: false
diff --git a/MAINTAINERS b/MAINTAINERS
index 396d4e76dccc..9379699d99c6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26668,6 +26668,13 @@ 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
+F:	Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml
+
 TI ADS7924 ADC DRIVER
 M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
 L:	linux-iio@vger.kernel.org

-- 
2.54.0


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

* [PATCH 2/5] iio: adc: Add ti-ads1262 driver
  2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
  2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja
@ 2026-06-12 22:46 ` Kurt Borja
  2026-06-12 23:01   ` sashiko-bot
  2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
  To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel, linux-gpio, Kurt Borja,
	Jonathan Cameron

Add ti-ads1262 driver for TI ADS1262 and ADS1263 ADCs with initial
support for the following features:

  - Power management
  - IIO direct and buffer modes
  - Channel hot-reloading
  - Internal or external oscillator
  - Internal or external voltage reference
  - Filter configuration
  - Sensor bias configuration
  - IDAC configuration
  - Level-shift voltage configuration
  - Auxiliary ADC interoperability considerations

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

diff --git a/MAINTAINERS b/MAINTAINERS
index 9379699d99c6..b874add5c924 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26674,6 +26674,7 @@ L:	linux-iio@vger.kernel.org
 S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.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 a3a93a47b43d..b6c35d0c88ed 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1796,6 +1796,19 @@ 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 && GPIOLIB
+	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.
+
+	  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 707dd708912f..e0653820081e 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -154,6 +154,7 @@ obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o
 obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
 obj-$(CONFIG_TI_ADS1119) += ti-ads1119.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..fd1911cf65ac
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -0,0 +1,1816 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments ADS1262 ADC driver
+ *
+ * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/align.h>
+#include <linux/bitfield.h>
+#include <linux/bitmap.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/math.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/overflow.h>
+#include <linux/property.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/time.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#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>
+
+/* Commands */
+#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
+
+/* Registers */
+#define ADS1262_ID_REG				0x00
+#define ADS1262_POWER_REG			0x01
+#define ADS1262_INTERFACE_REG			0x02
+#define ADS1262_MODE0_REG			0x03
+#define ADS1262_MODE1_REG			0x04
+#define ADS1262_MODE2_REG			0x05
+#define ADS1262_INPMUX_REG			0x06
+#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_IDACMAG_REG			0x0E
+#define ADS1262_REFMUX_REG			0x0F
+#define ADS1262_TDACP_REG			0x10
+#define ADS1262_TDACN_REG			0x11
+#define ADS1262_GPIOCON_REG			0x12
+#define ADS1262_GPIODIR_REG			0x13
+#define ADS1262_GPIODAT_REG			0x14
+#define ADS1262_ADC2CFG_REG			0x15
+#define ADS1262_ADC2MUX_REG			0x16
+#define ADS1262_ADC2OFC0_REG			0x17
+#define ADS1262_ADC2OFC1_REG			0x18
+#define ADS1262_ADC2FSC0_REG			0x19
+#define ADS1262_ADC2FSC1_REG			0x1A
+#define ADS1262_REG_COUNT			0x1B
+
+/* ID fields */
+#define ADS1262_DEV_ID_MASK			GENMASK(7, 5)
+#define ADS1262_REV_ID_MASK			GENMASK(4, 0)
+
+/* POWER fields */
+#define ADS1262_POWER_RESET_MASK		BIT(4)
+#define ADS1262_POWER_VBIAS_MASK		BIT(1)
+#define ADS1262_POWER_INTREF_MASK		BIT(0)
+
+/* INTERFACE fields */
+#define ADS1262_INTERFACE_TIMEOUT_MASK		BIT(3)
+#define ADS1262_INTERFACE_STATUS_MASK		BIT(2)
+#define ADS1262_INTERFACE_CRC_MASK		GENMASK(1, 0)
+
+/* MODE0 fields */
+#define ADS1262_MODE0_REFREV_MASK		BIT(7)
+#define ADS1262_MODE0_RUNMODE_MASK		BIT(6)
+#define ADS1262_MODE0_IDAC_ROT_MASK		BIT(5)
+#define ADS1262_MODE0_CHOP_MASK			BIT(4)
+#define ADS1262_MODE0_DELAY_MASK		GENMASK(3, 0)
+
+/* MODE1 fields */
+#define ADS1262_MODE1_FILTER_MASK		GENMASK(7, 5)
+#define ADS1262_MODE1_SBADC_MASK		BIT(4)
+#define ADS1262_MODE1_SBPOL_MASK		BIT(3)
+#define ADS1262_MODE1_SBMAG_MASK		GENMASK(2, 0)
+
+/* MODE2 fields */
+#define ADS1262_MODE2_BYPASS_MASK		BIT(7)
+#define ADS1262_MODE2_GAIN_MASK			GENMASK(6, 4)
+#define ADS1262_MODE2_DR_MASK			GENMASK(3, 0)
+
+/* INPMUX fields */
+#define ADS1262_INPMUX_MUXP_MASK		GENMASK(7, 4)
+#define ADS1262_INPMUX_MUXN_MASK		GENMASK(3, 0)
+
+/* IDACMUX fields */
+#define ADS1262_IDACMUX_MUX2_MASK		GENMASK(7, 4)
+#define ADS1262_IDACMUX_MUX1_MASK		GENMASK(3, 0)
+
+/* IDACMAG fields */
+#define ADS1262_IDACMAG_MAG2_MASK		GENMASK(7, 4)
+#define ADS1262_IDACMAG_MAG1_MASK		GENMASK(3, 0)
+
+/* REFMUX fields */
+#define ADS1262_REFMUX_RMUXP_MASK		GENMASK(5, 3)
+#define ADS1262_REFMUX_RMUXN_MASK		GENMASK(2, 0)
+
+/* TDACP fields */
+#define ADS1262_TDACP_OUTP_MASK			BIT(7)
+#define ADS1262_TDACP_MAGP_MASK			GENMASK(4, 0)
+
+/* TDACN fields */
+#define ADS1262_TDACN_OUTN_MASK			BIT(7)
+#define ADS1262_TDACN_MAGN_MASK			GENMASK(4, 0)
+
+/* ADC2CFG fields */
+#define ADS1262_ADC2CFG_DR2_MASK		GENMASK(7, 6)
+#define ADS1262_ADC2CFG_REF2_MASK		GENMASK(5, 3)
+#define ADS1262_ADC2CFG_GAIN2_MASK		GENMASK(2, 0)
+
+/* ADC2MUX fields */
+#define ADS1262_ADC2MUX_MUXP2_MASK		GENMASK(7, 4)
+#define ADS1262_ADC2MUX_MUXN2_MASK		GENMASK(3, 0)
+
+/* ID DEV_ID constants */
+#define ADS1262_DEV_ID_ADS1262			0
+#define ADS1262_DEV_ID_ADS1263			1
+
+/* MODE0 RUNMODE constants */
+#define ADS1262_RUNMODE_CONTINUOUS		0
+#define ADS1262_RUNMODE_PULSE			1
+
+/* MODE1 FILTER constants */
+#define ADS1262_FILTER_SINC1			0
+#define ADS1262_FILTER_SINC2			1
+#define ADS1262_FILTER_SINC3			2
+#define ADS1262_FILTER_SINC4			3
+#define ADS1262_FILTER_FIR			4
+
+/* MODE1 SENSOR BIAS constants */
+#define ADS1262_SBADC_COUNT			2
+#define ADS1262_SBPOL_COUNT			2
+#define ADS1262_SBMAG_COUNT			7
+
+/* MODE2 DATA RATE constants */
+#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
+
+/* INPMUX constants */
+#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
+
+/* IDACMUX constants */
+#define ADS1262_IDACMUX_NO_CONN			11
+#define ADS1262_IDACMUX_COUNT			12
+
+/* IDACMAG constants */
+#define ADS1262_IDACMAG_OFF			0
+#define ADS1262_IDACMAG_COUNT			11
+
+/* REFMUX constants */
+#define ADS1262_RMUX_INTER			0
+#define ADS1262_RMUX_AIN0_AIN1			1
+#define ADS1262_RMUX_AIN2_AIN3			2
+#define ADS1262_RMUX_AIN4_AIN5			3
+#define ADS1262_RMUX_AVDD_AVSS			4
+#define ADS1262_RMUX_COUNT			5
+
+struct ads1262_channel {
+	/* MODE0 */
+	u8 conv_delay:4;
+	u8 chop_mode:1;
+	u8 idac_rot_mode:1;
+	u8 runmode:1;
+	u8 rev_vref_pol:1;
+
+	/* MODE1 */
+	u8 sbias_magnitude:3;
+	u8 sbias_polarity:1;
+	u8 sbias_connection:1;
+	u8 filter:3;
+
+	/* MODE2 */
+	u8 data_rate:4;
+	u8 gain:3;
+	u8 pga_bypass:1;
+
+	/* INPMUX */
+	u8 negative_input:4;
+	u8 positive_input:4;
+};
+
+struct ads1262 {
+	struct spi_device *spi;
+	struct regmap *regmap;
+	struct iio_dev *indio_dev;
+	struct iio_trigger *trig;
+	struct gpio_desc *reset_gpiod;
+	struct gpio_desc *start_gpiod;
+
+	void *scan_buffer;
+	size_t scan_sz;
+
+	/* Protects channel state */
+	struct mutex chan_lock;
+	u32 vref_uV;
+	unsigned int num_channels;
+	struct ads1262_channel *channels;
+	struct completion drdy;
+	struct spi_message msg;
+	struct spi_transfer xfer;
+
+	/* Protects transfer buffers and concurrent SPI transfers */
+	struct mutex xfer_lock;
+
+	u8 tx[6] __aligned(IIO_DMA_MINALIGN);
+	union {
+		u8 rx[6];
+		struct {
+			__be32 data;
+		} __packed shift_reg;
+		struct {
+			u8 dummy;
+			__be32 data;
+		} __packed holding_reg;
+	};
+};
+
+static const int ads1262_data_rate_avail[][2] = {
+	[ADS1262_DR_2_5_SPS]	= { 2,		500000 },
+	[ADS1262_DR_5_SPS]	= { 5,		0 },
+	[ADS1262_DR_10_SPS]	= { 10,		0 },
+	[ADS1262_DR_16_6_SPS]	= { 16,		666667 },
+	[ADS1262_DR_20_SPS]	= { 20,		0 },
+	[ADS1262_DR_50_SPS]	= { 50,		0 },
+	[ADS1262_DR_60_SPS]	= { 60,		0 },
+	[ADS1262_DR_100_SPS]	= { 100,	0 },
+	[ADS1262_DR_400_SPS]	= { 400,	0 },
+	[ADS1262_DR_1200_SPS]	= { 1200,	0 },
+	[ADS1262_DR_2400_SPS]	= { 2400,	0 },
+	[ADS1262_DR_4800_SPS]	= { 4800,	0 },
+	[ADS1262_DR_7200_SPS]	= { 7200,	0 },
+	[ADS1262_DR_14400_SPS]	= { 14400,	0 },
+	[ADS1262_DR_19200_SPS]	= { 19200,	0 },
+	[ADS1262_DR_38400_SPS]	= { 38400,	0 },
+};
+
+static const int ads1262_conv_delay_avail[][2] = {
+	{ 0, 0 },
+	{ 0, 8700 },
+	{ 0, 17000 },
+	{ 0, 35000 },
+	{ 0, 69000 },
+	{ 0, 139000 },
+	{ 0, 278000 },
+	{ 0, 555000 },
+	{ 0, 1100000 },
+	{ 0, 2200000 },
+	{ 0, 4400000 },
+	{ 0, 8800000 },
+};
+
+static const int ads1262_pga_gain_avail[] = {
+	1, 2, 4, 8, 16, 32
+};
+
+static int ads1262_dev_power_on(struct ads1262 *st)
+{
+	int ret;
+
+	ret = gpiod_set_value_cansleep(st->reset_gpiod, 0);
+	if (ret)
+		return ret;
+
+	fsleep(9 * USEC_PER_MSEC);
+
+	return 0;
+}
+
+static int ads1262_dev_power_off(struct ads1262 *st)
+{
+	int ret;
+
+	ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
+	if (ret)
+		return ret;
+
+	fsleep(9 * USEC_PER_MSEC);
+
+	return 0;
+}
+
+static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
+{
+	guard(mutex)(&st->xfer_lock);
+
+	st->tx[0] = opcode;
+
+	return spi_write(st->spi, &st->tx[0], sizeof(st->tx[0]));
+}
+
+static int ads1262_dev_read_data_command(struct ads1262 *st, u8 cmd,
+					 __be32 *val)
+{
+	int ret;
+
+	guard(mutex)(&st->xfer_lock);
+
+	memset(st->tx, 0, sizeof(st->tx));
+	st->tx[0] = cmd;
+
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	*val = st->holding_reg.data;
+
+	return 0;
+}
+
+static int ads1262_dev_read_data_direct(struct ads1262 *st, __be32 *val)
+{
+	int ret;
+
+	/*
+	 * If reading data from the shift register, we should already be holding
+	 * the xfer_lock because all SPI activity is forbidden between the START
+	 * command and the actual data retrieval.
+	 */
+	lockdep_assert_held(&st->xfer_lock);
+
+	memset(st->tx, 0, sizeof(st->tx));
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	*val = st->shift_reg.data;
+
+	return 0;
+}
+
+static int ads1262_dev_reset(struct ads1262 *st)
+{
+	int ret;
+
+	if (st->reset_gpiod)
+		ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
+	else
+		ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET);
+	if (ret)
+		return ret;
+
+	fsleep(1);
+
+	if (st->reset_gpiod) {
+		ret = gpiod_set_value_cansleep(st->reset_gpiod, 0);
+		fsleep(1);
+	}
+
+	return ret;
+}
+
+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_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_cmd(st, ADS1262_OPCODE_STOP1);
+
+	return ret;
+}
+
+static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode)
+{
+	int ret;
+
+	ret = ads1262_dev_start(st);
+	if (ret)
+		return ret;
+
+	if (runmode == ADS1262_RUNMODE_CONTINUOUS)
+		return ads1262_dev_stop(st);
+
+	return 0;
+}
+
+static void ads1262_wait_for_conversion(struct ads1262 *st)
+{
+	reinit_completion(&st->drdy);
+
+	/*
+	 * 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 add the slowest configurable conversion delay
+	 * (9 ms) and multiply by 4 to account for chop and IDAC rotation modes
+	 * (Equation 20).
+	 *
+	 * Final result is 4 * (1600 ms + 9 ms) = 6436.
+	 */
+	wait_for_completion_timeout(&st->drdy, msecs_to_jiffies(6436));
+}
+
+static void ads1262_channel_set_runmode(struct ads1262 *st,
+					struct ads1262_channel *chan,
+					u8 runmode)
+{
+	guard(mutex)(&st->chan_lock);
+	chan->runmode = runmode;
+}
+
+static int ads1262_channel_enable_and_read(struct ads1262 *st,
+					   struct ads1262_channel *chan,
+					   __be32 *val)
+{
+	int ret;
+
+	lockdep_assert_held(&st->xfer_lock);
+
+	/*
+	 * Prepare the transfer buffer to do bulk register write
+	 * (Section 9.5.6).
+	 */
+	st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
+	st->tx[1] = sizeof(*chan) - 1;
+
+	/*
+	 * The ads1262_channel struct can be written directly to the chip's
+	 * configuration registers (MODE0, MODE1, MODE2, INPMUX) in a single
+	 * transfer, so it's necessary to assert it's size (4 bytes).
+	 */
+	static_assert(sizeof(*chan) == 4);
+	mutex_lock(&st->chan_lock);
+	memcpy(&st->tx[2], chan, sizeof(*chan));
+	mutex_unlock(&st->chan_lock);
+
+	ret = spi_sync(st->spi, &st->msg);
+	if (ret)
+		return ret;
+
+	/*
+	 * If new data is ready, it's shifted out on the same transfer.
+	 * (Section 9.4.7.1)
+	 */
+	if (val)
+		*val = st->shift_reg.data;
+
+	return 0;
+}
+
+static int ads1262_channel_enable(struct ads1262 *st,
+				  struct ads1262_channel *chan)
+{
+	guard(mutex)(&st->xfer_lock);
+
+	return ads1262_channel_enable_and_read(st, chan, NULL);
+}
+
+static int ads1262_channel_hot_reload(struct ads1262 *st,
+				      const struct iio_chan_spec *chan)
+{
+	unsigned int weight;
+	unsigned long i;
+	int ret;
+
+	/*
+	 * Hot reloading is only required on buffer mode and if only one channel
+	 * is enabled.
+	 */
+	if (!iio_device_try_claim_buffer_mode(st->indio_dev))
+		return 0;
+
+	weight = bitmap_weight(st->indio_dev->active_scan_mask,
+			       iio_get_masklength(st->indio_dev));
+	if (weight != 1) {
+		iio_device_release_direct(st->indio_dev);
+		return 0;
+	}
+
+	i = find_first_bit(st->indio_dev->active_scan_mask,
+			   iio_get_masklength(st->indio_dev));
+	if (i != chan->scan_index) {
+		iio_device_release_direct(st->indio_dev);
+		return 0;
+	}
+
+	/*
+	 * The device automatically hot reloads the channel after writing to
+	 * the configuration registers.
+	 */
+	ret = ads1262_channel_enable(st, &st->channels[chan->scan_index]);
+
+	iio_device_release_direct(st->indio_dev);
+
+	return ret;
+}
+
+static int ads1262_channel_read(struct ads1262 *st,
+				struct ads1262_channel *chan_data,
+				__be32 *val)
+{
+	struct device *dev = &st->spi->dev;
+	u8 runmode;
+	int ret;
+
+	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
+	if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+		return -ENXIO;
+
+	IIO_DEV_ACQUIRE_DIRECT_MODE(st->indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
+
+	/*
+	 * 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_data->chop_mode || chan_data->idac_rot_mode)
+		runmode = ADS1262_RUNMODE_CONTINUOUS;
+	else
+		runmode = ADS1262_RUNMODE_PULSE;
+
+	ads1262_channel_set_runmode(st, chan_data, runmode);
+
+	ret = ads1262_channel_enable(st, chan_data);
+	if (ret)
+		return ret;
+
+	ret = ads1262_dev_start_one(st, runmode);
+	if (ret)
+		return ret;
+
+	ads1262_wait_for_conversion(st);
+
+	return ads1262_dev_read_data_command(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)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan_data;
+	u8 mode, realbits;
+	__be32 raw;
+	u32 cnv;
+	int ret;
+
+	chan_data = &st->channels[chan->scan_index];
+	realbits = chan->scan_type.realbits;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = ads1262_channel_read(st, chan_data, &raw);
+		if (ret)
+			return ret;
+
+		cnv = be32_to_cpu(raw);
+		*val = sign_extend32(cnv, realbits - 1);
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		u64 divd, divr, tmp, rem;
+
+		mutex_lock(&st->chan_lock);
+		divd = st->vref_uV;
+		divr = BIT_ULL(chan_data->gain + realbits - 1) * 1000;
+		mutex_unlock(&st->chan_lock);
+
+		tmp = div64_u64(divd * 1000000000ULL, divr);
+		*val = div64_u64_rem(tmp, 1000000000ULL, &rem);
+		*val2 = rem;
+
+		return IIO_VAL_INT_PLUS_NANO;
+
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		mutex_lock(&st->chan_lock);
+		*val = ads1262_pga_gain_avail[chan_data->gain];
+		mutex_unlock(&st->chan_lock);
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		mutex_lock(&st->chan_lock);
+		mode = chan_data->data_rate;
+		mutex_unlock(&st->chan_lock);
+
+		*val = ads1262_data_rate_avail[mode][0];
+		*val2 = ads1262_data_rate_avail[mode][1];
+		return IIO_VAL_INT_PLUS_MICRO;
+
+	case IIO_CHAN_INFO_CONVDELAY:
+		mutex_lock(&st->chan_lock);
+		mode = chan_data->conv_delay;
+		mutex_unlock(&st->chan_lock);
+
+		*val = ads1262_conv_delay_avail[mode][0];
+		*val2 = ads1262_conv_delay_avail[mode][1];
+		return IIO_VAL_INT_PLUS_NANO;
+
+	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)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*type = IIO_VAL_INT_PLUS_MICRO;
+		*vals = (const int *)ads1262_data_rate_avail;
+		*length = ARRAY_SIZE(ads1262_data_rate_avail) * 2;
+		return IIO_AVAIL_LIST;
+
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		*type = IIO_VAL_INT;
+		*vals = ads1262_pga_gain_avail;
+		*length = ARRAY_SIZE(ads1262_pga_gain_avail);
+		return IIO_AVAIL_LIST;
+
+	case IIO_CHAN_INFO_CONVDELAY:
+		*type = IIO_VAL_INT_PLUS_NANO;
+		*vals = (const int *)ads1262_conv_delay_avail;
+		*length = ARRAY_SIZE(ads1262_conv_delay_avail) * 2;
+		return IIO_AVAIL_LIST;
+
+	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;
+	unsigned int i;
+
+	chan_data = &st->channels[chan->scan_index];
+
+	switch (mask) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		for (i = 0; i < ARRAY_SIZE(ads1262_data_rate_avail); i++) {
+			if (val == ads1262_data_rate_avail[i][0] &&
+			    val2 == ads1262_data_rate_avail[i][1])
+				break;
+		}
+		if (i == ARRAY_SIZE(ads1262_data_rate_avail))
+			return -EINVAL;
+
+		mutex_lock(&st->chan_lock);
+		chan_data->data_rate = i;
+		mutex_unlock(&st->chan_lock);
+
+		break;
+
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		for (i = 0; i < ARRAY_SIZE(ads1262_pga_gain_avail); i++) {
+			if (val == ads1262_pga_gain_avail[i])
+				break;
+		}
+		if (i == ARRAY_SIZE(ads1262_pga_gain_avail))
+			return -EINVAL;
+
+		mutex_lock(&st->chan_lock);
+		chan_data->gain = i;
+		mutex_unlock(&st->chan_lock);
+
+		break;
+
+	case IIO_CHAN_INFO_CONVDELAY:
+		for (i = 0; i < ARRAY_SIZE(ads1262_conv_delay_avail); i++) {
+			if (val == ads1262_conv_delay_avail[i][0] &&
+			    val2 == ads1262_conv_delay_avail[i][1])
+				break;
+		}
+		if (i == ARRAY_SIZE(ads1262_conv_delay_avail))
+			return -EINVAL;
+
+		mutex_lock(&st->chan_lock);
+		chan_data->conv_delay = i;
+		mutex_unlock(&st->chan_lock);
+
+		break;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ads1262_channel_hot_reload(st, chan);
+}
+
+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_CONVDELAY:
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+}
+
+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);
+	struct device *dev = &st->spi->dev;
+
+	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm);
+	if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+		return -ENXIO;
+
+	if (readval)
+		return regmap_read_bypassed(st->regmap, reg, readval);
+
+	return regmap_write(st->regmap, reg, writeval);
+}
+
+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,
+};
+
+static int ads1262_get_filter_type(struct iio_dev *indio_dev,
+				   const struct iio_chan_spec *chan)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan_data;
+
+	guard(mutex)(&st->chan_lock);
+
+	chan_data = &st->channels[chan->scan_index];
+	return chan_data->filter;
+}
+
+static int ads1262_set_filter_type(struct iio_dev *indio_dev,
+				   const struct iio_chan_spec *chan,
+				   unsigned int val)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct ads1262_channel *chan_data;
+
+	/* Can't guard() the lock here to avoid deadlock when hot reloading */
+	mutex_lock(&st->chan_lock);
+	chan_data = &st->channels[chan->scan_index];
+	chan_data->filter = val;
+	mutex_unlock(&st->chan_lock);
+
+	return ads1262_channel_hot_reload(st, chan);
+}
+
+static const char * const ads1262_filter_type_labels[] = {
+	[ADS1262_FILTER_SINC1] = "sinc1",
+	[ADS1262_FILTER_SINC2] = "sinc2",
+	[ADS1262_FILTER_SINC3] = "sinc3",
+	[ADS1262_FILTER_SINC4] = "sinc4",
+	[ADS1262_FILTER_FIR] = "fir",
+};
+
+static const struct iio_enum ads1262_filter_type_enum = {
+	.items = ads1262_filter_type_labels,
+	.num_items = ARRAY_SIZE(ads1262_filter_type_labels),
+	.get = ads1262_get_filter_type,
+	.set = ads1262_set_filter_type,
+};
+
+static const struct iio_chan_spec_ext_info ads1262_ext_info[] = {
+	IIO_ENUM("filter_type", IIO_SEPARATE, &ads1262_filter_type_enum),
+	IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE,
+			   &ads1262_filter_type_enum),
+	{ }
+};
+
+static const struct iio_chan_spec ads1262_iio_voltage_template = {
+	.type = IIO_VOLTAGE,
+	.indexed = true,
+	.scan_type = {
+		.format = IIO_SCAN_FORMAT_SIGNED_INT,
+		.realbits = 32,
+		.storagebits = 32,
+		.endianness = IIO_BE,
+	},
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			      BIT(IIO_CHAN_INFO_SCALE) |
+			      BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
+			      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+			      BIT(IIO_CHAN_INFO_CONVDELAY),
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
+					     BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+					     BIT(IIO_CHAN_INFO_CONVDELAY),
+	.ext_info = ads1262_ext_info,
+};
+
+static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
+{
+	struct ads1262 *st = dev_id;
+
+	if (iio_buffer_enabled(st->indio_dev))
+		iio_trigger_poll(st->trig);
+
+	complete(&st->drdy);
+
+	return IRQ_HANDLED;
+}
+
+static int ads1262_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+	unsigned int weight;
+	unsigned long i;
+	int ret;
+
+	weight = bitmap_weight(indio_dev->active_scan_mask,
+			       iio_get_masklength(indio_dev));
+	st->scan_sz = ALIGN(sizeof(__be32) * weight, sizeof(s64));
+	st->scan_sz += sizeof(s64);
+	st->scan_buffer = kzalloc(st->scan_sz, GFP_KERNEL);
+	if (!st->scan_buffer)
+		return -ENOMEM;
+
+	ret = spi_optimize_message(st->spi, &st->msg);
+	if (ret)
+		goto out_state_cleanup;
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret)
+		goto out_unoptimize_message;
+
+	iio_for_each_active_channel(indio_dev, i)
+		ads1262_channel_set_runmode(st, &st->channels[i],
+					    ADS1262_RUNMODE_CONTINUOUS);
+
+	if (weight == 1) {
+		i = find_first_bit(indio_dev->active_scan_mask,
+				   iio_get_masklength(indio_dev));
+		ret = ads1262_channel_enable(st, &st->channels[i]);
+		if (ret)
+			goto out_runtime_autosuspend;
+	}
+
+	ret = ads1262_dev_start(st);
+	if (ret)
+		goto out_runtime_autosuspend;
+
+	return 0;
+
+out_runtime_autosuspend:
+	pm_runtime_put_autosuspend(dev);
+
+out_unoptimize_message:
+	spi_unoptimize_message(&st->msg);
+
+out_state_cleanup:
+	kfree(st->scan_buffer);
+	st->scan_buffer = NULL;
+	st->scan_sz = 0;
+
+	return ret;
+}
+
+static int ads1262_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct device *dev = &st->spi->dev;
+
+	ads1262_dev_stop(st);
+	pm_runtime_put_autosuspend(dev);
+	spi_unoptimize_message(&st->msg);
+	kfree(st->scan_buffer);
+	st->scan_buffer = NULL;
+	st->scan_sz = 0;
+
+	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);
+	struct device *dev = &st->spi->dev;
+
+	if (iio_trigger_using_own(indio_dev)) {
+		dev_err(dev, "The %s trigger only supports one active channel\n",
+			st->trig->name);
+		return iio_validate_scan_mask_onehot(indio_dev, scan_mask);
+	}
+
+	return true;
+}
+
+static const struct iio_buffer_setup_ops ads1262_buffer_ops = {
+	.preenable = ads1262_buffer_preenable,
+	.postdisable = ads1262_buffer_postdisable,
+	.validate_scan_mask = ads1262_validate_scan_mask,
+};
+
+static int ads1262_fill_buffer_one(struct ads1262 *st)
+{
+	__be32 *scan_buffer = st->scan_buffer;
+
+	/*
+	 * When only one channel is enabled, we can't really avoid SPI activity
+	 * from happening when the auxiliary ADC is in use, thus we have to read
+	 * from the data-holding register (Section 9.4.7.2).
+	 */
+	return ads1262_dev_read_data_command(st, ADS1262_OPCODE_RDATA1,
+					     scan_buffer);
+}
+
+static int ads1262_fill_buffer_mult(struct ads1262 *st)
+{
+	__be32 val, *scan_buffer = st->scan_buffer;
+	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 SPI
+	 * activity happen 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(st->indio_dev, chan) {
+		ret = ads1262_channel_enable_and_read(st, &st->channels[chan],
+						      &val);
+		if (ret)
+			return ret;
+
+		if (i > -1)
+			scan_buffer[i] = val;
+		i++;
+
+		ads1262_wait_for_conversion(st);
+	}
+
+	return ads1262_dev_read_data_direct(st, &scan_buffer[i]);
+}
+
+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));
+
+	memset(st->scan_buffer, 0, st->scan_sz);
+
+	if (weight == 1)
+		ret = ads1262_fill_buffer_one(st);
+	else
+		ret = ads1262_fill_buffer_mult(st);
+	if (ret)
+		goto out_notify_done;
+
+	iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer,
+				    st->scan_sz, ts);
+
+out_notify_done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static int ads1262_parse_channel_data(struct ads1262 *st,
+				      struct ads1262_channel *chan_data,
+				      struct fwnode_handle *node)
+{
+	struct device *dev = &st->spi->dev;
+	const char *propname;
+	u32 val;
+	int ret;
+
+	if (fwnode_property_present(node, "ti,pga-bypass"))
+		chan_data->pga_bypass = true;
+
+	if (fwnode_property_present(node, "ti,rev-vref-pol"))
+		chan_data->rev_vref_pol = true;
+
+	if (fwnode_property_present(node, "ti,chop-mode"))
+		chan_data->chop_mode = true;
+
+	if (fwnode_property_present(node, "ti,idac-rotation-mode"))
+		chan_data->idac_rot_mode = true;
+
+	propname = "ti,sbias-connection";
+	if (fwnode_property_present(node, propname)) {
+		ret = fwnode_property_read_u32(node, propname, &val);
+		if (ret)
+			goto err_property_read;
+		if (val >= ADS1262_SBADC_COUNT) {
+			ret = -ERANGE;
+			goto err_property_read;
+		}
+
+		chan_data->sbias_connection = val;
+	}
+
+	propname = "ti,sbias-polarity";
+	if (fwnode_property_present(node, propname)) {
+		ret = fwnode_property_read_u32(node, propname, &val);
+		if (ret)
+			goto err_property_read;
+		if (val >= ADS1262_SBPOL_COUNT) {
+			ret = -ERANGE;
+			goto err_property_read;
+		}
+
+		chan_data->sbias_polarity = val;
+	}
+
+	propname = "ti,sbias-magnitude";
+	if (fwnode_property_present(node, propname)) {
+		ret = fwnode_property_read_u32(node, propname, &val);
+		if (ret)
+			goto err_property_read;
+		if (val >= ADS1262_SBMAG_COUNT) {
+			ret = -ERANGE;
+			goto err_property_read;
+		}
+
+		chan_data->sbias_magnitude = val;
+	}
+
+	return 0;
+
+err_property_read:
+	return dev_err_probe(dev, ret, "%s: Failed to read property %s\n",
+			     fwnode_get_name(node), propname);
+}
+
+static int ads1262_parse_channel_node(struct ads1262 *st,
+				      struct iio_chan_spec *chan,
+				      struct ads1262_channel *chan_data,
+				      struct fwnode_handle *node)
+{
+	struct device *dev = &st->spi->dev;
+	const char *propname;
+	u32 pins[2];
+	int ret;
+
+	propname = "diff-channels";
+	ret = fwnode_property_read_u32_array(node, propname, pins,
+					     ARRAY_SIZE(pins));
+	if (ret)
+		return dev_err_probe(dev, ret, "%s: Failed to read %s\n",
+				     fwnode_get_name(node), propname);
+
+	if (pins[0] > ADS1262_INPMUX_FLOAT)
+		return dev_err_probe(dev, -ENXIO,
+				     "%s: positive input %u not in range\n",
+				     fwnode_get_name(node), pins[0]);
+
+	if (pins[1] > ADS1262_INPMUX_FLOAT)
+		return dev_err_probe(dev, -ENXIO,
+				     "%s: negative input %u not in range\n",
+				     fwnode_get_name(node), pins[1]);
+
+	chan->channel = pins[0];
+	chan->channel2 = pins[1];
+	chan->differential = true;
+
+	chan_data->positive_input = pins[0];
+	chan_data->negative_input = pins[1];
+
+	/* Only non-zero default values are data rate and filter */
+	chan_data->data_rate = ADS1262_DR_20_SPS;
+	chan_data->filter = ADS1262_FILTER_FIR;
+
+	return ads1262_parse_channel_data(st, chan_data, node);
+}
+
+static int ads1262_parse_channels(struct iio_dev *indio_dev)
+{
+	struct ads1262 *st = iio_priv(indio_dev);
+	struct iio_chan_spec *channels, *chan;
+	struct device *dev = &st->spi->dev;
+	struct ads1262_channel *chan_data;
+	unsigned int num_channels;
+	u32 ch_reg;
+	int ret;
+
+	num_channels = device_get_named_child_node_count(dev, "channel");
+	if (!num_channels)
+		return dev_err_probe(dev, -ENXIO,
+				     "No 'channel' nodes configured\n");
+
+	st->num_channels = num_channels;
+	st->channels = devm_kcalloc(dev, num_channels, sizeof(*st->channels),
+				    GFP_KERNEL);
+	if (!st->channels)
+		return -ENOMEM;
+
+	/* Account for the timestamp channel */
+	num_channels++;
+	channels = devm_kcalloc(dev, num_channels, sizeof(*channels),
+				GFP_KERNEL);
+	if (!channels)
+		return -ENOMEM;
+
+	chan = channels;
+	device_for_each_named_child_node_scoped(dev, node, "channel") {
+		ret = fwnode_property_read_u32(node, "reg", &ch_reg);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "%s: Failed to read channel reg\n",
+					     fwnode_get_name(node));
+
+		/* Last channel is reserved for timestamp */
+		if (ch_reg >= num_channels - 1)
+			return dev_err_probe(dev, -EINVAL,
+					     "%s: reg %u out of range\n",
+					     fwnode_get_name(node), ch_reg);
+
+		*chan = ads1262_iio_voltage_template;
+		chan->scan_index = ch_reg;
+		chan_data = &st->channels[ch_reg];
+		ret = ads1262_parse_channel_node(st, chan++, chan_data, node);
+		if (ret)
+			return ret;
+	}
+
+	*chan = (struct iio_chan_spec)IIO_CHAN_SOFT_TIMESTAMP(num_channels - 1);
+
+	indio_dev->num_channels = num_channels;
+	indio_dev->channels = channels;
+
+	return 0;
+}
+
+static int ads1262_read_chip_name(struct ads1262 *st, char **name)
+{
+	struct device *dev = &st->spi->dev;
+	u8 dev_id;
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(st->regmap, ADS1262_ID_REG, &val);
+	if (ret)
+		return ret;
+
+	dev_id = FIELD_GET(ADS1262_DEV_ID_MASK, val);
+
+	switch (dev_id) {
+	case ADS1262_DEV_ID_ADS1262:
+		*name = "ads1262";
+		break;
+	case ADS1262_DEV_ID_ADS1263:
+		*name = "ads1263";
+		break;
+	default:
+		*name = "ads1262";
+		dev_dbg(dev, "Failed to identify device with ID 0x%x\n", val);
+	}
+
+	return 0;
+}
+
+static int ads1262_parse_idac_pins(struct ads1262 *st, u32 *pins,
+				   unsigned int num_pins)
+{
+	struct device *dev = &st->spi->dev;
+	char propname[10];
+	u32 val;
+	int ret;
+
+	for (unsigned int i = 0; i < num_pins; i++) {
+		scnprintf(propname, sizeof(propname), "idac%d-pin", i + 1);
+		if (!device_property_present(dev, propname)) {
+			pins[i] = ADS1262_IDACMUX_NO_CONN;
+			continue;
+		}
+
+		ret = device_property_read_u32(dev, propname, &val);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to read property %s\n",
+					     propname);
+		if (val < ADS1262_IDACMUX_COUNT)
+			return dev_err_probe(dev, -EINVAL,
+					     "%s: Pin number out of range %d\n",
+					     propname, val);
+
+		pins[i] = val;
+	}
+
+	return 0;
+}
+
+static int ads1262_parse_idac_mags(struct ads1262 *st, u32 *mags,
+				   unsigned int num_mags)
+{
+	static const u32 idac_microamps[] = {
+		0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000
+	};
+	struct device *dev = &st->spi->dev;
+	unsigned int mode;
+	char propname[15];
+	u32 val;
+	int ret;
+
+	for (unsigned int i = 0; i < num_mags; i++) {
+		scnprintf(propname, sizeof(propname), "idac%d-microamp", i + 1);
+		if (!device_property_present(dev, propname)) {
+			mags[i] = ADS1262_IDACMAG_OFF;
+			continue;
+		}
+
+		ret = device_property_read_u32(dev, propname, &val);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "Failed to read property %s\n",
+					     propname);
+
+		for (mode = 0; mode < ARRAY_SIZE(idac_microamps); i++) {
+			if (val == idac_microamps[mode])
+				break;
+		}
+		if (mode == ARRAY_SIZE(idac_microamps))
+			return dev_err_probe(dev, -EINVAL,
+					     "%s: Invalid value %d\n",
+					     propname, val);
+
+		mags[i] = mode;
+	}
+
+	return 0;
+}
+
+static int ads1262_dev_configure(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	u32 idac_pins[2], idac_mags[2];
+	u8 val;
+	int ret;
+
+	ret = ads1262_dev_reset(st);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to reset device\n");
+
+	ret = regmap_update_bits(st->regmap, ADS1262_INTERFACE_REG,
+				 ADS1262_INTERFACE_STATUS_MASK |
+				 ADS1262_INTERFACE_CRC_MASK, 0);
+	if (ret)
+		return ret;
+
+	if (device_property_present(dev, "ti,vbias")) {
+		ret = regmap_update_bits(st->regmap, ADS1262_POWER_REG,
+					 ADS1262_POWER_VBIAS_MASK,
+					 ADS1262_POWER_VBIAS_MASK);
+		if (ret)
+			return ret;
+	}
+
+	ret = ads1262_parse_idac_pins(st, idac_pins, ARRAY_SIZE(idac_pins));
+	if (ret)
+		return ret;
+
+	ret = ads1262_parse_idac_mags(st, idac_mags, ARRAY_SIZE(idac_mags));
+	if (ret)
+		return ret;
+
+	val = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, idac_pins[0]);
+	val |= FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, idac_pins[1]);
+
+	ret = regmap_update_bits(st->regmap, ADS1262_IDACMUX_REG,
+				 ADS1262_IDACMUX_MUX1_MASK |
+				 ADS1262_IDACMUX_MUX2_MASK, val);
+	if (ret)
+		return ret;
+
+	val = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, idac_mags[0]);
+	val |= FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, idac_mags[1]);
+
+	return regmap_update_bits(st->regmap, ADS1262_IDACMAG_REG,
+				  ADS1262_IDACMAG_MAG1_MASK |
+				  ADS1262_IDACMAG_MAG2_MASK, val);
+}
+
+static int ads1262_gpio_setup(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct gpio_desc *gpiod;
+	const char *con_id;
+
+	con_id = "start";
+	gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_OUT_LOW);
+	if (IS_ERR(gpiod))
+		return dev_err_probe(dev, PTR_ERR(gpiod),
+				     "Failed to get %s GPIO\n", con_id);
+	st->start_gpiod = gpiod;
+
+	con_id = "reset";
+	gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_OUT_HIGH);
+	if (IS_ERR(gpiod))
+		return dev_err_probe(dev, PTR_ERR(gpiod),
+				     "Failed to get %s GPIO\n", con_id);
+	st->reset_gpiod = gpiod;
+
+	return 0;
+}
+
+static int ads1262_clk_setup(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct clk *clk;
+	int 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");
+
+	/*
+	 * The nominal clock frequency as indicated by the datasheet is
+	 * 7372800.
+	 */
+	ret = clk_set_rate(clk, 7372800);
+	if (ret)
+		return dev_err_probe(dev, PTR_ERR(clk),
+				     "Failed to set the nominal clock frequency.\n");
+
+	return 0;
+}
+
+static int ads1262_regulator_setup(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	const char *reg_id, *prop;
+	u32 mux[2] = {};
+	int val, ret;
+
+	reg_id = "dvdd";
+	ret = devm_regulator_get_enable(dev, reg_id);
+	if (ret)
+		goto err_regulator_get;
+
+	reg_id = "avdd";
+	ret = devm_regulator_get_enable(dev, reg_id);
+	if (ret)
+		goto err_regulator_get;
+
+	prop = "ti,neg-refmux";
+	device_property_read_u32(dev, prop, &mux[0]);
+	if (mux[0] >= ADS1262_RMUX_COUNT)
+		return dev_err_probe(dev, -ENXIO, " %s out of range\n", prop);
+
+	prop = "ti,pos-refmux";
+	device_property_read_u32(dev, prop, &mux[1]);
+	if (mux[1] >= ADS1262_RMUX_COUNT)
+		return dev_err_probe(dev, -ENXIO, " %s out of range\n", prop);
+
+	if (mux[0] == ADS1262_RMUX_INTER && mux[1] == ADS1262_RMUX_INTER) {
+		/* The internal voltage reference is 2.5 V */
+		st->vref_uV = 2500000;
+		return 0;
+	}
+
+	val = FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, mux[0]);
+	val |= FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, mux[1]);
+	ret = regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
+				 ADS1262_REFMUX_RMUXN_MASK |
+				 ADS1262_REFMUX_RMUXP_MASK, val);
+	if (ret)
+		return ret;
+
+	reg_id = "vref";
+	st->vref_uV = devm_regulator_get_enable_read_voltage(dev, reg_id);
+	if (st->vref_uV < 0)
+		goto err_regulator_get;
+
+	return 0;
+
+err_regulator_get:
+	return dev_err_probe(dev, ret, "Failed to get regulator %s\n", reg_id);
+}
+
+static int ads1262_spi_message_setup(struct ads1262 *st)
+{
+	st->xfer.tx_buf = st->tx;
+	st->xfer.rx_buf = st->rx;
+	st->xfer.len = sizeof(st->tx);
+	spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
+
+	return 0;
+}
+
+static const struct regmap_range ads1262_read_write_range[] = {
+	regmap_reg_range(ADS1262_ID_REG, ADS1262_ADC2FSC1_REG),
+};
+
+static const struct regmap_range ads1262_read_only_range[] = {
+	regmap_reg_range(ADS1262_ID_REG, ADS1262_ID_REG),
+};
+
+static const struct regmap_range ads1262_volatile_range[] = {
+	/*
+	 * The channel configuration registers (MODE0, MODE1, MODE2, INPMUX) are
+	 * not actually volatile. However, we bypass the regmap API when
+	 * writing to these registers for optimization reasons.
+	 */
+	regmap_reg_range(ADS1262_MODE0_REG, ADS1262_INPMUX_REG),
+	regmap_reg_range(ADS1262_GPIODAT_REG, ADS1262_GPIODAT_REG),
+};
+
+static const struct regmap_access_table ads1262_wr_table = {
+	.yes_ranges = ads1262_read_write_range,
+	.n_yes_ranges = ARRAY_SIZE(ads1262_read_write_range),
+	.no_ranges = ads1262_read_only_range,
+	.n_no_ranges = ARRAY_SIZE(ads1262_read_only_range),
+};
+
+static const struct regmap_access_table ads1262_rd_table = {
+	.yes_ranges = ads1262_read_write_range,
+	.n_yes_ranges = ARRAY_SIZE(ads1262_read_write_range),
+};
+
+static const struct regmap_access_table ads1262_volatile_table = {
+	.yes_ranges = ads1262_volatile_range,
+	.n_yes_ranges = ARRAY_SIZE(ads1262_volatile_range),
+};
+
+static const struct reg_default ads1262_reg_defaults[] = {
+	{ ADS1262_POWER_REG,		0x11 },
+	{ ADS1262_INTERFACE_REG,	0x05 },
+	{ ADS1262_MODE0_REG,		0x00 },
+	{ ADS1262_MODE1_REG,		0x80 },
+	{ ADS1262_MODE2_REG,		0x04 },
+	{ ADS1262_INPMUX_REG,		0x01 },
+	{ ADS1262_OFCAL0_REG,		0x00 },
+	{ ADS1262_OFCAL1_REG,		0x00 },
+	{ ADS1262_OFCAL2_REG,		0x00 },
+	{ ADS1262_FSCAL0_REG,		0x00 },
+	{ ADS1262_FSCAL1_REG,		0x00 },
+	{ ADS1262_FSCAL2_REG,		0x40 },
+	{ ADS1262_IDACMUX_REG,		0xBB },
+	{ ADS1262_IDACMAG_REG,		0x00 },
+	{ ADS1262_REFMUX_REG,		0x00 },
+	{ ADS1262_TDACP_REG,		0x00 },
+	{ ADS1262_TDACN_REG,		0x00 },
+	{ ADS1262_GPIOCON_REG,		0x00 },
+	{ ADS1262_GPIODIR_REG,		0x00 },
+	{ ADS1262_GPIODAT_REG,		0x00 },
+	{ ADS1262_ADC2CFG_REG,		0x00 },
+	{ ADS1262_ADC2MUX_REG,		0x01 },
+	{ ADS1262_ADC2OFC0_REG,		0x00 },
+	{ ADS1262_ADC2OFC1_REG,		0x00 },
+	{ ADS1262_ADC2FSC0_REG,		0x00 },
+	{ ADS1262_ADC2FSC1_REG,		0x40 },
+};
+
+static const struct regmap_config ads1262_regmap_config = {
+	.reg_bits = 8,
+	.val_bits = 8,
+	.wr_table = &ads1262_wr_table,
+	.rd_table = &ads1262_rd_table,
+	.volatile_table = &ads1262_volatile_table,
+	.reg_defaults = ads1262_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
+	.max_register = ADS1262_ADC2FSC1_REG,
+	.read_flag_mask = ADS1262_OPCODE_RREG,
+	.write_flag_mask = ADS1262_OPCODE_WREG,
+	.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;
+	struct spi_transfer xfer[3] = {
+		{
+			.tx_buf = reg_buf,
+			.len = reg_size,
+		},
+		{
+			.tx_buf = &st->tx[0],
+			.len = sizeof(st->tx[0]),
+		},
+		{
+			.rx_buf = val_buf,
+			.len = val_size,
+		},
+	};
+
+	/*
+	 * It's necessary to lock the xfer_lock for the entirety of the
+	 * operation.
+	 *
+	 * Not only because the tx buffer is shared with conversion read
+	 * transfers, but also because some of these routines require no serial
+	 * activity happen between the DRDY signal and the actual read operation
+	 * (Section 9.4.7.1).
+	 *
+	 * The latter is specially important if the chip supports a second
+	 * auxiliary ADC (Section 9.3.15).
+	 */
+	guard(mutex)(&st->xfer_lock);
+
+	/*
+	 * The register read operation (RREG) allows bulk reading registers and
+	 * has the following structure (Section 9.5):
+	 *
+	 * Byte 1: ADS1262_OPCODE_RREG | REG
+	 * Byte 2: Number of registers minus 1
+	 * Byte 3+: Register data (MISO)
+	 */
+	st->tx[0] = val_size - 1;
+
+	return spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer));
+}
+
+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;
+	struct spi_transfer xfer[3] = {
+		{
+			.tx_buf = reg_buf,
+			.len = reg_size,
+		},
+		{
+			.tx_buf = &st->tx[0],
+			.len = sizeof(st->tx[0]),
+		},
+		{
+			.tx_buf = val_buf,
+			.len = val_size,
+		},
+	};
+
+	/*
+	 * It's necessary to lock the xfer_lock for the entirety of the
+	 * operation.
+	 *
+	 * Not only because the tx buffer is shared with conversion read
+	 * transfers, but also because some of these routines require no serial
+	 * activity happen between the DRDY signal and the actual read operation
+	 * (Section 9.4.7.1).
+	 *
+	 * The latter is specially important if the chip supports a second
+	 * auxiliary ADC (Section 9.3.15).
+	 */
+	guard(mutex)(&st->xfer_lock);
+
+	/*
+	 * The register write operation (WREG) allows bulk writing registers
+	 * and has the following structure (Section 9.5):
+	 *
+	 * Byte 1: ADS1262_OPCODE_WREG | REG
+	 * Byte 2: Number of registers minus 1
+	 * Byte 3+: Register data (MOSI)
+	 */
+	st->tx[0] = val_size - 1;
+
+	return spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer));
+}
+
+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,
+};
+
+static int ads1262_spi_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct iio_dev *indio_dev;
+	struct ads1262 *st;
+	char *name;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+	st->indio_dev = indio_dev;
+	init_completion(&st->drdy);
+	dev_set_drvdata(dev, st);
+
+	ret = devm_mutex_init(dev, &st->chan_lock);
+	if (ret)
+		return ret;
+	ret = devm_mutex_init(dev, &st->xfer_lock);
+	if (ret)
+		return ret;
+
+	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_spi_message_setup(st);
+	if (ret)
+		return ret;
+
+	ret = ads1262_regulator_setup(st);
+	if (ret)
+		return ret;
+
+	ret = ads1262_clk_setup(st);
+	if (ret)
+		return ret;
+
+	ret = ads1262_gpio_setup(st);
+	if (ret)
+		return ret;
+
+	ret = ads1262_dev_configure(st);
+	if (ret)
+		return dev_err_probe(dev, ret, "Failed to configure device\n");
+
+	ret = ads1262_read_chip_name(st, &name);
+	if (ret)
+		return ret;
+
+	indio_dev->name = name;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &ads1262_iio_info;
+	ret = ads1262_parse_channels(indio_dev);
+	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;
+
+	if (spi->irq > 0) {
+		st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", 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;
+
+		ret = devm_request_irq(dev, spi->irq, ads1262_irq_handler,
+				       IRQF_NO_THREAD, name, st);
+		if (ret)
+			return ret;
+	}
+
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_autosuspend_delay(dev, 10000);
+	pm_runtime_set_active(dev);
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static int ads1262_runtime_suspend(struct device *dev)
+{
+	struct ads1262 *st = dev_get_drvdata(dev);
+
+	if (!st->reset_gpiod)
+		return 0;
+
+	regcache_cache_only(st->regmap, true);
+
+	return ads1262_dev_power_off(st);
+}
+
+static int ads1262_runtime_resume(struct device *dev)
+{
+	struct ads1262 *st = dev_get_drvdata(dev);
+	int ret;
+
+	if (!st->reset_gpiod)
+		return 0;
+
+	ret = ads1262_dev_power_on(st);
+	if (ret)
+		return ret;
+
+	regcache_cache_only(st->regmap, false);
+	regcache_mark_dirty(st->regmap);
+
+	return regcache_sync(st->regmap);
+}
+
+DEFINE_RUNTIME_DEV_PM_OPS(ads1262_runtime_pm, ads1262_runtime_suspend,
+			  ads1262_runtime_resume, NULL);
+
+static const struct of_device_id ads1262_of_match[] = {
+	{ .compatible = "ti,ads1262" },
+	{ .compatible = "ti,ads1263" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ads1262_of_match);
+
+static const struct spi_device_id ads1262_spi_match[] = {
+	{ "ads1262" },
+	{ "ads1263" },
+	{ }
+};
+MODULE_DEVICE_TABLE(spi, ads1262_spi_match);
+
+static struct spi_driver ads1262_spi_driver = {
+	.driver = {
+		.name = "ads1262",
+		.of_match_table = ads1262_of_match,
+		.pm = pm_ptr(&ads1262_runtime_pm),
+	},
+	.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.54.0


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

* [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support
  2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
  2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja
  2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja
@ 2026-06-12 22:46 ` Kurt Borja
  2026-06-12 22:59   ` sashiko-bot
  2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 13+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
  To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel, linux-gpio, Kurt Borja,
	Jonathan Cameron

Add support for the GPIO controller capability found in both TI ADS1262
and ADS1263 ADCs.

Eight analog input pins can be programmed as GPIO. This configuration
does not prevent the pins from being used as analog inputs at the same
time, so no considerations were taken in that regard.

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

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index fd1911cf65ac..6d5f22836ad8 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -17,6 +17,7 @@
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
 #include <linux/interrupt.h>
 #include <linux/lockdep.h>
 #include <linux/math.h>
@@ -314,6 +315,10 @@ static const int ads1262_pga_gain_avail[] = {
 	1, 2, 4, 8, 16, 32
 };
 
+static const char * const ads1262_gpio_names[] = {
+	"AIN3", "AIN4", "AIN5", "AIN6", "AIN7", "AIN8", "AIN9", "AINCOM"
+};
+
 static int ads1262_dev_power_on(struct ads1262 *st)
 {
 	int ret;
@@ -1050,6 +1055,146 @@ static irqreturn_t ads1262_trigger_handler(int irq, void *p)
 	return IRQ_HANDLED;
 }
 
+static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+	struct device *dev = &st->spi->dev;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(dev);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG,
+				  BIT(offset), BIT(offset));
+}
+
+static void ads1262_gpiochip_free(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+	struct device *dev = &st->spi->dev;
+
+	regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG, BIT(offset), 0);
+	pm_runtime_put_autosuspend(dev);
+}
+
+static int ads1262_gpiochip_get_direction(struct gpio_chip *gc,
+					  unsigned int offset)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(st->regmap, ADS1262_GPIODIR_REG, &val);
+	if (ret)
+		return ret;
+
+	return val & BIT(offset);
+}
+
+static int ads1262_gpiochip_direction_input(struct gpio_chip *gc,
+					    unsigned int offset)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+
+	return regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG,
+				  BIT(offset), 1);
+}
+
+static int ads1262_gpiochip_direction_output(struct gpio_chip *gc,
+					     unsigned int offset, int value)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+	int ret;
+
+	ret = regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG,
+				 BIT(offset), 0);
+	if (ret)
+		return ret;
+
+	return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG,
+				  BIT(offset), value ? BIT(offset) : 0);
+}
+
+static int ads1262_gpiochip_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val);
+	if (ret)
+		return ret;
+
+	return val & BIT(offset);
+}
+
+static int ads1262_gpiochip_get_multiple(struct gpio_chip *gc,
+					 unsigned long *mask,
+					 unsigned long *bits)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val);
+	if (ret)
+		return ret;
+
+	return val & *mask;
+}
+
+static int ads1262_gpiochip_set(struct gpio_chip *gc, unsigned int offset,
+				int value)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+
+	return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG,
+				  BIT(offset), value ? BIT(offset) : 0);
+}
+
+static int ads1262_gpiochip_set_multiple(struct gpio_chip *gc,
+					 unsigned long *mask,
+					 unsigned long *bits)
+{
+	struct ads1262 *st = gpiochip_get_data(gc);
+
+	return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG, *mask,
+				  *bits);
+}
+
+static int ads1262_gpiochip_setup(struct ads1262 *st, const char *name)
+{
+	struct device *dev = &st->spi->dev;
+	struct gpio_chip *gc;
+
+	if (!device_property_present(dev, "gpio-controller"))
+		return 0;
+
+	gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL);
+	if (!gc)
+		return -ENOMEM;
+
+	gc->owner = THIS_MODULE;
+	gc->label = name;
+	gc->base = -1;
+	gc->ngpio = ARRAY_SIZE(ads1262_gpio_names);
+	gc->parent = dev;
+	gc->can_sleep = true;
+	gc->request = ads1262_gpiochip_request;
+	gc->free = ads1262_gpiochip_free;
+	gc->get_direction = ads1262_gpiochip_get_direction;
+	gc->direction_input = ads1262_gpiochip_direction_input;
+	gc->direction_output = ads1262_gpiochip_direction_output;
+	gc->get = ads1262_gpiochip_get;
+	gc->get_multiple = ads1262_gpiochip_get_multiple;
+	gc->set = ads1262_gpiochip_set;
+	gc->set_multiple = ads1262_gpiochip_set_multiple;
+	gc->names = ads1262_gpio_names;
+
+	return devm_gpiochip_add_data(dev, gc, st);
+}
+
 static int ads1262_parse_channel_data(struct ads1262 *st,
 				      struct ads1262_channel *chan_data,
 				      struct fwnode_handle *node)
@@ -1750,6 +1895,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	ret = ads1262_gpiochip_setup(st, name);
+	if (ret)
+		return ret;
+
 	return devm_iio_device_register(dev, indio_dev);
 }
 

-- 
2.54.0


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

* [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support
  2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (2 preceding siblings ...)
  2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja
@ 2026-06-12 22:46 ` Kurt Borja
  2026-06-12 23:02   ` sashiko-bot
  2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja
  2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner
  5 siblings, 1 reply; 13+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
  To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel, linux-gpio, Kurt Borja,
	Jonathan Cameron

Add channel calibration support.

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

diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 6d5f22836ad8..b33505e7fdc7 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -217,6 +217,10 @@
 #define ADS1262_RMUX_AVDD_AVSS			4
 #define ADS1262_RMUX_COUNT			5
 
+/* The calibration word is signed 24 bits value */
+#define ADS1262_CALIB_WORD_MAX		((int)(GENMASK(22, 0)))
+#define ADS1262_CALIB_WORD_MIN		(-ADS1262_CALIB_WORD_MAX - 1)
+
 struct ads1262_channel {
 	/* MODE0 */
 	u8 conv_delay:4;
@@ -453,6 +457,32 @@ static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode)
 	return 0;
 }
 
+static int ads1262_read_calib(struct ads1262 *st, unsigned int reg, u32 *val)
+{
+	__le32 lval;
+	int ret;
+
+	/*
+	 * The calibration word is a signed 24 bit LSB-first value.
+	 */
+	ret = regmap_bulk_read(st->regmap, reg, &lval, 3);
+	if (ret)
+		return ret;
+	*val = sign_extend32(le32_to_cpu(lval), 23);
+
+	return 0;
+}
+
+static int ads1262_write_calib(struct ads1262 *st, unsigned int reg, u32 val)
+{
+	__le32 lval = cpu_to_le32(val);
+
+	/*
+	 * The calibration word is a signed 24 bit LSB-first value.
+	 */
+	return regmap_bulk_write(st->regmap, reg, &lval, 3);
+}
+
 static void ads1262_wait_for_conversion(struct ads1262 *st)
 {
 	reinit_completion(&st->drdy);
@@ -673,6 +703,18 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
 		*val2 = ads1262_conv_delay_avail[mode][1];
 		return IIO_VAL_INT_PLUS_NANO;
 
+	case IIO_CHAN_INFO_CALIBSCALE:
+		ret = ads1262_read_calib(st, ADS1262_FSCAL0_REG, val);
+		if (ret)
+			return ret;
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_CALIBBIAS:
+		ret = ads1262_read_calib(st, ADS1262_OFCAL0_REG, val);
+		if (ret)
+			return ret;
+		return IIO_VAL_INT;
+
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -682,6 +724,9 @@ 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)
 {
+	static int calib_range[3] = { ADS1262_CALIB_WORD_MIN, 1,
+				      ADS1262_CALIB_WORD_MAX };
+
 	switch (mask) {
 	case IIO_CHAN_INFO_SAMP_FREQ:
 		*type = IIO_VAL_INT_PLUS_MICRO;
@@ -701,6 +746,13 @@ static int ads1262_read_avail(struct iio_dev *indio_dev,
 		*length = ARRAY_SIZE(ads1262_conv_delay_avail) * 2;
 		return IIO_AVAIL_LIST;
 
+	case IIO_CHAN_INFO_CALIBSCALE:
+	case IIO_CHAN_INFO_CALIBBIAS:
+		*type = IIO_VAL_INT;
+		*vals = calib_range;
+		*length = ARRAY_SIZE(calib_range);
+		return IIO_AVAIL_RANGE;
+
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -761,6 +813,18 @@ static int ads1262_write_raw(struct iio_dev *indio_dev,
 
 		break;
 
+	case IIO_CHAN_INFO_CALIBSCALE:
+		if (val > ADS1262_CALIB_WORD_MAX || val < ADS1262_CALIB_WORD_MIN)
+			return -EINVAL;
+
+		return ads1262_write_calib(st, ADS1262_FSCAL0_REG, val);
+
+	case IIO_CHAN_INFO_CALIBBIAS:
+		if (val > ADS1262_CALIB_WORD_MAX || val < ADS1262_CALIB_WORD_MIN)
+			return -EINVAL;
+
+		return ads1262_write_calib(st, ADS1262_OFCAL0_REG, val);
+
 	default:
 		return -EOPNOTSUPP;
 	}
@@ -867,9 +931,13 @@ static const struct iio_chan_spec ads1262_iio_voltage_template = {
 			      BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
 			      BIT(IIO_CHAN_INFO_SAMP_FREQ) |
 			      BIT(IIO_CHAN_INFO_CONVDELAY),
+	.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBSCALE) |
+				   BIT(IIO_CHAN_INFO_CALIBBIAS),
 	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
 					     BIT(IIO_CHAN_INFO_SAMP_FREQ) |
-					     BIT(IIO_CHAN_INFO_CONVDELAY),
+					     BIT(IIO_CHAN_INFO_CONVDELAY) |
+					     BIT(IIO_CHAN_INFO_CALIBSCALE) |
+					     BIT(IIO_CHAN_INFO_CALIBBIAS),
 	.ext_info = ads1262_ext_info,
 };
 

-- 
2.54.0


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

* [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver
  2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (3 preceding siblings ...)
  2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja
@ 2026-06-12 22:46 ` Kurt Borja
  2026-06-12 23:11   ` sashiko-bot
  2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner
  5 siblings, 1 reply; 13+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
  To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski
  Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
	devicetree, linux-kernel, linux-gpio, Kurt Borja,
	Jonathan Cameron

The TI ADS1263 includes an auxiliary, 24-bit, delta-sigma ADC (ADC2).
ADC2 operation is independent of ADC1, with independent selections of
input channel, reference voltage, sample rate, and channel gain

Add support for this ADC as an independent IIO device, through the
auxiliary bus API.

Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
 MAINTAINERS                       |   2 +
 drivers/iio/adc/Kconfig           |  13 ++
 drivers/iio/adc/Makefile          |   1 +
 drivers/iio/adc/ti-ads1262.c      | 147 ++++++++++++
 drivers/iio/adc/ti-ads1262.h      |  39 ++++
 drivers/iio/adc/ti-ads1263-adc2.c | 470 ++++++++++++++++++++++++++++++++++++++
 6 files changed, 672 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index b874add5c924..53ef8fdb5cc9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -26675,6 +26675,8 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
 F:	Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml
 F:	drivers/iio/adc/ti-ads1262.c
+F:	drivers/iio/adc/ti-ads1262.h
+F:	drivers/iio/adc/ti-ads1263-adc2.c
 
 TI ADS7924 ADC DRIVER
 M:	Hugo Villeneuve <hvilleneuve@dimonoff.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index b6c35d0c88ed..bb3a3f7742de 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1809,6 +1809,19 @@ config TI_ADS1262
 	  This driver can also be built as a module. If so, the module will be
 	  called ti-ads1262.
 
+config TI_ADS1263_ADC2
+	tristate "Texas Instruments ADS1263 auxiliary ADC (ADC2) driver"
+	depends on TI_ADS1262
+	select AUXILIARY_BUS
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	help
+	  If you say yes here you get support for Texas Instruments ADS1263
+	  auxiliary ADC (ADC2).
+
+	  This driver can also be built as a module. If so, the module will be
+	  called ti-ads1263-adc2.
+
 config TI_ADS1298
 	tristate "Texas Instruments ADS1298"
 	depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index e0653820081e..9120c9392176 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -155,6 +155,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
 obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
 obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
 obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o
+obj-$(CONFIG_TI_ADS1263_ADC2) += ti-ads1263-adc2.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
index b33505e7fdc7..1a4b2f934d43 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -14,10 +14,12 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/compiler_attributes.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/gpio/consumer.h>
 #include <linux/gpio/driver.h>
+#include <linux/idr.h>
 #include <linux/interrupt.h>
 #include <linux/lockdep.h>
 #include <linux/math.h>
@@ -42,6 +44,8 @@
 #include <linux/iio/trigger_consumer.h>
 #include <linux/iio/triggered_buffer.h>
 
+#include "ti-ads1262.h"
+
 /* Commands */
 #define ADS1262_OPCODE_NOP			0x00
 #define ADS1262_OPCODE_RESET			0x06
@@ -221,6 +225,8 @@
 #define ADS1262_CALIB_WORD_MAX		((int)(GENMASK(22, 0)))
 #define ADS1262_CALIB_WORD_MIN		(-ADS1262_CALIB_WORD_MAX - 1)
 
+static DEFINE_IDA(ads1262_ida);
+
 struct ads1262_channel {
 	/* MODE0 */
 	u8 conv_delay:4;
@@ -1123,6 +1129,143 @@ static irqreturn_t ads1262_trigger_handler(int irq, void *p)
 	return IRQ_HANDLED;
 }
 
+static int ads1263_adc2_enable(struct ads1263_adc2_ctx *ctx,
+			       const struct ads1263_adc2_channel *chan)
+{
+	struct ads1262 *st = ctx->chip;
+
+	/*
+	 * The ads1263_adc2_channel struct can be written directly to the chip's
+	 * configuration registers (ADC2CFG, ADC2MUX) in a single transfer, so
+	 * it's necessary to assert it's size (2 bytes).
+	 */
+	static_assert(sizeof(*chan) == 2);
+
+	guard(mutex)(&ctx->chan_lock);
+
+	return regmap_bulk_write(st->regmap, ADS1262_ADC2CFG_REG, chan,
+				 sizeof(*chan));
+}
+
+static int ads1263_adc2_start(struct ads1263_adc2_ctx *ctx)
+{
+	struct ads1262 *st = ctx->chip;
+
+	return ads1262_dev_cmd(st, ADS1262_OPCODE_START2);
+}
+
+static int ads1263_adc2_stop(struct ads1263_adc2_ctx *ctx)
+{
+	struct ads1262 *st = ctx->chip;
+
+	return ads1262_dev_cmd(st, ADS1262_OPCODE_STOP2);
+}
+
+static int ads1263_adc2_read(struct ads1263_adc2_ctx *ctx, __be32 *val)
+{
+	struct ads1262 *st = ctx->chip;
+
+	return ads1262_dev_read_data_command(st, ADS1262_OPCODE_RDATA2, val);
+}
+
+static void ads1262_aux_device_destroy(void *data)
+{
+	struct auxiliary_device *adev = data;
+
+	auxiliary_device_delete(adev);
+	auxiliary_device_uninit(adev);
+}
+
+static void ads1262_aux_device_release(struct device *dev)
+{
+	struct auxiliary_device *adev = to_auxiliary_dev(dev);
+	struct ads1263_adc2_ctx *ctx =
+		container_of(adev, struct ads1263_adc2_ctx, adev);
+	struct fwnode_handle *node = adev->dev.fwnode;
+
+	mutex_destroy(&ctx->chan_lock);
+	kfree(ctx->channels);
+	ida_free(&ads1262_ida, adev->id);
+	kfree(ctx);
+	fwnode_handle_put(node);
+}
+
+static int ads1262_aux_device_setup(struct ads1262 *st)
+{
+	struct device *dev = &st->spi->dev;
+	struct ads1263_adc2_channel *chans;
+	struct auxiliary_device *adev;
+	struct ads1263_adc2_ctx *ctx;
+	struct fwnode_handle *node;
+	int id, ret;
+
+	node = device_get_named_child_node(dev, "adc");
+	if (!node)
+		return 0;
+
+	ctx = kzalloc_obj(*ctx);
+	if (!ctx) {
+		ret = -ENOMEM;
+		goto out_node_put;
+	}
+
+	id = ida_alloc(&ads1262_ida, GFP_KERNEL);
+	if (id < 0) {
+		ret = id;
+		goto out_free_adc2;
+	}
+
+	chans = kcalloc(st->num_channels, sizeof(*chans), GFP_KERNEL);
+	if (!chans) {
+		ret = -ENOMEM;
+		goto out_free_id;
+	}
+
+	for (unsigned int i = 0; i < st->num_channels; i++) {
+		chans[i].negative_input = st->channels[i].negative_input;
+		chans[i].positive_input = st->channels[i].positive_input;
+	}
+
+	ctx->chip = st;
+	ctx->num_channels = st->num_channels;
+	ctx->channels = chans;
+	ctx->enable = ads1263_adc2_enable;
+	ctx->start = ads1263_adc2_start;
+	ctx->stop = ads1263_adc2_stop;
+	ctx->read = ads1263_adc2_read;
+	mutex_init(&ctx->chan_lock);
+
+	adev = &ctx->adev;
+	adev->name = "ads1263_adc2";
+	adev->id = id;
+	adev->dev.release = ads1262_aux_device_release;
+	adev->dev.parent = dev;
+	device_set_node(&adev->dev, no_free_ptr(node));
+
+	ret = auxiliary_device_init(adev);
+	if (ret)
+		goto out_free_channels;
+
+	ret = auxiliary_device_add(adev);
+	if (ret) {
+		auxiliary_device_uninit(adev);
+		return ret;
+	}
+
+	return devm_add_action_or_reset(dev, ads1262_aux_device_destroy, adev);
+
+out_free_channels:
+	kfree(chans);
+out_free_id:
+	ida_free(&ads1262_ida, id);
+out_free_adc2:
+	kfree(ctx);
+out_node_put:
+	fwnode_handle_put(node);
+
+	return ret;
+}
+
 static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset)
 {
 	struct ads1262 *st = gpiochip_get_data(gc);
@@ -1967,6 +2110,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
 	if (ret)
 		return ret;
 
+	ret = ads1262_aux_device_setup(st);
+	if (ret)
+		return ret;
+
 	return devm_iio_device_register(dev, indio_dev);
 }
 
diff --git a/drivers/iio/adc/ti-ads1262.h b/drivers/iio/adc/ti-ads1262.h
new file mode 100644
index 000000000000..98697d771da3
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1262.h
@@ -0,0 +1,39 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Texas Instruments ADS1262 ADC driver
+ *
+ * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
+ */
+
+#ifndef _ADS1262_H_
+#define _ADS1262_H_
+
+#include <linux/auxiliary_bus.h>
+#include <linux/types.h>
+
+struct ads1263_adc2_channel {
+	/* ADC2CFG */
+	u8 gain:3;
+	u8 refmux:3;
+	u8 data_rate:2;
+
+	/* ADC2MUX */
+	u8 negative_input:4;
+	u8 positive_input:4;
+};
+
+struct ads1263_adc2_ctx {
+	struct auxiliary_device adev;
+	struct ads1262 *chip;
+	/* Protects channel state */
+	struct mutex chan_lock;
+	struct ads1263_adc2_channel *channels;
+	unsigned int num_channels;
+	int (*enable)(struct ads1263_adc2_ctx *ctx,
+		      const struct ads1263_adc2_channel *chan);
+	int (*start)(struct ads1263_adc2_ctx *ctx);
+	int (*stop)(struct ads1263_adc2_ctx *ctx);
+	int (*read)(struct ads1263_adc2_ctx *ctx, __be32 *val);
+};
+
+#endif
diff --git a/drivers/iio/adc/ti-ads1263-adc2.c b/drivers/iio/adc/ti-ads1263-adc2.c
new file mode 100644
index 000000000000..d21f08bbd9ee
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1263-adc2.c
@@ -0,0 +1,470 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments ADS1263 auxiliary ADC (ADC2) driver
+ *
+ * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
+ */
+
+#include <linux/align.h>
+#include <linux/array_size.h>
+#include <linux/bitmap.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/container_of.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dev_printk.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/pm_runtime.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+
+#include "ti-ads1262.h"
+
+/* ADC2CFG REF2 constants */
+#define ADS1263_ADC2_REF2_INTER			0
+#define ADS1263_ADC2_REF2_COUNT			5
+
+struct ads1263_adc2 {
+	struct iio_dev *indio_dev;
+	struct ads1263_adc2_ctx *ctx;
+	u32 vref_uV;
+	u32 refmux;
+};
+
+static const int ads1263_adc2_gain_avail[] = {
+	1, 2, 4, 8, 16, 32, 64, 128
+};
+
+static const int ads1263_adc2_data_rate_avail[] = {
+	10, 100, 400, 800
+};
+
+static const unsigned long ads1263_adc2_latency_us[] = {
+	121000, 31200, 8710, 4970
+};
+
+static const struct iio_chan_spec ads1263_adc2_iio_voltage_template = {
+	.type = IIO_VOLTAGE,
+	.indexed = true,
+	.scan_type = {
+		.format = IIO_SCAN_FORMAT_SIGNED_INT,
+		.realbits = 24,
+		.storagebits = 32,
+		.shift = 8,
+		.endianness = IIO_BE,
+	},
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+			      BIT(IIO_CHAN_INFO_SCALE) |
+			      BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
+			      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+	.info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_HARDWAREGAIN) |
+					     BIT(IIO_CHAN_INFO_SAMP_FREQ),
+};
+
+static int ads1263_adc2_channel_hot_reload(struct ads1263_adc2 *st,
+					   const struct iio_chan_spec *chan)
+{
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	unsigned long i;
+	int ret;
+
+	/* Hot reloading is only required on buffer mode */
+	if (!iio_device_try_claim_buffer_mode(st->indio_dev))
+		return 0;
+
+	i = find_first_bit(st->indio_dev->active_scan_mask,
+			   iio_get_masklength(st->indio_dev));
+	if (i != chan->scan_index) {
+		iio_device_release_direct(st->indio_dev);
+		return 0;
+	}
+
+	ret = ctx->enable(ctx, &ctx->channels[chan->scan_index]);
+
+	iio_device_release_buffer_mode(st->indio_dev);
+
+	return ret;
+}
+
+static int ads1263_adc2_channel_read(struct iio_dev *indio_dev,
+				     struct ads1263_adc2_channel *chan_data,
+				     __be32 *val)
+{
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct device *dev = &ctx->adev.dev;
+	int ret;
+
+	PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev->parent, pm);
+	if (PM_RUNTIME_ACQUIRE_ERR(&pm))
+		return -ENXIO;
+
+	IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+	if (IIO_DEV_ACQUIRE_FAILED(claim))
+		return -EBUSY;
+
+	ret = ctx->enable(ctx, chan_data);
+	if (ret)
+		return ret;
+
+	ret = ctx->start(ctx);
+	if (ret)
+		return ret;
+
+	ret = ctx->stop(ctx);
+	if (ret)
+		return ret;
+
+	fsleep(ads1263_adc2_latency_us[chan_data->data_rate]);
+
+	return ctx->read(ctx, val);
+}
+
+static int ads1263_adc2_read_raw(struct iio_dev *indio_dev,
+				 struct iio_chan_spec const *chan,
+				 int *val, int *val2, long mask)
+{
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct ads1263_adc2_channel *chan_data;
+	u8 realbits;
+	__be32 raw;
+	u32 cnv;
+	int ret;
+
+	chan_data = &st->ctx->channels[chan->scan_index];
+	realbits = chan->scan_type.realbits;
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		ret = ads1263_adc2_channel_read(indio_dev, chan_data, &raw);
+		if (ret)
+			return ret;
+
+		cnv = be32_to_cpu(raw);
+		cnv >>= chan->scan_type.shift;
+		*val = sign_extend32(cnv, realbits - 1);
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		u64 divd, divr, tmp, rem;
+
+		mutex_lock(&ctx->chan_lock);
+		divd = st->vref_uV;
+		divr = BIT_ULL(chan_data->gain + realbits - 1) * 1000;
+		mutex_unlock(&ctx->chan_lock);
+
+		tmp = div64_u64(divd * 1000000000ULL, divr);
+		*val = div64_u64_rem(tmp, 1000000000ULL, &rem);
+		*val2 = rem;
+
+		return IIO_VAL_INT_PLUS_NANO;
+
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		mutex_lock(&ctx->chan_lock);
+		*val = ads1263_adc2_gain_avail[chan_data->gain];
+		mutex_unlock(&ctx->chan_lock);
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		mutex_lock(&ctx->chan_lock);
+		*val = ads1263_adc2_data_rate_avail[chan_data->data_rate];
+		mutex_unlock(&ctx->chan_lock);
+
+		return IIO_VAL_INT;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int
+ads1263_adc2_read_avail(struct iio_dev *indio_dev,
+			struct iio_chan_spec const *chan, const int **vals,
+			int *type, int *length, long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		*type = IIO_VAL_INT;
+		*vals = ads1263_adc2_gain_avail;
+		*length = ARRAY_SIZE(ads1263_adc2_gain_avail);
+		return IIO_AVAIL_LIST;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*type = IIO_VAL_INT;
+		*vals = ads1263_adc2_data_rate_avail;
+		*length = ARRAY_SIZE(ads1263_adc2_data_rate_avail);
+		return IIO_AVAIL_LIST;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static int ads1263_adc2_write_raw(struct iio_dev *indio_dev,
+				  struct iio_chan_spec const *chan,
+				  int val, int val2, long mask)
+{
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct ads1263_adc2_channel *chan_data;
+	unsigned int i;
+
+	chan_data = &ctx->channels[chan->scan_index];
+
+	switch (mask) {
+	case IIO_CHAN_INFO_HARDWAREGAIN:
+		for (i = 0; i < ARRAY_SIZE(ads1263_adc2_gain_avail); i++) {
+			if (val == ads1263_adc2_gain_avail[i])
+				break;
+		}
+		if (i == ARRAY_SIZE(ads1263_adc2_gain_avail))
+			return -EINVAL;
+
+		mutex_lock(&ctx->chan_lock);
+		chan_data->gain = i;
+		mutex_unlock(&ctx->chan_lock);
+
+		return 0;
+
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		for (i = 0; i < ARRAY_SIZE(ads1263_adc2_data_rate_avail); i++) {
+			if (val == ads1263_adc2_data_rate_avail[i])
+				break;
+		}
+		if (i == ARRAY_SIZE(ads1263_adc2_data_rate_avail))
+			return -EINVAL;
+
+		mutex_lock(&ctx->chan_lock);
+		chan_data->data_rate = i;
+		mutex_unlock(&ctx->chan_lock);
+
+		return 0;
+
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ads1263_adc2_channel_hot_reload(st, chan);
+}
+
+static int ads1263_adc2_write_raw_get_fmt(struct iio_dev *indio_dev,
+					  struct iio_chan_spec const *chan,
+					  long mask)
+{
+	switch (mask) {
+	case IIO_CHAN_INFO_CONVDELAY:
+		return IIO_VAL_INT_PLUS_NANO;
+	default:
+		return IIO_VAL_INT_PLUS_MICRO;
+	}
+}
+
+static const struct iio_info ads1263_adc2_iio_info = {
+	.read_raw = ads1263_adc2_read_raw,
+	.read_avail = ads1263_adc2_read_avail,
+	.write_raw = ads1263_adc2_write_raw,
+	.write_raw_get_fmt = ads1263_adc2_write_raw_get_fmt,
+};
+
+static int ads1263_adc2_buffer_preenable(struct iio_dev *indio_dev)
+{
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct device *dev = &ctx->adev.dev;
+	unsigned long i;
+	int ret;
+
+	ret = pm_runtime_resume_and_get(dev->parent);
+	if (ret)
+		return ret;
+
+	i = find_first_bit(indio_dev->active_scan_mask,
+			   iio_get_masklength(indio_dev));
+	ret = ctx->enable(ctx, &ctx->channels[i]);
+	if (ret)
+		goto out_runtime_autosuspend;
+
+	ret = ctx->start(ctx);
+	if (ret)
+		goto out_runtime_autosuspend;
+
+	return 0;
+
+out_runtime_autosuspend:
+	pm_runtime_put_autosuspend(dev->parent);
+
+	return ret;
+}
+
+static int ads1263_adc2_buffer_postdisable(struct iio_dev *indio_dev)
+{
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct device *dev = &ctx->adev.dev;
+
+	ctx->stop(ctx);
+	pm_runtime_put_autosuspend(dev->parent);
+
+	return 0;
+}
+
+static const struct iio_buffer_setup_ops ads1263_adc2_buffer_ops = {
+	.preenable = ads1263_adc2_buffer_preenable,
+	.postdisable = ads1263_adc2_buffer_postdisable,
+	.validate_scan_mask = iio_validate_scan_mask_onehot,
+};
+
+static irqreturn_t ads1263_adc2_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct {
+		__be32 conv;
+		aligned_s64 ts;
+	} scan = {};
+	int ret;
+
+	ret = ctx->read(ctx, &scan.conv);
+	if (ret)
+		goto out_notify_done;
+
+	iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan),
+				    pf->timestamp);
+
+out_notify_done:
+	iio_trigger_notify_done(indio_dev->trig);
+
+	return IRQ_HANDLED;
+}
+
+static int ads1263_adc2_channels_setup(struct iio_dev *indio_dev)
+{
+	struct ads1263_adc2 *st = iio_priv(indio_dev);
+	struct device *dev = &st->ctx->adev.dev;
+	struct ads1263_adc2_ctx *ctx = st->ctx;
+	struct iio_chan_spec *chns;
+	unsigned int i;
+
+	/* Account for the timestamp channel */
+	chns = devm_kcalloc(dev, ctx->num_channels + 1, sizeof(*chns),
+			    GFP_KERNEL);
+	if (!chns)
+		return -ENOMEM;
+
+	for (i = 0; i < ctx->num_channels; i++) {
+		guard(mutex)(&ctx->chan_lock);
+
+		ctx->channels[i].refmux = st->refmux;
+
+		chns[i] = ads1263_adc2_iio_voltage_template;
+		chns[i].scan_index = i;
+		chns[i].channel = ctx->channels[i].positive_input;
+		chns[i].channel2 = ctx->channels[i].negative_input;
+		chns[i].differential = true;
+	}
+
+	chns[i] = (struct iio_chan_spec)
+		IIO_CHAN_SOFT_TIMESTAMP(ctx->num_channels - 1);
+	chns[i].scan_index = i;
+
+	indio_dev->num_channels = ctx->num_channels + 1;
+	indio_dev->channels = chns;
+
+	return 0;
+}
+
+static int ads1263_adc2_regulator_setup(struct ads1263_adc2 *st)
+{
+	struct device *dev = &st->ctx->adev.dev;
+	const char *reg_id, *propname;
+	u32 refmux = 0;
+	int ret;
+
+	propname = "ti,refmux";
+	ret = device_property_read_u32(dev, propname, &refmux);
+	if (refmux >= ADS1263_ADC2_REF2_COUNT)
+		return dev_err_probe(dev, ret, "%s out of range\n", propname);
+	st->refmux = refmux;
+
+	if (refmux == ADS1263_ADC2_REF2_INTER) {
+		/* The internal voltage reference is 2.5 V */
+		st->vref_uV = 2500000;
+		return 0;
+	}
+
+	reg_id = "vref";
+	ret = devm_regulator_get_enable_read_voltage(dev, reg_id);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "Failed to get regulator %s\n",
+				     reg_id);
+	st->vref_uV = ret;
+
+	return 0;
+}
+
+static int ads1263_adc2_probe(struct auxiliary_device *auxdev,
+			      const struct auxiliary_device_id *id)
+{
+	struct ads1263_adc2_ctx *ctx =
+		container_of(auxdev, struct ads1263_adc2_ctx, adev);
+	struct device *dev = &auxdev->dev;
+	struct iio_dev *indio_dev;
+	struct ads1263_adc2 *st;
+	int ret;
+
+	indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->ctx = ctx;
+	st->indio_dev = indio_dev;
+
+	ret = ads1263_adc2_regulator_setup(st);
+	if (ret)
+		return ret;
+
+	indio_dev->name = (char *)id->driver_data;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->info = &ads1263_adc2_iio_info;
+	ret = ads1263_adc2_channels_setup(indio_dev);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+					      iio_pollfunc_store_time,
+					      ads1263_adc2_trigger_handler,
+					      &ads1263_adc2_buffer_ops);
+	if (ret)
+		return ret;
+
+	return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct auxiliary_device_id ads1263_adc2_auxiliary_match[] = {
+	{ .name = "ti_ads1262.ads1263_adc2",
+	  .driver_data = (kernel_ulong_t)"ads1263_adc2" },
+	{ }
+};
+MODULE_DEVICE_TABLE(auxiliary, ads1263_adc2_auxiliary_match);
+
+static struct auxiliary_driver ads1263_adc2_driver = {
+	.name = "ads1263_adc2",
+	.probe = ads1263_adc2_probe,
+	.id_table = ads1263_adc2_auxiliary_match,
+};
+module_auxiliary_driver(ads1263_adc2_driver);
+
+MODULE_DESCRIPTION("Texas Instruments ADS1263 auxiliary ADC (ADC2) driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>");

-- 
2.54.0


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

* Re: [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family
  2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja
@ 2026-06-12 22:53   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 22:53 UTC (permalink / raw)
  To: Kurt Borja; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Medium] Missing required GPIO properties in schema and examples
- [Medium] ADC2 child node is not conditionally restricted to the ads1263 compatible string
- [Medium] Contradictory schema definition for ADC2 child nodes
- [Medium] Incorrect schema property restriction blocking standard ADC properties
--

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

dt-bindings: iio: adc: Add TI ADS126x ADC family

This commit adds the devicetree bindings documentation for the TI ADS1262
and ADS1263 ADCs. It defines the main ADC schema, the properties for the
ADC channels, and includes a separate schema for the ADS1263 auxiliary ADC2.

> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> new file mode 100644
> index 0000000000000..1c4fde94f6c75
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> @@ -0,0 +1,308 @@

[ ... ]

> +  '#io-channel-cells':
> +    const: 1
> +
> +  '#gpio-cells':
> +    const: 2
> +
> +  gpio-controller: true
> +
> +  adc:
> +    $ref: /schemas/iio/adc/ti,ads1263-adc2.yaml#

[Severity: Medium]
The auxiliary ADC2 is only available on the ADS1263 variant, but the schema
allows the adc child node for all variants. Should an allOf block with an
if-then condition be added here or below to forbid the adc node when the
compatible string is ti,ads1262?

> +
> +required:
> +  - compatible
> +  - reg
> +  - avdd-supply
> +  - dvdd-supply
> +  - '#address-cells'
> +  - '#size-cells'

[Severity: Medium]
Since the binding provides GPIO controller capabilities, should the
gpio-controller and #gpio-cells properties be included in this required
list to ensure incomplete device tree nodes don't silently pass validation?

> +
> +unevaluatedProperties: false
> +
> +patternProperties:
> +  "^channel@[0-9]+$":
> +    $ref: /schemas/iio/adc/adc.yaml#
> +    additionalProperties: false

[Severity: Medium]
Does using additionalProperties: false here incorrectly prevent the use of
standard shared ADC channel properties from the referenced adc.yaml schema?
Should this be unevaluatedProperties: false instead to allow properties
evaluated by the referenced schema?

[ ... ]

> +allOf:
> +  - $ref: /schemas/spi/spi-peripheral-props.yaml#
> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +
> +        adc@0 {
> +            compatible = "ti,ads1263";
> +            reg = <0>;
> +            spi-max-frequency = <8000000>;
> +            spi-cpha;
> +            avdd-supply = <&avdd>;
> +            dvdd-supply = <&dvdd>;
> +            #address-cells = <1>;
> +            #size-cells = <0>;
> +
> +            reset-gpios = <&gpio 18 GPIO_ACTIVE_LOW>;

[Severity: Medium]
If gpio-controller and #gpio-cells are added to the required properties, do
they also need to be demonstrated in this example node to pass the
dt_binding_check validation?

[ ... ]

> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml
> new file mode 100644
> index 0000000000000..9dd5577589b1c
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml
> @@ -0,0 +1,49 @@

[ ... ]

> +  '#address-cells':
> +    const: 1
> +
> +  '#size-cells':
> +    const: 0
> +
> +  '#io-channel-cells':
> +    const: 1
> +
> +required:
> +  - compatible
> +
> +additionalProperties: false

[Severity: Medium]
The #address-cells and #size-cells properties imply the node expects child
nodes, but additionalProperties: false is specified globally and forbids them.

Since the driver dynamically derives ADC2 channels from the parent node rather
than parsing children from the ADC2 node, should the cell properties
#address-cells and #size-cells be removed to avoid this contradiction?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=1

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

* Re: [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support
  2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja
@ 2026-06-12 22:59   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 22:59 UTC (permalink / raw)
  To: Kurt Borja; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:

New issues:
- [High] The `ads1262_gpiochip_request` function leaks a runtime PM usage reference on the error path if `regmap_update_bits` fails.
- [High] `ads1262_gpiochip_get_multiple` does not populate the `bits` array and incorrectly returns a positive integer on success.
- [High] `ads1262_gpiochip_direction_input` clears the direction bit instead of setting it for any pin other than AIN3, incorrectly configuring them as outputs.
- [High] `ads1262_gpiochip_get_direction` returns unnormalized bitmask values, causing the GPIO core to reject them as errors.
- [High] Read-modify-write operations on the volatile `ADS1262_GPIODAT_REG` can corrupt other GPIO output states.
- [Medium] GPIO output direction and value are configured in the wrong order, causing a brief hardware glitch.

Pre-existing issues:
- [High] The driver uses C bitfields to define hardware registers, leading to incorrect device configuration on big-endian architectures.
--

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

iio: adc: ti-ads1262: Add GPIO controller support

Add support for the GPIO controller capability found in both TI ADS1262
and ADS1263 ADCs. Eight analog input pins can be programmed as GPIO.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index fd1911cf65ace..6d5f22836ad8b 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c

[ ... ]

> @@ -1050,6 +1055,146 @@ static irqreturn_t ads1262_trigger_handler(int irq, void *p)
>  	return IRQ_HANDLED;
>  }
>  
> +static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +	struct device *dev = &st->spi->dev;
> +	int ret;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG,
> +				  BIT(offset), BIT(offset));

[Severity: High]
Does this error path leak a runtime PM usage reference?

If regmap_update_bits() fails:

ads1262_gpiochip_request()
    ret = pm_runtime_resume_and_get(dev);
    // ...
    return regmap_update_bits(...); // Fails, returns error

Because the gpiolib core will not invoke .free for a pin that fails to be
requested, this usage count is permanently leaked, preventing the device from
ever entering runtime suspend.

> +}

[ ... ]

> +static int ads1262_gpiochip_get_direction(struct gpio_chip *gc,
> +					  unsigned int offset)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(st->regmap, ADS1262_GPIODIR_REG, &val);
> +	if (ret)
> +		return ret;
> +
> +	return val & BIT(offset);

[Severity: High]
Does this return unnormalized bitmask values?

For input pins with offset > 0, val & BIT(offset) returns values like 2, 4,
or 8. The GPIO subsystem strictly checks for 0 or 1 and returns -EBADE
otherwise, rendering the direction read functionality broken for most pins.

> +}
> +
> +static int ads1262_gpiochip_direction_input(struct gpio_chip *gc,
> +					    unsigned int offset)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +
> +	return regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG,
> +				  BIT(offset), 1);

[Severity: High]
Does this clear the direction bit instead of setting it for any pin other
than AIN3?

Because val is hardcoded to 1, val & BIT(offset) evaluates to 0 for any
offset > 0. This unintentionally clears the bit, configuring the pin as an
output instead of an input. If an external component is actively driving the
line, this creates an electrical short circuit, potentially causing hardware
damage.

> +}
> +
> +static int ads1262_gpiochip_direction_output(struct gpio_chip *gc,
> +					     unsigned int offset, int value)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +	int ret;
> +
> +	ret = regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG,
> +				 BIT(offset), 0);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG,
> +				  BIT(offset), value ? BIT(offset) : 0);

[Severity: Medium]
Are the GPIO output direction and value configured in the wrong order?

The direction register is set to output before setting the data register to
the desired value. This ordering drives the pin with the previously latched
output value during the interval between the two SPI transactions, causing a
brief hardware glitch.

The data register must be updated to the new output value before changing the
direction to output.

> +}

[ ... ]

> +static int ads1262_gpiochip_get_multiple(struct gpio_chip *gc,
> +					 unsigned long *mask,
> +					 unsigned long *bits)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val);
> +	if (ret)
> +		return ret;
> +
> +	return val & *mask;

[Severity: High]
Does this fail to populate the bits array and incorrectly return a positive
integer on success?

The gpiolib core expects get_multiple to populate the bits bitmap and return
0 on success. The implementation entirely ignores the bits argument and
returns the bitwise AND of the register value and mask (val & *mask).

If any requested GPIO line is high, this returns a positive integer, which
gpiolib treats as an error (-EBADE). This completely breaks the get_multiple
operation and leaves the bits array uninitialized.

> +}
> +
> +static int ads1262_gpiochip_set(struct gpio_chip *gc, unsigned int offset,
> +				int value)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +
> +	return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG,
> +				  BIT(offset), value ? BIT(offset) : 0);

[Severity: High]
Can this read-modify-write operation corrupt other GPIO output states?

ADS1262_GPIODAT_REG is a volatile register. Consequently,
regmap_update_bits() performs a physical read of the current GPIO pin levels
rather than reading a cached output latch state. If an output pin's physical
voltage is temporarily dragged down by a load or shorted to ground, the read
might yield 0 even if its output latch is 1.

The subsequent RMW cycle will permanently overwrite the output latch for that
pin with the incorrect physical value. A software shadow copy must be
maintained to prevent this corruption.

> +}

[ ... ]

[Severity: High]
This is a pre-existing issue, but does the driver use C bitfields to define
hardware registers?

Looking at the preexisting code in drivers/iio/adc/ti-ads1262.h:

struct ads1263_adc2_channel {
	/* ADC2CFG */
	u8 gain:3;
	u8 refmux:3;
	u8 data_rate:2;

	/* ADC2MUX */
	u8 negative_input:4;
	u8 positive_input:4;
};

And in drivers/iio/adc/ti-ads1262.c:

ads1262_channel_enable_and_read()
    static_assert(sizeof(*chan) == 4);
    mutex_lock(&st->chan_lock);
    memcpy(&st->tx[2], chan, sizeof(*chan));

The C standard leaves bitfield memory layout implementation-defined, and the
layout varies by architecture endianness. On big-endian systems, bitfields
are packed in reverse order compared to little-endian CPUs.

Copying these structs directly into SPI transfer buffers via memcpy or
writing them via regmap_bulk_write will send completely corrupted bit
patterns to the hardware registers. The kernel explicitly forbids using C
bitfields to describe hardware register layouts; FIELD_PREP, FIELD_GET, and
fixed-width types must be used instead.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=3

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

* Re: [PATCH 2/5] iio: adc: Add ti-ads1262 driver
  2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja
@ 2026-06-12 23:01   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 23:01 UTC (permalink / raw)
  To: Kurt Borja; +Cc: robh, conor+dt, devicetree

Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider:
- [High] Architecture Compatibility / Undefined Behavior due to direct bitfield mapping.
- [High] Out-of-Bounds Memory Access / Hardware Misconfiguration during active channel iteration.
- [High] Infinite Loop / Denial of Service in IDAC microamp parsing.
- [High] Logic Error: Inverted bounds check rejects valid pin numbers.
- [High] Race Condition / Lost Wakeup due to reinitializing completion after hardware trigger.
- [High] Missing Error Handling / Type Mismatch: Unsigned check of negative error code.
- [High] DMA Memory Corruption: Unaligned DMA buffer in union.
- [High] Uninitialized Memory Read (KMSAN) on stack variable.
- [High] Illegal DMA from Stack in regmap bus implementation.
- [High] Logic Error: GPIO get_multiple returns pin states incorrectly.
- [High] Hardware State / Timing violation during device reset.
- [High] Logic Error / Performance: Unconditional stall without IRQ.
- [Medium] Memory Leak of fwnode_handle reference on the error path.
- [Medium] Resource Leak of runtime PM usage counter on the error path.
- [Medium] API Contract Violation: Buffer mode lock released with direct mode API.
--

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

    iio: adc: Add ti-ads1262 driver
    
    Add ti-ads1262 driver for TI ADS1262 and ADS1263 ADCs with initial
    support for various features including IIO modes and channel configuration.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> new file mode 100644
> index 0000000000000..fd1911cf65ace
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -0,0 +1,1816 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
[ ... ]
> +struct ads1262_channel {
> +	/* MODE0 */
> +	u8 conv_delay:4;
> +	u8 chop_mode:1;
> +	u8 idac_rot_mode:1;
> +	u8 runmode:1;
> +	u8 rev_vref_pol:1;
[ ... ]
> +static int ads1262_channel_enable_and_read(struct ads1262 *st,
> +					   struct ads1262_channel *chan,
> +					   __be32 *val)
> +{
[ ... ]
> +	mutex_lock(&st->chan_lock);
> +	memcpy(&st->tx[2], chan, sizeof(*chan));
> +	mutex_unlock(&st->chan_lock);

[Severity: High]
Does this code map a C bitfield directly to the SPI TX buffer? 

C bitfield layout is implementation-defined, so this can send garbage
configuration bits to the hardware on big-endian architectures.

> +struct ads1262 {
[ ... ]
> +	u8 tx[6] __aligned(IIO_DMA_MINALIGN);
> +	union {
> +		u8 rx[6];
> +		struct {
> +			__be32 data;
> +		} __packed shift_reg;
> +		struct {
> +			u8 dummy;
> +			__be32 data;
> +		} __packed holding_reg;
> +	};
> +};

[Severity: High]
Can this union cause DMA memory corruption?

Since the anonymous union lacks an __aligned(IIO_DMA_MINALIGN) attribute,
the RX buffers can share a cache line with surrounding struct members,
leading to memory corruption during DMA on non-cache-coherent architectures.

> +static int ads1262_dev_reset(struct ads1262 *st)
> +{
> +	int ret;
> +
> +	if (st->reset_gpiod)
> +		ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
> +	else
> +		ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET);
> +	if (ret)
> +		return ret;
> +
> +	fsleep(1);

[Severity: High]
Is the 1 microsecond delay sufficient here? 

This short delay might violate hardware timing constraints for the device
reset, causing subsequent register writes to be ignored.

> +static int ads1262_read_calib(struct ads1262 *st, unsigned int reg, u32 *val)
> +{
> +	__le32 lval;
> +	int ret;
> +
> +	/*
> +	 * The calibration word is a signed 24 bit LSB-first value.
> +	 */
> +	ret = regmap_bulk_read(st->regmap, reg, &lval, 3);
> +	if (ret)
> +		return ret;
> +	*val = sign_extend32(le32_to_cpu(lval), 23);

[Severity: High]
Does this code evaluate an uninitialized byte?

The lval variable is 4 bytes long, but regmap_bulk_read only writes
3 bytes to it. The subsequent call to le32_to_cpu reads the
uninitialized 4th byte from the stack, which can trigger KMSAN warnings
and result in an incorrect sign extension.

> +static int ads1262_write_calib(struct ads1262 *st, unsigned int reg, u32 val)
> +{
> +	__le32 lval = cpu_to_le32(val);
> +
> +	/*
> +	 * The calibration word is a signed 24 bit LSB-first value.
> +	 */
> +	return regmap_bulk_write(st->regmap, reg, &lval, 3);
> +}

[Severity: High]
Can this result in an illegal DMA from the stack?

Calling regmap_bulk_write passes the lval stack variable to the
underlying SPI controller. This violates VMAP_STACK constraints and can
corrupt memory if the SPI controller uses DMA.

> +static void ads1262_wait_for_conversion(struct ads1262 *st)
> +{
> +	reinit_completion(&st->drdy);
> +
> +	/*
> +	 * The first conversion latency is affected by the channel's data rate,
[ ... ]
> +	wait_for_completion_timeout(&st->drdy, msecs_to_jiffies(6436));
> +}

[Severity: High]
Can wait_for_completion_timeout stall unconditionally if no IRQ is present?

Without a polling fallback, raw reads might block for the entire 6.4 second
timeout if the device isn't wired to an interrupt.

> +static int ads1262_channel_hot_reload(struct ads1262 *st,
> +				      const struct iio_chan_spec *chan)
> +{
[ ... ]
> +	weight = bitmap_weight(st->indio_dev->active_scan_mask,
> +			       iio_get_masklength(st->indio_dev));
> +	if (weight != 1) {
> +		iio_device_release_direct(st->indio_dev);
> +		return 0;
> +	}

[Severity: Medium]
Does this code release the wrong type of lock?

The iio_device_try_claim_buffer_mode API was used earlier to acquire
the buffer mode lock, but this path releases it using
iio_device_release_direct, violating the IIO locking contract.

> +static int ads1262_channel_read(struct ads1262 *st,
> +				struct ads1262_channel *chan_data,
> +				__be32 *val)
> +{
[ ... ]
> +	ret = ads1262_dev_start_one(st, runmode);
> +	if (ret)
> +		return ret;
> +
> +	ads1262_wait_for_conversion(st);

[Severity: High]
Is there a race condition with the completion here?

By calling ads1262_dev_start_one before ads1262_wait_for_conversion
(which re-initializes the completion), the hardware might fire the DRDY
interrupt before the completion is re-initialized, leading to a lost
wakeup.

> +static int ads1262_parse_idac_pins(struct ads1262 *st, u32 *pins,
> +				   unsigned int num_pins)
> +{
[ ... ]
> +		ret = device_property_read_u32(dev, propname, &val);
> +		if (ret)
> +			return dev_err_probe(dev, ret,
> +					     "Failed to read property %s\n",
> +					     propname);
> +		if (val < ADS1262_IDACMUX_COUNT)
> +			return dev_err_probe(dev, -EINVAL,
> +					     "%s: Pin number out of range %d\n",
> +					     propname, val);

[Severity: High]
Is the bounds check logic inverted here?

Checking if val < ADS1262_IDACMUX_COUNT causes valid pin numbers to be
rejected with an error instead of catching out-of-range ones.

> +static int ads1262_parse_idac_mags(struct ads1262 *st, u32 *mags,
> +				   unsigned int num_mags)
> +{
[ ... ]
> +		for (mode = 0; mode < ARRAY_SIZE(idac_microamps); i++) {
> +			if (val == idac_microamps[mode])
> +				break;
> +		}

[Severity: High]
Can this create an infinite loop?

The loop iterates the mode variable but increments i (the outer loop's
induction variable), resulting in a lockup during probe.

> +static int ads1262_regulator_setup(struct ads1262 *st)
> +{
[ ... ]
> +	reg_id = "vref";
> +	st->vref_uV = devm_regulator_get_enable_read_voltage(dev, reg_id);
> +	if (st->vref_uV < 0)
> +		goto err_regulator_get;

[Severity: High]
Will this error check ever succeed?

The return value of devm_regulator_get_enable_read_voltage is stored
in st->vref_uV, which is an unsigned 32-bit integer. The negative
error code becomes a large positive number, making st->vref_uV < 0
statically false.

> +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;
> +	struct spi_transfer xfer[3] = {
[ ... ]
> +		{
> +			.rx_buf = val_buf,
> +			.len = val_size,
> +		},
> +	};
[ ... ]
> +	return spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer));
> +}

[Severity: High]
Does this custom regmap bus allow SPI DMA transfers directly to user-provided
buffers?

Passing val_buf directly into rx_buf might result in DMA to stack
memory if the caller passes a stack variable, violating VMAP_STACK constraints
and causing memory corruption.

> +static int ads1262_gpiochip_get_multiple(struct gpio_chip *gc,
> +					 unsigned long *mask,
> +					 unsigned long *bits)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +	unsigned int val;
> +	int ret;
> +
> +	ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val);
> +	if (ret)
> +		return ret;
> +
> +	return val & *mask;
> +}

[Severity: High]
Does this code return the pin states incorrectly?

The GPIO get_multiple callback is expected to write the state to the
bits bitmap pointer, but instead it returns the state via the integer
return code.

> +static int ads1262_buffer_preenable(struct iio_dev *indio_dev)
> +{
[ ... ]
> +	iio_for_each_active_channel(indio_dev, i)
> +		ads1262_channel_set_runmode(st, &st->channels[i],
> +					    ADS1262_RUNMODE_CONTINUOUS);

[ ... ]
> +static int ads1262_fill_buffer_mult(struct ads1262 *st)
> +{
[ ... ]
> +	iio_for_each_active_channel(st->indio_dev, chan) {
> +		ret = ads1262_channel_enable_and_read(st, &st->channels[chan],
> +						      &val);

[Severity: High]
Can these loops access memory out of bounds?

iio_for_each_active_channel iterates over all active channels, including
the software timestamp channel if it is enabled. This can result in an
out-of-bounds array access on st->channels[chan] and misconfiguring the
hardware.

> +static int ads1262_aux_device_setup(struct ads1262 *st)
> +{
[ ... ]
> +out_node_put:
> +	fwnode_handle_put(node);
> +
> +	return ret;
> +}

[Severity: Medium]
Does this code leak the fwnode reference on the error path?

Because node is consumed and set to NULL by no_free_ptr(node) during
device_set_node, any subsequent failures jumping to out_node_put will
call fwnode_handle_put(NULL), leaving the reference leaked.

> +static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset)
> +{
> +	struct ads1262 *st = gpiochip_get_data(gc);
> +	struct device *dev = &st->spi->dev;
> +	int ret;
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret)
> +		return ret;
> +
> +	return regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG,
> +				  BIT(offset), BIT(offset));
> +}

[Severity: Medium]
Does this leak the runtime PM usage counter on failure?

If regmap_update_bits fails, the function returns the error directly
without calling pm_runtime_put_autosuspend(dev) to balance the resume.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=2

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

* Re: [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support
  2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja
@ 2026-06-12 23:02   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 23:02 UTC (permalink / raw)
  To: Kurt Borja; +Cc: conor+dt, devicetree, robh

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Using stack-allocated variables as buffers for SPI DMA transfers via `regmap_bulk_read()` and `regmap_bulk_write()` violates SPI subsystem constraints.
- [Medium] A partially uninitialized stack variable is read, which triggers Kernel Memory Sanitizer (KMSAN) warnings and constitutes undefined behavior.
- [Low] Implicit pointer conversion from `int *` to `u32 *` causes a `-Wincompatible-pointer-types` compiler warning.
- [Low] The `calib_range` array is missing a `const` qualifier.
--

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

iio: adc: ti-ads1262: Add calibration support

This commit adds channel calibration support to the ti-ads1262 ADC driver
by implementing scale and bias calibration via regmap bulk operations.

> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 6d5f22836ad8b..b33505e7fdc7a 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -453,6 +457,32 @@ static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode)
>  	return 0;
>  }
>  
> +static int ads1262_read_calib(struct ads1262 *st, unsigned int reg, u32 *val)
> +{
> +	__le32 lval;
> +	int ret;
> +
> +	/*
> +	 * The calibration word is a signed 24 bit LSB-first value.
> +	 */
> +	ret = regmap_bulk_read(st->regmap, reg, &lval, 3);

[Severity: High]
Does this safely handle DMA transfers? Passing a stack-allocated variable
like lval to regmap_bulk_read() eventually initiates an SPI DMA transfer.
Using stack memory for DMA transfers can cause spi_map_buf() to fail with
-EINVAL on systems with vmap stacks, or lead to memory corruption due to
cache line tearing on architectures without hardware cache coherency.

> +	if (ret)
> +		return ret;
> +	*val = sign_extend32(le32_to_cpu(lval), 23);

[Severity: Medium]
Is it possible this reads uninitialized memory? The stack variable lval is
declared without initialization. When passed to regmap_bulk_read() above,
only the first 3 bytes are populated. Calling le32_to_cpu(lval) will then
read all 4 bytes, including the uninitialized 4th byte, which can trigger a
KMSAN use-of-uninitialized-value warning.

> +
> +	return 0;
> +}
> +
> +static int ads1262_write_calib(struct ads1262 *st, unsigned int reg, u32 val)
> +{
> +	__le32 lval = cpu_to_le32(val);
> +
> +	/*
> +	 * The calibration word is a signed 24 bit LSB-first value.
> +	 */
> +	return regmap_bulk_write(st->regmap, reg, &lval, 3);

[Severity: High]
Can this also cause issues with SPI DMA? Similar to the read function,
passing the stack variable lval to regmap_bulk_write() routes a stack
address into the SPI subsystem, violating DMA constraints.

> +}
[ ... ]
> @@ -673,6 +703,18 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
>  		*val2 = ads1262_conv_delay_avail[mode][1];
>  		return IIO_VAL_INT_PLUS_NANO;
>  
> +	case IIO_CHAN_INFO_CALIBSCALE:
> +		ret = ads1262_read_calib(st, ADS1262_FSCAL0_REG, val);

[Severity: Low]
Will this cause a compiler warning for incompatible pointer types?
The val parameter provided by the IIO subsystem to ads1262_read_raw() is
an int pointer, but ads1262_read_calib() explicitly expects a u32 pointer.
Since Linux 6.8, incompatible pointer type warnings are treated as fatal
errors and can break the build.

> +		if (ret)
> +			return ret;
> +		return IIO_VAL_INT;
> +
[ ... ]
> @@ -682,6 +724,9 @@ 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)
>  {
> +	static int calib_range[3] = { ADS1262_CALIB_WORD_MIN, 1,
> +				      ADS1262_CALIB_WORD_MAX };

[Severity: Low]
Should this array be declared as const? Omitting the const qualifier places
calib_range in the mutable data section instead of the read-only rodata
section, leaving it unprotected against accidental runtime modifications.

> +
>  	switch (mask) {

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=4

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

* Re: [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver
  2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja
@ 2026-06-12 23:11   ` sashiko-bot
  0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-06-12 23:11 UTC (permalink / raw)
  To: Kurt Borja; +Cc: devicetree, conor+dt, robh

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [High] Probe silently succeeds with corrupted state when `ti,refmux` property is out of bounds.
- [High] Unportable C bitfields used for hardware registers break big-endian architectures.
- [High] Missing `select AUXILIARY_BUS` in Kconfig causes a build failure.
- [High] Heap out-of-bounds access if only the timestamp channel is enabled in buffer mode.
- [Medium] Dead code in `ads1263_adc2_write_raw()` silently prevents hardware updates on the fly (hot reload).
- [Medium] Permanent `fwnode_handle` resource leak on the error path of `ads1262_aux_device_setup()`.
- [Low] Semantic API misuse: releasing direct mode lock instead of buffer mode lock.
--

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

iio: adc: Add ti-ads1263-adc2 driver

The TI ADS1263 includes an auxiliary, 24-bit, delta-sigma ADC (ADC2) which
operates independently of ADC1. This patch adds support for ADC2 as an
independent IIO device using the auxiliary bus API.

> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1809,6 +1809,19 @@ config TI_ADS1262
>  	  This driver can also be built as a module. If so, the module will be
>  	  called ti-ads1262.
>  
> +config TI_ADS1263_ADC2
> +	tristate "Texas Instruments ADS1263 auxiliary ADC (ADC2) driver"
> +	depends on TI_ADS1262
> +	select AUXILIARY_BUS

[Severity: High]
Since ti-ads1262.c unconditionally calls auxiliary bus APIs like
auxiliary_device_init(), does config TI_ADS1262 also need to
select AUXILIARY_BUS? 

If CONFIG_TI_ADS1262=y and CONFIG_TI_ADS1263_ADC2=n, the auxiliary bus code
won't be compiled, which could result in undefined reference linker errors
when building the main driver.

[ ... ]
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.h
> @@ -0,0 +1,39 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +/*
> + * Texas Instruments ADS1262 ADC driver
> + *
> + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com>
> + */
> +
> +#ifndef _ADS1262_H_
> +#define _ADS1262_H_
> +
> +#include <linux/auxiliary_bus.h>
> +#include <linux/types.h>
> +
> +struct ads1263_adc2_channel {
> +	/* ADC2CFG */
> +	u8 gain:3;
> +	u8 refmux:3;
> +	u8 data_rate:2;
> +
> +	/* ADC2MUX */
> +	u8 negative_input:4;
> +	u8 positive_input:4;
> +};

[Severity: High]
Will this structure layout cause issues on big-endian architectures? 

The memory layout of C bitfields is implementation-defined, and this
structure appears to be written directly to the SPI hardware using
regmap_bulk_write() in ads1263_adc2_enable(). On big-endian systems, the
bits could be packed in reverse order and write invalid configuration data.

[ ... ]
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> +static int ads1262_aux_device_setup(struct ads1262 *st)
> +{
[ ... ]
> +	adev->dev.parent = dev;
> +	device_set_node(&adev->dev, no_free_ptr(node));
> +
> +	ret = auxiliary_device_init(adev);
> +	if (ret)
> +		goto out_free_channels;
> +
> +	ret = auxiliary_device_add(adev);
> +	if (ret) {
> +		auxiliary_device_uninit(adev);
> +		return ret;
> +	}
> +
> +	return devm_add_action_or_reset(dev, ads1262_aux_device_destroy, adev);
> +
> +out_free_channels:
> +	kfree(chans);
> +out_free_id:
> +	ida_free(&ads1262_ida, id);
> +out_free_adc2:
> +	kfree(ctx);
> +out_node_put:
> +	fwnode_handle_put(node);

[Severity: Medium]
Does this error path leak the fwnode reference? 

When device_set_node() is called above, no_free_ptr(node) unconditionally
sets the local node pointer to NULL. If auxiliary_device_init() later fails,
jumping to out_node_put will call fwnode_handle_put(NULL). This becomes a
no-op, leaving the reference permanently leaked.

[ ... ]
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1263-adc2.c
[ ... ]
> +static int ads1263_adc2_channel_hot_reload(struct ads1263_adc2 *st,
> +					   const struct iio_chan_spec *chan)
> +{
> +	struct ads1263_adc2_ctx *ctx = st->ctx;
> +	unsigned long i;
> +	int ret;
> +
> +	/* Hot reloading is only required on buffer mode */
> +	if (!iio_device_try_claim_buffer_mode(st->indio_dev))
> +		return 0;
> +
> +	i = find_first_bit(st->indio_dev->active_scan_mask,
> +			   iio_get_masklength(st->indio_dev));
> +	if (i != chan->scan_index) {
> +		iio_device_release_direct(st->indio_dev);

[Severity: Low]
Should this use iio_device_release_buffer_mode() instead of
iio_device_release_direct()? 

While both macros currently expand to the same internal unlock function,
this breaks the IIO API contract which expects symmetric lock and unlock
function pairs.

> +		return 0;
> +	}
[ ... ]
> +static int ads1263_adc2_write_raw(struct iio_dev *indio_dev,
> +				  struct iio_chan_spec const *chan,
> +				  int val, int val2, long mask)
> +{
[ ... ]
> +	switch (mask) {
> +	case IIO_CHAN_INFO_HARDWAREGAIN:
> +		for (i = 0; i < ARRAY_SIZE(ads1263_adc2_gain_avail); i++) {
> +			if (val == ads1263_adc2_gain_avail[i])
> +				break;
> +		}
> +		if (i == ARRAY_SIZE(ads1263_adc2_gain_avail))
> +			return -EINVAL;
> +
> +		mutex_lock(&ctx->chan_lock);
> +		chan_data->gain = i;
> +		mutex_unlock(&ctx->chan_lock);
> +
> +		return 0;

[Severity: Medium]
Will ads1263_adc2_channel_hot_reload() at the end of this function ever be
reached? 

Both the IIO_CHAN_INFO_HARDWAREGAIN and IIO_CHAN_INFO_SAMP_FREQ cases use
a return 0 instead of break after updating the software state. This causes
the function to exit immediately, which silently prevents hardware updates
during an active buffer mode.

> +
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		for (i = 0; i < ARRAY_SIZE(ads1263_adc2_data_rate_avail); i++) {
> +			if (val == ads1263_adc2_data_rate_avail[i])
> +				break;
> +		}
> +		if (i == ARRAY_SIZE(ads1263_adc2_data_rate_avail))
> +			return -EINVAL;
> +
> +		mutex_lock(&ctx->chan_lock);
> +		chan_data->data_rate = i;
> +		mutex_unlock(&ctx->chan_lock);
> +
> +		return 0;
> +
> +	default:
> +		return -EOPNOTSUPP;
> +	}
> +
> +	return ads1263_adc2_channel_hot_reload(st, chan);
> +}
[ ... ]
> +static int ads1263_adc2_buffer_preenable(struct iio_dev *indio_dev)
> +{
> +	struct ads1263_adc2 *st = iio_priv(indio_dev);
> +	struct ads1263_adc2_ctx *ctx = st->ctx;
> +	struct device *dev = &ctx->adev.dev;
> +	unsigned long i;
> +	int ret;
> +
> +	ret = pm_runtime_resume_and_get(dev->parent);
> +	if (ret)
> +		return ret;
> +
> +	i = find_first_bit(indio_dev->active_scan_mask,
> +			   iio_get_masklength(indio_dev));
> +	ret = ctx->enable(ctx, &ctx->channels[i]);

[Severity: High]
Is there a potential out-of-bounds array access here if only the timestamp
channel is enabled?

The iio_validate_scan_mask_onehot function allows a single channel to be
active. If the user only enables the timestamp channel, find_first_bit()
will return the timestamp channel's index, which is ctx->num_channels.

Since ctx->channels is allocated with exactly num_channels elements, accessing
ctx->channels[num_channels] reads out-of-bounds memory and passes that
data to the hardware via ctx->enable().

> +	if (ret)
> +		goto out_runtime_autosuspend;
> +
> +	ret = ctx->start(ctx);
> +	if (ret)
> +		goto out_runtime_autosuspend;
> +
> +	return 0;
[ ... ]
> +static int ads1263_adc2_regulator_setup(struct ads1263_adc2 *st)
> +{
> +	struct device *dev = &st->ctx->adev.dev;
> +	const char *reg_id, *propname;
> +	u32 refmux = 0;
> +	int ret;
> +
> +	propname = "ti,refmux";
> +	ret = device_property_read_u32(dev, propname, &refmux);
> +	if (refmux >= ADS1263_ADC2_REF2_COUNT)
> +		return dev_err_probe(dev, ret, "%s out of range\n", propname);

[Severity: High]
Does this silently succeed on failure? 

If the ti,refmux value exceeds ADS1263_ADC2_REF2_COUNT, the preceding
device_property_read_u32() was successful, meaning ret is 0. 

Because ret is 0, dev_err_probe() will return 0, causing the probe to
incorrectly succeed with an uninitialized vref_uV value. This could lead
to a divide-by-zero during scale calculations.

> +	st->refmux = refmux;
> +
> +	if (refmux == ADS1263_ADC2_REF2_INTER) {
> +		/* The internal voltage reference is 2.5 V */
> +		st->vref_uV = 2500000;
> +		return 0;
> +	}
[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=5

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

* Re: [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support
  2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
                   ` (4 preceding siblings ...)
  2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja
@ 2026-06-12 23:50 ` David Lechner
  2026-06-13  0:06   ` Kurt Borja
  5 siblings, 1 reply; 13+ messages in thread
From: David Lechner @ 2026-06-12 23:50 UTC (permalink / raw)
  To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Linus Walleij, Bartosz Golaszewski
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel, linux-gpio

On 6/12/26 5:46 PM, Kurt Borja wrote:
> 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.
> 
> The main ADC has quite a few features supported the main driver
> (ti-ads1262), including:
> 
>   - Power management
>   - IIO direct and buffer modes
>   - Channel hot-reloading
>   - Internal or external oscillator
>   - Internal or external voltage reference
>   - Filter configuration
>   - Sensor bias configuration
>   - IDAC configuration
>   - Level-shift voltage configuration
>   - Manual calibration support
>   - GPIO controller capabilities
> 
> I plan to add these features to the main driver soon:
> 
>   - SPI offload support (38400 SPS turns out to be too high for some
>     systems)
>   - User triggered, automatic calibration (Datasheet 9.4.9)
> 
> Additionally, full support for the (less capable) auxiliary ADC is
> introduced by the auxiliary ti-ads1263-adc2 driver included in this
> series.
> 
> 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-explainatory. 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>
> ---
> Kurt Borja (5):
>       dt-bindings: iio: adc: Add TI ADS126x ADC family
>       iio: adc: Add ti-ads1262 driver
>       iio: adc: ti-ads1262: Add GPIO controller support
>       iio: adc: ti-ads1262: Add calibration support
>       iio: adc: Add ti-ads1263-adc2 driver
> 
>  .../devicetree/bindings/iio/adc/ti,ads1262.yaml    |  308 +++
>  .../bindings/iio/adc/ti,ads1263-adc2.yaml          |   49 +
>  MAINTAINERS                                        |   10 +
>  drivers/iio/adc/Kconfig                            |   26 +
>  drivers/iio/adc/Makefile                           |    2 +
>  drivers/iio/adc/ti-ads1262.c                       | 2180 ++++++++++++++++++++
>  drivers/iio/adc/ti-ads1262.h                       |   39 +
>  drivers/iio/adc/ti-ads1263-adc2.c                  |  470 +++++
>  8 files changed, 3084 insertions(+)
> ---
> base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e
> change-id: 20251129-ads126x-fb6107505cae
> 

Hi Kurt,

I'm currently working on the TI ADS112C14 family of chips which
are functionally very similar (although have a bit of a different
register map).

I have some different ideas for the devicetree bindings that I
think will make it a bit more flexible. Given how similar the
chips are, I think we will want to align on how we do these (and
there was one more similar, and thankfully much simpler, TI ADC
driver submitted this week too!).

So rather that looking at your stuff too closely yet, I will send
what I have next week and we can compare notes then.



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

* Re: [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support
  2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner
@ 2026-06-13  0:06   ` Kurt Borja
  0 siblings, 0 replies; 13+ messages in thread
From: Kurt Borja @ 2026-06-13  0:06 UTC (permalink / raw)
  To: David Lechner, Kurt Borja, Jonathan Cameron, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	Bartosz Golaszewski
  Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
	linux-kernel, linux-gpio

On Fri Jun 12, 2026 at 6:50 PM -05, David Lechner wrote:
> On 6/12/26 5:46 PM, Kurt Borja wrote:
>> 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.
>> 
>> The main ADC has quite a few features supported the main driver
>> (ti-ads1262), including:
>> 
>>   - Power management
>>   - IIO direct and buffer modes
>>   - Channel hot-reloading
>>   - Internal or external oscillator
>>   - Internal or external voltage reference
>>   - Filter configuration
>>   - Sensor bias configuration
>>   - IDAC configuration
>>   - Level-shift voltage configuration
>>   - Manual calibration support
>>   - GPIO controller capabilities
>> 
>> I plan to add these features to the main driver soon:
>> 
>>   - SPI offload support (38400 SPS turns out to be too high for some
>>     systems)
>>   - User triggered, automatic calibration (Datasheet 9.4.9)
>> 
>> Additionally, full support for the (less capable) auxiliary ADC is
>> introduced by the auxiliary ti-ads1263-adc2 driver included in this
>> series.
>> 
>> 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-explainatory. 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>
>> ---
>> Kurt Borja (5):
>>       dt-bindings: iio: adc: Add TI ADS126x ADC family
>>       iio: adc: Add ti-ads1262 driver
>>       iio: adc: ti-ads1262: Add GPIO controller support
>>       iio: adc: ti-ads1262: Add calibration support
>>       iio: adc: Add ti-ads1263-adc2 driver
>> 
>>  .../devicetree/bindings/iio/adc/ti,ads1262.yaml    |  308 +++
>>  .../bindings/iio/adc/ti,ads1263-adc2.yaml          |   49 +
>>  MAINTAINERS                                        |   10 +
>>  drivers/iio/adc/Kconfig                            |   26 +
>>  drivers/iio/adc/Makefile                           |    2 +
>>  drivers/iio/adc/ti-ads1262.c                       | 2180 ++++++++++++++++++++
>>  drivers/iio/adc/ti-ads1262.h                       |   39 +
>>  drivers/iio/adc/ti-ads1263-adc2.c                  |  470 +++++
>>  8 files changed, 3084 insertions(+)
>> ---
>> base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e
>> change-id: 20251129-ads126x-fb6107505cae
>> 
>
> Hi Kurt,
>
> I'm currently working on the TI ADS112C14 family of chips which
> are functionally very similar (although have a bit of a different
> register map).
>
> I have some different ideas for the devicetree bindings that I
> think will make it a bit more flexible. Given how similar the
> chips are, I think we will want to align on how we do these (and
> there was one more similar, and thankfully much simpler, TI ADC
> driver submitted this week too!).
>
> So rather that looking at your stuff too closely yet, I will send
> what I have next week and we can compare notes then.

Hi David,

Of course!

Devicetree bindings were a bit of a pain for me. I definitely want to
check your approach, specially on the IDAC stuff. The ADS1262 has many
pin configurations. I even considered a pinctrl driver for it, which
would have taken care of IDAC, biases, etc.

I plan to work on sashiko's feedback this weekend and submit next week.
Won't make huge changes, just fix some rookie mistakes... :/

-- 
Thanks,
 ~ Kurt

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

end of thread, other threads:[~2026-06-13  0:06 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja
2026-06-12 22:53   ` sashiko-bot
2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja
2026-06-12 23:01   ` sashiko-bot
2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja
2026-06-12 22:59   ` sashiko-bot
2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja
2026-06-12 23:02   ` sashiko-bot
2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja
2026-06-12 23:11   ` sashiko-bot
2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner
2026-06-13  0:06   ` Kurt Borja

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