2014-12-09 16:47:17 -08:00
/*
* processor_thermal_device . 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 .
*
*/
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/init.h>
# include <linux/pci.h>
2015-03-02 13:13:00 -08:00
# include <linux/interrupt.h>
2014-12-09 16:47:17 -08:00
# include <linux/platform_device.h>
# include <linux/acpi.h>
2015-01-16 15:59:06 -08:00
# include <linux/thermal.h>
# include "int340x_thermal_zone.h"
2015-03-02 13:13:00 -08:00
# include "../intel_soc_dts_iosf.h"
2014-12-09 16:47:17 -08:00
/* Broadwell-U/HSB thermal reporting device */
# define PCI_DEVICE_ID_PROC_BDW_THERMAL 0x1603
# define PCI_DEVICE_ID_PROC_HSB_THERMAL 0x0A03
2015-04-20 11:25:37 -07:00
/* Skylake thermal reporting device */
# define PCI_DEVICE_ID_PROC_SKL_THERMAL 0x1903
2014-12-09 16:47:17 -08:00
/* Braswell thermal reporting device */
# define PCI_DEVICE_ID_PROC_BSW_THERMAL 0x22DC
2015-10-14 08:09:14 -07:00
/* Broxton thermal reporting device */
# define PCI_DEVICE_ID_PROC_BXT0_THERMAL 0x0A8C
# define PCI_DEVICE_ID_PROC_BXT1_THERMAL 0x1A8C
# define PCI_DEVICE_ID_PROC_BXTX_THERMAL 0x4A8C
# define PCI_DEVICE_ID_PROC_BXTP_THERMAL 0x5A8C
2014-12-09 16:47:17 -08:00
struct power_config {
u32 index ;
u32 min_uw ;
u32 max_uw ;
u32 tmin_us ;
u32 tmax_us ;
u32 step_uw ;
} ;
struct proc_thermal_device {
struct device * dev ;
struct acpi_device * adev ;
struct power_config power_limits [ 2 ] ;
2015-01-16 15:59:06 -08:00
struct int34x_thermal_zone * int340x_zone ;
2015-03-02 13:13:00 -08:00
struct intel_soc_dts_sensors * soc_dts ;
2014-12-09 16:47:17 -08:00
} ;
enum proc_thermal_emum_mode_type {
PROC_THERMAL_NONE ,
PROC_THERMAL_PCI ,
PROC_THERMAL_PLATFORM_DEV
} ;
/*
* We can have only one type of enumeration , PCI or Platform ,
* not both . So we don ' t need instance specific data .
*/
static enum proc_thermal_emum_mode_type proc_thermal_emum_mode =
PROC_THERMAL_NONE ;
# define POWER_LIMIT_SHOW(index, suffix) \
static ssize_t power_limit_ # # index # # _ # # suffix # # _show ( struct device * dev , \
struct device_attribute * attr , \
char * buf ) \
{ \
struct pci_dev * pci_dev ; \
struct platform_device * pdev ; \
struct proc_thermal_device * proc_dev ; \
\
if ( proc_thermal_emum_mode = = PROC_THERMAL_PLATFORM_DEV ) { \
pdev = to_platform_device ( dev ) ; \
proc_dev = platform_get_drvdata ( pdev ) ; \
} else { \
pci_dev = to_pci_dev ( dev ) ; \
proc_dev = pci_get_drvdata ( pci_dev ) ; \
} \
return sprintf ( buf , " %lu \n " , \
( unsigned long ) proc_dev - > power_limits [ index ] . suffix * 1000 ) ; \
}
POWER_LIMIT_SHOW ( 0 , min_uw )
POWER_LIMIT_SHOW ( 0 , max_uw )
POWER_LIMIT_SHOW ( 0 , step_uw )
POWER_LIMIT_SHOW ( 0 , tmin_us )
POWER_LIMIT_SHOW ( 0 , tmax_us )
POWER_LIMIT_SHOW ( 1 , min_uw )
POWER_LIMIT_SHOW ( 1 , max_uw )
POWER_LIMIT_SHOW ( 1 , step_uw )
POWER_LIMIT_SHOW ( 1 , tmin_us )
POWER_LIMIT_SHOW ( 1 , tmax_us )
static DEVICE_ATTR_RO ( power_limit_0_min_uw ) ;
static DEVICE_ATTR_RO ( power_limit_0_max_uw ) ;
static DEVICE_ATTR_RO ( power_limit_0_step_uw ) ;
static DEVICE_ATTR_RO ( power_limit_0_tmin_us ) ;
static DEVICE_ATTR_RO ( power_limit_0_tmax_us ) ;
static DEVICE_ATTR_RO ( power_limit_1_min_uw ) ;
static DEVICE_ATTR_RO ( power_limit_1_max_uw ) ;
static DEVICE_ATTR_RO ( power_limit_1_step_uw ) ;
static DEVICE_ATTR_RO ( power_limit_1_tmin_us ) ;
static DEVICE_ATTR_RO ( power_limit_1_tmax_us ) ;
static struct attribute * power_limit_attrs [ ] = {
& dev_attr_power_limit_0_min_uw . attr ,
& dev_attr_power_limit_1_min_uw . attr ,
& dev_attr_power_limit_0_max_uw . attr ,
& dev_attr_power_limit_1_max_uw . attr ,
& dev_attr_power_limit_0_step_uw . attr ,
& dev_attr_power_limit_1_step_uw . attr ,
& dev_attr_power_limit_0_tmin_us . attr ,
& dev_attr_power_limit_1_tmin_us . attr ,
& dev_attr_power_limit_0_tmax_us . attr ,
& dev_attr_power_limit_1_tmax_us . attr ,
NULL
} ;
static struct attribute_group power_limit_attribute_group = {
. attrs = power_limit_attrs ,
. name = " power_limits "
} ;
2015-01-16 15:59:06 -08:00
static int stored_tjmax ; /* since it is fixed, we can have local storage */
static int get_tjmax ( void )
{
u32 eax , edx ;
u32 val ;
int err ;
err = rdmsr_safe ( MSR_IA32_TEMPERATURE_TARGET , & eax , & edx ) ;
if ( err )
return err ;
val = ( eax > > 16 ) & 0xff ;
if ( val )
return val ;
return - EINVAL ;
}
2015-07-24 08:12:54 +02:00
static int read_temp_msr ( int * temp )
2015-01-16 15:59:06 -08:00
{
int cpu ;
u32 eax , edx ;
int err ;
unsigned long curr_temp_off = 0 ;
* temp = 0 ;
for_each_online_cpu ( cpu ) {
err = rdmsr_safe_on_cpu ( cpu , MSR_IA32_THERM_STATUS , & eax ,
& edx ) ;
if ( err )
goto err_ret ;
else {
if ( eax & 0x80000000 ) {
curr_temp_off = ( eax > > 16 ) & 0x7f ;
if ( ! * temp | | curr_temp_off < * temp )
* temp = curr_temp_off ;
} else {
err = - EINVAL ;
goto err_ret ;
}
}
}
return 0 ;
err_ret :
return err ;
}
static int proc_thermal_get_zone_temp ( struct thermal_zone_device * zone ,
2015-07-24 08:12:54 +02:00
int * temp )
2015-01-16 15:59:06 -08:00
{
int ret ;
ret = read_temp_msr ( temp ) ;
if ( ! ret )
* temp = ( stored_tjmax - * temp ) * 1000 ;
return ret ;
}
static struct thermal_zone_device_ops proc_thermal_local_ops = {
. get_temp = proc_thermal_get_zone_temp ,
} ;
2016-04-05 14:52:57 -07:00
static int proc_thermal_read_ppcc ( struct proc_thermal_device * proc_priv )
2014-12-09 16:47:17 -08:00
{
2016-04-05 14:52:57 -07:00
int i ;
2014-12-09 16:47:17 -08:00
acpi_status status ;
struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER , NULL } ;
union acpi_object * elements , * ppcc ;
union acpi_object * p ;
2016-04-05 14:52:57 -07:00
int ret = 0 ;
2014-12-09 16:47:17 -08:00
2016-04-05 14:52:57 -07:00
status = acpi_evaluate_object ( proc_priv - > adev - > handle , " PPCC " ,
NULL , & buf ) ;
2014-12-09 16:47:17 -08:00
if ( ACPI_FAILURE ( status ) )
return - ENODEV ;
p = buf . pointer ;
if ( ! p | | ( p - > type ! = ACPI_TYPE_PACKAGE ) ) {
2016-04-05 14:52:57 -07:00
dev_err ( proc_priv - > dev , " Invalid PPCC data \n " ) ;
2014-12-23 15:23:35 -08:00
ret = - EFAULT ;
goto free_buffer ;
2014-12-09 16:47:17 -08:00
}
2016-04-05 14:52:57 -07:00
2014-12-09 16:47:17 -08:00
if ( ! p - > package . count ) {
2016-04-05 14:52:57 -07:00
dev_err ( proc_priv - > dev , " Invalid PPCC package size \n " ) ;
2014-12-23 15:23:35 -08:00
ret = - EFAULT ;
goto free_buffer ;
2014-12-09 16:47:17 -08:00
}
for ( i = 0 ; i < min ( ( int ) p - > package . count - 1 , 2 ) ; + + i ) {
elements = & ( p - > package . elements [ i + 1 ] ) ;
if ( elements - > type ! = ACPI_TYPE_PACKAGE | |
2014-12-23 15:23:35 -08:00
elements - > package . count ! = 6 ) {
ret = - EFAULT ;
goto free_buffer ;
}
2014-12-09 16:47:17 -08:00
ppcc = elements - > package . elements ;
proc_priv - > power_limits [ i ] . index = ppcc [ 0 ] . integer . value ;
proc_priv - > power_limits [ i ] . min_uw = ppcc [ 1 ] . integer . value ;
proc_priv - > power_limits [ i ] . max_uw = ppcc [ 2 ] . integer . value ;
proc_priv - > power_limits [ i ] . tmin_us = ppcc [ 3 ] . integer . value ;
proc_priv - > power_limits [ i ] . tmax_us = ppcc [ 4 ] . integer . value ;
proc_priv - > power_limits [ i ] . step_uw = ppcc [ 5 ] . integer . value ;
}
2016-04-05 14:52:57 -07:00
free_buffer :
kfree ( buf . pointer ) ;
return ret ;
}
# define PROC_POWER_CAPABILITY_CHANGED 0x83
static void proc_thermal_notify ( acpi_handle handle , u32 event , void * data )
{
struct proc_thermal_device * proc_priv = data ;
if ( ! proc_priv )
return ;
switch ( event ) {
case PROC_POWER_CAPABILITY_CHANGED :
proc_thermal_read_ppcc ( proc_priv ) ;
2016-08-26 16:21:18 -07:00
int340x_thermal_zone_device_update ( proc_priv - > int340x_zone ,
THERMAL_DEVICE_POWER_CAPABILITY_CHANGED ) ;
2016-04-05 14:52:57 -07:00
break ;
default :
dev_err ( proc_priv - > dev , " Unsupported event [0x%x] \n " , event ) ;
break ;
}
}
static int proc_thermal_add ( struct device * dev ,
struct proc_thermal_device * * priv )
{
struct proc_thermal_device * proc_priv ;
struct acpi_device * adev ;
acpi_status status ;
unsigned long long tmp ;
struct thermal_zone_device_ops * ops = NULL ;
int ret ;
adev = ACPI_COMPANION ( dev ) ;
if ( ! adev )
return - ENODEV ;
proc_priv = devm_kzalloc ( dev , sizeof ( * proc_priv ) , GFP_KERNEL ) ;
if ( ! proc_priv )
return - ENOMEM ;
proc_priv - > dev = dev ;
proc_priv - > adev = adev ;
2014-12-09 16:47:17 -08:00
* priv = proc_priv ;
2016-04-05 14:52:57 -07:00
ret = proc_thermal_read_ppcc ( proc_priv ) ;
if ( ! ret ) {
ret = sysfs_create_group ( & dev - > kobj ,
& power_limit_attribute_group ) ;
}
2015-01-16 15:59:06 -08:00
if ( ret )
2016-04-05 14:52:57 -07:00
return ret ;
2015-01-16 15:59:06 -08:00
status = acpi_evaluate_integer ( adev - > handle , " _TMP " , NULL , & tmp ) ;
if ( ACPI_FAILURE ( status ) ) {
/* there is no _TMP method, add local method */
stored_tjmax = get_tjmax ( ) ;
if ( stored_tjmax > 0 )
ops = & proc_thermal_local_ops ;
}
proc_priv - > int340x_zone = int340x_thermal_zone_add ( adev , ops ) ;
if ( IS_ERR ( proc_priv - > int340x_zone ) ) {
ret = PTR_ERR ( proc_priv - > int340x_zone ) ;
2016-04-05 14:52:57 -07:00
goto remove_group ;
2015-01-16 15:59:06 -08:00
} else
ret = 0 ;
2014-12-23 15:23:35 -08:00
2016-04-05 14:52:57 -07:00
ret = acpi_install_notify_handler ( adev - > handle , ACPI_DEVICE_NOTIFY ,
proc_thermal_notify ,
( void * ) proc_priv ) ;
if ( ret )
goto remove_zone ;
return 0 ;
remove_zone :
int340x_thermal_zone_remove ( proc_priv - > int340x_zone ) ;
remove_group :
sysfs_remove_group ( & proc_priv - > dev - > kobj ,
& power_limit_attribute_group ) ;
2014-12-23 15:23:35 -08:00
return ret ;
2014-12-09 16:47:17 -08:00
}
2015-02-05 13:43:37 +00:00
static void proc_thermal_remove ( struct proc_thermal_device * proc_priv )
2014-12-09 16:47:17 -08:00
{
2016-04-05 14:52:57 -07:00
acpi_remove_notify_handler ( proc_priv - > adev - > handle ,
ACPI_DEVICE_NOTIFY , proc_thermal_notify ) ;
2015-01-16 15:59:06 -08:00
int340x_thermal_zone_remove ( proc_priv - > int340x_zone ) ;
2014-12-09 16:47:17 -08:00
sysfs_remove_group ( & proc_priv - > dev - > kobj ,
& power_limit_attribute_group ) ;
}
static int int3401_add ( struct platform_device * pdev )
{
struct proc_thermal_device * proc_priv ;
int ret ;
if ( proc_thermal_emum_mode = = PROC_THERMAL_PCI ) {
dev_err ( & pdev - > dev , " error: enumerated as PCI dev \n " ) ;
return - ENODEV ;
}
ret = proc_thermal_add ( & pdev - > dev , & proc_priv ) ;
if ( ret )
return ret ;
platform_set_drvdata ( pdev , proc_priv ) ;
proc_thermal_emum_mode = PROC_THERMAL_PLATFORM_DEV ;
return 0 ;
}
static int int3401_remove ( struct platform_device * pdev )
{
proc_thermal_remove ( platform_get_drvdata ( pdev ) ) ;
return 0 ;
}
2015-03-02 13:13:00 -08:00
static irqreturn_t proc_thermal_pci_msi_irq ( int irq , void * devid )
{
struct proc_thermal_device * proc_priv ;
struct pci_dev * pdev = devid ;
proc_priv = pci_get_drvdata ( pdev ) ;
intel_soc_dts_iosf_interrupt_handler ( proc_priv - > soc_dts ) ;
return IRQ_HANDLED ;
}
2014-12-09 16:47:17 -08:00
static int proc_thermal_pci_probe ( struct pci_dev * pdev ,
const struct pci_device_id * unused )
{
struct proc_thermal_device * proc_priv ;
int ret ;
if ( proc_thermal_emum_mode = = PROC_THERMAL_PLATFORM_DEV ) {
dev_err ( & pdev - > dev , " error: enumerated as platform dev \n " ) ;
return - ENODEV ;
}
ret = pci_enable_device ( pdev ) ;
if ( ret < 0 ) {
dev_err ( & pdev - > dev , " error: could not enable device \n " ) ;
return ret ;
}
ret = proc_thermal_add ( & pdev - > dev , & proc_priv ) ;
if ( ret ) {
pci_disable_device ( pdev ) ;
return ret ;
}
pci_set_drvdata ( pdev , proc_priv ) ;
proc_thermal_emum_mode = PROC_THERMAL_PCI ;
2015-03-02 13:13:00 -08:00
if ( pdev - > device = = PCI_DEVICE_ID_PROC_BSW_THERMAL ) {
/*
* Enumerate additional DTS sensors available via IOSF .
* But we are not treating as a failure condition , if
* there are no aux DTSs enabled or fails . This driver
* already exposes sensors , which can be accessed via
* ACPI / MSR . So we don ' t want to fail for auxiliary DTSs .
*/
proc_priv - > soc_dts = intel_soc_dts_iosf_init (
INTEL_SOC_DTS_INTERRUPT_MSI , 2 , 0 ) ;
if ( proc_priv - > soc_dts & & pdev - > irq ) {
ret = pci_enable_msi ( pdev ) ;
if ( ! ret ) {
ret = request_threaded_irq ( pdev - > irq , NULL ,
proc_thermal_pci_msi_irq ,
IRQF_ONESHOT , " proc_thermal " ,
pdev ) ;
if ( ret ) {
intel_soc_dts_iosf_exit (
proc_priv - > soc_dts ) ;
pci_disable_msi ( pdev ) ;
proc_priv - > soc_dts = NULL ;
}
}
} else
dev_err ( & pdev - > dev , " No auxiliary DTSs enabled \n " ) ;
}
2014-12-09 16:47:17 -08:00
return 0 ;
}
static void proc_thermal_pci_remove ( struct pci_dev * pdev )
{
2015-03-02 13:13:00 -08:00
struct proc_thermal_device * proc_priv = pci_get_drvdata ( pdev ) ;
if ( proc_priv - > soc_dts ) {
intel_soc_dts_iosf_exit ( proc_priv - > soc_dts ) ;
if ( pdev - > irq ) {
free_irq ( pdev - > irq , pdev ) ;
pci_disable_msi ( pdev ) ;
}
}
proc_thermal_remove ( proc_priv ) ;
2014-12-09 16:47:17 -08:00
pci_disable_device ( pdev ) ;
}
static const struct pci_device_id proc_thermal_pci_ids [ ] = {
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_BDW_THERMAL ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_HSB_THERMAL ) } ,
2015-04-20 11:25:37 -07:00
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_SKL_THERMAL ) } ,
2014-12-09 16:47:17 -08:00
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_BSW_THERMAL ) } ,
2015-10-14 08:09:14 -07:00
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_BXT0_THERMAL ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_BXT1_THERMAL ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_BXTX_THERMAL ) } ,
{ PCI_DEVICE ( PCI_VENDOR_ID_INTEL , PCI_DEVICE_ID_PROC_BXTP_THERMAL ) } ,
2014-12-09 16:47:17 -08:00
{ 0 , } ,
} ;
MODULE_DEVICE_TABLE ( pci , proc_thermal_pci_ids ) ;
static struct pci_driver proc_thermal_pci_driver = {
. name = " proc_thermal " ,
. probe = proc_thermal_pci_probe ,
. remove = proc_thermal_pci_remove ,
. id_table = proc_thermal_pci_ids ,
} ;
static const struct acpi_device_id int3401_device_ids [ ] = {
{ " INT3401 " , 0 } ,
{ " " , 0 } ,
} ;
MODULE_DEVICE_TABLE ( acpi , int3401_device_ids ) ;
static struct platform_driver int3401_driver = {
. probe = int3401_add ,
. remove = int3401_remove ,
. driver = {
. name = " int3401 thermal " ,
. acpi_match_table = int3401_device_ids ,
} ,
} ;
static int __init proc_thermal_init ( void )
{
int ret ;
ret = platform_driver_register ( & int3401_driver ) ;
if ( ret )
return ret ;
ret = pci_register_driver ( & proc_thermal_pci_driver ) ;
return ret ;
}
static void __exit proc_thermal_exit ( void )
{
platform_driver_unregister ( & int3401_driver ) ;
pci_unregister_driver ( & proc_thermal_pci_driver ) ;
}
module_init ( proc_thermal_init ) ;
module_exit ( proc_thermal_exit ) ;
MODULE_AUTHOR ( " Srinivas Pandruvada <srinivas.pandruvada@linux.intel.com> " ) ;
MODULE_DESCRIPTION ( " Processor Thermal Reporting Device Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;