2019-05-27 09:55:14 +03:00
// SPDX-License-Identifier: GPL-2.0-only
2005-04-17 02:20:36 +04:00
/*
* Intel SpeedStep SMI driver .
*
* ( C ) 2003 Hiroshi Miura < miura @ da - cha . org >
*/
/*********************************************************************
* SPEEDSTEP - DEFINITIONS *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2016-04-05 23:28:25 +03:00
# define pr_fmt(fmt) "cpufreq: " fmt
2005-04-17 02:20:36 +04:00
# include <linux/kernel.h>
2006-02-28 08:43:23 +03:00
# include <linux/module.h>
# include <linux/moduleparam.h>
2005-04-17 02:20:36 +04:00
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/delay.h>
2009-01-18 07:55:22 +03:00
# include <linux/io.h>
2005-04-17 02:20:36 +04:00
# include <asm/ist.h>
2012-01-26 03:09:12 +04:00
# include <asm/cpu_device_id.h>
2005-04-17 02:20:36 +04:00
# include "speedstep-lib.h"
/* speedstep system management interface port/command.
*
* These parameters are got from IST - SMI BIOS call .
* If user gives it , these are used .
2006-02-28 08:43:23 +03:00
*
2005-04-17 02:20:36 +04:00
*/
2009-01-18 07:55:22 +03:00
static int smi_port ;
static int smi_cmd ;
static unsigned int smi_sig ;
2005-04-17 02:20:36 +04:00
/* info about the processor */
2009-11-18 01:39:53 +03:00
static enum speedstep_processor speedstep_processor ;
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
/*
* There are only two frequency states for each processor . Values
2005-04-17 02:20:36 +04:00
* are in kHz for the time being .
*/
static struct cpufreq_frequency_table speedstep_freqs [ ] = {
2014-03-28 17:41:47 +04:00
{ 0 , SPEEDSTEP_HIGH , 0 } ,
{ 0 , SPEEDSTEP_LOW , 0 } ,
{ 0 , 0 , CPUFREQ_TABLE_END } ,
2005-04-17 02:20:36 +04:00
} ;
# define GET_SPEEDSTEP_OWNER 0
# define GET_SPEEDSTEP_STATE 1
# define SET_SPEEDSTEP_STATE 2
# define GET_SPEEDSTEP_FREQS 4
/* how often shall the SMI call be tried if it failed, e.g. because
* of DMA activity going on ? */
# define SMI_TRIES 5
/**
* speedstep_smi_ownership
*/
2009-01-18 07:55:22 +03:00
static int speedstep_smi_ownership ( void )
2005-04-17 02:20:36 +04:00
{
2008-03-10 18:05:41 +03:00
u32 command , result , magic , dummy ;
2005-04-17 02:20:36 +04:00
u32 function = GET_SPEEDSTEP_OWNER ;
unsigned char magic_data [ ] = " Copyright (c) 1999 Intel Corporation " ;
command = ( smi_sig & 0xffffff00 ) | ( smi_cmd & 0xff ) ;
magic = virt_to_phys ( magic_data ) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " trying to obtain ownership with command %x at port %x \n " ,
2009-01-18 07:55:22 +03:00
command , smi_port ) ;
2005-04-17 02:20:36 +04:00
__asm__ __volatile__ (
2008-03-10 18:05:41 +03:00
" push %%ebp \n "
2005-04-17 02:20:36 +04:00
" out %%al, (%%dx) \n "
2008-03-10 18:05:41 +03:00
" pop %%ebp \n "
2009-01-18 07:55:22 +03:00
: " =D " ( result ) ,
" =a " ( dummy ) , " =b " ( dummy ) , " =c " ( dummy ) , " =d " ( dummy ) ,
" =S " ( dummy )
2006-03-25 12:51:51 +03:00
: " a " ( command ) , " b " ( function ) , " c " ( 0 ) , " d " ( smi_port ) ,
2009-01-18 07:55:22 +03:00
" D " ( 0 ) , " S " ( magic )
2006-03-25 12:51:51 +03:00
: " memory "
2005-04-17 02:20:36 +04:00
) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " result is %x \n " , result ) ;
2005-04-17 02:20:36 +04:00
return result ;
}
/**
* speedstep_smi_get_freqs - get SpeedStep preferred & current freq .
* @ low : the low frequency value is placed here
* @ high : the high frequency value is placed here
*
* Only available on later SpeedStep - enabled systems , returns false results or
* even hangs [ cf . bugme . osdl . org # 1422 ] on earlier systems . Empirical testing
* shows that the latter occurs if ! ( ist_info . event & 0xFFFF ) .
*/
2009-01-18 07:55:22 +03:00
static int speedstep_smi_get_freqs ( unsigned int * low , unsigned int * high )
2005-04-17 02:20:36 +04:00
{
2008-03-10 18:05:41 +03:00
u32 command , result = 0 , edi , high_mhz , low_mhz , dummy ;
2009-01-18 07:55:22 +03:00
u32 state = 0 ;
2005-04-17 02:20:36 +04:00
u32 function = GET_SPEEDSTEP_FREQS ;
if ( ! ( ist_info . event & 0xFFFF ) ) {
2011-03-27 17:04:46 +04:00
pr_debug ( " bug #1422 -- can't read freqs from BIOS \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
command = ( smi_sig & 0xffffff00 ) | ( smi_cmd & 0xff ) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " trying to determine frequencies with command %x at port %x \n " ,
2009-01-18 07:55:22 +03:00
command , smi_port ) ;
2005-04-17 02:20:36 +04:00
2008-03-10 18:05:41 +03:00
__asm__ __volatile__ (
" push %%ebp \n "
2005-04-17 02:20:36 +04:00
" out %%al, (%%dx) \n "
2008-03-10 18:05:41 +03:00
" pop %%ebp "
2009-01-18 07:55:22 +03:00
: " =a " ( result ) ,
" =b " ( high_mhz ) ,
" =c " ( low_mhz ) ,
" =d " ( state ) , " =D " ( edi ) , " =S " ( dummy )
: " a " ( command ) ,
" b " ( function ) ,
" c " ( state ) ,
" d " ( smi_port ) , " S " ( 0 ) , " D " ( 0 )
2005-04-17 02:20:36 +04:00
) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " result %x, low_freq %u, high_freq %u \n " ,
2009-01-18 07:55:22 +03:00
result , low_mhz , high_mhz ) ;
2005-04-17 02:20:36 +04:00
/* abort if results are obviously incorrect... */
if ( ( high_mhz + low_mhz ) < 600 )
return - EINVAL ;
* high = high_mhz * 1000 ;
* low = low_mhz * 1000 ;
return result ;
2006-02-28 08:43:23 +03:00
}
2005-04-17 02:20:36 +04:00
/**
* speedstep_set_state - set the SpeedStep state
* @ state : new processor frequency state ( SPEEDSTEP_LOW or SPEEDSTEP_HIGH )
*
*/
2009-01-18 07:55:22 +03:00
static void speedstep_set_state ( unsigned int state )
2005-04-17 02:20:36 +04:00
{
2008-03-10 18:05:41 +03:00
unsigned int result = 0 , command , new_state , dummy ;
2005-04-17 02:20:36 +04:00
unsigned long flags ;
2009-01-18 07:55:22 +03:00
unsigned int function = SET_SPEEDSTEP_STATE ;
2005-04-17 02:20:36 +04:00
unsigned int retry = 0 ;
if ( state > 0x1 )
return ;
/* Disable IRQs */
2015-02-09 21:38:17 +03:00
preempt_disable ( ) ;
2005-04-17 02:20:36 +04:00
local_irq_save ( flags ) ;
command = ( smi_sig & 0xffffff00 ) | ( smi_cmd & 0xff ) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " trying to set frequency to state %u "
2009-01-18 07:55:22 +03:00
" with command %x at port %x \n " ,
state , command , smi_port ) ;
2005-04-17 02:20:36 +04:00
do {
if ( retry ) {
2015-02-09 21:38:17 +03:00
/*
* We need to enable interrupts , otherwise the blockage
* won ' t resolve .
*
* We disable preemption so that other processes don ' t
* run . If other processes were running , they could
* submit more DMA requests , making the blockage worse .
*/
2011-03-27 17:04:46 +04:00
pr_debug ( " retry %u, previous result %u, waiting... \n " ,
2009-01-18 07:55:22 +03:00
retry , result ) ;
2015-02-09 21:38:17 +03:00
local_irq_enable ( ) ;
2005-04-17 02:20:36 +04:00
mdelay ( retry * 50 ) ;
2015-02-09 21:38:17 +03:00
local_irq_disable ( ) ;
2005-04-17 02:20:36 +04:00
}
retry + + ;
__asm__ __volatile__ (
2008-03-10 18:05:41 +03:00
" push %%ebp \n "
2005-04-17 02:20:36 +04:00
" out %%al, (%%dx) \n "
2008-03-10 18:05:41 +03:00
" pop %%ebp "
2009-01-18 07:55:22 +03:00
: " =b " ( new_state ) , " =D " ( result ) ,
" =c " ( dummy ) , " =a " ( dummy ) ,
" =d " ( dummy ) , " =S " ( dummy )
: " a " ( command ) , " b " ( function ) , " c " ( state ) ,
" d " ( smi_port ) , " S " ( 0 ) , " D " ( 0 )
2005-04-17 02:20:36 +04:00
) ;
} while ( ( new_state ! = state ) & & ( retry < = SMI_TRIES ) ) ;
/* enable IRQs */
local_irq_restore ( flags ) ;
2015-02-09 21:38:17 +03:00
preempt_enable ( ) ;
2005-04-17 02:20:36 +04:00
2009-01-18 07:55:22 +03:00
if ( new_state = = state )
2011-03-27 17:04:46 +04:00
pr_debug ( " change to %u MHz succeeded after %u tries "
2009-01-18 07:55:22 +03:00
" with result %u \n " ,
( speedstep_freqs [ new_state ] . frequency / 1000 ) ,
retry , result ) ;
else
2016-04-05 23:28:25 +03:00
pr_err ( " change to state %u failed with new_state %u and result %u \n " ,
2016-04-05 23:28:24 +03:00
state , new_state , result ) ;
2005-04-17 02:20:36 +04:00
return ;
}
/**
* speedstep_target - set a new CPUFreq policy
* @ policy : new policy
2013-10-25 18:15:48 +04:00
* @ index : index of new freq
2005-04-17 02:20:36 +04:00
*
* Sets a new CPUFreq policy / freq .
*/
2013-10-25 18:15:48 +04:00
static int speedstep_target ( struct cpufreq_policy * policy , unsigned int index )
2005-04-17 02:20:36 +04:00
{
2013-10-25 18:15:48 +04:00
speedstep_set_state ( index ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
static int speedstep_cpu_init ( struct cpufreq_policy * policy )
{
int result ;
2009-01-18 07:55:22 +03:00
unsigned int * low , * high ;
2005-04-17 02:20:36 +04:00
/* capability check */
if ( policy - > cpu ! = 0 )
return - ENODEV ;
result = speedstep_smi_ownership ( ) ;
if ( result ) {
2011-03-27 17:04:46 +04:00
pr_debug ( " fails in acquiring ownership of a SMI interface. \n " ) ;
2005-04-17 02:20:36 +04:00
return - EINVAL ;
}
/* detect low and high frequency */
2009-01-18 07:55:22 +03:00
low = & speedstep_freqs [ SPEEDSTEP_LOW ] . frequency ;
high = & speedstep_freqs [ SPEEDSTEP_HIGH ] . frequency ;
result = speedstep_smi_get_freqs ( low , high ) ;
2005-04-17 02:20:36 +04:00
if ( result ) {
2009-01-18 07:55:22 +03:00
/* fall back to speedstep_lib.c dection mechanism:
* try both states out */
2011-03-27 17:04:46 +04:00
pr_debug ( " could not detect low and high frequencies "
2009-01-18 07:55:22 +03:00
" by SMI call. \n " ) ;
2005-04-17 02:20:36 +04:00
result = speedstep_get_freqs ( speedstep_processor ,
2009-01-18 07:55:22 +03:00
low , high ,
2005-12-02 23:59:41 +03:00
NULL ,
2005-04-17 02:20:36 +04:00
& speedstep_set_state ) ;
if ( result ) {
2011-03-27 17:04:46 +04:00
pr_debug ( " could not detect two different speeds "
2009-01-18 07:55:22 +03:00
" -- aborting. \n " ) ;
2005-04-17 02:20:36 +04:00
return result ;
} else
2011-03-27 17:04:46 +04:00
pr_debug ( " workaround worked. \n " ) ;
2005-04-17 02:20:36 +04:00
}
2018-02-26 08:09:08 +03:00
policy - > freq_table = speedstep_freqs ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
static unsigned int speedstep_get ( unsigned int cpu )
{
if ( cpu )
return - ENODEV ;
2009-01-18 07:55:22 +03:00
return speedstep_get_frequency ( speedstep_processor ) ;
2005-04-17 02:20:36 +04:00
}
static int speedstep_resume ( struct cpufreq_policy * policy )
{
int result = speedstep_smi_ownership ( ) ;
if ( result )
2011-03-27 17:04:46 +04:00
pr_debug ( " fails in re-acquiring ownership of a SMI interface. \n " ) ;
2005-04-17 02:20:36 +04:00
return result ;
}
2007-02-27 01:55:48 +03:00
static struct cpufreq_driver speedstep_driver = {
2005-04-17 02:20:36 +04:00
. name = " speedstep-smi " ,
2017-07-19 13:12:48 +03:00
. flags = CPUFREQ_NO_AUTO_DYNAMIC_SWITCHING ,
2013-10-03 18:58:28 +04:00
. verify = cpufreq_generic_frequency_table_verify ,
2013-10-25 18:15:48 +04:00
. target_index = speedstep_target ,
2005-04-17 02:20:36 +04:00
. init = speedstep_cpu_init ,
. get = speedstep_get ,
. resume = speedstep_resume ,
2013-10-03 18:58:28 +04:00
. attr = cpufreq_generic_attr ,
2005-04-17 02:20:36 +04:00
} ;
2012-01-26 03:09:12 +04:00
static const struct x86_cpu_id ss_smi_ids [ ] = {
2020-03-24 16:51:51 +03:00
X86_MATCH_VENDOR_FAM_MODEL ( INTEL , 6 , 0x8 , 0 ) ,
X86_MATCH_VENDOR_FAM_MODEL ( INTEL , 6 , 0xb , 0 ) ,
X86_MATCH_VENDOR_FAM_MODEL ( INTEL , 15 , 0x2 , 0 ) ,
2012-01-26 03:09:12 +04:00
{ }
} ;
2005-04-17 02:20:36 +04:00
/**
* speedstep_init - initializes the SpeedStep CPUFreq driver
*
* Initializes the SpeedStep support . Returns - ENODEV on unsupported
* BIOS , - EINVAL on problems during initiatization , and zero on
* success .
*/
static int __init speedstep_init ( void )
{
2012-01-26 03:09:12 +04:00
if ( ! x86_match_cpu ( ss_smi_ids ) )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
speedstep_processor = speedstep_detect_processor ( ) ;
switch ( speedstep_processor ) {
2009-01-18 07:55:22 +03:00
case SPEEDSTEP_CPU_PIII_T :
case SPEEDSTEP_CPU_PIII_C :
case SPEEDSTEP_CPU_PIII_C_EARLY :
2005-04-17 02:20:36 +04:00
break ;
default :
speedstep_processor = 0 ;
}
if ( ! speedstep_processor ) {
2011-03-27 17:04:46 +04:00
pr_debug ( " No supported Intel CPU detected. \n " ) ;
2005-04-17 02:20:36 +04:00
return - ENODEV ;
}
2014-08-07 09:52:23 +04:00
pr_debug ( " signature:0x%.8x, command:0x%.8x, "
" event:0x%.8x, perf_level:0x%.8x. \n " ,
2009-01-18 07:55:22 +03:00
ist_info . signature , ist_info . command ,
ist_info . event , ist_info . perf_level ) ;
2005-04-17 02:20:36 +04:00
2006-02-28 08:43:23 +03:00
/* Error if no IST-SMI BIOS or no PARM
2005-04-17 02:20:36 +04:00
sig = ' ISGE ' aka ' Intel Speedstep Gate E ' */
2006-02-28 08:43:23 +03:00
if ( ( ist_info . signature ! = 0x47534943 ) & & (
2005-04-17 02:20:36 +04:00
( smi_port = = 0 ) | | ( smi_cmd = = 0 ) ) )
return - ENODEV ;
if ( smi_sig = = 1 )
smi_sig = 0x47534943 ;
else
smi_sig = ist_info . signature ;
/* setup smi_port from MODLULE_PARM or BIOS */
2006-02-28 08:43:23 +03:00
if ( ( smi_port > 0xff ) | | ( smi_port < 0 ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2006-02-28 08:43:23 +03:00
else if ( smi_port = = 0 )
2005-04-17 02:20:36 +04:00
smi_port = ist_info . command & 0xff ;
2006-02-28 08:43:23 +03:00
if ( ( smi_cmd > 0xff ) | | ( smi_cmd < 0 ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
2006-02-28 08:43:23 +03:00
else if ( smi_cmd = = 0 )
2005-04-17 02:20:36 +04:00
smi_cmd = ( ist_info . command > > 16 ) & 0xff ;
return cpufreq_register_driver ( & speedstep_driver ) ;
}
/**
* speedstep_exit - unregisters SpeedStep support
*
* Unregisters SpeedStep support .
*/
static void __exit speedstep_exit ( void )
{
cpufreq_unregister_driver ( & speedstep_driver ) ;
}
2017-04-04 18:54:22 +03:00
module_param_hw ( smi_port , int , ioport , 0444 ) ;
2009-01-18 07:55:22 +03:00
module_param ( smi_cmd , int , 0444 ) ;
module_param ( smi_sig , uint , 0444 ) ;
2005-04-17 02:20:36 +04:00
2009-01-18 07:55:22 +03:00
MODULE_PARM_DESC ( smi_port , " Override the BIOS-given IST port with this value "
" -- Intel's default setting is 0xb2 " ) ;
MODULE_PARM_DESC ( smi_cmd , " Override the BIOS-given IST command with this value "
" -- Intel's default setting is 0x82 " ) ;
MODULE_PARM_DESC ( smi_sig , " Set to 1 to fake the IST signature when using the "
" SMI interface. " ) ;
2005-04-17 02:20:36 +04:00
2009-01-18 07:55:22 +03:00
MODULE_AUTHOR ( " Hiroshi Miura " ) ;
MODULE_DESCRIPTION ( " Speedstep driver for IST applet SMI interface. " ) ;
MODULE_LICENSE ( " GPL " ) ;
2005-04-17 02:20:36 +04:00
module_init ( speedstep_init ) ;
module_exit ( speedstep_exit ) ;