1703 lines
50 KiB
C
1703 lines
50 KiB
C
|
#include <linux/init.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/pm_qos.h>
|
||
|
#include <linux/cpufreq.h>
|
||
|
#include <linux/fs.h>
|
||
|
#include <linux/miscdevice.h>
|
||
|
#include <linux/wait.h>
|
||
|
#include <linux/uaccess.h>
|
||
|
#include <linux/poll.h>
|
||
|
|
||
|
#include <soc/samsung/exynos-migov.h>
|
||
|
#include <soc/samsung/exynos-migov-shared.h>
|
||
|
#include <soc/samsung/exynos-dm.h>
|
||
|
#include <soc/samsung/exynos_pm_qos.h>
|
||
|
#include <soc/samsung/freq-qos-tracer.h>
|
||
|
#include <soc/samsung/exynos-sci.h>
|
||
|
#include <soc/samsung/bts.h>
|
||
|
#include <soc/samsung/exynos-gpu-profiler.h>
|
||
|
|
||
|
#include "../../../kernel/sched/sched.h"
|
||
|
#include "../../../kernel/sched/ems/ems.h"
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Structure for migov */
|
||
|
/******************************************************************************/
|
||
|
#define MAXNUM_OF_DVFS 30
|
||
|
#define NUM_OF_CPU_DOMAIN (MIGOV_CL2 + 1)
|
||
|
#define MAXNUM_OF_CPU 8
|
||
|
|
||
|
enum profile_mode {
|
||
|
PROFILE_FINISH,
|
||
|
PROFILE_START_BY_GAME,
|
||
|
PROFILE_START_BY_GPU,
|
||
|
PROFILE_UPDATE,
|
||
|
PROFILE_INVALID,
|
||
|
};
|
||
|
|
||
|
/* Structure for IP's Private Data */
|
||
|
struct private_data_cpu {
|
||
|
struct private_fn_cpu *fn;
|
||
|
|
||
|
s32 pm_qos_cpu;
|
||
|
struct freq_qos_request pm_qos_min_req;
|
||
|
struct freq_qos_request pm_qos_max_req;
|
||
|
s32 pm_qos_min_freq;
|
||
|
s32 pm_qos_max_freq;
|
||
|
s32 hp_minlock_low_limit;
|
||
|
s32 lp_minlock_low_limit;
|
||
|
|
||
|
s32 num_of_cpu;
|
||
|
};
|
||
|
|
||
|
struct private_data_gpu {
|
||
|
struct private_fn_gpu *fn;
|
||
|
|
||
|
struct exynos_pm_qos_request pm_qos_max_req;
|
||
|
struct exynos_pm_qos_request pm_qos_min_req;
|
||
|
s32 pm_qos_max_class;
|
||
|
s32 pm_qos_max_freq;
|
||
|
s32 pm_qos_min_class;
|
||
|
s32 pm_qos_min_freq;
|
||
|
|
||
|
/* Tunables */
|
||
|
u64 q0_empty_pct_thr;
|
||
|
u64 q1_empty_pct_thr;
|
||
|
u64 gpu_active_pct_thr;
|
||
|
};
|
||
|
|
||
|
struct private_data_mif {
|
||
|
struct private_fn_mif *fn;
|
||
|
|
||
|
struct exynos_pm_qos_request pm_qos_min_req;
|
||
|
struct exynos_pm_qos_request pm_qos_max_req;
|
||
|
s32 pm_qos_min_class;
|
||
|
s32 pm_qos_max_class;
|
||
|
s32 pm_qos_min_freq;
|
||
|
s32 pm_qos_max_freq;
|
||
|
s32 hp_minlock_low_limit;
|
||
|
|
||
|
/* Tunables */
|
||
|
s32 stats0_mode_min_freq;
|
||
|
u64 stats0_sum_thr;
|
||
|
u64 stats0_updown_delta_pct_thr;
|
||
|
};
|
||
|
|
||
|
struct domain_data {
|
||
|
bool enabled;
|
||
|
s32 id;
|
||
|
|
||
|
struct domain_fn *fn;
|
||
|
|
||
|
void *private;
|
||
|
|
||
|
struct attribute_group attr_group;
|
||
|
};
|
||
|
|
||
|
struct device_file_operation {
|
||
|
struct file_operations fops;
|
||
|
struct miscdevice miscdev;
|
||
|
};
|
||
|
|
||
|
static struct migov {
|
||
|
bool running;
|
||
|
bool forced_running;
|
||
|
bool disable;
|
||
|
bool game_mode;
|
||
|
bool heavy_gpu_mode;
|
||
|
u32 cur_mode;
|
||
|
|
||
|
int bts_idx;
|
||
|
int len_mo_id;
|
||
|
int *mo_id;
|
||
|
|
||
|
bool llc_enable;
|
||
|
|
||
|
struct domain_data domains[NUM_OF_DOMAIN];
|
||
|
|
||
|
ktime_t start_time;
|
||
|
ktime_t end_time;
|
||
|
|
||
|
u64 start_frame_cnt;
|
||
|
u64 end_frame_cnt;
|
||
|
u64 start_frame_vsync_cnt;
|
||
|
u64 end_frame_vsync_cnt;
|
||
|
u64 start_fence_cnt;
|
||
|
u64 end_fence_cnt;
|
||
|
void (*get_frame_cnt)(u64 *cnt, ktime_t *time);
|
||
|
void (*get_vsync_cnt)(u64 *cnt, ktime_t *time);
|
||
|
void (*get_fence_cnt)(u64 *cnt, ktime_t *time);
|
||
|
|
||
|
s32 max_fps;
|
||
|
|
||
|
struct work_struct fps_work;
|
||
|
|
||
|
bool was_gpu_heavy;
|
||
|
ktime_t heavy_gpu_start_time;
|
||
|
ktime_t heavy_gpu_end_time;
|
||
|
s32 heavy_gpu_ms_thr;
|
||
|
s32 gpu_freq_thr;
|
||
|
s32 fragutil;
|
||
|
s32 fragutil_upper_thr;
|
||
|
s32 fragutil_lower_thr;
|
||
|
struct work_struct fragutil_work;
|
||
|
|
||
|
struct device_node *dn;
|
||
|
struct kobject *kobj;
|
||
|
|
||
|
u32 running_event;
|
||
|
struct device_file_operation gov_fops;
|
||
|
wait_queue_head_t wq;
|
||
|
} migov;
|
||
|
|
||
|
struct fps_profile {
|
||
|
/* store last profile average fps */
|
||
|
int start;
|
||
|
ktime_t profile_time; /* sec */
|
||
|
u64 fps;
|
||
|
} fps_profile;
|
||
|
|
||
|
/* shared data with Platform */
|
||
|
struct profile_sharing_data psd;
|
||
|
struct delta_sharing_data dsd;
|
||
|
struct tunable_sharing_data tsd;
|
||
|
|
||
|
static struct mutex migov_dsd_lock; /* SHOULD: should check to merge migov_lock and migov_dsd_lock */
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Helper functions */
|
||
|
/******************************************************************************/
|
||
|
static inline bool is_enabled(s32 id)
|
||
|
{
|
||
|
if (id >= NUM_OF_DOMAIN || id < 0)
|
||
|
return false;
|
||
|
|
||
|
return migov.domains[id].enabled;
|
||
|
}
|
||
|
|
||
|
static inline struct domain_data *next_domain(s32 *id)
|
||
|
{
|
||
|
s32 idx = *id;
|
||
|
|
||
|
for (++idx; idx < NUM_OF_DOMAIN; idx++)
|
||
|
if (migov.domains[idx].enabled)
|
||
|
break;
|
||
|
*id = idx;
|
||
|
|
||
|
return idx == NUM_OF_DOMAIN ? NULL : &migov.domains[idx];
|
||
|
}
|
||
|
|
||
|
#define for_each_mdomain(dom, id) \
|
||
|
for (id = -1; (dom) = next_domain(&id), id < NUM_OF_DOMAIN;)
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* fops */
|
||
|
/******************************************************************************/
|
||
|
#define IOCTL_MAGIC 'M'
|
||
|
#define IOCTL_READ_TSD _IOR(IOCTL_MAGIC, 0x50, struct tunable_sharing_data)
|
||
|
#define IOCTL_READ_PSD _IOR(IOCTL_MAGIC, 0x51, struct profile_sharing_data)
|
||
|
#define IOCTL_WR_DSD _IOWR(IOCTL_MAGIC, 0x52, struct delta_sharing_data)
|
||
|
|
||
|
ssize_t migov_fops_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
|
||
|
{
|
||
|
if (!access_ok(buf, sizeof(s32)))
|
||
|
return -EFAULT;
|
||
|
|
||
|
if (copy_to_user(buf, &migov.running_event, sizeof(s32)))
|
||
|
pr_info("MIGOV : Reading doesn't work!");
|
||
|
|
||
|
migov.running_event = PROFILE_INVALID;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
|
||
|
__poll_t migov_fops_poll(struct file *filp, struct poll_table_struct *wait)
|
||
|
{
|
||
|
__poll_t mask = 0;
|
||
|
|
||
|
poll_wait(filp, &migov.wq, wait);
|
||
|
|
||
|
switch (migov.running_event) {
|
||
|
case PROFILE_START_BY_GAME:
|
||
|
case PROFILE_START_BY_GPU:
|
||
|
case PROFILE_FINISH:
|
||
|
mask = EPOLLIN | EPOLLRDNORM;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return mask;
|
||
|
}
|
||
|
|
||
|
long migov_fops_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||
|
{
|
||
|
switch (cmd) {
|
||
|
case IOCTL_READ_TSD:
|
||
|
{
|
||
|
struct __user tunable_sharing_data *user_tsd = (struct __user tunable_sharing_data *)arg;
|
||
|
if (!access_ok(user_tsd, sizeof(struct __user tunable_sharing_data)))
|
||
|
return -EFAULT;
|
||
|
if (copy_to_user(user_tsd, &tsd, sizeof(struct tunable_sharing_data)))
|
||
|
pr_info("MIGOV : IOCTL_READ_TSD doesn't work!");
|
||
|
}
|
||
|
break;
|
||
|
case IOCTL_READ_PSD:
|
||
|
{
|
||
|
struct __user profile_sharing_data *user_psd = (struct __user profile_sharing_data *)arg;
|
||
|
if (!access_ok(user_psd, sizeof(struct __user profile_sharing_data)))
|
||
|
return -EFAULT;
|
||
|
if (copy_to_user(user_psd, &psd, sizeof(struct profile_sharing_data)))
|
||
|
pr_info("MIGOV : IOCTL_READ_PSD doesn't work!");
|
||
|
}
|
||
|
break;
|
||
|
case IOCTL_WR_DSD:
|
||
|
{
|
||
|
struct __user delta_sharing_data *user_dsd = (struct __user delta_sharing_data *)arg;
|
||
|
if (!access_ok(user_dsd, sizeof(struct __user delta_sharing_data)))
|
||
|
return -EFAULT;
|
||
|
mutex_lock(&migov_dsd_lock);
|
||
|
if (!copy_from_user(&dsd, user_dsd, sizeof(struct delta_sharing_data))) {
|
||
|
struct domain_data *dom;
|
||
|
if (!is_enabled(dsd.id)) {
|
||
|
mutex_unlock(&migov_dsd_lock);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
dom = &migov.domains[dsd.id];
|
||
|
dom->fn->get_power_change(dsd.id, dsd.freq_delta_pct,
|
||
|
&dsd.freq, &dsd.dyn_power, &dsd.st_power);
|
||
|
if (copy_to_user(user_dsd, &dsd, sizeof(struct delta_sharing_data)))
|
||
|
pr_info("MIGOV : IOCTL_RW_DSD doesn't work!");
|
||
|
}
|
||
|
mutex_unlock(&migov_dsd_lock);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int migov_fops_release(struct inode *node, struct file *filp)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* FPS functions */
|
||
|
/******************************************************************************/
|
||
|
void exynos_migov_register_frame_cnt(void (*fn)(u64 *cnt, ktime_t *time))
|
||
|
{
|
||
|
migov.get_frame_cnt = fn;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(exynos_migov_register_frame_cnt);
|
||
|
|
||
|
void exynos_migov_register_vsync_cnt(void (*fn)(u64 *cnt, ktime_t *time))
|
||
|
{
|
||
|
migov.get_vsync_cnt = fn;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(exynos_migov_register_vsync_cnt);
|
||
|
|
||
|
void exynos_migov_register_fence_cnt(void (*fn)(u64 *cnt, ktime_t *time))
|
||
|
{
|
||
|
migov.get_fence_cnt = fn;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(exynos_migov_register_fence_cnt);
|
||
|
|
||
|
void migov_update_fps_change(u32 new_fps)
|
||
|
{
|
||
|
if (migov.max_fps != new_fps * 10) {
|
||
|
migov.max_fps = new_fps * 10;
|
||
|
schedule_work(&migov.fps_work);
|
||
|
}
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(migov_update_fps_change);
|
||
|
|
||
|
static void migov_fps_change_work(struct work_struct *work)
|
||
|
{
|
||
|
tsd.max_fps = migov.max_fps;
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* profile functions */
|
||
|
/******************************************************************************/
|
||
|
static void migov_get_profile_data(struct domain_data *dom)
|
||
|
{
|
||
|
s32 id = dom->id;
|
||
|
|
||
|
/* dvfs */
|
||
|
psd.max_freq[id] = dom->fn->get_max_freq(id);
|
||
|
psd.min_freq[id] = dom->fn->get_min_freq(id);
|
||
|
psd.freq[id] = dom->fn->get_freq(id);
|
||
|
|
||
|
/* power */
|
||
|
dom->fn->get_power(id, &psd.dyn_power[id], &psd.st_power[id]);
|
||
|
|
||
|
/* temperature */
|
||
|
psd.temp[id] = dom->fn->get_temp(id);
|
||
|
|
||
|
/* active pct */
|
||
|
psd.active_pct[id] = dom->fn->get_active_pct(id);
|
||
|
|
||
|
/* private */
|
||
|
if (id <= MIGOV_CL2) {
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
|
||
|
private->fn->cpu_active_pct(dom->id, psd.cpu_active_pct[dom->id]);
|
||
|
} else if (id == MIGOV_GPU) {
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
int i;
|
||
|
|
||
|
psd.q0_empty_pct = private->fn->get_q_empty_pct(0);
|
||
|
psd.q1_empty_pct = private->fn->get_q_empty_pct(1);
|
||
|
psd.input_nr_avg_cnt = private->fn->get_input_nr_avg_cnt();
|
||
|
|
||
|
for (i = 0; i < NUM_OF_TIMEINFO; i++) psd.rtimes[i] = private->fn->get_timeinfo(i);
|
||
|
} else if (id == MIGOV_MIF) {
|
||
|
struct private_data_mif *private = dom->private;
|
||
|
|
||
|
psd.mif_pm_qos_cur_freq = exynos_pm_qos_request(private->pm_qos_min_class);
|
||
|
psd.stats0_sum = private->fn->get_stats0_sum();
|
||
|
psd.stats0_avg = private->fn->get_stats0_avg();
|
||
|
psd.stats1_sum = private->fn->get_stats1_sum();
|
||
|
psd.stats_ratio = private->fn->get_stats_ratio();
|
||
|
psd.llc_status = private->fn->get_llc_status();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void migov_set_tunable_data(void)
|
||
|
{
|
||
|
struct domain_data *dom;
|
||
|
s32 id;
|
||
|
|
||
|
for_each_mdomain(dom, id) {
|
||
|
tsd.enabled[id] = dom->enabled;
|
||
|
|
||
|
if (id <= MIGOV_CL2) {
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
|
||
|
tsd.first_cpu[id] = private->pm_qos_cpu;
|
||
|
tsd.num_of_cpu[id] = private->num_of_cpu;
|
||
|
tsd.minlock_low_limit[id] = private->pm_qos_min_freq;
|
||
|
tsd.maxlock_high_limit[id] = private->pm_qos_max_freq;
|
||
|
tsd.hp_minlock_low_limit[id] = private->hp_minlock_low_limit;
|
||
|
tsd.lp_minlock_low_limit[id] = private->lp_minlock_low_limit;
|
||
|
} else if (id == MIGOV_GPU) {
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
|
||
|
tsd.gpu_hw_status = private->fn->get_gpu_hw_status();
|
||
|
tsd.minlock_low_limit[id] = private->pm_qos_min_freq;
|
||
|
} else if (id == MIGOV_MIF) {
|
||
|
struct private_data_mif *private = dom->private;
|
||
|
|
||
|
tsd.stats0_mode_min_freq = private->stats0_mode_min_freq;
|
||
|
tsd.stats0_sum_thr = private->stats0_sum_thr;
|
||
|
tsd.stats0_updown_delta_pct_thr = private->stats0_updown_delta_pct_thr;
|
||
|
tsd.mif_hp_minlock_low_limit = private->hp_minlock_low_limit;
|
||
|
tsd.minlock_low_limit[id] = private->pm_qos_min_freq;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Profile functions */
|
||
|
/******************************************************************************/
|
||
|
extern void kbase_get_create_info(u64 *cnt, ktime_t *us);
|
||
|
static void control_llc(int value);
|
||
|
|
||
|
void migov_update_profile(void)
|
||
|
{
|
||
|
struct domain_data *dom;
|
||
|
ktime_t time = 0;
|
||
|
s32 id;
|
||
|
|
||
|
if (!migov.running)
|
||
|
return;
|
||
|
|
||
|
psd.monitor = tsd.monitor;
|
||
|
|
||
|
migov.end_time = ktime_get();
|
||
|
psd.profile_time_ms = ktime_to_ms(ktime_sub(migov.end_time, migov.start_time));
|
||
|
|
||
|
if (migov.get_frame_cnt)
|
||
|
migov.get_frame_cnt(&migov.end_frame_cnt, &time);
|
||
|
psd.profile_frame_cnt = migov.end_frame_cnt - migov.start_frame_cnt;
|
||
|
psd.frame_done_time_us = ktime_to_us(time);
|
||
|
|
||
|
if (migov.get_vsync_cnt)
|
||
|
migov.get_vsync_cnt(&migov.end_frame_vsync_cnt, &time);
|
||
|
|
||
|
psd.frame_vsync_time_us = ktime_to_us(time);
|
||
|
|
||
|
exynos_stats_get_frame_info(&psd.frame_cnt_by_swap,
|
||
|
&psd.profile_frame_vsync_cnt,
|
||
|
&psd.delta_ms_by_swap);
|
||
|
|
||
|
if (migov.get_fence_cnt)
|
||
|
migov.get_fence_cnt(&migov.end_fence_cnt, &time);
|
||
|
psd.profile_fence_cnt = migov.end_fence_cnt - migov.start_fence_cnt;
|
||
|
psd.profile_fence_time_us = ktime_to_us(time);
|
||
|
|
||
|
for_each_mdomain(dom, id) {
|
||
|
dom->fn->update_mode(id, 1);
|
||
|
migov_get_profile_data(dom);
|
||
|
}
|
||
|
|
||
|
migov.start_time = migov.end_time;
|
||
|
migov.start_frame_cnt = migov.end_frame_cnt;
|
||
|
migov.start_frame_vsync_cnt = migov.end_frame_vsync_cnt;
|
||
|
migov.start_fence_cnt = migov.end_fence_cnt;
|
||
|
}
|
||
|
|
||
|
void migov_start_profile(u32 mode)
|
||
|
{
|
||
|
struct domain_data *dom;
|
||
|
ktime_t time = 0;
|
||
|
s32 id;
|
||
|
|
||
|
if (migov.running)
|
||
|
return;
|
||
|
|
||
|
migov.running = true;
|
||
|
migov.cur_mode = mode;
|
||
|
migov.start_time = ktime_get();
|
||
|
if (migov.get_frame_cnt)
|
||
|
migov.get_frame_cnt(&migov.start_frame_cnt, &time);
|
||
|
if (migov.get_vsync_cnt)
|
||
|
migov.get_vsync_cnt(&migov.start_frame_vsync_cnt, &time);
|
||
|
|
||
|
if (!(tsd.profile_only || mode == PROFILE_START_BY_GPU)) {
|
||
|
if (is_enabled(MIGOV_CL0) || is_enabled(MIGOV_CL1) || is_enabled(MIGOV_CL2))
|
||
|
exynos_dm_dynamic_disable(1);
|
||
|
|
||
|
if (is_enabled(MIGOV_GPU))
|
||
|
gpu_dvfs_set_amigo_governor(1);
|
||
|
|
||
|
if (tsd.dyn_mo_control && migov.bts_idx)
|
||
|
bts_add_scenario(migov.bts_idx);
|
||
|
}
|
||
|
|
||
|
for_each_mdomain(dom, id) {
|
||
|
dom->fn->update_mode(dom->id, 1);
|
||
|
|
||
|
if (tsd.profile_only || mode == PROFILE_START_BY_GPU)
|
||
|
continue;
|
||
|
|
||
|
if (id <= MIGOV_CL2) {
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
|
||
|
if (freq_qos_request_active(&private->pm_qos_min_req)) {
|
||
|
freq_qos_update_request(&private->pm_qos_min_req,
|
||
|
private->pm_qos_min_freq);
|
||
|
}
|
||
|
if (freq_qos_request_active(&private->pm_qos_max_req)) {
|
||
|
freq_qos_update_request(&private->pm_qos_max_req, 0);
|
||
|
freq_qos_update_request(&private->pm_qos_max_req,
|
||
|
private->pm_qos_max_freq);
|
||
|
}
|
||
|
} else if (id == MIGOV_MIF) {
|
||
|
struct private_data_mif *private = dom->private;
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_max_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_max_req,
|
||
|
private->pm_qos_max_freq);
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_min_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_min_req,
|
||
|
private->pm_qos_min_freq);
|
||
|
} else {
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_max_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_max_req,
|
||
|
private->pm_qos_max_freq);
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_min_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_min_req,
|
||
|
private->pm_qos_min_freq);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
migov_set_tunable_data();
|
||
|
}
|
||
|
|
||
|
void migov_finish_profile(void)
|
||
|
{
|
||
|
struct domain_data *dom;
|
||
|
s32 id;
|
||
|
|
||
|
if (!migov.running)
|
||
|
return;
|
||
|
|
||
|
if (!tsd.profile_only) {
|
||
|
if (is_enabled(MIGOV_CL0) || is_enabled(MIGOV_CL1) || is_enabled(MIGOV_CL2))
|
||
|
exynos_dm_dynamic_disable(0);
|
||
|
|
||
|
if (is_enabled(MIGOV_GPU))
|
||
|
gpu_dvfs_set_amigo_governor(0);
|
||
|
|
||
|
if (tsd.dyn_mo_control && migov.bts_idx)
|
||
|
bts_del_scenario(migov.bts_idx);
|
||
|
}
|
||
|
|
||
|
for_each_mdomain(dom, id) {
|
||
|
dom->fn->update_mode(id, 0);
|
||
|
|
||
|
if (tsd.profile_only)
|
||
|
continue;
|
||
|
|
||
|
if (id <= MIGOV_CL2) {
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
|
||
|
if (freq_qos_request_active(&private->pm_qos_min_req))
|
||
|
freq_qos_update_request(&private->pm_qos_min_req,
|
||
|
PM_QOS_DEFAULT_VALUE);
|
||
|
if (freq_qos_request_active(&private->pm_qos_max_req))
|
||
|
freq_qos_update_request(&private->pm_qos_max_req,
|
||
|
PM_QOS_DEFAULT_VALUE);
|
||
|
} else if (id == MIGOV_MIF) {
|
||
|
struct private_data_mif *private = dom->private;
|
||
|
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_min_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_min_req,
|
||
|
EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_max_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_max_req,
|
||
|
PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE);
|
||
|
} else {
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
/* gpu use negative value for unlock */
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_max_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_max_req, EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_min_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_min_req, EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (migov.llc_enable) {
|
||
|
control_llc(false);
|
||
|
}
|
||
|
|
||
|
migov.running = false;
|
||
|
migov.cur_mode = PROFILE_FINISH;
|
||
|
}
|
||
|
|
||
|
static void wakeup_polling_task(void)
|
||
|
{
|
||
|
if (migov.disable && !migov.running)
|
||
|
return;
|
||
|
|
||
|
if (migov.game_mode)
|
||
|
migov.running_event = PROFILE_START_BY_GAME;
|
||
|
else if (migov.heavy_gpu_mode)
|
||
|
migov.running_event = PROFILE_START_BY_GPU;
|
||
|
else
|
||
|
migov.running_event = PROFILE_FINISH;
|
||
|
|
||
|
wake_up_interruptible(&migov.wq);
|
||
|
}
|
||
|
|
||
|
static void migov_fragutil_change_work(struct work_struct *work)
|
||
|
{
|
||
|
u32 gpu_freq = gpu_dvfs_get_cur_clock();
|
||
|
s64 elapsed_ms = 0;
|
||
|
bool is_gpu_heavy = false;
|
||
|
|
||
|
if (migov.fragutil >= migov.fragutil_upper_thr && gpu_freq >= migov.gpu_freq_thr)
|
||
|
is_gpu_heavy = true;
|
||
|
else if (migov.fragutil <= migov.fragutil_lower_thr || gpu_freq < migov.gpu_freq_thr)
|
||
|
is_gpu_heavy = false;
|
||
|
else
|
||
|
return;
|
||
|
|
||
|
if (migov.was_gpu_heavy != is_gpu_heavy) {
|
||
|
migov.was_gpu_heavy = is_gpu_heavy;
|
||
|
migov.heavy_gpu_start_time = migov.heavy_gpu_end_time;
|
||
|
}
|
||
|
migov.heavy_gpu_end_time = ktime_get();
|
||
|
|
||
|
if ((!is_gpu_heavy && migov.cur_mode == PROFILE_START_BY_GPU)
|
||
|
|| (is_gpu_heavy && migov.cur_mode == PROFILE_FINISH)) {
|
||
|
elapsed_ms = ktime_to_ms(ktime_sub(migov.heavy_gpu_end_time,
|
||
|
migov.heavy_gpu_start_time));
|
||
|
if (elapsed_ms >= migov.heavy_gpu_ms_thr) {
|
||
|
migov.heavy_gpu_mode = is_gpu_heavy;
|
||
|
wakeup_polling_task();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int migov_fragutil_change_callback(struct notifier_block *nb,
|
||
|
unsigned long val, void *info)
|
||
|
{
|
||
|
int util = *(int *)info;
|
||
|
|
||
|
if (util > 100)
|
||
|
return NOTIFY_DONE;
|
||
|
|
||
|
migov.fragutil = util;
|
||
|
schedule_work(&migov.fragutil_work);
|
||
|
|
||
|
return NOTIFY_OK;
|
||
|
}
|
||
|
|
||
|
static struct notifier_block migov_fragutil_change_notifier = {
|
||
|
.notifier_call = migov_fragutil_change_callback,
|
||
|
};
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* SYSFS functions */
|
||
|
/******************************************************************************/
|
||
|
/* only for fps profile without migov running and profile only mode */
|
||
|
static ssize_t fps_profile_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
s32 ret = 0;
|
||
|
u64 fps_ = fps_profile.fps / 10;
|
||
|
u64 _fps = fps_profile.fps % 10;
|
||
|
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "profile_time: %llu sec\n", fps_profile.profile_time);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "fps: %llu.%llu\n", fps_, _fps);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
static ssize_t fps_profile_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
u32 mode;
|
||
|
|
||
|
if (sscanf(buf, "%u", &mode) != 1)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (!migov.get_frame_cnt)
|
||
|
return -EINVAL;
|
||
|
|
||
|
/* start */
|
||
|
if (mode) {
|
||
|
migov.get_frame_cnt(&fps_profile.fps, &fps_profile.profile_time);
|
||
|
} else {
|
||
|
u64 cur_cnt;
|
||
|
ktime_t cur_time;
|
||
|
|
||
|
migov.get_frame_cnt(&cur_cnt, &cur_time);
|
||
|
fps_profile.profile_time = ktime_sub(cur_time, fps_profile.profile_time) / NSEC_PER_SEC;
|
||
|
fps_profile.fps = ((cur_cnt - fps_profile.fps) * 10) / fps_profile.profile_time;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(fps_profile);
|
||
|
|
||
|
static ssize_t running_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return snprintf(buf, PAGE_SIZE, "%d\n", migov.running);
|
||
|
}
|
||
|
static ssize_t running_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
u32 mode;
|
||
|
|
||
|
if (sscanf(buf, "%u", &mode) != 1)
|
||
|
return -EINVAL;
|
||
|
|
||
|
migov.game_mode = (bool)mode;
|
||
|
migov.forced_running = (bool) mode;
|
||
|
wakeup_polling_task();
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(running);
|
||
|
|
||
|
#define MIGOV_ATTR_RW(_name) \
|
||
|
static ssize_t _name##_show(struct device *dev, \
|
||
|
struct device_attribute *attr, char *buf) \
|
||
|
{ \
|
||
|
return snprintf(buf, PAGE_SIZE, "%d\n", migov._name); \
|
||
|
} \
|
||
|
static ssize_t _name##_store(struct device *dev, \
|
||
|
struct device_attribute *attr, const char *buf, size_t count) \
|
||
|
{ \
|
||
|
s32 val; \
|
||
|
\
|
||
|
if (sscanf(buf, "%d", &val) != 1) \
|
||
|
return -EINVAL; \
|
||
|
\
|
||
|
migov._name = val; \
|
||
|
\
|
||
|
return count; \
|
||
|
} \
|
||
|
static DEVICE_ATTR_RW(_name);
|
||
|
|
||
|
MIGOV_ATTR_RW(disable);
|
||
|
MIGOV_ATTR_RW(gpu_freq_thr);
|
||
|
MIGOV_ATTR_RW(heavy_gpu_ms_thr);
|
||
|
|
||
|
#define TUNABLE_ATTR_RW(_name) \
|
||
|
static ssize_t _name##_show(struct device *dev, \
|
||
|
struct device_attribute *attr, char *buf) \
|
||
|
{ \
|
||
|
return snprintf(buf, PAGE_SIZE, "%d\n", tsd._name); \
|
||
|
} \
|
||
|
static ssize_t _name##_store(struct device *dev, \
|
||
|
struct device_attribute *attr, const char *buf, size_t count) \
|
||
|
{ \
|
||
|
s32 val; \
|
||
|
\
|
||
|
if (sscanf(buf, "%d", &val) != 1) \
|
||
|
return -EINVAL; \
|
||
|
\
|
||
|
tsd._name = val; \
|
||
|
\
|
||
|
return count; \
|
||
|
} \
|
||
|
static DEVICE_ATTR_RW(_name);
|
||
|
|
||
|
TUNABLE_ATTR_RW(monitor);
|
||
|
TUNABLE_ATTR_RW(profile_only);
|
||
|
TUNABLE_ATTR_RW(window_period);
|
||
|
TUNABLE_ATTR_RW(window_number);
|
||
|
TUNABLE_ATTR_RW(active_pct_thr);
|
||
|
TUNABLE_ATTR_RW(valid_freq_delta_pct);
|
||
|
TUNABLE_ATTR_RW(min_sensitivity);
|
||
|
TUNABLE_ATTR_RW(cpu_bottleneck_thr);
|
||
|
TUNABLE_ATTR_RW(gpu_bottleneck_thr);
|
||
|
TUNABLE_ATTR_RW(gpu_ar_bottleneck_thr);
|
||
|
TUNABLE_ATTR_RW(mif_bottleneck_thr);
|
||
|
TUNABLE_ATTR_RW(dt_ctrl_en);
|
||
|
TUNABLE_ATTR_RW(dt_over_thr);
|
||
|
TUNABLE_ATTR_RW(dt_under_thr);
|
||
|
TUNABLE_ATTR_RW(dt_up_step);
|
||
|
TUNABLE_ATTR_RW(dt_down_step);
|
||
|
TUNABLE_ATTR_RW(dpat_upper_thr);
|
||
|
TUNABLE_ATTR_RW(dpat_lower_thr);
|
||
|
TUNABLE_ATTR_RW(dpat_lower_cnt_thr);
|
||
|
TUNABLE_ATTR_RW(dpat_up_step);
|
||
|
TUNABLE_ATTR_RW(dpat_down_step);
|
||
|
TUNABLE_ATTR_RW(inc_perf_temp_thr);
|
||
|
TUNABLE_ATTR_RW(inc_perf_power_thr);
|
||
|
TUNABLE_ATTR_RW(inc_perf_thr);
|
||
|
TUNABLE_ATTR_RW(dec_perf_thr);
|
||
|
TUNABLE_ATTR_RW(hp_minlock_fps_delta_pct_thr);
|
||
|
TUNABLE_ATTR_RW(hp_minlock_power_upper_thr);
|
||
|
TUNABLE_ATTR_RW(hp_minlock_power_lower_thr);
|
||
|
TUNABLE_ATTR_RW(dyn_mo_control);
|
||
|
|
||
|
static ssize_t stc_config_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return exynos_gpu_stc_config_show(PAGE_SIZE, buf);
|
||
|
}
|
||
|
|
||
|
static ssize_t stc_config_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
exynos_gpu_stc_config_store(buf);
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(stc_config);
|
||
|
|
||
|
|
||
|
#define PER_CPU_TUNABLE_ATTR_RW(_name) \
|
||
|
static ssize_t _name##_show(struct device *dev, \
|
||
|
struct device_attribute *attr, char *buf) \
|
||
|
{ \
|
||
|
struct domain_data *dom; \
|
||
|
s32 id, ret = 0; \
|
||
|
\
|
||
|
for_each_mdomain(dom, id) { \
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "[%d]%s: %d\n", \
|
||
|
id, domain_name[id], tsd._name[id]); \
|
||
|
} \
|
||
|
\
|
||
|
return ret; \
|
||
|
} \
|
||
|
static ssize_t _name##_store(struct device *dev, \
|
||
|
struct device_attribute *attr, const char *buf, size_t count) \
|
||
|
{ \
|
||
|
s32 id, val; \
|
||
|
\
|
||
|
if (sscanf(buf, "%d %d", &id, &val) != 2) \
|
||
|
return -EINVAL; \
|
||
|
\
|
||
|
if (!is_enabled(id)) \
|
||
|
return -EINVAL; \
|
||
|
tsd._name[id] = val; \
|
||
|
\
|
||
|
return count; \
|
||
|
} \
|
||
|
static DEVICE_ATTR_RW(_name);
|
||
|
|
||
|
PER_CPU_TUNABLE_ATTR_RW(max_margin);
|
||
|
PER_CPU_TUNABLE_ATTR_RW(min_margin);
|
||
|
PER_CPU_TUNABLE_ATTR_RW(margin_up_step);
|
||
|
PER_CPU_TUNABLE_ATTR_RW(margin_down_step);
|
||
|
PER_CPU_TUNABLE_ATTR_RW(margin_default_step);
|
||
|
|
||
|
static ssize_t runtime_thr_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
ssize_t ret = 0;
|
||
|
|
||
|
ret += snprintf(buf, PAGE_SIZE, "%d %d %d %d %d %d\n",
|
||
|
tsd.runtime_thr[0], tsd.runtime_thr[1], tsd.runtime_thr[2],
|
||
|
tsd.runtime_thr[3], tsd.runtime_thr[4], tsd.runtime_thr[5]);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
static ssize_t runtime_thr_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
unsigned int index;
|
||
|
int val;
|
||
|
|
||
|
if (sscanf(buf, "%u %d", &index, &val) != 2)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (index >= 6)
|
||
|
return -EINVAL;
|
||
|
|
||
|
tsd.runtime_thr[index] = val;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(runtime_thr);
|
||
|
|
||
|
static struct attribute *migov_attrs[] = {
|
||
|
&dev_attr_running.attr,
|
||
|
&dev_attr_fps_profile.attr,
|
||
|
&dev_attr_profile_only.attr,
|
||
|
&dev_attr_monitor.attr,
|
||
|
&dev_attr_disable.attr,
|
||
|
&dev_attr_window_period.attr,
|
||
|
&dev_attr_window_number.attr,
|
||
|
&dev_attr_active_pct_thr.attr,
|
||
|
&dev_attr_valid_freq_delta_pct.attr,
|
||
|
&dev_attr_min_sensitivity.attr,
|
||
|
&dev_attr_cpu_bottleneck_thr.attr,
|
||
|
&dev_attr_gpu_bottleneck_thr.attr,
|
||
|
&dev_attr_gpu_ar_bottleneck_thr.attr,
|
||
|
&dev_attr_mif_bottleneck_thr.attr,
|
||
|
&dev_attr_dt_ctrl_en.attr,
|
||
|
&dev_attr_dt_over_thr.attr,
|
||
|
&dev_attr_dt_under_thr.attr,
|
||
|
&dev_attr_dt_up_step.attr,
|
||
|
&dev_attr_dt_down_step.attr,
|
||
|
&dev_attr_dpat_upper_thr.attr,
|
||
|
&dev_attr_dpat_lower_thr.attr,
|
||
|
&dev_attr_dpat_lower_cnt_thr.attr,
|
||
|
&dev_attr_dpat_up_step.attr,
|
||
|
&dev_attr_dpat_down_step.attr,
|
||
|
&dev_attr_max_margin.attr,
|
||
|
&dev_attr_min_margin.attr,
|
||
|
&dev_attr_margin_up_step.attr,
|
||
|
&dev_attr_margin_down_step.attr,
|
||
|
&dev_attr_margin_default_step.attr,
|
||
|
&dev_attr_inc_perf_temp_thr.attr,
|
||
|
&dev_attr_inc_perf_power_thr.attr,
|
||
|
&dev_attr_inc_perf_thr.attr,
|
||
|
&dev_attr_dec_perf_thr.attr,
|
||
|
&dev_attr_gpu_freq_thr.attr,
|
||
|
&dev_attr_heavy_gpu_ms_thr.attr,
|
||
|
&dev_attr_hp_minlock_fps_delta_pct_thr.attr,
|
||
|
&dev_attr_hp_minlock_power_upper_thr.attr,
|
||
|
&dev_attr_hp_minlock_power_lower_thr.attr,
|
||
|
&dev_attr_dyn_mo_control.attr,
|
||
|
&dev_attr_runtime_thr.attr,
|
||
|
&dev_attr_stc_config.attr,
|
||
|
NULL,
|
||
|
};
|
||
|
static struct attribute_group migov_attr_group = {
|
||
|
.name = "migov",
|
||
|
.attrs = migov_attrs,
|
||
|
};
|
||
|
|
||
|
#define PRIVATE_ATTR_RW(_priv, _id, _ip, _name) \
|
||
|
static ssize_t _ip##_##_name##_show(struct device *dev, \
|
||
|
struct device_attribute *attr, char *buf) \
|
||
|
{ \
|
||
|
struct domain_data *dom = &migov.domains[_id]; \
|
||
|
struct private_data_##_priv *private; \
|
||
|
\
|
||
|
if (!dom) \
|
||
|
return 0; \
|
||
|
private = dom->private; \
|
||
|
return snprintf(buf, PAGE_SIZE, "%d\n", (int)private->_name); \
|
||
|
} \
|
||
|
static ssize_t _ip##_##_name##_store(struct device *dev, \
|
||
|
struct device_attribute *attr, const char *buf, size_t count) \
|
||
|
{ \
|
||
|
struct domain_data *dom = &migov.domains[_id]; \
|
||
|
struct private_data_##_priv *private; \
|
||
|
s32 val; \
|
||
|
\
|
||
|
if (sscanf(buf, "%d", &val) != 1) \
|
||
|
return -EINVAL; \
|
||
|
if (!dom) \
|
||
|
return 0; \
|
||
|
private = dom->private; \
|
||
|
private->_name = val; \
|
||
|
return count; \
|
||
|
} \
|
||
|
static DEVICE_ATTR_RW(_ip##_##_name);
|
||
|
#define CPU_PRIVATE_ATTR_RW(_id, _ip, _name) \
|
||
|
PRIVATE_ATTR_RW(cpu, _id, _ip, _name)
|
||
|
#define GPU_PRIVATE_ATTR_RW(_id, _ip, _name) \
|
||
|
PRIVATE_ATTR_RW(gpu, _id, _ip, _name)
|
||
|
#define MIF_PRIVATE_ATTR_RW(_id, _ip, _name) \
|
||
|
PRIVATE_ATTR_RW(mif, _id, _ip, _name)
|
||
|
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL0, cl0, pm_qos_min_freq);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL0, cl0, pm_qos_max_freq);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL0, cl0, hp_minlock_low_limit);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL0, cl0, lp_minlock_low_limit);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL1, cl1, pm_qos_min_freq);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL1, cl1, pm_qos_max_freq);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL1, cl1, hp_minlock_low_limit);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL1, cl1, lp_minlock_low_limit);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL2, cl2, pm_qos_min_freq);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL2, cl2, pm_qos_max_freq);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL2, cl2, hp_minlock_low_limit);
|
||
|
CPU_PRIVATE_ATTR_RW(MIGOV_CL2, cl2, lp_minlock_low_limit);
|
||
|
GPU_PRIVATE_ATTR_RW(MIGOV_GPU, gpu, q0_empty_pct_thr);
|
||
|
GPU_PRIVATE_ATTR_RW(MIGOV_GPU, gpu, q1_empty_pct_thr);
|
||
|
GPU_PRIVATE_ATTR_RW(MIGOV_GPU, gpu, gpu_active_pct_thr);
|
||
|
GPU_PRIVATE_ATTR_RW(MIGOV_GPU, gpu, pm_qos_max_freq);
|
||
|
GPU_PRIVATE_ATTR_RW(MIGOV_GPU, gpu, pm_qos_min_freq);
|
||
|
MIF_PRIVATE_ATTR_RW(MIGOV_MIF, mif, stats0_mode_min_freq);
|
||
|
MIF_PRIVATE_ATTR_RW(MIGOV_MIF, mif, stats0_sum_thr);
|
||
|
MIF_PRIVATE_ATTR_RW(MIGOV_MIF, mif, stats0_updown_delta_pct_thr);
|
||
|
MIF_PRIVATE_ATTR_RW(MIGOV_MIF, mif, hp_minlock_low_limit);
|
||
|
MIF_PRIVATE_ATTR_RW(MIGOV_MIF, mif, pm_qos_max_freq);
|
||
|
MIF_PRIVATE_ATTR_RW(MIGOV_MIF, mif, pm_qos_min_freq);
|
||
|
|
||
|
#define MAXNUM_OF_PRIV_ATTR 10
|
||
|
static struct attribute *private_attrs[NUM_OF_DOMAIN][MAXNUM_OF_PRIV_ATTR] = {
|
||
|
{
|
||
|
&dev_attr_cl0_pm_qos_min_freq.attr,
|
||
|
&dev_attr_cl0_pm_qos_max_freq.attr,
|
||
|
&dev_attr_cl0_hp_minlock_low_limit.attr,
|
||
|
&dev_attr_cl0_lp_minlock_low_limit.attr,
|
||
|
NULL,
|
||
|
},
|
||
|
{
|
||
|
&dev_attr_cl1_pm_qos_min_freq.attr,
|
||
|
&dev_attr_cl1_pm_qos_max_freq.attr,
|
||
|
&dev_attr_cl1_hp_minlock_low_limit.attr,
|
||
|
&dev_attr_cl1_lp_minlock_low_limit.attr,
|
||
|
NULL,
|
||
|
},
|
||
|
{
|
||
|
&dev_attr_cl2_pm_qos_min_freq.attr,
|
||
|
&dev_attr_cl2_pm_qos_max_freq.attr,
|
||
|
&dev_attr_cl2_hp_minlock_low_limit.attr,
|
||
|
&dev_attr_cl2_lp_minlock_low_limit.attr,
|
||
|
NULL,
|
||
|
},
|
||
|
{
|
||
|
&dev_attr_gpu_pm_qos_max_freq.attr,
|
||
|
&dev_attr_gpu_pm_qos_min_freq.attr,
|
||
|
&dev_attr_gpu_q0_empty_pct_thr.attr,
|
||
|
&dev_attr_gpu_q1_empty_pct_thr.attr,
|
||
|
&dev_attr_gpu_gpu_active_pct_thr.attr,
|
||
|
NULL,
|
||
|
},
|
||
|
{
|
||
|
&dev_attr_mif_stats0_mode_min_freq.attr,
|
||
|
&dev_attr_mif_stats0_sum_thr.attr,
|
||
|
&dev_attr_mif_stats0_updown_delta_pct_thr.attr,
|
||
|
&dev_attr_mif_hp_minlock_low_limit.attr,
|
||
|
&dev_attr_mif_pm_qos_max_freq.attr,
|
||
|
&dev_attr_mif_pm_qos_min_freq.attr,
|
||
|
NULL,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
#define RATIO_UNIT 1000
|
||
|
static ssize_t set_margin_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void control_max_qos(int id, int value)
|
||
|
{
|
||
|
struct domain_data *dom = &migov.domains[id];
|
||
|
|
||
|
switch (id) {
|
||
|
case MIGOV_CL0:
|
||
|
case MIGOV_CL1:
|
||
|
case MIGOV_CL2:
|
||
|
{
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
if (freq_qos_request_active(&private->pm_qos_max_req))
|
||
|
freq_qos_update_request(&private->pm_qos_max_req, value);
|
||
|
}
|
||
|
break;
|
||
|
case MIGOV_MIF:
|
||
|
{
|
||
|
struct private_data_mif *private = dom->private;
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_max_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_max_req, value);
|
||
|
}
|
||
|
break;
|
||
|
case MIGOV_GPU:
|
||
|
{
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_max_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_max_req, value);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void control_min_qos(int id, int value)
|
||
|
{
|
||
|
struct domain_data *dom = &migov.domains[id];
|
||
|
|
||
|
switch (id) {
|
||
|
case MIGOV_CL0:
|
||
|
case MIGOV_CL1:
|
||
|
case MIGOV_CL2:
|
||
|
{
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
if (freq_qos_request_active(&private->pm_qos_min_req))
|
||
|
freq_qos_update_request(&private->pm_qos_min_req, value);
|
||
|
}
|
||
|
break;
|
||
|
case MIGOV_MIF:
|
||
|
{
|
||
|
struct private_data_mif *private = dom->private;
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_min_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_min_req, value);
|
||
|
}
|
||
|
break;
|
||
|
case MIGOV_GPU:
|
||
|
{
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
if (exynos_pm_qos_request_active(&private->pm_qos_min_req))
|
||
|
exynos_pm_qos_update_request(&private->pm_qos_min_req, value);
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void control_margin(int id, int value)
|
||
|
{
|
||
|
struct domain_data *dom = &migov.domains[id];
|
||
|
|
||
|
/* CPU uses pelt margin via ems service */
|
||
|
if (id <= MIGOV_CL2)
|
||
|
return;
|
||
|
|
||
|
if (likely(dom->fn->set_margin))
|
||
|
dom->fn->set_margin(id, value);
|
||
|
}
|
||
|
static void control_mo(int id, int value)
|
||
|
{
|
||
|
int idx;
|
||
|
|
||
|
if (!migov.bts_idx || !migov.len_mo_id)
|
||
|
return;
|
||
|
|
||
|
for (idx = 0; idx < migov.len_mo_id; idx++)
|
||
|
bts_change_mo(migov.bts_idx, migov.mo_id[idx], value, value);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
static void control_llc(int value)
|
||
|
{
|
||
|
if (migov.llc_enable == !!value)
|
||
|
return;
|
||
|
|
||
|
migov.llc_enable = !!value;
|
||
|
|
||
|
if (migov.llc_enable) {
|
||
|
llc_enable(migov.llc_enable);
|
||
|
llc_region_alloc(LLC_REGION_MIGOV, 1, 16);
|
||
|
} else {
|
||
|
llc_region_alloc(LLC_REGION_MIGOV, 0, 0);
|
||
|
llc_enable(migov.llc_enable);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void control_rtp(int id, int value)
|
||
|
{
|
||
|
struct domain_data *dom = &migov.domains[MIGOV_GPU];
|
||
|
struct private_data_gpu *private = dom->private;
|
||
|
switch (id) {
|
||
|
case OP_RTP_TARGETFRAMETIME:
|
||
|
private->fn->set_targetframetime(value);
|
||
|
break;
|
||
|
case OP_RTP_TARGETTIMEMARGIN:
|
||
|
private->fn->set_targettime_margin(value);
|
||
|
break;
|
||
|
case OP_RTP_UTIL_MARGIN:
|
||
|
private->fn->set_util_margin(value);
|
||
|
break;
|
||
|
case OP_RTP_DECON_TIME:
|
||
|
private->fn->set_decon_time(value);
|
||
|
break;
|
||
|
case OP_RTP_COMBCTRL:
|
||
|
private->fn->set_comb_ctrl(value);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* kernel only to decode */
|
||
|
#define get_op_code(x) (((x) >> OP_CODE_SHIFT) & OP_CODE_MASK)
|
||
|
#define get_ip_id(x) ((x) & IP_ID_MASK)
|
||
|
static ssize_t set_margin_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
s32 id, op, ctrl, value;
|
||
|
|
||
|
if (tsd.profile_only)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (sscanf(buf, "%d %d", &ctrl, &value) != 2)
|
||
|
return -EINVAL;
|
||
|
|
||
|
id = get_ip_id(ctrl);
|
||
|
op = get_op_code(ctrl);
|
||
|
|
||
|
if (!is_enabled(id) || op == OP_INVALID)
|
||
|
return -EINVAL;
|
||
|
|
||
|
switch (op) {
|
||
|
case OP_PM_QOS_MAX:
|
||
|
control_max_qos(id, value);
|
||
|
break;
|
||
|
case OP_PM_QOS_MIN:
|
||
|
control_min_qos(id, value);
|
||
|
break;
|
||
|
case OP_MARGIN:
|
||
|
control_margin(id, value);
|
||
|
break;
|
||
|
case OP_MO:
|
||
|
control_mo(id, value);
|
||
|
break;
|
||
|
case OP_LLC:
|
||
|
control_llc(value);
|
||
|
break;
|
||
|
default:
|
||
|
control_rtp(op, value);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(set_margin);
|
||
|
|
||
|
static ssize_t control_profile_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
s32 ret = 0;
|
||
|
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "profile_time_ms=%lld\n", psd.profile_time_ms);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "profile_frame_cnt=%llu\n", psd.profile_frame_cnt);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "user_target_fps=%d\n", psd.user_target_fps);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "max_freq=%d, %d, %d, %d, %d\n",
|
||
|
psd.max_freq[0], psd.max_freq[1], psd.max_freq[2], psd.max_freq[3], psd.max_freq[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "min_freq=%d, %d, %d, %d, %d\n",
|
||
|
psd.min_freq[0], psd.min_freq[1], psd.min_freq[2], psd.min_freq[3], psd.min_freq[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "freq=%d, %d, %d, %d, %d\n",
|
||
|
psd.freq[0], psd.freq[1], psd.freq[2], psd.freq[3], psd.freq[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "dyn_power=%llu, %llu, %llu, %llu, %llu\n",
|
||
|
psd.dyn_power[0], psd.dyn_power[1], psd.dyn_power[2], psd.dyn_power[3], psd.dyn_power[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "st_power=%llu, %llu, %llu, %llu, %llu\n",
|
||
|
psd.st_power[0], psd.st_power[1], psd.st_power[2], psd.st_power[3], psd.st_power[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "temp=%d, %d, %d, %d, %d\n",
|
||
|
psd.temp[0], psd.temp[1], psd.temp[2], psd.temp[3], psd.temp[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "active_pct=%d, %d, %d, %d, %d\n",
|
||
|
psd.active_pct[0], psd.active_pct[1], psd.active_pct[2], psd.active_pct[3], psd.active_pct[4]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "q0_empty_pct=%llu\n", psd.q0_empty_pct);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "q1_empty_pct=%llu\n", psd.q1_empty_pct);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "input_nr_avg_cnt=%llu\n", psd.input_nr_avg_cnt);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "times=%llu %llu %llu %llu %llu\n",
|
||
|
psd.rtimes[PREAVG], psd.rtimes[CPUAVG], psd.rtimes[V2SAVG],
|
||
|
psd.rtimes[GPUAVG], psd.rtimes[V2FAVG]);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "stats0_sum=%llu\n", psd.stats0_sum);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "stats0_avg=%llu\n", psd.stats0_avg);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "stats_ratio=%llu\n", psd.stats_ratio);
|
||
|
ret += snprintf(buf + ret, PAGE_SIZE - ret, "mif_pm_qos_cur_freq=%d\n", psd.mif_pm_qos_cur_freq);
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
static ssize_t control_profile_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
u32 mode;
|
||
|
|
||
|
if (sscanf(buf, "%u", &mode) != 1)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (!migov.running && (mode == PROFILE_START_BY_GAME || mode == PROFILE_START_BY_GPU))
|
||
|
migov_start_profile(mode);
|
||
|
else if (migov.running && mode == PROFILE_FINISH)
|
||
|
migov_finish_profile();
|
||
|
else if (migov.running && mode == PROFILE_UPDATE)
|
||
|
migov_update_profile();
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(control_profile);
|
||
|
|
||
|
#define FRAGUTIL_THR_MARGIN 70
|
||
|
static ssize_t fragutil_thr_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return snprintf(buf, PAGE_SIZE, "upper=%d lower=%d\n",
|
||
|
migov.fragutil_upper_thr, migov.fragutil_lower_thr);
|
||
|
}
|
||
|
static ssize_t fragutil_thr_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
s32 val;
|
||
|
|
||
|
if (sscanf(buf, "%u", &val) != 1)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (val > 100 | val < 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
migov.fragutil_upper_thr = val;
|
||
|
migov.fragutil_lower_thr = val * FRAGUTIL_THR_MARGIN / 100;
|
||
|
migov.fragutil_lower_thr = max(0, migov.fragutil_lower_thr);
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(fragutil_thr);
|
||
|
|
||
|
static ssize_t user_target_fps_show(struct device *dev,
|
||
|
struct device_attribute *attr, char *buf)
|
||
|
{
|
||
|
return snprintf(buf, PAGE_SIZE, "%d\n", psd.user_target_fps / 10);
|
||
|
}
|
||
|
static ssize_t user_target_fps_store(struct device *dev,
|
||
|
struct device_attribute *attr, const char *buf, size_t count)
|
||
|
{
|
||
|
s32 val;
|
||
|
|
||
|
if (sscanf(buf, "%u", &val) != 1)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (val < 0)
|
||
|
return -EINVAL;
|
||
|
|
||
|
psd.user_target_fps = val * 10;
|
||
|
|
||
|
return count;
|
||
|
}
|
||
|
static DEVICE_ATTR_RW(user_target_fps);
|
||
|
|
||
|
static struct attribute *control_attrs[] = {
|
||
|
&dev_attr_set_margin.attr,
|
||
|
&dev_attr_control_profile.attr,
|
||
|
&dev_attr_fragutil_thr.attr,
|
||
|
&dev_attr_user_target_fps.attr,
|
||
|
NULL,
|
||
|
};
|
||
|
static struct attribute_group control_attr_group = {
|
||
|
.name = "control",
|
||
|
.attrs = control_attrs,
|
||
|
};
|
||
|
|
||
|
/*********************************************************************/
|
||
|
/* MIGOV mode change notifier */
|
||
|
/*********************************************************************/
|
||
|
static int migov_mode_update_callback(struct notifier_block *nb,
|
||
|
unsigned long val, void *v)
|
||
|
{
|
||
|
struct emstune_set *set = (struct emstune_set *)v;
|
||
|
|
||
|
if (migov.forced_running)
|
||
|
return NOTIFY_OK;
|
||
|
|
||
|
if (!migov.game_mode) {
|
||
|
/* start migov with game when */
|
||
|
if ((set->type & EMSTUNE_MODE_TYPE_GAME) &&
|
||
|
!(set->type & EMSTUNE_BOOST_TYPE_EXTREME)) {
|
||
|
migov.game_mode = true;
|
||
|
wakeup_polling_task();
|
||
|
}
|
||
|
} else {
|
||
|
/* finish migov */
|
||
|
if (!(set->type & EMSTUNE_MODE_TYPE_GAME) ||
|
||
|
(set->type & EMSTUNE_BOOST_TYPE_EXTREME)) {
|
||
|
migov.game_mode = false;
|
||
|
wakeup_polling_task();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return NOTIFY_OK;
|
||
|
}
|
||
|
static struct notifier_block migov_mode_update_notifier = {
|
||
|
.notifier_call = migov_mode_update_callback,
|
||
|
};
|
||
|
|
||
|
/******************************************************************************/
|
||
|
/* Initialize */
|
||
|
/******************************************************************************/
|
||
|
static s32 init_domain_data(struct device_node *root,
|
||
|
struct domain_data *dom, void *private_fn)
|
||
|
{
|
||
|
struct device_node *dn;
|
||
|
s32 cnt, ret = 0, id = dom->id;
|
||
|
|
||
|
dn = of_get_child_by_name(root, domain_name[dom->id]);
|
||
|
if (!dn) {
|
||
|
pr_err("migov: Failed to get node of domain(id=%d)\n", dom->id);
|
||
|
return -ENODEV;
|
||
|
}
|
||
|
|
||
|
tsd.freq_table_cnt[id] = dom->fn->get_table_cnt(id);
|
||
|
cnt = dom->fn->get_freq_table(id, tsd.freq_table[id]);
|
||
|
if (tsd.freq_table_cnt[id] != cnt) {
|
||
|
pr_err("migov: table cnt(%u) is un-synced with freq table cnt(%u)(id=%d)\n",
|
||
|
id, tsd.freq_table_cnt[id], cnt);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "max", &tsd.max_margin[id]);
|
||
|
ret |= of_property_read_s32(dn, "min", &tsd.min_margin[id]);
|
||
|
ret |= of_property_read_s32(dn, "up-step", &tsd.margin_up_step[id]);
|
||
|
ret |= of_property_read_s32(dn, "down-step", &tsd.margin_down_step[id]);
|
||
|
ret |= of_property_read_s32(dn, "default-step", &tsd.margin_default_step[id]);
|
||
|
if (ret)
|
||
|
return -EINVAL;
|
||
|
|
||
|
if (id <= MIGOV_CL2) {
|
||
|
struct private_data_cpu *private = dom->private;
|
||
|
struct cpufreq_policy *policy;
|
||
|
|
||
|
private = kzalloc(sizeof(struct private_data_cpu), GFP_KERNEL);
|
||
|
if (!private)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
private->fn = private_fn;
|
||
|
|
||
|
tsd.asv_ids[id] = private->fn->cpu_asv_ids(id);
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "pm-qos-cpu", &private->pm_qos_cpu);
|
||
|
policy = cpufreq_cpu_get(private->pm_qos_cpu);
|
||
|
if (!policy) {
|
||
|
kfree(private);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
private->num_of_cpu = cpumask_weight(policy->related_cpus);
|
||
|
|
||
|
if (of_property_read_s32(dn, "pm-qos-min-freq", &private->pm_qos_min_freq))
|
||
|
private->pm_qos_min_freq = PM_QOS_DEFAULT_VALUE;
|
||
|
if (of_property_read_s32(dn, "pm-qos-max-freq", &private->pm_qos_max_freq))
|
||
|
private->pm_qos_max_freq = PM_QOS_DEFAULT_VALUE;
|
||
|
if (of_property_read_s32(dn, "hp-minlock-low-limit", &private->hp_minlock_low_limit))
|
||
|
private->hp_minlock_low_limit = PM_QOS_DEFAULT_VALUE;
|
||
|
if (of_property_read_s32(dn, "lp-minlock-low-limit", &private->lp_minlock_low_limit))
|
||
|
private->lp_minlock_low_limit = PM_QOS_DEFAULT_VALUE;
|
||
|
|
||
|
freq_qos_tracer_add_request(&policy->constraints, &private->pm_qos_min_req,
|
||
|
FREQ_QOS_MIN, PM_QOS_DEFAULT_VALUE);
|
||
|
freq_qos_tracer_add_request(&policy->constraints, &private->pm_qos_max_req,
|
||
|
FREQ_QOS_MAX, PM_QOS_DEFAULT_VALUE);
|
||
|
|
||
|
if (!ret) {
|
||
|
dom->private = private;
|
||
|
} else {
|
||
|
kfree(private);
|
||
|
}
|
||
|
} else if (dom->id == MIGOV_GPU) {
|
||
|
struct private_data_gpu *private;
|
||
|
s32 val;
|
||
|
|
||
|
private = kzalloc(sizeof(struct private_data_gpu), GFP_KERNEL);
|
||
|
if (!private)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
private->fn = private_fn;
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "pm-qos-max-class",
|
||
|
&private->pm_qos_max_class);
|
||
|
if (of_property_read_s32(dn, "pm-qos-max-freq", &private->pm_qos_max_freq))
|
||
|
private->pm_qos_max_freq = PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE;
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "pm-qos-min-class",
|
||
|
&private->pm_qos_min_class);
|
||
|
if (of_property_read_s32(dn, "pm-qos-min-freq", &private->pm_qos_min_freq))
|
||
|
private->pm_qos_min_freq = PM_QOS_MIN_FREQUENCY_DEFAULT_VALUE;
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "q0-empty-pct-thr", &val);
|
||
|
private->q0_empty_pct_thr = val;
|
||
|
ret |= of_property_read_s32(dn, "q1-empty-pct-thr", &val);
|
||
|
private->q1_empty_pct_thr = val;
|
||
|
ret |= of_property_read_s32(dn, "active-pct-thr", &val);
|
||
|
private->gpu_active_pct_thr = val;
|
||
|
|
||
|
if (!ret) {
|
||
|
/* gpu use negative value for unlock */
|
||
|
exynos_pm_qos_add_request(&private->pm_qos_min_req,
|
||
|
private->pm_qos_min_class, EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
exynos_pm_qos_add_request(&private->pm_qos_max_req,
|
||
|
private->pm_qos_max_class, EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
|
||
|
dom->private = private;
|
||
|
} else {
|
||
|
kfree(private);
|
||
|
}
|
||
|
} else if (dom->id == MIGOV_MIF) {
|
||
|
struct private_data_mif *private;
|
||
|
s32 val;
|
||
|
|
||
|
private = kzalloc(sizeof(struct private_data_mif), GFP_KERNEL);
|
||
|
if (!private)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
private->fn = private_fn;
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "pm-qos-max-class", &private->pm_qos_max_class);
|
||
|
ret |= of_property_read_s32(dn, "pm-qos-min-class", &private->pm_qos_min_class);
|
||
|
ret |= of_property_read_s32(dn, "freq-stats0-mode-min-freq",
|
||
|
&private->stats0_mode_min_freq);
|
||
|
ret |= of_property_read_s32(dn, "freq-stats0-thr", &val);
|
||
|
private->stats0_sum_thr = val;
|
||
|
ret |= of_property_read_s32(dn, "freq-stats0-updown-delta-pct-thr", &val);
|
||
|
private->stats0_updown_delta_pct_thr = val;
|
||
|
ret |= of_property_read_s32(dn, "hp-minlock-low-limit", &private->hp_minlock_low_limit);
|
||
|
|
||
|
if (of_property_read_s32(dn, "pm-qos-max-freq", &private->pm_qos_max_freq))
|
||
|
private->pm_qos_max_freq = PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE;
|
||
|
if (of_property_read_s32(dn, "pm-qos-min-freq", &private->pm_qos_min_freq))
|
||
|
private->pm_qos_min_freq = PM_QOS_MIN_FREQUENCY_DEFAULT_VALUE;
|
||
|
|
||
|
if (!ret) {
|
||
|
exynos_pm_qos_add_request(&private->pm_qos_min_req,
|
||
|
private->pm_qos_min_class, EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
exynos_pm_qos_add_request(&private->pm_qos_max_req,
|
||
|
private->pm_qos_max_class, EXYNOS_PM_QOS_DEFAULT_VALUE);
|
||
|
|
||
|
dom->private = private;
|
||
|
} else {
|
||
|
kfree(private);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!ret) {
|
||
|
dom->attr_group.name = domain_name[dom->id];
|
||
|
dom->attr_group.attrs = private_attrs[dom->id];
|
||
|
sysfs_create_group(migov.kobj, &dom->attr_group);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
s32 exynos_migov_register_misc_device(void)
|
||
|
{
|
||
|
s32 ret;
|
||
|
|
||
|
migov.gov_fops.fops.owner = THIS_MODULE;
|
||
|
migov.gov_fops.fops.llseek = no_llseek;
|
||
|
migov.gov_fops.fops.read = migov_fops_read;
|
||
|
migov.gov_fops.fops.poll = migov_fops_poll;
|
||
|
migov.gov_fops.fops.unlocked_ioctl = migov_fops_ioctl;
|
||
|
migov.gov_fops.fops.compat_ioctl = migov_fops_ioctl;
|
||
|
migov.gov_fops.fops.release = migov_fops_release;
|
||
|
|
||
|
migov.gov_fops.miscdev.minor = MISC_DYNAMIC_MINOR;
|
||
|
migov.gov_fops.miscdev.name = "exynos-migov";
|
||
|
migov.gov_fops.miscdev.fops = &migov.gov_fops.fops;
|
||
|
|
||
|
ret = misc_register(&migov.gov_fops.miscdev);
|
||
|
if (ret) {
|
||
|
pr_err("exynos-migov couldn't register misc device!");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
s32 exynos_migov_register_domain(s32 id, struct domain_fn *fn, void *private_fn)
|
||
|
{
|
||
|
struct domain_data *dom = &migov.domains[id];
|
||
|
|
||
|
if (id >= NUM_OF_DOMAIN || (dom->enabled)) {
|
||
|
pr_err("migov: invalid id or duplicated register (id: %d)\n", id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (!fn) {
|
||
|
pr_err("migov: there is no callback address (id: %d)\n", id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dom->id = id;
|
||
|
dom->fn = fn;
|
||
|
|
||
|
if (init_domain_data(migov.dn, dom, private_fn)) {
|
||
|
pr_err("migov: failed to init domain data(id=%d)\n", id);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
dom->enabled = true;
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(exynos_migov_register_domain);
|
||
|
|
||
|
static s32 parse_migov_dt(struct device_node *dn)
|
||
|
{
|
||
|
char name[16];
|
||
|
s32 ret = 0;
|
||
|
s32 i;
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "window-period", &tsd.window_period);
|
||
|
ret |= of_property_read_s32(dn, "window-number", &tsd.window_number);
|
||
|
ret |= of_property_read_s32(dn, "active-pct-thr", &tsd.active_pct_thr);
|
||
|
ret |= of_property_read_s32(dn, "valid-freq-delta-pct", &tsd.valid_freq_delta_pct);
|
||
|
ret |= of_property_read_s32(dn, "min-sensitivity", &tsd.min_sensitivity);
|
||
|
ret |= of_property_read_s32(dn, "cpu-bottleneck-thr", &tsd.cpu_bottleneck_thr);
|
||
|
ret |= of_property_read_s32(dn, "gpu-bottleneck-thr", &tsd.gpu_bottleneck_thr);
|
||
|
ret |= of_property_read_s32(dn, "gpu-ar-bottleneck-thr", &tsd.gpu_ar_bottleneck_thr);
|
||
|
ret |= of_property_read_s32(dn, "mif-bottleneck-thr", &tsd.mif_bottleneck_thr);
|
||
|
ret |= of_property_read_s32(dn, "frame-src", &tsd.frame_src);
|
||
|
ret |= of_property_read_s32(dn, "max-fps", &tsd.max_fps);
|
||
|
ret |= of_property_read_s32(dn, "dt-ctrl-en", &tsd.dt_ctrl_en);
|
||
|
ret |= of_property_read_s32(dn, "dt-over-thr", &tsd.dt_over_thr);
|
||
|
ret |= of_property_read_s32(dn, "dt-under-thr", &tsd.dt_under_thr);
|
||
|
ret |= of_property_read_s32(dn, "dt-up-step", &tsd.dt_up_step);
|
||
|
ret |= of_property_read_s32(dn, "dt-down-step", &tsd.dt_down_step);
|
||
|
ret |= of_property_read_s32(dn, "dpat-upper-thr", &tsd.dpat_upper_thr);
|
||
|
ret |= of_property_read_s32(dn, "dpat-lower-thr", &tsd.dpat_lower_thr);
|
||
|
ret |= of_property_read_s32(dn, "dpat-lower-cnt-thr", &tsd.dpat_lower_cnt_thr);
|
||
|
ret |= of_property_read_s32(dn, "dpat-up-step", &tsd.dpat_up_step);
|
||
|
ret |= of_property_read_s32(dn, "dpat-down-step", &tsd.dpat_down_step);
|
||
|
ret |= of_property_read_s32(dn, "inc-perf-temp-thr", &tsd.inc_perf_temp_thr);
|
||
|
ret |= of_property_read_s32(dn, "inc-perf-power-thr", &tsd.inc_perf_power_thr);
|
||
|
ret |= of_property_read_s32(dn, "inc-perf-thr", &tsd.inc_perf_thr);
|
||
|
ret |= of_property_read_s32(dn, "dec-perf-thr", &tsd.dec_perf_thr);
|
||
|
ret |= of_property_read_s32(dn, "fragutil-thr", &migov.fragutil_upper_thr);
|
||
|
migov.fragutil_lower_thr = migov.fragutil_upper_thr * FRAGUTIL_THR_MARGIN / 100;
|
||
|
migov.fragutil_lower_thr = max(0, migov.fragutil_lower_thr);
|
||
|
ret |= of_property_read_s32(dn, "gpu-freq-thr", &migov.gpu_freq_thr);
|
||
|
ret |= of_property_read_s32(dn, "heavy-gpu-ms-thr", &migov.heavy_gpu_ms_thr);
|
||
|
ret |= of_property_read_s32(dn, "hp-minlock-fps-delta-pct-thr", &tsd.hp_minlock_fps_delta_pct_thr);
|
||
|
ret |= of_property_read_s32(dn, "hp-minlock-power-upper-thr", &tsd.hp_minlock_power_upper_thr);
|
||
|
ret |= of_property_read_s32(dn, "hp-minlock-power-lower-thr", &tsd.hp_minlock_power_lower_thr);
|
||
|
|
||
|
ret |= of_property_read_s32(dn, "dyn-mo-control", &tsd.dyn_mo_control);
|
||
|
migov.bts_idx = bts_get_scenindex("g3d_heavy");
|
||
|
migov.len_mo_id = of_property_count_u32_elems(dn, "mo-id");
|
||
|
if (migov.len_mo_id > 0) {
|
||
|
migov.mo_id = kzalloc(sizeof(int) * migov.len_mo_id, GFP_KERNEL);
|
||
|
if (!migov.mo_id)
|
||
|
return ret;
|
||
|
|
||
|
ret |= of_property_read_u32_array(dn, "mo-id", migov.mo_id, migov.len_mo_id);
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < 6; i++) {
|
||
|
snprintf(name, sizeof(name), "runtime-thr-%d", i);
|
||
|
ret |= of_property_read_s32(dn, name, &tsd.runtime_thr[i]);
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static s32 exynos_migov_suspend(struct device *dev)
|
||
|
{
|
||
|
if (migov.running) {
|
||
|
migov.game_mode = false;
|
||
|
migov.heavy_gpu_mode = false;
|
||
|
wakeup_polling_task();
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static s32 exynos_migov_resume(struct device *dev)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static s32 exynos_migov_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
s32 ret;
|
||
|
|
||
|
migov.disable = false;
|
||
|
migov.running = false;
|
||
|
|
||
|
migov.dn = pdev->dev.of_node;
|
||
|
if (!migov.dn) {
|
||
|
pr_err("migov: Failed to get device tree\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = parse_migov_dt(migov.dn);
|
||
|
if (ret) {
|
||
|
pr_err("migov: Failed to parse device tree\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
migov.kobj = &pdev->dev.kobj;
|
||
|
ret += sysfs_create_group(migov.kobj, &migov_attr_group);
|
||
|
ret += sysfs_create_group(migov.kobj, &control_attr_group);
|
||
|
if (ret) {
|
||
|
pr_err("migov: Failed to init sysfs\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
INIT_WORK(&migov.fps_work, migov_fps_change_work);
|
||
|
|
||
|
INIT_WORK(&migov.fragutil_work, migov_fragutil_change_work);
|
||
|
ret = gpu_dvfs_register_utilization_notifier(&migov_fragutil_change_notifier);
|
||
|
if (ret) {
|
||
|
pr_err("migov: Failed to register fragutil change notifier\n");
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
migov.running_event = PROFILE_INVALID;
|
||
|
init_waitqueue_head(&migov.wq);
|
||
|
mutex_init(&migov_dsd_lock);
|
||
|
exynos_migov_register_misc_device();
|
||
|
|
||
|
emstune_register_notifier(&migov_mode_update_notifier);
|
||
|
|
||
|
pr_info("migov: complete to probe migov\n");
|
||
|
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static const struct of_device_id exynos_migov_match[] = {
|
||
|
{ .compatible = "samsung,exynos-migov", },
|
||
|
{ },
|
||
|
};
|
||
|
MODULE_DEVICE_TABLE(of, exynos_migov_match);
|
||
|
|
||
|
static const struct dev_pm_ops exynos_migov_pm_ops = {
|
||
|
.suspend = exynos_migov_suspend,
|
||
|
.resume = exynos_migov_resume,
|
||
|
};
|
||
|
|
||
|
static struct platform_driver exynos_migov_driver = {
|
||
|
.probe = exynos_migov_probe,
|
||
|
.driver = {
|
||
|
.name = "exynos-migov",
|
||
|
.owner = THIS_MODULE,
|
||
|
.pm = &exynos_migov_pm_ops,
|
||
|
.of_match_table = exynos_migov_match,
|
||
|
},
|
||
|
};
|
||
|
|
||
|
static s32 exynos_migov_init(void)
|
||
|
{
|
||
|
return platform_driver_register(&exynos_migov_driver);
|
||
|
}
|
||
|
late_initcall(exynos_migov_init);
|
||
|
|
||
|
MODULE_DESCRIPTION("Exynos MIGOV");
|
||
|
MODULE_LICENSE("GPL");
|