18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci * Hwmon integration:
88c2ecf20Sopenharmony_ci * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
98c2ecf20Sopenharmony_ci * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
108c2ecf20Sopenharmony_ci * Copyright (C) 2014, 2015  Pali Rohár <pali@kernel.org>
118c2ecf20Sopenharmony_ci */
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ci#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#include <linux/cpu.h>
168c2ecf20Sopenharmony_ci#include <linux/delay.h>
178c2ecf20Sopenharmony_ci#include <linux/module.h>
188c2ecf20Sopenharmony_ci#include <linux/types.h>
198c2ecf20Sopenharmony_ci#include <linux/init.h>
208c2ecf20Sopenharmony_ci#include <linux/proc_fs.h>
218c2ecf20Sopenharmony_ci#include <linux/seq_file.h>
228c2ecf20Sopenharmony_ci#include <linux/dmi.h>
238c2ecf20Sopenharmony_ci#include <linux/capability.h>
248c2ecf20Sopenharmony_ci#include <linux/mutex.h>
258c2ecf20Sopenharmony_ci#include <linux/hwmon.h>
268c2ecf20Sopenharmony_ci#include <linux/hwmon-sysfs.h>
278c2ecf20Sopenharmony_ci#include <linux/uaccess.h>
288c2ecf20Sopenharmony_ci#include <linux/io.h>
298c2ecf20Sopenharmony_ci#include <linux/sched.h>
308c2ecf20Sopenharmony_ci#include <linux/ctype.h>
318c2ecf20Sopenharmony_ci#include <linux/smp.h>
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci#include <linux/i8k.h>
348c2ecf20Sopenharmony_ci
358c2ecf20Sopenharmony_ci#define I8K_SMM_FN_STATUS	0x0025
368c2ecf20Sopenharmony_ci#define I8K_SMM_POWER_STATUS	0x0069
378c2ecf20Sopenharmony_ci#define I8K_SMM_SET_FAN		0x01a3
388c2ecf20Sopenharmony_ci#define I8K_SMM_GET_FAN		0x00a3
398c2ecf20Sopenharmony_ci#define I8K_SMM_GET_SPEED	0x02a3
408c2ecf20Sopenharmony_ci#define I8K_SMM_GET_FAN_TYPE	0x03a3
418c2ecf20Sopenharmony_ci#define I8K_SMM_GET_NOM_SPEED	0x04a3
428c2ecf20Sopenharmony_ci#define I8K_SMM_GET_TEMP	0x10a3
438c2ecf20Sopenharmony_ci#define I8K_SMM_GET_TEMP_TYPE	0x11a3
448c2ecf20Sopenharmony_ci#define I8K_SMM_GET_DELL_SIG1	0xfea3
458c2ecf20Sopenharmony_ci#define I8K_SMM_GET_DELL_SIG2	0xffa3
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci#define I8K_FAN_MULT		30
488c2ecf20Sopenharmony_ci#define I8K_FAN_MAX_RPM		30000
498c2ecf20Sopenharmony_ci#define I8K_MAX_TEMP		127
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci#define I8K_FN_NONE		0x00
528c2ecf20Sopenharmony_ci#define I8K_FN_UP		0x01
538c2ecf20Sopenharmony_ci#define I8K_FN_DOWN		0x02
548c2ecf20Sopenharmony_ci#define I8K_FN_MUTE		0x04
558c2ecf20Sopenharmony_ci#define I8K_FN_MASK		0x07
568c2ecf20Sopenharmony_ci#define I8K_FN_SHIFT		8
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci#define I8K_POWER_AC		0x05
598c2ecf20Sopenharmony_ci#define I8K_POWER_BATTERY	0x01
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_cistatic DEFINE_MUTEX(i8k_mutex);
628c2ecf20Sopenharmony_cistatic char bios_version[4];
638c2ecf20Sopenharmony_cistatic char bios_machineid[16];
648c2ecf20Sopenharmony_cistatic struct device *i8k_hwmon_dev;
658c2ecf20Sopenharmony_cistatic u32 i8k_hwmon_flags;
668c2ecf20Sopenharmony_cistatic uint i8k_fan_mult = I8K_FAN_MULT;
678c2ecf20Sopenharmony_cistatic uint i8k_pwm_mult;
688c2ecf20Sopenharmony_cistatic uint i8k_fan_max = I8K_FAN_HIGH;
698c2ecf20Sopenharmony_cistatic bool disallow_fan_type_call;
708c2ecf20Sopenharmony_cistatic bool disallow_fan_support;
718c2ecf20Sopenharmony_cistatic unsigned int manual_fan;
728c2ecf20Sopenharmony_cistatic unsigned int auto_fan;
738c2ecf20Sopenharmony_ci
748c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
758c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
768c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
778c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
788c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP5	(1 << 4)
798c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP6	(1 << 5)
808c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP7	(1 << 6)
818c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP8	(1 << 7)
828c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP9	(1 << 8)
838c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_TEMP10	(1 << 9)
848c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_FAN1	(1 << 10)
858c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_FAN2	(1 << 11)
868c2ecf20Sopenharmony_ci#define I8K_HWMON_HAVE_FAN3	(1 << 12)
878c2ecf20Sopenharmony_ci
888c2ecf20Sopenharmony_ciMODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
898c2ecf20Sopenharmony_ciMODULE_AUTHOR("Pali Rohár <pali@kernel.org>");
908c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
918c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
928c2ecf20Sopenharmony_ciMODULE_ALIAS("i8k");
938c2ecf20Sopenharmony_ci
948c2ecf20Sopenharmony_cistatic bool force;
958c2ecf20Sopenharmony_cimodule_param(force, bool, 0);
968c2ecf20Sopenharmony_ciMODULE_PARM_DESC(force, "Force loading without checking for supported models");
978c2ecf20Sopenharmony_ci
988c2ecf20Sopenharmony_cistatic bool ignore_dmi;
998c2ecf20Sopenharmony_cimodule_param(ignore_dmi, bool, 0);
1008c2ecf20Sopenharmony_ciMODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
1018c2ecf20Sopenharmony_ci
1028c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_I8K)
1038c2ecf20Sopenharmony_cistatic bool restricted = true;
1048c2ecf20Sopenharmony_cimodule_param(restricted, bool, 0);
1058c2ecf20Sopenharmony_ciMODULE_PARM_DESC(restricted, "Restrict fan control and serial number to CAP_SYS_ADMIN (default: 1)");
1068c2ecf20Sopenharmony_ci
1078c2ecf20Sopenharmony_cistatic bool power_status;
1088c2ecf20Sopenharmony_cimodule_param(power_status, bool, 0600);
1098c2ecf20Sopenharmony_ciMODULE_PARM_DESC(power_status, "Report power status in /proc/i8k (default: 0)");
1108c2ecf20Sopenharmony_ci#endif
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_cistatic uint fan_mult;
1138c2ecf20Sopenharmony_cimodule_param(fan_mult, uint, 0);
1148c2ecf20Sopenharmony_ciMODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
1158c2ecf20Sopenharmony_ci
1168c2ecf20Sopenharmony_cistatic uint fan_max;
1178c2ecf20Sopenharmony_cimodule_param(fan_max, uint, 0);
1188c2ecf20Sopenharmony_ciMODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
1198c2ecf20Sopenharmony_ci
1208c2ecf20Sopenharmony_cistruct smm_regs {
1218c2ecf20Sopenharmony_ci	unsigned int eax;
1228c2ecf20Sopenharmony_ci	unsigned int ebx __packed;
1238c2ecf20Sopenharmony_ci	unsigned int ecx __packed;
1248c2ecf20Sopenharmony_ci	unsigned int edx __packed;
1258c2ecf20Sopenharmony_ci	unsigned int esi __packed;
1268c2ecf20Sopenharmony_ci	unsigned int edi __packed;
1278c2ecf20Sopenharmony_ci};
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_cistatic inline const char *i8k_get_dmi_data(int field)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	const char *dmi_data = dmi_get_system_info(field);
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_ci	return dmi_data && *dmi_data ? dmi_data : "?";
1348c2ecf20Sopenharmony_ci}
1358c2ecf20Sopenharmony_ci
1368c2ecf20Sopenharmony_ci/*
1378c2ecf20Sopenharmony_ci * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
1388c2ecf20Sopenharmony_ci */
1398c2ecf20Sopenharmony_cistatic int i8k_smm_func(void *par)
1408c2ecf20Sopenharmony_ci{
1418c2ecf20Sopenharmony_ci	int rc;
1428c2ecf20Sopenharmony_ci	struct smm_regs *regs = par;
1438c2ecf20Sopenharmony_ci	int eax = regs->eax;
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci#ifdef DEBUG
1468c2ecf20Sopenharmony_ci	int ebx = regs->ebx;
1478c2ecf20Sopenharmony_ci	unsigned long duration;
1488c2ecf20Sopenharmony_ci	ktime_t calltime, delta, rettime;
1498c2ecf20Sopenharmony_ci
1508c2ecf20Sopenharmony_ci	calltime = ktime_get();
1518c2ecf20Sopenharmony_ci#endif
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_ci	/* SMM requires CPU 0 */
1548c2ecf20Sopenharmony_ci	if (smp_processor_id() != 0)
1558c2ecf20Sopenharmony_ci		return -EBUSY;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci#if defined(CONFIG_X86_64)
1588c2ecf20Sopenharmony_ci	asm volatile("pushq %%rax\n\t"
1598c2ecf20Sopenharmony_ci		"movl 0(%%rax),%%edx\n\t"
1608c2ecf20Sopenharmony_ci		"pushq %%rdx\n\t"
1618c2ecf20Sopenharmony_ci		"movl 4(%%rax),%%ebx\n\t"
1628c2ecf20Sopenharmony_ci		"movl 8(%%rax),%%ecx\n\t"
1638c2ecf20Sopenharmony_ci		"movl 12(%%rax),%%edx\n\t"
1648c2ecf20Sopenharmony_ci		"movl 16(%%rax),%%esi\n\t"
1658c2ecf20Sopenharmony_ci		"movl 20(%%rax),%%edi\n\t"
1668c2ecf20Sopenharmony_ci		"popq %%rax\n\t"
1678c2ecf20Sopenharmony_ci		"out %%al,$0xb2\n\t"
1688c2ecf20Sopenharmony_ci		"out %%al,$0x84\n\t"
1698c2ecf20Sopenharmony_ci		"xchgq %%rax,(%%rsp)\n\t"
1708c2ecf20Sopenharmony_ci		"movl %%ebx,4(%%rax)\n\t"
1718c2ecf20Sopenharmony_ci		"movl %%ecx,8(%%rax)\n\t"
1728c2ecf20Sopenharmony_ci		"movl %%edx,12(%%rax)\n\t"
1738c2ecf20Sopenharmony_ci		"movl %%esi,16(%%rax)\n\t"
1748c2ecf20Sopenharmony_ci		"movl %%edi,20(%%rax)\n\t"
1758c2ecf20Sopenharmony_ci		"popq %%rdx\n\t"
1768c2ecf20Sopenharmony_ci		"movl %%edx,0(%%rax)\n\t"
1778c2ecf20Sopenharmony_ci		"pushfq\n\t"
1788c2ecf20Sopenharmony_ci		"popq %%rax\n\t"
1798c2ecf20Sopenharmony_ci		"andl $1,%%eax\n"
1808c2ecf20Sopenharmony_ci		: "=a"(rc)
1818c2ecf20Sopenharmony_ci		:    "a"(regs)
1828c2ecf20Sopenharmony_ci		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
1838c2ecf20Sopenharmony_ci#else
1848c2ecf20Sopenharmony_ci	asm volatile("pushl %%eax\n\t"
1858c2ecf20Sopenharmony_ci	    "movl 0(%%eax),%%edx\n\t"
1868c2ecf20Sopenharmony_ci	    "push %%edx\n\t"
1878c2ecf20Sopenharmony_ci	    "movl 4(%%eax),%%ebx\n\t"
1888c2ecf20Sopenharmony_ci	    "movl 8(%%eax),%%ecx\n\t"
1898c2ecf20Sopenharmony_ci	    "movl 12(%%eax),%%edx\n\t"
1908c2ecf20Sopenharmony_ci	    "movl 16(%%eax),%%esi\n\t"
1918c2ecf20Sopenharmony_ci	    "movl 20(%%eax),%%edi\n\t"
1928c2ecf20Sopenharmony_ci	    "popl %%eax\n\t"
1938c2ecf20Sopenharmony_ci	    "out %%al,$0xb2\n\t"
1948c2ecf20Sopenharmony_ci	    "out %%al,$0x84\n\t"
1958c2ecf20Sopenharmony_ci	    "xchgl %%eax,(%%esp)\n\t"
1968c2ecf20Sopenharmony_ci	    "movl %%ebx,4(%%eax)\n\t"
1978c2ecf20Sopenharmony_ci	    "movl %%ecx,8(%%eax)\n\t"
1988c2ecf20Sopenharmony_ci	    "movl %%edx,12(%%eax)\n\t"
1998c2ecf20Sopenharmony_ci	    "movl %%esi,16(%%eax)\n\t"
2008c2ecf20Sopenharmony_ci	    "movl %%edi,20(%%eax)\n\t"
2018c2ecf20Sopenharmony_ci	    "popl %%edx\n\t"
2028c2ecf20Sopenharmony_ci	    "movl %%edx,0(%%eax)\n\t"
2038c2ecf20Sopenharmony_ci	    "lahf\n\t"
2048c2ecf20Sopenharmony_ci	    "shrl $8,%%eax\n\t"
2058c2ecf20Sopenharmony_ci	    "andl $1,%%eax\n"
2068c2ecf20Sopenharmony_ci	    : "=a"(rc)
2078c2ecf20Sopenharmony_ci	    :    "a"(regs)
2088c2ecf20Sopenharmony_ci	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
2098c2ecf20Sopenharmony_ci#endif
2108c2ecf20Sopenharmony_ci	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
2118c2ecf20Sopenharmony_ci		rc = -EINVAL;
2128c2ecf20Sopenharmony_ci
2138c2ecf20Sopenharmony_ci#ifdef DEBUG
2148c2ecf20Sopenharmony_ci	rettime = ktime_get();
2158c2ecf20Sopenharmony_ci	delta = ktime_sub(rettime, calltime);
2168c2ecf20Sopenharmony_ci	duration = ktime_to_ns(delta) >> 10;
2178c2ecf20Sopenharmony_ci	pr_debug("smm(0x%.4x 0x%.4x) = 0x%.4x  (took %7lu usecs)\n", eax, ebx,
2188c2ecf20Sopenharmony_ci		(rc ? 0xffff : regs->eax & 0xffff), duration);
2198c2ecf20Sopenharmony_ci#endif
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_ci	return rc;
2228c2ecf20Sopenharmony_ci}
2238c2ecf20Sopenharmony_ci
2248c2ecf20Sopenharmony_ci/*
2258c2ecf20Sopenharmony_ci * Call the System Management Mode BIOS.
2268c2ecf20Sopenharmony_ci */
2278c2ecf20Sopenharmony_cistatic int i8k_smm(struct smm_regs *regs)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	int ret;
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_ci	get_online_cpus();
2328c2ecf20Sopenharmony_ci	ret = smp_call_on_cpu(0, i8k_smm_func, regs, true);
2338c2ecf20Sopenharmony_ci	put_online_cpus();
2348c2ecf20Sopenharmony_ci
2358c2ecf20Sopenharmony_ci	return ret;
2368c2ecf20Sopenharmony_ci}
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci/*
2398c2ecf20Sopenharmony_ci * Read the fan status.
2408c2ecf20Sopenharmony_ci */
2418c2ecf20Sopenharmony_cistatic int i8k_get_fan_status(int fan)
2428c2ecf20Sopenharmony_ci{
2438c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
2448c2ecf20Sopenharmony_ci
2458c2ecf20Sopenharmony_ci	if (disallow_fan_support)
2468c2ecf20Sopenharmony_ci		return -EINVAL;
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci	regs.ebx = fan & 0xff;
2498c2ecf20Sopenharmony_ci	return i8k_smm(&regs) ? : regs.eax & 0xff;
2508c2ecf20Sopenharmony_ci}
2518c2ecf20Sopenharmony_ci
2528c2ecf20Sopenharmony_ci/*
2538c2ecf20Sopenharmony_ci * Read the fan speed in RPM.
2548c2ecf20Sopenharmony_ci */
2558c2ecf20Sopenharmony_cistatic int i8k_get_fan_speed(int fan)
2568c2ecf20Sopenharmony_ci{
2578c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	if (disallow_fan_support)
2608c2ecf20Sopenharmony_ci		return -EINVAL;
2618c2ecf20Sopenharmony_ci
2628c2ecf20Sopenharmony_ci	regs.ebx = fan & 0xff;
2638c2ecf20Sopenharmony_ci	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
2648c2ecf20Sopenharmony_ci}
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci/*
2678c2ecf20Sopenharmony_ci * Read the fan type.
2688c2ecf20Sopenharmony_ci */
2698c2ecf20Sopenharmony_cistatic int _i8k_get_fan_type(int fan)
2708c2ecf20Sopenharmony_ci{
2718c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci	if (disallow_fan_support || disallow_fan_type_call)
2748c2ecf20Sopenharmony_ci		return -EINVAL;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci	regs.ebx = fan & 0xff;
2778c2ecf20Sopenharmony_ci	return i8k_smm(&regs) ? : regs.eax & 0xff;
2788c2ecf20Sopenharmony_ci}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_cistatic int i8k_get_fan_type(int fan)
2818c2ecf20Sopenharmony_ci{
2828c2ecf20Sopenharmony_ci	/* I8K_SMM_GET_FAN_TYPE SMM call is expensive, so cache values */
2838c2ecf20Sopenharmony_ci	static int types[3] = { INT_MIN, INT_MIN, INT_MIN };
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	if (types[fan] == INT_MIN)
2868c2ecf20Sopenharmony_ci		types[fan] = _i8k_get_fan_type(fan);
2878c2ecf20Sopenharmony_ci
2888c2ecf20Sopenharmony_ci	return types[fan];
2898c2ecf20Sopenharmony_ci}
2908c2ecf20Sopenharmony_ci
2918c2ecf20Sopenharmony_ci/*
2928c2ecf20Sopenharmony_ci * Read the fan nominal rpm for specific fan speed.
2938c2ecf20Sopenharmony_ci */
2948c2ecf20Sopenharmony_cistatic int i8k_get_fan_nominal_speed(int fan, int speed)
2958c2ecf20Sopenharmony_ci{
2968c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
2978c2ecf20Sopenharmony_ci
2988c2ecf20Sopenharmony_ci	if (disallow_fan_support)
2998c2ecf20Sopenharmony_ci		return -EINVAL;
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	regs.ebx = (fan & 0xff) | (speed << 8);
3028c2ecf20Sopenharmony_ci	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
3038c2ecf20Sopenharmony_ci}
3048c2ecf20Sopenharmony_ci
3058c2ecf20Sopenharmony_ci/*
3068c2ecf20Sopenharmony_ci * Enable or disable automatic BIOS fan control support
3078c2ecf20Sopenharmony_ci */
3088c2ecf20Sopenharmony_cistatic int i8k_enable_fan_auto_mode(bool enable)
3098c2ecf20Sopenharmony_ci{
3108c2ecf20Sopenharmony_ci	struct smm_regs regs = { };
3118c2ecf20Sopenharmony_ci
3128c2ecf20Sopenharmony_ci	if (disallow_fan_support)
3138c2ecf20Sopenharmony_ci		return -EINVAL;
3148c2ecf20Sopenharmony_ci
3158c2ecf20Sopenharmony_ci	regs.eax = enable ? auto_fan : manual_fan;
3168c2ecf20Sopenharmony_ci	return i8k_smm(&regs);
3178c2ecf20Sopenharmony_ci}
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci/*
3208c2ecf20Sopenharmony_ci * Set the fan speed (off, low, high, ...).
3218c2ecf20Sopenharmony_ci */
3228c2ecf20Sopenharmony_cistatic int i8k_set_fan(int fan, int speed)
3238c2ecf20Sopenharmony_ci{
3248c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
3258c2ecf20Sopenharmony_ci
3268c2ecf20Sopenharmony_ci	if (disallow_fan_support)
3278c2ecf20Sopenharmony_ci		return -EINVAL;
3288c2ecf20Sopenharmony_ci
3298c2ecf20Sopenharmony_ci	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
3308c2ecf20Sopenharmony_ci	regs.ebx = (fan & 0xff) | (speed << 8);
3318c2ecf20Sopenharmony_ci
3328c2ecf20Sopenharmony_ci	return i8k_smm(&regs);
3338c2ecf20Sopenharmony_ci}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_cistatic int i8k_get_temp_type(int sensor)
3368c2ecf20Sopenharmony_ci{
3378c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
3388c2ecf20Sopenharmony_ci
3398c2ecf20Sopenharmony_ci	regs.ebx = sensor & 0xff;
3408c2ecf20Sopenharmony_ci	return i8k_smm(&regs) ? : regs.eax & 0xff;
3418c2ecf20Sopenharmony_ci}
3428c2ecf20Sopenharmony_ci
3438c2ecf20Sopenharmony_ci/*
3448c2ecf20Sopenharmony_ci * Read the cpu temperature.
3458c2ecf20Sopenharmony_ci */
3468c2ecf20Sopenharmony_cistatic int _i8k_get_temp(int sensor)
3478c2ecf20Sopenharmony_ci{
3488c2ecf20Sopenharmony_ci	struct smm_regs regs = {
3498c2ecf20Sopenharmony_ci		.eax = I8K_SMM_GET_TEMP,
3508c2ecf20Sopenharmony_ci		.ebx = sensor & 0xff,
3518c2ecf20Sopenharmony_ci	};
3528c2ecf20Sopenharmony_ci
3538c2ecf20Sopenharmony_ci	return i8k_smm(&regs) ? : regs.eax & 0xff;
3548c2ecf20Sopenharmony_ci}
3558c2ecf20Sopenharmony_ci
3568c2ecf20Sopenharmony_cistatic int i8k_get_temp(int sensor)
3578c2ecf20Sopenharmony_ci{
3588c2ecf20Sopenharmony_ci	int temp = _i8k_get_temp(sensor);
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_ci	/*
3618c2ecf20Sopenharmony_ci	 * Sometimes the temperature sensor returns 0x99, which is out of range.
3628c2ecf20Sopenharmony_ci	 * In this case we retry (once) before returning an error.
3638c2ecf20Sopenharmony_ci	 # 1003655137 00000058 00005a4b
3648c2ecf20Sopenharmony_ci	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
3658c2ecf20Sopenharmony_ci	 # 1003655139 00000054 00005c52
3668c2ecf20Sopenharmony_ci	 */
3678c2ecf20Sopenharmony_ci	if (temp == 0x99) {
3688c2ecf20Sopenharmony_ci		msleep(100);
3698c2ecf20Sopenharmony_ci		temp = _i8k_get_temp(sensor);
3708c2ecf20Sopenharmony_ci	}
3718c2ecf20Sopenharmony_ci	/*
3728c2ecf20Sopenharmony_ci	 * Return -ENODATA for all invalid temperatures.
3738c2ecf20Sopenharmony_ci	 *
3748c2ecf20Sopenharmony_ci	 * Known instances are the 0x99 value as seen above as well as
3758c2ecf20Sopenharmony_ci	 * 0xc1 (193), which may be returned when trying to read the GPU
3768c2ecf20Sopenharmony_ci	 * temperature if the system supports a GPU and it is currently
3778c2ecf20Sopenharmony_ci	 * turned off.
3788c2ecf20Sopenharmony_ci	 */
3798c2ecf20Sopenharmony_ci	if (temp > I8K_MAX_TEMP)
3808c2ecf20Sopenharmony_ci		return -ENODATA;
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_ci	return temp;
3838c2ecf20Sopenharmony_ci}
3848c2ecf20Sopenharmony_ci
3858c2ecf20Sopenharmony_cistatic int i8k_get_dell_signature(int req_fn)
3868c2ecf20Sopenharmony_ci{
3878c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = req_fn, };
3888c2ecf20Sopenharmony_ci	int rc;
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	rc = i8k_smm(&regs);
3918c2ecf20Sopenharmony_ci	if (rc < 0)
3928c2ecf20Sopenharmony_ci		return rc;
3938c2ecf20Sopenharmony_ci
3948c2ecf20Sopenharmony_ci	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
3958c2ecf20Sopenharmony_ci}
3968c2ecf20Sopenharmony_ci
3978c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_I8K)
3988c2ecf20Sopenharmony_ci
3998c2ecf20Sopenharmony_ci/*
4008c2ecf20Sopenharmony_ci * Read the Fn key status.
4018c2ecf20Sopenharmony_ci */
4028c2ecf20Sopenharmony_cistatic int i8k_get_fn_status(void)
4038c2ecf20Sopenharmony_ci{
4048c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
4058c2ecf20Sopenharmony_ci	int rc;
4068c2ecf20Sopenharmony_ci
4078c2ecf20Sopenharmony_ci	rc = i8k_smm(&regs);
4088c2ecf20Sopenharmony_ci	if (rc < 0)
4098c2ecf20Sopenharmony_ci		return rc;
4108c2ecf20Sopenharmony_ci
4118c2ecf20Sopenharmony_ci	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
4128c2ecf20Sopenharmony_ci	case I8K_FN_UP:
4138c2ecf20Sopenharmony_ci		return I8K_VOL_UP;
4148c2ecf20Sopenharmony_ci	case I8K_FN_DOWN:
4158c2ecf20Sopenharmony_ci		return I8K_VOL_DOWN;
4168c2ecf20Sopenharmony_ci	case I8K_FN_MUTE:
4178c2ecf20Sopenharmony_ci		return I8K_VOL_MUTE;
4188c2ecf20Sopenharmony_ci	default:
4198c2ecf20Sopenharmony_ci		return 0;
4208c2ecf20Sopenharmony_ci	}
4218c2ecf20Sopenharmony_ci}
4228c2ecf20Sopenharmony_ci
4238c2ecf20Sopenharmony_ci/*
4248c2ecf20Sopenharmony_ci * Read the power status.
4258c2ecf20Sopenharmony_ci */
4268c2ecf20Sopenharmony_cistatic int i8k_get_power_status(void)
4278c2ecf20Sopenharmony_ci{
4288c2ecf20Sopenharmony_ci	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
4298c2ecf20Sopenharmony_ci	int rc;
4308c2ecf20Sopenharmony_ci
4318c2ecf20Sopenharmony_ci	rc = i8k_smm(&regs);
4328c2ecf20Sopenharmony_ci	if (rc < 0)
4338c2ecf20Sopenharmony_ci		return rc;
4348c2ecf20Sopenharmony_ci
4358c2ecf20Sopenharmony_ci	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
4368c2ecf20Sopenharmony_ci}
4378c2ecf20Sopenharmony_ci
4388c2ecf20Sopenharmony_ci/*
4398c2ecf20Sopenharmony_ci * Procfs interface
4408c2ecf20Sopenharmony_ci */
4418c2ecf20Sopenharmony_ci
4428c2ecf20Sopenharmony_cistatic int
4438c2ecf20Sopenharmony_cii8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
4448c2ecf20Sopenharmony_ci{
4458c2ecf20Sopenharmony_ci	int val = 0;
4468c2ecf20Sopenharmony_ci	int speed, err;
4478c2ecf20Sopenharmony_ci	unsigned char buff[16];
4488c2ecf20Sopenharmony_ci	int __user *argp = (int __user *)arg;
4498c2ecf20Sopenharmony_ci
4508c2ecf20Sopenharmony_ci	if (!argp)
4518c2ecf20Sopenharmony_ci		return -EINVAL;
4528c2ecf20Sopenharmony_ci
4538c2ecf20Sopenharmony_ci	switch (cmd) {
4548c2ecf20Sopenharmony_ci	case I8K_BIOS_VERSION:
4558c2ecf20Sopenharmony_ci		if (!isdigit(bios_version[0]) || !isdigit(bios_version[1]) ||
4568c2ecf20Sopenharmony_ci		    !isdigit(bios_version[2]))
4578c2ecf20Sopenharmony_ci			return -EINVAL;
4588c2ecf20Sopenharmony_ci
4598c2ecf20Sopenharmony_ci		val = (bios_version[0] << 16) |
4608c2ecf20Sopenharmony_ci				(bios_version[1] << 8) | bios_version[2];
4618c2ecf20Sopenharmony_ci		break;
4628c2ecf20Sopenharmony_ci
4638c2ecf20Sopenharmony_ci	case I8K_MACHINE_ID:
4648c2ecf20Sopenharmony_ci		if (restricted && !capable(CAP_SYS_ADMIN))
4658c2ecf20Sopenharmony_ci			return -EPERM;
4668c2ecf20Sopenharmony_ci
4678c2ecf20Sopenharmony_ci		memset(buff, 0, sizeof(buff));
4688c2ecf20Sopenharmony_ci		strlcpy(buff, bios_machineid, sizeof(buff));
4698c2ecf20Sopenharmony_ci		break;
4708c2ecf20Sopenharmony_ci
4718c2ecf20Sopenharmony_ci	case I8K_FN_STATUS:
4728c2ecf20Sopenharmony_ci		val = i8k_get_fn_status();
4738c2ecf20Sopenharmony_ci		break;
4748c2ecf20Sopenharmony_ci
4758c2ecf20Sopenharmony_ci	case I8K_POWER_STATUS:
4768c2ecf20Sopenharmony_ci		val = i8k_get_power_status();
4778c2ecf20Sopenharmony_ci		break;
4788c2ecf20Sopenharmony_ci
4798c2ecf20Sopenharmony_ci	case I8K_GET_TEMP:
4808c2ecf20Sopenharmony_ci		val = i8k_get_temp(0);
4818c2ecf20Sopenharmony_ci		break;
4828c2ecf20Sopenharmony_ci
4838c2ecf20Sopenharmony_ci	case I8K_GET_SPEED:
4848c2ecf20Sopenharmony_ci		if (copy_from_user(&val, argp, sizeof(int)))
4858c2ecf20Sopenharmony_ci			return -EFAULT;
4868c2ecf20Sopenharmony_ci
4878c2ecf20Sopenharmony_ci		val = i8k_get_fan_speed(val);
4888c2ecf20Sopenharmony_ci		break;
4898c2ecf20Sopenharmony_ci
4908c2ecf20Sopenharmony_ci	case I8K_GET_FAN:
4918c2ecf20Sopenharmony_ci		if (copy_from_user(&val, argp, sizeof(int)))
4928c2ecf20Sopenharmony_ci			return -EFAULT;
4938c2ecf20Sopenharmony_ci
4948c2ecf20Sopenharmony_ci		val = i8k_get_fan_status(val);
4958c2ecf20Sopenharmony_ci		break;
4968c2ecf20Sopenharmony_ci
4978c2ecf20Sopenharmony_ci	case I8K_SET_FAN:
4988c2ecf20Sopenharmony_ci		if (restricted && !capable(CAP_SYS_ADMIN))
4998c2ecf20Sopenharmony_ci			return -EPERM;
5008c2ecf20Sopenharmony_ci
5018c2ecf20Sopenharmony_ci		if (copy_from_user(&val, argp, sizeof(int)))
5028c2ecf20Sopenharmony_ci			return -EFAULT;
5038c2ecf20Sopenharmony_ci
5048c2ecf20Sopenharmony_ci		if (copy_from_user(&speed, argp + 1, sizeof(int)))
5058c2ecf20Sopenharmony_ci			return -EFAULT;
5068c2ecf20Sopenharmony_ci
5078c2ecf20Sopenharmony_ci		err = i8k_set_fan(val, speed);
5088c2ecf20Sopenharmony_ci		if (err < 0)
5098c2ecf20Sopenharmony_ci			return err;
5108c2ecf20Sopenharmony_ci
5118c2ecf20Sopenharmony_ci		val = i8k_get_fan_status(val);
5128c2ecf20Sopenharmony_ci		break;
5138c2ecf20Sopenharmony_ci
5148c2ecf20Sopenharmony_ci	default:
5158c2ecf20Sopenharmony_ci		return -EINVAL;
5168c2ecf20Sopenharmony_ci	}
5178c2ecf20Sopenharmony_ci
5188c2ecf20Sopenharmony_ci	if (val < 0)
5198c2ecf20Sopenharmony_ci		return val;
5208c2ecf20Sopenharmony_ci
5218c2ecf20Sopenharmony_ci	switch (cmd) {
5228c2ecf20Sopenharmony_ci	case I8K_BIOS_VERSION:
5238c2ecf20Sopenharmony_ci		if (copy_to_user(argp, &val, 4))
5248c2ecf20Sopenharmony_ci			return -EFAULT;
5258c2ecf20Sopenharmony_ci
5268c2ecf20Sopenharmony_ci		break;
5278c2ecf20Sopenharmony_ci	case I8K_MACHINE_ID:
5288c2ecf20Sopenharmony_ci		if (copy_to_user(argp, buff, 16))
5298c2ecf20Sopenharmony_ci			return -EFAULT;
5308c2ecf20Sopenharmony_ci
5318c2ecf20Sopenharmony_ci		break;
5328c2ecf20Sopenharmony_ci	default:
5338c2ecf20Sopenharmony_ci		if (copy_to_user(argp, &val, sizeof(int)))
5348c2ecf20Sopenharmony_ci			return -EFAULT;
5358c2ecf20Sopenharmony_ci
5368c2ecf20Sopenharmony_ci		break;
5378c2ecf20Sopenharmony_ci	}
5388c2ecf20Sopenharmony_ci
5398c2ecf20Sopenharmony_ci	return 0;
5408c2ecf20Sopenharmony_ci}
5418c2ecf20Sopenharmony_ci
5428c2ecf20Sopenharmony_cistatic long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
5438c2ecf20Sopenharmony_ci{
5448c2ecf20Sopenharmony_ci	long ret;
5458c2ecf20Sopenharmony_ci
5468c2ecf20Sopenharmony_ci	mutex_lock(&i8k_mutex);
5478c2ecf20Sopenharmony_ci	ret = i8k_ioctl_unlocked(fp, cmd, arg);
5488c2ecf20Sopenharmony_ci	mutex_unlock(&i8k_mutex);
5498c2ecf20Sopenharmony_ci
5508c2ecf20Sopenharmony_ci	return ret;
5518c2ecf20Sopenharmony_ci}
5528c2ecf20Sopenharmony_ci
5538c2ecf20Sopenharmony_ci/*
5548c2ecf20Sopenharmony_ci * Print the information for /proc/i8k.
5558c2ecf20Sopenharmony_ci */
5568c2ecf20Sopenharmony_cistatic int i8k_proc_show(struct seq_file *seq, void *offset)
5578c2ecf20Sopenharmony_ci{
5588c2ecf20Sopenharmony_ci	int fn_key, cpu_temp, ac_power;
5598c2ecf20Sopenharmony_ci	int left_fan, right_fan, left_speed, right_speed;
5608c2ecf20Sopenharmony_ci
5618c2ecf20Sopenharmony_ci	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
5628c2ecf20Sopenharmony_ci	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
5638c2ecf20Sopenharmony_ci	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
5648c2ecf20Sopenharmony_ci	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
5658c2ecf20Sopenharmony_ci	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
5668c2ecf20Sopenharmony_ci	fn_key		= i8k_get_fn_status();			/*   750 µs */
5678c2ecf20Sopenharmony_ci	if (power_status)
5688c2ecf20Sopenharmony_ci		ac_power = i8k_get_power_status();		/* 14700 µs */
5698c2ecf20Sopenharmony_ci	else
5708c2ecf20Sopenharmony_ci		ac_power = -1;
5718c2ecf20Sopenharmony_ci
5728c2ecf20Sopenharmony_ci	/*
5738c2ecf20Sopenharmony_ci	 * Info:
5748c2ecf20Sopenharmony_ci	 *
5758c2ecf20Sopenharmony_ci	 * 1)  Format version (this will change if format changes)
5768c2ecf20Sopenharmony_ci	 * 2)  BIOS version
5778c2ecf20Sopenharmony_ci	 * 3)  BIOS machine ID
5788c2ecf20Sopenharmony_ci	 * 4)  Cpu temperature
5798c2ecf20Sopenharmony_ci	 * 5)  Left fan status
5808c2ecf20Sopenharmony_ci	 * 6)  Right fan status
5818c2ecf20Sopenharmony_ci	 * 7)  Left fan speed
5828c2ecf20Sopenharmony_ci	 * 8)  Right fan speed
5838c2ecf20Sopenharmony_ci	 * 9)  AC power
5848c2ecf20Sopenharmony_ci	 * 10) Fn Key status
5858c2ecf20Sopenharmony_ci	 */
5868c2ecf20Sopenharmony_ci	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
5878c2ecf20Sopenharmony_ci		   I8K_PROC_FMT,
5888c2ecf20Sopenharmony_ci		   bios_version,
5898c2ecf20Sopenharmony_ci		   (restricted && !capable(CAP_SYS_ADMIN)) ? "-1" : bios_machineid,
5908c2ecf20Sopenharmony_ci		   cpu_temp,
5918c2ecf20Sopenharmony_ci		   left_fan, right_fan, left_speed, right_speed,
5928c2ecf20Sopenharmony_ci		   ac_power, fn_key);
5938c2ecf20Sopenharmony_ci
5948c2ecf20Sopenharmony_ci	return 0;
5958c2ecf20Sopenharmony_ci}
5968c2ecf20Sopenharmony_ci
5978c2ecf20Sopenharmony_cistatic int i8k_open_fs(struct inode *inode, struct file *file)
5988c2ecf20Sopenharmony_ci{
5998c2ecf20Sopenharmony_ci	return single_open(file, i8k_proc_show, NULL);
6008c2ecf20Sopenharmony_ci}
6018c2ecf20Sopenharmony_ci
6028c2ecf20Sopenharmony_cistatic const struct proc_ops i8k_proc_ops = {
6038c2ecf20Sopenharmony_ci	.proc_open	= i8k_open_fs,
6048c2ecf20Sopenharmony_ci	.proc_read	= seq_read,
6058c2ecf20Sopenharmony_ci	.proc_lseek	= seq_lseek,
6068c2ecf20Sopenharmony_ci	.proc_release	= single_release,
6078c2ecf20Sopenharmony_ci	.proc_ioctl	= i8k_ioctl,
6088c2ecf20Sopenharmony_ci};
6098c2ecf20Sopenharmony_ci
6108c2ecf20Sopenharmony_cistatic struct proc_dir_entry *entry;
6118c2ecf20Sopenharmony_ci
6128c2ecf20Sopenharmony_cistatic void __init i8k_init_procfs(void)
6138c2ecf20Sopenharmony_ci{
6148c2ecf20Sopenharmony_ci	/* Register the proc entry */
6158c2ecf20Sopenharmony_ci	entry = proc_create("i8k", 0, NULL, &i8k_proc_ops);
6168c2ecf20Sopenharmony_ci}
6178c2ecf20Sopenharmony_ci
6188c2ecf20Sopenharmony_cistatic void __exit i8k_exit_procfs(void)
6198c2ecf20Sopenharmony_ci{
6208c2ecf20Sopenharmony_ci	if (entry)
6218c2ecf20Sopenharmony_ci		remove_proc_entry("i8k", NULL);
6228c2ecf20Sopenharmony_ci}
6238c2ecf20Sopenharmony_ci
6248c2ecf20Sopenharmony_ci#else
6258c2ecf20Sopenharmony_ci
6268c2ecf20Sopenharmony_cistatic inline void __init i8k_init_procfs(void)
6278c2ecf20Sopenharmony_ci{
6288c2ecf20Sopenharmony_ci}
6298c2ecf20Sopenharmony_ci
6308c2ecf20Sopenharmony_cistatic inline void __exit i8k_exit_procfs(void)
6318c2ecf20Sopenharmony_ci{
6328c2ecf20Sopenharmony_ci}
6338c2ecf20Sopenharmony_ci
6348c2ecf20Sopenharmony_ci#endif
6358c2ecf20Sopenharmony_ci
6368c2ecf20Sopenharmony_ci/*
6378c2ecf20Sopenharmony_ci * Hwmon interface
6388c2ecf20Sopenharmony_ci */
6398c2ecf20Sopenharmony_ci
6408c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_temp_label_show(struct device *dev,
6418c2ecf20Sopenharmony_ci					 struct device_attribute *devattr,
6428c2ecf20Sopenharmony_ci					 char *buf)
6438c2ecf20Sopenharmony_ci{
6448c2ecf20Sopenharmony_ci	static const char * const labels[] = {
6458c2ecf20Sopenharmony_ci		"CPU",
6468c2ecf20Sopenharmony_ci		"GPU",
6478c2ecf20Sopenharmony_ci		"SODIMM",
6488c2ecf20Sopenharmony_ci		"Other",
6498c2ecf20Sopenharmony_ci		"Ambient",
6508c2ecf20Sopenharmony_ci		"Other",
6518c2ecf20Sopenharmony_ci	};
6528c2ecf20Sopenharmony_ci	int index = to_sensor_dev_attr(devattr)->index;
6538c2ecf20Sopenharmony_ci	int type;
6548c2ecf20Sopenharmony_ci
6558c2ecf20Sopenharmony_ci	type = i8k_get_temp_type(index);
6568c2ecf20Sopenharmony_ci	if (type < 0)
6578c2ecf20Sopenharmony_ci		return type;
6588c2ecf20Sopenharmony_ci	if (type >= ARRAY_SIZE(labels))
6598c2ecf20Sopenharmony_ci		type = ARRAY_SIZE(labels) - 1;
6608c2ecf20Sopenharmony_ci	return sprintf(buf, "%s\n", labels[type]);
6618c2ecf20Sopenharmony_ci}
6628c2ecf20Sopenharmony_ci
6638c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_temp_show(struct device *dev,
6648c2ecf20Sopenharmony_ci				   struct device_attribute *devattr,
6658c2ecf20Sopenharmony_ci				   char *buf)
6668c2ecf20Sopenharmony_ci{
6678c2ecf20Sopenharmony_ci	int index = to_sensor_dev_attr(devattr)->index;
6688c2ecf20Sopenharmony_ci	int temp;
6698c2ecf20Sopenharmony_ci
6708c2ecf20Sopenharmony_ci	temp = i8k_get_temp(index);
6718c2ecf20Sopenharmony_ci	if (temp < 0)
6728c2ecf20Sopenharmony_ci		return temp;
6738c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", temp * 1000);
6748c2ecf20Sopenharmony_ci}
6758c2ecf20Sopenharmony_ci
6768c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_fan_label_show(struct device *dev,
6778c2ecf20Sopenharmony_ci					struct device_attribute *devattr,
6788c2ecf20Sopenharmony_ci					char *buf)
6798c2ecf20Sopenharmony_ci{
6808c2ecf20Sopenharmony_ci	static const char * const labels[] = {
6818c2ecf20Sopenharmony_ci		"Processor Fan",
6828c2ecf20Sopenharmony_ci		"Motherboard Fan",
6838c2ecf20Sopenharmony_ci		"Video Fan",
6848c2ecf20Sopenharmony_ci		"Power Supply Fan",
6858c2ecf20Sopenharmony_ci		"Chipset Fan",
6868c2ecf20Sopenharmony_ci		"Other Fan",
6878c2ecf20Sopenharmony_ci	};
6888c2ecf20Sopenharmony_ci	int index = to_sensor_dev_attr(devattr)->index;
6898c2ecf20Sopenharmony_ci	bool dock = false;
6908c2ecf20Sopenharmony_ci	int type;
6918c2ecf20Sopenharmony_ci
6928c2ecf20Sopenharmony_ci	type = i8k_get_fan_type(index);
6938c2ecf20Sopenharmony_ci	if (type < 0)
6948c2ecf20Sopenharmony_ci		return type;
6958c2ecf20Sopenharmony_ci
6968c2ecf20Sopenharmony_ci	if (type & 0x10) {
6978c2ecf20Sopenharmony_ci		dock = true;
6988c2ecf20Sopenharmony_ci		type &= 0x0F;
6998c2ecf20Sopenharmony_ci	}
7008c2ecf20Sopenharmony_ci
7018c2ecf20Sopenharmony_ci	if (type >= ARRAY_SIZE(labels))
7028c2ecf20Sopenharmony_ci		type = (ARRAY_SIZE(labels) - 1);
7038c2ecf20Sopenharmony_ci
7048c2ecf20Sopenharmony_ci	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
7058c2ecf20Sopenharmony_ci}
7068c2ecf20Sopenharmony_ci
7078c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_fan_show(struct device *dev,
7088c2ecf20Sopenharmony_ci				  struct device_attribute *devattr, char *buf)
7098c2ecf20Sopenharmony_ci{
7108c2ecf20Sopenharmony_ci	int index = to_sensor_dev_attr(devattr)->index;
7118c2ecf20Sopenharmony_ci	int fan_speed;
7128c2ecf20Sopenharmony_ci
7138c2ecf20Sopenharmony_ci	fan_speed = i8k_get_fan_speed(index);
7148c2ecf20Sopenharmony_ci	if (fan_speed < 0)
7158c2ecf20Sopenharmony_ci		return fan_speed;
7168c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", fan_speed);
7178c2ecf20Sopenharmony_ci}
7188c2ecf20Sopenharmony_ci
7198c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_pwm_show(struct device *dev,
7208c2ecf20Sopenharmony_ci				  struct device_attribute *devattr, char *buf)
7218c2ecf20Sopenharmony_ci{
7228c2ecf20Sopenharmony_ci	int index = to_sensor_dev_attr(devattr)->index;
7238c2ecf20Sopenharmony_ci	int status;
7248c2ecf20Sopenharmony_ci
7258c2ecf20Sopenharmony_ci	status = i8k_get_fan_status(index);
7268c2ecf20Sopenharmony_ci	if (status < 0)
7278c2ecf20Sopenharmony_ci		return -EIO;
7288c2ecf20Sopenharmony_ci	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
7298c2ecf20Sopenharmony_ci}
7308c2ecf20Sopenharmony_ci
7318c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_pwm_store(struct device *dev,
7328c2ecf20Sopenharmony_ci				   struct device_attribute *attr,
7338c2ecf20Sopenharmony_ci				   const char *buf, size_t count)
7348c2ecf20Sopenharmony_ci{
7358c2ecf20Sopenharmony_ci	int index = to_sensor_dev_attr(attr)->index;
7368c2ecf20Sopenharmony_ci	unsigned long val;
7378c2ecf20Sopenharmony_ci	int err;
7388c2ecf20Sopenharmony_ci
7398c2ecf20Sopenharmony_ci	err = kstrtoul(buf, 10, &val);
7408c2ecf20Sopenharmony_ci	if (err)
7418c2ecf20Sopenharmony_ci		return err;
7428c2ecf20Sopenharmony_ci	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
7438c2ecf20Sopenharmony_ci
7448c2ecf20Sopenharmony_ci	mutex_lock(&i8k_mutex);
7458c2ecf20Sopenharmony_ci	err = i8k_set_fan(index, val);
7468c2ecf20Sopenharmony_ci	mutex_unlock(&i8k_mutex);
7478c2ecf20Sopenharmony_ci
7488c2ecf20Sopenharmony_ci	return err < 0 ? -EIO : count;
7498c2ecf20Sopenharmony_ci}
7508c2ecf20Sopenharmony_ci
7518c2ecf20Sopenharmony_cistatic ssize_t i8k_hwmon_pwm_enable_store(struct device *dev,
7528c2ecf20Sopenharmony_ci					  struct device_attribute *attr,
7538c2ecf20Sopenharmony_ci					  const char *buf, size_t count)
7548c2ecf20Sopenharmony_ci{
7558c2ecf20Sopenharmony_ci	int err;
7568c2ecf20Sopenharmony_ci	bool enable;
7578c2ecf20Sopenharmony_ci	unsigned long val;
7588c2ecf20Sopenharmony_ci
7598c2ecf20Sopenharmony_ci	if (!auto_fan)
7608c2ecf20Sopenharmony_ci		return -ENODEV;
7618c2ecf20Sopenharmony_ci
7628c2ecf20Sopenharmony_ci	err = kstrtoul(buf, 10, &val);
7638c2ecf20Sopenharmony_ci	if (err)
7648c2ecf20Sopenharmony_ci		return err;
7658c2ecf20Sopenharmony_ci
7668c2ecf20Sopenharmony_ci	if (val == 1)
7678c2ecf20Sopenharmony_ci		enable = false;
7688c2ecf20Sopenharmony_ci	else if (val == 2)
7698c2ecf20Sopenharmony_ci		enable = true;
7708c2ecf20Sopenharmony_ci	else
7718c2ecf20Sopenharmony_ci		return -EINVAL;
7728c2ecf20Sopenharmony_ci
7738c2ecf20Sopenharmony_ci	mutex_lock(&i8k_mutex);
7748c2ecf20Sopenharmony_ci	err = i8k_enable_fan_auto_mode(enable);
7758c2ecf20Sopenharmony_ci	mutex_unlock(&i8k_mutex);
7768c2ecf20Sopenharmony_ci
7778c2ecf20Sopenharmony_ci	return err ? err : count;
7788c2ecf20Sopenharmony_ci}
7798c2ecf20Sopenharmony_ci
7808c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_input, i8k_hwmon_temp, 0);
7818c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp1_label, i8k_hwmon_temp_label, 0);
7828c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp2_input, i8k_hwmon_temp, 1);
7838c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp2_label, i8k_hwmon_temp_label, 1);
7848c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp3_input, i8k_hwmon_temp, 2);
7858c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp3_label, i8k_hwmon_temp_label, 2);
7868c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp4_input, i8k_hwmon_temp, 3);
7878c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp4_label, i8k_hwmon_temp_label, 3);
7888c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp5_input, i8k_hwmon_temp, 4);
7898c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp5_label, i8k_hwmon_temp_label, 4);
7908c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp6_input, i8k_hwmon_temp, 5);
7918c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp6_label, i8k_hwmon_temp_label, 5);
7928c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp7_input, i8k_hwmon_temp, 6);
7938c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp7_label, i8k_hwmon_temp_label, 6);
7948c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp8_input, i8k_hwmon_temp, 7);
7958c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp8_label, i8k_hwmon_temp_label, 7);
7968c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp9_input, i8k_hwmon_temp, 8);
7978c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp9_label, i8k_hwmon_temp_label, 8);
7988c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp10_input, i8k_hwmon_temp, 9);
7998c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(temp10_label, i8k_hwmon_temp_label, 9);
8008c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan1_input, i8k_hwmon_fan, 0);
8018c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan1_label, i8k_hwmon_fan_label, 0);
8028c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm1, i8k_hwmon_pwm, 0);
8038c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_WO(pwm1_enable, i8k_hwmon_pwm_enable, 0);
8048c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan2_input, i8k_hwmon_fan, 1);
8058c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan2_label, i8k_hwmon_fan_label, 1);
8068c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm2, i8k_hwmon_pwm, 1);
8078c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan3_input, i8k_hwmon_fan, 2);
8088c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RO(fan3_label, i8k_hwmon_fan_label, 2);
8098c2ecf20Sopenharmony_cistatic SENSOR_DEVICE_ATTR_RW(pwm3, i8k_hwmon_pwm, 2);
8108c2ecf20Sopenharmony_ci
8118c2ecf20Sopenharmony_cistatic struct attribute *i8k_attrs[] = {
8128c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
8138c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
8148c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
8158c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
8168c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
8178c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
8188c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
8198c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
8208c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp5_input.dev_attr.attr,	/* 8 */
8218c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp5_label.dev_attr.attr,	/* 9 */
8228c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp6_input.dev_attr.attr,	/* 10 */
8238c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp6_label.dev_attr.attr,	/* 11 */
8248c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp7_input.dev_attr.attr,	/* 12 */
8258c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp7_label.dev_attr.attr,	/* 13 */
8268c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp8_input.dev_attr.attr,	/* 14 */
8278c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp8_label.dev_attr.attr,	/* 15 */
8288c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp9_input.dev_attr.attr,	/* 16 */
8298c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp9_label.dev_attr.attr,	/* 17 */
8308c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp10_input.dev_attr.attr,	/* 18 */
8318c2ecf20Sopenharmony_ci	&sensor_dev_attr_temp10_label.dev_attr.attr,	/* 19 */
8328c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 20 */
8338c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 21 */
8348c2ecf20Sopenharmony_ci	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 22 */
8358c2ecf20Sopenharmony_ci	&sensor_dev_attr_pwm1_enable.dev_attr.attr,	/* 23 */
8368c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 24 */
8378c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 25 */
8388c2ecf20Sopenharmony_ci	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 26 */
8398c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan3_input.dev_attr.attr,	/* 27 */
8408c2ecf20Sopenharmony_ci	&sensor_dev_attr_fan3_label.dev_attr.attr,	/* 28 */
8418c2ecf20Sopenharmony_ci	&sensor_dev_attr_pwm3.dev_attr.attr,		/* 29 */
8428c2ecf20Sopenharmony_ci	NULL
8438c2ecf20Sopenharmony_ci};
8448c2ecf20Sopenharmony_ci
8458c2ecf20Sopenharmony_cistatic umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
8468c2ecf20Sopenharmony_ci			      int index)
8478c2ecf20Sopenharmony_ci{
8488c2ecf20Sopenharmony_ci	if (disallow_fan_support && index >= 20)
8498c2ecf20Sopenharmony_ci		return 0;
8508c2ecf20Sopenharmony_ci	if (disallow_fan_type_call &&
8518c2ecf20Sopenharmony_ci	    (index == 21 || index == 25 || index == 28))
8528c2ecf20Sopenharmony_ci		return 0;
8538c2ecf20Sopenharmony_ci	if (index >= 0 && index <= 1 &&
8548c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
8558c2ecf20Sopenharmony_ci		return 0;
8568c2ecf20Sopenharmony_ci	if (index >= 2 && index <= 3 &&
8578c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
8588c2ecf20Sopenharmony_ci		return 0;
8598c2ecf20Sopenharmony_ci	if (index >= 4 && index <= 5 &&
8608c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
8618c2ecf20Sopenharmony_ci		return 0;
8628c2ecf20Sopenharmony_ci	if (index >= 6 && index <= 7 &&
8638c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
8648c2ecf20Sopenharmony_ci		return 0;
8658c2ecf20Sopenharmony_ci	if (index >= 8 && index <= 9 &&
8668c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP5))
8678c2ecf20Sopenharmony_ci		return 0;
8688c2ecf20Sopenharmony_ci	if (index >= 10 && index <= 11 &&
8698c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP6))
8708c2ecf20Sopenharmony_ci		return 0;
8718c2ecf20Sopenharmony_ci	if (index >= 12 && index <= 13 &&
8728c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP7))
8738c2ecf20Sopenharmony_ci		return 0;
8748c2ecf20Sopenharmony_ci	if (index >= 14 && index <= 15 &&
8758c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP8))
8768c2ecf20Sopenharmony_ci		return 0;
8778c2ecf20Sopenharmony_ci	if (index >= 16 && index <= 17 &&
8788c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP9))
8798c2ecf20Sopenharmony_ci		return 0;
8808c2ecf20Sopenharmony_ci	if (index >= 18 && index <= 19 &&
8818c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP10))
8828c2ecf20Sopenharmony_ci		return 0;
8838c2ecf20Sopenharmony_ci
8848c2ecf20Sopenharmony_ci	if (index >= 20 && index <= 23 &&
8858c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
8868c2ecf20Sopenharmony_ci		return 0;
8878c2ecf20Sopenharmony_ci	if (index >= 24 && index <= 26 &&
8888c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
8898c2ecf20Sopenharmony_ci		return 0;
8908c2ecf20Sopenharmony_ci	if (index >= 27 && index <= 29 &&
8918c2ecf20Sopenharmony_ci	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN3))
8928c2ecf20Sopenharmony_ci		return 0;
8938c2ecf20Sopenharmony_ci
8948c2ecf20Sopenharmony_ci	if (index == 23 && !auto_fan)
8958c2ecf20Sopenharmony_ci		return 0;
8968c2ecf20Sopenharmony_ci
8978c2ecf20Sopenharmony_ci	return attr->mode;
8988c2ecf20Sopenharmony_ci}
8998c2ecf20Sopenharmony_ci
9008c2ecf20Sopenharmony_cistatic const struct attribute_group i8k_group = {
9018c2ecf20Sopenharmony_ci	.attrs = i8k_attrs,
9028c2ecf20Sopenharmony_ci	.is_visible = i8k_is_visible,
9038c2ecf20Sopenharmony_ci};
9048c2ecf20Sopenharmony_ci__ATTRIBUTE_GROUPS(i8k);
9058c2ecf20Sopenharmony_ci
9068c2ecf20Sopenharmony_cistatic int __init i8k_init_hwmon(void)
9078c2ecf20Sopenharmony_ci{
9088c2ecf20Sopenharmony_ci	int err;
9098c2ecf20Sopenharmony_ci
9108c2ecf20Sopenharmony_ci	i8k_hwmon_flags = 0;
9118c2ecf20Sopenharmony_ci
9128c2ecf20Sopenharmony_ci	/* CPU temperature attributes, if temperature type is OK */
9138c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(0);
9148c2ecf20Sopenharmony_ci	if (err >= 0)
9158c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
9168c2ecf20Sopenharmony_ci	/* check for additional temperature sensors */
9178c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(1);
9188c2ecf20Sopenharmony_ci	if (err >= 0)
9198c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
9208c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(2);
9218c2ecf20Sopenharmony_ci	if (err >= 0)
9228c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
9238c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(3);
9248c2ecf20Sopenharmony_ci	if (err >= 0)
9258c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
9268c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(4);
9278c2ecf20Sopenharmony_ci	if (err >= 0)
9288c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP5;
9298c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(5);
9308c2ecf20Sopenharmony_ci	if (err >= 0)
9318c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP6;
9328c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(6);
9338c2ecf20Sopenharmony_ci	if (err >= 0)
9348c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP7;
9358c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(7);
9368c2ecf20Sopenharmony_ci	if (err >= 0)
9378c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP8;
9388c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(8);
9398c2ecf20Sopenharmony_ci	if (err >= 0)
9408c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP9;
9418c2ecf20Sopenharmony_ci	err = i8k_get_temp_type(9);
9428c2ecf20Sopenharmony_ci	if (err >= 0)
9438c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP10;
9448c2ecf20Sopenharmony_ci
9458c2ecf20Sopenharmony_ci	/* First fan attributes, if fan status or type is OK */
9468c2ecf20Sopenharmony_ci	err = i8k_get_fan_status(0);
9478c2ecf20Sopenharmony_ci	if (err < 0)
9488c2ecf20Sopenharmony_ci		err = i8k_get_fan_type(0);
9498c2ecf20Sopenharmony_ci	if (err >= 0)
9508c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
9518c2ecf20Sopenharmony_ci
9528c2ecf20Sopenharmony_ci	/* Second fan attributes, if fan status or type is OK */
9538c2ecf20Sopenharmony_ci	err = i8k_get_fan_status(1);
9548c2ecf20Sopenharmony_ci	if (err < 0)
9558c2ecf20Sopenharmony_ci		err = i8k_get_fan_type(1);
9568c2ecf20Sopenharmony_ci	if (err >= 0)
9578c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
9588c2ecf20Sopenharmony_ci
9598c2ecf20Sopenharmony_ci	/* Third fan attributes, if fan status or type is OK */
9608c2ecf20Sopenharmony_ci	err = i8k_get_fan_status(2);
9618c2ecf20Sopenharmony_ci	if (err < 0)
9628c2ecf20Sopenharmony_ci		err = i8k_get_fan_type(2);
9638c2ecf20Sopenharmony_ci	if (err >= 0)
9648c2ecf20Sopenharmony_ci		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN3;
9658c2ecf20Sopenharmony_ci
9668c2ecf20Sopenharmony_ci	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell_smm",
9678c2ecf20Sopenharmony_ci							  NULL, i8k_groups);
9688c2ecf20Sopenharmony_ci	if (IS_ERR(i8k_hwmon_dev)) {
9698c2ecf20Sopenharmony_ci		err = PTR_ERR(i8k_hwmon_dev);
9708c2ecf20Sopenharmony_ci		i8k_hwmon_dev = NULL;
9718c2ecf20Sopenharmony_ci		pr_err("hwmon registration failed (%d)\n", err);
9728c2ecf20Sopenharmony_ci		return err;
9738c2ecf20Sopenharmony_ci	}
9748c2ecf20Sopenharmony_ci	return 0;
9758c2ecf20Sopenharmony_ci}
9768c2ecf20Sopenharmony_ci
9778c2ecf20Sopenharmony_cistruct i8k_config_data {
9788c2ecf20Sopenharmony_ci	uint fan_mult;
9798c2ecf20Sopenharmony_ci	uint fan_max;
9808c2ecf20Sopenharmony_ci};
9818c2ecf20Sopenharmony_ci
9828c2ecf20Sopenharmony_cienum i8k_configs {
9838c2ecf20Sopenharmony_ci	DELL_LATITUDE_D520,
9848c2ecf20Sopenharmony_ci	DELL_PRECISION_490,
9858c2ecf20Sopenharmony_ci	DELL_STUDIO,
9868c2ecf20Sopenharmony_ci	DELL_XPS,
9878c2ecf20Sopenharmony_ci};
9888c2ecf20Sopenharmony_ci
9898c2ecf20Sopenharmony_cistatic const struct i8k_config_data i8k_config_data[] = {
9908c2ecf20Sopenharmony_ci	[DELL_LATITUDE_D520] = {
9918c2ecf20Sopenharmony_ci		.fan_mult = 1,
9928c2ecf20Sopenharmony_ci		.fan_max = I8K_FAN_TURBO,
9938c2ecf20Sopenharmony_ci	},
9948c2ecf20Sopenharmony_ci	[DELL_PRECISION_490] = {
9958c2ecf20Sopenharmony_ci		.fan_mult = 1,
9968c2ecf20Sopenharmony_ci		.fan_max = I8K_FAN_TURBO,
9978c2ecf20Sopenharmony_ci	},
9988c2ecf20Sopenharmony_ci	[DELL_STUDIO] = {
9998c2ecf20Sopenharmony_ci		.fan_mult = 1,
10008c2ecf20Sopenharmony_ci		.fan_max = I8K_FAN_HIGH,
10018c2ecf20Sopenharmony_ci	},
10028c2ecf20Sopenharmony_ci	[DELL_XPS] = {
10038c2ecf20Sopenharmony_ci		.fan_mult = 1,
10048c2ecf20Sopenharmony_ci		.fan_max = I8K_FAN_HIGH,
10058c2ecf20Sopenharmony_ci	},
10068c2ecf20Sopenharmony_ci};
10078c2ecf20Sopenharmony_ci
10088c2ecf20Sopenharmony_cistatic const struct dmi_system_id i8k_dmi_table[] __initconst = {
10098c2ecf20Sopenharmony_ci	{
10108c2ecf20Sopenharmony_ci		.ident = "Dell Inspiron",
10118c2ecf20Sopenharmony_ci		.matches = {
10128c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
10138c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
10148c2ecf20Sopenharmony_ci		},
10158c2ecf20Sopenharmony_ci	},
10168c2ecf20Sopenharmony_ci	{
10178c2ecf20Sopenharmony_ci		.ident = "Dell Latitude",
10188c2ecf20Sopenharmony_ci		.matches = {
10198c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
10208c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
10218c2ecf20Sopenharmony_ci		},
10228c2ecf20Sopenharmony_ci	},
10238c2ecf20Sopenharmony_ci	{
10248c2ecf20Sopenharmony_ci		.ident = "Dell Inspiron 2",
10258c2ecf20Sopenharmony_ci		.matches = {
10268c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10278c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
10288c2ecf20Sopenharmony_ci		},
10298c2ecf20Sopenharmony_ci	},
10308c2ecf20Sopenharmony_ci	{
10318c2ecf20Sopenharmony_ci		.ident = "Dell Latitude D520",
10328c2ecf20Sopenharmony_ci		.matches = {
10338c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10348c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
10358c2ecf20Sopenharmony_ci		},
10368c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
10378c2ecf20Sopenharmony_ci	},
10388c2ecf20Sopenharmony_ci	{
10398c2ecf20Sopenharmony_ci		.ident = "Dell Latitude 2",
10408c2ecf20Sopenharmony_ci		.matches = {
10418c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10428c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
10438c2ecf20Sopenharmony_ci		},
10448c2ecf20Sopenharmony_ci	},
10458c2ecf20Sopenharmony_ci	{	/* UK Inspiron 6400  */
10468c2ecf20Sopenharmony_ci		.ident = "Dell Inspiron 3",
10478c2ecf20Sopenharmony_ci		.matches = {
10488c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10498c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
10508c2ecf20Sopenharmony_ci		},
10518c2ecf20Sopenharmony_ci	},
10528c2ecf20Sopenharmony_ci	{
10538c2ecf20Sopenharmony_ci		.ident = "Dell Inspiron 3",
10548c2ecf20Sopenharmony_ci		.matches = {
10558c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10568c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
10578c2ecf20Sopenharmony_ci		},
10588c2ecf20Sopenharmony_ci	},
10598c2ecf20Sopenharmony_ci	{
10608c2ecf20Sopenharmony_ci		.ident = "Dell Precision 490",
10618c2ecf20Sopenharmony_ci		.matches = {
10628c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10638c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME,
10648c2ecf20Sopenharmony_ci				  "Precision WorkStation 490"),
10658c2ecf20Sopenharmony_ci		},
10668c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
10678c2ecf20Sopenharmony_ci	},
10688c2ecf20Sopenharmony_ci	{
10698c2ecf20Sopenharmony_ci		.ident = "Dell Precision",
10708c2ecf20Sopenharmony_ci		.matches = {
10718c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10728c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
10738c2ecf20Sopenharmony_ci		},
10748c2ecf20Sopenharmony_ci	},
10758c2ecf20Sopenharmony_ci	{
10768c2ecf20Sopenharmony_ci		.ident = "Dell Vostro",
10778c2ecf20Sopenharmony_ci		.matches = {
10788c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10798c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
10808c2ecf20Sopenharmony_ci		},
10818c2ecf20Sopenharmony_ci	},
10828c2ecf20Sopenharmony_ci	{
10838c2ecf20Sopenharmony_ci		.ident = "Dell Studio",
10848c2ecf20Sopenharmony_ci		.matches = {
10858c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10868c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
10878c2ecf20Sopenharmony_ci		},
10888c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
10898c2ecf20Sopenharmony_ci	},
10908c2ecf20Sopenharmony_ci	{
10918c2ecf20Sopenharmony_ci		.ident = "Dell XPS M140",
10928c2ecf20Sopenharmony_ci		.matches = {
10938c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
10948c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
10958c2ecf20Sopenharmony_ci		},
10968c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_config_data[DELL_XPS],
10978c2ecf20Sopenharmony_ci	},
10988c2ecf20Sopenharmony_ci	{
10998c2ecf20Sopenharmony_ci		.ident = "Dell XPS",
11008c2ecf20Sopenharmony_ci		.matches = {
11018c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11028c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_PRODUCT_NAME, "XPS"),
11038c2ecf20Sopenharmony_ci		},
11048c2ecf20Sopenharmony_ci	},
11058c2ecf20Sopenharmony_ci	{ }
11068c2ecf20Sopenharmony_ci};
11078c2ecf20Sopenharmony_ci
11088c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
11098c2ecf20Sopenharmony_ci
11108c2ecf20Sopenharmony_ci/*
11118c2ecf20Sopenharmony_ci * On some machines once I8K_SMM_GET_FAN_TYPE is issued then CPU fan speed
11128c2ecf20Sopenharmony_ci * randomly going up and down due to bug in Dell SMM or BIOS. Here is blacklist
11138c2ecf20Sopenharmony_ci * of affected Dell machines for which we disallow I8K_SMM_GET_FAN_TYPE call.
11148c2ecf20Sopenharmony_ci * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=100121
11158c2ecf20Sopenharmony_ci */
11168c2ecf20Sopenharmony_cistatic const struct dmi_system_id i8k_blacklist_fan_type_dmi_table[] __initconst = {
11178c2ecf20Sopenharmony_ci	{
11188c2ecf20Sopenharmony_ci		.ident = "Dell Studio XPS 8000",
11198c2ecf20Sopenharmony_ci		.matches = {
11208c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11218c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8000"),
11228c2ecf20Sopenharmony_ci		},
11238c2ecf20Sopenharmony_ci	},
11248c2ecf20Sopenharmony_ci	{
11258c2ecf20Sopenharmony_ci		.ident = "Dell Studio XPS 8100",
11268c2ecf20Sopenharmony_ci		.matches = {
11278c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11288c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Studio XPS 8100"),
11298c2ecf20Sopenharmony_ci		},
11308c2ecf20Sopenharmony_ci	},
11318c2ecf20Sopenharmony_ci	{
11328c2ecf20Sopenharmony_ci		.ident = "Dell Inspiron 580",
11338c2ecf20Sopenharmony_ci		.matches = {
11348c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11358c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 580 "),
11368c2ecf20Sopenharmony_ci		},
11378c2ecf20Sopenharmony_ci	},
11388c2ecf20Sopenharmony_ci	{ }
11398c2ecf20Sopenharmony_ci};
11408c2ecf20Sopenharmony_ci
11418c2ecf20Sopenharmony_ci/*
11428c2ecf20Sopenharmony_ci * On some machines all fan related SMM functions implemented by Dell BIOS
11438c2ecf20Sopenharmony_ci * firmware freeze kernel for about 500ms. Until Dell fixes these problems fan
11448c2ecf20Sopenharmony_ci * support for affected blacklisted Dell machines stay disabled.
11458c2ecf20Sopenharmony_ci * See bug: https://bugzilla.kernel.org/show_bug.cgi?id=195751
11468c2ecf20Sopenharmony_ci */
11478c2ecf20Sopenharmony_cistatic struct dmi_system_id i8k_blacklist_fan_support_dmi_table[] __initdata = {
11488c2ecf20Sopenharmony_ci	{
11498c2ecf20Sopenharmony_ci		.ident = "Dell Inspiron 7720",
11508c2ecf20Sopenharmony_ci		.matches = {
11518c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11528c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Inspiron 7720"),
11538c2ecf20Sopenharmony_ci		},
11548c2ecf20Sopenharmony_ci	},
11558c2ecf20Sopenharmony_ci	{
11568c2ecf20Sopenharmony_ci		.ident = "Dell Vostro 3360",
11578c2ecf20Sopenharmony_ci		.matches = {
11588c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11598c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Vostro 3360"),
11608c2ecf20Sopenharmony_ci		},
11618c2ecf20Sopenharmony_ci	},
11628c2ecf20Sopenharmony_ci	{
11638c2ecf20Sopenharmony_ci		.ident = "Dell XPS13 9333",
11648c2ecf20Sopenharmony_ci		.matches = {
11658c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11668c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "XPS13 9333"),
11678c2ecf20Sopenharmony_ci		},
11688c2ecf20Sopenharmony_ci	},
11698c2ecf20Sopenharmony_ci	{
11708c2ecf20Sopenharmony_ci		.ident = "Dell XPS 15 L502X",
11718c2ecf20Sopenharmony_ci		.matches = {
11728c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
11738c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Dell System XPS L502X"),
11748c2ecf20Sopenharmony_ci		},
11758c2ecf20Sopenharmony_ci	},
11768c2ecf20Sopenharmony_ci	{ }
11778c2ecf20Sopenharmony_ci};
11788c2ecf20Sopenharmony_ci
11798c2ecf20Sopenharmony_cistruct i8k_fan_control_data {
11808c2ecf20Sopenharmony_ci	unsigned int manual_fan;
11818c2ecf20Sopenharmony_ci	unsigned int auto_fan;
11828c2ecf20Sopenharmony_ci};
11838c2ecf20Sopenharmony_ci
11848c2ecf20Sopenharmony_cienum i8k_fan_controls {
11858c2ecf20Sopenharmony_ci	I8K_FAN_34A3_35A3,
11868c2ecf20Sopenharmony_ci};
11878c2ecf20Sopenharmony_ci
11888c2ecf20Sopenharmony_cistatic const struct i8k_fan_control_data i8k_fan_control_data[] = {
11898c2ecf20Sopenharmony_ci	[I8K_FAN_34A3_35A3] = {
11908c2ecf20Sopenharmony_ci		.manual_fan = 0x34a3,
11918c2ecf20Sopenharmony_ci		.auto_fan = 0x35a3,
11928c2ecf20Sopenharmony_ci	},
11938c2ecf20Sopenharmony_ci};
11948c2ecf20Sopenharmony_ci
11958c2ecf20Sopenharmony_cistatic struct dmi_system_id i8k_whitelist_fan_control[] __initdata = {
11968c2ecf20Sopenharmony_ci	{
11978c2ecf20Sopenharmony_ci		.ident = "Dell Precision 5530",
11988c2ecf20Sopenharmony_ci		.matches = {
11998c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
12008c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Precision 5530"),
12018c2ecf20Sopenharmony_ci		},
12028c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
12038c2ecf20Sopenharmony_ci	},
12048c2ecf20Sopenharmony_ci	{
12058c2ecf20Sopenharmony_ci		.ident = "Dell Latitude 5480",
12068c2ecf20Sopenharmony_ci		.matches = {
12078c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
12088c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 5480"),
12098c2ecf20Sopenharmony_ci		},
12108c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
12118c2ecf20Sopenharmony_ci	},
12128c2ecf20Sopenharmony_ci	{
12138c2ecf20Sopenharmony_ci		.ident = "Dell Latitude E6440",
12148c2ecf20Sopenharmony_ci		.matches = {
12158c2ecf20Sopenharmony_ci			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
12168c2ecf20Sopenharmony_ci			DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude E6440"),
12178c2ecf20Sopenharmony_ci		},
12188c2ecf20Sopenharmony_ci		.driver_data = (void *)&i8k_fan_control_data[I8K_FAN_34A3_35A3],
12198c2ecf20Sopenharmony_ci	},
12208c2ecf20Sopenharmony_ci	{ }
12218c2ecf20Sopenharmony_ci};
12228c2ecf20Sopenharmony_ci
12238c2ecf20Sopenharmony_ci/*
12248c2ecf20Sopenharmony_ci * Probe for the presence of a supported laptop.
12258c2ecf20Sopenharmony_ci */
12268c2ecf20Sopenharmony_cistatic int __init i8k_probe(void)
12278c2ecf20Sopenharmony_ci{
12288c2ecf20Sopenharmony_ci	const struct dmi_system_id *id, *fan_control;
12298c2ecf20Sopenharmony_ci	int fan, ret;
12308c2ecf20Sopenharmony_ci
12318c2ecf20Sopenharmony_ci	/*
12328c2ecf20Sopenharmony_ci	 * Get DMI information
12338c2ecf20Sopenharmony_ci	 */
12348c2ecf20Sopenharmony_ci	if (!dmi_check_system(i8k_dmi_table)) {
12358c2ecf20Sopenharmony_ci		if (!ignore_dmi && !force)
12368c2ecf20Sopenharmony_ci			return -ENODEV;
12378c2ecf20Sopenharmony_ci
12388c2ecf20Sopenharmony_ci		pr_info("not running on a supported Dell system.\n");
12398c2ecf20Sopenharmony_ci		pr_info("vendor=%s, model=%s, version=%s\n",
12408c2ecf20Sopenharmony_ci			i8k_get_dmi_data(DMI_SYS_VENDOR),
12418c2ecf20Sopenharmony_ci			i8k_get_dmi_data(DMI_PRODUCT_NAME),
12428c2ecf20Sopenharmony_ci			i8k_get_dmi_data(DMI_BIOS_VERSION));
12438c2ecf20Sopenharmony_ci	}
12448c2ecf20Sopenharmony_ci
12458c2ecf20Sopenharmony_ci	if (dmi_check_system(i8k_blacklist_fan_support_dmi_table)) {
12468c2ecf20Sopenharmony_ci		pr_warn("broken Dell BIOS detected, disallow fan support\n");
12478c2ecf20Sopenharmony_ci		if (!force)
12488c2ecf20Sopenharmony_ci			disallow_fan_support = true;
12498c2ecf20Sopenharmony_ci	}
12508c2ecf20Sopenharmony_ci
12518c2ecf20Sopenharmony_ci	if (dmi_check_system(i8k_blacklist_fan_type_dmi_table)) {
12528c2ecf20Sopenharmony_ci		pr_warn("broken Dell BIOS detected, disallow fan type call\n");
12538c2ecf20Sopenharmony_ci		if (!force)
12548c2ecf20Sopenharmony_ci			disallow_fan_type_call = true;
12558c2ecf20Sopenharmony_ci	}
12568c2ecf20Sopenharmony_ci
12578c2ecf20Sopenharmony_ci	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
12588c2ecf20Sopenharmony_ci		sizeof(bios_version));
12598c2ecf20Sopenharmony_ci	strlcpy(bios_machineid, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
12608c2ecf20Sopenharmony_ci		sizeof(bios_machineid));
12618c2ecf20Sopenharmony_ci
12628c2ecf20Sopenharmony_ci	/*
12638c2ecf20Sopenharmony_ci	 * Get SMM Dell signature
12648c2ecf20Sopenharmony_ci	 */
12658c2ecf20Sopenharmony_ci	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
12668c2ecf20Sopenharmony_ci	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
12678c2ecf20Sopenharmony_ci		pr_err("unable to get SMM Dell signature\n");
12688c2ecf20Sopenharmony_ci		if (!force)
12698c2ecf20Sopenharmony_ci			return -ENODEV;
12708c2ecf20Sopenharmony_ci	}
12718c2ecf20Sopenharmony_ci
12728c2ecf20Sopenharmony_ci	/*
12738c2ecf20Sopenharmony_ci	 * Set fan multiplier and maximal fan speed from dmi config
12748c2ecf20Sopenharmony_ci	 * Values specified in module parameters override values from dmi
12758c2ecf20Sopenharmony_ci	 */
12768c2ecf20Sopenharmony_ci	id = dmi_first_match(i8k_dmi_table);
12778c2ecf20Sopenharmony_ci	if (id && id->driver_data) {
12788c2ecf20Sopenharmony_ci		const struct i8k_config_data *conf = id->driver_data;
12798c2ecf20Sopenharmony_ci		if (!fan_mult && conf->fan_mult)
12808c2ecf20Sopenharmony_ci			fan_mult = conf->fan_mult;
12818c2ecf20Sopenharmony_ci		if (!fan_max && conf->fan_max)
12828c2ecf20Sopenharmony_ci			fan_max = conf->fan_max;
12838c2ecf20Sopenharmony_ci	}
12848c2ecf20Sopenharmony_ci
12858c2ecf20Sopenharmony_ci	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
12868c2ecf20Sopenharmony_ci	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
12878c2ecf20Sopenharmony_ci
12888c2ecf20Sopenharmony_ci	fan_control = dmi_first_match(i8k_whitelist_fan_control);
12898c2ecf20Sopenharmony_ci	if (fan_control && fan_control->driver_data) {
12908c2ecf20Sopenharmony_ci		const struct i8k_fan_control_data *data = fan_control->driver_data;
12918c2ecf20Sopenharmony_ci
12928c2ecf20Sopenharmony_ci		manual_fan = data->manual_fan;
12938c2ecf20Sopenharmony_ci		auto_fan = data->auto_fan;
12948c2ecf20Sopenharmony_ci		pr_info("enabling support for setting automatic/manual fan control\n");
12958c2ecf20Sopenharmony_ci	}
12968c2ecf20Sopenharmony_ci
12978c2ecf20Sopenharmony_ci	if (!fan_mult) {
12988c2ecf20Sopenharmony_ci		/*
12998c2ecf20Sopenharmony_ci		 * Autodetect fan multiplier based on nominal rpm
13008c2ecf20Sopenharmony_ci		 * If fan reports rpm value too high then set multiplier to 1
13018c2ecf20Sopenharmony_ci		 */
13028c2ecf20Sopenharmony_ci		for (fan = 0; fan < 2; ++fan) {
13038c2ecf20Sopenharmony_ci			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
13048c2ecf20Sopenharmony_ci			if (ret < 0)
13058c2ecf20Sopenharmony_ci				continue;
13068c2ecf20Sopenharmony_ci			if (ret > I8K_FAN_MAX_RPM)
13078c2ecf20Sopenharmony_ci				i8k_fan_mult = 1;
13088c2ecf20Sopenharmony_ci			break;
13098c2ecf20Sopenharmony_ci		}
13108c2ecf20Sopenharmony_ci	} else {
13118c2ecf20Sopenharmony_ci		/* Fan multiplier was specified in module param or in dmi */
13128c2ecf20Sopenharmony_ci		i8k_fan_mult = fan_mult;
13138c2ecf20Sopenharmony_ci	}
13148c2ecf20Sopenharmony_ci
13158c2ecf20Sopenharmony_ci	return 0;
13168c2ecf20Sopenharmony_ci}
13178c2ecf20Sopenharmony_ci
13188c2ecf20Sopenharmony_cistatic int __init i8k_init(void)
13198c2ecf20Sopenharmony_ci{
13208c2ecf20Sopenharmony_ci	int err;
13218c2ecf20Sopenharmony_ci
13228c2ecf20Sopenharmony_ci	/* Are we running on an supported laptop? */
13238c2ecf20Sopenharmony_ci	if (i8k_probe())
13248c2ecf20Sopenharmony_ci		return -ENODEV;
13258c2ecf20Sopenharmony_ci
13268c2ecf20Sopenharmony_ci	err = i8k_init_hwmon();
13278c2ecf20Sopenharmony_ci	if (err)
13288c2ecf20Sopenharmony_ci		return err;
13298c2ecf20Sopenharmony_ci
13308c2ecf20Sopenharmony_ci	i8k_init_procfs();
13318c2ecf20Sopenharmony_ci	return 0;
13328c2ecf20Sopenharmony_ci}
13338c2ecf20Sopenharmony_ci
13348c2ecf20Sopenharmony_cistatic void __exit i8k_exit(void)
13358c2ecf20Sopenharmony_ci{
13368c2ecf20Sopenharmony_ci	hwmon_device_unregister(i8k_hwmon_dev);
13378c2ecf20Sopenharmony_ci	i8k_exit_procfs();
13388c2ecf20Sopenharmony_ci}
13398c2ecf20Sopenharmony_ci
13408c2ecf20Sopenharmony_cimodule_init(i8k_init);
13418c2ecf20Sopenharmony_cimodule_exit(i8k_exit);
1342