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 
1527046a3fSJuergen Gross #include <linux/cpu.h>
16a5afba16SPali Rohár #include <linux/delay.h>
171492fa21SArmin Wolf #include <linux/err.h>
18a5afba16SPali Rohár #include <linux/module.h>
191492fa21SArmin Wolf #include <linux/platform_device.h>
20a5afba16SPali Rohár #include <linux/types.h>
21a5afba16SPali Rohár #include <linux/init.h>
22a5afba16SPali Rohár #include <linux/proc_fs.h>
23a5afba16SPali Rohár #include <linux/seq_file.h>
24a5afba16SPali Rohár #include <linux/dmi.h>
25a5afba16SPali Rohár #include <linux/capability.h>
26a5afba16SPali Rohár #include <linux/mutex.h>
27a5afba16SPali Rohár #include <linux/hwmon.h>
28a5afba16SPali Rohár #include <linux/uaccess.h>
29a5afba16SPali Rohár #include <linux/io.h>
30a5afba16SPali Rohár #include <linux/sched.h>
31053ea640SPali Rohár #include <linux/ctype.h>
3227046a3fSJuergen Gross #include <linux/smp.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];
79ba04d73cSArmin Wolf };
80a5afba16SPali Rohár 
81a5afba16SPali Rohár MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
82149ed3d4SPali Rohár MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
83039ae585SPali Rohár MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
84a5afba16SPali Rohár MODULE_LICENSE("GPL");
85a5afba16SPali Rohár MODULE_ALIAS("i8k");
86a5afba16SPali Rohár 
87a5afba16SPali Rohár static bool force;
88a5afba16SPali Rohár module_param(force, bool, 0);
89a5afba16SPali Rohár MODULE_PARM_DESC(force, "Force loading without checking for supported models");
90a5afba16SPali Rohár 
91a5afba16SPali Rohár static bool ignore_dmi;
92a5afba16SPali Rohár module_param(ignore_dmi, bool, 0);
93a5afba16SPali Rohár MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
94a5afba16SPali Rohár 
95039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
967613663cSPali Rohár static bool restricted = true;
97a5afba16SPali Rohár module_param(restricted, bool, 0);
987613663cSPali Rohár MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)");
99a5afba16SPali Rohár 
100a5afba16SPali Rohár static bool power_status;
101a5afba16SPali Rohár module_param(power_status, bool, 0600);
1027613663cSPali Rohár MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)");
103039ae585SPali Rohár #endif
104a5afba16SPali Rohár 
105a5afba16SPali Rohár static uint fan_mult;
106a5afba16SPali Rohár module_param(fan_mult, uint, 0);
107a5afba16SPali Rohár MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
108a5afba16SPali Rohár 
109a5afba16SPali Rohár static uint fan_max;
110a5afba16SPali Rohár module_param(fan_max, uint, 0);
111a5afba16SPali Rohár MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
112a5afba16SPali Rohár 
113a5afba16SPali Rohár struct smm_regs {
114a5afba16SPali Rohár 	unsigned int eax;
115a5afba16SPali Rohár 	unsigned int ebx __packed;
116a5afba16SPali Rohár 	unsigned int ecx __packed;
117a5afba16SPali Rohár 	unsigned int edx __packed;
118a5afba16SPali Rohár 	unsigned int esi __packed;
119a5afba16SPali Rohár 	unsigned int edi __packed;
120a5afba16SPali Rohár };
121a5afba16SPali Rohár 
122deeba244SArmin Wolf static const char * const temp_labels[] = {
123deeba244SArmin Wolf 	"CPU",
124deeba244SArmin Wolf 	"GPU",
125deeba244SArmin Wolf 	"SODIMM",
126deeba244SArmin Wolf 	"Other",
127deeba244SArmin Wolf 	"Ambient",
128deeba244SArmin Wolf 	"Other",
129deeba244SArmin Wolf };
130deeba244SArmin Wolf 
131deeba244SArmin Wolf static const char * const fan_labels[] = {
132deeba244SArmin Wolf 	"Processor Fan",
133deeba244SArmin Wolf 	"Motherboard Fan",
134deeba244SArmin Wolf 	"Video Fan",
135deeba244SArmin Wolf 	"Power Supply Fan",
136deeba244SArmin Wolf 	"Chipset Fan",
137deeba244SArmin Wolf 	"Other Fan",
138deeba244SArmin Wolf };
139deeba244SArmin Wolf 
140deeba244SArmin Wolf static const char * const docking_labels[] = {
141deeba244SArmin Wolf 	"Docking Processor Fan",
142deeba244SArmin Wolf 	"Docking Motherboard Fan",
143deeba244SArmin Wolf 	"Docking Video Fan",
144deeba244SArmin Wolf 	"Docking Power Supply Fan",
145deeba244SArmin Wolf 	"Docking Chipset Fan",
146deeba244SArmin Wolf 	"Docking Other Fan",
147deeba244SArmin Wolf };
148deeba244SArmin Wolf 
149c9363cdfSArmin Wolf static inline const char __init *i8k_get_dmi_data(int field)
150a5afba16SPali Rohár {
151a5afba16SPali Rohár 	const char *dmi_data = dmi_get_system_info(field);
152a5afba16SPali Rohár 
153a5afba16SPali Rohár 	return dmi_data && *dmi_data ? dmi_data : "?";
154a5afba16SPali Rohár }
155a5afba16SPali Rohár 
156a5afba16SPali Rohár /*
157a5afba16SPali Rohár  * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
158a5afba16SPali Rohár  */
15927046a3fSJuergen Gross static int i8k_smm_func(void *par)
160a5afba16SPali Rohár {
161a5afba16SPali Rohár 	int rc;
16227046a3fSJuergen Gross 	struct smm_regs *regs = par;
163a5afba16SPali Rohár 	int eax = regs->eax;
164a5afba16SPali Rohár 
1659d58bec0SPali Rohár #ifdef DEBUG
1669d58bec0SPali Rohár 	int ebx = regs->ebx;
1679d58bec0SPali Rohár 	unsigned long duration;
1689d58bec0SPali Rohár 	ktime_t calltime, delta, rettime;
1699d58bec0SPali Rohár 
1709d58bec0SPali Rohár 	calltime = ktime_get();
1719d58bec0SPali Rohár #endif
1729d58bec0SPali Rohár 
173a5afba16SPali Rohár 	/* SMM requires CPU 0 */
17427046a3fSJuergen Gross 	if (smp_processor_id() != 0)
17527046a3fSJuergen Gross 		return -EBUSY;
176a5afba16SPali Rohár 
177a5afba16SPali Rohár #if defined(CONFIG_X86_64)
178a5afba16SPali Rohár 	asm volatile("pushq %%rax\n\t"
179a5afba16SPali Rohár 		"movl 0(%%rax),%%edx\n\t"
180a5afba16SPali Rohár 		"pushq %%rdx\n\t"
181a5afba16SPali Rohár 		"movl 4(%%rax),%%ebx\n\t"
182a5afba16SPali Rohár 		"movl 8(%%rax),%%ecx\n\t"
183a5afba16SPali Rohár 		"movl 12(%%rax),%%edx\n\t"
184a5afba16SPali Rohár 		"movl 16(%%rax),%%esi\n\t"
185a5afba16SPali Rohár 		"movl 20(%%rax),%%edi\n\t"
186a5afba16SPali Rohár 		"popq %%rax\n\t"
187a5afba16SPali Rohár 		"out %%al,$0xb2\n\t"
188a5afba16SPali Rohár 		"out %%al,$0x84\n\t"
189a5afba16SPali Rohár 		"xchgq %%rax,(%%rsp)\n\t"
190a5afba16SPali Rohár 		"movl %%ebx,4(%%rax)\n\t"
191a5afba16SPali Rohár 		"movl %%ecx,8(%%rax)\n\t"
192a5afba16SPali Rohár 		"movl %%edx,12(%%rax)\n\t"
193a5afba16SPali Rohár 		"movl %%esi,16(%%rax)\n\t"
194a5afba16SPali Rohár 		"movl %%edi,20(%%rax)\n\t"
195a5afba16SPali Rohár 		"popq %%rdx\n\t"
196a5afba16SPali Rohár 		"movl %%edx,0(%%rax)\n\t"
197a5afba16SPali Rohár 		"pushfq\n\t"
198a5afba16SPali Rohár 		"popq %%rax\n\t"
199a5afba16SPali Rohár 		"andl $1,%%eax\n"
200a5afba16SPali Rohár 		: "=a"(rc)
201a5afba16SPali Rohár 		:    "a"(regs)
202a5afba16SPali Rohár 		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
203a5afba16SPali Rohár #else
204a5afba16SPali Rohár 	asm volatile("pushl %%eax\n\t"
205a5afba16SPali Rohár 	    "movl 0(%%eax),%%edx\n\t"
206a5afba16SPali Rohár 	    "push %%edx\n\t"
207a5afba16SPali Rohár 	    "movl 4(%%eax),%%ebx\n\t"
208a5afba16SPali Rohár 	    "movl 8(%%eax),%%ecx\n\t"
209a5afba16SPali Rohár 	    "movl 12(%%eax),%%edx\n\t"
210a5afba16SPali Rohár 	    "movl 16(%%eax),%%esi\n\t"
211a5afba16SPali Rohár 	    "movl 20(%%eax),%%edi\n\t"
212a5afba16SPali Rohár 	    "popl %%eax\n\t"
213a5afba16SPali Rohár 	    "out %%al,$0xb2\n\t"
214a5afba16SPali Rohár 	    "out %%al,$0x84\n\t"
215a5afba16SPali Rohár 	    "xchgl %%eax,(%%esp)\n\t"
216a5afba16SPali Rohár 	    "movl %%ebx,4(%%eax)\n\t"
217a5afba16SPali Rohár 	    "movl %%ecx,8(%%eax)\n\t"
218a5afba16SPali Rohár 	    "movl %%edx,12(%%eax)\n\t"
219a5afba16SPali Rohár 	    "movl %%esi,16(%%eax)\n\t"
220a5afba16SPali Rohár 	    "movl %%edi,20(%%eax)\n\t"
221a5afba16SPali Rohár 	    "popl %%edx\n\t"
222a5afba16SPali Rohár 	    "movl %%edx,0(%%eax)\n\t"
223a5afba16SPali Rohár 	    "lahf\n\t"
224a5afba16SPali Rohár 	    "shrl $8,%%eax\n\t"
225a5afba16SPali Rohár 	    "andl $1,%%eax\n"
226a5afba16SPali Rohár 	    : "=a"(rc)
227a5afba16SPali Rohár 	    :    "a"(regs)
228a5afba16SPali Rohár 	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
229a5afba16SPali Rohár #endif
230a5afba16SPali Rohár 	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
231a5afba16SPali Rohár 		rc = -EINVAL;
232a5afba16SPali Rohár 
2339d58bec0SPali Rohár #ifdef DEBUG
2349d58bec0SPali Rohár 	rettime = ktime_get();
2359d58bec0SPali Rohár 	delta = ktime_sub(rettime, calltime);
2369d58bec0SPali Rohár 	duration = ktime_to_ns(delta) >> 10;
2379d58bec0SPali Rohár 	pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x  (took %7lu usecs)\n", eax, ebx,
2389d58bec0SPali Rohár 		(rc ? 0xffff : regs->eax & 0xffff), duration);
2399d58bec0SPali Rohár #endif
2409d58bec0SPali Rohár 
241a5afba16SPali Rohár 	return rc;
242a5afba16SPali Rohár }
243a5afba16SPali Rohár 
244a5afba16SPali Rohár /*
24527046a3fSJuergen Gross  * Call the System Management Mode BIOS.
24627046a3fSJuergen Gross  */
24727046a3fSJuergen Gross static int i8k_smm(struct smm_regs *regs)
24827046a3fSJuergen Gross {
24927046a3fSJuergen Gross 	int ret;
25027046a3fSJuergen Gross 
251e104d530SSebastian Andrzej Siewior 	cpus_read_lock();
25227046a3fSJuergen Gross 	ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
253e104d530SSebastian Andrzej Siewior 	cpus_read_unlock();
25427046a3fSJuergen Gross 
25527046a3fSJuergen Gross 	return ret;
25627046a3fSJuergen Gross }
25727046a3fSJuergen Gross 
25827046a3fSJuergen Gross /*
259a5afba16SPali Rohár  * Read the fan status.
260a5afba16SPali Rohár  */
261ba04d73cSArmin Wolf static int i8k_get_fan_status(const struct dell_smm_data *data, int fan)
262a5afba16SPali Rohár {
263a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
264a5afba16SPali Rohár 
265ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
266f480ea90SPali Rohár 		return -EINVAL;
267f480ea90SPali Rohár 
268a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
269a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
270a5afba16SPali Rohár }
271a5afba16SPali Rohár 
272a5afba16SPali Rohár /*
273a5afba16SPali Rohár  * Read the fan speed in RPM.
274a5afba16SPali Rohár  */
275ba04d73cSArmin Wolf static int i8k_get_fan_speed(const struct dell_smm_data *data, int fan)
276a5afba16SPali Rohár {
277a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
278a5afba16SPali Rohár 
279ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
280f480ea90SPali Rohár 		return -EINVAL;
281f480ea90SPali Rohár 
282a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
283ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
284a5afba16SPali Rohár }
285a5afba16SPali Rohár 
286a5afba16SPali Rohár /*
287a5afba16SPali Rohár  * Read the fan type.
288a5afba16SPali Rohár  */
289ba04d73cSArmin Wolf static int _i8k_get_fan_type(const struct dell_smm_data *data, int fan)
290a5afba16SPali Rohár {
291a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
292a5afba16SPali Rohár 
293ba04d73cSArmin Wolf 	if (data->disallow_fan_support || data->disallow_fan_type_call)
2942744d2fdSPali Rohár 		return -EINVAL;
2952744d2fdSPali Rohár 
296a5afba16SPali Rohár 	regs.ebx = fan & 0xff;
297a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
298a5afba16SPali Rohár }
299a5afba16SPali Rohár 
300ba04d73cSArmin Wolf static int i8k_get_fan_type(struct dell_smm_data *data, int fan)
3015ce91714SPali Rohár {
3025ce91714SPali Rohár 	/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
303deeba244SArmin Wolf 	if (data->fan_type[fan] == INT_MIN)
304deeba244SArmin Wolf 		data->fan_type[fan] = _i8k_get_fan_type(data, fan);
3055ce91714SPali Rohár 
306deeba244SArmin Wolf 	return data->fan_type[fan];
3075ce91714SPali Rohár }
3085ce91714SPali Rohár 
309a5afba16SPali Rohár /*
310a5afba16SPali Rohár  * Read the fan nominal rpm for specific fan speed.
311a5afba16SPali Rohár  */
312*782a99c1SArmin Wolf static int __init i8k_get_fan_nominal_speed(const struct dell_smm_data *data, int fan, int speed)
313a5afba16SPali Rohár {
314a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
315a5afba16SPali Rohár 
316ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
317f480ea90SPali Rohár 		return -EINVAL;
318f480ea90SPali Rohár 
319a5afba16SPali Rohár 	regs.ebx = (fan & 0xff) | (speed << 8);
320ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
321a5afba16SPali Rohár }
322a5afba16SPali Rohár 
323a5afba16SPali Rohár /*
324afe45277SGiovanni Mascellani  * Enable or disable automatic BIOS fan control support
325afe45277SGiovanni Mascellani  */
326ba04d73cSArmin Wolf static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable)
327afe45277SGiovanni Mascellani {
328afe45277SGiovanni Mascellani 	struct smm_regs regs = { };
329afe45277SGiovanni Mascellani 
330ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
331afe45277SGiovanni Mascellani 		return -EINVAL;
332afe45277SGiovanni Mascellani 
333ba04d73cSArmin Wolf 	regs.eax = enable ? data->auto_fan : data->manual_fan;
334afe45277SGiovanni Mascellani 	return i8k_smm(&regs);
335afe45277SGiovanni Mascellani }
336afe45277SGiovanni Mascellani 
337afe45277SGiovanni Mascellani /*
338a5afba16SPali Rohár  * Set the fan speed (off, low, high). Returns the new fan status.
339a5afba16SPali Rohár  */
340ba04d73cSArmin Wolf static int i8k_set_fan(const struct dell_smm_data *data, int fan, int speed)
341a5afba16SPali Rohár {
342a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
343a5afba16SPali Rohár 
344ba04d73cSArmin Wolf 	if (data->disallow_fan_support)
345f480ea90SPali Rohár 		return -EINVAL;
346f480ea90SPali Rohár 
347ba04d73cSArmin Wolf 	speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
348a5afba16SPali Rohár 	regs.ebx = (fan & 0xff) | (speed << 8);
349a5afba16SPali Rohár 
350ba04d73cSArmin Wolf 	return i8k_smm(&regs) ? : i8k_get_fan_status(data, fan);
351a5afba16SPali Rohár }
352a5afba16SPali Rohár 
353deeba244SArmin Wolf static int __init i8k_get_temp_type(int sensor)
354a5afba16SPali Rohár {
355a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
356a5afba16SPali Rohár 
357a5afba16SPali Rohár 	regs.ebx = sensor & 0xff;
358a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
359a5afba16SPali Rohár }
360a5afba16SPali Rohár 
361a5afba16SPali Rohár /*
362a5afba16SPali Rohár  * Read the cpu temperature.
363a5afba16SPali Rohár  */
364a5afba16SPali Rohár static int _i8k_get_temp(int sensor)
365a5afba16SPali Rohár {
366a5afba16SPali Rohár 	struct smm_regs regs = {
367a5afba16SPali Rohár 		.eax = I8K_SMM_GET_TEMP,
368a5afba16SPali Rohár 		.ebx = sensor & 0xff,
369a5afba16SPali Rohár 	};
370a5afba16SPali Rohár 
371a5afba16SPali Rohár 	return i8k_smm(&regs) ? : regs.eax & 0xff;
372a5afba16SPali Rohár }
373a5afba16SPali Rohár 
374a5afba16SPali Rohár static int i8k_get_temp(int sensor)
375a5afba16SPali Rohár {
376a5afba16SPali Rohár 	int temp = _i8k_get_temp(sensor);
377a5afba16SPali Rohár 
378a5afba16SPali Rohár 	/*
379a5afba16SPali Rohár 	 * Sometimes the temperature sensor returns 0x99, which is out of range.
380a5afba16SPali Rohár 	 * In this case we retry (once) before returning an error.
381a5afba16SPali Rohár 	 # 1003655137 00000058 00005a4b
382a5afba16SPali Rohár 	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
383a5afba16SPali Rohár 	 # 1003655139 00000054 00005c52
384a5afba16SPali Rohár 	 */
385a5afba16SPali Rohár 	if (temp == 0x99) {
386a5afba16SPali Rohár 		msleep(100);
387a5afba16SPali Rohár 		temp = _i8k_get_temp(sensor);
388a5afba16SPali Rohár 	}
389a5afba16SPali Rohár 	/*
390a5afba16SPali Rohár 	 * Return -ENODATA for all invalid temperatures.
391a5afba16SPali Rohár 	 *
392a5afba16SPali Rohár 	 * Known instances are the 0x99 value as seen above as well as
393a5afba16SPali Rohár 	 * 0xc1 (193), which may be returned when trying to read the GPU
394a5afba16SPali Rohár 	 * temperature if the system supports a GPU and it is currently
395a5afba16SPali Rohár 	 * turned off.
396a5afba16SPali Rohár 	 */
397a5afba16SPali Rohár 	if (temp > I8K_MAX_TEMP)
398a5afba16SPali Rohár 		return -ENODATA;
399a5afba16SPali Rohár 
400a5afba16SPali Rohár 	return temp;
401a5afba16SPali Rohár }
402a5afba16SPali Rohár 
403c9363cdfSArmin Wolf static int __init i8k_get_dell_signature(int req_fn)
404a5afba16SPali Rohár {
405a5afba16SPali Rohár 	struct smm_regs regs = { .eax = req_fn, };
406a5afba16SPali Rohár 	int rc;
407a5afba16SPali Rohár 
408a5afba16SPali Rohár 	rc = i8k_smm(&regs);
409a5afba16SPali Rohár 	if (rc < 0)
410a5afba16SPali Rohár 		return rc;
411a5afba16SPali Rohár 
412a5afba16SPali Rohár 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
413a5afba16SPali Rohár }
414a5afba16SPali Rohár 
415039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
416039ae585SPali Rohár 
417039ae585SPali Rohár /*
418039ae585SPali Rohár  * Read the Fn key status.
419039ae585SPali Rohár  */
420039ae585SPali Rohár static int i8k_get_fn_status(void)
421039ae585SPali Rohár {
422039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
423039ae585SPali Rohár 	int rc;
424039ae585SPali Rohár 
425039ae585SPali Rohár 	rc = i8k_smm(&regs);
426039ae585SPali Rohár 	if (rc < 0)
427039ae585SPali Rohár 		return rc;
428039ae585SPali Rohár 
429039ae585SPali Rohár 	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
430039ae585SPali Rohár 	case I8K_FN_UP:
431039ae585SPali Rohár 		return I8K_VOL_UP;
432039ae585SPali Rohár 	case I8K_FN_DOWN:
433039ae585SPali Rohár 		return I8K_VOL_DOWN;
434039ae585SPali Rohár 	case I8K_FN_MUTE:
435039ae585SPali Rohár 		return I8K_VOL_MUTE;
436039ae585SPali Rohár 	default:
437039ae585SPali Rohár 		return 0;
438039ae585SPali Rohár 	}
439039ae585SPali Rohár }
440039ae585SPali Rohár 
441039ae585SPali Rohár /*
442039ae585SPali Rohár  * Read the power status.
443039ae585SPali Rohár  */
444039ae585SPali Rohár static int i8k_get_power_status(void)
445039ae585SPali Rohár {
446039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
447039ae585SPali Rohár 	int rc;
448039ae585SPali Rohár 
449039ae585SPali Rohár 	rc = i8k_smm(&regs);
450039ae585SPali Rohár 	if (rc < 0)
451039ae585SPali Rohár 		return rc;
452039ae585SPali Rohár 
453039ae585SPali Rohár 	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
454039ae585SPali Rohár }
455039ae585SPali Rohár 
456039ae585SPali Rohár /*
457039ae585SPali Rohár  * Procfs interface
458039ae585SPali Rohár  */
459039ae585SPali Rohár 
460a5afba16SPali Rohár static int
461ba04d73cSArmin Wolf i8k_ioctl_unlocked(struct file *fp, struct dell_smm_data *data, unsigned int cmd, unsigned long arg)
462a5afba16SPali Rohár {
463a5afba16SPali Rohár 	int val = 0;
464a5afba16SPali Rohár 	int speed;
465a5afba16SPali Rohár 	unsigned char buff[16];
466a5afba16SPali Rohár 	int __user *argp = (int __user *)arg;
467a5afba16SPali Rohár 
468a5afba16SPali Rohár 	if (!argp)
469a5afba16SPali Rohár 		return -EINVAL;
470a5afba16SPali Rohár 
471a5afba16SPali Rohár 	switch (cmd) {
472a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
473ba04d73cSArmin Wolf 		if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) ||
474ba04d73cSArmin Wolf 		    !isdigit(data->bios_version[2]))
475053ea640SPali Rohár 			return -EINVAL;
476053ea640SPali Rohár 
477ba04d73cSArmin Wolf 		val = (data->bios_version[0] << 16) |
478ba04d73cSArmin Wolf 				(data->bios_version[1] << 8) | data->bios_version[2];
479a5afba16SPali Rohár 		break;
480a5afba16SPali Rohár 
481a5afba16SPali Rohár 	case I8K_MACHINE_ID:
4827613663cSPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
4837613663cSPali Rohár 			return -EPERM;
4847613663cSPali Rohár 
4857613663cSPali Rohár 		memset(buff, 0, sizeof(buff));
486ba04d73cSArmin Wolf 		strscpy(buff, data->bios_machineid, sizeof(buff));
487a5afba16SPali Rohár 		break;
488a5afba16SPali Rohár 
489a5afba16SPali Rohár 	case I8K_FN_STATUS:
490a5afba16SPali Rohár 		val = i8k_get_fn_status();
491a5afba16SPali Rohár 		break;
492a5afba16SPali Rohár 
493a5afba16SPali Rohár 	case I8K_POWER_STATUS:
494a5afba16SPali Rohár 		val = i8k_get_power_status();
495a5afba16SPali Rohár 		break;
496a5afba16SPali Rohár 
497a5afba16SPali Rohár 	case I8K_GET_TEMP:
498a5afba16SPali Rohár 		val = i8k_get_temp(0);
499a5afba16SPali Rohár 		break;
500a5afba16SPali Rohár 
501a5afba16SPali Rohár 	case I8K_GET_SPEED:
502a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
503a5afba16SPali Rohár 			return -EFAULT;
504a5afba16SPali Rohár 
505ba04d73cSArmin Wolf 		val = i8k_get_fan_speed(data, val);
506a5afba16SPali Rohár 		break;
507a5afba16SPali Rohár 
508a5afba16SPali Rohár 	case I8K_GET_FAN:
509a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
510a5afba16SPali Rohár 			return -EFAULT;
511a5afba16SPali Rohár 
512ba04d73cSArmin Wolf 		val = i8k_get_fan_status(data, val);
513a5afba16SPali Rohár 		break;
514a5afba16SPali Rohár 
515a5afba16SPali Rohár 	case I8K_SET_FAN:
516a5afba16SPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
517a5afba16SPali Rohár 			return -EPERM;
518a5afba16SPali Rohár 
519a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
520a5afba16SPali Rohár 			return -EFAULT;
521a5afba16SPali Rohár 
522a5afba16SPali Rohár 		if (copy_from_user(&speed, argp + 1, sizeof(int)))
523a5afba16SPali Rohár 			return -EFAULT;
524a5afba16SPali Rohár 
525ba04d73cSArmin Wolf 		val = i8k_set_fan(data, val, speed);
526a5afba16SPali Rohár 		break;
527a5afba16SPali Rohár 
528a5afba16SPali Rohár 	default:
529a5afba16SPali Rohár 		return -EINVAL;
530a5afba16SPali Rohár 	}
531a5afba16SPali Rohár 
532a5afba16SPali Rohár 	if (val < 0)
533a5afba16SPali Rohár 		return val;
534a5afba16SPali Rohár 
535a5afba16SPali Rohár 	switch (cmd) {
536a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
537a5afba16SPali Rohár 		if (copy_to_user(argp, &val, 4))
538a5afba16SPali Rohár 			return -EFAULT;
539a5afba16SPali Rohár 
540a5afba16SPali Rohár 		break;
541a5afba16SPali Rohár 	case I8K_MACHINE_ID:
542a5afba16SPali Rohár 		if (copy_to_user(argp, buff, 16))
543a5afba16SPali Rohár 			return -EFAULT;
544a5afba16SPali Rohár 
545a5afba16SPali Rohár 		break;
546a5afba16SPali Rohár 	default:
547a5afba16SPali Rohár 		if (copy_to_user(argp, &val, sizeof(int)))
548a5afba16SPali Rohár 			return -EFAULT;
549a5afba16SPali Rohár 
550a5afba16SPali Rohár 		break;
551a5afba16SPali Rohár 	}
552a5afba16SPali Rohár 
553a5afba16SPali Rohár 	return 0;
554a5afba16SPali Rohár }
555a5afba16SPali Rohár 
556a5afba16SPali Rohár static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
557a5afba16SPali Rohár {
558ba04d73cSArmin Wolf 	struct dell_smm_data *data = PDE_DATA(file_inode(fp));
559a5afba16SPali Rohár 	long ret;
560a5afba16SPali Rohár 
561ba04d73cSArmin Wolf 	mutex_lock(&data->i8k_mutex);
562ba04d73cSArmin Wolf 	ret = i8k_ioctl_unlocked(fp, data, cmd, arg);
563ba04d73cSArmin Wolf 	mutex_unlock(&data->i8k_mutex);
564a5afba16SPali Rohár 
565a5afba16SPali Rohár 	return ret;
566a5afba16SPali Rohár }
567a5afba16SPali Rohár 
568a5afba16SPali Rohár /*
569a5afba16SPali Rohár  * Print the information for /proc/i8k.
570a5afba16SPali Rohár  */
571a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset)
572a5afba16SPali Rohár {
573ba04d73cSArmin Wolf 	struct dell_smm_data *data = seq->private;
574a5afba16SPali Rohár 	int fn_key, cpu_temp, ac_power;
575a5afba16SPali Rohár 	int left_fan, right_fan, left_speed, right_speed;
576a5afba16SPali Rohár 
577a5afba16SPali Rohár 	cpu_temp	= i8k_get_temp(0);				/* 11100 µs */
578ba04d73cSArmin Wolf 	left_fan	= i8k_get_fan_status(data, I8K_FAN_LEFT);	/*   580 µs */
579ba04d73cSArmin Wolf 	right_fan	= i8k_get_fan_status(data, I8K_FAN_RIGHT);	/*   580 µs */
580ba04d73cSArmin Wolf 	left_speed	= i8k_get_fan_speed(data, I8K_FAN_LEFT);	/*   580 µs */
581ba04d73cSArmin Wolf 	right_speed	= i8k_get_fan_speed(data, I8K_FAN_RIGHT);	/*   580 µs */
582a5afba16SPali Rohár 	fn_key		= i8k_get_fn_status();				/*   750 µs */
583a5afba16SPali Rohár 	if (power_status)
584a5afba16SPali Rohár 		ac_power = i8k_get_power_status();			/* 14700 µs */
585a5afba16SPali Rohár 	else
586a5afba16SPali Rohár 		ac_power = -1;
587a5afba16SPali Rohár 
588a5afba16SPali Rohár 	/*
589a5afba16SPali Rohár 	 * Info:
590a5afba16SPali Rohár 	 *
591a5afba16SPali Rohár 	 * 1)  Format version (this will change if format changes)
592a5afba16SPali Rohár 	 * 2)  BIOS version
593a5afba16SPali Rohár 	 * 3)  BIOS machine ID
594a5afba16SPali Rohár 	 * 4)  Cpu temperature
595a5afba16SPali Rohár 	 * 5)  Left fan status
596a5afba16SPali Rohár 	 * 6)  Right fan status
597a5afba16SPali Rohár 	 * 7)  Left fan speed
598a5afba16SPali Rohár 	 * 8)  Right fan speed
599a5afba16SPali Rohár 	 * 9)  AC power
600a5afba16SPali Rohár 	 * 10) Fn Key status
601a5afba16SPali Rohár 	 */
602a5afba16SPali Rohár 	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
603a5afba16SPali Rohár 		   I8K_PROC_FMT,
604ba04d73cSArmin Wolf 		   data->bios_version,
605ba04d73cSArmin Wolf 		   (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid,
606a5afba16SPali Rohár 		   cpu_temp,
607a5afba16SPali Rohár 		   left_fan, right_fan, left_speed, right_speed,
608a5afba16SPali Rohár 		   ac_power, fn_key);
609a5afba16SPali Rohár 
610a5afba16SPali Rohár 	return 0;
611a5afba16SPali Rohár }
612a5afba16SPali Rohár 
613a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file)
614a5afba16SPali Rohár {
615ba04d73cSArmin Wolf 	return single_open(file, i8k_proc_show, PDE_DATA(inode));
616a5afba16SPali Rohár }
617a5afba16SPali Rohár 
61897a32539SAlexey Dobriyan static const struct proc_ops i8k_proc_ops = {
61997a32539SAlexey Dobriyan 	.proc_open	= i8k_open_fs,
62097a32539SAlexey Dobriyan 	.proc_read	= seq_read,
62197a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
62297a32539SAlexey Dobriyan 	.proc_release	= single_release,
62397a32539SAlexey Dobriyan 	.proc_ioctl	= i8k_ioctl,
624039ae585SPali Rohár };
625039ae585SPali Rohár 
626a2cb66b4SArmin Wolf static void i8k_exit_procfs(void *param)
627039ae585SPali Rohár {
628039ae585SPali Rohár 	remove_proc_entry("i8k", NULL);
629039ae585SPali Rohár }
630039ae585SPali Rohár 
631a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
632039ae585SPali Rohár {
633ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
634ba04d73cSArmin Wolf 
635a2cb66b4SArmin Wolf 	/* Register the proc entry */
636ba04d73cSArmin Wolf 	proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data);
637a2cb66b4SArmin Wolf 
638a2cb66b4SArmin Wolf 	devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
639039ae585SPali Rohár }
640039ae585SPali Rohár 
641a2cb66b4SArmin Wolf #else
642a2cb66b4SArmin Wolf 
643a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
644039ae585SPali Rohár {
645039ae585SPali Rohár }
646039ae585SPali Rohár 
647039ae585SPali Rohár #endif
648a5afba16SPali Rohár 
649a5afba16SPali Rohár /*
650a5afba16SPali Rohár  * Hwmon interface
651a5afba16SPali Rohár  */
652a5afba16SPali Rohár 
653deeba244SArmin Wolf static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
654deeba244SArmin Wolf 				   int channel)
655a5afba16SPali Rohár {
656deeba244SArmin Wolf 	const struct dell_smm_data *data = drvdata;
657a5afba16SPali Rohár 
658deeba244SArmin Wolf 	switch (type) {
659deeba244SArmin Wolf 	case hwmon_temp:
660deeba244SArmin Wolf 		switch (attr) {
661deeba244SArmin Wolf 		case hwmon_temp_input:
662deeba244SArmin Wolf 		case hwmon_temp_label:
663deeba244SArmin Wolf 			if (data->temp_type[channel] >= 0)
664deeba244SArmin Wolf 				return 0444;
665deeba244SArmin Wolf 
666deeba244SArmin Wolf 			break;
667deeba244SArmin Wolf 		default:
668deeba244SArmin Wolf 			break;
669deeba244SArmin Wolf 		}
670deeba244SArmin Wolf 		break;
671deeba244SArmin Wolf 	case hwmon_fan:
672deeba244SArmin Wolf 		if (data->disallow_fan_support)
673deeba244SArmin Wolf 			break;
674deeba244SArmin Wolf 
675deeba244SArmin Wolf 		switch (attr) {
676deeba244SArmin Wolf 		case hwmon_fan_input:
677deeba244SArmin Wolf 			if (data->fan[channel])
678deeba244SArmin Wolf 				return 0444;
679deeba244SArmin Wolf 
680deeba244SArmin Wolf 			break;
681deeba244SArmin Wolf 		case hwmon_fan_label:
682deeba244SArmin Wolf 			if (data->fan[channel] && !data->disallow_fan_type_call)
683deeba244SArmin Wolf 				return 0444;
684deeba244SArmin Wolf 
685deeba244SArmin Wolf 			break;
686deeba244SArmin Wolf 		default:
687deeba244SArmin Wolf 			break;
688deeba244SArmin Wolf 		}
689deeba244SArmin Wolf 		break;
690deeba244SArmin Wolf 	case hwmon_pwm:
691deeba244SArmin Wolf 		if (data->disallow_fan_support)
692deeba244SArmin Wolf 			break;
693deeba244SArmin Wolf 
694deeba244SArmin Wolf 		switch (attr) {
695deeba244SArmin Wolf 		case hwmon_pwm_input:
696deeba244SArmin Wolf 			if (data->fan[channel])
697deeba244SArmin Wolf 				return 0644;
698deeba244SArmin Wolf 
699deeba244SArmin Wolf 			break;
700deeba244SArmin Wolf 		case hwmon_pwm_enable:
701deeba244SArmin Wolf 			if (data->auto_fan)
702deeba244SArmin Wolf 				/*
703deeba244SArmin Wolf 				 * There is no command for retrieve the current status
704deeba244SArmin Wolf 				 * from BIOS, and userspace/firmware itself can change
705deeba244SArmin Wolf 				 * it.
706deeba244SArmin Wolf 				 * Thus we can only provide write-only access for now.
707deeba244SArmin Wolf 				 */
708deeba244SArmin Wolf 				return 0200;
709deeba244SArmin Wolf 
710deeba244SArmin Wolf 			break;
711deeba244SArmin Wolf 		default:
712deeba244SArmin Wolf 			break;
713deeba244SArmin Wolf 		}
714deeba244SArmin Wolf 		break;
715deeba244SArmin Wolf 	default:
716deeba244SArmin Wolf 		break;
717a5afba16SPali Rohár 	}
718a5afba16SPali Rohár 
719deeba244SArmin Wolf 	return 0;
720a5afba16SPali Rohár }
721a5afba16SPali Rohár 
722deeba244SArmin Wolf static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
723deeba244SArmin Wolf 			 long *val)
724a5afba16SPali Rohár {
725ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
726deeba244SArmin Wolf 	int ret;
727a5afba16SPali Rohár 
728deeba244SArmin Wolf 	switch (type) {
729deeba244SArmin Wolf 	case hwmon_temp:
730deeba244SArmin Wolf 		switch (attr) {
731deeba244SArmin Wolf 		case hwmon_temp_input:
732deeba244SArmin Wolf 			ret = i8k_get_temp(channel);
733deeba244SArmin Wolf 			if (ret < 0)
734deeba244SArmin Wolf 				return ret;
735deeba244SArmin Wolf 
736deeba244SArmin Wolf 			*val = ret * 1000;
737deeba244SArmin Wolf 
738deeba244SArmin Wolf 			return 0;
739deeba244SArmin Wolf 		default:
740deeba244SArmin Wolf 			break;
741deeba244SArmin Wolf 		}
742deeba244SArmin Wolf 		break;
743deeba244SArmin Wolf 	case hwmon_fan:
744deeba244SArmin Wolf 		switch (attr) {
745deeba244SArmin Wolf 		case hwmon_fan_input:
746deeba244SArmin Wolf 			ret = i8k_get_fan_speed(data, channel);
747deeba244SArmin Wolf 			if (ret < 0)
748deeba244SArmin Wolf 				return ret;
749deeba244SArmin Wolf 
750deeba244SArmin Wolf 			*val = ret;
751deeba244SArmin Wolf 
752deeba244SArmin Wolf 			return 0;
753deeba244SArmin Wolf 		default:
754deeba244SArmin Wolf 			break;
755deeba244SArmin Wolf 		}
756deeba244SArmin Wolf 		break;
757deeba244SArmin Wolf 	case hwmon_pwm:
758deeba244SArmin Wolf 		switch (attr) {
759deeba244SArmin Wolf 		case hwmon_pwm_input:
760deeba244SArmin Wolf 			ret = i8k_get_fan_status(data, channel);
761deeba244SArmin Wolf 			if (ret < 0)
762deeba244SArmin Wolf 				return ret;
763deeba244SArmin Wolf 
764deeba244SArmin Wolf 			*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
765deeba244SArmin Wolf 
766deeba244SArmin Wolf 			return 0;
767deeba244SArmin Wolf 		default:
768deeba244SArmin Wolf 			break;
769deeba244SArmin Wolf 		}
770deeba244SArmin Wolf 		break;
771deeba244SArmin Wolf 	default:
772deeba244SArmin Wolf 		break;
773deeba244SArmin Wolf 	}
774deeba244SArmin Wolf 
775deeba244SArmin Wolf 	return -EOPNOTSUPP;
776deeba244SArmin Wolf }
777deeba244SArmin Wolf 
778deeba244SArmin Wolf static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel)
779deeba244SArmin Wolf {
780deeba244SArmin Wolf 	bool dock = false;
781deeba244SArmin Wolf 	int type = i8k_get_fan_type(data, channel);
782deeba244SArmin Wolf 
783a5afba16SPali Rohár 	if (type < 0)
784deeba244SArmin Wolf 		return ERR_PTR(type);
785a5afba16SPali Rohár 
786a5afba16SPali Rohár 	if (type & 0x10) {
787a5afba16SPali Rohár 		dock = true;
788a5afba16SPali Rohár 		type &= 0x0F;
789a5afba16SPali Rohár 	}
790a5afba16SPali Rohár 
791deeba244SArmin Wolf 	if (type >= ARRAY_SIZE(fan_labels))
792deeba244SArmin Wolf 		type = ARRAY_SIZE(fan_labels) - 1;
793a5afba16SPali Rohár 
794deeba244SArmin Wolf 	return dock ? docking_labels[type] : fan_labels[type];
795a5afba16SPali Rohár }
796a5afba16SPali Rohár 
797deeba244SArmin Wolf static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
798deeba244SArmin Wolf 				int channel, const char **str)
799a5afba16SPali Rohár {
800ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
801a5afba16SPali Rohár 
802deeba244SArmin Wolf 	switch (type) {
803deeba244SArmin Wolf 	case hwmon_temp:
804deeba244SArmin Wolf 		switch (attr) {
805deeba244SArmin Wolf 		case hwmon_temp_label:
806deeba244SArmin Wolf 			*str = temp_labels[data->temp_type[channel]];
807deeba244SArmin Wolf 			return 0;
808deeba244SArmin Wolf 		default:
809deeba244SArmin Wolf 			break;
810deeba244SArmin Wolf 		}
811deeba244SArmin Wolf 		break;
812deeba244SArmin Wolf 	case hwmon_fan:
813deeba244SArmin Wolf 		switch (attr) {
814deeba244SArmin Wolf 		case hwmon_fan_label:
815deeba244SArmin Wolf 			*str = dell_smm_fan_label(data, channel);
816deeba244SArmin Wolf 			return PTR_ERR_OR_ZERO(*str);
817deeba244SArmin Wolf 		default:
818deeba244SArmin Wolf 			break;
819deeba244SArmin Wolf 		}
820deeba244SArmin Wolf 		break;
821deeba244SArmin Wolf 	default:
822deeba244SArmin Wolf 		break;
823a5afba16SPali Rohár 	}
824a5afba16SPali Rohár 
825deeba244SArmin Wolf 	return -EOPNOTSUPP;
826a5afba16SPali Rohár }
827a5afba16SPali Rohár 
828deeba244SArmin Wolf static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
829deeba244SArmin Wolf 			  long val)
830a5afba16SPali Rohár {
831ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
832deeba244SArmin Wolf 	unsigned long pwm;
833deeba244SArmin Wolf 	bool enable;
834a5afba16SPali Rohár 	int err;
835a5afba16SPali Rohár 
836deeba244SArmin Wolf 	switch (type) {
837deeba244SArmin Wolf 	case hwmon_pwm:
838deeba244SArmin Wolf 		switch (attr) {
839deeba244SArmin Wolf 		case hwmon_pwm_input:
840deeba244SArmin Wolf 			pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0,
841deeba244SArmin Wolf 					data->i8k_fan_max);
842a5afba16SPali Rohár 
843ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
844deeba244SArmin Wolf 			err = i8k_set_fan(data, channel, pwm);
845ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
846a5afba16SPali Rohár 
847deeba244SArmin Wolf 			if (err < 0)
848afe45277SGiovanni Mascellani 				return err;
849afe45277SGiovanni Mascellani 
850deeba244SArmin Wolf 			return 0;
851deeba244SArmin Wolf 		case hwmon_pwm_enable:
852deeba244SArmin Wolf 			if (!val)
853deeba244SArmin Wolf 				return -EINVAL;
854deeba244SArmin Wolf 
855afe45277SGiovanni Mascellani 			if (val == 1)
856afe45277SGiovanni Mascellani 				enable = false;
857afe45277SGiovanni Mascellani 			else
858deeba244SArmin Wolf 				enable = true;
859afe45277SGiovanni Mascellani 
860ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
861ba04d73cSArmin Wolf 			err = i8k_enable_fan_auto_mode(data, enable);
862ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
863afe45277SGiovanni Mascellani 
864deeba244SArmin Wolf 			if (err < 0)
865deeba244SArmin Wolf 				return err;
866deeba244SArmin Wolf 
867deeba244SArmin Wolf 			return 0;
868deeba244SArmin Wolf 		default:
869deeba244SArmin Wolf 			break;
870deeba244SArmin Wolf 		}
871deeba244SArmin Wolf 		break;
872deeba244SArmin Wolf 	default:
873deeba244SArmin Wolf 		break;
874afe45277SGiovanni Mascellani 	}
875afe45277SGiovanni Mascellani 
876deeba244SArmin Wolf 	return -EOPNOTSUPP;
877deeba244SArmin Wolf }
878a5afba16SPali Rohár 
879deeba244SArmin Wolf static const struct hwmon_ops dell_smm_ops = {
880deeba244SArmin Wolf 	.is_visible = dell_smm_is_visible,
881deeba244SArmin Wolf 	.read = dell_smm_read,
882deeba244SArmin Wolf 	.read_string = dell_smm_read_string,
883deeba244SArmin Wolf 	.write = dell_smm_write,
884deeba244SArmin Wolf };
885deeba244SArmin Wolf 
886deeba244SArmin Wolf static const struct hwmon_channel_info *dell_smm_info[] = {
887deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
888deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(temp,
889deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
890deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
891deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
892deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
893deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
894deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
895deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
896deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
897deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
898deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL
899deeba244SArmin Wolf 			   ),
900deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(fan,
901deeba244SArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL,
902deeba244SArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL,
903deeba244SArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL
904deeba244SArmin Wolf 			   ),
905deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(pwm,
906deeba244SArmin Wolf 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
907deeba244SArmin Wolf 			   HWMON_PWM_INPUT,
908deeba244SArmin Wolf 			   HWMON_PWM_INPUT
909deeba244SArmin Wolf 			   ),
910a5afba16SPali Rohár 	NULL
911a5afba16SPali Rohár };
912a5afba16SPali Rohár 
913deeba244SArmin Wolf static const struct hwmon_chip_info dell_smm_chip_info = {
914deeba244SArmin Wolf 	.ops = &dell_smm_ops,
915deeba244SArmin Wolf 	.info = dell_smm_info,
916a5afba16SPali Rohár };
917a5afba16SPali Rohár 
9181492fa21SArmin Wolf static int __init dell_smm_init_hwmon(struct device *dev)
919a5afba16SPali Rohár {
920ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
921deeba244SArmin Wolf 	struct device *dell_smm_hwmon_dev;
922deeba244SArmin Wolf 	int i, err;
923a5afba16SPali Rohár 
924deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
925deeba244SArmin Wolf 		data->temp_type[i] = i8k_get_temp_type(i);
926deeba244SArmin Wolf 		if (data->temp_type[i] < 0)
927deeba244SArmin Wolf 			continue;
928a5afba16SPali Rohár 
929deeba244SArmin Wolf 		if (data->temp_type[i] >= ARRAY_SIZE(temp_labels))
930deeba244SArmin Wolf 			data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1;
931deeba244SArmin Wolf 	}
932deeba244SArmin Wolf 
933deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_FANS; i++) {
934deeba244SArmin Wolf 		data->fan_type[i] = INT_MIN;
935deeba244SArmin Wolf 		err = i8k_get_fan_status(data, i);
9365ce91714SPali Rohár 		if (err < 0)
937deeba244SArmin Wolf 			err = i8k_get_fan_type(data, i);
938a5afba16SPali Rohár 		if (err >= 0)
939deeba244SArmin Wolf 			data->fan[i] = true;
940deeba244SArmin Wolf 	}
941a5afba16SPali Rohár 
942deeba244SArmin Wolf 	dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data,
943deeba244SArmin Wolf 								  &dell_smm_chip_info, NULL);
944a5afba16SPali Rohár 
945deeba244SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
946a5afba16SPali Rohár }
947a5afba16SPali Rohár 
948a5afba16SPali Rohár struct i8k_config_data {
949a5afba16SPali Rohár 	uint fan_mult;
950a5afba16SPali Rohár 	uint fan_max;
951a5afba16SPali Rohár };
952a5afba16SPali Rohár 
953a5afba16SPali Rohár enum i8k_configs {
954a5afba16SPali Rohár 	DELL_LATITUDE_D520,
955a5afba16SPali Rohár 	DELL_PRECISION_490,
956a5afba16SPali Rohár 	DELL_STUDIO,
957a5afba16SPali Rohár 	DELL_XPS,
958a5afba16SPali Rohár };
959a5afba16SPali Rohár 
960c510f6acSArmin Wolf static const struct i8k_config_data i8k_config_data[] __initconst = {
961a5afba16SPali Rohár 	[DELL_LATITUDE_D520] = {
962a5afba16SPali Rohár 		.fan_mult = 1,
963a5afba16SPali Rohár 		.fan_max = I8K_FAN_TURBO,
964a5afba16SPali Rohár 	},
965a5afba16SPali Rohár 	[DELL_PRECISION_490] = {
966a5afba16SPali Rohár 		.fan_mult = 1,
967a5afba16SPali Rohár 		.fan_max = I8K_FAN_TURBO,
968a5afba16SPali Rohár 	},
969a5afba16SPali Rohár 	[DELL_STUDIO] = {
970a5afba16SPali Rohár 		.fan_mult = 1,
971a5afba16SPali Rohár 		.fan_max = I8K_FAN_HIGH,
972a5afba16SPali Rohár 	},
973a5afba16SPali Rohár 	[DELL_XPS] = {
974a5afba16SPali Rohár 		.fan_mult = 1,
975a5afba16SPali Rohár 		.fan_max = I8K_FAN_HIGH,
976a5afba16SPali Rohár 	},
977a5afba16SPali Rohár };
978a5afba16SPali Rohár 
9796faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_dmi_table[] __initconst = {
980a5afba16SPali Rohár 	{
981a5afba16SPali Rohár 		.ident = "Dell Inspiron",
982a5afba16SPali Rohár 		.matches = {
983a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
984a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
985a5afba16SPali Rohár 		},
986a5afba16SPali Rohár 	},
987a5afba16SPali Rohár 	{
988a5afba16SPali Rohár 		.ident = "Dell Latitude",
989a5afba16SPali Rohár 		.matches = {
990a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
991a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
992a5afba16SPali Rohár 		},
993a5afba16SPali Rohár 	},
994a5afba16SPali Rohár 	{
995a5afba16SPali Rohár 		.ident = "Dell Inspiron 2",
996a5afba16SPali Rohár 		.matches = {
997a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
998a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
999a5afba16SPali Rohár 		},
1000a5afba16SPali Rohár 	},
1001a5afba16SPali Rohár 	{
1002a5afba16SPali Rohár 		.ident = "Dell Latitude D520",
1003a5afba16SPali Rohár 		.matches = {
1004a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1005a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
1006a5afba16SPali Rohár 		},
1007a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
1008a5afba16SPali Rohár 	},
1009a5afba16SPali Rohár 	{
1010a5afba16SPali Rohár 		.ident = "Dell Latitude 2",
1011a5afba16SPali Rohár 		.matches = {
1012a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1013a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1014a5afba16SPali Rohár 		},
1015a5afba16SPali Rohár 	},
1016a5afba16SPali Rohár 	{	/* UK Inspiron 6400  */
1017a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1018a5afba16SPali Rohár 		.matches = {
1019a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1020a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
1021a5afba16SPali Rohár 		},
1022a5afba16SPali Rohár 	},
1023a5afba16SPali Rohár 	{
1024a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1025a5afba16SPali Rohár 		.matches = {
1026a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1027a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
1028a5afba16SPali Rohár 		},
1029a5afba16SPali Rohár 	},
1030a5afba16SPali Rohár 	{
1031a5afba16SPali Rohár 		.ident = "Dell Precision 490",
1032a5afba16SPali Rohár 		.matches = {
1033a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1034a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME,
1035a5afba16SPali Rohár 				  "Precision WorkStation 490"),
1036a5afba16SPali Rohár 		},
1037a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
1038a5afba16SPali Rohár 	},
1039a5afba16SPali Rohár 	{
1040a5afba16SPali Rohár 		.ident = "Dell Precision",
1041a5afba16SPali Rohár 		.matches = {
1042a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1043a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
1044a5afba16SPali Rohár 		},
1045a5afba16SPali Rohár 	},
1046a5afba16SPali Rohár 	{
1047a5afba16SPali Rohár 		.ident = "Dell Vostro",
1048a5afba16SPali Rohár 		.matches = {
1049a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1050a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
1051a5afba16SPali Rohár 		},
1052a5afba16SPali Rohár 	},
1053a5afba16SPali Rohár 	{
1054a5afba16SPali Rohár 		.ident = "Dell Studio",
1055a5afba16SPali Rohár 		.matches = {
1056a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1057a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
1058a5afba16SPali Rohár 		},
1059a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
1060a5afba16SPali Rohár 	},
1061a5afba16SPali Rohár 	{
1062a5afba16SPali Rohár 		.ident = "Dell XPS M140",
1063a5afba16SPali Rohár 		.matches = {
1064a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1065a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
1066a5afba16SPali Rohár 		},
1067a5afba16SPali Rohár 		.driver_data = (void *)&i8k_config_data[DELL_XPS],
1068a5afba16SPali Rohár 	},
1069a4811b6cSPali Rohár 	{
1070b8a13e5eSThomas Hebb 		.ident = "Dell XPS",
1071a4811b6cSPali Rohár 		.matches = {
1072a4811b6cSPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1073b8a13e5eSThomas Hebb 			DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
1074162372b0SMichele Sorcinelli 		},
1075162372b0SMichele Sorcinelli 	},
1076a5afba16SPali Rohár 	{ }
1077a5afba16SPali Rohár };
1078a5afba16SPali Rohár 
1079a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
1080a5afba16SPali Rohár 
1081a4b45b25SPali Rohár /*
10822744d2fdSPali Rohár  * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
10832744d2fdSPali Rohár  * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
10842744d2fdSPali Rohár  * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
10852744d2fdSPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121
10866220f4ebSThorsten Leemhuis  */
10876faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = {
10882744d2fdSPali Rohár 	{
10896220f4ebSThorsten Leemhuis 		.ident = "Dell Studio XPS 8000",
10906220f4ebSThorsten Leemhuis 		.matches = {
10916220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10926220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"),
10936220f4ebSThorsten Leemhuis 		},
10946220f4ebSThorsten Leemhuis 	},
10956220f4ebSThorsten Leemhuis 	{
1096a4b45b25SPali Rohár 		.ident = "Dell Studio XPS 8100",
1097a4b45b25SPali Rohár 		.matches = {
1098a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1099a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"),
1100a4b45b25SPali Rohár 		},
1101a4b45b25SPali Rohár 	},
11022744d2fdSPali Rohár 	{
11032744d2fdSPali Rohár 		.ident = "Dell Inspiron 580",
11042744d2fdSPali Rohár 		.matches = {
11052744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11062744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "),
11072744d2fdSPali Rohár 		},
11082744d2fdSPali Rohár 	},
1109a4b45b25SPali Rohár 	{ }
1110a4b45b25SPali Rohár };
1111a4b45b25SPali Rohár 
1112a5afba16SPali Rohár /*
1113f480ea90SPali Rohár  * On some machines all fan related SMM functions implemented by Dell BIOS
1114f480ea90SPali Rohár  * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan
1115f480ea90SPali Rohár  * support for affected blacklisted Dell machines stay disabled.
1116f480ea90SPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
1117f480ea90SPali Rohár  */
1118c510f6acSArmin Wolf static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initconst = {
1119f480ea90SPali Rohár 	{
1120f480ea90SPali Rohár 		.ident = "Dell Inspiron 7720",
1121f480ea90SPali Rohár 		.matches = {
1122f480ea90SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1123f480ea90SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
1124f480ea90SPali Rohár 		},
1125f480ea90SPali Rohár 	},
11266fbc4232SOleksandr Natalenko 	{
11276fbc4232SOleksandr Natalenko 		.ident = "Dell Vostro 3360",
11286fbc4232SOleksandr Natalenko 		.matches = {
11296fbc4232SOleksandr Natalenko 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11306fbc4232SOleksandr Natalenko 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
11316fbc4232SOleksandr Natalenko 		},
11326fbc4232SOleksandr Natalenko 	},
1133536e0019SHelge Eichelberg 	{
1134536e0019SHelge Eichelberg 		.ident = "Dell XPS13 9333",
1135536e0019SHelge Eichelberg 		.matches = {
1136536e0019SHelge Eichelberg 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1137536e0019SHelge Eichelberg 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
1138536e0019SHelge Eichelberg 		},
1139536e0019SHelge Eichelberg 	},
11404008bc7dSThomas Hebb 	{
11414008bc7dSThomas Hebb 		.ident = "Dell XPS 15 L502X",
11424008bc7dSThomas Hebb 		.matches = {
11434008bc7dSThomas Hebb 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11444008bc7dSThomas Hebb 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"),
11454008bc7dSThomas Hebb 		},
11464008bc7dSThomas Hebb 	},
1147f480ea90SPali Rohár 	{ }
1148f480ea90SPali Rohár };
1149f480ea90SPali Rohár 
1150afe45277SGiovanni Mascellani struct i8k_fan_control_data {
1151afe45277SGiovanni Mascellani 	unsigned int manual_fan;
1152afe45277SGiovanni Mascellani 	unsigned int auto_fan;
1153afe45277SGiovanni Mascellani };
1154afe45277SGiovanni Mascellani 
1155afe45277SGiovanni Mascellani enum i8k_fan_controls {
1156afe45277SGiovanni Mascellani 	I8K_FAN_34A3_35A3,
1157afe45277SGiovanni Mascellani };
1158afe45277SGiovanni Mascellani 
1159c510f6acSArmin Wolf static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
1160afe45277SGiovanni Mascellani 	[I8K_FAN_34A3_35A3] = {
1161afe45277SGiovanni Mascellani 		.manual_fan = 0x34a3,
1162afe45277SGiovanni Mascellani 		.auto_fan = 0x35a3,
1163afe45277SGiovanni Mascellani 	},
1164afe45277SGiovanni Mascellani };
1165afe45277SGiovanni Mascellani 
1166c510f6acSArmin Wolf static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
1167afe45277SGiovanni Mascellani 	{
11680ca8bb2cSJeffrey Lin 		.ident = "Dell Latitude 5480",
11690ca8bb2cSJeffrey Lin 		.matches = {
11700ca8bb2cSJeffrey Lin 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11710ca8bb2cSJeffrey Lin 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"),
11720ca8bb2cSJeffrey Lin 		},
11730ca8bb2cSJeffrey Lin 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
11740ca8bb2cSJeffrey Lin 	},
11750ca8bb2cSJeffrey Lin 	{
1176afe45277SGiovanni Mascellani 		.ident = "Dell Latitude E6440",
1177afe45277SGiovanni Mascellani 		.matches = {
1178afe45277SGiovanni Mascellani 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1179afe45277SGiovanni Mascellani 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
1180afe45277SGiovanni Mascellani 		},
1181afe45277SGiovanni Mascellani 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1182afe45277SGiovanni Mascellani 	},
1183807b8c29SSebastian Oechsle 	{
1184807b8c29SSebastian Oechsle 		.ident = "Dell Latitude E7440",
1185807b8c29SSebastian Oechsle 		.matches = {
1186807b8c29SSebastian Oechsle 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1187807b8c29SSebastian Oechsle 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E7440"),
1188807b8c29SSebastian Oechsle 		},
1189807b8c29SSebastian Oechsle 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1190807b8c29SSebastian Oechsle 	},
119195d88d05SCarlos Alberto Lopez Perez 	{
119295d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 5530",
119395d88d05SCarlos Alberto Lopez Perez 		.matches = {
119495d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
119595d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
119695d88d05SCarlos Alberto Lopez Perez 		},
119795d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
119895d88d05SCarlos Alberto Lopez Perez 	},
119995d88d05SCarlos Alberto Lopez Perez 	{
120095d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 7510",
120195d88d05SCarlos Alberto Lopez Perez 		.matches = {
120295d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
120395d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"),
120495d88d05SCarlos Alberto Lopez Perez 		},
120595d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
120695d88d05SCarlos Alberto Lopez Perez 	},
1207afe45277SGiovanni Mascellani 	{ }
1208afe45277SGiovanni Mascellani };
1209afe45277SGiovanni Mascellani 
12101492fa21SArmin Wolf static int __init dell_smm_probe(struct platform_device *pdev)
1211a5afba16SPali Rohár {
1212ba04d73cSArmin Wolf 	struct dell_smm_data *data;
1213afe45277SGiovanni Mascellani 	const struct dmi_system_id *id, *fan_control;
1214a5afba16SPali Rohár 	int fan, ret;
1215a5afba16SPali Rohár 
1216ba04d73cSArmin Wolf 	data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
1217ba04d73cSArmin Wolf 	if (!data)
1218ba04d73cSArmin Wolf 		return -ENOMEM;
1219ba04d73cSArmin Wolf 
1220ba04d73cSArmin Wolf 	mutex_init(&data->i8k_mutex);
1221ba04d73cSArmin Wolf 	data->i8k_fan_mult = I8K_FAN_MULT;
1222ba04d73cSArmin Wolf 	data->i8k_fan_max = I8K_FAN_HIGH;
1223ba04d73cSArmin Wolf 	platform_set_drvdata(pdev, data);
1224ba04d73cSArmin Wolf 
1225f480ea90SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
1226ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n");
1227f480ea90SPali Rohár 		if (!force)
1228ba04d73cSArmin Wolf 			data->disallow_fan_support = true;
1229f480ea90SPali Rohár 	}
1230f480ea90SPali Rohár 
1231836ad112SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
1232ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n");
1233836ad112SPali Rohár 		if (!force)
1234ba04d73cSArmin Wolf 			data->disallow_fan_type_call = true;
1235836ad112SPali Rohár 	}
12362744d2fdSPali Rohár 
1237ba04d73cSArmin Wolf 	strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
1238ba04d73cSArmin Wolf 		sizeof(data->bios_version));
1239ba04d73cSArmin Wolf 	strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
1240ba04d73cSArmin Wolf 		sizeof(data->bios_machineid));
1241a5afba16SPali Rohár 
1242a5afba16SPali Rohár 	/*
1243a5afba16SPali Rohár 	 * Set fan multiplier and maximal fan speed from dmi config
1244a5afba16SPali Rohár 	 * Values specified in module parameters override values from dmi
1245a5afba16SPali Rohár 	 */
1246a5afba16SPali Rohár 	id = dmi_first_match(i8k_dmi_table);
1247a5afba16SPali Rohár 	if (id && id->driver_data) {
1248a5afba16SPali Rohár 		const struct i8k_config_data *conf = id->driver_data;
12491492fa21SArmin Wolf 
1250a5afba16SPali Rohár 		if (!fan_mult && conf->fan_mult)
1251a5afba16SPali Rohár 			fan_mult = conf->fan_mult;
1252ba04d73cSArmin Wolf 
1253a5afba16SPali Rohár 		if (!fan_max && conf->fan_max)
1254a5afba16SPali Rohár 			fan_max = conf->fan_max;
1255a5afba16SPali Rohár 	}
1256a5afba16SPali Rohár 
1257ba04d73cSArmin Wolf 	data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
1258ba04d73cSArmin Wolf 	data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
1259a5afba16SPali Rohár 
1260afe45277SGiovanni Mascellani 	fan_control = dmi_first_match(i8k_whitelist_fan_control);
1261afe45277SGiovanni Mascellani 	if (fan_control && fan_control->driver_data) {
1262ba04d73cSArmin Wolf 		const struct i8k_fan_control_data *control = fan_control->driver_data;
1263afe45277SGiovanni Mascellani 
1264ba04d73cSArmin Wolf 		data->manual_fan = control->manual_fan;
1265ba04d73cSArmin Wolf 		data->auto_fan = control->auto_fan;
1266ba04d73cSArmin Wolf 		dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
1267afe45277SGiovanni Mascellani 	}
1268afe45277SGiovanni Mascellani 
1269a5afba16SPali Rohár 	if (!fan_mult) {
1270a5afba16SPali Rohár 		/*
1271a5afba16SPali Rohár 		 * Autodetect fan multiplier based on nominal rpm
1272a5afba16SPali Rohár 		 * If fan reports rpm value too high then set multiplier to 1
1273a5afba16SPali Rohár 		 */
12742757269aSArmin Wolf 		for (fan = 0; fan < DELL_SMM_NO_FANS; ++fan) {
1275ba04d73cSArmin Wolf 			ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max);
1276a5afba16SPali Rohár 			if (ret < 0)
1277a5afba16SPali Rohár 				continue;
1278ba04d73cSArmin Wolf 
1279a5afba16SPali Rohár 			if (ret > I8K_FAN_MAX_RPM)
1280ba04d73cSArmin Wolf 				data->i8k_fan_mult = 1;
1281a5afba16SPali Rohár 			break;
1282a5afba16SPali Rohár 		}
1283a5afba16SPali Rohár 	} else {
1284a5afba16SPali Rohár 		/* Fan multiplier was specified in module param or in dmi */
1285ba04d73cSArmin Wolf 		data->i8k_fan_mult = fan_mult;
1286a5afba16SPali Rohár 	}
1287a5afba16SPali Rohár 
12881492fa21SArmin Wolf 	ret = dell_smm_init_hwmon(&pdev->dev);
12891492fa21SArmin Wolf 	if (ret)
12901492fa21SArmin Wolf 		return ret;
12911492fa21SArmin Wolf 
1292a2cb66b4SArmin Wolf 	i8k_init_procfs(&pdev->dev);
12931492fa21SArmin Wolf 
12941492fa21SArmin Wolf 	return 0;
12951492fa21SArmin Wolf }
12961492fa21SArmin Wolf 
12971492fa21SArmin Wolf static struct platform_driver dell_smm_driver = {
12981492fa21SArmin Wolf 	.driver		= {
12991492fa21SArmin Wolf 		.name	= KBUILD_MODNAME,
13001492fa21SArmin Wolf 	},
13011492fa21SArmin Wolf };
13021492fa21SArmin Wolf 
13031492fa21SArmin Wolf static struct platform_device *dell_smm_device;
13041492fa21SArmin Wolf 
13051492fa21SArmin Wolf /*
13061492fa21SArmin Wolf  * Probe for the presence of a supported laptop.
13071492fa21SArmin Wolf  */
1308a5afba16SPali Rohár static int __init i8k_init(void)
1309a5afba16SPali Rohár {
13101492fa21SArmin Wolf 	/*
13111492fa21SArmin Wolf 	 * Get DMI information
13121492fa21SArmin Wolf 	 */
13131492fa21SArmin Wolf 	if (!dmi_check_system(i8k_dmi_table)) {
13141492fa21SArmin Wolf 		if (!ignore_dmi && !force)
1315a5afba16SPali Rohár 			return -ENODEV;
1316a5afba16SPali Rohár 
13171492fa21SArmin Wolf 		pr_info("not running on a supported Dell system.\n");
13181492fa21SArmin Wolf 		pr_info("vendor=%s, model=%s, version=%s\n",
13191492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_SYS_VENDOR),
13201492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_PRODUCT_NAME),
13211492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_BIOS_VERSION));
13221492fa21SArmin Wolf 	}
1323039ae585SPali Rohár 
13241492fa21SArmin Wolf 	/*
13251492fa21SArmin Wolf 	 * Get SMM Dell signature
13261492fa21SArmin Wolf 	 */
13271492fa21SArmin Wolf 	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
13281492fa21SArmin Wolf 	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
13291492fa21SArmin Wolf 		pr_err("unable to get SMM Dell signature\n");
13301492fa21SArmin Wolf 		if (!force)
13311492fa21SArmin Wolf 			return -ENODEV;
13321492fa21SArmin Wolf 	}
13331492fa21SArmin Wolf 
13341492fa21SArmin Wolf 	dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
13351492fa21SArmin Wolf 						 0);
13361492fa21SArmin Wolf 
13371492fa21SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_device);
1338a5afba16SPali Rohár }
1339a5afba16SPali Rohár 
1340a5afba16SPali Rohár static void __exit i8k_exit(void)
1341a5afba16SPali Rohár {
13421492fa21SArmin Wolf 	platform_device_unregister(dell_smm_device);
13431492fa21SArmin Wolf 	platform_driver_unregister(&dell_smm_driver);
1344a5afba16SPali Rohár }
1345a5afba16SPali Rohár 
1346a5afba16SPali Rohár module_init(i8k_init);
1347a5afba16SPali Rohár module_exit(i8k_exit);
1348