394 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Intel SpeedStep SMI driver.4 *5 * (C) 2003 Hiroshi Miura <miura@da-cha.org>6 */7 8 9/*********************************************************************10 * SPEEDSTEP - DEFINITIONS *11 *********************************************************************/12 13#define pr_fmt(fmt) "cpufreq: " fmt14 15#include <linux/kernel.h>16#include <linux/module.h>17#include <linux/moduleparam.h>18#include <linux/init.h>19#include <linux/cpufreq.h>20#include <linux/delay.h>21#include <linux/io.h>22#include <asm/ist.h>23#include <asm/cpu_device_id.h>24 25#include "speedstep-lib.h"26 27/* speedstep system management interface port/command.28 *29 * These parameters are got from IST-SMI BIOS call.30 * If user gives it, these are used.31 *32 */33static int smi_port;34static int smi_cmd;35static unsigned int smi_sig;36 37/* info about the processor */38static enum speedstep_processor speedstep_processor;39 40/*41 * There are only two frequency states for each processor. Values42 * are in kHz for the time being.43 */44static struct cpufreq_frequency_table speedstep_freqs[] = {45 {0, SPEEDSTEP_HIGH, 0},46 {0, SPEEDSTEP_LOW, 0},47 {0, 0, CPUFREQ_TABLE_END},48};49 50#define GET_SPEEDSTEP_OWNER 051#define GET_SPEEDSTEP_STATE 152#define SET_SPEEDSTEP_STATE 253#define GET_SPEEDSTEP_FREQS 454 55/* how often shall the SMI call be tried if it failed, e.g. because56 * of DMA activity going on? */57#define SMI_TRIES 558 59/**60 * speedstep_smi_ownership61 */62static int speedstep_smi_ownership(void)63{64 u32 command, result, magic, dummy;65 u32 function = GET_SPEEDSTEP_OWNER;66 unsigned char magic_data[] = "Copyright (c) 1999 Intel Corporation";67 68 command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff);69 magic = virt_to_phys(magic_data);70 71 pr_debug("trying to obtain ownership with command %x at port %x\n",72 command, smi_port);73 74 __asm__ __volatile__(75 "push %%ebp\n"76 "out %%al, (%%dx)\n"77 "pop %%ebp\n"78 : "=D" (result),79 "=a" (dummy), "=b" (dummy), "=c" (dummy), "=d" (dummy),80 "=S" (dummy)81 : "a" (command), "b" (function), "c" (0), "d" (smi_port),82 "D" (0), "S" (magic)83 : "memory"84 );85 86 pr_debug("result is %x\n", result);87 88 return result;89}90 91/**92 * speedstep_smi_get_freqs - get SpeedStep preferred & current freq.93 * @low: the low frequency value is placed here94 * @high: the high frequency value is placed here95 *96 * Only available on later SpeedStep-enabled systems, returns false results or97 * even hangs [cf. bugme.osdl.org # 1422] on earlier systems. Empirical testing98 * shows that the latter occurs if !(ist_info.event & 0xFFFF).99 */100static int speedstep_smi_get_freqs(unsigned int *low, unsigned int *high)101{102 u32 command, result = 0, edi, high_mhz, low_mhz, dummy;103 u32 state = 0;104 u32 function = GET_SPEEDSTEP_FREQS;105 106 if (!(ist_info.event & 0xFFFF)) {107 pr_debug("bug #1422 -- can't read freqs from BIOS\n");108 return -ENODEV;109 }110 111 command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff);112 113 pr_debug("trying to determine frequencies with command %x at port %x\n",114 command, smi_port);115 116 __asm__ __volatile__(117 "push %%ebp\n"118 "out %%al, (%%dx)\n"119 "pop %%ebp"120 : "=a" (result),121 "=b" (high_mhz),122 "=c" (low_mhz),123 "=d" (state), "=D" (edi), "=S" (dummy)124 : "a" (command),125 "b" (function),126 "c" (state),127 "d" (smi_port), "S" (0), "D" (0)128 );129 130 pr_debug("result %x, low_freq %u, high_freq %u\n",131 result, low_mhz, high_mhz);132 133 /* abort if results are obviously incorrect... */134 if ((high_mhz + low_mhz) < 600)135 return -EINVAL;136 137 *high = high_mhz * 1000;138 *low = low_mhz * 1000;139 140 return result;141}142 143/**144 * speedstep_set_state - set the SpeedStep state145 * @state: new processor frequency state (SPEEDSTEP_LOW or SPEEDSTEP_HIGH)146 *147 */148static void speedstep_set_state(unsigned int state)149{150 unsigned int result = 0, command, new_state, dummy;151 unsigned long flags;152 unsigned int function = SET_SPEEDSTEP_STATE;153 unsigned int retry = 0;154 155 if (state > 0x1)156 return;157 158 /* Disable IRQs */159 preempt_disable();160 local_irq_save(flags);161 162 command = (smi_sig & 0xffffff00) | (smi_cmd & 0xff);163 164 pr_debug("trying to set frequency to state %u "165 "with command %x at port %x\n",166 state, command, smi_port);167 168 do {169 if (retry) {170 /*171 * We need to enable interrupts, otherwise the blockage172 * won't resolve.173 *174 * We disable preemption so that other processes don't175 * run. If other processes were running, they could176 * submit more DMA requests, making the blockage worse.177 */178 pr_debug("retry %u, previous result %u, waiting...\n",179 retry, result);180 local_irq_enable();181 mdelay(retry * 50);182 local_irq_disable();183 }184 retry++;185 __asm__ __volatile__(186 "push %%ebp\n"187 "out %%al, (%%dx)\n"188 "pop %%ebp"189 : "=b" (new_state), "=D" (result),190 "=c" (dummy), "=a" (dummy),191 "=d" (dummy), "=S" (dummy)192 : "a" (command), "b" (function), "c" (state),193 "d" (smi_port), "S" (0), "D" (0)194 );195 } while ((new_state != state) && (retry <= SMI_TRIES));196 197 /* enable IRQs */198 local_irq_restore(flags);199 preempt_enable();200 201 if (new_state == state)202 pr_debug("change to %u MHz succeeded after %u tries "203 "with result %u\n",204 (speedstep_freqs[new_state].frequency / 1000),205 retry, result);206 else207 pr_err("change to state %u failed with new_state %u and result %u\n",208 state, new_state, result);209 210 return;211}212 213 214/**215 * speedstep_target - set a new CPUFreq policy216 * @policy: new policy217 * @index: index of new freq218 *219 * Sets a new CPUFreq policy/freq.220 */221static int speedstep_target(struct cpufreq_policy *policy, unsigned int index)222{223 speedstep_set_state(index);224 225 return 0;226}227 228 229static int speedstep_cpu_init(struct cpufreq_policy *policy)230{231 int result;232 unsigned int *low, *high;233 234 /* capability check */235 if (policy->cpu != 0)236 return -ENODEV;237 238 result = speedstep_smi_ownership();239 if (result) {240 pr_debug("fails in acquiring ownership of a SMI interface.\n");241 return -EINVAL;242 }243 244 /* detect low and high frequency */245 low = &speedstep_freqs[SPEEDSTEP_LOW].frequency;246 high = &speedstep_freqs[SPEEDSTEP_HIGH].frequency;247 248 result = speedstep_smi_get_freqs(low, high);249 if (result) {250 /* fall back to speedstep_lib.c dection mechanism:251 * try both states out */252 pr_debug("could not detect low and high frequencies "253 "by SMI call.\n");254 result = speedstep_get_freqs(speedstep_processor,255 low, high,256 NULL,257 &speedstep_set_state);258 259 if (result) {260 pr_debug("could not detect two different speeds"261 " -- aborting.\n");262 return result;263 } else264 pr_debug("workaround worked.\n");265 }266 267 policy->freq_table = speedstep_freqs;268 269 return 0;270}271 272static unsigned int speedstep_get(unsigned int cpu)273{274 if (cpu)275 return -ENODEV;276 return speedstep_get_frequency(speedstep_processor);277}278 279 280static int speedstep_resume(struct cpufreq_policy *policy)281{282 int result = speedstep_smi_ownership();283 284 if (result)285 pr_debug("fails in re-acquiring ownership of a SMI interface.\n");286 287 return result;288}289 290static struct cpufreq_driver speedstep_driver = {291 .name = "speedstep-smi",292 .flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING,293 .verify = cpufreq_generic_frequency_table_verify,294 .target_index = speedstep_target,295 .init = speedstep_cpu_init,296 .get = speedstep_get,297 .resume = speedstep_resume,298 .attr = cpufreq_generic_attr,299};300 301static const struct x86_cpu_id ss_smi_ids[] = {302 X86_MATCH_VENDOR_FAM_MODEL(INTEL, 6, 0x8, 0),303 X86_MATCH_VENDOR_FAM_MODEL(INTEL, 6, 0xb, 0),304 X86_MATCH_VENDOR_FAM_MODEL(INTEL, 15, 0x2, 0),305 {}306};307 308/**309 * speedstep_init - initializes the SpeedStep CPUFreq driver310 *311 * Initializes the SpeedStep support. Returns -ENODEV on unsupported312 * BIOS, -EINVAL on problems during initiatization, and zero on313 * success.314 */315static int __init speedstep_init(void)316{317 if (!x86_match_cpu(ss_smi_ids))318 return -ENODEV;319 320 speedstep_processor = speedstep_detect_processor();321 322 switch (speedstep_processor) {323 case SPEEDSTEP_CPU_PIII_T:324 case SPEEDSTEP_CPU_PIII_C:325 case SPEEDSTEP_CPU_PIII_C_EARLY:326 break;327 default:328 speedstep_processor = 0;329 }330 331 if (!speedstep_processor) {332 pr_debug("No supported Intel CPU detected.\n");333 return -ENODEV;334 }335 336 pr_debug("signature:0x%.8x, command:0x%.8x, "337 "event:0x%.8x, perf_level:0x%.8x.\n",338 ist_info.signature, ist_info.command,339 ist_info.event, ist_info.perf_level);340 341 /* Error if no IST-SMI BIOS or no PARM342 sig= 'ISGE' aka 'Intel Speedstep Gate E' */343 if ((ist_info.signature != 0x47534943) && (344 (smi_port == 0) || (smi_cmd == 0)))345 return -ENODEV;346 347 if (smi_sig == 1)348 smi_sig = 0x47534943;349 else350 smi_sig = ist_info.signature;351 352 /* setup smi_port from MODLULE_PARM or BIOS */353 if ((smi_port > 0xff) || (smi_port < 0))354 return -EINVAL;355 else if (smi_port == 0)356 smi_port = ist_info.command & 0xff;357 358 if ((smi_cmd > 0xff) || (smi_cmd < 0))359 return -EINVAL;360 else if (smi_cmd == 0)361 smi_cmd = (ist_info.command >> 16) & 0xff;362 363 return cpufreq_register_driver(&speedstep_driver);364}365 366 367/**368 * speedstep_exit - unregisters SpeedStep support369 *370 * Unregisters SpeedStep support.371 */372static void __exit speedstep_exit(void)373{374 cpufreq_unregister_driver(&speedstep_driver);375}376 377module_param_hw(smi_port, int, ioport, 0444);378module_param(smi_cmd, int, 0444);379module_param(smi_sig, uint, 0444);380 381MODULE_PARM_DESC(smi_port, "Override the BIOS-given IST port with this value "382 "-- Intel's default setting is 0xb2");383MODULE_PARM_DESC(smi_cmd, "Override the BIOS-given IST command with this value "384 "-- Intel's default setting is 0x82");385MODULE_PARM_DESC(smi_sig, "Set to 1 to fake the IST signature when using the "386 "SMI interface.");387 388MODULE_AUTHOR("Hiroshi Miura");389MODULE_DESCRIPTION("Speedstep driver for IST applet SMI interface.");390MODULE_LICENSE("GPL");391 392module_init(speedstep_init);393module_exit(speedstep_exit);394