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