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 ®s->eax,
256b7a4706fSArmin Wolf ®s->ebx,
257b7a4706fSArmin Wolf ®s->ecx,
258b7a4706fSArmin Wolf ®s->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, ®s) ? : 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, ®s) ? : (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, ®s) ? : 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, ®s) ? : (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, ®s);
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, ®s);
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, ®s) ? : 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, ®s) ? : 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, ®s);
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, ®s);
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, ®s);
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