* [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support
@ 2026-08-08 3:58 Kurt Borja
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
` (9 more replies)
0 siblings, 10 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, 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.
I plan to add these features to the main driver soon:
- Filters
- Calibration (manual and automatic)
- GPIO controller capabilities
- Channel hot-reloading in buffer mode
- SPI offload support (38400 SPS turns out to be too high for some
systems)
- Conversion delay support
- Full support for monitor channels
- The ti-ads1263-adc2 driver for ADC2
The auxiliary ADC operates almost completely independent of the main
ADC. The only consideration that has to be taken for interoperability is
when reading conversion data in direct mode (Datasheet 9.4.7.1), which
happens only in buffer mode, when multiple channels are enabled.
When reading data in direct mode, all SPI activity is forbidden between
the data-ready signal and the data retrieval. To achieve this a second
mutex called xfer_lock was introduced to block SPI activity on the
device.
This is one of the biggest drivers I've developed, so I hope the code
and the comments are self-explanatory. If not, please let me know so I
can clarify them.
As always, thanks for your reviews and help. Submitting upstream is
always a great learning experience :)
[1] https://www.ti.com/lit/ds/symlink/ads1263.pdf
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
v3:
dt-bindings
-----------
- Add interrupt-names property to support the dout/drdy DRDY pin
- Model vbias and refout as regulator providers under a "regulators"
node
- Removed ref{p,n}-supply in favor of refp[1-3]-supply and
refn[1-3]-supply, to match physical reference source pins.
- Add ti,refp[1-3]-refn[1-3]-resistor-ohms for boards where the
external reference is obtained from a resistor instead of being
driven by a supply
- Support single-channel and common-mode-channel
- Add common-mode-[N]-supply properties for pseudo-differential
inputs whose negative pin is tied to something other than ground
- Allow excitation-* arrays to have only one item
- Drop the "no connection" value from excitation-channels and the 0
value from excitation-current-nanoamp, both are expressed by
omitting the property or shortening the array
- Restrict diff-channels to 0-14: drop the "Float" entry.
- Add ti,reference-reversal
- Renamed ti,idac-chopping -> ti,idac-rotation to match datasheet
- Add avss-supply (negative analog supply) because it can actually be
below ground (min -2.5 V; max 0 V).
ti-ads1262
----------
- Split the v2 driver into several commits to aliviate review burden
- Conversion delay postponed to a later series
- Filter selection postponed to a later series
- Clock now allows the full frequency range. Worst case timing
requirements are now calculated taking the worst case clock rate.
- RESET signal now prefers GPIO if available
- Move xfer_lock outside regmap calls
- Use match_string() instead of ads1262_find_string()
- Add hardware reset to ads1262_dev_reset()
- ads1262_wait_for_conversion() now propagates errors
- Support external reference resistors. Channels referenced that way
are exposed as IIO_RESISTANCE.
- Drop HARDWAREGAIN in favor of per channel _scale_available with the
new IIO_VAL_DECIMAL64_PICO
- Now the probe fails if there is no 'drdy' IRQ (doesn't mean it's now
required in devicetree though). Support for no IRQ can be added if
needed.
- Style changes suggested by Jonathan and David.
IMPORTANT:
- Rework regulator parsing: now per-channel voltage reference is
allowed and users may configure a reference resistor instead of
supply.
Additionally, I added support for bipolar analog supplies
(AVSS < 0V). This configuration is important because it allows true
bipolar measurements, but comes with a lot of problems because
reference can also have voltage levels below ground and most
importantly the regulator subsystem doesn't support negative
voltages. The approach taken to solve this issue was the same as the
ad4170-4 driver.
More info in commit message and code comments.
- @David: I added support for the monitor channels, but I prefer to
parse them from DT instead of making them static (similar to the
ad4170-4 approach too :p).
- @David: About filters... As I mentioned in the previous version, the
data_rate configuration takes precedence over the filter selection.
If an incompatible filter (given a data rate) is selected, the chip
resorts to a sane compatible one when doing conversions (either
SINC1 or plain SINC5).
Now, I don't know how to expose this in userspace. Should I limit
the sampling_frequency_available attribute (given a filter)? Or
should it be the other way around, limit the filter_type_available
attribute (given a data rate)?.
ti-ads1263-adc2
---------------
- Postponed to a separate series to aliviate review burden.
v2: https://patch.msgid.link/20260628-ads126x-v2-0-4b1b231325ba@gmail.com
v1: https://patch.msgid.link/20260612-ads126x-v1-0-894c788d03ed@gmail.com
---
Kurt Borja (9):
dt-bindings: iio: adc: support the TI ADS126x ADC family
iio: adc: add the ti-ads1262 driver
iio: adc: ti-ads1262: support per-channel sampling frequency
iio: adc: ti-ads1262: support per-channel reference and gain
iio: adc: ti-ads1262: support input chopping
iio: adc: ti-ads1262: support excitation currents
iio: adc: ti-ads1262: support triggered buffer sampling
iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
iio: adc: ti-ads1262: support common mode supplies
.../devicetree/bindings/iio/adc/ti,ads1262.yaml | 379 ++++
MAINTAINERS | 7 +
drivers/iio/adc/Kconfig | 14 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads1262.c | 1890 ++++++++++++++++++++
5 files changed, 2291 insertions(+)
---
base-commit: 350d1fb9204b13c5f95e511e98b8bcb47574d425
change-id: 20251129-ads126x-fb6107505cae
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 4:08 ` sashiko-bot
2026-08-08 18:38 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
` (8 subsequent siblings)
9 siblings, 2 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
integrated PGA, internal reference, excitation and burn-out current
sources for sensor biasing and diagnostics. The ADS1263 adds a second,
24-bit delta-sigma ADC (ADC2) for background measurements.
Each can configure its own voltage reference source, the two excitation
current sources (IDAC), plus input and excitation channels rotation for
offset and IDAC mismatch cancellation. This lets the device drive and
ratiometrically measure RTDs and other resistive sensors.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
.../devicetree/bindings/iio/adc/ti,ads1262.yaml | 379 +++++++++++++++++++++
MAINTAINERS | 6 +
2 files changed, 385 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..c43ba7abb391
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
@@ -0,0 +1,379 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: TI ADS1262/ADS1263 analog to digital converter
+
+maintainers:
+ - Kurt Borja <kuurtb@gmail.com>
+
+description: |
+ The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an
+ integrated PGA, reference, and internal fault monitors. The ADS1263 integrates
+ an auxiliary, 24-bit, ΔΣ ADC intended for background measurements.
+
+ Datasheets:
+ - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf
+
+properties:
+ compatible:
+ oneOf:
+ - const: ti,ads1262
+ - items:
+ - const: ti,ads1263
+ - const: ti,ads1262
+
+ reg:
+ maxItems: 1
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+ spi-max-frequency:
+ maximum: 8000000
+
+ spi-cpha: true
+
+ interrupts:
+ description: Data Ready (DRDY) interrupt line.
+ maxItems: 1
+
+ interrupt-names:
+ description:
+ Specify which pin should be configured as Data Ready interrupt.
+ enum: [drdy, dout-drdy]
+
+ start-gpios:
+ description: Start conversion control.
+ maxItems: 1
+
+ reset-gpios:
+ maxItems: 1
+
+ dvdd-supply:
+ description: Digital power supply.
+
+ avdd-supply:
+ description:
+ Analog power supply. In bipolar supply configurations, the reported
+ voltage should be in reference to DGND.
+
+ avss-supply:
+ description:
+ Negative analog power supply for bipolar configurations. AVSS can only be
+ at or below the ground reference (DGND). If not described, AVSS is assumed
+ to be connected to ground (0V).
+
+ clocks:
+ maxItems: 1
+
+ '#io-channel-cells':
+ minimum: 1
+ maximum: 2
+ description:
+ The first cell selects the channel by it's reg. The second cell selects
+ between ADC1 (0) and ADC2 (1).
+
+ '#gpio-cells':
+ const: 2
+
+ gpio-controller: true
+
+ regulators:
+ type: object
+ description:
+ List of regulators provided by this chip.
+
+ properties:
+ vbias:
+ $ref: /schemas/regulator/regulator.yaml#
+ type: object
+ description:
+ Level-shift voltage output on the AINCOM pin. Its output is the
+ mid-voltage of the analog supply, (AVDD + AVSS) / 2, and is used to
+ shift floating sensors to within the ADC input range.
+ unevaluatedProperties: false
+
+ refout:
+ $ref: /schemas/regulator/regulator.yaml#
+ type: object
+ description:
+ Buffered internal voltage reference output on the REFOUT pin.
+ unevaluatedProperties: false
+
+ additionalProperties: false
+
+patternProperties:
+ "^ain([0-9]|com)-supply$":
+ description:
+ Common-mode voltage supply connected to AIN<N> or AINCOM.
+
+ "^refp[1-3]-supply$":
+ description:
+ Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
+ REFP3 (AIN4). If not described, its assumed to be connected to ground
+ (0V).
+
+ "^refn[1-3]-supply$":
+ description:
+ Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
+ REFN3 (AIN5). If not described, its assumed to be connected to ground
+ (0V).
+
+ "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
+ description:
+ Magnitude of the external reference resistor connected between REFP<N>
+ and REFN<M>. In ratiometric configurations, such as RTD measurements, the
+ IDAC excitation current returns through this resistor, generating the
+ reference voltage for the conversion.
+
+ "^channel@[0-9]+$":
+ $ref: /schemas/iio/adc/adc.yaml#
+ unevaluatedProperties: false
+
+ properties:
+ reg:
+ maxItems: 1
+
+ single-channel:
+ minimum: 0
+ maximum: 10
+
+ common-mode-channel:
+ minimum: 0
+ maximum: 10
+ default: 10
+
+ diff-channels:
+ description: |
+ In addition to the analog input pins 0 (AIN0) - 10 (AINCOM), there are
+ special inputs that can be selected from the following values:
+ 11: Temperature sensor monitor
+ 12: Analog power supply monitor
+ 13: Digital power supply monitor
+ 14: TDAC test signal
+ items:
+ minimum: 0
+ maximum: 14
+
+ reference-sources:
+ minItems: 2
+ description:
+ Indicates the reference sources for this channel. The first and second
+ items are the positive (REFP) and negative (REFN) sources of the main
+ ADC (ADC1). The third item is the reference source of the secondary
+ ADC (ADC2) and must always have a positive differential voltage.
+ items:
+ - enum: [internal, refp1, refp2, refp3, avdd]
+ - enum: [internal, refn1, refn2, refn3, avss]
+ - enum: [internal, refp1-refn1, refp2-refn2, refp3-refn3, avdd-avss]
+
+ ti,reference-reversal:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Indicates that the ADC1 (this has no effect on ADC2) reference voltage
+ for this channel has negative polarity and thus should be internally
+ reversed.
+
+ excitation-channels:
+ minItems: 1
+ maxItems: 2
+ description:
+ Selects the pins for the IDAC sources from 0 (AIN0) to 10 (AINCOM).
+ The first value corresponds to IDAC1 and the second to IDAC2.
+ items:
+ minimum: 0
+ maximum: 10
+
+ excitation-current-nanoamp:
+ minItems: 1
+ maxItems: 2
+ description:
+ The first value corresponds to IDAC1 and the second to IDAC2.
+ items:
+ enum: [50000, 100000, 250000, 500000, 750000, 1000000, 1500000,
+ 2000000, 2500000, 3000000]
+
+ burn-out-current-nanoamp:
+ description:
+ Selects current magnitude for the sensor bias current source.
+ enum: [500, 2000, 10000, 50000, 200000]
+
+ ti,burn-out-resistor:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Instead of a fixed current, the sensor bias (burn-out) current source
+ can be pulled using an internal 10 MΩ resistor.
+
+ ti,burn-out-polarity:
+ $ref: /schemas/types.yaml#/definitions/string
+ description:
+ The sensor bias can be configured to either pull-up or pull-down mode.
+ In pull-up mode, the current flows into the positive input and flows
+ out of the negative input. In pull-down mode, the polarities are
+ reversed.
+ enum: [pull-up, pull-down]
+ default: pull-up
+
+ input-chopping: true
+
+ ti,idac-rotation:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Automatically swap the IDAC1 and IDAC2 connections of alternate
+ conversions. The ADC averages the alternate conversions to eliminate
+ IDAC mismatch.
+
+ dependencies:
+ excitation-channels: [excitation-current-nanoamp]
+ excitation-current-nanoamp: [excitation-channels]
+ burn-out-current-nanoamp:
+ not:
+ required:
+ - ti,burn-out-resistor
+
+ required:
+ - reg
+
+ allOf:
+ - if:
+ properties:
+ excitation-channels:
+ maxItems: 1
+ required:
+ - excitation-channels
+ then:
+ properties:
+ excitation-current-nanoamp:
+ maxItems: 1
+ else:
+ properties:
+ excitation-current-nanoamp:
+ minItems: 2
+
+ oneOf:
+ - required: [single-channel]
+ - required: [diff-channels]
+
+dependencies:
+ interrupts: [interrupt-names]
+ interrupts-extended: [interrupt-names]
+
+required:
+ - compatible
+ - reg
+ - spi-cpha
+ - avdd-supply
+ - dvdd-supply
+ - '#address-cells'
+ - '#size-cells'
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: ti,ads1263
+ then:
+ properties:
+ '#io-channel-cells':
+ const: 2
+ patternProperties:
+ "^channel@[0-9]+$":
+ properties:
+ reference-sources:
+ minItems: 3
+ maxItems: 3
+ default: [internal, internal, internal]
+ else:
+ properties:
+ '#io-channel-cells':
+ const: 1
+ patternProperties:
+ "^channel@[0-9]+$":
+ properties:
+ reference-sources:
+ minItems: 2
+ maxItems: 2
+ default: [internal, internal]
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "ti,ads1262";
+ reg = <0>;
+ spi-max-frequency = <8000000>;
+ spi-cpha;
+ avdd-supply = <&avdd>;
+ dvdd-supply = <&dvdd>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
+ interrupt-names = "drdy";
+
+ /* Typical common mode voltage configuration */
+ aincom-supply = <&ads1262_vbias>;
+
+ regulators {
+ ads1262_vbias: vbias {
+ regulator-name = "vbias";
+ };
+ };
+
+ channel@0 {
+ reg = <0>;
+ single-channel = <0>;
+ /* The VBIAS is enabled on pin 10 (AINCOM) */
+ common-mode-channel = <10>;
+ };
+ };
+ };
+
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ adc@0 {
+ compatible = "ti,ads1263", "ti,ads1262";
+ reg = <0>;
+ spi-max-frequency = <8000000>;
+ spi-cpha;
+ avdd-supply = <&avdd>;
+ dvdd-supply = <&dvdd>;
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
+ interrupt-names = "drdy";
+
+ ti,refp2-refn2-resistor-ohms = <3520>;
+
+ channel@0 {
+ reg = <0>;
+ diff-channels = <4 5>;
+ reference-sources = "refp2", "refn2", "refp2-refn2";
+ excitation-channels = <1 6>;
+ excitation-current-nanoamp = <500000 500000>;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 04fa5322d9f7..e9248979b801 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27001,6 +27001,12 @@ S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml
F: drivers/iio/adc/ti-ads1018.c
+TI ADS1262 ADC DRIVER
+M: Kurt Borja <kuurtb@gmail.com>
+L: linux-iio@vger.kernel.org
+S: Maintained
+F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
+
TI ADS7924 ADC DRIVER
M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
L: linux-iio@vger.kernel.org
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 4:11 ` sashiko-bot
` (2 more replies)
2026-08-08 3:58 ` [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
` (7 subsequent siblings)
9 siblings, 3 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Add the ti-ads1262 driver with initial support for the primary ADC
(ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
and interoperability considerations were taken into account.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 11 +
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ti-ads1262.c | 847 +++++++++++++++++++++++++++++++++++++++++++
4 files changed, 860 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e9248979b801..3ee4a2f80733 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -27006,6 +27006,7 @@ M: Kurt Borja <kuurtb@gmail.com>
L: linux-iio@vger.kernel.org
S: Maintained
F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
+F: drivers/iio/adc/ti-ads1262.c
TI ADS7924 ADC DRIVER
M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 990e7b3e7212..dbf76427912b 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1841,6 +1841,17 @@ config TI_ADS124S08
This driver can also be built as a module. If so, the module will be
called ti-ads124s08.
+config TI_ADS1262
+ tristate "Texas Instruments ADS1262"
+ depends on SPI
+ select REGMAP
+ help
+ If you say yes here you get support for Texas Instruments ADS1262 and
+ ADS1263 ADC chips.
+
+ This driver can also be built as a module. If so, the module will be
+ called ti-ads1262.
+
config TI_ADS1298
tristate "Texas Instruments ADS1298"
depends on SPI
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index dcec0abb03b7..f85b89859fe9 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -157,6 +157,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
+obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o
obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
new file mode 100644
index 000000000000..d78e5e3ae13e
--- /dev/null
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -0,0 +1,847 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments ADS1262 ADC driver
+ *
+ * Copyright (C) 2026 Kurt Borja <kuurtb@gmail.com>
+ */
+
+#include <linux/array_size.h>
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/cleanup.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/compiler_attributes.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/lockdep.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+
+#include <asm/byteorder.h>
+
+#include <linux/iio/iio.h>
+
+#define ADS1262_OPCODE_NOP 0x00
+#define ADS1262_OPCODE_RESET 0x06
+#define ADS1262_OPCODE_START1 0x08
+#define ADS1262_OPCODE_STOP1 0x0A
+#define ADS1262_OPCODE_START2 0x0C
+#define ADS1262_OPCODE_STOP2 0x0E
+#define ADS1262_OPCODE_RDATA1 0x12
+#define ADS1262_OPCODE_RDATA2 0x14
+#define ADS1262_OPCODE_SYOCAL1 0x16
+#define ADS1262_OPCODE_SYGCAL1 0x17
+#define ADS1262_OPCODE_SFOCAL1 0x19
+#define ADS1262_OPCODE_SYOCAL2 0x1B
+#define ADS1262_OPCODE_SYGCAL2 0x1C
+#define ADS1262_OPCODE_SFOCAL2 0x1E
+#define ADS1262_OPCODE_RREG 0x20
+#define ADS1262_OPCODE_WREG 0x40
+
+#define ADS1262_ID_REG 0x00
+#define ADS1262_DEV_ID_MASK GENMASK(7, 5)
+#define ADS1262_REV_ID_MASK GENMASK(4, 0)
+
+#define ADS1262_POWER_REG 0x01
+#define ADS1262_POWER_RESET_MASK BIT(4)
+#define ADS1262_POWER_VBIAS_MASK BIT(1)
+#define ADS1262_POWER_INTREF_MASK BIT(0)
+
+#define ADS1262_INTERFACE_REG 0x02
+#define ADS1262_INTERFACE_TIMEOUT_MASK BIT(3)
+#define ADS1262_INTERFACE_STATUS_MASK BIT(2)
+#define ADS1262_INTERFACE_CRC_MASK GENMASK(1, 0)
+
+#define ADS1262_MODE0_REG 0x03
+#define ADS1262_MODE0_REFREV_MASK BIT(7)
+#define ADS1262_MODE0_RUNMODE_MASK BIT(6)
+#define ADS1262_MODE0_IDAC_CHOP_MASK BIT(5)
+#define ADS1262_MODE0_INPUT_CHOP_MASK BIT(4)
+#define ADS1262_MODE0_DELAY_MASK GENMASK(3, 0)
+
+#define ADS1262_MODE1_REG 0x04
+#define ADS1262_MODE1_FILTER_MASK GENMASK(7, 5)
+
+#define ADS1262_MODE2_REG 0x05
+#define ADS1262_MODE2_BYPASS_MASK BIT(7)
+#define ADS1262_MODE2_GAIN_MASK GENMASK(6, 4)
+#define ADS1262_MODE2_DR_MASK GENMASK(3, 0)
+
+#define ADS1262_INPMUX_REG 0x06
+#define ADS1262_INPMUX_MUXP_MASK GENMASK(7, 4)
+#define ADS1262_INPMUX_MUXN_MASK GENMASK(3, 0)
+
+#define ADS1262_OFCAL0_REG 0x07
+#define ADS1262_OFCAL1_REG 0x08
+#define ADS1262_OFCAL2_REG 0x09
+#define ADS1262_FSCAL0_REG 0x0A
+#define ADS1262_FSCAL1_REG 0x0B
+#define ADS1262_FSCAL2_REG 0x0C
+
+#define ADS1262_IDACMUX_REG 0x0D
+#define ADS1262_IDACMUX_MUX2_MASK GENMASK(7, 4)
+#define ADS1262_IDACMUX_MUX1_MASK GENMASK(3, 0)
+#define ADS1262_IDACMUX_NO_CONN 0xB
+
+#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_ADC2MUX_MUXP2_MASK GENMASK(7, 4)
+#define ADS1262_ADC2MUX_MUXN2_MASK GENMASK(3, 0)
+
+#define ADS1262_ADC2OFC0_REG 0x17
+#define ADS1262_ADC2OFC1_REG 0x18
+#define ADS1262_ADC2FSC0_REG 0x19
+#define ADS1262_ADC2FSC1_REG 0x1A
+
+#define ADS1262_REG_COUNT 0x1B
+
+#define ADS1262_MAX_CHANNEL_COUNT 16
+#define ADS1262_MAX_REGMAP_WRITE 8
+#define ADS1262_ADC1_RESOLUTION 32
+
+enum {
+ ADS1262_RUNMODE_CONTINUOUS,
+ ADS1262_RUNMODE_PULSE,
+};
+
+enum {
+ ADS1262_FILTER_SINC1,
+ ADS1262_FILTER_SINC2,
+ ADS1262_FILTER_SINC3,
+ ADS1262_FILTER_SINC4,
+ ADS1262_FILTER_FIR,
+};
+
+enum {
+ ADS1262_DR_2_5_SPS,
+ ADS1262_DR_5_SPS,
+ ADS1262_DR_10_SPS,
+ ADS1262_DR_16_6_SPS,
+ ADS1262_DR_20_SPS,
+ ADS1262_DR_50_SPS,
+ ADS1262_DR_60_SPS,
+ ADS1262_DR_100_SPS,
+ ADS1262_DR_400_SPS,
+ ADS1262_DR_1200_SPS,
+ ADS1262_DR_2400_SPS,
+ ADS1262_DR_4800_SPS,
+ ADS1262_DR_7200_SPS,
+ ADS1262_DR_14400_SPS,
+ ADS1262_DR_19200_SPS,
+ ADS1262_DR_38400_SPS,
+};
+
+enum {
+ ADS1262_INPMUX_AIN0,
+ ADS1262_INPMUX_AIN1,
+ ADS1262_INPMUX_AIN2,
+ ADS1262_INPMUX_AIN3,
+ ADS1262_INPMUX_AIN4,
+ ADS1262_INPMUX_AIN5,
+ ADS1262_INPMUX_AIN6,
+ ADS1262_INPMUX_AIN7,
+ ADS1262_INPMUX_AIN8,
+ ADS1262_INPMUX_AIN9,
+ ADS1262_INPMUX_AINCOM,
+ ADS1262_INPMUX_TEMP,
+ ADS1262_INPMUX_AVDD,
+ ADS1262_INPMUX_DVDD,
+ ADS1262_INPMUX_TDAC,
+ ADS1262_INPMUX_FLOAT,
+};
+
+struct ads1262_chip_info {
+ const char *name;
+};
+
+struct ads1262 {
+ struct spi_device *spi;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpiod;
+ struct gpio_desc *start_gpiod;
+ unsigned long clk_rate;
+
+ /* Protects channel state */
+ struct mutex chan_lock;
+ unsigned int num_channels;
+ struct completion drdy;
+
+ /* Protects transfer buffers and concurrent SPI transfers */
+ struct mutex xfer_lock;
+};
+
+static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
+{
+ guard(mutex)(&st->xfer_lock);
+
+ return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
+}
+
+static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
+{
+ guard(mutex)(&st->xfer_lock);
+
+ return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
+}
+
+static int ads1262_dev_reset(struct ads1262 *st)
+{
+ int ret;
+
+ if (st->reset_gpiod) {
+ ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
+ if (ret)
+ return ret;
+
+ /*
+ * The RESET pulse timing requirement is 4 clock cycles, at the
+ * minimum clock rate this is 4 microseconds.
+ */
+ fsleep(4);
+
+ ret = gpiod_set_value_cansleep(st->reset_gpiod, 0);
+ if (ret)
+ return ret;
+
+ /*
+ * The RESET timing requirement is 8 clock cycles, at the
+ * minimum clock rate this is 8 microseconds
+ */
+ fsleep(8);
+ } else {
+ ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET);
+ if (ret)
+ return ret;
+
+ /*
+ * The RESET timing requirement is 8 clock cycles, at the
+ * minimum clock rate this is 8 microseconds
+ */
+ fsleep(8);
+ }
+
+ return 0;
+}
+
+static int ads1262_dev_start(struct ads1262 *st)
+{
+ int ret;
+
+ if (st->start_gpiod)
+ ret = gpiod_set_value_cansleep(st->start_gpiod, 1);
+ else
+ ret = ads1262_dev_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)
+{
+ int ret;
+
+ ret = ads1262_dev_start(st);
+ if (ret)
+ return ret;
+
+ if (st->start_gpiod) {
+ /*
+ * The START pulse timing requirement is 4 clock cycles, at the
+ * minimum clock rate this is 4 microseconds.
+ */
+ fsleep(4);
+ return ads1262_dev_stop(st);
+ }
+
+ return 0;
+}
+
+static int ads1262_wait_for_conversion(struct ads1262 *st)
+{
+ u64 max_lat_ms;
+ long ret;
+
+ /*
+ * The first conversion latency is affected by the channel's data rate,
+ * filter, the configurable conversion delay and whether chop mode
+ * and/or IDAC rotation mode are enabled.
+ *
+ * The worst possible latency is calculated by taking the lowest data
+ * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
+ * (Table 9-13). Then we scale it by the actual clock rate and multiply
+ * by 4 to account for chop and IDAC rotation modes (Equation 20).
+ */
+ max_lat_ms = 4 * div_u64(mul_u32_u32(1600, 7372800), st->clk_rate);
+
+ ret = wait_for_completion_interruptible_timeout(&st->drdy,
+ msecs_to_jiffies(max_lat_ms));
+ if (ret < 0)
+ return ret;
+ if (!ret)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int ads1262_channel_enable(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ u8 val;
+
+ guard(mutex)(&st->xfer_lock);
+ guard(mutex)(&st->chan_lock);
+
+ val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
+ FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
+ return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+ ADS1262_INPMUX_MUXN_MASK |
+ ADS1262_INPMUX_MUXP_MASK, val);
+}
+
+static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
+{
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_MODE0_RUNMODE_MASK,
+ FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode));
+}
+
+static int ads1262_channel_read(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *spec, __be32 *val)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
+ if (ret)
+ return ret;
+
+ ret = ads1262_channel_enable(st, spec);
+ if (ret)
+ return ret;
+
+ reinit_completion(&st->drdy);
+
+ ret = ads1262_dev_start_one(st);
+ if (ret)
+ return ret;
+
+ ret = ads1262_wait_for_conversion(st);
+ if (ret)
+ return ret;
+
+ return ads1262_dev_read_by_cmd(st, ADS1262_OPCODE_RDATA1, val);
+}
+
+static int ads1262_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, int *val,
+ int *val2, long mask)
+{
+ __be32 raw;
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW:
+ ret = ads1262_channel_read(indio_dev, chan, &raw);
+ if (ret)
+ return ret;
+ *val = sign_extend32(be32_to_cpu(raw), ADS1262_ADC1_RESOLUTION - 1);
+
+ return IIO_VAL_INT;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ if (readval)
+ return regmap_read_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,
+ .debugfs_reg_access = ads1262_debugfs_reg_access,
+};
+
+static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
+{
+ struct ads1262 *st = dev_id;
+
+ complete(&st->drdy);
+
+ return IRQ_HANDLED;
+}
+
+static int ads1262_dev_configure(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = ads1262_dev_reset(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to reset device\n");
+
+ guard(mutex)(&st->xfer_lock);
+
+ ret = regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_RESET_MASK);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(st->regmap, ADS1262_INTERFACE_REG,
+ ADS1262_INTERFACE_STATUS_MASK |
+ ADS1262_INTERFACE_CRC_MASK);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static bool ads1262_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS1262_ID_REG ... ADS1262_ADC2FSC1_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ads1262_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS1262_POWER_REG ... ADS1262_ADC2FSC1_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool ads1262_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case ADS1262_POWER_REG:
+ case ADS1262_OFCAL0_REG ... ADS1262_FSCAL2_REG:
+ case ADS1262_GPIODAT_REG:
+ case ADS1262_ADC2OFC0_REG ... ADS1262_ADC2FSC1_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct reg_default ads1262_reg_defaults[] = {
+ { ADS1262_INTERFACE_REG,
+ FIELD_PREP_CONST(ADS1262_INTERFACE_STATUS_MASK, true) |
+ FIELD_PREP_CONST(ADS1262_INTERFACE_CRC_MASK, true) },
+ { ADS1262_MODE0_REG, 0x00 },
+ { ADS1262_MODE1_REG,
+ FIELD_PREP_CONST(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR) },
+ { ADS1262_MODE2_REG,
+ FIELD_PREP_CONST(ADS1262_MODE2_DR_MASK, ADS1262_DR_20_SPS) },
+ { ADS1262_INPMUX_REG,
+ FIELD_PREP_CONST(ADS1262_INPMUX_MUXN_MASK, ADS1262_INPMUX_AIN1) },
+ { ADS1262_IDACMUX_REG,
+ FIELD_PREP_CONST(ADS1262_IDACMUX_MUX2_MASK, ADS1262_IDACMUX_NO_CONN) |
+ FIELD_PREP_CONST(ADS1262_IDACMUX_MUX1_MASK, ADS1262_IDACMUX_NO_CONN) },
+ { 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_ADC2CFG_REG, 0x00 },
+ { ADS1262_ADC2MUX_REG,
+ FIELD_PREP_CONST(ADS1262_ADC2MUX_MUXN2_MASK, ADS1262_INPMUX_AIN1) },
+};
+
+static const struct regmap_config ads1262_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .writeable_reg = ads1262_writeable_reg,
+ .readable_reg = ads1262_readable_reg,
+ .volatile_reg = ads1262_volatile_reg,
+ .reg_defaults = ads1262_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
+ .max_register = ADS1262_ADC2FSC1_REG,
+ .can_sleep = true,
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int ads1262_regmap_read(void *context, const void *reg_buf,
+ size_t reg_size, void *val_buf, size_t val_size)
+{
+ struct ads1262 *st = context;
+ u8 tx[2];
+
+ lockdep_assert_held(&st->xfer_lock);
+
+ /*
+ * The register read operation uses a two byte command header followed
+ * by the register data:
+ *
+ * byte 0: RREG opcode | register address
+ * byte 1: number of registers to transfer, minus one
+ * byte 2..: register data
+ */
+ memcpy(tx, reg_buf, 1);
+ tx[0] |= ADS1262_OPCODE_RREG;
+ tx[1] = val_size - 1;
+
+ return spi_write_then_read(st->spi, tx, sizeof(tx), val_buf, val_size);
+}
+
+static int ads1262_regmap_gather_write(void *context, const void *reg_buf,
+ size_t reg_size, const void *val_buf,
+ size_t val_size)
+{
+ struct ads1262 *st = context;
+ u8 tx[ADS1262_MAX_REGMAP_WRITE + 2];
+
+ lockdep_assert_held(&st->xfer_lock);
+
+ /*
+ * The register write operation uses a two byte command header followed
+ * by the register data:
+ *
+ * byte 0: WREG opcode | register address
+ * byte 1: number of registers to transfer, minus one
+ * byte 2..: register data
+ */
+ memcpy(tx, reg_buf, 1);
+ tx[0] |= ADS1262_OPCODE_WREG;
+ tx[1] = val_size - 1;
+ memcpy(&tx[2], val_buf, val_size);
+
+ return spi_write_then_read(st->spi, tx, 2 + val_size, NULL, 0);
+}
+
+static int ads1262_regmap_write(void *context, const void *data, size_t count)
+{
+ return ads1262_regmap_gather_write(context, data, 1, data + 1,
+ count - 1);
+}
+
+static const struct regmap_bus ads1262_regmap_bus = {
+ .read = ads1262_regmap_read,
+ .gather_write = ads1262_regmap_gather_write,
+ .write = ads1262_regmap_write,
+ .reg_format_endian_default = REGMAP_ENDIAN_BIG,
+ .val_format_endian_default = REGMAP_ENDIAN_BIG,
+ .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
+};
+
+static int ads1262_gpio_setup(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+
+ st->start_gpiod = devm_gpiod_get_optional(dev, "start", GPIOD_OUT_LOW);
+ if (IS_ERR(st->start_gpiod))
+ return dev_err_probe(dev, PTR_ERR(st->start_gpiod),
+ "failed to get start GPIO\n");
+
+ st->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(st->reset_gpiod))
+ return dev_err_probe(dev, PTR_ERR(st->reset_gpiod),
+ "failed to get reset GPIO\n");
+
+ /*
+ * The power transition timing requirement is 65536 clock cycles, at the
+ * minimum clock frequency this is 65536 microseconds.
+ */
+ fsleep(65536);
+
+ return 0;
+}
+
+static int ads1262_parse_channel_node(struct ads1262 *st,
+ struct iio_chan_spec *spec,
+ struct fwnode_handle *node)
+{
+ struct device *dev = &st->spi->dev;
+ u32 pins[2];
+ int ret;
+
+ if (fwnode_property_present(node, "single-channel")) {
+ ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
+ if (ret)
+ return dev_err_probe(dev, ret, "%s: failed to read single-channel\n",
+ fwnode_get_name(node));
+
+ pins[1] = ADS1262_INPMUX_AINCOM;
+ fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
+ } else if (fwnode_property_present(node, "diff-channels")) {
+ ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
+ ARRAY_SIZE(pins));
+ if (ret)
+ return dev_err_probe(dev, ret, "%s: failed to read diff-channels\n",
+ fwnode_get_name(node));
+
+ if (pins[0] <= ADS1262_INPMUX_AINCOM || pins[1] <= ADS1262_INPMUX_AINCOM)
+ spec->differential = true;
+ } else {
+ return dev_err_probe(dev, -ENXIO,
+ "%s: one of single-channel or diff-channels is required\n",
+ fwnode_get_name(node));
+ }
+
+ if (pins[0] >= ADS1262_INPMUX_FLOAT || pins[1] >= ADS1262_INPMUX_FLOAT)
+ return dev_err_probe(dev, -EINVAL, "%s: input channels not in range\n",
+ fwnode_get_name(node));
+
+ if ((pins[0] >= ADS1262_INPMUX_TEMP ||
+ pins[1] >= ADS1262_INPMUX_TEMP) && pins[0] != pins[1])
+ return dev_err_probe(dev, -EINVAL,
+ "%s: monitor channels must be selected symmetrically\n",
+ fwnode_get_name(node));
+
+ spec->channel = pins[0];
+ spec->channel2 = pins[1];
+
+ return 0;
+}
+
+static int ads1262_parse_channels(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct device *dev = &st->spi->dev;
+ struct iio_chan_spec *specs;
+ unsigned long used_regs = 0;
+ int num_specs;
+ u32 reg;
+ int ret;
+
+ st->num_channels = device_get_named_child_node_count(dev, "channel");
+ if (!st->num_channels)
+ return dev_err_probe(dev, -ENXIO, "no 'channel' nodes configured\n");
+ if (st->num_channels > ADS1262_MAX_CHANNEL_COUNT)
+ return dev_err_probe(dev, -EINVAL, "too many channels\n");
+
+ /* Account for the timestamp channel */
+ num_specs = st->num_channels + 1;
+ specs = devm_kcalloc(dev, num_specs, sizeof(*specs), GFP_KERNEL);
+ if (!specs)
+ return -ENOMEM;
+
+ device_for_each_named_child_node_scoped(dev, node, "channel") {
+ ret = fwnode_property_read_u32(node, "reg", ®);
+ if (ret)
+ return dev_err_probe(dev, ret, "%s: failed to read channel reg\n",
+ fwnode_get_name(node));
+ if (reg >= st->num_channels)
+ return dev_err_probe(dev, -EINVAL, "%s: reg out of range\n",
+ fwnode_get_name(node));
+
+ static_assert(ADS1262_MAX_CHANNEL_COUNT < BITS_PER_LONG);
+ if (__test_and_set_bit(reg, &used_regs))
+ return dev_err_probe(dev, -EINVAL, "%s: duplicated channel reg\n",
+ fwnode_get_name(node));
+
+ specs[reg].scan_index = reg;
+ specs[reg].scan_type = (struct iio_scan_type) {
+ .format = IIO_SCAN_FORMAT_SIGNED_INT,
+ .realbits = ADS1262_ADC1_RESOLUTION,
+ .storagebits = 32,
+ .endianness = IIO_BE,
+ };
+
+ ret = ads1262_parse_channel_node(st, &specs[reg], node);
+ if (ret)
+ return ret;
+
+ if (specs[reg].channel == ADS1262_INPMUX_TEMP)
+ specs[reg].type = IIO_TEMP;
+ else
+ specs[reg].type = IIO_VOLTAGE;
+
+ if (specs[reg].channel != ADS1262_INPMUX_TEMP)
+ specs[reg].indexed = true;
+
+ specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ }
+
+ specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
+
+ indio_dev->channels = specs;
+ indio_dev->num_channels = num_specs;
+
+ return 0;
+}
+
+static int ads1262_supply_setup(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ int ret;
+
+ ret = devm_regulator_get_enable(dev, "dvdd");
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
+
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+
+ ret = devm_regulator_get_enable_optional(dev, "avss");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+
+ return 0;
+}
+
+static int ads1262_spi_probe(struct spi_device *spi)
+{
+ const struct ads1262_chip_info *info;
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ads1262 *st;
+ unsigned long rate;
+ struct clk *clk;
+ int irq;
+ int ret;
+
+ info = spi_get_device_match_data(spi);
+ if (!info)
+ return -EINVAL;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+ indio_dev->name = info->name;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->info = &ads1262_iio_info;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+ init_completion(&st->drdy);
+
+ ret = devm_mutex_init(dev, &st->chan_lock);
+ if (ret)
+ return ret;
+ ret = devm_mutex_init(dev, &st->xfer_lock);
+ if (ret)
+ return ret;
+
+ ret = ads1262_parse_channels(indio_dev);
+ if (ret)
+ return ret;
+
+ clk = devm_clk_get_optional_enabled(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "failed to get external clock\n");
+
+ rate = clk_get_rate(clk);
+ if (clk && !rate)
+ return dev_err_probe(dev, -ENXIO, "failed to get clock rate\n");
+ st->clk_rate = rate ? rate : 7372800;
+
+ ret = ads1262_supply_setup(st);
+ if (ret)
+ return ret;
+
+ ret = ads1262_gpio_setup(st);
+ 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_dev_configure(st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to configure device\n");
+
+ /*
+ * REVISIT: This chip has software polling capabilities, which could be
+ * used to stop depending on the 'drdy' IRQ.
+ *
+ * Additionally, the MISO pin also can be used as a DRDY IRQ, in which
+ * case the interrupt would be named 'dout-drdy', but requires a lot of
+ * timing and synchronization considerations to be reliable.
+ */
+ irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
+ if (irq < 0)
+ return dev_err_probe(dev, irq,
+ "the 'drdy' IRQ is currently required for operation\n");
+
+ ret = devm_request_irq(dev, irq, ads1262_irq_handler, IRQF_NO_THREAD,
+ info->name, st);
+ if (ret)
+ return ret;
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct ads1262_chip_info ads1262_chip_info = {
+ .name = "ads1262",
+};
+
+static const struct of_device_id ads1262_of_match[] = {
+ { .compatible = "ti,ads1262", .data = &ads1262_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ads1262_of_match);
+
+static const struct spi_device_id ads1262_spi_match[] = {
+ { .name = "ads1262", .driver_data = (kernel_ulong_t)&ads1262_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ads1262_spi_match);
+
+static struct spi_driver ads1262_spi_driver = {
+ .driver = {
+ .name = "ads1262",
+ .of_match_table = ads1262_of_match,
+ },
+ .probe = ads1262_spi_probe,
+ .id_table = ads1262_spi_match,
+};
+module_spi_driver(ads1262_spi_driver);
+
+MODULE_DESCRIPTION("Texas Instruments ADS1262 ADC driver");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>");
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
` (6 subsequent siblings)
9 siblings, 2 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Add per-channel sampling frequency support. The "available" attribute is
assigned per-channel too, in order to eventually support per-filter
availability.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 159 ++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 158 insertions(+), 1 deletion(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index d78e5e3ae13e..b3b7b1249102 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -26,6 +26,7 @@
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
#include <linux/types.h>
+#include <linux/units.h>
#include <asm/byteorder.h>
@@ -148,6 +149,7 @@ enum {
ADS1262_DR_14400_SPS,
ADS1262_DR_19200_SPS,
ADS1262_DR_38400_SPS,
+ ADS1262_DR_COUNT,
};
enum {
@@ -173,6 +175,11 @@ struct ads1262_chip_info {
const char *name;
};
+struct ads1262_channel {
+ u8 data_rate;
+ int samp_freqs[ADS1262_DR_COUNT][2];
+};
+
struct ads1262 {
struct spi_device *spi;
struct regmap *regmap;
@@ -183,12 +190,47 @@ struct ads1262 {
/* Protects channel state */
struct mutex chan_lock;
unsigned int num_channels;
+ struct ads1262_channel *channels;
struct completion drdy;
/* Protects transfer buffers and concurrent SPI transfers */
struct mutex xfer_lock;
};
+static const u32 ads1262_data_rate_div[] = {
+ [ADS1262_DR_2_5_SPS] = 8 * 64 * 5760,
+ [ADS1262_DR_5_SPS] = 8 * 64 * 2880,
+ [ADS1262_DR_10_SPS] = 8 * 64 * 1440,
+ [ADS1262_DR_16_6_SPS] = 8 * 64 * 864,
+ [ADS1262_DR_20_SPS] = 8 * 64 * 720,
+ [ADS1262_DR_50_SPS] = 8 * 64 * 288,
+ [ADS1262_DR_60_SPS] = 8 * 64 * 240,
+ [ADS1262_DR_100_SPS] = 8 * 64 * 144,
+ [ADS1262_DR_400_SPS] = 8 * 64 * 36,
+ [ADS1262_DR_1200_SPS] = 8 * 64 * 12,
+ [ADS1262_DR_2400_SPS] = 8 * 64 * 6,
+ [ADS1262_DR_4800_SPS] = 8 * 64 * 3,
+ [ADS1262_DR_7200_SPS] = 8 * 64 * 2,
+ [ADS1262_DR_14400_SPS] = 8 * 64 * 1,
+ [ADS1262_DR_19200_SPS] = 8 * 48 * 1,
+ [ADS1262_DR_38400_SPS] = 8 * 24 * 1,
+};
+
+static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
+ int val2)
+{
+ int i;
+
+ for (i = 0; i < num_elements; i++) {
+ if (val == array[i][0] && val2 == array[i][1])
+ break;
+ }
+ if (i == num_elements)
+ return -EINVAL;
+
+ return i;
+}
+
static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
{
guard(mutex)(&st->xfer_lock);
@@ -316,11 +358,19 @@ static int ads1262_wait_for_conversion(struct ads1262 *st)
static int ads1262_channel_enable(struct ads1262 *st,
const struct iio_chan_spec *spec)
{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ int ret;
u8 val;
guard(mutex)(&st->xfer_lock);
guard(mutex)(&st->chan_lock);
+ val = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate);
+ ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
+ ADS1262_MODE2_DR_MASK, val);
+ if (ret)
+ return ret;
+
val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
@@ -372,6 +422,8 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan, int *val,
int *val2, long mask)
{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
__be32 raw;
int ret;
@@ -384,6 +436,65 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SAMP_FREQ: {
+ guard(mutex)(&st->chan_lock);
+
+ *val = chan_data->samp_freqs[chan_data->data_rate][0];
+ *val2 = chan_data->samp_freqs[chan_data->data_rate][1];
+
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+
+ default:
+ return -EOPNOTSUPP;
+ }
+}
+
+static int ads1262_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, const int **vals,
+ int *type, int *length, long mask)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *type = IIO_VAL_INT_PLUS_MICRO;
+ *vals = (const int *)chan_data->samp_freqs;
+ *length = ARRAY_SIZE(chan_data->samp_freqs) * 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 = &st->channels[chan->scan_index];
+ int ret;
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ guard(mutex)(&st->chan_lock);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ ret = ads1262_find_two(chan_data->samp_freqs,
+ ARRAY_SIZE(chan_data->samp_freqs),
+ val, val2);
+ if (ret < 0)
+ return -EINVAL;
+
+ chan_data->data_rate = ret;
+
+ return 0;
+
default:
return -EOPNOTSUPP;
}
@@ -404,6 +515,8 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
static const struct iio_info ads1262_iio_info = {
.read_raw = ads1262_read_raw,
+ .read_avail = ads1262_read_avail,
+ .write_raw = ads1262_write_raw,
.debugfs_reg_access = ads1262_debugfs_reg_access,
};
@@ -575,6 +688,36 @@ static const struct regmap_bus ads1262_regmap_bus = {
.max_raw_write = ADS1262_MAX_REGMAP_WRITE,
};
+static void ads1262_populate_samp_freqs(struct ads1262 *st,
+ struct ads1262_channel *chan)
+{
+ int freq_Hz, freq_rem;
+ u64 freq_uHz;
+
+ for (unsigned int i = 0; i < ARRAY_SIZE(chan->samp_freqs); i++) {
+ freq_uHz = div_u64(mul_u32_u32(st->clk_rate, MICRO),
+ ads1262_data_rate_div[i]);
+ freq_Hz = div_u64_rem(freq_uHz, MICRO, &freq_rem);
+
+ chan->samp_freqs[i][0] = freq_Hz;
+ chan->samp_freqs[i][1] = freq_rem;
+ }
+}
+
+static int ads1262_populate_tables(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan;
+
+ for (unsigned int i = 0; i < st->num_channels; i++) {
+ chan = &st->channels[i];
+
+ ads1262_populate_samp_freqs(st, chan);
+ }
+
+ return 0;
+}
+
static int ads1262_gpio_setup(struct ads1262 *st)
{
struct device *dev = &st->spi->dev;
@@ -661,6 +804,11 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
if (st->num_channels > ADS1262_MAX_CHANNEL_COUNT)
return dev_err_probe(dev, -EINVAL, "too many channels\n");
+ st->channels = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels),
+ GFP_KERNEL);
+ if (!st->channels)
+ return -ENOMEM;
+
/* Account for the timestamp channel */
num_specs = st->num_channels + 1;
specs = devm_kcalloc(dev, num_specs, sizeof(*specs), GFP_KERNEL);
@@ -681,6 +829,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
return dev_err_probe(dev, -EINVAL, "%s: duplicated channel reg\n",
fwnode_get_name(node));
+ st->channels[reg].data_rate = ADS1262_DR_20_SPS;
+
specs[reg].scan_index = reg;
specs[reg].scan_type = (struct iio_scan_type) {
.format = IIO_SCAN_FORMAT_SIGNED_INT,
@@ -701,7 +851,10 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
if (specs[reg].channel != ADS1262_INPMUX_TEMP)
specs[reg].indexed = true;
- specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
+ specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+ BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ specs[reg].info_mask_separate_available =
+ BIT(IIO_CHAN_INFO_SAMP_FREQ);
}
specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
@@ -786,6 +939,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = ads1262_populate_tables(indio_dev);
+ if (ret)
+ return ret;
+
st->regmap = devm_regmap_init(dev, &ads1262_regmap_bus, st,
&ads1262_regmap_config);
if (IS_ERR(st->regmap))
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (2 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping Kurt Borja
` (5 subsequent siblings)
9 siblings, 1 reply; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Allow each channel to select its voltage reference through the
"reference-sources" firmware property. Then, use the reference voltage
to calculate available scales.
The ADS1262 allows single-ended supply configurations or bipolar supply
configurations. In single ended configurations both the analog and
digital rails share the same ground, i.e. AVSS = DGND = 0 V. In bipolar
supply configurations, AVSS can go below ground, e.g. AVSS = -2.5 V.
If AVSS is below ground, the ADC can achieve true bipolar measurements
and the external references can also have voltage levels below ground.
This is currently an issue because the regulator subsystem doesn't
support negative voltages.
The ad4170-4 driver faces this problem too and the same workaround is
used in this case: assume every regulator reports magnitudes (absolute
values). If the chip has a bipolar supply configuration, then assume
positive references are above ground (>= 0 V) and negative references
are below ground (<= 0 V). This is not a hardware constraint, but it is
the most common wiring.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 417 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 406 insertions(+), 11 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index b3b7b1249102..360ce01a5871 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -8,6 +8,7 @@
#include <linux/array_size.h>
#include <linux/bitfield.h>
#include <linux/bitops.h>
+#include <linux/bitmap.h>
#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/completion.h>
@@ -18,6 +19,7 @@
#include <linux/interrupt.h>
#include <linux/lockdep.h>
#include <linux/math64.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>
#include <linux/mutex.h>
@@ -25,6 +27,7 @@
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/string.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -97,6 +100,9 @@
#define ADS1262_IDACMAG_REG 0x0E
#define ADS1262_REFMUX_REG 0x0F
+#define ADS1262_REFMUX_RMUXP_MASK GENMASK(5, 3)
+#define ADS1262_REFMUX_RMUXN_MASK GENMASK(2, 0)
+
#define ADS1262_TDACP_REG 0x10
#define ADS1262_TDACN_REG 0x11
#define ADS1262_GPIOCON_REG 0x12
@@ -117,8 +123,12 @@
#define ADS1262_MAX_CHANNEL_COUNT 16
#define ADS1262_MAX_REGMAP_WRITE 8
+#define ADS1262_EXT_REF_COUNT 3
#define ADS1262_ADC1_RESOLUTION 32
+#define ADS1262_TEMP_SLOPE_uV_C 420ULL
+#define ADS1262_TEMP_ZERO_C 111900ULL
+
enum {
ADS1262_RUNMODE_CONTINUOUS,
ADS1262_RUNMODE_PULSE,
@@ -171,12 +181,38 @@ enum {
ADS1262_INPMUX_FLOAT,
};
+enum {
+ ADS1262_RMUXP_INTERNAL,
+ ADS1262_RMUXP_REFP1,
+ ADS1262_RMUXP_REFP2,
+ ADS1262_RMUXP_REFP3,
+ ADS1262_RMUXP_AVDD,
+ ADS1262_RMUXP_COUNT
+};
+
+enum {
+ ADS1262_RMUXN_INTERNAL,
+ ADS1262_RMUXN_REFN1,
+ ADS1262_RMUXN_REFN2,
+ ADS1262_RMUXN_REFN3,
+ ADS1262_RMUXN_AVSS,
+ ADS1262_RMUXN_COUNT
+};
+
struct ads1262_chip_info {
const char *name;
};
struct ads1262_channel {
u8 data_rate;
+ u8 gain;
+ u8 ref_p;
+ u8 ref_n;
+ bool ref_reversal;
+ bool is_resistance;
+ int offset;
+ int scales[6][2];
+ size_t num_scales;
int samp_freqs[ADS1262_DR_COUNT][2];
};
@@ -192,6 +228,12 @@ struct ads1262 {
unsigned int num_channels;
struct ads1262_channel *channels;
struct completion drdy;
+ u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
+ int refp_uV[ADS1262_RMUXP_COUNT];
+ int refn_uV[ADS1262_RMUXN_COUNT];
+ bool need_avdd_uV;
+ bool need_avss_uV;
+ bool bipolar_supply;
/* Protects transfer buffers and concurrent SPI transfers */
struct mutex xfer_lock;
@@ -216,6 +258,24 @@ static const u32 ads1262_data_rate_div[] = {
[ADS1262_DR_38400_SPS] = 8 * 24 * 1,
};
+static const char * const ads1262_ref_sources_pos[] = {
+ [ADS1262_RMUXP_INTERNAL] = "internal",
+ [ADS1262_RMUXP_REFP1] = "refp1",
+ [ADS1262_RMUXP_REFP2] = "refp2",
+ [ADS1262_RMUXP_REFP3] = "refp3",
+ [ADS1262_RMUXP_AVDD] = "avdd",
+ NULL
+};
+
+static const char * const ads1262_ref_sources_neg[] = {
+ [ADS1262_RMUXN_INTERNAL] = "internal",
+ [ADS1262_RMUXN_REFN1] = "refn1",
+ [ADS1262_RMUXN_REFN2] = "refn2",
+ [ADS1262_RMUXN_REFN3] = "refn3",
+ [ADS1262_RMUXN_AVSS] = "avss",
+ NULL
+};
+
static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
int val2)
{
@@ -231,6 +291,12 @@ static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
return i;
}
+static bool ads1262_ref_is_external(int ref_p, int ref_n)
+{
+ return in_range(ref_p, ADS1262_RMUXP_REFP1, ADS1262_EXT_REF_COUNT) &&
+ in_range(ref_n, ADS1262_RMUXN_REFN1, ADS1262_EXT_REF_COUNT);
+}
+
static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
{
guard(mutex)(&st->xfer_lock);
@@ -365,17 +431,33 @@ static int ads1262_channel_enable(struct ads1262 *st,
guard(mutex)(&st->xfer_lock);
guard(mutex)(&st->chan_lock);
- val = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate);
+ val = FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
+ ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_MODE0_REFREV_MASK, val);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+ FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
- ADS1262_MODE2_DR_MASK, val);
+ ADS1262_MODE2_DR_MASK |
+ ADS1262_MODE2_GAIN_MASK, val);
if (ret)
return ret;
val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
- return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
+ ret = regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
ADS1262_INPMUX_MUXN_MASK |
ADS1262_INPMUX_MUXP_MASK, val);
+ if (ret)
+ return ret;
+
+ val = FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n) |
+ FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p);
+ return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
+ ADS1262_REFMUX_RMUXN_MASK |
+ ADS1262_REFMUX_RMUXP_MASK, val);
}
static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
@@ -436,6 +518,26 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
+ case IIO_CHAN_INFO_SCALE: {
+ guard(mutex)(&st->chan_lock);
+
+ *val = chan_data->scales[chan_data->gain][0];
+ *val2 = chan_data->scales[chan_data->gain][1];
+
+ return IIO_VAL_DECIMAL64_PICO;
+ }
+
+ case IIO_CHAN_INFO_OFFSET: {
+ if (chan->type != IIO_TEMP)
+ return -EPERM;
+
+ guard(mutex)(&st->chan_lock);
+
+ *val = chan_data->offset;
+
+ return IIO_VAL_INT;
+ }
+
case IIO_CHAN_INFO_SAMP_FREQ: {
guard(mutex)(&st->chan_lock);
@@ -458,6 +560,12 @@ static int ads1262_read_avail(struct iio_dev *indio_dev,
struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ *type = IIO_VAL_DECIMAL64_PICO;
+ *vals = (const int *)chan_data->scales;
+ *length = chan_data->num_scales * 2;
+ return IIO_AVAIL_LIST;
+
case IIO_CHAN_INFO_SAMP_FREQ:
*type = IIO_VAL_INT_PLUS_MICRO;
*vals = (const int *)chan_data->samp_freqs;
@@ -484,6 +592,16 @@ static int ads1262_write_raw(struct iio_dev *indio_dev,
guard(mutex)(&st->chan_lock);
switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ ret = ads1262_find_two(chan_data->scales, chan_data->num_scales,
+ val, val2);
+ if (ret < 0)
+ return ret;
+
+ chan_data->gain = ret;
+
+ return 0;
+
case IIO_CHAN_INFO_SAMP_FREQ:
ret = ads1262_find_two(chan_data->samp_freqs,
ARRAY_SIZE(chan_data->samp_freqs),
@@ -513,10 +631,22 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
return regmap_write(st->regmap, reg, writeval);
}
+static int ads1262_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan, long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_SCALE:
+ return IIO_VAL_DECIMAL64_PICO;
+ default:
+ return IIO_VAL_INT_PLUS_MICRO;
+ }
+}
+
static 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,
};
@@ -688,6 +818,91 @@ static const struct regmap_bus ads1262_regmap_bus = {
.max_raw_write = ADS1262_MAX_REGMAP_WRITE,
};
+static void ads1262_calculate_scales(int (*scales)[2], size_t num_scales,
+ u32 full_scale, u64 mult,
+ u32 resolution)
+{
+ unsigned int i;
+ s64 val;
+
+ for (i = 0; i < num_scales; i++) {
+ val = mul_u64_u64_shr(full_scale, mult, resolution - 1 + i);
+ iio_val_s64_decompose(val, &scales[i][0], &scales[i][1]);
+ }
+}
+
+static int ads1262_populate_scales_resistance(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u32 full_scale;
+
+ if (WARN_ON(!ads1262_ref_is_external(chan->ref_p, chan->ref_n)))
+ return -EINVAL;
+
+ full_scale = st->rref_ohms[chan->ref_p - 1][chan->ref_n - 1];
+
+ chan->num_scales = ARRAY_SIZE(chan->scales);
+
+ ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+ PICO, ADS1262_ADC1_RESOLUTION);
+
+ return 0;
+}
+
+static int ads1262_populate_scales_temp(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct device *dev = &st->spi->dev;
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u32 full_scale;
+ u64 mult;
+
+ full_scale = abs(st->refp_uV[chan->ref_p] - st->refn_uV[chan->ref_n]);
+ if (full_scale < 900000)
+ return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
+ spec->scan_index);
+
+ chan->offset = -div_s64(ADS1262_TEMP_ZERO_C <<
+ (ADS1262_ADC1_RESOLUTION - 1), full_scale);
+
+ chan->num_scales = 1;
+
+ mult = PICO * MILLIDEGREE_PER_DEGREE / ADS1262_TEMP_SLOPE_uV_C;
+ ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+ mult, ADS1262_ADC1_RESOLUTION);
+
+ return 0;
+}
+
+static int ads1262_populate_scales_voltage(struct ads1262 *st,
+ const struct iio_chan_spec *spec)
+{
+ struct device *dev = &st->spi->dev;
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u32 full_scale;
+ u64 mult;
+
+ full_scale = abs(st->refp_uV[chan->ref_p] - st->refn_uV[chan->ref_n]);
+ if (full_scale < 900000)
+ return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
+ spec->scan_index);
+
+ if (spec->channel >= ADS1262_INPMUX_AVDD &&
+ spec->channel <= ADS1262_INPMUX_DVDD) {
+ chan->num_scales = 1;
+ mult = 4;
+ } else {
+ chan->num_scales = ARRAY_SIZE(chan->scales);
+ mult = 1;
+ }
+
+ ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
+ NANO * mult, ADS1262_ADC1_RESOLUTION);
+
+ return 0;
+}
+
static void ads1262_populate_samp_freqs(struct ads1262 *st,
struct ads1262_channel *chan)
{
@@ -707,12 +922,102 @@ static void ads1262_populate_samp_freqs(struct ads1262 *st,
static int ads1262_populate_tables(struct iio_dev *indio_dev)
{
struct ads1262 *st = iio_priv(indio_dev);
+ const struct iio_chan_spec *spec;
struct ads1262_channel *chan;
+ int ret;
+
for (unsigned int i = 0; i < st->num_channels; i++) {
+ spec = &indio_dev->channels[i];
chan = &st->channels[i];
ads1262_populate_samp_freqs(st, chan);
+
+ switch (spec->type) {
+ case IIO_VOLTAGE:
+ ret = ads1262_populate_scales_voltage(st, spec);
+ if (ret)
+ return ret;
+ break;
+ case IIO_TEMP:
+ ret = ads1262_populate_scales_temp(st, spec);
+ if (ret)
+ return ret;
+ break;
+ case IIO_RESISTANCE:
+ ret = ads1262_populate_scales_resistance(st, spec);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return 0;
+}
+
+static int ads1262_parse_references(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ unsigned int i, j;
+ char name[sizeof("ti,refpN-refnM-resistor-ohms")];
+ u32 ohms;
+ int ret;
+
+ st->refp_uV[ADS1262_RMUXP_INTERNAL] = st->refn_uV[ADS1262_RMUXN_AVSS] + 2500000;
+ st->refn_uV[ADS1262_RMUXN_INTERNAL] = st->refn_uV[ADS1262_RMUXN_AVSS];
+
+ for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+ scnprintf(name, sizeof(name), "refp%u", i);
+ ret = devm_regulator_get_enable_read_voltage(dev, name);
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+ name);
+
+ st->refp_uV[i] = ret == -ENODEV ? 0 : ret;
+ }
+
+ for (i = ADS1262_RMUXN_REFN1; i <= ADS1262_RMUXN_REFN3; i++) {
+ scnprintf(name, sizeof(name), "refn%u", i);
+ ret = devm_regulator_get_enable_read_voltage(dev, name);
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
+ name);
+
+ /*
+ * REVISIT: Currently the regulator subsystem doesn't support
+ * reading negative voltages. If we have a bipolar supply
+ * configuration (AVSS < 0), then we are forced to assume that
+ * negative references are either 0V (no regulator) or below
+ * ground magnitudes.
+ */
+ if (st->bipolar_supply)
+ st->refn_uV[i] = ret == -ENODEV ? 0 : -ret;
+ else
+ st->refn_uV[i] = ret == -ENODEV ? 0 : ret;
+ }
+
+ for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
+ for (j = ADS1262_RMUXN_REFN1; j <= ADS1262_RMUXN_REFN3; j++) {
+ scnprintf(name, sizeof(name),
+ "ti,refp%u-refn%u-resistor-ohms", i, j);
+
+ if (!device_property_present(dev, name))
+ continue;
+
+ ret = device_property_read_u32(dev, name, &ohms);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to read reference resistor: %s\n",
+ name);
+ if (!ohms)
+ return dev_err_probe(dev, -EINVAL,
+ "reference resistor can't be 0 ohms: %s\n",
+ name);
+
+ st->rref_ohms[i - 1][j - 1] = ohms;
+ }
}
return 0;
@@ -745,7 +1050,10 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
struct iio_chan_spec *spec,
struct fwnode_handle *node)
{
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
struct device *dev = &st->spi->dev;
+ const char *sources[2];
+ char name[sizeof("ti,refpN-refnM-resistor-ohms")];
u32 pins[2];
int ret;
@@ -785,6 +1093,49 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
spec->channel = pins[0];
spec->channel2 = pins[1];
+ if (fwnode_property_present(node, "reference-sources")) {
+ ret = fwnode_property_read_string_array(node, "reference-sources",
+ sources, ARRAY_SIZE(sources));
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s: failed to read reference-sources\n",
+ fwnode_get_name(node));
+ if (ret < 2)
+ return dev_err_probe(dev, -EINVAL, "%s: missing reference-sources\n",
+ fwnode_get_name(node));
+
+ ret = match_string(ads1262_ref_sources_pos, -1, sources[0]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s: invalid positive reference source\n",
+ fwnode_get_name(node));
+ chan->ref_p = ret;
+
+ ret = match_string(ads1262_ref_sources_neg, -1, sources[1]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s: invalid negative reference source\n",
+ fwnode_get_name(node));
+ chan->ref_n = ret;
+
+ if ((chan->ref_p == ADS1262_RMUXP_INTERNAL ||
+ chan->ref_n == ADS1262_RMUXN_INTERNAL) && chan->ref_p != chan->ref_n)
+ return dev_err_probe(dev, -EINVAL,
+ "%s: the internal reference must be selected symmetrically\n",
+ fwnode_get_name(node));
+
+ if (chan->ref_p == ADS1262_RMUXP_AVDD)
+ st->need_avdd_uV = true;
+ if (chan->ref_n == ADS1262_RMUXN_AVSS)
+ st->need_avss_uV = true;
+
+ if (ads1262_ref_is_external(chan->ref_p, chan->ref_n)) {
+ scnprintf(name, sizeof(name), "ti,refp%u-refn%u-resistor-ohms",
+ chan->ref_p, chan->ref_n);
+ if (device_property_present(dev, name))
+ chan->is_resistance = true;
+ }
+ }
+
+ chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
+
return 0;
}
@@ -845,6 +1196,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
if (specs[reg].channel == ADS1262_INPMUX_TEMP)
specs[reg].type = IIO_TEMP;
+ else if (st->channels[reg].is_resistance)
+ specs[reg].type = IIO_RESISTANCE;
else
specs[reg].type = IIO_VOLTAGE;
@@ -852,9 +1205,14 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
specs[reg].indexed = true;
specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE);
+ if (specs[reg].channel == ADS1262_INPMUX_TEMP)
+ specs[reg].info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
+
specs[reg].info_mask_separate_available =
- BIT(IIO_CHAN_INFO_SAMP_FREQ);
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_SCALE);
}
specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
@@ -874,13 +1232,46 @@ static int ads1262_supply_setup(struct ads1262 *st)
if (ret)
return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
- ret = devm_regulator_get_enable(dev, "avdd");
- if (ret < 0)
- return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+ if (st->need_avdd_uV) {
+ ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get avdd voltage\n");
- ret = devm_regulator_get_enable_optional(dev, "avss");
- if (ret < 0 && ret != -ENODEV)
- return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+ st->refp_uV[ADS1262_RMUXP_AVDD] = ret;
+ } else {
+ ret = devm_regulator_get_enable(dev, "avdd");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
+ }
+
+ /*
+ * REVISIT: The AVSS supply has a minimum of -2.5V and maximum of 0V.
+ * Currently the regulator subsystem doesn't support negative voltages,
+ * so we assume the value returned here is actually the magnitude
+ * (absolute value).
+ *
+ * This limitation forces us to assume that, if we have a bipolar supply
+ * (AVSS < 0V), all negative references are below ground (REFN <= 0V)
+ * and positive references are above ground (REFP >= 0V), as this is the
+ * most common configuration.
+ */
+ if (st->need_avss_uV) {
+ ret = devm_regulator_get_enable_read_voltage(dev, "avss");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to get avss voltage\n");
+
+ if (ret != -ENODEV) {
+ st->refn_uV[ADS1262_RMUXN_AVSS] = -ret;
+ st->bipolar_supply = true;
+ }
+ } else {
+ ret = devm_regulator_get_enable_optional(dev, "avss");
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to get avss regulator\n");
+
+ if (ret != -ENODEV)
+ st->bipolar_supply = true;
+ }
return 0;
}
@@ -939,6 +1330,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = ads1262_parse_references(st);
+ if (ret)
+ return ret;
+
ret = ads1262_populate_tables(indio_dev);
if (ret)
return ret;
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (3 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents Kurt Borja
` (4 subsequent siblings)
9 siblings, 1 reply; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Enable per-channel input chopping via the "input-chopping" property.
As the device withholds the first conversion in chop mode, single reads
are taken in CONTINUOUS run mode with a brief start/stop pulse, as
recommended by the datasheet (Section 9.4.1.2).
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 27 ++++++++++++++++++++++-----
1 file changed, 22 insertions(+), 5 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 360ce01a5871..e2659941e1af 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -210,6 +210,7 @@ struct ads1262_channel {
u8 ref_n;
bool ref_reversal;
bool is_resistance;
+ bool input_chop;
int offset;
int scales[6][2];
size_t num_scales;
@@ -374,7 +375,7 @@ static int ads1262_dev_stop(struct ads1262 *st)
return ret;
}
-static int ads1262_dev_start_one(struct ads1262 *st)
+static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode)
{
int ret;
@@ -382,7 +383,7 @@ static int ads1262_dev_start_one(struct ads1262 *st)
if (ret)
return ret;
- if (st->start_gpiod) {
+ if (runmode == ADS1262_RUNMODE_CONTINUOUS || st->start_gpiod) {
/*
* The START pulse timing requirement is 4 clock cycles, at the
* minimum clock rate this is 4 microseconds.
@@ -431,8 +432,10 @@ static int ads1262_channel_enable(struct ads1262 *st,
guard(mutex)(&st->xfer_lock);
guard(mutex)(&st->chan_lock);
- val = FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
+ val = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+ FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_MODE0_INPUT_CHOP_MASK |
ADS1262_MODE0_REFREV_MASK, val);
if (ret)
return ret;
@@ -473,13 +476,26 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
const struct iio_chan_spec *spec, __be32 *val)
{
struct ads1262 *st = iio_priv(indio_dev);
+ struct ads1262_channel *chan = &st->channels[spec->scan_index];
+ u8 runmode;
int ret;
IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
if (IIO_DEV_ACQUIRE_FAILED(claim))
return -EBUSY;
- ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
+ /*
+ * When a channel has chop mode or IDAC rotation mode, the first
+ * conversion is always withheld so the datasheet suggests using the
+ * CONTINUOUS mode and briefly starting and stopping conversions to
+ * achieve the same effect (Section 9.4.1.2).
+ */
+ if (chan->input_chop)
+ runmode = ADS1262_RUNMODE_CONTINUOUS;
+ else
+ runmode = ADS1262_RUNMODE_PULSE;
+
+ ret = ads1262_set_runmode(st, runmode);
if (ret)
return ret;
@@ -489,7 +505,7 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
reinit_completion(&st->drdy);
- ret = ads1262_dev_start_one(st);
+ ret = ads1262_dev_start_one(st, runmode);
if (ret)
return ret;
@@ -1134,6 +1150,7 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
}
}
+ chan->input_chop = fwnode_property_read_bool(node, "input-chopping");
chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (4 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 4:13 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
` (3 subsequent siblings)
9 siblings, 2 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Support the two IDAC excitation current sources. Each channel can route
its IDAC1/IDAC2 outputs to a pin via the "excitation-channels" property
and select a magnitude via "excitation-current-nanoamp".
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 101 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 98 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index e2659941e1af..d5464b4f2bfb 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -98,6 +98,8 @@
#define ADS1262_IDACMUX_NO_CONN 0xB
#define ADS1262_IDACMAG_REG 0x0E
+#define ADS1262_IDACMAG_MAG2_MASK GENMASK(7, 4)
+#define ADS1262_IDACMAG_MAG1_MASK GENMASK(3, 0)
#define ADS1262_REFMUX_REG 0x0F
#define ADS1262_REFMUX_RMUXP_MASK GENMASK(5, 3)
@@ -205,12 +207,15 @@ struct ads1262_chip_info {
struct ads1262_channel {
u8 data_rate;
+ u8 idac_mux[2];
+ u8 idac_mag[2];
u8 gain;
u8 ref_p;
u8 ref_n;
bool ref_reversal;
bool is_resistance;
bool input_chop;
+ bool idac_chop;
int offset;
int scales[6][2];
size_t num_scales;
@@ -277,6 +282,25 @@ static const char * const ads1262_ref_sources_neg[] = {
NULL
};
+static const u32 ads1262_idac_mags_nA[] = {
+ 0, 50000, 100000, 250000, 500000, 750000, 1000000, 1500000, 2000000,
+ 2500000, 3000000
+};
+
+static int ads1262_find_one(const int *array, size_t num_elements, int val)
+{
+ int i;
+
+ for (i = 0; i < num_elements; i++) {
+ if (val == array[i])
+ break;
+ }
+ if (i == num_elements)
+ return -EINVAL;
+
+ return i;
+}
+
static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
int val2)
{
@@ -433,9 +457,11 @@ static int ads1262_channel_enable(struct ads1262 *st,
guard(mutex)(&st->chan_lock);
val = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+ FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
ADS1262_MODE0_INPUT_CHOP_MASK |
+ ADS1262_MODE0_IDAC_CHOP_MASK |
ADS1262_MODE0_REFREV_MASK, val);
if (ret)
return ret;
@@ -456,6 +482,22 @@ static int ads1262_channel_enable(struct ads1262 *st,
if (ret)
return ret;
+ val = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+ FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[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, chan->idac_mag[0]) |
+ FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
+ ret = regmap_update_bits(st->regmap, ADS1262_IDACMAG_REG,
+ ADS1262_IDACMAG_MAG1_MASK |
+ ADS1262_IDACMAG_MAG2_MASK, val);
+ if (ret)
+ return ret;
+
val = FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n) |
FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p);
return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
@@ -490,7 +532,7 @@ static int ads1262_channel_read(struct iio_dev *indio_dev,
* CONTINUOUS mode and briefly starting and stopping conversions to
* achieve the same effect (Section 9.4.1.2).
*/
- if (chan->input_chop)
+ if (chan->input_chop || chan->idac_chop)
runmode = ADS1262_RUNMODE_CONTINUOUS;
else
runmode = ADS1262_RUNMODE_PULSE;
@@ -1070,8 +1112,8 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
struct device *dev = &st->spi->dev;
const char *sources[2];
char name[sizeof("ti,refpN-refnM-resistor-ohms")];
- u32 pins[2];
- int ret;
+ u32 pins[2], mags[2];
+ int count, ret;
if (fwnode_property_present(node, "single-channel")) {
ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
@@ -1150,7 +1192,58 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
}
}
+ if (fwnode_property_present(node, "excitation-channels")) {
+ count = fwnode_property_count_u32(node, "excitation-channels");
+ if (count < 0)
+ return dev_err_probe(dev, count,
+ "%s: failed to count excitation-channels\n",
+ fwnode_get_name(node));
+
+ pins[0] = ADS1262_IDACMUX_NO_CONN;
+ pins[1] = ADS1262_IDACMUX_NO_CONN;
+ ret = fwnode_property_read_u32_array(node, "excitation-channels",
+ pins, min(count, ARRAY_SIZE(pins)));
+ if (ret)
+ return dev_err_probe(dev, ret, "%s: failed to read excitation-channels\n",
+ fwnode_get_name(node));
+ if (pins[0] > ADS1262_IDACMUX_NO_CONN || pins[1] > ADS1262_IDACMUX_NO_CONN)
+ return dev_err_probe(dev, -EINVAL, "%s: excitation-channels not in range\n",
+ fwnode_get_name(node));
+ chan->idac_mux[0] = pins[0];
+ chan->idac_mux[1] = pins[1];
+
+ mags[0] = 0;
+ mags[1] = 0;
+ ret = fwnode_property_read_u32_array(node, "excitation-current-nanoamp",
+ mags, min(count, ARRAY_SIZE(mags)));
+ if (ret == -EOVERFLOW)
+ return dev_err_probe(dev, ret,
+ "%s: excitation-current-nanoamp size mismatch\n",
+ fwnode_get_name(node));
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "%s: failed to read excitation-current-nanoamp\n",
+ fwnode_get_name(node));
+
+ ret = ads1262_find_one(ads1262_idac_mags_nA,
+ ARRAY_SIZE(ads1262_idac_mags_nA), mags[0]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "%s: invalid excitation-current-nanoamp\n",
+ fwnode_get_name(node));
+ chan->idac_mag[0] = ret;
+
+ ret = ads1262_find_one(ads1262_idac_mags_nA,
+ ARRAY_SIZE(ads1262_idac_mags_nA), mags[1]);
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "%s: invalid excitation-current-nanoamp\n",
+ fwnode_get_name(node));
+ chan->idac_mag[1] = ret;
+ }
+
chan->input_chop = fwnode_property_read_bool(node, "input-chopping");
+ chan->idac_chop = fwnode_property_read_bool(node, "ti,idac-rotation");
chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
return 0;
@@ -1198,6 +1291,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
fwnode_get_name(node));
st->channels[reg].data_rate = ADS1262_DR_20_SPS;
+ st->channels[reg].idac_mux[0] = ADS1262_IDACMUX_NO_CONN;
+ st->channels[reg].idac_mux[1] = ADS1262_IDACMUX_NO_CONN;
specs[reg].scan_index = reg;
specs[reg].scan_type = (struct iio_scan_type) {
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (5 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 4:09 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
` (2 subsequent siblings)
9 siblings, 2 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Add triggered buffer support and a data-ready (DRDY) hardware trigger.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/Kconfig | 2 +
drivers/iio/adc/ti-ads1262.c | 264 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 266 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index dbf76427912b..b9b561be8347 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1845,6 +1845,8 @@ config TI_ADS1262
tristate "Texas Instruments ADS1262"
depends on SPI
select REGMAP
+ select IIO_BUFFER
+ select IIO_TRIGGERED_BUFFER
help
If you say yes here you get support for Texas Instruments ADS1262 and
ADS1263 ADC chips.
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index d5464b4f2bfb..24a7ecb9fbd4 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -34,6 +34,9 @@
#include <asm/byteorder.h>
#include <linux/iio/iio.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
#define ADS1262_OPCODE_NOP 0x00
#define ADS1262_OPCODE_RESET 0x06
@@ -225,6 +228,7 @@ struct ads1262_channel {
struct ads1262 {
struct spi_device *spi;
struct regmap *regmap;
+ struct iio_trigger *trig;
struct gpio_desc *reset_gpiod;
struct gpio_desc *start_gpiod;
unsigned long clk_rate;
@@ -241,8 +245,16 @@ struct ads1262 {
bool need_avss_uV;
bool bipolar_supply;
+ IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
+ ADS1262_MAX_CHANNEL_COUNT);
+
/* Protects transfer buffers and concurrent SPI transfers */
struct mutex xfer_lock;
+ struct spi_message msg;
+ struct spi_transfer xfer;
+
+ u8 tx[11] __aligned(IIO_DMA_MINALIGN);
+ u8 rx[11] __aligned(IIO_DMA_MINALIGN);
};
static const u32 ads1262_data_rate_div[] = {
@@ -708,10 +720,242 @@ static const struct iio_info ads1262_iio_info = {
.debugfs_reg_access = ads1262_debugfs_reg_access,
};
+static int ads1262_buffer_preenable(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ unsigned int weight;
+ unsigned long i;
+ int ret;
+
+ weight = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ if (weight > 1) {
+ /*
+ * Multiple channels use software sequencing: a single
+ * contiguous transfer rewrites the per-channel configuration
+ * registers in two (non-contiguous) groups.
+ *
+ * Group 1: write protocol (2 bytes) + MODE0, MODE1, MODE2,
+ * INPMUX (4 registers).
+ *
+ * Group 2: write protocol (2 bytes) + IDACMUX, IDACMAG,
+ * REFMUX (3 registers).
+ *
+ * Total: 11 bytes
+ */
+ st->xfer.len = 11;
+ } else {
+ /*
+ * A single channel is read by command (RDATA1), so the transfer
+ * holds the command byte plus the 4 conversion bytes.
+ *
+ * Total: 5 bytes
+ */
+ st->xfer.len = 5;
+
+ /*
+ * 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 (command
+ * mode).
+ */
+ memset(st->tx, 0, st->xfer.len);
+ st->tx[0] = ADS1262_OPCODE_RDATA1;
+
+ i = find_first_bit(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ ret = ads1262_channel_enable(st, &indio_dev->channels[i]);
+ if (ret)
+ return ret;
+ }
+
+ ret = ads1262_set_runmode(st, ADS1262_RUNMODE_CONTINUOUS);
+ if (ret)
+ return ret;
+
+ ret = spi_optimize_message(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ ret = ads1262_dev_start(st);
+ if (ret) {
+ spi_unoptimize_message(&st->msg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ads1262_buffer_postdisable(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ unsigned int weight;
+
+ ads1262_dev_stop(st);
+ spi_unoptimize_message(&st->msg);
+
+ weight = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+ if (weight > 1) {
+ regcache_drop_region(st->regmap, ADS1262_MODE0_REG,
+ ADS1262_INPMUX_REG);
+ regcache_drop_region(st->regmap, ADS1262_IDACMUX_REG,
+ ADS1262_REFMUX_REG);
+ }
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ads1262_buffer_ops = {
+ .preenable = ads1262_buffer_preenable,
+ .postdisable = ads1262_buffer_postdisable,
+};
+
+static int ads1262_enable_and_read_last(struct ads1262 *st,
+ const struct iio_chan_spec *spec,
+ __be32 *val)
+{
+ struct ads1262_channel *chan;
+ int ret;
+
+ lockdep_assert_held(&st->xfer_lock);
+
+ if (spec) {
+ guard(mutex)(&st->chan_lock);
+
+ chan = &st->channels[spec->scan_index];
+
+ /* Group 1: MODE0, MODE1, MODE2, INPMUX */
+ st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
+ st->tx[1] = ADS1262_INPMUX_REG - ADS1262_MODE0_REG;
+ st->tx[2] = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
+ FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
+ FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, ADS1262_RUNMODE_CONTINUOUS) |
+ FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
+ st->tx[3] = FIELD_PREP(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR);
+ st->tx[4] = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
+ FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
+ st->tx[5] = FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel) |
+ FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2);
+
+ /* Group 2: IDACMUX, IDACMAG, REFMUX */
+ st->tx[6] = ADS1262_IDACMUX_REG | ADS1262_OPCODE_WREG;
+ st->tx[7] = ADS1262_REFMUX_REG - ADS1262_IDACMUX_REG;
+ st->tx[8] = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
+ FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]);
+ st->tx[9] = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
+ FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
+ st->tx[10] = FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p) |
+ FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n);
+ } else {
+ memset(st->tx, 0, sizeof(st->tx));
+ }
+
+ ret = spi_sync(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ memcpy(val, st->rx, sizeof(*val));
+
+ return 0;
+}
+
+static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ unsigned int chan;
+ __be32 val;
+ int i = -1;
+ int ret;
+
+ /*
+ * This routine enables and reads channels in a full-duplex fashion.
+ *
+ * When a channel is enabled, the previous conversion is clocked out of
+ * the shift data register on the same transfer (Section 9.4.7.1). This
+ * allows for low latency software sequencing but forbids any
+ * communication with the chip in-between or data corruption may occur,
+ * hence the need to take the xfer_lock for the whole operation.
+ */
+ guard(mutex)(&st->xfer_lock);
+
+ iio_for_each_active_channel(indio_dev, chan) {
+ ret = ads1262_enable_and_read_last(st, &indio_dev->channels[chan],
+ &val);
+ if (ret)
+ return ret;
+
+ /*
+ * After writing to the channel configuration registers, the
+ * conversion-cycle is restarted and the data registers are
+ * cleared. This means we have to reinit the completion after
+ * enabling to avoid reading stale data.
+ */
+ reinit_completion(&st->drdy);
+
+ if (i > -1)
+ st->scan_buffer[i] = val;
+ i++;
+
+ ret = ads1262_wait_for_conversion(st);
+ if (ret)
+ return ret;
+ }
+
+ return ads1262_enable_and_read_last(st, NULL, &st->scan_buffer[i]);
+}
+
+static int ads1262_fill_buffer_one(struct iio_dev *indio_dev)
+{
+ struct ads1262 *st = iio_priv(indio_dev);
+ int ret;
+
+ guard(mutex)(&st->xfer_lock);
+
+ ret = spi_sync(st->spi, &st->msg);
+ if (ret)
+ return ret;
+
+ /* In command mode the conversion data is found at offset 1 */
+ memcpy(st->scan_buffer, &st->rx[1], sizeof(*st->scan_buffer));
+
+ return 0;
+}
+
+static irqreturn_t ads1262_trigger_handler(int irq, void *p)
+{
+ struct iio_poll_func *pf = p;
+ struct iio_dev *indio_dev = pf->indio_dev;
+ struct ads1262 *st = iio_priv(indio_dev);
+ s64 ts = pf->timestamp;
+ unsigned int weight;
+ int ret;
+
+ weight = bitmap_weight(indio_dev->active_scan_mask,
+ iio_get_masklength(indio_dev));
+
+ if (weight == 1)
+ ret = ads1262_fill_buffer_one(indio_dev);
+ else
+ ret = ads1262_fill_buffer_mult(indio_dev);
+ if (ret)
+ goto out_notify_done;
+
+ iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer,
+ sizeof(st->scan_buffer), ts);
+
+out_notify_done:
+ iio_trigger_notify_done(indio_dev->trig);
+
+ return IRQ_HANDLED;
+}
+
static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
{
struct ads1262 *st = dev_id;
+ iio_trigger_poll(st->trig);
complete(&st->drdy);
return IRQ_HANDLED;
@@ -1414,6 +1658,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
st->spi = spi;
init_completion(&st->drdy);
+ st->xfer.tx_buf = st->tx;
+ st->xfer.rx_buf = st->rx;
+ spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
+
ret = devm_mutex_init(dev, &st->chan_lock);
if (ret)
return ret;
@@ -1459,6 +1707,22 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return dev_err_probe(dev, ret, "failed to configure device\n");
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ads1262_trigger_handler,
+ &ads1262_buffer_ops);
+ if (ret)
+ return ret;
+
+ st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", info->name,
+ iio_device_id(indio_dev));
+ if (!st->trig)
+ return -ENOMEM;
+ iio_trigger_set_drvdata(st->trig, st);
+ ret = devm_iio_trigger_register(dev, st->trig);
+ if (ret)
+ return ret;
+
/*
* REVISIT: This chip has software polling capabilities, which could be
* used to stop depending on the 'drdy' IRQ.
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (6 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:40 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
2026-08-08 18:37 ` [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support David Lechner
9 siblings, 2 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Register the "refout" and "vbias" regulators to be able to use them as
common mode supplies.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ti-ads1262.c | 90 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 91 insertions(+)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index b9b561be8347..e5206438ac90 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -1845,6 +1845,7 @@ config TI_ADS1262
tristate "Texas Instruments ADS1262"
depends on SPI
select REGMAP
+ select REGULATOR
select IIO_BUFFER
select IIO_TRIGGERED_BUFFER
help
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 24a7ecb9fbd4..533574169b04 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -26,6 +26,7 @@
#include <linux/property.h>
#include <linux/regmap.h>
#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
#include <linux/spi/spi.h>
#include <linux/string.h>
#include <linux/types.h>
@@ -961,6 +962,91 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static int ads1262_regulator_enable(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_set_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_regulator_disable(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+
+ guard(mutex)(&st->xfer_lock);
+
+ return regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
+ ADS1262_POWER_VBIAS_MASK);
+}
+
+static int ads1262_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct ads1262 *st = rdev_get_drvdata(rdev);
+ unsigned int val;
+ int ret;
+
+ guard(mutex)(&st->xfer_lock);
+
+ ret = regmap_read(st->regmap, ADS1262_POWER_REG, &val);
+ if (ret)
+ return ret;
+
+ return field_get(ADS1262_POWER_VBIAS_MASK, val);
+}
+
+static const struct regulator_ops ads1262_vbias_regulator_ops = {
+ .enable = ads1262_regulator_enable,
+ .disable = ads1262_regulator_disable,
+ .is_enabled = ads1262_regulator_is_enabled,
+};
+
+static const struct regulator_ops ads1262_refout_regulator_ops = { };
+
+static const struct regulator_desc ads1262_vbias_regulator_desc = {
+ .name = "vbias",
+ .of_match = "vbias",
+ .regulators_node = "regulators",
+ .supply_name = "avdd",
+ .ops = &ads1262_vbias_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static const struct regulator_desc ads1262_refout_regulator_desc = {
+ .name = "refout",
+ .of_match = "refout",
+ .regulators_node = "regulators",
+ .supply_name = "avdd",
+ .n_voltages = 1,
+ .fixed_uV = 2500000,
+ .ops = &ads1262_refout_regulator_ops,
+ .type = REGULATOR_VOLTAGE,
+ .owner = THIS_MODULE,
+};
+
+static int ads1262_register_regulators(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct regulator_config config = {
+ .dev = dev,
+ .driver_data = st,
+ };
+ struct regulator_dev *rdev;
+
+ rdev = devm_regulator_register(dev, &ads1262_refout_regulator_desc,
+ &config);
+ if (IS_ERR(rdev))
+ return PTR_ERR(rdev);
+
+ rdev = devm_regulator_register(dev, &ads1262_vbias_regulator_desc,
+ &config);
+
+ return PTR_ERR_OR_ZERO(rdev);
+}
+
static int ads1262_dev_configure(struct ads1262 *st)
{
struct device *dev = &st->spi->dev;
@@ -1707,6 +1793,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return dev_err_probe(dev, ret, "failed to configure device\n");
+ ret = ads1262_register_regulators(st);
+ if (ret)
+ return ret;
+
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
ads1262_trigger_handler,
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (7 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
@ 2026-08-08 3:58 ` Kurt Borja
2026-08-08 18:40 ` David Lechner
2026-08-08 18:37 ` [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support David Lechner
9 siblings, 1 reply; 36+ messages in thread
From: Kurt Borja @ 2026-08-08 3:58 UTC (permalink / raw)
To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Linus Walleij, Bartosz Golaszewski, David Lechner
Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree,
linux-kernel, linux-gpio, Jonathan Cameron
Enable common mode regulators. The usual configuration is to have our
own 'vbias' regulator connected internally as common mode voltage on the
AINCOM pin.
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
drivers/iio/adc/ti-ads1262.c | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
index 533574169b04..238d803abc50 100644
--- a/drivers/iio/adc/ti-ads1262.c
+++ b/drivers/iio/adc/ti-ads1262.c
@@ -962,6 +962,27 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
return IRQ_HANDLED;
}
+static int ads1262_common_mode_setup(struct ads1262 *st)
+{
+ struct device *dev = &st->spi->dev;
+ char name[sizeof("aincom")];
+ int ret;
+
+ for (unsigned int i = 0; i <= ADS1262_INPMUX_AINCOM; i++) {
+ if (i < ADS1262_INPMUX_AINCOM)
+ scnprintf(name, sizeof(name), "ain%u", i);
+ else
+ scnprintf(name, sizeof(name), "aincom");
+
+ ret = devm_regulator_get_enable_optional(dev, name);
+ if (ret < 0 && ret != -ENODEV)
+ return dev_err_probe(dev, ret,
+ "failed to get common mode supply: %s\n", name);
+ }
+
+ return 0;
+}
+
static int ads1262_regulator_enable(struct regulator_dev *rdev)
{
struct ads1262 *st = rdev_get_drvdata(rdev);
@@ -1797,6 +1818,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
if (ret)
return ret;
+ ret = ads1262_common_mode_setup(st);
+ if (ret)
+ return ret;
+
ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
iio_pollfunc_store_time,
ads1262_trigger_handler,
--
2.55.0
^ permalink raw reply related [flat|nested] 36+ messages in thread
* Re: [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
@ 2026-08-08 4:08 ` sashiko-bot
2026-08-08 18:38 ` David Lechner
1 sibling, 0 replies; 36+ messages in thread
From: sashiko-bot @ 2026-08-08 4:08 UTC (permalink / raw)
To: Kurt Borja; +Cc: devicetree, conor+dt, robh
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] The `interrupt-names` constraint incorrectly uses `enum` directly instead of `items: enum:`, causing schema validation failures.
- [Medium] The `interrupt-names` property is semantically abused to configure hardware routing instead of naming interrupts.
- [Medium] The compatible string `ti,ads1263` lists `ti,ads1262` as a fallback, but they have incompatible IIO provider interfaces due to differing `#io-channel-cells` requirements.
--
--- Patch [1]: [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family ---
commit b073c29433e55e8e8b4d7125bae2d50b6eaba149
Author: Kurt Borja <kuurtb@gmail.com>
dt-bindings: iio: adc: support the TI ADS126x ADC family
The commit adds devicetree bindings for the TI ADS1262 and ADS1263 ADCs.
It defines the required and optional properties, including regulators, input
channels, excitation currents, and interrupts.
> diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> new file mode 100644
> index 0000000000000..c43ba7abb3916
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> @@ -0,0 +1,379 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: TI ADS1262/ADS1263 analog to digital converter
> +
> +maintainers:
> + - Kurt Borja <kuurtb@gmail.com>
> +
> +description: |
> + The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an
> + integrated PGA, reference, and internal fault monitors. The ADS1263 integrates
> + an auxiliary, 24-bit, ΔΣ ADC intended for background measurements.
> +
> + Datasheets:
> + - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf
> +
> +properties:
> + compatible:
> + oneOf:
> + - const: ti,ads1262
> + - items:
> + - const: ti,ads1263
> + - const: ti,ads1262
[Severity: Medium]
Since ti,ads1263 dictates #io-channel-cells: const: 2 (lower down in the
binding) and ti,ads1262 dictates const: 1, are they truly ABI compatible?
If an older OS driver only knows ti,ads1262, it will expect 1 cell. When
the IIO subsystem attempts to translate a consumer phandle with 2 cells via
of_xlate, it will reject it due to an invalid argument count, breaking all
IIO consumers.
Should ti,ads1263 just be a standalone compatible string rather than using
ti,ads1262 as a fallback?
[ ... ]
> + interrupt-names:
> + description:
> + Specify which pin should be configured as Data Ready interrupt.
> + enum: [drdy, dout-drdy]
[Severity: High]
Does this schema validation fail for device trees using interrupt-names?
In JSON schema, DT -names properties are implicitly parsed as arrays of
strings (via the core interrupts.yaml meta-schema). By using enum
directly on the array property instead of items: enum:, the schema checks
if the entire array instance (e.g., ["drdy"]) perfectly matches one of the
scalar string values in the enum list. Since an array is never equal to a
string, this will always evaluate to false, breaking schema validation.
[Severity: Medium]
Is interrupt-names being used here to configure hardware routing rather
than identifying interrupts to the OS driver?
The devicetree specification dictates that interrupt-names is strictly used
to map human-readable names to indices in the interrupts array. It must not
be used as a configuration property to tell the driver which physical pin on
the ADC to route a signal to.
Could a custom property (e.g., ti,drdy-pin) be used for hardware routing
configuration instead?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-ads126x-v3-0-f89925d72792@gmail.com?part=1
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling
2026-08-08 3:58 ` [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
@ 2026-08-08 4:09 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
1 sibling, 0 replies; 36+ messages in thread
From: sashiko-bot @ 2026-08-08 4:09 UTC (permalink / raw)
To: Kurt Borja; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Race condition between the hardware DRDY interrupt and `reinit_completion()` causes missed conversions and timeouts.
--
--- Patch [7]: [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling ---
commit a9e8c114122dfc9b8eb2cdf234373c01ea35d2cf
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support triggered buffer sampling
This commit adds triggered buffer support and a data-ready (DRDY)
hardware trigger for the Texas Instruments ADS1262 and ADS1263 ADCs.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index d5464b4f2bfb2..24a7ecb9fbd40 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> +static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + unsigned int chan;
> + __be32 val;
> + int i = -1;
> + int ret;
> +
> + /*
> + * This routine enables and reads channels in a full-duplex fashion.
> + *
> + * When a channel is enabled, the previous conversion is clocked out of
> + * the shift data register on the same transfer (Section 9.4.7.1). This
> + * allows for low latency software sequencing but forbids any
> + * communication with the chip in-between or data corruption may occur,
> + * hence the need to take the xfer_lock for the whole operation.
> + */
> + guard(mutex)(&st->xfer_lock);
> +
> + iio_for_each_active_channel(indio_dev, chan) {
> + ret = ads1262_enable_and_read_last(st, &indio_dev->channels[chan],
> + &val);
> + if (ret)
> + return ret;
> +
> + /*
> + * After writing to the channel configuration registers, the
> + * conversion-cycle is restarted and the data registers are
> + * cleared. This means we have to reinit the completion after
> + * enabling to avoid reading stale data.
> + */
> + reinit_completion(&st->drdy);
[Severity: High]
Can a fast conversion cause a race condition here that leads to a timeout?
If the sample rate is high (e.g., 38400 SPS, ~26µs latency), is it possible
for the hardware DRDY interrupt to fire immediately after the SPI transfer
in ads1262_enable_and_read_last() finishes, but before reinit_completion()
executes?
If the interrupt handler executes first and calls complete(&st->drdy),
would this reinit_completion() locklessly reset the state back to 0,
wiping out the completion signal?
If so, would the subsequent ads1262_wait_for_conversion() then wait
for an event that was already cleared, eventually returning -ETIMEDOUT?
> +
> + if (i > -1)
> + st->scan_buffer[i] = val;
> + i++;
> +
> + ret = ads1262_wait_for_conversion(st);
> + if (ret)
> + return ret;
> + }
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-ads126x-v3-0-f89925d72792@gmail.com?part=7
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-08-08 3:58 ` [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
@ 2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
1 sibling, 0 replies; 36+ messages in thread
From: sashiko-bot @ 2026-08-08 4:11 UTC (permalink / raw)
To: Kurt Borja; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Passing an `int *` to `div_u64_rem()` where a `u32 *` is expected will trigger a `-Wincompatible-pointer-types` error, breaking the build.
--
--- Patch [3]: [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency ---
commit a46f605d6f5895d7bee6634dd981e6c2e2026419
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support per-channel sampling frequency
This patch adds per-channel sampling frequency support to the ti-ads1262
driver. The "available" attribute is assigned per-channel to eventually
support per-filter availability.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index d78e5e3ae13ee..b3b7b12491026 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -575,6 +688,36 @@ static const struct regmap_bus ads1262_regmap_bus = {
> .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
> };
>
> +static void ads1262_populate_samp_freqs(struct ads1262 *st,
> + struct ads1262_channel *chan)
> +{
> + int freq_Hz, freq_rem;
> + u64 freq_uHz;
> +
> + for (unsigned int i = 0; i < ARRAY_SIZE(chan->samp_freqs); i++) {
> + freq_uHz = div_u64(mul_u32_u32(st->clk_rate, MICRO),
> + ads1262_data_rate_div[i]);
> + freq_Hz = div_u64_rem(freq_uHz, MICRO, &freq_rem);
[Severity: Low]
Will this cause a build failure due to incompatible pointer types?
div_u64_rem() expects a u32 * for its third argument, but freq_rem is
declared as an int. In modern kernel builds, -Wincompatible-pointer-types
warnings are treated as errors, which would break the build here.
Could freq_rem be declared as a u32 to prevent this mismatch?
> +
> + chan->samp_freqs[i][0] = freq_Hz;
> + chan->samp_freqs[i][1] = freq_rem;
> + }
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-ads126x-v3-0-f89925d72792@gmail.com?part=3
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
2026-08-08 3:58 ` [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
@ 2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:40 ` David Lechner
1 sibling, 0 replies; 36+ messages in thread
From: sashiko-bot @ 2026-08-08 4:11 UTC (permalink / raw)
To: Kurt Borja; +Cc: conor+dt, devicetree, robh
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] The `refout` regulator is registered with empty operations, causing `regulator_enable()` to fail with `-EINVAL` and leaving the internal reference disabled.
- [Medium] The `vbias` regulator lacks a `.get_voltage` callback, causing it to incorrectly report its parent's full voltage instead of the mid-supply voltage.
--
--- Patch [8]: [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators ---
commit 8d51db315e9b593705eb805bce9b112c942e4ee3
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
Register the "refout" and "vbias" regulators to be able to use them as
common mode supplies.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 24a7ecb9fbd40..533574169b044 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -961,6 +962,91 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
> return IRQ_HANDLED;
> }
[ ... ]
> +static const struct regulator_ops ads1262_vbias_regulator_ops = {
> + .enable = ads1262_regulator_enable,
> + .disable = ads1262_regulator_disable,
> + .is_enabled = ads1262_regulator_is_enabled,
> +};
[Severity: Medium]
Since this operations structure lacks a .get_voltage callback, won't calls
to regulator_get_voltage() fall back to returning the parent's full voltage
(AVDD) instead of the expected mid-supply voltage?
> +
> +static const struct regulator_ops ads1262_refout_regulator_ops = { };
[Severity: High]
Since this operations structure is empty and lacks an .enable callback, won't
regulator_enable() fail with -EINVAL?
This also means there is no callback to configure the hardware by setting the
ADS1262_POWER_INTREF_MASK bit, leaving the internal reference disabled.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-ads126x-v3-0-f89925d72792@gmail.com?part=8
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
@ 2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-08 22:28 ` Uwe Kleine-König
2 siblings, 0 replies; 36+ messages in thread
From: sashiko-bot @ 2026-08-08 4:11 UTC (permalink / raw)
To: Kurt Borja; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Inadequate delay after device reset will cause initialization to fail or silently leave the ADC in its default state, corrupting all data reads.
- [Medium] Regmap debugfs accesses bypass the driver's custom transfer lock, causing an unconditional lockdep splat.
--
--- Patch [2]: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver ---
commit 4680c0f9dff8858f99d46d1498d733421fe96922
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: add the ti-ads1262 driver
Add the ti-ads1262 driver with initial support for the primary ADC (ADC1).
The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver and
interoperability considerations were taken into account.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> new file mode 100644
> index 0000000000000..d78e5e3ae13ee
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> +static int ads1262_dev_reset(struct ads1262 *st)
> +{
> + int ret;
> +
> + if (st->reset_gpiod) {
> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET pulse timing requirement is 4 clock cycles, at the
> + * minimum clock rate this is 4 microseconds.
> + */
> + fsleep(4);
> +
> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 0);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds
> + */
> + fsleep(8);
> + } else {
> + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds
> + */
> + fsleep(8);
[Severity: High]
Is the 8 microsecond delay sufficient for the device to initialize after reset?
The standard ADC initialization requirement is typically much longer, requiring
4096 clock cycles for recovery (which corresponds to ~555 microseconds at
7.3728 MHz or ~4 milliseconds at 1 MHz).
If the delay is severely inadequate, the device might ignore the subsequent
SPI configuration writes in ads1262_dev_configure().
This could leave the STATUS and CRC bytes enabled in the data output format
by default, causing ads1262_channel_read() to read the 1-byte STATUS followed
by the upper 24 bits of data instead of the actual 32-bit conversion data,
corrupting the readings.
> + }
> +
> + return 0;
> +}
[ ... ]
> +static const struct regmap_config ads1262_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .writeable_reg = ads1262_writeable_reg,
> + .readable_reg = ads1262_readable_reg,
> + .volatile_reg = ads1262_volatile_reg,
> + .reg_defaults = ads1262_reg_defaults,
> + .num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
> + .max_register = ADS1262_ADC2FSC1_REG,
> + .can_sleep = true,
> + .cache_type = REGCACHE_MAPLE,
> +};
[Severity: Medium]
Could this configuration cause a lockdep warning when accessing the regmap
debugfs interface?
The driver provides custom regmap callbacks that assert st->xfer_lock is held,
but ads1262_regmap_config does not define custom .lock and .unlock callbacks.
Because of this, the regmap core will use its default internal mutex.
When userspace reads the regmap debugfs file, it will call into
ads1262_regmap_read() without holding st->xfer_lock:
> +static int ads1262_regmap_read(void *context, const void *reg_buf,
> + size_t reg_size, void *val_buf, size_t val_size)
> +{
> + struct ads1262 *st = context;
> + u8 tx[2];
> +
> + lockdep_assert_held(&st->xfer_lock);
This assertion would unconditionally fail.
> +
> + /*
> + * The register read operation uses a two byte command header followed
> + * by the register data:
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-ads126x-v3-0-f89925d72792@gmail.com?part=2
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents
2026-08-08 3:58 ` [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents Kurt Borja
@ 2026-08-08 4:13 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
1 sibling, 0 replies; 36+ messages in thread
From: sashiko-bot @ 2026-08-08 4:13 UTC (permalink / raw)
To: Kurt Borja; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] IDAC excitation currents will not work because the ADC's internal voltage reference is never enabled.
- [Low] Build failure due to incompatible pointer types when passing 'ads1262_idac_mags_nA' to 'ads1262_find_one()'.
--
--- Patch [6]: [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents ---
commit 62f1b6149a86f48a45e748f65baba909e175b7ee
Author: Kurt Borja <kuurtb@gmail.com>
iio: adc: ti-ads1262: support excitation currents
This commit adds support for two IDAC excitation current sources in the
ti-ads1262 driver. It allows routing IDAC1 and IDAC2 to specific pins and
configuring their magnitudes via device tree properties.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
[ ... ]
> @@ -456,6 +482,22 @@ static int ads1262_channel_enable(struct ads1262 *st,
> if (ret)
> return ret;
>
> + val = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
> + FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[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, chan->idac_mag[0]) |
> + FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
> + ret = regmap_update_bits(st->regmap, ADS1262_IDACMAG_REG,
> + ADS1262_IDACMAG_MAG1_MASK |
> + ADS1262_IDACMAG_MAG2_MASK, val);
[Severity: High]
Will the IDAC excitation currents function without the internal voltage
reference being enabled?
The driver configures the IDAC multiplexer and magnitude here in
ads1262_channel_enable(), but the ADC hardware physically requires the
internal reference (INTREF bit in the POWER register) to be active for the
current sources to output current. It appears this bit is never set in the
driver initialization or channel enable paths, leaving the internal
reference disabled by default.
[ ... ]
> @@ -1150,7 +1192,58 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
[ ... ]
> + ret = ads1262_find_one(ads1262_idac_mags_nA,
> + ARRAY_SIZE(ads1262_idac_mags_nA), mags[0]);
[Severity: Low]
Could this result in a build failure?
The ads1262_idac_mags_nA array is declared earlier in the file as a
const u32 array, but the ads1262_find_one() function explicitly expects a
const int pointer argument.
Passing an unsigned pointer where a signed pointer is expected triggers a
-Wincompatible-pointer-types warning. In modern kernel builds, this warning
is promoted to a fatal error and breaks the build.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260807-ads126x-v3-0-f89925d72792@gmail.com?part=6
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
` (8 preceding siblings ...)
2026-08-08 3:58 ` [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
@ 2026-08-08 18:37 ` David Lechner
2026-08-09 8:29 ` Kurt Borja
9 siblings, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:37 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 8/7/26 10:58 PM, Kurt Borja wrote:
...
> - @David: I added support for the monitor channels, but I prefer to
> parse them from DT instead of making them static (similar to the
> ad4170-4 approach too :p).
Why? Unless there really is some property that depends on how the
system is wired up, it seems like this is just making unnecessary
work for users to be able to use the monitor channels. And if someone
decided later that they do in fact want to use the monitoring channel
and it wasn't in the devicetree, sometimes it can be very difficult
to actually change the devicetree.
The monitor inputs also have many restrictions compared to a
normal input that it would be really hard to describe correctly
in the bindings without allowing things that should not actually
be allowed. (can't have excitation current or burnout, temperature
channel requires internal reference, most should be single-channel,
etc.)
>
> - @David: About filters... As I mentioned in the previous version, the
> data_rate configuration takes precedence over the filter selection.
> If an incompatible filter (given a data rate) is selected, the chip
> resorts to a sane compatible one when doing conversions (either
> SINC1 or plain SINC5).
>
> Now, I don't know how to expose this in userspace. Should I limit
> the sampling_frequency_available attribute (given a filter)? Or
> should it be the other way around, limit the filter_type_available
> attribute (given a data rate)?.
I figured that the filter type selection would be more important than
the rate so when I implemented it for ADS112C14, I made it so that
one has to pick the filter first and everything else flows from that.
(I didn't expose sampling frequency until the same time as filter type.)
The thinking behind this is that if you do care about filtering, then
you are picking filter type and sampling rate to get certain notches
and/or frequency response of the filter rather than trying to get a
faster or slower sample rate.
And the driver also allows using an hrtimer trigger to do single-shot
samples for cases where one doesn't want to sample as fast as possible
in continuous mode. This would be more useful to someone who just cares
about sample rate and not about filtering.
Just posted the series yesterday:
https://lore.kernel.org/linux-iio/20260807-iio-adc-ti-ads112c14-filter-support-v1-0-4d3ba00caf18@baylibre.com/T/#t
ADS126X seems a little less complicated in this regard though
as the same sampling rates are available for all filters with
the exception of the FIR filter having a limited subset. So I
would go with the option to limit sampling rate based on filter
type, not the other way around. If a higher rate is selected
when changing to the FIR filter type, just have it go to the
max (20 SPS).
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-08-08 4:08 ` sashiko-bot
@ 2026-08-08 18:38 ` David Lechner
2026-08-09 8:26 ` Kurt Borja
1 sibling, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:38 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 8/7/26 10:58 PM, Kurt Borja wrote:
> The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
> integrated PGA, internal reference, excitation and burn-out current
> sources for sensor biasing and diagnostics. The ADS1263 adds a second,
> 24-bit delta-sigma ADC (ADC2) for background measurements.
>
...
> +patternProperties:
> + "^ain([0-9]|com)-supply$":
> + description:
> + Common-mode voltage supply connected to AIN<N> or AINCOM.
> +
> + "^refp[1-3]-supply$":
> + description:
> + Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
> + REFP3 (AIN4). If not described, its assumed to be connected to ground
> + (0V).
Would we really have a case with a negative only reference? I would say if not
described, assume the pin is free for other use.
> +
> + "^refn[1-3]-supply$":
> + description:
> + Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
> + REFN3 (AIN5). If not described, its assumed to be connected to ground
> + (0V).
Assumption is only true when corresponding refp supply is described. Otherwise
we should assume the pin is free for other uses.
> +
> + "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
> + description:
> + Magnitude of the external reference resistor connected between REFP<N>
> + and REFN<M>. In ratiometric configurations, such as RTD measurements, the
> + IDAC excitation current returns through this resistor, generating the
> + reference voltage for the conversion.
> +
> + "^channel@[0-9]+$":
> + $ref: /schemas/iio/adc/adc.yaml#
> + unevaluatedProperties: false
> +
> + properties:
> + reg:
> + maxItems: 1
> +
> + single-channel:
> + minimum: 0
> + maximum: 10
> +
> + common-mode-channel:
> + minimum: 0
> + maximum: 10
> + default: 10
> +
> + diff-channels:
> + description: |
> + In addition to the analog input pins 0 (AIN0) - 10 (AINCOM), there are
> + special inputs that can be selected from the following values:
> + 11: Temperature sensor monitor
> + 12: Analog power supply monitor
> + 13: Digital power supply monitor
> + 14: TDAC test signal
For reasons mentioned in the reply to the cover letter, I'm not a fan of the
monitor channels here.
> + items:
> + minimum: 0
> + maximum: 14
> +
...
> + input-chopping: true
> +
> + ti,idac-rotation:
Should we make this one a standard property like input-chopping?
> + $ref: /schemas/types.yaml#/definitions/flag
> + description:
> + Automatically swap the IDAC1 and IDAC2 connections of alternate
> + conversions. The ADC averages the alternate conversions to eliminate
> + IDAC mismatch.
> +
...
> +examples:
> + - |
> + #include <dt-bindings/gpio/gpio.h>
> + #include <dt-bindings/interrupt-controller/irq.h>
> +
> + spi {
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + adc@0 {
> + compatible = "ti,ads1262";
> + reg = <0>;
> + spi-max-frequency = <8000000>;
> + spi-cpha;
> + avdd-supply = <&avdd>;
> + dvdd-supply = <&dvdd>;
> + #address-cells = <1>;
> + #size-cells = <0>;
> +
> + interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
> + interrupt-names = "drdy";
> +
> + /* Typical common mode voltage configuration */
> + aincom-supply = <&ads1262_vbias>;
> +
> + regulators {
> + ads1262_vbias: vbias {
> + regulator-name = "vbias";
The node name is already "vbias" so giving regulator-name is redundant.
> + };
> + };
> +
> + channel@0 {
> + reg = <0>;
> + single-channel = <0>;
> + /* The VBIAS is enabled on pin 10 (AINCOM) */
> + common-mode-channel = <10>;
> + };
> + };
> + };
> +
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-08-08 4:11 ` sashiko-bot
@ 2026-08-08 18:39 ` David Lechner
2026-08-09 8:26 ` Kurt Borja
2026-08-08 22:28 ` Uwe Kleine-König
2 siblings, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:39 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Add the ti-ads1262 driver with initial support for the primary ADC
> (ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
> and interoperability considerations were taken into account.
Should probably mention here that IIO_CHAN_INFO_SCALE is intentionally
left out here. (Or just implement it using internal reference voltage
to start with.)
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/iio/adc/Kconfig | 11 +
> drivers/iio/adc/Makefile | 1 +
> drivers/iio/adc/ti-ads1262.c | 847 +++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 860 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index e9248979b801..3ee4a2f80733 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27006,6 +27006,7 @@ M: Kurt Borja <kuurtb@gmail.com>
> L: linux-iio@vger.kernel.org
> S: Maintained
> F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml
> +F: drivers/iio/adc/ti-ads1262.c
>
> TI ADS7924 ADC DRIVER
> M: Hugo Villeneuve <hvilleneuve@dimonoff.com>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 990e7b3e7212..dbf76427912b 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1841,6 +1841,17 @@ config TI_ADS124S08
> This driver can also be built as a module. If so, the module will be
> called ti-ads124s08.
>
> +config TI_ADS1262
> + tristate "Texas Instruments ADS1262"
> + depends on SPI
> + select REGMAP
> + help
> + If you say yes here you get support for Texas Instruments ADS1262 and
> + ADS1263 ADC chips.
> +
> + This driver can also be built as a module. If so, the module will be
> + called ti-ads1262.
> +
> config TI_ADS1298
> tristate "Texas Instruments ADS1298"
> depends on SPI
> diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
> index dcec0abb03b7..f85b89859fe9 100644
> --- a/drivers/iio/adc/Makefile
> +++ b/drivers/iio/adc/Makefile
> @@ -157,6 +157,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o
> obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o
> obj-$(CONFIG_TI_ADS112C14) += ti-ads112c14.o
> obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o
> +obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o
> obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o
> obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o
> obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> new file mode 100644
> index 000000000000..d78e5e3ae13e
> --- /dev/null
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -0,0 +1,847 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Texas Instruments ADS1262 ADC driver
> + *
> + * Copyright (C) 2026 Kurt Borja <kuurtb@gmail.com>
> + */
> +
> +#include <linux/array_size.h>
> +#include <linux/bitfield.h>
> +#include <linux/bitops.h>
> +#include <linux/cleanup.h>
> +#include <linux/clk.h>
> +#include <linux/completion.h>
> +#include <linux/compiler_attributes.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/interrupt.h>
> +#include <linux/lockdep.h>
> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/mod_devicetable.h>
> +#include <linux/mutex.h>
> +#include <linux/property.h>
> +#include <linux/regmap.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +
> +#include <asm/byteorder.h>
> +
> +#include <linux/iio/iio.h>
> +
> +#define ADS1262_OPCODE_NOP 0x00
> +#define ADS1262_OPCODE_RESET 0x06
> +#define ADS1262_OPCODE_START1 0x08
> +#define ADS1262_OPCODE_STOP1 0x0A
> +#define ADS1262_OPCODE_START2 0x0C
> +#define ADS1262_OPCODE_STOP2 0x0E
> +#define ADS1262_OPCODE_RDATA1 0x12
> +#define ADS1262_OPCODE_RDATA2 0x14
> +#define ADS1262_OPCODE_SYOCAL1 0x16
> +#define ADS1262_OPCODE_SYGCAL1 0x17
> +#define ADS1262_OPCODE_SFOCAL1 0x19
> +#define ADS1262_OPCODE_SYOCAL2 0x1B
> +#define ADS1262_OPCODE_SYGCAL2 0x1C
> +#define ADS1262_OPCODE_SFOCAL2 0x1E
> +#define ADS1262_OPCODE_RREG 0x20
> +#define ADS1262_OPCODE_WREG 0x40
> +
> +#define ADS1262_ID_REG 0x00
> +#define ADS1262_DEV_ID_MASK GENMASK(7, 5)
> +#define ADS1262_REV_ID_MASK GENMASK(4, 0)
> +
> +#define ADS1262_POWER_REG 0x01
> +#define ADS1262_POWER_RESET_MASK BIT(4)
> +#define ADS1262_POWER_VBIAS_MASK BIT(1)
> +#define ADS1262_POWER_INTREF_MASK BIT(0)
> +
> +#define ADS1262_INTERFACE_REG 0x02
> +#define ADS1262_INTERFACE_TIMEOUT_MASK BIT(3)
> +#define ADS1262_INTERFACE_STATUS_MASK BIT(2)
> +#define ADS1262_INTERFACE_CRC_MASK GENMASK(1, 0)
> +
> +#define ADS1262_MODE0_REG 0x03
> +#define ADS1262_MODE0_REFREV_MASK BIT(7)
> +#define ADS1262_MODE0_RUNMODE_MASK BIT(6)
> +#define ADS1262_MODE0_IDAC_CHOP_MASK BIT(5)
> +#define ADS1262_MODE0_INPUT_CHOP_MASK BIT(4)
> +#define ADS1262_MODE0_DELAY_MASK GENMASK(3, 0)
> +
> +#define ADS1262_MODE1_REG 0x04
> +#define ADS1262_MODE1_FILTER_MASK GENMASK(7, 5)
> +
> +#define ADS1262_MODE2_REG 0x05
> +#define ADS1262_MODE2_BYPASS_MASK BIT(7)
> +#define ADS1262_MODE2_GAIN_MASK GENMASK(6, 4)
> +#define ADS1262_MODE2_DR_MASK GENMASK(3, 0)
> +
> +#define ADS1262_INPMUX_REG 0x06
> +#define ADS1262_INPMUX_MUXP_MASK GENMASK(7, 4)
> +#define ADS1262_INPMUX_MUXN_MASK GENMASK(3, 0)
> +
> +#define ADS1262_OFCAL0_REG 0x07
> +#define ADS1262_OFCAL1_REG 0x08
> +#define ADS1262_OFCAL2_REG 0x09
> +#define ADS1262_FSCAL0_REG 0x0A
> +#define ADS1262_FSCAL1_REG 0x0B
> +#define ADS1262_FSCAL2_REG 0x0C
> +
> +#define ADS1262_IDACMUX_REG 0x0D
> +#define ADS1262_IDACMUX_MUX2_MASK GENMASK(7, 4)
> +#define ADS1262_IDACMUX_MUX1_MASK GENMASK(3, 0)
> +#define ADS1262_IDACMUX_NO_CONN 0xB
> +
> +#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_ADC2MUX_MUXP2_MASK GENMASK(7, 4)
> +#define ADS1262_ADC2MUX_MUXN2_MASK GENMASK(3, 0)
> +
> +#define ADS1262_ADC2OFC0_REG 0x17
> +#define ADS1262_ADC2OFC1_REG 0x18
> +#define ADS1262_ADC2FSC0_REG 0x19
> +#define ADS1262_ADC2FSC1_REG 0x1A
> +
> +#define ADS1262_REG_COUNT 0x1B
> +
> +#define ADS1262_MAX_CHANNEL_COUNT 16
> +#define ADS1262_MAX_REGMAP_WRITE 8
> +#define ADS1262_ADC1_RESOLUTION 32
> +
> +enum {
> + ADS1262_RUNMODE_CONTINUOUS,
> + ADS1262_RUNMODE_PULSE,
> +};
> +
> +enum {
> + ADS1262_FILTER_SINC1,
> + ADS1262_FILTER_SINC2,
> + ADS1262_FILTER_SINC3,
> + ADS1262_FILTER_SINC4,
> + ADS1262_FILTER_FIR,
> +};
> +
> +enum {
> + ADS1262_DR_2_5_SPS,
> + ADS1262_DR_5_SPS,
> + ADS1262_DR_10_SPS,
> + ADS1262_DR_16_6_SPS,
> + ADS1262_DR_20_SPS,
> + ADS1262_DR_50_SPS,
> + ADS1262_DR_60_SPS,
> + ADS1262_DR_100_SPS,
> + ADS1262_DR_400_SPS,
> + ADS1262_DR_1200_SPS,
> + ADS1262_DR_2400_SPS,
> + ADS1262_DR_4800_SPS,
> + ADS1262_DR_7200_SPS,
> + ADS1262_DR_14400_SPS,
> + ADS1262_DR_19200_SPS,
> + ADS1262_DR_38400_SPS,
> +};
> +
> +enum {
> + ADS1262_INPMUX_AIN0,
> + ADS1262_INPMUX_AIN1,
> + ADS1262_INPMUX_AIN2,
> + ADS1262_INPMUX_AIN3,
> + ADS1262_INPMUX_AIN4,
> + ADS1262_INPMUX_AIN5,
> + ADS1262_INPMUX_AIN6,
> + ADS1262_INPMUX_AIN7,
> + ADS1262_INPMUX_AIN8,
> + ADS1262_INPMUX_AIN9,
> + ADS1262_INPMUX_AINCOM,
> + ADS1262_INPMUX_TEMP,
> + ADS1262_INPMUX_AVDD,
> + ADS1262_INPMUX_DVDD,
> + ADS1262_INPMUX_TDAC,
> + ADS1262_INPMUX_FLOAT,
> +};
> +
> +struct ads1262_chip_info {
> + const char *name;
> +};
> +
> +struct ads1262 {
> + struct spi_device *spi;
> + struct regmap *regmap;
> + struct gpio_desc *reset_gpiod;
> + struct gpio_desc *start_gpiod;
> + unsigned long clk_rate;
> +
> + /* Protects channel state */
> + struct mutex chan_lock;
> + unsigned int num_channels;
> + struct completion drdy;
> +
> + /* Protects transfer buffers and concurrent SPI transfers */
> + struct mutex xfer_lock;
> +};
> +
> +static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
I would add an action to the name, like write_cmd or send_cmd.
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return spi_write_then_read(st->spi, &opcode, sizeof(opcode), NULL, 0);
> +}
> +
> +static int ads1262_dev_read_by_cmd(struct ads1262 *st, u8 cmd, __be32 *val)
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return spi_write_then_read(st->spi, &cmd, sizeof(cmd), val, sizeof(*val));
> +}
> +
> +static int ads1262_dev_reset(struct ads1262 *st)
> +{
> + int ret;
> +
> + if (st->reset_gpiod) {
> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET pulse timing requirement is 4 clock cycles, at the
> + * minimum clock rate this is 4 microseconds.
> + */
> + fsleep(4);
How long do we have to hold reset before the chip powers down?
> +
> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 0);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds
> + */
> + fsleep(8);
> + } else {
> + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET);
> + if (ret)
> + return ret;
> +
> + /*
> + * The RESET timing requirement is 8 clock cycles, at the
> + * minimum clock rate this is 8 microseconds
> + */
> + fsleep(8);
> + }
Sleep after reset is the same in both branches, so can be moved here.
> +
> + return 0;
> +}
> +
> +static int ads1262_dev_start(struct ads1262 *st)
> +{
> + int ret;
> +
> + if (st->start_gpiod)
> + ret = gpiod_set_value_cansleep(st->start_gpiod, 1);
> + else
> + ret = ads1262_dev_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)
> +{
> + int ret;
> +
> + ret = ads1262_dev_start(st);
> + if (ret)
> + return ret;
> +
> + if (st->start_gpiod) {
> + /*
> + * The START pulse timing requirement is 4 clock cycles, at the
> + * minimum clock rate this is 4 microseconds.
> + */
> + fsleep(4);
> + return ads1262_dev_stop(st);
> + }
> +
> + return 0;
> +}
> +
> +static int ads1262_wait_for_conversion(struct ads1262 *st)
> +{
> + u64 max_lat_ms;
> + long ret;
> +
> + /*
> + * The first conversion latency is affected by the channel's data rate,
> + * filter, the configurable conversion delay and whether chop mode
> + * and/or IDAC rotation mode are enabled.
> + *
> + * The worst possible latency is calculated by taking the lowest data
> + * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
> + * (Table 9-13). Then we scale it by the actual clock rate and multiply
> + * by 4 to account for chop and IDAC rotation modes (Equation 20).
> + */
> + max_lat_ms = 4 * div_u64(mul_u32_u32(1600, 7372800), st->clk_rate);
These are constant values, so don't need mul_u32_u32(). Also, given the wide
range of possible sampling rates, I would include the current sampling rate
in the calculation. No need to wate 1.6 seconds for something that should
take a few 10s of microseconds.
> +
> + ret = wait_for_completion_interruptible_timeout(&st->drdy,
> + msecs_to_jiffies(max_lat_ms));
> + if (ret < 0)
> + return ret;
> + if (!ret)
> + return -ETIMEDOUT;
> +
> + return 0;
> +}
> +
> +static int ads1262_channel_enable(struct ads1262 *st,
> + const struct iio_chan_spec *spec)
> +{
> + u8 val;
> +
> + guard(mutex)(&st->xfer_lock);
> + guard(mutex)(&st->chan_lock);
> +
> + val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
> + FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
> + return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
> + ADS1262_INPMUX_MUXN_MASK |
> + ADS1262_INPMUX_MUXP_MASK, val);
> +}
> +
> +static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
> +{
> + guard(mutex)(&st->xfer_lock);
> +
> + return regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
> + ADS1262_MODE0_RUNMODE_MASK,
> + FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, runmode));
> +}
> +
> +static int ads1262_channel_read(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *spec, __be32 *val)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + int ret;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + ret = ads1262_set_runmode(st, ADS1262_RUNMODE_PULSE);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_channel_enable(st, spec);
> + if (ret)
> + return ret;
> +
> + reinit_completion(&st->drdy);
> +
> + ret = ads1262_dev_start_one(st);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_wait_for_conversion(st);
> + if (ret)
> + return ret;
> +
> + return ads1262_dev_read_by_cmd(st, ADS1262_OPCODE_RDATA1, val);
> +}
> +
> +static int ads1262_read_raw(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, int *val,
> + int *val2, long mask)
> +{
> + __be32 raw;
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW:
> + ret = ads1262_channel_read(indio_dev, chan, &raw);
> + if (ret)
> + return ret;
> + *val = sign_extend32(be32_to_cpu(raw), ADS1262_ADC1_RESOLUTION - 1);
It is already a 32-bit value, so sign extend doesn't make sense.
> +
> + return IIO_VAL_INT;
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg,
> + unsigned int writeval, unsigned int *readval)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + if (readval)
> + return regmap_read_bypassed(st->regmap, reg, readval);
Don't trust the cache? :-)
> +
> + return regmap_write(st->regmap, reg, writeval);
> +}
> +
> +static const struct iio_info ads1262_iio_info = {
> + .read_raw = ads1262_read_raw,
> + .debugfs_reg_access = ads1262_debugfs_reg_access,
> +};
> +
> +static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
> +{
> + struct ads1262 *st = dev_id;
> +
> + complete(&st->drdy);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static int ads1262_dev_configure(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + ret = ads1262_dev_reset(st);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to reset device\n");
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + ret = regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
> + ADS1262_POWER_RESET_MASK);
> + if (ret)
> + return ret;
> +
> + ret = regmap_clear_bits(st->regmap, ADS1262_INTERFACE_REG,
> + ADS1262_INTERFACE_STATUS_MASK |
> + ADS1262_INTERFACE_CRC_MASK);
> + if (ret)
> + return ret;
> +
> + return 0;
> +}
> +
> +static bool ads1262_readable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case ADS1262_ID_REG ... ADS1262_ADC2FSC1_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool ads1262_writeable_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case ADS1262_POWER_REG ... ADS1262_ADC2FSC1_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static bool ads1262_volatile_reg(struct device *dev, unsigned int reg)
> +{
> + switch (reg) {
> + case ADS1262_POWER_REG:
> + case ADS1262_OFCAL0_REG ... ADS1262_FSCAL2_REG:
> + case ADS1262_GPIODAT_REG:
> + case ADS1262_ADC2OFC0_REG ... ADS1262_ADC2FSC1_REG:
> + return true;
> + default:
> + return false;
> + }
> +}
> +
> +static const struct reg_default ads1262_reg_defaults[] = {
> + { ADS1262_INTERFACE_REG,
> + FIELD_PREP_CONST(ADS1262_INTERFACE_STATUS_MASK, true) |
> + FIELD_PREP_CONST(ADS1262_INTERFACE_CRC_MASK, true) },
> + { ADS1262_MODE0_REG, 0x00 },
> + { ADS1262_MODE1_REG,
> + FIELD_PREP_CONST(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR) },
> + { ADS1262_MODE2_REG,
> + FIELD_PREP_CONST(ADS1262_MODE2_DR_MASK, ADS1262_DR_20_SPS) },
> + { ADS1262_INPMUX_REG,
> + FIELD_PREP_CONST(ADS1262_INPMUX_MUXN_MASK, ADS1262_INPMUX_AIN1) },
> + { ADS1262_IDACMUX_REG,
> + FIELD_PREP_CONST(ADS1262_IDACMUX_MUX2_MASK, ADS1262_IDACMUX_NO_CONN) |
> + FIELD_PREP_CONST(ADS1262_IDACMUX_MUX1_MASK, ADS1262_IDACMUX_NO_CONN) },
> + { 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_ADC2CFG_REG, 0x00 },
> + { ADS1262_ADC2MUX_REG,
> + FIELD_PREP_CONST(ADS1262_ADC2MUX_MUXN2_MASK, ADS1262_INPMUX_AIN1) },
> +};
> +
> +static const struct regmap_config ads1262_regmap_config = {
> + .reg_bits = 8,
> + .val_bits = 8,
> + .writeable_reg = ads1262_writeable_reg,
> + .readable_reg = ads1262_readable_reg,
> + .volatile_reg = ads1262_volatile_reg,
> + .reg_defaults = ads1262_reg_defaults,
> + .num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults),
> + .max_register = ADS1262_ADC2FSC1_REG,
> + .can_sleep = true,
> + .cache_type = REGCACHE_MAPLE,
> +};
> +
> +static int ads1262_regmap_read(void *context, const void *reg_buf,
> + size_t reg_size, void *val_buf, size_t val_size)
> +{
> + struct ads1262 *st = context;
> + u8 tx[2];
> +
> + lockdep_assert_held(&st->xfer_lock);
> +
> + /*
> + * The register read operation uses a two byte command header followed
> + * by the register data:
> + *
> + * byte 0: RREG opcode | register address
> + * byte 1: number of registers to transfer, minus one
> + * byte 2..: register data
> + */
> + memcpy(tx, reg_buf, 1);
> + tx[0] |= ADS1262_OPCODE_RREG;
> + tx[1] = val_size - 1;
> +
> + return spi_write_then_read(st->spi, tx, sizeof(tx), val_buf, val_size);
> +}
> +
> +static int ads1262_regmap_gather_write(void *context, const void *reg_buf,
> + size_t reg_size, const void *val_buf,
> + size_t val_size)
> +{
> + struct ads1262 *st = context;
> + u8 tx[ADS1262_MAX_REGMAP_WRITE + 2];
> +
> + lockdep_assert_held(&st->xfer_lock);
> +
> + /*
> + * The register write operation uses a two byte command header followed
> + * by the register data:
> + *
> + * byte 0: WREG opcode | register address
> + * byte 1: number of registers to transfer, minus one
> + * byte 2..: register data
> + */
> + memcpy(tx, reg_buf, 1);
> + tx[0] |= ADS1262_OPCODE_WREG;
> + tx[1] = val_size - 1;
> + memcpy(&tx[2], val_buf, val_size);
> +
> + return spi_write_then_read(st->spi, tx, 2 + val_size, NULL, 0);
> +}
> +
> +static int ads1262_regmap_write(void *context, const void *data, size_t count)
> +{
> + return ads1262_regmap_gather_write(context, data, 1, data + 1,
> + count - 1);
> +}
> +
> +static const struct regmap_bus ads1262_regmap_bus = {
> + .read = ads1262_regmap_read,
> + .gather_write = ads1262_regmap_gather_write,
> + .write = ads1262_regmap_write,
> + .reg_format_endian_default = REGMAP_ENDIAN_BIG,
> + .val_format_endian_default = REGMAP_ENDIAN_BIG,
> + .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
> +};
> +
> +static int ads1262_gpio_setup(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> +
> + st->start_gpiod = devm_gpiod_get_optional(dev, "start", GPIOD_OUT_LOW);
> + if (IS_ERR(st->start_gpiod))
> + return dev_err_probe(dev, PTR_ERR(st->start_gpiod),
> + "failed to get start GPIO\n");
> +
> + st->reset_gpiod = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
Usually, we would just request this with GPIOD_OUT_HIGH in the reset function
(assuming reset only runs during probe, which is how it is usually done).
Then we don't need to keep a handle the gpiod in struct ads1262 and it saves
us a function call to set it high to perform the reset.
> + if (IS_ERR(st->reset_gpiod))
> + return dev_err_probe(dev, PTR_ERR(st->reset_gpiod),
> + "failed to get reset GPIO\n");
> +
> + /*
> + * The power transition timing requirement is 65536 clock cycles, at the
> + * minimum clock frequency this is 65536 microseconds.
> + */
> + fsleep(65536);
Sleep seems out of place here. Should be right after regulator enables in
ads1262_supply_setup().
> +
> + return 0;
> +}
> +
> +static int ads1262_parse_channel_node(struct ads1262 *st,
> + struct iio_chan_spec *spec,
> + struct fwnode_handle *node)
> +{
> + struct device *dev = &st->spi->dev;
> + u32 pins[2];
> + int ret;
> +
> + if (fwnode_property_present(node, "single-channel")) {
> + ret = fwnode_property_read_u32(node, "single-channel", &pins[0]);
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read single-channel\n",
> + fwnode_get_name(node));
> +
> + pins[1] = ADS1262_INPMUX_AINCOM;
> + fwnode_property_read_u32(node, "common-mode-channel", &pins[1]);
Why ignoring error?
> + } else if (fwnode_property_present(node, "diff-channels")) {
> + ret = fwnode_property_read_u32_array(node, "diff-channels", pins,
> + ARRAY_SIZE(pins));
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read diff-channels\n",
> + fwnode_get_name(node));
> +
> + if (pins[0] <= ADS1262_INPMUX_AINCOM || pins[1] <= ADS1262_INPMUX_AINCOM)
> + spec->differential = true;
TDAC input would be differential too. (But I don't think it should be requried to
be declared in the devicetree.)
> + } else {
> + return dev_err_probe(dev, -ENXIO,
Usually we just return -EINVAL and rely on the error message. This isn't a "no
such device or address" problem. Same applies to other ENXIO in this driver.
> + "%s: one of single-channel or diff-channels is required\n",
> + fwnode_get_name(node));
> + }
> +
> + if (pins[0] >= ADS1262_INPMUX_FLOAT || pins[1] >= ADS1262_INPMUX_FLOAT)
> + return dev_err_probe(dev, -EINVAL, "%s: input channels not in range\n",
> + fwnode_get_name(node));
> +
> + if ((pins[0] >= ADS1262_INPMUX_TEMP ||
> + pins[1] >= ADS1262_INPMUX_TEMP) && pins[0] != pins[1])
> + return dev_err_probe(dev, -EINVAL,
> + "%s: monitor channels must be selected symmetrically\n",
> + fwnode_get_name(node));
> +
> + spec->channel = pins[0];
> + spec->channel2 = pins[1];
> +
> + return 0;
> +}
> +
> +static int ads1262_parse_channels(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + struct device *dev = &st->spi->dev;
> + struct iio_chan_spec *specs;
> + unsigned long used_regs = 0;
> + int num_specs;
> + u32 reg;
> + int ret;
> +
> + st->num_channels = device_get_named_child_node_count(dev, "channel");
> + if (!st->num_channels)
> + return dev_err_probe(dev, -ENXIO, "no 'channel' nodes configured\n");
> + if (st->num_channels > ADS1262_MAX_CHANNEL_COUNT)
> + return dev_err_probe(dev, -EINVAL, "too many channels\n");
> +
> + /* Account for the timestamp channel */
> + num_specs = st->num_channels + 1;
> + specs = devm_kcalloc(dev, num_specs, sizeof(*specs), GFP_KERNEL);
> + if (!specs)
> + return -ENOMEM;
> +
> + device_for_each_named_child_node_scoped(dev, node, "channel") {
> + ret = fwnode_property_read_u32(node, "reg", ®);
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read channel reg\n",
> + fwnode_get_name(node));
> + if (reg >= st->num_channels)
> + return dev_err_probe(dev, -EINVAL, "%s: reg out of range\n",
> + fwnode_get_name(node));
> +
> + static_assert(ADS1262_MAX_CHANNEL_COUNT < BITS_PER_LONG);
> + if (__test_and_set_bit(reg, &used_regs))
> + return dev_err_probe(dev, -EINVAL, "%s: duplicated channel reg\n",
> + fwnode_get_name(node));
> +
> + specs[reg].scan_index = reg;
> + specs[reg].scan_type = (struct iio_scan_type) {
> + .format = IIO_SCAN_FORMAT_SIGNED_INT,
> + .realbits = ADS1262_ADC1_RESOLUTION,
> + .storagebits = 32,
> + .endianness = IIO_BE,
> + };
> +
> + ret = ads1262_parse_channel_node(st, &specs[reg], node);
> + if (ret)
> + return ret;
> +
> + if (specs[reg].channel == ADS1262_INPMUX_TEMP)
> + specs[reg].type = IIO_TEMP;
> + else
> + specs[reg].type = IIO_VOLTAGE;
> +
> + if (specs[reg].channel != ADS1262_INPMUX_TEMP)
> + specs[reg].indexed = true;
> +
> + specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> + }
If we are going to use reg to determine the scan index, we need to
make sure there are no holes in specs that didn't get filled in.
device_for_each_named_child_node_scoped() will skip `status = "disabled"`
channels, so this could be a possibility.
> +
> + specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
> +
> + indio_dev->channels = specs;
> + indio_dev->num_channels = num_specs;
> +
> + return 0;
> +}
> +
> +static int ads1262_supply_setup(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + int ret;
> +
> + ret = devm_regulator_get_enable(dev, "dvdd");
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
> +
> + ret = devm_regulator_get_enable(dev, "avdd");
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
> +
> + ret = devm_regulator_get_enable_optional(dev, "avss");
> + if (ret < 0 && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to get avss regulator\n");
> +
> + return 0;
> +}
> +
> +static int ads1262_spi_probe(struct spi_device *spi)
> +{
> + const struct ads1262_chip_info *info;
> + struct device *dev = &spi->dev;
> + struct iio_dev *indio_dev;
> + struct ads1262 *st;
> + unsigned long rate;
> + struct clk *clk;
> + int irq;
> + int ret;
> +
> + info = spi_get_device_match_data(spi);
> + if (!info)
> + return -EINVAL;
> +
> + indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
> + if (!indio_dev)
> + return -ENOMEM;
> + indio_dev->name = info->name;
> + indio_dev->modes = INDIO_DIRECT_MODE;
> + indio_dev->info = &ads1262_iio_info;
> +
> + st = iio_priv(indio_dev);
> + st->spi = spi;
> + init_completion(&st->drdy);
> +
> + ret = devm_mutex_init(dev, &st->chan_lock);
> + if (ret)
> + return ret;
> + ret = devm_mutex_init(dev, &st->xfer_lock);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_parse_channels(indio_dev);
> + if (ret)
> + return ret;
> +
> + clk = devm_clk_get_optional_enabled(dev, NULL);
> + if (IS_ERR(clk))
> + return dev_err_probe(dev, PTR_ERR(clk), "failed to get external clock\n");
> +
> + rate = clk_get_rate(clk);
> + if (clk && !rate)
> + return dev_err_probe(dev, -ENXIO, "failed to get clock rate\n");
> + st->clk_rate = rate ? rate : 7372800;
I would use a macro to self-document that 7372800 is the internal clock rate.
> +
> + ret = ads1262_supply_setup(st);
> + if (ret)
> + return ret;
Should probably apply power before enabling clock since most chips
don't like voltage applied to I/O pins before powering on.
> +
> + ret = ads1262_gpio_setup(st);
> + 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_dev_configure(st);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to configure device\n");
> +
> + /*
> + * REVISIT: This chip has software polling capabilities, which could be
> + * used to stop depending on the 'drdy' IRQ.
> + *
> + * Additionally, the MISO pin also can be used as a DRDY IRQ, in which
> + * case the interrupt would be named 'dout-drdy', but requires a lot of
> + * timing and synchronization considerations to be reliable.
> + */
> + irq = fwnode_irq_get_byname(dev_fwnode(dev), "drdy");
> + if (irq < 0)
> + return dev_err_probe(dev, irq,
> + "the 'drdy' IRQ is currently required for operation\n");
> +
> + ret = devm_request_irq(dev, irq, ads1262_irq_handler, IRQF_NO_THREAD,
> + info->name, st);
> + if (ret)
> + return ret;
> +
> + return devm_iio_device_register(dev, indio_dev);
> +}
> +
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-08-08 3:58 ` [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
2026-08-08 4:11 ` sashiko-bot
@ 2026-08-08 18:39 ` David Lechner
2026-08-09 8:27 ` Kurt Borja
1 sibling, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:39 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Add per-channel sampling frequency support. The "available" attribute is
> assigned per-channel too, in order to eventually support per-filter
> availability.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/iio/adc/ti-ads1262.c | 159 ++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 158 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index d78e5e3ae13e..b3b7b1249102 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -26,6 +26,7 @@
> #include <linux/regulator/consumer.h>
> #include <linux/spi/spi.h>
> #include <linux/types.h>
> +#include <linux/units.h>
>
> #include <asm/byteorder.h>
>
> @@ -148,6 +149,7 @@ enum {
> ADS1262_DR_14400_SPS,
> ADS1262_DR_19200_SPS,
> ADS1262_DR_38400_SPS,
> + ADS1262_DR_COUNT,
> };
>
/* FIR filter has limited data rate range. */
#define ADS1262_DR_COUNT_FIR_FILTER (ADS1262_DR_20_SPS + 1)
So we can properly restrict the rate when the FIR filter
is selected.
> enum {
> @@ -173,6 +175,11 @@ struct ads1262_chip_info {
> const char *name;
> };
>
> +struct ads1262_channel {
> + u8 data_rate;
> + int samp_freqs[ADS1262_DR_COUNT][2];
> +};
> +
> struct ads1262 {
> struct spi_device *spi;
> struct regmap *regmap;
> @@ -183,12 +190,47 @@ struct ads1262 {
> /* Protects channel state */
> struct mutex chan_lock;
> unsigned int num_channels;
> + struct ads1262_channel *channels;
> struct completion drdy;
>
> /* Protects transfer buffers and concurrent SPI transfers */
> struct mutex xfer_lock;
> };
>
Could use a comment to explain this is based on datasheet table
showing decimation ratios which correspond to the last two columns.
> +static const u32 ads1262_data_rate_div[] = {
> + [ADS1262_DR_2_5_SPS] = 8 * 64 * 5760,
> + [ADS1262_DR_5_SPS] = 8 * 64 * 2880,
> + [ADS1262_DR_10_SPS] = 8 * 64 * 1440,
> + [ADS1262_DR_16_6_SPS] = 8 * 64 * 864,
> + [ADS1262_DR_20_SPS] = 8 * 64 * 720,
> + [ADS1262_DR_50_SPS] = 8 * 64 * 288,
> + [ADS1262_DR_60_SPS] = 8 * 64 * 240,
> + [ADS1262_DR_100_SPS] = 8 * 64 * 144,
> + [ADS1262_DR_400_SPS] = 8 * 64 * 36,
> + [ADS1262_DR_1200_SPS] = 8 * 64 * 12,
> + [ADS1262_DR_2400_SPS] = 8 * 64 * 6,
> + [ADS1262_DR_4800_SPS] = 8 * 64 * 3,
> + [ADS1262_DR_7200_SPS] = 8 * 64 * 2,
> + [ADS1262_DR_14400_SPS] = 8 * 64 * 1,
> + [ADS1262_DR_19200_SPS] = 8 * 48 * 1,
> + [ADS1262_DR_38400_SPS] = 8 * 24 * 1,
> +};
> +
> +static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
> + int val2)
> +{
> + int i;
> +
> + for (i = 0; i < num_elements; i++) {
> + if (val == array[i][0] && val2 == array[i][1])
> + break;
Just return i directly here.
> + }
> + if (i == num_elements)
> + return -EINVAL;
Then can return directly here.
> +
> + return i;
> +}
> +
> static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
> {
> guard(mutex)(&st->xfer_lock);
> @@ -316,11 +358,19 @@ static int ads1262_wait_for_conversion(struct ads1262 *st)
> static int ads1262_channel_enable(struct ads1262 *st,
> const struct iio_chan_spec *spec)
> {
> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
> + int ret;
> u8 val;
>
> guard(mutex)(&st->xfer_lock);
> guard(mutex)(&st->chan_lock);
>
> + val = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate);
> + ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
> + ADS1262_MODE2_DR_MASK, val);
> + if (ret)
> + return ret;
> +
> val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
> FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
> return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
> @@ -372,6 +422,8 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
> struct iio_chan_spec const *chan, int *val,
> int *val2, long mask)
> {
> + struct ads1262 *st = iio_priv(indio_dev);
> + struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
> __be32 raw;
> int ret;
>
> @@ -384,6 +436,65 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
>
> return IIO_VAL_INT;
>
> + case IIO_CHAN_INFO_SAMP_FREQ: {
> + guard(mutex)(&st->chan_lock);
> +
> + *val = chan_data->samp_freqs[chan_data->data_rate][0];
> + *val2 = chan_data->samp_freqs[chan_data->data_rate][1];
> +
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +
> + default:
> + return -EOPNOTSUPP;
> + }
> +}
> +
> +static int ads1262_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, const int **vals,
> + int *type, int *length, long mask)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *type = IIO_VAL_INT_PLUS_MICRO;
> + *vals = (const int *)chan_data->samp_freqs;
> + *length = ARRAY_SIZE(chan_data->samp_freqs) * 2;
For now, this should be ADS1262_DR_COUNT_FIR_FILTER * 2 for now since
FIR filter is the default.
> + 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 = &st->channels[chan->scan_index];
> + int ret;
> +
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
> +
> + guard(mutex)(&st->chan_lock);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + ret = ads1262_find_two(chan_data->samp_freqs,
> + ARRAY_SIZE(chan_data->samp_freqs),
Likewise ADS1262_DR_COUNT_FIR_FILTER here too for now.
> + val, val2);
> + if (ret < 0)
> + return -EINVAL;
Just return ret;
> +
> + chan_data->data_rate = ret;
> +
> + return 0;
> +
> default:
> return -EOPNOTSUPP;
> }
> @@ -404,6 +515,8 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
>
> static const struct iio_info ads1262_iio_info = {
> .read_raw = ads1262_read_raw,
> + .read_avail = ads1262_read_avail,
> + .write_raw = ads1262_write_raw,
> .debugfs_reg_access = ads1262_debugfs_reg_access,
> };
>
> @@ -575,6 +688,36 @@ static const struct regmap_bus ads1262_regmap_bus = {
> .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
> };
>
> +static void ads1262_populate_samp_freqs(struct ads1262 *st,
> + struct ads1262_channel *chan)
> +{
> + int freq_Hz, freq_rem;
Shouldn't these be u64 and u32?
> + u64 freq_uHz;
> +
> + for (unsigned int i = 0; i < ARRAY_SIZE(chan->samp_freqs); i++) {
> + freq_uHz = div_u64(mul_u32_u32(st->clk_rate, MICRO),
> + ads1262_data_rate_div[i]);
> + freq_Hz = div_u64_rem(freq_uHz, MICRO, &freq_rem);
> +
> + chan->samp_freqs[i][0] = freq_Hz;
> + chan->samp_freqs[i][1] = freq_rem;
> + }
> +}
> +
> +static int ads1262_populate_tables(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + struct ads1262_channel *chan;
> +
> + for (unsigned int i = 0; i < st->num_channels; i++) {
> + chan = &st->channels[i];
> +
> + ads1262_populate_samp_freqs(st, chan);
> + }
> +
> + return 0;
> +}
> +
> static int ads1262_gpio_setup(struct ads1262 *st)
> {
> struct device *dev = &st->spi->dev;
> @@ -661,6 +804,11 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
> if (st->num_channels > ADS1262_MAX_CHANNEL_COUNT)
> return dev_err_probe(dev, -EINVAL, "too many channels\n");
>
> + st->channels = devm_kcalloc(dev, st->num_channels, sizeof(*st->channels),
> + GFP_KERNEL);
> + if (!st->channels)
> + return -ENOMEM;
> +
> /* Account for the timestamp channel */
> num_specs = st->num_channels + 1;
> specs = devm_kcalloc(dev, num_specs, sizeof(*specs), GFP_KERNEL);
> @@ -681,6 +829,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
> return dev_err_probe(dev, -EINVAL, "%s: duplicated channel reg\n",
> fwnode_get_name(node));
>
> + st->channels[reg].data_rate = ADS1262_DR_20_SPS;
> +
> specs[reg].scan_index = reg;
> specs[reg].scan_type = (struct iio_scan_type) {
> .format = IIO_SCAN_FORMAT_SIGNED_INT,
> @@ -701,7 +851,10 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
> if (specs[reg].channel != ADS1262_INPMUX_TEMP)
> specs[reg].indexed = true;
>
> - specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
> + specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> + BIT(IIO_CHAN_INFO_SAMP_FREQ);
> + specs[reg].info_mask_separate_available =
> + BIT(IIO_CHAN_INFO_SAMP_FREQ);
> }
>
> specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
> @@ -786,6 +939,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
> if (ret)
> return ret;
>
> + ret = ads1262_populate_tables(indio_dev);
> + if (ret)
> + return ret;
> +
> st->regmap = devm_regmap_init(dev, &ads1262_regmap_bus, st,
> &ads1262_regmap_config);
> if (IS_ERR(st->regmap))
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain
2026-08-08 3:58 ` [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
@ 2026-08-08 18:39 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
0 siblings, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:39 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Allow each channel to select its voltage reference through the
> "reference-sources" firmware property. Then, use the reference voltage
> to calculate available scales.
It looks like this is also implementing PGA gain at the same time, but
isn't mentioned in ght commit messsage. I would also expect something
here about how we should handle PGA bypass (even if it just says default
works always and we can consdier controlling it later).
>
> The ADS1262 allows single-ended supply configurations or bipolar supply
> configurations. In single ended configurations both the analog and
> digital rails share the same ground, i.e. AVSS = DGND = 0 V. In bipolar
> supply configurations, AVSS can go below ground, e.g. AVSS = -2.5 V.
>
> If AVSS is below ground, the ADC can achieve true bipolar measurements
> and the external references can also have voltage levels below ground.
> This is currently an issue because the regulator subsystem doesn't
> support negative voltages.
>
> The ad4170-4 driver faces this problem too and the same workaround is
> used in this case: assume every regulator reports magnitudes (absolute
> values). If the chip has a bipolar supply configuration, then assume
> positive references are above ground (>= 0 V) and negative references
> are below ground (<= 0 V). This is not a hardware constraint, but it is
> the most common wiring.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/iio/adc/ti-ads1262.c | 417 +++++++++++++++++++++++++++++++++++++++++--
> 1 file changed, 406 insertions(+), 11 deletions(-)
>
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index b3b7b1249102..360ce01a5871 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -8,6 +8,7 @@
> #include <linux/array_size.h>
> #include <linux/bitfield.h>
> #include <linux/bitops.h>
> +#include <linux/bitmap.h>
> #include <linux/cleanup.h>
> #include <linux/clk.h>
> #include <linux/completion.h>
> @@ -18,6 +19,7 @@
> #include <linux/interrupt.h>
> #include <linux/lockdep.h>
> #include <linux/math64.h>
> +#include <linux/minmax.h>
> #include <linux/module.h>
> #include <linux/mod_devicetable.h>
> #include <linux/mutex.h>
> @@ -25,6 +27,7 @@
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> #include <linux/spi/spi.h>
> +#include <linux/string.h>
> #include <linux/types.h>
> #include <linux/units.h>
>
> @@ -97,6 +100,9 @@
> #define ADS1262_IDACMAG_REG 0x0E
>
> #define ADS1262_REFMUX_REG 0x0F
> +#define ADS1262_REFMUX_RMUXP_MASK GENMASK(5, 3)
> +#define ADS1262_REFMUX_RMUXN_MASK GENMASK(2, 0)
> +
> #define ADS1262_TDACP_REG 0x10
> #define ADS1262_TDACN_REG 0x11
> #define ADS1262_GPIOCON_REG 0x12
> @@ -117,8 +123,12 @@
>
> #define ADS1262_MAX_CHANNEL_COUNT 16
> #define ADS1262_MAX_REGMAP_WRITE 8
> +#define ADS1262_EXT_REF_COUNT 3
> #define ADS1262_ADC1_RESOLUTION 32
>
> +#define ADS1262_TEMP_SLOPE_uV_C 420ULL
> +#define ADS1262_TEMP_ZERO_C 111900ULL
> +
> enum {
> ADS1262_RUNMODE_CONTINUOUS,
> ADS1262_RUNMODE_PULSE,
> @@ -171,12 +181,38 @@ enum {
> ADS1262_INPMUX_FLOAT,
> };
>
> +enum {
> + ADS1262_RMUXP_INTERNAL,
> + ADS1262_RMUXP_REFP1,
> + ADS1262_RMUXP_REFP2,
> + ADS1262_RMUXP_REFP3,
> + ADS1262_RMUXP_AVDD,
> + ADS1262_RMUXP_COUNT
> +};
> +
> +enum {
> + ADS1262_RMUXN_INTERNAL,
> + ADS1262_RMUXN_REFN1,
> + ADS1262_RMUXN_REFN2,
> + ADS1262_RMUXN_REFN3,
> + ADS1262_RMUXN_AVSS,
> + ADS1262_RMUXN_COUNT
> +};
> +
> struct ads1262_chip_info {
> const char *name;
> };
>
> struct ads1262_channel {
> u8 data_rate;
> + u8 gain;
> + u8 ref_p;
> + u8 ref_n;
> + bool ref_reversal;
> + bool is_resistance;
> + int offset;
> + int scales[6][2];
I would add a macro for 6 to explain that it is the number of possible
gains for the PGA. And that the gain multipler is the index in the array
to the power of 2.
> + size_t num_scales;
> int samp_freqs[ADS1262_DR_COUNT][2];
> };
>
> @@ -192,6 +228,12 @@ struct ads1262 {
> unsigned int num_channels;
> struct ads1262_channel *channels;
> struct completion drdy;
> + u32 rref_ohms[ADS1262_EXT_REF_COUNT][ADS1262_EXT_REF_COUNT];
> + int refp_uV[ADS1262_RMUXP_COUNT];
> + int refn_uV[ADS1262_RMUXN_COUNT];
> + bool need_avdd_uV;
> + bool need_avss_uV;
> + bool bipolar_supply;
>
> /* Protects transfer buffers and concurrent SPI transfers */
> struct mutex xfer_lock;
> @@ -216,6 +258,24 @@ static const u32 ads1262_data_rate_div[] = {
> [ADS1262_DR_38400_SPS] = 8 * 24 * 1,
> };
>
> +static const char * const ads1262_ref_sources_pos[] = {
> + [ADS1262_RMUXP_INTERNAL] = "internal",
> + [ADS1262_RMUXP_REFP1] = "refp1",
> + [ADS1262_RMUXP_REFP2] = "refp2",
> + [ADS1262_RMUXP_REFP3] = "refp3",
> + [ADS1262_RMUXP_AVDD] = "avdd",
> + NULL
> +};
> +
> +static const char * const ads1262_ref_sources_neg[] = {
> + [ADS1262_RMUXN_INTERNAL] = "internal",
> + [ADS1262_RMUXN_REFN1] = "refn1",
> + [ADS1262_RMUXN_REFN2] = "refn2",
> + [ADS1262_RMUXN_REFN3] = "refn3",
> + [ADS1262_RMUXN_AVSS] = "avss",
> + NULL
> +};
> +
> static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
> int val2)
> {
> @@ -231,6 +291,12 @@ static int ads1262_find_two(const int (*array)[2], size_t num_elements, int val,
> return i;
> }
>
> +static bool ads1262_ref_is_external(int ref_p, int ref_n)
> +{
> + return in_range(ref_p, ADS1262_RMUXP_REFP1, ADS1262_EXT_REF_COUNT) &&
> + in_range(ref_n, ADS1262_RMUXN_REFN1, ADS1262_EXT_REF_COUNT);
> +}
> +
> static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode)
> {
> guard(mutex)(&st->xfer_lock);
> @@ -365,17 +431,33 @@ static int ads1262_channel_enable(struct ads1262 *st,
> guard(mutex)(&st->xfer_lock);
> guard(mutex)(&st->chan_lock);
>
> - val = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate);
> + val = FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
> + ret = regmap_update_bits(st->regmap, ADS1262_MODE0_REG,
> + ADS1262_MODE0_REFREV_MASK, val);
> + if (ret)
> + return ret;
> +
> + val = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
> + FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
> ret = regmap_update_bits(st->regmap, ADS1262_MODE2_REG,
> - ADS1262_MODE2_DR_MASK, val);
> + ADS1262_MODE2_DR_MASK |
> + ADS1262_MODE2_GAIN_MASK, val);
> if (ret)
> return ret;
>
> val = FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2) |
> FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel);
> - return regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
> + ret = regmap_update_bits(st->regmap, ADS1262_INPMUX_REG,
> ADS1262_INPMUX_MUXN_MASK |
> ADS1262_INPMUX_MUXP_MASK, val);
Do we need to fix alignment of these lines too?
> + if (ret)
> + return ret;
> +
> + val = FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n) |
> + FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p);
> + return regmap_update_bits(st->regmap, ADS1262_REFMUX_REG,
> + ADS1262_REFMUX_RMUXN_MASK |
> + ADS1262_REFMUX_RMUXP_MASK, val);
> }
>
> static int ads1262_set_runmode(struct ads1262 *st, u8 runmode)
> @@ -436,6 +518,26 @@ static int ads1262_read_raw(struct iio_dev *indio_dev,
>
> return IIO_VAL_INT;
>
> + case IIO_CHAN_INFO_SCALE: {
> + guard(mutex)(&st->chan_lock);
> +
> + *val = chan_data->scales[chan_data->gain][0];
> + *val2 = chan_data->scales[chan_data->gain][1];
> +
> + return IIO_VAL_DECIMAL64_PICO;
> + }
> +
> + case IIO_CHAN_INFO_OFFSET: {
> + if (chan->type != IIO_TEMP)
> + return -EPERM;
Dont' return EPERM. That would lead people to belive it is a permission
issue that can be fixed with chown. Typically we just return -EINVAL.
> +
> + guard(mutex)(&st->chan_lock);
> +
> + *val = chan_data->offset;
> +
> + return IIO_VAL_INT;
> + }
> +
> case IIO_CHAN_INFO_SAMP_FREQ: {
> guard(mutex)(&st->chan_lock);
>
> @@ -458,6 +560,12 @@ static int ads1262_read_avail(struct iio_dev *indio_dev,
> struct ads1262_channel *chan_data = &st->channels[chan->scan_index];
>
> switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + *type = IIO_VAL_DECIMAL64_PICO;
> + *vals = (const int *)chan_data->scales;
> + *length = chan_data->num_scales * 2;
> + return IIO_AVAIL_LIST;
> +
> case IIO_CHAN_INFO_SAMP_FREQ:
> *type = IIO_VAL_INT_PLUS_MICRO;
> *vals = (const int *)chan_data->samp_freqs;
> @@ -484,6 +592,16 @@ static int ads1262_write_raw(struct iio_dev *indio_dev,
> guard(mutex)(&st->chan_lock);
>
> switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + ret = ads1262_find_two(chan_data->scales, chan_data->num_scales,
> + val, val2);
> + if (ret < 0)
> + return ret;
> +
> + chan_data->gain = ret;
> +
> + return 0;
> +
> case IIO_CHAN_INFO_SAMP_FREQ:
> ret = ads1262_find_two(chan_data->samp_freqs,
> ARRAY_SIZE(chan_data->samp_freqs),
> @@ -513,10 +631,22 @@ static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int re
> return regmap_write(st->regmap, reg, writeval);
> }
>
> +static int ads1262_write_raw_get_fmt(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan, long mask)
> +{
> + switch (mask) {
> + case IIO_CHAN_INFO_SCALE:
> + return IIO_VAL_DECIMAL64_PICO;
> + default:
> + return IIO_VAL_INT_PLUS_MICRO;
> + }
> +}
> +
> static 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,
> };
>
> @@ -688,6 +818,91 @@ static const struct regmap_bus ads1262_regmap_bus = {
> .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
> };
>
> +static void ads1262_calculate_scales(int (*scales)[2], size_t num_scales,
> + u32 full_scale, u64 mult,
> + u32 resolution)
> +{
> + unsigned int i;
> + s64 val;
> +
> + for (i = 0; i < num_scales; i++) {
This could use a comment explaining the relasionship of the index in the
array to the PGA multipier.
> + val = mul_u64_u64_shr(full_scale, mult, resolution - 1 + i);
> + iio_val_s64_decompose(val, &scales[i][0], &scales[i][1]);
> + }
> +}
> +
> +static int ads1262_populate_scales_resistance(struct ads1262 *st,
> + const struct iio_chan_spec *spec)
> +{
> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
> + u32 full_scale;
> +
> + if (WARN_ON(!ads1262_ref_is_external(chan->ref_p, chan->ref_n)))
> + return -EINVAL;
WARN_ON() is a bit strong for something that is coming from the devicetree.
I would just fail the parse() function with an appropriate error message
so that we don't have to check here.
> +
> + full_scale = st->rref_ohms[chan->ref_p - 1][chan->ref_n - 1];
> +
> + chan->num_scales = ARRAY_SIZE(chan->scales);
> +
> + ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
> + PICO, ADS1262_ADC1_RESOLUTION);
> +
> + return 0;
> +}
> +
> +static int ads1262_populate_scales_temp(struct ads1262 *st,
> + const struct iio_chan_spec *spec)
> +{
> + struct device *dev = &st->spi->dev;
> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
> + u32 full_scale;
> + u64 mult;
> +
> + full_scale = abs(st->refp_uV[chan->ref_p] - st->refn_uV[chan->ref_n]);
> + if (full_scale < 900000)
> + return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
> + spec->scan_index);
> +
> + chan->offset = -div_s64(ADS1262_TEMP_ZERO_C <<
> + (ADS1262_ADC1_RESOLUTION - 1), full_scale);
> +
> + chan->num_scales = 1;
> +
> + mult = PICO * MILLIDEGREE_PER_DEGREE / ADS1262_TEMP_SLOPE_uV_C;
> + ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
> + mult, ADS1262_ADC1_RESOLUTION);
> +
> + return 0;
> +}
> +
> +static int ads1262_populate_scales_voltage(struct ads1262 *st,
> + const struct iio_chan_spec *spec)
> +{
> + struct device *dev = &st->spi->dev;
> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
> + u32 full_scale;
full_scale_uV
> + u64 mult;
> +
> + full_scale = abs(st->refp_uV[chan->ref_p] - st->refn_uV[chan->ref_n]);
> + if (full_scale < 900000)
> + return dev_err_probe(dev, -EINVAL, "channel@%u: reference voltage below 0.9V\n",
> + spec->scan_index);
> +
> + if (spec->channel >= ADS1262_INPMUX_AVDD &&
> + spec->channel <= ADS1262_INPMUX_DVDD) {
> + chan->num_scales = 1;
> + mult = 4;
Could use a comment to explain where this 4 comes from.
> + } else {
> + chan->num_scales = ARRAY_SIZE(chan->scales);
> + mult = 1;
> + }
> +
> + ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
> + NANO * mult, ADS1262_ADC1_RESOLUTION);
> +
> + return 0;
> +}
> +
> static void ads1262_populate_samp_freqs(struct ads1262 *st,
> struct ads1262_channel *chan)
> {
> @@ -707,12 +922,102 @@ static void ads1262_populate_samp_freqs(struct ads1262 *st,
> static int ads1262_populate_tables(struct iio_dev *indio_dev)
> {
> struct ads1262 *st = iio_priv(indio_dev);
> + const struct iio_chan_spec *spec;
> struct ads1262_channel *chan;
> + int ret;
> +
>
> for (unsigned int i = 0; i < st->num_channels; i++) {
> + spec = &indio_dev->channels[i];
> chan = &st->channels[i];
>
> ads1262_populate_samp_freqs(st, chan);
> +
> + switch (spec->type) {
> + case IIO_VOLTAGE:
> + ret = ads1262_populate_scales_voltage(st, spec);
> + if (ret)
> + return ret;
> + break;
> + case IIO_TEMP:
> + ret = ads1262_populate_scales_temp(st, spec);
> + if (ret)
> + return ret;
> + break;
> + case IIO_RESISTANCE:
> + ret = ads1262_populate_scales_resistance(st, spec);
> + if (ret)
> + return ret;
> + break;
> + default:
> + return -EOPNOTSUPP;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int ads1262_parse_references(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + unsigned int i, j;
> + char name[sizeof("ti,refpN-refnM-resistor-ohms")];
> + u32 ohms;
> + int ret;
> +
> + st->refp_uV[ADS1262_RMUXP_INTERNAL] = st->refn_uV[ADS1262_RMUXN_AVSS] + 2500000;
Could use a comment explaining why the extra 2.5V.
> + st->refn_uV[ADS1262_RMUXN_INTERNAL] = st->refn_uV[ADS1262_RMUXN_AVSS];
> +
> + for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
> + scnprintf(name, sizeof(name), "refp%u", i);
> + ret = devm_regulator_get_enable_read_voltage(dev, name);
> + if (ret < 0 && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
> + name);
> +
> + st->refp_uV[i] = ret == -ENODEV ? 0 : ret;
> + }
> +
> + for (i = ADS1262_RMUXN_REFN1; i <= ADS1262_RMUXN_REFN3; i++) {
> + scnprintf(name, sizeof(name), "refn%u", i);
> + ret = devm_regulator_get_enable_read_voltage(dev, name);
> + if (ret < 0 && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to read reference voltage: %s\n",
> + name);
> +
> + /*
> + * REVISIT: Currently the regulator subsystem doesn't support
> + * reading negative voltages. If we have a bipolar supply
> + * configuration (AVSS < 0), then we are forced to assume that
> + * negative references are either 0V (no regulator) or below
> + * ground magnitudes.
> + */
> + if (st->bipolar_supply)
> + st->refn_uV[i] = ret == -ENODEV ? 0 : -ret;
> + else
> + st->refn_uV[i] = ret == -ENODEV ? 0 : ret;
> + }
> +
> + for (i = ADS1262_RMUXP_REFP1; i <= ADS1262_RMUXP_REFP3; i++) {
> + for (j = ADS1262_RMUXN_REFN1; j <= ADS1262_RMUXN_REFN3; j++) {
> + scnprintf(name, sizeof(name),
> + "ti,refp%u-refn%u-resistor-ohms", i, j);
> +
> + if (!device_property_present(dev, name))
> + continue;
> +
> + ret = device_property_read_u32(dev, name, &ohms);
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "failed to read reference resistor: %s\n",
> + name);
> + if (!ohms)
> + return dev_err_probe(dev, -EINVAL,
> + "reference resistor can't be 0 ohms: %s\n",
> + name);
> +
> + st->rref_ohms[i - 1][j - 1] = ohms;
> + }
> }
>
> return 0;
> @@ -745,7 +1050,10 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
> struct iio_chan_spec *spec,
> struct fwnode_handle *node)
> {
> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
> struct device *dev = &st->spi->dev;
> + const char *sources[2];
> + char name[sizeof("ti,refpN-refnM-resistor-ohms")];
> u32 pins[2];
> int ret;
>
> @@ -785,6 +1093,49 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
> spec->channel = pins[0];
> spec->channel2 = pins[1];
>
> + if (fwnode_property_present(node, "reference-sources")) {
> + ret = fwnode_property_read_string_array(node, "reference-sources",
> + sources, ARRAY_SIZE(sources));
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "%s: failed to read reference-sources\n",
> + fwnode_get_name(node));
> + if (ret < 2)
> + return dev_err_probe(dev, -EINVAL, "%s: missing reference-sources\n",
> + fwnode_get_name(node));
> +
> + ret = match_string(ads1262_ref_sources_pos, -1, sources[0]);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "%s: invalid positive reference source\n",
> + fwnode_get_name(node));
> + chan->ref_p = ret;
> +
> + ret = match_string(ads1262_ref_sources_neg, -1, sources[1]);
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "%s: invalid negative reference source\n",
> + fwnode_get_name(node));
> + chan->ref_n = ret;
> +
> + if ((chan->ref_p == ADS1262_RMUXP_INTERNAL ||
> + chan->ref_n == ADS1262_RMUXN_INTERNAL) && chan->ref_p != chan->ref_n)
> + return dev_err_probe(dev, -EINVAL,
> + "%s: the internal reference must be selected symmetrically\n",
> + fwnode_get_name(node));
> +
> + if (chan->ref_p == ADS1262_RMUXP_AVDD)
> + st->need_avdd_uV = true;
> + if (chan->ref_n == ADS1262_RMUXN_AVSS)
> + st->need_avss_uV = true;
> +
> + if (ads1262_ref_is_external(chan->ref_p, chan->ref_n)) {
> + scnprintf(name, sizeof(name), "ti,refp%u-refn%u-resistor-ohms",
> + chan->ref_p, chan->ref_n);
> + if (device_property_present(dev, name))
> + chan->is_resistance = true;
> + }
> + }
> +
> + chan->ref_reversal = fwnode_property_read_bool(node, "ti,reference-reversal");
> +
> return 0;
> }
>
> @@ -845,6 +1196,8 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
>
> if (specs[reg].channel == ADS1262_INPMUX_TEMP)
> specs[reg].type = IIO_TEMP;
> + else if (st->channels[reg].is_resistance)
> + specs[reg].type = IIO_RESISTANCE;
> else
> specs[reg].type = IIO_VOLTAGE;
>
> @@ -852,9 +1205,14 @@ static int ads1262_parse_channels(struct iio_dev *indio_dev)
> specs[reg].indexed = true;
>
> specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> - BIT(IIO_CHAN_INFO_SAMP_FREQ);
> + BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> + BIT(IIO_CHAN_INFO_SCALE);
> + if (specs[reg].channel == ADS1262_INPMUX_TEMP)
> + specs[reg].info_mask_separate |= BIT(IIO_CHAN_INFO_OFFSET);
> +
> specs[reg].info_mask_separate_available =
> - BIT(IIO_CHAN_INFO_SAMP_FREQ);
> + BIT(IIO_CHAN_INFO_SAMP_FREQ) |
> + BIT(IIO_CHAN_INFO_SCALE);
> }
>
> specs[num_specs - 1] = IIO_CHAN_SOFT_TIMESTAMP(num_specs - 1);
> @@ -874,13 +1232,46 @@ static int ads1262_supply_setup(struct ads1262 *st)
> if (ret)
> return dev_err_probe(dev, ret, "failed to get dvdd regulator\n");
>
> - ret = devm_regulator_get_enable(dev, "avdd");
> - if (ret < 0)
> - return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
> + if (st->need_avdd_uV) {
> + ret = devm_regulator_get_enable_read_voltage(dev, "avdd");
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to get avdd voltage\n");
>
> - ret = devm_regulator_get_enable_optional(dev, "avss");
> - if (ret < 0 && ret != -ENODEV)
> - return dev_err_probe(dev, ret, "failed to get avss regulator\n");
> + st->refp_uV[ADS1262_RMUXP_AVDD] = ret;
> + } else {
> + ret = devm_regulator_get_enable(dev, "avdd");
> + if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to get avdd regulator\n");
> + }
> +
> + /*
> + * REVISIT: The AVSS supply has a minimum of -2.5V and maximum of 0V.
> + * Currently the regulator subsystem doesn't support negative voltages,
> + * so we assume the value returned here is actually the magnitude
> + * (absolute value).
> + *
> + * This limitation forces us to assume that, if we have a bipolar supply
> + * (AVSS < 0V), all negative references are below ground (REFN <= 0V)
> + * and positive references are above ground (REFP >= 0V), as this is the
> + * most common configuration.
> + */
> + if (st->need_avss_uV) {
> + ret = devm_regulator_get_enable_read_voltage(dev, "avss");
> + if (ret < 0 && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to get avss voltage\n");
> +
> + if (ret != -ENODEV) {
> + st->refn_uV[ADS1262_RMUXN_AVSS] = -ret;
> + st->bipolar_supply = true;
> + }
> + } else {
> + ret = devm_regulator_get_enable_optional(dev, "avss");
> + if (ret < 0 && ret != -ENODEV)
> + return dev_err_probe(dev, ret, "failed to get avss regulator\n");
> +
> + if (ret != -ENODEV)
> + st->bipolar_supply = true;
> + }
>
> return 0;
> }
> @@ -939,6 +1330,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
> if (ret)
> return ret;
>
> + ret = ads1262_parse_references(st);
> + if (ret)
> + return ret;
> +
> ret = ads1262_populate_tables(indio_dev);
> if (ret)
> return ret;
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping
2026-08-08 3:58 ` [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping Kurt Borja
@ 2026-08-08 18:39 ` David Lechner
0 siblings, 0 replies; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:39 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Enable per-channel input chopping via the "input-chopping" property.
> As the device withholds the first conversion in chop mode, single reads
> are taken in CONTINUOUS run mode with a brief start/stop pulse, as
> recommended by the datasheet (Section 9.4.1.2).
>
Reviewed-by: David Lechner <dlechner@baylibre.com>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents
2026-08-08 3:58 ` [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents Kurt Borja
2026-08-08 4:13 ` sashiko-bot
@ 2026-08-08 18:39 ` David Lechner
1 sibling, 0 replies; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:39 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Support the two IDAC excitation current sources. Each channel can route
> its IDAC1/IDAC2 outputs to a pin via the "excitation-channels" property
> and select a magnitude via "excitation-current-nanoamp".
>
Reviewed-by: David Lechner <dlechner@baylibre.com>
> @@ -1150,7 +1192,58 @@ static int ads1262_parse_channel_node(struct ads1262 *st,
> }
> }
>
> + if (fwnode_property_present(node, "excitation-channels")) {
> + count = fwnode_property_count_u32(node, "excitation-channels");
> + if (count < 0)
> + return dev_err_probe(dev, count,
> + "%s: failed to count excitation-channels\n",
> + fwnode_get_name(node));
> +
> + pins[0] = ADS1262_IDACMUX_NO_CONN;
> + pins[1] = ADS1262_IDACMUX_NO_CONN;
> + ret = fwnode_property_read_u32_array(node, "excitation-channels",
> + pins, min(count, ARRAY_SIZE(pins)));
> + if (ret)
> + return dev_err_probe(dev, ret, "%s: failed to read excitation-channels\n",
> + fwnode_get_name(node));
> + if (pins[0] > ADS1262_IDACMUX_NO_CONN || pins[1] > ADS1262_IDACMUX_NO_CONN)
> + return dev_err_probe(dev, -EINVAL, "%s: excitation-channels not in range\n",
> + fwnode_get_name(node));
> + chan->idac_mux[0] = pins[0];
> + chan->idac_mux[1] = pins[1];
> +
> + mags[0] = 0;
> + mags[1] = 0;
> + ret = fwnode_property_read_u32_array(node, "excitation-current-nanoamp",
> + mags, min(count, ARRAY_SIZE(mags)));
> + if (ret == -EOVERFLOW)
> + return dev_err_probe(dev, ret,
> + "%s: excitation-current-nanoamp size mismatch\n",
> + fwnode_get_name(node));
I don't think we need a special error message for this case. The error code will already
be printed.
> + if (ret)
> + return dev_err_probe(dev, ret,
> + "%s: failed to read excitation-current-nanoamp\n",
> + fwnode_get_name(node));
> +
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling
2026-08-08 3:58 ` [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
2026-08-08 4:09 ` sashiko-bot
@ 2026-08-08 18:39 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
1 sibling, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:39 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Add triggered buffer support and a data-ready (DRDY) hardware trigger.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/iio/adc/Kconfig | 2 +
> drivers/iio/adc/ti-ads1262.c | 264 +++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 266 insertions(+)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index dbf76427912b..b9b561be8347 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1845,6 +1845,8 @@ config TI_ADS1262
> tristate "Texas Instruments ADS1262"
> depends on SPI
> select REGMAP
> + select IIO_BUFFER
> + select IIO_TRIGGERED_BUFFER
> help
> If you say yes here you get support for Texas Instruments ADS1262 and
> ADS1263 ADC chips.
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index d5464b4f2bfb..24a7ecb9fbd4 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -34,6 +34,9 @@
> #include <asm/byteorder.h>
>
> #include <linux/iio/iio.h>
> +#include <linux/iio/trigger.h>
> +#include <linux/iio/trigger_consumer.h>
> +#include <linux/iio/triggered_buffer.h>
>
> #define ADS1262_OPCODE_NOP 0x00
> #define ADS1262_OPCODE_RESET 0x06
> @@ -225,6 +228,7 @@ struct ads1262_channel {
> struct ads1262 {
> struct spi_device *spi;
> struct regmap *regmap;
> + struct iio_trigger *trig;
> struct gpio_desc *reset_gpiod;
> struct gpio_desc *start_gpiod;
> unsigned long clk_rate;
> @@ -241,8 +245,16 @@ struct ads1262 {
> bool need_avss_uV;
> bool bipolar_supply;
>
> + IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
> + ADS1262_MAX_CHANNEL_COUNT);
> +
> /* Protects transfer buffers and concurrent SPI transfers */
> struct mutex xfer_lock;
> + struct spi_message msg;
> + struct spi_transfer xfer;
> +
Where does the 11 come from?
> + u8 tx[11] __aligned(IIO_DMA_MINALIGN);
> + u8 rx[11] __aligned(IIO_DMA_MINALIGN);
don't need second one to be aligned, they aren't indepedant.
> };
>
> static const u32 ads1262_data_rate_div[] = {
> @@ -708,10 +720,242 @@ static const struct iio_info ads1262_iio_info = {
> .debugfs_reg_access = ads1262_debugfs_reg_access,
> };
>
> +static int ads1262_buffer_preenable(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + unsigned int weight;
> + unsigned long i;
> + int ret;
> +
> + weight = bitmap_weight(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> +
> + if (weight > 1) {
> + /*
> + * Multiple channels use software sequencing: a single
> + * contiguous transfer rewrites the per-channel configuration
> + * registers in two (non-contiguous) groups.
> + *
> + * Group 1: write protocol (2 bytes) + MODE0, MODE1, MODE2,
> + * INPMUX (4 registers).
> + *
> + * Group 2: write protocol (2 bytes) + IDACMUX, IDACMAG,
> + * REFMUX (3 registers).
> + *
> + * Total: 11 bytes
> + */
> + st->xfer.len = 11;
> + } else {
> + /*
> + * A single channel is read by command (RDATA1), so the transfer
> + * holds the command byte plus the 4 conversion bytes.
> + *
> + * Total: 5 bytes
> + */
> + st->xfer.len = 5;
> +
> + /*
> + * 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 (command
> + * mode).
> + */
> + memset(st->tx, 0, st->xfer.len);
> + st->tx[0] = ADS1262_OPCODE_RDATA1;
> +
> + i = find_first_bit(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> + ret = ads1262_channel_enable(st, &indio_dev->channels[i]);
> + if (ret)
> + return ret;
> + }
> +
> + ret = ads1262_set_runmode(st, ADS1262_RUNMODE_CONTINUOUS);
> + if (ret)
> + return ret;
> +
> + ret = spi_optimize_message(st->spi, &st->msg);
> + if (ret)
> + return ret;
> +
> + ret = ads1262_dev_start(st);
Start really should be in buffer postenable as the trigger poll function
hasn't been set up yet. It is fine to move all of this to postenable.
> + if (ret) {
> + spi_unoptimize_message(&st->msg);
> + return ret;
> + }
> +
> + return 0;
> +}
> +
> +static int ads1262_buffer_postdisable(struct iio_dev *indio_dev)
Likewise, this needs to be predisable so that it stops triggering
the interrupt before we remove the trigger poll function.
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + unsigned int weight;
> +
> + ads1262_dev_stop(st);
> + spi_unoptimize_message(&st->msg);
> +
> + weight = bitmap_weight(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> + if (weight > 1) {
> + regcache_drop_region(st->regmap, ADS1262_MODE0_REG,
> + ADS1262_INPMUX_REG);
> + regcache_drop_region(st->regmap, ADS1262_IDACMUX_REG,
> + ADS1262_REFMUX_REG);
> + }
> +
> + return 0;
> +}
> +
> +static const struct iio_buffer_setup_ops ads1262_buffer_ops = {
> + .preenable = ads1262_buffer_preenable,
> + .postdisable = ads1262_buffer_postdisable,
> +};
> +
> +static int ads1262_enable_and_read_last(struct ads1262 *st,
> + const struct iio_chan_spec *spec,
> + __be32 *val)
> +{
> + struct ads1262_channel *chan;
> + int ret;
> +
> + lockdep_assert_held(&st->xfer_lock);
What happens if something else (e.g. gpio in the future) decides to do a
register write here. If it wins the race, will it unintentially read the
data? So do we also need to read the stored data via command here too?
> +
> + if (spec) {
> + guard(mutex)(&st->chan_lock);
> +
> + chan = &st->channels[spec->scan_index];
> +
> + /* Group 1: MODE0, MODE1, MODE2, INPMUX */
> + st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
> + st->tx[1] = ADS1262_INPMUX_REG - ADS1262_MODE0_REG;
> + st->tx[2] = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
> + FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
> + FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, ADS1262_RUNMODE_CONTINUOUS) |
> + FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
> + st->tx[3] = FIELD_PREP(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR);
> + st->tx[4] = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
> + FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
> + st->tx[5] = FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel) |
> + FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2);
> +
> + /* Group 2: IDACMUX, IDACMAG, REFMUX */
> + st->tx[6] = ADS1262_IDACMUX_REG | ADS1262_OPCODE_WREG;
> + st->tx[7] = ADS1262_REFMUX_REG - ADS1262_IDACMUX_REG;
> + st->tx[8] = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
> + FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]);
> + st->tx[9] = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
> + FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
> + st->tx[10] = FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p) |
> + FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n);
> + } else {
> + memset(st->tx, 0, sizeof(st->tx));
> + }
> +
> + ret = spi_sync(st->spi, &st->msg);
> + if (ret)
> + return ret;
> +
> + memcpy(val, st->rx, sizeof(*val));
> +
> + return 0;
> +}
> +
> +static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + unsigned int chan;
> + __be32 val;
> + int i = -1;
> + int ret;
> +
> + /*
> + * This routine enables and reads channels in a full-duplex fashion.
> + *
> + * When a channel is enabled, the previous conversion is clocked out of
> + * the shift data register on the same transfer (Section 9.4.7.1). This
> + * allows for low latency software sequencing but forbids any
> + * communication with the chip in-between or data corruption may occur,
> + * hence the need to take the xfer_lock for the whole operation.
> + */
> + guard(mutex)(&st->xfer_lock);
> +
> + iio_for_each_active_channel(indio_dev, chan) {
> + ret = ads1262_enable_and_read_last(st, &indio_dev->channels[chan],
> + &val);
> + if (ret)
> + return ret;
> +
> + /*
> + * After writing to the channel configuration registers, the
> + * conversion-cycle is restarted and the data registers are
> + * cleared. This means we have to reinit the completion after
> + * enabling to avoid reading stale data.
> + */
> + reinit_completion(&st->drdy);
This seems racy still as DRDY could have been triggered already, in which case
we would time out waiting for the interrupt. Or does the DRDY toggle again
even if we don't read the data to trigger another conversion?
Would it be possible to make one big SPI messsage that contains all enabled
channels and just run that instead? Insted of waiting for drdy, it would have
to add a delay at the end of the sequence of xfers for setting up each channel
that was long enough to ensure that the conversion will be done when we read
it.
Or we could just do similar to the start_one() function and don't leave
it in continuous conversion mode. Using the delay option of spi xfers, we
could just tack on two more commands to start and stop the conversion after
after writing all of the mode stuff so that it still all happens in one SPI
message.
> +
> + if (i > -1)
> + st->scan_buffer[i] = val;
> + i++;
> +
> + ret = ads1262_wait_for_conversion(st);
> + if (ret)
> + return ret;
> + }
> +
> + return ads1262_enable_and_read_last(st, NULL, &st->scan_buffer[i]);
> +}
> +
> +static int ads1262_fill_buffer_one(struct iio_dev *indio_dev)
> +{
> + struct ads1262 *st = iio_priv(indio_dev);
> + int ret;
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + ret = spi_sync(st->spi, &st->msg);
> + if (ret)
> + return ret;
> +
> + /* In command mode the conversion data is found at offset 1 */
> + memcpy(st->scan_buffer, &st->rx[1], sizeof(*st->scan_buffer));
> +
> + return 0;
> +}
> +
> +static irqreturn_t ads1262_trigger_handler(int irq, void *p)
> +{
> + struct iio_poll_func *pf = p;
> + struct iio_dev *indio_dev = pf->indio_dev;
> + struct ads1262 *st = iio_priv(indio_dev);
> + s64 ts = pf->timestamp;
> + unsigned int weight;
> + int ret;
> +
> + weight = bitmap_weight(indio_dev->active_scan_mask,
> + iio_get_masklength(indio_dev));
> +
> + if (weight == 1)
Could make this:
if (io_validate_scan_mask_onehot(indio_dev))
> + ret = ads1262_fill_buffer_one(indio_dev);
> + else
> + ret = ads1262_fill_buffer_mult(indio_dev);
> + if (ret)
> + goto out_notify_done;
> +
> + iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer,
> + sizeof(st->scan_buffer), ts);
> +
> +out_notify_done:
> + iio_trigger_notify_done(indio_dev->trig);
> +
> + return IRQ_HANDLED;
> +}
> +
> static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
> {
> struct ads1262 *st = dev_id;
>
> + iio_trigger_poll(st->trig);
> complete(&st->drdy);
>
> return IRQ_HANDLED;
> @@ -1414,6 +1658,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
> st->spi = spi;
> init_completion(&st->drdy);
>
> + st->xfer.tx_buf = st->tx;
> + st->xfer.rx_buf = st->rx;
> + spi_message_init_with_transfers(&st->msg, &st->xfer, 1);
> +
> ret = devm_mutex_init(dev, &st->chan_lock);
> if (ret)
> return ret;
> @@ -1459,6 +1707,22 @@ static int ads1262_spi_probe(struct spi_device *spi)
> if (ret)
> return dev_err_probe(dev, ret, "failed to configure device\n");
>
> + ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> + iio_pollfunc_store_time,
> + ads1262_trigger_handler,
> + &ads1262_buffer_ops);
> + if (ret)
> + return ret;
> +
> + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", info->name,
> + iio_device_id(indio_dev));
> + if (!st->trig)
> + return -ENOMEM;
> + iio_trigger_set_drvdata(st->trig, st);
> + ret = devm_iio_trigger_register(dev, st->trig);
> + if (ret)
> + return ret;
> +
> /*
> * REVISIT: This chip has software polling capabilities, which could be
> * used to stop depending on the 'drdy' IRQ.
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
2026-08-08 3:58 ` [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-08-08 4:11 ` sashiko-bot
@ 2026-08-08 18:40 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
1 sibling, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:40 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Register the "refout" and "vbias" regulators to be able to use them as
> common mode supplies.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/iio/adc/Kconfig | 1 +
> drivers/iio/adc/ti-ads1262.c | 90 ++++++++++++++++++++++++++++++++++++++++++++
> 2 files changed, 91 insertions(+)
>
> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index b9b561be8347..e5206438ac90 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
> @@ -1845,6 +1845,7 @@ config TI_ADS1262
> tristate "Texas Instruments ADS1262"
> depends on SPI
> select REGMAP
> + select REGULATOR
> select IIO_BUFFER
> select IIO_TRIGGERED_BUFFER
> help
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 24a7ecb9fbd4..533574169b04 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -26,6 +26,7 @@
> #include <linux/property.h>
> #include <linux/regmap.h>
> #include <linux/regulator/consumer.h>
> +#include <linux/regulator/driver.h>
> #include <linux/spi/spi.h>
> #include <linux/string.h>
> #include <linux/types.h>
> @@ -961,6 +962,91 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
> return IRQ_HANDLED;
> }
>
> +static int ads1262_regulator_enable(struct regulator_dev *rdev)
> +{
> + struct ads1262 *st = rdev_get_drvdata(rdev);
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + return regmap_set_bits(st->regmap, ADS1262_POWER_REG,
> + ADS1262_POWER_VBIAS_MASK);
> +}
> +
> +static int ads1262_regulator_disable(struct regulator_dev *rdev)
> +{
> + struct ads1262 *st = rdev_get_drvdata(rdev);
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + return regmap_clear_bits(st->regmap, ADS1262_POWER_REG,
> + ADS1262_POWER_VBIAS_MASK);
> +}
> +
> +static int ads1262_regulator_is_enabled(struct regulator_dev *rdev)
> +{
> + struct ads1262 *st = rdev_get_drvdata(rdev);
> + unsigned int val;
> + int ret;
> +
> + guard(mutex)(&st->xfer_lock);
> +
> + ret = regmap_read(st->regmap, ADS1262_POWER_REG, &val);
Can be a bit simpler with regmap_test_bits().
> + if (ret)
> + return ret;
> +
> + return field_get(ADS1262_POWER_VBIAS_MASK, val);
> +}
> +
> +static const struct regulator_ops ads1262_vbias_regulator_ops = {
> + .enable = ads1262_regulator_enable,
> + .disable = ads1262_regulator_disable,
> + .is_enabled = ads1262_regulator_is_enabled,
This should also have a get_voltage() op that returns
(VAVDD + VAVSS) / 2. Otherwise it won't be usable as a
common mode voltage.
> +};
> +
> +static const struct regulator_ops ads1262_refout_regulator_ops = { };
> +
> +static const struct regulator_desc ads1262_vbias_regulator_desc = {
> + .name = "vbias",
> + .of_match = "vbias",
> + .regulators_node = "regulators",
> + .supply_name = "avdd",
What does supply_name do? Make "avdd-supply" the parent supply?
> + .ops = &ads1262_vbias_regulator_ops,
> + .type = REGULATOR_VOLTAGE,
> + .owner = THIS_MODULE,
> +};
> +
> +static const struct regulator_desc ads1262_refout_regulator_desc = {
> + .name = "refout",
> + .of_match = "refout",
> + .regulators_node = "regulators",
> + .supply_name = "avdd",
> + .n_voltages = 1,
> + .fixed_uV = 2500000,
> + .ops = &ads1262_refout_regulator_ops,
> + .type = REGULATOR_VOLTAGE,
> + .owner = THIS_MODULE,
> +};
> +
> +static int ads1262_register_regulators(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + struct regulator_config config = {
> + .dev = dev,
> + .driver_data = st,
> + };
> + struct regulator_dev *rdev;
Should we do...
if (!device_property_present(dev, "regulators"))
return 0;
here since regulators is not a required property?
> +
> + rdev = devm_regulator_register(dev, &ads1262_refout_regulator_desc,
> + &config);
> + if (IS_ERR(rdev))
> + return PTR_ERR(rdev);
> +
> + rdev = devm_regulator_register(dev, &ads1262_vbias_regulator_desc,
> + &config);
> +
> + return PTR_ERR_OR_ZERO(rdev);
> +}
> +
> static int ads1262_dev_configure(struct ads1262 *st)
> {
> struct device *dev = &st->spi->dev;
> @@ -1707,6 +1793,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
> if (ret)
> return dev_err_probe(dev, ret, "failed to configure device\n");
>
> + ret = ads1262_register_regulators(st);
> + if (ret)
> + return ret;
> +
> ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> iio_pollfunc_store_time,
> ads1262_trigger_handler,
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies
2026-08-08 3:58 ` [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
@ 2026-08-08 18:40 ` David Lechner
2026-08-09 8:29 ` Kurt Borja
0 siblings, 1 reply; 36+ messages in thread
From: David Lechner @ 2026-08-08 18:40 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 8/7/26 10:58 PM, Kurt Borja wrote:
> Enable common mode regulators. The usual configuration is to have our
> own 'vbias' regulator connected internally as common mode voltage on the
> AINCOM pin.
>
> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
> ---
> drivers/iio/adc/ti-ads1262.c | 25 +++++++++++++++++++++++++
> 1 file changed, 25 insertions(+)
>
> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
> index 533574169b04..238d803abc50 100644
> --- a/drivers/iio/adc/ti-ads1262.c
> +++ b/drivers/iio/adc/ti-ads1262.c
> @@ -962,6 +962,27 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
> return IRQ_HANDLED;
> }
>
> +static int ads1262_common_mode_setup(struct ads1262 *st)
> +{
> + struct device *dev = &st->spi->dev;
> + char name[sizeof("aincom")];
> + int ret;
> +
> + for (unsigned int i = 0; i <= ADS1262_INPMUX_AINCOM; i++) {
> + if (i < ADS1262_INPMUX_AINCOM)
> + scnprintf(name, sizeof(name), "ain%u", i);
> + else
> + scnprintf(name, sizeof(name), "aincom");
> +
> + ret = devm_regulator_get_enable_optional(dev, name);
We need to get the voltage which is then used to provide an offset
(IIO_CHAN_INFO_OFFSET) for the channel.
> + if (ret < 0 && ret != -ENODEV)
> + return dev_err_probe(dev, ret,
> + "failed to get common mode supply: %s\n", name);
> + }
> +
> + return 0;
> +}
> +
> static int ads1262_regulator_enable(struct regulator_dev *rdev)
> {
> struct ads1262 *st = rdev_get_drvdata(rdev);
> @@ -1797,6 +1818,10 @@ static int ads1262_spi_probe(struct spi_device *spi)
> if (ret)
> return ret;
>
> + ret = ads1262_common_mode_setup(st);
> + if (ret)
> + return ret;
> +
> ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
> iio_pollfunc_store_time,
> ads1262_trigger_handler,
>
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
@ 2026-08-08 22:28 ` Uwe Kleine-König
2026-08-09 16:24 ` Kurt Borja
2 siblings, 1 reply; 36+ messages in thread
From: Uwe Kleine-König @ 2026-08-08 22:28 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
[-- Attachment #1: Type: text/plain, Size: 323 bytes --]
Hello,
On Fri, Aug 07, 2026 at 10:58:24PM -0500, Kurt Borja wrote:
> +#include <linux/mod_devicetable.h>
> [...]
> +#include <linux/spi/spi.h>
Please rely on <linux/spi/spi.h> to provide the structs of_device_id and
spi_device_id and drop <linux/mod_devicetable.h>.
<linux/mod_devicetable.h> is planned to go away soon.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family
2026-08-08 18:38 ` David Lechner
@ 2026-08-09 8:26 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:26 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 Sat Aug 8, 2026 at 1:38 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> The ADS1262 and ADS1263 are 32-bit, 38.4-kSPS delta-sigma ADCs with an
>> integrated PGA, internal reference, excitation and burn-out current
>> sources for sensor biasing and diagnostics. The ADS1263 adds a second,
>> 24-bit delta-sigma ADC (ADC2) for background measurements.
>>
>
> ...
>
>> +patternProperties:
>> + "^ain([0-9]|com)-supply$":
>> + description:
>> + Common-mode voltage supply connected to AIN<N> or AINCOM.
>> +
>> + "^refp[1-3]-supply$":
>> + description:
>> + Positive voltage reference connected to REFP1 (AIN0), REFP2 (AIN2) or
>> + REFP3 (AIN4). If not described, its assumed to be connected to ground
>> + (0V).
>
> Would we really have a case with a negative only reference? I would say if not
> described, assume the pin is free for other use.
In bipolar supply configurations, I believe it would make sense to wire
the negative reference to -2.5V and ground the positive. Also we have
the ti,reference-reversal case, where you may wire the "negative"
reference to say 2.5V and the positive to ground.
>
>> +
>> + "^refn[1-3]-supply$":
>> + description:
>> + Negative voltage reference connected to REFN1 (AIN1), REFN2 (AIN3) or
>> + REFN3 (AIN5). If not described, its assumed to be connected to ground
>> + (0V).
>
> Assumption is only true when corresponding refp supply is described. Otherwise
> we should assume the pin is free for other uses.
I'll clarify it.
>
>> +
>> + "^ti,refp[1-3]-refn[1-3]-resistor-ohms$":
>> + description:
>> + Magnitude of the external reference resistor connected between REFP<N>
>> + and REFN<M>. In ratiometric configurations, such as RTD measurements, the
>> + IDAC excitation current returns through this resistor, generating the
>> + reference voltage for the conversion.
>> +
>> + "^channel@[0-9]+$":
>> + $ref: /schemas/iio/adc/adc.yaml#
>> + unevaluatedProperties: false
>> +
>> + properties:
>> + reg:
>> + maxItems: 1
>> +
>> + single-channel:
>> + minimum: 0
>> + maximum: 10
>> +
>> + common-mode-channel:
>> + minimum: 0
>> + maximum: 10
>> + default: 10
>> +
>> + diff-channels:
>> + description: |
>> + In addition to the analog input pins 0 (AIN0) - 10 (AINCOM), there are
>> + special inputs that can be selected from the following values:
>> + 11: Temperature sensor monitor
>> + 12: Analog power supply monitor
>> + 13: Digital power supply monitor
>> + 14: TDAC test signal
>
> For reasons mentioned in the reply to the cover letter, I'm not a fan of the
> monitor channels here.
>
>> + items:
>> + minimum: 0
>> + maximum: 14
>> +
>
> ...
>
>> + input-chopping: true
>> +
>> + ti,idac-rotation:
>
> Should we make this one a standard property like input-chopping?
Maybe excitation-current-chopping or excitation-channel-chopping?
>
>> + $ref: /schemas/types.yaml#/definitions/flag
>> + description:
>> + Automatically swap the IDAC1 and IDAC2 connections of alternate
>> + conversions. The ADC averages the alternate conversions to eliminate
>> + IDAC mismatch.
>> +
>
> ...
>
>> +examples:
>> + - |
>> + #include <dt-bindings/gpio/gpio.h>
>> + #include <dt-bindings/interrupt-controller/irq.h>
>> +
>> + spi {
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + adc@0 {
>> + compatible = "ti,ads1262";
>> + reg = <0>;
>> + spi-max-frequency = <8000000>;
>> + spi-cpha;
>> + avdd-supply = <&avdd>;
>> + dvdd-supply = <&dvdd>;
>> + #address-cells = <1>;
>> + #size-cells = <0>;
>> +
>> + interrupts-extended = <&gpio 0 IRQ_TYPE_EDGE_FALLING>;
>> + interrupt-names = "drdy";
>> +
>> + /* Typical common mode voltage configuration */
>> + aincom-supply = <&ads1262_vbias>;
>> +
>> + regulators {
>> + ads1262_vbias: vbias {
>> + regulator-name = "vbias";
>
> The node name is already "vbias" so giving regulator-name is redundant.
Is it okay to leave it empty?
ads1262_vbias: vbias { };
>
>> + };
>> + };
>> +
>> + channel@0 {
>> + reg = <0>;
>> + single-channel = <0>;
>> + /* The VBIAS is enabled on pin 10 (AINCOM) */
>> + common-mode-channel = <10>;
>> + };
>> + };
>> + };
>> +
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
2026-08-08 18:39 ` David Lechner
@ 2026-08-09 8:26 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:26 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 Sat Aug 8, 2026 at 1:39 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> Add the ti-ads1262 driver with initial support for the primary ADC
>> (ADC1). The ADS1263 auxiliary ADC (ADC2) is handled by a separate driver
>> and interoperability considerations were taken into account.
>
> Should probably mention here that IIO_CHAN_INFO_SCALE is intentionally
> left out here. (Or just implement it using internal reference voltage
> to start with.)
I'll mention the scale is left out.
>
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
[...]
>> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
[...]
>> +static int ads1262_dev_reset(struct ads1262 *st)
>> +{
>> + int ret;
>> +
>> + if (st->reset_gpiod) {
>> + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1);
>> + if (ret)
>> + return ret;
>> +
>> + /*
>> + * The RESET pulse timing requirement is 4 clock cycles, at the
>> + * minimum clock rate this is 4 microseconds.
>> + */
>> + fsleep(4);
>
> How long do we have to hold reset before the chip powers down?
For power down 65536 clk cycles. Less than that is simple reset.
[...]
>> +static int ads1262_wait_for_conversion(struct ads1262 *st)
>> +{
>> + u64 max_lat_ms;
>> + long ret;
>> +
>> + /*
>> + * The first conversion latency is affected by the channel's data rate,
>> + * filter, the configurable conversion delay and whether chop mode
>> + * and/or IDAC rotation mode are enabled.
>> + *
>> + * The worst possible latency is calculated by taking the lowest data
>> + * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms
>> + * (Table 9-13). Then we scale it by the actual clock rate and multiply
>> + * by 4 to account for chop and IDAC rotation modes (Equation 20).
>> + */
>> + max_lat_ms = 4 * div_u64(mul_u32_u32(1600, 7372800), st->clk_rate);
>
> These are constant values, so don't need mul_u32_u32(). Also, given the wide
> range of possible sampling rates, I would include the current sampling rate
> in the calculation. No need to wate 1.6 seconds for something that should
> take a few 10s of microseconds.
Can we leave it like this until I implement the settlingtime? That way I
can calculate the actual first conversion latency.
[...]
>> +static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg,
>> + unsigned int writeval, unsigned int *readval)
>> +{
>> + struct ads1262 *st = iio_priv(indio_dev);
>> +
>> + guard(mutex)(&st->xfer_lock);
>> +
>> + if (readval)
>> + return regmap_read_bypassed(st->regmap, reg, readval);
>
> Don't trust the cache? :-)
I don't trust myself :-) This was relevant early in development, I'll go
with the normal version.
[...]
>> +static int ads1262_parse_channels(struct iio_dev *indio_dev)
>> +{
>> + struct ads1262 *st = iio_priv(indio_dev);
>> + struct device *dev = &st->spi->dev;
>> + struct iio_chan_spec *specs;
>> + unsigned long used_regs = 0;
>> + int num_specs;
>> + u32 reg;
>> + int ret;
>> +
>> + st->num_channels = device_get_named_child_node_count(dev, "channel");
>> + if (!st->num_channels)
>> + return dev_err_probe(dev, -ENXIO, "no 'channel' nodes configured\n");
>> + if (st->num_channels > ADS1262_MAX_CHANNEL_COUNT)
>> + return dev_err_probe(dev, -EINVAL, "too many channels\n");
>> +
>> + /* Account for the timestamp channel */
>> + num_specs = st->num_channels + 1;
>> + specs = devm_kcalloc(dev, num_specs, sizeof(*specs), GFP_KERNEL);
>> + if (!specs)
>> + return -ENOMEM;
>> +
>> + device_for_each_named_child_node_scoped(dev, node, "channel") {
>> + ret = fwnode_property_read_u32(node, "reg", ®);
>> + if (ret)
>> + return dev_err_probe(dev, ret, "%s: failed to read channel reg\n",
>> + fwnode_get_name(node));
>> + if (reg >= st->num_channels)
>> + return dev_err_probe(dev, -EINVAL, "%s: reg out of range\n",
>> + fwnode_get_name(node));
>> +
>> + static_assert(ADS1262_MAX_CHANNEL_COUNT < BITS_PER_LONG);
>> + if (__test_and_set_bit(reg, &used_regs))
>> + return dev_err_probe(dev, -EINVAL, "%s: duplicated channel reg\n",
>> + fwnode_get_name(node));
>> +
>> + specs[reg].scan_index = reg;
>> + specs[reg].scan_type = (struct iio_scan_type) {
>> + .format = IIO_SCAN_FORMAT_SIGNED_INT,
>> + .realbits = ADS1262_ADC1_RESOLUTION,
>> + .storagebits = 32,
>> + .endianness = IIO_BE,
>> + };
>> +
>> + ret = ads1262_parse_channel_node(st, &specs[reg], node);
>> + if (ret)
>> + return ret;
>> +
>> + if (specs[reg].channel == ADS1262_INPMUX_TEMP)
>> + specs[reg].type = IIO_TEMP;
>> + else
>> + specs[reg].type = IIO_VOLTAGE;
>> +
>> + if (specs[reg].channel != ADS1262_INPMUX_TEMP)
>> + specs[reg].indexed = true;
>> +
>> + specs[reg].info_mask_separate = BIT(IIO_CHAN_INFO_RAW);
>> + }
>
> If we are going to use reg to determine the scan index, we need to
> make sure there are no holes in specs that didn't get filled in.
>
> device_for_each_named_child_node_scoped() will skip `status = "disabled"`
> channels, so this could be a possibility.
Ah, I didn't know some channels could be skipped. I'll check for holes.
[...]
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency
2026-08-08 18:39 ` David Lechner
@ 2026-08-09 8:27 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:27 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 Sat Aug 8, 2026 at 1:39 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> Add per-channel sampling frequency support. The "available" attribute is
>> assigned per-channel too, in order to eventually support per-filter
>> availability.
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> drivers/iio/adc/ti-ads1262.c | 159 ++++++++++++++++++++++++++++++++++++++++++-
>> 1 file changed, 158 insertions(+), 1 deletion(-)
>>
>> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
>> index d78e5e3ae13e..b3b7b1249102 100644
>> --- a/drivers/iio/adc/ti-ads1262.c
>> +++ b/drivers/iio/adc/ti-ads1262.c
[...]
>> @@ -148,6 +149,7 @@ enum {
>> ADS1262_DR_14400_SPS,
>> ADS1262_DR_19200_SPS,
>> ADS1262_DR_38400_SPS,
>> + ADS1262_DR_COUNT,
>> };
>>
>
> /* FIR filter has limited data rate range. */
> #define ADS1262_DR_COUNT_FIR_FILTER (ADS1262_DR_20_SPS + 1)
>
> So we can properly restrict the rate when the FIR filter
> is selected.
There's also a hole in between. The 16.6 SPS data rate doesn't support
the FIR filter. Maybe I'll need one array specifically for the FIR
filter.
[...]
>> +static void ads1262_populate_samp_freqs(struct ads1262 *st,
>> + struct ads1262_channel *chan)
>> +{
>> + int freq_Hz, freq_rem;
>
> Shouldn't these be u64 and u32?
I would have to cast them to int either way. Also I think it shouldn't
be a problem within the recommended clock rate.
[...]
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain
2026-08-08 18:39 ` David Lechner
@ 2026-08-09 8:28 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:28 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 Sat Aug 8, 2026 at 1:39 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> Allow each channel to select its voltage reference through the
>> "reference-sources" firmware property. Then, use the reference voltage
>> to calculate available scales.
>
> It looks like this is also implementing PGA gain at the same time, but
> isn't mentioned in ght commit messsage. I would also expect something
> here about how we should handle PGA bypass (even if it just says default
> works always and we can consdier controlling it later).
I forgot about the PGA bypass stuff. I do believe it should belong in
devicetree because it changes the voltage range of the analog inputs.
See datasheet section 10.3 and 7.3.
>
>>
>> The ADS1262 allows single-ended supply configurations or bipolar supply
>> configurations. In single ended configurations both the analog and
>> digital rails share the same ground, i.e. AVSS = DGND = 0 V. In bipolar
>> supply configurations, AVSS can go below ground, e.g. AVSS = -2.5 V.
>>
>> If AVSS is below ground, the ADC can achieve true bipolar measurements
>> and the external references can also have voltage levels below ground.
>> This is currently an issue because the regulator subsystem doesn't
>> support negative voltages.
>>
>> The ad4170-4 driver faces this problem too and the same workaround is
>> used in this case: assume every regulator reports magnitudes (absolute
>> values). If the chip has a bipolar supply configuration, then assume
>> positive references are above ground (>= 0 V) and negative references
>> are below ground (<= 0 V). This is not a hardware constraint, but it is
>> the most common wiring.
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> drivers/iio/adc/ti-ads1262.c | 417 +++++++++++++++++++++++++++++++++++++++++--
>> 1 file changed, 406 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
>> index b3b7b1249102..360ce01a5871 100644
>> --- a/drivers/iio/adc/ti-ads1262.c
>> +++ b/drivers/iio/adc/ti-ads1262.c
[...]
>> @@ -688,6 +818,91 @@ static const struct regmap_bus ads1262_regmap_bus = {
>> .max_raw_write = ADS1262_MAX_REGMAP_WRITE,
>> };
>>
>> +static void ads1262_calculate_scales(int (*scales)[2], size_t num_scales,
>> + u32 full_scale, u64 mult,
>> + u32 resolution)
>> +{
>> + unsigned int i;
>> + s64 val;
>> +
>> + for (i = 0; i < num_scales; i++) {
>
> This could use a comment explaining the relasionship of the index in the
> array to the PGA multipier.
>
>> + val = mul_u64_u64_shr(full_scale, mult, resolution - 1 + i);
>> + iio_val_s64_decompose(val, &scales[i][0], &scales[i][1]);
>> + }
>> +}
>> +
>> +static int ads1262_populate_scales_resistance(struct ads1262 *st,
>> + const struct iio_chan_spec *spec)
>> +{
>> + struct ads1262_channel *chan = &st->channels[spec->scan_index];
>> + u32 full_scale;
>> +
>> + if (WARN_ON(!ads1262_ref_is_external(chan->ref_p, chan->ref_n)))
>> + return -EINVAL;
>
> WARN_ON() is a bit strong for something that is coming from the devicetree.
> I would just fail the parse() function with an appropriate error message
> so that we don't have to check here.
Actually, an IIO_RESISTANCE channel without external reference is just a
bug and would read past the end of buffer below, which is the only
reason I verify it one last time. We should never actually hit this
warning. I'll add a comment explaining that.
>
>> +
>> + full_scale = st->rref_ohms[chan->ref_p - 1][chan->ref_n - 1];
>> +
>> + chan->num_scales = ARRAY_SIZE(chan->scales);
>> +
>> + ads1262_calculate_scales(chan->scales, chan->num_scales, full_scale,
>> + PICO, ADS1262_ADC1_RESOLUTION);
>> +
>> + return 0;
>> +}
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling
2026-08-08 18:39 ` David Lechner
@ 2026-08-09 8:28 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:28 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 Sat Aug 8, 2026 at 1:39 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> Add triggered buffer support and a data-ready (DRDY) hardware trigger.
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> drivers/iio/adc/Kconfig | 2 +
>> drivers/iio/adc/ti-ads1262.c | 264 +++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 266 insertions(+)
>>
>> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
>> index dbf76427912b..b9b561be8347 100644
>> --- a/drivers/iio/adc/Kconfig
>> +++ b/drivers/iio/adc/Kconfig
[...]
>> @@ -241,8 +245,16 @@ struct ads1262 {
>> bool need_avss_uV;
>> bool bipolar_supply;
>>
>> + IIO_DECLARE_BUFFER_WITH_TS(__be32, scan_buffer,
>> + ADS1262_MAX_CHANNEL_COUNT);
>> +
>> /* Protects transfer buffers and concurrent SPI transfers */
>> struct mutex xfer_lock;
>> + struct spi_message msg;
>> + struct spi_transfer xfer;
>> +
>
> Where does the 11 come from?
Size needed to hold both transfers for multi channel read. I can use a
macro here.
>
>> + u8 tx[11] __aligned(IIO_DMA_MINALIGN);
>> + u8 rx[11] __aligned(IIO_DMA_MINALIGN);
>
> don't need second one to be aligned, they aren't indepedant.
Ah, I forgot this observation in the last version. These are used in a
full-duplex transfer, wouldn't that require for both to be on its own
cache line? I just started learning about DMA.
[...]
>> +static int ads1262_enable_and_read_last(struct ads1262 *st,
>> + const struct iio_chan_spec *spec,
>> + __be32 *val)
>> +{
>> + struct ads1262_channel *chan;
>> + int ret;
>> +
>> + lockdep_assert_held(&st->xfer_lock);
>
> What happens if something else (e.g. gpio in the future) decides to do a
> register write here. If it wins the race, will it unintentially read the
> data? So do we also need to read the stored data via command here too?
On each trigger, we are holding the lock before we enable the first
channel, until after we read the final conversion. So we don't really
care if there's concurrent activity in-between triggers. Am I missing
something?
>
>> +
>> + if (spec) {
>> + guard(mutex)(&st->chan_lock);
>> +
>> + chan = &st->channels[spec->scan_index];
>> +
>> + /* Group 1: MODE0, MODE1, MODE2, INPMUX */
>> + st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG;
>> + st->tx[1] = ADS1262_INPMUX_REG - ADS1262_MODE0_REG;
>> + st->tx[2] = FIELD_PREP(ADS1262_MODE0_INPUT_CHOP_MASK, chan->input_chop) |
>> + FIELD_PREP(ADS1262_MODE0_IDAC_CHOP_MASK, chan->idac_chop) |
>> + FIELD_PREP(ADS1262_MODE0_RUNMODE_MASK, ADS1262_RUNMODE_CONTINUOUS) |
>> + FIELD_PREP(ADS1262_MODE0_REFREV_MASK, chan->ref_reversal);
>> + st->tx[3] = FIELD_PREP(ADS1262_MODE1_FILTER_MASK, ADS1262_FILTER_FIR);
>> + st->tx[4] = FIELD_PREP(ADS1262_MODE2_DR_MASK, chan->data_rate) |
>> + FIELD_PREP(ADS1262_MODE2_GAIN_MASK, chan->gain);
>> + st->tx[5] = FIELD_PREP(ADS1262_INPMUX_MUXP_MASK, spec->channel) |
>> + FIELD_PREP(ADS1262_INPMUX_MUXN_MASK, spec->channel2);
>> +
>> + /* Group 2: IDACMUX, IDACMAG, REFMUX */
>> + st->tx[6] = ADS1262_IDACMUX_REG | ADS1262_OPCODE_WREG;
>> + st->tx[7] = ADS1262_REFMUX_REG - ADS1262_IDACMUX_REG;
>> + st->tx[8] = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, chan->idac_mux[0]) |
>> + FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, chan->idac_mux[1]);
>> + st->tx[9] = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, chan->idac_mag[0]) |
>> + FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, chan->idac_mag[1]);
>> + st->tx[10] = FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, chan->ref_p) |
>> + FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, chan->ref_n);
>> + } else {
>> + memset(st->tx, 0, sizeof(st->tx));
>> + }
>> +
>> + ret = spi_sync(st->spi, &st->msg);
>> + if (ret)
>> + return ret;
>> +
>> + memcpy(val, st->rx, sizeof(*val));
>> +
>> + return 0;
>> +}
>> +
>> +static int ads1262_fill_buffer_mult(struct iio_dev *indio_dev)
>> +{
>> + struct ads1262 *st = iio_priv(indio_dev);
>> + unsigned int chan;
>> + __be32 val;
>> + int i = -1;
>> + int ret;
>> +
>> + /*
>> + * This routine enables and reads channels in a full-duplex fashion.
>> + *
>> + * When a channel is enabled, the previous conversion is clocked out of
>> + * the shift data register on the same transfer (Section 9.4.7.1). This
>> + * allows for low latency software sequencing but forbids any
>> + * communication with the chip in-between or data corruption may occur,
>> + * hence the need to take the xfer_lock for the whole operation.
>> + */
>> + guard(mutex)(&st->xfer_lock);
>> +
>> + iio_for_each_active_channel(indio_dev, chan) {
>> + ret = ads1262_enable_and_read_last(st, &indio_dev->channels[chan],
>> + &val);
>> + if (ret)
>> + return ret;
>> +
>> + /*
>> + * After writing to the channel configuration registers, the
>> + * conversion-cycle is restarted and the data registers are
>> + * cleared. This means we have to reinit the completion after
>> + * enabling to avoid reading stale data.
>> + */
>> + reinit_completion(&st->drdy);
>
> This seems racy still as DRDY could have been triggered already, in which case
> we would time out waiting for the interrupt. Or does the DRDY toggle again
> even if we don't read the data to trigger another conversion?
Yep, in continuous mode it just keeps toggling. However, data corruption
may occur if we read just before DRDY is about to toggle again. Which is
why...
>
> Would it be possible to make one big SPI messsage that contains all enabled
> channels and just run that instead? Insted of waiting for drdy, it would have
> to add a delay at the end of the sequence of xfers for setting up each channel
> that was long enough to ensure that the conversion will be done when we read
> it.
>
> Or we could just do similar to the start_one() function and don't leave
> it in continuous conversion mode. Using the delay option of spi xfers, we
> could just tack on two more commands to start and stop the conversion after
> after writing all of the mode stuff so that it still all happens in one SPI
> message.
...this gave me an idea. Instead of relying on delays, which are a bit
of a pain to calculate because the datasheet only gives latency values
for the nominal clock speed (I'll have to reverse engineer the formulas
for settlingtime :]). I can actually read in pulse mode here and stuff
the start commands inside the same transfer. The buffer would look like:
6 bytes | 5 bytes | 1 byte
--------------+---------------+----------
channel_cfg_1 | channel_cfg_2 | start cmd
Thankfully we can chain commands without having to lift the CS line so
this is efficient. I didn't know this when I first started developing
the driver.
The buffer of course can be further optimized if say, all channels share
the same IDAC and reference configuration, but that can be done later if
needed.
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators
2026-08-08 18:40 ` David Lechner
@ 2026-08-09 8:28 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:28 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 Sat Aug 8, 2026 at 1:40 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> Register the "refout" and "vbias" regulators to be able to use them as
>> common mode supplies.
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> drivers/iio/adc/Kconfig | 1 +
>> drivers/iio/adc/ti-ads1262.c | 90 ++++++++++++++++++++++++++++++++++++++++++++
>> 2 files changed, 91 insertions(+)
[...]
>> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
>> index 24a7ecb9fbd4..533574169b04 100644
>> --- a/drivers/iio/adc/ti-ads1262.c
>> +++ b/drivers/iio/adc/ti-ads1262.c
[...]
>> +static const struct regulator_ops ads1262_vbias_regulator_ops = {
>> + .enable = ads1262_regulator_enable,
>> + .disable = ads1262_regulator_disable,
>> + .is_enabled = ads1262_regulator_is_enabled,
>
> This should also have a get_voltage() op that returns
> (VAVDD + VAVSS) / 2. Otherwise it won't be usable as a
> common mode voltage.
I'll add this.
>
>> +};
>> +
>> +static const struct regulator_ops ads1262_refout_regulator_ops = { };
>> +
>> +static const struct regulator_desc ads1262_vbias_regulator_desc = {
>> + .name = "vbias",
>> + .of_match = "vbias",
>> + .regulators_node = "regulators",
>> + .supply_name = "avdd",
>
> What does supply_name do? Make "avdd-supply" the parent supply?
I haven't looked at the code, but it seems like it (sysfs verfies it).
I'll check how is supply_name handled before the next version.
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies
2026-08-08 18:40 ` David Lechner
@ 2026-08-09 8:29 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:29 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 Sat Aug 8, 2026 at 1:40 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>> Enable common mode regulators. The usual configuration is to have our
>> own 'vbias' regulator connected internally as common mode voltage on the
>> AINCOM pin.
>>
>> Signed-off-by: Kurt Borja <kuurtb@gmail.com>
>> ---
>> drivers/iio/adc/ti-ads1262.c | 25 +++++++++++++++++++++++++
>> 1 file changed, 25 insertions(+)
>>
>> diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c
>> index 533574169b04..238d803abc50 100644
>> --- a/drivers/iio/adc/ti-ads1262.c
>> +++ b/drivers/iio/adc/ti-ads1262.c
>> @@ -962,6 +962,27 @@ static irqreturn_t ads1262_irq_handler(int irq, void *dev_id)
>> return IRQ_HANDLED;
>> }
>>
>> +static int ads1262_common_mode_setup(struct ads1262 *st)
>> +{
>> + struct device *dev = &st->spi->dev;
>> + char name[sizeof("aincom")];
>> + int ret;
>> +
>> + for (unsigned int i = 0; i <= ADS1262_INPMUX_AINCOM; i++) {
>> + if (i < ADS1262_INPMUX_AINCOM)
>> + scnprintf(name, sizeof(name), "ain%u", i);
>> + else
>> + scnprintf(name, sizeof(name), "aincom");
>> +
>> + ret = devm_regulator_get_enable_optional(dev, name);
>
> We need to get the voltage which is then used to provide an offset
> (IIO_CHAN_INFO_OFFSET) for the channel.
Sure! I didn't think it was necessary but I can definitely add it.
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support
2026-08-08 18:37 ` [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support David Lechner
@ 2026-08-09 8:29 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 8:29 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 Sat Aug 8, 2026 at 1:37 PM -05, David Lechner wrote:
> On 8/7/26 10:58 PM, Kurt Borja wrote:
>
> ...
>
>> - @David: I added support for the monitor channels, but I prefer to
>> parse them from DT instead of making them static (similar to the
>> ad4170-4 approach too :p).
>
> Why? Unless there really is some property that depends on how the
> system is wired up, it seems like this is just making unnecessary
> work for users to be able to use the monitor channels. And if someone
> decided later that they do in fact want to use the monitoring channel
> and it wasn't in the devicetree, sometimes it can be very difficult
> to actually change the devicetree.
The only thing I can think of is the reference source. The datasheet
says "Measure the supply monitor readings using either the internal or
an external reference".
I saw that the ti-ads112c14 also allows the monitors to be referenced
externally but you didn't implement support for it. In my case I think
it's okay to leave it unimplemented too and make the channels static.
>
> The monitor inputs also have many restrictions compared to a
> normal input that it would be really hard to describe correctly
> in the bindings without allowing things that should not actually
> be allowed. (can't have excitation current or burnout, temperature
> channel requires internal reference, most should be single-channel,
> etc.)
Good point.
>
>>
>> - @David: About filters... As I mentioned in the previous version, the
>> data_rate configuration takes precedence over the filter selection.
>> If an incompatible filter (given a data rate) is selected, the chip
>> resorts to a sane compatible one when doing conversions (either
>> SINC1 or plain SINC5).
>>
>> Now, I don't know how to expose this in userspace. Should I limit
>> the sampling_frequency_available attribute (given a filter)? Or
>> should it be the other way around, limit the filter_type_available
>> attribute (given a data rate)?.
> I figured that the filter type selection would be more important than
> the rate so when I implemented it for ADS112C14, I made it so that
> one has to pick the filter first and everything else flows from that.
> (I didn't expose sampling frequency until the same time as filter type.)
>
> The thinking behind this is that if you do care about filtering, then
> you are picking filter type and sampling rate to get certain notches
> and/or frequency response of the filter rather than trying to get a
> faster or slower sample rate.
I think this makes a lot of sense in your chip because there is no
plain "data rate" register. The data rate ends up being a consequence of
the modulator divider + OSR/filter settings.
>
> And the driver also allows using an hrtimer trigger to do single-shot
> samples for cases where one doesn't want to sample as fast as possible
> in continuous mode. This would be more useful to someone who just cares
> about sample rate and not about filtering.
Why did you go for this instead of just leaving the continuous mode
running and reading on each trigger?
>
> Just posted the series yesterday:
> https://lore.kernel.org/linux-iio/20260807-iio-adc-ti-ads112c14-filter-support-v1-0-4d3ba00caf18@baylibre.com/T/#t
Can you Cc me this series too? The settlingtime stuff is something I'll
implement too.
>
> ADS126X seems a little less complicated in this regard though
> as the same sampling rates are available for all filters with
> the exception of the FIR filter having a limited subset. So I
> would go with the option to limit sampling rate based on filter
> type, not the other way around. If a higher rate is selected
> when changing to the FIR filter type, just have it go to the
> max (20 SPS).
I'll go for this!
>
Thank you very much for your review and tags :)
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
* Re: [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver
2026-08-08 22:28 ` Uwe Kleine-König
@ 2026-08-09 16:24 ` Kurt Borja
0 siblings, 0 replies; 36+ messages in thread
From: Kurt Borja @ 2026-08-09 16:24 UTC (permalink / raw)
To: Uwe Kleine-König, 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 Aug 8, 2026 at 5:28 PM -05, Uwe Kleine-König wrote:
> Hello,
>
> On Fri, Aug 07, 2026 at 10:58:24PM -0500, Kurt Borja wrote:
>> +#include <linux/mod_devicetable.h>
>> [...]
>> +#include <linux/spi/spi.h>
>
> Please rely on <linux/spi/spi.h> to provide the structs of_device_id and
> spi_device_id and drop <linux/mod_devicetable.h>.
> <linux/mod_devicetable.h> is planned to go away soon.
Thanks for the heads up!
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 36+ messages in thread
end of thread, other threads:[~2026-08-09 16:24 UTC | newest]
Thread overview: 36+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-08 3:58 [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support Kurt Borja
2026-08-08 3:58 ` [PATCH v3 1/9] dt-bindings: iio: adc: support the TI ADS126x ADC family Kurt Borja
2026-08-08 4:08 ` sashiko-bot
2026-08-08 18:38 ` David Lechner
2026-08-09 8:26 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 2/9] iio: adc: add the ti-ads1262 driver Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-09 8:26 ` Kurt Borja
2026-08-08 22:28 ` Uwe Kleine-König
2026-08-09 16:24 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 3/9] iio: adc: ti-ads1262: support per-channel sampling frequency Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-09 8:27 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 4/9] iio: adc: ti-ads1262: support per-channel reference and gain Kurt Borja
2026-08-08 18:39 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 5/9] iio: adc: ti-ads1262: support input chopping Kurt Borja
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 6/9] iio: adc: ti-ads1262: support excitation currents Kurt Borja
2026-08-08 4:13 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-08 3:58 ` [PATCH v3 7/9] iio: adc: ti-ads1262: support triggered buffer sampling Kurt Borja
2026-08-08 4:09 ` sashiko-bot
2026-08-08 18:39 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 8/9] iio: adc: ti-ads1262: support REFOUT and VBIAS regulators Kurt Borja
2026-08-08 4:11 ` sashiko-bot
2026-08-08 18:40 ` David Lechner
2026-08-09 8:28 ` Kurt Borja
2026-08-08 3:58 ` [PATCH v3 9/9] iio: adc: ti-ads1262: support common mode supplies Kurt Borja
2026-08-08 18:40 ` David Lechner
2026-08-09 8:29 ` Kurt Borja
2026-08-08 18:37 ` [PATCH v3 0/9] iio: adc: Add TI ADS126X ADC family support David Lechner
2026-08-09 8:29 ` Kurt Borja
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).