| /* |
| * Frequency variant cpufreq driver |
| * |
| * Copyright (C) 2017 Samsung Electronics Co., Ltd |
| * Park Bumgyu <bumgyu.park@samsung.com> |
| */ |
| |
| #include <linux/cpufreq.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <trace/events/ems.h> |
| |
| #include "../sched.h" |
| #include "ems.h" |
| |
| /********************************************************************** |
| * common APIs * |
| **********************************************************************/ |
| struct freqvar_table { |
| int frequency; |
| int value; |
| }; |
| |
| static int freqvar_get_value(int freq, struct freqvar_table *table) |
| { |
| struct freqvar_table *pos = table; |
| int value = -EINVAL; |
| |
| for (; pos->frequency != CPUFREQ_TABLE_END; pos++) |
| if (freq == pos->frequency) { |
| value = pos->value; |
| break; |
| } |
| |
| return value; |
| } |
| |
| static int freqvar_get_table_size(struct cpufreq_policy *policy) |
| { |
| struct cpufreq_frequency_table *cpufreq_table, *pos; |
| int size = 0; |
| |
| cpufreq_table = policy->freq_table; |
| if (unlikely(!cpufreq_table)) { |
| pr_debug("%s: Unable to find frequency table\n", __func__); |
| return -ENOENT; |
| } |
| |
| cpufreq_for_each_valid_entry(pos, cpufreq_table) |
| size++; |
| |
| return size; |
| } |
| |
| static int freqvar_fill_frequency_table(struct cpufreq_policy *policy, |
| struct freqvar_table *table) |
| { |
| struct cpufreq_frequency_table *cpufreq_table, *pos; |
| int index; |
| |
| cpufreq_table = policy->freq_table; |
| if (unlikely(!cpufreq_table)) { |
| pr_debug("%s: Unable to find frequency table\n", __func__); |
| return -ENOENT; |
| } |
| |
| index = 0; |
| cpufreq_for_each_valid_entry(pos, cpufreq_table) { |
| table[index].frequency = pos->frequency; |
| index++; |
| } |
| table[index].frequency = CPUFREQ_TABLE_END; |
| |
| return 0; |
| } |
| |
| static int freqvar_update_table(unsigned int *src, int src_size, |
| struct freqvar_table *dst) |
| { |
| struct freqvar_table *pos, *last_pos = dst; |
| unsigned int value = 0, freq = 0; |
| int i; |
| |
| for (i = src_size - 1; i >= 0; i--) { |
| value = src[i]; |
| freq = (i <= 0) ? 0 : src[i - 1]; |
| |
| for (pos = last_pos; pos->frequency != CPUFREQ_TABLE_END; pos++) |
| if (pos->frequency >= freq) { |
| pos->value = value; |
| } else { |
| last_pos = pos; |
| break; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int freqvar_parse_value_dt(struct device_node *dn, const char *table_name, |
| struct freqvar_table *table) |
| { |
| int size, ret = 0; |
| unsigned int *temp; |
| |
| /* get the table from device tree source */ |
| size = of_property_count_u32_elems(dn, table_name); |
| if (size <= 0) |
| return size; |
| |
| temp = kzalloc(sizeof(unsigned int) * size, GFP_KERNEL); |
| if (!temp) |
| return -ENOMEM; |
| |
| ret = of_property_read_u32_array(dn, table_name, temp, size); |
| if (ret) |
| goto fail_parsing; |
| |
| freqvar_update_table(temp, size, table); |
| |
| fail_parsing: |
| kfree(temp); |
| return ret; |
| } |
| |
| static void freqvar_free(void *data) |
| { |
| if (data) |
| kfree(data); |
| } |
| |
| static unsigned int *get_tokenized_data(const char *buf, int *num_tokens) |
| { |
| const char *cp; |
| int i; |
| int ntokens = 1; |
| unsigned int *tokenized_data; |
| int err = -EINVAL; |
| |
| cp = buf; |
| while ((cp = strpbrk(cp + 1, " :"))) |
| ntokens++; |
| |
| if (!(ntokens & 0x1)) |
| goto err; |
| |
| tokenized_data = kmalloc(ntokens * sizeof(unsigned int), GFP_KERNEL); |
| if (!tokenized_data) { |
| err = -ENOMEM; |
| goto err; |
| } |
| |
| cp = buf; |
| i = 0; |
| while (i < ntokens) { |
| if (sscanf(cp, "%u", &tokenized_data[i++]) != 1) |
| goto err_kfree; |
| |
| cp = strpbrk(cp, " :"); |
| if (!cp) |
| break; |
| cp++; |
| } |
| |
| if (i != ntokens) |
| goto err_kfree; |
| |
| *num_tokens = ntokens; |
| return tokenized_data; |
| |
| err_kfree: |
| kfree(tokenized_data); |
| err: |
| return ERR_PTR(err); |
| } |
| |
| #define attr_freqvar(type, name, table) \ |
| static ssize_t freqvar_##name##_show(struct gov_attr_set *attr_set, char *buf) \ |
| { \ |
| struct cpufreq_policy *policy = sugov_get_attr_policy(attr_set); \ |
| struct freqvar_##type *data = per_cpu(freqvar_##type, policy->cpu); \ |
| struct freqvar_table *pos = data->table; \ |
| int ret = 0; \ |
| \ |
| for (; pos->frequency != CPUFREQ_TABLE_END; pos++) \ |
| ret += sprintf(buf + ret, "%8d ratio:%3d \n", \ |
| pos->frequency, pos->value); \ |
| \ |
| return ret; \ |
| } \ |
| \ |
| static ssize_t freqvar_##name##_store(struct gov_attr_set *attr_set, \ |
| const char *buf, size_t count) \ |
| { \ |
| struct cpufreq_policy *policy = sugov_get_attr_policy(attr_set); \ |
| struct freqvar_##type *data = per_cpu(freqvar_##type, policy->cpu); \ |
| struct freqvar_table *old_table = data->table; \ |
| int *new_table = NULL; \ |
| int ntokens; \ |
| \ |
| new_table = get_tokenized_data(buf, &ntokens); \ |
| if (IS_ERR(new_table)) \ |
| return PTR_RET(new_table); \ |
| \ |
| freqvar_update_table(new_table, ntokens, old_table); \ |
| kfree(new_table); \ |
| \ |
| return count; \ |
| } \ |
| |
| int sugov_sysfs_add_attr(struct cpufreq_policy *policy, const struct attribute *attr); |
| struct cpufreq_policy *sugov_get_attr_policy(struct gov_attr_set *attr_set); |
| |
| /********************************************************************** |
| * freqvar boost * |
| **********************************************************************/ |
| struct freqvar_boost { |
| struct freqvar_table *table; |
| unsigned int ratio; |
| unsigned long step_max_util; |
| }; |
| DEFINE_PER_CPU(struct freqvar_boost *, freqvar_boost); |
| |
| attr_freqvar(boost, boost, table); |
| static struct governor_attr freqvar_boost_attr = __ATTR_RW(freqvar_boost); |
| |
| unsigned long freqvar_boost_vector(int cpu, unsigned long util) |
| { |
| struct freqvar_boost *boost = per_cpu(freqvar_boost, cpu); |
| unsigned long boosted_util = 0; |
| |
| if (!boost || !boost->ratio) |
| return util; |
| |
| if (util > boost->step_max_util) { |
| trace_ems_freqvar_boost(cpu, boost->ratio, boost->step_max_util, util, 0); |
| return util; |
| } |
| |
| boosted_util = util * boost->ratio / 100; |
| |
| if (boost->step_max_util) |
| boosted_util = min_t(unsigned long, boosted_util, boost->step_max_util); |
| |
| trace_ems_freqvar_boost(cpu, boost->ratio, boost->step_max_util, util, boosted_util); |
| |
| return boosted_util; |
| } |
| |
| static void freqvar_boost_free(struct freqvar_boost *boost) |
| { |
| if (boost) |
| freqvar_free(boost->table); |
| |
| freqvar_free(boost); |
| } |
| |
| static struct |
| freqvar_boost *freqvar_boost_alloc(struct cpufreq_policy *policy) |
| { |
| struct freqvar_boost *boost; |
| int size; |
| |
| boost = kzalloc(sizeof(*boost), GFP_KERNEL); |
| if (!boost) |
| return NULL; |
| |
| size = freqvar_get_table_size(policy); |
| if (size <= 0) |
| goto fail_alloc; |
| |
| boost->table = kzalloc(sizeof(struct freqvar_table) * (size + 1), GFP_KERNEL); |
| if (!boost->table) |
| goto fail_alloc; |
| |
| return boost; |
| |
| fail_alloc: |
| freqvar_boost_free(boost); |
| return NULL; |
| } |
| |
| static void freqvar_boost_update(int cpu, int new_freq); |
| static int freqvar_boost_init(struct device_node *dn, const struct cpumask *mask) |
| { |
| struct freqvar_boost *boost; |
| struct cpufreq_policy *policy; |
| int cpu, ret = 0; |
| |
| policy = cpufreq_cpu_get(cpumask_first(mask)); |
| if (!policy) |
| return -ENODEV; |
| |
| boost = freqvar_boost_alloc(policy); |
| if (!boost) { |
| ret = -ENOMEM; |
| goto fail_init; |
| } |
| |
| ret = freqvar_fill_frequency_table(policy, boost->table); |
| if (ret) |
| goto fail_init; |
| |
| ret = freqvar_parse_value_dt(dn, "boost_table", boost->table); |
| if (ret) |
| goto fail_init; |
| |
| for_each_cpu(cpu, mask) |
| per_cpu(freqvar_boost, cpu) = boost; |
| |
| freqvar_boost_update(policy->cpu, policy->cur); |
| |
| ret = sugov_sysfs_add_attr(policy, &freqvar_boost_attr.attr); |
| if (ret) |
| goto fail_init; |
| |
| return 0; |
| |
| fail_init: |
| cpufreq_cpu_put(policy); |
| freqvar_boost_free(boost); |
| |
| return ret; |
| } |
| |
| /********************************************************************** |
| * freqvar rate limit * |
| **********************************************************************/ |
| struct freqvar_rate_limit { |
| struct freqvar_table *up_table; |
| struct freqvar_table *down_table; |
| struct freqvar_table *st_table; |
| int ratio; |
| }; |
| DEFINE_PER_CPU(struct freqvar_rate_limit *, freqvar_rate_limit); |
| |
| attr_freqvar(rate_limit, up_rate_limit, up_table); |
| attr_freqvar(rate_limit, down_rate_limit, down_table); |
| attr_freqvar(rate_limit, st_boost, st_table); |
| static struct governor_attr freqvar_up_rate_limit = __ATTR_RW(freqvar_up_rate_limit); |
| static struct governor_attr freqvar_down_rate_limit = __ATTR_RW(freqvar_down_rate_limit); |
| static struct governor_attr freqvar_st_boost = __ATTR_RW(freqvar_st_boost); |
| |
| void sugov_update_rate_limit_us(struct cpufreq_policy *policy, |
| int up_rate_limit_ms, int down_rate_limit_ms); |
| static void freqvar_rate_limit_update(int cpu, int new_freq) |
| { |
| struct freqvar_rate_limit *rate_limit; |
| int up_rate_limit, down_rate_limit; |
| struct cpufreq_policy *policy; |
| |
| rate_limit = per_cpu(freqvar_rate_limit, cpu); |
| if (!rate_limit) |
| return; |
| |
| up_rate_limit = freqvar_get_value(new_freq, rate_limit->up_table); |
| down_rate_limit = freqvar_get_value(new_freq, rate_limit->down_table); |
| |
| policy = cpufreq_cpu_get(cpu); |
| if (!policy) |
| return; |
| |
| sugov_update_rate_limit_us(policy, up_rate_limit, down_rate_limit); |
| |
| cpufreq_cpu_put(policy); |
| } |
| |
| static void freqvar_rate_limit_free(struct freqvar_rate_limit *rate_limit) |
| { |
| if (rate_limit) { |
| freqvar_free(rate_limit->up_table); |
| freqvar_free(rate_limit->down_table); |
| } |
| |
| freqvar_free(rate_limit); |
| } |
| |
| static struct |
| freqvar_rate_limit *freqvar_rate_limit_alloc(struct cpufreq_policy *policy) |
| { |
| struct freqvar_rate_limit *rate_limit; |
| int size; |
| |
| rate_limit = kzalloc(sizeof(*rate_limit), GFP_KERNEL); |
| if (!rate_limit) |
| return NULL; |
| |
| size = freqvar_get_table_size(policy); |
| if (size <= 0) |
| goto fail_alloc; |
| |
| rate_limit->up_table = kzalloc(sizeof(struct freqvar_table) |
| * (size + 1), GFP_KERNEL); |
| if (!rate_limit->up_table) |
| goto fail_alloc; |
| |
| rate_limit->down_table = kzalloc(sizeof(struct freqvar_table) |
| * (size + 1), GFP_KERNEL); |
| if (!rate_limit->down_table) |
| goto fail_alloc; |
| |
| rate_limit->st_table= kzalloc(sizeof(struct freqvar_table) |
| * (size + 1), GFP_KERNEL); |
| if (!rate_limit->st_table) |
| goto fail_alloc; |
| |
| return rate_limit; |
| |
| fail_alloc: |
| freqvar_rate_limit_free(rate_limit); |
| return NULL; |
| } |
| |
| static int freqvar_rate_limit_init(struct device_node *dn, const struct cpumask *mask) |
| { |
| struct freqvar_rate_limit *rate_limit; |
| struct cpufreq_policy *policy; |
| int cpu, ret = 0; |
| |
| policy = cpufreq_cpu_get(cpumask_first(mask)); |
| if (!policy) |
| return -ENODEV; |
| |
| rate_limit = freqvar_rate_limit_alloc(policy); |
| if (!rate_limit) { |
| ret = -ENOMEM; |
| goto fail_init; |
| } |
| |
| ret = freqvar_fill_frequency_table(policy, rate_limit->up_table); |
| if (ret) |
| goto fail_init; |
| |
| ret = freqvar_fill_frequency_table(policy, rate_limit->down_table); |
| if (ret) |
| goto fail_init; |
| |
| ret = freqvar_fill_frequency_table(policy, rate_limit->st_table); |
| if (ret) |
| goto fail_init; |
| |
| ret = freqvar_parse_value_dt(dn, "up_rate_limit_table", rate_limit->up_table); |
| if (ret) |
| goto fail_init; |
| |
| ret = freqvar_parse_value_dt(dn, "down_rate_limit_table", rate_limit->down_table); |
| if (ret) |
| goto fail_init; |
| |
| ret = freqvar_parse_value_dt(dn, "st_table", rate_limit->st_table); |
| if (ret) |
| goto fail_init; |
| |
| ret = sugov_sysfs_add_attr(policy, &freqvar_up_rate_limit.attr); |
| if (ret) |
| goto fail_init; |
| |
| ret = sugov_sysfs_add_attr(policy, &freqvar_down_rate_limit.attr); |
| if (ret) |
| goto fail_init; |
| |
| ret = sugov_sysfs_add_attr(policy, &freqvar_st_boost.attr); |
| if (ret) |
| goto fail_init; |
| |
| for_each_cpu(cpu, mask) |
| per_cpu(freqvar_rate_limit, cpu) = rate_limit; |
| |
| freqvar_rate_limit_update(policy->cpu, policy->cur); |
| |
| return 0; |
| |
| fail_init: |
| freqvar_rate_limit_free(rate_limit); |
| cpufreq_cpu_put(policy); |
| |
| return ret; |
| } |
| |
| static void freqvar_boost_update(int cpu, int new_freq) |
| { |
| struct freqvar_boost *boost; |
| struct freqvar_rate_limit *rate_limit; |
| |
| boost = per_cpu(freqvar_boost, cpu); |
| if (!boost) |
| return; |
| |
| boost->ratio = freqvar_get_value(new_freq, boost->table); |
| boost->step_max_util = get_freq_cap(cpu, new_freq, 0) * boost->ratio / 100; |
| |
| rate_limit = per_cpu(freqvar_rate_limit, cpu); |
| if (!rate_limit) |
| return; |
| rate_limit->ratio = freqvar_get_value(new_freq, rate_limit->st_table); |
| |
| } |
| |
| unsigned long freqvar_st_boost_vector(int cpu) |
| { |
| struct freqvar_rate_limit *boost = per_cpu(freqvar_rate_limit, cpu); |
| |
| if (!boost) |
| return 0; |
| |
| trace_ems_freqvar_st_boost(cpu, boost->ratio); |
| |
| return boost->ratio; |
| } |
| |
| /********************************************************************** |
| * cpufreq notifier callback * |
| **********************************************************************/ |
| static int freqvar_cpufreq_callback(struct notifier_block *nb, |
| unsigned long val, void *data) |
| { |
| struct cpufreq_freqs *freq = data; |
| |
| if (freq->flags & CPUFREQ_CONST_LOOPS) |
| return NOTIFY_OK; |
| |
| if (val != CPUFREQ_POSTCHANGE) |
| return NOTIFY_OK; |
| |
| freqvar_boost_update(freq->cpu, freq->new); |
| freqvar_rate_limit_update(freq->cpu, freq->new); |
| |
| return 0; |
| } |
| |
| static struct notifier_block freqvar_cpufreq_notifier = { |
| .notifier_call = freqvar_cpufreq_callback, |
| }; |
| |
| /********************************************************************** |
| * initialization * |
| **********************************************************************/ |
| static int __init freqvar_tune_init(void) |
| { |
| struct device_node *dn = NULL; |
| struct cpumask shared_mask; |
| const char *buf; |
| |
| while ((dn = of_find_node_by_type(dn, "freqvar-tune"))) { |
| /* |
| * shared-cpus includes cpus scaling at the sametime. |
| * it is called "sibling cpus" in the CPUFreq and |
| * masked on the realated_cpus of the policy |
| */ |
| if (of_property_read_string(dn, "shared-cpus", &buf)) |
| continue; |
| |
| cpumask_clear(&shared_mask); |
| cpulist_parse(buf, &shared_mask); |
| cpumask_and(&shared_mask, &shared_mask, cpu_possible_mask); |
| if (cpumask_weight(&shared_mask) == 0) |
| continue; |
| |
| freqvar_boost_init(dn, &shared_mask); |
| freqvar_rate_limit_init(dn, &shared_mask); |
| } |
| |
| cpufreq_register_notifier(&freqvar_cpufreq_notifier, |
| CPUFREQ_TRANSITION_NOTIFIER); |
| |
| return 0; |
| } |
| late_initcall(freqvar_tune_init); |