* [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support
@ 2026-06-12 22:46 Kurt Borja
2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja
` (5 more replies)
0 siblings, 6 replies; 14+ messages in thread
From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw)
To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Linus Walleij, Bartosz Golaszewski
Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio,
devicetree, linux-kernel, linux-gpio, Kurt Borja,
Jonathan Cameron
Hi all,
This series introduces support for TI ADS1262 and ADS1263 ADCs [1].
These devices are very similar (if not the same), except ADS1263
includes a secondary auxiliary ADC.
The main ADC has quite a few features supported the main driver
(ti-ads1262), including:
- Power management
- IIO direct and buffer modes
- Channel hot-reloading
- Internal or external oscillator
- Internal or external voltage reference
- Filter configuration
- Sensor bias configuration
- IDAC configuration
- Level-shift voltage configuration
- Manual calibration support
- GPIO controller capabilities
I plan to add these features to the main driver soon:
- SPI offload support (38400 SPS turns out to be too high for some
systems)
- User triggered, automatic calibration (Datasheet 9.4.9)
Additionally, full support for the (less capable) auxiliary ADC is
introduced by the auxiliary ti-ads1263-adc2 driver included in this
series.
The auxiliary ADC operates almost completely independent of the main
ADC. The only consideration that has to be taken for interoperability is
when reading conversion data in direct mode (Datasheet 9.4.7.1), which
happens only in buffer mode, when multiple channels are enabled.
When reading data in direct mode, all SPI activity is forbidden between
the data-ready signal and the data retrieval. To achieve this a second
mutex called xfer_lock was introduced to block SPI activity on the
device.
This is one of the biggest drivers I've developed, so I hope the code
and the comments are self-explainatory. If not, please let me know so I
can clarify them.
As always, thanks for your reviews and help. Submitting upstream is
always a great learning experience :)
[1] https://www.ti.com/lit/ds/symlink/ads1263.pdf
Signed-off-by: Kurt Borja <kuurtb@gmail.com>
---
Kurt Borja (5):
dt-bindings: iio: adc: Add TI ADS126x ADC family
iio: adc: Add ti-ads1262 driver
iio: adc: ti-ads1262: Add GPIO controller support
iio: adc: ti-ads1262: Add calibration support
iio: adc: Add ti-ads1263-adc2 driver
.../devicetree/bindings/iio/adc/ti,ads1262.yaml | 308 +++
.../bindings/iio/adc/ti,ads1263-adc2.yaml | 49 +
MAINTAINERS | 10 +
drivers/iio/adc/Kconfig | 26 +
drivers/iio/adc/Makefile | 2 +
drivers/iio/adc/ti-ads1262.c | 2180 ++++++++++++++++++++
drivers/iio/adc/ti-ads1262.h | 39 +
drivers/iio/adc/ti-ads1263-adc2.c | 470 +++++
8 files changed, 3084 insertions(+)
---
base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e
change-id: 20251129-ads126x-fb6107505cae
--
Thanks,
~ Kurt
^ permalink raw reply [flat|nested] 14+ messages in thread* [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja @ 2026-06-12 22:46 ` Kurt Borja 2026-06-12 22:53 ` sashiko-bot 2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja ` (4 subsequent siblings) 5 siblings, 1 reply; 14+ messages in thread From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw) To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio, Kurt Borja, Jonathan Cameron Add TI ADS1262 and TI ADS1263 ADC2 devicetree bindings documentation. Signed-off-by: Kurt Borja <kuurtb@gmail.com> --- .../devicetree/bindings/iio/adc/ti,ads1262.yaml | 308 +++++++++++++++++++++ .../bindings/iio/adc/ti,ads1263-adc2.yaml | 49 ++++ MAINTAINERS | 7 + 3 files changed, 364 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml new file mode 100644 index 000000000000..1c4fde94f6c7 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml @@ -0,0 +1,308 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1262.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI ADS1262/ADS1263 analog to digital converter + +maintainers: + - Kurt Borja <kuurtb@gmail.com> + +description: | + The ADS1262 and ADS1263 are 38.4-kSPS, delta-sigma (ΔΣ) ADCs with an + integrated PGA, reference, and internal fault monitors. The ADS1263 integrates + an auxiliary, 24-bit, ΔΣ ADC intended for background measurements. + + Datasheets: + - ADS126x: https://www.ti.com/lit/ds/symlink/ads1262.pdf + +properties: + compatible: + enum: + - ti,ads1262 + - ti,ads1263 + + reg: + maxItems: 1 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + spi-max-frequency: + maximum: 8000000 + + spi-cpha: true + + interrupts: + description: Data ready (DRDY) interrupt line. + maxItems: 1 + + start-gpios: + description: Start conversion control. + maxItems: 1 + + reset-gpios: + maxItems: 1 + + dvdd-supply: + description: Digital power supply. + + avdd-supply: + description: Analog power supply. + + vref-supply: + description: Optional external voltage reference. + + ti,pos-refmux: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the positive voltage reference input: + 0: Internal 2.5 V reference + 1: AIN0 pin + 2: AIN2 pin + 3: AIN4 pin + 4: AVDD pin + minimum: 0 + maximum: 4 + default: 0 + + ti,neg-refmux: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the negative voltage reference input: + 0: Internal 2.5 V reference + 1: AIN1 pin + 2: AIN3 pin + 3: AIN5 pin + 4: AVSS pin + minimum: 0 + maximum: 4 + default: 0 + + ti,vbias: + $ref: /schemas/types.yaml#/definitions/flag + description: Enables the level-shift voltage on the AINCOM pin. + default: false + + ti,idac1-pin: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the analog input pin to connect IDAC1: + 0: AIN0 + 1: AIN1 + 2: AIN2 + 3: AIN3 + 4: AIN4 + 5: AIN5 + 6: AIN6 + 7: AIN7 + 8: AIN8 + 9: AIN9 + 10: AINCOM + 11: No Connection + minimum: 0 + maximum: 11 + default: 11 + + ti,idac1-microamp: + description: Selects the current values of IDAC1. + enum: [0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000] + default: 0 + + ti,idac2-pin: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the analog input pin to connect IDAC2: + 0: AIN0 + 1: AIN1 + 2: AIN2 + 3: AIN3 + 4: AIN4 + 5: AIN5 + 6: AIN6 + 7: AIN7 + 8: AIN8 + 9: AIN9 + 10: AINCOM + 11: No Connection + minimum: 0 + maximum: 11 + default: 11 + + ti,idac2-microamp: + description: Selects the current values of IDAC2. + enum: [0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000] + default: 0 + + clocks: + maxItems: 1 + + '#io-channel-cells': + const: 1 + + '#gpio-cells': + const: 2 + + gpio-controller: true + + adc: + $ref: /schemas/iio/adc/ti,ads1263-adc2.yaml# + +required: + - compatible + - reg + - avdd-supply + - dvdd-supply + - '#address-cells' + - '#size-cells' + +unevaluatedProperties: false + +patternProperties: + "^channel@[0-9]+$": + $ref: /schemas/iio/adc/adc.yaml# + additionalProperties: false + + properties: + reg: + maxItems: 1 + + diff-channels: + description: | + Selects the analog input configuration for this channel. The first + value is the positive input and the second is the negative input. + The following values are available: + 0: AIN0 pin + 1: AIN1 pin + 2: AIN2 pin + 3: AIN3 pin + 4: AIN4 pin + 5: AIN5 pin + 6: AIN6 pin + 7: AIN7 pin + 8: AIN8 pin + 9: AIN9 pin + 10: AINCOM pin + 11: Temperature sensor monitor + 12: Analog power supply monitor + 13: Digital power supply monitor + 14: TDAC test signal + 15: Float (open connection) + items: + minimum: 0 + maximum: 15 + + ti,chop-mode: + $ref: /schemas/types.yaml#/definitions/flag + description: + When enabled, the ADC performs two internal conversions to cancel the + input offset voltage. The first conversion is taken with normal input + polarity. The ADC reverses the internal input polarity for the second + conversion. The difference of the two conversions is computed to yield + the final corrected result with the offset voltage removed. + default: false + + ti,idac-rotation-mode: + $ref: /schemas/types.yaml#/definitions/flag + description: + The rotation mode automatically swaps the IDAC1 and IDAC2 connections + of alternate conversions. The ADC averages the alternate conversions + to eliminate IDAC mismatch. + default: false + + ti,pga-bypass: + $ref: /schemas/types.yaml#/definitions/flag + description: Bypass the Programmable Gain Amplifier (PGA). + default: false + + ti,rev-vref-pol: + $ref: /schemas/types.yaml#/definitions/flag + description: + The reference polarity can be negative, but the ADC requires a + positive voltage reference. In this case, the reference + polarity-reversal switch changes the reference polarity from negative + to positive. + default: false + + ti,sbias-connection: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the sensor bias current source connection: + 0: Sensor bias connected to ADC1 mux out + 1: Sensor bias connected to ADC2 mux out + minimum: 0 + maximum: 1 + default: 0 + + ti,sbias-polarity: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the sensor bias current source polarity: + 0: Sensor bias pull-up + 1: Sensor bias pull-down + minimum: 0 + maximum: 1 + default: 0 + + ti,sbias-magnitude: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the sensor bias magnitude: + 0: No sensor bias current or resistor + 1: 0.5-uA sensor bias current + 2: 2-uA sensor bias current + 3: 10-uA sensor bias current + 4: 50-uA sensor bias current + 5: 200-uA sensor bias current + 6: 10-Mohm resistor + minimum: 0 + maximum: 6 + default: 0 + + required: + - reg + +allOf: + - $ref: /schemas/spi/spi-peripheral-props.yaml# + +examples: + - | + #include <dt-bindings/gpio/gpio.h> + + spi { + #address-cells = <1>; + #size-cells = <0>; + + adc@0 { + compatible = "ti,ads1263"; + reg = <0>; + spi-max-frequency = <8000000>; + spi-cpha; + avdd-supply = <&avdd>; + dvdd-supply = <&dvdd>; + #address-cells = <1>; + #size-cells = <0>; + + reset-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; + + /* AINP: 0 - AINN: AINCOM */ + channel@0 { + reg = <0>; + diff-channels = <0x0 0xA>; + }; + + /* Temperature sensor monitor */ + channel@1 { + reg = <1>; + diff-channels = <0xB 0xB>; + }; + + adc { + compatible = "ti,ads1263-adc2"; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml new file mode 100644 index 000000000000..9dd5577589b1 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/iio/adc/ti,ads1263-adc2.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI ADS1263 ADC2 analog-to-digital converter + +maintainers: + - Kurt Borja <kuurtb@gmail.com> + +description: + The ADS1263 includes an auxiliary, 24-bit, delta-sigma ADC (ADC2). ADC2 + operation is independent of ADC1, with independent selections of input + channel, reference voltage, sample rate, and channel gain. + +properties: + compatible: + enum: + - ti,ads1263-adc2 + vref-supply: + description: Optional external voltage reference. + + ti,refmux: + $ref: /schemas/types.yaml#/definitions/uint32 + description: | + Selects the positive voltage reference input: + 0: Internal 2.5 V reference + 1: AIN0-AIN1 pins + 2: AIN2-AIN3 pins + 3: AIN4-AIN5 pins + 4: AVDD-AVSS pins + minimum: 0 + maximum: 4 + default: 0 + + '#address-cells': + const: 1 + + '#size-cells': + const: 0 + + '#io-channel-cells': + const: 1 + +required: + - compatible + +additionalProperties: false diff --git a/MAINTAINERS b/MAINTAINERS index 396d4e76dccc..9379699d99c6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26668,6 +26668,13 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1018.yaml F: drivers/iio/adc/ti-ads1018.c +TI ADS1262 ADC DRIVER +M: Kurt Borja <kuurtb@gmail.com> +L: linux-iio@vger.kernel.org +S: Maintained +F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml +F: Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml + TI ADS7924 ADC DRIVER M: Hugo Villeneuve <hvilleneuve@dimonoff.com> L: linux-iio@vger.kernel.org -- 2.54.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family 2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja @ 2026-06-12 22:53 ` sashiko-bot 0 siblings, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-06-12 22:53 UTC (permalink / raw) To: Kurt Borja; +Cc: devicetree, robh, conor+dt Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [Medium] Missing required GPIO properties in schema and examples - [Medium] ADC2 child node is not conditionally restricted to the ads1263 compatible string - [Medium] Contradictory schema definition for ADC2 child nodes - [Medium] Incorrect schema property restriction blocking standard ADC properties -- commit 42c82a528b0caecd291df843ad4bb9b4822940a2 Author: Kurt Borja <kuurtb@gmail.com> dt-bindings: iio: adc: Add TI ADS126x ADC family This commit adds the devicetree bindings documentation for the TI ADS1262 and ADS1263 ADCs. It defines the main ADC schema, the properties for the ADC channels, and includes a separate schema for the ADS1263 auxiliary ADC2. > diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml > new file mode 100644 > index 0000000000000..1c4fde94f6c75 > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml > @@ -0,0 +1,308 @@ [ ... ] > + '#io-channel-cells': > + const: 1 > + > + '#gpio-cells': > + const: 2 > + > + gpio-controller: true > + > + adc: > + $ref: /schemas/iio/adc/ti,ads1263-adc2.yaml# [Severity: Medium] The auxiliary ADC2 is only available on the ADS1263 variant, but the schema allows the adc child node for all variants. Should an allOf block with an if-then condition be added here or below to forbid the adc node when the compatible string is ti,ads1262? > + > +required: > + - compatible > + - reg > + - avdd-supply > + - dvdd-supply > + - '#address-cells' > + - '#size-cells' [Severity: Medium] Since the binding provides GPIO controller capabilities, should the gpio-controller and #gpio-cells properties be included in this required list to ensure incomplete device tree nodes don't silently pass validation? > + > +unevaluatedProperties: false > + > +patternProperties: > + "^channel@[0-9]+$": > + $ref: /schemas/iio/adc/adc.yaml# > + additionalProperties: false [Severity: Medium] Does using additionalProperties: false here incorrectly prevent the use of standard shared ADC channel properties from the referenced adc.yaml schema? Should this be unevaluatedProperties: false instead to allow properties evaluated by the referenced schema? [ ... ] > +allOf: > + - $ref: /schemas/spi/spi-peripheral-props.yaml# > + > +examples: > + - | > + #include <dt-bindings/gpio/gpio.h> > + > + spi { > + #address-cells = <1>; > + #size-cells = <0>; > + > + adc@0 { > + compatible = "ti,ads1263"; > + reg = <0>; > + spi-max-frequency = <8000000>; > + spi-cpha; > + avdd-supply = <&avdd>; > + dvdd-supply = <&dvdd>; > + #address-cells = <1>; > + #size-cells = <0>; > + > + reset-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; [Severity: Medium] If gpio-controller and #gpio-cells are added to the required properties, do they also need to be demonstrated in this example node to pass the dt_binding_check validation? [ ... ] > diff --git a/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml > new file mode 100644 > index 0000000000000..9dd5577589b1c > --- /dev/null > +++ b/Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml > @@ -0,0 +1,49 @@ [ ... ] > + '#address-cells': > + const: 1 > + > + '#size-cells': > + const: 0 > + > + '#io-channel-cells': > + const: 1 > + > +required: > + - compatible > + > +additionalProperties: false [Severity: Medium] The #address-cells and #size-cells properties imply the node expects child nodes, but additionalProperties: false is specified globally and forbids them. Since the driver dynamically derives ADC2 channels from the parent node rather than parsing children from the ADC2 node, should the cell properties #address-cells and #size-cells be removed to avoid this contradiction? -- Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=1 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 2/5] iio: adc: Add ti-ads1262 driver 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja 2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja @ 2026-06-12 22:46 ` Kurt Borja 2026-06-12 23:01 ` sashiko-bot 2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja ` (3 subsequent siblings) 5 siblings, 1 reply; 14+ messages in thread From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw) To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio, Kurt Borja, Jonathan Cameron Add ti-ads1262 driver for TI ADS1262 and ADS1263 ADCs with initial support for the following features: - Power management - IIO direct and buffer modes - Channel hot-reloading - Internal or external oscillator - Internal or external voltage reference - Filter configuration - Sensor bias configuration - IDAC configuration - Level-shift voltage configuration - Auxiliary ADC interoperability considerations Signed-off-by: Kurt Borja <kuurtb@gmail.com> --- MAINTAINERS | 1 + drivers/iio/adc/Kconfig | 13 + drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads1262.c | 1816 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1831 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 9379699d99c6..b874add5c924 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26674,6 +26674,7 @@ L: linux-iio@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml F: Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml +F: drivers/iio/adc/ti-ads1262.c TI ADS7924 ADC DRIVER M: Hugo Villeneuve <hvilleneuve@dimonoff.com> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index a3a93a47b43d..b6c35d0c88ed 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1796,6 +1796,19 @@ config TI_ADS124S08 This driver can also be built as a module. If so, the module will be called ti-ads124s08. +config TI_ADS1262 + tristate "Texas Instruments ADS1262" + depends on SPI && GPIOLIB + select REGMAP + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1262 and + ADS1263 ADC chips. + + This driver can also be built as a module. If so, the module will be + called ti-ads1262. + config TI_ADS1298 tristate "Texas Instruments ADS1298" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index 707dd708912f..e0653820081e 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -154,6 +154,7 @@ obj-$(CONFIG_TI_ADS1018) += ti-ads1018.o obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o +obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c new file mode 100644 index 000000000000..fd1911cf65ac --- /dev/null +++ b/drivers/iio/adc/ti-ads1262.c @@ -0,0 +1,1816 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments ADS1262 ADC driver + * + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#include <linux/array_size.h> +#include <linux/align.h> +#include <linux/bitfield.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/clk.h> +#include <linux/completion.h> +#include <linux/compiler_attributes.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/gpio/consumer.h> +#include <linux/interrupt.h> +#include <linux/lockdep.h> +#include <linux/math.h> +#include <linux/math64.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/overflow.h> +#include <linux/property.h> +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/spi/spi.h> +#include <linux/time.h> +#include <linux/types.h> +#include <linux/units.h> + +#include <asm/byteorder.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +/* Commands */ +#define ADS1262_OPCODE_NOP 0x00 +#define ADS1262_OPCODE_RESET 0x06 +#define ADS1262_OPCODE_START1 0x08 +#define ADS1262_OPCODE_STOP1 0x0A +#define ADS1262_OPCODE_START2 0x0C +#define ADS1262_OPCODE_STOP2 0x0E +#define ADS1262_OPCODE_RDATA1 0x12 +#define ADS1262_OPCODE_RDATA2 0x14 +#define ADS1262_OPCODE_SYOCAL1 0x16 +#define ADS1262_OPCODE_SYGCAL1 0x17 +#define ADS1262_OPCODE_SFOCAL1 0x19 +#define ADS1262_OPCODE_SYOCAL2 0x1B +#define ADS1262_OPCODE_SYGCAL2 0x1C +#define ADS1262_OPCODE_SFOCAL2 0x1E +#define ADS1262_OPCODE_RREG 0x20 +#define ADS1262_OPCODE_WREG 0x40 + +/* Registers */ +#define ADS1262_ID_REG 0x00 +#define ADS1262_POWER_REG 0x01 +#define ADS1262_INTERFACE_REG 0x02 +#define ADS1262_MODE0_REG 0x03 +#define ADS1262_MODE1_REG 0x04 +#define ADS1262_MODE2_REG 0x05 +#define ADS1262_INPMUX_REG 0x06 +#define ADS1262_OFCAL0_REG 0x07 +#define ADS1262_OFCAL1_REG 0x08 +#define ADS1262_OFCAL2_REG 0x09 +#define ADS1262_FSCAL0_REG 0x0A +#define ADS1262_FSCAL1_REG 0x0B +#define ADS1262_FSCAL2_REG 0x0C +#define ADS1262_IDACMUX_REG 0x0D +#define ADS1262_IDACMAG_REG 0x0E +#define ADS1262_REFMUX_REG 0x0F +#define ADS1262_TDACP_REG 0x10 +#define ADS1262_TDACN_REG 0x11 +#define ADS1262_GPIOCON_REG 0x12 +#define ADS1262_GPIODIR_REG 0x13 +#define ADS1262_GPIODAT_REG 0x14 +#define ADS1262_ADC2CFG_REG 0x15 +#define ADS1262_ADC2MUX_REG 0x16 +#define ADS1262_ADC2OFC0_REG 0x17 +#define ADS1262_ADC2OFC1_REG 0x18 +#define ADS1262_ADC2FSC0_REG 0x19 +#define ADS1262_ADC2FSC1_REG 0x1A +#define ADS1262_REG_COUNT 0x1B + +/* ID fields */ +#define ADS1262_DEV_ID_MASK GENMASK(7, 5) +#define ADS1262_REV_ID_MASK GENMASK(4, 0) + +/* POWER fields */ +#define ADS1262_POWER_RESET_MASK BIT(4) +#define ADS1262_POWER_VBIAS_MASK BIT(1) +#define ADS1262_POWER_INTREF_MASK BIT(0) + +/* INTERFACE fields */ +#define ADS1262_INTERFACE_TIMEOUT_MASK BIT(3) +#define ADS1262_INTERFACE_STATUS_MASK BIT(2) +#define ADS1262_INTERFACE_CRC_MASK GENMASK(1, 0) + +/* MODE0 fields */ +#define ADS1262_MODE0_REFREV_MASK BIT(7) +#define ADS1262_MODE0_RUNMODE_MASK BIT(6) +#define ADS1262_MODE0_IDAC_ROT_MASK BIT(5) +#define ADS1262_MODE0_CHOP_MASK BIT(4) +#define ADS1262_MODE0_DELAY_MASK GENMASK(3, 0) + +/* MODE1 fields */ +#define ADS1262_MODE1_FILTER_MASK GENMASK(7, 5) +#define ADS1262_MODE1_SBADC_MASK BIT(4) +#define ADS1262_MODE1_SBPOL_MASK BIT(3) +#define ADS1262_MODE1_SBMAG_MASK GENMASK(2, 0) + +/* MODE2 fields */ +#define ADS1262_MODE2_BYPASS_MASK BIT(7) +#define ADS1262_MODE2_GAIN_MASK GENMASK(6, 4) +#define ADS1262_MODE2_DR_MASK GENMASK(3, 0) + +/* INPMUX fields */ +#define ADS1262_INPMUX_MUXP_MASK GENMASK(7, 4) +#define ADS1262_INPMUX_MUXN_MASK GENMASK(3, 0) + +/* IDACMUX fields */ +#define ADS1262_IDACMUX_MUX2_MASK GENMASK(7, 4) +#define ADS1262_IDACMUX_MUX1_MASK GENMASK(3, 0) + +/* IDACMAG fields */ +#define ADS1262_IDACMAG_MAG2_MASK GENMASK(7, 4) +#define ADS1262_IDACMAG_MAG1_MASK GENMASK(3, 0) + +/* REFMUX fields */ +#define ADS1262_REFMUX_RMUXP_MASK GENMASK(5, 3) +#define ADS1262_REFMUX_RMUXN_MASK GENMASK(2, 0) + +/* TDACP fields */ +#define ADS1262_TDACP_OUTP_MASK BIT(7) +#define ADS1262_TDACP_MAGP_MASK GENMASK(4, 0) + +/* TDACN fields */ +#define ADS1262_TDACN_OUTN_MASK BIT(7) +#define ADS1262_TDACN_MAGN_MASK GENMASK(4, 0) + +/* ADC2CFG fields */ +#define ADS1262_ADC2CFG_DR2_MASK GENMASK(7, 6) +#define ADS1262_ADC2CFG_REF2_MASK GENMASK(5, 3) +#define ADS1262_ADC2CFG_GAIN2_MASK GENMASK(2, 0) + +/* ADC2MUX fields */ +#define ADS1262_ADC2MUX_MUXP2_MASK GENMASK(7, 4) +#define ADS1262_ADC2MUX_MUXN2_MASK GENMASK(3, 0) + +/* ID DEV_ID constants */ +#define ADS1262_DEV_ID_ADS1262 0 +#define ADS1262_DEV_ID_ADS1263 1 + +/* MODE0 RUNMODE constants */ +#define ADS1262_RUNMODE_CONTINUOUS 0 +#define ADS1262_RUNMODE_PULSE 1 + +/* MODE1 FILTER constants */ +#define ADS1262_FILTER_SINC1 0 +#define ADS1262_FILTER_SINC2 1 +#define ADS1262_FILTER_SINC3 2 +#define ADS1262_FILTER_SINC4 3 +#define ADS1262_FILTER_FIR 4 + +/* MODE1 SENSOR BIAS constants */ +#define ADS1262_SBADC_COUNT 2 +#define ADS1262_SBPOL_COUNT 2 +#define ADS1262_SBMAG_COUNT 7 + +/* MODE2 DATA RATE constants */ +#define ADS1262_DR_2_5_SPS 0 +#define ADS1262_DR_5_SPS 1 +#define ADS1262_DR_10_SPS 2 +#define ADS1262_DR_16_6_SPS 3 +#define ADS1262_DR_20_SPS 4 +#define ADS1262_DR_50_SPS 5 +#define ADS1262_DR_60_SPS 6 +#define ADS1262_DR_100_SPS 7 +#define ADS1262_DR_400_SPS 8 +#define ADS1262_DR_1200_SPS 9 +#define ADS1262_DR_2400_SPS 10 +#define ADS1262_DR_4800_SPS 11 +#define ADS1262_DR_7200_SPS 12 +#define ADS1262_DR_14400_SPS 13 +#define ADS1262_DR_19200_SPS 14 +#define ADS1262_DR_38400_SPS 15 + +/* INPMUX constants */ +#define ADS1262_INPMUX_AINCOM 10 +#define ADS1262_INPMUX_TEMP 11 +#define ADS1262_INPMUX_AVDD 12 +#define ADS1262_INPMUX_DVDD 13 +#define ADS1262_INPMUX_TDAC 14 +#define ADS1262_INPMUX_FLOAT 15 + +/* IDACMUX constants */ +#define ADS1262_IDACMUX_NO_CONN 11 +#define ADS1262_IDACMUX_COUNT 12 + +/* IDACMAG constants */ +#define ADS1262_IDACMAG_OFF 0 +#define ADS1262_IDACMAG_COUNT 11 + +/* REFMUX constants */ +#define ADS1262_RMUX_INTER 0 +#define ADS1262_RMUX_AIN0_AIN1 1 +#define ADS1262_RMUX_AIN2_AIN3 2 +#define ADS1262_RMUX_AIN4_AIN5 3 +#define ADS1262_RMUX_AVDD_AVSS 4 +#define ADS1262_RMUX_COUNT 5 + +struct ads1262_channel { + /* MODE0 */ + u8 conv_delay:4; + u8 chop_mode:1; + u8 idac_rot_mode:1; + u8 runmode:1; + u8 rev_vref_pol:1; + + /* MODE1 */ + u8 sbias_magnitude:3; + u8 sbias_polarity:1; + u8 sbias_connection:1; + u8 filter:3; + + /* MODE2 */ + u8 data_rate:4; + u8 gain:3; + u8 pga_bypass:1; + + /* INPMUX */ + u8 negative_input:4; + u8 positive_input:4; +}; + +struct ads1262 { + struct spi_device *spi; + struct regmap *regmap; + struct iio_dev *indio_dev; + struct iio_trigger *trig; + struct gpio_desc *reset_gpiod; + struct gpio_desc *start_gpiod; + + void *scan_buffer; + size_t scan_sz; + + /* Protects channel state */ + struct mutex chan_lock; + u32 vref_uV; + unsigned int num_channels; + struct ads1262_channel *channels; + struct completion drdy; + struct spi_message msg; + struct spi_transfer xfer; + + /* Protects transfer buffers and concurrent SPI transfers */ + struct mutex xfer_lock; + + u8 tx[6] __aligned(IIO_DMA_MINALIGN); + union { + u8 rx[6]; + struct { + __be32 data; + } __packed shift_reg; + struct { + u8 dummy; + __be32 data; + } __packed holding_reg; + }; +}; + +static const int ads1262_data_rate_avail[][2] = { + [ADS1262_DR_2_5_SPS] = { 2, 500000 }, + [ADS1262_DR_5_SPS] = { 5, 0 }, + [ADS1262_DR_10_SPS] = { 10, 0 }, + [ADS1262_DR_16_6_SPS] = { 16, 666667 }, + [ADS1262_DR_20_SPS] = { 20, 0 }, + [ADS1262_DR_50_SPS] = { 50, 0 }, + [ADS1262_DR_60_SPS] = { 60, 0 }, + [ADS1262_DR_100_SPS] = { 100, 0 }, + [ADS1262_DR_400_SPS] = { 400, 0 }, + [ADS1262_DR_1200_SPS] = { 1200, 0 }, + [ADS1262_DR_2400_SPS] = { 2400, 0 }, + [ADS1262_DR_4800_SPS] = { 4800, 0 }, + [ADS1262_DR_7200_SPS] = { 7200, 0 }, + [ADS1262_DR_14400_SPS] = { 14400, 0 }, + [ADS1262_DR_19200_SPS] = { 19200, 0 }, + [ADS1262_DR_38400_SPS] = { 38400, 0 }, +}; + +static const int ads1262_conv_delay_avail[][2] = { + { 0, 0 }, + { 0, 8700 }, + { 0, 17000 }, + { 0, 35000 }, + { 0, 69000 }, + { 0, 139000 }, + { 0, 278000 }, + { 0, 555000 }, + { 0, 1100000 }, + { 0, 2200000 }, + { 0, 4400000 }, + { 0, 8800000 }, +}; + +static const int ads1262_pga_gain_avail[] = { + 1, 2, 4, 8, 16, 32 +}; + +static int ads1262_dev_power_on(struct ads1262 *st) +{ + int ret; + + ret = gpiod_set_value_cansleep(st->reset_gpiod, 0); + if (ret) + return ret; + + fsleep(9 * USEC_PER_MSEC); + + return 0; +} + +static int ads1262_dev_power_off(struct ads1262 *st) +{ + int ret; + + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1); + if (ret) + return ret; + + fsleep(9 * USEC_PER_MSEC); + + return 0; +} + +static int ads1262_dev_cmd(struct ads1262 *st, u8 opcode) +{ + guard(mutex)(&st->xfer_lock); + + st->tx[0] = opcode; + + return spi_write(st->spi, &st->tx[0], sizeof(st->tx[0])); +} + +static int ads1262_dev_read_data_command(struct ads1262 *st, u8 cmd, + __be32 *val) +{ + int ret; + + guard(mutex)(&st->xfer_lock); + + memset(st->tx, 0, sizeof(st->tx)); + st->tx[0] = cmd; + + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + *val = st->holding_reg.data; + + return 0; +} + +static int ads1262_dev_read_data_direct(struct ads1262 *st, __be32 *val) +{ + int ret; + + /* + * If reading data from the shift register, we should already be holding + * the xfer_lock because all SPI activity is forbidden between the START + * command and the actual data retrieval. + */ + lockdep_assert_held(&st->xfer_lock); + + memset(st->tx, 0, sizeof(st->tx)); + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + *val = st->shift_reg.data; + + return 0; +} + +static int ads1262_dev_reset(struct ads1262 *st) +{ + int ret; + + if (st->reset_gpiod) + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1); + else + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET); + if (ret) + return ret; + + fsleep(1); + + if (st->reset_gpiod) { + ret = gpiod_set_value_cansleep(st->reset_gpiod, 0); + fsleep(1); + } + + return ret; +} + +static int ads1262_dev_start(struct ads1262 *st) +{ + int ret; + + if (st->start_gpiod) + ret = gpiod_set_value_cansleep(st->start_gpiod, 1); + else + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_START1); + + return ret; +} + +static int ads1262_dev_stop(struct ads1262 *st) +{ + int ret; + + if (st->start_gpiod) + ret = gpiod_set_value_cansleep(st->start_gpiod, 0); + else + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_STOP1); + + return ret; +} + +static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode) +{ + int ret; + + ret = ads1262_dev_start(st); + if (ret) + return ret; + + if (runmode == ADS1262_RUNMODE_CONTINUOUS) + return ads1262_dev_stop(st); + + return 0; +} + +static void ads1262_wait_for_conversion(struct ads1262 *st) +{ + reinit_completion(&st->drdy); + + /* + * The first conversion latency is affected by the channel's data rate, + * filter, the configurable conversion delay and whether chop mode + * and/or IDAC rotation mode are enabled. + * + * The worst possible latency is calculated by taking the lowest data + * rate (2.5 SPS) and the sinc4 filter. This gives a latency of 1600 ms + * (Table 9-13). Then add the slowest configurable conversion delay + * (9 ms) and multiply by 4 to account for chop and IDAC rotation modes + * (Equation 20). + * + * Final result is 4 * (1600 ms + 9 ms) = 6436. + */ + wait_for_completion_timeout(&st->drdy, msecs_to_jiffies(6436)); +} + +static void ads1262_channel_set_runmode(struct ads1262 *st, + struct ads1262_channel *chan, + u8 runmode) +{ + guard(mutex)(&st->chan_lock); + chan->runmode = runmode; +} + +static int ads1262_channel_enable_and_read(struct ads1262 *st, + struct ads1262_channel *chan, + __be32 *val) +{ + int ret; + + lockdep_assert_held(&st->xfer_lock); + + /* + * Prepare the transfer buffer to do bulk register write + * (Section 9.5.6). + */ + st->tx[0] = ADS1262_MODE0_REG | ADS1262_OPCODE_WREG; + st->tx[1] = sizeof(*chan) - 1; + + /* + * The ads1262_channel struct can be written directly to the chip's + * configuration registers (MODE0, MODE1, MODE2, INPMUX) in a single + * transfer, so it's necessary to assert it's size (4 bytes). + */ + static_assert(sizeof(*chan) == 4); + mutex_lock(&st->chan_lock); + memcpy(&st->tx[2], chan, sizeof(*chan)); + mutex_unlock(&st->chan_lock); + + ret = spi_sync(st->spi, &st->msg); + if (ret) + return ret; + + /* + * If new data is ready, it's shifted out on the same transfer. + * (Section 9.4.7.1) + */ + if (val) + *val = st->shift_reg.data; + + return 0; +} + +static int ads1262_channel_enable(struct ads1262 *st, + struct ads1262_channel *chan) +{ + guard(mutex)(&st->xfer_lock); + + return ads1262_channel_enable_and_read(st, chan, NULL); +} + +static int ads1262_channel_hot_reload(struct ads1262 *st, + const struct iio_chan_spec *chan) +{ + unsigned int weight; + unsigned long i; + int ret; + + /* + * Hot reloading is only required on buffer mode and if only one channel + * is enabled. + */ + if (!iio_device_try_claim_buffer_mode(st->indio_dev)) + return 0; + + weight = bitmap_weight(st->indio_dev->active_scan_mask, + iio_get_masklength(st->indio_dev)); + if (weight != 1) { + iio_device_release_direct(st->indio_dev); + return 0; + } + + i = find_first_bit(st->indio_dev->active_scan_mask, + iio_get_masklength(st->indio_dev)); + if (i != chan->scan_index) { + iio_device_release_direct(st->indio_dev); + return 0; + } + + /* + * The device automatically hot reloads the channel after writing to + * the configuration registers. + */ + ret = ads1262_channel_enable(st, &st->channels[chan->scan_index]); + + iio_device_release_direct(st->indio_dev); + + return ret; +} + +static int ads1262_channel_read(struct ads1262 *st, + struct ads1262_channel *chan_data, + __be32 *val) +{ + struct device *dev = &st->spi->dev; + u8 runmode; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + IIO_DEV_ACQUIRE_DIRECT_MODE(st->indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + /* + * When a channel has chop mode or IDAC rotation mode, the first + * conversion is always withheld so the datasheet suggests using the + * CONTINUOUS mode and briefly starting and stopping conversions to + * achieve the same effect (Section 9.4.1.2). + */ + if (chan_data->chop_mode || chan_data->idac_rot_mode) + runmode = ADS1262_RUNMODE_CONTINUOUS; + else + runmode = ADS1262_RUNMODE_PULSE; + + ads1262_channel_set_runmode(st, chan_data, runmode); + + ret = ads1262_channel_enable(st, chan_data); + if (ret) + return ret; + + ret = ads1262_dev_start_one(st, runmode); + if (ret) + return ret; + + ads1262_wait_for_conversion(st); + + return ads1262_dev_read_data_command(st, ADS1262_OPCODE_RDATA1, val); +} + +static int ads1262_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int *val, + int *val2, long mask) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct ads1262_channel *chan_data; + u8 mode, realbits; + __be32 raw; + u32 cnv; + int ret; + + chan_data = &st->channels[chan->scan_index]; + realbits = chan->scan_type.realbits; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads1262_channel_read(st, chan_data, &raw); + if (ret) + return ret; + + cnv = be32_to_cpu(raw); + *val = sign_extend32(cnv, realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + u64 divd, divr, tmp, rem; + + mutex_lock(&st->chan_lock); + divd = st->vref_uV; + divr = BIT_ULL(chan_data->gain + realbits - 1) * 1000; + mutex_unlock(&st->chan_lock); + + tmp = div64_u64(divd * 1000000000ULL, divr); + *val = div64_u64_rem(tmp, 1000000000ULL, &rem); + *val2 = rem; + + return IIO_VAL_INT_PLUS_NANO; + + case IIO_CHAN_INFO_HARDWAREGAIN: + mutex_lock(&st->chan_lock); + *val = ads1262_pga_gain_avail[chan_data->gain]; + mutex_unlock(&st->chan_lock); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&st->chan_lock); + mode = chan_data->data_rate; + mutex_unlock(&st->chan_lock); + + *val = ads1262_data_rate_avail[mode][0]; + *val2 = ads1262_data_rate_avail[mode][1]; + return IIO_VAL_INT_PLUS_MICRO; + + case IIO_CHAN_INFO_CONVDELAY: + mutex_lock(&st->chan_lock); + mode = chan_data->conv_delay; + mutex_unlock(&st->chan_lock); + + *val = ads1262_conv_delay_avail[mode][0]; + *val2 = ads1262_conv_delay_avail[mode][1]; + return IIO_VAL_INT_PLUS_NANO; + + default: + return -EOPNOTSUPP; + } +} + +static int ads1262_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT_PLUS_MICRO; + *vals = (const int *)ads1262_data_rate_avail; + *length = ARRAY_SIZE(ads1262_data_rate_avail) * 2; + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_HARDWAREGAIN: + *type = IIO_VAL_INT; + *vals = ads1262_pga_gain_avail; + *length = ARRAY_SIZE(ads1262_pga_gain_avail); + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_CONVDELAY: + *type = IIO_VAL_INT_PLUS_NANO; + *vals = (const int *)ads1262_conv_delay_avail; + *length = ARRAY_SIZE(ads1262_conv_delay_avail) * 2; + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int ads1262_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, int val, + int val2, long mask) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct ads1262_channel *chan_data; + unsigned int i; + + chan_data = &st->channels[chan->scan_index]; + + switch (mask) { + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(ads1262_data_rate_avail); i++) { + if (val == ads1262_data_rate_avail[i][0] && + val2 == ads1262_data_rate_avail[i][1]) + break; + } + if (i == ARRAY_SIZE(ads1262_data_rate_avail)) + return -EINVAL; + + mutex_lock(&st->chan_lock); + chan_data->data_rate = i; + mutex_unlock(&st->chan_lock); + + break; + + case IIO_CHAN_INFO_HARDWAREGAIN: + for (i = 0; i < ARRAY_SIZE(ads1262_pga_gain_avail); i++) { + if (val == ads1262_pga_gain_avail[i]) + break; + } + if (i == ARRAY_SIZE(ads1262_pga_gain_avail)) + return -EINVAL; + + mutex_lock(&st->chan_lock); + chan_data->gain = i; + mutex_unlock(&st->chan_lock); + + break; + + case IIO_CHAN_INFO_CONVDELAY: + for (i = 0; i < ARRAY_SIZE(ads1262_conv_delay_avail); i++) { + if (val == ads1262_conv_delay_avail[i][0] && + val2 == ads1262_conv_delay_avail[i][1]) + break; + } + if (i == ARRAY_SIZE(ads1262_conv_delay_avail)) + return -EINVAL; + + mutex_lock(&st->chan_lock); + chan_data->conv_delay = i; + mutex_unlock(&st->chan_lock); + + break; + + default: + return -EOPNOTSUPP; + } + + return ads1262_channel_hot_reload(st, chan); +} + +static int ads1262_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CONVDELAY: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static int ads1262_debugfs_reg_access(struct iio_dev *indio_dev, unsigned int reg, + unsigned int writeval, unsigned int *readval) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + if (readval) + return regmap_read_bypassed(st->regmap, reg, readval); + + return regmap_write(st->regmap, reg, writeval); +} + +static const struct iio_info ads1262_iio_info = { + .read_raw = ads1262_read_raw, + .read_avail = ads1262_read_avail, + .write_raw = ads1262_write_raw, + .write_raw_get_fmt = ads1262_write_raw_get_fmt, + .debugfs_reg_access = ads1262_debugfs_reg_access, +}; + +static int ads1262_get_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct ads1262_channel *chan_data; + + guard(mutex)(&st->chan_lock); + + chan_data = &st->channels[chan->scan_index]; + return chan_data->filter; +} + +static int ads1262_set_filter_type(struct iio_dev *indio_dev, + const struct iio_chan_spec *chan, + unsigned int val) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct ads1262_channel *chan_data; + + /* Can't guard() the lock here to avoid deadlock when hot reloading */ + mutex_lock(&st->chan_lock); + chan_data = &st->channels[chan->scan_index]; + chan_data->filter = val; + mutex_unlock(&st->chan_lock); + + return ads1262_channel_hot_reload(st, chan); +} + +static const char * const ads1262_filter_type_labels[] = { + [ADS1262_FILTER_SINC1] = "sinc1", + [ADS1262_FILTER_SINC2] = "sinc2", + [ADS1262_FILTER_SINC3] = "sinc3", + [ADS1262_FILTER_SINC4] = "sinc4", + [ADS1262_FILTER_FIR] = "fir", +}; + +static const struct iio_enum ads1262_filter_type_enum = { + .items = ads1262_filter_type_labels, + .num_items = ARRAY_SIZE(ads1262_filter_type_labels), + .get = ads1262_get_filter_type, + .set = ads1262_set_filter_type, +}; + +static const struct iio_chan_spec_ext_info ads1262_ext_info[] = { + IIO_ENUM("filter_type", IIO_SEPARATE, &ads1262_filter_type_enum), + IIO_ENUM_AVAILABLE("filter_type", IIO_SHARED_BY_TYPE, + &ads1262_filter_type_enum), + { } +}; + +static const struct iio_chan_spec ads1262_iio_voltage_template = { + .type = IIO_VOLTAGE, + .indexed = true, + .scan_type = { + .format = IIO_SCAN_FORMAT_SIGNED_INT, + .realbits = 32, + .storagebits = 32, + .endianness = IIO_BE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_CONVDELAY), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SAMP_FREQ) | + BIT(IIO_CHAN_INFO_CONVDELAY), + .ext_info = ads1262_ext_info, +}; + +static irqreturn_t ads1262_irq_handler(int irq, void *dev_id) +{ + struct ads1262 *st = dev_id; + + if (iio_buffer_enabled(st->indio_dev)) + iio_trigger_poll(st->trig); + + complete(&st->drdy); + + return IRQ_HANDLED; +} + +static int ads1262_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + unsigned int weight; + unsigned long i; + int ret; + + weight = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + st->scan_sz = ALIGN(sizeof(__be32) * weight, sizeof(s64)); + st->scan_sz += sizeof(s64); + st->scan_buffer = kzalloc(st->scan_sz, GFP_KERNEL); + if (!st->scan_buffer) + return -ENOMEM; + + ret = spi_optimize_message(st->spi, &st->msg); + if (ret) + goto out_state_cleanup; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + goto out_unoptimize_message; + + iio_for_each_active_channel(indio_dev, i) + ads1262_channel_set_runmode(st, &st->channels[i], + ADS1262_RUNMODE_CONTINUOUS); + + if (weight == 1) { + i = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + ret = ads1262_channel_enable(st, &st->channels[i]); + if (ret) + goto out_runtime_autosuspend; + } + + ret = ads1262_dev_start(st); + if (ret) + goto out_runtime_autosuspend; + + return 0; + +out_runtime_autosuspend: + pm_runtime_put_autosuspend(dev); + +out_unoptimize_message: + spi_unoptimize_message(&st->msg); + +out_state_cleanup: + kfree(st->scan_buffer); + st->scan_buffer = NULL; + st->scan_sz = 0; + + return ret; +} + +static int ads1262_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + + ads1262_dev_stop(st); + pm_runtime_put_autosuspend(dev); + spi_unoptimize_message(&st->msg); + kfree(st->scan_buffer); + st->scan_buffer = NULL; + st->scan_sz = 0; + + return 0; +} + +static bool ads1262_validate_scan_mask(struct iio_dev *indio_dev, + const unsigned long *scan_mask) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct device *dev = &st->spi->dev; + + if (iio_trigger_using_own(indio_dev)) { + dev_err(dev, "The %s trigger only supports one active channel\n", + st->trig->name); + return iio_validate_scan_mask_onehot(indio_dev, scan_mask); + } + + return true; +} + +static const struct iio_buffer_setup_ops ads1262_buffer_ops = { + .preenable = ads1262_buffer_preenable, + .postdisable = ads1262_buffer_postdisable, + .validate_scan_mask = ads1262_validate_scan_mask, +}; + +static int ads1262_fill_buffer_one(struct ads1262 *st) +{ + __be32 *scan_buffer = st->scan_buffer; + + /* + * When only one channel is enabled, we can't really avoid SPI activity + * from happening when the auxiliary ADC is in use, thus we have to read + * from the data-holding register (Section 9.4.7.2). + */ + return ads1262_dev_read_data_command(st, ADS1262_OPCODE_RDATA1, + scan_buffer); +} + +static int ads1262_fill_buffer_mult(struct ads1262 *st) +{ + __be32 val, *scan_buffer = st->scan_buffer; + unsigned int chan; + int i = -1; + int ret; + + /* + * This routine enables and reads channels in a full-duplex fashion. + * + * When a channel is enabled, the previous conversion is clocked out of + * the shift data register on the same transfer (Section 9.4.7.1). This + * allows for low latency software sequencing but forbids any SPI + * activity happen in between or data corruption may occur, hence the + * need to take the xfer_lock for the whole operation. + */ + + guard(mutex)(&st->xfer_lock); + + iio_for_each_active_channel(st->indio_dev, chan) { + ret = ads1262_channel_enable_and_read(st, &st->channels[chan], + &val); + if (ret) + return ret; + + if (i > -1) + scan_buffer[i] = val; + i++; + + ads1262_wait_for_conversion(st); + } + + return ads1262_dev_read_data_direct(st, &scan_buffer[i]); +} + +static irqreturn_t ads1262_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1262 *st = iio_priv(indio_dev); + s64 ts = pf->timestamp; + unsigned int weight; + int ret; + + weight = bitmap_weight(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + + memset(st->scan_buffer, 0, st->scan_sz); + + if (weight == 1) + ret = ads1262_fill_buffer_one(st); + else + ret = ads1262_fill_buffer_mult(st); + if (ret) + goto out_notify_done; + + iio_push_to_buffers_with_ts(indio_dev, st->scan_buffer, + st->scan_sz, ts); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1262_parse_channel_data(struct ads1262 *st, + struct ads1262_channel *chan_data, + struct fwnode_handle *node) +{ + struct device *dev = &st->spi->dev; + const char *propname; + u32 val; + int ret; + + if (fwnode_property_present(node, "ti,pga-bypass")) + chan_data->pga_bypass = true; + + if (fwnode_property_present(node, "ti,rev-vref-pol")) + chan_data->rev_vref_pol = true; + + if (fwnode_property_present(node, "ti,chop-mode")) + chan_data->chop_mode = true; + + if (fwnode_property_present(node, "ti,idac-rotation-mode")) + chan_data->idac_rot_mode = true; + + propname = "ti,sbias-connection"; + if (fwnode_property_present(node, propname)) { + ret = fwnode_property_read_u32(node, propname, &val); + if (ret) + goto err_property_read; + if (val >= ADS1262_SBADC_COUNT) { + ret = -ERANGE; + goto err_property_read; + } + + chan_data->sbias_connection = val; + } + + propname = "ti,sbias-polarity"; + if (fwnode_property_present(node, propname)) { + ret = fwnode_property_read_u32(node, propname, &val); + if (ret) + goto err_property_read; + if (val >= ADS1262_SBPOL_COUNT) { + ret = -ERANGE; + goto err_property_read; + } + + chan_data->sbias_polarity = val; + } + + propname = "ti,sbias-magnitude"; + if (fwnode_property_present(node, propname)) { + ret = fwnode_property_read_u32(node, propname, &val); + if (ret) + goto err_property_read; + if (val >= ADS1262_SBMAG_COUNT) { + ret = -ERANGE; + goto err_property_read; + } + + chan_data->sbias_magnitude = val; + } + + return 0; + +err_property_read: + return dev_err_probe(dev, ret, "%s: Failed to read property %s\n", + fwnode_get_name(node), propname); +} + +static int ads1262_parse_channel_node(struct ads1262 *st, + struct iio_chan_spec *chan, + struct ads1262_channel *chan_data, + struct fwnode_handle *node) +{ + struct device *dev = &st->spi->dev; + const char *propname; + u32 pins[2]; + int ret; + + propname = "diff-channels"; + ret = fwnode_property_read_u32_array(node, propname, pins, + ARRAY_SIZE(pins)); + if (ret) + return dev_err_probe(dev, ret, "%s: Failed to read %s\n", + fwnode_get_name(node), propname); + + if (pins[0] > ADS1262_INPMUX_FLOAT) + return dev_err_probe(dev, -ENXIO, + "%s: positive input %u not in range\n", + fwnode_get_name(node), pins[0]); + + if (pins[1] > ADS1262_INPMUX_FLOAT) + return dev_err_probe(dev, -ENXIO, + "%s: negative input %u not in range\n", + fwnode_get_name(node), pins[1]); + + chan->channel = pins[0]; + chan->channel2 = pins[1]; + chan->differential = true; + + chan_data->positive_input = pins[0]; + chan_data->negative_input = pins[1]; + + /* Only non-zero default values are data rate and filter */ + chan_data->data_rate = ADS1262_DR_20_SPS; + chan_data->filter = ADS1262_FILTER_FIR; + + return ads1262_parse_channel_data(st, chan_data, node); +} + +static int ads1262_parse_channels(struct iio_dev *indio_dev) +{ + struct ads1262 *st = iio_priv(indio_dev); + struct iio_chan_spec *channels, *chan; + struct device *dev = &st->spi->dev; + struct ads1262_channel *chan_data; + unsigned int num_channels; + u32 ch_reg; + int ret; + + num_channels = device_get_named_child_node_count(dev, "channel"); + if (!num_channels) + return dev_err_probe(dev, -ENXIO, + "No 'channel' nodes configured\n"); + + st->num_channels = num_channels; + st->channels = devm_kcalloc(dev, num_channels, sizeof(*st->channels), + GFP_KERNEL); + if (!st->channels) + return -ENOMEM; + + /* Account for the timestamp channel */ + num_channels++; + channels = devm_kcalloc(dev, num_channels, sizeof(*channels), + GFP_KERNEL); + if (!channels) + return -ENOMEM; + + chan = channels; + device_for_each_named_child_node_scoped(dev, node, "channel") { + ret = fwnode_property_read_u32(node, "reg", &ch_reg); + if (ret) + return dev_err_probe(dev, ret, + "%s: Failed to read channel reg\n", + fwnode_get_name(node)); + + /* Last channel is reserved for timestamp */ + if (ch_reg >= num_channels - 1) + return dev_err_probe(dev, -EINVAL, + "%s: reg %u out of range\n", + fwnode_get_name(node), ch_reg); + + *chan = ads1262_iio_voltage_template; + chan->scan_index = ch_reg; + chan_data = &st->channels[ch_reg]; + ret = ads1262_parse_channel_node(st, chan++, chan_data, node); + if (ret) + return ret; + } + + *chan = (struct iio_chan_spec)IIO_CHAN_SOFT_TIMESTAMP(num_channels - 1); + + indio_dev->num_channels = num_channels; + indio_dev->channels = channels; + + return 0; +} + +static int ads1262_read_chip_name(struct ads1262 *st, char **name) +{ + struct device *dev = &st->spi->dev; + u8 dev_id; + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADS1262_ID_REG, &val); + if (ret) + return ret; + + dev_id = FIELD_GET(ADS1262_DEV_ID_MASK, val); + + switch (dev_id) { + case ADS1262_DEV_ID_ADS1262: + *name = "ads1262"; + break; + case ADS1262_DEV_ID_ADS1263: + *name = "ads1263"; + break; + default: + *name = "ads1262"; + dev_dbg(dev, "Failed to identify device with ID 0x%x\n", val); + } + + return 0; +} + +static int ads1262_parse_idac_pins(struct ads1262 *st, u32 *pins, + unsigned int num_pins) +{ + struct device *dev = &st->spi->dev; + char propname[10]; + u32 val; + int ret; + + for (unsigned int i = 0; i < num_pins; i++) { + scnprintf(propname, sizeof(propname), "idac%d-pin", i + 1); + if (!device_property_present(dev, propname)) { + pins[i] = ADS1262_IDACMUX_NO_CONN; + continue; + } + + ret = device_property_read_u32(dev, propname, &val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read property %s\n", + propname); + if (val < ADS1262_IDACMUX_COUNT) + return dev_err_probe(dev, -EINVAL, + "%s: Pin number out of range %d\n", + propname, val); + + pins[i] = val; + } + + return 0; +} + +static int ads1262_parse_idac_mags(struct ads1262 *st, u32 *mags, + unsigned int num_mags) +{ + static const u32 idac_microamps[] = { + 0, 50, 100, 250, 500, 750, 1000, 1500, 2000, 2500, 3000 + }; + struct device *dev = &st->spi->dev; + unsigned int mode; + char propname[15]; + u32 val; + int ret; + + for (unsigned int i = 0; i < num_mags; i++) { + scnprintf(propname, sizeof(propname), "idac%d-microamp", i + 1); + if (!device_property_present(dev, propname)) { + mags[i] = ADS1262_IDACMAG_OFF; + continue; + } + + ret = device_property_read_u32(dev, propname, &val); + if (ret) + return dev_err_probe(dev, ret, + "Failed to read property %s\n", + propname); + + for (mode = 0; mode < ARRAY_SIZE(idac_microamps); i++) { + if (val == idac_microamps[mode]) + break; + } + if (mode == ARRAY_SIZE(idac_microamps)) + return dev_err_probe(dev, -EINVAL, + "%s: Invalid value %d\n", + propname, val); + + mags[i] = mode; + } + + return 0; +} + +static int ads1262_dev_configure(struct ads1262 *st) +{ + struct device *dev = &st->spi->dev; + u32 idac_pins[2], idac_mags[2]; + u8 val; + int ret; + + ret = ads1262_dev_reset(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to reset device\n"); + + ret = regmap_update_bits(st->regmap, ADS1262_INTERFACE_REG, + ADS1262_INTERFACE_STATUS_MASK | + ADS1262_INTERFACE_CRC_MASK, 0); + if (ret) + return ret; + + if (device_property_present(dev, "ti,vbias")) { + ret = regmap_update_bits(st->regmap, ADS1262_POWER_REG, + ADS1262_POWER_VBIAS_MASK, + ADS1262_POWER_VBIAS_MASK); + if (ret) + return ret; + } + + ret = ads1262_parse_idac_pins(st, idac_pins, ARRAY_SIZE(idac_pins)); + if (ret) + return ret; + + ret = ads1262_parse_idac_mags(st, idac_mags, ARRAY_SIZE(idac_mags)); + if (ret) + return ret; + + val = FIELD_PREP(ADS1262_IDACMUX_MUX1_MASK, idac_pins[0]); + val |= FIELD_PREP(ADS1262_IDACMUX_MUX2_MASK, idac_pins[1]); + + ret = regmap_update_bits(st->regmap, ADS1262_IDACMUX_REG, + ADS1262_IDACMUX_MUX1_MASK | + ADS1262_IDACMUX_MUX2_MASK, val); + if (ret) + return ret; + + val = FIELD_PREP(ADS1262_IDACMAG_MAG1_MASK, idac_mags[0]); + val |= FIELD_PREP(ADS1262_IDACMAG_MAG2_MASK, idac_mags[1]); + + return regmap_update_bits(st->regmap, ADS1262_IDACMAG_REG, + ADS1262_IDACMAG_MAG1_MASK | + ADS1262_IDACMAG_MAG2_MASK, val); +} + +static int ads1262_gpio_setup(struct ads1262 *st) +{ + struct device *dev = &st->spi->dev; + struct gpio_desc *gpiod; + const char *con_id; + + con_id = "start"; + gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return dev_err_probe(dev, PTR_ERR(gpiod), + "Failed to get %s GPIO\n", con_id); + st->start_gpiod = gpiod; + + con_id = "reset"; + gpiod = devm_gpiod_get_optional(dev, con_id, GPIOD_OUT_HIGH); + if (IS_ERR(gpiod)) + return dev_err_probe(dev, PTR_ERR(gpiod), + "Failed to get %s GPIO\n", con_id); + st->reset_gpiod = gpiod; + + return 0; +} + +static int ads1262_clk_setup(struct ads1262 *st) +{ + struct device *dev = &st->spi->dev; + struct clk *clk; + int ret; + + clk = devm_clk_get_optional_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to get external clock\n"); + + /* + * The nominal clock frequency as indicated by the datasheet is + * 7372800. + */ + ret = clk_set_rate(clk, 7372800); + if (ret) + return dev_err_probe(dev, PTR_ERR(clk), + "Failed to set the nominal clock frequency.\n"); + + return 0; +} + +static int ads1262_regulator_setup(struct ads1262 *st) +{ + struct device *dev = &st->spi->dev; + const char *reg_id, *prop; + u32 mux[2] = {}; + int val, ret; + + reg_id = "dvdd"; + ret = devm_regulator_get_enable(dev, reg_id); + if (ret) + goto err_regulator_get; + + reg_id = "avdd"; + ret = devm_regulator_get_enable(dev, reg_id); + if (ret) + goto err_regulator_get; + + prop = "ti,neg-refmux"; + device_property_read_u32(dev, prop, &mux[0]); + if (mux[0] >= ADS1262_RMUX_COUNT) + return dev_err_probe(dev, -ENXIO, " %s out of range\n", prop); + + prop = "ti,pos-refmux"; + device_property_read_u32(dev, prop, &mux[1]); + if (mux[1] >= ADS1262_RMUX_COUNT) + return dev_err_probe(dev, -ENXIO, " %s out of range\n", prop); + + if (mux[0] == ADS1262_RMUX_INTER && mux[1] == ADS1262_RMUX_INTER) { + /* The internal voltage reference is 2.5 V */ + st->vref_uV = 2500000; + return 0; + } + + val = FIELD_PREP(ADS1262_REFMUX_RMUXN_MASK, mux[0]); + val |= FIELD_PREP(ADS1262_REFMUX_RMUXP_MASK, mux[1]); + ret = regmap_update_bits(st->regmap, ADS1262_REFMUX_REG, + ADS1262_REFMUX_RMUXN_MASK | + ADS1262_REFMUX_RMUXP_MASK, val); + if (ret) + return ret; + + reg_id = "vref"; + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, reg_id); + if (st->vref_uV < 0) + goto err_regulator_get; + + return 0; + +err_regulator_get: + return dev_err_probe(dev, ret, "Failed to get regulator %s\n", reg_id); +} + +static int ads1262_spi_message_setup(struct ads1262 *st) +{ + st->xfer.tx_buf = st->tx; + st->xfer.rx_buf = st->rx; + st->xfer.len = sizeof(st->tx); + spi_message_init_with_transfers(&st->msg, &st->xfer, 1); + + return 0; +} + +static const struct regmap_range ads1262_read_write_range[] = { + regmap_reg_range(ADS1262_ID_REG, ADS1262_ADC2FSC1_REG), +}; + +static const struct regmap_range ads1262_read_only_range[] = { + regmap_reg_range(ADS1262_ID_REG, ADS1262_ID_REG), +}; + +static const struct regmap_range ads1262_volatile_range[] = { + /* + * The channel configuration registers (MODE0, MODE1, MODE2, INPMUX) are + * not actually volatile. However, we bypass the regmap API when + * writing to these registers for optimization reasons. + */ + regmap_reg_range(ADS1262_MODE0_REG, ADS1262_INPMUX_REG), + regmap_reg_range(ADS1262_GPIODAT_REG, ADS1262_GPIODAT_REG), +}; + +static const struct regmap_access_table ads1262_wr_table = { + .yes_ranges = ads1262_read_write_range, + .n_yes_ranges = ARRAY_SIZE(ads1262_read_write_range), + .no_ranges = ads1262_read_only_range, + .n_no_ranges = ARRAY_SIZE(ads1262_read_only_range), +}; + +static const struct regmap_access_table ads1262_rd_table = { + .yes_ranges = ads1262_read_write_range, + .n_yes_ranges = ARRAY_SIZE(ads1262_read_write_range), +}; + +static const struct regmap_access_table ads1262_volatile_table = { + .yes_ranges = ads1262_volatile_range, + .n_yes_ranges = ARRAY_SIZE(ads1262_volatile_range), +}; + +static const struct reg_default ads1262_reg_defaults[] = { + { ADS1262_POWER_REG, 0x11 }, + { ADS1262_INTERFACE_REG, 0x05 }, + { ADS1262_MODE0_REG, 0x00 }, + { ADS1262_MODE1_REG, 0x80 }, + { ADS1262_MODE2_REG, 0x04 }, + { ADS1262_INPMUX_REG, 0x01 }, + { ADS1262_OFCAL0_REG, 0x00 }, + { ADS1262_OFCAL1_REG, 0x00 }, + { ADS1262_OFCAL2_REG, 0x00 }, + { ADS1262_FSCAL0_REG, 0x00 }, + { ADS1262_FSCAL1_REG, 0x00 }, + { ADS1262_FSCAL2_REG, 0x40 }, + { ADS1262_IDACMUX_REG, 0xBB }, + { ADS1262_IDACMAG_REG, 0x00 }, + { ADS1262_REFMUX_REG, 0x00 }, + { ADS1262_TDACP_REG, 0x00 }, + { ADS1262_TDACN_REG, 0x00 }, + { ADS1262_GPIOCON_REG, 0x00 }, + { ADS1262_GPIODIR_REG, 0x00 }, + { ADS1262_GPIODAT_REG, 0x00 }, + { ADS1262_ADC2CFG_REG, 0x00 }, + { ADS1262_ADC2MUX_REG, 0x01 }, + { ADS1262_ADC2OFC0_REG, 0x00 }, + { ADS1262_ADC2OFC1_REG, 0x00 }, + { ADS1262_ADC2FSC0_REG, 0x00 }, + { ADS1262_ADC2FSC1_REG, 0x40 }, +}; + +static const struct regmap_config ads1262_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .wr_table = &ads1262_wr_table, + .rd_table = &ads1262_rd_table, + .volatile_table = &ads1262_volatile_table, + .reg_defaults = ads1262_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ads1262_reg_defaults), + .max_register = ADS1262_ADC2FSC1_REG, + .read_flag_mask = ADS1262_OPCODE_RREG, + .write_flag_mask = ADS1262_OPCODE_WREG, + .can_sleep = true, + .cache_type = REGCACHE_MAPLE, +}; + +static int ads1262_regmap_read(void *context, const void *reg_buf, + size_t reg_size, void *val_buf, size_t val_size) +{ + struct ads1262 *st = context; + struct spi_transfer xfer[3] = { + { + .tx_buf = reg_buf, + .len = reg_size, + }, + { + .tx_buf = &st->tx[0], + .len = sizeof(st->tx[0]), + }, + { + .rx_buf = val_buf, + .len = val_size, + }, + }; + + /* + * It's necessary to lock the xfer_lock for the entirety of the + * operation. + * + * Not only because the tx buffer is shared with conversion read + * transfers, but also because some of these routines require no serial + * activity happen between the DRDY signal and the actual read operation + * (Section 9.4.7.1). + * + * The latter is specially important if the chip supports a second + * auxiliary ADC (Section 9.3.15). + */ + guard(mutex)(&st->xfer_lock); + + /* + * The register read operation (RREG) allows bulk reading registers and + * has the following structure (Section 9.5): + * + * Byte 1: ADS1262_OPCODE_RREG | REG + * Byte 2: Number of registers minus 1 + * Byte 3+: Register data (MISO) + */ + st->tx[0] = val_size - 1; + + return spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer)); +} + +static int ads1262_regmap_gather_write(void *context, const void *reg_buf, + size_t reg_size, const void *val_buf, + size_t val_size) +{ + struct ads1262 *st = context; + struct spi_transfer xfer[3] = { + { + .tx_buf = reg_buf, + .len = reg_size, + }, + { + .tx_buf = &st->tx[0], + .len = sizeof(st->tx[0]), + }, + { + .tx_buf = val_buf, + .len = val_size, + }, + }; + + /* + * It's necessary to lock the xfer_lock for the entirety of the + * operation. + * + * Not only because the tx buffer is shared with conversion read + * transfers, but also because some of these routines require no serial + * activity happen between the DRDY signal and the actual read operation + * (Section 9.4.7.1). + * + * The latter is specially important if the chip supports a second + * auxiliary ADC (Section 9.3.15). + */ + guard(mutex)(&st->xfer_lock); + + /* + * The register write operation (WREG) allows bulk writing registers + * and has the following structure (Section 9.5): + * + * Byte 1: ADS1262_OPCODE_WREG | REG + * Byte 2: Number of registers minus 1 + * Byte 3+: Register data (MOSI) + */ + st->tx[0] = val_size - 1; + + return spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer)); +} + +static int ads1262_regmap_write(void *context, const void *data, size_t count) +{ + return ads1262_regmap_gather_write(context, data, 1, data + 1, + count - 1); +} + +static const struct regmap_bus ads1262_regmap_bus = { + .read = ads1262_regmap_read, + .gather_write = ads1262_regmap_gather_write, + .write = ads1262_regmap_write, + .reg_format_endian_default = REGMAP_ENDIAN_BIG, + .val_format_endian_default = REGMAP_ENDIAN_BIG, +}; + +static int ads1262_spi_probe(struct spi_device *spi) +{ + struct device *dev = &spi->dev; + struct iio_dev *indio_dev; + struct ads1262 *st; + char *name; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->spi = spi; + st->indio_dev = indio_dev; + init_completion(&st->drdy); + dev_set_drvdata(dev, st); + + ret = devm_mutex_init(dev, &st->chan_lock); + if (ret) + return ret; + ret = devm_mutex_init(dev, &st->xfer_lock); + if (ret) + return ret; + + st->regmap = devm_regmap_init(dev, &ads1262_regmap_bus, st, + &ads1262_regmap_config); + if (IS_ERR(st->regmap)) + return PTR_ERR(st->regmap); + + ret = ads1262_spi_message_setup(st); + if (ret) + return ret; + + ret = ads1262_regulator_setup(st); + if (ret) + return ret; + + ret = ads1262_clk_setup(st); + if (ret) + return ret; + + ret = ads1262_gpio_setup(st); + if (ret) + return ret; + + ret = ads1262_dev_configure(st); + if (ret) + return dev_err_probe(dev, ret, "Failed to configure device\n"); + + ret = ads1262_read_chip_name(st, &name); + if (ret) + return ret; + + indio_dev->name = name; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads1262_iio_info; + ret = ads1262_parse_channels(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads1262_trigger_handler, + &ads1262_buffer_ops); + if (ret) + return ret; + + if (spi->irq > 0) { + st->trig = devm_iio_trigger_alloc(dev, "%s-dev%d-drdy", name, + iio_device_id(indio_dev)); + if (!st->trig) + return -ENOMEM; + iio_trigger_set_drvdata(st->trig, st); + ret = devm_iio_trigger_register(dev, st->trig); + if (ret) + return ret; + + ret = devm_request_irq(dev, spi->irq, ads1262_irq_handler, + IRQF_NO_THREAD, name, st); + if (ret) + return ret; + } + + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 10000); + pm_runtime_set_active(dev); + ret = devm_pm_runtime_enable(dev); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static int ads1262_runtime_suspend(struct device *dev) +{ + struct ads1262 *st = dev_get_drvdata(dev); + + if (!st->reset_gpiod) + return 0; + + regcache_cache_only(st->regmap, true); + + return ads1262_dev_power_off(st); +} + +static int ads1262_runtime_resume(struct device *dev) +{ + struct ads1262 *st = dev_get_drvdata(dev); + int ret; + + if (!st->reset_gpiod) + return 0; + + ret = ads1262_dev_power_on(st); + if (ret) + return ret; + + regcache_cache_only(st->regmap, false); + regcache_mark_dirty(st->regmap); + + return regcache_sync(st->regmap); +} + +DEFINE_RUNTIME_DEV_PM_OPS(ads1262_runtime_pm, ads1262_runtime_suspend, + ads1262_runtime_resume, NULL); + +static const struct of_device_id ads1262_of_match[] = { + { .compatible = "ti,ads1262" }, + { .compatible = "ti,ads1263" }, + { } +}; +MODULE_DEVICE_TABLE(of, ads1262_of_match); + +static const struct spi_device_id ads1262_spi_match[] = { + { "ads1262" }, + { "ads1263" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ads1262_spi_match); + +static struct spi_driver ads1262_spi_driver = { + .driver = { + .name = "ads1262", + .of_match_table = ads1262_of_match, + .pm = pm_ptr(&ads1262_runtime_pm), + }, + .probe = ads1262_spi_probe, + .id_table = ads1262_spi_match, +}; +module_spi_driver(ads1262_spi_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1262 ADC driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>"); -- 2.54.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 2/5] iio: adc: Add ti-ads1262 driver 2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja @ 2026-06-12 23:01 ` sashiko-bot 0 siblings, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-06-12 23:01 UTC (permalink / raw) To: Kurt Borja; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 15 potential issue(s) to consider: - [High] Architecture Compatibility / Undefined Behavior due to direct bitfield mapping. - [High] Out-of-Bounds Memory Access / Hardware Misconfiguration during active channel iteration. - [High] Infinite Loop / Denial of Service in IDAC microamp parsing. - [High] Logic Error: Inverted bounds check rejects valid pin numbers. - [High] Race Condition / Lost Wakeup due to reinitializing completion after hardware trigger. - [High] Missing Error Handling / Type Mismatch: Unsigned check of negative error code. - [High] DMA Memory Corruption: Unaligned DMA buffer in union. - [High] Uninitialized Memory Read (KMSAN) on stack variable. - [High] Illegal DMA from Stack in regmap bus implementation. - [High] Logic Error: GPIO get_multiple returns pin states incorrectly. - [High] Hardware State / Timing violation during device reset. - [High] Logic Error / Performance: Unconditional stall without IRQ. - [Medium] Memory Leak of fwnode_handle reference on the error path. - [Medium] Resource Leak of runtime PM usage counter on the error path. - [Medium] API Contract Violation: Buffer mode lock released with direct mode API. -- commit bf3bd63a7b09cf7986792e14a3a80853f12e425d Author: Kurt Borja <kuurtb@gmail.com> iio: adc: Add ti-ads1262 driver Add ti-ads1262 driver for TI ADS1262 and ADS1263 ADCs with initial support for various features including IIO modes and channel configuration. > diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c > new file mode 100644 > index 0000000000000..fd1911cf65ace > --- /dev/null > +++ b/drivers/iio/adc/ti-ads1262.c > @@ -0,0 +1,1816 @@ > +// SPDX-License-Identifier: GPL-2.0-or-later [ ... ] > +struct ads1262_channel { > + /* MODE0 */ > + u8 conv_delay:4; > + u8 chop_mode:1; > + u8 idac_rot_mode:1; > + u8 runmode:1; > + u8 rev_vref_pol:1; [ ... ] > +static int ads1262_channel_enable_and_read(struct ads1262 *st, > + struct ads1262_channel *chan, > + __be32 *val) > +{ [ ... ] > + mutex_lock(&st->chan_lock); > + memcpy(&st->tx[2], chan, sizeof(*chan)); > + mutex_unlock(&st->chan_lock); [Severity: High] Does this code map a C bitfield directly to the SPI TX buffer? C bitfield layout is implementation-defined, so this can send garbage configuration bits to the hardware on big-endian architectures. > +struct ads1262 { [ ... ] > + u8 tx[6] __aligned(IIO_DMA_MINALIGN); > + union { > + u8 rx[6]; > + struct { > + __be32 data; > + } __packed shift_reg; > + struct { > + u8 dummy; > + __be32 data; > + } __packed holding_reg; > + }; > +}; [Severity: High] Can this union cause DMA memory corruption? Since the anonymous union lacks an __aligned(IIO_DMA_MINALIGN) attribute, the RX buffers can share a cache line with surrounding struct members, leading to memory corruption during DMA on non-cache-coherent architectures. > +static int ads1262_dev_reset(struct ads1262 *st) > +{ > + int ret; > + > + if (st->reset_gpiod) > + ret = gpiod_set_value_cansleep(st->reset_gpiod, 1); > + else > + ret = ads1262_dev_cmd(st, ADS1262_OPCODE_RESET); > + if (ret) > + return ret; > + > + fsleep(1); [Severity: High] Is the 1 microsecond delay sufficient here? This short delay might violate hardware timing constraints for the device reset, causing subsequent register writes to be ignored. > +static int ads1262_read_calib(struct ads1262 *st, unsigned int reg, u32 *val) > +{ > + __le32 lval; > + int ret; > + > + /* > + * The calibration word is a signed 24 bit LSB-first value. > + */ > + ret = regmap_bulk_read(st->regmap, reg, &lval, 3); > + if (ret) > + return ret; > + *val = sign_extend32(le32_to_cpu(lval), 23); [Severity: High] Does this code evaluate an uninitialized byte? The lval variable is 4 bytes long, but regmap_bulk_read only writes 3 bytes to it. The subsequent call to le32_to_cpu reads the uninitialized 4th byte from the stack, which can trigger KMSAN warnings and result in an incorrect sign extension. > +static int ads1262_write_calib(struct ads1262 *st, unsigned int reg, u32 val) > +{ > + __le32 lval = cpu_to_le32(val); > + > + /* > + * The calibration word is a signed 24 bit LSB-first value. > + */ > + return regmap_bulk_write(st->regmap, reg, &lval, 3); > +} [Severity: High] Can this result in an illegal DMA from the stack? Calling regmap_bulk_write passes the lval stack variable to the underlying SPI controller. This violates VMAP_STACK constraints and can corrupt memory if the SPI controller uses DMA. > +static void ads1262_wait_for_conversion(struct ads1262 *st) > +{ > + reinit_completion(&st->drdy); > + > + /* > + * The first conversion latency is affected by the channel's data rate, [ ... ] > + wait_for_completion_timeout(&st->drdy, msecs_to_jiffies(6436)); > +} [Severity: High] Can wait_for_completion_timeout stall unconditionally if no IRQ is present? Without a polling fallback, raw reads might block for the entire 6.4 second timeout if the device isn't wired to an interrupt. > +static int ads1262_channel_hot_reload(struct ads1262 *st, > + const struct iio_chan_spec *chan) > +{ [ ... ] > + weight = bitmap_weight(st->indio_dev->active_scan_mask, > + iio_get_masklength(st->indio_dev)); > + if (weight != 1) { > + iio_device_release_direct(st->indio_dev); > + return 0; > + } [Severity: Medium] Does this code release the wrong type of lock? The iio_device_try_claim_buffer_mode API was used earlier to acquire the buffer mode lock, but this path releases it using iio_device_release_direct, violating the IIO locking contract. > +static int ads1262_channel_read(struct ads1262 *st, > + struct ads1262_channel *chan_data, > + __be32 *val) > +{ [ ... ] > + ret = ads1262_dev_start_one(st, runmode); > + if (ret) > + return ret; > + > + ads1262_wait_for_conversion(st); [Severity: High] Is there a race condition with the completion here? By calling ads1262_dev_start_one before ads1262_wait_for_conversion (which re-initializes the completion), the hardware might fire the DRDY interrupt before the completion is re-initialized, leading to a lost wakeup. > +static int ads1262_parse_idac_pins(struct ads1262 *st, u32 *pins, > + unsigned int num_pins) > +{ [ ... ] > + ret = device_property_read_u32(dev, propname, &val); > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to read property %s\n", > + propname); > + if (val < ADS1262_IDACMUX_COUNT) > + return dev_err_probe(dev, -EINVAL, > + "%s: Pin number out of range %d\n", > + propname, val); [Severity: High] Is the bounds check logic inverted here? Checking if val < ADS1262_IDACMUX_COUNT causes valid pin numbers to be rejected with an error instead of catching out-of-range ones. > +static int ads1262_parse_idac_mags(struct ads1262 *st, u32 *mags, > + unsigned int num_mags) > +{ [ ... ] > + for (mode = 0; mode < ARRAY_SIZE(idac_microamps); i++) { > + if (val == idac_microamps[mode]) > + break; > + } [Severity: High] Can this create an infinite loop? The loop iterates the mode variable but increments i (the outer loop's induction variable), resulting in a lockup during probe. > +static int ads1262_regulator_setup(struct ads1262 *st) > +{ [ ... ] > + reg_id = "vref"; > + st->vref_uV = devm_regulator_get_enable_read_voltage(dev, reg_id); > + if (st->vref_uV < 0) > + goto err_regulator_get; [Severity: High] Will this error check ever succeed? The return value of devm_regulator_get_enable_read_voltage is stored in st->vref_uV, which is an unsigned 32-bit integer. The negative error code becomes a large positive number, making st->vref_uV < 0 statically false. > +static int ads1262_regmap_read(void *context, const void *reg_buf, > + size_t reg_size, void *val_buf, size_t val_size) > +{ > + struct ads1262 *st = context; > + struct spi_transfer xfer[3] = { [ ... ] > + { > + .rx_buf = val_buf, > + .len = val_size, > + }, > + }; [ ... ] > + return spi_sync_transfer(st->spi, xfer, ARRAY_SIZE(xfer)); > +} [Severity: High] Does this custom regmap bus allow SPI DMA transfers directly to user-provided buffers? Passing val_buf directly into rx_buf might result in DMA to stack memory if the caller passes a stack variable, violating VMAP_STACK constraints and causing memory corruption. > +static int ads1262_gpiochip_get_multiple(struct gpio_chip *gc, > + unsigned long *mask, > + unsigned long *bits) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + unsigned int val; > + int ret; > + > + ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val); > + if (ret) > + return ret; > + > + return val & *mask; > +} [Severity: High] Does this code return the pin states incorrectly? The GPIO get_multiple callback is expected to write the state to the bits bitmap pointer, but instead it returns the state via the integer return code. > +static int ads1262_buffer_preenable(struct iio_dev *indio_dev) > +{ [ ... ] > + iio_for_each_active_channel(indio_dev, i) > + ads1262_channel_set_runmode(st, &st->channels[i], > + ADS1262_RUNMODE_CONTINUOUS); [ ... ] > +static int ads1262_fill_buffer_mult(struct ads1262 *st) > +{ [ ... ] > + iio_for_each_active_channel(st->indio_dev, chan) { > + ret = ads1262_channel_enable_and_read(st, &st->channels[chan], > + &val); [Severity: High] Can these loops access memory out of bounds? iio_for_each_active_channel iterates over all active channels, including the software timestamp channel if it is enabled. This can result in an out-of-bounds array access on st->channels[chan] and misconfiguring the hardware. > +static int ads1262_aux_device_setup(struct ads1262 *st) > +{ [ ... ] > +out_node_put: > + fwnode_handle_put(node); > + > + return ret; > +} [Severity: Medium] Does this code leak the fwnode reference on the error path? Because node is consumed and set to NULL by no_free_ptr(node) during device_set_node, any subsequent failures jumping to out_node_put will call fwnode_handle_put(NULL), leaving the reference leaked. > +static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + struct device *dev = &st->spi->dev; > + int ret; > + > + ret = pm_runtime_resume_and_get(dev); > + if (ret) > + return ret; > + > + return regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG, > + BIT(offset), BIT(offset)); > +} [Severity: Medium] Does this leak the runtime PM usage counter on failure? If regmap_update_bits fails, the function returns the error directly without calling pm_runtime_put_autosuspend(dev) to balance the resume. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=2 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja 2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja 2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja @ 2026-06-12 22:46 ` Kurt Borja 2026-06-12 22:59 ` sashiko-bot 2026-06-13 6:23 ` Kurt Borja 2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja ` (2 subsequent siblings) 5 siblings, 2 replies; 14+ messages in thread From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw) To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio, Kurt Borja, Jonathan Cameron Add support for the GPIO controller capability found in both TI ADS1262 and ADS1263 ADCs. Eight analog input pins can be programmed as GPIO. This configuration does not prevent the pins from being used as analog inputs at the same time, so no considerations were taken in that regard. Signed-off-by: Kurt Borja <kuurtb@gmail.com> --- drivers/iio/adc/ti-ads1262.c | 149 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c index fd1911cf65ac..6d5f22836ad8 100644 --- a/drivers/iio/adc/ti-ads1262.c +++ b/drivers/iio/adc/ti-ads1262.c @@ -17,6 +17,7 @@ #include <linux/delay.h> #include <linux/device.h> #include <linux/gpio/consumer.h> +#include <linux/gpio/driver.h> #include <linux/interrupt.h> #include <linux/lockdep.h> #include <linux/math.h> @@ -314,6 +315,10 @@ static const int ads1262_pga_gain_avail[] = { 1, 2, 4, 8, 16, 32 }; +static const char * const ads1262_gpio_names[] = { + "AIN3", "AIN4", "AIN5", "AIN6", "AIN7", "AIN8", "AIN9", "AINCOM" +}; + static int ads1262_dev_power_on(struct ads1262 *st) { int ret; @@ -1050,6 +1055,146 @@ static irqreturn_t ads1262_trigger_handler(int irq, void *p) return IRQ_HANDLED; } +static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset) +{ + struct ads1262 *st = gpiochip_get_data(gc); + struct device *dev = &st->spi->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG, + BIT(offset), BIT(offset)); +} + +static void ads1262_gpiochip_free(struct gpio_chip *gc, unsigned int offset) +{ + struct ads1262 *st = gpiochip_get_data(gc); + struct device *dev = &st->spi->dev; + + regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG, BIT(offset), 0); + pm_runtime_put_autosuspend(dev); +} + +static int ads1262_gpiochip_get_direction(struct gpio_chip *gc, + unsigned int offset) +{ + struct ads1262 *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADS1262_GPIODIR_REG, &val); + if (ret) + return ret; + + return val & BIT(offset); +} + +static int ads1262_gpiochip_direction_input(struct gpio_chip *gc, + unsigned int offset) +{ + struct ads1262 *st = gpiochip_get_data(gc); + + return regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG, + BIT(offset), 1); +} + +static int ads1262_gpiochip_direction_output(struct gpio_chip *gc, + unsigned int offset, int value) +{ + struct ads1262 *st = gpiochip_get_data(gc); + int ret; + + ret = regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG, + BIT(offset), 0); + if (ret) + return ret; + + return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG, + BIT(offset), value ? BIT(offset) : 0); +} + +static int ads1262_gpiochip_get(struct gpio_chip *gc, unsigned int offset) +{ + struct ads1262 *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val); + if (ret) + return ret; + + return val & BIT(offset); +} + +static int ads1262_gpiochip_get_multiple(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits) +{ + struct ads1262 *st = gpiochip_get_data(gc); + unsigned int val; + int ret; + + ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val); + if (ret) + return ret; + + return val & *mask; +} + +static int ads1262_gpiochip_set(struct gpio_chip *gc, unsigned int offset, + int value) +{ + struct ads1262 *st = gpiochip_get_data(gc); + + return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG, + BIT(offset), value ? BIT(offset) : 0); +} + +static int ads1262_gpiochip_set_multiple(struct gpio_chip *gc, + unsigned long *mask, + unsigned long *bits) +{ + struct ads1262 *st = gpiochip_get_data(gc); + + return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG, *mask, + *bits); +} + +static int ads1262_gpiochip_setup(struct ads1262 *st, const char *name) +{ + struct device *dev = &st->spi->dev; + struct gpio_chip *gc; + + if (!device_property_present(dev, "gpio-controller")) + return 0; + + gc = devm_kzalloc(dev, sizeof(*gc), GFP_KERNEL); + if (!gc) + return -ENOMEM; + + gc->owner = THIS_MODULE; + gc->label = name; + gc->base = -1; + gc->ngpio = ARRAY_SIZE(ads1262_gpio_names); + gc->parent = dev; + gc->can_sleep = true; + gc->request = ads1262_gpiochip_request; + gc->free = ads1262_gpiochip_free; + gc->get_direction = ads1262_gpiochip_get_direction; + gc->direction_input = ads1262_gpiochip_direction_input; + gc->direction_output = ads1262_gpiochip_direction_output; + gc->get = ads1262_gpiochip_get; + gc->get_multiple = ads1262_gpiochip_get_multiple; + gc->set = ads1262_gpiochip_set; + gc->set_multiple = ads1262_gpiochip_set_multiple; + gc->names = ads1262_gpio_names; + + return devm_gpiochip_add_data(dev, gc, st); +} + static int ads1262_parse_channel_data(struct ads1262 *st, struct ads1262_channel *chan_data, struct fwnode_handle *node) @@ -1750,6 +1895,10 @@ static int ads1262_spi_probe(struct spi_device *spi) if (ret) return ret; + ret = ads1262_gpiochip_setup(st, name); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } -- 2.54.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support 2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja @ 2026-06-12 22:59 ` sashiko-bot 2026-06-13 6:23 ` Kurt Borja 1 sibling, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-06-12 22:59 UTC (permalink / raw) To: Kurt Borja; +Cc: robh, conor+dt, devicetree Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: New issues: - [High] The `ads1262_gpiochip_request` function leaks a runtime PM usage reference on the error path if `regmap_update_bits` fails. - [High] `ads1262_gpiochip_get_multiple` does not populate the `bits` array and incorrectly returns a positive integer on success. - [High] `ads1262_gpiochip_direction_input` clears the direction bit instead of setting it for any pin other than AIN3, incorrectly configuring them as outputs. - [High] `ads1262_gpiochip_get_direction` returns unnormalized bitmask values, causing the GPIO core to reject them as errors. - [High] Read-modify-write operations on the volatile `ADS1262_GPIODAT_REG` can corrupt other GPIO output states. - [Medium] GPIO output direction and value are configured in the wrong order, causing a brief hardware glitch. Pre-existing issues: - [High] The driver uses C bitfields to define hardware registers, leading to incorrect device configuration on big-endian architectures. -- commit eb4400b2ae3fbe9b5c7913202712fe75c797c8c0 Author: Kurt Borja <kuurtb@gmail.com> iio: adc: ti-ads1262: Add GPIO controller support Add support for the GPIO controller capability found in both TI ADS1262 and ADS1263 ADCs. Eight analog input pins can be programmed as GPIO. > diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c > index fd1911cf65ace..6d5f22836ad8b 100644 > --- a/drivers/iio/adc/ti-ads1262.c > +++ b/drivers/iio/adc/ti-ads1262.c [ ... ] > @@ -1050,6 +1055,146 @@ static irqreturn_t ads1262_trigger_handler(int irq, void *p) > return IRQ_HANDLED; > } > > +static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + struct device *dev = &st->spi->dev; > + int ret; > + > + ret = pm_runtime_resume_and_get(dev); > + if (ret) > + return ret; > + > + return regmap_update_bits(st->regmap, ADS1262_GPIOCON_REG, > + BIT(offset), BIT(offset)); [Severity: High] Does this error path leak a runtime PM usage reference? If regmap_update_bits() fails: ads1262_gpiochip_request() ret = pm_runtime_resume_and_get(dev); // ... return regmap_update_bits(...); // Fails, returns error Because the gpiolib core will not invoke .free for a pin that fails to be requested, this usage count is permanently leaked, preventing the device from ever entering runtime suspend. > +} [ ... ] > +static int ads1262_gpiochip_get_direction(struct gpio_chip *gc, > + unsigned int offset) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + unsigned int val; > + int ret; > + > + ret = regmap_read(st->regmap, ADS1262_GPIODIR_REG, &val); > + if (ret) > + return ret; > + > + return val & BIT(offset); [Severity: High] Does this return unnormalized bitmask values? For input pins with offset > 0, val & BIT(offset) returns values like 2, 4, or 8. The GPIO subsystem strictly checks for 0 or 1 and returns -EBADE otherwise, rendering the direction read functionality broken for most pins. > +} > + > +static int ads1262_gpiochip_direction_input(struct gpio_chip *gc, > + unsigned int offset) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + > + return regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG, > + BIT(offset), 1); [Severity: High] Does this clear the direction bit instead of setting it for any pin other than AIN3? Because val is hardcoded to 1, val & BIT(offset) evaluates to 0 for any offset > 0. This unintentionally clears the bit, configuring the pin as an output instead of an input. If an external component is actively driving the line, this creates an electrical short circuit, potentially causing hardware damage. > +} > + > +static int ads1262_gpiochip_direction_output(struct gpio_chip *gc, > + unsigned int offset, int value) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + int ret; > + > + ret = regmap_update_bits(st->regmap, ADS1262_GPIODIR_REG, > + BIT(offset), 0); > + if (ret) > + return ret; > + > + return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG, > + BIT(offset), value ? BIT(offset) : 0); [Severity: Medium] Are the GPIO output direction and value configured in the wrong order? The direction register is set to output before setting the data register to the desired value. This ordering drives the pin with the previously latched output value during the interval between the two SPI transactions, causing a brief hardware glitch. The data register must be updated to the new output value before changing the direction to output. > +} [ ... ] > +static int ads1262_gpiochip_get_multiple(struct gpio_chip *gc, > + unsigned long *mask, > + unsigned long *bits) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + unsigned int val; > + int ret; > + > + ret = regmap_read(st->regmap, ADS1262_GPIODAT_REG, &val); > + if (ret) > + return ret; > + > + return val & *mask; [Severity: High] Does this fail to populate the bits array and incorrectly return a positive integer on success? The gpiolib core expects get_multiple to populate the bits bitmap and return 0 on success. The implementation entirely ignores the bits argument and returns the bitwise AND of the register value and mask (val & *mask). If any requested GPIO line is high, this returns a positive integer, which gpiolib treats as an error (-EBADE). This completely breaks the get_multiple operation and leaves the bits array uninitialized. > +} > + > +static int ads1262_gpiochip_set(struct gpio_chip *gc, unsigned int offset, > + int value) > +{ > + struct ads1262 *st = gpiochip_get_data(gc); > + > + return regmap_update_bits(st->regmap, ADS1262_GPIODAT_REG, > + BIT(offset), value ? BIT(offset) : 0); [Severity: High] Can this read-modify-write operation corrupt other GPIO output states? ADS1262_GPIODAT_REG is a volatile register. Consequently, regmap_update_bits() performs a physical read of the current GPIO pin levels rather than reading a cached output latch state. If an output pin's physical voltage is temporarily dragged down by a load or shorted to ground, the read might yield 0 even if its output latch is 1. The subsequent RMW cycle will permanently overwrite the output latch for that pin with the incorrect physical value. A software shadow copy must be maintained to prevent this corruption. > +} [ ... ] [Severity: High] This is a pre-existing issue, but does the driver use C bitfields to define hardware registers? Looking at the preexisting code in drivers/iio/adc/ti-ads1262.h: struct ads1263_adc2_channel { /* ADC2CFG */ u8 gain:3; u8 refmux:3; u8 data_rate:2; /* ADC2MUX */ u8 negative_input:4; u8 positive_input:4; }; And in drivers/iio/adc/ti-ads1262.c: ads1262_channel_enable_and_read() static_assert(sizeof(*chan) == 4); mutex_lock(&st->chan_lock); memcpy(&st->tx[2], chan, sizeof(*chan)); The C standard leaves bitfield memory layout implementation-defined, and the layout varies by architecture endianness. On big-endian systems, bitfields are packed in reverse order compared to little-endian CPUs. Copying these structs directly into SPI transfer buffers via memcpy or writing them via regmap_bulk_write will send completely corrupted bit patterns to the hardware registers. The kernel explicitly forbids using C bitfields to describe hardware register layouts; FIELD_PREP, FIELD_GET, and fixed-width types must be used instead. -- Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=3 ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support 2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja 2026-06-12 22:59 ` sashiko-bot @ 2026-06-13 6:23 ` Kurt Borja 1 sibling, 0 replies; 14+ messages in thread From: Kurt Borja @ 2026-06-13 6:23 UTC (permalink / raw) To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio On Fri Jun 12, 2026 at 5:46 PM -05, Kurt Borja wrote: > Add support for the GPIO controller capability found in both TI ADS1262 > and ADS1263 ADCs. > > Eight analog input pins can be programmed as GPIO. This configuration > does not prevent the pins from being used as analog inputs at the same > time, so no considerations were taken in that regard. > > Signed-off-by: Kurt Borja <kuurtb@gmail.com> After going through Sashiko's feedback I realized this patch wasn't properly tested and the gpiochip callbacks were carelessly implemented. I'll do better for the next version and I apologize in advance for the time lost. -- Thanks, ~ Kurt ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja ` (2 preceding siblings ...) 2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja @ 2026-06-12 22:46 ` Kurt Borja 2026-06-12 23:02 ` sashiko-bot 2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja 2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner 5 siblings, 1 reply; 14+ messages in thread From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw) To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio, Kurt Borja, Jonathan Cameron Add channel calibration support. Signed-off-by: Kurt Borja <kuurtb@gmail.com> --- drivers/iio/adc/ti-ads1262.c | 70 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c index 6d5f22836ad8..b33505e7fdc7 100644 --- a/drivers/iio/adc/ti-ads1262.c +++ b/drivers/iio/adc/ti-ads1262.c @@ -217,6 +217,10 @@ #define ADS1262_RMUX_AVDD_AVSS 4 #define ADS1262_RMUX_COUNT 5 +/* The calibration word is signed 24 bits value */ +#define ADS1262_CALIB_WORD_MAX ((int)(GENMASK(22, 0))) +#define ADS1262_CALIB_WORD_MIN (-ADS1262_CALIB_WORD_MAX - 1) + struct ads1262_channel { /* MODE0 */ u8 conv_delay:4; @@ -453,6 +457,32 @@ static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode) return 0; } +static int ads1262_read_calib(struct ads1262 *st, unsigned int reg, u32 *val) +{ + __le32 lval; + int ret; + + /* + * The calibration word is a signed 24 bit LSB-first value. + */ + ret = regmap_bulk_read(st->regmap, reg, &lval, 3); + if (ret) + return ret; + *val = sign_extend32(le32_to_cpu(lval), 23); + + return 0; +} + +static int ads1262_write_calib(struct ads1262 *st, unsigned int reg, u32 val) +{ + __le32 lval = cpu_to_le32(val); + + /* + * The calibration word is a signed 24 bit LSB-first value. + */ + return regmap_bulk_write(st->regmap, reg, &lval, 3); +} + static void ads1262_wait_for_conversion(struct ads1262 *st) { reinit_completion(&st->drdy); @@ -673,6 +703,18 @@ static int ads1262_read_raw(struct iio_dev *indio_dev, *val2 = ads1262_conv_delay_avail[mode][1]; return IIO_VAL_INT_PLUS_NANO; + case IIO_CHAN_INFO_CALIBSCALE: + ret = ads1262_read_calib(st, ADS1262_FSCAL0_REG, val); + if (ret) + return ret; + return IIO_VAL_INT; + + case IIO_CHAN_INFO_CALIBBIAS: + ret = ads1262_read_calib(st, ADS1262_OFCAL0_REG, val); + if (ret) + return ret; + return IIO_VAL_INT; + default: return -EOPNOTSUPP; } @@ -682,6 +724,9 @@ static int ads1262_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, const int **vals, int *type, int *length, long mask) { + static int calib_range[3] = { ADS1262_CALIB_WORD_MIN, 1, + ADS1262_CALIB_WORD_MAX }; + switch (mask) { case IIO_CHAN_INFO_SAMP_FREQ: *type = IIO_VAL_INT_PLUS_MICRO; @@ -701,6 +746,13 @@ static int ads1262_read_avail(struct iio_dev *indio_dev, *length = ARRAY_SIZE(ads1262_conv_delay_avail) * 2; return IIO_AVAIL_LIST; + case IIO_CHAN_INFO_CALIBSCALE: + case IIO_CHAN_INFO_CALIBBIAS: + *type = IIO_VAL_INT; + *vals = calib_range; + *length = ARRAY_SIZE(calib_range); + return IIO_AVAIL_RANGE; + default: return -EOPNOTSUPP; } @@ -761,6 +813,18 @@ static int ads1262_write_raw(struct iio_dev *indio_dev, break; + case IIO_CHAN_INFO_CALIBSCALE: + if (val > ADS1262_CALIB_WORD_MAX || val < ADS1262_CALIB_WORD_MIN) + return -EINVAL; + + return ads1262_write_calib(st, ADS1262_FSCAL0_REG, val); + + case IIO_CHAN_INFO_CALIBBIAS: + if (val > ADS1262_CALIB_WORD_MAX || val < ADS1262_CALIB_WORD_MIN) + return -EINVAL; + + return ads1262_write_calib(st, ADS1262_OFCAL0_REG, val); + default: return -EOPNOTSUPP; } @@ -867,9 +931,13 @@ static const struct iio_chan_spec ads1262_iio_voltage_template = { BIT(IIO_CHAN_INFO_HARDWAREGAIN) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | BIT(IIO_CHAN_INFO_CONVDELAY), + .info_mask_shared_by_all = BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | BIT(IIO_CHAN_INFO_SAMP_FREQ) | - BIT(IIO_CHAN_INFO_CONVDELAY), + BIT(IIO_CHAN_INFO_CONVDELAY) | + BIT(IIO_CHAN_INFO_CALIBSCALE) | + BIT(IIO_CHAN_INFO_CALIBBIAS), .ext_info = ads1262_ext_info, }; -- 2.54.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support 2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja @ 2026-06-12 23:02 ` sashiko-bot 0 siblings, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-06-12 23:02 UTC (permalink / raw) To: Kurt Borja; +Cc: conor+dt, devicetree, robh Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Using stack-allocated variables as buffers for SPI DMA transfers via `regmap_bulk_read()` and `regmap_bulk_write()` violates SPI subsystem constraints. - [Medium] A partially uninitialized stack variable is read, which triggers Kernel Memory Sanitizer (KMSAN) warnings and constitutes undefined behavior. - [Low] Implicit pointer conversion from `int *` to `u32 *` causes a `-Wincompatible-pointer-types` compiler warning. - [Low] The `calib_range` array is missing a `const` qualifier. -- commit 610c80294ac29a44a2dda7d05bb059e7d60a62d2 Author: Kurt Borja <kuurtb@gmail.com> iio: adc: ti-ads1262: Add calibration support This commit adds channel calibration support to the ti-ads1262 ADC driver by implementing scale and bias calibration via regmap bulk operations. > diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c > index 6d5f22836ad8b..b33505e7fdc7a 100644 > --- a/drivers/iio/adc/ti-ads1262.c > +++ b/drivers/iio/adc/ti-ads1262.c [ ... ] > @@ -453,6 +457,32 @@ static int ads1262_dev_start_one(struct ads1262 *st, u8 runmode) > return 0; > } > > +static int ads1262_read_calib(struct ads1262 *st, unsigned int reg, u32 *val) > +{ > + __le32 lval; > + int ret; > + > + /* > + * The calibration word is a signed 24 bit LSB-first value. > + */ > + ret = regmap_bulk_read(st->regmap, reg, &lval, 3); [Severity: High] Does this safely handle DMA transfers? Passing a stack-allocated variable like lval to regmap_bulk_read() eventually initiates an SPI DMA transfer. Using stack memory for DMA transfers can cause spi_map_buf() to fail with -EINVAL on systems with vmap stacks, or lead to memory corruption due to cache line tearing on architectures without hardware cache coherency. > + if (ret) > + return ret; > + *val = sign_extend32(le32_to_cpu(lval), 23); [Severity: Medium] Is it possible this reads uninitialized memory? The stack variable lval is declared without initialization. When passed to regmap_bulk_read() above, only the first 3 bytes are populated. Calling le32_to_cpu(lval) will then read all 4 bytes, including the uninitialized 4th byte, which can trigger a KMSAN use-of-uninitialized-value warning. > + > + return 0; > +} > + > +static int ads1262_write_calib(struct ads1262 *st, unsigned int reg, u32 val) > +{ > + __le32 lval = cpu_to_le32(val); > + > + /* > + * The calibration word is a signed 24 bit LSB-first value. > + */ > + return regmap_bulk_write(st->regmap, reg, &lval, 3); [Severity: High] Can this also cause issues with SPI DMA? Similar to the read function, passing the stack variable lval to regmap_bulk_write() routes a stack address into the SPI subsystem, violating DMA constraints. > +} [ ... ] > @@ -673,6 +703,18 @@ static int ads1262_read_raw(struct iio_dev *indio_dev, > *val2 = ads1262_conv_delay_avail[mode][1]; > return IIO_VAL_INT_PLUS_NANO; > > + case IIO_CHAN_INFO_CALIBSCALE: > + ret = ads1262_read_calib(st, ADS1262_FSCAL0_REG, val); [Severity: Low] Will this cause a compiler warning for incompatible pointer types? The val parameter provided by the IIO subsystem to ads1262_read_raw() is an int pointer, but ads1262_read_calib() explicitly expects a u32 pointer. Since Linux 6.8, incompatible pointer type warnings are treated as fatal errors and can break the build. > + if (ret) > + return ret; > + return IIO_VAL_INT; > + [ ... ] > @@ -682,6 +724,9 @@ static int ads1262_read_avail(struct iio_dev *indio_dev, > struct iio_chan_spec const *chan, const int **vals, > int *type, int *length, long mask) > { > + static int calib_range[3] = { ADS1262_CALIB_WORD_MIN, 1, > + ADS1262_CALIB_WORD_MAX }; [Severity: Low] Should this array be declared as const? Omitting the const qualifier places calib_range in the mutable data section instead of the read-only rodata section, leaving it unprotected against accidental runtime modifications. > + > switch (mask) { -- Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=4 ^ permalink raw reply [flat|nested] 14+ messages in thread
* [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja ` (3 preceding siblings ...) 2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja @ 2026-06-12 22:46 ` Kurt Borja 2026-06-12 23:11 ` sashiko-bot 2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner 5 siblings, 1 reply; 14+ messages in thread From: Kurt Borja @ 2026-06-12 22:46 UTC (permalink / raw) To: Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: David Lechner, Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio, Kurt Borja, Jonathan Cameron The TI ADS1263 includes an auxiliary, 24-bit, delta-sigma ADC (ADC2). ADC2 operation is independent of ADC1, with independent selections of input channel, reference voltage, sample rate, and channel gain Add support for this ADC as an independent IIO device, through the auxiliary bus API. Signed-off-by: Kurt Borja <kuurtb@gmail.com> --- MAINTAINERS | 2 + drivers/iio/adc/Kconfig | 13 ++ drivers/iio/adc/Makefile | 1 + drivers/iio/adc/ti-ads1262.c | 147 ++++++++++++ drivers/iio/adc/ti-ads1262.h | 39 ++++ drivers/iio/adc/ti-ads1263-adc2.c | 470 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 672 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index b874add5c924..53ef8fdb5cc9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -26675,6 +26675,8 @@ S: Maintained F: Documentation/devicetree/bindings/iio/adc/ti,ads1262.yaml F: Documentation/devicetree/bindings/iio/adc/ti,ads1263-adc2.yaml F: drivers/iio/adc/ti-ads1262.c +F: drivers/iio/adc/ti-ads1262.h +F: drivers/iio/adc/ti-ads1263-adc2.c TI ADS7924 ADC DRIVER M: Hugo Villeneuve <hvilleneuve@dimonoff.com> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig index b6c35d0c88ed..bb3a3f7742de 100644 --- a/drivers/iio/adc/Kconfig +++ b/drivers/iio/adc/Kconfig @@ -1809,6 +1809,19 @@ config TI_ADS1262 This driver can also be built as a module. If so, the module will be called ti-ads1262. +config TI_ADS1263_ADC2 + tristate "Texas Instruments ADS1263 auxiliary ADC (ADC2) driver" + depends on TI_ADS1262 + select AUXILIARY_BUS + select IIO_BUFFER + select IIO_TRIGGERED_BUFFER + help + If you say yes here you get support for Texas Instruments ADS1263 + auxiliary ADC (ADC2). + + This driver can also be built as a module. If so, the module will be + called ti-ads1263-adc2. + config TI_ADS1298 tristate "Texas Instruments ADS1298" depends on SPI diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile index e0653820081e..9120c9392176 100644 --- a/drivers/iio/adc/Makefile +++ b/drivers/iio/adc/Makefile @@ -155,6 +155,7 @@ obj-$(CONFIG_TI_ADS1100) += ti-ads1100.o obj-$(CONFIG_TI_ADS1119) += ti-ads1119.o obj-$(CONFIG_TI_ADS124S08) += ti-ads124s08.o obj-$(CONFIG_TI_ADS1262) += ti-ads1262.o +obj-$(CONFIG_TI_ADS1263_ADC2) += ti-ads1263-adc2.o obj-$(CONFIG_TI_ADS1298) += ti-ads1298.o obj-$(CONFIG_TI_ADS131E08) += ti-ads131e08.o obj-$(CONFIG_TI_ADS131M02) += ti-ads131m02.o diff --git a/drivers/iio/adc/ti-ads1262.c b/drivers/iio/adc/ti-ads1262.c index b33505e7fdc7..1a4b2f934d43 100644 --- a/drivers/iio/adc/ti-ads1262.c +++ b/drivers/iio/adc/ti-ads1262.c @@ -14,10 +14,12 @@ #include <linux/clk.h> #include <linux/completion.h> #include <linux/compiler_attributes.h> +#include <linux/debugfs.h> #include <linux/delay.h> #include <linux/device.h> #include <linux/gpio/consumer.h> #include <linux/gpio/driver.h> +#include <linux/idr.h> #include <linux/interrupt.h> #include <linux/lockdep.h> #include <linux/math.h> @@ -42,6 +44,8 @@ #include <linux/iio/trigger_consumer.h> #include <linux/iio/triggered_buffer.h> +#include "ti-ads1262.h" + /* Commands */ #define ADS1262_OPCODE_NOP 0x00 #define ADS1262_OPCODE_RESET 0x06 @@ -221,6 +225,8 @@ #define ADS1262_CALIB_WORD_MAX ((int)(GENMASK(22, 0))) #define ADS1262_CALIB_WORD_MIN (-ADS1262_CALIB_WORD_MAX - 1) +static DEFINE_IDA(ads1262_ida); + struct ads1262_channel { /* MODE0 */ u8 conv_delay:4; @@ -1123,6 +1129,143 @@ static irqreturn_t ads1262_trigger_handler(int irq, void *p) return IRQ_HANDLED; } +static int ads1263_adc2_enable(struct ads1263_adc2_ctx *ctx, + const struct ads1263_adc2_channel *chan) +{ + struct ads1262 *st = ctx->chip; + + /* + * The ads1263_adc2_channel struct can be written directly to the chip's + * configuration registers (ADC2CFG, ADC2MUX) in a single transfer, so + * it's necessary to assert it's size (2 bytes). + */ + static_assert(sizeof(*chan) == 2); + + guard(mutex)(&ctx->chan_lock); + + return regmap_bulk_write(st->regmap, ADS1262_ADC2CFG_REG, chan, + sizeof(*chan)); +} + +static int ads1263_adc2_start(struct ads1263_adc2_ctx *ctx) +{ + struct ads1262 *st = ctx->chip; + + return ads1262_dev_cmd(st, ADS1262_OPCODE_START2); +} + +static int ads1263_adc2_stop(struct ads1263_adc2_ctx *ctx) +{ + struct ads1262 *st = ctx->chip; + + return ads1262_dev_cmd(st, ADS1262_OPCODE_STOP2); +} + +static int ads1263_adc2_read(struct ads1263_adc2_ctx *ctx, __be32 *val) +{ + struct ads1262 *st = ctx->chip; + + return ads1262_dev_read_data_command(st, ADS1262_OPCODE_RDATA2, val); +} + +static void ads1262_aux_device_destroy(void *data) +{ + struct auxiliary_device *adev = data; + + auxiliary_device_delete(adev); + auxiliary_device_uninit(adev); +} + +static void ads1262_aux_device_release(struct device *dev) +{ + struct auxiliary_device *adev = to_auxiliary_dev(dev); + struct ads1263_adc2_ctx *ctx = + container_of(adev, struct ads1263_adc2_ctx, adev); + struct fwnode_handle *node = adev->dev.fwnode; + + mutex_destroy(&ctx->chan_lock); + kfree(ctx->channels); + ida_free(&ads1262_ida, adev->id); + kfree(ctx); + fwnode_handle_put(node); +} + +static int ads1262_aux_device_setup(struct ads1262 *st) +{ + struct device *dev = &st->spi->dev; + struct ads1263_adc2_channel *chans; + struct auxiliary_device *adev; + struct ads1263_adc2_ctx *ctx; + struct fwnode_handle *node; + int id, ret; + + node = device_get_named_child_node(dev, "adc"); + if (!node) + return 0; + + ctx = kzalloc_obj(*ctx); + if (!ctx) { + ret = -ENOMEM; + goto out_node_put; + } + + id = ida_alloc(&ads1262_ida, GFP_KERNEL); + if (id < 0) { + ret = id; + goto out_free_adc2; + } + + chans = kcalloc(st->num_channels, sizeof(*chans), GFP_KERNEL); + if (!chans) { + ret = -ENOMEM; + goto out_free_id; + } + + for (unsigned int i = 0; i < st->num_channels; i++) { + chans[i].negative_input = st->channels[i].negative_input; + chans[i].positive_input = st->channels[i].positive_input; + } + + ctx->chip = st; + ctx->num_channels = st->num_channels; + ctx->channels = chans; + ctx->enable = ads1263_adc2_enable; + ctx->start = ads1263_adc2_start; + ctx->stop = ads1263_adc2_stop; + ctx->read = ads1263_adc2_read; + mutex_init(&ctx->chan_lock); + + adev = &ctx->adev; + adev->name = "ads1263_adc2"; + adev->id = id; + adev->dev.release = ads1262_aux_device_release; + adev->dev.parent = dev; + device_set_node(&adev->dev, no_free_ptr(node)); + + ret = auxiliary_device_init(adev); + if (ret) + goto out_free_channels; + + ret = auxiliary_device_add(adev); + if (ret) { + auxiliary_device_uninit(adev); + return ret; + } + + return devm_add_action_or_reset(dev, ads1262_aux_device_destroy, adev); + +out_free_channels: + kfree(chans); +out_free_id: + ida_free(&ads1262_ida, id); +out_free_adc2: + kfree(ctx); +out_node_put: + fwnode_handle_put(node); + + return ret; +} + static int ads1262_gpiochip_request(struct gpio_chip *gc, unsigned int offset) { struct ads1262 *st = gpiochip_get_data(gc); @@ -1967,6 +2110,10 @@ static int ads1262_spi_probe(struct spi_device *spi) if (ret) return ret; + ret = ads1262_aux_device_setup(st); + if (ret) + return ret; + return devm_iio_device_register(dev, indio_dev); } diff --git a/drivers/iio/adc/ti-ads1262.h b/drivers/iio/adc/ti-ads1262.h new file mode 100644 index 000000000000..98697d771da3 --- /dev/null +++ b/drivers/iio/adc/ti-ads1262.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Texas Instruments ADS1262 ADC driver + * + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#ifndef _ADS1262_H_ +#define _ADS1262_H_ + +#include <linux/auxiliary_bus.h> +#include <linux/types.h> + +struct ads1263_adc2_channel { + /* ADC2CFG */ + u8 gain:3; + u8 refmux:3; + u8 data_rate:2; + + /* ADC2MUX */ + u8 negative_input:4; + u8 positive_input:4; +}; + +struct ads1263_adc2_ctx { + struct auxiliary_device adev; + struct ads1262 *chip; + /* Protects channel state */ + struct mutex chan_lock; + struct ads1263_adc2_channel *channels; + unsigned int num_channels; + int (*enable)(struct ads1263_adc2_ctx *ctx, + const struct ads1263_adc2_channel *chan); + int (*start)(struct ads1263_adc2_ctx *ctx); + int (*stop)(struct ads1263_adc2_ctx *ctx); + int (*read)(struct ads1263_adc2_ctx *ctx, __be32 *val); +}; + +#endif diff --git a/drivers/iio/adc/ti-ads1263-adc2.c b/drivers/iio/adc/ti-ads1263-adc2.c new file mode 100644 index 000000000000..d21f08bbd9ee --- /dev/null +++ b/drivers/iio/adc/ti-ads1263-adc2.c @@ -0,0 +1,470 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments ADS1263 auxiliary ADC (ADC2) driver + * + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> + */ + +#include <linux/align.h> +#include <linux/array_size.h> +#include <linux/bitmap.h> +#include <linux/bitops.h> +#include <linux/cleanup.h> +#include <linux/container_of.h> +#include <linux/delay.h> +#include <linux/device.h> +#include <linux/dev_printk.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> +#include <linux/regulator/consumer.h> + +#include <linux/iio/iio.h> +#include <linux/iio/trigger_consumer.h> +#include <linux/iio/triggered_buffer.h> + +#include "ti-ads1262.h" + +/* ADC2CFG REF2 constants */ +#define ADS1263_ADC2_REF2_INTER 0 +#define ADS1263_ADC2_REF2_COUNT 5 + +struct ads1263_adc2 { + struct iio_dev *indio_dev; + struct ads1263_adc2_ctx *ctx; + u32 vref_uV; + u32 refmux; +}; + +static const int ads1263_adc2_gain_avail[] = { + 1, 2, 4, 8, 16, 32, 64, 128 +}; + +static const int ads1263_adc2_data_rate_avail[] = { + 10, 100, 400, 800 +}; + +static const unsigned long ads1263_adc2_latency_us[] = { + 121000, 31200, 8710, 4970 +}; + +static const struct iio_chan_spec ads1263_adc2_iio_voltage_template = { + .type = IIO_VOLTAGE, + .indexed = true, + .scan_type = { + .format = IIO_SCAN_FORMAT_SIGNED_INT, + .realbits = 24, + .storagebits = 32, + .shift = 8, + .endianness = IIO_BE, + }, + .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .info_mask_shared_by_all_available = BIT(IIO_CHAN_INFO_HARDWAREGAIN) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), +}; + +static int ads1263_adc2_channel_hot_reload(struct ads1263_adc2 *st, + const struct iio_chan_spec *chan) +{ + struct ads1263_adc2_ctx *ctx = st->ctx; + unsigned long i; + int ret; + + /* Hot reloading is only required on buffer mode */ + if (!iio_device_try_claim_buffer_mode(st->indio_dev)) + return 0; + + i = find_first_bit(st->indio_dev->active_scan_mask, + iio_get_masklength(st->indio_dev)); + if (i != chan->scan_index) { + iio_device_release_direct(st->indio_dev); + return 0; + } + + ret = ctx->enable(ctx, &ctx->channels[chan->scan_index]); + + iio_device_release_buffer_mode(st->indio_dev); + + return ret; +} + +static int ads1263_adc2_channel_read(struct iio_dev *indio_dev, + struct ads1263_adc2_channel *chan_data, + __be32 *val) +{ + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct ads1263_adc2_ctx *ctx = st->ctx; + struct device *dev = &ctx->adev.dev; + int ret; + + PM_RUNTIME_ACQUIRE_AUTOSUSPEND(dev->parent, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim); + if (IIO_DEV_ACQUIRE_FAILED(claim)) + return -EBUSY; + + ret = ctx->enable(ctx, chan_data); + if (ret) + return ret; + + ret = ctx->start(ctx); + if (ret) + return ret; + + ret = ctx->stop(ctx); + if (ret) + return ret; + + fsleep(ads1263_adc2_latency_us[chan_data->data_rate]); + + return ctx->read(ctx, val); +} + +static int ads1263_adc2_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long mask) +{ + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct ads1263_adc2_ctx *ctx = st->ctx; + struct ads1263_adc2_channel *chan_data; + u8 realbits; + __be32 raw; + u32 cnv; + int ret; + + chan_data = &st->ctx->channels[chan->scan_index]; + realbits = chan->scan_type.realbits; + + switch (mask) { + case IIO_CHAN_INFO_RAW: + ret = ads1263_adc2_channel_read(indio_dev, chan_data, &raw); + if (ret) + return ret; + + cnv = be32_to_cpu(raw); + cnv >>= chan->scan_type.shift; + *val = sign_extend32(cnv, realbits - 1); + + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SCALE: + u64 divd, divr, tmp, rem; + + mutex_lock(&ctx->chan_lock); + divd = st->vref_uV; + divr = BIT_ULL(chan_data->gain + realbits - 1) * 1000; + mutex_unlock(&ctx->chan_lock); + + tmp = div64_u64(divd * 1000000000ULL, divr); + *val = div64_u64_rem(tmp, 1000000000ULL, &rem); + *val2 = rem; + + return IIO_VAL_INT_PLUS_NANO; + + case IIO_CHAN_INFO_HARDWAREGAIN: + mutex_lock(&ctx->chan_lock); + *val = ads1263_adc2_gain_avail[chan_data->gain]; + mutex_unlock(&ctx->chan_lock); + return IIO_VAL_INT; + + case IIO_CHAN_INFO_SAMP_FREQ: + mutex_lock(&ctx->chan_lock); + *val = ads1263_adc2_data_rate_avail[chan_data->data_rate]; + mutex_unlock(&ctx->chan_lock); + + return IIO_VAL_INT; + + default: + return -EOPNOTSUPP; + } +} + +static int +ads1263_adc2_read_avail(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, const int **vals, + int *type, int *length, long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + *type = IIO_VAL_INT; + *vals = ads1263_adc2_gain_avail; + *length = ARRAY_SIZE(ads1263_adc2_gain_avail); + return IIO_AVAIL_LIST; + + case IIO_CHAN_INFO_SAMP_FREQ: + *type = IIO_VAL_INT; + *vals = ads1263_adc2_data_rate_avail; + *length = ARRAY_SIZE(ads1263_adc2_data_rate_avail); + return IIO_AVAIL_LIST; + + default: + return -EOPNOTSUPP; + } +} + +static int ads1263_adc2_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long mask) +{ + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct ads1263_adc2_ctx *ctx = st->ctx; + struct ads1263_adc2_channel *chan_data; + unsigned int i; + + chan_data = &ctx->channels[chan->scan_index]; + + switch (mask) { + case IIO_CHAN_INFO_HARDWAREGAIN: + for (i = 0; i < ARRAY_SIZE(ads1263_adc2_gain_avail); i++) { + if (val == ads1263_adc2_gain_avail[i]) + break; + } + if (i == ARRAY_SIZE(ads1263_adc2_gain_avail)) + return -EINVAL; + + mutex_lock(&ctx->chan_lock); + chan_data->gain = i; + mutex_unlock(&ctx->chan_lock); + + return 0; + + case IIO_CHAN_INFO_SAMP_FREQ: + for (i = 0; i < ARRAY_SIZE(ads1263_adc2_data_rate_avail); i++) { + if (val == ads1263_adc2_data_rate_avail[i]) + break; + } + if (i == ARRAY_SIZE(ads1263_adc2_data_rate_avail)) + return -EINVAL; + + mutex_lock(&ctx->chan_lock); + chan_data->data_rate = i; + mutex_unlock(&ctx->chan_lock); + + return 0; + + default: + return -EOPNOTSUPP; + } + + return ads1263_adc2_channel_hot_reload(st, chan); +} + +static int ads1263_adc2_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_CONVDELAY: + return IIO_VAL_INT_PLUS_NANO; + default: + return IIO_VAL_INT_PLUS_MICRO; + } +} + +static const struct iio_info ads1263_adc2_iio_info = { + .read_raw = ads1263_adc2_read_raw, + .read_avail = ads1263_adc2_read_avail, + .write_raw = ads1263_adc2_write_raw, + .write_raw_get_fmt = ads1263_adc2_write_raw_get_fmt, +}; + +static int ads1263_adc2_buffer_preenable(struct iio_dev *indio_dev) +{ + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct ads1263_adc2_ctx *ctx = st->ctx; + struct device *dev = &ctx->adev.dev; + unsigned long i; + int ret; + + ret = pm_runtime_resume_and_get(dev->parent); + if (ret) + return ret; + + i = find_first_bit(indio_dev->active_scan_mask, + iio_get_masklength(indio_dev)); + ret = ctx->enable(ctx, &ctx->channels[i]); + if (ret) + goto out_runtime_autosuspend; + + ret = ctx->start(ctx); + if (ret) + goto out_runtime_autosuspend; + + return 0; + +out_runtime_autosuspend: + pm_runtime_put_autosuspend(dev->parent); + + return ret; +} + +static int ads1263_adc2_buffer_postdisable(struct iio_dev *indio_dev) +{ + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct ads1263_adc2_ctx *ctx = st->ctx; + struct device *dev = &ctx->adev.dev; + + ctx->stop(ctx); + pm_runtime_put_autosuspend(dev->parent); + + return 0; +} + +static const struct iio_buffer_setup_ops ads1263_adc2_buffer_ops = { + .preenable = ads1263_adc2_buffer_preenable, + .postdisable = ads1263_adc2_buffer_postdisable, + .validate_scan_mask = iio_validate_scan_mask_onehot, +}; + +static irqreturn_t ads1263_adc2_trigger_handler(int irq, void *p) +{ + struct iio_poll_func *pf = p; + struct iio_dev *indio_dev = pf->indio_dev; + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct ads1263_adc2_ctx *ctx = st->ctx; + struct { + __be32 conv; + aligned_s64 ts; + } scan = {}; + int ret; + + ret = ctx->read(ctx, &scan.conv); + if (ret) + goto out_notify_done; + + iio_push_to_buffers_with_ts(indio_dev, &scan, sizeof(scan), + pf->timestamp); + +out_notify_done: + iio_trigger_notify_done(indio_dev->trig); + + return IRQ_HANDLED; +} + +static int ads1263_adc2_channels_setup(struct iio_dev *indio_dev) +{ + struct ads1263_adc2 *st = iio_priv(indio_dev); + struct device *dev = &st->ctx->adev.dev; + struct ads1263_adc2_ctx *ctx = st->ctx; + struct iio_chan_spec *chns; + unsigned int i; + + /* Account for the timestamp channel */ + chns = devm_kcalloc(dev, ctx->num_channels + 1, sizeof(*chns), + GFP_KERNEL); + if (!chns) + return -ENOMEM; + + for (i = 0; i < ctx->num_channels; i++) { + guard(mutex)(&ctx->chan_lock); + + ctx->channels[i].refmux = st->refmux; + + chns[i] = ads1263_adc2_iio_voltage_template; + chns[i].scan_index = i; + chns[i].channel = ctx->channels[i].positive_input; + chns[i].channel2 = ctx->channels[i].negative_input; + chns[i].differential = true; + } + + chns[i] = (struct iio_chan_spec) + IIO_CHAN_SOFT_TIMESTAMP(ctx->num_channels - 1); + chns[i].scan_index = i; + + indio_dev->num_channels = ctx->num_channels + 1; + indio_dev->channels = chns; + + return 0; +} + +static int ads1263_adc2_regulator_setup(struct ads1263_adc2 *st) +{ + struct device *dev = &st->ctx->adev.dev; + const char *reg_id, *propname; + u32 refmux = 0; + int ret; + + propname = "ti,refmux"; + ret = device_property_read_u32(dev, propname, &refmux); + if (refmux >= ADS1263_ADC2_REF2_COUNT) + return dev_err_probe(dev, ret, "%s out of range\n", propname); + st->refmux = refmux; + + if (refmux == ADS1263_ADC2_REF2_INTER) { + /* The internal voltage reference is 2.5 V */ + st->vref_uV = 2500000; + return 0; + } + + reg_id = "vref"; + ret = devm_regulator_get_enable_read_voltage(dev, reg_id); + if (ret < 0) + return dev_err_probe(dev, ret, "Failed to get regulator %s\n", + reg_id); + st->vref_uV = ret; + + return 0; +} + +static int ads1263_adc2_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *id) +{ + struct ads1263_adc2_ctx *ctx = + container_of(auxdev, struct ads1263_adc2_ctx, adev); + struct device *dev = &auxdev->dev; + struct iio_dev *indio_dev; + struct ads1263_adc2 *st; + int ret; + + indio_dev = devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st = iio_priv(indio_dev); + st->ctx = ctx; + st->indio_dev = indio_dev; + + ret = ads1263_adc2_regulator_setup(st); + if (ret) + return ret; + + indio_dev->name = (char *)id->driver_data; + indio_dev->modes = INDIO_DIRECT_MODE; + indio_dev->info = &ads1263_adc2_iio_info; + ret = ads1263_adc2_channels_setup(indio_dev); + if (ret) + return ret; + + ret = devm_iio_triggered_buffer_setup(dev, indio_dev, + iio_pollfunc_store_time, + ads1263_adc2_trigger_handler, + &ads1263_adc2_buffer_ops); + if (ret) + return ret; + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct auxiliary_device_id ads1263_adc2_auxiliary_match[] = { + { .name = "ti_ads1262.ads1263_adc2", + .driver_data = (kernel_ulong_t)"ads1263_adc2" }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, ads1263_adc2_auxiliary_match); + +static struct auxiliary_driver ads1263_adc2_driver = { + .name = "ads1263_adc2", + .probe = ads1263_adc2_probe, + .id_table = ads1263_adc2_auxiliary_match, +}; +module_auxiliary_driver(ads1263_adc2_driver); + +MODULE_DESCRIPTION("Texas Instruments ADS1263 auxiliary ADC (ADC2) driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kurt Borja <kuurtb@gmail.com>"); -- 2.54.0 ^ permalink raw reply related [flat|nested] 14+ messages in thread
* Re: [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver 2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja @ 2026-06-12 23:11 ` sashiko-bot 0 siblings, 0 replies; 14+ messages in thread From: sashiko-bot @ 2026-06-12 23:11 UTC (permalink / raw) To: Kurt Borja; +Cc: devicetree, conor+dt, robh Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider: - [High] Probe silently succeeds with corrupted state when `ti,refmux` property is out of bounds. - [High] Unportable C bitfields used for hardware registers break big-endian architectures. - [High] Missing `select AUXILIARY_BUS` in Kconfig causes a build failure. - [High] Heap out-of-bounds access if only the timestamp channel is enabled in buffer mode. - [Medium] Dead code in `ads1263_adc2_write_raw()` silently prevents hardware updates on the fly (hot reload). - [Medium] Permanent `fwnode_handle` resource leak on the error path of `ads1262_aux_device_setup()`. - [Low] Semantic API misuse: releasing direct mode lock instead of buffer mode lock. -- commit 6e165e44885d4dd5616f87d79875344d0f1cd3cb Author: Kurt Borja <kuurtb@gmail.com> iio: adc: Add ti-ads1263-adc2 driver The TI ADS1263 includes an auxiliary, 24-bit, delta-sigma ADC (ADC2) which operates independently of ADC1. This patch adds support for ADC2 as an independent IIO device using the auxiliary bus API. > --- a/drivers/iio/adc/Kconfig > +++ b/drivers/iio/adc/Kconfig > @@ -1809,6 +1809,19 @@ config TI_ADS1262 > This driver can also be built as a module. If so, the module will be > called ti-ads1262. > > +config TI_ADS1263_ADC2 > + tristate "Texas Instruments ADS1263 auxiliary ADC (ADC2) driver" > + depends on TI_ADS1262 > + select AUXILIARY_BUS [Severity: High] Since ti-ads1262.c unconditionally calls auxiliary bus APIs like auxiliary_device_init(), does config TI_ADS1262 also need to select AUXILIARY_BUS? If CONFIG_TI_ADS1262=y and CONFIG_TI_ADS1263_ADC2=n, the auxiliary bus code won't be compiled, which could result in undefined reference linker errors when building the main driver. [ ... ] > --- /dev/null > +++ b/drivers/iio/adc/ti-ads1262.h > @@ -0,0 +1,39 @@ > +/* SPDX-License-Identifier: GPL-2.0-or-later */ > +/* > + * Texas Instruments ADS1262 ADC driver > + * > + * Copyright (C) 2025 Kurt Borja <kuurtb@gmail.com> > + */ > + > +#ifndef _ADS1262_H_ > +#define _ADS1262_H_ > + > +#include <linux/auxiliary_bus.h> > +#include <linux/types.h> > + > +struct ads1263_adc2_channel { > + /* ADC2CFG */ > + u8 gain:3; > + u8 refmux:3; > + u8 data_rate:2; > + > + /* ADC2MUX */ > + u8 negative_input:4; > + u8 positive_input:4; > +}; [Severity: High] Will this structure layout cause issues on big-endian architectures? The memory layout of C bitfields is implementation-defined, and this structure appears to be written directly to the SPI hardware using regmap_bulk_write() in ads1263_adc2_enable(). On big-endian systems, the bits could be packed in reverse order and write invalid configuration data. [ ... ] > --- a/drivers/iio/adc/ti-ads1262.c > +++ b/drivers/iio/adc/ti-ads1262.c [ ... ] > +static int ads1262_aux_device_setup(struct ads1262 *st) > +{ [ ... ] > + adev->dev.parent = dev; > + device_set_node(&adev->dev, no_free_ptr(node)); > + > + ret = auxiliary_device_init(adev); > + if (ret) > + goto out_free_channels; > + > + ret = auxiliary_device_add(adev); > + if (ret) { > + auxiliary_device_uninit(adev); > + return ret; > + } > + > + return devm_add_action_or_reset(dev, ads1262_aux_device_destroy, adev); > + > +out_free_channels: > + kfree(chans); > +out_free_id: > + ida_free(&ads1262_ida, id); > +out_free_adc2: > + kfree(ctx); > +out_node_put: > + fwnode_handle_put(node); [Severity: Medium] Does this error path leak the fwnode reference? When device_set_node() is called above, no_free_ptr(node) unconditionally sets the local node pointer to NULL. If auxiliary_device_init() later fails, jumping to out_node_put will call fwnode_handle_put(NULL). This becomes a no-op, leaving the reference permanently leaked. [ ... ] > --- /dev/null > +++ b/drivers/iio/adc/ti-ads1263-adc2.c [ ... ] > +static int ads1263_adc2_channel_hot_reload(struct ads1263_adc2 *st, > + const struct iio_chan_spec *chan) > +{ > + struct ads1263_adc2_ctx *ctx = st->ctx; > + unsigned long i; > + int ret; > + > + /* Hot reloading is only required on buffer mode */ > + if (!iio_device_try_claim_buffer_mode(st->indio_dev)) > + return 0; > + > + i = find_first_bit(st->indio_dev->active_scan_mask, > + iio_get_masklength(st->indio_dev)); > + if (i != chan->scan_index) { > + iio_device_release_direct(st->indio_dev); [Severity: Low] Should this use iio_device_release_buffer_mode() instead of iio_device_release_direct()? While both macros currently expand to the same internal unlock function, this breaks the IIO API contract which expects symmetric lock and unlock function pairs. > + return 0; > + } [ ... ] > +static int ads1263_adc2_write_raw(struct iio_dev *indio_dev, > + struct iio_chan_spec const *chan, > + int val, int val2, long mask) > +{ [ ... ] > + switch (mask) { > + case IIO_CHAN_INFO_HARDWAREGAIN: > + for (i = 0; i < ARRAY_SIZE(ads1263_adc2_gain_avail); i++) { > + if (val == ads1263_adc2_gain_avail[i]) > + break; > + } > + if (i == ARRAY_SIZE(ads1263_adc2_gain_avail)) > + return -EINVAL; > + > + mutex_lock(&ctx->chan_lock); > + chan_data->gain = i; > + mutex_unlock(&ctx->chan_lock); > + > + return 0; [Severity: Medium] Will ads1263_adc2_channel_hot_reload() at the end of this function ever be reached? Both the IIO_CHAN_INFO_HARDWAREGAIN and IIO_CHAN_INFO_SAMP_FREQ cases use a return 0 instead of break after updating the software state. This causes the function to exit immediately, which silently prevents hardware updates during an active buffer mode. > + > + case IIO_CHAN_INFO_SAMP_FREQ: > + for (i = 0; i < ARRAY_SIZE(ads1263_adc2_data_rate_avail); i++) { > + if (val == ads1263_adc2_data_rate_avail[i]) > + break; > + } > + if (i == ARRAY_SIZE(ads1263_adc2_data_rate_avail)) > + return -EINVAL; > + > + mutex_lock(&ctx->chan_lock); > + chan_data->data_rate = i; > + mutex_unlock(&ctx->chan_lock); > + > + return 0; > + > + default: > + return -EOPNOTSUPP; > + } > + > + return ads1263_adc2_channel_hot_reload(st, chan); > +} [ ... ] > +static int ads1263_adc2_buffer_preenable(struct iio_dev *indio_dev) > +{ > + struct ads1263_adc2 *st = iio_priv(indio_dev); > + struct ads1263_adc2_ctx *ctx = st->ctx; > + struct device *dev = &ctx->adev.dev; > + unsigned long i; > + int ret; > + > + ret = pm_runtime_resume_and_get(dev->parent); > + if (ret) > + return ret; > + > + i = find_first_bit(indio_dev->active_scan_mask, > + iio_get_masklength(indio_dev)); > + ret = ctx->enable(ctx, &ctx->channels[i]); [Severity: High] Is there a potential out-of-bounds array access here if only the timestamp channel is enabled? The iio_validate_scan_mask_onehot function allows a single channel to be active. If the user only enables the timestamp channel, find_first_bit() will return the timestamp channel's index, which is ctx->num_channels. Since ctx->channels is allocated with exactly num_channels elements, accessing ctx->channels[num_channels] reads out-of-bounds memory and passes that data to the hardware via ctx->enable(). > + if (ret) > + goto out_runtime_autosuspend; > + > + ret = ctx->start(ctx); > + if (ret) > + goto out_runtime_autosuspend; > + > + return 0; [ ... ] > +static int ads1263_adc2_regulator_setup(struct ads1263_adc2 *st) > +{ > + struct device *dev = &st->ctx->adev.dev; > + const char *reg_id, *propname; > + u32 refmux = 0; > + int ret; > + > + propname = "ti,refmux"; > + ret = device_property_read_u32(dev, propname, &refmux); > + if (refmux >= ADS1263_ADC2_REF2_COUNT) > + return dev_err_probe(dev, ret, "%s out of range\n", propname); [Severity: High] Does this silently succeed on failure? If the ti,refmux value exceeds ADS1263_ADC2_REF2_COUNT, the preceding device_property_read_u32() was successful, meaning ret is 0. Because ret is 0, dev_err_probe() will return 0, causing the probe to incorrectly succeed with an uninitialized vref_uV value. This could lead to a divide-by-zero during scale calculations. > + st->refmux = refmux; > + > + if (refmux == ADS1263_ADC2_REF2_INTER) { > + /* The internal voltage reference is 2.5 V */ > + st->vref_uV = 2500000; > + return 0; > + } [ ... ] -- Sashiko AI review · https://sashiko.dev/#/patchset/20260612-ads126x-v1-0-894c788d03ed@gmail.com?part=5 ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja ` (4 preceding siblings ...) 2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja @ 2026-06-12 23:50 ` David Lechner 2026-06-13 0:06 ` Kurt Borja 5 siblings, 1 reply; 14+ messages in thread From: David Lechner @ 2026-06-12 23:50 UTC (permalink / raw) To: Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio On 6/12/26 5:46 PM, Kurt Borja wrote: > Hi all, > > This series introduces support for TI ADS1262 and ADS1263 ADCs [1]. > These devices are very similar (if not the same), except ADS1263 > includes a secondary auxiliary ADC. > > The main ADC has quite a few features supported the main driver > (ti-ads1262), including: > > - Power management > - IIO direct and buffer modes > - Channel hot-reloading > - Internal or external oscillator > - Internal or external voltage reference > - Filter configuration > - Sensor bias configuration > - IDAC configuration > - Level-shift voltage configuration > - Manual calibration support > - GPIO controller capabilities > > I plan to add these features to the main driver soon: > > - SPI offload support (38400 SPS turns out to be too high for some > systems) > - User triggered, automatic calibration (Datasheet 9.4.9) > > Additionally, full support for the (less capable) auxiliary ADC is > introduced by the auxiliary ti-ads1263-adc2 driver included in this > series. > > The auxiliary ADC operates almost completely independent of the main > ADC. The only consideration that has to be taken for interoperability is > when reading conversion data in direct mode (Datasheet 9.4.7.1), which > happens only in buffer mode, when multiple channels are enabled. > > When reading data in direct mode, all SPI activity is forbidden between > the data-ready signal and the data retrieval. To achieve this a second > mutex called xfer_lock was introduced to block SPI activity on the > device. > > This is one of the biggest drivers I've developed, so I hope the code > and the comments are self-explainatory. If not, please let me know so I > can clarify them. > > As always, thanks for your reviews and help. Submitting upstream is > always a great learning experience :) > > [1] https://www.ti.com/lit/ds/symlink/ads1263.pdf > > Signed-off-by: Kurt Borja <kuurtb@gmail.com> > --- > Kurt Borja (5): > dt-bindings: iio: adc: Add TI ADS126x ADC family > iio: adc: Add ti-ads1262 driver > iio: adc: ti-ads1262: Add GPIO controller support > iio: adc: ti-ads1262: Add calibration support > iio: adc: Add ti-ads1263-adc2 driver > > .../devicetree/bindings/iio/adc/ti,ads1262.yaml | 308 +++ > .../bindings/iio/adc/ti,ads1263-adc2.yaml | 49 + > MAINTAINERS | 10 + > drivers/iio/adc/Kconfig | 26 + > drivers/iio/adc/Makefile | 2 + > drivers/iio/adc/ti-ads1262.c | 2180 ++++++++++++++++++++ > drivers/iio/adc/ti-ads1262.h | 39 + > drivers/iio/adc/ti-ads1263-adc2.c | 470 +++++ > 8 files changed, 3084 insertions(+) > --- > base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e > change-id: 20251129-ads126x-fb6107505cae > Hi Kurt, I'm currently working on the TI ADS112C14 family of chips which are functionally very similar (although have a bit of a different register map). I have some different ideas for the devicetree bindings that I think will make it a bit more flexible. Given how similar the chips are, I think we will want to align on how we do these (and there was one more similar, and thankfully much simpler, TI ADC driver submitted this week too!). So rather that looking at your stuff too closely yet, I will send what I have next week and we can compare notes then. ^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support 2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner @ 2026-06-13 0:06 ` Kurt Borja 0 siblings, 0 replies; 14+ messages in thread From: Kurt Borja @ 2026-06-13 0:06 UTC (permalink / raw) To: David Lechner, Kurt Borja, Jonathan Cameron, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Linus Walleij, Bartosz Golaszewski Cc: Nuno Sá, Andy Shevchenko, linux-iio, devicetree, linux-kernel, linux-gpio On Fri Jun 12, 2026 at 6:50 PM -05, David Lechner wrote: > On 6/12/26 5:46 PM, Kurt Borja wrote: >> Hi all, >> >> This series introduces support for TI ADS1262 and ADS1263 ADCs [1]. >> These devices are very similar (if not the same), except ADS1263 >> includes a secondary auxiliary ADC. >> >> The main ADC has quite a few features supported the main driver >> (ti-ads1262), including: >> >> - Power management >> - IIO direct and buffer modes >> - Channel hot-reloading >> - Internal or external oscillator >> - Internal or external voltage reference >> - Filter configuration >> - Sensor bias configuration >> - IDAC configuration >> - Level-shift voltage configuration >> - Manual calibration support >> - GPIO controller capabilities >> >> I plan to add these features to the main driver soon: >> >> - SPI offload support (38400 SPS turns out to be too high for some >> systems) >> - User triggered, automatic calibration (Datasheet 9.4.9) >> >> Additionally, full support for the (less capable) auxiliary ADC is >> introduced by the auxiliary ti-ads1263-adc2 driver included in this >> series. >> >> The auxiliary ADC operates almost completely independent of the main >> ADC. The only consideration that has to be taken for interoperability is >> when reading conversion data in direct mode (Datasheet 9.4.7.1), which >> happens only in buffer mode, when multiple channels are enabled. >> >> When reading data in direct mode, all SPI activity is forbidden between >> the data-ready signal and the data retrieval. To achieve this a second >> mutex called xfer_lock was introduced to block SPI activity on the >> device. >> >> This is one of the biggest drivers I've developed, so I hope the code >> and the comments are self-explainatory. If not, please let me know so I >> can clarify them. >> >> As always, thanks for your reviews and help. Submitting upstream is >> always a great learning experience :) >> >> [1] https://www.ti.com/lit/ds/symlink/ads1263.pdf >> >> Signed-off-by: Kurt Borja <kuurtb@gmail.com> >> --- >> Kurt Borja (5): >> dt-bindings: iio: adc: Add TI ADS126x ADC family >> iio: adc: Add ti-ads1262 driver >> iio: adc: ti-ads1262: Add GPIO controller support >> iio: adc: ti-ads1262: Add calibration support >> iio: adc: Add ti-ads1263-adc2 driver >> >> .../devicetree/bindings/iio/adc/ti,ads1262.yaml | 308 +++ >> .../bindings/iio/adc/ti,ads1263-adc2.yaml | 49 + >> MAINTAINERS | 10 + >> drivers/iio/adc/Kconfig | 26 + >> drivers/iio/adc/Makefile | 2 + >> drivers/iio/adc/ti-ads1262.c | 2180 ++++++++++++++++++++ >> drivers/iio/adc/ti-ads1262.h | 39 + >> drivers/iio/adc/ti-ads1263-adc2.c | 470 +++++ >> 8 files changed, 3084 insertions(+) >> --- >> base-commit: ae696dfa47c30016cd429b9db5e70b259b8f509e >> change-id: 20251129-ads126x-fb6107505cae >> > > Hi Kurt, > > I'm currently working on the TI ADS112C14 family of chips which > are functionally very similar (although have a bit of a different > register map). > > I have some different ideas for the devicetree bindings that I > think will make it a bit more flexible. Given how similar the > chips are, I think we will want to align on how we do these (and > there was one more similar, and thankfully much simpler, TI ADC > driver submitted this week too!). > > So rather that looking at your stuff too closely yet, I will send > what I have next week and we can compare notes then. Hi David, Of course! Devicetree bindings were a bit of a pain for me. I definitely want to check your approach, specially on the IDAC stuff. The ADS1262 has many pin configurations. I even considered a pinctrl driver for it, which would have taken care of IDAC, biases, etc. I plan to work on sashiko's feedback this weekend and submit next week. Won't make huge changes, just fix some rookie mistakes... :/ -- Thanks, ~ Kurt ^ permalink raw reply [flat|nested] 14+ messages in thread
end of thread, other threads:[~2026-06-13 6:23 UTC | newest] Thread overview: 14+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-12 22:46 [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support Kurt Borja 2026-06-12 22:46 ` [PATCH 1/5] dt-bindings: iio: adc: Add TI ADS126x ADC family Kurt Borja 2026-06-12 22:53 ` sashiko-bot 2026-06-12 22:46 ` [PATCH 2/5] iio: adc: Add ti-ads1262 driver Kurt Borja 2026-06-12 23:01 ` sashiko-bot 2026-06-12 22:46 ` [PATCH 3/5] iio: adc: ti-ads1262: Add GPIO controller support Kurt Borja 2026-06-12 22:59 ` sashiko-bot 2026-06-13 6:23 ` Kurt Borja 2026-06-12 22:46 ` [PATCH 4/5] iio: adc: ti-ads1262: Add calibration support Kurt Borja 2026-06-12 23:02 ` sashiko-bot 2026-06-12 22:46 ` [PATCH 5/5] iio: adc: Add ti-ads1263-adc2 driver Kurt Borja 2026-06-12 23:11 ` sashiko-bot 2026-06-12 23:50 ` [PATCH 0/5] iio: adc: Add TI ADS126X ADC family support David Lechner 2026-06-13 0:06 ` Kurt Borja
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox