mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-26 02:39:48 +00:00
![Viresh Kumar](/assets/img/avatar_default.png)
PRECHANGE and POSTCHANGE notifiers must be called in groups, i.e either both should be called or both shouldn't be. In case we have started PRECHANGE notifier and found an error, we must call POSTCHANGE notifier with freqs.new = freqs.old to guarantee that sequence of calling notifiers is complete. This patch fixes it. This also removes code setting policy->cur as this is also done by POSTCHANGE notifier. Signed-off-by: Viresh Kumar <viresh.kumar@linaro.org>
271 lines
7.1 KiB
C
271 lines
7.1 KiB
C
/*
|
|
* ARM big.LITTLE Platforms CPUFreq support
|
|
*
|
|
* Copyright (C) 2013 ARM Ltd.
|
|
* Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com>
|
|
*
|
|
* Copyright (C) 2013 Linaro.
|
|
* Viresh Kumar <viresh.kumar@linaro.org>
|
|
*
|
|
* 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.
|
|
*
|
|
* This program is distributed "as is" WITHOUT ANY WARRANTY of any
|
|
* kind, whether express or implied; without even the implied warranty
|
|
* of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/export.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/opp.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/topology.h>
|
|
#include <linux/types.h>
|
|
|
|
#include "arm_big_little.h"
|
|
|
|
/* Currently we support only two clusters */
|
|
#define MAX_CLUSTERS 2
|
|
|
|
static struct cpufreq_arm_bL_ops *arm_bL_ops;
|
|
static struct clk *clk[MAX_CLUSTERS];
|
|
static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS];
|
|
static atomic_t cluster_usage[MAX_CLUSTERS] = {ATOMIC_INIT(0), ATOMIC_INIT(0)};
|
|
|
|
static unsigned int bL_cpufreq_get(unsigned int cpu)
|
|
{
|
|
u32 cur_cluster = cpu_to_cluster(cpu);
|
|
|
|
return clk_get_rate(clk[cur_cluster]) / 1000;
|
|
}
|
|
|
|
/* Validate policy frequency range */
|
|
static int bL_cpufreq_verify_policy(struct cpufreq_policy *policy)
|
|
{
|
|
u32 cur_cluster = cpu_to_cluster(policy->cpu);
|
|
|
|
return cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]);
|
|
}
|
|
|
|
/* Set clock frequency */
|
|
static int bL_cpufreq_set_target(struct cpufreq_policy *policy,
|
|
unsigned int target_freq, unsigned int relation)
|
|
{
|
|
struct cpufreq_freqs freqs;
|
|
u32 cpu = policy->cpu, freq_tab_idx, cur_cluster;
|
|
int ret = 0;
|
|
|
|
cur_cluster = cpu_to_cluster(policy->cpu);
|
|
|
|
freqs.old = bL_cpufreq_get(policy->cpu);
|
|
|
|
/* Determine valid target frequency using freq_table */
|
|
cpufreq_frequency_table_target(policy, freq_table[cur_cluster],
|
|
target_freq, relation, &freq_tab_idx);
|
|
freqs.new = freq_table[cur_cluster][freq_tab_idx].frequency;
|
|
|
|
pr_debug("%s: cpu: %d, cluster: %d, oldfreq: %d, target freq: %d, new freq: %d\n",
|
|
__func__, cpu, cur_cluster, freqs.old, target_freq,
|
|
freqs.new);
|
|
|
|
if (freqs.old == freqs.new)
|
|
return 0;
|
|
|
|
cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE);
|
|
|
|
ret = clk_set_rate(clk[cur_cluster], freqs.new * 1000);
|
|
if (ret) {
|
|
pr_err("clk_set_rate failed: %d\n", ret);
|
|
freqs.new = freqs.old;
|
|
}
|
|
|
|
cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void put_cluster_clk_and_freq_table(struct device *cpu_dev)
|
|
{
|
|
u32 cluster = cpu_to_cluster(cpu_dev->id);
|
|
|
|
if (!atomic_dec_return(&cluster_usage[cluster])) {
|
|
clk_put(clk[cluster]);
|
|
opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]);
|
|
dev_dbg(cpu_dev, "%s: cluster: %d\n", __func__, cluster);
|
|
}
|
|
}
|
|
|
|
static int get_cluster_clk_and_freq_table(struct device *cpu_dev)
|
|
{
|
|
u32 cluster = cpu_to_cluster(cpu_dev->id);
|
|
char name[14] = "cpu-cluster.";
|
|
int ret;
|
|
|
|
if (atomic_inc_return(&cluster_usage[cluster]) != 1)
|
|
return 0;
|
|
|
|
ret = arm_bL_ops->init_opp_table(cpu_dev);
|
|
if (ret) {
|
|
dev_err(cpu_dev, "%s: init_opp_table failed, cpu: %d, err: %d\n",
|
|
__func__, cpu_dev->id, ret);
|
|
goto atomic_dec;
|
|
}
|
|
|
|
ret = opp_init_cpufreq_table(cpu_dev, &freq_table[cluster]);
|
|
if (ret) {
|
|
dev_err(cpu_dev, "%s: failed to init cpufreq table, cpu: %d, err: %d\n",
|
|
__func__, cpu_dev->id, ret);
|
|
goto atomic_dec;
|
|
}
|
|
|
|
name[12] = cluster + '0';
|
|
clk[cluster] = clk_get_sys(name, NULL);
|
|
if (!IS_ERR(clk[cluster])) {
|
|
dev_dbg(cpu_dev, "%s: clk: %p & freq table: %p, cluster: %d\n",
|
|
__func__, clk[cluster], freq_table[cluster],
|
|
cluster);
|
|
return 0;
|
|
}
|
|
|
|
dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d, cluster: %d\n",
|
|
__func__, cpu_dev->id, cluster);
|
|
ret = PTR_ERR(clk[cluster]);
|
|
opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]);
|
|
|
|
atomic_dec:
|
|
atomic_dec(&cluster_usage[cluster]);
|
|
dev_err(cpu_dev, "%s: Failed to get data for cluster: %d\n", __func__,
|
|
cluster);
|
|
return ret;
|
|
}
|
|
|
|
/* Per-CPU initialization */
|
|
static int bL_cpufreq_init(struct cpufreq_policy *policy)
|
|
{
|
|
u32 cur_cluster = cpu_to_cluster(policy->cpu);
|
|
struct device *cpu_dev;
|
|
int ret;
|
|
|
|
cpu_dev = get_cpu_device(policy->cpu);
|
|
if (!cpu_dev) {
|
|
pr_err("%s: failed to get cpu%d device\n", __func__,
|
|
policy->cpu);
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = get_cluster_clk_and_freq_table(cpu_dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = cpufreq_frequency_table_cpuinfo(policy, freq_table[cur_cluster]);
|
|
if (ret) {
|
|
dev_err(cpu_dev, "CPU %d, cluster: %d invalid freq table\n",
|
|
policy->cpu, cur_cluster);
|
|
put_cluster_clk_and_freq_table(cpu_dev);
|
|
return ret;
|
|
}
|
|
|
|
cpufreq_frequency_table_get_attr(freq_table[cur_cluster], policy->cpu);
|
|
|
|
if (arm_bL_ops->get_transition_latency)
|
|
policy->cpuinfo.transition_latency =
|
|
arm_bL_ops->get_transition_latency(cpu_dev);
|
|
else
|
|
policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
|
|
|
|
policy->cur = bL_cpufreq_get(policy->cpu);
|
|
|
|
cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu));
|
|
|
|
dev_info(cpu_dev, "%s: CPU %d initialized\n", __func__, policy->cpu);
|
|
return 0;
|
|
}
|
|
|
|
static int bL_cpufreq_exit(struct cpufreq_policy *policy)
|
|
{
|
|
struct device *cpu_dev;
|
|
|
|
cpu_dev = get_cpu_device(policy->cpu);
|
|
if (!cpu_dev) {
|
|
pr_err("%s: failed to get cpu%d device\n", __func__,
|
|
policy->cpu);
|
|
return -ENODEV;
|
|
}
|
|
|
|
put_cluster_clk_and_freq_table(cpu_dev);
|
|
dev_dbg(cpu_dev, "%s: Exited, cpu: %d\n", __func__, policy->cpu);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Export freq_table to sysfs */
|
|
static struct freq_attr *bL_cpufreq_attr[] = {
|
|
&cpufreq_freq_attr_scaling_available_freqs,
|
|
NULL,
|
|
};
|
|
|
|
static struct cpufreq_driver bL_cpufreq_driver = {
|
|
.name = "arm-big-little",
|
|
.flags = CPUFREQ_STICKY,
|
|
.verify = bL_cpufreq_verify_policy,
|
|
.target = bL_cpufreq_set_target,
|
|
.get = bL_cpufreq_get,
|
|
.init = bL_cpufreq_init,
|
|
.exit = bL_cpufreq_exit,
|
|
.have_governor_per_policy = true,
|
|
.attr = bL_cpufreq_attr,
|
|
};
|
|
|
|
int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops)
|
|
{
|
|
int ret;
|
|
|
|
if (arm_bL_ops) {
|
|
pr_debug("%s: Already registered: %s, exiting\n", __func__,
|
|
arm_bL_ops->name);
|
|
return -EBUSY;
|
|
}
|
|
|
|
if (!ops || !strlen(ops->name) || !ops->init_opp_table) {
|
|
pr_err("%s: Invalid arm_bL_ops, exiting\n", __func__);
|
|
return -ENODEV;
|
|
}
|
|
|
|
arm_bL_ops = ops;
|
|
|
|
ret = cpufreq_register_driver(&bL_cpufreq_driver);
|
|
if (ret) {
|
|
pr_info("%s: Failed registering platform driver: %s, err: %d\n",
|
|
__func__, ops->name, ret);
|
|
arm_bL_ops = NULL;
|
|
} else {
|
|
pr_info("%s: Registered platform driver: %s\n", __func__,
|
|
ops->name);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(bL_cpufreq_register);
|
|
|
|
void bL_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops)
|
|
{
|
|
if (arm_bL_ops != ops) {
|
|
pr_err("%s: Registered with: %s, can't unregister, exiting\n",
|
|
__func__, arm_bL_ops->name);
|
|
return;
|
|
}
|
|
|
|
cpufreq_unregister_driver(&bL_cpufreq_driver);
|
|
pr_info("%s: Un-registered platform driver: %s\n", __func__,
|
|
arm_bL_ops->name);
|
|
arm_bL_ops = NULL;
|
|
}
|
|
EXPORT_SYMBOL_GPL(bL_cpufreq_unregister);
|