From 9280340fe029a23c170189dacdf9b2c734e9dfdd Mon Sep 17 00:00:00 2001
From: David Guillen Fandos <david@davidgf.net>
Date: Wed, 12 May 2021 02:16:25 +0200
Subject: [PATCH] CPU governor/frequency part 2

This adds managed policies and settings to store them and reload them at
startup. Only for Lakka builds!
---
 configuration.c               |  10 ++
 configuration.h               |   8 ++
 intl/msg_hash_us.h            |  68 ++++++++++++
 menu/cbs/menu_cbs_get_value.c |  90 ++++++++++++++++
 menu/cbs/menu_cbs_left.c      |  87 ++++++++++++++++
 menu/cbs/menu_cbs_right.c     |  89 +++++++++++++++-
 menu/cbs/menu_cbs_sublabel.c  |  20 +++-
 menu/menu_displaylist.c       |  65 ++++++++++--
 menu/menu_driver.h            |   2 +
 misc/cpufreq/cpufreq.c        | 189 +++++++++++++++++++++++++++++++---
 misc/cpufreq/cpufreq.h        |  46 ++++++++-
 msg_hash.h                    |  19 ++++
 retroarch.c                   |  24 +++++
 13 files changed, 691 insertions(+), 26 deletions(-)

diff --git a/configuration.c b/configuration.c
index ef622c9c9c..36f71d72ca 100644
--- a/configuration.c
+++ b/configuration.c
@@ -1279,6 +1279,10 @@ static struct config_array_setting *populate_settings_array(settings_t *settings
    SETTING_ARRAY("discord_app_id",           settings->arrays.discord_app_id, true, DEFAULT_DISCORD_APP_ID, true);
    SETTING_ARRAY("ai_service_url",           settings->arrays.ai_service_url, true, DEFAULT_AI_SERVICE_URL, true);
    SETTING_ARRAY("crt_switch_timings",       settings->arrays.crt_switch_timings, false, NULL, true);
+#ifdef HAVE_LAKKA
+   SETTING_ARRAY("cpu_main_gov",             settings->arrays.cpu_main_gov, false, NULL, true);
+   SETTING_ARRAY("cpu_menu_gov",             settings->arrays.cpu_menu_gov, false, NULL, true);
+#endif
 
    *size = count;
 
@@ -2113,6 +2117,12 @@ static struct config_uint_setting *populate_settings_uint(
 
    SETTING_UINT("video_black_frame_insertion",   &settings->uints.video_black_frame_insertion, true, DEFAULT_BLACK_FRAME_INSERTION, false);
 
+#ifdef HAVE_LAKKA
+   SETTING_UINT("cpu_scaling_mode",            &settings->uints.cpu_scaling_mode,    true,   0, false);
+   SETTING_UINT("cpu_min_freq",                &settings->uints.cpu_min_freq,        true,   1, false);
+   SETTING_UINT("cpu_max_freq",                &settings->uints.cpu_max_freq,        true, ~0U, false);
+#endif
+
    *size = count;
 
    return tmp;
diff --git a/configuration.h b/configuration.h
index 3dcdf9a8ee..055b787ebd 100644
--- a/configuration.h
+++ b/configuration.h
@@ -288,6 +288,12 @@ typedef struct settings
       unsigned core_updater_auto_backup_history_size;
       unsigned video_black_frame_insertion;
       unsigned quit_on_close_content;
+
+#ifdef HAVE_LAKKA
+      unsigned cpu_scaling_mode;
+      unsigned cpu_min_freq;
+      unsigned cpu_max_freq;
+#endif
    } uints;
 
    struct
@@ -387,6 +393,8 @@ typedef struct settings
       char crt_switch_timings[255];
 #ifdef HAVE_LAKKA
       char timezone[TIMEZONE_LENGTH];
+      char cpu_main_gov[32];
+      char cpu_menu_gov[32];
 #endif
    } arrays;
 
diff --git a/intl/msg_hash_us.h b/intl/msg_hash_us.h
index 50f84e7af5..4856e6b048 100644
--- a/intl/msg_hash_us.h
+++ b/intl/msg_hash_us.h
@@ -12092,6 +12092,58 @@ MSG_HASH(
    MENU_ENUM_LABEL_VALUE_CPU_POLICY_ENTRY,
    "Policy"
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE,
+   "Governing Mode"
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANUAL,
+   "Manual"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANUAL,
+   "Allows to manually tweak every detail in every CPU: governor, frequencies, etc. Only recommended for advanced users."
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF,
+   "Performance (Managed)"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF,
+   "Default and recommended mode. Maximum performance while playing while saving power when paused or browsing menus."
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PER_CONTEXT,
+   "Custom Managed"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANAGED_PER_CONTEXT,
+   "Allows to choose what governors to use in menus and during gameplay. Performance, Ondemand or Schedutil are recommended during gameplay."
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MAX_PERF,
+   "Maximum Performance"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MAX_PERF,
+   "Always maximum performance: highest frequencies for best experience."
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MIN_POWER,
+   "Minimum Power"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MIN_POWER,
+   "Use the lowest frequency available to save power. Useful on battery powered devices but performance will be significantly reduced."
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_BALANCED,
+   "Balanced"
+   )
+MSG_HASH(
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_BALANCED,
+   "Adapts to the current workload. Works well with most devices and emulators and helps saving power. Demanding games and cores might suffer a performance drop on some devices."
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_VALUE_CPU_POLICY_MIN_FREQ,
    "Minimum Frequency"
@@ -12100,10 +12152,26 @@ MSG_HASH(
    MENU_ENUM_LABEL_VALUE_CPU_POLICY_MAX_FREQ,
    "Maximum Frequency"
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_MANAGED_MIN_FREQ,
+   "Minimum Core Frequency"
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_MANAGED_MAX_FREQ,
+   "Maximum Core Frequency"
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_VALUE_CPU_POLICY_GOVERNOR,
    "CPU Governor"
    )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_POLICY_CORE_GOVERNOR,
+   "Core Governor"
+   )
+MSG_HASH(
+   MENU_ENUM_LABEL_VALUE_CPU_POLICY_MENU_GOVERNOR,
+   "Menu Governor"
+   )
 MSG_HASH(
    MENU_ENUM_LABEL_VALUE_PAL60_ENABLE,
    "Use PAL60 Mode"
diff --git a/menu/cbs/menu_cbs_get_value.c b/menu/cbs/menu_cbs_get_value.c
index f2a2ca9db7..f5befe000c 100644
--- a/menu/cbs/menu_cbs_get_value.c
+++ b/menu/cbs/menu_cbs_get_value.c
@@ -518,6 +518,50 @@ static void menu_action_setting_disp_set_label_core_manager_entry(
 
 #ifndef HAVE_LAKKA_SWITCH
 #ifdef HAVE_LAKKA
+static void menu_action_setting_disp_cpu_gov_mode(
+      file_list_t* list,
+      unsigned *w, unsigned type, unsigned i,
+      const char *label,
+      char *s, size_t len,
+      const char *path,
+      char *s2, size_t len2)
+{
+   const char *alt        = list->list[i].alt
+         ? list->list[i].alt
+         : list->list[i].path;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(NULL);
+
+   if (alt)
+      strlcpy(s2, alt, len2);
+
+   strlcpy(s, msg_hash_to_str(
+      MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF + (int)mode), len);
+}
+
+static void menu_action_setting_disp_cpu_gov_choose(
+      file_list_t* list,
+      unsigned *w, unsigned type, unsigned i,
+      const char *label,
+      char *s, size_t len,
+      const char *path,
+      char *s2, size_t len2)
+{
+   const char *alt        = list->list[i].alt
+         ? list->list[i].alt
+         : list->list[i].path;
+   int fnum = atoi(list->list[i].label);
+   cpu_scaling_opts_t opts;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(&opts);
+
+   if (alt)
+      strlcpy(s2, alt, len2);
+
+   if (!fnum)
+      strlcpy(s, opts.main_policy, len);
+   else
+      strlcpy(s, opts.menu_policy, len);
+}
+
 static void menu_action_setting_disp_set_label_cpu_policy(
       file_list_t* list,
       unsigned *w, unsigned type, unsigned i,
@@ -542,6 +586,39 @@ static void menu_action_setting_disp_set_label_cpu_policy(
          MENU_ENUM_LABEL_VALUE_CPU_POLICY_ENTRY), policyid);
 }
 
+static void menu_action_cpu_managed_freq_label(
+      file_list_t* list,
+      unsigned *w, unsigned type, unsigned i,
+      const char *label,
+      char *s, size_t len,
+      const char *path,
+      char *s2, size_t len2)
+{
+   uint32_t freq = 0;
+   cpu_scaling_opts_t opts;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(&opts);
+
+   switch (type) {
+   case MENU_SETTINGS_CPU_MANAGED_SET_MINFREQ:
+      strlcpy(s2, msg_hash_to_str(
+         MENU_ENUM_LABEL_VALUE_CPU_MANAGED_MIN_FREQ), len2);
+      freq = opts.min_freq;
+      break;
+   case MENU_SETTINGS_CPU_MANAGED_SET_MAXFREQ:
+      strlcpy(s2, msg_hash_to_str(
+         MENU_ENUM_LABEL_VALUE_CPU_MANAGED_MAX_FREQ), len2);
+      freq = opts.max_freq;
+      break;
+   };
+
+   if (freq == 1)
+      strlcpy(s, "Min.", len);
+   else if (freq == ~0U)
+      strlcpy(s, "Max.", len);
+   else
+      snprintf(s, len, "%u MHz", freq / 1000);
+}
+
 static void menu_action_cpu_freq_label(
       file_list_t* list,
       unsigned *w, unsigned type, unsigned i,
@@ -1786,6 +1863,15 @@ static int menu_cbs_init_bind_get_string_representation_compare_label(
             break;
          #ifndef HAVE_LAKKA_SWITCH
          #ifdef HAVE_LAKKA
+         case MENU_ENUM_LABEL_CPU_PERF_MODE:
+            BIND_ACTION_GET_VALUE(cbs,
+                  menu_action_setting_disp_cpu_gov_mode);
+            break;
+         case MENU_ENUM_LABEL_CPU_POLICY_CORE_GOVERNOR:
+         case MENU_ENUM_LABEL_CPU_POLICY_MENU_GOVERNOR:
+            BIND_ACTION_GET_VALUE(cbs,
+                  menu_action_setting_disp_cpu_gov_choose);
+            break;
          case MENU_ENUM_LABEL_CPU_POLICY_ENTRY:
             BIND_ACTION_GET_VALUE(cbs,
                   menu_action_setting_disp_set_label_cpu_policy);
@@ -1794,6 +1880,10 @@ static int menu_cbs_init_bind_get_string_representation_compare_label(
          case MENU_ENUM_LABEL_CPU_POLICY_MAX_FREQ:
             BIND_ACTION_GET_VALUE(cbs, menu_action_cpu_freq_label);
             break;
+         case MENU_ENUM_LABEL_CPU_MANAGED_MIN_FREQ:
+         case MENU_ENUM_LABEL_CPU_MANAGED_MAX_FREQ:
+            BIND_ACTION_GET_VALUE(cbs, menu_action_cpu_managed_freq_label);
+            break;
          case MENU_ENUM_LABEL_CPU_POLICY_GOVERNOR:
             BIND_ACTION_GET_VALUE(cbs, menu_action_cpu_governor_label);
             break;
diff --git a/menu/cbs/menu_cbs_left.c b/menu/cbs/menu_cbs_left.c
index bbe62cc687..66a85c1990 100644
--- a/menu/cbs/menu_cbs_left.c
+++ b/menu/cbs/menu_cbs_left.c
@@ -672,6 +672,82 @@ static int manual_content_scan_core_name_left(unsigned type, const char *label,
 }
 
 #ifdef HAVE_LAKKA
+static int cpu_policy_mode_change(unsigned type, const char *label,
+      bool wraparound)
+{
+   bool refresh = false;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(NULL);
+   if (mode != CPUSCALING_MANAGED_PERFORMANCE)
+      mode--;
+   set_cpu_scaling_mode(mode, NULL);
+   menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
+   return 0;
+}
+
+static int cpu_policy_freq_managed_tweak(unsigned type, const char *label,
+      bool wraparound)
+{
+   bool refresh = false;
+   cpu_scaling_opts_t opts;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(&opts);
+
+   switch (type) {
+   case MENU_SETTINGS_CPU_MANAGED_SET_MINFREQ:
+      opts.min_freq = get_cpu_scaling_next_frequency_limit(
+         opts.min_freq, -1);
+      set_cpu_scaling_mode(mode, &opts);
+      break;
+   case MENU_SETTINGS_CPU_MANAGED_SET_MAXFREQ:
+      opts.max_freq = get_cpu_scaling_next_frequency_limit(
+         opts.max_freq, -1);
+      set_cpu_scaling_mode(mode, &opts);
+      break;
+   };
+
+   return 0;
+}
+
+static int cpu_policy_freq_managed_gov(unsigned type, const char *label,
+      bool wraparound)
+{
+   int pidx;
+   bool refresh = false;
+   cpu_scaling_opts_t opts;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(&opts);
+   cpu_scaling_driver_t **drivers = get_cpu_scaling_drivers(false);
+
+   /* Using drivers[0] governors, should be improved */
+   if (!drivers || !drivers[0])
+      return -1;
+
+   switch (atoi(label)) {
+   case 0:
+      pidx = string_list_find_elem(drivers[0]->available_governors,
+         opts.main_policy);
+      if (pidx > 1)
+      {
+         strlcpy(opts.main_policy,
+            drivers[0]->available_governors->elems[pidx-2].data,
+            sizeof(opts.main_policy));
+         set_cpu_scaling_mode(mode, &opts);
+      }
+      break;
+   case 1:
+      pidx = string_list_find_elem(drivers[0]->available_governors,
+         opts.menu_policy);
+      if (pidx > 1)
+      {
+         strlcpy(opts.menu_policy,
+            drivers[0]->available_governors->elems[pidx-2].data,
+            sizeof(opts.menu_policy));
+         set_cpu_scaling_mode(mode, &opts);
+      }
+      break;
+   };
+
+   return 0;
+}
+
 static int cpu_policy_freq_tweak(unsigned type, const char *label,
       bool wraparound)
 {
@@ -1012,11 +1088,22 @@ static int menu_cbs_init_bind_left_compare_label(menu_file_list_cbs_t *cbs,
                break;
             #ifndef HAVE_LAKKA_SWITCH
             #ifdef HAVE_LAKKA
+            case MENU_ENUM_LABEL_CPU_PERF_MODE:
+               BIND_ACTION_LEFT(cbs, cpu_policy_mode_change);
+               break;
             case MENU_ENUM_LABEL_CPU_POLICY_MAX_FREQ:
             case MENU_ENUM_LABEL_CPU_POLICY_MIN_FREQ:
             case MENU_ENUM_LABEL_CPU_POLICY_GOVERNOR:
                BIND_ACTION_LEFT(cbs, cpu_policy_freq_tweak);
                break;
+            case MENU_ENUM_LABEL_CPU_MANAGED_MIN_FREQ:
+            case MENU_ENUM_LABEL_CPU_MANAGED_MAX_FREQ:
+               BIND_ACTION_LEFT(cbs, cpu_policy_freq_managed_tweak);
+               break;
+            case MENU_ENUM_LABEL_CPU_POLICY_CORE_GOVERNOR:
+            case MENU_ENUM_LABEL_CPU_POLICY_MENU_GOVERNOR:
+               BIND_ACTION_LEFT(cbs, cpu_policy_freq_managed_gov);
+               break;
             #endif
             #endif
             default:
diff --git a/menu/cbs/menu_cbs_right.c b/menu/cbs/menu_cbs_right.c
index 728c270ff0..aa68f6f863 100644
--- a/menu/cbs/menu_cbs_right.c
+++ b/menu/cbs/menu_cbs_right.c
@@ -789,8 +789,84 @@ static int manual_content_scan_core_name_right(unsigned type, const char *label,
    return 0;
 }
 
-#ifdef HAVE_LAKKA_SWITCH
+#ifndef HAVE_LAKKA_SWITCH
 #ifdef HAVE_LAKKA
+static int cpu_policy_mode_change(unsigned type, const char *label,
+      bool wraparound)
+{
+   bool refresh = false;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(NULL);
+   if (mode != CPUSCALING_MANUAL)
+      mode++;
+   set_cpu_scaling_mode(mode, NULL);
+   menu_entries_ctl(MENU_ENTRIES_CTL_SET_REFRESH, &refresh);
+   return 0;
+}
+
+static int cpu_policy_freq_managed_tweak(unsigned type, const char *label,
+      bool wraparound)
+{
+   bool refresh = false;
+   cpu_scaling_opts_t opts;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(&opts);
+
+   switch (type) {
+   case MENU_SETTINGS_CPU_MANAGED_SET_MINFREQ:
+      opts.min_freq = get_cpu_scaling_next_frequency_limit(
+         opts.min_freq, 1);
+      set_cpu_scaling_mode(mode, &opts);
+      break;
+   case MENU_SETTINGS_CPU_MANAGED_SET_MAXFREQ:
+      opts.max_freq = get_cpu_scaling_next_frequency_limit(
+         opts.max_freq, 1);
+      set_cpu_scaling_mode(mode, &opts);
+      break;
+   };
+
+   return 0;
+}
+
+static int cpu_policy_freq_managed_gov(unsigned type, const char *label,
+      bool wraparound)
+{
+   int pidx;
+   bool refresh = false;
+   cpu_scaling_opts_t opts;
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(&opts);
+   cpu_scaling_driver_t **drivers = get_cpu_scaling_drivers(false);
+
+   /* Using drivers[0] governors, should be improved */
+   if (!drivers || !drivers[0])
+      return -1;
+
+   switch (atoi(label)) {
+   case 0:
+      pidx = string_list_find_elem(drivers[0]->available_governors,
+         opts.main_policy);
+      if (pidx && pidx + 1 < drivers[0]->available_governors->size)
+      {
+         strlcpy(opts.main_policy,
+            drivers[0]->available_governors->elems[pidx].data,
+            sizeof(opts.main_policy));
+         set_cpu_scaling_mode(mode, &opts);
+      }
+      break;
+   case 1:
+      pidx = string_list_find_elem(drivers[0]->available_governors,
+         opts.menu_policy);
+      if (pidx && pidx + 1 < drivers[0]->available_governors->size)
+      {
+         strlcpy(opts.menu_policy,
+            drivers[0]->available_governors->elems[pidx].data,
+            sizeof(opts.menu_policy));
+         set_cpu_scaling_mode(mode, &opts);
+      }
+      break;
+   };
+
+   return 0;
+}
+
 static int cpu_policy_freq_tweak(unsigned type, const char *label,
       bool wraparound)
 {
@@ -1132,11 +1208,22 @@ static int menu_cbs_init_bind_right_compare_label(menu_file_list_cbs_t *cbs,
                break;
             #ifndef HAVE_LAKKA_SWITCH
             #ifdef HAVE_LAKKA
+            case MENU_ENUM_LABEL_CPU_PERF_MODE:
+               BIND_ACTION_RIGHT(cbs, cpu_policy_mode_change);
+               break;
             case MENU_ENUM_LABEL_CPU_POLICY_MAX_FREQ:
             case MENU_ENUM_LABEL_CPU_POLICY_MIN_FREQ:
             case MENU_ENUM_LABEL_CPU_POLICY_GOVERNOR:
                BIND_ACTION_RIGHT(cbs, cpu_policy_freq_tweak);
                break;
+            case MENU_ENUM_LABEL_CPU_MANAGED_MIN_FREQ:
+            case MENU_ENUM_LABEL_CPU_MANAGED_MAX_FREQ:
+               BIND_ACTION_RIGHT(cbs, cpu_policy_freq_managed_tweak);
+               break;
+            case MENU_ENUM_LABEL_CPU_POLICY_CORE_GOVERNOR:
+            case MENU_ENUM_LABEL_CPU_POLICY_MENU_GOVERNOR:
+               BIND_ACTION_RIGHT(cbs, cpu_policy_freq_managed_gov);
+               break;
             #endif
             #endif
             default:
diff --git a/menu/cbs/menu_cbs_sublabel.c b/menu/cbs/menu_cbs_sublabel.c
index 3c2851a4c1..00fbe1ec4f 100644
--- a/menu/cbs/menu_cbs_sublabel.c
+++ b/menu/cbs/menu_cbs_sublabel.c
@@ -1020,15 +1020,28 @@ static int action_bind_sublabel_cpu_policy_entry_list(
 {
    /* Displays info about the Policy entry */
    cpu_scaling_driver_t **drivers = get_cpu_scaling_drivers(false);
+   int idx = atoi(path);
    if (drivers)
    {
-      sprintf(s, "%s | Freq: %u MHz\n", drivers[i]->scaling_governor,
-         drivers[i]->current_frequency / 1000);
+      sprintf(s, "%s | Freq: %u MHz\n", drivers[idx]->scaling_governor,
+         drivers[idx]->current_frequency / 1000);
       return 0;
    }
 
    return -1;
 }
+static int action_bind_sublabel_cpu_perf_mode(
+      file_list_t *list,
+      unsigned type, unsigned i,
+      const char *label, const char *path,
+      char *s, size_t len)
+{
+   /* Displays info about the mode selected */
+   enum cpu_scaling_mode mode = get_cpu_scaling_mode(NULL);
+   strlcpy(s, msg_hash_to_str(
+      MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF + (int)mode), len);
+   return 0;
+}
 #endif
 #endif
 #ifdef HAVE_CHEEVOS
@@ -3947,6 +3960,9 @@ int menu_cbs_init_bind_sublabel(menu_file_list_cbs_t *cbs,
          case MENU_ENUM_LABEL_CPU_POLICY_ENTRY:
             BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cpu_policy_entry_list);
             break;
+         case MENU_ENUM_LABEL_CPU_PERF_MODE:
+            BIND_ACTION_SUBLABEL(cbs, action_bind_sublabel_cpu_perf_mode);
+            break;
 #endif
 #endif
          case MENU_ENUM_LABEL_USER_LANGUAGE:
diff --git a/menu/menu_displaylist.c b/menu/menu_displaylist.c
index 438a161d10..550d4ee99f 100644
--- a/menu/menu_displaylist.c
+++ b/menu/menu_displaylist.c
@@ -9869,17 +9869,66 @@ bool menu_displaylist_ctl(enum menu_displaylist_ctl_state type,
          if (drivers)
          {
             int count = 0;
-            while (*drivers)
+
+            menu_entries_append_enum(info->list,
+               msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE),
+               msg_hash_to_str(MENU_ENUM_LABEL_CPU_PERF_MODE),
+               MENU_ENUM_LABEL_CPU_PERF_MODE,
+               0, 0, 0);
+
+            switch (get_cpu_scaling_mode(NULL))
             {
-               char policyid[16];
-               sprintf(policyid, "%u", count++);
+            case CPUSCALING_MANUAL:
+               while (*drivers)
+               {
+                  char policyid[16];
+                  sprintf(policyid, "%u", count++);
+                  menu_entries_append_enum(info->list,
+                     policyid,
+                     policyid,
+                     MENU_ENUM_LABEL_CPU_POLICY_ENTRY,
+                     0, 0, 0);
+                  drivers++;
+               }
+               break;
+            case CPUSCALING_MANAGED_PER_CONTEXT:
+               /* Allows user to pick two governors */
                menu_entries_append_enum(info->list,
-                  policyid,
-                  policyid,
-                  MENU_ENUM_LABEL_CPU_POLICY_ENTRY,
+                  msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CPU_POLICY_CORE_GOVERNOR),
+                  "0",
+                  MENU_ENUM_LABEL_CPU_POLICY_CORE_GOVERNOR,
                   0, 0, 0);
-               drivers++;
-            }
+
+               menu_entries_append_enum(info->list,
+                  msg_hash_to_str(MENU_ENUM_LABEL_VALUE_CPU_POLICY_MENU_GOVERNOR),
+                  "1",
+                  MENU_ENUM_LABEL_CPU_POLICY_MENU_GOVERNOR,
+                  0, 0, 0);
+
+               /* fallthrough */
+            case CPUSCALING_MANAGED_PERFORMANCE:
+               /* Allow users to choose max/min frequencies */
+               menu_entries_append_enum(info->list,
+                  "0",
+                  "0",
+                  MENU_ENUM_LABEL_CPU_MANAGED_MIN_FREQ,
+                  MENU_SETTINGS_CPU_MANAGED_SET_MINFREQ,
+                  0, 0);
+
+               menu_entries_append_enum(info->list,
+                  "1",
+                  "1",
+                  MENU_ENUM_LABEL_CPU_MANAGED_MAX_FREQ,
+                  MENU_SETTINGS_CPU_MANAGED_SET_MAXFREQ,
+                  0, 0);
+
+               break;
+            case CPUSCALING_MAX_PERFORMANCE:
+            case CPUSCALING_MIN_POWER:
+            case CPUSCALING_BALANCED:
+               /* No settings for these modes */
+               break;
+            };
          }
 
          info->need_push    = true;
diff --git a/menu/menu_driver.h b/menu/menu_driver.h
index 19eb03fca3..1cff563d30 100644
--- a/menu/menu_driver.h
+++ b/menu/menu_driver.h
@@ -214,6 +214,8 @@ enum menu_settings_type
    MENU_SETTINGS_CPU_POLICY_SET_MINFREQ,
    MENU_SETTINGS_CPU_POLICY_SET_MAXFREQ,
    MENU_SETTINGS_CPU_POLICY_SET_GOVERNOR,
+   MENU_SETTINGS_CPU_MANAGED_SET_MINFREQ,
+   MENU_SETTINGS_CPU_MANAGED_SET_MAXFREQ,
 
    MENU_SET_CDROM_LIST,
    MENU_SET_LOAD_CDROM_LIST,
diff --git a/misc/cpufreq/cpufreq.c b/misc/cpufreq/cpufreq.c
index 1fba52ed97..84e8280b9f 100644
--- a/misc/cpufreq/cpufreq.c
+++ b/misc/cpufreq/cpufreq.c
@@ -24,12 +24,18 @@
 #include <retro_miscellaneous.h>
 
 #include "cpufreq.h"
+#include "../../configuration.h"
 
 #define REFRESH_TIMEOUT  2
 #define CPU_POLICIES_DIR "/sys/devices/system/cpu/cpufreq/"
 
 static time_t last_update = 0;
 static cpu_scaling_driver_t **scaling_drivers = NULL;
+/* Mode state and its options */
+static enum cpu_scaling_mode cur_smode = CPUSCALING_MANAGED_PERFORMANCE;
+static cpu_scaling_opts_t cur_smode_opts = { 1, ~0U, "performance", "ondemand" };
+/* Precalculate and store the absolute max and min frequencies */
+static uint32_t abs_min_freq = 1, abs_max_freq = ~0U;
 
 static bool readparse_uint32(const char *path, uint32_t *value)
 {
@@ -80,15 +86,6 @@ static void free_drivers(cpu_scaling_driver_t **d)
    }
 }
 
-void cpu_scaling_driver_free()
-{
-   if (scaling_drivers)
-      free_drivers(scaling_drivers);
-
-   scaling_drivers = NULL;
-   last_update = 0;
-}
-
 cpu_scaling_driver_t **get_cpu_scaling_drivers(bool can_update)
 {
    if (can_update && (time(NULL) > last_update + REFRESH_TIMEOUT ||
@@ -142,6 +139,12 @@ cpu_scaling_driver_t **get_cpu_scaling_drivers(bool can_update)
             "scaling_min_freq", sizeof(fpath));
          readparse_uint32(fpath, &drv->min_policy_freq);
 
+         /* Check current freq limits and update them */
+         if (abs_min_freq > drv->min_cpu_freq || abs_min_freq == 1)
+            abs_min_freq = drv->min_cpu_freq;
+         if (abs_max_freq < drv->max_cpu_freq || abs_max_freq == ~0U)
+            abs_max_freq = drv->max_cpu_freq;
+
          fill_pathname_join(fpath, policy_dir->elems[i].data,
             "scaling_max_freq", sizeof(fpath));
          readparse_uint32(fpath, &drv->max_policy_freq);
@@ -160,6 +163,7 @@ cpu_scaling_driver_t **get_cpu_scaling_drivers(bool can_update)
          filestream_read_file(fpath, (void**)&drv->scaling_governor, NULL);
          string_remove_all_chars(drv->scaling_governor, '\n');
 
+         /* This is not available in many platforms! */
          fill_pathname_join(fpath, policy_dir->elems[i].data,
             "scaling_available_frequencies", sizeof(fpath));
          tmplst = readparse_list(fpath);
@@ -168,7 +172,12 @@ cpu_scaling_driver_t **get_cpu_scaling_drivers(bool can_update)
             drv->available_freqs = calloc(tmplst->size, sizeof(uint32_t));
             for (j = 0; j < tmplst->size; j++)
             {
-               drv->available_freqs[j] = (uint32_t)atol(tmplst->elems[j].data);
+               uint32_t freq = (uint32_t)atol(tmplst->elems[j].data);
+               drv->available_freqs[j] = freq;
+               if (abs_min_freq > freq || abs_min_freq == 1)
+                  abs_min_freq = freq;
+               if (abs_max_freq < freq || abs_max_freq == ~0U)
+                  abs_max_freq = freq;
             }
             string_list_free(tmplst);
          }
@@ -254,10 +263,30 @@ uint32_t get_cpu_scaling_next_frequency(
       freq = freq + step * 100000;
    }
 
-   if (freq > driver->max_cpu_freq)
-      freq = driver->max_cpu_freq;
-   if (freq < driver->min_cpu_freq)
-      freq = driver->min_cpu_freq;
+   freq = MIN(freq, driver->max_cpu_freq);
+   freq = MAX(freq, driver->min_cpu_freq);
+
+   return freq;
+}
+
+uint32_t get_cpu_scaling_next_frequency_limit(uint32_t freq, int step)
+{
+   /* Tune step, if it's smaller than 100MHz */
+   unsigned fstep = 100000;
+   if ((abs_max_freq - abs_min_freq) / 20 < fstep)
+      fstep = 50000;
+
+   if (freq <= abs_min_freq && step < 0)
+      return 1;   /* Means "minimum frequency" */
+
+   if (freq >= abs_max_freq && step > 0)
+      return ~0U;   /* Means "maximum frequency" */
+
+   /* Just do small steps towards the max/min */
+   freq = freq + step * fstep;
+
+   freq = MIN(freq, abs_max_freq);
+   freq = MAX(freq, abs_min_freq);
 
    return freq;
 }
@@ -278,4 +307,136 @@ bool set_cpu_scaling_governor(cpu_scaling_driver_t *driver, const char* governor
    return false;
 }
 
+static void steer_all_drivers(
+   const char *governor,
+   uint32_t minfreq,
+   uint32_t maxfreq)
+{
+   cpu_scaling_driver_t **drivers = get_cpu_scaling_drivers(false);
+   if (!drivers)
+      return;
+   while (*drivers)
+   {
+      cpu_scaling_driver_t *d = *drivers++;
+      if (minfreq)
+         set_cpu_scaling_min_frequency(d, MAX(minfreq, d->min_cpu_freq));
+      if (maxfreq)
+         set_cpu_scaling_max_frequency(d, MIN(maxfreq, d->max_cpu_freq));
+      set_cpu_scaling_governor(d, governor);
+   }
+}
+
+void set_cpu_scaling_signal(enum cpu_scaling_event event)
+{
+   switch (cur_smode) {
+   case CPUSCALING_MANAGED_PERFORMANCE:
+      /* Bump to perf or fall back to ondemand depending on the RA state */
+      if (event == CPUSCALING_EVENT_FOCUS_CORE)
+         steer_all_drivers("performance", cur_smode_opts.min_freq,
+            cur_smode_opts.max_freq);
+      else
+         steer_all_drivers("ondemand", 1, ~0U);
+      break;
+   case CPUSCALING_MANAGED_PER_CONTEXT:
+      /* Apply the right settings the user specified */
+      if (event == CPUSCALING_EVENT_FOCUS_CORE)
+         steer_all_drivers(cur_smode_opts.main_policy, cur_smode_opts.min_freq,
+            cur_smode_opts.max_freq);
+      else
+         steer_all_drivers(cur_smode_opts.menu_policy, 1, ~0U);
+      break;
+   default:
+      break;
+   };
+}
+
+enum cpu_scaling_mode get_cpu_scaling_mode(cpu_scaling_opts_t *opts)
+{
+   if (opts)
+      *opts = cur_smode_opts;
+   return cur_smode;
+}
+
+void set_cpu_scaling_mode(
+   enum cpu_scaling_mode mode,
+   const cpu_scaling_opts_t *opts)
+{
+   settings_t *settings = config_get_ptr();
+
+   /* Store current state */
+   cur_smode = mode;
+   if (opts)
+      cur_smode_opts = *opts;
+
+   switch (mode)
+   {
+   case CPUSCALING_MANUAL:
+      /* Do nothing, the UI allows for tweaking directly */
+      break;
+   case CPUSCALING_MANAGED_PERFORMANCE:
+   case CPUSCALING_MANAGED_PER_CONTEXT:
+      /* Simulate a state change to enforce the policy */
+      set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
+      break;
+   case CPUSCALING_MAX_PERFORMANCE:
+      // Set performance and bump frequencies to min/max
+      steer_all_drivers("performance", 1, ~0U);
+      break;
+   case CPUSCALING_MIN_POWER:
+      // Set powersave and bump frequencies to min/max
+      steer_all_drivers("powersave", 1, ~0U);
+      break;
+   case CPUSCALING_BALANCED:
+      // Set ondemand and bump frequencies to min/max
+      steer_all_drivers("ondemand", 1, ~0U);
+      break;
+   };
+
+   if (settings)
+   {
+      /* Store current settings */
+      settings->uints.cpu_scaling_mode = (int)cur_smode;
+      settings->uints.cpu_min_freq = cur_smode_opts.min_freq;
+      settings->uints.cpu_max_freq = cur_smode_opts.max_freq;
+
+      strlcpy(settings->arrays.cpu_main_gov, cur_smode_opts.main_policy,
+         sizeof(settings->arrays.cpu_main_gov));
+      strlcpy(settings->arrays.cpu_menu_gov, cur_smode_opts.menu_policy,
+         sizeof(settings->arrays.cpu_menu_gov));
+   }
+};
+
+void cpu_scaling_driver_free()
+{
+   if (scaling_drivers)
+      free_drivers(scaling_drivers);
+
+   scaling_drivers = NULL;
+   last_update = 0;
+}
+
+void cpu_scaling_driver_init(void)
+{
+   /* Read the default settings */
+   settings_t *settings = config_get_ptr();
+   unsigned mode = settings->uints.cpu_scaling_mode;
+   cur_smode_opts.min_freq = settings->uints.cpu_min_freq;
+   cur_smode_opts.max_freq = settings->uints.cpu_max_freq;
+
+   if (mode <= (int)CPUSCALING_MANUAL)
+      cur_smode = (enum cpu_scaling_mode)mode;
+
+   if (settings->arrays.cpu_main_gov[0])
+      strlcpy(cur_smode_opts.main_policy, settings->arrays.cpu_main_gov,
+         sizeof(cur_smode_opts.main_policy));
+   if (settings->arrays.cpu_menu_gov[0])
+      strlcpy(cur_smode_opts.menu_policy, settings->arrays.cpu_menu_gov,
+         sizeof(cur_smode_opts.menu_policy));
+
+   /* Force update the policy tree */
+   get_cpu_scaling_drivers(true);
+
+   /* Force enforce these settings */
+   set_cpu_scaling_mode(cur_smode, NULL);
+}
 
diff --git a/misc/cpufreq/cpufreq.h b/misc/cpufreq/cpufreq.h
index 06712d4020..38607e6852 100644
--- a/misc/cpufreq/cpufreq.h
+++ b/misc/cpufreq/cpufreq.h
@@ -20,6 +20,36 @@
 
 RETRO_BEGIN_DECLS
 
+#define MAX_GOV_STRLEN   32
+
+/* Events from Frontend to the driver to drive policies */
+enum cpu_scaling_event
+{
+   CPUSCALING_EVENT_FOCUS_CORE,
+   CPUSCALING_EVENT_FOCUS_MENU,
+   CPUSCALING_EVENT_FOCUS_SCREENSAVER
+};
+
+/* Scaling mode selected by the user */
+enum cpu_scaling_mode
+{
+   CPUSCALING_MANAGED_PERFORMANCE = 0, /* Performance while running core     */
+   CPUSCALING_MANAGED_PER_CONTEXT,     /* Policies for core, menu, etc.      */
+   CPUSCALING_MAX_PERFORMANCE,         /* Performance (Max Freq)             */
+   CPUSCALING_MIN_POWER,               /* Use Powersave governor             */
+   CPUSCALING_BALANCED,                /* Uses schedutil/ondemand            */
+   CPUSCALING_MANUAL,                  /* Can manually tweak stuff           */
+};
+
+typedef struct cpu_scaling_opts
+{
+   /* Max/Min frequencies */
+   uint32_t min_freq, max_freq;
+   /* Options for CPUSCALING_POLICY_PER_CONTEXT */
+   char main_policy[MAX_GOV_STRLEN];
+   char menu_policy[MAX_GOV_STRLEN];
+} cpu_scaling_opts_t;
+
 typedef struct cpu_scaling_driver
 {
    /* Policy number in the sysfs tree */
@@ -39,7 +69,10 @@ typedef struct cpu_scaling_driver
 } cpu_scaling_driver_t;
 
 /* Safely free all memory used by the driver */
-void cpu_scaling_driver_free();
+void cpu_scaling_driver_free(void);
+
+/* Signal the initialization */
+void cpu_scaling_driver_init(void);
 
 /* Get a list of the available cpu scaling drivers */
 cpu_scaling_driver_t **get_cpu_scaling_drivers(bool can_update);
@@ -53,10 +86,21 @@ bool set_cpu_scaling_max_frequency(
 /* Calculate next/previous frequencies */
 uint32_t get_cpu_scaling_next_frequency(cpu_scaling_driver_t *driver,
    uint32_t freq, int step);
+uint32_t get_cpu_scaling_next_frequency_limit(uint32_t freq, int step);
 
 /* Set the scaling governor for this scaling driver */
 bool set_cpu_scaling_governor(cpu_scaling_driver_t *driver, const char* governor);
 
+/* Signal certain events that are of interest of this driver */
+void set_cpu_scaling_signal(enum cpu_scaling_event);
+
+/* Set the base cpufreq policy mode */
+void set_cpu_scaling_mode(enum cpu_scaling_mode mode,
+                          const cpu_scaling_opts_t *opts);
+
+/* Get the base cpufreq policy mode */
+enum cpu_scaling_mode get_cpu_scaling_mode(cpu_scaling_opts_t *opts);
+
 RETRO_END_DECLS
 
 #endif
diff --git a/msg_hash.h b/msg_hash.h
index a0433b8cf5..64c9cbbd81 100644
--- a/msg_hash.h
+++ b/msg_hash.h
@@ -2941,11 +2941,30 @@ enum msg_hash_enums
    MENU_LABEL(MIDI_VOLUME),
 
    MENU_LABEL(SUSTAINED_PERFORMANCE_MODE),
+   MENU_LABEL(CPU_PERF_MODE),
    MENU_LABEL(CPU_PERFPOWER),
    MENU_LABEL(CPU_POLICY_ENTRY),
    MENU_LABEL(CPU_POLICY_MIN_FREQ),
    MENU_LABEL(CPU_POLICY_MAX_FREQ),
    MENU_LABEL(CPU_POLICY_GOVERNOR),
+   MENU_LABEL(CPU_POLICY_CORE_GOVERNOR),
+   MENU_LABEL(CPU_POLICY_MENU_GOVERNOR),
+   MENU_LABEL(CPU_MANAGED_MIN_FREQ),
+   MENU_LABEL(CPU_MANAGED_MAX_FREQ),
+
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF,
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANAGED_PER_CONTEXT,
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MAX_PERF,
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MIN_POWER,
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_BALANCED,
+   MENU_ENUM_LABEL_VALUE_CPU_PERF_MODE_MANUAL,
+
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANAGED_PERF,
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANAGED_PER_CONTEXT,
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MAX_PERF,
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MIN_POWER,
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_BALANCED,
+   MENU_ENUM_SUBLABEL_VALUE_CPU_PERF_MODE_MANUAL,
 
    MENU_ENUM_LABEL_CHEAT_HANDLER_TYPE_EMU,
    MENU_ENUM_LABEL_CHEAT_HANDLER_TYPE_RETRO,
diff --git a/retroarch.c b/retroarch.c
index b82cc1c540..ac12405922 100644
--- a/retroarch.c
+++ b/retroarch.c
@@ -228,6 +228,7 @@
 #include "gfx/video_crt_switch.h"
 #include "bluetooth/bluetooth_driver.h"
 #include "wifi/wifi_driver.h"
+#include "misc/cpufreq/cpufreq.h"
 #include "led/led_driver.h"
 #include "midi/midi_driver.h"
 #include "core.h"
@@ -13314,10 +13315,18 @@ static void retroarch_pause_checks(struct rarch_state *p_rarch)
       userdata.status = DISCORD_PRESENCE_GAME_PAUSED;
       command_event(CMD_EVENT_DISCORD_UPDATE, &userdata);
 #endif
+
+#ifdef HAVE_LAKKA
+      set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
+#endif
    }
    else
    {
       RARCH_LOG("[Core]: %s\n", msg_hash_to_str(MSG_UNPAUSED));
+
+#ifdef HAVE_LAKKA
+      set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
+#endif
    }
 
 #if defined(HAVE_TRANSLATE) && defined(HAVE_GFX_WIDGETS)
@@ -33206,6 +33215,10 @@ static void drivers_init(struct rarch_state *p_rarch,
    /* Initialize MIDI  driver */
    if (flags & DRIVER_MIDI_MASK)
       midi_driver_init(p_rarch, settings);
+
+#ifdef HAVE_LAKKA
+   cpu_scaling_driver_init();
+#endif
 }
 
 /**
@@ -33298,6 +33311,10 @@ static void driver_uninit(struct rarch_state *p_rarch, int flags)
 
    if (flags & DRIVER_MIDI_MASK)
       midi_driver_free(p_rarch);
+
+#ifdef HAVE_LAKKA
+   cpu_scaling_driver_free();
+#endif
 }
 
 static void retroarch_deinit_drivers(
@@ -35632,6 +35649,13 @@ static void menu_driver_toggle(
 
    p_rarch->menu_driver_alive                 = on;
 
+#ifdef HAVE_LAKKA
+   if (on)
+      set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_MENU);
+   else
+      set_cpu_scaling_signal(CPUSCALING_EVENT_FOCUS_CORE);
+#endif
+
    /* Apply any required menu pointer input inhibits
     * (i.e. prevent phantom input when using an overlay
     * to toggle the menu on) */