* Re: [RFC PATCH v3 1/3] scripts: add kconfirm
From: Demi Marie Obenour @ 2026-05-17 7:32 UTC (permalink / raw)
To: Miguel Ojeda, Julian Braha
Cc: nathan, nsc, jani.nikula, akpm, gary, ljs, arnd, gregkh,
masahiroy, ojeda, corbet, qingfang.deng, yann.prono, ej,
linux-kernel, rust-for-linux, linux-doc, linux-kbuild
In-Reply-To: <CANiq72kr=tzvEitYj6xyT=jGnKQZK1dmekSU3us7MWGTrv0FNA@mail.gmail.com>
[-- Attachment #1.1.1: Type: text/plain, Size: 3067 bytes --]
On 5/17/26 02:28, Miguel Ojeda wrote:
> On Sat, May 16, 2026 at 11:54 PM Julian Braha <julianbraha@gmail.com> wrote:
>>
>> +CARGO = cargo
>
> Question to Kbuild: would it hurt to hardcore `--offline` here?
>
> If someone within Make actually ever needs Cargo to fetch something,
> then they should be very explicit about it (in which case we could
> have another variable etc.).
>
>> - rust/libpin_init_internal.so rust/libpin_init_internal.dylib
>> + rust/libpin_init_internal.so rust/libpin_init_internal.dylib \
>
> Spurious change?
>
>> +$(TARGET):
>> + $(CARGO) run --release --offline -p kconfirm-linux -- --linux-path $(srctree) --enable-arch $(SRCARCH) $(KCONFIRM_ARGS)
>
> This probably does not work in `O=` builds or in cases where the
> `srctree` is read-only (please see my other reply on the docs patch).
>
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +use crate::AnalysisArgs;
>
> This appears to use a quite different, custom Rust style. If this is
> going to be developed in-tree, then we should do our best to follow
> the guidelines:
>
> https://docs.kernel.org/rust/coding-guidelines.html
>
> For instance, a few key points are not followed here:
>
> - Public items are not documented. When they are, they seem to use
> comments instead of actual docs. Markdown is not used either.
>
> - No examples, doctests or tests.
>
> - No `// SAFETY` comments for unsafe code.
>
>> +unsafe extern "C" {
>> + fn curl_global_init(flags: c_long) -> CURLcode;
>> +
>> + fn curl_easy_init() -> *mut CURL;
>> +
>> + fn curl_easy_cleanup(handle: *mut CURL);
>> +
>> + fn curl_easy_perform(handle: *mut CURL) -> CURLcode;
>> +
>> + fn curl_easy_strerror(code: CURLcode) -> *const c_char;
>> +
>> + fn curl_easy_setopt(handle: *mut CURL, option: CURLoption, ...) -> CURLcode;
>> +
>> + fn curl_easy_getinfo(handle: *mut CURL, info: CURLINFO, ...) -> CURLcode;
>> +}
>
> I like minimizing dependencies, but since we require vendored
> dependencies anyway, then it may be simpler to use a common
> ("standard") Rust dependency for these things. Then we can all agree
> on a particular one and use that when the same need arises.
Using a ton of vendored dependencies would make for unreviewable
patches. It's also the kind of thing that gives distro packagers
headaches.
> In fact, it seems like FFI here and in the other file is the only
> source of `unsafe` code, no? We could perhaps even `forbid` it
> otherwise.
I don't want to pull in a huge library like clap. A simple getopt_long
implementation could also be an option.
> If we truly want to minimize dependencies even if we have vendored
> ones, then we could call into the `curl` CLI instead, just like we
> e.g. call into `bindgen` instead of using its library.
I'm the one who suggested using FFI here and for command-line parsing.
The command-line interface would also work.
--
Sincerely,
Demi Marie Obenour (she/her/hers)
[-- Attachment #1.1.2: OpenPGP public key --]
[-- Type: application/pgp-keys, Size: 7253 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* [PATCH v2 RESEND] design: fix typos in design.rst
From: Sakurai Shun @ 2026-05-17 7:34 UTC (permalink / raw)
To: SeongJae Park, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Mike Rapoport,
Suren Baghdasaryan, Michal Hocko, Jonathan Corbet, Shuah Khan
Cc: Sakurai Shun, damon, linux-mm, linux-doc, linux-kernel
L140: "unsinged" -> "unsigned"
L371: "sampleing" -> "sampling"
L387: "multipled" -> "multiplied"
Signed-off-by: Sakurai Shun <ssh1326@icloud.com>
---
Documentation/mm/damon/design.rst | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst
index afc7d52bd..9cc70a296 100644
--- a/Documentation/mm/damon/design.rst
+++ b/Documentation/mm/damon/design.rst
@@ -140,7 +140,7 @@ as Idle page tracking does.
Address Unit
------------
-DAMON core layer uses ``unsinged long`` type for monitoring target address
+DAMON core layer uses ``unsigned long`` type for monitoring target address
ranges. In some cases, the address space for a given operations set could be
too large to be handled with the type. ARM (32-bit) with large physical
address extension is an example. For such cases, a per-operations set
@@ -371,7 +371,7 @@ with theoretical maximum ``nr_accesses``, which can be calculated as
``aggregation interval / sampling interval``.
The mechanism calculates the ratio of access events for ``aggrs`` aggregations,
-and increases or decrease the ``sampleing interval`` and ``aggregation
+and increases or decrease the ``sampling interval`` and ``aggregation
interval`` in same ratio, if the observed access ratio is lower or higher than
the target, respectively. The ratio of the intervals change is decided in
proportion to the distance between current samples ratio and the target ratio.
@@ -387,7 +387,7 @@ The tuning is turned off by default, and need to be set explicitly by the user.
As a rule of thumbs and the Parreto principle, 4% access samples ratio target
is recommended. Note that Parreto principle (80/20 rule) has applied twice.
That is, assumes 4% (20% of 20%) DAMON-observed access events ratio (source)
-to capture 64% (80% multipled by 80%) real access events (outcomes).
+to capture 64% (80% multiplied by 80%) real access events (outcomes).
To know how user-space can use this feature via :ref:`DAMON sysfs interface
<sysfs_interface>`, refer to :ref:`intervals_goal
--
2.54.0
^ permalink raw reply related
* [PATCH v2 RESEND] docs: fix typo in mpo-overview.rst
From: Sakurai Shun @ 2026-05-17 7:40 UTC (permalink / raw)
To: Alex Deucher, Christian König, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, David Airlie, Simona Vetter,
Jonathan Corbet, Shuah Khan
Cc: Sakurai Shun, amd-gfx, dri-devel, linux-doc, linux-kernel
Replace "transparant" with "transparent"
Signed-off-by: Sakurai Shun <ssh1326@icloud.com>
---
Documentation/gpu/amdgpu/display/mpo-overview.rst | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Documentation/gpu/amdgpu/display/mpo-overview.rst b/Documentation/gpu/amdgpu/display/mpo-overview.rst
index 59a4f54a3..ed39e53ff 100644
--- a/Documentation/gpu/amdgpu/display/mpo-overview.rst
+++ b/Documentation/gpu/amdgpu/display/mpo-overview.rst
@@ -167,7 +167,7 @@ and interactions with operations such as DPMS and S3:
- ``kms_plane_multiple@atomic-pipe-*-tiling-``
- ``kms_plane_scaling@pipe-*-plane-scaling``
- ``kms_plane_alpha_blend@pipe-*-alpha-basic``
-- ``kms_plane_alpha_blend@pipe-*-alpha-transparant-fb``
+- ``kms_plane_alpha_blend@pipe-*-alpha-transparent-fb``
- ``kms_plane_alpha_blend@pipe-*-alpha-opaque-fb``
- ``kms_plane_alpha_blend@pipe-*-constant-alpha-min``
- ``kms_plane_alpha_blend@pipe-*-constant-alpha-mid``
--
2.54.0
^ permalink raw reply related
* Re: [PATCH v2 RESEND] design: fix typos in design.rst
From: Lorenzo Stoakes @ 2026-05-17 7:59 UTC (permalink / raw)
To: Sakurai Shun
Cc: SeongJae Park, Andrew Morton, David Hildenbrand, Liam R. Howlett,
Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
Jonathan Corbet, Shuah Khan, damon, linux-mm, linux-doc,
linux-kernel
In-Reply-To: <20260517073433.3015-1-ssh1326@icloud.com>
On Sun, May 17, 2026 at 04:34:26PM +0900, Sakurai Shun wrote:
> L140: "unsinged" -> "unsigned"
> L371: "sampleing" -> "sampling"
> L387: "multipled" -> "multiplied"
>
> Signed-off-by: Sakurai Shun <ssh1326@icloud.com>
LGTM, so:
Reviewed-by: Lorenzo Stoakes <ljs@kernel.org>
> ---
> Documentation/mm/damon/design.rst | 6 +++---
> 1 file changed, 3 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/mm/damon/design.rst b/Documentation/mm/damon/design.rst
> index afc7d52bd..9cc70a296 100644
> --- a/Documentation/mm/damon/design.rst
> +++ b/Documentation/mm/damon/design.rst
> @@ -140,7 +140,7 @@ as Idle page tracking does.
> Address Unit
> ------------
>
> -DAMON core layer uses ``unsinged long`` type for monitoring target address
> +DAMON core layer uses ``unsigned long`` type for monitoring target address
> ranges. In some cases, the address space for a given operations set could be
> too large to be handled with the type. ARM (32-bit) with large physical
> address extension is an example. For such cases, a per-operations set
> @@ -371,7 +371,7 @@ with theoretical maximum ``nr_accesses``, which can be calculated as
> ``aggregation interval / sampling interval``.
>
> The mechanism calculates the ratio of access events for ``aggrs`` aggregations,
> -and increases or decrease the ``sampleing interval`` and ``aggregation
> +and increases or decrease the ``sampling interval`` and ``aggregation
> interval`` in same ratio, if the observed access ratio is lower or higher than
> the target, respectively. The ratio of the intervals change is decided in
> proportion to the distance between current samples ratio and the target ratio.
> @@ -387,7 +387,7 @@ The tuning is turned off by default, and need to be set explicitly by the user.
> As a rule of thumbs and the Parreto principle, 4% access samples ratio target
> is recommended. Note that Parreto principle (80/20 rule) has applied twice.
> That is, assumes 4% (20% of 20%) DAMON-observed access events ratio (source)
> -to capture 64% (80% multipled by 80%) real access events (outcomes).
> +to capture 64% (80% multiplied by 80%) real access events (outcomes).
>
> To know how user-space can use this feature via :ref:`DAMON sysfs interface
> <sysfs_interface>`, refer to :ref:`intervals_goal
> --
> 2.54.0
>
^ permalink raw reply
* [PATCH v3 0/3] raspberrypi-hwmon voltage support and teardown fix
From: Shubham Chakraborty @ 2026-05-17 8:04 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260516164407.25255-1-chakrabortyshubham66@gmail.com>
This series adds firmware-backed voltage inputs to raspberrypi-hwmon and
includes a separate fix for the delayed polling work teardown path.
Patch 1 adds the firmware voltage IDs and the shared voltage request
structure to the Raspberry Pi firmware API header.
Patch 2 extends raspberrypi-hwmon to expose the firmware-provided core
and SDRAM voltage inputs through hwmon and documents the new sysfs
entries.
Patch 3 addresses the delayed polling work teardown concern raised
during review.
Changes in v3:
- corrected the SDRAM_P and SDRAM_I voltage ID mapping
- moved the voltage request structure into the firmware API header
- made the voltage request structure voltage-specific
- split the delayed-work teardown change into a separate patch
Tested on:
- Raspberry Pi 3B+ running Linux 6.12.75+rpt-rpi-v8
Shubham Chakraborty (3):
soc: bcm2835: raspberrypi-firmware: Add voltage domain IDs
hwmon: raspberrypi: Add voltage input support
hwmon: raspberrypi: Fix delayed-work teardown race
Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
drivers/hwmon/raspberrypi-hwmon.c | 139 ++++++++++++++++++++-
include/soc/bcm2835/raspberrypi-firmware.h | 25 ++++
3 files changed, 171 insertions(+), 8 deletions(-)
--
2.54.0
^ permalink raw reply
* [PATCH v3 1/3] soc: bcm2835: raspberrypi-firmware: Add voltage domain IDs
From: Shubham Chakraborty @ 2026-05-17 8:04 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260517080445.103962-1-chakrabortyshubham66@gmail.com>
Add Raspberry Pi firmware voltage domain identifiers for the mailbox
property interface.
Also add the voltage request structure used with
RPI_FIRMWARE_GET_VOLTAGE so firmware clients can share the common API
definition from the firmware header.
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
include/soc/bcm2835/raspberrypi-firmware.h | 25 ++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/include/soc/bcm2835/raspberrypi-firmware.h b/include/soc/bcm2835/raspberrypi-firmware.h
index e1f87fbfe554..975bef529854 100644
--- a/include/soc/bcm2835/raspberrypi-firmware.h
+++ b/include/soc/bcm2835/raspberrypi-firmware.h
@@ -156,6 +156,31 @@ enum rpi_firmware_clk_id {
RPI_FIRMWARE_NUM_CLK_ID,
};
+enum rpi_firmware_volt_id {
+ RPI_FIRMWARE_VOLT_ID_CORE = 1,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_C = 2,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_P = 3,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_I = 4,
+ RPI_FIRMWARE_NUM_VOLT_ID,
+};
+
+/**
+ * struct rpi_firmware_get_voltage_request - Firmware request for a voltage
+ * @id: ID of the voltage being queried
+ * @value: Voltage in microvolts. Set by the firmware.
+ *
+ * Used by @RPI_FIRMWARE_GET_VOLTAGE.
+ */
+struct rpi_firmware_get_voltage_request {
+ __le32 id;
+ __le32 value;
+} __packed;
+
+#define RPI_FIRMWARE_GET_VOLTAGE_REQUEST(_id) \
+ { \
+ .id = cpu_to_le32(_id), \
+ }
+
/**
* struct rpi_firmware_clk_rate_request - Firmware Request for a rate
* @id: ID of the clock being queried
--
2.54.0
^ permalink raw reply related
* [PATCH v3 2/3] hwmon: raspberrypi: Add voltage input support
From: Shubham Chakraborty @ 2026-05-17 8:04 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260517080445.103962-1-chakrabortyshubham66@gmail.com>
Extend the raspberrypi-hwmon driver to expose firmware-provided
voltage measurements through the hwmon subsystem.
The driver now exports the following voltage inputs:
- in0_input (core)
- in1_input (sdram_c)
- in2_input (sdram_i)
- in3_input (sdram_p)
Voltage values returned by firmware are converted from microvolts
to millivolts as expected by the hwmon subsystem.
Update the documentation related to it.
The existing undervoltage sticky alarm handling is preserved and
associated with the first voltage channel.
Tested in -
- Raspberry Pi 3b+ (Linux raspberrypi 6.12.75+rpt-rpi-v8 #1 SMP PREEMPT
Debian 1:6.12.75-1+rpt1 (2026-03-11) aarch64 GNU/Linux)
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
Documentation/hwmon/raspberrypi-hwmon.rst | 15 ++-
drivers/hwmon/raspberrypi-hwmon.c | 127 +++++++++++++++++++++-
2 files changed, 137 insertions(+), 5 deletions(-)
diff --git a/Documentation/hwmon/raspberrypi-hwmon.rst b/Documentation/hwmon/raspberrypi-hwmon.rst
index 8038ade36490..db315184b861 100644
--- a/Documentation/hwmon/raspberrypi-hwmon.rst
+++ b/Documentation/hwmon/raspberrypi-hwmon.rst
@@ -20,6 +20,17 @@ undervoltage conditions.
Sysfs entries
-------------
-======================= ==================
+======================= ======================================================
+in0_input Core voltage in millivolts
+in1_input SDRAM controller voltage in millivolts
+in2_input SDRAM I/O voltage in millivolts
+in3_input SDRAM PHY voltage in millivolts
+in0_label "core"
+in1_label "sdram_c"
+in2_label "sdram_i"
+in3_label "sdram_p"
in0_lcrit_alarm Undervoltage alarm
-======================= ==================
+======================= ======================================================
+
+The voltage inputs and labels are only exposed if the firmware reports support
+for the corresponding voltage ID.
diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
index a2938881ccd2..8ce6dacc19b0 100644
--- a/drivers/hwmon/raspberrypi-hwmon.c
+++ b/drivers/hwmon/raspberrypi-hwmon.c
@@ -5,6 +5,7 @@
* Based on firmware/raspberrypi.c by Noralf Trønnes
*
* Copyright (C) 2018 Stefan Wahren <stefan.wahren@i2se.com>
+ * Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
*/
#include <linux/device.h>
#include <linux/devm-helpers.h>
@@ -21,10 +22,18 @@
struct rpi_hwmon_data {
struct device *hwmon_dev;
struct rpi_firmware *fw;
+ u32 valid_inputs;
u32 last_throttled;
struct delayed_work get_values_poll_work;
};
+static const char * const rpi_hwmon_labels[] = {
+ "core",
+ "sdram_c",
+ "sdram_i",
+ "sdram_p",
+};
+
static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
{
u32 new_uv, old_uv, value;
@@ -56,6 +65,21 @@ static void rpi_firmware_get_throttled(struct rpi_hwmon_data *data)
hwmon_notify_event(data->hwmon_dev, hwmon_in, hwmon_in_lcrit_alarm, 0);
}
+static int rpi_firmware_get_voltage(struct rpi_hwmon_data *data, u32 id,
+ long *val)
+{
+ struct rpi_firmware_get_voltage_request packet =
+ RPI_FIRMWARE_GET_VOLTAGE_REQUEST(id);
+ int ret;
+ ret = rpi_firmware_property(data->fw, RPI_FIRMWARE_GET_VOLTAGE,
+ &packet, sizeof(packet));
+ if (ret)
+ return ret;
+
+ *val = le32_to_cpu(packet.value) / 1000;
+ return 0;
+}
+
static void get_values_poll(struct work_struct *work)
{
struct rpi_hwmon_data *data;
@@ -77,19 +101,94 @@ static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct rpi_hwmon_data *data = dev_get_drvdata(dev);
- *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_input:
+ switch (channel) {
+ case 0:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_CORE,
+ val);
+ case 1:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_C,
+ val);
+ case 2:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_I,
+ val);
+ case 3:
+ return rpi_firmware_get_voltage(data,
+ RPI_FIRMWARE_VOLT_ID_SDRAM_P,
+ val);
+ default:
+ return -EOPNOTSUPP;
+ }
+ case hwmon_in_lcrit_alarm:
+ if (channel == 0) {
+ *val = !!(data->last_throttled & UNDERVOLTAGE_STICKY_BIT);
+ return 0;
+ }
+ return -EOPNOTSUPP;
+ default:
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static int rpi_read_string(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, const char **str)
+{
+ if (type == hwmon_in && attr == hwmon_in_label) {
+ if (channel >= ARRAY_SIZE(rpi_hwmon_labels))
+ return -EOPNOTSUPP;
+
+ *str = rpi_hwmon_labels[channel];
+ return 0;
+ }
+
+ return -EOPNOTSUPP;
+}
+
+static umode_t rpi_is_visible(const void *_data, enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct rpi_hwmon_data *data = _data;
+
+ if (type == hwmon_in) {
+ switch (attr) {
+ case hwmon_in_input:
+ case hwmon_in_label:
+ if (!(data->valid_inputs & BIT(channel)))
+ return 0;
+ return 0444;
+ case hwmon_in_lcrit_alarm:
+ if (channel == 0)
+ return 0444;
+ return 0;
+ default:
+ return 0;
+ }
+ }
+
return 0;
}
static const struct hwmon_channel_info * const rpi_info[] = {
HWMON_CHANNEL_INFO(in,
- HWMON_I_LCRIT_ALARM),
+ HWMON_I_INPUT | HWMON_I_LABEL | HWMON_I_LCRIT_ALARM,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL,
+ HWMON_I_INPUT | HWMON_I_LABEL),
NULL
};
static const struct hwmon_ops rpi_hwmon_ops = {
- .visible = 0444,
+ .is_visible = rpi_is_visible,
.read = rpi_read,
+ .read_string = rpi_read_string,
};
static const struct hwmon_chip_info rpi_chip_info = {
@@ -101,6 +200,7 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct rpi_hwmon_data *data;
+ long voltage;
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
@@ -110,6 +210,26 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
/* Parent driver assure that firmware is correct */
data->fw = dev_get_drvdata(dev->parent);
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_CORE,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(0);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_C,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(1);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_I,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(2);
+
+ ret = rpi_firmware_get_voltage(data, RPI_FIRMWARE_VOLT_ID_SDRAM_P,
+ &voltage);
+ if (!ret)
+ data->valid_inputs |= BIT(3);
+
data->hwmon_dev = devm_hwmon_device_register_with_info(dev, "rpi_volt",
data,
&rpi_chip_info,
@@ -159,6 +279,7 @@ static struct platform_driver rpi_hwmon_driver = {
module_platform_driver(rpi_hwmon_driver);
MODULE_AUTHOR("Stefan Wahren <wahrenst@gmx.net>");
+MODULE_AUTHOR("Shubham Chakraborty <chakrabortyshubham66@gmail.com>");
MODULE_DESCRIPTION("Raspberry Pi voltage sensor driver");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:raspberrypi-hwmon");
--
2.54.0
^ permalink raw reply related
* [PATCH v3 3/3] hwmon: raspberrypi: Fix delayed-work teardown race
From: Shubham Chakraborty @ 2026-05-17 8:04 UTC (permalink / raw)
To: Guenter Roeck, Florian Fainelli, Jonathan Corbet
Cc: Shuah Khan, Broadcom internal kernel review list, Ray Jui,
Scott Branden, linux-hwmon, linux-doc, linux-rpi-kernel,
linux-arm-kernel, linux-kernel, Shubham Chakraborty
In-Reply-To: <20260517080445.103962-1-chakrabortyshubham66@gmail.com>
The delayed polling work rearms itself from the work function, so use
explicit delayed-work setup and cleanup instead of
devm_delayed_work_autocancel().
Initialize the delayed work with INIT_DELAYED_WORK() and register a
devres cleanup action that calls disable_delayed_work_sync() during
teardown.
This addresses the concern raised during review about the polling work
being able to requeue itself while the driver is being removed.
Signed-off-by: Shubham Chakraborty <chakrabortyshubham66@gmail.com>
---
drivers/hwmon/raspberrypi-hwmon.c | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/drivers/hwmon/raspberrypi-hwmon.c b/drivers/hwmon/raspberrypi-hwmon.c
index 8ce6dacc19b0..0bbc735f74a4 100644
--- a/drivers/hwmon/raspberrypi-hwmon.c
+++ b/drivers/hwmon/raspberrypi-hwmon.c
@@ -8,7 +8,6 @@
* Copyright (C) 2026 Shubham Chakraborty <chakrabortyshubham66@gmail.com>
*/
#include <linux/device.h>
-#include <linux/devm-helpers.h>
#include <linux/err.h>
#include <linux/hwmon.h>
#include <linux/module.h>
@@ -96,6 +95,13 @@ static void get_values_poll(struct work_struct *work)
schedule_delayed_work(&data->get_values_poll_work, 2 * HZ);
}
+static void rpi_hwmon_cancel_poll_work(void *res)
+{
+ struct rpi_hwmon_data *data = res;
+
+ disable_delayed_work_sync(&data->get_values_poll_work);
+}
+
static int rpi_read(struct device *dev, enum hwmon_sensor_types type,
u32 attr, int channel, long *val)
{
@@ -237,8 +243,8 @@ static int rpi_hwmon_probe(struct platform_device *pdev)
if (IS_ERR(data->hwmon_dev))
return PTR_ERR(data->hwmon_dev);
- ret = devm_delayed_work_autocancel(dev, &data->get_values_poll_work,
- get_values_poll);
+ INIT_DELAYED_WORK(&data->get_values_poll_work, get_values_poll);
+ ret = devm_add_action_or_reset(dev, rpi_hwmon_cancel_poll_work, data);
if (ret)
return ret;
platform_set_drvdata(pdev, data);
--
2.54.0
^ permalink raw reply related
* Re: Re: [PATCH v2] dcache: add fs.dentry-limit sysctl with negative-first reaper
From: Horst Birthelmer @ 2026-05-17 7:57 UTC (permalink / raw)
To: Matthew Wilcox
Cc: Horst Birthelmer, Miklos Szeredi, Jonathan Corbet, Shuah Khan,
Alexander Viro, Christian Brauner, Jan Kara, linux-doc,
linux-kernel, linux-fsdevel, Horst Birthelmer
In-Reply-To: <agj5JkPZ7eNbFueR@casper.infradead.org>
On Sun, May 17, 2026 at 12:09:26AM +0100, Matthew Wilcox wrote:
> On Sat, May 16, 2026 at 04:52:54PM +0200, Horst Birthelmer wrote:
> > There was a discussion at LSFMM about servers with too many cached
> > negative dentries.
> > That gave me the idea to keep the dentries in general limited
> > if the system administrator needs it to.
>
> I feel you should link to the dozens of previous attempts at this kind
> of thing to show that you're aware that this has been tried before and
> you're doing something meaningfully different.
>
Hi Matthew,
thanks for looking at this.
- The first limitation of dentries I could find was a patch for Linux 2.6.7
which introduced the vfs_cache_pressure option. [1]
This is still in use today but will not limit dentries as such just the
relation of where to release the pressure but you have to get into a pressure
situation for it to actually matter.
For my case when we get into pressure the fuse server could already be in heavy
trouble (we have had OOM events for the cgroup due to this)
- in 2011 there was the attempt to limit dentries by container [2] [3]
Here Dave Chinner made the point that the dentry cache is usually not the problem
but the inode cache, which is exactly what we see as well, since the fuse server
has to keep a lot of private data for every cached inode. However we have the
information for LRU only for the dentries, so it is the best way we can keep this
under control, limit the number of dentries to an acceptable amounr.
- there was an entire series by Waiman Long starting at around 2017 [4]
Here even you were part of the discussion, and I think the ideas are very similar,
I'm just more worried about unused dentries (I just prefer negative ones on
reclaim) There are several of different attempts in this context.
- then there was tbe one I mentioned in the cover letter [5]
which was trying to modify the caching to limit the excessing traversing when
there are so many entries.
I'm trying to save the same symptoms but not with that approach at all. I don't
worry at all about the chache structures.
- there was [6] by Gautham Ananthakrishna
Here the focus was on the memory used for the dentries. This is none of my concern
in this patch. I'm trying to just not keep dentries and indirectly inodes
unnecessarily in the kernel and as a consequence in the fuse server in user space.
- currently we have the possibility via /proc/sys/fs/dentry-negative to disable
negative dentries completely
I am completely agnostic to this. If an admin disables negative dentries I try to
free some by freeing unused ones and make the limit, if not this is no problem.
I'm sure I have probably missed some where the limitation of dentries was a
secodnary effect. I have searched for patches for fs/dcache.c that had anything
to do with dentries.
--
As a conclusion, I think I have an uncommon perspective on the cache entries
since I don't usually work on vfs but argue from the perspective of a fuse server
Where the kernel makes us waste resources. This hurts way more in the FUSE context
than in a 'normal' file system.
I have taken the look at the dentry cache just because people told me that this
has to be solved in the vfs (and I agree). I actually have a somewhat hacky patch
to do this from fuse and only for the fuse sb.
This patch will start a worker when we pass the set limit and free the negative
dentries then continue on with the unused dentries based on the LRU data.
What I'm trying to achieve is keep only actually used entries in if we are over
an arbitrary limit, and trying not to mess too much with the work that is done
by the kernel. Then there is the point that shrink_dentry_list() is only there
since 2019, so older approaches did not have tthe possibility.
The short version: This is noothing new, just a new combination of already existing
solutions, that could be useful.
[1] https://www.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/2.6.7/2.6.7-mm1/broken-out/vfs-shrinkage-tuning.patch
[2] https://lwn.net/Articles/441164/
[3] https://lore.kernel.org/all/4DBFF1AD.90303@parallels.com/
[4] https://lore.kernel.org/all/1500298773-7510-1-git-send-email-longman@redhat.com/
[5] https://lore.kernel.org/linux-fsdevel/20260331012925.74840-1-raven@themaw.net/
[6] https://lore.kernel.org/all/1611235185-1685-1-git-send-email-gautham.ananthakrishna@oracle.com/
^ permalink raw reply
* Re: [PATCH v2 RESEND] design: fix typos in design.rst
From: Mike Rapoport @ 2026-05-17 8:45 UTC (permalink / raw)
To: Sakurai Shun
Cc: SeongJae Park, Andrew Morton, David Hildenbrand, Lorenzo Stoakes,
Liam R. Howlett, Vlastimil Babka, Suren Baghdasaryan,
Michal Hocko, Jonathan Corbet, Shuah Khan, damon, linux-mm,
linux-doc, linux-kernel
In-Reply-To: <20260517073433.3015-1-ssh1326@icloud.com>
Hi,
On Sun, May 17, 2026 at 04:34:26PM +0900, Sakurai Shun wrote:
> Subject: [PATCH v2 RESEND] design: fix typos in design.rst
Please use more descriptive prefix next time, like SJ suggested
Docs/mm/damon/design
"design" is too vague and gives zero clues about what subsystem it is.
> L140: "unsinged" -> "unsigned"
> L371: "sampleing" -> "sampling"
> L387: "multipled" -> "multiplied"
>
> Signed-off-by: Sakurai Shun <ssh1326@icloud.com>
Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
> ---
> Documentation/mm/damon/design.rst | 6 +++---
> 1 file changed, 3 insertions(+), 3 deletions(-)
--
Sincerely yours,
Mike.
^ permalink raw reply
* Re: [PATCH v12 02/11] lib: kstrtox: add kstrtoudec64() and kstrtodec64()
From: Rodrigo Alencar @ 2026-05-17 9:10 UTC (permalink / raw)
To: David Laight, Rodrigo Alencar
Cc: rodrigo.alencar, linux-kernel, linux-iio, devicetree, linux-doc,
Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan
In-Reply-To: <20260515202142.5dc561e0@pumpkin>
On 26/05/15 08:21PM, David Laight wrote:
> On Fri, 15 May 2026 17:05:06 +0100
> Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
>
> > On 26/05/13 10:41AM, Rodrigo Alencar wrote:
> > > On 26/05/10 01:42PM, Rodrigo Alencar via B4 Relay wrote:
> > > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > >
> > > > Add helpers that parses decimal numbers into 64-bit number, i.e., decimal
> > > > point numbers with pre-defined scale are parsed into a 64-bit value (fixed
> > > > precision). After the decimal point, digits beyond the specified scale
> > > > are ignored.
> > >
> > > Hi Andy,
> > >
> > > I am starting over here, the other conversation is getting hard to follow.
> > > This is my new proposal...
> >
> > +cc David
>
> I just wouldn't do it this way :-)
>
> You end up with more code than you would get if you just converted the digits.
I am not sure about having more code, most of it is reused. The rest is input
validation and scaling.
...
> > > This function now becomes:
> > >
> > > static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
> > > {
> > > u64 _res = 0;
> > > unsigned int rv_int, rv_frac;
> > >
integer part is parsed here:
> > > rv_int = _parse_integer(s, 10, &_res);
> > > if (rv_int & KSTRTOX_OVERFLOW)
> > > return -ERANGE;
> > > s += rv_int;
> > >
> > > if (*s == '.')
> > > s++; /* skip decimal point */
> > >
fractional part is parsed here:
(combined into the result so we can just skip the decimal point)
> > > rv_frac = _parse_integer_limit_init(s, 10, _res, &_res, scale);
> > > if (rv_frac & KSTRTOX_OVERFLOW)
> > > return -ERANGE;
> > > s += rv_frac;
> > >
input validation on the presence of digits:
(similar to checking rv in _kstrtoull())
> > > if (!rv_int && !rv_frac && !isdigit(*s))
> > > return -EINVAL; /* no digits at all */
> > >
> > > while (isdigit(*s)) /* truncate digits */
> > > s++;
> > >
termination requirement:
> > > if (*s == '\n')
> > > s++;
> > > if (*s)
> > > return -EINVAL;
> > >
result is scaled according to (scale - rv_frac):
> > > if (_res && (scale > (19 + rv_frac) || /* log10(2^64) = 19.26 */
> > > check_mul_overflow(_res, int_pow(10, scale - rv_frac), &_res)))
> > > return -ERANGE;
> > >
> > > *res = _res;
> > > return 0;
> > > }
> > >
That above looks straightforward to me and the tests in the next patch are thorough.
...
> > I have a v13 ready with this. I'll give it a go soon...
> >
>
--
Kind regards,
Rodrigo Alencar
^ permalink raw reply
* [PATCH v13 01/12] dt-bindings: iio: frequency: add adf41513
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:13 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar,
Krzysztof Kozlowski
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
DT-bindings for ADF41513, an ultralow noise PLL frequency synthesizer that
can be used to implement local oscillators (LOs) as high as 26.5 GHz.
Some properties are based upon an existing PLL device properties
(e.g. ADF4350).
Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
.../bindings/iio/frequency/adi,adf41513.yaml | 227 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 234 insertions(+)
diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
new file mode 100644
index 000000000000..f4fae9210382
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
@@ -0,0 +1,227 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/frequency/adi,adf41513.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices ADF41513 PLL Frequency Synthesizer
+
+maintainers:
+ - Rodrigo Alencar <rodrigo.alencar@analog.com>
+
+description:
+ The ADF41513 is an ultralow noise frequency synthesizer that can be used to
+ implement local oscillators (LOs) as high as 26.5 GHz in the upconversion and
+ downconversion sections of wireless receivers and transmitters. The ADF41510
+ supports frequencies up to 10 GHz.
+
+ https://www.analog.com/en/products/adf41510.html
+ https://www.analog.com/en/products/adf41513.html
+
+properties:
+ compatible:
+ enum:
+ - adi,adf41510
+ - adi,adf41513
+
+ reg:
+ maxItems: 1
+
+ spi-max-frequency:
+ maximum: 25000000
+
+ clocks:
+ maxItems: 1
+ description: Clock that provides the reference input frequency.
+
+ avdd1-supply:
+ description: PFD and Up and Down Digital Driver Power Supply (3.3 V)
+
+ avdd2-supply:
+ description: RF Buffer and Prescaler Power Supply (3.3 V)
+
+ avdd3-supply:
+ description: N Divider Power Supply (3.3 V)
+
+ avdd4-supply:
+ description: R Divider and Lock Detector Power Supply (3.3 V)
+
+ avdd5-supply:
+ description: Sigma-Delta Modulator and SPI Power Supply (3.3 V)
+
+ vp-supply:
+ description: Charge Pump Power Supply (3.3 V)
+
+ enable-gpios:
+ description:
+ GPIO that controls the chip enable pin. A logic low on this pin
+ powers down the device and puts the charge pump output into
+ three-state mode.
+ maxItems: 1
+
+ lock-detect-gpios:
+ description:
+ GPIO for lock detect functionality. When configured for digital lock
+ detect, this pin will output a logic high when the PLL is locked.
+ maxItems: 1
+
+ adi,power-up-frequency-mhz:
+ minimum: 1000
+ maximum: 26500
+ default: 10000
+ description:
+ The PLL tunes to this frequency during the initialization sequence.
+ This property should be set to a frequency supported by the loop filter
+ and VCO used in the design. Range is 1 GHz to 26.5 GHz for ADF41513,
+ and 1 GHz to 10 GHz for ADF41510.
+
+ adi,reference-div-factor:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ minimum: 1
+ maximum: 32
+ default: 1
+ description:
+ Value for the reference division factor (R Counter). The driver will
+ increment R Counter as needed to achieve a PFD frequency within the
+ allowed range. High R counter values will reduce the PFD frequency, which
+ lowers the frequency resolution, and affects phase noise performance.
+ As it affects the PFD frequency, this value depends on the loop filter
+ design.
+
+ adi,reference-doubler-enable:
+ description:
+ Enables the reference doubler when deriving the PFD frequency.
+ The maximum reference frequency when the doubler is enabled is 225 MHz.
+ As it affects the PFD frequency, this value depends on the loop filter
+ design.
+ type: boolean
+
+ adi,reference-div2-enable:
+ description:
+ Enables the reference divide-by-2 function when deriving the PFD
+ frequency. As it affects the PFD frequency, this value depends on the
+ loop filter design.
+ type: boolean
+
+ adi,charge-pump-resistor-ohms:
+ minimum: 1800
+ maximum: 10000
+ default: 2700
+ description:
+ External charge pump resistor (R_SET) value in ohms. This sets the maximum
+ charge pump current along with the charge pump current setting.
+
+ adi,charge-pump-current-microamp:
+ minimum: 81
+ maximum: 7200
+ description:
+ Charge pump current (I_CP) in microamps. The value will be rounded to the
+ nearest supported value. Range of acceptable values depends on the
+ charge pump resistor value, such that 810 mV <= I_CP * R_SET <= 12960 mV.
+ This value depends on the loop filter and the VCO design.
+
+ adi,logic-level-1v8-enable:
+ description:
+ Set MUXOUT and DLD logic levels to 1.8V. Default is 3.3V.
+ type: boolean
+
+ adi,phase-detector-polarity-positive-enable:
+ description:
+ Set phase detector polarity to positive. Default is negative.
+ Use positive polarity with non-inverting loop filter and VCO with
+ positive tuning slope, or with inverting loop filter and VCO with
+ negative tuning slope.
+ type: boolean
+
+ adi,lock-detector-count:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ default: 64
+ description:
+ Sets the value for Lock Detector count of the PLL, which determines the
+ number of consecutive phase detector cycles that must be within the lock
+ detector window before lock is declared. Lower values increase the lock
+ detection sensitivity, while higher values provides a more stable lock
+ detection. Applications that consume the lock detect signal may require
+ different settings based on system requirements.
+ enum: [2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192]
+
+ adi,phase-resync-period-ns:
+ default: 0
+ description:
+ When this value is non-zero, enable phase resync functionality, which
+ produces a consistent output phase offset with respect to the input
+ reference. The value specifies the resync period in nanoseconds, used
+ to configure clock dividers with respect to the PFD frequency. This value
+ should be set to a value that is at least as long as the worst case lock
+ time, i.e., it depends mostly on the loop filter design.
+
+ adi,le-sync-enable:
+ description:
+ Synchronizes Load Enable (LE) transitions with the reference signal to
+ avoid asynchronous glitches in the output. This is recommended when using
+ the PLL as a frequency synthesizer, where the reference signal will always
+ be present while the device is being configured. When using the PLL as a
+ frequency tracker, where the reference signal may be absent, LE sync
+ should be left disabled.
+ type: boolean
+
+dependencies:
+ adi,charge-pump-resistor-ohms: [ 'adi,charge-pump-current-microamp' ]
+
+required:
+ - compatible
+ - reg
+ - clocks
+ - avdd1-supply
+ - avdd2-supply
+ - avdd3-supply
+ - avdd4-supply
+ - avdd5-supply
+ - vp-supply
+
+allOf:
+ - $ref: /schemas/spi/spi-peripheral-props.yaml#
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: adi,adf41510
+ then:
+ properties:
+ adi,power-up-frequency-mhz:
+ maximum: 10000
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/gpio/gpio.h>
+ spi {
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ pll@0 {
+ compatible = "adi,adf41513";
+ reg = <0>;
+ spi-max-frequency = <25000000>;
+ clocks = <&ref_clk>;
+ avdd1-supply = <&avdd1_3v3>;
+ avdd2-supply = <&avdd2_3v3>;
+ avdd3-supply = <&avdd3_3v3>;
+ avdd4-supply = <&avdd4_3v3>;
+ avdd5-supply = <&avdd5_3v3>;
+ vp-supply = <&vp_3v3>;
+ enable-gpios = <&gpio0 10 GPIO_ACTIVE_HIGH>;
+ lock-detect-gpios = <&gpio0 11 GPIO_ACTIVE_HIGH>;
+
+ adi,power-up-frequency-mhz = <15500>;
+ adi,charge-pump-current-microamp = <3600>;
+ adi,charge-pump-resistor-ohms = <2700>;
+ adi,reference-doubler-enable;
+ adi,lock-detector-count = <64>;
+ adi,phase-resync-period-ns = <0>;
+ adi,phase-detector-polarity-positive-enable;
+ adi,le-sync-enable;
+ };
+ };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 3115538ce829..e1c3a26a6e2f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1662,6 +1662,13 @@ W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/adc/adi,ade9000.yaml
F: drivers/iio/adc/ade9000.c
+ANALOG DEVICES INC ADF41513 DRIVER
+M: Rodrigo Alencar <rodrigo.alencar@analog.com>
+L: linux-iio@vger.kernel.org
+S: Supported
+W: https://ez.analog.com/linux-software-drivers
+F: Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
+
ANALOG DEVICES INC ADF4377 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
L: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related
* [PATCH v13 00/12] ADF41513/ADF41510 PLL frequency synthesizers
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:13 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar,
Krzysztof Kozlowski
This patch series adds support for the Analog Devices ADF41513 and ADF41510
ultralow noise PLL frequency synthesizers. These devices are designed for
implementing local oscillators (LOs) in high-frequency applications.
The ADF41513 covers frequencies from 1 GHz to 26.5 GHz, while the ADF41510
operates from 1 GHz to 10 GHz.
Key features supported by this driver:
- Integer-N and fractional-N operation modes
- High maximum PFD frequency (250 MHz integer-N, 125 MHz fractional-N)
- 25-bit fixed modulus or 49-bit variable modulus fractional modes
- Digital lock detect functionality
- Phase resync capability for consistent output phase
- Load Enable vs Reference signal syncronization
The series includes:
1. PLL driver implementation
2. Device tree bindings documentation
3. IIO ABI documentation
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Changes in v13:
- Introduce _parse_integer_limit_init()
- Better support for 0 and larger scales in kstrtodec64()
- Increase test scope for kstrtodec64()
- Fix LSB_P1 check at startup when going for fixed modulus mode.
- Clear phase adjust when setting phase (It turns out sashiko was correct).
- Make sure Load Enable is not selected when powering up.
- Address some minor comments.
- Link to v12: https://lore.kernel.org/r/20260510-adf41513-iio-driver-v12-0-34af2ed2779f@analog.com
Changes in v12:
- Contraint charge pump current.
- Fix division-by-zero issues.
- Address PM and sysfs powerdown conflicts.
- Program proper phase resync value in clk divider mode.
- Link to v11: https://lore.kernel.org/r/20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@analog.com
Changes in v11:
- Cleanup ext info attribute read/write callbacks.
- Adjust attribute names in the documentation.
- Turn s64 compose macros into static inline functions.
- Link to v10: https://lore.kernel.org/r/20260415-adf41513-iio-driver-v10-0-df61046d5457@analog.com
Changes in v10:
- Drop simple_strntoull() changes
- Create kstrtodec64() and kstrtoudec64() helpers.
- Add IIO value format for 64-bit decimal values.
- PLL driver code implements new decimal format for frequency attr.
- Link to v9: https://lore.kernel.org/r/20260320-adf41513-iio-driver-v9-0-132f0d076374@analog.com
Changes in v9:
- Expose simple_strntoull() in a safer prototype instead of new kstrntoull()
- Link to v8: https://lore.kernel.org/r/20260303-adf41513-iio-driver-v8-0-8dd2417cc465@analog.com
Changes in v8:
- Add new function kstrntoull() to lib/kstrtox.c and tests to lib/test-kstrtox.c.
- Drop custom iio u64 parser, replacing it for kstrntoull().
- Dedicated MAINTAINERS entry for drivers/iio/test/iio-test-fixpoint-parse.c.
- Link to v7: https://lore.kernel.org/r/20260216-adf41513-iio-driver-v7-0-b0ed387ab559@analog.com
Changes in v7:
- Addressed minor suggestions.
- frequency_resolution ABI for AD4350 removed in favor of generic one.
- Link to v6: https://lore.kernel.org/r/20260130-adf41513-iio-driver-v6-0-cf46239026bc@analog.com
Changes in v6:
- Drop usage of simple_strtoull().
- Implement better overflow checks with iio_safe_strntou64().
- Link to v5: https://lore.kernel.org/r/20260123-adf41513-iio-driver-v5-0-2dce812a2dda@analog.com
Changes in v5:
- Drop local parsing of 64-bit plus fractional parts
- Add iio_str_to_fixpoint64() to iio core with parsing tests
- Add DT property dependency for adi,charge-pump-resistor-ohms
- Add local definition for ADF41513_HZ_PER_GHZ and drop units.h patch
- Link to v4: https://lore.kernel.org/r/20260116-adf41513-iio-driver-v4-0-dbb7d6782217@analog.com
Changes in v4:
- Proper usage of units.h macros
- Simplifications to DT property parsing
- Adjustments to return value handling
- Drop of simple DT property node example
- Link to v3: https://lore.kernel.org/r/20260108-adf41513-iio-driver-v3-0-23d1371aef48@analog.com
Changes in v3:
- Use FIELD_MODIFY macro in driver implementation
- Drop refin_frequency iio attribute
- Drop muxout-select property from dt-bindings (and rename logic-level property)
- Use -mhz suffix in power-up frequency property
- Address documentation issues
- Link to v2: https://lore.kernel.org/r/20251219-adf41513-iio-driver-v2-0-be29a83d5793@analog.com
Changes in v2:
- separate driver implementation from extra features and improve commit messages
- use macros from units.h
- explanation of custom parse function: adf41513_parse_uhz
- reorganize driver data structures
- drop clock framework support for now
- reorganize documentation
- Link to v1: https://lore.kernel.org/r/20251110-adf41513-iio-driver-v1-0-2df8be0fdc6e@analog.com
---
Rodrigo Alencar (12):
dt-bindings: iio: frequency: add adf41513
iio: kstrtox: add local _parse_integer_limit_init() helper
lib: kstrtox: add kstrtoudec64() and kstrtodec64()
lib: test-kstrtox: tests for kstrtodec64() and kstrtoudec64()
lib: math: div64: add div64_s64_rem()
iio: core: add decimal value formatting into 64-bit value
iio: test: iio-test-format: add test case for decimal format
iio: frequency: adf41513: driver implementation
iio: frequency: adf41513: handle LE synchronization feature
iio: frequency: adf41513: features on frequency change
docs: iio: add documentation for adf41513 driver
Documentation: ABI: testing: add common ABI file for iio/frequency
Documentation/ABI/testing/sysfs-bus-iio-frequency | 11 +
.../ABI/testing/sysfs-bus-iio-frequency-adf4350 | 10 -
.../bindings/iio/frequency/adi,adf41513.yaml | 227 ++++
Documentation/iio/adf41513.rst | 199 ++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 9 +
drivers/iio/frequency/Kconfig | 10 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf41513.c | 1240 ++++++++++++++++++++
drivers/iio/industrialio-core.c | 46 +-
drivers/iio/test/iio-test-format.c | 97 +-
include/linux/iio/types.h | 29 +
include/linux/kstrtox.h | 3 +
include/linux/math64.h | 18 +
lib/kstrtox.c | 139 ++-
lib/math/div64.c | 15 +
lib/test-kstrtox.c | 182 +++
17 files changed, 2184 insertions(+), 53 deletions(-)
---
base-commit: 1548c54e9adc32a719499216f63fba14b2fc07c3
change-id: 20251110-adf41513-iio-driver-aaca8a7f808e
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply
* [PATCH v13 02/12] iio: kstrtox: add local _parse_integer_limit_init() helper
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:13 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add parsing helper that accepts an initial value for the accumulated
result when parsing an 64-bit integer. It reuses current implementation
for _parse_integer_limit(), which now consumes the new function with
init = 0. The diff algorithm would have the documentation header and
prototype of _parse_integer_limit() moved around so it is adjusted
according to guidelines.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
lib/kstrtox.c | 39 ++++++++++++++++++++++++++-------------
1 file changed, 26 insertions(+), 13 deletions(-)
diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index 97be2a39f537..0705461f51c0 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -39,23 +39,15 @@ const char *_parse_integer_fixup_radix(const char *s, unsigned int *base)
return s;
}
-/*
- * Convert non-negative integer string representation in explicitly given radix
- * to an integer. A maximum of max_chars characters will be converted.
- *
- * Return number of characters consumed maybe or-ed with overflow bit.
- * If overflow occurs, result integer (incorrect) is still returned.
- *
- * Don't you dare use this function.
- */
-noinline
-unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned long long *p,
- size_t max_chars)
+static unsigned int _parse_integer_limit_init(const char *s, unsigned int base,
+ unsigned long long init,
+ unsigned long long *p,
+ size_t max_chars)
{
unsigned long long res;
unsigned int rv;
- res = 0;
+ res = init;
rv = 0;
while (max_chars--) {
unsigned int c = *s;
@@ -87,6 +79,27 @@ unsigned int _parse_integer_limit(const char *s, unsigned int base, unsigned lon
return rv;
}
+/**
+ * _parse_integer_limit() - Convert integer string representation to an integer
+ * limiting the number of characters parsed.
+ * @s: The start of the string.
+ * @base: The number base to use.
+ * @p: Where to write the result of the conversion.
+ * @max_chars: Maximum amount of characters to consume.
+ *
+ * Convert non-negative integer string representation in explicitly given radix
+ * to an integer. A maximum of max_chars characters will be converted.
+ *
+ * Return: Number of characters consumed maybe or-ed with overflow bit.
+ * If overflow occurs, result integer (incorrect) is still returned.
+ */
+noinline
+unsigned int _parse_integer_limit(const char *s, unsigned int base,
+ unsigned long long *p, size_t max_chars)
+{
+ return _parse_integer_limit_init(s, base, 0, p, max_chars);
+}
+
noinline
unsigned int _parse_integer(const char *s, unsigned int base, unsigned long long *p)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v13 04/12] lib: test-kstrtox: tests for kstrtodec64() and kstrtoudec64()
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:13 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add tests for decimal parsing helpers kstrtodec64() and kstrtoudec64().
The test infrastructure is reused from other kstrto*() functions, i.e.,
the decimal parsers have fixed base of 10, so base field is used as
scale input for the helpers.
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
lib/test-kstrtox.c | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 182 insertions(+)
diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c
index ee87fef66cb5..145d268bbccc 100644
--- a/lib/test-kstrtox.c
+++ b/lib/test-kstrtox.c
@@ -703,6 +703,182 @@ static void __init test_kstrtos8_fail(void)
TEST_FAIL(kstrtos8, s8, "%hhd", test_s8_fail);
}
+static void __init test_kstrtoudec64_ok(void)
+{
+ DECLARE_TEST_OK(u64, struct test_udec64);
+ static DEFINE_TEST_OK(struct test_udec64, test_udec64_ok) = {
+ /* basic: integer.fraction, exact digits */
+ {"0.0", 1, 0},
+ {"1.5", 1, 15},
+ {"1.234", 3, 1234},
+ {"42.0", 1, 420},
+ /* zero */
+ {"0.0", 1, 0},
+ {"0.000", 3, 0},
+ /* integer only */
+ {"0", 1, 0},
+ {"42", 3, 42000},
+ {"123.", 2, 12300},
+ {"1", 1, 10},
+ /* fractional only (leading dot) */
+ {".5", 1, 5},
+ {".5", 0, 0},
+ {".123", 3, 123},
+ {".001", 3, 1},
+ /* zero padding: fewer fractional digits than scale */
+ {"1.2", 3, 1200},
+ {"1.2", 6, 1200000},
+ {"0.01", 3, 10},
+ {"0.1", 9, 100000000ULL},
+ {"0.01", 9, 10000000},
+ /* truncation: more fractional digits than scale */
+ {"1.23456", 3, 1234},
+ {"3.1415926535", 6, 3141592},
+ {"0.999999999", 3, 999},
+ {"1.99", 1, 19},
+ {"1.234", 0, 1},
+ /* trailing newline */
+ {"1.5\n", 1, 15},
+ {"42\n", 3, 42000},
+ /* plus sign */
+ {"+1.5", 1, 15},
+ {"+.5", 1, 5},
+ /* scale progression */
+ {"1.", 0, 1},
+ {"1.0", 1, 10},
+ {"1.00", 2, 100},
+ {"1.000", 3, 1000},
+ {"1.000000", 6, 1000000},
+ {"1.000000000", 9, 1000000000ULL},
+ /* max limit check */
+ {"18446744073.709551615", 9, ULLONG_MAX},
+ {"18446744073709.551615", 6, ULLONG_MAX},
+ {"0.18446744073709551615", 20, ULLONG_MAX},
+ /* scale > 19: representable when integer part is small */
+ {"0.00000000000000000001", 20, 1},
+ {"0.1", 20, 10000000000000000000ULL},
+ {"0.00000000000000000000001", 23, 1},
+ /* truncation with scale > 19 */
+ {"0.0000000000000000000012345", 23, 123},
+ /* truncation with many excess digits */
+ {"0.00000000000000000000000000000000423", 34, 42},
+ {"1.99999999999999999999999999999999999", 3, 1999},
+ };
+ TEST_OK(kstrtoudec64, u64, "%llu", test_udec64_ok);
+}
+
+static void __init test_kstrtoudec64_fail(void)
+{
+ static DEFINE_TEST_FAIL(test_udec64_fail) = {
+ /* empty / whitespace */
+ {"", 3},
+ {"\n", 3},
+ /* minus sign (unsigned) */
+ {"-1.5", 1},
+ {"-0.5", 1},
+ /* only a decimal point */
+ {".", 3},
+ {".", 0},
+ /* only a sign */
+ {"+", 3},
+ /* non-digit characters */
+ {"abc", 3},
+ {"1.2x", 3},
+ /* leading/trailing space */
+ {" 1.5", 1},
+ {"1.5 ", 1},
+ /* overflow */
+ {"18446744073710.551615", 6},
+ {"99999999999999999999", 1},
+ /* overflow with scale > 19 */
+ {"1.0", 21},
+ {"0.2", 20},
+ {"0.18446744073709551616", 20},
+ {"1", 20},
+ };
+ TEST_FAIL(kstrtoudec64, u64, "%llu", test_udec64_fail);
+}
+
+static void __init test_kstrtodec64_ok(void)
+{
+ DECLARE_TEST_OK(s64, struct test_dec64);
+ static DEFINE_TEST_OK(struct test_dec64, test_dec64_ok) = {
+ /* basic positive */
+ {"0.0", 1, 0},
+ {"1.5", 1, 15},
+ {"1.234", 3, 1234},
+ /* basic negative */
+ {"-1.5", 1, -15},
+ {"-1.234", 3, -1234},
+ {"-0.5", 1, -5},
+ {"-0.001", 3, -1},
+ /* zero (signed) */
+ {"-0", 1, 0},
+ {"-0.0", 1, 0},
+ {"0.000", 3, 0},
+ /* integer only */
+ {"42", 3, 42000},
+ {"-42", 3, -42000},
+ /* fractional only */
+ {".5", 1, 5},
+ {"-.5", 1, -5},
+ /* zero padding */
+ {"1.2", 3, 1200},
+ {"-1.2", 3, -1200},
+ {"0.01", 3, 10},
+ {"-0.01", 3, -10},
+ /* truncation */
+ {"1.23456", 3, 1234},
+ {"-1.23456", 3, -1234},
+ {"0.999999999", 3, 999},
+ {"-0.999999999", 3, -999},
+ /* trailing newline */
+ {"1.5\n", 1, 15},
+ {"-1.5\n", 1, -15},
+ /* plus sign */
+ {"+1.5", 1, 15},
+ /* limits */
+ {"9223372036.854775807", 9, LLONG_MAX},
+ {"-9223372036.854775808", 9, LLONG_MIN},
+ /* scale > 19 */
+ {"0.0", 20, 0},
+ {"-0.0", 20, 0},
+ {"0.00000000000000000001", 20, 1},
+ {"-0.00000000000000000001", 20, -1},
+ {"0.009223372036854775807", 21, LLONG_MAX},
+ {"-0.009223372036854775808", 21, LLONG_MIN},
+ };
+ TEST_OK(kstrtodec64, s64, "%lld", test_dec64_ok);
+}
+
+static void __init test_kstrtodec64_fail(void)
+{
+ static DEFINE_TEST_FAIL(test_dec64_fail) = {
+ /* empty / whitespace */
+ {"", 3},
+ {"\n", 3},
+ /* no digits after dot */
+ {".", 3},
+ {"-.", 3},
+ /* no digits at all */
+ {"+", 3},
+ {"-", 3},
+ /* non-digit characters */
+ {"abc", 3},
+ {"-1.2x", 3},
+ /* signed overflow */
+ {"9223372036.854775808", 9},
+ {"-9223372036.854775809", 9},
+ {"99999999999999999999", 1},
+ /* signed overflow with scale > 19 */
+ {"0.1", 20},
+ {"-0.1", 20},
+ {"0.09223372036854775808", 20},
+ {"-0.09223372036854775809", 20},
+ };
+ TEST_FAIL(kstrtodec64, s64, "%lld", test_dec64_fail);
+}
+
static int __init test_kstrtox_init(void)
{
test_kstrtoull_ok();
@@ -729,6 +905,12 @@ static int __init test_kstrtox_init(void)
test_kstrtou8_fail();
test_kstrtos8_ok();
test_kstrtos8_fail();
+
+ test_kstrtoudec64_ok();
+ test_kstrtoudec64_fail();
+ test_kstrtodec64_ok();
+ test_kstrtodec64_fail();
+
return -EINVAL;
}
module_init(test_kstrtox_init);
--
2.43.0
^ permalink raw reply related
* [PATCH v13 03/12] lib: kstrtox: add kstrtoudec64() and kstrtodec64()
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:13 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add helpers that parses decimal numbers into 64-bit number, i.e., decimal
point numbers with pre-defined scale are parsed into a 64-bit value (fixed
precision). After the decimal point, digits beyond the specified scale
are ignored.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
include/linux/kstrtox.h | 3 ++
lib/kstrtox.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 103 insertions(+)
diff --git a/include/linux/kstrtox.h b/include/linux/kstrtox.h
index 6ea897222af1..bec2fc17bde0 100644
--- a/include/linux/kstrtox.h
+++ b/include/linux/kstrtox.h
@@ -97,6 +97,9 @@ int __must_check kstrtou8(const char *s, unsigned int base, u8 *res);
int __must_check kstrtos8(const char *s, unsigned int base, s8 *res);
int __must_check kstrtobool(const char *s, bool *res);
+int __must_check kstrtoudec64(const char *s, unsigned int scale, u64 *res);
+int __must_check kstrtodec64(const char *s, unsigned int scale, s64 *res);
+
int __must_check kstrtoull_from_user(const char __user *s, size_t count, unsigned int base, unsigned long long *res);
int __must_check kstrtoll_from_user(const char __user *s, size_t count, unsigned int base, long long *res);
int __must_check kstrtoul_from_user(const char __user *s, size_t count, unsigned int base, unsigned long *res);
diff --git a/lib/kstrtox.c b/lib/kstrtox.c
index 0705461f51c0..71862b40db74 100644
--- a/lib/kstrtox.c
+++ b/lib/kstrtox.c
@@ -17,6 +17,7 @@
#include <linux/export.h>
#include <linux/kstrtox.h>
#include <linux/math64.h>
+#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/uaccess.h>
@@ -405,6 +406,105 @@ int kstrtobool(const char *s, bool *res)
}
EXPORT_SYMBOL(kstrtobool);
+static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+{
+ u64 _res = 0;
+ unsigned int rv_int, rv_frac;
+
+ rv_int = _parse_integer(s, 10, &_res);
+ if (rv_int & KSTRTOX_OVERFLOW)
+ return -ERANGE;
+ s += rv_int;
+
+ if (*s == '.')
+ s++; /* skip decimal point */
+
+ rv_frac = _parse_integer_limit_init(s, 10, _res, &_res, scale);
+ if (rv_frac & KSTRTOX_OVERFLOW)
+ return -ERANGE;
+ s += rv_frac;
+
+ if (!rv_int && !rv_frac && !isdigit(*s))
+ return -EINVAL; /* no digits at all */
+
+ while (isdigit(*s)) /* truncate digits */
+ s++;
+
+ if (*s == '\n')
+ s++;
+ if (*s)
+ return -EINVAL;
+
+ if (_res && (scale > (19 + rv_frac) || /* log10(2^64) = 19.26 */
+ check_mul_overflow(_res, int_pow(10, scale - rv_frac), &_res)))
+ return -ERANGE;
+
+ *res = _res;
+ return 0;
+}
+
+/**
+ * kstrtoudec64() - Convert a string to an unsigned 64-bit value that represents
+ * a scaled decimal number.
+ * @s: The start of the string. The string must be null-terminated, and may also
+ * include a single newline before its terminating null. The first character
+ * may also be a plus sign, but not a minus sign. Digits beyond the specified
+ * scale are ignored.
+ * @scale: The number of digits to the right of the decimal point. For example,
+ * a scale of 2 would mean the number is represented with two decimal places,
+ * so "123.45" would be represented as 12345.
+ * @res: Where to write the result of the conversion on success.
+ *
+ * Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
+ */
+noinline
+int kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+{
+ if (s[0] == '+')
+ s++;
+ return _kstrtoudec64(s, scale, res);
+}
+EXPORT_SYMBOL(kstrtoudec64);
+
+/**
+ * kstrtodec64() - Convert a string to a signed 64-bit value that represents a
+ * scaled decimal number.
+ * @s: The start of the string. The string must be null-terminated, and may also
+ * include a single newline before its terminating null. The first character
+ * may also be a plus sign or a minus sign. Digits beyond the specified scale
+ * are ignored.
+ * @scale: The number of digits to the right of the decimal point. For example,
+ * a scale of 5 would mean the number is represented with five decimal places,
+ * so "-3.141592" would be represented as -314159.
+ * @res: Where to write the result of the conversion on success.
+ *
+ * Return: 0 on success, -ERANGE on overflow and -EINVAL on parsing error.
+ */
+noinline
+int kstrtodec64(const char *s, unsigned int scale, s64 *res)
+{
+ u64 tmp;
+ int rv;
+
+ if (s[0] == '-') {
+ rv = _kstrtoudec64(s + 1, scale, &tmp);
+ if (rv < 0)
+ return rv;
+ if ((s64)-tmp > 0)
+ return -ERANGE;
+ *res = -tmp;
+ } else {
+ rv = kstrtoudec64(s, scale, &tmp);
+ if (rv < 0)
+ return rv;
+ if ((s64)tmp < 0)
+ return -ERANGE;
+ *res = tmp;
+ }
+ return 0;
+}
+EXPORT_SYMBOL(kstrtodec64);
+
/*
* Since "base" would be a nonsense argument, this open-codes the
* _from_user helper instead of using the helper macro below.
--
2.43.0
^ permalink raw reply related
* [PATCH v13 05/12] lib: math: div64: add div64_s64_rem()
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add div64_s64_rem() function, with 32-bit implementation that uses
div64_u64_rem() and a branchless approach to resolve the sign of the
remainder and quotient (negation in two's complement).
Reviewed-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
include/linux/math64.h | 18 ++++++++++++++++++
lib/math/div64.c | 15 +++++++++++++++
2 files changed, 33 insertions(+)
diff --git a/include/linux/math64.h b/include/linux/math64.h
index cc305206d89f..99189410d4bb 100644
--- a/include/linux/math64.h
+++ b/include/linux/math64.h
@@ -57,6 +57,20 @@ static inline u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder)
return dividend / divisor;
}
+/**
+ * div64_s64_rem - signed 64bit divide with 64bit divisor and remainder
+ * @dividend: signed 64bit dividend
+ * @divisor: signed 64bit divisor
+ * @remainder: pointer to signed 64bit remainder
+ *
+ * Return: sets ``*remainder``, then returns dividend / divisor
+ */
+static inline s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)
+{
+ *remainder = dividend % divisor;
+ return dividend / divisor;
+}
+
/**
* div64_u64 - unsigned 64bit divide with 64bit divisor
* @dividend: unsigned 64bit dividend
@@ -102,6 +116,10 @@ extern s64 div_s64_rem(s64 dividend, s32 divisor, s32 *remainder);
extern u64 div64_u64_rem(u64 dividend, u64 divisor, u64 *remainder);
#endif
+#ifndef div64_s64_rem
+extern s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder);
+#endif
+
#ifndef div64_u64
extern u64 div64_u64(u64 dividend, u64 divisor);
#endif
diff --git a/lib/math/div64.c b/lib/math/div64.c
index d1e92ea24fce..0b10ded09a9b 100644
--- a/lib/math/div64.c
+++ b/lib/math/div64.c
@@ -158,6 +158,21 @@ u64 div64_u64(u64 dividend, u64 divisor)
EXPORT_SYMBOL(div64_u64);
#endif
+#ifndef div64_s64_rem
+s64 div64_s64_rem(s64 dividend, s64 divisor, s64 *remainder)
+{
+ s64 quot, t, rem;
+
+ quot = div64_u64_rem(abs(dividend), abs(divisor), (u64 *)&rem);
+ t = dividend >> 63;
+ *remainder = (rem ^ t) - t;
+ t = (dividend ^ divisor) >> 63;
+
+ return (quot ^ t) - t;
+}
+EXPORT_SYMBOL(div64_s64_rem);
+#endif
+
#ifndef div64_s64
s64 div64_s64(s64 dividend, s64 divisor)
{
--
2.43.0
^ permalink raw reply related
* [PATCH v13 06/12] iio: core: add decimal value formatting into 64-bit value
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Create new format types for iio values (IIO_VAL_DECIMAL64_*), which
defines the representation of fixed decimal point values into a single
64-bit number. This new format increases the range of represented values,
allowing for integer parts greater than 2^32, as bits are not "wasted"
in the fractional part, which can be seen in IIO_VAL_INT_PLUS_MICRO and
IIO_VAL_INT_PLUS_NANO. Helpers are created to compose and decompose 64-bit
decimals into integer values used in IIO formatting interfaces, which
creates consistency and avoid error-prone manual assignments when using
wordpart macros. When doing the parsing, kstrtodec64() is used with the
scale defined by the specific decimal format type.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/industrialio-core.c | 46 ++++++++++++++++++++++++++++++++++-------
include/linux/iio/types.h | 29 ++++++++++++++++++++++++++
2 files changed, 67 insertions(+), 8 deletions(-)
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index bd6f4f9f4533..336aedc1fbab 100644
--- a/drivers/iio/industrialio-core.c
+++ b/drivers/iio/industrialio-core.c
@@ -19,6 +19,7 @@
#include <linux/idr.h>
#include <linux/kdev_t.h>
#include <linux/kernel.h>
+#include <linux/math64.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
@@ -26,7 +27,6 @@
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/wait.h>
-#include <linux/wordpart.h>
#include <linux/iio/buffer.h>
#include <linux/iio/buffer_impl.h>
@@ -707,8 +707,26 @@ static ssize_t __iio_format_value(char *buf, size_t offset, unsigned int type,
case IIO_VAL_CHAR:
return sysfs_emit_at(buf, offset, "%c", (char)vals[0]);
case IIO_VAL_INT_64:
- tmp2 = (s64)((((u64)vals[1]) << 32) | (u32)vals[0]);
+ tmp2 = iio_val_s64_from_s32s(vals);
return sysfs_emit_at(buf, offset, "%lld", tmp2);
+ case IIO_VAL_DECIMAL64_MILLI:
+ case IIO_VAL_DECIMAL64_MICRO:
+ case IIO_VAL_DECIMAL64_NANO:
+ case IIO_VAL_DECIMAL64_PICO:
+ {
+ int scale = type - IIO_VAL_DECIMAL64_BASE;
+ int l = 0;
+ s64 frac;
+
+ tmp2 = div64_s64_rem(iio_val_s64_from_s32s(vals),
+ int_pow(10, scale), &frac);
+ if (tmp2 == 0 && frac < 0)
+ l += sysfs_emit_at(buf, offset, "-");
+
+ l += sysfs_emit_at(buf, offset + l, "%lld.%0*lld", tmp2, scale,
+ abs(frac));
+ return l;
+ }
default:
return 0;
}
@@ -978,6 +996,7 @@ static ssize_t iio_write_channel_info(struct device *dev,
struct iio_dev *indio_dev = dev_to_iio_dev(dev);
struct iio_dev_attr *this_attr = to_iio_dev_attr(attr);
int ret, fract_mult = 100000;
+ int type, dec_scale = 0;
int integer, fract = 0;
long long integer64;
bool is_char = false;
@@ -988,9 +1007,11 @@ static ssize_t iio_write_channel_info(struct device *dev,
if (!indio_dev->info->write_raw)
return -EINVAL;
- if (indio_dev->info->write_raw_get_fmt)
- switch (indio_dev->info->write_raw_get_fmt(indio_dev,
- this_attr->c, this_attr->address)) {
+ if (indio_dev->info->write_raw_get_fmt) {
+ type = indio_dev->info->write_raw_get_fmt(indio_dev,
+ this_attr->c,
+ this_attr->address);
+ switch (type) {
case IIO_VAL_INT:
fract_mult = 0;
break;
@@ -1006,12 +1027,19 @@ static ssize_t iio_write_channel_info(struct device *dev,
case IIO_VAL_CHAR:
is_char = true;
break;
+ case IIO_VAL_DECIMAL64_MILLI:
+ case IIO_VAL_DECIMAL64_MICRO:
+ case IIO_VAL_DECIMAL64_NANO:
+ case IIO_VAL_DECIMAL64_PICO:
+ dec_scale = type - IIO_VAL_DECIMAL64_BASE;
+ fallthrough;
case IIO_VAL_INT_64:
is_64bit = true;
break;
default:
return -EINVAL;
}
+ }
if (is_char) {
char ch;
@@ -1020,12 +1048,14 @@ static ssize_t iio_write_channel_info(struct device *dev,
return -EINVAL;
integer = ch;
} else if (is_64bit) {
- ret = kstrtoll(buf, 0, &integer64);
+ if (dec_scale)
+ ret = kstrtodec64(buf, dec_scale, &integer64);
+ else
+ ret = kstrtoll(buf, 0, &integer64);
if (ret)
return ret;
- fract = upper_32_bits(integer64);
- integer = lower_32_bits(integer64);
+ iio_val_s64_decompose(integer64, &integer, &fract);
} else {
ret = __iio_str_to_fixpoint(buf, fract_mult, &integer, &fract,
scale_db);
diff --git a/include/linux/iio/types.h b/include/linux/iio/types.h
index 4e3099defc1d..16e0a0d002ac 100644
--- a/include/linux/iio/types.h
+++ b/include/linux/iio/types.h
@@ -7,6 +7,8 @@
#ifndef _IIO_TYPES_H_
#define _IIO_TYPES_H_
+#include <linux/wordpart.h>
+
#include <uapi/linux/iio/types.h>
enum iio_event_info {
@@ -34,6 +36,33 @@ enum iio_event_info {
#define IIO_VAL_FRACTIONAL_LOG2 11
#define IIO_VAL_CHAR 12
+#define IIO_VAL_DECIMAL64_BASE 32
+#define IIO_VAL_DECIMAL64_MILLI (IIO_VAL_DECIMAL64_BASE + 3)
+#define IIO_VAL_DECIMAL64_MICRO (IIO_VAL_DECIMAL64_BASE + 6)
+#define IIO_VAL_DECIMAL64_NANO (IIO_VAL_DECIMAL64_BASE + 9)
+#define IIO_VAL_DECIMAL64_PICO (IIO_VAL_DECIMAL64_BASE + 12)
+
+static inline s64 iio_val_s64_compose(s32 val0, s32 val1)
+{
+ return (s64)(((u64)val1 << 32) | (u32)val0);
+}
+
+static inline s64 iio_val_s64_from_s32s(const s32 *vals)
+{
+ return iio_val_s64_compose(vals[0], vals[1]);
+}
+
+static inline void iio_val_s64_decompose(s64 dec64, s32 *val0, s32 *val1)
+{
+ *val0 = lower_32_bits(dec64);
+ *val1 = upper_32_bits(dec64);
+}
+
+static inline void iio_val_s64_to_s32s(s64 dec64, s32 *vals)
+{
+ iio_val_s64_decompose(dec64, &vals[0], &vals[1]);
+}
+
enum iio_available_type {
IIO_AVAIL_LIST,
IIO_AVAIL_RANGE,
--
2.43.0
^ permalink raw reply related
* [PATCH v13 07/12] iio: test: iio-test-format: add test case for decimal format
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add iio_test_iio_format_value_decimal_64() kunit test case for decimal
value formatting, exploring different scales types. Also, the same
iio_val_s64_array_populate() macro used to populate local array is used in
iio_test_iio_format_value_integer_64().
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/test/iio-test-format.c | 97 +++++++++++++++++++++++++++++---------
1 file changed, 75 insertions(+), 22 deletions(-)
diff --git a/drivers/iio/test/iio-test-format.c b/drivers/iio/test/iio-test-format.c
index 872dd8582003..1920dee3bfb0 100644
--- a/drivers/iio/test/iio-test-format.c
+++ b/drivers/iio/test/iio-test-format.c
@@ -200,56 +200,108 @@ static void iio_test_iio_format_value_multiple(struct kunit *test)
static void iio_test_iio_format_value_integer_64(struct kunit *test)
{
int values[2];
- s64 value;
char *buf;
int ret;
buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
- value = 24;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(24, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "24\n");
- value = -24;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(-24, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-24\n");
- value = 0;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(0, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0\n");
- value = UINT_MAX;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(UINT_MAX, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "4294967295\n");
- value = -((s64)UINT_MAX);
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(-((s64)UINT_MAX), values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-4294967295\n");
- value = LLONG_MAX;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(LLONG_MAX, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854775807\n");
- value = LLONG_MIN;
- values[0] = lower_32_bits(value);
- values[1] = upper_32_bits(value);
+ iio_val_s64_to_s32s(LLONG_MIN, values);
ret = iio_format_value(buf, IIO_VAL_INT_64, ARRAY_SIZE(values), values);
IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854775808\n");
}
+static void iio_test_iio_format_value_decimal_64(struct kunit *test)
+{
+ int values[2];
+ char *buf;
+ int ret;
+
+ buf = kunit_kmalloc(test, PAGE_SIZE, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+
+ /* DECIMAL64_MILLI: positive >= 1, value 1.234 */
+ iio_val_s64_to_s32s(1234, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.234\n");
+
+ /* DECIMAL64_MICRO: positive >= 1, value 3.141592 */
+ iio_val_s64_to_s32s(3141592, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "3.141592\n");
+
+ /* DECIMAL64_MILLI: positive < 1, value 0.042 */
+ iio_val_s64_to_s32s(42, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.042\n");
+
+ /* DECIMAL64_MILLI: negative <= -1, value -1.234 */
+ iio_val_s64_to_s32s(-1234, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-1.234\n");
+
+ /* DECIMAL64_MILLI: negative > -1, value -0.123 */
+ iio_val_s64_to_s32s(-123, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-0.123\n");
+
+ /* DECIMAL64_MILLI: zero */
+ iio_val_s64_to_s32s(0, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MILLI, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "0.000\n");
+
+ /* DECIMAL64_NANO: value 1.000000001 */
+ iio_val_s64_to_s32s(1000000001, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "1.000000001\n");
+
+ /* DECIMAL64_MICRO: large value using upper 32 bits */
+ iio_val_s64_to_s32s(5000000000000042LL, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "5000000000.000042\n");
+
+ /* limits */
+ iio_val_s64_to_s32s(LLONG_MAX, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372.036854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036.854775807\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "9223372036854.775807\n");
+
+ iio_val_s64_to_s32s(LLONG_MIN, values);
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_PICO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372.036854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_NANO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036.854775808\n");
+ ret = iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(values), values);
+ IIO_TEST_FORMAT_EXPECT_EQ(test, buf, ret, "-9223372036854.775808\n");
+}
+
static struct kunit_case iio_format_test_cases[] = {
KUNIT_CASE(iio_test_iio_format_value_integer),
KUNIT_CASE(iio_test_iio_format_value_fixedpoint),
@@ -257,6 +309,7 @@ static struct kunit_case iio_format_test_cases[] = {
KUNIT_CASE(iio_test_iio_format_value_fractional_log2),
KUNIT_CASE(iio_test_iio_format_value_multiple),
KUNIT_CASE(iio_test_iio_format_value_integer_64),
+ KUNIT_CASE(iio_test_iio_format_value_decimal_64),
{ }
};
--
2.43.0
^ permalink raw reply related
* [PATCH v13 08/12] iio: frequency: adf41513: driver implementation
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
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 and
reference path configuration;
- Power management support with suspend/resume callbacks;
- Lock detect GPIO monitoring.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/frequency/Kconfig | 10 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf41513.c | 1106 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1118 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index e1c3a26a6e2f..6d695913e717 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1668,6 +1668,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
+F: drivers/iio/frequency/adf41513.c
ANALOG DEVICES INC ADF4377 DRIVER
M: Antoniu Miclaus <antoniu.miclaus@analog.com>
diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 583cbdf4e8cd..90c6304c4bcd 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -29,6 +29,16 @@ endmenu
menu "Phase-Locked Loop (PLL) frequency synthesizers"
+config ADF41513
+ tristate "Analog Devices ADF41513 PLL Frequency Synthesizer"
+ depends on SPI
+ help
+ Say yes here to build support for Analog Devices ADF41513
+ 26.5 GHz Integer-N/Fractional-N PLL Frequency Synthesizer.
+
+ To compile this driver as a module, choose M here: the
+ module will be called adf41513.
+
config ADF4350
tristate "Analog Devices ADF4350/ADF4351 Wideband Synthesizers"
depends on SPI
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..20ea7e82818f
--- /dev/null
+++ b/drivers/iio/frequency/adf41513.c
@@ -0,0 +1,1106 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ADF41513 SPI PLL Frequency Synthesizer driver
+ *
+ * Copyright 2026 Analog Devices Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/sysfs.h>
+#include <linux/log2.h>
+#include <linux/math64.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/spi/spi.h>
+#include <linux/types.h>
+#include <linux/units.h>
+
+/* Registers */
+#define ADF41513_REG0 0
+#define ADF41513_REG1 1
+#define ADF41513_REG2 2
+#define ADF41513_REG3 3
+#define ADF41513_REG4 4
+#define ADF41513_REG5 5
+#define ADF41513_REG6 6
+#define ADF41513_REG7 7
+#define ADF41513_REG8 8
+#define ADF41513_REG9 9
+#define ADF41513_REG10 10
+#define ADF41513_REG11 11
+#define ADF41513_REG12 12
+#define ADF41513_REG13 13
+#define ADF41513_REG_NUM 14
+
+#define ADF41513_SYNC_REG0 BIT(ADF41513_REG0)
+#define ADF41513_SYNC_REG1 BIT(ADF41513_REG1)
+#define ADF41513_SYNC_REG2 BIT(ADF41513_REG2)
+#define ADF41513_SYNC_REG3 BIT(ADF41513_REG3)
+#define ADF41513_SYNC_REG4 BIT(ADF41513_REG4)
+#define ADF41513_SYNC_REG5 BIT(ADF41513_REG5)
+#define ADF41513_SYNC_REG6 BIT(ADF41513_REG6)
+#define ADF41513_SYNC_REG7 BIT(ADF41513_REG7)
+#define ADF41513_SYNC_REG9 BIT(ADF41513_REG9)
+#define ADF41513_SYNC_REG11 BIT(ADF41513_REG11)
+#define ADF41513_SYNC_REG12 BIT(ADF41513_REG12)
+#define ADF41513_SYNC_REG13 BIT(ADF41513_REG13)
+#define ADF41513_SYNC_DIFF 0
+#define ADF41513_SYNC_ALL GENMASK(ADF41513_REG13, ADF41513_REG0)
+
+/* REG0 Bit Definitions */
+#define ADF41513_REG0_CTRL_BITS_MSK GENMASK(3, 0)
+#define ADF41513_REG0_INT_MSK GENMASK(19, 4)
+#define ADF41513_REG0_VAR_MOD_MSK BIT(28)
+
+/* REG1 Bit Definitions */
+#define ADF41513_REG1_FRAC1_MSK GENMASK(28, 4)
+#define ADF41513_REG1_DITHER2_MSK BIT(31)
+
+/* REG2 Bit Definitions */
+#define ADF41513_REG2_PHASE_VAL_MSK GENMASK(15, 4)
+#define ADF41513_REG2_PHASE_ADJ_MSK BIT(31)
+
+/* REG3 Bit Definitions */
+#define ADF41513_REG3_FRAC2_MSK GENMASK(27, 4)
+
+/* REG4 Bit Definitions */
+#define ADF41513_REG4_MOD2_MSK GENMASK(27, 4)
+
+/* REG5 Bit Definitions */
+#define ADF41513_REG5_CLK1_DIV_MSK GENMASK(15, 4)
+#define ADF41513_REG5_R_CNT_MSK GENMASK(20, 16)
+#define ADF41513_REG5_REF_DOUBLER_MSK BIT(21)
+#define ADF41513_REG5_RDIV2_MSK BIT(22)
+#define ADF41513_REG5_PRESCALER_MSK BIT(23)
+#define ADF41513_REG5_LSB_P1_MSK BIT(24)
+#define ADF41513_REG5_CP_CURRENT_MSK GENMASK(28, 25)
+#define ADF41513_REG5_DLD_MODES_MSK GENMASK(31, 30)
+
+/* REG6 Bit Definitions */
+#define ADF41513_REG6_COUNTER_RESET_MSK BIT(4)
+#define ADF41513_REG6_CP_TRISTATE_MSK BIT(5)
+#define ADF41513_REG6_POWER_DOWN_MSK BIT(6)
+#define ADF41513_REG6_PD_POLARITY_MSK BIT(7)
+#define ADF41513_REG6_LDP_MSK GENMASK(9, 8)
+#define ADF41513_REG6_CP_TRISTATE_PD_ON_MSK BIT(16)
+#define ADF41513_REG6_SD_RESET_MSK BIT(17)
+#define ADF41513_REG6_LOL_ENABLE_MSK BIT(18)
+#define ADF41513_REG6_ABP_MSK BIT(19)
+#define ADF41513_REG6_INT_MODE_MSK BIT(20)
+#define ADF41513_REG6_BLEED_ENABLE_MSK BIT(22)
+#define ADF41513_REG6_BLEED_POLARITY_MSK BIT(23)
+#define ADF41513_REG6_BLEED_CURRENT_MSK GENMASK(31, 24)
+
+/* REG7 Bit Definitions */
+#define ADF41513_REG7_CLK2_DIV_MSK GENMASK(17, 6)
+#define ADF41513_REG7_CLK_DIV_MODE_MSK GENMASK(19, 18)
+#define ADF41513_REG7_PS_BIAS_MSK GENMASK(21, 20)
+#define ADF41513_REG7_N_DELAY_MSK GENMASK(23, 22)
+#define ADF41513_REG7_LD_CLK_SEL_MSK BIT(26)
+#define ADF41513_REG7_LD_COUNT_MSK GENMASK(29, 27)
+
+/* REG9 Bit Definitions */
+#define ADF41513_REG9_LD_BIAS_MSK GENMASK(31, 30)
+
+/* REG11 Bit Definitions */
+#define ADF41513_REG11_POWER_DOWN_SEL_MSK BIT(31)
+
+/* REG12 Bit Definitions */
+#define ADF41513_REG12_READBACK_SEL_MSK GENMASK(19, 14)
+#define ADF41513_REG12_LE_SELECT_MSK BIT(20)
+#define ADF41513_REG12_MASTER_RESET_MSK BIT(22)
+#define ADF41513_REG12_LOGIC_LEVEL_MSK BIT(27)
+#define ADF41513_REG12_MUXOUT_MSK GENMASK(31, 28)
+
+/* MUXOUT Selection */
+#define ADF41513_MUXOUT_TRISTATE 0x0
+#define ADF41513_MUXOUT_DVDD 0x1
+#define ADF41513_MUXOUT_DGND 0x2
+#define ADF41513_MUXOUT_R_DIV 0x3
+#define ADF41513_MUXOUT_N_DIV 0x4
+#define ADF41513_MUXOUT_DIG_LD 0x6
+#define ADF41513_MUXOUT_SDO 0x7
+#define ADF41513_MUXOUT_READBACK 0x8
+#define ADF41513_MUXOUT_CLK1_DIV 0xA
+#define ADF41513_MUXOUT_R_DIV2 0xD
+#define ADF41513_MUXOUT_N_DIV2 0xE
+
+/* DLD Mode Selection */
+#define ADF41513_DLD_TRISTATE 0x0
+#define ADF41513_DLD_DIG_LD 0x1
+#define ADF41513_DLD_LOW 0x2
+#define ADF41513_DLD_HIGH 0x3
+
+/* Prescaler Selection */
+#define ADF41513_PRESCALER_4_5 0
+#define ADF41513_PRESCALER_8_9 1
+#define ADF41513_PRESCALER_AUTO 2
+
+/* Specifications */
+#define ADF41510_MAX_RF_FREQ_HZ (10ULL * HZ_PER_GHZ)
+#define ADF41513_MIN_RF_FREQ_HZ (1ULL * HZ_PER_GHZ)
+#define ADF41513_MAX_RF_FREQ_HZ (26500ULL * HZ_PER_MHZ)
+
+#define ADF41513_MIN_REF_FREQ_HZ (10 * HZ_PER_MHZ)
+#define ADF41513_MAX_REF_FREQ_HZ (800 * HZ_PER_MHZ)
+#define ADF41513_MAX_REF_FREQ_DOUBLER_HZ (225 * HZ_PER_MHZ)
+
+#define ADF41513_MAX_PFD_FREQ_INT_N_UHZ (250ULL * MEGA * MICROHZ_PER_HZ)
+#define ADF41513_MAX_PFD_FREQ_FRAC_N_UHZ (125ULL * MEGA * MICROHZ_PER_HZ)
+#define ADF41513_MAX_FREQ_RESOLUTION_UHZ (100ULL * KILO * MICROHZ_PER_HZ)
+
+#define ADF41513_MIN_INT_4_5 20
+#define ADF41513_MAX_INT_4_5 511
+#define ADF41513_MIN_INT_8_9 64
+#define ADF41513_MAX_INT_8_9 1023
+
+#define ADF41513_MIN_INT_FRAC_4_5 23
+#define ADF41513_MIN_INT_FRAC_8_9 75
+
+#define ADF41513_MIN_R_CNT 1
+#define ADF41513_MAX_R_CNT 32
+
+#define ADF41513_MIN_R_SET 1800
+#define ADF41513_DEFAULT_R_SET 2700
+#define ADF41513_MAX_R_SET 10000
+
+#define ADF41513_MIN_CP_VOLTAGE_mV 810
+#define ADF41513_DEFAULT_CP_VOLTAGE_mV 6480
+#define ADF41513_MAX_CP_VOLTAGE_mV 12960
+
+#define ADF41513_MIN_CP_CURRENT_uA 81
+#define ADF41513_MAX_CP_CURRENT_uA 7200
+
+#define ADF41513_LD_COUNT_FAST_MIN 2
+#define ADF41513_LD_COUNT_FAST_LIMIT 64
+#define ADF41513_LD_COUNT_MIN 64
+#define ADF41513_LD_COUNT_MAX 8192
+
+#define ADF41513_FIXED_MODULUS BIT(25)
+#define ADF41513_MAX_MOD2 (BIT(24) - 1)
+#define ADF41513_MAX_PHASE_VAL (BIT(12) - 1)
+#define ADF41513_MAX_CLK_DIVIDER (BIT(12) - 1)
+
+#define ADF41513_HZ_DECIMAL_SCALE 6
+#define ADF41513_PS_BIAS_INIT 0x2
+#define ADF41513_MAX_PHASE_MICRORAD ((2 * 314159265UL) / 100)
+
+enum adf41513_pll_mode {
+ ADF41513_MODE_INVALID,
+ ADF41513_MODE_INTEGER_N,
+ ADF41513_MODE_FIXED_MODULUS,
+ ADF41513_MODE_VARIABLE_MODULUS,
+};
+
+struct adf41513_chip_info {
+ const char *name;
+ u64 max_rf_freq_hz;
+ bool has_prescaler_8_9;
+};
+
+struct adf41513_data {
+ u64 power_up_frequency_hz;
+ u64 freq_resolution_uhz;
+ u32 charge_pump_voltage_mv;
+ u32 lock_detect_count;
+
+ u8 ref_div_factor;
+ bool ref_doubler_en;
+ bool ref_div2_en;
+ bool phase_detector_polarity;
+
+ bool logic_lvl_1v8_en;
+};
+
+struct adf41513_pll_settings {
+ enum adf41513_pll_mode mode;
+
+ /* reference path parameters */
+ u8 r_counter;
+ u8 ref_doubler;
+ u8 ref_div2;
+ u8 prescaler;
+
+ /* frequency parameters */
+ u64 target_frequency_uhz;
+ u64 actual_frequency_uhz;
+ u64 pfd_frequency_uhz;
+
+ /* pll parameters */
+ u32 frac1;
+ u32 frac2;
+ u32 mod2;
+ u16 int_val;
+};
+
+struct adf41513_state {
+ const struct adf41513_chip_info *chip_info;
+ struct spi_device *spi;
+ struct gpio_desc *lock_detect;
+ struct clk *ref_clk;
+ u32 ref_freq_hz;
+
+ /*
+ * Lock for accessing device registers. Some operations require
+ * multiple consecutive R/W operations, during which the device
+ * shouldn't be interrupted. The buffers are also shared across
+ * all operations so need to be protected on stand alone reads and
+ * writes.
+ */
+ struct mutex lock;
+
+ /* Cached register values */
+ u32 regs[ADF41513_REG_NUM];
+ u32 regs_hw[ADF41513_REG_NUM];
+
+ struct adf41513_data data;
+ struct adf41513_pll_settings settings;
+
+ bool powerdown;
+};
+
+static const char * const adf41513_power_supplies[] = {
+ "avdd1", "avdd2", "avdd3", "avdd4", "avdd5", "vp",
+};
+
+static int adf41513_sync_config(struct adf41513_state *st, u16 sync_mask)
+{
+ __be32 d32;
+ int ret;
+
+ /* write registers in reverse order (R13 to R0)*/
+ for (int i = ADF41513_REG13; i >= ADF41513_REG0; i--) {
+ 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 u64 adf41513_pll_get_rate(struct adf41513_state *st)
+{
+ struct adf41513_pll_settings *cfg = &st->settings;
+
+ if (cfg->mode != ADF41513_MODE_INVALID)
+ return cfg->actual_frequency_uhz;
+
+ /* get pll settings from regs_hw */
+ cfg->int_val = FIELD_GET(ADF41513_REG0_INT_MSK, st->regs_hw[ADF41513_REG0]);
+ cfg->frac1 = FIELD_GET(ADF41513_REG1_FRAC1_MSK, st->regs_hw[ADF41513_REG1]);
+ cfg->frac2 = FIELD_GET(ADF41513_REG3_FRAC2_MSK, st->regs_hw[ADF41513_REG3]);
+ cfg->mod2 = FIELD_GET(ADF41513_REG4_MOD2_MSK, st->regs_hw[ADF41513_REG4]);
+ cfg->r_counter = FIELD_GET(ADF41513_REG5_R_CNT_MSK, st->regs_hw[ADF41513_REG5]);
+ cfg->ref_doubler = FIELD_GET(ADF41513_REG5_REF_DOUBLER_MSK, st->regs_hw[ADF41513_REG5]);
+ cfg->ref_div2 = FIELD_GET(ADF41513_REG5_RDIV2_MSK, st->regs_hw[ADF41513_REG5]);
+ cfg->prescaler = FIELD_GET(ADF41513_REG5_PRESCALER_MSK, st->regs_hw[ADF41513_REG5]);
+
+ if (!cfg->mod2)
+ cfg->mod2 = 1;
+ if (!cfg->r_counter)
+ cfg->r_counter = ADF41513_MAX_R_CNT;
+
+ /* calculate pfd frequency */
+ cfg->pfd_frequency_uhz = (u64)st->ref_freq_hz * MICRO;
+ if (cfg->ref_doubler)
+ cfg->pfd_frequency_uhz <<= 1;
+ if (cfg->ref_div2)
+ cfg->pfd_frequency_uhz >>= 1;
+ cfg->pfd_frequency_uhz = div_u64(cfg->pfd_frequency_uhz, cfg->r_counter);
+ cfg->actual_frequency_uhz = (u64)cfg->int_val * cfg->pfd_frequency_uhz;
+
+ /* check if int mode is selected */
+ if (FIELD_GET(ADF41513_REG6_INT_MODE_MSK, st->regs_hw[ADF41513_REG6])) {
+ cfg->mode = ADF41513_MODE_INTEGER_N;
+ } else {
+ cfg->actual_frequency_uhz += mul_u64_u32_div(cfg->pfd_frequency_uhz,
+ cfg->frac1,
+ ADF41513_FIXED_MODULUS);
+
+ /* check if variable modulus is selected */
+ if (FIELD_GET(ADF41513_REG0_VAR_MOD_MSK, st->regs_hw[ADF41513_REG0])) {
+ cfg->actual_frequency_uhz +=
+ mul_u64_u64_div_u64(cfg->frac2,
+ cfg->pfd_frequency_uhz,
+ (u64)cfg->mod2 * ADF41513_FIXED_MODULUS);
+
+ cfg->mode = ADF41513_MODE_VARIABLE_MODULUS;
+ } else {
+ /* LSB_P1 offset */
+ if (!FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs_hw[ADF41513_REG5]))
+ cfg->actual_frequency_uhz +=
+ div_u64(cfg->pfd_frequency_uhz,
+ 2 * ADF41513_FIXED_MODULUS);
+ cfg->mode = ADF41513_MODE_FIXED_MODULUS;
+ }
+ }
+
+ cfg->target_frequency_uhz = cfg->actual_frequency_uhz;
+
+ return cfg->actual_frequency_uhz;
+}
+
+static int adf41513_calc_pfd_frequency(struct adf41513_state *st,
+ struct adf41513_pll_settings *result,
+ u64 fpfd_limit_uhz)
+{
+ result->ref_div2 = st->data.ref_div2_en;
+ result->ref_doubler = st->data.ref_doubler_en;
+
+ if (st->data.ref_doubler_en &&
+ st->ref_freq_hz > ADF41513_MAX_REF_FREQ_DOUBLER_HZ) {
+ result->ref_doubler = 0;
+ dev_warn(&st->spi->dev, "Disabling ref doubler due to high reference frequency\n");
+ }
+
+ result->r_counter = st->data.ref_div_factor - 1;
+ do {
+ result->r_counter++;
+ /* f_PFD = REF_IN × ((1 + D)/(R × (1 + T))) */
+ result->pfd_frequency_uhz = (u64)st->ref_freq_hz * MICRO;
+ if (result->ref_doubler)
+ result->pfd_frequency_uhz <<= 1;
+ if (result->ref_div2)
+ result->pfd_frequency_uhz >>= 1;
+ result->pfd_frequency_uhz = div_u64(result->pfd_frequency_uhz,
+ result->r_counter);
+ } while (result->pfd_frequency_uhz > fpfd_limit_uhz);
+
+ if (result->r_counter > ADF41513_MAX_R_CNT) {
+ dev_err(&st->spi->dev, "Cannot optimize PFD frequency\n");
+ return -ERANGE;
+ }
+
+ return 0;
+}
+
+static int adf41513_calc_integer_n(struct adf41513_state *st,
+ struct adf41513_pll_settings *result)
+{
+ u32 max_int = st->chip_info->has_prescaler_8_9 ?
+ ADF41513_MAX_INT_8_9 : ADF41513_MAX_INT_4_5;
+ u64 freq_error_uhz;
+ u32 int_val = div64_u64_rem(result->target_frequency_uhz, result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ /* check if freq error is within a tolerance of 1/2 resolution */
+ if (freq_error_uhz > (result->pfd_frequency_uhz >> 1) && int_val < max_int) {
+ int_val++;
+ freq_error_uhz = result->pfd_frequency_uhz - freq_error_uhz;
+ }
+
+ if (freq_error_uhz > st->data.freq_resolution_uhz)
+ return -ERANGE;
+
+ /* set prescaler */
+ if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_8_9 &&
+ int_val <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_val >= ADF41513_MIN_INT_4_5 && int_val <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ result->actual_frequency_uhz = (u64)int_val * result->pfd_frequency_uhz;
+ result->mode = ADF41513_MODE_INTEGER_N;
+ result->int_val = int_val;
+ result->frac1 = 0;
+ result->frac2 = 0;
+ result->mod2 = 0;
+
+ return 0;
+}
+
+static int adf41513_calc_fixed_mod(struct adf41513_state *st,
+ struct adf41513_pll_settings *result)
+{
+ u64 resolution_uhz = div_u64(result->pfd_frequency_uhz, ADF41513_FIXED_MODULUS);
+ u64 target_frequency_uhz = result->target_frequency_uhz;
+ u64 freq_error_uhz;
+ u32 int_val, frac1;
+ bool lsb_p1_offset = !FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs[ADF41513_REG5]);
+
+ /* LSB_P1 adds a frequency offset of f_pfd/2^26 */
+ if (lsb_p1_offset)
+ target_frequency_uhz -= resolution_uhz >> 1;
+
+ int_val = div64_u64_rem(target_frequency_uhz, result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_val <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_val >= ADF41513_MIN_INT_FRAC_4_5 && int_val <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ /* compute frac1 and fixed modulus error */
+ 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);
+
+ /* check if freq error is within a tolerance of 1/2 resolution */
+ if (freq_error_uhz > (resolution_uhz >> 1) && frac1 < (ADF41513_FIXED_MODULUS - 1)) {
+ frac1++;
+ freq_error_uhz = freq_error_uhz < resolution_uhz ?
+ resolution_uhz - freq_error_uhz : 0;
+ }
+
+ if (freq_error_uhz > st->data.freq_resolution_uhz)
+ return -ERANGE;
+
+ /* integer part */
+ result->actual_frequency_uhz = (u64)int_val * result->pfd_frequency_uhz;
+ /* fractional part */
+ if (lsb_p1_offset)
+ result->actual_frequency_uhz += (resolution_uhz >> 1);
+ result->actual_frequency_uhz += mul_u64_u32_div(result->pfd_frequency_uhz, frac1,
+ ADF41513_FIXED_MODULUS);
+ result->mode = ADF41513_MODE_FIXED_MODULUS;
+ result->int_val = int_val;
+ result->frac1 = frac1;
+ result->frac2 = 0;
+ result->mod2 = 0;
+
+ 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;
+ u32 int_val = div64_u64_rem(result->target_frequency_uhz,
+ result->pfd_frequency_uhz, &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_val >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_val <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_val >= ADF41513_MIN_INT_FRAC_4_5 && int_val <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ /* 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_val * 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_val = int_val;
+ 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)
+ 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)
+ 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) {
+ dev_err(&st->spi->dev,
+ "no valid PLL configuration found for %llu uHz\n",
+ rf_out_uhz);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 sync_mask)
+{
+ struct adf41513_pll_settings result;
+ int ret;
+
+ ret = adf41513_calc_pll_settings(st, &result, freq_uhz);
+ if (ret < 0)
+ return ret;
+
+ /* apply computed results to pll settings */
+ st->settings = result;
+
+ dev_dbg(&st->spi->dev,
+ "%s mode: int=%u, frac1=%u, frac2=%u, mod2=%u, fpdf=%llu Hz, prescaler=%s\n",
+ (result.mode == ADF41513_MODE_INTEGER_N) ? "integer-n" :
+ (result.mode == ADF41513_MODE_FIXED_MODULUS) ? "fixed-modulus" : "variable-modulus",
+ result.int_val, result.frac1, result.frac2, result.mod2,
+ div64_u64(result.pfd_frequency_uhz, MICRO),
+ result.prescaler ? "8/9" : "4/5");
+
+ st->regs[ADF41513_REG0] = FIELD_PREP(ADF41513_REG0_INT_MSK,
+ st->settings.int_val);
+ if (st->settings.mode == ADF41513_MODE_VARIABLE_MODULUS)
+ st->regs[ADF41513_REG0] |= ADF41513_REG0_VAR_MOD_MSK;
+
+ st->regs[ADF41513_REG1] = FIELD_PREP(ADF41513_REG1_FRAC1_MSK,
+ st->settings.frac1);
+ if (st->settings.mode != ADF41513_MODE_INTEGER_N)
+ st->regs[ADF41513_REG1] |= ADF41513_REG1_DITHER2_MSK;
+
+ st->regs[ADF41513_REG3] = FIELD_PREP(ADF41513_REG3_FRAC2_MSK,
+ st->settings.frac2);
+ FIELD_MODIFY(ADF41513_REG4_MOD2_MSK, &st->regs[ADF41513_REG4],
+ st->settings.mod2);
+ FIELD_MODIFY(ADF41513_REG5_R_CNT_MSK, &st->regs[ADF41513_REG5],
+ st->settings.r_counter % ADF41513_MAX_R_CNT);
+ FIELD_MODIFY(ADF41513_REG5_REF_DOUBLER_MSK, &st->regs[ADF41513_REG5],
+ st->settings.ref_doubler);
+ FIELD_MODIFY(ADF41513_REG5_RDIV2_MSK, &st->regs[ADF41513_REG5],
+ st->settings.ref_div2);
+ FIELD_MODIFY(ADF41513_REG5_PRESCALER_MSK, &st->regs[ADF41513_REG5],
+ st->settings.prescaler);
+
+ if (st->settings.mode == ADF41513_MODE_INTEGER_N) {
+ st->regs[ADF41513_REG6] |= ADF41513_REG6_INT_MODE_MSK;
+ st->regs[ADF41513_REG6] &= ~ADF41513_REG6_BLEED_ENABLE_MSK;
+ } else {
+ st->regs[ADF41513_REG6] &= ~ADF41513_REG6_INT_MODE_MSK;
+ st->regs[ADF41513_REG6] |= ADF41513_REG6_BLEED_ENABLE_MSK;
+ }
+
+ return adf41513_sync_config(st, sync_mask | ADF41513_SYNC_REG0);
+}
+
+static int adf41513_suspend(struct adf41513_state *st)
+{
+ st->regs[ADF41513_REG6] |= FIELD_PREP(ADF41513_REG6_POWER_DOWN_MSK, 1);
+ return adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+}
+
+static int adf41513_resume(struct adf41513_state *st)
+{
+ st->regs[ADF41513_REG6] &= ~ADF41513_REG6_POWER_DOWN_MSK;
+ return adf41513_sync_config(st, ADF41513_SYNC_ALL);
+}
+
+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);
+
+ iio_val_s64_to_s32s(st->data.freq_resolution_uhz, vals);
+ return iio_format_value(buf, IIO_VAL_DECIMAL64_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+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);
+
+ val = FIELD_GET(ADF41513_REG6_POWER_DOWN_MSK, st->regs_hw[ADF41513_REG6]);
+ return sysfs_emit(buf, "%u\n", val);
+}
+
+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;
+
+ if (freq_uhz == 0 || freq_uhz > ADF41513_MAX_FREQ_RESOLUTION_UHZ)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ st->data.freq_resolution_uhz = freq_uhz;
+ return len;
+}
+
+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);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret)
+ return ret;
+
+ guard(mutex)(&st->lock);
+
+ if (val)
+ ret = adf41513_suspend(st);
+ else
+ ret = adf41513_resume(st);
+ if (ret)
+ return ret;
+
+ st->powerdown = val;
+ return len;
+}
+
+static const struct iio_chan_spec_ext_info adf41513_ext_info[] = {
+ {
+ .name = "frequency_resolution",
+ .read = adf41513_read_resolution,
+ .write = adf41513_write_resolution,
+ .shared = IIO_SEPARATE,
+ },
+ {
+ .name = "powerdown",
+ .read = adf41513_read_powerdown,
+ .write = adf41513_write_powerdown,
+ .shared = IIO_SEPARATE,
+ },
+ { }
+};
+
+static const struct iio_chan_spec adf41513_chan = {
+ .type = IIO_ALTVOLTAGE,
+ .indexed = 1,
+ .output = 1,
+ .channel = 0,
+ .info_mask_separate = BIT(IIO_CHAN_INFO_FREQUENCY) |
+ BIT(IIO_CHAN_INFO_PHASE),
+ .ext_info = adf41513_ext_info,
+};
+
+static int adf41513_read_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int *val, int *val2, long info)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ u64 tmp64;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ if (st->lock_detect &&
+ !gpiod_get_value_cansleep(st->lock_detect)) {
+ dev_dbg(&st->spi->dev, "PLL un-locked\n");
+ return -EBUSY;
+ }
+ tmp64 = adf41513_pll_get_rate(st);
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ case IIO_CHAN_INFO_PHASE:
+ tmp64 = FIELD_GET(ADF41513_REG2_PHASE_VAL_MSK,
+ st->regs_hw[ADF41513_REG2]);
+ tmp64 = (tmp64 * ADF41513_MAX_PHASE_MICRORAD) >> 12;
+ iio_val_s64_decompose(tmp64, val, val2);
+ return IIO_VAL_DECIMAL64_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf41513_write_raw(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ int val, int val2, long info)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+ u64 tmp64 = iio_val_s64_compose(val, val2);
+ u16 phase_val;
+ int ret;
+
+ guard(mutex)(&st->lock);
+
+ switch (info) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ return adf41513_set_frequency(st, tmp64, ADF41513_SYNC_DIFF);
+ case IIO_CHAN_INFO_PHASE:
+ if (tmp64 >= ADF41513_MAX_PHASE_MICRORAD)
+ return -EINVAL;
+
+ phase_val = DIV_U64_ROUND_CLOSEST(tmp64 << 12,
+ ADF41513_MAX_PHASE_MICRORAD);
+ phase_val = min(phase_val, ADF41513_MAX_PHASE_VAL);
+ st->regs[ADF41513_REG2] |= ADF41513_REG2_PHASE_ADJ_MSK;
+ FIELD_MODIFY(ADF41513_REG2_PHASE_VAL_MSK,
+ &st->regs[ADF41513_REG2], phase_val);
+ ret = adf41513_sync_config(st, ADF41513_SYNC_REG0);
+ if (ret)
+ return ret;
+
+ /* clear phase adjust for the next sync */
+ st->regs[ADF41513_REG2] &= ~ADF41513_REG2_PHASE_ADJ_MSK;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf41513_write_raw_get_fmt(struct iio_dev *indio_dev,
+ struct iio_chan_spec const *chan,
+ long mask)
+{
+ switch (mask) {
+ case IIO_CHAN_INFO_FREQUENCY:
+ case IIO_CHAN_INFO_PHASE:
+ return IIO_VAL_DECIMAL64_MICRO;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int adf41513_reg_access(struct iio_dev *indio_dev, unsigned int reg,
+ unsigned int writeval, unsigned int *readval)
+{
+ struct adf41513_state *st = iio_priv(indio_dev);
+
+ if (reg > ADF41513_REG13)
+ return -EINVAL;
+
+ guard(mutex)(&st->lock);
+
+ if (!readval) {
+ if (reg <= ADF41513_REG6)
+ st->settings.mode = ADF41513_MODE_INVALID;
+ st->regs[reg] = writeval & ~0xF; /* Clear control bits */
+ return adf41513_sync_config(st, BIT(reg));
+ }
+
+ *readval = st->regs_hw[reg];
+ return 0;
+}
+
+static const struct iio_info adf41513_info = {
+ .read_raw = adf41513_read_raw,
+ .write_raw = adf41513_write_raw,
+ .write_raw_get_fmt = adf41513_write_raw_get_fmt,
+ .debugfs_reg_access = &adf41513_reg_access,
+};
+
+static int adf41513_parse_fw(struct adf41513_state *st)
+{
+ struct device *dev = &st->spi->dev;
+ u32 tmp, cp_resistance, cp_current;
+ int ret;
+
+ tmp = ADF41510_MAX_RF_FREQ_HZ / MEGA;
+ device_property_read_u32(dev, "adi,power-up-frequency-mhz", &tmp);
+ st->data.power_up_frequency_hz = (u64)tmp * MEGA;
+ if (st->data.power_up_frequency_hz < ADF41513_MIN_RF_FREQ_HZ ||
+ st->data.power_up_frequency_hz > st->chip_info->max_rf_freq_hz)
+ return dev_err_probe(dev, -ERANGE,
+ "power-up frequency %llu Hz out of range\n",
+ st->data.power_up_frequency_hz);
+
+ tmp = ADF41513_MIN_R_CNT;
+ device_property_read_u32(dev, "adi,reference-div-factor", &tmp);
+ if (tmp < ADF41513_MIN_R_CNT || tmp > ADF41513_MAX_R_CNT)
+ return dev_err_probe(dev, -ERANGE,
+ "invalid reference div factor %u\n", tmp);
+ st->data.ref_div_factor = tmp;
+
+ st->data.ref_doubler_en = device_property_read_bool(dev, "adi,reference-doubler-enable");
+ st->data.ref_div2_en = device_property_read_bool(dev, "adi,reference-div2-enable");
+
+ cp_resistance = ADF41513_DEFAULT_R_SET;
+ device_property_read_u32(dev, "adi,charge-pump-resistor-ohms", &cp_resistance);
+ if (cp_resistance < ADF41513_MIN_R_SET || cp_resistance > ADF41513_MAX_R_SET)
+ return dev_err_probe(dev, -ERANGE, "R_SET %u Ohms out of range\n", cp_resistance);
+
+ st->data.charge_pump_voltage_mv = ADF41513_DEFAULT_CP_VOLTAGE_mV;
+ ret = device_property_read_u32(dev, "adi,charge-pump-current-microamp", &cp_current);
+ if (!ret) {
+ if (cp_current < ADF41513_MIN_CP_CURRENT_uA ||
+ cp_current > ADF41513_MAX_CP_CURRENT_uA)
+ return dev_err_probe(dev, -ERANGE,
+ "I_CP %u uA out of range\n", cp_current);
+
+ tmp = DIV_ROUND_CLOSEST(cp_current * cp_resistance, MILLI);
+ if (tmp < ADF41513_MIN_CP_VOLTAGE_mV || tmp > ADF41513_MAX_CP_VOLTAGE_mV)
+ return dev_err_probe(dev, -ERANGE, "I_CP %u uA (%u Ohms) out of range\n",
+ cp_current, cp_resistance);
+ st->data.charge_pump_voltage_mv = tmp;
+ }
+
+ st->data.phase_detector_polarity =
+ device_property_read_bool(dev, "adi,phase-detector-polarity-positive-enable");
+
+ st->data.logic_lvl_1v8_en = device_property_read_bool(dev, "adi,logic-level-1v8-enable");
+
+ tmp = ADF41513_LD_COUNT_MIN;
+ device_property_read_u32(dev, "adi,lock-detector-count", &tmp);
+ if (tmp < ADF41513_LD_COUNT_FAST_MIN || tmp > ADF41513_LD_COUNT_MAX ||
+ !is_power_of_2(tmp))
+ return dev_err_probe(dev, -ERANGE,
+ "invalid lock detect count: %u\n", tmp);
+ st->data.lock_detect_count = tmp;
+
+ st->data.freq_resolution_uhz = MICROHZ_PER_HZ;
+
+ return 0;
+}
+
+static void adf41513_chip_disable(void *data)
+{
+ gpiod_set_value_cansleep(data, 0);
+}
+
+static void adf41513_close(void *data)
+{
+ adf41513_suspend(data);
+}
+
+static int adf41513_setup(struct device *dev, struct adf41513_state *st)
+{
+ u32 tmp;
+ int ret;
+
+ memset(st->regs_hw, 0xFF, sizeof(st->regs_hw));
+
+ /* assuming DLD pin is used for lock detection */
+ st->regs[ADF41513_REG5] = FIELD_PREP(ADF41513_REG5_DLD_MODES_MSK,
+ ADF41513_DLD_DIG_LD);
+
+ tmp = DIV_ROUND_CLOSEST(st->data.charge_pump_voltage_mv, ADF41513_MIN_CP_VOLTAGE_mV);
+ st->regs[ADF41513_REG5] |= FIELD_PREP(ADF41513_REG5_CP_CURRENT_MSK, tmp - 1);
+
+ st->regs[ADF41513_REG6] = ADF41513_REG6_ABP_MSK |
+ ADF41513_REG6_LOL_ENABLE_MSK |
+ ADF41513_REG6_SD_RESET_MSK;
+ if (st->data.phase_detector_polarity)
+ st->regs[ADF41513_REG6] |= ADF41513_REG6_PD_POLARITY_MSK;
+
+ st->regs[ADF41513_REG7] = FIELD_PREP(ADF41513_REG7_PS_BIAS_MSK,
+ ADF41513_PS_BIAS_INIT);
+ tmp = ilog2(st->data.lock_detect_count);
+ if (st->data.lock_detect_count < ADF41513_LD_COUNT_FAST_LIMIT) {
+ tmp -= const_ilog2(ADF41513_LD_COUNT_FAST_MIN);
+ st->regs[ADF41513_REG7] |= ADF41513_REG7_LD_CLK_SEL_MSK;
+ } else {
+ tmp -= const_ilog2(ADF41513_LD_COUNT_MIN);
+ }
+ st->regs[ADF41513_REG7] |= FIELD_PREP(ADF41513_REG7_LD_COUNT_MSK, tmp);
+
+ st->regs[ADF41513_REG11] = ADF41513_REG11_POWER_DOWN_SEL_MSK;
+ st->regs[ADF41513_REG12] = FIELD_PREP(ADF41513_REG12_LOGIC_LEVEL_MSK,
+ st->data.logic_lvl_1v8_en ? 0 : 1);
+
+ /* perform initialization sequence with power-up frequency */
+ ret = adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
+ ADF41513_SYNC_ALL);
+ if (ret)
+ return ret;
+
+ return devm_add_action_or_reset(dev, adf41513_close, st);
+}
+
+static int adf41513_pm_suspend(struct device *dev)
+{
+ struct adf41513_state *st = dev_get_drvdata(dev);
+
+ guard(mutex)(&st->lock);
+ return adf41513_suspend(st);
+}
+
+static int adf41513_pm_resume(struct device *dev)
+{
+ struct adf41513_state *st = dev_get_drvdata(dev);
+
+ guard(mutex)(&st->lock);
+ if (st->powerdown)
+ return 0; /* nothing to do */
+
+ return adf41513_resume(st);
+}
+
+static const struct adf41513_chip_info adf41510_chip_info = {
+ .name = "adf41510",
+ .max_rf_freq_hz = ADF41510_MAX_RF_FREQ_HZ,
+ .has_prescaler_8_9 = false,
+};
+
+static const struct adf41513_chip_info adf41513_chip_info = {
+ .name = "adf41513",
+ .max_rf_freq_hz = ADF41513_MAX_RF_FREQ_HZ,
+ .has_prescaler_8_9 = true,
+};
+
+static int adf41513_probe(struct spi_device *spi)
+{
+ struct device *dev = &spi->dev;
+ struct gpio_desc *chip_enable;
+ struct iio_dev *indio_dev;
+ struct adf41513_state *st;
+ int ret;
+
+ indio_dev = devm_iio_device_alloc(dev, sizeof(*st));
+ if (!indio_dev)
+ return -ENOMEM;
+
+ st = iio_priv(indio_dev);
+ st->spi = spi;
+ st->chip_info = spi_get_device_match_data(spi);
+ if (!st->chip_info)
+ return -EINVAL;
+
+ spi_set_drvdata(spi, st);
+
+ st->ref_clk = devm_clk_get_enabled(dev, NULL);
+ if (IS_ERR(st->ref_clk))
+ return PTR_ERR(st->ref_clk);
+
+ st->ref_freq_hz = clk_get_rate(st->ref_clk);
+ if (st->ref_freq_hz < ADF41513_MIN_REF_FREQ_HZ ||
+ st->ref_freq_hz > ADF41513_MAX_REF_FREQ_HZ)
+ return dev_err_probe(dev, -ERANGE,
+ "reference frequency %u Hz out of range\n",
+ st->ref_freq_hz);
+
+ ret = adf41513_parse_fw(st);
+ if (ret)
+ return ret;
+
+ ret = devm_regulator_bulk_get_enable(dev,
+ ARRAY_SIZE(adf41513_power_supplies),
+ adf41513_power_supplies);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to get and enable regulators\n");
+
+ st->lock_detect = devm_gpiod_get_optional(dev, "lock-detect", GPIOD_IN);
+ if (IS_ERR(st->lock_detect))
+ return dev_err_probe(dev, PTR_ERR(st->lock_detect),
+ "fail to request lock detect GPIO\n");
+
+ chip_enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(chip_enable))
+ return dev_err_probe(dev, PTR_ERR(chip_enable),
+ "fail to request chip enable GPIO\n");
+
+ ret = devm_add_action_or_reset(dev, adf41513_chip_disable, chip_enable);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add disable action\n");
+
+ ret = devm_mutex_init(dev, &st->lock);
+ if (ret)
+ return ret;
+
+ indio_dev->name = st->chip_info->name;
+ indio_dev->info = &adf41513_info;
+ indio_dev->modes = INDIO_DIRECT_MODE;
+ indio_dev->channels = &adf41513_chan;
+ indio_dev->num_channels = 1;
+
+ ret = adf41513_setup(dev, st);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to setup device\n");
+
+ return devm_iio_device_register(dev, indio_dev);
+}
+
+static const struct spi_device_id adf41513_id[] = {
+ {"adf41510", (kernel_ulong_t)&adf41510_chip_info},
+ {"adf41513", (kernel_ulong_t)&adf41513_chip_info},
+ { }
+};
+MODULE_DEVICE_TABLE(spi, adf41513_id);
+
+static const struct of_device_id adf41513_of_match[] = {
+ { .compatible = "adi,adf41510", .data = &adf41510_chip_info },
+ { .compatible = "adi,adf41513", .data = &adf41513_chip_info },
+ { }
+};
+MODULE_DEVICE_TABLE(of, adf41513_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(adf41513_pm_ops, adf41513_pm_suspend, adf41513_pm_resume);
+
+static struct spi_driver adf41513_driver = {
+ .driver = {
+ .name = "adf41513",
+ .pm = pm_ptr(&adf41513_pm_ops),
+ .of_match_table = adf41513_of_match,
+ },
+ .probe = adf41513_probe,
+ .id_table = adf41513_id,
+};
+module_spi_driver(adf41513_driver);
+
+MODULE_AUTHOR("Rodrigo Alencar <rodrigo.alencar@analog.com>");
+MODULE_DESCRIPTION("Analog Devices ADF41513 PLL Frequency Synthesizer");
+MODULE_LICENSE("GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH v13 09/12] iio: frequency: adf41513: handle LE synchronization feature
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
When LE sync is enabled, it must be set after powering up and it must be
disabled when powering down. It is recommended when using the PLL as
a frequency synthesizer, where reference signal will always be present
while the device is being configured.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/adf41513.c | 33 +++++++++++++++++++++++++++++++--
1 file changed, 31 insertions(+), 2 deletions(-)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index 20ea7e82818f..be29a0f6ede9 100644
--- a/drivers/iio/frequency/adf41513.c
+++ b/drivers/iio/frequency/adf41513.c
@@ -218,6 +218,7 @@ struct adf41513_data {
bool phase_detector_polarity;
bool logic_lvl_1v8_en;
+ bool le_sync_en;
};
struct adf41513_pll_settings {
@@ -635,13 +636,28 @@ static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 s
static int adf41513_suspend(struct adf41513_state *st)
{
st->regs[ADF41513_REG6] |= FIELD_PREP(ADF41513_REG6_POWER_DOWN_MSK, 1);
+ st->regs[ADF41513_REG12] &= ~ADF41513_REG12_LE_SELECT_MSK;
return adf41513_sync_config(st, ADF41513_SYNC_DIFF);
}
static int adf41513_resume(struct adf41513_state *st)
{
+ int ret;
+
st->regs[ADF41513_REG6] &= ~ADF41513_REG6_POWER_DOWN_MSK;
- return adf41513_sync_config(st, ADF41513_SYNC_ALL);
+ st->regs[ADF41513_REG12] &= ~ADF41513_REG12_LE_SELECT_MSK;
+ ret = adf41513_sync_config(st, ADF41513_SYNC_ALL);
+ if (ret)
+ return ret;
+
+ if (st->data.le_sync_en) {
+ st->regs[ADF41513_REG12] |= ADF41513_REG12_LE_SELECT_MSK;
+ ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static ssize_t adf41513_read_resolution(struct iio_dev *indio_dev,
@@ -911,6 +927,8 @@ static int adf41513_parse_fw(struct adf41513_state *st)
"invalid lock detect count: %u\n", tmp);
st->data.lock_detect_count = tmp;
+ /* load enable sync */
+ st->data.le_sync_en = device_property_read_bool(dev, "adi,le-sync-enable");
st->data.freq_resolution_uhz = MICROHZ_PER_HZ;
return 0;
@@ -967,7 +985,18 @@ static int adf41513_setup(struct device *dev, struct adf41513_state *st)
if (ret)
return ret;
- return devm_add_action_or_reset(dev, adf41513_close, st);
+ ret = devm_add_action_or_reset(dev, adf41513_close, st);
+ if (ret)
+ return ret;
+
+ if (st->data.le_sync_en) {
+ st->regs[ADF41513_REG12] |= ADF41513_REG12_LE_SELECT_MSK;
+ ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static int adf41513_pm_suspend(struct device *dev)
--
2.43.0
^ permalink raw reply related
* [PATCH v13 10/12] iio: frequency: adf41513: features on frequency change
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Set Bleed current when PFD frequency changes (bleed enabled when in
fractional mode). Set lock detector window size, handling bias and
precision. Add phase resync support, setting clock dividers when
PFD frequency changes.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
drivers/iio/frequency/adf41513.c | 105 +++++++++++++++++++++++++++++++++++++++
1 file changed, 105 insertions(+)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index be29a0f6ede9..b485b25136c4 100644
--- a/drivers/iio/frequency/adf41513.c
+++ b/drivers/iio/frequency/adf41513.c
@@ -20,6 +20,7 @@
#include <linux/property.h>
#include <linux/regulator/consumer.h>
#include <linux/spi/spi.h>
+#include <linux/time64.h>
#include <linux/types.h>
#include <linux/units.h>
@@ -144,6 +145,10 @@
#define ADF41513_PRESCALER_8_9 1
#define ADF41513_PRESCALER_AUTO 2
+/* CLK Divider mode */
+#define ADF41513_CLK_DIV_MODE_OFF 0
+#define ADF41513_CLK_DIV_MODE_PHASE_RESYNC 2
+
/* Specifications */
#define ADF41510_MAX_RF_FREQ_HZ (10ULL * HZ_PER_GHZ)
#define ADF41513_MIN_RF_FREQ_HZ (1ULL * HZ_PER_GHZ)
@@ -209,6 +214,7 @@ struct adf41513_chip_info {
struct adf41513_data {
u64 power_up_frequency_hz;
u64 freq_resolution_uhz;
+ u32 phase_resync_period_ns;
u32 charge_pump_voltage_mv;
u32 lock_detect_count;
@@ -268,6 +274,16 @@ struct adf41513_state {
bool powerdown;
};
+static const u16 adf41513_ld_window_x10_ns[] = {
+ 9, 12, 16, 17, 21, 28, 29, 35, /* 0 - 7 */
+ 43, 47, 49, 52, 70, 79, 115, /* 8 - 14 */
+};
+
+static const u8 adf41513_ldp_bias[] = {
+ 0xC, 0xD, 0xE, 0x8, 0x9, 0x4, 0xA, 0x5, /* 0 - 7 */
+ 0x0, 0x6, 0xB, 0x1, 0x2, 0x7, 0x3, /* 8 - 14 */
+};
+
static const char * const adf41513_power_supplies[] = {
"avdd1", "avdd2", "avdd3", "avdd4", "avdd5", "vp",
};
@@ -579,9 +595,83 @@ static int adf41513_calc_pll_settings(struct adf41513_state *st,
return 0;
}
+static void adf41513_set_bleed_val(struct adf41513_state *st)
+{
+ u32 bleed_value, cp_index;
+
+ if (st->data.phase_detector_polarity)
+ bleed_value = 90;
+ else
+ bleed_value = 144;
+
+ cp_index = 1 + FIELD_GET(ADF41513_REG5_CP_CURRENT_MSK,
+ st->regs[ADF41513_REG5]);
+ bleed_value = div64_u64(st->settings.pfd_frequency_uhz * cp_index * bleed_value,
+ 1600ULL * MEGA * MICROHZ_PER_HZ);
+
+ FIELD_MODIFY(ADF41513_REG6_BLEED_CURRENT_MSK, &st->regs[ADF41513_REG6],
+ bleed_value);
+}
+
+static void adf41513_set_ld_window(struct adf41513_state *st)
+{
+ /*
+ * The ideal lock detector window size is halfway between the max
+ * window, set by the phase comparison period t_PFD = (1 / f_PFD),
+ * and the minimum is set by (I_BLEED/I_CP) × t_PFD
+ */
+ u16 ld_window_10x_ns = div64_u64(10ULL * NSEC_PER_SEC * MICROHZ_PER_HZ,
+ st->settings.pfd_frequency_uhz << 1);
+ u8 ld_idx, ldp, ld_bias;
+
+ if (st->settings.mode != ADF41513_MODE_INTEGER_N) {
+ /* account for bleed current (deduced from eq.6 and eq.7) */
+ if (st->data.phase_detector_polarity)
+ ld_window_10x_ns += 4;
+ else
+ ld_window_10x_ns += 6;
+ }
+
+ ld_idx = find_closest(ld_window_10x_ns, adf41513_ld_window_x10_ns,
+ ARRAY_SIZE(adf41513_ld_window_x10_ns));
+ ldp = (adf41513_ldp_bias[ld_idx] >> 2) & 0x3;
+ ld_bias = adf41513_ldp_bias[ld_idx] & 0x3;
+
+ FIELD_MODIFY(ADF41513_REG6_LDP_MSK, &st->regs[ADF41513_REG6], ldp);
+ FIELD_MODIFY(ADF41513_REG9_LD_BIAS_MSK, &st->regs[ADF41513_REG9], ld_bias);
+}
+
+static void adf41513_set_phase_resync(struct adf41513_state *st)
+{
+ u32 total_div, clk1_div, clk2_div;
+
+ if (!st->data.phase_resync_period_ns)
+ return;
+
+ /* assuming both clock dividers hold similar values */
+ total_div = mul_u64_u64_div_u64(st->settings.pfd_frequency_uhz,
+ st->data.phase_resync_period_ns,
+ 1ULL * MICROHZ_PER_HZ * NSEC_PER_SEC);
+ clk1_div = clamp(int_sqrt(total_div), 1,
+ ADF41513_MAX_CLK_DIVIDER);
+ clk2_div = clamp(DIV_ROUND_CLOSEST(total_div, clk1_div), 1,
+ ADF41513_MAX_CLK_DIVIDER);
+
+ FIELD_MODIFY(ADF41513_REG5_CLK1_DIV_MSK, &st->regs[ADF41513_REG5],
+ clk1_div);
+ FIELD_MODIFY(ADF41513_REG7_CLK2_DIV_MSK, &st->regs[ADF41513_REG7],
+ clk2_div);
+
+ /* enable phase resync */
+ FIELD_MODIFY(ADF41513_REG7_CLK_DIV_MODE_MSK, &st->regs[ADF41513_REG7],
+ ADF41513_CLK_DIV_MODE_PHASE_RESYNC);
+}
+
static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 sync_mask)
{
struct adf41513_pll_settings result;
+ bool pfd_change = false;
+ bool mode_change = false;
int ret;
ret = adf41513_calc_pll_settings(st, &result, freq_uhz);
@@ -589,6 +679,8 @@ static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 s
return ret;
/* apply computed results to pll settings */
+ pfd_change = st->settings.pfd_frequency_uhz != result.pfd_frequency_uhz;
+ mode_change = st->settings.mode != result.mode;
st->settings = result;
dev_dbg(&st->spi->dev,
@@ -630,6 +722,14 @@ static int adf41513_set_frequency(struct adf41513_state *st, u64 freq_uhz, u16 s
st->regs[ADF41513_REG6] |= ADF41513_REG6_BLEED_ENABLE_MSK;
}
+ if (pfd_change) {
+ adf41513_set_bleed_val(st);
+ adf41513_set_phase_resync(st);
+ }
+
+ if (pfd_change || mode_change)
+ adf41513_set_ld_window(st);
+
return adf41513_sync_config(st, sync_mask | ADF41513_SYNC_REG0);
}
@@ -917,6 +1017,11 @@ static int adf41513_parse_fw(struct adf41513_state *st)
st->data.phase_detector_polarity =
device_property_read_bool(dev, "adi,phase-detector-polarity-positive-enable");
+ st->data.phase_resync_period_ns = 0;
+ ret = device_property_read_u32(dev, "adi,phase-resync-period-ns", &tmp);
+ if (!ret)
+ st->data.phase_resync_period_ns = tmp;
+
st->data.logic_lvl_1v8_en = device_property_read_bool(dev, "adi,logic-level-1v8-enable");
tmp = ADF41513_LD_COUNT_MIN;
--
2.43.0
^ permalink raw reply related
* [PATCH v13 12/12] Documentation: ABI: testing: add common ABI file for iio/frequency
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add ABI documentation file for PLL/DDS devices with frequency_resolution
sysfs entry attribute used by both ADF4350 and ADF41513.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Documentation/ABI/testing/sysfs-bus-iio-frequency | 11 +++++++++++
Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350 | 10 ----------
2 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency b/Documentation/ABI/testing/sysfs-bus-iio-frequency
new file mode 100644
index 000000000000..5af31b5b3a19
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency
@@ -0,0 +1,11 @@
+What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_resolution
+KernelVersion: 3.4.0
+Contact: linux-iio@vger.kernel.org
+Description:
+ Stores channel Y frequency resolution/channel spacing in Hz for PLL
+ devices. The given value directly influences the operating mode when
+ fractional-N synthesis is required, as it derives values for
+ configurable modulus parameters used in the calculation of the output
+ frequency. It is assumed that the algorithm that is used to compute
+ the various dividers, is able to generate proper values for multiples
+ of channel spacing.
diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350
index 1254457a726e..76987a119feb 100644
--- a/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350
+++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-adf4350
@@ -1,13 +1,3 @@
-What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_resolution
-KernelVersion: 3.4.0
-Contact: linux-iio@vger.kernel.org
-Description:
- Stores channel Y frequency resolution/channel spacing in Hz.
- The value given directly influences the MODULUS used by
- the fractional-N PLL. It is assumed that the algorithm
- that is used to compute the various dividers, is able to
- generate proper values for multiples of channel spacing.
-
What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_refin_frequency
KernelVersion: 3.4.0
Contact: linux-iio@vger.kernel.org
--
2.43.0
^ permalink raw reply related
* [PATCH v13 11/12] docs: iio: add documentation for adf41513 driver
From: Rodrigo Alencar via B4 Relay @ 2026-05-17 9:14 UTC (permalink / raw)
To: linux-kernel, linux-iio, devicetree, linux-doc
Cc: Jonathan Cameron, David Lechner, Andy Shevchenko,
Lars-Peter Clausen, Michael Hennerich, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Jonathan Corbet, Andrew Morton,
Petr Mladek, Steven Rostedt, Andy Shevchenko, Rasmus Villemoes,
Sergey Senozhatsky, Shuah Khan, Rodrigo Alencar
In-Reply-To: <20260517-adf41513-iio-driver-v13-0-bb6e134a360f@analog.com>
From: Rodrigo Alencar <rodrigo.alencar@analog.com>
Add documentation for ADF41513 driver, which describes the device
driver files and shows how userspace may consume the ABI for various
tasks.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Documentation/iio/adf41513.rst | 199 +++++++++++++++++++++++++++++++++++++++++
Documentation/iio/index.rst | 1 +
MAINTAINERS | 1 +
3 files changed, 201 insertions(+)
diff --git a/Documentation/iio/adf41513.rst b/Documentation/iio/adf41513.rst
new file mode 100644
index 000000000000..244453cce6f6
--- /dev/null
+++ b/Documentation/iio/adf41513.rst
@@ -0,0 +1,199 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+ADF41513 driver
+===============
+
+This driver supports Analog Devices' ADF41513 and similar SPI PLL frequency
+synthesizers.
+
+1. Supported devices
+====================
+
+* `ADF41510 <https://www.analog.com/ADF41510>`_
+* `ADF41513 <https://www.analog.com/ADF41513>`_
+
+The ADF41513 is an ultralow noise frequency synthesizer that can be used to
+implement local oscillators (LOs) as high as 26.5 GHz in the upconversion and
+downconversion sections of wireless receivers and transmitters. The ADF41510
+is a similar device that supports frequencies up to 10 GHz.
+
+Both devices support integer-N and fractional-N operation modes, providing
+excellent phase noise performance and flexible frequency generation
+capabilities.
+
+Key Features:
+
+- **ADF41510**: 1 GHz to 10 GHz frequency range
+- **ADF41513**: 1 GHz to 26.5 GHz frequency range
+- Integer-N and fractional-N operation modes
+- Ultra-low phase noise (-235 dBc/Hz integer-N, -231 dBc/Hz fractional-N)
+- High maximum PFD frequency (250 MHz integer-N, 125 MHz fractional-N)
+- 25-bit fixed modulus or 49-bit variable modulus fractional modes
+- Programmable charge pump currents with 16x range
+- Digital lock detect functionality
+- Phase resync capability for consistent output phase
+
+2. Device attributes
+====================
+
+The ADF41513 driver provides the following IIO extended attributes for
+frequency control and monitoring:
+
+Each IIO device has a device folder under ``/sys/bus/iio/devices/iio:deviceX``,
+where X is the IIO index of the device. Under these folders reside a set of
+device files that provide access to the synthesizer's functionality.
+
+The following table shows the ADF41513 related device files:
+
++--------------------------------------+-------------------------------------------------------+
+| Device file | Description |
++======================================+=======================================================+
+| out_altvoltage0_frequency | RF output frequency control and readback (Hz) |
++--------------------------------------+-------------------------------------------------------+
+| out_altvoltage0_frequency_resolution | Target frequency resolution control (Hz) |
++--------------------------------------+-------------------------------------------------------+
+| out_altvoltage0_powerdown | Power management control (0=active, 1=power down) |
++--------------------------------------+-------------------------------------------------------+
+| out_altvoltage0_phase | RF output phase adjustment and readback (radians) |
++--------------------------------------+-------------------------------------------------------+
+
+2.1 Frequency Control
+----------------------
+
+The ``out_altvoltage0_frequency`` attribute controls the RF output frequency
+with sub-Hz precision. The driver automatically selects between integer-N and
+fractional-N modes to achieve the requested frequency with the best possible
+phase noise performance.
+
+**Supported ranges:**
+
+- **ADF41510**: 1,000,000,000 Hz to 10,000,000,000 Hz (1 GHz to 10 GHz)
+- **ADF41513**: 1,000,000,000 Hz to 26,500,000,000 Hz (1 GHz to 26.5 GHz)
+
+The frequency is specified in Hz, for sub-Hz precision use decimal notation.
+For example, 12.102 GHz would be written as "12102000000.000000".
+
+2.2 Frequency Resolution Control
+--------------------------------
+
+The ``out_altvoltage0_frequency_resolution`` attribute controls the target
+frequency resolution that the driver attempts to achieve. This affects the
+choice between integer-N and fractional-N modes, including fixed modulus
+(25-bit) and variable modulus (49-bit) fractional-N modes:
+
+- **Integer-N**: Resolution = :math:`f_{PFD}` (same as PFD frequency)
+- **Fixed modulus**: Resolution = :math:`f_{PFD} / 2^{25}` (~3 Hz with 100 MHz PFD)
+- **Variable modulus**: Resolution = :math:`f_{PFD} / 2^{49}` (µHz resolution possible)
+
+Default resolution is 1 Hz (1,000,000 µHz).
+
+2.3 Phase adjustment
+--------------------
+
+The ``out_altvoltage0_phase`` attribute allows adjustment of the output phase
+in radians. Setting this attribute enables phase adjustment. It can be set
+from 0 to :math:`2\pi` radians. Reading this attribute returns the current
+phase offset of the output signal. To create a consistent phase relationship
+with the reference signal, the phase resync feature needs to be enabled by
+setting a non-zero value to the ``adi,phase-resync-period-ns`` device property,
+which triggers a phase resynchronization after locking is achieved.
+
+3. Operating modes
+==================
+
+3.1 Integer-N Mode
+------------------
+
+When the requested frequency can be achieved as an integer multiple of the PFD
+frequency (within the specified resolution tolerance), the driver automatically
+selects integer-N mode for optimal phase noise performance.
+
+In integer-N mode:
+
+- Phase noise: -235 dBc/Hz normalized floor
+- Frequency resolution: :math:`f_{PFD}` (same as PFD frequency)
+- Maximum PFD frequency: 250 MHz
+- Bleed current: Disabled
+
+3.2 Fractional-N Mode
+---------------------
+
+When sub-integer frequency steps are required, the driver automatically selects
+fractional-N mode using either fixed or variable modulus.
+
+**Fixed Modulus (25-bit)**:
+
+- Used when variable modulus is not required
+- Resolution: :math:`f_{PFD} / 2^{25}`
+- Simpler implementation, faster settling
+
+**Variable Modulus (49-bit)**:
+
+- Used for maximum resolution requirements
+- Resolution: :math:`f_{PFD} / 2^{49}` (theoretical)
+- Exact frequency synthesis capability
+
+In fractional-N mode:
+
+- Phase noise: -231 dBc/Hz normalized floor
+- Maximum PFD frequency: 125 MHz
+- Bleed current: Automatically enabled and optimized
+- Dithering: Enabled to reduce fractional spurs
+
+3.3 Automatic Mode Selection
+----------------------------
+
+The driver automatically selects the optimal operating mode based on:
+
+1. **Frequency accuracy requirements**: Determined by ``frequency_resolution`` setting
+2. **Phase noise optimization**: Integer-N preferred when possible
+3. **PFD frequency constraints**: Different limits for integer vs fractional modes
+4. **Prescaler selection**: Automatic 4/5 vs 8/9 prescaler selection based on frequency
+
+4. Usage examples
+=================
+
+4.1 Basic Frequency Setting
+----------------------------
+
+Set output frequency to 12.102 GHz:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> echo 12102000000 > out_altvoltage0_frequency
+
+Read current frequency:
+
+.. code-block:: bash
+
+ root:/sys/bus/iio/devices/iio:device0> cat out_altvoltage0_frequency
+ 12101999999.582767
+
+4.2 High Resolution Frequency Control
+-------------------------------------
+
+Configure for sub-Hz resolution and set a precise frequency:
+
+.. code-block:: bash
+
+ # Set resolution to 0.1 Hz (100,000 µHz)
+ root:/sys/bus/iio/devices/iio:device0> echo 0.1 > out_altvoltage0_frequency_resolution
+
+ # Set frequency to 12.102 GHz (1 µHz precision)
+ root:/sys/bus/iio/devices/iio:device0> echo 12102000000 > out_altvoltage0_frequency
+ root:/sys/bus/iio/devices/iio:device0> cat out_altvoltage0_frequency
+ 12101999999.980131
+
+4.3 Monitor Lock Status
+-----------------------
+
+When lock detect GPIO is configured, check if PLL is locked:
+
+.. code-block:: bash
+
+ # Read frequency - will return error if not locked
+ root:/sys/bus/iio/devices/iio:device0> cat out_altvoltage0_frequency
+
+If the PLL is not locked, the frequency read will return ``-EBUSY`` (Device or
+resource busy).
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index 007e0a1fcc5a..b02b879b053a 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -31,6 +31,7 @@ Industrial I/O Kernel Drivers
ad7625
ad7944
ade9000
+ adf41513
adis16475
adis16480
adis16550
diff --git a/MAINTAINERS b/MAINTAINERS
index 6d695913e717..33168d20ca48 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1668,6 +1668,7 @@ L: linux-iio@vger.kernel.org
S: Supported
W: https://ez.analog.com/linux-software-drivers
F: Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
+F: Documentation/iio/adf41513.rst
F: drivers/iio/frequency/adf41513.c
ANALOG DEVICES INC ADF4377 DRIVER
--
2.43.0
^ permalink raw reply related
* Re: [PATCH v2] dcache: add fs.dentry-limit sysctl with negative-first reaper
From: Mateusz Guzik @ 2026-05-17 9:15 UTC (permalink / raw)
To: Horst Birthelmer
Cc: Miklos Szeredi, Jonathan Corbet, Shuah Khan, Alexander Viro,
Christian Brauner, Jan Kara, linux-doc, linux-kernel,
linux-fsdevel, Horst Birthelmer
In-Reply-To: <20260516-limit-dentries-cache-v2-1-c733a78e603b@ddn.com>
On Sat, May 16, 2026 at 04:52:54PM +0200, Horst Birthelmer wrote:
> From: Horst Birthelmer <hbirthelmer@ddn.com>
>
> The dcache only shrinks under memory pressure, which is rarely reached
> on machines with ample RAM, so cached negative dentries can accumulate
> without bound. Give administrators a soft cap they can set,
> and a background worker that prefers negative dentries when reclaiming.
>
> Two new sysctls under /proc/sys/fs/:
>
> dentry-limit -- soft cap on nr_dentry. 0 (default)
> disables the feature; behaviour is then
> identical to before.
> dentry-limit-interval-ms -- pacing for the worker while still over
> the cap. Default 1000, minimum 1.
>
> When the cap is exceeded, a delayed_work runs in two phases:
>
> 1. iterate_supers() draining only negative dentries from every LRU.
> Positive entries are rotated past so the walk makes progress.
> DCACHE_REFERENCED is ignored here on purpose -- an admin-imposed
> cap should evict even hot negatives before any positive entry.
> 2. If still over the cap, iterate_supers() again with the same
> isolate callback the memory-pressure shrinker uses.
>
> Signed-off-by: Horst Birthelmer <hbirthelmer@ddn.com>
> ---
> There was a discussion at LSFMM about servers with too many cached
> negative dentries.
> That gave me the idea to keep the dentries in general limited
> if the system administrator needs it to.
>
I wrote about the negative entries problem here:
https://lore.kernel.org/linux-fsdevel/f7bp3ggliqbb7adyysonxgvo6zn76mo4unroagfcuu3bfghynu@7wkgqkfb5c43/#t
The mechanism as suggested here will end up evicting *useful* negative
entries. Granted, they will be recreated soon enough so it's not a
tragedy but it still is an avoidable perf loss.
What is needed in the long run is a mechanism which aggressively
recycles stale negative entries and recognizes which ones should be
saved for the time being.
Below some magic threshold you just allocate a new negative entry.
All new entries would get a grace period where they need to get hits and
prove useful OR get whacked. If you are at or above the threshold and
are allocating a new entry, you can whack the oldest negative one which
did not make it.
This is just one idea, what is not up for debate is the discrepancy
between small subset of negative entires with tons of hits vs the ones
which get virtually no traffic at all.
Whatever the mechanism it will have to take advantage of it.
> This is somewhat related to [1] where it would address the same
> symptoms but in a more unobtrusive way, by just garbage collecting
> the negative and then the unused cache entries.
>
> The other effect I have seen regarding this is that FUSE
> will not forget inodes (no FORGET call to the FUSE server)
> even after the latest reference has been closed until much later.
>
> In a FUSE server that mirrors the kernel cached inodes in user space
> because it has to keep a lot of private data for every node
> this puts an unnecessarry memory strain on that userspace entity
> especially if the memory is limited for its cgroup.
I don't know anything about how FUSE works. In this context I presume
you have a mount point backed by FUSE and the problematic memory usage
stems from inodes created against such a mount point.
This would suggest you would be better served with a mechanism which
allows userspace to cull some number of dentries for a given mount
point, maybe even with an optional preference for negative entries if
that's considered better for given fs.
Or to put it differently, I would look into exposing sb shrinkers to
root instead of rolling with a global scan.
> +static enum lru_status dentry_lru_isolate_negative(struct list_head *item,
> + struct list_lru_one *lru, void *arg)
> +{
> + struct list_head *freeable = arg;
> + struct dentry *dentry = container_of(item, struct dentry, d_lru);
> +
> + if (!spin_trylock(&dentry->d_lock))
> + return LRU_SKIP;
If anything of the sort is to land, you definitely want to pre-check
d_count and d_is_negative without the lock.
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox