blob: 5f33778d2af45897694a4adc186e3d3eed99ab05 [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
3 * See http://www.debian.org/~dz/i8k/ for more information
4 * and for latest version of this driver.
5 *
6 * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org>
7 *
Jean Delvare949a9d72011-05-25 20:43:33 +02008 * Hwmon integration:
9 * Copyright (C) 2011 Jean Delvare <khali@linux-fr.org>
10 *
Linus Torvalds1da177e2005-04-16 15:20:36 -070011 * This program is free software; you can redistribute it and/or modify it
12 * under the terms of the GNU General Public License as published by the
13 * Free Software Foundation; either version 2, or (at your option) any
14 * later version.
15 *
16 * This program is distributed in the hope that it will be useful, but
17 * WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * General Public License for more details.
20 */
21
Guenter Roeck60e71aa2013-12-14 09:30:08 -080022#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
23
Linus Torvalds1da177e2005-04-16 15:20:36 -070024#include <linux/module.h>
25#include <linux/types.h>
26#include <linux/init.h>
27#include <linux/proc_fs.h>
Dmitry Torokhov352f8f82005-06-25 14:54:26 -070028#include <linux/seq_file.h>
Dmitry Torokhove70c9d52005-06-25 14:54:25 -070029#include <linux/dmi.h>
Alexey Dobriyan7e80d0d2007-05-08 00:28:59 -070030#include <linux/capability.h>
Arnd Bergmann613655f2010-06-02 14:28:52 +020031#include <linux/mutex.h>
Jean Delvare949a9d72011-05-25 20:43:33 +020032#include <linux/hwmon.h>
33#include <linux/hwmon-sysfs.h>
Linus Torvalds1da177e2005-04-16 15:20:36 -070034#include <asm/uaccess.h>
35#include <asm/io.h>
36
37#include <linux/i8k.h>
38
Dmitry Torokhov352f8f82005-06-25 14:54:26 -070039#define I8K_VERSION "1.14 21/02/2005"
Linus Torvalds1da177e2005-04-16 15:20:36 -070040
41#define I8K_SMM_FN_STATUS 0x0025
42#define I8K_SMM_POWER_STATUS 0x0069
43#define I8K_SMM_SET_FAN 0x01a3
44#define I8K_SMM_GET_FAN 0x00a3
45#define I8K_SMM_GET_SPEED 0x02a3
46#define I8K_SMM_GET_TEMP 0x10a3
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -070047#define I8K_SMM_GET_DELL_SIG1 0xfea3
48#define I8K_SMM_GET_DELL_SIG2 0xffa3
Linus Torvalds1da177e2005-04-16 15:20:36 -070049#define I8K_SMM_BIOS_VERSION 0x00a6
50
51#define I8K_FAN_MULT 30
52#define I8K_MAX_TEMP 127
53
54#define I8K_FN_NONE 0x00
55#define I8K_FN_UP 0x01
56#define I8K_FN_DOWN 0x02
57#define I8K_FN_MUTE 0x04
58#define I8K_FN_MASK 0x07
59#define I8K_FN_SHIFT 8
60
61#define I8K_POWER_AC 0x05
62#define I8K_POWER_BATTERY 0x01
63
64#define I8K_TEMPERATURE_BUG 1
65
Arnd Bergmann613655f2010-06-02 14:28:52 +020066static DEFINE_MUTEX(i8k_mutex);
Dmitry Torokhove70c9d52005-06-25 14:54:25 -070067static char bios_version[4];
Jean Delvare949a9d72011-05-25 20:43:33 +020068static struct device *i8k_hwmon_dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -070069
70MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
71MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
72MODULE_LICENSE("GPL");
73
Rusty Russell90ab5ee2012-01-13 09:32:20 +103074static bool force;
Linus Torvalds1da177e2005-04-16 15:20:36 -070075module_param(force, bool, 0);
76MODULE_PARM_DESC(force, "Force loading without checking for supported models");
77
Rusty Russell90ab5ee2012-01-13 09:32:20 +103078static bool ignore_dmi;
Dmitry Torokhove70c9d52005-06-25 14:54:25 -070079module_param(ignore_dmi, bool, 0);
80MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
81
Rusty Russell90ab5ee2012-01-13 09:32:20 +103082static bool restricted;
Linus Torvalds1da177e2005-04-16 15:20:36 -070083module_param(restricted, bool, 0);
84MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
85
Rusty Russell90ab5ee2012-01-13 09:32:20 +103086static bool power_status;
Linus Torvalds1da177e2005-04-16 15:20:36 -070087module_param(power_status, bool, 0600);
88MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
89
Jochen Eisinger4ed99a22008-05-01 04:34:58 -070090static int fan_mult = I8K_FAN_MULT;
91module_param(fan_mult, int, 0);
92MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with");
93
Dmitry Torokhov352f8f82005-06-25 14:54:26 -070094static int i8k_open_fs(struct inode *inode, struct file *file);
Frederic Weisbeckerd79b6f42010-03-30 07:27:50 +020095static long i8k_ioctl(struct file *, unsigned int, unsigned long);
Linus Torvalds1da177e2005-04-16 15:20:36 -070096
Arjan van de Ven62322d22006-07-03 00:24:21 -070097static const struct file_operations i8k_fops = {
Denis V. Lunev1b502212008-04-29 01:02:34 -070098 .owner = THIS_MODULE,
Dmitry Torokhov352f8f82005-06-25 14:54:26 -070099 .open = i8k_open_fs,
100 .read = seq_read,
101 .llseek = seq_lseek,
102 .release = single_release,
Frederic Weisbeckerd79b6f42010-03-30 07:27:50 +0200103 .unlocked_ioctl = i8k_ioctl,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700104};
105
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700106struct smm_regs {
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700107 unsigned int eax;
108 unsigned int ebx __attribute__ ((packed));
109 unsigned int ecx __attribute__ ((packed));
110 unsigned int edx __attribute__ ((packed));
111 unsigned int esi __attribute__ ((packed));
112 unsigned int edi __attribute__ ((packed));
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700113};
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114
Jeff Garzik18552562007-10-03 15:15:40 -0400115static inline const char *i8k_get_dmi_data(int field)
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700116{
Jeff Garzik18552562007-10-03 15:15:40 -0400117 const char *dmi_data = dmi_get_system_info(field);
Dmitry Torokhov4f005552005-11-12 00:55:15 -0500118
119 return dmi_data && *dmi_data ? dmi_data : "?";
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700120}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700121
122/*
123 * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
124 */
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700125static int i8k_smm(struct smm_regs *regs)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700126{
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700127 int rc;
128 int eax = regs->eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700129
Bradley Smithfe04f222008-02-07 00:16:27 -0800130#if defined(CONFIG_X86_64)
Jim Bos22d32432010-11-15 21:22:37 +0100131 asm volatile("pushq %%rax\n\t"
Bradley Smithfe04f222008-02-07 00:16:27 -0800132 "movl 0(%%rax),%%edx\n\t"
133 "pushq %%rdx\n\t"
134 "movl 4(%%rax),%%ebx\n\t"
135 "movl 8(%%rax),%%ecx\n\t"
136 "movl 12(%%rax),%%edx\n\t"
137 "movl 16(%%rax),%%esi\n\t"
138 "movl 20(%%rax),%%edi\n\t"
139 "popq %%rax\n\t"
140 "out %%al,$0xb2\n\t"
141 "out %%al,$0x84\n\t"
142 "xchgq %%rax,(%%rsp)\n\t"
143 "movl %%ebx,4(%%rax)\n\t"
144 "movl %%ecx,8(%%rax)\n\t"
145 "movl %%edx,12(%%rax)\n\t"
146 "movl %%esi,16(%%rax)\n\t"
147 "movl %%edi,20(%%rax)\n\t"
148 "popq %%rdx\n\t"
149 "movl %%edx,0(%%rax)\n\t"
Luca Tettamantibc1f4192011-05-25 20:43:31 +0200150 "pushfq\n\t"
151 "popq %%rax\n\t"
Bradley Smithfe04f222008-02-07 00:16:27 -0800152 "andl $1,%%eax\n"
Jim Bos22d32432010-11-15 21:22:37 +0100153 :"=a"(rc)
Bradley Smithfe04f222008-02-07 00:16:27 -0800154 : "a"(regs)
155 : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
156#else
Jim Bos22d32432010-11-15 21:22:37 +0100157 asm volatile("pushl %%eax\n\t"
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700158 "movl 0(%%eax),%%edx\n\t"
159 "push %%edx\n\t"
160 "movl 4(%%eax),%%ebx\n\t"
161 "movl 8(%%eax),%%ecx\n\t"
162 "movl 12(%%eax),%%edx\n\t"
163 "movl 16(%%eax),%%esi\n\t"
164 "movl 20(%%eax),%%edi\n\t"
165 "popl %%eax\n\t"
166 "out %%al,$0xb2\n\t"
167 "out %%al,$0x84\n\t"
168 "xchgl %%eax,(%%esp)\n\t"
169 "movl %%ebx,4(%%eax)\n\t"
170 "movl %%ecx,8(%%eax)\n\t"
171 "movl %%edx,12(%%eax)\n\t"
172 "movl %%esi,16(%%eax)\n\t"
173 "movl %%edi,20(%%eax)\n\t"
174 "popl %%edx\n\t"
175 "movl %%edx,0(%%eax)\n\t"
176 "lahf\n\t"
177 "shrl $8,%%eax\n\t"
Jim Bos6b4e81d2010-11-13 12:13:53 +0100178 "andl $1,%%eax\n"
Jim Bos22d32432010-11-15 21:22:37 +0100179 :"=a"(rc)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700180 : "a"(regs)
181 : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
Bradley Smithfe04f222008-02-07 00:16:27 -0800182#endif
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700183 if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700184 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700185
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700186 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700187}
188
189/*
190 * Read the bios version. Return the version as an integer corresponding
191 * to the ascii value, for example "A17" is returned as 0x00413137.
192 */
193static int i8k_get_bios_version(void)
194{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700195 struct smm_regs regs = { .eax = I8K_SMM_BIOS_VERSION, };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700196
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700197 return i8k_smm(&regs) ? : regs.eax;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700198}
199
200/*
Linus Torvalds1da177e2005-04-16 15:20:36 -0700201 * Read the Fn key status.
202 */
203static int i8k_get_fn_status(void)
204{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700205 struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700206 int rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700207
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700208 if ((rc = i8k_smm(&regs)) < 0)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700209 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700210
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700211 switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
212 case I8K_FN_UP:
213 return I8K_VOL_UP;
214 case I8K_FN_DOWN:
215 return I8K_VOL_DOWN;
216 case I8K_FN_MUTE:
217 return I8K_VOL_MUTE;
218 default:
219 return 0;
220 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700221}
222
223/*
224 * Read the power status.
225 */
226static int i8k_get_power_status(void)
227{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700228 struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700229 int rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700230
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700231 if ((rc = i8k_smm(&regs)) < 0)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700232 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700233
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700234 return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700235}
236
237/*
238 * Read the fan status.
239 */
240static int i8k_get_fan_status(int fan)
241{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700242 struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700243
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700244 regs.ebx = fan & 0xff;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700245 return i8k_smm(&regs) ? : regs.eax & 0xff;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700246}
247
248/*
249 * Read the fan speed in RPM.
250 */
251static int i8k_get_fan_speed(int fan)
252{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700253 struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700254
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700255 regs.ebx = fan & 0xff;
Jochen Eisinger4ed99a22008-05-01 04:34:58 -0700256 return i8k_smm(&regs) ? : (regs.eax & 0xffff) * fan_mult;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700257}
258
259/*
260 * Set the fan speed (off, low, high). Returns the new fan status.
261 */
262static int i8k_set_fan(int fan, int speed)
263{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700264 struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
Linus Torvalds1da177e2005-04-16 15:20:36 -0700265
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700266 speed = (speed < 0) ? 0 : ((speed > I8K_FAN_MAX) ? I8K_FAN_MAX : speed);
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700267 regs.ebx = (fan & 0xff) | (speed << 8);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700268
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700269 return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270}
271
272/*
273 * Read the cpu temperature.
274 */
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700275static int i8k_get_temp(int sensor)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276{
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700277 struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP, };
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700278 int rc;
279 int temp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700280
281#ifdef I8K_TEMPERATURE_BUG
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700282 static int prev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700283#endif
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700284 regs.ebx = sensor & 0xff;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700285 if ((rc = i8k_smm(&regs)) < 0)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700286 return rc;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700287
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700288 temp = regs.eax & 0xff;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700289
290#ifdef I8K_TEMPERATURE_BUG
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700291 /*
292 * Sometimes the temperature sensor returns 0x99, which is out of range.
293 * In this case we return (once) the previous cached value. For example:
294 # 1003655137 00000058 00005a4b
295 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
296 # 1003655139 00000054 00005c52
297 */
298 if (temp > I8K_MAX_TEMP) {
299 temp = prev;
300 prev = I8K_MAX_TEMP;
301 } else {
302 prev = temp;
303 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700304#endif
305
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700306 return temp;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700307}
308
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700309static int i8k_get_dell_signature(int req_fn)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700310{
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700311 struct smm_regs regs = { .eax = req_fn, };
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700312 int rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700313
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700314 if ((rc = i8k_smm(&regs)) < 0)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700315 return rc;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700316
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700317 return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700318}
319
Frederic Weisbeckerd79b6f42010-03-30 07:27:50 +0200320static int
321i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700322{
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700323 int val = 0;
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700324 int speed;
325 unsigned char buff[16];
326 int __user *argp = (int __user *)arg;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700327
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700328 if (!argp)
329 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700330
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700331 switch (cmd) {
332 case I8K_BIOS_VERSION:
333 val = i8k_get_bios_version();
334 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700336 case I8K_MACHINE_ID:
337 memset(buff, 0, 16);
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700338 strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), sizeof(buff));
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700339 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700341 case I8K_FN_STATUS:
342 val = i8k_get_fn_status();
343 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700344
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700345 case I8K_POWER_STATUS:
346 val = i8k_get_power_status();
347 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700348
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700349 case I8K_GET_TEMP:
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700350 val = i8k_get_temp(0);
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700351 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700352
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700353 case I8K_GET_SPEED:
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700354 if (copy_from_user(&val, argp, sizeof(int)))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700355 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700356
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700357 val = i8k_get_fan_speed(val);
358 break;
359
360 case I8K_GET_FAN:
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700361 if (copy_from_user(&val, argp, sizeof(int)))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700362 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700363
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700364 val = i8k_get_fan_status(val);
365 break;
366
367 case I8K_SET_FAN:
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700368 if (restricted && !capable(CAP_SYS_ADMIN))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700369 return -EPERM;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700370
371 if (copy_from_user(&val, argp, sizeof(int)))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700372 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700373
374 if (copy_from_user(&speed, argp + 1, sizeof(int)))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700375 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700376
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700377 val = i8k_set_fan(val, speed);
378 break;
379
380 default:
381 return -EINVAL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700382 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700383
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700384 if (val < 0)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700385 return val;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700386
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700387 switch (cmd) {
388 case I8K_BIOS_VERSION:
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700389 if (copy_to_user(argp, &val, 4))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700390 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700391
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700392 break;
393 case I8K_MACHINE_ID:
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700394 if (copy_to_user(argp, buff, 16))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700395 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700396
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700397 break;
398 default:
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700399 if (copy_to_user(argp, &val, sizeof(int)))
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700400 return -EFAULT;
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700401
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700402 break;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700403 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700404
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700405 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700406}
407
Frederic Weisbeckerd79b6f42010-03-30 07:27:50 +0200408static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
409{
410 long ret;
411
Arnd Bergmann613655f2010-06-02 14:28:52 +0200412 mutex_lock(&i8k_mutex);
Frederic Weisbeckerd79b6f42010-03-30 07:27:50 +0200413 ret = i8k_ioctl_unlocked(fp, cmd, arg);
Arnd Bergmann613655f2010-06-02 14:28:52 +0200414 mutex_unlock(&i8k_mutex);
Frederic Weisbeckerd79b6f42010-03-30 07:27:50 +0200415
416 return ret;
417}
418
Linus Torvalds1da177e2005-04-16 15:20:36 -0700419/*
420 * Print the information for /proc/i8k.
421 */
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700422static int i8k_proc_show(struct seq_file *seq, void *offset)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700423{
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700424 int fn_key, cpu_temp, ac_power;
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700425 int left_fan, right_fan, left_speed, right_speed;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700426
Jan Engelhardt96de0e22007-10-19 23:21:04 +0200427 cpu_temp = i8k_get_temp(0); /* 11100 µs */
428 left_fan = i8k_get_fan_status(I8K_FAN_LEFT); /* 580 µs */
429 right_fan = i8k_get_fan_status(I8K_FAN_RIGHT); /* 580 µs */
430 left_speed = i8k_get_fan_speed(I8K_FAN_LEFT); /* 580 µs */
431 right_speed = i8k_get_fan_speed(I8K_FAN_RIGHT); /* 580 µs */
432 fn_key = i8k_get_fn_status(); /* 750 µs */
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700433 if (power_status)
Jan Engelhardt96de0e22007-10-19 23:21:04 +0200434 ac_power = i8k_get_power_status(); /* 14700 µs */
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700435 else
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700436 ac_power = -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700437
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700438 /*
439 * Info:
440 *
441 * 1) Format version (this will change if format changes)
442 * 2) BIOS version
443 * 3) BIOS machine ID
444 * 4) Cpu temperature
445 * 5) Left fan status
446 * 6) Right fan status
447 * 7) Left fan speed
448 * 8) Right fan speed
449 * 9) AC power
450 * 10) Fn Key status
451 */
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700452 return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
453 I8K_PROC_FMT,
454 bios_version,
Dmitry Torokhov4f005552005-11-12 00:55:15 -0500455 i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700456 cpu_temp,
457 left_fan, right_fan, left_speed, right_speed,
458 ac_power, fn_key);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700459}
460
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700461static int i8k_open_fs(struct inode *inode, struct file *file)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700462{
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700463 return single_open(file, i8k_proc_show, NULL);
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700464}
465
Jean Delvare949a9d72011-05-25 20:43:33 +0200466
467/*
468 * Hwmon interface
469 */
470
471static ssize_t i8k_hwmon_show_temp(struct device *dev,
472 struct device_attribute *devattr,
473 char *buf)
474{
475 int cpu_temp;
476
477 cpu_temp = i8k_get_temp(0);
478 if (cpu_temp < 0)
479 return cpu_temp;
480 return sprintf(buf, "%d\n", cpu_temp * 1000);
481}
482
483static ssize_t i8k_hwmon_show_fan(struct device *dev,
484 struct device_attribute *devattr,
485 char *buf)
486{
487 int index = to_sensor_dev_attr(devattr)->index;
488 int fan_speed;
489
490 fan_speed = i8k_get_fan_speed(index);
491 if (fan_speed < 0)
492 return fan_speed;
493 return sprintf(buf, "%d\n", fan_speed);
494}
495
496static ssize_t i8k_hwmon_show_label(struct device *dev,
497 struct device_attribute *devattr,
498 char *buf)
499{
500 static const char *labels[4] = {
501 "i8k",
502 "CPU",
503 "Left Fan",
504 "Right Fan",
505 };
506 int index = to_sensor_dev_attr(devattr)->index;
507
508 return sprintf(buf, "%s\n", labels[index]);
509}
510
511static DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL);
512static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
513 I8K_FAN_LEFT);
514static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
515 I8K_FAN_RIGHT);
516static SENSOR_DEVICE_ATTR(name, S_IRUGO, i8k_hwmon_show_label, NULL, 0);
517static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 1);
518static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_label, NULL, 2);
519static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_label, NULL, 3);
520
521static void i8k_hwmon_remove_files(struct device *dev)
522{
523 device_remove_file(dev, &dev_attr_temp1_input);
524 device_remove_file(dev, &sensor_dev_attr_fan1_input.dev_attr);
525 device_remove_file(dev, &sensor_dev_attr_fan2_input.dev_attr);
526 device_remove_file(dev, &sensor_dev_attr_temp1_label.dev_attr);
527 device_remove_file(dev, &sensor_dev_attr_fan1_label.dev_attr);
528 device_remove_file(dev, &sensor_dev_attr_fan2_label.dev_attr);
529 device_remove_file(dev, &sensor_dev_attr_name.dev_attr);
530}
531
532static int __init i8k_init_hwmon(void)
533{
534 int err;
535
536 i8k_hwmon_dev = hwmon_device_register(NULL);
537 if (IS_ERR(i8k_hwmon_dev)) {
538 err = PTR_ERR(i8k_hwmon_dev);
539 i8k_hwmon_dev = NULL;
Guenter Roeck60e71aa2013-12-14 09:30:08 -0800540 pr_err("hwmon registration failed (%d)\n", err);
Jean Delvare949a9d72011-05-25 20:43:33 +0200541 return err;
542 }
543
544 /* Required name attribute */
545 err = device_create_file(i8k_hwmon_dev,
546 &sensor_dev_attr_name.dev_attr);
547 if (err)
548 goto exit_unregister;
549
550 /* CPU temperature attributes, if temperature reading is OK */
551 err = i8k_get_temp(0);
552 if (err < 0) {
553 dev_dbg(i8k_hwmon_dev,
554 "Not creating temperature attributes (%d)\n", err);
555 } else {
556 err = device_create_file(i8k_hwmon_dev, &dev_attr_temp1_input);
557 if (err)
558 goto exit_remove_files;
559 err = device_create_file(i8k_hwmon_dev,
560 &sensor_dev_attr_temp1_label.dev_attr);
561 if (err)
562 goto exit_remove_files;
563 }
564
565 /* Left fan attributes, if left fan is present */
566 err = i8k_get_fan_status(I8K_FAN_LEFT);
567 if (err < 0) {
568 dev_dbg(i8k_hwmon_dev,
569 "Not creating %s fan attributes (%d)\n", "left", err);
570 } else {
571 err = device_create_file(i8k_hwmon_dev,
572 &sensor_dev_attr_fan1_input.dev_attr);
573 if (err)
574 goto exit_remove_files;
575 err = device_create_file(i8k_hwmon_dev,
576 &sensor_dev_attr_fan1_label.dev_attr);
577 if (err)
578 goto exit_remove_files;
579 }
580
581 /* Right fan attributes, if right fan is present */
582 err = i8k_get_fan_status(I8K_FAN_RIGHT);
583 if (err < 0) {
584 dev_dbg(i8k_hwmon_dev,
585 "Not creating %s fan attributes (%d)\n", "right", err);
586 } else {
587 err = device_create_file(i8k_hwmon_dev,
588 &sensor_dev_attr_fan2_input.dev_attr);
589 if (err)
590 goto exit_remove_files;
591 err = device_create_file(i8k_hwmon_dev,
592 &sensor_dev_attr_fan2_label.dev_attr);
593 if (err)
594 goto exit_remove_files;
595 }
596
597 return 0;
598
599 exit_remove_files:
600 i8k_hwmon_remove_files(i8k_hwmon_dev);
601 exit_unregister:
602 hwmon_device_unregister(i8k_hwmon_dev);
603 return err;
604}
605
606static void __exit i8k_exit_hwmon(void)
607{
608 i8k_hwmon_remove_files(i8k_hwmon_dev);
609 hwmon_device_unregister(i8k_hwmon_dev);
610}
611
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700612static struct dmi_system_id __initdata i8k_dmi_table[] = {
613 {
614 .ident = "Dell Inspiron",
615 .matches = {
616 DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
617 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
618 },
619 },
620 {
621 .ident = "Dell Latitude",
622 .matches = {
623 DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
624 DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
625 },
626 },
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700627 {
628 .ident = "Dell Inspiron 2",
629 .matches = {
630 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
631 DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
632 },
633 },
634 {
635 .ident = "Dell Latitude 2",
636 .matches = {
637 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
638 DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
639 },
640 },
Nick Warnea9000d02008-02-06 01:37:47 -0800641 { /* UK Inspiron 6400 */
642 .ident = "Dell Inspiron 3",
643 .matches = {
644 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
645 DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
646 },
647 },
Frank Sorenson48103c52008-02-07 00:16:31 -0800648 {
649 .ident = "Dell Inspiron 3",
650 .matches = {
651 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
652 DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
653 },
654 },
Andy Spencer7ab21a82009-01-02 16:19:13 +0000655 {
656 .ident = "Dell Precision",
657 .matches = {
658 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
659 DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
660 },
661 },
Federico Heinzbef2a502009-01-02 16:19:23 +0000662 {
663 .ident = "Dell Vostro",
664 .matches = {
665 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
666 DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
667 },
668 },
Alan Cox9aa5b012013-12-03 13:56:56 -0800669 {
670 .ident = "Dell XPS421",
671 .matches = {
672 DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
673 DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
674 },
675 },
Federico Heinzbef2a502009-01-02 16:19:23 +0000676 { }
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700677};
Linus Torvalds1da177e2005-04-16 15:20:36 -0700678
679/*
680 * Probe for the presence of a supported laptop.
681 */
682static int __init i8k_probe(void)
683{
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700684 char buff[4];
685 int version;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700686
Linus Torvalds1da177e2005-04-16 15:20:36 -0700687 /*
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700688 * Get DMI information
Linus Torvalds1da177e2005-04-16 15:20:36 -0700689 */
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700690 if (!dmi_check_system(i8k_dmi_table)) {
691 if (!ignore_dmi && !force)
692 return -ENODEV;
693
Guenter Roeck60e71aa2013-12-14 09:30:08 -0800694 pr_info("not running on a supported Dell system.\n");
695 pr_info("vendor=%s, model=%s, version=%s\n",
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700696 i8k_get_dmi_data(DMI_SYS_VENDOR),
697 i8k_get_dmi_data(DMI_PRODUCT_NAME),
698 i8k_get_dmi_data(DMI_BIOS_VERSION));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700699 }
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700700
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700701 strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), sizeof(bios_version));
702
Linus Torvalds1da177e2005-04-16 15:20:36 -0700703 /*
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700704 * Get SMM Dell signature
Linus Torvalds1da177e2005-04-16 15:20:36 -0700705 */
Dmitry Torokhov7e0fa312005-06-25 14:54:28 -0700706 if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
707 i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
Guenter Roeck60e71aa2013-12-14 09:30:08 -0800708 pr_err("unable to get SMM Dell signature\n");
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700709 if (!force)
710 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700711 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700712
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700713 /*
714 * Get SMM BIOS version.
715 */
716 version = i8k_get_bios_version();
717 if (version <= 0) {
Guenter Roeck60e71aa2013-12-14 09:30:08 -0800718 pr_warn("unable to get SMM BIOS version\n");
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700719 } else {
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700720 buff[0] = (version >> 16) & 0xff;
721 buff[1] = (version >> 8) & 0xff;
722 buff[2] = (version) & 0xff;
723 buff[3] = '\0';
724 /*
725 * If DMI BIOS version is unknown use SMM BIOS version.
726 */
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700727 if (!dmi_get_system_info(DMI_BIOS_VERSION))
728 strlcpy(bios_version, buff, sizeof(bios_version));
729
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700730 /*
731 * Check if the two versions match.
732 */
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700733 if (strncmp(buff, bios_version, sizeof(bios_version)) != 0)
Guenter Roeck60e71aa2013-12-14 09:30:08 -0800734 pr_warn("BIOS version mismatch: %s != %s\n",
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700735 buff, bios_version);
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700736 }
737
738 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700739}
740
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700741static int __init i8k_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700742{
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700743 struct proc_dir_entry *proc_i8k;
Jean Delvare949a9d72011-05-25 20:43:33 +0200744 int err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700745
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700746 /* Are we running on an supported laptop? */
Dmitry Torokhove70c9d52005-06-25 14:54:25 -0700747 if (i8k_probe())
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700748 return -ENODEV;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700749
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700750 /* Register the proc entry */
Denis V. Lunev1b502212008-04-29 01:02:34 -0700751 proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700752 if (!proc_i8k)
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700753 return -ENOENT;
Dmitry Torokhov352f8f82005-06-25 14:54:26 -0700754
Jean Delvare949a9d72011-05-25 20:43:33 +0200755 err = i8k_init_hwmon();
756 if (err)
757 goto exit_remove_proc;
758
Guenter Roeck60e71aa2013-12-14 09:30:08 -0800759 pr_info("Dell laptop SMM driver v%s Massimo Dal Zotto (dz@debian.org)\n",
760 I8K_VERSION);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700761
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700762 return 0;
Jean Delvare949a9d72011-05-25 20:43:33 +0200763
764 exit_remove_proc:
765 remove_proc_entry("i8k", NULL);
766 return err;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700767}
768
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700769static void __exit i8k_exit(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700770{
Jean Delvare949a9d72011-05-25 20:43:33 +0200771 i8k_exit_hwmon();
Dmitry Torokhovdec63ec2005-06-25 14:54:23 -0700772 remove_proc_entry("i8k", NULL);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700773}
Linus Torvalds1da177e2005-04-16 15:20:36 -0700774
Dmitry Torokhov8378b9242005-06-25 14:54:27 -0700775module_init(i8k_init);
776module_exit(i8k_exit);