From e47903449995ac952cef3bcaf171e67f05fa1071 Mon Sep 17 00:00:00 2001 From: Rajan Vaja Date: Wed, 28 Feb 2018 10:38:01 -0800 Subject: [PATCH] firmware: zynqmp: Add sysfs to set shutdown scope The Linux shutdown functionality implemented via PSCI system_off does not include an option to set a scope, i.e. which parts of the system to shut down. This patch creates sysfs that allows to set the shutdown scope for the next shutdown request. When the next shutdown is performed, the platform specific portion of PSCI-system_off can use the chosen shutdown scope. Signed-off-by: Rajan Vaja Signed-off-by: Stefan Krsmanovic Acked-by: Will Wong Acked-by: Jolly Shah Signed-off-by: Michal Simek --- .../ABI/stable/sysfs-firmware-zynqmp | 32 +++ drivers/firmware/xilinx/zynqmp/firmware-ggs.c | 12 +- drivers/firmware/xilinx/zynqmp/firmware.c | 183 +++++++++++++++++- .../linux/firmware/xilinx/zynqmp/firmware.h | 14 +- 4 files changed, 227 insertions(+), 14 deletions(-) diff --git a/Documentation/ABI/stable/sysfs-firmware-zynqmp b/Documentation/ABI/stable/sysfs-firmware-zynqmp index 0a758128fb34c6..13e4fd23662c6f 100644 --- a/Documentation/ABI/stable/sysfs-firmware-zynqmp +++ b/Documentation/ABI/stable/sysfs-firmware-zynqmp @@ -48,3 +48,35 @@ Description: # echo 0xFFFFFFFF 0x1234ABCD > /sys/firmware/zynqmp/pggs0 Users: Xilinx + +What: /sys/firmware/zynqmp/shutdown_scope +Date: February 2018 +KernelVersion: 4.15.6 +Contact: "Jolly Shah" +Description: + This sysfs interface allows to set the shutdown scope for the + next shutdown request. When the next shutdown is performed, the + platform specific portion of PSCI-system_off can use the chosen + shutdown scope. + + Following are available shutdown scopes(subtypes): + + subsystem: Only the APU along with all of its peripherals + not used by other processing units will be + shut down. This may result in the FPD power + domain being shut down provided that no other + processing unit uses FPD peripherals or DRAM. + ps_only: The complete PS will be shut down, including the + RPU, PMU, etc. Only the PL domain (FPGA) + remains untouched. + system: The complete system/device is shut down. + + Usage: + # cat /sys/firmware/zynqmp/shutdown_scope + # echo > /sys/firmware/zynqmp/shutdown_scope + + Example: + # cat /sys/firmware/zynqmp/shutdown_scope + # echo "subsystem" > /sys/firmware/zynqmp/shutdown_scope + +Users: Xilinx diff --git a/drivers/firmware/xilinx/zynqmp/firmware-ggs.c b/drivers/firmware/xilinx/zynqmp/firmware-ggs.c index e08f6a6389448a..9c4674ef995db4 100644 --- a/drivers/firmware/xilinx/zynqmp/firmware-ggs.c +++ b/drivers/firmware/xilinx/zynqmp/firmware-ggs.c @@ -283,15 +283,7 @@ static const struct attribute_group attr_group = { NULL, }; -int zynqmp_pm_ggs_init(void) +int zynqmp_pm_ggs_init(struct kobject *parent_kobj) { - struct kobject *zynqmp_kobj; - - zynqmp_kobj = kobject_create_and_add("zynqmp", firmware_kobj); - if (!zynqmp_kobj) { - pr_err("zynqmp: Firmware kobj add failed.\n"); - return -ENOMEM; - } - - return sysfs_create_group(zynqmp_kobj, &attr_group); + return sysfs_create_group(parent_kobj, &attr_group); } diff --git a/drivers/firmware/xilinx/zynqmp/firmware.c b/drivers/firmware/xilinx/zynqmp/firmware.c index 3bb2c8e4cb9c8d..708954bb1fb4dc 100644 --- a/drivers/firmware/xilinx/zynqmp/firmware.c +++ b/drivers/firmware/xilinx/zynqmp/firmware.c @@ -996,6 +996,184 @@ const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void) } EXPORT_SYMBOL_GPL(zynqmp_pm_get_eemi_ops); +/** + * 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; +} + +/** + * shutdown_scope_show - Show shutdown_scope sysfs attribute + * @kobj: Kobject structure + * @attr: Kobject attribute structure + * @buf: Requested available shutdown_scope attributes string + * + * User-space interface for viewing the available scope options for system + * shutdown. Scope option for next shutdown call is marked with []. + * + * Usage: cat /sys/firmware/zynqmp/shutdown_scope + * + * Return: Number of bytes printed into the buffer. + */ +static ssize_t shutdown_scope_show(struct kobject *kobj, + struct kobj_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); +} + +/** + * shutdown_scope_store - Store shutdown_scope sysfs attribute + * @kobj: Kobject structure + * @attr: Kobject attribute structure + * @buf: User entered shutdown_scope attribute string + * @count: Buffer size + * + * User-space interface for setting the scope for the next system shutdown. + * Usage: echo > /sys/firmware/zynqmp/shutdown_scope + * + * The Linux shutdown functionality implemented via PSCI system_off does not + * include an option to set a scope, i.e. which parts of the system to shut + * down. + * + * This API function allows to set the shutdown scope for the next shutdown + * request by passing it to the ATF running in EL3. When the next shutdown + * is performed, the platform specific portion of PSCI-system_off can use + * the chosen shutdown scope. + * + * subsystem: Only the APU along with all of its peripherals not used by other + * processing units will be shut down. This may result in the FPD + * power domain being shut down provided that no other processing + * unit uses FPD peripherals or DRAM. + * ps_only: The complete PS will be shut down, including the RPU, PMU, etc. + * Only the PL domain (FPGA) remains untouched. + * system: The complete system/device is shut down. + * + * Return: count argument if request succeeds, the corresponding error + * code otherwise + */ +static ssize_t shutdown_scope_store(struct kobject *kobj, + struct kobj_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 struct kobj_attribute zynqmp_attr_shutdown_scope = + __ATTR_RW(shutdown_scope); + +static struct attribute *attrs[] = { + &zynqmp_attr_shutdown_scope.attr, + NULL, +}; + +static const struct attribute_group attr_group = { + .attrs = attrs, + NULL, +}; + +static int zynqmp_pm_sysfs_init(void) +{ + struct kobject *zynqmp_kobj; + int ret; + + zynqmp_kobj = kobject_create_and_add("zynqmp", firmware_kobj); + if (!zynqmp_kobj) { + pr_err("zynqmp: Firmware kobj add failed.\n"); + return -ENOMEM; + } + + ret = sysfs_create_group(zynqmp_kobj, &attr_group); + if (ret) { + pr_err("%s() sysfs creation fail with error %d\n", + __func__, ret); + goto err; + } + + ret = zynqmp_pm_ggs_init(zynqmp_kobj); + if (ret) { + pr_err("%s() GGS init fail with error %d\n", + __func__, ret); + goto err; + } +err: + return ret; +} + static int __init zynqmp_plat_init(void) { struct device_node *np; @@ -1055,10 +1233,9 @@ static int zynqmp_firmware_init(void) { int ret; - ret = zynqmp_pm_ggs_init(); + ret = zynqmp_pm_sysfs_init(); if (ret) { - pr_err("%s() GGS init fail with error %d\n", - __func__, ret); + pr_err("%s() sysfs init fail with error %d\n", __func__, ret); return ret; } diff --git a/include/linux/firmware/xilinx/zynqmp/firmware.h b/include/linux/firmware/xilinx/zynqmp/firmware.h index 226d4f33155086..b01056fc26978c 100644 --- a/include/linux/firmware/xilinx/zynqmp/firmware.h +++ b/include/linux/firmware/xilinx/zynqmp/firmware.h @@ -301,6 +301,18 @@ enum zynqmp_pm_opchar_type { ZYNQMP_PM_OPERATING_CHARACTERISTIC_TEMPERATURE, }; +enum zynqmp_pm_shutdown_type { + ZYNQMP_PM_SHUTDOWN_TYPE_SHUTDOWN, + ZYNQMP_PM_SHUTDOWN_TYPE_RESET, + ZYNQMP_PM_SHUTDOWN_TYPE_SETSCOPE_ONLY, +}; + +enum zynqmp_pm_shutdown_subtype { + ZYNQMP_PM_SHUTDOWN_SUBTYPE_SUBSYSTEM, + ZYNQMP_PM_SHUTDOWN_SUBTYPE_PS_ONLY, + ZYNQMP_PM_SHUTDOWN_SUBTYPE_SYSTEM, +}; + enum pm_node_id { NODE_UNKNOWN = 0, NODE_APU, @@ -582,7 +594,7 @@ struct zynqmp_eemi_ops { int zynqmp_pm_invoke_fn(u32 pm_api_id, u32 arg0, u32 arg1, u32 arg2, u32 arg3, u32 *ret_payload); -int zynqmp_pm_ggs_init(void); +int zynqmp_pm_ggs_init(struct kobject *parent_kobj); #if IS_REACHABLE(CONFIG_ARCH_ZYNQMP) const struct zynqmp_eemi_ops *zynqmp_pm_get_eemi_ops(void);