* [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378
2026-07-02 21:44 [PATCH v5 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
@ 2026-07-02 21:45 ` Marcelo Schmitt
2026-07-02 21:52 ` sashiko-bot
2026-07-02 21:45 ` [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
` (2 subsequent siblings)
3 siblings, 1 reply; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-02 21:45 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, julianbraha, marcelo.schmitt1, Conor Dooley
Document how to describe LTC2378-20 and similar ADCs in device tree.
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v4 -> v5:
- LTC2338 is slightly different from LTC2378 and, because of that, software
can't really handle those the same way. No longer specify fallback for LTC2338.
.../bindings/iio/adc/adi,ltc2378.yaml | 184 ++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 191 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
new file mode 100644
index 000000000000..44ab4f1f7fa5
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
@@ -0,0 +1,184 @@
+# 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 <marcelo.schmitt@analog.com>
+
+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:
+ # Single compatible string match.
+ - enum:
+ - adi,ltc2338-18
+ - adi,ltc2364-16
+ - adi,ltc2364-18
+ - adi,ltc2367-16
+ - adi,ltc2367-18
+ - adi,ltc2368-16
+ - adi,ltc2368-18
+ - adi,ltc2369-18
+ - adi,ltc2370-16
+ - adi,ltc2376-16
+ - adi,ltc2376-18
+ - adi,ltc2376-20
+ - adi,ltc2377-16
+ - adi,ltc2377-18
+ - adi,ltc2377-20
+ - adi,ltc2378-16
+ - adi,ltc2378-18
+ - adi,ltc2378-20
+ - adi,ltc2379-18
+ - adi,ltc2380-16
+
+ # Low sample rate fallback for 16-bit unipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2370-16 # 2 MSPS
+ - adi,ltc2368-16 # 1 MSPS
+ - adi,ltc2367-16 # 500 kSPS
+ - const: adi,ltc2364-16 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 18-bit unipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2369-18 # 1.6 MSPS
+ - adi,ltc2368-18 # 1 MSPS
+ - adi,ltc2367-18 # 500 kSPS
+ - const: adi,ltc2364-18 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 16-bit bipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2380-16 # 2 MSPS
+ - adi,ltc2378-16 # 1 MSPS
+ - adi,ltc2377-16 # 500 kSPS
+ - const: adi,ltc2376-16 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 18-bit bipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2379-18 # 1.6 MSPS
+ - adi,ltc2378-18 # 1 MSPS
+ - adi,ltc2377-18 # 500 kSPS
+ - const: adi,ltc2376-18 # fallback (250 kSPS)
+
+ # Low sample rate fallback for 20-bit bipolar sensors.
+ - items:
+ - enum:
+ - adi,ltc2378-20 # 1 MSPS
+ - adi,ltc2377-20 # 500 kSPS
+ - const: adi,ltc2376-20 # fallback (250 kSPS)
+
+ 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:
+ Voltage reference input that determines the scale of ADC conversions.
+
+ refin-supply:
+ description:
+ Alternative voltage reference input.
+
+ cnv-gpios:
+ description:
+ When provided, this property indicates the GPIO that is connected to the
+ CNV pin.
+ maxItems: 1
+
+ pwms:
+ description: PWM signal 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
+
+allOf:
+ # Except for LTC2338, all designs require a voltage reference input
+ - if:
+ properties:
+ compatible:
+ not:
+ contains:
+ enum:
+ - adi,ltc2338-18
+ then:
+ required:
+ - ref-supply
+ properties:
+ refin-supply: false
+ else:
+ properties:
+ ref-supply: false
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+ adc@0 {
+ compatible = "adi,ltc2378-20", "adi,ltc2376-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>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index b051eccafa60..205acb4b0789 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15220,6 +15220,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 <marcelo.schmitt@analog.com>
+L: linux-iio@vger.kernel.org
+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 <michael.hennerich@analog.com>
M: Kim Seer Paller <kimseer.paller@analog.com>
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378
2026-07-02 21:45 ` [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
@ 2026-07-02 21:52 ` sashiko-bot
2026-07-03 17:04 ` Marcelo Schmitt
0 siblings, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-07-02 21:52 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] Compatible strings with required fallbacks are incorrectly allowed as standalone strings.
--
commit fff47614872c95c6a481f63da8b14dfcedd0bb53
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
dt-bindings: iio: adc: Add ltc2378
Document how to describe LTC2378-20 and similar ADCs in device tree.
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> new file mode 100644
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
[ ... ]
> +properties:
> + compatible:
> + oneOf:
> + # Single compatible string match.
> + - enum:
> + - adi,ltc2338-18
> + - adi,ltc2364-16
> + - adi,ltc2364-18
> + - adi,ltc2367-16
> + - adi,ltc2367-18
> + - adi,ltc2368-16
> + - adi,ltc2368-18
> + - adi,ltc2369-18
> + - adi,ltc2370-16
[Severity: Low]
Does this standalone enum allow devices that should require a fallback
to pass schema validation with only a single compatible string?
Because parts like "adi,ltc2370-16" and "adi,ltc2367-16" are listed here in
the single enum match, a device tree node specifying only
compatible = "adi,ltc2370-16" will satisfy this block and successfully pass
schema validation.
This seems to bypass the fallback requirements defined below in the items
blocks:
[ ... ]
> + # Low sample rate fallback for 16-bit unipolar sensors.
> + - items:
> + - enum:
> + - adi,ltc2370-16 # 2 MSPS
> + - adi,ltc2368-16 # 1 MSPS
> + - adi,ltc2367-16 # 500 kSPS
> + - const: adi,ltc2364-16 # fallback (250 kSPS)
Should the parts that require a fallback be removed from the single
compatible string enum so the schema correctly enforces the fallback?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783028033.git.marcelo.schmitt@analog.com?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378
2026-07-02 21:52 ` sashiko-bot
@ 2026-07-03 17:04 ` Marcelo Schmitt
2026-07-04 23:11 ` Jonathan Cameron
0 siblings, 1 reply; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-03 17:04 UTC (permalink / raw)
To: sashiko-reviews
Cc: Marcelo Schmitt, robh, conor+dt, devicetree, linux-iio,
marcelo.schmitt1
On 07/02, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] Compatible strings with required fallbacks are incorrectly allowed as standalone strings.
> --
>
> commit fff47614872c95c6a481f63da8b14dfcedd0bb53
> Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
>
> dt-bindings: iio: adc: Add ltc2378
>
> Document how to describe LTC2378-20 and similar ADCs in device tree.
>
> > diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> > new file mode 100644
> > --- /dev/null
> > +++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> [ ... ]
> > +properties:
> > + compatible:
> > + oneOf:
> > + # Single compatible string match.
> > + - enum:
> > + - adi,ltc2338-18
> > + - adi,ltc2364-16
> > + - adi,ltc2364-18
> > + - adi,ltc2367-16
> > + - adi,ltc2367-18
> > + - adi,ltc2368-16
> > + - adi,ltc2368-18
> > + - adi,ltc2369-18
> > + - adi,ltc2370-16
>
> [Severity: Low]
> Does this standalone enum allow devices that should require a fallback
> to pass schema validation with only a single compatible string?
>
> Because parts like "adi,ltc2370-16" and "adi,ltc2367-16" are listed here in
> the single enum match, a device tree node specifying only
> compatible = "adi,ltc2370-16" will satisfy this block and successfully pass
> schema validation.
>
> This seems to bypass the fallback requirements defined below in the items
> blocks:
>
> [ ... ]
> > + # Low sample rate fallback for 16-bit unipolar sensors.
> > + - items:
> > + - enum:
> > + - adi,ltc2370-16 # 2 MSPS
> > + - adi,ltc2368-16 # 1 MSPS
> > + - adi,ltc2367-16 # 500 kSPS
> > + - const: adi,ltc2364-16 # fallback (250 kSPS)
>
> Should the parts that require a fallback be removed from the single
> compatible string enum so the schema correctly enforces the fallback?
Why? Should users be prevented to specify a single specific compatible string?
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783028033.git.marcelo.schmitt@analog.com?part=1
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378
2026-07-03 17:04 ` Marcelo Schmitt
@ 2026-07-04 23:11 ` Jonathan Cameron
0 siblings, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-07-04 23:11 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: sashiko-reviews, Marcelo Schmitt, robh, conor+dt, devicetree,
linux-iio
On Fri, 3 Jul 2026 14:04:51 -0300
Marcelo Schmitt <marcelo.schmitt1@gmail.com> wrote:
> On 07/02, sashiko-bot@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> > - [Low] Compatible strings with required fallbacks are incorrectly allowed as standalone strings.
> > --
> >
> > commit fff47614872c95c6a481f63da8b14dfcedd0bb53
> > Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
> >
> > dt-bindings: iio: adc: Add ltc2378
> >
> > Document how to describe LTC2378-20 and similar ADCs in device tree.
> >
> > > diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> > > new file mode 100644
> > > --- /dev/null
> > > +++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> > [ ... ]
> > > +properties:
> > > + compatible:
> > > + oneOf:
> > > + # Single compatible string match.
> > > + - enum:
> > > + - adi,ltc2338-18
> > > + - adi,ltc2364-16
> > > + - adi,ltc2364-18
> > > + - adi,ltc2367-16
> > > + - adi,ltc2367-18
> > > + - adi,ltc2368-16
> > > + - adi,ltc2368-18
> > > + - adi,ltc2369-18
> > > + - adi,ltc2370-16
> >
> > [Severity: Low]
> > Does this standalone enum allow devices that should require a fallback
> > to pass schema validation with only a single compatible string?
> >
> > Because parts like "adi,ltc2370-16" and "adi,ltc2367-16" are listed here in
> > the single enum match, a device tree node specifying only
> > compatible = "adi,ltc2370-16" will satisfy this block and successfully pass
> > schema validation.
> >
> > This seems to bypass the fallback requirements defined below in the items
> > blocks:
> >
> > [ ... ]
> > > + # Low sample rate fallback for 16-bit unipolar sensors.
> > > + - items:
> > > + - enum:
> > > + - adi,ltc2370-16 # 2 MSPS
> > > + - adi,ltc2368-16 # 1 MSPS
> > > + - adi,ltc2367-16 # 500 kSPS
> > > + - const: adi,ltc2364-16 # fallback (250 kSPS)
> >
> > Should the parts that require a fallback be removed from the single
> > compatible string enum so the schema correctly enforces the fallback?
>
> Why? Should users be prevented to specify a single specific compatible string?
Yes we should. They should always use the double string to provide a fallback.
In practice the driver will work just fine with only a single entry irrespective
of what the binding doc says - that doesn't change that we insist on it in the binding
doc.
Jonathan
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
2026-07-02 21:44 [PATCH v5 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
2026-07-02 21:45 ` [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
@ 2026-07-02 21:45 ` Marcelo Schmitt
2026-07-02 22:02 ` sashiko-bot
2026-07-04 23:43 ` Jonathan Cameron
2026-07-02 21:45 ` [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
2026-07-02 21:46 ` [PATCH v5 4/4] iio: adc: ltc2378: Enable triggered buffer " Marcelo Schmitt
3 siblings, 2 replies; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-02 21:45 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, julianbraha, marcelo.schmitt1
Support for LTC2378-20 and similar analog-to-digital converters.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v4 -> v5:
- Took internal reference buffer amplification into account.
The _offload parameter is a preparation for the followup offload enablement patch.
Kept in the initial patch to reduce code diff.
The (((_real_bits) > 16) ? 32 : 16) part of normal channel macros could be moved
into LTC2378_DIFF_CHANNEL(), becoming
.realbits = (_offload ? (_real_bits) : (((_real_bits) > 16) ? 32 : 16)), \
IMHO, that is a bit harder to read so I kept a small repetition in favor of
readability.
Did not pick David's review tag because not all of his suggestions were applied.
MAINTAINERS | 1 +
drivers/iio/adc/Kconfig | 12 ++
drivers/iio/adc/Makefile | 1 +
drivers/iio/adc/ltc2378.c | 404 ++++++++++++++++++++++++++++++++++++++
4 files changed, 418 insertions(+)
create mode 100644 drivers/iio/adc/ltc2378.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 205acb4b0789..db24dfa087d9 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15226,6 +15226,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
+F: drivers/iio/adc/ltc2378.c
LTC2664 IIO DAC DRIVER
M: Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 756a3b1137a7..07a8a5911a09 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -941,6 +941,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 REGULATOR || COMPILE_TEST
+ depends on GPIOLIB
+ 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
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 707dd708912f..1814fb78dde3 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -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
diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
new file mode 100644
index 000000000000..b5cf2e974dac
--- /dev/null
+++ b/drivers/iio/adc/ltc2378.c
@@ -0,0 +1,404 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices LTC2378 ADC series driver
+ *
+ * Copyright (C) 2026 Analog Devices Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#include <linux/iio/iio.h>
+#include <linux/iio/types.h>
+
+#define LTC2378_DIFF_CHANNEL(_sign, _real_bits, _storage_bits, _offload) \
+{ \
+ .type = IIO_VOLTAGE, \
+ .info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | \
+ BIT(IIO_CHAN_INFO_SCALE) | \
+ (_offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0), \
+ .info_mask_separate_available = _offload ? BIT(IIO_CHAN_INFO_SAMP_FREQ) : 0,\
+ .scan_index = 0, \
+ .scan_type = { \
+ .format = _sign ? IIO_SCAN_FORMAT_SIGNED_INT : \
+ IIO_SCAN_FORMAT_UNSIGNED_INT, \
+ .realbits = _real_bits, \
+ .storagebits = _storage_bits, \
+ .shift = (_offload ? 0 : _storage_bits - _real_bits), \
+ .endianness = _offload ? IIO_CPU : IIO_BE \
+ }, \
+}
+
+#define LTC2378_BIPOLAR_DIFF_CHANNEL(_real_bits) \
+ LTC2378_DIFF_CHANNEL(1, _real_bits, (((_real_bits) > 16) ? 32 : 16), 0)
+
+#define LTC2378_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
+ LTC2378_DIFF_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16), 0)
+
+struct ltc2378_chip_info {
+ const char *name;
+ unsigned int internal_ref_uV;
+ struct iio_chan_spec chan;
+};
+
+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);
+};
+
+static const struct ltc2378_chip_info ltc2338_18_chip_info = {
+ .name = "ltc2338-18",
+ .internal_ref_uV = 4096000,
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2364_16_chip_info = {
+ .name = "ltc2364-16",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2364_18_chip_info = {
+ .name = "ltc2364-18",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2367_16_chip_info = {
+ .name = "ltc2367-16",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2367_18_chip_info = {
+ .name = "ltc2367-18",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2368_16_chip_info = {
+ .name = "ltc2368-16",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2368_18_chip_info = {
+ .name = "ltc2368-18",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2369_18_chip_info = {
+ .name = "ltc2369-18",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2370_16_chip_info = {
+ .name = "ltc2370-16",
+ .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2376_16_chip_info = {
+ .name = "ltc2376-16",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2376_18_chip_info = {
+ .name = "ltc2376-18",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2376_20_chip_info = {
+ .name = "ltc2376-20",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+};
+
+static const struct ltc2378_chip_info ltc2377_16_chip_info = {
+ .name = "ltc2377-16",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2377_18_chip_info = {
+ .name = "ltc2377-18",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2377_20_chip_info = {
+ .name = "ltc2377-20",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+};
+
+static const struct ltc2378_chip_info ltc2378_16_chip_info = {
+ .name = "ltc2378-16",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+};
+
+static const struct ltc2378_chip_info ltc2378_18_chip_info = {
+ .name = "ltc2378-18",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2378_20_chip_info = {
+ .name = "ltc2378-20",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+};
+
+static const struct ltc2378_chip_info ltc2379_18_chip_info = {
+ .name = "ltc2379-18",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+};
+
+static const struct ltc2378_chip_info ltc2380_16_chip_info = {
+ .name = "ltc2380-16",
+ .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+};
+
+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);
+ fsleep(4);
+ 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)
+{
+ 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 (chan->scan_type.endianness == IIO_BE) {
+ if (chan->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 (chan->scan_type.realbits > 16)
+ sample = st->scan.data.sample_buf32;
+ else
+ sample = st->scan.data.sample_buf16;
+ }
+
+ sample >>= chan->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;
+}
+
+static int ltc2378_read_raw(struct iio_dev *indio_dev,
+ const struct iio_chan_spec *chan,
+ int *val, int *val2, long mask)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+ int ret;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_RAW: {
+ 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 polarity / output code format:
+ * - straight binary: All precision/resolution bits are used.
+ * - 2's complement: One of the precision bits is used for sign.
+ */
+ if (chan->scan_type.format == IIO_SCAN_FORMAT_SIGNED_INT)
+ *val2 = chan->scan_type.realbits - 1;
+ else
+ *val2 = chan->scan_type.realbits;
+
+ return IIO_VAL_FRACTIONAL_LOG2;
+
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct iio_info ltc2378_iio_info = {
+ .read_raw = <c2378_read_raw,
+};
+
+static int ltc2378_refin_setup(struct device *dev, struct ltc2378_state *st)
+{
+ int ret;
+
+ /*
+ * The internal reference buffer amplifies both the internal reference
+ * and REFIN by a factor of 2.
+ */
+ ret = devm_regulator_get_enable_read_voltage(dev, "refin");
+ if (ret == -ENODEV) /* refin is optional */
+ st->ref_uV = st->info->internal_ref_uV * 2;
+ else if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read refin regulator\n");
+ else
+ st->ref_uV = ret * 2;
+
+ return 0;
+}
+
+static int ltc2378_ref_setup(struct device *dev, struct ltc2378_state *st)
+{
+ int ret;
+
+ ret = devm_regulator_get_enable_read_voltage(dev, "ref");
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to read ref regulator\n");
+
+ st->ref_uV = ret;
+
+ return 0;
+}
+
+static int ltc2378_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct iio_dev *indio_dev;
+ struct ltc2378_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+
+ st->info = spi_get_device_match_data(spi);
+ if (!st->info)
+ return -EINVAL;
+
+ if (st->info->internal_ref_uV)
+ ret = ltc2378_refin_setup(dev, st);
+ else
+ ret = ltc2378_ref_setup(dev, st);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->info->name;
+ indio_dev->info = <c2378_iio_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+
+ st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW);
+ if (IS_ERR(st->cnv_gpio))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
+ "failed to get CNV GPIO");
+
+ indio_dev->channels = &st->info->chan;
+ indio_dev->num_channels = 1;
+
+ st->xfer.rx_buf = &st->scan.data;
+ st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits);
+
+ return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id ltc2378_of_match[] = {
+ { .compatible = "adi,ltc2338-18", .data = <c2338_18_chip_info },
+ { .compatible = "adi,ltc2364-16", .data = <c2364_16_chip_info },
+ { .compatible = "adi,ltc2364-18", .data = <c2364_18_chip_info },
+ { .compatible = "adi,ltc2367-16", .data = <c2367_16_chip_info },
+ { .compatible = "adi,ltc2367-18", .data = <c2367_18_chip_info },
+ { .compatible = "adi,ltc2368-16", .data = <c2368_16_chip_info },
+ { .compatible = "adi,ltc2368-18", .data = <c2368_18_chip_info },
+ { .compatible = "adi,ltc2369-18", .data = <c2369_18_chip_info },
+ { .compatible = "adi,ltc2370-16", .data = <c2370_16_chip_info },
+ { .compatible = "adi,ltc2376-16", .data = <c2376_16_chip_info },
+ { .compatible = "adi,ltc2376-18", .data = <c2376_18_chip_info },
+ { .compatible = "adi,ltc2376-20", .data = <c2376_20_chip_info },
+ { .compatible = "adi,ltc2377-16", .data = <c2377_16_chip_info },
+ { .compatible = "adi,ltc2377-18", .data = <c2377_18_chip_info },
+ { .compatible = "adi,ltc2377-20", .data = <c2377_20_chip_info },
+ { .compatible = "adi,ltc2378-16", .data = <c2378_16_chip_info },
+ { .compatible = "adi,ltc2378-18", .data = <c2378_18_chip_info },
+ { .compatible = "adi,ltc2378-20", .data = <c2378_20_chip_info },
+ { .compatible = "adi,ltc2379-18", .data = <c2379_18_chip_info },
+ { .compatible = "adi,ltc2380-16", .data = <c2380_16_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ltc2378_of_match);
+
+static const struct spi_device_id ltc2378_spi_id[] = {
+ { .name = "ltc2338-18", .driver_data = (kernel_ulong_t)<c2338_18_chip_info },
+ { .name = "ltc2364-16", .driver_data = (kernel_ulong_t)<c2364_16_chip_info },
+ { .name = "ltc2364-18", .driver_data = (kernel_ulong_t)<c2364_18_chip_info },
+ { .name = "ltc2367-16", .driver_data = (kernel_ulong_t)<c2367_16_chip_info },
+ { .name = "ltc2367-18", .driver_data = (kernel_ulong_t)<c2367_18_chip_info },
+ { .name = "ltc2368-16", .driver_data = (kernel_ulong_t)<c2368_16_chip_info },
+ { .name = "ltc2368-18", .driver_data = (kernel_ulong_t)<c2368_18_chip_info },
+ { .name = "ltc2369-18", .driver_data = (kernel_ulong_t)<c2369_18_chip_info },
+ { .name = "ltc2370-16", .driver_data = (kernel_ulong_t)<c2370_16_chip_info },
+ { .name = "ltc2376-16", .driver_data = (kernel_ulong_t)<c2376_16_chip_info },
+ { .name = "ltc2376-18", .driver_data = (kernel_ulong_t)<c2376_18_chip_info },
+ { .name = "ltc2376-20", .driver_data = (kernel_ulong_t)<c2376_20_chip_info },
+ { .name = "ltc2377-16", .driver_data = (kernel_ulong_t)<c2377_16_chip_info },
+ { .name = "ltc2377-18", .driver_data = (kernel_ulong_t)<c2377_18_chip_info },
+ { .name = "ltc2377-20", .driver_data = (kernel_ulong_t)<c2377_20_chip_info },
+ { .name = "ltc2378-16", .driver_data = (kernel_ulong_t)<c2378_16_chip_info },
+ { .name = "ltc2378-18", .driver_data = (kernel_ulong_t)<c2378_18_chip_info },
+ { .name = "ltc2378-20", .driver_data = (kernel_ulong_t)<c2378_20_chip_info },
+ { .name = "ltc2379-18", .driver_data = (kernel_ulong_t)<c2379_18_chip_info },
+ { .name = "ltc2380-16", .driver_data = (kernel_ulong_t)<c2380_16_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(spi, ltc2378_spi_id);
+
+static struct spi_driver ltc2378_driver = {
+ .driver = {
+ .name = "ltc2378",
+ .of_match_table = ltc2378_of_match
+ },
+ .probe = ltc2378_probe,
+ .id_table = ltc2378_spi_id,
+};
+module_spi_driver(ltc2378_driver);
+
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
+MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
2026-07-02 21:45 ` [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
@ 2026-07-02 22:02 ` sashiko-bot
2026-07-03 17:14 ` Marcelo Schmitt
2026-07-04 23:43 ` Jonathan Cameron
1 sibling, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-07-02 22:02 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Hardware protocol violation: CNV is held high during SPI read
- [High] SPI bus not locked during ADC conversion phase
- [Medium] Incorrect scale calculation for LTC2338-18 and similar ADCs
- [Low] Undefined direct mode macros lead to build failure
--
commit b74b536d18af0084ea31bd1e1e8cf9454da5e29c
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
This commit adds support for the Analog Devices LTC2378-20 and similar
analog-to-digital converters. It introduces a new IIO driver using the
SPI subsystem.
> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> new file mode 100644
> index 0000000000000..b5cf2e974daca
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378.c
> @@ -0,0 +1,404 @@
[ ... ]
> +static const struct ltc2378_chip_info ltc2338_18_chip_info = {
> + .name = "ltc2338-18",
> + .internal_ref_uV = 4096000,
> + .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
> +};
[ ... ]
> +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);
> + fsleep(4);
[Severity: High]
Can this sequence cause data corruption on a shared SPI bus?
The CNV pin is toggled and an fsleep(4) occurs before spi_sync_transfer()
locks the bus. If another SPI transaction occurs during this sleep window,
could it toggle the SCK line and prematurely clock data out of the ADC?
Should the conversion-and-read sequence be wrapped in spi_bus_lock()
and spi_bus_unlock()?
> + ret = spi_sync_transfer(st->spi, &st->xfer, 1);
[Severity: High]
Does reading from the SPI bus while CNV is held high violate the hardware
protocol?
The ADC requires the CNV pin to be brought low to enable the SDO
output before clocking out data via SPI, but spi_sync_transfer() happens
before gpiod_set_value_cansleep(st->cnv_gpio, 0), which might result in
reading high-impedance garbage data.
> + gpiod_set_value_cansleep(st->cnv_gpio, 0);
> +
> + return ret;
> +}
[ ... ]
> +static int ltc2378_read_raw(struct iio_dev *indio_dev,
> + const struct iio_chan_spec *chan,
> + int *val, int *val2, long mask)
> +{
> + struct ltc2378_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_RAW: {
> + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> + if (IIO_DEV_ACQUIRE_FAILED(claim))
> + return -EBUSY;
[Severity: Low]
Will this cause a build failure?
The macros IIO_DEV_ACQUIRE_DIRECT_MODE and IIO_DEV_ACQUIRE_FAILED appear
to be undeclared in the kernel tree.
> +
> + 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;
[Severity: Medium]
Does this calculate an incorrect IIO scale for the LTC2338-18?
Does this miss the 2.5x input span multiplier unique to the LTC2338-18
when calculating the IIO scale here?
> + /*
> + * For all LTC2378-like devices, the amount of bits that express
> + * voltage magnitude depend on the polarity / output code format:
> + * - straight binary: All precision/resolution bits are used.
> + * - 2's complement: One of the precision bits is used for sign.
> + */
> + if (chan->scan_type.format == IIO_SCAN_FORMAT_SIGNED_INT)
> + *val2 = chan->scan_type.realbits - 1;
> + else
> + *val2 = chan->scan_type.realbits;
> +
> + return IIO_VAL_FRACTIONAL_LOG2;
> +
> + default:
> + return -EINVAL;
> + }
> +}
[ ... ]
> +static int ltc2378_refin_setup(struct device *dev, struct ltc2378_state *st)
> +{
> + int ret;
> +
> + /*
> + * The internal reference buffer amplifies both the internal reference
> + * and REFIN by a factor of 2.
> + */
> + ret = devm_regulator_get_enable_read_voltage(dev, "refin");
> + if (ret == -ENODEV) /* refin is optional */
> + st->ref_uV = st->info->internal_ref_uV * 2;
[Severity: Medium]
Does this incorrectly double the internal reference voltage?
The ltc2338_18_chip_info already defines internal_ref_uV as the post-buffer
value of 4096000 (4.096V). Multiplying it by 2 here would result in an
incorrect scale value.
> + else if (ret < 0)
> + return dev_err_probe(dev, ret, "failed to read refin regulator\n");
> + else
> + st->ref_uV = ret * 2;
> +
> + return 0;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783028033.git.marcelo.schmitt@analog.com?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
2026-07-02 22:02 ` sashiko-bot
@ 2026-07-03 17:14 ` Marcelo Schmitt
2026-07-04 23:22 ` Jonathan Cameron
0 siblings, 1 reply; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-03 17:14 UTC (permalink / raw)
To: sashiko-reviews
Cc: Marcelo Schmitt, devicetree, robh, conor+dt, linux-iio,
marcelo.schmitt1
On 07/02, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Hardware protocol violation: CNV is held high during SPI read
> - [High] SPI bus not locked during ADC conversion phase
> - [Medium] Incorrect scale calculation for LTC2338-18 and similar ADCs
> - [Low] Undefined direct mode macros lead to build failure
> --
>
> commit b74b536d18af0084ea31bd1e1e8cf9454da5e29c
> Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
>
> iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
>
> This commit adds support for the Analog Devices LTC2378-20 and similar
> analog-to-digital converters. It introduces a new IIO driver using the
> SPI subsystem.
>
> > diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> > new file mode 100644
> > index 0000000000000..b5cf2e974daca
> > --- /dev/null
> > +++ b/drivers/iio/adc/ltc2378.c
> > @@ -0,0 +1,404 @@
>
> [ ... ]
>
> > +static const struct ltc2378_chip_info ltc2338_18_chip_info = {
> > + .name = "ltc2338-18",
> > + .internal_ref_uV = 4096000,
> > + .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
> > +};
>
> [ ... ]
>
> > +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);
> > + fsleep(4);
>
> [Severity: High]
> Can this sequence cause data corruption on a shared SPI bus?
>
> The CNV pin is toggled and an fsleep(4) occurs before spi_sync_transfer()
> locks the bus. If another SPI transaction occurs during this sleep window,
> could it toggle the SCK line and prematurely clock data out of the ADC?
Makes sense.
>
> Should the conversion-and-read sequence be wrapped in spi_bus_lock()
> and spi_bus_unlock()?
Because these chips don't have CS line, it seems that locking the bus actually
makes sense.
>
> > + ret = spi_sync_transfer(st->spi, &st->xfer, 1);
>
> [Severity: High]
> Does reading from the SPI bus while CNV is held high violate the hardware
> protocol?
No.
>
> The ADC requires the CNV pin to be brought low to enable the SDO
> output before clocking out data via SPI, but spi_sync_transfer() happens
> before gpiod_set_value_cansleep(st->cnv_gpio, 0), which might result in
> reading high-impedance garbage data.
See 'Using a Single LTC23... in Normal Mode' diagrams (usually Figure 10, 13, or 15
in datasheets). CNV can be either low or high during data transfers.
>
> > + gpiod_set_value_cansleep(st->cnv_gpio, 0);
> > +
> > + return ret;
> > +}
>
> [ ... ]
>
> > +static int ltc2378_read_raw(struct iio_dev *indio_dev,
> > + const struct iio_chan_spec *chan,
> > + int *val, int *val2, long mask)
> > +{
> > + struct ltc2378_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_RAW: {
> > + IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
> > + if (IIO_DEV_ACQUIRE_FAILED(claim))
> > + return -EBUSY;
>
> [Severity: Low]
> Will this cause a build failure?
>
> The macros IIO_DEV_ACQUIRE_DIRECT_MODE and IIO_DEV_ACQUIRE_FAILED appear
> to be undeclared in the kernel tree.
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/iio/iio.h?h=v7.2-rc1#n785
Outdated LLM?
>
> > +
> > + 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;
>
> [Severity: Medium]
> Does this calculate an incorrect IIO scale for the LTC2338-18?
>
> Does this miss the 2.5x input span multiplier unique to the LTC2338-18
> when calculating the IIO scale here?
That's taken care of in ltc2378_refin_setup().
>
> > + /*
> > + * For all LTC2378-like devices, the amount of bits that express
> > + * voltage magnitude depend on the polarity / output code format:
> > + * - straight binary: All precision/resolution bits are used.
> > + * - 2's complement: One of the precision bits is used for sign.
> > + */
> > + if (chan->scan_type.format == IIO_SCAN_FORMAT_SIGNED_INT)
> > + *val2 = chan->scan_type.realbits - 1;
> > + else
> > + *val2 = chan->scan_type.realbits;
> > +
> > + return IIO_VAL_FRACTIONAL_LOG2;
> > +
> > + default:
> > + return -EINVAL;
> > + }
> > +}
>
> [ ... ]
>
> > +static int ltc2378_refin_setup(struct device *dev, struct ltc2378_state *st)
> > +{
> > + int ret;
> > +
> > + /*
> > + * The internal reference buffer amplifies both the internal reference
> > + * and REFIN by a factor of 2.
> > + */
> > + ret = devm_regulator_get_enable_read_voltage(dev, "refin");
> > + if (ret == -ENODEV) /* refin is optional */
> > + st->ref_uV = st->info->internal_ref_uV * 2;
>
> [Severity: Medium]
> Does this incorrectly double the internal reference voltage?
>
> The ltc2338_18_chip_info already defines internal_ref_uV as the post-buffer
> value of 4096000 (4.096V). Multiplying it by 2 here would result in an
> incorrect scale value.
Indeed,
- .internal_ref_uV = 4096000,
+ .internal_ref_uV = 2048000,
will solve the issue.
>
> > + else if (ret < 0)
> > + return dev_err_probe(dev, ret, "failed to read refin regulator\n");
> > + else
> > + st->ref_uV = ret * 2;
> > +
> > + return 0;
> > +}
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783028033.git.marcelo.schmitt@analog.com?part=2
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
2026-07-03 17:14 ` Marcelo Schmitt
@ 2026-07-04 23:22 ` Jonathan Cameron
0 siblings, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-07-04 23:22 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: sashiko-reviews, Marcelo Schmitt, devicetree, robh, conor+dt,
linux-iio
On Fri, 3 Jul 2026 14:14:50 -0300
Marcelo Schmitt <marcelo.schmitt1@gmail.com> wrote:
> On 07/02, sashiko-bot@kernel.org wrote:
> > Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> > - [High] Hardware protocol violation: CNV is held high during SPI read
> > - [High] SPI bus not locked during ADC conversion phase
> > - [Medium] Incorrect scale calculation for LTC2338-18 and similar ADCs
> > - [Low] Undefined direct mode macros lead to build failure
> > --
> >
> > commit b74b536d18af0084ea31bd1e1e8cf9454da5e29c
> > Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
> >
> > iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
> >
> > This commit adds support for the Analog Devices LTC2378-20 and similar
> > analog-to-digital converters. It introduces a new IIO driver using the
> > SPI subsystem.
> >
> > > diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> > > new file mode 100644
> > > index 0000000000000..b5cf2e974daca
> > > --- /dev/null
> > > +++ b/drivers/iio/adc/ltc2378.c
> > > @@ -0,0 +1,404 @@
> >
> > [ ... ]
> >
> > > +static const struct ltc2378_chip_info ltc2338_18_chip_info = {
> > > + .name = "ltc2338-18",
> > > + .internal_ref_uV = 4096000,
> > > + .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
> > > +};
> >
> > [ ... ]
> >
> > > +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);
> > > + fsleep(4);
> >
> > [Severity: High]
> > Can this sequence cause data corruption on a shared SPI bus?
> >
> > The CNV pin is toggled and an fsleep(4) occurs before spi_sync_transfer()
> > locks the bus. If another SPI transaction occurs during this sleep window,
> > could it toggle the SCK line and prematurely clock data out of the ADC?
> Makes sense.
> >
> > Should the conversion-and-read sequence be wrapped in spi_bus_lock()
> > and spi_bus_unlock()?
> Because these chips don't have CS line, it seems that locking the bus actually
> makes sense.
What does it even mean? If there is no CS then the bus can only sensibly
have one device on it. I'm not sure if the SPI core enforces that but
in practice you should never see a board that has multiple devices.
Hence locking could be at the device driver level rather than the bus.
I guess bus locking would provide the same conditions and maybe acts
as documentation.
Jonathan
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs
2026-07-02 21:45 ` [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
2026-07-02 22:02 ` sashiko-bot
@ 2026-07-04 23:43 ` Jonathan Cameron
1 sibling, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-07-04 23:43 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-kernel, nuno.sa, Michael.Hennerich,
dlechner, andy, robh, krzk+dt, conor+dt, julianbraha,
marcelo.schmitt1
On Thu, 2 Jul 2026 18:45:27 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> Support for LTC2378-20 and similar analog-to-digital converters.
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
Just a couple of trivial additional comments as you are spinning
again anyway.
> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> new file mode 100644
> index 000000000000..b5cf2e974dac
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378.c
> @@ -0,0 +1,404 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Analog Devices LTC2378 ADC series driver
> + *
> + * Copyright (C) 2026 Analog Devices Inc.
> + * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
> + */
> +
> +#include <linux/bitops.h>
> +#include <linux/bits.h>
> +#include <linux/cleanup.h>
> +#include <linux/delay.h>
> +#include <linux/err.h>
> +#include <linux/gpio/consumer.h>
> +#include <linux/mod_devicetable.h>
Drop this one. Uwe just had a series merged that moved
the relevant include path into spi/spi.h
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/diff/include/linux/spi/spi.h?id=ecca1d63c1eadbbb38ceab82de0f7adfbc2b465d
I'll probably rebase the iio tree on rc2 or merge that in
to resolve any conflicts.
> +#include <linux/module.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/spi/spi.h>
> +#include <linux/types.h>
> +#include <linux/units.h>
> +
> +#include <linux/iio/iio.h>
> +#include <linux/iio/types.h>
We had some discussion about this recently and IIRC concluded that types.h will
always be included by iio.h so maybe just iio.h is enough.
I don't care strongly either way though.
> +
> +static int ltc2378_channel_single_read(const struct iio_chan_spec *chan,
> + struct ltc2378_state *st, int *val)
> +{
> + 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 (chan->scan_type.endianness == IIO_BE) {
> + if (chan->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 {
Trivial but can we have a /* IIO_CPU */ for this else.
I briefly wondered why for little endian we didn't need to use lexx_to_cpu()
before registering this is for offload where the spi controller is dealing
with it (and hence IIO_CPU for the endian)
> + if (chan->scan_type.realbits > 16)
> + sample = st->scan.data.sample_buf32;
> + else
> + sample = st->scan.data.sample_buf16;
> + }
> +
> + sample >>= chan->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;
> +}
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture
2026-07-02 21:44 [PATCH v5 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
2026-07-02 21:45 ` [PATCH v5 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
2026-07-02 21:45 ` [PATCH v5 2/4] iio: adc: ltc2378: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
@ 2026-07-02 21:45 ` Marcelo Schmitt
2026-07-02 22:07 ` sashiko-bot
2026-07-04 23:48 ` Jonathan Cameron
2026-07-02 21:46 ` [PATCH v5 4/4] iio: adc: ltc2378: Enable triggered buffer " Marcelo Schmitt
3 siblings, 2 replies; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-02 21:45 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, julianbraha, marcelo.schmitt1
Make use of SPI transfer offloading to speed up data capture, enabling data
acquisition at faster sample rates (up to 2 MSPS).
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v4 -> v5:
- Added checks for PWM parameters after both CNV and Offload trigger PWMs
have been configured.
- Return EDOM instead of EIO when failing to achieve a specific PWM configuration.
- Set single-read transfer bits_per_word so the SPI subsystem re-arranges data
to CPU endianness, enabling us to reuse offload_chan specifications for
single-shot reads.
- Replaced open-coded check by in_range().
- Used conventional loop count decrement pattern instead of count increment.
- Renamed max_sample_rate_hz -> max_sample_rate_Hz.
- A few other minor codestyle improvements.
drivers/iio/adc/Kconfig | 6 +
drivers/iio/adc/ltc2378.c | 375 +++++++++++++++++++++++++++++++++++++-
2 files changed, 378 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 07a8a5911a09..8d2fadbf74b1 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -946,6 +946,12 @@ config LTC2378
depends on SPI
depends on REGULATOR || COMPILE_TEST
depends on GPIOLIB
+ depends on PWM
+ select IIO_BUFFER
+ select IIO_BUFFER_DMA
+ select IIO_BUFFER_DMAENGINE
+ select SPI_OFFLOAD
+ select SPI_OFFLOAD_TRIGGER_PWM
help
Say yes here to build support for Analog Devices LTC2378-20 and
similar analog to digital converters.
diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
index b5cf2e974dac..b25602d91bbe 100644
--- a/drivers/iio/adc/ltc2378.c
+++ b/drivers/iio/adc/ltc2378.c
@@ -12,16 +12,28 @@
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
+#include <linux/math64.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/regulator/consumer.h>
+#include <linux/pwm.h>
#include <linux/spi/spi.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#include <linux/time64.h>
#include <linux/types.h>
#include <linux/units.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>
+#define LTC2378_TDSDOBUSYL_NS 5
+#define LTC2378_TBUSYLH_NS 13
+#define LTC2378_TCNV_HIGH_NS 20
+
#define LTC2378_DIFF_CHANNEL(_sign, _real_bits, _storage_bits, _offload) \
{ \
.type = IIO_VOLTAGE, \
@@ -46,10 +58,19 @@
#define LTC2378_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
LTC2378_DIFF_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16), 0)
+#define LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(_real_bits) \
+ LTC2378_DIFF_CHANNEL(1, (_real_bits), 32, 1)
+
+#define LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
+ LTC2378_DIFF_CHANNEL(0, (_real_bits), 32, 1)
+
struct ltc2378_chip_info {
const char *name;
unsigned int internal_ref_uV;
struct iio_chan_spec chan;
+ struct iio_chan_spec offload_chan;
+ unsigned int max_sample_rate_Hz;
+ unsigned int tconv_ns;
};
struct ltc2378_state {
@@ -58,6 +79,15 @@ struct ltc2378_state {
struct spi_device *spi;
struct spi_transfer xfer;
int ref_uV;
+ 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;
+ unsigned int sample_freq_range[3];
/*
* DMA (thus cache coherency maintenance) requires the
@@ -78,101 +108,161 @@ static const struct ltc2378_chip_info ltc2338_18_chip_info = {
.name = "ltc2338-18",
.internal_ref_uV = 4096000,
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 1 * HZ_PER_MHZ,
+ .tconv_ns = 527,
};
static const struct ltc2378_chip_info ltc2364_16_chip_info = {
.name = "ltc2364-16",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
};
static const struct ltc2378_chip_info ltc2364_18_chip_info = {
.name = "ltc2364-18",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
};
static const struct ltc2378_chip_info ltc2367_16_chip_info = {
.name = "ltc2367-16",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
};
static const struct ltc2378_chip_info ltc2367_18_chip_info = {
.name = "ltc2367-18",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
};
static const struct ltc2378_chip_info ltc2368_16_chip_info = {
.name = "ltc2368-16",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 1 * HZ_PER_MHZ,
+ .tconv_ns = 527,
};
static const struct ltc2378_chip_info ltc2368_18_chip_info = {
.name = "ltc2368-18",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 1 * HZ_PER_MHZ,
+ .tconv_ns = 527,
};
static const struct ltc2378_chip_info ltc2369_18_chip_info = {
.name = "ltc2369-18",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 1600 * HZ_PER_KHZ,
+ .tconv_ns = 412,
};
static const struct ltc2378_chip_info ltc2370_16_chip_info = {
.name = "ltc2370-16",
.chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 2 * HZ_PER_MHZ,
+ .tconv_ns = 322,
};
static const struct ltc2378_chip_info ltc2376_16_chip_info = {
.name = "ltc2376-16",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
};
static const struct ltc2378_chip_info ltc2376_18_chip_info = {
.name = "ltc2376-18",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
};
static const struct ltc2378_chip_info ltc2376_20_chip_info = {
.name = "ltc2376-20",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(20),
+ .max_sample_rate_Hz = 250 * HZ_PER_KHZ,
+ .tconv_ns = 3000,
};
static const struct ltc2378_chip_info ltc2377_16_chip_info = {
.name = "ltc2377-16",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
};
static const struct ltc2378_chip_info ltc2377_18_chip_info = {
.name = "ltc2377-18",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
};
static const struct ltc2378_chip_info ltc2377_20_chip_info = {
.name = "ltc2377-20",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(20),
+ .max_sample_rate_Hz = 500 * HZ_PER_KHZ,
+ .tconv_ns = 1500,
};
static const struct ltc2378_chip_info ltc2378_16_chip_info = {
.name = "ltc2378-16",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 1 * HZ_PER_MHZ,
+ .tconv_ns = 527,
};
static const struct ltc2378_chip_info ltc2378_18_chip_info = {
.name = "ltc2378-18",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 1 * HZ_PER_MHZ,
+ .tconv_ns = 527,
};
static const struct ltc2378_chip_info ltc2378_20_chip_info = {
.name = "ltc2378-20",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(20),
+ .max_sample_rate_Hz = 1 * HZ_PER_MHZ,
+ .tconv_ns = 675,
};
static const struct ltc2378_chip_info ltc2379_18_chip_info = {
.name = "ltc2379-18",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
+ .max_sample_rate_Hz = 1600 * HZ_PER_KHZ,
+ .tconv_ns = 412,
};
static const struct ltc2378_chip_info ltc2380_16_chip_info = {
.name = "ltc2380-16",
.chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
+ .max_sample_rate_Hz = 2 * HZ_PER_MHZ,
+ .tconv_ns = 322,
};
static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
@@ -254,7 +344,136 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
*val2 = chan->scan_type.realbits;
return IIO_VAL_FRACTIONAL_LOG2;
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *val = st->cnv_Hz;
+ return IIO_VAL_INT;
+ default:
+ return -EINVAL;
+ }
+}
+static int ltc2378_read_avail(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ const int **vals, int *type, int *length, long mask)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ *vals = st->sample_freq_range;
+ *type = IIO_VAL_INT;
+ return IIO_AVAIL_RANGE;
+ default:
+ return -EINVAL;
+ }
+}
+
+/*
+ * 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;
+ unsigned int count;
+ u64 offload_offset_ns;
+ int ret;
+
+ if (freq_Hz == 0)
+ return -EINVAL;
+
+ if (!in_range(freq_Hz, 1, 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 (or reach a try count limit).
+ */
+ count = 100;
+ 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 (count-- && cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);
+
+ /* Check the minimum CNV high time is met */
+ if (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS)
+ return -EDOM;
+
+ /*
+ * 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;
+ count = 100;
+ 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 (count-- && config->periodic.offset_ns < min_read_offset);
+
+ /* Check the minimum CNV to SCLK delay is met */
+ if (config->periodic.offset_ns < min_read_offset)
+ return -EDOM;
+
+ /* Check the PWM periods remain the same */
+ offload_period_ns = div64_u64(HZ_PER_GHZ, config->periodic.frequency_hz);
+ if (cnv_wf.period_length_ns != offload_period_ns)
+ return -EDOM;
+
+ st->cnv_wf = cnv_wf;
+ st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns);
+
+ return 0;
+}
+
+static int ltc2378_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long mask)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+
+ IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+ if (IIO_DEV_ACQUIRE_FAILED(claim))
+ return -EBUSY;
+
+ switch (mask) {
+ case IIO_CHAN_INFO_SAMP_FREQ:
+ return ltc2378_update_conversion_rate(st, val);
default:
return -EINVAL;
}
@@ -264,6 +483,111 @@ static const struct iio_info ltc2378_iio_info = {
.read_raw = <c2378_read_raw,
};
+static const struct iio_info ltc2378_offload_iio_info = {
+ .read_raw = <c2378_read_raw,
+ .read_avail = <c2378_read_avail,
+ .write_raw = <c2378_write_raw,
+};
+
+static int ltc2378_prepare_offload_message(struct device *dev,
+ struct ltc2378_state *st)
+{
+ unsigned int resolution = st->info->offload_chan.scan_type.realbits;
+
+ st->offload_xfer.bits_per_word = resolution;
+ st->offload_xfer.len = spi_bpw_to_bytes(resolution);
+ st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+
+ /* 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);
+}
+
+static int ltc2378_offload_buffer_postenable(struct iio_dev *indio_dev)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+ int ret;
+
+ ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, true);
+ if (ret)
+ return ret;
+
+ ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
+ &st->offload_trigger_config);
+ if (ret)
+ goto out_pwm_disable;
+
+ return 0;
+
+out_pwm_disable:
+ pwm_disable(st->cnv_trigger);
+ return ret;
+}
+
+static int ltc2378_offload_buffer_predisable(struct iio_dev *indio_dev)
+{
+ struct ltc2378_state *st = iio_priv(indio_dev);
+
+ spi_offload_trigger_disable(st->offload, st->offload_trigger);
+ pwm_disable(st->cnv_trigger);
+
+ return 0;
+}
+
+static const struct iio_buffer_setup_ops ltc2378_offload_buffer_ops = {
+ .postenable = <c2378_offload_buffer_postenable,
+ .predisable = <c2378_offload_buffer_predisable,
+};
+
+static int ltc2378_spi_offload_setup(struct iio_dev *indio_dev,
+ struct ltc2378_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ struct dma_chan *rx_dma;
+
+ indio_dev->setup_ops = <c2378_offload_buffer_ops;
+
+ st->offload_trigger = devm_spi_offload_trigger_get(dev, st->offload,
+ SPI_OFFLOAD_TRIGGER_PERIODIC);
+ if (IS_ERR(st->offload_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->offload_trigger),
+ "failed to get offload trigger\n");
+
+ st->offload_trigger_config.type = SPI_OFFLOAD_TRIGGER_PERIODIC;
+
+ rx_dma = devm_spi_offload_rx_stream_request_dma_chan(dev, st->offload);
+ if (IS_ERR(rx_dma))
+ return dev_err_probe(dev, PTR_ERR(rx_dma), "failed to get offload RX DMA\n");
+
+ return devm_iio_dmaengine_buffer_setup_with_handle(dev, indio_dev, rx_dma,
+ IIO_BUFFER_DIRECTION_IN);
+}
+
+static int ltc2378_pwm_get(struct ltc2378_state *st)
+{
+ struct device *dev = &st->spi->dev;
+
+ st->cnv_trigger = devm_pwm_get(dev, NULL);
+ if (IS_ERR(st->cnv_trigger))
+ return dev_err_probe(dev, PTR_ERR(st->cnv_trigger),
+ "failed to get cnv pwm\n");
+
+ /*
+ * Disable the PWM connected to CNV in case it was left running by
+ * something else.
+ */
+ pwm_disable(st->cnv_trigger);
+
+ return 0;
+}
+
+static const struct spi_offload_config ltc2378_offload_config = {
+ .capability_flags = SPI_OFFLOAD_CAP_TRIGGER |
+ SPI_OFFLOAD_CAP_RX_STREAM_DMA,
+};
+
static int ltc2378_refin_setup(struct device *dev, struct ltc2378_state *st)
{
int ret;
@@ -322,7 +646,6 @@ static int ltc2378_probe(struct spi_device *spi)
return ret;
indio_dev->name = st->info->name;
- indio_dev->info = <c2378_iio_info;
indio_dev->modes = INDIO_DIRECT_MODE;
st->cnv_gpio = devm_gpiod_get(dev, "cnv", GPIOD_OUT_LOW);
@@ -330,8 +653,53 @@ static int ltc2378_probe(struct spi_device *spi)
return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
"failed to get CNV GPIO");
- indio_dev->channels = &st->info->chan;
- indio_dev->num_channels = 1;
+ st->offload = devm_spi_offload_get(dev, spi, <c2378_offload_config);
+ ret = PTR_ERR_OR_ZERO(st->offload);
+ /* Fall back to low speed usage when no SPI offload is available. */
+ if (ret == -ENODEV) {
+ indio_dev->info = <c2378_iio_info;
+ indio_dev->channels = &st->info->chan;
+ indio_dev->num_channels = 1;
+ } else if (ret) {
+ return dev_err_probe(dev, ret, "failed to get offload\n");
+ } else {
+ indio_dev->info = <c2378_offload_iio_info;
+ indio_dev->channels = &st->info->offload_chan;
+ indio_dev->num_channels = 1;
+ 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 dev_err_probe(dev, ret, "failed to get PWM\n");
+
+ st->sample_freq_range[0] = 1; /* min */
+ st->sample_freq_range[1] = 1; /* step */
+ st->sample_freq_range[2] = st->info->max_sample_rate_Hz; /* max */
+
+ /*
+ * Start with a slower sampling rate so there is some room for
+ * adjusting the sample averaging and the sampling frequency
+ * without hitting the maximum conversion rate.
+ */
+ ret = ltc2378_update_conversion_rate(st, st->info->max_sample_rate_Hz >> 4);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to set offload samp freq\n");
+
+ ret = ltc2378_prepare_offload_message(&spi->dev, st);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to optimize SPI message\n");
+
+ /*
+ * Set single-read transfer bits_per_word so the SPI subsystem
+ * rearanges data to CPU endianness, enabling us to reuse
+ * offload_chan specifications for single-shot reads.
+ */
+ st->xfer.bits_per_word = st->info->offload_chan.scan_type.realbits;
+ }
st->xfer.rx_buf = &st->scan.data;
st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits);
@@ -402,3 +770,4 @@ module_spi_driver(ltc2378_driver);
MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_DMAENGINE_BUFFER");
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread* Re: [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture
2026-07-02 21:45 ` [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
@ 2026-07-02 22:07 ` sashiko-bot
2026-07-03 17:19 ` Marcelo Schmitt
2026-07-04 23:48 ` Jonathan Cameron
1 sibling, 1 reply; 15+ messages in thread
From: sashiko-bot @ 2026-07-02 22:07 UTC (permalink / raw)
To: Marcelo Schmitt; +Cc: robh, devicetree, conor+dt
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
- [Critical] Access to non-existent `offset_ns` field in `struct spi_offload_trigger_periodic` causes a build failure.
- [Critical] The macro `HZ_PER_GHZ` is used but never defined anywhere in the kernel, causing a build failure.
- [High] Mismatch between SPI offload transfer length and IIO channel storage bits for 16-bit devices causes buffer data corruption.
- [High] Unassigned `length` pointer in `ltc2378_read_avail` API callback leaves caller variable uninitialized.
- [High] Unpredictable phase shift between two independent PWM channels risks hardware data corruption.
- [High] Forcing unsupported `bits_per_word` breaks single-shot reads on standard SPI controllers.
- [Medium] `st->offload_trigger_config` is updated in-place and not rolled back on validation failure, corrupting driver state.
--
commit 7c29c089819a4620f86fae01e267747bb7a266ff
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
iio: adc: ltc2378: Enable high-speed data capture
Make use of SPI transfer offloading to speed up data capture, enabling data
acquisition at faster sample rates (up to 2 MSPS).
> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> index b5cf2e974daca..b25602d91bbe1 100644
> --- a/drivers/iio/adc/ltc2378.c
> +++ b/drivers/iio/adc/ltc2378.c
[ ... ]
> @@ -46,10 +58,19 @@
> #define LTC2378_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
> LTC2378_DIFF_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16), 0)
>
> +#define LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(_real_bits) \
> + LTC2378_DIFF_CHANNEL(1, (_real_bits), 32, 1)
> +
> +#define LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
> + LTC2378_DIFF_CHANNEL(0, (_real_bits), 32, 1)
> +
[Severity: High]
Does hardcoding the storagebits to 32 here mismatch with the SPI transfer
length for 16-bit devices?
When ltc2378_prepare_offload_message() configures the transfer, it sets the
length to spi_bpw_to_bytes(16), which is 2 bytes. Since the IIO subsystem
expects 4 bytes per sample based on these channel definitions, could this
cause buffer reads to be misaligned and return garbage data to userspace?
[ ... ]
> @@ -254,7 +344,136 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
> *val2 = chan->scan_type.realbits;
>
> return IIO_VAL_FRACTIONAL_LOG2;
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *val = st->cnv_Hz;
> + return IIO_VAL_INT;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static int ltc2378_read_avail(struct iio_dev *indio_dev,
> + struct iio_chan_spec const *chan,
> + const int **vals, int *type, int *length, long mask)
> +{
> + struct ltc2378_state *st = iio_priv(indio_dev);
> +
> + switch (mask) {
> + case IIO_CHAN_INFO_SAMP_FREQ:
> + *vals = st->sample_freq_range;
> + *type = IIO_VAL_INT;
> + return IIO_AVAIL_RANGE;
[Severity: High]
Should this callback also initialize the length pointer?
Since this returns IIO_AVAIL_RANGE successfully but doesn't assign *length,
callers like iio_read_channel_info_avail() in the core might use an
uninitialized stack variable for the array size. Could this lead to
out-of-bounds array reads?
> + default:
> + return -EINVAL;
> + }
> +}
[ ... ]
> +static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
> +{
[ ... ]
> + /*
> + * 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);
[Severity: Critical]
Is HZ_PER_GHZ defined anywhere in the kernel?
Looking through the tree and include/linux/units.h, this macro doesn't appear
to exist. Won't this cause an unconditional build failure?
> + min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS;
> + offload_offset_ns = min_read_offset;
> + count = 100;
> + do {
> + config->periodic.offset_ns = offload_offset_ns;
[Severity: Critical]
Does struct spi_offload_trigger_periodic actually have an offset_ns field?
According to include/linux/spi/offload/types.h, struct
spi_offload_trigger_periodic only contains the frequency_hz field. This seems
like it will cause a build error.
> + ret = spi_offload_trigger_validate(st->offload_trigger, config);
> + if (ret)
> + return ret;
[Severity: Medium]
If validation fails here, does the in-place modification of the shared config
struct get rolled back?
Since config points directly to st->offload_trigger_config, returning early
leaves the device state with rejected values. If the IIO buffer is later
enabled, could the driver use these invalid config values to start the
offload trigger?
> + offload_offset_ns += 10;
> + } while (count-- && config->periodic.offset_ns < min_read_offset);
[ ... ]
> +static int ltc2378_offload_buffer_postenable(struct iio_dev *indio_dev)
> +{
> + struct ltc2378_state *st = iio_priv(indio_dev);
> + int ret;
> +
> + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, true);
> + if (ret)
> + return ret;
> +
> + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
> + &st->offload_trigger_config);
[Severity: High]
Does starting these two clock sources sequentially result in an unpredictable
phase shift?
Since st->cnv_trigger and st->offload_trigger are independent and enabled one
after the other in software, CPU execution delays might cause an uncontrolled
phase alignment. The LTC2378 datasheet forbids SCLK toggling during the
conversion phase. Could this random phase shift cause SPI transactions to
overlap with the conversion window and corrupt the sampled data?
> + if (ret)
> + goto out_pwm_disable;
> +
> + return 0;
[ ... ]
> @@ -330,8 +653,53 @@ static int ltc2378_probe(struct spi_device *spi)
[ ... ]
> + /*
> + * Set single-read transfer bits_per_word so the SPI subsystem
> + * rearanges data to CPU endianness, enabling us to reuse
> + * offload_chan specifications for single-shot reads.
> + */
> + st->xfer.bits_per_word = st->info->offload_chan.scan_type.realbits;
[Severity: High]
Is it safe to force this bits_per_word on the shared transfer struct used for
regular single-shot reads?
Most generic SPI host controllers only natively support standard word widths
(like 8, 16, 32). If bits_per_word is set to an arbitrary resolution like 18,
won't the SPI core's __spi_validate reject the transfer with -EINVAL and break
sysfs single-shot reads?
> + }
>
> st->xfer.rx_buf = &st->scan.data;
> st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits);
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783028033.git.marcelo.schmitt@analog.com?part=3
^ permalink raw reply [flat|nested] 15+ messages in thread* Re: [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture
2026-07-02 22:07 ` sashiko-bot
@ 2026-07-03 17:19 ` Marcelo Schmitt
0 siblings, 0 replies; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-03 17:19 UTC (permalink / raw)
To: sashiko-reviews
Cc: Marcelo Schmitt, robh, devicetree, conor+dt, linux-iio,
marcelo.schmitt1
On 07/02, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
> - [Critical] Access to non-existent `offset_ns` field in `struct spi_offload_trigger_periodic` causes a build failure.
> - [Critical] The macro `HZ_PER_GHZ` is used but never defined anywhere in the kernel, causing a build failure.
> - [High] Mismatch between SPI offload transfer length and IIO channel storage bits for 16-bit devices causes buffer data corruption.
> - [High] Unassigned `length` pointer in `ltc2378_read_avail` API callback leaves caller variable uninitialized.
> - [High] Unpredictable phase shift between two independent PWM channels risks hardware data corruption.
> - [High] Forcing unsupported `bits_per_word` breaks single-shot reads on standard SPI controllers.
> - [Medium] `st->offload_trigger_config` is updated in-place and not rolled back on validation failure, corrupting driver state.
> --
>
> commit 7c29c089819a4620f86fae01e267747bb7a266ff
> Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
>
> iio: adc: ltc2378: Enable high-speed data capture
>
> Make use of SPI transfer offloading to speed up data capture, enabling data
> acquisition at faster sample rates (up to 2 MSPS).
>
> > diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> > index b5cf2e974daca..b25602d91bbe1 100644
> > --- a/drivers/iio/adc/ltc2378.c
> > +++ b/drivers/iio/adc/ltc2378.c
> [ ... ]
> > @@ -46,10 +58,19 @@
> > #define LTC2378_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
> > LTC2378_DIFF_CHANNEL(0, _real_bits, (((_real_bits) > 16) ? 32 : 16), 0)
> >
> > +#define LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(_real_bits) \
> > + LTC2378_DIFF_CHANNEL(1, (_real_bits), 32, 1)
> > +
> > +#define LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(_real_bits) \
> > + LTC2378_DIFF_CHANNEL(0, (_real_bits), 32, 1)
> > +
>
> [Severity: High]
> Does hardcoding the storagebits to 32 here mismatch with the SPI transfer
> length for 16-bit devices?
>
> When ltc2378_prepare_offload_message() configures the transfer, it sets the
> length to spi_bpw_to_bytes(16), which is 2 bytes. Since the IIO subsystem
> expects 4 bytes per sample based on these channel definitions, could this
> cause buffer reads to be misaligned and return garbage data to userspace?
The available HDL only supports pushing 32-bit data elements to DMA buffers in
CPU endianness. For 16-bit precision parts, 32-bit elements (in CPU endianness)
containing the 16-bit data are pushed to DMA. For the offload use case, the IIO
buffer is configured for 32 storage bits in CPU endianness so data is correctly
aligned in user space (despite 2 out of the 4 bytes being always zeroed). I'll
add a comment with the above explanation.
>
> [ ... ]
> > @@ -254,7 +344,136 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
> > *val2 = chan->scan_type.realbits;
> >
> > return IIO_VAL_FRACTIONAL_LOG2;
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + *val = st->cnv_Hz;
> > + return IIO_VAL_INT;
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> > +
> > +static int ltc2378_read_avail(struct iio_dev *indio_dev,
> > + struct iio_chan_spec const *chan,
> > + const int **vals, int *type, int *length, long mask)
> > +{
> > + struct ltc2378_state *st = iio_priv(indio_dev);
> > +
> > + switch (mask) {
> > + case IIO_CHAN_INFO_SAMP_FREQ:
> > + *vals = st->sample_freq_range;
> > + *type = IIO_VAL_INT;
> > + return IIO_AVAIL_RANGE;
>
> [Severity: High]
> Should this callback also initialize the length pointer?
No.
>
> Since this returns IIO_AVAIL_RANGE successfully but doesn't assign *length,
> callers like iio_read_channel_info_avail() in the core might use an
> uninitialized stack variable for the array size. Could this lead to
> out-of-bounds array reads?
>
> > + default:
> > + return -EINVAL;
> > + }
> > +}
> [ ... ]
> > +static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
> > +{
> [ ... ]
> > + /*
> > + * 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);
>
> [Severity: Critical]
> Is HZ_PER_GHZ defined anywhere in the kernel?
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/units.h?h=v7.2-rc1#n50
>
> Looking through the tree and include/linux/units.h, this macro doesn't appear
> to exist. Won't this cause an unconditional build failure?
>
> > + min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS;
> > + offload_offset_ns = min_read_offset;
> > + count = 100;
> > + do {
> > + config->periodic.offset_ns = offload_offset_ns;
>
> [Severity: Critical]
> Does struct spi_offload_trigger_periodic actually have an offset_ns field?
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/spi/offload/types.h?h=v7.2-rc1#n70
>
> According to include/linux/spi/offload/types.h, struct
> spi_offload_trigger_periodic only contains the frequency_hz field. This seems
> like it will cause a build error.
>
> > + ret = spi_offload_trigger_validate(st->offload_trigger, config);
> > + if (ret)
> > + return ret;
>
> [Severity: Medium]
> If validation fails here, does the in-place modification of the shared config
> struct get rolled back?
>
> Since config points directly to st->offload_trigger_config, returning early
> leaves the device state with rejected values. If the IIO buffer is later
> enabled, could the driver use these invalid config values to start the
> offload trigger?
Makes no difference. The PWM configurations are recalculated and re-validated
every time the buffer is enabled. There's very little to no benefit in rolling
back PWM configurations.
>
> > + offload_offset_ns += 10;
> > + } while (count-- && config->periodic.offset_ns < min_read_offset);
> [ ... ]
> > +static int ltc2378_offload_buffer_postenable(struct iio_dev *indio_dev)
> > +{
> > + struct ltc2378_state *st = iio_priv(indio_dev);
> > + int ret;
> > +
> > + ret = pwm_set_waveform_might_sleep(st->cnv_trigger, &st->cnv_wf, true);
> > + if (ret)
> > + return ret;
> > +
> > + ret = spi_offload_trigger_enable(st->offload, st->offload_trigger,
> > + &st->offload_trigger_config);
>
> [Severity: High]
> Does starting these two clock sources sequentially result in an unpredictable
> phase shift?
>
> Since st->cnv_trigger and st->offload_trigger are independent and enabled one
> after the other in software, CPU execution delays might cause an uncontrolled
> phase alignment. The LTC2378 datasheet forbids SCLK toggling during the
> conversion phase. Could this random phase shift cause SPI transactions to
> overlap with the conversion window and corrupt the sampled data?
That's what the offset_ns field is for.
>
> > + if (ret)
> > + goto out_pwm_disable;
> > +
> > + return 0;
> [ ... ]
> > @@ -330,8 +653,53 @@ static int ltc2378_probe(struct spi_device *spi)
> [ ... ]
> > + /*
> > + * Set single-read transfer bits_per_word so the SPI subsystem
> > + * rearanges data to CPU endianness, enabling us to reuse
> > + * offload_chan specifications for single-shot reads.
> > + */
> > + st->xfer.bits_per_word = st->info->offload_chan.scan_type.realbits;
>
> [Severity: High]
> Is it safe to force this bits_per_word on the shared transfer struct used for
> regular single-shot reads?
>
> Most generic SPI host controllers only natively support standard word widths
> (like 8, 16, 32). If bits_per_word is set to an arbitrary resolution like 18,
> won't the SPI core's __spi_validate reject the transfer with -EINVAL and break
> sysfs single-shot reads?
Correct, offload support assumes a controller capable of supporting fine grained
number of bits_per_word.
>
> > + }
> >
> > st->xfer.rx_buf = &st->scan.data;
> > st->xfer.len = spi_bpw_to_bytes(indio_dev->channels[0].scan_type.realbits);
>
> --
> Sashiko AI review · https://sashiko.dev/#/patchset/cover.1783028033.git.marcelo.schmitt@analog.com?part=3
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture
2026-07-02 21:45 ` [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
2026-07-02 22:07 ` sashiko-bot
@ 2026-07-04 23:48 ` Jonathan Cameron
1 sibling, 0 replies; 15+ messages in thread
From: Jonathan Cameron @ 2026-07-04 23:48 UTC (permalink / raw)
To: Marcelo Schmitt
Cc: linux-iio, devicetree, linux-kernel, nuno.sa, Michael.Hennerich,
dlechner, andy, robh, krzk+dt, conor+dt, julianbraha,
marcelo.schmitt1
On Thu, 2 Jul 2026 18:45:49 -0300
Marcelo Schmitt <marcelo.schmitt@analog.com> wrote:
> Make use of SPI transfer offloading to speed up data capture, enabling data
> acquisition at faster sample rates (up to 2 MSPS).
>
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> index b5cf2e974dac..b25602d91bbe 100644
> --- a/drivers/iio/adc/ltc2378.c
> +++ b/drivers/iio/adc/ltc2378.c
> @@ -12,16 +12,28 @@
> #include <linux/delay.h>
> #include <linux/err.h>
> #include <linux/gpio/consumer.h>
> +#include <linux/math64.h>
> #include <linux/mod_devicetable.h>
> #include <linux/module.h>
> #include <linux/regulator/consumer.h>
> +#include <linux/pwm.h>
> #include <linux/spi/spi.h>
> +#include <linux/spi/offload/consumer.h>
> +#include <linux/spi/offload/types.h>
> +#include <linux/time64.h>
> #include <linux/types.h>
> #include <linux/units.h>
>
> +#include <linux/iio/buffer.h>
> +#include <linux/iio/buffer-dmaengine.h>
> #include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>
I'm probably missing something. Why this include?
Jonathan
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH v5 4/4] iio: adc: ltc2378: Enable triggered buffer data capture
2026-07-02 21:44 [PATCH v5 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
` (2 preceding siblings ...)
2026-07-02 21:45 ` [PATCH v5 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
@ 2026-07-02 21:46 ` Marcelo Schmitt
3 siblings, 0 replies; 15+ messages in thread
From: Marcelo Schmitt @ 2026-07-02 21:46 UTC (permalink / raw)
To: linux-iio, devicetree, linux-kernel
Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
conor+dt, julianbraha, marcelo.schmitt1
Enable users to run triggered data captures with LTC2378 and similar ADCs.
Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
Change log v4 -> v5:
- Dropped error message on devm_iio_triggered_buffer_setup() fail.
drivers/iio/adc/Kconfig | 1 +
drivers/iio/adc/ltc2378.c | 75 +++++++++++++++++++++++++++------------
2 files changed, 53 insertions(+), 23 deletions(-)
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 8d2fadbf74b1..ac162425dc91 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -950,6 +950,7 @@ config LTC2378
select IIO_BUFFER
select IIO_BUFFER_DMA
select IIO_BUFFER_DMAENGINE
+ select IIO_TRIGGERED_BUFFER
select SPI_OFFLOAD
select SPI_OFFLOAD_TRIGGER_PWM
help
diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
index b25602d91bbe..bcaecd484ab3 100644
--- a/drivers/iio/adc/ltc2378.c
+++ b/drivers/iio/adc/ltc2378.c
@@ -6,6 +6,7 @@
* Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
*/
+#include <linux/array_size.h>
#include <linux/bitops.h>
#include <linux/bits.h>
#include <linux/cleanup.h>
@@ -28,6 +29,8 @@
#include <linux/iio/buffer-dmaengine.h>
#include <linux/iio/iio.h>
#include <linux/iio/sysfs.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/iio/trigger_consumer.h>
#include <linux/iio/types.h>
#define LTC2378_TDSDOBUSYL_NS 5
@@ -67,7 +70,7 @@
struct ltc2378_chip_info {
const char *name;
unsigned int internal_ref_uV;
- struct iio_chan_spec chan;
+ struct iio_chan_spec chan[2]; /* 1 physical chan + 1 timestamp chan */
struct iio_chan_spec offload_chan;
unsigned int max_sample_rate_Hz;
unsigned int tconv_ns;
@@ -107,7 +110,7 @@ struct ltc2378_state {
static const struct ltc2378_chip_info ltc2338_18_chip_info = {
.name = "ltc2338-18",
.internal_ref_uV = 4096000,
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 1 * HZ_PER_MHZ,
.tconv_ns = 527,
@@ -115,7 +118,7 @@ static const struct ltc2378_chip_info ltc2338_18_chip_info = {
static const struct ltc2378_chip_info ltc2364_16_chip_info = {
.name = "ltc2364-16",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 250 * HZ_PER_KHZ,
.tconv_ns = 3000,
@@ -123,7 +126,7 @@ static const struct ltc2378_chip_info ltc2364_16_chip_info = {
static const struct ltc2378_chip_info ltc2364_18_chip_info = {
.name = "ltc2364-18",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 250 * HZ_PER_KHZ,
.tconv_ns = 3000,
@@ -131,7 +134,7 @@ static const struct ltc2378_chip_info ltc2364_18_chip_info = {
static const struct ltc2378_chip_info ltc2367_16_chip_info = {
.name = "ltc2367-16",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 500 * HZ_PER_KHZ,
.tconv_ns = 1500,
@@ -139,7 +142,7 @@ static const struct ltc2378_chip_info ltc2367_16_chip_info = {
static const struct ltc2378_chip_info ltc2367_18_chip_info = {
.name = "ltc2367-18",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 500 * HZ_PER_KHZ,
.tconv_ns = 1500,
@@ -147,7 +150,7 @@ static const struct ltc2378_chip_info ltc2367_18_chip_info = {
static const struct ltc2378_chip_info ltc2368_16_chip_info = {
.name = "ltc2368-16",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 1 * HZ_PER_MHZ,
.tconv_ns = 527,
@@ -155,7 +158,7 @@ static const struct ltc2378_chip_info ltc2368_16_chip_info = {
static const struct ltc2378_chip_info ltc2368_18_chip_info = {
.name = "ltc2368-18",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 1 * HZ_PER_MHZ,
.tconv_ns = 527,
@@ -163,7 +166,7 @@ static const struct ltc2378_chip_info ltc2368_18_chip_info = {
static const struct ltc2378_chip_info ltc2369_18_chip_info = {
.name = "ltc2369-18",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 1600 * HZ_PER_KHZ,
.tconv_ns = 412,
@@ -171,7 +174,7 @@ static const struct ltc2378_chip_info ltc2369_18_chip_info = {
static const struct ltc2378_chip_info ltc2370_16_chip_info = {
.name = "ltc2370-16",
- .chan = LTC2378_UNIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_UNIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_UNIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 2 * HZ_PER_MHZ,
.tconv_ns = 322,
@@ -179,7 +182,7 @@ static const struct ltc2378_chip_info ltc2370_16_chip_info = {
static const struct ltc2378_chip_info ltc2376_16_chip_info = {
.name = "ltc2376-16",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 250 * HZ_PER_KHZ,
.tconv_ns = 3000,
@@ -187,7 +190,7 @@ static const struct ltc2378_chip_info ltc2376_16_chip_info = {
static const struct ltc2378_chip_info ltc2376_18_chip_info = {
.name = "ltc2376-18",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 250 * HZ_PER_KHZ,
.tconv_ns = 3000,
@@ -195,7 +198,7 @@ static const struct ltc2378_chip_info ltc2376_18_chip_info = {
static const struct ltc2378_chip_info ltc2376_20_chip_info = {
.name = "ltc2376-20",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(20),
.max_sample_rate_Hz = 250 * HZ_PER_KHZ,
.tconv_ns = 3000,
@@ -203,7 +206,7 @@ static const struct ltc2378_chip_info ltc2376_20_chip_info = {
static const struct ltc2378_chip_info ltc2377_16_chip_info = {
.name = "ltc2377-16",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 500 * HZ_PER_KHZ,
.tconv_ns = 1500,
@@ -211,7 +214,7 @@ static const struct ltc2378_chip_info ltc2377_16_chip_info = {
static const struct ltc2378_chip_info ltc2377_18_chip_info = {
.name = "ltc2377-18",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 500 * HZ_PER_KHZ,
.tconv_ns = 1500,
@@ -219,7 +222,7 @@ static const struct ltc2378_chip_info ltc2377_18_chip_info = {
static const struct ltc2378_chip_info ltc2377_20_chip_info = {
.name = "ltc2377-20",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(20),
.max_sample_rate_Hz = 500 * HZ_PER_KHZ,
.tconv_ns = 1500,
@@ -227,7 +230,7 @@ static const struct ltc2378_chip_info ltc2377_20_chip_info = {
static const struct ltc2378_chip_info ltc2378_16_chip_info = {
.name = "ltc2378-16",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 1 * HZ_PER_MHZ,
.tconv_ns = 527,
@@ -235,7 +238,7 @@ static const struct ltc2378_chip_info ltc2378_16_chip_info = {
static const struct ltc2378_chip_info ltc2378_18_chip_info = {
.name = "ltc2378-18",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 1 * HZ_PER_MHZ,
.tconv_ns = 527,
@@ -243,7 +246,7 @@ static const struct ltc2378_chip_info ltc2378_18_chip_info = {
static const struct ltc2378_chip_info ltc2378_20_chip_info = {
.name = "ltc2378-20",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(20),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(20), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(20),
.max_sample_rate_Hz = 1 * HZ_PER_MHZ,
.tconv_ns = 675,
@@ -251,7 +254,7 @@ static const struct ltc2378_chip_info ltc2378_20_chip_info = {
static const struct ltc2378_chip_info ltc2379_18_chip_info = {
.name = "ltc2379-18",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(18),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(18), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(18),
.max_sample_rate_Hz = 1600 * HZ_PER_KHZ,
.tconv_ns = 412,
@@ -259,7 +262,7 @@ static const struct ltc2378_chip_info ltc2379_18_chip_info = {
static const struct ltc2378_chip_info ltc2380_16_chip_info = {
.name = "ltc2380-16",
- .chan = LTC2378_BIPOLAR_DIFF_CHANNEL(16),
+ .chan = { LTC2378_BIPOLAR_DIFF_CHANNEL(16), IIO_CHAN_SOFT_TIMESTAMP(1) },
.offload_chan = LTC2378_OFFLOAD_BIPOLAR_DIFF_CHANNEL(16),
.max_sample_rate_Hz = 2 * HZ_PER_MHZ,
.tconv_ns = 322,
@@ -278,6 +281,25 @@ static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
return ret;
}
+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_channel_single_read(const struct iio_chan_spec *chan,
struct ltc2378_state *st, int *val)
{
@@ -658,8 +680,15 @@ static int ltc2378_probe(struct spi_device *spi)
/* Fall back to low speed usage when no SPI offload is available. */
if (ret == -ENODEV) {
indio_dev->info = <c2378_iio_info;
- indio_dev->channels = &st->info->chan;
- indio_dev->num_channels = 1;
+ indio_dev->channels = st->info->chan;
+ indio_dev->num_channels = ARRAY_SIZE(st->info->chan);
+
+ ret = devm_iio_triggered_buffer_setup(dev, indio_dev,
+ iio_pollfunc_store_time,
+ ltc2378_trigger_handler,
+ NULL);
+ if (ret)
+ return ret;
} else if (ret) {
return dev_err_probe(dev, ret, "failed to get offload\n");
} else {
--
2.53.0
^ permalink raw reply related [flat|nested] 15+ messages in thread