Devicetree
 help / color / mirror / Atom feed
* [PATCH v1 0/4] iio: adc: Add support for LTC2378 and similar ADCs
@ 2026-05-18 15:20 Marcelo Schmitt
  2026-05-18 15:21 ` [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
                   ` (3 more replies)
  0 siblings, 4 replies; 10+ messages in thread
From: Marcelo Schmitt @ 2026-05-18 15:20 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel
  Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
	conor+dt, marcelo.schmitt1

From: Marcelo Schmitt <marcelo.schmitt@analog.com>

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

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

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

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

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

With best regards,
Marcelo


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

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


base-commit: 8678fb54958893818ddeccd05fea560a4e1fc759
-- 
2.53.0


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

* [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378
  2026-05-18 15:20 [PATCH v1 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
@ 2026-05-18 15:21 ` Marcelo Schmitt
  2026-05-18 15:29   ` sashiko-bot
  2026-05-18 17:06   ` Conor Dooley
  2026-05-18 15:21 ` [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 10+ messages in thread
From: Marcelo Schmitt @ 2026-05-18 15:21 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel
  Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
	conor+dt, marcelo.schmitt1

From: Marcelo Schmitt <marcelo.schmitt@analog.com>

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

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 .../bindings/iio/adc/adi,ltc2378.yaml         | 131 ++++++++++++++++++
 MAINTAINERS                                   |   7 +
 2 files changed, 138 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml

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..56e59bcd7306
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ltc2378.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices LTC2378 and similar Analog to Digital Converters
+
+maintainers:
+  - Marcelo Schmitt <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:
+      - enum:
+          - adi,ltc2338-18
+          - adi,ltc2369-18
+          - adi,ltc2370-16
+          - adi,ltc2379-18
+          - adi,ltc2380-16
+      - items:
+          - enum:
+              - adi,ltc2364-18
+              - adi,ltc2364-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2367-18
+              - adi,ltc2367-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2368-18
+              - adi,ltc2368-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2376-18
+              - adi,ltc2376-20
+              - adi,ltc2376-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2377-18
+              - adi,ltc2377-20
+              - adi,ltc2377-16 # fallback compatible
+      - items:
+          - enum:
+              - adi,ltc2378-18
+              - adi,ltc2378-20
+              - adi,ltc2378-16 # fallback compatible
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 100000000
+
+  vdd-supply:
+    description: A 2.5V supply that powers the chip (VDD).
+
+  ovdd-supply:
+    description:
+      A 1.71V to 5.25V supply that sets the logic level for digital interface.
+
+  ref-supply:
+    description:
+      A 2.5V to 5.1V supply for the reference input (REF).
+
+  cnv-gpios:
+    description:
+      When provided, this property indicates the GPIO that is connected to the
+      CNV pin.
+    maxItems: 1
+
+  interrupts:
+    description:
+      Interrupt for signaling the completion of conversion results. The active
+      low signal provided on the BUSY pin asserts when ADC conversions finish.
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - vdd-supply
+  - ovdd-supply
+  - ref-supply
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        adc@0 {
+            compatible = "adi,ltc2378-20";
+            reg = <0>;
+            spi-max-frequency = <71000000>;
+            vdd-supply = <&supply_2_5V>;
+            ovdd-supply = <&supply_3_3V>;
+            ref-supply = <&supply_5V>;
+            cnv-gpios = <&gpio0 88 GPIO_ACTIVE_HIGH>;
+            interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
+            interrupt-parent = <&gpio>;
+        };
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 0de74503df08..768f56eea476 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15195,6 +15195,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/iio/dac/lltc,ltc1660.yaml
 F:	drivers/iio/dac/ltc1660.c
 
+LTC2378 IIO ADC DRIVER
+M:	Marcelo Schmitt <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] 10+ messages in thread

* [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs
  2026-05-18 15:20 [PATCH v1 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
  2026-05-18 15:21 ` [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
@ 2026-05-18 15:21 ` Marcelo Schmitt
  2026-05-18 15:51   ` sashiko-bot
  2026-05-18 15:22 ` [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
  2026-05-18 15:22 ` [PATCH v1 4/4] iio: adc: ltc2378: Enable triggered buffer " Marcelo Schmitt
  3 siblings, 1 reply; 10+ messages in thread
From: Marcelo Schmitt @ 2026-05-18 15:21 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel
  Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
	conor+dt, marcelo.schmitt1

From: Marcelo Schmitt <marcelo.schmitt@analog.com>

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

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
The data union for the sample data and the filling of IIO channel scan_type
struct could probably have been avoided and postponed. Though, that would
probably imply larger git diffs in the buffer patches so I added those earlier
to minimize diff size in the buffer patches.

 MAINTAINERS               |   1 +
 drivers/iio/adc/Kconfig   |  12 ++
 drivers/iio/adc/Makefile  |   1 +
 drivers/iio/adc/ltc2378.c | 352 ++++++++++++++++++++++++++++++++++++++
 drivers/iio/adc/ltc2378.h |  48 ++++++
 5 files changed, 414 insertions(+)
 create mode 100644 drivers/iio/adc/ltc2378.c
 create mode 100644 drivers/iio/adc/ltc2378.h

diff --git a/MAINTAINERS b/MAINTAINERS
index 768f56eea476..d9a772ac599c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15201,6 +15201,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*
 
 LTC2664 IIO DAC DRIVER
 M:	Michael Hennerich <michael.hennerich@analog.com>
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 8550917226a1..70fec8e3e891 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -939,6 +939,18 @@ config LTC2309
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2309.
 
+config LTC2378
+	tristate "Analog Devices LTC2378 ADC driver"
+	depends on SPI
+	depends on GPIOLIB || PWM
+	select IIO_BUFFER
+	help
+	  Say yes here to build support for Analog Devices LTC2378-20 and
+	  similar analog to digital converters.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called ltc2378.
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
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..7916500c470c
--- /dev/null
+++ b/drivers/iio/adc/ltc2378.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Analog Devices LTC2378 ADC series driver
+ *
+ * Copyright (C) 2026 Analog Devices Inc.
+ * Author: Ioan-Daniel Pop <pop.ioan-daniel@analog.com>
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+
+#include "ltc2378.h"
+
+enum ltc2378_supported_device_ids {
+	ID_LTC2338_18,
+	ID_LTC2364_16,
+	ID_LTC2364_18,
+	ID_LTC2367_16,
+	ID_LTC2367_18,
+	ID_LTC2368_16,
+	ID_LTC2368_18,
+	ID_LTC2369_18,
+	ID_LTC2370_16,
+	ID_LTC2376_16,
+	ID_LTC2376_18,
+	ID_LTC2376_20,
+	ID_LTC2377_16,
+	ID_LTC2377_18,
+	ID_LTC2377_20,
+	ID_LTC2378_16,
+	ID_LTC2378_18,
+	ID_LTC2378_20,
+	ID_LTC2379_18,
+	ID_LTC2380_16,
+};
+
+static const struct ltc2378_chip_info ltc2378_chip_info[] = {
+	[ID_LTC2338_18] = {
+		.name = "ltc2338-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2364_16] = {
+		.name = "ltc2364-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2364_18] = {
+		.name = "ltc2364-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2367_16] = {
+		.name = "ltc2367-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2367_18] = {
+		.name = "ltc2367-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2368_16] = {
+		.name = "ltc2368-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2368_18] = {
+		.name = "ltc2368-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2369_18] = {
+		.name = "ltc2369-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2370_16] = {
+		.name = "ltc2370-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
+	},
+	[ID_LTC2376_16] = {
+		.name = "ltc2376-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2376_18] = {
+		.name = "ltc2376-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2376_20] = {
+		.name = "ltc2376-20",
+		.resolution = 20,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2377_16] = {
+		.name = "ltc2377-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2377_18] = {
+		.name = "ltc2377-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2377_20] = {
+		.name = "ltc2377-20",
+		.resolution = 20,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2378_16] = {
+		.name = "ltc2378-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2378_18] = {
+		.name = "ltc2378-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2378_20] = {
+		.name = "ltc2378-20",
+		.resolution = 20,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2379_18] = {
+		.name = "ltc2379-18",
+		.resolution = 18,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+	[ID_LTC2380_16] = {
+		.name = "ltc2380-16",
+		.resolution = 16,
+		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+	},
+};
+
+static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
+{
+	int ret;
+
+	/* Cause a rising edge of CNV to initiate a new ADC conversion */
+	gpiod_set_value_cansleep(st->cnv_gpio, 1);
+	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 (scan_type->endianness == IIO_BE) {
+		if (scan_type->realbits > 16)
+			sample = be32_to_cpu(st->scan.data.sample_buf32_be);
+		else
+			sample = be16_to_cpu(st->scan.data.sample_buf16_be);
+	} else {
+		if (scan_type->realbits > 16)
+			sample = st->scan.data.sample_buf32;
+		else
+			sample = st->scan.data.sample_buf16;
+	}
+
+	sample >>= scan_type->shift;
+
+	if (scan_type->format == IIO_SCAN_FORMAT_SIGNED_INT)
+		*val = sign_extend32(sample, scan_type->realbits - 1);
+	else
+		*val = sample;
+
+	return 0;
+}
+
+static int ltc2378_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan,
+			int *val, int *val2, long info)
+{
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	int ret;
+
+	switch (info) {
+	case IIO_CHAN_INFO_RAW:
+		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);
+		if (IIO_DEV_ACQUIRE_FAILED(claim))
+			return -EBUSY;
+
+		ret = ltc2378_channel_single_read(chan, st, val);
+		if (ret)
+			return ret;
+
+		return IIO_VAL_INT;
+
+	case IIO_CHAN_INFO_SCALE:
+		*val = st->ref_uV / MILLI;
+		/*
+		 * For all LTC2378-like devices, the amount of bits that express
+		 * voltage magnitude depend on the output code format:
+		 * - straight binary: All precision/resolution bits are used.
+		 * - 2's complement: One of the precision bits is used for sign.
+		 */
+		if (st->info->out_format == IIO_SCAN_FORMAT_SIGNED_INT)
+			*val2 = st->info->resolution - 1;
+		else
+			*val2 = st->info->resolution;
+
+		return IIO_VAL_FRACTIONAL_LOG2;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static const struct iio_info ltc2378_iio_info = {
+	.read_raw = &ltc2378_read_raw,
+};
+
+static int ltc2378_probe(struct spi_device *spi)
+{
+	struct iio_chan_spec *ltc2378_chan;
+	struct device *dev = &spi->dev;
+	unsigned int num_iio_chans = 1;
+	struct iio_dev *indio_dev;
+	struct ltc2378_state *st;
+
+	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	st = iio_priv(indio_dev);
+	st->spi = spi;
+
+	st->ref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
+	if (st->ref_uV < 0)
+		return dev_err_probe(dev, -ENODEV, "failed to read ref regulator\n");
+
+	st->info = spi_get_device_match_data(spi);
+	if (!st->info)
+		return -EINVAL;
+
+	indio_dev->name = st->info->name;
+	indio_dev->info = &ltc2378_iio_info;
+	indio_dev->modes = INDIO_DIRECT_MODE;
+
+	st->cnv_gpio = devm_gpiod_get_optional(dev, "cnv", GPIOD_OUT_LOW);
+	if (st->cnv_gpio && IS_ERR(st->cnv_gpio))
+		return dev_err_probe(dev, PTR_ERR(st->cnv_gpio),
+				     "failed to get CNV GPIO");
+
+	ltc2378_chan = devm_kzalloc(&spi->dev, sizeof(struct iio_chan_spec), GFP_KERNEL);
+	if (!ltc2378_chan)
+		return -ENOMEM;
+
+	*ltc2378_chan = (struct iio_chan_spec) {
+		.type = IIO_VOLTAGE,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
+				      BIT(IIO_CHAN_INFO_SCALE),
+		.scan_type = {
+			.format = st->info->out_format,
+			.realbits = st->info->resolution,
+			.storagebits = st->info->resolution > 16 ? 32 : 16,
+			.shift = st->info->resolution > 16 ? 8 : 0,
+			.endianness = IIO_BE,
+		},
+	};
+	st->xfer.rx_buf = &st->scan.data;
+	st->xfer.len = BITS_TO_BYTES(ltc2378_chan->scan_type.storagebits);
+
+	indio_dev->channels = ltc2378_chan;
+	indio_dev->num_channels = num_iio_chans;
+
+	return devm_iio_device_register(&spi->dev, indio_dev);
+}
+
+static const struct of_device_id ltc2378_of_match[] = {
+	{ .compatible = "adi,ltc2338-18", .data = &ltc2378_chip_info[ID_LTC2338_18] },
+	{ .compatible = "adi,ltc2364-16", .data = &ltc2378_chip_info[ID_LTC2364_16] },
+	{ .compatible = "adi,ltc2364-18", .data = &ltc2378_chip_info[ID_LTC2364_18] },
+	{ .compatible = "adi,ltc2367-16", .data = &ltc2378_chip_info[ID_LTC2367_16] },
+	{ .compatible = "adi,ltc2367-18", .data = &ltc2378_chip_info[ID_LTC2367_18] },
+	{ .compatible = "adi,ltc2368-16", .data = &ltc2378_chip_info[ID_LTC2368_16] },
+	{ .compatible = "adi,ltc2368-18", .data = &ltc2378_chip_info[ID_LTC2368_18] },
+	{ .compatible = "adi,ltc2369-18", .data = &ltc2378_chip_info[ID_LTC2369_18] },
+	{ .compatible = "adi,ltc2370-16", .data = &ltc2378_chip_info[ID_LTC2370_16] },
+	{ .compatible = "adi,ltc2376-16", .data = &ltc2378_chip_info[ID_LTC2376_16] },
+	{ .compatible = "adi,ltc2376-18", .data = &ltc2378_chip_info[ID_LTC2376_18] },
+	{ .compatible = "adi,ltc2376-20", .data = &ltc2378_chip_info[ID_LTC2376_20] },
+	{ .compatible = "adi,ltc2377-16", .data = &ltc2378_chip_info[ID_LTC2377_16] },
+	{ .compatible = "adi,ltc2377-18", .data = &ltc2378_chip_info[ID_LTC2377_18] },
+	{ .compatible = "adi,ltc2377-20", .data = &ltc2378_chip_info[ID_LTC2377_20] },
+	{ .compatible = "adi,ltc2378-16", .data = &ltc2378_chip_info[ID_LTC2378_16] },
+	{ .compatible = "adi,ltc2378-18", .data = &ltc2378_chip_info[ID_LTC2378_18] },
+	{ .compatible = "adi,ltc2378-20", .data = &ltc2378_chip_info[ID_LTC2378_20] },
+	{ .compatible = "adi,ltc2379-18", .data = &ltc2378_chip_info[ID_LTC2379_18] },
+	{ .compatible = "adi,ltc2380-16", .data = &ltc2378_chip_info[ID_LTC2380_16] },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, ltc2378_of_match);
+
+static const struct spi_device_id ltc2378_spi_id[] = {
+	{ "ltc2338-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2338_18] },
+	{ "ltc2364-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2364_16] },
+	{ "ltc2364-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2364_18] },
+	{ "ltc2367-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2367_16] },
+	{ "ltc2367-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2367_18] },
+	{ "ltc2368-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2368_16] },
+	{ "ltc2368-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2368_18] },
+	{ "ltc2369-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2369_18] },
+	{ "ltc2370-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2370_16] },
+	{ "ltc2376-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2376_16] },
+	{ "ltc2376-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2376_18] },
+	{ "ltc2376-20", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2376_20] },
+	{ "ltc2377-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2377_16] },
+	{ "ltc2377-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2377_18] },
+	{ "ltc2377-20", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2377_20] },
+	{ "ltc2378-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2378_16] },
+	{ "ltc2378-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2378_18] },
+	{ "ltc2378-20", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2378_20] },
+	{ "ltc2379-18", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2379_18] },
+	{ "ltc2380-16", (kernel_ulong_t)&ltc2378_chip_info[ID_LTC2380_16] },
+	{ },
+};
+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("Ioan-Daniel Pop <pop.ioan-daniel@analog.com>");
+MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
+MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/iio/adc/ltc2378.h b/drivers/iio/adc/ltc2378.h
new file mode 100644
index 000000000000..515f7e8a4f2e
--- /dev/null
+++ b/drivers/iio/adc/ltc2378.h
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Analog Devices LTC2378 and similar ADCs common definitions and properties
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#ifndef __DRIVERS_IIO_ADC_LTC2378_H__
+#define __DRIVERS_IIO_ADC_LTC2378_H__
+
+#include <linux/iio/iio.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+#define LTC2378_TDSDOBUSYL_NS		5
+#define LTC2378_TBUSYLH_NS		13
+#define LTC2378_TCNV_HIGH_NS		20
+
+struct ltc2378_chip_info {
+	const char *name;
+	int resolution;
+	const char out_format;
+};
+
+struct ltc2378_state {
+	const struct ltc2378_chip_info *info;
+	struct gpio_desc *cnv_gpio;
+	struct spi_device *spi;
+	struct spi_transfer xfer;
+	int ref_uV;
+
+	/*
+	 * DMA (thus cache coherency maintenance) requires the
+	 * transfer buffers to live in their own cache lines.
+	 */
+	struct {
+		union {
+			__be16 sample_buf16_be;
+			__be32 sample_buf32_be;
+			u16 sample_buf16;
+			u32 sample_buf32;
+		} data;
+		aligned_s64 timestamp;
+	} scan __aligned(IIO_DMA_MINALIGN);
+};
+
+#endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
-- 
2.53.0


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

* [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture
  2026-05-18 15:20 [PATCH v1 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
  2026-05-18 15:21 ` [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
  2026-05-18 15:21 ` [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
@ 2026-05-18 15:22 ` Marcelo Schmitt
  2026-05-18 16:19   ` sashiko-bot
  2026-05-18 15:22 ` [PATCH v1 4/4] iio: adc: ltc2378: Enable triggered buffer " Marcelo Schmitt
  3 siblings, 1 reply; 10+ messages in thread
From: Marcelo Schmitt @ 2026-05-18 15:22 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel
  Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
	conor+dt, marcelo.schmitt1

From: Marcelo Schmitt <marcelo.schmitt@analog.com>

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>
---
 drivers/iio/adc/Kconfig                  |  12 +
 drivers/iio/adc/Makefile                 |   1 +
 drivers/iio/adc/ltc2378-offload-buffer.c | 297 +++++++++++++++++++++++
 drivers/iio/adc/ltc2378.c                |  62 +++++
 drivers/iio/adc/ltc2378.h                |  34 +++
 5 files changed, 406 insertions(+)
 create mode 100644 drivers/iio/adc/ltc2378-offload-buffer.c

diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 70fec8e3e891..b5368ee783f7 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -944,6 +944,7 @@ config LTC2378
 	depends on SPI
 	depends on GPIOLIB || PWM
 	select IIO_BUFFER
+	imply LTC2378_OFFLOAD_BUFFER
 	help
 	  Say yes here to build support for Analog Devices LTC2378-20 and
 	  similar analog to digital converters.
@@ -951,6 +952,17 @@ config LTC2378
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2378.
 
+config LTC2378_OFFLOAD_BUFFER
+	bool "Offloaded data capture with LTC2378"
+	depends on SPI && LTC2378
+	depends on SPI_OFFLOAD=y
+	depends on PWM=y
+	depends on SPI_OFFLOAD_TRIGGER_PWM=y
+	depends on IIO_BUFFER_DMA=y
+	depends on IIO_BUFFER_DMAENGINE=y
+	help
+	  Say yes here to build support for high speed data capture with LTC2378
+
 config LTC2471
 	tristate "Linear Technology LTC2471 and LTC2473 ADC driver"
 	depends on I2C
diff --git a/drivers/iio/adc/Makefile b/drivers/iio/adc/Makefile
index 1814fb78dde3..2fa5dce0ceea 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -82,6 +82,7 @@ obj-$(CONFIG_LPC18XX_ADC) += lpc18xx_adc.o
 obj-$(CONFIG_LPC32XX_ADC) += lpc32xx_adc.o
 obj-$(CONFIG_LTC2309) += ltc2309.o
 obj-$(CONFIG_LTC2378) += ltc2378.o
+obj-$(CONFIG_LTC2378_OFFLOAD_BUFFER) += ltc2378-offload-buffer.o
 obj-$(CONFIG_LTC2471) += ltc2471.o
 obj-$(CONFIG_LTC2485) += ltc2485.o
 obj-$(CONFIG_LTC2496) += ltc2496.o ltc2497-core.o
diff --git a/drivers/iio/adc/ltc2378-offload-buffer.c b/drivers/iio/adc/ltc2378-offload-buffer.c
new file mode 100644
index 000000000000..ed09f9a55f93
--- /dev/null
+++ b/drivers/iio/adc/ltc2378-offload-buffer.c
@@ -0,0 +1,297 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2026 Analog Devices, Inc.
+ * Author: Marcelo Schmitt <marcelo.schmitt@analog.com>
+ */
+
+#include <linux/err.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/buffer-dmaengine.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/iio/types.h>
+#include <linux/kstrtox.h>
+#include <linux/limits.h>
+#include <linux/linkage.h>
+#include <linux/math.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
+#include <linux/pwm.h>
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#include <linux/time64.h>
+
+#include "ltc2378.h"
+
+/*
+ * SPI offload wiring schema
+ *
+ *     +-------------+         +-------------+
+ *     |         CNV |<-----+--| GPIO        |
+ *     |             |      +--| PWM0        |
+ *     |             |         |             |
+ *     |             |      +--| PWM1        |
+ *     |             |      |  +-------------+
+ *     |             |      +->| TRIGGER     |
+ *     |             |         |             |
+ *     |     ADC     |         |    SPI      |
+ *     |             |         | controller  |
+ *     |             |         |             |
+ *     |         SDI |<--------| SDO         |
+ *     |         SDO |-------->| SDI         |
+ *     |        SCLK |<--------| SCLK        |
+ *     +-------------+         +-------------+
+ *
+ */
+static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
+{
+	struct spi_offload_trigger_config *config = &st->offload_trigger_config;
+	unsigned int min_read_offset, offload_period_ns;
+	struct pwm_waveform cnv_wf = { };
+	u64 target = LTC2378_TCNV_HIGH_NS;
+	u64 offload_offset_ns;
+	int ret;
+
+	if (freq_Hz == 0)
+		return -EINVAL;
+
+	if (freq_Hz < 1 || freq_Hz > st->info->max_sample_rate_hz)
+		return -ERANGE;
+
+	/* Configure CNV PWM waveform */
+	cnv_wf.period_length_ns = DIV_ROUND_CLOSEST(NSEC_PER_SEC, freq_Hz);
+
+	/*
+	 * Ensure CNV high time meets minimum requirement (20ns).
+	 * The PWM hardware may round the duty cycle, so iterate
+	 * until we get at least the minimum required high time.
+	 */
+	do {
+		cnv_wf.duty_length_ns = target;
+		ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
+		if (ret)
+			return ret;
+		target += 10;  /* Increment by PWM duty cycle period */
+	} while (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);
+
+	/*
+	 * Configure SPI offload PWM trigger.
+	 * The trigger should fire after tBUSYLH + tCONV + tDSDOBUSYL.
+	 * Minimum time needed: TBUSYLH (13ns) + TCONV (part-specific) + TDSDOBUSYL (5ns)
+	 *
+	 * Use the same period as CNV PWM to avoid timing issues.
+	 * Convert back from period to frequency for the SPI offload API.
+	 */
+	offload_period_ns = cnv_wf.period_length_ns;
+	config->periodic.frequency_hz = DIV_ROUND_UP(HZ_PER_GHZ, offload_period_ns);
+	min_read_offset = LTC2378_TBUSYLH_NS + st->info->tconv_ns + LTC2378_TDSDOBUSYL_NS;
+	offload_offset_ns = min_read_offset;
+	do {
+		config->periodic.offset_ns = offload_offset_ns;
+		ret = spi_offload_trigger_validate(st->offload_trigger, config);
+		if (ret)
+			return ret;
+		offload_offset_ns += 10;
+	} while (config->periodic.offset_ns < min_read_offset);
+
+	st->cnv_wf = cnv_wf;
+	st->cnv_Hz = DIV_ROUND_CLOSEST_ULL(HZ_PER_GHZ, cnv_wf.period_length_ns);
+
+	return 0;
+}
+
+static ssize_t sampling_frequency_show(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct ltc2378_state *st = iio_priv(dev_to_iio_dev(dev));
+
+	return sysfs_emit(buf, "%u\n", st->cnv_Hz);
+}
+
+static ssize_t sampling_frequency_store(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t len)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	unsigned int val;
+	int ret;
+
+	if (!iio_device_claim_direct(indio_dev))
+		return -EBUSY;
+
+	ret = kstrtouint(buf, 10, &val);
+	if (ret)
+		goto out_store;
+
+	ret = ltc2378_update_conversion_rate(st, val);
+
+out_store:
+	iio_device_release_direct(indio_dev);
+	return ret ?: len;
+}
+
+static IIO_DEVICE_ATTR_RW(sampling_frequency, 0);
+
+static ssize_t sampling_frequency_available_show(struct device *dev,
+						 struct device_attribute *attr,
+						 char *buf)
+{
+	struct iio_dev *indio_dev = dev_to_iio_dev(dev);
+	struct ltc2378_state *st = iio_priv(indio_dev);
+
+	return sysfs_emit(buf, "[%u %u %u]\n",
+			  1, 1, st->info->max_sample_rate_hz);
+}
+
+static IIO_DEVICE_ATTR_RO(sampling_frequency_available, 0);
+
+static struct attribute *ltc2378_offload_attributes[] = {
+	&iio_dev_attr_sampling_frequency.dev_attr.attr,
+	&iio_dev_attr_sampling_frequency_available.dev_attr.attr,
+	NULL,
+};
+
+const struct attribute_group ltc2378_offload_attribute_group = {
+	.attrs = ltc2378_offload_attributes,
+};
+EXPORT_SYMBOL_NS_GPL(ltc2378_offload_attribute_group, "IIO_LTC2378");
+
+static int ltc2378_prepare_offload_message(struct device *dev,
+					   struct ltc2378_state *st)
+{
+	st->offload_xfer.bits_per_word = st->info->resolution;
+	/*
+	 * Ideally, we would ask the offload provider what data word sizes are
+	 * supported so we could use smaller words for less precise ADCs.
+	 * Though, the currently available SPI offloading hardware only supports
+	 * pushing 32-bit sized data elements to DMA memory. Because of that,
+	 * we hardcode set 4 byte sized transfers.
+	 */
+	st->offload_xfer.len = 4;
+	st->offload_xfer.offload_flags = SPI_OFFLOAD_XFER_RX_STREAM;
+
+	/* 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, false);
+	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 = &ltc2378_offload_buffer_postenable,
+	.predisable = &ltc2378_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 = &ltc2378_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");
+
+	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,
+};
+
+int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi)
+{
+	struct ltc2378_state *st = iio_priv(indio_dev);
+	struct device *dev = &spi->dev;
+	int ret;
+
+	st->offload = devm_spi_offload_get(dev, spi, &ltc2378_offload_config);
+	ret = PTR_ERR_OR_ZERO(st->offload);
+	if (ret && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to get offload\n");
+
+	ret = ltc2378_spi_offload_setup(indio_dev, st);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "failed to setup SPI offload\n");
+
+	ret = ltc2378_pwm_get(st);
+	if (ret)
+		return ret;
+
+	/*
+	 * Start with a slower sampling rate so there is some room for
+	 * adjusting the sampling frequency without hitting the maximum
+	 * conversion rate.
+	 */
+	ret = ltc2378_update_conversion_rate(st, st->info->max_sample_rate_hz >> 4);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to sampling frequency\n");
+
+	ret = ltc2378_prepare_offload_message(&spi->dev, st);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to optimize SPI message\n");
+
+	return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ltc2378_offload_buffer_setup, "IIO_LTC2378");
+
+MODULE_IMPORT_NS("IIO_LTC2378");
diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
index 7916500c470c..fdbe919d45d5 100644
--- a/drivers/iio/adc/ltc2378.c
+++ b/drivers/iio/adc/ltc2378.c
@@ -42,101 +42,141 @@ static const struct ltc2378_chip_info ltc2378_chip_info[] = {
 	[ID_LTC2338_18] = {
 		.name = "ltc2338-18",
 		.resolution = 18,
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 527,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2364_16] = {
 		.name = "ltc2364-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 250 * HZ_PER_KHZ,
+		.tconv_ns = 3000,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2364_18] = {
 		.name = "ltc2364-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 250 * HZ_PER_KHZ,
+		.tconv_ns = 3000,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2367_16] = {
 		.name = "ltc2367-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 500 * HZ_PER_KHZ,
+		.tconv_ns = 1500,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2367_18] = {
 		.name = "ltc2367-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 500 * HZ_PER_KHZ,
+		.tconv_ns = 1500,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2368_16] = {
 		.name = "ltc2368-16",
 		.resolution = 16,
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 527,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2368_18] = {
 		.name = "ltc2368-18",
 		.resolution = 18,
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 527,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2369_18] = {
 		.name = "ltc2369-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 1600 * HZ_PER_KHZ,
+		.tconv_ns = 412,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2370_16] = {
 		.name = "ltc2370-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 2 * HZ_PER_MHZ,
+		.tconv_ns = 322,
 		.out_format = IIO_SCAN_FORMAT_UNSIGNED_INT,
 	},
 	[ID_LTC2376_16] = {
 		.name = "ltc2376-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 250 * HZ_PER_KHZ,
+		.tconv_ns = 3000,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2376_18] = {
 		.name = "ltc2376-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 250 * HZ_PER_KHZ,
+		.tconv_ns = 3000,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2376_20] = {
 		.name = "ltc2376-20",
 		.resolution = 20,
+		.max_sample_rate_hz = 250 * HZ_PER_KHZ,
+		.tconv_ns = 3000,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2377_16] = {
 		.name = "ltc2377-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 500 * HZ_PER_KHZ,
+		.tconv_ns = 1500,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2377_18] = {
 		.name = "ltc2377-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 500 * HZ_PER_KHZ,
+		.tconv_ns = 1500,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2377_20] = {
 		.name = "ltc2377-20",
 		.resolution = 20,
+		.max_sample_rate_hz = 500 * HZ_PER_KHZ,
+		.tconv_ns = 1500,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2378_16] = {
 		.name = "ltc2378-16",
 		.resolution = 16,
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 527,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2378_18] = {
 		.name = "ltc2378-18",
 		.resolution = 18,
+		.max_sample_rate_hz = HZ_PER_MHZ,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
+		.tconv_ns = 527,
 	},
 	[ID_LTC2378_20] = {
 		.name = "ltc2378-20",
 		.resolution = 20,
+		.max_sample_rate_hz = HZ_PER_MHZ,
+		.tconv_ns = 675,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2379_18] = {
 		.name = "ltc2379-18",
 		.resolution = 18,
+		.max_sample_rate_hz = 1600 * HZ_PER_KHZ,
+		.tconv_ns = 412,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 	[ID_LTC2380_16] = {
 		.name = "ltc2380-16",
 		.resolution = 16,
+		.max_sample_rate_hz = 2 * HZ_PER_MHZ,
+		.tconv_ns = 322,
 		.out_format = IIO_SCAN_FORMAT_SIGNED_INT,
 	},
 };
@@ -226,6 +266,9 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
 }
 
 static const struct iio_info ltc2378_iio_info = {
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+	.attrs = &ltc2378_offload_attribute_group,
+#endif
 	.read_raw = &ltc2378_read_raw,
 };
 
@@ -236,6 +279,7 @@ static int ltc2378_probe(struct spi_device *spi)
 	unsigned int num_iio_chans = 1;
 	struct iio_dev *indio_dev;
 	struct ltc2378_state *st;
+	int ret;
 
 	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
 	if (!indio_dev)
@@ -280,6 +324,23 @@ static int ltc2378_probe(struct spi_device *spi)
 	st->xfer.rx_buf = &st->scan.data;
 	st->xfer.len = BITS_TO_BYTES(ltc2378_chan->scan_type.storagebits);
 
+	ret = ltc2378_offload_buffer_setup(indio_dev, spi);
+	if (ret == -ENODEV) {
+		/* SPI offloading is unavailable. Fall back to triggered buffer. */
+		dev_notice(dev, "buffered data capture not supported\n");
+	} else if (ret) {
+		return dev_err_probe(dev, ret, "error on SPI offload setup\n");
+	} else {
+		/*
+		 * Currently, the available offload hardware + DMA configuration
+		 * only supports pushing data to IIO buffers in CPU endianness.
+		 * That also requires we apply no shift to scan elements to
+		 * correctly read ADC sample data.
+		 */
+		ltc2378_chan->scan_type.shift = 0;
+		ltc2378_chan->scan_type.endianness = IIO_CPU;
+	}
+
 	indio_dev->channels = ltc2378_chan;
 	indio_dev->num_channels = num_iio_chans;
 
@@ -350,3 +411,4 @@ MODULE_AUTHOR("Ioan-Daniel Pop <pop.ioan-daniel@analog.com>");
 MODULE_AUTHOR("Marcelo Schmitt <marcelo.schmitt@analog.com>");
 MODULE_DESCRIPTION("Analog Devices LTC2378 ADC series driver");
 MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("IIO_LTC2378");
diff --git a/drivers/iio/adc/ltc2378.h b/drivers/iio/adc/ltc2378.h
index 515f7e8a4f2e..17c329b18333 100644
--- a/drivers/iio/adc/ltc2378.h
+++ b/drivers/iio/adc/ltc2378.h
@@ -9,7 +9,14 @@
 #define __DRIVERS_IIO_ADC_LTC2378_H__
 
 #include <linux/iio/iio.h>
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+#include <linux/pwm.h>
+#endif
 #include <linux/spi/spi.h>
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+#include <linux/spi/offload/consumer.h>
+#include <linux/spi/offload/types.h>
+#endif
 #include <linux/types.h>
 #include <linux/units.h>
 
@@ -20,6 +27,8 @@
 struct ltc2378_chip_info {
 	const char *name;
 	int resolution;
+	unsigned int max_sample_rate_hz;
+	unsigned int tconv_ns;
 	const char out_format;
 };
 
@@ -29,6 +38,16 @@ struct ltc2378_state {
 	struct spi_device *spi;
 	struct spi_transfer xfer;
 	int ref_uV;
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+	unsigned int cnv_Hz;
+	struct pwm_waveform cnv_wf;
+	struct spi_offload *offload;
+	struct spi_offload_trigger *offload_trigger;
+	struct spi_message offload_msg;
+	struct spi_transfer offload_xfer;
+	struct spi_offload_trigger_config offload_trigger_config;
+	struct pwm_device *cnv_trigger;
+#endif
 
 	/*
 	 * DMA (thus cache coherency maintenance) requires the
@@ -45,4 +64,19 @@ struct ltc2378_state {
 	} scan __aligned(IIO_DMA_MINALIGN);
 };
 
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+extern const struct attribute_group ltc2378_offload_attribute_group;
+#endif
+
+#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
+int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi);
+#else
+static inline int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev,
+					       struct spi_device *spi)
+{
+	might_sleep();
+	return -ENODEV;
+}
+#endif
+
 #endif /* __DRIVERS_IIO_ADC_LTC2378_H__ */
-- 
2.53.0


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

* [PATCH v1 4/4] iio: adc: ltc2378: Enable triggered buffer data capture
  2026-05-18 15:20 [PATCH v1 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
                   ` (2 preceding siblings ...)
  2026-05-18 15:22 ` [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
@ 2026-05-18 15:22 ` Marcelo Schmitt
  3 siblings, 0 replies; 10+ messages in thread
From: Marcelo Schmitt @ 2026-05-18 15:22 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel
  Cc: jic23, nuno.sa, Michael.Hennerich, dlechner, andy, robh, krzk+dt,
	conor+dt, marcelo.schmitt1

From: Marcelo Schmitt <marcelo.schmitt@analog.com>

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

Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
---
 drivers/iio/adc/Kconfig   |  1 +
 drivers/iio/adc/ltc2378.c | 39 ++++++++++++++++++++++++++++++++++++---
 2 files changed, 37 insertions(+), 3 deletions(-)

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


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

* Re: [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378
  2026-05-18 15:21 ` [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
@ 2026-05-18 15:29   ` sashiko-bot
  2026-05-18 17:06   ` Conor Dooley
  1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-18 15:29 UTC (permalink / raw)
  To: Marcelo Schmitt; +Cc: devicetree, robh, conor+dt

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

commit e7c5954ec798a9962f4a29e46b3240e39219ef7e
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>

dt-bindings: iio: adc: Add ltc2378

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

> 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:
> +      - enum:
> +          - adi,ltc2338-18
> +          - adi,ltc2369-18
> +          - adi,ltc2370-16
> +          - adi,ltc2379-18
> +          - adi,ltc2380-16
> +      - items:
> +          - enum:
> +              - adi,ltc2364-18
> +              - adi,ltc2364-16 # fallback compatible

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

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

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

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

[ ... ]
> +required:
> +  - compatible
> +  - reg
> +  - vdd-supply
> +  - ovdd-supply
> +  - ref-supply
> +
> +additionalProperties: false

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

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

Should this be unevaluatedProperties: false instead?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1779117444.git.marcelo.schmitt1@gmail.com?part=1

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

* Re: [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs
  2026-05-18 15:21 ` [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
@ 2026-05-18 15:51   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-18 15:51 UTC (permalink / raw)
  To: Marcelo Schmitt; +Cc: devicetree, conor+dt, robh

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

commit fc88b83a84e435cb25c9c503a7fbeb60ddc12e79
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>

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

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

> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> new file mode 100644
> index 0000000000000..7916500c470cf
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378.c

[ ... ]

> +static int ltc2378_convert_and_acquire(struct ltc2378_state *st)
> +{
> +	int ret;
> +
> +	/* Cause a rising edge of CNV to initiate a new ADC conversion */
> +	gpiod_set_value_cansleep(st->cnv_gpio, 1);
> +	ret = spi_sync_transfer(st->spi, &st->xfer, 1);

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

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

[ ... ]

> +static int ltc2378_read_raw(struct iio_dev *indio_dev,
> +			    const struct iio_chan_spec *chan,
> +			int *val, int *val2, long info)
> +{
> +	struct ltc2378_state *st = iio_priv(indio_dev);
> +	int ret;
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_RAW:
> +		IIO_DEV_ACQUIRE_DIRECT_MODE(indio_dev, claim);

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

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

> +		if (IIO_DEV_ACQUIRE_FAILED(claim))
> +			return -EBUSY;
> +
> +		ret = ltc2378_channel_single_read(chan, st, val);

[ ... ]

> +static int ltc2378_probe(struct spi_device *spi)
> +{
> +	struct iio_chan_spec *ltc2378_chan;
> +	struct device *dev = &spi->dev;
> +	unsigned int num_iio_chans = 1;
> +	struct iio_dev *indio_dev;
> +	struct ltc2378_state *st;
> +
> +	indio_dev = devm_iio_device_alloc(&spi->dev, sizeof(*st));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	st = iio_priv(indio_dev);
> +	st->spi = spi;
> +
> +	st->ref_uV = devm_regulator_get_enable_read_voltage(dev, "ref");
> +	if (st->ref_uV < 0)
> +		return dev_err_probe(dev, -ENODEV, "failed to read ref regulator\n");

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

> +
> +	st->info = spi_get_device_match_data(spi);

[ ... ]

> +	*ltc2378_chan = (struct iio_chan_spec) {
> +		.type = IIO_VOLTAGE,
> +		.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
> +				      BIT(IIO_CHAN_INFO_SCALE),
> +		.scan_type = {
> +			.format = st->info->out_format,
> +			.realbits = st->info->resolution,
> +			.storagebits = st->info->resolution > 16 ? 32 : 16,
> +			.shift = st->info->resolution > 16 ? 8 : 0,

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

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

> +			.endianness = IIO_BE,
> +		},
> +	};

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1779117444.git.marcelo.schmitt1@gmail.com?part=2

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

* Re: [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture
  2026-05-18 15:22 ` [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
@ 2026-05-18 16:19   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-05-18 16:19 UTC (permalink / raw)
  To: Marcelo Schmitt; +Cc: robh, conor+dt, devicetree

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

commit 87ed4789078a187c9262ff17873220fb5108858f
Author: Marcelo Schmitt <marcelo.schmitt@analog.com>

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

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

> diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
> index 70fec8e3e891f..b5368ee783f7b 100644
> --- a/drivers/iio/adc/Kconfig
> +++ b/drivers/iio/adc/Kconfig
[ ... ]
> +config LTC2378_OFFLOAD_BUFFER
> +	bool "Offloaded data capture with LTC2378"
> +	depends on SPI && LTC2378

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

> diff --git a/drivers/iio/adc/ltc2378-offload-buffer.c b/drivers/iio/adc/ltc2378-offload-buffer.c
> new file mode 100644
> index 0000000000000..ed09f9a55f93e
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2378-offload-buffer.c
[ ... ]
> +static int ltc2378_update_conversion_rate(struct ltc2378_state *st, int freq_Hz)
> +{
[ ... ]
> +	do {
> +		cnv_wf.duty_length_ns = target;
> +		ret = pwm_round_waveform_might_sleep(st->cnv_trigger, &cnv_wf);
> +		if (ret)
> +			return ret;
> +		target += 10;  /* Increment by PWM duty cycle period */
> +	} while (cnv_wf.duty_length_ns < LTC2378_TCNV_HIGH_NS);

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

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

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

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

[ ... ]
> +int ltc2378_offload_buffer_setup(struct iio_dev *indio_dev, struct spi_device *spi)
> +{
> +	struct ltc2378_state *st = iio_priv(indio_dev);
> +	struct device *dev = &spi->dev;
> +	int ret;
> +
> +	st->offload = devm_spi_offload_get(dev, spi, &ltc2378_offload_config);
> +	ret = PTR_ERR_OR_ZERO(st->offload);
> +	if (ret && ret != -ENODEV)
> +		return dev_err_probe(dev, ret, "failed to get offload\n");
> +
> +	ret = ltc2378_spi_offload_setup(indio_dev, st);

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

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

> diff --git a/drivers/iio/adc/ltc2378.c b/drivers/iio/adc/ltc2378.c
> index 7916500c470cf..fdbe919d45d55 100644
> --- a/drivers/iio/adc/ltc2378.c
> +++ b/drivers/iio/adc/ltc2378.c
[ ... ]
> @@ -226,6 +266,9 @@ static int ltc2378_read_raw(struct iio_dev *indio_dev,
>  }
>  
>  static const struct iio_info ltc2378_iio_info = {
> +#ifdef CONFIG_LTC2378_OFFLOAD_BUFFER
> +	.attrs = &ltc2378_offload_attribute_group,
> +#endif
>  	.read_raw = &ltc2378_read_raw,
>  };

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

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

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1779117444.git.marcelo.schmitt1@gmail.com?part=3

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

* Re: [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378
  2026-05-18 15:21 ` [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
  2026-05-18 15:29   ` sashiko-bot
@ 2026-05-18 17:06   ` Conor Dooley
  2026-05-18 18:42     ` Marcelo Schmitt
  1 sibling, 1 reply; 10+ messages in thread
From: Conor Dooley @ 2026-05-18 17:06 UTC (permalink / raw)
  To: Marcelo Schmitt
  Cc: linux-iio, devicetree, linux-kernel, jic23, nuno.sa,
	Michael.Hennerich, dlechner, andy, robh, krzk+dt, conor+dt

[-- Attachment #1: Type: text/plain, Size: 9144 bytes --]

On Mon, May 18, 2026 at 12:21:01PM -0300, Marcelo Schmitt wrote:
> From: Marcelo Schmitt <marcelo.schmitt@analog.com>
> 
> Document how to describe LTC2378-20 and similar ADCs in device tree.
> 
> Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> ---
>  .../bindings/iio/adc/adi,ltc2378.yaml         | 131 ++++++++++++++++++
>  MAINTAINERS                                   |   7 +
>  2 files changed, 138 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> 
> 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..56e59bcd7306
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
> @@ -0,0 +1,131 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/adi,ltc2378.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Analog Devices LTC2378 and similar Analog to Digital Converters
> +
> +maintainers:
> +  - Marcelo Schmitt <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:
> +      - enum:
> +          - adi,ltc2338-18
> +          - adi,ltc2369-18
> +          - adi,ltc2370-16
> +          - adi,ltc2379-18
> +          - adi,ltc2380-16
> +      - items:
> +          - enum:
> +              - adi,ltc2364-18
> +              - adi,ltc2364-16 # fallback compatible
> +      - items:
> +          - enum:
> +              - adi,ltc2367-18
> +              - adi,ltc2367-16 # fallback compatible
> +      - items:
> +          - enum:
> +              - adi,ltc2368-18
> +              - adi,ltc2368-16 # fallback compatible
> +      - items:
> +          - enum:
> +              - adi,ltc2376-18
> +              - adi,ltc2376-20
> +              - adi,ltc2376-16 # fallback compatible
> +      - items:
> +          - enum:
> +              - adi,ltc2377-18
> +              - adi,ltc2377-20
> +              - adi,ltc2377-16 # fallback compatible
> +      - items:
> +          - enum:
> +              - adi,ltc2378-18
> +              - adi,ltc2378-20
> +              - adi,ltc2378-16 # fallback compatible

All of these are wrong.

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

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

pw-bot: changes-requested

> +
> +  reg:
> +    maxItems: 1
> +
> +  spi-max-frequency:
> +    maximum: 100000000
> +
> +  vdd-supply:
> +    description: A 2.5V supply that powers the chip (VDD).
> +
> +  ovdd-supply:
> +    description:
> +      A 1.71V to 5.25V supply that sets the logic level for digital interface.
> +
> +  ref-supply:
> +    description:
> +      A 2.5V to 5.1V supply for the reference input (REF).
> +
> +  cnv-gpios:
> +    description:
> +      When provided, this property indicates the GPIO that is connected to the
> +      CNV pin.
> +    maxItems: 1
> +
> +  interrupts:
> +    description:
> +      Interrupt for signaling the completion of conversion results. The active
> +      low signal provided on the BUSY pin asserts when ADC conversions finish.
> +    maxItems: 1
> +
> +required:
> +  - compatible
> +  - reg
> +  - vdd-supply
> +  - ovdd-supply
> +  - ref-supply
> +
> +additionalProperties: false

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

Cheers,
Conor.

> +
> +examples:
> +  - |
> +    #include <dt-bindings/gpio/gpio.h>
> +    #include <dt-bindings/interrupt-controller/irq.h>
> +    spi {
> +        #address-cells = <1>;
> +        #size-cells = <0>;
> +        adc@0 {
> +            compatible = "adi,ltc2378-20";
> +            reg = <0>;
> +            spi-max-frequency = <71000000>;
> +            vdd-supply = <&supply_2_5V>;
> +            ovdd-supply = <&supply_3_3V>;
> +            ref-supply = <&supply_5V>;
> +            cnv-gpios = <&gpio0 88 GPIO_ACTIVE_HIGH>;
> +            interrupts = <7 IRQ_TYPE_EDGE_FALLING>;
> +            interrupt-parent = <&gpio>;
> +        };
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0de74503df08..768f56eea476 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15195,6 +15195,13 @@ S:	Maintained
>  F:	Documentation/devicetree/bindings/iio/dac/lltc,ltc1660.yaml
>  F:	drivers/iio/dac/ltc1660.c
>  
> +LTC2378 IIO ADC DRIVER
> +M:	Marcelo Schmitt <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
> 

[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]

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

* Re: [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378
  2026-05-18 17:06   ` Conor Dooley
@ 2026-05-18 18:42     ` Marcelo Schmitt
  0 siblings, 0 replies; 10+ messages in thread
From: Marcelo Schmitt @ 2026-05-18 18:42 UTC (permalink / raw)
  To: Conor Dooley
  Cc: linux-iio, devicetree, linux-kernel, jic23, nuno.sa,
	Michael.Hennerich, dlechner, andy, robh, krzk+dt, conor+dt

Hi Conor,

On 05/18, Conor Dooley wrote:
> On Mon, May 18, 2026 at 12:21:01PM -0300, Marcelo Schmitt wrote:
> > From: Marcelo Schmitt <marcelo.schmitt@analog.com>
> > 
> > Document how to describe LTC2378-20 and similar ADCs in device tree.
> > 
> > Signed-off-by: Marcelo Schmitt <marcelo.schmitt@analog.com>
> > ---
...
> > +properties:
> > +  compatible:
> > +    oneOf:
> > +      - enum:
> > +          - adi,ltc2338-18
> > +          - adi,ltc2369-18
> > +          - adi,ltc2370-16
> > +          - adi,ltc2379-18
> > +          - adi,ltc2380-16
> > +      - items:
> > +          - enum:
> > +              - adi,ltc2364-18
> > +              - adi,ltc2364-16 # fallback compatible
> > +      - items:
> > +          - enum:
> > +              - adi,ltc2367-18
> > +              - adi,ltc2367-16 # fallback compatible
> > +      - items:
> > +          - enum:
> > +              - adi,ltc2368-18
> > +              - adi,ltc2368-16 # fallback compatible
> > +      - items:
> > +          - enum:
> > +              - adi,ltc2376-18
> > +              - adi,ltc2376-20
> > +              - adi,ltc2376-16 # fallback compatible
> > +      - items:
> > +          - enum:
> > +              - adi,ltc2377-18
> > +              - adi,ltc2377-20
> > +              - adi,ltc2377-16 # fallback compatible
> > +      - items:
> > +          - enum:
> > +              - adi,ltc2378-18
> > +              - adi,ltc2378-20
> > +              - adi,ltc2378-16 # fallback compatible
> 
> All of these are wrong.
> 
> Firstly, your "fallback" setups don't work, because they only permit a
> single compatible. Secondly, if they did work, you still need to define
> the fallback compatible in isolation for it to be permitted for use when
> it isn't a fallback.
> And thirdly, your driver appears to show that these devices aren't
> actually compatible at all, given there's unique match data for each
> one.
...
> 
> Why do you have fallbacks here? Is it because a limited set of channels
> could be used in the fallback case? At the very least, the rationale for
> fallbacks should be explained.
> 
Some devices can be grouped by main number (e.g. ltc2378-16, -18, -20) and
those vary only on the amount of precision bits. I initially thought we could
fallback to reading only 16 or 18 bits if a driver compatible with ltc2378-20 is
not found. Though, I now realize that won't work because the less significant
bits would be missing which would compromise scaling the data to milli volts.
Will make this a unique match list (no separate items lists, no fallbacks).

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

Ack, switching to 'unevaluatedProperties: false'.

Thanks,
Marcelo

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

end of thread, other threads:[~2026-05-18 18:41 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-18 15:20 [PATCH v1 0/4] iio: adc: Add support for LTC2378 and similar ADCs Marcelo Schmitt
2026-05-18 15:21 ` [PATCH v1 1/4] dt-bindings: iio: adc: Add ltc2378 Marcelo Schmitt
2026-05-18 15:29   ` sashiko-bot
2026-05-18 17:06   ` Conor Dooley
2026-05-18 18:42     ` Marcelo Schmitt
2026-05-18 15:21 ` [PATCH v1 2/4] iio: adc: Add support for LTC2378-20 and similar ADCs Marcelo Schmitt
2026-05-18 15:51   ` sashiko-bot
2026-05-18 15:22 ` [PATCH v1 3/4] iio: adc: ltc2378: Enable high-speed data capture Marcelo Schmitt
2026-05-18 16:19   ` sashiko-bot
2026-05-18 15:22 ` [PATCH v1 4/4] iio: adc: ltc2378: Enable triggered buffer " Marcelo Schmitt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox