ftp://ftp.kernel.org/pub/linux/kernel/v2.6/linux-2.6.6.tar.bz2
[linux-2.6.git] / arch / i386 / kernel / cpu / cpufreq / longhaul.c
1 /*
2  *  (C) 2001-2004  Dave Jones. <davej@codemonkey.org.uk>
3  *  (C) 2002  Padraig Brady. <padraig@antefacto.com>
4  *
5  *  Licensed under the terms of the GNU GPL License version 2.
6  *  Based upon datasheets & sample CPUs kindly provided by VIA.
7  *
8  *  VIA have currently 2 different versions of Longhaul.
9  *  Version 1 (Longhaul) uses the BCR2 MSR at 0x1147.
10  *   It is present only in Samuel 1, Samuel 2 and Ezra.
11  *  Version 2 (Powersaver) uses the POWERSAVER MSR at 0x110a.
12  *   It is present in Ezra-T, Nehemiah and above.
13  *   In addition to scaling multiplier, it can also scale voltage.
14  *   There is provision for scaling FSB too, but this doesn't work
15  *   too well in practice.
16  *
17  *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
18  */
19
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
31 #include "longhaul.h"
32
33 #define DEBUG
34
35 #ifdef DEBUG
36 #define dprintk(msg...) printk(msg)
37 #else
38 #define dprintk(msg...) do { } while(0)
39 #endif
40
41 #define PFX "longhaul: "
42
43 static unsigned int numscales=16, numvscales;
44 static int minvid, maxvid;
45 static int can_scale_voltage;
46 static int vrmrev;
47
48
49 /* Module parameters */
50 static int dont_scale_voltage;
51 static unsigned int fsb;
52
53 #define __hlt()     __asm__ __volatile__("hlt": : :"memory")
54
55 /* Clock ratios multiplied by 10 */
56 static int clock_ratio[32];
57 static int eblcr_table[32];
58 static int voltage_table[32];
59 static unsigned int highest_speed, lowest_speed; /* kHz */
60 static int longhaul_version;
61 static struct cpufreq_frequency_table *longhaul_table;
62
63
64 static unsigned int calc_speed (int mult, int fsb)
65 {
66         int khz;
67         khz = (mult/10)*fsb;
68         if (mult%10)
69                 khz += fsb/2;
70         khz *= 1000;
71         return khz;
72 }
73
74
75 static int longhaul_get_cpu_mult (void)
76 {
77         unsigned long invalue=0,lo, hi;
78
79         rdmsr (MSR_IA32_EBL_CR_POWERON, lo, hi);
80         invalue = (lo & (1<<22|1<<23|1<<24|1<<25)) >>22;
81         if (longhaul_version==2) {
82                 if (lo & (1<<27))
83                         invalue+=16;
84         }
85         return eblcr_table[invalue];
86 }
87
88
89 /**
90  * longhaul_set_cpu_frequency()
91  * @clock_ratio_index : bitpattern of the new multiplier.
92  *
93  * Sets a new clock ratio, and -if applicable- a new Front Side Bus
94  */
95
96 static void longhaul_setstate (unsigned int clock_ratio_index)
97 {
98         int speed, mult;
99         struct cpufreq_freqs freqs;
100         union msr_longhaul longhaul;
101         union msr_bcr2 bcr2;
102
103         mult = clock_ratio[clock_ratio_index];
104         if (mult == -1)
105                 return;
106
107         speed = calc_speed (mult, fsb);
108         if ((speed > highest_speed) || (speed < lowest_speed))
109                 return;
110
111         freqs.old = calc_speed (longhaul_get_cpu_mult(), fsb);
112         freqs.new = speed;
113         freqs.cpu = 0; /* longhaul.c is UP only driver */
114
115         cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
116
117         dprintk (KERN_INFO PFX "FSB:%d Mult:%d.%dx\n", fsb,
118                                 mult/10, mult%10);
119
120         switch (longhaul_version) {
121         case 1:
122                 rdmsrl (MSR_VIA_BCR2, bcr2.val);
123                 /* Enable software clock multiplier */
124                 bcr2.bits.ESOFTBF = 1;
125                 bcr2.bits.CLOCKMUL = clock_ratio_index;
126                 wrmsrl (MSR_VIA_BCR2, bcr2.val);
127
128                 __hlt();
129
130                 /* Disable software clock multiplier */
131                 rdmsrl (MSR_VIA_BCR2, bcr2.val);
132                 bcr2.bits.ESOFTBF = 0;
133                 wrmsrl (MSR_VIA_BCR2, bcr2.val);
134                 break;
135
136         /*
137          * Powersaver. (Ezra-T [C5M], Nehemiah [C5N])
138          * We can scale voltage with this too, but that's currently
139          * disabled until we come up with a decent 'match freq to voltage'
140          * algorithm.
141          * We also need to do the voltage/freq setting in order depending
142          * on the direction of scaling (like we do in powernow-k7.c)
143          * Ezra-T was alleged to do FSB scaling too, but it never worked in practice.
144          */
145         case 2:
146                 rdmsrl (MSR_VIA_LONGHAUL, longhaul.val);
147                 longhaul.bits.SoftBusRatio = clock_ratio_index & 0xf;
148                 longhaul.bits.SoftBusRatio4 = (clock_ratio_index & 0x10) >> 4;
149                 longhaul.bits.EnableSoftBusRatio = 1;
150                 /* We must program the revision key only with values we
151                  * know about, not blindly copy it from 0:3 */
152                 longhaul.bits.RevisionKey = 3;  /* SoftVID & SoftBSEL */
153                 wrmsrl(MSR_VIA_LONGHAUL, longhaul.val);
154                 __hlt();
155
156                 rdmsrl (MSR_VIA_LONGHAUL, longhaul.val);
157                 longhaul.bits.EnableSoftBusRatio = 0;
158                 longhaul.bits.RevisionKey = 3;
159                 wrmsrl (MSR_VIA_LONGHAUL, longhaul.val);
160                 break;
161         }
162
163         cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
164 }
165
166 /*
167  * Centaur decided to make life a little more tricky.
168  * Only longhaul v1 is allowed to read EBLCR BSEL[0:1].
169  * Samuel2 and above have to try and guess what the FSB is.
170  * We do this by assuming we booted at maximum multiplier, and interpolate
171  * between that value multiplied by possible FSBs and cpu_mhz which
172  * was calculated at boot time. Really ugly, but no other way to do this.
173  */
174
175 #define ROUNDING        0xf
176
177 static int _guess (int guess, int maxmult)
178 {
179         int target;
180
181         target = ((maxmult/10)*guess);
182         if (maxmult%10 != 0)
183                 target += (guess/2);
184         target += ROUNDING/2;
185         target &= ~ROUNDING;
186         return target;
187 }
188
189
190 static int guess_fsb(int maxmult)
191 {
192         int speed = (cpu_khz/1000);
193         int i;
194         int speeds[3] = { 66, 100, 133 };
195
196         speed += ROUNDING/2;
197         speed &= ~ROUNDING;
198
199         for (i=0; i<3; i++) {
200                 if (_guess(speeds[i],maxmult) == speed)
201                         return speeds[i];
202         }
203         return 0;
204 }
205
206
207 static int __init longhaul_get_ranges (void)
208 {
209         struct cpuinfo_x86 *c = cpu_data;
210         unsigned long invalue;
211         unsigned int minmult=0, maxmult=0;
212         unsigned int multipliers[32]= {
213                 50,30,40,100,55,35,45,95,90,70,80,60,120,75,85,65,
214                 -1,110,120,-1,135,115,125,105,130,150,160,140,-1,155,-1,145 };
215         unsigned int j, k = 0;
216         union msr_longhaul longhaul;
217         unsigned long lo, hi;
218         unsigned int eblcr_fsb_table[] = { 66, 133, 100, -1 };
219
220         switch (longhaul_version) {
221         case 1:
222                 /* Ugh, Longhaul v1 didn't have the min/max MSRs.
223                    Assume min=3.0x & max = whatever we booted at. */
224                 minmult = 30;
225                 maxmult = longhaul_get_cpu_mult();
226                 rdmsr (MSR_IA32_EBL_CR_POWERON, lo, hi);
227                 invalue = (lo & (1<<18|1<<19)) >>18;
228                 if (c->x86_model==6)
229                         fsb = eblcr_fsb_table[invalue];
230                 else
231                         fsb = guess_fsb(maxmult);
232                 break;
233
234         case 2:
235                 rdmsrl (MSR_VIA_LONGHAUL, longhaul.val);
236
237                 //TODO: Nehemiah may have borken MaxMHzBR.
238                 // need to extrapolate from FSB.
239                 invalue = longhaul.bits.MaxMHzBR;
240                 if (longhaul.bits.MaxMHzBR4)
241                         invalue += 16;
242                 maxmult=multipliers[invalue];
243
244                 invalue = longhaul.bits.MinMHzBR;
245                 if (longhaul.bits.MinMHzBR4 == 1)
246                         minmult = 30;
247                 else
248                         minmult = multipliers[invalue];
249
250                 switch (longhaul.bits.MaxMHzFSB) {
251                 case 0x0:       fsb=133;
252                                 break;
253                 case 0x1:       fsb=100;
254                                 break;
255                 case 0x2:       printk (KERN_INFO PFX "Invalid (reserved) FSB!\n");
256                         return -EINVAL;
257                 case 0x3:       fsb=66;
258                                 break;
259                 }
260                 break;
261         }
262
263         dprintk (KERN_INFO PFX "MinMult=%d.%dx MaxMult=%d.%dx\n",
264                  minmult/10, minmult%10, maxmult/10, maxmult%10);
265         highest_speed = calc_speed (maxmult, fsb);
266         lowest_speed = calc_speed (minmult,fsb);
267         dprintk (KERN_INFO PFX "FSB: %dMHz Lowestspeed=%dMHz Highestspeed=%dMHz\n",
268                  fsb, lowest_speed/1000, highest_speed/1000);
269
270         if (lowest_speed == highest_speed) {
271                 printk (KERN_INFO PFX "highestspeed == lowest, aborting.\n");
272                 return -EINVAL;
273         }
274         if (lowest_speed > highest_speed) {
275                 printk (KERN_INFO PFX "nonsense! lowest (%d > %d) !\n",
276                         lowest_speed, highest_speed);
277                 return -EINVAL;
278         }
279
280         longhaul_table = kmalloc((numscales + 1) * sizeof(struct cpufreq_frequency_table), GFP_KERNEL);
281         if(!longhaul_table)
282                 return -ENOMEM;
283
284         for (j=0; j < numscales; j++) {
285                 unsigned int ratio;
286                 ratio = clock_ratio[j];
287                 if (ratio == -1)
288                         continue;
289                 if (ratio > maxmult || ratio < minmult)
290                         continue;
291                 longhaul_table[k].frequency = calc_speed (ratio, fsb);
292                 longhaul_table[k].index = j;
293                 k++;
294         }
295
296         longhaul_table[k].frequency = CPUFREQ_TABLE_END;
297         if (!k) {
298                 kfree (longhaul_table);
299                 return -EINVAL;
300         }
301
302         return 0;
303 }
304
305
306 static void __init longhaul_setup_voltagescaling(void)
307 {
308         union msr_longhaul longhaul;
309
310         rdmsrl (MSR_VIA_LONGHAUL, longhaul.val);
311
312         if (!(longhaul.bits.RevisionID & 1))
313                 return;
314
315         minvid = longhaul.bits.MinimumVID;
316         maxvid = longhaul.bits.MaximumVID;
317         vrmrev = longhaul.bits.VRMRev;
318
319         if (minvid == 0 || maxvid == 0) {
320                 printk (KERN_INFO PFX "Bogus values Min:%d.%03d Max:%d.%03d. "
321                                         "Voltage scaling disabled.\n",
322                                         minvid/1000, minvid%1000, maxvid/1000, maxvid%1000);
323                 return;
324         }
325
326         if (minvid == maxvid) {
327                 printk (KERN_INFO PFX "Claims to support voltage scaling but min & max are "
328                                 "both %d.%03d. Voltage scaling disabled\n",
329                                 maxvid/1000, maxvid%1000);
330                 return;
331         }
332
333         if (vrmrev==0) {
334                 dprintk (KERN_INFO PFX "VRM 8.5 : ");
335                 memcpy (voltage_table, vrm85scales, sizeof(voltage_table));
336                 numvscales = (voltage_table[maxvid]-voltage_table[minvid])/25;
337         } else {
338                 dprintk (KERN_INFO PFX "Mobile VRM : ");
339                 memcpy (voltage_table, mobilevrmscales, sizeof(voltage_table));
340                 numvscales = (voltage_table[maxvid]-voltage_table[minvid])/5;
341         }
342
343         /* Current voltage isn't readable at first, so we need to
344            set it to a known value. The spec says to use maxvid */
345         longhaul.bits.RevisionKey = longhaul.bits.RevisionID;   /* FIXME: This is bad. */
346         longhaul.bits.EnableSoftVID = 1;
347         longhaul.bits.SoftVID = maxvid;
348         wrmsrl (MSR_VIA_LONGHAUL, longhaul.val);
349
350         minvid = voltage_table[minvid];
351         maxvid = voltage_table[maxvid];
352
353         dprintk ("Min VID=%d.%03d Max VID=%d.%03d, %d possible voltage scales\n",
354                 maxvid/1000, maxvid%1000, minvid/1000, minvid%1000, numvscales);
355
356         can_scale_voltage = 1;
357 }
358
359
360 static int longhaul_verify(struct cpufreq_policy *policy)
361 {
362         return cpufreq_frequency_table_verify(policy, longhaul_table);
363 }
364
365
366 static int longhaul_target (struct cpufreq_policy *policy,
367                             unsigned int target_freq,
368                             unsigned int relation)
369 {
370         unsigned int table_index = 0;
371         unsigned int new_clock_ratio = 0;
372
373         if (cpufreq_frequency_table_target(policy, longhaul_table, target_freq, relation, &table_index))
374                 return -EINVAL;
375
376         new_clock_ratio = longhaul_table[table_index].index & 0xFF;
377  
378         longhaul_setstate(new_clock_ratio);
379
380         return 0;
381 }
382
383 static int __init longhaul_cpu_init (struct cpufreq_policy *policy)
384 {
385         struct cpuinfo_x86 *c = cpu_data;
386         char *cpuname=NULL;
387         int ret;
388
389         switch (c->x86_model) {
390         case 6:
391                 cpuname = "C3 'Samuel' [C5A]";
392                 longhaul_version=1;
393                 memcpy (clock_ratio, samuel1_clock_ratio, sizeof(samuel1_clock_ratio));
394                 memcpy (eblcr_table, samuel1_eblcr, sizeof(samuel1_eblcr));
395                 break;
396
397         case 7:         /* C5B / C5C */
398                 longhaul_version=1;
399                 switch (c->x86_mask) {
400                 case 0:
401                         cpuname = "C3 'Samuel 2' [C5B]";
402                         /* Note, this is not a typo, early Samuel2's had Samuel1 ratios. */
403                         memcpy (clock_ratio, samuel1_clock_ratio, sizeof(samuel1_clock_ratio));
404                         memcpy (eblcr_table, samuel2_eblcr, sizeof(samuel2_eblcr));
405                         break;
406                 case 1 ... 15:
407                         if (c->x86_mask < 8)
408                                 cpuname = "C3 'Samuel 2' [C5B]";
409                         else
410                                 cpuname = "C3 'Ezra' [C5C]";
411                         memcpy (clock_ratio, ezra_clock_ratio, sizeof(ezra_clock_ratio));
412                         memcpy (eblcr_table, ezra_eblcr, sizeof(ezra_eblcr));
413                         break;
414                 }
415                 break;
416
417         case 8:
418                 cpuname = "C3 'Ezra-T' [C5M]";
419                 longhaul_version=2;
420                 numscales=32;
421                 memcpy (clock_ratio, ezrat_clock_ratio, sizeof(ezrat_clock_ratio));
422                 memcpy (eblcr_table, ezrat_eblcr, sizeof(ezrat_eblcr));
423                 break;
424
425         case 9:
426                 cpuname = "C3 'Nehemiah' [C5N]";
427                 longhaul_version=2;
428                 numscales=32;
429                 memcpy (clock_ratio, nehemiah_clock_ratio, sizeof(nehemiah_clock_ratio));
430                 memcpy (eblcr_table, nehemiah_eblcr, sizeof(nehemiah_eblcr));
431                 break;
432
433         default:
434                 cpuname = "Unknown";
435                 break;
436         }
437
438         printk (KERN_INFO PFX "VIA %s CPU detected. Longhaul v%d supported.\n",
439                                         cpuname, longhaul_version);
440
441         ret = longhaul_get_ranges();
442         if (ret != 0)
443                 return ret;
444
445         if ((longhaul_version==2) && (dont_scale_voltage==0))
446                 longhaul_setup_voltagescaling();
447
448         policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
449         policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
450         policy->cur = calc_speed (longhaul_get_cpu_mult(), fsb);
451
452         ret = cpufreq_frequency_table_cpuinfo(policy, longhaul_table);
453         if (ret)
454                 return ret;
455
456         cpufreq_frequency_table_get_attr(longhaul_table, policy->cpu);
457
458         return 0;
459 }
460
461 static int __exit longhaul_cpu_exit(struct cpufreq_policy *policy)
462 {
463         cpufreq_frequency_table_put_attr(policy->cpu);
464         return 0;
465 }
466
467 static struct freq_attr* longhaul_attr[] = {
468         &cpufreq_freq_attr_scaling_available_freqs,
469         NULL,
470 };
471
472 static struct cpufreq_driver longhaul_driver = {
473         .verify         = longhaul_verify,
474         .target         = longhaul_target,
475         .init           = longhaul_cpu_init,
476         .exit           = longhaul_cpu_exit,
477         .name           = "longhaul",
478         .owner          = THIS_MODULE,
479         .attr           = longhaul_attr,
480 };
481
482 static int __init longhaul_init (void)
483 {
484         struct cpuinfo_x86 *c = cpu_data;
485
486         if (c->x86_vendor != X86_VENDOR_CENTAUR || c->x86 != 6)
487                 return -ENODEV;
488
489         switch (c->x86_model) {
490         case 6 ... 8:
491                 return cpufreq_register_driver(&longhaul_driver);
492         case 9:
493                 printk (KERN_INFO PFX "Nehemiah unsupported: Waiting on working silicon "
494                                                 "from VIA before this is usable.\n");
495                 break;
496         default:
497                 printk (KERN_INFO PFX "Unknown VIA CPU. Contact davej@codemonkey.org.uk\n");
498         }
499
500         return -ENODEV;
501 }
502
503 static void __exit longhaul_exit (void)
504 {
505         cpufreq_unregister_driver(&longhaul_driver);
506         kfree(longhaul_table);
507 }
508
509 MODULE_PARM (dont_scale_voltage, "i");
510
511 MODULE_AUTHOR ("Dave Jones <davej@codemonkey.org.uk>");
512 MODULE_DESCRIPTION ("Longhaul driver for VIA Cyrix processors.");
513 MODULE_LICENSE ("GPL");
514
515 module_init(longhaul_init);
516 module_exit(longhaul_exit);
517