2013-05-18 03:42:02 +04:00
/*
* x86_pkg_temp_thermal driver
* Copyright ( c ) 2013 , Intel Corporation .
*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* This program is distributed in the hope 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 .
*
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/module.h>
# include <linux/init.h>
# include <linux/err.h>
# include <linux/param.h>
# include <linux/device.h>
# include <linux/platform_device.h>
# include <linux/cpu.h>
# include <linux/smp.h>
# include <linux/slab.h>
# include <linux/pm.h>
# include <linux/thermal.h>
# include <linux/debugfs.h>
# include <asm/cpu_device_id.h>
# include <asm/mce.h>
/*
* Rate control delay : Idea is to introduce denounce effect
* This should be long enough to avoid reduce events , when
* threshold is set to a temperature , which is constantly
* violated , but at the short enough to take any action .
* The action can be remove threshold or change it to next
* interesting setting . Based on experiments , in around
* every 5 seconds under load will give us a significant
* temperature change .
*/
# define PKG_TEMP_THERMAL_NOTIFY_DELAY 5000
static int notify_delay_ms = PKG_TEMP_THERMAL_NOTIFY_DELAY ;
module_param ( notify_delay_ms , int , 0644 ) ;
MODULE_PARM_DESC ( notify_delay_ms ,
" User space notification delay in milli seconds. " ) ;
/* Number of trip points in thermal zone. Currently it can't
* be more than 2. MSR can allow setting and getting notifications
* for only 2 thresholds . This define enforces this , if there
* is some wrong values returned by cpuid for number of thresholds .
*/
# define MAX_NUMBER_OF_TRIPS 2
2016-11-22 20:57:08 +03:00
struct pkg_device {
2016-11-22 20:57:14 +03:00
int cpu ;
2016-11-22 20:57:12 +03:00
bool work_scheduled ;
2016-11-22 20:57:08 +03:00
u32 tj_max ;
u32 msr_pkg_therm_low ;
u32 msr_pkg_therm_high ;
2016-11-22 20:57:13 +03:00
struct delayed_work work ;
2016-11-22 20:57:08 +03:00
struct thermal_zone_device * tzone ;
2016-11-22 20:57:10 +03:00
struct cpumask cpumask ;
2013-05-18 03:42:02 +04:00
} ;
2015-03-03 18:30:50 +03:00
static struct thermal_zone_params pkg_temp_tz_params = {
2014-03-02 18:33:35 +04:00
. no_hwmon = true ,
} ;
2016-11-22 20:57:14 +03:00
/* Keep track of how many package pointers we allocated in init() */
static int max_packages __read_mostly ;
/* Array of package pointers */
static struct pkg_device * * packages ;
2016-11-22 20:57:10 +03:00
/* Serializes interrupt notification, work and hotplug */
static DEFINE_SPINLOCK ( pkg_temp_lock ) ;
/* Protects zone operation in the work function against hotplug removal */
static DEFINE_MUTEX ( thermal_zone_mutex ) ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:15 +03:00
/* The dynamically assigned cpu hotplug state for module_exit() */
static enum cpuhp_state pkg_thermal_hp_state __read_mostly ;
2013-05-18 03:42:02 +04:00
/* Debug counters to show using debugfs */
static struct dentry * debugfs ;
static unsigned int pkg_interrupt_cnt ;
static unsigned int pkg_work_cnt ;
static int pkg_temp_debugfs_init ( void )
{
struct dentry * d ;
debugfs = debugfs_create_dir ( " pkg_temp_thermal " , NULL ) ;
if ( ! debugfs )
return - ENOENT ;
d = debugfs_create_u32 ( " pkg_thres_interrupt " , S_IRUGO , debugfs ,
( u32 * ) & pkg_interrupt_cnt ) ;
if ( ! d )
goto err_out ;
d = debugfs_create_u32 ( " pkg_thres_work " , S_IRUGO , debugfs ,
( u32 * ) & pkg_work_cnt ) ;
if ( ! d )
goto err_out ;
return 0 ;
err_out :
debugfs_remove_recursive ( debugfs ) ;
return - ENOENT ;
}
2016-11-22 20:57:10 +03:00
/*
* Protection :
*
* - cpu hotplug : Read serialized by cpu hotplug lock
* Write must hold pkg_temp_lock
*
* - Other callsites : Must hold pkg_temp_lock
*/
2016-11-22 20:57:08 +03:00
static struct pkg_device * pkg_temp_thermal_get_dev ( unsigned int cpu )
2013-05-18 03:42:02 +04:00
{
2016-11-22 20:57:14 +03:00
int pkgid = topology_logical_package_id ( cpu ) ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:14 +03:00
if ( pkgid > = 0 & & pkgid < max_packages )
return packages [ pkgid ] ;
2013-05-18 03:42:02 +04:00
return NULL ;
}
/*
* tj - max is is interesting because threshold is set relative to this
* temperature .
*/
static int get_tj_max ( int cpu , u32 * tj_max )
{
2016-11-22 20:57:09 +03:00
u32 eax , edx , val ;
2013-05-18 03:42:02 +04:00
int err ;
err = rdmsr_safe_on_cpu ( cpu , MSR_IA32_TEMPERATURE_TARGET , & eax , & edx ) ;
if ( err )
2016-11-22 20:57:09 +03:00
return err ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:09 +03:00
val = ( eax > > 16 ) & 0xff ;
* tj_max = val * 1000 ;
return val ? 0 : - EINVAL ;
2013-05-18 03:42:02 +04:00
}
2015-07-24 09:12:54 +03:00
static int sys_get_curr_temp ( struct thermal_zone_device * tzd , int * temp )
2013-05-18 03:42:02 +04:00
{
2016-11-22 20:57:08 +03:00
struct pkg_device * pkgdev = tzd - > devdata ;
2013-05-18 03:42:02 +04:00
u32 eax , edx ;
2016-11-22 20:57:08 +03:00
rdmsr_on_cpu ( pkgdev - > cpu , MSR_IA32_PACKAGE_THERM_STATUS , & eax , & edx ) ;
2013-05-18 03:42:02 +04:00
if ( eax & 0x80000000 ) {
2016-11-22 20:57:08 +03:00
* temp = pkgdev - > tj_max - ( ( eax > > 16 ) & 0x7f ) * 1000 ;
2015-07-24 09:12:54 +03:00
pr_debug ( " sys_get_curr_temp %d \n " , * temp ) ;
2013-05-18 03:42:02 +04:00
return 0 ;
}
return - EINVAL ;
}
static int sys_get_trip_temp ( struct thermal_zone_device * tzd ,
2016-11-22 20:57:08 +03:00
int trip , int * temp )
2013-05-18 03:42:02 +04:00
{
2016-11-22 20:57:08 +03:00
struct pkg_device * pkgdev = tzd - > devdata ;
2013-05-18 03:42:02 +04:00
unsigned long thres_reg_value ;
2016-11-22 20:57:08 +03:00
u32 mask , shift , eax , edx ;
2013-05-18 03:42:02 +04:00
int ret ;
if ( trip > = MAX_NUMBER_OF_TRIPS )
return - EINVAL ;
if ( trip ) {
mask = THERM_MASK_THRESHOLD1 ;
shift = THERM_SHIFT_THRESHOLD1 ;
} else {
mask = THERM_MASK_THRESHOLD0 ;
shift = THERM_SHIFT_THRESHOLD0 ;
}
2016-11-22 20:57:08 +03:00
ret = rdmsr_on_cpu ( pkgdev - > cpu , MSR_IA32_PACKAGE_THERM_INTERRUPT ,
2016-11-22 20:57:07 +03:00
& eax , & edx ) ;
2013-05-18 03:42:02 +04:00
if ( ret < 0 )
2016-11-22 20:57:09 +03:00
return ret ;
2013-05-18 03:42:02 +04:00
thres_reg_value = ( eax & mask ) > > shift ;
if ( thres_reg_value )
2016-11-22 20:57:08 +03:00
* temp = pkgdev - > tj_max - thres_reg_value * 1000 ;
2013-05-18 03:42:02 +04:00
else
* temp = 0 ;
2015-07-24 09:12:54 +03:00
pr_debug ( " sys_get_trip_temp %d \n " , * temp ) ;
2013-05-18 03:42:02 +04:00
return 0 ;
}
2016-11-22 20:57:08 +03:00
static int
sys_set_trip_temp ( struct thermal_zone_device * tzd , int trip , int temp )
2013-05-18 03:42:02 +04:00
{
2016-11-22 20:57:08 +03:00
struct pkg_device * pkgdev = tzd - > devdata ;
u32 l , h , mask , shift , intr ;
2013-05-18 03:42:02 +04:00
int ret ;
2016-11-22 20:57:08 +03:00
if ( trip > = MAX_NUMBER_OF_TRIPS | | temp > = pkgdev - > tj_max )
2013-05-18 03:42:02 +04:00
return - EINVAL ;
2016-11-22 20:57:08 +03:00
ret = rdmsr_on_cpu ( pkgdev - > cpu , MSR_IA32_PACKAGE_THERM_INTERRUPT ,
2016-11-22 20:57:07 +03:00
& l , & h ) ;
2013-05-18 03:42:02 +04:00
if ( ret < 0 )
2016-11-22 20:57:09 +03:00
return ret ;
2013-05-18 03:42:02 +04:00
if ( trip ) {
mask = THERM_MASK_THRESHOLD1 ;
shift = THERM_SHIFT_THRESHOLD1 ;
intr = THERM_INT_THRESHOLD1_ENABLE ;
} else {
mask = THERM_MASK_THRESHOLD0 ;
shift = THERM_SHIFT_THRESHOLD0 ;
intr = THERM_INT_THRESHOLD0_ENABLE ;
}
l & = ~ mask ;
/*
* When users space sets a trip temperature = = 0 , which is indication
* that , it is no longer interested in receiving notifications .
*/
2016-11-22 20:57:08 +03:00
if ( ! temp ) {
2013-05-18 03:42:02 +04:00
l & = ~ intr ;
2016-11-22 20:57:08 +03:00
} else {
l | = ( pkgdev - > tj_max - temp ) / 1000 < < shift ;
2013-05-18 03:42:02 +04:00
l | = intr ;
}
2016-11-22 20:57:08 +03:00
return wrmsr_on_cpu ( pkgdev - > cpu , MSR_IA32_PACKAGE_THERM_INTERRUPT , l , h ) ;
2013-05-18 03:42:02 +04:00
}
2016-11-22 20:57:08 +03:00
static int sys_get_trip_type ( struct thermal_zone_device * thermal , int trip ,
enum thermal_trip_type * type )
2013-05-18 03:42:02 +04:00
{
* type = THERMAL_TRIP_PASSIVE ;
return 0 ;
}
/* Thermal zone callback registry */
static struct thermal_zone_device_ops tzone_ops = {
. get_temp = sys_get_curr_temp ,
. get_trip_temp = sys_get_trip_temp ,
. get_trip_type = sys_get_trip_type ,
. set_trip_temp = sys_set_trip_temp ,
} ;
2016-11-22 20:57:06 +03:00
static bool pkg_thermal_rate_control ( void )
2013-05-18 03:42:02 +04:00
{
return true ;
}
/* Enable threshold interrupt on local package/cpu */
static inline void enable_pkg_thres_interrupt ( void )
{
u8 thres_0 , thres_1 ;
2016-11-22 20:57:09 +03:00
u32 l , h ;
2013-05-18 03:42:02 +04:00
rdmsr ( MSR_IA32_PACKAGE_THERM_INTERRUPT , l , h ) ;
/* only enable/disable if it had valid threshold value */
thres_0 = ( l & THERM_MASK_THRESHOLD0 ) > > THERM_SHIFT_THRESHOLD0 ;
thres_1 = ( l & THERM_MASK_THRESHOLD1 ) > > THERM_SHIFT_THRESHOLD1 ;
if ( thres_0 )
l | = THERM_INT_THRESHOLD0_ENABLE ;
if ( thres_1 )
l | = THERM_INT_THRESHOLD1_ENABLE ;
wrmsr ( MSR_IA32_PACKAGE_THERM_INTERRUPT , l , h ) ;
}
/* Disable threshold interrupt on local package/cpu */
static inline void disable_pkg_thres_interrupt ( void )
{
u32 l , h ;
2016-11-22 20:57:09 +03:00
2013-05-18 03:42:02 +04:00
rdmsr ( MSR_IA32_PACKAGE_THERM_INTERRUPT , l , h ) ;
2016-11-22 20:57:09 +03:00
l & = ~ ( THERM_INT_THRESHOLD0_ENABLE | THERM_INT_THRESHOLD1_ENABLE ) ;
wrmsr ( MSR_IA32_PACKAGE_THERM_INTERRUPT , l , h ) ;
2013-05-18 03:42:02 +04:00
}
static void pkg_temp_thermal_threshold_work_fn ( struct work_struct * work )
{
2016-11-22 20:57:10 +03:00
struct thermal_zone_device * tzone = NULL ;
2016-11-22 20:57:12 +03:00
int cpu = smp_processor_id ( ) ;
2016-11-22 20:57:10 +03:00
struct pkg_device * pkgdev ;
2016-11-22 20:57:09 +03:00
u64 msr_val , wr_val ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:10 +03:00
mutex_lock ( & thermal_zone_mutex ) ;
spin_lock_irq ( & pkg_temp_lock ) ;
2013-05-18 03:42:02 +04:00
+ + pkg_work_cnt ;
2016-11-22 20:57:10 +03:00
pkgdev = pkg_temp_thermal_get_dev ( cpu ) ;
if ( ! pkgdev ) {
spin_unlock_irq ( & pkg_temp_lock ) ;
mutex_unlock ( & thermal_zone_mutex ) ;
2013-05-18 03:42:02 +04:00
return ;
}
2016-11-22 20:57:12 +03:00
pkgdev - > work_scheduled = false ;
2013-05-18 03:42:02 +04:00
rdmsrl ( MSR_IA32_PACKAGE_THERM_STATUS , msr_val ) ;
2016-11-22 20:57:09 +03:00
wr_val = msr_val & ~ ( THERM_LOG_THRESHOLD0 | THERM_LOG_THRESHOLD1 ) ;
if ( wr_val ! = msr_val ) {
wrmsrl ( MSR_IA32_PACKAGE_THERM_STATUS , wr_val ) ;
2016-11-22 20:57:10 +03:00
tzone = pkgdev - > tzone ;
2013-05-18 03:42:02 +04:00
}
2016-11-22 20:57:04 +03:00
enable_pkg_thres_interrupt ( ) ;
2016-11-22 20:57:10 +03:00
spin_unlock_irq ( & pkg_temp_lock ) ;
2016-11-22 20:57:04 +03:00
2016-11-22 20:57:10 +03:00
/*
* If tzone is not NULL , then thermal_zone_mutex will prevent the
* concurrent removal in the cpu offline callback .
*/
if ( tzone )
thermal_zone_device_update ( tzone , THERMAL_EVENT_UNSPECIFIED ) ;
mutex_unlock ( & thermal_zone_mutex ) ;
2013-05-18 03:42:02 +04:00
}
2016-11-22 20:57:13 +03:00
static void pkg_thermal_schedule_work ( int cpu , struct delayed_work * work )
{
unsigned long ms = msecs_to_jiffies ( notify_delay_ms ) ;
schedule_delayed_work_on ( cpu , work , ms ) ;
}
2016-11-22 20:57:09 +03:00
static int pkg_thermal_notify ( u64 msr_val )
2013-05-18 03:42:02 +04:00
{
int cpu = smp_processor_id ( ) ;
2016-11-22 20:57:10 +03:00
struct pkg_device * pkgdev ;
2016-11-22 20:57:09 +03:00
unsigned long flags ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:10 +03:00
spin_lock_irqsave ( & pkg_temp_lock , flags ) ;
2013-05-18 03:42:02 +04:00
+ + pkg_interrupt_cnt ;
disable_pkg_thres_interrupt ( ) ;
2016-11-22 20:57:10 +03:00
/* Work is per package, so scheduling it once is enough. */
pkgdev = pkg_temp_thermal_get_dev ( cpu ) ;
2016-11-22 20:57:12 +03:00
if ( pkgdev & & ! pkgdev - > work_scheduled ) {
pkgdev - > work_scheduled = true ;
2016-11-22 20:57:13 +03:00
pkg_thermal_schedule_work ( pkgdev - > cpu , & pkgdev - > work ) ;
2016-11-22 20:57:10 +03:00
}
spin_unlock_irqrestore ( & pkg_temp_lock , flags ) ;
2013-05-18 03:42:02 +04:00
return 0 ;
}
static int pkg_temp_thermal_device_add ( unsigned int cpu )
{
2016-11-22 20:57:14 +03:00
int pkgid = topology_logical_package_id ( cpu ) ;
2016-11-22 20:57:08 +03:00
u32 tj_max , eax , ebx , ecx , edx ;
struct pkg_device * pkgdev ;
int thres_count , err ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:14 +03:00
if ( pkgid > = max_packages )
return - ENOMEM ;
2013-05-18 03:42:02 +04:00
cpuid ( 6 , & eax , & ebx , & ecx , & edx ) ;
thres_count = ebx & 0x07 ;
if ( ! thres_count )
return - ENODEV ;
thres_count = clamp_val ( thres_count , 0 , MAX_NUMBER_OF_TRIPS ) ;
err = get_tj_max ( cpu , & tj_max ) ;
if ( err )
2016-11-22 20:57:10 +03:00
return err ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:08 +03:00
pkgdev = kzalloc ( sizeof ( * pkgdev ) , GFP_KERNEL ) ;
2016-11-22 20:57:10 +03:00
if ( ! pkgdev )
return - ENOMEM ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:13 +03:00
INIT_DELAYED_WORK ( & pkgdev - > work , pkg_temp_thermal_threshold_work_fn ) ;
2016-11-22 20:57:08 +03:00
pkgdev - > cpu = cpu ;
pkgdev - > tj_max = tj_max ;
pkgdev - > tzone = thermal_zone_device_register ( " x86_pkg_temp " ,
2013-05-18 03:42:02 +04:00
thres_count ,
2016-11-22 20:57:08 +03:00
( thres_count = = MAX_NUMBER_OF_TRIPS ) ? 0x03 : 0x01 ,
pkgdev , & tzone_ops , & pkg_temp_tz_params , 0 , 0 ) ;
if ( IS_ERR ( pkgdev - > tzone ) ) {
err = PTR_ERR ( pkgdev - > tzone ) ;
2016-11-22 20:57:10 +03:00
kfree ( pkgdev ) ;
return err ;
2013-05-18 03:42:02 +04:00
}
/* Store MSR value for package thermal interrupt, to restore at exit */
2016-11-22 20:57:15 +03:00
rdmsr ( MSR_IA32_PACKAGE_THERM_INTERRUPT , pkgdev - > msr_pkg_therm_low ,
pkgdev - > msr_pkg_therm_high ) ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:10 +03:00
cpumask_set_cpu ( cpu , & pkgdev - > cpumask ) ;
spin_lock_irq ( & pkg_temp_lock ) ;
2016-11-22 20:57:14 +03:00
packages [ pkgid ] = pkgdev ;
2016-11-22 20:57:10 +03:00
spin_unlock_irq ( & pkg_temp_lock ) ;
2013-05-18 03:42:02 +04:00
return 0 ;
}
2016-11-22 20:57:15 +03:00
static int pkg_thermal_cpu_offline ( unsigned int cpu )
2013-05-18 03:42:02 +04:00
{
2016-11-22 20:57:08 +03:00
struct pkg_device * pkgdev = pkg_temp_thermal_get_dev ( cpu ) ;
2016-11-22 20:57:13 +03:00
bool lastcpu , was_target ;
2016-11-22 20:57:07 +03:00
int target ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:08 +03:00
if ( ! pkgdev )
2016-11-22 20:57:15 +03:00
return 0 ;
2016-11-22 20:57:10 +03:00
target = cpumask_any_but ( & pkgdev - > cpumask , cpu ) ;
cpumask_clear_cpu ( cpu , & pkgdev - > cpumask ) ;
lastcpu = target > = nr_cpu_ids ;
/*
* Remove the sysfs files , if this is the last cpu in the package
* before doing further cleanups .
*/
if ( lastcpu ) {
struct thermal_zone_device * tzone = pkgdev - > tzone ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:10 +03:00
/*
* We must protect against a work function calling
* thermal_zone_update , after / while unregister . We null out
* the pointer under the zone mutex , so the worker function
* won ' t try to call .
*/
mutex_lock ( & thermal_zone_mutex ) ;
pkgdev - > tzone = NULL ;
mutex_unlock ( & thermal_zone_mutex ) ;
2016-11-22 20:57:05 +03:00
2016-11-22 20:57:10 +03:00
thermal_zone_device_unregister ( tzone ) ;
}
2016-11-22 20:57:13 +03:00
/* Protect against work and interrupts */
spin_lock_irq ( & pkg_temp_lock ) ;
2016-11-22 20:57:10 +03:00
/*
2016-11-22 20:57:13 +03:00
* Check whether this cpu was the current target and store the new
* one . When we drop the lock , then the interrupt notify function
* will see the new target .
*/
was_target = pkgdev - > cpu = = cpu ;
pkgdev - > cpu = target ;
/*
* If this is the last CPU in the package remove the package
2016-11-22 20:57:14 +03:00
* reference from the array and restore the interrupt MSR . When we
2016-11-22 20:57:13 +03:00
* drop the lock neither the interrupt notify function nor the
* worker will see the package anymore .
2016-11-22 20:57:10 +03:00
*/
if ( lastcpu ) {
2016-11-22 20:57:14 +03:00
packages [ topology_logical_package_id ( cpu ) ] = NULL ;
2016-11-22 20:57:15 +03:00
/* After this point nothing touches the MSR anymore. */
wrmsr ( MSR_IA32_PACKAGE_THERM_INTERRUPT ,
pkgdev - > msr_pkg_therm_low , pkgdev - > msr_pkg_therm_high ) ;
2016-11-22 20:57:05 +03:00
}
2016-11-22 20:57:07 +03:00
2016-11-22 20:57:10 +03:00
/*
2016-11-22 20:57:13 +03:00
* Check whether there is work scheduled and whether the work is
* targeted at the outgoing CPU .
2016-11-22 20:57:10 +03:00
*/
2016-11-22 20:57:13 +03:00
if ( pkgdev - > work_scheduled & & was_target ) {
/*
* To cancel the work we need to drop the lock , otherwise
* we might deadlock if the work needs to be flushed .
*/
spin_unlock_irq ( & pkg_temp_lock ) ;
cancel_delayed_work_sync ( & pkgdev - > work ) ;
spin_lock_irq ( & pkg_temp_lock ) ;
/*
* If this is not the last cpu in the package and the work
* did not run after we dropped the lock above , then we
* need to reschedule the work , otherwise the interrupt
* stays disabled forever .
*/
if ( ! lastcpu & & pkgdev - > work_scheduled )
pkg_thermal_schedule_work ( target , & pkgdev - > work ) ;
}
spin_unlock_irq ( & pkg_temp_lock ) ;
/* Final cleanup if this is the last cpu */
if ( lastcpu )
kfree ( pkgdev ) ;
2016-11-22 20:57:15 +03:00
return 0 ;
2013-05-18 03:42:02 +04:00
}
2016-11-22 20:57:15 +03:00
static int pkg_thermal_cpu_online ( unsigned int cpu )
2013-05-18 03:42:02 +04:00
{
2016-11-22 20:57:08 +03:00
struct pkg_device * pkgdev = pkg_temp_thermal_get_dev ( cpu ) ;
2013-05-18 03:42:02 +04:00
struct cpuinfo_x86 * c = & cpu_data ( cpu ) ;
2016-11-22 20:57:09 +03:00
/* Paranoia check */
if ( ! cpu_has ( c , X86_FEATURE_DTHERM ) | | ! cpu_has ( c , X86_FEATURE_PTS ) )
return - ENODEV ;
2016-11-22 20:57:08 +03:00
2016-11-22 20:57:09 +03:00
/* If the package exists, nothing to do */
2016-11-22 20:57:10 +03:00
if ( pkgdev ) {
cpumask_set_cpu ( cpu , & pkgdev - > cpumask ) ;
2016-11-22 20:57:09 +03:00
return 0 ;
2016-11-22 20:57:10 +03:00
}
2016-11-22 20:57:09 +03:00
return pkg_temp_thermal_device_add ( cpu ) ;
2013-05-18 03:42:02 +04:00
}
static const struct x86_cpu_id __initconst pkg_temp_thermal_ids [ ] = {
2013-07-11 20:50:30 +04:00
{ X86_VENDOR_INTEL , X86_FAMILY_ANY , X86_MODEL_ANY , X86_FEATURE_PTS } ,
2013-05-18 03:42:02 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , pkg_temp_thermal_ids ) ;
static int __init pkg_temp_thermal_init ( void )
{
2016-11-22 20:57:15 +03:00
int ret ;
2013-05-18 03:42:02 +04:00
if ( ! x86_match_cpu ( pkg_temp_thermal_ids ) )
return - ENODEV ;
2016-11-22 20:57:14 +03:00
max_packages = topology_max_packages ( ) ;
packages = kzalloc ( max_packages * sizeof ( struct pkg_device * ) , GFP_KERNEL ) ;
if ( ! packages )
return - ENOMEM ;
2016-11-22 20:57:15 +03:00
ret = cpuhp_setup_state ( CPUHP_AP_ONLINE_DYN , " thermal/x86_pkg:online " ,
pkg_thermal_cpu_online , pkg_thermal_cpu_offline ) ;
if ( ret < 0 )
goto err ;
/* Store the state for module exit */
pkg_thermal_hp_state = ret ;
2013-05-18 03:42:02 +04:00
2016-11-22 20:57:06 +03:00
platform_thermal_package_notify = pkg_thermal_notify ;
platform_thermal_package_rate_control = pkg_thermal_rate_control ;
2016-11-22 20:57:09 +03:00
/* Don't care if it fails */
pkg_temp_debugfs_init ( ) ;
2013-05-18 03:42:02 +04:00
return 0 ;
2016-11-22 20:57:15 +03:00
err :
2016-11-22 20:57:14 +03:00
kfree ( packages ) ;
2016-11-22 20:57:15 +03:00
return ret ;
2013-05-18 03:42:02 +04:00
}
2016-11-22 20:57:09 +03:00
module_init ( pkg_temp_thermal_init )
2013-05-18 03:42:02 +04:00
static void __exit pkg_temp_thermal_exit ( void )
{
2016-11-22 20:57:06 +03:00
platform_thermal_package_notify = NULL ;
platform_thermal_package_rate_control = NULL ;
2016-11-22 20:57:15 +03:00
cpuhp_remove_state ( pkg_thermal_hp_state ) ;
2013-05-18 03:42:02 +04:00
debugfs_remove_recursive ( debugfs ) ;
2016-11-22 20:57:14 +03:00
kfree ( packages ) ;
2013-05-18 03:42:02 +04:00
}
module_exit ( pkg_temp_thermal_exit )
MODULE_DESCRIPTION ( " X86 PKG TEMP Thermal Driver " ) ;
MODULE_AUTHOR ( " Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;