powernow-k6.c revision bb0a56ecc4ba2a3db1b6ea6949c309886e3447d3
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: " 25static unsigned int busfreq; /* FSB, in 10 kHz */ 26static unsigned int max_multiplier; 27 28 29/* Clock ratio multiplied by 10 - see table 27 in AMD#23446 */ 30static 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 */ 49static 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 */ 70static 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 */ 113static 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 */ 128static 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 144static 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 179static 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 190static 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 197static struct freq_attr *powernow_k6_attr[] = { 198 &cpufreq_freq_attr_scaling_available_freqs, 199 NULL, 200}; 201 202static 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 */ 221static 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 */ 248static void __exit powernow_k6_exit(void) 249{ 250 cpufreq_unregister_driver(&powernow_k6_driver); 251 release_region(POWERNOW_IOPORT, 16); 252} 253 254 255MODULE_AUTHOR("Arjan van de Ven, Dave Jones <davej@redhat.com>, " 256 "Dominik Brodowski <linux@brodo.de>"); 257MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors."); 258MODULE_LICENSE("GPL"); 259 260module_init(powernow_k6_init); 261module_exit(powernow_k6_exit); 262