1 /* 2 * elanfreq: cpufreq driver for the AMD ELAN family 3 * 4 * (c) Copyright 2002 Robert Schwebel <r.schwebel@pengutronix.de> 5 * 6 * Parts of this code are (c) Sven Geggus <sven@geggus.net> 7 * 8 * All Rights Reserved. 9 * 10 * This program is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU General Public License 12 * as published by the Free Software Foundation; either version 13 * 2 of the License, or (at your option) any later version. 14 * 15 * 2002-02-13: - initial revision for 2.4.18-pre9 by Robert Schwebel 16 * 17 */ 18 19 #include <linux/kernel.h> 20 #include <linux/module.h> 21 #include <linux/init.h> 22 23 #include <linux/delay.h> 24 #include <linux/cpufreq.h> 25 26 #include <asm/msr.h> 27 #include <linux/timex.h> 28 #include <linux/io.h> 29 30 #define REG_CSCIR 0x22 /* Chip Setup and Control Index Register */ 31 #define REG_CSCDR 0x23 /* Chip Setup and Control Data Register */ 32 33 /* Module parameter */ 34 static int max_freq; 35 36 struct s_elan_multiplier { 37 int clock; /* frequency in kHz */ 38 int val40h; /* PMU Force Mode register */ 39 int val80h; /* CPU Clock Speed Register */ 40 }; 41 42 /* 43 * It is important that the frequencies 44 * are listed in ascending order here! 45 */ 46 static struct s_elan_multiplier elan_multiplier[] = { 47 {1000, 0x02, 0x18}, 48 {2000, 0x02, 0x10}, 49 {4000, 0x02, 0x08}, 50 {8000, 0x00, 0x00}, 51 {16000, 0x00, 0x02}, 52 {33000, 0x00, 0x04}, 53 {66000, 0x01, 0x04}, 54 {99000, 0x01, 0x05} 55 }; 56 57 static struct cpufreq_frequency_table elanfreq_table[] = { 58 {0, 1000}, 59 {1, 2000}, 60 {2, 4000}, 61 {3, 8000}, 62 {4, 16000}, 63 {5, 33000}, 64 {6, 66000}, 65 {7, 99000}, 66 {0, CPUFREQ_TABLE_END}, 67 }; 68 69 70 /** 71 * elanfreq_get_cpu_frequency: determine current cpu speed 72 * 73 * Finds out at which frequency the CPU of the Elan SOC runs 74 * at the moment. Frequencies from 1 to 33 MHz are generated 75 * the normal way, 66 and 99 MHz are called "Hyperspeed Mode" 76 * and have the rest of the chip running with 33 MHz. 77 */ 78 79 static unsigned int elanfreq_get_cpu_frequency(unsigned int cpu) 80 { 81 u8 clockspeed_reg; /* Clock Speed Register */ 82 83 local_irq_disable(); 84 outb_p(0x80, REG_CSCIR); 85 clockspeed_reg = inb_p(REG_CSCDR); 86 local_irq_enable(); 87 88 if ((clockspeed_reg & 0xE0) == 0xE0) 89 return 0; 90 91 /* Are we in CPU clock multiplied mode (66/99 MHz)? */ 92 if ((clockspeed_reg & 0xE0) == 0xC0) { 93 if ((clockspeed_reg & 0x01) == 0) 94 return 66000; 95 else 96 return 99000; 97 } 98 99 /* 33 MHz is not 32 MHz... */ 100 if ((clockspeed_reg & 0xE0) == 0xA0) 101 return 33000; 102 103 return (1<<((clockspeed_reg & 0xE0) >> 5)) * 1000; 104 } 105 106 107 /** 108 * elanfreq_set_cpu_frequency: Change the CPU core frequency 109 * @cpu: cpu number 110 * @freq: frequency in kHz 111 * 112 * This function takes a frequency value and changes the CPU frequency 113 * according to this. Note that the frequency has to be checked by 114 * elanfreq_validatespeed() for correctness! 115 * 116 * There is no return value. 117 */ 118 119 static void elanfreq_set_cpu_state(unsigned int state) 120 { 121 struct cpufreq_freqs freqs; 122 123 freqs.old = elanfreq_get_cpu_frequency(0); 124 freqs.new = elan_multiplier[state].clock; 125 freqs.cpu = 0; /* elanfreq.c is UP only driver */ 126 127 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); 128 129 printk(KERN_INFO "elanfreq: attempting to set frequency to %i kHz\n", 130 elan_multiplier[state].clock); 131 132 133 /* 134 * Access to the Elan's internal registers is indexed via 135 * 0x22: Chip Setup & Control Register Index Register (CSCI) 136 * 0x23: Chip Setup & Control Register Data Register (CSCD) 137 * 138 */ 139 140 /* 141 * 0x40 is the Power Management Unit's Force Mode Register. 142 * Bit 6 enables Hyperspeed Mode (66/100 MHz core frequency) 143 */ 144 145 local_irq_disable(); 146 outb_p(0x40, REG_CSCIR); /* Disable hyperspeed mode */ 147 outb_p(0x00, REG_CSCDR); 148 local_irq_enable(); /* wait till internal pipelines and */ 149 udelay(1000); /* buffers have cleaned up */ 150 151 local_irq_disable(); 152 153 /* now, set the CPU clock speed register (0x80) */ 154 outb_p(0x80, REG_CSCIR); 155 outb_p(elan_multiplier[state].val80h, REG_CSCDR); 156 157 /* now, the hyperspeed bit in PMU Force Mode Register (0x40) */ 158 outb_p(0x40, REG_CSCIR); 159 outb_p(elan_multiplier[state].val40h, REG_CSCDR); 160 udelay(10000); 161 local_irq_enable(); 162 163 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); 164 }; 165 166 167 /** 168 * elanfreq_validatespeed: test if frequency range is valid 169 * @policy: the policy to validate 170 * 171 * This function checks if a given frequency range in kHz is valid 172 * for the hardware supported by the driver. 173 */ 174 175 static int elanfreq_verify(struct cpufreq_policy *policy) 176 { 177 return cpufreq_frequency_table_verify(policy, &elanfreq_table[0]); 178 } 179 180 static int elanfreq_target(struct cpufreq_policy *policy, 181 unsigned int target_freq, 182 unsigned int relation) 183 { 184 unsigned int newstate = 0; 185 186 if (cpufreq_frequency_table_target(policy, &elanfreq_table[0], 187 target_freq, relation, &newstate)) 188 return -EINVAL; 189 190 elanfreq_set_cpu_state(newstate); 191 192 return 0; 193 } 194 195 196 /* 197 * Module init and exit code 198 */ 199 200 static int elanfreq_cpu_init(struct cpufreq_policy *policy) 201 { 202 struct cpuinfo_x86 *c = &cpu_data(0); 203 unsigned int i; 204 int result; 205 206 /* capability check */ 207 if ((c->x86_vendor != X86_VENDOR_AMD) || 208 (c->x86 != 4) || (c->x86_model != 10)) 209 return -ENODEV; 210 211 /* max freq */ 212 if (!max_freq) 213 max_freq = elanfreq_get_cpu_frequency(0); 214 215 /* table init */ 216 for (i = 0; (elanfreq_table[i].frequency != CPUFREQ_TABLE_END); i++) { 217 if (elanfreq_table[i].frequency > max_freq) 218 elanfreq_table[i].frequency = CPUFREQ_ENTRY_INVALID; 219 } 220 221 /* cpuinfo and default policy values */ 222 policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; 223 policy->cur = elanfreq_get_cpu_frequency(0); 224 225 result = cpufreq_frequency_table_cpuinfo(policy, elanfreq_table); 226 if (result) 227 return result; 228 229 cpufreq_frequency_table_get_attr(elanfreq_table, policy->cpu); 230 return 0; 231 } 232 233 234 static int elanfreq_cpu_exit(struct cpufreq_policy *policy) 235 { 236 cpufreq_frequency_table_put_attr(policy->cpu); 237 return 0; 238 } 239 240 241 #ifndef MODULE 242 /** 243 * elanfreq_setup - elanfreq command line parameter parsing 244 * 245 * elanfreq command line parameter. Use: 246 * elanfreq=66000 247 * to set the maximum CPU frequency to 66 MHz. Note that in 248 * case you do not give this boot parameter, the maximum 249 * frequency will fall back to _current_ CPU frequency which 250 * might be lower. If you build this as a module, use the 251 * max_freq module parameter instead. 252 */ 253 static int __init elanfreq_setup(char *str) 254 { 255 max_freq = simple_strtoul(str, &str, 0); 256 printk(KERN_WARNING "You're using the deprecated elanfreq command line option. Use elanfreq.max_freq instead, please!\n"); 257 return 1; 258 } 259 __setup("elanfreq=", elanfreq_setup); 260 #endif 261 262 263 static struct freq_attr *elanfreq_attr[] = { 264 &cpufreq_freq_attr_scaling_available_freqs, 265 NULL, 266 }; 267 268 269 static struct cpufreq_driver elanfreq_driver = { 270 .get = elanfreq_get_cpu_frequency, 271 .verify = elanfreq_verify, 272 .target = elanfreq_target, 273 .init = elanfreq_cpu_init, 274 .exit = elanfreq_cpu_exit, 275 .name = "elanfreq", 276 .owner = THIS_MODULE, 277 .attr = elanfreq_attr, 278 }; 279 280 281 static int __init elanfreq_init(void) 282 { 283 struct cpuinfo_x86 *c = &cpu_data(0); 284 285 /* Test if we have the right hardware */ 286 if ((c->x86_vendor != X86_VENDOR_AMD) || 287 (c->x86 != 4) || (c->x86_model != 10)) { 288 printk(KERN_INFO "elanfreq: error: no Elan processor found!\n"); 289 return -ENODEV; 290 } 291 return cpufreq_register_driver(&elanfreq_driver); 292 } 293 294 295 static void __exit elanfreq_exit(void) 296 { 297 cpufreq_unregister_driver(&elanfreq_driver); 298 } 299 300 301 module_param(max_freq, int, 0444); 302 303 MODULE_LICENSE("GPL"); 304 MODULE_AUTHOR("Robert Schwebel <r.schwebel@pengutronix.de>, " 305 "Sven Geggus <sven@geggus.net>"); 306 MODULE_DESCRIPTION("cpufreq driver for AMD's Elan CPUs"); 307 308 module_init(elanfreq_init); 309 module_exit(elanfreq_exit); 310