* [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support
@ 2026-11-06 16:05 Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 1/7] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
` (6 more replies)
0 siblings, 7 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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. v1 added them as a separate ltc2387 driver. After
receiving feedback, v2 refactored the driver into the existing ad7625 and
modified the pwm backend for clear layering. This version gives the parts
a binding of their own.
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 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. It was pointed
out in review 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.
Patch 3 fixes problems of the existing sampling frequency code that the new
parts would also run into, patch 4 uses the standard unit names, and patch 5
only moves code, so that patch 7 adds the new parts without
touching more of the old code than it has to.
Changes since v2:
- Split the binding into adi,ltc2387.yaml and left adi,ad7625.yaml as it
was.
- Moved the EN pin setup into a helper in a patch of its own.
- Made the Kconfig prompt, the comment at the top of the driver and
MODULE_DESCRIPTION() generic, and sorted the part lists by part number.
- Used HZ_PER_MHZ for the maximum rate, and a default gain of
1 * MILLI instead of a gain without a unit.
- Simplified the gain, reference supply and lane count handling, did not nest
the rounding checks and replaced min_t() with min().
- Put Context before Returns in the kernel-doc of the new PWM function,
dropped a redundant check and named the rate clk_rate_Hz.
- Used the standard unit names and macros (vref_mV, HZ_PER_MHZ) for the
existing code in a patch of its own.
- Included property.h and types.h, and ordered the declarations of the
new code in reverse christmas tree order.
Changes in v2 (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/
Link to v2: https://lore.kernel.org/all/20261104132143.215581-1-Nicolae-daniel.Deaconescu@analog.com/
Nicolae-Daniel Deaconescu (5):
pwm: core: add pwm_get_waveform_step_might_sleep()
pwm: axi-pwmgen: report the waveform step
iio: adc: ad7625: fix sampling frequency handling
iio: adc: ad7625: use standard unit names
iio: adc: ad7625: move initial mode setup
Sergiu Cuciurean (2):
dt-bindings: iio: adc: add adi,ltc2387
iio: adc: ad7625: add LTC2387 and ADAQ2387x support
.../bindings/iio/adc/adi,ltc2387.yaml | 169 +++++++
Documentation/iio/ad7625.rst | 34 +-
MAINTAINERS | 3 +
drivers/iio/adc/Kconfig | 7 +-
drivers/iio/adc/ad7625.c | 470 ++++++++++++++++--
drivers/pwm/core.c | 65 +++
drivers/pwm/pwm-axi-pwmgen.c | 13 +
include/linux/pwm.h | 34 ++
8 files changed, 739 insertions(+), 56 deletions(-)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2387.yaml
base-commit: 43ffda3c7f8456bbe641ae8249a077a361b97880
--
2.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 1/7] pwm: core: add pwm_get_waveform_step_might_sleep()
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 2/7] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
` (5 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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 must meet minimum times, or place an edge relative to the
cycles of a clock a PWM gates, can't do that with the waveform API alone.
Rounding functions round down, so a minimum needs margin. Margin size is
the PWM step, known only to the provider.
Let the provider report the rate of the clock its timing derives from, and
the clock cycles per step. A consumer then works in whole steps, converts
to nanoseconds rounding up, and gets that waveform back from the hardware.
Without this, a consumer reads the rate from a clock it gets on its own.
That describes a relationship the hardware doesn't have, and breaks if the
PWM divides its clock.
Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
drivers/pwm/core.c | 65 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm.h | 34 ++++++++++++++++++++++++
2 files changed, 99 insertions(+)
diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c
index 2a050ff4608b..c8f9a53af770 100644
--- a/drivers/pwm/core.c
+++ b/drivers/pwm/core.c
@@ -14,6 +14,7 @@
#include <linux/of.h>
#include <linux/pwm.h>
#include <linux/list.h>
+#include <linux/math64.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
@@ -325,6 +326,67 @@ 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 that is asked for in whole
+ * steps, and is within the range of the hardware, 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.
+ *
+ * Context: May sleep.
+ * Returns: 0 on success, -EOPNOTSUPP if the provider doesn't report it or a
+ * negative errno
+ */
+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;
+
+ might_sleep();
+
+ if (!pwmchip_supports_waveform(chip) || !ops->get_waveform_step)
+ return -EOPNOTSUPP;
+
+ 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_rate_Hz > mul_u32_u32(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 +1772,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..c52f4c9153c2 100644
--- a/include/linux/pwm.h
+++ b/include/linux/pwm.h
@@ -76,6 +76,34 @@ 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. A
+ * waveform that is asked for in whole steps, and is within the range of the
+ * hardware, is not changed by rounding.
+ *
+ * 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 +315,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 +336,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 +427,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.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 2/7] pwm: axi-pwmgen: report the waveform step
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 1/7] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 3/7] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
` (4 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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..6b15244efabf 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.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 3/7] iio: adc: ad7625: fix sampling frequency handling
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 1/7] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 2/7] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 4/7] iio: adc: ad7625: use standard unit names Nicolae-Daniel Deaconescu
` (3 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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 sampling frequency handling issues:
- Fix issue where writing 0 to sampling_frequency caused a division by
zero. Return -EINVAL.
- Fix issue where a waveform rounded up by the PWM went unreported.
pwm_round_waveform_might_sleep() returns 1 in that case and
ad7625_set_sampling_freq() passed it on, so the sampling_frequency write
returned 1 and the new waveforms were dropped. Return -EINVAL.
- Fix issue where probe() continued after failing to set the initial
sampling frequency. The dev_err_probe() result was not returned, leaving
the PWMs 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 | 15 +++++++++++----
1 file changed, 11 insertions(+), 4 deletions(-)
diff --git a/drivers/iio/adc/ad7625.c b/drivers/iio/adc/ad7625.c
index e73a5c9e7f0b..ade2f2368bd8 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);
@@ -188,8 +191,10 @@ static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq)
cnv_wf.duty_length_ns = st->info->timing_spec->conv_high_ns;
ret = pwm_round_waveform_might_sleep(st->cnv_pwm, &cnv_wf);
- if (ret)
+ if (ret < 0)
return ret;
+ if (ret)
+ return -EINVAL;
/*
* Set up the burst signal for transferring data. period and
@@ -205,8 +210,10 @@ static int ad7625_set_sampling_freq(struct ad7625_state *st, u32 freq)
clk_gate_wf.duty_offset_ns = st->info->timing_spec->conv_msb_ns;
ret = pwm_round_waveform_might_sleep(st->clk_gate_pwm, &clk_gate_wf);
- if (ret)
+ if (ret < 0)
return ret;
+ if (ret)
+ return -EINVAL;
st->cnv_wf = cnv_wf;
st->clk_gate_wf = clk_gate_wf;
@@ -646,8 +653,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.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 4/7] iio: adc: ad7625: use standard unit names
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
` (2 preceding siblings ...)
2026-11-06 16:05 ` [PATCH v3 3/7] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 5/7] iio: adc: ad7625: move initial mode setup Nicolae-Daniel Deaconescu
` (2 subsequent siblings)
6 siblings, 0 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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
Use standard unit spelling and macros in existing code:
- Rename vref_mv to vref_mV and ref_mv to ref_mV, spelling the voltage unit
as mV.
- Use HZ_PER_MHZ instead of MEGA for frequencies.
- Use MICRO / MILLI instead of 1000 to convert the reference voltage from
uV to mV.
No behaviour change.
Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
drivers/iio/adc/ad7625.c | 46 ++++++++++++++++++++--------------------
1 file changed, 23 insertions(+), 23 deletions(-)
diff --git a/drivers/iio/adc/ad7625.c b/drivers/iio/adc/ad7625.c
index ade2f2368bd8..e699a7023b02 100644
--- a/drivers/iio/adc/ad7625.c
+++ b/drivers/iio/adc/ad7625.c
@@ -24,7 +24,7 @@
#include <linux/units.h>
#define AD7625_INTERNAL_REF_MV 4096
-#define AD7960_MAX_NBW_FREQ (2 * MEGA)
+#define AD7960_MAX_NBW_FREQ (2 * HZ_PER_MHZ)
struct ad7625_timing_spec {
/* Max conversion high time (t_{CNVH}). */
@@ -76,7 +76,7 @@ struct ad7625_state {
*/
struct pwm_waveform clk_gate_wf;
struct pwm_waveform cnv_wf;
- unsigned int vref_mv;
+ unsigned int vref_mV;
u32 sampling_freq_hz;
/*
* Optional GPIOs for controlling device state. EN0 and EN1
@@ -126,7 +126,7 @@ static const struct ad7625_timing_spec ad7960_timing_spec = {
static const struct ad7625_chip_info ad7625_chip_info = {
.name = "ad7625",
- .max_sample_freq_hz = 6 * MEGA,
+ .max_sample_freq_hz = 6 * HZ_PER_MHZ,
.timing_spec = &ad7625_timing_spec,
.chan_spec = AD7625_CHAN_SPEC(16),
.has_power_down_state = false,
@@ -136,7 +136,7 @@ static const struct ad7625_chip_info ad7625_chip_info = {
static const struct ad7625_chip_info ad7626_chip_info = {
.name = "ad7626",
- .max_sample_freq_hz = 10 * MEGA,
+ .max_sample_freq_hz = 10 * HZ_PER_MHZ,
.timing_spec = &ad7626_timing_spec,
.chan_spec = AD7625_CHAN_SPEC(16),
.has_power_down_state = true,
@@ -146,7 +146,7 @@ static const struct ad7625_chip_info ad7626_chip_info = {
static const struct ad7625_chip_info ad7960_chip_info = {
.name = "ad7960",
- .max_sample_freq_hz = 5 * MEGA,
+ .max_sample_freq_hz = 5 * HZ_PER_MHZ,
.timing_spec = &ad7960_timing_spec,
.chan_spec = AD7625_CHAN_SPEC(18),
.has_power_down_state = true,
@@ -156,7 +156,7 @@ static const struct ad7625_chip_info ad7960_chip_info = {
static const struct ad7625_chip_info ad7961_chip_info = {
.name = "ad7961",
- .max_sample_freq_hz = 5 * MEGA,
+ .max_sample_freq_hz = 5 * HZ_PER_MHZ,
.timing_spec = &ad7960_timing_spec,
.chan_spec = AD7625_CHAN_SPEC(16),
.has_power_down_state = true,
@@ -239,7 +239,7 @@ static int ad7625_read_raw(struct iio_dev *indio_dev,
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
- *val = st->vref_mv;
+ *val = st->vref_mV;
*val2 = chan->scan_type.realbits - 1;
return IIO_VAL_FRACTIONAL_LOG2;
@@ -346,12 +346,12 @@ static int ad7625_parse_mode(struct device *dev, struct ad7625_state *st,
/* Set EN1 and EN0 based on reference voltage source */
static void ad7625_set_en_gpios_for_vref(struct ad7625_state *st,
- bool have_refin, int ref_mv)
+ bool have_refin, int ref_mV)
{
- if (have_refin || ref_mv == 5000) {
+ if (have_refin || ref_mV == 5000) {
gpiod_set_value_cansleep(st->en_gpios[1], 0);
gpiod_set_value_cansleep(st->en_gpios[0], 1);
- } else if (ref_mv == 4096) {
+ } else if (ref_mV == 4096) {
gpiod_set_value_cansleep(st->en_gpios[1], 1);
gpiod_set_value_cansleep(st->en_gpios[0], 0);
} else {
@@ -365,7 +365,7 @@ static void ad7625_set_en_gpios_for_vref(struct ad7625_state *st,
}
static int ad7960_set_mode(struct ad7625_state *st, enum ad7960_mode mode,
- bool have_refin, int ref_mv)
+ bool have_refin, int ref_mV)
{
switch (mode) {
case AD7960_MODE_POWER_DOWN:
@@ -392,7 +392,7 @@ static int ad7960_set_mode(struct ad7625_state *st, enum ad7960_mode mode,
return -EINVAL;
gpiod_set_value_cansleep(st->en_gpios[2], 1);
- ad7625_set_en_gpios_for_vref(st, have_refin, ref_mv);
+ ad7625_set_en_gpios_for_vref(st, have_refin, ref_mV);
return 0;
@@ -401,7 +401,7 @@ static int ad7960_set_mode(struct ad7625_state *st, enum ad7960_mode mode,
return -EINVAL;
gpiod_set_value_cansleep(st->en_gpios[2], 0);
- ad7625_set_en_gpios_for_vref(st, have_refin, ref_mv);
+ ad7625_set_en_gpios_for_vref(st, have_refin, ref_mV);
return 0;
@@ -517,7 +517,7 @@ static int devm_ad7625_regulator_setup(struct device *dev,
struct ad7625_state *st)
{
static const char * const regulator_names[] = { "vio", "vdd2", "vdd1" };
- int ret, ref_mv;
+ int ret, ref_mV;
ret = devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(regulator_names),
regulator_names);
@@ -528,7 +528,7 @@ static int devm_ad7625_regulator_setup(struct device *dev,
if (ret < 0 && ret != -ENODEV)
return dev_err_probe(dev, ret, "failed to get REF voltage\n");
- ref_mv = ret == -ENODEV ? 0 : ret / 1000;
+ ref_mV = ret == -ENODEV ? 0 : ret / (MICRO / MILLI);
ret = devm_regulator_get_enable_optional(dev, "refin");
if (ret < 0 && ret != -ENODEV)
@@ -540,23 +540,23 @@ static int devm_ad7625_regulator_setup(struct device *dev,
return dev_err_probe(dev, -EINVAL,
"REFIN provided in unsupported mode\n");
- if (!st->info->has_internal_vref && !st->have_refin && !ref_mv)
+ if (!st->info->has_internal_vref && !st->have_refin && !ref_mV)
return dev_err_probe(dev, -EINVAL,
"Need either REFIN or REF\n");
- if (st->have_refin && ref_mv)
+ if (st->have_refin && ref_mV)
return dev_err_probe(dev, -EINVAL,
"cannot have both REFIN and REF supplies\n");
- if (ref_mv == 4096 && !st->can_ref_4v096)
+ if (ref_mV == 4096 && !st->can_ref_4v096)
return dev_err_probe(dev, -EINVAL,
"REF is 4.096V in unsupported mode\n");
- if (ref_mv == 5000 && !st->can_ref_5v)
+ if (ref_mV == 5000 && !st->can_ref_5v)
return dev_err_probe(dev, -EINVAL,
"REF is 5V in unsupported mode\n");
- st->vref_mv = ref_mv ?: AD7625_INTERNAL_REF_MV;
+ st->vref_mV = ref_mV ?: AD7625_INTERNAL_REF_MV;
return 0;
}
@@ -597,7 +597,7 @@ static int ad7625_probe(struct platform_device *pdev)
/* 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);
+ ad7625_set_en_gpios_for_vref(st, st->have_refin, st->vref_mV);
} else {
/*
* If neither sampling mode is available, then report an error,
@@ -605,10 +605,10 @@ static int ad7625_probe(struct platform_device *pdev)
*/
if (st->can_wide_bandwidth) {
ret = ad7960_set_mode(st, AD7960_MODE_WIDE_BANDWIDTH,
- st->have_refin, st->vref_mv);
+ 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);
+ st->have_refin, st->vref_mV);
} else {
return dev_err_probe(dev, -EINVAL,
"couldn't set device to wide or narrow bandwidth modes\n");
--
2.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 5/7] iio: adc: ad7625: move initial mode setup
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
` (3 preceding siblings ...)
2026-11-06 16:05 ` [PATCH v3 4/7] iio: adc: ad7625: use standard unit names Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 6/7] dt-bindings: iio: adc: add adi,ltc2387 Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 7/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
6 siblings, 0 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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
Move the EN pin mode setup from ad7625_probe() into
ad7625_set_initial_mode(). No behaviour change. A following patch adds
parts without EN pins that skip it.
Signed-off-by: Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
---
drivers/iio/adc/ad7625.c | 57 ++++++++++++++++++++++++----------------
1 file changed, 34 insertions(+), 23 deletions(-)
diff --git a/drivers/iio/adc/ad7625.c b/drivers/iio/adc/ad7625.c
index e699a7023b02..133c83df8a52 100644
--- a/drivers/iio/adc/ad7625.c
+++ b/drivers/iio/adc/ad7625.c
@@ -561,6 +561,37 @@ 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;
+
+ /* 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;
@@ -595,29 +626,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)
--
2.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 6/7] dt-bindings: iio: adc: add adi,ltc2387
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
` (4 preceding siblings ...)
2026-11-06 16:05 ` [PATCH v3 5/7] iio: adc: ad7625: move initial mode setup Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
2026-11-06 17:04 ` Conor Dooley
2026-11-06 16:05 ` [PATCH v3 7/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
6 siblings, 1 reply; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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 a binding for the LTC2387-16, LTC2387-18, ADAQ23875, ADAQ23876 and
ADAQ23878. They are single channel differential ADCs, read like the AD762x
and AD796x: two PWMs drive CNV and CLK, and an AXI ADC captures the LVDS
data. A separate binding fits, as few properties of those parts apply:
- Output is double data rate on one or two lanes, selected by the TWOLANES
pin, described by adi,use-two-lanes.
- No EN pins, no clocks property: the PWM provider reports the clock its
waveforms derive from. The second PWM is named clk, after the pin it
drives.
- Supplies are VDD, VDDL and OVDD on the LTC2387, VDD, VIO, VS+ and VS- on
the ADAQ modules.
- The ADAQ modules have a fully differential amplifier ahead of the ADC.
Gain is fixed on the ADAQ23875, pin strapped on the ADAQ23876 and
ADAQ23878, described by adi,gain-milli.
The devicetree describes gain, as the strapping can't be read from the
part. No fallback compatible: a driver for an AD762x or AD796x part would
read these wrongly, as their output is double data rate with other timing
limits.
Add the binding to the maintainers entry of the AD7625 driver.
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,ltc2387.yaml | 169 ++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 170 insertions(+)
create mode 100644 Documentation/devicetree/bindings/iio/adc/adi,ltc2387.yaml
diff --git a/Documentation/devicetree/bindings/iio/adc/adi,ltc2387.yaml b/Documentation/devicetree/bindings/iio/adc/adi,ltc2387.yaml
new file mode 100644
index 000000000000..f0f1071fd96d
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/adc/adi,ltc2387.yaml
@@ -0,0 +1,169 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/adc/adi,ltc2387.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices LTC2387 and ADAQ2387x Analog to Digital Converters
+
+maintainers:
+ - Nicolae-Daniel Deaconescu <Nicolae-daniel.Deaconescu@analog.com>
+ - Sergiu Cuciurean <sergiu.cuciurean@analog.com>
+
+description: |
+ Single channel differential 15 MSPS SAR analog to digital converters with an
+ LVDS interface, and the ADAQ uModule data acquisition systems that put a
+ fully differential amplifier in front of the LTC2387 die.
+
+ * https://www.analog.com/en/products/adaq23875.html
+ * https://www.analog.com/en/products/adaq23876.html
+ * https://www.analog.com/en/products/adaq23878.html
+ * https://www.analog.com/en/products/ltc2387-16.html
+ * https://www.analog.com/en/products/ltc2387-18.html
+
+properties:
+ compatible:
+ enum:
+ - adi,adaq23875
+ - adi,adaq23876
+ - adi,adaq23878
+ - adi,ltc2387-16
+ - adi,ltc2387-18
+
+ vdd-supply:
+ description: 5V analog power supply (VDD).
+
+ vddl-supply:
+ description: 2.5V analog power supply (VDDL), LTC2387 parts only.
+
+ ovdd-supply:
+ description: 2.5V output power supply (OVDD), LTC2387 parts only.
+
+ vio-supply:
+ description: 2.5V digital supply (VIO), ADAQ 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 on REFBUF. If it
+ is not present, the internal reference is used.
+
+ 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, so that
+ it sees the burst of pulses for each conversion.
+
+ pwm-names:
+ items:
+ - const: cnv
+ - const: clk
+
+ io-backends:
+ description:
+ The AXI ADC IP block connected to the DA+/- and DB+/- data lines and
+ the DCO+/- lines of the ADC.
+ maxItems: 1
+
+ adi,use-two-lanes:
+ $ref: /schemas/types.yaml#/definitions/flag
+ description:
+ Describes whether the TWOLANES pin 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
+ 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, or
+ on the LTC2387.
+ $ref: /schemas/types.yaml#/definitions/uint16
+ enum: [370, 730, 870, 1380, 2250]
+
+required:
+ - compatible
+ - pwms
+ - pwm-names
+ - io-backends
+ - vdd-supply
+
+allOf:
+ - 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
+ else:
+ 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:
+ - |
+ 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-p-supply = <&supply_5V>;
+ adi,gain-milli = /bits/ 16 <870>;
+ adi,use-two-lanes;
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 52024397dc78..9d07f1f47c6f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1658,6 +1658,7 @@ S: Supported
W: https://ez.analog.com/linux-software-drivers
W: http://analogdevicesinc.github.io/hdl/projects/pulsar_lvds/index.html
F: Documentation/devicetree/bindings/iio/adc/adi,ad7625.yaml
+F: Documentation/devicetree/bindings/iio/adc/adi,ltc2387.yaml
F: Documentation/iio/ad7625.rst
F: drivers/iio/adc/ad7625.c
--
2.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v3 7/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
` (5 preceding siblings ...)
2026-11-06 16:05 ` [PATCH v3 6/7] dt-bindings: iio: adc: add adi,ltc2387 Nicolae-Daniel Deaconescu
@ 2026-11-06 16:05 ` Nicolae-Daniel Deaconescu
6 siblings, 0 replies; 9+ messages in thread
From: Nicolae-Daniel Deaconescu @ 2026-11-06 16:05 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. The same two PWMs and IIO backend read them. Chip data
and a few quirks cover the differences, 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 4 pulses long.
- The burst of one conversion starts at least t_FIRSTCLK after its CNV and
ends at most t_LASTCLK after the next CNV, so at the maximum rate it runs
into the next conversion period.
- No EN pins.
- The ADAQ parts divide the scale by the gain of their front end.
Changes:
- Add chip info for the five parts, maximum sampling frequency 15 MSPS,
plus OF and platform device ID entries.
- Add struct ad7625_ddr_timing with t_CNVH, t_FIRSTCLK and t_LASTCLK. The
chip info of the new parts points to it, and a non-NULL pointer marks a
part with DDR output.
- Add ad7625_set_sampling_freq_ddr(). Unlike the AD762x and AD796x, the
sampling period rises until the CLK burst fits t_LASTCLK, so the period
clamp doesn't limit the maximum rate to 10 MSPS.
- Build waveforms in whole PWM steps, read with
pwm_get_waveform_step_might_sleep() at probe and on each sampling
frequency change.
- Get the second PWM as "clk" for the new parts and "clk_gate" for the
existing ones. No reference clock for the new parts, as the step gives
the PWM clock rate.
- Add ad7625_parse_ddr() reading adi,use-two-lanes and adi,gain-milli. Gain
defaults to the fixed ADAQ23875 gain and to 1 for the LTC2387. It is
required for the ADAQ23876 and ADAQ23878, whose gain is pin strapped, and
must be a supported value.
- Divide the scale by the front end gain for the new parts.
- Enable the supplies of the new parts, with VS- of the ADAQ modules
optional as it can be tied to ground. Read the optional ref-supply and
use the internal reference when it is absent.
- Set the backend number of lanes for the new parts. Skipping is allowed
only if the backend can't set it and one lane is used.
- Limit the default sampling frequency of the new parts to the PWM clock
rate.
- Skip the EN pin mode setup for the new parts, as they have no EN pins.
- Include math64.h, minmax.h, property.h and types.h for the new code.
- Add the new parts, sorted by part number, to the Kconfig help and
Documentation/iio/ad7625.rst.
- Make the Kconfig prompt, the top comment of the driver and
MODULE_DESCRIPTION() generic, as the driver supports more than the
AD7625.
- Add 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 | 358 ++++++++++++++++++++++++++++++++++-
4 files changed, 392 insertions(+), 9 deletions(-)
diff --git a/Documentation/iio/ad7625.rst b/Documentation/iio/ad7625.rst
index 61761e3b75c3..d37420b76d4b 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>`_
+* `ADAQ23875 <https://www.analog.com/ADAQ23875>`_
+* `ADAQ23876 <https://www.analog.com/ADAQ23876>`_
+* `ADAQ23878 <https://www.analog.com/ADAQ23878>`_
+* `LTC2387-16 <https://www.analog.com/LTC2387-16>`_
+* `LTC2387-18 <https://www.analog.com/LTC2387-18>`_
-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 9d07f1f47c6f..7c419c285e0a 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..eb217d7dbba1 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 high speed LVDS 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
+ * ADAQ23875 and ADAQ23876 16-Bit, 15 MSPS uModule Data Acquisition
+ Systems
+ * ADAQ23878 18-Bit, 15 MSPS uModule Data Acquisition System
+ * LTC2387-16 16-Bit, 15 MSPS SAR Analog-to-Digital Converter
+ * LTC2387-18 18-Bit, 15 MSPS SAR Analog-to-Digital Converter
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 133c83df8a52..0e7a059df8a6 100644
--- a/drivers/iio/adc/ad7625.c
+++ b/drivers/iio/adc/ad7625.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: (GPL-2.0-only)
/*
- * Analog Devices Inc. AD7625 ADC driver
+ * Analog Devices Inc. high speed LVDS ADC driver
*
* Copyright 2024 Analog Devices Inc.
* Copyright 2024 BayLibre, SAS
@@ -17,10 +17,14 @@
#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/property.h>
#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
+#include <linux/types.h>
#include <linux/units.h>
#define AD7625_INTERNAL_REF_MV 4096
@@ -33,6 +37,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 +54,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 of the ADAQ front end * MILLI, 0 if the board has to provide it. */
+ u16 default_gain;
};
/* AD7625_CHAN_SPEC - Define a chan spec structure for a specific chip */
@@ -78,6 +101,11 @@ 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;
+ /* Gain of the front end * MILLI */
+ u16 gain;
+ bool two_lanes;
/*
* Optional GPIOs for controlling device state. EN0 and EN1
* determine voltage reference configuration and on/off state.
@@ -164,6 +192,68 @@ 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" };
+
+static const struct ad7625_chip_info ltc2387_16_chip_info = {
+ .name = "ltc2387-16",
+ .max_sample_freq_hz = 15 * HZ_PER_MHZ,
+ .chan_spec = AD7625_CHAN_SPEC(16),
+ .has_internal_vref = true,
+ .ddr_timing = <c2387_ddr_timing,
+ .supplies = ltc2387_supplies,
+ .num_supplies = ARRAY_SIZE(ltc2387_supplies),
+ .default_gain = 1 * MILLI,
+};
+
+static const struct ad7625_chip_info ltc2387_18_chip_info = {
+ .name = "ltc2387-18",
+ .max_sample_freq_hz = 15 * HZ_PER_MHZ,
+ .chan_spec = AD7625_CHAN_SPEC(18),
+ .has_internal_vref = true,
+ .ddr_timing = <c2387_ddr_timing,
+ .supplies = ltc2387_supplies,
+ .num_supplies = ARRAY_SIZE(ltc2387_supplies),
+ .default_gain = 1 * MILLI,
+};
+
+static const struct ad7625_chip_info adaq23875_chip_info = {
+ .name = "adaq23875",
+ .max_sample_freq_hz = 15 * HZ_PER_MHZ,
+ .chan_spec = AD7625_CHAN_SPEC(16),
+ .has_internal_vref = true,
+ .ddr_timing = <c2387_ddr_timing,
+ .supplies = adaq2387x_supplies,
+ .num_supplies = ARRAY_SIZE(adaq2387x_supplies),
+ .default_gain = 2 * MILLI,
+};
+
+static const struct ad7625_chip_info adaq23876_chip_info = {
+ .name = "adaq23876",
+ .max_sample_freq_hz = 15 * HZ_PER_MHZ,
+ .chan_spec = AD7625_CHAN_SPEC(16),
+ .has_internal_vref = true,
+ .ddr_timing = <c2387_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 * HZ_PER_MHZ,
+ .chan_spec = AD7625_CHAN_SPEC(18),
+ .has_internal_vref = true,
+ .ddr_timing = <c2387_ddr_timing,
+ .supplies = adaq2387x_supplies,
+ .num_supplies = ARRAY_SIZE(adaq2387x_supplies),
+};
+
enum ad7960_mode {
AD7960_MODE_POWER_DOWN,
AD7960_MODE_SNOOZE,
@@ -172,12 +262,152 @@ 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 mul_u64_u64_div_u64_roundup(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 = mul_u32_u32(steps, st->step.clk_cycles_per_step);
+
+ return mul_u64_u64_div_u64_roundup(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)
+{
+ u32 period, min_period, gate, offset, lastclk, cnvh, burst_end;
+ const struct ad7625_ddr_timing *t = st->info->ddr_timing;
+ struct pwm_waveform clk_gate_wf = { }, cnv_wf = { };
+ struct pwm_waveform_step step;
+ u32 cycles_per_step;
+ 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 = mul_u64_u64_div_u64(t->last_clk_max_ns, st->step.clk_rate_Hz,
+ 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 < 0)
+ return ret;
+ if (ret)
+ return -EINVAL;
+
+ ret = pwm_round_waveform_might_sleep(st->clk_gate_pwm, &clk_gate_wf);
+ if (ret < 0)
+ return ret;
+ if (ret)
+ return -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,
+ mul_u32_u32(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;
@@ -239,6 +469,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 << (chan->scan_type.realbits - 1);
+
+ return IIO_VAL_FRACTIONAL;
+ }
+
*val = st->vref_mV;
*val2 = chan->scan_type.realbits - 1;
@@ -421,6 +659,33 @@ static int ad7960_set_mode(struct ad7625_state *st, enum ad7960_mode mode,
}
}
+static const u16 ad7625_gains[] = { 370, 730, 870, 1380, 2250 };
+
+static int ad7625_parse_ddr(struct device *dev, struct ad7625_state *st)
+{
+ int ret;
+
+ 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 = st->info->default_gain;
+ if (st->gain)
+ return 0;
+
+ ret = device_property_read_u16(dev, "adi,gain-milli", &st->gain);
+ 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 (unsigned int i = 0; i < ARRAY_SIZE(ad7625_gains); i++)
+ if (st->gain == ad7625_gains[i])
+ return 0;
+
+ return dev_err_probe(dev, -EINVAL, "unsupported adi,gain-milli %u\n",
+ st->gain);
+}
+
static int ad7625_buffer_preenable(struct iio_dev *indio_dev)
{
struct ad7625_state *st = iio_priv(indio_dev);
@@ -464,8 +729,10 @@ static const struct iio_buffer_setup_ops ad7625_buffer_setup_ops = {
static int devm_ad7625_pwm_get(struct device *dev,
struct ad7625_state *st)
{
+ const char *clk_name;
struct clk *ref_clk;
u32 ref_clk_rate_hz;
+ int ret;
st->cnv_pwm = devm_pwm_get(dev, "cnv");
if (IS_ERR(st->cnv_pwm))
@@ -475,14 +742,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,
+ "failed to get usable steps of the pwms\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),
@@ -498,6 +779,37 @@ 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");
+
+ /* VS- of the ADAQ modules is left out if it is tied to ground. */
+ ret = devm_regulator_get_enable_optional(dev, "vs-n");
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(dev, ret, "failed to enable VS-\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;
+ return 0;
+ }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to get REF voltage\n");
+ if (ret == 0)
+ return dev_err_probe(dev, -EINVAL, "REF voltage must not be 0\n");
+
+ st->vref_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
@@ -519,6 +831,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)
@@ -565,6 +880,10 @@ 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);
@@ -614,7 +933,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);
@@ -649,6 +970,19 @@ static int ad7625_probe(struct platform_device *pdev)
if (ret)
return ret;
+ 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");
+ }
+
ret = devm_iio_backend_enable(dev, st->back);
if (ret)
return ret;
@@ -662,6 +996,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(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,
@@ -675,6 +1013,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 = <c2387_16_chip_info },
+ { .compatible = "adi,ltc2387-18", .data = <c2387_18_chip_info },
{ }
};
MODULE_DEVICE_TABLE(of, ad7625_of_match);
@@ -684,6 +1027,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)<c2387_16_chip_info },
+ { .name = "ltc2387-18", .driver_data = (kernel_ulong_t)<c2387_18_chip_info },
{ }
};
MODULE_DEVICE_TABLE(platform, ad7625_device_ids);
@@ -699,6 +1047,6 @@ static struct platform_driver ad7625_driver = {
module_platform_driver(ad7625_driver);
MODULE_AUTHOR("Trevor Gamblin <tgamblin@baylibre.com>");
-MODULE_DESCRIPTION("Analog Devices AD7625 ADC");
+MODULE_DESCRIPTION("Analog Devices high speed LVDS ADC");
MODULE_LICENSE("GPL");
MODULE_IMPORT_NS("IIO_BACKEND");
--
2.56.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v3 6/7] dt-bindings: iio: adc: add adi,ltc2387
2026-11-06 16:05 ` [PATCH v3 6/7] dt-bindings: iio: adc: add adi,ltc2387 Nicolae-Daniel Deaconescu
@ 2026-11-06 17:04 ` Conor Dooley
0 siblings, 0 replies; 9+ messages in thread
From: Conor Dooley @ 2026-11-06 17:04 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: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-11-06 17:04 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-06 16:05 [PATCH v3 0/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 1/7] pwm: core: add pwm_get_waveform_step_might_sleep() Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 2/7] pwm: axi-pwmgen: report the waveform step Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 3/7] iio: adc: ad7625: fix sampling frequency handling Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 4/7] iio: adc: ad7625: use standard unit names Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 5/7] iio: adc: ad7625: move initial mode setup Nicolae-Daniel Deaconescu
2026-11-06 16:05 ` [PATCH v3 6/7] dt-bindings: iio: adc: add adi,ltc2387 Nicolae-Daniel Deaconescu
2026-11-06 17:04 ` Conor Dooley
2026-11-06 16:05 ` [PATCH v3 7/7] iio: adc: ad7625: add LTC2387 and ADAQ2387x support Nicolae-Daniel Deaconescu
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®