xref: /linux/drivers/hwmon/dell-smm-hwmon.c (revision deeba244b0feab6180b77fe98f2c19eb89163428)
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 
62*deeba244SArmin Wolf #define DELL_SMM_NO_TEMP	10
63*deeba244SArmin Wolf #define DELL_SMM_NO_FANS	3
64*deeba244SArmin 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;
76*deeba244SArmin Wolf 	int temp_type[DELL_SMM_NO_TEMP];
77*deeba244SArmin Wolf 	bool fan[DELL_SMM_NO_FANS];
78*deeba244SArmin 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 
122*deeba244SArmin Wolf static const char * const temp_labels[] = {
123*deeba244SArmin Wolf 	"CPU",
124*deeba244SArmin Wolf 	"GPU",
125*deeba244SArmin Wolf 	"SODIMM",
126*deeba244SArmin Wolf 	"Other",
127*deeba244SArmin Wolf 	"Ambient",
128*deeba244SArmin Wolf 	"Other",
129*deeba244SArmin Wolf };
130*deeba244SArmin Wolf 
131*deeba244SArmin Wolf static const char * const fan_labels[] = {
132*deeba244SArmin Wolf 	"Processor Fan",
133*deeba244SArmin Wolf 	"Motherboard Fan",
134*deeba244SArmin Wolf 	"Video Fan",
135*deeba244SArmin Wolf 	"Power Supply Fan",
136*deeba244SArmin Wolf 	"Chipset Fan",
137*deeba244SArmin Wolf 	"Other Fan",
138*deeba244SArmin Wolf };
139*deeba244SArmin Wolf 
140*deeba244SArmin Wolf static const char * const docking_labels[] = {
141*deeba244SArmin Wolf 	"Docking Processor Fan",
142*deeba244SArmin Wolf 	"Docking Motherboard Fan",
143*deeba244SArmin Wolf 	"Docking Video Fan",
144*deeba244SArmin Wolf 	"Docking Power Supply Fan",
145*deeba244SArmin Wolf 	"Docking Chipset Fan",
146*deeba244SArmin Wolf 	"Docking Other Fan",
147*deeba244SArmin Wolf };
148*deeba244SArmin 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 
25127046a3fSJuergen Gross 	get_online_cpus();
25227046a3fSJuergen Gross 	ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
25327046a3fSJuergen Gross 	put_online_cpus();
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 */
303*deeba244SArmin Wolf 	if (data->fan_type[fan] == INT_MIN)
304*deeba244SArmin Wolf 		data->fan_type[fan] = _i8k_get_fan_type(data, fan);
3055ce91714SPali Rohár 
306*deeba244SArmin 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  */
312ba04d73cSArmin Wolf static int 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 
353*deeba244SArmin 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 
653*deeba244SArmin Wolf static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
654*deeba244SArmin Wolf 				   int channel)
655a5afba16SPali Rohár {
656*deeba244SArmin Wolf 	const struct dell_smm_data *data = drvdata;
657a5afba16SPali Rohár 
658*deeba244SArmin Wolf 	switch (type) {
659*deeba244SArmin Wolf 	case hwmon_temp:
660*deeba244SArmin Wolf 		switch (attr) {
661*deeba244SArmin Wolf 		case hwmon_temp_input:
662*deeba244SArmin Wolf 		case hwmon_temp_label:
663*deeba244SArmin Wolf 			if (data->temp_type[channel] >= 0)
664*deeba244SArmin Wolf 				return 0444;
665*deeba244SArmin Wolf 
666*deeba244SArmin Wolf 			break;
667*deeba244SArmin Wolf 		default:
668*deeba244SArmin Wolf 			break;
669*deeba244SArmin Wolf 		}
670*deeba244SArmin Wolf 		break;
671*deeba244SArmin Wolf 	case hwmon_fan:
672*deeba244SArmin Wolf 		if (data->disallow_fan_support)
673*deeba244SArmin Wolf 			break;
674*deeba244SArmin Wolf 
675*deeba244SArmin Wolf 		switch (attr) {
676*deeba244SArmin Wolf 		case hwmon_fan_input:
677*deeba244SArmin Wolf 			if (data->fan[channel])
678*deeba244SArmin Wolf 				return 0444;
679*deeba244SArmin Wolf 
680*deeba244SArmin Wolf 			break;
681*deeba244SArmin Wolf 		case hwmon_fan_label:
682*deeba244SArmin Wolf 			if (data->fan[channel] && !data->disallow_fan_type_call)
683*deeba244SArmin Wolf 				return 0444;
684*deeba244SArmin Wolf 
685*deeba244SArmin Wolf 			break;
686*deeba244SArmin Wolf 		default:
687*deeba244SArmin Wolf 			break;
688*deeba244SArmin Wolf 		}
689*deeba244SArmin Wolf 		break;
690*deeba244SArmin Wolf 	case hwmon_pwm:
691*deeba244SArmin Wolf 		if (data->disallow_fan_support)
692*deeba244SArmin Wolf 			break;
693*deeba244SArmin Wolf 
694*deeba244SArmin Wolf 		switch (attr) {
695*deeba244SArmin Wolf 		case hwmon_pwm_input:
696*deeba244SArmin Wolf 			if (data->fan[channel])
697*deeba244SArmin Wolf 				return 0644;
698*deeba244SArmin Wolf 
699*deeba244SArmin Wolf 			break;
700*deeba244SArmin Wolf 		case hwmon_pwm_enable:
701*deeba244SArmin Wolf 			if (data->auto_fan)
702*deeba244SArmin Wolf 				/*
703*deeba244SArmin Wolf 				 * There is no command for retrieve the current status
704*deeba244SArmin Wolf 				 * from BIOS, and userspace/firmware itself can change
705*deeba244SArmin Wolf 				 * it.
706*deeba244SArmin Wolf 				 * Thus we can only provide write-only access for now.
707*deeba244SArmin Wolf 				 */
708*deeba244SArmin Wolf 				return 0200;
709*deeba244SArmin Wolf 
710*deeba244SArmin Wolf 			break;
711*deeba244SArmin Wolf 		default:
712*deeba244SArmin Wolf 			break;
713*deeba244SArmin Wolf 		}
714*deeba244SArmin Wolf 		break;
715*deeba244SArmin Wolf 	default:
716*deeba244SArmin Wolf 		break;
717a5afba16SPali Rohár 	}
718a5afba16SPali Rohár 
719*deeba244SArmin Wolf 	return 0;
720a5afba16SPali Rohár }
721a5afba16SPali Rohár 
722*deeba244SArmin Wolf static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
723*deeba244SArmin Wolf 			 long *val)
724a5afba16SPali Rohár {
725ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
726*deeba244SArmin Wolf 	int ret;
727a5afba16SPali Rohár 
728*deeba244SArmin Wolf 	switch (type) {
729*deeba244SArmin Wolf 	case hwmon_temp:
730*deeba244SArmin Wolf 		switch (attr) {
731*deeba244SArmin Wolf 		case hwmon_temp_input:
732*deeba244SArmin Wolf 			ret = i8k_get_temp(channel);
733*deeba244SArmin Wolf 			if (ret < 0)
734*deeba244SArmin Wolf 				return ret;
735*deeba244SArmin Wolf 
736*deeba244SArmin Wolf 			*val = ret * 1000;
737*deeba244SArmin Wolf 
738*deeba244SArmin Wolf 			return 0;
739*deeba244SArmin Wolf 		default:
740*deeba244SArmin Wolf 			break;
741*deeba244SArmin Wolf 		}
742*deeba244SArmin Wolf 		break;
743*deeba244SArmin Wolf 	case hwmon_fan:
744*deeba244SArmin Wolf 		switch (attr) {
745*deeba244SArmin Wolf 		case hwmon_fan_input:
746*deeba244SArmin Wolf 			ret = i8k_get_fan_speed(data, channel);
747*deeba244SArmin Wolf 			if (ret < 0)
748*deeba244SArmin Wolf 				return ret;
749*deeba244SArmin Wolf 
750*deeba244SArmin Wolf 			*val = ret;
751*deeba244SArmin Wolf 
752*deeba244SArmin Wolf 			return 0;
753*deeba244SArmin Wolf 		default:
754*deeba244SArmin Wolf 			break;
755*deeba244SArmin Wolf 		}
756*deeba244SArmin Wolf 		break;
757*deeba244SArmin Wolf 	case hwmon_pwm:
758*deeba244SArmin Wolf 		switch (attr) {
759*deeba244SArmin Wolf 		case hwmon_pwm_input:
760*deeba244SArmin Wolf 			ret = i8k_get_fan_status(data, channel);
761*deeba244SArmin Wolf 			if (ret < 0)
762*deeba244SArmin Wolf 				return ret;
763*deeba244SArmin Wolf 
764*deeba244SArmin Wolf 			*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
765*deeba244SArmin Wolf 
766*deeba244SArmin Wolf 			return 0;
767*deeba244SArmin Wolf 		default:
768*deeba244SArmin Wolf 			break;
769*deeba244SArmin Wolf 		}
770*deeba244SArmin Wolf 		break;
771*deeba244SArmin Wolf 	default:
772*deeba244SArmin Wolf 		break;
773*deeba244SArmin Wolf 	}
774*deeba244SArmin Wolf 
775*deeba244SArmin Wolf 	return -EOPNOTSUPP;
776*deeba244SArmin Wolf }
777*deeba244SArmin Wolf 
778*deeba244SArmin Wolf static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel)
779*deeba244SArmin Wolf {
780*deeba244SArmin Wolf 	bool dock = false;
781*deeba244SArmin Wolf 	int type = i8k_get_fan_type(data, channel);
782*deeba244SArmin Wolf 
783a5afba16SPali Rohár 	if (type < 0)
784*deeba244SArmin 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 
791*deeba244SArmin Wolf 	if (type >= ARRAY_SIZE(fan_labels))
792*deeba244SArmin Wolf 		type = ARRAY_SIZE(fan_labels) - 1;
793a5afba16SPali Rohár 
794*deeba244SArmin Wolf 	return dock ? docking_labels[type] : fan_labels[type];
795a5afba16SPali Rohár }
796a5afba16SPali Rohár 
797*deeba244SArmin Wolf static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
798*deeba244SArmin Wolf 				int channel, const char **str)
799a5afba16SPali Rohár {
800ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
801a5afba16SPali Rohár 
802*deeba244SArmin Wolf 	switch (type) {
803*deeba244SArmin Wolf 	case hwmon_temp:
804*deeba244SArmin Wolf 		switch (attr) {
805*deeba244SArmin Wolf 		case hwmon_temp_label:
806*deeba244SArmin Wolf 			*str = temp_labels[data->temp_type[channel]];
807*deeba244SArmin Wolf 			return 0;
808*deeba244SArmin Wolf 		default:
809*deeba244SArmin Wolf 			break;
810*deeba244SArmin Wolf 		}
811*deeba244SArmin Wolf 		break;
812*deeba244SArmin Wolf 	case hwmon_fan:
813*deeba244SArmin Wolf 		switch (attr) {
814*deeba244SArmin Wolf 		case hwmon_fan_label:
815*deeba244SArmin Wolf 			*str = dell_smm_fan_label(data, channel);
816*deeba244SArmin Wolf 			return PTR_ERR_OR_ZERO(*str);
817*deeba244SArmin Wolf 		default:
818*deeba244SArmin Wolf 			break;
819*deeba244SArmin Wolf 		}
820*deeba244SArmin Wolf 		break;
821*deeba244SArmin Wolf 	default:
822*deeba244SArmin Wolf 		break;
823a5afba16SPali Rohár 	}
824a5afba16SPali Rohár 
825*deeba244SArmin Wolf 	return -EOPNOTSUPP;
826a5afba16SPali Rohár }
827a5afba16SPali Rohár 
828*deeba244SArmin Wolf static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
829*deeba244SArmin Wolf 			  long val)
830a5afba16SPali Rohár {
831ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
832*deeba244SArmin Wolf 	unsigned long pwm;
833*deeba244SArmin Wolf 	bool enable;
834a5afba16SPali Rohár 	int err;
835a5afba16SPali Rohár 
836*deeba244SArmin Wolf 	switch (type) {
837*deeba244SArmin Wolf 	case hwmon_pwm:
838*deeba244SArmin Wolf 		switch (attr) {
839*deeba244SArmin Wolf 		case hwmon_pwm_input:
840*deeba244SArmin Wolf 			pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0,
841*deeba244SArmin Wolf 					data->i8k_fan_max);
842a5afba16SPali Rohár 
843ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
844*deeba244SArmin Wolf 			err = i8k_set_fan(data, channel, pwm);
845ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
846a5afba16SPali Rohár 
847*deeba244SArmin Wolf 			if (err < 0)
848afe45277SGiovanni Mascellani 				return err;
849afe45277SGiovanni Mascellani 
850*deeba244SArmin Wolf 			return 0;
851*deeba244SArmin Wolf 		case hwmon_pwm_enable:
852*deeba244SArmin Wolf 			if (!val)
853*deeba244SArmin Wolf 				return -EINVAL;
854*deeba244SArmin Wolf 
855afe45277SGiovanni Mascellani 			if (val == 1)
856afe45277SGiovanni Mascellani 				enable = false;
857afe45277SGiovanni Mascellani 			else
858*deeba244SArmin 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 
864*deeba244SArmin Wolf 			if (err < 0)
865*deeba244SArmin Wolf 				return err;
866*deeba244SArmin Wolf 
867*deeba244SArmin Wolf 			return 0;
868*deeba244SArmin Wolf 		default:
869*deeba244SArmin Wolf 			break;
870*deeba244SArmin Wolf 		}
871*deeba244SArmin Wolf 		break;
872*deeba244SArmin Wolf 	default:
873*deeba244SArmin Wolf 		break;
874afe45277SGiovanni Mascellani 	}
875afe45277SGiovanni Mascellani 
876*deeba244SArmin Wolf 	return -EOPNOTSUPP;
877*deeba244SArmin Wolf }
878a5afba16SPali Rohár 
879*deeba244SArmin Wolf static const struct hwmon_ops dell_smm_ops = {
880*deeba244SArmin Wolf 	.is_visible = dell_smm_is_visible,
881*deeba244SArmin Wolf 	.read = dell_smm_read,
882*deeba244SArmin Wolf 	.read_string = dell_smm_read_string,
883*deeba244SArmin Wolf 	.write = dell_smm_write,
884*deeba244SArmin Wolf };
885*deeba244SArmin Wolf 
886*deeba244SArmin Wolf static const struct hwmon_channel_info *dell_smm_info[] = {
887*deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
888*deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(temp,
889*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
890*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
891*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
892*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
893*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
894*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
895*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
896*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
897*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
898*deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL
899*deeba244SArmin Wolf 			   ),
900*deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(fan,
901*deeba244SArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL,
902*deeba244SArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL,
903*deeba244SArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL
904*deeba244SArmin Wolf 			   ),
905*deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(pwm,
906*deeba244SArmin Wolf 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
907*deeba244SArmin Wolf 			   HWMON_PWM_INPUT,
908*deeba244SArmin Wolf 			   HWMON_PWM_INPUT
909*deeba244SArmin Wolf 			   ),
910a5afba16SPali Rohár 	NULL
911a5afba16SPali Rohár };
912a5afba16SPali Rohár 
913*deeba244SArmin Wolf static const struct hwmon_chip_info dell_smm_chip_info = {
914*deeba244SArmin Wolf 	.ops = &dell_smm_ops,
915*deeba244SArmin 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);
921*deeba244SArmin Wolf 	struct device *dell_smm_hwmon_dev;
922*deeba244SArmin Wolf 	int i, err;
923a5afba16SPali Rohár 
924*deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
925*deeba244SArmin Wolf 		data->temp_type[i] = i8k_get_temp_type(i);
926*deeba244SArmin Wolf 		if (data->temp_type[i] < 0)
927*deeba244SArmin Wolf 			continue;
928a5afba16SPali Rohár 
929*deeba244SArmin Wolf 		if (data->temp_type[i] >= ARRAY_SIZE(temp_labels))
930*deeba244SArmin Wolf 			data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1;
931*deeba244SArmin Wolf 	}
932*deeba244SArmin Wolf 
933*deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_FANS; i++) {
934*deeba244SArmin Wolf 		data->fan_type[i] = INT_MIN;
935*deeba244SArmin Wolf 		err = i8k_get_fan_status(data, i);
9365ce91714SPali Rohár 		if (err < 0)
937*deeba244SArmin Wolf 			err = i8k_get_fan_type(data, i);
938a5afba16SPali Rohár 		if (err >= 0)
939*deeba244SArmin Wolf 			data->fan[i] = true;
940*deeba244SArmin Wolf 	}
941a5afba16SPali Rohár 
942*deeba244SArmin Wolf 	dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data,
943*deeba244SArmin Wolf 								  &dell_smm_chip_info, NULL);
944a5afba16SPali Rohár 
945*deeba244SArmin 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 
960a5afba16SPali Rohár static const struct i8k_config_data i8k_config_data[] = {
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  */
1118f480ea90SPali Rohár static struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = {
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 
1159afe45277SGiovanni Mascellani static const struct i8k_fan_control_data i8k_fan_control_data[] = {
1160afe45277SGiovanni Mascellani 	[I8K_FAN_34A3_35A3] = {
1161afe45277SGiovanni Mascellani 		.manual_fan = 0x34a3,
1162afe45277SGiovanni Mascellani 		.auto_fan = 0x35a3,
1163afe45277SGiovanni Mascellani 	},
1164afe45277SGiovanni Mascellani };
1165afe45277SGiovanni Mascellani 
1166afe45277SGiovanni Mascellani static struct dmi_system_id i8k_whitelist_fan_control[] __initdata = {
1167afe45277SGiovanni Mascellani 	{
1168afe45277SGiovanni Mascellani 		.ident = "Dell Precision 5530",
1169afe45277SGiovanni Mascellani 		.matches = {
1170afe45277SGiovanni Mascellani 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1171afe45277SGiovanni Mascellani 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
1172afe45277SGiovanni Mascellani 		},
1173afe45277SGiovanni Mascellani 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1174afe45277SGiovanni Mascellani 	},
1175afe45277SGiovanni Mascellani 	{
11760ca8bb2cSJeffrey Lin 		.ident = "Dell Latitude 5480",
11770ca8bb2cSJeffrey Lin 		.matches = {
11780ca8bb2cSJeffrey Lin 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11790ca8bb2cSJeffrey Lin 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"),
11800ca8bb2cSJeffrey Lin 		},
11810ca8bb2cSJeffrey Lin 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
11820ca8bb2cSJeffrey Lin 	},
11830ca8bb2cSJeffrey Lin 	{
1184afe45277SGiovanni Mascellani 		.ident = "Dell Latitude E6440",
1185afe45277SGiovanni Mascellani 		.matches = {
1186afe45277SGiovanni Mascellani 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1187afe45277SGiovanni Mascellani 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
1188afe45277SGiovanni Mascellani 		},
1189afe45277SGiovanni Mascellani 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1190afe45277SGiovanni Mascellani 	},
1191807b8c29SSebastian Oechsle 	{
1192807b8c29SSebastian Oechsle 		.ident = "Dell Latitude E7440",
1193807b8c29SSebastian Oechsle 		.matches = {
1194807b8c29SSebastian Oechsle 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1195807b8c29SSebastian Oechsle 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E7440"),
1196807b8c29SSebastian Oechsle 		},
1197807b8c29SSebastian Oechsle 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1198807b8c29SSebastian Oechsle 	},
1199afe45277SGiovanni Mascellani 	{ }
1200afe45277SGiovanni Mascellani };
1201afe45277SGiovanni Mascellani 
12021492fa21SArmin Wolf static int __init dell_smm_probe(struct platform_device *pdev)
1203a5afba16SPali Rohár {
1204ba04d73cSArmin Wolf 	struct dell_smm_data *data;
1205afe45277SGiovanni Mascellani 	const struct dmi_system_id *id, *fan_control;
1206a5afba16SPali Rohár 	int fan, ret;
1207a5afba16SPali Rohár 
1208ba04d73cSArmin Wolf 	data = devm_kzalloc(&pdev->dev, sizeof(struct dell_smm_data), GFP_KERNEL);
1209ba04d73cSArmin Wolf 	if (!data)
1210ba04d73cSArmin Wolf 		return -ENOMEM;
1211ba04d73cSArmin Wolf 
1212ba04d73cSArmin Wolf 	mutex_init(&data->i8k_mutex);
1213ba04d73cSArmin Wolf 	data->i8k_fan_mult = I8K_FAN_MULT;
1214ba04d73cSArmin Wolf 	data->i8k_fan_max = I8K_FAN_HIGH;
1215ba04d73cSArmin Wolf 	platform_set_drvdata(pdev, data);
1216ba04d73cSArmin Wolf 
1217f480ea90SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
1218ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan support\n");
1219f480ea90SPali Rohár 		if (!force)
1220ba04d73cSArmin Wolf 			data->disallow_fan_support = true;
1221f480ea90SPali Rohár 	}
1222f480ea90SPali Rohár 
1223836ad112SPali Rohár 	if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
1224ba04d73cSArmin Wolf 		dev_warn(&pdev->dev, "broken Dell BIOS detected, disallow fan type call\n");
1225836ad112SPali Rohár 		if (!force)
1226ba04d73cSArmin Wolf 			data->disallow_fan_type_call = true;
1227836ad112SPali Rohár 	}
12282744d2fdSPali Rohár 
1229ba04d73cSArmin Wolf 	strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
1230ba04d73cSArmin Wolf 		sizeof(data->bios_version));
1231ba04d73cSArmin Wolf 	strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
1232ba04d73cSArmin Wolf 		sizeof(data->bios_machineid));
1233a5afba16SPali Rohár 
1234a5afba16SPali Rohár 	/*
1235a5afba16SPali Rohár 	 * Set fan multiplier and maximal fan speed from dmi config
1236a5afba16SPali Rohár 	 * Values specified in module parameters override values from dmi
1237a5afba16SPali Rohár 	 */
1238a5afba16SPali Rohár 	id = dmi_first_match(i8k_dmi_table);
1239a5afba16SPali Rohár 	if (id && id->driver_data) {
1240a5afba16SPali Rohár 		const struct i8k_config_data *conf = id->driver_data;
12411492fa21SArmin Wolf 
1242a5afba16SPali Rohár 		if (!fan_mult && conf->fan_mult)
1243a5afba16SPali Rohár 			fan_mult = conf->fan_mult;
1244ba04d73cSArmin Wolf 
1245a5afba16SPali Rohár 		if (!fan_max && conf->fan_max)
1246a5afba16SPali Rohár 			fan_max = conf->fan_max;
1247a5afba16SPali Rohár 	}
1248a5afba16SPali Rohár 
1249ba04d73cSArmin Wolf 	data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
1250ba04d73cSArmin Wolf 	data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
1251a5afba16SPali Rohár 
1252afe45277SGiovanni Mascellani 	fan_control = dmi_first_match(i8k_whitelist_fan_control);
1253afe45277SGiovanni Mascellani 	if (fan_control && fan_control->driver_data) {
1254ba04d73cSArmin Wolf 		const struct i8k_fan_control_data *control = fan_control->driver_data;
1255afe45277SGiovanni Mascellani 
1256ba04d73cSArmin Wolf 		data->manual_fan = control->manual_fan;
1257ba04d73cSArmin Wolf 		data->auto_fan = control->auto_fan;
1258ba04d73cSArmin Wolf 		dev_info(&pdev->dev, "enabling support for setting automatic/manual fan control\n");
1259afe45277SGiovanni Mascellani 	}
1260afe45277SGiovanni Mascellani 
1261a5afba16SPali Rohár 	if (!fan_mult) {
1262a5afba16SPali Rohár 		/*
1263a5afba16SPali Rohár 		 * Autodetect fan multiplier based on nominal rpm
1264a5afba16SPali Rohár 		 * If fan reports rpm value too high then set multiplier to 1
1265a5afba16SPali Rohár 		 */
1266a5afba16SPali Rohár 		for (fan = 0; fan < 2; ++fan) {
1267ba04d73cSArmin Wolf 			ret = i8k_get_fan_nominal_speed(data, fan, data->i8k_fan_max);
1268a5afba16SPali Rohár 			if (ret < 0)
1269a5afba16SPali Rohár 				continue;
1270ba04d73cSArmin Wolf 
1271a5afba16SPali Rohár 			if (ret > I8K_FAN_MAX_RPM)
1272ba04d73cSArmin Wolf 				data->i8k_fan_mult = 1;
1273a5afba16SPali Rohár 			break;
1274a5afba16SPali Rohár 		}
1275a5afba16SPali Rohár 	} else {
1276a5afba16SPali Rohár 		/* Fan multiplier was specified in module param or in dmi */
1277ba04d73cSArmin Wolf 		data->i8k_fan_mult = fan_mult;
1278a5afba16SPali Rohár 	}
1279a5afba16SPali Rohár 
12801492fa21SArmin Wolf 	ret = dell_smm_init_hwmon(&pdev->dev);
12811492fa21SArmin Wolf 	if (ret)
12821492fa21SArmin Wolf 		return ret;
12831492fa21SArmin Wolf 
1284a2cb66b4SArmin Wolf 	i8k_init_procfs(&pdev->dev);
12851492fa21SArmin Wolf 
12861492fa21SArmin Wolf 	return 0;
12871492fa21SArmin Wolf }
12881492fa21SArmin Wolf 
12891492fa21SArmin Wolf static struct platform_driver dell_smm_driver = {
12901492fa21SArmin Wolf 	.driver		= {
12911492fa21SArmin Wolf 		.name	= KBUILD_MODNAME,
12921492fa21SArmin Wolf 	},
12931492fa21SArmin Wolf };
12941492fa21SArmin Wolf 
12951492fa21SArmin Wolf static struct platform_device *dell_smm_device;
12961492fa21SArmin Wolf 
12971492fa21SArmin Wolf /*
12981492fa21SArmin Wolf  * Probe for the presence of a supported laptop.
12991492fa21SArmin Wolf  */
1300a5afba16SPali Rohár static int __init i8k_init(void)
1301a5afba16SPali Rohár {
13021492fa21SArmin Wolf 	/*
13031492fa21SArmin Wolf 	 * Get DMI information
13041492fa21SArmin Wolf 	 */
13051492fa21SArmin Wolf 	if (!dmi_check_system(i8k_dmi_table)) {
13061492fa21SArmin Wolf 		if (!ignore_dmi && !force)
1307a5afba16SPali Rohár 			return -ENODEV;
1308a5afba16SPali Rohár 
13091492fa21SArmin Wolf 		pr_info("not running on a supported Dell system.\n");
13101492fa21SArmin Wolf 		pr_info("vendor=%s, model=%s, version=%s\n",
13111492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_SYS_VENDOR),
13121492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_PRODUCT_NAME),
13131492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_BIOS_VERSION));
13141492fa21SArmin Wolf 	}
1315039ae585SPali Rohár 
13161492fa21SArmin Wolf 	/*
13171492fa21SArmin Wolf 	 * Get SMM Dell signature
13181492fa21SArmin Wolf 	 */
13191492fa21SArmin Wolf 	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
13201492fa21SArmin Wolf 	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
13211492fa21SArmin Wolf 		pr_err("unable to get SMM Dell signature\n");
13221492fa21SArmin Wolf 		if (!force)
13231492fa21SArmin Wolf 			return -ENODEV;
13241492fa21SArmin Wolf 	}
13251492fa21SArmin Wolf 
13261492fa21SArmin Wolf 	dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
13271492fa21SArmin Wolf 						 0);
13281492fa21SArmin Wolf 
13291492fa21SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_device);
1330a5afba16SPali Rohár }
1331a5afba16SPali Rohár 
1332a5afba16SPali Rohár static void __exit i8k_exit(void)
1333a5afba16SPali Rohár {
13341492fa21SArmin Wolf 	platform_device_unregister(dell_smm_device);
13351492fa21SArmin Wolf 	platform_driver_unregister(&dell_smm_driver);
1336a5afba16SPali Rohár }
1337a5afba16SPali Rohár 
1338a5afba16SPali Rohár module_init(i8k_init);
1339a5afba16SPali Rohár module_exit(i8k_exit);
1340