kernel_samsung_a53x/drivers/soc/samsung/xperf/gmc.c

806 lines
20 KiB
C
Raw Normal View History

2024-06-15 16:02:09 -03:00
/*
* General Momentum Chaser (GMC)
* Jungwook Kim <jwook1.kim@samsung.com>
*/
#include <linux/device.h>
#include <linux/kobject.h>
#include <linux/cpufreq.h>
#include <linux/thermal.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/sysfs.h>
#include <linux/cpumask.h>
#include <linux/ems.h>
#include "../../../kernel/sched/sched.h"
#include <soc/samsung/cal-if.h>
#include <soc/samsung/gpu_cooling.h>
#include <soc/samsung/exynos_pm_qos.h>
#include <soc/samsung/freq-qos-tracer.h>
#include <soc/samsung/xperf.h>
#include "../../../thermal/samsung/exynos_tmu.h"
#include "xperf.h"
static const char *prefix = "gmc";
static int is_running;
struct ctrl {
int boost_cnt;
int boost_step;
int cpu_maxlock[MAX_CLUSTER];
int gpu_maxlock;
};
struct cond {
int gpu_fps_thd;
int gpu_freq_thd;
int gpu_temp_thd;
int cpu_temp_thd[MAX_CLUSTER];
};
struct type {
bool enable;
bool status;
/* control knob */
struct ctrl knob;
/* trigger & release condition */
struct cond trigger;
struct cond release;
int fps_drop_thd;
int throttle;
/* timer */
u64 cur_timer;
u64 set_timer;
u64 reset_timer;
/* util fingerprint */
int cpu_util_lthd[MAX_CLUSTER];
int cpu_util_hthd[MAX_CLUSTER];
};
static struct {
u32 run;
u32 debug;
u32 bind_cpu;
u32 polling_ms;
int type_cnt;
struct type *types;
} gmc;
#define for_each_type(type) \
for ((type) = 0; (type) < gmc.type_cnt; (type)++)
/******************************************************************************
* monitor functions *
******************************************************************************/
#define FPS_WINDOW 5
static int fps_idx;
static int fps_arr[FPS_WINDOW];
static int fps;
static u64 start_frame_cnt;
static u64 end_frame_cnt;
static void (*get_frame_cnt)(u64 *cnt, ktime_t *time);
void exynos_gmc_register_frame_cnt(void (*fn)(u64 *cnt, ktime_t *time))
{
get_frame_cnt = fn;
}
EXPORT_SYMBOL_GPL(exynos_gmc_register_frame_cnt);
static void monitor_fps(void)
{
ktime_t time;
u64 frame_cnt;
if (!get_frame_cnt)
return;
/* update fps window */
fps_arr[fps_idx] = fps;
fps_idx = (fps_idx + 1) % FPS_WINDOW;
start_frame_cnt = end_frame_cnt;
get_frame_cnt(&end_frame_cnt, &time);
/* update fps */
frame_cnt = end_frame_cnt - start_frame_cnt;
fps = frame_cnt * 1000 / gmc.polling_ms;
if (gmc.debug)
pr_info("[%s] mon gpu freq %d fps %d\n", prefix, gpu_dvfs_get_cur_clock(), fps);
}
/******************************************************************************
* helper functions *
******************************************************************************/
#define NORMAL_MODE 0
#define HIGH_MODE 6
#define DEFAULT_LEVEL 0
static const char *gpu_tz_names;
static const char *cpu_tz_names[MAX_CLUSTER];
static struct freq_qos_request fqos[NUM_OF_GMC_TYPE][MAX_CLUSTER];
static struct exynos_pm_qos_request gqos[NUM_OF_GMC_TYPE];
static bool gpu_hotter_than(int thd, int type)
{
int temp;
thermal_zone_get_temp(thermal_zone_get_zone_by_name(gpu_tz_names), &temp);
if (gmc.debug)
pr_info("[%s] type%d gpu temp %d temp-thd %d\n", prefix, type, temp, thd);
return (temp > thd);
}
static bool gpu_clock_higher_than(int thd, int type)
{
int freq = gpu_dvfs_get_cur_clock();
if (gmc.debug)
pr_info("[%s] type%d gpu freq %d freq-thd %d\n", prefix, type, freq, thd);
return (freq > thd);
}
static bool gpu_fps_higher_than(int thd, int type)
{
if (gmc.debug)
pr_info("[%s] type%d gpu fps %d fps-thd %d\n", prefix, type, fps, thd);
return (fps > thd);
}
static bool gpu_fps_sudden_drop(int type)
{
int i, sum = 0, avg = 0;
struct type *tp = &gmc.types[type];
for (i = 0; i < FPS_WINDOW; i++)
sum += fps_arr[i];
avg = sum / FPS_WINDOW;
if (gmc.debug)
pr_info("[%s] type%d gpu drop %d drop-thd %d\n",
prefix, type, avg - fps, tp->fps_drop_thd);
return (avg - fps) > tp->fps_drop_thd;
}
static bool all_cpu_hotter_than(int *thd, int type)
{
int cluster;
int temp[MAX_CLUSTER];
struct thermal_zone_device *tz;
for_each_cluster(cluster) {
tz = thermal_zone_get_zone_by_name(cpu_tz_names[cluster]);
thermal_zone_get_temp(tz, &temp[cluster]);
if (gmc.debug)
pr_info("[%s] type%d cluster%d temp %d temp-thd %d\n",
prefix, type, cluster, temp[cluster], thd[cluster]);
if (temp[cluster] <= thd[cluster])
return false;
}
return true;
}
static bool is_supported_mode(void)
{
return ((emstune_get_cur_mode() == EMSTUNE_NORMAL_MODE) ||
(emstune_get_cur_mode() == EMSTUNE_HIGH_MODE)) &&
(emstune_get_cur_level() == EMSTUNE_DEFAULT_LEVEL);
}
static bool match_util_finger_print(int type)
{
int cpu, cluster;
struct type *tp = &gmc.types[type];
for_each_cluster(cluster) {
int sum = 0, avg = 0;
int cpu_num = cpumask_weight(cls[cluster].siblings);
if (!cpu_num)
return false;
for_each_cpu(cpu, cls[cluster].siblings)
sum = cpu_util_avgs[cpu];
avg = sum / cpu_num;
if (gmc.debug)
pr_info("[%s] type%d cluster%d sum %d avg %d cpu-util-thd %d~%d\n",
prefix, type, cluster, sum, avg,
tp->cpu_util_lthd[cluster], tp->cpu_util_hthd[cluster]);
if (avg < tp->cpu_util_lthd[cluster] || avg > tp->cpu_util_hthd[cluster])
return false;
}
return true;
}
/******************************************************************************
* update type status *
******************************************************************************/
static bool set_timer_expired(int type)
{
struct type *tp = &gmc.types[type];
if (gmc.debug > 1)
pr_info("[%s] type%d cur-timer %llu set-timer %llu\n",
prefix, type, tp->cur_timer, tp->set_timer);
return tp->cur_timer >= tp->set_timer;
}
static bool reset_timer_expired(int type)
{
struct type *tp = &gmc.types[type];
if (gmc.debug > 1)
pr_info("[%s] type%d cur-timer %llu reset-timer %llu\n",
prefix, type, tp->cur_timer, tp->reset_timer);
return tp->cur_timer >= tp->reset_timer;
}
/*
* Returns true if all conditions are met
*/
static bool check_cond(struct cond *cd, int type)
{
if (!gpu_fps_higher_than(cd->gpu_fps_thd, type))
return false;
if (!gpu_clock_higher_than(cd->gpu_freq_thd, type))
return false;
if (!gpu_hotter_than(cd->gpu_temp_thd, type))
return false;
if (!all_cpu_hotter_than(cd->cpu_temp_thd, type))
return false;
return true;
}
/*
* Returns the status, '1' (trigger) or '0' (release).
* If the value is the same as before, returns '-1'.
*/
static int update_type_status(int type)
{
bool next;
struct type *tp = &gmc.types[type];
struct cond *cnd = tp->status ? &tp->release : &tp->trigger;
next = check_cond(cnd, type);
/* If the fingerprints do not match,
* release next status.
*/
if (next && !match_util_finger_print(type))
next = false;
/* When the fps drops suddenly,
* release next status.
*/
if (next && gpu_fps_sudden_drop(type))
next = false;
/* If the next status is 1, increment the current timer.
* Else decrement the current timer.
*/
if (next) {
tp->cur_timer++;
if (!tp->status)
next = set_timer_expired(type);
else
next = !reset_timer_expired(type);
} else
tp->cur_timer = 0;
if (tp->status == next)
return -1;
return next;
}
static void reset_control_knob(struct ctrl *kn, int type)
{
int cluster;
if (kn->gpu_maxlock >= 0)
exynos_pm_qos_update_request(&gqos[type], FREQ_QOS_MAX_DEFAULT_VALUE);
for_each_cluster(cluster)
if (kn->cpu_maxlock[cluster] >= 0)
freq_qos_update_request(&fqos[type][cluster], FREQ_QOS_MAX_DEFAULT_VALUE);
}
static void set_control_knob(struct ctrl *kn, int type)
{
int cluster;
if (kn->gpu_maxlock >= 0)
exynos_pm_qos_update_request(&gqos[type], kn->gpu_maxlock);
for_each_cluster(cluster)
if (kn->cpu_maxlock[cluster] >= 0)
freq_qos_update_request(&fqos[type][cluster], kn->cpu_maxlock[cluster]);
}
/*
* Set the type of control knob.
*/
static void control_type_knob(int type)
{
int cluster;
struct type *tp = &gmc.types[type];
struct ctrl *kn = &tp->knob;
struct thermal_zone_device *tz;
struct exynos_tmu_data *td;
if (tp->status) {
set_control_knob(kn, type);
if (tp->throttle) {
/* Set TMU throttle mode */
for_each_cluster(cluster) {
tz = thermal_zone_get_zone_by_name(cpu_tz_names[cluster]);
td = exynos_tmu_get_data_from_tz(tz);
exynos_tmu_set_boost_mode(td, THROTTLE_MODE, false);
}
tz = thermal_zone_get_zone_by_name(gpu_tz_names);
td = exynos_tmu_get_data_from_tz(tz);
exynos_tmu_set_boost_mode(td, THROTTLE_MODE, false);
if (gmc.debug)
pr_info("[%s] type%d tmu throttle\n", prefix, type);
}
} else
reset_control_knob(kn, type);
}
/******************************************************************************
* gmc main thread *
******************************************************************************/
static int gmc_thread(void *data)
{
int next, type;
if (is_running)
return 0;
is_running = 1;
while (is_running) {
monitor_fps();
for_each_type(type) {
struct type *tp = &gmc.types[type];
if (!tp->enable)
continue;
/* When the current mode is not supported,
* set type status to false and reset control knob.
*/
if (!is_supported_mode()) {
if (tp->status) {
tp->status = false;
reset_control_knob(&tp->knob, type);
}
continue;
}
next = update_type_status(type);
/* If there is no update, do nothing. */
if (next < 0)
continue;
tp->status = next;
control_type_knob(type);
if (gmc.debug)
pr_info("[%s] type%d %s\n",
prefix, type, tp->status ? "trigger" : "release");
}
msleep(gmc.polling_ms);
}
return 0;
}
static void gmc_stop(void)
{
int cluster, type;
for_each_type(type)
exynos_pm_qos_remove_request(&gqos[type]);
for_each_cluster(cluster)
for_each_type(type)
freq_qos_tracer_remove_request(&fqos[type][cluster]);
is_running = 0;
}
static struct task_struct *task;
static void gmc_start(void)
{
int cpu, cluster, type;
struct cpufreq_policy *policy;
if (is_running)
return;
for_each_type(type)
exynos_pm_qos_add_request(&gqos[type],
PM_QOS_GPU_THROUGHPUT_MAX,
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
for_each_cluster(cluster) {
cpu = cls[cluster].first_cpu;
policy = cpufreq_cpu_get(cpu);
if (!policy) {
gmc_stop();
return;
}
for_each_type(type)
freq_qos_tracer_add_request(&policy->constraints,
&fqos[type][cluster],
FREQ_QOS_MAX,
FREQ_QOS_MAX_DEFAULT_VALUE);
cpufreq_cpu_put(policy);
}
/* start gmc main thread */
task = kthread_create(gmc_thread, NULL, "gmc%u", 0);
kthread_bind(task, gmc.bind_cpu);
wake_up_process(task);
return;
}
/******************************************************************************
* sysfs interface *
******************************************************************************/
#define DEF_NODE_CTRL_RW(name) \
static ssize_t name##_show(struct kobject *k, struct kobj_attribute *attr, char *buf) \
{ \
int type; \
int ret = 0; \
for_each_type(type) \
ret += sprintf(buf + ret, "[type%d] %d\n", type, gmc.types[type].knob.name); \
ret += sprintf(buf + ret, "\n# echo <type> <value> > node\n"); \
return ret; } \
static ssize_t name##_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count) \
{ \
int type = -1; \
int val; \
if (sscanf(buf, "%d %d", &type, &val) != 2) \
return -EINVAL; \
if (type < 0 || type >= gmc.type_cnt) \
return -EINVAL; \
gmc.types[type].knob.name = val; \
return count; } \
static struct kobj_attribute name##_attr = __ATTR_RW(name)
DEF_NODE_CTRL_RW(boost_cnt);
DEF_NODE_CTRL_RW(boost_step);
DEF_NODE_CTRL_RW(gpu_maxlock);
static ssize_t cpu_maxlock_show(struct kobject *k, struct kobj_attribute *attr, char *buf)
{
int type, cluster, ret = 0;
for_each_type(type) {
ret += sprintf(buf + ret, "[type%d]\n", type);
for_each_cluster(cluster)
ret += sprintf(buf + ret, "cluster%d=%d\n",
cluster,
gmc.types[type].knob.cpu_maxlock[cluster]);
}
ret += sprintf(buf + ret, "\n# echo <cluster> <freq> > cpu_maxlock\n");
return ret;
}
static ssize_t cpu_maxlock_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count)
{
int type, cluster, val;
if (sscanf(buf, "%d %d %d", &type, &cluster, &val) != 3)
return -EINVAL;
if (type < 0 || type >= gmc.type_cnt)
return -EINVAL;
if (cluster < 0 || cluster >= MAX_CLUSTER)
return -EINVAL;
gmc.types[type].knob.cpu_maxlock[cluster] = val;
return count;
}
static struct kobj_attribute cpu_maxlock_attr = __ATTR_RW(cpu_maxlock);
#define DEF_NODE_GMC_RW(name) \
static ssize_t name##_show(struct kobject *k, struct kobj_attribute *attr, char *buf) \
{ \
int ret = 0; \
ret += sprintf(buf + ret, "%u\n", gmc.name); \
return ret; } \
static ssize_t name##_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count) \
{ \
if (kstrtouint(buf, 10, &gmc.name)) \
return -EINVAL; \
return count; } \
static struct kobj_attribute name##_attr = __ATTR_RW(name)
DEF_NODE_GMC_RW(debug);
DEF_NODE_GMC_RW(bind_cpu);
DEF_NODE_GMC_RW(polling_ms);
#define DEF_NODE_COND_RW(name) \
static ssize_t name##_show(struct kobject *k, struct kobj_attribute *attr, char *buf) \
{ \
int type; \
int ret = 0; \
for_each_type(type) { \
ret += sprintf(buf + ret, "[type%d] trigger=%-8d release=%-8d\n", \
type, \
gmc.types[type].trigger.name, \
gmc.types[type].release.name); \
} \
ret += sprintf(buf + ret, "\n# echo <type> <trigger> <release> > node\n"); \
return ret; } \
static ssize_t name##_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count) \
{ \
int type = -1; \
int val1, val2; \
if (sscanf(buf, "%d %d %d", &type, &val1, &val2) != 3) \
return -EINVAL; \
if (type < 0 || type >= gmc.type_cnt) \
return -EINVAL; \
gmc.types[type].trigger.name = val1; \
gmc.types[type].release.name = val2; \
return count; } \
static struct kobj_attribute name##_attr = __ATTR_RW(name)
DEF_NODE_COND_RW(gpu_fps_thd);
DEF_NODE_COND_RW(gpu_freq_thd);
DEF_NODE_COND_RW(gpu_temp_thd);
static ssize_t cpu_temp_thd_show(struct kobject *k, struct kobj_attribute *attr, char *buf)
{
int type, cluster, ret = 0;
for_each_type(type) {
ret += sprintf(buf + ret, "[type%d]\n", type);
for_each_cluster(cluster)
ret += sprintf(buf + ret, "cluster%d trigger=%-8d release=%-8d\n",
cluster,
gmc.types[type].trigger.cpu_temp_thd[cluster],
gmc.types[type].release.cpu_temp_thd[cluster]);
}
ret += sprintf(buf + ret, "\n# echo <cluster> <freq> > cpu_maxlock\n");
return ret;
}
static ssize_t cpu_temp_thd_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count)
{
int type, cluster;
int val1, val2;
if (sscanf(buf, "%d %d %d %d", &type, &cluster, &val1, &val2) != 4)
return -EINVAL;
if (type < 0 || type >= gmc.type_cnt)
return -EINVAL;
if (cluster < 0 || cluster >= MAX_CLUSTER)
return -EINVAL;
gmc.types[type].trigger.cpu_temp_thd[cluster] = val1;
gmc.types[type].release.cpu_temp_thd[cluster] = val2;
return count;
}
static struct kobj_attribute cpu_temp_thd_attr = __ATTR_RW(cpu_temp_thd);
static ssize_t run_show(struct kobject *k, struct kobj_attribute *attr, char *buf)
{
int ret = 0;
ret += sprintf(buf + ret, "%u\n", gmc.run);
return ret;
}
static ssize_t run_store(struct kobject *k, struct kobj_attribute *attr, const char *buf, size_t count)
{
if (kstrtouint(buf, 10, &gmc.run))
return -EINVAL;
if (gmc.run)
gmc_start();
else
gmc_stop();
return count;
}
static struct kobj_attribute run_attr = __ATTR_RW(run);
static struct attribute *gmc_attrs[] = {
&run_attr.attr,
&debug_attr.attr,
&bind_cpu_attr.attr,
&polling_ms_attr.attr,
&gpu_fps_thd_attr.attr,
&gpu_freq_thd_attr.attr,
&gpu_temp_thd_attr.attr,
&cpu_temp_thd_attr.attr,
&boost_step_attr.attr,
&boost_cnt_attr.attr,
&cpu_maxlock_attr.attr,
&gpu_maxlock_attr.attr,
NULL
};
static struct attribute_group gmc_group = {
.attrs = gmc_attrs,
};
/******************************************************************************
* initialization *
******************************************************************************/
static struct kobject *gmc_kobj;
int init_cond(struct device_node *dn, struct cond *con, int init)
{
int cluster, ret = 0;
if (!dn)
ret = -ENODATA;
if (ret || of_property_read_u32(dn, "gpu-fps", &con->gpu_fps_thd))
con->gpu_fps_thd = init;
if (ret || of_property_read_u32(dn, "gpu-freq", &con->gpu_freq_thd))
con->gpu_freq_thd = init;
if (ret || of_property_read_u32(dn, "gpu-temp", &con->gpu_temp_thd))
con->gpu_temp_thd = init;
if (ret || of_property_read_u32_array(dn, "cpu-temp", con->cpu_temp_thd, cl_cnt))
for_each_cluster(cluster)
con->cpu_temp_thd[cluster] = init;
return ret;
}
void init_type(struct device_node *dn, int type)
{
int cluster;
struct type *tp = &gmc.types[type];
tp->enable = true;
/* init control knob */
if (of_property_read_u32(dn, "boost-cnt", &tp->knob.boost_cnt))
tp->knob.boost_cnt = -ENODATA;
if (of_property_read_u32(dn, "boost-step", &tp->knob.boost_step))
tp->knob.boost_step = -ENODATA;
if (of_property_read_u32_array(dn, "cpu-maxlock", tp->knob.cpu_maxlock, cl_cnt))
for_each_cluster(cluster)
tp->knob.cpu_maxlock[cluster] = -ENODATA;
if (of_property_read_u32(dn, "gpu-maxlock", &tp->knob.gpu_maxlock))
tp->knob.gpu_maxlock = -ENODATA;
/* init util fingerprint */
if (of_property_read_u32_array(dn, "cpu-util-low", tp->cpu_util_lthd, cl_cnt))
for_each_cluster(cluster)
tp->cpu_util_lthd[cluster] = 0;
if (of_property_read_u32_array(dn, "cpu-util-high", tp->cpu_util_hthd, cl_cnt))
for_each_cluster(cluster)
tp->cpu_util_hthd[cluster] = 1024;
/* init condition */
if (of_property_read_u64(dn, "set-timer", &tp->set_timer))
tp->set_timer = 0;
if (of_property_read_u64(dn, "reset-timer", &tp->reset_timer))
tp->reset_timer = UINT_MAX;
if (of_property_read_u32(dn, "fps-drop", &tp->fps_drop_thd))
tp->fps_drop_thd = INT_MAX;
if (of_property_read_u32(dn, "throttle", &tp->throttle))
tp->throttle = 0;
if (init_cond(of_get_child_by_name(dn, "trigger"), &tp->trigger, INT_MAX))
tp->enable = false;
if (init_cond(of_get_child_by_name(dn, "release"), &tp->release, -ENODATA))
tp->enable = false;
}
int dt_init(struct device_node *dn)
{
int index;
struct device_node *gmc_node, *type_node;
gmc_node = of_get_child_by_name(dn, "gmc");
if (!gmc_node)
return -EINVAL;
/* init gmc settings */
if (of_property_read_u32(gmc_node, "bind-cpu", &gmc.bind_cpu))
return -ENODATA;
if (of_property_read_u32(gmc_node, "polling-ms", &gmc.polling_ms))
return -ENODATA;
/* init thermal zone */
if (of_property_read_string_array(gmc_node, "cpu-tz-names", cpu_tz_names, cl_cnt) < 0)
return -ENODATA;
if (of_property_read_string(gmc_node, "gpu-tz-names", &gpu_tz_names))
return -ENODATA;
gmc.type_cnt = of_get_child_count(gmc_node);
if (!gmc.type_cnt)
return -ENODATA;
gmc.types = kcalloc(gmc.type_cnt, sizeof(struct type), GFP_KERNEL);
if (!gmc.types)
return -ENOMEM;
index = 0;
for_each_child_of_node(gmc_node, type_node)
init_type(type_node, index++);
gmc.run = 1;
gmc.debug = 0;
return 0;
}
int xperf_gmc_init(struct platform_device *pdev, struct kobject *kobj)
{
int ret = 0;
ret = dt_init(pdev->dev.of_node);
if (ret) {
pr_info("[%s] failed to initialize driver with err=%d\n", prefix, ret);
return ret;
}
gmc_kobj = kobject_create_and_add("gmc", &pdev->dev.kobj);
if (!gmc_kobj) {
pr_info("[%s] create node failed: %s\n", prefix, __FILE__);
return -EINVAL;
}
ret = sysfs_create_group(gmc_kobj, &gmc_group);
if (ret) {
pr_info("[%s] create group failed: %s\n", prefix, __FILE__);
return -EINVAL;
}
gmc_start();
return 0;
}