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