Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# AMD PMF Driver
#
config AMD_PMF
tristate "AMD Platform Management Framework"
depends on ACPI && PCI
depends on POWER_SUPPLY
depends on AMD_NODE
select ACPI_PLATFORM_PROFILE
depends on TEE && AMDTEE
depends on AMD_SFH_HID
depends on HAS_IOMEM
help
This driver provides support for the AMD Platform Management Framework.
The goal is to enhance end user experience by making AMD PCs smarter,
quiter, power efficient by adapting to user behavior and environment.
To compile this driver as a module, choose M here: the module will
be called amd_pmf.
config AMD_PMF_DEBUG
bool "PMF debug information"
depends on AMD_PMF
help
Enabling this option would give more debug information on the OEM fed
power setting values for each of the PMF feature. PMF driver gets this
information after evaluating a ACPI method and the information is stored
in the PMF config store.
Say Y here to enable more debug logs and Say N here if you are not sure.
@@ -0,0 +1,10 @@
# SPDX-License-Identifier: GPL-2.0
#
# Makefile for linux/drivers/platform/x86/amd/pmf
# AMD Platform Management Framework
#
obj-$(CONFIG_AMD_PMF) += amd-pmf.o
amd-pmf-y := core.o acpi.o sps.o \
auto-mode.o cnqf.o \
tee-if.o spc.o
@@ -0,0 +1,646 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/acpi.h>
#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/dev_printk.h>
#include "pmf.h"
#define APMF_CQL_NOTIFICATION 2
#define APMF_AMT_NOTIFICATION 3
static union acpi_object *apmf_if_call(struct amd_pmf_dev *pdev, int fn, struct acpi_buffer *param)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_handle ahandle = ACPI_HANDLE(pdev->dev);
struct acpi_object_list apmf_if_arg_list;
union acpi_object apmf_if_args[2];
acpi_status status;
apmf_if_arg_list.count = 2;
apmf_if_arg_list.pointer = &apmf_if_args[0];
apmf_if_args[0].type = ACPI_TYPE_INTEGER;
apmf_if_args[0].integer.value = fn;
if (param) {
apmf_if_args[1].type = ACPI_TYPE_BUFFER;
apmf_if_args[1].buffer.length = param->length;
apmf_if_args[1].buffer.pointer = param->pointer;
} else {
apmf_if_args[1].type = ACPI_TYPE_INTEGER;
apmf_if_args[1].integer.value = 0;
}
status = acpi_evaluate_object(ahandle, "APMF", &apmf_if_arg_list, &buffer);
if (ACPI_FAILURE(status)) {
dev_err(pdev->dev, "APMF method:%d call failed\n", fn);
kfree(buffer.pointer);
return NULL;
}
return buffer.pointer;
}
static int apmf_if_call_store_buffer(struct amd_pmf_dev *pdev, int fn, void *dest, size_t out_sz)
{
union acpi_object *info;
size_t size;
int err = 0;
info = apmf_if_call(pdev, fn, NULL);
if (!info)
return -EIO;
if (info->type != ACPI_TYPE_BUFFER) {
dev_err(pdev->dev, "object is not a buffer\n");
err = -EINVAL;
goto out;
}
if (info->buffer.length < 2) {
dev_err(pdev->dev, "buffer too small\n");
err = -EINVAL;
goto out;
}
size = *(u16 *)info->buffer.pointer;
if (info->buffer.length < size) {
dev_err(pdev->dev, "buffer smaller then headersize %u < %zu\n",
info->buffer.length, size);
err = -EINVAL;
goto out;
}
if (size < out_sz) {
dev_err(pdev->dev, "buffer too small %zu\n", size);
err = -EINVAL;
goto out;
}
memcpy(dest, info->buffer.pointer, out_sz);
out:
kfree(info);
return err;
}
static union acpi_object *apts_if_call(struct amd_pmf_dev *pdev, u32 state_index)
{
struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
acpi_handle ahandle = ACPI_HANDLE(pdev->dev);
struct acpi_object_list apts_if_arg_list;
union acpi_object apts_if_args[3];
acpi_status status;
apts_if_arg_list.count = 3;
apts_if_arg_list.pointer = &apts_if_args[0];
apts_if_args[0].type = ACPI_TYPE_INTEGER;
apts_if_args[0].integer.value = 1;
apts_if_args[1].type = ACPI_TYPE_INTEGER;
apts_if_args[1].integer.value = state_index;
apts_if_args[2].type = ACPI_TYPE_INTEGER;
apts_if_args[2].integer.value = 0;
status = acpi_evaluate_object(ahandle, "APTS", &apts_if_arg_list, &buffer);
if (ACPI_FAILURE(status)) {
dev_err(pdev->dev, "APTS state_idx:%u call failed\n", state_index);
kfree(buffer.pointer);
return NULL;
}
return buffer.pointer;
}
static int apts_if_call_store_buffer(struct amd_pmf_dev *pdev,
u32 index, void *data, size_t out_sz)
{
union acpi_object *info;
size_t size;
int err = 0;
info = apts_if_call(pdev, index);
if (!info)
return -EIO;
if (info->type != ACPI_TYPE_BUFFER) {
dev_err(pdev->dev, "object is not a buffer\n");
err = -EINVAL;
goto out;
}
size = *(u16 *)info->buffer.pointer;
if (info->buffer.length < size) {
dev_err(pdev->dev, "buffer smaller than header size %u < %zu\n",
info->buffer.length, size);
err = -EINVAL;
goto out;
}
if (size < out_sz) {
dev_err(pdev->dev, "buffer too small %zu\n", size);
err = -EINVAL;
goto out;
}
memcpy(data, info->buffer.pointer, out_sz);
out:
kfree(info);
return err;
}
int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index)
{
/* If bit-n is set, that indicates function n+1 is supported */
return !!(pdev->supported_func & BIT(index - 1));
}
int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev)
{
return !!(pdev->notifications & CUSTOM_BIOS_INPUT_BITS);
}
int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
struct amd_pmf_apts_granular_output *data, u32 apts_idx)
{
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
return -EINVAL;
return apts_if_call_store_buffer(pdev, apts_idx, data, sizeof(*data));
}
int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
struct apmf_static_slider_granular_output_v2 *data)
{
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
return -EINVAL;
return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
data, sizeof(*data));
}
int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
struct apmf_static_slider_granular_output *data)
{
if (!is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
return -EINVAL;
return apmf_if_call_store_buffer(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR,
data, sizeof(*data));
}
int apmf_os_power_slider_update(struct amd_pmf_dev *pdev, u8 event)
{
struct os_power_slider args;
struct acpi_buffer params;
union acpi_object *info;
args.size = sizeof(args);
args.slider_event = event;
params.length = sizeof(args);
params.pointer = (void *)&args;
info = apmf_if_call(pdev, APMF_FUNC_OS_POWER_SLIDER_UPDATE, &params);
if (!info)
return -EIO;
kfree(info);
return 0;
}
static void apmf_sbios_heartbeat_notify(struct work_struct *work)
{
struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, heart_beat.work);
union acpi_object *info;
dev_dbg(dev->dev, "Sending heartbeat to SBIOS\n");
info = apmf_if_call(dev, APMF_FUNC_SBIOS_HEARTBEAT, NULL);
if (!info)
return;
schedule_delayed_work(&dev->heart_beat, secs_to_jiffies(dev->hb_interval));
kfree(info);
}
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag)
{
struct sbios_hb_event_v2 args = { };
struct acpi_buffer params;
union acpi_object *info;
args.size = sizeof(args);
switch (flag) {
case ON_LOAD:
args.load = 1;
break;
case ON_UNLOAD:
args.unload = 1;
break;
case ON_SUSPEND:
args.suspend = 1;
break;
case ON_RESUME:
args.resume = 1;
break;
default:
dev_dbg(dev->dev, "Failed to send v2 heartbeat event, flag:0x%x\n", flag);
return -EINVAL;
}
params.length = sizeof(args);
params.pointer = &args;
info = apmf_if_call(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2, &params);
if (!info)
return -EIO;
kfree(info);
return 0;
}
int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx)
{
union acpi_object *info;
struct apmf_fan_idx args;
struct acpi_buffer params;
args.size = sizeof(args);
args.fan_ctl_mode = manual;
args.fan_ctl_idx = idx;
params.length = sizeof(args);
params.pointer = (void *)&args;
info = apmf_if_call(pdev, APMF_FUNC_SET_FAN_IDX, &params);
if (!info)
return -EIO;
kfree(info);
return 0;
}
static int apmf_notify_smart_pc_update(struct amd_pmf_dev *pdev, u32 val, u32 preq, u32 index)
{
struct amd_pmf_notify_smart_pc_update args;
struct acpi_buffer params;
union acpi_object *info;
args.size = sizeof(args);
args.pending_req = preq;
args.custom_bios[index] = val;
params.length = sizeof(args);
params.pointer = &args;
info = apmf_if_call(pdev, APMF_FUNC_NOTIFY_SMART_PC_UPDATES, &params);
if (!info)
return -EIO;
kfree(info);
dev_dbg(pdev->dev, "Notify smart pc update, val: %u\n", val);
return 0;
}
int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_AUTO_MODE, data, sizeof(*data));
}
int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
}
int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS, req, sizeof(*req));
}
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_SBIOS_REQUESTS,
req, sizeof(*req));
}
/* Store custom BIOS inputs data in ring buffer */
static void amd_pmf_custom_bios_inputs_rb(struct amd_pmf_dev *pmf_dev)
{
struct pmf_cbi_ring_buffer *rb = &pmf_dev->cbi_buf;
int i;
guard(mutex)(&pmf_dev->cbi_mutex);
switch (pmf_dev->cpu_id) {
case AMD_CPU_ID_PS:
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs_v1); i++)
rb->data[rb->head].val[i] = pmf_dev->req1.custom_policy[i];
rb->data[rb->head].preq = pmf_dev->req1.pending_req;
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
rb->data[rb->head].val[i] = pmf_dev->req.custom_policy[i];
rb->data[rb->head].preq = pmf_dev->req.pending_req;
break;
default:
return;
}
if (CIRC_SPACE(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0) {
/* Rare case: ensures the newest BIOS input value is kept */
dev_warn(pmf_dev->dev, "Overwriting BIOS input value, data may be lost\n");
rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}
rb->head = (rb->head + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}
static void amd_pmf_handle_early_preq(struct amd_pmf_dev *pdev)
{
if (!pdev->cb_flag)
return;
amd_pmf_invoke_cmd_enact(pdev);
pdev->cb_flag = false;
}
static void apmf_event_handler_v2(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
int ret;
guard(mutex)(&pmf_dev->cb_mutex);
ret = apmf_get_sbios_requests_v2(pmf_dev, &pmf_dev->req);
if (ret) {
dev_err(pmf_dev->dev, "Failed to get v2 SBIOS requests: %d\n", ret);
return;
}
dev_dbg(pmf_dev->dev, "Pending request (preq): 0x%x\n", pmf_dev->req.pending_req);
amd_pmf_handle_early_preq(pmf_dev);
amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
static void apmf_event_handler_v1(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
int ret;
guard(mutex)(&pmf_dev->cb_mutex);
ret = apmf_get_sbios_requests_v1(pmf_dev, &pmf_dev->req1);
if (ret) {
dev_err(pmf_dev->dev, "Failed to get v1 SBIOS requests: %d\n", ret);
return;
}
dev_dbg(pmf_dev->dev, "Pending request (preq1): 0x%x\n", pmf_dev->req1.pending_req);
amd_pmf_handle_early_preq(pmf_dev);
amd_pmf_custom_bios_inputs_rb(pmf_dev);
}
static void apmf_event_handler(acpi_handle handle, u32 event, void *data)
{
struct amd_pmf_dev *pmf_dev = data;
struct apmf_sbios_req req;
int ret;
guard(mutex)(&pmf_dev->update_mutex);
ret = apmf_get_sbios_requests(pmf_dev, &req);
if (ret) {
dev_err(pmf_dev->dev, "Failed to get SBIOS requests:%d\n", ret);
return;
}
if (req.pending_req & BIT(APMF_AMT_NOTIFICATION)) {
dev_dbg(pmf_dev->dev, "AMT is supported and notifications %s\n",
req.amt_event ? "Enabled" : "Disabled");
pmf_dev->amt_enabled = !!req.amt_event;
if (pmf_dev->amt_enabled)
amd_pmf_handle_amt(pmf_dev);
else
amd_pmf_reset_amt(pmf_dev);
}
if (req.pending_req & BIT(APMF_CQL_NOTIFICATION)) {
dev_dbg(pmf_dev->dev, "CQL is supported and notifications %s\n",
req.cql_event ? "Enabled" : "Disabled");
/* update the target mode information */
if (pmf_dev->amt_enabled)
amd_pmf_update_2_cql(pmf_dev, req.cql_event);
}
}
static int apmf_if_verify_interface(struct amd_pmf_dev *pdev)
{
struct apmf_verify_interface output;
int err;
err = apmf_if_call_store_buffer(pdev, APMF_FUNC_VERIFY_INTERFACE, &output, sizeof(output));
if (err)
return err;
/* only set if not already set by a quirk */
if (!pdev->supported_func)
pdev->supported_func = output.supported_functions;
dev_dbg(pdev->dev, "supported functions:0x%x notifications:0x%x version:%u\n",
output.supported_functions, output.notification_mask, output.version);
pdev->pmf_if_version = output.version;
pdev->notifications = output.notification_mask;
return 0;
}
static int apmf_get_system_params(struct amd_pmf_dev *dev)
{
struct apmf_system_params params;
int err;
if (!is_apmf_func_supported(dev, APMF_FUNC_GET_SYS_PARAMS))
return -EINVAL;
err = apmf_if_call_store_buffer(dev, APMF_FUNC_GET_SYS_PARAMS, &params, sizeof(params));
if (err)
return err;
dev_dbg(dev->dev, "system params mask:0x%x flags:0x%x cmd_code:0x%x heartbeat:%d\n",
params.valid_mask,
params.flags,
params.command_code,
params.heartbeat_int);
params.flags = params.flags & params.valid_mask;
dev->hb_interval = params.heartbeat_int;
return 0;
}
int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_AC, data, sizeof(*data));
}
int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data)
{
return apmf_if_call_store_buffer(pdev, APMF_FUNC_DYN_SLIDER_DC, data, sizeof(*data));
}
static apmf_event_handler_t apmf_event_handlers[] = {
[PMF_IF_V1] = apmf_event_handler_v1,
[PMF_IF_V2] = apmf_event_handler_v2,
};
int apmf_install_handler(struct amd_pmf_dev *pmf_dev)
{
acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
acpi_status status;
/* Install the APMF Notify handler */
if (is_apmf_func_supported(pmf_dev, APMF_FUNC_AUTO_MODE) &&
is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS)) {
status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY,
apmf_event_handler, pmf_dev);
if (ACPI_FAILURE(status)) {
dev_err(pmf_dev->dev, "failed to install notify handler\n");
return -ENODEV;
}
/* Call the handler once manually to catch up with possibly missed notifies. */
apmf_event_handler(ahandle, 0, pmf_dev);
}
if (!pmf_dev->smart_pc_enabled)
return -EINVAL;
switch (pmf_dev->pmf_if_version) {
case PMF_IF_V1:
if (!is_apmf_bios_input_notifications_supported(pmf_dev))
break;
fallthrough;
case PMF_IF_V2:
status = acpi_install_notify_handler(ahandle, ACPI_ALL_NOTIFY,
apmf_event_handlers[pmf_dev->pmf_if_version], pmf_dev);
if (ACPI_FAILURE(status)) {
dev_err(pmf_dev->dev,
"failed to install notify handler v%d for custom BIOS inputs\n",
pmf_dev->pmf_if_version);
return -ENODEV;
}
break;
default:
break;
}
return 0;
}
int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev)
{
struct platform_device *pdev = to_platform_device(pmf_dev->dev);
pmf_dev->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!pmf_dev->res) {
dev_dbg(pmf_dev->dev, "Failed to get I/O memory resource\n");
return -EINVAL;
}
pmf_dev->policy_addr = pmf_dev->res->start;
/*
* We cannot use resource_size() here because it adds an extra byte to round off the size.
* In the case of PMF ResourceTemplate(), this rounding is already handled within the _CRS.
* Using resource_size() would increase the resource size by 1, causing a mismatch with the
* length field and leading to issues. Therefore, simply use end-start of the ACPI resource
* to obtain the actual length.
*/
pmf_dev->policy_sz = pmf_dev->res->end - pmf_dev->res->start;
if (!pmf_dev->policy_addr || pmf_dev->policy_sz > POLICY_BUF_MAX_SZ ||
pmf_dev->policy_sz == 0) {
dev_err(pmf_dev->dev, "Incorrect policy params, possibly a SBIOS bug\n");
return -EINVAL;
}
return 0;
}
int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq, u32 idx)
{
if (!is_apmf_func_supported(dev, APMF_FUNC_NOTIFY_SMART_PC_UPDATES))
return -EINVAL;
return apmf_notify_smart_pc_update(dev, val, preq, idx);
}
void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev)
{
acpi_handle ahandle = ACPI_HANDLE(pmf_dev->dev);
if (pmf_dev->hb_interval && pmf_dev->pmf_if_version == PMF_IF_V1)
cancel_delayed_work_sync(&pmf_dev->heart_beat);
if (is_apmf_func_supported(pmf_dev, APMF_FUNC_AUTO_MODE) &&
is_apmf_func_supported(pmf_dev, APMF_FUNC_SBIOS_REQUESTS))
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY, apmf_event_handler);
if (!pmf_dev->smart_pc_enabled)
return;
switch (pmf_dev->pmf_if_version) {
case PMF_IF_V1:
if (!is_apmf_bios_input_notifications_supported(pmf_dev))
break;
fallthrough;
case PMF_IF_V2:
acpi_remove_notify_handler(ahandle, ACPI_ALL_NOTIFY,
apmf_event_handlers[pmf_dev->pmf_if_version]);
break;
default:
break;
}
}
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev)
{
int ret;
ret = apmf_if_verify_interface(pmf_dev);
if (ret) {
dev_err(pmf_dev->dev, "APMF verify interface failed :%d\n", ret);
goto out;
}
ret = apmf_get_system_params(pmf_dev);
if (ret) {
dev_dbg(pmf_dev->dev, "APMF apmf_get_system_params failed :%d\n", ret);
goto out;
}
if (pmf_dev->hb_interval && pmf_dev->pmf_if_version == PMF_IF_V1) {
/* send heartbeats only if the interval is not zero */
INIT_DELAYED_WORK(&pmf_dev->heart_beat, apmf_sbios_heartbeat_notify);
schedule_delayed_work(&pmf_dev->heart_beat, 0);
}
out:
return ret;
}
@@ -0,0 +1,441 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/acpi.h>
#include <linux/workqueue.h>
#include "pmf.h"
static struct auto_mode_mode_config config_store;
static const char *state_as_str(unsigned int state);
#ifdef CONFIG_AMD_PMF_DEBUG
static void amd_pmf_dump_auto_mode_defaults(struct auto_mode_mode_config *data)
{
struct auto_mode_mode_settings *its_mode;
pr_debug("Auto Mode Data - BEGIN\n");
/* time constant */
pr_debug("balanced_to_perf: %u ms\n",
data->transition[AUTO_TRANSITION_TO_PERFORMANCE].time_constant);
pr_debug("perf_to_balanced: %u ms\n",
data->transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].time_constant);
pr_debug("quiet_to_balanced: %u ms\n",
data->transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].time_constant);
pr_debug("balanced_to_quiet: %u ms\n",
data->transition[AUTO_TRANSITION_TO_QUIET].time_constant);
/* power floor */
pr_debug("pfloor_perf: %u mW\n", data->mode_set[AUTO_PERFORMANCE].power_floor);
pr_debug("pfloor_balanced: %u mW\n", data->mode_set[AUTO_BALANCE].power_floor);
pr_debug("pfloor_quiet: %u mW\n", data->mode_set[AUTO_QUIET].power_floor);
/* Power delta for mode change */
pr_debug("pd_balanced_to_perf: %u mW\n",
data->transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta);
pr_debug("pd_perf_to_balanced: %u mW\n",
data->transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta);
pr_debug("pd_quiet_to_balanced: %u mW\n",
data->transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta);
pr_debug("pd_balanced_to_quiet: %u mW\n",
data->transition[AUTO_TRANSITION_TO_QUIET].power_delta);
/* skin temperature limits */
its_mode = &data->mode_set[AUTO_PERFORMANCE_ON_LAP];
pr_debug("stt_apu_perf_on_lap: %u C\n",
its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_perf_on_lap: %u C\n",
its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_perf_on_lap: %u mW\n", its_mode->power_control.stt_min);
its_mode = &data->mode_set[AUTO_PERFORMANCE];
pr_debug("stt_apu_perf: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_perf: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_perf: %u mW\n", its_mode->power_control.stt_min);
its_mode = &data->mode_set[AUTO_BALANCE];
pr_debug("stt_apu_balanced: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_balanced: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_balanced: %u mW\n", its_mode->power_control.stt_min);
its_mode = &data->mode_set[AUTO_QUIET];
pr_debug("stt_apu_quiet: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
pr_debug("stt_hs2_quiet: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
pr_debug("stt_min_limit_quiet: %u mW\n", its_mode->power_control.stt_min);
/* SPL based power limits */
its_mode = &data->mode_set[AUTO_PERFORMANCE_ON_LAP];
pr_debug("fppt_perf_on_lap: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_perf_on_lap: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_perf_on_lap: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_perf_on_lap: %u mW\n", its_mode->power_control.sppt_apu_only);
its_mode = &data->mode_set[AUTO_PERFORMANCE];
pr_debug("fppt_perf: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_perf: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_perf: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_perf: %u mW\n", its_mode->power_control.sppt_apu_only);
its_mode = &data->mode_set[AUTO_BALANCE];
pr_debug("fppt_balanced: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_balanced: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_balanced: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_balanced: %u mW\n", its_mode->power_control.sppt_apu_only);
its_mode = &data->mode_set[AUTO_QUIET];
pr_debug("fppt_quiet: %u mW\n", its_mode->power_control.fppt);
pr_debug("sppt_quiet: %u mW\n", its_mode->power_control.sppt);
pr_debug("spl_quiet: %u mW\n", its_mode->power_control.spl);
pr_debug("sppt_apu_only_quiet: %u mW\n", its_mode->power_control.sppt_apu_only);
/* Fan ID */
pr_debug("fan_id_perf: %lu\n",
data->mode_set[AUTO_PERFORMANCE].fan_control.fan_id);
pr_debug("fan_id_balanced: %lu\n",
data->mode_set[AUTO_BALANCE].fan_control.fan_id);
pr_debug("fan_id_quiet: %lu\n",
data->mode_set[AUTO_QUIET].fan_control.fan_id);
pr_debug("Auto Mode Data - END\n");
}
#else
static void amd_pmf_dump_auto_mode_defaults(struct auto_mode_mode_config *data) {}
#endif
static void amd_pmf_set_automode(struct amd_pmf_dev *dev, int idx,
struct auto_mode_mode_config *table)
{
struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, pwr_ctrl->spl, NULL);
amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, pwr_ctrl->fppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, pwr_ctrl->sppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, pwr_ctrl->sppt_apu_only, NULL);
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, pwr_ctrl->stt_min, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_APU]), NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_HS2]), NULL);
if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
apmf_update_fan_idx(dev, config_store.mode_set[idx].fan_control.manual,
config_store.mode_set[idx].fan_control.fan_id);
}
static int amd_pmf_get_moving_avg(struct amd_pmf_dev *pdev, int socket_power)
{
int i, total = 0;
if (pdev->socket_power_history_idx == -1) {
for (i = 0; i < AVG_SAMPLE_SIZE; i++)
pdev->socket_power_history[i] = socket_power;
}
pdev->socket_power_history_idx = (pdev->socket_power_history_idx + 1) % AVG_SAMPLE_SIZE;
pdev->socket_power_history[pdev->socket_power_history_idx] = socket_power;
for (i = 0; i < AVG_SAMPLE_SIZE; i++)
total += pdev->socket_power_history[i];
return total / AVG_SAMPLE_SIZE;
}
void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms)
{
int avg_power = 0;
bool update = false;
int i, j;
/* Get the average moving average computed by auto mode algorithm */
avg_power = amd_pmf_get_moving_avg(dev, socket_power);
for (i = 0; i < AUTO_TRANSITION_MAX; i++) {
if ((config_store.transition[i].shifting_up && avg_power >=
config_store.transition[i].power_threshold) ||
(!config_store.transition[i].shifting_up && avg_power <=
config_store.transition[i].power_threshold)) {
if (config_store.transition[i].timer <
config_store.transition[i].time_constant)
config_store.transition[i].timer += time_elapsed_ms;
} else {
config_store.transition[i].timer = 0;
}
if (config_store.transition[i].timer >=
config_store.transition[i].time_constant &&
!config_store.transition[i].applied) {
config_store.transition[i].applied = true;
update = true;
} else if (config_store.transition[i].timer <=
config_store.transition[i].time_constant &&
config_store.transition[i].applied) {
config_store.transition[i].applied = false;
update = true;
}
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "[AUTO MODE] average_power : %d mW mode: %s\n", avg_power,
state_as_str(config_store.current_mode));
dev_dbg(dev->dev, "[AUTO MODE] time: %lld ms timer: %u ms tc: %u ms\n",
time_elapsed_ms, config_store.transition[i].timer,
config_store.transition[i].time_constant);
dev_dbg(dev->dev, "[AUTO MODE] shiftup: %u pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[i].shifting_up,
config_store.transition[i].power_threshold,
config_store.mode_set[i].power_floor,
config_store.transition[i].power_delta);
#endif
}
dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
state_as_str(config_store.current_mode));
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "[AUTO MODE] priority1: %u priority2: %u priority3: %u priority4: %u\n",
config_store.transition[0].applied,
config_store.transition[1].applied,
config_store.transition[2].applied,
config_store.transition[3].applied);
#endif
if (update) {
for (j = 0; j < AUTO_TRANSITION_MAX; j++) {
/* Apply the mode with highest priority indentified */
if (config_store.transition[j].applied) {
if (config_store.current_mode !=
config_store.transition[j].target_mode) {
config_store.current_mode =
config_store.transition[j].target_mode;
dev_dbg(dev->dev, "[AUTO_MODE] moving to mode:%s\n",
state_as_str(config_store.current_mode));
amd_pmf_set_automode(dev, config_store.current_mode, NULL);
}
break;
}
}
}
}
void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event)
{
int mode = config_store.current_mode;
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode =
is_cql_event ? AUTO_PERFORMANCE_ON_LAP : AUTO_PERFORMANCE;
if ((mode == AUTO_PERFORMANCE || mode == AUTO_PERFORMANCE_ON_LAP) &&
mode != config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode) {
mode = config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode;
amd_pmf_set_automode(dev, mode, NULL);
}
dev_dbg(dev->dev, "updated CQL thermals\n");
}
static void amd_pmf_get_power_threshold(void)
{
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_threshold =
config_store.mode_set[AUTO_BALANCE].power_floor -
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta;
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_threshold =
config_store.mode_set[AUTO_BALANCE].power_floor -
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta;
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_threshold =
config_store.mode_set[AUTO_QUIET].power_floor -
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta;
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_threshold =
config_store.mode_set[AUTO_PERFORMANCE].power_floor -
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta;
#ifdef CONFIG_AMD_PMF_DEBUG
pr_debug("[AUTO MODE TO_QUIET] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_threshold,
config_store.mode_set[AUTO_BALANCE].power_floor,
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta);
pr_debug("[AUTO MODE TO_PERFORMANCE] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_threshold,
config_store.mode_set[AUTO_BALANCE].power_floor,
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta);
pr_debug("[AUTO MODE QUIET_TO_BALANCE] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE]
.power_threshold,
config_store.mode_set[AUTO_QUIET].power_floor,
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta);
pr_debug("[AUTO MODE PERFORMANCE_TO_BALANCE] pt: %u mW pf: %u mW pd: %u mW\n",
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE]
.power_threshold,
config_store.mode_set[AUTO_PERFORMANCE].power_floor,
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta);
#endif
}
static const char *state_as_str(unsigned int state)
{
switch (state) {
case AUTO_QUIET:
return "QUIET";
case AUTO_BALANCE:
return "BALANCED";
case AUTO_PERFORMANCE_ON_LAP:
return "ON_LAP";
case AUTO_PERFORMANCE:
return "PERFORMANCE";
default:
return "Unknown Auto Mode State";
}
}
static void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev)
{
struct apmf_auto_mode output;
struct power_table_control *pwr_ctrl;
int i;
apmf_get_auto_mode_def(dev, &output);
/* time constant */
config_store.transition[AUTO_TRANSITION_TO_QUIET].time_constant =
output.balanced_to_quiet;
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].time_constant =
output.balanced_to_perf;
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].time_constant =
output.quiet_to_balanced;
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].time_constant =
output.perf_to_balanced;
/* power floor */
config_store.mode_set[AUTO_QUIET].power_floor = output.pfloor_quiet;
config_store.mode_set[AUTO_BALANCE].power_floor = output.pfloor_balanced;
config_store.mode_set[AUTO_PERFORMANCE].power_floor = output.pfloor_perf;
config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].power_floor = output.pfloor_perf;
/* Power delta for mode change */
config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta =
output.pd_balanced_to_quiet;
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta =
output.pd_balanced_to_perf;
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta =
output.pd_quiet_to_balanced;
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta =
output.pd_perf_to_balanced;
/* Power threshold */
amd_pmf_get_power_threshold();
/* skin temperature limits */
pwr_ctrl = &config_store.mode_set[AUTO_QUIET].power_control;
pwr_ctrl->spl = output.spl_quiet;
pwr_ctrl->sppt = output.sppt_quiet;
pwr_ctrl->fppt = output.fppt_quiet;
pwr_ctrl->sppt_apu_only = output.sppt_apu_only_quiet;
pwr_ctrl->stt_min = output.stt_min_limit_quiet;
pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_quiet;
pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_quiet;
pwr_ctrl = &config_store.mode_set[AUTO_BALANCE].power_control;
pwr_ctrl->spl = output.spl_balanced;
pwr_ctrl->sppt = output.sppt_balanced;
pwr_ctrl->fppt = output.fppt_balanced;
pwr_ctrl->sppt_apu_only = output.sppt_apu_only_balanced;
pwr_ctrl->stt_min = output.stt_min_limit_balanced;
pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_balanced;
pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_balanced;
pwr_ctrl = &config_store.mode_set[AUTO_PERFORMANCE].power_control;
pwr_ctrl->spl = output.spl_perf;
pwr_ctrl->sppt = output.sppt_perf;
pwr_ctrl->fppt = output.fppt_perf;
pwr_ctrl->sppt_apu_only = output.sppt_apu_only_perf;
pwr_ctrl->stt_min = output.stt_min_limit_perf;
pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_perf;
pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_perf;
pwr_ctrl = &config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].power_control;
pwr_ctrl->spl = output.spl_perf_on_lap;
pwr_ctrl->sppt = output.sppt_perf_on_lap;
pwr_ctrl->fppt = output.fppt_perf_on_lap;
pwr_ctrl->sppt_apu_only = output.sppt_apu_only_perf_on_lap;
pwr_ctrl->stt_min = output.stt_min_limit_perf_on_lap;
pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_perf_on_lap;
pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_perf_on_lap;
/* Fan ID */
config_store.mode_set[AUTO_QUIET].fan_control.fan_id = output.fan_id_quiet;
config_store.mode_set[AUTO_BALANCE].fan_control.fan_id = output.fan_id_balanced;
config_store.mode_set[AUTO_PERFORMANCE].fan_control.fan_id = output.fan_id_perf;
config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].fan_control.fan_id =
output.fan_id_perf;
config_store.transition[AUTO_TRANSITION_TO_QUIET].target_mode = AUTO_QUIET;
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode =
AUTO_PERFORMANCE;
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].target_mode =
AUTO_BALANCE;
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].target_mode =
AUTO_BALANCE;
config_store.transition[AUTO_TRANSITION_TO_QUIET].shifting_up = false;
config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].shifting_up = true;
config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].shifting_up = true;
config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].shifting_up =
false;
for (i = 0 ; i < AUTO_MODE_MAX ; i++) {
if (config_store.mode_set[i].fan_control.fan_id == FAN_INDEX_AUTO)
config_store.mode_set[i].fan_control.manual = false;
else
config_store.mode_set[i].fan_control.manual = true;
}
/* set to initial default values */
config_store.current_mode = AUTO_BALANCE;
dev->socket_power_history_idx = -1;
amd_pmf_dump_auto_mode_defaults(&config_store);
}
int amd_pmf_reset_amt(struct amd_pmf_dev *dev)
{
/*
* OEM BIOS implementation guide says that if the auto mode is enabled
* the platform_profile registration shall be done by the OEM driver.
* There could be cases where both static slider and auto mode BIOS
* functions are enabled, in that case enable static slider updates
* only if it advertised as supported.
*/
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
dev_dbg(dev->dev, "resetting AMT thermals\n");
amd_pmf_set_sps_power_limits(dev);
}
return 0;
}
void amd_pmf_handle_amt(struct amd_pmf_dev *dev)
{
amd_pmf_set_automode(dev, config_store.current_mode, NULL);
}
void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev)
{
cancel_delayed_work_sync(&dev->work_buffer);
}
void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev)
{
amd_pmf_load_defaults_auto_mode(dev);
amd_pmf_init_metrics_table(dev);
}
@@ -0,0 +1,467 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/string_choices.h>
#include <linux/workqueue.h>
#include "pmf.h"
static struct cnqf_config config_store;
#ifdef CONFIG_AMD_PMF_DEBUG
static const char *state_as_str_cnqf(unsigned int state)
{
switch (state) {
case APMF_CNQF_TURBO:
return "turbo";
case APMF_CNQF_PERFORMANCE:
return "performance";
case APMF_CNQF_BALANCE:
return "balance";
case APMF_CNQF_QUIET:
return "quiet";
default:
return "Unknown CnQF State";
}
}
static void amd_pmf_cnqf_dump_defaults(struct apmf_dyn_slider_output *data, int idx)
{
int i;
pr_debug("Dynamic Slider %s Defaults - BEGIN\n", idx ? "DC" : "AC");
pr_debug("size: %u\n", data->size);
pr_debug("flags: 0x%x\n", data->flags);
/* Time constants */
pr_debug("t_perf_to_turbo: %u ms\n", data->t_perf_to_turbo);
pr_debug("t_balanced_to_perf: %u ms\n", data->t_balanced_to_perf);
pr_debug("t_quiet_to_balanced: %u ms\n", data->t_quiet_to_balanced);
pr_debug("t_balanced_to_quiet: %u ms\n", data->t_balanced_to_quiet);
pr_debug("t_perf_to_balanced: %u ms\n", data->t_perf_to_balanced);
pr_debug("t_turbo_to_perf: %u ms\n", data->t_turbo_to_perf);
for (i = 0 ; i < CNQF_MODE_MAX ; i++) {
pr_debug("pfloor_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].pfloor);
pr_debug("fppt_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].fppt);
pr_debug("sppt_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].sppt);
pr_debug("sppt_apuonly_%s: %u mW\n",
state_as_str_cnqf(i), data->ps[i].sppt_apu_only);
pr_debug("spl_%s: %u mW\n", state_as_str_cnqf(i), data->ps[i].spl);
pr_debug("stt_minlimit_%s: %u mW\n",
state_as_str_cnqf(i), data->ps[i].stt_min_limit);
pr_debug("stt_skintemp_apu_%s: %u C\n", state_as_str_cnqf(i),
data->ps[i].stt_skintemp[STT_TEMP_APU]);
pr_debug("stt_skintemp_hs2_%s: %u C\n", state_as_str_cnqf(i),
data->ps[i].stt_skintemp[STT_TEMP_HS2]);
pr_debug("fan_id_%s: %u\n", state_as_str_cnqf(i), data->ps[i].fan_id);
}
pr_debug("Dynamic Slider %s Defaults - END\n", idx ? "DC" : "AC");
}
#else
static void amd_pmf_cnqf_dump_defaults(struct apmf_dyn_slider_output *data, int idx) {}
#endif
static int amd_pmf_set_cnqf(struct amd_pmf_dev *dev, int src, int idx,
struct cnqf_config *table)
{
struct power_table_control *pc;
pc = &config_store.mode_set[src][idx].power_control;
amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, pc->spl, NULL);
amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, pc->fppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, pc->sppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, pc->sppt_apu_only, NULL);
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, pc->stt_min, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
fixp_q88_fromint(pc->stt_skin_temp[STT_TEMP_APU]), NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
fixp_q88_fromint(pc->stt_skin_temp[STT_TEMP_HS2]), NULL);
if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
apmf_update_fan_idx(dev,
config_store.mode_set[src][idx].fan_control.manual,
config_store.mode_set[src][idx].fan_control.fan_id);
return 0;
}
static void amd_pmf_update_power_threshold(int src)
{
struct cnqf_mode_settings *ts;
struct cnqf_tran_params *tp;
tp = &config_store.trans_param[src][CNQF_TRANSITION_TO_QUIET];
ts = &config_store.mode_set[src][CNQF_MODE_BALANCE];
tp->power_threshold = ts->power_floor;
tp = &config_store.trans_param[src][CNQF_TRANSITION_TO_TURBO];
ts = &config_store.mode_set[src][CNQF_MODE_PERFORMANCE];
tp->power_threshold = ts->power_floor;
tp = &config_store.trans_param[src][CNQF_TRANSITION_FROM_BALANCE_TO_PERFORMANCE];
ts = &config_store.mode_set[src][CNQF_MODE_BALANCE];
tp->power_threshold = ts->power_floor;
tp = &config_store.trans_param[src][CNQF_TRANSITION_FROM_PERFORMANCE_TO_BALANCE];
ts = &config_store.mode_set[src][CNQF_MODE_PERFORMANCE];
tp->power_threshold = ts->power_floor;
tp = &config_store.trans_param[src][CNQF_TRANSITION_FROM_QUIET_TO_BALANCE];
ts = &config_store.mode_set[src][CNQF_MODE_QUIET];
tp->power_threshold = ts->power_floor;
tp = &config_store.trans_param[src][CNQF_TRANSITION_FROM_TURBO_TO_PERFORMANCE];
ts = &config_store.mode_set[src][CNQF_MODE_TURBO];
tp->power_threshold = ts->power_floor;
}
static const char *state_as_str(unsigned int state)
{
switch (state) {
case CNQF_MODE_QUIET:
return "QUIET";
case CNQF_MODE_BALANCE:
return "BALANCED";
case CNQF_MODE_TURBO:
return "TURBO";
case CNQF_MODE_PERFORMANCE:
return "PERFORMANCE";
default:
return "Unknown CnQF mode";
}
}
static int amd_pmf_cnqf_get_power_source(struct amd_pmf_dev *dev)
{
if (is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_AC) &&
is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_DC))
return amd_pmf_get_power_source();
else if (is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_DC))
return POWER_SOURCE_DC;
else
return POWER_SOURCE_AC;
}
int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_lapsed_ms)
{
struct cnqf_tran_params *tp;
int src, i, j;
u32 avg_power = 0;
src = amd_pmf_cnqf_get_power_source(dev);
if (is_pprof_balanced(dev)) {
amd_pmf_set_cnqf(dev, src, config_store.current_mode, NULL);
} else {
/*
* Return from here if the platform_profile is not balanced
* so that preference is given to user mode selection, rather
* than enforcing CnQF to run all the time (if enabled)
*/
return -EINVAL;
}
for (i = 0; i < CNQF_TRANSITION_MAX; i++) {
config_store.trans_param[src][i].timer += time_lapsed_ms;
config_store.trans_param[src][i].total_power += socket_power;
config_store.trans_param[src][i].count++;
tp = &config_store.trans_param[src][i];
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "avg_power: %u mW total_power: %u mW count: %u timer: %u ms\n",
avg_power, config_store.trans_param[src][i].total_power,
config_store.trans_param[src][i].count,
config_store.trans_param[src][i].timer);
#endif
if (tp->timer >= tp->time_constant && tp->count) {
avg_power = tp->total_power / tp->count;
/* Reset the indices */
tp->timer = 0;
tp->total_power = 0;
tp->count = 0;
if ((tp->shifting_up && avg_power >= tp->power_threshold) ||
(!tp->shifting_up && avg_power <= tp->power_threshold)) {
tp->priority = true;
} else {
tp->priority = false;
}
}
}
dev_dbg(dev->dev, "[CNQF] Avg power: %u mW socket power: %u mW mode:%s\n",
avg_power, socket_power, state_as_str(config_store.current_mode));
#ifdef CONFIG_AMD_PMF_DEBUG
dev_dbg(dev->dev, "[CNQF] priority1: %u priority2: %u priority3: %u\n",
config_store.trans_param[src][0].priority,
config_store.trans_param[src][1].priority,
config_store.trans_param[src][2].priority);
dev_dbg(dev->dev, "[CNQF] priority4: %u priority5: %u priority6: %u\n",
config_store.trans_param[src][3].priority,
config_store.trans_param[src][4].priority,
config_store.trans_param[src][5].priority);
#endif
for (j = 0; j < CNQF_TRANSITION_MAX; j++) {
/* apply the highest priority */
if (config_store.trans_param[src][j].priority) {
if (config_store.current_mode !=
config_store.trans_param[src][j].target_mode) {
config_store.current_mode =
config_store.trans_param[src][j].target_mode;
dev_dbg(dev->dev, "Moving to Mode :%s\n",
state_as_str(config_store.current_mode));
amd_pmf_set_cnqf(dev, src,
config_store.current_mode, NULL);
}
break;
}
}
return 0;
}
static void amd_pmf_update_trans_data(int idx, struct apmf_dyn_slider_output *out)
{
struct cnqf_tran_params *tp;
tp = &config_store.trans_param[idx][CNQF_TRANSITION_TO_QUIET];
tp->time_constant = out->t_balanced_to_quiet;
tp->target_mode = CNQF_MODE_QUIET;
tp->shifting_up = false;
tp = &config_store.trans_param[idx][CNQF_TRANSITION_FROM_BALANCE_TO_PERFORMANCE];
tp->time_constant = out->t_balanced_to_perf;
tp->target_mode = CNQF_MODE_PERFORMANCE;
tp->shifting_up = true;
tp = &config_store.trans_param[idx][CNQF_TRANSITION_FROM_QUIET_TO_BALANCE];
tp->time_constant = out->t_quiet_to_balanced;
tp->target_mode = CNQF_MODE_BALANCE;
tp->shifting_up = true;
tp = &config_store.trans_param[idx][CNQF_TRANSITION_FROM_PERFORMANCE_TO_BALANCE];
tp->time_constant = out->t_perf_to_balanced;
tp->target_mode = CNQF_MODE_BALANCE;
tp->shifting_up = false;
tp = &config_store.trans_param[idx][CNQF_TRANSITION_FROM_TURBO_TO_PERFORMANCE];
tp->time_constant = out->t_turbo_to_perf;
tp->target_mode = CNQF_MODE_PERFORMANCE;
tp->shifting_up = false;
tp = &config_store.trans_param[idx][CNQF_TRANSITION_TO_TURBO];
tp->time_constant = out->t_perf_to_turbo;
tp->target_mode = CNQF_MODE_TURBO;
tp->shifting_up = true;
}
static void amd_pmf_update_mode_set(int idx, struct apmf_dyn_slider_output *out)
{
struct cnqf_mode_settings *ms;
/* Quiet Mode */
ms = &config_store.mode_set[idx][CNQF_MODE_QUIET];
ms->power_floor = out->ps[APMF_CNQF_QUIET].pfloor;
ms->power_control.fppt = out->ps[APMF_CNQF_QUIET].fppt;
ms->power_control.sppt = out->ps[APMF_CNQF_QUIET].sppt;
ms->power_control.sppt_apu_only = out->ps[APMF_CNQF_QUIET].sppt_apu_only;
ms->power_control.spl = out->ps[APMF_CNQF_QUIET].spl;
ms->power_control.stt_min = out->ps[APMF_CNQF_QUIET].stt_min_limit;
ms->power_control.stt_skin_temp[STT_TEMP_APU] =
out->ps[APMF_CNQF_QUIET].stt_skintemp[STT_TEMP_APU];
ms->power_control.stt_skin_temp[STT_TEMP_HS2] =
out->ps[APMF_CNQF_QUIET].stt_skintemp[STT_TEMP_HS2];
ms->fan_control.fan_id = out->ps[APMF_CNQF_QUIET].fan_id;
/* Balance Mode */
ms = &config_store.mode_set[idx][CNQF_MODE_BALANCE];
ms->power_floor = out->ps[APMF_CNQF_BALANCE].pfloor;
ms->power_control.fppt = out->ps[APMF_CNQF_BALANCE].fppt;
ms->power_control.sppt = out->ps[APMF_CNQF_BALANCE].sppt;
ms->power_control.sppt_apu_only = out->ps[APMF_CNQF_BALANCE].sppt_apu_only;
ms->power_control.spl = out->ps[APMF_CNQF_BALANCE].spl;
ms->power_control.stt_min = out->ps[APMF_CNQF_BALANCE].stt_min_limit;
ms->power_control.stt_skin_temp[STT_TEMP_APU] =
out->ps[APMF_CNQF_BALANCE].stt_skintemp[STT_TEMP_APU];
ms->power_control.stt_skin_temp[STT_TEMP_HS2] =
out->ps[APMF_CNQF_BALANCE].stt_skintemp[STT_TEMP_HS2];
ms->fan_control.fan_id = out->ps[APMF_CNQF_BALANCE].fan_id;
/* Performance Mode */
ms = &config_store.mode_set[idx][CNQF_MODE_PERFORMANCE];
ms->power_floor = out->ps[APMF_CNQF_PERFORMANCE].pfloor;
ms->power_control.fppt = out->ps[APMF_CNQF_PERFORMANCE].fppt;
ms->power_control.sppt = out->ps[APMF_CNQF_PERFORMANCE].sppt;
ms->power_control.sppt_apu_only = out->ps[APMF_CNQF_PERFORMANCE].sppt_apu_only;
ms->power_control.spl = out->ps[APMF_CNQF_PERFORMANCE].spl;
ms->power_control.stt_min = out->ps[APMF_CNQF_PERFORMANCE].stt_min_limit;
ms->power_control.stt_skin_temp[STT_TEMP_APU] =
out->ps[APMF_CNQF_PERFORMANCE].stt_skintemp[STT_TEMP_APU];
ms->power_control.stt_skin_temp[STT_TEMP_HS2] =
out->ps[APMF_CNQF_PERFORMANCE].stt_skintemp[STT_TEMP_HS2];
ms->fan_control.fan_id = out->ps[APMF_CNQF_PERFORMANCE].fan_id;
/* Turbo Mode */
ms = &config_store.mode_set[idx][CNQF_MODE_TURBO];
ms->power_floor = out->ps[APMF_CNQF_TURBO].pfloor;
ms->power_control.fppt = out->ps[APMF_CNQF_TURBO].fppt;
ms->power_control.sppt = out->ps[APMF_CNQF_TURBO].sppt;
ms->power_control.sppt_apu_only = out->ps[APMF_CNQF_TURBO].sppt_apu_only;
ms->power_control.spl = out->ps[APMF_CNQF_TURBO].spl;
ms->power_control.stt_min = out->ps[APMF_CNQF_TURBO].stt_min_limit;
ms->power_control.stt_skin_temp[STT_TEMP_APU] =
out->ps[APMF_CNQF_TURBO].stt_skintemp[STT_TEMP_APU];
ms->power_control.stt_skin_temp[STT_TEMP_HS2] =
out->ps[APMF_CNQF_TURBO].stt_skintemp[STT_TEMP_HS2];
ms->fan_control.fan_id = out->ps[APMF_CNQF_TURBO].fan_id;
}
static int amd_pmf_check_flags(struct amd_pmf_dev *dev)
{
struct apmf_dyn_slider_output out = {};
if (is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_AC))
apmf_get_dyn_slider_def_ac(dev, &out);
else if (is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_DC))
apmf_get_dyn_slider_def_dc(dev, &out);
return out.flags;
}
static int amd_pmf_load_defaults_cnqf(struct amd_pmf_dev *dev)
{
struct apmf_dyn_slider_output out;
int i, j, ret;
for (i = 0; i < POWER_SOURCE_MAX; i++) {
if (!is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_AC + i))
continue;
if (i == POWER_SOURCE_AC)
ret = apmf_get_dyn_slider_def_ac(dev, &out);
else
ret = apmf_get_dyn_slider_def_dc(dev, &out);
if (ret) {
dev_err(dev->dev, "APMF apmf_get_dyn_slider_def_dc failed :%d\n", ret);
return ret;
}
amd_pmf_cnqf_dump_defaults(&out, i);
amd_pmf_update_mode_set(i, &out);
amd_pmf_update_trans_data(i, &out);
amd_pmf_update_power_threshold(i);
for (j = 0; j < CNQF_MODE_MAX; j++) {
if (config_store.mode_set[i][j].fan_control.fan_id == FAN_INDEX_AUTO)
config_store.mode_set[i][j].fan_control.manual = false;
else
config_store.mode_set[i][j].fan_control.manual = true;
}
}
/* set to initial default values */
config_store.current_mode = CNQF_MODE_BALANCE;
return 0;
}
static ssize_t cnqf_enable_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
int result, src;
bool input;
result = kstrtobool(buf, &input);
if (result)
return result;
src = amd_pmf_cnqf_get_power_source(pdev);
pdev->cnqf_enabled = input;
if (pdev->cnqf_enabled && is_pprof_balanced(pdev)) {
amd_pmf_set_cnqf(pdev, src, config_store.current_mode, NULL);
} else {
if (is_apmf_func_supported(pdev, APMF_FUNC_STATIC_SLIDER_GRANULAR))
amd_pmf_set_sps_power_limits(pdev);
}
dev_dbg(pdev->dev, "Received CnQF %s\n", str_on_off(input));
return count;
}
static ssize_t cnqf_enable_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
return sysfs_emit(buf, "%s\n", str_on_off(pdev->cnqf_enabled));
}
static DEVICE_ATTR_RW(cnqf_enable);
static umode_t cnqf_feature_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
struct device *dev = kobj_to_dev(kobj);
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
return pdev->cnqf_supported ? attr->mode : 0;
}
static struct attribute *cnqf_feature_attrs[] = {
&dev_attr_cnqf_enable.attr,
NULL
};
const struct attribute_group cnqf_feature_attribute_group = {
.is_visible = cnqf_feature_is_visible,
.attrs = cnqf_feature_attrs,
};
void amd_pmf_deinit_cnqf(struct amd_pmf_dev *dev)
{
cancel_delayed_work_sync(&dev->work_buffer);
}
int amd_pmf_init_cnqf(struct amd_pmf_dev *dev)
{
int ret, src;
/*
* Note the caller of this function has already checked that both
* APMF_FUNC_DYN_SLIDER_AC and APMF_FUNC_DYN_SLIDER_DC are supported.
*/
ret = amd_pmf_load_defaults_cnqf(dev);
if (ret < 0)
return ret;
amd_pmf_init_metrics_table(dev);
dev->cnqf_supported = true;
dev->cnqf_enabled = amd_pmf_check_flags(dev);
/* update the thermal for CnQF */
if (dev->cnqf_enabled && is_pprof_balanced(dev)) {
src = amd_pmf_cnqf_get_power_source(dev);
amd_pmf_set_cnqf(dev, src, config_store.current_mode, NULL);
}
return 0;
}
@@ -0,0 +1,672 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/array_size.h>
#include <linux/cleanup.h>
#include <linux/debugfs.h>
#include <linux/iopoll.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/string.h>
#include <asm/amd/node.h>
#include "pmf.h"
/* PMF-SMU communication registers */
#define AMD_PMF_REGISTER_MESSAGE 0xA18
#define AMD_PMF_REGISTER_RESPONSE 0xA78
#define AMD_PMF_REGISTER_ARGUMENT 0xA58
/* Base address of SMU for mapping physical address to virtual address */
#define AMD_PMF_MAPPING_SIZE 0x01000
#define AMD_PMF_BASE_ADDR_OFFSET 0x10000
#define AMD_PMF_BASE_ADDR_LO 0x13B102E8
#define AMD_PMF_BASE_ADDR_HI 0x13B102EC
#define AMD_PMF_BASE_ADDR_LO_MASK GENMASK(15, 0)
#define AMD_PMF_BASE_ADDR_HI_MASK GENMASK(31, 20)
/* SMU Response Codes */
#define AMD_PMF_RESULT_OK 0x01
#define AMD_PMF_RESULT_CMD_REJECT_BUSY 0xFC
#define AMD_PMF_RESULT_CMD_REJECT_PREREQ 0xFD
#define AMD_PMF_RESULT_CMD_UNKNOWN 0xFE
#define AMD_PMF_RESULT_FAILED 0xFF
#define PMF_MSG_DELAY_MIN_US 50
#define RESPONSE_REGISTER_LOOP_MAX 20000
#define DELAY_MIN_US 2000
#define DELAY_MAX_US 3000
/* override Metrics Table sample size time (in ms) */
static int metrics_table_loop_ms = 1000;
module_param(metrics_table_loop_ms, int, 0644);
MODULE_PARM_DESC(metrics_table_loop_ms, "Metrics Table sample size time (default = 1000ms)");
/* Force load on supported older platforms */
static bool force_load;
module_param(force_load, bool, 0444);
MODULE_PARM_DESC(force_load, "Force load this driver on supported older platforms (experimental)");
static bool smart_pc_support = true;
module_param(smart_pc_support, bool, 0444);
MODULE_PARM_DESC(smart_pc_support, "Smart PC Support (default = true)");
static struct device *pmf_device;
static int amd_pmf_pwr_src_notify_call(struct notifier_block *nb, unsigned long event, void *data)
{
struct amd_pmf_dev *pmf = container_of(nb, struct amd_pmf_dev, pwr_src_notifier);
if (event != PSY_EVENT_PROP_CHANGED)
return NOTIFY_OK;
if (is_apmf_func_supported(pmf, APMF_FUNC_AUTO_MODE) ||
is_apmf_func_supported(pmf, APMF_FUNC_DYN_SLIDER_DC) ||
is_apmf_func_supported(pmf, APMF_FUNC_DYN_SLIDER_AC)) {
if ((pmf->amt_enabled || pmf->cnqf_enabled) && is_pprof_balanced(pmf))
return NOTIFY_DONE;
}
if (is_apmf_func_supported(pmf, APMF_FUNC_STATIC_SLIDER_GRANULAR))
amd_pmf_set_sps_power_limits(pmf);
if (is_apmf_func_supported(pmf, APMF_FUNC_OS_POWER_SLIDER_UPDATE))
amd_pmf_power_slider_update_event(pmf);
return NOTIFY_OK;
}
static int current_power_limits_show(struct seq_file *seq, void *unused)
{
struct amd_pmf_dev *dev = seq->private;
struct amd_pmf_static_slider_granular table;
int mode, src = 0;
mode = amd_pmf_get_pprof_modes(dev);
if (mode < 0)
return mode;
src = amd_pmf_get_power_source();
amd_pmf_update_slider(dev, SLIDER_OP_GET, mode, &table);
seq_printf(seq, "spl:%u fppt:%u sppt:%u sppt_apu_only:%u stt_min:%u stt[APU]:%u stt[HS2]: %u\n",
table.prop[src][mode].spl,
table.prop[src][mode].fppt,
table.prop[src][mode].sppt,
table.prop[src][mode].sppt_apu_only,
table.prop[src][mode].stt_min,
table.prop[src][mode].stt_skin_temp[STT_TEMP_APU],
table.prop[src][mode].stt_skin_temp[STT_TEMP_HS2]);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(current_power_limits);
static void amd_pmf_dbgfs_unregister(struct amd_pmf_dev *dev)
{
debugfs_remove_recursive(dev->dbgfs_dir);
}
static void amd_pmf_dbgfs_register(struct amd_pmf_dev *dev)
{
dev->dbgfs_dir = debugfs_create_dir("amd_pmf", NULL);
if (dev->pmf_if_version == PMF_IF_V1)
debugfs_create_file("current_power_limits", 0644, dev->dbgfs_dir, dev,
&current_power_limits_fops);
}
int amd_pmf_get_power_source(void)
{
if (power_supply_is_system_supplied() > 0)
return POWER_SOURCE_AC;
else
return POWER_SOURCE_DC;
}
static void amd_pmf_get_metrics(struct work_struct *work)
{
struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, work_buffer.work);
ktime_t time_elapsed_ms;
int socket_power;
guard(mutex)(&dev->update_mutex);
/* Transfer table contents */
memset(dev->buf, 0, sizeof(dev->m_table));
amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
memcpy(&dev->m_table, dev->buf, sizeof(dev->m_table));
time_elapsed_ms = ktime_to_ms(ktime_get()) - dev->start_time;
/* Calculate the avg SoC power consumption */
socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
if (dev->amt_enabled) {
/* Apply the Auto Mode transition */
amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
}
if (dev->cnqf_enabled) {
/* Apply the CnQF transition */
amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
}
dev->start_time = ktime_to_ms(ktime_get());
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms));
}
static inline u32 amd_pmf_reg_read(struct amd_pmf_dev *dev, int reg_offset)
{
return ioread32(dev->regbase + reg_offset);
}
static inline void amd_pmf_reg_write(struct amd_pmf_dev *dev, int reg_offset, u32 val)
{
iowrite32(val, dev->regbase + reg_offset);
}
static void __maybe_unused amd_pmf_dump_registers(struct amd_pmf_dev *dev)
{
u32 value;
value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_RESPONSE);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_RESPONSE:%x\n", value);
value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_ARGUMENT:%d\n", value);
value = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_MESSAGE);
dev_dbg(dev->dev, "AMD_PMF_REGISTER_MESSAGE:%x\n", value);
}
/**
* fixp_q88_fromint: Convert integer to Q8.8
* @val: input value
*
* Converts an integer into binary fixed point format where 8 bits
* are used for integer and 8 bits are used for the decimal.
*
* Return: unsigned integer converted to Q8.8 format
*/
u32 fixp_q88_fromint(u32 val)
{
return val << 8;
}
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data)
{
int rc;
u32 val;
guard(mutex)(&dev->lock);
/* Wait until we get a valid response */
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
val, val != 0, PMF_MSG_DELAY_MIN_US,
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
dev_err(dev->dev, "failed to talk to SMU\n");
return rc;
}
/* Write zero to response register */
amd_pmf_reg_write(dev, AMD_PMF_REGISTER_RESPONSE, 0);
/* Write argument into argument register */
amd_pmf_reg_write(dev, AMD_PMF_REGISTER_ARGUMENT, arg);
/* Write message ID to message ID register */
amd_pmf_reg_write(dev, AMD_PMF_REGISTER_MESSAGE, message);
/* Wait until we get a valid response */
rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMF_REGISTER_RESPONSE,
val, val != 0, PMF_MSG_DELAY_MIN_US,
PMF_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX);
if (rc) {
dev_err(dev->dev, "SMU response timed out\n");
return rc;
}
switch (val) {
case AMD_PMF_RESULT_OK:
if (get) {
/* PMFW may take longer time to return back the data */
usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US);
*data = amd_pmf_reg_read(dev, AMD_PMF_REGISTER_ARGUMENT);
}
break;
case AMD_PMF_RESULT_CMD_REJECT_BUSY:
dev_err(dev->dev, "SMU not ready. err: 0x%x\n", val);
rc = -EBUSY;
break;
case AMD_PMF_RESULT_CMD_UNKNOWN:
dev_err(dev->dev, "SMU cmd unknown. err: 0x%x\n", val);
rc = -EINVAL;
break;
case AMD_PMF_RESULT_CMD_REJECT_PREREQ:
case AMD_PMF_RESULT_FAILED:
default:
dev_err(dev->dev, "SMU cmd failed. err: 0x%x\n", val);
rc = -EIO;
break;
}
amd_pmf_dump_registers(dev);
return rc;
}
static const struct pci_device_id pmf_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_RMB) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, AMD_CPU_ID_PS) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M20H_ROOT) },
{ PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_1AH_M60H_ROOT) },
{ }
};
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer)
{
u64 phys_addr;
u32 hi, low;
/* Get Metrics Table Address */
if (alloc_buffer) {
switch (dev->cpu_id) {
case AMD_CPU_ID_PS:
case AMD_CPU_ID_RMB:
dev->mtable_size = sizeof(dev->m_table);
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
dev->mtable_size = sizeof(dev->m_table_v2);
break;
default:
dev_err(dev->dev, "Invalid CPU id: 0x%x", dev->cpu_id);
}
dev->buf = devm_kzalloc(dev->dev, dev->mtable_size, GFP_KERNEL);
if (!dev->buf)
return -ENOMEM;
}
phys_addr = virt_to_phys(dev->buf);
hi = phys_addr >> 32;
low = phys_addr & GENMASK(31, 0);
amd_pmf_send_cmd(dev, SET_DRAM_ADDR_HIGH, SET_CMD, hi, NULL);
amd_pmf_send_cmd(dev, SET_DRAM_ADDR_LOW, SET_CMD, low, NULL);
return 0;
}
int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev)
{
int ret;
INIT_DELAYED_WORK(&dev->work_buffer, amd_pmf_get_metrics);
ret = amd_pmf_set_dram_addr(dev, true);
if (ret)
return ret;
/*
* Start collecting the metrics data after a small delay
* or else, we might end up getting stale values from PMFW.
*/
schedule_delayed_work(&dev->work_buffer, msecs_to_jiffies(metrics_table_loop_ms * 3));
return 0;
}
static int is_npu_metrics_supported(struct amd_pmf_dev *pdev)
{
switch (pdev->cpu_id) {
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
return 0;
default:
return -EOPNOTSUPP;
}
}
static int amd_pmf_get_smu_metrics(struct amd_pmf_dev *dev, struct amd_pmf_npu_metrics *data)
{
int ret, i;
guard(mutex)(&dev->metrics_mutex);
ret = is_npu_metrics_supported(dev);
if (ret)
return ret;
ret = amd_pmf_set_dram_addr(dev, true);
if (ret)
return ret;
memset(dev->buf, 0, dev->mtable_size);
/* Send SMU command to get NPU metrics */
ret = amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
if (ret) {
dev_err(dev->dev, "SMU command failed to get NPU metrics: %d\n", ret);
return ret;
}
memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
data->npuclk_freq = dev->m_table_v2.npuclk_freq;
for (i = 0; i < ARRAY_SIZE(data->npu_busy); i++)
data->npu_busy[i] = dev->m_table_v2.npu_busy[i];
data->npu_power = dev->m_table_v2.npu_power;
data->mpnpuclk_freq = dev->m_table_v2.mpnpuclk_freq;
data->npu_reads = dev->m_table_v2.npu_reads;
data->npu_writes = dev->m_table_v2.npu_writes;
return 0;
}
int amd_pmf_get_npu_data(struct amd_pmf_npu_metrics *info)
{
struct amd_pmf_dev *pdev;
if (!info)
return -EINVAL;
if (!pmf_device)
return -ENODEV;
pdev = dev_get_drvdata(pmf_device);
if (!pdev)
return -ENODEV;
return amd_pmf_get_smu_metrics(pdev, info);
}
EXPORT_SYMBOL_NS_GPL(amd_pmf_get_npu_data, "AMD_PMF");
static int amd_pmf_reinit_ta(struct amd_pmf_dev *pdev)
{
bool status;
int ret, i;
for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
ret = amd_pmf_tee_init(pdev, &amd_pmf_ta_uuid[i]);
if (ret) {
dev_err(pdev->dev, "TEE init failed for UUID[%d] ret: %d\n", i, ret);
return ret;
}
ret = amd_pmf_start_policy_engine(pdev);
dev_dbg(pdev->dev, "start policy engine ret: %d (UUID idx: %d)\n", ret, i);
status = ret == TA_PMF_TYPE_SUCCESS;
if (status)
break;
amd_pmf_tee_deinit(pdev);
}
return 0;
}
static int amd_pmf_restore_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
int ret;
if (pdev->buf) {
ret = amd_pmf_set_dram_addr(pdev, false);
if (ret)
return ret;
}
if (pdev->smart_pc_enabled)
amd_pmf_reinit_ta(pdev);
return 0;
}
static int amd_pmf_freeze_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
if (!pdev->smart_pc_enabled)
return 0;
cancel_delayed_work_sync(&pdev->pb_work);
/* Clear all TEE resources */
amd_pmf_tee_deinit(pdev);
pdev->session_id = 0;
return 0;
}
static int amd_pmf_suspend_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
if (pdev->smart_pc_enabled)
cancel_delayed_work_sync(&pdev->pb_work);
if (is_apmf_func_supported(pdev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(pdev, ON_SUSPEND);
return 0;
}
static int amd_pmf_resume_handler(struct device *dev)
{
struct amd_pmf_dev *pdev = dev_get_drvdata(dev);
int ret;
if (pdev->buf) {
ret = amd_pmf_set_dram_addr(pdev, false);
if (ret)
return ret;
}
if (is_apmf_func_supported(pdev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(pdev, ON_RESUME);
if (pdev->smart_pc_enabled)
schedule_delayed_work(&pdev->pb_work, msecs_to_jiffies(2000));
return 0;
}
static const struct dev_pm_ops amd_pmf_pm = {
.suspend = amd_pmf_suspend_handler,
.resume = amd_pmf_resume_handler,
.freeze = amd_pmf_freeze_handler,
.restore = amd_pmf_restore_handler,
};
static void amd_pmf_init_features(struct amd_pmf_dev *dev)
{
int ret;
/* Enable Static Slider */
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR) ||
is_apmf_func_supported(dev, APMF_FUNC_OS_POWER_SLIDER_UPDATE)) {
amd_pmf_init_sps(dev);
dev->pwr_src_notifier.notifier_call = amd_pmf_pwr_src_notify_call;
power_supply_reg_notifier(&dev->pwr_src_notifier);
dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
}
if (smart_pc_support) {
amd_pmf_init_smart_pc(dev);
if (dev->smart_pc_enabled) {
dev_dbg(dev->dev, "Smart PC Solution Enabled\n");
/* If Smart PC is enabled, no need to check for other features */
return;
}
} else {
dev->smart_pc_enabled = false;
}
if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
amd_pmf_init_auto_mode(dev);
dev_dbg(dev->dev, "Auto Mode Init done\n");
} else if (is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_AC) ||
is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_DC)) {
ret = amd_pmf_init_cnqf(dev);
if (ret)
dev_warn(dev->dev, "CnQF Init failed\n");
}
}
static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
{
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR) ||
is_apmf_func_supported(dev, APMF_FUNC_OS_POWER_SLIDER_UPDATE)) {
power_supply_unreg_notifier(&dev->pwr_src_notifier);
}
if (dev->smart_pc_enabled) {
amd_pmf_deinit_smart_pc(dev);
} else if (is_apmf_func_supported(dev, APMF_FUNC_AUTO_MODE)) {
amd_pmf_deinit_auto_mode(dev);
} else if (is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_AC) ||
is_apmf_func_supported(dev, APMF_FUNC_DYN_SLIDER_DC)) {
amd_pmf_deinit_cnqf(dev);
}
}
static const struct acpi_device_id amd_pmf_acpi_ids[] = {
{"AMDI0100", 0x100},
{"AMDI0102", 0},
{"AMDI0103", 0},
{"AMDI0105", 0},
{"AMDI0107", 0},
{"AMDI0108", 0},
{ }
};
MODULE_DEVICE_TABLE(acpi, amd_pmf_acpi_ids);
static int amd_pmf_probe(struct platform_device *pdev)
{
const struct acpi_device_id *id;
struct amd_pmf_dev *dev;
struct pci_dev *rdev;
u32 base_addr_lo;
u32 base_addr_hi;
u64 base_addr;
u32 val;
int err;
id = acpi_match_device(amd_pmf_acpi_ids, &pdev->dev);
if (!id)
return -ENODEV;
if (id->driver_data == 0x100 && !force_load)
return -ENODEV;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->dev = &pdev->dev;
rdev = pci_get_domain_bus_and_slot(0, 0, PCI_DEVFN(0, 0));
if (!rdev || !pci_match_id(pmf_pci_ids, rdev)) {
pci_dev_put(rdev);
return -ENODEV;
}
dev->cpu_id = rdev->device;
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_LO, &val);
if (err) {
pci_dev_put(rdev);
return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
"error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_LO);
}
base_addr_lo = val & AMD_PMF_BASE_ADDR_HI_MASK;
err = amd_smn_read(0, AMD_PMF_BASE_ADDR_HI, &val);
if (err) {
pci_dev_put(rdev);
return dev_err_probe(dev->dev, pcibios_err_to_errno(err),
"error in reading from 0x%x\n", AMD_PMF_BASE_ADDR_HI);
}
base_addr_hi = val & AMD_PMF_BASE_ADDR_LO_MASK;
pci_dev_put(rdev);
base_addr = ((u64)base_addr_hi << 32 | base_addr_lo);
dev->regbase = devm_ioremap(dev->dev, base_addr + AMD_PMF_BASE_ADDR_OFFSET,
AMD_PMF_MAPPING_SIZE);
if (!dev->regbase)
return -ENOMEM;
err = devm_mutex_init(dev->dev, &dev->lock);
if (err)
return err;
err = devm_mutex_init(dev->dev, &dev->update_mutex);
if (err)
return err;
err = devm_mutex_init(dev->dev, &dev->cb_mutex);
if (err)
return err;
err = devm_mutex_init(dev->dev, &dev->cbi_mutex);
if (err)
return err;
err = devm_mutex_init(dev->dev, &dev->metrics_mutex);
if (err)
return err;
apmf_acpi_init(dev);
platform_set_drvdata(pdev, dev);
amd_pmf_dbgfs_register(dev);
amd_pmf_init_features(dev);
apmf_install_handler(dev);
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_LOAD);
pmf_device = dev->dev;
dev_info(dev->dev, "registered PMF device successfully\n");
return 0;
}
static void amd_pmf_remove(struct platform_device *pdev)
{
struct amd_pmf_dev *dev = platform_get_drvdata(pdev);
amd_pmf_deinit_features(dev);
if (is_apmf_func_supported(dev, APMF_FUNC_SBIOS_HEARTBEAT_V2))
amd_pmf_notify_sbios_heartbeat_event_v2(dev, ON_UNLOAD);
apmf_acpi_deinit(dev);
amd_pmf_dbgfs_unregister(dev);
}
static const struct attribute_group *amd_pmf_driver_groups[] = {
&cnqf_feature_attribute_group,
NULL,
};
static struct platform_driver amd_pmf_driver = {
.driver = {
.name = "amd-pmf",
.acpi_match_table = amd_pmf_acpi_ids,
.dev_groups = amd_pmf_driver_groups,
.pm = pm_sleep_ptr(&amd_pmf_pm),
},
.probe = amd_pmf_probe,
.remove = amd_pmf_remove,
};
module_platform_driver(amd_pmf_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("AMD Platform Management Framework Driver");
MODULE_SOFTDEP("pre: amdtee");
@@ -0,0 +1,931 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* AMD Platform Management Framework Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#ifndef PMF_H
#define PMF_H
#include <linux/acpi.h>
#include <linux/amd-pmf-io.h>
#include <linux/circ_buf.h>
#include <linux/input.h>
#include <linux/mutex_types.h>
#include <linux/platform_device.h>
#include <linux/platform_profile.h>
#define POLICY_BUF_MAX_SZ 0x4b000
#define POLICY_SIGN_COOKIE 0x31535024
#define POLICY_COOKIE_OFFSET 0x10
/* List of supported CPU ids */
#define AMD_CPU_ID_RMB 0x14b5
#define AMD_CPU_ID_PS 0x14e8
#define PCI_DEVICE_ID_AMD_1AH_M20H_ROOT 0x1507
#define PCI_DEVICE_ID_AMD_1AH_M60H_ROOT 0x1122
struct cookie_header {
u32 sign;
u32 length;
} __packed;
/* APMF Functions */
#define APMF_FUNC_VERIFY_INTERFACE 0
#define APMF_FUNC_GET_SYS_PARAMS 1
#define APMF_FUNC_SBIOS_REQUESTS 2
#define APMF_FUNC_SBIOS_HEARTBEAT 4
#define APMF_FUNC_AUTO_MODE 5
#define APMF_FUNC_SET_FAN_IDX 7
#define APMF_FUNC_OS_POWER_SLIDER_UPDATE 8
#define APMF_FUNC_STATIC_SLIDER_GRANULAR 9
#define APMF_FUNC_DYN_SLIDER_AC 11
#define APMF_FUNC_DYN_SLIDER_DC 12
#define APMF_FUNC_NOTIFY_SMART_PC_UPDATES 14
#define APMF_FUNC_SBIOS_HEARTBEAT_V2 16
/* Message Definitions */
#define SET_SPL 0x03 /* SPL: Sustained Power Limit */
#define SET_SPPT 0x05 /* SPPT: Slow Package Power Tracking */
#define SET_FPPT 0x07 /* FPPT: Fast Package Power Tracking */
#define GET_SPL 0x0B
#define GET_SPPT 0x0D
#define GET_FPPT 0x0F
#define SET_DRAM_ADDR_HIGH 0x14
#define SET_DRAM_ADDR_LOW 0x15
#define SET_TRANSFER_TABLE 0x16
#define SET_STT_MIN_LIMIT 0x18 /* STT: Skin Temperature Tracking */
#define SET_STT_LIMIT_APU 0x19
#define SET_STT_LIMIT_HS2 0x1A
#define SET_SPPT_APU_ONLY 0x1D
#define GET_SPPT_APU_ONLY 0x1E
#define GET_STT_MIN_LIMIT 0x1F
#define GET_STT_LIMIT_APU 0x20
#define GET_STT_LIMIT_HS2 0x21
#define SET_P3T 0x23 /* P3T: Peak Package Power Limit */
#define SET_PMF_PPT 0x25
#define SET_PMF_PPT_APU_ONLY 0x26
/* OS slider update notification */
#define DC_BEST_PERF 0
#define DC_BETTER_PERF 1
#define DC_BATTERY_SAVER 3
#define AC_BEST_PERF 4
#define AC_BETTER_PERF 5
#define AC_BETTER_BATTERY 6
/* Fan Index for Auto Mode */
#define FAN_INDEX_AUTO 0xFFFFFFFF
#define ARG_NONE 0
#define AVG_SAMPLE_SIZE 3
/* Policy Actions */
#define PMF_POLICY_SPL 2
#define PMF_POLICY_SPPT 3
#define PMF_POLICY_FPPT 4
#define PMF_POLICY_SPPT_APU_ONLY 5
#define PMF_POLICY_STT_MIN 6
#define PMF_POLICY_STT_SKINTEMP_APU 7
#define PMF_POLICY_STT_SKINTEMP_HS2 8
#define PMF_POLICY_SYSTEM_STATE 9
#define PMF_POLICY_BIOS_OUTPUT_1 10
#define PMF_POLICY_BIOS_OUTPUT_2 11
#define PMF_POLICY_P3T 38
#define PMF_POLICY_PMF_PPT 54
#define PMF_POLICY_PMF_PPT_APU_ONLY 55
#define PMF_POLICY_BIOS_OUTPUT_3 57
#define PMF_POLICY_BIOS_OUTPUT_4 58
#define PMF_POLICY_BIOS_OUTPUT_5 59
#define PMF_POLICY_BIOS_OUTPUT_6 60
#define PMF_POLICY_BIOS_OUTPUT_7 61
#define PMF_POLICY_BIOS_OUTPUT_8 62
#define PMF_POLICY_BIOS_OUTPUT_9 63
#define PMF_POLICY_BIOS_OUTPUT_10 64
/* TA macros */
#define PMF_TA_IF_VERSION_MAJOR 1
#define TA_PMF_ACTION_MAX 32
#define TA_PMF_UNDO_MAX 8
#define TA_OUTPUT_RESERVED_MEM 922
#define MAX_OPERATION_PARAMS 4
#define TA_ERROR_CRYPTO_INVALID_PARAM 0x20002
#define TA_ERROR_CRYPTO_BIN_TOO_LARGE 0x2000d
#define PMF_IF_V1 1
#define PMF_IF_V2 2
#define APTS_MAX_STATES 16
#define CUSTOM_BIOS_INPUT_BITS GENMASK(16, 7)
#define BIOS_INPUTS_MAX 10
#define CUSTOM_BIOS_INPUT_RING_ENTRIES 64 /* Must be power of two for CIRC_* macros */
/* amd_pmf_send_cmd() set/get */
#define SET_CMD false
#define GET_CMD true
#define METRICS_TABLE_ID 7
typedef void (*apmf_event_handler_t)(acpi_handle handle, u32 event, void *data);
static const uuid_t amd_pmf_ta_uuid[] __used = { UUID_INIT(0xd9b39bf2, 0x66bd, 0x4154, 0xaf, 0xb8,
0x8a, 0xcc, 0x2b, 0x2b, 0x60, 0xd6),
UUID_INIT(0x6fd93b77, 0x3fb8, 0x524d, 0xb1, 0x2d,
0xc5, 0x29, 0xb1, 0x3d, 0x85, 0x43),
};
/* APTS PMF BIOS Interface */
struct amd_pmf_apts_output {
u16 table_version;
u32 fan_table_idx;
u32 pmf_ppt;
u32 ppt_pmf_apu_only;
u32 stt_min_limit;
u8 stt_skin_temp_limit_apu;
u8 stt_skin_temp_limit_hs2;
} __packed;
struct amd_pmf_apts_granular_output {
u16 size;
struct amd_pmf_apts_output val;
} __packed;
struct amd_pmf_apts_granular {
u16 size;
struct amd_pmf_apts_output val[APTS_MAX_STATES];
};
struct sbios_hb_event_v2 {
u16 size;
u8 load;
u8 unload;
u8 suspend;
u8 resume;
} __packed;
enum sbios_hb_v2 {
ON_LOAD,
ON_UNLOAD,
ON_SUSPEND,
ON_RESUME,
};
/* AMD PMF BIOS interfaces */
struct apmf_verify_interface {
u16 size;
u16 version;
u32 notification_mask;
u32 supported_functions;
} __packed;
struct apmf_system_params {
u16 size;
u32 valid_mask;
u32 flags;
u8 command_code;
u32 heartbeat_int;
} __packed;
struct apmf_sbios_req {
u16 size;
u32 pending_req;
u8 rsd;
u8 cql_event;
u8 amt_event;
u32 fppt;
u32 sppt;
u32 fppt_apu_only;
u32 spl;
u32 stt_min_limit;
u8 skin_temp_apu;
u8 skin_temp_hs2;
} __packed;
/* As per APMF spec 1.3 */
struct apmf_sbios_req_v1 {
u16 size;
u32 pending_req;
u8 rsvd;
u8 cql_event;
u8 amt_event;
u32 fppt;
u32 sppt;
u32 sppt_apu_only;
u32 spl;
u32 stt_min_limit;
u8 skin_temp_apu;
u8 skin_temp_hs2;
u8 enable_cnqf;
u32 custom_policy[BIOS_INPUTS_MAX];
} __packed;
struct apmf_sbios_req_v2 {
u16 size;
u32 pending_req;
u8 rsd;
u32 ppt_pmf;
u32 ppt_pmf_apu_only;
u32 stt_min_limit;
u8 skin_temp_apu;
u8 skin_temp_hs2;
u32 custom_policy[BIOS_INPUTS_MAX];
} __packed;
struct apmf_fan_idx {
u16 size;
u8 fan_ctl_mode;
u32 fan_ctl_idx;
} __packed;
struct smu_pmf_metrics_v2 {
u16 core_frequency[16]; /* MHz */
u16 core_power[16]; /* mW */
u16 core_temp[16]; /* centi-C */
u16 gfx_temp; /* centi-C */
u16 soc_temp; /* centi-C */
u16 stapm_opn_limit; /* mW */
u16 stapm_cur_limit; /* mW */
u16 infra_cpu_maxfreq; /* MHz */
u16 infra_gfx_maxfreq; /* MHz */
u16 skin_temp; /* centi-C */
u16 gfxclk_freq; /* MHz */
u16 fclk_freq; /* MHz */
u16 gfx_activity; /* GFX busy % [0-100] */
u16 socclk_freq; /* MHz */
u16 vclk_freq; /* MHz */
u16 vcn_activity; /* VCN busy % [0-100] */
u16 vpeclk_freq; /* MHz */
u16 npuclk_freq; /* MHz */
u16 npu_busy[8]; /* NPU busy % [0-100] */
u16 dram_reads; /* MB/sec */
u16 dram_writes; /* MB/sec */
u16 core_c0residency[16]; /* C0 residency % [0-100] */
u16 npu_power; /* mW */
u32 apu_power; /* mW */
u32 gfx_power; /* mW */
u32 dgpu_power; /* mW */
u32 socket_power; /* mW */
u32 all_core_power; /* mW */
u32 filter_alpha_value; /* time constant [us] */
u32 metrics_counter;
u16 memclk_freq; /* MHz */
u16 mpnpuclk_freq; /* MHz */
u16 npu_reads; /* MB/sec */
u16 npu_writes; /* MB/sec */
u32 throttle_residency_prochot;
u32 throttle_residency_spl;
u32 throttle_residency_fppt;
u32 throttle_residency_sppt;
u32 throttle_residency_thm_core;
u32 throttle_residency_thm_gfx;
u32 throttle_residency_thm_soc;
u16 psys;
u16 spare1;
u32 spare[6];
} __packed;
struct smu_pmf_metrics {
u16 gfxclk_freq; /* in MHz */
u16 socclk_freq; /* in MHz */
u16 vclk_freq; /* in MHz */
u16 dclk_freq; /* in MHz */
u16 memclk_freq; /* in MHz */
u16 spare;
u16 gfx_activity; /* in Centi */
u16 uvd_activity; /* in Centi */
u16 voltage[2]; /* in mV */
u16 currents[2]; /* in mA */
u16 power[2];/* in mW */
u16 core_freq[8]; /* in MHz */
u16 core_power[8]; /* in mW */
u16 core_temp[8]; /* in centi-Celsius */
u16 l3_freq; /* in MHz */
u16 l3_temp; /* in centi-Celsius */
u16 gfx_temp; /* in centi-Celsius */
u16 soc_temp; /* in centi-Celsius */
u16 throttler_status;
u16 current_socketpower; /* in mW */
u16 stapm_orig_limit; /* in W */
u16 stapm_cur_limit; /* in W */
u32 apu_power; /* in mW */
u32 dgpu_power; /* in mW */
u16 vdd_tdc_val; /* in mA */
u16 soc_tdc_val; /* in mA */
u16 vdd_edc_val; /* in mA */
u16 soc_edcv_al; /* in mA */
u16 infra_cpu_maxfreq; /* in MHz */
u16 infra_gfx_maxfreq; /* in MHz */
u16 skin_temp; /* in centi-Celsius */
u16 device_state;
u16 curtemp; /* in centi-Celsius */
u16 filter_alpha_value;
u16 avg_gfx_clkfrequency;
u16 avg_fclk_frequency;
u16 avg_gfx_activity;
u16 avg_socclk_frequency;
u16 avg_vclk_frequency;
u16 avg_vcn_activity;
u16 avg_dram_reads;
u16 avg_dram_writes;
u16 avg_socket_power;
u16 avg_core_power[2];
u16 avg_core_c0residency[16];
u16 spare1;
u32 metrics_counter;
} __packed;
enum amd_stt_skin_temp {
STT_TEMP_APU,
STT_TEMP_HS2,
STT_TEMP_COUNT,
};
enum amd_slider_op {
SLIDER_OP_GET,
SLIDER_OP_SET,
};
enum power_source {
POWER_SOURCE_AC,
POWER_SOURCE_DC,
POWER_SOURCE_MAX,
};
enum power_modes {
POWER_MODE_PERFORMANCE,
POWER_MODE_BALANCED_POWER,
POWER_MODE_POWER_SAVER,
POWER_MODE_MAX,
};
enum power_modes_v2 {
POWER_MODE_BEST_PERFORMANCE,
POWER_MODE_BALANCED,
POWER_MODE_BEST_POWER_EFFICIENCY,
POWER_MODE_ENERGY_SAVE,
POWER_MODE_V2_MAX,
};
struct pmf_bios_inputs_prev {
u32 custom_bios_inputs[BIOS_INPUTS_MAX];
};
/**
* struct pmf_bios_input_entry - Snapshot of custom BIOS input event
* @val: Array of custom BIOS input values
* @preq: Pending request value associated with this event
*/
struct pmf_bios_input_entry {
u32 val[BIOS_INPUTS_MAX];
u32 preq;
};
struct pmf_cbi_ring_buffer {
struct pmf_bios_input_entry data[CUSTOM_BIOS_INPUT_RING_ENTRIES];
int head;
int tail;
};
struct amd_pmf_dev {
void __iomem *regbase;
void __iomem *smu_virt_addr;
void *buf;
u32 base_addr;
u32 cpu_id;
struct device *dev;
struct mutex lock; /* protects the PMF interface */
u32 supported_func;
enum platform_profile_option current_profile;
struct device *ppdev; /* platform profile class device */
struct dentry *dbgfs_dir;
int hb_interval; /* SBIOS heartbeat interval */
struct delayed_work heart_beat;
struct smu_pmf_metrics m_table;
struct smu_pmf_metrics_v2 m_table_v2;
struct delayed_work work_buffer;
ktime_t start_time;
int socket_power_history[AVG_SAMPLE_SIZE];
int socket_power_history_idx;
bool amt_enabled;
struct mutex update_mutex; /* protects race between ACPI handler and metrics thread */
bool cnqf_enabled;
bool cnqf_supported;
struct notifier_block pwr_src_notifier;
/* Smart PC solution builder */
struct dentry *esbin;
unsigned char *policy_buf;
resource_size_t policy_sz;
struct tee_context *tee_ctx;
struct tee_shm *fw_shm_pool;
u32 session_id;
void *shbuf;
struct delayed_work pb_work;
struct pmf_action_table *prev_data;
resource_size_t policy_addr;
void __iomem *policy_base;
bool smart_pc_enabled;
u16 pmf_if_version;
struct input_dev *pmf_idev;
size_t mtable_size;
struct resource *res;
struct apmf_sbios_req_v2 req; /* To get custom bios pending request */
struct mutex cb_mutex;
u32 notifications;
struct apmf_sbios_req_v1 req1;
struct pmf_bios_inputs_prev cb_prev; /* To preserve custom BIOS inputs */
bool cb_flag; /* To handle first custom BIOS input */
struct pmf_cbi_ring_buffer cbi_buf;
struct mutex cbi_mutex; /* Protects ring buffer access */
struct mutex metrics_mutex;
};
struct apmf_sps_prop_granular_v2 {
u8 power_states[POWER_SOURCE_MAX][POWER_MODE_V2_MAX];
} __packed;
struct apmf_sps_prop_granular {
u32 fppt;
u32 sppt;
u32 sppt_apu_only;
u32 spl;
u32 stt_min;
u8 stt_skin_temp[STT_TEMP_COUNT];
u32 fan_id;
} __packed;
/* Static Slider */
struct apmf_static_slider_granular_output {
u16 size;
struct apmf_sps_prop_granular prop[POWER_SOURCE_MAX * POWER_MODE_MAX];
} __packed;
struct amd_pmf_static_slider_granular {
u16 size;
struct apmf_sps_prop_granular prop[POWER_SOURCE_MAX][POWER_MODE_MAX];
};
struct apmf_static_slider_granular_output_v2 {
u16 size;
struct apmf_sps_prop_granular_v2 sps_idx;
} __packed;
struct amd_pmf_static_slider_granular_v2 {
u16 size;
struct apmf_sps_prop_granular_v2 sps_idx;
};
struct os_power_slider {
u16 size;
u8 slider_event;
} __packed;
struct amd_pmf_notify_smart_pc_update {
u16 size;
u32 pending_req;
u32 custom_bios[BIOS_INPUTS_MAX];
} __packed;
struct fan_table_control {
bool manual;
unsigned long fan_id;
};
struct power_table_control {
u32 spl;
u32 sppt;
u32 fppt;
u32 sppt_apu_only;
u32 stt_min;
u32 stt_skin_temp[STT_TEMP_COUNT];
u32 reserved[16];
};
/* Auto Mode Layer */
enum auto_mode_transition_priority {
AUTO_TRANSITION_TO_PERFORMANCE, /* Any other mode to Performance Mode */
AUTO_TRANSITION_FROM_QUIET_TO_BALANCE, /* Quiet Mode to Balance Mode */
AUTO_TRANSITION_TO_QUIET, /* Any other mode to Quiet Mode */
AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE, /* Performance Mode to Balance Mode */
AUTO_TRANSITION_MAX,
};
enum auto_mode_mode {
AUTO_QUIET,
AUTO_BALANCE,
AUTO_PERFORMANCE_ON_LAP,
AUTO_PERFORMANCE,
AUTO_MODE_MAX,
};
struct auto_mode_trans_params {
u32 time_constant; /* minimum time required to switch to next mode */
u32 power_delta; /* delta power to shift mode */
u32 power_threshold;
u32 timer; /* elapsed time. if timer > TimeThreshold, it will move to next mode */
u32 applied;
enum auto_mode_mode target_mode;
u32 shifting_up;
};
struct auto_mode_mode_settings {
struct power_table_control power_control;
struct fan_table_control fan_control;
u32 power_floor;
};
struct auto_mode_mode_config {
struct auto_mode_trans_params transition[AUTO_TRANSITION_MAX];
struct auto_mode_mode_settings mode_set[AUTO_MODE_MAX];
enum auto_mode_mode current_mode;
};
struct apmf_auto_mode {
u16 size;
/* time constant */
u32 balanced_to_perf;
u32 perf_to_balanced;
u32 quiet_to_balanced;
u32 balanced_to_quiet;
/* power floor */
u32 pfloor_perf;
u32 pfloor_balanced;
u32 pfloor_quiet;
/* Power delta for mode change */
u32 pd_balanced_to_perf;
u32 pd_perf_to_balanced;
u32 pd_quiet_to_balanced;
u32 pd_balanced_to_quiet;
/* skin temperature limits */
u8 stt_apu_perf_on_lap; /* CQL ON */
u8 stt_hs2_perf_on_lap; /* CQL ON */
u8 stt_apu_perf;
u8 stt_hs2_perf;
u8 stt_apu_balanced;
u8 stt_hs2_balanced;
u8 stt_apu_quiet;
u8 stt_hs2_quiet;
u32 stt_min_limit_perf_on_lap; /* CQL ON */
u32 stt_min_limit_perf;
u32 stt_min_limit_balanced;
u32 stt_min_limit_quiet;
/* SPL based */
u32 fppt_perf_on_lap; /* CQL ON */
u32 sppt_perf_on_lap; /* CQL ON */
u32 spl_perf_on_lap; /* CQL ON */
u32 sppt_apu_only_perf_on_lap; /* CQL ON */
u32 fppt_perf;
u32 sppt_perf;
u32 spl_perf;
u32 sppt_apu_only_perf;
u32 fppt_balanced;
u32 sppt_balanced;
u32 spl_balanced;
u32 sppt_apu_only_balanced;
u32 fppt_quiet;
u32 sppt_quiet;
u32 spl_quiet;
u32 sppt_apu_only_quiet;
/* Fan ID */
u32 fan_id_perf;
u32 fan_id_balanced;
u32 fan_id_quiet;
} __packed;
/* CnQF Layer */
enum cnqf_trans_priority {
CNQF_TRANSITION_TO_TURBO, /* Any other mode to Turbo Mode */
CNQF_TRANSITION_FROM_BALANCE_TO_PERFORMANCE, /* quiet/balance to Performance Mode */
CNQF_TRANSITION_FROM_QUIET_TO_BALANCE, /* Quiet Mode to Balance Mode */
CNQF_TRANSITION_TO_QUIET, /* Any other mode to Quiet Mode */
CNQF_TRANSITION_FROM_PERFORMANCE_TO_BALANCE, /* Performance/Turbo to Balance Mode */
CNQF_TRANSITION_FROM_TURBO_TO_PERFORMANCE, /* Turbo mode to Performance Mode */
CNQF_TRANSITION_MAX,
};
enum cnqf_mode {
CNQF_MODE_QUIET,
CNQF_MODE_BALANCE,
CNQF_MODE_PERFORMANCE,
CNQF_MODE_TURBO,
CNQF_MODE_MAX,
};
enum apmf_cnqf_pos {
APMF_CNQF_TURBO,
APMF_CNQF_PERFORMANCE,
APMF_CNQF_BALANCE,
APMF_CNQF_QUIET,
APMF_CNQF_MAX,
};
struct cnqf_mode_settings {
struct power_table_control power_control;
struct fan_table_control fan_control;
u32 power_floor;
};
struct cnqf_tran_params {
u32 time_constant; /* minimum time required to switch to next mode */
u32 power_threshold;
u32 timer; /* elapsed time. if timer > timethreshold, it will move to next mode */
u32 total_power;
u32 count;
bool priority;
bool shifting_up;
enum cnqf_mode target_mode;
};
struct cnqf_config {
struct cnqf_tran_params trans_param[POWER_SOURCE_MAX][CNQF_TRANSITION_MAX];
struct cnqf_mode_settings mode_set[POWER_SOURCE_MAX][CNQF_MODE_MAX];
struct power_table_control defaults;
enum cnqf_mode current_mode;
u32 power_src;
u32 avg_power;
};
struct apmf_cnqf_power_set {
u32 pfloor;
u32 fppt;
u32 sppt;
u32 sppt_apu_only;
u32 spl;
u32 stt_min_limit;
u8 stt_skintemp[STT_TEMP_COUNT];
u32 fan_id;
} __packed;
struct apmf_dyn_slider_output {
u16 size;
u16 flags;
u32 t_perf_to_turbo;
u32 t_balanced_to_perf;
u32 t_quiet_to_balanced;
u32 t_balanced_to_quiet;
u32 t_perf_to_balanced;
u32 t_turbo_to_perf;
struct apmf_cnqf_power_set ps[APMF_CNQF_MAX];
} __packed;
/* Smart PC - TA internals */
enum system_state {
SYSTEM_STATE_S0i3,
SYSTEM_STATE_S4,
SYSTEM_STATE_SCREEN_LOCK,
SYSTEM_STATE_MAX,
};
enum ta_slider {
TA_BEST_BATTERY,
TA_BETTER_BATTERY,
TA_BETTER_PERFORMANCE,
TA_BEST_PERFORMANCE,
TA_MAX,
};
struct amd_pmf_pb_bitmap {
const char *name;
u32 bit_mask;
};
static const struct amd_pmf_pb_bitmap custom_bios_inputs[] __used = {
{"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(5)},
{"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(6)},
{"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(7)},
{"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(8)},
{"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(9)},
{"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(10)},
{"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(11)},
{"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(12)},
{"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(13)},
{"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(14)},
};
static const struct amd_pmf_pb_bitmap custom_bios_inputs_v1[] __used = {
{"NOTIFY_CUSTOM_BIOS_INPUT1", BIT(7)},
{"NOTIFY_CUSTOM_BIOS_INPUT2", BIT(8)},
{"NOTIFY_CUSTOM_BIOS_INPUT3", BIT(9)},
{"NOTIFY_CUSTOM_BIOS_INPUT4", BIT(10)},
{"NOTIFY_CUSTOM_BIOS_INPUT5", BIT(11)},
{"NOTIFY_CUSTOM_BIOS_INPUT6", BIT(12)},
{"NOTIFY_CUSTOM_BIOS_INPUT7", BIT(13)},
{"NOTIFY_CUSTOM_BIOS_INPUT8", BIT(14)},
{"NOTIFY_CUSTOM_BIOS_INPUT9", BIT(15)},
{"NOTIFY_CUSTOM_BIOS_INPUT10", BIT(16)},
};
enum platform_type {
PTYPE_UNKNOWN = 0,
LID_CLOSE,
CLAMSHELL,
FLAT,
TENT,
STAND,
TABLET,
BOOK,
PRESENTATION,
PULL_FWD,
PTYPE_INVALID = 0xf,
};
/* Command ids for TA communication */
enum ta_pmf_command {
TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE,
TA_PMF_COMMAND_POLICY_BUILDER_ENACT_POLICIES,
};
enum ta_pmf_error_type {
TA_PMF_TYPE_SUCCESS,
TA_PMF_ERROR_TYPE_GENERIC,
TA_PMF_ERROR_TYPE_CRYPTO,
TA_PMF_ERROR_TYPE_CRYPTO_VALIDATE,
TA_PMF_ERROR_TYPE_CRYPTO_VERIFY_OEM,
TA_PMF_ERROR_TYPE_POLICY_BUILDER,
TA_PMF_ERROR_TYPE_PB_CONVERT,
TA_PMF_ERROR_TYPE_PB_SETUP,
TA_PMF_ERROR_TYPE_PB_ENACT,
TA_PMF_ERROR_TYPE_ASD_GET_DEVICE_INFO,
TA_PMF_ERROR_TYPE_ASD_GET_DEVICE_PCIE_INFO,
TA_PMF_ERROR_TYPE_SYS_DRV_FW_VALIDATION,
TA_PMF_ERROR_TYPE_MAX,
};
struct pmf_action_table {
enum system_state system_state;
u32 spl; /* in mW */
u32 sppt; /* in mW */
u32 sppt_apuonly; /* in mW */
u32 fppt; /* in mW */
u32 stt_minlimit; /* in mW */
u32 stt_skintemp_apu; /* in C */
u32 stt_skintemp_hs2; /* in C */
u32 p3t_limit; /* in mW */
u32 pmf_ppt; /* in mW */
u32 pmf_ppt_apu_only; /* in mW */
};
/* Input conditions */
struct ta_pmf_condition_info {
u32 power_source;
u32 bat_percentage;
u32 power_slider;
u32 lid_state;
bool user_present;
u32 bios_input_1[2];
u32 monitor_count;
u32 rsvd2[2];
u32 bat_design;
u32 full_charge_capacity;
int drain_rate;
bool user_engaged;
u32 device_state;
u32 socket_power;
u32 skin_temperature;
u32 rsvd3[2];
u32 platform_type;
u32 rsvd3_1[2];
u32 ambient_light;
u32 length;
u32 avg_c0residency;
u32 max_c0residency;
u32 s0i3_entry;
u32 gfx_busy;
u32 rsvd4[7];
bool camera_state;
u32 workload_type;
u32 display_type;
u32 display_state;
u32 rsvd5_1[17];
u32 bios_input_2[8];
u32 rsvd5[125];
};
struct ta_pmf_load_policy_table {
u32 table_size;
u8 table[POLICY_BUF_MAX_SZ];
};
/* TA initialization params */
struct ta_pmf_init_table {
u32 frequency; /* SMU sampling frequency */
bool validate;
bool sku_check;
bool metadata_macrocheck;
struct ta_pmf_load_policy_table policies_table;
};
/* Everything the TA needs to Enact Policies */
struct ta_pmf_enact_table {
struct ta_pmf_condition_info ev_info;
u32 name;
};
struct ta_pmf_action {
u32 action_index;
u32 value;
u32 spl_arg;
};
/* Output actions from TA */
struct ta_pmf_enact_result {
u32 actions_count;
struct ta_pmf_action actions_list[TA_PMF_ACTION_MAX];
u32 undo_count;
struct ta_pmf_action undo_list[TA_PMF_UNDO_MAX];
};
union ta_pmf_input {
struct ta_pmf_enact_table enact_table;
struct ta_pmf_init_table init_table;
};
union ta_pmf_output {
struct ta_pmf_enact_result policy_apply_table;
u32 rsvd[TA_OUTPUT_RESERVED_MEM];
};
struct ta_pmf_shared_memory {
int command_id;
int resp_id;
u32 pmf_result;
u32 if_version;
union ta_pmf_output pmf_output;
union ta_pmf_input pmf_input;
};
/* Core Layer */
int apmf_acpi_init(struct amd_pmf_dev *pmf_dev);
void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev);
int is_apmf_func_supported(struct amd_pmf_dev *pdev, unsigned long index);
int amd_pmf_send_cmd(struct amd_pmf_dev *dev, u8 message, bool get, u32 arg, u32 *data);
int amd_pmf_init_metrics_table(struct amd_pmf_dev *dev);
int amd_pmf_get_power_source(void);
int apmf_install_handler(struct amd_pmf_dev *pmf_dev);
int apmf_os_power_slider_update(struct amd_pmf_dev *dev, u8 flag);
int amd_pmf_set_dram_addr(struct amd_pmf_dev *dev, bool alloc_buffer);
int amd_pmf_notify_sbios_heartbeat_event_v2(struct amd_pmf_dev *dev, u8 flag);
u32 fixp_q88_fromint(u32 val);
int is_apmf_bios_input_notifications_supported(struct amd_pmf_dev *pdev);
/* SPS Layer */
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf);
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
struct amd_pmf_static_slider_granular *table);
int amd_pmf_init_sps(struct amd_pmf_dev *dev);
int apmf_get_static_slider_granular(struct amd_pmf_dev *pdev,
struct apmf_static_slider_granular_output *output);
bool is_pprof_balanced(struct amd_pmf_dev *pmf);
int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev);
const char *amd_pmf_source_as_str(unsigned int state);
const char *amd_pmf_source_as_str(unsigned int state);
int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx);
int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf);
int apmf_get_static_slider_granular_v2(struct amd_pmf_dev *dev,
struct apmf_static_slider_granular_output_v2 *data);
int apts_get_static_slider_granular_v2(struct amd_pmf_dev *pdev,
struct amd_pmf_apts_granular_output *data, u32 apts_idx);
/* Auto Mode Layer */
int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data);
void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
int apmf_get_sbios_requests(struct amd_pmf_dev *pdev, struct apmf_sbios_req *req);
int apmf_get_sbios_requests_v1(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v1 *req);
int apmf_get_sbios_requests_v2(struct amd_pmf_dev *pdev, struct apmf_sbios_req_v2 *req);
void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event);
int amd_pmf_reset_amt(struct amd_pmf_dev *dev);
void amd_pmf_handle_amt(struct amd_pmf_dev *dev);
/* CnQF Layer */
int apmf_get_dyn_slider_def_ac(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data);
int apmf_get_dyn_slider_def_dc(struct amd_pmf_dev *pdev, struct apmf_dyn_slider_output *data);
int amd_pmf_init_cnqf(struct amd_pmf_dev *dev);
void amd_pmf_deinit_cnqf(struct amd_pmf_dev *dev);
int amd_pmf_trans_cnqf(struct amd_pmf_dev *dev, int socket_power, ktime_t time_lapsed_ms);
extern const struct attribute_group cnqf_feature_attribute_group;
/* Smart PC builder Layer */
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev);
void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev);
int apmf_check_smart_pc(struct amd_pmf_dev *pmf_dev);
int amd_pmf_smartpc_apply_bios_output(struct amd_pmf_dev *dev, u32 val, u32 preq, u32 idx);
/* Smart PC - TA interfaces */
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in);
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev);
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid);
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev);
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev);
#endif /* PMF_H */
@@ -0,0 +1,348 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver - Smart PC Capabilities
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
* Patil Rajesh Reddy <Patil.Reddy@amd.com>
*/
#include <acpi/button.h>
#include <linux/amd-pmf-io.h>
#include <linux/cleanup.h>
#include <linux/power_supply.h>
#include <linux/units.h>
#include "pmf.h"
#ifdef CONFIG_AMD_PMF_DEBUG
static const char *platform_type_as_str(u16 platform_type)
{
switch (platform_type) {
case CLAMSHELL:
return "CLAMSHELL";
case FLAT:
return "FLAT";
case TENT:
return "TENT";
case STAND:
return "STAND";
case TABLET:
return "TABLET";
case BOOK:
return "BOOK";
case PRESENTATION:
return "PRESENTATION";
case PULL_FWD:
return "PULL_FWD";
default:
return "UNKNOWN";
}
}
static const char *laptop_placement_as_str(u16 device_state)
{
switch (device_state) {
case ON_TABLE:
return "ON_TABLE";
case ON_LAP_MOTION:
return "ON_LAP_MOTION";
case IN_BAG:
return "IN_BAG";
case OUT_OF_BAG:
return "OUT_OF_BAG";
default:
return "UNKNOWN";
}
}
static const char *ta_slider_as_str(unsigned int state)
{
switch (state) {
case TA_BEST_PERFORMANCE:
return "PERFORMANCE";
case TA_BETTER_PERFORMANCE:
return "BALANCED";
case TA_BEST_BATTERY:
return "POWER_SAVER";
default:
return "Unknown TA Slider State";
}
}
static u32 amd_pmf_get_ta_custom_bios_inputs(struct ta_pmf_enact_table *in, int index)
{
switch (index) {
case 0 ... 1:
return in->ev_info.bios_input_1[index];
case 2 ... 9:
return in->ev_info.bios_input_2[index - 2];
default:
return 0;
}
}
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
int i;
dev_dbg(dev->dev, "==== TA inputs START ====\n");
dev_dbg(dev->dev, "Slider State: %s\n", ta_slider_as_str(in->ev_info.power_slider));
dev_dbg(dev->dev, "Power Source: %s\n", amd_pmf_source_as_str(in->ev_info.power_source));
dev_dbg(dev->dev, "Battery Percentage: %u\n", in->ev_info.bat_percentage);
dev_dbg(dev->dev, "Designed Battery Capacity: %u\n", in->ev_info.bat_design);
dev_dbg(dev->dev, "Fully Charged Capacity: %u\n", in->ev_info.full_charge_capacity);
dev_dbg(dev->dev, "Drain Rate: %d\n", in->ev_info.drain_rate);
dev_dbg(dev->dev, "Socket Power: %u\n", in->ev_info.socket_power);
dev_dbg(dev->dev, "Skin Temperature: %u\n", in->ev_info.skin_temperature);
dev_dbg(dev->dev, "Avg C0 Residency: %u\n", in->ev_info.avg_c0residency);
dev_dbg(dev->dev, "Max C0 Residency: %u\n", in->ev_info.max_c0residency);
dev_dbg(dev->dev, "GFX Busy: %u\n", in->ev_info.gfx_busy);
dev_dbg(dev->dev, "LID State: %s\n", in->ev_info.lid_state ? "close" : "open");
dev_dbg(dev->dev, "User Presence: %s\n", in->ev_info.user_present ? "Present" : "Away");
dev_dbg(dev->dev, "Ambient Light: %d\n", in->ev_info.ambient_light);
dev_dbg(dev->dev, "Platform type: %s\n", platform_type_as_str(in->ev_info.platform_type));
dev_dbg(dev->dev, "Laptop placement: %s\n",
laptop_placement_as_str(in->ev_info.device_state));
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
dev_dbg(dev->dev, "Custom BIOS input%d: %u\n", i + 1,
amd_pmf_get_ta_custom_bios_inputs(in, i));
dev_dbg(dev->dev, "==== TA inputs END ====\n");
}
#else
void amd_pmf_dump_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in) {}
#endif
/*
* This helper function sets the appropriate BIOS input value in the TA enact
* table based on the provided index. We need this approach because the custom
* BIOS input array is not continuous, due to the existing TA structure layout.
*/
static void amd_pmf_set_ta_custom_bios_input(struct ta_pmf_enact_table *in, int index, u32 value)
{
switch (index) {
case 0 ... 1:
in->ev_info.bios_input_1[index] = value;
break;
case 2 ... 9:
in->ev_info.bios_input_2[index - 2] = value;
break;
default:
return;
}
}
static void amd_pmf_update_bios_inputs(struct amd_pmf_dev *pdev, struct pmf_bios_input_entry *data,
const struct amd_pmf_pb_bitmap *inputs,
struct ta_pmf_enact_table *in)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++) {
if (!(data->preq & inputs[i].bit_mask))
continue;
amd_pmf_set_ta_custom_bios_input(in, i, data->val[i]);
pdev->cb_prev.custom_bios_inputs[i] = data->val[i];
dev_dbg(pdev->dev, "Custom BIOS Input[%d]: %u\n", i, data->val[i]);
}
}
static void amd_pmf_get_custom_bios_inputs(struct amd_pmf_dev *pdev,
struct ta_pmf_enact_table *in)
{
struct pmf_cbi_ring_buffer *rb = &pdev->cbi_buf;
unsigned int i;
guard(mutex)(&pdev->cbi_mutex);
for (i = 0; i < ARRAY_SIZE(custom_bios_inputs); i++)
amd_pmf_set_ta_custom_bios_input(in, i, pdev->cb_prev.custom_bios_inputs[i]);
if (CIRC_CNT(rb->head, rb->tail, CUSTOM_BIOS_INPUT_RING_ENTRIES) == 0)
return;
/* If no active custom BIOS input pending request, do not consume further work */
if (!rb->data[rb->tail].preq)
goto out_rbadvance;
if (!pdev->smart_pc_enabled)
return;
switch (pdev->pmf_if_version) {
case PMF_IF_V1:
if (!is_apmf_bios_input_notifications_supported(pdev))
return;
amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs_v1, in);
break;
case PMF_IF_V2:
amd_pmf_update_bios_inputs(pdev, &rb->data[rb->tail], custom_bios_inputs, in);
break;
default:
break;
}
out_rbadvance:
rb->tail = (rb->tail + 1) & (CUSTOM_BIOS_INPUT_RING_ENTRIES - 1);
}
static void amd_pmf_get_c0_residency(u16 *core_res, size_t size, struct ta_pmf_enact_table *in)
{
u16 max, avg = 0;
int i;
/* Get the avg and max C0 residency of all the cores */
max = *core_res;
for (i = 0; i < size; i++) {
avg += core_res[i];
if (core_res[i] > max)
max = core_res[i];
}
avg = DIV_ROUND_CLOSEST(avg, size);
in->ev_info.avg_c0residency = avg;
in->ev_info.max_c0residency = max;
}
static void amd_pmf_get_smu_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
/* Get the updated metrics table data */
memset(dev->buf, 0, dev->mtable_size);
amd_pmf_send_cmd(dev, SET_TRANSFER_TABLE, SET_CMD, METRICS_TABLE_ID, NULL);
switch (dev->cpu_id) {
case AMD_CPU_ID_PS:
memcpy(&dev->m_table, dev->buf, dev->mtable_size);
in->ev_info.socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
in->ev_info.skin_temperature = dev->m_table.skin_temp;
in->ev_info.gfx_busy = dev->m_table.avg_gfx_activity;
amd_pmf_get_c0_residency(dev->m_table.avg_core_c0residency,
ARRAY_SIZE(dev->m_table.avg_core_c0residency), in);
break;
case PCI_DEVICE_ID_AMD_1AH_M20H_ROOT:
case PCI_DEVICE_ID_AMD_1AH_M60H_ROOT:
memcpy(&dev->m_table_v2, dev->buf, dev->mtable_size);
in->ev_info.socket_power = dev->m_table_v2.apu_power + dev->m_table_v2.dgpu_power;
in->ev_info.skin_temperature = dev->m_table_v2.skin_temp;
in->ev_info.gfx_busy = dev->m_table_v2.gfx_activity;
amd_pmf_get_c0_residency(dev->m_table_v2.core_c0residency,
ARRAY_SIZE(dev->m_table_v2.core_c0residency), in);
break;
default:
dev_err(dev->dev, "Unsupported CPU id: 0x%x", dev->cpu_id);
}
}
static const char * const pmf_battery_supply_name[] = {
"BATT",
"BAT0",
};
static int amd_pmf_get_battery_prop(enum power_supply_property prop)
{
union power_supply_propval value;
struct power_supply *psy;
int i, ret;
for (i = 0; i < ARRAY_SIZE(pmf_battery_supply_name); i++) {
psy = power_supply_get_by_name(pmf_battery_supply_name[i]);
if (!psy)
continue;
ret = power_supply_get_property(psy, prop, &value);
if (ret) {
power_supply_put(psy);
return ret;
}
}
return value.intval;
}
static int amd_pmf_get_battery_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
int val;
val = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_PRESENT);
if (val < 0)
return val;
if (val != 1)
return -ENODEV;
in->ev_info.bat_percentage = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_CAPACITY);
/* all values in mWh metrics */
in->ev_info.bat_design = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN) /
MILLIWATT_PER_WATT;
in->ev_info.full_charge_capacity = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_ENERGY_FULL) /
MILLIWATT_PER_WATT;
in->ev_info.drain_rate = amd_pmf_get_battery_prop(POWER_SUPPLY_PROP_POWER_NOW) /
MILLIWATT_PER_WATT;
return 0;
}
static int amd_pmf_get_slider_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
int val;
switch (dev->current_profile) {
case PLATFORM_PROFILE_PERFORMANCE:
case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
val = TA_BEST_PERFORMANCE;
break;
case PLATFORM_PROFILE_BALANCED:
val = TA_BETTER_PERFORMANCE;
break;
case PLATFORM_PROFILE_LOW_POWER:
case PLATFORM_PROFILE_QUIET:
val = TA_BEST_BATTERY;
break;
default:
dev_err(dev->dev, "Unknown Platform Profile.\n");
return -EOPNOTSUPP;
}
in->ev_info.power_slider = val;
return 0;
}
static void amd_pmf_get_sensor_info(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
struct amd_sfh_info sfh_info;
/* Get the latest information from SFH */
in->ev_info.user_present = false;
/* Get ALS data */
if (!amd_get_sfh_info(&sfh_info, MT_ALS))
in->ev_info.ambient_light = sfh_info.ambient_light;
else
dev_dbg(dev->dev, "ALS is not enabled/detected\n");
/* get HPD data */
if (!amd_get_sfh_info(&sfh_info, MT_HPD)) {
if (sfh_info.user_present == SFH_USER_PRESENT)
in->ev_info.user_present = true;
} else {
dev_dbg(dev->dev, "HPD is not enabled/detected\n");
}
/* Get SRA (Secondary Accelerometer) data */
if (!amd_get_sfh_info(&sfh_info, MT_SRA)) {
in->ev_info.platform_type = sfh_info.platform_type;
in->ev_info.device_state = sfh_info.laptop_placement;
} else {
dev_dbg(dev->dev, "SRA is not enabled/detected\n");
}
}
void amd_pmf_populate_ta_inputs(struct amd_pmf_dev *dev, struct ta_pmf_enact_table *in)
{
/* TA side lid open is 1 and close is 0, hence the ! here */
in->ev_info.lid_state = !acpi_lid_open();
in->ev_info.power_source = amd_pmf_get_power_source();
amd_pmf_get_smu_info(dev, in);
amd_pmf_get_battery_info(dev, in);
amd_pmf_get_slider_info(dev, in);
amd_pmf_get_sensor_info(dev, in);
amd_pmf_get_custom_bios_inputs(dev, in);
}
@@ -0,0 +1,444 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework (PMF) Driver
*
* Copyright (c) 2022, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include "pmf.h"
static struct amd_pmf_static_slider_granular_v2 config_store_v2;
static struct amd_pmf_static_slider_granular config_store;
static struct amd_pmf_apts_granular apts_config_store;
#ifdef CONFIG_AMD_PMF_DEBUG
static const char *slider_v2_as_str(unsigned int state)
{
switch (state) {
case POWER_MODE_BEST_PERFORMANCE:
return "Best Performance";
case POWER_MODE_BALANCED:
return "Balanced";
case POWER_MODE_BEST_POWER_EFFICIENCY:
return "Best Power Efficiency";
case POWER_MODE_ENERGY_SAVE:
return "Energy Save";
default:
return "Unknown Power Mode";
}
}
static const char *slider_as_str(unsigned int state)
{
switch (state) {
case POWER_MODE_PERFORMANCE:
return "PERFORMANCE";
case POWER_MODE_BALANCED_POWER:
return "BALANCED_POWER";
case POWER_MODE_POWER_SAVER:
return "POWER_SAVER";
default:
return "Unknown Slider State";
}
}
const char *amd_pmf_source_as_str(unsigned int state)
{
switch (state) {
case POWER_SOURCE_AC:
return "AC";
case POWER_SOURCE_DC:
return "DC";
default:
return "Unknown Power State";
}
}
static void amd_pmf_dump_sps_defaults(struct amd_pmf_static_slider_granular *data)
{
int i, j;
pr_debug("Static Slider Data - BEGIN\n");
for (i = 0; i < POWER_SOURCE_MAX; i++) {
for (j = 0; j < POWER_MODE_MAX; j++) {
pr_debug("--- Source:%s Mode:%s ---\n", amd_pmf_source_as_str(i),
slider_as_str(j));
pr_debug("SPL: %u mW\n", data->prop[i][j].spl);
pr_debug("SPPT: %u mW\n", data->prop[i][j].sppt);
pr_debug("SPPT_ApuOnly: %u mW\n", data->prop[i][j].sppt_apu_only);
pr_debug("FPPT: %u mW\n", data->prop[i][j].fppt);
pr_debug("STTMinLimit: %u mW\n", data->prop[i][j].stt_min);
pr_debug("STT_SkinTempLimit_APU: %u C\n",
data->prop[i][j].stt_skin_temp[STT_TEMP_APU]);
pr_debug("STT_SkinTempLimit_HS2: %u C\n",
data->prop[i][j].stt_skin_temp[STT_TEMP_HS2]);
}
}
pr_debug("Static Slider Data - END\n");
}
static void amd_pmf_dump_sps_defaults_v2(struct amd_pmf_static_slider_granular_v2 *data)
{
unsigned int i, j;
pr_debug("Static Slider APTS state index data - BEGIN");
pr_debug("size: %u\n", data->size);
for (i = 0; i < POWER_SOURCE_MAX; i++)
for (j = 0; j < POWER_MODE_V2_MAX; j++)
pr_debug("%s %s: %u\n", amd_pmf_source_as_str(i), slider_v2_as_str(j),
data->sps_idx.power_states[i][j]);
pr_debug("Static Slider APTS state index data - END\n");
}
static void amd_pmf_dump_apts_sps_defaults(struct amd_pmf_apts_granular *info)
{
int i;
pr_debug("Static Slider APTS index default values data - BEGIN");
for (i = 0; i < APTS_MAX_STATES; i++) {
pr_debug("Table Version[%d] = %u\n", i, info->val[i].table_version);
pr_debug("Fan Index[%d] = %u\n", i, info->val[i].fan_table_idx);
pr_debug("PPT[%d] = %u\n", i, info->val[i].pmf_ppt);
pr_debug("PPT APU[%d] = %u\n", i, info->val[i].ppt_pmf_apu_only);
pr_debug("STT Min[%d] = %u\n", i, info->val[i].stt_min_limit);
pr_debug("STT APU[%d] = %u\n", i, info->val[i].stt_skin_temp_limit_apu);
pr_debug("STT HS2[%d] = %u\n", i, info->val[i].stt_skin_temp_limit_hs2);
}
pr_debug("Static Slider APTS index default values data - END");
}
#else
static void amd_pmf_dump_sps_defaults(struct amd_pmf_static_slider_granular *data) {}
static void amd_pmf_dump_sps_defaults_v2(struct amd_pmf_static_slider_granular_v2 *data) {}
static void amd_pmf_dump_apts_sps_defaults(struct amd_pmf_apts_granular *info) {}
#endif
static void amd_pmf_load_apts_defaults_sps_v2(struct amd_pmf_dev *pdev)
{
struct amd_pmf_apts_granular_output output;
struct amd_pmf_apts_output *ps;
int i;
memset(&apts_config_store, 0, sizeof(apts_config_store));
ps = apts_config_store.val;
for (i = 0; i < APTS_MAX_STATES; i++) {
apts_get_static_slider_granular_v2(pdev, &output, i);
ps[i].table_version = output.val.table_version;
ps[i].fan_table_idx = output.val.fan_table_idx;
ps[i].pmf_ppt = output.val.pmf_ppt;
ps[i].ppt_pmf_apu_only = output.val.ppt_pmf_apu_only;
ps[i].stt_min_limit = output.val.stt_min_limit;
ps[i].stt_skin_temp_limit_apu = output.val.stt_skin_temp_limit_apu;
ps[i].stt_skin_temp_limit_hs2 = output.val.stt_skin_temp_limit_hs2;
}
amd_pmf_dump_apts_sps_defaults(&apts_config_store);
}
static void amd_pmf_load_defaults_sps_v2(struct amd_pmf_dev *dev)
{
struct apmf_static_slider_granular_output_v2 output;
unsigned int i, j;
memset(&config_store_v2, 0, sizeof(config_store_v2));
apmf_get_static_slider_granular_v2(dev, &output);
config_store_v2.size = output.size;
for (i = 0; i < POWER_SOURCE_MAX; i++)
for (j = 0; j < POWER_MODE_V2_MAX; j++)
config_store_v2.sps_idx.power_states[i][j] =
output.sps_idx.power_states[i][j];
amd_pmf_dump_sps_defaults_v2(&config_store_v2);
}
static void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev)
{
struct apmf_static_slider_granular_output output;
int i, j, idx = 0;
memset(&config_store, 0, sizeof(config_store));
apmf_get_static_slider_granular(dev, &output);
for (i = 0; i < POWER_SOURCE_MAX; i++) {
for (j = 0; j < POWER_MODE_MAX; j++) {
config_store.prop[i][j].spl = output.prop[idx].spl;
config_store.prop[i][j].sppt = output.prop[idx].sppt;
config_store.prop[i][j].sppt_apu_only =
output.prop[idx].sppt_apu_only;
config_store.prop[i][j].fppt = output.prop[idx].fppt;
config_store.prop[i][j].stt_min = output.prop[idx].stt_min;
config_store.prop[i][j].stt_skin_temp[STT_TEMP_APU] =
output.prop[idx].stt_skin_temp[STT_TEMP_APU];
config_store.prop[i][j].stt_skin_temp[STT_TEMP_HS2] =
output.prop[idx].stt_skin_temp[STT_TEMP_HS2];
config_store.prop[i][j].fan_id = output.prop[idx].fan_id;
idx++;
}
}
amd_pmf_dump_sps_defaults(&config_store);
}
static void amd_pmf_update_slider_v2(struct amd_pmf_dev *dev, int idx)
{
amd_pmf_send_cmd(dev, SET_PMF_PPT, SET_CMD, apts_config_store.val[idx].pmf_ppt, NULL);
amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, SET_CMD,
apts_config_store.val[idx].ppt_pmf_apu_only, NULL);
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD,
apts_config_store.val[idx].stt_min_limit, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
fixp_q88_fromint(apts_config_store.val[idx].stt_skin_temp_limit_apu),
NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
fixp_q88_fromint(apts_config_store.val[idx].stt_skin_temp_limit_hs2),
NULL);
}
void amd_pmf_update_slider(struct amd_pmf_dev *dev, bool op, int idx,
struct amd_pmf_static_slider_granular *table)
{
int src = amd_pmf_get_power_source();
if (op == SLIDER_OP_SET) {
amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, config_store.prop[src][idx].spl, NULL);
amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, config_store.prop[src][idx].fppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, config_store.prop[src][idx].sppt, NULL);
amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD,
config_store.prop[src][idx].sppt_apu_only, NULL);
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD,
config_store.prop[src][idx].stt_min, NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
fixp_q88_fromint(config_store.prop[src][idx].stt_skin_temp[STT_TEMP_APU]),
NULL);
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
fixp_q88_fromint(config_store.prop[src][idx].stt_skin_temp[STT_TEMP_HS2]),
NULL);
} else if (op == SLIDER_OP_GET) {
amd_pmf_send_cmd(dev, GET_SPL, GET_CMD, ARG_NONE, &table->prop[src][idx].spl);
amd_pmf_send_cmd(dev, GET_FPPT, GET_CMD, ARG_NONE, &table->prop[src][idx].fppt);
amd_pmf_send_cmd(dev, GET_SPPT, GET_CMD, ARG_NONE, &table->prop[src][idx].sppt);
amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, GET_CMD, ARG_NONE,
&table->prop[src][idx].sppt_apu_only);
amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, GET_CMD, ARG_NONE,
&table->prop[src][idx].stt_min);
amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, GET_CMD, ARG_NONE,
(u32 *)&table->prop[src][idx].stt_skin_temp[STT_TEMP_APU]);
amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, GET_CMD, ARG_NONE,
(u32 *)&table->prop[src][idx].stt_skin_temp[STT_TEMP_HS2]);
}
}
static int amd_pmf_update_sps_power_limits_v2(struct amd_pmf_dev *pdev, int pwr_mode)
{
int src, index;
src = amd_pmf_get_power_source();
switch (pwr_mode) {
case POWER_MODE_PERFORMANCE:
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_PERFORMANCE];
amd_pmf_update_slider_v2(pdev, index);
break;
case POWER_MODE_BALANCED_POWER:
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BALANCED];
amd_pmf_update_slider_v2(pdev, index);
break;
case POWER_MODE_POWER_SAVER:
index = config_store_v2.sps_idx.power_states[src][POWER_MODE_BEST_POWER_EFFICIENCY];
amd_pmf_update_slider_v2(pdev, index);
break;
default:
return -EINVAL;
}
return 0;
}
int amd_pmf_set_sps_power_limits(struct amd_pmf_dev *pmf)
{
int mode;
mode = amd_pmf_get_pprof_modes(pmf);
if (mode < 0)
return mode;
if (pmf->pmf_if_version == PMF_IF_V2)
return amd_pmf_update_sps_power_limits_v2(pmf, mode);
amd_pmf_update_slider(pmf, SLIDER_OP_SET, mode, NULL);
return 0;
}
bool is_pprof_balanced(struct amd_pmf_dev *pmf)
{
return pmf->current_profile == PLATFORM_PROFILE_BALANCED;
}
static int amd_pmf_profile_get(struct device *dev,
enum platform_profile_option *profile)
{
struct amd_pmf_dev *pmf = dev_get_drvdata(dev);
*profile = pmf->current_profile;
return 0;
}
int amd_pmf_get_pprof_modes(struct amd_pmf_dev *pmf)
{
int mode;
switch (pmf->current_profile) {
case PLATFORM_PROFILE_PERFORMANCE:
case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
mode = POWER_MODE_PERFORMANCE;
break;
case PLATFORM_PROFILE_BALANCED:
mode = POWER_MODE_BALANCED_POWER;
break;
case PLATFORM_PROFILE_LOW_POWER:
case PLATFORM_PROFILE_QUIET:
mode = POWER_MODE_POWER_SAVER;
break;
default:
dev_err(pmf->dev, "Unknown Platform Profile.\n");
return -EOPNOTSUPP;
}
return mode;
}
int amd_pmf_power_slider_update_event(struct amd_pmf_dev *dev)
{
u8 flag = 0;
int mode;
int src;
mode = amd_pmf_get_pprof_modes(dev);
if (mode < 0)
return mode;
src = amd_pmf_get_power_source();
if (src == POWER_SOURCE_AC) {
switch (mode) {
case POWER_MODE_PERFORMANCE:
flag |= BIT(AC_BEST_PERF);
break;
case POWER_MODE_BALANCED_POWER:
flag |= BIT(AC_BETTER_PERF);
break;
case POWER_MODE_POWER_SAVER:
flag |= BIT(AC_BETTER_BATTERY);
break;
default:
dev_err(dev->dev, "unsupported platform profile\n");
return -EOPNOTSUPP;
}
} else if (src == POWER_SOURCE_DC) {
switch (mode) {
case POWER_MODE_PERFORMANCE:
flag |= BIT(DC_BEST_PERF);
break;
case POWER_MODE_BALANCED_POWER:
flag |= BIT(DC_BETTER_PERF);
break;
case POWER_MODE_POWER_SAVER:
flag |= BIT(DC_BATTERY_SAVER);
break;
default:
dev_err(dev->dev, "unsupported platform profile\n");
return -EOPNOTSUPP;
}
}
apmf_os_power_slider_update(dev, flag);
return 0;
}
static int amd_pmf_profile_set(struct device *dev,
enum platform_profile_option profile)
{
struct amd_pmf_dev *pmf = dev_get_drvdata(dev);
int ret = 0;
pmf->current_profile = profile;
/* Notify EC about the slider position change */
if (is_apmf_func_supported(pmf, APMF_FUNC_OS_POWER_SLIDER_UPDATE)) {
ret = amd_pmf_power_slider_update_event(pmf);
if (ret)
return ret;
}
if (is_apmf_func_supported(pmf, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
ret = amd_pmf_set_sps_power_limits(pmf);
if (ret)
return ret;
}
return 0;
}
static int amd_pmf_hidden_choices(void *drvdata, unsigned long *choices)
{
set_bit(PLATFORM_PROFILE_QUIET, choices);
set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, choices);
return 0;
}
static int amd_pmf_profile_probe(void *drvdata, unsigned long *choices)
{
set_bit(PLATFORM_PROFILE_LOW_POWER, choices);
set_bit(PLATFORM_PROFILE_BALANCED, choices);
set_bit(PLATFORM_PROFILE_PERFORMANCE, choices);
return 0;
}
static const struct platform_profile_ops amd_pmf_profile_ops = {
.probe = amd_pmf_profile_probe,
.hidden_choices = amd_pmf_hidden_choices,
.profile_get = amd_pmf_profile_get,
.profile_set = amd_pmf_profile_set,
};
int amd_pmf_init_sps(struct amd_pmf_dev *dev)
{
dev->current_profile = PLATFORM_PROFILE_BALANCED;
if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
if (dev->pmf_if_version == PMF_IF_V2) {
amd_pmf_load_defaults_sps_v2(dev);
amd_pmf_load_apts_defaults_sps_v2(dev);
} else {
amd_pmf_load_defaults_sps(dev);
}
/* update SPS balanced power mode thermals */
amd_pmf_set_sps_power_limits(dev);
}
/* Create platform_profile structure and register */
dev->ppdev = devm_platform_profile_register(dev->dev, "amd-pmf", dev,
&amd_pmf_profile_ops);
if (IS_ERR(dev->ppdev))
dev_err(dev->dev, "Failed to register SPS support, this is most likely an SBIOS bug: %ld\n",
PTR_ERR(dev->ppdev));
return PTR_ERR_OR_ZERO(dev->ppdev);
}
@@ -0,0 +1,625 @@
// SPDX-License-Identifier: GPL-2.0
/*
* AMD Platform Management Framework Driver - TEE Interface
*
* Copyright (c) 2023, Advanced Micro Devices, Inc.
* All Rights Reserved.
*
* Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
*/
#include <linux/debugfs.h>
#include <linux/tee_drv.h>
#include <linux/uuid.h>
#include "pmf.h"
#define MAX_TEE_PARAM 4
/* Policy binary actions sampling frequency (in ms) */
static int pb_actions_ms = MSEC_PER_SEC;
/* Sideload policy binaries to debug policy failures */
static bool pb_side_load;
#ifdef CONFIG_AMD_PMF_DEBUG
module_param(pb_actions_ms, int, 0644);
MODULE_PARM_DESC(pb_actions_ms, "Policy binary actions sampling frequency (default = 1000ms)");
module_param(pb_side_load, bool, 0444);
MODULE_PARM_DESC(pb_side_load, "Sideload policy binaries debug policy failures");
#endif
static const char *amd_pmf_uevent_as_str(unsigned int state)
{
switch (state) {
case SYSTEM_STATE_S0i3:
return "S0i3";
case SYSTEM_STATE_S4:
return "S4";
case SYSTEM_STATE_SCREEN_LOCK:
return "SCREEN_LOCK";
default:
return "Unknown Smart PC event";
}
}
static void amd_pmf_prepare_args(struct amd_pmf_dev *dev, int cmd,
struct tee_ioctl_invoke_arg *arg,
struct tee_param *param)
{
memset(arg, 0, sizeof(*arg));
memset(param, 0, MAX_TEE_PARAM * sizeof(*param));
arg->func = cmd;
arg->session = dev->session_id;
arg->num_params = MAX_TEE_PARAM;
/* Fill invoke cmd params */
param[0].u.memref.size = sizeof(struct ta_pmf_shared_memory);
param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT;
param[0].u.memref.shm = dev->fw_shm_pool;
param[0].u.memref.shm_offs = 0;
}
static void amd_pmf_update_uevents(struct amd_pmf_dev *dev, u16 event)
{
input_report_key(dev->pmf_idev, event, 1); /* key press */
input_sync(dev->pmf_idev);
input_report_key(dev->pmf_idev, event, 0); /* key release */
input_sync(dev->pmf_idev);
}
static int amd_pmf_get_bios_output_idx(u32 action_idx)
{
switch (action_idx) {
case PMF_POLICY_BIOS_OUTPUT_1:
return 0;
case PMF_POLICY_BIOS_OUTPUT_2:
return 1;
case PMF_POLICY_BIOS_OUTPUT_3:
return 2;
case PMF_POLICY_BIOS_OUTPUT_4:
return 3;
case PMF_POLICY_BIOS_OUTPUT_5:
return 4;
case PMF_POLICY_BIOS_OUTPUT_6:
return 5;
case PMF_POLICY_BIOS_OUTPUT_7:
return 6;
case PMF_POLICY_BIOS_OUTPUT_8:
return 7;
case PMF_POLICY_BIOS_OUTPUT_9:
return 8;
case PMF_POLICY_BIOS_OUTPUT_10:
return 9;
default:
return -EINVAL;
}
}
static void amd_pmf_update_bios_output(struct amd_pmf_dev *pdev, struct ta_pmf_action *action)
{
u32 bios_idx;
bios_idx = amd_pmf_get_bios_output_idx(action->action_index);
amd_pmf_smartpc_apply_bios_output(pdev, action->value, BIT(bios_idx), bios_idx);
}
static void amd_pmf_apply_policies(struct amd_pmf_dev *dev, struct ta_pmf_enact_result *out)
{
struct ta_pmf_action *action;
u32 val;
int idx;
for (idx = 0; idx < out->actions_count; idx++) {
action = &out->actions_list[idx];
val = action->value;
switch (action->action_index) {
case PMF_POLICY_SPL:
if (dev->prev_data->spl != val) {
amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update SPL: %u\n", val);
dev->prev_data->spl = val;
}
break;
case PMF_POLICY_SPPT:
if (dev->prev_data->sppt != val) {
amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update SPPT: %u\n", val);
dev->prev_data->sppt = val;
}
break;
case PMF_POLICY_FPPT:
if (dev->prev_data->fppt != val) {
amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update FPPT: %u\n", val);
dev->prev_data->fppt = val;
}
break;
case PMF_POLICY_SPPT_APU_ONLY:
if (dev->prev_data->sppt_apuonly != val) {
amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update SPPT_APU_ONLY: %u\n", val);
dev->prev_data->sppt_apuonly = val;
}
break;
case PMF_POLICY_STT_MIN:
if (dev->prev_data->stt_minlimit != val) {
amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update STT_MIN: %u\n", val);
dev->prev_data->stt_minlimit = val;
}
break;
case PMF_POLICY_STT_SKINTEMP_APU:
if (dev->prev_data->stt_skintemp_apu != val) {
amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
fixp_q88_fromint(val), NULL);
dev_dbg(dev->dev, "update STT_SKINTEMP_APU: %u\n", val);
dev->prev_data->stt_skintemp_apu = val;
}
break;
case PMF_POLICY_STT_SKINTEMP_HS2:
if (dev->prev_data->stt_skintemp_hs2 != val) {
amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
fixp_q88_fromint(val), NULL);
dev_dbg(dev->dev, "update STT_SKINTEMP_HS2: %u\n", val);
dev->prev_data->stt_skintemp_hs2 = val;
}
break;
case PMF_POLICY_P3T:
if (dev->prev_data->p3t_limit != val) {
amd_pmf_send_cmd(dev, SET_P3T, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update P3T: %u\n", val);
dev->prev_data->p3t_limit = val;
}
break;
case PMF_POLICY_PMF_PPT:
if (dev->prev_data->pmf_ppt != val) {
amd_pmf_send_cmd(dev, SET_PMF_PPT, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update PMF PPT: %u\n", val);
dev->prev_data->pmf_ppt = val;
}
break;
case PMF_POLICY_PMF_PPT_APU_ONLY:
if (dev->prev_data->pmf_ppt_apu_only != val) {
amd_pmf_send_cmd(dev, SET_PMF_PPT_APU_ONLY, SET_CMD, val, NULL);
dev_dbg(dev->dev, "update PMF PPT APU ONLY: %u\n", val);
dev->prev_data->pmf_ppt_apu_only = val;
}
break;
case PMF_POLICY_SYSTEM_STATE:
switch (val) {
case 0:
amd_pmf_update_uevents(dev, KEY_SLEEP);
break;
case 1:
amd_pmf_update_uevents(dev, KEY_SUSPEND);
break;
case 2:
amd_pmf_update_uevents(dev, KEY_SCREENLOCK);
break;
default:
dev_err(dev->dev, "Invalid PMF policy system state: %d\n", val);
}
dev_dbg(dev->dev, "update SYSTEM_STATE: %s\n",
amd_pmf_uevent_as_str(val));
break;
case PMF_POLICY_BIOS_OUTPUT_1:
case PMF_POLICY_BIOS_OUTPUT_2:
case PMF_POLICY_BIOS_OUTPUT_3:
case PMF_POLICY_BIOS_OUTPUT_4:
case PMF_POLICY_BIOS_OUTPUT_5:
case PMF_POLICY_BIOS_OUTPUT_6:
case PMF_POLICY_BIOS_OUTPUT_7:
case PMF_POLICY_BIOS_OUTPUT_8:
case PMF_POLICY_BIOS_OUTPUT_9:
case PMF_POLICY_BIOS_OUTPUT_10:
amd_pmf_update_bios_output(dev, action);
break;
}
}
}
int amd_pmf_invoke_cmd_enact(struct amd_pmf_dev *dev)
{
struct ta_pmf_shared_memory *ta_sm = NULL;
struct ta_pmf_enact_result *out = NULL;
struct ta_pmf_enact_table *in = NULL;
struct tee_param param[MAX_TEE_PARAM];
struct tee_ioctl_invoke_arg arg;
int ret = 0;
if (!dev->tee_ctx)
return -ENODEV;
memset(dev->shbuf, 0, dev->policy_sz);
ta_sm = dev->shbuf;
out = &ta_sm->pmf_output.policy_apply_table;
in = &ta_sm->pmf_input.enact_table;
memset(ta_sm, 0, sizeof(*ta_sm));
ta_sm->command_id = TA_PMF_COMMAND_POLICY_BUILDER_ENACT_POLICIES;
ta_sm->if_version = PMF_TA_IF_VERSION_MAJOR;
amd_pmf_populate_ta_inputs(dev, in);
amd_pmf_prepare_args(dev, TA_PMF_COMMAND_POLICY_BUILDER_ENACT_POLICIES, &arg, param);
ret = tee_client_invoke_func(dev->tee_ctx, &arg, param);
if (ret < 0 || arg.ret != 0) {
dev_err(dev->dev, "TEE enact cmd failed. err: %x, ret:%d\n", arg.ret, ret);
return ret;
}
if (ta_sm->pmf_result == TA_PMF_TYPE_SUCCESS && out->actions_count) {
amd_pmf_dump_ta_inputs(dev, in);
dev_dbg(dev->dev, "action count:%u result:%x\n", out->actions_count,
ta_sm->pmf_result);
amd_pmf_apply_policies(dev, out);
}
return 0;
}
static int amd_pmf_invoke_cmd_init(struct amd_pmf_dev *dev)
{
struct ta_pmf_shared_memory *ta_sm = NULL;
struct tee_param param[MAX_TEE_PARAM];
struct ta_pmf_init_table *in = NULL;
struct tee_ioctl_invoke_arg arg;
int ret = 0;
if (!dev->tee_ctx) {
dev_err(dev->dev, "Failed to get TEE context\n");
return -ENODEV;
}
dev_dbg(dev->dev, "Policy Binary size: %llu bytes\n", (unsigned long long)dev->policy_sz);
memset(dev->shbuf, 0, dev->policy_sz);
ta_sm = dev->shbuf;
in = &ta_sm->pmf_input.init_table;
ta_sm->command_id = TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE;
ta_sm->if_version = PMF_TA_IF_VERSION_MAJOR;
in->metadata_macrocheck = false;
in->sku_check = false;
in->validate = true;
in->frequency = pb_actions_ms;
in->policies_table.table_size = dev->policy_sz;
memcpy(in->policies_table.table, dev->policy_buf, dev->policy_sz);
amd_pmf_prepare_args(dev, TA_PMF_COMMAND_POLICY_BUILDER_INITIALIZE, &arg, param);
ret = tee_client_invoke_func(dev->tee_ctx, &arg, param);
if (ret < 0 || arg.ret != 0) {
dev_err(dev->dev, "Failed to invoke TEE init cmd. err: %x, ret:%d\n", arg.ret, ret);
return ret;
}
return ta_sm->pmf_result;
}
static void amd_pmf_invoke_cmd(struct work_struct *work)
{
struct amd_pmf_dev *dev = container_of(work, struct amd_pmf_dev, pb_work.work);
amd_pmf_invoke_cmd_enact(dev);
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms));
}
int amd_pmf_start_policy_engine(struct amd_pmf_dev *dev)
{
struct cookie_header *header;
int res;
if (dev->policy_sz < POLICY_COOKIE_OFFSET + sizeof(*header))
return -EINVAL;
header = (struct cookie_header *)(dev->policy_buf + POLICY_COOKIE_OFFSET);
if (header->sign != POLICY_SIGN_COOKIE || !header->length) {
dev_dbg(dev->dev, "cookie doesn't match\n");
return -EINVAL;
}
if (dev->policy_sz < header->length + 512)
return -EINVAL;
/* Update the actual length */
dev->policy_sz = header->length + 512;
res = amd_pmf_invoke_cmd_init(dev);
if (res == TA_PMF_TYPE_SUCCESS) {
/* Now its safe to announce that smart pc is enabled */
dev->smart_pc_enabled = true;
/*
* Start collecting the data from TA FW after a small delay
* or else, we might end up getting stale values.
*/
schedule_delayed_work(&dev->pb_work, msecs_to_jiffies(pb_actions_ms * 3));
} else {
dev_dbg(dev->dev, "ta invoke cmd init failed err: %x\n", res);
dev->smart_pc_enabled = false;
return res;
}
return 0;
}
static inline bool amd_pmf_pb_valid(struct amd_pmf_dev *dev)
{
return memchr_inv(dev->policy_buf, 0xff, dev->policy_sz);
}
#ifdef CONFIG_AMD_PMF_DEBUG
static void amd_pmf_hex_dump_pb(struct amd_pmf_dev *dev)
{
print_hex_dump_debug("(pb): ", DUMP_PREFIX_OFFSET, 16, 1, dev->policy_buf,
dev->policy_sz, false);
}
static ssize_t amd_pmf_get_pb_data(struct file *filp, const char __user *buf,
size_t length, loff_t *pos)
{
struct amd_pmf_dev *dev = filp->private_data;
unsigned char *new_policy_buf;
int ret;
/* Policy binary size cannot exceed POLICY_BUF_MAX_SZ */
if (length > POLICY_BUF_MAX_SZ || length == 0)
return -EINVAL;
/* re-alloc to the new buffer length of the policy binary */
new_policy_buf = devm_kzalloc(dev->dev, length, GFP_KERNEL);
if (!new_policy_buf)
return -ENOMEM;
if (copy_from_user(new_policy_buf, buf, length)) {
devm_kfree(dev->dev, new_policy_buf);
return -EFAULT;
}
devm_kfree(dev->dev, dev->policy_buf);
dev->policy_buf = new_policy_buf;
dev->policy_sz = length;
if (!amd_pmf_pb_valid(dev))
return -EINVAL;
amd_pmf_hex_dump_pb(dev);
ret = amd_pmf_start_policy_engine(dev);
if (ret < 0)
return ret;
return length;
}
static const struct file_operations pb_fops = {
.write = amd_pmf_get_pb_data,
.open = simple_open,
};
static void amd_pmf_open_pb(struct amd_pmf_dev *dev, struct dentry *debugfs_root)
{
dev->esbin = debugfs_create_dir("pb", debugfs_root);
debugfs_create_file("update_policy", 0644, dev->esbin, dev, &pb_fops);
}
static void amd_pmf_remove_pb(struct amd_pmf_dev *dev)
{
debugfs_remove_recursive(dev->esbin);
}
#else
static void amd_pmf_open_pb(struct amd_pmf_dev *dev, struct dentry *debugfs_root) {}
static void amd_pmf_remove_pb(struct amd_pmf_dev *dev) {}
static void amd_pmf_hex_dump_pb(struct amd_pmf_dev *dev) {}
#endif
static int amd_pmf_amdtee_ta_match(struct tee_ioctl_version_data *ver, const void *data)
{
return ver->impl_id == TEE_IMPL_ID_AMDTEE;
}
static int amd_pmf_ta_open_session(struct tee_context *ctx, u32 *id, const uuid_t *uuid)
{
struct tee_ioctl_open_session_arg sess_arg = {};
int rc;
export_uuid(sess_arg.uuid, uuid);
sess_arg.clnt_login = TEE_IOCTL_LOGIN_PUBLIC;
sess_arg.num_params = 0;
rc = tee_client_open_session(ctx, &sess_arg, NULL);
if (rc < 0 || sess_arg.ret != 0) {
pr_err("Failed to open TEE session err:%#x, rc:%d\n", sess_arg.ret, rc);
return rc ?: -EINVAL;
}
*id = sess_arg.session;
return 0;
}
static int amd_pmf_register_input_device(struct amd_pmf_dev *dev)
{
int err;
dev->pmf_idev = devm_input_allocate_device(dev->dev);
if (!dev->pmf_idev)
return -ENOMEM;
dev->pmf_idev->name = "PMF-TA output events";
dev->pmf_idev->phys = "amd-pmf/input0";
input_set_capability(dev->pmf_idev, EV_KEY, KEY_SLEEP);
input_set_capability(dev->pmf_idev, EV_KEY, KEY_SCREENLOCK);
input_set_capability(dev->pmf_idev, EV_KEY, KEY_SUSPEND);
err = input_register_device(dev->pmf_idev);
if (err) {
dev_err(dev->dev, "Failed to register input device: %d\n", err);
return err;
}
return 0;
}
int amd_pmf_tee_init(struct amd_pmf_dev *dev, const uuid_t *uuid)
{
u32 size;
int ret;
dev->tee_ctx = tee_client_open_context(NULL, amd_pmf_amdtee_ta_match, NULL, NULL);
if (IS_ERR(dev->tee_ctx)) {
dev_err(dev->dev, "Failed to open TEE context\n");
ret = PTR_ERR(dev->tee_ctx);
dev->tee_ctx = NULL;
return ret;
}
ret = amd_pmf_ta_open_session(dev->tee_ctx, &dev->session_id, uuid);
if (ret) {
dev_err(dev->dev, "Failed to open TA session (%d)\n", ret);
ret = -EINVAL;
goto out_ctx;
}
size = sizeof(struct ta_pmf_shared_memory) + dev->policy_sz;
dev->fw_shm_pool = tee_shm_alloc_kernel_buf(dev->tee_ctx, size);
if (IS_ERR(dev->fw_shm_pool)) {
dev_err(dev->dev, "Failed to alloc TEE shared memory\n");
ret = PTR_ERR(dev->fw_shm_pool);
goto out_sess;
}
dev->shbuf = tee_shm_get_va(dev->fw_shm_pool, 0);
if (IS_ERR(dev->shbuf)) {
dev_err(dev->dev, "Failed to get TEE virtual address\n");
ret = PTR_ERR(dev->shbuf);
goto out_shm;
}
dev_dbg(dev->dev, "TEE init done\n");
return 0;
out_shm:
tee_shm_free(dev->fw_shm_pool);
out_sess:
tee_client_close_session(dev->tee_ctx, dev->session_id);
out_ctx:
tee_client_close_context(dev->tee_ctx);
return ret;
}
void amd_pmf_tee_deinit(struct amd_pmf_dev *dev)
{
if (!dev->tee_ctx)
return;
tee_shm_free(dev->fw_shm_pool);
tee_client_close_session(dev->tee_ctx, dev->session_id);
tee_client_close_context(dev->tee_ctx);
dev->tee_ctx = NULL;
}
int amd_pmf_init_smart_pc(struct amd_pmf_dev *dev)
{
bool status;
int ret, i;
ret = apmf_check_smart_pc(dev);
if (ret) {
/*
* Lets not return from here if Smart PC bit is not advertised in
* the BIOS. This way, there will be some amount of power savings
* to the user with static slider (if enabled).
*/
dev_info(dev->dev, "PMF Smart PC not advertised in BIOS!:%d\n", ret);
return -ENODEV;
}
INIT_DELAYED_WORK(&dev->pb_work, amd_pmf_invoke_cmd);
ret = amd_pmf_set_dram_addr(dev, true);
if (ret)
return ret;
dev->policy_base = devm_ioremap_resource(dev->dev, dev->res);
if (IS_ERR(dev->policy_base))
return PTR_ERR(dev->policy_base);
dev->policy_buf = devm_kzalloc(dev->dev, dev->policy_sz, GFP_KERNEL);
if (!dev->policy_buf)
return -ENOMEM;
memcpy_fromio(dev->policy_buf, dev->policy_base, dev->policy_sz);
if (!amd_pmf_pb_valid(dev)) {
dev_info(dev->dev, "No Smart PC policy present\n");
return -EINVAL;
}
amd_pmf_hex_dump_pb(dev);
dev->prev_data = devm_kzalloc(dev->dev, sizeof(*dev->prev_data), GFP_KERNEL);
if (!dev->prev_data)
return -ENOMEM;
for (i = 0; i < ARRAY_SIZE(amd_pmf_ta_uuid); i++) {
ret = amd_pmf_tee_init(dev, &amd_pmf_ta_uuid[i]);
if (ret)
return ret;
ret = amd_pmf_start_policy_engine(dev);
dev_dbg(dev->dev, "start policy engine ret: %d\n", ret);
status = ret == TA_PMF_TYPE_SUCCESS;
if (status) {
dev->cb_flag = true;
dev->cbi_buf.head = 0;
dev->cbi_buf.tail = 0;
break;
}
amd_pmf_tee_deinit(dev);
}
if (!status && !pb_side_load) {
ret = -EINVAL;
goto err;
}
if (pb_side_load)
amd_pmf_open_pb(dev, dev->dbgfs_dir);
ret = amd_pmf_register_input_device(dev);
if (ret)
goto err;
return 0;
err:
amd_pmf_deinit_smart_pc(dev);
return ret;
}
void amd_pmf_deinit_smart_pc(struct amd_pmf_dev *dev)
{
if (dev->pmf_idev)
input_unregister_device(dev->pmf_idev);
if (pb_side_load && dev->esbin)
amd_pmf_remove_pb(dev);
cancel_delayed_work_sync(&dev->pb_work);
amd_pmf_tee_deinit(dev);
}