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