2005-04-17 02:20:36 +04:00
/*
* i8k . c - - Linux driver for accessing the SMM BIOS on Dell laptops .
* See http : //www.debian.org/~dz/i8k/ for more information
* and for latest version of this driver .
*
* Copyright ( C ) 2001 Massimo Dal Zotto < dz @ debian . org >
*
2011-05-25 22:43:33 +04:00
* Hwmon integration :
* Copyright ( C ) 2011 Jean Delvare < khali @ linux - fr . org >
*
2005-04-17 02:20:36 +04:00
* This program is free software ; you can redistribute it and / or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation ; either version 2 , or ( at your option ) any
* later version .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*/
2013-12-14 21:30:08 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-04-17 02:20:36 +04:00
# include <linux/module.h>
# 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>
2005-04-17 02:20:36 +04:00
# include <asm/uaccess.h>
# include <asm/io.h>
# include <linux/i8k.h>
2005-06-26 01:54:26 +04:00
# define I8K_VERSION "1.14 21 / 02 / 2005"
2005-04-17 02:20:36 +04:00
# 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
# define I8K_SMM_GET_TEMP 0x10a3
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_SMM_BIOS_VERSION 0x00a6
# define I8K_FAN_MULT 30
# 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
# define I8K_TEMPERATURE_BUG 1
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 ] ;
2011-05-25 22:43:33 +04:00
static struct device * i8k_hwmon_dev ;
2005-04-17 02:20:36 +04:00
MODULE_AUTHOR ( " Massimo Dal Zotto (dz@debian.org) " ) ;
MODULE_DESCRIPTION ( " Driver for accessing SMM BIOS on Dell laptops " ) ;
MODULE_LICENSE ( " GPL " ) ;
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 " ) ;
2012-01-13 03:02:20 +04:00
static bool restricted ;
2005-04-17 02:20:36 +04:00
module_param ( restricted , bool , 0 ) ;
MODULE_PARM_DESC ( restricted , " Allow fan control if SYS_ADMIN capability set " ) ;
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 ) ;
MODULE_PARM_DESC ( power_status , " Report power status in /proc/i8k " ) ;
2008-05-01 15:34:58 +04:00
static int fan_mult = I8K_FAN_MULT ;
module_param ( fan_mult , int , 0 ) ;
MODULE_PARM_DESC ( fan_mult , " Factor to multiply fan speed with " ) ;
2005-06-26 01:54:26 +04:00
static int i8k_open_fs ( struct inode * inode , struct file * file ) ;
2010-03-30 09:27:50 +04:00
static long i8k_ioctl ( struct file * , unsigned int , unsigned long ) ;
2005-04-17 02:20:36 +04:00
2006-07-03 11:24:21 +04:00
static const struct file_operations i8k_fops = {
2008-04-29 12:02:34 +04:00
. owner = THIS_MODULE ,
2005-06-26 01:54:26 +04:00
. open = i8k_open_fs ,
. read = seq_read ,
. llseek = seq_lseek ,
. release = single_release ,
2010-03-30 09:27:50 +04:00
. unlocked_ioctl = i8k_ioctl ,
2005-04-17 02:20:36 +04:00
} ;
2005-06-26 01:54:27 +04:00
struct smm_regs {
2005-06-26 01:54:23 +04:00
unsigned int eax ;
unsigned int ebx __attribute__ ( ( packed ) ) ;
unsigned int ecx __attribute__ ( ( packed ) ) ;
unsigned int edx __attribute__ ( ( packed ) ) ;
unsigned int esi __attribute__ ( ( packed ) ) ;
unsigned int edi __attribute__ ( ( 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 .
*/
2005-06-26 01:54:27 +04:00
static int i8k_smm ( struct smm_regs * regs )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:23 +04:00
int rc ;
int eax = regs - > eax ;
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 "
2010-11-15 23:22:37 +03: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 "
2010-11-15 23:22:37 +03: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 )
2005-06-26 01:54:23 +04:00
return - EINVAL ;
return 0 ;
2005-04-17 02:20:36 +04:00
}
/*
* Read the bios version . Return the version as an integer corresponding
* to the ascii value , for example " A17 " is returned as 0x00413137 .
*/
static int i8k_get_bios_version ( void )
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_BIOS_VERSION , } ;
2005-04-17 02:20:36 +04:00
2005-06-26 01:54:27 +04:00
return i8k_smm ( & regs ) ? : regs . eax ;
2005-04-17 02:20:36 +04:00
}
/*
* Read the Fn key status .
*/
static int i8k_get_fn_status ( void )
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_FN_STATUS , } ;
2005-06-26 01:54:23 +04:00
int rc ;
2005-06-26 01:54:27 +04:00
if ( ( rc = i8k_smm ( & regs ) ) < 0 )
2005-06-26 01:54:23 +04:00
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 ;
}
2005-04-17 02:20:36 +04:00
}
/*
* Read the power status .
*/
static int i8k_get_power_status ( void )
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_POWER_STATUS , } ;
2005-06-26 01:54:23 +04:00
int rc ;
2005-06-26 01:54:27 +04:00
if ( ( rc = i8k_smm ( & regs ) ) < 0 )
2005-06-26 01:54:23 +04:00
return rc ;
2005-06-26 01:54:27 +04:00
return ( regs . eax & 0xff ) = = I8K_POWER_AC ? I8K_AC : I8K_BATTERY ;
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
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
2005-06-26 01:54:23 +04:00
regs . ebx = fan & 0xff ;
2008-05-01 15:34:58 +04:00
return i8k_smm ( & regs ) ? : ( regs . eax & 0xffff ) * fan_mult ;
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
2005-06-26 01:54:23 +04:00
speed = ( speed < 0 ) ? 0 : ( ( speed > I8K_FAN_MAX ) ? I8K_FAN_MAX : speed ) ;
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
}
/*
* Read the cpu temperature .
*/
2005-06-26 01:54:28 +04:00
static int i8k_get_temp ( int sensor )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:27 +04:00
struct smm_regs regs = { . eax = I8K_SMM_GET_TEMP , } ;
2005-06-26 01:54:23 +04:00
int rc ;
int temp ;
2005-04-17 02:20:36 +04:00
# ifdef I8K_TEMPERATURE_BUG
2005-06-26 01:54:27 +04:00
static int prev ;
2005-04-17 02:20:36 +04:00
# endif
2005-06-26 01:54:28 +04:00
regs . ebx = sensor & 0xff ;
2005-06-26 01:54:27 +04:00
if ( ( rc = i8k_smm ( & regs ) ) < 0 )
2005-06-26 01:54:23 +04:00
return rc ;
2005-06-26 01:54:27 +04:00
2005-06-26 01:54:23 +04:00
temp = regs . eax & 0xff ;
2005-04-17 02:20:36 +04:00
# ifdef I8K_TEMPERATURE_BUG
2005-06-26 01:54:23 +04:00
/*
* Sometimes the temperature sensor returns 0x99 , which is out of range .
* In this case we return ( once ) the previous cached value . For example :
# 1003655137 00000058 00005a4b
# 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
# 1003655139 00000054 00005c52
*/
if ( temp > I8K_MAX_TEMP ) {
temp = prev ;
prev = I8K_MAX_TEMP ;
} else {
prev = temp ;
}
2005-04-17 02:20:36 +04:00
# endif
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
2005-06-26 01:54:27 +04:00
if ( ( rc = i8k_smm ( & regs ) ) < 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
}
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 :
val = i8k_get_bios_version ( ) ;
break ;
case I8K_MACHINE_ID :
memset ( buff , 0 , 16 ) ;
2005-06-26 01:54:25 +04:00
strlcpy ( buff , i8k_get_dmi_data ( DMI_PRODUCT_SERIAL ) , 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
*/
2005-06-26 01:54:26 +04:00
return seq_printf ( seq , " %s %s %s %d %d %d %d %d %d %d \n " ,
I8K_PROC_FMT ,
bios_version ,
2005-11-12 08:55:15 +03:00
i8k_get_dmi_data ( DMI_PRODUCT_SERIAL ) ,
2005-06-26 01:54:26 +04:00
cpu_temp ,
left_fan , right_fan , left_speed , right_speed ,
ac_power , fn_key ) ;
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
}
2011-05-25 22:43:33 +04:00
/*
* Hwmon interface
*/
static ssize_t i8k_hwmon_show_temp ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
int cpu_temp ;
cpu_temp = i8k_get_temp ( 0 ) ;
if ( cpu_temp < 0 )
return cpu_temp ;
return sprintf ( buf , " %d \n " , cpu_temp * 1000 ) ;
}
static ssize_t i8k_hwmon_show_fan ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
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 ) ;
}
static ssize_t i8k_hwmon_show_label ( struct device * dev ,
struct device_attribute * devattr ,
char * buf )
{
static const char * labels [ 4 ] = {
" i8k " ,
" CPU " ,
" Left Fan " ,
" Right Fan " ,
} ;
int index = to_sensor_dev_attr ( devattr ) - > index ;
return sprintf ( buf , " %s \n " , labels [ index ] ) ;
}
static DEVICE_ATTR ( temp1_input , S_IRUGO , i8k_hwmon_show_temp , NULL ) ;
static SENSOR_DEVICE_ATTR ( fan1_input , S_IRUGO , i8k_hwmon_show_fan , NULL ,
I8K_FAN_LEFT ) ;
static SENSOR_DEVICE_ATTR ( fan2_input , S_IRUGO , i8k_hwmon_show_fan , NULL ,
I8K_FAN_RIGHT ) ;
static SENSOR_DEVICE_ATTR ( name , S_IRUGO , i8k_hwmon_show_label , NULL , 0 ) ;
static SENSOR_DEVICE_ATTR ( temp1_label , S_IRUGO , i8k_hwmon_show_label , NULL , 1 ) ;
static SENSOR_DEVICE_ATTR ( fan1_label , S_IRUGO , i8k_hwmon_show_label , NULL , 2 ) ;
static SENSOR_DEVICE_ATTR ( fan2_label , S_IRUGO , i8k_hwmon_show_label , NULL , 3 ) ;
static void i8k_hwmon_remove_files ( struct device * dev )
{
device_remove_file ( dev , & dev_attr_temp1_input ) ;
device_remove_file ( dev , & sensor_dev_attr_fan1_input . dev_attr ) ;
device_remove_file ( dev , & sensor_dev_attr_fan2_input . dev_attr ) ;
device_remove_file ( dev , & sensor_dev_attr_temp1_label . dev_attr ) ;
device_remove_file ( dev , & sensor_dev_attr_fan1_label . dev_attr ) ;
device_remove_file ( dev , & sensor_dev_attr_fan2_label . dev_attr ) ;
device_remove_file ( dev , & sensor_dev_attr_name . dev_attr ) ;
}
static int __init i8k_init_hwmon ( void )
{
int err ;
i8k_hwmon_dev = hwmon_device_register ( NULL ) ;
if ( IS_ERR ( i8k_hwmon_dev ) ) {
err = PTR_ERR ( i8k_hwmon_dev ) ;
i8k_hwmon_dev = NULL ;
2013-12-14 21:30:08 +04:00
pr_err ( " hwmon registration failed (%d) \n " , err ) ;
2011-05-25 22:43:33 +04:00
return err ;
}
/* Required name attribute */
err = device_create_file ( i8k_hwmon_dev ,
& sensor_dev_attr_name . dev_attr ) ;
if ( err )
goto exit_unregister ;
/* CPU temperature attributes, if temperature reading is OK */
err = i8k_get_temp ( 0 ) ;
if ( err < 0 ) {
dev_dbg ( i8k_hwmon_dev ,
" Not creating temperature attributes (%d) \n " , err ) ;
} else {
err = device_create_file ( i8k_hwmon_dev , & dev_attr_temp1_input ) ;
if ( err )
goto exit_remove_files ;
err = device_create_file ( i8k_hwmon_dev ,
& sensor_dev_attr_temp1_label . dev_attr ) ;
if ( err )
goto exit_remove_files ;
}
/* Left fan attributes, if left fan is present */
err = i8k_get_fan_status ( I8K_FAN_LEFT ) ;
if ( err < 0 ) {
dev_dbg ( i8k_hwmon_dev ,
" Not creating %s fan attributes (%d) \n " , " left " , err ) ;
} else {
err = device_create_file ( i8k_hwmon_dev ,
& sensor_dev_attr_fan1_input . dev_attr ) ;
if ( err )
goto exit_remove_files ;
err = device_create_file ( i8k_hwmon_dev ,
& sensor_dev_attr_fan1_label . dev_attr ) ;
if ( err )
goto exit_remove_files ;
}
/* Right fan attributes, if right fan is present */
err = i8k_get_fan_status ( I8K_FAN_RIGHT ) ;
if ( err < 0 ) {
dev_dbg ( i8k_hwmon_dev ,
" Not creating %s fan attributes (%d) \n " , " right " , err ) ;
} else {
err = device_create_file ( i8k_hwmon_dev ,
& sensor_dev_attr_fan2_input . dev_attr ) ;
if ( err )
goto exit_remove_files ;
err = device_create_file ( i8k_hwmon_dev ,
& sensor_dev_attr_fan2_label . dev_attr ) ;
if ( err )
goto exit_remove_files ;
}
return 0 ;
exit_remove_files :
i8k_hwmon_remove_files ( i8k_hwmon_dev ) ;
exit_unregister :
hwmon_device_unregister ( i8k_hwmon_dev ) ;
return err ;
}
static void __exit i8k_exit_hwmon ( void )
{
i8k_hwmon_remove_files ( i8k_hwmon_dev ) ;
hwmon_device_unregister ( i8k_hwmon_dev ) ;
}
2005-06-26 01:54:25 +04:00
static struct dmi_system_id __initdata i8k_dmi_table [ ] = {
{
. 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 " ) ,
} ,
} ,
{
. 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 " ) ,
} ,
} ,
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-04 01:56:56 +04:00
{
. ident = " Dell XPS421 " ,
. matches = {
DMI_MATCH ( DMI_SYS_VENDOR , " Dell Inc. " ) ,
DMI_MATCH ( DMI_PRODUCT_NAME , " XPS L421X " ) ,
} ,
} ,
2009-01-02 19:19:23 +03:00
{ }
2005-06-26 01:54:25 +04:00
} ;
2005-04-17 02:20:36 +04:00
/*
* Probe for the presence of a supported laptop .
*/
static int __init i8k_probe ( void )
{
2005-06-26 01:54:23 +04:00
char buff [ 4 ] ;
int version ;
2005-04-17 02:20:36 +04:00
/*
2005-06-26 01:54:23 +04:00
* Get DMI information
2005-04-17 02:20:36 +04:00
*/
2005-06-26 01:54:25 +04:00
if ( ! dmi_check_system ( i8k_dmi_table ) ) {
if ( ! ignore_dmi & & ! force )
return - ENODEV ;
2013-12-14 21:30:08 +04:00
pr_info ( " not running on a supported Dell system. \n " ) ;
pr_info ( " vendor=%s, model=%s, version=%s \n " ,
2005-06-26 01:54:25 +04:00
i8k_get_dmi_data ( DMI_SYS_VENDOR ) ,
i8k_get_dmi_data ( DMI_PRODUCT_NAME ) ,
i8k_get_dmi_data ( DMI_BIOS_VERSION ) ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:23 +04:00
2005-06-26 01:54:25 +04:00
strlcpy ( bios_version , i8k_get_dmi_data ( DMI_BIOS_VERSION ) , sizeof ( bios_version ) ) ;
2005-04-17 02:20:36 +04:00
/*
2005-06-26 01:54:23 +04:00
* Get SMM Dell signature
2005-04-17 02:20:36 +04:00
*/
2005-06-26 01:54:28 +04:00
if ( i8k_get_dell_signature ( I8K_SMM_GET_DELL_SIG1 ) & &
i8k_get_dell_signature ( I8K_SMM_GET_DELL_SIG2 ) ) {
2013-12-14 21:30:08 +04:00
pr_err ( " unable to get SMM Dell signature \n " ) ;
2005-06-26 01:54:25 +04:00
if ( ! force )
return - ENODEV ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:23 +04:00
/*
* Get SMM BIOS version .
*/
version = i8k_get_bios_version ( ) ;
if ( version < = 0 ) {
2013-12-14 21:30:08 +04:00
pr_warn ( " unable to get SMM BIOS version \n " ) ;
2005-06-26 01:54:23 +04:00
} else {
buff [ 0 ] = ( version > > 16 ) & 0xff ;
buff [ 1 ] = ( version > > 8 ) & 0xff ;
buff [ 2 ] = ( version ) & 0xff ;
buff [ 3 ] = ' \0 ' ;
/*
* If DMI BIOS version is unknown use SMM BIOS version .
*/
2005-06-26 01:54:25 +04:00
if ( ! dmi_get_system_info ( DMI_BIOS_VERSION ) )
strlcpy ( bios_version , buff , sizeof ( bios_version ) ) ;
2005-06-26 01:54:23 +04:00
/*
* Check if the two versions match .
*/
2005-06-26 01:54:25 +04:00
if ( strncmp ( buff , bios_version , sizeof ( bios_version ) ) ! = 0 )
2013-12-14 21:30:08 +04:00
pr_warn ( " BIOS version mismatch: %s != %s \n " ,
2005-06-26 01:54:25 +04:00
buff , bios_version ) ;
2005-06-26 01:54:23 +04:00
}
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
}
2005-06-26 01:54:27 +04:00
static int __init i8k_init ( void )
2005-04-17 02:20:36 +04:00
{
2005-06-26 01:54:23 +04:00
struct proc_dir_entry * proc_i8k ;
2011-05-25 22:43:33 +04:00
int err ;
2005-06-26 01:54:23 +04:00
/* Are we running on an supported laptop? */
2005-06-26 01:54:25 +04:00
if ( i8k_probe ( ) )
2005-06-26 01:54:23 +04:00
return - ENODEV ;
/* Register the proc entry */
2008-04-29 12:02:34 +04:00
proc_i8k = proc_create ( " i8k " , 0 , NULL , & i8k_fops ) ;
2005-06-26 01:54:26 +04:00
if ( ! proc_i8k )
2005-06-26 01:54:23 +04:00
return - ENOENT ;
2005-06-26 01:54:26 +04:00
2011-05-25 22:43:33 +04:00
err = i8k_init_hwmon ( ) ;
if ( err )
goto exit_remove_proc ;
2013-12-14 21:30:08 +04:00
pr_info ( " Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org) \n " ,
I8K_VERSION ) ;
2005-06-26 01:54:23 +04:00
return 0 ;
2011-05-25 22:43:33 +04:00
exit_remove_proc :
remove_proc_entry ( " i8k " , NULL ) ;
return err ;
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
{
2011-05-25 22:43:33 +04:00
i8k_exit_hwmon ( ) ;
2005-06-26 01:54:23 +04:00
remove_proc_entry ( " i8k " , NULL ) ;
2005-04-17 02:20:36 +04:00
}
2005-06-26 01:54:27 +04:00
module_init ( i8k_init ) ;
module_exit ( i8k_exit ) ;