xref: /linux/drivers/hwmon/dell-smm-hwmon.c (revision dbd3e6eaf3d813939b28e8a66e29d81cdc836445)
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 
156105870fSArmin Wolf #include <linux/capability.h>
1627046a3fSJuergen Gross #include <linux/cpu.h>
176105870fSArmin Wolf #include <linux/ctype.h>
18a5afba16SPali Rohár #include <linux/delay.h>
196105870fSArmin Wolf #include <linux/dmi.h>
201492fa21SArmin Wolf #include <linux/err.h>
21e64325e8SArmin Wolf #include <linux/errno.h>
226105870fSArmin Wolf #include <linux/hwmon.h>
23a5afba16SPali Rohár #include <linux/init.h>
246105870fSArmin Wolf #include <linux/module.h>
256105870fSArmin Wolf #include <linux/mutex.h>
266105870fSArmin Wolf #include <linux/platform_device.h>
27a5afba16SPali Rohár #include <linux/proc_fs.h>
28a5afba16SPali Rohár #include <linux/seq_file.h>
2938c5b0ddSArmin Wolf #include <linux/string.h>
3027046a3fSJuergen Gross #include <linux/smp.h>
316105870fSArmin Wolf #include <linux/types.h>
326105870fSArmin Wolf #include <linux/uaccess.h>
33a5afba16SPali Rohár 
34a5afba16SPali Rohár #include <linux/i8k.h>
35a5afba16SPali Rohár 
36a5afba16SPali Rohár #define I8K_SMM_FN_STATUS	0x0025
37a5afba16SPali Rohár #define I8K_SMM_POWER_STATUS	0x0069
38a5afba16SPali Rohár #define I8K_SMM_SET_FAN		0x01a3
39a5afba16SPali Rohár #define I8K_SMM_GET_FAN		0x00a3
40a5afba16SPali Rohár #define I8K_SMM_GET_SPEED	0x02a3
41a5afba16SPali Rohár #define I8K_SMM_GET_FAN_TYPE	0x03a3
42a5afba16SPali Rohár #define I8K_SMM_GET_NOM_SPEED	0x04a3
43a5afba16SPali Rohár #define I8K_SMM_GET_TEMP	0x10a3
44a5afba16SPali Rohár #define I8K_SMM_GET_TEMP_TYPE	0x11a3
45a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG1	0xfea3
46a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG2	0xffa3
47a5afba16SPali Rohár 
48a5afba16SPali Rohár #define I8K_FAN_MULT		30
49a5afba16SPali Rohár #define I8K_FAN_MAX_RPM		30000
50a5afba16SPali Rohár #define I8K_MAX_TEMP		127
51a5afba16SPali Rohár 
52a5afba16SPali Rohár #define I8K_FN_NONE		0x00
53a5afba16SPali Rohár #define I8K_FN_UP		0x01
54a5afba16SPali Rohár #define I8K_FN_DOWN		0x02
55a5afba16SPali Rohár #define I8K_FN_MUTE		0x04
56a5afba16SPali Rohár #define I8K_FN_MASK		0x07
57a5afba16SPali Rohár #define I8K_FN_SHIFT		8
58a5afba16SPali Rohár 
59a5afba16SPali Rohár #define I8K_POWER_AC		0x05
60a5afba16SPali Rohár #define I8K_POWER_BATTERY	0x01
61a5afba16SPali Rohár 
62deeba244SArmin Wolf #define DELL_SMM_NO_TEMP	10
63deeba244SArmin Wolf #define DELL_SMM_NO_FANS	3
64deeba244SArmin Wolf 
65ba04d73cSArmin Wolf struct dell_smm_data {
66ba04d73cSArmin Wolf 	struct mutex i8k_mutex; /* lock for sensors writes */
67ba04d73cSArmin Wolf 	char bios_version[4];
68ba04d73cSArmin Wolf 	char bios_machineid[16];
69ba04d73cSArmin Wolf 	uint i8k_fan_mult;
70ba04d73cSArmin Wolf 	uint i8k_pwm_mult;
71ba04d73cSArmin Wolf 	uint i8k_fan_max;
72ba04d73cSArmin Wolf 	bool disallow_fan_type_call;
73ba04d73cSArmin Wolf 	bool disallow_fan_support;
74ba04d73cSArmin Wolf 	unsigned int manual_fan;
75ba04d73cSArmin Wolf 	unsigned int auto_fan;
76deeba244SArmin Wolf 	int temp_type[DELL_SMM_NO_TEMP];
77deeba244SArmin Wolf 	bool fan[DELL_SMM_NO_FANS];
78deeba244SArmin Wolf 	int fan_type[DELL_SMM_NO_FANS];
79b1986c8eSArmin Wolf 	int *fan_nominal_speed[DELL_SMM_NO_FANS];
80ba04d73cSArmin Wolf };
81a5afba16SPali Rohár 
82a5afba16SPali Rohár MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
83149ed3d4SPali Rohár MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
84039ae585SPali Rohár MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
85a5afba16SPali Rohár MODULE_LICENSE("GPL");
86a5afba16SPali Rohár MODULE_ALIAS("i8k");
87a5afba16SPali Rohár 
88a5afba16SPali Rohár static bool force;
89a5afba16SPali Rohár module_param(force, bool, 0);
90a5afba16SPali Rohár MODULE_PARM_DESC(force, "Force loading without checking for supported models");
91a5afba16SPali Rohár 
92a5afba16SPali Rohár static bool ignore_dmi;
93a5afba16SPali Rohár module_param(ignore_dmi, bool, 0);
94a5afba16SPali Rohár MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
95a5afba16SPali Rohár 
96039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
977613663cSPali Rohár static bool restricted = true;
98a5afba16SPali Rohár module_param(restricted, bool, 0);
997613663cSPali Rohár MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)");
100a5afba16SPali Rohár 
101a5afba16SPali Rohár static bool power_status;
102a5afba16SPali Rohár module_param(power_status, bool, 0600);
1037613663cSPali Rohár MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)");
104039ae585SPali Rohár #endif
105a5afba16SPali Rohár 
106a5afba16SPali Rohár static uint fan_mult;
107a5afba16SPali Rohár module_param(fan_mult, uint, 0);
108a5afba16SPali Rohár MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
109a5afba16SPali Rohár 
110a5afba16SPali Rohár static uint fan_max;
111a5afba16SPali Rohár module_param(fan_max, uint, 0);
112a5afba16SPali Rohár MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
113a5afba16SPali Rohár 
114a5afba16SPali Rohár struct smm_regs {
115a5afba16SPali Rohár 	unsigned int eax;
116a5afba16SPali Rohár 	unsigned int ebx __packed;
117a5afba16SPali Rohár 	unsigned int ecx __packed;
118a5afba16SPali Rohár 	unsigned int edx __packed;
119a5afba16SPali Rohár 	unsigned int esi __packed;
120a5afba16SPali Rohár 	unsigned int edi __packed;
121a5afba16SPali Rohár };
122a5afba16SPali Rohár 
123deeba244SArmin Wolf static const char * const temp_labels[] = {
124deeba244SArmin Wolf 	"CPU",
125deeba244SArmin Wolf 	"GPU",
126deeba244SArmin Wolf 	"SODIMM",
127deeba244SArmin Wolf 	"Other",
128deeba244SArmin Wolf 	"Ambient",
129deeba244SArmin Wolf 	"Other",
130deeba244SArmin Wolf };
131deeba244SArmin Wolf 
132deeba244SArmin Wolf static const char * const fan_labels[] = {
133deeba244SArmin Wolf 	"Processor Fan",
134deeba244SArmin Wolf 	"Motherboard Fan",
135deeba244SArmin Wolf 	"Video Fan",
136deeba244SArmin Wolf 	"Power Supply Fan",
137deeba244SArmin Wolf 	"Chipset Fan",
138deeba244SArmin Wolf 	"Other Fan",
139deeba244SArmin Wolf };
140deeba244SArmin Wolf 
141deeba244SArmin Wolf static const char * const docking_labels[] = {
142deeba244SArmin Wolf 	"Docking Processor Fan",
143deeba244SArmin Wolf 	"Docking Motherboard Fan",
144deeba244SArmin Wolf 	"Docking Video Fan",
145deeba244SArmin Wolf 	"Docking Power Supply Fan",
146deeba244SArmin Wolf 	"Docking Chipset Fan",
147deeba244SArmin Wolf 	"Docking Other Fan",
148deeba244SArmin Wolf };
149deeba244SArmin Wolf 
150c9363cdfSArmin Wolf static inline const char __init *i8k_get_dmi_data(int field)
151a5afba16SPali Rohár {
152a5afba16SPali Rohár 	const char *dmi_data = dmi_get_system_info(field);
153a5afba16SPali Rohár 
154a5afba16SPali Rohár 	return dmi_data && *dmi_data ? dmi_data : "?";
155a5afba16SPali Rohár }
156a5afba16SPali Rohár 
157a5afba16SPali Rohár /*
158a5afba16SPali Rohár  * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
159a5afba16SPali Rohár  */
16027046a3fSJuergen Gross static int i8k_smm_func(void *par)
161a5afba16SPali Rohár {
1628713b4a4SArmin Wolf 	ktime_t calltime = ktime_get();
16327046a3fSJuergen Gross 	struct smm_regs *regs = par;
164a5afba16SPali Rohár 	int eax = regs->eax;
1659d58bec0SPali Rohár 	int ebx = regs->ebx;
1668713b4a4SArmin Wolf 	long long duration;
1678713b4a4SArmin Wolf 	int rc;
1689d58bec0SPali Rohár 
169a5afba16SPali Rohár 	/* SMM requires CPU 0 */
17027046a3fSJuergen Gross 	if (smp_processor_id() != 0)
17127046a3fSJuergen Gross 		return -EBUSY;
172a5afba16SPali Rohár 
173a5afba16SPali Rohár #if defined(CONFIG_X86_64)
174a5afba16SPali Rohár 	asm volatile("pushq %%rax\n\t"
175a5afba16SPali Rohár 		"movl 0(%%rax),%%edx\n\t"
176a5afba16SPali Rohár 		"pushq %%rdx\n\t"
177a5afba16SPali Rohár 		"movl 4(%%rax),%%ebx\n\t"
178a5afba16SPali Rohár 		"movl 8(%%rax),%%ecx\n\t"
179a5afba16SPali Rohár 		"movl 12(%%rax),%%edx\n\t"
180a5afba16SPali Rohár 		"movl 16(%%rax),%%esi\n\t"
181a5afba16SPali Rohár 		"movl 20(%%rax),%%edi\n\t"
182a5afba16SPali Rohár 		"popq %%rax\n\t"
183a5afba16SPali Rohár 		"out %%al,$0xb2\n\t"
184a5afba16SPali Rohár 		"out %%al,$0x84\n\t"
185a5afba16SPali Rohár 		"xchgq %%rax,(%%rsp)\n\t"
186a5afba16SPali Rohár 		"movl %%ebx,4(%%rax)\n\t"
187a5afba16SPali Rohár 		"movl %%ecx,8(%%rax)\n\t"
188a5afba16SPali Rohár 		"movl %%edx,12(%%rax)\n\t"
189a5afba16SPali Rohár 		"movl %%esi,16(%%rax)\n\t"
190a5afba16SPali Rohár 		"movl %%edi,20(%%rax)\n\t"
191a5afba16SPali Rohár 		"popq %%rdx\n\t"
192a5afba16SPali Rohár 		"movl %%edx,0(%%rax)\n\t"
193a5afba16SPali Rohár 		"pushfq\n\t"
194a5afba16SPali Rohár 		"popq %%rax\n\t"
195a5afba16SPali Rohár 		"andl $1,%%eax\n"
196a5afba16SPali Rohár 		: "=a"(rc)
197a5afba16SPali Rohár 		:    "a"(regs)
198a5afba16SPali Rohár 		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
199a5afba16SPali Rohár #else
200a5afba16SPali Rohár 	asm volatile("pushl %%eax\n\t"
201a5afba16SPali Rohár 	    "movl 0(%%eax),%%edx\n\t"
202a5afba16SPali Rohár 	    "push %%edx\n\t"
203a5afba16SPali Rohár 	    "movl 4(%%eax),%%ebx\n\t"
204a5afba16SPali Rohár 	    "movl 8(%%eax),%%ecx\n\t"
205a5afba16SPali Rohár 	    "movl 12(%%eax),%%edx\n\t"
206a5afba16SPali Rohár 	    "movl 16(%%eax),%%esi\n\t"
207a5afba16SPali Rohár 	    "movl 20(%%eax),%%edi\n\t"
208a5afba16SPali Rohár 	    "popl %%eax\n\t"
209a5afba16SPali Rohár 	    "out %%al,$0xb2\n\t"
210a5afba16SPali Rohár 	    "out %%al,$0x84\n\t"
211a5afba16SPali Rohár 	    "xchgl %%eax,(%%esp)\n\t"
212a5afba16SPali Rohár 	    "movl %%ebx,4(%%eax)\n\t"
213a5afba16SPali Rohár 	    "movl %%ecx,8(%%eax)\n\t"
214a5afba16SPali Rohár 	    "movl %%edx,12(%%eax)\n\t"
215a5afba16SPali Rohár 	    "movl %%esi,16(%%eax)\n\t"
216a5afba16SPali Rohár 	    "movl %%edi,20(%%eax)\n\t"
217a5afba16SPali Rohár 	    "popl %%edx\n\t"
218a5afba16SPali Rohár 	    "movl %%edx,0(%%eax)\n\t"
219a5afba16SPali Rohár 	    "lahf\n\t"
220a5afba16SPali Rohár 	    "shrl $8,%%eax\n\t"
221a5afba16SPali Rohár 	    "andl $1,%%eax\n"
222a5afba16SPali Rohár 	    : "=a"(rc)
223a5afba16SPali Rohár 	    :    "a"(regs)
224a5afba16SPali Rohár 	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
225a5afba16SPali Rohár #endif
226a5afba16SPali Rohár 	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
227a5afba16SPali Rohár 		rc = -EINVAL;
228a5afba16SPali Rohár 
2298713b4a4SArmin Wolf 	duration = ktime_us_delta(ktime_get(), calltime);
2308713b4a4SArmin Wolf 	pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x  (took %7lld usecs)\n", eax, ebx,
2319d58bec0SPali Rohár 		 (rc ? 0xffff : regs->eax & 0xffff), duration);
2329d58bec0SPali Rohár 
233a5afba16SPali Rohár 	return rc;
234a5afba16SPali Rohár }
235a5afba16SPali Rohár 
236a5afba16SPali Rohár /*
23727046a3fSJuergen Gross  * Call the System Management Mode BIOS.
23827046a3fSJuergen Gross  */
23927046a3fSJuergen Gross static int i8k_smm(struct smm_regs *regs)
24027046a3fSJuergen Gross {
24127046a3fSJuergen Gross 	int ret;
24227046a3fSJuergen Gross 
243e104d530SSebastian Andrzej Siewior 	cpus_read_lock();
24427046a3fSJuergen Gross 	ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
245e104d530SSebastian Andrzej Siewior 	cpus_read_unlock();
24627046a3fSJuergen Gross 
24727046a3fSJuergen Gross 	return ret;
24827046a3fSJuergen Gross }
24927046a3fSJuergen Gross 
25027046a3fSJuergen Gross /*
251a5afba16SPali Rohár  * Read the fan status.
252a5afba16SPali Rohár  */
253ba04d73cSArmin Wolf static int i8k_get_fan_status(const struct dell_smm_data *data, int fan)
254a5afba16SPali Rohár {
255a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
256a5afba16SPali Rohár 
257ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
258f480ea90SPali Rohár 		return -EINVAL;
259f480ea90SPali Rohár 
260a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
261a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
262a5afba16SPali Rohár }
263a5afba16SPali Rohár 
264a5afba16SPali Rohár /*
265a5afba16SPali Rohár  * Read the fan speed in RPM.
266a5afba16SPali Rohár  */
267ba04d73cSArmin Wolf static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan)
268a5afba16SPali Rohár {
269a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
270a5afba16SPali Rohár 
271ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
272f480ea90SPali Rohár 		return -EINVAL;
273f480ea90SPali Rohár 
274a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
275ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
276a5afba16SPali Rohár }
277a5afba16SPali Rohár 
278a5afba16SPali Rohár /*
279a5afba16SPali Rohár  * Read the fan type.
280a5afba16SPali Rohár  */
281ba04d73cSArmin Wolf static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan)
282a5afba16SPali Rohár {
283a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
284a5afba16SPali Rohár 
285ba04d73cSArmin Wolf 	if (data->disallow_fan_support || data->disallow_fan_type_call)
2862744d2fdSPali Rohár 		return -EINVAL;
2872744d2fdSPali Rohár 
288a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
289a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
290a5afba16SPali Rohár }
291a5afba16SPali Rohár 
292ba04d73cSArmin Wolf static int i8k_get_fan_type(struct dell_smm_data *data, int fan)
2935ce91714SPali Rohár {
2945ce91714SPali Rohár 	/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
295deeba244SArmin Wolf 	if (data->fan_type[fan] == INT_MIN)
296deeba244SArmin Wolf 		data->fan_type[fan] = _i8k_get_fan_type(data, fan);
2975ce91714SPali Rohár 
298deeba244SArmin Wolf 	return data->fan_type[fan];
2995ce91714SPali Rohár }
3005ce91714SPali Rohár 
301a5afba16SPali Rohár /*
302a5afba16SPali Rohár  * Read the fan nominal rpm for specific fan speed.
303a5afba16SPali Rohár  */
304782a99c1SArmin Wolf static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed)
305a5afba16SPali Rohár {
306a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
307a5afba16SPali Rohár 
308ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
309f480ea90SPali Rohár 		return -EINVAL;
310f480ea90SPali Rohár 
311a5afba16SPali Rohár 	regs.ebx = (fan & 0xff) | (speed << 8);
312ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
313a5afba16SPali Rohár }
314a5afba16SPali Rohár 
315a5afba16SPali Rohár /*
316afe45277SGiovanni Mascellani  * Enable or disable automatic BIOS fan control support
317afe45277SGiovanni Mascellani  */
318ba04d73cSArmin Wolf static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable)
319afe45277SGiovanni Mascellani {
320afe45277SGiovanni Mascellani 	struct smm_regs regs = { };
321afe45277SGiovanni Mascellani 
322ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
323afe45277SGiovanni Mascellani 		return -EINVAL;
324afe45277SGiovanni Mascellani 
325ba04d73cSArmin Wolf 	regs.eax = enable ? data->auto_fan : data->manual_fan;
326afe45277SGiovanni Mascellani 	return i8k_smm(&regs);
327afe45277SGiovanni Mascellani }
328afe45277SGiovanni Mascellani 
329afe45277SGiovanni Mascellani /*
330c0d79987SArmin Wolf  * Set the fan speed (off, low, high, ...).
331a5afba16SPali Rohár  */
332ba04d73cSArmin Wolf static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed)
333a5afba16SPali Rohár {
334a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
335a5afba16SPali Rohár 
336ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
337f480ea90SPali Rohár 		return -EINVAL;
338f480ea90SPali Rohár 
339ba04d73cSArmin Wolf 	speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
340a5afba16SPali Rohár 	regs.ebx = (fan & 0xff) | (speed << 8);
341a5afba16SPali Rohár 
342c0d79987SArmin Wolf 	return i8k_smm(&regs);
343a5afba16SPali Rohár }
344a5afba16SPali Rohár 
345deeba244SArmin Wolf static int __init i8k_get_temp_type(int sensor)
346a5afba16SPali Rohár {
347a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
348a5afba16SPali Rohár 
349a5afba16SPali Rohár 	regs.ebx = sensor & 0xff;
350a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
351a5afba16SPali Rohár }
352a5afba16SPali Rohár 
353a5afba16SPali Rohár /*
354a5afba16SPali Rohár  * Read the cpu temperature.
355a5afba16SPali Rohár  */
356a5afba16SPali Rohár static int _i8k_get_temp(int sensor)
357a5afba16SPali Rohár {
358a5afba16SPali Rohár 	struct smm_regs regs = {
359a5afba16SPali Rohár 		.eax = I8K_SMM_GET_TEMP,
360a5afba16SPali Rohár 		.ebx = sensor & 0xff,
361a5afba16SPali Rohár 	};
362a5afba16SPali Rohár 
363a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
364a5afba16SPali Rohár }
365a5afba16SPali Rohár 
366a5afba16SPali Rohár static int i8k_get_temp(int sensor)
367a5afba16SPali Rohár {
368a5afba16SPali Rohár 	int temp = _i8k_get_temp(sensor);
369a5afba16SPali Rohár 
370a5afba16SPali Rohár 	/*
371a5afba16SPali Rohár 	 * Sometimes the temperature sensor returns 0x99, which is out of range.
372a5afba16SPali Rohár 	 * In this case we retry (once) before returning an error.
373a5afba16SPali Rohár 	 # 1003655137 00000058 00005a4b
374a5afba16SPali Rohár 	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
375a5afba16SPali Rohár 	 # 1003655139 00000054 00005c52
376a5afba16SPali Rohár 	 */
377a5afba16SPali Rohár 	if (temp == 0x99) {
378a5afba16SPali Rohár 		msleep(100);
379a5afba16SPali Rohár 		temp = _i8k_get_temp(sensor);
380a5afba16SPali Rohár 	}
381a5afba16SPali Rohár 	/*
382a5afba16SPali Rohár 	 * Return -ENODATA for all invalid temperatures.
383a5afba16SPali Rohár 	 *
384a5afba16SPali Rohár 	 * Known instances are the 0x99 value as seen above as well as
385a5afba16SPali Rohár 	 * 0xc1 (193), which may be returned when trying to read the GPU
386a5afba16SPali Rohár 	 * temperature if the system supports a GPU and it is currently
387a5afba16SPali Rohár 	 * turned off.
388a5afba16SPali Rohár 	 */
389a5afba16SPali Rohár 	if (temp > I8K_MAX_TEMP)
390a5afba16SPali Rohár 		return -ENODATA;
391a5afba16SPali Rohár 
392a5afba16SPali Rohár 	return temp;
393a5afba16SPali Rohár }
394a5afba16SPali Rohár 
395c9363cdfSArmin Wolf static int __init i8k_get_dell_signature(int req_fn)
396a5afba16SPali Rohár {
397a5afba16SPali Rohár 	struct smm_regs regs = { .eax = req_fn, };
398a5afba16SPali Rohár 	int rc;
399a5afba16SPali Rohár 
400a5afba16SPali Rohár 	rc = i8k_smm(&regs);
401a5afba16SPali Rohár 	if (rc < 0)
402a5afba16SPali Rohár 		return rc;
403a5afba16SPali Rohár 
404a5afba16SPali Rohár 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
405a5afba16SPali Rohár }
406a5afba16SPali Rohár 
407039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
408039ae585SPali Rohár 
409039ae585SPali Rohár /*
410039ae585SPali Rohár  * Read the Fn key status.
411039ae585SPali Rohár  */
412039ae585SPali Rohár static int i8k_get_fn_status(void)
413039ae585SPali Rohár {
414039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
415039ae585SPali Rohár 	int rc;
416039ae585SPali Rohár 
417039ae585SPali Rohár 	rc = i8k_smm(&regs);
418039ae585SPali Rohár 	if (rc < 0)
419039ae585SPali Rohár 		return rc;
420039ae585SPali Rohár 
421039ae585SPali Rohár 	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
422039ae585SPali Rohár 	case I8K_FN_UP:
423039ae585SPali Rohár 		return I8K_VOL_UP;
424039ae585SPali Rohár 	case I8K_FN_DOWN:
425039ae585SPali Rohár 		return I8K_VOL_DOWN;
426039ae585SPali Rohár 	case I8K_FN_MUTE:
427039ae585SPali Rohár 		return I8K_VOL_MUTE;
428039ae585SPali Rohár 	default:
429039ae585SPali Rohár 		return 0;
430039ae585SPali Rohár 	}
431039ae585SPali Rohár }
432039ae585SPali Rohár 
433039ae585SPali Rohár /*
434039ae585SPali Rohár  * Read the power status.
435039ae585SPali Rohár  */
436039ae585SPali Rohár static int i8k_get_power_status(void)
437039ae585SPali Rohár {
438039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
439039ae585SPali Rohár 	int rc;
440039ae585SPali Rohár 
441039ae585SPali Rohár 	rc = i8k_smm(&regs);
442039ae585SPali Rohár 	if (rc < 0)
443039ae585SPali Rohár 		return rc;
444039ae585SPali Rohár 
445039ae585SPali Rohár 	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
446039ae585SPali Rohár }
447039ae585SPali Rohár 
448039ae585SPali Rohár /*
449039ae585SPali Rohár  * Procfs interface
450039ae585SPali Rohár  */
451039ae585SPali Rohár 
452a5afba16SPali Rohár static int
453ba04d73cSArmin Wolf i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg)
454a5afba16SPali Rohár {
455a5afba16SPali Rohár 	int val = 0;
456c0d79987SArmin Wolf 	int speed, err;
457a5afba16SPali Rohár 	unsigned char buff[16];
458a5afba16SPali Rohár 	int __user *argp = (int __user *)arg;
459a5afba16SPali Rohár 
460a5afba16SPali Rohár 	if (!argp)
461a5afba16SPali Rohár 		return -EINVAL;
462a5afba16SPali Rohár 
463a5afba16SPali Rohár 	switch (cmd) {
464a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
465ba04d73cSArmin Wolf 		if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) ||
466ba04d73cSArmin Wolf 		    !isdigit(data->bios_version[2]))
467053ea640SPali Rohár 			return -EINVAL;
468053ea640SPali Rohár 
469ba04d73cSArmin Wolf 		val = (data->bios_version[0] << 16) |
470ba04d73cSArmin Wolf 				(data->bios_version[1] << 8) | data->bios_version[2];
471a5afba16SPali Rohár 		break;
472a5afba16SPali Rohár 
473a5afba16SPali Rohár 	case I8K_MACHINE_ID:
4747613663cSPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
4757613663cSPali Rohár 			return -EPERM;
4767613663cSPali Rohár 
47738c5b0ddSArmin Wolf 		strscpy_pad(buff, data->bios_machineid, sizeof(buff));
478a5afba16SPali Rohár 		break;
479a5afba16SPali Rohár 
480a5afba16SPali Rohár 	case I8K_FN_STATUS:
481a5afba16SPali Rohár 		val = i8k_get_fn_status();
482a5afba16SPali Rohár 		break;
483a5afba16SPali Rohár 
484a5afba16SPali Rohár 	case I8K_POWER_STATUS:
485a5afba16SPali Rohár 		val = i8k_get_power_status();
486a5afba16SPali Rohár 		break;
487a5afba16SPali Rohár 
488a5afba16SPali Rohár 	case I8K_GET_TEMP:
489a5afba16SPali Rohár 		val = i8k_get_temp(0);
490a5afba16SPali Rohár 		break;
491a5afba16SPali Rohár 
492a5afba16SPali Rohár 	case I8K_GET_SPEED:
493a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
494a5afba16SPali Rohár 			return -EFAULT;
495a5afba16SPali Rohár 
496ba04d73cSArmin Wolf 		val = i8k_get_fan_speed(data, val);
497a5afba16SPali Rohár 		break;
498a5afba16SPali Rohár 
499a5afba16SPali Rohár 	case I8K_GET_FAN:
500a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
501a5afba16SPali Rohár 			return -EFAULT;
502a5afba16SPali Rohár 
503ba04d73cSArmin Wolf 		val = i8k_get_fan_status(data, val);
504a5afba16SPali Rohár 		break;
505a5afba16SPali Rohár 
506a5afba16SPali Rohár 	case I8K_SET_FAN:
507a5afba16SPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
508a5afba16SPali Rohár 			return -EPERM;
509a5afba16SPali Rohár 
510a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
511a5afba16SPali Rohár 			return -EFAULT;
512a5afba16SPali Rohár 
513a5afba16SPali Rohár 		if (copy_from_user(&speed, argp + 1, sizeof(int)))
514a5afba16SPali Rohár 			return -EFAULT;
515a5afba16SPali Rohár 
516c0d79987SArmin Wolf 		err = i8k_set_fan(data, val, speed);
517c0d79987SArmin Wolf 		if (err < 0)
518c0d79987SArmin Wolf 			return err;
519c0d79987SArmin Wolf 
520c0d79987SArmin Wolf 		val = i8k_get_fan_status(data, val);
521a5afba16SPali Rohár 		break;
522a5afba16SPali Rohár 
523a5afba16SPali Rohár 	default:
524e64325e8SArmin Wolf 		return -ENOIOCTLCMD;
525a5afba16SPali Rohár 	}
526a5afba16SPali Rohár 
527a5afba16SPali Rohár 	if (val < 0)
528a5afba16SPali Rohár 		return val;
529a5afba16SPali Rohár 
530a5afba16SPali Rohár 	switch (cmd) {
531a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
532a5afba16SPali Rohár 		if (copy_to_user(argp, &val, 4))
533a5afba16SPali Rohár 			return -EFAULT;
534a5afba16SPali Rohár 
535a5afba16SPali Rohár 		break;
536a5afba16SPali Rohár 	case I8K_MACHINE_ID:
537a5afba16SPali Rohár 		if (copy_to_user(argp, buff, 16))
538a5afba16SPali Rohár 			return -EFAULT;
539a5afba16SPali Rohár 
540a5afba16SPali Rohár 		break;
541a5afba16SPali Rohár 	default:
542a5afba16SPali Rohár 		if (copy_to_user(argp, &val, sizeof(int)))
543a5afba16SPali Rohár 			return -EFAULT;
544a5afba16SPali Rohár 
545a5afba16SPali Rohár 		break;
546a5afba16SPali Rohár 	}
547a5afba16SPali Rohár 
548a5afba16SPali Rohár 	return 0;
549a5afba16SPali Rohár }
550a5afba16SPali Rohár 
551a5afba16SPali Rohár static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
552a5afba16SPali Rohár {
553ba04d73cSArmin Wolf 	struct dell_smm_data *data = PDE_DATA(file_inode(fp));
554a5afba16SPali Rohár 	long ret;
555a5afba16SPali Rohár 
556ba04d73cSArmin Wolf 	mutex_lock(&data->i8k_mutex);
557ba04d73cSArmin Wolf 	ret = i8k_ioctl_unlocked(fp, data, cmd, arg);
558ba04d73cSArmin Wolf 	mutex_unlock(&data->i8k_mutex);
559a5afba16SPali Rohár 
560a5afba16SPali Rohár 	return ret;
561a5afba16SPali Rohár }
562a5afba16SPali Rohár 
563a5afba16SPali Rohár /*
564a5afba16SPali Rohár  * Print the information for /proc/i8k.
565a5afba16SPali Rohár  */
566a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset)
567a5afba16SPali Rohár {
568ba04d73cSArmin Wolf 	struct dell_smm_data *data = seq->private;
569a5afba16SPali Rohár 	int fn_key, cpu_temp, ac_power;
570a5afba16SPali Rohár 	int left_fan, right_fan, left_speed, right_speed;
571a5afba16SPali Rohár 
572a5afba16SPali Rohár 	cpu_temp	= i8k_get_temp(0);				/* 11100 µs */
573ba04d73cSArmin Wolf 	left_fan	= i8k_get_fan_status(data, I8K_FAN_LEFT);	/*   580 µs */
574ba04d73cSArmin Wolf 	right_fan	= i8k_get_fan_status(data, I8K_FAN_RIGHT);	/*   580 µs */
575ba04d73cSArmin Wolf 	left_speed	= i8k_get_fan_speed(data, I8K_FAN_LEFT);	/*   580 µs */
576ba04d73cSArmin Wolf 	right_speed	= i8k_get_fan_speed(data, I8K_FAN_RIGHT);	/*   580 µs */
577a5afba16SPali Rohár 	fn_key		= i8k_get_fn_status();				/*   750 µs */
578a5afba16SPali Rohár 	if (power_status)
579a5afba16SPali Rohár 		ac_power = i8k_get_power_status();			/* 14700 µs */
580a5afba16SPali Rohár 	else
581a5afba16SPali Rohár 		ac_power = -1;
582a5afba16SPali Rohár 
583a5afba16SPali Rohár 	/*
584a5afba16SPali Rohár 	 * Info:
585a5afba16SPali Rohár 	 *
586a5afba16SPali Rohár 	 * 1)  Format version (this will change if format changes)
587a5afba16SPali Rohár 	 * 2)  BIOS version
588a5afba16SPali Rohár 	 * 3)  BIOS machine ID
589a5afba16SPali Rohár 	 * 4)  Cpu temperature
590a5afba16SPali Rohár 	 * 5)  Left fan status
591a5afba16SPali Rohár 	 * 6)  Right fan status
592a5afba16SPali Rohár 	 * 7)  Left fan speed
593a5afba16SPali Rohár 	 * 8)  Right fan speed
594a5afba16SPali Rohár 	 * 9)  AC power
595a5afba16SPali Rohár 	 * 10) Fn Key status
596a5afba16SPali Rohár 	 */
597a5afba16SPali Rohár 	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
598a5afba16SPali Rohár 		   I8K_PROC_FMT,
599ba04d73cSArmin Wolf 		   data->bios_version,
600ba04d73cSArmin Wolf 		   (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid,
601a5afba16SPali Rohár 		   cpu_temp,
602a5afba16SPali Rohár 		   left_fan, right_fan, left_speed, right_speed,
603a5afba16SPali Rohár 		   ac_power, fn_key);
604a5afba16SPali Rohár 
605a5afba16SPali Rohár 	return 0;
606a5afba16SPali Rohár }
607a5afba16SPali Rohár 
608a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file)
609a5afba16SPali Rohár {
610ba04d73cSArmin Wolf 	return single_open(file, i8k_proc_show, PDE_DATA(inode));
611a5afba16SPali Rohár }
612a5afba16SPali Rohár 
61397a32539SAlexey Dobriyan static const struct proc_ops i8k_proc_ops = {
61497a32539SAlexey Dobriyan 	.proc_open	= i8k_open_fs,
61597a32539SAlexey Dobriyan 	.proc_read	= seq_read,
61697a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
61797a32539SAlexey Dobriyan 	.proc_release	= single_release,
61897a32539SAlexey Dobriyan 	.proc_ioctl	= i8k_ioctl,
619039ae585SPali Rohár };
620039ae585SPali Rohár 
621a2cb66b4SArmin Wolf static void i8k_exit_procfs(void *param)
622039ae585SPali Rohár {
623039ae585SPali Rohár 	remove_proc_entry("i8k", NULL);
624039ae585SPali Rohár }
625039ae585SPali Rohár 
626a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
627039ae585SPali Rohár {
628ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
629ba04d73cSArmin Wolf 
630*dbd3e6eaSArmin Wolf 	/* Only register exit function if creation was successful */
631*dbd3e6eaSArmin Wolf 	if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data))
632a2cb66b4SArmin Wolf 		devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
633039ae585SPali Rohár }
634039ae585SPali Rohár 
635a2cb66b4SArmin Wolf #else
636a2cb66b4SArmin Wolf 
637a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
638039ae585SPali Rohár {
639039ae585SPali Rohár }
640039ae585SPali Rohár 
641039ae585SPali Rohár #endif
642a5afba16SPali Rohár 
643a5afba16SPali Rohár /*
644a5afba16SPali Rohár  * Hwmon interface
645a5afba16SPali Rohár  */
646a5afba16SPali Rohár 
647deeba244SArmin Wolf static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
648deeba244SArmin Wolf 				   int channel)
649a5afba16SPali Rohár {
650deeba244SArmin Wolf 	const struct dell_smm_data *data = drvdata;
651a5afba16SPali Rohár 
652deeba244SArmin Wolf 	switch (type) {
653deeba244SArmin Wolf 	case hwmon_temp:
654deeba244SArmin Wolf 		switch (attr) {
655deeba244SArmin Wolf 		case hwmon_temp_input:
656deeba244SArmin Wolf 		case hwmon_temp_label:
657deeba244SArmin Wolf 			if (data->temp_type[channel] >= 0)
658deeba244SArmin Wolf 				return 0444;
659deeba244SArmin Wolf 
660deeba244SArmin Wolf 			break;
661deeba244SArmin Wolf 		default:
662deeba244SArmin Wolf 			break;
663deeba244SArmin Wolf 		}
664deeba244SArmin Wolf 		break;
665deeba244SArmin Wolf 	case hwmon_fan:
666deeba244SArmin Wolf 		if (data->disallow_fan_support)
667deeba244SArmin Wolf 			break;
668deeba244SArmin Wolf 
669deeba244SArmin Wolf 		switch (attr) {
670deeba244SArmin Wolf 		case hwmon_fan_input:
671deeba244SArmin Wolf 			if (data->fan[channel])
672deeba244SArmin Wolf 				return 0444;
673deeba244SArmin Wolf 
674deeba244SArmin Wolf 			break;
675deeba244SArmin Wolf 		case hwmon_fan_label:
676deeba244SArmin Wolf 			if (data->fan[channel] && !data->disallow_fan_type_call)
677deeba244SArmin Wolf 				return 0444;
678deeba244SArmin Wolf 
679deeba244SArmin Wolf 			break;
680b1986c8eSArmin Wolf 		case hwmon_fan_min:
681b1986c8eSArmin Wolf 		case hwmon_fan_max:
682b1986c8eSArmin Wolf 		case hwmon_fan_target:
683b1986c8eSArmin Wolf 			if (data->fan_nominal_speed[channel])
684b1986c8eSArmin Wolf 				return 0444;
685b1986c8eSArmin Wolf 
686b1986c8eSArmin Wolf 			break;
687deeba244SArmin Wolf 		default:
688deeba244SArmin Wolf 			break;
689deeba244SArmin Wolf 		}
690deeba244SArmin Wolf 		break;
691deeba244SArmin Wolf 	case hwmon_pwm:
692deeba244SArmin Wolf 		if (data->disallow_fan_support)
693deeba244SArmin Wolf 			break;
694deeba244SArmin Wolf 
695deeba244SArmin Wolf 		switch (attr) {
696deeba244SArmin Wolf 		case hwmon_pwm_input:
697deeba244SArmin Wolf 			if (data->fan[channel])
698deeba244SArmin Wolf 				return 0644;
699deeba244SArmin Wolf 
700deeba244SArmin Wolf 			break;
701deeba244SArmin Wolf 		case hwmon_pwm_enable:
702deeba244SArmin Wolf 			if (data->auto_fan)
703deeba244SArmin Wolf 				/*
704deeba244SArmin Wolf 				 * There is no command for retrieve the current status
705deeba244SArmin Wolf 				 * from BIOS, and userspace/firmware itself can change
706deeba244SArmin Wolf 				 * it.
707deeba244SArmin Wolf 				 * Thus we can only provide write-only access for now.
708deeba244SArmin Wolf 				 */
709deeba244SArmin Wolf 				return 0200;
710deeba244SArmin Wolf 
711deeba244SArmin Wolf 			break;
712deeba244SArmin Wolf 		default:
713deeba244SArmin Wolf 			break;
714deeba244SArmin Wolf 		}
715deeba244SArmin Wolf 		break;
716deeba244SArmin Wolf 	default:
717deeba244SArmin Wolf 		break;
718a5afba16SPali Rohár 	}
719a5afba16SPali Rohár 
720deeba244SArmin Wolf 	return 0;
721a5afba16SPali Rohár }
722a5afba16SPali Rohár 
723deeba244SArmin Wolf static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
724deeba244SArmin Wolf 			 long *val)
725a5afba16SPali Rohár {
726ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
727deeba244SArmin Wolf 	int ret;
728a5afba16SPali Rohár 
729deeba244SArmin Wolf 	switch (type) {
730deeba244SArmin Wolf 	case hwmon_temp:
731deeba244SArmin Wolf 		switch (attr) {
732deeba244SArmin Wolf 		case hwmon_temp_input:
733deeba244SArmin Wolf 			ret = i8k_get_temp(channel);
734deeba244SArmin Wolf 			if (ret < 0)
735deeba244SArmin Wolf 				return ret;
736deeba244SArmin Wolf 
737deeba244SArmin Wolf 			*val = ret * 1000;
738deeba244SArmin Wolf 
739deeba244SArmin Wolf 			return 0;
740deeba244SArmin Wolf 		default:
741deeba244SArmin Wolf 			break;
742deeba244SArmin Wolf 		}
743deeba244SArmin Wolf 		break;
744deeba244SArmin Wolf 	case hwmon_fan:
745deeba244SArmin Wolf 		switch (attr) {
746deeba244SArmin Wolf 		case hwmon_fan_input:
747deeba244SArmin Wolf 			ret = i8k_get_fan_speed(data, channel);
748deeba244SArmin Wolf 			if (ret < 0)
749deeba244SArmin Wolf 				return ret;
750deeba244SArmin Wolf 
751deeba244SArmin Wolf 			*val = ret;
752deeba244SArmin Wolf 
753deeba244SArmin Wolf 			return 0;
754b1986c8eSArmin Wolf 		case hwmon_fan_min:
755b1986c8eSArmin Wolf 			*val = data->fan_nominal_speed[channel][0];
756b1986c8eSArmin Wolf 
757b1986c8eSArmin Wolf 			return 0;
758b1986c8eSArmin Wolf 		case hwmon_fan_max:
759b1986c8eSArmin Wolf 			*val = data->fan_nominal_speed[channel][data->i8k_fan_max];
760b1986c8eSArmin Wolf 
761b1986c8eSArmin Wolf 			return 0;
762b1986c8eSArmin Wolf 		case hwmon_fan_target:
763b1986c8eSArmin Wolf 			ret = i8k_get_fan_status(data, channel);
764b1986c8eSArmin Wolf 			if (ret < 0)
765b1986c8eSArmin Wolf 				return ret;
766b1986c8eSArmin Wolf 
767b1986c8eSArmin Wolf 			if (ret > data->i8k_fan_max)
768b1986c8eSArmin Wolf 				ret = data->i8k_fan_max;
769b1986c8eSArmin Wolf 
770b1986c8eSArmin Wolf 			*val = data->fan_nominal_speed[channel][ret];
771b1986c8eSArmin Wolf 
772b1986c8eSArmin Wolf 			return 0;
773deeba244SArmin Wolf 		default:
774deeba244SArmin Wolf 			break;
775deeba244SArmin Wolf 		}
776deeba244SArmin Wolf 		break;
777deeba244SArmin Wolf 	case hwmon_pwm:
778deeba244SArmin Wolf 		switch (attr) {
779deeba244SArmin Wolf 		case hwmon_pwm_input:
780deeba244SArmin Wolf 			ret = i8k_get_fan_status(data, channel);
781deeba244SArmin Wolf 			if (ret < 0)
782deeba244SArmin Wolf 				return ret;
783deeba244SArmin Wolf 
784deeba244SArmin Wolf 			*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
785deeba244SArmin Wolf 
786deeba244SArmin Wolf 			return 0;
787deeba244SArmin Wolf 		default:
788deeba244SArmin Wolf 			break;
789deeba244SArmin Wolf 		}
790deeba244SArmin Wolf 		break;
791deeba244SArmin Wolf 	default:
792deeba244SArmin Wolf 		break;
793deeba244SArmin Wolf 	}
794deeba244SArmin Wolf 
795deeba244SArmin Wolf 	return -EOPNOTSUPP;
796deeba244SArmin Wolf }
797deeba244SArmin Wolf 
798deeba244SArmin Wolf static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel)
799deeba244SArmin Wolf {
800deeba244SArmin Wolf 	bool dock = false;
801deeba244SArmin Wolf 	int type = i8k_get_fan_type(data, channel);
802deeba244SArmin Wolf 
803a5afba16SPali Rohár 	if (type < 0)
804deeba244SArmin Wolf 		return ERR_PTR(type);
805a5afba16SPali Rohár 
806a5afba16SPali Rohár 	if (type & 0x10) {
807a5afba16SPali Rohár 		dock = true;
808a5afba16SPali Rohár 		type &= 0x0F;
809a5afba16SPali Rohár 	}
810a5afba16SPali Rohár 
811deeba244SArmin Wolf 	if (type >= ARRAY_SIZE(fan_labels))
812deeba244SArmin Wolf 		type = ARRAY_SIZE(fan_labels) - 1;
813a5afba16SPali Rohár 
814deeba244SArmin Wolf 	return dock ? docking_labels[type] : fan_labels[type];
815a5afba16SPali Rohár }
816a5afba16SPali Rohár 
817deeba244SArmin Wolf static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
818deeba244SArmin Wolf 				int channel, const char **str)
819a5afba16SPali Rohár {
820ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
821a5afba16SPali Rohár 
822deeba244SArmin Wolf 	switch (type) {
823deeba244SArmin Wolf 	case hwmon_temp:
824deeba244SArmin Wolf 		switch (attr) {
825deeba244SArmin Wolf 		case hwmon_temp_label:
826deeba244SArmin Wolf 			*str = temp_labels[data->temp_type[channel]];
827deeba244SArmin Wolf 			return 0;
828deeba244SArmin Wolf 		default:
829deeba244SArmin Wolf 			break;
830deeba244SArmin Wolf 		}
831deeba244SArmin Wolf 		break;
832deeba244SArmin Wolf 	case hwmon_fan:
833deeba244SArmin Wolf 		switch (attr) {
834deeba244SArmin Wolf 		case hwmon_fan_label:
835deeba244SArmin Wolf 			*str = dell_smm_fan_label(data, channel);
836deeba244SArmin Wolf 			return PTR_ERR_OR_ZERO(*str);
837deeba244SArmin Wolf 		default:
838deeba244SArmin Wolf 			break;
839deeba244SArmin Wolf 		}
840deeba244SArmin Wolf 		break;
841deeba244SArmin Wolf 	default:
842deeba244SArmin Wolf 		break;
843a5afba16SPali Rohár 	}
844a5afba16SPali Rohár 
845deeba244SArmin Wolf 	return -EOPNOTSUPP;
846a5afba16SPali Rohár }
847a5afba16SPali Rohár 
848deeba244SArmin Wolf static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
849deeba244SArmin Wolf 			  long val)
850a5afba16SPali Rohár {
851ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
852deeba244SArmin Wolf 	unsigned long pwm;
853deeba244SArmin Wolf 	bool enable;
854a5afba16SPali Rohár 	int err;
855a5afba16SPali Rohár 
856deeba244SArmin Wolf 	switch (type) {
857deeba244SArmin Wolf 	case hwmon_pwm:
858deeba244SArmin Wolf 		switch (attr) {
859deeba244SArmin Wolf 		case hwmon_pwm_input:
860deeba244SArmin Wolf 			pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0,
861deeba244SArmin Wolf 					data->i8k_fan_max);
862a5afba16SPali Rohár 
863ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
864deeba244SArmin Wolf 			err = i8k_set_fan(data, channel, pwm);
865ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
866a5afba16SPali Rohár 
867deeba244SArmin Wolf 			if (err < 0)
868afe45277SGiovanni Mascellani 				return err;
869afe45277SGiovanni Mascellani 
870deeba244SArmin Wolf 			return 0;
871deeba244SArmin Wolf 		case hwmon_pwm_enable:
872deeba244SArmin Wolf 			if (!val)
873deeba244SArmin Wolf 				return -EINVAL;
874deeba244SArmin Wolf 
875afe45277SGiovanni Mascellani 			if (val == 1)
876afe45277SGiovanni Mascellani 				enable = false;
877afe45277SGiovanni Mascellani 			else
878deeba244SArmin Wolf 				enable = true;
879afe45277SGiovanni Mascellani 
880ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
881ba04d73cSArmin Wolf 			err = i8k_enable_fan_auto_mode(data, enable);
882ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
883afe45277SGiovanni Mascellani 
884deeba244SArmin Wolf 			if (err < 0)
885deeba244SArmin Wolf 				return err;
886deeba244SArmin Wolf 
887deeba244SArmin Wolf 			return 0;
888deeba244SArmin Wolf 		default:
889deeba244SArmin Wolf 			break;
890deeba244SArmin Wolf 		}
891deeba244SArmin Wolf 		break;
892deeba244SArmin Wolf 	default:
893deeba244SArmin Wolf 		break;
894afe45277SGiovanni Mascellani 	}
895afe45277SGiovanni Mascellani 
896deeba244SArmin Wolf 	return -EOPNOTSUPP;
897deeba244SArmin Wolf }
898a5afba16SPali Rohár 
899deeba244SArmin Wolf static const struct hwmon_ops dell_smm_ops = {
900deeba244SArmin Wolf 	.is_visible = dell_smm_is_visible,
901deeba244SArmin Wolf 	.read = dell_smm_read,
902deeba244SArmin Wolf 	.read_string = dell_smm_read_string,
903deeba244SArmin Wolf 	.write = dell_smm_write,
904deeba244SArmin Wolf };
905deeba244SArmin Wolf 
906deeba244SArmin Wolf static const struct hwmon_channel_info *dell_smm_info[] = {
907deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
908deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(temp,
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 			   HWMON_T_INPUT | HWMON_T_LABEL,
916deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
917deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
918deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL
919deeba244SArmin Wolf 			   ),
920deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(fan,
921b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
922b1986c8eSArmin Wolf 			   HWMON_F_TARGET,
923b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
924b1986c8eSArmin Wolf 			   HWMON_F_TARGET,
925b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
926b1986c8eSArmin Wolf 			   HWMON_F_TARGET
927deeba244SArmin Wolf 			   ),
928deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(pwm,
929deeba244SArmin Wolf 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
930deeba244SArmin Wolf 			   HWMON_PWM_INPUT,
931deeba244SArmin Wolf 			   HWMON_PWM_INPUT
932deeba244SArmin Wolf 			   ),
933a5afba16SPali Rohár 	NULL
934a5afba16SPali Rohár };
935a5afba16SPali Rohár 
936deeba244SArmin Wolf static const struct hwmon_chip_info dell_smm_chip_info = {
937deeba244SArmin Wolf 	.ops = &dell_smm_ops,
938deeba244SArmin Wolf 	.info = dell_smm_info,
939a5afba16SPali Rohár };
940a5afba16SPali Rohár 
9411492fa21SArmin Wolf static int __init dell_smm_init_hwmon(struct device *dev)
942a5afba16SPali Rohár {
943ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
944deeba244SArmin Wolf 	struct device *dell_smm_hwmon_dev;
945b1986c8eSArmin Wolf 	int i, state, err;
946a5afba16SPali Rohár 
947deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
948deeba244SArmin Wolf 		data->temp_type[i] = i8k_get_temp_type(i);
949deeba244SArmin Wolf 		if (data->temp_type[i] < 0)
950deeba244SArmin Wolf 			continue;
951a5afba16SPali Rohár 
952deeba244SArmin Wolf 		if (data->temp_type[i] >= ARRAY_SIZE(temp_labels))
953deeba244SArmin Wolf 			data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1;
954deeba244SArmin Wolf 	}
955deeba244SArmin Wolf 
956deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_FANS; i++) {
957deeba244SArmin Wolf 		data->fan_type[i] = INT_MIN;
958deeba244SArmin Wolf 		err = i8k_get_fan_status(data, i);
9595ce91714SPali Rohár 		if (err < 0)
960deeba244SArmin Wolf 			err = i8k_get_fan_type(data, i);
961b1986c8eSArmin Wolf 
962b1986c8eSArmin Wolf 		if (err < 0)
963b1986c8eSArmin Wolf 			continue;
964b1986c8eSArmin Wolf 
965deeba244SArmin Wolf 		data->fan[i] = true;
966b1986c8eSArmin Wolf 		data->fan_nominal_speed[i] = devm_kmalloc_array(dev, data->i8k_fan_max + 1,
967b1986c8eSArmin Wolf 								sizeof(*data->fan_nominal_speed[i]),
968b1986c8eSArmin Wolf 								GFP_KERNEL);
969b1986c8eSArmin Wolf 		if (!data->fan_nominal_speed[i])
970b1986c8eSArmin Wolf 			continue;
971b1986c8eSArmin Wolf 
972b1986c8eSArmin Wolf 		for (state = 0; state <= data->i8k_fan_max; state++) {
973b1986c8eSArmin Wolf 			err = i8k_get_fan_nominal_speed(data, i, state);
974b1986c8eSArmin Wolf 			if (err < 0) {
975b1986c8eSArmin Wolf 				/* Mark nominal speed table as invalid in case of error */
976b1986c8eSArmin Wolf 				devm_kfree(dev, data->fan_nominal_speed[i]);
977b1986c8eSArmin Wolf 				data->fan_nominal_speed[i] = NULL;
978b1986c8eSArmin Wolf 				break;
979b1986c8eSArmin Wolf 			}
980b1986c8eSArmin Wolf 			data->fan_nominal_speed[i][state] = err;
981b1986c8eSArmin Wolf 		}
982deeba244SArmin Wolf 	}
983a5afba16SPali Rohár 
984deeba244SArmin Wolf 	dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data,
985deeba244SArmin Wolf 								  &dell_smm_chip_info, NULL);
986a5afba16SPali Rohár 
987deeba244SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
988a5afba16SPali Rohár }
989a5afba16SPali Rohár 
990a5afba16SPali Rohár struct i8k_config_data {
991a5afba16SPali Rohár 	uint fan_mult;
992a5afba16SPali Rohár 	uint fan_max;
993a5afba16SPali Rohár };
994a5afba16SPali Rohár 
995a5afba16SPali Rohár enum i8k_configs {
996a5afba16SPali Rohár 	DELL_LATITUDE_D520,
997a5afba16SPali Rohár 	DELL_PRECISION_490,
998a5afba16SPali Rohár 	DELL_STUDIO,
999a5afba16SPali Rohár 	DELL_XPS,
1000a5afba16SPali Rohár };
1001a5afba16SPali Rohár 
1002927d89eeSArmin Wolf /*
1003927d89eeSArmin Wolf  * Only use for machines which need some special configuration
1004927d89eeSArmin Wolf  * in order to work correctly (e.g. if autoconfig fails on this machines).
1005927d89eeSArmin Wolf  */
1006927d89eeSArmin Wolf 
1007c510f6acSArmin Wolf static const struct i8k_config_data i8k_config_data[] __initconst = {
1008a5afba16SPali Rohár 	[DELL_LATITUDE_D520] = {
1009a5afba16SPali Rohár 		.fan_mult = 1,
1010a5afba16SPali Rohár 		.fan_max = I8K_FAN_TURBO,
1011a5afba16SPali Rohár 	},
1012a5afba16SPali Rohár 	[DELL_PRECISION_490] = {
1013a5afba16SPali Rohár 		.fan_mult = 1,
1014a5afba16SPali Rohár 		.fan_max = I8K_FAN_TURBO,
1015a5afba16SPali Rohár 	},
1016a5afba16SPali Rohár 	[DELL_STUDIO] = {
1017a5afba16SPali Rohár 		.fan_mult = 1,
1018a5afba16SPali Rohár 		.fan_max = I8K_FAN_HIGH,
1019a5afba16SPali Rohár 	},
1020a5afba16SPali Rohár 	[DELL_XPS] = {
1021a5afba16SPali Rohár 		.fan_mult = 1,
1022a5afba16SPali Rohár 		.fan_max = I8K_FAN_HIGH,
1023a5afba16SPali Rohár 	},
1024a5afba16SPali Rohár };
1025a5afba16SPali Rohár 
10266faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_dmi_table[] __initconst = {
1027a5afba16SPali Rohár 	{
1028a5afba16SPali Rohár 		.ident = "Dell Inspiron",
1029a5afba16SPali Rohár 		.matches = {
1030a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
1031a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
1032a5afba16SPali Rohár 		},
1033a5afba16SPali Rohár 	},
1034a5afba16SPali Rohár 	{
1035a5afba16SPali Rohár 		.ident = "Dell Latitude",
1036a5afba16SPali Rohár 		.matches = {
1037a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
1038a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1039a5afba16SPali Rohár 		},
1040a5afba16SPali Rohár 	},
1041a5afba16SPali Rohár 	{
1042a5afba16SPali Rohár 		.ident = "Dell Inspiron 2",
1043a5afba16SPali Rohár 		.matches = {
1044a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1045a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
1046a5afba16SPali Rohár 		},
1047a5afba16SPali Rohár 	},
1048a5afba16SPali Rohár 	{
1049a5afba16SPali Rohár 		.ident = "Dell Latitude D520",
1050a5afba16SPali Rohár 		.matches = {
1051a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1052a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
1053a5afba16SPali Rohár 		},
1054a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
1055a5afba16SPali Rohár 	},
1056a5afba16SPali Rohár 	{
1057a5afba16SPali Rohár 		.ident = "Dell Latitude 2",
1058a5afba16SPali Rohár 		.matches = {
1059a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1060a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1061a5afba16SPali Rohár 		},
1062a5afba16SPali Rohár 	},
1063a5afba16SPali Rohár 	{	/* UK Inspiron 6400  */
1064a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1065a5afba16SPali Rohár 		.matches = {
1066a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1067a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
1068a5afba16SPali Rohár 		},
1069a5afba16SPali Rohár 	},
1070a5afba16SPali Rohár 	{
1071a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1072a5afba16SPali Rohár 		.matches = {
1073a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1074a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
1075a5afba16SPali Rohár 		},
1076a5afba16SPali Rohár 	},
1077a5afba16SPali Rohár 	{
1078a5afba16SPali Rohár 		.ident = "Dell Precision 490",
1079a5afba16SPali Rohár 		.matches = {
1080a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1081a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME,
1082a5afba16SPali Rohár 				  "Precision WorkStation 490"),
1083a5afba16SPali Rohár 		},
1084a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
1085a5afba16SPali Rohár 	},
1086a5afba16SPali Rohár 	{
1087a5afba16SPali Rohár 		.ident = "Dell Precision",
1088a5afba16SPali Rohár 		.matches = {
1089a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1090a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
1091a5afba16SPali Rohár 		},
1092a5afba16SPali Rohár 	},
1093a5afba16SPali Rohár 	{
1094a5afba16SPali Rohár 		.ident = "Dell Vostro",
1095a5afba16SPali Rohár 		.matches = {
1096a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1097a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
1098a5afba16SPali Rohár 		},
1099a5afba16SPali Rohár 	},
1100a5afba16SPali Rohár 	{
1101a5afba16SPali Rohár 		.ident = "Dell Studio",
1102a5afba16SPali Rohár 		.matches = {
1103a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1104a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
1105a5afba16SPali Rohár 		},
1106a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
1107a5afba16SPali Rohár 	},
1108a5afba16SPali Rohár 	{
1109a5afba16SPali Rohár 		.ident = "Dell XPS M140",
1110a5afba16SPali Rohár 		.matches = {
1111a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1112a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
1113a5afba16SPali Rohár 		},
1114a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_XPS],
1115a5afba16SPali Rohár 	},
1116a4811b6cSPali Rohár 	{
1117b8a13e5eSThomas Hebb 		.ident = "Dell XPS",
1118a4811b6cSPali Rohár 		.matches = {
1119a4811b6cSPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1120b8a13e5eSThomas Hebb 			DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
1121162372b0SMichele Sorcinelli 		},
1122162372b0SMichele Sorcinelli 	},
1123a5afba16SPali Rohár 	{ }
1124a5afba16SPali Rohár };
1125a5afba16SPali Rohár 
1126a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
1127a5afba16SPali Rohár 
1128a4b45b25SPali Rohár /*
11292744d2fdSPali Rohár  * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
11302744d2fdSPali Rohár  * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
11312744d2fdSPali Rohár  * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
11322744d2fdSPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121
11336220f4ebSThorsten Leemhuis  */
11346faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = {
11352744d2fdSPali Rohár 	{
11366220f4ebSThorsten Leemhuis 		.ident = "Dell Studio XPS 8000",
11376220f4ebSThorsten Leemhuis 		.matches = {
11386220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11396220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"),
11406220f4ebSThorsten Leemhuis 		},
11416220f4ebSThorsten Leemhuis 	},
11426220f4ebSThorsten Leemhuis 	{
1143a4b45b25SPali Rohár 		.ident = "Dell Studio XPS 8100",
1144a4b45b25SPali Rohár 		.matches = {
1145a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1146a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"),
1147a4b45b25SPali Rohár 		},
1148a4b45b25SPali Rohár 	},
11492744d2fdSPali Rohár 	{
11502744d2fdSPali Rohár 		.ident = "Dell Inspiron 580",
11512744d2fdSPali Rohár 		.matches = {
11522744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11532744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "),
11542744d2fdSPali Rohár 		},
11552744d2fdSPali Rohár 	},
1156a4b45b25SPali Rohár 	{ }
1157a4b45b25SPali Rohár };
1158a4b45b25SPali Rohár 
1159a5afba16SPali Rohár /*
1160f480ea90SPali Rohár  * On some machines all fan related SMM functions implemented by Dell BIOS
1161f480ea90SPali Rohár  * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan
1162f480ea90SPali Rohár  * support for affected blacklisted Dell machines stay disabled.
1163f480ea90SPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
1164f480ea90SPali Rohár  */
1165c510f6acSArmin Wolf static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initconst = {
1166f480ea90SPali Rohár 	{
1167f480ea90SPali Rohár 		.ident = "Dell Inspiron 7720",
1168f480ea90SPali Rohár 		.matches = {
1169f480ea90SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1170f480ea90SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
1171f480ea90SPali Rohár 		},
1172f480ea90SPali Rohár 	},
11736fbc4232SOleksandr Natalenko 	{
11746fbc4232SOleksandr Natalenko 		.ident = "Dell Vostro 3360",
11756fbc4232SOleksandr Natalenko 		.matches = {
11766fbc4232SOleksandr Natalenko 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11776fbc4232SOleksandr Natalenko 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
11786fbc4232SOleksandr Natalenko 		},
11796fbc4232SOleksandr Natalenko 	},
1180536e0019SHelge Eichelberg 	{
1181536e0019SHelge Eichelberg 		.ident = "Dell XPS13 9333",
1182536e0019SHelge Eichelberg 		.matches = {
1183536e0019SHelge Eichelberg 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1184536e0019SHelge Eichelberg 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
1185536e0019SHelge Eichelberg 		},
1186536e0019SHelge Eichelberg 	},
11874008bc7dSThomas Hebb 	{
11884008bc7dSThomas Hebb 		.ident = "Dell XPS 15 L502X",
11894008bc7dSThomas Hebb 		.matches = {
11904008bc7dSThomas Hebb 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11914008bc7dSThomas Hebb 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"),
11924008bc7dSThomas Hebb 		},
11934008bc7dSThomas Hebb 	},
1194f480ea90SPali Rohár 	{ }
1195f480ea90SPali Rohár };
1196f480ea90SPali Rohár 
1197afe45277SGiovanni Mascellani struct i8k_fan_control_data {
1198afe45277SGiovanni Mascellani 	unsigned int manual_fan;
1199afe45277SGiovanni Mascellani 	unsigned int auto_fan;
1200afe45277SGiovanni Mascellani };
1201afe45277SGiovanni Mascellani 
1202afe45277SGiovanni Mascellani enum i8k_fan_controls {
1203afe45277SGiovanni Mascellani 	I8K_FAN_34A3_35A3,
1204afe45277SGiovanni Mascellani };
1205afe45277SGiovanni Mascellani 
1206c510f6acSArmin Wolf static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
1207afe45277SGiovanni Mascellani 	[I8K_FAN_34A3_35A3] = {
1208afe45277SGiovanni Mascellani 		.manual_fan = 0x34a3,
1209afe45277SGiovanni Mascellani 		.auto_fan = 0x35a3,
1210afe45277SGiovanni Mascellani 	},
1211afe45277SGiovanni Mascellani };
1212afe45277SGiovanni Mascellani 
1213c510f6acSArmin Wolf static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
1214afe45277SGiovanni Mascellani 	{
12150ca8bb2cSJeffrey Lin 		.ident = "Dell Latitude 5480",
12160ca8bb2cSJeffrey Lin 		.matches = {
12170ca8bb2cSJeffrey Lin 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
12180ca8bb2cSJeffrey Lin 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"),
12190ca8bb2cSJeffrey Lin 		},
12200ca8bb2cSJeffrey Lin 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
12210ca8bb2cSJeffrey Lin 	},
12220ca8bb2cSJeffrey Lin 	{
1223afe45277SGiovanni Mascellani 		.ident = "Dell Latitude E6440",
1224afe45277SGiovanni Mascellani 		.matches = {
1225afe45277SGiovanni Mascellani 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1226afe45277SGiovanni Mascellani 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
1227afe45277SGiovanni Mascellani 		},
1228afe45277SGiovanni Mascellani 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1229afe45277SGiovanni Mascellani 	},
1230807b8c29SSebastian Oechsle 	{
1231807b8c29SSebastian Oechsle 		.ident = "Dell Latitude E7440",
1232807b8c29SSebastian Oechsle 		.matches = {
1233807b8c29SSebastian Oechsle 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1234807b8c29SSebastian Oechsle 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E7440"),
1235807b8c29SSebastian Oechsle 		},
1236807b8c29SSebastian Oechsle 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1237807b8c29SSebastian Oechsle 	},
123895d88d05SCarlos Alberto Lopez Perez 	{
123995d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 5530",
124095d88d05SCarlos Alberto Lopez Perez 		.matches = {
124195d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
124295d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
124395d88d05SCarlos Alberto Lopez Perez 		},
124495d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
124595d88d05SCarlos Alberto Lopez Perez 	},
124695d88d05SCarlos Alberto Lopez Perez 	{
124795d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 7510",
124895d88d05SCarlos Alberto Lopez Perez 		.matches = {
124995d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
125095d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"),
125195d88d05SCarlos Alberto Lopez Perez 		},
125295d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
125395d88d05SCarlos Alberto Lopez Perez 	},
1254afe45277SGiovanni Mascellani 	{ }
1255afe45277SGiovanni Mascellani };
1256afe45277SGiovanni Mascellani 
12571492fa21SArmin Wolf static int __init dell_smm_probe(struct platform_device *pdev)
1258a5afba16SPali Rohár {
1259ba04d73cSArmin Wolf 	struct dell_smm_data *data;
1260afe45277SGiovanni Mascellani 	const struct dmi_system_id *id, *fan_control;
1261a5afba16SPali Rohár 	int fan, ret;
1262a5afba16SPali Rohár 
1263ba04d73cSArmin Wolf 	data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
1264ba04d73cSArmin Wolf 	if (!data)
1265ba04d73cSArmin Wolf 		return -ENOMEM;
1266ba04d73cSArmin Wolf 
1267ba04d73cSArmin Wolf 	mutex_init(&data->i8k_mutex);
1268ba04d73cSArmin Wolf 	data->i8k_fan_mult = I8K_FAN_MULT;
1269ba04d73cSArmin Wolf 	data->i8k_fan_max = I8K_FAN_HIGH;
1270ba04d73cSArmin Wolf 	platform_set_drvdata(pdev, data);
1271ba04d73cSArmin Wolf 
1272f480ea90SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
1273ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n");
1274f480ea90SPali Rohár 		if (!force)
1275ba04d73cSArmin Wolf 			data->disallow_fan_support = true;
1276f480ea90SPali Rohár 	}
1277f480ea90SPali Rohár 
1278836ad112SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
1279ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n");
1280836ad112SPali Rohár 		if (!force)
1281ba04d73cSArmin Wolf 			data->disallow_fan_type_call = true;
1282836ad112SPali Rohár 	}
12832744d2fdSPali Rohár 
1284ba04d73cSArmin Wolf 	strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
1285ba04d73cSArmin Wolf 		sizeof(data->bios_version));
1286ba04d73cSArmin Wolf 	strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
1287ba04d73cSArmin Wolf 		sizeof(data->bios_machineid));
1288a5afba16SPali Rohár 
1289a5afba16SPali Rohár 	/*
1290a5afba16SPali Rohár 	 * Set fan multiplier and maximal fan speed from dmi config
1291a5afba16SPali Rohár 	 * Values specified in module parameters override values from dmi
1292a5afba16SPali Rohár 	 */
1293a5afba16SPali Rohár 	id = dmi_first_match(i8k_dmi_table);
1294a5afba16SPali Rohár 	if (id && id->driver_data) {
1295a5afba16SPali Rohár 		const struct i8k_config_data *conf = id->driver_data;
12961492fa21SArmin Wolf 
1297a5afba16SPali Rohár 		if (!fan_mult && conf->fan_mult)
1298a5afba16SPali Rohár 			fan_mult = conf->fan_mult;
1299ba04d73cSArmin Wolf 
1300a5afba16SPali Rohár 		if (!fan_max && conf->fan_max)
1301a5afba16SPali Rohár 			fan_max = conf->fan_max;
1302a5afba16SPali Rohár 	}
1303a5afba16SPali Rohár 
1304ba04d73cSArmin Wolf 	data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
1305ba04d73cSArmin Wolf 	data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
1306a5afba16SPali Rohár 
1307afe45277SGiovanni Mascellani 	fan_control = dmi_first_match(i8k_whitelist_fan_control);
1308afe45277SGiovanni Mascellani 	if (fan_control && fan_control->driver_data) {
1309ba04d73cSArmin Wolf 		const struct i8k_fan_control_data *control = fan_control->driver_data;
1310afe45277SGiovanni Mascellani 
1311ba04d73cSArmin Wolf 		data->manual_fan = control->manual_fan;
1312ba04d73cSArmin Wolf 		data->auto_fan = control->auto_fan;
1313ba04d73cSArmin Wolf 		dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
1314afe45277SGiovanni Mascellani 	}
1315afe45277SGiovanni Mascellani 
1316a5afba16SPali Rohár 	if (!fan_mult) {
1317a5afba16SPali Rohár 		/*
1318a5afba16SPali Rohár 		 * Autodetect fan multiplier based on nominal rpm
1319a5afba16SPali Rohár 		 * If fan reports rpm value too high then set multiplier to 1
1320a5afba16SPali Rohár 		 */
13212757269aSArmin Wolf 		for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) {
1322ba04d73cSArmin Wolf 			ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max);
1323a5afba16SPali Rohár 			if (ret < 0)
1324a5afba16SPali Rohár 				continue;
1325ba04d73cSArmin Wolf 
1326a5afba16SPali Rohár 			if (ret > I8K_FAN_MAX_RPM)
1327ba04d73cSArmin Wolf 				data->i8k_fan_mult = 1;
1328a5afba16SPali Rohár 			break;
1329a5afba16SPali Rohár 		}
1330a5afba16SPali Rohár 	} else {
1331a5afba16SPali Rohár 		/* Fan multiplier was specified in module param or in dmi */
1332ba04d73cSArmin Wolf 		data->i8k_fan_mult = fan_mult;
1333a5afba16SPali Rohár 	}
1334a5afba16SPali Rohár 
13351492fa21SArmin Wolf 	ret = dell_smm_init_hwmon(&pdev->dev);
13361492fa21SArmin Wolf 	if (ret)
13371492fa21SArmin Wolf 		return ret;
13381492fa21SArmin Wolf 
1339a2cb66b4SArmin Wolf 	i8k_init_procfs(&pdev->dev);
13401492fa21SArmin Wolf 
13411492fa21SArmin Wolf 	return 0;
13421492fa21SArmin Wolf }
13431492fa21SArmin Wolf 
13441492fa21SArmin Wolf static struct platform_driver dell_smm_driver = {
13451492fa21SArmin Wolf 	.driver		= {
13461492fa21SArmin Wolf 		.name	= KBUILD_MODNAME,
13471492fa21SArmin Wolf 	},
13481492fa21SArmin Wolf };
13491492fa21SArmin Wolf 
13501492fa21SArmin Wolf static struct platform_device *dell_smm_device;
13511492fa21SArmin Wolf 
13521492fa21SArmin Wolf /*
13531492fa21SArmin Wolf  * Probe for the presence of a supported laptop.
13541492fa21SArmin Wolf  */
1355a5afba16SPali Rohár static int __init i8k_init(void)
1356a5afba16SPali Rohár {
13571492fa21SArmin Wolf 	/*
13581492fa21SArmin Wolf 	 * Get DMI information
13591492fa21SArmin Wolf 	 */
13601492fa21SArmin Wolf 	if (!dmi_check_system(i8k_dmi_table)) {
13611492fa21SArmin Wolf 		if (!ignore_dmi && !force)
1362a5afba16SPali Rohár 			return -ENODEV;
1363a5afba16SPali Rohár 
13641492fa21SArmin Wolf 		pr_info("not running on a supported Dell system.\n");
13651492fa21SArmin Wolf 		pr_info("vendor=%s, model=%s, version=%s\n",
13661492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_SYS_VENDOR),
13671492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_PRODUCT_NAME),
13681492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_BIOS_VERSION));
13691492fa21SArmin Wolf 	}
1370039ae585SPali Rohár 
13711492fa21SArmin Wolf 	/*
13721492fa21SArmin Wolf 	 * Get SMM Dell signature
13731492fa21SArmin Wolf 	 */
13741492fa21SArmin Wolf 	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
13751492fa21SArmin Wolf 	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
13761492fa21SArmin Wolf 		pr_err("unable to get SMM Dell signature\n");
13771492fa21SArmin Wolf 		if (!force)
13781492fa21SArmin Wolf 			return -ENODEV;
13791492fa21SArmin Wolf 	}
13801492fa21SArmin Wolf 
13811492fa21SArmin Wolf 	dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
13821492fa21SArmin Wolf 						 0);
13831492fa21SArmin Wolf 
13841492fa21SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_device);
1385a5afba16SPali Rohár }
1386a5afba16SPali Rohár 
1387a5afba16SPali Rohár static void __exit i8k_exit(void)
1388a5afba16SPali Rohár {
13891492fa21SArmin Wolf 	platform_device_unregister(dell_smm_device);
13901492fa21SArmin Wolf 	platform_driver_unregister(&dell_smm_driver);
1391a5afba16SPali Rohár }
1392a5afba16SPali Rohár 
1393a5afba16SPali Rohár module_init(i8k_init);
1394a5afba16SPali Rohár module_exit(i8k_exit);
1395