1 /* 2 * Cpufreq driver for the loongson-2 processors 3 * 4 * The 2E revision of loongson processor not support this feature. 5 * 6 * Copyright (C) 2006 - 2008 Lemote Inc. & Insititute of Computing Technology 7 * Author: Yanhua, yanh@lemote.com 8 * 9 * This file is subject to the terms and conditions of the GNU General Public 10 * License. See the file "COPYING" in the main directory of this archive 11 * for more details. 12 */ 13 #include <linux/cpufreq.h> 14 #include <linux/module.h> 15 #include <linux/err.h> 16 #include <linux/sched.h> /* set_cpus_allowed() */ 17 #include <linux/delay.h> 18 #include <linux/platform_device.h> 19 20 #include <asm/clock.h> 21 #include <asm/idle.h> 22 23 #include <asm/mach-loongson/loongson.h> 24 25 static uint nowait; 26 27 static struct clk *cpuclk; 28 29 static void (*saved_cpu_wait) (void); 30 31 static int loongson2_cpu_freq_notifier(struct notifier_block *nb, 32 unsigned long val, void *data); 33 34 static struct notifier_block loongson2_cpufreq_notifier_block = { 35 .notifier_call = loongson2_cpu_freq_notifier 36 }; 37 38 static int loongson2_cpu_freq_notifier(struct notifier_block *nb, 39 unsigned long val, void *data) 40 { 41 if (val == CPUFREQ_POSTCHANGE) 42 current_cpu_data.udelay_val = loops_per_jiffy; 43 44 return 0; 45 } 46 47 static unsigned int loongson2_cpufreq_get(unsigned int cpu) 48 { 49 return clk_get_rate(cpuclk); 50 } 51 52 /* 53 * Here we notify other drivers of the proposed change and the final change. 54 */ 55 static int loongson2_cpufreq_target(struct cpufreq_policy *policy, 56 unsigned int target_freq, 57 unsigned int relation) 58 { 59 unsigned int cpu = policy->cpu; 60 unsigned int newstate = 0; 61 cpumask_t cpus_allowed; 62 struct cpufreq_freqs freqs; 63 unsigned int freq; 64 65 cpus_allowed = current->cpus_allowed; 66 set_cpus_allowed_ptr(current, cpumask_of(cpu)); 67 68 if (cpufreq_frequency_table_target 69 (policy, &loongson2_clockmod_table[0], target_freq, relation, 70 &newstate)) 71 return -EINVAL; 72 73 freq = 74 ((cpu_clock_freq / 1000) * 75 loongson2_clockmod_table[newstate].driver_data) / 8; 76 if (freq < policy->min || freq > policy->max) 77 return -EINVAL; 78 79 pr_debug("cpufreq: requested frequency %u Hz\n", target_freq * 1000); 80 81 freqs.old = loongson2_cpufreq_get(cpu); 82 freqs.new = freq; 83 freqs.flags = 0; 84 85 if (freqs.new == freqs.old) 86 return 0; 87 88 /* notifiers */ 89 cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); 90 91 set_cpus_allowed_ptr(current, &cpus_allowed); 92 93 /* setting the cpu frequency */ 94 clk_set_rate(cpuclk, freq); 95 96 /* notifiers */ 97 cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); 98 99 pr_debug("cpufreq: set frequency %u kHz\n", freq); 100 101 return 0; 102 } 103 104 static int loongson2_cpufreq_cpu_init(struct cpufreq_policy *policy) 105 { 106 int i; 107 unsigned long rate; 108 int ret; 109 110 cpuclk = clk_get(NULL, "cpu_clk"); 111 if (IS_ERR(cpuclk)) { 112 printk(KERN_ERR "cpufreq: couldn't get CPU clk\n"); 113 return PTR_ERR(cpuclk); 114 } 115 116 rate = cpu_clock_freq / 1000; 117 if (!rate) { 118 clk_put(cpuclk); 119 return -EINVAL; 120 } 121 ret = clk_set_rate(cpuclk, rate); 122 if (ret) { 123 clk_put(cpuclk); 124 return ret; 125 } 126 127 /* clock table init */ 128 for (i = 2; 129 (loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END); 130 i++) 131 loongson2_clockmod_table[i].frequency = (rate * i) / 8; 132 133 policy->cur = loongson2_cpufreq_get(policy->cpu); 134 135 cpufreq_frequency_table_get_attr(&loongson2_clockmod_table[0], 136 policy->cpu); 137 138 return cpufreq_frequency_table_cpuinfo(policy, 139 &loongson2_clockmod_table[0]); 140 } 141 142 static int loongson2_cpufreq_verify(struct cpufreq_policy *policy) 143 { 144 return cpufreq_frequency_table_verify(policy, 145 &loongson2_clockmod_table[0]); 146 } 147 148 static int loongson2_cpufreq_exit(struct cpufreq_policy *policy) 149 { 150 clk_put(cpuclk); 151 return 0; 152 } 153 154 static struct freq_attr *loongson2_table_attr[] = { 155 &cpufreq_freq_attr_scaling_available_freqs, 156 NULL, 157 }; 158 159 static struct cpufreq_driver loongson2_cpufreq_driver = { 160 .owner = THIS_MODULE, 161 .name = "loongson2", 162 .init = loongson2_cpufreq_cpu_init, 163 .verify = loongson2_cpufreq_verify, 164 .target = loongson2_cpufreq_target, 165 .get = loongson2_cpufreq_get, 166 .exit = loongson2_cpufreq_exit, 167 .attr = loongson2_table_attr, 168 }; 169 170 static struct platform_device_id platform_device_ids[] = { 171 { 172 .name = "loongson2_cpufreq", 173 }, 174 {} 175 }; 176 177 MODULE_DEVICE_TABLE(platform, platform_device_ids); 178 179 static struct platform_driver platform_driver = { 180 .driver = { 181 .name = "loongson2_cpufreq", 182 .owner = THIS_MODULE, 183 }, 184 .id_table = platform_device_ids, 185 }; 186 187 /* 188 * This is the simple version of Loongson-2 wait, Maybe we need do this in 189 * interrupt disabled context. 190 */ 191 192 static DEFINE_SPINLOCK(loongson2_wait_lock); 193 194 static void loongson2_cpu_wait(void) 195 { 196 unsigned long flags; 197 u32 cpu_freq; 198 199 spin_lock_irqsave(&loongson2_wait_lock, flags); 200 cpu_freq = LOONGSON_CHIPCFG0; 201 LOONGSON_CHIPCFG0 &= ~0x7; /* Put CPU into wait mode */ 202 LOONGSON_CHIPCFG0 = cpu_freq; /* Restore CPU state */ 203 spin_unlock_irqrestore(&loongson2_wait_lock, flags); 204 local_irq_enable(); 205 } 206 207 static int __init cpufreq_init(void) 208 { 209 int ret; 210 211 /* Register platform stuff */ 212 ret = platform_driver_register(&platform_driver); 213 if (ret) 214 return ret; 215 216 pr_info("cpufreq: Loongson-2F CPU frequency driver.\n"); 217 218 cpufreq_register_notifier(&loongson2_cpufreq_notifier_block, 219 CPUFREQ_TRANSITION_NOTIFIER); 220 221 ret = cpufreq_register_driver(&loongson2_cpufreq_driver); 222 223 if (!ret && !nowait) { 224 saved_cpu_wait = cpu_wait; 225 cpu_wait = loongson2_cpu_wait; 226 } 227 228 return ret; 229 } 230 231 static void __exit cpufreq_exit(void) 232 { 233 if (!nowait && saved_cpu_wait) 234 cpu_wait = saved_cpu_wait; 235 cpufreq_unregister_driver(&loongson2_cpufreq_driver); 236 cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block, 237 CPUFREQ_TRANSITION_NOTIFIER); 238 239 platform_driver_unregister(&platform_driver); 240 } 241 242 module_init(cpufreq_init); 243 module_exit(cpufreq_exit); 244 245 module_param(nowait, uint, 0644); 246 MODULE_PARM_DESC(nowait, "Disable Loongson-2F specific wait"); 247 248 MODULE_AUTHOR("Yanhua <yanh@lemote.com>"); 249 MODULE_DESCRIPTION("cpufreq driver for Loongson2F"); 250 MODULE_LICENSE("GPL"); 251