]>
Commit | Line | Data |
---|---|---|
3e0a4e85 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1da177e4 | 2 | /* |
a5afba16 | 3 | * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops. |
1da177e4 LT |
4 | * |
5 | * Copyright (C) 2001 Massimo Dal Zotto <dz@debian.org> | |
6 | * | |
949a9d70 | 7 | * Hwmon integration: |
7c81c60f | 8 | * Copyright (C) 2011 Jean Delvare <jdelvare@suse.de> |
564132d9 | 9 | * Copyright (C) 2013, 2014 Guenter Roeck <linux@roeck-us.net> |
149ed3d4 | 10 | * Copyright (C) 2014, 2015 Pali Rohár <pali@kernel.org> |
1da177e4 LT |
11 | */ |
12 | ||
60e71aaf GR |
13 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
14 | ||
27046a3f | 15 | #include <linux/cpu.h> |
564132d9 | 16 | #include <linux/delay.h> |
1492fa21 | 17 | #include <linux/err.h> |
1da177e4 | 18 | #include <linux/module.h> |
1492fa21 | 19 | #include <linux/platform_device.h> |
1da177e4 LT |
20 | #include <linux/types.h> |
21 | #include <linux/init.h> | |
22 | #include <linux/proc_fs.h> | |
352f8f8b | 23 | #include <linux/seq_file.h> |
e70c9d5e | 24 | #include <linux/dmi.h> |
7e80d0d0 | 25 | #include <linux/capability.h> |
613655fa | 26 | #include <linux/mutex.h> |
949a9d70 | 27 | #include <linux/hwmon.h> |
12186f51 GR |
28 | #include <linux/uaccess.h> |
29 | #include <linux/io.h> | |
f36fdb9f | 30 | #include <linux/sched.h> |
053ea640 | 31 | #include <linux/ctype.h> |
27046a3f | 32 | #include <linux/smp.h> |
1da177e4 LT |
33 | |
34 | #include <linux/i8k.h> | |
35 | ||
1da177e4 LT |
36 | #define I8K_SMM_FN_STATUS 0x0025 |
37 | #define I8K_SMM_POWER_STATUS 0x0069 | |
38 | #define I8K_SMM_SET_FAN 0x01a3 | |
39 | #define I8K_SMM_GET_FAN 0x00a3 | |
40 | #define I8K_SMM_GET_SPEED 0x02a3 | |
f989e554 | 41 | #define I8K_SMM_GET_FAN_TYPE 0x03a3 |
8f21d8e9 | 42 | #define I8K_SMM_GET_NOM_SPEED 0x04a3 |
1da177e4 | 43 | #define I8K_SMM_GET_TEMP 0x10a3 |
5114b474 | 44 | #define I8K_SMM_GET_TEMP_TYPE 0x11a3 |
7e0fa31d DT |
45 | #define I8K_SMM_GET_DELL_SIG1 0xfea3 |
46 | #define I8K_SMM_GET_DELL_SIG2 0xffa3 | |
1da177e4 LT |
47 | |
48 | #define I8K_FAN_MULT 30 | |
8f21d8e9 | 49 | #define I8K_FAN_MAX_RPM 30000 |
1da177e4 LT |
50 | #define I8K_MAX_TEMP 127 |
51 | ||
52 | #define I8K_FN_NONE 0x00 | |
53 | #define I8K_FN_UP 0x01 | |
54 | #define I8K_FN_DOWN 0x02 | |
55 | #define I8K_FN_MUTE 0x04 | |
56 | #define I8K_FN_MASK 0x07 | |
57 | #define I8K_FN_SHIFT 8 | |
58 | ||
59 | #define I8K_POWER_AC 0x05 | |
60 | #define I8K_POWER_BATTERY 0x01 | |
61 | ||
deeba244 AW |
62 | #define DELL_SMM_NO_TEMP 10 |
63 | #define DELL_SMM_NO_FANS 3 | |
64 | ||
ba04d73c AW |
65 | struct dell_smm_data { |
66 | struct mutex i8k_mutex; /* lock for sensors writes */ | |
67 | char bios_version[4]; | |
68 | char bios_machineid[16]; | |
ba04d73c AW |
69 | uint i8k_fan_mult; |
70 | uint i8k_pwm_mult; | |
71 | uint i8k_fan_max; | |
72 | bool disallow_fan_type_call; | |
73 | bool disallow_fan_support; | |
74 | unsigned int manual_fan; | |
75 | unsigned int auto_fan; | |
deeba244 AW |
76 | int temp_type[DELL_SMM_NO_TEMP]; |
77 | bool fan[DELL_SMM_NO_FANS]; | |
78 | int fan_type[DELL_SMM_NO_FANS]; | |
ba04d73c | 79 | }; |
82ba1d3f | 80 | |
1da177e4 | 81 | MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)"); |
149ed3d4 | 82 | MODULE_AUTHOR("Pali Rohár <pali@kernel.org>"); |
039ae585 | 83 | MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver"); |
1da177e4 | 84 | MODULE_LICENSE("GPL"); |
a5afba16 | 85 | MODULE_ALIAS("i8k"); |
1da177e4 | 86 | |
90ab5ee9 | 87 | static bool force; |
1da177e4 LT |
88 | module_param(force, bool, 0); |
89 | MODULE_PARM_DESC(force, "Force loading without checking for supported models"); | |
90 | ||
90ab5ee9 | 91 | static bool ignore_dmi; |
e70c9d5e DT |
92 | module_param(ignore_dmi, bool, 0); |
93 | MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match"); | |
94 | ||
039ae585 | 95 | #if IS_ENABLED(CONFIG_I8K) |
7613663c | 96 | static bool restricted = true; |
1da177e4 | 97 | module_param(restricted, bool, 0); |
7613663c | 98 | MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)"); |
1da177e4 | 99 | |
90ab5ee9 | 100 | static bool power_status; |
1da177e4 | 101 | module_param(power_status, bool, 0600); |
7613663c | 102 | MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)"); |
039ae585 | 103 | #endif |
1da177e4 | 104 | |
8f21d8e9 | 105 | static uint fan_mult; |
7f69fb03 | 106 | module_param(fan_mult, uint, 0); |
8f21d8e9 | 107 | MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)"); |
4ed99a27 | 108 | |
8f21d8e9 | 109 | static uint fan_max; |
7f69fb03 | 110 | module_param(fan_max, uint, 0); |
8f21d8e9 | 111 | MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)"); |
81474fc2 | 112 | |
8378b924 | 113 | struct smm_regs { |
dec63ec3 | 114 | unsigned int eax; |
12186f51 GR |
115 | unsigned int ebx __packed; |
116 | unsigned int ecx __packed; | |
117 | unsigned int edx __packed; | |
118 | unsigned int esi __packed; | |
119 | unsigned int edi __packed; | |
8378b924 | 120 | }; |
1da177e4 | 121 | |
deeba244 AW |
122 | static const char * const temp_labels[] = { |
123 | "CPU", | |
124 | "GPU", | |
125 | "SODIMM", | |
126 | "Other", | |
127 | "Ambient", | |
128 | "Other", | |
129 | }; | |
130 | ||
131 | static const char * const fan_labels[] = { | |
132 | "Processor Fan", | |
133 | "Motherboard Fan", | |
134 | "Video Fan", | |
135 | "Power Supply Fan", | |
136 | "Chipset Fan", | |
137 | "Other Fan", | |
138 | }; | |
139 | ||
140 | static const char * const docking_labels[] = { | |
141 | "Docking Processor Fan", | |
142 | "Docking Motherboard Fan", | |
143 | "Docking Video Fan", | |
144 | "Docking Power Supply Fan", | |
145 | "Docking Chipset Fan", | |
146 | "Docking Other Fan", | |
147 | }; | |
148 | ||
c9363cdf | 149 | static inline const char __init *i8k_get_dmi_data(int field) |
e70c9d5e | 150 | { |
1855256c | 151 | const char *dmi_data = dmi_get_system_info(field); |
4f005551 DT |
152 | |
153 | return dmi_data && *dmi_data ? dmi_data : "?"; | |
e70c9d5e | 154 | } |
1da177e4 LT |
155 | |
156 | /* | |
157 | * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard. | |
158 | */ | |
27046a3f | 159 | static int i8k_smm_func(void *par) |
1da177e4 | 160 | { |
dec63ec3 | 161 | int rc; |
27046a3f | 162 | struct smm_regs *regs = par; |
dec63ec3 | 163 | int eax = regs->eax; |
f36fdb9f | 164 | |
9d58bec0 PR |
165 | #ifdef DEBUG |
166 | int ebx = regs->ebx; | |
167 | unsigned long duration; | |
168 | ktime_t calltime, delta, rettime; | |
169 | ||
170 | calltime = ktime_get(); | |
171 | #endif | |
172 | ||
f36fdb9f | 173 | /* SMM requires CPU 0 */ |
27046a3f JG |
174 | if (smp_processor_id() != 0) |
175 | return -EBUSY; | |
dec63ec3 | 176 | |
fe04f22f | 177 | #if defined(CONFIG_X86_64) |
22d3243d | 178 | asm volatile("pushq %%rax\n\t" |
fe04f22f BS |
179 | "movl 0(%%rax),%%edx\n\t" |
180 | "pushq %%rdx\n\t" | |
181 | "movl 4(%%rax),%%ebx\n\t" | |
182 | "movl 8(%%rax),%%ecx\n\t" | |
183 | "movl 12(%%rax),%%edx\n\t" | |
184 | "movl 16(%%rax),%%esi\n\t" | |
185 | "movl 20(%%rax),%%edi\n\t" | |
186 | "popq %%rax\n\t" | |
187 | "out %%al,$0xb2\n\t" | |
188 | "out %%al,$0x84\n\t" | |
189 | "xchgq %%rax,(%%rsp)\n\t" | |
190 | "movl %%ebx,4(%%rax)\n\t" | |
191 | "movl %%ecx,8(%%rax)\n\t" | |
192 | "movl %%edx,12(%%rax)\n\t" | |
193 | "movl %%esi,16(%%rax)\n\t" | |
194 | "movl %%edi,20(%%rax)\n\t" | |
195 | "popq %%rdx\n\t" | |
196 | "movl %%edx,0(%%rax)\n\t" | |
bc1f419c LT |
197 | "pushfq\n\t" |
198 | "popq %%rax\n\t" | |
fe04f22f | 199 | "andl $1,%%eax\n" |
12186f51 | 200 | : "=a"(rc) |
fe04f22f BS |
201 | : "a"(regs) |
202 | : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); | |
203 | #else | |
22d3243d | 204 | asm volatile("pushl %%eax\n\t" |
dec63ec3 DT |
205 | "movl 0(%%eax),%%edx\n\t" |
206 | "push %%edx\n\t" | |
207 | "movl 4(%%eax),%%ebx\n\t" | |
208 | "movl 8(%%eax),%%ecx\n\t" | |
209 | "movl 12(%%eax),%%edx\n\t" | |
210 | "movl 16(%%eax),%%esi\n\t" | |
211 | "movl 20(%%eax),%%edi\n\t" | |
212 | "popl %%eax\n\t" | |
213 | "out %%al,$0xb2\n\t" | |
214 | "out %%al,$0x84\n\t" | |
215 | "xchgl %%eax,(%%esp)\n\t" | |
216 | "movl %%ebx,4(%%eax)\n\t" | |
217 | "movl %%ecx,8(%%eax)\n\t" | |
218 | "movl %%edx,12(%%eax)\n\t" | |
219 | "movl %%esi,16(%%eax)\n\t" | |
220 | "movl %%edi,20(%%eax)\n\t" | |
221 | "popl %%edx\n\t" | |
222 | "movl %%edx,0(%%eax)\n\t" | |
223 | "lahf\n\t" | |
224 | "shrl $8,%%eax\n\t" | |
6b4e81db | 225 | "andl $1,%%eax\n" |
12186f51 | 226 | : "=a"(rc) |
dec63ec3 DT |
227 | : "a"(regs) |
228 | : "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory"); | |
fe04f22f | 229 | #endif |
8378b924 | 230 | if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax) |
f36fdb9f | 231 | rc = -EINVAL; |
dec63ec3 | 232 | |
9d58bec0 PR |
233 | #ifdef DEBUG |
234 | rettime = ktime_get(); | |
235 | delta = ktime_sub(rettime, calltime); | |
236 | duration = ktime_to_ns(delta) >> 10; | |
237 | pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x (took %7lu usecs)\n", eax, ebx, | |
238 | (rc ? 0xffff : regs->eax & 0xffff), duration); | |
239 | #endif | |
240 | ||
f36fdb9f | 241 | return rc; |
1da177e4 LT |
242 | } |
243 | ||
27046a3f JG |
244 | /* |
245 | * Call the System Management Mode BIOS. | |
246 | */ | |
247 | static int i8k_smm(struct smm_regs *regs) | |
248 | { | |
249 | int ret; | |
250 | ||
e104d530 | 251 | cpus_read_lock(); |
27046a3f | 252 | ret = smp_call_on_cpu(0, i8k_smm_func, regs, true); |
e104d530 | 253 | cpus_read_unlock(); |
27046a3f JG |
254 | |
255 | return ret; | |
256 | } | |
257 | ||
1da177e4 LT |
258 | /* |
259 | * Read the fan status. | |
260 | */ | |
ba04d73c | 261 | static int i8k_get_fan_status(const struct dell_smm_data *data, int fan) |
1da177e4 | 262 | { |
8378b924 | 263 | struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, }; |
1da177e4 | 264 | |
ba04d73c | 265 | if (data->disallow_fan_support) |
f480ea90 PR |
266 | return -EINVAL; |
267 | ||
dec63ec3 | 268 | regs.ebx = fan & 0xff; |
8378b924 | 269 | return i8k_smm(®s) ? : regs.eax & 0xff; |
1da177e4 LT |
270 | } |
271 | ||
272 | /* | |
273 | * Read the fan speed in RPM. | |
274 | */ | |
ba04d73c | 275 | static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan) |
1da177e4 | 276 | { |
8378b924 | 277 | struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, }; |
1da177e4 | 278 | |
ba04d73c | 279 | if (data->disallow_fan_support) |
f480ea90 PR |
280 | return -EINVAL; |
281 | ||
dec63ec3 | 282 | regs.ebx = fan & 0xff; |
ba04d73c | 283 | return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; |
1da177e4 LT |
284 | } |
285 | ||
f989e554 PR |
286 | /* |
287 | * Read the fan type. | |
288 | */ | |
ba04d73c | 289 | static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan) |
f989e554 PR |
290 | { |
291 | struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, }; | |
292 | ||
ba04d73c | 293 | if (data->disallow_fan_support || data->disallow_fan_type_call) |
2744d2fd PR |
294 | return -EINVAL; |
295 | ||
f989e554 PR |
296 | regs.ebx = fan & 0xff; |
297 | return i8k_smm(®s) ? : regs.eax & 0xff; | |
298 | } | |
299 | ||
ba04d73c | 300 | static int i8k_get_fan_type(struct dell_smm_data *data, int fan) |
5ce91714 PR |
301 | { |
302 | /* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */ | |
deeba244 AW |
303 | if (data->fan_type[fan] == INT_MIN) |
304 | data->fan_type[fan] = _i8k_get_fan_type(data, fan); | |
5ce91714 | 305 | |
deeba244 | 306 | return data->fan_type[fan]; |
5ce91714 PR |
307 | } |
308 | ||
8f21d8e9 PR |
309 | /* |
310 | * Read the fan nominal rpm for specific fan speed. | |
311 | */ | |
ba04d73c | 312 | static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed) |
8f21d8e9 PR |
313 | { |
314 | struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, }; | |
315 | ||
ba04d73c | 316 | if (data->disallow_fan_support) |
f480ea90 PR |
317 | return -EINVAL; |
318 | ||
8f21d8e9 | 319 | regs.ebx = (fan & 0xff) | (speed << 8); |
ba04d73c | 320 | return i8k_smm(®s) ? : (regs.eax & 0xffff) * data->i8k_fan_mult; |
8f21d8e9 PR |
321 | } |
322 | ||
afe45277 GM |
323 | /* |
324 | * Enable or disable automatic BIOS fan control support | |
325 | */ | |
ba04d73c | 326 | static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable) |
afe45277 GM |
327 | { |
328 | struct smm_regs regs = { }; | |
329 | ||
ba04d73c | 330 | if (data->disallow_fan_support) |
afe45277 GM |
331 | return -EINVAL; |
332 | ||
ba04d73c | 333 | regs.eax = enable ? data->auto_fan : data->manual_fan; |
afe45277 GM |
334 | return i8k_smm(®s); |
335 | } | |
336 | ||
1da177e4 LT |
337 | /* |
338 | * Set the fan speed (off, low, high). Returns the new fan status. | |
339 | */ | |
ba04d73c | 340 | static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed) |
1da177e4 | 341 | { |
8378b924 | 342 | struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, }; |
1da177e4 | 343 | |
ba04d73c | 344 | if (data->disallow_fan_support) |
f480ea90 PR |
345 | return -EINVAL; |
346 | ||
ba04d73c | 347 | speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed); |
dec63ec3 | 348 | regs.ebx = (fan & 0xff) | (speed << 8); |
1da177e4 | 349 | |
ba04d73c | 350 | return i8k_smm(®s) ? : i8k_get_fan_status(data, fan); |
1da177e4 LT |
351 | } |
352 | ||
deeba244 | 353 | static int __init i8k_get_temp_type(int sensor) |
5114b474 PR |
354 | { |
355 | struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, }; | |
356 | ||
357 | regs.ebx = sensor & 0xff; | |
358 | return i8k_smm(®s) ? : regs.eax & 0xff; | |
359 | } | |
360 | ||
1da177e4 LT |
361 | /* |
362 | * Read the cpu temperature. | |
363 | */ | |
564132d9 | 364 | static int _i8k_get_temp(int sensor) |
1da177e4 | 365 | { |
564132d9 GR |
366 | struct smm_regs regs = { |
367 | .eax = I8K_SMM_GET_TEMP, | |
368 | .ebx = sensor & 0xff, | |
369 | }; | |
1da177e4 | 370 | |
564132d9 GR |
371 | return i8k_smm(®s) ? : regs.eax & 0xff; |
372 | } | |
8378b924 | 373 | |
564132d9 GR |
374 | static int i8k_get_temp(int sensor) |
375 | { | |
376 | int temp = _i8k_get_temp(sensor); | |
1da177e4 | 377 | |
dec63ec3 DT |
378 | /* |
379 | * Sometimes the temperature sensor returns 0x99, which is out of range. | |
564132d9 | 380 | * In this case we retry (once) before returning an error. |
dec63ec3 DT |
381 | # 1003655137 00000058 00005a4b |
382 | # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees | |
383 | # 1003655139 00000054 00005c52 | |
384 | */ | |
564132d9 GR |
385 | if (temp == 0x99) { |
386 | msleep(100); | |
387 | temp = _i8k_get_temp(sensor); | |
dec63ec3 | 388 | } |
564132d9 GR |
389 | /* |
390 | * Return -ENODATA for all invalid temperatures. | |
391 | * | |
392 | * Known instances are the 0x99 value as seen above as well as | |
393 | * 0xc1 (193), which may be returned when trying to read the GPU | |
394 | * temperature if the system supports a GPU and it is currently | |
395 | * turned off. | |
396 | */ | |
723493ca | 397 | if (temp > I8K_MAX_TEMP) |
83d514d7 | 398 | return -ENODATA; |
1da177e4 | 399 | |
dec63ec3 | 400 | return temp; |
1da177e4 LT |
401 | } |
402 | ||
c9363cdf | 403 | static int __init i8k_get_dell_signature(int req_fn) |
1da177e4 | 404 | { |
7e0fa31d | 405 | struct smm_regs regs = { .eax = req_fn, }; |
dec63ec3 | 406 | int rc; |
1da177e4 | 407 | |
12186f51 GR |
408 | rc = i8k_smm(®s); |
409 | if (rc < 0) | |
dec63ec3 | 410 | return rc; |
1da177e4 | 411 | |
8378b924 | 412 | return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1; |
1da177e4 LT |
413 | } |
414 | ||
039ae585 PR |
415 | #if IS_ENABLED(CONFIG_I8K) |
416 | ||
417 | /* | |
418 | * Read the Fn key status. | |
419 | */ | |
420 | static int i8k_get_fn_status(void) | |
421 | { | |
422 | struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, }; | |
423 | int rc; | |
424 | ||
425 | rc = i8k_smm(®s); | |
426 | if (rc < 0) | |
427 | return rc; | |
428 | ||
429 | switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) { | |
430 | case I8K_FN_UP: | |
431 | return I8K_VOL_UP; | |
432 | case I8K_FN_DOWN: | |
433 | return I8K_VOL_DOWN; | |
434 | case I8K_FN_MUTE: | |
435 | return I8K_VOL_MUTE; | |
436 | default: | |
437 | return 0; | |
438 | } | |
439 | } | |
440 | ||
441 | /* | |
442 | * Read the power status. | |
443 | */ | |
444 | static int i8k_get_power_status(void) | |
445 | { | |
446 | struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, }; | |
447 | int rc; | |
448 | ||
449 | rc = i8k_smm(®s); | |
450 | if (rc < 0) | |
451 | return rc; | |
452 | ||
453 | return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY; | |
454 | } | |
455 | ||
456 | /* | |
457 | * Procfs interface | |
458 | */ | |
459 | ||
d79b6f4d | 460 | static int |
ba04d73c | 461 | i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg) |
1da177e4 | 462 | { |
e70c9d5e | 463 | int val = 0; |
dec63ec3 DT |
464 | int speed; |
465 | unsigned char buff[16]; | |
466 | int __user *argp = (int __user *)arg; | |
467 | ||
468 | if (!argp) | |
469 | return -EINVAL; | |
470 | ||
471 | switch (cmd) { | |
472 | case I8K_BIOS_VERSION: | |
ba04d73c AW |
473 | if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) || |
474 | !isdigit(data->bios_version[2])) | |
053ea640 PR |
475 | return -EINVAL; |
476 | ||
ba04d73c AW |
477 | val = (data->bios_version[0] << 16) | |
478 | (data->bios_version[1] << 8) | data->bios_version[2]; | |
dec63ec3 DT |
479 | break; |
480 | ||
481 | case I8K_MACHINE_ID: | |
7613663c PR |
482 | if (restricted && !capable(CAP_SYS_ADMIN)) |
483 | return -EPERM; | |
484 | ||
485 | memset(buff, 0, sizeof(buff)); | |
ba04d73c | 486 | strscpy(buff, data->bios_machineid, sizeof(buff)); |
dec63ec3 DT |
487 | break; |
488 | ||
489 | case I8K_FN_STATUS: | |
490 | val = i8k_get_fn_status(); | |
491 | break; | |
492 | ||
493 | case I8K_POWER_STATUS: | |
494 | val = i8k_get_power_status(); | |
495 | break; | |
496 | ||
497 | case I8K_GET_TEMP: | |
7e0fa31d | 498 | val = i8k_get_temp(0); |
dec63ec3 DT |
499 | break; |
500 | ||
501 | case I8K_GET_SPEED: | |
8378b924 | 502 | if (copy_from_user(&val, argp, sizeof(int))) |
dec63ec3 | 503 | return -EFAULT; |
8378b924 | 504 | |
ba04d73c | 505 | val = i8k_get_fan_speed(data, val); |
dec63ec3 | 506 | break; |
1da177e4 | 507 | |
dec63ec3 | 508 | case I8K_GET_FAN: |
8378b924 | 509 | if (copy_from_user(&val, argp, sizeof(int))) |
dec63ec3 | 510 | return -EFAULT; |
8378b924 | 511 | |
ba04d73c | 512 | val = i8k_get_fan_status(data, val); |
dec63ec3 | 513 | break; |
1da177e4 | 514 | |
dec63ec3 | 515 | case I8K_SET_FAN: |
8378b924 | 516 | if (restricted && !capable(CAP_SYS_ADMIN)) |
dec63ec3 | 517 | return -EPERM; |
8378b924 DT |
518 | |
519 | if (copy_from_user(&val, argp, sizeof(int))) | |
dec63ec3 | 520 | return -EFAULT; |
8378b924 DT |
521 | |
522 | if (copy_from_user(&speed, argp + 1, sizeof(int))) | |
dec63ec3 | 523 | return -EFAULT; |
8378b924 | 524 | |
ba04d73c | 525 | val = i8k_set_fan(data, val, speed); |
dec63ec3 | 526 | break; |
1da177e4 | 527 | |
dec63ec3 DT |
528 | default: |
529 | return -EINVAL; | |
1da177e4 | 530 | } |
1da177e4 | 531 | |
8378b924 | 532 | if (val < 0) |
dec63ec3 | 533 | return val; |
1da177e4 | 534 | |
dec63ec3 DT |
535 | switch (cmd) { |
536 | case I8K_BIOS_VERSION: | |
8378b924 | 537 | if (copy_to_user(argp, &val, 4)) |
dec63ec3 | 538 | return -EFAULT; |
8378b924 | 539 | |
dec63ec3 DT |
540 | break; |
541 | case I8K_MACHINE_ID: | |
8378b924 | 542 | if (copy_to_user(argp, buff, 16)) |
dec63ec3 | 543 | return -EFAULT; |
8378b924 | 544 | |
dec63ec3 DT |
545 | break; |
546 | default: | |
8378b924 | 547 | if (copy_to_user(argp, &val, sizeof(int))) |
dec63ec3 | 548 | return -EFAULT; |
8378b924 | 549 | |
dec63ec3 | 550 | break; |
1da177e4 | 551 | } |
1da177e4 | 552 | |
dec63ec3 | 553 | return 0; |
1da177e4 LT |
554 | } |
555 | ||
d79b6f4d FW |
556 | static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) |
557 | { | |
ba04d73c | 558 | struct dell_smm_data *data = PDE_DATA(file_inode(fp)); |
d79b6f4d FW |
559 | long ret; |
560 | ||
ba04d73c AW |
561 | mutex_lock(&data->i8k_mutex); |
562 | ret = i8k_ioctl_unlocked(fp, data, cmd, arg); | |
563 | mutex_unlock(&data->i8k_mutex); | |
d79b6f4d FW |
564 | |
565 | return ret; | |
566 | } | |
567 | ||
1da177e4 LT |
568 | /* |
569 | * Print the information for /proc/i8k. | |
570 | */ | |
352f8f8b | 571 | static int i8k_proc_show(struct seq_file *seq, void *offset) |
1da177e4 | 572 | { |
ba04d73c | 573 | struct dell_smm_data *data = seq->private; |
352f8f8b | 574 | int fn_key, cpu_temp, ac_power; |
dec63ec3 DT |
575 | int left_fan, right_fan, left_speed, right_speed; |
576 | ||
ba04d73c AW |
577 | cpu_temp = i8k_get_temp(0); /* 11100 µs */ |
578 | left_fan = i8k_get_fan_status(data, I8K_FAN_LEFT); /* 580 µs */ | |
579 | right_fan = i8k_get_fan_status(data, I8K_FAN_RIGHT); /* 580 µs */ | |
580 | left_speed = i8k_get_fan_speed(data, I8K_FAN_LEFT); /* 580 µs */ | |
581 | right_speed = i8k_get_fan_speed(data, I8K_FAN_RIGHT); /* 580 µs */ | |
582 | fn_key = i8k_get_fn_status(); /* 750 µs */ | |
8378b924 | 583 | if (power_status) |
ba04d73c | 584 | ac_power = i8k_get_power_status(); /* 14700 µs */ |
8378b924 | 585 | else |
dec63ec3 | 586 | ac_power = -1; |
dec63ec3 DT |
587 | |
588 | /* | |
589 | * Info: | |
590 | * | |
591 | * 1) Format version (this will change if format changes) | |
592 | * 2) BIOS version | |
593 | * 3) BIOS machine ID | |
594 | * 4) Cpu temperature | |
595 | * 5) Left fan status | |
596 | * 6) Right fan status | |
597 | * 7) Left fan speed | |
598 | * 8) Right fan speed | |
599 | * 9) AC power | |
600 | * 10) Fn Key status | |
601 | */ | |
3a267d3b JP |
602 | seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n", |
603 | I8K_PROC_FMT, | |
ba04d73c AW |
604 | data->bios_version, |
605 | (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid, | |
3a267d3b JP |
606 | cpu_temp, |
607 | left_fan, right_fan, left_speed, right_speed, | |
608 | ac_power, fn_key); | |
609 | ||
610 | return 0; | |
1da177e4 LT |
611 | } |
612 | ||
352f8f8b | 613 | static int i8k_open_fs(struct inode *inode, struct file *file) |
1da177e4 | 614 | { |
ba04d73c | 615 | return single_open(file, i8k_proc_show, PDE_DATA(inode)); |
1da177e4 LT |
616 | } |
617 | ||
97a32539 AD |
618 | static const struct proc_ops i8k_proc_ops = { |
619 | .proc_open = i8k_open_fs, | |
620 | .proc_read = seq_read, | |
621 | .proc_lseek = seq_lseek, | |
622 | .proc_release = single_release, | |
623 | .proc_ioctl = i8k_ioctl, | |
039ae585 PR |
624 | }; |
625 | ||
a2cb66b4 | 626 | static void i8k_exit_procfs(void *param) |
039ae585 | 627 | { |
a2cb66b4 | 628 | remove_proc_entry("i8k", NULL); |
039ae585 PR |
629 | } |
630 | ||
a2cb66b4 | 631 | static void __init i8k_init_procfs(struct device *dev) |
039ae585 | 632 | { |
ba04d73c AW |
633 | struct dell_smm_data *data = dev_get_drvdata(dev); |
634 | ||
a2cb66b4 | 635 | /* Register the proc entry */ |
ba04d73c | 636 | proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data); |
a2cb66b4 AW |
637 | |
638 | devm_add_action_or_reset(dev, i8k_exit_procfs, NULL); | |
039ae585 PR |
639 | } |
640 | ||
641 | #else | |
642 | ||
a2cb66b4 | 643 | static void __init i8k_init_procfs(struct device *dev) |
039ae585 PR |
644 | { |
645 | } | |
646 | ||
647 | #endif | |
949a9d70 JD |
648 | |
649 | /* | |
650 | * Hwmon interface | |
651 | */ | |
652 | ||
deeba244 AW |
653 | static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr, |
654 | int channel) | |
5114b474 | 655 | { |
deeba244 AW |
656 | const struct dell_smm_data *data = drvdata; |
657 | ||
658 | switch (type) { | |
659 | case hwmon_temp: | |
660 | switch (attr) { | |
661 | case hwmon_temp_input: | |
662 | case hwmon_temp_label: | |
663 | if (data->temp_type[channel] >= 0) | |
664 | return 0444; | |
665 | ||
666 | break; | |
667 | default: | |
668 | break; | |
669 | } | |
670 | break; | |
671 | case hwmon_fan: | |
672 | if (data->disallow_fan_support) | |
673 | break; | |
674 | ||
675 | switch (attr) { | |
676 | case hwmon_fan_input: | |
677 | if (data->fan[channel]) | |
678 | return 0444; | |
679 | ||
680 | break; | |
681 | case hwmon_fan_label: | |
682 | if (data->fan[channel] && !data->disallow_fan_type_call) | |
683 | return 0444; | |
684 | ||
685 | break; | |
686 | default: | |
687 | break; | |
688 | } | |
689 | break; | |
690 | case hwmon_pwm: | |
691 | if (data->disallow_fan_support) | |
692 | break; | |
693 | ||
694 | switch (attr) { | |
695 | case hwmon_pwm_input: | |
696 | if (data->fan[channel]) | |
697 | return 0644; | |
698 | ||
699 | break; | |
700 | case hwmon_pwm_enable: | |
701 | if (data->auto_fan) | |
702 | /* | |
703 | * There is no command for retrieve the current status | |
704 | * from BIOS, and userspace/firmware itself can change | |
705 | * it. | |
706 | * Thus we can only provide write-only access for now. | |
707 | */ | |
708 | return 0200; | |
709 | ||
710 | break; | |
711 | default: | |
712 | break; | |
713 | } | |
714 | break; | |
715 | default: | |
716 | break; | |
717 | } | |
5114b474 | 718 | |
deeba244 | 719 | return 0; |
5114b474 PR |
720 | } |
721 | ||
deeba244 AW |
722 | static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, |
723 | long *val) | |
949a9d70 | 724 | { |
deeba244 AW |
725 | struct dell_smm_data *data = dev_get_drvdata(dev); |
726 | int ret; | |
727 | ||
728 | switch (type) { | |
729 | case hwmon_temp: | |
730 | switch (attr) { | |
731 | case hwmon_temp_input: | |
732 | ret = i8k_get_temp(channel); | |
733 | if (ret < 0) | |
734 | return ret; | |
735 | ||
736 | *val = ret * 1000; | |
737 | ||
738 | return 0; | |
739 | default: | |
740 | break; | |
741 | } | |
742 | break; | |
743 | case hwmon_fan: | |
744 | switch (attr) { | |
745 | case hwmon_fan_input: | |
746 | ret = i8k_get_fan_speed(data, channel); | |
747 | if (ret < 0) | |
748 | return ret; | |
749 | ||
750 | *val = ret; | |
751 | ||
752 | return 0; | |
753 | default: | |
754 | break; | |
755 | } | |
756 | break; | |
757 | case hwmon_pwm: | |
758 | switch (attr) { | |
759 | case hwmon_pwm_input: | |
760 | ret = i8k_get_fan_status(data, channel); | |
761 | if (ret < 0) | |
762 | return ret; | |
763 | ||
764 | *val = clamp_val(ret * data->i8k_pwm_mult, 0, 255); | |
765 | ||
766 | return 0; | |
767 | default: | |
768 | break; | |
769 | } | |
770 | break; | |
771 | default: | |
772 | break; | |
773 | } | |
949a9d70 | 774 | |
deeba244 | 775 | return -EOPNOTSUPP; |
949a9d70 JD |
776 | } |
777 | ||
deeba244 | 778 | static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel) |
f989e554 | 779 | { |
f989e554 | 780 | bool dock = false; |
deeba244 | 781 | int type = i8k_get_fan_type(data, channel); |
f989e554 | 782 | |
f989e554 | 783 | if (type < 0) |
deeba244 | 784 | return ERR_PTR(type); |
f989e554 PR |
785 | |
786 | if (type & 0x10) { | |
787 | dock = true; | |
788 | type &= 0x0F; | |
789 | } | |
790 | ||
deeba244 AW |
791 | if (type >= ARRAY_SIZE(fan_labels)) |
792 | type = ARRAY_SIZE(fan_labels) - 1; | |
f989e554 | 793 | |
deeba244 | 794 | return dock ? docking_labels[type] : fan_labels[type]; |
f989e554 PR |
795 | } |
796 | ||
deeba244 AW |
797 | static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr, |
798 | int channel, const char **str) | |
949a9d70 | 799 | { |
ba04d73c | 800 | struct dell_smm_data *data = dev_get_drvdata(dev); |
949a9d70 | 801 | |
deeba244 AW |
802 | switch (type) { |
803 | case hwmon_temp: | |
804 | switch (attr) { | |
805 | case hwmon_temp_label: | |
806 | *str = temp_labels[data->temp_type[channel]]; | |
807 | return 0; | |
808 | default: | |
809 | break; | |
810 | } | |
811 | break; | |
812 | case hwmon_fan: | |
813 | switch (attr) { | |
814 | case hwmon_fan_label: | |
815 | *str = dell_smm_fan_label(data, channel); | |
816 | return PTR_ERR_OR_ZERO(*str); | |
817 | default: | |
818 | break; | |
819 | } | |
820 | break; | |
821 | default: | |
822 | break; | |
823 | } | |
e7f5f275 | 824 | |
deeba244 | 825 | return -EOPNOTSUPP; |
e7f5f275 GR |
826 | } |
827 | ||
deeba244 AW |
828 | static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel, |
829 | long val) | |
e7f5f275 | 830 | { |
ba04d73c | 831 | struct dell_smm_data *data = dev_get_drvdata(dev); |
deeba244 AW |
832 | unsigned long pwm; |
833 | bool enable; | |
e7f5f275 GR |
834 | int err; |
835 | ||
deeba244 AW |
836 | switch (type) { |
837 | case hwmon_pwm: | |
838 | switch (attr) { | |
839 | case hwmon_pwm_input: | |
840 | pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0, | |
841 | data->i8k_fan_max); | |
842 | ||
843 | mutex_lock(&data->i8k_mutex); | |
844 | err = i8k_set_fan(data, channel, pwm); | |
845 | mutex_unlock(&data->i8k_mutex); | |
846 | ||
847 | if (err < 0) | |
848 | return err; | |
849 | ||
850 | return 0; | |
851 | case hwmon_pwm_enable: | |
852 | if (!val) | |
853 | return -EINVAL; | |
854 | ||
855 | if (val == 1) | |
856 | enable = false; | |
857 | else | |
858 | enable = true; | |
859 | ||
860 | mutex_lock(&data->i8k_mutex); | |
861 | err = i8k_enable_fan_auto_mode(data, enable); | |
862 | mutex_unlock(&data->i8k_mutex); | |
863 | ||
864 | if (err < 0) | |
865 | return err; | |
866 | ||
867 | return 0; | |
868 | default: | |
869 | break; | |
870 | } | |
871 | break; | |
872 | default: | |
873 | break; | |
874 | } | |
e7f5f275 | 875 | |
deeba244 | 876 | return -EOPNOTSUPP; |
e7f5f275 GR |
877 | } |
878 | ||
deeba244 AW |
879 | static const struct hwmon_ops dell_smm_ops = { |
880 | .is_visible = dell_smm_is_visible, | |
881 | .read = dell_smm_read, | |
882 | .read_string = dell_smm_read_string, | |
883 | .write = dell_smm_write, | |
884 | }; | |
afe45277 | 885 | |
deeba244 AW |
886 | static const struct hwmon_channel_info *dell_smm_info[] = { |
887 | HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ), | |
888 | HWMON_CHANNEL_INFO(temp, | |
889 | HWMON_T_INPUT | HWMON_T_LABEL, | |
890 | HWMON_T_INPUT | HWMON_T_LABEL, | |
891 | HWMON_T_INPUT | HWMON_T_LABEL, | |
892 | HWMON_T_INPUT | HWMON_T_LABEL, | |
893 | HWMON_T_INPUT | HWMON_T_LABEL, | |
894 | HWMON_T_INPUT | HWMON_T_LABEL, | |
895 | HWMON_T_INPUT | HWMON_T_LABEL, | |
896 | HWMON_T_INPUT | HWMON_T_LABEL, | |
897 | HWMON_T_INPUT | HWMON_T_LABEL, | |
898 | HWMON_T_INPUT | HWMON_T_LABEL | |
899 | ), | |
900 | HWMON_CHANNEL_INFO(fan, | |
901 | HWMON_F_INPUT | HWMON_F_LABEL, | |
902 | HWMON_F_INPUT | HWMON_F_LABEL, | |
903 | HWMON_F_INPUT | HWMON_F_LABEL | |
904 | ), | |
905 | HWMON_CHANNEL_INFO(pwm, | |
906 | HWMON_PWM_INPUT | HWMON_PWM_ENABLE, | |
907 | HWMON_PWM_INPUT, | |
908 | HWMON_PWM_INPUT | |
909 | ), | |
82ba1d3f GR |
910 | NULL |
911 | }; | |
949a9d70 | 912 | |
deeba244 AW |
913 | static const struct hwmon_chip_info dell_smm_chip_info = { |
914 | .ops = &dell_smm_ops, | |
915 | .info = dell_smm_info, | |
916 | }; | |
917 | ||
918 | static int __init dell_smm_init_hwmon(struct device *dev) | |
949a9d70 | 919 | { |
ba04d73c | 920 | struct dell_smm_data *data = dev_get_drvdata(dev); |
deeba244 AW |
921 | struct device *dell_smm_hwmon_dev; |
922 | int i, err; | |
ba04d73c | 923 | |
deeba244 AW |
924 | for (i = 0; i < DELL_SMM_NO_TEMP; i++) { |
925 | data->temp_type[i] = i8k_get_temp_type(i); | |
926 | if (data->temp_type[i] < 0) | |
927 | continue; | |
afe45277 | 928 | |
deeba244 AW |
929 | if (data->temp_type[i] >= ARRAY_SIZE(temp_labels)) |
930 | data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1; | |
931 | } | |
949a9d70 | 932 | |
deeba244 AW |
933 | for (i = 0; i < DELL_SMM_NO_FANS; i++) { |
934 | data->fan_type[i] = INT_MIN; | |
935 | err = i8k_get_fan_status(data, i); | |
936 | if (err < 0) | |
937 | err = i8k_get_fan_type(data, i); | |
938 | if (err >= 0) | |
939 | data->fan[i] = true; | |
940 | } | |
82ba1d3f | 941 | |
deeba244 AW |
942 | dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data, |
943 | &dell_smm_chip_info, NULL); | |
949a9d70 | 944 | |
deeba244 | 945 | return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev); |
949a9d70 JD |
946 | } |
947 | ||
81474fc2 | 948 | struct i8k_config_data { |
7f69fb03 PR |
949 | uint fan_mult; |
950 | uint fan_max; | |
81474fc2 GR |
951 | }; |
952 | ||
953 | enum i8k_configs { | |
7b883446 GR |
954 | DELL_LATITUDE_D520, |
955 | DELL_PRECISION_490, | |
81474fc2 | 956 | DELL_STUDIO, |
34ae40f6 | 957 | DELL_XPS, |
81474fc2 GR |
958 | }; |
959 | ||
960 | static const struct i8k_config_data i8k_config_data[] = { | |
7b883446 GR |
961 | [DELL_LATITUDE_D520] = { |
962 | .fan_mult = 1, | |
963 | .fan_max = I8K_FAN_TURBO, | |
964 | }, | |
965 | [DELL_PRECISION_490] = { | |
966 | .fan_mult = 1, | |
967 | .fan_max = I8K_FAN_TURBO, | |
968 | }, | |
81474fc2 GR |
969 | [DELL_STUDIO] = { |
970 | .fan_mult = 1, | |
971 | .fan_max = I8K_FAN_HIGH, | |
972 | }, | |
34ae40f6 | 973 | [DELL_XPS] = { |
81474fc2 GR |
974 | .fan_mult = 1, |
975 | .fan_max = I8K_FAN_HIGH, | |
976 | }, | |
977 | }; | |
978 | ||
6faadbbb | 979 | static const struct dmi_system_id i8k_dmi_table[] __initconst = { |
e70c9d5e DT |
980 | { |
981 | .ident = "Dell Inspiron", | |
982 | .matches = { | |
983 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), | |
984 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), | |
985 | }, | |
986 | }, | |
987 | { | |
988 | .ident = "Dell Latitude", | |
989 | .matches = { | |
990 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"), | |
991 | DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), | |
992 | }, | |
993 | }, | |
7e0fa31d DT |
994 | { |
995 | .ident = "Dell Inspiron 2", | |
996 | .matches = { | |
997 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
998 | DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"), | |
999 | }, | |
1000 | }, | |
7b883446 GR |
1001 | { |
1002 | .ident = "Dell Latitude D520", | |
1003 | .matches = { | |
1004 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1005 | DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"), | |
1006 | }, | |
1007 | .driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520], | |
1008 | }, | |
7e0fa31d DT |
1009 | { |
1010 | .ident = "Dell Latitude 2", | |
1011 | .matches = { | |
1012 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1013 | DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"), | |
1014 | }, | |
1015 | }, | |
a9000d03 NW |
1016 | { /* UK Inspiron 6400 */ |
1017 | .ident = "Dell Inspiron 3", | |
1018 | .matches = { | |
1019 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1020 | DMI_MATCH(DMI_PRODUCT_NAME, "MM061"), | |
1021 | }, | |
1022 | }, | |
48103c52 FS |
1023 | { |
1024 | .ident = "Dell Inspiron 3", | |
1025 | .matches = { | |
1026 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1027 | DMI_MATCH(DMI_PRODUCT_NAME, "MP061"), | |
1028 | }, | |
1029 | }, | |
7b883446 GR |
1030 | { |
1031 | .ident = "Dell Precision 490", | |
1032 | .matches = { | |
1033 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1034 | DMI_MATCH(DMI_PRODUCT_NAME, | |
1035 | "Precision WorkStation 490"), | |
1036 | }, | |
1037 | .driver_data = (void *)&i8k_config_data[DELL_PRECISION_490], | |
1038 | }, | |
7ab21a86 AS |
1039 | { |
1040 | .ident = "Dell Precision", | |
1041 | .matches = { | |
1042 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1043 | DMI_MATCH(DMI_PRODUCT_NAME, "Precision"), | |
1044 | }, | |
1045 | }, | |
bef2a508 FH |
1046 | { |
1047 | .ident = "Dell Vostro", | |
1048 | .matches = { | |
1049 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1050 | DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"), | |
1051 | }, | |
1052 | }, | |
ff457bde GR |
1053 | { |
1054 | .ident = "Dell Studio", | |
1055 | .matches = { | |
1056 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1057 | DMI_MATCH(DMI_PRODUCT_NAME, "Studio"), | |
1058 | }, | |
81474fc2 | 1059 | .driver_data = (void *)&i8k_config_data[DELL_STUDIO], |
ff457bde | 1060 | }, |
919a0304 GR |
1061 | { |
1062 | .ident = "Dell XPS M140", | |
1063 | .matches = { | |
1064 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1065 | DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"), | |
1066 | }, | |
34ae40f6 | 1067 | .driver_data = (void *)&i8k_config_data[DELL_XPS], |
919a0304 | 1068 | }, |
a4811b6c | 1069 | { |
b8a13e5e | 1070 | .ident = "Dell XPS", |
162372b0 MS |
1071 | .matches = { |
1072 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
b8a13e5e | 1073 | DMI_MATCH(DMI_PRODUCT_NAME, "XPS"), |
162372b0 MS |
1074 | }, |
1075 | }, | |
12186f51 | 1076 | { } |
e70c9d5e | 1077 | }; |
1da177e4 | 1078 | |
148b1fda PR |
1079 | MODULE_DEVICE_TABLE(dmi, i8k_dmi_table); |
1080 | ||
2744d2fd PR |
1081 | /* |
1082 | * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed | |
1083 | * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist | |
1084 | * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call. | |
1085 | * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121 | |
1086 | */ | |
6faadbbb | 1087 | static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = { |
6220f4eb | 1088 | { |
6220f4eb TL |
1089 | .ident = "Dell Studio XPS 8000", |
1090 | .matches = { | |
1091 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1092 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"), | |
1093 | }, | |
1094 | }, | |
a4b45b25 | 1095 | { |
a4b45b25 PR |
1096 | .ident = "Dell Studio XPS 8100", |
1097 | .matches = { | |
1098 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1099 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"), | |
1100 | }, | |
1101 | }, | |
2744d2fd PR |
1102 | { |
1103 | .ident = "Dell Inspiron 580", | |
1104 | .matches = { | |
1105 | DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1106 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "), | |
1107 | }, | |
1108 | }, | |
a4b45b25 PR |
1109 | { } |
1110 | }; | |
1111 | ||
f480ea90 PR |
1112 | /* |
1113 | * On some machines all fan related SMM functions implemented by Dell BIOS | |
1114 | * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan | |
1115 | * support for affected blacklisted Dell machines stay disabled. | |
1116 | * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751 | |
1117 | */ | |
1118 | static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = { | |
1119 | { | |
1120 | .ident = "Dell Inspiron 7720", | |
1121 | .matches = { | |
1122 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1123 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"), | |
1124 | }, | |
1125 | }, | |
6fbc4232 ON |
1126 | { |
1127 | .ident = "Dell Vostro 3360", | |
1128 | .matches = { | |
1129 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1130 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"), | |
1131 | }, | |
1132 | }, | |
536e0019 HE |
1133 | { |
1134 | .ident = "Dell XPS13 9333", | |
1135 | .matches = { | |
1136 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1137 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"), | |
1138 | }, | |
1139 | }, | |
4008bc7d TH |
1140 | { |
1141 | .ident = "Dell XPS 15 L502X", | |
1142 | .matches = { | |
1143 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1144 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"), | |
1145 | }, | |
1146 | }, | |
f480ea90 PR |
1147 | { } |
1148 | }; | |
1149 | ||
afe45277 GM |
1150 | struct i8k_fan_control_data { |
1151 | unsigned int manual_fan; | |
1152 | unsigned int auto_fan; | |
1153 | }; | |
1154 | ||
1155 | enum i8k_fan_controls { | |
1156 | I8K_FAN_34A3_35A3, | |
1157 | }; | |
1158 | ||
1159 | static const struct i8k_fan_control_data i8k_fan_control_data[] = { | |
1160 | [I8K_FAN_34A3_35A3] = { | |
1161 | .manual_fan = 0x34a3, | |
1162 | .auto_fan = 0x35a3, | |
1163 | }, | |
1164 | }; | |
1165 | ||
1166 | static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = { | |
0ca8bb2c JL |
1167 | { |
1168 | .ident = "Dell Latitude 5480", | |
1169 | .matches = { | |
1170 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1171 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"), | |
1172 | }, | |
1173 | .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], | |
1174 | }, | |
afe45277 GM |
1175 | { |
1176 | .ident = "Dell Latitude E6440", | |
1177 | .matches = { | |
1178 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1179 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"), | |
1180 | }, | |
1181 | .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], | |
1182 | }, | |
807b8c29 SO |
1183 | { |
1184 | .ident = "Dell Latitude E7440", | |
1185 | .matches = { | |
1186 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1187 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E7440"), | |
1188 | }, | |
1189 | .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], | |
1190 | }, | |
95d88d05 CALP |
1191 | { |
1192 | .ident = "Dell Precision 5530", | |
1193 | .matches = { | |
1194 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1195 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"), | |
1196 | }, | |
1197 | .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], | |
1198 | }, | |
1199 | { | |
1200 | .ident = "Dell Precision 7510", | |
1201 | .matches = { | |
1202 | DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), | |
1203 | DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"), | |
1204 | }, | |
1205 | .driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3], | |
1206 | }, | |
afe45277 GM |
1207 | { } |
1208 | }; | |
1209 | ||
1492fa21 | 1210 | static int __init dell_smm_probe(struct platform_device *pdev) |
1da177e4 | 1211 | { |
ba04d73c | 1212 | struct dell_smm_data *data; |
afe45277 | 1213 | const struct dmi_system_id *id, *fan_control; |
8f21d8e9 | 1214 | int fan, ret; |
dec63ec3 | 1215 | |
ba04d73c AW |
1216 | data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL); |
1217 | if (!data) | |
1218 | return -ENOMEM; | |
1219 | ||
1220 | mutex_init(&data->i8k_mutex); | |
1221 | data->i8k_fan_mult = I8K_FAN_MULT; | |
1222 | data->i8k_fan_max = I8K_FAN_HIGH; | |
ba04d73c AW |
1223 | platform_set_drvdata(pdev, data); |
1224 | ||
f480ea90 | 1225 | if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) { |
ba04d73c | 1226 | dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n"); |
f480ea90 | 1227 | if (!force) |
ba04d73c | 1228 | data->disallow_fan_support = true; |
f480ea90 PR |
1229 | } |
1230 | ||
836ad112 | 1231 | if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) { |
ba04d73c | 1232 | dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n"); |
836ad112 | 1233 | if (!force) |
ba04d73c | 1234 | data->disallow_fan_type_call = true; |
836ad112 | 1235 | } |
2744d2fd | 1236 | |
ba04d73c AW |
1237 | strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION), |
1238 | sizeof(data->bios_version)); | |
1239 | strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL), | |
1240 | sizeof(data->bios_machineid)); | |
e70c9d5e | 1241 | |
8f21d8e9 PR |
1242 | /* |
1243 | * Set fan multiplier and maximal fan speed from dmi config | |
1244 | * Values specified in module parameters override values from dmi | |
1245 | */ | |
26d09382 | 1246 | id = dmi_first_match(i8k_dmi_table); |
81474fc2 GR |
1247 | if (id && id->driver_data) { |
1248 | const struct i8k_config_data *conf = id->driver_data; | |
1492fa21 | 1249 | |
8f21d8e9 PR |
1250 | if (!fan_mult && conf->fan_mult) |
1251 | fan_mult = conf->fan_mult; | |
ba04d73c | 1252 | |
8f21d8e9 PR |
1253 | if (!fan_max && conf->fan_max) |
1254 | fan_max = conf->fan_max; | |
81474fc2 | 1255 | } |
8f21d8e9 | 1256 | |
ba04d73c AW |
1257 | data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH; /* Must not be 0 */ |
1258 | data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max); | |
26d09382 | 1259 | |
afe45277 GM |
1260 | fan_control = dmi_first_match(i8k_whitelist_fan_control); |
1261 | if (fan_control && fan_control->driver_data) { | |
ba04d73c | 1262 | const struct i8k_fan_control_data *control = fan_control->driver_data; |
afe45277 | 1263 | |
ba04d73c AW |
1264 | data->manual_fan = control->manual_fan; |
1265 | data->auto_fan = control->auto_fan; | |
1266 | dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n"); | |
afe45277 GM |
1267 | } |
1268 | ||
8f21d8e9 PR |
1269 | if (!fan_mult) { |
1270 | /* | |
1271 | * Autodetect fan multiplier based on nominal rpm | |
1272 | * If fan reports rpm value too high then set multiplier to 1 | |
1273 | */ | |
2757269a | 1274 | for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) { |
ba04d73c | 1275 | ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max); |
8f21d8e9 PR |
1276 | if (ret < 0) |
1277 | continue; | |
ba04d73c | 1278 | |
8f21d8e9 | 1279 | if (ret > I8K_FAN_MAX_RPM) |
ba04d73c | 1280 | data->i8k_fan_mult = 1; |
8f21d8e9 PR |
1281 | break; |
1282 | } | |
1283 | } else { | |
1284 | /* Fan multiplier was specified in module param or in dmi */ | |
ba04d73c | 1285 | data->i8k_fan_mult = fan_mult; |
8f21d8e9 PR |
1286 | } |
1287 | ||
1492fa21 AW |
1288 | ret = dell_smm_init_hwmon(&pdev->dev); |
1289 | if (ret) | |
1290 | return ret; | |
1291 | ||
a2cb66b4 | 1292 | i8k_init_procfs(&pdev->dev); |
1492fa21 AW |
1293 | |
1294 | return 0; | |
1295 | } | |
1296 | ||
1297 | static struct platform_driver dell_smm_driver = { | |
1298 | .driver = { | |
1299 | .name = KBUILD_MODNAME, | |
1300 | }, | |
1492fa21 AW |
1301 | }; |
1302 | ||
1303 | static struct platform_device *dell_smm_device; | |
1304 | ||
1305 | /* | |
1306 | * Probe for the presence of a supported laptop. | |
1307 | */ | |
8378b924 | 1308 | static int __init i8k_init(void) |
1da177e4 | 1309 | { |
1492fa21 AW |
1310 | /* |
1311 | * Get DMI information | |
1312 | */ | |
1313 | if (!dmi_check_system(i8k_dmi_table)) { | |
1314 | if (!ignore_dmi && !force) | |
1315 | return -ENODEV; | |
dec63ec3 | 1316 | |
1492fa21 AW |
1317 | pr_info("not running on a supported Dell system.\n"); |
1318 | pr_info("vendor=%s, model=%s, version=%s\n", | |
1319 | i8k_get_dmi_data(DMI_SYS_VENDOR), | |
1320 | i8k_get_dmi_data(DMI_PRODUCT_NAME), | |
1321 | i8k_get_dmi_data(DMI_BIOS_VERSION)); | |
1322 | } | |
dec63ec3 | 1323 | |
1492fa21 AW |
1324 | /* |
1325 | * Get SMM Dell signature | |
1326 | */ | |
1327 | if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) && | |
1328 | i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) { | |
1329 | pr_err("unable to get SMM Dell signature\n"); | |
1330 | if (!force) | |
1331 | return -ENODEV; | |
1332 | } | |
949a9d70 | 1333 | |
1492fa21 AW |
1334 | dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL, |
1335 | 0); | |
1336 | ||
1337 | return PTR_ERR_OR_ZERO(dell_smm_device); | |
1da177e4 LT |
1338 | } |
1339 | ||
8378b924 | 1340 | static void __exit i8k_exit(void) |
1da177e4 | 1341 | { |
1492fa21 AW |
1342 | platform_device_unregister(dell_smm_device); |
1343 | platform_driver_unregister(&dell_smm_driver); | |
1da177e4 | 1344 | } |
1da177e4 | 1345 | |
8378b924 DT |
1346 | module_init(i8k_init); |
1347 | module_exit(i8k_exit); |