1a5afba16SPali Rohár /* 2a5afba16SPali Rohár * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops. 3a5afba16SPali Rohár * 4a5afba16SPali Rohár * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org> 5a5afba16SPali Rohár * 6a5afba16SPali Rohár * Hwmon integration: 7a5afba16SPali Rohár * Copyright (C) 2011 Jean Delvare <jdelvare@suse.de> 8a5afba16SPali Rohár * Copyright (C) 2013, 2014 Guenter Roeck <linux@roeck-us.net> 9a5afba16SPali Rohár * Copyright (C) 2014, 2015 Pali Rohár <pali.rohar@gmail.com> 10a5afba16SPali Rohár * 11a5afba16SPali Rohár * This program is free software; you can redistribute it and/or modify it 12a5afba16SPali Rohár * under the terms of the GNU General Public License as published by the 13a5afba16SPali Rohár * Free Software Foundation; either version 2, or (at your option) any 14a5afba16SPali Rohár * later version. 15a5afba16SPali Rohár * 16a5afba16SPali Rohár * This program is distributed in the hope that it will be useful, but 17a5afba16SPali Rohár * WITHOUT ANY WARRANTY; without even the implied warranty of 18a5afba16SPali Rohár * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19a5afba16SPali Rohár * General Public License for more details. 20a5afba16SPali Rohár */ 21a5afba16SPali Rohár 22a5afba16SPali Rohár #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 23a5afba16SPali Rohár 24*27046a3fSJuergen Gross #include <linux/cpu.h> 25a5afba16SPali Rohár #include <linux/delay.h> 26a5afba16SPali Rohár #include <linux/module.h> 27a5afba16SPali Rohár #include <linux/types.h> 28a5afba16SPali Rohár #include <linux/init.h> 29a5afba16SPali Rohár #include <linux/proc_fs.h> 30a5afba16SPali Rohár #include <linux/seq_file.h> 31a5afba16SPali Rohár #include <linux/dmi.h> 32a5afba16SPali Rohár #include <linux/capability.h> 33a5afba16SPali Rohár #include <linux/mutex.h> 34a5afba16SPali Rohár #include <linux/hwmon.h> 35a5afba16SPali Rohár #include <linux/hwmon-sysfs.h> 36a5afba16SPali Rohár #include <linux/uaccess.h> 37a5afba16SPali Rohár #include <linux/io.h> 38a5afba16SPali Rohár #include <linux/sched.h> 39053ea640SPali Rohár #include <linux/ctype.h> 40*27046a3fSJuergen Gross #include <linux/smp.h> 41a5afba16SPali Rohár 42a5afba16SPali Rohár #include <linux/i8k.h> 43a5afba16SPali Rohár 44a5afba16SPali Rohár #define I8K_SMM_FN_STATUS 0x0025 45a5afba16SPali Rohár #define I8K_SMM_POWER_STATUS 0x0069 46a5afba16SPali Rohár #define I8K_SMM_SET_FAN 0x01a3 47a5afba16SPali Rohár #define I8K_SMM_GET_FAN 0x00a3 48a5afba16SPali Rohár #define I8K_SMM_GET_SPEED 0x02a3 49a5afba16SPali Rohár #define I8K_SMM_GET_FAN_TYPE 0x03a3 50a5afba16SPali Rohár #define I8K_SMM_GET_NOM_SPEED 0x04a3 51a5afba16SPali Rohár #define I8K_SMM_GET_TEMP 0x10a3 52a5afba16SPali Rohár #define I8K_SMM_GET_TEMP_TYPE 0x11a3 53a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG1 0xfea3 54a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG2 0xffa3 55a5afba16SPali Rohár 56a5afba16SPali Rohár #define I8K_FAN_MULT 30 57a5afba16SPali Rohár #define I8K_FAN_MAX_RPM 30000 58a5afba16SPali Rohár #define I8K_MAX_TEMP 127 59a5afba16SPali Rohár 60a5afba16SPali Rohár #define I8K_FN_NONE 0x00 61a5afba16SPali Rohár #define I8K_FN_UP 0x01 62a5afba16SPali Rohár #define I8K_FN_DOWN 0x02 63a5afba16SPali Rohár #define I8K_FN_MUTE 0x04 64a5afba16SPali Rohár #define I8K_FN_MASK 0x07 65a5afba16SPali Rohár #define I8K_FN_SHIFT 8 66a5afba16SPali Rohár 67a5afba16SPali Rohár #define I8K_POWER_AC 0x05 68a5afba16SPali Rohár #define I8K_POWER_BATTERY 0x01 69a5afba16SPali Rohár 70a5afba16SPali Rohár static DEFINE_MUTEX(i8k_mutex); 71a5afba16SPali Rohár static char bios_version[4]; 727613663cSPali Rohár static char bios_machineid[16]; 73a5afba16SPali Rohár static struct device *i8k_hwmon_dev; 74a5afba16SPali Rohár static u32 i8k_hwmon_flags; 75a5afba16SPali Rohár static uint i8k_fan_mult = I8K_FAN_MULT; 76a5afba16SPali Rohár static uint i8k_pwm_mult; 77a5afba16SPali Rohár static uint i8k_fan_max = I8K_FAN_HIGH; 782744d2fdSPali Rohár static bool disallow_fan_type_call; 79a5afba16SPali Rohár 80a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP1 (1 << 0) 81a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP2 (1 << 1) 82a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP3 (1 << 2) 83a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP4 (1 << 3) 84a5afba16SPali Rohár #define I8K_HWMON_HAVE_FAN1 (1 << 4) 85a5afba16SPali Rohár #define I8K_HWMON_HAVE_FAN2 (1 << 5) 86747bc8b0SPali Rohár #define I8K_HWMON_HAVE_FAN3 (1 << 6) 87a5afba16SPali Rohár 88a5afba16SPali Rohár MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); 89a5afba16SPali Rohár MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); 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 */ 139*27046a3fSJuergen Gross static int i8k_smm_func(void *par) 140a5afba16SPali Rohár { 141a5afba16SPali Rohár int rc; 142*27046a3fSJuergen 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 */ 154*27046a3fSJuergen Gross if (smp_processor_id() != 0) 155*27046a3fSJuergen 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 /* 225*27046a3fSJuergen Gross * Call the System Management Mode BIOS. 226*27046a3fSJuergen Gross */ 227*27046a3fSJuergen Gross static int i8k_smm(struct smm_regs *regs) 228*27046a3fSJuergen Gross { 229*27046a3fSJuergen Gross int ret; 230*27046a3fSJuergen Gross 231*27046a3fSJuergen Gross get_online_cpus(); 232*27046a3fSJuergen Gross ret = smp_call_on_cpu(0, i8k_smm_func, regs, true); 233*27046a3fSJuergen Gross put_online_cpus(); 234*27046a3fSJuergen Gross 235*27046a3fSJuergen Gross return ret; 236*27046a3fSJuergen Gross } 237*27046a3fSJuergen Gross 238*27046a3fSJuergen 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 245a5afba16SPali Rohár regs.ebx = fan & 0xff; 246a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 247a5afba16SPali Rohár } 248a5afba16SPali Rohár 249a5afba16SPali Rohár /* 250a5afba16SPali Rohár * Read the fan speed in RPM. 251a5afba16SPali Rohár */ 252a5afba16SPali Rohár static int i8k_get_fan_speed(int fan) 253a5afba16SPali Rohár { 254a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; 255a5afba16SPali Rohár 256a5afba16SPali Rohár regs.ebx = fan & 0xff; 257a5afba16SPali Rohár return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; 258a5afba16SPali Rohár } 259a5afba16SPali Rohár 260a5afba16SPali Rohár /* 261a5afba16SPali Rohár * Read the fan type. 262a5afba16SPali Rohár */ 2635ce91714SPali Rohár static int _i8k_get_fan_type(int fan) 264a5afba16SPali Rohár { 265a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; 266a5afba16SPali Rohár 2672744d2fdSPali Rohár if (disallow_fan_type_call) 2682744d2fdSPali Rohár return -EINVAL; 2692744d2fdSPali Rohár 270a5afba16SPali Rohár regs.ebx = fan & 0xff; 271a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 272a5afba16SPali Rohár } 273a5afba16SPali Rohár 2745ce91714SPali Rohár static int i8k_get_fan_type(int fan) 2755ce91714SPali Rohár { 2765ce91714SPali Rohár /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ 277747bc8b0SPali Rohár static int types[3] = { INT_MIN, INT_MIN, INT_MIN }; 2785ce91714SPali Rohár 2795ce91714SPali Rohár if (types[fan] == INT_MIN) 2805ce91714SPali Rohár types[fan] = _i8k_get_fan_type(fan); 2815ce91714SPali Rohár 2825ce91714SPali Rohár return types[fan]; 2835ce91714SPali Rohár } 2845ce91714SPali Rohár 285a5afba16SPali Rohár /* 286a5afba16SPali Rohár * Read the fan nominal rpm for specific fan speed. 287a5afba16SPali Rohár */ 288a5afba16SPali Rohár static int i8k_get_fan_nominal_speed(int fan, int speed) 289a5afba16SPali Rohár { 290a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; 291a5afba16SPali Rohár 292a5afba16SPali Rohár regs.ebx = (fan & 0xff) | (speed << 8); 293a5afba16SPali Rohár return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; 294a5afba16SPali Rohár } 295a5afba16SPali Rohár 296a5afba16SPali Rohár /* 297a5afba16SPali Rohár * Set the fan speed (off, low, high). Returns the new fan status. 298a5afba16SPali Rohár */ 299a5afba16SPali Rohár static int i8k_set_fan(int fan, int speed) 300a5afba16SPali Rohár { 301a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; 302a5afba16SPali Rohár 303a5afba16SPali Rohár speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); 304a5afba16SPali Rohár regs.ebx = (fan & 0xff) | (speed << 8); 305a5afba16SPali Rohár 306a5afba16SPali Rohár return i8k_smm(®s) ? : i8k_get_fan_status(fan); 307a5afba16SPali Rohár } 308a5afba16SPali Rohár 309a5afba16SPali Rohár static int i8k_get_temp_type(int sensor) 310a5afba16SPali Rohár { 311a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; 312a5afba16SPali Rohár 313a5afba16SPali Rohár regs.ebx = sensor & 0xff; 314a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 315a5afba16SPali Rohár } 316a5afba16SPali Rohár 317a5afba16SPali Rohár /* 318a5afba16SPali Rohár * Read the cpu temperature. 319a5afba16SPali Rohár */ 320a5afba16SPali Rohár static int _i8k_get_temp(int sensor) 321a5afba16SPali Rohár { 322a5afba16SPali Rohár struct smm_regs regs = { 323a5afba16SPali Rohár .eax = I8K_SMM_GET_TEMP, 324a5afba16SPali Rohár .ebx = sensor & 0xff, 325a5afba16SPali Rohár }; 326a5afba16SPali Rohár 327a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 328a5afba16SPali Rohár } 329a5afba16SPali Rohár 330a5afba16SPali Rohár static int i8k_get_temp(int sensor) 331a5afba16SPali Rohár { 332a5afba16SPali Rohár int temp = _i8k_get_temp(sensor); 333a5afba16SPali Rohár 334a5afba16SPali Rohár /* 335a5afba16SPali Rohár * Sometimes the temperature sensor returns 0x99, which is out of range. 336a5afba16SPali Rohár * In this case we retry (once) before returning an error. 337a5afba16SPali Rohár # 1003655137 00000058 00005a4b 338a5afba16SPali Rohár # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees 339a5afba16SPali Rohár # 1003655139 00000054 00005c52 340a5afba16SPali Rohár */ 341a5afba16SPali Rohár if (temp == 0x99) { 342a5afba16SPali Rohár msleep(100); 343a5afba16SPali Rohár temp = _i8k_get_temp(sensor); 344a5afba16SPali Rohár } 345a5afba16SPali Rohár /* 346a5afba16SPali Rohár * Return -ENODATA for all invalid temperatures. 347a5afba16SPali Rohár * 348a5afba16SPali Rohár * Known instances are the 0x99 value as seen above as well as 349a5afba16SPali Rohár * 0xc1 (193), which may be returned when trying to read the GPU 350a5afba16SPali Rohár * temperature if the system supports a GPU and it is currently 351a5afba16SPali Rohár * turned off. 352a5afba16SPali Rohár */ 353a5afba16SPali Rohár if (temp > I8K_MAX_TEMP) 354a5afba16SPali Rohár return -ENODATA; 355a5afba16SPali Rohár 356a5afba16SPali Rohár return temp; 357a5afba16SPali Rohár } 358a5afba16SPali Rohár 359a5afba16SPali Rohár static int i8k_get_dell_signature(int req_fn) 360a5afba16SPali Rohár { 361a5afba16SPali Rohár struct smm_regs regs = { .eax = req_fn, }; 362a5afba16SPali Rohár int rc; 363a5afba16SPali Rohár 364a5afba16SPali Rohár rc = i8k_smm(®s); 365a5afba16SPali Rohár if (rc < 0) 366a5afba16SPali Rohár return rc; 367a5afba16SPali Rohár 368a5afba16SPali Rohár return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1; 369a5afba16SPali Rohár } 370a5afba16SPali Rohár 371039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K) 372039ae585SPali Rohár 373039ae585SPali Rohár /* 374039ae585SPali Rohár * Read the Fn key status. 375039ae585SPali Rohár */ 376039ae585SPali Rohár static int i8k_get_fn_status(void) 377039ae585SPali Rohár { 378039ae585SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, }; 379039ae585SPali Rohár int rc; 380039ae585SPali Rohár 381039ae585SPali Rohár rc = i8k_smm(®s); 382039ae585SPali Rohár if (rc < 0) 383039ae585SPali Rohár return rc; 384039ae585SPali Rohár 385039ae585SPali Rohár switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) { 386039ae585SPali Rohár case I8K_FN_UP: 387039ae585SPali Rohár return I8K_VOL_UP; 388039ae585SPali Rohár case I8K_FN_DOWN: 389039ae585SPali Rohár return I8K_VOL_DOWN; 390039ae585SPali Rohár case I8K_FN_MUTE: 391039ae585SPali Rohár return I8K_VOL_MUTE; 392039ae585SPali Rohár default: 393039ae585SPali Rohár return 0; 394039ae585SPali Rohár } 395039ae585SPali Rohár } 396039ae585SPali Rohár 397039ae585SPali Rohár /* 398039ae585SPali Rohár * Read the power status. 399039ae585SPali Rohár */ 400039ae585SPali Rohár static int i8k_get_power_status(void) 401039ae585SPali Rohár { 402039ae585SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, }; 403039ae585SPali Rohár int rc; 404039ae585SPali Rohár 405039ae585SPali Rohár rc = i8k_smm(®s); 406039ae585SPali Rohár if (rc < 0) 407039ae585SPali Rohár return rc; 408039ae585SPali Rohár 409039ae585SPali Rohár return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY; 410039ae585SPali Rohár } 411039ae585SPali Rohár 412039ae585SPali Rohár /* 413039ae585SPali Rohár * Procfs interface 414039ae585SPali Rohár */ 415039ae585SPali Rohár 416a5afba16SPali Rohár static int 417a5afba16SPali Rohár i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) 418a5afba16SPali Rohár { 419a5afba16SPali Rohár int val = 0; 420a5afba16SPali Rohár int speed; 421a5afba16SPali Rohár unsigned char buff[16]; 422a5afba16SPali Rohár int __user *argp = (int __user *)arg; 423a5afba16SPali Rohár 424a5afba16SPali Rohár if (!argp) 425a5afba16SPali Rohár return -EINVAL; 426a5afba16SPali Rohár 427a5afba16SPali Rohár switch (cmd) { 428a5afba16SPali Rohár case I8K_BIOS_VERSION: 429053ea640SPali Rohár if (!isdigit(bios_version[0]) || !isdigit(bios_version[1]) || 430053ea640SPali Rohár !isdigit(bios_version[2])) 431053ea640SPali Rohár return -EINVAL; 432053ea640SPali Rohár 433a5afba16SPali Rohár val = (bios_version[0] << 16) | 434a5afba16SPali Rohár (bios_version[1] << 8) | bios_version[2]; 435a5afba16SPali Rohár break; 436a5afba16SPali Rohár 437a5afba16SPali Rohár case I8K_MACHINE_ID: 4387613663cSPali Rohár if (restricted && !capable(CAP_SYS_ADMIN)) 4397613663cSPali Rohár return -EPERM; 4407613663cSPali Rohár 4417613663cSPali Rohár memset(buff, 0, sizeof(buff)); 4427613663cSPali Rohár strlcpy(buff, bios_machineid, sizeof(buff)); 443a5afba16SPali Rohár break; 444a5afba16SPali Rohár 445a5afba16SPali Rohár case I8K_FN_STATUS: 446a5afba16SPali Rohár val = i8k_get_fn_status(); 447a5afba16SPali Rohár break; 448a5afba16SPali Rohár 449a5afba16SPali Rohár case I8K_POWER_STATUS: 450a5afba16SPali Rohár val = i8k_get_power_status(); 451a5afba16SPali Rohár break; 452a5afba16SPali Rohár 453a5afba16SPali Rohár case I8K_GET_TEMP: 454a5afba16SPali Rohár val = i8k_get_temp(0); 455a5afba16SPali Rohár break; 456a5afba16SPali Rohár 457a5afba16SPali Rohár case I8K_GET_SPEED: 458a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 459a5afba16SPali Rohár return -EFAULT; 460a5afba16SPali Rohár 461a5afba16SPali Rohár val = i8k_get_fan_speed(val); 462a5afba16SPali Rohár break; 463a5afba16SPali Rohár 464a5afba16SPali Rohár case I8K_GET_FAN: 465a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 466a5afba16SPali Rohár return -EFAULT; 467a5afba16SPali Rohár 468a5afba16SPali Rohár val = i8k_get_fan_status(val); 469a5afba16SPali Rohár break; 470a5afba16SPali Rohár 471a5afba16SPali Rohár case I8K_SET_FAN: 472a5afba16SPali Rohár if (restricted && !capable(CAP_SYS_ADMIN)) 473a5afba16SPali Rohár return -EPERM; 474a5afba16SPali Rohár 475a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 476a5afba16SPali Rohár return -EFAULT; 477a5afba16SPali Rohár 478a5afba16SPali Rohár if (copy_from_user(&speed, argp + 1, sizeof(int))) 479a5afba16SPali Rohár return -EFAULT; 480a5afba16SPali Rohár 481a5afba16SPali Rohár val = i8k_set_fan(val, speed); 482a5afba16SPali Rohár break; 483a5afba16SPali Rohár 484a5afba16SPali Rohár default: 485a5afba16SPali Rohár return -EINVAL; 486a5afba16SPali Rohár } 487a5afba16SPali Rohár 488a5afba16SPali Rohár if (val < 0) 489a5afba16SPali Rohár return val; 490a5afba16SPali Rohár 491a5afba16SPali Rohár switch (cmd) { 492a5afba16SPali Rohár case I8K_BIOS_VERSION: 493a5afba16SPali Rohár if (copy_to_user(argp, &val, 4)) 494a5afba16SPali Rohár return -EFAULT; 495a5afba16SPali Rohár 496a5afba16SPali Rohár break; 497a5afba16SPali Rohár case I8K_MACHINE_ID: 498a5afba16SPali Rohár if (copy_to_user(argp, buff, 16)) 499a5afba16SPali Rohár return -EFAULT; 500a5afba16SPali Rohár 501a5afba16SPali Rohár break; 502a5afba16SPali Rohár default: 503a5afba16SPali Rohár if (copy_to_user(argp, &val, sizeof(int))) 504a5afba16SPali Rohár return -EFAULT; 505a5afba16SPali Rohár 506a5afba16SPali Rohár break; 507a5afba16SPali Rohár } 508a5afba16SPali Rohár 509a5afba16SPali Rohár return 0; 510a5afba16SPali Rohár } 511a5afba16SPali Rohár 512a5afba16SPali Rohár static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) 513a5afba16SPali Rohár { 514a5afba16SPali Rohár long ret; 515a5afba16SPali Rohár 516a5afba16SPali Rohár mutex_lock(&i8k_mutex); 517a5afba16SPali Rohár ret = i8k_ioctl_unlocked(fp, cmd, arg); 518a5afba16SPali Rohár mutex_unlock(&i8k_mutex); 519a5afba16SPali Rohár 520a5afba16SPali Rohár return ret; 521a5afba16SPali Rohár } 522a5afba16SPali Rohár 523a5afba16SPali Rohár /* 524a5afba16SPali Rohár * Print the information for /proc/i8k. 525a5afba16SPali Rohár */ 526a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset) 527a5afba16SPali Rohár { 528a5afba16SPali Rohár int fn_key, cpu_temp, ac_power; 529a5afba16SPali Rohár int left_fan, right_fan, left_speed, right_speed; 530a5afba16SPali Rohár 531a5afba16SPali Rohár cpu_temp = i8k_get_temp(0); /* 11100 µs */ 532a5afba16SPali Rohár left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */ 533a5afba16SPali Rohár right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */ 534a5afba16SPali Rohár left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */ 535a5afba16SPali Rohár right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */ 536a5afba16SPali Rohár fn_key = i8k_get_fn_status(); /* 750 µs */ 537a5afba16SPali Rohár if (power_status) 538a5afba16SPali Rohár ac_power = i8k_get_power_status(); /* 14700 µs */ 539a5afba16SPali Rohár else 540a5afba16SPali Rohár ac_power = -1; 541a5afba16SPali Rohár 542a5afba16SPali Rohár /* 543a5afba16SPali Rohár * Info: 544a5afba16SPali Rohár * 545a5afba16SPali Rohár * 1) Format version (this will change if format changes) 546a5afba16SPali Rohár * 2) BIOS version 547a5afba16SPali Rohár * 3) BIOS machine ID 548a5afba16SPali Rohár * 4) Cpu temperature 549a5afba16SPali Rohár * 5) Left fan status 550a5afba16SPali Rohár * 6) Right fan status 551a5afba16SPali Rohár * 7) Left fan speed 552a5afba16SPali Rohár * 8) Right fan speed 553a5afba16SPali Rohár * 9) AC power 554a5afba16SPali Rohár * 10) Fn Key status 555a5afba16SPali Rohár */ 556a5afba16SPali Rohár seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", 557a5afba16SPali Rohár I8K_PROC_FMT, 558a5afba16SPali Rohár bios_version, 5597613663cSPali Rohár (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : bios_machineid, 560a5afba16SPali Rohár cpu_temp, 561a5afba16SPali Rohár left_fan, right_fan, left_speed, right_speed, 562a5afba16SPali Rohár ac_power, fn_key); 563a5afba16SPali Rohár 564a5afba16SPali Rohár return 0; 565a5afba16SPali Rohár } 566a5afba16SPali Rohár 567a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file) 568a5afba16SPali Rohár { 569a5afba16SPali Rohár return single_open(file, i8k_proc_show, NULL); 570a5afba16SPali Rohár } 571a5afba16SPali Rohár 572039ae585SPali Rohár static const struct file_operations i8k_fops = { 573039ae585SPali Rohár .owner = THIS_MODULE, 574039ae585SPali Rohár .open = i8k_open_fs, 575039ae585SPali Rohár .read = seq_read, 576039ae585SPali Rohár .llseek = seq_lseek, 577039ae585SPali Rohár .release = single_release, 578039ae585SPali Rohár .unlocked_ioctl = i8k_ioctl, 579039ae585SPali Rohár }; 580039ae585SPali Rohár 581039ae585SPali Rohár static void __init i8k_init_procfs(void) 582039ae585SPali Rohár { 583039ae585SPali Rohár /* Register the proc entry */ 584039ae585SPali Rohár proc_create("i8k", 0, NULL, &i8k_fops); 585039ae585SPali Rohár } 586039ae585SPali Rohár 587039ae585SPali Rohár static void __exit i8k_exit_procfs(void) 588039ae585SPali Rohár { 589039ae585SPali Rohár remove_proc_entry("i8k", NULL); 590039ae585SPali Rohár } 591039ae585SPali Rohár 592039ae585SPali Rohár #else 593039ae585SPali Rohár 594039ae585SPali Rohár static inline void __init i8k_init_procfs(void) 595039ae585SPali Rohár { 596039ae585SPali Rohár } 597039ae585SPali Rohár 598039ae585SPali Rohár static inline void __exit i8k_exit_procfs(void) 599039ae585SPali Rohár { 600039ae585SPali Rohár } 601039ae585SPali Rohár 602039ae585SPali Rohár #endif 603a5afba16SPali Rohár 604a5afba16SPali Rohár /* 605a5afba16SPali Rohár * Hwmon interface 606a5afba16SPali Rohár */ 607a5afba16SPali Rohár 608a5afba16SPali Rohár static ssize_t i8k_hwmon_show_temp_label(struct device *dev, 609a5afba16SPali Rohár struct device_attribute *devattr, 610a5afba16SPali Rohár char *buf) 611a5afba16SPali Rohár { 612a5afba16SPali Rohár static const char * const labels[] = { 613a5afba16SPali Rohár "CPU", 614a5afba16SPali Rohár "GPU", 615a5afba16SPali Rohár "SODIMM", 616a5afba16SPali Rohár "Other", 617a5afba16SPali Rohár "Ambient", 618a5afba16SPali Rohár "Other", 619a5afba16SPali Rohár }; 620a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 621a5afba16SPali Rohár int type; 622a5afba16SPali Rohár 623a5afba16SPali Rohár type = i8k_get_temp_type(index); 624a5afba16SPali Rohár if (type < 0) 625a5afba16SPali Rohár return type; 626a5afba16SPali Rohár if (type >= ARRAY_SIZE(labels)) 627a5afba16SPali Rohár type = ARRAY_SIZE(labels) - 1; 628a5afba16SPali Rohár return sprintf(buf, "%s\n", labels[type]); 629a5afba16SPali Rohár } 630a5afba16SPali Rohár 631a5afba16SPali Rohár static ssize_t i8k_hwmon_show_temp(struct device *dev, 632a5afba16SPali Rohár struct device_attribute *devattr, 633a5afba16SPali Rohár char *buf) 634a5afba16SPali Rohár { 635a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 636a5afba16SPali Rohár int temp; 637a5afba16SPali Rohár 638a5afba16SPali Rohár temp = i8k_get_temp(index); 639a5afba16SPali Rohár if (temp < 0) 640a5afba16SPali Rohár return temp; 641a5afba16SPali Rohár return sprintf(buf, "%d\n", temp * 1000); 642a5afba16SPali Rohár } 643a5afba16SPali Rohár 644a5afba16SPali Rohár static ssize_t i8k_hwmon_show_fan_label(struct device *dev, 645a5afba16SPali Rohár struct device_attribute *devattr, 646a5afba16SPali Rohár char *buf) 647a5afba16SPali Rohár { 648a5afba16SPali Rohár static const char * const labels[] = { 649a5afba16SPali Rohár "Processor Fan", 650a5afba16SPali Rohár "Motherboard Fan", 651a5afba16SPali Rohár "Video Fan", 652a5afba16SPali Rohár "Power Supply Fan", 653a5afba16SPali Rohár "Chipset Fan", 654a5afba16SPali Rohár "Other Fan", 655a5afba16SPali Rohár }; 656a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 657a5afba16SPali Rohár bool dock = false; 658a5afba16SPali Rohár int type; 659a5afba16SPali Rohár 660a5afba16SPali Rohár type = i8k_get_fan_type(index); 661a5afba16SPali Rohár if (type < 0) 662a5afba16SPali Rohár return type; 663a5afba16SPali Rohár 664a5afba16SPali Rohár if (type & 0x10) { 665a5afba16SPali Rohár dock = true; 666a5afba16SPali Rohár type &= 0x0F; 667a5afba16SPali Rohár } 668a5afba16SPali Rohár 669a5afba16SPali Rohár if (type >= ARRAY_SIZE(labels)) 670a5afba16SPali Rohár type = (ARRAY_SIZE(labels) - 1); 671a5afba16SPali Rohár 672a5afba16SPali Rohár return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]); 673a5afba16SPali Rohár } 674a5afba16SPali Rohár 675a5afba16SPali Rohár static ssize_t i8k_hwmon_show_fan(struct device *dev, 676a5afba16SPali Rohár struct device_attribute *devattr, 677a5afba16SPali Rohár char *buf) 678a5afba16SPali Rohár { 679a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 680a5afba16SPali Rohár int fan_speed; 681a5afba16SPali Rohár 682a5afba16SPali Rohár fan_speed = i8k_get_fan_speed(index); 683a5afba16SPali Rohár if (fan_speed < 0) 684a5afba16SPali Rohár return fan_speed; 685a5afba16SPali Rohár return sprintf(buf, "%d\n", fan_speed); 686a5afba16SPali Rohár } 687a5afba16SPali Rohár 688a5afba16SPali Rohár static ssize_t i8k_hwmon_show_pwm(struct device *dev, 689a5afba16SPali Rohár struct device_attribute *devattr, 690a5afba16SPali Rohár char *buf) 691a5afba16SPali Rohár { 692a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 693a5afba16SPali Rohár int status; 694a5afba16SPali Rohár 695a5afba16SPali Rohár status = i8k_get_fan_status(index); 696a5afba16SPali Rohár if (status < 0) 697a5afba16SPali Rohár return -EIO; 698a5afba16SPali Rohár return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255)); 699a5afba16SPali Rohár } 700a5afba16SPali Rohár 701a5afba16SPali Rohár static ssize_t i8k_hwmon_set_pwm(struct device *dev, 702a5afba16SPali Rohár struct device_attribute *attr, 703a5afba16SPali Rohár const char *buf, size_t count) 704a5afba16SPali Rohár { 705a5afba16SPali Rohár int index = to_sensor_dev_attr(attr)->index; 706a5afba16SPali Rohár unsigned long val; 707a5afba16SPali Rohár int err; 708a5afba16SPali Rohár 709a5afba16SPali Rohár err = kstrtoul(buf, 10, &val); 710a5afba16SPali Rohár if (err) 711a5afba16SPali Rohár return err; 712a5afba16SPali Rohár val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max); 713a5afba16SPali Rohár 714a5afba16SPali Rohár mutex_lock(&i8k_mutex); 715a5afba16SPali Rohár err = i8k_set_fan(index, val); 716a5afba16SPali Rohár mutex_unlock(&i8k_mutex); 717a5afba16SPali Rohár 718a5afba16SPali Rohár return err < 0 ? -EIO : count; 719a5afba16SPali Rohár } 720a5afba16SPali Rohár 721a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0); 722a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 723a5afba16SPali Rohár 0); 724a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1); 725a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 726a5afba16SPali Rohár 1); 727a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2); 728a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 729a5afba16SPali Rohár 2); 730a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3); 731a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 732a5afba16SPali Rohár 3); 733a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0); 734a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, 735a5afba16SPali Rohár 0); 736a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, 737a5afba16SPali Rohár i8k_hwmon_set_pwm, 0); 738a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 739a5afba16SPali Rohár 1); 740a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, 741a5afba16SPali Rohár 1); 742a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, 743a5afba16SPali Rohár i8k_hwmon_set_pwm, 1); 744747bc8b0SPali Rohár static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 745747bc8b0SPali Rohár 2); 746747bc8b0SPali Rohár static SENSOR_DEVICE_ATTR(fan3_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, 747747bc8b0SPali Rohár 2); 748747bc8b0SPali Rohár static SENSOR_DEVICE_ATTR(pwm3, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, 749747bc8b0SPali Rohár i8k_hwmon_set_pwm, 2); 750a5afba16SPali Rohár 751a5afba16SPali Rohár static struct attribute *i8k_attrs[] = { 752a5afba16SPali Rohár &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ 753a5afba16SPali Rohár &sensor_dev_attr_temp1_label.dev_attr.attr, /* 1 */ 754a5afba16SPali Rohár &sensor_dev_attr_temp2_input.dev_attr.attr, /* 2 */ 755a5afba16SPali Rohár &sensor_dev_attr_temp2_label.dev_attr.attr, /* 3 */ 756a5afba16SPali Rohár &sensor_dev_attr_temp3_input.dev_attr.attr, /* 4 */ 757a5afba16SPali Rohár &sensor_dev_attr_temp3_label.dev_attr.attr, /* 5 */ 758a5afba16SPali Rohár &sensor_dev_attr_temp4_input.dev_attr.attr, /* 6 */ 759a5afba16SPali Rohár &sensor_dev_attr_temp4_label.dev_attr.attr, /* 7 */ 760a5afba16SPali Rohár &sensor_dev_attr_fan1_input.dev_attr.attr, /* 8 */ 761a5afba16SPali Rohár &sensor_dev_attr_fan1_label.dev_attr.attr, /* 9 */ 762a5afba16SPali Rohár &sensor_dev_attr_pwm1.dev_attr.attr, /* 10 */ 763a5afba16SPali Rohár &sensor_dev_attr_fan2_input.dev_attr.attr, /* 11 */ 764a5afba16SPali Rohár &sensor_dev_attr_fan2_label.dev_attr.attr, /* 12 */ 765a5afba16SPali Rohár &sensor_dev_attr_pwm2.dev_attr.attr, /* 13 */ 766747bc8b0SPali Rohár &sensor_dev_attr_fan3_input.dev_attr.attr, /* 14 */ 767747bc8b0SPali Rohár &sensor_dev_attr_fan3_label.dev_attr.attr, /* 15 */ 768747bc8b0SPali Rohár &sensor_dev_attr_pwm3.dev_attr.attr, /* 16 */ 769a5afba16SPali Rohár NULL 770a5afba16SPali Rohár }; 771a5afba16SPali Rohár 772a5afba16SPali Rohár static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, 773a5afba16SPali Rohár int index) 774a5afba16SPali Rohár { 7752744d2fdSPali Rohár if (disallow_fan_type_call && 776747bc8b0SPali Rohár (index == 9 || index == 12 || index == 15)) 7772744d2fdSPali Rohár return 0; 778a5afba16SPali Rohár if (index >= 0 && index <= 1 && 779a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) 780a5afba16SPali Rohár return 0; 781a5afba16SPali Rohár if (index >= 2 && index <= 3 && 782a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) 783a5afba16SPali Rohár return 0; 784a5afba16SPali Rohár if (index >= 4 && index <= 5 && 785a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) 786a5afba16SPali Rohár return 0; 787a5afba16SPali Rohár if (index >= 6 && index <= 7 && 788a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) 789a5afba16SPali Rohár return 0; 790a5afba16SPali Rohár if (index >= 8 && index <= 10 && 791a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) 792a5afba16SPali Rohár return 0; 793a5afba16SPali Rohár if (index >= 11 && index <= 13 && 794a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) 795a5afba16SPali Rohár return 0; 796747bc8b0SPali Rohár if (index >= 14 && index <= 16 && 797747bc8b0SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3)) 798747bc8b0SPali Rohár return 0; 799a5afba16SPali Rohár 800a5afba16SPali Rohár return attr->mode; 801a5afba16SPali Rohár } 802a5afba16SPali Rohár 803a5afba16SPali Rohár static const struct attribute_group i8k_group = { 804a5afba16SPali Rohár .attrs = i8k_attrs, 805a5afba16SPali Rohár .is_visible = i8k_is_visible, 806a5afba16SPali Rohár }; 807a5afba16SPali Rohár __ATTRIBUTE_GROUPS(i8k); 808a5afba16SPali Rohár 809a5afba16SPali Rohár static int __init i8k_init_hwmon(void) 810a5afba16SPali Rohár { 811a5afba16SPali Rohár int err; 812a5afba16SPali Rohár 813a5afba16SPali Rohár i8k_hwmon_flags = 0; 814a5afba16SPali Rohár 815a5afba16SPali Rohár /* CPU temperature attributes, if temperature type is OK */ 816a5afba16SPali Rohár err = i8k_get_temp_type(0); 817a5afba16SPali Rohár if (err >= 0) 818a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; 819a5afba16SPali Rohár /* check for additional temperature sensors */ 820a5afba16SPali Rohár err = i8k_get_temp_type(1); 821a5afba16SPali Rohár if (err >= 0) 822a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; 823a5afba16SPali Rohár err = i8k_get_temp_type(2); 824a5afba16SPali Rohár if (err >= 0) 825a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; 826a5afba16SPali Rohár err = i8k_get_temp_type(3); 827a5afba16SPali Rohár if (err >= 0) 828a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; 829a5afba16SPali Rohár 8305ce91714SPali Rohár /* First fan attributes, if fan status or type is OK */ 8315ce91714SPali Rohár err = i8k_get_fan_status(0); 8325ce91714SPali Rohár if (err < 0) 833a5afba16SPali Rohár err = i8k_get_fan_type(0); 834a5afba16SPali Rohár if (err >= 0) 835a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; 836a5afba16SPali Rohár 8375ce91714SPali Rohár /* Second fan attributes, if fan status or type is OK */ 8385ce91714SPali Rohár err = i8k_get_fan_status(1); 8395ce91714SPali Rohár if (err < 0) 840a5afba16SPali Rohár err = i8k_get_fan_type(1); 841a5afba16SPali Rohár if (err >= 0) 842a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; 843a5afba16SPali Rohár 844747bc8b0SPali Rohár /* Third fan attributes, if fan status or type is OK */ 845747bc8b0SPali Rohár err = i8k_get_fan_status(2); 846747bc8b0SPali Rohár if (err < 0) 847747bc8b0SPali Rohár err = i8k_get_fan_type(2); 848747bc8b0SPali Rohár if (err >= 0) 849747bc8b0SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3; 850747bc8b0SPali Rohár 8519026cae1SGabriele Mazzotta i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm", 852039ae585SPali Rohár NULL, i8k_groups); 853a5afba16SPali Rohár if (IS_ERR(i8k_hwmon_dev)) { 854a5afba16SPali Rohár err = PTR_ERR(i8k_hwmon_dev); 855a5afba16SPali Rohár i8k_hwmon_dev = NULL; 856a5afba16SPali Rohár pr_err("hwmon registration failed (%d)\n", err); 857a5afba16SPali Rohár return err; 858a5afba16SPali Rohár } 859a5afba16SPali Rohár return 0; 860a5afba16SPali Rohár } 861a5afba16SPali Rohár 862a5afba16SPali Rohár struct i8k_config_data { 863a5afba16SPali Rohár uint fan_mult; 864a5afba16SPali Rohár uint fan_max; 865a5afba16SPali Rohár }; 866a5afba16SPali Rohár 867a5afba16SPali Rohár enum i8k_configs { 868a5afba16SPali Rohár DELL_LATITUDE_D520, 869a5afba16SPali Rohár DELL_PRECISION_490, 870a5afba16SPali Rohár DELL_STUDIO, 871a5afba16SPali Rohár DELL_XPS, 872a5afba16SPali Rohár }; 873a5afba16SPali Rohár 874a5afba16SPali Rohár static const struct i8k_config_data i8k_config_data[] = { 875a5afba16SPali Rohár [DELL_LATITUDE_D520] = { 876a5afba16SPali Rohár .fan_mult = 1, 877a5afba16SPali Rohár .fan_max = I8K_FAN_TURBO, 878a5afba16SPali Rohár }, 879a5afba16SPali Rohár [DELL_PRECISION_490] = { 880a5afba16SPali Rohár .fan_mult = 1, 881a5afba16SPali Rohár .fan_max = I8K_FAN_TURBO, 882a5afba16SPali Rohár }, 883a5afba16SPali Rohár [DELL_STUDIO] = { 884a5afba16SPali Rohár .fan_mult = 1, 885a5afba16SPali Rohár .fan_max = I8K_FAN_HIGH, 886a5afba16SPali Rohár }, 887a5afba16SPali Rohár [DELL_XPS] = { 888a5afba16SPali Rohár .fan_mult = 1, 889a5afba16SPali Rohár .fan_max = I8K_FAN_HIGH, 890a5afba16SPali Rohár }, 891a5afba16SPali Rohár }; 892a5afba16SPali Rohár 893a5afba16SPali Rohár static struct dmi_system_id i8k_dmi_table[] __initdata = { 894a5afba16SPali Rohár { 895a5afba16SPali Rohár .ident = "Dell Inspiron", 896a5afba16SPali Rohár .matches = { 897a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), 898a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), 899a5afba16SPali Rohár }, 900a5afba16SPali Rohár }, 901a5afba16SPali Rohár { 902a5afba16SPali Rohár .ident = "Dell Latitude", 903a5afba16SPali Rohár .matches = { 904a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), 905a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), 906a5afba16SPali Rohár }, 907a5afba16SPali Rohár }, 908a5afba16SPali Rohár { 909a5afba16SPali Rohár .ident = "Dell Inspiron 2", 910a5afba16SPali Rohár .matches = { 911a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 912a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), 913a5afba16SPali Rohár }, 914a5afba16SPali Rohár }, 915a5afba16SPali Rohár { 916a5afba16SPali Rohár .ident = "Dell Latitude D520", 917a5afba16SPali Rohár .matches = { 918a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 919a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), 920a5afba16SPali Rohár }, 921a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], 922a5afba16SPali Rohár }, 923a5afba16SPali Rohár { 924a5afba16SPali Rohár .ident = "Dell Latitude 2", 925a5afba16SPali Rohár .matches = { 926a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 927a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), 928a5afba16SPali Rohár }, 929a5afba16SPali Rohár }, 930a5afba16SPali Rohár { /* UK Inspiron 6400 */ 931a5afba16SPali Rohár .ident = "Dell Inspiron 3", 932a5afba16SPali Rohár .matches = { 933a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 934a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MM061"), 935a5afba16SPali Rohár }, 936a5afba16SPali Rohár }, 937a5afba16SPali Rohár { 938a5afba16SPali Rohár .ident = "Dell Inspiron 3", 939a5afba16SPali Rohár .matches = { 940a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 941a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MP061"), 942a5afba16SPali Rohár }, 943a5afba16SPali Rohár }, 944a5afba16SPali Rohár { 945a5afba16SPali Rohár .ident = "Dell Precision 490", 946a5afba16SPali Rohár .matches = { 947a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 948a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, 949a5afba16SPali Rohár "Precision WorkStation 490"), 950a5afba16SPali Rohár }, 951a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], 952a5afba16SPali Rohár }, 953a5afba16SPali Rohár { 954a5afba16SPali Rohár .ident = "Dell Precision", 955a5afba16SPali Rohár .matches = { 956a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 957a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Precision"), 958a5afba16SPali Rohár }, 959a5afba16SPali Rohár }, 960a5afba16SPali Rohár { 961a5afba16SPali Rohár .ident = "Dell Vostro", 962a5afba16SPali Rohár .matches = { 963a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 964a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"), 965a5afba16SPali Rohár }, 966a5afba16SPali Rohár }, 967a5afba16SPali Rohár { 968a5afba16SPali Rohár .ident = "Dell XPS421", 969a5afba16SPali Rohár .matches = { 970a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 971a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"), 972a5afba16SPali Rohár }, 973a5afba16SPali Rohár }, 974a5afba16SPali Rohár { 975a5afba16SPali Rohár .ident = "Dell Studio", 976a5afba16SPali Rohár .matches = { 977a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 978a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), 979a5afba16SPali Rohár }, 980a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_STUDIO], 981a5afba16SPali Rohár }, 982a5afba16SPali Rohár { 983a5afba16SPali Rohár .ident = "Dell XPS 13", 984a5afba16SPali Rohár .matches = { 985a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 986a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"), 987a5afba16SPali Rohár }, 988a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_XPS], 989a5afba16SPali Rohár }, 990a5afba16SPali Rohár { 991a5afba16SPali Rohár .ident = "Dell XPS M140", 992a5afba16SPali Rohár .matches = { 993a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 994a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), 995a5afba16SPali Rohár }, 996a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_XPS], 997a5afba16SPali Rohár }, 998a5afba16SPali Rohár { } 999a5afba16SPali Rohár }; 1000a5afba16SPali Rohár 1001a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table); 1002a5afba16SPali Rohár 1003a4b45b25SPali Rohár /* 10042744d2fdSPali Rohár * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed 10052744d2fdSPali Rohár * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist 10062744d2fdSPali Rohár * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call. 10072744d2fdSPali Rohár * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121 10086220f4ebSThorsten Leemhuis */ 10092744d2fdSPali Rohár static struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initdata = { 10102744d2fdSPali Rohár { 10116220f4ebSThorsten Leemhuis .ident = "Dell Studio XPS 8000", 10126220f4ebSThorsten Leemhuis .matches = { 10136220f4ebSThorsten Leemhuis DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 10146220f4ebSThorsten Leemhuis DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"), 10156220f4ebSThorsten Leemhuis }, 10166220f4ebSThorsten Leemhuis }, 10176220f4ebSThorsten Leemhuis { 1018a4b45b25SPali Rohár .ident = "Dell Studio XPS 8100", 1019a4b45b25SPali Rohár .matches = { 1020a4b45b25SPali Rohár DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 1021a4b45b25SPali Rohár DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"), 1022a4b45b25SPali Rohár }, 1023a4b45b25SPali Rohár }, 10242744d2fdSPali Rohár { 10252744d2fdSPali Rohár .ident = "Dell Inspiron 580", 10262744d2fdSPali Rohár .matches = { 10272744d2fdSPali Rohár DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 10282744d2fdSPali Rohár DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "), 10292744d2fdSPali Rohár }, 10302744d2fdSPali Rohár }, 1031a4b45b25SPali Rohár { } 1032a4b45b25SPali Rohár }; 1033a4b45b25SPali Rohár 1034a5afba16SPali Rohár /* 1035a5afba16SPali Rohár * Probe for the presence of a supported laptop. 1036a5afba16SPali Rohár */ 1037a5afba16SPali Rohár static int __init i8k_probe(void) 1038a5afba16SPali Rohár { 1039a5afba16SPali Rohár const struct dmi_system_id *id; 1040a5afba16SPali Rohár int fan, ret; 1041a5afba16SPali Rohár 1042a5afba16SPali Rohár /* 1043a5afba16SPali Rohár * Get DMI information 1044a5afba16SPali Rohár */ 10452744d2fdSPali Rohár if (!dmi_check_system(i8k_dmi_table)) { 1046a5afba16SPali Rohár if (!ignore_dmi && !force) 1047a5afba16SPali Rohár return -ENODEV; 1048a5afba16SPali Rohár 1049a5afba16SPali Rohár pr_info("not running on a supported Dell system.\n"); 1050a5afba16SPali Rohár pr_info("vendor=%s, model=%s, version=%s\n", 1051a5afba16SPali Rohár i8k_get_dmi_data(DMI_SYS_VENDOR), 1052a5afba16SPali Rohár i8k_get_dmi_data(DMI_PRODUCT_NAME), 1053a5afba16SPali Rohár i8k_get_dmi_data(DMI_BIOS_VERSION)); 1054a5afba16SPali Rohár } 1055a5afba16SPali Rohár 10562744d2fdSPali Rohár if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) 10572744d2fdSPali Rohár disallow_fan_type_call = true; 10582744d2fdSPali Rohár 1059a5afba16SPali Rohár strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), 1060a5afba16SPali Rohár sizeof(bios_version)); 10617613663cSPali Rohár strlcpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), 10627613663cSPali Rohár sizeof(bios_machineid)); 1063a5afba16SPali Rohár 1064a5afba16SPali Rohár /* 1065a5afba16SPali Rohár * Get SMM Dell signature 1066a5afba16SPali Rohár */ 1067a5afba16SPali Rohár if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && 1068a5afba16SPali Rohár i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { 1069a5afba16SPali Rohár pr_err("unable to get SMM Dell signature\n"); 1070a5afba16SPali Rohár if (!force) 1071a5afba16SPali Rohár return -ENODEV; 1072a5afba16SPali Rohár } 1073a5afba16SPali Rohár 1074a5afba16SPali Rohár /* 1075a5afba16SPali Rohár * Set fan multiplier and maximal fan speed from dmi config 1076a5afba16SPali Rohár * Values specified in module parameters override values from dmi 1077a5afba16SPali Rohár */ 1078a5afba16SPali Rohár id = dmi_first_match(i8k_dmi_table); 1079a5afba16SPali Rohár if (id && id->driver_data) { 1080a5afba16SPali Rohár const struct i8k_config_data *conf = id->driver_data; 1081a5afba16SPali Rohár if (!fan_mult && conf->fan_mult) 1082a5afba16SPali Rohár fan_mult = conf->fan_mult; 1083a5afba16SPali Rohár if (!fan_max && conf->fan_max) 1084a5afba16SPali Rohár fan_max = conf->fan_max; 1085a5afba16SPali Rohár } 1086a5afba16SPali Rohár 1087a5afba16SPali Rohár i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ 1088a5afba16SPali Rohár i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max); 1089a5afba16SPali Rohár 1090a5afba16SPali Rohár if (!fan_mult) { 1091a5afba16SPali Rohár /* 1092a5afba16SPali Rohár * Autodetect fan multiplier based on nominal rpm 1093a5afba16SPali Rohár * If fan reports rpm value too high then set multiplier to 1 1094a5afba16SPali Rohár */ 1095a5afba16SPali Rohár for (fan = 0; fan < 2; ++fan) { 1096a5afba16SPali Rohár ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max); 1097a5afba16SPali Rohár if (ret < 0) 1098a5afba16SPali Rohár continue; 1099a5afba16SPali Rohár if (ret > I8K_FAN_MAX_RPM) 1100a5afba16SPali Rohár i8k_fan_mult = 1; 1101a5afba16SPali Rohár break; 1102a5afba16SPali Rohár } 1103a5afba16SPali Rohár } else { 1104a5afba16SPali Rohár /* Fan multiplier was specified in module param or in dmi */ 1105a5afba16SPali Rohár i8k_fan_mult = fan_mult; 1106a5afba16SPali Rohár } 1107a5afba16SPali Rohár 1108a5afba16SPali Rohár return 0; 1109a5afba16SPali Rohár } 1110a5afba16SPali Rohár 1111a5afba16SPali Rohár static int __init i8k_init(void) 1112a5afba16SPali Rohár { 1113a5afba16SPali Rohár int err; 1114a5afba16SPali Rohár 1115a5afba16SPali Rohár /* Are we running on an supported laptop? */ 1116a5afba16SPali Rohár if (i8k_probe()) 1117a5afba16SPali Rohár return -ENODEV; 1118a5afba16SPali Rohár 1119a5afba16SPali Rohár err = i8k_init_hwmon(); 1120a5afba16SPali Rohár if (err) 1121a5afba16SPali Rohár return err; 1122039ae585SPali Rohár 1123039ae585SPali Rohár i8k_init_procfs(); 1124039ae585SPali Rohár return 0; 1125a5afba16SPali Rohár } 1126a5afba16SPali Rohár 1127a5afba16SPali Rohár static void __exit i8k_exit(void) 1128a5afba16SPali Rohár { 1129a5afba16SPali Rohár hwmon_device_unregister(i8k_hwmon_dev); 1130039ae585SPali Rohár i8k_exit_procfs(); 1131a5afba16SPali Rohár } 1132a5afba16SPali Rohár 1133a5afba16SPali Rohár module_init(i8k_init); 1134a5afba16SPali Rohár module_exit(i8k_exit); 1135