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
2021-10-21 22:05:27 +03:00
# include <linux/capability.h>
2016-08-29 09:48:47 +03:00
# include <linux/cpu.h>
2021-10-21 22:05:27 +03:00
# include <linux/ctype.h>
2015-01-12 16:32:00 +03:00
# include <linux/delay.h>
2021-10-21 22:05:27 +03:00
# include <linux/dmi.h>
2021-07-29 01:15:52 +03:00
# include <linux/err.h>
2021-10-21 22:05:29 +03:00
# include <linux/errno.h>
2021-10-21 22:05:27 +03:00
# include <linux/hwmon.h>
# include <linux/init.h>
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
2021-10-21 22:05:27 +03:00
# include <linux/mutex.h>
2021-07-29 01:15:52 +03:00
# include <linux/platform_device.h>
2005-04-17 02:20:36 +04:00
# include <linux/proc_fs.h>
2005-06-26 01:54:26 +04:00
# include <linux/seq_file.h>
2021-10-21 22:05:28 +03:00
# include <linux/string.h>
2016-08-29 09:48:47 +03:00
# include <linux/smp.h>
2021-10-21 22:05:27 +03:00
# include <linux/types.h>
# include <linux/uaccess.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
2021-07-29 01:15:56 +03:00
# define DELL_SMM_NO_TEMP 10
# define DELL_SMM_NO_FANS 3
2021-07-29 01:15:55 +03:00
struct dell_smm_data {
struct mutex i8k_mutex ; /* lock for sensors writes */
char bios_version [ 4 ] ;
char bios_machineid [ 16 ] ;
uint i8k_fan_mult ;
uint i8k_pwm_mult ;
uint i8k_fan_max ;
bool disallow_fan_type_call ;
bool disallow_fan_support ;
unsigned int manual_fan ;
unsigned int auto_fan ;
2021-07-29 01:15:56 +03:00
int temp_type [ DELL_SMM_NO_TEMP ] ;
bool fan [ DELL_SMM_NO_FANS ] ;
int fan_type [ DELL_SMM_NO_FANS ] ;
2021-09-27 01:10:43 +03:00
int * fan_nominal_speed [ DELL_SMM_NO_FANS ] ;
2021-07-29 01:15:55 +03:00
} ;
2013-12-14 21:30:10 +04:00
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 ;
2021-12-21 19:28:05 +03:00
unsigned int ebx ;
unsigned int ecx ;
unsigned int edx ;
unsigned int esi ;
unsigned int edi ;
} __packed ;
2005-04-17 02:20:36 +04:00
2021-07-29 01:15:56 +03:00
static const char * const temp_labels [ ] = {
" CPU " ,
" GPU " ,
" SODIMM " ,
" Other " ,
" Ambient " ,
" Other " ,
} ;
static const char * const fan_labels [ ] = {
" Processor Fan " ,
" Motherboard Fan " ,
" Video Fan " ,
" Power Supply Fan " ,
" Chipset Fan " ,
" Other Fan " ,
} ;
static const char * const docking_labels [ ] = {
" Docking Processor Fan " ,
" Docking Motherboard Fan " ,
" Docking Video Fan " ,
" Docking Power Supply Fan " ,
" Docking Chipset Fan " ,
" Docking Other Fan " ,
} ;
2021-07-29 01:15:53 +03:00
static inline const char __init * 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
{
2021-08-14 22:05:16 +03:00
ktime_t calltime = ktime_get ( ) ;
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 ;
2016-06-18 01:54:49 +03:00
int ebx = regs - > ebx ;
2021-08-14 22:05:16 +03:00
long long duration ;
int rc ;
2016-06-18 01:54:49 +03:00
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
2021-08-14 22:05:16 +03:00
duration = ktime_us_delta ( ktime_get ( ) , calltime ) ;
pr_debug ( " smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lld usecs) \n " , eax , ebx ,
( rc ? 0xffff : regs - > eax & 0xffff ) , duration ) ;
2016-06-18 01:54:49 +03:00
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 ;
2021-08-03 17:15:56 +03:00
cpus_read_lock ( ) ;
2016-08-29 09:48:47 +03:00
ret = smp_call_on_cpu ( 0 , i8k_smm_func , regs , true ) ;
2021-08-03 17:15:56 +03:00
cpus_read_unlock ( ) ;
2016-08-29 09:48:47 +03:00
return ret ;
}
2005-04-17 02:20:36 +04:00
/*
* Read the fan status .
*/
2021-07-29 01:15:55 +03:00
static int i8k_get_fan_status ( const struct dell_smm_data * data , int fan )
2005-04-17 02:20:36 +04:00
{
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
2021-07-29 01:15:55 +03:00
if ( data - > disallow_fan_support )
2018-01-27 19:28:34 +03:00
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 .
*/
2021-07-29 01:15:55 +03:00
static int i8k_get_fan_speed ( const struct dell_smm_data * data , int fan )
2005-04-17 02:20:36 +04:00
{
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
2021-07-29 01:15:55 +03:00
if ( data - > disallow_fan_support )
2018-01-27 19:28:34 +03:00
return - EINVAL ;
2005-06-26 01:54:23 +04:00
regs . ebx = fan & 0xff ;
2021-07-29 01:15:55 +03:00
return i8k_smm ( & regs ) ? : ( regs . eax & 0xffff ) * data - > i8k_fan_mult ;
2005-04-17 02:20:36 +04:00
}
2015-01-12 16:32:05 +03:00
/*
* Read the fan type .
*/
2021-07-29 01:15:55 +03:00
static int _i8k_get_fan_type ( const struct dell_smm_data * data , int fan )
2015-01-12 16:32:05 +03:00
{
struct smm_regs regs = { . eax = I8K_SMM_GET_FAN_TYPE , } ;
2021-07-29 01:15:55 +03:00
if ( data - > disallow_fan_support | | data - > 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 ;
}
2021-07-29 01:15:55 +03:00
static int i8k_get_fan_type ( struct dell_smm_data * data , int fan )
2016-06-18 01:54:47 +03:00
{
/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
2021-07-29 01:15:56 +03:00
if ( data - > fan_type [ fan ] = = INT_MIN )
data - > fan_type [ fan ] = _i8k_get_fan_type ( data , fan ) ;
2016-06-18 01:54:47 +03:00
2021-07-29 01:15:56 +03:00
return data - > fan_type [ fan ] ;
2016-06-18 01:54:47 +03:00
}
2015-01-12 16:32:03 +03:00
/*
* Read the fan nominal rpm for specific fan speed .
*/
2021-08-14 17:36:37 +03:00
static int __init i8k_get_fan_nominal_speed ( const struct dell_smm_data * data , int fan , int speed )
2015-01-12 16:32:03 +03:00
{
struct smm_regs regs = { . eax = I8K_SMM_GET_NOM_SPEED , } ;
2021-07-29 01:15:55 +03:00
if ( data - > disallow_fan_support )
2018-01-27 19:28:34 +03:00
return - EINVAL ;
2015-01-12 16:32:03 +03:00
regs . ebx = ( fan & 0xff ) | ( speed < < 8 ) ;
2021-07-29 01:15:55 +03:00
return i8k_smm ( & regs ) ? : ( regs . eax & 0xffff ) * data - > i8k_fan_mult ;
2015-01-12 16:32:03 +03:00
}
2019-11-22 13:15:18 +03:00
/*
* Enable or disable automatic BIOS fan control support
*/
2021-07-29 01:15:55 +03:00
static int i8k_enable_fan_auto_mode ( const struct dell_smm_data * data , bool enable )
2019-11-22 13:15:18 +03:00
{
struct smm_regs regs = { } ;
2021-07-29 01:15:55 +03:00
if ( data - > disallow_fan_support )
2019-11-22 13:15:18 +03:00
return - EINVAL ;
2021-07-29 01:15:55 +03:00
regs . eax = enable ? data - > auto_fan : data - > manual_fan ;
2019-11-22 13:15:18 +03:00
return i8k_smm ( & regs ) ;
}
2005-04-17 02:20:36 +04:00
/*
2021-10-21 22:05:31 +03:00
* Set the fan speed ( off , low , high , . . . ) .
2005-04-17 02:20:36 +04:00
*/
2021-07-29 01:15:55 +03:00
static int i8k_set_fan ( const struct dell_smm_data * data , int fan , int speed )
2005-04-17 02:20:36 +04:00
{
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
2021-07-29 01:15:55 +03:00
if ( data - > disallow_fan_support )
2018-01-27 19:28:34 +03:00
return - EINVAL ;
2021-07-29 01:15:55 +03:00
speed = ( speed < 0 ) ? 0 : ( ( speed > data - > i8k_fan_max ) ? data - > 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
2021-10-21 22:05:31 +03:00
return i8k_smm ( & regs ) ;
2005-04-17 02:20:36 +04:00
}
2021-07-29 01:15:56 +03:00
static int __init i8k_get_temp_type ( int sensor )
2015-01-12 16:31:57 +03:00
{
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
}
2021-07-29 01:15:53 +03:00
static int __init 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
*/
2021-12-11 18:54:22 +03:00
static long i8k_ioctl ( struct file * fp , unsigned int cmd , unsigned long arg )
2005-04-17 02:20:36 +04:00
{
2022-01-22 09:14:23 +03:00
struct dell_smm_data * data = pde_data ( file_inode ( fp ) ) ;
2005-06-26 01:54:23 +04:00
int __user * argp = ( int __user * ) arg ;
2021-12-11 18:54:22 +03:00
int speed , err ;
int val = 0 ;
2005-06-26 01:54:23 +04:00
if ( ! argp )
return - EINVAL ;
switch ( cmd ) {
case I8K_BIOS_VERSION :
2021-07-29 01:15:55 +03:00
if ( ! isdigit ( data - > bios_version [ 0 ] ) | | ! isdigit ( data - > bios_version [ 1 ] ) | |
! isdigit ( data - > bios_version [ 2 ] ) )
2016-06-18 01:54:44 +03:00
return - EINVAL ;
2021-07-29 01:15:55 +03:00
val = ( data - > bios_version [ 0 ] < < 16 ) |
( data - > bios_version [ 1 ] < < 8 ) | data - > bios_version [ 2 ] ;
2005-06-26 01:54:23 +04:00
2021-12-11 18:54:21 +03:00
if ( copy_to_user ( argp , & val , sizeof ( val ) ) )
return - EFAULT ;
return 0 ;
2005-06-26 01:54:23 +04:00
case I8K_MACHINE_ID :
2016-06-18 01:54:45 +03:00
if ( restricted & & ! capable ( CAP_SYS_ADMIN ) )
return - EPERM ;
2021-12-11 18:54:21 +03:00
if ( copy_to_user ( argp , data - > bios_machineid , sizeof ( data - > bios_machineid ) ) )
return - EFAULT ;
2005-06-26 01:54:23 +04:00
2021-12-11 18:54:21 +03:00
return 0 ;
2005-06-26 01:54:23 +04:00
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
2021-07-29 01:15:55 +03:00
val = i8k_get_fan_speed ( data , val ) ;
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
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
2021-07-29 01:15:55 +03:00
val = i8k_get_fan_status ( data , val ) ;
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
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
2021-12-11 18:54:22 +03:00
mutex_lock ( & data - > i8k_mutex ) ;
2021-10-21 22:05:31 +03:00
err = i8k_set_fan ( data , val , speed ) ;
if ( err < 0 )
2021-12-11 18:54:22 +03:00
val = err ;
else
val = i8k_get_fan_status ( data , val ) ;
mutex_unlock ( & data - > i8k_mutex ) ;
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
default :
2021-10-21 22:05:29 +03:00
return - ENOIOCTLCMD ;
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
2021-12-11 18:54:21 +03:00
if ( copy_to_user ( argp , & val , sizeof ( int ) ) )
return - EFAULT ;
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
}
/*
* 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
{
2021-07-29 01:15:55 +03:00
struct dell_smm_data * data = seq - > private ;
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 ;
2021-07-29 01:15:55 +03:00
cpu_temp = i8k_get_temp ( 0 ) ; /* 11100 µs */
left_fan = i8k_get_fan_status ( data , I8K_FAN_LEFT ) ; /* 580 µs */
right_fan = i8k_get_fan_status ( data , I8K_FAN_RIGHT ) ; /* 580 µs */
left_speed = i8k_get_fan_speed ( data , I8K_FAN_LEFT ) ; /* 580 µs */
right_speed = i8k_get_fan_speed ( data , I8K_FAN_RIGHT ) ; /* 580 µs */
fn_key = i8k_get_fn_status ( ) ; /* 750 µs */
2005-06-26 01:54:27 +04:00
if ( power_status )
2021-07-29 01:15:55 +03: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 ,
2021-07-29 01:15:55 +03:00
data - > bios_version ,
( restricted & & ! capable ( CAP_SYS_ADMIN ) ) ? " -1 " : data - > 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
{
2022-01-22 09:14:23 +03:00
return single_open ( file , i8k_proc_show , pde_data ( inode ) ) ;
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
} ;
2021-07-29 01:15:54 +03:00
static void i8k_exit_procfs ( void * param )
2015-05-14 14:16:37 +03:00
{
2021-07-29 01:15:54 +03:00
remove_proc_entry ( " i8k " , NULL ) ;
2015-05-14 14:16:37 +03:00
}
2021-07-29 01:15:54 +03:00
static void __init i8k_init_procfs ( struct device * dev )
2015-05-14 14:16:37 +03:00
{
2021-07-29 01:15:55 +03:00
struct dell_smm_data * data = dev_get_drvdata ( dev ) ;
2021-11-12 20:14:40 +03:00
/* Only register exit function if creation was successful */
if ( proc_create_data ( " i8k " , 0 , NULL , & i8k_proc_ops , data ) )
devm_add_action_or_reset ( dev , i8k_exit_procfs , NULL ) ;
2015-05-14 14:16:37 +03:00
}
# else
2021-07-29 01:15:54 +03:00
static void __init i8k_init_procfs ( struct device * dev )
2015-05-14 14:16:37 +03:00
{
}
# endif
2011-05-25 22:43:33 +04:00
/*
* Hwmon interface
*/
2021-07-29 01:15:56 +03:00
static umode_t dell_smm_is_visible ( const void * drvdata , enum hwmon_sensor_types type , u32 attr ,
int channel )
2015-01-12 16:31:57 +03:00
{
2021-07-29 01:15:56 +03:00
const struct dell_smm_data * data = drvdata ;
switch ( type ) {
case hwmon_temp :
switch ( attr ) {
case hwmon_temp_input :
case hwmon_temp_label :
if ( data - > temp_type [ channel ] > = 0 )
return 0444 ;
break ;
default :
break ;
}
break ;
case hwmon_fan :
if ( data - > disallow_fan_support )
break ;
switch ( attr ) {
case hwmon_fan_input :
if ( data - > fan [ channel ] )
return 0444 ;
break ;
case hwmon_fan_label :
if ( data - > fan [ channel ] & & ! data - > disallow_fan_type_call )
return 0444 ;
2021-09-27 01:10:43 +03:00
break ;
case hwmon_fan_min :
case hwmon_fan_max :
case hwmon_fan_target :
if ( data - > fan_nominal_speed [ channel ] )
return 0444 ;
2021-07-29 01:15:56 +03:00
break ;
default :
break ;
}
break ;
case hwmon_pwm :
if ( data - > disallow_fan_support )
break ;
switch ( attr ) {
case hwmon_pwm_input :
if ( data - > fan [ channel ] )
return 0644 ;
break ;
case hwmon_pwm_enable :
if ( data - > auto_fan )
/*
* There is no command for retrieve the current status
* from BIOS , and userspace / firmware itself can change
* it .
* Thus we can only provide write - only access for now .
*/
return 0200 ;
break ;
default :
break ;
}
break ;
default :
break ;
}
2015-01-12 16:31:57 +03:00
2021-07-29 01:15:56 +03:00
return 0 ;
2015-01-12 16:31:57 +03:00
}
2021-07-29 01:15:56 +03:00
static int dell_smm_read ( struct device * dev , enum hwmon_sensor_types type , u32 attr , int channel ,
long * val )
2011-05-25 22:43:33 +04:00
{
2021-07-29 01:15:56 +03:00
struct dell_smm_data * data = dev_get_drvdata ( dev ) ;
int ret ;
switch ( type ) {
case hwmon_temp :
switch ( attr ) {
case hwmon_temp_input :
ret = i8k_get_temp ( channel ) ;
if ( ret < 0 )
return ret ;
* val = ret * 1000 ;
return 0 ;
default :
break ;
}
break ;
case hwmon_fan :
switch ( attr ) {
case hwmon_fan_input :
ret = i8k_get_fan_speed ( data , channel ) ;
if ( ret < 0 )
return ret ;
* val = ret ;
2021-09-27 01:10:43 +03:00
return 0 ;
case hwmon_fan_min :
* val = data - > fan_nominal_speed [ channel ] [ 0 ] ;
return 0 ;
case hwmon_fan_max :
* val = data - > fan_nominal_speed [ channel ] [ data - > i8k_fan_max ] ;
return 0 ;
case hwmon_fan_target :
ret = i8k_get_fan_status ( data , channel ) ;
if ( ret < 0 )
return ret ;
if ( ret > data - > i8k_fan_max )
ret = data - > i8k_fan_max ;
* val = data - > fan_nominal_speed [ channel ] [ ret ] ;
2021-07-29 01:15:56 +03:00
return 0 ;
default :
break ;
}
break ;
case hwmon_pwm :
switch ( attr ) {
case hwmon_pwm_input :
ret = i8k_get_fan_status ( data , channel ) ;
if ( ret < 0 )
return ret ;
* val = clamp_val ( ret * data - > i8k_pwm_mult , 0 , 255 ) ;
return 0 ;
default :
break ;
}
break ;
default :
break ;
}
2011-05-25 22:43:33 +04:00
2021-07-29 01:15:56 +03:00
return - EOPNOTSUPP ;
2011-05-25 22:43:33 +04:00
}
2021-07-29 01:15:56 +03:00
static const char * dell_smm_fan_label ( struct dell_smm_data * data , int channel )
2015-01-12 16:32:05 +03:00
{
bool dock = false ;
2021-07-29 01:15:56 +03:00
int type = i8k_get_fan_type ( data , channel ) ;
2015-01-12 16:32:05 +03:00
if ( type < 0 )
2021-07-29 01:15:56 +03:00
return ERR_PTR ( type ) ;
2015-01-12 16:32:05 +03:00
if ( type & 0x10 ) {
dock = true ;
type & = 0x0F ;
}
2021-07-29 01:15:56 +03:00
if ( type > = ARRAY_SIZE ( fan_labels ) )
type = ARRAY_SIZE ( fan_labels ) - 1 ;
2015-01-12 16:32:05 +03:00
2021-07-29 01:15:56 +03:00
return dock ? docking_labels [ type ] : fan_labels [ type ] ;
2015-01-12 16:32:05 +03:00
}
2021-07-29 01:15:56 +03:00
static int dell_smm_read_string ( struct device * dev , enum hwmon_sensor_types type , u32 attr ,
int channel , const char * * str )
2011-05-25 22:43:33 +04:00
{
2021-07-29 01:15:55 +03:00
struct dell_smm_data * data = dev_get_drvdata ( dev ) ;
2011-05-25 22:43:33 +04:00
2021-07-29 01:15:56 +03:00
switch ( type ) {
case hwmon_temp :
switch ( attr ) {
case hwmon_temp_label :
* str = temp_labels [ data - > temp_type [ channel ] ] ;
return 0 ;
default :
break ;
}
break ;
case hwmon_fan :
switch ( attr ) {
case hwmon_fan_label :
* str = dell_smm_fan_label ( data , channel ) ;
return PTR_ERR_OR_ZERO ( * str ) ;
default :
break ;
}
break ;
default :
break ;
}
2013-12-14 21:30:21 +04:00
2021-07-29 01:15:56 +03:00
return - EOPNOTSUPP ;
2013-12-14 21:30:21 +04:00
}
2021-07-29 01:15:56 +03:00
static int dell_smm_write ( struct device * dev , enum hwmon_sensor_types type , u32 attr , int channel ,
long val )
2013-12-14 21:30:21 +04:00
{
2021-07-29 01:15:55 +03:00
struct dell_smm_data * data = dev_get_drvdata ( dev ) ;
2021-07-29 01:15:56 +03:00
unsigned long pwm ;
bool enable ;
2013-12-14 21:30:21 +04:00
int err ;
2021-07-29 01:15:56 +03:00
switch ( type ) {
case hwmon_pwm :
switch ( attr ) {
case hwmon_pwm_input :
pwm = clamp_val ( DIV_ROUND_CLOSEST ( val , data - > i8k_pwm_mult ) , 0 ,
data - > i8k_fan_max ) ;
mutex_lock ( & data - > i8k_mutex ) ;
err = i8k_set_fan ( data , channel , pwm ) ;
mutex_unlock ( & data - > i8k_mutex ) ;
if ( err < 0 )
return err ;
return 0 ;
case hwmon_pwm_enable :
if ( ! val )
return - EINVAL ;
if ( val = = 1 )
enable = false ;
else
enable = true ;
mutex_lock ( & data - > i8k_mutex ) ;
err = i8k_enable_fan_auto_mode ( data , enable ) ;
mutex_unlock ( & data - > i8k_mutex ) ;
if ( err < 0 )
return err ;
return 0 ;
default :
break ;
}
break ;
default :
break ;
}
2013-12-14 21:30:21 +04:00
2021-07-29 01:15:56 +03:00
return - EOPNOTSUPP ;
2013-12-14 21:30:21 +04:00
}
2021-07-29 01:15:56 +03:00
static const struct hwmon_ops dell_smm_ops = {
. is_visible = dell_smm_is_visible ,
. read = dell_smm_read ,
. read_string = dell_smm_read_string ,
. write = dell_smm_write ,
} ;
2019-11-22 13:15:18 +03:00
2021-07-29 01:15:56 +03:00
static const struct hwmon_channel_info * dell_smm_info [ ] = {
HWMON_CHANNEL_INFO ( chip , HWMON_C_REGISTER_TZ ) ,
HWMON_CHANNEL_INFO ( temp ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL ,
HWMON_T_INPUT | HWMON_T_LABEL
) ,
HWMON_CHANNEL_INFO ( fan ,
2021-09-27 01:10:43 +03:00
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
HWMON_F_TARGET ,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
HWMON_F_TARGET ,
HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
HWMON_F_TARGET
2021-07-29 01:15:56 +03:00
) ,
HWMON_CHANNEL_INFO ( pwm ,
HWMON_PWM_INPUT | HWMON_PWM_ENABLE ,
HWMON_PWM_INPUT ,
HWMON_PWM_INPUT
) ,
2013-12-14 21:30:10 +04:00
NULL
} ;
2011-05-25 22:43:33 +04:00
2021-07-29 01:15:56 +03:00
static const struct hwmon_chip_info dell_smm_chip_info = {
. ops = & dell_smm_ops ,
. info = dell_smm_info ,
} ;
static int __init dell_smm_init_hwmon ( struct device * dev )
2011-05-25 22:43:33 +04:00
{
2021-07-29 01:15:55 +03:00
struct dell_smm_data * data = dev_get_drvdata ( dev ) ;
2021-07-29 01:15:56 +03:00
struct device * dell_smm_hwmon_dev ;
2021-09-27 01:10:43 +03:00
int i , state , err ;
2021-07-29 01:15:55 +03:00
2021-07-29 01:15:56 +03:00
for ( i = 0 ; i < DELL_SMM_NO_TEMP ; i + + ) {
data - > temp_type [ i ] = i8k_get_temp_type ( i ) ;
if ( data - > temp_type [ i ] < 0 )
continue ;
2019-11-22 13:15:18 +03:00
2021-07-29 01:15:56 +03:00
if ( data - > temp_type [ i ] > = ARRAY_SIZE ( temp_labels ) )
data - > temp_type [ i ] = ARRAY_SIZE ( temp_labels ) - 1 ;
}
2011-05-25 22:43:33 +04:00
2021-07-29 01:15:56 +03:00
for ( i = 0 ; i < DELL_SMM_NO_FANS ; i + + ) {
data - > fan_type [ i ] = INT_MIN ;
err = i8k_get_fan_status ( data , i ) ;
if ( err < 0 )
err = i8k_get_fan_type ( data , i ) ;
2021-09-27 01:10:43 +03:00
if ( err < 0 )
continue ;
data - > fan [ i ] = true ;
data - > fan_nominal_speed [ i ] = devm_kmalloc_array ( dev , data - > i8k_fan_max + 1 ,
sizeof ( * data - > fan_nominal_speed [ i ] ) ,
GFP_KERNEL ) ;
if ( ! data - > fan_nominal_speed [ i ] )
continue ;
for ( state = 0 ; state < = data - > i8k_fan_max ; state + + ) {
err = i8k_get_fan_nominal_speed ( data , i , state ) ;
if ( err < 0 ) {
/* Mark nominal speed table as invalid in case of error */
devm_kfree ( dev , data - > fan_nominal_speed [ i ] ) ;
data - > fan_nominal_speed [ i ] = NULL ;
break ;
}
data - > fan_nominal_speed [ i ] [ state ] = err ;
}
2021-07-29 01:15:56 +03:00
}
2013-12-14 21:30:10 +04:00
2021-07-29 01:15:56 +03:00
dell_smm_hwmon_dev = devm_hwmon_device_register_with_info ( dev , " dell_smm " , data ,
& dell_smm_chip_info , NULL ) ;
2011-05-25 22:43:33 +04:00
2021-07-29 01:15:56 +03:00
return PTR_ERR_OR_ZERO ( dell_smm_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
} ;
2021-10-21 22:05:30 +03:00
/*
* Only use for machines which need some special configuration
* in order to work correctly ( e . g . if autoconfig fails on this machines ) .
*/
2021-08-14 17:36:34 +03:00
static const struct i8k_config_data i8k_config_data [ ] __initconst = {
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
*/
2021-08-14 17:36:34 +03:00
static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table [ ] __initconst = {
2018-01-27 19:28:34 +03:00
{
. 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 ,
} ;
2021-08-14 17:36:34 +03:00
static const struct i8k_fan_control_data i8k_fan_control_data [ ] __initconst = {
2019-11-22 13:15:18 +03:00
[ I8K_FAN_34A3_35A3 ] = {
. manual_fan = 0x34a3 ,
. auto_fan = 0x35a3 ,
} ,
} ;
2021-08-14 17:36:34 +03:00
static const struct dmi_system_id i8k_whitelist_fan_control [ ] __initconst = {
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 ] ,
} ,
2021-08-02 16:15:38 +03:00
{
. 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 ] ,
} ,
{
. ident = " Dell Precision 7510 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_EXACT_MATCH ( DMI_PRODUCT_NAME , " Precision 7510 " ) ,
} ,
. 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
{
2021-07-29 01:15:55 +03:00
struct dell_smm_data * data ;
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
2021-07-29 01:15:55 +03:00
data = devm_kzalloc ( & pdev - > dev , sizeof ( struct dell_smm_data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
mutex_init ( & data - > i8k_mutex ) ;
data - > i8k_fan_mult = I8K_FAN_MULT ;
data - > i8k_fan_max = I8K_FAN_HIGH ;
platform_set_drvdata ( pdev , data ) ;
2018-01-27 19:28:34 +03:00
if ( dmi_check_system ( i8k_blacklist_fan_support_dmi_table ) ) {
2021-07-29 01:15:55 +03:00
dev_warn ( & pdev - > dev , " broken Dell BIOS detected, disallow fan support \n " ) ;
2018-01-27 19:28:34 +03:00
if ( ! force )
2021-07-29 01:15:55 +03:00
data - > disallow_fan_support = true ;
2018-01-27 19:28:34 +03:00
}
2018-01-27 19:22:01 +03:00
if ( dmi_check_system ( i8k_blacklist_fan_type_dmi_table ) ) {
2021-07-29 01:15:55 +03:00
dev_warn ( & pdev - > dev , " broken Dell BIOS detected, disallow fan type call \n " ) ;
2018-01-27 19:22:01 +03:00
if ( ! force )
2021-07-29 01:15:55 +03:00
data - > disallow_fan_type_call = true ;
2018-01-27 19:22:01 +03:00
}
2016-06-18 01:54:46 +03:00
2021-07-29 01:15:55 +03:00
strscpy ( data - > bios_version , i8k_get_dmi_data ( DMI_BIOS_VERSION ) ,
sizeof ( data - > bios_version ) ) ;
strscpy ( data - > bios_machineid , i8k_get_dmi_data ( DMI_PRODUCT_SERIAL ) ,
sizeof ( data - > 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 ;
2021-07-29 01:15:55 +03:00
2015-01-12 16:32:03 +03:00
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
2021-07-29 01:15:55 +03:00
data - > i8k_fan_max = fan_max ? : I8K_FAN_HIGH ; /* Must not be 0 */
data - > i8k_pwm_mult = DIV_ROUND_UP ( 255 , data - > 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 ) {
2021-07-29 01:15:55 +03:00
const struct i8k_fan_control_data * control = fan_control - > driver_data ;
2019-11-22 13:15:18 +03:00
2021-07-29 01:15:55 +03:00
data - > manual_fan = control - > manual_fan ;
data - > auto_fan = control - > auto_fan ;
dev_info ( & pdev - > dev , " enabling support for setting automatic/manual fan control \n " ) ;
2019-11-22 13:15:18 +03:00
}
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
*/
2021-07-29 01:15:57 +03:00
for ( fan = 0 ; fan < DELL_SMM_NO_FANS ; + + fan ) {
2021-07-29 01:15:55 +03:00
ret = i8k_get_fan_nominal_speed ( data , fan , data - > i8k_fan_max ) ;
2015-01-12 16:32:03 +03:00
if ( ret < 0 )
continue ;
2021-07-29 01:15:55 +03:00
2015-01-12 16:32:03 +03:00
if ( ret > I8K_FAN_MAX_RPM )
2021-07-29 01:15:55 +03:00
data - > i8k_fan_mult = 1 ;
2015-01-12 16:32:03 +03:00
break ;
}
} else {
/* Fan multiplier was specified in module param or in dmi */
2021-07-29 01:15:55 +03:00
data - > i8k_fan_mult = fan_mult ;
2015-01-12 16:32:03 +03:00
}
2021-07-29 01:15:52 +03:00
ret = dell_smm_init_hwmon ( & pdev - > dev ) ;
if ( ret )
return ret ;
2021-07-29 01:15:54 +03:00
i8k_init_procfs ( & pdev - > dev ) ;
2021-07-29 01:15:52 +03:00
return 0 ;
}
static struct platform_driver dell_smm_driver = {
. driver = {
. name = KBUILD_MODNAME ,
} ,
} ;
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 ) ;