From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 58C153C661D; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=KanRZ0a/uDJ2fRWetaBXKvf00ZOa8eHPMOom9Pjcsp+Px12G1+Me0nQcDjC6xGJZMuLnqyzGFFxrirDxtC0r3dfu9wm0+hiabt9FrsXghWC8HzWGTh9SsoVS8vPHWighkuKlmp05xpk2meZv2luLfIJPuUXZ98m5LckFqwfnDuw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=gSzjzZpWyN5p9u4MH/AHyWy9legDqAcplirEJBm6wWM=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=LO6b0cbhJkQ4L9+zk6ZA23fS7jM4iGpPcs8l7duaPnxkWjbOb8u7XLNCZwcTijgNbMzCf1MqGiOw4nPVuiuU8+c8PZWZJbvG6flUja6WPxMdKxpN/ephA3Jmc1M6eaOsz9BP+8a3QMi3eNPhfGLsNfHrkyw1a84pX2Ohs+0Dz3c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ZIX5ophN; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ZIX5ophN" Received: by smtp.kernel.org (Postfix) with ESMTPS id 28953C2BCC6; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=gSzjzZpWyN5p9u4MH/AHyWy9legDqAcplirEJBm6wWM=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ZIX5ophNJ99CR/R5vHEhHATCg1xKniWykwyM2ukiIlbfszniHv33/7Fimdv49OqVW ewoeYB+DSJ+SACkfeC4T13QIHgdC9STwSaz6iIGweA+55cYdWTNRff+Aca+f7CQErO nq5/N1V2cab0JmY7Q6BpGgKB2kdJmll9HsOlUCnSvaDGIurNAGg+R+rVtm4ZYHc9SR yI0WnlexlwapbrJ02ccxKpV7zXQuCLRHz58Q4US8JgFV/AnCZxIr7KEVsqujNQW/K1 HCSG8uBsjmW9a7JmaicCgMQv5VhbpejIkC9YMTsWzEyKvBlzaynv8L2UdQcDGZPDtC Tv8ZGw/Rguqbw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 15E17CD4F4A; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:45 +0100 Subject: [PATCH v5 01/13] iio: core: validate file offset in iio_debugfs_write_reg() Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-1-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 , sashiko-bot@kernel.org X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=1108; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=rpvrvxHbTOo5EkMCZusLPjn94XbWzpS5zKDDEjpU2jk=; b=BUCN7UhT4hmZxpaaGhdA7LrjXns0A75goPn3s2+S4yaVEr9TuKBEURlCtB52ip/HlPOJOi0fE 8wEFa6dVUUkC7jGaRj3a1WBv5qF8rZn0kQiQvz24qf6tTaU2pmLMmT8 X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Check that a file offset is zero so that simple_write_to_buffer() can be used safely, i.e., buf array is not left with uninitialized memory at its start. It is not a big concern as it is a debug interface, but it is still a hardening measure. The issue was introduced when direct call to copy_from_user() was replaced by simple_write_to_buffer(). Fixes: 6d5dd486c715 ("iio: core: make use of simple_write_to_buffer()") Reported-by: sashiko-bot@kernel.org Signed-off-by: Rodrigo Alencar --- drivers/iio/industrialio-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-cor= e.c index bd6f4f9f4533..e587aca79b8e 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -419,7 +419,7 @@ static ssize_t iio_debugfs_write_reg(struct file *file, char buf[80]; int ret; =20 - if (count >=3D sizeof(buf)) + if (count >=3D sizeof(buf) || *ppos !=3D 0) return -EINVAL; =20 ret =3D simple_write_to_buffer(buf, sizeof(buf) - 1, ppos, userbuf, --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A5B43CF67B; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=ppXU3HHIHIjVeXvzWqlEdAJUtdgnbEvAfeIXcn5uIRMIyClFzM8tme7B5OEhjceIPa+LPStSInN8FN5LVBsnJqwFJcgHyp2P2NXYpB/jGDIIXDWzzJsQxaeQD0duUzRwTu3Ro3I9caPu0KfNW1+fGGkCnBjAWfL5dbsyGfQ+t6Q= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=XXEKBbnn2b4WIW8cukD4U2syNYLBt6kVGwp48KEnI44=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HoCoHVbc9frPJlDSl67W/DDGMfNJAhQ8FQ/YD4JdBNROU+BDfQFxf3dhnVSnn58VrmmID4juZZMHNCvpESvZxyb7hHzQFdjJr0FT/P/ZV2JspUwtwQC4ua8z4fNpFxI+uX4cSxNUsN0q43BfysjEmLwqZcaLbhpaWAHFef6H33I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=TqrFbPze; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="TqrFbPze" Received: by smtp.kernel.org (Postfix) with ESMTPS id 34DB3C2BCB8; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=XXEKBbnn2b4WIW8cukD4U2syNYLBt6kVGwp48KEnI44=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=TqrFbPzeC12woo/CGrncDVpKKlNxtjUv2kZQPC9o7/dH2F01yvmDXfRM4rhU4Mj0x aR8QMQ2cWy6RHfPjqzprLbkTJ4G8Ym/RowjZfXwPOIMvYrw99oqNXhL/oOGtlTZJ6E XnuMgba4FsLmgi0f81MRS2ZKtg56n6YXORcGk38nPZvWmBZXEpFKiEX+toHNrdrPDF BpgMnOPa7eArSiP9ZdQ7yf9SM6249hsB3avP02e44yh+1xke4OfmfnMcuL+ztXdxa8 nXnLbTSdAFTxtKn2YCBbMO+BT2riccn3weEDJ2JWkiXW+WktQjeGQaCGOy5xWbDNRc DEQQJ+FQOAmOw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 262C4CD4F47; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:46 +0100 Subject: [PATCH v5 02/13] iio: core: support 64-bit register through debugfs Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-2-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=5120; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=wYppTE64TIXIigAhylTVeSmh8UE6/agBsJuV1e7zqxw=; b=lPOYHAlpyoACJ0rgFx+Ed5BVdDLxq5fDnw0yOn7H2IEAYv7td5RsTKjES5Fqoz9CyalLBsZSt rxTy3JPXnJWD0r5vT1R+VVErnjoDJI1dMfIjjgUV4m4k5ArjSy5Huw4 X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add debugfs_reg64_access function pointer field into iio_info and modify file operation callbacks to favor 64-bit variant when it is available. Signed-off-by: Rodrigo Alencar --- drivers/iio/industrialio-core.c | 33 ++++++++++++++++++++++++--------- include/linux/iio/iio-opaque.h | 2 +- include/linux/iio/iio.h | 4 ++++ 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-cor= e.c index e587aca79b8e..5c8404efd0a5 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -386,6 +386,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, = char __user *userbuf, struct iio_dev *indio_dev =3D file->private_data; struct iio_dev_opaque *iio_dev_opaque =3D to_iio_dev_opaque(indio_dev); unsigned int val =3D 0; + u64 val64 =3D 0; int ret; =20 if (*ppos > 0) @@ -393,9 +394,17 @@ static ssize_t iio_debugfs_read_reg(struct file *file,= char __user *userbuf, iio_dev_opaque->read_buf, iio_dev_opaque->read_buf_len); =20 - ret =3D indio_dev->info->debugfs_reg_access(indio_dev, - iio_dev_opaque->cached_reg_addr, - 0, &val); + if (indio_dev->info->debugfs_reg64_access) { + ret =3D indio_dev->info->debugfs_reg64_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val64); + } else { + ret =3D indio_dev->info->debugfs_reg_access(indio_dev, + iio_dev_opaque->cached_reg_addr, + 0, &val); + val64 =3D val; + } + if (ret) { dev_err(indio_dev->dev.parent, "%s: read failed\n", __func__); return ret; @@ -403,7 +412,7 @@ static ssize_t iio_debugfs_read_reg(struct file *file, = char __user *userbuf, =20 iio_dev_opaque->read_buf_len =3D snprintf(iio_dev_opaque->read_buf, sizeof(iio_dev_opaque->read_buf), - "0x%X\n", val); + "0x%llX\n", val64); =20 return simple_read_from_buffer(userbuf, count, ppos, iio_dev_opaque->read_buf, @@ -415,8 +424,9 @@ static ssize_t iio_debugfs_write_reg(struct file *file, { struct iio_dev *indio_dev =3D file->private_data; struct iio_dev_opaque *iio_dev_opaque =3D to_iio_dev_opaque(indio_dev); - unsigned int reg, val; + unsigned int reg; char buf[80]; + u64 val64; int ret; =20 if (count >=3D sizeof(buf) || *ppos !=3D 0) @@ -429,7 +439,7 @@ static ssize_t iio_debugfs_write_reg(struct file *file, =20 buf[ret] =3D '\0'; =20 - ret =3D sscanf(buf, "%i %i", ®, &val); + ret =3D sscanf(buf, "%i %lli", ®, &val64); =20 switch (ret) { case 1: @@ -437,8 +447,12 @@ static ssize_t iio_debugfs_write_reg(struct file *file, break; case 2: iio_dev_opaque->cached_reg_addr =3D reg; - ret =3D indio_dev->info->debugfs_reg_access(indio_dev, reg, - val, NULL); + if (indio_dev->info->debugfs_reg64_access) + ret =3D indio_dev->info->debugfs_reg64_access(indio_dev, reg, + val64, NULL); + else + ret =3D indio_dev->info->debugfs_reg_access(indio_dev, reg, + val64, NULL); if (ret) { dev_err(indio_dev->dev.parent, "%s: write failed\n", __func__); @@ -469,7 +483,8 @@ static void iio_device_register_debugfs(struct iio_dev = *indio_dev) { struct iio_dev_opaque *iio_dev_opaque; =20 - if (indio_dev->info->debugfs_reg_access =3D=3D NULL) + if (!indio_dev->info->debugfs_reg_access && + !indio_dev->info->debugfs_reg64_access) return; =20 if (!iio_debugfs_dentry) diff --git a/include/linux/iio/iio-opaque.h b/include/linux/iio/iio-opaque.h index b87841a355f8..98330385e08d 100644 --- a/include/linux/iio/iio-opaque.h +++ b/include/linux/iio/iio-opaque.h @@ -73,7 +73,7 @@ struct iio_dev_opaque { #if defined(CONFIG_DEBUG_FS) struct dentry *debugfs_dentry; unsigned int cached_reg_addr; - char read_buf[20]; + char read_buf[24]; unsigned int read_buf_len; #endif }; diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 96b05c86c325..86d17ee69e05 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -484,6 +484,7 @@ struct iio_trigger; /* forward declaration */ * @update_scan_mode: function to configure device and scan buffer when * channels have changed * @debugfs_reg_access: function to read or write register value of device + * @debugfs_reg64_access: function to read or write 64-bit register value = of device * @fwnode_xlate: fwnode based function pointer to obtain channel specifie= r index. * @hwfifo_set_watermark: function pointer to set the current hardware * fifo watermark level; see hwfifo_* entries in @@ -572,6 +573,9 @@ struct iio_info { int (*debugfs_reg_access)(struct iio_dev *indio_dev, unsigned int reg, unsigned int writeval, unsigned int *readval); + int (*debugfs_reg64_access)(struct iio_dev *indio_dev, + unsigned int reg, u64 writeval, + u64 *readval); int (*fwnode_xlate)(struct iio_dev *indio_dev, const struct fwnode_reference_args *iiospec); int (*hwfifo_set_watermark)(struct iio_dev *indio_dev, unsigned int val); --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8A6383CF67F; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=ZHP1dZ5uhU7b0a9NYs7oojbewHQ2M4ZwfOqKIgRwiZMkR3MuzUwMJhZ0YCbiqgSaNXqNL0idTWWZpnk2V0oWYZL93vWMjAJW4rPAoBrWfxqh3cRayowJmB0G6pNLHNohQzavXXcfcnETdMxlBbNAwcmJ8uuOPfqVk5tR8gIRDgo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=uSGL8dMbIPWbCHilTKkbyu1O1HC9fYsQy2nXzCbqsRA=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=uFwmczMj1/uRpGjqlxRFqT3BRD/7kGITVPAmhlNI0T01aq7yV0lt1joEtsD7mpmGCmi9mALtPenvFv+/mLO+OzM4MgQihtijOi0MERQbjpweFr+bbDe5TYmpdrl549RPUUoqzIIBFqDa6YgomOMYm2vAaToTqUR+N+ss/1XeCq4= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=ln83ZWhJ; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="ln83ZWhJ" Received: by smtp.kernel.org (Postfix) with ESMTPS id 3F8A9C2BCFD; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=uSGL8dMbIPWbCHilTKkbyu1O1HC9fYsQy2nXzCbqsRA=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=ln83ZWhJtVD82v89MPR1kcP+HU1Yv5SG1jkVdwfKXiZ6Xd6dCYwxQ3mBTcaTeC94h T6Rqp6i9NFgX5DCXWkSKT9CC9G1fZ4gR8ZLRdxkZewpV+3V+acg6TjAv+2i8UyQjJh s8AU+GCTcVHDvWLtJiRs1I33UcJU8ScdNIp3DoE9eBOPf5KQn8Ov58yD0KDLf5rAj1 D34qU3bD/lRgcyx39nwUcHOD0ob7Stdclw5Nhg6KvIVZ8V0JNIy8mFaqDgFN1rel+6 IouTzxK9l9Hots5+RxWslfiyZgrY/VyEZMUdiN7XCuk1Ojkc7029KN2mPol8nrRgaG UExtKmbF/lSIw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 35EFACD4F3D; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:47 +0100 Subject: [PATCH v5 03/13] iio: core: add hierarchical channel relationships Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-3-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=3380; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=A7fhI4etUphuvUOLbYT+tvvvSQiaXddEibbLDuTWMKQ=; b=O166oJxTAzSETzOt/aK2ziw8mQeBeTOK5BgBoxWQh9PTsv+5c8xmim0lhMPYWXWmqwJ3rwPtr R2rn8X3X8VmDxfHjtsa4meQ9o7lZHkZGYcPqwyIIh6w4TSiSHScY2vg X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add parent-child relationship between iio channels by creating a parent pointer field in iio_chan_spec struct and exposing a sysfs attribute that returns the parent channel label. Signed-off-by: Rodrigo Alencar --- drivers/iio/industrialio-core.c | 38 ++++++++++++++++++++++++++++++++++++++ include/linux/iio/iio.h | 5 +++++ 2 files changed, 43 insertions(+) diff --git a/drivers/iio/industrialio-core.c b/drivers/iio/industrialio-cor= e.c index 5c8404efd0a5..348ac7a59738 100644 --- a/drivers/iio/industrialio-core.c +++ b/drivers/iio/industrialio-core.c @@ -776,6 +776,14 @@ static ssize_t iio_read_channel_label(struct device *d= ev, to_iio_dev_attr(attr)->c, buf); } =20 +static ssize_t iio_read_channel_parent(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + return do_iio_read_channel_label(dev_to_iio_dev(dev), + to_iio_dev_attr(attr)->c->parent, buf); +} + static ssize_t iio_read_channel_info(struct device *dev, struct device_attribute *attr, char *buf) @@ -1263,6 +1271,31 @@ static int iio_device_add_channel_label(struct iio_d= ev *indio_dev, return 1; } =20 +static int iio_device_add_channel_parent(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan) +{ + struct iio_dev_opaque *iio_dev_opaque =3D to_iio_dev_opaque(indio_dev); + int ret; + + if (!chan->parent || (!indio_dev->info->read_label && + !chan->parent->extend_name)) + return 0; + + ret =3D __iio_add_chan_devattr("parent", + chan, + &iio_read_channel_parent, + NULL, + 0, + IIO_SEPARATE, + &indio_dev->dev, + NULL, + &iio_dev_opaque->channel_attr_list); + if (ret < 0) + return ret; + + return 1; +} + static int iio_device_add_info_mask_type(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, enum iio_shared_by shared_by, @@ -1401,6 +1434,11 @@ static int iio_device_add_channel_sysfs(struct iio_d= ev *indio_dev, return ret; attrcount +=3D ret; =20 + ret =3D iio_device_add_channel_parent(indio_dev, chan); + if (ret < 0) + return ret; + attrcount +=3D ret; + if (chan->ext_info) { unsigned int i =3D 0; =20 diff --git a/include/linux/iio/iio.h b/include/linux/iio/iio.h index 86d17ee69e05..09a97518e4bd 100644 --- a/include/linux/iio/iio.h +++ b/include/linux/iio/iio.h @@ -258,6 +258,10 @@ struct iio_scan_type { * by all channels. * @info_mask_shared_by_all_available: What availability information is to= be * exported that is shared by all channels. + * @parent: Optional pointer to the parent channel spec for + * hierarchical channel relationships. When set, a read-only + * "parent" sysfs attribute is created containing the + * parent channel's label. * @event_spec: Array of events which should be registered for this * channel. * @num_event_specs: Size of the event_spec array. @@ -306,6 +310,7 @@ struct iio_chan_spec { unsigned long info_mask_shared_by_dir_available; unsigned long info_mask_shared_by_all; unsigned long info_mask_shared_by_all_available; + const struct iio_chan_spec *parent; const struct iio_event_spec *event_spec; unsigned int num_event_specs; const struct iio_chan_spec_ext_info *ext_info; --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 7943A3CF045; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=REoqx5cCmX/albaXXJYJ9Ucr+AB3FgqNgtoUI0PtFMVtq4w7BNXK5uj0tDO2PQOZdtJ/50uB+C2mlhdh1L/9BXQG09V8i2ma9DqiWXCJ4RvvmRalSnKq1QER98f4NKKY8FK+86HAJK8bEYDu6Ck2ArYvjA6t9bu2D1m8SRR6yy8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=2dSzBuo6NGIU/MzLmZIMIR/j3XnHkSP0ZXWfWsJr7aY=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=ISoXKuvwdKEnTXOS/MpGwEosuHNbSmKww5FR+eaORNKh4OVSCV3oVsRy58Vs0+1nq4s22W7a5GQ2dGIV1D9gSJFawm4BinIcqJ3jiZED39Ug+oYV32pdpWqWiEWgXOYQgvZhNwieJgJM6DRfPFuIkqaMiPmsT7FUTV88U3GGzwM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=OcrruC1V; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="OcrruC1V" Received: by smtp.kernel.org (Postfix) with ESMTPS id 5207BC2BCFF; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=2dSzBuo6NGIU/MzLmZIMIR/j3XnHkSP0ZXWfWsJr7aY=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=OcrruC1V+l+oEwuXlQCaER3JnXH+mlh1PUHmEDKUsYW6WCrfoVo+KylgFuWLH/9gW gpoFSGhLB3ulkLCXajlXTIpI7nQYo8Sll2z14neiVgCNbrpp5rZPDTYoAd64h0Hnhi MVGQ9ZPHzrmRF7h1K/rN9tAcK7DuvtKpYd2fykDrJuX7jmYVjnpojGjjGGFfvavfjM 1M0S3L2rvZ8p6vxA64ZaF0UG2L0TTO8S0oLBbu5+fujFnUsHRCph8IES9W32zgIgIK GsSl01cphHysY+NQXk4ExvatOKhxkSi6bzficQOglF5rG5d+qcaVZUZNxzRILdVNxn CvyifCrCMe7+Q== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 45781CD4F4D; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:48 +0100 Subject: [PATCH v5 04/13] Documentation: ABI: testing: add parent entry for iio channels Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-4-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=1464; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=XOyaqKzDL3PPgyWAcsX4v184IyQlIm8Au4yWWWsAm+k=; b=rZVnmvddX2rI5fphSz4iKSZnXP3zEVP+jKYJeq7KlWz+a8soPLFMO3QB8k7Yy2cXZPLWNMeaB ZDzkhX63Qz9C/gMYJ1GSUy1dL6zNyul++R5Czl7FJMSuEwPY5OEI7MM X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add documentation for a read-only sysfs attribute that allows to expose parent-child relationships between IIO channels. Signed-off-by: Rodrigo Alencar --- Documentation/ABI/testing/sysfs-bus-iio | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio b/Documentation/ABI/te= sting/sysfs-bus-iio index 925a33fd309a..399944974e34 100644 --- a/Documentation/ABI/testing/sysfs-bus-iio +++ b/Documentation/ABI/testing/sysfs-bus-iio @@ -2118,6 +2118,19 @@ Description: specific attributes. This is useful for userspace to be able to better identify an individual channel. =20 +What: /sys/bus/iio/devices/iio:deviceX/in_voltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/out_voltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/in_altvoltageY_parent +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_parent +KernelVersion: 7.1 +Contact: linux-iio@vger.kernel.org +Description: + Read-only attribute containing the label of the parent channel + for hierarchical channel relationships. Only present on channels + that have a parent channel with a valid label. This is useful for + userspace to organize channels in tree-like structures that reflects + the physical or logical relationships between them. + What: /sys/bus/iio/devices/iio:deviceX/in_phaseY_raw KernelVersion: 4.18 Contact: linux-iio@vger.kernel.org --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8B8793CF680; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=mdYyP7JupF5mQQSZ8d5NNLhz42QGhvzT6kXiiOcZMmka9w8kRb0zH5iMtxsC3GX7LFm6sCztAI+pp61XHlqrTkbu535ftpMjrGX3FYLv7BxTCApe7JK036bzgmfzViS7u1WxcEVleXyOkgQeDWd91SvUcRC4TMWKfWlnabZq58I= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=vw/6QJJ2gS7QVTkMQLuUMXyrddyW6VBFtu0qRhUhZsw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=NZfiz3qTADv6urgMYjVuxQJYc2QkfBWkQZHNVdsbwx1KEQMKkh+QwJhMQPwdVt58Z2rVUocWgwGNbSbtnV/AUM5ZJf/lPJ+mEx2jfYjUFtau2XZRySYsdj5SG4SZZcdOMg39IU5ua2z9RkndYUfJjVmoYfBaSEkWmWxgVhJBXds= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=i6dQnPQd; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="i6dQnPQd" Received: by smtp.kernel.org (Postfix) with ESMTPS id 614F8C4AF14; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=vw/6QJJ2gS7QVTkMQLuUMXyrddyW6VBFtu0qRhUhZsw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=i6dQnPQdikLKJSE/uTtXtHLRfpR4xDv5Eg5HIM3avVkvaP4SKpSBHyn18GcNk+DDJ EGRSveftHjyfSM28ACWCWmclWOZQ7e6BV4+ZB250Ou2NndG2TOBMjBNSBlnMwWNJzc saeHRqfFsiry4dC/J/KSbSNoRelJHal4ExjnoNx/KUqGDCxRkyFW/iUPUcU41GV5eQ webeWHxguLsen5f2HYmugpyGvTT5sidhi3V+I1xpMhdW9hAF3/RpYhhWOLk4b2Yyd7 VMUWqsCVH2arUIc7iwIpBw4helI1PP64a7VwLRscqE9mCpjsAwdEp5zGmJW4fUbB6+ J3EhQ84oQ63hQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 57273CD4F4F; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:49 +0100 Subject: [PATCH v5 05/13] dt-bindings: iio: frequency: add ad9910 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-5-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=6780; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=ng92/K6wd/76MVn3VHCyx1hqbcXC28DpSN9wOX05Goc=; b=8Xl6q7Li1bCjW9aCvSitPxC0g8sERIi0zu+LJTUjyD7+e9h7Bt9ZMSutxTV/OA05GZkMPps+9 /7PItZ2kDoqD5iAxjPAeUg28OLusVwLg2XaKlYWkVFxpFU2ZkXh506P X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar 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 --- .../bindings/iio/frequency/adi,ad9910.yaml | 200 +++++++++++++++++= ++++ MAINTAINERS | 7 + 2 files changed, 207 insertions(+) diff --git a/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yam= l b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml new file mode 100644 index 000000000000..5ab0db62dbf1 --- /dev/null +++ b/Documentation/devicetree/bindings/iio/frequency/adi,ad9910.yaml @@ -0,0 +1,200 @@ +# 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 + +description: + The AD9910 is a 1 GSPS direct digital synthesizer (DDS) with an integrat= ed + 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 + items: + - description: Reference clock (REF_CLK). + - description: 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 + items: + - description: + Signal that indicates that Digital Ramp Generator has reached a = limit. + - description: + Signal that indicates the end of a RAM Sweep. + + 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. + + lock-detect-gpios: + maxItems: 1 + description: + GPIO used to read PLL_LOCK pin status. + + adi,pll-enable: + type: boolean + description: + Indicates that a loop filter is connected and the internal PLL is en= abled. + 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 inter= nal + 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. + + output-range-microamp: + description: DAC full-scale output current in microamps. + items: + - const: 0 + - minimum: 8640 + maximum: 31590 + default: 20070 + +dependencies: + adi,charge-pump-current-microamp: [ 'adi,pll-enable' ] + adi,refclk-out-drive-strength: [ 'adi,pll-enable' ] + lock-detect-gpios: [ '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 + spi { + #address-cells =3D <1>; + #size-cells =3D <0>; + dds@0 { + compatible =3D "adi,ad9910"; + reg =3D <0>; + spi-max-frequency =3D <1000000>; + clocks =3D <&ad9910_refclk>; + clock-names =3D "ref_clk"; + + dvdd-io33-supply =3D <&vdd_io33>; + avdd33-supply =3D <&vdd_a33>; + dvdd18-supply =3D <&vdd_d18>; + avdd18-supply =3D <&vdd_a18>; + + reset-gpios =3D <&gpio 0 GPIO_ACTIVE_HIGH>; + io-reset-gpios =3D <&gpio 1 GPIO_ACTIVE_HIGH>; + powerdown-gpios =3D <&gpio 2 GPIO_ACTIVE_HIGH>; + update-gpios =3D <&gpio 3 GPIO_ACTIVE_HIGH>; + profile-gpios =3D <&gpio 4 GPIO_ACTIVE_HIGH>, + <&gpio 5 GPIO_ACTIVE_HIGH>, + <&gpio 6 GPIO_ACTIVE_HIGH>; + + adi,pll-enable; + adi,charge-pump-current-microamp =3D <387>; + adi,refclk-out-drive-strength =3D "disabled"; + output-range-microamp =3D <0 20070>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 3115538ce829..ea70b8449eb4 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1638,6 +1638,13 @@ W: https://ez.analog.com/linux-software-drivers F: Documentation/devicetree/bindings/iio/dac/adi,ad9739a.yaml F: drivers/iio/dac/ad9739a.c =20 +ANALOG DEVICES INC AD9910 DRIVER +M: Rodrigo Alencar +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 L: linux-iio@vger.kernel.org --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE4323D0907; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=YUuXAkLff4810fyAoYzA3NmAPoFOOuZT+T6dM1lQfIqWPj4HmN3o+iuIOaqQeuM22GXt99xrN1Rrwgm7zVAE5Hk5mBws2+7VqrBa15Lr5GJ3MgYGExLBiqHDlIh2kfGVsUA3GfVW9wfqL/OHwb9loqnXTb3257bHe2L+ptkVT1M= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=KCOSZlBFAQPBZj4X9mySPrMpwvSH1rwAFcDxDuNzSlU=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=Ieo3BEjvUPVFiDmP8O9jvW8m1FDClcuzJUffqDfUm88CpOVkOPW/xoHwaf0bj9A5gciiZgXEK+zw88rAX1wh9I2WXqPY6U6JqCiF7Sy9XaEQVQH7WocAtUIuQG3gVQmNYwWWQDp19TcRm72R+iUa+Nk9KBw0V2dTdTUfgpGNDX0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=dvZencic; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="dvZencic" Received: by smtp.kernel.org (Postfix) with ESMTPS id 720DEC4AF1D; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=KCOSZlBFAQPBZj4X9mySPrMpwvSH1rwAFcDxDuNzSlU=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=dvZencic9kKg9494Q4UludnHMwdswfZ06xh3RHMB/sF6EFrYgnN0CqK8O3e8Rcib6 wJUPp/Yzx0CNEOIOAhgUWVkW8o0rh5pA0i0KnMZLdkAlABvAnvU4MPw56MRwrcNmh0 g3tohpumWcDHs9hZ5kWR9V5ESv6EUP9NJXqDpD3/5yxdnxBFVu92JHXSgO0oQ0a3Rv W3WxknC4emwhicO9q/tDfYEPh2oAsGpVNrAcmnI3Qm6Q0+kfvWE7ijuo0XSk1B31Hx sTbUZZBJ4Ib7eigZbwMwktDpNMUPxy7IAVfoATn3SMDWWdI4bqaWNB68chPa6m3V4e FtWDTgtdo0p0A== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 6A411CD4F4A; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:50 +0100 Subject: [PATCH v5 06/13] iio: frequency: ad9910: initial driver implementation Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-6-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=34739; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=eS/Jp3zgUnwhd/ajOpG5mGVN/eiKtTMNL7TYsOL13jw=; b=TCUWOqpSfVjPRbnBGZPTPDD4dcacuUYX9auerbQqbhs5dCz6Tt1hamZoRj1YNABVODGoYxPV3 1mAwKVKCiZ+CdTL+cAKekGONy7ErrZk+bdoNS9xm70kBDe5MN/pB1Mr X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add the core AD9910 DDS driver infrastructure with single tone mode support. This includes SPI register access, profile management via GPIO pins, PLL/DAC configuration from firmware properties, and single tone frequency/phase/amplitude control through IIO attributes. Signed-off-by: Rodrigo Alencar --- MAINTAINERS | 1 + drivers/iio/frequency/Kconfig | 18 + drivers/iio/frequency/Makefile | 1 + drivers/iio/frequency/ad9910.c | 1060 ++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 1080 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index ea70b8449eb4..b2b7f54f5a24 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1644,6 +1644,7 @@ 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 +F: drivers/iio/frequency/ad9910.c =20 ANALOG DEVICES INC MAX22007 DRIVER M: Janani Sunil diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig index 583cbdf4e8cd..180e74f62d11 100644 --- a/drivers/iio/frequency/Kconfig +++ b/drivers/iio/frequency/Kconfig @@ -23,6 +23,24 @@ config AD9523 =20 endmenu =20 +menu "Direct Digital Synthesis" + +config AD9910 + tristate "Analog Devices AD9910 Direct Digital Synthesizer" + depends on SPI + depends on GPIOLIB + help + Say yes here to build support for Analog Devices AD9910 + 1 GSPS, 14-Bit DDS with integrated DAC. + + Supports single tone mode with 8 configurable profiles + and digital ramp generation. + + To compile this driver as a module, choose M here: the + module will be called ad9910. + +endmenu + # # Phase-Locked Loop (PLL) frequency synthesizers # diff --git a/drivers/iio/frequency/Makefile b/drivers/iio/frequency/Makefile index 70d0e0b70e80..39271dd209ca 100644 --- a/drivers/iio/frequency/Makefile +++ b/drivers/iio/frequency/Makefile @@ -5,6 +5,7 @@ =20 # When adding new entries keep the list in alphabetical order obj-$(CONFIG_AD9523) +=3D ad9523.o +obj-$(CONFIG_AD9910) +=3D ad9910.o obj-$(CONFIG_ADF4350) +=3D adf4350.o obj-$(CONFIG_ADF4371) +=3D adf4371.o obj-$(CONFIG_ADF4377) +=3D adf4377.o diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c new file mode 100644 index 000000000000..c7b1e474c92d --- /dev/null +++ b/drivers/iio/frequency/ad9910.c @@ -0,0 +1,1060 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AD9910 SPI DDS (Direct Digital Synthesizer) driver + * + * Copyright 2026 Analog Devices Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Register addresses */ +#define AD9910_REG_CFR1 0x00 +#define AD9910_REG_CFR2 0x01 +#define AD9910_REG_CFR3 0x02 +#define AD9910_REG_AUX_DAC 0x03 +#define AD9910_REG_IO_UPDATE_RATE 0x04 +#define AD9910_REG_FTW 0x07 +#define AD9910_REG_POW 0x08 +#define AD9910_REG_ASF 0x09 +#define AD9910_REG_MULTICHIP_SYNC 0x0A +#define AD9910_REG_DRG_LIMIT 0x0B +#define AD9910_REG_DRG_STEP 0x0C +#define AD9910_REG_DRG_RATE 0x0D +#define AD9910_REG_PROFILE0 0x0E +#define AD9910_REG_PROFILE1 0x0F +#define AD9910_REG_PROFILE2 0x10 +#define AD9910_REG_PROFILE3 0x11 +#define AD9910_REG_PROFILE4 0x12 +#define AD9910_REG_PROFILE5 0x13 +#define AD9910_REG_PROFILE6 0x14 +#define AD9910_REG_PROFILE7 0x15 +#define AD9910_REG_RAM 0x16 + +#define AD9910_REG_NUM_CACHED 0x16 +#define AD9910_REG_PROFILE(x) (AD9910_REG_PROFILE0 + (x)) + +/* CFR1 bit definitions */ +#define AD9910_CFR1_RAM_ENABLE_MSK BIT(31) +#define AD9910_CFR1_RAM_PLAYBACK_DEST_MSK GENMASK(30, 29) +#define AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK BIT(23) +#define AD9910_CFR1_INV_SINC_EN_MSK BIT(22) +#define AD9910_CFR1_INT_PROFILE_CTL_MSK GENMASK(20, 17) +#define AD9910_CFR1_SELECT_SINE_MSK BIT(16) +#define AD9910_CFR1_LOAD_LRR_IO_UPDATE_MSK BIT(15) +#define AD9910_CFR1_AUTOCLR_DIG_RAMP_ACCUM_MSK BIT(14) +#define AD9910_CFR1_AUTOCLR_PHASE_ACCUM_MSK BIT(13) +#define AD9910_CFR1_CLEAR_DIG_RAMP_ACCUM_MSK BIT(12) +#define AD9910_CFR1_CLEAR_PHASE_ACCUM_MSK BIT(11) +#define AD9910_CFR1_LOAD_ARR_IO_UPDATE_MSK BIT(10) +#define AD9910_CFR1_OSK_ENABLE_MSK BIT(9) +#define AD9910_CFR1_SELECT_AUTO_OSK_MSK BIT(8) +#define AD9910_CFR1_DIGITAL_POWER_DOWN_MSK BIT(7) +#define AD9910_CFR1_DAC_POWER_DOWN_MSK BIT(6) +#define AD9910_CFR1_REFCLK_INPUT_POWER_DOWN_MSK BIT(5) +#define AD9910_CFR1_AUX_DAC_POWER_DOWN_MSK BIT(4) +#define AD9910_CFR1_SOFT_POWER_DOWN_MSK GENMASK(7, 4) +#define AD9910_CFR1_EXT_POWER_DOWN_CTL_MSK BIT(3) +#define AD9910_CFR1_SDIO_INPUT_ONLY_MSK BIT(1) +#define AD9910_CFR1_LSB_FIRST_MSK BIT(0) + +/* CFR2 bit definitions */ +#define AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK BIT(24) +#define AD9910_CFR2_INTERNAL_IO_UPDATE_MSK BIT(23) +#define AD9910_CFR2_SYNC_CLK_EN_MSK BIT(22) +#define AD9910_CFR2_DRG_DEST_MSK GENMASK(21, 20) +#define AD9910_CFR2_DRG_ENABLE_MSK BIT(19) +#define AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK BIT(18) +#define AD9910_CFR2_DRG_NO_DWELL_LOW_MSK BIT(17) +#define AD9910_CFR2_DRG_NO_DWELL_MSK GENMASK(18, 17) +#define AD9910_CFR2_READ_EFFECTIVE_FTW_MSK BIT(16) +#define AD9910_CFR2_IO_UPDATE_RATE_CTL_MSK GENMASK(15, 14) +#define AD9910_CFR2_PDCLK_ENABLE_MSK BIT(11) +#define AD9910_CFR2_PDCLK_INVERT_MSK BIT(10) +#define AD9910_CFR2_TXENABLE_INVERT_MSK BIT(9) +#define AD9910_CFR2_MATCHED_LATENCY_EN_MSK BIT(7) +#define AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK BIT(6) +#define AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK BIT(5) +#define AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK BIT(4) +#define AD9910_CFR2_FM_GAIN_MSK GENMASK(3, 0) + +/* CFR3 bit definitions */ +#define AD9910_CFR3_OPEN_MSK 0x08070000 +#define AD9910_CFR3_DRV0_MSK GENMASK(29, 28) +#define AD9910_CFR3_VCO_SEL_MSK GENMASK(26, 24) +#define AD9910_CFR3_ICP_MSK GENMASK(21, 19) +#define AD9910_CFR3_REFCLK_DIV_BYPASS_MSK BIT(15) +#define AD9910_CFR3_REFCLK_DIV_RESETB_MSK BIT(14) +#define AD9910_CFR3_PFD_RESET_MSK BIT(10) +#define AD9910_CFR3_PLL_EN_MSK BIT(8) +#define AD9910_CFR3_N_MSK GENMASK(7, 1) + +/* Auxiliary DAC Control Register Bits */ +#define AD9910_AUX_DAC_FSC_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_STEP_SIZE_MSK GENMASK(1, 0) + +/* Multichip Sync Register Bits */ +#define AD9910_MC_SYNC_VALIDATION_DELAY_MSK GENMASK(31, 28) +#define AD9910_MC_SYNC_RECEIVER_ENABLE_MSK BIT(27) +#define AD9910_MC_SYNC_GENERATOR_ENABLE_MSK BIT(26) +#define AD9910_MC_SYNC_GENERATOR_POLARITY_MSK BIT(25) +#define AD9910_MC_SYNC_STATE_PRESET_MSK GENMASK(23, 18) +#define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11) +#define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3) + +/* 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) +#define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0) + +/* Device constants */ +#define AD9910_PI_NANORAD 3141592653UL + +#define AD9910_MAX_SYSCLK_HZ (1000 * HZ_PER_MHZ) +#define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500) + +#define AD9910_ASF_MAX GENMASK(13, 0) +#define AD9910_POW_MAX GENMASK(15, 0) +#define AD9910_NUM_PROFILES 8 + +/* PLL constants */ +#define AD9910_PLL_MIN_N 12 +#define AD9910_PLL_MAX_N 127 + +#define AD9910_PLL_IN_MIN_FREQ_HZ (3200 * HZ_PER_KHZ) +#define AD9910_PLL_IN_MAX_FREQ_HZ (60 * HZ_PER_MHZ) + +#define AD9910_PLL_OUT_MIN_FREQ_HZ (420 * HZ_PER_MHZ) +#define AD9910_PLL_OUT_MAX_FREQ_HZ AD9910_MAX_SYSCLK_HZ + +#define AD9910_VCO0_RANGE_AUTO_MAX_HZ (457 * HZ_PER_MHZ) +#define AD9910_VCO1_RANGE_AUTO_MAX_HZ (530 * HZ_PER_MHZ) +#define AD9910_VCO2_RANGE_AUTO_MAX_HZ (632 * HZ_PER_MHZ) +#define AD9910_VCO3_RANGE_AUTO_MAX_HZ (775 * HZ_PER_MHZ) +#define AD9910_VCO4_RANGE_AUTO_MAX_HZ (897 * HZ_PER_MHZ) +#define AD9910_VCO_RANGE_NUM 6 + +#define AD9910_REFCLK_OUT_DRV_DISABLED 0 + +#define AD9910_ICP_MIN_uA 212 +#define AD9910_ICP_MAX_uA 387 +#define AD9910_ICP_STEP_uA 25 + +#define AD9910_DAC_IOUT_MAX_uA 31590 +#define AD9910_DAC_IOUT_DEFAULT_uA 20070 +#define AD9910_DAC_IOUT_MIN_uA 8640 + +#define AD9910_REFDIV2_MIN_FREQ_HZ (120 * HZ_PER_MHZ) +#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_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) + +/** + * enum ad9910_channel - AD9910 channel identifiers in priority order + * + * @AD9910_CHANNEL_PHY: Physical output channel + * @AD9910_CHANNEL_PROFILE_0: Profile 0 output channel + * @AD9910_CHANNEL_PROFILE_1: Profile 1 output channel + * @AD9910_CHANNEL_PROFILE_2: Profile 2 output channel + * @AD9910_CHANNEL_PROFILE_3: Profile 3 output channel + * @AD9910_CHANNEL_PROFILE_4: Profile 4 output channel + * @AD9910_CHANNEL_PROFILE_5: Profile 5 output channel + * @AD9910_CHANNEL_PROFILE_6: Profile 6 output channel + * @AD9910_CHANNEL_PROFILE_7: Profile 7 output channel + */ +enum ad9910_channel { + AD9910_CHANNEL_PHY =3D 100, + AD9910_CHANNEL_PROFILE_0 =3D 110, + AD9910_CHANNEL_PROFILE_1 =3D 111, + AD9910_CHANNEL_PROFILE_2 =3D 112, + AD9910_CHANNEL_PROFILE_3 =3D 113, + AD9910_CHANNEL_PROFILE_4 =3D 114, + AD9910_CHANNEL_PROFILE_5 =3D 115, + AD9910_CHANNEL_PROFILE_6 =3D 116, + AD9910_CHANNEL_PROFILE_7 =3D 117, +}; + +enum { + AD9910_CHAN_IDX_PHY, + AD9910_CHAN_IDX_PROFILE_0, + AD9910_CHAN_IDX_PROFILE_1, + AD9910_CHAN_IDX_PROFILE_2, + AD9910_CHAN_IDX_PROFILE_3, + AD9910_CHAN_IDX_PROFILE_4, + AD9910_CHAN_IDX_PROFILE_5, + AD9910_CHAN_IDX_PROFILE_6, + AD9910_CHAN_IDX_PROFILE_7, +}; + +enum { + AD9910_POWERDOWN, +}; + +struct ad9910_data { + u32 sysclk_freq_hz; + u32 dac_output_current; + + u16 pll_charge_pump_current; + u8 refclk_out_drv; + bool pll_enabled; +}; + +union ad9910_reg { + u64 val64; + u32 val32; + u16 val16; +}; + +struct ad9910_state { + struct spi_device *spi; + struct clk *refclk; + + struct gpio_desc *gpio_pwdown; + struct gpio_desc *gpio_update; + struct gpio_descs *gpio_profile; + + /* cached registers */ + union ad9910_reg reg[AD9910_REG_NUM_CACHED]; + + /* Lock for accessing device registers and state variables */ + struct mutex lock; + + struct ad9910_data data; + u8 profile; + + /* + * RAM loading requires a reasonable amount of bytes, at the same time + * DMA capable SPI drivers requires the transfer buffers to live in + * their own cache lines. + */ + u8 tx_buf[AD9910_SPI_MESSAGE_LEN_MAX] __aligned(IIO_DMA_MINALIGN); +}; + +/** + * ad9910_rational_scale() - Perform scaling of input given a reference. + * @input: The input value to be scaled. + * @scale: The numerator of the scaling factor. + * @reference: The denominator of the scaling factor. + * + * Closest rounding with mul_u64_add_u64_div_u64 + * + * Return: The scaled value. + */ +static inline u64 ad9910_rational_scale(u64 input, u64 scale, u64 referenc= e) +{ + return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference); +} + +static int ad9910_io_update(struct ad9910_state *st) +{ + if (st->gpio_update) { + gpiod_set_value_cansleep(st->gpio_update, 1); + udelay(1); + gpiod_set_value_cansleep(st->gpio_update, 0); + } + + return 0; +} + +static inline int ad9910_spi_read(struct ad9910_state *st, u8 reg, void *d= ata, + size_t len) +{ + u8 inst =3D AD9910_SPI_READ_MSK | FIELD_PREP(AD9910_SPI_ADDR_MSK, reg); + + return spi_write_then_read(st->spi, &inst, sizeof(inst), data, len); +} + +static inline int ad9910_spi_write(struct ad9910_state *st, u8 reg, size_t= len, + bool update) +{ + int ret; + + st->tx_buf[0] =3D FIELD_PREP(AD9910_SPI_ADDR_MSK, reg); + ret =3D spi_write(st->spi, st->tx_buf, AD9910_SPI_DATA_IDX + len); + if (ret) + return ret; + + if (update) + return ad9910_io_update(st); + + return 0; +} + +#define AD9910_REG_READ_FN(nb) \ +static int ad9910_reg##nb##_read(struct ad9910_state *st, u8 reg, \ + u##nb * data) \ +{ \ + __be##nb be_data; \ + int ret; \ + \ + ret =3D ad9910_spi_read(st, reg, &be_data, sizeof(be_data)); \ + if (ret) \ + return ret; \ + \ + *data =3D be##nb##_to_cpu(be_data); \ + return ret; \ +} + +AD9910_REG_READ_FN(16) +AD9910_REG_READ_FN(32) +AD9910_REG_READ_FN(64) + +#define AD9910_REG_WRITE_FN(nb) \ +static int ad9910_reg##nb##_write(struct ad9910_state *st, u8 reg, \ + u##nb data, bool update) \ +{ \ + int ret; \ + \ + put_unaligned_be##nb(data, &st->tx_buf[AD9910_SPI_DATA_IDX]); \ + ret =3D ad9910_spi_write(st, reg, sizeof(data), update); \ + if (ret) \ + return ret; \ + \ + st->reg[reg].val##nb =3D data; \ + return ret; \ +} + +AD9910_REG_WRITE_FN(16) +AD9910_REG_WRITE_FN(32) +AD9910_REG_WRITE_FN(64) + +#define AD9910_REG_UPDATE_FN(nb) \ +static int ad9910_reg##nb##_update(struct ad9910_state *st, \ + u8 reg, u##nb mask, \ + u##nb data, bool update) \ +{ \ + u##nb reg_val =3D (st->reg[reg].val##nb & ~mask) | (data & mask); \ + \ + if (reg_val =3D=3D st->reg[reg].val##nb && !update) \ + return 0; \ + \ + return ad9910_reg##nb##_write(st, reg, reg_val, update); \ +} + +AD9910_REG_UPDATE_FN(16) +AD9910_REG_UPDATE_FN(32) +AD9910_REG_UPDATE_FN(64) + +static int ad9910_set_dac_current(struct ad9910_state *st, bool update) +{ + u32 fsc_code; + + /* FSC =3D (86.4 / Rset) * (1 + CODE/256) where Rset =3D 10k ohms */ + fsc_code =3D DIV_ROUND_CLOSEST(st->data.dac_output_current, 90) - 96; + fsc_code &=3D 0xFF; + + return ad9910_reg32_write(st, AD9910_REG_AUX_DAC, fsc_code, update); +} + +static int ad9910_set_sysclk_freq(struct ad9910_state *st, u32 freq_hz, + bool update) +{ + struct device *dev =3D &st->spi->dev; + u32 sysclk_freq_hz, refclk_freq_hz; + u32 tmp32, vco_sel; + int ret; + + if (!freq_hz || freq_hz > AD9910_MAX_SYSCLK_HZ) + return -EINVAL; + + refclk_freq_hz =3D clk_get_rate(st->refclk); + if (st->data.pll_enabled) { + if (refclk_freq_hz < AD9910_PLL_IN_MIN_FREQ_HZ || + refclk_freq_hz > AD9910_PLL_IN_MAX_FREQ_HZ) { + dev_err(dev, + "REF_CLK frequency %u Hz is out of PLL input range\n", + refclk_freq_hz); + return -ERANGE; + } + + tmp32 =3D DIV_ROUND_CLOSEST(freq_hz, refclk_freq_hz); + tmp32 =3D clamp(tmp32, DIV_ROUND_UP(AD9910_PLL_OUT_MIN_FREQ_HZ, refclk_f= req_hz), + AD9910_PLL_OUT_MAX_FREQ_HZ / refclk_freq_hz); + tmp32 =3D clamp(tmp32, AD9910_PLL_MIN_N, AD9910_PLL_MAX_N); + sysclk_freq_hz =3D refclk_freq_hz * tmp32; + + if (sysclk_freq_hz <=3D AD9910_VCO0_RANGE_AUTO_MAX_HZ) + vco_sel =3D 0; + else if (sysclk_freq_hz <=3D AD9910_VCO1_RANGE_AUTO_MAX_HZ) + vco_sel =3D 1; + else if (sysclk_freq_hz <=3D AD9910_VCO2_RANGE_AUTO_MAX_HZ) + vco_sel =3D 2; + else if (sysclk_freq_hz <=3D AD9910_VCO3_RANGE_AUTO_MAX_HZ) + vco_sel =3D 3; + else if (sysclk_freq_hz <=3D AD9910_VCO4_RANGE_AUTO_MAX_HZ) + vco_sel =3D 4; + else + vco_sel =3D 5; + + ret =3D ad9910_reg32_update(st, AD9910_REG_CFR3, + AD9910_CFR3_N_MSK | AD9910_CFR3_VCO_SEL_MSK, + FIELD_PREP(AD9910_CFR3_N_MSK, tmp32) | + FIELD_PREP(AD9910_CFR3_VCO_SEL_MSK, vco_sel), + update); + if (ret) + return ret; + } else { + tmp32 =3D DIV_ROUND_CLOSEST(refclk_freq_hz, freq_hz); + tmp32 =3D clamp(tmp32, 1U, 2U); + sysclk_freq_hz =3D refclk_freq_hz / tmp32; + tmp32 =3D AD9910_CFR3_VCO_SEL_MSK | + FIELD_PREP(AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, tmp32 % 2); + ret =3D ad9910_reg32_update(st, AD9910_REG_CFR3, + AD9910_CFR3_VCO_SEL_MSK | + AD9910_CFR3_REFCLK_DIV_BYPASS_MSK, + tmp32, update); + if (ret) + return ret; + } + + st->data.sysclk_freq_hz =3D sysclk_freq_hz; + + return 0; +} + +static int ad9910_profile_set(struct ad9910_state *st, u8 profile) +{ + DECLARE_BITMAP(values, BITS_PER_TYPE(profile)); + + st->profile =3D profile; + values[0] =3D profile; + gpiod_multi_set_value_cansleep(st->gpio_profile, values); + + return 0; +} + +static inline bool ad9910_sw_powerdown_get(struct ad9910_state *st) +{ + return FIELD_GET(AD9910_CFR1_SOFT_POWER_DOWN_MSK, + st->reg[AD9910_REG_CFR1].val32) ? true : false; +} + +static int ad9910_sw_powerdown_set(struct ad9910_state *st, bool enable) +{ + if (ad9910_sw_powerdown_get(st) =3D=3D enable) + return 0; + + return ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SOFT_POWER_DOWN_MSK, + enable ? AD9910_CFR1_SOFT_POWER_DOWN_MSK : 0, + true); +} + +static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev, + uintptr_t private, + const struct iio_chan_spec *chan, + char *buf) +{ + struct ad9910_state *st =3D iio_priv(indio_dev); + int val; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_POWERDOWN: + val =3D ad9910_sw_powerdown_get(st); + break; + default: + return -EINVAL; + } + + return iio_format_value(buf, IIO_VAL_INT, 1, &val); +} + +static ssize_t ad9910_ext_info_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 =3D iio_priv(indio_dev); + u32 val32; + int ret; + + ret =3D kstrtou32(buf, 10, &val32); + if (ret) + return ret; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_POWERDOWN: + ret =3D ad9910_sw_powerdown_set(st, val32 ? true : false); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + +#define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \ + .name =3D _name, \ + .read =3D ad9910_ ## _fn_desc ## _read, \ + .write =3D ad9910_ ## _fn_desc ## _write, \ + .private =3D _ident, \ + .shared =3D _shared, \ +} + +#define AD9910_EXT_INFO(_name, _ident, _shared) \ + AD9910_EXT_INFO_TMPL(_name, _ident, _shared, ext_info) + +static const struct iio_chan_spec_ext_info ad9910_phy_ext_info[] =3D { + AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE), + { } +}; + +#define AD9910_PROFILE_CHAN(idx) { \ + .type =3D IIO_ALTVOLTAGE, \ + .indexed =3D 1, \ + .output =3D 1, \ + .channel =3D AD9910_CHANNEL_PROFILE_ ## idx, \ + .address =3D AD9910_CHAN_IDX_PROFILE_ ## idx, \ + .info_mask_separate =3D BIT(IIO_CHAN_INFO_ENABLE) | \ + BIT(IIO_CHAN_INFO_FREQUENCY) | \ + BIT(IIO_CHAN_INFO_PHASE) | \ + BIT(IIO_CHAN_INFO_SCALE), \ + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], \ +} + +static const struct iio_chan_spec ad9910_channels[] =3D { + [AD9910_CHAN_IDX_PHY] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_PHY, + .address =3D AD9910_CHAN_IDX_PHY, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info =3D ad9910_phy_ext_info, + }, + [AD9910_CHAN_IDX_PROFILE_0] =3D AD9910_PROFILE_CHAN(0), + [AD9910_CHAN_IDX_PROFILE_1] =3D AD9910_PROFILE_CHAN(1), + [AD9910_CHAN_IDX_PROFILE_2] =3D AD9910_PROFILE_CHAN(2), + [AD9910_CHAN_IDX_PROFILE_3] =3D AD9910_PROFILE_CHAN(3), + [AD9910_CHAN_IDX_PROFILE_4] =3D AD9910_PROFILE_CHAN(4), + [AD9910_CHAN_IDX_PROFILE_5] =3D AD9910_PROFILE_CHAN(5), + [AD9910_CHAN_IDX_PROFILE_6] =3D AD9910_PROFILE_CHAN(6), + [AD9910_CHAN_IDX_PROFILE_7] =3D AD9910_PROFILE_CHAN(7), +}; + +static int ad9910_read_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int *val, int *val2, long info) +{ + struct ad9910_state *st =3D iio_priv(indio_dev); + u64 tmp64; + u32 tmp32; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + if (ad9910_sw_powerdown_get(st)) { + *val =3D 0; + } else { + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + *val =3D (tmp32 =3D=3D st->profile); + } + break; + default: + return -EINVAL; + } + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 =3D FIELD_GET(AD9910_PROFILE_ST_FTW_MSK, + st->reg[AD9910_REG_PROFILE(tmp32)].val64); + break; + default: + return -EINVAL; + } + tmp64 *=3D st->data.sysclk_freq_hz; + *val =3D tmp64 >> 32; + *val2 =3D ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32; + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_PHASE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 =3D FIELD_GET(AD9910_PROFILE_ST_POW_MSK, + st->reg[AD9910_REG_PROFILE(tmp32)].val64); + tmp32 =3D (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16; + *val =3D tmp32 / MICRO; + *val2 =3D tmp32 % MICRO; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 =3D FIELD_GET(AD9910_PROFILE_ST_ASF_MSK, + st->reg[AD9910_REG_PROFILE(tmp32)].val64); + *val =3D 0; + *val2 =3D tmp64 * MICRO >> 14; + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + switch (chan->channel) { + case AD9910_CHANNEL_PHY: + *val =3D st->data.sysclk_freq_hz; + return IIO_VAL_INT; + default: + return -EINVAL; + } + default: + return -EINVAL; + } +} + +static int ad9910_write_raw(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + int val, int val2, long info) +{ + struct ad9910_state *st =3D iio_priv(indio_dev); + u64 tmp64; + u32 tmp32; + int ret; + + guard(mutex)(&st->lock); + + switch (info) { + case IIO_CHAN_INFO_ENABLE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + if (!val) { + if (tmp32 !=3D st->profile) + return 0; /* nothing to do */ + + return ad9910_sw_powerdown_set(st, true); + } + + ret =3D ad9910_sw_powerdown_set(st, false); + if (ret) + return ret; + + return ad9910_profile_set(st, tmp32); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_FREQUENCY: + if (val < 0 || val2 < 0 || val >=3D st->data.sysclk_freq_hz / 2) + return -EINVAL; + + tmp64 =3D ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32), + (u64)MICRO * st->data.sysclk_freq_hz); + tmp64 =3D min(tmp64, U32_MAX); + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 =3D FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_FTW_MSK, + tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_PHASE: + if (val < 0 || val2 < 0) + return -EINVAL; + + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 =3D (u64)val * MICRO + val2; + if (tmp64 >=3D AD9910_MAX_PHASE_MICRORAD) + return -EINVAL; + + tmp64 <<=3D 16; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD); + tmp64 =3D min(tmp64, AD9910_POW_MAX); + tmp64 =3D FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_POW_MSK, + tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SCALE: + if (val < 0 || val2 < 0 || val > 1 || (val =3D=3D 1 && val2 > 0)) + return -EINVAL; + + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; + tmp64 =3D ((u64)val * MICRO + val2) << 14; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, MICRO); + tmp64 =3D min(tmp64, AD9910_ASF_MAX); + tmp64 =3D FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64); + return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), + AD9910_PROFILE_ST_ASF_MSK, + tmp64, true); + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return ad9910_set_sysclk_freq(st, val, true); + default: + return -EINVAL; + } +} + +static int ad9910_write_raw_get_fmt(struct iio_dev *indio_dev, + struct iio_chan_spec const *chan, + long mask) +{ + switch (mask) { + case IIO_CHAN_INFO_ENABLE: + return IIO_VAL_INT; + case IIO_CHAN_INFO_FREQUENCY: + return IIO_VAL_INT_PLUS_MICRO; + case IIO_CHAN_INFO_PHASE: + case IIO_CHAN_INFO_SCALE: + switch (chan->channel) { + case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: + return IIO_VAL_INT_PLUS_MICRO; + default: + return -EINVAL; + } + case IIO_CHAN_INFO_SAMP_FREQ: + return IIO_VAL_INT; + default: + return -EINVAL; + } +} + +static int ad9910_debugfs_reg_access(struct iio_dev *indio_dev, + unsigned int reg, u64 writeval, + u64 *readval) +{ + struct ad9910_state *st =3D iio_priv(indio_dev); + union ad9910_reg tmp; + int ret; + + if (reg >=3D AD9910_REG_RAM) + return -EINVAL; + + guard(mutex)(&st->lock); + + switch (reg) { + case AD9910_REG_DRG_LIMIT: + case AD9910_REG_DRG_STEP: + case AD9910_REG_PROFILE0 ... AD9910_REG_PROFILE7: + if (!readval) + return ad9910_reg64_write(st, reg, writeval, true); + + ret =3D ad9910_reg64_read(st, reg, &tmp.val64); + if (ret) + return ret; + *readval =3D tmp.val64; + return 0; + case AD9910_REG_POW: + if (!readval) + return ad9910_reg16_write(st, reg, writeval, true); + + ret =3D ad9910_reg16_read(st, reg, &tmp.val16); + if (ret) + return ret; + *readval =3D tmp.val16; + return 0; + default: + if (!readval) + return ad9910_reg32_write(st, reg, writeval, true); + + ret =3D ad9910_reg32_read(st, reg, &tmp.val32); + if (ret) + return ret; + *readval =3D tmp.val32; + return 0; + } +} + +static const char * const ad9910_channel_str[] =3D { + [AD9910_CHAN_IDX_PHY] =3D "phy", + [AD9910_CHAN_IDX_PROFILE_0] =3D "profile0", + [AD9910_CHAN_IDX_PROFILE_1] =3D "profile1", + [AD9910_CHAN_IDX_PROFILE_2] =3D "profile2", + [AD9910_CHAN_IDX_PROFILE_3] =3D "profile3", + [AD9910_CHAN_IDX_PROFILE_4] =3D "profile4", + [AD9910_CHAN_IDX_PROFILE_5] =3D "profile5", + [AD9910_CHAN_IDX_PROFILE_6] =3D "profile6", + [AD9910_CHAN_IDX_PROFILE_7] =3D "profile7", +}; + +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 =3D { + .read_raw =3D ad9910_read_raw, + .write_raw =3D ad9910_write_raw, + .write_raw_get_fmt =3D ad9910_write_raw_get_fmt, + .read_label =3D ad9910_read_label, + .debugfs_reg64_access =3D &ad9910_debugfs_reg_access, +}; + +static int ad9910_cfg_sysclk(struct ad9910_state *st, bool update) +{ + u32 cfr3 =3D AD9910_CFR3_OPEN_MSK; + u32 tmp32; + + cfr3 |=3D FIELD_PREP(AD9910_CFR3_DRV0_MSK, st->data.refclk_out_drv); + + if (st->data.pll_enabled) { + tmp32 =3D st->data.pll_charge_pump_current - AD9910_ICP_MIN_uA; + tmp32 =3D DIV_ROUND_CLOSEST(tmp32, AD9910_ICP_STEP_uA); + cfr3 |=3D FIELD_PREP(AD9910_CFR3_ICP_MSK, tmp32) | + AD9910_CFR3_PLL_EN_MSK; + } else { + cfr3 |=3D AD9910_CFR3_ICP_MSK | + AD9910_CFR3_REFCLK_DIV_RESETB_MSK | + AD9910_CFR3_PFD_RESET_MSK; + } + st->reg[AD9910_REG_CFR3].val32 =3D cfr3; + + return ad9910_set_sysclk_freq(st, AD9910_MAX_SYSCLK_HZ, update); +} + +static int ad9910_parse_fw(struct ad9910_state *st) +{ + static const char * const refclk_out_drv0[] =3D { + "disabled", "low", "medium", "high", + }; + struct device *dev =3D &st->spi->dev; + u32 tmp[2]; + int ret; + + st->data.pll_enabled =3D device_property_read_bool(dev, "adi,pll-enable"); + if (st->data.pll_enabled) { + tmp[0] =3D AD9910_ICP_MIN_uA; + device_property_read_u32(dev, "adi,charge-pump-current-microamp", &tmp[0= ]); + if (tmp[0] < AD9910_ICP_MIN_uA || tmp[0] > AD9910_ICP_MAX_uA) + return dev_err_probe(dev, -ERANGE, + "invalid charge pump current %u\n", tmp[0]); + st->data.pll_charge_pump_current =3D tmp[0]; + + ret =3D device_property_match_property_string(dev, + "adi,refclk-out-drive-strength", + refclk_out_drv0, + ARRAY_SIZE(refclk_out_drv0)); + if (ret < 0) + st->data.refclk_out_drv =3D AD9910_REFCLK_OUT_DRV_DISABLED; + else + st->data.refclk_out_drv =3D ret; + } + + tmp[1] =3D AD9910_DAC_IOUT_DEFAULT_uA; + device_property_read_u32_array(dev, "output-range-microamp", tmp, + ARRAY_SIZE(tmp)); + if (tmp[1] < AD9910_DAC_IOUT_MIN_uA || tmp[1] > AD9910_DAC_IOUT_MAX_uA) + return dev_err_probe(dev, -ERANGE, + "Invalid DAC output current %u uA\n", tmp[1]); + st->data.dac_output_current =3D tmp[1]; + + return 0; +} + +static void ad9910_sw_powerdown_action(void *data) +{ + ad9910_sw_powerdown_set(data, true); +} + +static void ad9910_hw_powerdown_action(void *data) +{ + struct ad9910_state *st =3D data; + + gpiod_set_value_cansleep(st->gpio_pwdown, 1); +} + +static int ad9910_setup(struct device *dev, struct ad9910_state *st, + struct reset_control *dev_rst) +{ + int ret; + + ret =3D reset_control_deassert(dev_rst); + if (ret) + return ret; + + ret =3D ad9910_reg32_write(st, AD9910_REG_CFR1, + (st->spi->mode & SPI_3WIRE ? 0 : + AD9910_CFR1_SDIO_INPUT_ONLY_MSK), false); + if (ret) + return ret; + + ret =3D devm_add_action_or_reset(dev, ad9910_sw_powerdown_action, st); + if (ret) + return ret; + + ret =3D ad9910_reg32_write(st, AD9910_REG_CFR2, + AD9910_CFR2_AMP_SCALE_SINGLE_TONE_MSK | + AD9910_CFR2_SYNC_TIMING_VAL_DISABLE_MSK | + AD9910_CFR2_DRG_NO_DWELL_MSK | + AD9910_CFR2_DATA_ASM_HOLD_LAST_MSK | + AD9910_CFR2_SYNC_CLK_EN_MSK | + AD9910_CFR2_PDCLK_ENABLE_MSK, false); + if (ret) + return ret; + + ret =3D ad9910_cfg_sysclk(st, false); + if (ret) + return ret; + + ret =3D ad9910_set_dac_current(st, false); + if (ret) + return ret; + + return ad9910_io_update(st); +} + +static int ad9910_probe(struct spi_device *spi) +{ + static const char * const supplies[] =3D { + "dvdd-io33", "avdd33", "dvdd18", "avdd18", + }; + struct device *dev =3D &spi->dev; + struct reset_control *dev_rst; + struct gpio_desc *io_rst_gpio; + struct iio_dev *indio_dev; + struct ad9910_state *st; + int ret; + + indio_dev =3D devm_iio_device_alloc(dev, sizeof(*st)); + if (!indio_dev) + return -ENOMEM; + + st =3D iio_priv(indio_dev); + st->spi =3D spi; + + st->refclk =3D devm_clk_get_enabled(dev, "ref_clk"); + if (IS_ERR(st->refclk)) + return dev_err_probe(dev, PTR_ERR(st->refclk), + "Failed to get reference clock\n"); + + ret =3D devm_regulator_bulk_get_enable(dev, ARRAY_SIZE(supplies), supplie= s); + if (ret) + return dev_err_probe(dev, ret, "Failed to get regulators\n"); + + ret =3D devm_mutex_init(dev, &st->lock); + if (ret) + return ret; + + indio_dev->name =3D "ad9910"; + indio_dev->info =3D &ad9910_info; + indio_dev->modes =3D INDIO_DIRECT_MODE; + indio_dev->channels =3D ad9910_channels; + indio_dev->num_channels =3D ARRAY_SIZE(ad9910_channels); + + dev_rst =3D devm_reset_control_get_optional_exclusive(dev, NULL); + if (IS_ERR(dev_rst)) + return dev_err_probe(dev, PTR_ERR(dev_rst), + "failed to get device reset control\n"); + + /* + * The IO RESET pin is not used in this driver, as we assume that all + * SPI transfers are complete, but if it is wired up, we need to make + * sure it is not floating. We can use either a reset controller or a + * GPIO for this. + */ + io_rst_gpio =3D devm_gpiod_get_optional(dev, "io-reset", GPIOD_OUT_LOW); + if (IS_ERR(io_rst_gpio)) + return dev_err_probe(dev, PTR_ERR(io_rst_gpio), + "failed to get io reset gpio\n"); + + st->gpio_update =3D devm_gpiod_get_optional(dev, "update", GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_update)) + return dev_err_probe(dev, PTR_ERR(st->gpio_update), + "failed to get update gpio\n"); + + st->gpio_profile =3D devm_gpiod_get_array_optional(dev, "profile", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_profile)) + return dev_err_probe(dev, PTR_ERR(st->gpio_profile), + "failed to get profile gpios\n"); + + st->gpio_pwdown =3D devm_gpiod_get_optional(dev, "powerdown", + GPIOD_OUT_LOW); + if (IS_ERR(st->gpio_pwdown)) + return dev_err_probe(dev, PTR_ERR(st->gpio_pwdown), + "failed to get powerdown gpio\n"); + + ret =3D devm_add_action_or_reset(dev, ad9910_hw_powerdown_action, st); + if (ret) + return dev_err_probe(dev, ret, + "failed to add hw powerdown action\n"); + + ret =3D ad9910_parse_fw(st); + if (ret) + return ret; + + ret =3D ad9910_setup(dev, st, dev_rst); + if (ret) + return dev_err_probe(dev, ret, "device setup failed\n"); + + return devm_iio_device_register(dev, indio_dev); +} + +static const struct spi_device_id ad9910_id[] =3D { + { "ad9910" }, + { } +}; +MODULE_DEVICE_TABLE(spi, ad9910_id); + +static const struct of_device_id ad9910_of_match[] =3D { + { .compatible =3D "adi,ad9910" }, + { } +}; +MODULE_DEVICE_TABLE(of, ad9910_of_match); + +static struct spi_driver ad9910_driver =3D { + .driver =3D { + .name =3D "ad9910", + .of_match_table =3D ad9910_of_match, + }, + .probe =3D ad9910_probe, + .id_table =3D ad9910_id, +}; +module_spi_driver(ad9910_driver); + +MODULE_AUTHOR("Rodrigo Alencar "); +MODULE_DESCRIPTION("Analog Devices AD9910 DDS driver"); +MODULE_LICENSE("GPL"); --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DA91C3D0919; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; cv=none; b=JXU5ui+aH680YYtdes0arI5crugqr2z45Z1LxK8n+YlFadQwarju4iJhHleOG2BRWYLBbcess81jiSe+rJxe5xVKUSoEPY07m+4fbPQnYngYNvFbw6teZLvazRV4+v2ykEVUzXO+p6RWske17PRYvsJo79gakcdI6saOvDL4Kkw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043096; c=relaxed/simple; bh=OybH4aJnHtW4vcqTUf6jKHuG8Vkpm4uTnDXDjYhiuJ0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=tVJG4HtGG21IAe88e6wt+Y0mhozb2MV0ch6v+CLHlWWx+3zOT8mjt3Pwmrpd9TSIa7wq4DGZ9cSi43OQ1T27s5gugysMlmfWy1NzPi8XC/u8WzCxWOcG1mZrIzKHJyw5eap5sLsJisZeD74BAi+DzOSu4mFW1kJF150doCdj/kw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=LjxSoHqF; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="LjxSoHqF" Received: by smtp.kernel.org (Postfix) with ESMTPS id 87749C4AF63; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=OybH4aJnHtW4vcqTUf6jKHuG8Vkpm4uTnDXDjYhiuJ0=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=LjxSoHqFMnEuG+/3/DUY6dVeIy14n+i9ByaCIFc+OL6P4TNgRpo0kTJ4VnpR+2YKM 0pt+cDzkAbh2Wpf0+sD5RUVhltIhq5EXSTMQgFAfN/yWbhwGSV7CVMVKz4N9M8BoGk LPTxYG/W2dIHwGe16UiLub/s3C+Gcn3G9GLbAWuLeRhfRK3JENOhvmsjtFVv5Bq++n 9YROlpTyU3dkXtIfceTwfFma5/rjIHUAGR7UutXsWyi5iZQJyCVAu1qVeEaoBVXv/0 1M9fETnLUfXXq7kTaMZkLMAFKdA21t1TGuYSaluZULvbhq2U6fVi2NiU+3sT9v6shc gOMKtfPHqiEuQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 7B3F2CD4F3D; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:51 +0100 Subject: [PATCH v5 07/13] iio: frequency: ad9910: add basic parallel port support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-7-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=7799; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=HHXvaPp38MKZcS7jVNJ49gCWgpBxPRPLRK67CceFj8M=; b=aUesB46GNDRlLQWIm7kRqdIa950gz3f/ozaCBEG6CxqT7nV9oEDkNXxPxQRfCIqtaOge2IItW ubabJZdh7k0DPgenq2UuqRBSndycsnpYwOZLJQH82tDtpjluH93hdfH X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add parallel port channel with frequency scale, frequency offset, phase offset, and amplitude offset extended attributes for configuring the parallel data path. Enabling and disabling of parallel mode will be implemented with buffer setup ops when an IIO backend integration is in place. Signed-off-by: Rodrigo Alencar --- drivers/iio/frequency/ad9910.c | 147 +++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 147 insertions(+) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index c7b1e474c92d..d2f40927b9be 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -112,9 +112,13 @@ /* Auxiliary DAC Control Register Bits */ #define AD9910_AUX_DAC_FSC_MSK GENMASK(7, 0) =20 +/* 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) =20 /* Multichip Sync Register Bits */ @@ -138,7 +142,9 @@ #define AD9910_MAX_PHASE_MICRORAD (AD9910_PI_NANORAD / 500) =20 #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 =20 /* PLL constants */ @@ -189,6 +195,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 =3D 100, @@ -200,6 +207,7 @@ enum ad9910_channel { AD9910_CHANNEL_PROFILE_5 =3D 115, AD9910_CHANNEL_PROFILE_6 =3D 116, AD9910_CHANNEL_PROFILE_7 =3D 117, + AD9910_CHANNEL_PARALLEL_PORT =3D 120, }; =20 enum { @@ -212,10 +220,15 @@ enum { AD9910_CHAN_IDX_PROFILE_5, AD9910_CHAN_IDX_PROFILE_6, AD9910_CHAN_IDX_PROFILE_7, + AD9910_CHAN_IDX_PARALLEL_PORT, }; =20 enum { AD9910_POWERDOWN, + AD9910_PP_FREQ_SCALE, + AD9910_PP_FREQ_OFFSET, + AD9910_PP_PHASE_OFFSET, + AD9910_PP_AMP_OFFSET, }; =20 struct ad9910_data { @@ -482,6 +495,10 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *in= dio_dev, case AD9910_POWERDOWN: val =3D ad9910_sw_powerdown_get(st); break; + case AD9910_PP_FREQ_SCALE: + val =3D BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, + st->reg[AD9910_REG_CFR2].val32)); + break; default: return -EINVAL; } @@ -510,6 +527,115 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *= indio_dev, if (ret) return ret; break; + case AD9910_PP_FREQ_SCALE: + if (val32 > BIT(15) || !is_power_of_2(val32)) + return -EINVAL; + + val32 =3D FIELD_PREP(AD9910_CFR2_FM_GAIN_MSK, ilog2(val32)); + ret =3D ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_FM_GAIN_MSK, + val32, true); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return 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 =3D iio_priv(indio_dev); + int vals[2]; + u32 tmp32; + u64 tmp64; + + guard(mutex)(&st->lock); + + switch (private) { + case AD9910_PP_FREQ_OFFSET: + tmp64 =3D (u64)st->reg[AD9910_REG_FTW].val32 * st->data.sysclk_freq_hz; + vals[0] =3D tmp64 >> 32; + vals[1] =3D ((tmp64 & GENMASK_ULL(31, 0)) * MICRO) >> 32; + break; + case AD9910_PP_PHASE_OFFSET: + tmp32 =3D FIELD_GET(AD9910_POW_PP_LSB_MSK, + st->reg[AD9910_REG_POW].val16); + tmp32 =3D ((u64)tmp32 * AD9910_MAX_PHASE_MICRORAD) >> 16; + vals[0] =3D tmp32 / MICRO; + vals[1] =3D tmp32 % MICRO; + break; + case AD9910_PP_AMP_OFFSET: + tmp32 =3D FIELD_GET(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, + st->reg[AD9910_REG_ASF].val32); + vals[0] =3D 0; + vals[1] =3D (u64)tmp32 * MICRO >> 14; + break; + default: + return -EINVAL; + } + + return iio_format_value(buf, IIO_VAL_INT_PLUS_MICRO, ARRAY_SIZE(vals), va= ls); +} + +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 =3D iio_priv(indio_dev); + int val, val2; + u32 tmp32; + int ret; + + ret =3D 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 (val < 0 || val2 < 0 || val >=3D st->data.sysclk_freq_hz / 2) + return -EINVAL; + + tmp32 =3D ad9910_rational_scale((u64)val * MICRO + val2, BIT_ULL(32), + (u64)MICRO * st->data.sysclk_freq_hz); + ret =3D ad9910_reg32_write(st, AD9910_REG_FTW, tmp32, true); + if (ret) + return ret; + break; + case AD9910_PP_PHASE_OFFSET: + if (val !=3D 0 || val2 < 0 || val2 >=3D (AD9910_MAX_PHASE_MICRORAD >> 8)) + return -EINVAL; + + tmp32 =3D DIV_ROUND_CLOSEST((u32)val2 << 16, AD9910_MAX_PHASE_MICRORAD); + tmp32 =3D min(tmp32, AD9910_POW_PP_LSB_MAX); + tmp32 =3D FIELD_PREP(AD9910_POW_PP_LSB_MSK, tmp32); + ret =3D ad9910_reg16_update(st, AD9910_REG_POW, + AD9910_POW_PP_LSB_MSK, + tmp32, true); + if (ret) + return ret; + break; + case AD9910_PP_AMP_OFFSET: + if (val !=3D 0 || val2 < 0 || val2 >=3D (MICRO >> 8)) + return -EINVAL; + + tmp32 =3D DIV_ROUND_CLOSEST((u32)val2 << 14, MICRO); + tmp32 =3D min(tmp32, AD9910_ASF_PP_LSB_MAX); + tmp32 =3D FIELD_PREP(AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, tmp32); + ret =3D ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_SCALE_FACTOR_PP_LSB_MSK, + tmp32, true); + if (ret) + return ret; + break; default: return -EINVAL; } @@ -528,11 +654,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) =20 +#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[] =3D { AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE), { } }; =20 +static const struct iio_chan_spec_ext_info ad9910_pp_ext_info[] =3D { + 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 =3D IIO_ALTVOLTAGE, \ .indexed =3D 1, \ @@ -564,6 +701,15 @@ static const struct iio_chan_spec ad9910_channels[] = =3D { [AD9910_CHAN_IDX_PROFILE_5] =3D AD9910_PROFILE_CHAN(5), [AD9910_CHAN_IDX_PROFILE_6] =3D AD9910_PROFILE_CHAN(6), [AD9910_CHAN_IDX_PROFILE_7] =3D AD9910_PROFILE_CHAN(7), + [AD9910_CHAN_IDX_PARALLEL_PORT] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_PARALLEL_PORT, + .address =3D AD9910_CHAN_IDX_PARALLEL_PORT, + .ext_info =3D ad9910_pp_ext_info, + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, }; =20 static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -816,6 +962,7 @@ static const char * const ad9910_channel_str[] =3D { [AD9910_CHAN_IDX_PROFILE_5] =3D "profile5", [AD9910_CHAN_IDX_PROFILE_6] =3D "profile6", [AD9910_CHAN_IDX_PROFILE_7] =3D "profile7", + [AD9910_CHAN_IDX_PARALLEL_PORT] =3D "parallel_port", }; =20 static int ad9910_read_label(struct iio_dev *indio_dev, --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F31A33D0BEC; Sun, 17 May 2026 18:38:16 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; cv=none; b=cuGLuk99iIebUS/tHLv/XV9D40NChI6c5XCzBIMXySuQY+BMBgSqoIlVe0C97bA14cWVmIY6+ScmlM3lLlFQqIqqE43pekpDvCJlpvI6O8/LZxDzijsGQLnt4nLAoPJS8osLX9W8hpqE6xvluuSfirG8/Y9McyTHht6cq35zHRI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; c=relaxed/simple; bh=tKZhqTzHLIlNktJ4Q8WZGOXEXju/WHRrV14eTqCP4HI=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=S+oAQGPtp0pFGsidGVVB964ohRwPIVF8bUQ2MRjA8PvTgnQAzccYyvmz6snmxhFPa2dSR8sYim+4yUYwV0FkLd2qjEsA1x/Z/Z1Y3AT0gC/mxSRrf9vL7wpbb++LDCCzM85xISfs9CdR9+PYhRfmrhwTYjHZqz6VySKePDJ4Drw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=o0Lc531U; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="o0Lc531U" Received: by smtp.kernel.org (Postfix) with ESMTPS id 984E3C4AF64; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=tKZhqTzHLIlNktJ4Q8WZGOXEXju/WHRrV14eTqCP4HI=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=o0Lc531UP/6Q3sKZWQDYtJgO2Ws6oblK96SxCKmOACh3pHWisArSpVutDMQ0wMXeT +jJ9jOd+7mHnCvLKE1zegQv74yoetHli4AfgtcFWuB/KK9aPfGzwk4Phr09ymFMfOM CPWDts780CPUiIPZ6Z2rZxf1e96lHdcSFtzU6mtVBbNGRQM2eeqVIC9capRO8bk3t9 YO14sF/hVydnNmeHkhfMb88mYDj85xKU7BzdxXsDbzg/1RzOh0NtTkH4nB3wBfvA5e 9nWCqBiki+d1020HpL/ju1ff/na9+369CnDuPfqNvyORpvpF6IYVvM3EEQ4NU3WPt9 HSLjET0GltQfA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8D224CD4F51; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:52 +0100 Subject: [PATCH v5 08/13] iio: frequency: ad9910: add digital ramp generator support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-8-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=22644; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=3Bx/pvMkXPlBfgHSdOFot3NJeauj7Cz5zEi3RggBnrQ=; b=SCa4rR9Vc8V8Oc/KiUXxSioTQHOhbno0SAfpEXSgrSW5ktBLqHo2TawDCslZE7UG9POfVmpf2 Bh2dNPlbiWkBMabI/752b1XrY23bjHHIrwqOUHvDlWqzrZOVPNgXIeB X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add Digital Ramp Generator channels with destination selection (frequency, phase, or amplitude) based on attribute writes, dwell mode control, configurable upper/lower limits, step size controlled with rate of change config, and step rate controlled as sampling frequency. Signed-off-by: Rodrigo Alencar --- drivers/iio/frequency/ad9910.c | 515 +++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 513 insertions(+), 2 deletions(-) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index d2f40927b9be..4ad80475139d 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -130,6 +130,18 @@ #define AD9910_MC_SYNC_OUTPUT_DELAY_MSK GENMASK(15, 11) #define AD9910_MC_SYNC_INPUT_DELAY_MSK GENMASK(7, 3) =20 +/* 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) @@ -145,6 +157,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 =20 /* PLL constants */ @@ -196,6 +209,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 =3D 100, @@ -208,6 +224,24 @@ enum ad9910_channel { AD9910_CHANNEL_PROFILE_6 =3D 116, AD9910_CHANNEL_PROFILE_7 =3D 117, AD9910_CHANNEL_PARALLEL_PORT =3D 120, + AD9910_CHANNEL_DRG =3D 130, + AD9910_CHANNEL_DRG_RAMP_UP =3D 131, + AD9910_CHANNEL_DRG_RAMP_DOWN =3D 132, +}; + +/** + * 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, }; =20 enum { @@ -221,6 +255,9 @@ enum { AD9910_CHAN_IDX_PROFILE_6, AD9910_CHAN_IDX_PROFILE_7, AD9910_CHAN_IDX_PARALLEL_PORT, + AD9910_CHAN_IDX_DRG, + AD9910_CHAN_IDX_DRG_RAMP_UP, + AD9910_CHAN_IDX_DRG_RAMP_DOWN, }; =20 enum { @@ -229,6 +266,10 @@ enum { AD9910_PP_FREQ_OFFSET, AD9910_PP_PHASE_OFFSET, AD9910_PP_AMP_OFFSET, + AD9910_DRG_FREQ_ROC, + AD9910_DRG_PHASE_ROC, + AD9910_DRG_AMP_ROC, + AD9910_DRG_DWELL_EN, }; =20 struct ad9910_data { @@ -481,6 +522,26 @@ static int ad9910_sw_powerdown_set(struct ad9910_state= *st, bool enable) true); } =20 +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 inline int ad9910_drg_destination_assert(struct ad9910_state *st, + enum ad9910_destination dest) +{ + enum ad9910_destination drg_dest; + + drg_dest =3D (enum ad9910_destination)FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32); + return drg_dest =3D=3D dest ? 0 : -EBUSY; +} + static ssize_t ad9910_ext_info_read(struct iio_dev *indio_dev, uintptr_t private, const struct iio_chan_spec *chan, @@ -499,6 +560,14 @@ static ssize_t ad9910_ext_info_read(struct iio_dev *in= dio_dev, val =3D BIT(FIELD_GET(AD9910_CFR2_FM_GAIN_MSK, st->reg[AD9910_REG_CFR2].val32)); break; + case AD9910_DRG_DWELL_EN: + if (chan->channel =3D=3D AD9910_CHANNEL_DRG_RAMP_UP) + val =3D FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, + st->reg[AD9910_REG_CFR2].val32) ? 0 : 1; + else + val =3D FIELD_GET(AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, + st->reg[AD9910_REG_CFR2].val32) ? 0 : 1; + break; default: return -EINVAL; } @@ -538,6 +607,23 @@ static ssize_t ad9910_ext_info_write(struct iio_dev *i= ndio_dev, if (ret) return ret; break; + case AD9910_DRG_DWELL_EN: + if (chan->channel =3D=3D AD9910_CHANNEL_DRG_RAMP_UP) { + val32 =3D val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK; + ret =3D ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_NO_DWELL_HIGH_MSK, + val32, true); + if (ret) + return ret; + } else { + val32 =3D val32 ? 0 : AD9910_CFR2_DRG_NO_DWELL_LOW_MSK; + ret =3D ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_NO_DWELL_LOW_MSK, + val32, true); + if (ret) + return ret; + } + break; default: return -EINVAL; } @@ -643,6 +729,179 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *= indio_dev, return len; } =20 +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 =3D iio_priv(indio_dev); + unsigned int type; + int ret, vals[2]; + u64 roc64; + u32 rate; + + guard(mutex)(&st->lock); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + roc64 =3D FIELD_GET(AD9910_DRG_STEP_INC_MSK, + st->reg[AD9910_REG_DRG_STEP].val64); + rate =3D FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + roc64 =3D FIELD_GET(AD9910_DRG_STEP_DEC_MSK, + st->reg[AD9910_REG_DRG_STEP].val64); + rate =3D FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + default: + return -EINVAL; + } + + if (!rate) + return -ERANGE; + + roc64 *=3D st->data.sysclk_freq_hz; + rate *=3D 4; + + switch (private) { + case AD9910_DRG_FREQ_ROC: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_FREQUENCY); + if (ret) + return ret; + + type =3D IIO_VAL_INT_64; + roc64 =3D ad9910_rational_scale(roc64, st->data.sysclk_freq_hz, + BIT_ULL(32) * rate); + vals[0] =3D (u32)roc64; + vals[1] =3D (u32)(roc64 >> 32); + break; + case AD9910_DRG_PHASE_ROC: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_PHASE); + if (ret) + return ret; + + type =3D IIO_VAL_INT_PLUS_NANO; + roc64 =3D ad9910_rational_scale(roc64, AD9910_PI_NANORAD, + BIT_ULL(31) * rate); + vals[0] =3D div_s64_rem(roc64, NANO, &vals[1]); + break; + case AD9910_DRG_AMP_ROC: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_AMPLITUDE); + if (ret) + return ret; + + type =3D IIO_VAL_INT_PLUS_NANO; + roc64 =3D ad9910_rational_scale(roc64, NANO, BIT_ULL(32) * rate); + vals[0] =3D div_s64_rem(roc64, NANO, &vals[1]); + 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 =3D iio_priv(indio_dev); + enum ad9910_destination dest; + int val, val2; + u64 tmp64; + u32 rate; + int ret; + + guard(mutex)(&st->lock); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + rate =3D FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + rate =3D FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + default: + return -EINVAL; + } + + if (!rate) + return -ERANGE; + + rate *=3D 4; + + switch (private) { + case AD9910_DRG_FREQ_ROC: + ret =3D kstrtou64(buf, 10, &tmp64); + if (ret) + return ret; + + tmp64 =3D ad9910_rational_scale(tmp64, BIT_ULL(32) * rate, + (u64)st->data.sysclk_freq_hz * + st->data.sysclk_freq_hz); + dest =3D AD9910_DEST_FREQUENCY; + break; + case AD9910_DRG_PHASE_ROC: + ret =3D iio_str_to_fixpoint(buf, NANO / 10, &val, &val2); + if (ret) + return ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + tmp64 =3D (u64)val * NANO + val2; + tmp64 =3D ad9910_rational_scale(tmp64, BIT_ULL(31) * rate, + (u64)AD9910_PI_NANORAD * + st->data.sysclk_freq_hz); + dest =3D AD9910_DEST_PHASE; + break; + case AD9910_DRG_AMP_ROC: + ret =3D iio_str_to_fixpoint(buf, NANO / 10, &val, &val2); + if (ret) + return ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + tmp64 =3D (u64)val * NANO + val2; + tmp64 =3D ad9910_rational_scale(tmp64, BIT_ULL(32) * rate, + (u64)NANO * st->data.sysclk_freq_hz); + dest =3D AD9910_DEST_AMPLITUDE; + break; + default: + return -EINVAL; + } + + ret =3D ad9910_drg_destination_set(st, dest, false); + if (ret) + return ret; + + tmp64 =3D min(tmp64, U32_MAX); + + if (chan->channel =3D=3D AD9910_CHANNEL_DRG_RAMP_UP) { + ret =3D ad9910_reg64_update(st, AD9910_REG_DRG_STEP, + AD9910_DRG_STEP_INC_MSK, + FIELD_PREP(AD9910_DRG_STEP_INC_MSK, tmp64), + true); + if (ret) + return ret; + } else { + ret =3D ad9910_reg64_update(st, AD9910_REG_DRG_STEP, + AD9910_DRG_STEP_DEC_MSK, + FIELD_PREP(AD9910_DRG_STEP_DEC_MSK, tmp64), + true); + if (ret) + return ret; + } + + return len; +} + #define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \ .name =3D _name, \ .read =3D ad9910_ ## _fn_desc ## _read, \ @@ -657,6 +916,9 @@ static ssize_t ad9910_pp_attrs_write(struct iio_dev *in= dio_dev, #define AD9910_PP_EXT_INFO(_name, _ident) \ AD9910_EXT_INFO_TMPL(_name, _ident, IIO_SEPARATE, pp_attrs) =20 +#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[] =3D { AD9910_EXT_INFO("powerdown", AD9910_POWERDOWN, IIO_SEPARATE), { } @@ -670,6 +932,14 @@ static const struct iio_chan_spec_ext_info ad9910_pp_e= xt_info[] =3D { { } }; =20 +static const struct iio_chan_spec_ext_info ad9910_drg_ramp_ext_info[] =3D { + AD9910_EXT_INFO("dwell_en", AD9910_DRG_DWELL_EN, IIO_SEPARATE), + AD9910_DRG_EXT_INFO("frequency_roc", AD9910_DRG_FREQ_ROC), + AD9910_DRG_EXT_INFO("phase_roc", AD9910_DRG_PHASE_ROC), + AD9910_DRG_EXT_INFO("scale_roc", AD9910_DRG_AMP_ROC), + { } +}; + #define AD9910_PROFILE_CHAN(idx) { \ .type =3D IIO_ALTVOLTAGE, \ .indexed =3D 1, \ @@ -710,6 +980,41 @@ static const struct iio_chan_spec ad9910_channels[] = =3D { .ext_info =3D ad9910_pp_ext_info, .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], }, + [AD9910_CHAN_IDX_DRG] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_DRG, + .address =3D AD9910_CHAN_IDX_DRG, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_ENABLE), + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, + [AD9910_CHAN_IDX_DRG_RAMP_UP] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_DRG_RAMP_UP, + .address =3D AD9910_CHAN_IDX_DRG_RAMP_UP, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info =3D ad9910_drg_ramp_ext_info, + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_DRG], + }, + [AD9910_CHAN_IDX_DRG_RAMP_DOWN] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_DRG_RAMP_DOWN, + .address =3D AD9910_CHAN_IDX_DRG_RAMP_DOWN, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info =3D ad9910_drg_ramp_ext_info, + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_DRG], + }, }; =20 static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -719,6 +1024,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, struct ad9910_state *st =3D iio_priv(indio_dev); u64 tmp64; u32 tmp32; + int ret; =20 guard(mutex)(&st->lock); =20 @@ -733,6 +1039,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val =3D (tmp32 =3D=3D st->profile); } break; + case AD9910_CHANNEL_DRG: + *val =3D FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + break; default: return -EINVAL; } @@ -744,6 +1054,22 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp64 =3D FIELD_GET(AD9910_PROFILE_ST_FTW_MSK, st->reg[AD9910_REG_PROFILE(tmp32)].val64); break; + case AD9910_CHANNEL_DRG_RAMP_UP: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_FREQUENCY); + if (ret) + return ret; + + tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_FREQUENCY); + if (ret) + return ret; + + tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + break; default: return -EINVAL; } @@ -761,6 +1087,26 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val =3D tmp32 / MICRO; *val2 =3D tmp32 % MICRO; return IIO_VAL_INT_PLUS_MICRO; + case AD9910_CHANNEL_DRG_RAMP_UP: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_PHASE); + if (ret) + return ret; + + tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + tmp64 =3D (tmp64 * AD9910_PI_NANORAD) >> 31; + *val =3D div_s64_rem(tmp64, NANO, val2); + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_PHASE); + if (ret) + return ret; + + tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + tmp64 =3D (tmp64 * AD9910_PI_NANORAD) >> 31; + *val =3D div_s64_rem(tmp64, NANO, val2); + return IIO_VAL_INT_PLUS_NANO; default: return -EINVAL; } @@ -773,6 +1119,26 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val =3D 0; *val2 =3D tmp64 * MICRO >> 14; return IIO_VAL_INT_PLUS_MICRO; + case AD9910_CHANNEL_DRG_RAMP_UP: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_AMPLITUDE); + if (ret) + return ret; + + tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_UPPER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + *val =3D 0; + *val2 =3D tmp64 * NANO >> 32; + return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_AMPLITUDE); + if (ret) + return ret; + + tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK, + st->reg[AD9910_REG_DRG_LIMIT].val64); + *val =3D 0; + *val2 =3D tmp64 * NANO >> 32; + return IIO_VAL_INT_PLUS_NANO; default: return -EINVAL; } @@ -781,9 +1147,23 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PHY: *val =3D st->data.sysclk_freq_hz; return IIO_VAL_INT; + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp32 =3D FIELD_GET(AD9910_DRG_RATE_INC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; + case AD9910_CHANNEL_DRG_RAMP_DOWN: + tmp32 =3D FIELD_GET(AD9910_DRG_RATE_DEC_MSK, + st->reg[AD9910_REG_DRG_RATE].val32); + break; default: return -EINVAL; } + if (!tmp32) + return -ERANGE; + tmp32 *=3D 4; + *val =3D st->data.sysclk_freq_hz / tmp32; + *val2 =3D div_u64((u64)(st->data.sysclk_freq_hz % tmp32) * MICRO, tmp32); + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } @@ -817,6 +1197,11 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, return ret; =20 return ad9910_profile_set(st, tmp32); + case AD9910_CHANNEL_DRG: + tmp32 =3D FIELD_PREP(AD9910_CFR2_DRG_ENABLE_MSK, !!val); + return ad9910_reg32_update(st, AD9910_REG_CFR2, + AD9910_CFR2_DRG_ENABLE_MSK, + tmp32, true); default: return -EINVAL; } @@ -834,6 +1219,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 =3D ad9910_drg_destination_set(st, + AD9910_DEST_FREQUENCY, + false); + if (ret) + return ret; + + tmp64 =3D 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 =3D ad9910_drg_destination_set(st, + AD9910_DEST_FREQUENCY, + false); + if (ret) + return ret; + + tmp64 =3D 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; } @@ -855,6 +1262,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 =3D (u64)val * NANO + val2; + if (tmp64 > 2ULL * AD9910_PI_NANORAD) + return -EINVAL; + + ret =3D ad9910_drg_destination_set(st, AD9910_DEST_PHASE, + false); + if (ret) + return ret; + + tmp64 <<=3D 31; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD); + tmp64 =3D min(tmp64, U32_MAX); + tmp64 =3D 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 =3D (u64)val * NANO + val2; + if (tmp64 > 2ULL * AD9910_PI_NANORAD) + return -EINVAL; + + ret =3D ad9910_drg_destination_set(st, AD9910_DEST_PHASE, + false); + if (ret) + return ret; + + tmp64 <<=3D 31; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, AD9910_PI_NANORAD); + tmp64 =3D min(tmp64, U32_MAX); + tmp64 =3D 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; } @@ -872,11 +1313,65 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_ASF_MSK, tmp64, true); + case AD9910_CHANNEL_DRG_RAMP_UP: + ret =3D ad9910_drg_destination_set(st, + AD9910_DEST_AMPLITUDE, + false); + if (ret) + return ret; + + tmp64 =3D ((u64)val * NANO + val2) << 32; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, NANO); + tmp64 =3D min(tmp64, U32_MAX); + tmp64 =3D 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 =3D ad9910_drg_destination_set(st, + AD9910_DEST_AMPLITUDE, + false); + if (ret) + return ret; + + tmp64 =3D ((u64)val * NANO + val2) << 32; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, NANO); + tmp64 =3D min(tmp64, U32_MAX); + tmp64 =3D 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 =3D=3D AD9910_CHANNEL_PHY) + return ad9910_set_sysclk_freq(st, val, true); + + if (val < 0 || val2 < 0 || val > st->data.sysclk_freq_hz / 4) + return -EINVAL; + + tmp64 =3D ((u64)val * MICRO + val2) * 4; + if (!tmp64) + return -EINVAL; + + tmp64 =3D DIV64_U64_ROUND_CLOSEST((u64)st->data.sysclk_freq_hz * MICRO, = tmp64); + tmp32 =3D clamp(tmp64, 1U, AD9910_STEP_RATE_MAX); + + switch (chan->channel) { + case AD9910_CHANNEL_DRG_RAMP_UP: + tmp32 =3D 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 =3D 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; } @@ -896,11 +1391,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 =3D=3D AD9910_CHANNEL_PHY) + return IIO_VAL_INT; + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } @@ -963,6 +1463,9 @@ static const char * const ad9910_channel_str[] =3D { [AD9910_CHAN_IDX_PROFILE_6] =3D "profile6", [AD9910_CHAN_IDX_PROFILE_7] =3D "profile7", [AD9910_CHAN_IDX_PARALLEL_PORT] =3D "parallel_port", + [AD9910_CHAN_IDX_DRG] =3D "digital_ramp_generator", + [AD9910_CHAN_IDX_DRG_RAMP_UP] =3D "digital_ramp_up", + [AD9910_CHAN_IDX_DRG_RAMP_DOWN] =3D "digital_ramp_down", }; =20 static int ad9910_read_label(struct iio_dev *indio_dev, @@ -1090,6 +1593,14 @@ static int ad9910_setup(struct device *dev, struct a= d9910_state *st, if (ret) return ret; =20 + /* configure step rate with default values */ + ret =3D ad9910_reg32_write(st, AD9910_REG_DRG_RATE, + FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) | + FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1), + false); + if (ret) + return ret; + return ad9910_io_update(st); } =20 --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0F29E3D0C03; Sun, 17 May 2026 18:38:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; cv=none; b=eHQg7b94PZ3GIqmEMCiEeCKFsxJNeE566RC/cPeRmvdduKhwDYfStwptCmwX/SJlxrVArj4YR4ptS0fXy3CXC2Oesl9nCkZyhTOfmLWEGyVTtpKFkl+vjbVOZVriTOXKcR3vOqREG2VmdK2wkf+hEiynYY31tSSZUJA3CMgaO+c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; c=relaxed/simple; bh=Y4PWlJbmxndjSpPO2yYnOCm+ENm9ZS9XJwolUZP+ZXo=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nfRUxPeMjDI3xu43IEo5/pVaZ5nDPnY1pLexeTWwf9xoSTFraQBDrcKscy1YmyybOGNTzJRQGXsJhqKNrcB5jzTIGkard/j4Evzgr4aW6RhIU+SrFBaTJ9mGgi5Tm11T1tG/zB2Xuko8/zZD2x+GMeUayUpQqdG1MNIcF4Bjztw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=pR50CJkw; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="pR50CJkw" Received: by smtp.kernel.org (Postfix) with ESMTPS id B7D58C2BD04; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=Y4PWlJbmxndjSpPO2yYnOCm+ENm9ZS9XJwolUZP+ZXo=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=pR50CJkwq95jtv6+p6JzO5O6HEiHR/jN5FRUd+DyiE4F/In7GGPqS/6dmKymsH3HA D9oNCOxgqyURGab7BBUX2WCmDpYicsLFDt1UvgQj4HV+0hpjfujXFYE8u7tHJRDbpl dW1aDc8JkH0m04LCiazwB8S6q8rCrhkIUmlsGbIPtU4MAC4HI2NOEB02ldgi2GuyLX XnOF4jM5nOOHh0kvrCpUuKTF9+CK3AaFrToZiaVlwkdZfcftGRSrfk9JxkHSV19HhC JsjFia7apkWT+1rWRq3Mu0cPGt7FySWQNRZzbDACygvPVsZ5pMEvgcZz6xqO9FSTXc oBRa6KmyDFqvA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id A20F8CD4F52; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:53 +0100 Subject: [PATCH v5 09/13] iio: frequency: ad9910: add RAM mode support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-9-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=21842; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=xi2uZfWodHYt7pHoERh1xg+AUqNn7Aukay6n47JQtRY=; b=BDcqb/hXLH+fzFtD+TtLvUbJPHgLtkemq0ARKdUO24pb1tbGM6Mq4BaorxAWlRB1ebfF0mMsQ c5YC2RJt4PKAQfOU9Kan5kld/7Kd4d6+OqxkQoiHALFczdOsgaZqSx8 X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar 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 --- drivers/iio/frequency/Kconfig | 3 + drivers/iio/frequency/ad9910.c | 356 +++++++++++++++++++++++++++++++++++++= +++- 2 files changed, 353 insertions(+), 6 deletions(-) diff --git a/drivers/iio/frequency/Kconfig b/drivers/iio/frequency/Kconfig index 180e74f62d11..0b058dce2a6f 100644 --- a/drivers/iio/frequency/Kconfig +++ b/drivers/iio/frequency/Kconfig @@ -29,6 +29,9 @@ config AD9910 tristate "Analog Devices AD9910 Direct Digital Synthesizer" depends on SPI depends on GPIOLIB + select CRC32 + 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 4ad80475139d..86ed350011cf 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -8,9 +8,12 @@ #include #include #include +#include +#include #include #include #include +#include #include #include #include @@ -147,6 +150,15 @@ #define AD9910_PROFILE_ST_POW_MSK GENMASK_ULL(47, 32) #define AD9910_PROFILE_ST_FTW_MSK GENMASK_ULL(31, 0) =20 +/* Profile Register Format (RAM Mode) */ +#define AD9910_PROFILE_RAM_OPEN_MSK GENMASK_ULL(61, 56) +#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 =20 @@ -160,6 +172,16 @@ #define AD9910_STEP_RATE_MAX GENMASK(15, 0) #define AD9910_NUM_PROFILES 8 =20 +#define AD9910_RAM_FW_MAGIC 0x00AD9910 +#define AD9910_RAM_FW_V1 0x0001 +#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 @@ -191,7 +213,7 @@ #define AD9910_REFDIV2_MAX_FREQ_HZ (1900 * HZ_PER_MHZ) =20 #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) @@ -212,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 =3D 100, @@ -227,6 +250,7 @@ enum ad9910_channel { AD9910_CHANNEL_DRG =3D 130, AD9910_CHANNEL_DRG_RAMP_UP =3D 131, AD9910_CHANNEL_DRG_RAMP_DOWN =3D 132, + AD9910_CHANNEL_RAM =3D 140, }; =20 /** @@ -244,6 +268,29 @@ enum ad9910_destination { AD9910_DEST_POLAR, }; =20 +/** + * struct ad9910_ram_fw - AD9910 RAM firmware format + * @magic: Magic number for RAM firmware validation + * @version: Firmware version number + * @wcount: Number of RAM words to be written + * @crc: CRC32 checksum of the RAM data for integrity verification + * @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 + * @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; + __be16 version; + __be16 wcount; + __be32 crc; + __be32 cfr1; + __be64 profiles[AD9910_NUM_PROFILES]; + __be32 words[] __counted_by_be(wcount); +} __packed; + enum { AD9910_CHAN_IDX_PHY, AD9910_CHAN_IDX_PROFILE_0, @@ -258,6 +305,7 @@ enum { AD9910_CHAN_IDX_DRG, AD9910_CHAN_IDX_DRG_RAMP_UP, AD9910_CHAN_IDX_DRG_RAMP_DOWN, + AD9910_CHAN_IDX_RAM, }; =20 enum { @@ -290,6 +338,7 @@ union ad9910_reg { struct ad9910_state { struct spi_device *spi; struct clk *refclk; + struct fw_upload *ram_fwu; =20 struct gpio_desc *gpio_pwdown; struct gpio_desc *gpio_update; @@ -298,12 +347,22 @@ struct ad9910_state { /* cached registers */ union ad9910_reg reg[AD9910_REG_NUM_CACHED]; =20 + /* + * 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; =20 struct ad9910_data data; u8 profile; =20 + bool ram_fwu_cancel; + char ram_fwu_name[20]; + /* * RAM loading requires a reasonable amount of bytes, at the same time * DMA capable SPI drivers requires the transfer buffers to live in @@ -327,6 +386,22 @@ static inline u64 ad9910_rational_scale(u64 input, u64= scale, u64 reference) return mul_u64_add_u64_div_u64(input, scale, reference >> 1, reference); } =20 +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 profil= e) +{ + 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) { @@ -1015,6 +1090,18 @@ static const struct iio_chan_spec ad9910_channels[] = =3D { .ext_info =3D ad9910_drg_ramp_ext_info, .parent =3D &ad9910_channels[AD9910_CHAN_IDX_DRG], }, + [AD9910_CHAN_IDX_RAM] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_RAM, + .address =3D AD9910_CHAN_IDX_RAM, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_FREQUENCY) | + BIT(IIO_CHAN_INFO_PHASE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, }; =20 static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -1043,6 +1130,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val =3D FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, st->reg[AD9910_REG_CFR2].val32); break; + case AD9910_CHANNEL_RAM: + *val =3D FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + break; default: return -EINVAL; } @@ -1052,7 +1143,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; tmp64 =3D 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: ret =3D ad9910_drg_destination_assert(st, AD9910_DEST_FREQUENCY); @@ -1070,6 +1161,9 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp64 =3D FIELD_GET(AD9910_DRG_LIMIT_LOWER_MSK, st->reg[AD9910_REG_DRG_LIMIT].val64); break; + case AD9910_CHANNEL_RAM: + tmp64 =3D st->reg[AD9910_REG_FTW].val32; + break; default: return -EINVAL; } @@ -1082,7 +1176,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; tmp64 =3D FIELD_GET(AD9910_PROFILE_ST_POW_MSK, - st->reg[AD9910_REG_PROFILE(tmp32)].val64); + ad9910_st_profile_val(st, tmp32)); tmp32 =3D (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16; *val =3D tmp32 / MICRO; *val2 =3D tmp32 % MICRO; @@ -1107,6 +1201,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp64 =3D (tmp64 * AD9910_PI_NANORAD) >> 31; *val =3D div_s64_rem(tmp64, NANO, val2); return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHANNEL_RAM: + tmp64 =3D st->reg[AD9910_REG_POW].val16; + tmp32 =3D (tmp64 * AD9910_MAX_PHASE_MICRORAD) >> 16; + *val =3D tmp32 / MICRO; + *val2 =3D tmp32 % MICRO; + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } @@ -1115,7 +1215,7 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 =3D chan->channel - AD9910_CHANNEL_PROFILE_0; tmp64 =3D FIELD_GET(AD9910_PROFILE_ST_ASF_MSK, - st->reg[AD9910_REG_PROFILE(tmp32)].val64); + ad9910_st_profile_val(st, tmp32)); *val =3D 0; *val2 =3D tmp64 * MICRO >> 14; return IIO_VAL_INT_PLUS_MICRO; @@ -1155,6 +1255,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp32 =3D FIELD_GET(AD9910_DRG_RATE_DEC_MSK, st->reg[AD9910_REG_DRG_RATE].val32); break; + case AD9910_CHANNEL_RAM: + tmp32 =3D FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK, + ad9910_ram_profile_val(st)); + break; default: return -EINVAL; } @@ -1176,7 +1280,7 @@ static int ad9910_write_raw(struct iio_dev *indio_dev, struct ad9910_state *st =3D iio_priv(indio_dev); u64 tmp64; u32 tmp32; - int ret; + int ret, i; =20 guard(mutex)(&st->lock); =20 @@ -1202,6 +1306,41 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, return ad9910_reg32_update(st, AD9910_REG_CFR2, AD9910_CFR2_DRG_ENABLE_MSK, tmp32, true); + case AD9910_CHANNEL_RAM: + if (AD9910_RAM_ENABLED(st) =3D=3D !!val) + return 0; + + /* swap profile configs */ + for (i =3D 0; i < AD9910_NUM_PROFILES; i++) { + tmp64 =3D st->reg[AD9910_REG_PROFILE(i)].val64; + ret =3D ad9910_reg64_write(st, + AD9910_REG_PROFILE(i), + st->reg_profile[i], + false); + if (ret) + break; + st->reg_profile[i] =3D tmp64; + } + + if (ret) { + /* + * After the write failure, profiles 0..i-1 were + * already swapped in SW, but Hw registers are + * still pending an IO update, so swap them back + * in SW to keep the state consistent. + */ + while (i--) { + tmp64 =3D st->reg[AD9910_REG_PROFILE(i)].val64; + st->reg[AD9910_REG_PROFILE(i)].val64 =3D st->reg_profile[i]; + st->reg_profile[i] =3D tmp64; + } + return ret; + } + + tmp32 =3D 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; } @@ -1215,6 +1354,11 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, switch (chan->channel) { case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: tmp32 =3D 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 =3D FIELD_PREP(AD9910_PROFILE_ST_FTW_MSK, tmp64); return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_FTW_MSK, @@ -1241,6 +1385,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; } @@ -1258,6 +1404,13 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, tmp64 <<=3D 16; tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD); tmp64 =3D 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 =3D FIELD_PREP(AD9910_PROFILE_ST_POW_MSK, tmp64); return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_POW_MSK, @@ -1296,6 +1449,15 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT, AD9910_DRG_LIMIT_LOWER_MSK, tmp64, true); + case AD9910_CHANNEL_RAM: + tmp64 =3D (u64)val * MICRO + val2; + if (tmp64 >=3D AD9910_MAX_PHASE_MICRORAD) + return -EINVAL; + + tmp64 <<=3D 16; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, AD9910_MAX_PHASE_MICRORAD); + tmp64 =3D min(tmp64, AD9910_POW_MAX); + return ad9910_reg16_write(st, AD9910_REG_POW, tmp64, true); default: return -EINVAL; } @@ -1309,6 +1471,13 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, tmp64 =3D ((u64)val * MICRO + val2) << 14; tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, MICRO); tmp64 =3D 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 =3D FIELD_PREP(AD9910_PROFILE_ST_ASF_MSK, tmp64); return ad9910_reg64_update(st, AD9910_REG_PROFILE(tmp32), AD9910_PROFILE_ST_ASF_MSK, @@ -1369,6 +1538,17 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, 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 =3D 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; } @@ -1390,6 +1570,7 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *i= ndio_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: @@ -1466,6 +1647,7 @@ static const char * const ad9910_channel_str[] =3D { [AD9910_CHAN_IDX_DRG] =3D "digital_ramp_generator", [AD9910_CHAN_IDX_DRG_RAMP_UP] =3D "digital_ramp_up", [AD9910_CHAN_IDX_DRG_RAMP_DOWN] =3D "digital_ramp_down", + [AD9910_CHAN_IDX_RAM] =3D "ram_control", }; =20 static int ad9910_read_label(struct iio_dev *indio_dev, @@ -1475,6 +1657,126 @@ static int ad9910_read_label(struct iio_dev *indio_= dev, return sysfs_emit(label, "%s\n", ad9910_channel_str[chan->address]); } =20 +static enum fw_upload_err ad9910_ram_fwu_prepare(struct fw_upload *fw_uplo= ad, + const u8 *data, u32 size) +{ + struct ad9910_state *st =3D fw_upload->dd_handle; + const struct ad9910_ram_fw *fw_data =3D (const struct ad9910_ram_fw *)dat= a; + size_t wcount, bcount; + + if (size < sizeof(struct ad9910_ram_fw)) + return FW_UPLOAD_ERR_INVALID_SIZE; + + if (get_unaligned_be32(&fw_data->magic) !=3D AD9910_RAM_FW_MAGIC) + return FW_UPLOAD_ERR_FW_INVALID; + + if (get_unaligned_be16(&fw_data->version) !=3D AD9910_RAM_FW_V1) + return FW_UPLOAD_ERR_FW_INVALID; + + wcount =3D get_unaligned_be16(&fw_data->wcount); + bcount =3D size - sizeof(struct ad9910_ram_fw); + if (wcount > AD9910_RAM_SIZE_MAX_WORDS || + bcount !=3D (wcount * AD9910_RAM_WORD_SIZE)) + return FW_UPLOAD_ERR_INVALID_SIZE; + + bcount +=3D sizeof(fw_data->cfr1) + sizeof(fw_data->profiles); + if (crc32(0, &fw_data->cfr1, bcount) !=3D get_unaligned_be32(&fw_data->cr= c)) + return FW_UPLOAD_ERR_FW_INVALID; + + guard(mutex)(&st->lock); + st->ram_fwu_cancel =3D 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) +{ + const struct ad9910_ram_fw *fw_data =3D (const struct ad9910_ram_fw *)dat= a; + struct ad9910_state *st =3D fw_upload->dd_handle; + int ret, ret2, idx, wcount; + u64 tmp64, backup; + + if (offset !=3D 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; + + for (idx =3D 0; idx < AD9910_NUM_PROFILES; idx++) + st->reg_profile[idx] =3D get_unaligned_be64(&fw_data->profiles[idx]) | + AD9910_PROFILE_RAM_OPEN_MSK; + + ret =3D 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 =3D get_unaligned_be16(&fw_data->wcount); + if (!wcount) { + *written =3D size; + return FW_UPLOAD_ERR_NONE; /* nothing else to write */ + } + + ret =3D 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 =3D st->reg[AD9910_REG_PROFILE(st->profile)].val64; + tmp64 =3D 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 =3D ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), tmp64, tr= ue); + if (ret) + return FW_UPLOAD_ERR_RW_ERROR; + + memcpy(&st->tx_buf[1], fw_data->words, wcount * AD9910_RAM_WORD_SIZE); + + /* write ram data and restore profile register */ + ret =3D ad9910_spi_write(st, AD9910_REG_RAM, + wcount * AD9910_RAM_WORD_SIZE, false); + ret2 =3D ad9910_reg64_write(st, AD9910_REG_PROFILE(st->profile), backup, = true); + if (ret || ret2) + return FW_UPLOAD_ERR_RW_ERROR; + + *written =3D size; + return FW_UPLOAD_ERR_NONE; +} + +static enum fw_upload_err ad9910_ram_fwu_poll_complete(struct fw_upload *f= w_upload) +{ + return FW_UPLOAD_ERR_NONE; +} + +static void ad9910_ram_fwu_cancel(struct fw_upload *fw_upload) +{ + struct ad9910_state *st =3D fw_upload->dd_handle; + + guard(mutex)(&st->lock); + st->ram_fwu_cancel =3D true; +} + +static void ad9910_ram_fwu_unregister(void *data) +{ + firmware_upload_unregister(data); +} + +static const struct fw_upload_ops ad9910_ram_fwu_ops =3D { + .prepare =3D ad9910_ram_fwu_prepare, + .write =3D ad9910_ram_fwu_write, + .poll_complete =3D ad9910_ram_fwu_poll_complete, + .cancel =3D ad9910_ram_fwu_cancel +}; + static const struct iio_info ad9910_info =3D { .read_raw =3D ad9910_read_raw, .write_raw =3D ad9910_write_raw, @@ -1601,9 +1903,33 @@ static int ad9910_setup(struct device *dev, struct a= d9910_state *st, if (ret) return ret; =20 + for (int i =3D 0; i < AD9910_NUM_PROFILES; i++) { + st->reg_profile[i] =3D AD9910_PROFILE_RAM_OPEN_MSK; + st->reg_profile[i] |=3D FIELD_PREP(AD9910_PROFILE_RAM_STEP_RATE_MSK, 1); + st->reg_profile[i] |=3D FIELD_PREP(AD9910_PROFILE_RAM_END_ADDR_MSK, + AD9910_RAM_ADDR_MAX); + } + return ad9910_io_update(st); } =20 +static inline void ad9910_debugfs_init(struct ad9910_state *st, + struct iio_dev *indio_dev) +{ + struct dentry *d =3D iio_get_debugfs_dentry(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", d, buf); + + snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/data", st->ram_fwu_nam= e); + debugfs_create_symlink("ram_data", d, buf); +} + static int ad9910_probe(struct spi_device *spi) { static const char * const supplies[] =3D { @@ -1688,7 +2014,25 @@ static int ad9910_probe(struct spi_device *spi) if (ret) return dev_err_probe(dev, ret, "device setup failed\n"); =20 - return devm_iio_device_register(dev, indio_dev); + snprintf(st->ram_fwu_name, sizeof(st->ram_fwu_name), "%s:ram", + dev_name(&indio_dev->dev)); + st->ram_fwu =3D firmware_upload_register(THIS_MODULE, dev, st->ram_fwu_na= me, + &ad9910_ram_fwu_ops, st); + if (IS_ERR(st->ram_fwu)) + return dev_err_probe(dev, PTR_ERR(st->ram_fwu), + "failed to register ram upload ops\n"); + + ret =3D devm_add_action_or_reset(dev, ad9910_ram_fwu_unregister, st->ram_= fwu); + if (ret) + return dev_err_probe(dev, ret, + "failed to add ram upload unregister action\n"); + + ret =3D devm_iio_device_register(dev, indio_dev); + if (ret) + return ret; + + ad9910_debugfs_init(st, indio_dev); + return 0; } =20 static const struct spi_device_id ad9910_id[] =3D { --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 200C83D1A82; Sun, 17 May 2026 18:38:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; cv=none; b=UvLqmPQiqZJ4bnAgL9icTkEp60lMZSQ/wZHkH3JAboffUQVrCBJS4TejDHW6+FjeDGnXXKACyPrFekviQIGFM9LmrbrBIaugGpMJSXdRPk39pvIA0Pyjf5+z9oSAci31PLuhclUiRsIr8ICEuCFyb7UlPgnSAkvVWcZAy36+pZ0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; c=relaxed/simple; bh=84Kal7cFW+KGgKwFiIbDUspM24SgWHAvZxhfCvtXrgc=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=VFzhjnSya3Q/DV8fOA2HxPS8492HTPXOrXT8+AYAIehjSf5Ki02cvUBbupNrDLbgB9perctpxmzMUy4npMmCyOYOS6REbcRY0IVU17tapMk5A60F0R5I/Ic6EfFdLQEKj1Vscc9opTSKJ5JkAVfWp4tgk11N2AXMNkZmgZQ8YOY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=Ah8PYahW; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="Ah8PYahW" Received: by smtp.kernel.org (Postfix) with ESMTPS id C423FC4AF1B; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=84Kal7cFW+KGgKwFiIbDUspM24SgWHAvZxhfCvtXrgc=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=Ah8PYahWSsC9FM7Ix6chPZSuBUSO/NOX4Jdm+l/uGEUcEPP/9C9AXhJolIpkCCm7b snlpIc2iqUwtl50QdX2NS7ueOmSaRRbfXrk8oifw7hXSBnCkxkBCaSE9RQSgTdYaEp eXRwULO5S1zsofQTudVlxcY59Tc3iA9063OBHrEFIM5E6ir/e9Cry1CFzVVs5dx7aU caQBYOsXQQIM2EpiFgbPKFx6keceulLXcoZrwoY+0LGKA86M5yGiMmlyW4mJAqYvjM J9e3PLgv6MZW/RQ0Dadv6wIY9zFMQJI0kfoTAebo2/L6oyt7J0YweScbIOCmYWmYDS 5vWsl+5bLAu5w== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B53AECD4F4A; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:54 +0100 Subject: [PATCH v5 10/13] iio: frequency: ad9910: add output shift keying support Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-10-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=11061; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=pnDIOx00dJa84RQDwArtfVFkLSpBVu3NlwOnM1swaFE=; b=l4bqhC/wgW/pZvD8tkZvgVDYePOpLobntAUVBtPPUFjPcWYkCiyRmoGiPMe2pFwItBYyc9eBu NKP5G+6kEn+DNFc+Sh3A36M0VCN2UYnDwlQRInuRBCxhaYi9wHT/BLZ X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar 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); - Automatic OSK step size configurable through the scale_roc extended attribute, which allows for selectable step sizes in nano-units: - 0: no step, means manual mode (NOT pin controlled) - 61035: 1/2^14 step, automatic mode (pin controlled) - 122070: 2/2^14 step, automatic mode (pin controlled) - 244141: 4/2^14 step, automatic mode (pin controlled) - 488281: 8/2^14 step, automatic mode (pin controlled) - 1000000000: 1.0 step (max), manual mode (pin controlled) The ASF register is initialized with a default amplitude ramp rate during device setup to ensure valid readback. Signed-off-by: Rodrigo Alencar --- drivers/iio/frequency/ad9910.c | 203 +++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 203 insertions(+) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index 86ed350011cf..91f28c7de68b 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 =3D 100, @@ -251,6 +252,7 @@ enum ad9910_channel { AD9910_CHANNEL_DRG_RAMP_UP =3D 131, AD9910_CHANNEL_DRG_RAMP_DOWN =3D 132, AD9910_CHANNEL_RAM =3D 140, + AD9910_CHANNEL_OSK =3D 150, }; =20 /** @@ -306,6 +308,7 @@ enum { AD9910_CHAN_IDX_DRG_RAMP_UP, AD9910_CHAN_IDX_DRG_RAMP_DOWN, AD9910_CHAN_IDX_RAM, + AD9910_CHAN_IDX_OSK, }; =20 enum { @@ -318,6 +321,8 @@ enum { AD9910_DRG_PHASE_ROC, AD9910_DRG_AMP_ROC, AD9910_DRG_DWELL_EN, + AD9910_OSK_AUTO_ROC, + AD9910_OSK_AUTO_ROC_AVAIL, }; =20 struct ad9910_data { @@ -977,6 +982,135 @@ static ssize_t ad9910_drg_attrs_write(struct iio_dev = *indio_dev, return len; } =20 +static const u32 ad9910_osk_nstep[] =3D { + 0, /* no step: manual mode (NOT pin controlled) */ + 61035, /* 1/2^14 step: automatic mode (pin controlled) */ + 122070, /* 2/2^14 step: automatic mode (pin controlled) */ + 244141, /* 4/2^14 step: automatic mode (pin controlled) */ + 488281, /* 8/2^14 step: automatic mode (pin controlled) */ + NANO, /* full scale step: manual mode (pin controlled) */ +}; + +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 =3D iio_priv(indio_dev); + bool auto_en, pinctrl_en; + u32 rate, step; + int vals[2]; + u64 roc64; + + guard(mutex)(&st->lock); + + rate =3D 4 * FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, + st->reg[AD9910_REG_ASF].val32); + if (!rate) + return -ERANGE; + + switch (private) { + case AD9910_OSK_AUTO_ROC: + auto_en =3D FIELD_GET(AD9910_CFR1_SELECT_AUTO_OSK_MSK, + st->reg[AD9910_REG_CFR1].val32); + pinctrl_en =3D FIELD_GET(AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (auto_en) { + step =3D FIELD_GET(AD9910_ASF_STEP_SIZE_MSK, + st->reg[AD9910_REG_ASF].val32); + step =3D ad9910_osk_nstep[step + 1]; + } else if (pinctrl_en) { + step =3D ad9910_osk_nstep[ARRAY_SIZE(ad9910_osk_nstep) - 1]; + } else { + step =3D ad9910_osk_nstep[0]; + } + + roc64 =3D div_u64((u64)step * st->data.sysclk_freq_hz, rate); + vals[0] =3D div_s64_rem(roc64, NANO, &vals[1]); + return iio_format_value(buf, IIO_VAL_INT_PLUS_NANO, + ARRAY_SIZE(vals), vals); + case AD9910_OSK_AUTO_ROC_AVAIL: { + ssize_t len =3D 0; + + for (unsigned int i =3D 0; i < ARRAY_SIZE(ad9910_osk_nstep); i++) { + roc64 =3D div_u64((u64)ad9910_osk_nstep[i] * + st->data.sysclk_freq_hz, rate); + vals[0] =3D div_s64_rem(roc64, NANO, &vals[1]); + if (!vals[1]) + len +=3D sysfs_emit_at(buf, len, "%d ", vals[0]); + else + len +=3D sysfs_emit_at(buf, len, "%d.%09d ", + vals[0], vals[1]); + } + + buf[len - 1] =3D '\n'; /* replace last space with a newline */ + return len; + } + 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 =3D iio_priv(indio_dev); + int val, val2, ret; + u32 idx, cfg, rate; + u64 nstep; + + ret =3D iio_str_to_fixpoint(buf, NANO / 10, &val, &val2); + if (ret) + return ret; + + if (val < 0 || val2 < 0) + return -EINVAL; + + guard(mutex)(&st->lock); + + rate =3D 4 * FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, + st->reg[AD9910_REG_ASF].val32); + if (!rate) + return -ERANGE; + + switch (private) { + case AD9910_OSK_AUTO_ROC: + nstep =3D ad9910_rational_scale((u64)val * NANO + val2, rate, + st->data.sysclk_freq_hz); + nstep =3D min(nstep, NANO); + idx =3D find_closest(nstep, ad9910_osk_nstep, + ARRAY_SIZE(ad9910_osk_nstep)); + if (idx =3D=3D ARRAY_SIZE(ad9910_osk_nstep) - 1) { + cfg =3D AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK; + } else if (idx =3D=3D 0) { + cfg =3D 0; + } else { + cfg =3D FIELD_PREP(AD9910_ASF_STEP_SIZE_MSK, idx - 1); + ret =3D ad9910_reg32_update(st, AD9910_REG_ASF, + AD9910_ASF_STEP_SIZE_MSK, + cfg, false); + if (ret) + return ret; + + cfg =3D AD9910_CFR1_SELECT_AUTO_OSK_MSK; + } + + ret =3D ad9910_reg32_update(st, AD9910_REG_CFR1, + AD9910_CFR1_SELECT_AUTO_OSK_MSK | + AD9910_CFR1_OSK_MANUAL_EXT_CTL_MSK, + cfg, true); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + + return len; +} + #define AD9910_EXT_INFO_TMPL(_name, _ident, _shared, _fn_desc) { \ .name =3D _name, \ .read =3D ad9910_ ## _fn_desc ## _read, \ @@ -1015,6 +1149,23 @@ static const struct iio_chan_spec_ext_info ad9910_dr= g_ramp_ext_info[] =3D { { } }; =20 +static const struct iio_chan_spec_ext_info ad9910_osk_ext_info[] =3D { + { + .name =3D "scale_roc", + .read =3D ad9910_osk_attrs_read, + .write =3D ad9910_osk_attrs_write, + .private =3D AD9910_OSK_AUTO_ROC, + .shared =3D IIO_SEPARATE, + }, + { + .name =3D "scale_roc_available", + .read =3D ad9910_osk_attrs_read, + .private =3D AD9910_OSK_AUTO_ROC_AVAIL, + .shared =3D IIO_SEPARATE, + }, + { } +}; + #define AD9910_PROFILE_CHAN(idx) { \ .type =3D IIO_ALTVOLTAGE, \ .indexed =3D 1, \ @@ -1102,6 +1253,18 @@ static const struct iio_chan_spec ad9910_channels[] = =3D { BIT(IIO_CHAN_INFO_SAMP_FREQ), .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], }, + [AD9910_CHAN_IDX_OSK] =3D { + .type =3D IIO_ALTVOLTAGE, + .indexed =3D 1, + .output =3D 1, + .channel =3D AD9910_CHANNEL_OSK, + .address =3D AD9910_CHAN_IDX_OSK, + .info_mask_separate =3D BIT(IIO_CHAN_INFO_ENABLE) | + BIT(IIO_CHAN_INFO_SCALE) | + BIT(IIO_CHAN_INFO_SAMP_FREQ), + .ext_info =3D ad9910_osk_ext_info, + .parent =3D &ad9910_channels[AD9910_CHAN_IDX_PHY], + }, }; =20 static int ad9910_read_raw(struct iio_dev *indio_dev, @@ -1134,6 +1297,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val =3D FIELD_GET(AD9910_CFR1_RAM_ENABLE_MSK, st->reg[AD9910_REG_CFR1].val32); break; + case AD9910_CHANNEL_OSK: + *val =3D FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + break; default: return -EINVAL; } @@ -1239,6 +1406,12 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, *val =3D 0; *val2 =3D tmp64 * NANO >> 32; return IIO_VAL_INT_PLUS_NANO; + case AD9910_CHANNEL_OSK: + tmp64 =3D FIELD_GET(AD9910_ASF_SCALE_FACTOR_MSK, + st->reg[AD9910_REG_ASF].val32); + *val =3D 0; + *val2 =3D tmp64 * MICRO >> 14; + return IIO_VAL_INT_PLUS_MICRO; default: return -EINVAL; } @@ -1259,6 +1432,10 @@ static int ad9910_read_raw(struct iio_dev *indio_dev, tmp32 =3D FIELD_GET(AD9910_PROFILE_RAM_STEP_RATE_MSK, ad9910_ram_profile_val(st)); break; + case AD9910_CHANNEL_OSK: + tmp32 =3D FIELD_GET(AD9910_ASF_RAMP_RATE_MSK, + st->reg[AD9910_REG_ASF].val32); + break; default: return -EINVAL; } @@ -1341,6 +1518,11 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, return ad9910_reg32_update(st, AD9910_REG_CFR1, AD9910_CFR1_RAM_ENABLE_MSK, tmp32, true); + case AD9910_CHANNEL_OSK: + tmp32 =3D 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; } @@ -1510,6 +1692,14 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, return ad9910_reg64_update(st, AD9910_REG_DRG_LIMIT, AD9910_DRG_LIMIT_LOWER_MSK, tmp64, true); + case AD9910_CHANNEL_OSK: + tmp64 =3D ((u64)val * MICRO + val2) << 14; + tmp64 =3D DIV_U64_ROUND_CLOSEST(tmp64, MICRO); + tmp32 =3D min(tmp64, AD9910_ASF_MAX); + tmp32 =3D 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; } @@ -1549,6 +1739,11 @@ static int ad9910_write_raw(struct iio_dev *indio_de= v, return ad9910_reg64_update(st, AD9910_REG_PROFILE(st->profile), AD9910_PROFILE_RAM_STEP_RATE_MSK, tmp64, true); + 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; } @@ -1571,6 +1766,7 @@ static int ad9910_write_raw_get_fmt(struct iio_dev *i= ndio_dev, switch (chan->channel) { case AD9910_CHANNEL_PROFILE_0 ... AD9910_CHANNEL_PROFILE_7: case AD9910_CHANNEL_RAM: + case AD9910_CHANNEL_OSK: return IIO_VAL_INT_PLUS_MICRO; case AD9910_CHANNEL_DRG_RAMP_UP: case AD9910_CHANNEL_DRG_RAMP_DOWN: @@ -1648,6 +1844,7 @@ static const char * const ad9910_channel_str[] =3D { [AD9910_CHAN_IDX_DRG_RAMP_UP] =3D "digital_ramp_up", [AD9910_CHAN_IDX_DRG_RAMP_DOWN] =3D "digital_ramp_down", [AD9910_CHAN_IDX_RAM] =3D "ram_control", + [AD9910_CHAN_IDX_OSK] =3D "output_shift_keying", }; =20 static int ad9910_read_label(struct iio_dev *indio_dev, @@ -1896,6 +2093,12 @@ static int ad9910_setup(struct device *dev, struct a= d9910_state *st, return ret; =20 /* configure step rate with default values */ + ret =3D ad9910_reg32_write(st, AD9910_REG_ASF, + FIELD_PREP(AD9910_ASF_RAMP_RATE_MSK, 1), + false); + if (ret) + return ret; + ret =3D ad9910_reg32_write(st, AD9910_REG_DRG_RATE, FIELD_PREP(AD9910_DRG_RATE_DEC_MSK, 1) | FIELD_PREP(AD9910_DRG_RATE_INC_MSK, 1), --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 317D93D1AA0; Sun, 17 May 2026 18:38:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; cv=none; b=XEvNI6HKvjpy9LVHLDjTvQPw0uE9DBKhR7HyQuB68uHhl+FwIij2CSghAReX7MTVQJTKe+GIWQcgnBf8K4A/12xmXXCaHhrZ02Q4r/2648Rcu/3xTOPjr9w2r33eCubPLQzZB9eDvPGdtEsQGie9gm4upT9FrvkYsEiInxa7Agw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; c=relaxed/simple; bh=H83DBNbbCZW0bIb629FiJ4hdTDEDxOp35tsDNpj6CLQ=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=HM8AvKksnIIyCHUwR3HgRG0484c/ZP7z/G/7skElI42rySa6IIjYcrMjuH30rl+yyfxECkHl1Ney6HDwKDjpbABsvajJEQZV/5w/5PupcGvYO+JUR4pVGj5kORSH+6i2ZA74/cetDJBeNFvRegTz55hsJ5XFhlr12VHeP8P8Wuw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=fTGZK0DU; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="fTGZK0DU" Received: by smtp.kernel.org (Postfix) with ESMTPS id CEA7DC2BCF7; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043096; bh=H83DBNbbCZW0bIb629FiJ4hdTDEDxOp35tsDNpj6CLQ=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=fTGZK0DUcEkGLZvXRetq5UtUqxcL5//tirRIRwncRvrIv9Ikh2j1SLNnNumt5oHXF nuMmqrHW5Ghv9vuMNU3G+K9BAXvoW5QlYFkn/XjqG4XvZTasJ+pA3dXdTlyxya+B+Z +TCPEEYnnTSq0mNsiM5ggRqwnCbVuVHxrth8frN7QsV8CqjpY0AxAEFOsyh7c6S1Kc ZhWaHdyhl3uf1NXBdJ6LE6YTgjBzlKkG25wijQIbA9Qwf58hJISGPRBepCvEoNHfka ZB67fxzBGfdbsVK3v59kqXojMyaxkJCDlvQlIYgvljrWH+xhAmVMerG44VdOSd4ses T13J82eIaMaMQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id C5F8DCD4F50; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:55 +0100 Subject: [PATCH v5 11/13] iio: frequency: ad9910: show channel priority in debugfs Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-11-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=6698; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=YXB2Pe5UcxllzPIM4SqwqoK/3Q7QbKnTwc7le1YcLk8=; b=ZxTd/vhWZHPNWu7V9uedAgluD4SMUfm2YuHYr4IjGHLToRRIi8gr8wf/mKiwHknmd2BWGrgyP /qAOXRbzByKCFkZPidDeqFBA7DURGOkpToDpUkd04qr3GHxeF/9wR3R X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Expose frequency_source, phase_source and amplitude_source attributes in debugfs. Those indicate from which channel the specific DDS parameter is being sourced by returning its label. The implementation follows the priority table found in the datasheet. Signed-off-by: Rodrigo Alencar --- drivers/iio/frequency/ad9910.c | 148 +++++++++++++++++++++++++++++++++++++= ++++ 1 file changed, 148 insertions(+) diff --git a/drivers/iio/frequency/ad9910.c b/drivers/iio/frequency/ad9910.c index 91f28c7de68b..8c8e73f340f8 100644 --- a/drivers/iio/frequency/ad9910.c +++ b/drivers/iio/frequency/ad9910.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -2116,6 +2117,146 @@ static int ad9910_setup(struct device *dev, struct = ad9910_state *st, return ad9910_io_update(st); } =20 +static inline const char *ad9910_frequency_source_get(struct ad9910_state = *st) +{ + bool ram_en, mode_en; + + guard(mutex)(&st->lock); + + /* RAM enabled and data destination is frequency */ + ram_en =3D AD9910_RAM_ENABLED(st); + if (ram_en && AD9910_DEST_FREQUENCY =3D=3D + FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* DRG enabled and data destination is frequency */ + mode_en =3D FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_FREQUENCY =3D=3D + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG]; + + /* Parallel data port enabled and data destination is frequency */ + mode_en =3D FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en) /* TODO: get destination from backend once it is supported */ + return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_PORT]; + + /* FTW: RAM enabled and data destination is phase, amplitude, or polar */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_frequency_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_frequency_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_frequency_source); + +static inline const char *ad9910_phase_source_get(struct ad9910_state *st) +{ + bool ram_en, mode_en; + u32 destination; + + guard(mutex)(&st->lock); + + /* RAM enabled and data destination is phase or polar */ + ram_en =3D AD9910_RAM_ENABLED(st); + if (ram_en) { + destination =3D FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (destination =3D=3D AD9910_DEST_PHASE || + destination =3D=3D AD9910_DEST_POLAR) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + } + + /* DRG enabled and data destination is phase */ + mode_en =3D FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_PHASE =3D=3D + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG]; + + /* Parallel data port enabled and data destination is phase */ + mode_en =3D FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en) /* TODO: get destination from backend once it is supported */ + return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_PORT]; + + /* POW: RAM enabled and data destination is frequency, amplitude, or pola= r */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_phase_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_phase_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_phase_source); + +static inline const char *ad9910_amplitude_source_get(struct ad9910_state = *st) +{ + bool ram_en, mode_en; + u32 destination; + + guard(mutex)(&st->lock); + + /* OSK enabled */ + mode_en =3D FIELD_GET(AD9910_CFR1_OSK_ENABLE_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (mode_en) + return ad9910_channel_str[AD9910_CHAN_IDX_OSK]; + + /* RAM enabled and data destination is amplitude or polar */ + ram_en =3D AD9910_RAM_ENABLED(st); + if (ram_en) { + destination =3D FIELD_GET(AD9910_CFR1_RAM_PLAYBACK_DEST_MSK, + st->reg[AD9910_REG_CFR1].val32); + if (destination =3D=3D AD9910_DEST_AMPLITUDE || + destination =3D=3D AD9910_DEST_POLAR) + return ad9910_channel_str[AD9910_CHAN_IDX_RAM]; + } + + /* DRG enabled and data destination is amplitude */ + mode_en =3D FIELD_GET(AD9910_CFR2_DRG_ENABLE_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en && AD9910_DEST_AMPLITUDE =3D=3D + FIELD_GET(AD9910_CFR2_DRG_DEST_MSK, + st->reg[AD9910_REG_CFR2].val32)) + return ad9910_channel_str[AD9910_CHAN_IDX_DRG]; + + /* Parallel data port enabled and data destination is amplitude */ + mode_en =3D FIELD_GET(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, + st->reg[AD9910_REG_CFR2].val32); + if (mode_en) /* TODO: get destination from backend once it is supported */ + return ad9910_channel_str[AD9910_CHAN_IDX_PARALLEL_PORT]; + + /* only way to control amplitude at this point is through OSK */ + if (ram_en) + return ad9910_channel_str[AD9910_CHAN_IDX_OSK]; + + /* single tone profiles */ + return ad9910_channel_str[AD9910_CHAN_IDX_PROFILE_0 + st->profile]; +} + +static int ad9910_amplitude_source_show(struct seq_file *s, void *ignored) +{ + seq_printf(s, "%s\n", ad9910_amplitude_source_get(s->private)); + return 0; +} +DEFINE_SHOW_ATTRIBUTE(ad9910_amplitude_source); + static inline void ad9910_debugfs_init(struct ad9910_state *st, struct iio_dev *indio_dev) { @@ -2131,6 +2272,13 @@ static inline void ad9910_debugfs_init(struct ad9910= _state *st, =20 snprintf(buf, sizeof(buf), "/sys/class/firmware/%s/data", st->ram_fwu_nam= e); debugfs_create_symlink("ram_data", d, buf); + + debugfs_create_file("frequency_source", 0400, d, st, + &ad9910_frequency_source_fops); + debugfs_create_file("phase_source", 0400, d, st, + &ad9910_phase_source_fops); + debugfs_create_file("amplitude_source", 0400, d, st, + &ad9910_amplitude_source_fops); } =20 static int ad9910_probe(struct spi_device *spi) --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 2A52A3D1A97; Sun, 17 May 2026 18:38:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; cv=none; b=X2Uc6QXSkZ6vNnQfUaGNccDtOOCUX06vOXVWnmX3zQa8KdhJ0o1I4qsRbUdVvsHQbVNnVA6KwS8IF/QdyUZJM8TRY9enYZEY8AqoZWUPmcqSSx65qKuKLJVc1mARVRVsStOj8EuY96jRuDNwB/ffHvPPUBPP+MYOUTdYcJMlOdI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; c=relaxed/simple; bh=zU8kqOJYEyW48g2H5RnwB6TqD4QaQ1elueHqtC2b73A=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=vD8fwGcfZD3IPJIeVcvESvEKlSNgkeZjmk6WM+9ZpngfanvFF122PNpqIASQvODViC5LD3iNPbAjm7+KOvlRCzElhsRRqXbESQmbnAlZgr2iN1gKpijFXscRQmnMaSGXqTXiiwC7lMzB1fYHDz3uAT0Zm8/qQcdzlZbxi0JcCuI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=VLtBGikh; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="VLtBGikh" Received: by smtp.kernel.org (Postfix) with ESMTPS id E4657C4AF65; Sun, 17 May 2026 18:38:16 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043097; bh=zU8kqOJYEyW48g2H5RnwB6TqD4QaQ1elueHqtC2b73A=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=VLtBGikhkoxA+nsAZXgiu9YK3INa4puOoZNdhEWDJfDKk2MZcPtzhYvdBlBTvpq0q PPAFkhhZNp2yJWzIxSJYiCK7OJiEVeVrS9/fJiO1xX2I4WNUnaC3W2zVnI7W8i+qc6 iWzccRDXJjomu50zj2LZh6bK+CYZyhb0la/NqdGeEMePn+K5RK86fCi7BOOsqt5T+W 8PSP92sFU4ODcXaAaIgHUZJ9KaFfVDV9DPEg1tNyGWsGLBQU08f8jU+Lz5V2wlgdXl Xn44APBhUEkWlafacWsLZWoMsgHAuCDpLGpumDpumiRieCdNXSRlh0MYjg/NQ8Zshv w1uNj62BnbBOQ== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id D55EFCD4F54; Sun, 17 May 2026 18:38:16 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:56 +0100 Subject: [PATCH v5 12/13] Documentation: ABI: testing: add docs for ad9910 sysfs entries Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-12-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=4308; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=vq/r4p3mHdAjM1JHWSiE7n5FYlIvuWbgRwIbdnmEcsk=; b=sHP674t9lVcpzbTlqajdKrXwjkrCKjGVrjxpSnSAawspN+mIOPtLFaWU6XQlZpYMIc/WDHpP9 AJCXY5sqvJOAdWNWkaTHVvN1KyTSa/VCyVBebIhDP9kSD0dQWRDoQxg X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar 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 --- .../ABI/testing/sysfs-bus-iio-frequency-ad9910 | 76 ++++++++++++++++++= ++++ MAINTAINERS | 1 + 2 files changed, 77 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 b/Doc= umentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 new file mode 100644 index 000000000000..934e6e8f0595 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-bus-iio-frequency-ad9910 @@ -0,0 +1,76 @@ +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 derived from this offset combined with the processed buffer sample + 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 + derived from this offset combined with the processed buffer sample + 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 derived from this offset combined with the processed + buffer sample value. + +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_dwell_en +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + For a channel that produces parametric sweeps, this attribute controls + the sweep behavior at the configured limits. It enables dwell mode at a + sweep limit when set to 1. Otherwise, the sweep may stop at the initial + position or restart from that initial position or continue by reversing + its direction. + +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_frequency_roc +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + Frequency rate of change in Hz/s for channels that produce linear + frequency sweeps. This value may be influenced by the channel's + sampling_frequency setting. + +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_phase_roc +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + Phase rate of change in rad/s for channels that produce linear + phase sweeps. This value may be influenced by the channel's + sampling_frequency setting. + +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_roc +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + Amplitude scale rate of change in 1/s for channels that ramp + amplitude. This value may be influenced by the channel's + sampling_frequency setting. + +What: /sys/bus/iio/devices/iio:deviceX/out_altvoltageY_scale_roc_available +KernelVersion: +Contact: linux-iio@vger.kernel.org +Description: + Lists the available scale_roc values for the channel based on + the current sampling_frequency. Values are space-separated in + ascending order. diff --git a/MAINTAINERS b/MAINTAINERS index b2b7f54f5a24..c39affe4157a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1643,6 +1643,7 @@ M: Rodrigo Alencar 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 =20 --=20 2.43.0 From nobody Mon May 25 06:43:07 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 477763D3008; Sun, 17 May 2026 18:38:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; cv=none; b=VtXUWNh920FIdkcqo4CWqBhtnOMHYiI6cbBiyBnAYZpF4NXLgp0Rmkq8rirvspifoVZtVAVkEvwKeOBOBIAyowz5UVhAPxyh60sGEWKHoN7IhwIj6Plb87t9tmMokvgD3Pqj2uY09YRuB70olJbLUHLoCeYtPReua/xR9ankB4g= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779043097; c=relaxed/simple; bh=uuFr967gKm5GxlaFs+MCsKrIHZCHHDMIdqk/GQ9ZKbk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=laaecTbN3c9f0fabbw9akYph50xteYnNx3LcUFiZlibL7jwHtj8vTUPvZzoNWfvllhuhhXjsObMbmLpo1WiSvs7V1G3E/VMbOV1HZwJ7jv9WHrEzAsY7LK5TqFnjWCDbQYBbecOqMQtEJ3Y68Q9sNMVi+LbdGgoM3qTdqkZru9c= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=bZQeTZcC; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="bZQeTZcC" Received: by smtp.kernel.org (Postfix) with ESMTPS id 19928C4AF0E; Sun, 17 May 2026 18:38:17 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1779043097; bh=uuFr967gKm5GxlaFs+MCsKrIHZCHHDMIdqk/GQ9ZKbk=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=bZQeTZcC4sTzkiz9Gv2qmblbIqW23dfkegB2Kwz3J2d8Jk/twOi1zu3fI0sxptwCg Zov4vzHI3lMjQMRKcu0KKdt4V+5YT0gl6B6A4G/qu+9k9Nvtl6mvwUTX5wvW81zPEH W2L6C2uAepxwujuKZnmRhk8mQ/6ASa0V4Z75QcdsDI+nus97K1ZDbIK6y7hY0+PADs bDr/ovZv1v0/ZqQW8lXA0MKQMUsvo7v/YazSw3hnjitvRA5zwFZorFBU45zTDNdG0a 43bvPWtvvmfWKl9dc45tSJsOzwzGTqQPBjWkTzvoNJqhpjCKqNTyDmgE7rBoEl5F/S X1upga+4g+oKw== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 105B6CD4F3D; Sun, 17 May 2026 18:38:17 +0000 (UTC) From: Rodrigo Alencar via B4 Relay Date: Sun, 17 May 2026 19:37:57 +0100 Subject: [PATCH v5 13/13] docs: iio: add documentation for ad9910 driver Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260517-ad9910-iio-driver-v5-13-31599c88314a@analog.com> References: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> In-Reply-To: <20260517-ad9910-iio-driver-v5-0-31599c88314a@analog.com> To: linux-iio@vger.kernel.org, devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, linux-doc@vger.kernel.org, linux-hardening@vger.kernel.org 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 X-Mailer: b4 0.14.3 X-Developer-Signature: v=1; a=ed25519-sha256; t=1779043094; l=22157; i=rodrigo.alencar@analog.com; s=default; h=from:subject:message-id; bh=Ll4UAPPHH/JlIJ2c6AjBprSAnwegS2CDUDCIoJel7ko=; b=E9gHNLsZL99K+5Mwa+/RZTzTpXFMTEwd8XBlkzAS10Upyyxe9leUhnhck6xBKpOYAj+KTPIH3 1Ym8MzbI70yD/MDGgH1Jai7D3L/Ex5Y3dzeYly4sioz74S4+w8qUoh1 X-Developer-Key: i=rodrigo.alencar@analog.com; a=ed25519; pk=ULeHbgU/OYh/PG/4anHDfLgldFItQHAhOktYRVLMFRo= X-Endpoint-Received: by B4 Relay for rodrigo.alencar@analog.com/default with auth_id=561 X-Original-From: Rodrigo Alencar Reply-To: rodrigo.alencar@analog.com From: Rodrigo Alencar Add documentation for the AD9910 DDS IIO driver, which describes channels, DDS modes, attributes and ABI usage examples. Signed-off-by: Rodrigo Alencar --- Documentation/iio/ad9910.rst | 666 +++++++++++++++++++++++++++++++++++++++= ++++ Documentation/iio/index.rst | 1 + MAINTAINERS | 1 + 3 files changed, 668 insertions(+) diff --git a/Documentation/iio/ad9910.rst b/Documentation/iio/ad9910.rst new file mode 100644 index 000000000000..dbcf8f8a1dda --- /dev/null +++ b/Documentation/iio/ad9910.rst @@ -0,0 +1,666 @@ +.. SPDX-License-Identifier: GPL-2.0-only + +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D +AD9910 driver +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +Direct Digital Synthesizer (DDS) driver for the Analog Devices Inc. AD9910. +The module name is ``ad9910``. + +* `AD9910 `_ + +The AD9910 is a 1 GSPS DDS with a 14-bit DAC, controlled over SPI. The dri= ver +exposes the device through the IIO ``altvoltage`` channel type and supports +five DDS operating modes: single tone, parallel port modulation, digital r= amp +generation (DRG), RAM playback and output shift keying (OSK). The device h= as +8 hardware profiles, each capable of storing independent single tone and R= AM +playback parameters. + + +Channel hierarchy +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The driver exposes the following IIO output channels, each identified by a +unique channel number and a human-readable label. The ``phy`` channel is t= he +root of the hierarchy. Changing its ``sampling_frequency`` reconfigures the +system clock (SYSCLK) which affects all other channels. Most of the +mode-specific channels have an ``enable`` attribute that turns the mode on= /off. + +.. flat-table:: + :header-rows: 1 + + * - Channel + - Label + - Parent + - Description + + * - ``out_altvoltage100`` + - ``phy`` + - + - Physical output: system clock and profile control. + See `Physical channel`_. + + * - ``out_altvoltage110`` ... ``out_altvoltage117`` + - ``profile0`` ... ``profile7`` + - ``phy`` + - Single tone control: frequency, phase, amplitude. + See `Single Tone mode`_. + + * - ``out_altvoltage120`` + - ``parallel_port`` + - ``phy`` + - Parallel port modulation channel. + See `Parallel Port mode`_. + + * - ``out_altvoltage130`` + - ``digital_ramp_generator`` + - ``phy`` + - Digital ramp generator (DRG) control: enable. + See `Digital ramp generator (DRG)`_. + + * - ``out_altvoltage131`` + - ``digital_ramp_up`` + - ``digital_ramp_generator`` + - DRG ramp-up parameters: dwell enable, limits, rate of change, ramp = rate. + + * - ``out_altvoltage132`` + - ``digital_ramp_down`` + - ``digital_ramp_generator`` + - DRG ramp-down parameters: dwell enable, limits, rate of change, ram= p rate. + + * - ``out_altvoltage140`` + - ``ram_control`` + - ``phy`` + - RAM playback: enable, frequency, phase and sampling frequency for a= ctive + profile. See `RAM mode`_. + + * - ``out_altvoltage150`` + - ``output_shift_keying`` + - ``phy`` + - Output shift keying (OSK): enable, amplitude scale, ramp rate, + rate of change control. See `Output Shift Keying (OSK)`_. + +DDS modes +=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The AD9910 supports multiple modes of operation that can be configured +independently or in combination. Such modes and their corresponding IIO ch= annels +are described in this section. Each DDS core parameter (frequency, phase a= nd +amplitude) value can come from different sources, but only one is active a= t a +time. This activation depends on a priority list, which is based on the en= able +and destination configurations for such modes. 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 simultaneou= sly: + +.. 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 Frequency Tuning Word, FTW (frequency_offset) + - Parallel data port enabled and data destination is frequency + + * - + - FTW register (frequency) + - RAM enabled and data destination is not frequency + + * - Lowest Priority + - FTW (frequency) in single tone channel for the active profile + - All other cases + +.. 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 Phase Offset Word, POW register LSBs (phase_= offset) + - Parallel data port enabled and data destination is polar + + * - + - POW register (phase) + - RAM enabled and destination is not phase nor polar + + * - Lowest Priority + - POW (phase) in single tone channel for the active profile + - All other cases + +.. flat-table:: DDS Amplitude Control + :header-rows: 1 + + * - Priority + - Data Source + - Conditions + + * - Highest Priority + - Amplitude Scale Factor, ASF register and OSK generator + - OSK enabled + + * - + - 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) + +While debugging or testing, the debug attributes ``frequency_source``, +``phase_source`` and ``amplitude_source`` can be used to read the label of +the channel that is actively controlling the correspondent DDS parameter, +which reflects the priority list described above. + +Single Tone mode +---------------- + +Single tone is the baseline operating mode. The ``profileY`` channels +provide enable, frequency, phase and amplitude control: + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``en`` + - boolean (0 or 1) + - 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 brings the device to a powered down sta= te. + + * - ``frequency`` + - Hz + - Output frequency. Range :math:`[0, f_{SYSCLK}/2)`. Stored in the + profile's frequency tuning word (FTW). + + * - ``phase`` + - rad + - Phase offset. Range :math:`[0, 2\pi)`. Stored in the profile's phase + offset word (POW). + + * - ``scale`` + - fractional + - Amplitude scale factor. Range :math:`[0, 1]`. Stored in the profile= 's + amplitude scale factor (ASF). + +Profile switching is allowed while RAM mode is enabled. In that case singl= e tone +parameters are stored in a shadow register and are not written to hardware= until +RAM mode is disabled. + +Usage examples +^^^^^^^^^^^^^^ + +Configure a 100 MHz tone in profile to 2 and set it as the active profile: + +.. code-block:: bash + + echo 100000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage112_freq= uency + echo 0.5 > /sys/bus/iio/devices/iio:device0/out_altvoltage112_scale + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage112_phase + + # Activate profile 2 + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage112_en + +Read back the current single tone frequency: + +.. code-block:: bash + + cat /sys/bus/iio/devices/iio:device0/out_altvoltage112_frequency + +Parallel Port mode +------------------ + +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 + + * - ``frequency_scale`` + - power-of-2 + - Frequency modulation (FM) gain multiplier applied to 16-bit parallel + input. Range :math:`[1, 32768]`, must be a power of 2. + + * - ``frequency_offset`` + - Hz + - Base FTW to which scaled parallel data is added. Range :math:`[0, f= _{SYSCLK}/2)`. + + * - ``phase_offset`` + - rad + - Base phase for polar modulation. Lower 8 bits of POW register. + Range :math:`[0, 2\pi/256)`. + + * - ``scale_offset`` + - fractional + - Base amplitude for polar modulation. Lower 6 bits of ASF register. + Range :math:`[0, 1/256)`. + +Usage examples +^^^^^^^^^^^^^^ + +Set parallel port frequency modulation with a scale of 16 and a 50 MHz +offset: + +.. code-block:: bash + + echo 16 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_frequency_s= cale + echo 50000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage120_frequ= ency_offset + +Digital ramp generator (DRG) +---------------------------- + +The DRG produces linear frequency, phase or amplitude sweeps using dedicat= ed +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``). + +The DRG can target only one destination at a time (frequency, phase or +amplitude). Destination selection follows a "last write wins" policy: writ= ing +any value (including zero) to a destination-specific attribute (e.g. +``frequency``, ``frequency_roc``, ``phase``, ``phase_roc``, ``scale`` or +``scale_roc``) switches the DRG destination accordingly. Reading an attrib= ute +whose destination is not currently active returns ``-EBUSY``. + +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 + + * - ``dwell_en`` + - boolean + - Enable dwell at the ramp limit. When disabled, the ramp auto-transi= tions + at this limit without waiting for the DRCTL pin. Disabling both cre= ates a + bidirectional continuous ramp (Triangular pattern). Other configura= tions + create a single-shot ramp at the transition of the DRCTL pin: ramp-= up + only, ramp-down only or bidirectional with dwell at the limits. + + * - ``frequency`` + - Hz + - Frequency ramp limit. Range: :math:`[0, f_{SYSCLK}/2)`. Writing a v= alue + sets the ramp destination to frequency. Reading back returns the + currently active frequency limit or -EBUSY if other destination is + active (phase or amplitude). + + * - ``phase`` + - rad + - Phase ramp limit. Range: :math:`[0, 2\pi)`. Writing a value sets the + ramp destination to phase. Reading back returns the currently active + phase limit or -EBUSY if other destination is active (frequency or + amplitude). + + * - ``scale`` + - fractional + - Amplitude scale ramp limit. Range: :math:`[0, 1)`. Writing a value = sets + the ramp destination to amplitude. Reading back returns the current= ly + active scale limit or -EBUSY if other destination is active (freque= ncy + or phase). + + * - ``sampling_frequency`` + - Hz + - Ramp clock rate. It is controlled by an integer divider so the requ= ested + value will adjust to nearest supported value. + + * - ``frequency_roc`` + - Hz/s + - Frequency rate of change. Sets the per-tick frequency increment/dec= rement + based on the current ramp clock rate. + + * - ``phase_roc`` + - rad/s + - Phase rate of change. Sets the per-tick phase increment/decrement b= ased + on the current ramp clock rate. + + * - ``scale_roc`` + - 1/s + - Amplitude scale rate of change. Sets the per-tick amplitude scale + increment/decrement based on the current ramp clock rate. + +Usage examples +^^^^^^^^^^^^^^ + +Configure a frequency sweep from 40 MHz to 60 MHz with a rate of change of +25 GHz/s: + +.. code-block:: bash + + # Disable dwell on both limits for a bidirectional continuous ramp + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_dwell_en + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_dwell_en + + # Set ramp limits + echo 60000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_frequ= ency + echo 40000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_frequ= ency + + # Set ramp rate + echo 25000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_sampl= ing_frequency + echo 25000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_sampl= ing_frequency + + # Set frequency rate of change (Hz/s) + echo 25000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage131_fr= equency_roc + echo 25000000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage132_fr= equency_roc + + # Enable the DRG + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage130_en + +RAM mode +-------- + +The AD9910 contains a 1024 x 32-bit RAM that can be loaded with waveform d= ata +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 betwe= en + 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: :math:`[0, f_{SYSCLK}/= 2)`. + + * - ``phase`` + - rad + - Phase offset word used as the single tone phase when RAM destination + is not ``phase``. Range: :math:`[0, 2\pi)`. + + * - ``sampling_frequency`` + - Hz + - RAM playback step rate of the active profile, which controls how fa= st the + address counter advances. It is controlled by an integer divider so= the + requested value will adjust to nearest supported value. + +Loading RAM data +^^^^^^^^^^^^^^^^ + +RAM data is loaded through the firmware upload framework. The driver regis= ters +a firmware upload sysfs entry named ``iio_deviceX:ram``. The firmware data +follows a binary format (version 1) with an 80-byte header followed by data +words. All fields are big-endian. + +.. flat-table:: RAM firmware header (80 bytes) + :header-rows: 1 + + * - Offset + - Size + - Field + - Description + + * - 0 + - 4 + - ``magic`` + - Magic number: ``0x00AD9910`` + + * - 4 + - 2 + - ``version`` + - Format version: ``0x0001`` + + * - 6 + - 2 + - ``wcount`` + - Number of 32-bit RAM data words (0--1024) + + * - 8 + - 4 + - ``crc`` + - CRC32 checksum over ``cfr1``, ``profiles`` and ``words`` + + * - 12 + - 4 + - ``cfr1`` + - CFR1 register value. Only RAM-relevant bits are used: + bits [30:29] set data destination (00: frequency, 01: phase, + 10: amplitude, 11: polar); bits [20:17] set internal profile + control (see datasheet Table 14) + + * - 16 + - 64 + - ``profiles[0..7]`` + - 8 sets of 8-byte RAM profile configurations (see below) + + * - 80 + - 4 x wcount + - ``words[]`` + - RAM data words in reverse order + +Each 8-byte profile entry contains: + +.. flat-table:: RAM profile entry (8 bytes) + :header-rows: 1 + + * - Bits + - Field + - Description + + * - [55:40] + - Address step rate + - Controls playback speed for this profile + + * - [39:30] + - End address + - Last RAM address for this profile + + * - [23:14] + - Start address + - First RAM address for this profile + + * - [5] + - No-dwell high + - No-dwell at high limit (ramp-up mode) + + * - [3] + - Zero-crossing + - Zero-crossing enable (direct-switch mode) + + * - [2:0] + - Operating mode + - 000: direct switch, 001: ramp-up, 010: bidirectional, + 011: bidirectional continuous, 100: ramp-up continuous + +Usage examples +^^^^^^^^^^^^^^ + +Configure RAM mode with firmware data and enable it: + +.. code-block:: bash + + # Load RAM data via firmware upload + echo 1 > /sys/class/firmware/iio\:device0\:ram/loading + cat ad9910-ram.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_altvoltage140_en + +Output Shift Keying (OSK) +------------------------- + +OSK controls the output amplitude envelope, allowing the output to be ramp= ed +on/off rather than switched abruptly. + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``en`` + - boolean (0 or 1) + - Enable/disable OSK. + + * - ``scale`` + - fractional + - Target amplitude for the OSK ramp. 14-bit ASF field. Range: :math:`= [0, 1)`. + + * - ``scale_roc`` + - 1/s + - Amplitude scale rate of change. Writing a non-zero value enables + automatic OSK and selects the closest hardware step size. Writing `= `0`` + disables automatic ramping (manual control of the ASF register using + ``scale``). Writing the maximum available value enables pin-control= led + immediate transition with no ramping. + + * - ``scale_roc_available`` + - 1/s + - Lists the available ``scale_roc`` values based on the current + ``sampling_frequency``. The first value is always ``0`` (disabled) = and + the last value corresponds to pin-controlled immediate mode. + + * - ``sampling_frequency`` + - Hz + - OSK ramp rate. It is controlled by an integer divider so the reques= ted + value will adjust to nearest supported value. + +Usage examples +^^^^^^^^^^^^^^ + +Enable OSK with automatic ramping: + +.. code-block:: bash + + # Set ramp rate + echo 1000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_sampli= ng_frequency + + # Check available rate of change values + cat /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale_roc_availab= le + + # Enable automatic OSK with a rate of change + echo 61.035000000 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_s= cale_roc + + # Enable OSK + echo 1 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_en + +Enable pin-controlled immediate OSK: + +.. code-block:: bash + + # Read the last (highest) available value for pin-controlled mode + cat /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale_roc_availab= le + + # Enable OSK in manual mode (no rate of change) + echo 0 > /sys/bus/iio/devices/iio:device0/out_altvoltage150_scale_roc + 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 +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The ``phy`` channel provides device-level control: + +.. flat-table:: + :header-rows: 1 + + * - Attribute + - Unit + - Description + + * - ``sampling_frequency`` + - Hz + - System clock (SYSCLK) frequency. When the internal PLL is enabled + (via the ``adi,pll-enable`` devicetree property), configures the PLL + multiplier (range 420--1000 MHz). Without PLL, the reference clock = can + only be divided by 2. + + * - ``powerdown`` + - boolean (0 or 1) + - 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_sam= pling_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 007e0a1fcc5a..1ada7b460066 100644 --- a/Documentation/iio/index.rst +++ b/Documentation/iio/index.rst @@ -30,6 +30,7 @@ Industrial I/O Kernel Drivers ad7606 ad7625 ad7944 + ad9910 ade9000 adis16475 adis16480 diff --git a/MAINTAINERS b/MAINTAINERS index c39affe4157a..363b7af827c3 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1645,6 +1645,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 =20 ANALOG DEVICES INC MAX22007 DRIVER --=20 2.43.0