| /* |
| * 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); |
| |