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 22#include <loongson.h> 23 24static uint nowait; 25 26static struct clk *cpuclk; 27 28static void (*saved_cpu_wait) (void); 29 30static int loongson2_cpu_freq_notifier(struct notifier_block *nb, 31 unsigned long val, void *data); 32 33static struct notifier_block loongson2_cpufreq_notifier_block = { 34 .notifier_call = loongson2_cpu_freq_notifier 35}; 36 37static int loongson2_cpu_freq_notifier(struct notifier_block *nb, 38 unsigned long val, void *data) 39{ 40 if (val == CPUFREQ_POSTCHANGE) 41 current_cpu_data.udelay_val = loops_per_jiffy; 42 43 return 0; 44} 45 46static unsigned int loongson2_cpufreq_get(unsigned int cpu) 47{ 48 return clk_get_rate(cpuclk); 49} 50 51/* 52 * Here we notify other drivers of the proposed change and the final change. 53 */ 54static int loongson2_cpufreq_target(struct cpufreq_policy *policy, 55 unsigned int target_freq, 56 unsigned int relation) 57{ 58 unsigned int cpu = policy->cpu; 59 unsigned int newstate = 0; 60 cpumask_t cpus_allowed; 61 struct cpufreq_freqs freqs; 62 unsigned int freq; 63 64 if (!cpu_online(cpu)) 65 return -ENODEV; 66 67 cpus_allowed = current->cpus_allowed; 68 set_cpus_allowed_ptr(current, cpumask_of(cpu)); 69 70 if (cpufreq_frequency_table_target 71 (policy, &loongson2_clockmod_table[0], target_freq, relation, 72 &newstate)) 73 return -EINVAL; 74 75 freq = 76 ((cpu_clock_freq / 1000) * 77 loongson2_clockmod_table[newstate].index) / 8; 78 if (freq < policy->min || freq > policy->max) 79 return -EINVAL; 80 81 pr_debug("cpufreq: requested frequency %u Hz\n", target_freq * 1000); 82 83 freqs.cpu = cpu; 84 freqs.old = loongson2_cpufreq_get(cpu); 85 freqs.new = freq; 86 freqs.flags = 0; 87 88 if (freqs.new == freqs.old) 89 return 0; 90 91 /* notifiers */ 92 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); 93 94 set_cpus_allowed_ptr(current, &cpus_allowed); 95 96 /* setting the cpu frequency */ 97 clk_set_rate(cpuclk, freq); 98 99 /* notifiers */ 100 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); 101 102 pr_debug("cpufreq: set frequency %u kHz\n", freq); 103 104 return 0; 105} 106 107static int loongson2_cpufreq_cpu_init(struct cpufreq_policy *policy) 108{ 109 int i; 110 111 if (!cpu_online(policy->cpu)) 112 return -ENODEV; 113 114 cpuclk = clk_get(NULL, "cpu_clk"); 115 if (IS_ERR(cpuclk)) { 116 printk(KERN_ERR "cpufreq: couldn't get CPU clk\n"); 117 return PTR_ERR(cpuclk); 118 } 119 120 cpuclk->rate = cpu_clock_freq / 1000; 121 if (!cpuclk->rate) 122 return -EINVAL; 123 124 /* clock table init */ 125 for (i = 2; 126 (loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END); 127 i++) 128 loongson2_clockmod_table[i].frequency = (cpuclk->rate * i) / 8; 129 130 policy->cur = loongson2_cpufreq_get(policy->cpu); 131 132 cpufreq_frequency_table_get_attr(&loongson2_clockmod_table[0], 133 policy->cpu); 134 135 return cpufreq_frequency_table_cpuinfo(policy, 136 &loongson2_clockmod_table[0]); 137} 138 139static int loongson2_cpufreq_verify(struct cpufreq_policy *policy) 140{ 141 return cpufreq_frequency_table_verify(policy, 142 &loongson2_clockmod_table[0]); 143} 144 145static int loongson2_cpufreq_exit(struct cpufreq_policy *policy) 146{ 147 clk_put(cpuclk); 148 return 0; 149} 150 151static struct freq_attr *loongson2_table_attr[] = { 152 &cpufreq_freq_attr_scaling_available_freqs, 153 NULL, 154}; 155 156static struct cpufreq_driver loongson2_cpufreq_driver = { 157 .owner = THIS_MODULE, 158 .name = "loongson2", 159 .init = loongson2_cpufreq_cpu_init, 160 .verify = loongson2_cpufreq_verify, 161 .target = loongson2_cpufreq_target, 162 .get = loongson2_cpufreq_get, 163 .exit = loongson2_cpufreq_exit, 164 .attr = loongson2_table_attr, 165}; 166 167static struct platform_device_id platform_device_ids[] = { 168 { 169 .name = "loongson2_cpufreq", 170 }, 171 {} 172}; 173 174MODULE_DEVICE_TABLE(platform, platform_device_ids); 175 176static struct platform_driver platform_driver = { 177 .driver = { 178 .name = "loongson2_cpufreq", 179 .owner = THIS_MODULE, 180 }, 181 .id_table = platform_device_ids, 182}; 183 184static int __init cpufreq_init(void) 185{ 186 int ret; 187 188 /* Register platform stuff */ 189 ret = platform_driver_register(&platform_driver); 190 if (ret) 191 return ret; 192 193 pr_info("cpufreq: Loongson-2F CPU frequency driver.\n"); 194 195 cpufreq_register_notifier(&loongson2_cpufreq_notifier_block, 196 CPUFREQ_TRANSITION_NOTIFIER); 197 198 ret = cpufreq_register_driver(&loongson2_cpufreq_driver); 199 200 if (!ret && !nowait) { 201 saved_cpu_wait = cpu_wait; 202 cpu_wait = loongson2_cpu_wait; 203 } 204 205 return ret; 206} 207 208static void __exit cpufreq_exit(void) 209{ 210 if (!nowait && saved_cpu_wait) 211 cpu_wait = saved_cpu_wait; 212 cpufreq_unregister_driver(&loongson2_cpufreq_driver); 213 cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block, 214 CPUFREQ_TRANSITION_NOTIFIER); 215 216 platform_driver_unregister(&platform_driver); 217} 218 219module_init(cpufreq_init); 220module_exit(cpufreq_exit); 221 222module_param(nowait, uint, 0644); 223MODULE_PARM_DESC(nowait, "Disable Loongson-2F specific wait"); 224 225MODULE_AUTHOR("Yanhua <yanh@lemote.com>"); 226MODULE_DESCRIPTION("cpufreq driver for Loongson2F"); 227MODULE_LICENSE("GPL"); 228