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