xref: /linux/drivers/hwmon/dell-smm-hwmon.c (revision 6105870f794ded0306dbff4ab017063e4d0f631a)
13e0a4e85SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
2a5afba16SPali Rohár /*
3a5afba16SPali Rohár  * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
4a5afba16SPali Rohár  *
5a5afba16SPali Rohár  * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
6a5afba16SPali Rohár  *
7a5afba16SPali Rohár  * Hwmon integration:
8a5afba16SPali Rohár  * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
9a5afba16SPali Rohár  * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
10149ed3d4SPali Rohár  * Copyright (C) 2014, 2015  Pali Rohár <pali@kernel.org>
11a5afba16SPali Rohár  */
12a5afba16SPali Rohár 
13a5afba16SPali Rohár #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
14a5afba16SPali Rohár 
15*6105870fSArmin Wolf #include <linux/capability.h>
1627046a3fSJuergen Gross #include <linux/cpu.h>
17*6105870fSArmin Wolf #include <linux/ctype.h>
18a5afba16SPali Rohár #include <linux/delay.h>
19*6105870fSArmin Wolf #include <linux/dmi.h>
201492fa21SArmin Wolf #include <linux/err.h>
21*6105870fSArmin Wolf #include <linux/hwmon.h>
22a5afba16SPali Rohár #include <linux/init.h>
23*6105870fSArmin Wolf #include <linux/module.h>
24*6105870fSArmin Wolf #include <linux/mutex.h>
25*6105870fSArmin Wolf #include <linux/platform_device.h>
26a5afba16SPali Rohár #include <linux/proc_fs.h>
27a5afba16SPali Rohár #include <linux/seq_file.h>
2827046a3fSJuergen Gross #include <linux/smp.h>
29*6105870fSArmin Wolf #include <linux/types.h>
30*6105870fSArmin Wolf #include <linux/uaccess.h>
31a5afba16SPali Rohár 
32a5afba16SPali Rohár #include <linux/i8k.h>
33a5afba16SPali Rohár 
34a5afba16SPali Rohár #define I8K_SMM_FN_STATUS	0x0025
35a5afba16SPali Rohár #define I8K_SMM_POWER_STATUS	0x0069
36a5afba16SPali Rohár #define I8K_SMM_SET_FAN		0x01a3
37a5afba16SPali Rohár #define I8K_SMM_GET_FAN		0x00a3
38a5afba16SPali Rohár #define I8K_SMM_GET_SPEED	0x02a3
39a5afba16SPali Rohár #define I8K_SMM_GET_FAN_TYPE	0x03a3
40a5afba16SPali Rohár #define I8K_SMM_GET_NOM_SPEED	0x04a3
41a5afba16SPali Rohár #define I8K_SMM_GET_TEMP	0x10a3
42a5afba16SPali Rohár #define I8K_SMM_GET_TEMP_TYPE	0x11a3
43a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG1	0xfea3
44a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG2	0xffa3
45a5afba16SPali Rohár 
46a5afba16SPali Rohár #define I8K_FAN_MULT		30
47a5afba16SPali Rohár #define I8K_FAN_MAX_RPM		30000
48a5afba16SPali Rohár #define I8K_MAX_TEMP		127
49a5afba16SPali Rohár 
50a5afba16SPali Rohár #define I8K_FN_NONE		0x00
51a5afba16SPali Rohár #define I8K_FN_UP		0x01
52a5afba16SPali Rohár #define I8K_FN_DOWN		0x02
53a5afba16SPali Rohár #define I8K_FN_MUTE		0x04
54a5afba16SPali Rohár #define I8K_FN_MASK		0x07
55a5afba16SPali Rohár #define I8K_FN_SHIFT		8
56a5afba16SPali Rohár 
57a5afba16SPali Rohár #define I8K_POWER_AC		0x05
58a5afba16SPali Rohár #define I8K_POWER_BATTERY	0x01
59a5afba16SPali Rohár 
60deeba244SArmin Wolf #define DELL_SMM_NO_TEMP	10
61deeba244SArmin Wolf #define DELL_SMM_NO_FANS	3
62deeba244SArmin Wolf 
63ba04d73cSArmin Wolf struct dell_smm_data {
64ba04d73cSArmin Wolf 	struct mutex i8k_mutex; /* lock for sensors writes */
65ba04d73cSArmin Wolf 	char bios_version[4];
66ba04d73cSArmin Wolf 	char bios_machineid[16];
67ba04d73cSArmin Wolf 	uint i8k_fan_mult;
68ba04d73cSArmin Wolf 	uint i8k_pwm_mult;
69ba04d73cSArmin Wolf 	uint i8k_fan_max;
70ba04d73cSArmin Wolf 	bool disallow_fan_type_call;
71ba04d73cSArmin Wolf 	bool disallow_fan_support;
72ba04d73cSArmin Wolf 	unsigned int manual_fan;
73ba04d73cSArmin Wolf 	unsigned int auto_fan;
74deeba244SArmin Wolf 	int temp_type[DELL_SMM_NO_TEMP];
75deeba244SArmin Wolf 	bool fan[DELL_SMM_NO_FANS];
76deeba244SArmin Wolf 	int fan_type[DELL_SMM_NO_FANS];
77b1986c8eSArmin Wolf 	int *fan_nominal_speed[DELL_SMM_NO_FANS];
78ba04d73cSArmin Wolf };
79a5afba16SPali Rohár 
80a5afba16SPali Rohár MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
81149ed3d4SPali Rohár MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
82039ae585SPali Rohár MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
83a5afba16SPali Rohár MODULE_LICENSE("GPL");
84a5afba16SPali Rohár MODULE_ALIAS("i8k");
85a5afba16SPali Rohár 
86a5afba16SPali Rohár static bool force;
87a5afba16SPali Rohár module_param(force, bool, 0);
88a5afba16SPali Rohár MODULE_PARM_DESC(force, "Force loading without checking for supported models");
89a5afba16SPali Rohár 
90a5afba16SPali Rohár static bool ignore_dmi;
91a5afba16SPali Rohár module_param(ignore_dmi, bool, 0);
92a5afba16SPali Rohár MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
93a5afba16SPali Rohár 
94039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
957613663cSPali Rohár static bool restricted = true;
96a5afba16SPali Rohár module_param(restricted, bool, 0);
977613663cSPali Rohár MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)");
98a5afba16SPali Rohár 
99a5afba16SPali Rohár static bool power_status;
100a5afba16SPali Rohár module_param(power_status, bool, 0600);
1017613663cSPali Rohár MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)");
102039ae585SPali Rohár #endif
103a5afba16SPali Rohár 
104a5afba16SPali Rohár static uint fan_mult;
105a5afba16SPali Rohár module_param(fan_mult, uint, 0);
106a5afba16SPali Rohár MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
107a5afba16SPali Rohár 
108a5afba16SPali Rohár static uint fan_max;
109a5afba16SPali Rohár module_param(fan_max, uint, 0);
110a5afba16SPali Rohár MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
111a5afba16SPali Rohár 
112a5afba16SPali Rohár struct smm_regs {
113a5afba16SPali Rohár 	unsigned int eax;
114a5afba16SPali Rohár 	unsigned int ebx __packed;
115a5afba16SPali Rohár 	unsigned int ecx __packed;
116a5afba16SPali Rohár 	unsigned int edx __packed;
117a5afba16SPali Rohár 	unsigned int esi __packed;
118a5afba16SPali Rohár 	unsigned int edi __packed;
119a5afba16SPali Rohár };
120a5afba16SPali Rohár 
121deeba244SArmin Wolf static const char * const temp_labels[] = {
122deeba244SArmin Wolf 	"CPU",
123deeba244SArmin Wolf 	"GPU",
124deeba244SArmin Wolf 	"SODIMM",
125deeba244SArmin Wolf 	"Other",
126deeba244SArmin Wolf 	"Ambient",
127deeba244SArmin Wolf 	"Other",
128deeba244SArmin Wolf };
129deeba244SArmin Wolf 
130deeba244SArmin Wolf static const char * const fan_labels[] = {
131deeba244SArmin Wolf 	"Processor Fan",
132deeba244SArmin Wolf 	"Motherboard Fan",
133deeba244SArmin Wolf 	"Video Fan",
134deeba244SArmin Wolf 	"Power Supply Fan",
135deeba244SArmin Wolf 	"Chipset Fan",
136deeba244SArmin Wolf 	"Other Fan",
137deeba244SArmin Wolf };
138deeba244SArmin Wolf 
139deeba244SArmin Wolf static const char * const docking_labels[] = {
140deeba244SArmin Wolf 	"Docking Processor Fan",
141deeba244SArmin Wolf 	"Docking Motherboard Fan",
142deeba244SArmin Wolf 	"Docking Video Fan",
143deeba244SArmin Wolf 	"Docking Power Supply Fan",
144deeba244SArmin Wolf 	"Docking Chipset Fan",
145deeba244SArmin Wolf 	"Docking Other Fan",
146deeba244SArmin Wolf };
147deeba244SArmin Wolf 
148c9363cdfSArmin Wolf static inline const char __init *i8k_get_dmi_data(int field)
149a5afba16SPali Rohár {
150a5afba16SPali Rohár 	const char *dmi_data = dmi_get_system_info(field);
151a5afba16SPali Rohár 
152a5afba16SPali Rohár 	return dmi_data && *dmi_data ? dmi_data : "?";
153a5afba16SPali Rohár }
154a5afba16SPali Rohár 
155a5afba16SPali Rohár /*
156a5afba16SPali Rohár  * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
157a5afba16SPali Rohár  */
15827046a3fSJuergen Gross static int i8k_smm_func(void *par)
159a5afba16SPali Rohár {
1608713b4a4SArmin Wolf 	ktime_t calltime = ktime_get();
16127046a3fSJuergen Gross 	struct smm_regs *regs = par;
162a5afba16SPali Rohár 	int eax = regs->eax;
1639d58bec0SPali Rohár 	int ebx = regs->ebx;
1648713b4a4SArmin Wolf 	long long duration;
1658713b4a4SArmin Wolf 	int rc;
1669d58bec0SPali Rohár 
167a5afba16SPali Rohár 	/* SMM requires CPU 0 */
16827046a3fSJuergen Gross 	if (smp_processor_id() != 0)
16927046a3fSJuergen Gross 		return -EBUSY;
170a5afba16SPali Rohár 
171a5afba16SPali Rohár #if defined(CONFIG_X86_64)
172a5afba16SPali Rohár 	asm volatile("pushq %%rax\n\t"
173a5afba16SPali Rohár 		"movl 0(%%rax),%%edx\n\t"
174a5afba16SPali Rohár 		"pushq %%rdx\n\t"
175a5afba16SPali Rohár 		"movl 4(%%rax),%%ebx\n\t"
176a5afba16SPali Rohár 		"movl 8(%%rax),%%ecx\n\t"
177a5afba16SPali Rohár 		"movl 12(%%rax),%%edx\n\t"
178a5afba16SPali Rohár 		"movl 16(%%rax),%%esi\n\t"
179a5afba16SPali Rohár 		"movl 20(%%rax),%%edi\n\t"
180a5afba16SPali Rohár 		"popq %%rax\n\t"
181a5afba16SPali Rohár 		"out %%al,$0xb2\n\t"
182a5afba16SPali Rohár 		"out %%al,$0x84\n\t"
183a5afba16SPali Rohár 		"xchgq %%rax,(%%rsp)\n\t"
184a5afba16SPali Rohár 		"movl %%ebx,4(%%rax)\n\t"
185a5afba16SPali Rohár 		"movl %%ecx,8(%%rax)\n\t"
186a5afba16SPali Rohár 		"movl %%edx,12(%%rax)\n\t"
187a5afba16SPali Rohár 		"movl %%esi,16(%%rax)\n\t"
188a5afba16SPali Rohár 		"movl %%edi,20(%%rax)\n\t"
189a5afba16SPali Rohár 		"popq %%rdx\n\t"
190a5afba16SPali Rohár 		"movl %%edx,0(%%rax)\n\t"
191a5afba16SPali Rohár 		"pushfq\n\t"
192a5afba16SPali Rohár 		"popq %%rax\n\t"
193a5afba16SPali Rohár 		"andl $1,%%eax\n"
194a5afba16SPali Rohár 		: "=a"(rc)
195a5afba16SPali Rohár 		:    "a"(regs)
196a5afba16SPali Rohár 		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
197a5afba16SPali Rohár #else
198a5afba16SPali Rohár 	asm volatile("pushl %%eax\n\t"
199a5afba16SPali Rohár 	    "movl 0(%%eax),%%edx\n\t"
200a5afba16SPali Rohár 	    "push %%edx\n\t"
201a5afba16SPali Rohár 	    "movl 4(%%eax),%%ebx\n\t"
202a5afba16SPali Rohár 	    "movl 8(%%eax),%%ecx\n\t"
203a5afba16SPali Rohár 	    "movl 12(%%eax),%%edx\n\t"
204a5afba16SPali Rohár 	    "movl 16(%%eax),%%esi\n\t"
205a5afba16SPali Rohár 	    "movl 20(%%eax),%%edi\n\t"
206a5afba16SPali Rohár 	    "popl %%eax\n\t"
207a5afba16SPali Rohár 	    "out %%al,$0xb2\n\t"
208a5afba16SPali Rohár 	    "out %%al,$0x84\n\t"
209a5afba16SPali Rohár 	    "xchgl %%eax,(%%esp)\n\t"
210a5afba16SPali Rohár 	    "movl %%ebx,4(%%eax)\n\t"
211a5afba16SPali Rohár 	    "movl %%ecx,8(%%eax)\n\t"
212a5afba16SPali Rohár 	    "movl %%edx,12(%%eax)\n\t"
213a5afba16SPali Rohár 	    "movl %%esi,16(%%eax)\n\t"
214a5afba16SPali Rohár 	    "movl %%edi,20(%%eax)\n\t"
215a5afba16SPali Rohár 	    "popl %%edx\n\t"
216a5afba16SPali Rohár 	    "movl %%edx,0(%%eax)\n\t"
217a5afba16SPali Rohár 	    "lahf\n\t"
218a5afba16SPali Rohár 	    "shrl $8,%%eax\n\t"
219a5afba16SPali Rohár 	    "andl $1,%%eax\n"
220a5afba16SPali Rohár 	    : "=a"(rc)
221a5afba16SPali Rohár 	    :    "a"(regs)
222a5afba16SPali Rohár 	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
223a5afba16SPali Rohár #endif
224a5afba16SPali Rohár 	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
225a5afba16SPali Rohár 		rc = -EINVAL;
226a5afba16SPali Rohár 
2278713b4a4SArmin Wolf 	duration = ktime_us_delta(ktime_get(), calltime);
2288713b4a4SArmin Wolf 	pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x  (took %7lld usecs)\n", eax, ebx,
2299d58bec0SPali Rohár 		 (rc ? 0xffff : regs->eax & 0xffff), duration);
2309d58bec0SPali Rohár 
231a5afba16SPali Rohár 	return rc;
232a5afba16SPali Rohár }
233a5afba16SPali Rohár 
234a5afba16SPali Rohár /*
23527046a3fSJuergen Gross  * Call the System Management Mode BIOS.
23627046a3fSJuergen Gross  */
23727046a3fSJuergen Gross static int i8k_smm(struct smm_regs *regs)
23827046a3fSJuergen Gross {
23927046a3fSJuergen Gross 	int ret;
24027046a3fSJuergen Gross 
241e104d530SSebastian Andrzej Siewior 	cpus_read_lock();
24227046a3fSJuergen Gross 	ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
243e104d530SSebastian Andrzej Siewior 	cpus_read_unlock();
24427046a3fSJuergen Gross 
24527046a3fSJuergen Gross 	return ret;
24627046a3fSJuergen Gross }
24727046a3fSJuergen Gross 
24827046a3fSJuergen Gross /*
249a5afba16SPali Rohár  * Read the fan status.
250a5afba16SPali Rohár  */
251ba04d73cSArmin Wolf static int i8k_get_fan_status(const struct dell_smm_data *data, int fan)
252a5afba16SPali Rohár {
253a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
254a5afba16SPali Rohár 
255ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
256f480ea90SPali Rohár 		return -EINVAL;
257f480ea90SPali Rohár 
258a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
259a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
260a5afba16SPali Rohár }
261a5afba16SPali Rohár 
262a5afba16SPali Rohár /*
263a5afba16SPali Rohár  * Read the fan speed in RPM.
264a5afba16SPali Rohár  */
265ba04d73cSArmin Wolf static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan)
266a5afba16SPali Rohár {
267a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
268a5afba16SPali Rohár 
269ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
270f480ea90SPali Rohár 		return -EINVAL;
271f480ea90SPali Rohár 
272a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
273ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
274a5afba16SPali Rohár }
275a5afba16SPali Rohár 
276a5afba16SPali Rohár /*
277a5afba16SPali Rohár  * Read the fan type.
278a5afba16SPali Rohár  */
279ba04d73cSArmin Wolf static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan)
280a5afba16SPali Rohár {
281a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
282a5afba16SPali Rohár 
283ba04d73cSArmin Wolf 	if (data->disallow_fan_support || data->disallow_fan_type_call)
2842744d2fdSPali Rohár 		return -EINVAL;
2852744d2fdSPali Rohár 
286a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
287a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
288a5afba16SPali Rohár }
289a5afba16SPali Rohár 
290ba04d73cSArmin Wolf static int i8k_get_fan_type(struct dell_smm_data *data, int fan)
2915ce91714SPali Rohár {
2925ce91714SPali Rohár 	/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
293deeba244SArmin Wolf 	if (data->fan_type[fan] == INT_MIN)
294deeba244SArmin Wolf 		data->fan_type[fan] = _i8k_get_fan_type(data, fan);
2955ce91714SPali Rohár 
296deeba244SArmin Wolf 	return data->fan_type[fan];
2975ce91714SPali Rohár }
2985ce91714SPali Rohár 
299a5afba16SPali Rohár /*
300a5afba16SPali Rohár  * Read the fan nominal rpm for specific fan speed.
301a5afba16SPali Rohár  */
302782a99c1SArmin Wolf static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed)
303a5afba16SPali Rohár {
304a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
305a5afba16SPali Rohár 
306ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
307f480ea90SPali Rohár 		return -EINVAL;
308f480ea90SPali Rohár 
309a5afba16SPali Rohár 	regs.ebx = (fan & 0xff) | (speed << 8);
310ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
311a5afba16SPali Rohár }
312a5afba16SPali Rohár 
313a5afba16SPali Rohár /*
314afe45277SGiovanni Mascellani  * Enable or disable automatic BIOS fan control support
315afe45277SGiovanni Mascellani  */
316ba04d73cSArmin Wolf static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable)
317afe45277SGiovanni Mascellani {
318afe45277SGiovanni Mascellani 	struct smm_regs regs = { };
319afe45277SGiovanni Mascellani 
320ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
321afe45277SGiovanni Mascellani 		return -EINVAL;
322afe45277SGiovanni Mascellani 
323ba04d73cSArmin Wolf 	regs.eax = enable ? data->auto_fan : data->manual_fan;
324afe45277SGiovanni Mascellani 	return i8k_smm(&regs);
325afe45277SGiovanni Mascellani }
326afe45277SGiovanni Mascellani 
327afe45277SGiovanni Mascellani /*
328a5afba16SPali Rohár  * Set the fan speed (off, low, high). Returns the new fan status.
329a5afba16SPali Rohár  */
330ba04d73cSArmin Wolf static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed)
331a5afba16SPali Rohár {
332a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
333a5afba16SPali Rohár 
334ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
335f480ea90SPali Rohár 		return -EINVAL;
336f480ea90SPali Rohár 
337ba04d73cSArmin Wolf 	speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
338a5afba16SPali Rohár 	regs.ebx = (fan & 0xff) | (speed << 8);
339a5afba16SPali Rohár 
340ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : i8k_get_fan_status(data, fan);
341a5afba16SPali Rohár }
342a5afba16SPali Rohár 
343deeba244SArmin Wolf static int __init i8k_get_temp_type(int sensor)
344a5afba16SPali Rohár {
345a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
346a5afba16SPali Rohár 
347a5afba16SPali Rohár 	regs.ebx = sensor & 0xff;
348a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
349a5afba16SPali Rohár }
350a5afba16SPali Rohár 
351a5afba16SPali Rohár /*
352a5afba16SPali Rohár  * Read the cpu temperature.
353a5afba16SPali Rohár  */
354a5afba16SPali Rohár static int _i8k_get_temp(int sensor)
355a5afba16SPali Rohár {
356a5afba16SPali Rohár 	struct smm_regs regs = {
357a5afba16SPali Rohár 		.eax = I8K_SMM_GET_TEMP,
358a5afba16SPali Rohár 		.ebx = sensor & 0xff,
359a5afba16SPali Rohár 	};
360a5afba16SPali Rohár 
361a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
362a5afba16SPali Rohár }
363a5afba16SPali Rohár 
364a5afba16SPali Rohár static int i8k_get_temp(int sensor)
365a5afba16SPali Rohár {
366a5afba16SPali Rohár 	int temp = _i8k_get_temp(sensor);
367a5afba16SPali Rohár 
368a5afba16SPali Rohár 	/*
369a5afba16SPali Rohár 	 * Sometimes the temperature sensor returns 0x99, which is out of range.
370a5afba16SPali Rohár 	 * In this case we retry (once) before returning an error.
371a5afba16SPali Rohár 	 # 1003655137 00000058 00005a4b
372a5afba16SPali Rohár 	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
373a5afba16SPali Rohár 	 # 1003655139 00000054 00005c52
374a5afba16SPali Rohár 	 */
375a5afba16SPali Rohár 	if (temp == 0x99) {
376a5afba16SPali Rohár 		msleep(100);
377a5afba16SPali Rohár 		temp = _i8k_get_temp(sensor);
378a5afba16SPali Rohár 	}
379a5afba16SPali Rohár 	/*
380a5afba16SPali Rohár 	 * Return -ENODATA for all invalid temperatures.
381a5afba16SPali Rohár 	 *
382a5afba16SPali Rohár 	 * Known instances are the 0x99 value as seen above as well as
383a5afba16SPali Rohár 	 * 0xc1 (193), which may be returned when trying to read the GPU
384a5afba16SPali Rohár 	 * temperature if the system supports a GPU and it is currently
385a5afba16SPali Rohár 	 * turned off.
386a5afba16SPali Rohár 	 */
387a5afba16SPali Rohár 	if (temp > I8K_MAX_TEMP)
388a5afba16SPali Rohár 		return -ENODATA;
389a5afba16SPali Rohár 
390a5afba16SPali Rohár 	return temp;
391a5afba16SPali Rohár }
392a5afba16SPali Rohár 
393c9363cdfSArmin Wolf static int __init i8k_get_dell_signature(int req_fn)
394a5afba16SPali Rohár {
395a5afba16SPali Rohár 	struct smm_regs regs = { .eax = req_fn, };
396a5afba16SPali Rohár 	int rc;
397a5afba16SPali Rohár 
398a5afba16SPali Rohár 	rc = i8k_smm(&regs);
399a5afba16SPali Rohár 	if (rc < 0)
400a5afba16SPali Rohár 		return rc;
401a5afba16SPali Rohár 
402a5afba16SPali Rohár 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
403a5afba16SPali Rohár }
404a5afba16SPali Rohár 
405039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
406039ae585SPali Rohár 
407039ae585SPali Rohár /*
408039ae585SPali Rohár  * Read the Fn key status.
409039ae585SPali Rohár  */
410039ae585SPali Rohár static int i8k_get_fn_status(void)
411039ae585SPali Rohár {
412039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
413039ae585SPali Rohár 	int rc;
414039ae585SPali Rohár 
415039ae585SPali Rohár 	rc = i8k_smm(&regs);
416039ae585SPali Rohár 	if (rc < 0)
417039ae585SPali Rohár 		return rc;
418039ae585SPali Rohár 
419039ae585SPali Rohár 	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
420039ae585SPali Rohár 	case I8K_FN_UP:
421039ae585SPali Rohár 		return I8K_VOL_UP;
422039ae585SPali Rohár 	case I8K_FN_DOWN:
423039ae585SPali Rohár 		return I8K_VOL_DOWN;
424039ae585SPali Rohár 	case I8K_FN_MUTE:
425039ae585SPali Rohár 		return I8K_VOL_MUTE;
426039ae585SPali Rohár 	default:
427039ae585SPali Rohár 		return 0;
428039ae585SPali Rohár 	}
429039ae585SPali Rohár }
430039ae585SPali Rohár 
431039ae585SPali Rohár /*
432039ae585SPali Rohár  * Read the power status.
433039ae585SPali Rohár  */
434039ae585SPali Rohár static int i8k_get_power_status(void)
435039ae585SPali Rohár {
436039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
437039ae585SPali Rohár 	int rc;
438039ae585SPali Rohár 
439039ae585SPali Rohár 	rc = i8k_smm(&regs);
440039ae585SPali Rohár 	if (rc < 0)
441039ae585SPali Rohár 		return rc;
442039ae585SPali Rohár 
443039ae585SPali Rohár 	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
444039ae585SPali Rohár }
445039ae585SPali Rohár 
446039ae585SPali Rohár /*
447039ae585SPali Rohár  * Procfs interface
448039ae585SPali Rohár  */
449039ae585SPali Rohár 
450a5afba16SPali Rohár static int
451ba04d73cSArmin Wolf i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg)
452a5afba16SPali Rohár {
453a5afba16SPali Rohár 	int val = 0;
454a5afba16SPali Rohár 	int speed;
455a5afba16SPali Rohár 	unsigned char buff[16];
456a5afba16SPali Rohár 	int __user *argp = (int __user *)arg;
457a5afba16SPali Rohár 
458a5afba16SPali Rohár 	if (!argp)
459a5afba16SPali Rohár 		return -EINVAL;
460a5afba16SPali Rohár 
461a5afba16SPali Rohár 	switch (cmd) {
462a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
463ba04d73cSArmin Wolf 		if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) ||
464ba04d73cSArmin Wolf 		    !isdigit(data->bios_version[2]))
465053ea640SPali Rohár 			return -EINVAL;
466053ea640SPali Rohár 
467ba04d73cSArmin Wolf 		val = (data->bios_version[0] << 16) |
468ba04d73cSArmin Wolf 				(data->bios_version[1] << 8) | data->bios_version[2];
469a5afba16SPali Rohár 		break;
470a5afba16SPali Rohár 
471a5afba16SPali Rohár 	case I8K_MACHINE_ID:
4727613663cSPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
4737613663cSPali Rohár 			return -EPERM;
4747613663cSPali Rohár 
4757613663cSPali Rohár 		memset(buff, 0, sizeof(buff));
476ba04d73cSArmin Wolf 		strscpy(buff, data->bios_machineid, sizeof(buff));
477a5afba16SPali Rohár 		break;
478a5afba16SPali Rohár 
479a5afba16SPali Rohár 	case I8K_FN_STATUS:
480a5afba16SPali Rohár 		val = i8k_get_fn_status();
481a5afba16SPali Rohár 		break;
482a5afba16SPali Rohár 
483a5afba16SPali Rohár 	case I8K_POWER_STATUS:
484a5afba16SPali Rohár 		val = i8k_get_power_status();
485a5afba16SPali Rohár 		break;
486a5afba16SPali Rohár 
487a5afba16SPali Rohár 	case I8K_GET_TEMP:
488a5afba16SPali Rohár 		val = i8k_get_temp(0);
489a5afba16SPali Rohár 		break;
490a5afba16SPali Rohár 
491a5afba16SPali Rohár 	case I8K_GET_SPEED:
492a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
493a5afba16SPali Rohár 			return -EFAULT;
494a5afba16SPali Rohár 
495ba04d73cSArmin Wolf 		val = i8k_get_fan_speed(data, val);
496a5afba16SPali Rohár 		break;
497a5afba16SPali Rohár 
498a5afba16SPali Rohár 	case I8K_GET_FAN:
499a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
500a5afba16SPali Rohár 			return -EFAULT;
501a5afba16SPali Rohár 
502ba04d73cSArmin Wolf 		val = i8k_get_fan_status(data, val);
503a5afba16SPali Rohár 		break;
504a5afba16SPali Rohár 
505a5afba16SPali Rohár 	case I8K_SET_FAN:
506a5afba16SPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
507a5afba16SPali Rohár 			return -EPERM;
508a5afba16SPali Rohár 
509a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
510a5afba16SPali Rohár 			return -EFAULT;
511a5afba16SPali Rohár 
512a5afba16SPali Rohár 		if (copy_from_user(&speed, argp + 1, sizeof(int)))
513a5afba16SPali Rohár 			return -EFAULT;
514a5afba16SPali Rohár 
515ba04d73cSArmin Wolf 		val = i8k_set_fan(data, val, speed);
516a5afba16SPali Rohár 		break;
517a5afba16SPali Rohár 
518a5afba16SPali Rohár 	default:
519a5afba16SPali Rohár 		return -EINVAL;
520a5afba16SPali Rohár 	}
521a5afba16SPali Rohár 
522a5afba16SPali Rohár 	if (val < 0)
523a5afba16SPali Rohár 		return val;
524a5afba16SPali Rohár 
525a5afba16SPali Rohár 	switch (cmd) {
526a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
527a5afba16SPali Rohár 		if (copy_to_user(argp, &val, 4))
528a5afba16SPali Rohár 			return -EFAULT;
529a5afba16SPali Rohár 
530a5afba16SPali Rohár 		break;
531a5afba16SPali Rohár 	case I8K_MACHINE_ID:
532a5afba16SPali Rohár 		if (copy_to_user(argp, buff, 16))
533a5afba16SPali Rohár 			return -EFAULT;
534a5afba16SPali Rohár 
535a5afba16SPali Rohár 		break;
536a5afba16SPali Rohár 	default:
537a5afba16SPali Rohár 		if (copy_to_user(argp, &val, sizeof(int)))
538a5afba16SPali Rohár 			return -EFAULT;
539a5afba16SPali Rohár 
540a5afba16SPali Rohár 		break;
541a5afba16SPali Rohár 	}
542a5afba16SPali Rohár 
543a5afba16SPali Rohár 	return 0;
544a5afba16SPali Rohár }
545a5afba16SPali Rohár 
546a5afba16SPali Rohár static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
547a5afba16SPali Rohár {
548ba04d73cSArmin Wolf 	struct dell_smm_data *data = PDE_DATA(file_inode(fp));
549a5afba16SPali Rohár 	long ret;
550a5afba16SPali Rohár 
551ba04d73cSArmin Wolf 	mutex_lock(&data->i8k_mutex);
552ba04d73cSArmin Wolf 	ret = i8k_ioctl_unlocked(fp, data, cmd, arg);
553ba04d73cSArmin Wolf 	mutex_unlock(&data->i8k_mutex);
554a5afba16SPali Rohár 
555a5afba16SPali Rohár 	return ret;
556a5afba16SPali Rohár }
557a5afba16SPali Rohár 
558a5afba16SPali Rohár /*
559a5afba16SPali Rohár  * Print the information for /proc/i8k.
560a5afba16SPali Rohár  */
561a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset)
562a5afba16SPali Rohár {
563ba04d73cSArmin Wolf 	struct dell_smm_data *data = seq->private;
564a5afba16SPali Rohár 	int fn_key, cpu_temp, ac_power;
565a5afba16SPali Rohár 	int left_fan, right_fan, left_speed, right_speed;
566a5afba16SPali Rohár 
567a5afba16SPali Rohár 	cpu_temp	= i8k_get_temp(0);				/* 11100 µs */
568ba04d73cSArmin Wolf 	left_fan	= i8k_get_fan_status(data, I8K_FAN_LEFT);	/*   580 µs */
569ba04d73cSArmin Wolf 	right_fan	= i8k_get_fan_status(data, I8K_FAN_RIGHT);	/*   580 µs */
570ba04d73cSArmin Wolf 	left_speed	= i8k_get_fan_speed(data, I8K_FAN_LEFT);	/*   580 µs */
571ba04d73cSArmin Wolf 	right_speed	= i8k_get_fan_speed(data, I8K_FAN_RIGHT);	/*   580 µs */
572a5afba16SPali Rohár 	fn_key		= i8k_get_fn_status();				/*   750 µs */
573a5afba16SPali Rohár 	if (power_status)
574a5afba16SPali Rohár 		ac_power = i8k_get_power_status();			/* 14700 µs */
575a5afba16SPali Rohár 	else
576a5afba16SPali Rohár 		ac_power = -1;
577a5afba16SPali Rohár 
578a5afba16SPali Rohár 	/*
579a5afba16SPali Rohár 	 * Info:
580a5afba16SPali Rohár 	 *
581a5afba16SPali Rohár 	 * 1)  Format version (this will change if format changes)
582a5afba16SPali Rohár 	 * 2)  BIOS version
583a5afba16SPali Rohár 	 * 3)  BIOS machine ID
584a5afba16SPali Rohár 	 * 4)  Cpu temperature
585a5afba16SPali Rohár 	 * 5)  Left fan status
586a5afba16SPali Rohár 	 * 6)  Right fan status
587a5afba16SPali Rohár 	 * 7)  Left fan speed
588a5afba16SPali Rohár 	 * 8)  Right fan speed
589a5afba16SPali Rohár 	 * 9)  AC power
590a5afba16SPali Rohár 	 * 10) Fn Key status
591a5afba16SPali Rohár 	 */
592a5afba16SPali Rohár 	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
593a5afba16SPali Rohár 		   I8K_PROC_FMT,
594ba04d73cSArmin Wolf 		   data->bios_version,
595ba04d73cSArmin Wolf 		   (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid,
596a5afba16SPali Rohár 		   cpu_temp,
597a5afba16SPali Rohár 		   left_fan, right_fan, left_speed, right_speed,
598a5afba16SPali Rohár 		   ac_power, fn_key);
599a5afba16SPali Rohár 
600a5afba16SPali Rohár 	return 0;
601a5afba16SPali Rohár }
602a5afba16SPali Rohár 
603a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file)
604a5afba16SPali Rohár {
605ba04d73cSArmin Wolf 	return single_open(file, i8k_proc_show, PDE_DATA(inode));
606a5afba16SPali Rohár }
607a5afba16SPali Rohár 
60897a32539SAlexey Dobriyan static const struct proc_ops i8k_proc_ops = {
60997a32539SAlexey Dobriyan 	.proc_open	= i8k_open_fs,
61097a32539SAlexey Dobriyan 	.proc_read	= seq_read,
61197a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
61297a32539SAlexey Dobriyan 	.proc_release	= single_release,
61397a32539SAlexey Dobriyan 	.proc_ioctl	= i8k_ioctl,
614039ae585SPali Rohár };
615039ae585SPali Rohár 
616a2cb66b4SArmin Wolf static void i8k_exit_procfs(void *param)
617039ae585SPali Rohár {
618039ae585SPali Rohár 	remove_proc_entry("i8k", NULL);
619039ae585SPali Rohár }
620039ae585SPali Rohár 
621a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
622039ae585SPali Rohár {
623ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
624ba04d73cSArmin Wolf 
625a2cb66b4SArmin Wolf 	/* Register the proc entry */
626ba04d73cSArmin Wolf 	proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data);
627a2cb66b4SArmin Wolf 
628a2cb66b4SArmin Wolf 	devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
629039ae585SPali Rohár }
630039ae585SPali Rohár 
631a2cb66b4SArmin Wolf #else
632a2cb66b4SArmin Wolf 
633a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
634039ae585SPali Rohár {
635039ae585SPali Rohár }
636039ae585SPali Rohár 
637039ae585SPali Rohár #endif
638a5afba16SPali Rohár 
639a5afba16SPali Rohár /*
640a5afba16SPali Rohár  * Hwmon interface
641a5afba16SPali Rohár  */
642a5afba16SPali Rohár 
643deeba244SArmin Wolf static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
644deeba244SArmin Wolf 				   int channel)
645a5afba16SPali Rohár {
646deeba244SArmin Wolf 	const struct dell_smm_data *data = drvdata;
647a5afba16SPali Rohár 
648deeba244SArmin Wolf 	switch (type) {
649deeba244SArmin Wolf 	case hwmon_temp:
650deeba244SArmin Wolf 		switch (attr) {
651deeba244SArmin Wolf 		case hwmon_temp_input:
652deeba244SArmin Wolf 		case hwmon_temp_label:
653deeba244SArmin Wolf 			if (data->temp_type[channel] >= 0)
654deeba244SArmin Wolf 				return 0444;
655deeba244SArmin Wolf 
656deeba244SArmin Wolf 			break;
657deeba244SArmin Wolf 		default:
658deeba244SArmin Wolf 			break;
659deeba244SArmin Wolf 		}
660deeba244SArmin Wolf 		break;
661deeba244SArmin Wolf 	case hwmon_fan:
662deeba244SArmin Wolf 		if (data->disallow_fan_support)
663deeba244SArmin Wolf 			break;
664deeba244SArmin Wolf 
665deeba244SArmin Wolf 		switch (attr) {
666deeba244SArmin Wolf 		case hwmon_fan_input:
667deeba244SArmin Wolf 			if (data->fan[channel])
668deeba244SArmin Wolf 				return 0444;
669deeba244SArmin Wolf 
670deeba244SArmin Wolf 			break;
671deeba244SArmin Wolf 		case hwmon_fan_label:
672deeba244SArmin Wolf 			if (data->fan[channel] && !data->disallow_fan_type_call)
673deeba244SArmin Wolf 				return 0444;
674deeba244SArmin Wolf 
675deeba244SArmin Wolf 			break;
676b1986c8eSArmin Wolf 		case hwmon_fan_min:
677b1986c8eSArmin Wolf 		case hwmon_fan_max:
678b1986c8eSArmin Wolf 		case hwmon_fan_target:
679b1986c8eSArmin Wolf 			if (data->fan_nominal_speed[channel])
680b1986c8eSArmin Wolf 				return 0444;
681b1986c8eSArmin Wolf 
682b1986c8eSArmin Wolf 			break;
683deeba244SArmin Wolf 		default:
684deeba244SArmin Wolf 			break;
685deeba244SArmin Wolf 		}
686deeba244SArmin Wolf 		break;
687deeba244SArmin Wolf 	case hwmon_pwm:
688deeba244SArmin Wolf 		if (data->disallow_fan_support)
689deeba244SArmin Wolf 			break;
690deeba244SArmin Wolf 
691deeba244SArmin Wolf 		switch (attr) {
692deeba244SArmin Wolf 		case hwmon_pwm_input:
693deeba244SArmin Wolf 			if (data->fan[channel])
694deeba244SArmin Wolf 				return 0644;
695deeba244SArmin Wolf 
696deeba244SArmin Wolf 			break;
697deeba244SArmin Wolf 		case hwmon_pwm_enable:
698deeba244SArmin Wolf 			if (data->auto_fan)
699deeba244SArmin Wolf 				/*
700deeba244SArmin Wolf 				 * There is no command for retrieve the current status
701deeba244SArmin Wolf 				 * from BIOS, and userspace/firmware itself can change
702deeba244SArmin Wolf 				 * it.
703deeba244SArmin Wolf 				 * Thus we can only provide write-only access for now.
704deeba244SArmin Wolf 				 */
705deeba244SArmin Wolf 				return 0200;
706deeba244SArmin Wolf 
707deeba244SArmin Wolf 			break;
708deeba244SArmin Wolf 		default:
709deeba244SArmin Wolf 			break;
710deeba244SArmin Wolf 		}
711deeba244SArmin Wolf 		break;
712deeba244SArmin Wolf 	default:
713deeba244SArmin Wolf 		break;
714a5afba16SPali Rohár 	}
715a5afba16SPali Rohár 
716deeba244SArmin Wolf 	return 0;
717a5afba16SPali Rohár }
718a5afba16SPali Rohár 
719deeba244SArmin Wolf static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
720deeba244SArmin Wolf 			 long *val)
721a5afba16SPali Rohár {
722ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
723deeba244SArmin Wolf 	int ret;
724a5afba16SPali Rohár 
725deeba244SArmin Wolf 	switch (type) {
726deeba244SArmin Wolf 	case hwmon_temp:
727deeba244SArmin Wolf 		switch (attr) {
728deeba244SArmin Wolf 		case hwmon_temp_input:
729deeba244SArmin Wolf 			ret = i8k_get_temp(channel);
730deeba244SArmin Wolf 			if (ret < 0)
731deeba244SArmin Wolf 				return ret;
732deeba244SArmin Wolf 
733deeba244SArmin Wolf 			*val = ret * 1000;
734deeba244SArmin Wolf 
735deeba244SArmin Wolf 			return 0;
736deeba244SArmin Wolf 		default:
737deeba244SArmin Wolf 			break;
738deeba244SArmin Wolf 		}
739deeba244SArmin Wolf 		break;
740deeba244SArmin Wolf 	case hwmon_fan:
741deeba244SArmin Wolf 		switch (attr) {
742deeba244SArmin Wolf 		case hwmon_fan_input:
743deeba244SArmin Wolf 			ret = i8k_get_fan_speed(data, channel);
744deeba244SArmin Wolf 			if (ret < 0)
745deeba244SArmin Wolf 				return ret;
746deeba244SArmin Wolf 
747deeba244SArmin Wolf 			*val = ret;
748deeba244SArmin Wolf 
749deeba244SArmin Wolf 			return 0;
750b1986c8eSArmin Wolf 		case hwmon_fan_min:
751b1986c8eSArmin Wolf 			*val = data->fan_nominal_speed[channel][0];
752b1986c8eSArmin Wolf 
753b1986c8eSArmin Wolf 			return 0;
754b1986c8eSArmin Wolf 		case hwmon_fan_max:
755b1986c8eSArmin Wolf 			*val = data->fan_nominal_speed[channel][data->i8k_fan_max];
756b1986c8eSArmin Wolf 
757b1986c8eSArmin Wolf 			return 0;
758b1986c8eSArmin Wolf 		case hwmon_fan_target:
759b1986c8eSArmin Wolf 			ret = i8k_get_fan_status(data, channel);
760b1986c8eSArmin Wolf 			if (ret < 0)
761b1986c8eSArmin Wolf 				return ret;
762b1986c8eSArmin Wolf 
763b1986c8eSArmin Wolf 			if (ret > data->i8k_fan_max)
764b1986c8eSArmin Wolf 				ret = data->i8k_fan_max;
765b1986c8eSArmin Wolf 
766b1986c8eSArmin Wolf 			*val = data->fan_nominal_speed[channel][ret];
767b1986c8eSArmin Wolf 
768b1986c8eSArmin Wolf 			return 0;
769deeba244SArmin Wolf 		default:
770deeba244SArmin Wolf 			break;
771deeba244SArmin Wolf 		}
772deeba244SArmin Wolf 		break;
773deeba244SArmin Wolf 	case hwmon_pwm:
774deeba244SArmin Wolf 		switch (attr) {
775deeba244SArmin Wolf 		case hwmon_pwm_input:
776deeba244SArmin Wolf 			ret = i8k_get_fan_status(data, channel);
777deeba244SArmin Wolf 			if (ret < 0)
778deeba244SArmin Wolf 				return ret;
779deeba244SArmin Wolf 
780deeba244SArmin Wolf 			*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
781deeba244SArmin Wolf 
782deeba244SArmin Wolf 			return 0;
783deeba244SArmin Wolf 		default:
784deeba244SArmin Wolf 			break;
785deeba244SArmin Wolf 		}
786deeba244SArmin Wolf 		break;
787deeba244SArmin Wolf 	default:
788deeba244SArmin Wolf 		break;
789deeba244SArmin Wolf 	}
790deeba244SArmin Wolf 
791deeba244SArmin Wolf 	return -EOPNOTSUPP;
792deeba244SArmin Wolf }
793deeba244SArmin Wolf 
794deeba244SArmin Wolf static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel)
795deeba244SArmin Wolf {
796deeba244SArmin Wolf 	bool dock = false;
797deeba244SArmin Wolf 	int type = i8k_get_fan_type(data, channel);
798deeba244SArmin Wolf 
799a5afba16SPali Rohár 	if (type < 0)
800deeba244SArmin Wolf 		return ERR_PTR(type);
801a5afba16SPali Rohár 
802a5afba16SPali Rohár 	if (type & 0x10) {
803a5afba16SPali Rohár 		dock = true;
804a5afba16SPali Rohár 		type &= 0x0F;
805a5afba16SPali Rohár 	}
806a5afba16SPali Rohár 
807deeba244SArmin Wolf 	if (type >= ARRAY_SIZE(fan_labels))
808deeba244SArmin Wolf 		type = ARRAY_SIZE(fan_labels) - 1;
809a5afba16SPali Rohár 
810deeba244SArmin Wolf 	return dock ? docking_labels[type] : fan_labels[type];
811a5afba16SPali Rohár }
812a5afba16SPali Rohár 
813deeba244SArmin Wolf static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
814deeba244SArmin Wolf 				int channel, const char **str)
815a5afba16SPali Rohár {
816ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
817a5afba16SPali Rohár 
818deeba244SArmin Wolf 	switch (type) {
819deeba244SArmin Wolf 	case hwmon_temp:
820deeba244SArmin Wolf 		switch (attr) {
821deeba244SArmin Wolf 		case hwmon_temp_label:
822deeba244SArmin Wolf 			*str = temp_labels[data->temp_type[channel]];
823deeba244SArmin Wolf 			return 0;
824deeba244SArmin Wolf 		default:
825deeba244SArmin Wolf 			break;
826deeba244SArmin Wolf 		}
827deeba244SArmin Wolf 		break;
828deeba244SArmin Wolf 	case hwmon_fan:
829deeba244SArmin Wolf 		switch (attr) {
830deeba244SArmin Wolf 		case hwmon_fan_label:
831deeba244SArmin Wolf 			*str = dell_smm_fan_label(data, channel);
832deeba244SArmin Wolf 			return PTR_ERR_OR_ZERO(*str);
833deeba244SArmin Wolf 		default:
834deeba244SArmin Wolf 			break;
835deeba244SArmin Wolf 		}
836deeba244SArmin Wolf 		break;
837deeba244SArmin Wolf 	default:
838deeba244SArmin Wolf 		break;
839a5afba16SPali Rohár 	}
840a5afba16SPali Rohár 
841deeba244SArmin Wolf 	return -EOPNOTSUPP;
842a5afba16SPali Rohár }
843a5afba16SPali Rohár 
844deeba244SArmin Wolf static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
845deeba244SArmin Wolf 			  long val)
846a5afba16SPali Rohár {
847ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
848deeba244SArmin Wolf 	unsigned long pwm;
849deeba244SArmin Wolf 	bool enable;
850a5afba16SPali Rohár 	int err;
851a5afba16SPali Rohár 
852deeba244SArmin Wolf 	switch (type) {
853deeba244SArmin Wolf 	case hwmon_pwm:
854deeba244SArmin Wolf 		switch (attr) {
855deeba244SArmin Wolf 		case hwmon_pwm_input:
856deeba244SArmin Wolf 			pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0,
857deeba244SArmin Wolf 					data->i8k_fan_max);
858a5afba16SPali Rohár 
859ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
860deeba244SArmin Wolf 			err = i8k_set_fan(data, channel, pwm);
861ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
862a5afba16SPali Rohár 
863deeba244SArmin Wolf 			if (err < 0)
864afe45277SGiovanni Mascellani 				return err;
865afe45277SGiovanni Mascellani 
866deeba244SArmin Wolf 			return 0;
867deeba244SArmin Wolf 		case hwmon_pwm_enable:
868deeba244SArmin Wolf 			if (!val)
869deeba244SArmin Wolf 				return -EINVAL;
870deeba244SArmin Wolf 
871afe45277SGiovanni Mascellani 			if (val == 1)
872afe45277SGiovanni Mascellani 				enable = false;
873afe45277SGiovanni Mascellani 			else
874deeba244SArmin Wolf 				enable = true;
875afe45277SGiovanni Mascellani 
876ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
877ba04d73cSArmin Wolf 			err = i8k_enable_fan_auto_mode(data, enable);
878ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
879afe45277SGiovanni Mascellani 
880deeba244SArmin Wolf 			if (err < 0)
881deeba244SArmin Wolf 				return err;
882deeba244SArmin Wolf 
883deeba244SArmin Wolf 			return 0;
884deeba244SArmin Wolf 		default:
885deeba244SArmin Wolf 			break;
886deeba244SArmin Wolf 		}
887deeba244SArmin Wolf 		break;
888deeba244SArmin Wolf 	default:
889deeba244SArmin Wolf 		break;
890afe45277SGiovanni Mascellani 	}
891afe45277SGiovanni Mascellani 
892deeba244SArmin Wolf 	return -EOPNOTSUPP;
893deeba244SArmin Wolf }
894a5afba16SPali Rohár 
895deeba244SArmin Wolf static const struct hwmon_ops dell_smm_ops = {
896deeba244SArmin Wolf 	.is_visible = dell_smm_is_visible,
897deeba244SArmin Wolf 	.read = dell_smm_read,
898deeba244SArmin Wolf 	.read_string = dell_smm_read_string,
899deeba244SArmin Wolf 	.write = dell_smm_write,
900deeba244SArmin Wolf };
901deeba244SArmin Wolf 
902deeba244SArmin Wolf static const struct hwmon_channel_info *dell_smm_info[] = {
903deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
904deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(temp,
905deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
906deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
907deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
908deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
909deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
910deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
911deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
912deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
913deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
914deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL
915deeba244SArmin Wolf 			   ),
916deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(fan,
917b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
918b1986c8eSArmin Wolf 			   HWMON_F_TARGET,
919b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
920b1986c8eSArmin Wolf 			   HWMON_F_TARGET,
921b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
922b1986c8eSArmin Wolf 			   HWMON_F_TARGET
923deeba244SArmin Wolf 			   ),
924deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(pwm,
925deeba244SArmin Wolf 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
926deeba244SArmin Wolf 			   HWMON_PWM_INPUT,
927deeba244SArmin Wolf 			   HWMON_PWM_INPUT
928deeba244SArmin Wolf 			   ),
929a5afba16SPali Rohár 	NULL
930a5afba16SPali Rohár };
931a5afba16SPali Rohár 
932deeba244SArmin Wolf static const struct hwmon_chip_info dell_smm_chip_info = {
933deeba244SArmin Wolf 	.ops = &dell_smm_ops,
934deeba244SArmin Wolf 	.info = dell_smm_info,
935a5afba16SPali Rohár };
936a5afba16SPali Rohár 
9371492fa21SArmin Wolf static int __init dell_smm_init_hwmon(struct device *dev)
938a5afba16SPali Rohár {
939ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
940deeba244SArmin Wolf 	struct device *dell_smm_hwmon_dev;
941b1986c8eSArmin Wolf 	int i, state, err;
942a5afba16SPali Rohár 
943deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
944deeba244SArmin Wolf 		data->temp_type[i] = i8k_get_temp_type(i);
945deeba244SArmin Wolf 		if (data->temp_type[i] < 0)
946deeba244SArmin Wolf 			continue;
947a5afba16SPali Rohár 
948deeba244SArmin Wolf 		if (data->temp_type[i] >= ARRAY_SIZE(temp_labels))
949deeba244SArmin Wolf 			data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1;
950deeba244SArmin Wolf 	}
951deeba244SArmin Wolf 
952deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_FANS; i++) {
953deeba244SArmin Wolf 		data->fan_type[i] = INT_MIN;
954deeba244SArmin Wolf 		err = i8k_get_fan_status(data, i);
9555ce91714SPali Rohár 		if (err < 0)
956deeba244SArmin Wolf 			err = i8k_get_fan_type(data, i);
957b1986c8eSArmin Wolf 
958b1986c8eSArmin Wolf 		if (err < 0)
959b1986c8eSArmin Wolf 			continue;
960b1986c8eSArmin Wolf 
961deeba244SArmin Wolf 		data->fan[i] = true;
962b1986c8eSArmin Wolf 		data->fan_nominal_speed[i] = devm_kmalloc_array(dev, data->i8k_fan_max + 1,
963b1986c8eSArmin Wolf 								sizeof(*data->fan_nominal_speed[i]),
964b1986c8eSArmin Wolf 								GFP_KERNEL);
965b1986c8eSArmin Wolf 		if (!data->fan_nominal_speed[i])
966b1986c8eSArmin Wolf 			continue;
967b1986c8eSArmin Wolf 
968b1986c8eSArmin Wolf 		for (state = 0; state <= data->i8k_fan_max; state++) {
969b1986c8eSArmin Wolf 			err = i8k_get_fan_nominal_speed(data, i, state);
970b1986c8eSArmin Wolf 			if (err < 0) {
971b1986c8eSArmin Wolf 				/* Mark nominal speed table as invalid in case of error */
972b1986c8eSArmin Wolf 				devm_kfree(dev, data->fan_nominal_speed[i]);
973b1986c8eSArmin Wolf 				data->fan_nominal_speed[i] = NULL;
974b1986c8eSArmin Wolf 				break;
975b1986c8eSArmin Wolf 			}
976b1986c8eSArmin Wolf 			data->fan_nominal_speed[i][state] = err;
977b1986c8eSArmin Wolf 		}
978deeba244SArmin Wolf 	}
979a5afba16SPali Rohár 
980deeba244SArmin Wolf 	dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data,
981deeba244SArmin Wolf 								  &dell_smm_chip_info, NULL);
982a5afba16SPali Rohár 
983deeba244SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
984a5afba16SPali Rohár }
985a5afba16SPali Rohár 
986a5afba16SPali Rohár struct i8k_config_data {
987a5afba16SPali Rohár 	uint fan_mult;
988a5afba16SPali Rohár 	uint fan_max;
989a5afba16SPali Rohár };
990a5afba16SPali Rohár 
991a5afba16SPali Rohár enum i8k_configs {
992a5afba16SPali Rohár 	DELL_LATITUDE_D520,
993a5afba16SPali Rohár 	DELL_PRECISION_490,
994a5afba16SPali Rohár 	DELL_STUDIO,
995a5afba16SPali Rohár 	DELL_XPS,
996a5afba16SPali Rohár };
997a5afba16SPali Rohár 
998c510f6acSArmin Wolf static const struct i8k_config_data i8k_config_data[] __initconst = {
999a5afba16SPali Rohár 	[DELL_LATITUDE_D520] = {
1000a5afba16SPali Rohár 		.fan_mult = 1,
1001a5afba16SPali Rohár 		.fan_max = I8K_FAN_TURBO,
1002a5afba16SPali Rohár 	},
1003a5afba16SPali Rohár 	[DELL_PRECISION_490] = {
1004a5afba16SPali Rohár 		.fan_mult = 1,
1005a5afba16SPali Rohár 		.fan_max = I8K_FAN_TURBO,
1006a5afba16SPali Rohár 	},
1007a5afba16SPali Rohár 	[DELL_STUDIO] = {
1008a5afba16SPali Rohár 		.fan_mult = 1,
1009a5afba16SPali Rohár 		.fan_max = I8K_FAN_HIGH,
1010a5afba16SPali Rohár 	},
1011a5afba16SPali Rohár 	[DELL_XPS] = {
1012a5afba16SPali Rohár 		.fan_mult = 1,
1013a5afba16SPali Rohár 		.fan_max = I8K_FAN_HIGH,
1014a5afba16SPali Rohár 	},
1015a5afba16SPali Rohár };
1016a5afba16SPali Rohár 
10176faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_dmi_table[] __initconst = {
1018a5afba16SPali Rohár 	{
1019a5afba16SPali Rohár 		.ident = "Dell Inspiron",
1020a5afba16SPali Rohár 		.matches = {
1021a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
1022a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
1023a5afba16SPali Rohár 		},
1024a5afba16SPali Rohár 	},
1025a5afba16SPali Rohár 	{
1026a5afba16SPali Rohár 		.ident = "Dell Latitude",
1027a5afba16SPali Rohár 		.matches = {
1028a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
1029a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1030a5afba16SPali Rohár 		},
1031a5afba16SPali Rohár 	},
1032a5afba16SPali Rohár 	{
1033a5afba16SPali Rohár 		.ident = "Dell Inspiron 2",
1034a5afba16SPali Rohár 		.matches = {
1035a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1036a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
1037a5afba16SPali Rohár 		},
1038a5afba16SPali Rohár 	},
1039a5afba16SPali Rohár 	{
1040a5afba16SPali Rohár 		.ident = "Dell Latitude D520",
1041a5afba16SPali Rohár 		.matches = {
1042a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1043a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
1044a5afba16SPali Rohár 		},
1045a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
1046a5afba16SPali Rohár 	},
1047a5afba16SPali Rohár 	{
1048a5afba16SPali Rohár 		.ident = "Dell Latitude 2",
1049a5afba16SPali Rohár 		.matches = {
1050a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1051a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1052a5afba16SPali Rohár 		},
1053a5afba16SPali Rohár 	},
1054a5afba16SPali Rohár 	{	/* UK Inspiron 6400  */
1055a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1056a5afba16SPali Rohár 		.matches = {
1057a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1058a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
1059a5afba16SPali Rohár 		},
1060a5afba16SPali Rohár 	},
1061a5afba16SPali Rohár 	{
1062a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1063a5afba16SPali Rohár 		.matches = {
1064a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1065a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
1066a5afba16SPali Rohár 		},
1067a5afba16SPali Rohár 	},
1068a5afba16SPali Rohár 	{
1069a5afba16SPali Rohár 		.ident = "Dell Precision 490",
1070a5afba16SPali Rohár 		.matches = {
1071a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1072a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME,
1073a5afba16SPali Rohár 				  "Precision WorkStation 490"),
1074a5afba16SPali Rohár 		},
1075a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
1076a5afba16SPali Rohár 	},
1077a5afba16SPali Rohár 	{
1078a5afba16SPali Rohár 		.ident = "Dell Precision",
1079a5afba16SPali Rohár 		.matches = {
1080a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1081a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
1082a5afba16SPali Rohár 		},
1083a5afba16SPali Rohár 	},
1084a5afba16SPali Rohár 	{
1085a5afba16SPali Rohár 		.ident = "Dell Vostro",
1086a5afba16SPali Rohár 		.matches = {
1087a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1088a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
1089a5afba16SPali Rohár 		},
1090a5afba16SPali Rohár 	},
1091a5afba16SPali Rohár 	{
1092a5afba16SPali Rohár 		.ident = "Dell Studio",
1093a5afba16SPali Rohár 		.matches = {
1094a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1095a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
1096a5afba16SPali Rohár 		},
1097a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
1098a5afba16SPali Rohár 	},
1099a5afba16SPali Rohár 	{
1100a5afba16SPali Rohár 		.ident = "Dell XPS M140",
1101a5afba16SPali Rohár 		.matches = {
1102a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1103a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
1104a5afba16SPali Rohár 		},
1105a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_XPS],
1106a5afba16SPali Rohár 	},
1107a4811b6cSPali Rohár 	{
1108b8a13e5eSThomas Hebb 		.ident = "Dell XPS",
1109a4811b6cSPali Rohár 		.matches = {
1110a4811b6cSPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1111b8a13e5eSThomas Hebb 			DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
1112162372b0SMichele Sorcinelli 		},
1113162372b0SMichele Sorcinelli 	},
1114a5afba16SPali Rohár 	{ }
1115a5afba16SPali Rohár };
1116a5afba16SPali Rohár 
1117a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
1118a5afba16SPali Rohár 
1119a4b45b25SPali Rohár /*
11202744d2fdSPali Rohár  * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
11212744d2fdSPali Rohár  * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
11222744d2fdSPali Rohár  * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
11232744d2fdSPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121
11246220f4ebSThorsten Leemhuis  */
11256faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = {
11262744d2fdSPali Rohár 	{
11276220f4ebSThorsten Leemhuis 		.ident = "Dell Studio XPS 8000",
11286220f4ebSThorsten Leemhuis 		.matches = {
11296220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11306220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"),
11316220f4ebSThorsten Leemhuis 		},
11326220f4ebSThorsten Leemhuis 	},
11336220f4ebSThorsten Leemhuis 	{
1134a4b45b25SPali Rohár 		.ident = "Dell Studio XPS 8100",
1135a4b45b25SPali Rohár 		.matches = {
1136a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1137a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"),
1138a4b45b25SPali Rohár 		},
1139a4b45b25SPali Rohár 	},
11402744d2fdSPali Rohár 	{
11412744d2fdSPali Rohár 		.ident = "Dell Inspiron 580",
11422744d2fdSPali Rohár 		.matches = {
11432744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11442744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "),
11452744d2fdSPali Rohár 		},
11462744d2fdSPali Rohár 	},
1147a4b45b25SPali Rohár 	{ }
1148a4b45b25SPali Rohár };
1149a4b45b25SPali Rohár 
1150a5afba16SPali Rohár /*
1151f480ea90SPali Rohár  * On some machines all fan related SMM functions implemented by Dell BIOS
1152f480ea90SPali Rohár  * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan
1153f480ea90SPali Rohár  * support for affected blacklisted Dell machines stay disabled.
1154f480ea90SPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
1155f480ea90SPali Rohár  */
1156c510f6acSArmin Wolf static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initconst = {
1157f480ea90SPali Rohár 	{
1158f480ea90SPali Rohár 		.ident = "Dell Inspiron 7720",
1159f480ea90SPali Rohár 		.matches = {
1160f480ea90SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1161f480ea90SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
1162f480ea90SPali Rohár 		},
1163f480ea90SPali Rohár 	},
11646fbc4232SOleksandr Natalenko 	{
11656fbc4232SOleksandr Natalenko 		.ident = "Dell Vostro 3360",
11666fbc4232SOleksandr Natalenko 		.matches = {
11676fbc4232SOleksandr Natalenko 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11686fbc4232SOleksandr Natalenko 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
11696fbc4232SOleksandr Natalenko 		},
11706fbc4232SOleksandr Natalenko 	},
1171536e0019SHelge Eichelberg 	{
1172536e0019SHelge Eichelberg 		.ident = "Dell XPS13 9333",
1173536e0019SHelge Eichelberg 		.matches = {
1174536e0019SHelge Eichelberg 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1175536e0019SHelge Eichelberg 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
1176536e0019SHelge Eichelberg 		},
1177536e0019SHelge Eichelberg 	},
11784008bc7dSThomas Hebb 	{
11794008bc7dSThomas Hebb 		.ident = "Dell XPS 15 L502X",
11804008bc7dSThomas Hebb 		.matches = {
11814008bc7dSThomas Hebb 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11824008bc7dSThomas Hebb 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"),
11834008bc7dSThomas Hebb 		},
11844008bc7dSThomas Hebb 	},
1185f480ea90SPali Rohár 	{ }
1186f480ea90SPali Rohár };
1187f480ea90SPali Rohár 
1188afe45277SGiovanni Mascellani struct i8k_fan_control_data {
1189afe45277SGiovanni Mascellani 	unsigned int manual_fan;
1190afe45277SGiovanni Mascellani 	unsigned int auto_fan;
1191afe45277SGiovanni Mascellani };
1192afe45277SGiovanni Mascellani 
1193afe45277SGiovanni Mascellani enum i8k_fan_controls {
1194afe45277SGiovanni Mascellani 	I8K_FAN_34A3_35A3,
1195afe45277SGiovanni Mascellani };
1196afe45277SGiovanni Mascellani 
1197c510f6acSArmin Wolf static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
1198afe45277SGiovanni Mascellani 	[I8K_FAN_34A3_35A3] = {
1199afe45277SGiovanni Mascellani 		.manual_fan = 0x34a3,
1200afe45277SGiovanni Mascellani 		.auto_fan = 0x35a3,
1201afe45277SGiovanni Mascellani 	},
1202afe45277SGiovanni Mascellani };
1203afe45277SGiovanni Mascellani 
1204c510f6acSArmin Wolf static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
1205afe45277SGiovanni Mascellani 	{
12060ca8bb2cSJeffrey Lin 		.ident = "Dell Latitude 5480",
12070ca8bb2cSJeffrey Lin 		.matches = {
12080ca8bb2cSJeffrey Lin 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
12090ca8bb2cSJeffrey Lin 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"),
12100ca8bb2cSJeffrey Lin 		},
12110ca8bb2cSJeffrey Lin 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
12120ca8bb2cSJeffrey Lin 	},
12130ca8bb2cSJeffrey Lin 	{
1214afe45277SGiovanni Mascellani 		.ident = "Dell Latitude E6440",
1215afe45277SGiovanni Mascellani 		.matches = {
1216afe45277SGiovanni Mascellani 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1217afe45277SGiovanni Mascellani 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
1218afe45277SGiovanni Mascellani 		},
1219afe45277SGiovanni Mascellani 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1220afe45277SGiovanni Mascellani 	},
1221807b8c29SSebastian Oechsle 	{
1222807b8c29SSebastian Oechsle 		.ident = "Dell Latitude E7440",
1223807b8c29SSebastian Oechsle 		.matches = {
1224807b8c29SSebastian Oechsle 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1225807b8c29SSebastian Oechsle 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E7440"),
1226807b8c29SSebastian Oechsle 		},
1227807b8c29SSebastian Oechsle 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1228807b8c29SSebastian Oechsle 	},
122995d88d05SCarlos Alberto Lopez Perez 	{
123095d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 5530",
123195d88d05SCarlos Alberto Lopez Perez 		.matches = {
123295d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
123395d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
123495d88d05SCarlos Alberto Lopez Perez 		},
123595d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
123695d88d05SCarlos Alberto Lopez Perez 	},
123795d88d05SCarlos Alberto Lopez Perez 	{
123895d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 7510",
123995d88d05SCarlos Alberto Lopez Perez 		.matches = {
124095d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
124195d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"),
124295d88d05SCarlos Alberto Lopez Perez 		},
124395d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
124495d88d05SCarlos Alberto Lopez Perez 	},
1245afe45277SGiovanni Mascellani 	{ }
1246afe45277SGiovanni Mascellani };
1247afe45277SGiovanni Mascellani 
12481492fa21SArmin Wolf static int __init dell_smm_probe(struct platform_device *pdev)
1249a5afba16SPali Rohár {
1250ba04d73cSArmin Wolf 	struct dell_smm_data *data;
1251afe45277SGiovanni Mascellani 	const struct dmi_system_id *id, *fan_control;
1252a5afba16SPali Rohár 	int fan, ret;
1253a5afba16SPali Rohár 
1254ba04d73cSArmin Wolf 	data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
1255ba04d73cSArmin Wolf 	if (!data)
1256ba04d73cSArmin Wolf 		return -ENOMEM;
1257ba04d73cSArmin Wolf 
1258ba04d73cSArmin Wolf 	mutex_init(&data->i8k_mutex);
1259ba04d73cSArmin Wolf 	data->i8k_fan_mult = I8K_FAN_MULT;
1260ba04d73cSArmin Wolf 	data->i8k_fan_max = I8K_FAN_HIGH;
1261ba04d73cSArmin Wolf 	platform_set_drvdata(pdev, data);
1262ba04d73cSArmin Wolf 
1263f480ea90SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
1264ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n");
1265f480ea90SPali Rohár 		if (!force)
1266ba04d73cSArmin Wolf 			data->disallow_fan_support = true;
1267f480ea90SPali Rohár 	}
1268f480ea90SPali Rohár 
1269836ad112SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
1270ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n");
1271836ad112SPali Rohár 		if (!force)
1272ba04d73cSArmin Wolf 			data->disallow_fan_type_call = true;
1273836ad112SPali Rohár 	}
12742744d2fdSPali Rohár 
1275ba04d73cSArmin Wolf 	strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
1276ba04d73cSArmin Wolf 		sizeof(data->bios_version));
1277ba04d73cSArmin Wolf 	strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
1278ba04d73cSArmin Wolf 		sizeof(data->bios_machineid));
1279a5afba16SPali Rohár 
1280a5afba16SPali Rohár 	/*
1281a5afba16SPali Rohár 	 * Set fan multiplier and maximal fan speed from dmi config
1282a5afba16SPali Rohár 	 * Values specified in module parameters override values from dmi
1283a5afba16SPali Rohár 	 */
1284a5afba16SPali Rohár 	id = dmi_first_match(i8k_dmi_table);
1285a5afba16SPali Rohár 	if (id && id->driver_data) {
1286a5afba16SPali Rohár 		const struct i8k_config_data *conf = id->driver_data;
12871492fa21SArmin Wolf 
1288a5afba16SPali Rohár 		if (!fan_mult && conf->fan_mult)
1289a5afba16SPali Rohár 			fan_mult = conf->fan_mult;
1290ba04d73cSArmin Wolf 
1291a5afba16SPali Rohár 		if (!fan_max && conf->fan_max)
1292a5afba16SPali Rohár 			fan_max = conf->fan_max;
1293a5afba16SPali Rohár 	}
1294a5afba16SPali Rohár 
1295ba04d73cSArmin Wolf 	data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
1296ba04d73cSArmin Wolf 	data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
1297a5afba16SPali Rohár 
1298afe45277SGiovanni Mascellani 	fan_control = dmi_first_match(i8k_whitelist_fan_control);
1299afe45277SGiovanni Mascellani 	if (fan_control && fan_control->driver_data) {
1300ba04d73cSArmin Wolf 		const struct i8k_fan_control_data *control = fan_control->driver_data;
1301afe45277SGiovanni Mascellani 
1302ba04d73cSArmin Wolf 		data->manual_fan = control->manual_fan;
1303ba04d73cSArmin Wolf 		data->auto_fan = control->auto_fan;
1304ba04d73cSArmin Wolf 		dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
1305afe45277SGiovanni Mascellani 	}
1306afe45277SGiovanni Mascellani 
1307a5afba16SPali Rohár 	if (!fan_mult) {
1308a5afba16SPali Rohár 		/*
1309a5afba16SPali Rohár 		 * Autodetect fan multiplier based on nominal rpm
1310a5afba16SPali Rohár 		 * If fan reports rpm value too high then set multiplier to 1
1311a5afba16SPali Rohár 		 */
13122757269aSArmin Wolf 		for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) {
1313ba04d73cSArmin Wolf 			ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max);
1314a5afba16SPali Rohár 			if (ret < 0)
1315a5afba16SPali Rohár 				continue;
1316ba04d73cSArmin Wolf 
1317a5afba16SPali Rohár 			if (ret > I8K_FAN_MAX_RPM)
1318ba04d73cSArmin Wolf 				data->i8k_fan_mult = 1;
1319a5afba16SPali Rohár 			break;
1320a5afba16SPali Rohár 		}
1321a5afba16SPali Rohár 	} else {
1322a5afba16SPali Rohár 		/* Fan multiplier was specified in module param or in dmi */
1323ba04d73cSArmin Wolf 		data->i8k_fan_mult = fan_mult;
1324a5afba16SPali Rohár 	}
1325a5afba16SPali Rohár 
13261492fa21SArmin Wolf 	ret = dell_smm_init_hwmon(&pdev->dev);
13271492fa21SArmin Wolf 	if (ret)
13281492fa21SArmin Wolf 		return ret;
13291492fa21SArmin Wolf 
1330a2cb66b4SArmin Wolf 	i8k_init_procfs(&pdev->dev);
13311492fa21SArmin Wolf 
13321492fa21SArmin Wolf 	return 0;
13331492fa21SArmin Wolf }
13341492fa21SArmin Wolf 
13351492fa21SArmin Wolf static struct platform_driver dell_smm_driver = {
13361492fa21SArmin Wolf 	.driver		= {
13371492fa21SArmin Wolf 		.name	= KBUILD_MODNAME,
13381492fa21SArmin Wolf 	},
13391492fa21SArmin Wolf };
13401492fa21SArmin Wolf 
13411492fa21SArmin Wolf static struct platform_device *dell_smm_device;
13421492fa21SArmin Wolf 
13431492fa21SArmin Wolf /*
13441492fa21SArmin Wolf  * Probe for the presence of a supported laptop.
13451492fa21SArmin Wolf  */
1346a5afba16SPali Rohár static int __init i8k_init(void)
1347a5afba16SPali Rohár {
13481492fa21SArmin Wolf 	/*
13491492fa21SArmin Wolf 	 * Get DMI information
13501492fa21SArmin Wolf 	 */
13511492fa21SArmin Wolf 	if (!dmi_check_system(i8k_dmi_table)) {
13521492fa21SArmin Wolf 		if (!ignore_dmi && !force)
1353a5afba16SPali Rohár 			return -ENODEV;
1354a5afba16SPali Rohár 
13551492fa21SArmin Wolf 		pr_info("not running on a supported Dell system.\n");
13561492fa21SArmin Wolf 		pr_info("vendor=%s, model=%s, version=%s\n",
13571492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_SYS_VENDOR),
13581492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_PRODUCT_NAME),
13591492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_BIOS_VERSION));
13601492fa21SArmin Wolf 	}
1361039ae585SPali Rohár 
13621492fa21SArmin Wolf 	/*
13631492fa21SArmin Wolf 	 * Get SMM Dell signature
13641492fa21SArmin Wolf 	 */
13651492fa21SArmin Wolf 	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
13661492fa21SArmin Wolf 	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
13671492fa21SArmin Wolf 		pr_err("unable to get SMM Dell signature\n");
13681492fa21SArmin Wolf 		if (!force)
13691492fa21SArmin Wolf 			return -ENODEV;
13701492fa21SArmin Wolf 	}
13711492fa21SArmin Wolf 
13721492fa21SArmin Wolf 	dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
13731492fa21SArmin Wolf 						 0);
13741492fa21SArmin Wolf 
13751492fa21SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_device);
1376a5afba16SPali Rohár }
1377a5afba16SPali Rohár 
1378a5afba16SPali Rohár static void __exit i8k_exit(void)
1379a5afba16SPali Rohár {
13801492fa21SArmin Wolf 	platform_device_unregister(dell_smm_device);
13811492fa21SArmin Wolf 	platform_driver_unregister(&dell_smm_driver);
1382a5afba16SPali Rohár }
1383a5afba16SPali Rohár 
1384a5afba16SPali Rohár module_init(i8k_init);
1385a5afba16SPali Rohár module_exit(i8k_exit);
1386