13e0a4e85SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 2a5afba16SPali Rohár /* 3a5afba16SPali Rohár * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops. 4a5afba16SPali Rohár * 5a5afba16SPali Rohár * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org> 6a5afba16SPali Rohár * 7a5afba16SPali Rohár * Hwmon integration: 8a5afba16SPali Rohár * Copyright (C) 2011 Jean Delvare <jdelvare@suse.de> 9a5afba16SPali Rohár * Copyright (C) 2013, 2014 Guenter Roeck <linux@roeck-us.net> 10149ed3d4SPali Rohár * Copyright (C) 2014, 2015 Pali Rohár <pali@kernel.org> 11a5afba16SPali Rohár */ 12a5afba16SPali Rohár 13a5afba16SPali Rohár #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 14a5afba16SPali Rohár 1527046a3fSJuergen Gross #include <linux/cpu.h> 16a5afba16SPali Rohár #include <linux/delay.h> 17a5afba16SPali Rohár #include <linux/module.h> 18a5afba16SPali Rohár #include <linux/types.h> 19a5afba16SPali Rohár #include <linux/init.h> 20a5afba16SPali Rohár #include <linux/proc_fs.h> 21a5afba16SPali Rohár #include <linux/seq_file.h> 22a5afba16SPali Rohár #include <linux/dmi.h> 23a5afba16SPali Rohár #include <linux/capability.h> 24a5afba16SPali Rohár #include <linux/mutex.h> 25a5afba16SPali Rohár #include <linux/hwmon.h> 26a5afba16SPali Rohár #include <linux/hwmon-sysfs.h> 27a5afba16SPali Rohár #include <linux/uaccess.h> 28a5afba16SPali Rohár #include <linux/io.h> 29a5afba16SPali Rohár #include <linux/sched.h> 30053ea640SPali Rohár #include <linux/ctype.h> 3127046a3fSJuergen Gross #include <linux/smp.h> 32a5afba16SPali Rohár 33a5afba16SPali Rohár #include <linux/i8k.h> 34a5afba16SPali Rohár 35a5afba16SPali Rohár #define I8K_SMM_FN_STATUS 0x0025 36a5afba16SPali Rohár #define I8K_SMM_POWER_STATUS 0x0069 37a5afba16SPali Rohár #define I8K_SMM_SET_FAN 0x01a3 38a5afba16SPali Rohár #define I8K_SMM_GET_FAN 0x00a3 39a5afba16SPali Rohár #define I8K_SMM_GET_SPEED 0x02a3 40a5afba16SPali Rohár #define I8K_SMM_GET_FAN_TYPE 0x03a3 41a5afba16SPali Rohár #define I8K_SMM_GET_NOM_SPEED 0x04a3 42a5afba16SPali Rohár #define I8K_SMM_GET_TEMP 0x10a3 43a5afba16SPali Rohár #define I8K_SMM_GET_TEMP_TYPE 0x11a3 44a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG1 0xfea3 45a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG2 0xffa3 46a5afba16SPali Rohár 47a5afba16SPali Rohár #define I8K_FAN_MULT 30 48a5afba16SPali Rohár #define I8K_FAN_MAX_RPM 30000 49a5afba16SPali Rohár #define I8K_MAX_TEMP 127 50a5afba16SPali Rohár 51a5afba16SPali Rohár #define I8K_FN_NONE 0x00 52a5afba16SPali Rohár #define I8K_FN_UP 0x01 53a5afba16SPali Rohár #define I8K_FN_DOWN 0x02 54a5afba16SPali Rohár #define I8K_FN_MUTE 0x04 55a5afba16SPali Rohár #define I8K_FN_MASK 0x07 56a5afba16SPali Rohár #define I8K_FN_SHIFT 8 57a5afba16SPali Rohár 58a5afba16SPali Rohár #define I8K_POWER_AC 0x05 59a5afba16SPali Rohár #define I8K_POWER_BATTERY 0x01 60a5afba16SPali Rohár 61a5afba16SPali Rohár static DEFINE_MUTEX(i8k_mutex); 62a5afba16SPali Rohár static char bios_version[4]; 637613663cSPali Rohár static char bios_machineid[16]; 64a5afba16SPali Rohár static struct device *i8k_hwmon_dev; 65a5afba16SPali Rohár static u32 i8k_hwmon_flags; 66a5afba16SPali Rohár static uint i8k_fan_mult = I8K_FAN_MULT; 67a5afba16SPali Rohár static uint i8k_pwm_mult; 68a5afba16SPali Rohár static uint i8k_fan_max = I8K_FAN_HIGH; 692744d2fdSPali Rohár static bool disallow_fan_type_call; 70f480ea90SPali Rohár static bool disallow_fan_support; 71afe45277SGiovanni Mascellani static unsigned int manual_fan; 72afe45277SGiovanni Mascellani static unsigned int auto_fan; 73a5afba16SPali Rohár 74a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP1 (1 << 0) 75a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP2 (1 << 1) 76a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP3 (1 << 2) 77a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP4 (1 << 3) 781bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_TEMP5 (1 << 4) 791bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_TEMP6 (1 << 5) 801bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_TEMP7 (1 << 6) 811bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_TEMP8 (1 << 7) 821bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_TEMP9 (1 << 8) 831bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_TEMP10 (1 << 9) 841bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_FAN1 (1 << 10) 851bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_FAN2 (1 << 11) 861bb46a20SMichele Sorcinelli #define I8K_HWMON_HAVE_FAN3 (1 << 12) 87a5afba16SPali Rohár 88a5afba16SPali Rohár MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); 89149ed3d4SPali Rohár MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); 90039ae585SPali Rohár MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); 91a5afba16SPali Rohár MODULE_LICENSE("GPL"); 92a5afba16SPali Rohár MODULE_ALIAS("i8k"); 93a5afba16SPali Rohár 94a5afba16SPali Rohár static bool force; 95a5afba16SPali Rohár module_param(force, bool, 0); 96a5afba16SPali Rohár MODULE_PARM_DESC(force, "Force loading without checking for supported models"); 97a5afba16SPali Rohár 98a5afba16SPali Rohár static bool ignore_dmi; 99a5afba16SPali Rohár module_param(ignore_dmi, bool, 0); 100a5afba16SPali Rohár MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match"); 101a5afba16SPali Rohár 102039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K) 1037613663cSPali Rohár static bool restricted = true; 104a5afba16SPali Rohár module_param(restricted, bool, 0); 1057613663cSPali Rohár MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)"); 106a5afba16SPali Rohár 107a5afba16SPali Rohár static bool power_status; 108a5afba16SPali Rohár module_param(power_status, bool, 0600); 1097613663cSPali Rohár MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)"); 110039ae585SPali Rohár #endif 111a5afba16SPali Rohár 112a5afba16SPali Rohár static uint fan_mult; 113a5afba16SPali Rohár module_param(fan_mult, uint, 0); 114a5afba16SPali Rohár MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)"); 115a5afba16SPali Rohár 116a5afba16SPali Rohár static uint fan_max; 117a5afba16SPali Rohár module_param(fan_max, uint, 0); 118a5afba16SPali Rohár MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)"); 119a5afba16SPali Rohár 120a5afba16SPali Rohár struct smm_regs { 121a5afba16SPali Rohár unsigned int eax; 122a5afba16SPali Rohár unsigned int ebx __packed; 123a5afba16SPali Rohár unsigned int ecx __packed; 124a5afba16SPali Rohár unsigned int edx __packed; 125a5afba16SPali Rohár unsigned int esi __packed; 126a5afba16SPali Rohár unsigned int edi __packed; 127a5afba16SPali Rohár }; 128a5afba16SPali Rohár 129a5afba16SPali Rohár static inline const char *i8k_get_dmi_data(int field) 130a5afba16SPali Rohár { 131a5afba16SPali Rohár const char *dmi_data = dmi_get_system_info(field); 132a5afba16SPali Rohár 133a5afba16SPali Rohár return dmi_data && *dmi_data ? dmi_data : "?"; 134a5afba16SPali Rohár } 135a5afba16SPali Rohár 136a5afba16SPali Rohár /* 137a5afba16SPali Rohár * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard. 138a5afba16SPali Rohár */ 13927046a3fSJuergen Gross static int i8k_smm_func(void *par) 140a5afba16SPali Rohár { 141a5afba16SPali Rohár int rc; 14227046a3fSJuergen Gross struct smm_regs *regs = par; 143a5afba16SPali Rohár int eax = regs->eax; 144a5afba16SPali Rohár 1459d58bec0SPali Rohár #ifdef DEBUG 1469d58bec0SPali Rohár int ebx = regs->ebx; 1479d58bec0SPali Rohár unsigned long duration; 1489d58bec0SPali Rohár ktime_t calltime, delta, rettime; 1499d58bec0SPali Rohár 1509d58bec0SPali Rohár calltime = ktime_get(); 1519d58bec0SPali Rohár #endif 1529d58bec0SPali Rohár 153a5afba16SPali Rohár /* SMM requires CPU 0 */ 15427046a3fSJuergen Gross if (smp_processor_id() != 0) 15527046a3fSJuergen Gross return -EBUSY; 156a5afba16SPali Rohár 157a5afba16SPali Rohár #if defined(CONFIG_X86_64) 158a5afba16SPali Rohár asm volatile("pushq %%rax\n\t" 159a5afba16SPali Rohár "movl 0(%%rax),%%edx\n\t" 160a5afba16SPali Rohár "pushq %%rdx\n\t" 161a5afba16SPali Rohár "movl 4(%%rax),%%ebx\n\t" 162a5afba16SPali Rohár "movl 8(%%rax),%%ecx\n\t" 163a5afba16SPali Rohár "movl 12(%%rax),%%edx\n\t" 164a5afba16SPali Rohár "movl 16(%%rax),%%esi\n\t" 165a5afba16SPali Rohár "movl 20(%%rax),%%edi\n\t" 166a5afba16SPali Rohár "popq %%rax\n\t" 167a5afba16SPali Rohár "out %%al,$0xb2\n\t" 168a5afba16SPali Rohár "out %%al,$0x84\n\t" 169a5afba16SPali Rohár "xchgq %%rax,(%%rsp)\n\t" 170a5afba16SPali Rohár "movl %%ebx,4(%%rax)\n\t" 171a5afba16SPali Rohár "movl %%ecx,8(%%rax)\n\t" 172a5afba16SPali Rohár "movl %%edx,12(%%rax)\n\t" 173a5afba16SPali Rohár "movl %%esi,16(%%rax)\n\t" 174a5afba16SPali Rohár "movl %%edi,20(%%rax)\n\t" 175a5afba16SPali Rohár "popq %%rdx\n\t" 176a5afba16SPali Rohár "movl %%edx,0(%%rax)\n\t" 177a5afba16SPali Rohár "pushfq\n\t" 178a5afba16SPali Rohár "popq %%rax\n\t" 179a5afba16SPali Rohár "andl $1,%%eax\n" 180a5afba16SPali Rohár : "=a"(rc) 181a5afba16SPali Rohár : "a"(regs) 182a5afba16SPali Rohár : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); 183a5afba16SPali Rohár #else 184a5afba16SPali Rohár asm volatile("pushl %%eax\n\t" 185a5afba16SPali Rohár "movl 0(%%eax),%%edx\n\t" 186a5afba16SPali Rohár "push %%edx\n\t" 187a5afba16SPali Rohár "movl 4(%%eax),%%ebx\n\t" 188a5afba16SPali Rohár "movl 8(%%eax),%%ecx\n\t" 189a5afba16SPali Rohár "movl 12(%%eax),%%edx\n\t" 190a5afba16SPali Rohár "movl 16(%%eax),%%esi\n\t" 191a5afba16SPali Rohár "movl 20(%%eax),%%edi\n\t" 192a5afba16SPali Rohár "popl %%eax\n\t" 193a5afba16SPali Rohár "out %%al,$0xb2\n\t" 194a5afba16SPali Rohár "out %%al,$0x84\n\t" 195a5afba16SPali Rohár "xchgl %%eax,(%%esp)\n\t" 196a5afba16SPali Rohár "movl %%ebx,4(%%eax)\n\t" 197a5afba16SPali Rohár "movl %%ecx,8(%%eax)\n\t" 198a5afba16SPali Rohár "movl %%edx,12(%%eax)\n\t" 199a5afba16SPali Rohár "movl %%esi,16(%%eax)\n\t" 200a5afba16SPali Rohár "movl %%edi,20(%%eax)\n\t" 201a5afba16SPali Rohár "popl %%edx\n\t" 202a5afba16SPali Rohár "movl %%edx,0(%%eax)\n\t" 203a5afba16SPali Rohár "lahf\n\t" 204a5afba16SPali Rohár "shrl $8,%%eax\n\t" 205a5afba16SPali Rohár "andl $1,%%eax\n" 206a5afba16SPali Rohár : "=a"(rc) 207a5afba16SPali Rohár : "a"(regs) 208a5afba16SPali Rohár : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); 209a5afba16SPali Rohár #endif 210a5afba16SPali Rohár if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax) 211a5afba16SPali Rohár rc = -EINVAL; 212a5afba16SPali Rohár 2139d58bec0SPali Rohár #ifdef DEBUG 2149d58bec0SPali Rohár rettime = ktime_get(); 2159d58bec0SPali Rohár delta = ktime_sub(rettime, calltime); 2169d58bec0SPali Rohár duration = ktime_to_ns(delta) >> 10; 2179d58bec0SPali Rohár pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs)\n", eax, ebx, 2189d58bec0SPali Rohár (rc ? 0xffff : regs->eax & 0xffff), duration); 2199d58bec0SPali Rohár #endif 2209d58bec0SPali Rohár 221a5afba16SPali Rohár return rc; 222a5afba16SPali Rohár } 223a5afba16SPali Rohár 224a5afba16SPali Rohár /* 22527046a3fSJuergen Gross * Call the System Management Mode BIOS. 22627046a3fSJuergen Gross */ 22727046a3fSJuergen Gross static int i8k_smm(struct smm_regs *regs) 22827046a3fSJuergen Gross { 22927046a3fSJuergen Gross int ret; 23027046a3fSJuergen Gross 23127046a3fSJuergen Gross get_online_cpus(); 23227046a3fSJuergen Gross ret = smp_call_on_cpu(0, i8k_smm_func, regs, true); 23327046a3fSJuergen Gross put_online_cpus(); 23427046a3fSJuergen Gross 23527046a3fSJuergen Gross return ret; 23627046a3fSJuergen Gross } 23727046a3fSJuergen Gross 23827046a3fSJuergen Gross /* 239a5afba16SPali Rohár * Read the fan status. 240a5afba16SPali Rohár */ 241a5afba16SPali Rohár static int i8k_get_fan_status(int fan) 242a5afba16SPali Rohár { 243a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; 244a5afba16SPali Rohár 245f480ea90SPali Rohár if (disallow_fan_support) 246f480ea90SPali Rohár return -EINVAL; 247f480ea90SPali Rohár 248a5afba16SPali Rohár regs.ebx = fan & 0xff; 249a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 250a5afba16SPali Rohár } 251a5afba16SPali Rohár 252a5afba16SPali Rohár /* 253a5afba16SPali Rohár * Read the fan speed in RPM. 254a5afba16SPali Rohár */ 255a5afba16SPali Rohár static int i8k_get_fan_speed(int fan) 256a5afba16SPali Rohár { 257a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; 258a5afba16SPali Rohár 259f480ea90SPali Rohár if (disallow_fan_support) 260f480ea90SPali Rohár return -EINVAL; 261f480ea90SPali Rohár 262a5afba16SPali Rohár regs.ebx = fan & 0xff; 263a5afba16SPali Rohár return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; 264a5afba16SPali Rohár } 265a5afba16SPali Rohár 266a5afba16SPali Rohár /* 267a5afba16SPali Rohár * Read the fan type. 268a5afba16SPali Rohár */ 2695ce91714SPali Rohár static int _i8k_get_fan_type(int fan) 270a5afba16SPali Rohár { 271a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; 272a5afba16SPali Rohár 273f480ea90SPali Rohár if (disallow_fan_support || disallow_fan_type_call) 2742744d2fdSPali Rohár return -EINVAL; 2752744d2fdSPali Rohár 276a5afba16SPali Rohár regs.ebx = fan & 0xff; 277a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 278a5afba16SPali Rohár } 279a5afba16SPali Rohár 2805ce91714SPali Rohár static int i8k_get_fan_type(int fan) 2815ce91714SPali Rohár { 2825ce91714SPali Rohár /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ 283747bc8b0SPali Rohár static int types[3] = { INT_MIN, INT_MIN, INT_MIN }; 2845ce91714SPali Rohár 2855ce91714SPali Rohár if (types[fan] == INT_MIN) 2865ce91714SPali Rohár types[fan] = _i8k_get_fan_type(fan); 2875ce91714SPali Rohár 2885ce91714SPali Rohár return types[fan]; 2895ce91714SPali Rohár } 2905ce91714SPali Rohár 291a5afba16SPali Rohár /* 292a5afba16SPali Rohár * Read the fan nominal rpm for specific fan speed. 293a5afba16SPali Rohár */ 294a5afba16SPali Rohár static int i8k_get_fan_nominal_speed(int fan, int speed) 295a5afba16SPali Rohár { 296a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; 297a5afba16SPali Rohár 298f480ea90SPali Rohár if (disallow_fan_support) 299f480ea90SPali Rohár return -EINVAL; 300f480ea90SPali Rohár 301a5afba16SPali Rohár regs.ebx = (fan & 0xff) | (speed << 8); 302a5afba16SPali Rohár return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; 303a5afba16SPali Rohár } 304a5afba16SPali Rohár 305a5afba16SPali Rohár /* 306afe45277SGiovanni Mascellani * Enable or disable automatic BIOS fan control support 307afe45277SGiovanni Mascellani */ 308afe45277SGiovanni Mascellani static int i8k_enable_fan_auto_mode(bool enable) 309afe45277SGiovanni Mascellani { 310afe45277SGiovanni Mascellani struct smm_regs regs = { }; 311afe45277SGiovanni Mascellani 312afe45277SGiovanni Mascellani if (disallow_fan_support) 313afe45277SGiovanni Mascellani return -EINVAL; 314afe45277SGiovanni Mascellani 315afe45277SGiovanni Mascellani regs.eax = enable ? auto_fan : manual_fan; 316afe45277SGiovanni Mascellani return i8k_smm(®s); 317afe45277SGiovanni Mascellani } 318afe45277SGiovanni Mascellani 319afe45277SGiovanni Mascellani /* 320a5afba16SPali Rohár * Set the fan speed (off, low, high). Returns the new fan status. 321a5afba16SPali Rohár */ 322a5afba16SPali Rohár static int i8k_set_fan(int fan, int speed) 323a5afba16SPali Rohár { 324a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; 325a5afba16SPali Rohár 326f480ea90SPali Rohár if (disallow_fan_support) 327f480ea90SPali Rohár return -EINVAL; 328f480ea90SPali Rohár 329a5afba16SPali Rohár speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); 330a5afba16SPali Rohár regs.ebx = (fan & 0xff) | (speed << 8); 331a5afba16SPali Rohár 332a5afba16SPali Rohár return i8k_smm(®s) ? : i8k_get_fan_status(fan); 333a5afba16SPali Rohár } 334a5afba16SPali Rohár 335a5afba16SPali Rohár static int i8k_get_temp_type(int sensor) 336a5afba16SPali Rohár { 337a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; 338a5afba16SPali Rohár 339a5afba16SPali Rohár regs.ebx = sensor & 0xff; 340a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 341a5afba16SPali Rohár } 342a5afba16SPali Rohár 343a5afba16SPali Rohár /* 344a5afba16SPali Rohár * Read the cpu temperature. 345a5afba16SPali Rohár */ 346a5afba16SPali Rohár static int _i8k_get_temp(int sensor) 347a5afba16SPali Rohár { 348a5afba16SPali Rohár struct smm_regs regs = { 349a5afba16SPali Rohár .eax = I8K_SMM_GET_TEMP, 350a5afba16SPali Rohár .ebx = sensor & 0xff, 351a5afba16SPali Rohár }; 352a5afba16SPali Rohár 353a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 354a5afba16SPali Rohár } 355a5afba16SPali Rohár 356a5afba16SPali Rohár static int i8k_get_temp(int sensor) 357a5afba16SPali Rohár { 358a5afba16SPali Rohár int temp = _i8k_get_temp(sensor); 359a5afba16SPali Rohár 360a5afba16SPali Rohár /* 361a5afba16SPali Rohár * Sometimes the temperature sensor returns 0x99, which is out of range. 362a5afba16SPali Rohár * In this case we retry (once) before returning an error. 363a5afba16SPali Rohár # 1003655137 00000058 00005a4b 364a5afba16SPali Rohár # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees 365a5afba16SPali Rohár # 1003655139 00000054 00005c52 366a5afba16SPali Rohár */ 367a5afba16SPali Rohár if (temp == 0x99) { 368a5afba16SPali Rohár msleep(100); 369a5afba16SPali Rohár temp = _i8k_get_temp(sensor); 370a5afba16SPali Rohár } 371a5afba16SPali Rohár /* 372a5afba16SPali Rohár * Return -ENODATA for all invalid temperatures. 373a5afba16SPali Rohár * 374a5afba16SPali Rohár * Known instances are the 0x99 value as seen above as well as 375a5afba16SPali Rohár * 0xc1 (193), which may be returned when trying to read the GPU 376a5afba16SPali Rohár * temperature if the system supports a GPU and it is currently 377a5afba16SPali Rohár * turned off. 378a5afba16SPali Rohár */ 379a5afba16SPali Rohár if (temp > I8K_MAX_TEMP) 380a5afba16SPali Rohár return -ENODATA; 381a5afba16SPali Rohár 382a5afba16SPali Rohár return temp; 383a5afba16SPali Rohár } 384a5afba16SPali Rohár 385a5afba16SPali Rohár static int i8k_get_dell_signature(int req_fn) 386a5afba16SPali Rohár { 387a5afba16SPali Rohár struct smm_regs regs = { .eax = req_fn, }; 388a5afba16SPali Rohár int rc; 389a5afba16SPali Rohár 390a5afba16SPali Rohár rc = i8k_smm(®s); 391a5afba16SPali Rohár if (rc < 0) 392a5afba16SPali Rohár return rc; 393a5afba16SPali Rohár 394a5afba16SPali Rohár return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1; 395a5afba16SPali Rohár } 396a5afba16SPali Rohár 397039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K) 398039ae585SPali Rohár 399039ae585SPali Rohár /* 400039ae585SPali Rohár * Read the Fn key status. 401039ae585SPali Rohár */ 402039ae585SPali Rohár static int i8k_get_fn_status(void) 403039ae585SPali Rohár { 404039ae585SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, }; 405039ae585SPali Rohár int rc; 406039ae585SPali Rohár 407039ae585SPali Rohár rc = i8k_smm(®s); 408039ae585SPali Rohár if (rc < 0) 409039ae585SPali Rohár return rc; 410039ae585SPali Rohár 411039ae585SPali Rohár switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) { 412039ae585SPali Rohár case I8K_FN_UP: 413039ae585SPali Rohár return I8K_VOL_UP; 414039ae585SPali Rohár case I8K_FN_DOWN: 415039ae585SPali Rohár return I8K_VOL_DOWN; 416039ae585SPali Rohár case I8K_FN_MUTE: 417039ae585SPali Rohár return I8K_VOL_MUTE; 418039ae585SPali Rohár default: 419039ae585SPali Rohár return 0; 420039ae585SPali Rohár } 421039ae585SPali Rohár } 422039ae585SPali Rohár 423039ae585SPali Rohár /* 424039ae585SPali Rohár * Read the power status. 425039ae585SPali Rohár */ 426039ae585SPali Rohár static int i8k_get_power_status(void) 427039ae585SPali Rohár { 428039ae585SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, }; 429039ae585SPali Rohár int rc; 430039ae585SPali Rohár 431039ae585SPali Rohár rc = i8k_smm(®s); 432039ae585SPali Rohár if (rc < 0) 433039ae585SPali Rohár return rc; 434039ae585SPali Rohár 435039ae585SPali Rohár return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY; 436039ae585SPali Rohár } 437039ae585SPali Rohár 438039ae585SPali Rohár /* 439039ae585SPali Rohár * Procfs interface 440039ae585SPali Rohár */ 441039ae585SPali Rohár 442a5afba16SPali Rohár static int 443a5afba16SPali Rohár i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) 444a5afba16SPali Rohár { 445a5afba16SPali Rohár int val = 0; 446a5afba16SPali Rohár int speed; 447a5afba16SPali Rohár unsigned char buff[16]; 448a5afba16SPali Rohár int __user *argp = (int __user *)arg; 449a5afba16SPali Rohár 450a5afba16SPali Rohár if (!argp) 451a5afba16SPali Rohár return -EINVAL; 452a5afba16SPali Rohár 453a5afba16SPali Rohár switch (cmd) { 454a5afba16SPali Rohár case I8K_BIOS_VERSION: 455053ea640SPali Rohár if (!isdigit(bios_version[0]) || !isdigit(bios_version[1]) || 456053ea640SPali Rohár !isdigit(bios_version[2])) 457053ea640SPali Rohár return -EINVAL; 458053ea640SPali Rohár 459a5afba16SPali Rohár val = (bios_version[0] << 16) | 460a5afba16SPali Rohár (bios_version[1] << 8) | bios_version[2]; 461a5afba16SPali Rohár break; 462a5afba16SPali Rohár 463a5afba16SPali Rohár case I8K_MACHINE_ID: 4647613663cSPali Rohár if (restricted && !capable(CAP_SYS_ADMIN)) 4657613663cSPali Rohár return -EPERM; 4667613663cSPali Rohár 4677613663cSPali Rohár memset(buff, 0, sizeof(buff)); 4687613663cSPali Rohár strlcpy(buff, bios_machineid, sizeof(buff)); 469a5afba16SPali Rohár break; 470a5afba16SPali Rohár 471a5afba16SPali Rohár case I8K_FN_STATUS: 472a5afba16SPali Rohár val = i8k_get_fn_status(); 473a5afba16SPali Rohár break; 474a5afba16SPali Rohár 475a5afba16SPali Rohár case I8K_POWER_STATUS: 476a5afba16SPali Rohár val = i8k_get_power_status(); 477a5afba16SPali Rohár break; 478a5afba16SPali Rohár 479a5afba16SPali Rohár case I8K_GET_TEMP: 480a5afba16SPali Rohár val = i8k_get_temp(0); 481a5afba16SPali Rohár break; 482a5afba16SPali Rohár 483a5afba16SPali Rohár case I8K_GET_SPEED: 484a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 485a5afba16SPali Rohár return -EFAULT; 486a5afba16SPali Rohár 487a5afba16SPali Rohár val = i8k_get_fan_speed(val); 488a5afba16SPali Rohár break; 489a5afba16SPali Rohár 490a5afba16SPali Rohár case I8K_GET_FAN: 491a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 492a5afba16SPali Rohár return -EFAULT; 493a5afba16SPali Rohár 494a5afba16SPali Rohár val = i8k_get_fan_status(val); 495a5afba16SPali Rohár break; 496a5afba16SPali Rohár 497a5afba16SPali Rohár case I8K_SET_FAN: 498a5afba16SPali Rohár if (restricted && !capable(CAP_SYS_ADMIN)) 499a5afba16SPali Rohár return -EPERM; 500a5afba16SPali Rohár 501a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 502a5afba16SPali Rohár return -EFAULT; 503a5afba16SPali Rohár 504a5afba16SPali Rohár if (copy_from_user(&speed, argp + 1, sizeof(int))) 505a5afba16SPali Rohár return -EFAULT; 506a5afba16SPali Rohár 507a5afba16SPali Rohár val = i8k_set_fan(val, speed); 508a5afba16SPali Rohár break; 509a5afba16SPali Rohár 510a5afba16SPali Rohár default: 511a5afba16SPali Rohár return -EINVAL; 512a5afba16SPali Rohár } 513a5afba16SPali Rohár 514a5afba16SPali Rohár if (val < 0) 515a5afba16SPali Rohár return val; 516a5afba16SPali Rohár 517a5afba16SPali Rohár switch (cmd) { 518a5afba16SPali Rohár case I8K_BIOS_VERSION: 519a5afba16SPali Rohár if (copy_to_user(argp, &val, 4)) 520a5afba16SPali Rohár return -EFAULT; 521a5afba16SPali Rohár 522a5afba16SPali Rohár break; 523a5afba16SPali Rohár case I8K_MACHINE_ID: 524a5afba16SPali Rohár if (copy_to_user(argp, buff, 16)) 525a5afba16SPali Rohár return -EFAULT; 526a5afba16SPali Rohár 527a5afba16SPali Rohár break; 528a5afba16SPali Rohár default: 529a5afba16SPali Rohár if (copy_to_user(argp, &val, sizeof(int))) 530a5afba16SPali Rohár return -EFAULT; 531a5afba16SPali Rohár 532a5afba16SPali Rohár break; 533a5afba16SPali Rohár } 534a5afba16SPali Rohár 535a5afba16SPali Rohár return 0; 536a5afba16SPali Rohár } 537a5afba16SPali Rohár 538a5afba16SPali Rohár static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) 539a5afba16SPali Rohár { 540a5afba16SPali Rohár long ret; 541a5afba16SPali Rohár 542a5afba16SPali Rohár mutex_lock(&i8k_mutex); 543a5afba16SPali Rohár ret = i8k_ioctl_unlocked(fp, cmd, arg); 544a5afba16SPali Rohár mutex_unlock(&i8k_mutex); 545a5afba16SPali Rohár 546a5afba16SPali Rohár return ret; 547a5afba16SPali Rohár } 548a5afba16SPali Rohár 549a5afba16SPali Rohár /* 550a5afba16SPali Rohár * Print the information for /proc/i8k. 551a5afba16SPali Rohár */ 552a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset) 553a5afba16SPali Rohár { 554a5afba16SPali Rohár int fn_key, cpu_temp, ac_power; 555a5afba16SPali Rohár int left_fan, right_fan, left_speed, right_speed; 556a5afba16SPali Rohár 557a5afba16SPali Rohár cpu_temp = i8k_get_temp(0); /* 11100 µs */ 558a5afba16SPali Rohár left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */ 559a5afba16SPali Rohár right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */ 560a5afba16SPali Rohár left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */ 561a5afba16SPali Rohár right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */ 562a5afba16SPali Rohár fn_key = i8k_get_fn_status(); /* 750 µs */ 563a5afba16SPali Rohár if (power_status) 564a5afba16SPali Rohár ac_power = i8k_get_power_status(); /* 14700 µs */ 565a5afba16SPali Rohár else 566a5afba16SPali Rohár ac_power = -1; 567a5afba16SPali Rohár 568a5afba16SPali Rohár /* 569a5afba16SPali Rohár * Info: 570a5afba16SPali Rohár * 571a5afba16SPali Rohár * 1) Format version (this will change if format changes) 572a5afba16SPali Rohár * 2) BIOS version 573a5afba16SPali Rohár * 3) BIOS machine ID 574a5afba16SPali Rohár * 4) Cpu temperature 575a5afba16SPali Rohár * 5) Left fan status 576a5afba16SPali Rohár * 6) Right fan status 577a5afba16SPali Rohár * 7) Left fan speed 578a5afba16SPali Rohár * 8) Right fan speed 579a5afba16SPali Rohár * 9) AC power 580a5afba16SPali Rohár * 10) Fn Key status 581a5afba16SPali Rohár */ 582a5afba16SPali Rohár seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", 583a5afba16SPali Rohár I8K_PROC_FMT, 584a5afba16SPali Rohár bios_version, 5857613663cSPali Rohár (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : bios_machineid, 586a5afba16SPali Rohár cpu_temp, 587a5afba16SPali Rohár left_fan, right_fan, left_speed, right_speed, 588a5afba16SPali Rohár ac_power, fn_key); 589a5afba16SPali Rohár 590a5afba16SPali Rohár return 0; 591a5afba16SPali Rohár } 592a5afba16SPali Rohár 593a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file) 594a5afba16SPali Rohár { 595a5afba16SPali Rohár return single_open(file, i8k_proc_show, NULL); 596a5afba16SPali Rohár } 597a5afba16SPali Rohár 59897a32539SAlexey Dobriyan static const struct proc_ops i8k_proc_ops = { 59997a32539SAlexey Dobriyan .proc_open = i8k_open_fs, 60097a32539SAlexey Dobriyan .proc_read = seq_read, 60197a32539SAlexey Dobriyan .proc_lseek = seq_lseek, 60297a32539SAlexey Dobriyan .proc_release = single_release, 60397a32539SAlexey Dobriyan .proc_ioctl = i8k_ioctl, 604039ae585SPali Rohár }; 605039ae585SPali Rohár 606039ae585SPali Rohár static void __init i8k_init_procfs(void) 607039ae585SPali Rohár { 608039ae585SPali Rohár /* Register the proc entry */ 60997a32539SAlexey Dobriyan proc_create("i8k", 0, NULL, &i8k_proc_ops); 610039ae585SPali Rohár } 611039ae585SPali Rohár 612039ae585SPali Rohár static void __exit i8k_exit_procfs(void) 613039ae585SPali Rohár { 614039ae585SPali Rohár remove_proc_entry("i8k", NULL); 615039ae585SPali Rohár } 616039ae585SPali Rohár 617039ae585SPali Rohár #else 618039ae585SPali Rohár 619039ae585SPali Rohár static inline void __init i8k_init_procfs(void) 620039ae585SPali Rohár { 621039ae585SPali Rohár } 622039ae585SPali Rohár 623039ae585SPali Rohár static inline void __exit i8k_exit_procfs(void) 624039ae585SPali Rohár { 625039ae585SPali Rohár } 626039ae585SPali Rohár 627039ae585SPali Rohár #endif 628a5afba16SPali Rohár 629a5afba16SPali Rohár /* 630a5afba16SPali Rohár * Hwmon interface 631a5afba16SPali Rohár */ 632a5afba16SPali Rohár 633ba949ed6SGuenter Roeck static ssize_t i8k_hwmon_temp_label_show(struct device *dev, 634a5afba16SPali Rohár struct device_attribute *devattr, 635a5afba16SPali Rohár char *buf) 636a5afba16SPali Rohár { 637a5afba16SPali Rohár static const char * const labels[] = { 638a5afba16SPali Rohár "CPU", 639a5afba16SPali Rohár "GPU", 640a5afba16SPali Rohár "SODIMM", 641a5afba16SPali Rohár "Other", 642a5afba16SPali Rohár "Ambient", 643a5afba16SPali Rohár "Other", 644a5afba16SPali Rohár }; 645a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 646a5afba16SPali Rohár int type; 647a5afba16SPali Rohár 648a5afba16SPali Rohár type = i8k_get_temp_type(index); 649a5afba16SPali Rohár if (type < 0) 650a5afba16SPali Rohár return type; 651a5afba16SPali Rohár if (type >= ARRAY_SIZE(labels)) 652a5afba16SPali Rohár type = ARRAY_SIZE(labels) - 1; 653a5afba16SPali Rohár return sprintf(buf, "%s\n", labels[type]); 654a5afba16SPali Rohár } 655a5afba16SPali Rohár 656ba949ed6SGuenter Roeck static ssize_t i8k_hwmon_temp_show(struct device *dev, 657a5afba16SPali Rohár struct device_attribute *devattr, 658a5afba16SPali Rohár char *buf) 659a5afba16SPali Rohár { 660a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 661a5afba16SPali Rohár int temp; 662a5afba16SPali Rohár 663a5afba16SPali Rohár temp = i8k_get_temp(index); 664a5afba16SPali Rohár if (temp < 0) 665a5afba16SPali Rohár return temp; 666a5afba16SPali Rohár return sprintf(buf, "%d\n", temp * 1000); 667a5afba16SPali Rohár } 668a5afba16SPali Rohár 669ba949ed6SGuenter Roeck static ssize_t i8k_hwmon_fan_label_show(struct device *dev, 670a5afba16SPali Rohár struct device_attribute *devattr, 671a5afba16SPali Rohár char *buf) 672a5afba16SPali Rohár { 673a5afba16SPali Rohár static const char * const labels[] = { 674a5afba16SPali Rohár "Processor Fan", 675a5afba16SPali Rohár "Motherboard Fan", 676a5afba16SPali Rohár "Video Fan", 677a5afba16SPali Rohár "Power Supply Fan", 678a5afba16SPali Rohár "Chipset Fan", 679a5afba16SPali Rohár "Other Fan", 680a5afba16SPali Rohár }; 681a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 682a5afba16SPali Rohár bool dock = false; 683a5afba16SPali Rohár int type; 684a5afba16SPali Rohár 685a5afba16SPali Rohár type = i8k_get_fan_type(index); 686a5afba16SPali Rohár if (type < 0) 687a5afba16SPali Rohár return type; 688a5afba16SPali Rohár 689a5afba16SPali Rohár if (type & 0x10) { 690a5afba16SPali Rohár dock = true; 691a5afba16SPali Rohár type &= 0x0F; 692a5afba16SPali Rohár } 693a5afba16SPali Rohár 694a5afba16SPali Rohár if (type >= ARRAY_SIZE(labels)) 695a5afba16SPali Rohár type = (ARRAY_SIZE(labels) - 1); 696a5afba16SPali Rohár 697a5afba16SPali Rohár return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]); 698a5afba16SPali Rohár } 699a5afba16SPali Rohár 700ba949ed6SGuenter Roeck static ssize_t i8k_hwmon_fan_show(struct device *dev, 701ba949ed6SGuenter Roeck struct device_attribute *devattr, char *buf) 702a5afba16SPali Rohár { 703a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 704a5afba16SPali Rohár int fan_speed; 705a5afba16SPali Rohár 706a5afba16SPali Rohár fan_speed = i8k_get_fan_speed(index); 707a5afba16SPali Rohár if (fan_speed < 0) 708a5afba16SPali Rohár return fan_speed; 709a5afba16SPali Rohár return sprintf(buf, "%d\n", fan_speed); 710a5afba16SPali Rohár } 711a5afba16SPali Rohár 712ba949ed6SGuenter Roeck static ssize_t i8k_hwmon_pwm_show(struct device *dev, 713ba949ed6SGuenter Roeck struct device_attribute *devattr, char *buf) 714a5afba16SPali Rohár { 715a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 716a5afba16SPali Rohár int status; 717a5afba16SPali Rohár 718a5afba16SPali Rohár status = i8k_get_fan_status(index); 719a5afba16SPali Rohár if (status < 0) 720a5afba16SPali Rohár return -EIO; 721a5afba16SPali Rohár return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255)); 722a5afba16SPali Rohár } 723a5afba16SPali Rohár 724ba949ed6SGuenter Roeck static ssize_t i8k_hwmon_pwm_store(struct device *dev, 725a5afba16SPali Rohár struct device_attribute *attr, 726a5afba16SPali Rohár const char *buf, size_t count) 727a5afba16SPali Rohár { 728a5afba16SPali Rohár int index = to_sensor_dev_attr(attr)->index; 729a5afba16SPali Rohár unsigned long val; 730a5afba16SPali Rohár int err; 731a5afba16SPali Rohár 732a5afba16SPali Rohár err = kstrtoul(buf, 10, &val); 733a5afba16SPali Rohár if (err) 734a5afba16SPali Rohár return err; 735a5afba16SPali Rohár val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max); 736a5afba16SPali Rohár 737a5afba16SPali Rohár mutex_lock(&i8k_mutex); 738a5afba16SPali Rohár err = i8k_set_fan(index, val); 739a5afba16SPali Rohár mutex_unlock(&i8k_mutex); 740a5afba16SPali Rohár 741a5afba16SPali Rohár return err < 0 ? -EIO : count; 742a5afba16SPali Rohár } 743a5afba16SPali Rohár 744afe45277SGiovanni Mascellani static ssize_t i8k_hwmon_pwm_enable_store(struct device *dev, 745afe45277SGiovanni Mascellani struct device_attribute *attr, 746afe45277SGiovanni Mascellani const char *buf, size_t count) 747afe45277SGiovanni Mascellani { 748afe45277SGiovanni Mascellani int err; 749afe45277SGiovanni Mascellani bool enable; 750afe45277SGiovanni Mascellani unsigned long val; 751afe45277SGiovanni Mascellani 752afe45277SGiovanni Mascellani if (!auto_fan) 753afe45277SGiovanni Mascellani return -ENODEV; 754afe45277SGiovanni Mascellani 755afe45277SGiovanni Mascellani err = kstrtoul(buf, 10, &val); 756afe45277SGiovanni Mascellani if (err) 757afe45277SGiovanni Mascellani return err; 758afe45277SGiovanni Mascellani 759afe45277SGiovanni Mascellani if (val == 1) 760afe45277SGiovanni Mascellani enable = false; 761afe45277SGiovanni Mascellani else if (val == 2) 762afe45277SGiovanni Mascellani enable = true; 763afe45277SGiovanni Mascellani else 764afe45277SGiovanni Mascellani return -EINVAL; 765afe45277SGiovanni Mascellani 766afe45277SGiovanni Mascellani mutex_lock(&i8k_mutex); 767afe45277SGiovanni Mascellani err = i8k_enable_fan_auto_mode(enable); 768afe45277SGiovanni Mascellani mutex_unlock(&i8k_mutex); 769afe45277SGiovanni Mascellani 770afe45277SGiovanni Mascellani return err ? err : count; 771afe45277SGiovanni Mascellani } 772afe45277SGiovanni Mascellani 773ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp1_input, i8k_hwmon_temp, 0); 774ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp1_label, i8k_hwmon_temp_label, 0); 775ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp2_input, i8k_hwmon_temp, 1); 776ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp2_label, i8k_hwmon_temp_label, 1); 777ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp3_input, i8k_hwmon_temp, 2); 778ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp3_label, i8k_hwmon_temp_label, 2); 779ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp4_input, i8k_hwmon_temp, 3); 780ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(temp4_label, i8k_hwmon_temp_label, 3); 7811bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp5_input, i8k_hwmon_temp, 4); 7821bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp5_label, i8k_hwmon_temp_label, 4); 7831bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp6_input, i8k_hwmon_temp, 5); 7841bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp6_label, i8k_hwmon_temp_label, 5); 7851bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp7_input, i8k_hwmon_temp, 6); 7861bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp7_label, i8k_hwmon_temp_label, 6); 7871bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp8_input, i8k_hwmon_temp, 7); 7881bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp8_label, i8k_hwmon_temp_label, 7); 7891bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp9_input, i8k_hwmon_temp, 8); 7901bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp9_label, i8k_hwmon_temp_label, 8); 7911bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp10_input, i8k_hwmon_temp, 9); 7921bb46a20SMichele Sorcinelli static SENSOR_DEVICE_ATTR_RO(temp10_label, i8k_hwmon_temp_label, 9); 793ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan1_input, i8k_hwmon_fan, 0); 794ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan1_label, i8k_hwmon_fan_label, 0); 795ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RW(pwm1, i8k_hwmon_pwm, 0); 796afe45277SGiovanni Mascellani static SENSOR_DEVICE_ATTR_WO(pwm1_enable, i8k_hwmon_pwm_enable, 0); 797ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan2_input, i8k_hwmon_fan, 1); 798ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan2_label, i8k_hwmon_fan_label, 1); 799ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RW(pwm2, i8k_hwmon_pwm, 1); 800ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan3_input, i8k_hwmon_fan, 2); 801ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RO(fan3_label, i8k_hwmon_fan_label, 2); 802ba949ed6SGuenter Roeck static SENSOR_DEVICE_ATTR_RW(pwm3, i8k_hwmon_pwm, 2); 803a5afba16SPali Rohár 804a5afba16SPali Rohár static struct attribute *i8k_attrs[] = { 805a5afba16SPali Rohár &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ 806a5afba16SPali Rohár &sensor_dev_attr_temp1_label.dev_attr.attr, /* 1 */ 807a5afba16SPali Rohár &sensor_dev_attr_temp2_input.dev_attr.attr, /* 2 */ 808a5afba16SPali Rohár &sensor_dev_attr_temp2_label.dev_attr.attr, /* 3 */ 809a5afba16SPali Rohár &sensor_dev_attr_temp3_input.dev_attr.attr, /* 4 */ 810a5afba16SPali Rohár &sensor_dev_attr_temp3_label.dev_attr.attr, /* 5 */ 811a5afba16SPali Rohár &sensor_dev_attr_temp4_input.dev_attr.attr, /* 6 */ 812a5afba16SPali Rohár &sensor_dev_attr_temp4_label.dev_attr.attr, /* 7 */ 8131bb46a20SMichele Sorcinelli &sensor_dev_attr_temp5_input.dev_attr.attr, /* 8 */ 8141bb46a20SMichele Sorcinelli &sensor_dev_attr_temp5_label.dev_attr.attr, /* 9 */ 8151bb46a20SMichele Sorcinelli &sensor_dev_attr_temp6_input.dev_attr.attr, /* 10 */ 8161bb46a20SMichele Sorcinelli &sensor_dev_attr_temp6_label.dev_attr.attr, /* 11 */ 8171bb46a20SMichele Sorcinelli &sensor_dev_attr_temp7_input.dev_attr.attr, /* 12 */ 8181bb46a20SMichele Sorcinelli &sensor_dev_attr_temp7_label.dev_attr.attr, /* 13 */ 8191bb46a20SMichele Sorcinelli &sensor_dev_attr_temp8_input.dev_attr.attr, /* 14 */ 8201bb46a20SMichele Sorcinelli &sensor_dev_attr_temp8_label.dev_attr.attr, /* 15 */ 8211bb46a20SMichele Sorcinelli &sensor_dev_attr_temp9_input.dev_attr.attr, /* 16 */ 8221bb46a20SMichele Sorcinelli &sensor_dev_attr_temp9_label.dev_attr.attr, /* 17 */ 8231bb46a20SMichele Sorcinelli &sensor_dev_attr_temp10_input.dev_attr.attr, /* 18 */ 8241bb46a20SMichele Sorcinelli &sensor_dev_attr_temp10_label.dev_attr.attr, /* 19 */ 8251bb46a20SMichele Sorcinelli &sensor_dev_attr_fan1_input.dev_attr.attr, /* 20 */ 8261bb46a20SMichele Sorcinelli &sensor_dev_attr_fan1_label.dev_attr.attr, /* 21 */ 8271bb46a20SMichele Sorcinelli &sensor_dev_attr_pwm1.dev_attr.attr, /* 22 */ 828afe45277SGiovanni Mascellani &sensor_dev_attr_pwm1_enable.dev_attr.attr, /* 23 */ 829afe45277SGiovanni Mascellani &sensor_dev_attr_fan2_input.dev_attr.attr, /* 24 */ 830afe45277SGiovanni Mascellani &sensor_dev_attr_fan2_label.dev_attr.attr, /* 25 */ 831afe45277SGiovanni Mascellani &sensor_dev_attr_pwm2.dev_attr.attr, /* 26 */ 832afe45277SGiovanni Mascellani &sensor_dev_attr_fan3_input.dev_attr.attr, /* 27 */ 833afe45277SGiovanni Mascellani &sensor_dev_attr_fan3_label.dev_attr.attr, /* 28 */ 834afe45277SGiovanni Mascellani &sensor_dev_attr_pwm3.dev_attr.attr, /* 29 */ 835a5afba16SPali Rohár NULL 836a5afba16SPali Rohár }; 837a5afba16SPali Rohár 838a5afba16SPali Rohár static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, 839a5afba16SPali Rohár int index) 840a5afba16SPali Rohár { 841f480ea90SPali Rohár if (disallow_fan_support && index >= 8) 842f480ea90SPali Rohár return 0; 8432744d2fdSPali Rohár if (disallow_fan_type_call && 844747bc8b0SPali Rohár (index == 9 || index == 12 || index == 15)) 8452744d2fdSPali Rohár return 0; 846a5afba16SPali Rohár if (index >= 0 && index <= 1 && 847a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) 848a5afba16SPali Rohár return 0; 849a5afba16SPali Rohár if (index >= 2 && index <= 3 && 850a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) 851a5afba16SPali Rohár return 0; 852a5afba16SPali Rohár if (index >= 4 && index <= 5 && 853a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) 854a5afba16SPali Rohár return 0; 855a5afba16SPali Rohár if (index >= 6 && index <= 7 && 856a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) 857a5afba16SPali Rohár return 0; 8581bb46a20SMichele Sorcinelli if (index >= 8 && index <= 9 && 8591bb46a20SMichele Sorcinelli !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5)) 8601bb46a20SMichele Sorcinelli return 0; 8611bb46a20SMichele Sorcinelli if (index >= 10 && index <= 11 && 8621bb46a20SMichele Sorcinelli !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6)) 8631bb46a20SMichele Sorcinelli return 0; 8641bb46a20SMichele Sorcinelli if (index >= 12 && index <= 13 && 8651bb46a20SMichele Sorcinelli !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7)) 8661bb46a20SMichele Sorcinelli return 0; 8671bb46a20SMichele Sorcinelli if (index >= 14 && index <= 15 && 8681bb46a20SMichele Sorcinelli !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8)) 8691bb46a20SMichele Sorcinelli return 0; 8701bb46a20SMichele Sorcinelli if (index >= 16 && index <= 17 && 8711bb46a20SMichele Sorcinelli !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9)) 8721bb46a20SMichele Sorcinelli return 0; 8731bb46a20SMichele Sorcinelli if (index >= 18 && index <= 19 && 8741bb46a20SMichele Sorcinelli !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10)) 8751bb46a20SMichele Sorcinelli return 0; 8761bb46a20SMichele Sorcinelli 877afe45277SGiovanni Mascellani if (index >= 20 && index <= 23 && 878a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) 879a5afba16SPali Rohár return 0; 880afe45277SGiovanni Mascellani if (index >= 24 && index <= 26 && 881a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) 882a5afba16SPali Rohár return 0; 883afe45277SGiovanni Mascellani if (index >= 27 && index <= 29 && 884747bc8b0SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) 885747bc8b0SPali Rohár return 0; 886a5afba16SPali Rohár 887afe45277SGiovanni Mascellani if (index == 23 && !auto_fan) 888afe45277SGiovanni Mascellani return 0; 889afe45277SGiovanni Mascellani 890a5afba16SPali Rohár return attr->mode; 891a5afba16SPali Rohár } 892a5afba16SPali Rohár 893a5afba16SPali Rohár static const struct attribute_group i8k_group = { 894a5afba16SPali Rohár .attrs = i8k_attrs, 895a5afba16SPali Rohár .is_visible = i8k_is_visible, 896a5afba16SPali Rohár }; 897a5afba16SPali Rohár __ATTRIBUTE_GROUPS(i8k); 898a5afba16SPali Rohár 899a5afba16SPali Rohár static int __init i8k_init_hwmon(void) 900a5afba16SPali Rohár { 901a5afba16SPali Rohár int err; 902a5afba16SPali Rohár 903a5afba16SPali Rohár i8k_hwmon_flags = 0; 904a5afba16SPali Rohár 905a5afba16SPali Rohár /* CPU temperature attributes, if temperature type is OK */ 906a5afba16SPali Rohár err = i8k_get_temp_type(0); 907a5afba16SPali Rohár if (err >= 0) 908a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; 909a5afba16SPali Rohár /* check for additional temperature sensors */ 910a5afba16SPali Rohár err = i8k_get_temp_type(1); 911a5afba16SPali Rohár if (err >= 0) 912a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; 913a5afba16SPali Rohár err = i8k_get_temp_type(2); 914a5afba16SPali Rohár if (err >= 0) 915a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; 916a5afba16SPali Rohár err = i8k_get_temp_type(3); 917a5afba16SPali Rohár if (err >= 0) 918a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; 9191bb46a20SMichele Sorcinelli err = i8k_get_temp_type(4); 9201bb46a20SMichele Sorcinelli if (err >= 0) 9211bb46a20SMichele Sorcinelli i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP5; 9221bb46a20SMichele Sorcinelli err = i8k_get_temp_type(5); 9231bb46a20SMichele Sorcinelli if (err >= 0) 9241bb46a20SMichele Sorcinelli i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP6; 9251bb46a20SMichele Sorcinelli err = i8k_get_temp_type(6); 9261bb46a20SMichele Sorcinelli if (err >= 0) 9271bb46a20SMichele Sorcinelli i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP7; 9281bb46a20SMichele Sorcinelli err = i8k_get_temp_type(7); 9291bb46a20SMichele Sorcinelli if (err >= 0) 9301bb46a20SMichele Sorcinelli i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP8; 9311bb46a20SMichele Sorcinelli err = i8k_get_temp_type(8); 9321bb46a20SMichele Sorcinelli if (err >= 0) 9331bb46a20SMichele Sorcinelli i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP9; 9341bb46a20SMichele Sorcinelli err = i8k_get_temp_type(9); 9351bb46a20SMichele Sorcinelli if (err >= 0) 9361bb46a20SMichele Sorcinelli i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP10; 937a5afba16SPali Rohár 9385ce91714SPali Rohár /* First fan attributes, if fan status or type is OK */ 9395ce91714SPali Rohár err = i8k_get_fan_status(0); 9405ce91714SPali Rohár if (err < 0) 941a5afba16SPali Rohár err = i8k_get_fan_type(0); 942a5afba16SPali Rohár if (err >= 0) 943a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; 944a5afba16SPali Rohár 9455ce91714SPali Rohár /* Second fan attributes, if fan status or type is OK */ 9465ce91714SPali Rohár err = i8k_get_fan_status(1); 9475ce91714SPali Rohár if (err < 0) 948a5afba16SPali Rohár err = i8k_get_fan_type(1); 949a5afba16SPali Rohár if (err >= 0) 950a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; 951a5afba16SPali Rohár 952747bc8b0SPali Rohár /* Third fan attributes, if fan status or type is OK */ 953747bc8b0SPali Rohár err = i8k_get_fan_status(2); 954747bc8b0SPali Rohár if (err < 0) 955747bc8b0SPali Rohár err = i8k_get_fan_type(2); 956747bc8b0SPali Rohár if (err >= 0) 957747bc8b0SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; 958747bc8b0SPali Rohár 9599026cae1SGabriele Mazzotta i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm", 960039ae585SPali Rohár NULL, i8k_groups); 961a5afba16SPali Rohár if (IS_ERR(i8k_hwmon_dev)) { 962a5afba16SPali Rohár err = PTR_ERR(i8k_hwmon_dev); 963a5afba16SPali Rohár i8k_hwmon_dev = NULL; 964a5afba16SPali Rohár pr_err("hwmon registration failed (%d)\n", err); 965a5afba16SPali Rohár return err; 966a5afba16SPali Rohár } 967a5afba16SPali Rohár return 0; 968a5afba16SPali Rohár } 969a5afba16SPali Rohár 970a5afba16SPali Rohár struct i8k_config_data { 971a5afba16SPali Rohár uint fan_mult; 972a5afba16SPali Rohár uint fan_max; 973a5afba16SPali Rohár }; 974a5afba16SPali Rohár 975a5afba16SPali Rohár enum i8k_configs { 976a5afba16SPali Rohár DELL_LATITUDE_D520, 977a5afba16SPali Rohár DELL_PRECISION_490, 978a5afba16SPali Rohár DELL_STUDIO, 979a5afba16SPali Rohár DELL_XPS, 980a5afba16SPali Rohár }; 981a5afba16SPali Rohár 982a5afba16SPali Rohár static const struct i8k_config_data i8k_config_data[] = { 983a5afba16SPali Rohár [DELL_LATITUDE_D520] = { 984a5afba16SPali Rohár .fan_mult = 1, 985a5afba16SPali Rohár .fan_max = I8K_FAN_TURBO, 986a5afba16SPali Rohár }, 987a5afba16SPali Rohár [DELL_PRECISION_490] = { 988a5afba16SPali Rohár .fan_mult = 1, 989a5afba16SPali Rohár .fan_max = I8K_FAN_TURBO, 990a5afba16SPali Rohár }, 991a5afba16SPali Rohár [DELL_STUDIO] = { 992a5afba16SPali Rohár .fan_mult = 1, 993a5afba16SPali Rohár .fan_max = I8K_FAN_HIGH, 994a5afba16SPali Rohár }, 995a5afba16SPali Rohár [DELL_XPS] = { 996a5afba16SPali Rohár .fan_mult = 1, 997a5afba16SPali Rohár .fan_max = I8K_FAN_HIGH, 998a5afba16SPali Rohár }, 999a5afba16SPali Rohár }; 1000a5afba16SPali Rohár 10016faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_dmi_table[] __initconst = { 1002a5afba16SPali Rohár { 1003a5afba16SPali Rohár .ident = "Dell Inspiron", 1004a5afba16SPali Rohár .matches = { 1005a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), 1006a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), 1007a5afba16SPali Rohár }, 1008a5afba16SPali Rohár }, 1009a5afba16SPali Rohár { 1010a5afba16SPali Rohár .ident = "Dell Latitude", 1011a5afba16SPali Rohár .matches = { 1012a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), 1013a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), 1014a5afba16SPali Rohár }, 1015a5afba16SPali Rohár }, 1016a5afba16SPali Rohár { 1017a5afba16SPali Rohár .ident = "Dell Inspiron 2", 1018a5afba16SPali Rohár .matches = { 1019a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1020a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), 1021a5afba16SPali Rohár }, 1022a5afba16SPali Rohár }, 1023a5afba16SPali Rohár { 1024a5afba16SPali Rohár .ident = "Dell Latitude D520", 1025a5afba16SPali Rohár .matches = { 1026a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1027a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), 1028a5afba16SPali Rohár }, 1029a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], 1030a5afba16SPali Rohár }, 1031a5afba16SPali Rohár { 1032a5afba16SPali Rohár .ident = "Dell Latitude 2", 1033a5afba16SPali Rohár .matches = { 1034a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1035a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), 1036a5afba16SPali Rohár }, 1037a5afba16SPali Rohár }, 1038a5afba16SPali Rohár { /* UK Inspiron 6400 */ 1039a5afba16SPali Rohár .ident = "Dell Inspiron 3", 1040a5afba16SPali Rohár .matches = { 1041a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1042a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MM061"), 1043a5afba16SPali Rohár }, 1044a5afba16SPali Rohár }, 1045a5afba16SPali Rohár { 1046a5afba16SPali Rohár .ident = "Dell Inspiron 3", 1047a5afba16SPali Rohár .matches = { 1048a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1049a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MP061"), 1050a5afba16SPali Rohár }, 1051a5afba16SPali Rohár }, 1052a5afba16SPali Rohár { 1053a5afba16SPali Rohár .ident = "Dell Precision 490", 1054a5afba16SPali Rohár .matches = { 1055a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1056a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, 1057a5afba16SPali Rohár "Precision WorkStation 490"), 1058a5afba16SPali Rohár }, 1059a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], 1060a5afba16SPali Rohár }, 1061a5afba16SPali Rohár { 1062a5afba16SPali Rohár .ident = "Dell Precision", 1063a5afba16SPali Rohár .matches = { 1064a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1065a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Precision"), 1066a5afba16SPali Rohár }, 1067a5afba16SPali Rohár }, 1068a5afba16SPali Rohár { 1069a5afba16SPali Rohár .ident = "Dell Vostro", 1070a5afba16SPali Rohár .matches = { 1071a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1072a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"), 1073a5afba16SPali Rohár }, 1074a5afba16SPali Rohár }, 1075a5afba16SPali Rohár { 1076a5afba16SPali Rohár .ident = "Dell Studio", 1077a5afba16SPali Rohár .matches = { 1078a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1079a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), 1080a5afba16SPali Rohár }, 1081a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_STUDIO], 1082a5afba16SPali Rohár }, 1083a5afba16SPali Rohár { 1084a5afba16SPali Rohár .ident = "Dell XPS M140", 1085a5afba16SPali Rohár .matches = { 1086a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1087a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), 1088a5afba16SPali Rohár }, 1089a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_XPS], 1090a5afba16SPali Rohár }, 1091a4811b6cSPali Rohár { 1092b8a13e5eSThomas Hebb .ident = "Dell XPS", 1093a4811b6cSPali Rohár .matches = { 1094a4811b6cSPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1095b8a13e5eSThomas Hebb DMI_MATCH(DMI_PRODUCT_NAME, "XPS"), 1096162372b0SMichele Sorcinelli }, 1097162372b0SMichele Sorcinelli }, 1098a5afba16SPali Rohár { } 1099a5afba16SPali Rohár }; 1100a5afba16SPali Rohár 1101a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table); 1102a5afba16SPali Rohár 1103a4b45b25SPali Rohár /* 11042744d2fdSPali Rohár * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed 11052744d2fdSPali Rohár * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist 11062744d2fdSPali Rohár * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call. 11072744d2fdSPali Rohár * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121 11086220f4ebSThorsten Leemhuis */ 11096faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = { 11102744d2fdSPali Rohár { 11116220f4ebSThorsten Leemhuis .ident = "Dell Studio XPS 8000", 11126220f4ebSThorsten Leemhuis .matches = { 11136220f4ebSThorsten Leemhuis DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 11146220f4ebSThorsten Leemhuis DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"), 11156220f4ebSThorsten Leemhuis }, 11166220f4ebSThorsten Leemhuis }, 11176220f4ebSThorsten Leemhuis { 1118a4b45b25SPali Rohár .ident = "Dell Studio XPS 8100", 1119a4b45b25SPali Rohár .matches = { 1120a4b45b25SPali Rohár DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1121a4b45b25SPali Rohár DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"), 1122a4b45b25SPali Rohár }, 1123a4b45b25SPali Rohár }, 11242744d2fdSPali Rohár { 11252744d2fdSPali Rohár .ident = "Dell Inspiron 580", 11262744d2fdSPali Rohár .matches = { 11272744d2fdSPali Rohár DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 11282744d2fdSPali Rohár DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "), 11292744d2fdSPali Rohár }, 11302744d2fdSPali Rohár }, 1131a4b45b25SPali Rohár { } 1132a4b45b25SPali Rohár }; 1133a4b45b25SPali Rohár 1134a5afba16SPali Rohár /* 1135f480ea90SPali Rohár * On some machines all fan related SMM functions implemented by Dell BIOS 1136f480ea90SPali Rohár * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan 1137f480ea90SPali Rohár * support for affected blacklisted Dell machines stay disabled. 1138f480ea90SPali Rohár * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751 1139f480ea90SPali Rohár */ 1140f480ea90SPali Rohár static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { 1141f480ea90SPali Rohár { 1142f480ea90SPali Rohár .ident = "Dell Inspiron 7720", 1143f480ea90SPali Rohár .matches = { 1144f480ea90SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1145f480ea90SPali Rohár DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), 1146f480ea90SPali Rohár }, 1147f480ea90SPali Rohár }, 11486fbc4232SOleksandr Natalenko { 11496fbc4232SOleksandr Natalenko .ident = "Dell Vostro 3360", 11506fbc4232SOleksandr Natalenko .matches = { 11516fbc4232SOleksandr Natalenko DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 11526fbc4232SOleksandr Natalenko DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), 11536fbc4232SOleksandr Natalenko }, 11546fbc4232SOleksandr Natalenko }, 1155536e0019SHelge Eichelberg { 1156536e0019SHelge Eichelberg .ident = "Dell XPS13 9333", 1157536e0019SHelge Eichelberg .matches = { 1158536e0019SHelge Eichelberg DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1159536e0019SHelge Eichelberg DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), 1160536e0019SHelge Eichelberg }, 1161536e0019SHelge Eichelberg }, 1162f480ea90SPali Rohár { } 1163f480ea90SPali Rohár }; 1164f480ea90SPali Rohár 1165afe45277SGiovanni Mascellani struct i8k_fan_control_data { 1166afe45277SGiovanni Mascellani unsigned int manual_fan; 1167afe45277SGiovanni Mascellani unsigned int auto_fan; 1168afe45277SGiovanni Mascellani }; 1169afe45277SGiovanni Mascellani 1170afe45277SGiovanni Mascellani enum i8k_fan_controls { 1171afe45277SGiovanni Mascellani I8K_FAN_34A3_35A3, 1172afe45277SGiovanni Mascellani }; 1173afe45277SGiovanni Mascellani 1174afe45277SGiovanni Mascellani static const struct i8k_fan_control_data i8k_fan_control_data[] = { 1175afe45277SGiovanni Mascellani [I8K_FAN_34A3_35A3] = { 1176afe45277SGiovanni Mascellani .manual_fan = 0x34a3, 1177afe45277SGiovanni Mascellani .auto_fan = 0x35a3, 1178afe45277SGiovanni Mascellani }, 1179afe45277SGiovanni Mascellani }; 1180afe45277SGiovanni Mascellani 1181afe45277SGiovanni Mascellani static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { 1182afe45277SGiovanni Mascellani { 1183afe45277SGiovanni Mascellani .ident = "Dell Precision 5530", 1184afe45277SGiovanni Mascellani .matches = { 1185afe45277SGiovanni Mascellani DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1186afe45277SGiovanni Mascellani DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"), 1187afe45277SGiovanni Mascellani }, 1188afe45277SGiovanni Mascellani .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], 1189afe45277SGiovanni Mascellani }, 1190afe45277SGiovanni Mascellani { 1191*0ca8bb2cSJeffrey Lin .ident = "Dell Latitude 5480", 1192*0ca8bb2cSJeffrey Lin .matches = { 1193*0ca8bb2cSJeffrey Lin DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1194*0ca8bb2cSJeffrey Lin DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"), 1195*0ca8bb2cSJeffrey Lin }, 1196*0ca8bb2cSJeffrey Lin .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], 1197*0ca8bb2cSJeffrey Lin }, 1198*0ca8bb2cSJeffrey Lin { 1199afe45277SGiovanni Mascellani .ident = "Dell Latitude E6440", 1200afe45277SGiovanni Mascellani .matches = { 1201afe45277SGiovanni Mascellani DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1202afe45277SGiovanni Mascellani DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"), 1203afe45277SGiovanni Mascellani }, 1204afe45277SGiovanni Mascellani .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], 1205afe45277SGiovanni Mascellani }, 1206afe45277SGiovanni Mascellani { } 1207afe45277SGiovanni Mascellani }; 1208afe45277SGiovanni Mascellani 1209f480ea90SPali Rohár /* 1210a5afba16SPali Rohár * Probe for the presence of a supported laptop. 1211a5afba16SPali Rohár */ 1212a5afba16SPali Rohár static int __init i8k_probe(void) 1213a5afba16SPali Rohár { 1214afe45277SGiovanni Mascellani const struct dmi_system_id *id, *fan_control; 1215a5afba16SPali Rohár int fan, ret; 1216a5afba16SPali Rohár 1217a5afba16SPali Rohár /* 1218a5afba16SPali Rohár * Get DMI information 1219a5afba16SPali Rohár */ 12202744d2fdSPali Rohár if (!dmi_check_system(i8k_dmi_table)) { 1221a5afba16SPali Rohár if (!ignore_dmi && !force) 1222a5afba16SPali Rohár return -ENODEV; 1223a5afba16SPali Rohár 1224a5afba16SPali Rohár pr_info("not running on a supported Dell system.\n"); 1225a5afba16SPali Rohár pr_info("vendor=%s, model=%s, version=%s\n", 1226a5afba16SPali Rohár i8k_get_dmi_data(DMI_SYS_VENDOR), 1227a5afba16SPali Rohár i8k_get_dmi_data(DMI_PRODUCT_NAME), 1228a5afba16SPali Rohár i8k_get_dmi_data(DMI_BIOS_VERSION)); 1229a5afba16SPali Rohár } 1230a5afba16SPali Rohár 1231f480ea90SPali Rohár if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { 1232f480ea90SPali Rohár pr_warn("broken Dell BIOS detected, disallow fan support\n"); 1233f480ea90SPali Rohár if (!force) 1234f480ea90SPali Rohár disallow_fan_support = true; 1235f480ea90SPali Rohár } 1236f480ea90SPali Rohár 1237836ad112SPali Rohár if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { 1238836ad112SPali Rohár pr_warn("broken Dell BIOS detected, disallow fan type call\n"); 1239836ad112SPali Rohár if (!force) 12402744d2fdSPali Rohár disallow_fan_type_call = true; 1241836ad112SPali Rohár } 12422744d2fdSPali Rohár 1243a5afba16SPali Rohár strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), 1244a5afba16SPali Rohár sizeof(bios_version)); 12457613663cSPali Rohár strlcpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), 12467613663cSPali Rohár sizeof(bios_machineid)); 1247a5afba16SPali Rohár 1248a5afba16SPali Rohár /* 1249a5afba16SPali Rohár * Get SMM Dell signature 1250a5afba16SPali Rohár */ 1251a5afba16SPali Rohár if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && 1252a5afba16SPali Rohár i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { 1253a5afba16SPali Rohár pr_err("unable to get SMM Dell signature\n"); 1254a5afba16SPali Rohár if (!force) 1255a5afba16SPali Rohár return -ENODEV; 1256a5afba16SPali Rohár } 1257a5afba16SPali Rohár 1258a5afba16SPali Rohár /* 1259a5afba16SPali Rohár * Set fan multiplier and maximal fan speed from dmi config 1260a5afba16SPali Rohár * Values specified in module parameters override values from dmi 1261a5afba16SPali Rohár */ 1262a5afba16SPali Rohár id = dmi_first_match(i8k_dmi_table); 1263a5afba16SPali Rohár if (id && id->driver_data) { 1264a5afba16SPali Rohár const struct i8k_config_data *conf = id->driver_data; 1265a5afba16SPali Rohár if (!fan_mult && conf->fan_mult) 1266a5afba16SPali Rohár fan_mult = conf->fan_mult; 1267a5afba16SPali Rohár if (!fan_max && conf->fan_max) 1268a5afba16SPali Rohár fan_max = conf->fan_max; 1269a5afba16SPali Rohár } 1270a5afba16SPali Rohár 1271a5afba16SPali Rohár i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ 1272a5afba16SPali Rohár i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max); 1273a5afba16SPali Rohár 1274afe45277SGiovanni Mascellani fan_control = dmi_first_match(i8k_whitelist_fan_control); 1275afe45277SGiovanni Mascellani if (fan_control && fan_control->driver_data) { 1276afe45277SGiovanni Mascellani const struct i8k_fan_control_data *data = fan_control->driver_data; 1277afe45277SGiovanni Mascellani 1278afe45277SGiovanni Mascellani manual_fan = data->manual_fan; 1279afe45277SGiovanni Mascellani auto_fan = data->auto_fan; 1280afe45277SGiovanni Mascellani pr_info("enabling support for setting automatic/manual fan control\n"); 1281afe45277SGiovanni Mascellani } 1282afe45277SGiovanni Mascellani 1283a5afba16SPali Rohár if (!fan_mult) { 1284a5afba16SPali Rohár /* 1285a5afba16SPali Rohár * Autodetect fan multiplier based on nominal rpm 1286a5afba16SPali Rohár * If fan reports rpm value too high then set multiplier to 1 1287a5afba16SPali Rohár */ 1288a5afba16SPali Rohár for (fan = 0; fan < 2; ++fan) { 1289a5afba16SPali Rohár ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max); 1290a5afba16SPali Rohár if (ret < 0) 1291a5afba16SPali Rohár continue; 1292a5afba16SPali Rohár if (ret > I8K_FAN_MAX_RPM) 1293a5afba16SPali Rohár i8k_fan_mult = 1; 1294a5afba16SPali Rohár break; 1295a5afba16SPali Rohár } 1296a5afba16SPali Rohár } else { 1297a5afba16SPali Rohár /* Fan multiplier was specified in module param or in dmi */ 1298a5afba16SPali Rohár i8k_fan_mult = fan_mult; 1299a5afba16SPali Rohár } 1300a5afba16SPali Rohár 1301a5afba16SPali Rohár return 0; 1302a5afba16SPali Rohár } 1303a5afba16SPali Rohár 1304a5afba16SPali Rohár static int __init i8k_init(void) 1305a5afba16SPali Rohár { 1306a5afba16SPali Rohár int err; 1307a5afba16SPali Rohár 1308a5afba16SPali Rohár /* Are we running on an supported laptop? */ 1309a5afba16SPali Rohár if (i8k_probe()) 1310a5afba16SPali Rohár return -ENODEV; 1311a5afba16SPali Rohár 1312a5afba16SPali Rohár err = i8k_init_hwmon(); 1313a5afba16SPali Rohár if (err) 1314a5afba16SPali Rohár return err; 1315039ae585SPali Rohár 1316039ae585SPali Rohár i8k_init_procfs(); 1317039ae585SPali Rohár return 0; 1318a5afba16SPali Rohár } 1319a5afba16SPali Rohár 1320a5afba16SPali Rohár static void __exit i8k_exit(void) 1321a5afba16SPali Rohár { 1322a5afba16SPali Rohár hwmon_device_unregister(i8k_hwmon_dev); 1323039ae585SPali Rohár i8k_exit_procfs(); 1324a5afba16SPali Rohár } 1325a5afba16SPali Rohár 1326a5afba16SPali Rohár module_init(i8k_init); 1327a5afba16SPali Rohár module_exit(i8k_exit); 1328