diff mbox series

[v1,12/15] platform/x86/amd/pmf: Add support for Auto mode feature

Message ID 20220712145847.3438544-13-Shyam-sundar.S-k@amd.com (mailing list archive)
State Changes Requested, archived
Headers show
Series platform/x86/amd/pmf: Introduce AMD PMF Driver | expand

Commit Message

Shyam Sundar S K July 12, 2022, 2:58 p.m. UTC
The objective of this feature is to track the moving average of system
power over the time period specified and switch to the subsequent mode.

This feature has 3 modes: quiet, balanced, performance.

In order to do this, PMF driver will get the moving average of APU power
from PMFW and power threshold, time constants, system config parameters
from OEM inputs.

System power as read by PMF driver from PMFW is the filtered value over
the sampling window. Every sampling window, moving average of system power
is computed. At the end of the monitoring window, the moving average is
compared against the threshold for mode switch for decision making.

With AMD managing the system config limits, any mode switch within
auto-mode will result in limits of fPPT/sPPT/STAPM or STT being scaled
down.

When "auto mode" is enabled, the static slider control remains out of
the PMF driver, so the platform_profile registration would not
happen in PMF driver.

The transition to auto-mode only happens when the APMF fn5 is enabled
in BIOS, platform_profile set to "balanced" and a AMT
(Auto Mode transition) is received.

Signed-off-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
---
 drivers/platform/x86/amd/pmf/Makefile    |   2 +-
 drivers/platform/x86/amd/pmf/acpi.c      |  28 ++
 drivers/platform/x86/amd/pmf/auto-mode.c | 317 +++++++++++++++++++++++
 drivers/platform/x86/amd/pmf/core.c      |  25 +-
 drivers/platform/x86/amd/pmf/pmf.h       | 104 ++++++++
 5 files changed, 473 insertions(+), 3 deletions(-)
 create mode 100644 drivers/platform/x86/amd/pmf/auto-mode.c

Comments

Hans de Goede July 27, 2022, 9:22 p.m. UTC | #1
Hi,

On 7/12/22 16:58, Shyam Sundar S K wrote:
> The objective of this feature is to track the moving average of system
> power over the time period specified and switch to the subsequent mode.
> 
> This feature has 3 modes: quiet, balanced, performance.
> 
> In order to do this, PMF driver will get the moving average of APU power
> from PMFW and power threshold, time constants, system config parameters
> from OEM inputs.
> 
> System power as read by PMF driver from PMFW is the filtered value over
> the sampling window. Every sampling window, moving average of system power
> is computed. At the end of the monitoring window, the moving average is
> compared against the threshold for mode switch for decision making.
> 
> With AMD managing the system config limits, any mode switch within
> auto-mode will result in limits of fPPT/sPPT/STAPM or STT being scaled
> down.
> 
> When "auto mode" is enabled, the static slider control remains out of
> the PMF driver, so the platform_profile registration would not
> happen in PMF driver.

This is not what happens in the code, the platform_profile registration
is still happening AFAICT.

Like with CnQF for this other auto mode we also need to figure out the
relation with the regular static platform_profile settings.

I see this in the code:

@@ -122,6 +122,11 @@ static void amd_pmf_get_metrics(struct work_struct *work)
 	/* Calculate the avg SoC power consumption */
 	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
 
+	if (current_profile == PLATFORM_PROFILE_BALANCED) {
+		/* Apply the Auto Mode transition */
+		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
+	}
+

So it seems that unlike CnQF this other auto-mode only does mode transitions
when the static-slider is set to balanced ?


> 
> The transition to auto-mode only happens when the APMF fn5 is enabled
> in BIOS, platform_profile set to "balanced" and a AMT
> (Auto Mode transition) is received.

Right, this part of the commit msg matches the code, but it conflicts with
the "When "auto mode" is enabled, the static slider control remains out of
the PMF driver:" part of the commit msg above.

> 
> Signed-off-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
> ---
>  drivers/platform/x86/amd/pmf/Makefile    |   2 +-
>  drivers/platform/x86/amd/pmf/acpi.c      |  28 ++
>  drivers/platform/x86/amd/pmf/auto-mode.c | 317 +++++++++++++++++++++++
>  drivers/platform/x86/amd/pmf/core.c      |  25 +-
>  drivers/platform/x86/amd/pmf/pmf.h       | 104 ++++++++
>  5 files changed, 473 insertions(+), 3 deletions(-)
>  create mode 100644 drivers/platform/x86/amd/pmf/auto-mode.c
> 
> diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
> index d02a0bdc6429..2a9568bf9064 100644
> --- a/drivers/platform/x86/amd/pmf/Makefile
> +++ b/drivers/platform/x86/amd/pmf/Makefile
> @@ -6,4 +6,4 @@
>  
>  obj-$(CONFIG_AMD_PMF) += amd-pmf.o
>  amd-pmf-objs := core.o acpi.o sps.o \
> -		cnqf.o
> +		cnqf.o auto-mode.o
> diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
> index a3ff91c605b5..e9f33e61659f 100644
> --- a/drivers/platform/x86/amd/pmf/acpi.c
> +++ b/drivers/platform/x86/amd/pmf/acpi.c
> @@ -55,6 +55,7 @@ static void apmf_if_parse_functions(struct apmf_if_functions *func, u32 mask)
>  {
>  	func->system_params = mask & APMF_FUNC_GET_SYS_PARAMS;
>  	func->static_slider_granular = mask & APMF_FUNC_STATIC_SLIDER_GRANULAR;
> +	func->auto_mode_def = mask & APMF_FUNC_AUTO_MODE;
>  	func->fan_table_idx = mask & APMF_FUNC_SET_FAN_IDX;
>  	func->dyn_slider_ac = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_AC;
>  	func->dyn_slider_dc = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_DC;
> @@ -210,6 +211,33 @@ int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx)
>  	return err;
>  }
>  
> +int apmf_get_auto_mode_def(struct apmf_if *ampf_if, struct apmf_auto_mode *data)
> +{
> +	union acpi_object *info;
> +	size_t size;
> +	int err = 0;
> +
> +	info = apmf_if_call(ampf_if, APMF_FUNC_AUTO_MODE, NULL);
> +	if (!info)
> +		return -EIO;
> +
> +	size = *(u16 *)info->buffer.pointer;
> +
> +	if (size < sizeof(*data)) {
> +		pr_debug("buffer too small %zu\n", size);
> +		err = -EINVAL;
> +		goto out;
> +	}
> +
> +	size = min(sizeof(*data), size);
> +	memset(data, 0, sizeof(*data));
> +	memcpy(data, info->buffer.pointer, size);
> +
> +out:
> +	kfree(info);
> +	return err;
> +}
> +

Please use apmf_if_call_store_buffer() for this (see review of 4/15)

Regards,

Hans


>  int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data)
>  {
>  	union acpi_object *info;
> diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
> new file mode 100644
> index 000000000000..954fde25e71e
> --- /dev/null
> +++ b/drivers/platform/x86/amd/pmf/auto-mode.c
> @@ -0,0 +1,317 @@
> +// 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/list.h>
> +#include <linux/workqueue.h>
> +#include "pmf.h"
> +
> +#define AVG_SAMPLE_SIZE 3
> +
> +struct power_history {
> +	int socket_power;
> +	struct list_head list;
> +	int avg;
> +	int total;
> +};
> +
> +struct list_pdata {
> +	int total;
> +	int avg;
> +};
> +
> +static struct power_history power_list;
> +static struct list_pdata pdata;
> +
> +static struct auto_mode_mode_config config_store;
> +static const char *state_as_str(unsigned int state);
> +
> +static void amd_pmf_handle_automode(struct amd_pmf_dev *dev, bool op, int idx,
> +				    struct auto_mode_mode_config *table)
> +{
> +	if (op == SLIDER_OP_SET) {
> +		struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
> +
> +		amd_pmf_send_cmd(dev, SET_SPL, false, pwr_ctrl->spl, NULL);
> +		amd_pmf_send_cmd(dev, SET_FPPT, false, pwr_ctrl->fppt, NULL);
> +		amd_pmf_send_cmd(dev, SET_SPPT, false, pwr_ctrl->sppt, NULL);
> +		amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
> +		amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
> +		amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
> +				 pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
> +		amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
> +				 pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
> +	} else if (op == SLIDER_OP_GET) {
> +		amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.spl);
> +		amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.fppt);
> +		amd_pmf_send_cmd(dev, GET_SPPT, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.sppt);
> +		amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.sppt_apu_only);
> +		amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.stt_min);
> +		amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_APU]);
> +		amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, true, ARG_NONE,
> +				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_HS2]);
> +	}
> +
> +	if (dev->apmf_if->func.fan_table_idx)
> +		apmf_update_fan_idx(dev->apmf_if, config_store.mode_set[idx].fan_control.manual,
> +				    config_store.mode_set[idx].fan_control.fan_id);
> +}
> +
> +static int amd_pmf_get_moving_avg(int socket_power)
> +{
> +	struct power_history *tmp;
> +	struct list_head *pos, *q;
> +	static int count;
> +
> +	tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
> +	tmp->socket_power = socket_power;
> +	list_add_tail(&tmp->list, &power_list.list);
> +
> +	list_for_each_safe(pos, q, &power_list.list) {
> +		if (count >= AVG_SAMPLE_SIZE) {
> +			tmp = list_first_entry(pos, struct power_history, list);
> +			list_del_init(pos);
> +			goto next;
> +		}
> +	}
> +
> +next:
> +	pdata.total = 0;
> +	pdata.avg = 0;
> +
> +	list_for_each(pos, &power_list.list) {
> +		tmp = list_entry(pos, struct power_history, list);
> +		pdata.total += tmp->socket_power;
> +		pdata.avg = pdata.total / AVG_SAMPLE_SIZE;
> +	}
> +
> +	count++;
> +	if (count >= AVG_SAMPLE_SIZE)
> +		return pdata.avg;
> +
> +	return 0;
> +}
> +
> +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(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;
> +		}
> +	}
> +
> +	dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
> +		state_as_str(config_store.current_mode));
> +
> +	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_handle_automode(dev, SLIDER_OP_SET,
> +								config_store.current_mode, NULL);
> +				}
> +				break;
> +			}
> +		}
> +	}
> +}
> +
> +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;
> +}
> +
> +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";
> +	}
> +}
> +
> +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;
> +
> +	if (dev->apmf_if->func.auto_mode_def) {
> +		apmf_get_auto_mode_def(dev->apmf_if, &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;
> +		}
> +	}
> +}
> +
> +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)
> +{
> +	INIT_LIST_HEAD(&power_list.list);
> +
> +	amd_pmf_init_metrics_table(dev);
> +	amd_pmf_load_defaults_auto_mode(dev);
> +
> +	/* update the thermal for Automode */
> +	amd_pmf_handle_automode(dev, SLIDER_OP_SET, config_store.current_mode, NULL);
> +}
> diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
> index bc267d333b76..674ddf599135 100644
> --- a/drivers/platform/x86/amd/pmf/core.c
> +++ b/drivers/platform/x86/amd/pmf/core.c
> @@ -122,6 +122,11 @@ static void amd_pmf_get_metrics(struct work_struct *work)
>  	/* Calculate the avg SoC power consumption */
>  	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
>  
> +	if (current_profile == PLATFORM_PROFILE_BALANCED) {
> +		/* Apply the Auto Mode transition */
> +		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
> +	}
> +
>  	if (dev->cnqf_feat) {
>  		/* Apply the CnQF transition */
>  		amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
> @@ -260,9 +265,18 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
>  		amd_pmf_init_sps(dev);
>  		dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
>  	}
> -	/* Enable Cool n Quiet Framework (CnQF) */
> -	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
> +
> +	/* Enable Auto Mode */
> +	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
> +		amd_pmf_init_auto_mode(dev);
> +		dev_dbg(dev->dev, "Auto Mode Init done\n");
> +		/*
> +		 * Auto mode and CnQF cannot co-exist. If auto mode is supported it takes
> +		 * higher priority over CnQF.
> +		 */
> +	} else if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>  	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC)) {
> +		/* Enable Cool n Quiet Framework (CnQF) */
>  		amd_pmf_init_cnqf(dev);
>  		dev_dbg(dev->dev, "CnQF Init done\n");
>  	}
> @@ -272,6 +286,13 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
>  {
>  	if (is_apmf_func_supported(APMF_FUNC_STATIC_SLIDER_GRANULAR))
>  		amd_pmf_deinit_sps(dev);
> +
> +	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
> +		amd_pmf_deinit_auto_mode(dev);
> +		/* If auto mode is supported, there is no need to proceed */
> +		return;
> +	}
> +
>  	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>  	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC))
>  		amd_pmf_deinit_cnqf(dev);
> diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
> index 452266809dfa..0532f49e9613 100644
> --- a/drivers/platform/x86/amd/pmf/pmf.h
> +++ b/drivers/platform/x86/amd/pmf/pmf.h
> @@ -18,6 +18,7 @@
>  #define APMF_FUNC_VERIFY_INTERFACE			0
>  #define APMF_FUNC_GET_SYS_PARAMS			1
>  #define APMF_FUNC_SBIOS_HEARTBEAT			4
> +#define APMF_FUNC_AUTO_MODE					5
>  #define APMF_FUNC_SET_FAN_IDX				7
>  #define APMF_FUNC_STATIC_SLIDER_GRANULAR	9
>  #define APMF_FUNC_DYN_SLIDER_GRANULAR_AC	11
> @@ -51,6 +52,7 @@
>  struct apmf_if_functions {
>  	bool system_params;
>  	bool sbios_heartbeat;
> +	bool auto_mode_def;
>  	bool fan_table_idx;
>  	bool static_slider_granular;
>  	bool dyn_slider_ac;
> @@ -197,6 +199,33 @@ struct fan_table_control {
>  	unsigned long fan_id;
>  };
>  
> +/* 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 power_table_control {
>  	u32 spl;
>  	u32 sppt;
> @@ -207,6 +236,74 @@ struct power_table_control {
>  	u32 reserved[16];
>  };
>  
> +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;
> +	bool on_lap;
> +	bool better_perf;
> +	u32 amt_enabled; /* Auto Mode Transition */
> +	u32 avg_power;
> +};
> +
> +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 */
> @@ -317,6 +414,13 @@ void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev);
>  
>  int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx);
>  
> +/* Auto Mode Layer */
> +void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev);
> +int apmf_get_auto_mode_def(struct apmf_if *ampf_if, 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_lapsed_ms);
> +
>  /* CnQF Layer */
>  int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
>  int apmf_get_dyn_slider_def_dc(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
Shyam Sundar S K July 28, 2022, 12:57 p.m. UTC | #2
Hi Hans,

On 7/28/2022 2:52 AM, Hans de Goede wrote:
> Hi,
> 
> On 7/12/22 16:58, Shyam Sundar S K wrote:
>> The objective of this feature is to track the moving average of system
>> power over the time period specified and switch to the subsequent mode.
>>
>> This feature has 3 modes: quiet, balanced, performance.
>>
>> In order to do this, PMF driver will get the moving average of APU power
>> from PMFW and power threshold, time constants, system config parameters
>> from OEM inputs.
>>
>> System power as read by PMF driver from PMFW is the filtered value over
>> the sampling window. Every sampling window, moving average of system power
>> is computed. At the end of the monitoring window, the moving average is
>> compared against the threshold for mode switch for decision making.
>>
>> With AMD managing the system config limits, any mode switch within
>> auto-mode will result in limits of fPPT/sPPT/STAPM or STT being scaled
>> down.
>>
>> When "auto mode" is enabled, the static slider control remains out of
>> the PMF driver, so the platform_profile registration would not
>> happen in PMF driver.
> 
> This is not what happens in the code, the platform_profile registration
> is still happening AFAICT.

platform_profile_register gets called in amd_pmf_init_sps() and it gets
triggered only when APMF_FUNC_STATIC_SLIDER_GRANULAR (fn9) is enabled in
the BIOS.

OEMs have been already told in the BIOS implementation guide that, if
the "Auto Mode" is enabled, the "Static platform_profile" registration
shall happen at the OEM driver side. In this case, it will be
thinkpad_acpi which shall do the "platform_profile_register".

> 
> Like with CnQF for this other auto mode we also need to figure out the
> relation with the regular static platform_profile settings.
> 
> I see this in the code:
> 
> @@ -122,6 +122,11 @@ static void amd_pmf_get_metrics(struct work_struct *work)
>  	/* Calculate the avg SoC power consumption */
>  	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
>  
> +	if (current_profile == PLATFORM_PROFILE_BALANCED) {
> +		/* Apply the Auto Mode transition */
> +		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
> +	}
> +
> 
> So it seems that unlike CnQF this other auto-mode only does mode transitions
> when the static-slider is set to balanced ?

CnQF and Auto-mode are complimentary to eachother. The key difference
between them are:

1. CnQF will have 4 internal modes (low-power, balanced, performance,
turbo) and Auto-mode will not have the turbo mode.

2. CnQF will be enabled only when the APCI call to the BIOS function
returns a spl flag which gets set from the BIOS side and driver sees it.
That is when the driver activates the CnQF. Irrespective of the
platform_profile choice selection, if the CnQF is enabled the values
passed in the BIOS tables for static slider are ignored.

3. Auto-mode will get activated only when an AMT ON event (which will be
sent by thinkpad_acpi driver in this case) is received and the
platform_profile is set to balanced. In other modes it keeps turned off.

4. The sampling window, the threshold calculation mechanism and the
power floor measurements are different w.r.t to CnQF and auto-mode.

5. OEMs are free to choose what kind of optimizations they are looking
for by enabling one or more of such features within PMF.

Hope it clarifies :-)

Thanks,
Shyam

> 
> 
>>
>> The transition to auto-mode only happens when the APMF fn5 is enabled
>> in BIOS, platform_profile set to "balanced" and a AMT
>> (Auto Mode transition) is received.
> 
> Right, this part of the commit msg matches the code, but it conflicts with
> the "When "auto mode" is enabled, the static slider control remains out of
> the PMF driver:" part of the commit msg above.
> 
>>
>> Signed-off-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
>> ---
>>  drivers/platform/x86/amd/pmf/Makefile    |   2 +-
>>  drivers/platform/x86/amd/pmf/acpi.c      |  28 ++
>>  drivers/platform/x86/amd/pmf/auto-mode.c | 317 +++++++++++++++++++++++
>>  drivers/platform/x86/amd/pmf/core.c      |  25 +-
>>  drivers/platform/x86/amd/pmf/pmf.h       | 104 ++++++++
>>  5 files changed, 473 insertions(+), 3 deletions(-)
>>  create mode 100644 drivers/platform/x86/amd/pmf/auto-mode.c
>>
>> diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
>> index d02a0bdc6429..2a9568bf9064 100644
>> --- a/drivers/platform/x86/amd/pmf/Makefile
>> +++ b/drivers/platform/x86/amd/pmf/Makefile
>> @@ -6,4 +6,4 @@
>>  
>>  obj-$(CONFIG_AMD_PMF) += amd-pmf.o
>>  amd-pmf-objs := core.o acpi.o sps.o \
>> -		cnqf.o
>> +		cnqf.o auto-mode.o
>> diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
>> index a3ff91c605b5..e9f33e61659f 100644
>> --- a/drivers/platform/x86/amd/pmf/acpi.c
>> +++ b/drivers/platform/x86/amd/pmf/acpi.c
>> @@ -55,6 +55,7 @@ static void apmf_if_parse_functions(struct apmf_if_functions *func, u32 mask)
>>  {
>>  	func->system_params = mask & APMF_FUNC_GET_SYS_PARAMS;
>>  	func->static_slider_granular = mask & APMF_FUNC_STATIC_SLIDER_GRANULAR;
>> +	func->auto_mode_def = mask & APMF_FUNC_AUTO_MODE;
>>  	func->fan_table_idx = mask & APMF_FUNC_SET_FAN_IDX;
>>  	func->dyn_slider_ac = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_AC;
>>  	func->dyn_slider_dc = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_DC;
>> @@ -210,6 +211,33 @@ int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx)
>>  	return err;
>>  }
>>  
>> +int apmf_get_auto_mode_def(struct apmf_if *ampf_if, struct apmf_auto_mode *data)
>> +{
>> +	union acpi_object *info;
>> +	size_t size;
>> +	int err = 0;
>> +
>> +	info = apmf_if_call(ampf_if, APMF_FUNC_AUTO_MODE, NULL);
>> +	if (!info)
>> +		return -EIO;
>> +
>> +	size = *(u16 *)info->buffer.pointer;
>> +
>> +	if (size < sizeof(*data)) {
>> +		pr_debug("buffer too small %zu\n", size);
>> +		err = -EINVAL;
>> +		goto out;
>> +	}
>> +
>> +	size = min(sizeof(*data), size);
>> +	memset(data, 0, sizeof(*data));
>> +	memcpy(data, info->buffer.pointer, size);
>> +
>> +out:
>> +	kfree(info);
>> +	return err;
>> +}
>> +
> 
> Please use apmf_if_call_store_buffer() for this (see review of 4/15)
> 
> Regards,
> 
> Hans
> 
> 
>>  int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data)
>>  {
>>  	union acpi_object *info;
>> diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
>> new file mode 100644
>> index 000000000000..954fde25e71e
>> --- /dev/null
>> +++ b/drivers/platform/x86/amd/pmf/auto-mode.c
>> @@ -0,0 +1,317 @@
>> +// 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/list.h>
>> +#include <linux/workqueue.h>
>> +#include "pmf.h"
>> +
>> +#define AVG_SAMPLE_SIZE 3
>> +
>> +struct power_history {
>> +	int socket_power;
>> +	struct list_head list;
>> +	int avg;
>> +	int total;
>> +};
>> +
>> +struct list_pdata {
>> +	int total;
>> +	int avg;
>> +};
>> +
>> +static struct power_history power_list;
>> +static struct list_pdata pdata;
>> +
>> +static struct auto_mode_mode_config config_store;
>> +static const char *state_as_str(unsigned int state);
>> +
>> +static void amd_pmf_handle_automode(struct amd_pmf_dev *dev, bool op, int idx,
>> +				    struct auto_mode_mode_config *table)
>> +{
>> +	if (op == SLIDER_OP_SET) {
>> +		struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
>> +
>> +		amd_pmf_send_cmd(dev, SET_SPL, false, pwr_ctrl->spl, NULL);
>> +		amd_pmf_send_cmd(dev, SET_FPPT, false, pwr_ctrl->fppt, NULL);
>> +		amd_pmf_send_cmd(dev, SET_SPPT, false, pwr_ctrl->sppt, NULL);
>> +		amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
>> +		amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
>> +		amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
>> +				 pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
>> +		amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
>> +				 pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
>> +	} else if (op == SLIDER_OP_GET) {
>> +		amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.spl);
>> +		amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.fppt);
>> +		amd_pmf_send_cmd(dev, GET_SPPT, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.sppt);
>> +		amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.sppt_apu_only);
>> +		amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.stt_min);
>> +		amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_APU]);
>> +		amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, true, ARG_NONE,
>> +				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_HS2]);
>> +	}
>> +
>> +	if (dev->apmf_if->func.fan_table_idx)
>> +		apmf_update_fan_idx(dev->apmf_if, config_store.mode_set[idx].fan_control.manual,
>> +				    config_store.mode_set[idx].fan_control.fan_id);
>> +}
>> +
>> +static int amd_pmf_get_moving_avg(int socket_power)
>> +{
>> +	struct power_history *tmp;
>> +	struct list_head *pos, *q;
>> +	static int count;
>> +
>> +	tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
>> +	tmp->socket_power = socket_power;
>> +	list_add_tail(&tmp->list, &power_list.list);
>> +
>> +	list_for_each_safe(pos, q, &power_list.list) {
>> +		if (count >= AVG_SAMPLE_SIZE) {
>> +			tmp = list_first_entry(pos, struct power_history, list);
>> +			list_del_init(pos);
>> +			goto next;
>> +		}
>> +	}
>> +
>> +next:
>> +	pdata.total = 0;
>> +	pdata.avg = 0;
>> +
>> +	list_for_each(pos, &power_list.list) {
>> +		tmp = list_entry(pos, struct power_history, list);
>> +		pdata.total += tmp->socket_power;
>> +		pdata.avg = pdata.total / AVG_SAMPLE_SIZE;
>> +	}
>> +
>> +	count++;
>> +	if (count >= AVG_SAMPLE_SIZE)
>> +		return pdata.avg;
>> +
>> +	return 0;
>> +}
>> +
>> +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(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;
>> +		}
>> +	}
>> +
>> +	dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
>> +		state_as_str(config_store.current_mode));
>> +
>> +	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_handle_automode(dev, SLIDER_OP_SET,
>> +								config_store.current_mode, NULL);
>> +				}
>> +				break;
>> +			}
>> +		}
>> +	}
>> +}
>> +
>> +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;
>> +}
>> +
>> +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";
>> +	}
>> +}
>> +
>> +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;
>> +
>> +	if (dev->apmf_if->func.auto_mode_def) {
>> +		apmf_get_auto_mode_def(dev->apmf_if, &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;
>> +		}
>> +	}
>> +}
>> +
>> +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)
>> +{
>> +	INIT_LIST_HEAD(&power_list.list);
>> +
>> +	amd_pmf_init_metrics_table(dev);
>> +	amd_pmf_load_defaults_auto_mode(dev);
>> +
>> +	/* update the thermal for Automode */
>> +	amd_pmf_handle_automode(dev, SLIDER_OP_SET, config_store.current_mode, NULL);
>> +}
>> diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
>> index bc267d333b76..674ddf599135 100644
>> --- a/drivers/platform/x86/amd/pmf/core.c
>> +++ b/drivers/platform/x86/amd/pmf/core.c
>> @@ -122,6 +122,11 @@ static void amd_pmf_get_metrics(struct work_struct *work)
>>  	/* Calculate the avg SoC power consumption */
>>  	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
>>  
>> +	if (current_profile == PLATFORM_PROFILE_BALANCED) {
>> +		/* Apply the Auto Mode transition */
>> +		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
>> +	}
>> +
>>  	if (dev->cnqf_feat) {
>>  		/* Apply the CnQF transition */
>>  		amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
>> @@ -260,9 +265,18 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
>>  		amd_pmf_init_sps(dev);
>>  		dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
>>  	}
>> -	/* Enable Cool n Quiet Framework (CnQF) */
>> -	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>> +
>> +	/* Enable Auto Mode */
>> +	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
>> +		amd_pmf_init_auto_mode(dev);
>> +		dev_dbg(dev->dev, "Auto Mode Init done\n");
>> +		/*
>> +		 * Auto mode and CnQF cannot co-exist. If auto mode is supported it takes
>> +		 * higher priority over CnQF.
>> +		 */
>> +	} else if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>>  	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC)) {
>> +		/* Enable Cool n Quiet Framework (CnQF) */
>>  		amd_pmf_init_cnqf(dev);
>>  		dev_dbg(dev->dev, "CnQF Init done\n");
>>  	}
>> @@ -272,6 +286,13 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
>>  {
>>  	if (is_apmf_func_supported(APMF_FUNC_STATIC_SLIDER_GRANULAR))
>>  		amd_pmf_deinit_sps(dev);
>> +
>> +	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
>> +		amd_pmf_deinit_auto_mode(dev);
>> +		/* If auto mode is supported, there is no need to proceed */
>> +		return;
>> +	}
>> +
>>  	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>>  	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC))
>>  		amd_pmf_deinit_cnqf(dev);
>> diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
>> index 452266809dfa..0532f49e9613 100644
>> --- a/drivers/platform/x86/amd/pmf/pmf.h
>> +++ b/drivers/platform/x86/amd/pmf/pmf.h
>> @@ -18,6 +18,7 @@
>>  #define APMF_FUNC_VERIFY_INTERFACE			0
>>  #define APMF_FUNC_GET_SYS_PARAMS			1
>>  #define APMF_FUNC_SBIOS_HEARTBEAT			4
>> +#define APMF_FUNC_AUTO_MODE					5
>>  #define APMF_FUNC_SET_FAN_IDX				7
>>  #define APMF_FUNC_STATIC_SLIDER_GRANULAR	9
>>  #define APMF_FUNC_DYN_SLIDER_GRANULAR_AC	11
>> @@ -51,6 +52,7 @@
>>  struct apmf_if_functions {
>>  	bool system_params;
>>  	bool sbios_heartbeat;
>> +	bool auto_mode_def;
>>  	bool fan_table_idx;
>>  	bool static_slider_granular;
>>  	bool dyn_slider_ac;
>> @@ -197,6 +199,33 @@ struct fan_table_control {
>>  	unsigned long fan_id;
>>  };
>>  
>> +/* 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 power_table_control {
>>  	u32 spl;
>>  	u32 sppt;
>> @@ -207,6 +236,74 @@ struct power_table_control {
>>  	u32 reserved[16];
>>  };
>>  
>> +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;
>> +	bool on_lap;
>> +	bool better_perf;
>> +	u32 amt_enabled; /* Auto Mode Transition */
>> +	u32 avg_power;
>> +};
>> +
>> +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 */
>> @@ -317,6 +414,13 @@ void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev);
>>  
>>  int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx);
>>  
>> +/* Auto Mode Layer */
>> +void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev);
>> +int apmf_get_auto_mode_def(struct apmf_if *ampf_if, 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_lapsed_ms);
>> +
>>  /* CnQF Layer */
>>  int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
>>  int apmf_get_dyn_slider_def_dc(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
>
Hans de Goede July 28, 2022, 1:15 p.m. UTC | #3
Hi Shyam,

On 7/28/22 14:57, Shyam Sundar S K wrote:
> Hi Hans,
> 
> On 7/28/2022 2:52 AM, Hans de Goede wrote:
>> Hi,
>>
>> On 7/12/22 16:58, Shyam Sundar S K wrote:
>>> The objective of this feature is to track the moving average of system
>>> power over the time period specified and switch to the subsequent mode.
>>>
>>> This feature has 3 modes: quiet, balanced, performance.
>>>
>>> In order to do this, PMF driver will get the moving average of APU power
>>> from PMFW and power threshold, time constants, system config parameters
>>> from OEM inputs.
>>>
>>> System power as read by PMF driver from PMFW is the filtered value over
>>> the sampling window. Every sampling window, moving average of system power
>>> is computed. At the end of the monitoring window, the moving average is
>>> compared against the threshold for mode switch for decision making.
>>>
>>> With AMD managing the system config limits, any mode switch within
>>> auto-mode will result in limits of fPPT/sPPT/STAPM or STT being scaled
>>> down.
>>>
>>> When "auto mode" is enabled, the static slider control remains out of
>>> the PMF driver, so the platform_profile registration would not
>>> happen in PMF driver.
>>
>> This is not what happens in the code, the platform_profile registration
>> is still happening AFAICT.
> 
> platform_profile_register gets called in amd_pmf_init_sps() and it gets
> triggered only when APMF_FUNC_STATIC_SLIDER_GRANULAR (fn9) is enabled in
> the BIOS.
> 
> OEMs have been already told in the BIOS implementation guide that, if
> the "Auto Mode" is enabled, the "Static platform_profile" registration
> shall happen at the OEM driver side. In this case, it will be
> thinkpad_acpi which shall do the "platform_profile_register".

I see. IMHO it would still be good to check that we never end up
with both the static-slider + AMT support enabled at the same time.

It is better to catch this; ignore the static-slider support
(clear the feature flag I guess) and log an error then to get surprised
by firmware bugs and having to figure out what is going on when
a user reports some weird bug.

> 
>>
>> Like with CnQF for this other auto mode we also need to figure out the
>> relation with the regular static platform_profile settings.
>>
>> I see this in the code:
>>
>> @@ -122,6 +122,11 @@ static void amd_pmf_get_metrics(struct work_struct *work)
>>  	/* Calculate the avg SoC power consumption */
>>  	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
>>  
>> +	if (current_profile == PLATFORM_PROFILE_BALANCED) {
>> +		/* Apply the Auto Mode transition */
>> +		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
>> +	}
>> +
>>
>> So it seems that unlike CnQF this other auto-mode only does mode transitions
>> when the static-slider is set to balanced ?
> 
> CnQF and Auto-mode are complimentary to eachother. The key difference
> between them are:
> 
> 1. CnQF will have 4 internal modes (low-power, balanced, performance,
> turbo) and Auto-mode will not have the turbo mode.
> 
> 2. CnQF will be enabled only when the APCI call to the BIOS function
> returns a spl flag which gets set from the BIOS side and driver sees it.
> That is when the driver activates the CnQF. Irrespective of the
> platform_profile choice selection, if the CnQF is enabled the values
> passed in the BIOS tables for static slider are ignored.

Right, this is rather "rude" though, to ignore a clear user preference.

We really need to think of the userspace API results here. Arguably
when CnQF gets enabled the platform_profile handler should just be
unregistered to indicate that there is no platform_profile support.

IMHO that would be better then still advertising platform_profile
support and not have it working.

Also platform_profile support is also used to implement a low-battery
mode where userspace automatically switches the profile to low-power
when the battery is below say 20%. How is such a power-saver mode
supposed to be implemented with CnQF ?

> 3. Auto-mode will get activated only when an AMT ON event (which will be
> sent by thinkpad_acpi driver in this case) is received and the
> platform_profile is set to balanced. In other modes it keeps turned off.

Right, note that as discussed elsewhere in the thread the decision to
only enable AMT when platform_profile == balanced is a policy coded
inside the thinkpad_acpi code and IMHO this policy decision should not
be duplicated in the amd-pmf code. Instead it should just enable/disable
AMT soleley on the BIOS AMT enable/disable setting/events.

> 4. The sampling window, the threshold calculation mechanism and the
> power floor measurements are different w.r.t to CnQF and auto-mode.
> 
> 5. OEMs are free to choose what kind of optimizations they are looking
> for by enabling one or more of such features within PMF.
> 
> Hope it clarifies :-)

It helps a bit, but there are still questions. Off-list Mario suggested
to maybe just drop CnQF from the patch-set (for now) and first focus on
getting the amd-pmf code to work in the thinkpad case. And then once
that is solved re-introduce the CnQF patches/code. I think that might
be a good idea to move this forward a bit faster. Especially since
the CnQF story involves some userspace API issues (as mentioned) and
I would like to make sure we get the userspace API issues cleared up
before merging the CnQF bits.

>>> The transition to auto-mode only happens when the APMF fn5 is enabled
>>> in BIOS, platform_profile set to "balanced" and a AMT
>>> (Auto Mode transition) is received.
>>
>> Right, this part of the commit msg matches the code, but it conflicts with
>> the "When "auto mode" is enabled, the static slider control remains out of
>> the PMF driver:" part of the commit msg above.

Note this bit of the commit message needs to be claried that the mentioned
platform_profile setting is coming from another driver and not form
amd-pmf. Or better yet as mentioned just only check the BIOS AMT flag
and then this bit of the commit msg can simply be dropped.

Regards,

Hans



>>
>>>
>>> Signed-off-by: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
>>> ---
>>>  drivers/platform/x86/amd/pmf/Makefile    |   2 +-
>>>  drivers/platform/x86/amd/pmf/acpi.c      |  28 ++
>>>  drivers/platform/x86/amd/pmf/auto-mode.c | 317 +++++++++++++++++++++++
>>>  drivers/platform/x86/amd/pmf/core.c      |  25 +-
>>>  drivers/platform/x86/amd/pmf/pmf.h       | 104 ++++++++
>>>  5 files changed, 473 insertions(+), 3 deletions(-)
>>>  create mode 100644 drivers/platform/x86/amd/pmf/auto-mode.c
>>>
>>> diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
>>> index d02a0bdc6429..2a9568bf9064 100644
>>> --- a/drivers/platform/x86/amd/pmf/Makefile
>>> +++ b/drivers/platform/x86/amd/pmf/Makefile
>>> @@ -6,4 +6,4 @@
>>>  
>>>  obj-$(CONFIG_AMD_PMF) += amd-pmf.o
>>>  amd-pmf-objs := core.o acpi.o sps.o \
>>> -		cnqf.o
>>> +		cnqf.o auto-mode.o
>>> diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
>>> index a3ff91c605b5..e9f33e61659f 100644
>>> --- a/drivers/platform/x86/amd/pmf/acpi.c
>>> +++ b/drivers/platform/x86/amd/pmf/acpi.c
>>> @@ -55,6 +55,7 @@ static void apmf_if_parse_functions(struct apmf_if_functions *func, u32 mask)
>>>  {
>>>  	func->system_params = mask & APMF_FUNC_GET_SYS_PARAMS;
>>>  	func->static_slider_granular = mask & APMF_FUNC_STATIC_SLIDER_GRANULAR;
>>> +	func->auto_mode_def = mask & APMF_FUNC_AUTO_MODE;
>>>  	func->fan_table_idx = mask & APMF_FUNC_SET_FAN_IDX;
>>>  	func->dyn_slider_ac = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_AC;
>>>  	func->dyn_slider_dc = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_DC;
>>> @@ -210,6 +211,33 @@ int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx)
>>>  	return err;
>>>  }
>>>  
>>> +int apmf_get_auto_mode_def(struct apmf_if *ampf_if, struct apmf_auto_mode *data)
>>> +{
>>> +	union acpi_object *info;
>>> +	size_t size;
>>> +	int err = 0;
>>> +
>>> +	info = apmf_if_call(ampf_if, APMF_FUNC_AUTO_MODE, NULL);
>>> +	if (!info)
>>> +		return -EIO;
>>> +
>>> +	size = *(u16 *)info->buffer.pointer;
>>> +
>>> +	if (size < sizeof(*data)) {
>>> +		pr_debug("buffer too small %zu\n", size);
>>> +		err = -EINVAL;
>>> +		goto out;
>>> +	}
>>> +
>>> +	size = min(sizeof(*data), size);
>>> +	memset(data, 0, sizeof(*data));
>>> +	memcpy(data, info->buffer.pointer, size);
>>> +
>>> +out:
>>> +	kfree(info);
>>> +	return err;
>>> +}
>>> +
>>
>> Please use apmf_if_call_store_buffer() for this (see review of 4/15)
>>
>> Regards,
>>
>> Hans
>>
>>
>>>  int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data)
>>>  {
>>>  	union acpi_object *info;
>>> diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
>>> new file mode 100644
>>> index 000000000000..954fde25e71e
>>> --- /dev/null
>>> +++ b/drivers/platform/x86/amd/pmf/auto-mode.c
>>> @@ -0,0 +1,317 @@
>>> +// 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/list.h>
>>> +#include <linux/workqueue.h>
>>> +#include "pmf.h"
>>> +
>>> +#define AVG_SAMPLE_SIZE 3
>>> +
>>> +struct power_history {
>>> +	int socket_power;
>>> +	struct list_head list;
>>> +	int avg;
>>> +	int total;
>>> +};
>>> +
>>> +struct list_pdata {
>>> +	int total;
>>> +	int avg;
>>> +};
>>> +
>>> +static struct power_history power_list;
>>> +static struct list_pdata pdata;
>>> +
>>> +static struct auto_mode_mode_config config_store;
>>> +static const char *state_as_str(unsigned int state);
>>> +
>>> +static void amd_pmf_handle_automode(struct amd_pmf_dev *dev, bool op, int idx,
>>> +				    struct auto_mode_mode_config *table)
>>> +{
>>> +	if (op == SLIDER_OP_SET) {
>>> +		struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
>>> +
>>> +		amd_pmf_send_cmd(dev, SET_SPL, false, pwr_ctrl->spl, NULL);
>>> +		amd_pmf_send_cmd(dev, SET_FPPT, false, pwr_ctrl->fppt, NULL);
>>> +		amd_pmf_send_cmd(dev, SET_SPPT, false, pwr_ctrl->sppt, NULL);
>>> +		amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
>>> +		amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
>>> +		amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
>>> +				 pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
>>> +		amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
>>> +				 pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
>>> +	} else if (op == SLIDER_OP_GET) {
>>> +		amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.spl);
>>> +		amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.fppt);
>>> +		amd_pmf_send_cmd(dev, GET_SPPT, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.sppt);
>>> +		amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.sppt_apu_only);
>>> +		amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.stt_min);
>>> +		amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_APU]);
>>> +		amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, true, ARG_NONE,
>>> +				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_HS2]);
>>> +	}
>>> +
>>> +	if (dev->apmf_if->func.fan_table_idx)
>>> +		apmf_update_fan_idx(dev->apmf_if, config_store.mode_set[idx].fan_control.manual,
>>> +				    config_store.mode_set[idx].fan_control.fan_id);
>>> +}
>>> +
>>> +static int amd_pmf_get_moving_avg(int socket_power)
>>> +{
>>> +	struct power_history *tmp;
>>> +	struct list_head *pos, *q;
>>> +	static int count;
>>> +
>>> +	tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
>>> +	tmp->socket_power = socket_power;
>>> +	list_add_tail(&tmp->list, &power_list.list);
>>> +
>>> +	list_for_each_safe(pos, q, &power_list.list) {
>>> +		if (count >= AVG_SAMPLE_SIZE) {
>>> +			tmp = list_first_entry(pos, struct power_history, list);
>>> +			list_del_init(pos);
>>> +			goto next;
>>> +		}
>>> +	}
>>> +
>>> +next:
>>> +	pdata.total = 0;
>>> +	pdata.avg = 0;
>>> +
>>> +	list_for_each(pos, &power_list.list) {
>>> +		tmp = list_entry(pos, struct power_history, list);
>>> +		pdata.total += tmp->socket_power;
>>> +		pdata.avg = pdata.total / AVG_SAMPLE_SIZE;
>>> +	}
>>> +
>>> +	count++;
>>> +	if (count >= AVG_SAMPLE_SIZE)
>>> +		return pdata.avg;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +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(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;
>>> +		}
>>> +	}
>>> +
>>> +	dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
>>> +		state_as_str(config_store.current_mode));
>>> +
>>> +	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_handle_automode(dev, SLIDER_OP_SET,
>>> +								config_store.current_mode, NULL);
>>> +				}
>>> +				break;
>>> +			}
>>> +		}
>>> +	}
>>> +}
>>> +
>>> +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;
>>> +}
>>> +
>>> +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";
>>> +	}
>>> +}
>>> +
>>> +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;
>>> +
>>> +	if (dev->apmf_if->func.auto_mode_def) {
>>> +		apmf_get_auto_mode_def(dev->apmf_if, &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;
>>> +		}
>>> +	}
>>> +}
>>> +
>>> +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)
>>> +{
>>> +	INIT_LIST_HEAD(&power_list.list);
>>> +
>>> +	amd_pmf_init_metrics_table(dev);
>>> +	amd_pmf_load_defaults_auto_mode(dev);
>>> +
>>> +	/* update the thermal for Automode */
>>> +	amd_pmf_handle_automode(dev, SLIDER_OP_SET, config_store.current_mode, NULL);
>>> +}
>>> diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
>>> index bc267d333b76..674ddf599135 100644
>>> --- a/drivers/platform/x86/amd/pmf/core.c
>>> +++ b/drivers/platform/x86/amd/pmf/core.c
>>> @@ -122,6 +122,11 @@ static void amd_pmf_get_metrics(struct work_struct *work)
>>>  	/* Calculate the avg SoC power consumption */
>>>  	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
>>>  
>>> +	if (current_profile == PLATFORM_PROFILE_BALANCED) {
>>> +		/* Apply the Auto Mode transition */
>>> +		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
>>> +	}
>>> +
>>>  	if (dev->cnqf_feat) {
>>>  		/* Apply the CnQF transition */
>>>  		amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
>>> @@ -260,9 +265,18 @@ static void amd_pmf_init_features(struct amd_pmf_dev *dev)
>>>  		amd_pmf_init_sps(dev);
>>>  		dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
>>>  	}
>>> -	/* Enable Cool n Quiet Framework (CnQF) */
>>> -	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>>> +
>>> +	/* Enable Auto Mode */
>>> +	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
>>> +		amd_pmf_init_auto_mode(dev);
>>> +		dev_dbg(dev->dev, "Auto Mode Init done\n");
>>> +		/*
>>> +		 * Auto mode and CnQF cannot co-exist. If auto mode is supported it takes
>>> +		 * higher priority over CnQF.
>>> +		 */
>>> +	} else if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>>>  	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC)) {
>>> +		/* Enable Cool n Quiet Framework (CnQF) */
>>>  		amd_pmf_init_cnqf(dev);
>>>  		dev_dbg(dev->dev, "CnQF Init done\n");
>>>  	}
>>> @@ -272,6 +286,13 @@ static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
>>>  {
>>>  	if (is_apmf_func_supported(APMF_FUNC_STATIC_SLIDER_GRANULAR))
>>>  		amd_pmf_deinit_sps(dev);
>>> +
>>> +	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
>>> +		amd_pmf_deinit_auto_mode(dev);
>>> +		/* If auto mode is supported, there is no need to proceed */
>>> +		return;
>>> +	}
>>> +
>>>  	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
>>>  	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC))
>>>  		amd_pmf_deinit_cnqf(dev);
>>> diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
>>> index 452266809dfa..0532f49e9613 100644
>>> --- a/drivers/platform/x86/amd/pmf/pmf.h
>>> +++ b/drivers/platform/x86/amd/pmf/pmf.h
>>> @@ -18,6 +18,7 @@
>>>  #define APMF_FUNC_VERIFY_INTERFACE			0
>>>  #define APMF_FUNC_GET_SYS_PARAMS			1
>>>  #define APMF_FUNC_SBIOS_HEARTBEAT			4
>>> +#define APMF_FUNC_AUTO_MODE					5
>>>  #define APMF_FUNC_SET_FAN_IDX				7
>>>  #define APMF_FUNC_STATIC_SLIDER_GRANULAR	9
>>>  #define APMF_FUNC_DYN_SLIDER_GRANULAR_AC	11
>>> @@ -51,6 +52,7 @@
>>>  struct apmf_if_functions {
>>>  	bool system_params;
>>>  	bool sbios_heartbeat;
>>> +	bool auto_mode_def;
>>>  	bool fan_table_idx;
>>>  	bool static_slider_granular;
>>>  	bool dyn_slider_ac;
>>> @@ -197,6 +199,33 @@ struct fan_table_control {
>>>  	unsigned long fan_id;
>>>  };
>>>  
>>> +/* 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 power_table_control {
>>>  	u32 spl;
>>>  	u32 sppt;
>>> @@ -207,6 +236,74 @@ struct power_table_control {
>>>  	u32 reserved[16];
>>>  };
>>>  
>>> +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;
>>> +	bool on_lap;
>>> +	bool better_perf;
>>> +	u32 amt_enabled; /* Auto Mode Transition */
>>> +	u32 avg_power;
>>> +};
>>> +
>>> +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 */
>>> @@ -317,6 +414,13 @@ void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev);
>>>  
>>>  int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx);
>>>  
>>> +/* Auto Mode Layer */
>>> +void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev);
>>> +int apmf_get_auto_mode_def(struct apmf_if *ampf_if, 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_lapsed_ms);
>>> +
>>>  /* CnQF Layer */
>>>  int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
>>>  int apmf_get_dyn_slider_def_dc(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
>>
>
diff mbox series

Patch

diff --git a/drivers/platform/x86/amd/pmf/Makefile b/drivers/platform/x86/amd/pmf/Makefile
index d02a0bdc6429..2a9568bf9064 100644
--- a/drivers/platform/x86/amd/pmf/Makefile
+++ b/drivers/platform/x86/amd/pmf/Makefile
@@ -6,4 +6,4 @@ 
 
 obj-$(CONFIG_AMD_PMF) += amd-pmf.o
 amd-pmf-objs := core.o acpi.o sps.o \
-		cnqf.o
+		cnqf.o auto-mode.o
diff --git a/drivers/platform/x86/amd/pmf/acpi.c b/drivers/platform/x86/amd/pmf/acpi.c
index a3ff91c605b5..e9f33e61659f 100644
--- a/drivers/platform/x86/amd/pmf/acpi.c
+++ b/drivers/platform/x86/amd/pmf/acpi.c
@@ -55,6 +55,7 @@  static void apmf_if_parse_functions(struct apmf_if_functions *func, u32 mask)
 {
 	func->system_params = mask & APMF_FUNC_GET_SYS_PARAMS;
 	func->static_slider_granular = mask & APMF_FUNC_STATIC_SLIDER_GRANULAR;
+	func->auto_mode_def = mask & APMF_FUNC_AUTO_MODE;
 	func->fan_table_idx = mask & APMF_FUNC_SET_FAN_IDX;
 	func->dyn_slider_ac = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_AC;
 	func->dyn_slider_dc = mask & APMF_FUNC_DYN_SLIDER_GRANULAR_DC;
@@ -210,6 +211,33 @@  int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx)
 	return err;
 }
 
+int apmf_get_auto_mode_def(struct apmf_if *ampf_if, struct apmf_auto_mode *data)
+{
+	union acpi_object *info;
+	size_t size;
+	int err = 0;
+
+	info = apmf_if_call(ampf_if, APMF_FUNC_AUTO_MODE, NULL);
+	if (!info)
+		return -EIO;
+
+	size = *(u16 *)info->buffer.pointer;
+
+	if (size < sizeof(*data)) {
+		pr_debug("buffer too small %zu\n", size);
+		err = -EINVAL;
+		goto out;
+	}
+
+	size = min(sizeof(*data), size);
+	memset(data, 0, sizeof(*data));
+	memcpy(data, info->buffer.pointer, size);
+
+out:
+	kfree(info);
+	return err;
+}
+
 int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data)
 {
 	union acpi_object *info;
diff --git a/drivers/platform/x86/amd/pmf/auto-mode.c b/drivers/platform/x86/amd/pmf/auto-mode.c
new file mode 100644
index 000000000000..954fde25e71e
--- /dev/null
+++ b/drivers/platform/x86/amd/pmf/auto-mode.c
@@ -0,0 +1,317 @@ 
+// 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/list.h>
+#include <linux/workqueue.h>
+#include "pmf.h"
+
+#define AVG_SAMPLE_SIZE 3
+
+struct power_history {
+	int socket_power;
+	struct list_head list;
+	int avg;
+	int total;
+};
+
+struct list_pdata {
+	int total;
+	int avg;
+};
+
+static struct power_history power_list;
+static struct list_pdata pdata;
+
+static struct auto_mode_mode_config config_store;
+static const char *state_as_str(unsigned int state);
+
+static void amd_pmf_handle_automode(struct amd_pmf_dev *dev, bool op, int idx,
+				    struct auto_mode_mode_config *table)
+{
+	if (op == SLIDER_OP_SET) {
+		struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;
+
+		amd_pmf_send_cmd(dev, SET_SPL, false, pwr_ctrl->spl, NULL);
+		amd_pmf_send_cmd(dev, SET_FPPT, false, pwr_ctrl->fppt, NULL);
+		amd_pmf_send_cmd(dev, SET_SPPT, false, pwr_ctrl->sppt, NULL);
+		amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, false, pwr_ctrl->sppt_apu_only, NULL);
+		amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, false, pwr_ctrl->stt_min, NULL);
+		amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, false,
+				 pwr_ctrl->stt_skin_temp[STT_TEMP_APU], NULL);
+		amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, false,
+				 pwr_ctrl->stt_skin_temp[STT_TEMP_HS2], NULL);
+	} else if (op == SLIDER_OP_GET) {
+		amd_pmf_send_cmd(dev, GET_SPL, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.spl);
+		amd_pmf_send_cmd(dev, GET_FPPT, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.fppt);
+		amd_pmf_send_cmd(dev, GET_SPPT, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.sppt);
+		amd_pmf_send_cmd(dev, GET_SPPT_APU_ONLY, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.sppt_apu_only);
+		amd_pmf_send_cmd(dev, GET_STT_MIN_LIMIT, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.stt_min);
+		amd_pmf_send_cmd(dev, GET_STT_LIMIT_APU, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_APU]);
+		amd_pmf_send_cmd(dev, GET_STT_LIMIT_HS2, true, ARG_NONE,
+				 &table->mode_set[idx].power_control.stt_skin_temp[STT_TEMP_HS2]);
+	}
+
+	if (dev->apmf_if->func.fan_table_idx)
+		apmf_update_fan_idx(dev->apmf_if, config_store.mode_set[idx].fan_control.manual,
+				    config_store.mode_set[idx].fan_control.fan_id);
+}
+
+static int amd_pmf_get_moving_avg(int socket_power)
+{
+	struct power_history *tmp;
+	struct list_head *pos, *q;
+	static int count;
+
+	tmp = kzalloc(sizeof(*tmp), GFP_KERNEL);
+	tmp->socket_power = socket_power;
+	list_add_tail(&tmp->list, &power_list.list);
+
+	list_for_each_safe(pos, q, &power_list.list) {
+		if (count >= AVG_SAMPLE_SIZE) {
+			tmp = list_first_entry(pos, struct power_history, list);
+			list_del_init(pos);
+			goto next;
+		}
+	}
+
+next:
+	pdata.total = 0;
+	pdata.avg = 0;
+
+	list_for_each(pos, &power_list.list) {
+		tmp = list_entry(pos, struct power_history, list);
+		pdata.total += tmp->socket_power;
+		pdata.avg = pdata.total / AVG_SAMPLE_SIZE;
+	}
+
+	count++;
+	if (count >= AVG_SAMPLE_SIZE)
+		return pdata.avg;
+
+	return 0;
+}
+
+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(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;
+		}
+	}
+
+	dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
+		state_as_str(config_store.current_mode));
+
+	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_handle_automode(dev, SLIDER_OP_SET,
+								config_store.current_mode, NULL);
+				}
+				break;
+			}
+		}
+	}
+}
+
+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;
+}
+
+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";
+	}
+}
+
+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;
+
+	if (dev->apmf_if->func.auto_mode_def) {
+		apmf_get_auto_mode_def(dev->apmf_if, &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;
+		}
+	}
+}
+
+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)
+{
+	INIT_LIST_HEAD(&power_list.list);
+
+	amd_pmf_init_metrics_table(dev);
+	amd_pmf_load_defaults_auto_mode(dev);
+
+	/* update the thermal for Automode */
+	amd_pmf_handle_automode(dev, SLIDER_OP_SET, config_store.current_mode, NULL);
+}
diff --git a/drivers/platform/x86/amd/pmf/core.c b/drivers/platform/x86/amd/pmf/core.c
index bc267d333b76..674ddf599135 100644
--- a/drivers/platform/x86/amd/pmf/core.c
+++ b/drivers/platform/x86/amd/pmf/core.c
@@ -122,6 +122,11 @@  static void amd_pmf_get_metrics(struct work_struct *work)
 	/* Calculate the avg SoC power consumption */
 	socket_power = dev->m_table.apu_power + dev->m_table.dgpu_power;
 
+	if (current_profile == PLATFORM_PROFILE_BALANCED) {
+		/* Apply the Auto Mode transition */
+		amd_pmf_trans_automode(dev, socket_power, time_elapsed_ms);
+	}
+
 	if (dev->cnqf_feat) {
 		/* Apply the CnQF transition */
 		amd_pmf_trans_cnqf(dev, socket_power, time_elapsed_ms);
@@ -260,9 +265,18 @@  static void amd_pmf_init_features(struct amd_pmf_dev *dev)
 		amd_pmf_init_sps(dev);
 		dev_dbg(dev->dev, "SPS enabled and Platform Profiles registered\n");
 	}
-	/* Enable Cool n Quiet Framework (CnQF) */
-	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
+
+	/* Enable Auto Mode */
+	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
+		amd_pmf_init_auto_mode(dev);
+		dev_dbg(dev->dev, "Auto Mode Init done\n");
+		/*
+		 * Auto mode and CnQF cannot co-exist. If auto mode is supported it takes
+		 * higher priority over CnQF.
+		 */
+	} else if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
 	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC)) {
+		/* Enable Cool n Quiet Framework (CnQF) */
 		amd_pmf_init_cnqf(dev);
 		dev_dbg(dev->dev, "CnQF Init done\n");
 	}
@@ -272,6 +286,13 @@  static void amd_pmf_deinit_features(struct amd_pmf_dev *dev)
 {
 	if (is_apmf_func_supported(APMF_FUNC_STATIC_SLIDER_GRANULAR))
 		amd_pmf_deinit_sps(dev);
+
+	if (is_apmf_func_supported(APMF_FUNC_AUTO_MODE)) {
+		amd_pmf_deinit_auto_mode(dev);
+		/* If auto mode is supported, there is no need to proceed */
+		return;
+	}
+
 	if (is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_AC) ||
 	    is_apmf_func_supported(APMF_FUNC_DYN_SLIDER_GRANULAR_DC))
 		amd_pmf_deinit_cnqf(dev);
diff --git a/drivers/platform/x86/amd/pmf/pmf.h b/drivers/platform/x86/amd/pmf/pmf.h
index 452266809dfa..0532f49e9613 100644
--- a/drivers/platform/x86/amd/pmf/pmf.h
+++ b/drivers/platform/x86/amd/pmf/pmf.h
@@ -18,6 +18,7 @@ 
 #define APMF_FUNC_VERIFY_INTERFACE			0
 #define APMF_FUNC_GET_SYS_PARAMS			1
 #define APMF_FUNC_SBIOS_HEARTBEAT			4
+#define APMF_FUNC_AUTO_MODE					5
 #define APMF_FUNC_SET_FAN_IDX				7
 #define APMF_FUNC_STATIC_SLIDER_GRANULAR	9
 #define APMF_FUNC_DYN_SLIDER_GRANULAR_AC	11
@@ -51,6 +52,7 @@ 
 struct apmf_if_functions {
 	bool system_params;
 	bool sbios_heartbeat;
+	bool auto_mode_def;
 	bool fan_table_idx;
 	bool static_slider_granular;
 	bool dyn_slider_ac;
@@ -197,6 +199,33 @@  struct fan_table_control {
 	unsigned long fan_id;
 };
 
+/* 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 power_table_control {
 	u32 spl;
 	u32 sppt;
@@ -207,6 +236,74 @@  struct power_table_control {
 	u32 reserved[16];
 };
 
+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;
+	bool on_lap;
+	bool better_perf;
+	u32 amt_enabled; /* Auto Mode Transition */
+	u32 avg_power;
+};
+
+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 */
@@ -317,6 +414,13 @@  void amd_pmf_load_defaults_sps(struct amd_pmf_dev *dev);
 
 int apmf_update_fan_idx(struct apmf_if *ampf_if, bool manual, u32 idx);
 
+/* Auto Mode Layer */
+void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev);
+int apmf_get_auto_mode_def(struct apmf_if *ampf_if, 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_lapsed_ms);
+
 /* CnQF Layer */
 int apmf_get_dyn_slider_def_ac(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);
 int apmf_get_dyn_slider_def_dc(struct apmf_if *ampf_if, struct apmf_dyn_slider_output *data);