From: Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org>
To: linux-iio@vger.kernel.org, linux-kernel@vger.kernel.org,
linux@analog.com, devicetree@vger.kernel.org,
linux-pwm@vger.kernel.org, linux-doc@vger.kernel.org,
Jonathan Cameron <jonathan.cameron@oss.qualcomm.com>
Cc: "Olivier Moysan" <olivier.moysan@foss.st.com>,
"Jonathan Cameron" <jic23@kernel.org>,
"David Lechner" <dlechner@baylibre.com>,
"Andy Shevchenko" <andy@kernel.org>,
"Michael Hennerich" <Michael.Hennerich@analog.com>,
"Rob Herring" <robh@kernel.org>,
"Krzysztof Kozlowski" <krzk+dt@kernel.org>,
"Conor Dooley" <conor+dt@kernel.org>,
"Uwe Kleine-König" <ukleinek@kernel.org>,
"Jonathan Corbet" <corbet@lwn.net>,
"Shuah Khan" <skhan@linuxfoundation.org>,
"Randy Dunlap" <rdunlap@infradead.org>,
"Rodrigo Alencar" <rodrigo.alencar@analog.com>
Subject: [PATCH v2 07/10] iio: frequency: ad9910: support drctl control
Date: Thu, 22 Oct 2026 12:19:05 -0300 [thread overview]
Message-ID: <20261022-ad9910-backend-support-v2-7-0ff7f0bff326@analog.com> (raw)
In-Reply-To: <20261022-ad9910-backend-support-v2-0-0ff7f0bff326@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
The DRCTL pin controls the slope direction of the AD9910 digital ramp
generator (DRG). When dwell is enabled at a ramp limit, the DRG holds
that limit until the next DRCTL transition, so the sweep pattern is
defined by the waveform applied to that pin, which can be generated by
a PWM.
Get the optional "drctl" PWM and expose the integration_time attribute
in the DRG ramp up and ramp down channels, which sets the time interval
assigned to each ramp direction while the sweep parameter is accumulating.
The DRCTL waveform is described with the PWM waveform API, using the duty
offset when DRCTL needs to start low. The PWM is disabled when the
resulting period is zero. The waveform is rounded by the PWM provider
before being applied, so that integration_time reports the intervals
achieved by it. Writing to integration_time fails with -EOPNOTSUPP when
no DRCTL PWM is available. As the waveform API has no stubs, the driver
now depends on PWM.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/Kconfig | 1 +
drivers/iio/frequency/ad9910.c | 143 +++++++++++++++++++++++++++++++++++++++--
2 files changed, 138 insertions(+), 6 deletions(-)
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index f7f5c3cce005..591476f23ace 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -29,6 +29,7 @@ config AD9910
tristate "Analog Devices AD9910 Direct Digital Synthesizer"
depends on SPI
depends on GPIOLIB
+ depends on PWM
select CRC32
select FW_LOADER
select FW_UPLOAD
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index dc13986dff3d..1de02017f78c 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -21,6 +21,7 @@
#include <linux/math64.h>
#include <linux/module.h>
#include <linux/property.h>
+#include <linux/pwm.h>
#include <linux/regulator/consumer.h>
#include <linux/reset.h>
#include <linux/seq_file.h>
@@ -324,12 +325,16 @@ union ad9910_reg {
struct ad9910_state {
struct spi_device *spi;
struct clk *refclk;
+ struct pwm_device *drctl_pwm;
struct fw_upload *ram_fwu;
struct gpio_desc *gpio_pwdown;
struct gpio_desc *gpio_update;
struct gpio_descs *gpio_profile;
+ s64 ramp_up_time_ns;
+ s64 ramp_down_time_ns;
+
/* cached registers */
union ad9910_reg reg[AD9910_REG_NUM_CACHED];
@@ -609,6 +614,58 @@ static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable)
AD9910_CFR1_SW1_POWER_DOWN_MSK, 0, true);
}
+static int ad9910_drctl_sync(struct ad9910_state *st)
+{
+ struct pwm_waveform wf = { };
+ bool ndh, ndl;
+ int ret;
+
+ if (!st->drctl_pwm)
+ return 0;
+
+ ndh = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+ ndl = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+ st->reg[AD9910_REG_CFR2].val32);
+
+ if (!ndh && ndl) {
+ /* DRCTL low on the first half, so that it ramps down first */
+ wf.period_length_ns = st->ramp_down_time_ns;
+ wf.duty_offset_ns = st->ramp_down_time_ns / 2;
+ wf.duty_length_ns = wf.period_length_ns - wf.duty_offset_ns;
+ } else if (ndh && !ndl) {
+ wf.period_length_ns = st->ramp_up_time_ns;
+ wf.duty_length_ns = st->ramp_up_time_ns / 2;
+ } else if (!ndh && !ndl) {
+ wf.period_length_ns = st->ramp_up_time_ns + st->ramp_down_time_ns;
+ wf.duty_length_ns = st->ramp_up_time_ns;
+ } else {
+ wf.period_length_ns = st->ramp_up_time_ns + st->ramp_down_time_ns;
+ }
+
+ /* a zero period disables the PWM, nothing to round */
+ if (wf.period_length_ns) {
+ ret = pwm_round_waveform_might_sleep(st->drctl_pwm, &wf);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = pwm_set_waveform_might_sleep(st->drctl_pwm, &wf, false);
+ if (ret)
+ return ret;
+
+ if (!ndh && ndl) {
+ st->ramp_down_time_ns = wf.period_length_ns;
+ } else if (ndh && !ndl) {
+ st->ramp_up_time_ns = wf.period_length_ns;
+ } else if (!ndh && !ndl) {
+ st->ramp_up_time_ns = wf.duty_length_ns;
+ st->ramp_down_time_ns = wf.period_length_ns - wf.duty_length_ns;
+ }
+
+ return 0;
+}
+
static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
uintptr_t private,
const struct iio_chan_spec *chan,
@@ -675,6 +732,13 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
if (ret)
return ret;
}
+
+ /*
+ * DRCTL waveform depends on the dwell configuration, but the
+ * previous integration time values may not suit the new
+ * waveform, which should not prevent dwell_en from changing.
+ */
+ ad9910_drctl_sync(st);
break;
default:
return -EINVAL;
@@ -1054,7 +1118,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
.channel = AD9910_CHANNEL_DRG_RAMP_UP,
.address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_UP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
.ext_info = ad9910_drg_ramp_ext_info,
.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
},
@@ -1065,7 +1130,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
.address = AD9910_CHAN_IDX_DRG_FREQ_RAMP_DOWN,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
.ext_info = ad9910_drg_ramp_ext_info,
.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_FREQ],
},
@@ -1076,7 +1142,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
.channel = AD9910_CHANNEL_DRG_RAMP_UP,
.address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_UP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
.ext_info = ad9910_drg_ramp_ext_info,
.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
},
@@ -1087,7 +1154,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
.address = AD9910_CHAN_IDX_DRG_PHASE_RAMP_DOWN,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
.ext_info = ad9910_drg_ramp_ext_info,
.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_PHASE],
},
@@ -1098,7 +1166,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
.channel = AD9910_CHANNEL_DRG_RAMP_UP,
.address = AD9910_CHAN_IDX_DRG_AMP_RAMP_UP,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
.ext_info = ad9910_drg_ramp_ext_info,
.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
},
@@ -1109,7 +1178,8 @@ static const struct iio_chan_spec ad9910_channels[] = {
.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
.address = AD9910_CHAN_IDX_DRG_AMP_RAMP_DOWN,
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) |
- BIT(IIO_CHAN_INFO_SAMP_FREQ),
+ BIT(IIO_CHAN_INFO_SAMP_FREQ) |
+ BIT(IIO_CHAN_INFO_INT_TIME),
.ext_info = ad9910_drg_ramp_ext_info,
.parent = &ad9910_channels[AD9910_CHAN_IDX_DRG_AMP],
},
@@ -1337,6 +1407,17 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
default:
return -EINVAL;
}
+ case IIO_CHAN_INFO_INT_TIME:
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ iio_val_s64_decompose(st->ramp_up_time_ns, val, val2);
+ return IIO_VAL_DECIMAL64_NANO;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ iio_val_s64_decompose(st->ramp_down_time_ns, val, val2);
+ return IIO_VAL_DECIMAL64_NANO;
+ default:
+ return -EINVAL;
+ }
default:
return -EINVAL;
}
@@ -1637,6 +1718,34 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
return -EINVAL;
}
}
+ case IIO_CHAN_INFO_INT_TIME: {
+ s64 val64 = iio_val_s64_compose(val, val2);
+ s64 *time_ns, prev_ns;
+
+ if (!st->drctl_pwm)
+ return -EOPNOTSUPP;
+ if (val64 < 0)
+ return -EINVAL;
+
+ switch (chan->channel) {
+ case AD9910_CHANNEL_DRG_RAMP_UP:
+ time_ns = &st->ramp_up_time_ns;
+ break;
+ case AD9910_CHANNEL_DRG_RAMP_DOWN:
+ time_ns = &st->ramp_down_time_ns;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ prev_ns = *time_ns;
+ *time_ns = val64;
+ ret = ad9910_drctl_sync(st);
+ if (ret)
+ *time_ns = prev_ns;
+
+ return ret;
+ }
default:
return -EINVAL;
}
@@ -1671,6 +1780,8 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
return IIO_VAL_INT_PLUS_NANO;
case IIO_CHAN_INFO_OFFSET:
return IIO_VAL_DECIMAL64_MICRO;
+ case IIO_CHAN_INFO_INT_TIME:
+ return IIO_VAL_DECIMAL64_NANO;
default:
return -EINVAL;
}
@@ -1958,6 +2069,11 @@ static void ad9910_sw_powerdown_action(void *data)
ad9910_sw_powerdown_set(data, true);
}
+static void ad9910_drctl_disable_action(void *data)
+{
+ pwm_disable(data);
+}
+
static void ad9910_hw_powerdown_action(void *data)
{
struct ad9910_state *st = data;
@@ -2249,6 +2365,21 @@ static int ad9910_probe(struct spi_device *spi)
fsleep(AD9910_WAKEUP_DELAY_us);
+ st->drctl_pwm = devm_pwm_get(dev, "drctl");
+ if (IS_ERR(st->drctl_pwm)) {
+ ret = PTR_ERR(st->drctl_pwm);
+ if (ret != -ENOENT && ret != -ENODEV)
+ return dev_err_probe(dev, PTR_ERR(st->drctl_pwm),
+ "failed to get drctl pwm\n");
+ st->drctl_pwm = NULL;
+ } else {
+ ret = devm_add_action_or_reset(dev, ad9910_drctl_disable_action,
+ st->drctl_pwm);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to add drctl disable action\n");
+ }
+
ret = ad9910_parse_fw(st);
if (ret)
return ret;
--
2.43.0
next prev parent reply other threads:[~2026-10-22 15:19 UTC|newest]
Thread overview: 17+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 15:18 [PATCH v2 00/10] iio: frequency: ad9910: add IIO backend support Rodrigo Alencar via B4 Relay
2026-10-22 15:18 ` [PATCH v2 01/10] iio: frequency: ad9910: powerdown fix Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 02/10] iio: backend: add devm_iio_backend_get_optional() Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 03/10] dt-bindings: iio: frequency: add adi,ad9910-axi-backend Rodrigo Alencar via B4 Relay
2026-10-22 18:38 ` Conor Dooley
2026-10-23 11:15 ` Rodrigo Alencar
2026-10-23 17:39 ` Conor Dooley
2026-10-22 15:19 ` [PATCH v2 04/10] dt-bindings: iio: frequency: ad9910: add io-backends property Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 05/10] iio: frequency: ad9910: move channel indexes into a shared header Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 06/10] iio: frequency: ad9910: add axi backend driver Rodrigo Alencar via B4 Relay
2026-10-26 0:29 ` Jonathan Cameron
2026-10-22 15:19 ` Rodrigo Alencar via B4 Relay [this message]
2026-10-23 10:40 ` [PATCH v2 07/10] iio: frequency: ad9910: support drctl control Rodrigo Alencar
2026-10-23 14:48 ` Nuno Sá
2026-10-22 15:19 ` [PATCH v2 08/10] iio: frequency: ad9910: support iio backend Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 09/10] iio: ABI: add integration_time for output channels Rodrigo Alencar via B4 Relay
2026-10-22 15:19 ` [PATCH v2 10/10] docs: iio: ad9910: document DRCTL pin control Rodrigo Alencar via B4 Relay
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261022-ad9910-backend-support-v2-7-0ff7f0bff326@analog.com \
--to=devnull+rodrigo.alencar.analog.com@kernel.org \
--cc=Michael.Hennerich@analog.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=corbet@lwn.net \
--cc=devicetree@vger.kernel.org \
--cc=dlechner@baylibre.com \
--cc=jic23@kernel.org \
--cc=jonathan.cameron@oss.qualcomm.com \
--cc=krzk+dt@kernel.org \
--cc=linux-doc@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pwm@vger.kernel.org \
--cc=linux@analog.com \
--cc=olivier.moysan@foss.st.com \
--cc=rdunlap@infradead.org \
--cc=robh@kernel.org \
--cc=rodrigo.alencar@analog.com \
--cc=skhan@linuxfoundation.org \
--cc=ukleinek@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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®