2014-04-08 00:57:15 +04:00
/*
* intel_soc_dts_thermal . c
* Copyright ( c ) 2014 , 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 .
*
*/
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
# include <linux/module.h>
# include <linux/slab.h>
# include <linux/interrupt.h>
# include <linux/thermal.h>
# include <asm/cpu_device_id.h>
# include <asm/iosf_mbi.h>
# define SOC_DTS_OFFSET_ENABLE 0xB0
# define SOC_DTS_OFFSET_TEMP 0xB1
# define SOC_DTS_OFFSET_PTPS 0xB2
# define SOC_DTS_OFFSET_PTTS 0xB3
# define SOC_DTS_OFFSET_PTTSS 0xB4
# define SOC_DTS_OFFSET_PTMC 0x80
# define SOC_DTS_TE_AUX0 0xB5
# define SOC_DTS_TE_AUX1 0xB6
# define SOC_DTS_AUX0_ENABLE_BIT BIT(0)
# define SOC_DTS_AUX1_ENABLE_BIT BIT(1)
# define SOC_DTS_CPU_MODULE0_ENABLE_BIT BIT(16)
# define SOC_DTS_CPU_MODULE1_ENABLE_BIT BIT(17)
# define SOC_DTS_TE_SCI_ENABLE BIT(9)
# define SOC_DTS_TE_SMI_ENABLE BIT(10)
# define SOC_DTS_TE_MSI_ENABLE BIT(11)
# define SOC_DTS_TE_APICA_ENABLE BIT(14)
# define SOC_DTS_PTMC_APIC_DEASSERT_BIT BIT(4)
/* DTS encoding for TJ MAX temperature */
# define SOC_DTS_TJMAX_ENCODING 0x7F
/* IRQ 86 is a fixed APIC interrupt for BYT DTS Aux threshold notifications */
# define BYT_SOC_DTS_APIC_IRQ 86
/* Only 2 out of 4 is allowed for OSPM */
# define SOC_MAX_DTS_TRIPS 2
/* Mask for two trips in status bits */
# define SOC_DTS_TRIP_MASK 0x03
/* DTS0 and DTS 1 */
# define SOC_MAX_DTS_SENSORS 2
# define CRITICAL_OFFSET_FROM_TJ_MAX 5000
struct soc_sensor_entry {
int id ;
u32 tj_max ;
u32 temp_mask ;
u32 temp_shift ;
u32 store_status ;
struct thermal_zone_device * tzone ;
} ;
static struct soc_sensor_entry * soc_dts [ SOC_MAX_DTS_SENSORS ] ;
static int crit_offset = CRITICAL_OFFSET_FROM_TJ_MAX ;
module_param ( crit_offset , int , 0644 ) ;
MODULE_PARM_DESC ( crit_offset ,
" Critical Temperature offset from tj max in millidegree Celsius. " ) ;
static DEFINE_MUTEX ( aux_update_mutex ) ;
static spinlock_t intr_notify_lock ;
static int soc_dts_thres_irq ;
static int get_tj_max ( u32 * tj_max )
{
u32 eax , edx ;
u32 val ;
int err ;
err = rdmsr_safe ( MSR_IA32_TEMPERATURE_TARGET , & eax , & edx ) ;
if ( err )
goto err_ret ;
else {
val = ( eax > > 16 ) & 0xff ;
if ( val )
* tj_max = val * 1000 ;
else {
err = - EINVAL ;
goto err_ret ;
}
}
return 0 ;
err_ret :
* tj_max = 0 ;
return err ;
}
static int sys_get_trip_temp ( struct thermal_zone_device * tzd ,
int trip , unsigned long * temp )
{
int status ;
u32 out ;
struct soc_sensor_entry * aux_entry ;
aux_entry = tzd - > devdata ;
if ( ! trip ) {
/* Just return the critical temp */
* temp = aux_entry - > tj_max - crit_offset ;
return 0 ;
}
mutex_lock ( & aux_update_mutex ) ;
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_PTPS , & out ) ;
mutex_unlock ( & aux_update_mutex ) ;
if ( status )
return status ;
out = ( out > > ( trip * 8 ) ) & SOC_DTS_TJMAX_ENCODING ;
if ( ! out )
* temp = 0 ;
else
* temp = aux_entry - > tj_max - out * 1000 ;
return 0 ;
}
static int update_trip_temp ( struct soc_sensor_entry * aux_entry ,
int thres_index , unsigned long temp )
{
int status ;
u32 temp_out ;
u32 out ;
u32 store_ptps ;
u32 store_ptmc ;
u32 store_te_out ;
u32 te_out ;
u32 int_enable_bit = SOC_DTS_TE_APICA_ENABLE |
SOC_DTS_TE_MSI_ENABLE ;
temp_out = ( aux_entry - > tj_max - temp ) / 1000 ;
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_PTPS , & store_ptps ) ;
if ( status )
return status ;
out = ( store_ptps & ~ ( 0xFF < < ( thres_index * 8 ) ) ) ;
out | = ( temp_out & 0xFF ) < < ( thres_index * 8 ) ;
status = iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTPS , out ) ;
if ( status )
return status ;
pr_debug ( " update_trip_temp PTPS = %x \n " , out ) ;
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_PTMC , & out ) ;
if ( status )
goto err_restore_ptps ;
store_ptmc = out ;
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_TE_AUX0 + thres_index ,
& te_out ) ;
if ( status )
goto err_restore_ptmc ;
store_te_out = te_out ;
/* Enable for CPU module 0 and module 1 */
out | = ( SOC_DTS_CPU_MODULE0_ENABLE_BIT |
SOC_DTS_CPU_MODULE1_ENABLE_BIT ) ;
if ( temp ) {
if ( thres_index )
out | = SOC_DTS_AUX1_ENABLE_BIT ;
else
out | = SOC_DTS_AUX0_ENABLE_BIT ;
te_out | = int_enable_bit ;
} else {
if ( thres_index )
out & = ~ SOC_DTS_AUX1_ENABLE_BIT ;
else
out & = ~ SOC_DTS_AUX0_ENABLE_BIT ;
te_out & = ~ int_enable_bit ;
}
status = iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTMC , out ) ;
if ( status )
goto err_restore_te_out ;
status = iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_TE_AUX0 + thres_index ,
te_out ) ;
if ( status )
goto err_restore_te_out ;
return 0 ;
err_restore_te_out :
iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTMC , store_te_out ) ;
err_restore_ptmc :
iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTMC , store_ptmc ) ;
err_restore_ptps :
iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTPS , store_ptps ) ;
/* Nothing we can do if restore fails */
return status ;
}
static int sys_set_trip_temp ( struct thermal_zone_device * tzd , int trip ,
unsigned long temp )
{
struct soc_sensor_entry * aux_entry = tzd - > devdata ;
int status ;
if ( temp > ( aux_entry - > tj_max - crit_offset ) )
return - EINVAL ;
mutex_lock ( & aux_update_mutex ) ;
status = update_trip_temp ( tzd - > devdata , trip , temp ) ;
mutex_unlock ( & aux_update_mutex ) ;
return status ;
}
static int sys_get_trip_type ( struct thermal_zone_device * thermal ,
int trip , enum thermal_trip_type * type )
{
if ( trip )
* type = THERMAL_TRIP_PASSIVE ;
else
* type = THERMAL_TRIP_CRITICAL ;
return 0 ;
}
static int sys_get_curr_temp ( struct thermal_zone_device * tzd ,
unsigned long * temp )
{
int status ;
u32 out ;
struct soc_sensor_entry * aux_entry ;
aux_entry = tzd - > devdata ;
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_TEMP , & out ) ;
if ( status )
return status ;
out = ( out & aux_entry - > temp_mask ) > > aux_entry - > temp_shift ;
out - = SOC_DTS_TJMAX_ENCODING ;
* temp = aux_entry - > tj_max - out * 1000 ;
return 0 ;
}
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 ,
} ;
static void free_soc_dts ( struct soc_sensor_entry * aux_entry )
{
if ( aux_entry ) {
iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_ENABLE , aux_entry - > store_status ) ;
thermal_zone_device_unregister ( aux_entry - > tzone ) ;
kfree ( aux_entry ) ;
}
}
static int soc_dts_enable ( int id )
{
u32 out ;
int ret ;
ret = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_ENABLE , & out ) ;
if ( ret )
return ret ;
if ( ! ( out & BIT ( id ) ) ) {
out | = BIT ( id ) ;
ret = iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_ENABLE , out ) ;
if ( ret )
return ret ;
}
return ret ;
}
2015-01-28 22:48:02 +03:00
static struct soc_sensor_entry * alloc_soc_dts ( int id , u32 tj_max ,
bool notification_support )
2014-04-08 00:57:15 +04:00
{
struct soc_sensor_entry * aux_entry ;
char name [ 10 ] ;
2015-01-28 22:48:02 +03:00
int trip_count = 0 ;
int trip_mask = 0 ;
2014-04-08 00:57:15 +04:00
int err ;
aux_entry = kzalloc ( sizeof ( * aux_entry ) , GFP_KERNEL ) ;
if ( ! aux_entry ) {
err = - ENOMEM ;
return ERR_PTR ( - ENOMEM ) ;
}
/* Store status to restor on exit */
err = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_ENABLE ,
& aux_entry - > store_status ) ;
if ( err )
goto err_ret ;
aux_entry - > id = id ;
aux_entry - > tj_max = tj_max ;
aux_entry - > temp_mask = 0x00FF < < ( id * 8 ) ;
aux_entry - > temp_shift = id * 8 ;
2015-01-28 22:48:02 +03:00
if ( notification_support ) {
trip_count = SOC_MAX_DTS_TRIPS ;
trip_mask = 0x02 ;
}
2014-04-08 00:57:15 +04:00
snprintf ( name , sizeof ( name ) , " soc_dts%d " , id ) ;
aux_entry - > tzone = thermal_zone_device_register ( name ,
2015-01-28 22:48:02 +03:00
trip_count ,
trip_mask ,
aux_entry , & tzone_ops ,
NULL , 0 , 0 ) ;
2014-04-08 00:57:15 +04:00
if ( IS_ERR ( aux_entry - > tzone ) ) {
err = PTR_ERR ( aux_entry - > tzone ) ;
goto err_ret ;
}
err = soc_dts_enable ( id ) ;
if ( err )
goto err_aux_status ;
return aux_entry ;
err_aux_status :
thermal_zone_device_unregister ( aux_entry - > tzone ) ;
err_ret :
kfree ( aux_entry ) ;
return ERR_PTR ( err ) ;
}
static void proc_thermal_interrupt ( void )
{
u32 sticky_out ;
int status ;
u32 ptmc_out ;
2014-11-28 05:11:41 +03:00
unsigned long flags ;
spin_lock_irqsave ( & intr_notify_lock , flags ) ;
2014-04-08 00:57:15 +04:00
/* Clear APIC interrupt */
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_PTMC , & ptmc_out ) ;
ptmc_out | = SOC_DTS_PTMC_APIC_DEASSERT_BIT ;
status = iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTMC , ptmc_out ) ;
/* Read status here */
status = iosf_mbi_read ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_READ ,
SOC_DTS_OFFSET_PTTSS , & sticky_out ) ;
pr_debug ( " status %d PTTSS %x \n " , status , sticky_out ) ;
if ( sticky_out & SOC_DTS_TRIP_MASK ) {
int i ;
/* reset sticky bit */
status = iosf_mbi_write ( BT_MBI_UNIT_PMC , BT_MBI_BUNIT_WRITE ,
SOC_DTS_OFFSET_PTTSS , sticky_out ) ;
2014-11-28 05:11:41 +03:00
spin_unlock_irqrestore ( & intr_notify_lock , flags ) ;
2014-04-08 00:57:15 +04:00
for ( i = 0 ; i < SOC_MAX_DTS_SENSORS ; + + i ) {
pr_debug ( " TZD update for zone %d \n " , i ) ;
thermal_zone_device_update ( soc_dts [ i ] - > tzone ) ;
}
2014-11-28 05:11:41 +03:00
} else
spin_unlock_irqrestore ( & intr_notify_lock , flags ) ;
2014-04-08 00:57:15 +04:00
}
static irqreturn_t soc_irq_thread_fn ( int irq , void * dev_data )
{
proc_thermal_interrupt ( ) ;
pr_debug ( " proc_thermal_interrupt \n " ) ;
return IRQ_HANDLED ;
}
static const struct x86_cpu_id soc_thermal_ids [ ] = {
{ X86_VENDOR_INTEL , X86_FAMILY_ANY , 0x37 , 0 , BYT_SOC_DTS_APIC_IRQ } ,
2015-01-28 22:48:02 +03:00
{ X86_VENDOR_INTEL , X86_FAMILY_ANY , 0x4c , 0 , 0 } ,
2014-04-08 00:57:15 +04:00
{ }
} ;
MODULE_DEVICE_TABLE ( x86cpu , soc_thermal_ids ) ;
static int __init intel_soc_thermal_init ( void )
{
u32 tj_max ;
int err = 0 ;
int i ;
const struct x86_cpu_id * match_cpu ;
match_cpu = x86_match_cpu ( soc_thermal_ids ) ;
if ( ! match_cpu )
return - ENODEV ;
if ( get_tj_max ( & tj_max ) )
return - EINVAL ;
2015-01-28 22:48:02 +03:00
soc_dts_thres_irq = ( int ) match_cpu - > driver_data ;
2014-04-08 00:57:15 +04:00
for ( i = 0 ; i < SOC_MAX_DTS_SENSORS ; + + i ) {
2015-01-28 22:48:02 +03:00
soc_dts [ i ] = alloc_soc_dts ( i , tj_max ,
soc_dts_thres_irq ? true : false ) ;
2014-04-08 00:57:15 +04:00
if ( IS_ERR ( soc_dts [ i ] ) ) {
err = PTR_ERR ( soc_dts [ i ] ) ;
goto err_free ;
}
}
spin_lock_init ( & intr_notify_lock ) ;
2015-01-28 22:48:02 +03:00
if ( soc_dts_thres_irq ) {
err = request_threaded_irq ( soc_dts_thres_irq , NULL ,
soc_irq_thread_fn ,
IRQF_TRIGGER_RISING | IRQF_ONESHOT ,
" soc_dts " , soc_dts ) ;
if ( err ) {
pr_err ( " request_threaded_irq ret %d \n " , err ) ;
goto err_free ;
}
2014-04-08 00:57:15 +04:00
}
for ( i = 0 ; i < SOC_MAX_DTS_SENSORS ; + + i ) {
err = update_trip_temp ( soc_dts [ i ] , 0 , tj_max - crit_offset ) ;
if ( err )
goto err_trip_temp ;
}
return 0 ;
err_trip_temp :
i = SOC_MAX_DTS_SENSORS ;
2015-01-28 22:48:02 +03:00
if ( soc_dts_thres_irq )
free_irq ( soc_dts_thres_irq , soc_dts ) ;
2014-04-08 00:57:15 +04:00
err_free :
while ( - - i > = 0 )
free_soc_dts ( soc_dts [ i ] ) ;
return err ;
}
static void __exit intel_soc_thermal_exit ( void )
{
int i ;
for ( i = 0 ; i < SOC_MAX_DTS_SENSORS ; + + i )
update_trip_temp ( soc_dts [ i ] , 0 , 0 ) ;
2015-01-28 22:48:02 +03:00
if ( soc_dts_thres_irq )
free_irq ( soc_dts_thres_irq , soc_dts ) ;
2014-04-08 00:57:15 +04:00
for ( i = 0 ; i < SOC_MAX_DTS_SENSORS ; + + i )
free_soc_dts ( soc_dts [ i ] ) ;
}
module_init ( intel_soc_thermal_init )
module_exit ( intel_soc_thermal_exit )
MODULE_DESCRIPTION ( " Intel SoC DTS Thermal Driver " ) ;
MODULE_AUTHOR ( " Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;