5510cd6a28f62114a9c781999196459b7b2996e6
[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         if (longhaul_version==4) {
86                 if (lo & (1<<27))
87                         invalue+=16;
88         }
89         return eblcr_table[invalue];
90 }
91
92
93 /**
94  * longhaul_set_cpu_frequency()
95  * @clock_ratio_index : bitpattern of the new multiplier.
96  *
97  * Sets a new clock ratio, and -if applicable- a new Front Side Bus
98  */
99
100 static void longhaul_setstate (unsigned int clock_ratio_index)
101 {
102         int speed, mult;
103         struct cpufreq_freqs freqs;
104         union msr_longhaul longhaul;
105         union msr_bcr2 bcr2;
106
107         mult = clock_ratio[clock_ratio_index];
108         if (mult == -1)
109                 return;
110
111         speed = calc_speed (mult, fsb);
112         if ((speed > highest_speed) || (speed < lowest_speed))
113                 return;
114
115         freqs.old = calc_speed (longhaul_get_cpu_mult(), fsb);
116         freqs.new = speed;
117         freqs.cpu = 0; /* longhaul.c is UP only driver */
118
119         cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
120
121         dprintk (KERN_INFO PFX "FSB:%d Mult:%d.%dx\n", fsb,
122                                 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
505         default:
506                 cpuname = "Unknown";
507                 break;
508         }
509
510         printk (KERN_INFO PFX "VIA %s CPU detected. Longhaul v%d supported.\n",
511                                         cpuname, longhaul_version);
512
513         ret = longhaul_get_ranges();
514         if (ret != 0)
515                 return ret;
516
517         if ((longhaul_version==2) && (dont_scale_voltage==0))
518                 longhaul_setup_voltagescaling();
519
520         policy->governor = CPUFREQ_DEFAULT_GOVERNOR;
521         policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL;
522         policy->cur = calc_speed (longhaul_get_cpu_mult(), fsb);
523
524         ret = cpufreq_frequency_table_cpuinfo(policy, longhaul_table);
525         if (ret)
526                 return ret;
527
528         cpufreq_frequency_table_get_attr(longhaul_table, policy->cpu);
529
530         return 0;
531 }
532
533 static int __exit longhaul_cpu_exit(struct cpufreq_policy *policy)
534 {
535         cpufreq_frequency_table_put_attr(policy->cpu);
536         return 0;
537 }
538
539 static struct freq_attr* longhaul_attr[] = {
540         &cpufreq_freq_attr_scaling_available_freqs,
541         NULL,
542 };
543
544 static struct cpufreq_driver longhaul_driver = {
545         .verify         = longhaul_verify,
546         .target         = longhaul_target,
547         .get            = longhaul_get,
548         .init           = longhaul_cpu_init,
549         .exit           = longhaul_cpu_exit,
550         .name           = "longhaul",
551         .owner          = THIS_MODULE,
552         .attr           = longhaul_attr,
553 };
554
555 static int __init longhaul_init (void)
556 {
557         struct cpuinfo_x86 *c = cpu_data;
558
559         if (c->x86_vendor != X86_VENDOR_CENTAUR || c->x86 != 6)
560                 return -ENODEV;
561
562         switch (c->x86_model) {
563         case 6 ... 8:
564                 return cpufreq_register_driver(&longhaul_driver);
565         case 9:
566                 printk (KERN_INFO PFX "Nehemiah unsupported: Waiting on working silicon "
567                                                 "from VIA before this is usable.\n");
568                 break;
569         default:
570                 printk (KERN_INFO PFX "Unknown VIA CPU. Contact davej@codemonkey.org.uk\n");
571         }
572
573         return -ENODEV;
574 }
575
576 static void __exit longhaul_exit (void)
577 {
578         cpufreq_unregister_driver(&longhaul_driver);
579         kfree(longhaul_table);
580 }
581
582 MODULE_PARM (dont_scale_voltage, "i");
583
584 MODULE_AUTHOR ("Dave Jones <davej@codemonkey.org.uk>");
585 MODULE_DESCRIPTION ("Longhaul driver for VIA Cyrix processors.");
586 MODULE_LICENSE ("GPL");
587
588 module_init(longhaul_init);
589 module_exit(longhaul_exit);
590