]> Pileus Git - ~andy/linux/blob - drivers/cpufreq/e_powersaver.c
[CPUFREQ] e_powersaver: Additional checks
[~andy/linux] / drivers / cpufreq / e_powersaver.c
1 /*
2  *  Based on documentation provided by Dave Jones. Thanks!
3  *
4  *  Licensed under the terms of the GNU GPL License version 2.
5  *
6  *  BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous*
7  */
8
9 #include <linux/kernel.h>
10 #include <linux/module.h>
11 #include <linux/init.h>
12 #include <linux/cpufreq.h>
13 #include <linux/ioport.h>
14 #include <linux/slab.h>
15 #include <linux/timex.h>
16 #include <linux/io.h>
17 #include <linux/delay.h>
18
19 #include <asm/msr.h>
20 #include <asm/tsc.h>
21
22 #define EPS_BRAND_C7M   0
23 #define EPS_BRAND_C7    1
24 #define EPS_BRAND_EDEN  2
25 #define EPS_BRAND_C3    3
26 #define EPS_BRAND_C7D   4
27
28 struct eps_cpu_data {
29         u32 fsb;
30         struct cpufreq_frequency_table freq_table[];
31 };
32
33 static struct eps_cpu_data *eps_cpu[NR_CPUS];
34
35 /* Module parameters */
36 static int freq_failsafe_off;
37 static int voltage_failsafe_off;
38
39
40 static unsigned int eps_get(unsigned int cpu)
41 {
42         struct eps_cpu_data *centaur;
43         u32 lo, hi;
44
45         if (cpu)
46                 return 0;
47         centaur = eps_cpu[cpu];
48         if (centaur == NULL)
49                 return 0;
50
51         /* Return current frequency */
52         rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
53         return centaur->fsb * ((lo >> 8) & 0xff);
54 }
55
56 static int eps_set_state(struct eps_cpu_data *centaur,
57                          unsigned int cpu,
58                          u32 dest_state)
59 {
60         struct cpufreq_freqs freqs;
61         u32 lo, hi;
62         int err = 0;
63         int i;
64
65         freqs.old = eps_get(cpu);
66         freqs.new = centaur->fsb * ((dest_state >> 8) & 0xff);
67         freqs.cpu = cpu;
68         cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE);
69
70         /* Wait while CPU is busy */
71         rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
72         i = 0;
73         while (lo & ((1 << 16) | (1 << 17))) {
74                 udelay(16);
75                 rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
76                 i++;
77                 if (unlikely(i > 64)) {
78                         err = -ENODEV;
79                         goto postchange;
80                 }
81         }
82         /* Set new multiplier and voltage */
83         wrmsr(MSR_IA32_PERF_CTL, dest_state & 0xffff, 0);
84         /* Wait until transition end */
85         i = 0;
86         do {
87                 udelay(16);
88                 rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
89                 i++;
90                 if (unlikely(i > 64)) {
91                         err = -ENODEV;
92                         goto postchange;
93                 }
94         } while (lo & ((1 << 16) | (1 << 17)));
95
96         /* Return current frequency */
97 postchange:
98         rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
99         freqs.new = centaur->fsb * ((lo >> 8) & 0xff);
100
101 #ifdef DEBUG
102         {
103         u8 current_multiplier, current_voltage;
104
105         /* Print voltage and multiplier */
106         rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
107         current_voltage = lo & 0xff;
108         printk(KERN_INFO "eps: Current voltage = %dmV\n",
109                 current_voltage * 16 + 700);
110         current_multiplier = (lo >> 8) & 0xff;
111         printk(KERN_INFO "eps: Current multiplier = %d\n",
112                 current_multiplier);
113         }
114 #endif
115         cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE);
116         return err;
117 }
118
119 static int eps_target(struct cpufreq_policy *policy,
120                                unsigned int target_freq,
121                                unsigned int relation)
122 {
123         struct eps_cpu_data *centaur;
124         unsigned int newstate = 0;
125         unsigned int cpu = policy->cpu;
126         unsigned int dest_state;
127         int ret;
128
129         if (unlikely(eps_cpu[cpu] == NULL))
130                 return -ENODEV;
131         centaur = eps_cpu[cpu];
132
133         if (unlikely(cpufreq_frequency_table_target(policy,
134                         &eps_cpu[cpu]->freq_table[0],
135                         target_freq,
136                         relation,
137                         &newstate))) {
138                 return -EINVAL;
139         }
140
141         /* Make frequency transition */
142         dest_state = centaur->freq_table[newstate].index & 0xffff;
143         ret = eps_set_state(centaur, cpu, dest_state);
144         if (ret)
145                 printk(KERN_ERR "eps: Timeout!\n");
146         return ret;
147 }
148
149 static int eps_verify(struct cpufreq_policy *policy)
150 {
151         return cpufreq_frequency_table_verify(policy,
152                         &eps_cpu[policy->cpu]->freq_table[0]);
153 }
154
155 static int eps_cpu_init(struct cpufreq_policy *policy)
156 {
157         unsigned int i;
158         u32 lo, hi;
159         u64 val;
160         u8 current_multiplier, current_voltage;
161         u8 max_multiplier, max_voltage;
162         u8 min_multiplier, min_voltage;
163         u8 brand = 0;
164         u32 fsb;
165         struct eps_cpu_data *centaur;
166         struct cpuinfo_x86 *c = &cpu_data(0);
167         struct cpufreq_frequency_table *f_table;
168         int k, step, voltage;
169         int ret;
170         int states;
171
172         if (policy->cpu != 0)
173                 return -ENODEV;
174
175         /* Check brand */
176         printk(KERN_INFO "eps: Detected VIA ");
177
178         switch (c->x86_model) {
179         case 10:
180                 rdmsr(0x1153, lo, hi);
181                 brand = (((lo >> 2) ^ lo) >> 18) & 3;
182                 printk(KERN_CONT "Model A ");
183                 break;
184         case 13:
185                 rdmsr(0x1154, lo, hi);
186                 brand = (((lo >> 4) ^ (lo >> 2))) & 0x000000ff;
187                 printk(KERN_CONT "Model D ");
188                 break;
189         }
190
191         switch (brand) {
192         case EPS_BRAND_C7M:
193                 printk(KERN_CONT "C7-M\n");
194                 break;
195         case EPS_BRAND_C7:
196                 printk(KERN_CONT "C7\n");
197                 break;
198         case EPS_BRAND_EDEN:
199                 printk(KERN_CONT "Eden\n");
200                 break;
201         case EPS_BRAND_C7D:
202                 printk(KERN_CONT "C7-D\n");
203                 break;
204         case EPS_BRAND_C3:
205                 printk(KERN_CONT "C3\n");
206                 return -ENODEV;
207                 break;
208         }
209         /* Enable Enhanced PowerSaver */
210         rdmsrl(MSR_IA32_MISC_ENABLE, val);
211         if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
212                 val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP;
213                 wrmsrl(MSR_IA32_MISC_ENABLE, val);
214                 /* Can be locked at 0 */
215                 rdmsrl(MSR_IA32_MISC_ENABLE, val);
216                 if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) {
217                         printk(KERN_INFO "eps: Can't enable Enhanced PowerSaver\n");
218                         return -ENODEV;
219                 }
220         }
221
222         /* Print voltage and multiplier */
223         rdmsr(MSR_IA32_PERF_STATUS, lo, hi);
224         current_voltage = lo & 0xff;
225         printk(KERN_INFO "eps: Current voltage = %dmV\n",
226                         current_voltage * 16 + 700);
227         current_multiplier = (lo >> 8) & 0xff;
228         printk(KERN_INFO "eps: Current multiplier = %d\n", current_multiplier);
229
230         /* Print limits */
231         max_voltage = hi & 0xff;
232         printk(KERN_INFO "eps: Highest voltage = %dmV\n",
233                         max_voltage * 16 + 700);
234         max_multiplier = (hi >> 8) & 0xff;
235         printk(KERN_INFO "eps: Highest multiplier = %d\n", max_multiplier);
236         min_voltage = (hi >> 16) & 0xff;
237         printk(KERN_INFO "eps: Lowest voltage = %dmV\n",
238                         min_voltage * 16 + 700);
239         min_multiplier = (hi >> 24) & 0xff;
240         printk(KERN_INFO "eps: Lowest multiplier = %d\n", min_multiplier);
241
242         /* Sanity checks */
243         if (current_multiplier == 0 || max_multiplier == 0
244             || min_multiplier == 0)
245                 return -EINVAL;
246         if (current_multiplier > max_multiplier
247             || max_multiplier <= min_multiplier)
248                 return -EINVAL;
249         if (current_voltage > 0x1f || max_voltage > 0x1f)
250                 return -EINVAL;
251         if (max_voltage < min_voltage
252             || current_voltage < min_voltage
253             || current_voltage > max_voltage)
254                 return -EINVAL;
255
256         /* Check for systems using underclocked CPU */
257         if (!freq_failsafe_off && max_multiplier != current_multiplier) {
258                 printk(KERN_INFO "eps: Your processor is running at different "
259                         "frequency then its maximum. Aborting.\n");
260                 printk(KERN_INFO "eps: You can use freq_failsafe_off option "
261                         "to disable this check.\n");
262                 return -EINVAL;
263         }
264         if (!voltage_failsafe_off && max_voltage != current_voltage) {
265                 printk(KERN_INFO "eps: Your processor is running at different "
266                         "voltage then its maximum. Aborting.\n");
267                 printk(KERN_INFO "eps: You can use voltage_failsafe_off "
268                         "option to disable this check.\n");
269                 return -EINVAL;
270         }
271
272         /* Calc FSB speed */
273         fsb = cpu_khz / current_multiplier;
274         /* Calc number of p-states supported */
275         if (brand == EPS_BRAND_C7M)
276                 states = max_multiplier - min_multiplier + 1;
277         else
278                 states = 2;
279
280         /* Allocate private data and frequency table for current cpu */
281         centaur = kzalloc(sizeof(struct eps_cpu_data)
282                     + (states + 1) * sizeof(struct cpufreq_frequency_table),
283                     GFP_KERNEL);
284         if (!centaur)
285                 return -ENOMEM;
286         eps_cpu[0] = centaur;
287
288         /* Copy basic values */
289         centaur->fsb = fsb;
290
291         /* Fill frequency and MSR value table */
292         f_table = &centaur->freq_table[0];
293         if (brand != EPS_BRAND_C7M) {
294                 f_table[0].frequency = fsb * min_multiplier;
295                 f_table[0].index = (min_multiplier << 8) | min_voltage;
296                 f_table[1].frequency = fsb * max_multiplier;
297                 f_table[1].index = (max_multiplier << 8) | max_voltage;
298                 f_table[2].frequency = CPUFREQ_TABLE_END;
299         } else {
300                 k = 0;
301                 step = ((max_voltage - min_voltage) * 256)
302                         / (max_multiplier - min_multiplier);
303                 for (i = min_multiplier; i <= max_multiplier; i++) {
304                         voltage = (k * step) / 256 + min_voltage;
305                         f_table[k].frequency = fsb * i;
306                         f_table[k].index = (i << 8) | voltage;
307                         k++;
308                 }
309                 f_table[k].frequency = CPUFREQ_TABLE_END;
310         }
311
312         policy->cpuinfo.transition_latency = 140000; /* 844mV -> 700mV in ns */
313         policy->cur = fsb * current_multiplier;
314
315         ret = cpufreq_frequency_table_cpuinfo(policy, &centaur->freq_table[0]);
316         if (ret) {
317                 kfree(centaur);
318                 return ret;
319         }
320
321         cpufreq_frequency_table_get_attr(&centaur->freq_table[0], policy->cpu);
322         return 0;
323 }
324
325 static int eps_cpu_exit(struct cpufreq_policy *policy)
326 {
327         unsigned int cpu = policy->cpu;
328
329         /* Bye */
330         cpufreq_frequency_table_put_attr(policy->cpu);
331         kfree(eps_cpu[cpu]);
332         eps_cpu[cpu] = NULL;
333         return 0;
334 }
335
336 static struct freq_attr *eps_attr[] = {
337         &cpufreq_freq_attr_scaling_available_freqs,
338         NULL,
339 };
340
341 static struct cpufreq_driver eps_driver = {
342         .verify         = eps_verify,
343         .target         = eps_target,
344         .init           = eps_cpu_init,
345         .exit           = eps_cpu_exit,
346         .get            = eps_get,
347         .name           = "e_powersaver",
348         .owner          = THIS_MODULE,
349         .attr           = eps_attr,
350 };
351
352 static int __init eps_init(void)
353 {
354         struct cpuinfo_x86 *c = &cpu_data(0);
355
356         /* This driver will work only on Centaur C7 processors with
357          * Enhanced SpeedStep/PowerSaver registers */
358         if (c->x86_vendor != X86_VENDOR_CENTAUR
359             || c->x86 != 6 || c->x86_model < 10)
360                 return -ENODEV;
361         if (!cpu_has(c, X86_FEATURE_EST))
362                 return -ENODEV;
363
364         if (cpufreq_register_driver(&eps_driver))
365                 return -EINVAL;
366         return 0;
367 }
368
369 static void __exit eps_exit(void)
370 {
371         cpufreq_unregister_driver(&eps_driver);
372 }
373
374 /* Allow user to overclock his machine or to change frequency to higher after
375  * unloading module */
376 module_param(freq_failsafe_off, int, 0644);
377 MODULE_PARM_DESC(freq_failsafe_off, "Disable current vs max frequency check");
378 module_param(voltage_failsafe_off, int, 0644);
379 MODULE_PARM_DESC(voltage_failsafe_off, "Disable current vs max voltage check");
380
381 MODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>");
382 MODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's.");
383 MODULE_LICENSE("GPL");
384
385 module_init(eps_init);
386 module_exit(eps_exit);