mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support
@ 2026-10-27 13:44 Nicolae-Daniel Deaconescu
  2026-10-27 13:44 ` [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387 Nicolae-Daniel Deaconescu
                   ` (2 more replies)
  0 siblings, 3 replies; 6+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-10-27 13:44 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu, Sergiu Cuciurean, Jonathan Cameron,
	David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm

This series adds a driver for the LTC2387-16/18, 15 Msps SAR ADCs with
an LVDS data interface, and for the ADAQ23875, ADAQ23876 and ADAQ23878
uModule ADCs, which pair an LTC2387 die with a fully differential
amplifier front end.

Conversion start (CNV) and the gated LVDS data clock are generated by
two channels of an AXI PWM generator. Sample data is captured by the
AXI ADC IP core and reached through the IIO backend framework. The
front-end gain of the ADAQ parts is described with adi,gain-milli
(fixed at 2 on the ADAQ23875, pin-strapped on the ADAQ23876/78) and
applied to in_voltage_scale.

Tested on an EV-ADAQ23875FMCZ on a ZedBoard running the adaq2387x HDL
project in two-lane, 16-bit mode: probe, in_voltage_scale,
sampling_frequency (writes, rounding and range checks), buffered
capture up to 1M samples, and unbind/rebind. The bare LTC2387
variants, one-lane mode and the -x4 variants were not tested on
hardware.

Datasheets:
  https://www.analog.com/LTC2387-16
  https://www.analog.com/LTC2387-18
  https://www.analog.com/ADAQ23875
  https://www.analog.com/ADAQ23876
  https://www.analog.com/ADAQ23878

Evaluation board:
  https://www.analog.com/EVAL-ADAQ23875

HDL reference design and IP cores:
  https://analogdevicesinc.github.io/hdl/projects/adaq23875/index.html
  https://analogdevicesinc.github.io/hdl/library/axi_ltc2387/index.html
  https://analogdevicesinc.github.io/hdl/library/axi_pwm_gen/index.html

Sergiu Cuciurean (2):
  dt-bindings: iio: adc: add lltc,ltc2387
  iio: adc: add ltc2387 driver

 .../bindings/iio/adc/lltc,ltc2387.yaml        | 138 +++++
 MAINTAINERS                                   |   9 +
 drivers/iio/adc/Kconfig                       |  11 +
 drivers/iio/adc/Makefile                      |   1 +
 drivers/iio/adc/ltc2387.c                     | 482 ++++++++++++++++++
 5 files changed, 641 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
 create mode 100644 drivers/iio/adc/ltc2387.c


base-commit: 43ffda3c7f8456bbe641ae8249a077a361b97880
-- 
2.34.1


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

* [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387
  2026-10-27 13:44 [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
@ 2026-10-27 13:44 ` Nicolae-Daniel Deaconescu
  2026-10-27 18:28   ` Conor Dooley
  2026-10-27 13:44 ` [PATCH 2/2] iio: adc: add ltc2387 driver Nicolae-Daniel Deaconescu
  2026-10-27 15:56 ` [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Andy Shevchenko
  2 siblings, 1 reply; 6+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-10-27 13:44 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu, Sergiu Cuciurean, Jonathan Cameron,
	David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm

From: Sergiu Cuciurean <sergiu.cuciurean@analog.com>

Add bindings for the LTC2387-16 and LTC2387-18, 15 Msps SAR ADCs with
an LVDS data interface, and for the ADAQ23875, ADAQ23876 and ADAQ23878
uModule ADCs, which pair an LTC2387 die with a fully differential
amplifier front end.

Conversion start (CNV) and the gated LVDS clock are driven by two PWM
channels. Sample data is captured by an AXI ADC IP core, referenced
through io-backends. adi,use-two-lanes selects two-lane output, and
adi,gain-milli gives the front-end gain: fixed at 2 on the ADAQ23875,
set by pin strapping on the ADAQ23876 and ADAQ23878.

Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Co-developed-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
Assisted-by: Claude:claude-opus-5-5
---
 .../bindings/iio/adc/lltc,ltc2387.yaml        | 138 ++++++++++++++++++
 MAINTAINERS                                   |   8 +
 2 files changed, 146 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml

diff --git a/Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
new file mode 100644
index 000000000000..f912260c8f79
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
@@ -0,0 +1,138 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+# Copyright 2022 Analog Devices Inc.
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/lltc,ltc2387.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Linear Technology / Analog Devices LTC2387 ADC
+
+maintainers:
+  - Sergiu Cuciurean <sergiu.cuciurean@analog.com>
+  - Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
+
+description: |
+  The LTC2387-18 is a low noise, high speed, 18-bit 15Msps
+  successive approximation register (SAR) ADC ideally
+  suited for a wide range of applications.
+
+  https://www.analog.com/media/en/technical-documentation/data-sheets/238718fa.pdf
+  https://www.analog.com/media/en/technical-documentation/data-sheets/238716f.pdf
+
+properties:
+  compatible:
+    enum:
+      - adi,adaq23875
+      - adi,adaq23876
+      - adi,adaq23878
+      - lltc,ltc2387-16
+      - lltc,ltc2387-16-x4
+      - lltc,ltc2387-18
+      - lltc,ltc2387-18-x4
+
+  clocks:
+    description: Sampling clock
+    maxItems: 1
+
+  pwms:
+    maxItems: 2
+    description:
+      PWM signals used to synchronize the sampling procedure
+
+  pwm-names:
+    items:
+      - const: cnv
+      - const: clk_gate
+
+  io-backends:
+    description:
+      The AXI ADC IP block (axi_ltc2387) that captures the LVDS data stream.
+    maxItems: 1
+
+  vref-supply:
+    description:
+      External voltage reference. When absent the device's internal 4.096 V
+      reference is assumed.
+
+  adi,use-two-lanes:
+    description: The device uses two lanes for data transfer
+    type: boolean
+
+  adi,gain-milli:
+    description:
+      Gain of the fully-differential amplifier that the ADAQ2387x modules
+      integrate ahead of the LTC2387 die, expressed as gain * 1000. It is
+      fixed on the ADAQ23875 and set by pin strapping on the ADAQ23876 and
+      ADAQ23878, so it must match the board. The bare ltc2387-* parts have
+      no amplifier.
+    $ref: /schemas/types.yaml#/definitions/uint32
+    enum: [370, 730, 870, 1380, 2000, 2250]
+
+required:
+  - compatible
+  - clocks
+  - io-backends
+  - pwms
+  - pwm-names
+
+allOf:
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - lltc,ltc2387-16
+              - lltc,ltc2387-16-x4
+              - lltc,ltc2387-18
+              - lltc,ltc2387-18-x4
+    then:
+      properties:
+        adi,gain-milli: false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: adi,adaq23875
+    then:
+      properties:
+        adi,gain-milli:
+          const: 2000
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - adi,adaq23876
+              - adi,adaq23878
+    then:
+      properties:
+        adi,gain-milli:
+          enum: [370, 730, 870, 1380, 2250]
+      required:
+        - adi,gain-milli
+
+additionalProperties: false
+
+examples:
+  - |
+    adc {
+            compatible = "lltc,ltc2387-16";
+            clocks = <&clk 0>;
+            io-backends = <&axi_adc>;
+            pwms = <&pwm 0 0>, <&pwm 1 0>;
+            pwm-names = "cnv", "clk_gate";
+            vref-supply = <&vref>;
+    };
+  - |
+    adc {
+            compatible = "adi,adaq23878";
+            clocks = <&clk 0>;
+            io-backends = <&axi_adc>;
+            pwms = <&pwm 0 0>, <&pwm 1 0>;
+            pwm-names = "cnv", "clk_gate";
+            vref-supply = <&vref>;
+            adi,use-two-lanes;
+            adi,gain-milli = <870>;
+    };
diff --git a/MAINTAINERS b/MAINTAINERS
index 52024397dc78..b871d7f25d76 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15529,6 +15529,14 @@ W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
 F:	drivers/iio/adc/ltc2378.c
 
+LTC2387 IIO ADC DRIVER
+M:	Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
+M:	Sergiu Cuciurean <sergiu.cuciurean@analog.com>
+L:	linux-iio@vger.kernel.org
+S:	Supported
+W:	https://ez.analog.com/linux-software-drivers
+F:	Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
+
 LTC2664 IIO DAC DRIVER
 M:	Michael Hennerich <michael.hennerich@analog.com>
 M:	Kim Seer Paller <kimseer.paller@analog.com>
-- 
2.34.1


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

* [PATCH 2/2] iio: adc: add ltc2387 driver
  2026-10-27 13:44 [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
  2026-10-27 13:44 ` [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387 Nicolae-Daniel Deaconescu
@ 2026-10-27 13:44 ` Nicolae-Daniel Deaconescu
  2026-10-27 14:37   ` Joshua Crofts
  2026-10-27 15:56 ` [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Andy Shevchenko
  2 siblings, 1 reply; 6+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-10-27 13:44 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu, Sergiu Cuciurean, Jonathan Cameron,
	David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König
  Cc: linux-iio, devicetree, linux-kernel, linux-pwm

From: Sergiu Cuciurean <sergiu.cuciurean@analog.com>

Add a driver for the LTC2387-16/18 and the ADAQ23875/23876/23878.

The driver generates CNV and the gated data clock with two PWM
channels, timed against the reference clock to meet t_FIRSTCLK, and
gets sample data through the IIO backend framework from the AXI ADC
core. It supports one-lane and two-lane output and reports the
sampling frequency the PWMs produce after rounding to whole reference
clock cycles. in_voltage_scale is referred to the analog input, so on
the ADAQ parts it is divided by the front-end gain.

Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
Co-developed-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
Assisted-by: Claude:claude-opus-5-5
---
 MAINTAINERS               |   1 +
 drivers/iio/adc/Kconfig   |  11 +
 drivers/iio/adc/Makefile  |   1 +
 drivers/iio/adc/ltc2387.c | 482 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 495 insertions(+)
 create mode 100644 drivers/iio/adc/ltc2387.c

diff --git a/MAINTAINERS b/MAINTAINERS
index b871d7f25d76..6e242a955e09 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15536,6 +15536,7 @@ L:	linux-iio@vger.kernel.org
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
+F:	drivers/iio/adc/ltc2387.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 02de4809ae31..c543f066dbee 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -994,6 +994,17 @@ config LTC2378
 	  This driver can also be built as a module. If so, the module will
 	  be called ltc2378.
 
+config LTC2387
+	tristate "Linear Technology LTC2387 ADC driver"
+	depends on PWM
+	select IIO_BACKEND
+	help
+	  Say yes here to build support for Linear Technology LTC2387 18/16
+	  bit 15Msps SAR ADC.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called ltc2387.
+
 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 9e82c21dbd37..4032276f55f1 100644
--- a/drivers/iio/adc/Makefile
+++ b/drivers/iio/adc/Makefile
@@ -84,6 +84,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_LTC2387) += ltc2387.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/ltc2387.c b/drivers/iio/adc/ltc2387.c
new file mode 100644
index 000000000000..c236b99baa4a
--- /dev/null
+++ b/drivers/iio/adc/ltc2387.c
@@ -0,0 +1,482 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+/*
+ * Linear Technology LTC2387 ADC driver
+ *
+ * Copyright 2022 - 2026 Analog Devices Inc.
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/iio/backend.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mod_devicetable.h>
+
+#define LTC2387_VREF		4096
+#define LTC2387_T_CNVH		8
+
+/*
+ * Minimal value for t_{FIRSTCLK} according to
+ * https://www.analog.com/media/en/technical-documentation/data-sheets/238718fa.pdf
+ * is 65 ns. Add some slack because there is some rounding involved in the PWM
+ * driver. With the PWM driver rounding to the nearest possible value, targeting
+ * 70 ns works for input clk rates >= 100 MHz.
+ */
+#define LTC2387_T_FIRSTCLK_NS	70
+
+#define KHz 1000
+#define MHz (1000 * KHz)
+
+#define LTC2378_MULTIPLE_CHAN(_idx, _storagebits, _realbits, _shift)	\
+	{								\
+		.type = IIO_VOLTAGE,					\
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
+		.indexed = 1,						\
+		.channel = _idx,					\
+		.scan_index = _idx,					\
+		.scan_type = {						\
+			.sign = 's',					\
+			.storagebits = _storagebits,			\
+			.realbits = _realbits,				\
+			.shift = _shift,				\
+		},							\
+	}
+
+#define LTC2378_CHAN(_realbits, _storagebits)				\
+	{								\
+		.type = IIO_VOLTAGE,					\
+		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
+		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
+		.scan_type = {						\
+			.sign = 's',					\
+			.storagebits = _storagebits,			\
+			.realbits = _realbits,				\
+		},							\
+	}
+
+enum ltc2387_lane_modes {
+	ONE_LANE = 0,
+	TWO_LANES = 1
+};
+
+enum ltc2387_id {
+	ID_LTC2387_16,
+	ID_LTC2387_16_X4,
+	ID_LTC2387_18,
+	ID_LTC2387_18_X4,
+	ID_ADAQ23875,
+	ID_ADAQ23876,
+	ID_ADAQ23878,
+};
+
+struct ltc2387_info {
+	const char *name;
+	struct iio_chan_spec channels[4];
+	unsigned int test_pattern[2];
+	int num_channels;
+	int resolution;
+	/* Gain * 1000 when adi,gain-milli is absent; unity for the bare parts. */
+	u32 default_gain_milli;
+};
+
+#define LTC2387_TEST_PATTERN_16 {					\
+	[ONE_LANE]  = 0b1010000001111111,				\
+	[TWO_LANES] = 0b1100110000111111,				\
+}
+
+#define LTC2387_TEST_PATTERN_18 {					\
+	[ONE_LANE]  = 0b101000000111111100,				\
+	[TWO_LANES] = 0b110011000011111100,				\
+}
+
+static const struct ltc2387_info ltc2387_infos[] = {
+	[ID_LTC2387_16] = {
+		.name = "ltc2387-16",
+		.default_gain_milli = 1000,
+		.resolution = 16,
+		.test_pattern = LTC2387_TEST_PATTERN_16,
+		.channels = { LTC2378_CHAN(16, 16) },
+		.num_channels = 1,
+	},
+	[ID_LTC2387_16_X4] = {
+		.name = "ltc2387-16-x4",
+		.default_gain_milli = 1000,
+		.resolution = 16,
+		.test_pattern = LTC2387_TEST_PATTERN_16,
+		.channels = {
+			LTC2378_MULTIPLE_CHAN(0, 64, 16, 0),
+			LTC2378_MULTIPLE_CHAN(1, 64, 16, 16),
+			LTC2378_MULTIPLE_CHAN(2, 64, 16, 32),
+			LTC2378_MULTIPLE_CHAN(3, 64, 16, 48),
+		},
+		.num_channels = 4,
+	},
+	[ID_LTC2387_18] = {
+		.name = "ltc2387-18",
+		.default_gain_milli = 1000,
+		.resolution = 18,
+		.test_pattern = LTC2387_TEST_PATTERN_18,
+		.channels = { LTC2378_CHAN(18, 32) },
+		.num_channels = 1,
+	},
+	[ID_LTC2387_18_X4] = {
+		.name = "ltc2387-18-x4",
+		.default_gain_milli = 1000,
+		.resolution = 18,
+		.test_pattern = LTC2387_TEST_PATTERN_18,
+		.channels = {
+			LTC2378_MULTIPLE_CHAN(0, 128, 18, 0),
+			LTC2378_MULTIPLE_CHAN(1, 128, 18, 32),
+			LTC2378_MULTIPLE_CHAN(2, 128, 18, 64),
+			LTC2378_MULTIPLE_CHAN(3, 128, 18, 96),
+		},
+		.num_channels = 4,
+	},
+	[ID_ADAQ23875] = {
+		.name = "adaq23875",
+		.default_gain_milli = 2000,
+		.resolution = 16,
+		.test_pattern = LTC2387_TEST_PATTERN_16,
+		.channels = { LTC2378_CHAN(16, 16) },
+		.num_channels = 1,
+	},
+	[ID_ADAQ23876] = {
+		.name = "adaq23876",
+		.default_gain_milli = 0,
+		.resolution = 16,
+		.test_pattern = LTC2387_TEST_PATTERN_16,
+		.channels = { LTC2378_CHAN(16, 16) },
+		.num_channels = 1,
+	},
+	[ID_ADAQ23878] = {
+		.name = "adaq23878",
+		.default_gain_milli = 0,
+		.resolution = 18,
+		.test_pattern = LTC2387_TEST_PATTERN_18,
+		.channels = { LTC2378_CHAN(18, 32) },
+		.num_channels = 1,
+	},
+};
+
+struct ltc2387_dev {
+	const struct ltc2387_info *device_info;
+	enum ltc2387_lane_modes lane_mode;
+	unsigned long ref_clk_rate;
+	struct iio_backend *back;
+	struct pwm_device *clk_gate;
+	struct regulator *vref;
+	struct pwm_device *cnv;
+	struct clk *ref_clk;
+
+	unsigned int vref_mv;
+	u32 sampling_freq;
+	u32 gain_milli;
+};
+
+static int ltc2387_set_sampling_freq(struct ltc2387_dev *ltc, int freq)
+{
+	unsigned long long ref_clk_period_ns;
+	struct pwm_waveform clk_gate_wf = { }, cnv_wf = { };
+	unsigned long long period_cycles;
+	int ret, clk_en_time;
+	u32 rem;
+
+	if (freq <= 0 || freq > ltc->ref_clk_rate)
+		return -EINVAL;
+
+	ref_clk_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, ltc->ref_clk_rate);
+
+	cnv_wf.duty_length_ns = ref_clk_period_ns;
+
+	/*
+	 * The goal here is that the PWM is configured with a minimal period not
+	 * less than 1 / freq (with freq measured in Hz).
+	 *
+	 * When a period P (measured in ns) is passed to pwm_apply_state(), the
+	 * actually implemented period is:
+	 *
+	 *      round_down(P * R / NSEC_PER_SEC) / R
+	 *
+	 * (measured in s) with R = ltc->ref_clk_rate. So we have:
+	 *
+	 *        round_down(P * R / NSEC_PER_SEC) / R ≥ 1 / freq
+	 *      ⟺ round_down(P * R / NSEC_PER_SEC) ≥ R / freq
+	 *
+	 * With the LHS being integer this is equivalent to:
+	 *
+	 *        round_down(P * R / NSEC_PER_SEC) ≥ round_up(R / freq)
+	 *      ⟺ P * R / NSEC_PER_SEC ≥ round_up(R / freq)
+	 *      ⟺ P ≥ round_up(R / freq) * NSEC_PER_SEC / R
+	 */
+
+	cnv_wf.period_length_ns =
+		div_u64_rem((u64)DIV_ROUND_UP(ltc->ref_clk_rate, freq) * NSEC_PER_SEC,
+			    ltc->ref_clk_rate, &rem);
+	if (rem)
+		cnv_wf.period_length_ns += 1;
+
+	/* Gate the active period of the clock (see page 10-13 for both LTC's) */
+	if (ltc->lane_mode == TWO_LANES)
+		clk_en_time = DIV_ROUND_UP_ULL(ltc->device_info->resolution, 4);
+	else
+		clk_en_time = DIV_ROUND_UP_ULL(ltc->device_info->resolution, 2);
+
+	clk_gate_wf.duty_length_ns = ref_clk_period_ns * clk_en_time;
+	clk_gate_wf.duty_offset_ns = LTC2387_T_FIRSTCLK_NS;
+
+	if (cnv_wf.period_length_ns < clk_gate_wf.duty_length_ns)
+		cnv_wf.period_length_ns = clk_gate_wf.duty_length_ns + ref_clk_period_ns;
+
+	clk_gate_wf.period_length_ns = cnv_wf.period_length_ns;
+
+	if (clk_gate_wf.duty_offset_ns >= clk_gate_wf.period_length_ns)
+		div64_u64_rem(clk_gate_wf.duty_offset_ns,
+			      clk_gate_wf.period_length_ns,
+			      &clk_gate_wf.duty_offset_ns);
+
+	ret = pwm_set_waveform_might_sleep(ltc->cnv, &cnv_wf, false);
+	if (ret < 0)
+		return ret;
+
+	ret = pwm_set_waveform_might_sleep(ltc->clk_gate, &clk_gate_wf, false);
+	if (ret < 0)
+		return ret;
+
+	period_cycles = mul_u64_u32_div(cnv_wf.period_length_ns,
+					ltc->ref_clk_rate, NSEC_PER_SEC);
+	if (!period_cycles)
+		return -EINVAL;
+
+	ltc->sampling_freq = DIV_ROUND_CLOSEST_ULL(ltc->ref_clk_rate,
+						   period_cycles);
+
+	return 0;
+}
+
+static int ltc2387_setup(struct iio_dev *indio_dev)
+{
+	struct ltc2387_dev *ltc = iio_priv(indio_dev);
+	struct device *dev = indio_dev->dev.parent;
+
+	if (device_property_present(dev, "adi,use-two-lanes"))
+		ltc->lane_mode = TWO_LANES;
+
+	return ltc2387_set_sampling_freq(ltc, 15 * MHz);
+}
+
+static int ltc2387_read_raw(struct iio_dev *indio_dev,
+			    const struct iio_chan_spec *chan,
+			    int *val, int *val2, long info)
+{
+	struct ltc2387_dev *ltc = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		*val = ltc->sampling_freq;
+		return IIO_VAL_INT;
+	case IIO_CHAN_INFO_SCALE:
+		/* Referred to the SMA input, so divided by the front-end gain. */
+		*val = ltc->vref_mv * 2 * 1000;
+		*val2 = ltc->gain_milli << chan->scan_type.realbits;
+		return IIO_VAL_FRACTIONAL;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int ltc2387_write_raw(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     int val, int val2, long info)
+{
+	struct ltc2387_dev *ltc = iio_priv(indio_dev);
+
+	switch (info) {
+	case IIO_CHAN_INFO_SAMP_FREQ:
+		return ltc2387_set_sampling_freq(ltc, val);
+
+	default:
+		return -EINVAL;
+	}
+}
+
+static void ltc2387_pwm_disable(void *data)
+{
+	pwm_disable(data);
+}
+
+static void ltc2387_regulator_disable(void *data)
+{
+	regulator_disable(data);
+}
+
+static void ltc2387_clk_disable(void *data)
+{
+	clk_disable_unprepare(data);
+}
+
+static const struct iio_info ltc2387_info = {
+	.read_raw = ltc2387_read_raw,
+	.write_raw = ltc2387_write_raw,
+};
+
+static const struct of_device_id ltc2387_of_match[] = {
+	{
+		.compatible = "lltc,ltc2387-16",
+		.data = &ltc2387_infos[ID_LTC2387_16]
+	}, {
+		.compatible = "lltc,ltc2387-16-x4",
+		.data = &ltc2387_infos[ID_LTC2387_16_X4]
+	}, {
+		.compatible = "lltc,ltc2387-18",
+		.data = &ltc2387_infos[ID_LTC2387_18]
+	}, {
+		.compatible = "lltc,ltc2387-18-x4",
+		.data = &ltc2387_infos[ID_LTC2387_18_X4]
+	}, {
+		.compatible = "adi,adaq23875",
+		.data = &ltc2387_infos[ID_ADAQ23875]
+	}, {
+		.compatible = "adi,adaq23876",
+		.data = &ltc2387_infos[ID_ADAQ23876]
+	}, {
+		.compatible = "adi,adaq23878",
+		.data = &ltc2387_infos[ID_ADAQ23878]
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, ltc2387_of_match);
+
+static int ltc2387_probe(struct platform_device *pdev)
+{
+	struct iio_dev			*indio_dev;
+	struct ltc2387_dev		*ltc;
+	int				ret;
+
+	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ltc));
+	if (!indio_dev)
+		return -ENOMEM;
+
+	ltc = iio_priv(indio_dev);
+
+	ltc->vref = devm_regulator_get_optional(&pdev->dev, "vref");
+	if (!IS_ERR(ltc->vref)) {
+		ret = regulator_enable(ltc->vref);
+		if (ret) {
+			dev_err(&pdev->dev, "Can't enable vref regulator\n");
+			return ret;
+		}
+		ret = regulator_get_voltage(ltc->vref);
+		if (ret < 0)
+			return ret;
+
+		ltc->vref_mv = ret / 1000;
+		ret = devm_add_action_or_reset(&pdev->dev,
+					       ltc2387_regulator_disable,
+					       ltc->vref);
+		if (ret)
+			return ret;
+	} else {
+		if (PTR_ERR(ltc->vref) != -ENODEV)
+			return PTR_ERR(ltc->vref);
+
+		ltc->vref_mv = LTC2387_VREF; /* Internal vref is used */
+	}
+
+	ltc->ref_clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(ltc->ref_clk))
+		return PTR_ERR(ltc->ref_clk);
+
+	ret = clk_prepare_enable(ltc->ref_clk);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(&pdev->dev, ltc2387_clk_disable,
+				       ltc->ref_clk);
+	if (ret)
+		return ret;
+	ltc->ref_clk_rate = clk_get_rate(ltc->ref_clk);
+
+	ltc->clk_gate = devm_pwm_get(&pdev->dev, "clk_gate");
+	if (IS_ERR(ltc->clk_gate))
+		return PTR_ERR(ltc->clk_gate);
+
+	ret = devm_add_action_or_reset(&pdev->dev, ltc2387_pwm_disable,
+				       ltc->clk_gate);
+	if (ret)
+		return ret;
+
+	ltc->cnv = devm_pwm_get(&pdev->dev, "cnv");
+	if (IS_ERR(ltc->cnv))
+		return PTR_ERR(ltc->cnv);
+
+	ret = devm_add_action_or_reset(&pdev->dev, ltc2387_pwm_disable,
+				       ltc->cnv);
+
+	if (ret)
+		return ret;
+
+	ltc->device_info = device_get_match_data(&pdev->dev);
+	if (!ltc->device_info)
+		return -EINVAL;
+
+	ltc->gain_milli = ltc->device_info->default_gain_milli;
+	device_property_read_u32(&pdev->dev, "adi,gain-milli", &ltc->gain_milli);
+	if (!ltc->gain_milli)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "adi,gain-milli must be nonzero\n");
+
+	indio_dev->channels = ltc->device_info->channels;
+	indio_dev->num_channels = ltc->device_info->num_channels;
+	indio_dev->dev.parent = &pdev->dev;
+	indio_dev->name = ltc->device_info->name;
+	indio_dev->info = &ltc2387_info;
+
+	ltc->back = devm_iio_backend_get(&pdev->dev, NULL);
+	if (IS_ERR(ltc->back))
+		return dev_err_probe(&pdev->dev, PTR_ERR(ltc->back),
+				     "failed to get IIO backend\n");
+
+	ret = devm_iio_backend_request_buffer(&pdev->dev, ltc->back, indio_dev);
+	if (ret)
+		return ret;
+
+	ret = devm_iio_backend_enable(&pdev->dev, ltc->back);
+	if (ret)
+		return ret;
+
+	/* axi_ltc2387 has a single capture channel carrying every scan element. */
+	ret = iio_backend_chan_enable(ltc->back, 0);
+	if (ret)
+		return ret;
+
+	ret = ltc2387_setup(indio_dev);
+	if (ret < 0) {
+		dev_err(&pdev->dev, "\nltc2387 setup failed\n");
+		return ret;
+	}
+
+	return devm_iio_device_register(&pdev->dev, indio_dev);
+}
+
+static struct platform_driver ltc2387_driver = {
+	.probe          = ltc2387_probe,
+	.driver         = {
+		.name   = "ltc2387",
+		.of_match_table = ltc2387_of_match,
+	},
+};
+module_platform_driver(ltc2387_driver);
+
+MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@analog.com>");
+MODULE_DESCRIPTION("Linear Technology LTC2387 ADC");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_IMPORT_NS("IIO_BACKEND");
-- 
2.34.1


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

* Re: [PATCH 2/2] iio: adc: add ltc2387 driver
  2026-10-27 13:44 ` [PATCH 2/2] iio: adc: add ltc2387 driver Nicolae-Daniel Deaconescu
@ 2026-10-27 14:37   ` Joshua Crofts
  0 siblings, 0 replies; 6+ messages in thread
From: Joshua Crofts @ 2026-10-27 14:37 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu
  Cc: Sergiu Cuciurean, Jonathan Cameron, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, linux-iio, devicetree, linux-kernel,
	linux-pwm

On Tue, 27 Oct 2026 15:44:09 +0200
Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com> wrote:

> From: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> 
> Add a driver for the LTC2387-16/18 and the ADAQ23875/23876/23878.

Hi, some comments from me. Additionally, please check out what
Sashiko says:

https://sashiko.dev/#/patchset/20261027134409.76824-1-Nicolae-daniel.Deaconescu%40analog.com


> The driver generates CNV and the gated data clock with two PWM
> channels, timed against the reference clock to meet t_FIRSTCLK, and
> gets sample data through the IIO backend framework from the AXI ADC
> core. It supports one-lane and two-lane output and reports the
> sampling frequency the PWMs produce after rounding to whole reference
> clock cycles. in_voltage_scale is referred to the analog input, so on
> the ADAQ parts it is divided by the front-end gain.
> 
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Co-developed-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
> Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
> Assisted-by: Claude:claude-opus-5-5

Drop this tag, it has been deprecated.

> diff --git a/drivers/iio/adc/ltc2387.c b/drivers/iio/adc/ltc2387.c
> new file mode 100644
> index 000000000000..c236b99baa4a
> --- /dev/null
> +++ b/drivers/iio/adc/ltc2387.c
> @@ -0,0 +1,482 @@
> +// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
> +/*
> + * Linear Technology LTC2387 ADC driver
> + *
> + * Copyright 2022 - 2026 Analog Devices Inc.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/device.h>
> +#include <linux/err.h>
> +#include <linux/iio/backend.h>
> +#include <linux/iio/iio.h>
> +#include <linux/iio/sysfs.h>

Better to have the iio specific headers separately after the generic
linux headers.

> +#include <linux/kernel.h>

No, do not use this header for new drivers. Use the IWYU principle when
deciding what to include.

> +#include <linux/math64.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/pwm.h>
> +#include <linux/regulator/consumer.h>
> +#include <linux/mod_devicetable.h>

Drop this header, it's included by platform_device.h

+ you're missing additional headers, e.g. types.h

> +
> +#define LTC2387_VREF		4096
> +#define LTC2387_T_CNVH		8
> +
> +/*
> + * Minimal value for t_{FIRSTCLK} according to
> + * https://www.analog.com/media/en/technical-documentation/data-sheets/238718fa.pdf
> + * is 65 ns. Add some slack because there is some rounding involved in the PWM
> + * driver. With the PWM driver rounding to the nearest possible value, targeting
> + * 70 ns works for input clk rates >= 100 MHz.
> + */
> +#define LTC2387_T_FIRSTCLK_NS	70
> +
> +#define KHz 1000

We have units.h for this.

> +#define MHz (1000 * KHz)
> +
> +#define LTC2378_MULTIPLE_CHAN(_idx, _storagebits, _realbits, _shift)	\
> +	{								\
> +		.type = IIO_VOLTAGE,					\
> +		.info_mask_shared_by_all = BIT(IIO_CHAN_INFO_SAMP_FREQ),\
> +		.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE),\
> +		.indexed = 1,						\
> +		.channel = _idx,					\
> +		.scan_index = _idx,					\
> +		.scan_type = {						\
> +			.sign = 's',					\
> +			.storagebits = _storagebits,			\
> +			.realbits = _realbits,				\
> +			.shift = _shift,				\
> +		},							\
> +	}
> +

...

> +struct ltc2387_dev {
> +	const struct ltc2387_info *device_info;
> +	enum ltc2387_lane_modes lane_mode;
> +	unsigned long ref_clk_rate;
> +	struct iio_backend *back;
> +	struct pwm_device *clk_gate;
> +	struct regulator *vref;
> +	struct pwm_device *cnv;
> +	struct clk *ref_clk;
> +
> +	unsigned int vref_mv;

_mV

> +	u32 sampling_freq;
> +	u32 gain_milli;
> +};
> +
> +static int ltc2387_set_sampling_freq(struct ltc2387_dev *ltc, int freq)
> +{
> +	unsigned long long ref_clk_period_ns;
> +	struct pwm_waveform clk_gate_wf = { }, cnv_wf = { };

Reverse xmas tree please.

> +	unsigned long long period_cycles;
> +	int ret, clk_en_time;
> +	u32 rem;
> +
> +	if (freq <= 0 || freq > ltc->ref_clk_rate)
> +		return -EINVAL;
> +
> +	ref_clk_period_ns = DIV_ROUND_UP(NSEC_PER_SEC, ltc->ref_clk_rate);
> +
> +	cnv_wf.duty_length_ns = ref_clk_period_ns;
> +
> +	/*
> +	 * The goal here is that the PWM is configured with a minimal period not
> +	 * less than 1 / freq (with freq measured in Hz).
> +	 *
> +	 * When a period P (measured in ns) is passed to pwm_apply_state(), the
> +	 * actually implemented period is:
> +	 *
> +	 *      round_down(P * R / NSEC_PER_SEC) / R
> +	 *
> +	 * (measured in s) with R = ltc->ref_clk_rate. So we have:
> +	 *
> +	 *        round_down(P * R / NSEC_PER_SEC) / R ≥ 1 / freq
> +	 *      ⟺ round_down(P * R / NSEC_PER_SEC) ≥ R / freq

Ehhh, not really sure about these non-standard characters in the comments.
Had to check lore to see what this renders to. Wouldn't it be easier to use
a trigraph <=>? Same for the digraph stuff you have.

> +	 *
> +	 * With the LHS being integer this is equivalent to:
> +	 *
> +	 *        round_down(P * R / NSEC_PER_SEC) ≥ round_up(R / freq)
> +	 *      ⟺ P * R / NSEC_PER_SEC ≥ round_up(R / freq)
> +	 *      ⟺ P ≥ round_up(R / freq) * NSEC_PER_SEC / R
> +	 */
> +
> +	cnv_wf.period_length_ns =
> +		div_u64_rem((u64)DIV_ROUND_UP(ltc->ref_clk_rate, freq) * NSEC_PER_SEC,
> +			    ltc->ref_clk_rate, &rem);
> +	if (rem)
> +		cnv_wf.period_length_ns += 1;
> +
> +	/* Gate the active period of the clock (see page 10-13 for both LTC's) */
> +	if (ltc->lane_mode == TWO_LANES)
> +		clk_en_time = DIV_ROUND_UP_ULL(ltc->device_info->resolution, 4);
> +	else
> +		clk_en_time = DIV_ROUND_UP_ULL(ltc->device_info->resolution, 2);
> +
> +	clk_gate_wf.duty_length_ns = ref_clk_period_ns * clk_en_time;
> +	clk_gate_wf.duty_offset_ns = LTC2387_T_FIRSTCLK_NS;
> +
> +	if (cnv_wf.period_length_ns < clk_gate_wf.duty_length_ns)
> +		cnv_wf.period_length_ns = clk_gate_wf.duty_length_ns + ref_clk_period_ns;
> +
> +	clk_gate_wf.period_length_ns = cnv_wf.period_length_ns;
> +
> +	if (clk_gate_wf.duty_offset_ns >= clk_gate_wf.period_length_ns)
> +		div64_u64_rem(clk_gate_wf.duty_offset_ns,
> +			      clk_gate_wf.period_length_ns,
> +			      &clk_gate_wf.duty_offset_ns);
> +
> +	ret = pwm_set_waveform_might_sleep(ltc->cnv, &cnv_wf, false);
> +	if (ret < 0)
> +		return ret;
> +
> +	ret = pwm_set_waveform_might_sleep(ltc->clk_gate, &clk_gate_wf, false);
> +	if (ret < 0)
> +		return ret;
> +
> +	period_cycles = mul_u64_u32_div(cnv_wf.period_length_ns,
> +					ltc->ref_clk_rate, NSEC_PER_SEC);
> +	if (!period_cycles)
> +		return -EINVAL;
> +
> +	ltc->sampling_freq = DIV_ROUND_CLOSEST_ULL(ltc->ref_clk_rate,
> +						   period_cycles);
> +
> +	return 0;
> +}
> +
> +static int ltc2387_setup(struct iio_dev *indio_dev)
> +{
> +	struct ltc2387_dev *ltc = iio_priv(indio_dev);
> +	struct device *dev = indio_dev->dev.parent;
> +
> +	if (device_property_present(dev, "adi,use-two-lanes"))
> +		ltc->lane_mode = TWO_LANES;
> +
> +	return ltc2387_set_sampling_freq(ltc, 15 * MHz);
> +}
> +
> +static int ltc2387_read_raw(struct iio_dev *indio_dev,
> +			    const struct iio_chan_spec *chan,
> +			    int *val, int *val2, long info)
> +{
> +	struct ltc2387_dev *ltc = iio_priv(indio_dev);
> +
> +	switch (info) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		*val = ltc->sampling_freq;
> +		return IIO_VAL_INT;
> +	case IIO_CHAN_INFO_SCALE:
> +		/* Referred to the SMA input, so divided by the front-end gain. */
> +		*val = ltc->vref_mv * 2 * 1000;

vref_mv * 2 * MILLI;

> +		*val2 = ltc->gain_milli << chan->scan_type.realbits;
> +		return IIO_VAL_FRACTIONAL;
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static int ltc2387_write_raw(struct iio_dev *indio_dev,
> +			     struct iio_chan_spec const *chan,
> +			     int val, int val2, long info)
> +{
> +	struct ltc2387_dev *ltc = iio_priv(indio_dev);
> +

Probably add a mutex here to protect writing, ideally in guard() form.

> +	switch (info) {
> +	case IIO_CHAN_INFO_SAMP_FREQ:
> +		return ltc2387_set_sampling_freq(ltc, val);
> +
> +	default:
> +		return -EINVAL;
> +	}
> +}
> +
> +static void ltc2387_pwm_disable(void *data)
> +{
> +	pwm_disable(data);
> +}
> +
> +static void ltc2387_regulator_disable(void *data)
> +{
> +	regulator_disable(data);
> +}
> +
> +static void ltc2387_clk_disable(void *data)
> +{
> +	clk_disable_unprepare(data);
> +}
> +
> +static const struct iio_info ltc2387_info = {
> +	.read_raw = ltc2387_read_raw,
> +	.write_raw = ltc2387_write_raw,
> +};
> +
> +static const struct of_device_id ltc2387_of_match[] = {
> +	{
> +		.compatible = "lltc,ltc2387-16",
> +		.data = &ltc2387_infos[ID_LTC2387_16]
> +	}, {
> +		.compatible = "lltc,ltc2387-16-x4",
> +		.data = &ltc2387_infos[ID_LTC2387_16_X4]
> +	}, {
> +		.compatible = "lltc,ltc2387-18",
> +		.data = &ltc2387_infos[ID_LTC2387_18]
> +	}, {
> +		.compatible = "lltc,ltc2387-18-x4",
> +		.data = &ltc2387_infos[ID_LTC2387_18_X4]
> +	}, {
> +		.compatible = "adi,adaq23875",
> +		.data = &ltc2387_infos[ID_ADAQ23875]
> +	}, {
> +		.compatible = "adi,adaq23876",
> +		.data = &ltc2387_infos[ID_ADAQ23876]
> +	}, {
> +		.compatible = "adi,adaq23878",
> +		.data = &ltc2387_infos[ID_ADAQ23878]
> +	},
> +	{}
> +};
> +MODULE_DEVICE_TABLE(of, ltc2387_of_match);

Have the device tables after the probe() function.

> +
> +static int ltc2387_probe(struct platform_device *pdev)
> +{
> +	struct iio_dev			*indio_dev;
> +	struct ltc2387_dev		*ltc;
> +	int				ret;

Maybe unify the style you define variables? (aligned vs. non-aligned).

> +
> +	indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*ltc));
> +	if (!indio_dev)
> +		return -ENOMEM;
> +
> +	ltc = iio_priv(indio_dev);
> +
> +	ltc->vref = devm_regulator_get_optional(&pdev->dev, "vref");
> +	if (!IS_ERR(ltc->vref)) {
> +		ret = regulator_enable(ltc->vref);
> +		if (ret) {
> +			dev_err(&pdev->dev, "Can't enable vref regulator\n");

dev_err_probe() instead of dev_err()

> +			return ret;
> +		}
> +		ret = regulator_get_voltage(ltc->vref);
> +		if (ret < 0)
> +			return ret;
> +
> +		ltc->vref_mv = ret / 1000;

ret / (MICRO / MILLI)

> +		ret = devm_add_action_or_reset(&pdev->dev,
> +					       ltc2387_regulator_disable,
> +					       ltc->vref);
> +		if (ret)
> +			return ret;
> +	} else {
> +		if (PTR_ERR(ltc->vref) != -ENODEV)
> +			return PTR_ERR(ltc->vref);
> +
> +		ltc->vref_mv = LTC2387_VREF; /* Internal vref is used */
> +	}
> +
> +	ltc->ref_clk = devm_clk_get(&pdev->dev, NULL);
> +	if (IS_ERR(ltc->ref_clk))
> +		return PTR_ERR(ltc->ref_clk);
> +
> +	ret = clk_prepare_enable(ltc->ref_clk);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_add_action_or_reset(&pdev->dev, ltc2387_clk_disable,
> +				       ltc->ref_clk);
> +	if (ret)
> +		return ret;

+ blank line.

> +	ltc->ref_clk_rate = clk_get_rate(ltc->ref_clk);
> +
> +	ltc->clk_gate = devm_pwm_get(&pdev->dev, "clk_gate");
> +	if (IS_ERR(ltc->clk_gate))
> +		return PTR_ERR(ltc->clk_gate);
> +
> +	ret = devm_add_action_or_reset(&pdev->dev, ltc2387_pwm_disable,
> +				       ltc->clk_gate);
> +	if (ret)
> +		return ret;
> +
> +	ltc->cnv = devm_pwm_get(&pdev->dev, "cnv");
> +	if (IS_ERR(ltc->cnv))
> +		return PTR_ERR(ltc->cnv);
> +
> +	ret = devm_add_action_or_reset(&pdev->dev, ltc2387_pwm_disable,
> +				       ltc->cnv);
> +
> +	if (ret)
> +		return ret;
> +
> +	ltc->device_info = device_get_match_data(&pdev->dev);
> +	if (!ltc->device_info)
> +		return -EINVAL;

return -ENODATA;

> +
> +	ltc->gain_milli = ltc->device_info->default_gain_milli;
> +	device_property_read_u32(&pdev->dev, "adi,gain-milli", &ltc->gain_milli);
> +	if (!ltc->gain_milli)
> +		return dev_err_probe(&pdev->dev, -EINVAL,
> +				     "adi,gain-milli must be nonzero\n");
> +
> +	indio_dev->channels = ltc->device_info->channels;
> +	indio_dev->num_channels = ltc->device_info->num_channels;
> +	indio_dev->dev.parent = &pdev->dev;
> +	indio_dev->name = ltc->device_info->name;
> +	indio_dev->info = &ltc2387_info;
> +
> +	ltc->back = devm_iio_backend_get(&pdev->dev, NULL);
> +	if (IS_ERR(ltc->back))
> +		return dev_err_probe(&pdev->dev, PTR_ERR(ltc->back),
> +				     "failed to get IIO backend\n");
> +
> +	ret = devm_iio_backend_request_buffer(&pdev->dev, ltc->back, indio_dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = devm_iio_backend_enable(&pdev->dev, ltc->back);
> +	if (ret)
> +		return ret;
> +
> +	/* axi_ltc2387 has a single capture channel carrying every scan element. */
> +	ret = iio_backend_chan_enable(ltc->back, 0);
> +	if (ret)
> +		return ret;
> +
> +	ret = ltc2387_setup(indio_dev);
> +	if (ret < 0) {
> +		dev_err(&pdev->dev, "\nltc2387 setup failed\n");

Why the additional \n at the beginning.
> +		return ret;
> +	}
> +
> +	return devm_iio_device_register(&pdev->dev, indio_dev);
> +}
> +
> +static struct platform_driver ltc2387_driver = {
> +	.probe          = ltc2387_probe,
> +	.driver         = {
> +		.name   = "ltc2387",
> +		.of_match_table = ltc2387_of_match,
> +	},
> +};
> +module_platform_driver(ltc2387_driver);
> +
> +MODULE_AUTHOR("Sergiu Cuciurean <sergiu.cuciurean@analog.com>");
> +MODULE_DESCRIPTION("Linear Technology LTC2387 ADC");

The description should be the same as what's in the Kconfig tristate entry.

> +MODULE_LICENSE("Dual BSD/GPL");
> +MODULE_IMPORT_NS("IIO_BACKEND");



-- 
Kind regards,
Joshua Crofts

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

* Re: [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support
  2026-10-27 13:44 [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
  2026-10-27 13:44 ` [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387 Nicolae-Daniel Deaconescu
  2026-10-27 13:44 ` [PATCH 2/2] iio: adc: add ltc2387 driver Nicolae-Daniel Deaconescu
@ 2026-10-27 15:56 ` Andy Shevchenko
  2 siblings, 0 replies; 6+ messages in thread
From: Andy Shevchenko @ 2026-10-27 15:56 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu
  Cc: Sergiu Cuciurean, Jonathan Cameron, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, linux-iio, devicetree, linux-kernel,
	linux-pwm

On Tue, Oct 27, 2026 at 03:44:07PM +0200, Nicolae-Daniel Deaconescu wrote:
> This series adds a driver for the LTC2387-16/18, 15 Msps SAR ADCs with
> an LVDS data interface, and for the ADAQ23875, ADAQ23876 and ADAQ23878
> uModule ADCs, which pair an LTC2387 die with a fully differential
> amplifier front end.

Why have you written a brand new driver?

> Conversion start (CNV) and the gated LVDS data clock are generated by
> two channels of an AXI PWM generator. Sample data is captured by the
> AXI ADC IP core and reached through the IIO backend framework. The
> front-end gain of the ADAQ parts is described with adi,gain-milli
> (fixed at 2 on the ADAQ23875, pin-strapped on the ADAQ23876/78) and
> applied to in_voltage_scale.
> 
> Tested on an EV-ADAQ23875FMCZ on a ZedBoard running the adaq2387x HDL
> project in two-lane, 16-bit mode: probe, in_voltage_scale,
> sampling_frequency (writes, rounding and range checks), buffered
> capture up to 1M samples, and unbind/rebind. The bare LTC2387
> variants, one-lane mode and the -x4 variants were not tested on
> hardware.

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387
  2026-10-27 13:44 ` [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387 Nicolae-Daniel Deaconescu
@ 2026-10-27 18:28   ` Conor Dooley
  0 siblings, 0 replies; 6+ messages in thread
From: Conor Dooley @ 2026-10-27 18:28 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu
  Cc: Sergiu Cuciurean, Jonathan Cameron, David Lechner, Nuno Sá,
	Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Uwe Kleine-König, linux-iio, devicetree, linux-kernel,
	linux-pwm

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

On Tue, Oct 27, 2026 at 03:44:08PM +0200, Nicolae-Daniel Deaconescu wrote:
> From: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> 
> Add bindings for the LTC2387-16 and LTC2387-18, 15 Msps SAR ADCs with
> an LVDS data interface, and for the ADAQ23875, ADAQ23876 and ADAQ23878
> uModule ADCs, which pair an LTC2387 die with a fully differential
> amplifier front end.
> 
> Conversion start (CNV) and the gated LVDS clock are driven by two PWM
> channels. Sample data is captured by an AXI ADC IP core, referenced
> through io-backends. adi,use-two-lanes selects two-lane output, and
> adi,gain-milli gives the front-end gain: fixed at 2 on the ADAQ23875,
> set by pin strapping on the ADAQ23876 and ADAQ23878.
> 
> Signed-off-by: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> Co-developed-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
> Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
> Assisted-by: Claude:claude-opus-5-5
> ---
>  .../bindings/iio/adc/lltc,ltc2387.yaml        | 138 ++++++++++++++++++
>  MAINTAINERS                                   |   8 +
>  2 files changed, 146 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
> new file mode 100644
> index 000000000000..f912260c8f79
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
> @@ -0,0 +1,138 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +# Copyright 2022 Analog Devices Inc.
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/lltc,ltc2387.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Linear Technology / Analog Devices LTC2387 ADC
> +
> +maintainers:
> +  - Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> +  - Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
> +
> +description: |
> +  The LTC2387-18 is a low noise, high speed, 18-bit 15Msps
> +  successive approximation register (SAR) ADC ideally
> +  suited for a wide range of applications.
> +
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/238718fa.pdf
> +  https://www.analog.com/media/en/technical-documentation/data-sheets/238716f.pdf
> +
> +properties:
> +  compatible:
> +    enum:
> +      - adi,adaq23875
> +      - adi,adaq23876
> +      - adi,adaq23878
> +      - lltc,ltc2387-16
> +      - lltc,ltc2387-16-x4
> +      - lltc,ltc2387-18
> +      - lltc,ltc2387-18-x4

Why are we getting new devices with linear vendor prefixes? My
understanding was that new devices from ADI subsidiaries were to use the
adi vendor prefix.


> +
> +  clocks:
> +    description: Sampling clock
> +    maxItems: 1
> +
> +  pwms:
> +    maxItems: 2
> +    description:
> +      PWM signals used to synchronize the sampling procedure
> +
> +  pwm-names:
> +    items:
> +      - const: cnv
> +      - const: clk_gate

Hmm, ltc2387-16 does not appear to have these signals?
Well, it has "cnv" but "cnv" has "sample clock" as the input name so is
the clocks property above and pwm-clock can be used if the provider is a
pwm?

Either there's some mutual exclusion missing here or the modelling in
incorrect.

"clk_gate" doesn't appear in either of the datasheets I looked at, what
does that actually do?

pw-bot: changes-requested

> +
> +  io-backends:
> +    description:
> +      The AXI ADC IP block (axi_ltc2387) that captures the LVDS data stream.
> +    maxItems: 1
> +
> +  vref-supply:
> +    description:
> +      External voltage reference. When absent the device's internal 4.096 V
> +      reference is assumed.
> +
> +  adi,use-two-lanes:
> +    description: The device uses two lanes for data transfer
> +    type: boolean

Please explain how this is not determinable from the compatible.

Cheers,
Conor.

> +
> +  adi,gain-milli:
> +    description:
> +      Gain of the fully-differential amplifier that the ADAQ2387x modules
> +      integrate ahead of the LTC2387 die, expressed as gain * 1000. It is
> +      fixed on the ADAQ23875 and set by pin strapping on the ADAQ23876 and
> +      ADAQ23878, so it must match the board. The bare ltc2387-* parts have
> +      no amplifier.
> +    $ref: /schemas/types.yaml#/definitions/uint32
> +    enum: [370, 730, 870, 1380, 2000, 2250]
> +
> +required:
> +  - compatible
> +  - clocks
> +  - io-backends
> +  - pwms
> +  - pwm-names
> +
> +allOf:
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - lltc,ltc2387-16
> +              - lltc,ltc2387-16-x4
> +              - lltc,ltc2387-18
> +              - lltc,ltc2387-18-x4
> +    then:
> +      properties:
> +        adi,gain-milli: false
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: adi,adaq23875
> +    then:
> +      properties:
> +        adi,gain-milli:
> +          const: 2000
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,adaq23876
> +              - adi,adaq23878
> +    then:
> +      properties:
> +        adi,gain-milli:
> +          enum: [370, 730, 870, 1380, 2250]
> +      required:
> +        - adi,gain-milli
> +
> +additionalProperties: false
> +
> +examples:
> +  - |
> +    adc {
> +            compatible = "lltc,ltc2387-16";
> +            clocks = <&clk 0>;
> +            io-backends = <&axi_adc>;
> +            pwms = <&pwm 0 0>, <&pwm 1 0>;
> +            pwm-names = "cnv", "clk_gate";
> +            vref-supply = <&vref>;
> +    };
> +  - |
> +    adc {
> +            compatible = "adi,adaq23878";
> +            clocks = <&clk 0>;
> +            io-backends = <&axi_adc>;
> +            pwms = <&pwm 0 0>, <&pwm 1 0>;
> +            pwm-names = "cnv", "clk_gate";
> +            vref-supply = <&vref>;
> +            adi,use-two-lanes;
> +            adi,gain-milli = <870>;
> +    };
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 52024397dc78..b871d7f25d76 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -15529,6 +15529,14 @@ W:	https://ez.analog.com/linux-software-drivers
>  F:	Documentation/devicetree/bindings/iio/adc/adi,ltc2378.yaml
>  F:	drivers/iio/adc/ltc2378.c
>  
> +LTC2387 IIO ADC DRIVER
> +M:	Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
> +M:	Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> +L:	linux-iio@vger.kernel.org
> +S:	Supported
> +W:	https://ez.analog.com/linux-software-drivers
> +F:	Documentation/devicetree/bindings/iio/adc/lltc,ltc2387.yaml
> +
>  LTC2664 IIO DAC DRIVER
>  M:	Michael Hennerich <michael.hennerich@analog.com>
>  M:	Kim Seer Paller <kimseer.paller@analog.com>
> -- 
> 2.34.1
> 

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

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

end of thread, other threads:[~2026-10-27 18:28 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-27 13:44 [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-10-27 13:44 ` [PATCH 1/2] dt-bindings: iio: adc: add lltc,ltc2387 Nicolae-Daniel Deaconescu
2026-10-27 18:28   ` Conor Dooley
2026-10-27 13:44 ` [PATCH 2/2] iio: adc: add ltc2387 driver Nicolae-Daniel Deaconescu
2026-10-27 14:37   ` Joshua Crofts
2026-10-27 15:56 ` [PATCH 0/2] iio: adc: add LTC2387 and ADAQ2387x support Andy Shevchenko

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

all inboxes | Powered by JetHome®