18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * (C) 2002 - 2003 Dominik Brodowski <linux@brodo.de> 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * BIG FAT DISCLAIMER: Work in progress code. Possibly *dangerous* 68c2ecf20Sopenharmony_ci */ 78c2ecf20Sopenharmony_ci 88c2ecf20Sopenharmony_ci#include <linux/kernel.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <linux/cpufreq.h> 128c2ecf20Sopenharmony_ci#include <linux/timex.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_ci#include <asm/msr.h> 158c2ecf20Sopenharmony_ci#include <asm/processor.h> 168c2ecf20Sopenharmony_ci#include <asm/cpu_device_id.h> 178c2ecf20Sopenharmony_ci 188c2ecf20Sopenharmony_cistatic struct cpufreq_driver longrun_driver; 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_ci/** 218c2ecf20Sopenharmony_ci * longrun_{low,high}_freq is needed for the conversion of cpufreq kHz 228c2ecf20Sopenharmony_ci * values into per cent values. In TMTA microcode, the following is valid: 238c2ecf20Sopenharmony_ci * performance_pctg = (current_freq - low_freq)/(high_freq - low_freq) 248c2ecf20Sopenharmony_ci */ 258c2ecf20Sopenharmony_cistatic unsigned int longrun_low_freq, longrun_high_freq; 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci/** 298c2ecf20Sopenharmony_ci * longrun_get_policy - get the current LongRun policy 308c2ecf20Sopenharmony_ci * @policy: struct cpufreq_policy where current policy is written into 318c2ecf20Sopenharmony_ci * 328c2ecf20Sopenharmony_ci * Reads the current LongRun policy by access to MSR_TMTA_LONGRUN_FLAGS 338c2ecf20Sopenharmony_ci * and MSR_TMTA_LONGRUN_CTRL 348c2ecf20Sopenharmony_ci */ 358c2ecf20Sopenharmony_cistatic void longrun_get_policy(struct cpufreq_policy *policy) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci u32 msr_lo, msr_hi; 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi); 408c2ecf20Sopenharmony_ci pr_debug("longrun flags are %x - %x\n", msr_lo, msr_hi); 418c2ecf20Sopenharmony_ci if (msr_lo & 0x01) 428c2ecf20Sopenharmony_ci policy->policy = CPUFREQ_POLICY_PERFORMANCE; 438c2ecf20Sopenharmony_ci else 448c2ecf20Sopenharmony_ci policy->policy = CPUFREQ_POLICY_POWERSAVE; 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi); 478c2ecf20Sopenharmony_ci pr_debug("longrun ctrl is %x - %x\n", msr_lo, msr_hi); 488c2ecf20Sopenharmony_ci msr_lo &= 0x0000007F; 498c2ecf20Sopenharmony_ci msr_hi &= 0x0000007F; 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci if (longrun_high_freq <= longrun_low_freq) { 528c2ecf20Sopenharmony_ci /* Assume degenerate Longrun table */ 538c2ecf20Sopenharmony_ci policy->min = policy->max = longrun_high_freq; 548c2ecf20Sopenharmony_ci } else { 558c2ecf20Sopenharmony_ci policy->min = longrun_low_freq + msr_lo * 568c2ecf20Sopenharmony_ci ((longrun_high_freq - longrun_low_freq) / 100); 578c2ecf20Sopenharmony_ci policy->max = longrun_low_freq + msr_hi * 588c2ecf20Sopenharmony_ci ((longrun_high_freq - longrun_low_freq) / 100); 598c2ecf20Sopenharmony_ci } 608c2ecf20Sopenharmony_ci policy->cpu = 0; 618c2ecf20Sopenharmony_ci} 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_ci 648c2ecf20Sopenharmony_ci/** 658c2ecf20Sopenharmony_ci * longrun_set_policy - sets a new CPUFreq policy 668c2ecf20Sopenharmony_ci * @policy: new policy 678c2ecf20Sopenharmony_ci * 688c2ecf20Sopenharmony_ci * Sets a new CPUFreq policy on LongRun-capable processors. This function 698c2ecf20Sopenharmony_ci * has to be called with cpufreq_driver locked. 708c2ecf20Sopenharmony_ci */ 718c2ecf20Sopenharmony_cistatic int longrun_set_policy(struct cpufreq_policy *policy) 728c2ecf20Sopenharmony_ci{ 738c2ecf20Sopenharmony_ci u32 msr_lo, msr_hi; 748c2ecf20Sopenharmony_ci u32 pctg_lo, pctg_hi; 758c2ecf20Sopenharmony_ci 768c2ecf20Sopenharmony_ci if (!policy) 778c2ecf20Sopenharmony_ci return -EINVAL; 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci if (longrun_high_freq <= longrun_low_freq) { 808c2ecf20Sopenharmony_ci /* Assume degenerate Longrun table */ 818c2ecf20Sopenharmony_ci pctg_lo = pctg_hi = 100; 828c2ecf20Sopenharmony_ci } else { 838c2ecf20Sopenharmony_ci pctg_lo = (policy->min - longrun_low_freq) / 848c2ecf20Sopenharmony_ci ((longrun_high_freq - longrun_low_freq) / 100); 858c2ecf20Sopenharmony_ci pctg_hi = (policy->max - longrun_low_freq) / 868c2ecf20Sopenharmony_ci ((longrun_high_freq - longrun_low_freq) / 100); 878c2ecf20Sopenharmony_ci } 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci if (pctg_hi > 100) 908c2ecf20Sopenharmony_ci pctg_hi = 100; 918c2ecf20Sopenharmony_ci if (pctg_lo > pctg_hi) 928c2ecf20Sopenharmony_ci pctg_lo = pctg_hi; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci /* performance or economy mode */ 958c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi); 968c2ecf20Sopenharmony_ci msr_lo &= 0xFFFFFFFE; 978c2ecf20Sopenharmony_ci switch (policy->policy) { 988c2ecf20Sopenharmony_ci case CPUFREQ_POLICY_PERFORMANCE: 998c2ecf20Sopenharmony_ci msr_lo |= 0x00000001; 1008c2ecf20Sopenharmony_ci break; 1018c2ecf20Sopenharmony_ci case CPUFREQ_POLICY_POWERSAVE: 1028c2ecf20Sopenharmony_ci break; 1038c2ecf20Sopenharmony_ci } 1048c2ecf20Sopenharmony_ci wrmsr(MSR_TMTA_LONGRUN_FLAGS, msr_lo, msr_hi); 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci /* lower and upper boundary */ 1078c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi); 1088c2ecf20Sopenharmony_ci msr_lo &= 0xFFFFFF80; 1098c2ecf20Sopenharmony_ci msr_hi &= 0xFFFFFF80; 1108c2ecf20Sopenharmony_ci msr_lo |= pctg_lo; 1118c2ecf20Sopenharmony_ci msr_hi |= pctg_hi; 1128c2ecf20Sopenharmony_ci wrmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi); 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return 0; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci 1188c2ecf20Sopenharmony_ci/** 1198c2ecf20Sopenharmony_ci * longrun_verify_poliy - verifies a new CPUFreq policy 1208c2ecf20Sopenharmony_ci * @policy: the policy to verify 1218c2ecf20Sopenharmony_ci * 1228c2ecf20Sopenharmony_ci * Validates a new CPUFreq policy. This function has to be called with 1238c2ecf20Sopenharmony_ci * cpufreq_driver locked. 1248c2ecf20Sopenharmony_ci */ 1258c2ecf20Sopenharmony_cistatic int longrun_verify_policy(struct cpufreq_policy_data *policy) 1268c2ecf20Sopenharmony_ci{ 1278c2ecf20Sopenharmony_ci if (!policy) 1288c2ecf20Sopenharmony_ci return -EINVAL; 1298c2ecf20Sopenharmony_ci 1308c2ecf20Sopenharmony_ci policy->cpu = 0; 1318c2ecf20Sopenharmony_ci cpufreq_verify_within_cpu_limits(policy); 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci return 0; 1348c2ecf20Sopenharmony_ci} 1358c2ecf20Sopenharmony_ci 1368c2ecf20Sopenharmony_cistatic unsigned int longrun_get(unsigned int cpu) 1378c2ecf20Sopenharmony_ci{ 1388c2ecf20Sopenharmony_ci u32 eax, ebx, ecx, edx; 1398c2ecf20Sopenharmony_ci 1408c2ecf20Sopenharmony_ci if (cpu) 1418c2ecf20Sopenharmony_ci return 0; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci cpuid(0x80860007, &eax, &ebx, &ecx, &edx); 1448c2ecf20Sopenharmony_ci pr_debug("cpuid eax is %u\n", eax); 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci return eax * 1000; 1478c2ecf20Sopenharmony_ci} 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci/** 1508c2ecf20Sopenharmony_ci * longrun_determine_freqs - determines the lowest and highest possible core frequency 1518c2ecf20Sopenharmony_ci * @low_freq: an int to put the lowest frequency into 1528c2ecf20Sopenharmony_ci * @high_freq: an int to put the highest frequency into 1538c2ecf20Sopenharmony_ci * 1548c2ecf20Sopenharmony_ci * Determines the lowest and highest possible core frequencies on this CPU. 1558c2ecf20Sopenharmony_ci * This is necessary to calculate the performance percentage according to 1568c2ecf20Sopenharmony_ci * TMTA rules: 1578c2ecf20Sopenharmony_ci * performance_pctg = (target_freq - low_freq)/(high_freq - low_freq) 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_cistatic int longrun_determine_freqs(unsigned int *low_freq, 1608c2ecf20Sopenharmony_ci unsigned int *high_freq) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci u32 msr_lo, msr_hi; 1638c2ecf20Sopenharmony_ci u32 save_lo, save_hi; 1648c2ecf20Sopenharmony_ci u32 eax, ebx, ecx, edx; 1658c2ecf20Sopenharmony_ci u32 try_hi; 1668c2ecf20Sopenharmony_ci struct cpuinfo_x86 *c = &cpu_data(0); 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (!low_freq || !high_freq) 1698c2ecf20Sopenharmony_ci return -EINVAL; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (cpu_has(c, X86_FEATURE_LRTI)) { 1728c2ecf20Sopenharmony_ci /* if the LongRun Table Interface is present, the 1738c2ecf20Sopenharmony_ci * detection is a bit easier: 1748c2ecf20Sopenharmony_ci * For minimum frequency, read out the maximum 1758c2ecf20Sopenharmony_ci * level (msr_hi), write that into "currently 1768c2ecf20Sopenharmony_ci * selected level", and read out the frequency. 1778c2ecf20Sopenharmony_ci * For maximum frequency, read out level zero. 1788c2ecf20Sopenharmony_ci */ 1798c2ecf20Sopenharmony_ci /* minimum */ 1808c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LRTI_READOUT, msr_lo, msr_hi); 1818c2ecf20Sopenharmony_ci wrmsr(MSR_TMTA_LRTI_READOUT, msr_hi, msr_hi); 1828c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LRTI_VOLT_MHZ, msr_lo, msr_hi); 1838c2ecf20Sopenharmony_ci *low_freq = msr_lo * 1000; /* to kHz */ 1848c2ecf20Sopenharmony_ci 1858c2ecf20Sopenharmony_ci /* maximum */ 1868c2ecf20Sopenharmony_ci wrmsr(MSR_TMTA_LRTI_READOUT, 0, msr_hi); 1878c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LRTI_VOLT_MHZ, msr_lo, msr_hi); 1888c2ecf20Sopenharmony_ci *high_freq = msr_lo * 1000; /* to kHz */ 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci pr_debug("longrun table interface told %u - %u kHz\n", 1918c2ecf20Sopenharmony_ci *low_freq, *high_freq); 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (*low_freq > *high_freq) 1948c2ecf20Sopenharmony_ci *low_freq = *high_freq; 1958c2ecf20Sopenharmony_ci return 0; 1968c2ecf20Sopenharmony_ci } 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci /* set the upper border to the value determined during TSC init */ 1998c2ecf20Sopenharmony_ci *high_freq = (cpu_khz / 1000); 2008c2ecf20Sopenharmony_ci *high_freq = *high_freq * 1000; 2018c2ecf20Sopenharmony_ci pr_debug("high frequency is %u kHz\n", *high_freq); 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci /* get current borders */ 2048c2ecf20Sopenharmony_ci rdmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi); 2058c2ecf20Sopenharmony_ci save_lo = msr_lo & 0x0000007F; 2068c2ecf20Sopenharmony_ci save_hi = msr_hi & 0x0000007F; 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_ci /* if current perf_pctg is larger than 90%, we need to decrease the 2098c2ecf20Sopenharmony_ci * upper limit to make the calculation more accurate. 2108c2ecf20Sopenharmony_ci */ 2118c2ecf20Sopenharmony_ci cpuid(0x80860007, &eax, &ebx, &ecx, &edx); 2128c2ecf20Sopenharmony_ci /* try decreasing in 10% steps, some processors react only 2138c2ecf20Sopenharmony_ci * on some barrier values */ 2148c2ecf20Sopenharmony_ci for (try_hi = 80; try_hi > 0 && ecx > 90; try_hi -= 10) { 2158c2ecf20Sopenharmony_ci /* set to 0 to try_hi perf_pctg */ 2168c2ecf20Sopenharmony_ci msr_lo &= 0xFFFFFF80; 2178c2ecf20Sopenharmony_ci msr_hi &= 0xFFFFFF80; 2188c2ecf20Sopenharmony_ci msr_hi |= try_hi; 2198c2ecf20Sopenharmony_ci wrmsr(MSR_TMTA_LONGRUN_CTRL, msr_lo, msr_hi); 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci /* read out current core MHz and current perf_pctg */ 2228c2ecf20Sopenharmony_ci cpuid(0x80860007, &eax, &ebx, &ecx, &edx); 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci /* restore values */ 2258c2ecf20Sopenharmony_ci wrmsr(MSR_TMTA_LONGRUN_CTRL, save_lo, save_hi); 2268c2ecf20Sopenharmony_ci } 2278c2ecf20Sopenharmony_ci pr_debug("percentage is %u %%, freq is %u MHz\n", ecx, eax); 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci /* performance_pctg = (current_freq - low_freq)/(high_freq - low_freq) 2308c2ecf20Sopenharmony_ci * eqals 2318c2ecf20Sopenharmony_ci * low_freq * (1 - perf_pctg) = (cur_freq - high_freq * perf_pctg) 2328c2ecf20Sopenharmony_ci * 2338c2ecf20Sopenharmony_ci * high_freq * perf_pctg is stored tempoarily into "ebx". 2348c2ecf20Sopenharmony_ci */ 2358c2ecf20Sopenharmony_ci ebx = (((cpu_khz / 1000) * ecx) / 100); /* to MHz */ 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci if ((ecx > 95) || (ecx == 0) || (eax < ebx)) 2388c2ecf20Sopenharmony_ci return -EIO; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci edx = ((eax - ebx) * 100) / (100 - ecx); 2418c2ecf20Sopenharmony_ci *low_freq = edx * 1000; /* back to kHz */ 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_ci pr_debug("low frequency is %u kHz\n", *low_freq); 2448c2ecf20Sopenharmony_ci 2458c2ecf20Sopenharmony_ci if (*low_freq > *high_freq) 2468c2ecf20Sopenharmony_ci *low_freq = *high_freq; 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ci return 0; 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_cistatic int longrun_cpu_init(struct cpufreq_policy *policy) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci int result = 0; 2558c2ecf20Sopenharmony_ci 2568c2ecf20Sopenharmony_ci /* capability check */ 2578c2ecf20Sopenharmony_ci if (policy->cpu != 0) 2588c2ecf20Sopenharmony_ci return -ENODEV; 2598c2ecf20Sopenharmony_ci 2608c2ecf20Sopenharmony_ci /* detect low and high frequency */ 2618c2ecf20Sopenharmony_ci result = longrun_determine_freqs(&longrun_low_freq, &longrun_high_freq); 2628c2ecf20Sopenharmony_ci if (result) 2638c2ecf20Sopenharmony_ci return result; 2648c2ecf20Sopenharmony_ci 2658c2ecf20Sopenharmony_ci /* cpuinfo and default policy values */ 2668c2ecf20Sopenharmony_ci policy->cpuinfo.min_freq = longrun_low_freq; 2678c2ecf20Sopenharmony_ci policy->cpuinfo.max_freq = longrun_high_freq; 2688c2ecf20Sopenharmony_ci longrun_get_policy(policy); 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci return 0; 2718c2ecf20Sopenharmony_ci} 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic struct cpufreq_driver longrun_driver = { 2758c2ecf20Sopenharmony_ci .flags = CPUFREQ_CONST_LOOPS, 2768c2ecf20Sopenharmony_ci .verify = longrun_verify_policy, 2778c2ecf20Sopenharmony_ci .setpolicy = longrun_set_policy, 2788c2ecf20Sopenharmony_ci .get = longrun_get, 2798c2ecf20Sopenharmony_ci .init = longrun_cpu_init, 2808c2ecf20Sopenharmony_ci .name = "longrun", 2818c2ecf20Sopenharmony_ci}; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_cistatic const struct x86_cpu_id longrun_ids[] = { 2848c2ecf20Sopenharmony_ci X86_MATCH_VENDOR_FEATURE(TRANSMETA, X86_FEATURE_LONGRUN, NULL), 2858c2ecf20Sopenharmony_ci {} 2868c2ecf20Sopenharmony_ci}; 2878c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(x86cpu, longrun_ids); 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_ci/** 2908c2ecf20Sopenharmony_ci * longrun_init - initializes the Transmeta Crusoe LongRun CPUFreq driver 2918c2ecf20Sopenharmony_ci * 2928c2ecf20Sopenharmony_ci * Initializes the LongRun support. 2938c2ecf20Sopenharmony_ci */ 2948c2ecf20Sopenharmony_cistatic int __init longrun_init(void) 2958c2ecf20Sopenharmony_ci{ 2968c2ecf20Sopenharmony_ci if (!x86_match_cpu(longrun_ids)) 2978c2ecf20Sopenharmony_ci return -ENODEV; 2988c2ecf20Sopenharmony_ci return cpufreq_register_driver(&longrun_driver); 2998c2ecf20Sopenharmony_ci} 3008c2ecf20Sopenharmony_ci 3018c2ecf20Sopenharmony_ci 3028c2ecf20Sopenharmony_ci/** 3038c2ecf20Sopenharmony_ci * longrun_exit - unregisters LongRun support 3048c2ecf20Sopenharmony_ci */ 3058c2ecf20Sopenharmony_cistatic void __exit longrun_exit(void) 3068c2ecf20Sopenharmony_ci{ 3078c2ecf20Sopenharmony_ci cpufreq_unregister_driver(&longrun_driver); 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dominik Brodowski <linux@brodo.de>"); 3128c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("LongRun driver for Transmeta Crusoe and " 3138c2ecf20Sopenharmony_ci "Efficeon processors."); 3148c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_cimodule_init(longrun_init); 3178c2ecf20Sopenharmony_cimodule_exit(longrun_exit); 318