diff options
Diffstat (limited to 'drivers/cpufreq/arm_big_little.c')
| -rw-r--r-- | drivers/cpufreq/arm_big_little.c | 278 | 
1 files changed, 278 insertions, 0 deletions
diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c new file mode 100644 index 00000000000..dbdf677d2f3 --- /dev/null +++ b/drivers/cpufreq/arm_big_little.c @@ -0,0 +1,278 @@ +/* + * 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 int cpu_to_cluster(int cpu) +{ +	return topology_physical_package_id(cpu); +} + +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); +		return ret; +	} + +	policy->cur = freqs.new; + +	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, "CPU %d initialized\n", 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);  |