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 24a5afba16SPali Rohár #include <linux/delay.h> 25a5afba16SPali Rohár #include <linux/module.h> 26a5afba16SPali Rohár #include <linux/types.h> 27a5afba16SPali Rohár #include <linux/init.h> 28a5afba16SPali Rohár #include <linux/proc_fs.h> 29a5afba16SPali Rohár #include <linux/seq_file.h> 30a5afba16SPali Rohár #include <linux/dmi.h> 31a5afba16SPali Rohár #include <linux/capability.h> 32a5afba16SPali Rohár #include <linux/mutex.h> 33a5afba16SPali Rohár #include <linux/hwmon.h> 34a5afba16SPali Rohár #include <linux/hwmon-sysfs.h> 35a5afba16SPali Rohár #include <linux/uaccess.h> 36a5afba16SPali Rohár #include <linux/io.h> 37a5afba16SPali Rohár #include <linux/sched.h> 38a5afba16SPali Rohár 39a5afba16SPali Rohár #include <linux/i8k.h> 40a5afba16SPali Rohár 41a5afba16SPali Rohár #define I8K_SMM_FN_STATUS 0x0025 42a5afba16SPali Rohár #define I8K_SMM_POWER_STATUS 0x0069 43a5afba16SPali Rohár #define I8K_SMM_SET_FAN 0x01a3 44a5afba16SPali Rohár #define I8K_SMM_GET_FAN 0x00a3 45a5afba16SPali Rohár #define I8K_SMM_GET_SPEED 0x02a3 46a5afba16SPali Rohár #define I8K_SMM_GET_FAN_TYPE 0x03a3 47a5afba16SPali Rohár #define I8K_SMM_GET_NOM_SPEED 0x04a3 48a5afba16SPali Rohár #define I8K_SMM_GET_TEMP 0x10a3 49a5afba16SPali Rohár #define I8K_SMM_GET_TEMP_TYPE 0x11a3 50a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG1 0xfea3 51a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG2 0xffa3 52a5afba16SPali Rohár 53a5afba16SPali Rohár #define I8K_FAN_MULT 30 54a5afba16SPali Rohár #define I8K_FAN_MAX_RPM 30000 55a5afba16SPali Rohár #define I8K_MAX_TEMP 127 56a5afba16SPali Rohár 57a5afba16SPali Rohár #define I8K_FN_NONE 0x00 58a5afba16SPali Rohár #define I8K_FN_UP 0x01 59a5afba16SPali Rohár #define I8K_FN_DOWN 0x02 60a5afba16SPali Rohár #define I8K_FN_MUTE 0x04 61a5afba16SPali Rohár #define I8K_FN_MASK 0x07 62a5afba16SPali Rohár #define I8K_FN_SHIFT 8 63a5afba16SPali Rohár 64a5afba16SPali Rohár #define I8K_POWER_AC 0x05 65a5afba16SPali Rohár #define I8K_POWER_BATTERY 0x01 66a5afba16SPali Rohár 67a5afba16SPali Rohár static DEFINE_MUTEX(i8k_mutex); 68a5afba16SPali Rohár static char bios_version[4]; 69a5afba16SPali Rohár static struct device *i8k_hwmon_dev; 70a5afba16SPali Rohár static u32 i8k_hwmon_flags; 71a5afba16SPali Rohár static uint i8k_fan_mult = I8K_FAN_MULT; 72a5afba16SPali Rohár static uint i8k_pwm_mult; 73a5afba16SPali Rohár static uint i8k_fan_max = I8K_FAN_HIGH; 74a5afba16SPali Rohár 75a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP1 (1 << 0) 76a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP2 (1 << 1) 77a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP3 (1 << 2) 78a5afba16SPali Rohár #define I8K_HWMON_HAVE_TEMP4 (1 << 3) 79a5afba16SPali Rohár #define I8K_HWMON_HAVE_FAN1 (1 << 4) 80a5afba16SPali Rohár #define I8K_HWMON_HAVE_FAN2 (1 << 5) 81a5afba16SPali Rohár 82a5afba16SPali Rohár MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); 83a5afba16SPali Rohár MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); 84039ae585SPali Rohár MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); 85a5afba16SPali Rohár MODULE_LICENSE("GPL"); 86a5afba16SPali Rohár MODULE_ALIAS("i8k"); 87a5afba16SPali Rohár 88a5afba16SPali Rohár static bool force; 89a5afba16SPali Rohár module_param(force, bool, 0); 90a5afba16SPali Rohár MODULE_PARM_DESC(force, "Force loading without checking for supported models"); 91a5afba16SPali Rohár 92a5afba16SPali Rohár static bool ignore_dmi; 93a5afba16SPali Rohár module_param(ignore_dmi, bool, 0); 94a5afba16SPali Rohár MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match"); 95a5afba16SPali Rohár 96039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K) 97a5afba16SPali Rohár static bool restricted; 98a5afba16SPali Rohár module_param(restricted, bool, 0); 99a5afba16SPali Rohár MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set"); 100a5afba16SPali Rohár 101a5afba16SPali Rohár static bool power_status; 102a5afba16SPali Rohár module_param(power_status, bool, 0600); 103a5afba16SPali Rohár MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k"); 104039ae585SPali Rohár #endif 105a5afba16SPali Rohár 106a5afba16SPali Rohár static uint fan_mult; 107a5afba16SPali Rohár module_param(fan_mult, uint, 0); 108a5afba16SPali Rohár MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)"); 109a5afba16SPali Rohár 110a5afba16SPali Rohár static uint fan_max; 111a5afba16SPali Rohár module_param(fan_max, uint, 0); 112a5afba16SPali Rohár MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)"); 113a5afba16SPali Rohár 114a5afba16SPali Rohár struct smm_regs { 115a5afba16SPali Rohár unsigned int eax; 116a5afba16SPali Rohár unsigned int ebx __packed; 117a5afba16SPali Rohár unsigned int ecx __packed; 118a5afba16SPali Rohár unsigned int edx __packed; 119a5afba16SPali Rohár unsigned int esi __packed; 120a5afba16SPali Rohár unsigned int edi __packed; 121a5afba16SPali Rohár }; 122a5afba16SPali Rohár 123a5afba16SPali Rohár static inline const char *i8k_get_dmi_data(int field) 124a5afba16SPali Rohár { 125a5afba16SPali Rohár const char *dmi_data = dmi_get_system_info(field); 126a5afba16SPali Rohár 127a5afba16SPali Rohár return dmi_data && *dmi_data ? dmi_data : "?"; 128a5afba16SPali Rohár } 129a5afba16SPali Rohár 130a5afba16SPali Rohár /* 131a5afba16SPali Rohár * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard. 132a5afba16SPali Rohár */ 133a5afba16SPali Rohár static int i8k_smm(struct smm_regs *regs) 134a5afba16SPali Rohár { 135a5afba16SPali Rohár int rc; 136a5afba16SPali Rohár int eax = regs->eax; 137a5afba16SPali Rohár cpumask_var_t old_mask; 138a5afba16SPali Rohár 139a5afba16SPali Rohár /* SMM requires CPU 0 */ 140a5afba16SPali Rohár if (!alloc_cpumask_var(&old_mask, GFP_KERNEL)) 141a5afba16SPali Rohár return -ENOMEM; 142a5afba16SPali Rohár cpumask_copy(old_mask, ¤t->cpus_allowed); 143a5afba16SPali Rohár rc = set_cpus_allowed_ptr(current, cpumask_of(0)); 144a5afba16SPali Rohár if (rc) 145a5afba16SPali Rohár goto out; 146a5afba16SPali Rohár if (smp_processor_id() != 0) { 147a5afba16SPali Rohár rc = -EBUSY; 148a5afba16SPali Rohár goto out; 149a5afba16SPali Rohár } 150a5afba16SPali Rohár 151a5afba16SPali Rohár #if defined(CONFIG_X86_64) 152a5afba16SPali Rohár asm volatile("pushq %%rax\n\t" 153a5afba16SPali Rohár "movl 0(%%rax),%%edx\n\t" 154a5afba16SPali Rohár "pushq %%rdx\n\t" 155a5afba16SPali Rohár "movl 4(%%rax),%%ebx\n\t" 156a5afba16SPali Rohár "movl 8(%%rax),%%ecx\n\t" 157a5afba16SPali Rohár "movl 12(%%rax),%%edx\n\t" 158a5afba16SPali Rohár "movl 16(%%rax),%%esi\n\t" 159a5afba16SPali Rohár "movl 20(%%rax),%%edi\n\t" 160a5afba16SPali Rohár "popq %%rax\n\t" 161a5afba16SPali Rohár "out %%al,$0xb2\n\t" 162a5afba16SPali Rohár "out %%al,$0x84\n\t" 163a5afba16SPali Rohár "xchgq %%rax,(%%rsp)\n\t" 164a5afba16SPali Rohár "movl %%ebx,4(%%rax)\n\t" 165a5afba16SPali Rohár "movl %%ecx,8(%%rax)\n\t" 166a5afba16SPali Rohár "movl %%edx,12(%%rax)\n\t" 167a5afba16SPali Rohár "movl %%esi,16(%%rax)\n\t" 168a5afba16SPali Rohár "movl %%edi,20(%%rax)\n\t" 169a5afba16SPali Rohár "popq %%rdx\n\t" 170a5afba16SPali Rohár "movl %%edx,0(%%rax)\n\t" 171a5afba16SPali Rohár "pushfq\n\t" 172a5afba16SPali Rohár "popq %%rax\n\t" 173a5afba16SPali Rohár "andl $1,%%eax\n" 174a5afba16SPali Rohár : "=a"(rc) 175a5afba16SPali Rohár : "a"(regs) 176a5afba16SPali Rohár : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); 177a5afba16SPali Rohár #else 178a5afba16SPali Rohár asm volatile("pushl %%eax\n\t" 179a5afba16SPali Rohár "movl 0(%%eax),%%edx\n\t" 180a5afba16SPali Rohár "push %%edx\n\t" 181a5afba16SPali Rohár "movl 4(%%eax),%%ebx\n\t" 182a5afba16SPali Rohár "movl 8(%%eax),%%ecx\n\t" 183a5afba16SPali Rohár "movl 12(%%eax),%%edx\n\t" 184a5afba16SPali Rohár "movl 16(%%eax),%%esi\n\t" 185a5afba16SPali Rohár "movl 20(%%eax),%%edi\n\t" 186a5afba16SPali Rohár "popl %%eax\n\t" 187a5afba16SPali Rohár "out %%al,$0xb2\n\t" 188a5afba16SPali Rohár "out %%al,$0x84\n\t" 189a5afba16SPali Rohár "xchgl %%eax,(%%esp)\n\t" 190a5afba16SPali Rohár "movl %%ebx,4(%%eax)\n\t" 191a5afba16SPali Rohár "movl %%ecx,8(%%eax)\n\t" 192a5afba16SPali Rohár "movl %%edx,12(%%eax)\n\t" 193a5afba16SPali Rohár "movl %%esi,16(%%eax)\n\t" 194a5afba16SPali Rohár "movl %%edi,20(%%eax)\n\t" 195a5afba16SPali Rohár "popl %%edx\n\t" 196a5afba16SPali Rohár "movl %%edx,0(%%eax)\n\t" 197a5afba16SPali Rohár "lahf\n\t" 198a5afba16SPali Rohár "shrl $8,%%eax\n\t" 199a5afba16SPali Rohár "andl $1,%%eax\n" 200a5afba16SPali Rohár : "=a"(rc) 201a5afba16SPali Rohár : "a"(regs) 202a5afba16SPali Rohár : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); 203a5afba16SPali Rohár #endif 204a5afba16SPali Rohár if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax) 205a5afba16SPali Rohár rc = -EINVAL; 206a5afba16SPali Rohár 207a5afba16SPali Rohár out: 208a5afba16SPali Rohár set_cpus_allowed_ptr(current, old_mask); 209a5afba16SPali Rohár free_cpumask_var(old_mask); 210a5afba16SPali Rohár return rc; 211a5afba16SPali Rohár } 212a5afba16SPali Rohár 213a5afba16SPali Rohár /* 214a5afba16SPali Rohár * Read the fan status. 215a5afba16SPali Rohár */ 216a5afba16SPali Rohár static int i8k_get_fan_status(int fan) 217a5afba16SPali Rohár { 218a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; 219a5afba16SPali Rohár 220a5afba16SPali Rohár regs.ebx = fan & 0xff; 221a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 222a5afba16SPali Rohár } 223a5afba16SPali Rohár 224a5afba16SPali Rohár /* 225a5afba16SPali Rohár * Read the fan speed in RPM. 226a5afba16SPali Rohár */ 227a5afba16SPali Rohár static int i8k_get_fan_speed(int fan) 228a5afba16SPali Rohár { 229a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; 230a5afba16SPali Rohár 231a5afba16SPali Rohár regs.ebx = fan & 0xff; 232a5afba16SPali Rohár return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; 233a5afba16SPali Rohár } 234a5afba16SPali Rohár 235a5afba16SPali Rohár /* 236a5afba16SPali Rohár * Read the fan type. 237a5afba16SPali Rohár */ 238a5afba16SPali Rohár static int i8k_get_fan_type(int fan) 239a5afba16SPali Rohár { 240a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; 241a5afba16SPali Rohár 242a5afba16SPali Rohár regs.ebx = fan & 0xff; 243a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 244a5afba16SPali Rohár } 245a5afba16SPali Rohár 246a5afba16SPali Rohár /* 247a5afba16SPali Rohár * Read the fan nominal rpm for specific fan speed. 248a5afba16SPali Rohár */ 249a5afba16SPali Rohár static int i8k_get_fan_nominal_speed(int fan, int speed) 250a5afba16SPali Rohár { 251a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; 252a5afba16SPali Rohár 253a5afba16SPali Rohár regs.ebx = (fan & 0xff) | (speed << 8); 254a5afba16SPali Rohár return i8k_smm(®s) ? : (regs.eax & 0xffff) * i8k_fan_mult; 255a5afba16SPali Rohár } 256a5afba16SPali Rohár 257a5afba16SPali Rohár /* 258a5afba16SPali Rohár * Set the fan speed (off, low, high). Returns the new fan status. 259a5afba16SPali Rohár */ 260a5afba16SPali Rohár static int i8k_set_fan(int fan, int speed) 261a5afba16SPali Rohár { 262a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; 263a5afba16SPali Rohár 264a5afba16SPali Rohár speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed); 265a5afba16SPali Rohár regs.ebx = (fan & 0xff) | (speed << 8); 266a5afba16SPali Rohár 267a5afba16SPali Rohár return i8k_smm(®s) ? : i8k_get_fan_status(fan); 268a5afba16SPali Rohár } 269a5afba16SPali Rohár 270a5afba16SPali Rohár static int i8k_get_temp_type(int sensor) 271a5afba16SPali Rohár { 272a5afba16SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; 273a5afba16SPali Rohár 274a5afba16SPali Rohár regs.ebx = sensor & 0xff; 275a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 276a5afba16SPali Rohár } 277a5afba16SPali Rohár 278a5afba16SPali Rohár /* 279a5afba16SPali Rohár * Read the cpu temperature. 280a5afba16SPali Rohár */ 281a5afba16SPali Rohár static int _i8k_get_temp(int sensor) 282a5afba16SPali Rohár { 283a5afba16SPali Rohár struct smm_regs regs = { 284a5afba16SPali Rohár .eax = I8K_SMM_GET_TEMP, 285a5afba16SPali Rohár .ebx = sensor & 0xff, 286a5afba16SPali Rohár }; 287a5afba16SPali Rohár 288a5afba16SPali Rohár return i8k_smm(®s) ? : regs.eax & 0xff; 289a5afba16SPali Rohár } 290a5afba16SPali Rohár 291a5afba16SPali Rohár static int i8k_get_temp(int sensor) 292a5afba16SPali Rohár { 293a5afba16SPali Rohár int temp = _i8k_get_temp(sensor); 294a5afba16SPali Rohár 295a5afba16SPali Rohár /* 296a5afba16SPali Rohár * Sometimes the temperature sensor returns 0x99, which is out of range. 297a5afba16SPali Rohár * In this case we retry (once) before returning an error. 298a5afba16SPali Rohár # 1003655137 00000058 00005a4b 299a5afba16SPali Rohár # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees 300a5afba16SPali Rohár # 1003655139 00000054 00005c52 301a5afba16SPali Rohár */ 302a5afba16SPali Rohár if (temp == 0x99) { 303a5afba16SPali Rohár msleep(100); 304a5afba16SPali Rohár temp = _i8k_get_temp(sensor); 305a5afba16SPali Rohár } 306a5afba16SPali Rohár /* 307a5afba16SPali Rohár * Return -ENODATA for all invalid temperatures. 308a5afba16SPali Rohár * 309a5afba16SPali Rohár * Known instances are the 0x99 value as seen above as well as 310a5afba16SPali Rohár * 0xc1 (193), which may be returned when trying to read the GPU 311a5afba16SPali Rohár * temperature if the system supports a GPU and it is currently 312a5afba16SPali Rohár * turned off. 313a5afba16SPali Rohár */ 314a5afba16SPali Rohár if (temp > I8K_MAX_TEMP) 315a5afba16SPali Rohár return -ENODATA; 316a5afba16SPali Rohár 317a5afba16SPali Rohár return temp; 318a5afba16SPali Rohár } 319a5afba16SPali Rohár 320a5afba16SPali Rohár static int i8k_get_dell_signature(int req_fn) 321a5afba16SPali Rohár { 322a5afba16SPali Rohár struct smm_regs regs = { .eax = req_fn, }; 323a5afba16SPali Rohár int rc; 324a5afba16SPali Rohár 325a5afba16SPali Rohár rc = i8k_smm(®s); 326a5afba16SPali Rohár if (rc < 0) 327a5afba16SPali Rohár return rc; 328a5afba16SPali Rohár 329a5afba16SPali Rohár return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1; 330a5afba16SPali Rohár } 331a5afba16SPali Rohár 332039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K) 333039ae585SPali Rohár 334039ae585SPali Rohár /* 335039ae585SPali Rohár * Read the Fn key status. 336039ae585SPali Rohár */ 337039ae585SPali Rohár static int i8k_get_fn_status(void) 338039ae585SPali Rohár { 339039ae585SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, }; 340039ae585SPali Rohár int rc; 341039ae585SPali Rohár 342039ae585SPali Rohár rc = i8k_smm(®s); 343039ae585SPali Rohár if (rc < 0) 344039ae585SPali Rohár return rc; 345039ae585SPali Rohár 346039ae585SPali Rohár switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) { 347039ae585SPali Rohár case I8K_FN_UP: 348039ae585SPali Rohár return I8K_VOL_UP; 349039ae585SPali Rohár case I8K_FN_DOWN: 350039ae585SPali Rohár return I8K_VOL_DOWN; 351039ae585SPali Rohár case I8K_FN_MUTE: 352039ae585SPali Rohár return I8K_VOL_MUTE; 353039ae585SPali Rohár default: 354039ae585SPali Rohár return 0; 355039ae585SPali Rohár } 356039ae585SPali Rohár } 357039ae585SPali Rohár 358039ae585SPali Rohár /* 359039ae585SPali Rohár * Read the power status. 360039ae585SPali Rohár */ 361039ae585SPali Rohár static int i8k_get_power_status(void) 362039ae585SPali Rohár { 363039ae585SPali Rohár struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, }; 364039ae585SPali Rohár int rc; 365039ae585SPali Rohár 366039ae585SPali Rohár rc = i8k_smm(®s); 367039ae585SPali Rohár if (rc < 0) 368039ae585SPali Rohár return rc; 369039ae585SPali Rohár 370039ae585SPali Rohár return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY; 371039ae585SPali Rohár } 372039ae585SPali Rohár 373039ae585SPali Rohár /* 374039ae585SPali Rohár * Procfs interface 375039ae585SPali Rohár */ 376039ae585SPali Rohár 377a5afba16SPali Rohár static int 378a5afba16SPali Rohár i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg) 379a5afba16SPali Rohár { 380a5afba16SPali Rohár int val = 0; 381a5afba16SPali Rohár int speed; 382a5afba16SPali Rohár unsigned char buff[16]; 383a5afba16SPali Rohár int __user *argp = (int __user *)arg; 384a5afba16SPali Rohár 385a5afba16SPali Rohár if (!argp) 386a5afba16SPali Rohár return -EINVAL; 387a5afba16SPali Rohár 388a5afba16SPali Rohár switch (cmd) { 389a5afba16SPali Rohár case I8K_BIOS_VERSION: 390a5afba16SPali Rohár val = (bios_version[0] << 16) | 391a5afba16SPali Rohár (bios_version[1] << 8) | bios_version[2]; 392a5afba16SPali Rohár break; 393a5afba16SPali Rohár 394a5afba16SPali Rohár case I8K_MACHINE_ID: 395a5afba16SPali Rohár memset(buff, 0, 16); 396a5afba16SPali Rohár strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), 397a5afba16SPali Rohár sizeof(buff)); 398a5afba16SPali Rohár break; 399a5afba16SPali Rohár 400a5afba16SPali Rohár case I8K_FN_STATUS: 401a5afba16SPali Rohár val = i8k_get_fn_status(); 402a5afba16SPali Rohár break; 403a5afba16SPali Rohár 404a5afba16SPali Rohár case I8K_POWER_STATUS: 405a5afba16SPali Rohár val = i8k_get_power_status(); 406a5afba16SPali Rohár break; 407a5afba16SPali Rohár 408a5afba16SPali Rohár case I8K_GET_TEMP: 409a5afba16SPali Rohár val = i8k_get_temp(0); 410a5afba16SPali Rohár break; 411a5afba16SPali Rohár 412a5afba16SPali Rohár case I8K_GET_SPEED: 413a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 414a5afba16SPali Rohár return -EFAULT; 415a5afba16SPali Rohár 416a5afba16SPali Rohár val = i8k_get_fan_speed(val); 417a5afba16SPali Rohár break; 418a5afba16SPali Rohár 419a5afba16SPali Rohár case I8K_GET_FAN: 420a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 421a5afba16SPali Rohár return -EFAULT; 422a5afba16SPali Rohár 423a5afba16SPali Rohár val = i8k_get_fan_status(val); 424a5afba16SPali Rohár break; 425a5afba16SPali Rohár 426a5afba16SPali Rohár case I8K_SET_FAN: 427a5afba16SPali Rohár if (restricted && !capable(CAP_SYS_ADMIN)) 428a5afba16SPali Rohár return -EPERM; 429a5afba16SPali Rohár 430a5afba16SPali Rohár if (copy_from_user(&val, argp, sizeof(int))) 431a5afba16SPali Rohár return -EFAULT; 432a5afba16SPali Rohár 433a5afba16SPali Rohár if (copy_from_user(&speed, argp + 1, sizeof(int))) 434a5afba16SPali Rohár return -EFAULT; 435a5afba16SPali Rohár 436a5afba16SPali Rohár val = i8k_set_fan(val, speed); 437a5afba16SPali Rohár break; 438a5afba16SPali Rohár 439a5afba16SPali Rohár default: 440a5afba16SPali Rohár return -EINVAL; 441a5afba16SPali Rohár } 442a5afba16SPali Rohár 443a5afba16SPali Rohár if (val < 0) 444a5afba16SPali Rohár return val; 445a5afba16SPali Rohár 446a5afba16SPali Rohár switch (cmd) { 447a5afba16SPali Rohár case I8K_BIOS_VERSION: 448a5afba16SPali Rohár if (copy_to_user(argp, &val, 4)) 449a5afba16SPali Rohár return -EFAULT; 450a5afba16SPali Rohár 451a5afba16SPali Rohár break; 452a5afba16SPali Rohár case I8K_MACHINE_ID: 453a5afba16SPali Rohár if (copy_to_user(argp, buff, 16)) 454a5afba16SPali Rohár return -EFAULT; 455a5afba16SPali Rohár 456a5afba16SPali Rohár break; 457a5afba16SPali Rohár default: 458a5afba16SPali Rohár if (copy_to_user(argp, &val, sizeof(int))) 459a5afba16SPali Rohár return -EFAULT; 460a5afba16SPali Rohár 461a5afba16SPali Rohár break; 462a5afba16SPali Rohár } 463a5afba16SPali Rohár 464a5afba16SPali Rohár return 0; 465a5afba16SPali Rohár } 466a5afba16SPali Rohár 467a5afba16SPali Rohár static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) 468a5afba16SPali Rohár { 469a5afba16SPali Rohár long ret; 470a5afba16SPali Rohár 471a5afba16SPali Rohár mutex_lock(&i8k_mutex); 472a5afba16SPali Rohár ret = i8k_ioctl_unlocked(fp, cmd, arg); 473a5afba16SPali Rohár mutex_unlock(&i8k_mutex); 474a5afba16SPali Rohár 475a5afba16SPali Rohár return ret; 476a5afba16SPali Rohár } 477a5afba16SPali Rohár 478a5afba16SPali Rohár /* 479a5afba16SPali Rohár * Print the information for /proc/i8k. 480a5afba16SPali Rohár */ 481a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset) 482a5afba16SPali Rohár { 483a5afba16SPali Rohár int fn_key, cpu_temp, ac_power; 484a5afba16SPali Rohár int left_fan, right_fan, left_speed, right_speed; 485a5afba16SPali Rohár 486a5afba16SPali Rohár cpu_temp = i8k_get_temp(0); /* 11100 µs */ 487a5afba16SPali Rohár left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */ 488a5afba16SPali Rohár right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */ 489a5afba16SPali Rohár left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */ 490a5afba16SPali Rohár right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */ 491a5afba16SPali Rohár fn_key = i8k_get_fn_status(); /* 750 µs */ 492a5afba16SPali Rohár if (power_status) 493a5afba16SPali Rohár ac_power = i8k_get_power_status(); /* 14700 µs */ 494a5afba16SPali Rohár else 495a5afba16SPali Rohár ac_power = -1; 496a5afba16SPali Rohár 497a5afba16SPali Rohár /* 498a5afba16SPali Rohár * Info: 499a5afba16SPali Rohár * 500a5afba16SPali Rohár * 1) Format version (this will change if format changes) 501a5afba16SPali Rohár * 2) BIOS version 502a5afba16SPali Rohár * 3) BIOS machine ID 503a5afba16SPali Rohár * 4) Cpu temperature 504a5afba16SPali Rohár * 5) Left fan status 505a5afba16SPali Rohár * 6) Right fan status 506a5afba16SPali Rohár * 7) Left fan speed 507a5afba16SPali Rohár * 8) Right fan speed 508a5afba16SPali Rohár * 9) AC power 509a5afba16SPali Rohár * 10) Fn Key status 510a5afba16SPali Rohár */ 511a5afba16SPali Rohár seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", 512a5afba16SPali Rohár I8K_PROC_FMT, 513a5afba16SPali Rohár bios_version, 514a5afba16SPali Rohár i8k_get_dmi_data(DMI_PRODUCT_SERIAL), 515a5afba16SPali Rohár cpu_temp, 516a5afba16SPali Rohár left_fan, right_fan, left_speed, right_speed, 517a5afba16SPali Rohár ac_power, fn_key); 518a5afba16SPali Rohár 519a5afba16SPali Rohár return 0; 520a5afba16SPali Rohár } 521a5afba16SPali Rohár 522a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file) 523a5afba16SPali Rohár { 524a5afba16SPali Rohár return single_open(file, i8k_proc_show, NULL); 525a5afba16SPali Rohár } 526a5afba16SPali Rohár 527039ae585SPali Rohár static const struct file_operations i8k_fops = { 528039ae585SPali Rohár .owner = THIS_MODULE, 529039ae585SPali Rohár .open = i8k_open_fs, 530039ae585SPali Rohár .read = seq_read, 531039ae585SPali Rohár .llseek = seq_lseek, 532039ae585SPali Rohár .release = single_release, 533039ae585SPali Rohár .unlocked_ioctl = i8k_ioctl, 534039ae585SPali Rohár }; 535039ae585SPali Rohár 536039ae585SPali Rohár static void __init i8k_init_procfs(void) 537039ae585SPali Rohár { 538039ae585SPali Rohár /* Register the proc entry */ 539039ae585SPali Rohár proc_create("i8k", 0, NULL, &i8k_fops); 540039ae585SPali Rohár } 541039ae585SPali Rohár 542039ae585SPali Rohár static void __exit i8k_exit_procfs(void) 543039ae585SPali Rohár { 544039ae585SPali Rohár remove_proc_entry("i8k", NULL); 545039ae585SPali Rohár } 546039ae585SPali Rohár 547039ae585SPali Rohár #else 548039ae585SPali Rohár 549039ae585SPali Rohár static inline void __init i8k_init_procfs(void) 550039ae585SPali Rohár { 551039ae585SPali Rohár } 552039ae585SPali Rohár 553039ae585SPali Rohár static inline void __exit i8k_exit_procfs(void) 554039ae585SPali Rohár { 555039ae585SPali Rohár } 556039ae585SPali Rohár 557039ae585SPali Rohár #endif 558a5afba16SPali Rohár 559a5afba16SPali Rohár /* 560a5afba16SPali Rohár * Hwmon interface 561a5afba16SPali Rohár */ 562a5afba16SPali Rohár 563a5afba16SPali Rohár static ssize_t i8k_hwmon_show_temp_label(struct device *dev, 564a5afba16SPali Rohár struct device_attribute *devattr, 565a5afba16SPali Rohár char *buf) 566a5afba16SPali Rohár { 567a5afba16SPali Rohár static const char * const labels[] = { 568a5afba16SPali Rohár "CPU", 569a5afba16SPali Rohár "GPU", 570a5afba16SPali Rohár "SODIMM", 571a5afba16SPali Rohár "Other", 572a5afba16SPali Rohár "Ambient", 573a5afba16SPali Rohár "Other", 574a5afba16SPali Rohár }; 575a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 576a5afba16SPali Rohár int type; 577a5afba16SPali Rohár 578a5afba16SPali Rohár type = i8k_get_temp_type(index); 579a5afba16SPali Rohár if (type < 0) 580a5afba16SPali Rohár return type; 581a5afba16SPali Rohár if (type >= ARRAY_SIZE(labels)) 582a5afba16SPali Rohár type = ARRAY_SIZE(labels) - 1; 583a5afba16SPali Rohár return sprintf(buf, "%s\n", labels[type]); 584a5afba16SPali Rohár } 585a5afba16SPali Rohár 586a5afba16SPali Rohár static ssize_t i8k_hwmon_show_temp(struct device *dev, 587a5afba16SPali Rohár struct device_attribute *devattr, 588a5afba16SPali Rohár char *buf) 589a5afba16SPali Rohár { 590a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 591a5afba16SPali Rohár int temp; 592a5afba16SPali Rohár 593a5afba16SPali Rohár temp = i8k_get_temp(index); 594a5afba16SPali Rohár if (temp < 0) 595a5afba16SPali Rohár return temp; 596a5afba16SPali Rohár return sprintf(buf, "%d\n", temp * 1000); 597a5afba16SPali Rohár } 598a5afba16SPali Rohár 599a5afba16SPali Rohár static ssize_t i8k_hwmon_show_fan_label(struct device *dev, 600a5afba16SPali Rohár struct device_attribute *devattr, 601a5afba16SPali Rohár char *buf) 602a5afba16SPali Rohár { 603a5afba16SPali Rohár static const char * const labels[] = { 604a5afba16SPali Rohár "Processor Fan", 605a5afba16SPali Rohár "Motherboard Fan", 606a5afba16SPali Rohár "Video Fan", 607a5afba16SPali Rohár "Power Supply Fan", 608a5afba16SPali Rohár "Chipset Fan", 609a5afba16SPali Rohár "Other Fan", 610a5afba16SPali Rohár }; 611a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 612a5afba16SPali Rohár bool dock = false; 613a5afba16SPali Rohár int type; 614a5afba16SPali Rohár 615a5afba16SPali Rohár type = i8k_get_fan_type(index); 616a5afba16SPali Rohár if (type < 0) 617a5afba16SPali Rohár return type; 618a5afba16SPali Rohár 619a5afba16SPali Rohár if (type & 0x10) { 620a5afba16SPali Rohár dock = true; 621a5afba16SPali Rohár type &= 0x0F; 622a5afba16SPali Rohár } 623a5afba16SPali Rohár 624a5afba16SPali Rohár if (type >= ARRAY_SIZE(labels)) 625a5afba16SPali Rohár type = (ARRAY_SIZE(labels) - 1); 626a5afba16SPali Rohár 627a5afba16SPali Rohár return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]); 628a5afba16SPali Rohár } 629a5afba16SPali Rohár 630a5afba16SPali Rohár static ssize_t i8k_hwmon_show_fan(struct device *dev, 631a5afba16SPali Rohár struct device_attribute *devattr, 632a5afba16SPali Rohár char *buf) 633a5afba16SPali Rohár { 634a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 635a5afba16SPali Rohár int fan_speed; 636a5afba16SPali Rohár 637a5afba16SPali Rohár fan_speed = i8k_get_fan_speed(index); 638a5afba16SPali Rohár if (fan_speed < 0) 639a5afba16SPali Rohár return fan_speed; 640a5afba16SPali Rohár return sprintf(buf, "%d\n", fan_speed); 641a5afba16SPali Rohár } 642a5afba16SPali Rohár 643a5afba16SPali Rohár static ssize_t i8k_hwmon_show_pwm(struct device *dev, 644a5afba16SPali Rohár struct device_attribute *devattr, 645a5afba16SPali Rohár char *buf) 646a5afba16SPali Rohár { 647a5afba16SPali Rohár int index = to_sensor_dev_attr(devattr)->index; 648a5afba16SPali Rohár int status; 649a5afba16SPali Rohár 650a5afba16SPali Rohár status = i8k_get_fan_status(index); 651a5afba16SPali Rohár if (status < 0) 652a5afba16SPali Rohár return -EIO; 653a5afba16SPali Rohár return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255)); 654a5afba16SPali Rohár } 655a5afba16SPali Rohár 656a5afba16SPali Rohár static ssize_t i8k_hwmon_set_pwm(struct device *dev, 657a5afba16SPali Rohár struct device_attribute *attr, 658a5afba16SPali Rohár const char *buf, size_t count) 659a5afba16SPali Rohár { 660a5afba16SPali Rohár int index = to_sensor_dev_attr(attr)->index; 661a5afba16SPali Rohár unsigned long val; 662a5afba16SPali Rohár int err; 663a5afba16SPali Rohár 664a5afba16SPali Rohár err = kstrtoul(buf, 10, &val); 665a5afba16SPali Rohár if (err) 666a5afba16SPali Rohár return err; 667a5afba16SPali Rohár val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max); 668a5afba16SPali Rohár 669a5afba16SPali Rohár mutex_lock(&i8k_mutex); 670a5afba16SPali Rohár err = i8k_set_fan(index, val); 671a5afba16SPali Rohár mutex_unlock(&i8k_mutex); 672a5afba16SPali Rohár 673a5afba16SPali Rohár return err < 0 ? -EIO : count; 674a5afba16SPali Rohár } 675a5afba16SPali Rohár 676a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0); 677a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 678a5afba16SPali Rohár 0); 679a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1); 680a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 681a5afba16SPali Rohár 1); 682a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2); 683a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 684a5afba16SPali Rohár 2); 685a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3); 686a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL, 687a5afba16SPali Rohár 3); 688a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0); 689a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, 690a5afba16SPali Rohár 0); 691a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, 692a5afba16SPali Rohár i8k_hwmon_set_pwm, 0); 693a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 694a5afba16SPali Rohár 1); 695a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL, 696a5afba16SPali Rohár 1); 697a5afba16SPali Rohár static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm, 698a5afba16SPali Rohár i8k_hwmon_set_pwm, 1); 699a5afba16SPali Rohár 700a5afba16SPali Rohár static struct attribute *i8k_attrs[] = { 701a5afba16SPali Rohár &sensor_dev_attr_temp1_input.dev_attr.attr, /* 0 */ 702a5afba16SPali Rohár &sensor_dev_attr_temp1_label.dev_attr.attr, /* 1 */ 703a5afba16SPali Rohár &sensor_dev_attr_temp2_input.dev_attr.attr, /* 2 */ 704a5afba16SPali Rohár &sensor_dev_attr_temp2_label.dev_attr.attr, /* 3 */ 705a5afba16SPali Rohár &sensor_dev_attr_temp3_input.dev_attr.attr, /* 4 */ 706a5afba16SPali Rohár &sensor_dev_attr_temp3_label.dev_attr.attr, /* 5 */ 707a5afba16SPali Rohár &sensor_dev_attr_temp4_input.dev_attr.attr, /* 6 */ 708a5afba16SPali Rohár &sensor_dev_attr_temp4_label.dev_attr.attr, /* 7 */ 709a5afba16SPali Rohár &sensor_dev_attr_fan1_input.dev_attr.attr, /* 8 */ 710a5afba16SPali Rohár &sensor_dev_attr_fan1_label.dev_attr.attr, /* 9 */ 711a5afba16SPali Rohár &sensor_dev_attr_pwm1.dev_attr.attr, /* 10 */ 712a5afba16SPali Rohár &sensor_dev_attr_fan2_input.dev_attr.attr, /* 11 */ 713a5afba16SPali Rohár &sensor_dev_attr_fan2_label.dev_attr.attr, /* 12 */ 714a5afba16SPali Rohár &sensor_dev_attr_pwm2.dev_attr.attr, /* 13 */ 715a5afba16SPali Rohár NULL 716a5afba16SPali Rohár }; 717a5afba16SPali Rohár 718a5afba16SPali Rohár static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr, 719a5afba16SPali Rohár int index) 720a5afba16SPali Rohár { 721a5afba16SPali Rohár if (index >= 0 && index <= 1 && 722a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1)) 723a5afba16SPali Rohár return 0; 724a5afba16SPali Rohár if (index >= 2 && index <= 3 && 725a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2)) 726a5afba16SPali Rohár return 0; 727a5afba16SPali Rohár if (index >= 4 && index <= 5 && 728a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3)) 729a5afba16SPali Rohár return 0; 730a5afba16SPali Rohár if (index >= 6 && index <= 7 && 731a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4)) 732a5afba16SPali Rohár return 0; 733a5afba16SPali Rohár if (index >= 8 && index <= 10 && 734a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1)) 735a5afba16SPali Rohár return 0; 736a5afba16SPali Rohár if (index >= 11 && index <= 13 && 737a5afba16SPali Rohár !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2)) 738a5afba16SPali Rohár return 0; 739a5afba16SPali Rohár 740a5afba16SPali Rohár return attr->mode; 741a5afba16SPali Rohár } 742a5afba16SPali Rohár 743a5afba16SPali Rohár static const struct attribute_group i8k_group = { 744a5afba16SPali Rohár .attrs = i8k_attrs, 745a5afba16SPali Rohár .is_visible = i8k_is_visible, 746a5afba16SPali Rohár }; 747a5afba16SPali Rohár __ATTRIBUTE_GROUPS(i8k); 748a5afba16SPali Rohár 749a5afba16SPali Rohár static int __init i8k_init_hwmon(void) 750a5afba16SPali Rohár { 751a5afba16SPali Rohár int err; 752a5afba16SPali Rohár 753a5afba16SPali Rohár i8k_hwmon_flags = 0; 754a5afba16SPali Rohár 755a5afba16SPali Rohár /* CPU temperature attributes, if temperature type is OK */ 756a5afba16SPali Rohár err = i8k_get_temp_type(0); 757a5afba16SPali Rohár if (err >= 0) 758a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1; 759a5afba16SPali Rohár /* check for additional temperature sensors */ 760a5afba16SPali Rohár err = i8k_get_temp_type(1); 761a5afba16SPali Rohár if (err >= 0) 762a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2; 763a5afba16SPali Rohár err = i8k_get_temp_type(2); 764a5afba16SPali Rohár if (err >= 0) 765a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3; 766a5afba16SPali Rohár err = i8k_get_temp_type(3); 767a5afba16SPali Rohár if (err >= 0) 768a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4; 769a5afba16SPali Rohár 770a5afba16SPali Rohár /* First fan attributes, if fan type is OK */ 771a5afba16SPali Rohár err = i8k_get_fan_type(0); 772a5afba16SPali Rohár if (err >= 0) 773a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1; 774a5afba16SPali Rohár 775a5afba16SPali Rohár /* Second fan attributes, if fan type is OK */ 776a5afba16SPali Rohár err = i8k_get_fan_type(1); 777a5afba16SPali Rohár if (err >= 0) 778a5afba16SPali Rohár i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2; 779a5afba16SPali Rohár 7809026cae1SGabriele Mazzotta i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm", 781039ae585SPali Rohár NULL, i8k_groups); 782a5afba16SPali Rohár if (IS_ERR(i8k_hwmon_dev)) { 783a5afba16SPali Rohár err = PTR_ERR(i8k_hwmon_dev); 784a5afba16SPali Rohár i8k_hwmon_dev = NULL; 785a5afba16SPali Rohár pr_err("hwmon registration failed (%d)\n", err); 786a5afba16SPali Rohár return err; 787a5afba16SPali Rohár } 788a5afba16SPali Rohár return 0; 789a5afba16SPali Rohár } 790a5afba16SPali Rohár 791a5afba16SPali Rohár struct i8k_config_data { 792a5afba16SPali Rohár uint fan_mult; 793a5afba16SPali Rohár uint fan_max; 794a5afba16SPali Rohár }; 795a5afba16SPali Rohár 796a5afba16SPali Rohár enum i8k_configs { 797a5afba16SPali Rohár DELL_LATITUDE_D520, 798a5afba16SPali Rohár DELL_PRECISION_490, 799a5afba16SPali Rohár DELL_STUDIO, 800a5afba16SPali Rohár DELL_XPS, 801a5afba16SPali Rohár }; 802a5afba16SPali Rohár 803a5afba16SPali Rohár static const struct i8k_config_data i8k_config_data[] = { 804a5afba16SPali Rohár [DELL_LATITUDE_D520] = { 805a5afba16SPali Rohár .fan_mult = 1, 806a5afba16SPali Rohár .fan_max = I8K_FAN_TURBO, 807a5afba16SPali Rohár }, 808a5afba16SPali Rohár [DELL_PRECISION_490] = { 809a5afba16SPali Rohár .fan_mult = 1, 810a5afba16SPali Rohár .fan_max = I8K_FAN_TURBO, 811a5afba16SPali Rohár }, 812a5afba16SPali Rohár [DELL_STUDIO] = { 813a5afba16SPali Rohár .fan_mult = 1, 814a5afba16SPali Rohár .fan_max = I8K_FAN_HIGH, 815a5afba16SPali Rohár }, 816a5afba16SPali Rohár [DELL_XPS] = { 817a5afba16SPali Rohár .fan_mult = 1, 818a5afba16SPali Rohár .fan_max = I8K_FAN_HIGH, 819a5afba16SPali Rohár }, 820a5afba16SPali Rohár }; 821a5afba16SPali Rohár 822a5afba16SPali Rohár static struct dmi_system_id i8k_dmi_table[] __initdata = { 823a5afba16SPali Rohár { 824a5afba16SPali Rohár .ident = "Dell Inspiron", 825a5afba16SPali Rohár .matches = { 826a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), 827a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), 828a5afba16SPali Rohár }, 829a5afba16SPali Rohár }, 830a5afba16SPali Rohár { 831a5afba16SPali Rohár .ident = "Dell Latitude", 832a5afba16SPali Rohár .matches = { 833a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), 834a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), 835a5afba16SPali Rohár }, 836a5afba16SPali Rohár }, 837a5afba16SPali Rohár { 838a5afba16SPali Rohár .ident = "Dell Inspiron 2", 839a5afba16SPali Rohár .matches = { 840a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 841a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), 842a5afba16SPali Rohár }, 843a5afba16SPali Rohár }, 844a5afba16SPali Rohár { 845a5afba16SPali Rohár .ident = "Dell Latitude D520", 846a5afba16SPali Rohár .matches = { 847a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 848a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), 849a5afba16SPali Rohár }, 850a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], 851a5afba16SPali Rohár }, 852a5afba16SPali Rohár { 853a5afba16SPali Rohár .ident = "Dell Latitude 2", 854a5afba16SPali Rohár .matches = { 855a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 856a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), 857a5afba16SPali Rohár }, 858a5afba16SPali Rohár }, 859a5afba16SPali Rohár { /* UK Inspiron 6400 */ 860a5afba16SPali Rohár .ident = "Dell Inspiron 3", 861a5afba16SPali Rohár .matches = { 862a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 863a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MM061"), 864a5afba16SPali Rohár }, 865a5afba16SPali Rohár }, 866a5afba16SPali Rohár { 867a5afba16SPali Rohár .ident = "Dell Inspiron 3", 868a5afba16SPali Rohár .matches = { 869a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 870a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MP061"), 871a5afba16SPali Rohár }, 872a5afba16SPali Rohár }, 873a5afba16SPali Rohár { 874a5afba16SPali Rohár .ident = "Dell Precision 490", 875a5afba16SPali Rohár .matches = { 876a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 877a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, 878a5afba16SPali Rohár "Precision WorkStation 490"), 879a5afba16SPali Rohár }, 880a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], 881a5afba16SPali Rohár }, 882a5afba16SPali Rohár { 883a5afba16SPali Rohár .ident = "Dell Precision", 884a5afba16SPali Rohár .matches = { 885a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 886a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Precision"), 887a5afba16SPali Rohár }, 888a5afba16SPali Rohár }, 889a5afba16SPali Rohár { 890a5afba16SPali Rohár .ident = "Dell Vostro", 891a5afba16SPali Rohár .matches = { 892a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 893a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"), 894a5afba16SPali Rohár }, 895a5afba16SPali Rohár }, 896a5afba16SPali Rohár { 897a5afba16SPali Rohár .ident = "Dell XPS421", 898a5afba16SPali Rohár .matches = { 899a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 900a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"), 901a5afba16SPali Rohár }, 902a5afba16SPali Rohár }, 903a5afba16SPali Rohár { 904a5afba16SPali Rohár .ident = "Dell Studio", 905a5afba16SPali Rohár .matches = { 906a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 907a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), 908a5afba16SPali Rohár }, 909a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_STUDIO], 910a5afba16SPali Rohár }, 911a5afba16SPali Rohár { 912a5afba16SPali Rohár .ident = "Dell XPS 13", 913a5afba16SPali Rohár .matches = { 914a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 915a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"), 916a5afba16SPali Rohár }, 917a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_XPS], 918a5afba16SPali Rohár }, 919a5afba16SPali Rohár { 920a5afba16SPali Rohár .ident = "Dell XPS M140", 921a5afba16SPali Rohár .matches = { 922a5afba16SPali Rohár DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 923a5afba16SPali Rohár DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), 924a5afba16SPali Rohár }, 925a5afba16SPali Rohár .driver_data = (void *)&i8k_config_data[DELL_XPS], 926a5afba16SPali Rohár }, 927a5afba16SPali Rohár { } 928a5afba16SPali Rohár }; 929a5afba16SPali Rohár 930a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table); 931a5afba16SPali Rohár 932a4b45b25SPali Rohár static struct dmi_system_id i8k_blacklist_dmi_table[] __initdata = { 933a4b45b25SPali Rohár { 934a4b45b25SPali Rohár /* 9356220f4ebSThorsten Leemhuis * CPU fan speed going up and down on Dell Studio XPS 8000 9366220f4ebSThorsten Leemhuis * for unknown reasons. 9376220f4ebSThorsten Leemhuis */ 9386220f4ebSThorsten Leemhuis .ident = "Dell Studio XPS 8000", 9396220f4ebSThorsten Leemhuis .matches = { 9406220f4ebSThorsten Leemhuis DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 9416220f4ebSThorsten Leemhuis DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"), 9426220f4ebSThorsten Leemhuis }, 9436220f4ebSThorsten Leemhuis }, 9446220f4ebSThorsten Leemhuis { 9456220f4ebSThorsten Leemhuis /* 946a4b45b25SPali Rohár * CPU fan speed going up and down on Dell Studio XPS 8100 947a4b45b25SPali Rohár * for unknown reasons. 948a4b45b25SPali Rohár */ 949a4b45b25SPali Rohár .ident = "Dell Studio XPS 8100", 950a4b45b25SPali Rohár .matches = { 951a4b45b25SPali Rohár DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), 952a4b45b25SPali Rohár DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"), 953a4b45b25SPali Rohár }, 954a4b45b25SPali Rohár }, 955a4b45b25SPali Rohár { } 956a4b45b25SPali Rohár }; 957a4b45b25SPali Rohár 958a5afba16SPali Rohár /* 959a5afba16SPali Rohár * Probe for the presence of a supported laptop. 960a5afba16SPali Rohár */ 961a5afba16SPali Rohár static int __init i8k_probe(void) 962a5afba16SPali Rohár { 963a5afba16SPali Rohár const struct dmi_system_id *id; 964a5afba16SPali Rohár int fan, ret; 965a5afba16SPali Rohár 966a5afba16SPali Rohár /* 967a5afba16SPali Rohár * Get DMI information 968a5afba16SPali Rohár */ 969a4b45b25SPali Rohár if (!dmi_check_system(i8k_dmi_table) || 970a4b45b25SPali Rohár dmi_check_system(i8k_blacklist_dmi_table)) { 971a5afba16SPali Rohár if (!ignore_dmi && !force) 972a5afba16SPali Rohár return -ENODEV; 973a5afba16SPali Rohár 974a5afba16SPali Rohár pr_info("not running on a supported Dell system.\n"); 975a5afba16SPali Rohár pr_info("vendor=%s, model=%s, version=%s\n", 976a5afba16SPali Rohár i8k_get_dmi_data(DMI_SYS_VENDOR), 977a5afba16SPali Rohár i8k_get_dmi_data(DMI_PRODUCT_NAME), 978a5afba16SPali Rohár i8k_get_dmi_data(DMI_BIOS_VERSION)); 979a5afba16SPali Rohár } 980a5afba16SPali Rohár 981a5afba16SPali Rohár strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), 982a5afba16SPali Rohár sizeof(bios_version)); 983a5afba16SPali Rohár 984a5afba16SPali Rohár /* 985a5afba16SPali Rohár * Get SMM Dell signature 986a5afba16SPali Rohár */ 987a5afba16SPali Rohár if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && 988a5afba16SPali Rohár i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { 989a5afba16SPali Rohár pr_err("unable to get SMM Dell signature\n"); 990a5afba16SPali Rohár if (!force) 991a5afba16SPali Rohár return -ENODEV; 992a5afba16SPali Rohár } 993a5afba16SPali Rohár 994a5afba16SPali Rohár /* 995a5afba16SPali Rohár * Set fan multiplier and maximal fan speed from dmi config 996a5afba16SPali Rohár * Values specified in module parameters override values from dmi 997a5afba16SPali Rohár */ 998a5afba16SPali Rohár id = dmi_first_match(i8k_dmi_table); 999a5afba16SPali Rohár if (id && id->driver_data) { 1000a5afba16SPali Rohár const struct i8k_config_data *conf = id->driver_data; 1001a5afba16SPali Rohár if (!fan_mult && conf->fan_mult) 1002a5afba16SPali Rohár fan_mult = conf->fan_mult; 1003a5afba16SPali Rohár if (!fan_max && conf->fan_max) 1004a5afba16SPali Rohár fan_max = conf->fan_max; 1005a5afba16SPali Rohár } 1006a5afba16SPali Rohár 1007a5afba16SPali Rohár i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ 1008a5afba16SPali Rohár i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max); 1009a5afba16SPali Rohár 1010a5afba16SPali Rohár if (!fan_mult) { 1011a5afba16SPali Rohár /* 1012a5afba16SPali Rohár * Autodetect fan multiplier based on nominal rpm 1013a5afba16SPali Rohár * If fan reports rpm value too high then set multiplier to 1 1014a5afba16SPali Rohár */ 1015a5afba16SPali Rohár for (fan = 0; fan < 2; ++fan) { 1016a5afba16SPali Rohár ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max); 1017a5afba16SPali Rohár if (ret < 0) 1018a5afba16SPali Rohár continue; 1019a5afba16SPali Rohár if (ret > I8K_FAN_MAX_RPM) 1020a5afba16SPali Rohár i8k_fan_mult = 1; 1021a5afba16SPali Rohár break; 1022a5afba16SPali Rohár } 1023a5afba16SPali Rohár } else { 1024a5afba16SPali Rohár /* Fan multiplier was specified in module param or in dmi */ 1025a5afba16SPali Rohár i8k_fan_mult = fan_mult; 1026a5afba16SPali Rohár } 1027a5afba16SPali Rohár 1028a5afba16SPali Rohár return 0; 1029a5afba16SPali Rohár } 1030a5afba16SPali Rohár 1031a5afba16SPali Rohár static int __init i8k_init(void) 1032a5afba16SPali Rohár { 1033a5afba16SPali Rohár int err; 1034a5afba16SPali Rohár 1035a5afba16SPali Rohár /* Are we running on an supported laptop? */ 1036a5afba16SPali Rohár if (i8k_probe()) 1037a5afba16SPali Rohár return -ENODEV; 1038a5afba16SPali Rohár 1039a5afba16SPali Rohár err = i8k_init_hwmon(); 1040a5afba16SPali Rohár if (err) 1041a5afba16SPali Rohár return err; 1042039ae585SPali Rohár 1043039ae585SPali Rohár i8k_init_procfs(); 1044039ae585SPali Rohár return 0; 1045a5afba16SPali Rohár } 1046a5afba16SPali Rohár 1047a5afba16SPali Rohár static void __exit i8k_exit(void) 1048a5afba16SPali Rohár { 1049a5afba16SPali Rohár hwmon_device_unregister(i8k_hwmon_dev); 1050039ae585SPali Rohár i8k_exit_procfs(); 1051a5afba16SPali Rohár } 1052a5afba16SPali Rohár 1053a5afba16SPali Rohár module_init(i8k_init); 1054a5afba16SPali Rohár module_exit(i8k_exit); 1055