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_target - 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 int powernow_k6_target(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 -EINVAL; 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 0; 104 } 105 106 107 static int powernow_k6_cpu_init(struct cpufreq_policy *policy) 108 { 109 unsigned int i, f; 110 111 if (policy->cpu != 0) 112 return -ENODEV; 113 114 /* get frequencies */ 115 max_multiplier = powernow_k6_get_cpu_multiplier(); 116 busfreq = cpu_khz / max_multiplier; 117 118 /* table init */ 119 for (i = 0; (clock_ratio[i].frequency != CPUFREQ_TABLE_END); i++) { 120 f = clock_ratio[i].driver_data; 121 if (f > max_multiplier) 122 clock_ratio[i].frequency = CPUFREQ_ENTRY_INVALID; 123 else 124 clock_ratio[i].frequency = busfreq * f; 125 } 126 127 /* cpuinfo and default policy values */ 128 policy->cpuinfo.transition_latency = 200000; 129 130 return cpufreq_table_validate_and_show(policy, clock_ratio); 131 } 132 133 134 static int powernow_k6_cpu_exit(struct cpufreq_policy *policy) 135 { 136 unsigned int i; 137 for (i = 0; i < 8; i++) { 138 if (i == max_multiplier) 139 powernow_k6_target(policy, i); 140 } 141 cpufreq_frequency_table_put_attr(policy->cpu); 142 return 0; 143 } 144 145 static unsigned int powernow_k6_get(unsigned int cpu) 146 { 147 unsigned int ret; 148 ret = (busfreq * powernow_k6_get_cpu_multiplier()); 149 return ret; 150 } 151 152 static struct cpufreq_driver powernow_k6_driver = { 153 .verify = cpufreq_generic_frequency_table_verify, 154 .target_index = powernow_k6_target, 155 .init = powernow_k6_cpu_init, 156 .exit = powernow_k6_cpu_exit, 157 .get = powernow_k6_get, 158 .name = "powernow-k6", 159 .attr = cpufreq_generic_attr, 160 }; 161 162 static const struct x86_cpu_id powernow_k6_ids[] = { 163 { X86_VENDOR_AMD, 5, 12 }, 164 { X86_VENDOR_AMD, 5, 13 }, 165 {} 166 }; 167 MODULE_DEVICE_TABLE(x86cpu, powernow_k6_ids); 168 169 /** 170 * powernow_k6_init - initializes the k6 PowerNow! CPUFreq driver 171 * 172 * Initializes the K6 PowerNow! support. Returns -ENODEV on unsupported 173 * devices, -EINVAL or -ENOMEM on problems during initiatization, and zero 174 * on success. 175 */ 176 static int __init powernow_k6_init(void) 177 { 178 if (!x86_match_cpu(powernow_k6_ids)) 179 return -ENODEV; 180 181 if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) { 182 printk(KERN_INFO PFX "PowerNow IOPORT region already used.\n"); 183 return -EIO; 184 } 185 186 if (cpufreq_register_driver(&powernow_k6_driver)) { 187 release_region(POWERNOW_IOPORT, 16); 188 return -EINVAL; 189 } 190 191 return 0; 192 } 193 194 195 /** 196 * powernow_k6_exit - unregisters AMD K6-2+/3+ PowerNow! support 197 * 198 * Unregisters AMD K6-2+ / K6-3+ PowerNow! support. 199 */ 200 static void __exit powernow_k6_exit(void) 201 { 202 cpufreq_unregister_driver(&powernow_k6_driver); 203 release_region(POWERNOW_IOPORT, 16); 204 } 205 206 207 MODULE_AUTHOR("Arjan van de Ven, Dave Jones <davej@redhat.com>, " 208 "Dominik Brodowski <linux@brodo.de>"); 209 MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors."); 210 MODULE_LICENSE("GPL"); 211 212 module_init(powernow_k6_init); 213 module_exit(powernow_k6_exit); 214