Rev 600 | Go to most recent revision | Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
582 | mauro | 1 | /* |
2 | * AMD K7 Powernow driver. |
||
3 | * (C) 2003 Dave Jones <davej@codemonkey.org.uk> on behalf of SuSE Labs. |
||
4 | * (C) 2003 Dave Jones <davej@redhat.com> |
||
5 | * |
||
6 | * Licensed under the terms of the GNU GPL License version 2. |
||
7 | * Based upon datasheets & sample CPUs kindly provided by AMD. |
||
8 | * |
||
9 | * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* |
||
10 | * |
||
11 | * Errata 5: Processor may fail to execute a FID/VID change in presence of interrupt. |
||
12 | * - We cli/sti on stepping A0 CPUs around the FID/VID transition. |
||
13 | * Errata 15: Processors with half frequency multipliers may hang upon wakeup from disconnect. |
||
14 | * - We disable half multipliers if ACPI is used on A0 stepping CPUs. |
||
15 | */ |
||
16 | |||
17 | #include <linuxcomp.h> |
||
18 | |||
19 | #include <linux/config.h> |
||
20 | #include <linux/kernel.h> |
||
21 | #include <linux/module.h> |
||
22 | #include <linux/init.h> |
||
23 | #include <linux/cpufreq.h> |
||
24 | #include <linux/slab.h> |
||
25 | #include <linux/string.h> |
||
26 | |||
27 | #include <asm/msr.h> |
||
28 | #include <asm/timex.h> |
||
29 | #include <asm/io.h> |
||
30 | #include <asm/system.h> |
||
31 | |||
32 | #include "powernow-k7.h" |
||
33 | |||
34 | #define DEBUG |
||
35 | |||
36 | #ifdef DEBUG |
||
37 | #define dprintk(msg...) printk(msg) |
||
38 | #else |
||
39 | #define dprintk(msg...) do { } while(0) |
||
40 | #endif |
||
41 | |||
42 | #define PFX "powernow: " |
||
43 | |||
44 | extern struct cpuinfo_x86 new_cpu_data; |
||
45 | |||
46 | struct psb_s { |
||
47 | u8 signature[10]; |
||
48 | u8 tableversion; |
||
49 | u8 flags; |
||
50 | u16 settlingtime; |
||
51 | u8 reserved1; |
||
52 | u8 numpst; |
||
53 | }; |
||
54 | |||
55 | struct pst_s { |
||
56 | u32 cpuid; |
||
57 | u8 fsbspeed; |
||
58 | u8 maxfid; |
||
59 | u8 startvid; |
||
60 | u8 numpstates; |
||
61 | }; |
||
62 | |||
63 | |||
64 | /* divide by 1000 to get VID. */ |
||
65 | static int mobile_vid_table[32] = { |
||
66 | 2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650, |
||
67 | 1600, 1550, 1500, 1450, 1400, 1350, 1300, 0, |
||
68 | 1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100, |
||
69 | 1075, 1050, 1024, 1000, 975, 950, 925, 0, |
||
70 | }; |
||
71 | |||
72 | /* divide by 10 to get FID. */ |
||
73 | static int fid_codes[32] = { |
||
74 | 110, 115, 120, 125, 50, 55, 60, 65, |
||
75 | 70, 75, 80, 85, 90, 95, 100, 105, |
||
76 | 30, 190, 40, 200, 130, 135, 140, 210, |
||
77 | 150, 225, 160, 165, 170, 180, -1, -1, |
||
78 | }; |
||
79 | |||
80 | static struct cpufreq_frequency_table *powernow_table; |
||
81 | |||
82 | static unsigned int can_scale_bus; |
||
83 | static unsigned int can_scale_vid; |
||
84 | static unsigned int minimum_speed=-1; |
||
85 | static unsigned int maximum_speed; |
||
86 | static unsigned int number_scales; |
||
87 | static unsigned int fsb; |
||
88 | static unsigned int latency; |
||
89 | static char have_a0; |
||
90 | |||
91 | |||
92 | static int check_powernow(void) |
||
93 | { |
||
94 | struct cpuinfo_x86 *c = &new_cpu_data; /* Nino */ |
||
95 | unsigned int maxei, eax, ebx, ecx, edx; |
||
96 | |||
97 | if (c->x86_vendor != X86_VENDOR_AMD) { |
||
98 | dprintk (KERN_INFO PFX "AMD processor not detected.\n"); |
||
99 | return 0; |
||
100 | } |
||
101 | |||
102 | if (c->x86 !=6) { |
||
103 | dprintk (KERN_INFO PFX "This module only works with AMD K7 CPUs\n"); |
||
104 | return 0; |
||
105 | } |
||
106 | |||
107 | printk (KERN_INFO PFX "AMD K7 CPU detected.\n"); |
||
108 | |||
109 | if ((c->x86_model == 6) && (c->x86_mask == 0)) { |
||
110 | dprintk (KERN_INFO PFX "K7 660[A0] core detected, enabling errata workarounds\n"); |
||
111 | have_a0 = 1; |
||
112 | } |
||
113 | |||
114 | /* Get maximum capabilities */ |
||
115 | maxei = cpuid_eax (0x80000000); |
||
116 | if (maxei < 0x80000007) { /* Any powernow info ? */ |
||
117 | printk (KERN_INFO PFX "No powernow capabilities detected\n"); |
||
118 | return 0; |
||
119 | } |
||
120 | |||
121 | cpuid(0x80000007, &eax, &ebx, &ecx, &edx); |
||
122 | printk (KERN_INFO PFX "PowerNOW! Technology present. Can scale: "); |
||
123 | |||
124 | if (edx & 1 << 1) { |
||
125 | printk ("frequency"); |
||
126 | can_scale_bus=1; |
||
127 | } |
||
128 | |||
129 | if ((edx & (1 << 1 | 1 << 2)) == 0x6) |
||
130 | printk (" and "); |
||
131 | |||
132 | if (edx & 1 << 2) { |
||
133 | printk ("voltage"); |
||
134 | can_scale_vid=1; |
||
135 | } |
||
136 | |||
137 | if (!(edx & (1 << 1 | 1 << 2))) { |
||
138 | printk ("nothing.\n"); |
||
139 | return 0; |
||
140 | } |
||
141 | |||
142 | printk (".\n"); |
||
143 | return 1; |
||
144 | } |
||
145 | |||
146 | |||
147 | static int get_ranges (unsigned char *pst) |
||
148 | { |
||
149 | unsigned int j, speed; |
||
150 | u8 fid, vid; |
||
151 | |||
152 | powernow_table = kmalloc((sizeof(struct cpufreq_frequency_table) * (number_scales + 1)), GFP_KERNEL); |
||
153 | if (!powernow_table) |
||
154 | return -ENOMEM; |
||
155 | memset(powernow_table, 0, (sizeof(struct cpufreq_frequency_table) * (number_scales + 1))); |
||
156 | |||
157 | for (j=0 ; j < number_scales; j++) { |
||
158 | fid = *pst++; |
||
159 | |||
160 | powernow_table[j].frequency = fsb * fid_codes[fid] * 100; |
||
161 | powernow_table[j].index = fid; /* lower 8 bits */ |
||
162 | |||
163 | speed = fsb * (fid_codes[fid]/10); |
||
164 | if ((fid_codes[fid] % 10)==5) { |
||
165 | speed += fsb/2; |
||
166 | #if defined(CONFIG_ACPI_PROCESSOR) || defined(CONFIG_ACPI_PROCESSOR_MODULE) |
||
167 | if (have_a0 == 1) |
||
168 | powernow_table[j].frequency = CPUFREQ_ENTRY_INVALID; |
||
169 | #endif |
||
170 | } |
||
171 | |||
172 | dprintk (KERN_INFO PFX " FID: 0x%x (%d.%dx [%dMHz])\t", fid, |
||
173 | fid_codes[fid] / 10, fid_codes[fid] % 10, speed); |
||
174 | |||
175 | if (speed < minimum_speed) |
||
176 | minimum_speed = speed; |
||
177 | if (speed > maximum_speed) |
||
178 | maximum_speed = speed; |
||
179 | |||
180 | vid = *pst++; |
||
181 | powernow_table[j].index |= (vid << 8); /* upper 8 bits */ |
||
182 | dprintk ("VID: 0x%x (%d.%03dV)\n", vid, mobile_vid_table[vid]/1000, |
||
183 | mobile_vid_table[vid]%1000); |
||
184 | } |
||
185 | dprintk ("\n"); |
||
186 | |||
187 | powernow_table[number_scales].frequency = CPUFREQ_TABLE_END; |
||
188 | powernow_table[number_scales].index = 0; |
||
189 | |||
190 | return 0; |
||
191 | } |
||
192 | |||
193 | |||
194 | static void change_FID(int fid) |
||
195 | { |
||
196 | union msr_fidvidctl fidvidctl; |
||
197 | |||
198 | rdmsrl (MSR_K7_FID_VID_CTL, fidvidctl.val); |
||
199 | if (fidvidctl.bits.FID != fid) { |
||
200 | fidvidctl.bits.SGTC = latency; |
||
201 | fidvidctl.bits.FID = fid; |
||
202 | fidvidctl.bits.VIDC = 0; |
||
203 | fidvidctl.bits.FIDC = 1; |
||
204 | wrmsrl (MSR_K7_FID_VID_CTL, fidvidctl.val); |
||
205 | } |
||
206 | } |
||
207 | |||
208 | |||
209 | static void change_VID(int vid) |
||
210 | { |
||
211 | union msr_fidvidctl fidvidctl; |
||
212 | |||
213 | rdmsrl (MSR_K7_FID_VID_CTL, fidvidctl.val); |
||
214 | if (fidvidctl.bits.VID != vid) { |
||
215 | fidvidctl.bits.SGTC = latency; |
||
216 | fidvidctl.bits.VID = vid; |
||
217 | fidvidctl.bits.FIDC = 0; |
||
218 | fidvidctl.bits.VIDC = 1; |
||
219 | wrmsrl (MSR_K7_FID_VID_CTL, fidvidctl.val); |
||
220 | } |
||
221 | } |
||
222 | |||
223 | |||
224 | static void change_speed (unsigned int index) |
||
225 | { |
||
226 | u8 fid, vid; |
||
227 | struct cpufreq_freqs freqs; |
||
228 | union msr_fidvidstatus fidvidstatus; |
||
229 | int cfid; |
||
230 | |||
231 | /* fid are the lower 8 bits of the index we stored into |
||
232 | * the cpufreq frequency table in powernow_decode_bios, |
||
233 | * vid are the upper 8 bits. |
||
234 | */ |
||
235 | |||
236 | fid = powernow_table[index].index & 0xFF; |
||
237 | vid = (powernow_table[index].index & 0xFF00) >> 8; |
||
238 | |||
239 | freqs.cpu = 0; |
||
240 | |||
241 | rdmsrl (MSR_K7_FID_VID_STATUS, fidvidstatus.val); |
||
242 | cfid = fidvidstatus.bits.CFID; |
||
243 | freqs.old = fsb * fid_codes[cfid] * 100; |
||
244 | freqs.new = powernow_table[index].frequency; |
||
245 | |||
246 | //!!!cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); |
||
247 | |||
248 | /* Now do the magic poking into the MSRs. */ |
||
249 | |||
250 | if (have_a0 == 1) /* A0 errata 5 */ |
||
251 | local_irq_disable(); |
||
252 | |||
253 | if (freqs.old > freqs.new) { |
||
254 | /* Going down, so change FID first */ |
||
255 | change_FID(fid); |
||
256 | change_VID(vid); |
||
257 | } else { |
||
258 | /* Going up, so change VID first */ |
||
259 | change_VID(vid); |
||
260 | change_FID(fid); |
||
261 | } |
||
262 | |||
263 | |||
264 | if (have_a0 == 1) |
||
265 | local_irq_enable(); |
||
266 | |||
267 | //!!!cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); |
||
268 | } |
||
269 | |||
270 | |||
271 | static int powernow_decode_bios (int maxfid, int startvid) |
||
272 | { |
||
273 | struct psb_s *psb; |
||
274 | struct pst_s *pst; |
||
275 | struct cpuinfo_x86 *c = &new_cpu_data; /* Nino */ |
||
276 | unsigned int i, j; |
||
277 | unsigned char *p; |
||
278 | unsigned int etuple; |
||
279 | unsigned int ret; |
||
280 | |||
281 | etuple = cpuid_eax(0x80000001); |
||
282 | etuple &= 0xf00; |
||
283 | etuple |= (c->x86_model<<4)|(c->x86_mask); |
||
284 | |||
285 | for (i=0xC0000; i < 0xffff0 ; i+=16) { |
||
286 | |||
287 | p = phys_to_virt(i); |
||
288 | |||
289 | if (memcmp(p, "AMDK7PNOW!", 10) == 0){ |
||
290 | dprintk (KERN_INFO PFX "Found PSB header at %p\n", p); |
||
291 | psb = (struct psb_s *) p; |
||
292 | dprintk (KERN_INFO PFX "Table version: 0x%x\n", psb->tableversion); |
||
293 | if (psb->tableversion != 0x12) { |
||
294 | printk (KERN_INFO PFX "Sorry, only v1.2 tables supported right now\n"); |
||
295 | return -ENODEV; |
||
296 | } |
||
297 | |||
298 | dprintk (KERN_INFO PFX "Flags: 0x%x (", psb->flags); |
||
299 | if ((psb->flags & 1)==0) { |
||
300 | dprintk ("Mobile"); |
||
301 | } else { |
||
302 | dprintk ("Desktop"); |
||
303 | } |
||
304 | dprintk (" voltage regulator)\n"); |
||
305 | |||
306 | latency = psb->settlingtime; |
||
307 | if (latency < 100) { |
||
308 | printk (KERN_INFO PFX "BIOS set settling time to %d microseconds." |
||
309 | "Should be at least 100. Correcting.\n", latency); |
||
310 | latency = 100; |
||
311 | } |
||
312 | dprintk (KERN_INFO PFX "Settling Time: %d microseconds.\n", psb->settlingtime); |
||
313 | dprintk (KERN_INFO PFX "Has %d PST tables. (Only dumping ones relevant to this CPU).\n", psb->numpst); |
||
314 | latency *= 100; /* SGTC needs to be in units of 10ns */ |
||
315 | |||
316 | p += sizeof (struct psb_s); |
||
317 | |||
318 | pst = (struct pst_s *) p; |
||
319 | |||
320 | for (i = 0 ; i <psb->numpst; i++) { |
||
321 | pst = (struct pst_s *) p; |
||
322 | number_scales = pst->numpstates; |
||
323 | |||
324 | if ((etuple == pst->cpuid) && (maxfid==pst->maxfid) && (startvid==pst->startvid)) |
||
325 | { |
||
326 | dprintk (KERN_INFO PFX "PST:%d (@%p)\n", i, pst); |
||
327 | dprintk (KERN_INFO PFX " cpuid: 0x%x\t", pst->cpuid); |
||
328 | dprintk ("fsb: %d\t", pst->fsbspeed); |
||
329 | dprintk ("maxFID: 0x%x\t", pst->maxfid); |
||
330 | dprintk ("startvid: 0x%x\n", pst->startvid); |
||
331 | |||
332 | fsb = pst->fsbspeed; |
||
333 | ret = get_ranges ((char *) pst + sizeof (struct pst_s)); |
||
334 | return ret; |
||
335 | |||
336 | } else { |
||
337 | p = (char *) pst + sizeof (struct pst_s); |
||
338 | for (j=0 ; j < number_scales; j++) |
||
339 | p+=2; |
||
340 | } |
||
341 | } |
||
342 | printk (KERN_INFO PFX "No PST tables match this cpuid (0x%x)\n", etuple); |
||
343 | printk ("This is indicative of a broken BIOS. Email davej@redhat.com\n"); |
||
344 | return -EINVAL; |
||
345 | } |
||
346 | p++; |
||
347 | } |
||
348 | |||
349 | return -ENODEV; |
||
350 | } |
||
351 | |||
352 | |||
353 | static int powernow_target (struct cpufreq_policy *policy, |
||
354 | unsigned int target_freq, |
||
355 | unsigned int relation) |
||
356 | { |
||
357 | unsigned int newstate; |
||
358 | |||
359 | if (cpufreq_frequency_table_target(policy, powernow_table, target_freq, relation, &newstate)) |
||
360 | return -EINVAL; |
||
361 | |||
362 | change_speed(newstate); |
||
363 | |||
364 | return 0; |
||
365 | } |
||
366 | |||
367 | |||
368 | static int powernow_verify (struct cpufreq_policy *policy) |
||
369 | { |
||
370 | return cpufreq_frequency_table_verify(policy, powernow_table); |
||
371 | } |
||
372 | |||
373 | |||
374 | static int __init powernow_cpu_init (struct cpufreq_policy *policy) |
||
375 | { |
||
376 | union msr_fidvidstatus fidvidstatus; |
||
377 | int result; |
||
378 | |||
379 | if (policy->cpu != 0) |
||
380 | return -ENODEV; |
||
381 | |||
382 | rdmsrl (MSR_K7_FID_VID_STATUS, fidvidstatus.val); |
||
383 | |||
384 | result = powernow_decode_bios(fidvidstatus.bits.MFID, fidvidstatus.bits.SVID); |
||
385 | if (result) |
||
386 | return result; |
||
387 | |||
388 | printk (KERN_INFO PFX "Minimum speed %d MHz. Maximum speed %d MHz.\n", |
||
389 | minimum_speed, maximum_speed); |
||
390 | |||
391 | policy->governor = 0; //!!!CPUFREQ_DEFAULT_GOVERNOR; |
||
392 | policy->cpuinfo.transition_latency = latency; |
||
393 | policy->cur = maximum_speed; |
||
394 | |||
395 | return cpufreq_frequency_table_cpuinfo(policy, powernow_table); |
||
396 | } |
||
397 | |||
398 | static struct cpufreq_driver powernow_driver = { |
||
399 | .verify = powernow_verify, |
||
400 | .target = powernow_target, |
||
401 | .init = powernow_cpu_init, |
||
402 | .name = "powernow-k7", |
||
403 | .owner = THIS_MODULE, |
||
404 | }; |
||
405 | |||
406 | /*static*/ int __init powernow_init (void) |
||
407 | { |
||
408 | /*!!!if (dmi_broken & BROKEN_CPUFREQ) { |
||
409 | printk (KERN_INFO PFX "Disabled at boot time by DMI,\n"); |
||
410 | return -ENODEV; |
||
411 | }*/ |
||
412 | if (check_powernow()==0) |
||
413 | return -ENODEV; |
||
414 | return cpufreq_register_driver(&powernow_driver); |
||
415 | } |
||
416 | |||
417 | |||
418 | /*static*/ void __exit powernow_exit (void) |
||
419 | { |
||
420 | cpufreq_unregister_driver(&powernow_driver); |
||
421 | if (powernow_table) |
||
422 | kfree(powernow_table); |
||
423 | } |
||
424 | |||
425 | MODULE_AUTHOR ("Dave Jones <davej@codemonkey.org.uk>"); |
||
426 | MODULE_DESCRIPTION ("Powernow driver for AMD K7 processors."); |
||
427 | MODULE_LICENSE ("GPL"); |
||
428 | |||
429 | module_init(powernow_init); |
||
430 | module_exit(powernow_exit); |
||
431 |