xref: /linux/drivers/hwmon/dell-smm-hwmon.c (revision 7ec462100ef9142344ddbf86f2c3008b97acddbe)
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 
15b7a4706fSArmin Wolf #include <linux/acpi.h>
166105870fSArmin Wolf #include <linux/capability.h>
1727046a3fSJuergen Gross #include <linux/cpu.h>
186105870fSArmin Wolf #include <linux/ctype.h>
19a5afba16SPali Rohár #include <linux/delay.h>
206105870fSArmin Wolf #include <linux/dmi.h>
211492fa21SArmin Wolf #include <linux/err.h>
22e64325e8SArmin Wolf #include <linux/errno.h>
236105870fSArmin Wolf #include <linux/hwmon.h>
24a5afba16SPali Rohár #include <linux/init.h>
25e0d3f7cbSArmin Wolf #include <linux/kconfig.h>
264d9983deSArmin Wolf #include <linux/kernel.h>
276105870fSArmin Wolf #include <linux/module.h>
286105870fSArmin Wolf #include <linux/mutex.h>
296105870fSArmin Wolf #include <linux/platform_device.h>
30a5afba16SPali Rohár #include <linux/proc_fs.h>
31a5afba16SPali Rohár #include <linux/seq_file.h>
32e0d3f7cbSArmin Wolf #include <linux/slab.h>
3327046a3fSJuergen Gross #include <linux/smp.h>
34e0d3f7cbSArmin Wolf #include <linux/string.h>
35e0d3f7cbSArmin Wolf #include <linux/thermal.h>
366105870fSArmin Wolf #include <linux/types.h>
376105870fSArmin Wolf #include <linux/uaccess.h>
38b7a4706fSArmin Wolf #include <linux/wmi.h>
39a5afba16SPali Rohár 
40a5afba16SPali Rohár #include <linux/i8k.h>
41*5f60d5f6SAl Viro #include <linux/unaligned.h>
42a5afba16SPali Rohár 
43a5afba16SPali Rohár #define I8K_SMM_FN_STATUS	0x0025
44a5afba16SPali Rohár #define I8K_SMM_POWER_STATUS	0x0069
45a5afba16SPali Rohár #define I8K_SMM_SET_FAN		0x01a3
46a5afba16SPali Rohár #define I8K_SMM_GET_FAN		0x00a3
47a5afba16SPali Rohár #define I8K_SMM_GET_SPEED	0x02a3
48a5afba16SPali Rohár #define I8K_SMM_GET_FAN_TYPE	0x03a3
49a5afba16SPali Rohár #define I8K_SMM_GET_NOM_SPEED	0x04a3
50a5afba16SPali Rohár #define I8K_SMM_GET_TEMP	0x10a3
51a5afba16SPali Rohár #define I8K_SMM_GET_TEMP_TYPE	0x11a3
52a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG1	0xfea3
53a5afba16SPali Rohár #define I8K_SMM_GET_DELL_SIG2	0xffa3
54a5afba16SPali Rohár 
55981c5f3cSArmin Wolf /* in usecs */
56981c5f3cSArmin Wolf #define DELL_SMM_MAX_DURATION  250000
57981c5f3cSArmin Wolf 
58a5afba16SPali Rohár #define I8K_FAN_MULT		30
594fc1a51cSArmin Wolf #define I8K_FAN_RPM_THRESHOLD	1000
60a5afba16SPali Rohár #define I8K_MAX_TEMP		127
61a5afba16SPali Rohár 
62a5afba16SPali Rohár #define I8K_FN_NONE		0x00
63a5afba16SPali Rohár #define I8K_FN_UP		0x01
64a5afba16SPali Rohár #define I8K_FN_DOWN		0x02
65a5afba16SPali Rohár #define I8K_FN_MUTE		0x04
66a5afba16SPali Rohár #define I8K_FN_MASK		0x07
67a5afba16SPali Rohár #define I8K_FN_SHIFT		8
68a5afba16SPali Rohár 
69a5afba16SPali Rohár #define I8K_POWER_AC		0x05
70a5afba16SPali Rohár #define I8K_POWER_BATTERY	0x01
71a5afba16SPali Rohár 
72b7a4706fSArmin Wolf #define DELL_SMM_WMI_GUID	"F1DDEE52-063C-4784-A11E-8A06684B9B01"
73b7a4706fSArmin Wolf #define DELL_SMM_LEGACY_EXECUTE	0x1
74b7a4706fSArmin Wolf 
75deeba244SArmin Wolf #define DELL_SMM_NO_TEMP	10
76deeba244SArmin Wolf #define DELL_SMM_NO_FANS	3
77deeba244SArmin Wolf 
78744f7be3SArmin Wolf struct smm_regs {
79744f7be3SArmin Wolf 	unsigned int eax;
80744f7be3SArmin Wolf 	unsigned int ebx;
81744f7be3SArmin Wolf 	unsigned int ecx;
82744f7be3SArmin Wolf 	unsigned int edx;
83744f7be3SArmin Wolf 	unsigned int esi;
84744f7be3SArmin Wolf 	unsigned int edi;
85744f7be3SArmin Wolf };
86744f7be3SArmin Wolf 
87744f7be3SArmin Wolf struct dell_smm_ops {
88744f7be3SArmin Wolf 	struct device *smm_dev;
89744f7be3SArmin Wolf 	int (*smm_call)(struct device *smm_dev, struct smm_regs *regs);
90744f7be3SArmin Wolf };
91744f7be3SArmin Wolf 
92ba04d73cSArmin Wolf struct dell_smm_data {
93ba04d73cSArmin Wolf 	struct mutex i8k_mutex; /* lock for sensors writes */
94ba04d73cSArmin Wolf 	char bios_version[4];
95ba04d73cSArmin Wolf 	char bios_machineid[16];
96ba04d73cSArmin Wolf 	uint i8k_fan_mult;
97ba04d73cSArmin Wolf 	uint i8k_pwm_mult;
98ba04d73cSArmin Wolf 	uint i8k_fan_max;
99deeba244SArmin Wolf 	int temp_type[DELL_SMM_NO_TEMP];
100deeba244SArmin Wolf 	bool fan[DELL_SMM_NO_FANS];
101deeba244SArmin Wolf 	int fan_type[DELL_SMM_NO_FANS];
102b1986c8eSArmin Wolf 	int *fan_nominal_speed[DELL_SMM_NO_FANS];
103744f7be3SArmin Wolf 	const struct dell_smm_ops *ops;
104ba04d73cSArmin Wolf };
105a5afba16SPali Rohár 
106e0d3f7cbSArmin Wolf struct dell_smm_cooling_data {
107e0d3f7cbSArmin Wolf 	u8 fan_num;
108e0d3f7cbSArmin Wolf 	struct dell_smm_data *data;
109e0d3f7cbSArmin Wolf };
110e0d3f7cbSArmin Wolf 
1116a57a219SAhelenia Ziemiańska MODULE_AUTHOR("Massimo Dal Zotto <dz@debian.org>");
112149ed3d4SPali Rohár MODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
113039ae585SPali Rohár MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
114a5afba16SPali Rohár MODULE_LICENSE("GPL");
115a5afba16SPali Rohár MODULE_ALIAS("i8k");
116a5afba16SPali Rohár 
117a5afba16SPali Rohár static bool force;
1187cd682b0SArmin Wolf module_param_unsafe(force, bool, 0);
1197cd682b0SArmin Wolf MODULE_PARM_DESC(force, "Force loading without checking for supported models and features");
120a5afba16SPali Rohár 
121a5afba16SPali Rohár static bool ignore_dmi;
122a5afba16SPali Rohár module_param(ignore_dmi, bool, 0);
123a5afba16SPali Rohár MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
124a5afba16SPali Rohár 
125039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
1267613663cSPali Rohár static bool restricted = true;
127a5afba16SPali Rohár module_param(restricted, bool, 0);
1287613663cSPali Rohár MODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)");
129a5afba16SPali Rohár 
130a5afba16SPali Rohár static bool power_status;
131a5afba16SPali Rohár module_param(power_status, bool, 0600);
1327613663cSPali Rohár MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)");
133039ae585SPali Rohár #endif
134a5afba16SPali Rohár 
135a5afba16SPali Rohár static uint fan_mult;
136a5afba16SPali Rohár module_param(fan_mult, uint, 0);
137a5afba16SPali Rohár MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
138a5afba16SPali Rohár 
139a5afba16SPali Rohár static uint fan_max;
140a5afba16SPali Rohár module_param(fan_max, uint, 0);
141a5afba16SPali Rohár MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
142a5afba16SPali Rohár 
1437fd2e1caSArmin Wolf static bool disallow_fan_type_call, disallow_fan_support;
1447fd2e1caSArmin Wolf 
1459848fcf4SArmin Wolf static unsigned int manual_fan, auto_fan;
1469848fcf4SArmin Wolf 
147deeba244SArmin Wolf static const char * const temp_labels[] = {
148deeba244SArmin Wolf 	"CPU",
149deeba244SArmin Wolf 	"GPU",
150deeba244SArmin Wolf 	"SODIMM",
151deeba244SArmin Wolf 	"Other",
152deeba244SArmin Wolf 	"Ambient",
153deeba244SArmin Wolf 	"Other",
154deeba244SArmin Wolf };
155deeba244SArmin Wolf 
156deeba244SArmin Wolf static const char * const fan_labels[] = {
157deeba244SArmin Wolf 	"Processor Fan",
158deeba244SArmin Wolf 	"Motherboard Fan",
159deeba244SArmin Wolf 	"Video Fan",
160deeba244SArmin Wolf 	"Power Supply Fan",
161deeba244SArmin Wolf 	"Chipset Fan",
162deeba244SArmin Wolf 	"Other Fan",
163deeba244SArmin Wolf };
164deeba244SArmin Wolf 
165deeba244SArmin Wolf static const char * const docking_labels[] = {
166deeba244SArmin Wolf 	"Docking Processor Fan",
167deeba244SArmin Wolf 	"Docking Motherboard Fan",
168deeba244SArmin Wolf 	"Docking Video Fan",
169deeba244SArmin Wolf 	"Docking Power Supply Fan",
170deeba244SArmin Wolf 	"Docking Chipset Fan",
171deeba244SArmin Wolf 	"Docking Other Fan",
172deeba244SArmin Wolf };
173deeba244SArmin Wolf 
i8k_get_dmi_data(int field)174c9363cdfSArmin Wolf static inline const char __init *i8k_get_dmi_data(int field)
175a5afba16SPali Rohár {
176a5afba16SPali Rohár 	const char *dmi_data = dmi_get_system_info(field);
177a5afba16SPali Rohár 
178a5afba16SPali Rohár 	return dmi_data && *dmi_data ? dmi_data : "?";
179a5afba16SPali Rohár }
180a5afba16SPali Rohár 
181a5afba16SPali Rohár /*
182a5afba16SPali Rohár  * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
183a5afba16SPali Rohár  */
i8k_smm_func(void * par)18427046a3fSJuergen Gross static int i8k_smm_func(void *par)
185a5afba16SPali Rohár {
18627046a3fSJuergen Gross 	struct smm_regs *regs = par;
187c10d52d6SArmin Wolf 	unsigned char carry;
1889d58bec0SPali Rohár 
189a5afba16SPali Rohár 	/* SMM requires CPU 0 */
19027046a3fSJuergen Gross 	if (smp_processor_id() != 0)
19127046a3fSJuergen Gross 		return -EBUSY;
192a5afba16SPali Rohár 
193c10d52d6SArmin Wolf 	asm volatile("out %%al,$0xb2\n\t"
194a5afba16SPali Rohár 		     "out %%al,$0x84\n\t"
195c10d52d6SArmin Wolf 		     "setc %0\n"
196c10d52d6SArmin Wolf 		     : "=mr" (carry),
197c10d52d6SArmin Wolf 		       "+a" (regs->eax),
198c10d52d6SArmin Wolf 		       "+b" (regs->ebx),
199c10d52d6SArmin Wolf 		       "+c" (regs->ecx),
200c10d52d6SArmin Wolf 		       "+d" (regs->edx),
201c10d52d6SArmin Wolf 		       "+S" (regs->esi),
202c10d52d6SArmin Wolf 		       "+D" (regs->edi));
203a5afba16SPali Rohár 
204744f7be3SArmin Wolf 	if (carry)
205c10d52d6SArmin Wolf 		return -EINVAL;
206c10d52d6SArmin Wolf 
207c10d52d6SArmin Wolf 	return 0;
208a5afba16SPali Rohár }
209a5afba16SPali Rohár 
210a5afba16SPali Rohár /*
21127046a3fSJuergen Gross  * Call the System Management Mode BIOS.
21227046a3fSJuergen Gross  */
i8k_smm_call(struct device * dummy,struct smm_regs * regs)213744f7be3SArmin Wolf static int i8k_smm_call(struct device *dummy, struct smm_regs *regs)
21427046a3fSJuergen Gross {
21527046a3fSJuergen Gross 	int ret;
21627046a3fSJuergen Gross 
217e104d530SSebastian Andrzej Siewior 	cpus_read_lock();
21827046a3fSJuergen Gross 	ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
219e104d530SSebastian Andrzej Siewior 	cpus_read_unlock();
22027046a3fSJuergen Gross 
22127046a3fSJuergen Gross 	return ret;
22227046a3fSJuergen Gross }
22327046a3fSJuergen Gross 
224744f7be3SArmin Wolf static const struct dell_smm_ops i8k_smm_ops = {
225744f7be3SArmin Wolf 	.smm_call = i8k_smm_call,
226744f7be3SArmin Wolf };
227744f7be3SArmin Wolf 
228b7a4706fSArmin Wolf /*
229b7a4706fSArmin Wolf  * Call the System Management Mode BIOS over WMI.
230b7a4706fSArmin Wolf  */
wmi_parse_register(u8 * buffer,u32 length,unsigned int * reg)231b7a4706fSArmin Wolf static ssize_t wmi_parse_register(u8 *buffer, u32 length, unsigned int *reg)
232b7a4706fSArmin Wolf {
233b7a4706fSArmin Wolf 	__le32 value;
234b7a4706fSArmin Wolf 	u32 reg_size;
235b7a4706fSArmin Wolf 
236b7a4706fSArmin Wolf 	if (length <= sizeof(reg_size))
237b7a4706fSArmin Wolf 		return -ENODATA;
238b7a4706fSArmin Wolf 
239b7a4706fSArmin Wolf 	reg_size = get_unaligned_le32(buffer);
240b7a4706fSArmin Wolf 	if (!reg_size || reg_size > sizeof(value))
241b7a4706fSArmin Wolf 		return -ENOMSG;
242b7a4706fSArmin Wolf 
243b7a4706fSArmin Wolf 	if (length < sizeof(reg_size) + reg_size)
244b7a4706fSArmin Wolf 		return -ENODATA;
245b7a4706fSArmin Wolf 
246b7a4706fSArmin Wolf 	memcpy_and_pad(&value, sizeof(value), buffer + sizeof(reg_size), reg_size, 0);
247b7a4706fSArmin Wolf 	*reg = le32_to_cpu(value);
248b7a4706fSArmin Wolf 
249b7a4706fSArmin Wolf 	return reg_size + sizeof(reg_size);
250b7a4706fSArmin Wolf }
251b7a4706fSArmin Wolf 
wmi_parse_response(u8 * buffer,u32 length,struct smm_regs * regs)252b7a4706fSArmin Wolf static int wmi_parse_response(u8 *buffer, u32 length, struct smm_regs *regs)
253b7a4706fSArmin Wolf {
254b7a4706fSArmin Wolf 	unsigned int *registers[] = {
255b7a4706fSArmin Wolf 		&regs->eax,
256b7a4706fSArmin Wolf 		&regs->ebx,
257b7a4706fSArmin Wolf 		&regs->ecx,
258b7a4706fSArmin Wolf 		&regs->edx
259b7a4706fSArmin Wolf 	};
260b7a4706fSArmin Wolf 	u32 offset = 0;
261b7a4706fSArmin Wolf 	ssize_t ret;
262b7a4706fSArmin Wolf 	int i;
263b7a4706fSArmin Wolf 
264b7a4706fSArmin Wolf 	for (i = 0; i < ARRAY_SIZE(registers); i++) {
265b7a4706fSArmin Wolf 		if (offset >= length)
266b7a4706fSArmin Wolf 			return -ENODATA;
267b7a4706fSArmin Wolf 
268b7a4706fSArmin Wolf 		ret = wmi_parse_register(buffer + offset, length - offset, registers[i]);
269b7a4706fSArmin Wolf 		if (ret < 0)
270b7a4706fSArmin Wolf 			return ret;
271b7a4706fSArmin Wolf 
272b7a4706fSArmin Wolf 		offset += ret;
273b7a4706fSArmin Wolf 	}
274b7a4706fSArmin Wolf 
275b7a4706fSArmin Wolf 	if (offset != length)
276b7a4706fSArmin Wolf 		return -ENOMSG;
277b7a4706fSArmin Wolf 
278b7a4706fSArmin Wolf 	return 0;
279b7a4706fSArmin Wolf }
280b7a4706fSArmin Wolf 
wmi_smm_call(struct device * dev,struct smm_regs * regs)281b7a4706fSArmin Wolf static int wmi_smm_call(struct device *dev, struct smm_regs *regs)
282b7a4706fSArmin Wolf {
283b7a4706fSArmin Wolf 	struct wmi_device *wdev = container_of(dev, struct wmi_device, dev);
284b7a4706fSArmin Wolf 	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
285b7a4706fSArmin Wolf 	u32 wmi_payload[] = {
286b7a4706fSArmin Wolf 		sizeof(regs->eax),
287b7a4706fSArmin Wolf 		regs->eax,
288b7a4706fSArmin Wolf 		sizeof(regs->ebx),
289b7a4706fSArmin Wolf 		regs->ebx,
290b7a4706fSArmin Wolf 		sizeof(regs->ecx),
291b7a4706fSArmin Wolf 		regs->ecx,
292b7a4706fSArmin Wolf 		sizeof(regs->edx),
293b7a4706fSArmin Wolf 		regs->edx
294b7a4706fSArmin Wolf 	};
295b7a4706fSArmin Wolf 	const struct acpi_buffer in = {
296b7a4706fSArmin Wolf 		.length = sizeof(wmi_payload),
297b7a4706fSArmin Wolf 		.pointer = &wmi_payload,
298b7a4706fSArmin Wolf 	};
299b7a4706fSArmin Wolf 	union acpi_object *obj;
300b7a4706fSArmin Wolf 	acpi_status status;
301b7a4706fSArmin Wolf 	int ret;
302b7a4706fSArmin Wolf 
303b7a4706fSArmin Wolf 	status = wmidev_evaluate_method(wdev, 0x0, DELL_SMM_LEGACY_EXECUTE, &in, &out);
304b7a4706fSArmin Wolf 	if (ACPI_FAILURE(status))
305b7a4706fSArmin Wolf 		return -EIO;
306b7a4706fSArmin Wolf 
307b7a4706fSArmin Wolf 	obj = out.pointer;
308b7a4706fSArmin Wolf 	if (!obj)
309b7a4706fSArmin Wolf 		return -ENODATA;
310b7a4706fSArmin Wolf 
311b7a4706fSArmin Wolf 	if (obj->type != ACPI_TYPE_BUFFER) {
312b7a4706fSArmin Wolf 		ret = -ENOMSG;
313b7a4706fSArmin Wolf 
314b7a4706fSArmin Wolf 		goto err_free;
315b7a4706fSArmin Wolf 	}
316b7a4706fSArmin Wolf 
317b7a4706fSArmin Wolf 	ret = wmi_parse_response(obj->buffer.pointer, obj->buffer.length, regs);
318b7a4706fSArmin Wolf 
319b7a4706fSArmin Wolf err_free:
320b7a4706fSArmin Wolf 	kfree(obj);
321b7a4706fSArmin Wolf 
322b7a4706fSArmin Wolf 	return ret;
323b7a4706fSArmin Wolf }
324b7a4706fSArmin Wolf 
dell_smm_call(const struct dell_smm_ops * ops,struct smm_regs * regs)325744f7be3SArmin Wolf static int dell_smm_call(const struct dell_smm_ops *ops, struct smm_regs *regs)
326744f7be3SArmin Wolf {
327744f7be3SArmin Wolf 	unsigned int eax = regs->eax;
328744f7be3SArmin Wolf 	unsigned int ebx = regs->ebx;
329744f7be3SArmin Wolf 	long long duration;
330744f7be3SArmin Wolf 	ktime_t calltime;
331744f7be3SArmin Wolf 	int ret;
332744f7be3SArmin Wolf 
333744f7be3SArmin Wolf 	calltime = ktime_get();
334744f7be3SArmin Wolf 	ret = ops->smm_call(ops->smm_dev, regs);
335744f7be3SArmin Wolf 	duration = ktime_us_delta(ktime_get(), calltime);
336744f7be3SArmin Wolf 
337744f7be3SArmin Wolf 	pr_debug("SMM(0x%.4x 0x%.4x) = 0x%.4x status: %d (took %7lld usecs)\n",
338744f7be3SArmin Wolf 		 eax, ebx, regs->eax & 0xffff, ret, duration);
339744f7be3SArmin Wolf 
340744f7be3SArmin Wolf 	if (duration > DELL_SMM_MAX_DURATION)
341744f7be3SArmin Wolf 		pr_warn_once("SMM call took %lld usecs!\n", duration);
342744f7be3SArmin Wolf 
343744f7be3SArmin Wolf 	if (ret < 0)
344744f7be3SArmin Wolf 		return ret;
345744f7be3SArmin Wolf 
346744f7be3SArmin Wolf 	if ((regs->eax & 0xffff) == 0xffff || regs->eax == eax)
347744f7be3SArmin Wolf 		return -EINVAL;
348744f7be3SArmin Wolf 
349744f7be3SArmin Wolf 	return 0;
350744f7be3SArmin Wolf }
351744f7be3SArmin Wolf 
35227046a3fSJuergen Gross /*
353a5afba16SPali Rohár  * Read the fan status.
354a5afba16SPali Rohár  */
i8k_get_fan_status(const struct dell_smm_data * data,u8 fan)3554d9983deSArmin Wolf static int i8k_get_fan_status(const struct dell_smm_data *data, u8 fan)
356a5afba16SPali Rohár {
3574d9983deSArmin Wolf 	struct smm_regs regs = {
3584d9983deSArmin Wolf 		.eax = I8K_SMM_GET_FAN,
3594d9983deSArmin Wolf 		.ebx = fan,
3604d9983deSArmin Wolf 	};
361a5afba16SPali Rohár 
3627fd2e1caSArmin Wolf 	if (disallow_fan_support)
363f480ea90SPali Rohár 		return -EINVAL;
364f480ea90SPali Rohár 
365744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
366a5afba16SPali Rohár }
367a5afba16SPali Rohár 
368a5afba16SPali Rohár /*
369a5afba16SPali Rohár  * Read the fan speed in RPM.
370a5afba16SPali Rohár  */
i8k_get_fan_speed(const struct dell_smm_data * data,u8 fan)3714d9983deSArmin Wolf static int i8k_get_fan_speed(const struct dell_smm_data *data, u8 fan)
372a5afba16SPali Rohár {
3734d9983deSArmin Wolf 	struct smm_regs regs = {
3744d9983deSArmin Wolf 		.eax = I8K_SMM_GET_SPEED,
3754d9983deSArmin Wolf 		.ebx = fan,
3764d9983deSArmin Wolf 	};
377a5afba16SPali Rohár 
3787fd2e1caSArmin Wolf 	if (disallow_fan_support)
379f480ea90SPali Rohár 		return -EINVAL;
380f480ea90SPali Rohár 
381744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff) * data->i8k_fan_mult;
382a5afba16SPali Rohár }
383a5afba16SPali Rohár 
384a5afba16SPali Rohár /*
385a5afba16SPali Rohár  * Read the fan type.
386a5afba16SPali Rohár  */
_i8k_get_fan_type(const struct dell_smm_data * data,u8 fan)3874d9983deSArmin Wolf static int _i8k_get_fan_type(const struct dell_smm_data *data, u8 fan)
388a5afba16SPali Rohár {
3894d9983deSArmin Wolf 	struct smm_regs regs = {
3904d9983deSArmin Wolf 		.eax = I8K_SMM_GET_FAN_TYPE,
3914d9983deSArmin Wolf 		.ebx = fan,
3924d9983deSArmin Wolf 	};
393a5afba16SPali Rohár 
3947fd2e1caSArmin Wolf 	if (disallow_fan_support || disallow_fan_type_call)
3952744d2fdSPali Rohár 		return -EINVAL;
3962744d2fdSPali Rohár 
397744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
398a5afba16SPali Rohár }
399a5afba16SPali Rohár 
i8k_get_fan_type(struct dell_smm_data * data,u8 fan)4004d9983deSArmin Wolf static int i8k_get_fan_type(struct dell_smm_data *data, u8 fan)
4015ce91714SPali Rohár {
4025ce91714SPali Rohár 	/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
403deeba244SArmin Wolf 	if (data->fan_type[fan] == INT_MIN)
404deeba244SArmin Wolf 		data->fan_type[fan] = _i8k_get_fan_type(data, fan);
4055ce91714SPali Rohár 
406deeba244SArmin Wolf 	return data->fan_type[fan];
4075ce91714SPali Rohár }
4085ce91714SPali Rohár 
409a5afba16SPali Rohár /*
410a5afba16SPali Rohár  * Read the fan nominal rpm for specific fan speed.
411a5afba16SPali Rohár  */
i8k_get_fan_nominal_speed(const struct dell_smm_data * data,u8 fan,int speed)412b7a4706fSArmin Wolf static int i8k_get_fan_nominal_speed(const struct dell_smm_data *data, u8 fan, int speed)
413a5afba16SPali Rohár {
4144d9983deSArmin Wolf 	struct smm_regs regs = {
4154d9983deSArmin Wolf 		.eax = I8K_SMM_GET_NOM_SPEED,
4164d9983deSArmin Wolf 		.ebx = fan | (speed << 8),
4174d9983deSArmin Wolf 	};
418a5afba16SPali Rohár 
4197fd2e1caSArmin Wolf 	if (disallow_fan_support)
420f480ea90SPali Rohár 		return -EINVAL;
421f480ea90SPali Rohár 
422744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs) ? : (regs.eax & 0xffff);
423a5afba16SPali Rohár }
424a5afba16SPali Rohár 
425a5afba16SPali Rohár /*
426afe45277SGiovanni Mascellani  * Enable or disable automatic BIOS fan control support
427afe45277SGiovanni Mascellani  */
i8k_enable_fan_auto_mode(const struct dell_smm_data * data,bool enable)428ba04d73cSArmin Wolf static int i8k_enable_fan_auto_mode(const struct dell_smm_data *data, bool enable)
429afe45277SGiovanni Mascellani {
430afe45277SGiovanni Mascellani 	struct smm_regs regs = { };
431afe45277SGiovanni Mascellani 
4327fd2e1caSArmin Wolf 	if (disallow_fan_support)
433afe45277SGiovanni Mascellani 		return -EINVAL;
434afe45277SGiovanni Mascellani 
4359848fcf4SArmin Wolf 	regs.eax = enable ? auto_fan : manual_fan;
436744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs);
437afe45277SGiovanni Mascellani }
438afe45277SGiovanni Mascellani 
439afe45277SGiovanni Mascellani /*
440c0d79987SArmin Wolf  * Set the fan speed (off, low, high, ...).
441a5afba16SPali Rohár  */
i8k_set_fan(const struct dell_smm_data * data,u8 fan,int speed)4424d9983deSArmin Wolf static int i8k_set_fan(const struct dell_smm_data *data, u8 fan, int speed)
443a5afba16SPali Rohár {
444a5afba16SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
445a5afba16SPali Rohár 
4467fd2e1caSArmin Wolf 	if (disallow_fan_support)
447f480ea90SPali Rohár 		return -EINVAL;
448f480ea90SPali Rohár 
449ba04d73cSArmin Wolf 	speed = (speed < 0) ? 0 : ((speed > data->i8k_fan_max) ? data->i8k_fan_max : speed);
4504d9983deSArmin Wolf 	regs.ebx = fan | (speed << 8);
451a5afba16SPali Rohár 
452744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs);
453a5afba16SPali Rohár }
454a5afba16SPali Rohár 
i8k_get_temp_type(const struct dell_smm_data * data,u8 sensor)455b7a4706fSArmin Wolf static int i8k_get_temp_type(const struct dell_smm_data *data, u8 sensor)
456a5afba16SPali Rohár {
4574d9983deSArmin Wolf 	struct smm_regs regs = {
4584d9983deSArmin Wolf 		.eax = I8K_SMM_GET_TEMP_TYPE,
4594d9983deSArmin Wolf 		.ebx = sensor,
4604d9983deSArmin Wolf 	};
461a5afba16SPali Rohár 
462744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
463a5afba16SPali Rohár }
464a5afba16SPali Rohár 
465a5afba16SPali Rohár /*
466a5afba16SPali Rohár  * Read the cpu temperature.
467a5afba16SPali Rohár  */
_i8k_get_temp(const struct dell_smm_data * data,u8 sensor)468744f7be3SArmin Wolf static int _i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
469a5afba16SPali Rohár {
470a5afba16SPali Rohár 	struct smm_regs regs = {
471a5afba16SPali Rohár 		.eax = I8K_SMM_GET_TEMP,
4724d9983deSArmin Wolf 		.ebx = sensor,
473a5afba16SPali Rohár 	};
474a5afba16SPali Rohár 
475744f7be3SArmin Wolf 	return dell_smm_call(data->ops, &regs) ? : regs.eax & 0xff;
476a5afba16SPali Rohár }
477a5afba16SPali Rohár 
i8k_get_temp(const struct dell_smm_data * data,u8 sensor)478744f7be3SArmin Wolf static int i8k_get_temp(const struct dell_smm_data *data, u8 sensor)
479a5afba16SPali Rohár {
480744f7be3SArmin Wolf 	int temp = _i8k_get_temp(data, sensor);
481a5afba16SPali Rohár 
482a5afba16SPali Rohár 	/*
483a5afba16SPali Rohár 	 * Sometimes the temperature sensor returns 0x99, which is out of range.
484a5afba16SPali Rohár 	 * In this case we retry (once) before returning an error.
485a5afba16SPali Rohár 	 # 1003655137 00000058 00005a4b
486a5afba16SPali Rohár 	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
487a5afba16SPali Rohár 	 # 1003655139 00000054 00005c52
488a5afba16SPali Rohár 	 */
489a5afba16SPali Rohár 	if (temp == 0x99) {
490a5afba16SPali Rohár 		msleep(100);
491744f7be3SArmin Wolf 		temp = _i8k_get_temp(data, sensor);
492a5afba16SPali Rohár 	}
493a5afba16SPali Rohár 	/*
494a5afba16SPali Rohár 	 * Return -ENODATA for all invalid temperatures.
495a5afba16SPali Rohár 	 *
496a5afba16SPali Rohár 	 * Known instances are the 0x99 value as seen above as well as
497a5afba16SPali Rohár 	 * 0xc1 (193), which may be returned when trying to read the GPU
498a5afba16SPali Rohár 	 * temperature if the system supports a GPU and it is currently
499a5afba16SPali Rohár 	 * turned off.
500a5afba16SPali Rohár 	 */
501a5afba16SPali Rohár 	if (temp > I8K_MAX_TEMP)
502a5afba16SPali Rohár 		return -ENODATA;
503a5afba16SPali Rohár 
504a5afba16SPali Rohár 	return temp;
505a5afba16SPali Rohár }
506a5afba16SPali Rohár 
dell_smm_get_signature(const struct dell_smm_ops * ops,int req_fn)507b7a4706fSArmin Wolf static int dell_smm_get_signature(const struct dell_smm_ops *ops, int req_fn)
508a5afba16SPali Rohár {
509a5afba16SPali Rohár 	struct smm_regs regs = { .eax = req_fn, };
510a5afba16SPali Rohár 	int rc;
511a5afba16SPali Rohár 
512744f7be3SArmin Wolf 	rc = dell_smm_call(ops, &regs);
513a5afba16SPali Rohár 	if (rc < 0)
514a5afba16SPali Rohár 		return rc;
515a5afba16SPali Rohár 
516a5afba16SPali Rohár 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
517a5afba16SPali Rohár }
518a5afba16SPali Rohár 
519039ae585SPali Rohár #if IS_ENABLED(CONFIG_I8K)
520039ae585SPali Rohár 
521039ae585SPali Rohár /*
522039ae585SPali Rohár  * Read the Fn key status.
523039ae585SPali Rohár  */
i8k_get_fn_status(const struct dell_smm_data * data)524744f7be3SArmin Wolf static int i8k_get_fn_status(const struct dell_smm_data *data)
525039ae585SPali Rohár {
526039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
527039ae585SPali Rohár 	int rc;
528039ae585SPali Rohár 
529744f7be3SArmin Wolf 	rc = dell_smm_call(data->ops, &regs);
530039ae585SPali Rohár 	if (rc < 0)
531039ae585SPali Rohár 		return rc;
532039ae585SPali Rohár 
533039ae585SPali Rohár 	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
534039ae585SPali Rohár 	case I8K_FN_UP:
535039ae585SPali Rohár 		return I8K_VOL_UP;
536039ae585SPali Rohár 	case I8K_FN_DOWN:
537039ae585SPali Rohár 		return I8K_VOL_DOWN;
538039ae585SPali Rohár 	case I8K_FN_MUTE:
539039ae585SPali Rohár 		return I8K_VOL_MUTE;
540039ae585SPali Rohár 	default:
541039ae585SPali Rohár 		return 0;
542039ae585SPali Rohár 	}
543039ae585SPali Rohár }
544039ae585SPali Rohár 
545039ae585SPali Rohár /*
546039ae585SPali Rohár  * Read the power status.
547039ae585SPali Rohár  */
i8k_get_power_status(const struct dell_smm_data * data)548744f7be3SArmin Wolf static int i8k_get_power_status(const struct dell_smm_data *data)
549039ae585SPali Rohár {
550039ae585SPali Rohár 	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
551039ae585SPali Rohár 	int rc;
552039ae585SPali Rohár 
553744f7be3SArmin Wolf 	rc = dell_smm_call(data->ops, &regs);
554039ae585SPali Rohár 	if (rc < 0)
555039ae585SPali Rohár 		return rc;
556039ae585SPali Rohár 
557039ae585SPali Rohár 	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
558039ae585SPali Rohár }
559039ae585SPali Rohár 
560039ae585SPali Rohár /*
561039ae585SPali Rohár  * Procfs interface
562039ae585SPali Rohár  */
563039ae585SPali Rohár 
i8k_ioctl(struct file * fp,unsigned int cmd,unsigned long arg)56487b93329SArmin Wolf static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
565a5afba16SPali Rohár {
566359745d7SMuchun Song 	struct dell_smm_data *data = pde_data(file_inode(fp));
567a5afba16SPali Rohár 	int __user *argp = (int __user *)arg;
56887b93329SArmin Wolf 	int speed, err;
56987b93329SArmin Wolf 	int val = 0;
570a5afba16SPali Rohár 
571a5afba16SPali Rohár 	if (!argp)
572a5afba16SPali Rohár 		return -EINVAL;
573a5afba16SPali Rohár 
574a5afba16SPali Rohár 	switch (cmd) {
575a5afba16SPali Rohár 	case I8K_BIOS_VERSION:
576ba04d73cSArmin Wolf 		if (!isdigit(data->bios_version[0]) || !isdigit(data->bios_version[1]) ||
577ba04d73cSArmin Wolf 		    !isdigit(data->bios_version[2]))
578053ea640SPali Rohár 			return -EINVAL;
579053ea640SPali Rohár 
580ba04d73cSArmin Wolf 		val = (data->bios_version[0] << 16) |
581ba04d73cSArmin Wolf 				(data->bios_version[1] << 8) | data->bios_version[2];
582a5afba16SPali Rohár 
58302405387SArmin Wolf 		if (copy_to_user(argp, &val, sizeof(val)))
58402405387SArmin Wolf 			return -EFAULT;
58502405387SArmin Wolf 
58602405387SArmin Wolf 		return 0;
587a5afba16SPali Rohár 	case I8K_MACHINE_ID:
5887613663cSPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
5897613663cSPali Rohár 			return -EPERM;
5907613663cSPali Rohár 
59102405387SArmin Wolf 		if (copy_to_user(argp, data->bios_machineid, sizeof(data->bios_machineid)))
59202405387SArmin Wolf 			return -EFAULT;
593a5afba16SPali Rohár 
59402405387SArmin Wolf 		return 0;
595a5afba16SPali Rohár 	case I8K_FN_STATUS:
596744f7be3SArmin Wolf 		val = i8k_get_fn_status(data);
597a5afba16SPali Rohár 		break;
598a5afba16SPali Rohár 
599a5afba16SPali Rohár 	case I8K_POWER_STATUS:
600744f7be3SArmin Wolf 		val = i8k_get_power_status(data);
601a5afba16SPali Rohár 		break;
602a5afba16SPali Rohár 
603a5afba16SPali Rohár 	case I8K_GET_TEMP:
604744f7be3SArmin Wolf 		val = i8k_get_temp(data, 0);
605a5afba16SPali Rohár 		break;
606a5afba16SPali Rohár 
607a5afba16SPali Rohár 	case I8K_GET_SPEED:
608a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
609a5afba16SPali Rohár 			return -EFAULT;
610a5afba16SPali Rohár 
6114d9983deSArmin Wolf 		if (val > U8_MAX || val < 0)
6124d9983deSArmin Wolf 			return -EINVAL;
6134d9983deSArmin Wolf 
614ba04d73cSArmin Wolf 		val = i8k_get_fan_speed(data, val);
615a5afba16SPali Rohár 		break;
616a5afba16SPali Rohár 
617a5afba16SPali Rohár 	case I8K_GET_FAN:
618a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
619a5afba16SPali Rohár 			return -EFAULT;
620a5afba16SPali Rohár 
6214d9983deSArmin Wolf 		if (val > U8_MAX || val < 0)
6224d9983deSArmin Wolf 			return -EINVAL;
6234d9983deSArmin Wolf 
624ba04d73cSArmin Wolf 		val = i8k_get_fan_status(data, val);
625a5afba16SPali Rohár 		break;
626a5afba16SPali Rohár 
627a5afba16SPali Rohár 	case I8K_SET_FAN:
628a5afba16SPali Rohár 		if (restricted && !capable(CAP_SYS_ADMIN))
629a5afba16SPali Rohár 			return -EPERM;
630a5afba16SPali Rohár 
631a5afba16SPali Rohár 		if (copy_from_user(&val, argp, sizeof(int)))
632a5afba16SPali Rohár 			return -EFAULT;
633a5afba16SPali Rohár 
6344d9983deSArmin Wolf 		if (val > U8_MAX || val < 0)
6354d9983deSArmin Wolf 			return -EINVAL;
6364d9983deSArmin Wolf 
637a5afba16SPali Rohár 		if (copy_from_user(&speed, argp + 1, sizeof(int)))
638a5afba16SPali Rohár 			return -EFAULT;
639a5afba16SPali Rohár 
64087b93329SArmin Wolf 		mutex_lock(&data->i8k_mutex);
641c0d79987SArmin Wolf 		err = i8k_set_fan(data, val, speed);
642c0d79987SArmin Wolf 		if (err < 0)
64387b93329SArmin Wolf 			val = err;
64487b93329SArmin Wolf 		else
645c0d79987SArmin Wolf 			val = i8k_get_fan_status(data, val);
64687b93329SArmin Wolf 		mutex_unlock(&data->i8k_mutex);
647a5afba16SPali Rohár 		break;
648a5afba16SPali Rohár 
649a5afba16SPali Rohár 	default:
650e64325e8SArmin Wolf 		return -ENOIOCTLCMD;
651a5afba16SPali Rohár 	}
652a5afba16SPali Rohár 
653a5afba16SPali Rohár 	if (val < 0)
654a5afba16SPali Rohár 		return val;
655a5afba16SPali Rohár 
656a5afba16SPali Rohár 	if (copy_to_user(argp, &val, sizeof(int)))
657a5afba16SPali Rohár 		return -EFAULT;
658a5afba16SPali Rohár 
659a5afba16SPali Rohár 	return 0;
660a5afba16SPali Rohár }
661a5afba16SPali Rohár 
662a5afba16SPali Rohár /*
663a5afba16SPali Rohár  * Print the information for /proc/i8k.
664a5afba16SPali Rohár  */
i8k_proc_show(struct seq_file * seq,void * offset)665a5afba16SPali Rohár static int i8k_proc_show(struct seq_file *seq, void *offset)
666a5afba16SPali Rohár {
667ba04d73cSArmin Wolf 	struct dell_smm_data *data = seq->private;
668a5afba16SPali Rohár 	int fn_key, cpu_temp, ac_power;
669a5afba16SPali Rohár 	int left_fan, right_fan, left_speed, right_speed;
670a5afba16SPali Rohár 
671744f7be3SArmin Wolf 	cpu_temp	= i8k_get_temp(data, 0);			/* 11100 µs */
672ba04d73cSArmin Wolf 	left_fan	= i8k_get_fan_status(data, I8K_FAN_LEFT);	/*   580 µs */
673ba04d73cSArmin Wolf 	right_fan	= i8k_get_fan_status(data, I8K_FAN_RIGHT);	/*   580 µs */
674ba04d73cSArmin Wolf 	left_speed	= i8k_get_fan_speed(data, I8K_FAN_LEFT);	/*   580 µs */
675ba04d73cSArmin Wolf 	right_speed	= i8k_get_fan_speed(data, I8K_FAN_RIGHT);	/*   580 µs */
676744f7be3SArmin Wolf 	fn_key		= i8k_get_fn_status(data);			/*   750 µs */
677a5afba16SPali Rohár 	if (power_status)
678744f7be3SArmin Wolf 		ac_power = i8k_get_power_status(data);			/* 14700 µs */
679a5afba16SPali Rohár 	else
680a5afba16SPali Rohár 		ac_power = -1;
681a5afba16SPali Rohár 
682a5afba16SPali Rohár 	/*
683a5afba16SPali Rohár 	 * Info:
684a5afba16SPali Rohár 	 *
685a5afba16SPali Rohár 	 * 1)  Format version (this will change if format changes)
686a5afba16SPali Rohár 	 * 2)  BIOS version
687a5afba16SPali Rohár 	 * 3)  BIOS machine ID
688a5afba16SPali Rohár 	 * 4)  Cpu temperature
689a5afba16SPali Rohár 	 * 5)  Left fan status
690a5afba16SPali Rohár 	 * 6)  Right fan status
691a5afba16SPali Rohár 	 * 7)  Left fan speed
692a5afba16SPali Rohár 	 * 8)  Right fan speed
693a5afba16SPali Rohár 	 * 9)  AC power
694a5afba16SPali Rohár 	 * 10) Fn Key status
695a5afba16SPali Rohár 	 */
696a5afba16SPali Rohár 	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
697a5afba16SPali Rohár 		   I8K_PROC_FMT,
698ba04d73cSArmin Wolf 		   data->bios_version,
699ba04d73cSArmin Wolf 		   (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : data->bios_machineid,
700a5afba16SPali Rohár 		   cpu_temp,
701a5afba16SPali Rohár 		   left_fan, right_fan, left_speed, right_speed,
702a5afba16SPali Rohár 		   ac_power, fn_key);
703a5afba16SPali Rohár 
704a5afba16SPali Rohár 	return 0;
705a5afba16SPali Rohár }
706a5afba16SPali Rohár 
i8k_open_fs(struct inode * inode,struct file * file)707a5afba16SPali Rohár static int i8k_open_fs(struct inode *inode, struct file *file)
708a5afba16SPali Rohár {
709359745d7SMuchun Song 	return single_open(file, i8k_proc_show, pde_data(inode));
710a5afba16SPali Rohár }
711a5afba16SPali Rohár 
71297a32539SAlexey Dobriyan static const struct proc_ops i8k_proc_ops = {
71397a32539SAlexey Dobriyan 	.proc_open	= i8k_open_fs,
71497a32539SAlexey Dobriyan 	.proc_read	= seq_read,
71597a32539SAlexey Dobriyan 	.proc_lseek	= seq_lseek,
71697a32539SAlexey Dobriyan 	.proc_release	= single_release,
71797a32539SAlexey Dobriyan 	.proc_ioctl	= i8k_ioctl,
718039ae585SPali Rohár };
719039ae585SPali Rohár 
i8k_exit_procfs(void * param)720a2cb66b4SArmin Wolf static void i8k_exit_procfs(void *param)
721039ae585SPali Rohár {
722039ae585SPali Rohár 	remove_proc_entry("i8k", NULL);
723039ae585SPali Rohár }
724039ae585SPali Rohár 
i8k_init_procfs(struct device * dev)725a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
726039ae585SPali Rohár {
727ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
728ba04d73cSArmin Wolf 
72920bdeebcSArmin Wolf 	strscpy(data->bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
73020bdeebcSArmin Wolf 		sizeof(data->bios_version));
73120bdeebcSArmin Wolf 	strscpy(data->bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
73220bdeebcSArmin Wolf 		sizeof(data->bios_machineid));
73320bdeebcSArmin Wolf 
734dbd3e6eaSArmin Wolf 	/* Only register exit function if creation was successful */
735dbd3e6eaSArmin Wolf 	if (proc_create_data("i8k", 0, NULL, &i8k_proc_ops, data))
736a2cb66b4SArmin Wolf 		devm_add_action_or_reset(dev, i8k_exit_procfs, NULL);
737039ae585SPali Rohár }
738039ae585SPali Rohár 
739a2cb66b4SArmin Wolf #else
740a2cb66b4SArmin Wolf 
i8k_init_procfs(struct device * dev)741a2cb66b4SArmin Wolf static void __init i8k_init_procfs(struct device *dev)
742039ae585SPali Rohár {
743039ae585SPali Rohár }
744039ae585SPali Rohár 
745039ae585SPali Rohár #endif
746a5afba16SPali Rohár 
dell_smm_get_max_state(struct thermal_cooling_device * dev,unsigned long * state)747e0d3f7cbSArmin Wolf static int dell_smm_get_max_state(struct thermal_cooling_device *dev, unsigned long *state)
748e0d3f7cbSArmin Wolf {
749e0d3f7cbSArmin Wolf 	struct dell_smm_cooling_data *cdata = dev->devdata;
750e0d3f7cbSArmin Wolf 
751e0d3f7cbSArmin Wolf 	*state = cdata->data->i8k_fan_max;
752e0d3f7cbSArmin Wolf 
753e0d3f7cbSArmin Wolf 	return 0;
754e0d3f7cbSArmin Wolf }
755e0d3f7cbSArmin Wolf 
dell_smm_get_cur_state(struct thermal_cooling_device * dev,unsigned long * state)756e0d3f7cbSArmin Wolf static int dell_smm_get_cur_state(struct thermal_cooling_device *dev, unsigned long *state)
757e0d3f7cbSArmin Wolf {
758e0d3f7cbSArmin Wolf 	struct dell_smm_cooling_data *cdata = dev->devdata;
759e0d3f7cbSArmin Wolf 	int ret;
760e0d3f7cbSArmin Wolf 
761e0d3f7cbSArmin Wolf 	ret = i8k_get_fan_status(cdata->data, cdata->fan_num);
762e0d3f7cbSArmin Wolf 	if (ret < 0)
763e0d3f7cbSArmin Wolf 		return ret;
764e0d3f7cbSArmin Wolf 
765e0d3f7cbSArmin Wolf 	*state = ret;
766e0d3f7cbSArmin Wolf 
767e0d3f7cbSArmin Wolf 	return 0;
768e0d3f7cbSArmin Wolf }
769e0d3f7cbSArmin Wolf 
dell_smm_set_cur_state(struct thermal_cooling_device * dev,unsigned long state)770e0d3f7cbSArmin Wolf static int dell_smm_set_cur_state(struct thermal_cooling_device *dev, unsigned long state)
771e0d3f7cbSArmin Wolf {
772e0d3f7cbSArmin Wolf 	struct dell_smm_cooling_data *cdata = dev->devdata;
773e0d3f7cbSArmin Wolf 	struct dell_smm_data *data = cdata->data;
774e0d3f7cbSArmin Wolf 	int ret;
775e0d3f7cbSArmin Wolf 
776e0d3f7cbSArmin Wolf 	if (state > data->i8k_fan_max)
777e0d3f7cbSArmin Wolf 		return -EINVAL;
778e0d3f7cbSArmin Wolf 
779e0d3f7cbSArmin Wolf 	mutex_lock(&data->i8k_mutex);
780e0d3f7cbSArmin Wolf 	ret = i8k_set_fan(data, cdata->fan_num, (int)state);
781e0d3f7cbSArmin Wolf 	mutex_unlock(&data->i8k_mutex);
782e0d3f7cbSArmin Wolf 
783e0d3f7cbSArmin Wolf 	return ret;
784e0d3f7cbSArmin Wolf }
785e0d3f7cbSArmin Wolf 
786e0d3f7cbSArmin Wolf static const struct thermal_cooling_device_ops dell_smm_cooling_ops = {
787e0d3f7cbSArmin Wolf 	.get_max_state = dell_smm_get_max_state,
788e0d3f7cbSArmin Wolf 	.get_cur_state = dell_smm_get_cur_state,
789e0d3f7cbSArmin Wolf 	.set_cur_state = dell_smm_set_cur_state,
790e0d3f7cbSArmin Wolf };
791a5afba16SPali Rohár 
dell_smm_is_visible(const void * drvdata,enum hwmon_sensor_types type,u32 attr,int channel)792deeba244SArmin Wolf static umode_t dell_smm_is_visible(const void *drvdata, enum hwmon_sensor_types type, u32 attr,
793deeba244SArmin Wolf 				   int channel)
794a5afba16SPali Rohár {
795deeba244SArmin Wolf 	const struct dell_smm_data *data = drvdata;
796a5afba16SPali Rohár 
797deeba244SArmin Wolf 	switch (type) {
798deeba244SArmin Wolf 	case hwmon_temp:
799deeba244SArmin Wolf 		switch (attr) {
800deeba244SArmin Wolf 		case hwmon_temp_input:
801c82fdd42SArmin Wolf 			/* _i8k_get_temp() is fine since we do not care about the actual value */
802744f7be3SArmin Wolf 			if (data->temp_type[channel] >= 0 || _i8k_get_temp(data, channel) >= 0)
803c82fdd42SArmin Wolf 				return 0444;
804c82fdd42SArmin Wolf 
805c82fdd42SArmin Wolf 			break;
806deeba244SArmin Wolf 		case hwmon_temp_label:
807deeba244SArmin Wolf 			if (data->temp_type[channel] >= 0)
808deeba244SArmin Wolf 				return 0444;
809deeba244SArmin Wolf 
810deeba244SArmin Wolf 			break;
811deeba244SArmin Wolf 		default:
812deeba244SArmin Wolf 			break;
813deeba244SArmin Wolf 		}
814deeba244SArmin Wolf 		break;
815deeba244SArmin Wolf 	case hwmon_fan:
8167fd2e1caSArmin Wolf 		if (disallow_fan_support)
817deeba244SArmin Wolf 			break;
818deeba244SArmin Wolf 
819deeba244SArmin Wolf 		switch (attr) {
820deeba244SArmin Wolf 		case hwmon_fan_input:
821deeba244SArmin Wolf 			if (data->fan[channel])
822deeba244SArmin Wolf 				return 0444;
823deeba244SArmin Wolf 
824deeba244SArmin Wolf 			break;
825deeba244SArmin Wolf 		case hwmon_fan_label:
8267fd2e1caSArmin Wolf 			if (data->fan[channel] && !disallow_fan_type_call)
827deeba244SArmin Wolf 				return 0444;
828deeba244SArmin Wolf 
829deeba244SArmin Wolf 			break;
830b1986c8eSArmin Wolf 		case hwmon_fan_min:
831b1986c8eSArmin Wolf 		case hwmon_fan_max:
832b1986c8eSArmin Wolf 		case hwmon_fan_target:
833b1986c8eSArmin Wolf 			if (data->fan_nominal_speed[channel])
834b1986c8eSArmin Wolf 				return 0444;
835b1986c8eSArmin Wolf 
836b1986c8eSArmin Wolf 			break;
837deeba244SArmin Wolf 		default:
838deeba244SArmin Wolf 			break;
839deeba244SArmin Wolf 		}
840deeba244SArmin Wolf 		break;
841deeba244SArmin Wolf 	case hwmon_pwm:
8427fd2e1caSArmin Wolf 		if (disallow_fan_support)
843deeba244SArmin Wolf 			break;
844deeba244SArmin Wolf 
845deeba244SArmin Wolf 		switch (attr) {
846deeba244SArmin Wolf 		case hwmon_pwm_input:
847deeba244SArmin Wolf 			if (data->fan[channel])
848deeba244SArmin Wolf 				return 0644;
849deeba244SArmin Wolf 
850deeba244SArmin Wolf 			break;
851deeba244SArmin Wolf 		case hwmon_pwm_enable:
8529848fcf4SArmin Wolf 			if (auto_fan)
853deeba244SArmin Wolf 				/*
854deeba244SArmin Wolf 				 * There is no command for retrieve the current status
855deeba244SArmin Wolf 				 * from BIOS, and userspace/firmware itself can change
856deeba244SArmin Wolf 				 * it.
857deeba244SArmin Wolf 				 * Thus we can only provide write-only access for now.
858deeba244SArmin Wolf 				 */
859deeba244SArmin Wolf 				return 0200;
860deeba244SArmin Wolf 
861deeba244SArmin Wolf 			break;
862deeba244SArmin Wolf 		default:
863deeba244SArmin Wolf 			break;
864deeba244SArmin Wolf 		}
865deeba244SArmin Wolf 		break;
866deeba244SArmin Wolf 	default:
867deeba244SArmin Wolf 		break;
868a5afba16SPali Rohár 	}
869a5afba16SPali Rohár 
870deeba244SArmin Wolf 	return 0;
871a5afba16SPali Rohár }
872a5afba16SPali Rohár 
dell_smm_read(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long * val)873deeba244SArmin Wolf static int dell_smm_read(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
874deeba244SArmin Wolf 			 long *val)
875a5afba16SPali Rohár {
876ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
8774fc1a51cSArmin Wolf 	int mult = data->i8k_fan_mult;
878deeba244SArmin Wolf 	int ret;
879a5afba16SPali Rohár 
880deeba244SArmin Wolf 	switch (type) {
881deeba244SArmin Wolf 	case hwmon_temp:
882deeba244SArmin Wolf 		switch (attr) {
883deeba244SArmin Wolf 		case hwmon_temp_input:
884744f7be3SArmin Wolf 			ret = i8k_get_temp(data, channel);
885deeba244SArmin Wolf 			if (ret < 0)
886deeba244SArmin Wolf 				return ret;
887deeba244SArmin Wolf 
888deeba244SArmin Wolf 			*val = ret * 1000;
889deeba244SArmin Wolf 
890deeba244SArmin Wolf 			return 0;
891deeba244SArmin Wolf 		default:
892deeba244SArmin Wolf 			break;
893deeba244SArmin Wolf 		}
894deeba244SArmin Wolf 		break;
895deeba244SArmin Wolf 	case hwmon_fan:
896deeba244SArmin Wolf 		switch (attr) {
897deeba244SArmin Wolf 		case hwmon_fan_input:
898deeba244SArmin Wolf 			ret = i8k_get_fan_speed(data, channel);
899deeba244SArmin Wolf 			if (ret < 0)
900deeba244SArmin Wolf 				return ret;
901deeba244SArmin Wolf 
902deeba244SArmin Wolf 			*val = ret;
903deeba244SArmin Wolf 
904deeba244SArmin Wolf 			return 0;
905b1986c8eSArmin Wolf 		case hwmon_fan_min:
9064fc1a51cSArmin Wolf 			*val = data->fan_nominal_speed[channel][0] * mult;
907b1986c8eSArmin Wolf 
908b1986c8eSArmin Wolf 			return 0;
909b1986c8eSArmin Wolf 		case hwmon_fan_max:
9104fc1a51cSArmin Wolf 			*val = data->fan_nominal_speed[channel][data->i8k_fan_max] * mult;
911b1986c8eSArmin Wolf 
912b1986c8eSArmin Wolf 			return 0;
913b1986c8eSArmin Wolf 		case hwmon_fan_target:
914b1986c8eSArmin Wolf 			ret = i8k_get_fan_status(data, channel);
915b1986c8eSArmin Wolf 			if (ret < 0)
916b1986c8eSArmin Wolf 				return ret;
917b1986c8eSArmin Wolf 
918b1986c8eSArmin Wolf 			if (ret > data->i8k_fan_max)
919b1986c8eSArmin Wolf 				ret = data->i8k_fan_max;
920b1986c8eSArmin Wolf 
9214fc1a51cSArmin Wolf 			*val = data->fan_nominal_speed[channel][ret] * mult;
922b1986c8eSArmin Wolf 
923b1986c8eSArmin Wolf 			return 0;
924deeba244SArmin Wolf 		default:
925deeba244SArmin Wolf 			break;
926deeba244SArmin Wolf 		}
927deeba244SArmin Wolf 		break;
928deeba244SArmin Wolf 	case hwmon_pwm:
929deeba244SArmin Wolf 		switch (attr) {
930deeba244SArmin Wolf 		case hwmon_pwm_input:
931deeba244SArmin Wolf 			ret = i8k_get_fan_status(data, channel);
932deeba244SArmin Wolf 			if (ret < 0)
933deeba244SArmin Wolf 				return ret;
934deeba244SArmin Wolf 
935deeba244SArmin Wolf 			*val = clamp_val(ret * data->i8k_pwm_mult, 0, 255);
936deeba244SArmin Wolf 
937deeba244SArmin Wolf 			return 0;
938deeba244SArmin Wolf 		default:
939deeba244SArmin Wolf 			break;
940deeba244SArmin Wolf 		}
941deeba244SArmin Wolf 		break;
942deeba244SArmin Wolf 	default:
943deeba244SArmin Wolf 		break;
944deeba244SArmin Wolf 	}
945deeba244SArmin Wolf 
946deeba244SArmin Wolf 	return -EOPNOTSUPP;
947deeba244SArmin Wolf }
948deeba244SArmin Wolf 
dell_smm_fan_label(struct dell_smm_data * data,int channel)949deeba244SArmin Wolf static const char *dell_smm_fan_label(struct dell_smm_data *data, int channel)
950deeba244SArmin Wolf {
951deeba244SArmin Wolf 	bool dock = false;
952deeba244SArmin Wolf 	int type = i8k_get_fan_type(data, channel);
953deeba244SArmin Wolf 
954a5afba16SPali Rohár 	if (type < 0)
955deeba244SArmin Wolf 		return ERR_PTR(type);
956a5afba16SPali Rohár 
957a5afba16SPali Rohár 	if (type & 0x10) {
958a5afba16SPali Rohár 		dock = true;
959a5afba16SPali Rohár 		type &= 0x0F;
960a5afba16SPali Rohár 	}
961a5afba16SPali Rohár 
962deeba244SArmin Wolf 	if (type >= ARRAY_SIZE(fan_labels))
963deeba244SArmin Wolf 		type = ARRAY_SIZE(fan_labels) - 1;
964a5afba16SPali Rohár 
965deeba244SArmin Wolf 	return dock ? docking_labels[type] : fan_labels[type];
966a5afba16SPali Rohár }
967a5afba16SPali Rohár 
dell_smm_read_string(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,const char ** str)968deeba244SArmin Wolf static int dell_smm_read_string(struct device *dev, enum hwmon_sensor_types type, u32 attr,
969deeba244SArmin Wolf 				int channel, const char **str)
970a5afba16SPali Rohár {
971ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
972a5afba16SPali Rohár 
973deeba244SArmin Wolf 	switch (type) {
974deeba244SArmin Wolf 	case hwmon_temp:
975deeba244SArmin Wolf 		switch (attr) {
976deeba244SArmin Wolf 		case hwmon_temp_label:
977deeba244SArmin Wolf 			*str = temp_labels[data->temp_type[channel]];
978deeba244SArmin Wolf 			return 0;
979deeba244SArmin Wolf 		default:
980deeba244SArmin Wolf 			break;
981deeba244SArmin Wolf 		}
982deeba244SArmin Wolf 		break;
983deeba244SArmin Wolf 	case hwmon_fan:
984deeba244SArmin Wolf 		switch (attr) {
985deeba244SArmin Wolf 		case hwmon_fan_label:
986deeba244SArmin Wolf 			*str = dell_smm_fan_label(data, channel);
987deeba244SArmin Wolf 			return PTR_ERR_OR_ZERO(*str);
988deeba244SArmin Wolf 		default:
989deeba244SArmin Wolf 			break;
990deeba244SArmin Wolf 		}
991deeba244SArmin Wolf 		break;
992deeba244SArmin Wolf 	default:
993deeba244SArmin Wolf 		break;
994a5afba16SPali Rohár 	}
995a5afba16SPali Rohár 
996deeba244SArmin Wolf 	return -EOPNOTSUPP;
997a5afba16SPali Rohár }
998a5afba16SPali Rohár 
dell_smm_write(struct device * dev,enum hwmon_sensor_types type,u32 attr,int channel,long val)999deeba244SArmin Wolf static int dell_smm_write(struct device *dev, enum hwmon_sensor_types type, u32 attr, int channel,
1000deeba244SArmin Wolf 			  long val)
1001a5afba16SPali Rohár {
1002ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
1003deeba244SArmin Wolf 	unsigned long pwm;
1004deeba244SArmin Wolf 	bool enable;
1005a5afba16SPali Rohár 	int err;
1006a5afba16SPali Rohár 
1007deeba244SArmin Wolf 	switch (type) {
1008deeba244SArmin Wolf 	case hwmon_pwm:
1009deeba244SArmin Wolf 		switch (attr) {
1010deeba244SArmin Wolf 		case hwmon_pwm_input:
1011deeba244SArmin Wolf 			pwm = clamp_val(DIV_ROUND_CLOSEST(val, data->i8k_pwm_mult), 0,
1012deeba244SArmin Wolf 					data->i8k_fan_max);
1013a5afba16SPali Rohár 
1014ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
1015deeba244SArmin Wolf 			err = i8k_set_fan(data, channel, pwm);
1016ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
1017a5afba16SPali Rohár 
1018deeba244SArmin Wolf 			if (err < 0)
1019afe45277SGiovanni Mascellani 				return err;
1020afe45277SGiovanni Mascellani 
1021deeba244SArmin Wolf 			return 0;
1022deeba244SArmin Wolf 		case hwmon_pwm_enable:
1023deeba244SArmin Wolf 			if (!val)
1024deeba244SArmin Wolf 				return -EINVAL;
1025deeba244SArmin Wolf 
1026afe45277SGiovanni Mascellani 			if (val == 1)
1027afe45277SGiovanni Mascellani 				enable = false;
1028afe45277SGiovanni Mascellani 			else
1029deeba244SArmin Wolf 				enable = true;
1030afe45277SGiovanni Mascellani 
1031ba04d73cSArmin Wolf 			mutex_lock(&data->i8k_mutex);
1032ba04d73cSArmin Wolf 			err = i8k_enable_fan_auto_mode(data, enable);
1033ba04d73cSArmin Wolf 			mutex_unlock(&data->i8k_mutex);
1034afe45277SGiovanni Mascellani 
1035deeba244SArmin Wolf 			if (err < 0)
1036deeba244SArmin Wolf 				return err;
1037deeba244SArmin Wolf 
1038deeba244SArmin Wolf 			return 0;
1039deeba244SArmin Wolf 		default:
1040deeba244SArmin Wolf 			break;
1041deeba244SArmin Wolf 		}
1042deeba244SArmin Wolf 		break;
1043deeba244SArmin Wolf 	default:
1044deeba244SArmin Wolf 		break;
1045afe45277SGiovanni Mascellani 	}
1046afe45277SGiovanni Mascellani 
1047deeba244SArmin Wolf 	return -EOPNOTSUPP;
1048deeba244SArmin Wolf }
1049a5afba16SPali Rohár 
1050deeba244SArmin Wolf static const struct hwmon_ops dell_smm_ops = {
1051deeba244SArmin Wolf 	.is_visible = dell_smm_is_visible,
1052deeba244SArmin Wolf 	.read = dell_smm_read,
1053deeba244SArmin Wolf 	.read_string = dell_smm_read_string,
1054deeba244SArmin Wolf 	.write = dell_smm_write,
1055deeba244SArmin Wolf };
1056deeba244SArmin Wolf 
1057f4ddd8f2SKrzysztof Kozlowski static const struct hwmon_channel_info * const dell_smm_info[] = {
1058deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ),
1059deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(temp,
1060deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1061deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1062deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1063deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1064deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1065deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1066deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1067deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1068deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL,
1069deeba244SArmin Wolf 			   HWMON_T_INPUT | HWMON_T_LABEL
1070deeba244SArmin Wolf 			   ),
1071deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(fan,
1072b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
1073b1986c8eSArmin Wolf 			   HWMON_F_TARGET,
1074b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
1075b1986c8eSArmin Wolf 			   HWMON_F_TARGET,
1076b1986c8eSArmin Wolf 			   HWMON_F_INPUT | HWMON_F_LABEL | HWMON_F_MIN | HWMON_F_MAX |
1077b1986c8eSArmin Wolf 			   HWMON_F_TARGET
1078deeba244SArmin Wolf 			   ),
1079deeba244SArmin Wolf 	HWMON_CHANNEL_INFO(pwm,
1080deeba244SArmin Wolf 			   HWMON_PWM_INPUT | HWMON_PWM_ENABLE,
1081deeba244SArmin Wolf 			   HWMON_PWM_INPUT,
1082deeba244SArmin Wolf 			   HWMON_PWM_INPUT
1083deeba244SArmin Wolf 			   ),
1084a5afba16SPali Rohár 	NULL
1085a5afba16SPali Rohár };
1086a5afba16SPali Rohár 
1087deeba244SArmin Wolf static const struct hwmon_chip_info dell_smm_chip_info = {
1088deeba244SArmin Wolf 	.ops = &dell_smm_ops,
1089deeba244SArmin Wolf 	.info = dell_smm_info,
1090a5afba16SPali Rohár };
1091a5afba16SPali Rohár 
dell_smm_init_cdev(struct device * dev,u8 fan_num)1092b7a4706fSArmin Wolf static int dell_smm_init_cdev(struct device *dev, u8 fan_num)
1093e0d3f7cbSArmin Wolf {
1094e0d3f7cbSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
1095e0d3f7cbSArmin Wolf 	struct thermal_cooling_device *cdev;
1096e0d3f7cbSArmin Wolf 	struct dell_smm_cooling_data *cdata;
1097e0d3f7cbSArmin Wolf 	int ret = 0;
1098e0d3f7cbSArmin Wolf 	char *name;
1099e0d3f7cbSArmin Wolf 
1100e0d3f7cbSArmin Wolf 	name = kasprintf(GFP_KERNEL, "dell-smm-fan%u", fan_num + 1);
1101e0d3f7cbSArmin Wolf 	if (!name)
1102e0d3f7cbSArmin Wolf 		return -ENOMEM;
1103e0d3f7cbSArmin Wolf 
1104e0d3f7cbSArmin Wolf 	cdata = devm_kmalloc(dev, sizeof(*cdata), GFP_KERNEL);
1105e0d3f7cbSArmin Wolf 	if (cdata) {
1106e0d3f7cbSArmin Wolf 		cdata->fan_num = fan_num;
1107e0d3f7cbSArmin Wolf 		cdata->data = data;
1108e0d3f7cbSArmin Wolf 		cdev = devm_thermal_of_cooling_device_register(dev, NULL, name, cdata,
1109e0d3f7cbSArmin Wolf 							       &dell_smm_cooling_ops);
1110e0d3f7cbSArmin Wolf 		if (IS_ERR(cdev)) {
1111e0d3f7cbSArmin Wolf 			devm_kfree(dev, cdata);
1112e0d3f7cbSArmin Wolf 			ret = PTR_ERR(cdev);
1113e0d3f7cbSArmin Wolf 		}
1114e0d3f7cbSArmin Wolf 	} else {
1115e0d3f7cbSArmin Wolf 		ret = -ENOMEM;
1116e0d3f7cbSArmin Wolf 	}
1117e0d3f7cbSArmin Wolf 
1118e0d3f7cbSArmin Wolf 	kfree(name);
1119e0d3f7cbSArmin Wolf 
1120e0d3f7cbSArmin Wolf 	return ret;
1121e0d3f7cbSArmin Wolf }
1122e0d3f7cbSArmin Wolf 
dell_smm_init_hwmon(struct device * dev)1123b7a4706fSArmin Wolf static int dell_smm_init_hwmon(struct device *dev)
1124a5afba16SPali Rohár {
1125ba04d73cSArmin Wolf 	struct dell_smm_data *data = dev_get_drvdata(dev);
1126deeba244SArmin Wolf 	struct device *dell_smm_hwmon_dev;
11274d9983deSArmin Wolf 	int state, err;
11284d9983deSArmin Wolf 	u8 i;
1129a5afba16SPali Rohár 
1130deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_TEMP; i++) {
1131744f7be3SArmin Wolf 		data->temp_type[i] = i8k_get_temp_type(data, i);
1132deeba244SArmin Wolf 		if (data->temp_type[i] < 0)
1133deeba244SArmin Wolf 			continue;
1134a5afba16SPali Rohár 
1135deeba244SArmin Wolf 		if (data->temp_type[i] >= ARRAY_SIZE(temp_labels))
1136deeba244SArmin Wolf 			data->temp_type[i] = ARRAY_SIZE(temp_labels) - 1;
1137deeba244SArmin Wolf 	}
1138deeba244SArmin Wolf 
1139deeba244SArmin Wolf 	for (i = 0; i < DELL_SMM_NO_FANS; i++) {
1140deeba244SArmin Wolf 		data->fan_type[i] = INT_MIN;
1141deeba244SArmin Wolf 		err = i8k_get_fan_status(data, i);
11425ce91714SPali Rohár 		if (err < 0)
1143deeba244SArmin Wolf 			err = i8k_get_fan_type(data, i);
1144b1986c8eSArmin Wolf 
1145b1986c8eSArmin Wolf 		if (err < 0)
1146b1986c8eSArmin Wolf 			continue;
1147b1986c8eSArmin Wolf 
1148deeba244SArmin Wolf 		data->fan[i] = true;
1149e0d3f7cbSArmin Wolf 
1150e0d3f7cbSArmin Wolf 		/* the cooling device is not critical, ignore failures */
1151e0d3f7cbSArmin Wolf 		if (IS_REACHABLE(CONFIG_THERMAL)) {
1152e0d3f7cbSArmin Wolf 			err = dell_smm_init_cdev(dev, i);
1153e0d3f7cbSArmin Wolf 			if (err < 0)
1154e0d3f7cbSArmin Wolf 				dev_warn(dev, "Failed to register cooling device for fan %u\n",
1155e0d3f7cbSArmin Wolf 					 i + 1);
1156e0d3f7cbSArmin Wolf 		}
1157e0d3f7cbSArmin Wolf 
1158b1986c8eSArmin Wolf 		data->fan_nominal_speed[i] = devm_kmalloc_array(dev, data->i8k_fan_max + 1,
1159b1986c8eSArmin Wolf 								sizeof(*data->fan_nominal_speed[i]),
1160b1986c8eSArmin Wolf 								GFP_KERNEL);
1161b1986c8eSArmin Wolf 		if (!data->fan_nominal_speed[i])
1162b1986c8eSArmin Wolf 			continue;
1163b1986c8eSArmin Wolf 
1164b1986c8eSArmin Wolf 		for (state = 0; state <= data->i8k_fan_max; state++) {
1165b1986c8eSArmin Wolf 			err = i8k_get_fan_nominal_speed(data, i, state);
1166b1986c8eSArmin Wolf 			if (err < 0) {
1167b1986c8eSArmin Wolf 				/* Mark nominal speed table as invalid in case of error */
1168b1986c8eSArmin Wolf 				devm_kfree(dev, data->fan_nominal_speed[i]);
1169b1986c8eSArmin Wolf 				data->fan_nominal_speed[i] = NULL;
1170b1986c8eSArmin Wolf 				break;
1171b1986c8eSArmin Wolf 			}
1172b1986c8eSArmin Wolf 			data->fan_nominal_speed[i][state] = err;
11734fc1a51cSArmin Wolf 			/*
11744fc1a51cSArmin Wolf 			 * Autodetect fan multiplier based on nominal rpm if multiplier
11754fc1a51cSArmin Wolf 			 * was not specified as module param or in DMI. If fan reports
11764fc1a51cSArmin Wolf 			 * rpm value too high then set multiplier to 1.
11774fc1a51cSArmin Wolf 			 */
11784fc1a51cSArmin Wolf 			if (!fan_mult && err > I8K_FAN_RPM_THRESHOLD)
11794fc1a51cSArmin Wolf 				data->i8k_fan_mult = 1;
1180b1986c8eSArmin Wolf 		}
1181deeba244SArmin Wolf 	}
1182a5afba16SPali Rohár 
1183deeba244SArmin Wolf 	dell_smm_hwmon_dev = devm_hwmon_device_register_with_info(dev, "dell_smm", data,
1184deeba244SArmin Wolf 								  &dell_smm_chip_info, NULL);
1185a5afba16SPali Rohár 
1186deeba244SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_hwmon_dev);
1187a5afba16SPali Rohár }
1188a5afba16SPali Rohár 
dell_smm_init_data(struct device * dev,const struct dell_smm_ops * ops)1189b7a4706fSArmin Wolf static int dell_smm_init_data(struct device *dev, const struct dell_smm_ops *ops)
119020bdeebcSArmin Wolf {
119120bdeebcSArmin Wolf 	struct dell_smm_data *data;
119220bdeebcSArmin Wolf 
119320bdeebcSArmin Wolf 	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
119420bdeebcSArmin Wolf 	if (!data)
119520bdeebcSArmin Wolf 		return -ENOMEM;
119620bdeebcSArmin Wolf 
119720bdeebcSArmin Wolf 	mutex_init(&data->i8k_mutex);
119820bdeebcSArmin Wolf 	dev_set_drvdata(dev, data);
119920bdeebcSArmin Wolf 
120020bdeebcSArmin Wolf 	data->ops = ops;
120120bdeebcSArmin Wolf 	/* All options must not be 0 */
120220bdeebcSArmin Wolf 	data->i8k_fan_mult = fan_mult ? : I8K_FAN_MULT;
120320bdeebcSArmin Wolf 	data->i8k_fan_max = fan_max ? : I8K_FAN_HIGH;
120420bdeebcSArmin Wolf 	data->i8k_pwm_mult = DIV_ROUND_UP(255, data->i8k_fan_max);
120520bdeebcSArmin Wolf 
120620bdeebcSArmin Wolf 	return 0;
120720bdeebcSArmin Wolf }
120820bdeebcSArmin Wolf 
12096faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_dmi_table[] __initconst = {
1210a5afba16SPali Rohár 	{
1211489dd8f0SArmin Wolf 		.ident = "Dell G5 5590",
1212489dd8f0SArmin Wolf 		.matches = {
1213489dd8f0SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1214489dd8f0SArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G5 5590"),
1215489dd8f0SArmin Wolf 		},
1216489dd8f0SArmin Wolf 	},
1217489dd8f0SArmin Wolf 	{
1218df43ade4STobias Jakobi 		.ident = "Dell G5 5505",
1219df43ade4STobias Jakobi 		.matches = {
1220df43ade4STobias Jakobi 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1221df43ade4STobias Jakobi 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "G5 5505"),
1222df43ade4STobias Jakobi 		},
1223df43ade4STobias Jakobi 	},
1224df43ade4STobias Jakobi 	{
1225a5afba16SPali Rohár 		.ident = "Dell Inspiron",
1226a5afba16SPali Rohár 		.matches = {
1227a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
1228a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
1229a5afba16SPali Rohár 		},
1230a5afba16SPali Rohár 	},
1231a5afba16SPali Rohár 	{
1232a5afba16SPali Rohár 		.ident = "Dell Latitude",
1233a5afba16SPali Rohár 		.matches = {
1234a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
1235a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1236a5afba16SPali Rohár 		},
1237a5afba16SPali Rohár 	},
1238a5afba16SPali Rohár 	{
1239a5afba16SPali Rohár 		.ident = "Dell Inspiron 2",
1240a5afba16SPali Rohár 		.matches = {
1241a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1242a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
1243a5afba16SPali Rohár 		},
1244a5afba16SPali Rohár 	},
1245a5afba16SPali Rohár 	{
1246a5afba16SPali Rohár 		.ident = "Dell Latitude 2",
1247a5afba16SPali Rohár 		.matches = {
1248a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1249a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
1250a5afba16SPali Rohár 		},
1251a5afba16SPali Rohár 	},
1252a5afba16SPali Rohár 	{	/* UK Inspiron 6400  */
1253a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1254a5afba16SPali Rohár 		.matches = {
1255a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1256a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
1257a5afba16SPali Rohár 		},
1258a5afba16SPali Rohár 	},
1259a5afba16SPali Rohár 	{
1260a5afba16SPali Rohár 		.ident = "Dell Inspiron 3",
1261a5afba16SPali Rohár 		.matches = {
1262a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1263a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
1264a5afba16SPali Rohár 		},
1265a5afba16SPali Rohár 	},
1266a5afba16SPali Rohár 	{
1267ef8df816SArmin Wolf 		.ident = "Dell OptiPlex 7060",
1268ef8df816SArmin Wolf 		.matches = {
1269ef8df816SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1270ef8df816SArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7060"),
1271ef8df816SArmin Wolf 		},
1272ef8df816SArmin Wolf 	},
1273ef8df816SArmin Wolf 	{
1274a5afba16SPali Rohár 		.ident = "Dell Precision",
1275a5afba16SPali Rohár 		.matches = {
1276a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1277a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
1278a5afba16SPali Rohár 		},
1279a5afba16SPali Rohár 	},
1280a5afba16SPali Rohár 	{
1281a5afba16SPali Rohár 		.ident = "Dell Vostro",
1282a5afba16SPali Rohár 		.matches = {
1283a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1284a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
1285a5afba16SPali Rohár 		},
1286a5afba16SPali Rohár 	},
1287a5afba16SPali Rohár 	{
1288a5afba16SPali Rohár 		.ident = "Dell Studio",
1289a5afba16SPali Rohár 		.matches = {
1290a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1291a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
1292a5afba16SPali Rohár 		},
1293a5afba16SPali Rohár 	},
1294a5afba16SPali Rohár 	{
1295a5afba16SPali Rohár 		.ident = "Dell XPS M140",
1296a5afba16SPali Rohár 		.matches = {
1297a5afba16SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1298a5afba16SPali Rohár 			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
1299a5afba16SPali Rohár 		},
1300a5afba16SPali Rohár 	},
1301a4811b6cSPali Rohár 	{
1302b8a13e5eSThomas Hebb 		.ident = "Dell XPS",
1303a4811b6cSPali Rohár 		.matches = {
1304a4811b6cSPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1305b8a13e5eSThomas Hebb 			DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
1306162372b0SMichele Sorcinelli 		},
1307162372b0SMichele Sorcinelli 	},
1308a5afba16SPali Rohár 	{ }
1309a5afba16SPali Rohár };
1310a5afba16SPali Rohár 
1311a5afba16SPali Rohár MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
1312a5afba16SPali Rohár 
1313a4b45b25SPali Rohár /*
13145aad36f4SArmin Wolf  * Only use for machines which need some special configuration
13155aad36f4SArmin Wolf  * in order to work correctly (e.g. if autoconfig fails on this machines).
13165aad36f4SArmin Wolf  */
13175aad36f4SArmin Wolf struct i8k_config_data {
13185aad36f4SArmin Wolf 	uint fan_mult;
13195aad36f4SArmin Wolf 	uint fan_max;
13205aad36f4SArmin Wolf };
13215aad36f4SArmin Wolf 
13225aad36f4SArmin Wolf enum i8k_configs {
13235aad36f4SArmin Wolf 	DELL_LATITUDE_D520,
13245aad36f4SArmin Wolf 	DELL_PRECISION_490,
13255aad36f4SArmin Wolf 	DELL_STUDIO,
13265aad36f4SArmin Wolf 	DELL_XPS,
13275aad36f4SArmin Wolf };
13285aad36f4SArmin Wolf 
13295aad36f4SArmin Wolf static const struct i8k_config_data i8k_config_data[] __initconst = {
13305aad36f4SArmin Wolf 	[DELL_LATITUDE_D520] = {
13315aad36f4SArmin Wolf 		.fan_mult = 1,
13325aad36f4SArmin Wolf 		.fan_max = I8K_FAN_TURBO,
13335aad36f4SArmin Wolf 	},
13345aad36f4SArmin Wolf 	[DELL_PRECISION_490] = {
13355aad36f4SArmin Wolf 		.fan_mult = 1,
13365aad36f4SArmin Wolf 		.fan_max = I8K_FAN_TURBO,
13375aad36f4SArmin Wolf 	},
13385aad36f4SArmin Wolf 	[DELL_STUDIO] = {
13395aad36f4SArmin Wolf 		.fan_mult = 1,
13405aad36f4SArmin Wolf 		.fan_max = I8K_FAN_HIGH,
13415aad36f4SArmin Wolf 	},
13425aad36f4SArmin Wolf 	[DELL_XPS] = {
13435aad36f4SArmin Wolf 		.fan_mult = 1,
13445aad36f4SArmin Wolf 		.fan_max = I8K_FAN_HIGH,
13455aad36f4SArmin Wolf 	},
13465aad36f4SArmin Wolf };
13475aad36f4SArmin Wolf 
13485aad36f4SArmin Wolf static const struct dmi_system_id i8k_config_dmi_table[] __initconst = {
13495aad36f4SArmin Wolf 	{
13505aad36f4SArmin Wolf 		.ident = "Dell Latitude D520",
13515aad36f4SArmin Wolf 		.matches = {
13525aad36f4SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
13535aad36f4SArmin Wolf 			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
13545aad36f4SArmin Wolf 		},
13555aad36f4SArmin Wolf 		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
13565aad36f4SArmin Wolf 	},
13575aad36f4SArmin Wolf 	{
13585aad36f4SArmin Wolf 		.ident = "Dell Precision 490",
13595aad36f4SArmin Wolf 		.matches = {
13605aad36f4SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
13615aad36f4SArmin Wolf 			DMI_MATCH(DMI_PRODUCT_NAME,
13625aad36f4SArmin Wolf 				  "Precision WorkStation 490"),
13635aad36f4SArmin Wolf 		},
13645aad36f4SArmin Wolf 		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
13655aad36f4SArmin Wolf 	},
13665aad36f4SArmin Wolf 	{
13675aad36f4SArmin Wolf 		.ident = "Dell Studio",
13685aad36f4SArmin Wolf 		.matches = {
13695aad36f4SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
13705aad36f4SArmin Wolf 			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
13715aad36f4SArmin Wolf 		},
13725aad36f4SArmin Wolf 		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
13735aad36f4SArmin Wolf 	},
13745aad36f4SArmin Wolf 	{
13755aad36f4SArmin Wolf 		.ident = "Dell XPS M140",
13765aad36f4SArmin Wolf 		.matches = {
13775aad36f4SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
13785aad36f4SArmin Wolf 			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
13795aad36f4SArmin Wolf 		},
13805aad36f4SArmin Wolf 		.driver_data = (void *)&i8k_config_data[DELL_XPS],
13815aad36f4SArmin Wolf 	},
13825aad36f4SArmin Wolf 	{ }
13835aad36f4SArmin Wolf };
13845aad36f4SArmin Wolf 
13855aad36f4SArmin Wolf /*
13862744d2fdSPali Rohár  * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
13872744d2fdSPali Rohár  * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
13882744d2fdSPali Rohár  * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
13892744d2fdSPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121
13906220f4ebSThorsten Leemhuis  */
13916faadbbbSChristoph Hellwig static const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = {
13922744d2fdSPali Rohár 	{
13936220f4ebSThorsten Leemhuis 		.ident = "Dell Studio XPS 8000",
13946220f4ebSThorsten Leemhuis 		.matches = {
13956220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
13966220f4ebSThorsten Leemhuis 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"),
13976220f4ebSThorsten Leemhuis 		},
13986220f4ebSThorsten Leemhuis 	},
13996220f4ebSThorsten Leemhuis 	{
1400a4b45b25SPali Rohár 		.ident = "Dell Studio XPS 8100",
1401a4b45b25SPali Rohár 		.matches = {
1402a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1403a4b45b25SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"),
1404a4b45b25SPali Rohár 		},
1405a4b45b25SPali Rohár 	},
14062744d2fdSPali Rohár 	{
14072744d2fdSPali Rohár 		.ident = "Dell Inspiron 580",
14082744d2fdSPali Rohár 		.matches = {
14092744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
14102744d2fdSPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "),
14112744d2fdSPali Rohár 		},
14122744d2fdSPali Rohár 	},
14136ba463edSArmin Wolf 	{
14146ba463edSArmin Wolf 		.ident = "Dell Inspiron 3505",
14156ba463edSArmin Wolf 		.matches = {
14166ba463edSArmin Wolf 			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
14176ba463edSArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 3505"),
14186ba463edSArmin Wolf 		},
14196ba463edSArmin Wolf 	},
1420a4b45b25SPali Rohár 	{ }
1421a4b45b25SPali Rohár };
1422a4b45b25SPali Rohár 
1423a5afba16SPali Rohár /*
1424f480ea90SPali Rohár  * On some machines all fan related SMM functions implemented by Dell BIOS
1425f480ea90SPali Rohár  * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan
1426f480ea90SPali Rohár  * support for affected blacklisted Dell machines stay disabled.
1427f480ea90SPali Rohár  * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
1428f480ea90SPali Rohár  */
1429c510f6acSArmin Wolf static const struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initconst = {
1430f480ea90SPali Rohár 	{
1431f480ea90SPali Rohár 		.ident = "Dell Inspiron 7720",
1432f480ea90SPali Rohár 		.matches = {
1433f480ea90SPali Rohár 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1434f480ea90SPali Rohár 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
1435f480ea90SPali Rohár 		},
1436f480ea90SPali Rohár 	},
14376fbc4232SOleksandr Natalenko 	{
14386fbc4232SOleksandr Natalenko 		.ident = "Dell Vostro 3360",
14396fbc4232SOleksandr Natalenko 		.matches = {
14406fbc4232SOleksandr Natalenko 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
14416fbc4232SOleksandr Natalenko 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
14426fbc4232SOleksandr Natalenko 		},
14436fbc4232SOleksandr Natalenko 	},
1444536e0019SHelge Eichelberg 	{
1445536e0019SHelge Eichelberg 		.ident = "Dell XPS13 9333",
1446536e0019SHelge Eichelberg 		.matches = {
1447536e0019SHelge Eichelberg 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1448536e0019SHelge Eichelberg 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
1449536e0019SHelge Eichelberg 		},
1450536e0019SHelge Eichelberg 	},
14514008bc7dSThomas Hebb 	{
14524008bc7dSThomas Hebb 		.ident = "Dell XPS 15 L502X",
14534008bc7dSThomas Hebb 		.matches = {
14544008bc7dSThomas Hebb 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
14554008bc7dSThomas Hebb 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"),
14564008bc7dSThomas Hebb 		},
14574008bc7dSThomas Hebb 	},
1458f480ea90SPali Rohár 	{ }
1459f480ea90SPali Rohár };
1460f480ea90SPali Rohár 
1461afe45277SGiovanni Mascellani struct i8k_fan_control_data {
1462afe45277SGiovanni Mascellani 	unsigned int manual_fan;
1463afe45277SGiovanni Mascellani 	unsigned int auto_fan;
1464afe45277SGiovanni Mascellani };
1465afe45277SGiovanni Mascellani 
1466afe45277SGiovanni Mascellani enum i8k_fan_controls {
14678debe3c1SArmin Wolf 	I8K_FAN_30A3_31A3,
1468afe45277SGiovanni Mascellani 	I8K_FAN_34A3_35A3,
1469afe45277SGiovanni Mascellani };
1470afe45277SGiovanni Mascellani 
1471c510f6acSArmin Wolf static const struct i8k_fan_control_data i8k_fan_control_data[] __initconst = {
14728debe3c1SArmin Wolf 	[I8K_FAN_30A3_31A3] = {
14738debe3c1SArmin Wolf 		.manual_fan = 0x30a3,
14748debe3c1SArmin Wolf 		.auto_fan = 0x31a3,
14758debe3c1SArmin Wolf 	},
1476afe45277SGiovanni Mascellani 	[I8K_FAN_34A3_35A3] = {
1477afe45277SGiovanni Mascellani 		.manual_fan = 0x34a3,
1478afe45277SGiovanni Mascellani 		.auto_fan = 0x35a3,
1479afe45277SGiovanni Mascellani 	},
1480afe45277SGiovanni Mascellani };
1481afe45277SGiovanni Mascellani 
1482c510f6acSArmin Wolf static const struct dmi_system_id i8k_whitelist_fan_control[] __initconst = {
1483afe45277SGiovanni Mascellani 	{
14840ca8bb2cSJeffrey Lin 		.ident = "Dell Latitude 5480",
14850ca8bb2cSJeffrey Lin 		.matches = {
14860ca8bb2cSJeffrey Lin 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
14870ca8bb2cSJeffrey Lin 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"),
14880ca8bb2cSJeffrey Lin 		},
14890ca8bb2cSJeffrey Lin 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
14900ca8bb2cSJeffrey Lin 	},
14910ca8bb2cSJeffrey Lin 	{
1492b4be5130SArmin Wolf 		.ident = "Dell Latitude 7320",
1493b4be5130SArmin Wolf 		.matches = {
1494b4be5130SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1495b4be5130SArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 7320"),
1496b4be5130SArmin Wolf 		},
1497b4be5130SArmin Wolf 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
1498b4be5130SArmin Wolf 	},
1499b4be5130SArmin Wolf 	{
1500afe45277SGiovanni Mascellani 		.ident = "Dell Latitude E6440",
1501afe45277SGiovanni Mascellani 		.matches = {
1502afe45277SGiovanni Mascellani 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1503afe45277SGiovanni Mascellani 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
1504afe45277SGiovanni Mascellani 		},
1505afe45277SGiovanni Mascellani 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1506afe45277SGiovanni Mascellani 	},
1507807b8c29SSebastian Oechsle 	{
1508807b8c29SSebastian Oechsle 		.ident = "Dell Latitude E7440",
1509807b8c29SSebastian Oechsle 		.matches = {
1510807b8c29SSebastian Oechsle 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1511807b8c29SSebastian Oechsle 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E7440"),
1512807b8c29SSebastian Oechsle 		},
1513807b8c29SSebastian Oechsle 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1514807b8c29SSebastian Oechsle 	},
151595d88d05SCarlos Alberto Lopez Perez 	{
151695d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 5530",
151795d88d05SCarlos Alberto Lopez Perez 		.matches = {
151895d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
151995d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
152095d88d05SCarlos Alberto Lopez Perez 		},
152195d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
152295d88d05SCarlos Alberto Lopez Perez 	},
152395d88d05SCarlos Alberto Lopez Perez 	{
152495d88d05SCarlos Alberto Lopez Perez 		.ident = "Dell Precision 7510",
152595d88d05SCarlos Alberto Lopez Perez 		.matches = {
152695d88d05SCarlos Alberto Lopez Perez 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
152795d88d05SCarlos Alberto Lopez Perez 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7510"),
152895d88d05SCarlos Alberto Lopez Perez 		},
152995d88d05SCarlos Alberto Lopez Perez 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
153095d88d05SCarlos Alberto Lopez Perez 	},
1531385e5f57SArmin Wolf 	{
1532c84e93daSSeiji Nishikawa 		.ident = "Dell Precision 7540",
1533c84e93daSSeiji Nishikawa 		.matches = {
1534c84e93daSSeiji Nishikawa 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1535c84e93daSSeiji Nishikawa 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 7540"),
1536c84e93daSSeiji Nishikawa 		},
1537c84e93daSSeiji Nishikawa 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1538c84e93daSSeiji Nishikawa 	},
1539c84e93daSSeiji Nishikawa 	{
1540385e5f57SArmin Wolf 		.ident = "Dell XPS 13 7390",
1541385e5f57SArmin Wolf 		.matches = {
1542385e5f57SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1543385e5f57SArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 13 7390"),
1544385e5f57SArmin Wolf 		},
1545385e5f57SArmin Wolf 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1546385e5f57SArmin Wolf 	},
1547159e459cSArmin Wolf 	{
1548159e459cSArmin Wolf 		.ident = "Dell Optiplex 7000",
1549159e459cSArmin Wolf 		.matches = {
1550159e459cSArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1551159e459cSArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7000"),
1552159e459cSArmin Wolf 		},
1553159e459cSArmin Wolf 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
1554159e459cSArmin Wolf 	},
15558debe3c1SArmin Wolf 	{
15568debe3c1SArmin Wolf 		.ident = "Dell XPS 9315",
15578debe3c1SArmin Wolf 		.matches = {
15588debe3c1SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
15598debe3c1SArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS 9315"),
15608debe3c1SArmin Wolf 		},
15618debe3c1SArmin Wolf 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
15628debe3c1SArmin Wolf 	},
1563fa0bc8f2SArmin Wolf 	{
1564fa0bc8f2SArmin Wolf 		.ident = "Dell G15 5511",
1565fa0bc8f2SArmin Wolf 		.matches = {
1566fa0bc8f2SArmin Wolf 			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
1567fa0bc8f2SArmin Wolf 			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell G15 5511"),
1568fa0bc8f2SArmin Wolf 		},
1569fa0bc8f2SArmin Wolf 		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_30A3_31A3],
1570fa0bc8f2SArmin Wolf 	},
1571afe45277SGiovanni Mascellani 	{ }
1572afe45277SGiovanni Mascellani };
1573afe45277SGiovanni Mascellani 
1574b7a4706fSArmin Wolf /*
1575b7a4706fSArmin Wolf  * Legacy SMM backend driver.
1576b7a4706fSArmin Wolf  */
dell_smm_probe(struct platform_device * pdev)15771492fa21SArmin Wolf static int __init dell_smm_probe(struct platform_device *pdev)
1578a5afba16SPali Rohár {
15794d9983deSArmin Wolf 	int ret;
1580a5afba16SPali Rohár 
158120bdeebcSArmin Wolf 	ret = dell_smm_init_data(&pdev->dev, &i8k_smm_ops);
158220bdeebcSArmin Wolf 	if (ret < 0)
158320bdeebcSArmin Wolf 		return ret;
1584a5afba16SPali Rohár 
15851492fa21SArmin Wolf 	ret = dell_smm_init_hwmon(&pdev->dev);
15861492fa21SArmin Wolf 	if (ret)
15871492fa21SArmin Wolf 		return ret;
15881492fa21SArmin Wolf 
1589a2cb66b4SArmin Wolf 	i8k_init_procfs(&pdev->dev);
15901492fa21SArmin Wolf 
15911492fa21SArmin Wolf 	return 0;
15921492fa21SArmin Wolf }
15931492fa21SArmin Wolf 
15941492fa21SArmin Wolf static struct platform_driver dell_smm_driver = {
15951492fa21SArmin Wolf 	.driver		= {
15961492fa21SArmin Wolf 		.name	= KBUILD_MODNAME,
15971492fa21SArmin Wolf 	},
15981492fa21SArmin Wolf };
15991492fa21SArmin Wolf 
16001492fa21SArmin Wolf static struct platform_device *dell_smm_device;
16011492fa21SArmin Wolf 
16021492fa21SArmin Wolf /*
1603b7a4706fSArmin Wolf  * WMI SMM backend driver.
1604b7a4706fSArmin Wolf  */
dell_smm_wmi_probe(struct wmi_device * wdev,const void * context)1605b7a4706fSArmin Wolf static int dell_smm_wmi_probe(struct wmi_device *wdev, const void *context)
1606b7a4706fSArmin Wolf {
1607b7a4706fSArmin Wolf 	struct dell_smm_ops *ops;
1608b7a4706fSArmin Wolf 	int ret;
1609b7a4706fSArmin Wolf 
1610b7a4706fSArmin Wolf 	ops = devm_kzalloc(&wdev->dev, sizeof(*ops), GFP_KERNEL);
1611b7a4706fSArmin Wolf 	if (!ops)
1612b7a4706fSArmin Wolf 		return -ENOMEM;
1613b7a4706fSArmin Wolf 
1614b7a4706fSArmin Wolf 	ops->smm_call = wmi_smm_call;
1615b7a4706fSArmin Wolf 	ops->smm_dev = &wdev->dev;
1616b7a4706fSArmin Wolf 
1617b7a4706fSArmin Wolf 	if (dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG1) &&
1618b7a4706fSArmin Wolf 	    dell_smm_get_signature(ops, I8K_SMM_GET_DELL_SIG2))
1619b7a4706fSArmin Wolf 		return -ENODEV;
1620b7a4706fSArmin Wolf 
1621b7a4706fSArmin Wolf 	ret = dell_smm_init_data(&wdev->dev, ops);
1622b7a4706fSArmin Wolf 	if (ret < 0)
1623b7a4706fSArmin Wolf 		return ret;
1624b7a4706fSArmin Wolf 
1625b7a4706fSArmin Wolf 	return dell_smm_init_hwmon(&wdev->dev);
1626b7a4706fSArmin Wolf }
1627b7a4706fSArmin Wolf 
1628b7a4706fSArmin Wolf static const struct wmi_device_id dell_smm_wmi_id_table[] = {
1629b7a4706fSArmin Wolf 	{ DELL_SMM_WMI_GUID, NULL },
1630b7a4706fSArmin Wolf 	{ }
1631b7a4706fSArmin Wolf };
1632b7a4706fSArmin Wolf MODULE_DEVICE_TABLE(wmi, dell_smm_wmi_id_table);
1633b7a4706fSArmin Wolf 
1634b7a4706fSArmin Wolf static struct wmi_driver dell_smm_wmi_driver = {
1635b7a4706fSArmin Wolf 	.driver = {
1636b7a4706fSArmin Wolf 		.name = KBUILD_MODNAME,
1637b7a4706fSArmin Wolf 		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
1638b7a4706fSArmin Wolf 	},
1639b7a4706fSArmin Wolf 	.id_table = dell_smm_wmi_id_table,
1640b7a4706fSArmin Wolf 	.probe = dell_smm_wmi_probe,
1641a66ccfc2SArmin Wolf 	.no_singleton = true,
1642b7a4706fSArmin Wolf };
1643b7a4706fSArmin Wolf 
1644b7a4706fSArmin Wolf /*
16451492fa21SArmin Wolf  * Probe for the presence of a supported laptop.
16461492fa21SArmin Wolf  */
dell_smm_init_dmi(void)16477fd2e1caSArmin Wolf static void __init dell_smm_init_dmi(void)
16487fd2e1caSArmin Wolf {
16499848fcf4SArmin Wolf 	struct i8k_fan_control_data *control;
16502615f1eeSArmin Wolf 	struct i8k_config_data *config;
16519848fcf4SArmin Wolf 	const struct dmi_system_id *id;
16529848fcf4SArmin Wolf 
16537fd2e1caSArmin Wolf 	if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
16547fd2e1caSArmin Wolf 		if (!force) {
16557fd2e1caSArmin Wolf 			pr_notice("Disabling fan support due to BIOS bugs\n");
16567fd2e1caSArmin Wolf 			disallow_fan_support = true;
16577fd2e1caSArmin Wolf 		} else {
16587fd2e1caSArmin Wolf 			pr_warn("Enabling fan support despite BIOS bugs\n");
16597fd2e1caSArmin Wolf 		}
16607fd2e1caSArmin Wolf 	}
16617fd2e1caSArmin Wolf 
16627fd2e1caSArmin Wolf 	if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
16637fd2e1caSArmin Wolf 		if (!force) {
16647fd2e1caSArmin Wolf 			pr_notice("Disabling fan type call due to BIOS bugs\n");
16657fd2e1caSArmin Wolf 			disallow_fan_type_call = true;
16667fd2e1caSArmin Wolf 		} else {
16677fd2e1caSArmin Wolf 			pr_warn("Enabling fan type call despite BIOS bugs\n");
16687fd2e1caSArmin Wolf 		}
16697fd2e1caSArmin Wolf 	}
16709848fcf4SArmin Wolf 
16712615f1eeSArmin Wolf 	/*
16722615f1eeSArmin Wolf 	 * Set fan multiplier and maximal fan speed from DMI config.
16732615f1eeSArmin Wolf 	 * Values specified in module parameters override values from DMI.
16742615f1eeSArmin Wolf 	 */
16755aad36f4SArmin Wolf 	id = dmi_first_match(i8k_config_dmi_table);
16762615f1eeSArmin Wolf 	if (id && id->driver_data) {
16772615f1eeSArmin Wolf 		config = id->driver_data;
16782615f1eeSArmin Wolf 		if (!fan_mult && config->fan_mult)
16792615f1eeSArmin Wolf 			fan_mult = config->fan_mult;
16802615f1eeSArmin Wolf 
16812615f1eeSArmin Wolf 		if (!fan_max && config->fan_max)
16822615f1eeSArmin Wolf 			fan_max = config->fan_max;
16832615f1eeSArmin Wolf 	}
16842615f1eeSArmin Wolf 
16859848fcf4SArmin Wolf 	id = dmi_first_match(i8k_whitelist_fan_control);
16869848fcf4SArmin Wolf 	if (id && id->driver_data) {
16879848fcf4SArmin Wolf 		control = id->driver_data;
16889848fcf4SArmin Wolf 		manual_fan = control->manual_fan;
16899848fcf4SArmin Wolf 		auto_fan = control->auto_fan;
16909848fcf4SArmin Wolf 
16919848fcf4SArmin Wolf 		pr_info("Enabling support for setting automatic/manual fan control\n");
16929848fcf4SArmin Wolf 	}
16937fd2e1caSArmin Wolf }
16947fd2e1caSArmin Wolf 
dell_smm_legacy_check(void)1695b7a4706fSArmin Wolf static int __init dell_smm_legacy_check(void)
1696a5afba16SPali Rohár {
16971492fa21SArmin Wolf 	if (!dmi_check_system(i8k_dmi_table)) {
16981492fa21SArmin Wolf 		if (!ignore_dmi && !force)
1699a5afba16SPali Rohár 			return -ENODEV;
1700a5afba16SPali Rohár 
1701b7a4706fSArmin Wolf 		pr_info("Probing for legacy SMM handler on unsupported machine\n");
17021492fa21SArmin Wolf 		pr_info("vendor=%s, model=%s, version=%s\n",
17031492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_SYS_VENDOR),
17041492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_PRODUCT_NAME),
17051492fa21SArmin Wolf 			i8k_get_dmi_data(DMI_BIOS_VERSION));
17061492fa21SArmin Wolf 	}
1707039ae585SPali Rohár 
1708744f7be3SArmin Wolf 	if (dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG1) &&
1709744f7be3SArmin Wolf 	    dell_smm_get_signature(&i8k_smm_ops, I8K_SMM_GET_DELL_SIG2)) {
17101492fa21SArmin Wolf 		if (!force)
17111492fa21SArmin Wolf 			return -ENODEV;
1712688fcd04SArmin Wolf 
1713b7a4706fSArmin Wolf 		pr_warn("Forcing legacy SMM calls on a possibly incompatible machine\n");
1714b7a4706fSArmin Wolf 	}
1715b7a4706fSArmin Wolf 
1716b7a4706fSArmin Wolf 	return 0;
1717b7a4706fSArmin Wolf }
1718b7a4706fSArmin Wolf 
i8k_init(void)1719b7a4706fSArmin Wolf static int __init i8k_init(void)
1720b7a4706fSArmin Wolf {
1721b7a4706fSArmin Wolf 	int ret;
1722b7a4706fSArmin Wolf 
1723b7a4706fSArmin Wolf 	dell_smm_init_dmi();
1724b7a4706fSArmin Wolf 
1725b7a4706fSArmin Wolf 	ret = dell_smm_legacy_check();
1726b7a4706fSArmin Wolf 	if (ret < 0) {
1727b7a4706fSArmin Wolf 		/*
1728b7a4706fSArmin Wolf 		 * On modern machines, SMM communication happens over WMI, meaning
1729b7a4706fSArmin Wolf 		 * the SMM handler might not react to legacy SMM calls.
1730b7a4706fSArmin Wolf 		 */
1731b7a4706fSArmin Wolf 		return wmi_driver_register(&dell_smm_wmi_driver);
17321492fa21SArmin Wolf 	}
17331492fa21SArmin Wolf 
17341492fa21SArmin Wolf 	dell_smm_device = platform_create_bundle(&dell_smm_driver, dell_smm_probe, NULL, 0, NULL,
17351492fa21SArmin Wolf 						 0);
17361492fa21SArmin Wolf 
17371492fa21SArmin Wolf 	return PTR_ERR_OR_ZERO(dell_smm_device);
1738a5afba16SPali Rohár }
1739a5afba16SPali Rohár 
i8k_exit(void)1740a5afba16SPali Rohár static void __exit i8k_exit(void)
1741a5afba16SPali Rohár {
1742b7a4706fSArmin Wolf 	if (dell_smm_device) {
17431492fa21SArmin Wolf 		platform_device_unregister(dell_smm_device);
17441492fa21SArmin Wolf 		platform_driver_unregister(&dell_smm_driver);
1745b7a4706fSArmin Wolf 	} else {
1746b7a4706fSArmin Wolf 		wmi_driver_unregister(&dell_smm_wmi_driver);
1747b7a4706fSArmin Wolf 	}
1748a5afba16SPali Rohár }
1749a5afba16SPali Rohár 
1750a5afba16SPali Rohár module_init(i8k_init);
1751a5afba16SPali Rohár module_exit(i8k_exit);
1752