Linux IIO development
 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; 23+ 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] 23+ 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-13 18:54   ` Krzysztof Kozlowski
  2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 23+ 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] 23+ 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-13 13:45   ` Jonathan Cameron
  2026-06-13 18:59   ` Krzysztof Kozlowski
  2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja
                   ` (3 subsequent siblings)
  5 siblings, 2 replies; 23+ 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] 23+ 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-13  6:23   ` Kurt Borja
  2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 23+ 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] 23+ 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-13 13:50   ` Jonathan Cameron
  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; 23+ 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] 23+ 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-13 14:10   ` Jonathan Cameron
  2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner
  5 siblings, 1 reply; 23+ 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] 23+ 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; 23+ 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] 23+ 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; 23+ 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] 23+ 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-13  6:23   ` Kurt Borja
  0 siblings, 0 replies; 23+ messages in thread
From: Kurt Borja @ 2026-06-13  6:23 UTC (permalink / raw)
  To: Kurt Borja, 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

On Fri Jun 12, 2026 at 5:46 PM -05, Kurt Borja wrote:
> 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>

After going through Sashiko's feedback I realized this patch wasn't
properly tested and the gpiochip callbacks were carelessly implemented.

I'll do better for the next version and I apologize in advance for the
time lost.

-- 
Thanks,
 ~ Kurt

^ permalink raw reply	[flat|nested] 23+ 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-13 13:45   ` Jonathan Cameron
  2026-06-13 14:06     ` Jonathan Cameron
  2026-06-14 20:27     ` Kurt Borja
  2026-06-13 18:59   ` Krzysztof Kozlowski
  1 sibling, 2 replies; 23+ messages in thread
From: Jonathan Cameron @ 2026-06-13 13:45 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	Bartosz Golaszewski, David Lechner, Nuno Sá, Andy Shevchenko,
	linux-iio, devicetree, linux-kernel, linux-gpio

On Fri, 12 Jun 2026 17:46:20 -0500
Kurt Borja <kuurtb@gmail.com> wrote:

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

That is rather too big. I think you'll have to work out how to split this
up into more manageable chunks.  Staying under a 1000 (preferably a lot less)
per patch makes it much easier for people to review.

Given the complexity of the device this might be one that has to go
in as several series, building up functionality as we go.

I'll ignore all the DT stuff as sounds like that may radically change and
just take a fairly superficial first look at this.

Jonathan


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

Fairly unusual to see that header in a driver.
What's it here for?

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


> +/* IDACMAG constants */
> +#define ADS1262_IDACMAG_OFF			0
> +#define ADS1262_IDACMAG_COUNT			11
> +
> +/* REFMUX constants */

Naming is good enough I'm not sure I'd bother with the comments
to say what these are.

On option is to just group them with the register they are about
and using extra indenting to visually separate them from the register

#define ADS1262_REFMUX_REG			0xxx
#define   ADS1262_REFMUX_RMUXP_MASK		GENMASK(5, 3)
#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
However, if you are going to have a terminating entry, an anonymous enum might be better
with that just as the last item.

#define   ADS1262_REFMUX_RMUXN_MASK		GENMASK(2, 0)


> +#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 {

As a general rule we tend to avoid bitfields because of all the problems
with how loose the C spec is on how these actually get laid out.
I'd just have this as a suitable 32 bit value and then have
defines for masks within that.

> +	/* 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;
I think this is always accessed as a __be32. If so just type it as that.

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

I guess Sashiko already moaned about unaligned accesses (I'm too lazy to check :)
You may have to always memcpy this to a local variable rather than accessing
it directly.

> +		} __packed holding_reg;
> +	};
> +};

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

Given how similar these two are (assuming they don't get more complex
later) perhaps just combine them into one function, or use a shared function
plus trivial wrappers.

> +
> +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]));
maybe use

	return spi_write_then_read(st->spi, opcode, sizeof(opcode), NULL, 0);

Might as well use the helpers that bounce buffer in cases like this rather
than rolling our own.

> +}

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

Look at IIO_ACQUIRE_DIRECT_MODE()

That should let you simplify the error paths in here.
I see you used it elsewhere.

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

Trivial: Probably move val to line above.

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

I'd be tempted to do these two as part of declarations.

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

Might as well combine these lines and avoid need for local cnv.

		*VAL = sign_extend32(be32_to_cpu(raw), 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);

Use the NANO define probably to avoid need to zero count when reviewing.

> +		*val2 = rem;
> +
> +		return IIO_VAL_INT_PLUS_NANO;

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

Are either scan_buffer or scan_sz ever checked?  If not I would not
bother clearing them as their state doesn't matter when we aren't
in buffered mode.  Maybe there is a small argument that clearing them
might help with debug but I don't see that as a worth the implication that
something might be gated on these.

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

Given this can be trivially triggered from userspace - if you want to have
a print here, use a rate limited version.

> +		return iio_validate_scan_mask_onehot(indio_dev, scan_mask);
> +	}
> +
> +	return true;
> +}

> +
> +static int ads1262_fill_buffer_mult(struct ads1262 *st)
> +{
> +	__be32 val, *scan_buffer = st->scan_buffer;

Avoid mixing pointer and no point, or anything with assignments
as it makes the code harder to read.

> +	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.
> +	 */

Just to check: Is SPI traffic on the same bus to a different device fine?
If not you'd need spi_bus_lock(). If it is fine then reword this to talk about
communications with this device just to avoid confusion.

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

It's fine to go a little past 80 chars on a line if it helps readabiilty.
The harder limit is 100 chars though.

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

Given, at somepoint I would guess you'll want to support the auxiliary adc
on the 1263, I'd start with a struct chip_info  (with the name in there)
and pick that rather than just the name here.

> +		break;
> +	case ADS1262_DEV_ID_ADS1263:
Not particularly important but common practice to just change the prefix
for anything device specific.
	case ADS1263_DEV_ID

> +		*name = "ads1263";
> +		break;
> +	default:
> +		*name = "ads1262";
Given we'll ultimately want fallback compatibles to work and so allow
for firmware to specify which device to fallback to, this should really be
using the guidance from firmware to select rather than always guessing
the 1262 variant.  That is safe though given the 'subset' nature so this
doesn't matter as much as it normally does.

> +		dev_dbg(dev, "Failed to identify device with ID 0x%x\n", val);
> +	}
> +
> +	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);

regmap_clear_bits()

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

> +		if (ret)
> +			return ret;
> +	}

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

Whilst not duplicating the string con_id is nice, it does break grepping for error
messages. So I'd just duplicate it.

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

I'd be tempted to just duplicate the print in the interests of simpler code flow.
Would avoid the need for a local variable to stash 'last one I tried'.


> +}

> +
> +static const struct reg_default ads1262_reg_defaults[] = {
> +	{ ADS1262_POWER_REG,		0x11 },

Is it sensible to specify these in terms of the fields that make them up?
Can make it easier to see what the default state actually means.
Sometimes it is just too complex, so we don't bother.

> +	{ 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 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" },

Named initializers please.  Uwe is driving through updating
all the existing drivers to use them (various reasons to do that
but simplest one is it's inconsistent with what we have always done
for of_device_id!)

> +	{ }
> +};
> +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>");
> 


^ permalink raw reply	[flat|nested] 23+ 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-13 13:50   ` Jonathan Cameron
  2026-06-14 20:31     ` Kurt Borja
  0 siblings, 1 reply; 23+ messages in thread
From: Jonathan Cameron @ 2026-06-13 13:50 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	Bartosz Golaszewski, David Lechner, Nuno Sá, Andy Shevchenko,
	linux-iio, devicetree, linux-kernel, linux-gpio

On Fri, 12 Jun 2026 17:46:22 -0500
Kurt Borja <kuurtb@gmail.com> wrote:

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

Single line comment.  However, it's also fairly clear from the code, so maybe
no comment at all. 

> +	 */
> +	ret = regmap_bulk_read(st->regmap, reg, &lval, 3);

Read it into a u8 [3] and use get_unaligned_le24()
That avoids us having to think too much about the bits that aren't
initialized and static analysis / compilers having to figure out
they don't matter. I general it is easier to understand.

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

Similar with a u8 [3] for the '__le24' storage.

> +}
> +



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

* Re: [PATCH 2/5] iio: adc: Add ti-ads1262 driver
  2026-06-13 13:45   ` Jonathan Cameron
@ 2026-06-13 14:06     ` Jonathan Cameron
  2026-06-14 20:27     ` Kurt Borja
  1 sibling, 0 replies; 23+ messages in thread
From: Jonathan Cameron @ 2026-06-13 14:06 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	Bartosz Golaszewski, David Lechner, Nuno Sá, Andy Shevchenko,
	linux-iio, devicetree, linux-kernel, linux-gpio


> 
> > +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;  
> 
> Look at IIO_ACQUIRE_DIRECT_MODE()
obviously that comment is garbage  - so please ignore!

> 
> That should let you simplify the error paths in here.
> I see you used it elsewhere.

^ permalink raw reply	[flat|nested] 23+ 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-13 14:10   ` Jonathan Cameron
  2026-06-14 20:43     ` Kurt Borja
  0 siblings, 1 reply; 23+ messages in thread
From: Jonathan Cameron @ 2026-06-13 14:10 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	Bartosz Golaszewski, David Lechner, Nuno Sá, Andy Shevchenko,
	linux-iio, devicetree, linux-kernel, linux-gpio

On Fri, 12 Jun 2026 17:46:23 -0500
Kurt Borja <kuurtb@gmail.com> wrote:

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

A few things inline.

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

> 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

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

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

> 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 {

As with earlier patch avoid bitfields where layout matters.
Better to just use defines for the field makss.

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

I'm not sure I see this loose coupling as that useful. I'd just export the
functions from the other module and add them to this header.
Maybe I'm missing why you need this complexity.
> +};
> +
> +#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

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

Is that really an unaligned be24?  Might be better to handle it as such.

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

NANO

> +		*val2 = rem;

> +static int
This is oddly different to formatting of most other functions.
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)
Seems to be under 80 chars + for this sort of thing, going a bit
over is fine anyway.

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

Slightly nicer to add scope got each cases block and then do just guard() for these

> +		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_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;
Rather than using a template like this I'd just set it all here using
a designated initializer.  Means there is one place to see all the fields.

		chns[i] = (struct iio_chan_spec) {
			.type = IIO_VOLTAGE,
			.indexed = true,
			.differential = true, //not sure why this wasn't in your template.
			.channel = ctx->channels[i].positive_input;
			.channel2 = ctx->channels[i].negative_input;
			.scan_index = i,
			.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),
		}
> +		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);
That macro has recently become a designated intializer so
	chns[i] = IIO_CHAN_SOFT_TIMESTAMP(ctx->num_channels - 1);

> +	chns[i].scan_index = i;

Isn't this just overwriting the ctx->num_channels - 1 we just
passed in above?

> +
> +	indio_dev->num_channels = ctx->num_channels + 1;
> +	indio_dev->channels = chns;
> +
> +	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;

See below.

> +	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" },
	{
		.name = "ti_ads1262.ads1263_adc2",
	  	.driver_data = (kernel_ulong_t)"ads1263_adc2",

Though I really don't like forcing that cast in there and it should be irrelevant
given there is only one entry in this table.  Should be fine to just hard code that
where used.  If you need this later, wrap it in a structure.

	},

> +	{ }
> +};
> +MODULE_DEVICE_TABLE(auxiliary, ads1263_adc2_auxiliary_match);



^ permalink raw reply	[flat|nested] 23+ 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-13 18:54   ` Krzysztof Kozlowski
  2026-06-14 20:53     ` Kurt Borja
  0 siblings, 1 reply; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-13 18:54 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski, David Lechner, Nuno Sá,
	Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio

On Fri, Jun 12, 2026 at 05:46:19PM -0500, Kurt Borja wrote:
> +  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

There is no such syntax, drop.

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

Not a separate device node. Fold into the parent... or explain in
commit msg. You have entire commit msg to explain odd things.

In that binding description you call it "independent", so it should have
its own SPI chip select? Why "independent" and part of this binding?
Maybe not independent, so basically part of this device?

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

patternProps always follow properties. Please open example-schema for
template.

> +  "^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

Use string enum.

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

Use string enum.

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

Heavily incomplete... Drop the sub node.

Best regards,
Krzysztof


^ permalink raw reply	[flat|nested] 23+ 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-13 13:45   ` Jonathan Cameron
@ 2026-06-13 18:59   ` Krzysztof Kozlowski
  2026-06-14 13:39     ` Jonathan Cameron
  2026-06-14 20:56     ` Kurt Borja
  1 sibling, 2 replies; 23+ messages in thread
From: Krzysztof Kozlowski @ 2026-06-13 18:59 UTC (permalink / raw)
  To: Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski, David Lechner, Nuno Sá,
	Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio

On Fri, Jun 12, 2026 at 05:46:20PM -0500, Kurt Borja wrote:
> +
> +	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";

Nope, see further

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

Nope

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

Nope.

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

Don't do such syntax. You make git grep unnecesssary difficult.

> +	device_property_read_u32(dev, prop, &mux[1]);

And this shows in `git grep` as completely pointless code.

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

Nope.

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

Functions used by probe() should be before probe(), not somewhere in the
middle of the code. IOW, entire probe is together.
...

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

So devices are fully compatible? Then it should be expressed in the
binding and drop one entry here.

Best regards,
Krzysztof


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

* Re: [PATCH 2/5] iio: adc: Add ti-ads1262 driver
  2026-06-13 18:59   ` Krzysztof Kozlowski
@ 2026-06-14 13:39     ` Jonathan Cameron
  2026-06-14 20:56     ` Kurt Borja
  1 sibling, 0 replies; 23+ messages in thread
From: Jonathan Cameron @ 2026-06-14 13:39 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Kurt Borja, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski, David Lechner, Nuno Sá,
	Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio


> > +
> > +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" },  
> 
> So devices are fully compatible? Then it should be expressed in the
> binding and drop one entry here.

They aren't. It's relying on one of them having a subnode that spins up an
auxdev for the hardware block they don't share.  A fallback would be fine
but (to the device that has the more minimal feature set). I'd prefer the
driver to have a check on whether the subnode is allowed before blindly
registering it.

Jonathan


> 
> Best regards,
> Krzysztof
> 


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

* Re: [PATCH 2/5] iio: adc: Add ti-ads1262 driver
  2026-06-13 13:45   ` Jonathan Cameron
  2026-06-13 14:06     ` Jonathan Cameron
@ 2026-06-14 20:27     ` Kurt Borja
  1 sibling, 0 replies; 23+ messages in thread
From: Kurt Borja @ 2026-06-14 20:27 UTC (permalink / raw)
  To: Jonathan Cameron, Kurt Borja
  Cc: Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij,
	Bartosz Golaszewski, David Lechner, Nuno Sá, Andy Shevchenko,
	linux-iio, devicetree, linux-kernel, linux-gpio

Hi Jonathan,

On Sat Jun 13, 2026 at 8:45 AM -05, Jonathan Cameron wrote:
> On Fri, 12 Jun 2026 17:46:20 -0500
> Kurt Borja <kuurtb@gmail.com> wrote:
>
>> 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 ++++++++++++++++++++++++++++++++++++++++++
>
> That is rather too big. I think you'll have to work out how to split this
> up into more manageable chunks.  Staying under a 1000 (preferably a lot less)
> per patch makes it much easier for people to review.
>
> Given the complexity of the device this might be one that has to go
> in as several series, building up functionality as we go.

I'll split it up as much as possible for next version.

I was thinking of taking out the hot-reloading stuff for a follow-up
series. In that case I would also add IIO_ACQUIRE_BUFFER_MODE().
What do you think?

>
> I'll ignore all the DT stuff as sounds like that may radically change and
> just take a fairly superficial first look at this.

Yes, I will just address Krzysztof comments and leave that patch until
we can discuss it with David.

>
> Jonathan
>

[...]

>> +#include <linux/lockdep.h>
>
> Fairly unusual to see that header in a driver.
> What's it here for?

I included it for lockdep_assert_held().

[...]

>> +/* IDACMAG constants */
>> +#define ADS1262_IDACMAG_OFF			0
>> +#define ADS1262_IDACMAG_COUNT			11
>> +
>> +/* REFMUX constants */
>
> Naming is good enough I'm not sure I'd bother with the comments
> to say what these are.
>
> On option is to just group them with the register they are about
> and using extra indenting to visually separate them from the register
>
> #define ADS1262_REFMUX_REG			0xxx
> #define   ADS1262_REFMUX_RMUXP_MASK		GENMASK(5, 3)
> #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

I like this...

> However, if you are going to have a terminating entry, an anonymous enum might be better
> with that just as the last item.

...but this sounds good too. I'll go for what looks more organized.

>
> #define   ADS1262_REFMUX_RMUXN_MASK		GENMASK(2, 0)
>
>
>> +#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 {
>
> As a general rule we tend to avoid bitfields because of all the problems
> with how loose the C spec is on how these actually get laid out.
> I'd just have this as a suitable 32 bit value and then have
> defines for masks within that.

Are you suggesting storing this whole struct data as a u32 and
reading/writing with FIELD_*() helpers? I think that may be less
readable but it would save memory. I don't know if I understood
correctly though.

I'm dropping the bitfield approach for next version anyway.

[...]

>> +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;
> I think this is always accessed as a __be32. If so just type it as that.

I was hesitant to do that because of the space reserved at the end for
the timestamp. Didn't feel right to assign __be32 when it would actually
be something like

	struct {
		__be32 buff;
		aligned_s64 ts;
	};

But I have no problem doing it.

[...]

>> +static int ads1262_fill_buffer_mult(struct ads1262 *st)
>> +{
>> +	__be32 val, *scan_buffer = st->scan_buffer;
>
> Avoid mixing pointer and no point, or anything with assignments
> as it makes the code harder to read.
>
>> +	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.
>> +	 */
>
> Just to check: Is SPI traffic on the same bus to a different device fine?
> If not you'd need spi_bus_lock(). If it is fine then reword this to talk about
> communications with this device just to avoid confusion.

Yes, to a different device is fine. I'll reword it.

[...]

>> +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";
>
> Given, at somepoint I would guess you'll want to support the auxiliary adc
> on the 1263, I'd start with a struct chip_info  (with the name in there)
> and pick that rather than just the name here.

Makes sense. In that case I can add a dev_warn if the name doesn't match
the internal model. Would that be ok or would you prefer dev_dbg?

>
>> +		break;
>> +	case ADS1262_DEV_ID_ADS1263:
> Not particularly important but common practice to just change the prefix
> for anything device specific.
> 	case ADS1263_DEV_ID

Good to know!

>
>> +		*name = "ads1263";
>> +		break;
>> +	default:
>> +		*name = "ads1262";
> Given we'll ultimately want fallback compatibles to work and so allow
> for firmware to specify which device to fallback to, this should really be
> using the guidance from firmware to select rather than always guessing
> the 1262 variant.  That is safe though given the 'subset' nature so this
> doesn't matter as much as it normally does.

Agreed.

[...]

>> +static const struct reg_default ads1262_reg_defaults[] = {
>> +	{ ADS1262_POWER_REG,		0x11 },
>
> Is it sensible to specify these in terms of the fields that make them up?
> Can make it easier to see what the default state actually means.
> Sometimes it is just too complex, so we don't bother.

I prefer not to do it because it would be too complex. I'll try though.

[...]

>> +MODULE_DESCRIPTION("Texas Instruments ADS1262 ADC driver");
>> +MODULE_LICENSE("GPL");
>> +MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>");
>> 

Ack to the rest of comments!

-- 
Thanks,
 ~ Kurt


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

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

On Sat Jun 13, 2026 at 8:50 AM -05, Jonathan Cameron wrote:
> On Fri, 12 Jun 2026 17:46:22 -0500
> Kurt Borja <kuurtb@gmail.com> wrote:
>
>> Add channel calibration support.
>> 
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---

[...]

> Read it into a u8 [3] and use get_unaligned_le24()
> That avoids us having to think too much about the bits that aren't
> initialized and static analysis / compilers having to figure out
> they don't matter. I general it is easier to understand.

I didn't know there was a 24 bit version. I'll definitely use it.

>
>> +	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);
>
> Similar with a u8 [3] for the '__le24' storage.
>
>> +}
>> +

-- 
Thanks,
 ~ Kurt

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

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

On Sat Jun 13, 2026 at 9:10 AM -05, Jonathan Cameron wrote:
> On Fri, 12 Jun 2026 17:46:23 -0500
> Kurt Borja <kuurtb@gmail.com> wrote:
>
>> 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.
>
> A few things inline.
>
>> 
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>
>> 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
>
>> +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);
> devm_mutex_init()

I actually call mutex_destroy() on device .release.

I think it makes more sense that way, otherwise we would UAF?

[...]

>> +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);
>
> I'm not sure I see this loose coupling as that useful. I'd just export the
> functions from the other module and add them to this header.
> Maybe I'm missing why you need this complexity.

I'll go for the exported (NS) functions. Much cleaner that way.

I don't know where did I read this callback approach was a way to handle
auxiliary devices and I got fixated on that.

[...]

>> +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;
> Rather than using a template like this I'd just set it all here using
> a designated initializer.  Means there is one place to see all the fields.
>
> 		chns[i] = (struct iio_chan_spec) {
> 			.type = IIO_VOLTAGE,
> 			.indexed = true,
> 			.differential = true, //not sure why this wasn't in your template.
> 			.channel = ctx->channels[i].positive_input;
> 			.channel2 = ctx->channels[i].negative_input;
> 			.scan_index = i,
> 			.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),

Sounds good to me.

> 		}
>> +		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);
> That macro has recently become a designated intializer so
> 	chns[i] = IIO_CHAN_SOFT_TIMESTAMP(ctx->num_channels - 1);
>
>> +	chns[i].scan_index = i;
>
> Isn't this just overwriting the ctx->num_channels - 1 we just
> passed in above?

It is. Thanks!

[...]

>> +static const struct auxiliary_device_id ads1263_adc2_auxiliary_match[] = {
>> +	{ .name = "ti_ads1262.ads1263_adc2",
>> +	  .driver_data = (kernel_ulong_t)"ads1263_adc2" },
> 	{
> 		.name = "ti_ads1262.ads1263_adc2",
> 	  	.driver_data = (kernel_ulong_t)"ads1263_adc2",
>
> Though I really don't like forcing that cast in there and it should be irrelevant
> given there is only one entry in this table.  Should be fine to just hard code that
> where used.  If you need this later, wrap it in a structure.

You're right. I'll add a NAME macro.

I don't think we'll ever add entries here.

>
> 	},
>
>> +	{ }
>> +};
>> +MODULE_DEVICE_TABLE(auxiliary, ads1263_adc2_auxiliary_match);

Thanks for your feedback, Jonathan! Apologies if this version was a
little rough... I'm a bit embarrased by the bugs found by Sashiko.

-- 
Thanks,
 ~ Kurt

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

* Re: [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family
  2026-06-13 18:54   ` Krzysztof Kozlowski
@ 2026-06-14 20:53     ` Kurt Borja
  2026-06-14 21:37       ` David Lechner
  0 siblings, 1 reply; 23+ messages in thread
From: Kurt Borja @ 2026-06-14 20:53 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski, David Lechner, Nuno Sá,
	Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio

Hi Krzysztof,

On Sat Jun 13, 2026 at 1:54 PM -05, Krzysztof Kozlowski wrote:
> On Fri, Jun 12, 2026 at 05:46:19PM -0500, Kurt Borja wrote:
>> +  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
>
> There is no such syntax, drop.

The "default: false" syntax? Sure I'll drop.

>
>> +
>> +  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#
>
> Not a separate device node. Fold into the parent... or explain in
> commit msg. You have entire commit msg to explain odd things.
>
> In that binding description you call it "independent", so it should have
> its own SPI chip select? Why "independent" and part of this binding?
> Maybe not independent, so basically part of this device?

It's independent in the sense that it is a proper subdevice on the same
chip. It shares the serial interface but operates completely in
parallel.

I decided to add a subnode because other devices might request their
io-channels and most importantly a different voltage reference might be
connected to it.

I'll clarify this in the commmit message on the next version. Although
after seeing this submitted bindings [1], I wonder if it's a better
approach to do something like

	spi@0 {
		mydevice@0 {
			...
			adc@0 { ... };
			adc@1 { ... };
		};
	};

Any thoughts?

> Best regards,
> Krzysztof

Ack to the rest of comments.

[1] https://lore.kernel.org/linux-iio/20260519-ad5529r-driver-v3-1-267c0731aa68@analog.com/

-- 
Thanks,
 ~ Kurt

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

* Re: [PATCH 2/5] iio: adc: Add ti-ads1262 driver
  2026-06-13 18:59   ` Krzysztof Kozlowski
  2026-06-14 13:39     ` Jonathan Cameron
@ 2026-06-14 20:56     ` Kurt Borja
  1 sibling, 0 replies; 23+ messages in thread
From: Kurt Borja @ 2026-06-14 20:56 UTC (permalink / raw)
  To: Krzysztof Kozlowski, Kurt Borja
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski, David Lechner, Nuno Sá,
	Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio

On Sat Jun 13, 2026 at 1:59 PM -05, Krzysztof Kozlowski wrote:

[...]

> Functions used by probe() should be before probe(), not somewhere in the
> middle of the code. IOW, entire probe is together.

I they all are, it's just that regmap stuff takes a huge chunk. I'll
check how to reorganize.

[...]

>> +static const struct of_device_id ads1262_of_match[] = {
>> +	{ .compatible = "ti,ads1262" },
>> +	{ .compatible = "ti,ads1263" },
>
> So devices are fully compatible? Then it should be expressed in the
> binding and drop one entry here.

Not fully compatible as Jonathan said. One is a subset of the other.

I'll make it more clear in the commit message.

>
> Best regards,
> Krzysztof

Ack to the rest of comments.

-- 
Thanks,
 ~ Kurt

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

* Re: [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family
  2026-06-14 20:53     ` Kurt Borja
@ 2026-06-14 21:37       ` David Lechner
  2026-06-14 21:57         ` Kurt Borja
  0 siblings, 1 reply; 23+ messages in thread
From: David Lechner @ 2026-06-14 21:37 UTC (permalink / raw)
  To: Kurt Borja, Krzysztof Kozlowski
  Cc: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Linus Walleij, Bartosz Golaszewski, Nuno Sá, Andy Shevchenko,
	linux-iio, devicetree, linux-kernel, linux-gpio

On 6/14/26 3:53 PM, Kurt Borja wrote:

...

>> Not a separate device node. Fold into the parent... or explain in
>> commit msg. You have entire commit msg to explain odd things.
>>
>> In that binding description you call it "independent", so it should have
>> its own SPI chip select? Why "independent" and part of this binding?
>> Maybe not independent, so basically part of this device?
> 
> It's independent in the sense that it is a proper subdevice on the same
> chip. It shares the serial interface but operates completely in
> parallel.
> 
> I decided to add a subnode because other devices might request their
> io-channels and most importantly a different voltage reference might be
> connected to it.
> 
> I'll clarify this in the commmit message on the next version. Although
> after seeing this submitted bindings [1], I wonder if it's a better
> approach to do something like
> 
> 	spi@0 {
> 		mydevice@0 {
> 			...
> 			adc@0 { ... };
> 			adc@1 { ... };
> 		};
> 	};
> 
> Any thoughts?

I don't see how this relates to the linked patch at all. The linked
patch looks just like a normal DAC binding.

What is the point of the 2nd ADC in this chip? Is it just to be able
to do simultaneous sampling of two different measurements at the same
time? We have other simultaneous sampling ADC chips and just model them
as a single device.

Since everything can be muxed to either ADC at runtime, I don't see
any reason the devicetree should care about it. Forcing certain pins
to be assigned to a certain ADC seems overly restrictive.

And unless you have an application that specifically needs it, I
wouldn't bother trying to implement the 2nd ADC in the IIO driver.
I didn't see any hints in the datasheet as to when it would actually
make sense to use this 2nd ADC. My first thought is that it might
make sense to use the 2nd ADC for a 2nd buffer so that you can do
2 buffered reads at the same time. But without knowing why this chip
was designed this way, I don't know if that is the right idea or not.


> Ack to the rest of comments.
> 
> [1] https://lore.kernel.org/linux-iio/20260519-ad5529r-driver-v3-1-267c0731aa68@analog.com/
> 


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

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

On Sun Jun 14, 2026 at 4:37 PM -05, David Lechner wrote:
> On 6/14/26 3:53 PM, Kurt Borja wrote:
>
> ...
>
>>> Not a separate device node. Fold into the parent... or explain in
>>> commit msg. You have entire commit msg to explain odd things.
>>>
>>> In that binding description you call it "independent", so it should have
>>> its own SPI chip select? Why "independent" and part of this binding?
>>> Maybe not independent, so basically part of this device?
>> 
>> It's independent in the sense that it is a proper subdevice on the same
>> chip. It shares the serial interface but operates completely in
>> parallel.
>> 
>> I decided to add a subnode because other devices might request their
>> io-channels and most importantly a different voltage reference might be
>> connected to it.
>> 
>> I'll clarify this in the commmit message on the next version. Although
>> after seeing this submitted bindings [1], I wonder if it's a better
>> approach to do something like
>> 
>> 	spi@0 {
>> 		mydevice@0 {
>> 			...
>> 			adc@0 { ... };
>> 			adc@1 { ... };
>> 		};
>> 	};
>> 
>> Any thoughts?
>
> I don't see how this relates to the linked patch at all. The linked
> patch looks just like a normal DAC binding.

Ah, wrong link. This is the correct one [1]. The suggestion just at the
end.

>
> What is the point of the 2nd ADC in this chip? Is it just to be able
> to do simultaneous sampling of two different measurements at the same
> time? We have other simultaneous sampling ADC chips and just model them
> as a single device.

It does simultaneous sampling of the same channel, as well as different
channels. Also the secondary ADC is only 24 bit instead of 32 bit, has a
different noise profile and has a different PGA configuration (goes up
to 128 gain, instead of 32).

Taken from the datasheet (Section 9.3.15):

	Use ADC2 to perform main channel (ADC1) cross-checking
	measurements (for example, diagnostics purposes and redundant
	channel measurements), system background measurements, or
	temperature compensation of the primary sensor (such as
	thermocouple cold junction compensation). Using data rates of
	10, 100, and 400 SPS for both ADCs, ADC2 performs virtual
	parallel conversions with ADC1 on the same input channel.

>
> Since everything can be muxed to either ADC at runtime, I don't see
> any reason the devicetree should care about it. Forcing certain pins
> to be assigned to a certain ADC seems overly restrictive.
>
> And unless you have an application that specifically needs it, I
> wouldn't bother trying to implement the 2nd ADC in the IIO driver.
> I didn't see any hints in the datasheet as to when it would actually
> make sense to use this 2nd ADC. My first thought is that it might
> make sense to use the 2nd ADC for a 2nd buffer so that you can do
> 2 buffered reads at the same time. But without knowing why this chip
> was designed this way, I don't know if that is the right idea or not.

I myself don't have an application for this feature. But I don't see why
not adding support for this feature, given that I already implemented a
driver (Patch 5) and is capable, as you said, of 2 buffered reads at the
same time.

I do believe I have to explain all this better in commit messages
though.

>
>
>> Ack to the rest of comments.
>> 
>> [1] https://lore.kernel.org/linux-iio/20260519-ad5529r-driver-v3-1-267c0731aa68@analog.com/
>> 

[1] https://lore.kernel.org/linux-iio/25mh6grzh7zh3b4uytcqnusyv5zjuf6ia4if3ce3oqzqz56ehi@le72iqv7ye3d/

-- 
Thanks,
 ~ Kurt

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

end of thread, other threads:[~2026-06-14 21:57 UTC | newest]

Thread overview: 23+ 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-13 18:54   ` Krzysztof Kozlowski
2026-06-14 20:53     ` Kurt Borja
2026-06-14 21:37       ` David Lechner
2026-06-14 21:57         ` Kurt Borja
2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja
2026-06-13 13:45   ` Jonathan Cameron
2026-06-13 14:06     ` Jonathan Cameron
2026-06-14 20:27     ` Kurt Borja
2026-06-13 18:59   ` Krzysztof Kozlowski
2026-06-14 13:39     ` Jonathan Cameron
2026-06-14 20:56     ` Kurt Borja
2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja
2026-06-13  6:23   ` Kurt Borja
2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja
2026-06-13 13:50   ` Jonathan Cameron
2026-06-14 20:31     ` Kurt Borja
2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja
2026-06-13 14:10   ` Jonathan Cameron
2026-06-14 20:43     ` Kurt Borja
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