2019-05-23 12:14:55 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2005-04-17 02:20:36 +04:00
/*
2015-05-14 14:16:36 +03:00
* dell - smm - hwmon . c - - Linux driver for accessing the SMM BIOS on Dell laptops .
2005-04-17 02:20:36 +04:00
*
* Copyright ( C ) 2001 Massimo Dal Zotto < dz @ debian . org >
*
2011-05-25 22:43:33 +04:00
* Hwmon integration :
2014-01-29 23:40:08 +04:00
* Copyright ( C ) 2011 Jean Delvare < jdelvare @ suse . de >
2015-01-12 16:32:00 +03:00
* Copyright ( C ) 2013 , 2014 Guenter Roeck < linux @ roeck - us . net >
2020-04-11 00:34:00 +03:00
* Copyright ( C ) 2014 , 2015 Pali Rohár < pali @ kernel . org >
2005-04-17 02:20:36 +04:00
*/
2013-12-14 21:30:08 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2016-08-29 09:48:47 +03:00
# include <linux/cpu.h>
2015-01-12 16:32:00 +03:00
# include <linux/delay.h>
2021-07-29 01:15:52 +03:00
# include <linux/err.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
2021-07-29 01:15:52 +03:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include <linux/types.h>
# include <linux/init.h>
# include <linux/proc_fs.h>
2005-06-26 01:54:26 +04:00
# include <linux/seq_file.h>
2005-06-26 01:54:25 +04:00
# include <linux/dmi.h>
2007-05-08 11:28:59 +04:00
# include <linux/capability.h>
2010-06-02 16:28:52 +04:00
# include <linux/mutex.h>
2011-05-25 22:43:33 +04:00
# include <linux/hwmon.h>
# include <linux/hwmon-sysfs.h>
2013-12-14 21:30:09 +04:00
# include <linux/uaccess.h>
# include <linux/io.h>
2013-12-14 21:30:15 +04:00
# include <linux/sched.h>
2016-06-18 01:54:44 +03:00
# include <linux/ctype.h>
2016-08-29 09:48:47 +03:00
# include <linux/smp.h>
2005-04-17 02:20:36 +04:00
# include <linux/i8k.h>
# define I8K_SMM_FN_STATUS 0x0025
# define I8K_SMM_POWER_STATUS 0x0069
# define I8K_SMM_SET_FAN 0x01a3
# define I8K_SMM_GET_FAN 0x00a3
# define I8K_SMM_GET_SPEED 0x02a3
2015-01-12 16:32:05 +03:00
# define I8K_SMM_GET_FAN_TYPE 0x03a3
2015-01-12 16:32:03 +03:00
# define I8K_SMM_GET_NOM_SPEED 0x04a3
2005-04-17 02:20:36 +04:00
# define I8K_SMM_GET_TEMP 0x10a3
2015-01-12 16:31:57 +03:00
# define I8K_SMM_GET_TEMP_TYPE 0x11a3
2005-06-26 01:54:28 +04:00
# define I8K_SMM_GET_DELL_SIG1 0xfea3
# define I8K_SMM_GET_DELL_SIG2 0xffa3
2005-04-17 02:20:36 +04:00
# define I8K_FAN_MULT 30
2015-01-12 16:32:03 +03:00
# define I8K_FAN_MAX_RPM 30000
2005-04-17 02:20:36 +04:00
# define I8K_MAX_TEMP 127
# define I8K_FN_NONE 0x00
# define I8K_FN_UP 0x01
# define I8K_FN_DOWN 0x02
# define I8K_FN_MUTE 0x04
# define I8K_FN_MASK 0x07
# define I8K_FN_SHIFT 8
# define I8K_POWER_AC 0x05
# define I8K_POWER_BATTERY 0x01
2010-06-02 16:28:52 +04:00
static DEFINE_MUTEX ( i8k_mutex ) ;
2005-06-26 01:54:25 +04:00
static char bios_version [ 4 ] ;
2016-06-18 01:54:45 +03:00
static char bios_machineid [ 16 ] ;
2011-05-25 22:43:33 +04:00
static struct device * i8k_hwmon_dev ;
2013-12-14 21:30:10 +04:00
static u32 i8k_hwmon_flags ;
2015-01-12 16:32:03 +03:00
static uint i8k_fan_mult = I8K_FAN_MULT ;
2015-01-12 16:32:02 +03:00
static uint i8k_pwm_mult ;
static uint i8k_fan_max = I8K_FAN_HIGH ;
2016-06-18 01:54:46 +03:00
static bool disallow_fan_type_call ;
2018-01-27 19:28:34 +03:00
static bool disallow_fan_support ;
2019-11-22 13:15:18 +03:00
static unsigned int manual_fan ;
static unsigned int auto_fan ;
2013-12-14 21:30:10 +04:00
# define I8K_HWMON_HAVE_TEMP1 (1 << 0)
2013-12-14 21:30:11 +04:00
# define I8K_HWMON_HAVE_TEMP2 (1 << 1)
# define I8K_HWMON_HAVE_TEMP3 (1 << 2)
# define I8K_HWMON_HAVE_TEMP4 (1 << 3)
2018-12-24 19:15:00 +03:00
# define I8K_HWMON_HAVE_TEMP5 (1 << 4)
# define I8K_HWMON_HAVE_TEMP6 (1 << 5)
# define I8K_HWMON_HAVE_TEMP7 (1 << 6)
# define I8K_HWMON_HAVE_TEMP8 (1 << 7)
# define I8K_HWMON_HAVE_TEMP9 (1 << 8)
# define I8K_HWMON_HAVE_TEMP10 (1 << 9)
# define I8K_HWMON_HAVE_FAN1 (1 << 10)
# define I8K_HWMON_HAVE_FAN2 (1 << 11)
# define I8K_HWMON_HAVE_FAN3 (1 << 12)
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Massimo Dal Zotto (dz@debian.org) " ) ;
2020-04-11 00:34:00 +03:00
MODULE_AUTHOR ( " Pali Rohár <pali@kernel.org> " ) ;
2015-05-14 14:16:37 +03:00
MODULE_DESCRIPTION ( " Dell laptop SMM BIOS hwmon driver " ) ;
2005-04-17 02:20:36 +04:00
MODULE_LICENSE ( " GPL " ) ;
2015-05-14 14:16:36 +03:00
MODULE_ALIAS ( " i8k " ) ;
2005-04-17 02:20:36 +04:00
2012-01-13 03:02:20 +04:00
static bool force ;
2005-04-17 02:20:36 +04:00
module_param ( force , bool , 0 ) ;
MODULE_PARM_DESC ( force , " Force loading without checking for supported models " ) ;
2012-01-13 03:02:20 +04:00
static bool ignore_dmi ;
2005-06-26 01:54:25 +04:00
module_param ( ignore_dmi , bool , 0 ) ;
MODULE_PARM_DESC ( ignore_dmi , " Continue probing hardware even if DMI data does not match " ) ;
2015-05-14 14:16:37 +03:00
# if IS_ENABLED(CONFIG_I8K)
2016-06-18 01:54:45 +03:00
static bool restricted = true ;
2005-04-17 02:20:36 +04:00
module_param ( restricted , bool , 0 ) ;
2016-06-18 01:54:45 +03:00
MODULE_PARM_DESC ( restricted , " Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1) " ) ;
2005-04-17 02:20:36 +04:00
2012-01-13 03:02:20 +04:00
static bool power_status ;
2005-04-17 02:20:36 +04:00
module_param ( power_status , bool , 0600 ) ;
2016-06-18 01:54:45 +03:00
MODULE_PARM_DESC ( power_status , " Report power status in /proc/i8k (default: 0) " ) ;
2015-05-14 14:16:37 +03:00
# endif
2005-04-17 02:20:36 +04:00
2015-01-12 16:32:03 +03:00
static uint fan_mult ;
2015-01-12 16:32:02 +03:00
module_param ( fan_mult , uint , 0 ) ;
2015-01-12 16:32:03 +03:00
MODULE_PARM_DESC ( fan_mult , " Factor to multiply fan speed with (default: autodetect) " ) ;
2008-05-01 15:34:58 +04:00
2015-01-12 16:32:03 +03:00
static uint fan_max ;
2015-01-12 16:32:02 +03:00
module_param ( fan_max , uint , 0 ) ;
2015-01-12 16:32:03 +03:00
MODULE_PARM_DESC ( fan_max , " Maximum configurable fan speed (default: autodetect) " ) ;
2014-06-21 19:08:10 +04:00
2005-06-26 01:54:27 +04:00
struct smm_regs {
2005-06-26 01:54:23 +04:00
unsigned int eax ;
2013-12-14 21:30:09 +04:00
unsigned int ebx __packed ;
unsigned int ecx __packed ;
unsigned int edx __packed ;
unsigned int esi __packed ;
unsigned int edi __packed ;
2005-06-26 01:54:27 +04:00
} ;
2005-04-17 02:20:36 +04:00
2007-10-03 23:15:40 +04:00
static inline const char * i8k_get_dmi_data ( int field )
2005-06-26 01:54:25 +04:00
{
2007-10-03 23:15:40 +04:00
const char * dmi_data = dmi_get_system_info ( field ) ;
2005-11-12 08:55:15 +03:00
return dmi_data & & * dmi_data ? dmi_data : " ? " ;
2005-06-26 01:54:25 +04:00
}
2005-04-17 02:20:36 +04:00
/*
* Call the System Management Mode BIOS . Code provided by Jonathan Buzzard .
*/
2016-08-29 09:48:47 +03:00
static int i8k_smm_func ( void * par )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:23 +04:00
int rc ;
2016-08-29 09:48:47 +03:00
struct smm_regs * regs = par ;
2005-06-26 01:54:23 +04:00
int eax = regs - > eax ;
2013-12-14 21:30:15 +04:00
2016-06-18 01:54:49 +03:00
# ifdef DEBUG
int ebx = regs - > ebx ;
unsigned long duration ;
ktime_t calltime , delta , rettime ;
calltime = ktime_get ( ) ;
# endif
2013-12-14 21:30:15 +04:00
/* SMM requires CPU 0 */
2016-08-29 09:48:47 +03:00
if ( smp_processor_id ( ) ! = 0 )
return - EBUSY ;
2005-06-26 01:54:23 +04:00
2008-02-07 11:16:27 +03:00
# if defined(CONFIG_X86_64)
2010-11-15 23:22:37 +03:00
asm volatile ( " pushq %%rax \n \t "
2008-02-07 11:16:27 +03:00
" movl 0(%%rax),%%edx \n \t "
" pushq %%rdx \n \t "
" movl 4(%%rax),%%ebx \n \t "
" movl 8(%%rax),%%ecx \n \t "
" movl 12(%%rax),%%edx \n \t "
" movl 16(%%rax),%%esi \n \t "
" movl 20(%%rax),%%edi \n \t "
" popq %%rax \n \t "
" out %%al,$0xb2 \n \t "
" out %%al,$0x84 \n \t "
" xchgq %%rax,(%%rsp) \n \t "
" movl %%ebx,4(%%rax) \n \t "
" movl %%ecx,8(%%rax) \n \t "
" movl %%edx,12(%%rax) \n \t "
" movl %%esi,16(%%rax) \n \t "
" movl %%edi,20(%%rax) \n \t "
" popq %%rdx \n \t "
" movl %%edx,0(%%rax) \n \t "
2011-05-25 22:43:31 +04:00
" pushfq \n \t "
" popq %%rax \n \t "
2008-02-07 11:16:27 +03:00
" andl $1,%%eax \n "
2013-12-14 21:30:09 +04:00
: " =a " ( rc )
2008-02-07 11:16:27 +03:00
: " a " ( regs )
: " %ebx " , " %ecx " , " %edx " , " %esi " , " %edi " , " memory " ) ;
# else
2010-11-15 23:22:37 +03:00
asm volatile ( " pushl %%eax \n \t "
2005-06-26 01:54:23 +04:00
" movl 0(%%eax),%%edx \n \t "
" push %%edx \n \t "
" movl 4(%%eax),%%ebx \n \t "
" movl 8(%%eax),%%ecx \n \t "
" movl 12(%%eax),%%edx \n \t "
" movl 16(%%eax),%%esi \n \t "
" movl 20(%%eax),%%edi \n \t "
" popl %%eax \n \t "
" out %%al,$0xb2 \n \t "
" out %%al,$0x84 \n \t "
" xchgl %%eax,(%%esp) \n \t "
" movl %%ebx,4(%%eax) \n \t "
" movl %%ecx,8(%%eax) \n \t "
" movl %%edx,12(%%eax) \n \t "
" movl %%esi,16(%%eax) \n \t "
" movl %%edi,20(%%eax) \n \t "
" popl %%edx \n \t "
" movl %%edx,0(%%eax) \n \t "
" lahf \n \t "
" shrl $8,%%eax \n \t "
2010-11-13 14:13:53 +03:00
" andl $1,%%eax \n "
2013-12-14 21:30:09 +04:00
: " =a " ( rc )
2005-06-26 01:54:23 +04:00
: " a " ( regs )
: " %ebx " , " %ecx " , " %edx " , " %esi " , " %edi " , " memory " ) ;
2008-02-07 11:16:27 +03:00
# endif
2005-06-26 01:54:27 +04:00
if ( rc ! = 0 | | ( regs - > eax & 0xffff ) = = 0xffff | | regs - > eax = = eax )
2013-12-14 21:30:15 +04:00
rc = - EINVAL ;
2005-06-26 01:54:23 +04:00
2016-06-18 01:54:49 +03:00
# ifdef DEBUG
rettime = ktime_get ( ) ;
delta = ktime_sub ( rettime , calltime ) ;
duration = ktime_to_ns ( delta ) > > 10 ;
pr_debug ( " smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs) \n " , eax , ebx ,
( rc ? 0xffff : regs - > eax & 0xffff ) , duration ) ;
# endif
2013-12-14 21:30:15 +04:00
return rc ;
2005-04-17 02:20:36 +04:00
}
2016-08-29 09:48:47 +03:00
/*
* Call the System Management Mode BIOS .
*/
static int i8k_smm ( struct smm_regs * regs )
{
int ret ;
get_online_cpus ( ) ;
ret = smp_call_on_cpu ( 0 , i8k_smm_func , regs , true ) ;
put_online_cpus ( ) ;
return ret ;
}
2005-04-17 02:20:36 +04:00
/*
* Read the fan status .
*/
static int i8k_get_fan_status ( int fan )
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_GET_FAN , } ;
2005-04-17 02:20:36 +04:00
2018-01-27 19:28:34 +03:00
if ( disallow_fan_support )
return - EINVAL ;
2005-06-26 01:54:23 +04:00
regs . ebx = fan & 0xff ;
2005-06-26 01:54:27 +04:00
return i8k_smm ( & regs ) ? : regs . eax & 0xff ;
2005-04-17 02:20:36 +04:00
}
/*
* Read the fan speed in RPM .
*/
static int i8k_get_fan_speed ( int fan )
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_GET_SPEED , } ;
2005-04-17 02:20:36 +04:00
2018-01-27 19:28:34 +03:00
if ( disallow_fan_support )
return - EINVAL ;
2005-06-26 01:54:23 +04:00
regs . ebx = fan & 0xff ;
2013-12-14 21:30:19 +04:00
return i8k_smm ( & regs ) ? : ( regs . eax & 0xffff ) * i8k_fan_mult ;
2005-04-17 02:20:36 +04:00
}
2015-01-12 16:32:05 +03:00
/*
* Read the fan type .
*/
2016-06-18 01:54:47 +03:00
static int _i8k_get_fan_type ( int fan )
2015-01-12 16:32:05 +03:00
{
struct smm_regs regs = { . eax = I8K_SMM_GET_FAN_TYPE , } ;
2018-01-27 19:28:34 +03:00
if ( disallow_fan_support | | disallow_fan_type_call )
2016-06-18 01:54:46 +03:00
return - EINVAL ;
2015-01-12 16:32:05 +03:00
regs . ebx = fan & 0xff ;
return i8k_smm ( & regs ) ? : regs . eax & 0xff ;
}
2016-06-18 01:54:47 +03:00
static int i8k_get_fan_type ( int fan )
{
/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
2016-06-18 01:54:48 +03:00
static int types [ 3 ] = { INT_MIN , INT_MIN , INT_MIN } ;
2016-06-18 01:54:47 +03:00
if ( types [ fan ] = = INT_MIN )
types [ fan ] = _i8k_get_fan_type ( fan ) ;
return types [ fan ] ;
}
2015-01-12 16:32:03 +03:00
/*
* Read the fan nominal rpm for specific fan speed .
*/
static int i8k_get_fan_nominal_speed ( int fan , int speed )
{
struct smm_regs regs = { . eax = I8K_SMM_GET_NOM_SPEED , } ;
2018-01-27 19:28:34 +03:00
if ( disallow_fan_support )
return - EINVAL ;
2015-01-12 16:32:03 +03:00
regs . ebx = ( fan & 0xff ) | ( speed < < 8 ) ;
return i8k_smm ( & regs ) ? : ( regs . eax & 0xffff ) * i8k_fan_mult ;
}
2019-11-22 13:15:18 +03:00
/*
* Enable or disable automatic BIOS fan control support
*/
static int i8k_enable_fan_auto_mode ( bool enable )
{
struct smm_regs regs = { } ;
if ( disallow_fan_support )
return - EINVAL ;
regs . eax = enable ? auto_fan : manual_fan ;
return i8k_smm ( & regs ) ;
}
2005-04-17 02:20:36 +04:00
/*
* Set the fan speed ( off , low , high ) . Returns the new fan status .
*/
static int i8k_set_fan ( int fan , int speed )
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_SET_FAN , } ;
2005-04-17 02:20:36 +04:00
2018-01-27 19:28:34 +03:00
if ( disallow_fan_support )
return - EINVAL ;
2014-06-21 19:08:10 +04:00
speed = ( speed < 0 ) ? 0 : ( ( speed > i8k_fan_max ) ? i8k_fan_max : speed ) ;
2005-06-26 01:54:23 +04:00
regs . ebx = ( fan & 0xff ) | ( speed < < 8 ) ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:27 +04:00
return i8k_smm ( & regs ) ? : i8k_get_fan_status ( fan ) ;
2005-04-17 02:20:36 +04:00
}
2015-01-12 16:31:57 +03:00
static int i8k_get_temp_type ( int sensor )
{
struct smm_regs regs = { . eax = I8K_SMM_GET_TEMP_TYPE , } ;
regs . ebx = sensor & 0xff ;
return i8k_smm ( & regs ) ? : regs . eax & 0xff ;
}
2005-04-17 02:20:36 +04:00
/*
* Read the cpu temperature .
*/
2015-01-12 16:32:00 +03:00
static int _i8k_get_temp ( int sensor )
2005-04-17 02:20:36 +04:00
{
2015-01-12 16:32:00 +03:00
struct smm_regs regs = {
. eax = I8K_SMM_GET_TEMP ,
. ebx = sensor & 0xff ,
} ;
2005-04-17 02:20:36 +04:00
2015-01-12 16:32:00 +03:00
return i8k_smm ( & regs ) ? : regs . eax & 0xff ;
}
2005-06-26 01:54:27 +04:00
2015-01-12 16:32:00 +03:00
static int i8k_get_temp ( int sensor )
{
int temp = _i8k_get_temp ( sensor ) ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:23 +04:00
/*
* Sometimes the temperature sensor returns 0x99 , which is out of range .
2015-01-12 16:32:00 +03:00
* In this case we retry ( once ) before returning an error .
2005-06-26 01:54:23 +04:00
# 1003655137 00000058 00005a4b
# 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
# 1003655139 00000054 00005c52
*/
2015-01-12 16:32:00 +03:00
if ( temp = = 0x99 ) {
msleep ( 100 ) ;
temp = _i8k_get_temp ( sensor ) ;
2005-06-26 01:54:23 +04:00
}
2015-01-12 16:32:00 +03:00
/*
* Return - ENODATA for all invalid temperatures .
*
* Known instances are the 0x99 value as seen above as well as
* 0xc1 ( 193 ) , which may be returned when trying to read the GPU
* temperature if the system supports a GPU and it is currently
* turned off .
*/
2014-11-18 17:56:54 +03:00
if ( temp > I8K_MAX_TEMP )
2015-01-12 16:31:59 +03:00
return - ENODATA ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:23 +04:00
return temp ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:28 +04:00
static int i8k_get_dell_signature ( int req_fn )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:28 +04:00
struct smm_regs regs = { . eax = req_fn , } ;
2005-06-26 01:54:23 +04:00
int rc ;
2005-04-17 02:20:36 +04:00
2013-12-14 21:30:09 +04:00
rc = i8k_smm ( & regs ) ;
if ( rc < 0 )
2005-06-26 01:54:23 +04:00
return rc ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:27 +04:00
return regs . eax = = 1145651527 & & regs . edx = = 1145392204 ? 0 : - 1 ;
2005-04-17 02:20:36 +04:00
}
2015-05-14 14:16:37 +03:00
# if IS_ENABLED(CONFIG_I8K)
/*
* Read the Fn key status .
*/
static int i8k_get_fn_status ( void )
{
struct smm_regs regs = { . eax = I8K_SMM_FN_STATUS , } ;
int rc ;
rc = i8k_smm ( & regs ) ;
if ( rc < 0 )
return rc ;
switch ( ( regs . eax > > I8K_FN_SHIFT ) & I8K_FN_MASK ) {
case I8K_FN_UP :
return I8K_VOL_UP ;
case I8K_FN_DOWN :
return I8K_VOL_DOWN ;
case I8K_FN_MUTE :
return I8K_VOL_MUTE ;
default :
return 0 ;
}
}
/*
* Read the power status .
*/
static int i8k_get_power_status ( void )
{
struct smm_regs regs = { . eax = I8K_SMM_POWER_STATUS , } ;
int rc ;
rc = i8k_smm ( & regs ) ;
if ( rc < 0 )
return rc ;
return ( regs . eax & 0xff ) = = I8K_POWER_AC ? I8K_AC : I8K_BATTERY ;
}
/*
* Procfs interface
*/
2010-03-30 09:27:50 +04:00
static int
i8k_ioctl_unlocked ( struct file * fp , unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:25 +04:00
int val = 0 ;
2005-06-26 01:54:23 +04:00
int speed ;
unsigned char buff [ 16 ] ;
int __user * argp = ( int __user * ) arg ;
if ( ! argp )
return - EINVAL ;
switch ( cmd ) {
case I8K_BIOS_VERSION :
2016-06-18 01:54:44 +03:00
if ( ! isdigit ( bios_version [ 0 ] ) | | ! isdigit ( bios_version [ 1 ] ) | |
! isdigit ( bios_version [ 2 ] ) )
return - EINVAL ;
2013-12-14 21:30:20 +04:00
val = ( bios_version [ 0 ] < < 16 ) |
( bios_version [ 1 ] < < 8 ) | bios_version [ 2 ] ;
2005-06-26 01:54:23 +04:00
break ;
case I8K_MACHINE_ID :
2016-06-18 01:54:45 +03:00
if ( restricted & & ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
memset ( buff , 0 , sizeof ( buff ) ) ;
strlcpy ( buff , bios_machineid , sizeof ( buff ) ) ;
2005-06-26 01:54:23 +04:00
break ;
case I8K_FN_STATUS :
val = i8k_get_fn_status ( ) ;
break ;
case I8K_POWER_STATUS :
val = i8k_get_power_status ( ) ;
break ;
case I8K_GET_TEMP :
2005-06-26 01:54:28 +04:00
val = i8k_get_temp ( 0 ) ;
2005-06-26 01:54:23 +04:00
break ;
case I8K_GET_SPEED :
2005-06-26 01:54:27 +04:00
if ( copy_from_user ( & val , argp , sizeof ( int ) ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
val = i8k_get_fan_speed ( val ) ;
break ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:23 +04:00
case I8K_GET_FAN :
2005-06-26 01:54:27 +04:00
if ( copy_from_user ( & val , argp , sizeof ( int ) ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
val = i8k_get_fan_status ( val ) ;
break ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:23 +04:00
case I8K_SET_FAN :
2005-06-26 01:54:27 +04:00
if ( restricted & & ! capable ( CAP_SYS_ADMIN ) )
2005-06-26 01:54:23 +04:00
return - EPERM ;
2005-06-26 01:54:27 +04:00
if ( copy_from_user ( & val , argp , sizeof ( int ) ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
if ( copy_from_user ( & speed , argp + 1 , sizeof ( int ) ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
val = i8k_set_fan ( val , speed ) ;
break ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:23 +04:00
default :
return - EINVAL ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:27 +04:00
if ( val < 0 )
2005-06-26 01:54:23 +04:00
return val ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:23 +04:00
switch ( cmd ) {
case I8K_BIOS_VERSION :
2005-06-26 01:54:27 +04:00
if ( copy_to_user ( argp , & val , 4 ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
break ;
case I8K_MACHINE_ID :
2005-06-26 01:54:27 +04:00
if ( copy_to_user ( argp , buff , 16 ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
break ;
default :
2005-06-26 01:54:27 +04:00
if ( copy_to_user ( argp , & val , sizeof ( int ) ) )
2005-06-26 01:54:23 +04:00
return - EFAULT ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
break ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:23 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2010-03-30 09:27:50 +04:00
static long i8k_ioctl ( struct file * fp , unsigned int cmd , unsigned long arg )
{
long ret ;
2010-06-02 16:28:52 +04:00
mutex_lock ( & i8k_mutex ) ;
2010-03-30 09:27:50 +04:00
ret = i8k_ioctl_unlocked ( fp , cmd , arg ) ;
2010-06-02 16:28:52 +04:00
mutex_unlock ( & i8k_mutex ) ;
2010-03-30 09:27:50 +04:00
return ret ;
}
2005-04-17 02:20:36 +04:00
/*
* Print the information for / proc / i8k .
*/
2005-06-26 01:54:26 +04:00
static int i8k_proc_show ( struct seq_file * seq , void * offset )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:26 +04:00
int fn_key , cpu_temp , ac_power ;
2005-06-26 01:54:23 +04:00
int left_fan , right_fan , left_speed , right_speed ;
2007-10-20 01:21:04 +04:00
cpu_temp = i8k_get_temp ( 0 ) ; /* 11100 µs */
left_fan = i8k_get_fan_status ( I8K_FAN_LEFT ) ; /* 580 µs */
right_fan = i8k_get_fan_status ( I8K_FAN_RIGHT ) ; /* 580 µs */
left_speed = i8k_get_fan_speed ( I8K_FAN_LEFT ) ; /* 580 µs */
right_speed = i8k_get_fan_speed ( I8K_FAN_RIGHT ) ; /* 580 µs */
fn_key = i8k_get_fn_status ( ) ; /* 750 µs */
2005-06-26 01:54:27 +04:00
if ( power_status )
2007-10-20 01:21:04 +04:00
ac_power = i8k_get_power_status ( ) ; /* 14700 µs */
2005-06-26 01:54:27 +04:00
else
2005-06-26 01:54:23 +04:00
ac_power = - 1 ;
/*
* Info :
*
* 1 ) Format version ( this will change if format changes )
* 2 ) BIOS version
* 3 ) BIOS machine ID
* 4 ) Cpu temperature
* 5 ) Left fan status
* 6 ) Right fan status
* 7 ) Left fan speed
* 8 ) Right fan speed
* 9 ) AC power
* 10 ) Fn Key status
*/
2015-02-22 05:53:47 +03:00
seq_printf ( seq , " %s %s %s %d %d %d %d %d %d %d \n " ,
I8K_PROC_FMT ,
bios_version ,
2016-06-18 01:54:45 +03:00
( restricted & & ! capable ( CAP_SYS_ADMIN ) ) ? " -1 " : bios_machineid ,
2015-02-22 05:53:47 +03:00
cpu_temp ,
left_fan , right_fan , left_speed , right_speed ,
ac_power , fn_key ) ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:26 +04:00
static int i8k_open_fs ( struct inode * inode , struct file * file )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:26 +04:00
return single_open ( file , i8k_proc_show , NULL ) ;
2005-04-17 02:20:36 +04:00
}
2020-02-04 04:37:17 +03:00
static const struct proc_ops i8k_proc_ops = {
. proc_open = i8k_open_fs ,
. proc_read = seq_read ,
. proc_lseek = seq_lseek ,
. proc_release = single_release ,
. proc_ioctl = i8k_ioctl ,
2015-05-14 14:16:37 +03:00
} ;
static void __init i8k_init_procfs ( void )
{
/* Register the proc entry */
2020-02-04 04:37:17 +03:00
proc_create ( " i8k " , 0 , NULL , & i8k_proc_ops ) ;
2015-05-14 14:16:37 +03:00
}
static void __exit i8k_exit_procfs ( void )
{
remove_proc_entry ( " i8k " , NULL ) ;
}
# else
static inline void __init i8k_init_procfs ( void )
{
}
static inline void __exit i8k_exit_procfs ( void )
{
}
# endif
2011-05-25 22:43:33 +04:00
/*
* Hwmon interface
*/
2018-12-11 01:02:04 +03:00
static ssize_t i8k_hwmon_temp_label_show ( struct device * dev ,
2015-01-12 16:31:57 +03:00
struct device_attribute * devattr ,
char * buf )
{
static const char * const labels [ ] = {
" CPU " ,
" GPU " ,
" SODIMM " ,
" Other " ,
" Ambient " ,
" Other " ,
} ;
int index = to_sensor_dev_attr ( devattr ) - > index ;
int type ;
type = i8k_get_temp_type ( index ) ;
if ( type < 0 )
return type ;
if ( type > = ARRAY_SIZE ( labels ) )
type = ARRAY_SIZE ( labels ) - 1 ;
return sprintf ( buf , " %s \n " , labels [ type ] ) ;
}
2018-12-11 01:02:04 +03:00
static ssize_t i8k_hwmon_temp_show ( struct device * dev ,
2011-05-25 22:43:33 +04:00
struct device_attribute * devattr ,
char * buf )
{
2013-12-14 21:30:11 +04:00
int index = to_sensor_dev_attr ( devattr ) - > index ;
int temp ;
2011-05-25 22:43:33 +04:00
2013-12-14 21:30:11 +04:00
temp = i8k_get_temp ( index ) ;
if ( temp < 0 )
return temp ;
return sprintf ( buf , " %d \n " , temp * 1000 ) ;
2011-05-25 22:43:33 +04:00
}
2018-12-11 01:02:04 +03:00
static ssize_t i8k_hwmon_fan_label_show ( struct device * dev ,
2015-01-12 16:32:05 +03:00
struct device_attribute * devattr ,
char * buf )
{
static const char * const labels [ ] = {
" Processor Fan " ,
" Motherboard Fan " ,
" Video Fan " ,
" Power Supply Fan " ,
" Chipset Fan " ,
" Other Fan " ,
} ;
int index = to_sensor_dev_attr ( devattr ) - > index ;
bool dock = false ;
int type ;
type = i8k_get_fan_type ( index ) ;
if ( type < 0 )
return type ;
if ( type & 0x10 ) {
dock = true ;
type & = 0x0F ;
}
if ( type > = ARRAY_SIZE ( labels ) )
type = ( ARRAY_SIZE ( labels ) - 1 ) ;
return sprintf ( buf , " %s%s \n " , ( dock ? " Docking " : " " ) , labels [ type ] ) ;
}
2018-12-11 01:02:04 +03:00
static ssize_t i8k_hwmon_fan_show ( struct device * dev ,
struct device_attribute * devattr , char * buf )
2011-05-25 22:43:33 +04:00
{
int index = to_sensor_dev_attr ( devattr ) - > index ;
int fan_speed ;
fan_speed = i8k_get_fan_speed ( index ) ;
if ( fan_speed < 0 )
return fan_speed ;
return sprintf ( buf , " %d \n " , fan_speed ) ;
}
2018-12-11 01:02:04 +03:00
static ssize_t i8k_hwmon_pwm_show ( struct device * dev ,
struct device_attribute * devattr , char * buf )
2013-12-14 21:30:21 +04:00
{
int index = to_sensor_dev_attr ( devattr ) - > index ;
int status ;
status = i8k_get_fan_status ( index ) ;
if ( status < 0 )
return - EIO ;
2014-06-21 19:08:10 +04:00
return sprintf ( buf , " %d \n " , clamp_val ( status * i8k_pwm_mult , 0 , 255 ) ) ;
2013-12-14 21:30:21 +04:00
}
2018-12-11 01:02:04 +03:00
static ssize_t i8k_hwmon_pwm_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
2013-12-14 21:30:21 +04:00
{
int index = to_sensor_dev_attr ( attr ) - > index ;
unsigned long val ;
int err ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
2014-06-21 19:08:10 +04:00
val = clamp_val ( DIV_ROUND_CLOSEST ( val , i8k_pwm_mult ) , 0 , i8k_fan_max ) ;
2013-12-14 21:30:21 +04:00
mutex_lock ( & i8k_mutex ) ;
err = i8k_set_fan ( index , val ) ;
mutex_unlock ( & i8k_mutex ) ;
return err < 0 ? - EIO : count ;
}
2019-11-22 13:15:18 +03:00
static ssize_t i8k_hwmon_pwm_enable_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buf , size_t count )
{
int err ;
bool enable ;
unsigned long val ;
if ( ! auto_fan )
return - ENODEV ;
err = kstrtoul ( buf , 10 , & val ) ;
if ( err )
return err ;
if ( val = = 1 )
enable = false ;
else if ( val = = 2 )
enable = true ;
else
return - EINVAL ;
mutex_lock ( & i8k_mutex ) ;
err = i8k_enable_fan_auto_mode ( enable ) ;
mutex_unlock ( & i8k_mutex ) ;
return err ? err : count ;
}
2018-12-11 01:02:04 +03:00
static SENSOR_DEVICE_ATTR_RO ( temp1_input , i8k_hwmon_temp , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp1_label , i8k_hwmon_temp_label , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp2_input , i8k_hwmon_temp , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp2_label , i8k_hwmon_temp_label , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp3_input , i8k_hwmon_temp , 2 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp3_label , i8k_hwmon_temp_label , 2 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp4_input , i8k_hwmon_temp , 3 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp4_label , i8k_hwmon_temp_label , 3 ) ;
2018-12-24 19:15:00 +03:00
static SENSOR_DEVICE_ATTR_RO ( temp5_input , i8k_hwmon_temp , 4 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp5_label , i8k_hwmon_temp_label , 4 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp6_input , i8k_hwmon_temp , 5 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp6_label , i8k_hwmon_temp_label , 5 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp7_input , i8k_hwmon_temp , 6 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp7_label , i8k_hwmon_temp_label , 6 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp8_input , i8k_hwmon_temp , 7 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp8_label , i8k_hwmon_temp_label , 7 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp9_input , i8k_hwmon_temp , 8 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp9_label , i8k_hwmon_temp_label , 8 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp10_input , i8k_hwmon_temp , 9 ) ;
static SENSOR_DEVICE_ATTR_RO ( temp10_label , i8k_hwmon_temp_label , 9 ) ;
2018-12-11 01:02:04 +03:00
static SENSOR_DEVICE_ATTR_RO ( fan1_input , i8k_hwmon_fan , 0 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan1_label , i8k_hwmon_fan_label , 0 ) ;
static SENSOR_DEVICE_ATTR_RW ( pwm1 , i8k_hwmon_pwm , 0 ) ;
2019-11-22 13:15:18 +03:00
static SENSOR_DEVICE_ATTR_WO ( pwm1_enable , i8k_hwmon_pwm_enable , 0 ) ;
2018-12-11 01:02:04 +03:00
static SENSOR_DEVICE_ATTR_RO ( fan2_input , i8k_hwmon_fan , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan2_label , i8k_hwmon_fan_label , 1 ) ;
static SENSOR_DEVICE_ATTR_RW ( pwm2 , i8k_hwmon_pwm , 1 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan3_input , i8k_hwmon_fan , 2 ) ;
static SENSOR_DEVICE_ATTR_RO ( fan3_label , i8k_hwmon_fan_label , 2 ) ;
static SENSOR_DEVICE_ATTR_RW ( pwm3 , i8k_hwmon_pwm , 2 ) ;
2013-12-14 21:30:10 +04:00
static struct attribute * i8k_attrs [ ] = {
2013-12-14 21:30:11 +04:00
& sensor_dev_attr_temp1_input . dev_attr . attr , /* 0 */
2015-01-12 16:31:57 +03:00
& sensor_dev_attr_temp1_label . dev_attr . attr , /* 1 */
& sensor_dev_attr_temp2_input . dev_attr . attr , /* 2 */
& sensor_dev_attr_temp2_label . dev_attr . attr , /* 3 */
& sensor_dev_attr_temp3_input . dev_attr . attr , /* 4 */
& sensor_dev_attr_temp3_label . dev_attr . attr , /* 5 */
& sensor_dev_attr_temp4_input . dev_attr . attr , /* 6 */
& sensor_dev_attr_temp4_label . dev_attr . attr , /* 7 */
2018-12-24 19:15:00 +03:00
& sensor_dev_attr_temp5_input . dev_attr . attr , /* 8 */
& sensor_dev_attr_temp5_label . dev_attr . attr , /* 9 */
& sensor_dev_attr_temp6_input . dev_attr . attr , /* 10 */
& sensor_dev_attr_temp6_label . dev_attr . attr , /* 11 */
& sensor_dev_attr_temp7_input . dev_attr . attr , /* 12 */
& sensor_dev_attr_temp7_label . dev_attr . attr , /* 13 */
& sensor_dev_attr_temp8_input . dev_attr . attr , /* 14 */
& sensor_dev_attr_temp8_label . dev_attr . attr , /* 15 */
& sensor_dev_attr_temp9_input . dev_attr . attr , /* 16 */
& sensor_dev_attr_temp9_label . dev_attr . attr , /* 17 */
& sensor_dev_attr_temp10_input . dev_attr . attr , /* 18 */
& sensor_dev_attr_temp10_label . dev_attr . attr , /* 19 */
& sensor_dev_attr_fan1_input . dev_attr . attr , /* 20 */
& sensor_dev_attr_fan1_label . dev_attr . attr , /* 21 */
& sensor_dev_attr_pwm1 . dev_attr . attr , /* 22 */
2019-11-22 13:15:18 +03:00
& sensor_dev_attr_pwm1_enable . dev_attr . attr , /* 23 */
& sensor_dev_attr_fan2_input . dev_attr . attr , /* 24 */
& sensor_dev_attr_fan2_label . dev_attr . attr , /* 25 */
& sensor_dev_attr_pwm2 . dev_attr . attr , /* 26 */
& sensor_dev_attr_fan3_input . dev_attr . attr , /* 27 */
& sensor_dev_attr_fan3_label . dev_attr . attr , /* 28 */
& sensor_dev_attr_pwm3 . dev_attr . attr , /* 29 */
2013-12-14 21:30:10 +04:00
NULL
} ;
2011-05-25 22:43:33 +04:00
2013-12-14 21:30:10 +04:00
static umode_t i8k_is_visible ( struct kobject * kobj , struct attribute * attr ,
int index )
2011-05-25 22:43:33 +04:00
{
2021-05-13 18:45:46 +03:00
if ( disallow_fan_support & & index > = 20 )
2018-01-27 19:28:34 +03:00
return 0 ;
2016-06-18 01:54:46 +03:00
if ( disallow_fan_type_call & &
2021-05-13 18:45:46 +03:00
( index = = 21 | | index = = 25 | | index = = 28 ) )
2016-06-18 01:54:46 +03:00
return 0 ;
2015-01-12 16:31:57 +03:00
if ( index > = 0 & & index < = 1 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1 ) )
2013-12-14 21:30:10 +04:00
return 0 ;
2015-01-12 16:31:57 +03:00
if ( index > = 2 & & index < = 3 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2 ) )
2013-12-14 21:30:11 +04:00
return 0 ;
2015-01-12 16:31:57 +03:00
if ( index > = 4 & & index < = 5 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3 ) )
2013-12-14 21:30:11 +04:00
return 0 ;
2015-01-12 16:31:57 +03:00
if ( index > = 6 & & index < = 7 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4 ) )
2013-12-14 21:30:11 +04:00
return 0 ;
2018-12-24 19:15:00 +03:00
if ( index > = 8 & & index < = 9 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5 ) )
return 0 ;
if ( index > = 10 & & index < = 11 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6 ) )
return 0 ;
if ( index > = 12 & & index < = 13 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7 ) )
return 0 ;
if ( index > = 14 & & index < = 15 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8 ) )
return 0 ;
if ( index > = 16 & & index < = 17 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9 ) )
return 0 ;
if ( index > = 18 & & index < = 19 & &
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10 ) )
return 0 ;
2019-11-22 13:15:18 +03:00
if ( index > = 20 & & index < = 23 & &
2013-12-14 21:30:10 +04:00
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1 ) )
return 0 ;
2019-11-22 13:15:18 +03:00
if ( index > = 24 & & index < = 26 & &
2013-12-14 21:30:10 +04:00
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2 ) )
return 0 ;
2019-11-22 13:15:18 +03:00
if ( index > = 27 & & index < = 29 & &
2016-06-18 01:54:48 +03:00
! ( i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3 ) )
return 0 ;
2013-12-14 21:30:10 +04:00
2019-11-22 13:15:18 +03:00
if ( index = = 23 & & ! auto_fan )
return 0 ;
2013-12-14 21:30:10 +04:00
return attr - > mode ;
2011-05-25 22:43:33 +04:00
}
2013-12-14 21:30:10 +04:00
static const struct attribute_group i8k_group = {
. attrs = i8k_attrs ,
. is_visible = i8k_is_visible ,
} ;
__ATTRIBUTE_GROUPS ( i8k ) ;
2021-07-29 01:15:52 +03:00
static int __init dell_smm_init_hwmon ( struct device * dev )
2011-05-25 22:43:33 +04:00
{
int err ;
2013-12-14 21:30:10 +04:00
i8k_hwmon_flags = 0 ;
2011-05-25 22:43:33 +04:00
2015-01-12 16:31:58 +03:00
/* CPU temperature attributes, if temperature type is OK */
err = i8k_get_temp_type ( 0 ) ;
if ( err > = 0 )
2013-12-14 21:30:10 +04:00
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP1 ;
2013-12-14 21:30:11 +04:00
/* check for additional temperature sensors */
2015-01-12 16:31:58 +03:00
err = i8k_get_temp_type ( 1 ) ;
if ( err > = 0 )
2013-12-14 21:30:11 +04:00
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP2 ;
2015-01-12 16:31:58 +03:00
err = i8k_get_temp_type ( 2 ) ;
if ( err > = 0 )
2013-12-14 21:30:11 +04:00
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP3 ;
2015-01-12 16:31:58 +03:00
err = i8k_get_temp_type ( 3 ) ;
if ( err > = 0 )
2013-12-14 21:30:11 +04:00
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP4 ;
2018-12-24 19:15:00 +03:00
err = i8k_get_temp_type ( 4 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP5 ;
err = i8k_get_temp_type ( 5 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP6 ;
err = i8k_get_temp_type ( 6 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP7 ;
err = i8k_get_temp_type ( 7 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP8 ;
err = i8k_get_temp_type ( 8 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP9 ;
err = i8k_get_temp_type ( 9 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_TEMP10 ;
2011-05-25 22:43:33 +04:00
2016-06-18 01:54:47 +03:00
/* First fan attributes, if fan status or type is OK */
err = i8k_get_fan_status ( 0 ) ;
if ( err < 0 )
err = i8k_get_fan_type ( 0 ) ;
2013-12-14 21:30:10 +04:00
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_FAN1 ;
2011-05-25 22:43:33 +04:00
2016-06-18 01:54:47 +03:00
/* Second fan attributes, if fan status or type is OK */
err = i8k_get_fan_status ( 1 ) ;
if ( err < 0 )
err = i8k_get_fan_type ( 1 ) ;
2013-12-14 21:30:10 +04:00
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_FAN2 ;
2011-05-25 22:43:33 +04:00
2016-06-18 01:54:48 +03:00
/* Third fan attributes, if fan status or type is OK */
err = i8k_get_fan_status ( 2 ) ;
if ( err < 0 )
err = i8k_get_fan_type ( 2 ) ;
if ( err > = 0 )
i8k_hwmon_flags | = I8K_HWMON_HAVE_FAN3 ;
2021-07-29 01:15:52 +03:00
i8k_hwmon_dev = devm_hwmon_device_register_with_groups ( dev , " dell_smm " , NULL , i8k_groups ) ;
return PTR_ERR_OR_ZERO ( i8k_hwmon_dev ) ;
2011-05-25 22:43:33 +04:00
}
2014-06-21 19:08:10 +04:00
struct i8k_config_data {
2015-01-12 16:32:02 +03:00
uint fan_mult ;
uint fan_max ;
2014-06-21 19:08:10 +04:00
} ;
enum i8k_configs {
2014-06-21 19:08:12 +04:00
DELL_LATITUDE_D520 ,
DELL_PRECISION_490 ,
2014-06-21 19:08:10 +04:00
DELL_STUDIO ,
2015-01-12 16:32:01 +03:00
DELL_XPS ,
2014-06-21 19:08:10 +04:00
} ;
static const struct i8k_config_data i8k_config_data [ ] = {
2014-06-21 19:08:12 +04:00
[ DELL_LATITUDE_D520 ] = {
. fan_mult = 1 ,
. fan_max = I8K_FAN_TURBO ,
} ,
[ DELL_PRECISION_490 ] = {
. fan_mult = 1 ,
. fan_max = I8K_FAN_TURBO ,
} ,
2014-06-21 19:08:10 +04:00
[ DELL_STUDIO ] = {
. fan_mult = 1 ,
. fan_max = I8K_FAN_HIGH ,
} ,
2015-01-12 16:32:01 +03:00
[ DELL_XPS ] = {
2014-06-21 19:08:10 +04:00
. fan_mult = 1 ,
. fan_max = I8K_FAN_HIGH ,
} ,
} ;
2017-09-14 12:59:30 +03:00
static const struct dmi_system_id i8k_dmi_table [ ] __initconst = {
2005-06-26 01:54:25 +04:00
{
. ident = " Dell Inspiron " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Computer " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron " ) ,
} ,
} ,
{
. ident = " Dell Latitude " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Computer " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Latitude " ) ,
} ,
} ,
2005-06-26 01:54:28 +04:00
{
. ident = " Dell Inspiron 2 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Inspiron " ) ,
} ,
} ,
2014-06-21 19:08:12 +04:00
{
. ident = " Dell Latitude D520 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Latitude D520 " ) ,
} ,
. driver_data = ( void * ) & i8k_config_data [ DELL_LATITUDE_D520 ] ,
} ,
2005-06-26 01:54:28 +04:00
{
. ident = " Dell Latitude 2 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Latitude " ) ,
} ,
} ,
2008-02-06 12:37:47 +03:00
{ /* UK Inspiron 6400 */
. ident = " Dell Inspiron 3 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " MM061 " ) ,
} ,
} ,
2008-02-07 11:16:31 +03:00
{
. ident = " Dell Inspiron 3 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " MP061 " ) ,
} ,
} ,
2014-06-21 19:08:12 +04:00
{
. ident = " Dell Precision 490 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME ,
" Precision WorkStation 490 " ) ,
} ,
. driver_data = ( void * ) & i8k_config_data [ DELL_PRECISION_490 ] ,
} ,
2009-01-02 19:19:13 +03:00
{
. ident = " Dell Precision " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Precision " ) ,
} ,
} ,
2009-01-02 19:19:23 +03:00
{
. ident = " Dell Vostro " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Vostro " ) ,
} ,
} ,
2013-12-14 21:30:17 +04:00
{
. ident = " Dell Studio " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " Studio " ) ,
} ,
2014-06-21 19:08:10 +04:00
. driver_data = ( void * ) & i8k_config_data [ DELL_STUDIO ] ,
2013-12-14 21:30:17 +04:00
} ,
2013-12-14 21:30:18 +04:00
{
. ident = " Dell XPS M140 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " MXC051 " ) ,
} ,
2015-01-12 16:32:01 +03:00
. driver_data = ( void * ) & i8k_config_data [ DELL_XPS ] ,
2013-12-14 21:30:18 +04:00
} ,
2017-03-04 01:41:52 +03:00
{
2020-04-04 23:49:00 +03:00
. ident = " Dell XPS " ,
2018-11-30 21:42:56 +03:00
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
2020-04-04 23:49:00 +03:00
DMI_MATCH ( DMI_PRODUCT_NAME , " XPS " ) ,
2018-11-30 21:42:56 +03:00
} ,
} ,
2013-12-14 21:30:09 +04:00
{ }
2005-06-26 01:54:25 +04:00
} ;
2005-04-17 02:20:36 +04:00
2014-10-18 02:17:49 +04:00
MODULE_DEVICE_TABLE ( dmi , i8k_dmi_table ) ;
2016-06-18 01:54:46 +03:00
/*
* On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
* randomly going up and down due to bug in Dell SMM or BIOS . Here is blacklist
* of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call .
* See bug : https : //bugzilla.kernel.org/show_bug.cgi?id=100121
*/
2017-09-14 12:59:30 +03:00
static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table [ ] __initconst = {
2016-01-17 18:03:04 +03:00
{
. ident = " Dell Studio XPS 8000 " ,
. matches = {
DMI_EXACT_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Studio XPS 8000 " ) ,
} ,
} ,
2015-07-30 21:41:57 +03:00
{
. ident = " Dell Studio XPS 8100 " ,
. matches = {
DMI_EXACT_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Studio XPS 8100 " ) ,
} ,
} ,
2016-06-18 01:54:46 +03:00
{
. ident = " Dell Inspiron 580 " ,
. matches = {
DMI_EXACT_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Inspiron 580 " ) ,
} ,
} ,
2015-07-30 21:41:57 +03:00
{ }
} ;
2018-01-27 19:28:34 +03:00
/*
* On some machines all fan related SMM functions implemented by Dell BIOS
* firmware freeze kernel for about 500 ms . Until Dell fixes these problems fan
* support for affected blacklisted Dell machines stay disabled .
* See bug : https : //bugzilla.kernel.org/show_bug.cgi?id=195751
*/
static struct dmi_system_id i8k_blacklist_fan_support_dmi_table [ ] __initdata = {
{
. ident = " Dell Inspiron 7720 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Inspiron 7720 " ) ,
} ,
} ,
2018-01-27 19:23:29 +03:00
{
. ident = " Dell Vostro 3360 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Vostro 3360 " ) ,
} ,
} ,
2018-06-05 20:38:32 +03:00
{
. ident = " Dell XPS13 9333 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " XPS13 9333 " ) ,
} ,
} ,
2021-01-24 05:46:08 +03:00
{
. ident = " Dell XPS 15 L502X " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Dell System XPS L502X " ) ,
} ,
} ,
2018-01-27 19:28:34 +03:00
{ }
} ;
2019-11-22 13:15:18 +03:00
struct i8k_fan_control_data {
unsigned int manual_fan ;
unsigned int auto_fan ;
} ;
enum i8k_fan_controls {
I8K_FAN_34A3_35A3 ,
} ;
static const struct i8k_fan_control_data i8k_fan_control_data [ ] = {
[ I8K_FAN_34A3_35A3 ] = {
. manual_fan = 0x34a3 ,
. auto_fan = 0x35a3 ,
} ,
} ;
static struct dmi_system_id i8k_whitelist_fan_control [ ] __initdata = {
{
. ident = " Dell Precision 5530 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Precision 5530 " ) ,
} ,
. driver_data = ( void * ) & i8k_fan_control_data [ I8K_FAN_34A3_35A3 ] ,
} ,
2020-06-21 07:52:48 +03:00
{
. ident = " Dell Latitude 5480 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Latitude 5480 " ) ,
} ,
. driver_data = ( void * ) & i8k_fan_control_data [ I8K_FAN_34A3_35A3 ] ,
} ,
2019-11-22 13:15:18 +03:00
{
. ident = " Dell Latitude E6440 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Latitude E6440 " ) ,
} ,
. driver_data = ( void * ) & i8k_fan_control_data [ I8K_FAN_34A3_35A3 ] ,
} ,
2021-04-11 12:57:41 +03:00
{
. ident = " Dell Latitude E7440 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Latitude E7440 " ) ,
} ,
. driver_data = ( void * ) & i8k_fan_control_data [ I8K_FAN_34A3_35A3 ] ,
} ,
2019-11-22 13:15:18 +03:00
{ }
} ;
2021-07-29 01:15:52 +03:00
static int __init dell_smm_probe ( struct platform_device * pdev )
2005-04-17 02:20:36 +04:00
{
2019-11-22 13:15:18 +03:00
const struct dmi_system_id * id , * fan_control ;
2015-01-12 16:32:03 +03:00
int fan , ret ;
2005-06-26 01:54:23 +04:00
2018-01-27 19:28:34 +03:00
if ( dmi_check_system ( i8k_blacklist_fan_support_dmi_table ) ) {
pr_warn ( " broken Dell BIOS detected, disallow fan support \n " ) ;
if ( ! force )
disallow_fan_support = true ;
}
2018-01-27 19:22:01 +03:00
if ( dmi_check_system ( i8k_blacklist_fan_type_dmi_table ) ) {
pr_warn ( " broken Dell BIOS detected, disallow fan type call \n " ) ;
if ( ! force )
disallow_fan_type_call = true ;
}
2016-06-18 01:54:46 +03:00
2021-07-29 01:15:52 +03:00
strscpy ( bios_version , i8k_get_dmi_data ( DMI_BIOS_VERSION ) ,
2013-12-14 21:30:09 +04:00
sizeof ( bios_version ) ) ;
2021-07-29 01:15:52 +03:00
strscpy ( bios_machineid , i8k_get_dmi_data ( DMI_PRODUCT_SERIAL ) ,
2016-06-18 01:54:45 +03:00
sizeof ( bios_machineid ) ) ;
2005-06-26 01:54:25 +04:00
2015-01-12 16:32:03 +03:00
/*
* Set fan multiplier and maximal fan speed from dmi config
* Values specified in module parameters override values from dmi
*/
2013-12-14 21:30:19 +04:00
id = dmi_first_match ( i8k_dmi_table ) ;
2014-06-21 19:08:10 +04:00
if ( id & & id - > driver_data ) {
const struct i8k_config_data * conf = id - > driver_data ;
2021-07-29 01:15:52 +03:00
2015-01-12 16:32:03 +03:00
if ( ! fan_mult & & conf - > fan_mult )
fan_mult = conf - > fan_mult ;
if ( ! fan_max & & conf - > fan_max )
fan_max = conf - > fan_max ;
2014-06-21 19:08:10 +04:00
}
2015-01-12 16:32:03 +03:00
i8k_fan_max = fan_max ? : I8K_FAN_HIGH ; /* Must not be 0 */
2014-06-21 19:08:10 +04:00
i8k_pwm_mult = DIV_ROUND_UP ( 255 , i8k_fan_max ) ;
2013-12-14 21:30:19 +04:00
2019-11-22 13:15:18 +03:00
fan_control = dmi_first_match ( i8k_whitelist_fan_control ) ;
if ( fan_control & & fan_control - > driver_data ) {
const struct i8k_fan_control_data * data = fan_control - > driver_data ;
manual_fan = data - > manual_fan ;
auto_fan = data - > auto_fan ;
pr_info ( " enabling support for setting automatic/manual fan control \n " ) ;
}
2015-01-12 16:32:03 +03:00
if ( ! fan_mult ) {
/*
* Autodetect fan multiplier based on nominal rpm
* If fan reports rpm value too high then set multiplier to 1
*/
for ( fan = 0 ; fan < 2 ; + + fan ) {
ret = i8k_get_fan_nominal_speed ( fan , i8k_fan_max ) ;
if ( ret < 0 )
continue ;
if ( ret > I8K_FAN_MAX_RPM )
i8k_fan_mult = 1 ;
break ;
}
} else {
/* Fan multiplier was specified in module param or in dmi */
i8k_fan_mult = fan_mult ;
}
2021-07-29 01:15:52 +03:00
ret = dell_smm_init_hwmon ( & pdev - > dev ) ;
if ( ret )
return ret ;
i8k_init_procfs ( ) ;
2005-06-26 01:54:23 +04:00
return 0 ;
2005-04-17 02:20:36 +04:00
}
2021-07-29 01:15:52 +03:00
static int dell_smm_remove ( struct platform_device * pdev )
{
i8k_exit_procfs ( ) ;
return 0 ;
}
static struct platform_driver dell_smm_driver = {
. driver = {
. name = KBUILD_MODNAME ,
} ,
. remove = dell_smm_remove ,
} ;
static struct platform_device * dell_smm_device ;
/*
* Probe for the presence of a supported laptop .
*/
2005-06-26 01:54:27 +04:00
static int __init i8k_init ( void )
2005-04-17 02:20:36 +04:00
{
2021-07-29 01:15:52 +03:00
/*
* Get DMI information
*/
if ( ! dmi_check_system ( i8k_dmi_table ) ) {
if ( ! ignore_dmi & & ! force )
return - ENODEV ;
2005-06-26 01:54:23 +04:00
2021-07-29 01:15:52 +03:00
pr_info ( " not running on a supported Dell system. \n " ) ;
pr_info ( " vendor=%s, model=%s, version=%s \n " ,
i8k_get_dmi_data ( DMI_SYS_VENDOR ) ,
i8k_get_dmi_data ( DMI_PRODUCT_NAME ) ,
i8k_get_dmi_data ( DMI_BIOS_VERSION ) ) ;
}
2005-06-26 01:54:23 +04:00
2021-07-29 01:15:52 +03:00
/*
* Get SMM Dell signature
*/
if ( i8k_get_dell_signature ( I8K_SMM_GET_DELL_SIG1 ) & &
i8k_get_dell_signature ( I8K_SMM_GET_DELL_SIG2 ) ) {
pr_err ( " unable to get SMM Dell signature \n " ) ;
if ( ! force )
return - ENODEV ;
}
2011-05-25 22:43:33 +04:00
2021-07-29 01:15:52 +03:00
dell_smm_device = platform_create_bundle ( & dell_smm_driver , dell_smm_probe , NULL , 0 , NULL ,
0 ) ;
return PTR_ERR_OR_ZERO ( dell_smm_device ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:27 +04:00
static void __exit i8k_exit ( void )
2005-04-17 02:20:36 +04:00
{
2021-07-29 01:15:52 +03:00
platform_device_unregister ( dell_smm_device ) ;
platform_driver_unregister ( & dell_smm_driver ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:27 +04:00
module_init ( i8k_init ) ;
module_exit ( i8k_exit ) ;