Firmware-debug provides debugfs interface to all APIs. Signed-off-by: Jolly Shah <jollys@xxxxxxxxxx> Signed-off-by: Rajan Vaja <rajanv@xxxxxxxxxx> --- drivers/firmware/xilinx/zynqmp/Kconfig | 7 + drivers/firmware/xilinx/zynqmp/Makefile | 1 + drivers/firmware/xilinx/zynqmp/firmware-debug.c | 511 +++++++++++++++++++++ drivers/firmware/xilinx/zynqmp/firmware.c | 7 +- .../linux/firmware/xilinx/zynqmp/firmware-debug.h | 31 ++ 5 files changed, 556 insertions(+), 1 deletion(-) create mode 100644 drivers/firmware/xilinx/zynqmp/firmware-debug.c create mode 100644 include/linux/firmware/xilinx/zynqmp/firmware-debug.h diff --git a/drivers/firmware/xilinx/zynqmp/Kconfig b/drivers/firmware/xilinx/zynqmp/Kconfig index 8f7709d..bdd0188 100644 --- a/drivers/firmware/xilinx/zynqmp/Kconfig +++ b/drivers/firmware/xilinx/zynqmp/Kconfig @@ -13,4 +13,11 @@ config ZYNQMP_FIRMWARE Say yes to enable zynqmp firmware interface driver. In doubt, say N +config ZYNQMP_FIRMWARE_DEBUG + bool "Enable Xilinx Zynq MPSoC firmware debug APIs" + default ARCH_ZYNQMP && ZYNQMP_FIRMWARE && DEBUG_FS + help + Say yes to enable zynqmp firmware interface debug APIs. + In doubt, say N + endmenu diff --git a/drivers/firmware/xilinx/zynqmp/Makefile b/drivers/firmware/xilinx/zynqmp/Makefile index 6629781..02f0c9a 100644 --- a/drivers/firmware/xilinx/zynqmp/Makefile +++ b/drivers/firmware/xilinx/zynqmp/Makefile @@ -2,3 +2,4 @@ # Makefile for Xilinx firmwares obj-$(CONFIG_ZYNQMP_FIRMWARE) += firmware.o firmware-ggs.o +obj-$(CONFIG_ZYNQMP_FIRMWARE_DEBUG) += firmware-debug.o diff --git a/drivers/firmware/xilinx/zynqmp/firmware-debug.c b/drivers/firmware/xilinx/zynqmp/firmware-debug.c new file mode 100644 index 0000000..daefc62 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp/firmware-debug.c @@ -0,0 +1,511 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Xilinx Zynq MPSoC Firmware layer for debugfs APIs + * + * Copyright (C) 2014-2018 Xilinx, Inc. + * + * Michal Simek <michal.simek@xxxxxxxxxx> + * Davorin Mista <davorin.mista@xxxxxxxxxx> + * Jolly Shah <jollys@xxxxxxxxxx> + * Rajan Vaja <rajanv@xxxxxxxxxx> + */ + +#include <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> +#include <linux/firmware/xilinx/zynqmp/firmware.h> +#include <linux/firmware/xilinx/zynqmp/firmware-debug.h> + +#define DRIVER_NAME "zynqmp-firmware" + +#define PM_API_NAME_LEN 50 +#define MAX_API_VERSION_RESP_LEN 50 + +struct pm_api_info { + u32 api_id; + char api_name[PM_API_NAME_LEN]; + char api_name_len; +}; + +#define PM_API(id) {id, #id, strlen(#id)} +static struct pm_api_info pm_api_list[] = { + PM_API(PM_REQUEST_SUSPEND), + PM_API(PM_SELF_SUSPEND), + PM_API(PM_FORCE_POWERDOWN), + PM_API(PM_ABORT_SUSPEND), + PM_API(PM_REQUEST_WAKEUP), + PM_API(PM_SET_WAKEUP_SOURCE), + PM_API(PM_SYSTEM_SHUTDOWN), + PM_API(PM_REQUEST_NODE), + PM_API(PM_RELEASE_NODE), + PM_API(PM_SET_REQUIREMENT), + PM_API(PM_SET_MAX_LATENCY), + PM_API(PM_GET_API_VERSION), + PM_API(PM_SET_CONFIGURATION), + PM_API(PM_GET_NODE_STATUS), + PM_API(PM_GET_OPERATING_CHARACTERISTIC), + PM_API(PM_REGISTER_NOTIFIER), + PM_API(PM_RESET_ASSERT), + PM_API(PM_RESET_GET_STATUS), + PM_API(PM_MMIO_READ), + PM_API(PM_MMIO_WRITE), + PM_API(PM_GET_CHIPID), + PM_API(PM_PINCTRL_GET_FUNCTION), + PM_API(PM_PINCTRL_SET_FUNCTION), + PM_API(PM_PINCTRL_CONFIG_PARAM_GET), + PM_API(PM_PINCTRL_CONFIG_PARAM_SET), + PM_API(PM_IOCTL), + PM_API(PM_CLOCK_ENABLE), + PM_API(PM_CLOCK_DISABLE), + PM_API(PM_CLOCK_GETSTATE), + PM_API(PM_CLOCK_SETDIVIDER), + PM_API(PM_CLOCK_GETDIVIDER), + PM_API(PM_CLOCK_SETRATE), + PM_API(PM_CLOCK_GETRATE), + PM_API(PM_CLOCK_SETPARENT), + PM_API(PM_CLOCK_GETPARENT), + PM_API(PM_QUERY_DATA), +}; + +/** + * zynqmp_pm_self_suspend - PM call for master to suspend itself + * @node: Node ID of the master or subsystem + * @latency: Requested maximum wakeup latency (not supported) + * @state: Requested state (not supported) + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_self_suspend(const u32 node, + const u32 latency, + const u32 state) +{ + return invoke_pm_fn(PM_SELF_SUSPEND, node, latency, state, 0, NULL); +} + +/** + * zynqmp_pm_abort_suspend - PM call to announce that a prior suspend request + * is to be aborted. + * @reason: Reason for the abort + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_abort_suspend(const enum zynqmp_pm_abort_reason reason) +{ + return invoke_pm_fn(PM_ABORT_SUSPEND, reason, 0, 0, 0, NULL); +} + +/** + * zynqmp_pm_register_notifier - Register the PU to be notified of PM events + * @node: Node ID of the slave + * @event: The event to be notified about + * @wake: Wake up on event + * @enable: Enable or disable the notifier + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_register_notifier(const u32 node, const u32 event, + const u32 wake, const u32 enable) +{ + return invoke_pm_fn(PM_REGISTER_NOTIFIER, node, event, + wake, enable, NULL); +} + +/** + * zynqmp_pm_argument_value - Extract argument value from a PM-API request + * @arg: Entered PM-API argument in string format + * + * Return: Argument value in unsigned integer format on success + * 0 otherwise + */ +static u64 zynqmp_pm_argument_value(char *arg) +{ + u64 value; + + if (!arg) + return 0; + + if (!kstrtou64(arg, 0, &value)) + return value; + + return 0; +} + +static int get_pm_api_id(char *pm_api_req, u32 *pm_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(pm_api_list) ; i++) { + if (!strncasecmp(pm_api_req, pm_api_list[i].api_name, + pm_api_list[i].api_name_len)) { + *pm_id = pm_api_list[i].api_id; + break; + } + } + + /* If no name was entered look for PM-API ID instead */ + if (i == ARRAY_SIZE(pm_api_list) && kstrtouint(pm_api_req, 10, pm_id)) + return -EINVAL; + + return 0; +} + +static int process_api_request(u32 pm_id, u64 *pm_api_arg, u32 *pm_api_ret) +{ + const struct zynqmp_eemi_ops *eemi_ops = get_eemi_ops(); + u32 pm_api_version; + int ret; + + if (!eemi_ops) + return -ENXIO; + + switch (pm_id) { + case PM_GET_API_VERSION: + eemi_ops->get_api_version(&pm_api_version); + pr_info("%s PM-API Version = %d.%d\n", __func__, + pm_api_version >> 16, pm_api_version & 0xffff); + break; + case PM_REQUEST_SUSPEND: + ret = eemi_ops->request_suspend(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_REQUEST_ACK_NO, + pm_api_arg[2] ? pm_api_arg[2] : + ZYNQMP_PM_MAX_LATENCY, 0); + break; + case PM_SELF_SUSPEND: + ret = zynqmp_pm_self_suspend(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_MAX_LATENCY, 0); + break; + case PM_FORCE_POWERDOWN: + ret = eemi_ops->force_powerdown(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_REQUEST_ACK_NO); + break; + case PM_ABORT_SUSPEND: + ret = zynqmp_pm_abort_suspend(pm_api_arg[0] ? pm_api_arg[0] : + ZYNQMP_PM_ABORT_REASON_UNKNOWN); + break; + case PM_REQUEST_WAKEUP: + ret = eemi_ops->request_wakeup(pm_api_arg[0], + pm_api_arg[1], pm_api_arg[2], + pm_api_arg[3] ? pm_api_arg[3] : + ZYNQMP_PM_REQUEST_ACK_NO); + break; + case PM_SET_WAKEUP_SOURCE: + ret = eemi_ops->set_wakeup_source(pm_api_arg[0], pm_api_arg[1], + pm_api_arg[2]); + break; + case PM_SYSTEM_SHUTDOWN: + ret = eemi_ops->system_shutdown(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_REQUEST_NODE: + ret = eemi_ops->request_node(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_CAPABILITY_ACCESS, + pm_api_arg[2] ? pm_api_arg[2] : 0, + pm_api_arg[3] ? pm_api_arg[3] : + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + break; + case PM_RELEASE_NODE: + ret = eemi_ops->release_node(pm_api_arg[0]); + break; + case PM_SET_REQUIREMENT: + ret = eemi_ops->set_requirement(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_CAPABILITY_CONTEXT, + pm_api_arg[2] ? + pm_api_arg[2] : 0, + pm_api_arg[3] ? pm_api_arg[3] : + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + break; + case PM_SET_MAX_LATENCY: + ret = eemi_ops->set_max_latency(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_MAX_LATENCY); + break; + case PM_SET_CONFIGURATION: + ret = eemi_ops->set_configuration(pm_api_arg[0]); + break; + case PM_GET_NODE_STATUS: + ret = eemi_ops->get_node_status(pm_api_arg[0], + &pm_api_ret[0], + &pm_api_ret[1], + &pm_api_ret[2]); + if (!ret) + pr_info("GET_NODE_STATUS:\n\tNodeId: %llu\n\tStatus: %u\n\tRequirements: %u\n\tUsage: %u\n", + pm_api_arg[0], pm_api_ret[0], + pm_api_ret[1], pm_api_ret[2]); + break; + case PM_GET_OPERATING_CHARACTERISTIC: + ret = eemi_ops->get_operating_characteristic(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_OPERATING_CHARACTERISTIC_POWER, + &pm_api_ret[0]); + if (!ret) + pr_info("GET_OPERATING_CHARACTERISTIC:\n\tNodeId: %llu\n\tType: %llu\n\tResult: %u\n", + pm_api_arg[0], pm_api_arg[1], pm_api_ret[0]); + break; + case PM_REGISTER_NOTIFIER: + ret = zynqmp_pm_register_notifier(pm_api_arg[0], + pm_api_arg[1] ? + pm_api_arg[1] : 0, + pm_api_arg[2] ? + pm_api_arg[2] : 0, + pm_api_arg[3] ? + pm_api_arg[3] : 0); + break; + case PM_RESET_ASSERT: + ret = eemi_ops->reset_assert(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_RESET_GET_STATUS: + ret = eemi_ops->reset_get_status(pm_api_arg[0], &pm_api_ret[0]); + pr_info("%s Reset status: %u\n", __func__, pm_api_ret[0]); + break; + case PM_GET_CHIPID: + ret = eemi_ops->get_chipid(&pm_api_ret[0], &pm_api_ret[1]); + pr_info("%s idcode: %#x, version:%#x\n", + __func__, pm_api_ret[0], pm_api_ret[1]); + break; + case PM_PINCTRL_GET_FUNCTION: + ret = eemi_ops->pinctrl_get_function(pm_api_arg[0], + &pm_api_ret[0]); + pr_info("%s Current set function for the pin: %u\n", + __func__, pm_api_ret[0]); + break; + case PM_PINCTRL_SET_FUNCTION: + ret = eemi_ops->pinctrl_set_function(pm_api_arg[0], + pm_api_arg[1]); + break; + case PM_PINCTRL_CONFIG_PARAM_GET: + ret = eemi_ops->pinctrl_get_config(pm_api_arg[0], pm_api_arg[1], + &pm_api_ret[0]); + pr_info("%s pin: %llu, param: %llu, value: %u\n", + __func__, pm_api_arg[0], pm_api_arg[1], + pm_api_ret[0]); + break; + case PM_PINCTRL_CONFIG_PARAM_SET: + ret = eemi_ops->pinctrl_set_config(pm_api_arg[0], + pm_api_arg[1], + pm_api_arg[2]); + break; + case PM_IOCTL: + ret = eemi_ops->ioctl(pm_api_arg[0], pm_api_arg[1], + pm_api_arg[2], pm_api_arg[3], + &pm_api_ret[0]); + if (pm_api_arg[1] == IOCTL_GET_RPU_OPER_MODE || + pm_api_arg[1] == IOCTL_GET_PLL_FRAC_MODE || + pm_api_arg[1] == IOCTL_GET_PLL_FRAC_DATA || + pm_api_arg[1] == IOCTL_READ_GGS || + pm_api_arg[1] == IOCTL_READ_PGGS) + pr_info("%s Value: %u\n", + __func__, pm_api_ret[1]); + break; + case PM_CLOCK_ENABLE: + ret = eemi_ops->clock_enable(pm_api_arg[0]); + break; + case PM_CLOCK_DISABLE: + ret = eemi_ops->clock_disable(pm_api_arg[0]); + break; + case PM_CLOCK_GETSTATE: + ret = eemi_ops->clock_getstate(pm_api_arg[0], &pm_api_ret[0]); + pr_info("%s state: %u\n", __func__, pm_api_ret[0]); + break; + case PM_CLOCK_SETDIVIDER: + ret = eemi_ops->clock_setdivider(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_CLOCK_GETDIVIDER: + ret = eemi_ops->clock_getdivider(pm_api_arg[0], &pm_api_ret[0]); + pr_info("%s Divider Value: %d\n", __func__, pm_api_ret[0]); + break; + case PM_CLOCK_SETRATE: + ret = eemi_ops->clock_setrate(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_CLOCK_GETRATE: + ret = eemi_ops->clock_getrate(pm_api_arg[0], &pm_api_ret[0]); + pr_info("%s Rate Value: %u\n", __func__, pm_api_ret[0]); + break; + case PM_CLOCK_SETPARENT: + ret = eemi_ops->clock_setparent(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_CLOCK_GETPARENT: + ret = eemi_ops->clock_getparent(pm_api_arg[0], &pm_api_ret[0]); + pr_info("%s Parent Index: %u\n", __func__, pm_api_ret[0]); + break; + case PM_QUERY_DATA: + { + struct zynqmp_pm_query_data qdata = {0}; + + qdata.qid = pm_api_arg[0]; + qdata.arg1 = pm_api_arg[1]; + qdata.arg2 = pm_api_arg[2]; + qdata.arg3 = pm_api_arg[3]; + + ret = eemi_ops->query_data(qdata, pm_api_ret); + + pr_info("%s: data[0] = 0x%08x\n", __func__, pm_api_ret[0]); + pr_info("%s: data[1] = 0x%08x\n", __func__, pm_api_ret[1]); + pr_info("%s: data[2] = 0x%08x\n", __func__, pm_api_ret[2]); + pr_info("%s: data[3] = 0x%08x\n", __func__, pm_api_ret[3]); + break; + } + default: + pr_err("%s Unsupported PM-API request\n", __func__); + ret = -EINVAL; + } + + return ret; +} + +/** + * zynqmp_pm_debugfs_api_write - debugfs write function + * @file: User file structure + * @ptr: User entered PM-API string + * @len: Length of the userspace buffer + * @off: Offset within the file + * + * Return: Number of bytes copied if PM-API request succeeds, + * the corresponding error code otherwise + * + * Used for triggering pm api functions by writing + * echo <pm_api_id> > /sys/kernel/debug/zynqmp_pm/power or + * echo <pm_api_name> > /sys/kernel/debug/zynqmp_pm/power + */ +static ssize_t zynqmp_pm_debugfs_api_write(struct file *file, + const char __user *ptr, size_t len, + loff_t *off) +{ + char *kern_buff, *tmp_buff; + char *pm_api_req; + u32 pm_id = 0; + u64 pm_api_arg[4] = {0, 0, 0, 0}; + /* Return values from PM APIs calls */ + u32 pm_api_ret[4] = {0, 0, 0, 0}; + + int ret; + int i = 0; + + if (*off != 0 || len == 0) + return -EINVAL; + + kern_buff = memdup_user(ptr, len); + if (IS_ERR(kern_buff)) + return PTR_ERR(kern_buff); + + tmp_buff = kern_buff; + + /* Read the API name from a user request */ + pm_api_req = strsep(&kern_buff, " "); + + ret = get_pm_api_id(pm_api_req, &pm_id); + if (ret < 0) + goto err; + + /* Read node_id and arguments from the PM-API request */ + pm_api_req = strsep(&kern_buff, " "); + while ((i < ARRAY_SIZE(pm_api_arg)) && pm_api_req) { + pm_api_arg[i++] = zynqmp_pm_argument_value(pm_api_req); + pm_api_req = strsep(&kern_buff, " "); + } + + ret = process_api_request(pm_id, pm_api_arg, pm_api_ret); + +err: + kfree(tmp_buff); + if (ret) + return ret; + else + return len; +} + +/** + * zynqmp_pm_debugfs_api_version_read - debugfs read function + * @file: User file structure + * @ptr: Requested pm_api_version string + * @len: Length of the userspace buffer + * @off: Offset within the file + * + * Return: Length of the version string on success + * -EFAULT otherwise + * + * Used to display the pm api version. + * cat /sys/kernel/debug/zynqmp_pm/pm_api_version + */ +static ssize_t zynqmp_pm_debugfs_api_version_read(struct file *file, + char __user *ptr, size_t len, + loff_t *off) +{ + int ret; + u32 pm_api_version; + const struct zynqmp_eemi_ops *eemi_ops = get_eemi_ops(); + char resp[MAX_API_VERSION_RESP_LEN]; + u32 resp_len; + + if (!eemi_ops || !eemi_ops->get_api_version) + return -ENXIO; + + if (*off != 0) + return 0; + + eemi_ops->get_api_version(&pm_api_version); + resp_len = snprintf(resp, MAX_API_VERSION_RESP_LEN, + "PM-API Version = %d.%d\n", + pm_api_version >> 16, + pm_api_version & 0xffff); + + if (len < resp_len) + return -EINVAL; + + ret = copy_to_user(ptr, resp, resp_len); + if (ret) + return -EFAULT; + + *off = resp_len + 1; + + return resp_len; +} + +/* Setup debugfs fops */ +static const struct file_operations fops_zynqmp_pm_dbgfs = { + .owner = THIS_MODULE, + .write = zynqmp_pm_debugfs_api_write, + .read = zynqmp_pm_debugfs_api_version_read, +}; + +/** + * zynqmp_pm_api_debugfs_init - Initialize debugfs interface + * + * Return: None + */ +void zynqmp_pm_api_debugfs_init(void) +{ + struct dentry *root_dir; + struct dentry *d; + + /* Initialize debugfs interface */ + root_dir = debugfs_create_dir(DRIVER_NAME, NULL); + if (!root_dir) { + pr_warn("debugfs_create_dir failed\n"); + return; + } + + d = debugfs_create_file("pm", 0220, root_dir, NULL, + &fops_zynqmp_pm_dbgfs); + if (!d) { + pr_warn("debugfs_create_file power failed\n"); + goto err_dbgfs; + } + + d = debugfs_create_file("api_version", 0444, root_dir, + NULL, &fops_zynqmp_pm_dbgfs); + if (!d) { + pr_warn("debugfs_create_file api_version failed\n"); + goto err_dbgfs; + } + + return; + +err_dbgfs: + debugfs_remove_recursive(root_dir); +} diff --git a/drivers/firmware/xilinx/zynqmp/firmware.c b/drivers/firmware/xilinx/zynqmp/firmware.c index fe7b044..1731b86 100644 --- a/drivers/firmware/xilinx/zynqmp/firmware.c +++ b/drivers/firmware/xilinx/zynqmp/firmware.c @@ -20,6 +20,7 @@ #include <linux/uaccess.h> #include <linux/firmware/xilinx/zynqmp/firmware.h> +#include <linux/firmware/xilinx/zynqmp/firmware-debug.h> #define DRIVER_NAME "zynqmp_firmware" @@ -1011,9 +1012,13 @@ static int zynqmp_firmware_probe(struct platform_device *pdev) int ret; ret = zynqmp_pm_ggs_init(&pdev->dev); - if (ret) + if (ret) { dev_err(&pdev->dev, "%s() GGS init fail with error %d\n", __func__, ret); + return ret; + } + + zynqmp_pm_api_debugfs_init(); return ret; } diff --git a/include/linux/firmware/xilinx/zynqmp/firmware-debug.h b/include/linux/firmware/xilinx/zynqmp/firmware-debug.h new file mode 100644 index 0000000..a298dce --- /dev/null +++ b/include/linux/firmware/xilinx/zynqmp/firmware-debug.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Xilinx Zynq MPSoC Firmware layer + * + * Copyright (C) 2014-2018 Xilinx + * + * Michal Simek <michal.simek@xxxxxxxxxx> + * Davorin Mista <davorin.mista@xxxxxxxxxx> + * Jolly Shah <jollys@xxxxxxxxxx> + * Rajan Vaja <rajanv@xxxxxxxxxx> + */ + +#ifndef __SOC_ZYNQMP_FIRMWARE_DEBUG_H__ +#define __SOC_ZYNQMP_FIRMWARE_DEBUG_H__ + +#include <linux/firmware/xilinx/zynqmp/firmware.h> + +int zynqmp_pm_self_suspend(const u32 node, + const u32 latency, + const u32 state); +int zynqmp_pm_abort_suspend(const enum zynqmp_pm_abort_reason reason); +int zynqmp_pm_register_notifier(const u32 node, const u32 event, + const u32 wake, const u32 enable); + +#if IS_REACHABLE(CONFIG_ZYNQMP_FIRMWARE_DEBUG) +void zynqmp_pm_api_debugfs_init(void); +#else +static inline void zynqmp_pm_api_debugfs_init(void) { return; } +#endif + +#endif /* __SOC_ZYNQMP_FIRMWARE_DEBUG_H__ */ -- 2.7.4 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html