diff options
| -rw-r--r-- | drivers/soc/qcom/msm_performance.c | 199 |
1 files changed, 198 insertions, 1 deletions
diff --git a/drivers/soc/qcom/msm_performance.c b/drivers/soc/qcom/msm_performance.c index f307c664615e..56de8c59fb7f 100644 --- a/drivers/soc/qcom/msm_performance.c +++ b/drivers/soc/qcom/msm_performance.c @@ -16,6 +16,7 @@ #include <linux/cpu.h> #include <linux/moduleparam.h> #include <linux/cpumask.h> +#include <linux/cpufreq.h> #include <trace/events/power.h> @@ -37,6 +38,13 @@ struct delayed_work try_hotplug_work; static unsigned int num_online_managed(void); +/* To handle cpufreq min/max request */ +struct cpu_status { + unsigned int min; + unsigned int max; +}; +static DEFINE_PER_CPU(struct cpu_status, cpu_stats); + static int set_max_cpus(const char *buf, const struct kernel_param *kp) { unsigned int val; @@ -119,6 +127,192 @@ static unsigned int num_online_managed(void) } /* + * Userspace sends cpu#:min_freq_value to vote for min_freq_value as the new + * scaling_min. To withdraw its vote it needs to enter cpu#:0 + */ +static int set_cpu_min_freq(const char *buf, const struct kernel_param *kp) +{ + int i, j, ntokens = 0; + unsigned int val, cpu; + const char *cp = buf; + struct cpu_status *i_cpu_stats; + struct cpufreq_policy policy; + cpumask_var_t limit_mask; + int ret; + + while ((cp = strpbrk(cp + 1, " :"))) + ntokens++; + + /* CPU:value pair */ + if (!(ntokens % 2)) + return -EINVAL; + + cp = buf; + cpumask_clear(limit_mask); + for (i = 0; i < ntokens; i += 2) { + if (sscanf(cp, "%u:%u", &cpu, &val) != 2) + return -EINVAL; + if (cpu > num_present_cpus()) + return -EINVAL; + + i_cpu_stats = &per_cpu(cpu_stats, cpu); + + i_cpu_stats->min = val; + cpumask_set_cpu(cpu, limit_mask); + + cp = strnchr(cp, strlen(cp), ' '); + cp++; + } + + /* + * Since on synchronous systems policy is shared amongst multiple + * CPUs only one CPU needs to be updated for the limit to be + * reflected for the entire cluster. We can avoid updating the policy + * of other CPUs in the cluster once it is done for at least one CPU + * in the cluster + */ + get_online_cpus(); + for_each_cpu(i, limit_mask) { + i_cpu_stats = &per_cpu(cpu_stats, i); + + if (cpufreq_get_policy(&policy, i)) + continue; + + if (cpu_online(i) && (policy.min != i_cpu_stats->min)) { + ret = cpufreq_update_policy(i); + if (ret) + continue; + } + for_each_cpu(j, policy.related_cpus) + cpumask_clear_cpu(j, limit_mask); + } + put_online_cpus(); + + return 0; +} + +static int get_cpu_min_freq(char *buf, const struct kernel_param *kp) +{ + int cnt = 0, cpu; + + for_each_present_cpu(cpu) { + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, + "%d:%u ", cpu, per_cpu(cpu_stats, cpu).min); + } + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "\n"); + return cnt; +} + +static const struct kernel_param_ops param_ops_cpu_min_freq = { + .set = set_cpu_min_freq, + .get = get_cpu_min_freq, +}; +module_param_cb(cpu_min_freq, ¶m_ops_cpu_min_freq, NULL, 0644); + +/* + * Userspace sends cpu#:max_freq_value to vote for max_freq_value as the new + * scaling_max. To withdraw its vote it needs to enter cpu#:UINT_MAX + */ +static int set_cpu_max_freq(const char *buf, const struct kernel_param *kp) +{ + int i, j, ntokens = 0; + unsigned int val, cpu; + const char *cp = buf; + struct cpu_status *i_cpu_stats; + struct cpufreq_policy policy; + cpumask_var_t limit_mask; + int ret; + + while ((cp = strpbrk(cp + 1, " :"))) + ntokens++; + + /* CPU:value pair */ + if (!(ntokens % 2)) + return -EINVAL; + + cp = buf; + cpumask_clear(limit_mask); + for (i = 0; i < ntokens; i += 2) { + if (sscanf(cp, "%u:%u", &cpu, &val) != 2) + return -EINVAL; + if (cpu > num_present_cpus()) + return -EINVAL; + + i_cpu_stats = &per_cpu(cpu_stats, cpu); + + i_cpu_stats->max = val; + cpumask_set_cpu(cpu, limit_mask); + + cp = strnchr(cp, strlen(cp), ' '); + cp++; + } + + get_online_cpus(); + for_each_cpu(i, limit_mask) { + i_cpu_stats = &per_cpu(cpu_stats, i); + if (cpufreq_get_policy(&policy, i)) + continue; + + if (cpu_online(i) && (policy.max != i_cpu_stats->max)) { + ret = cpufreq_update_policy(i); + if (ret) + continue; + } + for_each_cpu(j, policy.related_cpus) + cpumask_clear_cpu(j, limit_mask); + } + put_online_cpus(); + + return 0; +} + +static int get_cpu_max_freq(char *buf, const struct kernel_param *kp) +{ + int cnt = 0, cpu; + + for_each_present_cpu(cpu) { + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, + "%d:%u ", cpu, per_cpu(cpu_stats, cpu).max); + } + cnt += snprintf(buf + cnt, PAGE_SIZE - cnt, "\n"); + return cnt; +} + +static const struct kernel_param_ops param_ops_cpu_max_freq = { + .set = set_cpu_max_freq, + .get = get_cpu_max_freq, +}; +module_param_cb(cpu_max_freq, ¶m_ops_cpu_max_freq, NULL, 0644); + +static int perf_adjust_notify(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct cpufreq_policy *policy = data; + unsigned int cpu = policy->cpu; + struct cpu_status *cpu_st = &per_cpu(cpu_stats, cpu); + unsigned int min = cpu_st->min, max = cpu_st->max; + + + if (val != CPUFREQ_ADJUST) + return NOTIFY_OK; + + pr_debug("msm_perf: CPU%u policy before: %u:%u kHz\n", cpu, + policy->min, policy->max); + pr_debug("msm_perf: CPU%u seting min:max %u:%u kHz\n", cpu, min, max); + + cpufreq_verify_within_limits(policy, min, max); + + pr_debug("msm_perf: CPU%u policy after: %u:%u kHz\n", cpu, + policy->min, policy->max); + + return NOTIFY_OK; +} + +static struct notifier_block perf_cpufreq_nb = { + .notifier_call = perf_adjust_notify, +}; + +/* * try_hotplug tries to online/offline cores based on the current requirement. * It loops through the currently managed CPUs and tries to online/offline * them until the max_cpus criteria is met. @@ -213,13 +407,16 @@ static struct notifier_block __refdata msm_performance_cpu_notifier = { static int __init msm_performance_init(void) { + int cpu; INIT_DELAYED_WORK(&try_hotplug_work, try_hotplug); mutex_init(&managed_cpus_lock); cpumask_clear(&managed_offline_cpus); + cpufreq_register_notifier(&perf_cpufreq_nb, CPUFREQ_POLICY_NOTIFIER); + for_each_present_cpu(cpu) + per_cpu(cpu_stats, cpu).max = UINT_MAX; register_cpu_notifier(&msm_performance_cpu_notifier); return 0; } late_initcall(msm_performance_init); - |
