exynos-linux-stable/drivers/misc/gaming_control.c

629 lines
17 KiB
C
Raw Normal View History

/*
* Simple kernel gaming control
*
* Copyright (C) 2019
* Diep Quynh Nguyen <remilia.1505@gmail.com>
* Mustafa Gökmen <mustafa.gokmen2004@gmail.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/binfmts.h>
#include <linux/module.h>
#include <linux/kobject.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/pm_qos.h>
#include <linux/workqueue.h>
#include <linux/regulator/consumer.h>
#include <linux/gaming_control.h>
#include <soc/samsung/cal-if.h>
#include <soc/samsung/exynos-cpu_hotplug.h>
#include <dt-bindings/clock/exynos9810.h>
#include "../soc/samsung/cal-if/exynos9810/cmucal-vclk.h"
#include "../soc/samsung/cal-if/exynos9810/cmucal-node.h"
#define DEFAULT_CPU_LIMIT 1794000
#define TEMP_EMULATION 20000
#define STEP_UV (6250)
static struct delayed_work customs_delayed_work;
static struct regulator *little_regulator, *big_regulator, *g3d_regulator;
extern unsigned long arg_cpu_max_c1;
extern unsigned long arg_cpu_min_c1;
extern unsigned long arg_cpu_max_c2;
extern unsigned long arg_cpu_min_c2;
extern unsigned long arg_gpu_min;
extern unsigned long arg_gpu_max;
extern int exynos_cpufreq_update_volt_table(void);
extern void exynos_cpufreq_set_gaming_mode(void);
/* PM QoS implementation */
static struct pm_qos_request gaming_control_min_int_qos;
static struct pm_qos_request gaming_control_min_mif_qos;
static struct pm_qos_request gaming_control_min_big_qos;
static struct pm_qos_request gaming_control_max_big_qos;
static struct pm_qos_request gaming_control_min_little_qos;
static struct pm_qos_request gaming_control_max_little_qos;
static unsigned int min_int_freq = 534000;
static unsigned int min_mif_freq = 1794000;
static unsigned int min_little_freq = 1456000;
static unsigned int max_little_freq = 2002000;
static unsigned int min_big_freq = 1469000;
static unsigned int max_big_freq = 2886000;
static unsigned int min_gpu_freq = 598000;
static unsigned int max_gpu_freq = 598000;
static unsigned int custom_little_freq, custom_little_voltage, custom_big_freq, custom_big_voltage, custom_gpu_freq, custom_gpu_voltage = 0;
static unsigned int back_little_freq, back_little_voltage, back_big_freq, back_big_voltage, back_gpu_freq, back_gpu_voltage = 0;
static int nr_running_games = 0;
static bool always_on = 0;
static bool battery_idle = 0;
static int thermal_bypass = 0;
static bool gaming_mode_initialized = 0;
bool gaming_mode;
char games_list[GAME_LIST_LENGTH] = {0};
pid_t games_pid[NUM_SUPPORTED_RUNNING_GAMES] = {
[0 ... (NUM_SUPPORTED_RUNNING_GAMES - 1)] = -1
};
static inline void set_custom_gaming_mode(void)
{
unsigned int little_freq = max_little_freq;
unsigned int big_freq = max_big_freq;
unsigned int gpu_freq = max_gpu_freq;
gaming_mode_initialized = gaming_mode;
if (custom_little_freq) {
if (custom_little_freq <= arg_cpu_min_c1)
little_freq = arg_cpu_min_c1;
else if (custom_little_freq >= arg_cpu_max_c1)
little_freq = arg_cpu_max_c1;
__cal_dfs_set_rate(ACPM_DVFS_CPUCL0, gaming_mode ? custom_little_freq : little_freq);
__cal_dfs_set_rate(VCLK_CLUSTER0, (gaming_mode ? custom_little_freq : little_freq) * 1000);
__cal_dfs_set_rate(PLL_CPUCL0, (gaming_mode ? custom_little_freq : little_freq) * 1000);
}
if (custom_big_freq) {
if (custom_big_freq <= arg_cpu_min_c2) {
big_freq = arg_cpu_min_c2;
} else if (custom_big_freq >= arg_cpu_max_c2) {
if (gaming_mode || DEFAULT_CPU_LIMIT > arg_cpu_max_c2)
big_freq = arg_cpu_max_c2;
else
big_freq = DEFAULT_CPU_LIMIT;
}
__cal_dfs_set_rate(ACPM_DVFS_CPUCL1, gaming_mode ? custom_big_freq : big_freq);
__cal_dfs_set_rate(VCLK_CLUSTER1, (gaming_mode ? custom_big_freq : big_freq) * 1000);
__cal_dfs_set_rate(PLL_CPUCL1, (gaming_mode ? custom_big_freq : big_freq) * 1000);
}
if (custom_gpu_freq) {
if (custom_gpu_freq <= arg_gpu_min)
gpu_freq = arg_gpu_min;
else if (custom_gpu_freq >= arg_gpu_max)
gpu_freq = arg_gpu_max;
__cal_dfs_set_rate(ACPM_DVFS_G3D, gaming_mode ? custom_gpu_freq : gpu_freq);
__cal_dfs_set_rate(VCLK_GPU, (gaming_mode ? custom_gpu_freq : gpu_freq) * 1000);
__cal_dfs_set_rate(PLL_G3D, (gaming_mode ? custom_gpu_freq : gpu_freq) * 1000);
}
}
static void set_custom_gaming_mode_handler(struct work_struct *work)
{
if (gaming_mode && little_regulator && big_regulator && g3d_regulator) {
if ((!custom_little_voltage || abs(custom_little_voltage - regulator_get_voltage(little_regulator)) < STEP_UV)
&& (!custom_big_voltage || abs(custom_big_voltage - regulator_get_voltage(big_regulator)) < STEP_UV)
&& (!custom_gpu_voltage || abs(custom_gpu_voltage - regulator_get_voltage(g3d_regulator)) < STEP_UV))
set_custom_gaming_mode();
else
queue_delayed_work(system_power_efficient_wq, &customs_delayed_work, msecs_to_jiffies(1000));
} else if (gaming_mode_initialized || little_regulator == NULL || big_regulator == NULL || g3d_regulator == NULL) {
set_custom_gaming_mode();
}
}
static inline void set_gaming_mode(bool mode, bool force)
{
unsigned int little_max, little_min, big_max, big_min, gpu_max, gpu_min;
if (always_on)
mode = 1;
if (mode == gaming_mode && !force)
return;
else
gaming_mode = mode;
if (!mode) {
cancel_delayed_work_sync(&customs_delayed_work);
set_custom_gaming_mode();
}
little_max = max_little_freq;
little_min = min_little_freq;
big_max = max_big_freq;
big_min = min_big_freq;
gpu_max = max_gpu_freq;
gpu_min = min_gpu_freq;
if (custom_little_freq) {
if (custom_little_freq <= arg_cpu_min_c1)
little_max = little_min = arg_cpu_min_c1;
else if (custom_little_freq >= arg_cpu_max_c1)
little_max = little_min = arg_cpu_max_c1;
}
if (!back_little_freq)
back_little_freq = little_max;
if (custom_little_voltage && mode && !back_little_voltage) {
back_little_voltage = fvmap_read(DVFS_CPUCL0, READ_VOLT, back_little_freq);
fvmap_patch(DVFS_CPUCL0, back_little_freq, custom_little_voltage);
} else if (!mode && back_little_voltage) {
fvmap_patch(DVFS_CPUCL0, back_little_freq, back_little_voltage);
back_little_freq = back_little_voltage = 0;
}
if (custom_big_freq) {
if (custom_big_freq <= arg_cpu_min_c2) {
big_max = big_min = arg_cpu_min_c2;
} else if (custom_big_freq >= arg_cpu_max_c2) {
if (mode || DEFAULT_CPU_LIMIT > arg_cpu_max_c2)
big_max = big_min = arg_cpu_max_c2;
else
big_max = big_min = DEFAULT_CPU_LIMIT;
}
}
if (!back_big_freq)
back_big_freq = big_max;
if (custom_big_voltage && mode && !back_big_voltage) {
back_big_voltage = fvmap_read(DVFS_CPUCL1, READ_VOLT, back_big_freq);
fvmap_patch(DVFS_CPUCL1, back_big_freq, custom_big_voltage);
} else if (!mode && back_big_voltage) {
fvmap_patch(DVFS_CPUCL1, back_big_freq, back_big_voltage);
back_big_freq = back_big_voltage = 0;
}
if (custom_gpu_freq) {
if (custom_gpu_freq <= arg_gpu_min)
gpu_max = gpu_min = arg_gpu_min;
else if (custom_gpu_freq >= arg_gpu_max)
gpu_max = gpu_min = arg_gpu_max;
}
if (!back_gpu_freq)
back_gpu_freq = gpu_max;
if (custom_gpu_voltage && mode && !back_gpu_voltage) {
back_gpu_voltage = gpex_clock_get_voltage(back_gpu_freq);
fvmap_patch(DVFS_G3D, back_gpu_freq, custom_gpu_voltage);
} else if (!mode && back_gpu_voltage) {
fvmap_patch(DVFS_G3D, back_gpu_freq, back_gpu_voltage);
back_gpu_freq = back_gpu_voltage = 0;
}
gpex_clock_update_config_data_from_dt();
exynos_cpufreq_update_volt_table();
exynos_cpufreq_set_gaming_mode();
pm_qos_update_request(&gaming_control_min_int_qos, mode && min_int_freq ? min_int_freq : PM_QOS_DEVICE_THROUGHPUT_DEFAULT_VALUE);
pm_qos_update_request(&gaming_control_min_mif_qos, mode && min_mif_freq ? min_mif_freq : PM_QOS_BUS_THROUGHPUT_DEFAULT_VALUE);
pm_qos_update_request(&gaming_control_min_little_qos, mode && little_min ? little_min : PM_QOS_CLUSTER0_FREQ_MIN_DEFAULT_VALUE);
pm_qos_update_request(&gaming_control_max_little_qos, mode && little_max ? little_max : PM_QOS_CLUSTER0_FREQ_MAX_DEFAULT_VALUE);
pm_qos_update_request(&gaming_control_min_big_qos, mode && big_min ? big_min : PM_QOS_CLUSTER1_FREQ_MIN_DEFAULT_VALUE);
pm_qos_update_request(&gaming_control_max_big_qos, mode && big_max ? big_max : PM_QOS_CLUSTER1_FREQ_MAX_DEFAULT_VALUE);
gpu_custom_max_clock(mode ? gpu_max : 0);
gpu_custom_min_clock(mode ? gpu_min : 0);
if (mode)
set_custom_gaming_mode_handler(NULL);
}
unsigned long cal_dfs_check_gaming_mode(unsigned int id) {
unsigned long ret = 0;
if (!gaming_mode_initialized)
return ret;
switch (id) {
/* LITTLE */
case ACPM_DVFS_CPUCL0:
ret = custom_little_freq;
break;
/* BIG */
case ACPM_DVFS_CPUCL1:
ret = custom_big_freq;
break;
/* GPU */
case ACPM_DVFS_G3D:
ret = custom_gpu_freq;
break;
default:
break;
}
return ret;
}
int fake_freq_gaming(int id) {
int ret = 0;
if (!gaming_mode)
return ret;
switch (id) {
/* LITTLE */
case ACPM_DVFS_CPUCL0:
case DVFS_CPUCL0:
ret = back_little_freq;
break;
/* BIG */
case ACPM_DVFS_CPUCL1:
case DVFS_CPUCL1:
ret = back_big_freq;
break;
/* GPU */
case ACPM_DVFS_G3D:
case DVFS_G3D:
ret = back_gpu_freq;
break;
default:
break;
}
return ret;
}
bool battery_idle_gaming(void) {
if (gaming_mode && battery_idle)
return 1;
return 0;
}
int thermal_bypass_gaming(void) {
if (gaming_mode && thermal_bypass) {
if (thermal_bypass == 1)
return TEMP_EMULATION;
else
return thermal_bypass;
}
return 0;
}
static inline void store_game_pid(pid_t pid)
{
int i;
for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) {
if (games_pid[i] == -1) {
games_pid[i] = pid;
nr_running_games++;
break;
}
}
}
static inline int check_game_pid(pid_t pid)
{
int i;
for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) {
if (games_pid[i] != -1) {
if (games_pid[i] == pid)
return 1;
}
}
return 0;
}
static inline void clear_dead_pids(void)
{
int i;
for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) {
if (games_pid[i] != -1) {
rcu_read_lock();
if (!find_task_by_vpid(games_pid[i])) {
games_pid[i] = -1;
nr_running_games--;
}
rcu_read_unlock();
}
}
/* If there's no running games, turn off game mode */
if (nr_running_games == 0)
set_gaming_mode(false, false);
}
static inline void removeSubstringAfterChar(char *str, char ch) {
char *ptr = str;
while (*ptr != '\0') {
if (*ptr == ch) {
*(ptr) = '\0';
return;
}
ptr++;
}
}
static int check_for_games(struct task_struct *tsk)
{
char *cmdline;
int ret;
cmdline = kmalloc(GAME_LIST_LENGTH, GFP_KERNEL);
if (!cmdline)
return 0;
ret = get_cmdline(tsk, cmdline, GAME_LIST_LENGTH);
if (ret == 0) {
kfree(cmdline);
return 0;
}
/* clean extra process identifier */
removeSubstringAfterChar(cmdline, ':');
/* Invalid Android process name. Bail out */
if (!strcmp(cmdline, "android") || strlen(cmdline) < 7) {
kfree(cmdline);
return 0;
}
/* tsk isn't a game. Bail out */
if (strstr(games_list, cmdline) == NULL) {
kfree(cmdline);
return 0;
}
kfree(cmdline);
return 1;
}
void game_option(struct task_struct *tsk, enum game_opts opts)
{
pid_t pid;
int ret;
/* Remove all zombie tasks PIDs */
clear_dead_pids();
if(always_on) {
set_gaming_mode(true, false);
return;
}
ret = check_for_games(tsk);
if (!ret)
return;
switch (opts) {
case GAME_START:
case GAME_RUNNING:
set_gaming_mode(true, false);
pid = task_pid_vnr(tsk);
if (tsk->app_state == TASK_STARTED || check_game_pid(pid))
break;
store_game_pid(pid);
tsk->app_state = TASK_STARTED;
break;
case GAME_PAUSE:
set_gaming_mode(false, false);
break;
default:
break;
}
}
/* Show added games list */
static ssize_t game_packages_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", games_list);
}
/* Store added games list */
static ssize_t game_packages_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
char games[GAME_LIST_LENGTH];
sscanf(buf, "%s", games);
sprintf(games_list, "%s", buf);
return count;
}
/* Show/Store value */
#define attr_value(type) \
static ssize_t type##_store(struct kobject *kobj, \
struct kobj_attribute *attr, const char *buf, size_t count) \
{ \
unsigned int value; \
\
sscanf(buf, "%u\n", &value); \
type = value; \
\
set_gaming_mode(gaming_mode, gaming_mode); \
\
return count; \
} \
\
static ssize_t type##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%u\n", type); \
} \
\
static struct kobj_attribute type##_attribute = \
__ATTR(type, 0644, type##_show, type##_store);
attr_value(always_on);
attr_value(battery_idle);
attr_value(thermal_bypass);
attr_value(min_int_freq);
attr_value(min_mif_freq);
attr_value(min_little_freq);
attr_value(max_little_freq);
attr_value(min_big_freq);
attr_value(max_big_freq);
attr_value(min_gpu_freq);
attr_value(max_gpu_freq);
attr_value(custom_little_freq);
attr_value(custom_little_voltage);
attr_value(custom_big_freq);
attr_value(custom_big_voltage);
attr_value(custom_gpu_freq);
attr_value(custom_gpu_voltage);
static ssize_t status_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
ssize_t len = 0;
int i;
len += sprintf(buf + len, "%d\n\n", nr_running_games);
for (i = 0; i < NUM_SUPPORTED_RUNNING_GAMES; i++) {
if (games_pid[i] != -1)
len += sprintf(buf + len, "%d\n", games_pid[i]);
}
buf[len] = '\0';
return len;
}
static ssize_t version_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%s\n", GAMING_CONTROL_VERSION);
}
static struct kobj_attribute status_attribute =
__ATTR(status, 0444, status_show, NULL);
static struct kobj_attribute version_attribute =
__ATTR(version, 0444, version_show, NULL);
static struct kobj_attribute game_packages_attribute =
__ATTR(game_packages, 0644, game_packages_show, game_packages_store);
static struct attribute *gaming_control_attributes[] = {
&game_packages_attribute.attr,
&status_attribute.attr,
&version_attribute.attr,
&always_on_attribute.attr,
&battery_idle_attribute.attr,
&thermal_bypass_attribute.attr,
&min_int_freq_attribute.attr,
&min_mif_freq_attribute.attr,
&min_little_freq_attribute.attr,
&max_little_freq_attribute.attr,
&min_big_freq_attribute.attr,
&max_big_freq_attribute.attr,
&min_gpu_freq_attribute.attr,
&max_gpu_freq_attribute.attr,
&custom_little_freq_attribute.attr,
&custom_little_voltage_attribute.attr,
&custom_big_freq_attribute.attr,
&custom_big_voltage_attribute.attr,
&custom_gpu_freq_attribute.attr,
&custom_gpu_voltage_attribute.attr,
NULL
};
static struct attribute_group gaming_control_control_group = {
.attrs = gaming_control_attributes,
};
static struct kobject *gaming_control_kobj;
static int __init gaming_control_init(void)
{
int sysfs_result;
little_regulator = regulator_get(NULL, "vdd_cpucl0");
if (IS_ERR(little_regulator))
little_regulator = NULL;
big_regulator = regulator_get(NULL, "vdd_cpucl1");
if (IS_ERR(big_regulator))
big_regulator = NULL;
g3d_regulator = regulator_get(NULL, "vdd_g3d");
if (IS_ERR(g3d_regulator))
g3d_regulator = NULL;
INIT_DELAYED_WORK(&customs_delayed_work, set_custom_gaming_mode_handler);
pm_qos_add_request(&gaming_control_min_int_qos, PM_QOS_DEVICE_THROUGHPUT, PM_QOS_DEVICE_THROUGHPUT_DEFAULT_VALUE);
pm_qos_add_request(&gaming_control_min_mif_qos, PM_QOS_BUS_THROUGHPUT, PM_QOS_BUS_THROUGHPUT_DEFAULT_VALUE);
pm_qos_add_request(&gaming_control_min_little_qos, PM_QOS_CLUSTER0_FREQ_MIN, PM_QOS_CLUSTER0_FREQ_MIN_DEFAULT_VALUE);
pm_qos_add_request(&gaming_control_max_little_qos, PM_QOS_CLUSTER0_FREQ_MAX, PM_QOS_CLUSTER0_FREQ_MAX_DEFAULT_VALUE);
pm_qos_add_request(&gaming_control_min_big_qos, PM_QOS_CLUSTER1_FREQ_MIN, PM_QOS_CLUSTER1_FREQ_MIN_DEFAULT_VALUE);
pm_qos_add_request(&gaming_control_max_big_qos, PM_QOS_CLUSTER1_FREQ_MAX, PM_QOS_CLUSTER1_FREQ_MAX_DEFAULT_VALUE);
gaming_control_kobj = kobject_create_and_add("gaming_control", kernel_kobj);
if (!gaming_control_kobj) {
pr_err("%s gaming_control kobject create failed!\n", __FUNCTION__);
return -ENOMEM;
}
sysfs_result = sysfs_create_group(gaming_control_kobj,
&gaming_control_control_group);
if (sysfs_result) {
pr_info("%s gaming_control sysfs create failed!\n", __FUNCTION__);
kobject_put(gaming_control_kobj);
}
return sysfs_result;
}
static void __exit gaming_control_exit(void)
{
cancel_delayed_work_sync(&customs_delayed_work);
pm_qos_remove_request(&gaming_control_min_int_qos);
pm_qos_remove_request(&gaming_control_min_mif_qos);
pm_qos_remove_request(&gaming_control_min_little_qos);
pm_qos_remove_request(&gaming_control_max_little_qos);
pm_qos_remove_request(&gaming_control_min_big_qos);
pm_qos_remove_request(&gaming_control_max_big_qos);
if (gaming_control_kobj != NULL)
kobject_put(gaming_control_kobj);
}
module_init(gaming_control_init);
module_exit(gaming_control_exit);