Linux Documentation
 help / color / mirror / Atom feed
* Re: [RFC, PATCH 00/12] userfaultfd: working set tracking for VM guest memory
From: Kiryl Shutsemau @ 2026-04-17 12:26 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Andrew Morton, Peter Xu, Lorenzo Stoakes, Mike Rapoport,
	Suren Baghdasaryan, Vlastimil Babka, Liam R . Howlett, Zi Yan,
	Jonathan Corbet, Shuah Khan, Sean Christopherson, Paolo Bonzini,
	linux-mm, linux-kernel, linux-doc, linux-kselftest, kvm
In-Reply-To: <4c635703-3d8d-4cfa-bb98-7f6f5fcbe547@kernel.org>

On Fri, Apr 17, 2026 at 01:43:36PM +0200, David Hildenbrand (Arm) wrote:
> On 4/16/26 22:25, Kiryl Shutsemau wrote:
> > On Thu, Apr 16, 2026 at 08:32:19PM +0200, David Hildenbrand (Arm) wrote:
> >> On 4/16/26 15:49, Kiryl Shutsemau wrote:
> >>>
> >>> Here is an updated version:
> >>>
> >>> https://git.kernel.org/pub/scm/linux/kernel/git/kas/linux.git/log/?h=uffd/rfc-v2
> >>>
> >>> will post after -rc1 is tagged.
> >>>
> >>> I like it more. It got substantially cleaner.
> >>
> >> I don't have time to look into the details just yet, but my thinking was
> >> that
> >>
> >> a) It would avoid the zap+refault
> > 
> > Yep.
> > 
> >> b) We could reuse the uffd-wp PTE bit + marker to indicate/remember the
> >>    protection, making it co-exist with NUMA hinting naturally.
> >>
> >> b) obviously means that we cannot use uffd-wp and uffd-rwp at the same
> >> time in the same uffd area. I guess that should be acceptable for the
> >> use cases we you should have in mind?
> > 
> > I took a different path: I still use PROT_NONE PTEs, so it cannot
> > co-exist with NUMA balancing [fully], but WP + RWP should be fine. I
> > need to add a test for this.
> > 
> > I didn't give up on NUMA balancing completely. task_numa_fault() is
> > called on RWP fault. So it should help scheduler decisions somewhat.
> > 
> > I think an RWP user might want to use WP too.
> > 
> > Do you see this trade-off as reasonable?
> 
> One reason why the PTE bit was added for the WP case was to distinguish
> it from other write faults.
> 
> I assume without a dedicated PTE bit your design will always suffer from
> false positive notifications.
> 
> Leaving NUMA-balancing aside, a simple
> mprotect(PROT_NONE)+mprotect(PROT_READ) would already be problematic to
> distinguish both cases.

Hm. I didn't consider this case (miss some uffd lore). Will rework to
reuse existing PTE bit.

Thanks for the feedback!

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

^ permalink raw reply

* Re: [PATCH v2 07/28] fsnotify: add FSNOTIFY_EVENT_RENAME data type
From: Jan Kara @ 2026-04-17 11:56 UTC (permalink / raw)
  To: Jeff Layton
  Cc: Alexander Viro, Christian Brauner, Jan Kara, Chuck Lever,
	Alexander Aring, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, NeilBrown,
	Olga Kornievskaia, Dai Ngo, Tom Talpey, Trond Myklebust,
	Anna Schumaker, Amir Goldstein, Calum Mackay, linux-fsdevel,
	linux-kernel, linux-trace-kernel, linux-doc, linux-nfs
In-Reply-To: <20260416-dir-deleg-v2-7-851426a550f6@kernel.org>

On Thu 16-04-26 10:35:08, Jeff Layton wrote:
> Add a new fsnotify_rename_data struct and FSNOTIFY_EVENT_RENAME data
> type that carries both the moved dentry and the inode that was
> overwritten by the rename (if any).
> 
> Update fsnotify_data_inode(), fsnotify_data_dentry(), and
> fsnotify_data_sb() to handle the new type, and add a new
> fsnotify_data_rename_target() helper for extracting the overwritten
> target inode.
> 
> Update fsnotify_move() to use the new data type for FS_RENAME and
> FS_MOVED_TO events, passing the overwritten target inode through the
> event data. FS_MOVED_FROM is unchanged since the source directory
> doesn't need overwrite information.
> 
> This is done so that fsnotify consumers like nfsd can atomically
> observe the overwritten file when a rename replaces an existing entry,
> without needing a separate FS_DELETE event.
> 
> Assisted-by: Claude (Anthropic Claude Code)
> Signed-off-by: Jeff Layton <jlayton@kernel.org>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  include/linux/fsnotify.h         |  8 ++++++--
>  include/linux/fsnotify_backend.h | 20 ++++++++++++++++++++
>  2 files changed, 26 insertions(+), 2 deletions(-)
> 
> diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h
> index 079c18bcdbde..bda798bc67bc 100644
> --- a/include/linux/fsnotify.h
> +++ b/include/linux/fsnotify.h
> @@ -257,6 +257,10 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
>  	__u32 new_dir_mask = FS_MOVED_TO;
>  	__u32 rename_mask = FS_RENAME;
>  	const struct qstr *new_name = &moved->d_name;
> +	struct fsnotify_rename_data rd = {
> +		.moved = moved,
> +		.target = target,
> +	};
>  
>  	if (isdir) {
>  		old_dir_mask |= FS_ISDIR;
> @@ -265,12 +269,12 @@ static inline void fsnotify_move(struct inode *old_dir, struct inode *new_dir,
>  	}
>  
>  	/* Event with information about both old and new parent+name */
> -	fsnotify_name(rename_mask, moved, FSNOTIFY_EVENT_DENTRY,
> +	fsnotify_name(rename_mask, &rd, FSNOTIFY_EVENT_RENAME,
>  		      old_dir, old_name, 0);
>  
>  	fsnotify_name(old_dir_mask, source, FSNOTIFY_EVENT_INODE,
>  		      old_dir, old_name, fs_cookie);
> -	fsnotify_name(new_dir_mask, source, FSNOTIFY_EVENT_INODE,
> +	fsnotify_name(new_dir_mask, &rd, FSNOTIFY_EVENT_RENAME,
>  		      new_dir, new_name, fs_cookie);
>  
>  	if (target)
> diff --git a/include/linux/fsnotify_backend.h b/include/linux/fsnotify_backend.h
> index 66e185bd1b1b..f8c8fb7f34ae 100644
> --- a/include/linux/fsnotify_backend.h
> +++ b/include/linux/fsnotify_backend.h
> @@ -311,6 +311,7 @@ enum fsnotify_data_type {
>  	FSNOTIFY_EVENT_DENTRY,
>  	FSNOTIFY_EVENT_MNT,
>  	FSNOTIFY_EVENT_ERROR,
> +	FSNOTIFY_EVENT_RENAME,
>  };
>  
>  struct fs_error_report {
> @@ -335,6 +336,11 @@ struct fsnotify_mnt {
>  	u64 mnt_id;
>  };
>  
> +struct fsnotify_rename_data {
> +	struct dentry *moved;	/* the dentry that was renamed */
> +	struct inode *target;	/* inode overwritten by rename, or NULL */
> +};
> +
>  static inline struct inode *fsnotify_data_inode(const void *data, int data_type)
>  {
>  	switch (data_type) {
> @@ -348,6 +354,8 @@ static inline struct inode *fsnotify_data_inode(const void *data, int data_type)
>  		return d_inode(file_range_path(data)->dentry);
>  	case FSNOTIFY_EVENT_ERROR:
>  		return ((struct fs_error_report *)data)->inode;
> +	case FSNOTIFY_EVENT_RENAME:
> +		return d_inode(((const struct fsnotify_rename_data *)data)->moved);
>  	default:
>  		return NULL;
>  	}
> @@ -363,6 +371,8 @@ static inline struct dentry *fsnotify_data_dentry(const void *data, int data_typ
>  		return ((const struct path *)data)->dentry;
>  	case FSNOTIFY_EVENT_FILE_RANGE:
>  		return file_range_path(data)->dentry;
> +	case FSNOTIFY_EVENT_RENAME:
> +		return ((struct fsnotify_rename_data *)data)->moved;
>  	default:
>  		return NULL;
>  	}
> @@ -395,6 +405,8 @@ static inline struct super_block *fsnotify_data_sb(const void *data,
>  		return file_range_path(data)->dentry->d_sb;
>  	case FSNOTIFY_EVENT_ERROR:
>  		return ((struct fs_error_report *) data)->sb;
> +	case FSNOTIFY_EVENT_RENAME:
> +		return ((const struct fsnotify_rename_data *)data)->moved->d_sb;
>  	default:
>  		return NULL;
>  	}
> @@ -430,6 +442,14 @@ static inline struct fs_error_report *fsnotify_data_error_report(
>  	}
>  }
>  
> +static inline struct inode *fsnotify_data_rename_target(const void *data,
> +							int data_type)
> +{
> +	if (data_type == FSNOTIFY_EVENT_RENAME)
> +		return ((const struct fsnotify_rename_data *)data)->target;
> +	return NULL;
> +}
> +
>  static inline const struct file_range *fsnotify_data_file_range(
>  							const void *data,
>  							int data_type)
> 
> -- 
> 2.53.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [PATCH v2 05/28] fsnotify: new tracepoint in fsnotify()
From: Jan Kara @ 2026-04-17 11:49 UTC (permalink / raw)
  To: Jeff Layton
  Cc: Alexander Viro, Christian Brauner, Jan Kara, Chuck Lever,
	Alexander Aring, Steven Rostedt, Masami Hiramatsu,
	Mathieu Desnoyers, Jonathan Corbet, Shuah Khan, NeilBrown,
	Olga Kornievskaia, Dai Ngo, Tom Talpey, Trond Myklebust,
	Anna Schumaker, Amir Goldstein, Calum Mackay, linux-fsdevel,
	linux-kernel, linux-trace-kernel, linux-doc, linux-nfs
In-Reply-To: <20260416-dir-deleg-v2-5-851426a550f6@kernel.org>

On Thu 16-04-26 10:35:06, Jeff Layton wrote:
> Add a tracepoint so we can see exactly how this is being called.
> 
> Signed-off-by: Jeff Layton <jlayton@kernel.org>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
>  fs/notify/fsnotify.c            |  5 ++++
>  include/trace/events/fsnotify.h | 51 +++++++++++++++++++++++++++++++++++++++++
>  include/trace/misc/fsnotify.h   | 35 ++++++++++++++++++++++++++++
>  3 files changed, 91 insertions(+)
> 
> diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c
> index 9995de1710e5..5448738635f6 100644
> --- a/fs/notify/fsnotify.c
> +++ b/fs/notify/fsnotify.c
> @@ -14,6 +14,9 @@
>  #include <linux/fsnotify_backend.h>
>  #include "fsnotify.h"
>  
> +#define CREATE_TRACE_POINTS
> +#include <trace/events/fsnotify.h>
> +
>  /*
>   * Clear all of the marks on an inode when it is being evicted from core
>   */
> @@ -504,6 +507,8 @@ int fsnotify(__u32 mask, const void *data, int data_type, struct inode *dir,
>  	int ret = 0;
>  	__u32 test_mask, marks_mask = 0;
>  
> +	trace_fsnotify(mask, data, data_type, dir, file_name, inode, cookie);
> +
>  	if (path)
>  		mnt = real_mount(path->mnt);
>  
> diff --git a/include/trace/events/fsnotify.h b/include/trace/events/fsnotify.h
> new file mode 100644
> index 000000000000..341bbd57a39b
> --- /dev/null
> +++ b/include/trace/events/fsnotify.h
> @@ -0,0 +1,51 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +#undef TRACE_SYSTEM
> +#define TRACE_SYSTEM fsnotify
> +
> +#if !defined(_TRACE_FSNOTIFY_H) || defined(TRACE_HEADER_MULTI_READ)
> +#define _TRACE_FSNOTIFY_H
> +
> +#include <linux/tracepoint.h>
> +
> +#include <trace/misc/fsnotify.h>
> +
> +TRACE_EVENT(fsnotify,
> +	TP_PROTO(__u32 mask, const void *data, int data_type,
> +		 struct inode *dir, const struct qstr *file_name,
> +		 struct inode *inode, u32 cookie),
> +
> +	TP_ARGS(mask, data, data_type, dir, file_name, inode, cookie),
> +
> +	TP_STRUCT__entry(
> +		__field(__u32, mask)
> +		__field(unsigned long, dir_ino)
> +		__field(unsigned long, ino)
> +		__field(dev_t, s_dev)
> +		__field(int, data_type)
> +		__field(u32, cookie)
> +		__string(file_name, file_name ? (const char *)file_name->name : "")
> +	),
> +
> +	TP_fast_assign(
> +		__entry->mask = mask;
> +		__entry->dir_ino = dir ? dir->i_ino : 0;
> +		__entry->ino = inode ? inode->i_ino : 0;
> +		__entry->s_dev = dir ? dir->i_sb->s_dev :
> +				 inode ? inode->i_sb->s_dev : 0;
> +		__entry->data_type = data_type;
> +		__entry->cookie = cookie;
> +		__assign_str(file_name);
> +	),
> +
> +	TP_printk("dev=%d:%d dir=%lu ino=%lu data_type=%d cookie=0x%x mask=0x%x %s name=%s",
> +		  MAJOR(__entry->s_dev), MINOR(__entry->s_dev),
> +		  __entry->dir_ino, __entry->ino,
> +		  __entry->data_type, __entry->cookie,
> +		  __entry->mask, show_fsnotify_mask(__entry->mask),
> +		  __get_str(file_name))
> +);
> +
> +#endif /* _TRACE_FSNOTIFY_H */
> +
> +/* This part must be outside protection */
> +#include <trace/define_trace.h>
> diff --git a/include/trace/misc/fsnotify.h b/include/trace/misc/fsnotify.h
> new file mode 100644
> index 000000000000..a201e1bd6d8c
> --- /dev/null
> +++ b/include/trace/misc/fsnotify.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Display helpers for fsnotify events
> + */
> +
> +#include <linux/fsnotify_backend.h>
> +
> +#define show_fsnotify_mask(mask) \
> +	__print_flags(mask, "|", \
> +		{ FS_ACCESS,		"ACCESS" }, \
> +		{ FS_MODIFY,		"MODIFY" }, \
> +		{ FS_ATTRIB,		"ATTRIB" }, \
> +		{ FS_CLOSE_WRITE,	"CLOSE_WRITE" }, \
> +		{ FS_CLOSE_NOWRITE,	"CLOSE_NOWRITE" }, \
> +		{ FS_OPEN,		"OPEN" }, \
> +		{ FS_MOVED_FROM,	"MOVED_FROM" }, \
> +		{ FS_MOVED_TO,		"MOVED_TO" }, \
> +		{ FS_CREATE,		"CREATE" }, \
> +		{ FS_DELETE,		"DELETE" }, \
> +		{ FS_DELETE_SELF,	"DELETE_SELF" }, \
> +		{ FS_MOVE_SELF,		"MOVE_SELF" }, \
> +		{ FS_OPEN_EXEC,		"OPEN_EXEC" }, \
> +		{ FS_UNMOUNT,		"UNMOUNT" }, \
> +		{ FS_Q_OVERFLOW,	"Q_OVERFLOW" }, \
> +		{ FS_ERROR,		"ERROR" }, \
> +		{ FS_OPEN_PERM,		"OPEN_PERM" }, \
> +		{ FS_ACCESS_PERM,	"ACCESS_PERM" }, \
> +		{ FS_OPEN_EXEC_PERM,	"OPEN_EXEC_PERM" }, \
> +		{ FS_PRE_ACCESS,	"PRE_ACCESS" }, \
> +		{ FS_MNT_ATTACH,	"MNT_ATTACH" }, \
> +		{ FS_MNT_DETACH,	"MNT_DETACH" }, \
> +		{ FS_EVENT_ON_CHILD,	"EVENT_ON_CHILD" }, \
> +		{ FS_RENAME,		"RENAME" }, \
> +		{ FS_DN_MULTISHOT,	"DN_MULTISHOT" }, \
> +		{ FS_ISDIR,		"ISDIR" })
> 
> -- 
> 2.53.0
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

^ permalink raw reply

* Re: [RFC, PATCH 00/12] userfaultfd: working set tracking for VM guest memory
From: David Hildenbrand (Arm) @ 2026-04-17 11:43 UTC (permalink / raw)
  To: Kiryl Shutsemau
  Cc: Andrew Morton, Peter Xu, Lorenzo Stoakes, Mike Rapoport,
	Suren Baghdasaryan, Vlastimil Babka, Liam R . Howlett, Zi Yan,
	Jonathan Corbet, Shuah Khan, Sean Christopherson, Paolo Bonzini,
	linux-mm, linux-kernel, linux-doc, linux-kselftest, kvm
In-Reply-To: <aeFCuLJXT8VOkzH7@thinkstation>

On 4/16/26 22:25, Kiryl Shutsemau wrote:
> On Thu, Apr 16, 2026 at 08:32:19PM +0200, David Hildenbrand (Arm) wrote:
>> On 4/16/26 15:49, Kiryl Shutsemau wrote:
>>>
>>> Here is an updated version:
>>>
>>> https://git.kernel.org/pub/scm/linux/kernel/git/kas/linux.git/log/?h=uffd/rfc-v2
>>>
>>> will post after -rc1 is tagged.
>>>
>>> I like it more. It got substantially cleaner.
>>
>> I don't have time to look into the details just yet, but my thinking was
>> that
>>
>> a) It would avoid the zap+refault
> 
> Yep.
> 
>> b) We could reuse the uffd-wp PTE bit + marker to indicate/remember the
>>    protection, making it co-exist with NUMA hinting naturally.
>>
>> b) obviously means that we cannot use uffd-wp and uffd-rwp at the same
>> time in the same uffd area. I guess that should be acceptable for the
>> use cases we you should have in mind?
> 
> I took a different path: I still use PROT_NONE PTEs, so it cannot
> co-exist with NUMA balancing [fully], but WP + RWP should be fine. I
> need to add a test for this.
> 
> I didn't give up on NUMA balancing completely. task_numa_fault() is
> called on RWP fault. So it should help scheduler decisions somewhat.
> 
> I think an RWP user might want to use WP too.
> 
> Do you see this trade-off as reasonable?

One reason why the PTE bit was added for the WP case was to distinguish
it from other write faults.

I assume without a dedicated PTE bit your design will always suffer from
false positive notifications.

Leaving NUMA-balancing aside, a simple
mprotect(PROT_NONE)+mprotect(PROT_READ) would already be problematic to
distinguish both cases. Zap+refault for shmem would likely have similar
problems (we'd need a marker).

I don't think a design that allows for false positives is what we really
want, especially as it would diverge from what we already have for WP.

Yes, using the PTE bit (that we already have) implies that we could, for
now, not allow the combination of WP + RWP.

-- 
Cheers,

David

^ permalink raw reply

* Re: [RFC, PATCH 00/12] userfaultfd: working set tracking for VM guest memory
From: Kiryl Shutsemau @ 2026-04-17 11:02 UTC (permalink / raw)
  To: David Hildenbrand (Arm)
  Cc: Andrew Morton, Peter Xu, Lorenzo Stoakes, Mike Rapoport,
	Suren Baghdasaryan, Vlastimil Babka, Liam R . Howlett, Zi Yan,
	Jonathan Corbet, Shuah Khan, Sean Christopherson, Paolo Bonzini,
	linux-mm, linux-kernel, linux-doc, linux-kselftest, kvm
In-Reply-To: <aeFCuLJXT8VOkzH7@thinkstation>

On Thu, Apr 16, 2026 at 09:25:25PM +0100, Kiryl Shutsemau wrote:
> > b) obviously means that we cannot use uffd-wp and uffd-rwp at the same
> > time in the same uffd area. I guess that should be acceptable for the
> > use cases we you should have in mind?
> 
> I took a different path: I still use PROT_NONE PTEs, so it cannot
> co-exist with NUMA balancing [fully], but WP + RWP should be fine. I
> need to add a test for this.

WP + RWP works. Test case added.

-- 
  Kiryl Shutsemau / Kirill A. Shutemov

^ permalink raw reply

* Re: [PATCH] crash: Support high memory reservation for range syntax
From: Youling Tang @ 2026-04-17  9:23 UTC (permalink / raw)
  To: Baoquan He
  Cc: Baoquan He, Sourabh Jain, Andrew Morton, Jonathan Corbet,
	Vivek Goyal, Dave Young, kexec, linux-kernel, linux-doc,
	Youling Tang
In-Reply-To: <ad92ix7d7I8jsykV@MiWiFi-R3L-srv>

On 4/15/26 19:29, Baoquan He wrote:

> On 04/09/26 at 09:55am, Youling Tang wrote:
>> Hi, Baoquan
>>
>> On 4/8/26 21:32, Baoquan He wrote:
>>> On 04/08/26 at 10:01am, Sourabh Jain wrote:
>>>> Hello Youling,
>>>>
>>>> On 04/04/26 13:11, Youling Tang wrote:
>>>>> From: Youling Tang <tangyouling@kylinos.cn>
>>>>>
>>>>> The crashkernel range syntax (range1:size1[,range2:size2,...]) allows
>>>>> automatic size selection based on system RAM, but it always reserves
>>>>> from low memory. When a large crashkernel is selected, this can
>>>>> consume most of the low memory, causing subsequent hardware
>>>>> hotplug or drivers requiring low memory to fail due to allocation
>>>>> failures.
>>>> Support for high crashkernel reservation has been added to
>>>> address the above problem.
>>>>
>>>> However, high crashkernel reservation is not supported with
>>>> range-based crashkernel kernel command-line arguments.
>>>> For example: crashkernel=0M-1G:100M,1G-4G:160M,4G-8G:192M
>>>>
>>>> Many users, including some distributions, use range-based
>>>> crashkernel configuration. So, adding support for high crashkernel
>>>> reservation with range-based configuration would be useful.
>>> Sorry for late response. And I have to say sorry because I have some
>>> negative tendency on this change.
>>>
>>> We use crashkernel=xM|G and crashkernel=range1:size1[,range2:size2,...]
>>> as default setting, so that people only need to set suggested amount
>>> of memory. While crashkernel=,high|low is for advanced user to customize
>>> their crashkernel value. In that case, user knows what's high memory and
>>> low memory, and how much is needed separately to achieve their goal, e.g
>>> saving low memory, taking away more high memory.
>>>
>>> To be honest, above grammers sounds simple, right? I believe both of you
>>> know very well how complicated the current crashkernel code is. I would
>>> suggest not letting them becomre more and more complicated by extending
>>> the grammer further and further. Unless you meet unavoidable issue with
>>> the existing grammer.
>>>
>>> Here comes my question, do you meet unavoidable issue with the existing
>>> grammer when you use crashkernel=range1:size1[,range2:size2,...] and
>>> think it's not satisfactory, and at the same time crashkernel=,high|low
>>> can't meet your demand either?
>> Yes, regular users generally don't know about high memory and low memory,
>> and probably don't know how much crashkernel memory should be reserved
>> either. They mostly just use the default crashkernel parameters configured
>> by the distribution.
>>
>> For advanced users, the current grammar is sufficient, because
>> 'crashkernel=<range1>:<size1>[,<range2>:<size2>,...][@offset],>boundary'
>> can definitely be replaced with 'crashkernel=size,high'.
>>
>> The main purpose of this patch is to provide distributions with a more
>> reasonable default parameter configuration (satisfying most requirements),
>> without having to set different distribution default parameters for
>> different
>> scenarios (physical machines, virtual machines) and different machine
>> models.
> OK, do you have a concrete case? e.g in your distros, what will you set
> with this patchset applied? Let's see if it can cover all cases with one
> simple and satisfying parameter.

For our production deployment across various hardware configurations
(physical servers, VMs with different memory sizes), I'm planning to
use the following crashkernel configuration:
crashkernel=1G-4G:256M,4G-12G:384M,12G-48G:512M,48G-128G:768M,128G-:1024M,>384M

Thanks,
Youling.

^ permalink raw reply

* Re: [PATCH v4 0/3] mm/memory-failure: add panic option for unrecoverable pages
From: Breno Leitao @ 2026-04-17  9:10 UTC (permalink / raw)
  To: Jiaqi Yan
  Cc: Miaohe Lin, Naoya Horiguchi, Andrew Morton, Jonathan Corbet,
	Shuah Khan, David Hildenbrand, Lorenzo Stoakes, Liam R. Howlett,
	Vlastimil Babka, Mike Rapoport, Suren Baghdasaryan, Michal Hocko,
	linux-mm, linux-kernel, linux-doc, kernel-team
In-Reply-To: <CACw3F50WYH8Vmd9EXx9+3yM=FU5-1WBkNffkGucC+wSjL+=wFQ@mail.gmail.com>

On Thu, Apr 16, 2026 at 09:26:08AM -0700, Jiaqi Yan wrote:

> So we will always get the same stack trace below, right?
> 
>           panic+0xb4/0xc0
>           action_result+0x278/0x340
>           memory_failure+0x152b/0x1c80
> 
> IIUC, this stack trace itself doesn't provide any useful information
> about the memory error, right? What exactly can we use from the stack
> trace? It is just a side-effect that we failed immediately.

We can use it to correlate problems across a fleet of machines. Let me
share how crash dump analysis works in large datacenters.

There are thousands of crashes a day (to stay on the low ballpark), and
different services try to correlate and categorize them into a few
buckets, something like:

	1. New crash — needs investigation
	2. Known issue — fix is being rolled out
	3. Hardware problem — do not spend engineering time on it

When a machine crashes at a random code path like d_lookup() 67 seconds
after the memory error, the automated triage classifies it as a kernel
bug in VFS/dcache and assigns it to the filesystem team for
investigation. Engineers spend time chasing a bug that doesn't exist in
software — it's a hardware problem.

With the immediate panic at memory_failure(), the stack trace is always
recognizable and can be automatically classified as category 3 (hardware
problem). The static stack trace is the feature, not a limitation: it
gives triage automation a stable signature to match on.

The value isn't in what the stack trace and the panic() tells a human reading
one crash — it's in what it tells automated systems processing thousands of
them.

> You can still correlate failure with "Memory failure: 0x1: unhandlable
> page" and keep running until the actual fatal poison consumption takes
> down the system. Drawback is that these will be cascading events that
> can be "noisy". What I see is the choice between failing fast versus
> failing safe.

Correlating the "unhandlable page" log with a later crash is
theoretically possible but breaks down in practice at scale:

- The crash may happen seconds, minutes, or hours later — or never, if
the page isn't accessed again before a reboot.

- The crash happens on a different CPU, different task, different context

— there's no breadcrumb linking it back to the memory error.

- Automated triage systems work on stack traces and panic strings, not
by correlating dmesg lines across time with later crashes.

- The later crash looks completely different depending on the
architecture. On arm64, you get a "synchronous external abort". On
x86, it's a machine check exception. On some platforms, it might be a
generic page fault or a BUG_ON in a subsystem that found inconsistent
data. There is no single signature to match — every architecture and
every consumption path produces a different crash, making automated
correlation essentially impossible.

- Worse, the crash may never happen at all. If the corrupted memory is
read but the corruption doesn't trigger a fault — say, a flipped bit
in a permission field, a size, a pointer that still maps to valid
memory, or a data buffer — the result is silent data corruption with
no crash to correlate against. The system continues operating on wrong
data with no indication anything went wrong.

Also, I wouldn't call continuing with known-corrupted kernel memory
"failing safe" — it's the opposite. The kernel has no mechanism to
fence off a poisoned slab page or page table from future access.
Continuing is failing unsafely with a delayed, unpredictable
consequence.


> > Isn't the clean approach way better than the random one?
> 
> I don't fully agree. In the past upstream has enhanced many kernel mm
> services (e.g. khugepaged, page migration, dump_user_range()) to
> recover from memory error in order to improve system availability,
> given these service or tools can fail safe. Seeing many crashes
> pointing to a certain in-kernel service at consumption time helped us
> decide what services we should enhance, and which service we should
> prioritize. Of course not all kernel code can be recovered from memory
> error, but that doesn't mean knowing what kernel code often caused
> crash isn't useful.


That's a fair point — consumption-time crashes have historically been
useful for identifying which kernel services to harden. But I'd argue
this patch doesn't prevent that analysis, it complements it.

The sysctl defaults to off. Operators who want to observe where poison
is consumed — to prioritize which services to enhance — can leave it
disabled and get exactly the behavior they have today.

But for operators running large fleets where the priority is fast
diagnosis and machine replacement rather than kernel hardening research,
the immediate panic is what they need. They already know the memory is
bad, they don't need the kernel to keep running to find out which
subsystem hits it first.

Also, the services you mention — khugepaged, page migration,
dump_user_range() — were enhanced to handle errors in user pages,
where recovery is possible (kill the process, fail the migration). The
pages this patch panics on — reserved pages, unknown page types — are
kernel memory where _no_ recovery mechanism exists or is likely to exist.
There's no service to enhance for those; the only options are crash now
or crash later, given a crucial memory page got lost. 

> Anyway, I only have a second opinion on the usefulness of a static
> stack trace. This fail-fast option is good to have. Thanks!

Thanks for the review! Just to make sure I understand your position correctly —
are you saying you'd like changes to the patch, or is this more of a general
observation about the tradeoff?

--breno

^ permalink raw reply

* Re: [PATCH V10 00/10] famfs: port into fuse
From: Amir Goldstein @ 2026-04-17  9:06 UTC (permalink / raw)
  To: Gregory Price
  Cc: Joanne Koong, John Groves, Darrick J. Wong, Miklos Szeredi,
	Bernd Schubert, John Groves, Dan Williams, Bernd Schubert,
	Alison Schofield, John Groves, Jonathan Corbet, Shuah Khan,
	Vishal Verma, Dave Jiang, Matthew Wilcox, Jan Kara,
	Alexander Viro, David Hildenbrand, Christian Brauner,
	Randy Dunlap, Jeff Layton, Jonathan Cameron, Stefan Hajnoczi,
	Josef Bacik, Bagas Sanjaya, Chen Linxuan, James Morse, Fuad Tabba,
	Sean Christopherson, Shivank Garg, Ackerley Tng, Aravind Ramesh,
	Ajay Joshi, venkataravis@micron.com, linux-doc@vger.kernel.org,
	linux-kernel@vger.kernel.org, nvdimm@lists.linux.dev,
	linux-cxl@vger.kernel.org, linux-fsdevel@vger.kernel.org, djbw,
	Christoph Hellwig
In-Reply-To: <aeHXQ2EW2ivlLb_N@gourry-fedora-PF4VCD3F>

On Fri, Apr 17, 2026 at 8:46 AM Gregory Price <gourry@gourry.net> wrote:
>
> On Thu, Apr 16, 2026 at 06:24:02PM -0700, Joanne Koong wrote:
> > On Thu, Apr 16, 2026 at 1:14 PM Gregory Price <gourry@gourry.net> wrote:
> > >
> > > I worry that this discussion is going to turn towards implementing a
> > > solution grounded in parsing arbitrary formats and how to store them,
> > > and that is completely detached from why FAMFS went this route in the
> > > first place.
> > >
> > > I question whether the actual issue here lies in the interface APPEARING
> > > more general purpose than it actually is - and therefore inviting
> > > attempts to over-genericize it.
> >
> > Would you mind clarifying this part? Are you saying that the interface
> > and logic is *already* generic and usable for other dax-backed
> > servers, just that everything is *named* famfs but it's not really
> > famfs specific?
>
> Yes.
>
> If you just find/replace "famfs" with "dax_iomap", the structures
> here don't really seem all *that* crazy specific - they're just
> optimized for memory speeds instead of I/O.
>
> There is a circular nature to this - FAMFS figured it out first, in
> what we think is a reasonably generic way, but we can't know for sure.
>
> John, Dan, and Darrick have all proposed reasonable ways to hedge
> against the obvious fact the interface will not be perfect - which
> incorporates your BPF proposal along with a reasonably straight forward
> deprecation path that's not always possible in other arenas.
>
> All that while solving a real (and novel) problem.
>
> That's actually pretty damn cool.
>
> I would urge you to consider these proposals earnestly.
>

Apart from your suggestion to replace s/famfs/dax_iomap/
the fact that this logic sits in fs/fuse/famfs.c (or fuse/dax_iomap.c)
is the other big architecture issue.

If this logic was to be placed in fs/iomap/ as Christoph suggested,
I think the rest of the UAPI issues could be sorted out.

In any case, considering the sheer amount of discussion on this thread
I have scheduled a cross-track FS+MM+IO for Famfs and DAX iomap.

I wasn't going to include Storage people at first, but since Christoph
mentioned that stride/offset iomap could be useful for block iomap,
I included them as well.

Thanks,
Amir.

^ permalink raw reply

* Re: [PATCH 1/3] rust: tests: drop 'use crate' in bitmap and atomic KUnit tests
From: David Gow @ 2026-04-17  9:00 UTC (permalink / raw)
  To: Yury Norov, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
	Uladzislau Rezki, Burak Emir, Yury Norov, Brendan Higgins,
	Rae Moar, Will Deacon, Peter Zijlstra, Mark Rutland,
	Nathan Chancellor, Kees Cook, Nicolas Schier,
	Thomas Weißschuh, Thomas Gleixner, Douglas Anderson,
	Shakeel Butt, Christian Brauner, Randy Dunlap, Tamir Duberstein,
	rust-for-linux, linux-doc, linux-kernel, linux-kselftest,
	kunit-dev
In-Reply-To: <20260417031531.315281-2-ynorov@nvidia.com>

Le 17/04/2026 à 11:15 AM, Yury Norov a écrit :
> The following patch makes usage of macros::kunit_tests crate conditional
> on the corresponding configs. When the configs are disabled, compiler
> warns on unused crate. So, embed it in unit test declaration.
> 
> Signed-off-by: Yury Norov <ynorov@nvidia.com>
> ---

Looks good to me!

Reviewed-by: David Gow <david@davidgow.net>

Cheers,
-- David

>   rust/kernel/bitmap.rs                | 4 +---
>   rust/kernel/sync/atomic/predefine.rs | 4 +---
>   2 files changed, 2 insertions(+), 6 deletions(-)
> 
> diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs
> index 83d7dea99137..894043c9e460 100644
> --- a/rust/kernel/bitmap.rs
> +++ b/rust/kernel/bitmap.rs
> @@ -499,9 +499,7 @@ pub fn next_zero_bit(&self, start: usize) -> Option<usize> {
>       }
>   }
>   
> -use macros::kunit_tests;
> -
> -#[kunit_tests(rust_kernel_bitmap)]
> +#[macros::kunit_tests(rust_kernel_bitmap)]
>   mod tests {
>       use super::*;
>       use kernel::alloc::flags::GFP_KERNEL;
> diff --git a/rust/kernel/sync/atomic/predefine.rs b/rust/kernel/sync/atomic/predefine.rs
> index 1d53834fcb12..84fcd7cfcb73 100644
> --- a/rust/kernel/sync/atomic/predefine.rs
> +++ b/rust/kernel/sync/atomic/predefine.rs
> @@ -154,9 +154,7 @@ fn rhs_into_delta(rhs: usize) -> isize_atomic_repr {
>       }
>   }
>   
> -use crate::macros::kunit_tests;
> -
> -#[kunit_tests(rust_atomics)]
> +#[macros::kunit_tests(rust_atomics)]
>   mod tests {
>       use super::super::*;
>   


^ permalink raw reply

* Re: [PATCH 2/3] rust: testing: add Kconfig for KUnit test
From: David Gow @ 2026-04-17  9:00 UTC (permalink / raw)
  To: Yury Norov, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
	Uladzislau Rezki, Burak Emir, Yury Norov, Brendan Higgins,
	Rae Moar, Will Deacon, Peter Zijlstra, Mark Rutland,
	Nathan Chancellor, Kees Cook, Nicolas Schier,
	Thomas Weißschuh, Thomas Gleixner, Douglas Anderson,
	Shakeel Butt, Christian Brauner, Randy Dunlap, Tamir Duberstein,
	rust-for-linux, linux-doc, linux-kernel, linux-kselftest,
	kunit-dev
In-Reply-To: <20260417031531.315281-3-ynorov@nvidia.com>

Le 17/04/2026 à 11:15 AM, Yury Norov a écrit :
> There are 6 individual Rust KUnit tests. All the tests are compiled
> unconditionally now, which adds ~200 kB to the kernel image for me
> on x86_64. As Rust matures, this bloating will inevitably grow.
> 
> Add Kconfig.test which includes a RUST_KUNIT_TESTS menu, and all
> individual tests under it.

I think this makes a lot of sense for tests within the kernel crate (and 
any other 'system' crates). Individual drivers probably should still 
keep their Kconfig options under the corresponding subsystem.

> 
> As usual, new tests are all enabled if KUNIT_ALL_TESTS=y.
> 
> Suggested-by: Alice Ryhl <aliceryhl@google.com>
> Signed-off-by: Yury Norov <ynorov@nvidia.com>
> ---
> This doesn't create a new entry in MAINTAINERS for the Kconfig.test,
> so the new file just follows the implicit rule for the rust/ directory.
> Please let me know if the explicit entry is needed.
> 

I think it's fine to leave this as implicitly part of rust/, personally.

Still, this looks good.

Reviewed-by: David Gow <david@davidgow.net>

Cheers,
-- David

>   init/Kconfig                         |  2 +
>   rust/kernel/Kconfig.test             | 76 ++++++++++++++++++++++++++++
>   rust/kernel/alloc/allocator.rs       |  1 +
>   rust/kernel/alloc/kvec.rs            |  1 +
>   rust/kernel/bitmap.rs                |  1 +
>   rust/kernel/kunit.rs                 |  1 +
>   rust/kernel/str.rs                   |  1 +
>   rust/kernel/sync/atomic/predefine.rs |  1 +
>   8 files changed, 84 insertions(+)
>   create mode 100644 rust/kernel/Kconfig.test
> 
> diff --git a/init/Kconfig b/init/Kconfig
> index 43875ef36752..4af544514e6c 100644
> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -2208,6 +2208,8 @@ config RUST
>   
>   	  If unsure, say N.
>   
> +source "rust/kernel/Kconfig.test"
> +
>   config RUSTC_VERSION_TEXT
>   	string
>   	depends on RUST
> diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test
> new file mode 100644
> index 000000000000..a05243696a01
> --- /dev/null
> +++ b/rust/kernel/Kconfig.test
> @@ -0,0 +1,76 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +menuconfig RUST_KUNIT_TESTS
> +	bool "Rust KUnit tests"
> +	depends on KUNIT && RUST
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This menu collects all options for Rust Kunit tests.
> +	  See Documentation/rust/testing.rst for how to protect
> +	  unit tests with these options.
> +
> +	  Say Y here to enable Rust KUnit tests.
> +
> +	  If unsure, say N.
> +
> +if RUST_KUNIT_TESTS
> +config RUST_ALLOCATOR_KUNIT_TEST
> +	bool "KUnit tests for Rust allocator API" if !KUNIT_ALL_TESTS
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This option enables KUnit tests for the Rust allocator API.
> +	  These are only for development and testing, not for regular
> +	  kernel use cases.
> +
> +	  If unsure, say N.
> +
> +config RUST_KVEC_KUNIT_TEST
> +	bool "KUnit tests for Rust KVEC API" if !KUNIT_ALL_TESTS
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This option enables KUnit tests for the Rust KVEC API.
> +	  These are only for development and testing, not for
> +	  regular kernel use cases.
> +
> +	  If unsure, say N.
> +
> +config RUST_BITMAP_KUNIT_TEST
> +	bool "KUnit tests for Rust bitmap API" if !KUNIT_ALL_TESTS
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This option enables KUnit tests for the Rust bitmap API.
> +	  These are only for development and testing, not for regular
> +	  kernel use cases.
> +
> +	  If unsure, say N.
> +
> +config RUST_KUNIT_SELFTEST
> +	bool "KUnit selftests for Rust" if !KUNIT_ALL_TESTS
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This option enables KUnit selftests. These are only
> +	  for development and testing, not for regular kernel
> +	  use cases.
> +
> +	  If unsure, say N.
> +
> +config RUST_STR_KUNIT_TEST
> +	bool "KUnit tests for Rust strings APIs" if !KUNIT_ALL_TESTS
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This option enables KUnit tests for the Rust strings API.
> +	  These are only for development and testing, not for regular
> +	  kernel use cases.
> +
> +	  If unsure, say N.
> +
> +config RUST_ATOMICS_KUNIT_TEST
> +	bool "KUnit tests for Rust atomics APIs" if !KUNIT_ALL_TESTS
> +	default KUNIT_ALL_TESTS
> +	help
> +	  This option enables KUnit tests for the Rust atomics API.
> +	  These are only for development and testing, not for regular
> +	  kernel use cases.
> +
> +	  If unsure, say N.
> +
> +endif
> diff --git a/rust/kernel/alloc/allocator.rs b/rust/kernel/alloc/allocator.rs
> index 63bfb91b3671..0d3434bca867 100644
> --- a/rust/kernel/alloc/allocator.rs
> +++ b/rust/kernel/alloc/allocator.rs
> @@ -251,6 +251,7 @@ unsafe fn realloc(
>       }
>   }
>   
> +#[cfg(CONFIG_RUST_ALLOCATOR_KUNIT_TEST)]
>   #[macros::kunit_tests(rust_allocator)]
>   mod tests {
>       use super::*;
> diff --git a/rust/kernel/alloc/kvec.rs b/rust/kernel/alloc/kvec.rs
> index ac8d6f763ae8..563c760c8105 100644
> --- a/rust/kernel/alloc/kvec.rs
> +++ b/rust/kernel/alloc/kvec.rs
> @@ -1351,6 +1351,7 @@ fn drop(&mut self) {
>       }
>   }
>   
> +#[cfg(CONFIG_RUST_KVEC_KUNIT_TEST)]
>   #[macros::kunit_tests(rust_kvec)]
>   mod tests {
>       use super::*;
> diff --git a/rust/kernel/bitmap.rs b/rust/kernel/bitmap.rs
> index 894043c9e460..b27e0ec80d64 100644
> --- a/rust/kernel/bitmap.rs
> +++ b/rust/kernel/bitmap.rs
> @@ -499,6 +499,7 @@ pub fn next_zero_bit(&self, start: usize) -> Option<usize> {
>       }
>   }
>   
> +#[cfg(CONFIG_RUST_BITMAP_KUNIT_TEST)]
>   #[macros::kunit_tests(rust_kernel_bitmap)]
>   mod tests {
>       use super::*;
> diff --git a/rust/kernel/kunit.rs b/rust/kernel/kunit.rs
> index a1edf7491579..cdee5f27bd7f 100644
> --- a/rust/kernel/kunit.rs
> +++ b/rust/kernel/kunit.rs
> @@ -329,6 +329,7 @@ pub fn in_kunit_test() -> bool {
>       !unsafe { bindings::kunit_get_current_test() }.is_null()
>   }
>   
> +#[cfg(CONFIG_RUST_KUNIT_SELFTEST)]
>   #[kunit_tests(rust_kernel_kunit)]
>   mod tests {
>       use super::*;
> diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
> index 8311d91549e1..a435674f05ea 100644
> --- a/rust/kernel/str.rs
> +++ b/rust/kernel/str.rs
> @@ -415,6 +415,7 @@ macro_rules! c_str {
>       }};
>   }
>   
> +#[cfg(CONFIG_RUST_STR_KUNIT_TEST)]
>   #[kunit_tests(rust_kernel_str)]
>   mod tests {
>       use super::*;
> diff --git a/rust/kernel/sync/atomic/predefine.rs b/rust/kernel/sync/atomic/predefine.rs
> index 84fcd7cfcb73..7468153429e1 100644
> --- a/rust/kernel/sync/atomic/predefine.rs
> +++ b/rust/kernel/sync/atomic/predefine.rs
> @@ -154,6 +154,7 @@ fn rhs_into_delta(rhs: usize) -> isize_atomic_repr {
>       }
>   }
>   
> +#[cfg(CONFIG_RUST_ATOMICS_KUNIT_TEST)]
>   #[macros::kunit_tests(rust_atomics)]
>   mod tests {
>       use super::super::*;


^ permalink raw reply

* Re: [PATCH 3/3] Documentation: rust: testing: add Kconfig guidance
From: David Gow @ 2026-04-17  9:00 UTC (permalink / raw)
  To: Yury Norov, Miguel Ojeda, Boqun Feng, Gary Guo,
	Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
	Trevor Gross, Danilo Krummrich, Jonathan Corbet, Shuah Khan,
	Lorenzo Stoakes, Vlastimil Babka, Liam R. Howlett,
	Uladzislau Rezki, Burak Emir, Yury Norov, Brendan Higgins,
	Rae Moar, Will Deacon, Peter Zijlstra, Mark Rutland,
	Nathan Chancellor, Kees Cook, Nicolas Schier,
	Thomas Weißschuh, Thomas Gleixner, Douglas Anderson,
	Shakeel Butt, Christian Brauner, Randy Dunlap, Tamir Duberstein,
	rust-for-linux, linux-doc, linux-kernel, linux-kselftest,
	kunit-dev
In-Reply-To: <20260417031531.315281-4-ynorov@nvidia.com>

Le 17/04/2026 à 11:15 AM, Yury Norov a écrit :
> Now that rust KUnit tests are protected with Kconfig, update the
> documentation to mention it.
> 
> Signed-off-by: Yury Norov <ynorov@nvidia.com>
> ---

Looks good to me.

Reviewed-by: David Gow <david@davidgow.net>

Cheers,
-- David

>   Documentation/rust/testing.rst | 5 ++++-
>   1 file changed, 4 insertions(+), 1 deletion(-)
> 
> diff --git a/Documentation/rust/testing.rst b/Documentation/rust/testing.rst
> index f43cb77bcc69..24de173471b2 100644
> --- a/Documentation/rust/testing.rst
> +++ b/Documentation/rust/testing.rst
> @@ -141,10 +141,13 @@ These tests are introduced by the ``kunit_tests`` procedural macro, which takes
>   the name of the test suite as an argument.
>   
>   For instance, assume we want to test the function ``f`` from the documentation
> -tests section. We could write, in the same file where we have our function:
> +tests section. We could write, in the same file where we have our function.
> +Each test is protected with the corresponding Kconfig option, see
> +rust/kernel/Kconfig.test.
>   
>   .. code-block:: rust
>   
> +	#[cfg(CONFIG_RUST_MYMOD_KUNIT_TEST)]
>   	#[kunit_tests(rust_kernel_mymod)]
>   	mod tests {
>   	    use super::*;


^ permalink raw reply

* Re: [PATCH v4 7/8] ARM: dts: Declare UART1 on zx297520v3 boards
From: Arnd Bergmann @ 2026-04-17  8:59 UTC (permalink / raw)
  To: Stefan Dösinger, Jonathan Corbet, Shuah Khan, Russell King,
	Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Krzysztof Kozlowski, Alexandre Belloni, Linus Walleij,
	Drew Fustini, Greg Kroah-Hartman, Jiri Slaby
  Cc: linux-doc, linux-kernel, linux-arm-kernel, devicetree, soc,
	linux-serial
In-Reply-To: <20260416-send-v4-7-e19d02b944ec@gmail.com>

On Thu, Apr 16, 2026, at 22:19, Stefan Dösinger wrote:
>
> The reason why I add the serial1=uart1 alias is to keep console=ttyAMA1
> stable regardless of the other enabled UARTs. UART0, as the name
> implies, has a lower MMIO address, but uart1 is the one that usually has
> the boot output and console.

I'm not sure I'm following here. You generally want to either make
sure the alias matches whatever number is printed on the product
if there are multiple numbered ports, or you just use 'serial0'
as the only alias if there is only one port.

> +	aliases {
> +		serial1 = &uart1;
> +	};

Either way, the alias should go into the board specific file, not
the general SoC file, as a board might be using a different
set of UARTs.

> +
> +		/* The UART clock defaults to 26 mhz. It will be replaced when the zx29 clock
> +		 * framework is added.
> +		 */
> +		uartclk: uartclk: clock-26000000 {
> +			#clock-cells = <0>;
> +			compatible = "fixed-clock";
> +			clock-frequency = <26000000>;
> +		};
> +
> +		uart1: serial@1408000 {
> +			compatible = "arm,pl011", "arm,primecell";
> +			arm,primecell-periphid = <0x001feffe>;
> +			reg = <0x01408000 0x1000>;
> +			interrupts = <GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH>;
> +			clocks = <&uartclk>;
> +			clock-names = "apb_pclk";
> +		};

Since you know the addresses of the other uart instances, I would
suggest you add all of them at the same time.

       Arnd

^ permalink raw reply

* Re: [PATCH v10 02/11] lib: kstrtox: add kstrtoudec64() and kstrtodec64()
From: Rodrigo Alencar @ 2026-04-17  8:36 UTC (permalink / raw)
  To: Rodrigo Alencar, 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
In-Reply-To: <20260415-adf41513-iio-driver-v10-2-df61046d5457@analog.com>

On 26/04/15 10:51AM, Rodrigo Alencar wrote:
> 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.

...

> +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;
> +}

I have an alternative (slightly more complex) implementation of this function
that handles E notation. I find this particularly handy when writting big
values like 25 GHz when the ABI is defined in Hz, so instead of writing
25000000000, one can just use 25e9, or 2.5e10. I found that my python code
was printing big floating point values or really small ones using E notation
and that was giving me -EINVAL, so I had to adjust formatting when generating
the string input to the file. No big deal, and we would not need this here,
but if maintainers find this useful I could add it into a v11 of this series.

-- 
Kind regards,

Rodrigo Alencar

^ permalink raw reply

* [PATCH v3 2/2] hwmon: (pmbus/max20830) add driver for max20830
From: Alexis Czezar Torreno @ 2026-04-17  8:27 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc,
	Alexis Czezar Torreno
In-Reply-To: <20260417-dev_max20830-v3-0-0cb8d56067aa@analog.com>

Add support for MAX20830 step-down DC-DC switching regulator with
PMBus interface. It allows monitoring of input/output voltage,
output current and temperature through the PMBus serial interface.

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
---
 Documentation/hwmon/index.rst    |  1 +
 Documentation/hwmon/max20830.rst | 49 ++++++++++++++++++++++
 MAINTAINERS                      |  2 +
 drivers/hwmon/pmbus/Kconfig      |  9 ++++
 drivers/hwmon/pmbus/Makefile     |  1 +
 drivers/hwmon/pmbus/max20830.c   | 88 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 150 insertions(+)

diff --git a/Documentation/hwmon/index.rst b/Documentation/hwmon/index.rst
index 8b655e5d6b68b90c697a52c7bf526e81d370caf7..56f7eb761be76dd627a2f34135abad05203b0582 100644
--- a/Documentation/hwmon/index.rst
+++ b/Documentation/hwmon/index.rst
@@ -158,6 +158,7 @@ Hardware Monitoring Kernel Drivers
    max197
    max20730
    max20751
+   max20830
    max31722
    max31730
    max31760
diff --git a/Documentation/hwmon/max20830.rst b/Documentation/hwmon/max20830.rst
new file mode 100644
index 0000000000000000000000000000000000000000..936e409dcc5c0898dde27d782308d4a7e1357e73
--- /dev/null
+++ b/Documentation/hwmon/max20830.rst
@@ -0,0 +1,49 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+Kernel driver max20830
+======================
+
+Supported chips:
+
+  * Analog Devices MAX20830
+
+    Prefix: 'max20830'
+
+    Addresses scanned: -
+
+    Datasheet: https://www.analog.com/media/en/technical-documentation/data-sheets/max20830.pdf
+
+Author:
+
+  - Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
+
+
+Description
+-----------
+
+This driver supports hardware monitoring for Analog Devices MAX20830
+Step-Down Switching Regulator with PMBus Interface.
+
+The MAX20830 is a 2.7V to 16V, 30A fully integrated step-down DC-DC switching
+regulator. Through the PMBus interface, the device can monitor input/output
+voltages, output current and temperature.
+
+The driver is a client driver to the core PMBus driver. Please see
+Documentation/hwmon/pmbus.rst for details on PMBus client drivers.
+
+Sysfs entries
+-------------
+
+================= ========================================
+in1_label         "vin"
+in1_input         Measured input voltage
+in1_alarm         Input voltage alarm
+in2_label         "vout1"
+in2_input         Measured output voltage
+in2_alarm         Output voltage alarm
+curr1_label       "iout1"
+curr1_input       Measured output current
+curr1_alarm       Output current alarm
+temp1_input       Measured temperature
+temp1_alarm       Chip temperature alarm
+================= ========================================
diff --git a/MAINTAINERS b/MAINTAINERS
index 031c743e979521a92ed9ac67915c178ce31727bd..d6a6745e2dae29c3b8f80bbe61c54a2f5ecd9f47 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15585,6 +15585,8 @@ L:	linux-hwmon@vger.kernel.org
 S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
+F:	Documentation/hwmon/max20830.rst
+F:	drivers/hwmon/pmbus/max20830.c
 
 MAX2175 SDR TUNER DRIVER
 M:	Ramesh Shanmugasundaram <rashanmu@gmail.com>
diff --git a/drivers/hwmon/pmbus/Kconfig b/drivers/hwmon/pmbus/Kconfig
index 8f4bff375ecbc355f5ed3400855c2852ec2aa5ef..987705bf45b75b7b91ccc469247909f3c3f53d77 100644
--- a/drivers/hwmon/pmbus/Kconfig
+++ b/drivers/hwmon/pmbus/Kconfig
@@ -365,6 +365,15 @@ config SENSORS_MAX20751
 	  This driver can also be built as a module. If so, the module will
 	  be called max20751.
 
+config SENSORS_MAX20830
+	tristate "Analog Devices MAX20830"
+	help
+	  If you say yes here you get hardware monitoring support for Analog
+	  Devices MAX20830.
+
+	  This driver can also be built as a module. If so, the module will
+	  be called max20830.
+
 config SENSORS_MAX31785
 	tristate "Maxim MAX31785 and compatibles"
 	help
diff --git a/drivers/hwmon/pmbus/Makefile b/drivers/hwmon/pmbus/Makefile
index 7129b62bc00f8a2e98de14004997752a856dfda2..bc52f930e0825a902a0dd1c9e2b44f2e8d577c35 100644
--- a/drivers/hwmon/pmbus/Makefile
+++ b/drivers/hwmon/pmbus/Makefile
@@ -36,6 +36,7 @@ obj-$(CONFIG_SENSORS_MAX16601)	+= max16601.o
 obj-$(CONFIG_SENSORS_MAX17616)	+= max17616.o
 obj-$(CONFIG_SENSORS_MAX20730)	+= max20730.o
 obj-$(CONFIG_SENSORS_MAX20751)	+= max20751.o
+obj-$(CONFIG_SENSORS_MAX20830)	+= max20830.o
 obj-$(CONFIG_SENSORS_MAX31785)	+= max31785.o
 obj-$(CONFIG_SENSORS_MAX34440)	+= max34440.o
 obj-$(CONFIG_SENSORS_MAX8688)	+= max8688.o
diff --git a/drivers/hwmon/pmbus/max20830.c b/drivers/hwmon/pmbus/max20830.c
new file mode 100644
index 0000000000000000000000000000000000000000..b1c6985067d7c2c8eed1b00f81c8622d946acbcd
--- /dev/null
+++ b/drivers/hwmon/pmbus/max20830.c
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hardware monitoring driver for Analog Devices MAX20830
+ *
+ * Copyright (C) 2026 Analog Devices, Inc.
+ */
+
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include "pmbus.h"
+
+static struct pmbus_driver_info max20830_info = {
+	.pages = 1,
+	.format[PSC_VOLTAGE_IN] = linear,
+	.format[PSC_VOLTAGE_OUT] = linear,
+	.format[PSC_CURRENT_OUT] = linear,
+	.format[PSC_TEMPERATURE] = linear,
+	.func[0] = PMBUS_HAVE_VIN | PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT |
+		PMBUS_HAVE_TEMP |
+		PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT |
+		PMBUS_HAVE_STATUS_INPUT | PMBUS_HAVE_STATUS_TEMP,
+};
+
+static int max20830_probe(struct i2c_client *client)
+{
+	u8 buf[I2C_SMBUS_BLOCK_MAX + 1] = {};
+	u8 len;
+	int ret;
+
+	if (!i2c_check_functionality(client->adapter,
+				     I2C_FUNC_SMBUS_READ_I2C_BLOCK))
+		return -ENODEV;
+
+	/*
+	 * Use i2c_smbus_read_i2c_block_data() instead of
+	 * i2c_smbus_read_block_data() to support I2C controllers
+	 * which do not support SMBus block reads.
+	 */
+	ret = i2c_smbus_read_i2c_block_data(client, PMBUS_IC_DEVICE_ID,
+					    I2C_SMBUS_BLOCK_MAX, buf);
+	if (ret < 0)
+		return dev_err_probe(&client->dev, ret,
+				     "Failed to read IC_DEVICE_ID\n");
+
+	/* First byte is the block length (including itself). */
+	len = buf[0];
+	if (len != 9 || ret < len)
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "IC_DEVICE_ID length mismatch: reported %u, read %d\n",
+				     len, ret);
+
+	/* Data is at buf[1..8], so null terminator goes at buf[9]. */
+	buf[len] = '\0';
+	if (strncmp(buf + 1, "MAX20830", 8))
+		return dev_err_probe(&client->dev, -ENODEV,
+				     "Unsupported device: '%s'\n", buf + 1);
+
+	return pmbus_do_probe(client, &max20830_info);
+}
+
+static const struct i2c_device_id max20830_id[] = {
+	{"max20830"},
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, max20830_id);
+
+static const struct of_device_id max20830_of_match[] = {
+	{ .compatible = "adi,max20830" },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, max20830_of_match);
+
+static struct i2c_driver max20830_driver = {
+	.driver = {
+		.name = "max20830",
+		.of_match_table = max20830_of_match,
+	},
+	.probe = max20830_probe,
+	.id_table = max20830_id,
+};
+
+module_i2c_driver(max20830_driver);
+
+MODULE_AUTHOR("Alexis Czezar Torreno <alexisczezar.torreno@analog.com>");
+MODULE_DESCRIPTION("PMBus driver for Analog Devices MAX20830");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("PMBUS");

-- 
2.34.1


^ permalink raw reply related

* [PATCH v3 1/2] dt-bindings: hwmon: pmbus: add max20830
From: Alexis Czezar Torreno @ 2026-04-17  8:27 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc,
	Alexis Czezar Torreno
In-Reply-To: <20260417-dev_max20830-v3-0-0cb8d56067aa@analog.com>

Add device tree documentation for MAX20830 step-down DC-DC switching
regulator with PMBus interface.

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
---
 .../bindings/hwmon/pmbus/adi,max20830.yaml         | 66 ++++++++++++++++++++++
 MAINTAINERS                                        |  7 +++
 2 files changed, 73 insertions(+)

diff --git a/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..1625dd59417f1b3ca689a9c86ca266da913d1217
--- /dev/null
+++ b/Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
@@ -0,0 +1,66 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/hwmon/pmbus/adi,max20830.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices MAX20830 Step-Down Switching Regulator with PMBus
+
+maintainers:
+  - Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
+
+description: |
+  The MAX20830 is a fully integrated step-down DC-DC switching regulator with
+  PMBus interface. It provides 2.7V to 16V input, 0.4V to 5.8V adjustable
+  output, and up to 30A output current. It allows monitoring of input/output
+  voltage, output current and temperature through the PMBus serial interface.
+  Datasheet:
+    https://www.analog.com/en/products/max20830.html
+
+allOf:
+  - $ref: /schemas/regulator/regulator.yaml#
+
+properties:
+  compatible:
+    const: adi,max20830
+
+  reg:
+    maxItems: 1
+
+  vddh-supply:
+    description:
+      Phandle to the regulator that provides the VDDH power supply.
+
+  avdd-supply:
+    description:
+      Phandle to the regulator that provides the AVDD power supply.
+
+  ldoin-supply:
+    description:
+      Optional 2.5V to 5.5V LDO input supply.
+
+  pwr-good-gpios:
+    description:
+      GPIO connected to the power-good status output pin.
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - vddh-supply
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    i2c {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        regulator@30 {
+            compatible = "adi,max20830";
+            reg = <0x30>;
+            vddh-supply = <&vddh>;
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 0a3991c10ade20dd79cc7d1bf2a1d307ba6bd19d..031c743e979521a92ed9ac67915c178ce31727bd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -15579,6 +15579,13 @@ F:	Documentation/devicetree/bindings/hwmon/pmbus/adi,max17616.yaml
 F:	Documentation/hwmon/max17616.rst
 F:	drivers/hwmon/pmbus/max17616.c
 
+MAX20830 HARDWARE MONITOR DRIVER
+M:	Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
+L:	linux-hwmon@vger.kernel.org
+S:	Supported
+W:	https://ez.analog.com/linux-software-drivers
+F:	Documentation/devicetree/bindings/hwmon/pmbus/adi,max20830.yaml
+
 MAX2175 SDR TUNER DRIVER
 M:	Ramesh Shanmugasundaram <rashanmu@gmail.com>
 L:	linux-media@vger.kernel.org

-- 
2.34.1


^ permalink raw reply related

* [PATCH v3 0/2] Add support for MAX20830 PMBUS
From: Alexis Czezar Torreno @ 2026-04-17  8:27 UTC (permalink / raw)
  To: Guenter Roeck, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Jonathan Corbet, Shuah Khan
  Cc: linux-hwmon, devicetree, linux-kernel, linux-doc,
	Alexis Czezar Torreno

This series adds support for the Analog Devices MAX20830 step-down
switching regulator with PMBus interface.

The MAX20830 provides 2.7V to 16V input, 0.4V to 5.8V output, and up
to 30A output current. It supports monitoring of input/output voltage,
output current, and temperature via PMBus.

Datasheet: https://www.analog.com/en/products/max20830.html

Signed-off-by: Alexis Czezar Torreno <alexisczezar.torreno@analog.com>
---
Changes in v3:
- bindings:
  - Added pwr-good-gpios property
- driver:
  - Zero initialized IC_DEVICE_ID buffer
  - added ret < len for validation of actual read bytes
  - added comments clarifying block length format and null terminator placement
- Link to v2: https://lore.kernel.org/r/20260416-dev_max20830-v2-0-2c7d676dc0bd@analog.com

Changes in v2:
- bindings:
  - did not add interrupt, smbalert pin does not exist in device.
  - added allof with ref to regulator.yaml
  - changed additionalprop to unevaluatedprop
  - device node name in example changed to regulator
- driver: 
  - max20830.rst: Added missing in2_alarm
  - max20830.c: 
    - added missing quotes in MODULE_IMPORT_NS
    - added comment on why i2c_smbus_read_i2c_block_data is used
    - first byte of buffer used as length instead of the return value
    - "unsupported device" log now does not print first byte of buffer
- Link to v1: https://lore.kernel.org/r/20260414-dev_max20830-v1-0-210d3f82c571@analog.com

---
Alexis Czezar Torreno (2):
      dt-bindings: hwmon: pmbus: add max20830
      hwmon: (pmbus/max20830) add driver for max20830

 .../bindings/hwmon/pmbus/adi,max20830.yaml         | 66 ++++++++++++++++
 Documentation/hwmon/index.rst                      |  1 +
 Documentation/hwmon/max20830.rst                   | 49 ++++++++++++
 MAINTAINERS                                        |  9 +++
 drivers/hwmon/pmbus/Kconfig                        |  9 +++
 drivers/hwmon/pmbus/Makefile                       |  1 +
 drivers/hwmon/pmbus/max20830.c                     | 88 ++++++++++++++++++++++
 7 files changed, 223 insertions(+)
---
base-commit: fb447217c59a13b2fff22d94de2498c185cd9032
change-id: 20260414-dev_max20830-9460b92cf6aa

Best regards,
-- 
Alexis Czezar Torreno <alexisczezar.torreno@analog.com>


^ permalink raw reply

* [PATCH RFC v3 9/9] docs: iio: add documentation for ad9910 driver
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

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>
---
 Documentation/iio/ad9910.rst | 586 +++++++++++++++++++++++++++++++++++++++++++
 Documentation/iio/index.rst  |   1 +
 MAINTAINERS                  |   1 +
 3 files changed, 588 insertions(+)

diff --git a/Documentation/iio/ad9910.rst b/Documentation/iio/ad9910.rst
new file mode 100644
index 000000000000..a79819b5afe5
--- /dev/null
+++ b/Documentation/iio/ad9910.rst
@@ -0,0 +1,586 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+=============
+AD9910 driver
+=============
+
+DDS (Direct Digital Synthesizer) driver for the Analog Devices Inc. AD9910.
+The module name is ``ad9910``.
+
+* `AD9910 <https://www.analog.com/en/products/ad9910.html>`_
+
+The AD9910 is a 1 GSPS DDS with a 14-bit DAC, driven over SPI. The driver
+exposes the device through the IIO ``altvoltage`` channel type and supports
+five DDS operating modes: single tone, parallel port modulation, digital ramp
+generation (DRG), RAM playback and output shift keying (OSK). The device has
+8 hardware profiles, each capable of storing independent single tone and RAM
+playback parameters.
+
+
+Channel hierarchy
+=================
+
+The driver exposes the following IIO output channels, each identified by a
+unique channel number and a human-readable label:
+
+* ``out_altvoltage100``: ``phy``: Physical output: system clock and profile control
+
+  * ``out_altvoltage101``: ``profile[0]``: Single tone control for profile 0:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage102``: ``profile[1]``: Single tone control for profile 1:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage103``: ``profile[2]``: Single tone control for profile 2:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage104``: ``profile[3]``: Single tone control for profile 3:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage105``: ``profile[4]``: Single tone control for profile 4:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage106``: ``profile[5]``: Single tone control for profile 5:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage107``: ``profile[6]``: Single tone control for profile 6:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage108``: ``profile[7]``: Single tone control for profile 7:
+    frequency, phase, amplitude
+
+  * ``out_altvoltage110``: ``parallel_port``: Parallel port modulation: enable
+    and offset/scale parameters
+
+  * ``out_altvoltage120``: ``digital_ramp_generator``: DRG control: enable
+
+    * ``out_altvoltage121``: ``digital_ramp_up``: DRG ramp-up parameters:
+      no-dwell enable, limits, step sizes, ramp rate
+    * ``out_altvoltage122``: ``digital_ramp_down``: DRG ramp-down parameters:
+      no-dwell enable, limits, step sizes, ramp rate
+
+  * ``out_altvoltage130``: ``ram_control``: RAM playback: enable, frequency,
+    phase and sampling frequency for active profile. Other configurations are
+    provided through a firmware upload interface.
+
+  * ``out_altvoltage150``: ``output_shift_keying``: OSK: enable, amplitude
+    scale, ramp rate, auto/manual control
+
+The ``phy`` channel is the root of the hierarchy. Changing its
+``sampling_frequency`` reconfigures the system clock (SYSCLK) which affects all
+other channels.
+
+All mode-specific channels (single-tone, parallel port, DRG, RAM, OSK) have an
+``enable`` attribute that turns the mode on/off.
+
+DDS modes
+=========
+
+The AD9910 supports multiple modes of operation that can be configured
+independently or in combination. Such modes and their corresponding IIO channels
+are described in this section. The following tables are extracted from the
+AD9910 datasheet and summarizes the control parameters for each mode and their
+priority when multiple sources are enabled simultaneously:
+
+.. flat-table:: DDS Frequency Control
+   :header-rows: 1
+
+   * - Priority
+     - Data Source
+     - Conditions
+
+   * - Highest Priority
+     - RAM
+     - RAM enabled and data destination is frequency
+
+   * -
+     - DRG
+     - DRG enabled and data destination is frequency
+
+   * -
+     - Parallel data and FTW (frequency_offset)
+     - Parallel data port enabled and data destination is frequency
+
+   * -
+     - FTW (frequency)
+     - RAM enabled and data destination is not frequency
+
+   * -
+     - FTW (frequency) in single tone channel for the active profile
+     - DRG enabled and data destination is not frequency
+
+   * -
+     - FTW (frequency) in single tone channel for the active profile
+     - Parallel data port enabled and data destination is not frequency
+
+   * - Lowest Priority
+     - FTW (frequency) in single tone channel for the active profile
+     - None
+
+.. flat-table:: DDS Phase Control
+   :header-rows: 1
+
+   * - Priority
+     - Data Source
+     - Conditions
+
+   * - Highest Priority
+     - RAM
+     - RAM enabled and data destination is phase or polar
+
+   * -
+     - DRG
+     - DRG enabled and data destination is phase
+
+   * -
+     - Parallel data port
+     - Parallel data port enabled and data destination is phase
+
+   * -
+     - Parallel data port and POW register LSBs (phase_offset)
+     - Parallel data port enabled and data destination is polar
+
+   * -
+     - POW (phase)
+     - RAM enabled and destination is not phase nor polar
+
+   * -
+     - POW (phase) in single tone channel for the active profile
+     - DRG enabled and data destination is not phase
+
+   * -
+     - POW (phase) in single tone channel for the active profile
+     - Parallel data port enabled and data destination is not phase nor polar
+
+   * - Lowest Priority
+     - POW (phase) in single tone channel for the active profile
+     - None
+
+.. flat-table:: DDS Amplitude Control
+   :header-rows: 1
+
+   * - Priority
+     - Data Source
+     - Conditions
+
+   * - Highest Priority
+     - OSK generator
+     - OSK enabled (auto mode)
+
+   * -
+     - ASF register
+     - OSK enabled (manual mode)
+
+   * -
+     - RAM
+     - RAM enabled and data destination is amplitude or polar
+
+   * -
+     - DRG
+     - DRG enabled and data destination is amplitude
+
+   * -
+     - Parallel data port
+     - Parallel data port enabled and data destination is amplitude
+
+   * -
+     - Parallel data port and ASF register LSBs (scale_offset)
+     - Parallel data port enabled and data destination is polar
+
+   * - Lowest Priority
+     - ASF (scale) in single tone channel for the active profile
+     - (Amplitude scale is already enabled by default)
+
+Single tone mode
+----------------
+
+Single tone is the baseline operating mode. The ``profile[Y]`` channels
+provides enable, frequency, phase and amplitude control:
+
+.. flat-table::
+   :header-rows: 1
+
+   * - Attribute
+     - Unit
+     - Description
+
+   * - ``en``
+     - boolean
+     - Enable/disable profile Y. Only one profile can be active at a
+       time. Then enabling a profile disables the current active profile.
+       Disabling an active profile enables the next profile in ascending order,
+       wrapping around from 7 to 0.
+
+   * - ``frequency``
+     - Hz
+     - Output frequency. Range [0, SYSCLK/2). Stored in the profile's frequency
+       tuning word (FTW).
+
+   * - ``phase``
+     - rad
+     - Phase offset. Range [0, 2*pi). Stored in the profile's phase offset word
+       (POW).
+
+   * - ``scale``
+     - fractional
+     - Amplitude scale factor. Range [0, 1]. Stored in the profile's amplitude
+       scale factor (ASF).
+
+Profile switching is allowed while RAM mode is enabled. In that case single tone
+parameters are stored in a shadow register and are not written to hardware until
+RAM mode is disabled.
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Set the active profile to 2 and configure a 100 MHz tone:
+
+.. code-block:: bash
+
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage103_en
+	echo 100000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage103_frequency
+	echo 0.5 > /sys/bus/iio/devices/iio:device0/out_altvoltage103_scale
+	echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage103_phase
+
+Read back the current single tone frequency:
+
+.. code-block:: bash
+
+	cat /sys/bus/iio/devices/iio:device0/out_altvoltage103_frequency
+
+Parallel port mode
+------------------
+
+When enabled, the parallel port allows real-time modulation of DDS parameters
+through a 16-bit external data bus.
+
+.. flat-table::
+   :header-rows: 1
+
+   * - Attribute
+     - Unit
+     - Description
+
+   * - ``en``
+     - boolean
+     - Enable/disable the parallel data port.
+
+   * - ``frequency_scale``
+     - power-of-2
+     - FM gain multiplier applied to 16-bit parallel input. Range [1, 32768],
+       must be a power of 2.
+
+   * - ``frequency_offset``
+     - Hz
+     - Base FTW to which scaled parallel data is added. Range [0, SYSCLK/2).
+
+   * - ``phase_offset``
+     - rad
+     - Base phase for polar modulation. Lower 8 bits of POW register.
+       Range [0, 2*pi/256).
+
+   * - ``scale_offset``
+     - fractional
+     - Base amplitude for polar modulation. Lower 6 bits of ASF register.
+       Range [0, 1/256).
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Enable parallel port with a frequency scale of 16 and a 50 MHz offset:
+
+.. code-block:: bash
+
+	echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_frequency_scale
+	echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_frequency_offset
+  echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage110_en
+
+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
+       only, ramp-down only or bidirectional with dwell at the limits.
+
+   * - ``frequency``
+     - Hz
+     - Frequency ramp limit. Range [0, SYSCLK/2).
+
+   * - ``phase``
+     - rad
+     - Phase ramp limit. Range [0, 2*pi).
+
+   * - ``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).
+
+   * - ``phase_step``
+     - rad
+     - Per-tick phase increment/decrement. Range [0, 2*pi).
+
+   * - ``scale_step``
+     - fractional
+     - Per-tick amplitude scale increment/decrement. Range [0, 1).
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Configure a frequency sweep from 40 MHz to 60 MHz at a 1 kHz step:
+
+.. code-block:: bash
+
+	# Enable both no-dwell modes for a bidirectional ramp
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_en
+  echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_en
+
+	# Set ramp limits
+	echo 60000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_frequency
+	echo 40000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_frequency
+
+	# Set ramp step size to 1 kHz
+	echo 1000 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_frequency_step
+	echo 1000 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_frequency_step
+
+	# Set ramp rate at 25 MHz
+	echo 25000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage121_sampling_frequency
+  echo 25000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage122_sampling_frequency
+
+	# Enable the DRG
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_en
+
+RAM mode
+--------
+
+The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform data
+and played back to modulate frequency, phase, amplitude, or polar (phase +
+amplitude) parameters.
+
+RAM control channel attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. flat-table::
+   :header-rows: 1
+
+   * - Attribute
+     - Unit
+     - Description
+
+   * - ``en``
+     - boolean
+     - Enable/disable RAM playback. Toggling swaps profile registers between
+       single tone and RAM configurations across all 8 profiles.
+
+   * - ``frequency``
+     - Hz
+     - Frequency tuning word used as the single tone frequency when
+       RAM destination is not ``frequency``. Range [0, SYSCLK/2).
+
+   * - ``phase``
+     - rad
+     - Phase offset word used as the single tone phase when RAM destination
+       is not ``phase``. Range [0, 2*pi).
+
+   * - ``sampling_frequency``
+     - Hz
+     - RAM playback step rate of the active profile, which controls how fast the
+       address counter advances: SYSCLK / (4 * step_rate).
+
+Loading RAM data
+^^^^^^^^^^^^^^^^
+
+RAM data is loaded through the firmware upload framework. The driver registers
+a firmware upload sysfs entry named ``iio_deviceX:ram``. The FW data follows
+a simple binary format:
+
+- 72-byte header:
+  - 4-byte big-endian word count: number of 32-bit words to be loaded (0-1024)
+  - 4-byte big-endian CFR1 value: configuration for the CFR1 register. Only
+    bits relevant to RAM mode (data destination and internal profile control)
+    are considered. Other bits are ignored and have no effect.
+    - Bits [30:29]: RAM data destination:
+      - 00: frequency
+      - 01: phase
+      - 10: amplitude
+      - 11: polar
+    - Bits [20:17]: Internal profile control (see Table 14 of the datasheet).
+  - 8 sets of 8-byte big-endian profile data for profiles 0-7. Each set contains:
+    - Bits [55:40]: Address step rate value
+    - Bits [39:30]: End address for the profile
+    - Bits [23:14]: Start address for the profile
+    - Bit [5]: no-dwell high for ramp-up mode
+    - Bit [3]: zero-crossing for direct-switch mode
+    - Bits [2:0]: operating mode:
+      - 000: direct switch
+      - 001: ramp-up
+      - 010: bidirectional
+      - 011: bidirectional continuous
+      - 100: ramp-up continuous
+- Followed by the specified number of 32-bit big-endian data words.
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Configure RAM mode with frequency destination and load a waveform:
+
+.. code-block:: bash
+
+	# Load RAM data via firmware upload
+	echo 1 > /sys/class/firmware/iio\:device0\:ram/loading
+	cat waveform.bin > /sys/class/firmware/iio\:device0\:ram/data
+	echo 0 > /sys/class/firmware/iio\:device0\:ram/loading
+
+	# Enable RAM mode
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage130_en
+
+Output shift keying (OSK)
+-------------------------
+
+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.
+
+   * - ``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.
+
+Usage examples
+^^^^^^^^^^^^^^
+
+Enable OSK with automatic ramping:
+
+.. code-block:: bash
+
+	# Set ramp rate
+	echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_sampling_frequency
+
+	# Enable automatic OSK with step size
+	echo 0.000244 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale_step
+
+	# Enable OSK
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en
+
+Enable manual pin-controlled OSK:
+
+.. code-block:: bash
+
+	# Enable manual pin control
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_pinctrl_en
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en
+
+	# Set target amplitude to full scale
+	echo 1.0 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale
+
+Physical channel
+================
+
+The ``phy`` channel provides device-level control:
+
+.. flat-table::
+   :header-rows: 1
+
+   * - Attribute
+     - Unit
+     - Description
+
+   * - ``sampling_frequency``
+     - Hz
+     - System clock (SYSCLK) frequency. With PLL enabled, configures the PLL
+       multiplier (range 420-1000 MHz). Without PLL, ref clock can only be
+       divided by 2.
+
+   * - ``powerdown``
+     - boolean
+     - Software power-down. Writing 1 powers down the digital core, DAC,
+       reference clock input and auxiliary DAC simultaneously.
+
+Usage examples
+--------------
+
+Set the system clock to 1 GHz:
+
+.. code-block:: bash
+
+	echo 1000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_sampling_frequency
+
+Read current system clock frequency:
+
+.. code-block:: bash
+
+	cat /sys/bus/iio/devices/iio:device0/out_altvoltage100_sampling_frequency
+
+Power down the device:
+
+.. code-block:: bash
+
+	echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage100_powerdown
diff --git a/Documentation/iio/index.rst b/Documentation/iio/index.rst
index ba3e609c6a13..55cb1ce84ba8 100644
--- a/Documentation/iio/index.rst
+++ b/Documentation/iio/index.rst
@@ -29,6 +29,7 @@ Industrial I/O Kernel Drivers
    ad7606
    ad7625
    ad7944
+   ad9910
    ade9000
    adis16475
    adis16480
diff --git a/MAINTAINERS b/MAINTAINERS
index edd87ee7da5f..14e4272357ce 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1637,6 +1637,7 @@ S:	Supported
 W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
+F:	Documentation/iio/ad9910.rst
 F:	drivers/iio/frequency/ad9910.c
 
 ANALOG DEVICES INC MAX22007 DRIVER

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 8/9] Documentation: ABI: testing: add docs for ad9910 sysfs entries
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add custom ABI documentation file for the DDS AD9910 with sysfs entries to
control Parallel Port, Digital Ramp Generator and OSK parameters.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 .../ABI/testing/sysfs-bus-iio-frequency-ad9910     | 62 ++++++++++++++++++++++
 MAINTAINERS                                        |  1 +
 2 files changed, 63 insertions(+)

diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
new file mode 100644
index 000000000000..fabc2a5417d1
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910
@@ -0,0 +1,62 @@
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_offset
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		For a channel that allows frequency control through buffers, this
+		represents the base frequency value in Hz. The actual output frequency
+		is a result with the sum of this value.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_scale
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		For a channel that allows frequency control through buffers, this
+		represents the frequency modulation gain. This value multiplies the
+		buffer input sample value before it is added to a frequency offset.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_offset
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		For a channel that allows phase control through buffers, this
+		represents the base phase value in radians. The actual output phase
+		is a result with the sum of this value.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_offset
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		For a channel that allows amplitude control through buffers, this
+		represents the value for a base amplitude scale. The actual output
+		amplitude scale is a result with the sum of this value.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_step
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		Channels that sweep frequency values at determined rate use this value
+		to set the frequency step size in Hz.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_step
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		Channels that sweep phase values at determined rate use this value
+		to set the phase step size in radians.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_step
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		Channels that sweep amplitude values at determined rate use this value
+		to set the amplite scale step.
+
+What:		/sys/bus/iio/devices/iio:deviceX/out_altvoltageY_pinctrl_en
+KernelVersion:
+Contact:	linux-iio@vger.kernel.org
+Description:
+		Channels that supports pin control to enable/disable its output use
+		this attribute to set the pin control mode. When set to 1, the output
+		state is controlled by a physical pin, and the channel is enabled when
+		the pin is active. When set to 0, only software control is used to
+		enable/disable the channel output.
diff --git a/MAINTAINERS b/MAINTAINERS
index 6403439b530d..edd87ee7da5f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1635,6 +1635,7 @@ 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/ABI/testing/sysfs-bus-iio-frequency-ad9910
 F:	Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
 F:	drivers/iio/frequency/ad9910.c
 

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 7/9] iio: frequency: ad9910: add channel labels
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add human-readable labels for all AD9910 IIO channels via the read_label
callback.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/ad9910.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index e43df6265fd4..50d97ce937c7 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -1816,10 +1816,36 @@ static const struct fw_upload_ops ad9910_ram_fwu_ops = {
 	.cancel = ad9910_ram_fwu_cancel
 };
 
+static const char * const ad9910_channel_str[] = {
+	[AD9910_CHAN_IDX_PHY] = "phy",
+	[AD9910_CHAN_IDX_PROFILE_0] = "profile[0]",
+	[AD9910_CHAN_IDX_PROFILE_1] = "profile[1]",
+	[AD9910_CHAN_IDX_PROFILE_2] = "profile[2]",
+	[AD9910_CHAN_IDX_PROFILE_3] = "profile[3]",
+	[AD9910_CHAN_IDX_PROFILE_4] = "profile[4]",
+	[AD9910_CHAN_IDX_PROFILE_5] = "profile[5]",
+	[AD9910_CHAN_IDX_PROFILE_6] = "profile[6]",
+	[AD9910_CHAN_IDX_PROFILE_7] = "profile[7]",
+	[AD9910_CHAN_IDX_PARALLEL_PORT] = "parallel_port",
+	[AD9910_CHAN_IDX_DRG] = "digital_ramp_generator",
+	[AD9910_CHAN_IDX_DRG_RAMP_UP] = "digital_ramp_up",
+	[AD9910_CHAN_IDX_DRG_RAMP_DOWN] = "digital_ramp_down",
+	[AD9910_CHAN_IDX_RAM] = "ram_control",
+	[AD9910_CHAN_IDX_OSK] = "output_shift_keying",
+};
+
+static int ad9910_read_label(struct iio_dev *indio_dev,
+			     struct iio_chan_spec const *chan,
+			     char *label)
+{
+	return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]);
+}
+
 static const struct iio_info ad9910_info = {
 	.read_raw = ad9910_read_raw,
 	.write_raw = ad9910_write_raw,
 	.write_raw_get_fmt = ad9910_write_raw_get_fmt,
+	.read_label = ad9910_read_label,
 	.debugfs_reg_access = &ad9910_debugfs_reg_access,
 };
 

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 6/9] iio: frequency: ad9910: add output shift keying support
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add OSK channel with amplitude envelope control capabilities:
- OSK enable/disable via IIO_CHAN_INFO_ENABLE;
- Amplitude ramp rate control via IIO_CHAN_INFO_SAMP_FREQ;
- Amplitude scale readback via IIO_CHAN_INFO_SCALE (ASF register);
- Manual/external pin control via pinctrl_en ext_info attribute;
- Automatic OSK step size configuration via scale_increment ext_info;
  attribute with selectable step sizes (61, 122, 244, 488 micro-units)

The ASF register is initialized with a default amplitude ramp rate during
device setup to ensure valid readback.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/ad9910.c | 153 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 152 insertions(+), 1 deletion(-)

diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 7880beaa0bc4..e43df6265fd4 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -235,6 +235,7 @@
  * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
  * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
  * @AD9910_CHANNEL_RAM: RAM control output channel
+ * @AD9910_CHANNEL_OSK: Output Shift Keying output channel
  */
 enum ad9910_channel {
 	AD9910_CHANNEL_PHY = 100,
@@ -251,6 +252,7 @@ enum ad9910_channel {
 	AD9910_CHANNEL_DRG_RAMP_UP = 121,
 	AD9910_CHANNEL_DRG_RAMP_DOWN = 122,
 	AD9910_CHANNEL_RAM = 130,
+	AD9910_CHANNEL_OSK = 140,
 };
 
 /**
@@ -316,6 +318,8 @@ enum {
 	AD9910_DRG_FREQ_STEP,
 	AD9910_DRG_PHASE_STEP,
 	AD9910_DRG_AMP_STEP,
+	AD9910_OSK_MANUAL_EXTCTL,
+	AD9910_OSK_AUTO_STEP,
 };
 
 struct ad9910_data {
@@ -611,6 +615,10 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
 		val = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
 				    st->reg[AD9910_REG_CFR2].val32));
 		break;
+	case AD9910_OSK_MANUAL_EXTCTL:
+		val = FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+				st->reg[AD9910_REG_CFR1].val32);
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -650,6 +658,12 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
 					  AD9910_CFR2_FM_GAIN_MSK,
 					  val32, true);
 		break;
+	case AD9910_OSK_MANUAL_EXTCTL:
+		val32 = val32 ? AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK : 0;
+		ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+					  AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK,
+					  val32, true);
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -889,6 +903,84 @@ static ssize_t ad9910_drg_attrs_write(struct iio_dev *indio_dev,
 	return ret ?: len;
 }
 
+static const u16 ad9910_osk_ustep[] = {
+	0, 61, 122, 244, 488,
+};
+
+static ssize_t ad9910_osk_attrs_read(struct iio_dev *indio_dev,
+				     uintptr_t private,
+				     const struct iio_chan_spec *chan,
+				     char *buf)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	int vals[2];
+	bool auto_en;
+	u32 raw_val;
+
+	guard(mutex)(&st->lock);
+
+	switch (private) {
+	case AD9910_OSK_AUTO_STEP:
+		auto_en = FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK,
+				    st->reg[AD9910_REG_CFR1].val32);
+		raw_val = FIELD_GET(AD9910_ASF_STEP_SIZE_MSK,
+				    st->reg[AD9910_REG_ASF].val32);
+		vals[0] = 0;
+		vals[1] = auto_en ? ad9910_osk_ustep[raw_val + 1] : 0;
+
+		return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, 2, vals);
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t ad9910_osk_attrs_write(struct iio_dev *indio_dev,
+				      uintptr_t private,
+				      const struct iio_chan_spec *chan,
+				      const char *buf, size_t len)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	int val, val2;
+	int ret;
+	u32 raw_val;
+
+	ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (private) {
+	case AD9910_OSK_AUTO_STEP:
+		if (val != 0)
+			return -EINVAL;
+
+		raw_val = find_closest(val2, ad9910_osk_ustep,
+				       ARRAY_SIZE(ad9910_osk_ustep));
+		if (raw_val) {
+			/* set OSK step and get automatic OSK enabled */
+			raw_val = FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK,
+					     raw_val - 1);
+			ret = ad9910_reg32_update(st, AD9910_REG_ASF,
+						  AD9910_ASF_STEP_SIZE_MSK,
+						  raw_val, true);
+			if (ret)
+				return ret;
+
+			raw_val = AD9910_CFR1_SELECT_AUTO_OSK_MSK;
+		}
+
+		ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+					  AD9910_CFR1_SELECT_AUTO_OSK_MSK,
+					  raw_val, true);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret ?: len;
+}
+
 #define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \
 	.name = _name, \
 	.read = ad9910_ ## _fn_desc ## _read, \
@@ -906,6 +998,9 @@ static ssize_t ad9910_drg_attrs_write(struct iio_dev *indio_dev,
 #define AD9910_DRG_EXT_INFO(_name, _ident) \
 	AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, drg_attrs)
 
+#define AD9910_OSK_EXT_INFO(_name, _ident) \
+	AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, osk_attrs)
+
 static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
 	AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
 	{ }
@@ -926,6 +1021,12 @@ static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
 	{ }
 };
 
+static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] = {
+	AD9910_EXT_INFO("pinctrl_en", AD9910_OSK_MANUAL_EXTCTL, IIO_SEPARATE),
+	AD9910_OSK_EXT_INFO("scale_step", AD9910_OSK_AUTO_STEP),
+	{ }
+};
+
 #define AD9910_PROFILE_CHAN(idx) {				\
 	.type = IIO_ALTVOLTAGE,					\
 	.indexed = 1,						\
@@ -1016,6 +1117,18 @@ static const struct iio_chan_spec ad9910_channels[] = {
 				      BIT(IIO_CHAN_INFO_PHASE) |
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
 	},
+	[AD9910_CHAN_IDX_OSK] = {
+		.type = IIO_ALTVOLTAGE,
+		.indexed = 1,
+		.output = 1,
+		.channel = AD9910_CHANNEL_OSK,
+		.address = AD9910_CHAN_IDX_OSK,
+		.scan_index = -1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.ext_info = ad9910_osk_ext_info,
+	},
 };
 
 static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -1055,6 +1168,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			*val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
 					 st->reg[AD9910_REG_CFR1].val32);
 			break;
+		case AD9910_CHANNEL_OSK:
+			*val = FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK,
+					 st->reg[AD9910_REG_CFR1].val32);
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -1136,6 +1253,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			*val = 0;
 			*val2 = tmp64 * NANO >> 32;
 			return IIO_VAL_INT_PLUS_NANO;
+		case AD9910_CHANNEL_OSK:
+			tmp64 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK,
+					  st->reg[AD9910_REG_ASF].val32);
+			*val = 0;
+			*val2 = tmp64 * MICRO >> 14;
+			return IIO_VAL_INT_PLUS_MICRO;
 		default:
 			return -EINVAL;
 		}
@@ -1156,6 +1279,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
 					  ad9910_ram_profile_val(st));
 			break;
+		case AD9910_CHANNEL_OSK:
+			tmp32 = FIELD_GET(AD9910_ASF_RAMP_RATE_MSK,
+					  st->reg[AD9910_REG_ASF].val32);
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -1234,6 +1361,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg32_update(st, AD9910_REG_CFR1,
 						   AD9910_CFR1_RAM_ENABLE_MSK,
 						   tmp32, true);
+		case AD9910_CHANNEL_OSK:
+			tmp32 = FIELD_PREP(AD9910_CFR1_OSK_ENABLE_MSK, val);
+			return ad9910_reg32_update(st, AD9910_REG_CFR1,
+						   AD9910_CFR1_OSK_ENABLE_MSK,
+						   tmp32, true);
 		default:
 			return -EINVAL;
 		}
@@ -1403,6 +1535,14 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
 						   AD9910_DRG_LIMIT_LOWER_MSK,
 						   tmp64, true);
+		case AD9910_CHANNEL_OSK:
+			tmp64 = ((u64)val * MICRO + val2) << 14;
+			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
+			tmp32 = min(tmp64, AD9910_ASF_MAX);
+			tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_MSK, tmp32);
+			return ad9910_reg32_update(st, AD9910_REG_ASF,
+						   AD9910_ASF_SCALE_FACTOR_MSK,
+						   tmp32, true);
 		default:
 			return -EINVAL;
 		}
@@ -1439,7 +1579,12 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
 						   AD9910_PROFILE_RAM_STEP_RATE_MSK,
 						   tmp64, true);
-
+			break;
+		case AD9910_CHANNEL_OSK:
+			return ad9910_reg32_update(st, AD9910_REG_ASF,
+						   AD9910_ASF_RAMP_RATE_MSK,
+						   FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, tmp32),
+						   true);
 		default:
 			return -EINVAL;
 		}
@@ -1769,6 +1914,12 @@ static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
 		return ret;
 
 	/* configure step rate with default values */
+	ret = ad9910_reg32_write(st, AD9910_REG_ASF,
+				 FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1),
+				 false);
+	if (ret)
+		return ret;
+
 	reg32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) |
 		FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1);
 	ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, reg32, false);

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 5/9] iio: frequency: ad9910: add RAM mode support
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add RAM control channel, which includes:
- RAM data loading via firmware upload interface;
- Per-profile configuration and DDS core parameter destination as firmware
  metadata;
- Profile switching relying on profile channels;
- Sampling frequency control of the active profile;
- ram-enable-aware read/write paths that redirect single tone
  frequency/phase/amplitude access through reg_profile cache when RAM is
  active;

When RAM is enabled, the DDS profile parameters (frequency, phase,
amplitude) for the single tone mode are sourced from a shadow register
cache (reg_profile[]) since the profile registers are repurposed for RAM
control.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/Kconfig  |   2 +
 drivers/iio/frequency/ad9910.c | 328 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 324 insertions(+), 6 deletions(-)

diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig
index 180e74f62d11..a5b2e5cb5269 100644
--- a/drivers/iio/frequency/Kconfig
+++ b/drivers/iio/frequency/Kconfig
@@ -29,6 +29,8 @@ config AD9910
 	tristate "Analog Devices AD9910 Direct Digital Synthesizer"
 	depends on SPI
 	depends on GPIOLIB
+	select FW_LOADER
+	select FW_UPLOAD
 	help
 	  Say yes here to build support for Analog Devices AD9910
 	  1 GSPS, 14-Bit DDS with integrated DAC.
diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index c9ec677cd63a..7880beaa0bc4 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -8,9 +8,11 @@
 #include <linux/array_size.h>
 #include <linux/bitfield.h>
 #include <linux/clk.h>
+#include <linux/debugfs.h>
 #include <linux/delay.h>
 #include <linux/device/devres.h>
 #include <linux/err.h>
+#include <linux/firmware.h>
 #include <linux/gpio/consumer.h>
 #include <linux/log2.h>
 #include <linux/math64.h>
@@ -149,6 +151,15 @@
 #define AD9910_PROFILE_ST_POW_MSK		GENMASK_ULL(47, 32)
 #define AD9910_PROFILE_ST_FTW_MSK		GENMASK_ULL(31, 0)
 
+/* Profile Register Format (RAM Mode) */
+#define AD9910_PROFILE_RAM_OPEN_MSK		GENMASK_ULL(61, 57)
+#define AD9910_PROFILE_RAM_STEP_RATE_MSK	GENMASK_ULL(55, 40)
+#define AD9910_PROFILE_RAM_END_ADDR_MSK		GENMASK_ULL(39, 30)
+#define AD9910_PROFILE_RAM_START_ADDR_MSK	GENMASK_ULL(23, 14)
+#define AD9910_PROFILE_RAM_NO_DWELL_HIGH_MSK	BIT_ULL(5)
+#define AD9910_PROFILE_RAM_ZERO_CROSSING_MSK	BIT_ULL(3)
+#define AD9910_PROFILE_RAM_MODE_CONTROL_MSK	GENMASK_ULL(2, 0)
+
 /* Device constants */
 #define AD9910_PI_NANORAD		3141592653UL
 
@@ -162,6 +173,15 @@
 #define AD9910_STEP_RATE_MAX		GENMASK(15, 0)
 #define AD9910_NUM_PROFILES		8
 
+#define AD9910_RAM_FW_MAGIC		0x00AD9910
+#define AD9910_RAM_SIZE_MAX_WORDS	1024
+#define AD9910_RAM_WORD_SIZE		sizeof(u32)
+#define AD9910_RAM_SIZE_MAX_BYTES	(AD9910_RAM_SIZE_MAX_WORDS * AD9910_RAM_WORD_SIZE)
+#define AD9910_RAM_ADDR_MAX		(AD9910_RAM_SIZE_MAX_WORDS - 1)
+
+#define AD9910_RAM_ENABLED(st)		\
+	FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, (st)->reg[AD9910_REG_CFR1].val32)
+
 /* PLL constants */
 #define AD9910_PLL_MIN_N		12
 #define AD9910_PLL_MAX_N		127
@@ -193,7 +213,7 @@
 #define AD9910_REFDIV2_MAX_FREQ_HZ	(1900 * HZ_PER_MHZ)
 
 #define AD9910_SPI_DATA_IDX		1
-#define AD9910_SPI_DATA_LEN_MAX		sizeof(__be64)
+#define AD9910_SPI_DATA_LEN_MAX		AD9910_RAM_SIZE_MAX_BYTES
 #define AD9910_SPI_MESSAGE_LEN_MAX	(AD9910_SPI_DATA_IDX + AD9910_SPI_DATA_LEN_MAX)
 #define AD9910_SPI_READ_MSK		BIT(7)
 #define AD9910_SPI_ADDR_MSK		GENMASK(4, 0)
@@ -214,6 +234,7 @@
  * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel
  * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
  * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
+ * @AD9910_CHANNEL_RAM: RAM control output channel
  */
 enum ad9910_channel {
 	AD9910_CHANNEL_PHY = 100,
@@ -229,6 +250,7 @@ enum ad9910_channel {
 	AD9910_CHANNEL_DRG = 120,
 	AD9910_CHANNEL_DRG_RAMP_UP = 121,
 	AD9910_CHANNEL_DRG_RAMP_DOWN = 122,
+	AD9910_CHANNEL_RAM = 130,
 };
 
 /**
@@ -246,6 +268,27 @@ enum ad9910_destination {
 	AD9910_DEST_POLAR,
 };
 
+/**
+ * struct ad9910_ram_fw - AD9910 RAM firmware format
+ * @magic:	Magic number for RAM firmware validation
+ * @cfr1:	Value of CFR1 register to be configured (not all fields are
+ *		used, but this is included here for convenience)
+ * @profiles:	Array of RAM profile configurations
+ * @reserved:	Reserved field for future use, should be set to 0
+ * @wcount:	Number of RAM words to be written
+ * @words:	Array of RAM words to be written. Data pattern should be set in
+ *		reverse order and wcount specifies the number of words in this
+ *		array
+ */
+struct ad9910_ram_fw {
+	__be32 magic;
+	__be32 cfr1;
+	__be64 profiles[AD9910_NUM_PROFILES];
+	__be32 reserved;
+	__be32 wcount;
+	__be32 words[] __counted_by_be(wcount);
+} __packed;
+
 enum {
 	AD9910_CHAN_IDX_PHY,
 	AD9910_CHAN_IDX_PROFILE_0,
@@ -293,6 +336,7 @@ union ad9910_reg {
 struct ad9910_state {
 	struct spi_device *spi;
 	struct clk *refclk;
+	struct fw_upload *ram_fwu;
 
 	struct gpio_desc *gpio_pwdown;
 	struct gpio_desc *gpio_update;
@@ -301,12 +345,22 @@ struct ad9910_state {
 	/* cached registers */
 	union ad9910_reg reg[AD9910_REG_NUM_CACHED];
 
+	/*
+	 * alternate profile registers used to store RAM profile settings when
+	 * RAM mode is disabled and Single Tone profile settings when RAM mode
+	 * is enabled.
+	 */
+	u64 reg_profile[AD9910_NUM_PROFILES];
+
 	/* Lock for accessing device registers and state variables */
 	struct mutex lock;
 
 	struct ad9910_data data;
 	u8 profile;
 
+	bool ram_fwu_cancel;
+	char ram_fwu_name[20];
+
 	union {
 		__be64 be64;
 		__be32 be32;
@@ -335,6 +389,22 @@ struct ad9910_state {
 	mul_u64_add_u64_div_u64(input, scale, _tmp >> 1, _tmp);	\
 })
 
+static inline u64 ad9910_ram_profile_val(struct ad9910_state *st)
+{
+	if (AD9910_RAM_ENABLED(st))
+		return st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+	else
+		return st->reg_profile[st->profile];
+}
+
+static inline u64 ad9910_st_profile_val(struct ad9910_state *st, u8 profile)
+{
+	if (AD9910_RAM_ENABLED(st))
+		return st->reg_profile[profile];
+	else
+		return st->reg[AD9910_REG_PROFILE(profile)].val64;
+}
+
 static int ad9910_io_update(struct ad9910_state *st)
 {
 	if (st->gpio_update) {
@@ -934,6 +1004,18 @@ static const struct iio_chan_spec ad9910_channels[] = {
 				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
 		.ext_info = ad9910_drg_ramp_ext_info,
 	},
+	[AD9910_CHAN_IDX_RAM] = {
+		.type = IIO_ALTVOLTAGE,
+		.indexed = 1,
+		.output = 1,
+		.channel = AD9910_CHANNEL_RAM,
+		.address = AD9910_CHAN_IDX_RAM,
+		.scan_index = -1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+				      BIT(IIO_CHAN_INFO_FREQUENCY) |
+				      BIT(IIO_CHAN_INFO_PHASE) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+	},
 };
 
 static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -969,6 +1051,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			*val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
 					 st->reg[AD9910_REG_CFR2].val32);
 			break;
+		case AD9910_CHANNEL_RAM:
+			*val = FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK,
+					 st->reg[AD9910_REG_CFR1].val32);
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -978,7 +1064,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
 			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
 			tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
-					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
+					  ad9910_st_profile_val(st, tmp32));
 			break;
 		case AD9910_CHANNEL_DRG_RAMP_UP:
 			tmp32 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
@@ -988,6 +1074,9 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			tmp32 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
 					  st->reg[AD9910_REG_DRG_LIMIT].val64);
 			break;
+		case AD9910_CHANNEL_RAM:
+			tmp32 = st->reg[AD9910_REG_FTW].val32;
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -1000,7 +1089,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
 			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
 			tmp64 = FIELD_GET(AD9910_PROFILE_ST_POW_MSK,
-					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
+					  ad9910_st_profile_val(st, tmp32));
 			tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
 			*val = tmp32 / MICRO;
 			*val2 = tmp32 % MICRO;
@@ -1017,6 +1106,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			tmp64 = (tmp64 * AD9910_PI_NANORAD) >> 31;
 			*val = div_u64_rem(tmp64, NANO, val2);
 			return IIO_VAL_INT_PLUS_NANO;
+		case AD9910_CHANNEL_RAM:
+			tmp64 = st->reg[AD9910_REG_POW].val16;
+			tmp32 = (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+			*val = tmp32 / MICRO;
+			*val2 = tmp32 % MICRO;
+			return IIO_VAL_INT_PLUS_MICRO;
 		default:
 			return -EINVAL;
 		}
@@ -1025,7 +1120,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
 			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
 			tmp64 = FIELD_GET(AD9910_PROFILE_ST_ASF_MSK,
-					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
+					  ad9910_st_profile_val(st, tmp32));
 			*val = 0;
 			*val2 = tmp64 * MICRO >> 14;
 			return IIO_VAL_INT_PLUS_MICRO;
@@ -1057,6 +1152,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
 					  st->reg[AD9910_REG_DRG_RATE].val32);
 			break;
+		case AD9910_CHANNEL_RAM:
+			tmp32 = FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+					  ad9910_ram_profile_val(st));
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -1078,7 +1177,7 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 	struct ad9910_state *st = iio_priv(indio_dev);
 	u64 tmp64;
 	u32 tmp32;
-	int ret;
+	int ret, i;
 
 	guard(mutex)(&st->lock);
 
@@ -1115,6 +1214,26 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg32_update(st, AD9910_REG_CFR2,
 						   AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
 						   tmp32, true);
+		case AD9910_CHANNEL_RAM:
+			if (AD9910_RAM_ENABLED(st) == !!val)
+				return 0;
+
+			/* switch profile configs */
+			for (i = 0; i < AD9910_NUM_PROFILES; i++) {
+				tmp64 = st->reg[AD9910_REG_PROFILE(i)].val64;
+				ret = ad9910_reg64_write(st,
+							 AD9910_REG_PROFILE(i),
+							 st->reg_profile[i],
+							 false);
+				if (ret)
+					return ret;
+				st->reg_profile[i] = tmp64;
+			}
+
+			tmp32 = FIELD_PREP(AD9910_CFR1_RAM_ENABLE_MSK, !!val);
+			return ad9910_reg32_update(st, AD9910_REG_CFR1,
+						   AD9910_CFR1_RAM_ENABLE_MSK,
+						   tmp32, true);
 		default:
 			return -EINVAL;
 		}
@@ -1128,6 +1247,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 		switch (chan->channel) {
 		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
 			tmp32 = chan->channel - AD9910_CHANNEL_PROFILE_0;
+			if (AD9910_RAM_ENABLED(st)) {
+				FIELD_MODIFY(AD9910_PROFILE_ST_FTW_MSK,
+					     &st->reg_profile[tmp32], tmp64);
+				return 0;
+			}
 			tmp64 = FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64);
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
 						   AD9910_PROFILE_ST_FTW_MSK,
@@ -1154,6 +1278,8 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
 						   AD9910_DRG_LIMIT_LOWER_MSK,
 						   tmp64, true);
+		case AD9910_CHANNEL_RAM:
+			return ad9910_reg32_write(st, AD9910_REG_FTW, tmp64, true);
 		default:
 			return -EINVAL;
 		}
@@ -1171,6 +1297,13 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			tmp64 <<= 16;
 			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD);
 			tmp64 = min(tmp64, AD9910_POW_MAX);
+
+			if (AD9910_RAM_ENABLED(st)) {
+				FIELD_MODIFY(AD9910_PROFILE_ST_POW_MSK,
+					     &st->reg_profile[tmp32], tmp64);
+				return 0;
+			}
+
 			tmp64 = FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64);
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
 						   AD9910_PROFILE_ST_POW_MSK,
@@ -1209,6 +1342,15 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
 						   AD9910_DRG_LIMIT_LOWER_MSK,
 						   tmp64, true);
+		case AD9910_CHANNEL_RAM:
+			tmp64 = (u64)val * MICRO + val2;
+			if (tmp64 >= AD9910_MAX_PHASE_MICRORAD)
+				return -EINVAL;
+
+			tmp64 <<= 16;
+			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD);
+			tmp64 = min(tmp64, AD9910_POW_MAX);
+			return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true);
 		default:
 			return -EINVAL;
 		}
@@ -1222,6 +1364,13 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			tmp64 = ((u64)val * MICRO + val2) << 14;
 			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, MICRO);
 			tmp64 = min(tmp64, AD9910_ASF_MAX);
+
+			if (AD9910_RAM_ENABLED(st)) {
+				FIELD_MODIFY(AD9910_PROFILE_ST_ASF_MSK,
+					     &st->reg_profile[tmp32], tmp64);
+				return 0;
+			}
+
 			tmp64 = FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64);
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
 						   AD9910_PROFILE_ST_ASF_MSK,
@@ -1279,6 +1428,18 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
 						   AD9910_DRG_RATE_DEC_MSK,
 						   tmp32, true);
+		case AD9910_CHANNEL_RAM:
+			if (!AD9910_RAM_ENABLED(st)) {
+				FIELD_MODIFY(AD9910_PROFILE_RAM_STEP_RATE_MSK,
+					     &st->reg_profile[st->profile], tmp32);
+				return 0;
+			}
+
+			tmp64 = FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp32);
+			return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile),
+						   AD9910_PROFILE_RAM_STEP_RATE_MSK,
+						   tmp64, true);
+
 		default:
 			return -EINVAL;
 		}
@@ -1300,6 +1461,7 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
 	case IIO_CHAN_INFO_SCALE:
 		switch (chan->channel) {
 		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
+		case AD9910_CHANNEL_RAM:
 			return IIO_VAL_INT_PLUS_MICRO;
 		case AD9910_CHANNEL_DRG_RAMP_UP:
 		case AD9910_CHANNEL_DRG_RAMP_DOWN:
@@ -1392,6 +1554,123 @@ static int ad9910_debugfs_reg_access(struct iio_dev *indio_dev,
 		return ad9910_debugfs_reg_write(st, high32, reg, writeval);
 }
 
+static enum fw_upload_err ad9910_ram_fwu_prepare(struct fw_upload *fw_upload,
+						 const u8 *data, u32 size)
+{
+	struct ad9910_state *st = fw_upload->dd_handle;
+	const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
+	u32 wcount, bcount;
+
+	if (size < sizeof(struct ad9910_ram_fw))
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+
+	if (get_unaligned_be32(&fw_data->magic) != AD9910_RAM_FW_MAGIC)
+		return FW_UPLOAD_ERR_FW_INVALID;
+
+	wcount = get_unaligned_be32(&fw_data->wcount);
+	bcount = size - sizeof(struct ad9910_ram_fw);
+	if (wcount > AD9910_RAM_SIZE_MAX_WORDS ||
+	    bcount != (wcount * AD9910_RAM_WORD_SIZE))
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+
+	guard(mutex)(&st->lock);
+	st->ram_fwu_cancel = false;
+
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err ad9910_ram_fwu_write(struct fw_upload *fw_upload,
+					       const u8 *data, u32 offset,
+					       u32 size, u32 *written)
+{
+	struct ad9910_state *st = fw_upload->dd_handle;
+	const struct ad9910_ram_fw *fw_data = (const struct ad9910_ram_fw *)data;
+	int ret, ret2, idx, wcount;
+	u64 tmp64, backup;
+
+	if (offset != 0)
+		return FW_UPLOAD_ERR_INVALID_SIZE;
+
+	guard(mutex)(&st->lock);
+
+	if (st->ram_fwu_cancel)
+		return FW_UPLOAD_ERR_CANCELED;
+
+	if (AD9910_RAM_ENABLED(st))
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	/* copy ram profiles */
+	for (idx = 0; idx < AD9910_NUM_PROFILES; idx++)
+		st->reg_profile[idx] = get_unaligned_be64(&fw_data->profiles[idx]) |
+				       AD9910_PROFILE_RAM_OPEN_MSK;
+
+	/* update CFR1 */
+	ret = ad9910_reg32_update(st, AD9910_REG_CFR1,
+				  AD9910_CFR1_RAM_PLAYBACK_DEST_MSK |
+				  AD9910_CFR1_INT_PROFILE_CTL_MSK,
+				  get_unaligned_be32(&fw_data->cfr1), true);
+	if (ret)
+		return FW_UPLOAD_ERR_RW_ERROR;
+
+	wcount = get_unaligned_be32(&fw_data->wcount);
+	if (!wcount) {
+		*written = size;
+		return FW_UPLOAD_ERR_NONE; /* nothing else to write */
+	}
+
+	/* ensure profile is selected */
+	ret = ad9910_profile_set(st, st->profile);
+	if (ret)
+		return FW_UPLOAD_ERR_HW_ERROR;
+
+	/* backup profile register and update it with required address range */
+	backup = st->reg[AD9910_REG_PROFILE(st->profile)].val64;
+	tmp64 = AD9910_PROFILE_RAM_STEP_RATE_MSK |
+		FIELD_PREP(AD9910_PROFILE_RAM_START_ADDR_MSK, 0) |
+		FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, wcount - 1);
+	ret = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, true);
+	if (ret)
+		return FW_UPLOAD_ERR_RW_ERROR;
+
+	/* populate words into tx_buf[1:] */
+	memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE);
+
+	/* write ram data and restore profile register */
+	ret = ad9910_spi_write(st, AD9910_REG_RAM,
+			       wcount * AD9910_RAM_WORD_SIZE, false);
+	ret2 = ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, true);
+	if (ret || ret2)
+		return FW_UPLOAD_ERR_RW_ERROR;
+
+	*written = size;
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static enum fw_upload_err ad9910_ram_fwu_poll_complete(struct fw_upload *fw_upload)
+{
+	return FW_UPLOAD_ERR_NONE;
+}
+
+static void ad9910_ram_fwu_cancel(struct fw_upload *fw_upload)
+{
+	struct ad9910_state *st = fw_upload->dd_handle;
+
+	guard(mutex)(&st->lock);
+	st->ram_fwu_cancel = true;
+}
+
+static void ad9910_ram_fwu_unregister(void *data)
+{
+	firmware_upload_unregister(data);
+}
+
+static const struct fw_upload_ops ad9910_ram_fwu_ops = {
+	.prepare = ad9910_ram_fwu_prepare,
+	.write = ad9910_ram_fwu_write,
+	.poll_complete = ad9910_ram_fwu_poll_complete,
+	.cancel = ad9910_ram_fwu_cancel
+};
+
 static const struct iio_info ad9910_info = {
 	.read_raw = ad9910_read_raw,
 	.write_raw = ad9910_write_raw,
@@ -1496,6 +1775,13 @@ static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
 	if (ret)
 		return ret;
 
+	for (int i = 0; i < AD9910_NUM_PROFILES; i++) {
+		st->reg_profile[i] = AD9910_PROFILE_RAM_OPEN_MSK;
+		st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, 1);
+		st->reg_profile[i] |= FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK,
+						 AD9910_RAM_ADDR_MAX);
+	}
+
 	return ad9910_io_update(st);
 }
 
@@ -1512,6 +1798,22 @@ static void ad9910_release(void *data)
 			    true);
 }
 
+static inline void ad9910_debugfs_init(struct ad9910_state *st,
+				       struct iio_dev *indio_dev)
+{
+	char buf[64];
+
+	/*
+	 * symlinks are created here so iio userspace tools can refer to them
+	 * as debug attributes.
+	 */
+	snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/loading", st->ram_fwu_name);
+	debugfs_create_symlink("ram_loading", iio_get_debugfs_dentry(indio_dev), buf);
+
+	snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/data", st->ram_fwu_name);
+	debugfs_create_symlink("ram_data", iio_get_debugfs_dentry(indio_dev), buf);
+}
+
 static int ad9910_probe(struct spi_device *spi)
 {
 	static const char * const supplies[] = {
@@ -1595,7 +1897,21 @@ static int ad9910_probe(struct spi_device *spi)
 	if (ret)
 		return dev_err_probe(dev, ret, "failed to add release action\n");
 
-	return devm_iio_device_register(dev, indio_dev);
+	ret = devm_iio_device_register(dev, indio_dev);
+	if (ret)
+		return ret;
+
+	snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram",
+		 dev_name(&indio_dev->dev));
+	st->ram_fwu = firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_name,
+					       &ad9910_ram_fwu_ops, st);
+	if (IS_ERR(st->ram_fwu))
+		return dev_err_probe(dev, PTR_ERR(st->ram_fwu),
+				     "failed to register to the RAM Upload\n");
+
+	ad9910_debugfs_init(st, indio_dev);
+
+	return devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_fwu);
 }
 
 static const struct spi_device_id ad9910_id[] = {

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 4/9] iio: frequency: ad9910: add digital ramp generator support
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add DRG channels with destination selection (frequency, phase, or
amplitude) based on attribute writes, dwell mode control,
configurable upper/lower limits, increment/decrement step sizes, and
step rate settings.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/ad9910.c | 425 ++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 423 insertions(+), 2 deletions(-)

diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index 5b4076028a29..c9ec677cd63a 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -132,6 +132,18 @@
 #define AD9910_MC_SYNC_OUTPUT_DELAY_MSK		GENMASK(15, 11)
 #define AD9910_MC_SYNC_INPUT_DELAY_MSK		GENMASK(7, 3)
 
+/* Digital Ramp Limit Register */
+#define AD9910_DRG_LIMIT_UPPER_MSK		GENMASK_ULL(63, 32)
+#define AD9910_DRG_LIMIT_LOWER_MSK		GENMASK_ULL(31, 0)
+
+/* Digital Ramp Step Register */
+#define AD9910_DRG_STEP_DEC_MSK			GENMASK_ULL(63, 32)
+#define AD9910_DRG_STEP_INC_MSK			GENMASK_ULL(31, 0)
+
+/* Digital Ramp Rate Register */
+#define AD9910_DRG_RATE_DEC_MSK			GENMASK(31, 16)
+#define AD9910_DRG_RATE_INC_MSK			GENMASK(15, 0)
+
 /* Profile Register Format (Single Tone Mode) */
 #define AD9910_PROFILE_ST_ASF_MSK		GENMASK_ULL(61, 48)
 #define AD9910_PROFILE_ST_POW_MSK		GENMASK_ULL(47, 32)
@@ -147,6 +159,7 @@
 #define AD9910_ASF_PP_LSB_MAX		GENMASK(5, 0)
 #define AD9910_POW_MAX			GENMASK(15, 0)
 #define AD9910_POW_PP_LSB_MAX		GENMASK(7, 0)
+#define AD9910_STEP_RATE_MAX		GENMASK(15, 0)
 #define AD9910_NUM_PROFILES		8
 
 /* PLL constants */
@@ -198,6 +211,9 @@
  * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel
  * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel
  * @AD9910_CHANNEL_PARALLEL_PORT: Parallel port output channel
+ * @AD9910_CHANNEL_DRG: Digital Ramp Generator output channel
+ * @AD9910_CHANNEL_DRG_RAMP_UP: DRG ramp up channel
+ * @AD9910_CHANNEL_DRG_RAMP_DOWN: DRG ramp down channel
  */
 enum ad9910_channel {
 	AD9910_CHANNEL_PHY = 100,
@@ -210,6 +226,24 @@ enum ad9910_channel {
 	AD9910_CHANNEL_PROFILE_6 = 107,
 	AD9910_CHANNEL_PROFILE_7 = 108,
 	AD9910_CHANNEL_PARALLEL_PORT = 110,
+	AD9910_CHANNEL_DRG = 120,
+	AD9910_CHANNEL_DRG_RAMP_UP = 121,
+	AD9910_CHANNEL_DRG_RAMP_DOWN = 122,
+};
+
+/**
+ * enum ad9910_destination - AD9910 DDS core parameter destination
+ *
+ * @AD9910_DEST_FREQUENCY: Frequency destination
+ * @AD9910_DEST_PHASE: Phase destination
+ * @AD9910_DEST_AMPLITUDE: Amplitude destination
+ * @AD9910_DEST_POLAR: Polar destination
+ */
+enum ad9910_destination {
+	AD9910_DEST_FREQUENCY,
+	AD9910_DEST_PHASE,
+	AD9910_DEST_AMPLITUDE,
+	AD9910_DEST_POLAR,
 };
 
 enum {
@@ -236,6 +270,9 @@ enum {
 	AD9910_PP_FREQ_OFFSET,
 	AD9910_PP_PHASE_OFFSET,
 	AD9910_PP_AMP_OFFSET,
+	AD9910_DRG_FREQ_STEP,
+	AD9910_DRG_PHASE_STEP,
+	AD9910_DRG_AMP_STEP,
 };
 
 struct ad9910_data {
@@ -475,6 +512,16 @@ static int ad9910_powerdown_set(struct ad9910_state *st, bool enable)
 	return gpiod_set_value_cansleep(st->gpio_pwdown, enable);
 }
 
+static inline int ad9910_drg_destination_set(struct ad9910_state *st,
+					     enum ad9910_destination dest,
+					     bool update)
+{
+	return ad9910_reg32_update(st, AD9910_REG_CFR2,
+				   AD9910_CFR2_DRG_DEST_MSK,
+				   FIELD_PREP(AD9910_CFR2_DRG_DEST_MSK, dest),
+				   update);
+}
+
 static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
 				    uintptr_t private,
 				    const struct iio_chan_spec *chan,
@@ -638,6 +685,140 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
 	return ret ?: len;
 }
 
+static ssize_t ad9910_drg_attrs_read(struct iio_dev *indio_dev,
+				     uintptr_t private,
+				     const struct iio_chan_spec *chan,
+				     char *buf)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	unsigned int type;
+	int vals[2];
+	u64 tmp64;
+
+	guard(mutex)(&st->lock);
+
+	switch (chan->channel) {
+	case AD9910_CHANNEL_DRG_RAMP_UP:
+		tmp64 = FIELD_GET(AD9910_DRG_STEP_INC_MSK,
+				  st->reg[AD9910_REG_DRG_STEP].val64);
+		break;
+	case AD9910_CHANNEL_DRG_RAMP_DOWN:
+		tmp64 = FIELD_GET(AD9910_DRG_STEP_DEC_MSK,
+				  st->reg[AD9910_REG_DRG_STEP].val64);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (private) {
+	case AD9910_DRG_FREQ_STEP:
+		type = IIO_VAL_INT_PLUS_MICRO;
+		tmp64 *= st->data.sysclk_freq_hz;
+		vals[0] = tmp64 >> 32;
+		vals[1] = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32;
+		break;
+	case AD9910_DRG_PHASE_STEP:
+		type = IIO_VAL_INT_PLUS_NANO;
+		tmp64 *= AD9910_PI_NANORAD;
+		tmp64 >>= 31;
+		vals[0] = div_u64_rem(tmp64, NANO, &vals[1]);
+		break;
+	case AD9910_DRG_AMP_STEP:
+		type = IIO_VAL_INT_PLUS_NANO;
+		vals[0] = 0;
+		vals[1] = tmp64 * NANO >> 32;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return iio_format_value(buf, type, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ad9910_drg_attrs_write(struct iio_dev *indio_dev,
+				      uintptr_t private,
+				      const struct iio_chan_spec *chan,
+				      const char *buf, size_t len)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	enum ad9910_destination dest;
+	int val, val2;
+	u64 tmp64;
+	int ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (private) {
+	case AD9910_DRG_FREQ_STEP:
+		ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+		if (ret)
+			return ret;
+
+		if (!in_range(val, 0, st->data.sysclk_freq_hz / 2))
+			return -EINVAL;
+
+		tmp64 = (u64)val * MICRO + val2;
+		tmp64 = ad9910_rational_scale(tmp64, BIT_ULL(32),
+					      (u64)MICRO * st->data.sysclk_freq_hz);
+		dest = AD9910_DEST_FREQUENCY;
+		break;
+	case AD9910_DRG_PHASE_STEP:
+		ret = iio_str_to_fixpoint(buf, NANO / 10, &val, &val2);
+		if (ret)
+			return ret;
+
+		if (val < 0 || val2 < 0)
+			return -EINVAL;
+
+		tmp64 = (u64)val * NANO + val2;
+		if (tmp64 > 2ULL * AD9910_PI_NANORAD)
+			return -EINVAL;
+
+		tmp64 <<= 31;
+		tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD);
+		dest = AD9910_DEST_PHASE;
+		break;
+	case AD9910_DRG_AMP_STEP:
+		ret = iio_str_to_fixpoint(buf, NANO / 10, &val, &val2);
+		if (ret)
+			return ret;
+
+		if (val < 0 || val > 1 || (val == 1 && val2 > 0))
+			return -EINVAL;
+
+		tmp64 = ((u64)val * NANO + val2) << 32;
+		tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, NANO);
+		dest = AD9910_DEST_AMPLITUDE;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	tmp64 = min(tmp64, U32_MAX);
+	ret = ad9910_drg_destination_set(st, dest, false);
+	if (ret)
+		return ret;
+
+	switch (chan->channel) {
+	case AD9910_CHANNEL_DRG_RAMP_UP:
+		ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+					  AD9910_DRG_STEP_INC_MSK,
+					  FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64),
+					  true);
+		break;
+	case AD9910_CHANNEL_DRG_RAMP_DOWN:
+		ret = ad9910_reg64_update(st, AD9910_REG_DRG_STEP,
+					  AD9910_DRG_STEP_DEC_MSK,
+					  FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64),
+					  true);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret ?: len;
+}
+
 #define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \
 	.name = _name, \
 	.read = ad9910_ ## _fn_desc ## _read, \
@@ -652,6 +833,9 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
 #define AD9910_PP_EXT_INFO(_name, _ident) \
 	AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, pp_attrs)
 
+#define AD9910_DRG_EXT_INFO(_name, _ident) \
+	AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, drg_attrs)
+
 static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
 	AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
 	{ }
@@ -665,6 +849,13 @@ static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
 	{ }
 };
 
+static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] = {
+	AD9910_DRG_EXT_INFO("frequency_step", AD9910_DRG_FREQ_STEP),
+	AD9910_DRG_EXT_INFO("phase_step", AD9910_DRG_PHASE_STEP),
+	AD9910_DRG_EXT_INFO("scale_step", AD9910_DRG_AMP_STEP),
+	{ }
+};
+
 #define AD9910_PROFILE_CHAN(idx) {				\
 	.type = IIO_ALTVOLTAGE,					\
 	.indexed = 1,						\
@@ -706,6 +897,43 @@ static const struct iio_chan_spec ad9910_channels[] = {
 		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
 		.ext_info = ad9910_pp_ext_info,
 	},
+	[AD9910_CHAN_IDX_DRG] = {
+		.type = IIO_ALTVOLTAGE,
+		.indexed = 1,
+		.output = 1,
+		.channel = AD9910_CHANNEL_DRG,
+		.address = AD9910_CHAN_IDX_DRG,
+		.scan_index = -1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
+	},
+	[AD9910_CHAN_IDX_DRG_RAMP_UP] = {
+		.type = IIO_ALTVOLTAGE,
+		.indexed = 1,
+		.output = 1,
+		.channel = AD9910_CHANNEL_DRG_RAMP_UP,
+		.address = AD9910_CHAN_IDX_DRG_RAMP_UP,
+		.scan_index = -1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+				      BIT(IIO_CHAN_INFO_FREQUENCY) |
+				      BIT(IIO_CHAN_INFO_PHASE) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.ext_info = ad9910_drg_ramp_ext_info,
+	},
+	[AD9910_CHAN_IDX_DRG_RAMP_DOWN] = {
+		.type = IIO_ALTVOLTAGE,
+		.indexed = 1,
+		.output = 1,
+		.channel = AD9910_CHANNEL_DRG_RAMP_DOWN,
+		.address = AD9910_CHAN_IDX_DRG_RAMP_DOWN,
+		.scan_index = -1,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE) |
+				      BIT(IIO_CHAN_INFO_FREQUENCY) |
+				      BIT(IIO_CHAN_INFO_PHASE) |
+				      BIT(IIO_CHAN_INFO_SCALE) |
+				      BIT(IIO_CHAN_INFO_SAMP_FREQ),
+		.ext_info = ad9910_drg_ramp_ext_info,
+	},
 };
 
 static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -729,6 +957,18 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			*val = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
 					 st->reg[AD9910_REG_CFR2].val32);
 			break;
+		case AD9910_CHANNEL_DRG:
+			*val = FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK,
+					 st->reg[AD9910_REG_CFR2].val32);
+			break;
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			*val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+					 st->reg[AD9910_REG_CFR2].val32);
+			break;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			*val = FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+					 st->reg[AD9910_REG_CFR2].val32);
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -740,6 +980,14 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			tmp32 = FIELD_GET(AD9910_PROFILE_ST_FTW_MSK,
 					  st->reg[AD9910_REG_PROFILE(tmp32)].val64);
 			break;
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp32 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+					  st->reg[AD9910_REG_DRG_LIMIT].val64);
+			break;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp32 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+					  st->reg[AD9910_REG_DRG_LIMIT].val64);
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -757,6 +1005,18 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			*val = tmp32 / MICRO;
 			*val2 = tmp32 % MICRO;
 			return IIO_VAL_INT_PLUS_MICRO;
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+					  st->reg[AD9910_REG_DRG_LIMIT].val64);
+			tmp64 = (tmp64 * AD9910_PI_NANORAD) >> 31;
+			*val = div_u64_rem(tmp64, NANO, val2);
+			return IIO_VAL_INT_PLUS_NANO;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+					  st->reg[AD9910_REG_DRG_LIMIT].val64);
+			tmp64 = (tmp64 * AD9910_PI_NANORAD) >> 31;
+			*val = div_u64_rem(tmp64, NANO, val2);
+			return IIO_VAL_INT_PLUS_NANO;
 		default:
 			return -EINVAL;
 		}
@@ -769,6 +1029,18 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			*val = 0;
 			*val2 = tmp64 * MICRO >> 14;
 			return IIO_VAL_INT_PLUS_MICRO;
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp64 = FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK,
+					  st->reg[AD9910_REG_DRG_LIMIT].val64);
+			*val = 0;
+			*val2 = tmp64 * NANO >> 32;
+			return IIO_VAL_INT_PLUS_NANO;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp64 = FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK,
+					  st->reg[AD9910_REG_DRG_LIMIT].val64);
+			*val = 0;
+			*val2 = tmp64 * NANO >> 32;
+			return IIO_VAL_INT_PLUS_NANO;
 		default:
 			return -EINVAL;
 		}
@@ -777,9 +1049,23 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 		case AD9910_CHANNEL_PHY:
 			*val = st->data.sysclk_freq_hz;
 			return IIO_VAL_INT;
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp32 = FIELD_GET(AD9910_DRG_RATE_INC_MSK,
+					  st->reg[AD9910_REG_DRG_RATE].val32);
+			break;
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp32 = FIELD_GET(AD9910_DRG_RATE_DEC_MSK,
+					  st->reg[AD9910_REG_DRG_RATE].val32);
+			break;
 		default:
 			return -EINVAL;
 		}
+		if (!tmp32)
+			return -ERANGE;
+		tmp32 *= 4;
+		*val = st->data.sysclk_freq_hz / tmp32;
+		*val2 = div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32);
+		return IIO_VAL_INT_PLUS_MICRO;
 	default:
 		return -EINVAL;
 	}
@@ -792,6 +1078,7 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 	struct ad9910_state *st = iio_priv(indio_dev);
 	u64 tmp64;
 	u32 tmp32;
+	int ret;
 
 	guard(mutex)(&st->lock);
 
@@ -813,6 +1100,21 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg32_update(st, AD9910_REG_CFR2,
 						   AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
 						   tmp32, true);
+		case AD9910_CHANNEL_DRG:
+			tmp32 = FIELD_PREP(AD9910_CFR2_DRG_ENABLE_MSK, val);
+			return ad9910_reg32_update(st, AD9910_REG_CFR2,
+						   AD9910_CFR2_DRG_ENABLE_MSK,
+						   tmp32, true);
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp32 = FIELD_PREP(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, !!val);
+			return ad9910_reg32_update(st, AD9910_REG_CFR2,
+						   AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK,
+						   tmp32, true);
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp32 = FIELD_PREP(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, !!val);
+			return ad9910_reg32_update(st, AD9910_REG_CFR2,
+						   AD9910_CFR2_DRG_NO_DWELL_LOW_MSK,
+						   tmp32, true);
 		default:
 			return -EINVAL;
 		}
@@ -830,6 +1132,28 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
 						   AD9910_PROFILE_ST_FTW_MSK,
 						   tmp64, true);
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			ret = ad9910_drg_destination_set(st,
+							 AD9910_DEST_FREQUENCY,
+							 false);
+			if (ret)
+				return ret;
+
+			tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+						   AD9910_DRG_LIMIT_UPPER_MSK,
+						   tmp64, true);
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			ret = ad9910_drg_destination_set(st,
+							 AD9910_DEST_FREQUENCY,
+							 false);
+			if (ret)
+				return ret;
+
+			tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+						   AD9910_DRG_LIMIT_LOWER_MSK,
+						   tmp64, true);
 		default:
 			return -EINVAL;
 		}
@@ -851,6 +1175,40 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
 						   AD9910_PROFILE_ST_POW_MSK,
 						   tmp64, true);
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp64 = (u64)val * NANO + val2;
+			if (tmp64 > 2ULL * AD9910_PI_NANORAD)
+				return -EINVAL;
+
+			ret = ad9910_drg_destination_set(st, AD9910_DEST_PHASE,
+							 false);
+			if (ret)
+				return ret;
+
+			tmp64 <<= 31;
+			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD);
+			tmp64 = min(tmp64, U32_MAX);
+			tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+						   AD9910_DRG_LIMIT_UPPER_MSK,
+						   tmp64, true);
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp64 = (u64)val * NANO + val2;
+			if (tmp64 > 2ULL * AD9910_PI_NANORAD)
+				return -EINVAL;
+
+			ret = ad9910_drg_destination_set(st, AD9910_DEST_PHASE,
+							 false);
+			if (ret)
+				return ret;
+
+			tmp64 <<= 31;
+			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD);
+			tmp64 = min(tmp64, U32_MAX);
+			tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+						   AD9910_DRG_LIMIT_LOWER_MSK,
+						   tmp64, true);
 		default:
 			return -EINVAL;
 		}
@@ -868,11 +1226,62 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32),
 						   AD9910_PROFILE_ST_ASF_MSK,
 						   tmp64, true);
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			ret = ad9910_drg_destination_set(st,
+							 AD9910_DEST_AMPLITUDE,
+							 false);
+			if (ret)
+				return ret;
+
+			tmp64 = ((u64)val * NANO + val2) << 32;
+			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, NANO);
+			tmp64 = min(tmp64, U32_MAX);
+			tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_UPPER_MSK, tmp64);
+			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+						   AD9910_DRG_LIMIT_UPPER_MSK,
+						   tmp64, true);
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			ret = ad9910_drg_destination_set(st,
+							 AD9910_DEST_AMPLITUDE,
+							 false);
+			if (ret)
+				return ret;
+
+			tmp64 = ((u64)val * NANO + val2) << 32;
+			tmp64 = DIV_U64_ROUND_CLOSEST(tmp64, NANO);
+			tmp64 = min(tmp64, U32_MAX);
+			tmp64 = FIELD_PREP(AD9910_DRG_LIMIT_LOWER_MSK, tmp64);
+			return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT,
+						   AD9910_DRG_LIMIT_LOWER_MSK,
+						   tmp64, true);
 		default:
 			return -EINVAL;
 		}
 	case IIO_CHAN_INFO_SAMP_FREQ:
-		return ad9910_set_sysclk_freq(st, val, true);
+		if (chan->channel == AD9910_CHANNEL_PHY)
+			return ad9910_set_sysclk_freq(st, val, true);
+
+		tmp64 = ((u64)val * MICRO + val2) * 4;
+		if (!tmp64)
+			return -EINVAL;
+
+		tmp64 = DIV64_U64_ROUND_CLOSEST((u64)st->data.sysclk_freq_hz * MICRO, tmp64);
+		tmp32 = clamp(tmp64, 1U, AD9910_STEP_RATE_MAX);
+
+		switch (chan->channel) {
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+			tmp32 = FIELD_PREP(AD9910_DRG_RATE_INC_MSK, tmp32);
+			return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+						   AD9910_DRG_RATE_INC_MSK,
+						   tmp32, true);
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			tmp32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, tmp32);
+			return ad9910_reg32_update(st, AD9910_REG_DRG_RATE,
+						   AD9910_DRG_RATE_DEC_MSK,
+						   tmp32, true);
+		default:
+			return -EINVAL;
+		}
 	default:
 		return -EINVAL;
 	}
@@ -892,11 +1301,16 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev,
 		switch (chan->channel) {
 		case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7:
 			return IIO_VAL_INT_PLUS_MICRO;
+		case AD9910_CHANNEL_DRG_RAMP_UP:
+		case AD9910_CHANNEL_DRG_RAMP_DOWN:
+			return IIO_VAL_INT_PLUS_NANO;
 		default:
 			return -EINVAL;
 		}
 	case IIO_CHAN_INFO_SAMP_FREQ:
-		return IIO_VAL_INT;
+		if (chan->channel == AD9910_CHANNEL_PHY)
+			return IIO_VAL_INT;
+		return IIO_VAL_INT_PLUS_MICRO;
 	default:
 		return -EINVAL;
 	}
@@ -1075,6 +1489,13 @@ static int ad9910_setup(struct ad9910_state *st, struct reset_control *dev_rst)
 	if (ret)
 		return ret;
 
+	/* configure step rate with default values */
+	reg32 = FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) |
+		FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1);
+	ret = ad9910_reg32_write(st, AD9910_REG_DRG_RATE, reg32, false);
+	if (ret)
+		return ret;
+
 	return ad9910_io_update(st);
 }
 

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 3/9] iio: frequency: ad9910: add simple parallel port mode support
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

Add parallel port channel with frequency scale, frequency offset, phase
offset, and amplitude offset extended attributes for configuring the
parallel data path.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 drivers/iio/frequency/ad9910.c | 152 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 152 insertions(+)

diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c
index e9005037db1a..5b4076028a29 100644
--- a/drivers/iio/frequency/ad9910.c
+++ b/drivers/iio/frequency/ad9910.c
@@ -114,9 +114,13 @@
 /* Auxiliary DAC Control Register Bits */
 #define AD9910_AUX_DAC_FSC_MSK			GENMASK(7, 0)
 
+/* POW Register Bits */
+#define AD9910_POW_PP_LSB_MSK			GENMASK(7, 0)
+
 /* ASF Register Bits */
 #define AD9910_ASF_RAMP_RATE_MSK		GENMASK(31, 16)
 #define AD9910_ASF_SCALE_FACTOR_MSK		GENMASK(15, 2)
+#define AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK	GENMASK(7, 2)
 #define AD9910_ASF_STEP_SIZE_MSK		GENMASK(1, 0)
 
 /* Multichip Sync Register Bits */
@@ -140,7 +144,9 @@
 #define AD9910_MAX_PHASE_MICRORAD	(AD9910_PI_NANORAD / 500)
 
 #define AD9910_ASF_MAX			GENMASK(13, 0)
+#define AD9910_ASF_PP_LSB_MAX		GENMASK(5, 0)
 #define AD9910_POW_MAX			GENMASK(15, 0)
+#define AD9910_POW_PP_LSB_MAX		GENMASK(7, 0)
 #define AD9910_NUM_PROFILES		8
 
 /* PLL constants */
@@ -191,6 +197,7 @@
  * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel
  * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel
  * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel
+ * @AD9910_CHANNEL_PARALLEL_PORT: Parallel port output channel
  */
 enum ad9910_channel {
 	AD9910_CHANNEL_PHY = 100,
@@ -202,6 +209,7 @@ enum ad9910_channel {
 	AD9910_CHANNEL_PROFILE_5 = 106,
 	AD9910_CHANNEL_PROFILE_6 = 107,
 	AD9910_CHANNEL_PROFILE_7 = 108,
+	AD9910_CHANNEL_PARALLEL_PORT = 110,
 };
 
 enum {
@@ -224,6 +232,10 @@ enum {
 
 enum {
 	AD9910_POWERDOWN,
+	AD9910_PP_FREQ_SCALE,
+	AD9910_PP_FREQ_OFFSET,
+	AD9910_PP_PHASE_OFFSET,
+	AD9910_PP_AMP_OFFSET,
 };
 
 struct ad9910_data {
@@ -478,6 +490,10 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev,
 		val = !!FIELD_GET(AD9910_CFR1_SOFT_POWER_DOWN_MSK,
 				  st->reg[AD9910_REG_CFR1].val32);
 		break;
+	case AD9910_PP_FREQ_SCALE:
+		val = BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK,
+				    st->reg[AD9910_REG_CFR2].val32));
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -508,6 +524,113 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
 					  AD9910_CFR1_SOFT_POWER_DOWN_MSK,
 					  val32, true);
 		break;
+	case AD9910_PP_FREQ_SCALE:
+		if (val32 > BIT(15) || !is_power_of_2(val32))
+			return -EINVAL;
+
+		val32 = FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(val32));
+		ret = ad9910_reg32_update(st, AD9910_REG_CFR2,
+					  AD9910_CFR2_FM_GAIN_MSK,
+					  val32, true);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret ?: len;
+}
+
+static ssize_t ad9910_pp_attrs_read(struct iio_dev *indio_dev,
+				    uintptr_t private,
+				    const struct iio_chan_spec *chan,
+				    char *buf)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	int vals[2];
+	u32 tmp32;
+	u64 tmp64;
+
+	guard(mutex)(&st->lock);
+
+	switch (private) {
+	case AD9910_PP_FREQ_OFFSET:
+		tmp64 = (u64)st->reg[AD9910_REG_FTW].val32 * st->data.sysclk_freq_hz;
+		vals[0] = tmp64 >> 32;
+		vals[1] = ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32;
+		break;
+	case AD9910_PP_PHASE_OFFSET:
+		tmp32 = FIELD_GET(AD9910_POW_PP_LSB_MSK,
+				  st->reg[AD9910_REG_POW].val16);
+		tmp32 = (tmp32 * AD9910_MAX_PHASE_MICRORAD) >> 16;
+		vals[0] = tmp32 / MICRO;
+		vals[1] = tmp32 % MICRO;
+		break;
+	case AD9910_PP_AMP_OFFSET:
+		tmp32 = FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+				  st->reg[AD9910_REG_ASF].val32);
+		vals[0] = 0;
+		vals[1] = (u64)tmp32 * MICRO >> 14;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), vals);
+}
+
+static ssize_t ad9910_pp_attrs_write(struct iio_dev *indio_dev,
+				     uintptr_t private,
+				     const struct iio_chan_spec *chan,
+				     const char *buf, size_t len)
+{
+	struct ad9910_state *st = iio_priv(indio_dev);
+	int val, val2;
+	u32 tmp32;
+	int ret;
+
+	ret = iio_str_to_fixpoint(buf, MICRO / 10, &val, &val2);
+	if (ret)
+		return ret;
+
+	guard(mutex)(&st->lock);
+
+	switch (private) {
+	case AD9910_PP_FREQ_OFFSET:
+		if (!in_range(val, 0, st->data.sysclk_freq_hz / 2))
+			return -EINVAL;
+
+		tmp32 = ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32),
+					      (u64)MICRO * st->data.sysclk_freq_hz);
+		ret = ad9910_reg32_write(st, AD9910_REG_FTW, tmp32, true);
+		break;
+	case AD9910_PP_PHASE_OFFSET:
+		if (val)
+			return -EINVAL;
+
+		if (!in_range(val2, 0, (AD9910_MAX_PHASE_MICRORAD >> 8)))
+			return -EINVAL;
+
+		tmp32 = DIV_ROUND_CLOSEST((u32)val2 << 16, AD9910_MAX_PHASE_MICRORAD);
+		tmp32 = min(tmp32, AD9910_POW_PP_LSB_MAX);
+		tmp32 = FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp32);
+		ret = ad9910_reg16_update(st, AD9910_REG_POW,
+					  AD9910_POW_PP_LSB_MSK,
+					  tmp32, true);
+		break;
+	case AD9910_PP_AMP_OFFSET:
+		if (val)
+			return -EINVAL;
+
+		if (!in_range(val2, 0, (MICRO >> 8)))
+			return -EINVAL;
+
+		tmp32 = DIV_ROUND_CLOSEST((u32)val2 << 14, MICRO);
+		tmp32 = min(tmp32, AD9910_ASF_PP_LSB_MAX);
+		tmp32 = FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp32);
+		ret = ad9910_reg32_update(st, AD9910_REG_ASF,
+					  AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK,
+					  tmp32, true);
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -526,11 +649,22 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *indio_dev,
 #define AD9910_EXT_INFO(_name, _ident, _shared) \
 	AD9910_EXT_INFO_TMPL(_name, _ident, _shared, ext_info)
 
+#define AD9910_PP_EXT_INFO(_name, _ident) \
+	AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, pp_attrs)
+
 static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] = {
 	AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE),
 	{ }
 };
 
+static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] = {
+	AD9910_EXT_INFO("frequency_scale", AD9910_PP_FREQ_SCALE, IIO_SEPARATE),
+	AD9910_PP_EXT_INFO("frequency_offset", AD9910_PP_FREQ_OFFSET),
+	AD9910_PP_EXT_INFO("phase_offset", AD9910_PP_PHASE_OFFSET),
+	AD9910_PP_EXT_INFO("scale_offset", AD9910_PP_AMP_OFFSET),
+	{ }
+};
+
 #define AD9910_PROFILE_CHAN(idx) {				\
 	.type = IIO_ALTVOLTAGE,					\
 	.indexed = 1,						\
@@ -563,6 +697,15 @@ static const struct iio_chan_spec ad9910_channels[] = {
 	[AD9910_CHAN_IDX_PROFILE_5] = AD9910_PROFILE_CHAN(5),
 	[AD9910_CHAN_IDX_PROFILE_6] = AD9910_PROFILE_CHAN(6),
 	[AD9910_CHAN_IDX_PROFILE_7] = AD9910_PROFILE_CHAN(7),
+	[AD9910_CHAN_IDX_PARALLEL_PORT] = {
+		.type = IIO_ALTVOLTAGE,
+		.indexed = 1,
+		.output = 1,
+		.channel = AD9910_CHANNEL_PARALLEL_PORT,
+		.address = AD9910_CHAN_IDX_PARALLEL_PORT,
+		.info_mask_separate = BIT(IIO_CHAN_INFO_ENABLE),
+		.ext_info = ad9910_pp_ext_info,
+	},
 };
 
 static int ad9910_read_raw(struct iio_dev *indio_dev,
@@ -582,6 +725,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev,
 			tmp32 = (chan->channel - AD9910_CHANNEL_PROFILE_0);
 			*val = (tmp32 == st->profile);
 			break;
+		case AD9910_CHANNEL_PARALLEL_PORT:
+			*val = FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+					 st->reg[AD9910_REG_CFR2].val32);
+			break;
 		default:
 			return -EINVAL;
 		}
@@ -661,6 +808,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev,
 			}
 
 			return ad9910_profile_set(st, tmp32);
+		case AD9910_CHANNEL_PARALLEL_PORT:
+			tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, !!val);
+			return ad9910_reg32_update(st, AD9910_REG_CFR2,
+						   AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
+						   tmp32, true);
 		default:
 			return -EINVAL;
 		}

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 1/9] dt-bindings: iio: frequency: add ad9910
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar
In-Reply-To: <20260417-ad9910-iio-driver-v3-0-29b93712a228@analog.com>

From: Rodrigo Alencar <rodrigo.alencar@analog.com>

DT-bindings for AD9910, a 1 GSPS DDS with 14-bit DAC. It includes
configurations for clocks, DAC current, reset and basic GPIO control.

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
 .../bindings/iio/frequency/adi,ad9910.yaml         | 189 +++++++++++++++++++++
 MAINTAINERS                                        |   7 +
 2 files changed, 196 insertions(+)

diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
new file mode 100644
index 000000000000..61e879bca5c2
--- /dev/null
+++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml
@@ -0,0 +1,189 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/iio/frequency/adi,ad9910.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Analog Devices AD9910 Direct Digital Synthesizer
+
+maintainers:
+  - Rodrigo Alencar <rodrigo.alencar@analog.com>
+
+description:
+  The AD9910 is a 1 GSPS direct digital synthesizer (DDS) with an integrated
+  14-bit DAC. It features single tone mode with 8 configurable profiles,
+  a digital ramp generator, RAM control, OSK, and a parallel data port for
+  high-speed streaming.
+
+  https://www.analog.com/en/products/ad9910.html
+
+properties:
+  compatible:
+    const: adi,ad9910
+
+  reg:
+    maxItems: 1
+
+  spi-max-frequency:
+    maximum: 70000000
+
+  clocks:
+    minItems: 1
+    maxItems: 2
+    description:
+      First clock is always the reference clock (REF_CLK), while the second
+      clock is an optional synchronization clock (SYNC_IN).
+
+  clock-names:
+    oneOf:
+      - items:
+          - const: ref_clk
+      - items:
+          - const: ref_clk
+          - const: sync_in
+
+  '#clock-cells':
+    const: 1
+
+  clock-output-names:
+    minItems: 1
+    maxItems: 3
+    items:
+      enum: [ sync_clk, pdclk, sync_out ]
+
+  interrupts:
+    minItems: 1
+    maxItems: 2
+
+  interrupt-names:
+    minItems: 1
+    maxItems: 2
+    items:
+      enum: [ drover, ram_swp_ovr ]
+
+  dvdd-io33-supply:
+    description: 3.3V Digital I/O supply.
+
+  avdd33-supply:
+    description: 3.3V Analog DAC supply.
+
+  dvdd18-supply:
+    description: 1.8V Digital Core supply.
+
+  avdd18-supply:
+    description: 1.8V Analog Core supply.
+
+  reset-gpios:
+    description:
+      GPIOs controlling the Main Device reset.
+
+  io-reset-gpios:
+    maxItems: 1
+    description:
+      GPIO controlling the I/O_RESET pin.
+
+  powerdown-gpios:
+    maxItems: 1
+    description:
+      GPIO controlling the EXT_PWR_DWN pin.
+
+  update-gpios:
+    maxItems: 1
+    description:
+      GPIO controlling the I/O_UPDATE pin.
+
+  profile-gpios:
+    minItems: 3
+    maxItems: 3
+    description:
+      GPIOs controlling the PROFILE[2:0] pins for profile selection.
+
+  sync-err-gpios:
+    maxItems: 1
+    description:
+      GPIO used to read SYNC_SMP_ERR pin status.
+
+  adi,pll-enable:
+    type: boolean
+    description:
+      Indicates that a loop filter is connected and the internal PLL is enabled.
+      Often used when the reference clock is provided by a crystal or by a
+      single-ended on-board oscillator.
+
+  adi,charge-pump-current-microamp:
+    minimum: 212
+    maximum: 387
+    default: 212
+    description:
+      PLL charge pump current in microamps. Only applicable when the internal
+      PLL is enabled. The value is rounded to the nearest supported step. This
+      value depends mostly on the loop filter design.
+
+  adi,refclk-out-drive-strength:
+    $ref: /schemas/types.yaml#/definitions/string
+    enum: [ disabled, low, medium, high ]
+    default: disabled
+    description:
+      Reference clock output (DRV0) drive strength. Only applicable when
+      the internal PLL is enabled.
+
+  adi,dac-output-current-microamp:
+    minimum: 8640
+    maximum: 31590
+    default: 20070
+    description:
+      DAC full-scale output current in microamps.
+
+dependencies:
+  adi,charge-pump-current-microamp: [ 'adi,pll-enable' ]
+  adi,refclk-out-drive-strength: [ 'adi,pll-enable' ]
+  interrupts: [ interrupt-names ]
+  clocks: [ clock-names ]
+  '#clock-cells': [ clock-output-names ]
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - dvdd-io33-supply
+  - avdd33-supply
+  - dvdd18-supply
+  - avdd18-supply
+
+allOf:
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+        dds@0 {
+            compatible = "adi,ad9910";
+            reg = <0>;
+            spi-max-frequency = <1000000>;
+            clocks = <&ad9910_refclk>;
+            clock-names = "ref_clk";
+
+            dvdd-io33-supply = <&vdd_io33>;
+            avdd33-supply = <&vdd_a33>;
+            dvdd18-supply = <&vdd_d18>;
+            avdd18-supply = <&vdd_a18>;
+
+            reset-gpios = <&gpio 0 GPIO_ACTIVE_HIGH>;
+            io-reset-gpios = <&gpio 1 GPIO_ACTIVE_HIGH>;
+            powerdown-gpios = <&gpio 2 GPIO_ACTIVE_HIGH>;
+            update-gpios = <&gpio 3 GPIO_ACTIVE_HIGH>;
+            profile-gpios = <&gpio 4 GPIO_ACTIVE_HIGH>,
+                            <&gpio 5 GPIO_ACTIVE_HIGH>,
+                            <&gpio 6 GPIO_ACTIVE_HIGH>;
+
+            adi,pll-enable;
+            adi,charge-pump-current-microamp = <387>;
+            adi,refclk-out-drive-strength = "disabled";
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index 08d8ddf4ef68..2ca8b68e5daa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1630,6 +1630,13 @@ W:	https://ez.analog.com/linux-software-drivers
 F:	Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml
 F:	drivers/iio/dac/ad9739a.c
 
+ANALOG DEVICES INC AD9910 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,ad9910.yaml
+
 ANALOG DEVICES INC MAX22007 DRIVER
 M:	Janani Sunil <janani.sunil@analog.com>
 L:	linux-iio@vger.kernel.org

-- 
2.43.0



^ permalink raw reply related

* [PATCH RFC v3 0/9] AD9910 Direct Digital Synthesizer
From: Rodrigo Alencar via B4 Relay @ 2026-04-17  8:17 UTC (permalink / raw)
  To: linux-iio, devicetree, linux-kernel, linux-doc, linux-hardening
  Cc: Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
	David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan,
	Kees Cook, Gustavo A. R. Silva, Rodrigo Alencar

This patch series adds support for the Analog Devices AD9910 DDS.
This is a RFC so that we can agree/discuss on the design that follows:

This is a follow-up of the V2 discussion. For V1, we reached into
this channel composition agreement where physical channels may have
sub-channels. That adds the flexibility necessary for this design.
During V2, some feedback indicated that the ABI is too device-specific,
so DRG/RAM destination and operating modes are configured through
alternate paths and profile channels are created.

The AD9910 DDS core can be driven through several independent mechanisms:
single tone profiles, a digital ramp generator, an internal RAM playback
engine, a parallel data port, and output shift keying. Each of these
represents a distinct signal path into the DDS accumulator, so the driver
models them as separate IIO output channels (all IIO_ALTVOLTAGE type).
This per-channel separation allows userspace to configure each mode
independently through its own set of sysfs attributes, and to
enable/disable modes individually via IIO_CHAN_INFO_ENABLE, relying on
the hardware's own mode selection architecture.

The AD9910 register map is not suited for the regmap framework: register
widths vary across the map (16, 32, and 64 bits). The driver instead
implements direct SPI access helpers with a software register cache, using
type-specific read/write/update functions (ad9910_reg{16,32,64}_{read,
write,update}) that handle endianness conversion and cache coherency.

Registers are cached for several reasons. The control/function registers
(CFR1, CFR2) are frequently queried to determine the current operating
mode (e.g., checking RAM_ENABLE before every profile register access),
and caching avoids repeated SPI read transactions for what are
essentially state checks. The cache also enables efficient
read-modify-write updates on multi-byte registers: the update functions
merge new field values with the cached register content without issuing
a SPI read, and skip the write entirely when the value is unchanged.
Finally, the profile registers serve dual purposes depending on whether
RAM mode is active -- they hold single tone parameters (FTW, POW, ASF)
in normal operation but are repurposed for RAM playback configuration
(start/end address, step rate, operating mode) when RAM is enabled. A
shadow register array (reg_profile[]) preserves the inactive mode's
settings across transitions, so no state is lost when switching between
single tone and RAM operation.

RAM data is loaded through firmware upload infrastructure. Userspace
writes the waveform data as a raw binary buffer (up to 4096 bytes for
the full 1024x32-bit RAM), and the driver reverses the byte array and
transfers it to the device in a single SPI transaction. Per-profile
start/end addresses and playback parameters (operating mode, step rate,
no-dwell control) are also configured through firmware update, using
metadata in the header.

Streaming data to the DDS core through the parallel data port at the
PD_CLK rate is not covered by this series. That functionality would
be added in a separate patch series, building on top of the IIO backend
infrastructure to provide a proper buffered data path.

As I am pushing implementation, as lot has been done already without much
supervision or agreement, still I would be interested on hearing about
the design choices discussed above.

Kind regards,

Rodrigo Alencar

Signed-off-by: Rodrigo Alencar <rodrigo.alencar@analog.com>
---
Changes in v3:
- RAM custom configs (address range, destination, modes) loaded during firmware write.
- DRG destination defined when attrs are written.
- DRG modes broken down into enable attrs for ramp up/down channels.
- Add separate profile channels, switching done through enable attr
- Link to v2: https://lore.kernel.org/r/20260318-ad9910-iio-driver-v2-0-e79f93becf11@analog.com

Changes in v2:
- Device-tree bindings changes.
- RAM loading to use firmware update interface.
- Rearrange of channels into a hierarchy.
- Link to v1: https://lore.kernel.org/r/20260220-ad9910-iio-driver-v1-0-3b264aa48a10@analog.com

---
Rodrigo Alencar (9):
      dt-bindings: iio: frequency: add ad9910
      iio: frequency: ad9910: initial driver implementation
      iio: frequency: ad9910: add simple parallel port mode support
      iio: frequency: ad9910: add digital ramp generator support
      iio: frequency: ad9910: add RAM mode support
      iio: frequency: ad9910: add output shift keying support
      iio: frequency: ad9910: add channel labels
      Documentation: ABI: testing: add docs for ad9910 sysfs entries
      docs: iio: add documentation for ad9910 driver

 .../ABI/testing/sysfs-bus-iio-frequency-ad9910     |   62 +
 .../bindings/iio/frequency/adi,ad9910.yaml         |  189 ++
 Documentation/iio/ad9910.rst                       |  586 ++++++
 Documentation/iio/index.rst                        |    1 +
 MAINTAINERS                                        |   10 +
 drivers/iio/frequency/Kconfig                      |   20 +
 drivers/iio/frequency/Makefile                     |    1 +
 drivers/iio/frequency/ad9910.c                     | 2118 ++++++++++++++++++++
 8 files changed, 2987 insertions(+)
---
base-commit: ff0843ceb1fb11a6b73e0e77b932ef7967aecd4b
change-id: 20260218-ad9910-iio-driver-9b3d214c251f

Best regards,
-- 
Rodrigo Alencar <rodrigo.alencar@analog.com>



^ permalink raw reply


This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox