/* * drivers/cpufreq/cpufreq_limit.c * * Copyright (c) 2019 Samsung Electronics Co., Ltd. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #define SCHED_BOOST_ENABLE 1 enum { SCHED_NO_BOOST = 0, SCHED_ALL_BOOST, SCHED_FG_BOOST, #ifdef CONFIG_PRIO_PINNED_BOOST SCHED_PINNED_BOOST, #endif SCHED_UNKNOWN_BOOST }; #if SCHED_BOOST_ENABLE extern int set_sched_boost_type(int type); #endif /* cpu frequency table from cpufreq dt parse */ static struct cpufreq_frequency_table *cpuftbl_L; static struct cpufreq_frequency_table *cpuftbl_b; static int ltl_max_freq_div; static struct cpu_ctrl_data *freq_to_set[DVFS_MAX_ID]; static unsigned int sched_boost_state[DVFS_MAX_ID]; DEFINE_MUTEX(cpufreq_limit_mutex); static void cpufreq_limit_set_sched_boost(int id, unsigned int val); struct cpufreq_limit_parameter { unsigned int ltl_cpu_start; unsigned int big_cpu_start; unsigned int ltl_max_freq; unsigned int ltl_min_lock_freq; unsigned int big_max_lock_freq; unsigned int ltl_divider; unsigned int over_limit; }; #ifdef CONFIG_CPU_FREQ_LTL_LIMIT #define MAX_LTL_LIMIT 5 struct freq_map { unsigned int in; unsigned int out; }; struct cpufreq_ltl_limit { struct freq_map ltl_limit_map[MAX_LTL_LIMIT]; unsigned int max_ltl_limit; unsigned int ltl_divider; }; #endif #if defined(CONFIG_MACH_MT6853) static struct cpufreq_limit_parameter param = { .ltl_cpu_start = 0, .big_cpu_start = 6, .ltl_max_freq = 2000000, .ltl_min_lock_freq = 1128000, .big_max_lock_freq = 725000, .ltl_divider = 4, .over_limit = -1, }; #elif defined(CONFIG_MACH_MT6833) static struct cpufreq_limit_parameter param = { .ltl_cpu_start = 0, .big_cpu_start = 6, .ltl_max_freq = 2000000, .ltl_min_lock_freq = 1115000, .big_max_lock_freq = 725000, .ltl_divider = 4, .over_limit = -1, }; #elif defined(CONFIG_MACH_MT6768) static struct cpufreq_limit_parameter param = { .ltl_cpu_start = 0, .big_cpu_start = 6, .ltl_max_freq = 1800000, .ltl_min_lock_freq = 1175000, .big_max_lock_freq = 850000, .ltl_divider = 4, .over_limit = -1, }; #elif defined(CONFIG_MACH_MT6765) static struct cpufreq_limit_parameter param = { .ltl_cpu_start = 4, .big_cpu_start = 0, .ltl_max_freq = 1800000, .ltl_min_lock_freq = 1138000, .big_max_lock_freq = 900000, .ltl_divider = 2, .over_limit = -1, }; #elif defined(CONFIG_MACH_MT6877) #ifdef CONFIG_CPU_FREQ_LTL_LIMIT static struct cpufreq_ltl_limit ltl_limit = { .ltl_limit_map = { {2000000, 2000000}, {1900000, 1903000}, {1660000, 1800000}, {1540000, 1703000}, {650000, 1600000}, }, .max_ltl_limit = 5, .ltl_divider = 4, }; #endif static struct cpufreq_limit_parameter param = { .ltl_cpu_start = 0, .big_cpu_start = 6, .ltl_max_freq = 2000000, .ltl_min_lock_freq = 1150000, .big_max_lock_freq = 910000, .ltl_divider = 4, .over_limit = -1, }; #else static struct cpufreq_limit_parameter param = { .ltl_cpu_start = 0, .big_cpu_start = 6, .ltl_max_freq = 1800000, .ltl_min_lock_freq = 1175000, .big_max_lock_freq = 850000, .ltl_divider = 4, .over_limit = -1, }; #endif static int little_policy_index = 0; static int big_policy_index = 1; #define CLUSTER_NUM 2 #define LITTLE little_policy_index #define BIG big_policy_index #define MAX(x, y) (((x) > (y) ? (x) : (y))) #define MIN(x, y) (((x) < (y) ? (x) : (y))) #ifdef CONFIG_CPU_FREQ_LTL_LIMIT static int get_ltl_limit(int freq) { int i; pr_info("%s: freq=%d\n", __func__, freq); for (i = 0; i < ltl_limit.max_ltl_limit; i++) if (freq >= ltl_limit.ltl_limit_map[i].in) return ltl_limit.ltl_limit_map[i].out; pr_info("%s: freq * ltl_limit.ltl_divider=%d\n", __func__, freq * ltl_limit.ltl_divider); return freq * ltl_limit.ltl_divider; } #endif void cpufreq_limit_set_table(int cpu, struct cpufreq_frequency_table *ftbl) { if (cpu == param.big_cpu_start) cpuftbl_b = ftbl; else if (cpu == param.ltl_cpu_start) cpuftbl_L = ftbl; } int set_freq_limit(unsigned int id, int freq) { pr_err("%s: id=%u freq=%d\n", __func__, id, freq); #ifdef CONFIG_CPU_FREQ_LTL_LIMIT int new_max = -1; #endif if (unlikely(!freq_to_set[id])) { pr_err("%s: cpufreq_limit driver uninitialization\n", __func__); return -ENODEV; } mutex_lock(&cpufreq_limit_mutex); if (freq == -1) { freq_to_set[id][LITTLE].min = freq; freq_to_set[id][BIG].min = freq; cpufreq_limit_set_sched_boost(id, SCHED_NO_BOOST); } else if (freq > ltl_max_freq_div) { freq_to_set[id][LITTLE].min = param.ltl_min_lock_freq; freq_to_set[id][BIG].min = freq; #ifdef CONFIG_PRIO_PINNED_BOOST cpufreq_limit_set_sched_boost(id, SCHED_PINNED_BOOST); #else cpufreq_limit_set_sched_boost(id, SCHED_ALL_BOOST); #endif } else { freq_to_set[id][LITTLE].min = freq * param.ltl_divider; freq_to_set[id][BIG].min = -1; cpufreq_limit_set_sched_boost(id, SCHED_NO_BOOST); } #ifdef CONFIG_CPU_FREQ_LTL_LIMIT if (id == DVFS_USER_ID || id == DVFS_TOUCH_ID) { if (freq_to_set[DVFS_USER_ID][BIG].max > 0) { if (freq > -1) { new_max = MAX((int)param.over_limit, (int)freq_to_set[DVFS_USER_ID][BIG].max); } else if (freq == -1) { new_max = freq_to_set[DVFS_USER_ID][BIG].max; } freq_to_set[DVFS_USER_ID][LITTLE].max = get_ltl_limit(new_max); } } #endif switch (id) { case DVFS_USER_ID: update_userlimit_cpu_freq(CPU_KIR_SEC_LIMIT, CLUSTER_NUM, freq_to_set[id]); break; case DVFS_TOUCH_ID: update_userlimit_cpu_freq(CPU_KIR_SEC_TOUCH, CLUSTER_NUM, freq_to_set[id]); break; case DVFS_FINGER_ID: update_userlimit_cpu_freq(CPU_KIR_SEC_FINGER, CLUSTER_NUM, freq_to_set[id]); break; } mutex_unlock(&cpufreq_limit_mutex); return 0; } /** * cpufreq_limit_get_table - fill the cpufreq table to support HMP * @buf a buf that has been requested to fill the cpufreq table */ static ssize_t cpufreq_limit_get_table(char *buf) { ssize_t len = 0; int i, k; int count_b = 0, count_l = 0; if (!cpuftbl_b || !cpuftbl_L) { pr_err("%s: Can not find cpufreq table\n", __func__); return len; } for (i = 0; cpuftbl_b[i].frequency != CPUFREQ_TABLE_END; i++) ; count_b = i; for (i = 0; cpuftbl_L[i].frequency != CPUFREQ_TABLE_END; i++) ; count_l = i; for (i = 0, k = 0; i < count_b && k < count_l; ) { if (cpuftbl_b[i].frequency > (unsigned int)(cpuftbl_L[k].frequency / param.ltl_divider)) len += sprintf(buf + len, "%u ", cpuftbl_b[i++].frequency); else if (cpuftbl_b[i].frequency < (unsigned int)(cpuftbl_L[k].frequency / param.ltl_divider)) len += sprintf(buf + len, "%u ", (unsigned int)(cpuftbl_L[k++].frequency / param.ltl_divider)); else { len += sprintf(buf + len, "%u ", cpuftbl_b[i].frequency); i++; k++; } } while (i < count_b) len += sprintf(buf + len, "%u ", cpuftbl_b[i++].frequency); while (k < count_l) len += sprintf(buf + len, "%u ", (unsigned int)(cpuftbl_L[k++].frequency / param.ltl_divider)); len = (len != 0) ? len - 1 : len; len += sprintf(buf + len, "\n"); pr_info("%s: %s", __func__, buf); return len; } #if SCHED_BOOST_ENABLE static void cpufreq_limit_set_sched_boost(int id, unsigned int val) { int i; int boost_val = SCHED_NO_BOOST; if (val >= SCHED_UNKNOWN_BOOST || val < SCHED_NO_BOOST) return; sched_boost_state[id] = val; for (i = 0; i < DVFS_MAX_ID; i++) { if (sched_boost_state[i] > boost_val) boost_val = sched_boost_state[i]; } set_sched_boost_type(boost_val); } #else static void cpufreq_limit_set_sched_boost(int id, unsigned int val) {} #endif #define cpufreq_limit_attr(_name) \ static struct kobj_attribute _name##_attr = { \ .attr = { \ .name = __stringify(_name), \ .mode = 0644, \ }, \ .show = _name##_show, \ .store = _name##_store, \ } #define cpufreq_limit_attr_ro(_name) \ static struct kobj_attribute _name##_attr = { \ .attr = { \ .name = __stringify(_name), \ .mode = 0444, \ }, \ .show = _name##_show, \ } static ssize_t cpufreq_table_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return cpufreq_limit_get_table(buf); } static ssize_t cpufreq_max_limit_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i, val = 0xFFFFFFF; mutex_lock(&cpufreq_limit_mutex); for (i = 0; i < DVFS_MAX_ID; i++) { if (i == DVFS_OVERLIMIT_ID) continue; #ifdef CONFIG_CPU_FREQ_LTL_LIMIT if (freq_to_set[i][BIG].max != -1 && freq_to_set[i][LITTLE].max != -1 ) val = MIN(freq_to_set[i][BIG].max, val); #else if (freq_to_set[i][BIG].max != -1) val = MIN(freq_to_set[i][BIG].max, val); if (freq_to_set[i][LITTLE].max != -1) val = MIN((int)(freq_to_set[i][LITTLE].max / param.ltl_divider), val); #endif } mutex_unlock(&cpufreq_limit_mutex); val = val != 0xFFFFFFF ? val : -1; return sprintf(buf, "%d\n", val); } static ssize_t cpufreq_max_limit_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val; ssize_t ret = -EINVAL; #ifdef CONFIG_CPU_FREQ_LTL_LIMIT int new_max = -1; #endif if (kstrtoint(buf, 10, &val)) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } mutex_lock(&cpufreq_limit_mutex); if (val == -1) { freq_to_set[DVFS_USER_ID][LITTLE].max = val; freq_to_set[DVFS_USER_ID][BIG].max = val; } else if (val > ltl_max_freq_div) { freq_to_set[DVFS_USER_ID][LITTLE].max = -1; freq_to_set[DVFS_USER_ID][BIG].max = val; #if CONFIG_CPU_FREQ_LTL_LIMIT new_max = MAX((int)param.over_limit, (int)freq_to_set[DVFS_USER_ID][BIG].max); freq_to_set[DVFS_USER_ID][LITTLE].max = get_ltl_limit(new_max); #endif } else { freq_to_set[DVFS_USER_ID][LITTLE].max = val * param.ltl_divider; freq_to_set[DVFS_USER_ID][BIG].max = param.big_max_lock_freq; } update_userlimit_cpu_freq(CPU_KIR_SEC_LIMIT, CLUSTER_NUM, freq_to_set[DVFS_USER_ID]); mutex_unlock(&cpufreq_limit_mutex); ret = count; out: return ret; } static ssize_t cpufreq_min_limit_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i, val = -1; mutex_lock(&cpufreq_limit_mutex); for (i = 0; i < DVFS_MAX_ID; i++) { if (freq_to_set[i][BIG].min != -1) val = MAX(freq_to_set[i][BIG].min, val); if (freq_to_set[i][LITTLE].min != -1) val = MAX((int)(freq_to_set[i][LITTLE].min / param.ltl_divider), val); } mutex_unlock(&cpufreq_limit_mutex); return sprintf(buf, "%d\n", val); } static ssize_t cpufreq_min_limit_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val; ssize_t ret = -EINVAL; if (kstrtoint(buf, 10, &val)) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } set_freq_limit(DVFS_USER_ID, val); ret = count; out: return ret; } static ssize_t over_limit_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return sprintf(buf, "%d\n", param.over_limit); } static ssize_t over_limit_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { unsigned int val; ssize_t ret = -EINVAL; ret = kstrtoint(buf, 10, &val); if (ret < 0) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } mutex_lock(&cpufreq_limit_mutex); param.over_limit = (unsigned int)val; if (val == -1) { freq_to_set[DVFS_OVERLIMIT_ID][LITTLE].max = val; freq_to_set[DVFS_OVERLIMIT_ID][BIG].max = val; } else if (val > ltl_max_freq_div) { freq_to_set[DVFS_OVERLIMIT_ID][LITTLE].max = -1; freq_to_set[DVFS_OVERLIMIT_ID][BIG].max = val; #ifdef CONFIG_CPU_FREQ_LTL_LIMIT freq_to_set[DVFS_OVERLIMIT_ID][LITTLE].max = get_ltl_limit(freq_to_set[DVFS_OVERLIMIT_ID][BIG].max); #endif } else { freq_to_set[DVFS_OVERLIMIT_ID][LITTLE].max = val * param.ltl_divider; freq_to_set[DVFS_OVERLIMIT_ID][BIG].max = param.big_max_lock_freq; } update_userlimit_cpu_freq(CPU_KIR_SEC_OVERLIMIT, CLUSTER_NUM, freq_to_set[DVFS_OVERLIMIT_ID]); mutex_unlock(&cpufreq_limit_mutex); ret = count; out: return ret; } cpufreq_limit_attr_ro(cpufreq_table); cpufreq_limit_attr(cpufreq_max_limit); cpufreq_limit_attr(cpufreq_min_limit); cpufreq_limit_attr(over_limit); static struct attribute *g[] = { &cpufreq_table_attr.attr, &cpufreq_max_limit_attr.attr, &cpufreq_min_limit_attr.attr, &over_limit_attr.attr, NULL, }; static const struct attribute_group limit_attr_group = { .attrs = g, }; #define show_one(_name, object) \ static ssize_t _name##_show \ (struct kobject *kobj, struct kobj_attribute *attr, char *buf) \ { \ return sprintf(buf, "%u\n", param.object); \ } \ show_one(ltl_cpu_start, ltl_cpu_start); show_one(big_cpu_start, big_cpu_start); show_one(ltl_max_freq, ltl_max_freq); show_one(ltl_min_lock_freq, ltl_min_lock_freq); show_one(big_max_lock_freq, big_max_lock_freq); show_one(ltl_divider, ltl_divider); static ssize_t cpufreq_limit_requests_show (struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int i; ssize_t count = 0; mutex_lock(&cpufreq_limit_mutex); for (i = 0; i < DVFS_MAX_ID; i++) { count += sprintf(buf + count, "ID[%d]: %d %d %d %d\n", i, freq_to_set[i][LITTLE].min, freq_to_set[i][BIG].min, freq_to_set[i][LITTLE].max, freq_to_set[i][BIG].max); } mutex_unlock(&cpufreq_limit_mutex); return count; } static ssize_t ltl_max_freq_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val; ssize_t ret = -EINVAL; if (kstrtoint(buf, 10, &val)) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } param.ltl_max_freq = val; ltl_max_freq_div = (int)(param.ltl_max_freq / param.ltl_divider); ret = count; out: return ret; } static ssize_t ltl_min_lock_freq_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val; ssize_t ret = -EINVAL; if (kstrtoint(buf, 10, &val)) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } param.ltl_min_lock_freq = val; ret = count; out: return ret; } static ssize_t big_max_lock_freq_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val; ssize_t ret = -EINVAL; if (kstrtoint(buf, 10, &val)) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } param.big_max_lock_freq = val; ret = count; out: return ret; } static ssize_t ltl_divider_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count) { int val; ssize_t ret = -EINVAL; if (kstrtoint(buf, 10, &val)) { pr_err("%s: Invalid cpufreq format\n", __func__); goto out; } param.ltl_divider = val; ltl_max_freq_div = (int)(param.ltl_max_freq / param.ltl_divider); ret = count; out: return ret; } cpufreq_limit_attr_ro(cpufreq_limit_requests); cpufreq_limit_attr_ro(ltl_cpu_start); cpufreq_limit_attr_ro(big_cpu_start); cpufreq_limit_attr(ltl_max_freq); cpufreq_limit_attr(ltl_min_lock_freq); cpufreq_limit_attr(big_max_lock_freq); cpufreq_limit_attr(ltl_divider); static struct attribute *limit_param_attributes[] = { &cpufreq_limit_requests_attr.attr, <l_cpu_start_attr.attr, &big_cpu_start_attr.attr, <l_max_freq_attr.attr, <l_min_lock_freq_attr.attr, &big_max_lock_freq_attr.attr, <l_divider_attr.attr, NULL, }; static struct attribute_group limit_param_attr_group = { .attrs = limit_param_attributes, .name = "cpufreq_limit", }; static int __init cpufreq_limit_init(void) { int i, ret = 0; if (param.ltl_cpu_start > param.big_cpu_start) { little_policy_index = 1; big_policy_index = 0; } for (i = 0; i < DVFS_MAX_ID; i++) { freq_to_set[i] = kcalloc(CLUSTER_NUM, sizeof(struct cpu_ctrl_data), GFP_KERNEL); if (!freq_to_set[i]) { pr_err("%s: failed, kcalloc freq_to_set fail\n", __func__); return -ENOMEM; } } for (i = 0; i < DVFS_MAX_ID; i++) { freq_to_set[i][LITTLE].min = -1; freq_to_set[i][LITTLE].max = -1; freq_to_set[i][BIG].min = -1; freq_to_set[i][BIG].max = -1; sched_boost_state[i] = SCHED_NO_BOOST; } ltl_max_freq_div = (int)(param.ltl_max_freq / param.ltl_divider); if (power_kobj) { ret = sysfs_create_group(power_kobj, &limit_attr_group); if (ret) pr_err("%s: failed %d\n", __func__, ret); } if (cpufreq_global_kobject) { ret = sysfs_create_group(cpufreq_global_kobject, &limit_param_attr_group); if (ret) pr_err("%s: failed\n", __func__, ret); } pr_info("%s: cpufreq_limit driver initialization done\n", __func__); return ret; } static void __exit cpufreq_limit_exit(void) { int i; sysfs_remove_group(power_kobj, &limit_param_attr_group); sysfs_remove_group(power_kobj, &limit_attr_group); for (i = 0; i < DVFS_MAX_ID; i++) kfree(freq_to_set[i]); } MODULE_DESCRIPTION("A driver to limit cpu frequency"); MODULE_LICENSE("GPL"); module_init(cpufreq_limit_init); module_exit(cpufreq_limit_exit);