All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support
@ 2026-11-04 13:21 Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-04 13:21 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Uwe Kleine-König, Conor Dooley, Krzysztof Kozlowski,
	Rob Herring, David Lechner, Nuno Sá, Andy Shevchenko,
	Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel, Nicolae-Daniel Deaconescu

Hi all,

This adds the LTC2387-16, LTC2387-18, ADAQ23875, ADAQ23876 and ADAQ23878
to the ad7625 driver and binding. v1 added them as a separate ltc2387
driver. After receiving feedback, this version refactors the driver
into the existing ad7625 and modifies the pwm backend for clear
layering.

The parts are single channel differential 16 and 18 bit SAR ADCs, up to
15 MSPS. Like the AD762x and AD796x they are read with two PWMs (CNV and
CLK) and an IIO backend that captures the LVDS data. The differences are
handled as chip data and a few quirks:

- Output is double data rate on one or two lanes (TWOLANES pin), so the
  CLK burst is resolution / 4 or resolution / 2 pulses long.
- The burst has to start at least t_FIRSTCLK after CNV and end at most
  t_LASTCLK after the next CNV, so at high rates it runs into the next
  conversion period. The sampling period is raised until it fits. With two
  lanes 15 MSPS is reachable, with one lane the limit is about 10.9 MSPS
  (16 bit) and 10 MSPS (18 bit) at a 120 MHz PWM clock.
- No EN pins. The second PWM is named "clk" for these parts, as Conor
  asked, and keeps the name "clk_gate" for the existing parts, as renaming
  it would change their binding.
- The ADAQ modules have a fully differential amplifier in front of the
  ADC, so the scale is divided by its gain. The gain is fixed on the
  ADAQ23875 and pin strapped on the ADAQ23876 and ADAQ23878, so it is
  given in the devicetree (adi,gain-milli).

Patches 1 and 2 add what the driver needs from the PWM. The timing of the
burst has to be placed in whole steps of the PWM, and a rounding function
that rounds down can't tell the consumer how big a step is. Conor pointed
out that reading the rate from a clock breaks if the PWM provider has
a mandatory divider. So the provider now reports the clock rate and the
number of clock cycles per step.

Changes since v1 (separate ltc2387 driver):
- Joined to ad7625 (binding, driver, Kconfig and documentation).
- Dropped the clocks property of the ADC, added the PWM waveform step API.
- Compatibles use the adi, prefix and the datasheet part numbers.
- Dropped the x4 parts.
- Fixed The missing return of dev_err_probe() for the initial sampling
 frequency.
- Added the maintainers of the new parts to the AD7625 entry.

AI disclosure: parts of the commit text and the code were checked with an
AI coding assistant (Claude): the first version of the merge into ad7625,
the PWM step API, the timing arithmetic and the test scripts. I reviewed
every change, built it and ran tests on the ADAQ23875 + ZedBoard.

Link to v1: https://lore.kernel.org/all/20261027134409.76824-1-Nicolae-daniel.Deaconescu@analog.com/

Nicolae-Daniel Deaconescu (3):
  pwm: core: add pwm_get_waveform_step_might_sleep()
  pwm: axi-pwmgen: report the waveform step
  iio: adc: ad7625: fix sampling frequency handling

Sergiu Cuciurean (2):
  dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x
  iio: adc: ad7625: add LTC2387 and ADAQ2387x support

 .../bindings/iio/adc/adi,ad7625.yaml          | 193 ++++++++-
 Documentation/iio/ad7625.rst                  |  34 +-
 MAINTAINERS                                   |   2 +
 drivers/iio/adc/Kconfig                       |   7 +-
 drivers/iio/adc/ad7625.c                      | 402 ++++++++++++++++--
 drivers/pwm/core.c                            |  63 +++
 drivers/pwm/pwm-axi-pwmgen.c                  |  13 +
 include/linux/pwm.h                           |  32 ++
 8 files changed, 703 insertions(+), 43 deletions(-)


base-commit: 43ffda3c7f8456bbe641ae8249a077a361b97880
-- 
2.34.1


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

* [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep()
  2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
@ 2026-11-04 13:21 ` Nicolae-Daniel Deaconescu
  2026-11-04 19:51   ` Andy Shevchenko
  2026-11-04 13:21 ` [PATCH v2 2/5] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-04 13:21 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Uwe Kleine-König, Conor Dooley, Krzysztof Kozlowski,
	Rob Herring, David Lechner, Nuno Sá, Andy Shevchenko,
	Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel, Nicolae-Daniel Deaconescu

A consumer that has to meet minimum times, or place an edge relative to
the cycles of a clock that a PWM gates, cannot do that with the waveform
API alone. The rounding functions round down, so a minimum has to be
requested with some margin, and the size of that margin is the step of
the PWM, which only the provider knows.

A provider can report the rate of the clock its timing is derived from
and how many cycles of that clock one step lasts. A consumer can then
work in whole steps, convert them to nanoseconds rounding up, and get
that waveform back from the hardware. Without this, a consumer has to
read the rate from a clock it gets on its own, which describes
a relationship the hardware doesn't have, and is wrong if
PWM divides the clock.

Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
 drivers/pwm/core.c  | 63 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/pwm.h | 32 +++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 2a050ff4608b..6184f17497f0 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -325,6 +325,66 @@ int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf
 }
 EXPORT_SYMBOL_GPL(pwm_get_waveform_might_sleep);
 
+/**
+ * pwm_get_waveform_step_might_sleep - Query the granularity of the waveforms
+ * Cannot be used in atomic context.
+ * @pwm: PWM device
+ * @step: output parameter
+ *
+ * Stores in @step the clock rate the timing of @pwm is derived from and how
+ * many cycles of that clock one step lasts. Period, duty length and duty
+ * offset are multiples of that step, so a waveform asked for in whole steps
+ * is not changed by rounding. Compute the length in nanoseconds of n steps
+ * as n * @step->clk_cycles_per_step * NSEC_PER_SEC / @step->clk_rate_hz and
+ * round it up, because pwm_round_waveform_might_sleep() and
+ * pwm_set_waveform_might_sleep() round down.
+ *
+ * The values can change when a clock changes its rate, so they are only valid
+ * as long as the provider's input clocks are not reconfigured.
+ *
+ * Returns: 0 on success, -EOPNOTSUPP if the provider doesn't report it or a
+ * negative errno
+ * Context: May sleep.
+ */
+int pwm_get_waveform_step_might_sleep(struct pwm_device *pwm,
+				      struct pwm_waveform_step *step)
+{
+	struct pwm_chip *chip = pwm->chip;
+	const struct pwm_ops *ops = chip->ops;
+	int ret;
+
+	if (!pwmchip_supports_waveform(chip) || !ops->get_waveform_step)
+		return -EOPNOTSUPP;
+
+	might_sleep();
+
+	guard(pwmchip)(chip);
+
+	if (!chip->operational)
+		return -ENODEV;
+
+	if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
+		/*
+		 * Catch any drivers that have been marked as atomic but
+		 * that will sleep anyway.
+		 */
+		non_block_start();
+		ret = ops->get_waveform_step(chip, pwm, step);
+		non_block_end();
+	} else {
+		ret = ops->get_waveform_step(chip, pwm, step);
+	}
+	if (ret)
+		return ret;
+
+	if (!step->clk_rate_hz || !step->clk_cycles_per_step ||
+	    step->clk_rate_hz > (u64)step->clk_cycles_per_step * NSEC_PER_SEC)
+		return -EINVAL;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(pwm_get_waveform_step_might_sleep);
+
 /* Called with the pwmchip lock held */
 static int __pwm_set_waveform(struct pwm_device *pwm,
 			      const struct pwm_waveform *wf,
@@ -1710,6 +1770,9 @@ static bool pwm_ops_check(const struct pwm_chip *chip)
 		if (!ops->apply)
 			return false;
 
+		if (ops->get_waveform_step)
+			return false;
+
 		if (IS_ENABLED(CONFIG_PWM_DEBUG) && !ops->get_state)
 			dev_warn(pwmchip_parent(chip),
 				 "Please implement the .get_state() callback\n");
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
index b11ae91723f8..f6e943607542 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -76,6 +76,32 @@ struct pwm_waveform {
 	u64 duty_offset_ns;
 };
 
+/**
+ * struct pwm_waveform_step - granularity of the waveforms a PWM can generate
+ * @clk_rate_hz: rate of the clock the PWM's timing is derived from
+ * @clk_cycles_per_step: number of those clock cycles one step lasts
+ *
+ * The period, the duty length and the duty offset of a waveform are multiples
+ * of one step, which lasts @clk_cycles_per_step / @clk_rate_hz seconds. A PWM
+ * that divides its input clock in hardware has @clk_cycles_per_step > 1.
+ *
+ * The step is the same for every waveform and for the period, the duty length
+ * and the duty offset, so a provider whose granularity depends on the
+ * requested period (for example through a prescaler) can't describe itself
+ * with it. One step also has to last at least 1 ns, otherwise rounding a
+ * length in nanoseconds up can add a whole step.
+ *
+ * A consumer that needs a minimum time, or that has to place an edge relative
+ * to the cycles of the clock a PWM channel gates, can use this to ask for
+ * the next possible value instead of one that is rounded down, and to compute
+ * the frequency a rounded period results in without the error of the
+ * nanosecond resolution of struct pwm_waveform.
+ */
+struct pwm_waveform_step {
+	unsigned long clk_rate_hz;
+	unsigned int clk_cycles_per_step;
+};
+
 /*
  * struct pwm_state - state of a PWM channel
  * @period: PWM period (in nanoseconds)
@@ -287,6 +313,9 @@ struct pwm_capture {
  * @round_waveform_fromhw: convert a driver specific waveform presentation to struct pwm_waveform
  * @read_waveform: read driver specific waveform presentation from hardware
  * @write_waveform: write driver specific waveform presentation to hardware
+ * @get_waveform_step: optional, report the granularity of the waveforms. Called
+ *	with the chip locked, so it must not sleep on an atomic chip and should
+ *	report a cached clock rate instead of calling clk_get_rate()
  * @apply: atomically apply a new PWM config
  * @get_state: get the current PWM state.
  */
@@ -305,6 +334,8 @@ struct pwm_ops {
 			    void *wfhw);
 	int (*write_waveform)(struct pwm_chip *chip, struct pwm_device *pwm,
 			      const void *wfhw);
+	int (*get_waveform_step)(struct pwm_chip *chip, struct pwm_device *pwm,
+				 struct pwm_waveform_step *step);
 
 	int (*apply)(struct pwm_chip *chip, struct pwm_device *pwm,
 		     const struct pwm_state *state);
@@ -394,6 +425,7 @@ static inline void pwmchip_set_drvdata(struct pwm_chip *chip, void *data)
 /* PWM consumer APIs */
 int pwm_round_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf);
 int pwm_get_waveform_might_sleep(struct pwm_device *pwm, struct pwm_waveform *wf);
+int pwm_get_waveform_step_might_sleep(struct pwm_device *pwm, struct pwm_waveform_step *step);
 int pwm_set_waveform_might_sleep(struct pwm_device *pwm, const struct pwm_waveform *wf, bool exact);
 int pwm_apply_might_sleep(struct pwm_device *pwm, const struct pwm_state *state);
 int pwm_apply_atomic(struct pwm_device *pwm, const struct pwm_state *state);
-- 
2.34.1


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

* [PATCH v2 2/5] pwm: axi-pwmgen: report the waveform step
  2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
@ 2026-11-04 13:21 ` Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 3/5] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-04 13:21 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Uwe Kleine-König, Conor Dooley, Krzysztof Kozlowski,
	Rob Herring, David Lechner, Nuno Sá, Andy Shevchenko,
	Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel, Nicolae-Daniel Deaconescu

The period, duty and offset counters of the AXI PWM generator count
cycles of its external clock, without a divider. Report that clock rate
and one cycle per step.

Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
 drivers/pwm/pwm-axi-pwmgen.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/pwm/pwm-axi-pwmgen.c b/drivers/pwm/pwm-axi-pwmgen.c
index b40522f01002..5b6a6943d08a 100644
--- a/drivers/pwm/pwm-axi-pwmgen.c
+++ b/drivers/pwm/pwm-axi-pwmgen.c
@@ -136,6 +136,18 @@ static int axi_pwmgen_round_waveform_fromhw(struct pwm_chip *chip, struct pwm_de
 	return 0;
 }
 
+static int axi_pwmgen_get_waveform_step(struct pwm_chip *chip,
+					struct pwm_device *pwm,
+					struct pwm_waveform_step *step)
+{
+	struct axi_pwmgen_ddata *ddata = axi_pwmgen_ddata_from_chip(chip);
+
+	step->clk_rate_hz = ddata->clk_rate_hz;
+	step->clk_cycles_per_step = 1;
+
+	return 0;
+}
+
 static int axi_pwmgen_write_waveform(struct pwm_chip *chip,
 				     struct pwm_device *pwm,
 				     const void *_wfhw)
@@ -201,6 +213,7 @@ static const struct pwm_ops axi_pwmgen_pwm_ops = {
 	.round_waveform_fromhw = axi_pwmgen_round_waveform_fromhw,
 	.read_waveform = axi_pwmgen_read_waveform,
 	.write_waveform = axi_pwmgen_write_waveform,
+	.get_waveform_step = axi_pwmgen_get_waveform_step,
 };
 
 static int axi_pwmgen_setup(struct regmap *regmap, struct device *dev)
-- 
2.34.1


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

* [PATCH v2 3/5] iio: adc: ad7625: fix sampling frequency handling
  2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 2/5] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
@ 2026-11-04 13:21 ` Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x Nicolae-Daniel Deaconescu
  2026-11-04 13:21 ` [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
  4 siblings, 0 replies; 10+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-04 13:21 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Uwe Kleine-König, Conor Dooley, Krzysztof Kozlowski,
	Rob Herring, David Lechner, Nuno Sá, Andy Shevchenko,
	Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel, Nicolae-Daniel Deaconescu

Fix the following issues in the handling of the sampling frequency:

- Fix the issue where writing 0 to sampling_frequency caused a division
  by zero. Return -EINVAL instead.
- Fix the issue where a waveform that the PWM had to round up was not
  reported as an error. pwm_round_waveform_might_sleep() returns 1 in that
  case and ad7625_set_sampling_freq() passed it on, so the write to
  sampling_frequency returned 1 and the new waveforms were dropped. Return
  -EINVAL instead.
- Fix the issue where probe() went on after failing to set the initial
  sampling frequency. The result of dev_err_probe() was not returned, so
  the PWMs stayed unconfigured. Return the error.

Fixes: b7ffd0fa65e9 ("iio: adc: ad7625: add driver")
Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
 drivers/iio/adc/ad7625.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/drivers/iio/adc/ad7625.c b/drivers/iio/adc/ad7625.c
index e73a5c9e7f0b..0de7f29797ef 100644
--- a/drivers/iio/adc/ad7625.c
+++ b/drivers/iio/adc/ad7625.c
@@ -178,6 +178,9 @@ static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq)
 	unsigned long target;
 	int ret;
 
+	if (!freq)
+		return -EINVAL;
+
 	target = DIV_ROUND_UP(NSEC_PER_SEC, freq);
 	cnv_wf.period_length_ns = clamp(target, 100UL, 10UL * NSEC_PER_USEC);
 
@@ -189,7 +192,7 @@ static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq)
 
 	ret = pwm_round_waveform_might_sleep(st->cnv_pwm, &cnv_wf);
 	if (ret)
-		return ret;
+		return ret < 0 ? ret : -EINVAL;
 
 	/*
 	 * Set up the burst signal for transferring data. period and
@@ -206,7 +209,7 @@ static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq)
 
 	ret = pwm_round_waveform_might_sleep(st->clk_gate_pwm, &clk_gate_wf);
 	if (ret)
-		return ret;
+		return ret < 0 ? ret : -EINVAL;
 
 	st->cnv_wf = cnv_wf;
 	st->clk_gate_wf = clk_gate_wf;
@@ -646,8 +649,8 @@ static int ad7625_probe(struct platform_device *pdev)
 
 	ret = ad7625_set_sampling_freq(st, default_sample_freq);
 	if (ret)
-		dev_err_probe(dev, ret,
-			      "failed to set valid sampling frequency\n");
+		return dev_err_probe(dev, ret,
+				     "failed to set valid sampling frequency\n");
 
 	return devm_iio_device_register(dev, indio_dev);
 }
-- 
2.34.1


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

* [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x
  2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
                   ` (2 preceding siblings ...)
  2026-11-04 13:21 ` [PATCH v2 3/5] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
@ 2026-11-04 13:21 ` Nicolae-Daniel Deaconescu
  2026-11-04 13:28   ` sashiko-bot
  2026-11-05  0:23   ` Conor Dooley
  2026-11-04 13:21 ` [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
  4 siblings, 2 replies; 10+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-04 13:21 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Uwe Kleine-König, Conor Dooley, Krzysztof Kozlowski,
	Rob Herring, David Lechner, Nuno Sá, Andy Shevchenko,
	Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel, Nicolae-Daniel Deaconescu

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

Add the LTC2387-16, LTC2387-18, ADAQ23875, ADAQ23876 and ADAQ23878 to the
binding. They are single channel differential ADCs like the AD762x and
AD796x and are read the same way: CNV and CLK are driven by two PWMs and
the LVDS data is captured by an AXI ADC. They differ in what is around the
converter:

- Their output is double data rate on one or two lanes, selected by the
  TWOLANES pin, described by adi,use-two-lanes.
- They have no EN pins, and no clocks property, because the PWM provider
  reports the clock its waveforms are derived from. The second PWM is named
  clk, as that is the pin it drives, where the AD762x and AD796x name it
  clk_gate.
- The supplies are VDD, VDDL and OVDD on the LTC2387, and VDD, VIO, VS+ and
  VS- on the ADAQ modules.
- The ADAQ modules have a fully differential amplifier in front of the ADC.
  Its gain is fixed on the ADAQ23875 and set by pin strapping on the
  ADAQ23876 and ADAQ23878, described by adi,gain-milli.

The properties and supplies of the two groups of parts are made exclusive.
The gain is described in the devicetree as the pin strapping can't be read
from the part. There is no fallback compatible, as a driver for one of the
existing parts would read these wrongly: their output is double data rate
and has other timing limits.

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>
---
 .../bindings/iio/adc/adi,ad7625.yaml          | 193 +++++++++++++++++-
 1 file changed, 184 insertions(+), 9 deletions(-)

diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
index 8848562af28f..dbdded88236c 100644
--- a/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
@@ -4,7 +4,7 @@
 $id: http://devicetree.org/schemas/iio/adc/adi,ad7625.yaml#
 $schema: http://devicetree.org/meta-schemas/core.yaml#
 
-title: Analog Devices Fast PulSAR Analog to Digital Converters
+title: Analog Devices Fast PulSAR, LTC2387 and ADAQ2387x Analog to Digital Converters
 
 maintainers:
   - Michael Hennerich <Michael.Hennerich@analog.com>
@@ -17,6 +17,11 @@ description: |
   * https://www.analog.com/en/products/ad7626.html
   * https://www.analog.com/en/products/ad7960.html
   * https://www.analog.com/en/products/ad7961.html
+  * https://www.analog.com/en/products/ltc2387-16.html
+  * https://www.analog.com/en/products/ltc2387-18.html
+  * https://www.analog.com/en/products/adaq23875.html
+  * https://www.analog.com/en/products/adaq23876.html
+  * https://www.analog.com/en/products/adaq23878.html
 
 properties:
   compatible:
@@ -25,14 +30,37 @@ properties:
       - adi,ad7626
       - adi,ad7960
       - adi,ad7961
+      - adi,adaq23875
+      - adi,adaq23876
+      - adi,adaq23878
+      - adi,ltc2387-16
+      - adi,ltc2387-18
 
   vdd1-supply: true
   vdd2-supply: true
   vio-supply: true
 
+  vdd-supply:
+    description: 5V analog power supply (VDD), LTC2387 and ADAQ parts only.
+
+  vddl-supply:
+    description: 2.5V analog power supply (VDDL), LTC2387 parts only.
+
+  ovdd-supply:
+    description: 2.5V output power supply (OVDD), LTC2387 parts only.
+
+  vs-p-supply:
+    description: Amplifier positive supply (VS+), ADAQ parts only.
+
+  vs-n-supply:
+    description:
+      Amplifier negative supply (VS-), ADAQ parts only. Leave it out if VS-
+      is tied to ground.
+
   ref-supply:
     description:
-      Voltage regulator for the external reference voltage (REF).
+      Voltage regulator for the external reference voltage (REF, or REFBUF
+      on the LTC2387 and ADAQ2387x).
 
   refin-supply:
     description:
@@ -40,18 +68,23 @@ properties:
 
   clocks:
     description:
-      The clock connected to the CLK pins, gated by the clk_gate PWM.
+      The clock connected to the CLK pins, gated by the clk_gate PWM. Not
+      described for the LTC2387 and ADAQ parts.
     maxItems: 1
 
   pwms:
     items:
       - description: PWM connected to the CNV input on the ADC.
-      - description: PWM that gates the clock connected to the ADC's CLK input.
+      - description:
+          PWM that gates the clock connected to the ADC's CLK input, so that
+          it sees the burst of pulses for each conversion. It is named
+          clk_gate for the AD762x and AD796x, and clk for the LTC2387 and
+          ADAQ2387x.
 
   pwm-names:
     items:
       - const: cnv
-      - const: clk_gate
+      - enum: [clk_gate, clk]
 
   io-backends:
     description:
@@ -91,6 +124,23 @@ properties:
       Indicates if EN3 is hard-wired to the high state. If neither this
       nor en3-gpios are present, then EN3 is hard-wired low.
 
+  adi,use-two-lanes:
+    $ref: /schemas/types.yaml#/definitions/flag
+    description:
+      Describes whether the TWOLANES pin of an LTC2387 or ADAQ part is set
+      high, selecting two-lane LVDS output, or low, selecting one-lane
+      output (the default).
+
+  adi,gain-milli:
+    description:
+      Gain of the fully-differential amplifier that the ADAQ 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. Not allowed on the ADAQ23875,
+      whose gain is fixed at 2.
+    $ref: /schemas/types.yaml#/definitions/uint16
+    enum: [370, 730, 870, 1380, 2250]
+
   en0-gpios:
     description:
       Configurable EN0 pin.
@@ -109,10 +159,6 @@ properties:
 
 required:
   - compatible
-  - vdd1-supply
-  - vdd2-supply
-  - vio-supply
-  - clocks
   - pwms
   - pwm-names
   - io-backends
@@ -157,6 +203,111 @@ allOf:
         - required: [ref-supply]
         - required: [refin-supply]
 
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - adi,ad7625
+              - adi,ad7626
+              - adi,ad7960
+              - adi,ad7961
+    then:
+      properties:
+        pwm-names:
+          items:
+            - const: cnv
+            - const: clk_gate
+        vdd-supply: false
+        vddl-supply: false
+        ovdd-supply: false
+        vs-p-supply: false
+        vs-n-supply: false
+        adi,use-two-lanes: false
+        adi,gain-milli: false
+      required:
+        - vdd1-supply
+        - vdd2-supply
+        - vio-supply
+        - clocks
+    else:
+      # The LTC2387 and ADAQ parts have no EN pins and no clock to describe.
+      properties:
+        pwm-names:
+          items:
+            - const: cnv
+            - const: clk
+        vdd1-supply: false
+        vdd2-supply: false
+        clocks: false
+        # REFIN is the bandgap output and the input of the reference buffer.
+        # An external reference goes to REFBUF, through ref-supply.
+        refin-supply: false
+        en0-gpios: false
+        en1-gpios: false
+        en2-gpios: false
+        en3-gpios: false
+        adi,en0-always-on: false
+        adi,en1-always-on: false
+        adi,en2-always-on: false
+        adi,en3-always-on: false
+        adi,no-dco: false
+      required:
+        - vdd-supply
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - adi,ltc2387-16
+              - adi,ltc2387-18
+    then:
+      properties:
+        vio-supply: false
+        vs-p-supply: false
+        vs-n-supply: false
+        adi,gain-milli: false
+      required:
+        - vddl-supply
+        - ovdd-supply
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - adi,adaq23875
+              - adi,adaq23876
+              - adi,adaq23878
+    then:
+      properties:
+        vddl-supply: false
+        ovdd-supply: false
+      required:
+        - vio-supply
+        - vs-p-supply
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            const: adi,adaq23875
+    then:
+      properties:
+        adi,gain-milli: false
+
+  - if:
+      properties:
+        compatible:
+          contains:
+            enum:
+              - adi,adaq23876
+              - adi,adaq23878
+    then:
+      required:
+        - adi,gain-milli
+
 additionalProperties: false
 
 examples:
@@ -174,3 +325,27 @@ examples:
         en0-gpios = <&gpio0 86 GPIO_ACTIVE_HIGH>;
         en1-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>;
     };
+  - |
+    adc {
+        compatible = "adi,ltc2387-16";
+        io-backends = <&axi_adc>;
+        ovdd-supply = <&supply_2_5V>;
+        pwms = <&axi_pwm_gen 0 0 0>, <&axi_pwm_gen 1 0 0>;
+        pwm-names = "cnv", "clk";
+        ref-supply = <&supply_4_096V>;
+        vdd-supply = <&supply_5V>;
+        vddl-supply = <&supply_2_5V>;
+    };
+  - |
+    adc {
+        compatible = "adi,adaq23878";
+        io-backends = <&axi_adc>;
+        pwms = <&axi_pwm_gen 0 0 0>, <&axi_pwm_gen 1 0 0>;
+        pwm-names = "cnv", "clk";
+        vdd-supply = <&supply_5V>;
+        vio-supply = <&supply_2_5V>;
+        vs-n-supply = <&supply_0V>;
+        vs-p-supply = <&supply_5V>;
+        adi,gain-milli = /bits/ 16 <870>;
+        adi,use-two-lanes;
+    };
-- 
2.34.1


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

* [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support
  2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
                   ` (3 preceding siblings ...)
  2026-11-04 13:21 ` [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x Nicolae-Daniel Deaconescu
@ 2026-11-04 13:21 ` Nicolae-Daniel Deaconescu
  2026-11-04 20:22   ` Andy Shevchenko
  4 siblings, 1 reply; 10+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-04 13:21 UTC (permalink / raw)
  To: Jonathan Cameron
  Cc: Uwe Kleine-König, Conor Dooley, Krzysztof Kozlowski,
	Rob Herring, David Lechner, Nuno Sá, Andy Shevchenko,
	Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel, Nicolae-Daniel Deaconescu

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

Add the LTC2387-16, LTC2387-18, ADAQ23875, ADAQ23876 and ADAQ23878 as
chip data of this driver. They are read through the same two PWMs and
IIO backend. They differ in a few ways that the chip data and a few
quirks cover, see the binding patch for the list:

- Data is output at double data rate on one or two lanes, so the CLK
  burst is the number of bits divided by 2 or by 4 pulses long.
- The burst of one conversion starts at least t_FIRSTCLK after its CNV
  and has to end at most t_LASTCLK after the next CNV, so at the maximum
  rate it runs into the next conversion period.
- They have no EN pins.
- The ADAQ parts divide the scale by the gain of their front end.

The changes are:

- Add chip info for the five parts, with a maximum sampling frequency of
  15 MSPS, and the OF and platform device ID entries for them.
- Add struct ad7625_ddr_timing with t_CNVH, t_FIRSTCLK and t_LASTCLK, and
  point the chip info of the new parts to it. A non-NULL pointer marks a
  part with DDR output.
- Add ad7625_set_sampling_freq_ddr(). Unlike for the AD762x and AD796x,
  the sampling period is raised until the CLK burst fits t_LASTCLK, so the
  maximum rate isn't limited to 10 MSPS by the period clamp.
- Build the waveforms in whole steps of the PWMs, which are read with
  pwm_get_waveform_step_might_sleep() when probing and each time the
  sampling frequency changes.
- Get the second PWM as "clk" for the new parts and "clk_gate" for the
  existing ones, and don't get a reference clock for the new parts, as the
  step already tells the clock rate of the PWM.
- Add ad7625_parse_ddr() to read adi,use-two-lanes and adi,gain-milli. The
  gain defaults to the fixed gain of the ADAQ23875 and to 1 for the
  LTC2387, and is required for the ADAQ23876 and ADAQ23878, whose gain is
  pin strapped and has to be one of the supported values.
- Divide the scale by the gain of the front end for the new parts.
- Enable the supplies of the new parts, and read their optional
  ref-supply, using the internal reference when it is absent.
- Set the number of lanes of the backend for the new parts. This can only
  be skipped if the backend can't set it and one lane is used.
- Limit the default sampling frequency of the new parts to the clock rate
  of the PWMs.
- Move the setup of the EN pin mode into ad7625_set_initial_mode(), which
  does nothing for the parts without EN pins. The code that sets the pins
  of the existing parts is unchanged.
- Include math64.h and minmax.h for the helpers that the new code uses.
- Add the new parts to the Kconfig help and Documentation/iio/ad7625.rst,
  and new maintainer entries.

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>
---
 Documentation/iio/ad7625.rst |  34 ++-
 MAINTAINERS                  |   2 +
 drivers/iio/adc/Kconfig      |   7 +-
 drivers/iio/adc/ad7625.c     | 391 ++++++++++++++++++++++++++++++++---
 4 files changed, 404 insertions(+), 30 deletions(-)

diff --git a/Documentation/iio/ad7625.rst b/Documentation/iio/ad7625.rst
index 61761e3b75c3..5f7c481b13a5 100644
--- a/Documentation/iio/ad7625.rst
+++ b/Documentation/iio/ad7625.rst
@@ -4,8 +4,9 @@
 AD7625 driver
 ====================
 
-ADC driver for Analog Devices Inc. AD7625, AD7626, AD7960, and AD7961
-devices. The module name is ``ad7625``.
+ADC driver for Analog Devices Inc. AD7625, AD7626, AD7960, AD7961, LTC2387-16,
+LTC2387-18, ADAQ23875, ADAQ23876 and ADAQ23878 devices. The module name is
+``ad7625``.
 
 Supported devices
 =================
@@ -16,8 +17,14 @@ The following chips are supported by this driver:
 * `AD7626 <https://www.analog.com/AD7626>`_
 * `AD7960 <https://www.analog.com/AD7960>`_
 * `AD7961 <https://www.analog.com/AD7961>`_
+* `LTC2387-16 <https://www.analog.com/LTC2387-16>`_
+* `LTC2387-18 <https://www.analog.com/LTC2387-18>`_
+* `ADAQ23875 <https://www.analog.com/ADAQ23875>`_
+* `ADAQ23876 <https://www.analog.com/ADAQ23876>`_
+* `ADAQ23878 <https://www.analog.com/ADAQ23878>`_
 
-The driver requires use of the Pulsar LVDS HDL project:
+The driver requires use of the Pulsar LVDS HDL project for the AD7625, AD7626,
+AD7960 and AD7961:
 
 * `Pulsar LVDS HDL <http://analogdevicesinc.github.io/hdl/projects/pulsar_lvds/index.html>`_
 
@@ -65,6 +72,27 @@ The source is determined by the device tree. If ``ref-supply`` is present, then
 the external reference is used. If ``refin-supply`` is present, then the internal
 buffer is used. If neither is present, then the internal reference is used.
 
+LTC2387 and ADAQ2387x
+---------------------
+
+These parts output data on one or two lanes at double data rate, selected by
+the TWOLANES pin, which the ``adi,use-two-lanes`` property describes. They have no
+EN pins and no ``clocks`` property. The driver asks the two PWMs for their
+step with ``pwm_get_waveform_step_might_sleep()`` and builds the CNV and CLK
+waveforms in whole steps, so that the burst of CLK pulses starts at least
+t\ :sub:`FIRSTCLK` after CNV and ends at most t\ :sub:`LASTCLK` after the
+next CNV. The burst can run into the next conversion period, which limits the
+sampling frequency in one-lane mode. The ADAQ parts divide the scale by the
+gain of their front end, given by ``adi,gain-milli``.
+
+The driver counts every cycle of the clock that the PWM reports as one pulse
+of the CLK input, so the clock gated onto CLK has to be the one the PWM
+counts. They need an AXI ADC design of their own, not the Pulsar LVDS one: the
+DDR capture and the lane count have to match it, as the AXI ADC backend has no
+DDR operation to select it, and for two lanes it has to implement
+``num_lanes_set``. The first sample after the buffer is enabled can be stale,
+as the CLK burst of a conversion may fall into the next conversion period.
+
 Unimplemented features
 ----------------------
 
diff --git a/MAINTAINERS b/MAINTAINERS
index 52024397dc78..39c50a0b72ee 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1653,6 +1653,8 @@ F:	include/dt-bindings/iio/addac/adi,ad74413r.h
 ANALOG DEVICES INC AD7625 DRIVER
 M:	Michael Hennerich <Michael.Hennerich@analog.com>
 M:	Nuno Sá <nuno.sa@analog.com>
+M:	Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
+M:	Sergiu Cuciurean <sergiu.cuciurean@analog.com>
 R:	Trevor Gamblin <tgamblin@baylibre.com>
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
diff --git a/drivers/iio/adc/Kconfig b/drivers/iio/adc/Kconfig
index 02de4809ae31..7fd54ad8889c 100644
--- a/drivers/iio/adc/Kconfig
+++ b/drivers/iio/adc/Kconfig
@@ -404,7 +404,7 @@ config AD7606_IFACE_SPI
 	  module will be called ad7606_spi.
 
 config AD7625
-	tristate "Analog Devices AD7625/AD7626 High Speed ADC driver"
+	tristate "Analog Devices AD762x, AD796x, LTC2387 and ADAQ2387x ADC driver"
 	depends on PWM
 	select IIO_BACKEND
 	help
@@ -413,6 +413,11 @@ config AD7625
 	  * AD7626 16-Bit, 10 MSPS PulSAR Analog-to-Digital Converter
 	  * AD7960 18-Bit, 5 MSPS PulSAR Analog-to-Digital Converter
 	  * AD7961 16-Bit, 5 MSPS PulSAR Analog-to-Digital Converter
+	  * LTC2387-16 16-Bit, 15 MSPS SAR Analog-to-Digital Converter
+	  * LTC2387-18 18-Bit, 15 MSPS SAR Analog-to-Digital Converter
+	  * ADAQ23875 and ADAQ23876 16-Bit, 15 MSPS uModule Data Acquisition
+	    Systems
+	  * ADAQ23878 18-Bit, 15 MSPS uModule Data Acquisition System
 
 	  The driver requires the assistance of the AXI ADC IP core to operate.
 
diff --git a/drivers/iio/adc/ad7625.c b/drivers/iio/adc/ad7625.c
index 0de7f29797ef..3489ff3349ae 100644
--- a/drivers/iio/adc/ad7625.c
+++ b/drivers/iio/adc/ad7625.c
@@ -17,6 +17,8 @@
 #include <linux/iio/backend.h>
 #include <linux/iio/iio.h>
 #include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
 #include <linux/pwm.h>
@@ -33,6 +35,15 @@ struct ad7625_timing_spec {
 	unsigned int conv_msb_ns;
 };
 
+struct ad7625_ddr_timing {
+	/* Minimum conversion high time (t_{CNVH}). */
+	unsigned int cnv_high_min_ns;
+	/* Minimum CNV to first CLK edge of the same conversion (t_{FIRSTCLK}). */
+	unsigned int first_clk_min_ns;
+	/* Maximum next CNV to last CLK edge of the previous one (t_{LASTCLK}). */
+	unsigned int last_clk_max_ns;
+};
+
 struct ad7625_chip_info {
 	const char *name;
 	const unsigned int max_sample_freq_hz;
@@ -41,6 +52,16 @@ struct ad7625_chip_info {
 	const bool has_power_down_state;
 	const bool has_bandwidth_control;
 	const bool has_internal_vref;
+	/*
+	 * The LTC2387 and ADAQ2387x output data on one or two lanes at double
+	 * data rate, have no EN pins and learn their timing from the PWMs.
+	 * They are the parts with @ddr_timing set.
+	 */
+	const struct ad7625_ddr_timing *ddr_timing;
+	const char * const *supplies;
+	int num_supplies;
+	/* Gain * 1000 of the ADAQ front end, 0 if the board has to provide it. */
+	const u16 default_gain_milli;
 };
 
 /* AD7625_CHAN_SPEC - Define a chan spec structure for a specific chip */
@@ -78,6 +99,10 @@ struct ad7625_state {
 	struct pwm_waveform cnv_wf;
 	unsigned int vref_mv;
 	u32 sampling_freq_hz;
+	/* Step of the PWMs and lane count, for the parts with DDR output */
+	struct pwm_waveform_step step;
+	u16 gain_milli;
+	bool two_lanes;
 	/*
 	 * Optional GPIOs for controlling device state. EN0 and EN1
 	 * determine voltage reference configuration and on/off state.
@@ -164,6 +189,70 @@ static const struct ad7625_chip_info ad7961_chip_info = {
 	.has_internal_vref = false,
 };
 
+static const struct ad7625_ddr_timing ltc2387_ddr_timing = {
+	.cnv_high_min_ns = 5,
+	.first_clk_min_ns = 65,
+	.last_clk_max_ns = 49,
+};
+
+static const char * const ltc2387_supplies[] = { "vdd", "vddl", "ovdd" };
+static const char * const adaq2387x_supplies[] = {
+	"vdd", "vio", "vs-p", "vs-n"
+};
+
+static const struct ad7625_chip_info ltc2387_16_chip_info = {
+	.name = "ltc2387-16",
+	.max_sample_freq_hz = 15 * MEGA,
+	.chan_spec = AD7625_CHAN_SPEC(16),
+	.has_internal_vref = true,
+	.ddr_timing = &ltc2387_ddr_timing,
+	.supplies = ltc2387_supplies,
+	.num_supplies = ARRAY_SIZE(ltc2387_supplies),
+	.default_gain_milli = 1000,
+};
+
+static const struct ad7625_chip_info ltc2387_18_chip_info = {
+	.name = "ltc2387-18",
+	.max_sample_freq_hz = 15 * MEGA,
+	.chan_spec = AD7625_CHAN_SPEC(18),
+	.has_internal_vref = true,
+	.ddr_timing = &ltc2387_ddr_timing,
+	.supplies = ltc2387_supplies,
+	.num_supplies = ARRAY_SIZE(ltc2387_supplies),
+	.default_gain_milli = 1000,
+};
+
+static const struct ad7625_chip_info adaq23875_chip_info = {
+	.name = "adaq23875",
+	.max_sample_freq_hz = 15 * MEGA,
+	.chan_spec = AD7625_CHAN_SPEC(16),
+	.has_internal_vref = true,
+	.ddr_timing = &ltc2387_ddr_timing,
+	.supplies = adaq2387x_supplies,
+	.num_supplies = ARRAY_SIZE(adaq2387x_supplies),
+	.default_gain_milli = 2000,
+};
+
+static const struct ad7625_chip_info adaq23876_chip_info = {
+	.name = "adaq23876",
+	.max_sample_freq_hz = 15 * MEGA,
+	.chan_spec = AD7625_CHAN_SPEC(16),
+	.has_internal_vref = true,
+	.ddr_timing = &ltc2387_ddr_timing,
+	.supplies = adaq2387x_supplies,
+	.num_supplies = ARRAY_SIZE(adaq2387x_supplies),
+};
+
+static const struct ad7625_chip_info adaq23878_chip_info = {
+	.name = "adaq23878",
+	.max_sample_freq_hz = 15 * MEGA,
+	.chan_spec = AD7625_CHAN_SPEC(18),
+	.has_internal_vref = true,
+	.ddr_timing = &ltc2387_ddr_timing,
+	.supplies = adaq2387x_supplies,
+	.num_supplies = ARRAY_SIZE(adaq2387x_supplies),
+};
+
 enum ad7960_mode {
 	AD7960_MODE_POWER_DOWN,
 	AD7960_MODE_SNOOZE,
@@ -172,12 +261,146 @@ enum ad7960_mode {
 	AD7960_MODE_TEST_PATTERN,
 };
 
+/* Cycles of the gated clock needed to cover @ns, rounded up. */
+static u32 ad7625_ddr_ns_to_cycles(struct ad7625_state *st, u32 ns)
+{
+	return DIV_ROUND_UP_ULL((u64)ns * st->step.clk_rate_hz, NSEC_PER_SEC);
+}
+
+/* PWM steps needed to cover @cycles clock cycles, rounded up. */
+static u32 ad7625_ddr_cycles_to_steps(struct ad7625_state *st, u32 cycles)
+{
+	return DIV_ROUND_UP(cycles, st->step.clk_cycles_per_step);
+}
+
+/*
+ * The PWMs round a requested length down to whole steps, so asking for
+ * round_up(n * step) ns results in exactly n steps.
+ */
+static u64 ad7625_ddr_steps_to_ns(struct ad7625_state *st, u32 steps)
+{
+	u64 cycles = (u64)steps * st->step.clk_cycles_per_step;
+
+	return DIV64_U64_ROUND_UP(cycles * NSEC_PER_SEC, st->step.clk_rate_hz);
+}
+
+/* CLK pulses per conversion. Each pulse carries 2 bits per lane (DDR). */
+static u32 ad7625_ddr_burst_pulses(struct ad7625_state *st)
+{
+	return DIV_ROUND_UP(st->info->chan_spec.scan_type.realbits,
+			    st->two_lanes ? 4 : 2);
+}
+
+/*
+ * Read the step of the PWMs. They have to agree, as CNV and CLK are one
+ * burst, and the burst has to be a whole number of steps long, so a PWM that
+ * takes several clock cycles per step can only make a matching number of
+ * pulses.
+ */
+static int ad7625_ddr_read_step(struct ad7625_state *st,
+				struct pwm_waveform_step *step)
+{
+	struct pwm_waveform_step cnv_step;
+	int ret;
+
+	ret = pwm_get_waveform_step_might_sleep(st->clk_gate_pwm, step);
+	if (ret)
+		return ret;
+
+	ret = pwm_get_waveform_step_might_sleep(st->cnv_pwm, &cnv_step);
+	if (ret)
+		return ret;
+
+	if (cnv_step.clk_rate_hz != step->clk_rate_hz ||
+	    cnv_step.clk_cycles_per_step != step->clk_cycles_per_step)
+		return -EINVAL;
+
+	if (ad7625_ddr_burst_pulses(st) % step->clk_cycles_per_step)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int ad7625_set_sampling_freq_ddr(struct ad7625_state *st, u32 freq)
+{
+	const struct ad7625_ddr_timing *t = st->info->ddr_timing;
+	struct pwm_waveform clk_gate_wf = { }, cnv_wf = { };
+	u32 period, min_period, gate, offset, lastclk, cnvh, burst_end;
+	struct pwm_waveform_step step;
+	u32 cycles_per_step;
+	u64 lastclk_cycles;
+	int ret;
+
+	/* The step changes if the clock of the PWMs was reconfigured since probe. */
+	ret = ad7625_ddr_read_step(st, &step);
+	if (ret)
+		return ret;
+
+	st->step = step;
+
+	if (!freq || freq > st->info->max_sample_freq_hz ||
+	    freq > st->step.clk_rate_hz)
+		return -EINVAL;
+
+	cycles_per_step = st->step.clk_cycles_per_step;
+
+	/* Everything but lastclk and burst_end is in steps of the PWMs. */
+	gate = ad7625_ddr_burst_pulses(st) / cycles_per_step;
+	cnvh = ad7625_ddr_ns_to_cycles(st, t->cnv_high_min_ns);
+	cnvh = max(ad7625_ddr_cycles_to_steps(st, cnvh), 1U);
+	offset = ad7625_ddr_ns_to_cycles(st, t->first_clk_min_ns);
+	offset = ad7625_ddr_cycles_to_steps(st, offset);
+	lastclk_cycles = (u64)t->last_clk_max_ns * st->step.clk_rate_hz;
+	lastclk = div_u64(lastclk_cycles, NSEC_PER_SEC);
+
+	/*
+	 * The gated clock burst for one conversion starts t_FIRSTCLK after its
+	 * CNV and may run into the next period, but has to end within
+	 * t_LASTCLK of the next CNV. It must also leave the gate low for at
+	 * least one step per period.
+	 */
+	burst_end = (offset + gate) * cycles_per_step;
+	min_period = burst_end > lastclk ?
+		     DIV_ROUND_UP(burst_end - lastclk, cycles_per_step) : 0;
+	period = DIV_ROUND_UP(st->step.clk_rate_hz, freq);
+	period = max3(DIV_ROUND_UP(period, cycles_per_step), gate + 1, min_period);
+
+	cnv_wf.period_length_ns = ad7625_ddr_steps_to_ns(st, period);
+	cnv_wf.duty_length_ns = ad7625_ddr_steps_to_ns(st, cnvh);
+
+	clk_gate_wf.period_length_ns = cnv_wf.period_length_ns;
+	clk_gate_wf.duty_length_ns = ad7625_ddr_steps_to_ns(st, gate);
+	clk_gate_wf.duty_offset_ns = ad7625_ddr_steps_to_ns(st, offset % period);
+
+	/*
+	 * The waveforms are whole steps, so nothing should have to grow. If it
+	 * did, a minimum time of the part is no longer met.
+	 */
+	ret = pwm_round_waveform_might_sleep(st->cnv_pwm, &cnv_wf);
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+
+	ret = pwm_round_waveform_might_sleep(st->clk_gate_pwm, &clk_gate_wf);
+	if (ret)
+		return ret < 0 ? ret : -EINVAL;
+
+	st->cnv_wf = cnv_wf;
+	st->clk_gate_wf = clk_gate_wf;
+	st->sampling_freq_hz = DIV64_U64_ROUND_CLOSEST(st->step.clk_rate_hz,
+						       (u64)period * cycles_per_step);
+
+	return 0;
+}
+
 static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq)
 {
 	struct pwm_waveform clk_gate_wf = { }, cnv_wf = { };
 	unsigned long target;
 	int ret;
 
+	if (st->info->ddr_timing)
+		return ad7625_set_sampling_freq_ddr(st, freq);
+
 	if (!freq)
 		return -EINVAL;
 
@@ -235,6 +458,14 @@ static int ad7625_read_raw(struct iio_dev *indio_dev,
 		return IIO_VAL_INT;
 
 	case IIO_CHAN_INFO_SCALE:
+		if (st->info->ddr_timing) {
+			/* Referred to the input of the ADAQ front end, so divided by its gain. */
+			*val = st->vref_mv * MILLI;
+			*val2 = st->gain_milli << (chan->scan_type.realbits - 1);
+
+			return IIO_VAL_FRACTIONAL;
+		}
+
 		*val = st->vref_mv;
 		*val2 = chan->scan_type.realbits - 1;
 
@@ -417,6 +648,35 @@ static int ad7960_set_mode(struct ad7625_state *st, enum ad7960_mode mode,
 	}
 }
 
+static const u16 ad7625_gains_milli[] = { 370, 730, 870, 1380, 2250 };
+
+static int ad7625_parse_ddr(struct device *dev, struct ad7625_state *st)
+{
+	int ret, i;
+
+	st->two_lanes = device_property_read_bool(dev, "adi,use-two-lanes");
+
+	/* A fixed gain part has no adi,gain-milli, the others are pin strapped. */
+	st->gain_milli = st->info->default_gain_milli;
+	if (st->gain_milli)
+		return 0;
+
+	if (!device_property_present(dev, "adi,gain-milli"))
+		return dev_err_probe(dev, -EINVAL, "adi,gain-milli must be set\n");
+
+	ret = device_property_read_u16(dev, "adi,gain-milli", &st->gain_milli);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to read adi,gain-milli\n");
+
+	/* The scale shifts the gain by up to 17 bits, so it has to stay small. */
+	for (i = 0; i < ARRAY_SIZE(ad7625_gains_milli); i++)
+		if (st->gain_milli == ad7625_gains_milli[i])
+			return 0;
+
+	return dev_err_probe(dev, -EINVAL, "unsupported adi,gain-milli %u\n",
+			     st->gain_milli);
+}
+
 static int ad7625_buffer_preenable(struct iio_dev *indio_dev)
 {
 	struct ad7625_state *st = iio_priv(indio_dev);
@@ -462,6 +722,8 @@ static int devm_ad7625_pwm_get(struct device *dev,
 {
 	struct clk *ref_clk;
 	u32 ref_clk_rate_hz;
+	const char *clk_name;
+	int ret;
 
 	st->cnv_pwm = devm_pwm_get(dev, "cnv");
 	if (IS_ERR(st->cnv_pwm))
@@ -471,14 +733,28 @@ static int devm_ad7625_pwm_get(struct device *dev,
 	/* Preemptively disable the PWM in case it was enabled at boot */
 	pwm_disable(st->cnv_pwm);
 
-	st->clk_gate_pwm = devm_pwm_get(dev, "clk_gate");
+	clk_name = st->info->ddr_timing ? "clk" : "clk_gate";
+	st->clk_gate_pwm = devm_pwm_get(dev, clk_name);
 	if (IS_ERR(st->clk_gate_pwm))
 		return dev_err_probe(dev, PTR_ERR(st->clk_gate_pwm),
-				     "failed to get clk_gate pwm\n");
+				     "failed to get %s pwm\n", clk_name);
 
 	/* Preemptively disable the PWM in case it was enabled at boot */
 	pwm_disable(st->clk_gate_pwm);
 
+	/*
+	 * The PWMs tell the step of their waveforms, so the parts with DDR
+	 * output need no clock of their own to know the length of a CLK pulse.
+	 */
+	if (st->info->ddr_timing) {
+		ret = ad7625_ddr_read_step(st, &st->step);
+		if (ret)
+			return dev_err_probe(dev, ret,
+					     "the steps of the pwms don't fit the CLK burst\n");
+
+		return 0;
+	}
+
 	ref_clk = devm_clk_get_enabled(dev, NULL);
 	if (IS_ERR(ref_clk))
 		return dev_err_probe(dev, PTR_ERR(ref_clk),
@@ -494,6 +770,28 @@ static int devm_ad7625_pwm_get(struct device *dev,
 	return 0;
 }
 
+static int devm_ad7625_ddr_regulator_setup(struct device *dev,
+					   struct ad7625_state *st)
+{
+	int ret;
+
+	ret = devm_regulator_bulk_get_enable(dev, st->info->num_supplies,
+					     st->info->supplies);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to enable supplies\n");
+
+	ret = devm_regulator_get_enable_read_voltage(dev, "ref");
+	if (ret < 0 && ret != -ENODEV)
+		return dev_err_probe(dev, ret, "failed to get REF voltage\n");
+	if (!ret)
+		return dev_err_probe(dev, -EINVAL, "REF voltage must not be 0\n");
+
+	/* Without ref-supply the internal reference is used. */
+	st->vref_mv = ret == -ENODEV ? AD7625_INTERNAL_REF_MV : ret / (MICRO / MILLI);
+
+	return 0;
+}
+
 /*
  * There are three required input voltages for each device, plus two
  * conditionally-optional (depending on part) REF and REFIN voltages
@@ -515,6 +813,9 @@ static int devm_ad7625_regulator_setup(struct device *dev,
 	static const char * const regulator_names[] = { "vio", "vdd2", "vdd1" };
 	int ret, ref_mv;
 
+	if (st->info->ddr_timing)
+		return devm_ad7625_ddr_regulator_setup(dev, st);
+
 	ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
 					     regulator_names);
 	if (ret)
@@ -557,6 +858,41 @@ static int devm_ad7625_regulator_setup(struct device *dev,
 	return 0;
 }
 
+static int ad7625_set_initial_mode(struct device *dev, struct ad7625_state *st)
+{
+	int ret;
+
+	/* The parts with DDR output have no EN pins. */
+	if (st->info->ddr_timing)
+		return 0;
+
+	/* Set the device mode based on detected EN configuration. */
+	if (!st->info->has_bandwidth_control) {
+		ad7625_set_en_gpios_for_vref(st, st->have_refin, st->vref_mv);
+		return 0;
+	}
+
+	/*
+	 * If neither sampling mode is available, then report an error,
+	 * since the other modes are not useful defaults.
+	 */
+	if (st->can_wide_bandwidth) {
+		ret = ad7960_set_mode(st, AD7960_MODE_WIDE_BANDWIDTH,
+				      st->have_refin, st->vref_mv);
+	} else if (st->can_narrow_bandwidth) {
+		ret = ad7960_set_mode(st, AD7960_MODE_NARROW_BANDWIDTH,
+				      st->have_refin, st->vref_mv);
+	} else {
+		return dev_err_probe(dev, -EINVAL,
+			"couldn't set device to wide or narrow bandwidth modes\n");
+	}
+
+	if (ret)
+		return dev_err_probe(dev, -EINVAL, "failed to set EN pins\n");
+
+	return 0;
+}
+
 static int ad7625_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -579,7 +915,9 @@ static int ad7625_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, -EINVAL,
 				     "self-clocked mode not supported\n");
 
-	if (st->info->has_bandwidth_control)
+	if (st->info->ddr_timing)
+		ret = ad7625_parse_ddr(dev, st);
+	else if (st->info->has_bandwidth_control)
 		ret = ad7625_parse_mode(dev, st, 4);
 	else
 		ret = ad7625_parse_mode(dev, st, 2);
@@ -591,29 +929,9 @@ static int ad7625_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	/* Set the device mode based on detected EN configuration. */
-	if (!st->info->has_bandwidth_control) {
-		ad7625_set_en_gpios_for_vref(st, st->have_refin, st->vref_mv);
-	} else {
-		/*
-		 * If neither sampling mode is available, then report an error,
-		 * since the other modes are not useful defaults.
-		 */
-		if (st->can_wide_bandwidth) {
-			ret = ad7960_set_mode(st, AD7960_MODE_WIDE_BANDWIDTH,
-					      st->have_refin, st->vref_mv);
-		} else if (st->can_narrow_bandwidth) {
-			ret = ad7960_set_mode(st, AD7960_MODE_NARROW_BANDWIDTH,
-					      st->have_refin, st->vref_mv);
-		} else {
-			return dev_err_probe(dev, -EINVAL,
-				"couldn't set device to wide or narrow bandwidth modes\n");
-		}
-
-		if (ret)
-			return dev_err_probe(dev, -EINVAL,
-					     "failed to set EN pins\n");
-	}
+	ret = ad7625_set_initial_mode(dev, st);
+	if (ret)
+		return ret;
 
 	ret = devm_ad7625_pwm_get(dev, st);
 	if (ret)
@@ -634,6 +952,13 @@ static int ad7625_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	if (st->info->ddr_timing) {
+		ret = iio_backend_num_lanes_set(st->back, st->two_lanes ? 2 : 1);
+		/* Without the op, a backend can only be left on one lane. */
+		if (ret && (ret != -EOPNOTSUPP || st->two_lanes))
+			return dev_err_probe(dev, ret, "failed to set the number of lanes\n");
+	}
+
 	ret = devm_iio_backend_enable(dev, st->back);
 	if (ret)
 		return ret;
@@ -647,6 +972,10 @@ static int ad7625_probe(struct platform_device *pdev)
 	if (st->info->has_bandwidth_control && !st->can_wide_bandwidth)
 		default_sample_freq = AD7960_MAX_NBW_FREQ;
 
+	/* The DDR parts reject rates above the clock of the PWMs. */
+	if (st->info->ddr_timing)
+		default_sample_freq = min_t(u32, default_sample_freq, st->step.clk_rate_hz);
+
 	ret = ad7625_set_sampling_freq(st, default_sample_freq);
 	if (ret)
 		return dev_err_probe(dev, ret,
@@ -660,6 +989,11 @@ static const struct of_device_id ad7625_of_match[] = {
 	{ .compatible = "adi,ad7626", .data = &ad7626_chip_info },
 	{ .compatible = "adi,ad7960", .data = &ad7960_chip_info },
 	{ .compatible = "adi,ad7961", .data = &ad7961_chip_info },
+	{ .compatible = "adi,adaq23875", .data = &adaq23875_chip_info },
+	{ .compatible = "adi,adaq23876", .data = &adaq23876_chip_info },
+	{ .compatible = "adi,adaq23878", .data = &adaq23878_chip_info },
+	{ .compatible = "adi,ltc2387-16", .data = &ltc2387_16_chip_info },
+	{ .compatible = "adi,ltc2387-18", .data = &ltc2387_18_chip_info },
 	{ }
 };
 MODULE_DEVICE_TABLE(of, ad7625_of_match);
@@ -669,6 +1003,11 @@ static const struct platform_device_id ad7625_device_ids[] = {
 	{ .name = "ad7626", .driver_data = (kernel_ulong_t)&ad7626_chip_info },
 	{ .name = "ad7960", .driver_data = (kernel_ulong_t)&ad7960_chip_info },
 	{ .name = "ad7961", .driver_data = (kernel_ulong_t)&ad7961_chip_info },
+	{ .name = "adaq23875", .driver_data = (kernel_ulong_t)&adaq23875_chip_info },
+	{ .name = "adaq23876", .driver_data = (kernel_ulong_t)&adaq23876_chip_info },
+	{ .name = "adaq23878", .driver_data = (kernel_ulong_t)&adaq23878_chip_info },
+	{ .name = "ltc2387-16", .driver_data = (kernel_ulong_t)&ltc2387_16_chip_info },
+	{ .name = "ltc2387-18", .driver_data = (kernel_ulong_t)&ltc2387_18_chip_info },
 	{ }
 };
 MODULE_DEVICE_TABLE(platform, ad7625_device_ids);
-- 
2.34.1


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

* Re: [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x
  2026-11-04 13:21 ` [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x Nicolae-Daniel Deaconescu
@ 2026-11-04 13:28   ` sashiko-bot
  2026-11-05  0:23   ` Conor Dooley
  1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-11-04 13:28 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu; +Cc: devicetree, robh, conor+dt

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] dt-bindings: iio: adc: adi,ad7625: split LTC2387 and ADAQ2387x into separate schema
--

commit c29e24d5cf54c71037786f3f4fd74c43de8e27b1
Author: Sergiu Cuciurean <sergiu.cuciurean@analog.com>

dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x

This commit adds devicetree bindings for the LTC2387-16, LTC2387-18,
ADAQ23875, ADAQ23876, and ADAQ23878 ADCs to the existing adi,ad7625.yaml
schema. It defines their power supplies, PWM names, and configuration
properties alongside the existing AD762x and AD796x family bindings.

> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
> index 8848562af28f2..dbdded88236ca 100644
> --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
[ ... ]
> @@ -109,10 +159,6 @@ properties:
>  
>  required:
>    - compatible
> -  - vdd1-supply
> -  - vdd2-supply
> -  - vio-supply
> -  - clocks
>    - pwms
>    - pwm-names
>    - io-backends
> @@ -157,6 +203,111 @@ allOf:
>          - required: [ref-supply]
>          - required: [refin-supply]
>  
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,ad7625
> +              - adi,ad7626
> +              - adi,ad7960
> +              - adi,ad7961
> +    then:
> +      properties:
> +        pwm-names:
> +          items:
> +            - const: cnv
> +            - const: clk_gate
> +        vdd-supply: false
> +        vddl-supply: false
> +        ovdd-supply: false
> +        vs-p-supply: false
> +        vs-n-supply: false
> +        adi,use-two-lanes: false
> +        adi,gain-milli: false
> +      required:
> +        - vdd1-supply
> +        - vdd2-supply
> +        - vio-supply
> +        - clocks
> +    else:
> +      # The LTC2387 and ADAQ parts have no EN pins and no clock to describe.
> +      properties:
> +        pwm-names:
> +          items:
> +            - const: cnv
> +            - const: clk
> +        vdd1-supply: false
> +        vdd2-supply: false
> +        clocks: false
> +        # REFIN is the bandgap output and the input of the reference buffer.
> +        # An external reference goes to REFBUF, through ref-supply.
> +        refin-supply: false
> +        en0-gpios: false
> +        en1-gpios: false
> +        en2-gpios: false
> +        en3-gpios: false
> +        adi,en0-always-on: false
> +        adi,en1-always-on: false
> +        adi,en2-always-on: false
> +        adi,en3-always-on: false
> +        adi,no-dco: false
> +      required:
> +        - vdd-supply
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,ltc2387-16
> +              - adi,ltc2387-18
> +    then:
> +      properties:
> +        vio-supply: false
> +        vs-p-supply: false
> +        vs-n-supply: false
> +        adi,gain-milli: false
> +      required:
> +        - vddl-supply
> +        - ovdd-supply
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,adaq23875
> +              - adi,adaq23876
> +              - adi,adaq23878
> +    then:
> +      properties:
> +        vddl-supply: false
> +        ovdd-supply: false
> +      required:
> +        - vio-supply
> +        - vs-p-supply
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: adi,adaq23875
> +    then:
> +      properties:
> +        adi,gain-milli: false
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,adaq23876
> +              - adi,adaq23878
> +    then:
> +      required:
> +        - adi,gain-milli

[Severity: Low]
This isn't a bug, but since the LTC2387 and ADAQ2387x parts only share
compatible, pwms, io-backends, and optional ref-supply with the AD762x and
AD796x family (while even pwm-names differs on the second entry), combining
them into adi,ad7625.yaml requires removing the top-level required properties
and adding five if-then-else blocks under allOf to make nearly all supplies,
clocks, and device properties mutually exclusive.

Would it be cleaner to split the LTC2387 and ADAQ2387x parts into a separate
binding schema document rather than adding these conditionals to
adi,ad7625.yaml?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261104132143.215581-1-Nicolae-daniel.Deaconescu@analog.com?part=4

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

* Re: [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep()
  2026-11-04 13:21 ` [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
@ 2026-11-04 19:51   ` Andy Shevchenko
  0 siblings, 0 replies; 10+ messages in thread
From: Andy Shevchenko @ 2026-11-04 19:51 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu
  Cc: Jonathan Cameron, Uwe Kleine-König, Conor Dooley,
	Krzysztof Kozlowski, Rob Herring, David Lechner, Nuno Sá,
	Andy Shevchenko, Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel

On Wed, Nov 04, 2026 at 03:21:39PM +0200, Nicolae-Daniel Deaconescu wrote:
> A consumer that has to meet minimum times, or place an edge relative to
> the cycles of a clock that a PWM gates, cannot do that with the waveform
> API alone. The rounding functions round down, so a minimum has to be
> requested with some margin, and the size of that margin is the step of
> the PWM, which only the provider knows.
> 
> A provider can report the rate of the clock its timing is derived from
> and how many cycles of that clock one step lasts. A consumer can then
> work in whole steps, convert them to nanoseconds rounding up, and get
> that waveform back from the hardware. Without this, a consumer has to
> read the rate from a clock it gets on its own, which describes
> a relationship the hardware doesn't have, and is wrong if
> PWM divides the clock.

...

> +/**
> + * pwm_get_waveform_step_might_sleep - Query the granularity of the waveforms
> + * Cannot be used in atomic context.
> + * @pwm: PWM device
> + * @step: output parameter
> + *
> + * Stores in @step the clock rate the timing of @pwm is derived from and how
> + * many cycles of that clock one step lasts. Period, duty length and duty
> + * offset are multiples of that step, so a waveform asked for in whole steps
> + * is not changed by rounding. Compute the length in nanoseconds of n steps
> + * as n * @step->clk_cycles_per_step * NSEC_PER_SEC / @step->clk_rate_hz and
> + * round it up, because pwm_round_waveform_might_sleep() and
> + * pwm_set_waveform_might_sleep() round down.
> + *
> + * The values can change when a clock changes its rate, so they are only valid
> + * as long as the provider's input clocks are not reconfigured.
> + *
> + * Returns: 0 on success, -EOPNOTSUPP if the provider doesn't report it or a
> + * negative errno

> + * Context: May sleep.

This is incorrect. The Returns section has to be the last one in the
kernel-doc. It's documented.

> + */

> +int pwm_get_waveform_step_might_sleep(struct pwm_device *pwm,
> +				      struct pwm_waveform_step *step)
> +{
> +	struct pwm_chip *chip = pwm->chip;
> +	const struct pwm_ops *ops = chip->ops;
> +	int ret;
> +
> +	if (!pwmchip_supports_waveform(chip) || !ops->get_waveform_step)
> +		return -EOPNOTSUPP;
> +
> +	might_sleep();
> +
> +	guard(pwmchip)(chip);
> +
> +	if (!chip->operational)
> +		return -ENODEV;
> +
> +	if (IS_ENABLED(CONFIG_PWM_DEBUG) && chip->atomic) {
> +		/*
> +		 * Catch any drivers that have been marked as atomic but
> +		 * that will sleep anyway.
> +		 */
> +		non_block_start();
> +		ret = ops->get_waveform_step(chip, pwm, step);
> +		non_block_end();
> +	} else {
> +		ret = ops->get_waveform_step(chip, pwm, step);
> +	}
> +	if (ret)
> +		return ret;
> +
> +	if (!step->clk_rate_hz || !step->clk_cycles_per_step ||

The second check is unneeded, the third one covers it.

> +	    step->clk_rate_hz > (u64)step->clk_cycles_per_step * NSEC_PER_SEC)
> +		return -EINVAL;
> +
> +	return 0;
> +}

...

> +/**
> + * struct pwm_waveform_step - granularity of the waveforms a PWM can generate
> + * @clk_rate_hz: rate of the clock the PWM's timing is derived from
> + * @clk_cycles_per_step: number of those clock cycles one step lasts
> + *
> + * The period, the duty length and the duty offset of a waveform are multiples
> + * of one step, which lasts @clk_cycles_per_step / @clk_rate_hz seconds. A PWM
> + * that divides its input clock in hardware has @clk_cycles_per_step > 1.
> + *
> + * The step is the same for every waveform and for the period, the duty length
> + * and the duty offset, so a provider whose granularity depends on the
> + * requested period (for example through a prescaler) can't describe itself
> + * with it. One step also has to last at least 1 ns, otherwise rounding a
> + * length in nanoseconds up can add a whole step.
> + *
> + * A consumer that needs a minimum time, or that has to place an edge relative
> + * to the cycles of the clock a PWM channel gates, can use this to ask for
> + * the next possible value instead of one that is rounded down, and to compute
> + * the frequency a rounded period results in without the error of the
> + * nanosecond resolution of struct pwm_waveform.
> + */
> +struct pwm_waveform_step {
> +	unsigned long clk_rate_hz;

_Hz ?

> +	unsigned int clk_cycles_per_step;
> +};

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support
  2026-11-04 13:21 ` [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
@ 2026-11-04 20:22   ` Andy Shevchenko
  0 siblings, 0 replies; 10+ messages in thread
From: Andy Shevchenko @ 2026-11-04 20:22 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu
  Cc: Jonathan Cameron, Uwe Kleine-König, Conor Dooley,
	Krzysztof Kozlowski, Rob Herring, David Lechner, Nuno Sá,
	Andy Shevchenko, Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel

On Wed, Nov 04, 2026 at 03:21:43PM +0200, Nicolae-Daniel Deaconescu wrote:
> 
> Add the LTC2387-16, LTC2387-18, ADAQ23875, ADAQ23876 and ADAQ23878 as
> chip data of this driver. They are read through the same two PWMs and
> IIO backend. They differ in a few ways that the chip data and a few
> quirks cover, see the binding patch for the list:
> 
> - Data is output at double data rate on one or two lanes, so the CLK
>   burst is the number of bits divided by 2 or by 4 pulses long.
> - The burst of one conversion starts at least t_FIRSTCLK after its CNV
>   and has to end at most t_LASTCLK after the next CNV, so at the maximum
>   rate it runs into the next conversion period.
> - They have no EN pins.
> - The ADAQ parts divide the scale by the gain of their front end.
> 
> The changes are:
> 
> - Add chip info for the five parts, with a maximum sampling frequency of
>   15 MSPS, and the OF and platform device ID entries for them.
> - Add struct ad7625_ddr_timing with t_CNVH, t_FIRSTCLK and t_LASTCLK, and
>   point the chip info of the new parts to it. A non-NULL pointer marks a
>   part with DDR output.
> - Add ad7625_set_sampling_freq_ddr(). Unlike for the AD762x and AD796x,
>   the sampling period is raised until the CLK burst fits t_LASTCLK, so the
>   maximum rate isn't limited to 10 MSPS by the period clamp.
> - Build the waveforms in whole steps of the PWMs, which are read with
>   pwm_get_waveform_step_might_sleep() when probing and each time the
>   sampling frequency changes.
> - Get the second PWM as "clk" for the new parts and "clk_gate" for the
>   existing ones, and don't get a reference clock for the new parts, as the
>   step already tells the clock rate of the PWM.
> - Add ad7625_parse_ddr() to read adi,use-two-lanes and adi,gain-milli. The
>   gain defaults to the fixed gain of the ADAQ23875 and to 1 for the
>   LTC2387, and is required for the ADAQ23876 and ADAQ23878, whose gain is
>   pin strapped and has to be one of the supported values.
> - Divide the scale by the gain of the front end for the new parts.
> - Enable the supplies of the new parts, and read their optional
>   ref-supply, using the internal reference when it is absent.
> - Set the number of lanes of the backend for the new parts. This can only
>   be skipped if the backend can't set it and one lane is used.
> - Limit the default sampling frequency of the new parts to the clock rate
>   of the PWMs.
> - Move the setup of the EN pin mode into ad7625_set_initial_mode(), which
>   does nothing for the parts without EN pins. The code that sets the pins
>   of the existing parts is unchanged.
> - Include math64.h and minmax.h for the helpers that the new code uses.
> - Add the new parts to the Kconfig help and Documentation/iio/ad7625.rst,
>   and new maintainer entries.

...

>  config AD7625
> -	tristate "Analog Devices AD7625/AD7626 High Speed ADC driver"
> +	tristate "Analog Devices AD762x, AD796x, LTC2387 and ADAQ2387x ADC driver"

This does not scale. Please, make it more generic and make sure the text in the
top comment of the driver along with MODULE_DESCRIPTION() is quite similar to
this (maybe without "driver" word).

...

>  	  * AD7626 16-Bit, 10 MSPS PulSAR Analog-to-Digital Converter
>  	  * AD7960 18-Bit, 5 MSPS PulSAR Analog-to-Digital Converter
>  	  * AD7961 16-Bit, 5 MSPS PulSAR Analog-to-Digital Converter
> +	  * LTC2387-16 16-Bit, 15 MSPS SAR Analog-to-Digital Converter
> +	  * LTC2387-18 18-Bit, 15 MSPS SAR Analog-to-Digital Converter
> +	  * ADAQ23875 and ADAQ23876 16-Bit, 15 MSPS uModule Data Acquisition
> +	    Systems
> +	  * ADAQ23878 18-Bit, 15 MSPS uModule Data Acquisition System

Why not keep this list sorted by a part number?

...

>  struct ad7625_chip_info {
>  	const char *name;
>  	const unsigned int max_sample_freq_hz;

>  	const bool has_power_down_state;
>  	const bool has_bandwidth_control;
>  	const bool has_internal_vref;

Side note: What the point in const to these (plain booleans, integers)?
Chip info as a whole should be const, no?

...

> +	/*
> +	 * The LTC2387 and ADAQ2387x output data on one or two lanes at double
> +	 * data rate, have no EN pins and learn their timing from the PWMs.
> +	 * They are the parts with @ddr_timing set.
> +	 */
> +	const struct ad7625_ddr_timing *ddr_timing;
> +	const char * const *supplies;

Again, why the second const here...

> +	int num_supplies;
> +	/* Gain * 1000 of the ADAQ front end, 0 if the board has to provide it. */
> +	const u16 default_gain_milli;

...and the const here?

milli is multiplier and not a unit. But if you think it's better this way, I am
not going to object.

>  };

...

> +static const char * const adaq2387x_supplies[] = {
> +	"vdd", "vio", "vs-p", "vs-n"

When not on a single line, add a trailing comma.

> +};

...

> +static const struct ad7625_chip_info ltc2387_16_chip_info = {
> +	.name = "ltc2387-16",
> +	.max_sample_freq_hz = 15 * MEGA,

No, we have HZ_PER_MHZ for this.

> +	.chan_spec = AD7625_CHAN_SPEC(16),
> +	.has_internal_vref = true,
> +	.ddr_timing = &ltc2387_ddr_timing,
> +	.supplies = ltc2387_supplies,
> +	.num_supplies = ARRAY_SIZE(ltc2387_supplies),
> +	.default_gain_milli = 1000,

As I mentioned above this multiplier without unit is confusing.
Simply

	.default_gain = 1 * MILLI,

?

> +};

Same comments to all added chip info:s.

...

> +	/*
> +	 * The waveforms are whole steps, so nothing should have to grow. If it
> +	 * did, a minimum time of the part is no longer met.
> +	 */
> +	ret = pwm_round_waveform_might_sleep(st->cnv_pwm, &cnv_wf);
> +	if (ret)
> +		return ret < 0 ? ret : -EINVAL;
> +
> +	ret = pwm_round_waveform_might_sleep(st->clk_gate_pwm, &clk_gate_wf);
> +	if (ret)
> +		return ret < 0 ? ret : -EINVAL;

Don't nest branches, you can just move ret == 0 check out of that in both cases.

...

> +static int ad7625_parse_ddr(struct device *dev, struct ad7625_state *st)
> +{
> +	int ret, i;

Why is 'i' signed and why can't it be defined inside for-loop?

> +	st->two_lanes = device_property_read_bool(dev, "adi,use-two-lanes");
> +
> +	/* A fixed gain part has no adi,gain-milli, the others are pin strapped. */
> +	st->gain_milli = st->info->default_gain_milli;
> +	if (st->gain_milli)
> +		return 0;
> +
> +	if (!device_property_present(dev, "adi,gain-milli"))
> +		return dev_err_probe(dev, -EINVAL, "adi,gain-milli must be set\n");
> +
> +	ret = device_property_read_u16(dev, "adi,gain-milli", &st->gain_milli);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to read adi,gain-milli\n");

	const char *propname;
	...
	propname = "adi,gain-milli";

	// Why do we even need the presence check?
	if (!device_property_present(dev, propname))
		return dev_err_probe(dev, -EINVAL, "%s must be set\n", propname);

	ret = device_property_read_u16(dev, propname, &st->gain_milli);
	if (ret)
		return dev_err_probe(dev, ret, "failed to read %s\n", propname);


> +	/* The scale shifts the gain by up to 17 bits, so it has to stay small. */
> +	for (i = 0; i < ARRAY_SIZE(ad7625_gains_milli); i++)
> +		if (st->gain_milli == ad7625_gains_milli[i])
> +			return 0;
> +
> +	return dev_err_probe(dev, -EINVAL, "unsupported adi,gain-milli %u\n",
> +			     st->gain_milli);
> +}

...

> +static int devm_ad7625_ddr_regulator_setup(struct device *dev,
> +					   struct ad7625_state *st)
> +{
> +	int ret;
> +
> +	ret = devm_regulator_bulk_get_enable(dev, st->info->num_supplies,
> +					     st->info->supplies);
> +	if (ret)
> +		return dev_err_probe(dev, ret, "failed to enable supplies\n");
> +
> +	ret = devm_regulator_get_enable_read_voltage(dev, "ref");
> +	if (ret < 0 && ret != -ENODEV)

Too many ENODEV checks, see below how to deduplicate.

> +		return dev_err_probe(dev, ret, "failed to get REF voltage\n");
> +	if (!ret)
> +		return dev_err_probe(dev, -EINVAL, "REF voltage must not be 0\n");

	ret = devm_regulator_get_enable_read_voltage(dev, "ref");
	if (ret == -ENODEV)
		/* Without ref-supply the internal reference is used. */
		st->vref_mv = AD7625_INTERNAL_REF_MV;
	else if (ret < 0)
		return dev_err_probe(dev, ret, "failed to get REF voltage\n");
	else if (ret == 0)
		return dev_err_probe(dev, -EINVAL, "REF voltage must not be 0\n");

	st->vref_mv = ret / (MICRO / MILLI);

> +	/* Without ref-supply the internal reference is used. */
> +	st->vref_mv = ret == -ENODEV ? AD7625_INTERNAL_REF_MV : ret / (MICRO / MILLI);
> +
> +	return 0;
> +}

...

> -		/*
> -		 * If neither sampling mode is available, then report an error,
> -		 * since the other modes are not useful defaults.
> -		 */
> -		if (st->can_wide_bandwidth) {
> -			ret = ad7960_set_mode(st, AD7960_MODE_WIDE_BANDWIDTH,
> -					      st->have_refin, st->vref_mv);
> -		} else if (st->can_narrow_bandwidth) {
> -			ret = ad7960_set_mode(st, AD7960_MODE_NARROW_BANDWIDTH,
> -					      st->have_refin, st->vref_mv);
> -		} else {
> -			return dev_err_probe(dev, -EINVAL,
> -				"couldn't set device to wide or narrow bandwidth modes\n");
> -		}
> -
> -		if (ret)
> -			return dev_err_probe(dev, -EINVAL,
> -					     "failed to set EN pins\n");

AFAICS the above can be moved to a helper in the separate patch and moved as a
couple of lines only in this patch.

...

> +	if (st->info->ddr_timing) {
> +		ret = iio_backend_num_lanes_set(st->back, st->two_lanes ? 2 : 1);
> +		/* Without the op, a backend can only be left on one lane. */
> +		if (ret && (ret != -EOPNOTSUPP || st->two_lanes))
> +			return dev_err_probe(dev, ret, "failed to set the number of lanes\n");
> +	}

Same problem, too many (hidden) branches makes code unreadable.

	if (st->info->ddr_timing) {
		if (st->two_lanes) {
			ret = iio_backend_num_lanes_set(st->back, 2);
		} else {
			ret = iio_backend_num_lanes_set(st->back, 1);
			/* Without the op, a backend can only be left on one lane. */
			if (ret == -EOPNOTSUPP)
				ret = 0;
		}
		if (ret)
			return dev_err_probe(dev, ret, "failed to set the number of lanes\n");
	}

...

> +	/* The DDR parts reject rates above the clock of the PWMs. */
> +	if (st->info->ddr_timing)
> +		default_sample_freq = min_t(u32, default_sample_freq, st->step.clk_rate_hz);

No min_t().

-- 
With Best Regards,
Andy Shevchenko



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

* Re: [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x
  2026-11-04 13:21 ` [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x Nicolae-Daniel Deaconescu
  2026-11-04 13:28   ` sashiko-bot
@ 2026-11-05  0:23   ` Conor Dooley
  1 sibling, 0 replies; 10+ messages in thread
From: Conor Dooley @ 2026-11-05  0:23 UTC (permalink / raw)
  To: Nicolae-Daniel Deaconescu
  Cc: Jonathan Cameron, Uwe Kleine-König, Conor Dooley,
	Krzysztof Kozlowski, Rob Herring, David Lechner, Nuno Sá,
	Andy Shevchenko, Michael Hennerich, Trevor Gamblin, Joshua Crofts,
	Sergiu Cuciurean, linux-iio, devicetree, linux-pwm, linux-doc,
	linux-kernel

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

On Wed, Nov 04, 2026 at 03:21:42PM +0200, Nicolae-Daniel Deaconescu wrote:
> From: Sergiu Cuciurean <sergiu.cuciurean@analog.com>
> 
> Add the LTC2387-16, LTC2387-18, ADAQ23875, ADAQ23876 and ADAQ23878 to the
> binding. They are single channel differential ADCs like the AD762x and
> AD796x and are read the same way: CNV and CLK are driven by two PWMs and
> the LVDS data is captured by an AXI ADC. They differ in what is around the
> converter:
> 
> - Their output is double data rate on one or two lanes, selected by the
>   TWOLANES pin, described by adi,use-two-lanes.
> - They have no EN pins, and no clocks property, because the PWM provider
>   reports the clock its waveforms are derived from. The second PWM is named
>   clk, as that is the pin it drives, where the AD762x and AD796x name it
>   clk_gate.
> - The supplies are VDD, VDDL and OVDD on the LTC2387, and VDD, VIO, VS+ and
>   VS- on the ADAQ modules.
> - The ADAQ modules have a fully differential amplifier in front of the ADC.
>   Its gain is fixed on the ADAQ23875 and set by pin strapping on the
>   ADAQ23876 and ADAQ23878, described by adi,gain-milli.
> 
> The properties and supplies of the two groups of parts are made exclusive.
> The gain is described in the devicetree as the pin strapping can't be read
> from the part. There is no fallback compatible, as a driver for one of the
> existing parts would read these wrongly: their output is double data rate
> and has other timing limits.
> 
> 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>
> ---
>  .../bindings/iio/adc/adi,ad7625.yaml          | 193 +++++++++++++++++-
>  1 file changed, 184 insertions(+), 9 deletions(-)
> 
> diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
> index 8848562af28f..dbdded88236c 100644
> --- a/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
> +++ b/Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
> @@ -4,7 +4,7 @@
>  $id: http://devicetree.org/schemas/iio/adc/adi,ad7625.yaml#
>  $schema: http://devicetree.org/meta-schemas/core.yaml#
>  
> -title: Analog Devices Fast PulSAR Analog to Digital Converters
> +title: Analog Devices Fast PulSAR, LTC2387 and ADAQ2387x Analog to Digital Converters
>  
>  maintainers:
>    - Michael Hennerich <Michael.Hennerich@analog.com>
> @@ -17,6 +17,11 @@ description: |
>    * https://www.analog.com/en/products/ad7626.html
>    * https://www.analog.com/en/products/ad7960.html
>    * https://www.analog.com/en/products/ad7961.html
> +  * https://www.analog.com/en/products/ltc2387-16.html
> +  * https://www.analog.com/en/products/ltc2387-18.html
> +  * https://www.analog.com/en/products/adaq23875.html
> +  * https://www.analog.com/en/products/adaq23876.html
> +  * https://www.analog.com/en/products/adaq23878.html
>  
>  properties:
>    compatible:
> @@ -25,14 +30,37 @@ properties:
>        - adi,ad7626
>        - adi,ad7960
>        - adi,ad7961
> +      - adi,adaq23875
> +      - adi,adaq23876
> +      - adi,adaq23878
> +      - adi,ltc2387-16
> +      - adi,ltc2387-18
>  
>    vdd1-supply: true
>    vdd2-supply: true
>    vio-supply: true
>  
> +  vdd-supply:
> +    description: 5V analog power supply (VDD), LTC2387 and ADAQ parts only.
> +
> +  vddl-supply:
> +    description: 2.5V analog power supply (VDDL), LTC2387 parts only.
> +
> +  ovdd-supply:
> +    description: 2.5V output power supply (OVDD), LTC2387 parts only.
> +
> +  vs-p-supply:
> +    description: Amplifier positive supply (VS+), ADAQ parts only.
> +
> +  vs-n-supply:
> +    description:
> +      Amplifier negative supply (VS-), ADAQ parts only. Leave it out if VS-
> +      is tied to ground.
> +
>    ref-supply:
>      description:
> -      Voltage regulator for the external reference voltage (REF).
> +      Voltage regulator for the external reference voltage (REF, or REFBUF
> +      on the LTC2387 and ADAQ2387x).
>  
>    refin-supply:
>      description:
> @@ -40,18 +68,23 @@ properties:
>  
>    clocks:
>      description:
> -      The clock connected to the CLK pins, gated by the clk_gate PWM.
> +      The clock connected to the CLK pins, gated by the clk_gate PWM. Not
> +      described for the LTC2387 and ADAQ parts.
>      maxItems: 1
>  
>    pwms:
>      items:
>        - description: PWM connected to the CNV input on the ADC.
> -      - description: PWM that gates the clock connected to the ADC's CLK input.
> +      - description:
> +          PWM that gates the clock connected to the ADC's CLK input, so that
> +          it sees the burst of pulses for each conversion. It is named
> +          clk_gate for the AD762x and AD796x, and clk for the LTC2387 and
> +          ADAQ2387x.
>  
>    pwm-names:
>      items:
>        - const: cnv
> -      - const: clk_gate
> +      - enum: [clk_gate, clk]
>  
>    io-backends:
>      description:
> @@ -91,6 +124,23 @@ properties:
>        Indicates if EN3 is hard-wired to the high state. If neither this
>        nor en3-gpios are present, then EN3 is hard-wired low.
>  
> +  adi,use-two-lanes:
> +    $ref: /schemas/types.yaml#/definitions/flag
> +    description:
> +      Describes whether the TWOLANES pin of an LTC2387 or ADAQ part is set
> +      high, selecting two-lane LVDS output, or low, selecting one-lane
> +      output (the default).
> +
> +  adi,gain-milli:
> +    description:
> +      Gain of the fully-differential amplifier that the ADAQ 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. Not allowed on the ADAQ23875,
> +      whose gain is fixed at 2.
> +    $ref: /schemas/types.yaml#/definitions/uint16
> +    enum: [370, 730, 870, 1380, 2250]
> +
>    en0-gpios:
>      description:
>        Configurable EN0 pin.
> @@ -109,10 +159,6 @@ properties:
>  
>  required:
>    - compatible
> -  - vdd1-supply
> -  - vdd2-supply
> -  - vio-supply
> -  - clocks
>    - pwms
>    - pwm-names
>    - io-backends
> @@ -157,6 +203,111 @@ allOf:
>          - required: [ref-supply]
>          - required: [refin-supply]
>  

> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,ad7625
> +              - adi,ad7626
> +              - adi,ad7960
> +              - adi,ad7961
> +    then:
> +      properties:
> +        pwm-names:
> +          items:
> +            - const: cnv
> +            - const: clk_gate
> +        vdd-supply: false
> +        vddl-supply: false
> +        ovdd-supply: false
> +        vs-p-supply: false
> +        vs-n-supply: false
> +        adi,use-two-lanes: false
> +        adi,gain-milli: false
> +      required:
> +        - vdd1-supply
> +        - vdd2-supply
> +        - vio-supply
> +        - clocks
> +    else:
> +      # The LTC2387 and ADAQ parts have no EN pins and no clock to describe.
> +      properties:
> +        pwm-names:
> +          items:
> +            - const: cnv
> +            - const: clk
> +        vdd1-supply: false
> +        vdd2-supply: false
> +        clocks: false
> +        # REFIN is the bandgap output and the input of the reference buffer.
> +        # An external reference goes to REFBUF, through ref-supply.
> +        refin-supply: false
> +        en0-gpios: false
> +        en1-gpios: false
> +        en2-gpios: false
> +        en3-gpios: false
> +        adi,en0-always-on: false
> +        adi,en1-always-on: false
> +        adi,en2-always-on: false
> +        adi,en3-always-on: false
> +        adi,no-dco: false
> +      required:
> +        - vdd-supply

Honestly, when the devices are this different, I don't think having a
shared binding is worth it. Shared driver perhaps, but when so many
properties don't apply the things should be kept in different binding
files I think, this is just confusingly different IMO.

Thanks for sorting out the clocks thing though.

Conor.

> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,ltc2387-16
> +              - adi,ltc2387-18
> +    then:
> +      properties:
> +        vio-supply: false
> +        vs-p-supply: false
> +        vs-n-supply: false
> +        adi,gain-milli: false
> +      required:
> +        - vddl-supply
> +        - ovdd-supply
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,adaq23875
> +              - adi,adaq23876
> +              - adi,adaq23878
> +    then:
> +      properties:
> +        vddl-supply: false
> +        ovdd-supply: false
> +      required:
> +        - vio-supply
> +        - vs-p-supply
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            const: adi,adaq23875
> +    then:
> +      properties:
> +        adi,gain-milli: false
> +
> +  - if:
> +      properties:
> +        compatible:
> +          contains:
> +            enum:
> +              - adi,adaq23876
> +              - adi,adaq23878
> +    then:
> +      required:
> +        - adi,gain-milli
> +
>  additionalProperties: false
>  
>  examples:
> @@ -174,3 +325,27 @@ examples:
>          en0-gpios = <&gpio0 86 GPIO_ACTIVE_HIGH>;
>          en1-gpios = <&gpio0 87 GPIO_ACTIVE_HIGH>;
>      };
> +  - |
> +    adc {
> +        compatible = "adi,ltc2387-16";
> +        io-backends = <&axi_adc>;
> +        ovdd-supply = <&supply_2_5V>;
> +        pwms = <&axi_pwm_gen 0 0 0>, <&axi_pwm_gen 1 0 0>;
> +        pwm-names = "cnv", "clk";
> +        ref-supply = <&supply_4_096V>;
> +        vdd-supply = <&supply_5V>;
> +        vddl-supply = <&supply_2_5V>;
> +    };
> +  - |
> +    adc {
> +        compatible = "adi,adaq23878";
> +        io-backends = <&axi_adc>;
> +        pwms = <&axi_pwm_gen 0 0 0>, <&axi_pwm_gen 1 0 0>;
> +        pwm-names = "cnv", "clk";
> +        vdd-supply = <&supply_5V>;
> +        vio-supply = <&supply_2_5V>;
> +        vs-n-supply = <&supply_0V>;
> +        vs-p-supply = <&supply_5V>;
> +        adi,gain-milli = /bits/ 16 <870>;
> +        adi,use-two-lanes;
> +    };
> -- 
> 2.34.1
> 

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

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

end of thread, other threads:[~2026-11-05  0:23 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-04 13:21 [PATCH v2 0/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 1/5] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
2026-11-04 19:51   ` Andy Shevchenko
2026-11-04 13:21 ` [PATCH v2 2/5] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 3/5] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
2026-11-04 13:21 ` [PATCH v2 4/5] dt-bindings: iio: adc: adi,ad7625: add LTC2387 and ADAQ2387x Nicolae-Daniel Deaconescu
2026-11-04 13:28   ` sashiko-bot
2026-11-05  0:23   ` Conor Dooley
2026-11-04 13:21 ` [PATCH v2 5/5] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-04 20:22   ` Andy Shevchenko

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.