* Re: [PATCH 4/8] drm/panthor: Add support for protected memory allocation in panthor
From: Boris Brezillon @ 2026-05-06 15:05 UTC (permalink / raw)
To: Maxime Ripard
Cc: Ketil Johnsen, David Airlie, Simona Vetter, Maarten Lankhorst,
Thomas Zimmermann, Jonathan Corbet, Shuah Khan, Sumit Semwal,
Benjamin Gaignard, Brian Starkey, John Stultz, T.J. Mercier,
Christian König, Steven Price, Liviu Dudau, Daniel Almeida,
Alice Ryhl, Matthias Brugger, AngeloGioacchino Del Regno,
dri-devel, linux-doc, linux-kernel, linux-media, linaro-mm-sig,
linux-arm-kernel, linux-mediatek, Florent Tomasin
In-Reply-To: <20260506-golden-python-of-aptitude-ff972a@houat>
On Wed, 6 May 2026 15:12:37 +0200
Maxime Ripard <mripard@kernel.org> wrote:
> On Wed, May 06, 2026 at 12:50:15PM +0200, Boris Brezillon wrote:
> > On Wed, 6 May 2026 12:08:24 +0200
> > Maxime Ripard <mripard@kernel.org> wrote:
> >
> > > Hi,
> > >
> > > On Tue, May 05, 2026 at 04:05:10PM +0200, Ketil Johnsen wrote:
> > > > From: Florent Tomasin <florent.tomasin@arm.com>
> > > >
> > > > This patch allows Panthor to allocate buffer objects from a
> > > > protected heap. The Panthor driver should be seen as a consumer
> > > > of the heap and not an exporter.
> > > >
> > > > Protected memory buffers needed by the Panthor driver:
> > > > - On CSF FW load, the Panthor driver must allocate a protected
> > > > buffer object to hold data to use by the FW when in protected
> > > > mode. This protected buffer object is owned by the device
> > > > and does not belong to a process.
> > > > - On CSG creation, the Panthor driver must allocate a protected
> > > > suspend buffer object for the FW to store data when suspending
> > > > the CSG while in protected mode. The kernel owns this allocation
> > > > and does not allow user space mapping. The format of the data
> > > > in this buffer is only known by the FW and does not need to be
> > > > shared with other entities.
> > > >
> > > > The driver will retrieve the protected heap using the name of the
> > > > heap provided to the driver as module parameter.
> > >
> > > I know it's what dma_heap_find asks for, but I wonder if it wouldn't be
> > > better in the device tree and lookup through the device node? heaps are
> > > going to have a node anyway, right?
> >
> > I'm not too sure. Take the PROTMEM (name="protected,xxxx") dma_heaps
> > instantiated by optee for instance, I don't think the originating
> > tee_device comes from a device node, nor is the underlying heap
> > described as a device node. The reserved memory pool this protected heap
> > comes from is most likely defined somewhere as reserved memory in the
> > DT, but there's nothing to correlate this range of reserved mem to some
> > sub-range that the TEE implementation is carving out to provide
> > protected memory.
>
> Maybe we should be working on a dt bindings for heaps then? Something
> simple like we have for clocks with a phandle and an ID would probably
> be enough. In optee's case, it looks like it would map nicely with
> TEE_DMA_HEAP_* flags too.
Sure.
>
> The only two that wouldn't be covered would be the system and default
> CMA heap if not setup in the DT, which shouldn't be too bad for this
> particular use-case.
I'm not opposed to the idea of describing the association through the
DT (with a <phandle, ID> pair). My main fear is that it drags us into
endless discussions around what's considered HW description and what's
not (PTSD of all those DT-bindings discussions I suppose :-)), which
ends up delaying the merging of Panthor's protected memory support.
Honestly, at this point I'm considering going back to my initial
suggestion to add a dedicated ioctl() (requiring high privilege) to let
the user pass the memory for the FW protected sections as a dmabuf FD.
Given we don't need those sections to be populated for the FW to boot,
it wouldn't block the probe of the driver, it would just prevent PROTM
usage until those sections are populated.
This would let us make progress with the rest of the changes in this
patchset, while the community decides how they want to expose dma_heaps
to in-kernel users.
^ permalink raw reply
* Re: [PATCH 6/8] watchdog: sc520: Drop AMD Elan SC520 support
From: Guenter Roeck @ 2026-05-06 14:51 UTC (permalink / raw)
To: Sean Young, linux-kernel, Wim Van Sebroeck, Jonathan Corbet,
Shuah Khan
Cc: linux-watchdog, linux-doc
In-Reply-To: <59c97749c14a4ad1039bb9cc920cf32babb9d6f7.1778071745.git.sean@mess.org>
On 5/6/26 07:42, Sean Young wrote:
> Since commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"),
> this board is no longer supported. Remove the watchdog too.
>
> Signed-off-by: Sean Young <sean@mess.org>
I already submitted the same patch yesterday and applied it to my
watchdog-next branch.
Guenter
^ permalink raw reply
* Re: [PATCH 07/11] of: reserved_mem: add no-dump crash_mem exclusion helpers
From: Rob Herring @ 2026-05-06 14:50 UTC (permalink / raw)
To: Chen Wandun
Cc: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree, akpm, bhe, rppt, pasha.tatashin,
pratyush, ruirui.yang, corbet, skhan, catalin.marinas, will,
chenhuacai, kernel, pjw, palmer, aou, saravanak, chenwandun,
zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-8-chenwandun@lixiang.com>
On Wed, Apr 29, 2026 at 02:58:27PM +0800, Chen Wandun wrote:
> Provide two kdump-oriented helpers so that arch kexec_file code does
> not have to open-code the no-dump filtering loop:
>
> - of_reserved_mem_no_dump_nr_ranges() returns the number of reserved
> regions flagged with linux,no-dump. Each exclusion may split one
> existing crash_mem range into two, so callers use this count to
> pre-size their crash_mem allocation.
>
> - of_reserved_mem_exclude_no_dump() walks the reserved_mem[] array
> and calls crash_exclude_mem_range() for each no-dump region.
>
> Both helpers are guarded by CONFIG_KEXEC_FILE; empty inline stubs are
> provided for the !KEXEC_FILE case so architecture code can call them
> unconditionally.
>
> The consumers are added in the following arm64, riscv and loongarch
> patches in this series.
>
> Signed-off-by: Chen Wandun <chenwandun@lixiang.com>
> Tested-by: Zhao Meijing <zhaomeijing@lixiang.com>
> ---
> drivers/of/of_reserved_mem.c | 54 +++++++++++++++++++++++++++++++++
> include/linux/of_reserved_mem.h | 15 +++++++++
> 2 files changed, 69 insertions(+)
>
> diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c
> index 4b80420da2d2..038056a6408a 100644
> --- a/drivers/of/of_reserved_mem.c
> +++ b/drivers/of/of_reserved_mem.c
> @@ -27,6 +27,10 @@
>
> #include "of_private.h"
>
> +#ifdef CONFIG_KEXEC_FILE
> +#include <linux/crash_core.h>
> +#endif
You shouldn't need ifdef around includes.
> +
> static struct reserved_mem reserved_mem_array[MAX_RESERVED_REGIONS] __initdata;
> static struct reserved_mem *reserved_mem __refdata = reserved_mem_array;
> static int total_reserved_mem_cnt = MAX_RESERVED_REGIONS;
> @@ -916,6 +920,56 @@ struct reserved_mem *of_reserved_mem_lookup(struct device_node *np)
> }
> EXPORT_SYMBOL_GPL(of_reserved_mem_lookup);
>
> +#ifdef CONFIG_KEXEC_FILE
Use 'if (IS_ENABLED())' within the function.
> +/**
> + * of_reserved_mem_no_dump_nr_ranges() - count reserved regions flagged
> + * with the linux,no-dump property.
> + *
> + * Each such region may split an existing crash_mem range into two when
> + * it is excluded, so callers can use this count to pre-size their
> + * crash_mem allocation.
> + */
> +unsigned int of_reserved_mem_no_dump_nr_ranges(void)
> +{
> + unsigned int i, n = 0;
> +
> + for (i = 0; i < reserved_mem_count; i++)
> + if (reserved_mem[i].no_dump)
> + n++;
> + return n;
> +}
> +
> +/**
> + * of_reserved_mem_exclude_no_dump() - exclude no-dump reserved regions
> + * from a crash_mem list.
> + * @cmem: crash memory list to modify
> + *
> + * Walks the reserved_mem[] array and calls crash_exclude_mem_range() for
> + * every region with no_dump set. Intended to be called from arch kdump
> + * code when constructing the elfcorehdr.
> + *
> + * Returns 0 on success, or a negative error returned by
> + * crash_exclude_mem_range() on the first failure.
> + */
> +int of_reserved_mem_exclude_no_dump(struct crash_mem *cmem)
> +{
> + unsigned int i;
> + int ret;
> +
> + for (i = 0; i < reserved_mem_count; i++) {
> + struct reserved_mem *r = &reserved_mem[i];
> +
> + if (!r->no_dump || !r->size)
> + continue;
> + ret = crash_exclude_mem_range(cmem, r->base,
> + r->base + r->size - 1);
> + if (ret)
> + return ret;
> + }
> + return 0;
> +}
> +#endif /* CONFIG_KEXEC_FILE */
^ permalink raw reply
* Re: [PATCH 05/11] of: reserved_mem: add linux,no-dump property support for reserved memory regions
From: Rob Herring @ 2026-05-06 14:45 UTC (permalink / raw)
To: Chen Wandun
Cc: kexec, linux-doc, linux-kernel, linux-arm-kernel, loongarch,
linux-riscv, devicetree, akpm, bhe, rppt, pasha.tatashin,
pratyush, ruirui.yang, corbet, skhan, catalin.marinas, will,
chenhuacai, kernel, pjw, palmer, aou, saravanak, chenwandun,
zhaomeijing, everyzhao
In-Reply-To: <20260429065831.1510858-6-chenwandun@lixiang.com>
On Wed, Apr 29, 2026 at 02:58:25PM +0800, Chen Wandun wrote:
> Add a 'no_dump' field to struct reserved_mem and parse the
> 'linux,no-dump' device tree property during reserved memory node
> initialization. This property allows device tree authors to mark
> specific reserved memory regions that should be excluded from kdump
> vmcore dumps.
>
> Reserved memory regions used by device firmware (e.g., GPU, DSP, modem)
> typically contain data that is not useful for kernel crash analysis and
> can significantly increase vmcore size. The 'linux,no-dump' property
> provides a declarative way to indicate these regions should be filtered
> out when constructing the elfcorehdr for kdump.
>
> The property is named with a 'linux,' prefix because kdump/vmcore is
> Linux-specific and the property is an OS hint rather than a hardware
> description, matching existing properties such as 'linux,cma-default'
> and 'linux,usable-memory-range'.
>
> The 'linux,no-dump' property is only effective when the region:
> - Does not have 'no-map': these regions are already excluded from
> vmcore since they are removed from the linear mapping (MEMBLOCK_NOMAP).
> - Does not have 'reusable': CMA reusable regions are actively used by
> the kernel for movable page allocations, and their contents are
> valuable for crash analysis.
>
> The no-dump status is also printed in the boot log alongside the
> existing nomap and reusable flags for diagnostic purposes.
I think this property is the wrong way around and probably not needed.
The default should be exclude the regions, but if Linux is using the
regions (like CMA) then it can decide on its own to include them.
With the restructuring that went into 7.1, it should be possible for the
CMA code (and code for any other regions) to set some flag for the
region.
Rob
^ permalink raw reply
* [PATCH 6/8] watchdog: sc520: Drop AMD Elan SC520 support
From: Sean Young @ 2026-05-06 14:42 UTC (permalink / raw)
To: linux-kernel, Wim Van Sebroeck, Guenter Roeck, Jonathan Corbet,
Shuah Khan
Cc: linux-watchdog, linux-doc
In-Reply-To: <cover.1778071745.git.sean@mess.org>
Since commit 8b793a92d862 ("x86/cpu: Remove M486/M486SX/ELAN support"),
this board is no longer supported. Remove the watchdog too.
Signed-off-by: Sean Young <sean@mess.org>
---
.../watchdog/watchdog-parameters.rst | 9 -
drivers/watchdog/Kconfig | 13 -
drivers/watchdog/Makefile | 1 -
drivers/watchdog/sc520_wdt.c | 430 ------------------
4 files changed, 453 deletions(-)
delete mode 100644 drivers/watchdog/sc520_wdt.c
diff --git a/Documentation/watchdog/watchdog-parameters.rst b/Documentation/watchdog/watchdog-parameters.rst
index 773241ed9986..638b972e0b16 100644
--- a/Documentation/watchdog/watchdog-parameters.rst
+++ b/Documentation/watchdog/watchdog-parameters.rst
@@ -532,15 +532,6 @@ sc1200wdt:
-------------------------------------------------
-sc520_wdt:
- timeout:
- Watchdog timeout in seconds. (1 <= timeout <= 3600, default=30)
- nowayout:
- Watchdog cannot be stopped once started
- (default=kernel config parameter)
-
--------------------------------------------------
-
sch311x_wdt:
force_id:
Override the detected device ID
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index dc78729ba2a5..4e36e4b5bf39 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -1270,19 +1270,6 @@ config GEODE_WDT
You can compile this driver directly into the kernel, or use
it as a module. The module will be called geodewdt.
-config SC520_WDT
- tristate "AMD Elan SC520 processor Watchdog"
- depends on MELAN || COMPILE_TEST
- help
- This is the driver for the hardware watchdog built in to the
- AMD "Elan" SC520 microcomputer commonly used in embedded systems.
- This watchdog simply watches your kernel to make sure it doesn't
- freeze, and if it does, it reboots your computer after a certain
- amount of time.
-
- You can compile this driver directly into the kernel, or use
- it as a module. The module will be called sc520_wdt.
-
config SBC_FITPC2_WATCHDOG
tristate "Compulab SBC-FITPC2 watchdog"
depends on (X86 || COMPILE_TEST) && HAS_IOPORT
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index d2fb16b9f9ce..1daeab5b2473 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -116,7 +116,6 @@ obj-$(CONFIG_EXAR_WDT) += exar_wdt.o
obj-$(CONFIG_F71808E_WDT) += f71808e_wdt.o
obj-$(CONFIG_SP5100_TCO) += sp5100_tco.o
obj-$(CONFIG_GEODE_WDT) += geodewdt.o
-obj-$(CONFIG_SC520_WDT) += sc520_wdt.o
obj-$(CONFIG_SBC_FITPC2_WATCHDOG) += sbc_fitpc2_wdt.o
obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o
obj-$(CONFIG_IB700_WDT) += ib700wdt.o
diff --git a/drivers/watchdog/sc520_wdt.c b/drivers/watchdog/sc520_wdt.c
deleted file mode 100644
index 005f62e4a4fb..000000000000
--- a/drivers/watchdog/sc520_wdt.c
+++ /dev/null
@@ -1,430 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-/*
- * AMD Elan SC520 processor Watchdog Timer driver
- *
- * Based on acquirewdt.c by Alan Cox,
- * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net>
- *
- * The authors do NOT admit liability nor provide warranty for
- * any of this software. This material is provided "AS-IS" in
- * the hope that it may be useful for others.
- *
- * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net>
- * 9/27 - 2001 [Initial release]
- *
- * Additional fixes Alan Cox
- * - Fixed formatting
- * - Removed debug printks
- * - Fixed SMP built kernel deadlock
- * - Switched to private locks not lock_kernel
- * - Used ioremap/writew/readw
- * - Added NOWAYOUT support
- * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com>
- * - Change comments
- * - Eliminate fop_llseek
- * - Change CONFIG_WATCHDOG_NOWAYOUT semantics
- * - Add KERN_* tags to printks
- * - fix possible wdt_is_open race
- * - Report proper capabilities in watchdog_info
- * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT,
- * GETTIMEOUT, SETOPTIONS} ioctls
- * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be>
- * - cleanup of trailing spaces
- * - added extra printk's for startup problems
- * - use module_param
- * - made timeout (the emulated heartbeat) a module_param
- * - made the keepalive ping an internal subroutine
- * 3/27 - 2004 Changes by Sean Young <sean@mess.org>
- * - set MMCR_BASE to 0xfffef000
- * - CBAR does not need to be read
- * - removed debugging printks
- *
- * This WDT driver is different from most other Linux WDT
- * drivers in that the driver will ping the watchdog by itself,
- * because this particular WDT has a very short timeout (1.6
- * seconds) and it would be insane to count on any userspace
- * daemon always getting scheduled within that time frame.
- *
- * This driver uses memory mapped IO, and spinlock.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/module.h>
-#include <linux/moduleparam.h>
-#include <linux/types.h>
-#include <linux/timer.h>
-#include <linux/miscdevice.h>
-#include <linux/watchdog.h>
-#include <linux/fs.h>
-#include <linux/ioport.h>
-#include <linux/notifier.h>
-#include <linux/reboot.h>
-#include <linux/init.h>
-#include <linux/jiffies.h>
-#include <linux/io.h>
-#include <linux/uaccess.h>
-
-
-/*
- * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7)
- *
- * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s
- * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s
- *
- * We will program the SC520 watchdog for a timeout of 2.01s.
- * If we reset the watchdog every ~250ms we should be safe.
- */
-
-#define WDT_INTERVAL (HZ/4+1)
-
-/*
- * We must not require too good response from the userspace daemon.
- * Here we require the userspace daemon to send us a heartbeat
- * char to /dev/watchdog every 30 seconds.
- */
-
-#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */
-/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */
-static int timeout = WATCHDOG_TIMEOUT;
-module_param(timeout, int, 0);
-MODULE_PARM_DESC(timeout,
- "Watchdog timeout in seconds. (1 <= timeout <= 3600, default="
- __MODULE_STRING(WATCHDOG_TIMEOUT) ")");
-
-static bool nowayout = WATCHDOG_NOWAYOUT;
-module_param(nowayout, bool, 0);
-MODULE_PARM_DESC(nowayout,
- "Watchdog cannot be stopped once started (default="
- __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
-
-/*
- * AMD Elan SC520 - Watchdog Timer Registers
- */
-#define MMCR_BASE 0xfffef000 /* The default base address */
-#define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */
-
-/* WDT Control Register bit definitions */
-#define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */
-#define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */
-#define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */
-#define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */
-#define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */
-#define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */
-#define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */
-#define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */
-#define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */
-#define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */
-#define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */
-
-static __u16 __iomem *wdtmrctl;
-
-static void wdt_timer_ping(struct timer_list *);
-static DEFINE_TIMER(timer, wdt_timer_ping);
-static unsigned long next_heartbeat;
-static unsigned long wdt_is_open;
-static char wdt_expect_close;
-static DEFINE_SPINLOCK(wdt_spinlock);
-
-/*
- * Whack the dog
- */
-
-static void wdt_timer_ping(struct timer_list *unused)
-{
- /* If we got a heartbeat pulse within the WDT_US_INTERVAL
- * we agree to ping the WDT
- */
- if (time_before(jiffies, next_heartbeat)) {
- /* Ping the WDT */
- spin_lock(&wdt_spinlock);
- writew(0xAAAA, wdtmrctl);
- writew(0x5555, wdtmrctl);
- spin_unlock(&wdt_spinlock);
-
- /* Re-set the timer interval */
- mod_timer(&timer, jiffies + WDT_INTERVAL);
- } else
- pr_warn("Heartbeat lost! Will not ping the watchdog\n");
-}
-
-/*
- * Utility routines
- */
-
-static void wdt_config(int writeval)
-{
- unsigned long flags;
-
- /* buy some time (ping) */
- spin_lock_irqsave(&wdt_spinlock, flags);
- readw(wdtmrctl); /* ensure write synchronization */
- writew(0xAAAA, wdtmrctl);
- writew(0x5555, wdtmrctl);
- /* unlock WDT = make WDT configuration register writable one time */
- writew(0x3333, wdtmrctl);
- writew(0xCCCC, wdtmrctl);
- /* write WDT configuration register */
- writew(writeval, wdtmrctl);
- spin_unlock_irqrestore(&wdt_spinlock, flags);
-}
-
-static int wdt_startup(void)
-{
- next_heartbeat = jiffies + (timeout * HZ);
-
- /* Start the timer */
- mod_timer(&timer, jiffies + WDT_INTERVAL);
-
- /* Start the watchdog */
- wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04);
-
- pr_info("Watchdog timer is now enabled\n");
- return 0;
-}
-
-static int wdt_turnoff(void)
-{
- /* Stop the timer */
- timer_delete_sync(&timer);
-
- /* Stop the watchdog */
- wdt_config(0);
-
- pr_info("Watchdog timer is now disabled...\n");
- return 0;
-}
-
-static int wdt_keepalive(void)
-{
- /* user land ping */
- next_heartbeat = jiffies + (timeout * HZ);
- return 0;
-}
-
-static int wdt_set_heartbeat(int t)
-{
- if ((t < 1) || (t > 3600)) /* arbitrary upper limit */
- return -EINVAL;
-
- timeout = t;
- return 0;
-}
-
-/*
- * /dev/watchdog handling
- */
-
-static ssize_t fop_write(struct file *file, const char __user *buf,
- size_t count, loff_t *ppos)
-{
- /* See if we got the magic character 'V' and reload the timer */
- if (count) {
- if (!nowayout) {
- size_t ofs;
-
- /* note: just in case someone wrote the magic character
- * five months ago... */
- wdt_expect_close = 0;
-
- /* now scan */
- for (ofs = 0; ofs != count; ofs++) {
- char c;
- if (get_user(c, buf + ofs))
- return -EFAULT;
- if (c == 'V')
- wdt_expect_close = 42;
- }
- }
-
- /* Well, anyhow someone wrote to us, we should
- return that favour */
- wdt_keepalive();
- }
- return count;
-}
-
-static int fop_open(struct inode *inode, struct file *file)
-{
- /* Just in case we're already talking to someone... */
- if (test_and_set_bit(0, &wdt_is_open))
- return -EBUSY;
- if (nowayout)
- __module_get(THIS_MODULE);
-
- /* Good, fire up the show */
- wdt_startup();
- return stream_open(inode, file);
-}
-
-static int fop_close(struct inode *inode, struct file *file)
-{
- if (wdt_expect_close == 42)
- wdt_turnoff();
- else {
- pr_crit("Unexpected close, not stopping watchdog!\n");
- wdt_keepalive();
- }
- clear_bit(0, &wdt_is_open);
- wdt_expect_close = 0;
- return 0;
-}
-
-static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
-{
- void __user *argp = (void __user *)arg;
- int __user *p = argp;
- static const struct watchdog_info ident = {
- .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT
- | WDIOF_MAGICCLOSE,
- .firmware_version = 1,
- .identity = "SC520",
- };
-
- switch (cmd) {
- case WDIOC_GETSUPPORT:
- return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0;
- case WDIOC_GETSTATUS:
- case WDIOC_GETBOOTSTATUS:
- return put_user(0, p);
- case WDIOC_SETOPTIONS:
- {
- int new_options, retval = -EINVAL;
-
- if (get_user(new_options, p))
- return -EFAULT;
-
- if (new_options & WDIOS_DISABLECARD) {
- wdt_turnoff();
- retval = 0;
- }
-
- if (new_options & WDIOS_ENABLECARD) {
- wdt_startup();
- retval = 0;
- }
-
- return retval;
- }
- case WDIOC_KEEPALIVE:
- wdt_keepalive();
- return 0;
- case WDIOC_SETTIMEOUT:
- {
- int new_timeout;
-
- if (get_user(new_timeout, p))
- return -EFAULT;
-
- if (wdt_set_heartbeat(new_timeout))
- return -EINVAL;
-
- wdt_keepalive();
- }
- fallthrough;
- case WDIOC_GETTIMEOUT:
- return put_user(timeout, p);
- default:
- return -ENOTTY;
- }
-}
-
-static const struct file_operations wdt_fops = {
- .owner = THIS_MODULE,
- .write = fop_write,
- .open = fop_open,
- .release = fop_close,
- .unlocked_ioctl = fop_ioctl,
- .compat_ioctl = compat_ptr_ioctl,
-};
-
-static struct miscdevice wdt_miscdev = {
- .minor = WATCHDOG_MINOR,
- .name = "watchdog",
- .fops = &wdt_fops,
-};
-
-/*
- * Notifier for system down
- */
-
-static int wdt_notify_sys(struct notifier_block *this, unsigned long code,
- void *unused)
-{
- if (code == SYS_DOWN || code == SYS_HALT)
- wdt_turnoff();
- return NOTIFY_DONE;
-}
-
-/*
- * The WDT needs to learn about soft shutdowns in order to
- * turn the timebomb registers off.
- */
-
-static struct notifier_block wdt_notifier = {
- .notifier_call = wdt_notify_sys,
-};
-
-static void __exit sc520_wdt_unload(void)
-{
- if (!nowayout)
- wdt_turnoff();
-
- /* Deregister */
- misc_deregister(&wdt_miscdev);
- unregister_reboot_notifier(&wdt_notifier);
- iounmap(wdtmrctl);
-}
-
-static int __init sc520_wdt_init(void)
-{
- int rc = -EBUSY;
-
- /* Check that the timeout value is within it's range ;
- if not reset to the default */
- if (wdt_set_heartbeat(timeout)) {
- wdt_set_heartbeat(WATCHDOG_TIMEOUT);
- pr_info("timeout value must be 1 <= timeout <= 3600, using %d\n",
- WATCHDOG_TIMEOUT);
- }
-
- wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, 2);
- if (!wdtmrctl) {
- pr_err("Unable to remap memory\n");
- rc = -ENOMEM;
- goto err_out_region2;
- }
-
- rc = register_reboot_notifier(&wdt_notifier);
- if (rc) {
- pr_err("cannot register reboot notifier (err=%d)\n", rc);
- goto err_out_ioremap;
- }
-
- rc = misc_register(&wdt_miscdev);
- if (rc) {
- pr_err("cannot register miscdev on minor=%d (err=%d)\n",
- WATCHDOG_MINOR, rc);
- goto err_out_notifier;
- }
-
- pr_info("WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n",
- timeout, nowayout);
-
- return 0;
-
-err_out_notifier:
- unregister_reboot_notifier(&wdt_notifier);
-err_out_ioremap:
- iounmap(wdtmrctl);
-err_out_region2:
- return rc;
-}
-
-module_init(sc520_wdt_init);
-module_exit(sc520_wdt_unload);
-
-MODULE_AUTHOR("Scott and Bill Jennings");
-MODULE_DESCRIPTION(
- "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor");
-MODULE_LICENSE("GPL");
--
2.54.0
^ permalink raw reply related
* Re: [PATCH] crypto: af_alg - Document the deprecation of AF_ALG
From: Jeff Barnes @ 2026-05-06 14:42 UTC (permalink / raw)
To: Andy Lutomirski
Cc: Eric Biggers, linux-crypto@vger.kernel.org, Herbert Xu,
linux-doc@vger.kernel.org, linux-api@vger.kernel.org,
linux-kernel@vger.kernel.org, netdev@vger.kernel.org,
Linus Torvalds
In-Reply-To: <CALCETrVqG+1yErRJjkxvJrf=A+Vu84HTR4Bx1Pcd8G1C0PJcMA@mail.gmail.com>
Hi,
On May 5 2026, at 7:17 pm, Andy Lutomirski <luto@amacapital.net> wrote:
>> On Apr 29, 2026, at 6:19 PM, Eric Biggers <ebiggers@kernel.org> wrote:
>>
>> AF_ALG is almost completely unnecessary, and it exposes a massive attack
>> surface that hasn't been standing up to modern vulnerability discovery
>> tools. The latest one even has its own website, providing a small
>> Python script that reliably roots most Linux distros: https://copy.fail/
>
> How about adding a configuration option, defaulted on, that requires
> capable(CAP_SYS_ADMIN) to create the socket (and maybe also to bind /
> connect it). And a sysctl to allow the administrator to override this
> in the unlikely event that it’s needed.
>
> IIRC cryptsetup used to and maybe even still does require these
> sockets sometimes and this would let it keep working. And there's all
> the FIPS stuff downthread.
Apologize in advance for the long-winded answer.
The "FIPS stuff" centers on using sha512hmac -> libkcapi -> AF_ALG for
verifying integrity. The early‑boot sha512hmac check that some
distributions use (typically from initramfs) sits at an awkward
intersection of multiple standards, and it may help to clarify where it
actually fits and where it doesn't.
From a standards perspective, FIPS 140‑3 requires a cryptographic module
to perform self‑integrity verification using an approved algorithm and
to prevent the module from entering an operational state on failure. In
the Linux kernel, the cryptographic module is the kernel crypto
subsystem, and these requirements are met by the kernel’s internal
power‑up self‑tests (KATs, etc.) on the crypto code and critical data as
loaded into memory.
FIPS 199 / SP 800‑53 (e.g., SI‑7) impose system‑level integrity
requirements (for Moderate impact systems), i.e., that unauthorized
modification of critical components is prevented or detected and that
failures result in a protective action. These controls are explicitly
technology‑agnostic and are not limited to cryptographic‑module self‑tests.
The sha512hmac check is not the FIPS 140‑3 cryptographic‑module
self‑integrity test. Instead, it has historically been used as a system
integrity control that provides auditors with assurance that the kernel
image containing the cryptographic module has not been modified prior to
execution, and that a failure will halt the boot.
Although FIPS 140‑3 does not mandate an HMAC over the kernel image, the
early‑boot HMAC became an accepted evidence pattern for satisfying
system‑integrity expectations (FIPS 199 / SI‑7) alongside a kernel
crypto validation. This is why it is often perceived as “required” for
FIPS submissions, even though it is not normatively required by
FIPS 140‑3 itself.
With the deprecation/removal of AF_ALG for this use case, there is no
longer a supported way to perform an early‑boot, userspace‑driven HMAC
using validated kernel crypto without introducing circular dependencies
(e.g., relying on userspace crypto before crypto self‑tests complete).
As a result, there is no drop‑in replacement for sha512hmac that
preserves all of its historical properties.
This is a new development that challenges a long‑standing assumption:
that system‑integrity evidence and cryptographic‑module self‑integrity
can be cleanly separated while still being demonstrated by a single
early‑boot mechanism. That assumption no longer holds given proposed
kernel interfaces.
A more accurate decomposition (and one that aligns with the intent of
the standards) is to separate integrity enforcement by system phase.
1. Secure Boot (or equivalent platform verification) ensures that a
modified kernel image is not executed at all. This satisfies the
requirement that critical components are not loaded in a modified state
and that integrity failure results in a protective action (boot prevention).
2. IMA (with appraisal and enforcement) ensures that modified
executables, modules, or firmware cannot be loaded or executed once the
kernel is running.
3. Kernel crypto self‑tests continue to satisfy FIPS 140‑3
self‑integrity requirements independently of the above.
Taken together, Secure Boot + IMA provide continuous system‑integrity
enforcement without re‑introducing early‑boot HMACs or AF_ALG
dependencies, while keeping cryptographic‑module self‑integrity
correctly scoped to the kernel crypto subsystem.
The transition away from sha512hmac is therefore not a removal of
integrity enforcement, but a shift from a single, early‑boot mechanism
to a phased integrity model that better reflects the separation of
concerns already present in the standards — even though this separation
was previously masked by the hacky HMAC approach.
This change will require updated documentation and auditor education,
but it reflects the current technical reality and avoids perpetuating an
interface that no longer has a sustainable implementation path.
>
>
>>
>> This isn't sustainable, especially as LLMs have accelerated the rate the
>> vulnerabilities are coming in. The effort that is being put into this
>> thing is vastly disproportional to the few programs that actually use
>> it, and those programs would be better served by userspace code anyway.
>>
>> These issues have been noted in many mailing list discussions already.
>> But until now they haven't been reflected in the documentation or
>> kconfig menu itself, and the vulnerabilities are still coming in.
>>
>> Let's go ahead and document the deprecation.
>>
>> This isn't intended to change anything overnight. After all, most Linux
>> distros won't be able to disable the kconfig options quite yet, mainly
>> because of iwd. But this should create a bit more impetus for these
>> userspace programs to be fixed, and the documentation update should also
>> help prevent more users from appearing.
>>
>> Signed-off-by: Eric Biggers <ebiggers@kernel.org>
>> ---
>>
>> This patch is targeting crypto/master
>>
>> Documentation/crypto/userspace-if.rst | 82 ++++++++++++++++++++-------
>> crypto/Kconfig | 69 ++++++++++++++++------
>> 2 files changed, 113 insertions(+), 38 deletions(-)
>>
>> diff --git a/Documentation/crypto/userspace-if.rst b/Documentation/crypto/userspace-if.rst
>> index 021759198fe7..c39f5c79a5b7 100644
>> --- a/Documentation/crypto/userspace-if.rst
>> +++ b/Documentation/crypto/userspace-if.rst
>> @@ -2,30 +2,72 @@ User Space Interface
>> ====================
>>
>> Introduction
>> ------------
>>
>> -The concepts of the kernel crypto API visible to kernel space is fully
>> -applicable to the user space interface as well. Therefore, the kernel
>> -crypto API high level discussion for the in-kernel use cases applies
>> -here as well.
>> -
>> -The major difference, however, is that user space can only act as a
>> -consumer and never as a provider of a transformation or cipher
>> -algorithm.
>> -
>> -The following covers the user space interface exported by the kernel
>> -crypto API. A working example of this description is libkcapi that can
>> -be obtained from [1]. That library can be used by user space
>> -applications that require cryptographic services from the kernel.
>> -
>> -Some details of the in-kernel kernel crypto API aspects do not apply to
>> -user space, however. This includes the difference between synchronous
>> -and asynchronous invocations. The user space API call is fully
>> -synchronous.
>> -
>> -[1] https://www.chronox.de/libkcapi/index.html
>> +AF_ALG provides unprivileged userspace programs access to arbitrary hash,
>> +symmetric cipher, AEAD, and RNG algorithms that are implemented in kernel-mode
>> +code.
>> +
>> +AF_ALG is insecure and is deprecated. Originally added to the kernel
>> in 2010,
>> +most kernel developers now consider it to be a mistake.
>> +
>> +AF_ALG continues to be supported only for backwards compatibility.
>> On systems
>> +where no programs using AF_ALG remain, the support for it should be
>> disabled by
>> +disabling ``CONFIG_CRYPTO_USER_API_*``.
>> +
>> +Deprecation
>> +-----------
>> +
>> +AF_ALG was originally intended to provide userspace programs access
>> to crypto
>> +accelerators that they wouldn't otherwise have access to.
>> +
>> +However, that capability turned out to not be useful on very many
>> systems. More
>> +significantly, the actual implementation exposes a vastly greater
>> amount of
>> +functionality than that. It actually provides access to all software algorithms.
>> +
>> +This includes arbitrary compositions of different algorithms created
>> via a
>> +complex template system, as well as algorithms that only make sense
>> as internal
>> +implementation details of other algorithms. It also includes full zero-copy
>> +support, which is difficult for the kernel to implement securely.
>> +
>> +Ultimately, these algorithms are just math computations. They use
>> the same
>> +instructions that userspace programs already have access to, just
>> accessed in a
>> +much more convoluted and less efficient way.
>> +
>> +Indeed, userspace code is nearly always what is being used anyway.
>> These same
>> +algorithms are widely implemented in userspace crypto libraries.
>> +
>> +Meanwhile, AF_ALG hasn't been withstanding modern vulnerability
>> discovery tools
>> +such as syzbot and large language models. It receives a steady
>> stream of CVEs.
>> +Some of the examples include:
>> +
>> +- CVE-2026-31677
>> +- CVE-2026-31431 (https://copy.fail)
>> +- CVE-2025-38079
>> +- CVE-2025-37808
>> +- CVE-2024-26824
>> +- CVE-2022-48781
>> +- CVE-2019-8912
>> +- CVE-2018-14619
>> +- CVE-2017-18075
>> +- CVE-2017-17806
>> +- CVE-2017-17805
>> +- CVE-2016-10147
>> +- CVE-2015-8970
>> +- CVE-2015-3331
>> +- CVE-2014-9644
>> +- CVE-2013-7421
>> +- CVE-2011-4081
>> +
>> +It is recommended that, whenever possible, userspace programs be
>> migrated to
>> +userspace crypto code (which again, is what is normally used anyway) and
>> +``CONFIG_CRYPTO_USER_API_*`` be disabled. On systems that use
>> SELinux, SELinux
>> +can also be used to restrict the use of AF_ALG to trusted programs.
>> +
>> +The remainder of this documentation provides the historical
>> documentation for
>> +the deprecated AF_ALG interface.
>>
>> User Space API General Remarks
>> ------------------------------
>>
>> The kernel crypto API is accessible from user space. Currently, the
>> diff --git a/crypto/Kconfig b/crypto/Kconfig
>> index 103d1f58cb7c..6cd1c478d4be 100644
>> --- a/crypto/Kconfig
>> +++ b/crypto/Kconfig
>> @@ -1278,48 +1278,72 @@ config CRYPTO_DF80090A
>> tristate
>> select CRYPTO_AES
>> select CRYPTO_CTR
>>
>> endmenu
>> -menu "Userspace interface"
>> +menu "Userspace interface (deprecated)"
>>
>> config CRYPTO_USER_API
>> tristate
>>
>> config CRYPTO_USER_API_HASH
>> - tristate "Hash algorithms"
>> + tristate "Hash algorithms (deprecated)"
>> depends on NET
>> select CRYPTO_HASH
>> select CRYPTO_USER_API
>> help
>> - Enable the userspace interface for hash algorithms.
>> + Enable the AF_ALG userspace interface for hash algorithms. This
>> + provides unprivileged userspace programs access to arbitrary hash
>> + algorithms implemented in the kernel's privileged execution context.
>>
>> - See Documentation/crypto/userspace-if.rst and
>> - https://www.chronox.de/libkcapi/html/index.html
>> + This interface is deprecated and is supported only for backwards
>> + compatibility. It regularly has vulnerabilities, and the capabilities
>> + it provides are redundant with userspace crypto libraries.
>> +
>> + Enable this only if needed for support for a program that
>> hasn't yet
>> + been converted to userspace crypto, for example iwd.
>> +
>> + See also Documentation/crypto/userspace-if.rst
>>
>> config CRYPTO_USER_API_SKCIPHER
>> - tristate "Symmetric key cipher algorithms"
>> + tristate "Symmetric key cipher algorithms (deprecated)"
>> depends on NET
>> select CRYPTO_SKCIPHER
>> select CRYPTO_USER_API
>> help
>> - Enable the userspace interface for symmetric key cipher algorithms.
>> + Enable the AF_ALG userspace interface for symmetric key algorithms.
>> + This provides unprivileged userspace programs access to arbitrary
>> + symmetric key algorithms implemented in the kernel's privileged
>> + execution context.
>> +
>> + This interface is deprecated and is supported only for backwards
>> + compatibility. It regularly has vulnerabilities, and the capabilities
>> + it provides are redundant with userspace crypto libraries.
>> +
>> + Enable this only if needed for support for a program that
>> hasn't yet
>> + been converted to userspace crypto, for example iwd, or cryptsetup
>> + with certain algorithms.
>>
>> - See Documentation/crypto/userspace-if.rst and
>> - https://www.chronox.de/libkcapi/html/index.html
>> + See also Documentation/crypto/userspace-if.rst
>>
>> config CRYPTO_USER_API_RNG
>> - tristate "RNG (random number generator) algorithms"
>> + tristate "Random number generation algorithms (deprecated)"
>> depends on NET
>> select CRYPTO_RNG
>> select CRYPTO_USER_API
>> help
>> - Enable the userspace interface for RNG (random number generator)
>> - algorithms.
>> + Enable the AF_ALG userspace interface for random number generation
>> + (RNG) algorithms. This provides unprivileged userspace programs
>> + access to arbitrary RNG algorithms implemented in the kernel's
>> + privileged execution context.
>>
>> - See Documentation/crypto/userspace-if.rst and
>> - https://www.chronox.de/libkcapi/html/index.html
>> + This interface is deprecated and is supported only for backwards
>> + compatibility. It regularly has vulnerabilities, and the capabilities
>> + it provides are redundant with userspace crypto libraries as
>> well as
>> + the normal kernel RNG (e.g., /dev/urandom and getrandom(2)).
>> +
>> + See also Documentation/crypto/userspace-if.rst
>>
>> config CRYPTO_USER_API_RNG_CAVP
>> bool "Enable CAVP testing of DRBG"
>> depends on CRYPTO_USER_API_RNG && CRYPTO_DRBG
>> help
>> @@ -1330,20 +1354,29 @@ config CRYPTO_USER_API_RNG_CAVP
>>
>> This should only be enabled for CAVP testing. You should say
>> no unless you know what this is.
>>
>> config CRYPTO_USER_API_AEAD
>> - tristate "AEAD cipher algorithms"
>> + tristate "AEAD cipher algorithms (deprecated)"
>> depends on NET
>> select CRYPTO_AEAD
>> select CRYPTO_SKCIPHER
>> select CRYPTO_USER_API
>> help
>> - Enable the userspace interface for AEAD cipher algorithms.
>> + Enable the AF_ALG userspace interface for authenticated encryption
>> + with associated data (AEAD) algorithms. This provides unprivileged
>> + userspace programs access to arbitrary AEAD algorithms
>> implemented in
>> + the kernel's privileged execution context.
>> +
>> + This interface is deprecated and is supported only for backwards
>> + compatibility. It regularly has vulnerabilities, and the capabilities
>> + it provides are redundant with userspace crypto libraries.
>> +
>> + Enable this only if needed for support for a program that
>> hasn't yet
>> + been converted to userspace crypto, for example iwd.
>>
>> - See Documentation/crypto/userspace-if.rst and
>> - https://www.chronox.de/libkcapi/html/index.html
>> + See also Documentation/crypto/userspace-if.rst
>>
>> config CRYPTO_USER_API_ENABLE_OBSOLETE
>> bool "Obsolete cryptographic algorithms"
>> depends on CRYPTO_USER_API
>> default y
>>
>> base-commit: 57b8e2d666a31fa201432d58f5fe3469a0dd83ba
>> --
>> 2.54.0
>>
>>
>
^ permalink raw reply
* Re: [PATCH v12 04/22] gpu: nova-core: mm: Add support to use PRAMIN windows to write to VRAM
From: Joel Fernandes @ 2026-05-06 14:38 UTC (permalink / raw)
To: Alexandre Courbot
Cc: linux-kernel@vger.kernel.org, Miguel Ojeda, Boqun Feng, Gary Guo,
Bjorn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Dave Airlie, Daniel Almeida,
dri-devel@lists.freedesktop.org, rust-for-linux@vger.kernel.org,
nova-gpu@lists.linux.dev, Nikola Djukic, David Airlie, Boqun Feng,
John Hubbard, Alistair Popple, Timur Tabi, Edwin Peer,
Andrea Righi, Andy Ritger, Zhi Wang, Balbir Singh,
Philipp Stanner, Alexey Ivanov, Eliot Courtney,
joel@joelfernandes.org, linux-doc@vger.kernel.org
In-Reply-To: <DIB6H6HHFW29.816CGQFBOGWC@nvidia.com>
On 5/5/2026 8:47 PM, Alexandre Courbot wrote:
> On Wed May 6, 2026 at 7:59 AM JST, Joel Fernandes wrote:
>>
>>
>>> On May 2, 2026, at 11:42 AM, Alexandre Courbot <acourbot@nvidia.com>
>>
>>>> +
>>>> +/// PRAMIN aperture manager.
>>>> +///
>>>> +/// Call [`Pramin::get_window()`] to acquire exclusive PRAMIN access.
>>>> +#[pin_data]
>>>> +pub(crate) struct Pramin {
>>>> + bar: Arc<Devres<Bar0>>,
>>>
>>> The HRT series [1] will allow you to greatly simplify all this by
>>> storing a `&'a Bar0` directly in this structure. It will most likely
>>> land this cycle, so I think it's a good idea to proactively depend on
>>> them. It should apply cleanly (modulo the Tyr patches IIRC, but you can
>>> skip them if you don't build the driver) on top of `drm-rust-next` - on
>>> top of which this should also be rebased anyway.
>>>
>>> [1] https://lore.kernel.org/all/20260427221155.2144848-1-dakr@kernel.org/
>>>
>>
>> I tried to apply it but had a lot of conflict.
>>
>> Danilo, do you have a tree of these patches somewhere that applies on drm-rust-next?
>
> I have a tree here, it should build fine as long as Tyr is not enabled:
>
> https://github.com/Gnurou/linux/tree/drm-rust-next-hrt
Thank you Alex, I will give this a try.
^ permalink raw reply
* Re: [PATCH RFC v3 9/9] docs: iio: add documentation for ad9910 driver
From: Jonathan Cameron @ 2026-05-06 14:34 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: Rodrigo Alencar via B4 Relay, rodrigo.alencar, linux-iio,
devicetree, linux-kernel, linux-doc, linux-hardening,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan, Kees Cook,
Gustavo A. R. Silva
In-Reply-To: <kwx4kgvagwoaljurnkvuvwggrss2qdjwzszpju6mkdxjfi4n4n@yjsv33ewxkzy>
On Wed, 6 May 2026 11:41:00 +0100
Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> On 26/05/05 06:08PM, Jonathan Cameron wrote:
> > On Mon, 27 Apr 2026 15:35:21 +0100
> > Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> >
> > > On 26/04/27 10:46AM, Jonathan Cameron wrote:
> > > > On Sun, 26 Apr 2026 21:42:15 +0100
> > > > Rodrigo Alencar <455.rodrigo.alencar@gmail.com> wrote:
> > > >
> > > > > On 26/04/26 02:10PM, Jonathan Cameron wrote:
> > > > > > On Fri, 17 Apr 2026 09:17:38 +0100
> > > > > > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
> > > > > >
> > > > > > > From: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > > > > >
> > > > > > > Add documentation for the AD9910 DDS IIO driver, which describes channels,
> > > > > > > DDS modes, attributes and ABI usage examples.
> > > > > > >
> > > > > > > Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
> > > > > >
> > > > > > Hi Rodrigo,
> > > > > >
> > > > > > I think this is getting close to something workable subject to some tweaks
> > > > > > to not make the priority thing visible and use rate of change parameters
> > > > > > so /Sec rather than steps.
> > > > >
> > > > > I am not sure about this one. Getting the value into units per seconds will
> > > > > increase the range of values by a lot, e.g., for the frequency case the step
> > > > > size can range from a few Hz up to the entire supported range (hundreds of
> > > > > MHz), and if you consider that one would often have the sampling_frequency
> > > > > at 250 MHz... an attribute frequency_roc could have an order of 10^17 Hz/s,
> > > > > and I am not sure how practical is that, although it can have a physical meaning,
> > > > > like a "chirp slope".
> > > >
> > > > That scaling is indeed a bit of a pain though it will go in a 64 bit int
> > > > however, seems likely we'll get higher frequency devices one day that will
> > > > limb even faster.
> > > >
> > > > Maybe wait and see if anyone else has input on this.
>
> Maybe we add this _roc ABI for this device and figure out scaling issues on new devices
> later.
That sounds like a way forwards. If we do end up with a more complex ABI long term
we can always add it to this driver if useful - but only once we know what it needs
to look like.
>
> > > > >
> > > > > >
> > > > > > Given this defines the ABI for a whole class of new devices that are
> > > > > > rather complex, one concern is whether whatever we define here is general
> > > > > > enough to be useful.
> > > > > >
> > > > > > Do you have any other DDS in your queue to upstream? Maybe it's worth
> > > > > > sanity checking the ABI against them to see if it is fit for purpose?
> > > > >
> > > > > Not really, still the only DDS. Other DDS of the same family have a similar
> > > > > Digital Ramp Generator with controls over ramp limits, rates and step.
> > > >
> > > > There are two in staging that have been there a very long time...
> > > > ad9832 and ad9834. I haven't looked at how they correspond to this.
> > >
> > > It seems they can benefit of this child channel concept, as they define
> > > multiple frequency and phase configuration for the same physical DAC.
> > > I see the following in the most complicated one:
> > > - out_altvoltage0_frequency0
> > > - out_altvoltage0_frequency1
> > > - out_altvoltage0_frequency_scale
> > > - out_altvoltage0_phase0
> > > - out_altvoltage0_phase1
> > > - out__altvoltage0_phase_scale
> > > - out_altvoltage0_pincontrol_en
> > > - out_altvoltage0_frequencysymbol
> > > - out_altvoltage0_phasesymbol
> > > - out_altvoltage0_out_enable
> > > - out_altvoltage0_out1_enable
> > > - out_altvoltage0_out0_wavetype
> > > - out_altvoltage0_out1_wavetype
> > > - out_altvoltage0_out0_wavetype_available
> > > - out_altvoltage0_out1_wavetype_available
> > >
> > > less complicated, no RAM, no DRG nor parallel port. In terms of common stuff I can see
> > > frequency_scale and pinctrl_en.
> >
> > My main concern here is IIRC these are very much PSK / FSK devices with expectation
> > of symbol being externally controlled. (maybe I remembered that wrong ;)
> > So the symbol relationship would need to map to child channels in some fashion.
>
> With multiple channels, I'd say that is already figured with the standard "enable" ABI,
> As the "symbol" to select is a choice of one of the channels to enable. Like it is
> being done here for the single tone profiles. Being externally controlled is a user
> problem, being its responsability to make sure that expectations on the values are
> aligned during some specific actions. For instance, this very driver, expects the selected profile
> to be trully selected by the profile pins so that RAM loading works. Also, there is another
> weird case, where the profile needs to be selected to make sure a read of the profile register
> works (which is another reason to use cached reads). Apart from those cases, I suppose
> profile pin control can be "detached" from the driver.
If you are doing FSK or PSK they are almost always detached from the driver as driven by
some high speed external source. This chip is probably massive overkill for anyone
wanting to do something so simple. So maybe it's not a thing.
If we need to retrofit it I can see assigning a new _symbol type label to
channels as a way to make it obvious how they are related. Can solve
that when we need to (if we ever do!)
> > > > *sigh* I'm talking my self around to needing ABI to indicate a channel
> > > > is active. The symbol stuff gets us some of the way there (and would
> > > > work for the tones) but doesn't cover the added complexity of RAM etc.
> > > > So 'maybe' new ABI for _isactive or something like that?
> > >
> > > active_params or active_targets? listing out DDS parameters that is currently
> > > active for that mode/channel? like printing "frequency phase amplitude", so
> > > that would mean the mode is driving all of them. That would be a read-only
> > > status.
> >
> > Would have to be simple per channel sysfs attribute that would say
> > is this particular channel actually contributing to the current output.
> > Where it can contribute in multiple ways we'll need separate attributes.
> >
> > Anything else is going to break the rules on one thing per each sysfs
> > attribute.
> > out_altvoltageXXX_phase_active
> > out_altvoltageXXX_freq_active
> > or something like that.
>
> Yes, that could be done. This would be just informative, and I suppose that
> any userspace software that knows how to control the part would not even
> look at those. Probably it would just be used while debugging.
Agreed it is mostly about debugging but I think this thing is complex enough
that it's useful info. I guess we could put it in debugfs for now.
>
> > >
> > > > Perhaps the boundary we put on this is the ABI should be such that
> > > > simple choices such as enabling a single tone, or single RAM mode
> > > > setting are intuitive.
> > > >
> > > > Why do we only have one ram channel? I'd kind of expect the firmware
> > > > to fill all 8 RAM profiles because of that 'external' profile pins
> > > > use case.
> > >
> > > Most of the RAM configuration is now comming from the firmware, so there
> > > is no much info to display/configure in multiple channels. The per-profile
> > > configs require the weird ABI like operating modes and address start/end.
> > > By removing those, I have mostly "global" stuff.
> > If folk are doing profile control from external pins I'd kind of expect to
> > see some description of what they are controlling even if it's read only.
> >
> > Maybe we don't do this now but we should make sure there is space in the ABI
> > by thinking what it might look like.
>
> Right now, I am not seeing anything generic in RAM control appart from a
> "sampling_frequency". A lot of it seems device-specific:
>
> - Destination (freq/phase/amplitude/polar)
> - Internal profile control config (sequenced modes I had before)
> - Per-profile config:
> - Start address
> - End address
> - Address step rate (sampling freq)
> - RAM mode:
> - direct switch
> - ramp-up
> - bidirectional
> - bidirectional continuous
> - ramp-up continuous
> - No-dwell high bit (specific to ramp-up mode)
> - Zero-crossing bit (specific to direct switch mode)
It's the external use of the profile pins that makes me wonder if we
will need this at somepoint. For now it may just be a case of making
sure there is room in the channel numbering etc which I guess there
already is.
>
> ...
>
> > > > > > > +Digital ramp generator (DRG)
> > > > > > > +----------------------------
> > > > > > > +
> > > > > > > +The DRG produces linear frequency, phase or amplitude sweeps using dedicated
> > > > > > > +hardware. It is controlled through three channels: a parent control channel
> > > > > > > +(``digital_ramp_generator``) and two child ramp channels
> > > > > > > +(``digital_ramp_up``, ``digital_ramp_down``). DRG destination is set when
> > > > > > > +ramp attributes are written, i.e. writing to ``frequency`` or ``frequency_step``
> > > > > > > +sets the destination to frequency.
> > > > > > > +
> > > > > > > +Control channel attributes
> > > > > > > +^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > > > > > +
> > > > > > > +.. flat-table::
> > > > > > > + :header-rows: 1
> > > > > > > +
> > > > > > > + * - Attribute
> > > > > > > + - Unit
> > > > > > > + - Description
> > > > > > > +
> > > > > > > + * - ``en``
> > > > > > > + - boolean
> > > > > > > + - Enable/disable the DRG.
> > > > > > > +
> > > > > > > +Ramp channel attributes
> > > > > > > +^^^^^^^^^^^^^^^^^^^^^^^^
> > > > > > > +
> > > > > > > +The ``digital_ramp_up`` and ``digital_ramp_down`` channels share the same
> > > > > > > +attribute set but configure ascending and descending ramp parameters
> > > > > > > +independently:
> > > > > > > +
> > > > > > > +.. flat-table::
> > > > > > > + :header-rows: 1
> > > > > > > +
> > > > > > > + * - Attribute
> > > > > > > + - Unit
> > > > > > > + - Description
> > > > > > > +
> > > > > > > + * - ``en``
> > > > > > > + - boolean
> > > > > > > + - Enable/disable the ramp no-dwell behavior. Enabling both creates a
> > > > > > > + bidirectional continuous ramp (Triangular pattern). Other configurations
> > > > > > > + creates a single-shot ramp at the trasition of the DRCTL pin: ramp-up
> > > > > >
> > > > > > transition
> > > > > >
> > > > > > > + only, ramp-down only or bidirectional with dwell at the limits.
> > > > > >
> > > > > > Feels a little unintuitive to use the generic enable for this.
> > > > > > We might need a specific control for this one.
> > > > >
> > > > > How about dwell_en, but it might not sound that generic. I used "enable" because:
> > > > > - no-dwell high means a ramp-up pattern (only enabling the ramp-up channel)
> > > > > - no-dwell low means a ramp-down pattern (only enabling the ramp-down channel)
> > > > > - both no-dwell is a continuous ramp that goes up and down. (both enabled)
> > > > > The last case is a bit off though, when both are disabled we get the normal mode, which
> > > > > is also a ramps up and down, but dwelling in the limits.
> > > > >
> > > > > > > +
> > > > > > > + * - ``frequency``
> > > > > > > + - Hz
> > > > > > > + - Frequency ramp limit. Range [0, SYSCLK/2).
> > > > > > > +
> > > > > > > + * - ``phase``
> > > > > > > + - rad
> > > > > > > + - Phase ramp limit. Range [0, 2*pi).
> > > >
> > > > Looking at this again, how do we set the DRG mode? E.g. if it effects
> > > > only phase?
> > >
> > > You mean the destination? I removed the destination ABI. so now destination is
> > > set when we write to either frequency, phase or scale properties.
> > > * writing to frequency or frequency_step sets the destination to frequency
> > > * writing to phase or phase_step sets the destination to phase
> > > * writing to scale or scale_step sets the destination to amplitude
> >
> > So it's last write that sets it. We definitely need a way to know which
> > one is active if we get multiple writes. What do the others return if they
> > were set but something else has been set since?
>
> All those values represent the same set of registers with different scaling for
> frequency, phase or scale units. I can get the other values to return -EBUSY
> when reading a destination attribute that was not set.
Given the overlap is non obvious I think that making it clear the channel is not
in use makes sense. Mind you it's a bit of an odd interface to return -EBUSY on
reads but allow writes. I'd like others input on this one.
>
> > >
> > > The DRG mode (dwell mode) is now controlled with the enable bits in the ramp
> > > up/down channels
> > >
> > > > > > > +
> > > > > > > + * - ``scale``
> > > > > > > + - fractional
> > > > > > > + - Amplitude scale ramp limit. Range [0, 1).
> > > > > > > +
> > > > > > > + * - ``sampling_frequency``
> > > > > > > + - Hz
> > > > > > > + - Ramp clock rate: SYSCLK / (4 * divider).
> > > > > > > +
> > > > > > > + * - ``frequency_step``
> > > > > > > + - Hz
> > > > > > > + - Per-tick frequency increment/decrement. Range [0, SYSCLK/2).
> > > > > >
> > > > > > So this was the bit I referred to earlier. Normally we do
> > > > > > rate of change measurements for this stuff rather than what happens on
> > > > > > each tick (based on how we handle things like ROC events)
> > > > > >
> > > > > > So could we make these
> > > > > > ``frequency_roc`` units HZ/Sec
> > > > > > etc? Then from the mix configured would need to work out the optimum
> > > > > > tick to deliver it.
> > > > > >
> > > > > > I suppose it's possible that someone might want a stepped frequency
> > > > > > though which would break this approach? Does anyone actually do that?
> > > > > > If so we'd need to keep the samping_frequency but then control _roc
> > > > > > with that in mind.
> > > > >
> > > > > yeah... frequency steps would make sense when the user controls when to
> > > > > perform the updates, or when it comes from certain events.
> > > >
> > > > You've lost me here. How can they do that? Some external clocking
> > > > or event?
> > >
> > > That would depend on what the user does. This part has this DRHOLD pin
> > > which can freeze the ramp. If the user sets this HIGH and creates pulses
> > > it is able to control the stepping of the RAMP manually. But I assume
> > > that no one would do that... such application is unknown to me.
> >
> > They'd have to sync that with the clock driving the ramp. I guess that's
> > possible - but nasty. I vote we pretend this usecase doesn't exist for now ;)
> >
> > > > > > > +
> > > > > > > +Output shift keying (OSK)
> > > > > > This is a new one on me...
> > > > > > > +-------------------------
> > > > > > > +
> > > > > > > +OSK controls the output amplitude envelope, allowing the output to be ramped
> > > > > > > +on/off rather than switched abruptly.
> > > > > >
> > > > > > > +
> > > > > > > +.. flat-table::
> > > > > > > + :header-rows: 1
> > > > > > > +
> > > > > > > + * - Attribute
> > > > > > > + - Unit
> > > > > > > + - Description
> > > > > > > +
> > > > > > > + * - ``en``
> > > > > > > + - boolean
> > > > > > > + - Enable/disable OSK.
> > > > > > > +
> > > > > > > + * - ``scale``
> > > > > > > + - fractional
> > > > > > > + - Target amplitude for the OSK ramp. 14-bit ASF field. Range [0, 1).
> > > > > > > +
> > > > > > > + * - ``sampling_frequency``
> > > > > > > + - Hz
> > > > > > > + - OSK ramp rate: SYSCLK / (4 * divider).
> > > > > > > +
> > > > > > > + * - ``pinctrl_en``
> > > > > > > + - boolean
> > > > > > > + - Enable manual external pin control. When enabled, the OSK pin directly
> > > > > > > + gates the output on/off instead of using the automatic ramp.
> > > > > >
> > > > > > I wonder if we should split the various OSK modes into different channels given
> > > > > > only some properties apply to each of automatic and manual modes. Also I think
> > > > > > automatic mode is meaningless without pinctrl_en (so that can be replaced
> > > > > > by simply enabling that mode). I have no idea if anyone cares about pin ctrl
> > > > > > with manual mode or not? That one seems even more odd.
> > > > >
> > > > > OSK is either in manual or auto:
> > > > > * In manual mode the OSK pin enables and disables the output based on its level.
> > > > > * In auto, the OSK pin controls the direction the amplitude updates.
> > > > >
> > > > > If we enable RAM mode, and other modes do not target amplitude, the only way to
> > > > > manually configure the amplitude in software (i.e. without using an OSK gpio)
> > > > > is going manual mode (scale_step == 0), disable this pinctrl_en and then set the
> > > > > scale property (ASF register). That is the only reason I added this property.
> > > >
> > > > Ah. Maybe we hide that away and make the amplitude a property of RAM channel?
> > >
> > > And what if a user is in fact willing to use the OSK pin?
> >
> > Sigh. I don't have a good answer, but I don't like the weird 'special' nature
> > of this attribute. I guess sometimes there isn't a good answer to be found.
> >
> > OSK + RAM is odd enough that I'm not that bothered if we have to go weird
> > here.
>
> I will remove this then. I suppose that adding another possible value for
> scale_roc allows us to go around this, i.e., if scale_roc is such that the
> amplitude step is a value equal or greater than the amplitude itself (or even the
> max amplitude value) I could assume that this would be the manual mode with pinctrl
> enable as it would be like an automatic mode with no ramp, but an immediate on-off
> transition. Then scale_roc == 0 means manual mode with pinctrl disabled.
> Will try to develop on this....
I'll reserve judgement on this. Might work out but I'm never that keen on values
that aren't intuitive such as scaling anything by 0.
>
> > >
> > > > It can do this magic under the hood. I don't mind the attributes for OSK changing
> > > > if this trick is in use (they won't be active anyway).
> > >
> > > OSK has the highest priority of all, but it only acts on the amplitude.
> > >
> > > > > > > +
> > > > > > > + * - ``scale_step``
> > > > > > > + - fractional
> > > > > > > + - Automatic OSK amplitude step. Writing non-zero enables automatic OSK
> > > > > > > + and sets the per-tick increment. Writing ``0`` disables it. Rounded to
> > > > > > > + nearest hardware step: 0.000061, 0.000122, 0.000244 or 0.000488.
> > > > > >
> > > > > > Similar thing about rate of change of amplitude fitting better with current ABI
> > > > > > than step does.
> > > > >
> > > > > ok... and this one is still missing the correspondent available attr.
> > > >
> > > > Available is a bit tricky when there is an inverse relationship involved in the maths
> > > > as what do we put the step as. Maybe we should add a note on that to the ABI
> > > > docs. [min step max] where step gives the minimum step that due to non linearity
> > > > may not be applicable between discrete values that may be taken away from that
> > > > minimum granularity base value. If that occurs the driver will round to the
> > > > nearest possible value.
>
^ permalink raw reply
* Re: [PATCH 0/2] cgroup/dmem: introduce a peak file
From: Thadeu Lima de Souza Cascardo @ 2026-05-06 14:18 UTC (permalink / raw)
To: Michal Koutný
Cc: Tejun Heo, Johannes Weiner, Michal Hocko, Roman Gushchin,
Shakeel Butt, Muchun Song, Andrew Morton, Jonathan Corbet,
Shuah Khan, Maarten Lankhorst, Maxime Ripard, Natalie Vock,
Tvrtko Ursulin, cgroups, linux-kernel, linux-mm, linux-doc,
dri-devel, kernel-dev
In-Reply-To: <aftB-cc5EhDXxCGA@localhost.localdomain>
On Wed, May 06, 2026 at 03:53:59PM +0200, Michal Koutný wrote:
> Hello Thadeu.
>
> On Wed, May 06, 2026 at 08:58:23AM -0300, Thadeu Lima de Souza Cascardo <cascardo@igalia.com> wrote:
> > Just like we have memory.peak, introduce a dmem.peak, which uses the
> > page_counter support for that.
> >
> > It can be written to in order to reset the peak, but different from
> > memory.peak, which expects any write, dmem.peak expects the region name to
> > be written to it. That region peak is the one that is reset.
> >
> > That requires ofp_peak to carry a pointer to the pool that was reset.
>
> (It'd be nicer to have generic data in that generic structure, at least
> some void *priv. But see below.)
>
I used void *, at first, but as the only current use is for the pool and as
mixing different uses may lead to misuse, I thought it would be safer to
use the type directly. This has been pointed out before for other members
of cgroup_file_ctx. See [1].
> > Writing a different region name will reset the different region and make
> > the original region peak get back to its non-reset value.
>
> I'm slightly confused by this fds x pool matricity when there's only
> a single slot in cgroup_file_ctx::cgroup_of_peak.
>
> The intended use case is that users should maintain one fd per pool and
> not mix it up?
> This stanza would better fit to cgroup-v2.rst proper than the commit
> message. Or make it simpler and start with non-resettable peak file
> (like memory.peak had started too) and see how it fares. WDYT?
>
That is also due to the limitation that each file descriptor has a single
linked list under cgroup_file_ctx::cgroup_of_peak. To allow for all the
pools to be reset at once, we would need one list per file descriptor.
But, on the other hand, as you pointed out, this leads to the flexibility
of being able to reset only one pool, while leaving the others as is.
Whereas, if one needs to reset all pools, they can use one file descriptor
per region.
I started with a non-resettable peak file, but as memory.peak can be reset,
I added that feature too. If we want to merge a non-resettable support
ealier and need to take longer to discuss how to work on the resettable
support given the above, I can resubmit. But I guess we can see if we can
reach an agreement sonner rather than later.
Thanks.
Cascardo.
>
> Thanks,
> Michal
[1] https://lore.kernel.org/all/CAHk-=wgiYkECT=hZRKj8ZwfBPw2Uz=gpOGBGd4ny0KYhSsjC0w@mail.gmail.com/
^ permalink raw reply
* [PATCH v11 09/11] iio: frequency: adf41513: features on frequency change
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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 | 100 +++++++++++++++++++++++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index 6072ceae9bfb..238bf6e2e55a 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>
@@ -206,6 +207,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;
@@ -264,6 +266,16 @@ struct adf41513_state {
struct adf41513_pll_settings settings;
};
+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",
};
@@ -571,9 +583,82 @@ 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 */
+ st->regs[ADF41513_REG7] |= ADF41513_REG7_CLK_DIV_MODE_MSK;
+}
+
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);
@@ -581,6 +666,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,
@@ -622,6 +709,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);
}
@@ -895,6 +990,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 v11 10/11] docs: iio: add documentation for adf41513 driver
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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 b5bf5f7de9c9..4c326244d496 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
* [PATCH v11 11/11] Documentation: ABI: testing: add common ABI file for iio/frequency
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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..1ce8ae578fd6
--- /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: 6.20
+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 v11 08/11] iio: frequency: adf41513: handle LE synchronization feature
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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 | 35 ++++++++++++++++++++++++++++++++---
1 file changed, 32 insertions(+), 3 deletions(-)
diff --git a/drivers/iio/frequency/adf41513.c b/drivers/iio/frequency/adf41513.c
index 04bb70f8db53..6072ceae9bfb 100644
--- a/drivers/iio/frequency/adf41513.c
+++ b/drivers/iio/frequency/adf41513.c
@@ -215,6 +215,7 @@ struct adf41513_data {
bool phase_detector_polarity;
bool logic_lvl_1v8_en;
+ bool le_sync_en;
};
struct adf41513_pll_settings {
@@ -627,13 +628,27 @@ 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_DIFF);
+ ret = adf41513_sync_config(st, ADF41513_SYNC_DIFF);
+ 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,
@@ -890,6 +905,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;
@@ -897,6 +914,7 @@ static int adf41513_parse_fw(struct adf41513_state *st)
static int adf41513_setup(struct adf41513_state *st)
{
+ int ret;
u32 tmp;
memset(st->regs_hw, 0xFF, sizeof(st->regs_hw));
@@ -930,8 +948,19 @@ static int adf41513_setup(struct adf41513_state *st)
st->data.logic_lvl_1v8_en ? 0 : 1);
/* perform initialization sequence with power-up frequency */
- return adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
- ADF41513_SYNC_ALL);
+ ret = adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
+ 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 void adf41513_power_down(void *data)
--
2.43.0
^ permalink raw reply related
* [PATCH v11 07/11] iio: frequency: adf41513: driver implementation
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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,
reference path configuration and muxout options
- Power management support with suspend/resume callbacks
- Lock detect GPIO monitoring
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
MAINTAINERS | 1 +
drivers/iio/frequency/Kconfig | 10 +
drivers/iio/frequency/Makefile | 1 +
drivers/iio/frequency/adf41513.c | 1068 ++++++++++++++++++++++++++++++++++++++
4 files changed, 1080 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 69646ebe5762..b5bf5f7de9c9 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..04bb70f8db53
--- /dev/null
+++ b/drivers/iio/frequency/adf41513.c
@@ -0,0 +1,1068 @@
+// 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_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_value;
+};
+
+struct adf41513_state {
+ const struct adf41513_chip_info *chip_info;
+ struct spi_device *spi;
+ struct gpio_desc *lock_detect;
+ struct gpio_desc *chip_enable;
+ 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;
+};
+
+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_value = 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]);
+
+ /* 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_value * 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)
+{
+ u16 max_int = st->chip_info->has_prescaler_8_9 ?
+ ADF41513_MAX_INT_8_9 : ADF41513_MAX_INT_4_5;
+ u64 freq_error_uhz;
+ u16 int_value = 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_value < max_int) {
+ int_value++;
+ 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_value >= ADF41513_MIN_INT_8_9 &&
+ int_value <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_value >= ADF41513_MIN_INT_4_5 && int_value <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ result->actual_frequency_uhz = (u64)int_value * result->pfd_frequency_uhz;
+ result->mode = ADF41513_MODE_INTEGER_N;
+ result->int_value = int_value;
+ 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 frac1;
+ u16 int_value;
+ bool lsb_p1_offset = !FIELD_GET(ADF41513_REG5_LSB_P1_MSK, st->regs_hw[ADF41513_REG5]);
+
+ /* LSB_P1 adds a frequency offset of f_pfd/2^26 */
+ if (lsb_p1_offset)
+ target_frequency_uhz -= resolution_uhz >> 1;
+
+ int_value = div64_u64_rem(target_frequency_uhz, result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_value >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_value <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_value >= ADF41513_MIN_INT_FRAC_4_5 && int_value <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ /* 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 = resolution_uhz - freq_error_uhz;
+ }
+
+ if (freq_error_uhz > st->data.freq_resolution_uhz)
+ return -ERANGE;
+
+ /* integer part */
+ result->actual_frequency_uhz = (u64)int_value * 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_value = int_value;
+ 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;
+ u16 int_value = div64_u64_rem(result->target_frequency_uhz,
+ result->pfd_frequency_uhz,
+ &freq_error_uhz);
+
+ if (st->chip_info->has_prescaler_8_9 && int_value >= ADF41513_MIN_INT_FRAC_8_9 &&
+ int_value <= ADF41513_MAX_INT_8_9)
+ result->prescaler = 1;
+ else if (int_value >= ADF41513_MIN_INT_FRAC_4_5 && int_value <= ADF41513_MAX_INT_4_5)
+ result->prescaler = 0;
+ else
+ return -ERANGE;
+
+ /* calculate required mod2 based on target resolution / 2 */
+ mod2 = DIV64_U64_ROUND_CLOSEST(result->pfd_frequency_uhz << 1,
+ st->data.freq_resolution_uhz * ADF41513_FIXED_MODULUS);
+ /* ensure mod2 is at least 2 for meaningful operation */
+ mod2 = clamp(mod2, 2, ADF41513_MAX_MOD2);
+
+ /* calculate frac1 and frac2 */
+ frac1 = mul_u64_u64_div_u64(freq_error_uhz, ADF41513_FIXED_MODULUS,
+ result->pfd_frequency_uhz);
+ freq_error_uhz -= mul_u64_u32_div(result->pfd_frequency_uhz, frac1,
+ ADF41513_FIXED_MODULUS);
+ frac2 = mul_u64_u64_div_u64(freq_error_uhz, mod2 * ADF41513_FIXED_MODULUS,
+ result->pfd_frequency_uhz);
+
+ /* integer part */
+ result->actual_frequency_uhz = (u64)int_value * result->pfd_frequency_uhz;
+ /* fractional part */
+ result->actual_frequency_uhz += mul_u64_u64_div_u64(mod2 * frac1 + frac2,
+ result->pfd_frequency_uhz,
+ mod2 * ADF41513_FIXED_MODULUS);
+ result->mode = ADF41513_MODE_VARIABLE_MODULUS;
+ result->int_value = int_value;
+ result->frac1 = frac1;
+ result->frac2 = frac2;
+ result->mod2 = mod2;
+
+ return 0;
+}
+
+static int adf41513_calc_pll_settings(struct adf41513_state *st,
+ struct adf41513_pll_settings *result,
+ u64 rf_out_uhz)
+{
+ u64 max_rf_freq_uhz = st->chip_info->max_rf_freq_hz * MICRO;
+ u64 min_rf_freq_uhz = ADF41513_MIN_RF_FREQ_HZ * MICRO;
+ u64 pfd_freq_limit_uhz;
+ int ret;
+
+ if (rf_out_uhz < min_rf_freq_uhz || rf_out_uhz > max_rf_freq_uhz) {
+ dev_err(&st->spi->dev, "RF frequency %llu uHz out of range [%llu, %llu] uHz\n",
+ rf_out_uhz, min_rf_freq_uhz, max_rf_freq_uhz);
+ return -EINVAL;
+ }
+
+ result->target_frequency_uhz = rf_out_uhz;
+
+ /* try integer-N first (best phase noise performance) */
+ pfd_freq_limit_uhz = min(div_u64(rf_out_uhz, ADF41513_MIN_INT_4_5),
+ ADF41513_MAX_PFD_FREQ_INT_N_UHZ);
+ ret = adf41513_calc_pfd_frequency(st, result, pfd_freq_limit_uhz);
+ if (ret)
+ 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_value, 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_value);
+ 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);
+ 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_DIFF);
+}
+
+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_array_populate(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;
+
+ 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;
+
+ 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);
+ return adf41513_sync_config(st, ADF41513_SYNC_REG0);
+ 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 > ADF41513_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) {
+ tmp = DIV_ROUND_CLOSEST(cp_current * cp_resistance, MILLI); /* convert to mV */
+ 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 int adf41513_setup(struct adf41513_state *st)
+{
+ u32 tmp;
+
+ 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 */
+ return adf41513_set_frequency(st, st->data.power_up_frequency_hz * MICRO,
+ ADF41513_SYNC_ALL);
+}
+
+static void adf41513_power_down(void *data)
+{
+ struct adf41513_state *st = data;
+
+ adf41513_suspend(st);
+ gpiod_set_value_cansleep(st->chip_enable, 0);
+}
+
+static int adf41513_pm_suspend(struct device *dev)
+{
+ return adf41513_suspend(dev_get_drvdata(dev));
+}
+
+static int adf41513_pm_resume(struct device *dev)
+{
+ return adf41513_resume(dev_get_drvdata(dev));
+}
+
+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 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->chip_enable = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(st->chip_enable))
+ return dev_err_probe(dev, PTR_ERR(st->chip_enable),
+ "fail to request chip enable GPIO\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");
+
+ 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(st);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "failed to setup device\n");
+
+ ret = devm_add_action_or_reset(dev, adf41513_power_down, st);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add power down action\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 v11 05/11] iio: core: add decimal value formatting into 64-bit value
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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 | 28 +++++++++++++++++++++++++
2 files changed, 65 insertions(+), 9 deletions(-)
diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-core.c
index bd6f4f9f4533..24bc1577fdac 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,25 @@ 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_array(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:
+ {
+ s64 frac;
+ unsigned int scale = type - IIO_VAL_DECIMAL64_BASE;
+
+ tmp2 = div64_s64_rem(iio_val_s64_from_array(vals),
+ int_pow(10, scale), &frac);
+ if (tmp2 == 0 && frac < 0)
+ return sysfs_emit_at(buf, offset, "-0.%0*lld", scale,
+ abs(frac));
+ else
+ return sysfs_emit_at(buf, offset, "%lld.%0*lld", tmp2,
+ scale, abs(frac));
+ }
default:
return 0;
}
@@ -977,7 +994,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, ret, fract_mult = 100000, dec_scale = 0;
int integer, fract = 0;
long long integer64;
bool is_char = false;
@@ -988,9 +1005,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 +1025,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 +1046,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..bc0e6f66bd9c 100644
--- a/include/linux/iio/types.h
+++ b/include/linux/iio/types.h
@@ -7,6 +7,7 @@
#ifndef _IIO_TYPES_H_
#define _IIO_TYPES_H_
+#include <linux/wordpart.h>
#include <uapi/linux/iio/types.h>
enum iio_event_info {
@@ -34,6 +35,33 @@ enum iio_event_info {
#define IIO_VAL_FRACTIONAL_LOG2 11
#define IIO_VAL_CHAR 12
+#define IIO_VAL_DECIMAL64_BASE 100
+#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(int val0, int val1)
+{
+ return (s64)(((u64)val1 << 32) | (u32)val0);
+}
+
+static inline s64 iio_val_s64_from_array(const int *vals)
+{
+ return iio_val_s64_compose(vals[0], vals[1]);
+}
+
+static inline void iio_val_s64_decompose(s64 dec64, int *val0, int *val1)
+{
+ *val0 = lower_32_bits(dec64);
+ *val1 = upper_32_bits(dec64);
+}
+
+static inline void iio_val_s64_array_populate(s64 dec64, int *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 v11 06/11] iio: test: iio-test-format: add test case for decimal format
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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..a2a9b4360c92 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_array_populate(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_array_populate(-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_array_populate(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_array_populate(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_array_populate(-((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_array_populate(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_array_populate(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_array_populate(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_array_populate(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_array_populate(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_array_populate(-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_array_populate(-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_array_populate(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_array_populate(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_array_populate(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_array_populate(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_array_populate(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 v11 04/11] lib: math: div64: add div64_s64_rem()
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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).
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 v11 01/11] dt-bindings: iio: frequency: add adf41513
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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 | 215 +++++++++++++++++++++
MAINTAINERS | 7 +
2 files changed, 222 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..2d09cb94b6ff
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,adf41513.yaml
@@ -0,0 +1,215 @@
+# 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
+
+$ref: /schemas/spi/spi-peripheral-props.yaml#
+
+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:
+ 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
+
+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 bc42e1cacd6b..69646ebe5762 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 v11 03/11] lib: test-kstrtox: tests for kstrtodec64() and kstrtoudec64()
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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.
Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
lib/test-kstrtox.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 156 insertions(+)
diff --git a/lib/test-kstrtox.c b/lib/test-kstrtox.c
index ee87fef66cb5..ee9b535bcf1c 100644
--- a/lib/test-kstrtox.c
+++ b/lib/test-kstrtox.c
@@ -703,6 +703,156 @@ 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 (no decimal point) */
+ {"0", 1, 0},
+ {"42", 3, 42000},
+ {"1", 1, 10},
+ /* fractional only (leading dot) */
+ {".5", 1, 5},
+ {".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},
+ /* 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, 10},
+ {"1.00", 2, 100},
+ {"1.000", 3, 1000},
+ {"1.000000", 6, 1000000},
+ {"1.000000000", 9, 1000000000ULL},
+ /* large values spanning u64 range */
+ {"9223372036.854775807", 9, 9223372036854775807ULL},
+ {"18446744073709.551615", 6, 18446744073709551615ULL},
+ };
+ 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},
+ /* invalid scale */
+ {"1.0", 21},
+ /* minus sign (unsigned) */
+ {"-1.5", 1},
+ {"-0.5", 1},
+ /* no digits after dot */
+ {"1.", 3},
+ {".", 3},
+ /* no digits at all */
+ {"+", 3},
+ /* non-digit characters */
+ {"abc", 3},
+ {"1.2x", 3},
+ /* leading/trailing space */
+ {" 1.5", 1},
+ {"1.5 ", 1},
+ /* overflow */
+ {"18446744073710.551615", 6},
+ {"99999999999999999999", 1},
+ };
+ 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},
+ };
+ 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},
+ /* invalid scale */
+ {"1.0", 21},
+ /* no digits after dot */
+ {"1.", 3},
+ {".", 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},
+ };
+ TEST_FAIL(kstrtodec64, s64, "%lld", test_dec64_fail);
+}
+
static int __init test_kstrtox_init(void)
{
test_kstrtoull_ok();
@@ -729,6 +879,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 v11 00/11] ADF41513/ADF41510 PLL frequency synthesizers
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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 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 (11):
dt-bindings: iio: frequency: add adf41513
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 | 215 ++++
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 | 1197 ++++++++++++++++++++
drivers/iio/industrialio-core.c | 46 +-
drivers/iio/test/iio-test-format.c | 97 +-
include/linux/iio/types.h | 28 +
include/linux/kstrtox.h | 3 +
include/linux/math64.h | 18 +
lib/kstrtox.c | 105 ++
lib/math/div64.c | 15 +
lib/test-kstrtox.c | 156 +++
17 files changed, 2080 insertions(+), 41 deletions(-)
---
base-commit: 39b80c5c9830d12d2d6531059001301c4265322a
change-id: 20251110-adf41513-iio-driver-aaca8a7f808e
Best regards,
--
Rodrigo Alencar <rodrigo.alencar@analog.com>
^ permalink raw reply
* [PATCH v11 02/11] lib: kstrtox: add kstrtoudec64() and kstrtodec64()
From: Rodrigo Alencar via B4 Relay @ 2026-05-06 14:08 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: <20260506-adf41513-iio-driver-v11-0-2b7e99cfe8f2@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 | 105 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 108 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 97be2a39f537..c7625ba4ac88 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>
@@ -392,6 +393,110 @@ int kstrtobool(const char *s, bool *res)
}
EXPORT_SYMBOL(kstrtobool);
+static int _kstrtoudec64(const char *s, unsigned int scale, u64 *res)
+{
+ u64 _res = 0, _frac = 0;
+ unsigned int rv;
+
+ if (scale > 19) /* log10(2^64) = 19.26 */
+ return -EINVAL;
+
+ if (*s != '.') {
+ rv = _parse_integer(s, 10, &_res);
+ if (rv & KSTRTOX_OVERFLOW)
+ return -ERANGE;
+ if (rv == 0)
+ return -EINVAL;
+ s += rv;
+ }
+
+ if (*s == '.' && scale) {
+ s++; /* skip decimal point */
+ rv = _parse_integer_limit(s, 10, &_frac, scale);
+ if (rv & KSTRTOX_OVERFLOW)
+ return -ERANGE;
+ if (rv == 0)
+ return -EINVAL;
+ s += rv;
+ if (rv < scale)
+ _frac *= int_pow(10, scale - rv);
+ while (isdigit(*s)) /* truncate */
+ s++;
+ }
+
+ if (*s == '\n')
+ s++;
+ if (*s)
+ return -EINVAL;
+
+ if (check_mul_overflow(_res, int_pow(10, scale), &_res) ||
+ check_add_overflow(_res, _frac, &_res))
+ return -ERANGE;
+
+ *res = _res;
+ return 0;
+}
+
+/**
+ * kstrtoudec64 - convert a string to an unsigned 64-bit 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.
+ *
+ * Returns 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 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.
+ *
+ * Returns 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
* Re: [PATCH v3 0/2] platform/x86/intel-uncore-freq: Expose instance ID in the sysfs
From: Maciej Wieczor-Retman @ 2026-05-06 14:08 UTC (permalink / raw)
To: Ilpo Järvinen
Cc: skhan, hansg, corbet, srinivas.pandruvada, Maciej Wieczor-Retman,
linux-kernel, platform-driver-x86, linux-doc
In-Reply-To: <177807566314.8094.18400142922022766172.b4-ty@linux.intel.com>
On 2026-05-06 at 16:54:23 +0300, Ilpo Järvinen wrote:
>On Wed, 08 Apr 2026 16:27:40 +0000, Maciej Wieczor-Retman wrote:
>
>> --- Motivation
>>
>> This patchset is about exporting instance ID, a value used to uniquely
>> identify MMIO blocks in TPMI devices. Userspace tools like "pepc" [1]
>> can use it for direct MMIO reads or writes.
>>
>> Currently exported information allows doing this on non-partitioned
>> systems, but partitioned systems require additional steps to map MMIO
>> blocks.
>>
>> [...]
>
>
>Thank you for your contribution, it has been applied to my local
>review-ilpo-next branch. Note it will show up in the public
>platform-drivers-x86/review-ilpo-next branch only once I've pushed my
>local branch there, which might take a while.
>
>The list of commits applied:
>[1/2] platform/x86/intel-uncore-freq: Rename instance_id
> commit: d8e484a452ca195b7c099373f3c7901bd405b623
>[2/2] platform/x86/intel-uncore-freq: Expose instance ID in the sysfs
> commit: 6cf1c1e9f21ba2e44e05e691d5241290c7d6c41a
>
>--
> i.
>
Thanks! :)
--
Kind regards
Maciej Wieczór-Retman
^ permalink raw reply
* Re: [PATCH v9 00/22] Enable FRED with KVM VMX
From: Maciej Wieczor-Retman @ 2026-05-06 14:05 UTC (permalink / raw)
To: Andrew Cooper
Cc: Xin Li, David Woodhouse, linux-kernel, kvm, linux-doc,
Saenz Julienne, Nicolas, pbonzini, seanjc, corbet, tglx, mingo,
bp, dave.hansen, x86, hpa, luto, peterz, chao.gao, hch,
sohil.mehta
In-Reply-To: <0cf63e0d-8983-40e8-bf0c-938affb73744@citrix.com>
On 2026-05-05 at 21:27:12 +0100, Andrew Cooper wrote:
>On 05/05/2026 9:20 pm, Maciej Wieczor-Retman wrote:
>> On 2026-05-05 at 19:30:21 +0100, Andrew Cooper wrote:
>>> On 05/05/2026 7:04 pm, Maciej Wieczor-Retman wrote:
>>>
>>>> I think you'd need to add another check in vmx_inject_exception() to handle that
>>>> DB_VECTOR too. Simply changing the event type if the vector is of DB_VECTOR type
>>>> fixes that problem but then the selftest fails in other places (assert
>>>> fred_handler_called and saved rip vs expected_rip). I didn't yet have the time
>>>> to figure out what could be wrong there, maybe you would have more of an idea :)
>>> #DB is intercepted to mitigate CVE-2015-8104 (systemwide DoS). But, to
>>> start with, check that the test passes when #DB is not intercepted.
>>> That's the basecase for architectural behaviour.
>> I take it you mean dropping the ICEBP selftest test case and just checking INT3
>> and INT $0x20? In that case the other two tests pass after a minor change -
>> namely in guest_code() the expected_rip needs to be volatile as well. Otherwise
>> there is a RIP mismatch.
>>
>> Or did you mean I should check something else?
>
>The selftest is correct AIUI. You should be able to prove this by
>disabling #DB interception, and observing the test to pass.
Okay, I commented out '(1u << DB_VECTOR)' from vmx_update_exception_bitmap(),
and booting with that and applying the two changes to the selftest that I
mentioned [1] it passes as you said.
>Then, there's a logic bug to fix to cause the test to pass even when #DB
>interception is active (which is necessary due to CVE-2015-8104).
I'll dig around the #DB thing, maybe I'll figure out what's wrong there.
>
>~Andrew
[1] Changes being making fred_handler() non-static and making expected_rip
volatile.
--
Kind regards
Maciej Wieczór-Retman
^ permalink raw reply
* Re: [PATCH v3 0/2] platform/x86/intel-uncore-freq: Expose instance ID in the sysfs
From: Ilpo Järvinen @ 2026-05-06 13:54 UTC (permalink / raw)
To: skhan, hansg, corbet, srinivas.pandruvada, Maciej Wieczor-Retman
Cc: linux-kernel, platform-driver-x86, linux-doc
In-Reply-To: <cover.1775665057.git.m.wieczorretman@pm.me>
On Wed, 08 Apr 2026 16:27:40 +0000, Maciej Wieczor-Retman wrote:
> --- Motivation
>
> This patchset is about exporting instance ID, a value used to uniquely
> identify MMIO blocks in TPMI devices. Userspace tools like "pepc" [1]
> can use it for direct MMIO reads or writes.
>
> Currently exported information allows doing this on non-partitioned
> systems, but partitioned systems require additional steps to map MMIO
> blocks.
>
> [...]
Thank you for your contribution, it has been applied to my local
review-ilpo-next branch. Note it will show up in the public
platform-drivers-x86/review-ilpo-next branch only once I've pushed my
local branch there, which might take a while.
The list of commits applied:
[1/2] platform/x86/intel-uncore-freq: Rename instance_id
commit: d8e484a452ca195b7c099373f3c7901bd405b623
[2/2] platform/x86/intel-uncore-freq: Expose instance ID in the sysfs
commit: 6cf1c1e9f21ba2e44e05e691d5241290c7d6c41a
--
i.
^ permalink raw reply
* Re: [PATCH 0/2] cgroup/dmem: introduce a peak file
From: Michal Koutný @ 2026-05-06 13:53 UTC (permalink / raw)
To: Thadeu Lima de Souza Cascardo
Cc: Tejun Heo, Johannes Weiner, Michal Hocko, Roman Gushchin,
Shakeel Butt, Muchun Song, Andrew Morton, Jonathan Corbet,
Shuah Khan, Maarten Lankhorst, Maxime Ripard, Natalie Vock,
Tvrtko Ursulin, cgroups, linux-kernel, linux-mm, linux-doc,
dri-devel, kernel-dev
In-Reply-To: <20260506-dmem_peak-v1-0-8d803eb3449c@igalia.com>
[-- Attachment #1: Type: text/plain, Size: 1205 bytes --]
Hello Thadeu.
On Wed, May 06, 2026 at 08:58:23AM -0300, Thadeu Lima de Souza Cascardo <cascardo@igalia.com> wrote:
> Just like we have memory.peak, introduce a dmem.peak, which uses the
> page_counter support for that.
>
> It can be written to in order to reset the peak, but different from
> memory.peak, which expects any write, dmem.peak expects the region name to
> be written to it. That region peak is the one that is reset.
>
> That requires ofp_peak to carry a pointer to the pool that was reset.
(It'd be nicer to have generic data in that generic structure, at least
some void *priv. But see below.)
> Writing a different region name will reset the different region and make
> the original region peak get back to its non-reset value.
I'm slightly confused by this fds x pool matricity when there's only
a single slot in cgroup_file_ctx::cgroup_of_peak.
The intended use case is that users should maintain one fd per pool and
not mix it up?
This stanza would better fit to cgroup-v2.rst proper than the commit
message. Or make it simpler and start with non-resettable peak file
(like memory.peak had started too) and see how it fares. WDYT?
Thanks,
Michal
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 265 bytes --]
^ 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