From: Jonathan Cameron <jic23@kernel.org>
To: Rodrigo Alencar via B4 Relay
<devnull+rodrigo.alencar.analog.com@kernel.org>
Cc: rodrigo.alencar@analog.com, linux-kernel@vger.kernel.org,
linux-iio@vger.kernel.org, devicetree@vger.kernel.org,
linux-doc@vger.kernel.org, David Lechner <dlechner@baylibre.com>,
Andy Shevchenko <andy@kernel.org>,
Lars-Peter Clausen <lars@metafoo.de>,
Michael Hennerich <Michael.Hennerich@analog.com>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Jonathan Corbet <corbet@lwn.net>,
Andrew Morton <akpm@linux-foundation.org>,
Petr Mladek <pmladek@suse.com>,
Steven Rostedt <rostedt@goodmis.org>,
Andy Shevchenko <andriy.shevchenko@linux.intel.com>,
Rasmus Villemoes <linux@rasmusvillemoes.dk>,
Sergey Senozhatsky <senozhatsky@chromium.org>,
Shuah Khan <skhan@linuxfoundation.org>
Subject: Re: [PATCH v10 07/11] iio: frequency: adf41513: driver implementation
Date: Sat, 25 Apr 2026 17:33:55 +0100 [thread overview]
Message-ID: <20260425173355.464f6899@jic23-huawei> (raw)
In-Reply-To: <20260415-adf41513-iio-driver-v10-7-df61046d5457@analog.com>
On Wed, 15 Apr 2026 10:51:50 +0100
Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> From: Rodrigo Alencar <rodrigo.alencar@analog.com>
>
> The driver is based on existing PLL drivers in the IIO subsystem and
> implements the following key features:
>
> - Integer-N and fractional-N (fixed/variable modulus) synthesis modes
> - High-resolution frequency calculations using microhertz (µHz) precision
> to handle sub-Hz resolution across multi-GHz frequency ranges
> - IIO debugfs interface for direct register access
> - FW property parsing from devicetree including charge pump settings,
> reference path configuration and muxout options
> - Power management support with suspend/resume callbacks
> - Lock detect GPIO monitoring
>
> Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
Hi Rodrigo
I was thinking the only thing that was still under possible discussion
was the parsing code and related tests, but given that's been quiet
I took what I was thinking would be the final look at this.
Found a few bits of code that are unnecessarily general.
Other stuff is all trivial.
Anyhow, sashiko is now running on linux-iio so please also take a look
at what it thinks of your v11. It's been finding some subtle bugs but
obviously take into account it might be making things up ;)
Thanks,
Jonathan
> diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile
> index 70d0e0b70e80..53b4d01414d8 100644
> --- a/drivers/iio/frequency/Makefile
> +++ b/drivers/iio/frequency/Makefile
> @@ -5,6 +5,7 @@
>
> # When adding new entries keep the list in alphabetical order
> obj-$(CONFIG_AD9523) += ad9523.o
> +obj-$(CONFIG_ADF41513) += adf41513.o
> obj-$(CONFIG_ADF4350) += adf4350.o
> obj-$(CONFIG_ADF4371) += adf4371.o
> obj-$(CONFIG_ADF4377) += adf4377.o
> diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
> new file mode 100644
> index 000000000000..bf2d6c941082
> --- /dev/null
> +++ b/drivers/iio/frequency/adf41513.c
> +
> +static int adf41513_sync_config(struct adf41513_state *st, u16 sync_mask)
> +{
> + __be32 d32;
> + int ret, i;
> +
> + /* write registers in reverse order (R13 to R0)*/
> + for (i = ADF41513_REG13; i >= ADF41513_REG0; i--) {
Maybe
for (int i = ..
given that's now considered fine in kernel code.
You could reduce the scope of d32 and ret but that doesn't bring much advantage here.
> + if (st->regs_hw[i] == st->regs[i] && !(sync_mask & BIT(i)))
> + continue;
> +
> + d32 = cpu_to_be32(st->regs[i] | i);
> + ret = spi_write_then_read(st->spi, &d32, sizeof(d32), NULL, 0);
> + if (ret < 0)
> + return ret;
> + st->regs_hw[i] = st->regs[i];
> + dev_dbg(&st->spi->dev, "REG%d <= 0x%08X\n", i, st->regs[i] | i);
> + }
> +
> + return 0;
> +}
> +
> +static int adf41513_calc_variable_mod(struct adf41513_state *st,
> + struct adf41513_pll_settings *result)
> +{
> + u64 freq_error_uhz, mod2;
> + u32 frac1, frac2;
> + u16 int_value = div64_u64_rem(result->target_frequency_uhz,
> + result->pfd_frequency_uhz,
> + &freq_error_uhz);
> +
> + if (st->chip_info->has_prescaler_8_9 && int_value >= ADF41513_MIN_INT_FRAC_8_9 &&
> + int_value <= ADF41513_MAX_INT_8_9)
> + result->prescaler = 1;
> + else if (int_value >= ADF41513_MIN_INT_FRAC_4_5 && int_value <= ADF41513_MAX_INT_4_5)
> + result->prescaler = 0;
> + else
> + return -ERANGE;
See below for this value getting switched at caller.
> +
> + /* calculate required mod2 based on target resolution / 2 */
> + mod2 = DIV64_U64_ROUND_CLOSEST(result->pfd_frequency_uhz << 1,
> + st->data.freq_resolution_uhz * ADF41513_FIXED_MODULUS);
> + /* ensure mod2 is at least 2 for meaningful operation */
> + mod2 = clamp(mod2, 2, ADF41513_MAX_MOD2);
> +
> + /* calculate frac1 and frac2 */
> + frac1 = mul_u64_u64_div_u64(freq_error_uhz, ADF41513_FIXED_MODULUS,
> + result->pfd_frequency_uhz);
> + freq_error_uhz -= mul_u64_u32_div(result->pfd_frequency_uhz, frac1,
> + ADF41513_FIXED_MODULUS);
> + frac2 = mul_u64_u64_div_u64(freq_error_uhz, mod2 * ADF41513_FIXED_MODULUS,
> + result->pfd_frequency_uhz);
> +
> + /* integer part */
> + result->actual_frequency_uhz = (u64)int_value * result->pfd_frequency_uhz;
> + /* fractional part */
> + result->actual_frequency_uhz += mul_u64_u64_div_u64(mod2 * frac1 + frac2,
> + result->pfd_frequency_uhz,
> + mod2 * ADF41513_FIXED_MODULUS);
> + result->mode = ADF41513_MODE_VARIABLE_MODULUS;
> + result->int_value = int_value;
> + result->frac1 = frac1;
> + result->frac2 = frac2;
> + result->mod2 = mod2;
> +
> + return 0;
> +}
> +
> +static int adf41513_calc_pll_settings(struct adf41513_state *st,
> + struct adf41513_pll_settings *result,
> + u64 rf_out_uhz)
> +{
> + u64 max_rf_freq_uhz = st->chip_info->max_rf_freq_hz * MICRO;
> + u64 min_rf_freq_uhz = ADF41513_MIN_RF_FREQ_HZ * MICRO;
> + u64 pfd_freq_limit_uhz;
> + int ret;
> +
> + if (rf_out_uhz < min_rf_freq_uhz || rf_out_uhz > max_rf_freq_uhz) {
> + dev_err(&st->spi->dev, "RF frequency %llu uHz out of range [%llu, %llu] uHz\n",
> + rf_out_uhz, min_rf_freq_uhz, max_rf_freq_uhz);
> + return -EINVAL;
> + }
> +
> + result->target_frequency_uhz = rf_out_uhz;
> +
> + /* try integer-N first (best phase noise performance) */
> + pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_4_5),
> + ADF41513_MAX_PFD_FREQ_INT_N_UHZ);
> + ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz);
> + if (ret < 0)
> + return ret;
> +
> + if (adf41513_calc_integer_n(st, result) == 0)
> + return 0;
> +
> + /* try fractional-N: recompute pfd frequency if necessary */
> + pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_FRAC_4_5),
> + ADF41513_MAX_PFD_FREQ_FRAC_N_UHZ);
> + if (pfd_freq_limit_uhz < result->pfd_frequency_uhz) {
> + ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz);
> + if (ret < 0)
> + return ret;
> + }
> +
> + /* fixed-modulus attempt */
> + if (adf41513_calc_fixed_mod(st, result) == 0)
> + return 0;
> +
> + /* variable-modulus attempt */
> + ret = adf41513_calc_variable_mod(st, result);
> + if (ret < 0) {
> + dev_err(&st->spi->dev,
> + "no valid PLL configuration found for %llu uHz\n",
> + rf_out_uhz);
> + return -EINVAL;
Why eat value of ret? If that does make sense add a comment so
this doesn't get 'cleaned up' in future.
I can sort of see it making sense as all this is a case of all
sorts of different cases failed rather than just this last one.
> + }
> +
> + return 0;
> +}
> +
> +static ssize_t adf41513_read_resolution(struct iio_dev *indio_dev,
> + uintptr_t private,
> + const struct iio_chan_spec *chan,
> + char *buf)
> +{
> + struct adf41513_state *st = iio_priv(indio_dev);
> + int vals[2];
> +
> + guard(mutex)(&st->lock);
> +
> + switch (private) {
> + case ADF41513_FREQ_RESOLUTION:
See below.
> + iio_val_s64_array_populate(st->data.freq_resolution_uhz, vals);
> + return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO,
> + ARRAY_SIZE(vals), vals);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t adf41513_read_powerdown(struct iio_dev *indio_dev,
> + uintptr_t private,
> + const struct iio_chan_spec *chan,
> + char *buf)
> +{
> + struct adf41513_state *st = iio_priv(indio_dev);
> + u32 val;
> +
> + guard(mutex)(&st->lock);
> +
> + switch (private) {
> + case ADF41513_POWER_DOWN:
See below.
> + val = FIELD_GET(ADF41513_REG6_POWER_DOWN_MSK,
> + st->regs_hw[ADF41513_REG6]);
> + return sysfs_emit(buf, "%u\n", val);
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t adf41513_write_resolution(struct iio_dev *indio_dev,
> + uintptr_t private,
> + const struct iio_chan_spec *chan,
> + const char *buf, size_t len)
> +{
> + struct adf41513_state *st = iio_priv(indio_dev);
> + u64 freq_uhz;
> + int ret;
> +
> + ret = kstrtoudec64(buf, ADF41513_HZ_DECIMAL_SCALE, &freq_uhz);
> + if (ret)
> + return ret;
> +
> + guard(mutex)(&st->lock);
> +
> + switch ((u32)private) {
> + case ADF41513_FREQ_RESOLUTION:
See below.
> + if (freq_uhz == 0 || freq_uhz > ADF41513_MAX_FREQ_RESOLUTION_UHZ)
> + return -EINVAL;
> + st->data.freq_resolution_uhz = freq_uhz;
> + return len;
> + default:
> + return -EINVAL;
> + }
> +}
> +
> +static ssize_t adf41513_write_powerdown(struct iio_dev *indio_dev,
> + uintptr_t private,
> + const struct iio_chan_spec *chan,
> + const char *buf, size_t len)
> +{
> + struct adf41513_state *st = iio_priv(indio_dev);
> + unsigned long readin;
> + int ret;
> +
> + ret = kstrtoul(buf, 10, &readin);
> + if (ret)
> + return ret;
> +
> + guard(mutex)(&st->lock);
> +
> + switch ((u32)private) {
> + case ADF41513_POWER_DOWN:
Silly question. How do we get a different value of this?
I'm going to guess this is either a code evolution thing where
we have reached a silly state, or you have other code that goes
on top of this series that needs this. If it's the 'more code'
case then do a refactor at start of that series and keep this simple for now.
Similar applies for the resolution functions.
> + if (readin)
> + ret = adf41513_suspend(st);
> + else
> + ret = adf41513_resume(st);
> + break;
Really trivial but I'd prefer.
if (ret)
return ret;
break;
Then unconditionally return len below. Tends to make things a bit
simpler to read if we get more elements in the switch in the longer term
as no need to go look for what happens in the error path.
> + default:
> + return -EINVAL;
> + }
> +
> + return ret ?: len;
> +}
> +
> +#define _ADF41513_EXT_PD_INFO(_name, _ident) { \
> + .name = _name, \
> + .read = adf41513_read_powerdown, \
> + .write = adf41513_write_powerdown, \
> + .private = _ident, \
> + .shared = IIO_SEPARATE, \
> +}
> +
> +#define _ADF41513_EXT_RES_INFO(_name, _ident) { \
> + .name = _name, \
> + .read = adf41513_read_resolution, \
> + .write = adf41513_write_resolution, \
> + .private = _ident, \
> + .shared = IIO_SEPARATE, \
> +}
> +
> +static const struct iio_chan_spec_ext_info adf41513_ext_info[] = {
> + _ADF41513_EXT_RES_INFO("frequency_resolution", ADF41513_FREQ_RESOLUTION),
> + _ADF41513_EXT_PD_INFO("powerdown", ADF41513_POWER_DOWN),
IF these only occur once - which I think is the case, drop the macros.
> + { }
> +};
> +
>
next prev parent reply other threads:[~2026-04-25 16:34 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-15 9:51 [PATCH v10 00/11] ADF41513/ADF41510 PLL frequency synthesizers Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 01/11] dt-bindings: iio: frequency: add adf41513 Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 02/11] lib: kstrtox: add kstrtoudec64() and kstrtodec64() Rodrigo Alencar via B4 Relay
2026-04-17 8:36 ` Rodrigo Alencar
2026-04-25 15:40 ` Jonathan Cameron
2026-04-25 22:33 ` David Laight
2026-04-26 8:02 ` Rodrigo Alencar
2026-04-15 9:51 ` [PATCH v10 03/11] lib: test-kstrtox: tests for kstrtodec64() and kstrtoudec64() Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 04/11] lib: math: div64: add div64_s64_rem() Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 05/11] iio: core: add decimal value formatting into 64-bit value Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 06/11] iio: test: iio-test-format: add test case for decimal format Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 07/11] iio: frequency: adf41513: driver implementation Rodrigo Alencar via B4 Relay
2026-04-25 16:33 ` Jonathan Cameron [this message]
2026-04-15 9:51 ` [PATCH v10 08/11] iio: frequency: adf41513: handle LE synchronization feature Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 09/11] iio: frequency: adf41513: features on frequency change Rodrigo Alencar via B4 Relay
2026-04-15 9:51 ` [PATCH v10 10/11] docs: iio: add documentation for adf41513 driver Rodrigo Alencar via B4 Relay
2026-04-25 16:40 ` Jonathan Cameron
2026-04-15 9:51 ` [PATCH v10 11/11] Documentation: ABI: testing: add common ABI file for iio/frequency Rodrigo Alencar via B4 Relay
2026-04-15 10:24 ` [PATCH v10 00/11] ADF41513/ADF41510 PLL frequency synthesizers Andy Shevchenko
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=20260425173355.464f6899@jic23-huawei \
--to=jic23@kernel.org \
--cc=Michael.Hennerich@analog.com \
--cc=akpm@linux-foundation.org \
--cc=andriy.shevchenko@linux.intel.com \
--cc=andy@kernel.org \
--cc=conor+dt@kernel.org \
--cc=corbet@lwn.net \
--cc=devicetree@vger.kernel.org \
--cc=devnull+rodrigo.alencar.analog.com@kernel.org \
--cc=dlechner@baylibre.com \
--cc=krzk+dt@kernel.org \
--cc=lars@metafoo.de \
--cc=linux-doc@vger.kernel.org \
--cc=linux-iio@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux@rasmusvillemoes.dk \
--cc=pmladek@suse.com \
--cc=robh@kernel.org \
--cc=rodrigo.alencar@analog.com \
--cc=rostedt@goodmis.org \
--cc=senozhatsky@chromium.org \
--cc=skhan@linuxfoundation.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