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