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 index) 57 { 58 unsigned int cpu = policy->cpu; 59 cpumask_t cpus_allowed; 60 unsigned int freq; 61 62 cpus_allowed = current->cpus_allowed; 63 set_cpus_allowed_ptr(current, cpumask_of(cpu)); 64 65 freq = 66 ((cpu_clock_freq / 1000) * 67 loongson2_clockmod_table[index].driver_data) / 8; 68 69 set_cpus_allowed_ptr(current, &cpus_allowed); 70 71 /* setting the cpu frequency */ 72 clk_set_rate(cpuclk, freq); 73 74 return 0; 75 } 76 77 static int loongson2_cpufreq_cpu_init(struct cpufreq_policy *policy) 78 { 79 int i; 80 unsigned long rate; 81 int ret; 82 83 cpuclk = clk_get(NULL, "cpu_clk"); 84 if (IS_ERR(cpuclk)) { 85 printk(KERN_ERR "cpufreq: couldn't get CPU clk\n"); 86 return PTR_ERR(cpuclk); 87 } 88 89 rate = cpu_clock_freq / 1000; 90 if (!rate) { 91 clk_put(cpuclk); 92 return -EINVAL; 93 } 94 95 /* clock table init */ 96 for (i = 2; 97 (loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END); 98 i++) 99 loongson2_clockmod_table[i].frequency = (rate * i) / 8; 100 101 ret = clk_set_rate(cpuclk, rate); 102 if (ret) { 103 clk_put(cpuclk); 104 return ret; 105 } 106 107 return cpufreq_generic_init(policy, &loongson2_clockmod_table[0], 0); 108 } 109 110 static int loongson2_cpufreq_exit(struct cpufreq_policy *policy) 111 { 112 cpufreq_frequency_table_put_attr(policy->cpu); 113 clk_put(cpuclk); 114 return 0; 115 } 116 117 static struct cpufreq_driver loongson2_cpufreq_driver = { 118 .name = "loongson2", 119 .init = loongson2_cpufreq_cpu_init, 120 .verify = cpufreq_generic_frequency_table_verify, 121 .target_index = loongson2_cpufreq_target, 122 .get = loongson2_cpufreq_get, 123 .exit = loongson2_cpufreq_exit, 124 .attr = cpufreq_generic_attr, 125 }; 126 127 static struct platform_device_id platform_device_ids[] = { 128 { 129 .name = "loongson2_cpufreq", 130 }, 131 {} 132 }; 133 134 MODULE_DEVICE_TABLE(platform, platform_device_ids); 135 136 static struct platform_driver platform_driver = { 137 .driver = { 138 .name = "loongson2_cpufreq", 139 .owner = THIS_MODULE, 140 }, 141 .id_table = platform_device_ids, 142 }; 143 144 /* 145 * This is the simple version of Loongson-2 wait, Maybe we need do this in 146 * interrupt disabled context. 147 */ 148 149 static DEFINE_SPINLOCK(loongson2_wait_lock); 150 151 static void loongson2_cpu_wait(void) 152 { 153 unsigned long flags; 154 u32 cpu_freq; 155 156 spin_lock_irqsave(&loongson2_wait_lock, flags); 157 cpu_freq = LOONGSON_CHIPCFG0; 158 LOONGSON_CHIPCFG0 &= ~0x7; /* Put CPU into wait mode */ 159 LOONGSON_CHIPCFG0 = cpu_freq; /* Restore CPU state */ 160 spin_unlock_irqrestore(&loongson2_wait_lock, flags); 161 local_irq_enable(); 162 } 163 164 static int __init cpufreq_init(void) 165 { 166 int ret; 167 168 /* Register platform stuff */ 169 ret = platform_driver_register(&platform_driver); 170 if (ret) 171 return ret; 172 173 pr_info("cpufreq: Loongson-2F CPU frequency driver.\n"); 174 175 cpufreq_register_notifier(&loongson2_cpufreq_notifier_block, 176 CPUFREQ_TRANSITION_NOTIFIER); 177 178 ret = cpufreq_register_driver(&loongson2_cpufreq_driver); 179 180 if (!ret && !nowait) { 181 saved_cpu_wait = cpu_wait; 182 cpu_wait = loongson2_cpu_wait; 183 } 184 185 return ret; 186 } 187 188 static void __exit cpufreq_exit(void) 189 { 190 if (!nowait && saved_cpu_wait) 191 cpu_wait = saved_cpu_wait; 192 cpufreq_unregister_driver(&loongson2_cpufreq_driver); 193 cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block, 194 CPUFREQ_TRANSITION_NOTIFIER); 195 196 platform_driver_unregister(&platform_driver); 197 } 198 199 module_init(cpufreq_init); 200 module_exit(cpufreq_exit); 201 202 module_param(nowait, uint, 0644); 203 MODULE_PARM_DESC(nowait, "Disable Loongson-2F specific wait"); 204 205 MODULE_AUTHOR("Yanhua <yanh@lemote.com>"); 206 MODULE_DESCRIPTION("cpufreq driver for Loongson2F"); 207 MODULE_LICENSE("GPL"); 208