2005-04-17 02:20:36 +04:00
/*
* Intel SpeedStep SMI driver .
*
* ( C ) 2003 Hiroshi Miura < miura @ da - cha . org >
*
* Licensed under the terms of the GNU GPL License version 2.
*
*/
/*********************************************************************
* SPEEDSTEP - DEFINITIONS *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
# 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 [ ] = {
2006-02-28 08:43:23 +03:00
{ SPEEDSTEP_HIGH , 0 } ,
2005-04-17 02:20:36 +04:00
{ SPEEDSTEP_LOW , 0 } ,
{ 0 , CPUFREQ_TABLE_END } ,
} ;
# 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_get_state - set the SpeedStep state
* @ state : processor frequency state ( SPEEDSTEP_LOW or SPEEDSTEP_HIGH )
*
*/
2009-01-18 07:55:22 +03:00
static int speedstep_get_state ( void )
2005-04-17 02:20:36 +04:00
{
2009-01-18 07:55:22 +03:00
u32 function = GET_SPEEDSTEP_STATE ;
2008-03-10 18:05:41 +03:00
u32 result , state , edi , command , dummy ;
2005-04-17 02:20:36 +04:00
command = ( smi_sig & 0xffffff00 ) | ( smi_cmd & 0xff ) ;
2011-03-27 17:04:46 +04:00
pr_debug ( " trying to determine current setting with command %x "
2009-01-18 07:55:22 +03:00
" at port %x \n " , 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 \n "
2009-01-18 07:55:22 +03:00
: " =a " ( result ) ,
" =b " ( state ) , " =D " ( edi ) ,
" =c " ( dummy ) , " =d " ( dummy ) , " =S " ( dummy )
: " a " ( command ) , " b " ( function ) , " c " ( 0 ) ,
" 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 ( " state is %x, result is %x \n " , state , result ) ;
2005-04-17 02:20:36 +04:00
2009-01-18 07:55:22 +03:00
return state & 1 ;
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 */
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 ) {
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 ) ;
2005-04-17 02:20:36 +04:00
mdelay ( retry * 50 ) ;
}
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 ) ;
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
printk ( KERN_ERR " cpufreq: change to state %u "
" failed with new_state %u and result %u \n " ,
state , new_state , result ) ;
2005-04-17 02:20:36 +04:00
return ;
}
/**
* speedstep_target - set a new CPUFreq policy
* @ policy : new policy
* @ target_freq : new freq
2006-02-28 08:43:23 +03:00
* @ relation :
2005-04-17 02:20:36 +04:00
*
* Sets a new CPUFreq policy / freq .
*/
2009-01-18 07:55:22 +03:00
static int speedstep_target ( struct cpufreq_policy * policy ,
2005-04-17 02:20:36 +04:00
unsigned int target_freq , unsigned int relation )
{
unsigned int newstate = 0 ;
struct cpufreq_freqs freqs ;
2009-01-18 07:55:22 +03:00
if ( cpufreq_frequency_table_target ( policy , & speedstep_freqs [ 0 ] ,
target_freq , relation , & newstate ) )
2005-04-17 02:20:36 +04:00
return - EINVAL ;
freqs . old = speedstep_freqs [ speedstep_get_state ( ) ] . frequency ;
freqs . new = speedstep_freqs [ newstate ] . frequency ;
if ( freqs . old = = freqs . new )
return 0 ;
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_PRECHANGE ) ;
2005-04-17 02:20:36 +04:00
speedstep_set_state ( newstate ) ;
2013-03-24 10:26:43 +04:00
cpufreq_notify_transition ( policy , & freqs , CPUFREQ_POSTCHANGE ) ;
2005-04-17 02:20:36 +04:00
return 0 ;
}
/**
* speedstep_verify - verifies a new CPUFreq policy
* @ policy : new policy
*
* Limit must be within speedstep_low_freq and speedstep_high_freq , with
* at least one border included .
*/
2009-01-18 07:55:22 +03:00
static int speedstep_verify ( struct cpufreq_policy * policy )
2005-04-17 02:20:36 +04:00
{
return cpufreq_frequency_table_verify ( policy , & speedstep_freqs [ 0 ] ) ;
}
static int speedstep_cpu_init ( struct cpufreq_policy * policy )
{
int result ;
2009-01-18 07:55:22 +03:00
unsigned int speed , state ;
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
}
/* get current speed setting */
state = speedstep_get_state ( ) ;
speed = speedstep_freqs [ state ] . frequency ;
2011-03-27 17:04:46 +04:00
pr_debug ( " currently at %s speed setting - %i MHz \n " ,
2009-01-18 07:55:22 +03:00
( speed = = speedstep_freqs [ SPEEDSTEP_LOW ] . frequency )
? " low " : " high " ,
2005-04-17 02:20:36 +04:00
( speed / 1000 ) ) ;
/* cpuinfo and default policy values */
policy - > cpuinfo . transition_latency = CPUFREQ_ETERNAL ;
policy - > cur = speed ;
2013-09-16 17:26:38 +04:00
return cpufreq_table_validate_and_show ( policy , speedstep_freqs ) ;
2005-04-17 02:20:36 +04:00
}
static int speedstep_cpu_exit ( struct cpufreq_policy * policy )
{
cpufreq_frequency_table_put_attr ( policy - > cpu ) ;
return 0 ;
}
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 ;
}
2009-01-18 07:55:22 +03:00
static struct freq_attr * speedstep_attr [ ] = {
2005-04-17 02:20:36 +04:00
& cpufreq_freq_attr_scaling_available_freqs ,
NULL ,
} ;
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 " ,
2006-02-28 08:43:23 +03:00
. verify = speedstep_verify ,
. target = speedstep_target ,
2005-04-17 02:20:36 +04:00
. init = speedstep_cpu_init ,
. exit = speedstep_cpu_exit ,
. get = speedstep_get ,
. resume = speedstep_resume ,
. attr = speedstep_attr ,
} ;
2012-01-26 03:09:12 +04:00
static const struct x86_cpu_id ss_smi_ids [ ] = {
{ X86_VENDOR_INTEL , 6 , 0xb , } ,
{ X86_VENDOR_INTEL , 6 , 0x8 , } ,
{ X86_VENDOR_INTEL , 15 , 2 } ,
{ }
} ;
#if 0
/* Not auto loaded currently */
MODULE_DEVICE_TABLE ( x86cpu , ss_smi_ids ) ;
# endif
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 ;
}
2011-03-27 17:04:46 +04:00
pr_debug ( " signature:0x%.8ulx, command:0x%.8ulx, "
" event:0x%.8ulx, perf_level:0x%.8ulx. \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 ) ;
}
2009-01-18 07:55:22 +03:00
module_param ( smi_port , int , 0444 ) ;
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 ) ;