18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Based on documentation provided by Dave Jones. Thanks! 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 98c2ecf20Sopenharmony_ci 108c2ecf20Sopenharmony_ci#include <linux/kernel.h> 118c2ecf20Sopenharmony_ci#include <linux/module.h> 128c2ecf20Sopenharmony_ci#include <linux/init.h> 138c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 148c2ecf20Sopenharmony_ci#include <linux/ioport.h> 158c2ecf20Sopenharmony_ci#include <linux/slab.h> 168c2ecf20Sopenharmony_ci#include <linux/timex.h> 178c2ecf20Sopenharmony_ci#include <linux/io.h> 188c2ecf20Sopenharmony_ci#include <linux/delay.h> 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h> 218c2ecf20Sopenharmony_ci#include <asm/msr.h> 228c2ecf20Sopenharmony_ci#include <asm/tsc.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 258c2ecf20Sopenharmony_ci#include <linux/acpi.h> 268c2ecf20Sopenharmony_ci#include <acpi/processor.h> 278c2ecf20Sopenharmony_ci#endif 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci#define EPS_BRAND_C7M 0 308c2ecf20Sopenharmony_ci#define EPS_BRAND_C7 1 318c2ecf20Sopenharmony_ci#define EPS_BRAND_EDEN 2 328c2ecf20Sopenharmony_ci#define EPS_BRAND_C3 3 338c2ecf20Sopenharmony_ci#define EPS_BRAND_C7D 4 348c2ecf20Sopenharmony_ci 358c2ecf20Sopenharmony_cistruct eps_cpu_data { 368c2ecf20Sopenharmony_ci u32 fsb; 378c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 388c2ecf20Sopenharmony_ci u32 bios_limit; 398c2ecf20Sopenharmony_ci#endif 408c2ecf20Sopenharmony_ci struct cpufreq_frequency_table freq_table[]; 418c2ecf20Sopenharmony_ci}; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_cistatic struct eps_cpu_data *eps_cpu[NR_CPUS]; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci/* Module parameters */ 468c2ecf20Sopenharmony_cistatic int freq_failsafe_off; 478c2ecf20Sopenharmony_cistatic int voltage_failsafe_off; 488c2ecf20Sopenharmony_cistatic int set_max_voltage; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 518c2ecf20Sopenharmony_cistatic int ignore_acpi_limit; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_cistatic struct acpi_processor_performance *eps_acpi_cpu_perf; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci/* Minimum necessary to get acpi_processor_get_bios_limit() working */ 568c2ecf20Sopenharmony_cistatic int eps_acpi_init(void) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci eps_acpi_cpu_perf = kzalloc(sizeof(*eps_acpi_cpu_perf), 598c2ecf20Sopenharmony_ci GFP_KERNEL); 608c2ecf20Sopenharmony_ci if (!eps_acpi_cpu_perf) 618c2ecf20Sopenharmony_ci return -ENOMEM; 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci if (!zalloc_cpumask_var(&eps_acpi_cpu_perf->shared_cpu_map, 648c2ecf20Sopenharmony_ci GFP_KERNEL)) { 658c2ecf20Sopenharmony_ci kfree(eps_acpi_cpu_perf); 668c2ecf20Sopenharmony_ci eps_acpi_cpu_perf = NULL; 678c2ecf20Sopenharmony_ci return -ENOMEM; 688c2ecf20Sopenharmony_ci } 698c2ecf20Sopenharmony_ci 708c2ecf20Sopenharmony_ci if (acpi_processor_register_performance(eps_acpi_cpu_perf, 0)) { 718c2ecf20Sopenharmony_ci free_cpumask_var(eps_acpi_cpu_perf->shared_cpu_map); 728c2ecf20Sopenharmony_ci kfree(eps_acpi_cpu_perf); 738c2ecf20Sopenharmony_ci eps_acpi_cpu_perf = NULL; 748c2ecf20Sopenharmony_ci return -EIO; 758c2ecf20Sopenharmony_ci } 768c2ecf20Sopenharmony_ci return 0; 778c2ecf20Sopenharmony_ci} 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_cistatic int eps_acpi_exit(struct cpufreq_policy *policy) 808c2ecf20Sopenharmony_ci{ 818c2ecf20Sopenharmony_ci if (eps_acpi_cpu_perf) { 828c2ecf20Sopenharmony_ci acpi_processor_unregister_performance(0); 838c2ecf20Sopenharmony_ci free_cpumask_var(eps_acpi_cpu_perf->shared_cpu_map); 848c2ecf20Sopenharmony_ci kfree(eps_acpi_cpu_perf); 858c2ecf20Sopenharmony_ci eps_acpi_cpu_perf = NULL; 868c2ecf20Sopenharmony_ci } 878c2ecf20Sopenharmony_ci return 0; 888c2ecf20Sopenharmony_ci} 898c2ecf20Sopenharmony_ci#endif 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_cistatic unsigned int eps_get(unsigned int cpu) 928c2ecf20Sopenharmony_ci{ 938c2ecf20Sopenharmony_ci struct eps_cpu_data *centaur; 948c2ecf20Sopenharmony_ci u32 lo, hi; 958c2ecf20Sopenharmony_ci 968c2ecf20Sopenharmony_ci if (cpu) 978c2ecf20Sopenharmony_ci return 0; 988c2ecf20Sopenharmony_ci centaur = eps_cpu[cpu]; 998c2ecf20Sopenharmony_ci if (centaur == NULL) 1008c2ecf20Sopenharmony_ci return 0; 1018c2ecf20Sopenharmony_ci 1028c2ecf20Sopenharmony_ci /* Return current frequency */ 1038c2ecf20Sopenharmony_ci rdmsr(MSR_IA32_PERF_STATUS, lo, hi); 1048c2ecf20Sopenharmony_ci return centaur->fsb * ((lo >> 8) & 0xff); 1058c2ecf20Sopenharmony_ci} 1068c2ecf20Sopenharmony_ci 1078c2ecf20Sopenharmony_cistatic int eps_set_state(struct eps_cpu_data *centaur, 1088c2ecf20Sopenharmony_ci struct cpufreq_policy *policy, 1098c2ecf20Sopenharmony_ci u32 dest_state) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci u32 lo, hi; 1128c2ecf20Sopenharmony_ci int i; 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci /* Wait while CPU is busy */ 1158c2ecf20Sopenharmony_ci rdmsr(MSR_IA32_PERF_STATUS, lo, hi); 1168c2ecf20Sopenharmony_ci i = 0; 1178c2ecf20Sopenharmony_ci while (lo & ((1 << 16) | (1 << 17))) { 1188c2ecf20Sopenharmony_ci udelay(16); 1198c2ecf20Sopenharmony_ci rdmsr(MSR_IA32_PERF_STATUS, lo, hi); 1208c2ecf20Sopenharmony_ci i++; 1218c2ecf20Sopenharmony_ci if (unlikely(i > 64)) { 1228c2ecf20Sopenharmony_ci return -ENODEV; 1238c2ecf20Sopenharmony_ci } 1248c2ecf20Sopenharmony_ci } 1258c2ecf20Sopenharmony_ci /* Set new multiplier and voltage */ 1268c2ecf20Sopenharmony_ci wrmsr(MSR_IA32_PERF_CTL, dest_state & 0xffff, 0); 1278c2ecf20Sopenharmony_ci /* Wait until transition end */ 1288c2ecf20Sopenharmony_ci i = 0; 1298c2ecf20Sopenharmony_ci do { 1308c2ecf20Sopenharmony_ci udelay(16); 1318c2ecf20Sopenharmony_ci rdmsr(MSR_IA32_PERF_STATUS, lo, hi); 1328c2ecf20Sopenharmony_ci i++; 1338c2ecf20Sopenharmony_ci if (unlikely(i > 64)) { 1348c2ecf20Sopenharmony_ci return -ENODEV; 1358c2ecf20Sopenharmony_ci } 1368c2ecf20Sopenharmony_ci } while (lo & ((1 << 16) | (1 << 17))); 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci#ifdef DEBUG 1398c2ecf20Sopenharmony_ci { 1408c2ecf20Sopenharmony_ci u8 current_multiplier, current_voltage; 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Print voltage and multiplier */ 1438c2ecf20Sopenharmony_ci rdmsr(MSR_IA32_PERF_STATUS, lo, hi); 1448c2ecf20Sopenharmony_ci current_voltage = lo & 0xff; 1458c2ecf20Sopenharmony_ci pr_info("Current voltage = %dmV\n", current_voltage * 16 + 700); 1468c2ecf20Sopenharmony_ci current_multiplier = (lo >> 8) & 0xff; 1478c2ecf20Sopenharmony_ci pr_info("Current multiplier = %d\n", current_multiplier); 1488c2ecf20Sopenharmony_ci } 1498c2ecf20Sopenharmony_ci#endif 1508c2ecf20Sopenharmony_ci return 0; 1518c2ecf20Sopenharmony_ci} 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_cistatic int eps_target(struct cpufreq_policy *policy, unsigned int index) 1548c2ecf20Sopenharmony_ci{ 1558c2ecf20Sopenharmony_ci struct eps_cpu_data *centaur; 1568c2ecf20Sopenharmony_ci unsigned int cpu = policy->cpu; 1578c2ecf20Sopenharmony_ci unsigned int dest_state; 1588c2ecf20Sopenharmony_ci int ret; 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci if (unlikely(eps_cpu[cpu] == NULL)) 1618c2ecf20Sopenharmony_ci return -ENODEV; 1628c2ecf20Sopenharmony_ci centaur = eps_cpu[cpu]; 1638c2ecf20Sopenharmony_ci 1648c2ecf20Sopenharmony_ci /* Make frequency transition */ 1658c2ecf20Sopenharmony_ci dest_state = centaur->freq_table[index].driver_data & 0xffff; 1668c2ecf20Sopenharmony_ci ret = eps_set_state(centaur, policy, dest_state); 1678c2ecf20Sopenharmony_ci if (ret) 1688c2ecf20Sopenharmony_ci pr_err("Timeout!\n"); 1698c2ecf20Sopenharmony_ci return ret; 1708c2ecf20Sopenharmony_ci} 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_cistatic int eps_cpu_init(struct cpufreq_policy *policy) 1738c2ecf20Sopenharmony_ci{ 1748c2ecf20Sopenharmony_ci unsigned int i; 1758c2ecf20Sopenharmony_ci u32 lo, hi; 1768c2ecf20Sopenharmony_ci u64 val; 1778c2ecf20Sopenharmony_ci u8 current_multiplier, current_voltage; 1788c2ecf20Sopenharmony_ci u8 max_multiplier, max_voltage; 1798c2ecf20Sopenharmony_ci u8 min_multiplier, min_voltage; 1808c2ecf20Sopenharmony_ci u8 brand = 0; 1818c2ecf20Sopenharmony_ci u32 fsb; 1828c2ecf20Sopenharmony_ci struct eps_cpu_data *centaur; 1838c2ecf20Sopenharmony_ci struct cpuinfo_x86 *c = &cpu_data(0); 1848c2ecf20Sopenharmony_ci struct cpufreq_frequency_table *f_table; 1858c2ecf20Sopenharmony_ci int k, step, voltage; 1868c2ecf20Sopenharmony_ci int states; 1878c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 1888c2ecf20Sopenharmony_ci unsigned int limit; 1898c2ecf20Sopenharmony_ci#endif 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (policy->cpu != 0) 1928c2ecf20Sopenharmony_ci return -ENODEV; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci /* Check brand */ 1958c2ecf20Sopenharmony_ci pr_info("Detected VIA "); 1968c2ecf20Sopenharmony_ci 1978c2ecf20Sopenharmony_ci switch (c->x86_model) { 1988c2ecf20Sopenharmony_ci case 10: 1998c2ecf20Sopenharmony_ci rdmsr(0x1153, lo, hi); 2008c2ecf20Sopenharmony_ci brand = (((lo >> 2) ^ lo) >> 18) & 3; 2018c2ecf20Sopenharmony_ci pr_cont("Model A "); 2028c2ecf20Sopenharmony_ci break; 2038c2ecf20Sopenharmony_ci case 13: 2048c2ecf20Sopenharmony_ci rdmsr(0x1154, lo, hi); 2058c2ecf20Sopenharmony_ci brand = (((lo >> 4) ^ (lo >> 2))) & 0x000000ff; 2068c2ecf20Sopenharmony_ci pr_cont("Model D "); 2078c2ecf20Sopenharmony_ci break; 2088c2ecf20Sopenharmony_ci } 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci switch (brand) { 2118c2ecf20Sopenharmony_ci case EPS_BRAND_C7M: 2128c2ecf20Sopenharmony_ci pr_cont("C7-M\n"); 2138c2ecf20Sopenharmony_ci break; 2148c2ecf20Sopenharmony_ci case EPS_BRAND_C7: 2158c2ecf20Sopenharmony_ci pr_cont("C7\n"); 2168c2ecf20Sopenharmony_ci break; 2178c2ecf20Sopenharmony_ci case EPS_BRAND_EDEN: 2188c2ecf20Sopenharmony_ci pr_cont("Eden\n"); 2198c2ecf20Sopenharmony_ci break; 2208c2ecf20Sopenharmony_ci case EPS_BRAND_C7D: 2218c2ecf20Sopenharmony_ci pr_cont("C7-D\n"); 2228c2ecf20Sopenharmony_ci break; 2238c2ecf20Sopenharmony_ci case EPS_BRAND_C3: 2248c2ecf20Sopenharmony_ci pr_cont("C3\n"); 2258c2ecf20Sopenharmony_ci return -ENODEV; 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci /* Enable Enhanced PowerSaver */ 2288c2ecf20Sopenharmony_ci rdmsrl(MSR_IA32_MISC_ENABLE, val); 2298c2ecf20Sopenharmony_ci if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) { 2308c2ecf20Sopenharmony_ci val |= MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP; 2318c2ecf20Sopenharmony_ci wrmsrl(MSR_IA32_MISC_ENABLE, val); 2328c2ecf20Sopenharmony_ci /* Can be locked at 0 */ 2338c2ecf20Sopenharmony_ci rdmsrl(MSR_IA32_MISC_ENABLE, val); 2348c2ecf20Sopenharmony_ci if (!(val & MSR_IA32_MISC_ENABLE_ENHANCED_SPEEDSTEP)) { 2358c2ecf20Sopenharmony_ci pr_info("Can't enable Enhanced PowerSaver\n"); 2368c2ecf20Sopenharmony_ci return -ENODEV; 2378c2ecf20Sopenharmony_ci } 2388c2ecf20Sopenharmony_ci } 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci /* Print voltage and multiplier */ 2418c2ecf20Sopenharmony_ci rdmsr(MSR_IA32_PERF_STATUS, lo, hi); 2428c2ecf20Sopenharmony_ci current_voltage = lo & 0xff; 2438c2ecf20Sopenharmony_ci pr_info("Current voltage = %dmV\n", current_voltage * 16 + 700); 2448c2ecf20Sopenharmony_ci current_multiplier = (lo >> 8) & 0xff; 2458c2ecf20Sopenharmony_ci pr_info("Current multiplier = %d\n", current_multiplier); 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci /* Print limits */ 2488c2ecf20Sopenharmony_ci max_voltage = hi & 0xff; 2498c2ecf20Sopenharmony_ci pr_info("Highest voltage = %dmV\n", max_voltage * 16 + 700); 2508c2ecf20Sopenharmony_ci max_multiplier = (hi >> 8) & 0xff; 2518c2ecf20Sopenharmony_ci pr_info("Highest multiplier = %d\n", max_multiplier); 2528c2ecf20Sopenharmony_ci min_voltage = (hi >> 16) & 0xff; 2538c2ecf20Sopenharmony_ci pr_info("Lowest voltage = %dmV\n", min_voltage * 16 + 700); 2548c2ecf20Sopenharmony_ci min_multiplier = (hi >> 24) & 0xff; 2558c2ecf20Sopenharmony_ci pr_info("Lowest multiplier = %d\n", min_multiplier); 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci /* Sanity checks */ 2588c2ecf20Sopenharmony_ci if (current_multiplier == 0 || max_multiplier == 0 2598c2ecf20Sopenharmony_ci || min_multiplier == 0) 2608c2ecf20Sopenharmony_ci return -EINVAL; 2618c2ecf20Sopenharmony_ci if (current_multiplier > max_multiplier 2628c2ecf20Sopenharmony_ci || max_multiplier <= min_multiplier) 2638c2ecf20Sopenharmony_ci return -EINVAL; 2648c2ecf20Sopenharmony_ci if (current_voltage > 0x1f || max_voltage > 0x1f) 2658c2ecf20Sopenharmony_ci return -EINVAL; 2668c2ecf20Sopenharmony_ci if (max_voltage < min_voltage 2678c2ecf20Sopenharmony_ci || current_voltage < min_voltage 2688c2ecf20Sopenharmony_ci || current_voltage > max_voltage) 2698c2ecf20Sopenharmony_ci return -EINVAL; 2708c2ecf20Sopenharmony_ci 2718c2ecf20Sopenharmony_ci /* Check for systems using underclocked CPU */ 2728c2ecf20Sopenharmony_ci if (!freq_failsafe_off && max_multiplier != current_multiplier) { 2738c2ecf20Sopenharmony_ci pr_info("Your processor is running at different frequency then its maximum. Aborting.\n"); 2748c2ecf20Sopenharmony_ci pr_info("You can use freq_failsafe_off option to disable this check.\n"); 2758c2ecf20Sopenharmony_ci return -EINVAL; 2768c2ecf20Sopenharmony_ci } 2778c2ecf20Sopenharmony_ci if (!voltage_failsafe_off && max_voltage != current_voltage) { 2788c2ecf20Sopenharmony_ci pr_info("Your processor is running at different voltage then its maximum. Aborting.\n"); 2798c2ecf20Sopenharmony_ci pr_info("You can use voltage_failsafe_off option to disable this check.\n"); 2808c2ecf20Sopenharmony_ci return -EINVAL; 2818c2ecf20Sopenharmony_ci } 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci /* Calc FSB speed */ 2848c2ecf20Sopenharmony_ci fsb = cpu_khz / current_multiplier; 2858c2ecf20Sopenharmony_ci 2868c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 2878c2ecf20Sopenharmony_ci /* Check for ACPI processor speed limit */ 2888c2ecf20Sopenharmony_ci if (!ignore_acpi_limit && !eps_acpi_init()) { 2898c2ecf20Sopenharmony_ci if (!acpi_processor_get_bios_limit(policy->cpu, &limit)) { 2908c2ecf20Sopenharmony_ci pr_info("ACPI limit %u.%uGHz\n", 2918c2ecf20Sopenharmony_ci limit/1000000, 2928c2ecf20Sopenharmony_ci (limit%1000000)/10000); 2938c2ecf20Sopenharmony_ci eps_acpi_exit(policy); 2948c2ecf20Sopenharmony_ci /* Check if max_multiplier is in BIOS limits */ 2958c2ecf20Sopenharmony_ci if (limit && max_multiplier * fsb > limit) { 2968c2ecf20Sopenharmony_ci pr_info("Aborting\n"); 2978c2ecf20Sopenharmony_ci return -EINVAL; 2988c2ecf20Sopenharmony_ci } 2998c2ecf20Sopenharmony_ci } 3008c2ecf20Sopenharmony_ci } 3018c2ecf20Sopenharmony_ci#endif 3028c2ecf20Sopenharmony_ci 3038c2ecf20Sopenharmony_ci /* Allow user to set lower maximum voltage then that reported 3048c2ecf20Sopenharmony_ci * by processor */ 3058c2ecf20Sopenharmony_ci if (brand == EPS_BRAND_C7M && set_max_voltage) { 3068c2ecf20Sopenharmony_ci u32 v; 3078c2ecf20Sopenharmony_ci 3088c2ecf20Sopenharmony_ci /* Change mV to something hardware can use */ 3098c2ecf20Sopenharmony_ci v = (set_max_voltage - 700) / 16; 3108c2ecf20Sopenharmony_ci /* Check if voltage is within limits */ 3118c2ecf20Sopenharmony_ci if (v >= min_voltage && v <= max_voltage) { 3128c2ecf20Sopenharmony_ci pr_info("Setting %dmV as maximum\n", v * 16 + 700); 3138c2ecf20Sopenharmony_ci max_voltage = v; 3148c2ecf20Sopenharmony_ci } 3158c2ecf20Sopenharmony_ci } 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci /* Calc number of p-states supported */ 3188c2ecf20Sopenharmony_ci if (brand == EPS_BRAND_C7M) 3198c2ecf20Sopenharmony_ci states = max_multiplier - min_multiplier + 1; 3208c2ecf20Sopenharmony_ci else 3218c2ecf20Sopenharmony_ci states = 2; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci /* Allocate private data and frequency table for current cpu */ 3248c2ecf20Sopenharmony_ci centaur = kzalloc(struct_size(centaur, freq_table, states + 1), 3258c2ecf20Sopenharmony_ci GFP_KERNEL); 3268c2ecf20Sopenharmony_ci if (!centaur) 3278c2ecf20Sopenharmony_ci return -ENOMEM; 3288c2ecf20Sopenharmony_ci eps_cpu[0] = centaur; 3298c2ecf20Sopenharmony_ci 3308c2ecf20Sopenharmony_ci /* Copy basic values */ 3318c2ecf20Sopenharmony_ci centaur->fsb = fsb; 3328c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 3338c2ecf20Sopenharmony_ci centaur->bios_limit = limit; 3348c2ecf20Sopenharmony_ci#endif 3358c2ecf20Sopenharmony_ci 3368c2ecf20Sopenharmony_ci /* Fill frequency and MSR value table */ 3378c2ecf20Sopenharmony_ci f_table = ¢aur->freq_table[0]; 3388c2ecf20Sopenharmony_ci if (brand != EPS_BRAND_C7M) { 3398c2ecf20Sopenharmony_ci f_table[0].frequency = fsb * min_multiplier; 3408c2ecf20Sopenharmony_ci f_table[0].driver_data = (min_multiplier << 8) | min_voltage; 3418c2ecf20Sopenharmony_ci f_table[1].frequency = fsb * max_multiplier; 3428c2ecf20Sopenharmony_ci f_table[1].driver_data = (max_multiplier << 8) | max_voltage; 3438c2ecf20Sopenharmony_ci f_table[2].frequency = CPUFREQ_TABLE_END; 3448c2ecf20Sopenharmony_ci } else { 3458c2ecf20Sopenharmony_ci k = 0; 3468c2ecf20Sopenharmony_ci step = ((max_voltage - min_voltage) * 256) 3478c2ecf20Sopenharmony_ci / (max_multiplier - min_multiplier); 3488c2ecf20Sopenharmony_ci for (i = min_multiplier; i <= max_multiplier; i++) { 3498c2ecf20Sopenharmony_ci voltage = (k * step) / 256 + min_voltage; 3508c2ecf20Sopenharmony_ci f_table[k].frequency = fsb * i; 3518c2ecf20Sopenharmony_ci f_table[k].driver_data = (i << 8) | voltage; 3528c2ecf20Sopenharmony_ci k++; 3538c2ecf20Sopenharmony_ci } 3548c2ecf20Sopenharmony_ci f_table[k].frequency = CPUFREQ_TABLE_END; 3558c2ecf20Sopenharmony_ci } 3568c2ecf20Sopenharmony_ci 3578c2ecf20Sopenharmony_ci policy->cpuinfo.transition_latency = 140000; /* 844mV -> 700mV in ns */ 3588c2ecf20Sopenharmony_ci policy->freq_table = ¢aur->freq_table[0]; 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci return 0; 3618c2ecf20Sopenharmony_ci} 3628c2ecf20Sopenharmony_ci 3638c2ecf20Sopenharmony_cistatic int eps_cpu_exit(struct cpufreq_policy *policy) 3648c2ecf20Sopenharmony_ci{ 3658c2ecf20Sopenharmony_ci unsigned int cpu = policy->cpu; 3668c2ecf20Sopenharmony_ci 3678c2ecf20Sopenharmony_ci /* Bye */ 3688c2ecf20Sopenharmony_ci kfree(eps_cpu[cpu]); 3698c2ecf20Sopenharmony_ci eps_cpu[cpu] = NULL; 3708c2ecf20Sopenharmony_ci return 0; 3718c2ecf20Sopenharmony_ci} 3728c2ecf20Sopenharmony_ci 3738c2ecf20Sopenharmony_cistatic struct cpufreq_driver eps_driver = { 3748c2ecf20Sopenharmony_ci .verify = cpufreq_generic_frequency_table_verify, 3758c2ecf20Sopenharmony_ci .target_index = eps_target, 3768c2ecf20Sopenharmony_ci .init = eps_cpu_init, 3778c2ecf20Sopenharmony_ci .exit = eps_cpu_exit, 3788c2ecf20Sopenharmony_ci .get = eps_get, 3798c2ecf20Sopenharmony_ci .name = "e_powersaver", 3808c2ecf20Sopenharmony_ci .attr = cpufreq_generic_attr, 3818c2ecf20Sopenharmony_ci}; 3828c2ecf20Sopenharmony_ci 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci/* This driver will work only on Centaur C7 processors with 3858c2ecf20Sopenharmony_ci * Enhanced SpeedStep/PowerSaver registers */ 3868c2ecf20Sopenharmony_cistatic const struct x86_cpu_id eps_cpu_id[] = { 3878c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FAM_FEATURE(CENTAUR, 6, X86_FEATURE_EST, NULL), 3888c2ecf20Sopenharmony_ci {} 3898c2ecf20Sopenharmony_ci}; 3908c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(x86cpu, eps_cpu_id); 3918c2ecf20Sopenharmony_ci 3928c2ecf20Sopenharmony_cistatic int __init eps_init(void) 3938c2ecf20Sopenharmony_ci{ 3948c2ecf20Sopenharmony_ci if (!x86_match_cpu(eps_cpu_id) || boot_cpu_data.x86_model < 10) 3958c2ecf20Sopenharmony_ci return -ENODEV; 3968c2ecf20Sopenharmony_ci if (cpufreq_register_driver(&eps_driver)) 3978c2ecf20Sopenharmony_ci return -EINVAL; 3988c2ecf20Sopenharmony_ci return 0; 3998c2ecf20Sopenharmony_ci} 4008c2ecf20Sopenharmony_ci 4018c2ecf20Sopenharmony_cistatic void __exit eps_exit(void) 4028c2ecf20Sopenharmony_ci{ 4038c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&eps_driver); 4048c2ecf20Sopenharmony_ci} 4058c2ecf20Sopenharmony_ci 4068c2ecf20Sopenharmony_ci/* Allow user to overclock his machine or to change frequency to higher after 4078c2ecf20Sopenharmony_ci * unloading module */ 4088c2ecf20Sopenharmony_cimodule_param(freq_failsafe_off, int, 0644); 4098c2ecf20Sopenharmony_ciMODULE_PARM_DESC(freq_failsafe_off, "Disable current vs max frequency check"); 4108c2ecf20Sopenharmony_cimodule_param(voltage_failsafe_off, int, 0644); 4118c2ecf20Sopenharmony_ciMODULE_PARM_DESC(voltage_failsafe_off, "Disable current vs max voltage check"); 4128c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_ACPI_PROCESSOR) 4138c2ecf20Sopenharmony_cimodule_param(ignore_acpi_limit, int, 0644); 4148c2ecf20Sopenharmony_ciMODULE_PARM_DESC(ignore_acpi_limit, "Don't check ACPI's processor speed limit"); 4158c2ecf20Sopenharmony_ci#endif 4168c2ecf20Sopenharmony_cimodule_param(set_max_voltage, int, 0644); 4178c2ecf20Sopenharmony_ciMODULE_PARM_DESC(set_max_voltage, "Set maximum CPU voltage (mV) C7-M only"); 4188c2ecf20Sopenharmony_ci 4198c2ecf20Sopenharmony_ciMODULE_AUTHOR("Rafal Bilski <rafalbilski@interia.pl>"); 4208c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Enhanced PowerSaver driver for VIA C7 CPU's."); 4218c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_cimodule_init(eps_init); 4248c2ecf20Sopenharmony_cimodule_exit(eps_exit); 425