diff options
| author | Mark Brown <broonie@kernel.org> | 2026-05-29 18:09:08 +0100 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-05-29 18:09:08 +0100 |
| commit | d668848e9d7fc23918835b741cc350cddb14b483 (patch) | |
| tree | 26a0d93ca6c1e79f59059d0f5e0e2be067bd8dd6 /drivers | |
| parent | a3f2fd910514c1df8adf5869c587d9cd9ac77ba1 (diff) | |
| parent | 47d7bca76dd4f36ba0525d761f247c76ec9e4b17 (diff) | |
| download | linux-next-history-d668848e9d7fc23918835b741cc350cddb14b483.tar.gz | |
Merge branch 'for-next' of https://github.com/Xilinx/linux-xlnx.git
Diffstat (limited to 'drivers')
| -rw-r--r-- | drivers/firmware/xilinx/Makefile | 2 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-csu-reg.c | 258 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-csu-reg.h | 18 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp.c | 6 | ||||
| -rw-r--r-- | drivers/soc/xilinx/zynqmp_power.c | 47 |
5 files changed, 303 insertions, 28 deletions
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 = ®->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[] = { |
