blob: dd3b9c41cd372778b32ea9df88a39525cad3a799 [file] [log] [blame]
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/compiler.h>
#include <linux/gfp.h>
#include <asm/system.h>
#include <mach/hardware.h>
#include <linux/netdevice.h>
#include <linux/clk.h>
#define NR_FREQS 15
//#define C2K_CPUFREQ_DEBUG
#ifdef C2K_CPUFREQ_DEBUG
#define c2k_cpufreq_debug(fmt, arg...) printk(fmt, ##arg)
#else
#define c2k_cpufreq_debug(fmt, arg...) ;
#endif
extern struct cpufreq_governor cpufreq_gov_ondemand;
static struct cpufreq_frequency_table comcerto_clk_frqs[NR_FREQS + 1];
static int comcerto_verify_speed(struct cpufreq_policy *policy)
{
if (policy->cpu < 0)
return -EINVAL;
return cpufreq_frequency_table_verify(policy, comcerto_clk_frqs);
}
static unsigned int comcerto_getspeed(unsigned int cpu)
{
struct clk *clk_arm;
if (cpu < 0)
return -EINVAL;
c2k_cpufreq_debug ("%s: Get speed for cpu(%d)\n", __func__, cpu);
clk_arm = clk_get(NULL,"arm");
if (IS_ERR(clk_arm)) {
pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n",
PTR_ERR(clk_arm));
return PTR_ERR(clk_arm);
}
return clk_get_rate(clk_arm); /* we get freq in hz */
}
static int comcerto_set_target(struct cpufreq_policy *policy,
unsigned int target_freq,
unsigned int relation)
{
struct cpufreq_freqs freqs;
unsigned int index;
struct clk *clk_arm;
int ret;
int j = 0;
ret = cpufreq_frequency_table_target(policy, comcerto_clk_frqs, target_freq, relation, &index);
if (ret < 0)
return ret;
if (policy->cpu >= NR_CPUS) {
pr_debug("%s: couldn't limit to CPUs in this domain\n", \
__func__);
return -EAGAIN;
}
clk_arm = clk_get(NULL,"arm");
if (IS_ERR(clk_arm)) {
pr_err("%s: cpufreq: Unable to obtain ARMCLK: %ld\n", __func__,\
PTR_ERR(clk_arm));
return PTR_ERR(clk_arm);
}
freqs.old = comcerto_getspeed(policy->cpu);
if (freqs.old == target_freq)
{
c2k_cpufreq_debug ("%s: old freq (%d) equals new freq(%d).\n", \
__func__, freqs.old, target_freq);
return 0;
}
freqs.new = comcerto_clk_frqs[index].frequency;
c2k_cpufreq_debug("%s: Transition(cpu:%d) %d-%dHz\n", __func__,\
policy->cpu, freqs.old, freqs.new);
while (j < NR_CPUS)
{
if (!cpu_online(j))
{
j++;
continue;
}
freqs.cpu = j;
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
j++;
}
j = 0;
policy->cur = freqs.new;
ret = clk_set_rate(clk_arm, freqs.new);
if (ret < 0) {
pr_debug("cpufreq: Failed to set rate %dHz: %d\n",
freqs.new, ret);
j = 0;
goto err;
}
while (j < NR_CPUS)
{
if (!cpu_online(j))
{
j++;
continue;
}
freqs.cpu = j;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
j++;
}
c2k_cpufreq_debug("%s:Set actual frequency %luHz\n", __func__, \
clk_get_rate(clk_arm));
return 0;
err:
policy->cur = freqs.old;
freqs.new = freqs.old;
while (j < NR_CPUS)
{
if (!cpu_online(j))
{
j++;
continue;
}
freqs.cpu = j;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
j++;
}
return ret;
}
static int comcerto_init_table(int cpu)
{
int i;
struct clk *clk_arm;
struct clk *clk_pll;
if (cpu != 0)
return 0;
clk_arm = clk_get(NULL,"arm");
if (IS_ERR(clk_arm)) {
pr_err("cpufreq: Unable to obtain ARMCLK: %ld\n",
PTR_ERR(clk_arm));
return PTR_ERR(clk_arm);
}
clk_pll = clk_get_parent(clk_arm);
comcerto_clk_frqs[0].frequency = clk_get_rate(clk_arm);
comcerto_clk_frqs[0].index = clk_get_rate(clk_pll)/clk_get_rate(clk_arm);
c2k_cpufreq_debug("\n\n ### cpufreq Table ###\n");
c2k_cpufreq_debug ("comcerto_clk_frqs[0].index = %d, comcerto_clk_frqs[0].frequency = %d\n",\
comcerto_clk_frqs[0].index, comcerto_clk_frqs[0].frequency);
for (i = 1; comcerto_clk_frqs[0].index + i <= NR_FREQS; i++)
{
comcerto_clk_frqs[i].frequency = (comcerto_clk_frqs[0].frequency \
* comcerto_clk_frqs[0].index) / (comcerto_clk_frqs[0].index + i);
comcerto_clk_frqs[i].index = comcerto_clk_frqs[0].index + i;
c2k_cpufreq_debug ("comcerto_clk_frqs[%d].index = %d, comcerto_clk_frqs[%d].frequency = %d\n",\
i, comcerto_clk_frqs[i].index, i, comcerto_clk_frqs[i].frequency);
}
comcerto_clk_frqs[i].frequency = CPUFREQ_TABLE_END;
return 0;
}
static int comcerto_cpu_init(struct cpufreq_policy *policy)
{
comcerto_init_table(policy->cpu);
cpufreq_frequency_table_cpuinfo(policy, comcerto_clk_frqs);
cpufreq_frequency_table_get_attr(comcerto_clk_frqs, policy->cpu);
/* PLL stabalisation time */
policy->cpuinfo.transition_latency = 10000; /* 10us */
/* Current Frequency */
policy->cur = comcerto_clk_frqs[0].frequency;
/* Governor used */
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
return 0;
}
static struct freq_attr *comcerto_cpufreq_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
static struct cpufreq_driver comcerto_driver = {
.name = "comcerto",
.init = comcerto_cpu_init,
.verify = comcerto_verify_speed,
.target = comcerto_set_target,
.get = comcerto_getspeed,
.attr = comcerto_cpufreq_attr,
.flags = CPUFREQ_STICKY,
.owner = THIS_MODULE,
};
static int __init comcerto_cpufreq_init(void)
{
printk ("Registering CPUFreq(%s)\n", comcerto_driver.name);
return cpufreq_register_driver(&comcerto_driver);
}
static void __exit comcerto_cpufreq_exit(void)
{
cpufreq_unregister_driver(&comcerto_driver);
}
MODULE_AUTHOR ("Satendra Pratap <satendra.pratap@gmail.com>");
MODULE_DESCRIPTION ("CPUFreq driver for Mindspeed's C2000 SoC");
MODULE_LICENSE ("GPL");
late_initcall(comcerto_cpufreq_init);
module_exit(comcerto_cpufreq_exit);