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