blob: e9a87501da139d1e8584936f3c47f8719d1f999c [file] [log] [blame]
/*
* arch/arm/mach-kirkwood/cpufreq.c
*
* Clock scaling for Kirkwood SoC
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed "as is" without any
* warranty of any kind, whether express or implied.
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cpufreq.h>
#include <linux/io.h>
#include <linux/proc_fs.h>
#include <config/mvSysHwConfig.h>
#include "mvCommon.h"
#include "mvOs.h"
#include "ctrlEnv/mvCtrlEnvLib.h"
#include "boardEnv/mvBoardEnvLib.h"
#include "cpu/mvCpu.h"
#undef DEBUG
#ifdef DEBUG
#define DB(x) x
#else
#define DB(x)
#endif
extern struct proc_dir_entry *mv_pm_proc_entry;
enum kw_cpufreq_range {
KW_CPUFREQ_LOW = 0,
KW_CPUFREQ_HIGH = 1
};
static struct cpufreq_frequency_table kw_freqs[] = {
{ KW_CPUFREQ_LOW, 0 },
{ KW_CPUFREQ_HIGH, 0 },
{ 0, CPUFREQ_TABLE_END }
};
/*
* Power management function: set or unset powersave mode
*/
static inline void kw_set_powersave(u8 on)
{
DB(printk(KERN_DEBUG "cpufreq: Setting PowerSaveState to %s\n", on ? "on" : "off"));
if (on)
mvCtrlPwrSaveOn();
else
mvCtrlPwrSaveOff();
}
static int kw_cpufreq_verify(struct cpufreq_policy *policy)
{
if (unlikely(!cpu_online(policy->cpu)))
return -ENODEV;
return cpufreq_frequency_table_verify(policy, kw_freqs);
}
/*
* Get the current frequency for a given cpu.
*/
static unsigned int kw_cpufreq_get(unsigned int cpu)
{
unsigned int freq;
u32 reg;
if (unlikely(!cpu_online(cpu)))
return -ENODEV;
/* To get the current frequency, we have to check if
* the powersave mode is set. */
reg = MV_REG_READ(POWER_MNG_CTRL_REG);
if (reg & PMC_POWERSAVE_EN)
freq = kw_freqs[KW_CPUFREQ_LOW].frequency;
else
freq = kw_freqs[KW_CPUFREQ_HIGH].frequency;
return freq;
}
/*
* Set the frequency for a given cpu.
*/
static int kw_cpufreq_target(struct cpufreq_policy *policy,
unsigned int target_freq, unsigned int relation)
{
unsigned int index;
struct cpufreq_freqs freqs;
if (unlikely(!cpu_online(policy->cpu)))
return -ENODEV;
/* Lookup the next frequency */
if (unlikely(cpufreq_frequency_table_target(policy,
kw_freqs, target_freq, relation, &index)))
return -EINVAL;
freqs.old = policy->cur;
freqs.new = kw_freqs[index].frequency;
freqs.cpu = policy->cpu;
DB(printk(KERN_DEBUG "cpufreq: Setting CPU Frequency to %u KHz\n",freqs.new));
cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
/* Interruptions will be disabled in the low level power mode
* functions. */
if (index == KW_CPUFREQ_LOW)
kw_set_powersave(1);
else if (index == KW_CPUFREQ_HIGH)
kw_set_powersave(0);
else
return -EINVAL;
cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
return 0;
}
static int kw_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
if (unlikely(!cpu_online(policy->cpu)))
return -ENODEV;
kw_freqs[KW_CPUFREQ_HIGH].frequency = mvCpuPclkGet()/1000;
/* CPU low frequency is the DDR frequency. */
kw_freqs[KW_CPUFREQ_LOW].frequency = mvBoardSysClkGet()/1000;
printk(KERN_DEBUG
"cpufreq: High frequency: %uKHz - Low frequency: %uKHz\n",
kw_freqs[KW_CPUFREQ_HIGH].frequency,
kw_freqs[KW_CPUFREQ_LOW].frequency);
policy->cpuinfo.transition_latency = 5000;
policy->cur = kw_cpufreq_get(0);
policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
cpufreq_frequency_table_get_attr(kw_freqs, policy->cpu);
return cpufreq_frequency_table_cpuinfo(policy, kw_freqs);
}
static int kw_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
cpufreq_frequency_table_put_attr(policy->cpu);
return 0;
}
static struct freq_attr *kw_freq_attr[] = {
&cpufreq_freq_attr_scaling_available_freqs,
NULL,
};
static struct cpufreq_driver kw_freq_driver = {
.owner = THIS_MODULE,
.name = "kw_cpufreq",
.init = kw_cpufreq_cpu_init,
.verify = kw_cpufreq_verify,
.exit = kw_cpufreq_cpu_exit,
.target = kw_cpufreq_target,
.get = kw_cpufreq_get,
.attr = kw_freq_attr,
};
#ifdef CONFIG_MV_PMU_PROC
static int mv_cpu_freq_write(struct file *file, const char *buffer,
unsigned long count, void *data)
{
struct cpufreq_policy policy;
/* Reading / Writing from system controller internal registers */
if (!strncmp (buffer, "enable", strlen("enable"))) {
cpufreq_register_driver(&kw_freq_driver);
} else if (!strncmp (buffer, "disable", strlen("disable"))) {
cpufreq_get_policy(&policy, smp_processor_id());
kw_cpufreq_target(&policy, kw_freqs[KW_CPUFREQ_HIGH].frequency, CPUFREQ_RELATION_H);
cpufreq_unregister_driver(&kw_freq_driver);
} else if (!strncmp (buffer, "fast", strlen("fast"))) {
mvCtrlPwrSaveOff();
} else if (!strncmp (buffer, "slow", strlen("slow"))) {
mvCtrlPwrSaveOn();
}
return count;
}
static int mv_cpu_freq_read(char *buffer, char **buffer_location, off_t offset,
int buffer_length, int *zero, void *ptr)
{
if (offset > 0)
return 0;
return sprintf(buffer, "enable - Enable CPU-Freq framework.\n"
"disable - Disable CPU-Freq framework.\n"
"fast - Manually set the CPU to fast frequency mode (in Disable mode).\n"
"slow - Manually set the CPU to slow frequency mode (in Disable mode).\n");
}
#endif /* CONFIG_MV_PMU_PROC */
static int __init kw_cpufreq_init(void)
{
#ifdef CONFIG_MV_PMU_PROC
struct proc_dir_entry *cpu_freq_proc;
#endif /* CONFIG_MV_PMU_PROC */
if (MV_6601_DEV_ID == mvCtrlModelGet())
return 0;
printk(KERN_INFO "cpufreq: Init kirkwood cpufreq driver\n");
#ifdef CONFIG_MV_PMU_PROC
/* Create proc entry. */
cpu_freq_proc = create_proc_entry("cpu_freq", 0666, mv_pm_proc_entry);
cpu_freq_proc->read_proc = mv_cpu_freq_read;
cpu_freq_proc->write_proc = mv_cpu_freq_write;
cpu_freq_proc->nlink = 1;
#endif /* CONFIG_MV_PMU_PROC */
return cpufreq_register_driver(&kw_freq_driver);
}
static void __exit kw_cpufreq_exit(void)
{
cpufreq_unregister_driver(&kw_freq_driver);
}
MODULE_AUTHOR("Marvell Semiconductors ltd.");
MODULE_DESCRIPTION("CPU frequency scaling for Kirkwood SoC");
MODULE_LICENSE("GPL");
module_init(kw_cpufreq_init);
module_exit(kw_cpufreq_exit);