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