Skip to contentSkip to conversation

iio: adc: Add support for LTC2378 and similar ADCs

PATCH In review Marcelo Schmitt 4 participants 15 messages May 18 – May 20

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

cover iio: adc: Add support for LTC2378 and similar ADCs

From: Marcelo Schmitt <[email protected]>

This patch series adds support for LTC2378 and similar low noise, low power, high speed, successive approximation register (SAR) ADCs. These ADCs are similar among each other, varying mainly on the amount of precision bits, maximum sample rate, and input configuration (either fully differential or pseudo-differential).

The initial support patch enables single-shot sample read with a GPIO connected to the CNV pin.

The second support patch enables high-speed data captures with SPI offloading. The setup is similar to AD4030, with a specialized PWM generator being used both for SPI offload triggering and conversion start signaling.

The last support patch enables running buffered data captures without SPI offloading.

Some structures and variables are introduced earlier to reduce diff in latter patches.

With best regards,
Marcelo

Marcelo Schmitt (4):
dt-bindings: iio: adc: Add ltc2378
iio: adc: Add support for LTC2378-20 and similar ADCs
iio: adc: ltc2378: Enable high-speed data capture
iio: adc: ltc2378: Enable triggered buffer data capture

 .../bindings/iio/adc/adi,ltc2378.yaml         | 131 +++++
 MAINTAINERS                                   |   8 +
 drivers/iio/adc/Kconfig                       |  25 +
 drivers/iio/adc/Makefile                      |   2 +
 drivers/iio/adc/ltc2378-offload-buffer.c      | 297 ++++++++++++
 drivers/iio/adc/ltc2378.c                     | 447 ++++++++++++++++++
 drivers/iio/adc/ltc2378.h                     |  82 ++++
 7 files changed, 992 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
 create mode 100644 drivers/iio/adc/ltc2378-offload-buffer.c
 create mode 100644 drivers/iio/adc/ltc2378.c
 create mode 100644 drivers/iio/adc/ltc2378.h

base-commit: 8678fb54958893818ddeccd05fea560a4e1fc759

Signature · 1 line
2.53.0
#1 lore raw

PATCH 1/4 dt-bindings: iio: adc: Add ltc2378

From: Marcelo Schmitt <[email protected]>

Document how to describe LTC2378-20 and similar ADCs in device tree.

---
 .../bindings/iio/adc/adi,ltc2378.yaml         | 131 ++++++++++++++++++
 MAINTAINERS                                   |   7 +
 2 files changed, 138 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +131 -0
new file mode 100644
index 000000000000..56e59bcd7306
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ltc2378.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices LTC2378 and similar Analog to Digital Converters
+
+maintainers:
+  - Marcelo Schmitt <[email protected]>
+
+description: |
+  Analog Devices LTC2378 series of ADCs.
+  Specifications can be found at:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/233818fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236416fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236418f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236716fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236718f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236816f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236818f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236918fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237016fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237616fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237618fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237620fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237716fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237718fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237720fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237816fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237818fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237820fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237918fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/238016fb.pdf
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    oneOf:
+      - enum:
+          - adi,ltc2338-18
+          - adi,ltc2369-18
+          - adi,ltc2370-16
+          - adi,ltc2379-18
+          - adi,ltc2380-16
+      - items:
+          - enum:
+              - adi,ltc2364-18
+              - adi,ltc2364-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2367-18
+              - adi,ltc2367-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2368-18
+              - adi,ltc2368-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2376-18
+              - adi,ltc2376-20
+              - adi,ltc2376-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2377-18
+              - adi,ltc2377-20
+              - adi,ltc2377-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2378-18
+              - adi,ltc2378-20
+              - adi,ltc2378-16 # fallback compatible
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 100000000
+
+  vdd-supply:
+    description: A 2.5V supply that powers the chip (VDD).
+
+  ovdd-supply:
+    description:
+      A 1.71V to 5.25V supply that sets the logic level for digital interface.
+
+  ref-supply:
+    description:
+      A 2.5V to 5.1V supply for the reference input (REF).
+
+  cnv-gpios:
+    description:
+      When provided, this property indicates the GPIO that is connected to the
+      CNV pin.
+    maxItems: 1
+
+  interrupts:
+    description:
+      Interrupt for signaling the completion of conversion results. The active
+      low signal provided on the BUSY pin asserts when ADC conversions finish.
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+  - ovdd-supply
+  - ref-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        adc@0 {
+            compatible = "adi,ltc2378-20";
+            reg = <0>;
+            spi-max-frequency = <71000000>;
+            vdd-supply = <&supply_2_5V>;
+            ovdd-supply = <&supply_3_3V>;
+            ref-supply = <&supply_5V>;
+            cnv-gpios = <&gpio0 88 GPIO_ACTIVE_HIGH>;
+            interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
+            interrupt-parent = <&gpio>;
+        };
+    };
MAINTAINERS +7 -0
index 0de74503df08..768f56eea476 100644
@@ -15195,6 +15195,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/dac/lltc,ltc1660.yaml
 F:	drivers/iio/dac/ltc1660.c
 
+LTC2378 IIO ADC DRIVER
+M:	Marcelo Schmitt <[email protected]>
+L:	[email protected]
+S:	Supported
+W:	https://ez.analog.com/linux-software-drivers
+F:	Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
+
 LTC2664 IIO DAC DRIVER
 M:	Michael Hennerich <[email protected]>
 M:	Kim Seer Paller <[email protected]>
Signature · 1 line
2.53.0
#2 lore raw

Re: [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Medium] The YAML schema incorrectly models fallback compatible strings by grouping specific and fallback…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] The YAML schema incorrectly models fallback compatible strings by grouping specific and fallback strings into a single-element list, rejecting actual fallback usage in the device tree.
- [Medium] additionalProperties: false is used instead of unevaluatedProperties: false alongside a common schema reference, causing erroneous schema validation failures for valid SPI properties. --

commit e7c5954ec798a9962f4a29e46b3240e39219ef7e
Author: Marcelo Schmitt <[email protected]>

dt-bindings: iio: adc: Add ltc2378

This commit documents the device tree bindings for the Analog Devices LTC2378 and similar ADCs. It introduces a new YAML schema defining the compatible strings, power supplies, interrupts, and SPI peripheral properties.

Quoted 4 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +0 -0
new file mode 100644

[ ... ]

Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml line 50

Quoted 13 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +13 -0
+properties:
+  compatible:
+    oneOf:
+      - enum:
+          - adi,ltc2338-18
+          - adi,ltc2369-18
+          - adi,ltc2370-16
+          - adi,ltc2379-18
+          - adi,ltc2380-16
+      - items:
+          - enum:
+              - adi,ltc2364-18
+              - adi,ltc2364-16 # fallback compatible

Does this definition restrict the compatible property to an array of exactly one string?

Grouping a specific variant and its fallback into a single enum under a one-element items array natively rejects the standard device tree formatting for a specific device paired with a fallback, such as:

compatible = "adi,ltc2364-18", "adi,ltc2364-16";

Should this list declare two discrete elements positionally instead, with the specific variant first, followed by a const for the fallback string? This pattern repeats for the other fallback compatibles below.

[ ... ]

Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml line 111

Quoted 8 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +8 -0
+required:
+  - compatible
+  - reg
+  - vdd-supply
+  - ovdd-supply
+  - ref-supply
+
+additionalProperties: false

Does this introduce a regression where valid SPI peripheral properties are erroneously rejected?

The binding includes a $ref to /schemas/spi/spi-peripheral-props.yaml# at the top of the file, but concludes with additionalProperties: false. Since additionalProperties only considers properties explicitly defined in this specific properties block, device tree nodes using standard SPI properties that are not manually redefined here (like spi-cpha or spi-cs-high) will fail validation.

Should this be unevaluatedProperties: false instead?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#3 lore raw

Re: [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378

In reply to Marcelo Schmitt
Quoted 80 lines · Marcelo Schmitt

From: Marcelo Schmitt <[email protected]>

Document how to describe LTC2378-20 and similar ADCs in device tree.

---
 .../bindings/iio/adc/adi,ltc2378.yaml         | 131 ++++++++++++++++++
 MAINTAINERS                                   |   7 +
 2 files changed, 138 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +63 -0
new file mode 100644
index 000000000000..56e59bcd7306
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ltc2378.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices LTC2378 and similar Analog to Digital Converters
+
+maintainers:
+  - Marcelo Schmitt <[email protected]>
+
+description: |
+  Analog Devices LTC2378 series of ADCs.
+  Specifications can be found at:
+    https://www.analog.com/media/en/technical-documentation/data-sheets/233818fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236416fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236418f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236716fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236718f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236816f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236818f.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/236918fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237016fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237616fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237618fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237620fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237716fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237718fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237720fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237816fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237818fa.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237820fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/237918fb.pdf
+    https://www.analog.com/media/en/technical-documentation/data-sheets/238016fb.pdf
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    oneOf:
+      - enum:
+          - adi,ltc2338-18
+          - adi,ltc2369-18
+          - adi,ltc2370-16
+          - adi,ltc2379-18
+          - adi,ltc2380-16
+      - items:
+          - enum:
+              - adi,ltc2364-18
+              - adi,ltc2364-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2367-18
+              - adi,ltc2367-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2368-18
+              - adi,ltc2368-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2376-18
+              - adi,ltc2376-20
+              - adi,ltc2376-16 # fallback compatible

Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml line 73

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +10 -0
+      - items:
+          - enum:
+              - adi,ltc2377-18
+              - adi,ltc2377-20
+              - adi,ltc2377-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2378-18
+              - adi,ltc2378-20
+              - adi,ltc2378-16 # fallback compatible

All of these are wrong.

Firstly, your "fallback" setups don't work, because they only permit a single compatible. Secondly, if they did work, you still need to define the fallback compatible in isolation for it to be permitted for use when it isn't a fallback.
And thirdly, your driver appears to show that these devices aren't actually compatible at all, given there's unique match data for each one.
+static const struct of_device_id ltc2378_of_match[] = {
+ { .compatible = "adi,ltc2338-18", .data = &ltc2378_chip_info[ID_LTC2338_18] },
+ { .compatible = "adi,ltc2364-16", .data = &ltc2378_chip_info[ID_LTC2364_16] },
+ { .compatible = "adi,ltc2364-18", .data = &ltc2378_chip_info[ID_LTC2364_18] },
+ { .compatible = "adi,ltc2367-16", .data = &ltc2378_chip_info[ID_LTC2367_16] },
+ { .compatible = "adi,ltc2367-18", .data = &ltc2378_chip_info[ID_LTC2367_18] },
+ { .compatible = "adi,ltc2368-16", .data = &ltc2378_chip_info[ID_LTC2368_16] },
+ { .compatible = "adi,ltc2368-18", .data = &ltc2378_chip_info[ID_LTC2368_18] },
+ { .compatible = "adi,ltc2369-18", .data = &ltc2378_chip_info[ID_LTC2369_18] },
+ { .compatible = "adi,ltc2370-16", .data = &ltc2378_chip_info[ID_LTC2370_16] },
+ { .compatible = "adi,ltc2376-16", .data = &ltc2378_chip_info[ID_LTC2376_16] },
+ { .compatible = "adi,ltc2376-18", .data = &ltc2378_chip_info[ID_LTC2376_18] },
+ { .compatible = "adi,ltc2376-20", .data = &ltc2378_chip_info[ID_LTC2376_20] },
+ { .compatible = "adi,ltc2377-16", .data = &ltc2378_chip_info[ID_LTC2377_16] },
+ { .compatible = "adi,ltc2377-18", .data = &ltc2378_chip_info[ID_LTC2377_18] },
+ { .compatible = "adi,ltc2377-20", .data = &ltc2378_chip_info[ID_LTC2377_20] },
+ { .compatible = "adi,ltc2378-16", .data = &ltc2378_chip_info[ID_LTC2378_16] },
+ { .compatible = "adi,ltc2378-18", .data = &ltc2378_chip_info[ID_LTC2378_18] },
+ { .compatible = "adi,ltc2378-20", .data = &ltc2378_chip_info[ID_LTC2378_20] },
+ { .compatible = "adi,ltc2379-18", .data = &ltc2378_chip_info[ID_LTC2379_18] },
+ { .compatible = "adi,ltc2380-16", .data = &ltc2378_chip_info[ID_LTC2380_16] },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ltc2378_of_match);

Why do you have fallbacks here? Is it because a limited set of channels could be used in the fallback case? At the very least, the rationale for fallbacks should be explained.

pw-bot: changes-requested

Quoted 28 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +28 -0
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 100000000
+
+  vdd-supply:
+    description: A 2.5V supply that powers the chip (VDD).
+
+  ovdd-supply:
+    description:
+      A 1.71V to 5.25V supply that sets the logic level for digital interface.
+
+  ref-supply:
+    description:
+      A 2.5V to 5.1V supply for the reference input (REF).
+
+  cnv-gpios:
+    description:
+      When provided, this property indicates the GPIO that is connected to the
+      CNV pin.
+    maxItems: 1
+
+  interrupts:
+    description:
+      Interrupt for signaling the completion of conversion results. The active
+      low signal provided on the BUSY pin asserts when ADC conversions finish.

Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml line 111

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +10 -0
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+  - ovdd-supply
+  - ref-supply
+
+additionalProperties: false

As sashiko pointed out, this is not correct if you want to permit spi properties other than spi-max-frequency (although I could definitely see how this would be correct).

Cheers,
Conor.

Quoted 41 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +20 -0
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        adc@0 {
+            compatible = "adi,ltc2378-20";
+            reg = <0>;
+            spi-max-frequency = <71000000>;
+            vdd-supply = <&supply_2_5V>;
+            ovdd-supply = <&supply_3_3V>;
+            ref-supply = <&supply_5V>;
+            cnv-gpios = <&gpio0 88 GPIO_ACTIVE_HIGH>;
+            interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
+            interrupt-parent = <&gpio>;
+        };
+    };
MAINTAINERS +7 -0
index 0de74503df08..768f56eea476 100644
@@ -15195,6 +15195,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/dac/lltc,ltc1660.yaml
 F:	drivers/iio/dac/ltc1660.c
 
+LTC2378 IIO ADC DRIVER
+M:	Marcelo Schmitt <[email protected]>
+L:	[email protected]
+S:	Supported
+W:	https://ez.analog.com/linux-software-drivers
+F:	Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
+
 LTC2664 IIO DAC DRIVER
 M:	Michael Hennerich <[email protected]>
 M:	Kim Seer Paller <[email protected]>
Signature · 1 line
2.53.0
#4 lore raw

Re: [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378

Hi Conor,

Quoted 7 lines · Conor Dooley
Quoted 6 lines · Marcelo Schmitt

From: Marcelo Schmitt <[email protected]>

Document how to describe LTC2378-20 and similar ADCs in device tree.

---

...

Quoted 46 lines
Quoted 26 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +26 -0
+properties:
+  compatible:
+    oneOf:
+      - enum:
+          - adi,ltc2338-18
+          - adi,ltc2369-18
+          - adi,ltc2370-16
+          - adi,ltc2379-18
+          - adi,ltc2380-16
+      - items:
+          - enum:
+              - adi,ltc2364-18
+              - adi,ltc2364-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2367-18
+              - adi,ltc2367-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2368-18
+              - adi,ltc2368-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2376-18
+              - adi,ltc2376-20
+              - adi,ltc2376-16 # fallback compatible

Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml line 73

Quoted 10 lines
Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml new file +10 -0
+      - items:
+          - enum:
+              - adi,ltc2377-18
+              - adi,ltc2377-20
+              - adi,ltc2377-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2378-18
+              - adi,ltc2378-20
+              - adi,ltc2378-16 # fallback compatible

All of these are wrong.

Firstly, your "fallback" setups don't work, because they only permit a single compatible. Secondly, if they did work, you still need to define the fallback compatible in isolation for it to be permitted for use when it isn't a fallback.
And thirdly, your driver appears to show that these devices aren't actually compatible at all, given there's unique match data for each one.

...

Quoted 5 lines

Why do you have fallbacks here? Is it because a limited set of channels could be used in the fallback case? At the very least, the rationale for fallbacks should be explained.

Some devices can be grouped by main number (e.g. ltc2378-16, -18, -20) and those vary only on the amount of precision bits. I initially thought we could fallback to reading only 16 or 18 bits if a driver compatible with ltc2378-20 is not found. Though, I now realize that won't work because the less significant bits would be missing which would compromise scaling the data to milli volts. Will make this a unique match list (no separate items lists, no fallbacks).

...

Quoted 6 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+
+additionalProperties: false

As sashiko pointed out, this is not correct if you want to permit spi properties other than spi-max-frequency (although I could definitely see how this would be correct).

Ack, switching to 'unevaluatedProperties: false'.

Thanks,
Marcelo

#5 lore raw

PATCH 2/4 iio: adc: Add support for LTC2378-20 and similar ADCs

In reply to Marcelo Schmitt

From: Marcelo Schmitt <[email protected]>

Initial support for LTC2378-20 and similar analog-to-digital converters.

---
The data union for the sample data and the filling of IIO channel scan_type struct could probably have been avoided and postponed. Though, that would probably imply larger git diffs in the buffer patches so I added those earlier to minimize diff size in the buffer patches.

 MAINTAINERS               |   1 +
 drivers/iio/adc/Kconfig   |  12 ++
 drivers/iio/adc/Makefile  |   1 +
 drivers/iio/adc/ltc2378.c | 352 ++++++++++++++++++++++++++++++++++++++
 drivers/iio/adc/ltc2378.h |  48 ++++++
 5 files changed, 414 insertions(+)
 create mode 100644 drivers/iio/adc/ltc2378.c
 create mode 100644 drivers/iio/adc/ltc2378.h
MAINTAINERS +1 -0
index 768f56eea476..d9a772ac599c 100644
@@ -15201,6 +15201,7 @@ L:	[email protected]
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
+F:	drivers/iio/adc/ltc2378*
 
 LTC2664 IIO DAC DRIVER
 M:	Michael Hennerich <[email protected]>
drivers/iio/adc/Kconfig +12 -0
index 8550917226a1..70fec8e3e891 100644
@@ -939,6 +939,18 @@ config LTC2309
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2309.
 
+config LTC2378
+	tristate "Analog Devices LTC2378 ADC driver"
+	depends on SPI
+	depends on GPIOLIB || PWM
+	select IIO_BUFFER
+	help
+	  Say yes here to build support for Analog Devices LTC2378-20 and
+	  similar analog to digital converters.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called ltc2378.
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +1 -0
index 707dd708912f..1814fb78dde3 100644
@@ -81,6 +81,7 @@ obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
 obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
 obj-$(CONFIG_LTC2309) += ltc2309.o
+obj-$(CONFIG_LTC2378) += ltc2378.o
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
drivers/iio/adc/ltc2378.c new file +352 -0

352 lines not on this page · raw message

drivers/iio/adc/ltc2378.h new file +48 -0

48 lines not on this page · raw message

Signature · 1 line
2.53.0
#6 lore raw

Re: [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider: - [High] Incorrect bit shift for 18-bit and 20-bit resolutions causes data corruption. - [High] Missing…

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Incorrect bit shift for 18-bit and 20-bit resolutions causes data corruption.
- [High] Missing wait for conversion completion before SPI read leads to hardware timing violation.
- [High] Regulator error code is overridden, breaking probe deferral.
- [Low] Un-scoped guard declaration inside switch statement causes a compilation error. --

commit fc88b83a84e435cb25c9c503a7fbeb60ddc12e79
Author: Marcelo Schmitt <[email protected]>

iio: adc: Add support for LTC2378-20 and similar ADCs

Initial support for LTC2378-20 and similar analog-to-digital converters.

Quoted 5 lines
drivers/iio/adc/ltc2378.c new file +0 -0
new file mode 100644
index 0000000000000..7916500c470cf

[ ... ]

drivers/iio/adc/ltc2378.c line 150

Quoted 7 lines
drivers/iio/adc/ltc2378.c new file +7 -0
+static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
+{
+	int ret;
+
+	/* Cause a rising edge of CNV to initiate a new ADC conversion */
+	gpiod_set_value_cansleep(st->cnv_gpio, 1);
+	ret = spi_sync_transfer(st->spi, &st->xfer, 1);

The ADC requires a minimum conversion time after the rising edge of the CNV signal before data can be clocked out.

If spi_sync_transfer() is called immediately, could the SPI controller start clocking the SDO line while the ADC is still converting? Should there be an explicit delay here (e.g., using ndelay()) to ensure the conversion is complete?

[ ... ]

drivers/iio/adc/ltc2378.c line 198

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+static int ltc2378_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan,
+			int *val, int *val2, long info)
+{
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	int ret;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);

IIO_DEV_ACQUIRE_DIRECT_MODE() declares a scoped variable using the __cleanup__ attribute. Because it is placed directly under the case label without an enclosing block, its scope extends to the entire switch statement.

If info evaluates to IIO_CHAN_INFO_SCALE, the jump bypasses the variable initialization. Does this cause a compilation error? Wrapping the case block in braces would resolve this.

Quoted 4 lines
drivers/iio/adc/ltc2378.c new file +4 -0
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		ret = ltc2378_channel_single_read(chan, st, val);

[ ... ]

Quoted 8 lines
drivers/iio/adc/ltc2378.c new file +8 -0
+static int ltc2378_probe(struct spi_device *spi)
+{
+	struct iio_chan_spec *ltc2378_chan;
+	struct device *dev = &spi->dev;
+	unsigned int num_iio_chans = 1;
+	struct iio_dev *indio_dev;
+	struct ltc2378_state *st;
+

drivers/iio/adc/ltc2378.c line 249

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+
+	st->ref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
+	if (st->ref_uV < 0)
+		return dev_err_probe(dev, -ENODEV, "failed to read ref regulator\n");

devm_regulator_get_enable_read_voltage() can return -EPROBE_DEFER if the regulator is not yet initialized. By explicitly passing -ENODEV to dev_err_probe() instead of propagating st->ref_uV, does this break the probe deferral mechanism for the regulator?

Quoted 2 lines
drivers/iio/adc/ltc2378.c new file +2 -0
+
+	st->info = spi_get_device_match_data(spi);

[ ... ]

drivers/iio/adc/ltc2378.c line 276

Quoted 9 lines
drivers/iio/adc/ltc2378.c new file +9 -0
+	*ltc2378_chan = (struct iio_chan_spec) {
+		.type = IIO_VOLTAGE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_type = {
+			.format = st->info->out_format,
+			.realbits = st->info->resolution,
+			.storagebits = st->info->resolution > 16 ? 32 : 16,
+			.shift = st->info->resolution > 16 ? 8 : 0,

For ADCs with >16 bits resolution, storagebits is 32 (4 bytes). Because the ADC clocks out data MSB-first, the 18 or 20 bits of data are left-aligned in the 32-bit word.

To properly right-align the data, shouldn't the shift be 32 - realbits (14 for 18-bit, 12 for 20-bit) instead of a hardcoded 8? Does leaving the data misaligned cause sign_extend32() to evaluate the wrong bit as the sign bit later on, corrupting the sample values?

Quoted 3 lines
drivers/iio/adc/ltc2378.c new file +3 -0
+			.endianness = IIO_BE,
+		},
+	};
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=2
maintainer
#7 lore raw

Re: [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs

In reply to Marcelo Schmitt
Quoted 10 lines · On Mon, 18 May 2026 12:21:33 -0300 Marcelo Schmitt…

From: Marcelo Schmitt <[email protected]>

Initial support for LTC2378-20 and similar analog-to-digital converters.

---
The data union for the sample data and the filling of IIO channel scan_type struct could probably have been avoided and postponed. Though, that would probably imply larger git diffs in the buffer patches so I added those earlier to minimize diff size in the buffer patches.

Could we set the SPI word size larger? That would then make normal SPI controllers provide the data in CPU Endian form as well.

I've no idea what percentage of SPI controllers will do both 16 and 32 bit words though but if it simplifies the driver and you don't have a customer right now with a rubbish SPI controller, I'd prefer this route.

Quoted 22 lines
drivers/iio/adc/Makefile +1 -0
index 707dd708912f..1814fb78dde3 100644
@@ -81,6 +81,7 @@ obj-$(CONFIG_LP8788_ADC) += lp8788_adc.o
 obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
 obj-$(CONFIG_LTC2309) += ltc2309.o
+obj-$(CONFIG_LTC2378) += ltc2378.o
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
drivers/iio/adc/ltc2378.c new file +4 -0
new file mode 100644
index 000000000000..7916500c470c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices LTC2378 ADC series driver
+ *

drivers/iio/adc/ltc2378.c line 14

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+ * Copyright (C) 2026 Analog Devices Inc.
+ * Author: Ioan-Daniel Pop <[email protected]>
+ * Author: Marcelo Schmitt <[email protected]>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>

That's a very short list.

dev_printk.h seems to be missing - please check in general against IWYU principles. We shouldn't really be relying on anything included in ltc2378.h either.

Quoted 4 lines
drivers/iio/adc/ltc2378.c new file +4 -0
+
+#include "ltc2378.h"
+
+enum ltc2378_supported_device_ids {

...

drivers/iio/adc/ltc2378.c line 38

Quoted 1 line
drivers/iio/adc/ltc2378.c new file +1 -0
+	ID_LTC2380_16,

Whilst we used to do this enum thing and array we are moving away from it - mostly because it makes it far too easy for people to use it for the wrong things. Names structures are neater and tend to give shorter line lengths.

drivers/iio/adc/ltc2378.c line 46

Quoted 8 lines
drivers/iio/adc/ltc2378.c new file +8 -0
+};
+
+static const struct ltc2378_chip_info ltc2378_chip_info[] = {
+	[ID_LTC2338_18] = {
+		.name = "ltc2338-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},

That is break this up into
static const struct ltc2378_chip_info ltc2338_18_chip_info = { };
etc

drivers/iio/adc/ltc2378.c line 149

Quoted 12 lines
drivers/iio/adc/ltc2378.c new file +11 -0
+	[ID_LTC2364_16] = {
+		.name = "ltc2364-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 
+
+static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
+{
+	int ret;
+
+	/* Cause a rising edge of CNV to initiate a new ADC conversion */
+	gpiod_set_value_cansleep(st->cnv_gpio, 1);

There is a note from sashiko on whether this needs a short delay.

Quoted 8 lines
drivers/iio/adc/ltc2378.c new file +8 -0
+	ret = spi_sync_transfer(st->spi, &st->xfer, 1);
+	gpiod_set_value_cansleep(st->cnv_gpio, 0);
+
+	return ret;
+}
+
+static int ltc2378_channel_single_read(const struct iio_chan_spec *chan,
+				       struct ltc2378_state *st, int *val)

drivers/iio/adc/ltc2378.c line 167

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+{
+	const struct iio_scan_type *scan_type = &chan->scan_type;
+	u32 sample;
+	int ret;
+
+	ret = ltc2378_convert_and_acquire(st);
+	if (ret)
+		return ret;
+
+	if (scan_type->endianness == IIO_BE) {

This smells overly flexible given for now at least we only have IIO_BE. The word size suggestion might resolve this moer simplly.

Quoted 20 lines
drivers/iio/adc/ltc2378.c new file +20 -0
+		if (scan_type->realbits > 16)
+			sample = be32_to_cpu(st->scan.data.sample_buf32_be);
+		else
+			sample = be16_to_cpu(st->scan.data.sample_buf16_be);
+	} else {
+		if (scan_type->realbits > 16)
+			sample = st->scan.data.sample_buf32;
+		else
+			sample = st->scan.data.sample_buf16;
+	}
+
+	sample >>= scan_type->shift;
+
+	if (scan_type->format == IIO_SCAN_FORMAT_SIGNED_INT)
+		*val = sign_extend32(sample, scan_type->realbits - 1);
+	else
+		*val = sample;
+
+	return 0;
+}

drivers/iio/adc/ltc2378.c line 197

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+
+static int ltc2378_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan,
+			int *val, int *val2, long info)
+{
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	int ret;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:

Scope missing. The next bit declares a local variable under the hood.

Quoted 40 lines
drivers/iio/adc/ltc2378.c new file +40 -0
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		ret = ltc2378_channel_single_read(chan, st, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		*val = st->ref_uV / MILLI;
+		/*
+		 * For all LTC2378-like devices, the amount of bits that express
+		 * voltage magnitude depend on the output code format:
+		 * - straight binary: All precision/resolution bits are used.
+		 * - 2's complement: One of the precision bits is used for sign.
+		 */
+		if (st->info->out_format == IIO_SCAN_FORMAT_SIGNED_INT)
+			*val2 = st->info->resolution - 1;
+		else
+			*val2 = st->info->resolution;
+
+		return IIO_VAL_FRACTIONAL_LOG2;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info ltc2378_iio_info = {
+	.read_raw = &ltc2378_read_raw,
+};
+
+static int ltc2378_probe(struct spi_device *spi)
+{
+	struct iio_chan_spec *ltc2378_chan;
+	struct device *dev = &spi->dev;
+	unsigned int num_iio_chans = 1;
+	struct iio_dev *indio_dev;

drivers/iio/adc/ltc2378.c line 247

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+	struct ltc2378_state *st;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+
+	st->ref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");

I missed this but sashiko is better than me :) This eats return value which might be EPROBE_DEFER.

Quoted 7 lines
drivers/iio/adc/ltc2378.c new file +7 -0
+	if (st->ref_uV < 0)
+		return dev_err_probe(dev, -ENODEV, "failed to read ref regulator\n");
+
+	st->info = spi_get_device_match_data(spi);
+	if (!st->info)
+		return -EINVAL;
+

drivers/iio/adc/ltc2378.c line 264

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+	indio_dev->name = st->info->name;
+	indio_dev->info = &ltc2378_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	st->cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
+	if (st->cnv_gpio && IS_ERR(st->cnv_gpio))
+		return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
+				     "failed to get CNV GPIO");
+
+	ltc2378_chan = devm_kzalloc(&spi->dev, sizeof(struct iio_chan_spec), GFP_KERNEL);

Embed it in the iio_priv(). Saves us one allocation and that has effectively the same lifetime as this.

drivers/iio/adc/ltc2378.c line 276

Quoted 12 lines
drivers/iio/adc/ltc2378.c new file +12 -0
+	if (!ltc2378_chan)
+		return -ENOMEM;
+
+	*ltc2378_chan = (struct iio_chan_spec) {
+		.type = IIO_VOLTAGE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_type = {
+			.format = st->info->out_format,
+			.realbits = st->info->resolution,
+			.storagebits = st->info->resolution > 16 ? 32 : 16,
+			.shift = st->info->resolution > 16 ? 8 : 0,

There is a comment from Sashiko on this. It's definitely suspicious given the different resolutions + sign extend.

drivers/iio/adc/ltc2378.c line 279

Quoted 3 lines
drivers/iio/adc/ltc2378.c new file +3 -0
+			.endianness = IIO_BE,
+		},
+	};

There are a reasonable number of options I suppose though my gut instinct would still be to have them all as data and point to that from the structure
in st->info. Little bit of bloat but puts avoids a level of indirection
in how this is being created. Maybe this is worth it given the multiple modes. Not sure yet.

Quoted 20 lines
drivers/iio/adc/ltc2378.c new file +20 -0
+	st->xfer.rx_buf = &st->scan.data;
+	st->xfer.len = BITS_TO_BYTES(ltc2378_chan->scan_type.storagebits);
+
+	indio_dev->channels = ltc2378_chan;
+	indio_dev->num_channels = num_iio_chans;
+
+	return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id ltc2378_of_match[] = {
+	{ .compatible = "adi,ltc2338-18", .data = &ltc2378_chip_info[ID_LTC2338_18] },
+	{ .compatible = "adi,ltc2364-16", .data = &ltc2378_chip_info[ID_LTC2364_16] },
+	{ .compatible = "adi,ltc2364-18", .data = &ltc2378_chip_info[ID_LTC2364_18] },
+	{ .compatible = "adi,ltc2367-16", .data = &ltc2378_chip_info[ID_LTC2367_16] },
+	{ .compatible = "adi,ltc2367-18", .data = &ltc2378_chip_info[ID_LTC2367_18] },
+	{ .compatible = "adi,ltc2368-16", .data = &ltc2378_chip_info[ID_LTC2368_16] },
+	{ .compatible = "adi,ltc2368-18", .data = &ltc2378_chip_info[ID_LTC2368_18] },
+	{ .compatible = "adi,ltc2369-18", .data = &ltc2378_chip_info[ID_LTC2369_18] },
+	{ .compatible = "adi,ltc2370-16", .data = &ltc2378_chip_info[ID_LTC2370_16] },
+	{ .compatible = "adi,ltc2376-16", .data = &ltc2378_chip_info[ID_LTC2376_16] },

drivers/iio/adc/ltc2378.c line 309

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+	{ .compatible = "adi,ltc2376-18", .data = &ltc2378_chip_info[ID_LTC2376_18] },
+	{ .compatible = "adi,ltc2376-20", .data = &ltc2378_chip_info[ID_LTC2376_20] },
+	{ .compatible = "adi,ltc2377-16", .data = &ltc2378_chip_info[ID_LTC2377_16] },
+	{ .compatible = "adi,ltc2377-18", .data = &ltc2378_chip_info[ID_LTC2377_18] },
+	{ .compatible = "adi,ltc2377-20", .data = &ltc2378_chip_info[ID_LTC2377_20] },
+	{ .compatible = "adi,ltc2378-16", .data = &ltc2378_chip_info[ID_LTC2378_16] },
+	{ .compatible = "adi,ltc2378-18", .data = &ltc2378_chip_info[ID_LTC2378_18] },
+	{ .compatible = "adi,ltc2378-20", .data = &ltc2378_chip_info[ID_LTC2378_20] },
+	{ .compatible = "adi,ltc2379-18", .data = &ltc2378_chip_info[ID_LTC2379_18] },
+	{ .compatible = "adi,ltc2380-16", .data = &ltc2378_chip_info[ID_LTC2380_16] },

As above, split the array into separate named structure instances.

drivers/iio/adc/ltc2378.c

Quoted 1 line
drivers/iio/adc/ltc2378.c new file +1 -0
+	{ },

No trailing comma on a 'terminator' like this.

Quoted 15 lines
drivers/iio/adc/ltc2378.c new file +15 -0
+};
+MODULE_DEVICE_TABLE(of, ltc2378_of_match);
+
+static const struct spi_device_id ltc2378_spi_id[] = {
+	{ "ltc2338-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2338_18] },
+	{ "ltc2364-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2364_16] },
+	{ "ltc2364-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2364_18] },
+	{ "ltc2367-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2367_16] },
+	{ "ltc2367-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2367_18] },
+	{ "ltc2368-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2368_16] },
+	{ "ltc2368-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2368_18] },
+	{ "ltc2369-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2369_18] },
+	{ "ltc2370-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2370_16] },
+	{ "ltc2376-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2376_16] },
+	{ "ltc2376-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2376_18] },

drivers/iio/adc/ltc2378.c line 335

Quoted 10 lines
drivers/iio/adc/ltc2378.c new file +10 -0
+	{ "ltc2376-20", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2376_20] },
+	{ "ltc2377-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2377_16] },
+	{ "ltc2377-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2377_18] },
+	{ "ltc2377-20", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2377_20] },
+	{ "ltc2378-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2378_16] },
+	{ "ltc2378-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2378_18] },
+	{ "ltc2378-20", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2378_20] },
+	{ "ltc2379-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2379_18] },
+	{ "ltc2380-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2380_16] },
+	{ },

No trailing comma, plus for new code lets use named intializers to make the work Uwe is doing easier in the long run.

Quoted 22 lines
drivers/iio/adc/ltc2378.c new file +2 -0
+};
+MODULE_DEVICE_TABLE(spi, ltc2378_spi_id);
drivers/iio/adc/ltc2378.h new file +13 -0
new file mode 100644
index 000000000000..515f7e8a4f2e
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Analog Devices LTC2378 and similar ADCs common definitions and properties
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <[email protected]>
+ */
+
+#ifndef __DRIVERS_IIO_ADC_LTC2378_H__
+#define __DRIVERS_IIO_ADC_LTC2378_H__
+
+#include <linux/iio/iio.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>

drivers/iio/adc/ltc2378.h line 23

Quoted 10 lines
drivers/iio/adc/ltc2378.h new file +10 -0
+#include <linux/units.h>
+
+#define LTC2378_TDSDOBUSYL_NS		5
+#define LTC2378_TBUSYLH_NS		13
+#define LTC2378_TCNV_HIGH_NS		20
+
+struct ltc2378_chip_info {
+	const char *name;
+	int resolution;
+	const char out_format;

This feels like it should have some documentation. I'm going to guess it is really bipolar / unipolar? If so I'd have a bool bipolar flag.

Using a format char is a bit too flexible for my liking.

Quoted 25 lines
drivers/iio/adc/ltc2378.h new file +25 -0
+};
+
+struct ltc2378_state {
+	const struct ltc2378_chip_info *info;
+	struct gpio_desc *cnv_gpio;
+	struct spi_device *spi;
+	struct spi_transfer xfer;
+	int ref_uV;
+
+	/*
+	 * DMA (thus cache coherency maintenance) requires the
+	 * transfer buffers to live in their own cache lines.
+	 */
+	struct {
+		union {
+			__be16 sample_buf16_be;
+			__be32 sample_buf32_be;
+			u16 sample_buf16;
+			u32 sample_buf32;
+		} data;
+		aligned_s64 timestamp;
+	} scan __aligned(IIO_DMA_MINALIGN);
+};
+
+#endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
#8 lore raw

PATCH 3/4 iio: adc: ltc2378: Enable high-speed data capture

In reply to Marcelo Schmitt

From: Marcelo Schmitt <[email protected]>

Make use of SPI transfer offloading to speed up data capture, enabling data acquisition at faster sample rates (up to 2 MSPS).

---
 drivers/iio/adc/Kconfig                  |  12 +
 drivers/iio/adc/Makefile                 |   1 +
 drivers/iio/adc/ltc2378-offload-buffer.c | 297 +++++++++++++++++++++++
 drivers/iio/adc/ltc2378.c                |  62 +++++
 drivers/iio/adc/ltc2378.h                |  34 +++
 5 files changed, 406 insertions(+)
 create mode 100644 drivers/iio/adc/ltc2378-offload-buffer.c
drivers/iio/adc/Kconfig +12 -0
index 70fec8e3e891..b5368ee783f7 100644
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
 	  similar analog to digital converters.
@@ -951,6 +952,17 @@ config LTC2378
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2378.
 
+config LTC2378_OFFLOAD_BUFFER
+	bool "Offloaded data capture with LTC2378"
+	depends on SPI && LTC2378
+	depends on SPI_OFFLOAD=y
+	depends on PWM=y
+	depends on SPI_OFFLOAD_TRIGGER_PWM=y
+	depends on IIO_BUFFER_DMA=y
+	depends on IIO_BUFFER_DMAENGINE=y
+	help
+	  Say yes here to build support for high speed data capture with LTC2378
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +1 -0
index 1814fb78dde3..2fa5dce0ceea 100644
@@ -82,6 +82,7 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
 obj-$(CONFIG_LTC2309) += ltc2309.o
 obj-$(CONFIG_LTC2378) += ltc2378.o
+obj-$(CONFIG_LTC2378_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
drivers/iio/adc/ltc2378-offload-buffer.c new file +297 -0

297 lines not on this page · raw message

drivers/iio/adc/ltc2378.c +62 -0

184 lines not on this page · raw message

drivers/iio/adc/ltc2378.h +34 -0
index 515f7e8a4f2e..17c329b18333 100644
@@ -9,7 +9,14 @@
 #define __DRIVERS_IIO_ADC_LTC2378_H__
 
 #include <linux/iio/iio.h>
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+#include <linux/pwm.h>
+#endif
 #include <linux/spi/spi.h>
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#endif
 #include <linux/types.h>
 #include <linux/units.h>
 
@@ -20,6 +27,8 @@
 struct ltc2378_chip_info {
 	const char *name;
 	int resolution;
+	unsigned int max_sample_rate_hz;
+	unsigned int tconv_ns;
 	const char out_format;
 };
 
@@ -29,6 +38,16 @@ struct ltc2378_state {
 	struct spi_device *spi;
 	struct spi_transfer xfer;
 	int ref_uV;
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+	unsigned int cnv_Hz;
+	struct pwm_waveform cnv_wf;
+	struct spi_offload *offload;
+	struct spi_offload_trigger *offload_trigger;
+	struct spi_message offload_msg;
+	struct spi_transfer offload_xfer;
+	struct spi_offload_trigger_config offload_trigger_config;
+	struct pwm_device *cnv_trigger;
+#endif
 
 	/*
 	 * DMA (thus cache coherency maintenance) requires the
@@ -45,4 +64,19 @@ struct ltc2378_state {
 	} scan __aligned(IIO_DMA_MINALIGN);
 };
 
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+extern const struct attribute_group ltc2378_offload_attribute_group;
+#endif
+
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi);
+#else
+static inline int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev,
+					       struct spi_device *spi)
+{
+	might_sleep();
+	return -ENODEV;
+}
+#endif
+
 #endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
Signature · 1 line
2.53.0
#9 lore raw

Re: [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider: - [High] Missing early return in `ltc2378_offload_buffer_setup()` causes error pointer dereference and…

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [High] Missing early return in ltc2378_offload_buffer_setup() causes error pointer dereference and breaks fallback mode.
- [High] Unconditional exposure of sysfs attributes causes a NULL pointer dereference on devices without SPI offload.
- [High] Unbounded do...while loops querying hardware limits can cause an infinite loop and system hang.
- [High] Hardcoded 4-byte SPI offload transfers conflict with 16-bit IIO buffer allocations, causing data corruption.
- [Medium] Kconfig dependency mismatch prevents enabling the offload feature when the driver is compiled as a module. --

commit 87ed4789078a187c9262ff17873220fb5108858f
Author: Marcelo Schmitt <[email protected]>

iio: adc: ltc2378: Enable high-speed data capture

Makes use of SPI transfer offloading to speed up data capture, enabling data acquisition at faster sample rates (up to 2 MSPS).

Quoted 4 lines
drivers/iio/adc/Kconfig +0 -0
index 70fec8e3e891f..b5368ee783f7b 100644

[ ... ]

drivers/iio/adc/Kconfig config LTC2378 · line 957

Quoted 3 lines
drivers/iio/adc/Kconfig +3 -0
+config LTC2378_OFFLOAD_BUFFER
+	bool "Offloaded data capture with LTC2378"
+	depends on SPI && LTC2378

LTC2378_OFFLOAD_BUFFER is a bool that depends on LTC2378. Since LTC2378 is a tristate, if a user compiles the driver as a module, won't Kconfig restrict this dependent boolean to n? Does this prevent enabling the offload feature entirely unless the driver is built-in?

Quoted 5 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +0 -0
new file mode 100644
index 0000000000000..ed09f9a55f93e

[ ... ]

Quoted 2 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +2 -0
+static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
+{

[ ... ]

drivers/iio/adc/ltc2378-offload-buffer.c line 75

Quoted 7 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +7 -0
+	do {
+		cnv_wf.duty_length_ns = target;
+		ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
+		if (ret)
+			return ret;
+		target += 10;  /* Increment by PWM duty cycle period */
+	} while (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);

If the physical PWM hardware clamps the duty cycle below LTC2378_TCNV_HIGH_NS due to hardware limits or resolution, will this loop increment target indefinitely and stall the CPU? Should there be a maximum iteration limit?

This same concern applies to the SPI trigger offset negotiation loop located just below this one.

[ ... ]

drivers/iio/adc/ltc2378-offload-buffer.c line 171

Quoted 12 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +12 -0
+static int ltc2378_prepare_offload_message(struct device *dev,
+					   struct ltc2378_state *st)
+{
+	st->offload_xfer.bits_per_word = st->info->resolution;
+	/*
+	 * Ideally, we would ask the offload provider what data word sizes are
+	 * supported so we could use smaller words for less precise ADCs.
+	 * Though, the currently available SPI offloading hardware only supports
+	 * pushing 32-bit sized data elements to DMA memory. Because of that,
+	 * we hardcode set 4 byte sized transfers.
+	 */
+	st->offload_xfer.len = 4;

Since st->offload_xfer.len is hardcoded to 4 bytes here, but ltc2378_chan->scan_type.storagebits remains at 16 for ADCs with 16-bit or lower resolution, does this cause a mismatch? Will the IIO core allocate DMA buffers assuming 2 bytes per sample while the DMA hardware pushes 4 bytes, potentially leading to buffer overruns and data corruption?

[ ... ]

drivers/iio/adc/ltc2378-offload-buffer.c line 271

Quoted 12 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +12 -0
+int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi)
+{
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	struct device *dev = &spi->dev;
+	int ret;
+
+	st->offload = devm_spi_offload_get(dev, spi, &ltc2378_offload_config);
+	ret = PTR_ERR_OR_ZERO(st->offload);
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to get offload\n");
+
+	ret = ltc2378_spi_offload_setup(indio_dev, st);

If devm_spi_offload_get() returns -ENODEV, this check bypasses the dev_err_probe() call but continues to ltc2378_spi_offload_setup() without returning.

Could this pass an ERR_PTR(-ENODEV) via st->offload to devm_spi_offload_trigger_get(), causing a panic or breaking ltc2378_probe()'s intended fallback to non-offloaded direct mode?

Quoted 4 lines
drivers/iio/adc/ltc2378.c +0 -0
index 7916500c470cf..fdbe919d45d55 100644

[ ... ]

drivers/iio/adc/ltc2378.c static int ltc2378_read_raw(struct iio_dev *indio_dev, · line 273

Quoted 9 lines
drivers/iio/adc/ltc2378.c +3 -0
@@ -226,6 +266,9 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
 }
 
 static const struct iio_info ltc2378_iio_info = {
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+	.attrs = &ltc2378_offload_attribute_group,
+#endif
 	.read_raw = &ltc2378_read_raw,
 };

If a device probes successfully without SPI offload using standard direct mode, st->cnv_trigger remains uninitialized. Since these attributes are statically assigned without an is_visible callback, they remain exposed to userspace.

Could writing to sampling_frequency in sysfs trigger a NULL pointer dereference when ltc2378_update_conversion_rate() passes the uninitialized st->cnv_trigger to pwm_round_waveform_might_sleep()?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
maintainer
#10 lore raw

Re: [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture

In reply to Marcelo Schmitt
Quoted 6 lines · On Mon, 18 May 2026 12:22:03 -0300 Marcelo Schmitt…

From: Marcelo Schmitt <[email protected]>

Make use of SPI transfer offloading to speed up data capture, enabling data acquisition at faster sample rates (up to 2 MSPS).

Various comments inline. Some from me, some from sashiko (with my comments on top)

Thanks,

Jonathan

Quoted 23 lines
---
 drivers/iio/adc/Kconfig                  |  12 +
 drivers/iio/adc/Makefile                 |   1 +
 drivers/iio/adc/ltc2378-offload-buffer.c | 297 +++++++++++++++++++++++
 drivers/iio/adc/ltc2378.c                |  62 +++++
 drivers/iio/adc/ltc2378.h                |  34 +++
 5 files changed, 406 insertions(+)
 create mode 100644 drivers/iio/adc/ltc2378-offload-buffer.c
drivers/iio/adc/Kconfig +1 -0
index 70fec8e3e891..b5368ee783f7 100644
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
 	  similar analog to digital converters.
@@ -951,6 +952,17 @@ config LTC2378
 	  This driver can also be built as a module. If so, the module will

drivers/iio/adc/Kconfig config LTC2378 · line 962

Quoted 10 lines
drivers/iio/adc/Kconfig +8 -0
 	  be called ltc2378.
 
+config LTC2378_OFFLOAD_BUFFER
+	bool "Offloaded data capture with LTC2378"
+	depends on SPI && LTC2378
+	depends on SPI_OFFLOAD=y
+	depends on PWM=y
+	depends on SPI_OFFLOAD_TRIGGER_PWM=y
+	depends on IIO_BUFFER_DMA=y
+	depends on IIO_BUFFER_DMAENGINE=y

why do all these have to be built in? In general I think we need to jusitfy why this driver needs to work on systems with out this stuff being built. I.e. why do we need the separate optional file at all?

Quoted 11 lines
drivers/iio/adc/Kconfig +3 -0
+	help
+	  Say yes here to build support for high speed data capture with LTC2378
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +0 -0
index 1814fb78dde3..2fa5dce0ceea 100644

drivers/iio/adc/Makefile obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o · line 85

Quoted 4 lines
drivers/iio/adc/Makefile +1 -0
@@ -82,6 +82,7 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
 obj-$(CONFIG_LTC2309) += ltc2309.o
 obj-$(CONFIG_LTC2378) += ltc2378.o
+obj-$(CONFIG_LTC2378_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o

Hmm. This is odd. The driver can be modular but not this and it's build seperately rather than as part of the module. I think these bit needs a redesign. We definitely want this extra support to be part of the module. See how something like the ADIS IMU library in iio/imu/Makefile is done.

Quoted 11 lines
drivers/iio/adc/Makefile +0 -0
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
drivers/iio/adc/ltc2378-offload-buffer.c new file +2 -0
new file mode 100644
index 000000000000..ed09f9a55f93
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*

drivers/iio/adc/ltc2378-offload-buffer.c line 12

Quoted 10 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +10 -0
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <[email protected]>
+ */
+
+#include <linux/err.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/types.h>

We are somewhat standarizing on having the iio headers in their own bock after the more generic other ones you have here.

Quoted 45 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +45 -0
+#include <linux/kstrtox.h>
+#include <linux/limits.h>
+#include <linux/linkage.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/pwm.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#include <linux/time64.h>
+
+#include "ltc2378.h"
+
+/*
+ * SPI offload wiring schema
+ *
+ *     +-------------+         +-------------+
+ *     |         CNV |<-----+--| GPIO        |
+ *     |             |      +--| PWM0        |
+ *     |             |         |             |
+ *     |             |      +--| PWM1        |
+ *     |             |      |  +-------------+
+ *     |             |      +->| TRIGGER     |
+ *     |             |         |             |
+ *     |     ADC     |         |    SPI      |
+ *     |             |         | controller  |
+ *     |             |         |             |
+ *     |         SDI |<--------| SDO         |
+ *     |         SDO |-------->| SDI         |
+ *     |        SCLK |<--------| SCLK        |
+ *     +-------------+         +-------------+
+ *
+ */
+static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
+{
+	struct spi_offload_trigger_config *config = &st->offload_trigger_config;
+	unsigned int min_read_offset, offload_period_ns;
+	struct pwm_waveform cnv_wf = { };
+	u64 target = LTC2378_TCNV_HIGH_NS;
+	u64 offload_offset_ns;
+	int ret;
+
+	if (freq_Hz == 0)
+		return -EINVAL;
+

drivers/iio/adc/ltc2378-offload-buffer.c line 67

Quoted 10 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +10 -0
+	if (freq_Hz < 1 || freq_Hz > st->info->max_sample_rate_hz)
+		return -ERANGE;
+
+	/* Configure CNV PWM waveform */
+	cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_Hz);
+
+	/*
+	 * Ensure CNV high time meets minimum requirement (20ns).
+	 * The PWM hardware may round the duty cycle, so iterate
+	 * until we get at least the minimum required high time.

wrap is a bit short. I'd go nearer the 80 char standard.

Quoted 32 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +32 -0
+	 */
+	do {
+		cnv_wf.duty_length_ns = target;
+		ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
+		if (ret)
+			return ret;
+		target += 10;  /* Increment by PWM duty cycle period */
+	} while (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);
+
+	/*
+	 * Configure SPI offload PWM trigger.
+	 * The trigger should fire after tBUSYLH + tCONV + tDSDOBUSYL.
+	 * Minimum time needed: TBUSYLH (13ns) + TCONV (part-specific) + TDSDOBUSYL (5ns)
+	 *
+	 * Use the same period as CNV PWM to avoid timing issues.
+	 * Convert back from period to frequency for the SPI offload API.
+	 */
+	offload_period_ns = cnv_wf.period_length_ns;
+	config->periodic.frequency_hz = DIV_ROUND_UP(HZ_PER_GHZ, offload_period_ns);
+	min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS;
+	offload_offset_ns = min_read_offset;
+	do {
+		config->periodic.offset_ns = offload_offset_ns;
+		ret = spi_offload_trigger_validate(st->offload_trigger, config);
+		if (ret)
+			return ret;
+		offload_offset_ns += 10;
+	} while (config->periodic.offset_ns < min_read_offset);
+
+	st->cnv_wf = cnv_wf;
+	st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns);
+

drivers/iio/adc/ltc2378-offload-buffer.c line 152

Quoted 10 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +9 -0
+	return 0;
+}
 
+
+static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0);
+
+static struct attribute *ltc2378_offload_attributes[] = {
+	&iio_dev_attr_sampling_frequency.dev_attr.attr,
+	&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+	NULL,

If this was staying, no trailing comma.

drivers/iio/adc/ltc2378-offload-buffer.c line 155

Quoted 3 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +3 -0
+};
+
+const struct attribute_group ltc2378_offload_attribute_group = {

These are standard properties - do them via the chan_spec and read_raw() read_avail()

Quoted 7 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +7 -0
+	.attrs = ltc2378_offload_attributes,
+};
+EXPORT_SYMBOL_NS_GPL(ltc2378_offload_attribute_group, "IIO_LTC2378");
+
+static int ltc2378_prepare_offload_message(struct device *dev,
+					   struct ltc2378_state *st)
+{

drivers/iio/adc/ltc2378-offload-buffer.c line 172

Quoted 10 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +10 -0
+	st->offload_xfer.bits_per_word = st->info->resolution;
+	/*
+	 * Ideally, we would ask the offload provider what data word sizes are
+	 * supported so we could use smaller words for less precise ADCs.
+	 * Though, the currently available SPI offloading hardware only supports
+	 * pushing 32-bit sized data elements to DMA memory. Because of that,
+	 * we hardcode set 4 byte sized transfers.
+	 */
+	st->offload_xfer.len = 4;
+	st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;

There is a question form sashiko on whether the channel spec description for smaller resolution parts is correct as you don't update storage bits and it might be 16.

Quoted 10 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +9 -0
+
+	/* Initialize message with offload */
+	spi_message_init_with_transfers(&st->offload_msg, &st->offload_xfer, 1);
+	st->offload_msg.offload = st->offload;
+
+	return devm_spi_optimize_message(dev, st->spi, &st->offload_msg);
+}
 
+
+int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi)

drivers/iio/adc/ltc2378-offload-buffer.c line 189

Quoted 10 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +10 -0
+{
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	struct device *dev = &spi->dev;
+	int ret;
+
+	st->offload = devm_spi_offload_get(dev, spi, &ltc2378_offload_config);
+	ret = PTR_ERR_OR_ZERO(st->offload);
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to get offload\n");
+

If it was -ENODEV, why does the rest make sense? Add a comment if it does.

drivers/iio/adc/ltc2378-offload-buffer.c line 283

Quoted 13 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +13 -0
+	ret = ltc2378_spi_offload_setup(indio_dev, st);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to setup SPI offload\n");
+
+	ret = ltc2378_pwm_get(st);
+	if (ret)
+		return ret;
+
+	/*
+	 * Start with a slower sampling rate so there is some room for
+	 * adjusting the sampling frequency without hitting the maximum
+	 * conversion rate.

Not sure of that logic. Can just adjust it down if we started with maximum.

It's a random choice so I wouldn't bother justifying it ;)

Quoted 22 lines
drivers/iio/adc/ltc2378-offload-buffer.c new file +14 -0
+	 */
+	ret = ltc2378_update_conversion_rate(st, st->info->max_sample_rate_hz >> 4);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to sampling frequency\n");
+
+	ret = ltc2378_prepare_offload_message(&spi->dev, st);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to optimize SPI message\n");
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ltc2378_offload_buffer_setup, "IIO_LTC2378");
+
+MODULE_IMPORT_NS("IIO_LTC2378");
drivers/iio/adc/ltc2378.c +0 -0
index 7916500c470c..fdbe919d45d5 100644
 
 	[ID_LTC2378_16] = {
 		.name = "ltc2378-16",
 		.resolution = 16,

drivers/iio/adc/ltc2378.c static const struct ltc2378_chip_info ltc2378_chip_info[] = { · line 159

Quoted 10 lines
drivers/iio/adc/ltc2378.c +4 -0
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 527,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2378_18] = {
 		.name = "ltc2378-18",
 		.resolution = 18,
+		.max_sample_rate_hz = HZ_PER_MHZ,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+		.tconv_ns = 527,

Keep ordering the same.

Quoted 24 lines
drivers/iio/adc/ltc2378.c +6 -0
 	},
 	[ID_LTC2378_20] = {
 		.name = "ltc2378-20",
 		.resolution = 20,
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 675,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2379_18] = {
 		.name = "ltc2379-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 1600 * HZ_PER_KHZ,
+		.tconv_ns = 412,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2380_16] = {
 		.name = "ltc2380-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 2 * HZ_PER_MHZ,
+		.tconv_ns = 322,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 };

drivers/iio/adc/ltc2378.c static int ltc2378_read_raw(struct iio_dev *indio_dev, · line 269

Quoted 4 lines
drivers/iio/adc/ltc2378.c +1 -0
@@ -226,6 +266,9 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
 }
 
 static const struct iio_info ltc2378_iio_info = {
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER

Can we stub that in the header? Might be a little fiddly. Basic rule is no ifdefs in c files if we can avoid it as they are a paint to read.

Proabbly wants an is_visible anyway in case we get this built but not useable due to lack of spi offload hardware.

Quoted 14 lines
drivers/iio/adc/ltc2378.c +3 -0
+	.attrs = &ltc2378_offload_attribute_group,
+#endif
 	.read_raw = &ltc2378_read_raw,
 };
 
@@ -236,6 +279,7 @@ static int ltc2378_probe(struct spi_device *spi)
 	unsigned int num_iio_chans = 1;
 	struct iio_dev *indio_dev;
 	struct ltc2378_state *st;
+	int ret;
 
 	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
 	if (!indio_dev)

drivers/iio/adc/ltc2378.c static int ltc2378_probe(struct spi_device *spi) · line 330

Quoted 7 lines
drivers/iio/adc/ltc2378.c +4 -0
@@ -280,6 +324,23 @@ static int ltc2378_probe(struct spi_device *spi)
 	st->xfer.rx_buf = &st->scan.data;
 	st->xfer.len = BITS_TO_BYTES(ltc2378_chan->scan_type.storagebits);
 
+	ret = ltc2378_offload_buffer_setup(indio_dev, spi);
+	if (ret == -ENODEV) {
+		/* SPI offloading is unavailable. Fall back to triggered buffer. */
+		dev_notice(dev, "buffered data capture not supported\n");

Is that not obvious for other reasons? Demote to dev_dbg() probably.

drivers/iio/adc/ltc2378.c static int ltc2378_probe(struct spi_device *spi) · line 336

Quoted 6 lines
drivers/iio/adc/ltc2378.c +6 -0
+	} else if (ret) {
+		return dev_err_probe(dev, ret, "error on SPI offload setup\n");
+	} else {
+		/*
+		 * Currently, the available offload hardware + DMA configuration
+		 * only supports pushing data to IIO buffers in CPU endianness.

If it's cpu endian that implies a larger word size. Can we just encode that in the request and not worry about it?

drivers/iio/adc/ltc2378.c static int ltc2378_probe(struct spi_device *spi) · line 341

Quoted 5 lines
drivers/iio/adc/ltc2378.c +5 -0
+		 * That also requires we apply no shift to scan elements to
+		 * correctly read ADC sample data.
+		 */
+		ltc2378_chan->scan_type.shift = 0;
+		ltc2378_chan->scan_type.endianness = IIO_CPU;

Can we move the main init of scan_type down here and do it in the if / else so as to avoid setting it to the wrong thing then updating?

Quoted 15 lines
drivers/iio/adc/ltc2378.c +3 -0
+	}
+
 	indio_dev->channels = ltc2378_chan;
 	indio_dev->num_channels = num_iio_chans;
 
@@ -350,3 +411,4 @@ MODULE_AUTHOR("Ioan-Daniel Pop <[email protected]>");
 MODULE_AUTHOR("Marcelo Schmitt <[email protected]>");
 MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_LTC2378");
drivers/iio/adc/ltc2378.h +0 -0
index 515f7e8a4f2e..17c329b18333 100644

drivers/iio/adc/ltc2378.h line 12

Quoted 4 lines
drivers/iio/adc/ltc2378.h +1 -0
@@ -9,7 +9,14 @@
 #define __DRIVERS_IIO_ADC_LTC2378_H__
 
 #include <linux/iio/iio.h>
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER

Not keen on these ifdefs. What harm does including these do if they functionality isn't in use? I suspect nothing, in which case drop the guarding.

Quoted 41 lines
drivers/iio/adc/ltc2378.h +19 -0
+#include <linux/pwm.h>
+#endif
 #include <linux/spi/spi.h>
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#endif
 #include <linux/types.h>
 #include <linux/units.h>
 
@@ -20,6 +27,8 @@
 struct ltc2378_chip_info {
 	const char *name;
 	int resolution;
+	unsigned int max_sample_rate_hz;
+	unsigned int tconv_ns;
 	const char out_format;
 };
 
@@ -29,6 +38,16 @@ struct ltc2378_state {
 	struct spi_device *spi;
 	struct spi_transfer xfer;
 	int ref_uV;
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+	unsigned int cnv_Hz;
+	struct pwm_waveform cnv_wf;
+	struct spi_offload *offload;
+	struct spi_offload_trigger *offload_trigger;
+	struct spi_message offload_msg;
+	struct spi_transfer offload_xfer;
+	struct spi_offload_trigger_config offload_trigger_config;
+	struct pwm_device *cnv_trigger;
+#endif
 
 	/*
 	 * DMA (thus cache coherency maintenance) requires the
@@ -45,4 +64,19 @@ struct ltc2378_state {
 	} scan __aligned(IIO_DMA_MINALIGN);
 };
 
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER

drivers/iio/adc/ltc2378.h struct ltc2378_state { · line 77

Quoted 10 lines
drivers/iio/adc/ltc2378.h +10 -0
+extern const struct attribute_group ltc2378_offload_attribute_group;
+#endif
+
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi);
+#else
+static inline int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev,
+					       struct spi_device *spi)
+{
+	might_sleep();

Seems pretty unlikely ;) How useful is this marking in code that isn't running? I'd hope build tests will trigger on the the version where this is built and sleeps.

Quoted 5 lines
drivers/iio/adc/ltc2378.h +4 -0
+	return -ENODEV;
+}
+#endif
+
 #endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
#11 lore raw

Re: [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture

Quoted 16 lines
Quoted 16 lines
drivers/iio/adc/Kconfig +1 -0
index 70fec8e3e891..b5368ee783f7 100644
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
 	  similar analog to digital converters.
@@ -951,6 +952,17 @@ config LTC2378
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2378.
 
Quoted 12 lines

drivers/iio/adc/Kconfig config LTC2378 · line 962

Quoted 8 lines
drivers/iio/adc/Kconfig +8 -0
+config LTC2378_OFFLOAD_BUFFER
+	bool "Offloaded data capture with LTC2378"
+	depends on SPI && LTC2378
+	depends on SPI_OFFLOAD=y
+	depends on PWM=y
+	depends on SPI_OFFLOAD_TRIGGER_PWM=y
+	depends on IIO_BUFFER_DMA=y
+	depends on IIO_BUFFER_DMAENGINE=y

why do all these have to be built in? In general I think we need to jusitfy why this driver needs to work on systems with out this stuff being built. I.e. why do we need the separate optional file at all?

Hi Jonathan, thank you for your review.

I've applied fixes to all issues reported by sashiko, except for Kconfig configuration (see below) and the is_visible() thing I'm still working on.

My intent with this design choice was to keep chip intrinsic dependencies separate from SPI offload dependencies. The indented benefit is to enable using the device without having to enable SPI_OFFLOAD (which is boolean and might not be supported on all platforms). The offload feature is indeed highly desirable hence the imply on the main driver Kconfig entry. The other symbols are tristate so, if we keep this approach, will drop =y.

The separate optional file is part of the push for dependency separation. It helps avoid #ifdef in c code. If the proposal is not far from something acceptable, I'd like to have another try of making it work. Still, it's arguable that the real interest is only on supporting the offload thing. Also, since I don't have any request to support these without offload, I think it will also be okay to have everything combined into a single driver.

Will follow all the other suggestions.

Quoted 13 lines
Quoted 13 lines
drivers/iio/adc/Kconfig +3 -0
+	help
+	  Say yes here to build support for high speed data capture with LTC2378
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +0 -0
index 1814fb78dde3..2fa5dce0ceea 100644
@@ -82,6 +82,7 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
Quoted 8 lines

drivers/iio/adc/Makefile obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o · line 85

Quoted 3 lines
drivers/iio/adc/Makefile +1 -0
 obj-$(CONFIG_LTC2309) += ltc2309.o
 obj-$(CONFIG_LTC2378) += ltc2378.o
+obj-$(CONFIG_LTC2378_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o

Hmm. This is odd. The driver can be modular but not this and it's build seperately rather than as part of the module. I think these bit needs a redesign. We definitely want this extra support to be part of the module. See how something like the ADIS IMU library in iio/imu/Makefile is done.

Will check this out too.

Quoted 4 lines
Quoted 4 lines
drivers/iio/adc/Makefile +0 -0
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
maintainer
#12 lore raw

Re: [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture

Quoted 34 lines · On Wed, 20 May 2026 14:31:51 -0300 Marcelo Schmitt…
Quoted 16 lines
Quoted 16 lines
drivers/iio/adc/Kconfig +1 -0
index 70fec8e3e891..b5368ee783f7 100644
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
 	  similar analog to digital converters.
@@ -951,6 +952,17 @@ config LTC2378
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2378.
 
Quoted 12 lines

drivers/iio/adc/Kconfig config LTC2378 · line 962

Quoted 8 lines
drivers/iio/adc/Kconfig +8 -0
+config LTC2378_OFFLOAD_BUFFER
+	bool "Offloaded data capture with LTC2378"
+	depends on SPI && LTC2378
+	depends on SPI_OFFLOAD=y
+	depends on PWM=y
+	depends on SPI_OFFLOAD_TRIGGER_PWM=y
+	depends on IIO_BUFFER_DMA=y
+	depends on IIO_BUFFER_DMAENGINE=y  

why do all these have to be built in? In general I think we need to jusitfy why this driver needs to work on systems with out this stuff being built. I.e. why do we need the separate optional file at all?

Hi Jonathan, thank you for your review.

I've applied fixes to all issues reported by sashiko, except for Kconfig configuration (see below) and the is_visible() thing I'm still working on.

Quoted 6 lines

My intent with this design choice was to keep chip intrinsic dependencies separate from SPI offload dependencies. The indented benefit is to enable using the device without having to enable SPI_OFFLOAD (which is boolean and might not be supported on all platforms). The offload feature is indeed highly desirable hence the imply on the main driver Kconfig entry. The other symbols are tristate so, if we keep this approach, will drop =y.

I can see there will be platforms where SPI_OFFLOAD doesn't make sense, but are there cases where it can't be built anyway? It's not that big and doesn't seem to have dependencies beyond those of SPI.

Quoted 7 lines

The separate optional file is part of the push for dependency separation. It helps avoid #ifdef in c code. If the proposal is not far from something acceptable, I'd like to have another try of making it work. Still, it's arguable that the real interest is only on supporting the offload thing. Also, since I don't have any request to support these without offload, I think it will also be okay to have everything combined into a single driver.

I don't mind the separate file, but I do mind the need for so much to be built in rather than modular. So I think the Kconfig needs some changes rather than it being a fundamental problem.

Jonathan

Quoted 32 lines

Will follow all the other suggestions.

Quoted 13 lines
Quoted 13 lines
drivers/iio/adc/Kconfig +3 -0
+	help
+	  Say yes here to build support for high speed data capture with LTC2378
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
drivers/iio/adc/Makefile +0 -0
index 1814fb78dde3..2fa5dce0ceea 100644
@@ -82,6 +82,7 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
Quoted 8 lines

drivers/iio/adc/Makefile obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o · line 85

Quoted 3 lines
drivers/iio/adc/Makefile +1 -0
 obj-$(CONFIG_LTC2309) += ltc2309.o
 obj-$(CONFIG_LTC2378) += ltc2378.o
+obj-$(CONFIG_LTC2378_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o  

Hmm. This is odd. The driver can be modular but not this and it's build seperately rather than as part of the module. I think these bit needs a redesign. We definitely want this extra support to be part of the module. See how something like the ADIS IMU library in iio/imu/Makefile is done.

Will check this out too.

Quoted 4 lines
Quoted 4 lines
drivers/iio/adc/Makefile +0 -0
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o  
#13 lore raw

PATCH 4/4 iio: adc: ltc2378: Enable triggered buffer data capture

In reply to Marcelo Schmitt

From: Marcelo Schmitt <[email protected]>

Enable users to run triggered data captures with LTC2378 and similar ADCs.

---
 drivers/iio/adc/Kconfig   |  1 +
 drivers/iio/adc/ltc2378.c | 39 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 37 insertions(+), 3 deletions(-)
drivers/iio/adc/Kconfig +1 -0
index b5368ee783f7..265c4a2b5fb7 100644
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
 	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
drivers/iio/adc/ltc2378.c +36 -3
index fdbe919d45d5..993e6b09bb41 100644
@@ -10,6 +10,9 @@
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
 #include <linux/module.h>
 #include <linux/regulator/consumer.h>
 
@@ -272,6 +275,25 @@ static const struct iio_info ltc2378_iio_info = {
 	.read_raw = &ltc2378_read_raw,
 };
 
+static irqreturn_t ltc2378_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ltc2378_convert_and_acquire(st);
+	if (ret < 0)
+		goto err_out;
+
+	iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+				    pf->timestamp);
+
+err_out:
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
 static int ltc2378_probe(struct spi_device *spi)
 {
 	struct iio_chan_spec *ltc2378_chan;
@@ -305,11 +327,11 @@ static int ltc2378_probe(struct spi_device *spi)
 		return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
 				     "failed to get CNV GPIO");
 
-	ltc2378_chan = devm_kzalloc(&spi->dev, sizeof(struct iio_chan_spec), GFP_KERNEL);
+	ltc2378_chan = devm_kzalloc(&spi->dev, 2 * sizeof(struct iio_chan_spec), GFP_KERNEL);
 	if (!ltc2378_chan)
 		return -ENOMEM;
 
-	*ltc2378_chan = (struct iio_chan_spec) {
+	ltc2378_chan[0] = (struct iio_chan_spec) {
 		.type = IIO_VOLTAGE,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SCALE),
@@ -327,7 +349,18 @@ static int ltc2378_probe(struct spi_device *spi)
 	ret = ltc2378_offload_buffer_setup(indio_dev, spi);
 	if (ret == -ENODEV) {
 		/* SPI offloading is unavailable. Fall back to triggered buffer. */
-		dev_notice(dev, "buffered data capture not supported\n");
+		ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+						      &iio_pollfunc_store_time,
+						      &ltc2378_trigger_handler,
+						      NULL);
+		if (ret)
+			return ret;
+
+		/* Add timestamp channel */
+		struct iio_chan_spec ts_chan = IIO_CHAN_SOFT_TIMESTAMP(1);
+
+		ltc2378_chan[1] = ts_chan;
+		num_iio_chans++;
 	} else if (ret) {
 		return dev_err_probe(dev, ret, "error on SPI offload setup\n");
 	} else {
Signature · 1 line
2.53.0
maintainer
#14 lore raw

Re: [PATCH v1 4/4] iio: adc: ltc2378: Enable triggered buffer data capture

Quoted 5 lines · On Mon, 18 May 2026 12:22:29 -0300 Marcelo Schmitt…

From: Marcelo Schmitt <[email protected]>

Enable users to run triggered data captures with LTC2378 and similar ADCs.

A few trivial things inline.

Quoted 58 lines
---
 drivers/iio/adc/Kconfig   |  1 +
 drivers/iio/adc/ltc2378.c | 39 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 37 insertions(+), 3 deletions(-)
drivers/iio/adc/Kconfig +1 -0
index b5368ee783f7..265c4a2b5fb7 100644
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
 	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
drivers/iio/adc/ltc2378.c +22 -0
index fdbe919d45d5..993e6b09bb41 100644
@@ -10,6 +10,9 @@
 #include <linux/bitops.h>
 #include <linux/delay.h>
 #include <linux/err.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
 #include <linux/module.h>
 #include <linux/regulator/consumer.h>
 
@@ -272,6 +275,25 @@ static const struct iio_info ltc2378_iio_info = {
 	.read_raw = &ltc2378_read_raw,
 };
 
+static irqreturn_t ltc2378_trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	int ret;
+
+	ret = ltc2378_convert_and_acquire(st);
+	if (ret < 0)
+		goto err_out;
+
+	iio_push_to_buffers_with_ts(indio_dev, &st->scan, sizeof(st->scan),
+				    pf->timestamp);
+
+err_out:
+	iio_trigger_notify_done(indio_dev->trig);
+	return IRQ_HANDLED;
+}
+
 static int ltc2378_probe(struct spi_device *spi)
 {
 	struct iio_chan_spec *ltc2378_chan;

drivers/iio/adc/ltc2378.c static int ltc2378_probe(struct spi_device *spi) · line 330

Quoted 5 lines
drivers/iio/adc/ltc2378.c +1 -1
@@ -305,11 +327,11 @@ static int ltc2378_probe(struct spi_device *spi)
 		return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
 				     "failed to get CNV GPIO");
 
-	ltc2378_chan = devm_kzalloc(&spi->dev, sizeof(struct iio_chan_spec), GFP_KERNEL);
+	ltc2378_chan = devm_kzalloc(&spi->dev, 2 * sizeof(struct iio_chan_spec), GFP_KERNEL);

If you don't end up doing static arrays can just embed this array in the ioi_priv()

Quoted 12 lines
drivers/iio/adc/ltc2378.c +1 -1
 	if (!ltc2378_chan)
 		return -ENOMEM;
 
-	*ltc2378_chan = (struct iio_chan_spec) {
+	ltc2378_chan[0] = (struct iio_chan_spec) {
 		.type = IIO_VOLTAGE,
 		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
 				      BIT(IIO_CHAN_INFO_SCALE),
@@ -327,7 +349,18 @@ static int ltc2378_probe(struct spi_device *spi)
 	ret = ltc2378_offload_buffer_setup(indio_dev, spi);
 	if (ret == -ENODEV) {
 		/* SPI offloading is unavailable. Fall back to triggered buffer. */

drivers/iio/adc/ltc2378.c static int ltc2378_probe(struct spi_device *spi) · line 360

Quoted 10 lines
drivers/iio/adc/ltc2378.c +9 -1
-		dev_notice(dev, "buffered data capture not supported\n");
+		ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+						      &iio_pollfunc_store_time,
+						      &ltc2378_trigger_handler,
+						      NULL);
+		if (ret)
+			return ret;
+
+		/* Add timestamp channel */
+		struct iio_chan_spec ts_chan = IIO_CHAN_SOFT_TIMESTAMP(1);

With the series that is a combination of Andy and David's work you should be able to do this without the local variable. That series is fairly simple so should land shortly. If you want to make sure this gets fixed in the meantime you can do

		ltc2378_chan[1] = (struct iio_chan_spec)IIO_CHAN_SOFT_TIMESTAMP(1);

then I'll get a build error once that series lands and remember to fix it ;)

If you move to a static array we can just do manipulation of num_iio_chans to include the trailing timestamp element or not.

Quoted 6 lines
drivers/iio/adc/ltc2378.c +3 -0
+
+		ltc2378_chan[1] = ts_chan;
+		num_iio_chans++;
 	} else if (ret) {
 		return dev_err_probe(dev, ret, "error on SPI offload setup\n");
 	} else {