aboutsummaryrefslogtreecommitdiffstats
diff options
authorMark Brown <broonie@kernel.org>2026-05-29 18:09:08 +0100
committerMark Brown <broonie@kernel.org>2026-05-29 18:09:08 +0100
commitd668848e9d7fc23918835b741cc350cddb14b483 (patch)
tree26a0d93ca6c1e79f59059d0f5e0e2be067bd8dd6
parenta3f2fd910514c1df8adf5869c587d9cd9ac77ba1 (diff)
parent47d7bca76dd4f36ba0525d761f247c76ec9e4b17 (diff)
downloadlinux-next-history-d668848e9d7fc23918835b741cc350cddb14b483.tar.gz
Merge branch 'for-next' of https://github.com/Xilinx/linux-xlnx.git
-rw-r--r--Documentation/ABI/stable/sysfs-driver-firmware-zynqmp33
-rw-r--r--MAINTAINERS10
-rw-r--r--drivers/firmware/xilinx/Makefile2
-rw-r--r--drivers/firmware/xilinx/zynqmp-csu-reg.c258
-rw-r--r--drivers/firmware/xilinx/zynqmp-csu-reg.h18
-rw-r--r--drivers/firmware/xilinx/zynqmp.c6
-rw-r--r--drivers/soc/xilinx/zynqmp_power.c47
-rw-r--r--include/linux/firmware/xlnx-zynqmp.h4
8 files changed, 349 insertions, 29 deletions
diff --git a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
index c3fec3c835af3..ac8c2314deee2 100644
--- a/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
+++ b/Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
@@ -254,3 +254,36 @@ Description:
The expected result is 500.
Users: Xilinx
+
+What: /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/*
+Date: May 2026
+KernelVersion: 7.2
+Contact: "Ronak Jain" <ronak.jain@amd.com>
+Description:
+ Read/Write CSU (Configuration Security Unit) registers.
+
+ This interface provides dynamic access to CSU registers that are
+ discovered from the firmware at boot time using PM_QUERY_DATA API.
+
+ The supported registers are:
+
+ - multiboot: CSU_MULTI_BOOT register
+ - idcode: CSU_IDCODE register (read-only)
+ - pcap-status: CSU_PCAP_STATUS register (read-only)
+
+ Read operations use the existing IOCTL_READ_REG API.
+ Write operations use the existing IOCTL_MASK_WRITE_REG API.
+
+ The firmware enforces access control - read-only registers will reject
+ write attempts even though the sysfs permissions show write access.
+
+ Usage for reading::
+
+ # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot
+ # cat /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/idcode
+
+ Usage for writing (mask and value are in hexadecimal)::
+
+ # echo 0xFFFFFFF 0x0 > /sys/devices/platform/firmware\:zynqmp-firmware/csu_registers/multiboot
+
+Users: Xilinx/AMD
diff --git a/MAINTAINERS b/MAINTAINERS
index 7e5b32e18b0ae..0a9a60c301276 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29298,6 +29298,16 @@ F: drivers/dma/xilinx/xdma.c
F: include/linux/dma/amd_xdma.h
F: include/linux/platform_data/amd_xdma.h
+XILINX ZYNQMP CSU REGISTER DRIVER
+M: Senthil Nathan Thangaraj <senthilnathan.thangaraj@amd.com>
+R: Michal Simek <michal.simek@amd.com>
+R: Ronak Jain <ronak.jain@amd.com>
+L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
+S: Maintained
+F: Documentation/ABI/stable/sysfs-driver-firmware-zynqmp
+F: drivers/firmware/xilinx/zynqmp-csu-reg.c
+F: drivers/firmware/xilinx/zynqmp-csu-reg.h
+
XILINX ZYNQMP DPDMA DRIVER
M: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
L: dmaengine@vger.kernel.org
diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile
index 8db0e66b6b7eb..6203f41daaa6a 100644
--- a/drivers/firmware/xilinx/Makefile
+++ b/drivers/firmware/xilinx/Makefile
@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for Xilinx firmwares
-obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o
+obj-$(CONFIG_ZYNQMP_FIRMWARE) += zynqmp.o zynqmp-ufs.o zynqmp-crypto.o zynqmp-csu-reg.o
obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += zynqmp-debug.o
diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.c b/drivers/firmware/xilinx/zynqmp-csu-reg.c
new file mode 100644
index 0000000000000..6e11a9b019f78
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-csu-reg.c
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Xilinx Zynq MPSoC CSU Register Access
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ * Michal Simek <michal.simek@amd.com>
+ * Ronak Jain <ronak.jain@amd.com>
+ */
+
+#include <linux/firmware/xlnx-zynqmp.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "zynqmp-csu-reg.h"
+
+/* Node ID for CSU module in firmware */
+#define CSU_NODE_ID 0
+
+/* Maximum number of CSU registers supported */
+#define MAX_CSU_REGS 50
+
+/* Size of register name returned by firmware (3 u32 words = 12 bytes) */
+#define CSU_REG_NAME_LEN 12
+
+/**
+ * struct zynqmp_csu_reg - CSU register information
+ * @id: Register index from firmware
+ * @name: Register name
+ * @attr: Device attribute for sysfs
+ */
+struct zynqmp_csu_reg {
+ u32 id;
+ char name[CSU_REG_NAME_LEN];
+ struct device_attribute attr;
+};
+
+/**
+ * struct zynqmp_csu_data - Per-device CSU data
+ * @csu_regs: Array of CSU registers
+ * @csu_attr_group: Attribute group for sysfs
+ */
+struct zynqmp_csu_data {
+ struct zynqmp_csu_reg *csu_regs;
+ struct attribute_group csu_attr_group;
+};
+
+/**
+ * zynqmp_pm_get_node_count() - Get number of supported nodes via QUERY_DATA
+ *
+ * Return: Number of nodes on success, or negative error code
+ */
+static int zynqmp_pm_get_node_count(void)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_GET_NODE_COUNT;
+
+ ret = zynqmp_pm_query_data(qdata, ret_payload);
+ if (ret)
+ return ret;
+
+ return ret_payload[1];
+}
+
+/**
+ * zynqmp_pm_get_node_name() - Get node name via QUERY_DATA
+ * @index: Register index
+ * @name: Buffer to store register name
+ *
+ * Return: 0 on success, error code otherwise
+ */
+static int zynqmp_pm_get_node_name(u32 index, char *name)
+{
+ struct zynqmp_pm_query_data qdata = {0};
+ u32 ret_payload[PAYLOAD_ARG_CNT];
+ int ret;
+
+ qdata.qid = PM_QID_GET_NODE_NAME;
+ qdata.arg1 = index;
+
+ ret = zynqmp_pm_query_data(qdata, ret_payload);
+ if (ret)
+ return ret;
+
+ memcpy(name, &ret_payload[1], CSU_REG_NAME_LEN);
+ name[CSU_REG_NAME_LEN - 1] = '\0';
+
+ return 0;
+}
+
+/**
+ * zynqmp_csu_reg_show() - Generic show function for all registers
+ * @dev: Device pointer
+ * @attr: Device attribute
+ * @buf: Output buffer
+ *
+ * Return: Number of bytes written to buffer, or error code
+ */
+static ssize_t zynqmp_csu_reg_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct zynqmp_csu_reg *reg;
+ u32 value;
+ int ret;
+
+ /* Use container_of to get register directly */
+ reg = container_of(attr, struct zynqmp_csu_reg, attr);
+
+ ret = zynqmp_pm_sec_read_reg(CSU_NODE_ID, reg->id, &value);
+ if (ret)
+ return ret;
+
+ return sysfs_emit(buf, "0x%08x\n", value);
+}
+
+/**
+ * zynqmp_csu_reg_store() - Generic store function for writable registers
+ * @dev: Device pointer
+ * @attr: Device attribute
+ * @buf: Input buffer
+ * @count: Buffer size
+ *
+ * Format: "mask value" - both mask and value required
+ * Example: echo "0xFFFFFFFF 0x12345678" > register
+ *
+ * Return: count on success, error code otherwise
+ */
+static ssize_t zynqmp_csu_reg_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct zynqmp_csu_reg *reg;
+ u32 mask, value;
+ int ret;
+
+ reg = container_of(attr, struct zynqmp_csu_reg, attr);
+
+ if (sscanf(buf, "%x %x", &mask, &value) != 2)
+ return -EINVAL;
+
+ ret = zynqmp_pm_sec_mask_write_reg(CSU_NODE_ID, reg->id, mask, value);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+/**
+ * zynqmp_csu_discover_registers() - Discover CSU registers from firmware
+ * @pdev: Platform device pointer
+ *
+ * This function uses PM_QUERY_DATA to discover all available CSU registers
+ * and creates sysfs group under /sys/devices/platform/firmware:zynqmp-firmware/
+ *
+ * Return: 0 on success, error code otherwise
+ */
+int zynqmp_csu_discover_registers(struct platform_device *pdev)
+{
+ struct zynqmp_csu_data *csu_data;
+ struct attribute **attrs;
+ int count, ret, i;
+
+ ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_COUNT);
+ if (ret) {
+ dev_dbg(&pdev->dev, "CSU register discovery not supported by current firmware\n");
+ return 0;
+ }
+
+ ret = zynqmp_pm_is_function_supported(PM_QUERY_DATA, PM_QID_GET_NODE_NAME);
+ if (ret) {
+ dev_dbg(&pdev->dev, "CSU register name query not supported by current firmware\n");
+ return 0;
+ }
+
+ count = zynqmp_pm_get_node_count();
+ if (count < 0)
+ return count;
+ if (count == 0) {
+ dev_dbg(&pdev->dev, "No nodes available from firmware\n");
+ return 0;
+ }
+
+ /* Validate count to prevent excessive memory allocation */
+ if (count > MAX_CSU_REGS) {
+ dev_err(&pdev->dev, "Register count %d exceeds maximum %d\n",
+ count, MAX_CSU_REGS);
+ return -EINVAL;
+ }
+
+ dev_dbg(&pdev->dev, "Discovered %d nodes from firmware\n", count);
+
+ csu_data = devm_kzalloc(&pdev->dev, sizeof(*csu_data), GFP_KERNEL);
+ if (!csu_data)
+ return -ENOMEM;
+
+ csu_data->csu_regs = devm_kcalloc(&pdev->dev, count, sizeof(*csu_data->csu_regs),
+ GFP_KERNEL);
+ if (!csu_data->csu_regs) {
+ devm_kfree(&pdev->dev, csu_data);
+ return -ENOMEM;
+ }
+
+ attrs = devm_kcalloc(&pdev->dev, count + 1, sizeof(*attrs), GFP_KERNEL);
+ if (!attrs) {
+ devm_kfree(&pdev->dev, csu_data->csu_regs);
+ devm_kfree(&pdev->dev, csu_data);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < count; i++) {
+ struct zynqmp_csu_reg *reg = &csu_data->csu_regs[i];
+ struct device_attribute *dev_attr = &reg->attr;
+
+ reg->id = i;
+
+ ret = zynqmp_pm_get_node_name(i, reg->name);
+ if (ret) {
+ dev_warn(&pdev->dev, "Failed to get name for register %d\n", i);
+ snprintf(reg->name, sizeof(reg->name), "csu_reg_%d", i);
+ }
+
+ /*
+ * The firmware does not expose per-register access mode via
+ * PM_QUERY_DATA today, so the kernel cannot tell read-only
+ * registers from read-write ones at discovery time. Expose
+ * every register as 0644 and rely on the firmware to reject
+ * IOCTL_MASK_WRITE_REG on read-only registers; the error is
+ * propagated back to userspace from the store callback.
+ */
+ sysfs_attr_init(&dev_attr->attr);
+ dev_attr->attr.name = reg->name;
+ dev_attr->attr.mode = 0644;
+ dev_attr->show = zynqmp_csu_reg_show;
+ dev_attr->store = zynqmp_csu_reg_store;
+
+ attrs[i] = &dev_attr->attr;
+
+ dev_dbg(&pdev->dev, "Register %d: id=%d name=%s\n", i, reg->id, reg->name);
+ }
+
+ csu_data->csu_attr_group.name = "csu_registers";
+ csu_data->csu_attr_group.attrs = attrs;
+
+ ret = devm_device_add_group(&pdev->dev, &csu_data->csu_attr_group);
+ if (ret) {
+ devm_kfree(&pdev->dev, attrs);
+ devm_kfree(&pdev->dev, csu_data->csu_regs);
+ devm_kfree(&pdev->dev, csu_data);
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(zynqmp_csu_discover_registers);
diff --git a/drivers/firmware/xilinx/zynqmp-csu-reg.h b/drivers/firmware/xilinx/zynqmp-csu-reg.h
new file mode 100644
index 0000000000000..b12415db3496f
--- /dev/null
+++ b/drivers/firmware/xilinx/zynqmp-csu-reg.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Xilinx Zynq MPSoC CSU Register Access
+ *
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ *
+ * Michal Simek <michal.simek@amd.com>
+ * Ronak Jain <ronak.jain@amd.com>
+ */
+
+#ifndef __ZYNQMP_CSU_REG_H__
+#define __ZYNQMP_CSU_REG_H__
+
+#include <linux/platform_device.h>
+
+int zynqmp_csu_discover_registers(struct platform_device *pdev);
+
+#endif /* __ZYNQMP_CSU_REG_H__ */
diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c
index fbe8510f49271..b549d07f74971 100644
--- a/drivers/firmware/xilinx/zynqmp.c
+++ b/drivers/firmware/xilinx/zynqmp.c
@@ -27,6 +27,7 @@
#include <linux/firmware/xlnx-zynqmp.h>
#include <linux/firmware/xlnx-event-manager.h>
+#include "zynqmp-csu-reg.h"
#include "zynqmp-debug.h"
/* Max HashMap Order for PM API feature check (1<<7 = 128) */
@@ -2120,6 +2121,11 @@ static int zynqmp_firmware_probe(struct platform_device *pdev)
dev_err_probe(&pdev->dev, PTR_ERR(em_dev), "EM register fail with error\n");
}
+ /* Discover CSU registers dynamically */
+ ret = zynqmp_csu_discover_registers(pdev);
+ if (ret)
+ dev_warn(&pdev->dev, "CSU register discovery failed: %d\n", ret);
+
return of_platform_populate(dev->of_node, NULL, NULL, dev);
}
diff --git a/drivers/soc/xilinx/zynqmp_power.c b/drivers/soc/xilinx/zynqmp_power.c
index 9085db1b480aa..370e61ac47d88 100644
--- a/drivers/soc/xilinx/zynqmp_power.c
+++ b/drivers/soc/xilinx/zynqmp_power.c
@@ -303,18 +303,18 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
* is not available to use) or -ENODEV(Xilinx Event Manager not compiled),
* then use ipi-mailbox or interrupt method.
*/
+ zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
+ sizeof(struct zynqmp_pm_work_struct),
+ GFP_KERNEL);
+ if (!zynqmp_pm_init_suspend_work)
+ return -ENOMEM;
+
+ INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
+ zynqmp_pm_init_suspend_work_fn);
+
ret = register_event(&pdev->dev, PM_INIT_SUSPEND_CB, 0, 0, false,
suspend_event_callback);
if (!ret) {
- zynqmp_pm_init_suspend_work = devm_kzalloc(&pdev->dev,
- sizeof(struct zynqmp_pm_work_struct),
- GFP_KERNEL);
- if (!zynqmp_pm_init_suspend_work)
- return -ENOMEM;
-
- INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
- zynqmp_pm_init_suspend_work_fn);
-
ret = zynqmp_pm_get_family_info(&pm_family_code);
if (ret < 0)
return ret;
@@ -326,14 +326,6 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
else
return -ENODEV;
- ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART,
- false, subsystem_restart_event_callback);
- if (ret) {
- dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n",
- ret);
- return ret;
- }
-
zynqmp_pm_init_restart_work = devm_kzalloc(&pdev->dev,
sizeof(struct zynqmp_pm_work_struct),
GFP_KERNEL);
@@ -342,19 +334,18 @@ static int zynqmp_pm_probe(struct platform_device *pdev)
INIT_WORK(&zynqmp_pm_init_restart_work->callback_work,
zynqmp_pm_subsystem_restart_work_fn);
+
+ ret = register_event(&pdev->dev, PM_NOTIFY_CB, node_id, EVENT_SUBSYSTEM_RESTART,
+ false, subsystem_restart_event_callback);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n",
+ ret);
+ return ret;
+ }
} else if (ret != -EACCES && ret != -ENODEV) {
dev_err(&pdev->dev, "Failed to Register with Xilinx Event manager %d\n", ret);
return ret;
} else if (of_property_present(pdev->dev.of_node, "mboxes")) {
- zynqmp_pm_init_suspend_work =
- devm_kzalloc(&pdev->dev,
- sizeof(struct zynqmp_pm_work_struct),
- GFP_KERNEL);
- if (!zynqmp_pm_init_suspend_work)
- return -ENOMEM;
-
- INIT_WORK(&zynqmp_pm_init_suspend_work->callback_work,
- zynqmp_pm_init_suspend_work_fn);
client = devm_kzalloc(&pdev->dev, sizeof(*client), GFP_KERNEL);
if (!client)
return -ENOMEM;
@@ -398,8 +389,10 @@ static void zynqmp_pm_remove(struct platform_device *pdev)
{
sysfs_remove_file(&pdev->dev.kobj, &dev_attr_suspend_mode.attr);
- if (!rx_chan)
+ if (rx_chan) {
mbox_free_channel(rx_chan);
+ rx_chan = NULL;
+ }
}
static const struct of_device_id pm_of_match[] = {
diff --git a/include/linux/firmware/xlnx-zynqmp.h b/include/linux/firmware/xlnx-zynqmp.h
index d70dcd462b441..a4b293eb96cef 100644
--- a/include/linux/firmware/xlnx-zynqmp.h
+++ b/include/linux/firmware/xlnx-zynqmp.h
@@ -3,7 +3,7 @@
* Xilinx Zynq MPSoC Firmware layer
*
* Copyright (C) 2014-2021 Xilinx
- * Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc.
+ * Copyright (C) 2022 - 2026 Advanced Micro Devices, Inc.
*
* Michal Simek <michal.simek@amd.com>
* Davorin Mista <davorin.mista@aggios.com>
@@ -262,6 +262,8 @@ enum pm_query_id {
PM_QID_CLOCK_GET_NUM_CLOCKS = 12,
PM_QID_CLOCK_GET_MAX_DIVISOR = 13,
PM_QID_PINCTRL_GET_ATTRIBUTES = 15,
+ PM_QID_GET_NODE_NAME = 16,
+ PM_QID_GET_NODE_COUNT = 17,
};
enum rpu_oper_mode {