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