--- /dev/null
+// 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);
+
+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, 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);
+
+       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;
+               }
+       }
+
+       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_set_automode(dev, 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";
+       }
+}
+
+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;
+}
+
+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);
+       /* update the thermal limits for Automode */
+       amd_pmf_set_automode(dev, config_store.current_mode, NULL);
+       amd_pmf_init_metrics_table(dev);
+}
 
 #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 FAN_INDEX_AUTO         0xFFFFFFFF
 
 #define ARG_NONE 0
+#define AVG_SAMPLE_SIZE 3
 
 /* AMD PMF BIOS interfaces */
 struct apmf_verify_interface {
        struct smu_pmf_metrics m_table;
        struct delayed_work work_buffer;
        ktime_t start_time;
+       int socket_power_history[AVG_SAMPLE_SIZE];
+       int socket_power_history_idx;
 };
 
 struct apmf_sps_prop_granular {
        unsigned long fan_id;
 };
 
+struct power_table_control {
+       u32 spl;
+       u32 sppt;
+       u32 fppt;
+       u32 sppt_apu_only;
+       u32 stt_min;
+       u32 stt_skin_temp[STT_TEMP_COUNT];
+       u32 reserved[16];
+};
+
+/* Auto Mode Layer */
+enum auto_mode_transition_priority {
+       AUTO_TRANSITION_TO_PERFORMANCE, /* Any other mode to Performance Mode */
+       AUTO_TRANSITION_FROM_QUIET_TO_BALANCE, /* Quiet Mode to Balance Mode */
+       AUTO_TRANSITION_TO_QUIET, /* Any other mode to Quiet Mode */
+       AUTO_TRANSITION_FROM_PERFORMANCE_TO_BALANCE, /* Performance Mode to Balance Mode */
+       AUTO_TRANSITION_MAX,
+};
+
+enum auto_mode_mode {
+       AUTO_QUIET,
+       AUTO_BALANCE,
+       AUTO_PERFORMANCE_ON_LAP,
+       AUTO_PERFORMANCE,
+       AUTO_MODE_MAX,
+};
+
+struct auto_mode_trans_params {
+       u32 time_constant; /* minimum time required to switch to next mode */
+       u32 power_delta; /* delta power to shift mode */
+       u32 power_threshold;
+       u32 timer; /* elapsed time. if timer > TimeThreshold, it will move to next mode */
+       u32 applied;
+       enum auto_mode_mode target_mode;
+       u32 shifting_up;
+};
+
+struct auto_mode_mode_settings {
+       struct power_table_control power_control;
+       struct fan_table_control fan_control;
+       u32 power_floor;
+};
+
+struct auto_mode_mode_config {
+       struct auto_mode_trans_params transition[AUTO_TRANSITION_MAX];
+       struct auto_mode_mode_settings mode_set[AUTO_MODE_MAX];
+       enum auto_mode_mode current_mode;
+};
+
+struct apmf_auto_mode {
+       u16 size;
+       /* time constant */
+       u32 balanced_to_perf;
+       u32 perf_to_balanced;
+       u32 quiet_to_balanced;
+       u32 balanced_to_quiet;
+       /* power floor */
+       u32 pfloor_perf;
+       u32 pfloor_balanced;
+       u32 pfloor_quiet;
+       /* Power delta for mode change */
+       u32 pd_balanced_to_perf;
+       u32 pd_perf_to_balanced;
+       u32 pd_quiet_to_balanced;
+       u32 pd_balanced_to_quiet;
+       /* skin temperature limits */
+       u8 stt_apu_perf_on_lap; /* CQL ON */
+       u8 stt_hs2_perf_on_lap; /* CQL ON */
+       u8 stt_apu_perf;
+       u8 stt_hs2_perf;
+       u8 stt_apu_balanced;
+       u8 stt_hs2_balanced;
+       u8 stt_apu_quiet;
+       u8 stt_hs2_quiet;
+       u32 stt_min_limit_perf_on_lap; /* CQL ON */
+       u32 stt_min_limit_perf;
+       u32 stt_min_limit_balanced;
+       u32 stt_min_limit_quiet;
+       /* SPL based */
+       u32 fppt_perf_on_lap; /* CQL ON */
+       u32 sppt_perf_on_lap; /* CQL ON */
+       u32 spl_perf_on_lap; /* CQL ON */
+       u32 sppt_apu_only_perf_on_lap; /* CQL ON */
+       u32 fppt_perf;
+       u32 sppt_perf;
+       u32 spl_perf;
+       u32 sppt_apu_only_perf;
+       u32 fppt_balanced;
+       u32 sppt_balanced;
+       u32 spl_balanced;
+       u32 sppt_apu_only_balanced;
+       u32 fppt_quiet;
+       u32 sppt_quiet;
+       u32 spl_quiet;
+       u32 sppt_apu_only_quiet;
+       /* Fan ID */
+       u32 fan_id_perf;
+       u32 fan_id_balanced;
+       u32 fan_id_quiet;
+} __packed;
+
 /* Core Layer */
 int apmf_acpi_init(struct amd_pmf_dev *pmf_dev);
 void apmf_acpi_deinit(struct amd_pmf_dev *pmf_dev);
 
 
 int apmf_update_fan_idx(struct amd_pmf_dev *pdev, bool manual, u32 idx);
+
+/* Auto Mode Layer */
+int apmf_get_auto_mode_def(struct amd_pmf_dev *pdev, struct apmf_auto_mode *data);
+void amd_pmf_init_auto_mode(struct amd_pmf_dev *dev);
+void amd_pmf_deinit_auto_mode(struct amd_pmf_dev *dev);
+void amd_pmf_trans_automode(struct amd_pmf_dev *dev, int socket_power, ktime_t time_elapsed_ms);
+
 #endif /* PMF_H */