424 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*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 ": " fmt9 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#endif28 29#define EPS_BRAND_C7M 030#define EPS_BRAND_C7 131#define EPS_BRAND_EDEN 232#define EPS_BRAND_C3 333#define EPS_BRAND_C7D 434 35struct eps_cpu_data {36 u32 fsb;37#if IS_ENABLED(CONFIG_ACPI_PROCESSOR)38 u32 bios_limit;39#endif40 struct cpufreq_frequency_table freq_table[];41};42 43static struct eps_cpu_data *eps_cpu[NR_CPUS];44 45/* Module parameters */46static int freq_failsafe_off;47static int voltage_failsafe_off;48static int set_max_voltage;49 50#if IS_ENABLED(CONFIG_ACPI_PROCESSOR)51static int ignore_acpi_limit;52 53static struct acpi_processor_performance *eps_acpi_cpu_perf;54 55/* Minimum necessary to get acpi_processor_get_bios_limit() working */56static int eps_acpi_init(void)57{58 eps_acpi_cpu_perf = kzalloc(sizeof(*eps_acpi_cpu_perf),59 GFP_KERNEL);60 if (!eps_acpi_cpu_perf)61 return -ENOMEM;62 63 if (!zalloc_cpumask_var(&eps_acpi_cpu_perf->shared_cpu_map,64 GFP_KERNEL)) {65 kfree(eps_acpi_cpu_perf);66 eps_acpi_cpu_perf = NULL;67 return -ENOMEM;68 }69 70 if (acpi_processor_register_performance(eps_acpi_cpu_perf, 0)) {71 free_cpumask_var(eps_acpi_cpu_perf->shared_cpu_map);72 kfree(eps_acpi_cpu_perf);73 eps_acpi_cpu_perf = NULL;74 return -EIO;75 }76 return 0;77}78 79static int eps_acpi_exit(struct cpufreq_policy *policy)80{81 if (eps_acpi_cpu_perf) {82 acpi_processor_unregister_performance(0);83 free_cpumask_var(eps_acpi_cpu_perf->shared_cpu_map);84 kfree(eps_acpi_cpu_perf);85 eps_acpi_cpu_perf = NULL;86 }87 return 0;88}89#endif90 91static 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 107static 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 DEBUG139 {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#endif150 return 0;151}152 153static 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 172static 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#endif190 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 == 0259 || min_multiplier == 0)260 return -EINVAL;261 if (current_multiplier > max_multiplier262 || max_multiplier <= min_multiplier)263 return -EINVAL;264 if (current_voltage > 0x1f || max_voltage > 0x1f)265 return -EINVAL;266 if (max_voltage < min_voltage267 || current_voltage < min_voltage268 || 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(policy->cpu, &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#endif302 303 /* Allow user to set lower maximum voltage then that reported304 * 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 else321 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#endif335 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 363static void eps_cpu_exit(struct cpufreq_policy *policy)364{365 unsigned int cpu = policy->cpu;366 367 /* Bye */368 kfree(eps_cpu[cpu]);369 eps_cpu[cpu] = NULL;370}371 372static struct cpufreq_driver eps_driver = {373 .verify = cpufreq_generic_frequency_table_verify,374 .target_index = eps_target,375 .init = eps_cpu_init,376 .exit = eps_cpu_exit,377 .get = eps_get,378 .name = "e_powersaver",379 .attr = cpufreq_generic_attr,380};381 382 383/* This driver will work only on Centaur C7 processors with384 * Enhanced SpeedStep/PowerSaver registers */385static const struct x86_cpu_id eps_cpu_id[] = {386 X86_MATCH_VENDOR_FAM_FEATURE(CENTAUR, 6, X86_FEATURE_EST, NULL),387 {}388};389MODULE_DEVICE_TABLE(x86cpu, eps_cpu_id);390 391static int __init eps_init(void)392{393 if (!x86_match_cpu(eps_cpu_id) || boot_cpu_data.x86_model < 10)394 return -ENODEV;395 if (cpufreq_register_driver(&eps_driver))396 return -EINVAL;397 return 0;398}399 400static void __exit eps_exit(void)401{402 cpufreq_unregister_driver(&eps_driver);403}404 405/* Allow user to overclock his machine or to change frequency to higher after406 * unloading module */407module_param(freq_failsafe_off, int, 0644);408MODULE_PARM_DESC(freq_failsafe_off, "Disable current vs max frequency check");409module_param(voltage_failsafe_off, int, 0644);410MODULE_PARM_DESC(voltage_failsafe_off, "Disable current vs max voltage check");411#if IS_ENABLED(CONFIG_ACPI_PROCESSOR)412module_param(ignore_acpi_limit, int, 0644);413MODULE_PARM_DESC(ignore_acpi_limit, "Don't check ACPI's processor speed limit");414#endif415module_param(set_max_voltage, int, 0644);416MODULE_PARM_DESC(set_max_voltage, "Set maximum CPU voltage (mV) C7-M only");417 418MODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>");419MODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's.");420MODULE_LICENSE("GPL");421 422module_init(eps_init);423module_exit(eps_exit);424