#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");