1
2
3
4
5
6
7
8
9
10
11#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
12
13#include <linux/kernel.h>
14#include <linux/module.h>
15#include <linux/init.h>
16#include <linux/cpufreq.h>
17#include <linux/ioport.h>
18#include <linux/timex.h>
19#include <linux/io.h>
20
21#include <asm/cpu_device_id.h>
22#include <asm/msr.h>
23
24#define POWERNOW_IOPORT 0xfff0
25
26
27static unsigned int busfreq;
28static unsigned int max_multiplier;
29
30static unsigned int param_busfreq = 0;
31static unsigned int param_max_multiplier = 0;
32
33module_param_named(max_multiplier, param_max_multiplier, uint, S_IRUGO);
34MODULE_PARM_DESC(max_multiplier, "Maximum multiplier (allowed values: 20 30 35 40 45 50 55 60)");
35
36module_param_named(bus_frequency, param_busfreq, uint, S_IRUGO);
37MODULE_PARM_DESC(bus_frequency, "Bus frequency in kHz");
38
39
40static struct cpufreq_frequency_table clock_ratio[] = {
41 {0, 60, 0},
42 {0, 55, 0},
43 {0, 50, 0},
44 {0, 45, 0},
45 {0, 40, 0},
46 {0, 35, 0},
47 {0, 30, 0},
48 {0, 20, 0},
49 {0, 0, CPUFREQ_TABLE_END}
50};
51
52static const u8 index_to_register[8] = { 6, 3, 1, 0, 2, 7, 5, 4 };
53static const u8 register_to_index[8] = { 3, 2, 4, 1, 7, 6, 0, 5 };
54
55static const struct {
56 unsigned freq;
57 unsigned mult;
58} usual_frequency_table[] = {
59 { 350000, 35 },
60 { 400000, 40 },
61 { 450000, 45 },
62 { 475000, 50 },
63 { 500000, 50 },
64 { 506250, 45 },
65 { 533500, 55 },
66 { 550000, 55 },
67 { 562500, 50 },
68 { 570000, 60 },
69 { 600000, 60 },
70 { 618750, 55 },
71 { 660000, 55 },
72 { 675000, 60 },
73 { 720000, 60 },
74};
75
76#define FREQ_RANGE 3000
77
78
79
80
81
82
83
84static int powernow_k6_get_cpu_multiplier(void)
85{
86 unsigned long invalue = 0;
87 u32 msrval;
88
89 local_irq_disable();
90
91 msrval = POWERNOW_IOPORT + 0x1;
92 wrmsr(MSR_K6_EPMR, msrval, 0);
93 invalue = inl(POWERNOW_IOPORT + 0x8);
94 msrval = POWERNOW_IOPORT + 0x0;
95 wrmsr(MSR_K6_EPMR, msrval, 0);
96
97 local_irq_enable();
98
99 return clock_ratio[register_to_index[(invalue >> 5)&7]].driver_data;
100}
101
102static void powernow_k6_set_cpu_multiplier(unsigned int best_i)
103{
104 unsigned long outvalue, invalue;
105 unsigned long msrval;
106 unsigned long cr0;
107
108
109
110
111
112
113
114 local_irq_disable();
115 cr0 = read_cr0();
116 write_cr0(cr0 | X86_CR0_CD);
117 wbinvd();
118
119 outvalue = (1<<12) | (1<<10) | (1<<9) | (index_to_register[best_i]<<5);
120
121 msrval = POWERNOW_IOPORT + 0x1;
122 wrmsr(MSR_K6_EPMR, msrval, 0);
123 invalue = inl(POWERNOW_IOPORT + 0x8);
124 invalue = invalue & 0x1f;
125 outvalue = outvalue | invalue;
126 outl(outvalue, (POWERNOW_IOPORT + 0x8));
127 msrval = POWERNOW_IOPORT + 0x0;
128 wrmsr(MSR_K6_EPMR, msrval, 0);
129
130 write_cr0(cr0);
131 local_irq_enable();
132}
133
134
135
136
137
138
139
140static int powernow_k6_target(struct cpufreq_policy *policy,
141 unsigned int best_i)
142{
143
144 if (clock_ratio[best_i].driver_data > max_multiplier) {
145 pr_err("invalid target frequency\n");
146 return -EINVAL;
147 }
148
149 powernow_k6_set_cpu_multiplier(best_i);
150
151 return 0;
152}
153
154static int powernow_k6_cpu_init(struct cpufreq_policy *policy)
155{
156 struct cpufreq_frequency_table *pos;
157 unsigned int i, f;
158 unsigned khz;
159
160 if (policy->cpu != 0)
161 return -ENODEV;
162
163 max_multiplier = 0;
164 khz = cpu_khz;
165 for (i = 0; i < ARRAY_SIZE(usual_frequency_table); i++) {
166 if (khz >= usual_frequency_table[i].freq - FREQ_RANGE &&
167 khz <= usual_frequency_table[i].freq + FREQ_RANGE) {
168 khz = usual_frequency_table[i].freq;
169 max_multiplier = usual_frequency_table[i].mult;
170 break;
171 }
172 }
173 if (param_max_multiplier) {
174 cpufreq_for_each_entry(pos, clock_ratio)
175 if (pos->driver_data == param_max_multiplier) {
176 max_multiplier = param_max_multiplier;
177 goto have_max_multiplier;
178 }
179 pr_err("invalid max_multiplier parameter, valid parameters 20, 30, 35, 40, 45, 50, 55, 60\n");
180 return -EINVAL;
181 }
182
183 if (!max_multiplier) {
184 pr_warn("unknown frequency %u, cannot determine current multiplier\n",
185 khz);
186 pr_warn("use module parameters max_multiplier and bus_frequency\n");
187 return -EOPNOTSUPP;
188 }
189
190have_max_multiplier:
191 param_max_multiplier = max_multiplier;
192
193 if (param_busfreq) {
194 if (param_busfreq >= 50000 && param_busfreq <= 150000) {
195 busfreq = param_busfreq / 10;
196 goto have_busfreq;
197 }
198 pr_err("invalid bus_frequency parameter, allowed range 50000 - 150000 kHz\n");
199 return -EINVAL;
200 }
201
202 busfreq = khz / max_multiplier;
203have_busfreq:
204 param_busfreq = busfreq * 10;
205
206
207 cpufreq_for_each_entry(pos, clock_ratio) {
208 f = pos->driver_data;
209 if (f > max_multiplier)
210 pos->frequency = CPUFREQ_ENTRY_INVALID;
211 else
212 pos->frequency = busfreq * f;
213 }
214
215
216 policy->cpuinfo.transition_latency = 500000;
217 policy->freq_table = clock_ratio;
218
219 return 0;
220}
221
222
223static int powernow_k6_cpu_exit(struct cpufreq_policy *policy)
224{
225 unsigned int i;
226
227 for (i = 0; (clock_ratio[i].frequency != CPUFREQ_TABLE_END); i++) {
228 if (clock_ratio[i].driver_data == max_multiplier) {
229 struct cpufreq_freqs freqs;
230
231 freqs.old = policy->cur;
232 freqs.new = clock_ratio[i].frequency;
233 freqs.flags = 0;
234
235 cpufreq_freq_transition_begin(policy, &freqs);
236 powernow_k6_target(policy, i);
237 cpufreq_freq_transition_end(policy, &freqs, 0);
238 break;
239 }
240 }
241 return 0;
242}
243
244static unsigned int powernow_k6_get(unsigned int cpu)
245{
246 unsigned int ret;
247 ret = (busfreq * powernow_k6_get_cpu_multiplier());
248 return ret;
249}
250
251static struct cpufreq_driver powernow_k6_driver = {
252 .verify = cpufreq_generic_frequency_table_verify,
253 .target_index = powernow_k6_target,
254 .init = powernow_k6_cpu_init,
255 .exit = powernow_k6_cpu_exit,
256 .get = powernow_k6_get,
257 .name = "powernow-k6",
258 .attr = cpufreq_generic_attr,
259};
260
261static const struct x86_cpu_id powernow_k6_ids[] = {
262 { X86_VENDOR_AMD, 5, 12 },
263 { X86_VENDOR_AMD, 5, 13 },
264 {}
265};
266MODULE_DEVICE_TABLE(x86cpu, powernow_k6_ids);
267
268
269
270
271
272
273
274
275static int __init powernow_k6_init(void)
276{
277 if (!x86_match_cpu(powernow_k6_ids))
278 return -ENODEV;
279
280 if (!request_region(POWERNOW_IOPORT, 16, "PowerNow!")) {
281 pr_info("PowerNow IOPORT region already used\n");
282 return -EIO;
283 }
284
285 if (cpufreq_register_driver(&powernow_k6_driver)) {
286 release_region(POWERNOW_IOPORT, 16);
287 return -EINVAL;
288 }
289
290 return 0;
291}
292
293
294
295
296
297
298
299static void __exit powernow_k6_exit(void)
300{
301 cpufreq_unregister_driver(&powernow_k6_driver);
302 release_region(POWERNOW_IOPORT, 16);
303}
304
305
306MODULE_AUTHOR("Arjan van de Ven, Dave Jones, "
307 "Dominik Brodowski <linux@brodo.de>");
308MODULE_DESCRIPTION("PowerNow! driver for AMD K6-2+ / K6-3+ processors.");
309MODULE_LICENSE("GPL");
310
311module_init(powernow_k6_init);
312module_exit(powernow_k6_exit);
313