2005-04-17 02:20:36 +04:00
/*
* processor_thermal . c - Passive cooling submodule of the ACPI processor driver
*
* Copyright ( C ) 2001 , 2002 Andy Grover < andrew . grover @ intel . com >
* Copyright ( C ) 2001 , 2002 Paul Diefenbaugh < paul . s . diefenbaugh @ intel . com >
* Copyright ( C ) 2004 Dominik Brodowski < linux @ brodo . de >
* Copyright ( C ) 2004 Anil S Keshavamurthy < anil . s . keshavamurthy @ intel . com >
* - Added processor hotplug support
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or ( at
* your option ) any later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA .
*
* ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~ ~
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/cpufreq.h>
# include <linux/proc_fs.h>
# include <linux/seq_file.h>
2008-01-17 10:51:23 +03:00
# include <linux/sysdev.h>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include <acpi/acpi_bus.h>
# include <acpi/processor.h>
# include <acpi/acpi_drivers.h>
# define ACPI_PROCESSOR_COMPONENT 0x01000000
# define ACPI_PROCESSOR_CLASS "processor"
# define _COMPONENT ACPI_PROCESSOR_COMPONENT
2007-02-13 06:42:12 +03:00
ACPI_MODULE_NAME ( " processor_thermal " ) ;
2005-04-17 02:20:36 +04:00
/* --------------------------------------------------------------------------
Limit Interface
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
2005-08-05 08:44:28 +04:00
static int acpi_processor_apply_limit ( struct acpi_processor * pr )
2005-04-17 02:20:36 +04:00
{
2005-08-05 08:44:28 +04:00
int result = 0 ;
u16 px = 0 ;
u16 tx = 0 ;
2005-04-17 02:20:36 +04:00
if ( ! pr )
2006-06-27 08:41:40 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
if ( ! pr - > flags . limit )
2006-06-27 08:41:40 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
if ( pr - > flags . throttling ) {
if ( pr - > limit . user . tx > tx )
tx = pr - > limit . user . tx ;
if ( pr - > limit . thermal . tx > tx )
tx = pr - > limit . thermal . tx ;
result = acpi_processor_set_throttling ( pr , tx ) ;
if ( result )
goto end ;
}
pr - > limit . state . px = px ;
pr - > limit . state . tx = tx ;
2005-08-05 08:44:28 +04:00
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
" Processor [%d] limit set to (P%d:T%d) \n " , pr - > id ,
pr - > limit . state . px , pr - > limit . state . tx ) ) ;
2005-04-17 02:20:36 +04:00
2005-08-05 08:44:28 +04:00
end :
2005-04-17 02:20:36 +04:00
if ( result )
2006-06-27 07:41:38 +04:00
printk ( KERN_ERR PREFIX " Unable to set limit \n " ) ;
2005-04-17 02:20:36 +04:00
2006-06-27 08:41:40 +04:00
return result ;
2005-04-17 02:20:36 +04:00
}
# ifdef CONFIG_CPU_FREQ
/* If a passive cooling situation is detected, primarily CPUfreq is used, as it
* offers ( in most cases ) voltage scaling in addition to frequency scaling , and
* thus a cubic ( instead of linear ) reduction of energy . Also , we allow for
* _any_ cpufreq driver and not only the acpi - cpufreq driver .
*/
2008-01-17 10:51:23 +03:00
# define CPUFREQ_THERMAL_MIN_STEP 0
# define CPUFREQ_THERMAL_MAX_STEP 3
2008-03-05 19:31:29 +03:00
static DEFINE_PER_CPU ( unsigned int , cpufreq_thermal_reduction_pctg ) ;
2005-04-17 02:20:36 +04:00
static unsigned int acpi_thermal_cpufreq_is_init = 0 ;
static int cpu_has_cpufreq ( unsigned int cpu )
{
struct cpufreq_policy policy ;
2004-09-16 19:07:00 +04:00
if ( ! acpi_thermal_cpufreq_is_init | | cpufreq_get_policy ( & policy , cpu ) )
2005-12-21 09:29:00 +03:00
return 0 ;
return 1 ;
2005-04-17 02:20:36 +04:00
}
static int acpi_thermal_cpufreq_increase ( unsigned int cpu )
{
if ( ! cpu_has_cpufreq ( cpu ) )
return - ENODEV ;
2008-03-05 19:31:29 +03:00
if ( per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) <
2008-01-17 10:51:23 +03:00
CPUFREQ_THERMAL_MAX_STEP ) {
2008-03-05 19:31:29 +03:00
per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) + + ;
2005-04-17 02:20:36 +04:00
cpufreq_update_policy ( cpu ) ;
return 0 ;
}
return - ERANGE ;
}
static int acpi_thermal_cpufreq_decrease ( unsigned int cpu )
{
if ( ! cpu_has_cpufreq ( cpu ) )
return - ENODEV ;
2008-03-05 19:31:29 +03:00
if ( per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) >
2008-01-17 10:51:23 +03:00
( CPUFREQ_THERMAL_MIN_STEP + 1 ) )
2008-03-05 19:31:29 +03:00
per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) - - ;
2004-09-16 19:07:00 +04:00
else
2008-03-05 19:31:29 +03:00
per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) = 0 ;
2004-09-16 19:07:00 +04:00
cpufreq_update_policy ( cpu ) ;
/* We reached max freq again and can leave passive mode */
2008-03-05 19:31:29 +03:00
return ! per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) ;
2005-04-17 02:20:36 +04:00
}
2005-08-05 08:44:28 +04:00
static int acpi_thermal_cpufreq_notifier ( struct notifier_block * nb ,
unsigned long event , void * data )
2005-04-17 02:20:36 +04:00
{
struct cpufreq_policy * policy = data ;
unsigned long max_freq = 0 ;
if ( event ! = CPUFREQ_ADJUST )
goto out ;
2008-03-05 19:31:29 +03:00
max_freq = (
policy - > cpuinfo . max_freq *
( 100 - per_cpu ( cpufreq_thermal_reduction_pctg , policy - > cpu ) * 20 )
) / 100 ;
2005-04-17 02:20:36 +04:00
cpufreq_verify_within_limits ( policy , 0 , max_freq ) ;
2005-08-05 08:44:28 +04:00
out :
2005-04-17 02:20:36 +04:00
return 0 ;
}
static struct notifier_block acpi_thermal_cpufreq_notifier_block = {
. notifier_call = acpi_thermal_cpufreq_notifier ,
} ;
2008-01-17 10:51:23 +03:00
static int cpufreq_get_max_state ( unsigned int cpu )
{
if ( ! cpu_has_cpufreq ( cpu ) )
return 0 ;
return CPUFREQ_THERMAL_MAX_STEP ;
}
static int cpufreq_get_cur_state ( unsigned int cpu )
{
if ( ! cpu_has_cpufreq ( cpu ) )
return 0 ;
2008-03-05 19:31:29 +03:00
return per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) ;
2008-01-17 10:51:23 +03:00
}
static int cpufreq_set_cur_state ( unsigned int cpu , int state )
{
if ( ! cpu_has_cpufreq ( cpu ) )
return 0 ;
2008-03-05 19:31:29 +03:00
per_cpu ( cpufreq_thermal_reduction_pctg , cpu ) = state ;
2008-01-17 10:51:23 +03:00
cpufreq_update_policy ( cpu ) ;
return 0 ;
}
2005-08-05 08:44:28 +04:00
void acpi_thermal_cpufreq_init ( void )
{
2005-04-17 02:20:36 +04:00
int i ;
2008-03-05 19:31:29 +03:00
for ( i = 0 ; i < nr_cpu_ids ; i + + )
if ( cpu_present ( i ) )
per_cpu ( cpufreq_thermal_reduction_pctg , i ) = 0 ;
2005-04-17 02:20:36 +04:00
2005-08-05 08:44:28 +04:00
i = cpufreq_register_notifier ( & acpi_thermal_cpufreq_notifier_block ,
CPUFREQ_POLICY_NOTIFIER ) ;
2005-04-17 02:20:36 +04:00
if ( ! i )
acpi_thermal_cpufreq_is_init = 1 ;
}
2005-08-05 08:44:28 +04:00
void acpi_thermal_cpufreq_exit ( void )
{
2005-04-17 02:20:36 +04:00
if ( acpi_thermal_cpufreq_is_init )
2005-08-05 08:44:28 +04:00
cpufreq_unregister_notifier
( & acpi_thermal_cpufreq_notifier_block ,
CPUFREQ_POLICY_NOTIFIER ) ;
2005-04-17 02:20:36 +04:00
acpi_thermal_cpufreq_is_init = 0 ;
}
2005-08-05 08:44:28 +04:00
# else /* ! CONFIG_CPU_FREQ */
2008-01-17 10:51:23 +03:00
static int cpufreq_get_max_state ( unsigned int cpu )
{
return 0 ;
}
static int cpufreq_get_cur_state ( unsigned int cpu )
{
return 0 ;
}
static int cpufreq_set_cur_state ( unsigned int cpu , int state )
{
return 0 ;
}
2005-04-17 02:20:36 +04:00
2005-08-05 08:44:28 +04:00
static int acpi_thermal_cpufreq_increase ( unsigned int cpu )
{
return - ENODEV ;
}
static int acpi_thermal_cpufreq_decrease ( unsigned int cpu )
{
return - ENODEV ;
}
2005-04-17 02:20:36 +04:00
# endif
2005-08-05 08:44:28 +04:00
int acpi_processor_set_thermal_limit ( acpi_handle handle , int type )
2005-04-17 02:20:36 +04:00
{
2005-08-05 08:44:28 +04:00
int result = 0 ;
struct acpi_processor * pr = NULL ;
struct acpi_device * device = NULL ;
2004-09-16 19:07:00 +04:00
int tx = 0 , max_tx_px = 0 ;
2005-04-17 02:20:36 +04:00
if ( ( type < ACPI_PROCESSOR_LIMIT_NONE )
2005-08-05 08:44:28 +04:00
| | ( type > ACPI_PROCESSOR_LIMIT_DECREMENT ) )
2006-06-27 08:41:40 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
result = acpi_bus_get_device ( handle , & device ) ;
if ( result )
2006-06-27 08:41:40 +04:00
return result ;
2005-04-17 02:20:36 +04:00
2006-10-01 02:28:50 +04:00
pr = acpi_driver_data ( device ) ;
2005-04-17 02:20:36 +04:00
if ( ! pr )
2006-06-27 08:41:40 +04:00
return - ENODEV ;
2005-04-17 02:20:36 +04:00
/* Thermal limits are always relative to the current Px/Tx state. */
if ( pr - > flags . throttling )
pr - > limit . thermal . tx = pr - > throttling . state ;
/*
* Our default policy is to only use throttling at the lowest
* performance state .
*/
tx = pr - > limit . thermal . tx ;
switch ( type ) {
case ACPI_PROCESSOR_LIMIT_NONE :
do {
result = acpi_thermal_cpufreq_decrease ( pr - > id ) ;
} while ( ! result ) ;
tx = 0 ;
break ;
case ACPI_PROCESSOR_LIMIT_INCREMENT :
/* if going up: P-states first, T-states later */
result = acpi_thermal_cpufreq_increase ( pr - > id ) ;
if ( ! result )
goto end ;
else if ( result = = - ERANGE )
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
2005-08-05 08:44:28 +04:00
" At maximum performance state \n " ) ) ;
2005-04-17 02:20:36 +04:00
if ( pr - > flags . throttling ) {
if ( tx = = ( pr - > throttling . state_count - 1 ) )
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
2005-08-05 08:44:28 +04:00
" At maximum throttling state \n " ) ) ;
2005-04-17 02:20:36 +04:00
else
tx + + ;
}
break ;
case ACPI_PROCESSOR_LIMIT_DECREMENT :
/* if going down: T-states first, P-states later */
if ( pr - > flags . throttling ) {
2004-09-16 19:07:00 +04:00
if ( tx = = 0 ) {
max_tx_px = 1 ;
2005-04-17 02:20:36 +04:00
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
2005-08-05 08:44:28 +04:00
" At minimum throttling state \n " ) ) ;
2004-09-16 19:07:00 +04:00
} else {
2005-04-17 02:20:36 +04:00
tx - - ;
goto end ;
}
}
result = acpi_thermal_cpufreq_decrease ( pr - > id ) ;
2004-09-16 19:07:00 +04:00
if ( result ) {
/*
* We only could get - ERANGE , 1 or 0.
* In the first two cases we reached max freq again .
*/
2005-04-17 02:20:36 +04:00
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO ,
2005-08-05 08:44:28 +04:00
" At minimum performance state \n " ) ) ;
2004-09-16 19:07:00 +04:00
max_tx_px = 1 ;
} else
max_tx_px = 0 ;
2005-04-17 02:20:36 +04:00
break ;
}
2005-08-05 08:44:28 +04:00
end :
2005-04-17 02:20:36 +04:00
if ( pr - > flags . throttling ) {
pr - > limit . thermal . px = 0 ;
pr - > limit . thermal . tx = tx ;
result = acpi_processor_apply_limit ( pr ) ;
if ( result )
2006-06-27 07:41:38 +04:00
printk ( KERN_ERR PREFIX " Unable to set thermal limit \n " ) ;
2005-04-17 02:20:36 +04:00
ACPI_DEBUG_PRINT ( ( ACPI_DB_INFO , " Thermal limit now (P%d:T%d) \n " ,
2005-08-05 08:44:28 +04:00
pr - > limit . thermal . px , pr - > limit . thermal . tx ) ) ;
2005-04-17 02:20:36 +04:00
} else
result = 0 ;
2004-09-16 19:07:00 +04:00
if ( max_tx_px )
2006-06-27 08:41:40 +04:00
return 1 ;
2004-09-16 19:07:00 +04:00
else
2006-06-27 08:41:40 +04:00
return result ;
2005-04-17 02:20:36 +04:00
}
2005-08-05 08:44:28 +04:00
int acpi_processor_get_limit_info ( struct acpi_processor * pr )
2005-04-17 02:20:36 +04:00
{
if ( ! pr )
2006-06-27 08:41:40 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
if ( pr - > flags . throttling )
pr - > flags . limit = 1 ;
2006-06-27 08:41:40 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2008-01-17 10:51:23 +03:00
/* thermal coolign device callbacks */
static int acpi_processor_max_state ( struct acpi_processor * pr )
{
int max_state = 0 ;
/*
* There exists four states according to
* cpufreq_thermal_reduction_ptg . 0 , 1 , 2 , 3
*/
max_state + = cpufreq_get_max_state ( pr - > id ) ;
if ( pr - > flags . throttling )
max_state + = ( pr - > throttling . state_count - 1 ) ;
return max_state ;
}
static int
processor_get_max_state ( struct thermal_cooling_device * cdev , char * buf )
{
struct acpi_device * device = cdev - > devdata ;
struct acpi_processor * pr = acpi_driver_data ( device ) ;
if ( ! device | | ! pr )
return - EINVAL ;
return sprintf ( buf , " %d \n " , acpi_processor_max_state ( pr ) ) ;
}
static int
processor_get_cur_state ( struct thermal_cooling_device * cdev , char * buf )
{
struct acpi_device * device = cdev - > devdata ;
struct acpi_processor * pr = acpi_driver_data ( device ) ;
int cur_state ;
if ( ! device | | ! pr )
return - EINVAL ;
cur_state = cpufreq_get_cur_state ( pr - > id ) ;
if ( pr - > flags . throttling )
cur_state + = pr - > throttling . state ;
return sprintf ( buf , " %d \n " , cur_state ) ;
}
static int
processor_set_cur_state ( struct thermal_cooling_device * cdev , unsigned int state )
{
struct acpi_device * device = cdev - > devdata ;
struct acpi_processor * pr = acpi_driver_data ( device ) ;
int result = 0 ;
int max_pstate ;
if ( ! device | | ! pr )
return - EINVAL ;
max_pstate = cpufreq_get_max_state ( pr - > id ) ;
if ( state > acpi_processor_max_state ( pr ) )
return - EINVAL ;
if ( state < = max_pstate ) {
if ( pr - > flags . throttling & & pr - > throttling . state )
result = acpi_processor_set_throttling ( pr , 0 ) ;
cpufreq_set_cur_state ( pr - > id , state ) ;
} else {
cpufreq_set_cur_state ( pr - > id , max_pstate ) ;
result = acpi_processor_set_throttling ( pr ,
state - max_pstate ) ;
}
return result ;
}
struct thermal_cooling_device_ops processor_cooling_ops = {
. get_max_state = processor_get_max_state ,
. get_cur_state = processor_get_cur_state ,
. set_cur_state = processor_set_cur_state ,
} ;
2005-04-17 02:20:36 +04:00
/* /proc interface */
static int acpi_processor_limit_seq_show ( struct seq_file * seq , void * offset )
{
2005-08-05 08:44:28 +04:00
struct acpi_processor * pr = ( struct acpi_processor * ) seq - > private ;
2005-04-17 02:20:36 +04:00
if ( ! pr )
goto end ;
if ( ! pr - > flags . limit ) {
seq_puts ( seq , " <not supported> \n " ) ;
goto end ;
}
seq_printf ( seq , " active limit: P%d:T%d \n "
2005-08-05 08:44:28 +04:00
" user limit: P%d:T%d \n "
" thermal limit: P%d:T%d \n " ,
pr - > limit . state . px , pr - > limit . state . tx ,
pr - > limit . user . px , pr - > limit . user . tx ,
pr - > limit . thermal . px , pr - > limit . thermal . tx ) ;
2005-04-17 02:20:36 +04:00
2005-08-05 08:44:28 +04:00
end :
2006-06-27 08:41:40 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
static int acpi_processor_limit_open_fs ( struct inode * inode , struct file * file )
{
return single_open ( file , acpi_processor_limit_seq_show ,
2005-08-05 08:44:28 +04:00
PDE ( inode ) - > data ) ;
2005-04-17 02:20:36 +04:00
}
2006-01-07 21:19:00 +03:00
static ssize_t acpi_processor_write_limit ( struct file * file ,
const char __user * buffer ,
size_t count , loff_t * data )
2005-04-17 02:20:36 +04:00
{
2005-08-05 08:44:28 +04:00
int result = 0 ;
2006-10-01 02:28:50 +04:00
struct seq_file * m = file - > private_data ;
struct acpi_processor * pr = m - > private ;
2005-08-05 08:44:28 +04:00
char limit_string [ 25 ] = { ' \0 ' } ;
int px = 0 ;
int tx = 0 ;
2005-04-17 02:20:36 +04:00
if ( ! pr | | ( count > sizeof ( limit_string ) - 1 ) ) {
2006-06-27 08:41:40 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
if ( copy_from_user ( limit_string , buffer , count ) ) {
2006-06-27 08:41:40 +04:00
return - EFAULT ;
2005-04-17 02:20:36 +04:00
}
limit_string [ count ] = ' \0 ' ;
if ( sscanf ( limit_string , " %d:%d " , & px , & tx ) ! = 2 ) {
2006-06-27 07:41:38 +04:00
printk ( KERN_ERR PREFIX " Invalid data format \n " ) ;
2006-06-27 08:41:40 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
if ( pr - > flags . throttling ) {
if ( ( tx < 0 ) | | ( tx > ( pr - > throttling . state_count - 1 ) ) ) {
2006-06-27 07:41:38 +04:00
printk ( KERN_ERR PREFIX " Invalid tx \n " ) ;
2006-06-27 08:41:40 +04:00
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
pr - > limit . user . tx = tx ;
}
result = acpi_processor_apply_limit ( pr ) ;
2006-06-27 08:41:40 +04:00
return count ;
2005-04-17 02:20:36 +04:00
}
struct file_operations acpi_processor_limit_fops = {
2005-08-05 08:44:28 +04:00
. open = acpi_processor_limit_open_fs ,
. read = seq_read ,
2006-01-07 00:47:00 +03:00
. write = acpi_processor_write_limit ,
2005-08-05 08:44:28 +04:00
. llseek = seq_lseek ,
. release = single_release ,
2005-04-17 02:20:36 +04:00
} ;