root/drivers/platform/x86/amd/pmf/auto-mode.c
// SPDX-License-Identifier: GPL-2.0
/*
 * AMD Platform Management Framework Driver
 *
 * Copyright (c) 2022, Advanced Micro Devices, Inc.
 * All Rights Reserved.
 *
 * Author: Shyam Sundar S K <Shyam-sundar.S-k@amd.com>
 */

#include <linux/acpi.h>
#include <linux/workqueue.h>
#include "pmf.h"

static struct auto_mode_mode_config config_store;
static const char *state_as_str(unsigned int state);

#ifdef CONFIG_AMD_PMF_DEBUG
static void amd_pmf_dump_auto_mode_defaults(struct auto_mode_mode_config *data)
{
        struct auto_mode_mode_settings *its_mode;

        pr_debug("Auto Mode Data - BEGIN\n");

        /* time constant */
        pr_debug("balanced_to_perf: %u ms\n",
                 data->transition[AUTO_TRANSITION_TO_PERFORMANCE].time_constant);
        pr_debug("perf_to_balanced: %u ms\n",
                 data->transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].time_constant);
        pr_debug("quiet_to_balanced: %u ms\n",
                 data->transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].time_constant);
        pr_debug("balanced_to_quiet: %u ms\n",
                 data->transition[AUTO_TRANSITION_TO_QUIET].time_constant);

        /* power floor */
        pr_debug("pfloor_perf: %u mW\n", data->mode_set[AUTO_PERFORMANCE].power_floor);
        pr_debug("pfloor_balanced: %u mW\n", data->mode_set[AUTO_BALANCE].power_floor);
        pr_debug("pfloor_quiet: %u mW\n", data->mode_set[AUTO_QUIET].power_floor);

        /* Power delta for mode change */
        pr_debug("pd_balanced_to_perf: %u mW\n",
                 data->transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta);
        pr_debug("pd_perf_to_balanced: %u mW\n",
                 data->transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta);
        pr_debug("pd_quiet_to_balanced: %u mW\n",
                 data->transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta);
        pr_debug("pd_balanced_to_quiet: %u mW\n",
                 data->transition[AUTO_TRANSITION_TO_QUIET].power_delta);

        /* skin temperature limits */
        its_mode = &data->mode_set[AUTO_PERFORMANCE_ON_LAP];
        pr_debug("stt_apu_perf_on_lap: %u C\n",
                 its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
        pr_debug("stt_hs2_perf_on_lap: %u C\n",
                 its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
        pr_debug("stt_min_limit_perf_on_lap: %u mW\n", its_mode->power_control.stt_min);

        its_mode = &data->mode_set[AUTO_PERFORMANCE];
        pr_debug("stt_apu_perf: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
        pr_debug("stt_hs2_perf: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
        pr_debug("stt_min_limit_perf: %u mW\n", its_mode->power_control.stt_min);

        its_mode = &data->mode_set[AUTO_BALANCE];
        pr_debug("stt_apu_balanced: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
        pr_debug("stt_hs2_balanced: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
        pr_debug("stt_min_limit_balanced: %u mW\n", its_mode->power_control.stt_min);

        its_mode = &data->mode_set[AUTO_QUIET];
        pr_debug("stt_apu_quiet: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_APU]);
        pr_debug("stt_hs2_quiet: %u C\n", its_mode->power_control.stt_skin_temp[STT_TEMP_HS2]);
        pr_debug("stt_min_limit_quiet: %u mW\n", its_mode->power_control.stt_min);

        /* SPL based power limits */
        its_mode = &data->mode_set[AUTO_PERFORMANCE_ON_LAP];
        pr_debug("fppt_perf_on_lap: %u mW\n", its_mode->power_control.fppt);
        pr_debug("sppt_perf_on_lap: %u mW\n", its_mode->power_control.sppt);
        pr_debug("spl_perf_on_lap: %u mW\n", its_mode->power_control.spl);
        pr_debug("sppt_apu_only_perf_on_lap: %u mW\n", its_mode->power_control.sppt_apu_only);

        its_mode = &data->mode_set[AUTO_PERFORMANCE];
        pr_debug("fppt_perf: %u mW\n", its_mode->power_control.fppt);
        pr_debug("sppt_perf: %u mW\n", its_mode->power_control.sppt);
        pr_debug("spl_perf: %u mW\n", its_mode->power_control.spl);
        pr_debug("sppt_apu_only_perf: %u mW\n", its_mode->power_control.sppt_apu_only);

        its_mode = &data->mode_set[AUTO_BALANCE];
        pr_debug("fppt_balanced: %u mW\n", its_mode->power_control.fppt);
        pr_debug("sppt_balanced: %u mW\n", its_mode->power_control.sppt);
        pr_debug("spl_balanced: %u mW\n", its_mode->power_control.spl);
        pr_debug("sppt_apu_only_balanced: %u mW\n", its_mode->power_control.sppt_apu_only);

        its_mode = &data->mode_set[AUTO_QUIET];
        pr_debug("fppt_quiet: %u mW\n", its_mode->power_control.fppt);
        pr_debug("sppt_quiet: %u mW\n", its_mode->power_control.sppt);
        pr_debug("spl_quiet: %u mW\n", its_mode->power_control.spl);
        pr_debug("sppt_apu_only_quiet: %u mW\n", its_mode->power_control.sppt_apu_only);

        /* Fan ID */
        pr_debug("fan_id_perf: %lu\n",
                 data->mode_set[AUTO_PERFORMANCE].fan_control.fan_id);
        pr_debug("fan_id_balanced: %lu\n",
                 data->mode_set[AUTO_BALANCE].fan_control.fan_id);
        pr_debug("fan_id_quiet: %lu\n",
                 data->mode_set[AUTO_QUIET].fan_control.fan_id);

        pr_debug("Auto Mode Data - END\n");
}
#else
static void amd_pmf_dump_auto_mode_defaults(struct auto_mode_mode_config *data) {}
#endif

static void amd_pmf_set_automode(struct amd_pmf_dev *dev, int idx,
                                 struct auto_mode_mode_config *table)
{
        struct power_table_control *pwr_ctrl = &config_store.mode_set[idx].power_control;

        amd_pmf_send_cmd(dev, SET_SPL, SET_CMD, pwr_ctrl->spl, NULL);
        amd_pmf_send_cmd(dev, SET_FPPT, SET_CMD, pwr_ctrl->fppt, NULL);
        amd_pmf_send_cmd(dev, SET_SPPT, SET_CMD, pwr_ctrl->sppt, NULL);
        amd_pmf_send_cmd(dev, SET_SPPT_APU_ONLY, SET_CMD, pwr_ctrl->sppt_apu_only, NULL);
        amd_pmf_send_cmd(dev, SET_STT_MIN_LIMIT, SET_CMD, pwr_ctrl->stt_min, NULL);
        amd_pmf_send_cmd(dev, SET_STT_LIMIT_APU, SET_CMD,
                         fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_APU]), NULL);
        amd_pmf_send_cmd(dev, SET_STT_LIMIT_HS2, SET_CMD,
                         fixp_q88_fromint(pwr_ctrl->stt_skin_temp[STT_TEMP_HS2]), NULL);

        if (is_apmf_func_supported(dev, APMF_FUNC_SET_FAN_IDX))
                apmf_update_fan_idx(dev, config_store.mode_set[idx].fan_control.manual,
                                    config_store.mode_set[idx].fan_control.fan_id);
}

static int amd_pmf_get_moving_avg(struct amd_pmf_dev *pdev, int socket_power)
{
        int i, total = 0;

        if (pdev->socket_power_history_idx == -1) {
                for (i = 0; i < AVG_SAMPLE_SIZE; i++)
                        pdev->socket_power_history[i] = socket_power;
        }

        pdev->socket_power_history_idx = (pdev->socket_power_history_idx + 1) % AVG_SAMPLE_SIZE;
        pdev->socket_power_history[pdev->socket_power_history_idx] = socket_power;

        for (i = 0; i < AVG_SAMPLE_SIZE; i++)
                total += pdev->socket_power_history[i];

        return total / AVG_SAMPLE_SIZE;
}

void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms)
{
        int avg_power = 0;
        bool update = false;
        int i, j;

        /* Get the average moving average computed by auto mode algorithm */
        avg_power = amd_pmf_get_moving_avg(dev, socket_power);

        for (i = 0; i < AUTO_TRANSITION_MAX; i++) {
                if ((config_store.transition[i].shifting_up && avg_power >=
                     config_store.transition[i].power_threshold) ||
                    (!config_store.transition[i].shifting_up && avg_power <=
                     config_store.transition[i].power_threshold)) {
                        if (config_store.transition[i].timer <
                            config_store.transition[i].time_constant)
                                config_store.transition[i].timer += time_elapsed_ms;
                } else {
                        config_store.transition[i].timer = 0;
                }

                if (config_store.transition[i].timer >=
                    config_store.transition[i].time_constant &&
                    !config_store.transition[i].applied) {
                        config_store.transition[i].applied = true;
                        update = true;
                } else if (config_store.transition[i].timer <=
                           config_store.transition[i].time_constant &&
                           config_store.transition[i].applied) {
                        config_store.transition[i].applied = false;
                        update = true;
                }

#ifdef CONFIG_AMD_PMF_DEBUG
                dev_dbg(dev->dev, "[AUTO MODE] average_power : %d mW mode: %s\n", avg_power,
                        state_as_str(config_store.current_mode));

                dev_dbg(dev->dev, "[AUTO MODE] time: %lld ms timer: %u ms tc: %u ms\n",
                        time_elapsed_ms, config_store.transition[i].timer,
                        config_store.transition[i].time_constant);

                dev_dbg(dev->dev, "[AUTO MODE] shiftup: %u pt: %u mW pf: %u mW pd: %u mW\n",
                        config_store.transition[i].shifting_up,
                        config_store.transition[i].power_threshold,
                        config_store.mode_set[i].power_floor,
                        config_store.transition[i].power_delta);
#endif
        }

        dev_dbg(dev->dev, "[AUTO_MODE] avg power: %u mW mode: %s\n", avg_power,
                state_as_str(config_store.current_mode));

#ifdef CONFIG_AMD_PMF_DEBUG
        dev_dbg(dev->dev, "[AUTO MODE] priority1: %u priority2: %u priority3: %u priority4: %u\n",
                config_store.transition[0].applied,
                config_store.transition[1].applied,
                config_store.transition[2].applied,
                config_store.transition[3].applied);
#endif

        if (update) {
                for (j = 0; j < AUTO_TRANSITION_MAX; j++) {
                        /* Apply the mode with highest priority indentified */
                        if (config_store.transition[j].applied) {
                                if (config_store.current_mode !=
                                    config_store.transition[j].target_mode) {
                                        config_store.current_mode =
                                                        config_store.transition[j].target_mode;
                                        dev_dbg(dev->dev, "[AUTO_MODE] moving to mode:%s\n",
                                                state_as_str(config_store.current_mode));
                                        amd_pmf_set_automode(dev, config_store.current_mode, NULL);
                                }
                                break;
                        }
                }
        }
}

void amd_pmf_update_2_cql(struct amd_pmf_dev *dev, bool is_cql_event)
{
        int mode = config_store.current_mode;

        config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode =
                                   is_cql_event ? AUTO_PERFORMANCE_ON_LAP : AUTO_PERFORMANCE;

        if ((mode == AUTO_PERFORMANCE || mode == AUTO_PERFORMANCE_ON_LAP) &&
            mode != config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode) {
                mode = config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode;
                amd_pmf_set_automode(dev, mode, NULL);
        }
        dev_dbg(dev->dev, "updated CQL thermals\n");
}

static void amd_pmf_get_power_threshold(void)
{
        config_store.transition[AUTO_TRANSITION_TO_QUIET].power_threshold =
                                config_store.mode_set[AUTO_BALANCE].power_floor -
                                config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta;

        config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_threshold =
                                config_store.mode_set[AUTO_BALANCE].power_floor -
                                config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta;

        config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_threshold =
                        config_store.mode_set[AUTO_QUIET].power_floor -
                        config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta;

        config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_threshold =
                config_store.mode_set[AUTO_PERFORMANCE].power_floor -
                config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta;

#ifdef CONFIG_AMD_PMF_DEBUG
        pr_debug("[AUTO MODE TO_QUIET] pt: %u mW pf: %u mW pd: %u mW\n",
                 config_store.transition[AUTO_TRANSITION_TO_QUIET].power_threshold,
                 config_store.mode_set[AUTO_BALANCE].power_floor,
                 config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta);

        pr_debug("[AUTO MODE TO_PERFORMANCE] pt: %u mW pf: %u mW pd: %u mW\n",
                 config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_threshold,
                 config_store.mode_set[AUTO_BALANCE].power_floor,
                 config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta);

        pr_debug("[AUTO MODE QUIET_TO_BALANCE] pt: %u mW pf: %u mW pd: %u mW\n",
                 config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE]
                 .power_threshold,
                 config_store.mode_set[AUTO_QUIET].power_floor,
                 config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta);

        pr_debug("[AUTO MODE PERFORMANCE_TO_BALANCE] pt: %u mW pf: %u mW pd: %u mW\n",
                 config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE]
                 .power_threshold,
                 config_store.mode_set[AUTO_PERFORMANCE].power_floor,
                 config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta);
#endif
}

static const char *state_as_str(unsigned int state)
{
        switch (state) {
        case AUTO_QUIET:
                return "QUIET";
        case AUTO_BALANCE:
                return "BALANCED";
        case AUTO_PERFORMANCE_ON_LAP:
                return "ON_LAP";
        case AUTO_PERFORMANCE:
                return "PERFORMANCE";
        default:
                return "Unknown Auto Mode State";
        }
}

static void amd_pmf_load_defaults_auto_mode(struct amd_pmf_dev *dev)
{
        struct apmf_auto_mode output;
        struct power_table_control *pwr_ctrl;
        int i;

        apmf_get_auto_mode_def(dev, &output);
        /* time constant */
        config_store.transition[AUTO_TRANSITION_TO_QUIET].time_constant =
                                                                output.balanced_to_quiet;
        config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].time_constant =
                                                                output.balanced_to_perf;
        config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].time_constant =
                                                                output.quiet_to_balanced;
        config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].time_constant =
                                                                output.perf_to_balanced;

        /* power floor */
        config_store.mode_set[AUTO_QUIET].power_floor = output.pfloor_quiet;
        config_store.mode_set[AUTO_BALANCE].power_floor = output.pfloor_balanced;
        config_store.mode_set[AUTO_PERFORMANCE].power_floor = output.pfloor_perf;
        config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].power_floor = output.pfloor_perf;

        /* Power delta for mode change */
        config_store.transition[AUTO_TRANSITION_TO_QUIET].power_delta =
                                                                output.pd_balanced_to_quiet;
        config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].power_delta =
                                                                output.pd_balanced_to_perf;
        config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].power_delta =
                                                                output.pd_quiet_to_balanced;
        config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].power_delta =
                                                                output.pd_perf_to_balanced;

        /* Power threshold */
        amd_pmf_get_power_threshold();

        /* skin temperature limits */
        pwr_ctrl = &config_store.mode_set[AUTO_QUIET].power_control;
        pwr_ctrl->spl = output.spl_quiet;
        pwr_ctrl->sppt = output.sppt_quiet;
        pwr_ctrl->fppt = output.fppt_quiet;
        pwr_ctrl->sppt_apu_only = output.sppt_apu_only_quiet;
        pwr_ctrl->stt_min = output.stt_min_limit_quiet;
        pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_quiet;
        pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_quiet;

        pwr_ctrl = &config_store.mode_set[AUTO_BALANCE].power_control;
        pwr_ctrl->spl = output.spl_balanced;
        pwr_ctrl->sppt = output.sppt_balanced;
        pwr_ctrl->fppt = output.fppt_balanced;
        pwr_ctrl->sppt_apu_only = output.sppt_apu_only_balanced;
        pwr_ctrl->stt_min = output.stt_min_limit_balanced;
        pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_balanced;
        pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_balanced;

        pwr_ctrl = &config_store.mode_set[AUTO_PERFORMANCE].power_control;
        pwr_ctrl->spl = output.spl_perf;
        pwr_ctrl->sppt = output.sppt_perf;
        pwr_ctrl->fppt = output.fppt_perf;
        pwr_ctrl->sppt_apu_only = output.sppt_apu_only_perf;
        pwr_ctrl->stt_min = output.stt_min_limit_perf;
        pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_perf;
        pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_perf;

        pwr_ctrl = &config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].power_control;
        pwr_ctrl->spl = output.spl_perf_on_lap;
        pwr_ctrl->sppt = output.sppt_perf_on_lap;
        pwr_ctrl->fppt = output.fppt_perf_on_lap;
        pwr_ctrl->sppt_apu_only = output.sppt_apu_only_perf_on_lap;
        pwr_ctrl->stt_min = output.stt_min_limit_perf_on_lap;
        pwr_ctrl->stt_skin_temp[STT_TEMP_APU] = output.stt_apu_perf_on_lap;
        pwr_ctrl->stt_skin_temp[STT_TEMP_HS2] = output.stt_hs2_perf_on_lap;

        /* Fan ID */
        config_store.mode_set[AUTO_QUIET].fan_control.fan_id = output.fan_id_quiet;
        config_store.mode_set[AUTO_BALANCE].fan_control.fan_id = output.fan_id_balanced;
        config_store.mode_set[AUTO_PERFORMANCE].fan_control.fan_id = output.fan_id_perf;
        config_store.mode_set[AUTO_PERFORMANCE_ON_LAP].fan_control.fan_id =
                                                                        output.fan_id_perf;

        config_store.transition[AUTO_TRANSITION_TO_QUIET].target_mode = AUTO_QUIET;
        config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].target_mode =
                                                                AUTO_PERFORMANCE;
        config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].target_mode =
                                                                        AUTO_BALANCE;
        config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].target_mode =
                                                                        AUTO_BALANCE;

        config_store.transition[AUTO_TRANSITION_TO_QUIET].shifting_up = false;
        config_store.transition[AUTO_TRANSITION_TO_PERFORMANCE].shifting_up = true;
        config_store.transition[AUTO_TRANSITION_FROM_QUIET_TO_BALANCE].shifting_up = true;
        config_store.transition[AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE].shifting_up =
                                                                                false;

        for (i = 0 ; i < AUTO_MODE_MAX ; i++) {
                if (config_store.mode_set[i].fan_control.fan_id == FAN_INDEX_AUTO)
                        config_store.mode_set[i].fan_control.manual = false;
                else
                        config_store.mode_set[i].fan_control.manual = true;
        }

        /* set to initial default values */
        config_store.current_mode = AUTO_BALANCE;
        dev->socket_power_history_idx = -1;

        amd_pmf_dump_auto_mode_defaults(&config_store);
}

int amd_pmf_reset_amt(struct amd_pmf_dev *dev)
{
        /*
         * OEM BIOS implementation guide says that if the auto mode is enabled
         * the platform_profile registration shall be done by the OEM driver.
         * There could be cases where both static slider and auto mode BIOS
         * functions are enabled, in that case enable static slider updates
         * only if it advertised as supported.
         */

        if (is_apmf_func_supported(dev, APMF_FUNC_STATIC_SLIDER_GRANULAR)) {
                dev_dbg(dev->dev, "resetting AMT thermals\n");
                amd_pmf_set_sps_power_limits(dev);
        }
        return 0;
}

void amd_pmf_handle_amt(struct amd_pmf_dev *dev)
{
        amd_pmf_set_automode(dev, config_store.current_mode, NULL);
}

void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev)
{
        cancel_delayed_work_sync(&dev->work_buffer);
}

void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev)
{
        amd_pmf_load_defaults_auto_mode(dev);
        amd_pmf_init_metrics_table(dev);
}