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