blob: bb523b081158453223dd240c6c8194c37dce4fac [file] [log] [blame]
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/cpufreq.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/err.h>
#include <linux/clk.h>
#define NUM_CPUS 2
static struct clk* p7_cpu_clk;
static struct cpufreq_frequency_table freq_table[] = {
{ 0, 780000 },
{ 1, 390000 },
{ 2, 195000 },
{ 3, 97500 },
{ 4, CPUFREQ_TABLE_END },
};
static int p7_verify_speed(struct cpufreq_policy *policy)
{
return cpufreq_frequency_table_verify(policy, freq_table);
}
static unsigned int p7_getspeed(unsigned int cpu)
{
unsigned long rate;
if (cpu >= NR_CPUS)
return 0;
rate = clk_get_rate(p7_cpu_clk) / 1000;
return rate;
}
static int p7_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
int ret;
unsigned int i;
unsigned int freq;
struct cpufreq_freqs freqs;
cpufreq_frequency_table_target(policy, freq_table, target_freq,
relation, &i);
freqs.new = freq_table[i].frequency;
freqs.old = p7_getspeed(policy->cpu);
freqs.cpu = policy->cpu;
if (freqs.old == freqs.new && policy->cur == freqs.new)
return 0;
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
}
freq = freqs.new * 1000;
pr_debug("%s: %u MHz --> %u MHz\n", __func__,
freqs.old / 1000, freqs.new / 1000);
ret = clk_set_rate(p7_cpu_clk, freq);
freqs.new = freq / 1000;
for_each_cpu(i, policy->cpus) {
freqs.cpu = i;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
}
return ret;
}
static int p7_cpu_init(struct cpufreq_policy *policy)
{
int i;
struct clk *p7_sys_clk;
unsigned long rate;
p7_sys_clk = clk_get_sys("sys_clk", NULL);
if (IS_ERR(p7_sys_clk)) {
pr_err("cpufreq-p7: failed to get sys_clk\n");
return -EINVAL;
}
rate = clk_get_rate(p7_sys_clk);
rate /= 1000;
clk_put(p7_sys_clk);
/*
* (From P7 UserManual)
* cpu_clk / sys_clk
* This clocks are generated from fast_clk, and can be dynamically
* divided. So the only thing needed to do is to configure the division
* ratio. For now, the recommended ratio are sys_clk=fast_clk/2 and
* cpu_clk=fast_clk or cpu_clk=fast_clk/2. Other ratio requires extra
* care. This is done in SYSTEM_CLKGEN_CPU_DIV and SYSTEM_CLKGEN_SYS_DIV
*
* Note : sys_clk frequency must not be greater than cpu_clk frequency.
*/
for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++)
{
if (freq_table[i].frequency < rate)
{
freq_table[i].frequency = CPUFREQ_TABLE_END;
break;
}
}
cpufreq_frequency_table_cpuinfo(policy, freq_table);
cpufreq_frequency_table_get_attr(freq_table, policy->cpu);
policy->min = policy->cpuinfo.min_freq;
policy->max = policy->cpuinfo.max_freq;
policy->cur = p7_getspeed(policy->cpu);
/*
* Both processors share the voltage and clock. So both CPUs
* needs to be scaled together and hence needs software
* co-ordination. Use cpufreq affected_cpus interface to
* handle this scenario.
*/
policy->shared_type = CPUFREQ_SHARED_TYPE_ANY;
cpumask_setall(policy->cpus);
p7_cpu_clk = clk_get_sys("cpu_clk", NULL);
if (IS_ERR(p7_cpu_clk)) {
pr_err("cpufreq-p7: failed to get cpu_clk\n");
return -EINVAL;
}
return 0;
}
static int p7_cpu_exit(struct cpufreq_policy *policy)
{
clk_put(p7_cpu_clk);
return 0;
}
static struct freq_attr *p7_cpufreq_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
static struct cpufreq_driver p7_cpufreq_driver = {
.flags = CPUFREQ_STICKY,
.verify = p7_verify_speed,
.target = p7_target,
.get = p7_getspeed,
.init = p7_cpu_init,
.exit = p7_cpu_exit,
.name = "p7",
.attr = p7_cpufreq_attr,
};
static int __init p7_cpufreq_init(void)
{
return cpufreq_register_driver(&p7_cpufreq_driver);
}
static void __exit p7_cpufreq_exit(void)
{
cpufreq_unregister_driver(&p7_cpufreq_driver);
}
MODULE_AUTHOR("Aurelien Lefebvre <aurelien.lefebvre@parrot.com>");
MODULE_DESCRIPTION("cpufreq driver for p7");
MODULE_LICENSE("GPL");
module_init(p7_cpufreq_init);
module_exit(p7_cpufreq_exit);