1 /* 2 * This file was based upon code in Powertweak Linux (http://powertweak.sf.net) 3 * (C) 2000-2003 Dave Jones, Arjan van de Ven, Janne Pänkälä, 4 * Dominik Brodowski. 5 * 6 * Licensed under the terms of the GNU GPL License version 2. 7 * 8 * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* 9 */ 10 11 #include <linux/kernel.h> 12 #include <linux/module.h> 13 #include <linux/init.h> 14 #include <linux/cpufreq.h> 15 #include <linux/ioport.h> 16 #include <linux/timex.h> 17 #include <linux/io.h> 18 19 #include <asm/msr.h> 20 21 #define POWERNOW_IOPORT 0xfff0 /* it doesn't matter where, as long 22 as it is unused */ 23 24 #define PFX "powernow-k6: " 25 static unsigned int busfreq; /* FSB, in 10 kHz */ 26 static unsigned int max_multiplier; 27 28 29 /* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */ 30 static struct cpufreq_frequency_table clock_ratio[] = { 31 {45, /* 000 -> 4.5x */ 0}, 32 {50, /* 001 -> 5.0x */ 0}, 33 {40, /* 010 -> 4.0x */ 0}, 34 {55, /* 011 -> 5.5x */ 0}, 35 {20, /* 100 -> 2.0x */ 0}, 36 {30, /* 101 -> 3.0x */ 0}, 37 {60, /* 110 -> 6.0x */ 0}, 38 {35, /* 111 -> 3.5x */ 0}, 39 {0, CPUFREQ_TABLE_END} 40 }; 41 42 43 /** 44 * powernow_k6_get_cpu_multiplier - returns the current FSB multiplier 45 * 46 * Returns the current setting of the frequency multiplier. Core clock 47 * speed is frequency of the Front-Side Bus multiplied with this value. 48 */ 49 static int powernow_k6_get_cpu_multiplier(void) 50 { 51 u64 invalue = 0; 52 u32 msrval; 53 54 msrval = POWERNOW_IOPORT + 0x1; 55 wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */ 56 invalue = inl(POWERNOW_IOPORT + 0x8); 57 msrval = POWERNOW_IOPORT + 0x0; 58 wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */ 59 60 return clock_ratio[(invalue >> 5)&7].index; 61 } 62 63 64 /** 65 * powernow_k6_set_state - set the PowerNow! multiplier 66 * @best_i: clock_ratio[best_i] is the target multiplier 67 * 68 * Tries to change the PowerNow! multiplier 69 */ 70 static void powernow_k6_set_state(unsigned int best_i) 71 { 72 unsigned long outvalue = 0, invalue = 0; 73 unsigned long msrval; 74 struct cpufreq_freqs freqs; 75 76 if (clock_ratio[best_i].index > max_multiplier) { 77 printk(KERN_ERR PFX "invalid target frequency\n"); 78 return; 79 } 80 81 freqs.old = busfreq * powernow_k6_get_cpu_multiplier(); 82 freqs.new = busfreq * clock_ratio[best_i].index; 83 freqs.cpu = 0; /* powernow-k6.c is UP only driver */ 84 85 cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); 86 87 /* we now need to transform best_i to the BVC format, see AMD#23446 */ 88 89 outvalue = (1<<12) | (1<<10) | (1<<9) | (best_i<<5); 90 91 msrval = POWERNOW_IOPORT + 0x1; 92 wrmsr(MSR_K6_EPMR, msrval, 0); /* enable the PowerNow port */ 93 invalue = inl(POWERNOW_IOPORT + 0x8); 94 invalue = invalue & 0xf; 95 outvalue = outvalue | invalue; 96 outl(outvalue , (POWERNOW_IOPORT + 0x8)); 97 msrval = POWERNOW_IOPORT + 0x0; 98 wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */ 99 100 cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); 101 102 return; 103 } 104 105 106 /** 107 * powernow_k6_verify - verifies a new CPUfreq policy 108 * @policy: new policy 109 * 110 * Policy must be within lowest and highest possible CPU Frequency, 111 * and at least one possible state must be within min and max. 112 */ 113 static int powernow_k6_verify(struct cpufreq_policy *policy) 114 { 115 return cpufreq_frequency_table_verify(policy, &clock_ratio[0]); 116 } 117 118 119 /** 120 * powernow_k6_setpolicy - sets a new CPUFreq policy 121 * @policy: new policy 122 * @target_freq: the target frequency 123 * @relation: how that frequency relates to achieved frequency 124 * (CPUFREQ_RELATION_L or CPUFREQ_RELATION_H) 125 * 126 * sets a new CPUFreq policy 127 */ 128 static int powernow_k6_target(struct cpufreq_policy *policy, 129 unsigned int target_freq, 130 unsigned int relation) 131 { 132 unsigned int newstate = 0; 133 134 if (cpufreq_frequency_table_target(policy, &clock_ratio[0], 135 target_freq, relation, &newstate)) 136 return -EINVAL; 137 138 powernow_k6_set_state(newstate); 139 140 return 0; 141 } 142 143 144 static int powernow_k6_cpu_init(struct cpufreq_policy *policy) 145 { 146 unsigned int i, f; 147 int result; 148 149 if (policy->cpu != 0) 150 return -ENODEV; 151 152 /* get frequencies */ 153 max_multiplier = powernow_k6_get_cpu_multiplier(); 154 busfreq = cpu_khz / max_multiplier; 155 156 /* table init */ 157 for (i = 0; (clock_ratio[i].frequency != CPUFREQ_TABLE_END); i++) { 158 f = clock_ratio[i].index; 159 if (f > max_multiplier) 160 clock_ratio[i].frequency = CPUFREQ_ENTRY_INVALID; 161 else 162 clock_ratio[i].frequency = busfreq * f; 163 } 164 165 /* cpuinfo and default policy values */ 166 policy->cpuinfo.transition_latency = 200000; 167 policy->cur = busfreq * max_multiplier; 168 169 result = cpufreq_frequency_table_cpuinfo(policy, clock_ratio); 170 if (result) 171 return result; 172 173 cpufreq_frequency_table_get_attr(clock_ratio, policy->cpu); 174 175 return 0; 176 } 177 178 179 static int powernow_k6_cpu_exit(struct cpufreq_policy *policy) 180 { 181 unsigned int i; 182 for (i = 0; i < 8; i++) { 183 if (i == max_multiplier) 184 powernow_k6_set_state(i); 185 } 186 cpufreq_frequency_table_put_attr(policy->cpu); 187 return 0; 188 } 189 190 static unsigned int powernow_k6_get(unsigned int cpu) 191 { 192 unsigned int ret; 193 ret = (busfreq * powernow_k6_get_cpu_multiplier()); 194 return ret; 195 } 196 197 static struct freq_attr *powernow_k6_attr[] = { 198 &cpufreq_freq_attr_scaling_available_freqs, 199 NULL, 200 }; 201 202 static struct cpufreq_driver powernow_k6_driver = { 203 .verify = powernow_k6_verify, 204 .target = powernow_k6_target, 205 .init = powernow_k6_cpu_init, 206 .exit = powernow_k6_cpu_exit, 207 .get = powernow_k6_get, 208 .name = "powernow-k6", 209 .owner = THIS_MODULE, 210 .attr = powernow_k6_attr, 211 }; 212 213 214 /** 215 * powernow_k6_init - initializes the k6 PowerNow! CPUFreq driver 216 * 217 * Initializes the K6 PowerNow! support. Returns -ENODEV on unsupported 218 * devices, -EINVAL or -ENOMEM on problems during initiatization, and zero 219 * on success. 220 */ 221 static int __init powernow_k6_init(void) 222 { 223 struct cpuinfo_x86 *c = &cpu_data(0); 224 225 if ((c->x86_vendor != X86_VENDOR_AMD) || (c->x86 != 5) || 226 ((c->x86_model != 12) && (c->x86_model != 13))) 227 return -ENODEV; 228 229 if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) { 230 printk(KERN_INFO PFX "PowerNow IOPORT region already used.\n"); 231 return -EIO; 232 } 233 234 if (cpufreq_register_driver(&powernow_k6_driver)) { 235 release_region(POWERNOW_IOPORT, 16); 236 return -EINVAL; 237 } 238 239 return 0; 240 } 241 242 243 /** 244 * powernow_k6_exit - unregisters AMD K6-2+/3+ PowerNow! support 245 * 246 * Unregisters AMD K6-2+ / K6-3+ PowerNow! support. 247 */ 248 static void __exit powernow_k6_exit(void) 249 { 250 cpufreq_unregister_driver(&powernow_k6_driver); 251 release_region(POWERNOW_IOPORT, 16); 252 } 253 254 255 MODULE_AUTHOR("Arjan van de Ven, Dave Jones <davej@redhat.com>, " 256 "Dominik Brodowski <linux@brodo.de>"); 257 MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors."); 258 MODULE_LICENSE("GPL"); 259 260 module_init(powernow_k6_init); 261 module_exit(powernow_k6_exit); 262