2009-12-16 23:38:25 +03:00
/*
2013-08-24 00:14:03 +04:00
* k10temp . c - AMD Family 10 h / 11 h / 12 h / 14 h / 15 h / 16 h processor hardware monitoring
2009-12-16 23:38:25 +03:00
*
* Copyright ( c ) 2009 Clemens Ladisch < clemens @ ladisch . de >
*
*
* This driver is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License ; either
* version 2 of the License , or ( at your option ) any later version .
*
* This driver 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 driver ; if not , see < http : //www.gnu.org/licenses/>.
*/
# include <linux/err.h>
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
# include <linux/init.h>
# include <linux/module.h>
# include <linux/pci.h>
2018-05-04 23:01:33 +03:00
# include <asm/amd_nb.h>
2009-12-16 23:38:25 +03:00
# include <asm/processor.h>
2011-05-25 22:43:31 +04:00
MODULE_DESCRIPTION ( " AMD Family 10h+ CPU core temperature monitor " ) ;
2009-12-16 23:38:25 +03:00
MODULE_AUTHOR ( " Clemens Ladisch <clemens@ladisch.de> " ) ;
MODULE_LICENSE ( " GPL " ) ;
static bool force ;
module_param ( force , bool , 0444 ) ;
MODULE_PARM_DESC ( force , " force loading on processors with erratum 319 " ) ;
2014-08-15 03:15:27 +04:00
/* Provide lock for writing to NB_SMU_IND_ADDR */
static DEFINE_MUTEX ( nb_smu_ind_mutex ) ;
2018-04-29 19:16:45 +03:00
# ifndef PCI_DEVICE_ID_AMD_15H_M70H_NB_F3
# define PCI_DEVICE_ID_AMD_15H_M70H_NB_F3 0x15b3
# endif
2017-09-05 04:33:53 +03:00
# ifndef PCI_DEVICE_ID_AMD_17H_DF_F3
# define PCI_DEVICE_ID_AMD_17H_DF_F3 0x1463
# endif
2018-05-04 23:01:33 +03:00
# ifndef PCI_DEVICE_ID_AMD_17H_M10H_DF_F3
# define PCI_DEVICE_ID_AMD_17H_M10H_DF_F3 0x15eb
2018-04-24 18:59:45 +03:00
# endif
2010-01-10 22:52:34 +03:00
/* CPUID function 0x80000001, ebx */
# define CPUID_PKGTYPE_MASK 0xf0000000
# define CPUID_PKGTYPE_F 0x00000000
# define CPUID_PKGTYPE_AM2R2_AM3 0x10000000
/* DRAM controller (PCI function 2) */
# define REG_DCT0_CONFIG_HIGH 0x094
# define DDR3_MODE 0x00000100
/* miscellaneous (PCI function 3) */
2009-12-16 23:38:25 +03:00
# define REG_HARDWARE_THERMAL_CONTROL 0x64
# define HTC_ENABLE 0x00000001
# define REG_REPORTED_TEMPERATURE 0xa4
# define REG_NORTHBRIDGE_CAPABILITIES 0xe8
# define NB_CAP_HTC 0x00000400
2014-08-15 03:15:27 +04:00
/*
2018-04-29 18:08:24 +03:00
* For F15h M60h and M70h , REG_HARDWARE_THERMAL_CONTROL
* and REG_REPORTED_TEMPERATURE have been moved to
* D0F0xBC_xD820_0C64 [ Hardware Temperature Control ]
* D0F0xBC_xD820_0CA4 [ Reported Temperature Control ]
2014-08-15 03:15:27 +04:00
*/
2018-04-29 18:08:24 +03:00
# define F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET 0xd8200c64
2014-08-15 03:15:27 +04:00
# define F15H_M60H_REPORTED_TEMP_CTRL_OFFSET 0xd8200ca4
2017-09-05 04:33:53 +03:00
/* F17h M01h Access througn SMN */
# define F17H_M01H_REPORTED_TEMP_CTRL_OFFSET 0x00059800
2017-09-05 04:33:53 +03:00
struct k10temp_data {
struct pci_dev * pdev ;
2018-04-29 18:08:24 +03:00
void ( * read_htcreg ) ( struct pci_dev * pdev , u32 * regval ) ;
2017-09-05 04:33:53 +03:00
void ( * read_tempreg ) ( struct pci_dev * pdev , u32 * regval ) ;
2017-09-05 04:33:53 +03:00
int temp_offset ;
2018-04-24 16:55:55 +03:00
u32 temp_adjust_mask ;
2018-04-26 22:22:29 +03:00
bool show_tdie ;
2017-09-05 04:33:53 +03:00
} ;
struct tctl_offset {
u8 model ;
char const * id ;
int offset ;
} ;
static const struct tctl_offset tctl_offset_table [ ] = {
2017-11-13 23:38:23 +03:00
{ 0x17 , " AMD Ryzen 5 1600X " , 20000 } ,
2017-09-05 04:33:53 +03:00
{ 0x17 , " AMD Ryzen 7 1700X " , 20000 } ,
{ 0x17 , " AMD Ryzen 7 1800X " , 20000 } ,
2018-04-24 16:55:55 +03:00
{ 0x17 , " AMD Ryzen 7 2700X " , 10000 } ,
2017-09-05 04:33:53 +03:00
{ 0x17 , " AMD Ryzen Threadripper 1950X " , 27000 } ,
{ 0x17 , " AMD Ryzen Threadripper 1920X " , 27000 } ,
2018-01-19 17:38:03 +03:00
{ 0x17 , " AMD Ryzen Threadripper 1900X " , 27000 } ,
2017-09-05 04:33:53 +03:00
{ 0x17 , " AMD Ryzen Threadripper 1950 " , 10000 } ,
{ 0x17 , " AMD Ryzen Threadripper 1920 " , 10000 } ,
{ 0x17 , " AMD Ryzen Threadripper 1910 " , 10000 } ,
2018-08-07 16:54:54 +03:00
{ 0x17 , " AMD Ryzen Threadripper 2950X " , 27000 } ,
{ 0x17 , " AMD Ryzen Threadripper 2990WX " , 27000 } ,
2017-09-05 04:33:53 +03:00
} ;
2018-04-29 18:08:24 +03:00
static void read_htcreg_pci ( struct pci_dev * pdev , u32 * regval )
{
pci_read_config_dword ( pdev , REG_HARDWARE_THERMAL_CONTROL , regval ) ;
}
2017-09-05 04:33:53 +03:00
static void read_tempreg_pci ( struct pci_dev * pdev , u32 * regval )
{
pci_read_config_dword ( pdev , REG_REPORTED_TEMPERATURE , regval ) ;
}
static void amd_nb_index_read ( struct pci_dev * pdev , unsigned int devfn ,
unsigned int base , int offset , u32 * val )
2014-08-15 03:15:27 +04:00
{
mutex_lock ( & nb_smu_ind_mutex ) ;
pci_bus_write_config_dword ( pdev - > bus , devfn ,
2017-09-05 04:33:53 +03:00
base , offset ) ;
2014-08-15 03:15:27 +04:00
pci_bus_read_config_dword ( pdev - > bus , devfn ,
2017-09-05 04:33:53 +03:00
base + 4 , val ) ;
2014-08-15 03:15:27 +04:00
mutex_unlock ( & nb_smu_ind_mutex ) ;
}
2018-04-29 18:08:24 +03:00
static void read_htcreg_nb_f15 ( struct pci_dev * pdev , u32 * regval )
{
amd_nb_index_read ( pdev , PCI_DEVFN ( 0 , 0 ) , 0xb8 ,
F15H_M60H_HARDWARE_TEMP_CTRL_OFFSET , regval ) ;
}
2017-09-05 04:33:53 +03:00
static void read_tempreg_nb_f15 ( struct pci_dev * pdev , u32 * regval )
{
amd_nb_index_read ( pdev , PCI_DEVFN ( 0 , 0 ) , 0xb8 ,
F15H_M60H_REPORTED_TEMP_CTRL_OFFSET , regval ) ;
}
2017-09-05 04:33:53 +03:00
static void read_tempreg_nb_f17 ( struct pci_dev * pdev , u32 * regval )
{
2018-05-04 23:01:33 +03:00
amd_smn_read ( amd_pci_dev_to_node_id ( pdev ) ,
F17H_M01H_REPORTED_TEMP_CTRL_OFFSET , regval ) ;
2017-09-05 04:33:53 +03:00
}
2018-06-01 16:37:13 +03:00
static unsigned int get_raw_temp ( struct k10temp_data * data )
2009-12-16 23:38:25 +03:00
{
2017-09-05 04:33:53 +03:00
unsigned int temp ;
2018-04-26 22:22:29 +03:00
u32 regval ;
2017-09-05 04:33:53 +03:00
data - > read_tempreg ( data - > pdev , & regval ) ;
temp = ( regval > > 21 ) * 125 ;
2018-04-24 16:55:55 +03:00
if ( regval & data - > temp_adjust_mask )
temp - = 49000 ;
2018-04-26 22:22:29 +03:00
return temp ;
}
static ssize_t temp1_input_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct k10temp_data * data = dev_get_drvdata ( dev ) ;
unsigned int temp = get_raw_temp ( data ) ;
2018-02-08 04:49:39 +03:00
if ( temp > data - > temp_offset )
temp - = data - > temp_offset ;
else
temp = 0 ;
2017-09-05 04:33:53 +03:00
return sprintf ( buf , " %u \n " , temp ) ;
2009-12-16 23:38:25 +03:00
}
2018-04-26 22:22:29 +03:00
static ssize_t temp2_input_show ( struct device * dev ,
struct device_attribute * devattr , char * buf )
{
struct k10temp_data * data = dev_get_drvdata ( dev ) ;
unsigned int temp = get_raw_temp ( data ) ;
return sprintf ( buf , " %u \n " , temp ) ;
}
static ssize_t temp_label_show ( struct device * dev ,
struct device_attribute * devattr , char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
return sprintf ( buf , " %s \n " , attr - > index ? " Tctl " : " Tdie " ) ;
}
2016-12-22 15:05:19 +03:00
static ssize_t temp1_max_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
2009-12-16 23:38:25 +03:00
{
return sprintf ( buf , " %d \n " , 70 * 1000 ) ;
}
static ssize_t show_temp_crit ( struct device * dev ,
struct device_attribute * devattr , char * buf )
{
struct sensor_device_attribute * attr = to_sensor_dev_attr ( devattr ) ;
2017-09-05 04:33:53 +03:00
struct k10temp_data * data = dev_get_drvdata ( dev ) ;
2009-12-16 23:38:25 +03:00
int show_hyst = attr - > index ;
u32 regval ;
int value ;
2018-04-29 18:08:24 +03:00
data - > read_htcreg ( data - > pdev , & regval ) ;
2009-12-16 23:38:25 +03:00
value = ( ( regval > > 16 ) & 0x7f ) * 500 + 52000 ;
if ( show_hyst )
value - = ( ( regval > > 24 ) & 0xf ) * 500 ;
return sprintf ( buf , " %d \n " , value ) ;
}
2016-12-22 15:05:19 +03:00
static DEVICE_ATTR_RO ( temp1_input ) ;
static DEVICE_ATTR_RO ( temp1_max ) ;
2009-12-16 23:38:25 +03:00
static SENSOR_DEVICE_ATTR ( temp1_crit , S_IRUGO , show_temp_crit , NULL , 0 ) ;
static SENSOR_DEVICE_ATTR ( temp1_crit_hyst , S_IRUGO , show_temp_crit , NULL , 1 ) ;
2014-08-15 20:27:03 +04:00
2018-04-26 22:22:29 +03:00
static SENSOR_DEVICE_ATTR ( temp1_label , 0444 , temp_label_show , NULL , 0 ) ;
static DEVICE_ATTR_RO ( temp2_input ) ;
static SENSOR_DEVICE_ATTR ( temp2_label , 0444 , temp_label_show , NULL , 1 ) ;
2014-08-15 20:27:03 +04:00
static umode_t k10temp_is_visible ( struct kobject * kobj ,
struct attribute * attr , int index )
{
struct device * dev = container_of ( kobj , struct device , kobj ) ;
2017-09-05 04:33:53 +03:00
struct k10temp_data * data = dev_get_drvdata ( dev ) ;
struct pci_dev * pdev = data - > pdev ;
2018-04-26 22:22:29 +03:00
u32 reg ;
2014-08-15 20:27:03 +04:00
2018-04-26 22:22:29 +03:00
switch ( index ) {
case 0 . . . 1 : /* temp1_input, temp1_max */
default :
break ;
case 2 . . . 3 : /* temp1_crit, temp1_crit_hyst */
2018-04-29 18:08:24 +03:00
if ( ! data - > read_htcreg )
return 0 ;
2014-08-15 20:27:03 +04:00
pci_read_config_dword ( pdev , REG_NORTHBRIDGE_CAPABILITIES ,
2018-04-29 18:08:24 +03:00
& reg ) ;
if ( ! ( reg & NB_CAP_HTC ) )
return 0 ;
data - > read_htcreg ( data - > pdev , & reg ) ;
if ( ! ( reg & HTC_ENABLE ) )
2014-08-15 20:27:03 +04:00
return 0 ;
2018-04-26 22:22:29 +03:00
break ;
case 4 . . . 6 : /* temp1_label, temp2_input, temp2_label */
if ( ! data - > show_tdie )
return 0 ;
break ;
2014-08-15 20:27:03 +04:00
}
return attr - > mode ;
}
static struct attribute * k10temp_attrs [ ] = {
& dev_attr_temp1_input . attr ,
& dev_attr_temp1_max . attr ,
& sensor_dev_attr_temp1_crit . dev_attr . attr ,
& sensor_dev_attr_temp1_crit_hyst . dev_attr . attr ,
2018-04-26 22:22:29 +03:00
& sensor_dev_attr_temp1_label . dev_attr . attr ,
& dev_attr_temp2_input . attr ,
& sensor_dev_attr_temp2_label . dev_attr . attr ,
2014-08-15 20:27:03 +04:00
NULL
} ;
static const struct attribute_group k10temp_group = {
. attrs = k10temp_attrs ,
. is_visible = k10temp_is_visible ,
} ;
__ATTRIBUTE_GROUPS ( k10temp ) ;
2009-12-16 23:38:25 +03:00
2012-11-19 22:22:35 +04:00
static bool has_erratum_319 ( struct pci_dev * pdev )
2009-12-16 23:38:25 +03:00
{
2010-01-10 22:52:34 +03:00
u32 pkg_type , reg_dram_cfg ;
if ( boot_cpu_data . x86 ! = 0x10 )
return false ;
2009-12-16 23:38:25 +03:00
/*
2010-01-10 22:52:34 +03:00
* Erratum 319 : The thermal sensor of Socket F / AM2 + processors
* may be unreliable .
2009-12-16 23:38:25 +03:00
*/
2010-01-10 22:52:34 +03:00
pkg_type = cpuid_ebx ( 0x80000001 ) & CPUID_PKGTYPE_MASK ;
if ( pkg_type = = CPUID_PKGTYPE_F )
return true ;
if ( pkg_type ! = CPUID_PKGTYPE_AM2R2_AM3 )
return false ;
2010-06-20 11:22:31 +04:00
/* DDR3 memory implies socket AM3, which is good */
2010-01-10 22:52:34 +03:00
pci_bus_read_config_dword ( pdev - > bus ,
PCI_DEVFN ( PCI_SLOT ( pdev - > devfn ) , 2 ) ,
REG_DCT0_CONFIG_HIGH , & reg_dram_cfg ) ;
2010-06-20 11:22:31 +04:00
if ( reg_dram_cfg & DDR3_MODE )
return false ;
/*
* Unfortunately it is possible to run a socket AM3 CPU with DDR2
* memory . We blacklist all the cores which do exist in socket AM2 +
* format . It still isn ' t perfect , as RB - C2 cores exist in both AM2 +
* and AM3 formats , but that ' s the best we can do .
*/
return boot_cpu_data . x86_model < 4 | |
2018-01-01 04:52:10 +03:00
( boot_cpu_data . x86_model = = 4 & & boot_cpu_data . x86_stepping < = 2 ) ;
2009-12-16 23:38:25 +03:00
}
2012-11-19 22:22:35 +04:00
static int k10temp_probe ( struct pci_dev * pdev ,
2009-12-16 23:38:25 +03:00
const struct pci_device_id * id )
{
2010-01-10 22:52:34 +03:00
int unreliable = has_erratum_319 ( pdev ) ;
2014-08-15 20:27:03 +04:00
struct device * dev = & pdev - > dev ;
2017-09-05 04:33:53 +03:00
struct k10temp_data * data ;
2014-08-15 20:27:03 +04:00
struct device * hwmon_dev ;
2017-09-05 04:33:53 +03:00
int i ;
2009-12-16 23:38:25 +03:00
2014-08-15 20:27:03 +04:00
if ( unreliable ) {
if ( ! force ) {
dev_err ( dev ,
" unreliable CPU thermal sensor; monitoring disabled \n " ) ;
return - ENODEV ;
}
dev_warn ( dev ,
2009-12-16 23:38:25 +03:00
" unreliable CPU thermal sensor; check erratum 319 \n " ) ;
2014-08-15 20:27:03 +04:00
}
2009-12-16 23:38:25 +03:00
2017-09-05 04:33:53 +03:00
data = devm_kzalloc ( dev , sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
data - > pdev = pdev ;
if ( boot_cpu_data . x86 = = 0x15 & & ( boot_cpu_data . x86_model = = 0x60 | |
2018-04-24 16:55:55 +03:00
boot_cpu_data . x86_model = = 0x70 ) ) {
2018-04-29 18:08:24 +03:00
data - > read_htcreg = read_htcreg_nb_f15 ;
2017-09-05 04:33:53 +03:00
data - > read_tempreg = read_tempreg_nb_f15 ;
2018-04-24 16:55:55 +03:00
} else if ( boot_cpu_data . x86 = = 0x17 ) {
data - > temp_adjust_mask = 0x80000 ;
2017-09-05 04:33:53 +03:00
data - > read_tempreg = read_tempreg_nb_f17 ;
2018-04-26 22:22:29 +03:00
data - > show_tdie = true ;
2018-04-24 16:55:55 +03:00
} else {
2018-04-29 18:08:24 +03:00
data - > read_htcreg = read_htcreg_pci ;
2017-09-05 04:33:53 +03:00
data - > read_tempreg = read_tempreg_pci ;
2018-04-24 16:55:55 +03:00
}
2017-09-05 04:33:53 +03:00
2017-09-05 04:33:53 +03:00
for ( i = 0 ; i < ARRAY_SIZE ( tctl_offset_table ) ; i + + ) {
const struct tctl_offset * entry = & tctl_offset_table [ i ] ;
if ( boot_cpu_data . x86 = = entry - > model & &
strstr ( boot_cpu_data . x86_model_id , entry - > id ) ) {
data - > temp_offset = entry - > offset ;
break ;
}
}
2017-09-05 04:33:53 +03:00
hwmon_dev = devm_hwmon_device_register_with_groups ( dev , " k10temp " , data ,
2014-08-15 20:27:03 +04:00
k10temp_groups ) ;
return PTR_ERR_OR_ZERO ( hwmon_dev ) ;
2009-12-16 23:38:25 +03:00
}
2013-12-03 11:10:29 +04:00
static const struct pci_device_id k10temp_id_table [ ] = {
2009-12-16 23:38:25 +03:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_10H_NB_MISC ) } ,
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_11H_NB_MISC ) } ,
2011-02-17 11:22:40 +03:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_CNB17H_F3 ) } ,
2011-05-25 22:43:31 +04:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_15H_NB_F3 ) } ,
2012-05-04 20:28:21 +04:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_15H_M10H_F3 ) } ,
2014-01-14 22:46:46 +04:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_15H_M30H_NB_F3 ) } ,
2014-08-15 03:15:27 +04:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_15H_M60H_NB_F3 ) } ,
2018-04-29 19:16:45 +03:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_15H_M70H_NB_F3 ) } ,
2013-08-24 00:14:03 +04:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_16H_NB_F3 ) } ,
2014-03-12 01:25:59 +04:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_16H_M30H_NB_F3 ) } ,
2017-09-05 04:33:53 +03:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_17H_DF_F3 ) } ,
2018-05-04 23:01:33 +03:00
{ PCI_VDEVICE ( AMD , PCI_DEVICE_ID_AMD_17H_M10H_DF_F3 ) } ,
2009-12-16 23:38:25 +03:00
{ }
} ;
MODULE_DEVICE_TABLE ( pci , k10temp_id_table ) ;
static struct pci_driver k10temp_driver = {
. name = " k10temp " ,
. id_table = k10temp_id_table ,
. probe = k10temp_probe ,
} ;
2012-04-03 05:25:46 +04:00
module_pci_driver ( k10temp_driver ) ;