diff options
| author | Adrian Betschart <adrian.betschart@cinemaone.ch> | 2026-09-11 17:13:14 +0200 |
|---|---|---|
| committer | Alex Deucher <alexander.deucher@amd.com> | 2026-10-01 17:18:05 -0400 |
| commit | 106b4a2a78b2a2f14885604891957b84bba502db (patch) | |
| tree | 5d40eef2428fb1a4791ab46a4b19091a5910eac2 /drivers/firmware/xilinx | |
| download | linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.tar.gz linux-stable-106b4a2a78b2a2f14885604891957b84bba502db.zip | |
drm/amd/display: Try RGB before YCbCr 4:4:4 in stream validationgrafted
amdgpu_dm_create_validate_stream_for_sink() walks encoding_order[] and
uses the first encoding that validates. YCbCr 4:4:4 is listed before
RGB, so an HDMI sink that advertises 4:4:4 gets YCbCr 4:4:4 whenever the
"color format" property is left at AUTO, even though RGB fits the same
link.
That contradicts the documented AUTO behaviour for HDMI in enum
drm_connector_color_format (RGB, falling back to YCbCr 4:2:0 only when
the bandwidth is not available or the mode is 4:2:0-only), which the
amdgpu implementation of the property also describes. It also leaves
the "Broadcast RGB" property without effect on such sinks, since the
quantization range it selects only applies to RGB output.
Try RGB first. The mask still holds every encoding the sink supports,
so a mode that cannot carry RGB falls back exactly as before.
For reference, v7.2 picked RGB here unless YCbCr 4:4:4 was forced
through debugfs, while earlier kernels picked YCbCr 4:4:4 for any HDMI
sink that advertised it.
Fixes: 0b0ff65d3ca1 ("drm/amd/display: Refactor stream validation")
Suggested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Assisted-by: Claude Code:claude-fable-5-1
Signed-off-by: Adrian Betschart <adrian.betschart@cinemaone.ch>
Reviewed-by: Fangzhi Zuo <jerry.zuo@amd.com>
Tested-by: Adolfo Rodrigues <adolfotregosa@gmail.com>
Tested-by: Daniel Wheeler <daniel.wheeler@amd.com>
Signed-off-by: Alex Deucher <alexander.deucher@amd.com>
(cherry picked from commit 7de432bc753133764dc40d37f9388da56de251e7)
Diffstat (limited to 'drivers/firmware/xilinx')
| -rw-r--r-- | drivers/firmware/xilinx/Kconfig | 26 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/Makefile | 5 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-crypto.c | 238 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-debug.c | 412 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-debug.h | 24 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp-ufs.c | 118 | ||||
| -rw-r--r-- | drivers/firmware/xilinx/zynqmp.c | 2385 |
7 files changed, 3208 insertions, 0 deletions
diff --git a/drivers/firmware/xilinx/Kconfig b/drivers/firmware/xilinx/Kconfig new file mode 100644 index 000000000..9a9bd1908 --- /dev/null +++ b/drivers/firmware/xilinx/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0 +# Kconfig for Xilinx firmwares + +menu "Zynq MPSoC Firmware Drivers" + depends on ARCH_ZYNQMP + +config ZYNQMP_FIRMWARE + bool "Enable Xilinx Zynq MPSoC firmware interface" + depends on ARCH_ZYNQMP + default y if ARCH_ZYNQMP + select MFD_CORE + help + Firmware interface driver is used by different + drivers to communicate with the firmware for + various platform management services. + Say yes to enable ZynqMP firmware interface driver. + If in doubt, say N. + +config ZYNQMP_FIRMWARE_DEBUG + bool "Enable Xilinx Zynq MPSoC firmware debug APIs" + depends on ZYNQMP_FIRMWARE && DEBUG_FS + help + Say yes to enable ZynqMP firmware interface debug APIs. + If in doubt, say N. + +endmenu diff --git a/drivers/firmware/xilinx/Makefile b/drivers/firmware/xilinx/Makefile new file mode 100644 index 000000000..8db0e66b6 --- /dev/null +++ b/drivers/firmware/xilinx/Makefile @@ -0,0 +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_DEBUG) += zynqmp-debug.o diff --git a/drivers/firmware/xilinx/zynqmp-crypto.c b/drivers/firmware/xilinx/zynqmp-crypto.c new file mode 100644 index 000000000..f06f1e2f6 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-crypto.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Firmware layer for XilSecure APIs. + * + * Copyright (C) 2014-2022 Xilinx, Inc. + * Copyright (C) 2022-2025 Advanced Micro Devices, Inc. + */ + +#include <linux/firmware/xlnx-zynqmp.h> +#include <linux/module.h> + +/** + * zynqmp_pm_aes_engine - Access AES hardware to encrypt/decrypt the data using + * AES-GCM core. + * @address: Address of the AesParams structure. + * @out: Returned output value + * + * Return: Returns status, either success or error code. + */ +int zynqmp_pm_aes_engine(const u64 address, u32 *out) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!out) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_SECURE_AES, ret_payload, 2, upper_32_bits(address), + lower_32_bits(address)); + *out = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_aes_engine); + +/** + * zynqmp_pm_sha_hash - Access the SHA engine to calculate the hash + * @address: Address of the data/ Address of output buffer where + * hash should be stored. + * @size: Size of the data. + * @flags: + * BIT(0) - for initializing csudma driver and SHA3(Here address + * and size inputs can be NULL). + * BIT(1) - to call Sha3_Update API which can be called multiple + * times when data is not contiguous. + * BIT(2) - to get final hash of the whole updated data. + * Hash will be overwritten at provided address with + * 48 bytes. + * + * Return: Returns status, either success or error code. + */ +int zynqmp_pm_sha_hash(const u64 address, const u32 size, const u32 flags) +{ + u32 lower_addr = lower_32_bits(address); + u32 upper_addr = upper_32_bits(address); + + return zynqmp_pm_invoke_fn(PM_SECURE_SHA, NULL, 4, upper_addr, lower_addr, size, flags); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_sha_hash); + +/** + * xlnx_get_crypto_dev_data() - Get crypto dev data of platform + * @feature_map: List of available feature map of all platform + * + * Return: Returns crypto dev data, either address crypto dev or ERR PTR + */ +void *xlnx_get_crypto_dev_data(struct xlnx_feature *feature_map) +{ + struct xlnx_feature *feature; + u32 pm_family_code; + int ret; + + /* Get the Family code and sub family code of platform */ + ret = zynqmp_pm_get_family_info(&pm_family_code); + if (ret < 0) + return ERR_PTR(ret); + + feature = feature_map; + for (; feature->family; feature++) { + if (feature->family == pm_family_code) { + ret = zynqmp_pm_feature(feature->feature_id); + if (ret < 0) + return ERR_PTR(ret); + + return feature->data; + } + } + return ERR_PTR(-ENODEV); +} +EXPORT_SYMBOL_GPL(xlnx_get_crypto_dev_data); + +/** + * versal_pm_aes_key_write - Write AES key registers + * @keylen: Size of the input key to be written + * @keysrc: Key Source to be selected to which provided + * key should be updated + * @keyaddr: Address of a buffer which should contain the key + * to be written + * + * This function provides support to write AES volatile user keys. + * + * Return: Returns status, either success or error+reason + */ +int versal_pm_aes_key_write(const u32 keylen, + const u32 keysrc, const u64 keyaddr) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_WRITE_KEY, NULL, 4, + keylen, keysrc, + lower_32_bits(keyaddr), + upper_32_bits(keyaddr)); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_key_write); + +/** + * versal_pm_aes_key_zero - Zeroise AES User key registers + * @keysrc: Key Source to be selected to which provided + * key should be updated + * + * This function provides support to zeroise AES volatile user keys. + * + * Return: Returns status, either success or error+reason + */ +int versal_pm_aes_key_zero(const u32 keysrc) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_KEY_ZERO, NULL, 1, keysrc); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_key_zero); + +/** + * versal_pm_aes_op_init - Init AES operation + * @hw_req: AES op init structure address + * + * This function provides support to init AES operation. + * + * Return: Returns status, either success or error+reason + */ +int versal_pm_aes_op_init(const u64 hw_req) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_OP_INIT, NULL, 2, + lower_32_bits(hw_req), + upper_32_bits(hw_req)); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_op_init); + +/** + * versal_pm_aes_update_aad - AES update aad + * @aad_addr: AES aad address + * @aad_len: AES aad data length + * + * This function provides support to update AAD data. + * + * Return: Returns status, either success or error+reason + */ +int versal_pm_aes_update_aad(const u64 aad_addr, const u32 aad_len) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_UPDATE_AAD, NULL, 3, + lower_32_bits(aad_addr), + upper_32_bits(aad_addr), + aad_len); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_update_aad); + +/** + * versal_pm_aes_enc_update - Access AES hardware to encrypt the data using + * AES-GCM core. + * @in_params: Address of the AesParams structure + * @in_addr: Address of input buffer + * + * Return: Returns status, either success or error code. + */ +int versal_pm_aes_enc_update(const u64 in_params, const u64 in_addr) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_ENCRYPT_UPDATE, NULL, 4, + lower_32_bits(in_params), + upper_32_bits(in_params), + lower_32_bits(in_addr), + upper_32_bits(in_addr)); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_enc_update); + +/** + * versal_pm_aes_enc_final - Access AES hardware to store the GCM tag + * @gcm_addr: Address of the gcm tag + * + * Return: Returns status, either success or error code. + */ +int versal_pm_aes_enc_final(const u64 gcm_addr) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_ENCRYPT_FINAL, NULL, 2, + lower_32_bits(gcm_addr), + upper_32_bits(gcm_addr)); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_enc_final); + +/** + * versal_pm_aes_dec_update - Access AES hardware to decrypt the data using + * AES-GCM core. + * @in_params: Address of the AesParams structure + * @in_addr: Address of input buffer + * + * Return: Returns status, either success or error code. + */ +int versal_pm_aes_dec_update(const u64 in_params, const u64 in_addr) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_DECRYPT_UPDATE, NULL, 4, + lower_32_bits(in_params), + upper_32_bits(in_params), + lower_32_bits(in_addr), + upper_32_bits(in_addr)); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_dec_update); + +/** + * versal_pm_aes_dec_final - Access AES hardware to get the GCM tag + * @gcm_addr: Address of the gcm tag + * + * Return: Returns status, either success or error code. + */ +int versal_pm_aes_dec_final(const u64 gcm_addr) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_DECRYPT_FINAL, NULL, 2, + lower_32_bits(gcm_addr), + upper_32_bits(gcm_addr)); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_dec_final); + +/** + * versal_pm_aes_init - Init AES block + * + * This function initialise AES block. + * + * Return: Returns status, either success or error+reason + */ +int versal_pm_aes_init(void) +{ + return zynqmp_pm_invoke_fn(XSECURE_API_AES_INIT, NULL, 0); +} +EXPORT_SYMBOL_GPL(versal_pm_aes_init); diff --git a/drivers/firmware/xilinx/zynqmp-debug.c b/drivers/firmware/xilinx/zynqmp-debug.c new file mode 100644 index 000000000..36efb827f --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-debug.c @@ -0,0 +1,412 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Zynq MPSoC Firmware layer for debugfs APIs + * + * Copyright (C) 2014-2018 Xilinx, Inc. + * Copyright (C) 2022 - 2025 Advanced Micro Devices, Inc. + * + * Michal Simek <michal.simek@amd.com> + * Davorin Mista <davorin.mista@aggios.com> + * Jolly Shah <jollys@xilinx.com> + * Rajan Vaja <rajanv@xilinx.com> + */ + +#include <linux/compiler.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/debugfs.h> +#include <linux/uaccess.h> + +#include <linux/firmware/xlnx-zynqmp.h> +#include "zynqmp-debug.h" + +#define PM_API_NAME_LEN 50 + +struct pm_api_info { + u32 api_id; + char api_name[PM_API_NAME_LEN]; + char api_name_len; +}; + +static char debugfs_buf[PAGE_SIZE]; + +#define PM_API(id) {id, #id, strlen(#id)} +static struct pm_api_info pm_api_list[] = { + PM_API(PM_FORCE_POWERDOWN), + PM_API(PM_REQUEST_WAKEUP), + PM_API(PM_SYSTEM_SHUTDOWN), + PM_API(PM_REQUEST_NODE), + PM_API(PM_RELEASE_NODE), + PM_API(PM_SET_REQUIREMENT), + PM_API(PM_GET_API_VERSION), + PM_API(PM_GET_NODE_STATUS), + PM_API(PM_REGISTER_NOTIFIER), + PM_API(PM_RESET_ASSERT), + PM_API(PM_RESET_GET_STATUS), + PM_API(PM_GET_CHIPID), + 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_SETPARENT), + PM_API(PM_CLOCK_GETPARENT), + PM_API(PM_QUERY_DATA), +}; + +static struct dentry *firmware_debugfs_root; + +/** + * zynqmp_pm_ioctl - PM IOCTL for device control and configs + * @node: Node ID of the device + * @ioctl: ID of the requested IOCTL + * @arg1: Argument 1 of requested IOCTL call + * @arg2: Argument 2 of requested IOCTL call + * @arg3: Argument 3 of requested IOCTL call + * @out: Returned output value + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_ioctl(const u32 node, const u32 ioctl, const u32 arg1, + const u32 arg2, const u32 arg3, u32 *out) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, out, 5, node, ioctl, arg1, arg2, arg3); +} + +/** + * 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; +} + +/** + * get_pm_api_id() - Extract API-ID from a PM-API request + * @pm_api_req: Entered PM-API argument in string format + * @pm_id: API-ID + * + * Return: 0 on success else error code + */ +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) +{ + u32 pm_api_version; + int ret; + struct zynqmp_pm_query_data qdata = {0}; + + switch (pm_id) { + case PM_GET_API_VERSION: + ret = zynqmp_pm_get_api_version(&pm_api_version); + sprintf(debugfs_buf, "PM-API Version = %d.%d\n", + pm_api_version >> 16, pm_api_version & 0xffff); + break; + case PM_FORCE_POWERDOWN: + ret = zynqmp_pm_force_pwrdwn(pm_api_arg[0], + pm_api_arg[1] ? pm_api_arg[1] : + ZYNQMP_PM_REQUEST_ACK_NO); + break; + case PM_REQUEST_WAKEUP: + ret = zynqmp_pm_request_wake(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_SYSTEM_SHUTDOWN: + ret = zynqmp_pm_system_shutdown(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_REQUEST_NODE: + ret = zynqmp_pm_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 = zynqmp_pm_release_node(pm_api_arg[0]); + break; + case PM_SET_REQUIREMENT: + ret = zynqmp_pm_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_GET_NODE_STATUS: + ret = zynqmp_pm_get_node_status(pm_api_arg[0], + &pm_api_ret[0], + &pm_api_ret[1], + &pm_api_ret[2]); + if (!ret) + sprintf(debugfs_buf, + "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_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 = zynqmp_pm_reset_assert(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_RESET_GET_STATUS: + ret = zynqmp_pm_reset_get_status(pm_api_arg[0], &pm_api_ret[0]); + if (!ret) + sprintf(debugfs_buf, "Reset status: %u\n", + pm_api_ret[0]); + break; + case PM_GET_CHIPID: + ret = zynqmp_pm_get_chipid(&pm_api_ret[0], &pm_api_ret[1]); + if (!ret) + sprintf(debugfs_buf, "Idcode: %#x, Version:%#x\n", + pm_api_ret[0], pm_api_ret[1]); + break; + case PM_PINCTRL_SET_FUNCTION: + ret = zynqmp_pm_pinctrl_set_function(pm_api_arg[0], + pm_api_arg[1]); + break; + case PM_PINCTRL_CONFIG_PARAM_GET: + ret = zynqmp_pm_pinctrl_get_config(pm_api_arg[0], pm_api_arg[1], + &pm_api_ret[0]); + if (!ret) + sprintf(debugfs_buf, + "Pin: %llu, Param: %llu, Value: %u\n", + pm_api_arg[0], pm_api_arg[1], + pm_api_ret[0]); + break; + case PM_PINCTRL_CONFIG_PARAM_SET: + ret = zynqmp_pm_pinctrl_set_config(pm_api_arg[0], + pm_api_arg[1], + pm_api_arg[2]); + break; + case PM_IOCTL: + ret = zynqmp_pm_ioctl(pm_api_arg[0], pm_api_arg[1], + pm_api_arg[2], pm_api_arg[3], + pm_api_arg[4], &pm_api_ret[0]); + if (!ret && (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 || + pm_api_arg[1] == IOCTL_READ_REG)) + sprintf(debugfs_buf, "IOCTL return value: %u\n", + pm_api_ret[1]); + if (!ret && pm_api_arg[1] == IOCTL_GET_QOS) + sprintf(debugfs_buf, "Default QoS: %u\nCurrent QoS: %u\n", + pm_api_ret[1], pm_api_ret[2]); + break; + case PM_CLOCK_ENABLE: + ret = zynqmp_pm_clock_enable(pm_api_arg[0]); + break; + case PM_CLOCK_DISABLE: + ret = zynqmp_pm_clock_disable(pm_api_arg[0]); + break; + case PM_CLOCK_GETSTATE: + ret = zynqmp_pm_clock_getstate(pm_api_arg[0], &pm_api_ret[0]); + if (!ret) + sprintf(debugfs_buf, "Clock state: %u\n", + pm_api_ret[0]); + break; + case PM_CLOCK_SETDIVIDER: + ret = zynqmp_pm_clock_setdivider(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_CLOCK_GETDIVIDER: + ret = zynqmp_pm_clock_getdivider(pm_api_arg[0], &pm_api_ret[0]); + if (!ret) + sprintf(debugfs_buf, "Divider Value: %d\n", + pm_api_ret[0]); + break; + case PM_CLOCK_SETPARENT: + ret = zynqmp_pm_clock_setparent(pm_api_arg[0], pm_api_arg[1]); + break; + case PM_CLOCK_GETPARENT: + ret = zynqmp_pm_clock_getparent(pm_api_arg[0], &pm_api_ret[0]); + if (!ret) + sprintf(debugfs_buf, + "Clock parent Index: %u\n", pm_api_ret[0]); + break; + case PM_QUERY_DATA: + 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 = zynqmp_pm_query_data(qdata, pm_api_ret); + if (ret) + break; + + switch (qdata.qid) { + case PM_QID_CLOCK_GET_NAME: + sprintf(debugfs_buf, "Clock name = %s\n", + (char *)pm_api_ret); + break; + case PM_QID_CLOCK_GET_FIXEDFACTOR_PARAMS: + sprintf(debugfs_buf, "Multiplier = %d, Divider = %d\n", + pm_api_ret[1], pm_api_ret[2]); + break; + default: + sprintf(debugfs_buf, + "data[0] = 0x%08x\ndata[1] = 0x%08x\n data[2] = 0x%08x\ndata[3] = 0x%08x\n", + pm_api_ret[0], pm_api_ret[1], + pm_api_ret[2], pm_api_ret[3]); + } + break; + default: + sprintf(debugfs_buf, "Unsupported PM-API request\n"); + ret = -EINVAL; + } + + return ret; +} + +/** + * zynqmp_pm_debugfs_api_write() - debugfs write function + * @file: User file + * @ptr: User entered PM-API string + * @len: Length of the userspace buffer + * @off: Offset within the file + * + * 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 + * + * Return: Number of bytes copied if PM-API request succeeds, + * the corresponding error code otherwise + */ +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[5] = {0, 0, 0, 0, 0}; + /* Return values from PM APIs calls */ + u32 pm_api_ret[4] = {0, 0, 0, 0}; + + int ret; + int i = 0; + + strcpy(debugfs_buf, ""); + + if (*off != 0 || len <= 1 || len > PAGE_SIZE - 1) + return -EINVAL; + + kern_buff = memdup_user_nul(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; + + return len; +} + +/** + * zynqmp_pm_debugfs_api_read() - debugfs read function + * @file: User file + * @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 + * else error code + */ +static ssize_t zynqmp_pm_debugfs_api_read(struct file *file, char __user *ptr, + size_t len, loff_t *off) +{ + return simple_read_from_buffer(ptr, len, off, debugfs_buf, + strlen(debugfs_buf)); +} + +/* 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_read, +}; + +/** + * zynqmp_pm_api_debugfs_init - Initialize debugfs interface + * + * Return: None + */ +void zynqmp_pm_api_debugfs_init(void) +{ + /* Initialize debugfs interface */ + firmware_debugfs_root = debugfs_create_dir("zynqmp-firmware", NULL); + debugfs_create_file("pm", 0660, firmware_debugfs_root, NULL, + &fops_zynqmp_pm_dbgfs); +} + +/** + * zynqmp_pm_api_debugfs_exit - Remove debugfs interface + * + * Return: None + */ +void zynqmp_pm_api_debugfs_exit(void) +{ + debugfs_remove_recursive(firmware_debugfs_root); +} diff --git a/drivers/firmware/xilinx/zynqmp-debug.h b/drivers/firmware/xilinx/zynqmp-debug.h new file mode 100644 index 000000000..e1515a93e --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-debug.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Xilinx Zynq MPSoC Firmware layer + * + * Copyright (C) 2014-2018 Xilinx + * + * Michal Simek <michal.simek@amd.com> + * Davorin Mista <davorin.mista@aggios.com> + * Jolly Shah <jollys@xilinx.com> + * Rajan Vaja <rajanv@xilinx.com> + */ + +#ifndef __FIRMWARE_ZYNQMP_DEBUG_H__ +#define __FIRMWARE_ZYNQMP_DEBUG_H__ + +#if IS_REACHABLE(CONFIG_ZYNQMP_FIRMWARE_DEBUG) +void zynqmp_pm_api_debugfs_init(void); +void zynqmp_pm_api_debugfs_exit(void); +#else +static inline void zynqmp_pm_api_debugfs_init(void) { } +static inline void zynqmp_pm_api_debugfs_exit(void) { } +#endif + +#endif /* __FIRMWARE_ZYNQMP_DEBUG_H__ */ diff --git a/drivers/firmware/xilinx/zynqmp-ufs.c b/drivers/firmware/xilinx/zynqmp-ufs.c new file mode 100644 index 000000000..85da8a822 --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp-ufs.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Firmware Layer for UFS APIs + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + */ + +#include <linux/firmware/xlnx-zynqmp.h> +#include <linux/module.h> + +/* Register Node IDs */ +#define PM_REGNODE_PMC_IOU_SLCR 0x30000002 /* PMC IOU SLCR */ +#define PM_REGNODE_EFUSE_CACHE 0x30000003 /* EFUSE Cache */ + +/* Register Offsets for PMC IOU SLCR */ +#define SRAM_CSR_OFFSET 0x104C /* SRAM Control and Status */ +#define TXRX_CFGRDY_OFFSET 0x1054 /* M-PHY TX-RX Config ready */ + +/* Masks for SRAM Control and Status Register */ +#define SRAM_CSR_INIT_DONE_MASK BIT(0) /* SRAM initialization done */ +#define SRAM_CSR_EXT_LD_DONE_MASK BIT(1) /* SRAM External load done */ +#define SRAM_CSR_BYPASS_MASK BIT(2) /* Bypass SRAM interface */ + +/* Mask to check M-PHY TX-RX configuration readiness */ +#define TX_RX_CFG_RDY_MASK GENMASK(3, 0) + +/* Register Offsets for EFUSE Cache */ +#define UFS_CAL_1_OFFSET 0xBE8 /* UFS Calibration Value */ + +/** + * zynqmp_pm_is_mphy_tx_rx_config_ready - check M-PHY TX-RX config readiness + * @is_ready: Store output status (true/false) + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_is_mphy_tx_rx_config_ready(bool *is_ready) +{ + u32 regval; + int ret; + + if (!is_ready) + return -EINVAL; + + ret = zynqmp_pm_sec_read_reg(PM_REGNODE_PMC_IOU_SLCR, TXRX_CFGRDY_OFFSET, ®val); + if (ret) + return ret; + + regval &= TX_RX_CFG_RDY_MASK; + if (regval) + *is_ready = true; + else + *is_ready = false; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_is_mphy_tx_rx_config_ready); + +/** + * zynqmp_pm_is_sram_init_done - check SRAM initialization + * @is_done: Store output status (true/false) + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_is_sram_init_done(bool *is_done) +{ + u32 regval; + int ret; + + if (!is_done) + return -EINVAL; + + ret = zynqmp_pm_sec_read_reg(PM_REGNODE_PMC_IOU_SLCR, SRAM_CSR_OFFSET, ®val); + if (ret) + return ret; + + regval &= SRAM_CSR_INIT_DONE_MASK; + if (regval) + *is_done = true; + else + *is_done = false; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_is_sram_init_done); + +/** + * zynqmp_pm_set_sram_bypass - Set SRAM bypass Control + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_set_sram_bypass(void) +{ + u32 sram_csr; + int ret; + + ret = zynqmp_pm_sec_read_reg(PM_REGNODE_PMC_IOU_SLCR, SRAM_CSR_OFFSET, &sram_csr); + if (ret) + return ret; + + sram_csr &= ~SRAM_CSR_EXT_LD_DONE_MASK; + sram_csr |= SRAM_CSR_BYPASS_MASK; + + return zynqmp_pm_sec_mask_write_reg(PM_REGNODE_PMC_IOU_SLCR, SRAM_CSR_OFFSET, + GENMASK(2, 1), sram_csr); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_sram_bypass); + +/** + * zynqmp_pm_get_ufs_calibration_values - Read UFS calibration values + * @val: Store the calibration value + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_get_ufs_calibration_values(u32 *val) +{ + return zynqmp_pm_sec_read_reg(PM_REGNODE_EFUSE_CACHE, UFS_CAL_1_OFFSET, val); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_ufs_calibration_values); diff --git a/drivers/firmware/xilinx/zynqmp.c b/drivers/firmware/xilinx/zynqmp.c new file mode 100644 index 000000000..fe650747a --- /dev/null +++ b/drivers/firmware/xilinx/zynqmp.c @@ -0,0 +1,2385 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Xilinx Zynq MPSoC Firmware layer + * + * Copyright (C) 2014-2022 Xilinx, Inc. + * Copyright (C) 2022 - 2026 Advanced Micro Devices, Inc. + * + * Michal Simek <michal.simek@amd.com> + * Davorin Mista <davorin.mista@aggios.com> + * Jolly Shah <jollys@xilinx.com> + * Rajan Vaja <rajanv@xilinx.com> + */ + +#include <linux/arm-smccc.h> +#include <linux/compiler.h> +#include <linux/crash_dump.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/mfd/core.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/pm_domain.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/hashtable.h> + +#include <linux/firmware/xlnx-zynqmp.h> +#include <linux/firmware/xlnx-event-manager.h> +#include "zynqmp-debug.h" + +/* Max HashMap Order for PM API feature check (1<<7 = 128) */ +#define PM_API_FEATURE_CHECK_MAX_ORDER 7 + +/* CRL registers and bitfields */ +#define CRL_APB_BASE 0xFF5E0000U +/* BOOT_PIN_CTRL- Used to control the mode pins after boot */ +#define CRL_APB_BOOT_PIN_CTRL (CRL_APB_BASE + (0x250U)) +/* BOOT_PIN_CTRL_MASK- out_val[11:8], out_en[3:0] */ +#define CRL_APB_BOOTPIN_CTRL_MASK 0xF0FU + +/* IOCTL/QUERY feature payload size */ +#define FEATURE_PAYLOAD_SIZE 2 + +static bool feature_check_enabled; +static DEFINE_HASHTABLE(pm_api_features_map, PM_API_FEATURE_CHECK_MAX_ORDER); +static u32 ioctl_features[FEATURE_PAYLOAD_SIZE]; +static u32 query_features[FEATURE_PAYLOAD_SIZE]; + +static u32 sip_svc_version; +static struct platform_device *em_dev; + +/** + * struct zynqmp_devinfo - Structure for Zynqmp device instance + * @dev: Device Pointer + * @feature_conf_id: Feature conf id + */ +struct zynqmp_devinfo { + struct device *dev; + u32 feature_conf_id; +}; + +/** + * struct pm_api_feature_data - PM API Feature data + * @pm_api_id: PM API Id, used as key to index into hashmap + * @feature_status: status of PM API feature: valid, invalid + * @hentry: hlist_node that hooks this entry into hashtable + */ +struct pm_api_feature_data { + u32 pm_api_id; + int feature_status; + struct hlist_node hentry; +}; + +struct platform_fw_data { + /* + * Family code for platform. + */ + const u32 family_code; +}; + +static struct platform_fw_data *active_platform_fw_data; + +static const struct mfd_cell firmware_devs[] = { + { + .name = "zynqmp_power_controller", + }, +}; + +/** + * zynqmp_pm_ret_code() - Convert PMU-FW error codes to Linux error codes + * @ret_status: PMUFW return code + * + * Return: corresponding Linux error code + */ +static int zynqmp_pm_ret_code(u32 ret_status) +{ + switch (ret_status) { + case XST_PM_SUCCESS: + case XST_PM_DOUBLE_REQ: + return 0; + case XST_PM_NO_FEATURE: + return -ENOTSUPP; + case XST_PM_INVALID_VERSION: + return -EOPNOTSUPP; + case XST_PM_NO_ACCESS: + return -EACCES; + case XST_PM_ABORT_SUSPEND: + return -ECANCELED; + case XST_PM_MULT_USER: + return -EUSERS; + case XST_PM_INTERNAL: + case XST_PM_CONFLICT: + case XST_PM_INVALID_NODE: + case XST_PM_INVALID_CRC: + default: + return -EINVAL; + } +} + +static noinline int do_fw_call_fail(u32 *ret_payload, u32 num_args, ...) +{ + return -ENODEV; +} + +/* + * PM function call wrapper + * Invoke do_fw_call_smc or do_fw_call_hvc, depending on the configuration + */ +static int (*do_fw_call)(u32 *ret_payload, u32, ...) = do_fw_call_fail; + +/** + * do_fw_call_smc() - Call system-level platform management layer (SMC) + * @num_args: Number of variable arguments should be <= 8 + * @ret_payload: Returned value array + * + * Invoke platform management function via SMC call (no hypervisor present). + * + * Return: Returns status, either success or error+reason + */ +static noinline int do_fw_call_smc(u32 *ret_payload, u32 num_args, ...) +{ + struct arm_smccc_res res; + u64 args[8] = {0}; + va_list arg_list; + u8 i; + + if (num_args > 8) + return -EINVAL; + + va_start(arg_list, num_args); + + for (i = 0; i < num_args; i++) + args[i] = va_arg(arg_list, u64); + + va_end(arg_list); + + arm_smccc_smc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], &res); + + if (ret_payload) { + ret_payload[0] = lower_32_bits(res.a0); + ret_payload[1] = upper_32_bits(res.a0); + ret_payload[2] = lower_32_bits(res.a1); + ret_payload[3] = upper_32_bits(res.a1); + ret_payload[4] = lower_32_bits(res.a2); + ret_payload[5] = upper_32_bits(res.a2); + ret_payload[6] = lower_32_bits(res.a3); + } + + return zynqmp_pm_ret_code((enum pm_ret_status)res.a0); +} + +/** + * do_fw_call_hvc() - Call system-level platform management layer (HVC) + * @num_args: Number of variable arguments should be <= 8 + * @ret_payload: Returned value array + * + * Invoke platform management function via HVC + * HVC-based for communication through hypervisor + * (no direct communication with ATF). + * + * Return: Returns status, either success or error+reason + */ +static noinline int do_fw_call_hvc(u32 *ret_payload, u32 num_args, ...) +{ + struct arm_smccc_res res; + u64 args[8] = {0}; + va_list arg_list; + u8 i; + + if (num_args > 8) + return -EINVAL; + + va_start(arg_list, num_args); + + for (i = 0; i < num_args; i++) + args[i] = va_arg(arg_list, u64); + + va_end(arg_list); + + arm_smccc_hvc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], &res); + + if (ret_payload) { + ret_payload[0] = lower_32_bits(res.a0); + ret_payload[1] = upper_32_bits(res.a0); + ret_payload[2] = lower_32_bits(res.a1); + ret_payload[3] = upper_32_bits(res.a1); + ret_payload[4] = lower_32_bits(res.a2); + ret_payload[5] = upper_32_bits(res.a2); + ret_payload[6] = lower_32_bits(res.a3); + } + + return zynqmp_pm_ret_code((enum pm_ret_status)res.a0); +} + +static int __do_feature_check_call(const u32 api_id, u32 *ret_payload) +{ + int ret; + u64 smc_arg[2]; + u32 module_id; + u32 feature_check_api_id; + + module_id = FIELD_GET(MODULE_ID_MASK, api_id); + + /* + * Feature check of APIs belonging to PM and XSEM are handled by calling + * PM_FEATURE_CHECK API. For other modules, call PM_API_FEATURES API. + */ + if (module_id == PM_MODULE_ID || module_id == XSEM_MODULE_ID) + feature_check_api_id = PM_FEATURE_CHECK; + else + feature_check_api_id = PM_API_FEATURES; + + if (module_id == TF_A_MODULE_ID) + smc_arg[1] = api_id; + else + smc_arg[1] = (api_id & API_ID_MASK); + + smc_arg[0] = PM_SIP_SVC | FIELD_PREP(MODULE_ID_MASK, module_id) | feature_check_api_id; + + ret = do_fw_call(ret_payload, 2, smc_arg[0], smc_arg[1]); + + /* + * For TF-A APIs, if the feature check with PM_API_FEATURES fails, + * retry with the legacy PM_FEATURE_CHECK for backward compatibility. + */ + if (module_id == TF_A_MODULE_ID && ret) { + smc_arg[0] = PM_SIP_SVC | FIELD_PREP(MODULE_ID_MASK, PM_MODULE_ID) | + PM_FEATURE_CHECK; + ret = do_fw_call(ret_payload, 2, smc_arg[0], smc_arg[1]); + } + + if (ret) + return ret; + + return ret_payload[1]; +} + +static int do_feature_check_call(const u32 api_id) +{ + int ret; + u32 ret_payload[PAYLOAD_ARG_CNT]; + struct pm_api_feature_data *feature_data; + + /* Check for existing entry in hash table for given api */ + hash_for_each_possible(pm_api_features_map, feature_data, hentry, + api_id) { + if (feature_data->pm_api_id == api_id) + return feature_data->feature_status; + } + + /* Add new entry if not present */ + feature_data = kmalloc_obj(*feature_data, GFP_ATOMIC); + if (!feature_data) + return -ENOMEM; + + feature_data->pm_api_id = api_id; + ret = __do_feature_check_call(api_id, ret_payload); + + feature_data->feature_status = ret; + hash_add(pm_api_features_map, &feature_data->hentry, api_id); + + if (api_id == PM_IOCTL) + /* Store supported IOCTL IDs mask */ + memcpy(ioctl_features, &ret_payload[2], FEATURE_PAYLOAD_SIZE * 4); + else if (api_id == PM_QUERY_DATA) + /* Store supported QUERY IDs mask */ + memcpy(query_features, &ret_payload[2], FEATURE_PAYLOAD_SIZE * 4); + + return ret; +} + +/** + * zynqmp_pm_feature() - Check whether given feature is supported or not and + * store supported IOCTL/QUERY ID mask + * @api_id: API ID to check + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_feature(const u32 api_id) +{ + int ret; + + if (!feature_check_enabled) + return 0; + + ret = do_feature_check_call(api_id); + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_feature); + +/** + * zynqmp_pm_is_function_supported() - Check whether given IOCTL/QUERY function + * is supported or not + * @api_id: PM_IOCTL or PM_QUERY_DATA + * @id: IOCTL or QUERY function IDs + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_is_function_supported(const u32 api_id, const u32 id) +{ + int ret; + u32 *bit_mask; + + /* Input arguments validation */ + if (id >= 64 || (api_id != PM_IOCTL && api_id != PM_QUERY_DATA)) + return -EINVAL; + + /* Check feature check API version */ + ret = do_feature_check_call(PM_FEATURE_CHECK); + if (ret < 0) + return ret; + + /* Check if feature check version 2 is supported or not */ + if ((ret & FIRMWARE_VERSION_MASK) == PM_API_VERSION_2) { + /* + * Call feature check for IOCTL/QUERY API to get IOCTL ID or + * QUERY ID feature status. + */ + ret = do_feature_check_call(api_id); + if (ret < 0) + return ret; + + bit_mask = (api_id == PM_IOCTL) ? ioctl_features : query_features; + + if ((bit_mask[(id / 32)] & BIT((id % 32))) == 0U) + return -EOPNOTSUPP; + } else { + return -ENODATA; + } + + return 0; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_is_function_supported); + +/** + * zynqmp_pm_invoke_fw_fn() - Invoke the system-level platform management layer + * caller function depending on the configuration + * @pm_api_id: Requested PM-API call + * @ret_payload: Returned value array + * @num_args: Number of arguments to requested PM-API call + * + * Invoke platform management function for SMC or HVC call, depending on + * configuration. + * Following SMC Calling Convention (SMCCC) for SMC64: + * Pm Function Identifier, + * PM_SIP_SVC + PASS_THROUGH_FW_CMD_ID = + * ((SMC_TYPE_FAST << FUNCID_TYPE_SHIFT) + * ((SMC_64) << FUNCID_CC_SHIFT) + * ((SIP_START) << FUNCID_OEN_SHIFT) + * (PASS_THROUGH_FW_CMD_ID)) + * + * PM_SIP_SVC - Registered ZynqMP SIP Service Call. + * PASS_THROUGH_FW_CMD_ID - Fixed SiP SVC call ID for FW specific calls. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_invoke_fw_fn(u32 pm_api_id, u32 *ret_payload, u32 num_args, ...) +{ + /* + * Added SIP service call Function Identifier + * Make sure to stay in x0 register + */ + u64 smc_arg[SMC_ARG_CNT_64]; + int ret, i; + va_list arg_list; + u32 args[SMC_ARG_CNT_32] = {0}; + u32 module_id; + + if (num_args > SMC_ARG_CNT_32) + return -EINVAL; + + va_start(arg_list, num_args); + + /* Check if feature is supported or not */ + ret = zynqmp_pm_feature(pm_api_id); + if (ret < 0) + return ret; + + for (i = 0; i < num_args; i++) + args[i] = va_arg(arg_list, u32); + + va_end(arg_list); + + module_id = FIELD_GET(PLM_MODULE_ID_MASK, pm_api_id); + + if (module_id == 0) + module_id = XPM_MODULE_ID; + + smc_arg[0] = PM_SIP_SVC | PASS_THROUGH_FW_CMD_ID; + smc_arg[1] = ((u64)args[0] << 32U) | FIELD_PREP(PLM_MODULE_ID_MASK, module_id) | + (pm_api_id & API_ID_MASK); + for (i = 1; i < (SMC_ARG_CNT_64 - 1); i++) + smc_arg[i + 1] = ((u64)args[(i * 2)] << 32U) | args[(i * 2) - 1]; + + return do_fw_call(ret_payload, 8, smc_arg[0], smc_arg[1], smc_arg[2], smc_arg[3], + smc_arg[4], smc_arg[5], smc_arg[6], smc_arg[7]); +} + +/** + * zynqmp_pm_invoke_fn() - Invoke the system-level platform management layer + * caller function depending on the configuration + * @pm_api_id: Requested PM-API call + * @ret_payload: Returned value array + * @num_args: Number of arguments to requested PM-API call + * + * Invoke platform management function for SMC or HVC call, depending on + * configuration. + * Following SMC Calling Convention (SMCCC) for SMC64: + * Pm Function Identifier, + * PM_SIP_SVC + PM_API_ID = + * ((SMC_TYPE_FAST << FUNCID_TYPE_SHIFT) + * ((SMC_64) << FUNCID_CC_SHIFT) + * ((SIP_START) << FUNCID_OEN_SHIFT) + * ((PM_API_ID) & FUNCID_NUM_MASK)) + * + * PM_SIP_SVC - Registered ZynqMP SIP Service Call. + * PM_API_ID - Platform Management API ID. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 *ret_payload, u32 num_args, ...) +{ + /* + * Added SIP service call Function Identifier + * Make sure to stay in x0 register + */ + u64 smc_arg[8]; + int ret, i; + va_list arg_list; + u32 args[14] = {0}; + + if (num_args > 14) + return -EINVAL; + + va_start(arg_list, num_args); + + /* Check if feature is supported or not */ + ret = zynqmp_pm_feature(pm_api_id); + if (ret < 0) + return ret; + + for (i = 0; i < num_args; i++) + args[i] = va_arg(arg_list, u32); + + va_end(arg_list); + + smc_arg[0] = PM_SIP_SVC | pm_api_id; + for (i = 0; i < 7; i++) + smc_arg[i + 1] = ((u64)args[(i * 2) + 1] << 32) | args[i * 2]; + + return do_fw_call(ret_payload, 8, smc_arg[0], smc_arg[1], smc_arg[2], smc_arg[3], + smc_arg[4], smc_arg[5], smc_arg[6], smc_arg[7]); +} + +static u32 pm_api_version; +static u32 pm_tz_version; + +int zynqmp_pm_register_sgi(u32 sgi_num, u32 reset) +{ + int ret; + + ret = zynqmp_pm_invoke_fn(TF_A_PM_REGISTER_SGI, NULL, 2, sgi_num, reset); + if (ret != -EOPNOTSUPP && !ret) + return ret; + + /* try old implementation as fallback strategy if above fails */ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 3, IOCTL_REGISTER_SGI, sgi_num, reset); +} + +/** + * zynqmp_pm_get_api_version() - Get version number of PMU PM firmware + * @version: Returned version value + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_api_version(u32 *version) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!version) + return -EINVAL; + + /* Check is PM API version already verified */ + if (pm_api_version > 0) { + *version = pm_api_version; + return 0; + } + ret = zynqmp_pm_invoke_fn(PM_GET_API_VERSION, ret_payload, 0); + *version = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_api_version); + +/** + * zynqmp_pm_get_chipid - Get silicon ID registers + * @idcode: IDCODE register + * @version: version register + * + * Return: Returns the status of the operation and the idcode and version + * registers in @idcode and @version. + */ +int zynqmp_pm_get_chipid(u32 *idcode, u32 *version) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!idcode || !version) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_GET_CHIPID, ret_payload, 0); + *idcode = ret_payload[1]; + *version = ret_payload[2]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_chipid); + +/** + * zynqmp_pm_get_family_info() - Get family info of platform + * @family: Returned family code value + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_family_info(u32 *family) +{ + if (!active_platform_fw_data) + return -ENODEV; + + if (!family) + return -EINVAL; + + *family = active_platform_fw_data->family_code; + + return 0; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_family_info); + +/** + * zynqmp_pm_get_sip_svc_version() - Get SiP service call version + * @version: Returned version value + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_get_sip_svc_version(u32 *version) +{ + struct arm_smccc_res res; + u64 args[SMC_ARG_CNT_64] = {0}; + + if (!version) + return -EINVAL; + + /* Check if SiP SVC version already verified */ + if (sip_svc_version > 0) { + *version = sip_svc_version; + return 0; + } + + args[0] = GET_SIP_SVC_VERSION; + + arm_smccc_smc(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], &res); + + *version = ((lower_32_bits(res.a0) << 16U) | lower_32_bits(res.a1)); + + return zynqmp_pm_ret_code(XST_PM_SUCCESS); +} + +/** + * zynqmp_pm_get_trustzone_version() - Get secure trustzone firmware version + * @version: Returned version value + * + * Return: Returns status, either success or error+reason + */ +static int zynqmp_pm_get_trustzone_version(u32 *version) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!version) + return -EINVAL; + + /* Check is PM trustzone version already verified */ + if (pm_tz_version > 0) { + *version = pm_tz_version; + return 0; + } + ret = zynqmp_pm_invoke_fn(PM_GET_TRUSTZONE_VERSION, ret_payload, 0); + *version = ret_payload[1]; + + return ret; +} + +/** + * get_set_conduit_method() - Choose SMC or HVC based communication + * @np: Pointer to the device_node structure + * + * Use SMC or HVC-based functions to communicate with EL2/EL3. + * + * Return: Returns 0 on success or error code + */ +static int get_set_conduit_method(struct device_node *np) +{ + const char *method; + + if (of_property_read_string(np, "method", &method)) { + pr_warn("%s missing \"method\" property\n", __func__); + return -ENXIO; + } + + if (!strcmp("hvc", method)) { + do_fw_call = do_fw_call_hvc; + } else if (!strcmp("smc", method)) { + do_fw_call = do_fw_call_smc; + } else { + pr_warn("%s Invalid \"method\" property: %s\n", + __func__, method); + return -EINVAL; + } + + return 0; +} + +/** + * zynqmp_pm_query_data() - Get query data from firmware + * @qdata: Variable to the zynqmp_pm_query_data structure + * @out: Returned output value + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_query_data(struct zynqmp_pm_query_data qdata, u32 *out) +{ + int ret, i = 0; + u32 ret_payload[PAYLOAD_ARG_CNT] = {0}; + + if (sip_svc_version >= SIP_SVC_PASSTHROUGH_VERSION) { + ret = zynqmp_pm_invoke_fw_fn(PM_QUERY_DATA, ret_payload, 4, + qdata.qid, qdata.arg1, + qdata.arg2, qdata.arg3); + /* To support backward compatibility */ + if (!ret && !ret_payload[0]) { + /* + * TF-A passes return status on 0th index but + * api to get clock name reads data from 0th + * index so pass data at 0th index instead of + * return status + */ + if (qdata.qid == PM_QID_CLOCK_GET_NAME || + qdata.qid == PM_QID_PINCTRL_GET_FUNCTION_NAME) + i = 1; + + for (; i < PAYLOAD_ARG_CNT; i++, out++) + *out = ret_payload[i]; + + return ret; + } + } + + ret = zynqmp_pm_invoke_fn(PM_QUERY_DATA, out, 4, qdata.qid, + qdata.arg1, qdata.arg2, qdata.arg3); + + /* + * For clock name query, all bytes in SMC response are clock name + * characters and return code is always success. For invalid clocks, + * clock name bytes would be zeros. + */ + return qdata.qid == PM_QID_CLOCK_GET_NAME ? 0 : ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_query_data); + +/** + * zynqmp_pm_clock_enable() - Enable the clock for given id + * @clock_id: ID of the clock to be enabled + * + * This function is used by master to enable the clock + * including peripherals and PLL clocks. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_enable(u32 clock_id) +{ + return zynqmp_pm_invoke_fn(PM_CLOCK_ENABLE, NULL, 1, clock_id); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_enable); + +/** + * zynqmp_pm_clock_disable() - Disable the clock for given id + * @clock_id: ID of the clock to be disable + * + * This function is used by master to disable the clock + * including peripherals and PLL clocks. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_disable(u32 clock_id) +{ + return zynqmp_pm_invoke_fn(PM_CLOCK_DISABLE, NULL, 1, clock_id); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_disable); + +/** + * zynqmp_pm_clock_getstate() - Get the clock state for given id + * @clock_id: ID of the clock to be queried + * @state: 1/0 (Enabled/Disabled) + * + * This function is used by master to get the state of clock + * including peripherals and PLL clocks. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_getstate(u32 clock_id, u32 *state) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETSTATE, ret_payload, 1, clock_id); + *state = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_getstate); + +/** + * zynqmp_pm_clock_setdivider() - Set the clock divider for given id + * @clock_id: ID of the clock + * @divider: divider value + * + * This function is used by master to set divider for any clock + * to achieve desired rate. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_setdivider(u32 clock_id, u32 divider) +{ + return zynqmp_pm_invoke_fn(PM_CLOCK_SETDIVIDER, NULL, 2, clock_id, divider); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_setdivider); + +/** + * zynqmp_pm_clock_getdivider() - Get the clock divider for given id + * @clock_id: ID of the clock + * @divider: divider value + * + * This function is used by master to get divider values + * for any clock. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_getdivider(u32 clock_id, u32 *divider) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETDIVIDER, ret_payload, 1, clock_id); + *divider = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_getdivider); + +/** + * zynqmp_pm_clock_setparent() - Set the clock parent for given id + * @clock_id: ID of the clock + * @parent_id: parent id + * + * This function is used by master to set parent for any clock. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_setparent(u32 clock_id, u32 parent_id) +{ + return zynqmp_pm_invoke_fn(PM_CLOCK_SETPARENT, NULL, 2, clock_id, parent_id); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_setparent); + +/** + * zynqmp_pm_clock_getparent() - Get the clock parent for given id + * @clock_id: ID of the clock + * @parent_id: parent id + * + * This function is used by master to get parent index + * for any clock. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_clock_getparent(u32 clock_id, u32 *parent_id) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + ret = zynqmp_pm_invoke_fn(PM_CLOCK_GETPARENT, ret_payload, 1, clock_id); + *parent_id = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_clock_getparent); + +/** + * zynqmp_pm_set_pll_frac_mode() - PM API for set PLL mode + * + * @clk_id: PLL clock ID + * @mode: PLL mode (PLL_MODE_FRAC/PLL_MODE_INT) + * + * This function sets PLL mode + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_pll_frac_mode(u32 clk_id, u32 mode) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, 0, IOCTL_SET_PLL_FRAC_MODE, clk_id, mode); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_pll_frac_mode); + +/** + * zynqmp_pm_get_pll_frac_mode() - PM API for get PLL mode + * + * @clk_id: PLL clock ID + * @mode: PLL mode + * + * This function return current PLL mode + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_pll_frac_mode(u32 clk_id, u32 *mode) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, mode, 3, 0, IOCTL_GET_PLL_FRAC_MODE, clk_id); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_pll_frac_mode); + +/** + * zynqmp_pm_set_pll_frac_data() - PM API for setting pll fraction data + * + * @clk_id: PLL clock ID + * @data: fraction data + * + * This function sets fraction data. + * It is valid for fraction mode only. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_pll_frac_data(u32 clk_id, u32 data) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, 0, IOCTL_SET_PLL_FRAC_DATA, clk_id, data); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_pll_frac_data); + +/** + * zynqmp_pm_get_pll_frac_data() - PM API for getting pll fraction data + * + * @clk_id: PLL clock ID + * @data: fraction data + * + * This function returns fraction data value. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_pll_frac_data(u32 clk_id, u32 *data) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, data, 3, 0, IOCTL_GET_PLL_FRAC_DATA, clk_id); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_pll_frac_data); + +/** + * zynqmp_pm_set_sd_tapdelay() - Set tap delay for the SD device + * + * @node_id: Node ID of the device + * @type: Type of tap delay to set (input/output) + * @value: Value to set fot the tap delay + * + * This function sets input/output tap delay for the SD device. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_sd_tapdelay(u32 node_id, u32 type, u32 value) +{ + u32 reg = (type == PM_TAPDELAY_INPUT) ? SD_ITAPDLY : SD_OTAPDLYSEL; + u32 mask = (node_id == NODE_SD_0) ? GENMASK(15, 0) : GENMASK(31, 16); + + if (value) { + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, node_id, IOCTL_SET_SD_TAPDELAY, type, + value); + } + + /* + * Work around completely misdesigned firmware API on Xilinx ZynqMP. + * The IOCTL_SET_SD_TAPDELAY firmware call allows the caller to only + * ever set IOU_SLCR SD_ITAPDLY Register SD0_ITAPDLYENA/SD1_ITAPDLYENA + * bits, but there is no matching call to clear those bits. If those + * bits are not cleared, SDMMC tuning may fail. + * + * Luckily, there are PM_MMIO_READ/PM_MMIO_WRITE calls which seem to + * allow complete unrestricted access to all address space, including + * IOU_SLCR SD_ITAPDLY Register and all the other registers, access + * to which was supposed to be protected by the current firmware API. + * + * Use PM_MMIO_READ/PM_MMIO_WRITE to re-implement the missing counter + * part of IOCTL_SET_SD_TAPDELAY which clears SDx_ITAPDLYENA bits. + */ + return zynqmp_pm_invoke_fn(PM_MMIO_WRITE, NULL, 2, reg, mask); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_sd_tapdelay); + +/** + * zynqmp_pm_sd_dll_reset() - Reset DLL logic + * + * @node_id: Node ID of the device + * @type: Reset type + * + * This function resets DLL logic for the SD device. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_sd_dll_reset(u32 node_id, u32 type) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 3, node_id, IOCTL_SD_DLL_RESET, type); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_sd_dll_reset); + +/** + * zynqmp_pm_ospi_mux_select() - OSPI Mux selection + * + * @dev_id: Device Id of the OSPI device. + * @select: OSPI Mux select value. + * + * This function select the OSPI Mux. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_ospi_mux_select(u32 dev_id, u32 select) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 3, dev_id, IOCTL_OSPI_MUX_SELECT, select); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_ospi_mux_select); + +/** + * zynqmp_pm_write_ggs() - PM API for writing global general storage (ggs) + * @index: GGS register index + * @value: Register value to be written + * + * This function writes value to GGS register. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_write_ggs(u32 index, u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, 0, IOCTL_WRITE_GGS, index, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_write_ggs); + +/** + * zynqmp_pm_read_ggs() - PM API for reading global general storage (ggs) + * @index: GGS register index + * @value: Register value to be written + * + * This function returns GGS register value. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_read_ggs(u32 index, u32 *value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, value, 3, 0, IOCTL_READ_GGS, index); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_read_ggs); + +/** + * zynqmp_pm_write_pggs() - PM API for writing persistent global general + * storage (pggs) + * @index: PGGS register index + * @value: Register value to be written + * + * This function writes value to PGGS register. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_write_pggs(u32 index, u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, 0, IOCTL_WRITE_PGGS, index, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_write_pggs); + +/** + * zynqmp_pm_read_pggs() - PM API for reading persistent global general + * storage (pggs) + * @index: PGGS register index + * @value: Register value to be written + * + * This function returns PGGS register value. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_read_pggs(u32 index, u32 *value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, value, 3, 0, IOCTL_READ_PGGS, index); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_read_pggs); + +int zynqmp_pm_set_tapdelay_bypass(u32 index, u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, 0, IOCTL_SET_TAPDELAY_BYPASS, index, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_tapdelay_bypass); + +/** + * zynqmp_pm_set_boot_health_status() - PM API for setting healthy boot status + * @value: Status value to be written + * + * This function sets healthy bit value to indicate boot health status + * to firmware. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_boot_health_status(u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 3, 0, IOCTL_SET_BOOT_HEALTH_STATUS, value); +} + +/** + * zynqmp_pm_reset_assert - Request setting of reset (1 - assert, 0 - release) + * @reset: Reset to be configured + * @assert_flag: Flag stating should reset be asserted (1) or + * released (0) + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_reset_assert(const u32 reset, + const enum zynqmp_pm_reset_action assert_flag) +{ + return zynqmp_pm_invoke_fn(PM_RESET_ASSERT, NULL, 2, reset, assert_flag); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_reset_assert); + +/** + * zynqmp_pm_reset_get_status - Get status of the reset + * @reset: Reset whose status should be returned + * @status: Returned status + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_reset_get_status(const u32 reset, u32 *status) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!status) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_RESET_GET_STATUS, ret_payload, 1, reset); + *status = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_reset_get_status); + +/** + * zynqmp_pm_fpga_load - Perform the fpga load + * @address: Address to write to + * @size: pl bitstream size + * @flags: Bitstream type + * -XILINX_ZYNQMP_PM_FPGA_FULL: FPGA full reconfiguration + * -XILINX_ZYNQMP_PM_FPGA_PARTIAL: FPGA partial reconfiguration + * + * This function provides access to pmufw. To transfer + * the required bitstream into PL. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_fpga_load(const u64 address, const u32 size, const u32 flags) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + ret = zynqmp_pm_invoke_fn(PM_FPGA_LOAD, ret_payload, 4, lower_32_bits(address), + upper_32_bits(address), size, flags); + if (ret_payload[0]) + return -ret_payload[0]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_fpga_load); + +/** + * zynqmp_pm_fpga_get_status - Read value from PCAP status register + * @value: Value to read + * + * This function provides access to the pmufw to get the PCAP + * status + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_fpga_get_status(u32 *value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!value) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_FPGA_GET_STATUS, ret_payload, 0); + *value = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_fpga_get_status); + +/** + * zynqmp_pm_fpga_get_config_status - Get the FPGA configuration status. + * @value: Buffer to store FPGA configuration status. + * + * This function provides access to the pmufw to get the FPGA configuration + * status + * + * Return: 0 on success, a negative value on error + */ +int zynqmp_pm_fpga_get_config_status(u32 *value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!value) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_FPGA_READ, ret_payload, 4, + XILINX_ZYNQMP_PM_FPGA_CONFIG_STAT_OFFSET, 0, 0, + XILINX_ZYNQMP_PM_FPGA_READ_CONFIG_REG); + + *value = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_fpga_get_config_status); + +/** + * zynqmp_pm_pinctrl_request - Request Pin from firmware + * @pin: Pin number to request + * + * This function requests pin from firmware. + * + * Return: Returns status, either success or error+reason. + */ +int zynqmp_pm_pinctrl_request(const u32 pin) +{ + return zynqmp_pm_invoke_fn(PM_PINCTRL_REQUEST, NULL, 1, pin); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_pinctrl_request); + +/** + * zynqmp_pm_pinctrl_release - Inform firmware that Pin control is released + * @pin: Pin number to release + * + * This function release pin from firmware. + * + * Return: Returns status, either success or error+reason. + */ +int zynqmp_pm_pinctrl_release(const u32 pin) +{ + return zynqmp_pm_invoke_fn(PM_PINCTRL_RELEASE, NULL, 1, pin); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_pinctrl_release); + +/** + * zynqmp_pm_pinctrl_set_function - Set requested function for the pin + * @pin: Pin number + * @id: Function ID to set + * + * This function sets requested function for the given pin. + * + * Return: Returns status, either success or error+reason. + */ +int zynqmp_pm_pinctrl_set_function(const u32 pin, const u32 id) +{ + return zynqmp_pm_invoke_fn(PM_PINCTRL_SET_FUNCTION, NULL, 2, pin, id); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_pinctrl_set_function); + +/** + * zynqmp_pm_pinctrl_get_config - Get configuration parameter for the pin + * @pin: Pin number + * @param: Parameter to get + * @value: Buffer to store parameter value + * + * This function gets requested configuration parameter for the given pin. + * + * Return: Returns status, either success or error+reason. + */ +int zynqmp_pm_pinctrl_get_config(const u32 pin, const u32 param, + u32 *value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!value) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_PINCTRL_CONFIG_PARAM_GET, ret_payload, 2, pin, param); + *value = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_pinctrl_get_config); + +/** + * zynqmp_pm_pinctrl_set_config - Set configuration parameter for the pin + * @pin: Pin number + * @param: Parameter to set + * @value: Parameter value to set + * + * This function sets requested configuration parameter for the given pin. + * + * Return: Returns status, either success or error+reason. + */ +int zynqmp_pm_pinctrl_set_config(const u32 pin, const u32 param, + u32 value) +{ + int ret; + u32 pm_family_code; + + ret = zynqmp_pm_get_family_info(&pm_family_code); + if (ret) + return ret; + + if (pm_family_code == PM_ZYNQMP_FAMILY_CODE && + param == PM_PINCTRL_CONFIG_TRI_STATE) { + ret = zynqmp_pm_feature(PM_PINCTRL_CONFIG_PARAM_SET); + if (ret < PM_PINCTRL_PARAM_SET_VERSION) { + pr_warn("The requested pinctrl feature is not supported in the current firmware.\n" + "Expected firmware version is 2023.1 and above for this feature to work.\r\n"); + return -EOPNOTSUPP; + } + } + + return zynqmp_pm_invoke_fn(PM_PINCTRL_CONFIG_PARAM_SET, NULL, 3, pin, param, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_pinctrl_set_config); + +/** + * zynqmp_pm_bootmode_read() - PM Config API for read bootpin status + * @ps_mode: Returned output value of ps_mode + * + * This API function is to be used for notify the power management controller + * to read bootpin status. + * + * Return: status, either success or error+reason + */ +unsigned int zynqmp_pm_bootmode_read(u32 *ps_mode) +{ + unsigned int ret; + u32 ret_payload[PAYLOAD_ARG_CNT]; + + ret = zynqmp_pm_invoke_fn(PM_MMIO_READ, ret_payload, 1, CRL_APB_BOOT_PIN_CTRL); + + *ps_mode = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_bootmode_read); + +/** + * zynqmp_pm_bootmode_write() - PM Config API for Configure bootpin + * @ps_mode: Value to be written to the bootpin ctrl register + * + * This API function is to be used for notify the power management controller + * to configure bootpin. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_bootmode_write(u32 ps_mode) +{ + return zynqmp_pm_invoke_fn(PM_MMIO_WRITE, NULL, 3, CRL_APB_BOOT_PIN_CTRL, + CRL_APB_BOOTPIN_CTRL_MASK, ps_mode); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_bootmode_write); + +/** + * zynqmp_pm_init_finalize() - PM call to inform firmware that the caller + * master has initialized its own power management + * + * Return: Returns status, either success or error+reason + * + * This API function is to be used for notify the power management controller + * about the completed power management initialization. + */ +static int zynqmp_pm_init_finalize(void) +{ + return zynqmp_pm_invoke_fn(PM_PM_INIT_FINALIZE, NULL, 0); +} + +/** + * zynqmp_pm_set_suspend_mode() - Set system suspend mode + * @mode: Mode to set for system suspend + * + * This API function is used to set mode of system suspend. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_suspend_mode(u32 mode) +{ + return zynqmp_pm_invoke_fn(PM_SET_SUSPEND_MODE, NULL, 1, mode); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_suspend_mode); + +/** + * zynqmp_pm_request_node() - Request a node with specific capabilities + * @node: Node ID of the slave + * @capabilities: Requested capabilities of the slave + * @qos: Quality of service (not supported) + * @ack: Flag to specify whether acknowledge is requested + * + * This function is used by master to request particular node from firmware. + * Every master must request node before using it. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_request_node(const u32 node, const u32 capabilities, + const u32 qos, const enum zynqmp_pm_request_ack ack) +{ + return zynqmp_pm_invoke_fn(PM_REQUEST_NODE, NULL, 4, node, capabilities, qos, ack); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_request_node); + +/** + * zynqmp_pm_release_node() - Release a node + * @node: Node ID of the slave + * + * This function is used by master to inform firmware that master + * has released node. Once released, master must not use that node + * without re-request. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_release_node(const u32 node) +{ + return zynqmp_pm_invoke_fn(PM_RELEASE_NODE, NULL, 1, node); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_release_node); + +/** + * zynqmp_pm_get_rpu_mode() - Get RPU mode + * @node_id: Node ID of the device + * @rpu_mode: return by reference value + * either split or lockstep + * + * Return: return 0 on success or error+reason. + * if success, then rpu_mode will be set + * to current rpu mode. + */ +int zynqmp_pm_get_rpu_mode(u32 node_id, enum rpu_oper_mode *rpu_mode) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + ret = zynqmp_pm_invoke_fn(PM_IOCTL, ret_payload, 2, node_id, IOCTL_GET_RPU_OPER_MODE); + + /* only set rpu_mode if no error */ + if (ret == XST_PM_SUCCESS) + *rpu_mode = ret_payload[0]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_rpu_mode); + +/** + * zynqmp_pm_set_rpu_mode() - Set RPU mode + * @node_id: Node ID of the device + * @rpu_mode: Argument 1 to requested IOCTL call. either split or lockstep + * + * This function is used to set RPU mode to split or + * lockstep + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_rpu_mode(u32 node_id, enum rpu_oper_mode rpu_mode) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 3, node_id, IOCTL_SET_RPU_OPER_MODE, + (u32)rpu_mode); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_rpu_mode); + +/** + * zynqmp_pm_set_tcm_config - configure TCM + * @node_id: Firmware specific TCM subsystem ID + * @tcm_mode: Argument 1 to requested IOCTL call + * either PM_RPU_TCM_COMB or PM_RPU_TCM_SPLIT + * + * This function is used to set RPU mode to split or combined + * + * Return: status: 0 for success, else failure + */ +int zynqmp_pm_set_tcm_config(u32 node_id, enum rpu_tcm_comb tcm_mode) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 3, node_id, IOCTL_TCM_COMB_CONFIG, + (u32)tcm_mode); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_tcm_config); + +/** + * zynqmp_pm_get_node_status - PM call to request a node's current power state + * @node: ID of the component or sub-system in question + * @status: Current operating state of the requested node + * @requirements: Current requirements asserted on the node, + * used for slave nodes only. + * @usage: Usage information, used for slave nodes only: + * PM_USAGE_NO_MASTER - No master is currently using + * the node + * PM_USAGE_CURRENT_MASTER - Only requesting master is + * currently using the node + * PM_USAGE_OTHER_MASTER - Only other masters are + * currently using the node + * PM_USAGE_BOTH_MASTERS - Both the current and at least + * one other master is currently + * using the node + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_node_status(const u32 node, u32 *const status, + u32 *const requirements, u32 *const usage) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!status || !requirements || !usage) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_GET_NODE_STATUS, ret_payload, 1, node); + if (ret_payload[0] == XST_PM_SUCCESS) { + *status = ret_payload[1]; + *requirements = ret_payload[2]; + *usage = ret_payload[3]; + } + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_node_status); + +/** + * zynqmp_pm_get_rpu_node_status - PM call to request a RPU node's current power state + * @node: ID of the RPU component or sub-system in question + * @status: Current operating state of the requested RPU node. + * @requirements: Current requirements asserted on the RPU node. + * @usage: Usage information, used for RPU slave nodes only: + * PM_USAGE_NO_MASTER - No master is currently using + * the node + * PM_USAGE_CURRENT_MASTER - Only requesting master is + * currently using the node + * PM_USAGE_OTHER_MASTER - Only other masters are + * currently using the node + * PM_USAGE_BOTH_MASTERS - Both the current and at least + * one other master is currently + * using the node + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_get_rpu_node_status(const u32 node, u32 *const status, + u32 *const requirements, u32 *const usage) +{ + if (zynqmp_pm_feature(PM_GET_NODE_STATUS) < PM_API_VERSION_2) + return -EOPNOTSUPP; + + return zynqmp_pm_get_node_status(node, status, requirements, usage); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_get_rpu_node_status); + +/** + * zynqmp_pm_force_pwrdwn - PM call to request for another PU or subsystem to + * be powered down forcefully + * @node: Node ID of the targeted PU or subsystem + * @ack: Flag to specify whether acknowledge is requested + * + * Return: status, either success or error+reason + */ +int zynqmp_pm_force_pwrdwn(const u32 node, + const enum zynqmp_pm_request_ack ack) +{ + return zynqmp_pm_invoke_fn(PM_FORCE_POWERDOWN, NULL, 2, node, ack); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_force_pwrdwn); + +/** + * zynqmp_pm_request_wake - PM call to wake up selected master or subsystem + * @node: Node ID of the master or subsystem + * @set_addr: Specifies whether the address argument is relevant + * @address: Address from which to resume when woken up + * @ack: Flag to specify whether acknowledge requested + * + * Return: status, either success or error+reason + */ +int zynqmp_pm_request_wake(const u32 node, + const bool set_addr, + const u64 address, + const enum zynqmp_pm_request_ack ack) +{ + /* set_addr flag is encoded into 1st bit of address */ + return zynqmp_pm_invoke_fn(PM_REQUEST_WAKEUP, NULL, 4, node, address | set_addr, + address >> 32, ack); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_request_wake); + +/** + * zynqmp_pm_start_rpu - Boot Real-time Processing Unit (Cortex-R) on SoC + * + * @node: power-domains id of the core + * @bootaddr: Boot address of elf + * + * Return: status, either success or error+reason + */ +int zynqmp_pm_start_rpu(const u32 node, const u64 bootaddr) +{ + enum rpu_boot_mem bootmem; + int ret; + + /* + * The exception vector pointers (EVP) refer to the base-address of + * exception vectors (for reset, IRQ, FIQ, etc). The reset-vector + * starts at the base-address and subsequent vectors are on 4-byte + * boundaries. + * + * Exception vectors can start either from 0x0000_0000 (LOVEC) or + * from 0xFFFF_0000 (HIVEC) which is mapped in the OCM (On-Chip Memory) + * + * Usually firmware will put Exception vectors at LOVEC. + * + * It is not recommend that you change the exception vector. + * Changing the EVP to HIVEC will result in increased interrupt latency + * and jitter. Also, if the OCM is secured and the Cortex-R5F processor + * is non-secured, then the Cortex-R5F processor cannot access the + * HIVEC exception vectors in the OCM. + */ + bootmem = (bootaddr >= 0xFFFC0000) ? + PM_RPU_BOOTMEM_HIVEC : PM_RPU_BOOTMEM_LOVEC; + + pr_debug("RPU boot addr 0x%llx from %s.", bootaddr, + bootmem == PM_RPU_BOOTMEM_HIVEC ? "OCM" : "TCM"); + + /* Request node before starting RPU core if new version of API is supported */ + if (zynqmp_pm_feature(PM_REQUEST_NODE) > PM_API_VERSION_1) { + ret = zynqmp_pm_request_node(node, + ZYNQMP_PM_CAPABILITY_ACCESS, 0, + ZYNQMP_PM_REQUEST_ACK_BLOCKING); + if (ret < 0) { + pr_err("failed to request 0x%x", node); + return ret; + } + } + + ret = zynqmp_pm_request_wake(node, true, + bootmem, ZYNQMP_PM_REQUEST_ACK_NO); + if (ret) + pr_err("failed to start RPU = 0x%x\n", node); + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_start_rpu); + +/** + * zynqmp_pm_stop_rpu - Stop Real-time Processing Unit (Cortex-R) on SoC + * + * @node: power-domains id of the core + * + * Return: status, either success or error+reason + */ +int zynqmp_pm_stop_rpu(const u32 node) +{ + int ret; + + /* Use release node API to stop core if new version of API is supported */ + if (zynqmp_pm_feature(PM_RELEASE_NODE) > PM_API_VERSION_1) { + ret = zynqmp_pm_release_node(node); + if (ret) + pr_err("failed to stop remoteproc RPU %d\n", ret); + return ret; + } + + /* + * Check expected version of EEMI call before calling it. This avoids + * any error or warning prints from firmware as it is expected that fw + * doesn't support it. + */ + if (zynqmp_pm_feature(PM_FORCE_POWERDOWN) != PM_API_VERSION_1) { + pr_debug("EEMI interface %d ver 1 not supported\n", + PM_FORCE_POWERDOWN); + return -EOPNOTSUPP; + } + + /* maintain force pwr down for backward compatibility */ + ret = zynqmp_pm_force_pwrdwn(node, ZYNQMP_PM_REQUEST_ACK_BLOCKING); + if (ret) + pr_err("core force power down failed\n"); + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_stop_rpu); + +/** + * zynqmp_pm_set_requirement() - PM call to set requirement for PM slaves + * @node: Node ID of the slave + * @capabilities: Requested capabilities of the slave + * @qos: Quality of service (not supported) + * @ack: Flag to specify whether acknowledge is requested + * + * This API function is to be used for slaves a PU already has requested + * to change its capabilities. + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_set_requirement(const u32 node, const u32 capabilities, + const u32 qos, + const enum zynqmp_pm_request_ack ack) +{ + return zynqmp_pm_invoke_fn(PM_SET_REQUIREMENT, NULL, 4, node, capabilities, qos, ack); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_requirement); + +/** + * zynqmp_pm_load_pdi - Load and process PDI + * @src: Source device where PDI is located + * @address: PDI src address + * + * This function provides support to load PDI from linux + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_load_pdi(const u32 src, const u64 address) +{ + return zynqmp_pm_invoke_fn(PM_LOAD_PDI, NULL, 3, src, lower_32_bits(address), + upper_32_bits(address)); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_load_pdi); + +/** + * zynqmp_pm_efuse_access - Provides access to efuse memory. + * @address: Address of the efuse params structure + * @out: Returned output value + * + * Return: Returns status, either success or error code. + */ +int zynqmp_pm_efuse_access(const u64 address, u32 *out) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + int ret; + + if (!out) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_EFUSE_ACCESS, ret_payload, 2, + upper_32_bits(address), + lower_32_bits(address)); + *out = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_efuse_access); + +/** + * zynqmp_pm_register_notifier() - PM API for register a subsystem + * to be notified about specific + * event/error. + * @node: Node ID to which the event is related. + * @event: Event Mask of Error events for which wants to get notified. + * @wake: Wake subsystem upon capturing the event if value 1 + * @enable: Enable the registration for value 1, disable for value 0 + * + * This function is used to register/un-register for particular node-event + * combination in firmware. + * + * 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 zynqmp_pm_invoke_fn(PM_REGISTER_NOTIFIER, NULL, 4, node, event, wake, enable); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_register_notifier); + +/** + * zynqmp_pm_system_shutdown - PM call to request a system shutdown or restart + * @type: Shutdown or restart? 0 for shutdown, 1 for restart + * @subtype: Specifies which system should be restarted or shut down + * + * Return: Returns status, either success or error+reason + */ +int zynqmp_pm_system_shutdown(const u32 type, const u32 subtype) +{ + return zynqmp_pm_invoke_fn(PM_SYSTEM_SHUTDOWN, NULL, 2, type, subtype); +} + +/** + * zynqmp_pm_set_feature_config - PM call to request IOCTL for feature config + * @id: The config ID of the feature to be configured + * @value: The config value of the feature to be configured + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_set_feature_config(enum pm_feature_config_id id, u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, 0, IOCTL_SET_FEATURE_CONFIG, id, value); +} + +/** + * zynqmp_pm_get_feature_config - PM call to get value of configured feature + * @id: The config id of the feature to be queried + * @payload: Returned value array + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_get_feature_config(enum pm_feature_config_id id, + u32 *payload) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, payload, 3, 0, IOCTL_GET_FEATURE_CONFIG, id); +} + +/** + * zynqmp_pm_sec_read_reg - PM call to securely read from given offset + * of the node + * @node_id: Node Id of the device + * @offset: Offset to be used (20-bit) + * @ret_value: Output data read from the given offset after + * firmware access policy is successfully enforced + * + * Return: Returns 0 on success or error value on failure + */ +int zynqmp_pm_sec_read_reg(u32 node_id, u32 offset, u32 *ret_value) +{ + u32 ret_payload[PAYLOAD_ARG_CNT]; + u32 count = 1; + int ret; + + if (!ret_value) + return -EINVAL; + + ret = zynqmp_pm_invoke_fn(PM_IOCTL, ret_payload, 4, node_id, IOCTL_READ_REG, + offset, count); + + *ret_value = ret_payload[1]; + + return ret; +} +EXPORT_SYMBOL_GPL(zynqmp_pm_sec_read_reg); + +/** + * zynqmp_pm_sec_mask_write_reg - PM call to securely write to given offset + * of the node + * @node_id: Node Id of the device + * @offset: Offset to be used (20-bit) + * @mask: Mask to be used + * @value: Value to be written + * + * Return: Returns 0 on success or error value on failure + */ +int zynqmp_pm_sec_mask_write_reg(const u32 node_id, const u32 offset, u32 mask, + u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 5, node_id, IOCTL_MASK_WRITE_REG, + offset, mask, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_sec_mask_write_reg); + +/** + * zynqmp_pm_set_sd_config - PM call to set value of SD config registers + * @node: SD node ID + * @config: The config type of SD registers + * @value: Value to be set + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_set_sd_config(u32 node, enum pm_sd_config_type config, u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, node, IOCTL_SET_SD_CONFIG, config, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_sd_config); + +/** + * zynqmp_pm_set_gem_config - PM call to set value of GEM config registers + * @node: GEM node ID + * @config: The config type of GEM registers + * @value: Value to be set + * + * Return: Returns 0 on success or error value on failure. + */ +int zynqmp_pm_set_gem_config(u32 node, enum pm_gem_config_type config, + u32 value) +{ + return zynqmp_pm_invoke_fn(PM_IOCTL, NULL, 4, node, IOCTL_SET_GEM_CONFIG, config, value); +} +EXPORT_SYMBOL_GPL(zynqmp_pm_set_gem_config); + +/** + * struct zynqmp_pm_shutdown_scope - Struct for shutdown scope + * @subtype: Shutdown subtype + * @name: Matching string for scope argument + * + * This struct encapsulates mapping between shutdown scope ID and string. + */ +struct zynqmp_pm_shutdown_scope { + const enum zynqmp_pm_shutdown_subtype subtype; + const char *name; +}; + +static struct zynqmp_pm_shutdown_scope shutdown_scopes[] = { + [ZYNQMP_PM_SHUTDOWN_SUBTYPE_SUBSYSTEM] = { + .subtype = ZYNQMP_PM_SHUTDOWN_SUBTYPE_SUBSYSTEM, + .name = "subsystem", + }, + [ZYNQMP_PM_SHUTDOWN_SUBTYPE_PS_ONLY] = { + .subtype = ZYNQMP_PM_SHUTDOWN_SUBTYPE_PS_ONLY, + .name = "ps_only", + }, + [ZYNQMP_PM_SHUTDOWN_SUBTYPE_SYSTEM] = { + .subtype = ZYNQMP_PM_SHUTDOWN_SUBTYPE_SYSTEM, + .name = "system", + }, +}; + +static struct zynqmp_pm_shutdown_scope *selected_scope = + &shutdown_scopes[ZYNQMP_PM_SHUTDOWN_SUBTYPE_SYSTEM]; + +/** + * zynqmp_pm_is_shutdown_scope_valid - Check if shutdown scope string is valid + * @scope_string: Shutdown scope string + * + * Return: Return pointer to matching shutdown scope struct from + * array of available options in system if string is valid, + * otherwise returns NULL. + */ +static struct zynqmp_pm_shutdown_scope* + zynqmp_pm_is_shutdown_scope_valid(const char *scope_string) +{ + int count; + + for (count = 0; count < ARRAY_SIZE(shutdown_scopes); count++) + if (sysfs_streq(scope_string, shutdown_scopes[count].name)) + return &shutdown_scopes[count]; + + return NULL; +} + +static ssize_t shutdown_scope_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(shutdown_scopes); i++) { + if (&shutdown_scopes[i] == selected_scope) { + strcat(buf, "["); + strcat(buf, shutdown_scopes[i].name); + strcat(buf, "]"); + } else { + strcat(buf, shutdown_scopes[i].name); + } + strcat(buf, " "); + } + strcat(buf, "\n"); + + return strlen(buf); +} + +static ssize_t shutdown_scope_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + struct zynqmp_pm_shutdown_scope *scope; + + scope = zynqmp_pm_is_shutdown_scope_valid(buf); + if (!scope) + return -EINVAL; + + ret = zynqmp_pm_system_shutdown(ZYNQMP_PM_SHUTDOWN_TYPE_SETSCOPE_ONLY, + scope->subtype); + if (ret) { + pr_err("unable to set shutdown scope %s\n", buf); + return ret; + } + + selected_scope = scope; + + return count; +} + +static DEVICE_ATTR_RW(shutdown_scope); + +static ssize_t health_status_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + unsigned int value; + + ret = kstrtouint(buf, 10, &value); + if (ret) + return ret; + + ret = zynqmp_pm_set_boot_health_status(value); + if (ret) { + dev_err(device, "unable to set healthy bit value to %u\n", + value); + return ret; + } + + return count; +} + +static DEVICE_ATTR_WO(health_status); + +static ssize_t ggs_show(struct device *device, + struct device_attribute *attr, + char *buf, + u32 reg) +{ + int ret; + u32 ret_payload[PAYLOAD_ARG_CNT]; + + ret = zynqmp_pm_read_ggs(reg, ret_payload); + if (ret) + return ret; + + return sprintf(buf, "0x%x\n", ret_payload[1]); +} + +static ssize_t ggs_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count, + u32 reg) +{ + long value; + int ret; + + if (reg >= GSS_NUM_REGS) + return -EINVAL; + + ret = kstrtol(buf, 16, &value); + if (ret) { + count = -EFAULT; + goto err; + } + + ret = zynqmp_pm_write_ggs(reg, value); + if (ret) + count = -EFAULT; +err: + return count; +} + +/* GGS register show functions */ +#define GGS0_SHOW(N) \ + ssize_t ggs##N##_show(struct device *device, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + return ggs_show(device, attr, buf, N); \ + } + +static GGS0_SHOW(0); +static GGS0_SHOW(1); +static GGS0_SHOW(2); +static GGS0_SHOW(3); + +/* GGS register store function */ +#define GGS0_STORE(N) \ + ssize_t ggs##N##_store(struct device *device, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ + { \ + return ggs_store(device, attr, buf, count, N); \ + } + +static GGS0_STORE(0); +static GGS0_STORE(1); +static GGS0_STORE(2); +static GGS0_STORE(3); + +static ssize_t pggs_show(struct device *device, + struct device_attribute *attr, + char *buf, + u32 reg) +{ + int ret; + u32 ret_payload[PAYLOAD_ARG_CNT]; + + ret = zynqmp_pm_read_pggs(reg, ret_payload); + if (ret) + return ret; + + return sprintf(buf, "0x%x\n", ret_payload[1]); +} + +static ssize_t pggs_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count, + u32 reg) +{ + long value; + int ret; + + if (reg >= GSS_NUM_REGS) + return -EINVAL; + + ret = kstrtol(buf, 16, &value); + if (ret) { + count = -EFAULT; + goto err; + } + + ret = zynqmp_pm_write_pggs(reg, value); + if (ret) + count = -EFAULT; + +err: + return count; +} + +#define PGGS0_SHOW(N) \ + ssize_t pggs##N##_show(struct device *device, \ + struct device_attribute *attr, \ + char *buf) \ + { \ + return pggs_show(device, attr, buf, N); \ + } + +#define PGGS0_STORE(N) \ + ssize_t pggs##N##_store(struct device *device, \ + struct device_attribute *attr, \ + const char *buf, \ + size_t count) \ + { \ + return pggs_store(device, attr, buf, count, N); \ + } + +/* PGGS register show functions */ +static PGGS0_SHOW(0); +static PGGS0_SHOW(1); +static PGGS0_SHOW(2); +static PGGS0_SHOW(3); + +/* PGGS register store functions */ +static PGGS0_STORE(0); +static PGGS0_STORE(1); +static PGGS0_STORE(2); +static PGGS0_STORE(3); + +/* GGS register attributes */ +static DEVICE_ATTR_RW(ggs0); +static DEVICE_ATTR_RW(ggs1); +static DEVICE_ATTR_RW(ggs2); +static DEVICE_ATTR_RW(ggs3); + +/* PGGS register attributes */ +static DEVICE_ATTR_RW(pggs0); +static DEVICE_ATTR_RW(pggs1); +static DEVICE_ATTR_RW(pggs2); +static DEVICE_ATTR_RW(pggs3); + +static ssize_t feature_config_id_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct zynqmp_devinfo *devinfo = dev_get_drvdata(device); + + return sysfs_emit(buf, "%d\n", devinfo->feature_conf_id); +} + +static ssize_t feature_config_id_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 config_id; + int ret; + struct zynqmp_devinfo *devinfo = dev_get_drvdata(device); + + if (!buf) + return -EINVAL; + + ret = kstrtou32(buf, 10, &config_id); + if (ret) + return ret; + + devinfo->feature_conf_id = config_id; + + return count; +} + +static DEVICE_ATTR_RW(feature_config_id); + +static ssize_t feature_config_value_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + int ret; + u32 ret_payload[PAYLOAD_ARG_CNT]; + struct zynqmp_devinfo *devinfo = dev_get_drvdata(device); + + ret = zynqmp_pm_get_feature_config(devinfo->feature_conf_id, + ret_payload); + if (ret) + return ret; + + return sysfs_emit(buf, "%d\n", ret_payload[1]); +} + +static ssize_t feature_config_value_store(struct device *device, + struct device_attribute *attr, + const char *buf, size_t count) +{ + u32 value; + int ret; + struct zynqmp_devinfo *devinfo = dev_get_drvdata(device); + + if (!buf) + return -EINVAL; + + ret = kstrtou32(buf, 10, &value); + if (ret) + return ret; + + ret = zynqmp_pm_set_feature_config(devinfo->feature_conf_id, + value); + if (ret) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(feature_config_value); + +static struct attribute *zynqmp_firmware_attrs[] = { + &dev_attr_ggs0.attr, + &dev_attr_ggs1.attr, + &dev_attr_ggs2.attr, + &dev_attr_ggs3.attr, + &dev_attr_pggs0.attr, + &dev_attr_pggs1.attr, + &dev_attr_pggs2.attr, + &dev_attr_pggs3.attr, + &dev_attr_shutdown_scope.attr, + &dev_attr_health_status.attr, + &dev_attr_feature_config_id.attr, + &dev_attr_feature_config_value.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(zynqmp_firmware); + +/** + * zynqmp_clear_pm_state() - Clear subsystem state + * @dev: Device pointer used for logging + * + * Clears PM specific data in EL3 and platform firmware. + * + * Return: Returns status, either success or error + */ +static int zynqmp_clear_pm_state(struct device *dev) +{ + u32 pm_family_code; + int ret; + + /* Get the Family code of platform */ + ret = zynqmp_pm_get_family_info(&pm_family_code); + if (ret < 0) + return ret; + + /* Supporting on Versal and Versal Net platforms only */ + if (pm_family_code == PM_VERSAL_FAMILY_CODE || + pm_family_code == PM_VERSAL_NET_FAMILY_CODE) { + /* Check if EL3 firmware supports TF_A_CLEAR_PM_STATE */ + ret = do_feature_check_call(TF_A_CLEAR_PM_STATE); + if (ret >= 0 && ((ret & FIRMWARE_VERSION_MASK) >= PM_API_VERSION_1)) { + /* Clear PM specific data in EL3 firmware */ + ret = zynqmp_pm_invoke_fn(TF_A_CLEAR_PM_STATE, NULL, 0); + if (ret) + dev_err(dev, + "Failed to clear EL3 PM subsystem state: %d\n", ret); + } else { + dev_warn(dev, "TF_A_CLEAR_PM_STATE is not supported by EL3 firmware: %d\n", ret); + ret = 0; + } + + /* Check if the firmware supports the PM_DEV_ALL_PERIPH node ID */ + ret = do_feature_check_call(PM_RELEASE_NODE); + if (ret >= 0 && ((ret & FIRMWARE_VERSION_MASK) >= PM_API_VERSION_3)) { + /* Attempt to release all peripheral devices via firmware */ + ret = zynqmp_pm_release_node(PM_DEV_ALL_PERIPH); + if (ret) + dev_err(dev, "Failed to release all peripheral devices: %d\n", ret); + } else { + dev_warn(dev, + "Bulk device release is not supported by firmware: %d\n", ret); + ret = 0; + } + + /* Check if the firmware supports the PM_ALL_NOTIFIERS node ID */ + ret = do_feature_check_call(PM_REGISTER_NOTIFIER); + if (ret >= 0 && ((ret & FIRMWARE_VERSION_MASK) >= PM_API_VERSION_3)) { + /* Attempt to unregister all notifier callbacks via firmware */ + ret = zynqmp_pm_register_notifier(PM_ALL_NOTIFIERS, 0, 0, 0); + if (ret) + dev_err(dev, "Failed to unregister all notifiers: %d\n", ret); + } else { + dev_warn(dev, + "Firmware doesn't support unregister all notifiers at once: %d\n", + ret); + ret = 0; + } + } + + return ret; +} + +static int zynqmp_firmware_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct zynqmp_devinfo *devinfo; + u32 pm_family_code; + int ret; + + ret = get_set_conduit_method(dev->of_node); + if (ret) + return ret; + + /* Get platform-specific firmware data from device tree match */ + active_platform_fw_data = (struct platform_fw_data *)device_get_match_data(dev); + if (!active_platform_fw_data) + return -EINVAL; + + /* Get SiP SVC version number */ + ret = zynqmp_pm_get_sip_svc_version(&sip_svc_version); + if (ret) + return ret; + + ret = do_feature_check_call(PM_FEATURE_CHECK); + if (ret >= 0 && ((ret & FIRMWARE_VERSION_MASK) >= PM_API_VERSION_1)) + feature_check_enabled = true; + + devinfo = devm_kzalloc(dev, sizeof(*devinfo), GFP_KERNEL); + if (!devinfo) + return -ENOMEM; + + devinfo->dev = dev; + + platform_set_drvdata(pdev, devinfo); + + /* Check PM API version number */ + ret = zynqmp_pm_get_api_version(&pm_api_version); + if (ret) + return ret; + + if (pm_api_version < ZYNQMP_PM_VERSION) { + panic("%s Platform Management API version error. Expected: v%d.%d - Found: v%d.%d\n", + __func__, + ZYNQMP_PM_VERSION_MAJOR, ZYNQMP_PM_VERSION_MINOR, + pm_api_version >> 16, pm_api_version & 0xFFFF); + } + + pr_info("%s Platform Management API v%d.%d\n", __func__, + pm_api_version >> 16, pm_api_version & 0xFFFF); + + /* Get the Family code of platform */ + ret = zynqmp_pm_get_family_info(&pm_family_code); + if (ret < 0) + return ret; + + if (is_kdump_kernel()) + zynqmp_clear_pm_state(dev); + + /* Check trustzone version number */ + ret = zynqmp_pm_get_trustzone_version(&pm_tz_version); + if (ret) + panic("Legacy trustzone found without version support\n"); + + if (pm_tz_version < ZYNQMP_TZ_VERSION) + panic("%s Trustzone version error. Expected: v%d.%d - Found: v%d.%d\n", + __func__, + ZYNQMP_TZ_VERSION_MAJOR, ZYNQMP_TZ_VERSION_MINOR, + pm_tz_version >> 16, pm_tz_version & 0xFFFF); + + pr_info("%s Trustzone version v%d.%d\n", __func__, + pm_tz_version >> 16, pm_tz_version & 0xFFFF); + + ret = mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE, firmware_devs, + ARRAY_SIZE(firmware_devs), NULL, 0, NULL); + if (ret) { + dev_err(&pdev->dev, "failed to add MFD devices %d\n", ret); + return ret; + } + + zynqmp_pm_api_debugfs_init(); + + if (pm_family_code != PM_ZYNQMP_FAMILY_CODE) { + em_dev = platform_device_register_data(&pdev->dev, "xlnx_event_manager", + -1, NULL, 0); + if (IS_ERR(em_dev)) + dev_err_probe(&pdev->dev, PTR_ERR(em_dev), "EM register fail with error\n"); + } + + return of_platform_populate(dev->of_node, NULL, NULL, dev); +} + +static void zynqmp_firmware_shutdown(struct platform_device *pdev) +{ + zynqmp_clear_pm_state(&pdev->dev); +} + +static void zynqmp_firmware_remove(struct platform_device *pdev) +{ + struct pm_api_feature_data *feature_data; + struct hlist_node *tmp; + int i; + + mfd_remove_devices(&pdev->dev); + zynqmp_pm_api_debugfs_exit(); + + hash_for_each_safe(pm_api_features_map, i, tmp, feature_data, hentry) { + hash_del(&feature_data->hentry); + kfree(feature_data); + } + + platform_device_unregister(em_dev); +} + +static void zynqmp_firmware_sync_state(struct device *dev) +{ + struct device_node *np = dev->of_node; + + if (!of_device_is_compatible(np, "xlnx,zynqmp-firmware")) + return; + + of_genpd_sync_state(np); + + if (zynqmp_pm_init_finalize()) + dev_warn(dev, "failed to release power management to firmware\n"); +} + +static const struct platform_fw_data platform_fw_data_versal = { + .family_code = PM_VERSAL_FAMILY_CODE, +}; + +static const struct platform_fw_data platform_fw_data_versal_net = { + .family_code = PM_VERSAL_NET_FAMILY_CODE, +}; + +static const struct platform_fw_data platform_fw_data_zynqmp = { + .family_code = PM_ZYNQMP_FAMILY_CODE, +}; + +static const struct of_device_id zynqmp_firmware_of_match[] = { + {.compatible = "xlnx,zynqmp-firmware", .data = &platform_fw_data_zynqmp}, + {.compatible = "xlnx,versal-firmware", .data = &platform_fw_data_versal}, + {.compatible = "xlnx,versal-net-firmware", .data = &platform_fw_data_versal_net}, + {}, +}; +MODULE_DEVICE_TABLE(of, zynqmp_firmware_of_match); + +static struct platform_driver zynqmp_firmware_driver = { + .driver = { + .name = "zynqmp_firmware", + .of_match_table = zynqmp_firmware_of_match, + .dev_groups = zynqmp_firmware_groups, + .sync_state = zynqmp_firmware_sync_state, + }, + .probe = zynqmp_firmware_probe, + .remove = zynqmp_firmware_remove, + .shutdown = zynqmp_firmware_shutdown, +}; +module_platform_driver(zynqmp_firmware_driver); |
