linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/2] i8k.c => dell-smm-hwmon.c
@ 2015-03-28 10:24 Pali Rohár
  2015-03-28 10:24 ` [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
                   ` (2 more replies)
  0 siblings, 3 replies; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 10:24 UTC (permalink / raw)
  To: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors, Pali Rohár

This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
and allows to compile hwmon driver without /proc/i8k file.

Pali Rohár (2):
  hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k

 MAINTAINERS                    |    3 +-
 arch/x86/Kconfig               |   25 +-
 drivers/char/Makefile          |    1 -
 drivers/char/i8k.c             | 1005 ---------------------------------------
 drivers/hwmon/Kconfig          |   11 +
 drivers/hwmon/Makefile         |    1 +
 drivers/hwmon/dell-smm-hwmon.c | 1019 ++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1042 insertions(+), 1023 deletions(-)
 delete mode 100644 drivers/char/i8k.c
 create mode 100644 drivers/hwmon/dell-smm-hwmon.c

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  2015-03-28 10:24 [PATCH 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
@ 2015-03-28 10:24 ` Pali Rohár
  2015-03-28 14:31   ` Guenter Roeck
  2015-03-28 10:24 ` [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
  2015-03-29 12:57 ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
  2 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 10:24 UTC (permalink / raw)
  To: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors, Pali Rohár

This commit moves i8k driver to hwmon tree under name dell-smm-hwmon which is
better name then abbreviation i8k. For backward compatibility is added macro
MODULE_ALIAS("i8k") so modprobe will load driver also old name i8k. CONFIG_I8K
compile option was not changed.

This commit also adds me as maintainer of this new dell-smm-hwmon driver.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
---
 MAINTAINERS                    |    3 +-
 drivers/char/Makefile          |    1 -
 drivers/char/i8k.c             | 1005 ---------------------------------------
 drivers/hwmon/Makefile         |    1 +
 drivers/hwmon/dell-smm-hwmon.c | 1007 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1010 insertions(+), 1007 deletions(-)
 delete mode 100644 drivers/char/i8k.c
 create mode 100644 drivers/hwmon/dell-smm-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 88c09ca..bf5bb05 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3064,8 +3064,9 @@ F:	drivers/platform/x86/dell-laptop.c
 
 DELL LAPTOP SMM DRIVER
 M:	Guenter Roeck <linux@roeck-us.net>
+M:	Pali Rohár <pali.rohar@gmail.com>
 S:	Maintained
-F:	drivers/char/i8k.c
+F:	drivers/hwmon/dell-smm-hwmon.c
 F:	include/uapi/linux/i8k.h
 
 DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas)
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index d06cde26..1d9cf00 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -36,7 +36,6 @@ else
   obj-$(CONFIG_NVRAM)	+= nvram.o
 endif
 obj-$(CONFIG_TOSHIBA)		+= toshiba.o
-obj-$(CONFIG_I8K)		+= i8k.o
 obj-$(CONFIG_DS1620)		+= ds1620.o
 obj-$(CONFIG_HW_RANDOM)		+= hw_random/
 obj-$(CONFIG_PPDEV)		+= ppdev.o
diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c
deleted file mode 100644
index 24cc4ed..0000000
--- a/drivers/char/i8k.c
+++ /dev/null
@@ -1,1005 +0,0 @@
-/*
- * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
- *
- * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
- *
- * Hwmon integration:
- * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
- * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
- * Copyright (C) 2014  Pali Rohár <pali.rohar@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/delay.h>
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/init.h>
-#include <linux/proc_fs.h>
-#include <linux/seq_file.h>
-#include <linux/dmi.h>
-#include <linux/capability.h>
-#include <linux/mutex.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/uaccess.h>
-#include <linux/io.h>
-#include <linux/sched.h>
-
-#include <linux/i8k.h>
-
-#define I8K_SMM_FN_STATUS	0x0025
-#define I8K_SMM_POWER_STATUS	0x0069
-#define I8K_SMM_SET_FAN		0x01a3
-#define I8K_SMM_GET_FAN		0x00a3
-#define I8K_SMM_GET_SPEED	0x02a3
-#define I8K_SMM_GET_FAN_TYPE	0x03a3
-#define I8K_SMM_GET_NOM_SPEED	0x04a3
-#define I8K_SMM_GET_TEMP	0x10a3
-#define I8K_SMM_GET_TEMP_TYPE	0x11a3
-#define I8K_SMM_GET_DELL_SIG1	0xfea3
-#define I8K_SMM_GET_DELL_SIG2	0xffa3
-
-#define I8K_FAN_MULT		30
-#define I8K_FAN_MAX_RPM		30000
-#define I8K_MAX_TEMP		127
-
-#define I8K_FN_NONE		0x00
-#define I8K_FN_UP		0x01
-#define I8K_FN_DOWN		0x02
-#define I8K_FN_MUTE		0x04
-#define I8K_FN_MASK		0x07
-#define I8K_FN_SHIFT		8
-
-#define I8K_POWER_AC		0x05
-#define I8K_POWER_BATTERY	0x01
-
-static DEFINE_MUTEX(i8k_mutex);
-static char bios_version[4];
-static struct device *i8k_hwmon_dev;
-static u32 i8k_hwmon_flags;
-static uint i8k_fan_mult = I8K_FAN_MULT;
-static uint i8k_pwm_mult;
-static uint i8k_fan_max = I8K_FAN_HIGH;
-
-#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
-#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
-#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
-#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
-#define I8K_HWMON_HAVE_FAN1	(1 << 4)
-#define I8K_HWMON_HAVE_FAN2	(1 << 5)
-
-MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
-MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
-MODULE_LICENSE("GPL");
-
-static bool force;
-module_param(force, bool, 0);
-MODULE_PARM_DESC(force, "Force loading without checking for supported models");
-
-static bool ignore_dmi;
-module_param(ignore_dmi, bool, 0);
-MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
-
-static bool restricted;
-module_param(restricted, bool, 0);
-MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
-
-static bool power_status;
-module_param(power_status, bool, 0600);
-MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
-
-static uint fan_mult;
-module_param(fan_mult, uint, 0);
-MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
-
-static uint fan_max;
-module_param(fan_max, uint, 0);
-MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
-
-static int i8k_open_fs(struct inode *inode, struct file *file);
-static long i8k_ioctl(struct file *, unsigned int, unsigned long);
-
-static const struct file_operations i8k_fops = {
-	.owner		= THIS_MODULE,
-	.open		= i8k_open_fs,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= single_release,
-	.unlocked_ioctl	= i8k_ioctl,
-};
-
-struct smm_regs {
-	unsigned int eax;
-	unsigned int ebx __packed;
-	unsigned int ecx __packed;
-	unsigned int edx __packed;
-	unsigned int esi __packed;
-	unsigned int edi __packed;
-};
-
-static inline const char *i8k_get_dmi_data(int field)
-{
-	const char *dmi_data = dmi_get_system_info(field);
-
-	return dmi_data && *dmi_data ? dmi_data : "?";
-}
-
-/*
- * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
- */
-static int i8k_smm(struct smm_regs *regs)
-{
-	int rc;
-	int eax = regs->eax;
-	cpumask_var_t old_mask;
-
-	/* SMM requires CPU 0 */
-	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
-		return -ENOMEM;
-	cpumask_copy(old_mask, &current->cpus_allowed);
-	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
-	if (rc)
-		goto out;
-	if (smp_processor_id() != 0) {
-		rc = -EBUSY;
-		goto out;
-	}
-
-#if defined(CONFIG_X86_64)
-	asm volatile("pushq %%rax\n\t"
-		"movl 0(%%rax),%%edx\n\t"
-		"pushq %%rdx\n\t"
-		"movl 4(%%rax),%%ebx\n\t"
-		"movl 8(%%rax),%%ecx\n\t"
-		"movl 12(%%rax),%%edx\n\t"
-		"movl 16(%%rax),%%esi\n\t"
-		"movl 20(%%rax),%%edi\n\t"
-		"popq %%rax\n\t"
-		"out %%al,$0xb2\n\t"
-		"out %%al,$0x84\n\t"
-		"xchgq %%rax,(%%rsp)\n\t"
-		"movl %%ebx,4(%%rax)\n\t"
-		"movl %%ecx,8(%%rax)\n\t"
-		"movl %%edx,12(%%rax)\n\t"
-		"movl %%esi,16(%%rax)\n\t"
-		"movl %%edi,20(%%rax)\n\t"
-		"popq %%rdx\n\t"
-		"movl %%edx,0(%%rax)\n\t"
-		"pushfq\n\t"
-		"popq %%rax\n\t"
-		"andl $1,%%eax\n"
-		: "=a"(rc)
-		:    "a"(regs)
-		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
-#else
-	asm volatile("pushl %%eax\n\t"
-	    "movl 0(%%eax),%%edx\n\t"
-	    "push %%edx\n\t"
-	    "movl 4(%%eax),%%ebx\n\t"
-	    "movl 8(%%eax),%%ecx\n\t"
-	    "movl 12(%%eax),%%edx\n\t"
-	    "movl 16(%%eax),%%esi\n\t"
-	    "movl 20(%%eax),%%edi\n\t"
-	    "popl %%eax\n\t"
-	    "out %%al,$0xb2\n\t"
-	    "out %%al,$0x84\n\t"
-	    "xchgl %%eax,(%%esp)\n\t"
-	    "movl %%ebx,4(%%eax)\n\t"
-	    "movl %%ecx,8(%%eax)\n\t"
-	    "movl %%edx,12(%%eax)\n\t"
-	    "movl %%esi,16(%%eax)\n\t"
-	    "movl %%edi,20(%%eax)\n\t"
-	    "popl %%edx\n\t"
-	    "movl %%edx,0(%%eax)\n\t"
-	    "lahf\n\t"
-	    "shrl $8,%%eax\n\t"
-	    "andl $1,%%eax\n"
-	    : "=a"(rc)
-	    :    "a"(regs)
-	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
-#endif
-	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
-		rc = -EINVAL;
-
-out:
-	set_cpus_allowed_ptr(current, old_mask);
-	free_cpumask_var(old_mask);
-	return rc;
-}
-
-/*
- * Read the Fn key status.
- */
-static int i8k_get_fn_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
-	case I8K_FN_UP:
-		return I8K_VOL_UP;
-	case I8K_FN_DOWN:
-		return I8K_VOL_DOWN;
-	case I8K_FN_MUTE:
-		return I8K_VOL_MUTE;
-	default:
-		return 0;
-	}
-}
-
-/*
- * Read the power status.
- */
-static int i8k_get_power_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
-}
-
-/*
- * Read the fan status.
- */
-static int i8k_get_fan_status(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the fan speed in RPM.
- */
-static int i8k_get_fan_speed(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
-}
-
-/*
- * Read the fan type.
- */
-static int i8k_get_fan_type(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the fan nominal rpm for specific fan speed.
- */
-static int i8k_get_fan_nominal_speed(int fan, int speed)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
-
-	regs.ebx = (fan & 0xff) | (speed << 8);
-	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
-}
-
-/*
- * Set the fan speed (off, low, high). Returns the new fan status.
- */
-static int i8k_set_fan(int fan, int speed)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
-
-	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
-	regs.ebx = (fan & 0xff) | (speed << 8);
-
-	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
-}
-
-static int i8k_get_temp_type(int sensor)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
-
-	regs.ebx = sensor & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the cpu temperature.
- */
-static int _i8k_get_temp(int sensor)
-{
-	struct smm_regs regs = {
-		.eax = I8K_SMM_GET_TEMP,
-		.ebx = sensor & 0xff,
-	};
-
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-static int i8k_get_temp(int sensor)
-{
-	int temp = _i8k_get_temp(sensor);
-
-	/*
-	 * Sometimes the temperature sensor returns 0x99, which is out of range.
-	 * In this case we retry (once) before returning an error.
-	 # 1003655137 00000058 00005a4b
-	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
-	 # 1003655139 00000054 00005c52
-	 */
-	if (temp == 0x99) {
-		msleep(100);
-		temp = _i8k_get_temp(sensor);
-	}
-	/*
-	 * Return -ENODATA for all invalid temperatures.
-	 *
-	 * Known instances are the 0x99 value as seen above as well as
-	 * 0xc1 (193), which may be returned when trying to read the GPU
-	 * temperature if the system supports a GPU and it is currently
-	 * turned off.
-	 */
-	if (temp > I8K_MAX_TEMP)
-		return -ENODATA;
-
-	return temp;
-}
-
-static int i8k_get_dell_signature(int req_fn)
-{
-	struct smm_regs regs = { .eax = req_fn, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
-}
-
-static int
-i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
-{
-	int val = 0;
-	int speed;
-	unsigned char buff[16];
-	int __user *argp = (int __user *)arg;
-
-	if (!argp)
-		return -EINVAL;
-
-	switch (cmd) {
-	case I8K_BIOS_VERSION:
-		val = (bios_version[0] << 16) |
-				(bios_version[1] << 8) | bios_version[2];
-		break;
-
-	case I8K_MACHINE_ID:
-		memset(buff, 0, 16);
-		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-			sizeof(buff));
-		break;
-
-	case I8K_FN_STATUS:
-		val = i8k_get_fn_status();
-		break;
-
-	case I8K_POWER_STATUS:
-		val = i8k_get_power_status();
-		break;
-
-	case I8K_GET_TEMP:
-		val = i8k_get_temp(0);
-		break;
-
-	case I8K_GET_SPEED:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_get_fan_speed(val);
-		break;
-
-	case I8K_GET_FAN:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_get_fan_status(val);
-		break;
-
-	case I8K_SET_FAN:
-		if (restricted && !capable(CAP_SYS_ADMIN))
-			return -EPERM;
-
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		if (copy_from_user(&speed, argp + 1, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_set_fan(val, speed);
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
-	if (val < 0)
-		return val;
-
-	switch (cmd) {
-	case I8K_BIOS_VERSION:
-		if (copy_to_user(argp, &val, 4))
-			return -EFAULT;
-
-		break;
-	case I8K_MACHINE_ID:
-		if (copy_to_user(argp, buff, 16))
-			return -EFAULT;
-
-		break;
-	default:
-		if (copy_to_user(argp, &val, sizeof(int)))
-			return -EFAULT;
-
-		break;
-	}
-
-	return 0;
-}
-
-static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
-{
-	long ret;
-
-	mutex_lock(&i8k_mutex);
-	ret = i8k_ioctl_unlocked(fp, cmd, arg);
-	mutex_unlock(&i8k_mutex);
-
-	return ret;
-}
-
-/*
- * Print the information for /proc/i8k.
- */
-static int i8k_proc_show(struct seq_file *seq, void *offset)
-{
-	int fn_key, cpu_temp, ac_power;
-	int left_fan, right_fan, left_speed, right_speed;
-
-	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
-	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
-	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
-	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
-	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
-	fn_key		= i8k_get_fn_status();			/*   750 µs */
-	if (power_status)
-		ac_power = i8k_get_power_status();		/* 14700 µs */
-	else
-		ac_power = -1;
-
-	/*
-	 * Info:
-	 *
-	 * 1)  Format version (this will change if format changes)
-	 * 2)  BIOS version
-	 * 3)  BIOS machine ID
-	 * 4)  Cpu temperature
-	 * 5)  Left fan status
-	 * 6)  Right fan status
-	 * 7)  Left fan speed
-	 * 8)  Right fan speed
-	 * 9)  AC power
-	 * 10) Fn Key status
-	 */
-	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
-			  I8K_PROC_FMT,
-			  bios_version,
-			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-			  cpu_temp,
-			  left_fan, right_fan, left_speed, right_speed,
-			  ac_power, fn_key);
-}
-
-static int i8k_open_fs(struct inode *inode, struct file *file)
-{
-	return single_open(file, i8k_proc_show, NULL);
-}
-
-
-/*
- * Hwmon interface
- */
-
-static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
-					 struct device_attribute *devattr,
-					 char *buf)
-{
-	static const char * const labels[] = {
-		"CPU",
-		"GPU",
-		"SODIMM",
-		"Other",
-		"Ambient",
-		"Other",
-	};
-	int index = to_sensor_dev_attr(devattr)->index;
-	int type;
-
-	type = i8k_get_temp_type(index);
-	if (type < 0)
-		return type;
-	if (type >= ARRAY_SIZE(labels))
-		type = ARRAY_SIZE(labels) - 1;
-	return sprintf(buf, "%s\n", labels[type]);
-}
-
-static ssize_t i8k_hwmon_show_temp(struct device *dev,
-				   struct device_attribute *devattr,
-				   char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int temp;
-
-	temp = i8k_get_temp(index);
-	if (temp < 0)
-		return temp;
-	return sprintf(buf, "%d\n", temp * 1000);
-}
-
-static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
-					struct device_attribute *devattr,
-					char *buf)
-{
-	static const char * const labels[] = {
-		"Processor Fan",
-		"Motherboard Fan",
-		"Video Fan",
-		"Power Supply Fan",
-		"Chipset Fan",
-		"Other Fan",
-	};
-	int index = to_sensor_dev_attr(devattr)->index;
-	bool dock = false;
-	int type;
-
-	type = i8k_get_fan_type(index);
-	if (type < 0)
-		return type;
-
-	if (type & 0x10) {
-		dock = true;
-		type &= 0x0F;
-	}
-
-	if (type >= ARRAY_SIZE(labels))
-		type = (ARRAY_SIZE(labels) - 1);
-
-	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
-}
-
-static ssize_t i8k_hwmon_show_fan(struct device *dev,
-				  struct device_attribute *devattr,
-				  char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int fan_speed;
-
-	fan_speed = i8k_get_fan_speed(index);
-	if (fan_speed < 0)
-		return fan_speed;
-	return sprintf(buf, "%d\n", fan_speed);
-}
-
-static ssize_t i8k_hwmon_show_pwm(struct device *dev,
-				  struct device_attribute *devattr,
-				  char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int status;
-
-	status = i8k_get_fan_status(index);
-	if (status < 0)
-		return -EIO;
-	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
-}
-
-static ssize_t i8k_hwmon_set_pwm(struct device *dev,
-				 struct device_attribute *attr,
-				 const char *buf, size_t count)
-{
-	int index = to_sensor_dev_attr(attr)->index;
-	unsigned long val;
-	int err;
-
-	err = kstrtoul(buf, 10, &val);
-	if (err)
-		return err;
-	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
-
-	mutex_lock(&i8k_mutex);
-	err = i8k_set_fan(index, val);
-	mutex_unlock(&i8k_mutex);
-
-	return err < 0 ? -EIO : count;
-}
-
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
-static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  0);
-static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  2);
-static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
-static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  3);
-static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
-static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
-			  0);
-static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
-			  i8k_hwmon_set_pwm, 0);
-static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
-			  i8k_hwmon_set_pwm, 1);
-
-static struct attribute *i8k_attrs[] = {
-	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
-	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
-	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
-	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
-	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
-	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
-	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
-	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
-	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
-	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
-	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
-	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
-	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
-	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
-	NULL
-};
-
-static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
-			      int index)
-{
-	if (index >= 0 && index <= 1 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
-		return 0;
-	if (index >= 2 && index <= 3 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
-		return 0;
-	if (index >= 4 && index <= 5 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
-		return 0;
-	if (index >= 6 && index <= 7 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
-		return 0;
-	if (index >= 8 && index <= 10 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
-		return 0;
-	if (index >= 11 && index <= 13 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
-		return 0;
-
-	return attr->mode;
-}
-
-static const struct attribute_group i8k_group = {
-	.attrs = i8k_attrs,
-	.is_visible = i8k_is_visible,
-};
-__ATTRIBUTE_GROUPS(i8k);
-
-static int __init i8k_init_hwmon(void)
-{
-	int err;
-
-	i8k_hwmon_flags = 0;
-
-	/* CPU temperature attributes, if temperature type is OK */
-	err = i8k_get_temp_type(0);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
-	/* check for additional temperature sensors */
-	err = i8k_get_temp_type(1);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
-	err = i8k_get_temp_type(2);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
-	err = i8k_get_temp_type(3);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
-
-	/* First fan attributes, if fan type is OK */
-	err = i8k_get_fan_type(0);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
-
-	/* Second fan attributes, if fan type is OK */
-	err = i8k_get_fan_type(1);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
-
-	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
-							  i8k_groups);
-	if (IS_ERR(i8k_hwmon_dev)) {
-		err = PTR_ERR(i8k_hwmon_dev);
-		i8k_hwmon_dev = NULL;
-		pr_err("hwmon registration failed (%d)\n", err);
-		return err;
-	}
-	return 0;
-}
-
-struct i8k_config_data {
-	uint fan_mult;
-	uint fan_max;
-};
-
-enum i8k_configs {
-	DELL_LATITUDE_D520,
-	DELL_PRECISION_490,
-	DELL_STUDIO,
-	DELL_XPS,
-};
-
-static const struct i8k_config_data i8k_config_data[] = {
-	[DELL_LATITUDE_D520] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_TURBO,
-	},
-	[DELL_PRECISION_490] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_TURBO,
-	},
-	[DELL_STUDIO] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_HIGH,
-	},
-	[DELL_XPS] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_HIGH,
-	},
-};
-
-static struct dmi_system_id i8k_dmi_table[] __initdata = {
-	{
-		.ident = "Dell Inspiron",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
-		},
-	},
-	{
-		.ident = "Dell Latitude",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
-		},
-	},
-	{
-		.ident = "Dell Inspiron 2",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
-		},
-	},
-	{
-		.ident = "Dell Latitude D520",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
-	},
-	{
-		.ident = "Dell Latitude 2",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
-		},
-	},
-	{	/* UK Inspiron 6400  */
-		.ident = "Dell Inspiron 3",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
-		},
-	},
-	{
-		.ident = "Dell Inspiron 3",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
-		},
-	},
-	{
-		.ident = "Dell Precision 490",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME,
-				  "Precision WorkStation 490"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
-	},
-	{
-		.ident = "Dell Precision",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
-		},
-	},
-	{
-		.ident = "Dell Vostro",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
-		},
-	},
-	{
-		.ident = "Dell XPS421",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
-		},
-	},
-	{
-		.ident = "Dell Studio",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
-	},
-	{
-		.ident = "Dell XPS 13",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_XPS],
-	},
-	{
-		.ident = "Dell XPS M140",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_XPS],
-	},
-	{ }
-};
-
-MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
-
-/*
- * Probe for the presence of a supported laptop.
- */
-static int __init i8k_probe(void)
-{
-	const struct dmi_system_id *id;
-	int fan, ret;
-
-	/*
-	 * Get DMI information
-	 */
-	if (!dmi_check_system(i8k_dmi_table)) {
-		if (!ignore_dmi && !force)
-			return -ENODEV;
-
-		pr_info("not running on a supported Dell system.\n");
-		pr_info("vendor=%s, model=%s, version=%s\n",
-			i8k_get_dmi_data(DMI_SYS_VENDOR),
-			i8k_get_dmi_data(DMI_PRODUCT_NAME),
-			i8k_get_dmi_data(DMI_BIOS_VERSION));
-	}
-
-	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
-		sizeof(bios_version));
-
-	/*
-	 * Get SMM Dell signature
-	 */
-	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
-	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
-		pr_err("unable to get SMM Dell signature\n");
-		if (!force)
-			return -ENODEV;
-	}
-
-	/*
-	 * Set fan multiplier and maximal fan speed from dmi config
-	 * Values specified in module parameters override values from dmi
-	 */
-	id = dmi_first_match(i8k_dmi_table);
-	if (id && id->driver_data) {
-		const struct i8k_config_data *conf = id->driver_data;
-		if (!fan_mult && conf->fan_mult)
-			fan_mult = conf->fan_mult;
-		if (!fan_max && conf->fan_max)
-			fan_max = conf->fan_max;
-	}
-
-	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
-	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
-
-	if (!fan_mult) {
-		/*
-		 * Autodetect fan multiplier based on nominal rpm
-		 * If fan reports rpm value too high then set multiplier to 1
-		 */
-		for (fan = 0; fan < 2; ++fan) {
-			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
-			if (ret < 0)
-				continue;
-			if (ret > I8K_FAN_MAX_RPM)
-				i8k_fan_mult = 1;
-			break;
-		}
-	} else {
-		/* Fan multiplier was specified in module param or in dmi */
-		i8k_fan_mult = fan_mult;
-	}
-
-	return 0;
-}
-
-static int __init i8k_init(void)
-{
-	struct proc_dir_entry *proc_i8k;
-	int err;
-
-	/* Are we running on an supported laptop? */
-	if (i8k_probe())
-		return -ENODEV;
-
-	/* Register the proc entry */
-	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
-	if (!proc_i8k)
-		return -ENOENT;
-
-	err = i8k_init_hwmon();
-	if (err)
-		goto exit_remove_proc;
-
-	return 0;
-
- exit_remove_proc:
-	remove_proc_entry("i8k", NULL);
-	return err;
-}
-
-static void __exit i8k_exit(void)
-{
-	hwmon_device_unregister(i8k_hwmon_dev);
-	remove_proc_entry("i8k", NULL);
-}
-
-module_init(i8k_init);
-module_exit(i8k_exit);
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 6c94147..1c3e458 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -155,6 +155,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
+obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
new file mode 100644
index 0000000..2b04e4f
--- /dev/null
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -0,0 +1,1007 @@
+/*
+ * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
+ *
+ * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
+ *
+ * Hwmon integration:
+ * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
+ * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
+ * Copyright (C) 2014, 2015  Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/dmi.h>
+#include <linux/capability.h>
+#include <linux/mutex.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+
+#include <linux/i8k.h>
+
+#define I8K_SMM_FN_STATUS	0x0025
+#define I8K_SMM_POWER_STATUS	0x0069
+#define I8K_SMM_SET_FAN		0x01a3
+#define I8K_SMM_GET_FAN		0x00a3
+#define I8K_SMM_GET_SPEED	0x02a3
+#define I8K_SMM_GET_FAN_TYPE	0x03a3
+#define I8K_SMM_GET_NOM_SPEED	0x04a3
+#define I8K_SMM_GET_TEMP	0x10a3
+#define I8K_SMM_GET_TEMP_TYPE	0x11a3
+#define I8K_SMM_GET_DELL_SIG1	0xfea3
+#define I8K_SMM_GET_DELL_SIG2	0xffa3
+
+#define I8K_FAN_MULT		30
+#define I8K_FAN_MAX_RPM		30000
+#define I8K_MAX_TEMP		127
+
+#define I8K_FN_NONE		0x00
+#define I8K_FN_UP		0x01
+#define I8K_FN_DOWN		0x02
+#define I8K_FN_MUTE		0x04
+#define I8K_FN_MASK		0x07
+#define I8K_FN_SHIFT		8
+
+#define I8K_POWER_AC		0x05
+#define I8K_POWER_BATTERY	0x01
+
+static DEFINE_MUTEX(i8k_mutex);
+static char bios_version[4];
+static struct device *i8k_hwmon_dev;
+static u32 i8k_hwmon_flags;
+static uint i8k_fan_mult = I8K_FAN_MULT;
+static uint i8k_pwm_mult;
+static uint i8k_fan_max = I8K_FAN_HIGH;
+
+#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
+#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
+#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
+#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
+#define I8K_HWMON_HAVE_FAN1	(1 << 4)
+#define I8K_HWMON_HAVE_FAN2	(1 << 5)
+
+MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("i8k");
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force loading without checking for supported models");
+
+static bool ignore_dmi;
+module_param(ignore_dmi, bool, 0);
+MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
+
+static bool restricted;
+module_param(restricted, bool, 0);
+MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
+
+static bool power_status;
+module_param(power_status, bool, 0600);
+MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+
+static uint fan_mult;
+module_param(fan_mult, uint, 0);
+MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
+
+static uint fan_max;
+module_param(fan_max, uint, 0);
+MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
+
+static int i8k_open_fs(struct inode *inode, struct file *file);
+static long i8k_ioctl(struct file *, unsigned int, unsigned long);
+
+static const struct file_operations i8k_fops = {
+	.owner		= THIS_MODULE,
+	.open		= i8k_open_fs,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.unlocked_ioctl	= i8k_ioctl,
+};
+
+struct smm_regs {
+	unsigned int eax;
+	unsigned int ebx __packed;
+	unsigned int ecx __packed;
+	unsigned int edx __packed;
+	unsigned int esi __packed;
+	unsigned int edi __packed;
+};
+
+static inline const char *i8k_get_dmi_data(int field)
+{
+	const char *dmi_data = dmi_get_system_info(field);
+
+	return dmi_data && *dmi_data ? dmi_data : "?";
+}
+
+/*
+ * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
+ */
+static int i8k_smm(struct smm_regs *regs)
+{
+	int rc;
+	int eax = regs->eax;
+	cpumask_var_t old_mask;
+
+	/* SMM requires CPU 0 */
+	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
+		return -ENOMEM;
+	cpumask_copy(old_mask, &current->cpus_allowed);
+	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
+	if (rc)
+		goto out;
+	if (smp_processor_id() != 0) {
+		rc = -EBUSY;
+		goto out;
+	}
+
+#if defined(CONFIG_X86_64)
+	asm volatile("pushq %%rax\n\t"
+		"movl 0(%%rax),%%edx\n\t"
+		"pushq %%rdx\n\t"
+		"movl 4(%%rax),%%ebx\n\t"
+		"movl 8(%%rax),%%ecx\n\t"
+		"movl 12(%%rax),%%edx\n\t"
+		"movl 16(%%rax),%%esi\n\t"
+		"movl 20(%%rax),%%edi\n\t"
+		"popq %%rax\n\t"
+		"out %%al,$0xb2\n\t"
+		"out %%al,$0x84\n\t"
+		"xchgq %%rax,(%%rsp)\n\t"
+		"movl %%ebx,4(%%rax)\n\t"
+		"movl %%ecx,8(%%rax)\n\t"
+		"movl %%edx,12(%%rax)\n\t"
+		"movl %%esi,16(%%rax)\n\t"
+		"movl %%edi,20(%%rax)\n\t"
+		"popq %%rdx\n\t"
+		"movl %%edx,0(%%rax)\n\t"
+		"pushfq\n\t"
+		"popq %%rax\n\t"
+		"andl $1,%%eax\n"
+		: "=a"(rc)
+		:    "a"(regs)
+		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+#else
+	asm volatile("pushl %%eax\n\t"
+	    "movl 0(%%eax),%%edx\n\t"
+	    "push %%edx\n\t"
+	    "movl 4(%%eax),%%ebx\n\t"
+	    "movl 8(%%eax),%%ecx\n\t"
+	    "movl 12(%%eax),%%edx\n\t"
+	    "movl 16(%%eax),%%esi\n\t"
+	    "movl 20(%%eax),%%edi\n\t"
+	    "popl %%eax\n\t"
+	    "out %%al,$0xb2\n\t"
+	    "out %%al,$0x84\n\t"
+	    "xchgl %%eax,(%%esp)\n\t"
+	    "movl %%ebx,4(%%eax)\n\t"
+	    "movl %%ecx,8(%%eax)\n\t"
+	    "movl %%edx,12(%%eax)\n\t"
+	    "movl %%esi,16(%%eax)\n\t"
+	    "movl %%edi,20(%%eax)\n\t"
+	    "popl %%edx\n\t"
+	    "movl %%edx,0(%%eax)\n\t"
+	    "lahf\n\t"
+	    "shrl $8,%%eax\n\t"
+	    "andl $1,%%eax\n"
+	    : "=a"(rc)
+	    :    "a"(regs)
+	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+#endif
+	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+		rc = -EINVAL;
+
+out:
+	set_cpus_allowed_ptr(current, old_mask);
+	free_cpumask_var(old_mask);
+	return rc;
+}
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+	case I8K_FN_UP:
+		return I8K_VOL_UP;
+	case I8K_FN_DOWN:
+		return I8K_VOL_DOWN;
+	case I8K_FN_MUTE:
+		return I8K_VOL_MUTE;
+	default:
+		return 0;
+	}
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
+}
+
+/*
+ * Read the fan status.
+ */
+static int i8k_get_fan_status(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the fan speed in RPM.
+ */
+static int i8k_get_fan_speed(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+}
+
+/*
+ * Read the fan type.
+ */
+static int i8k_get_fan_type(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the fan nominal rpm for specific fan speed.
+ */
+static int i8k_get_fan_nominal_speed(int fan, int speed)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
+
+	regs.ebx = (fan & 0xff) | (speed << 8);
+	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+}
+
+/*
+ * Set the fan speed (off, low, high). Returns the new fan status.
+ */
+static int i8k_set_fan(int fan, int speed)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
+
+	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
+	regs.ebx = (fan & 0xff) | (speed << 8);
+
+	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
+}
+
+static int i8k_get_temp_type(int sensor)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
+
+	regs.ebx = sensor & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the cpu temperature.
+ */
+static int _i8k_get_temp(int sensor)
+{
+	struct smm_regs regs = {
+		.eax = I8K_SMM_GET_TEMP,
+		.ebx = sensor & 0xff,
+	};
+
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+static int i8k_get_temp(int sensor)
+{
+	int temp = _i8k_get_temp(sensor);
+
+	/*
+	 * Sometimes the temperature sensor returns 0x99, which is out of range.
+	 * In this case we retry (once) before returning an error.
+	 # 1003655137 00000058 00005a4b
+	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
+	 # 1003655139 00000054 00005c52
+	 */
+	if (temp == 0x99) {
+		msleep(100);
+		temp = _i8k_get_temp(sensor);
+	}
+	/*
+	 * Return -ENODATA for all invalid temperatures.
+	 *
+	 * Known instances are the 0x99 value as seen above as well as
+	 * 0xc1 (193), which may be returned when trying to read the GPU
+	 * temperature if the system supports a GPU and it is currently
+	 * turned off.
+	 */
+	if (temp > I8K_MAX_TEMP)
+		return -ENODATA;
+
+	return temp;
+}
+
+static int i8k_get_dell_signature(int req_fn)
+{
+	struct smm_regs regs = { .eax = req_fn, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
+}
+
+static int
+i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	int val = 0;
+	int speed;
+	unsigned char buff[16];
+	int __user *argp = (int __user *)arg;
+
+	if (!argp)
+		return -EINVAL;
+
+	switch (cmd) {
+	case I8K_BIOS_VERSION:
+		val = (bios_version[0] << 16) |
+				(bios_version[1] << 8) | bios_version[2];
+		break;
+
+	case I8K_MACHINE_ID:
+		memset(buff, 0, 16);
+		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+			sizeof(buff));
+		break;
+
+	case I8K_FN_STATUS:
+		val = i8k_get_fn_status();
+		break;
+
+	case I8K_POWER_STATUS:
+		val = i8k_get_power_status();
+		break;
+
+	case I8K_GET_TEMP:
+		val = i8k_get_temp(0);
+		break;
+
+	case I8K_GET_SPEED:
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_get_fan_speed(val);
+		break;
+
+	case I8K_GET_FAN:
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_get_fan_status(val);
+		break;
+
+	case I8K_SET_FAN:
+		if (restricted && !capable(CAP_SYS_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		if (copy_from_user(&speed, argp + 1, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_set_fan(val, speed);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (val < 0)
+		return val;
+
+	switch (cmd) {
+	case I8K_BIOS_VERSION:
+		if (copy_to_user(argp, &val, 4))
+			return -EFAULT;
+
+		break;
+	case I8K_MACHINE_ID:
+		if (copy_to_user(argp, buff, 16))
+			return -EFAULT;
+
+		break;
+	default:
+		if (copy_to_user(argp, &val, sizeof(int)))
+			return -EFAULT;
+
+		break;
+	}
+
+	return 0;
+}
+
+static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	long ret;
+
+	mutex_lock(&i8k_mutex);
+	ret = i8k_ioctl_unlocked(fp, cmd, arg);
+	mutex_unlock(&i8k_mutex);
+
+	return ret;
+}
+
+/*
+ * Print the information for /proc/i8k.
+ */
+static int i8k_proc_show(struct seq_file *seq, void *offset)
+{
+	int fn_key, cpu_temp, ac_power;
+	int left_fan, right_fan, left_speed, right_speed;
+
+	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
+	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
+	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
+	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
+	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
+	fn_key		= i8k_get_fn_status();			/*   750 µs */
+	if (power_status)
+		ac_power = i8k_get_power_status();		/* 14700 µs */
+	else
+		ac_power = -1;
+
+	/*
+	 * Info:
+	 *
+	 * 1)  Format version (this will change if format changes)
+	 * 2)  BIOS version
+	 * 3)  BIOS machine ID
+	 * 4)  Cpu temperature
+	 * 5)  Left fan status
+	 * 6)  Right fan status
+	 * 7)  Left fan speed
+	 * 8)  Right fan speed
+	 * 9)  AC power
+	 * 10) Fn Key status
+	 */
+	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
+			  I8K_PROC_FMT,
+			  bios_version,
+			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+			  cpu_temp,
+			  left_fan, right_fan, left_speed, right_speed,
+			  ac_power, fn_key);
+}
+
+static int i8k_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, i8k_proc_show, NULL);
+}
+
+
+/*
+ * Hwmon interface
+ */
+
+static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
+					 struct device_attribute *devattr,
+					 char *buf)
+{
+	static const char * const labels[] = {
+		"CPU",
+		"GPU",
+		"SODIMM",
+		"Other",
+		"Ambient",
+		"Other",
+	};
+	int index = to_sensor_dev_attr(devattr)->index;
+	int type;
+
+	type = i8k_get_temp_type(index);
+	if (type < 0)
+		return type;
+	if (type >= ARRAY_SIZE(labels))
+		type = ARRAY_SIZE(labels) - 1;
+	return sprintf(buf, "%s\n", labels[type]);
+}
+
+static ssize_t i8k_hwmon_show_temp(struct device *dev,
+				   struct device_attribute *devattr,
+				   char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int temp;
+
+	temp = i8k_get_temp(index);
+	if (temp < 0)
+		return temp;
+	return sprintf(buf, "%d\n", temp * 1000);
+}
+
+static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
+					struct device_attribute *devattr,
+					char *buf)
+{
+	static const char * const labels[] = {
+		"Processor Fan",
+		"Motherboard Fan",
+		"Video Fan",
+		"Power Supply Fan",
+		"Chipset Fan",
+		"Other Fan",
+	};
+	int index = to_sensor_dev_attr(devattr)->index;
+	bool dock = false;
+	int type;
+
+	type = i8k_get_fan_type(index);
+	if (type < 0)
+		return type;
+
+	if (type & 0x10) {
+		dock = true;
+		type &= 0x0F;
+	}
+
+	if (type >= ARRAY_SIZE(labels))
+		type = (ARRAY_SIZE(labels) - 1);
+
+	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
+}
+
+static ssize_t i8k_hwmon_show_fan(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int fan_speed;
+
+	fan_speed = i8k_get_fan_speed(index);
+	if (fan_speed < 0)
+		return fan_speed;
+	return sprintf(buf, "%d\n", fan_speed);
+}
+
+static ssize_t i8k_hwmon_show_pwm(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int status;
+
+	status = i8k_get_fan_status(index);
+	if (status < 0)
+		return -EIO;
+	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
+}
+
+static ssize_t i8k_hwmon_set_pwm(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(attr)->index;
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err)
+		return err;
+	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
+
+	mutex_lock(&i8k_mutex);
+	err = i8k_set_fan(index, val);
+	mutex_unlock(&i8k_mutex);
+
+	return err < 0 ? -EIO : count;
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  2);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  3);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
+			  0);
+static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
+			  i8k_hwmon_set_pwm, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
+			  i8k_hwmon_set_pwm, 1);
+
+static struct attribute *i8k_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
+	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
+	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
+	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
+	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
+	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
+	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
+	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
+	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
+	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
+	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
+	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
+	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
+	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
+	NULL
+};
+
+static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
+			      int index)
+{
+	if (index >= 0 && index <= 1 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
+		return 0;
+	if (index >= 2 && index <= 3 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
+		return 0;
+	if (index >= 4 && index <= 5 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
+		return 0;
+	if (index >= 6 && index <= 7 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
+		return 0;
+	if (index >= 8 && index <= 10 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
+		return 0;
+	if (index >= 11 && index <= 13 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
+		return 0;
+
+	return attr->mode;
+}
+
+static const struct attribute_group i8k_group = {
+	.attrs = i8k_attrs,
+	.is_visible = i8k_is_visible,
+};
+__ATTRIBUTE_GROUPS(i8k);
+
+static int __init i8k_init_hwmon(void)
+{
+	int err;
+
+	i8k_hwmon_flags = 0;
+
+	/* CPU temperature attributes, if temperature type is OK */
+	err = i8k_get_temp_type(0);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
+	/* check for additional temperature sensors */
+	err = i8k_get_temp_type(1);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
+	err = i8k_get_temp_type(2);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
+	err = i8k_get_temp_type(3);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
+
+	/* First fan attributes, if fan type is OK */
+	err = i8k_get_fan_type(0);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
+
+	/* Second fan attributes, if fan type is OK */
+	err = i8k_get_fan_type(1);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
+
+	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
+							  i8k_groups);
+	if (IS_ERR(i8k_hwmon_dev)) {
+		err = PTR_ERR(i8k_hwmon_dev);
+		i8k_hwmon_dev = NULL;
+		pr_err("hwmon registration failed (%d)\n", err);
+		return err;
+	}
+	return 0;
+}
+
+struct i8k_config_data {
+	uint fan_mult;
+	uint fan_max;
+};
+
+enum i8k_configs {
+	DELL_LATITUDE_D520,
+	DELL_PRECISION_490,
+	DELL_STUDIO,
+	DELL_XPS,
+};
+
+static const struct i8k_config_data i8k_config_data[] = {
+	[DELL_LATITUDE_D520] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_TURBO,
+	},
+	[DELL_PRECISION_490] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_TURBO,
+	},
+	[DELL_STUDIO] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_HIGH,
+	},
+	[DELL_XPS] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_HIGH,
+	},
+};
+
+static struct dmi_system_id i8k_dmi_table[] __initdata = {
+	{
+		.ident = "Dell Inspiron",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+		},
+	},
+	{
+		.ident = "Dell Latitude",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
+		},
+	},
+	{
+		.ident = "Dell Inspiron 2",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+		},
+	},
+	{
+		.ident = "Dell Latitude D520",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
+	},
+	{
+		.ident = "Dell Latitude 2",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
+		},
+	},
+	{	/* UK Inspiron 6400  */
+		.ident = "Dell Inspiron 3",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
+		},
+	},
+	{
+		.ident = "Dell Inspiron 3",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
+		},
+	},
+	{
+		.ident = "Dell Precision 490",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME,
+				  "Precision WorkStation 490"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
+	},
+	{
+		.ident = "Dell Precision",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
+		},
+	},
+	{
+		.ident = "Dell Vostro",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
+		},
+	},
+	{
+		.ident = "Dell XPS421",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
+		},
+	},
+	{
+		.ident = "Dell Studio",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
+	},
+	{
+		.ident = "Dell XPS 13",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_XPS],
+	},
+	{
+		.ident = "Dell XPS M140",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_XPS],
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static int __init i8k_probe(void)
+{
+	const struct dmi_system_id *id;
+	int fan, ret;
+
+	/*
+	 * Get DMI information
+	 */
+	if (!dmi_check_system(i8k_dmi_table)) {
+		if (!ignore_dmi && !force)
+			return -ENODEV;
+
+		pr_info("not running on a supported Dell system.\n");
+		pr_info("vendor=%s, model=%s, version=%s\n",
+			i8k_get_dmi_data(DMI_SYS_VENDOR),
+			i8k_get_dmi_data(DMI_PRODUCT_NAME),
+			i8k_get_dmi_data(DMI_BIOS_VERSION));
+	}
+
+	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
+		sizeof(bios_version));
+
+	/*
+	 * Get SMM Dell signature
+	 */
+	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
+	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
+		pr_err("unable to get SMM Dell signature\n");
+		if (!force)
+			return -ENODEV;
+	}
+
+	/*
+	 * Set fan multiplier and maximal fan speed from dmi config
+	 * Values specified in module parameters override values from dmi
+	 */
+	id = dmi_first_match(i8k_dmi_table);
+	if (id && id->driver_data) {
+		const struct i8k_config_data *conf = id->driver_data;
+		if (!fan_mult && conf->fan_mult)
+			fan_mult = conf->fan_mult;
+		if (!fan_max && conf->fan_max)
+			fan_max = conf->fan_max;
+	}
+
+	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
+	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
+
+	if (!fan_mult) {
+		/*
+		 * Autodetect fan multiplier based on nominal rpm
+		 * If fan reports rpm value too high then set multiplier to 1
+		 */
+		for (fan = 0; fan < 2; ++fan) {
+			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
+			if (ret < 0)
+				continue;
+			if (ret > I8K_FAN_MAX_RPM)
+				i8k_fan_mult = 1;
+			break;
+		}
+	} else {
+		/* Fan multiplier was specified in module param or in dmi */
+		i8k_fan_mult = fan_mult;
+	}
+
+	return 0;
+}
+
+static int __init i8k_init(void)
+{
+	struct proc_dir_entry *proc_i8k;
+	int err;
+
+	/* Are we running on an supported laptop? */
+	if (i8k_probe())
+		return -ENODEV;
+
+	/* Register the proc entry */
+	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
+	if (!proc_i8k)
+		return -ENOENT;
+
+	err = i8k_init_hwmon();
+	if (err)
+		goto exit_remove_proc;
+
+	return 0;
+
+ exit_remove_proc:
+	remove_proc_entry("i8k", NULL);
+	return err;
+}
+
+static void __exit i8k_exit(void)
+{
+	hwmon_device_unregister(i8k_hwmon_dev);
+	remove_proc_entry("i8k", NULL);
+}
+
+module_init(i8k_init);
+module_exit(i8k_exit);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 10:24 [PATCH 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
  2015-03-28 10:24 ` [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
@ 2015-03-28 10:24 ` Pali Rohár
  2015-03-28 11:04   ` Paul Bolle
  2015-03-28 14:23   ` Guenter Roeck
  2015-03-29 12:57 ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
  2 siblings, 2 replies; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 10:24 UTC (permalink / raw)
  To: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors, Pali Rohár

This patch splits CONFIG_I8K compile option to SENSORS_DELL_SMM and CONFIG_I8K.
Option SENSORS_DELL_SMM is now used to enable compilation of dell-smm-hwmon
driver and old CONFIG_I8K option to enable /proc/i8k interface in driver.

So this change allows to compile dell-smm-hwmon driver without legacy /proc/i8k
interface which is needed only for old Dell Inspirion models or for userspace
i8kutils package.

For backward compatibility when CONFIG_I8K is enabled then also SENSORS_DELL_SMM
is enabled and so driver dell-smm-hwmon (with /proc/i8k) is compiled.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
---
 arch/x86/Kconfig               |   25 +++++++++----------------
 drivers/hwmon/Kconfig          |   11 +++++++++++
 drivers/hwmon/Makefile         |    2 +-
 drivers/hwmon/dell-smm-hwmon.c |   20 ++++++++++++++++----
 4 files changed, 37 insertions(+), 21 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index b7d31ca..8d0266b 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1063,24 +1063,17 @@ config TOSHIBA
 	  Say N otherwise.
 
 config I8K
-	tristate "Dell laptop support"
-	select HWMON
+	bool "Dell i8k legacy laptop support"
+	select SENSORS_DELL_SMM
 	---help---
-	  This adds a driver to safely access the System Management Mode
-	  of the CPU on the Dell Inspiron 8000. The System Management Mode
-	  is used to read cpu temperature and cooling fan status and to
-	  control the fans on the I8K portables.
+	  This options enables legacy /proc/i8k userspace interface in
+	  dell-smm-hwmon driver. Character file /proc/i8k reports power and
+	  hotkey status on old Dell laptops (like Dell Inspiron 8000) via
+	  System Management Mode provided by Dell BIOS. This /proc/i8k interface
+	  is also used by userspace package i8kutils to control laptop fans.
 
-	  This driver has been tested only on the Inspiron 8000 but it may
-	  also work with other Dell laptops. You can force loading on other
-	  models by passing the parameter `force=1' to the module. Use at
-	  your own risk.
-
-	  For information on utilities to make use of this driver see the
-	  I8K Linux utilities web site at:
-	  <http://people.debian.org/~dz/i8k/>
-
-	  Say Y if you intend to run this kernel on a Dell Inspiron 8000.
+	  Say Y if you intend to run this kernel on old Dell laptops or want to
+	  use userspace package i8kutils.
 	  Say N otherwise.
 
 config X86_REBOOTFIXUPS
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 110fade..86eeb7d 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
 	  This driver provides support for the Ultra45 workstation environmental
 	  sensors.
 
+config SENSORS_DELL_SMM
+	tristate "Dell laptop SMM BIOS hwmon driver"
+	depends ON X86
+	---help---
+	  This hwmon driver adds support for reporting temperature of different
+	  sensors and controls the fans on Dell laptops via System Management
+	  Mode provided by Dell BIOS.
+
+	  When option I8K is also enabled this driver provides legacy /proc/i8k
+	  userspace interface for i8kutils package.
+
 if ACPI
 
 comment "ACPI drivers"
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 1c3e458..9eec614 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -155,7 +155,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
-obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
+obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 2b04e4f..e9661dc 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -81,7 +81,7 @@ static uint i8k_fan_max = I8K_FAN_HIGH;
 
 MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
 MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
-MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("i8k");
 
@@ -93,6 +93,7 @@ static bool ignore_dmi;
 module_param(ignore_dmi, bool, 0);
 MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
 
+#ifdef CONFIG_I8K
 static bool restricted;
 module_param(restricted, bool, 0);
 MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
@@ -100,6 +101,7 @@ MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
 static bool power_status;
 module_param(power_status, bool, 0600);
 MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+#endif
 
 static uint fan_mult;
 module_param(fan_mult, uint, 0);
@@ -109,6 +111,7 @@ static uint fan_max;
 module_param(fan_max, uint, 0);
 MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
 
+#ifdef CONFIG_I8K
 static int i8k_open_fs(struct inode *inode, struct file *file);
 static long i8k_ioctl(struct file *, unsigned int, unsigned long);
 
@@ -120,6 +123,7 @@ static const struct file_operations i8k_fops = {
 	.release	= single_release,
 	.unlocked_ioctl	= i8k_ioctl,
 };
+#endif
 
 struct smm_regs {
 	unsigned int eax;
@@ -220,6 +224,7 @@ out:
 	return rc;
 }
 
+#ifdef CONFIG_I8K
 /*
  * Read the Fn key status.
  */
@@ -258,6 +263,7 @@ static int i8k_get_power_status(void)
 
 	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
 }
+#endif
 
 /*
  * Read the fan status.
@@ -378,6 +384,7 @@ static int i8k_get_dell_signature(int req_fn)
 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
 }
 
+#ifdef CONFIG_I8K
 static int
 i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
 {
@@ -525,6 +532,7 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
 {
 	return single_open(file, i8k_proc_show, NULL);
 }
+#endif
 
 
 /*
@@ -974,17 +982,17 @@ static int __init i8k_probe(void)
 
 static int __init i8k_init(void)
 {
-	struct proc_dir_entry *proc_i8k;
 	int err;
 
 	/* Are we running on an supported laptop? */
 	if (i8k_probe())
 		return -ENODEV;
 
+#ifdef CONFIG_I8K
 	/* Register the proc entry */
-	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
-	if (!proc_i8k)
+	if (!proc_create("i8k", 0, NULL, &i8k_fops))
 		return -ENOENT;
+#endif
 
 	err = i8k_init_hwmon();
 	if (err)
@@ -993,14 +1001,18 @@ static int __init i8k_init(void)
 	return 0;
 
  exit_remove_proc:
+#ifdef CONFIG_I8K
 	remove_proc_entry("i8k", NULL);
+#endif
 	return err;
 }
 
 static void __exit i8k_exit(void)
 {
 	hwmon_device_unregister(i8k_hwmon_dev);
+#ifdef CONFIG_I8K
 	remove_proc_entry("i8k", NULL);
+#endif
 }
 
 module_init(i8k_init);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 10:24 ` [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
@ 2015-03-28 11:04   ` Paul Bolle
  2015-03-28 12:54     ` Steven Honeyman
  2015-03-28 21:55     ` Pali Rohár
  2015-03-28 14:23   ` Guenter Roeck
  1 sibling, 2 replies; 40+ messages in thread
From: Paul Bolle @ 2015-03-28 11:04 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

On Sat, 2015-03-28 at 11:24 +0100, Pali Rohár wrote:
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
>  	  This driver provides support for the Ultra45 workstation environmental
>  	  sensors.
>  
> +config SENSORS_DELL_SMM
> +	tristate "Dell laptop SMM BIOS hwmon driver"
> +	depends ON X86

How did this past your testing?

> +	---help---
> +	  This hwmon driver adds support for reporting temperature of different
> +	  sensors and controls the fans on Dell laptops via System Management
> +	  Mode provided by Dell BIOS.
> +
> +	  When option I8K is also enabled this driver provides legacy /proc/i8k
> +	  userspace interface for i8kutils package.
> + 


Paul Bolle


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 11:04   ` Paul Bolle
@ 2015-03-28 12:54     ` Steven Honeyman
  2015-03-28 14:13       ` Guenter Roeck
  2015-03-28 22:00       ` Pali Rohár
  2015-03-28 21:55     ` Pali Rohár
  1 sibling, 2 replies; 40+ messages in thread
From: Steven Honeyman @ 2015-03-28 12:54 UTC (permalink / raw)
  To: Paul Bolle
  Cc: Pali Rohár, Guenter Roeck, Arnd Bergmann,
	Greg Kroah-Hartman, Jean Delvare, Valdis Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

On 28 March 2015 at 11:04, Paul Bolle <pebolle@tiscali.nl> wrote:
> On Sat, 2015-03-28 at 11:24 +0100, Pali Rohár wrote:
>> --- a/drivers/hwmon/Kconfig
>> +++ b/drivers/hwmon/Kconfig
>> @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
>>         This driver provides support for the Ultra45 workstation environmental
>>         sensors.
>>
>> +config SENSORS_DELL_SMM
>> +     tristate "Dell laptop SMM BIOS hwmon driver"
>> +     depends ON X86
>
> How did this past your testing?
>
>> +     ---help---
>> +       This hwmon driver adds support for reporting temperature of different
>> +       sensors and controls the fans on Dell laptops via System Management
>> +       Mode provided by Dell BIOS.
>> +
>> +       When option I8K is also enabled this driver provides legacy /proc/i8k
>> +       userspace interface for i8kutils package.
>> +

It's working OK for me (after fixing the mistake pointed out above).

[Latitude e6540]
-----
coretemp-isa-0000
Adapter: ISA adapter
Physical id 0:  +53.0°C  (high = +84.0°C, crit = +100.0°C)
Core 0:         +51.0°C  (high = +84.0°C, crit = +100.0°C)
Core 1:         +53.0°C  (high = +84.0°C, crit = +100.0°C)

i8k-virtual-0
Adapter: Virtual device
Processor Fan: 3171 RPM
CPU:            +52.0°C
Ambient:        +46.0°C
SODIMM:         +40.0°C
-----

Ambient and SODIMM look swapped - but it's probably just another Dell
error. I really need to look at getting coreboot on this thing.

On 28 March 2015 at 10:24, Pali Rohár <pali.rohar@gmail.com> wrote:
...
>  config I8K
> -       tristate "Dell laptop support"
> -       select HWMON
> +       bool "Dell i8k legacy laptop support"
> +       select SENSORS_DELL_SMM
...
> +config SENSORS_DELL_SMM
> +       tristate "Dell laptop SMM BIOS hwmon driver"

The only change I'd suggest is not to change the tristate to bool for
I8K. Forcing that to bool means that SENSORS_DELL_SMM can't be
compiled as a module if the user wishes to keep i8k hanging around
"just in case". It'll probably annoy distro kernel packagers too.


Thanks,
Steven

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 12:54     ` Steven Honeyman
@ 2015-03-28 14:13       ` Guenter Roeck
  2015-03-28 22:00       ` Pali Rohár
  1 sibling, 0 replies; 40+ messages in thread
From: Guenter Roeck @ 2015-03-28 14:13 UTC (permalink / raw)
  To: Steven Honeyman, Paul Bolle
  Cc: Pali Rohár, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare,
	Valdis Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On 03/28/2015 05:54 AM, Steven Honeyman wrote:
> On 28 March 2015 at 11:04, Paul Bolle <pebolle@tiscali.nl> wrote:
>> On Sat, 2015-03-28 at 11:24 +0100, Pali Rohár wrote:
>>> --- a/drivers/hwmon/Kconfig
>>> +++ b/drivers/hwmon/Kconfig
>>> @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
>>>          This driver provides support for the Ultra45 workstation environmental
>>>          sensors.
>>>
>>> +config SENSORS_DELL_SMM
>>> +     tristate "Dell laptop SMM BIOS hwmon driver"
>>> +     depends ON X86
>>
>> How did this past your testing?
>>
>>> +     ---help---
>>> +       This hwmon driver adds support for reporting temperature of different
>>> +       sensors and controls the fans on Dell laptops via System Management
>>> +       Mode provided by Dell BIOS.
>>> +
>>> +       When option I8K is also enabled this driver provides legacy /proc/i8k
>>> +       userspace interface for i8kutils package.
>>> +
>
> It's working OK for me (after fixing the mistake pointed out above).
>
> [Latitude e6540]
> -----
> coretemp-isa-0000
> Adapter: ISA adapter
> Physical id 0:  +53.0°C  (high = +84.0°C, crit = +100.0°C)
> Core 0:         +51.0°C  (high = +84.0°C, crit = +100.0°C)
> Core 1:         +53.0°C  (high = +84.0°C, crit = +100.0°C)
>
> i8k-virtual-0
> Adapter: Virtual device
> Processor Fan: 3171 RPM
> CPU:            +52.0°C
> Ambient:        +46.0°C
> SODIMM:         +40.0°C
> -----
>
> Ambient and SODIMM look swapped - but it's probably just another Dell
> error. I really need to look at getting coreboot on this thing.
>
> On 28 March 2015 at 10:24, Pali Rohár <pali.rohar@gmail.com> wrote:
> ...
>>   config I8K
>> -       tristate "Dell laptop support"
>> -       select HWMON
>> +       bool "Dell i8k legacy laptop support"
>> +       select SENSORS_DELL_SMM
> ...
>> +config SENSORS_DELL_SMM
>> +       tristate "Dell laptop SMM BIOS hwmon driver"
>
> The only change I'd suggest is not to change the tristate to bool for
> I8K. Forcing that to bool means that SENSORS_DELL_SMM can't be
> compiled as a module if the user wishes to keep i8k hanging around
> "just in case". It'll probably annoy distro kernel packagers too.
>

Agreed. It also still needs to select HWMON (SENSORS_DELL_SMM is
only visible if HWMON is selected).

Guenter


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 10:24 ` [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
  2015-03-28 11:04   ` Paul Bolle
@ 2015-03-28 14:23   ` Guenter Roeck
  2015-03-28 22:04     ` Pali Rohár
  1 sibling, 1 reply; 40+ messages in thread
From: Guenter Roeck @ 2015-03-28 14:23 UTC (permalink / raw)
  To: Pali Rohár, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

On 03/28/2015 03:24 AM, Pali Rohár wrote:
> This patch splits CONFIG_I8K compile option to SENSORS_DELL_SMM and CONFIG_I8K.
> Option SENSORS_DELL_SMM is now used to enable compilation of dell-smm-hwmon
> driver and old CONFIG_I8K option to enable /proc/i8k interface in driver.
>
> So this change allows to compile dell-smm-hwmon driver without legacy /proc/i8k
> interface which is needed only for old Dell Inspirion models or for userspace
> i8kutils package.
>
> For backward compatibility when CONFIG_I8K is enabled then also SENSORS_DELL_SMM
> is enabled and so driver dell-smm-hwmon (with /proc/i8k) is compiled.
>
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
> ---
>   arch/x86/Kconfig               |   25 +++++++++----------------
>   drivers/hwmon/Kconfig          |   11 +++++++++++
>   drivers/hwmon/Makefile         |    2 +-
>   drivers/hwmon/dell-smm-hwmon.c |   20 ++++++++++++++++----
>   4 files changed, 37 insertions(+), 21 deletions(-)
>
> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
> index b7d31ca..8d0266b 100644
> --- a/arch/x86/Kconfig
> +++ b/arch/x86/Kconfig
> @@ -1063,24 +1063,17 @@ config TOSHIBA
>   	  Say N otherwise.
>
>   config I8K
> -	tristate "Dell laptop support"
> -	select HWMON
> +	bool "Dell i8k legacy laptop support"

tristate and still "select HWMON".

> +	select SENSORS_DELL_SMM
>   	---help---
> -	  This adds a driver to safely access the System Management Mode
> -	  of the CPU on the Dell Inspiron 8000. The System Management Mode
> -	  is used to read cpu temperature and cooling fan status and to
> -	  control the fans on the I8K portables.
> +	  This options enables legacy /proc/i8k userspace interface in
> +	  dell-smm-hwmon driver. Character file /proc/i8k reports power and
> +	  hotkey status on old Dell laptops (like Dell Inspiron 8000) via
> +	  System Management Mode provided by Dell BIOS. This /proc/i8k interface
> +	  is also used by userspace package i8kutils to control laptop fans.
>
> -	  This driver has been tested only on the Inspiron 8000 but it may
> -	  also work with other Dell laptops. You can force loading on other
> -	  models by passing the parameter `force=1' to the module. Use at
> -	  your own risk.
> -
> -	  For information on utilities to make use of this driver see the
> -	  I8K Linux utilities web site at:
> -	  <http://people.debian.org/~dz/i8k/>
> -
> -	  Say Y if you intend to run this kernel on a Dell Inspiron 8000.
> +	  Say Y if you intend to run this kernel on old Dell laptops or want to
> +	  use userspace package i8kutils.
>   	  Say N otherwise.
>
>   config X86_REBOOTFIXUPS
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 110fade..86eeb7d 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
>   	  This driver provides support for the Ultra45 workstation environmental
>   	  sensors.
>
> +config SENSORS_DELL_SMM
> +	tristate "Dell laptop SMM BIOS hwmon driver"
> +	depends ON X86

Wondering as well .. .did you test this ?

> +	---help---
> +	  This hwmon driver adds support for reporting temperature of different
> +	  sensors and controls the fans on Dell laptops via System Management
> +	  Mode provided by Dell BIOS.
> +
> +	  When option I8K is also enabled this driver provides legacy /proc/i8k
> +	  userspace interface for i8kutils package.
> +
Please add this in alphabetic order.

>   if ACPI
>
>   comment "ACPI drivers"
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 1c3e458..9eec614 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -155,7 +155,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
>   obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
>   obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
>   obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
> -obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
> +obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o

Same here.

>
>   obj-$(CONFIG_PMBUS)		+= pmbus/
>
> diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
> index 2b04e4f..e9661dc 100644
> --- a/drivers/hwmon/dell-smm-hwmon.c
> +++ b/drivers/hwmon/dell-smm-hwmon.c
> @@ -81,7 +81,7 @@ static uint i8k_fan_max = I8K_FAN_HIGH;
>
>   MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
>   MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
> -MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
> +MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
>   MODULE_LICENSE("GPL");
>   MODULE_ALIAS("i8k");
>
> @@ -93,6 +93,7 @@ static bool ignore_dmi;
>   module_param(ignore_dmi, bool, 0);
>   MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
>
> +#ifdef CONFIG_I8K
>   static bool restricted;
>   module_param(restricted, bool, 0);
>   MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
> @@ -100,6 +101,7 @@ MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
>   static bool power_status;
>   module_param(power_status, bool, 0600);
>   MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
> +#endif
>
>   static uint fan_mult;
>   module_param(fan_mult, uint, 0);
> @@ -109,6 +111,7 @@ static uint fan_max;
>   module_param(fan_max, uint, 0);
>   MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
>
> +#ifdef CONFIG_I8K
>   static int i8k_open_fs(struct inode *inode, struct file *file);
>   static long i8k_ioctl(struct file *, unsigned int, unsigned long);
>
> @@ -120,6 +123,7 @@ static const struct file_operations i8k_fops = {
>   	.release	= single_release,
>   	.unlocked_ioctl	= i8k_ioctl,
>   };
> +#endif
>
>   struct smm_regs {
>   	unsigned int eax;
> @@ -220,6 +224,7 @@ out:
>   	return rc;
>   }
>
> +#ifdef CONFIG_I8K
>   /*
>    * Read the Fn key status.
>    */
> @@ -258,6 +263,7 @@ static int i8k_get_power_status(void)
>
>   	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
>   }
> +#endif
>
>   /*
>    * Read the fan status.
> @@ -378,6 +384,7 @@ static int i8k_get_dell_signature(int req_fn)
>   	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
>   }
>
> +#ifdef CONFIG_I8K
>   static int
>   i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
>   {
> @@ -525,6 +532,7 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
>   {
>   	return single_open(file, i8k_proc_show, NULL);
>   }
> +#endif
>
>
>   /*
> @@ -974,17 +982,17 @@ static int __init i8k_probe(void)
>
>   static int __init i8k_init(void)
>   {
> -	struct proc_dir_entry *proc_i8k;
>   	int err;
>
>   	/* Are we running on an supported laptop? */
>   	if (i8k_probe())
>   		return -ENODEV;
>
> +#ifdef CONFIG_I8K
>   	/* Register the proc entry */
> -	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
> -	if (!proc_i8k)
> +	if (!proc_create("i8k", 0, NULL, &i8k_fops))
>   		return -ENOENT;

I would prefer not to fail here but report a warning.
This is no longer a fatal condition.

> +#endif
>
>   	err = i8k_init_hwmon();
>   	if (err)
> @@ -993,14 +1001,18 @@ static int __init i8k_init(void)
>   	return 0;
>
>    exit_remove_proc:
> +#ifdef CONFIG_I8K
>   	remove_proc_entry("i8k", NULL);
> +#endif
>   	return err;
>   }
>
>   static void __exit i8k_exit(void)
>   {
>   	hwmon_device_unregister(i8k_hwmon_dev);
> +#ifdef CONFIG_I8K
>   	remove_proc_entry("i8k", NULL);
> +#endif
>   }
>
>   module_init(i8k_init);
>

Can you move all the conditional functions and global variables together
under a single #ifdef ? That should include functions to create the proc
entries, and shim functions for the same if I8K is not configured.

Also, the #ifdef would not cover the case where I8K is configured as module
(there is no reason to force it to bool). You should use "#if IS_ENABLED(CONFIG_I8K)"
instead.

Thanks,
Guenter


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  2015-03-28 10:24 ` [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
@ 2015-03-28 14:31   ` Guenter Roeck
  0 siblings, 0 replies; 40+ messages in thread
From: Guenter Roeck @ 2015-03-28 14:31 UTC (permalink / raw)
  To: Pali Rohár, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

On 03/28/2015 03:24 AM, Pali Rohár wrote:
> This commit moves i8k driver to hwmon tree under name dell-smm-hwmon which is
> better name then abbreviation i8k. For backward compatibility is added macro
> MODULE_ALIAS("i8k") so modprobe will load driver also old name i8k. CONFIG_I8K
> compile option was not changed.
>
> This commit also adds me as maintainer of this new dell-smm-hwmon driver.
>
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>

Looks ok to me, so I'll be happy to take it for 4.1 if Greg agrees.

I thought about moving the I8K configuration option as well. Not really sure
what to do there. Greg, any opinion ?
It would have to reside outside the HWMON block of drivers/hwmon/Kconfig if
we move it.

We also can drop me as explicit maintainer - I am on the hook from the hwmon side
anyway, and we don't need to step on each other's foot for this driver.

Thanks,
Guenter

> ---
>   MAINTAINERS                    |    3 +-
>   drivers/char/Makefile          |    1 -
>   drivers/char/i8k.c             | 1005 ---------------------------------------
>   drivers/hwmon/Makefile         |    1 +
>   drivers/hwmon/dell-smm-hwmon.c | 1007 ++++++++++++++++++++++++++++++++++++++++
>   5 files changed, 1010 insertions(+), 1007 deletions(-)
>   delete mode 100644 drivers/char/i8k.c
>   create mode 100644 drivers/hwmon/dell-smm-hwmon.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 88c09ca..bf5bb05 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -3064,8 +3064,9 @@ F:	drivers/platform/x86/dell-laptop.c
>
>   DELL LAPTOP SMM DRIVER
>   M:	Guenter Roeck <linux@roeck-us.net>
> +M:	Pali Rohár <pali.rohar@gmail.com>
>   S:	Maintained
> -F:	drivers/char/i8k.c
> +F:	drivers/hwmon/dell-smm-hwmon.c
>   F:	include/uapi/linux/i8k.h
>
>   DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas)
> diff --git a/drivers/char/Makefile b/drivers/char/Makefile
> index d06cde26..1d9cf00 100644
> --- a/drivers/char/Makefile
> +++ b/drivers/char/Makefile
> @@ -36,7 +36,6 @@ else
>     obj-$(CONFIG_NVRAM)	+= nvram.o
>   endif
>   obj-$(CONFIG_TOSHIBA)		+= toshiba.o
> -obj-$(CONFIG_I8K)		+= i8k.o
>   obj-$(CONFIG_DS1620)		+= ds1620.o
>   obj-$(CONFIG_HW_RANDOM)		+= hw_random/
>   obj-$(CONFIG_PPDEV)		+= ppdev.o
> diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c
> deleted file mode 100644
> index 24cc4ed..0000000
> --- a/drivers/char/i8k.c
> +++ /dev/null
> @@ -1,1005 +0,0 @@
> -/*
> - * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
> - *
> - * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
> - *
> - * Hwmon integration:
> - * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
> - * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
> - * Copyright (C) 2014  Pali Rohár <pali.rohar@gmail.com>
> - *
> - * This program is free software; you can redistribute it and/or modify it
> - * under the terms of the GNU General Public License as published by the
> - * Free Software Foundation; either version 2, or (at your option) any
> - * later version.
> - *
> - * This program is distributed in the hope that it will be useful, but
> - * WITHOUT ANY WARRANTY; without even the implied warranty of
> - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> - * General Public License for more details.
> - */
> -
> -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> -
> -#include <linux/delay.h>
> -#include <linux/module.h>
> -#include <linux/types.h>
> -#include <linux/init.h>
> -#include <linux/proc_fs.h>
> -#include <linux/seq_file.h>
> -#include <linux/dmi.h>
> -#include <linux/capability.h>
> -#include <linux/mutex.h>
> -#include <linux/hwmon.h>
> -#include <linux/hwmon-sysfs.h>
> -#include <linux/uaccess.h>
> -#include <linux/io.h>
> -#include <linux/sched.h>
> -
> -#include <linux/i8k.h>
> -
> -#define I8K_SMM_FN_STATUS	0x0025
> -#define I8K_SMM_POWER_STATUS	0x0069
> -#define I8K_SMM_SET_FAN		0x01a3
> -#define I8K_SMM_GET_FAN		0x00a3
> -#define I8K_SMM_GET_SPEED	0x02a3
> -#define I8K_SMM_GET_FAN_TYPE	0x03a3
> -#define I8K_SMM_GET_NOM_SPEED	0x04a3
> -#define I8K_SMM_GET_TEMP	0x10a3
> -#define I8K_SMM_GET_TEMP_TYPE	0x11a3
> -#define I8K_SMM_GET_DELL_SIG1	0xfea3
> -#define I8K_SMM_GET_DELL_SIG2	0xffa3
> -
> -#define I8K_FAN_MULT		30
> -#define I8K_FAN_MAX_RPM		30000
> -#define I8K_MAX_TEMP		127
> -
> -#define I8K_FN_NONE		0x00
> -#define I8K_FN_UP		0x01
> -#define I8K_FN_DOWN		0x02
> -#define I8K_FN_MUTE		0x04
> -#define I8K_FN_MASK		0x07
> -#define I8K_FN_SHIFT		8
> -
> -#define I8K_POWER_AC		0x05
> -#define I8K_POWER_BATTERY	0x01
> -
> -static DEFINE_MUTEX(i8k_mutex);
> -static char bios_version[4];
> -static struct device *i8k_hwmon_dev;
> -static u32 i8k_hwmon_flags;
> -static uint i8k_fan_mult = I8K_FAN_MULT;
> -static uint i8k_pwm_mult;
> -static uint i8k_fan_max = I8K_FAN_HIGH;
> -
> -#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
> -#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
> -#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
> -#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
> -#define I8K_HWMON_HAVE_FAN1	(1 << 4)
> -#define I8K_HWMON_HAVE_FAN2	(1 << 5)
> -
> -MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
> -MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
> -MODULE_LICENSE("GPL");
> -
> -static bool force;
> -module_param(force, bool, 0);
> -MODULE_PARM_DESC(force, "Force loading without checking for supported models");
> -
> -static bool ignore_dmi;
> -module_param(ignore_dmi, bool, 0);
> -MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
> -
> -static bool restricted;
> -module_param(restricted, bool, 0);
> -MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
> -
> -static bool power_status;
> -module_param(power_status, bool, 0600);
> -MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
> -
> -static uint fan_mult;
> -module_param(fan_mult, uint, 0);
> -MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
> -
> -static uint fan_max;
> -module_param(fan_max, uint, 0);
> -MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
> -
> -static int i8k_open_fs(struct inode *inode, struct file *file);
> -static long i8k_ioctl(struct file *, unsigned int, unsigned long);
> -
> -static const struct file_operations i8k_fops = {
> -	.owner		= THIS_MODULE,
> -	.open		= i8k_open_fs,
> -	.read		= seq_read,
> -	.llseek		= seq_lseek,
> -	.release	= single_release,
> -	.unlocked_ioctl	= i8k_ioctl,
> -};
> -
> -struct smm_regs {
> -	unsigned int eax;
> -	unsigned int ebx __packed;
> -	unsigned int ecx __packed;
> -	unsigned int edx __packed;
> -	unsigned int esi __packed;
> -	unsigned int edi __packed;
> -};
> -
> -static inline const char *i8k_get_dmi_data(int field)
> -{
> -	const char *dmi_data = dmi_get_system_info(field);
> -
> -	return dmi_data && *dmi_data ? dmi_data : "?";
> -}
> -
> -/*
> - * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
> - */
> -static int i8k_smm(struct smm_regs *regs)
> -{
> -	int rc;
> -	int eax = regs->eax;
> -	cpumask_var_t old_mask;
> -
> -	/* SMM requires CPU 0 */
> -	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
> -		return -ENOMEM;
> -	cpumask_copy(old_mask, &current->cpus_allowed);
> -	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
> -	if (rc)
> -		goto out;
> -	if (smp_processor_id() != 0) {
> -		rc = -EBUSY;
> -		goto out;
> -	}
> -
> -#if defined(CONFIG_X86_64)
> -	asm volatile("pushq %%rax\n\t"
> -		"movl 0(%%rax),%%edx\n\t"
> -		"pushq %%rdx\n\t"
> -		"movl 4(%%rax),%%ebx\n\t"
> -		"movl 8(%%rax),%%ecx\n\t"
> -		"movl 12(%%rax),%%edx\n\t"
> -		"movl 16(%%rax),%%esi\n\t"
> -		"movl 20(%%rax),%%edi\n\t"
> -		"popq %%rax\n\t"
> -		"out %%al,$0xb2\n\t"
> -		"out %%al,$0x84\n\t"
> -		"xchgq %%rax,(%%rsp)\n\t"
> -		"movl %%ebx,4(%%rax)\n\t"
> -		"movl %%ecx,8(%%rax)\n\t"
> -		"movl %%edx,12(%%rax)\n\t"
> -		"movl %%esi,16(%%rax)\n\t"
> -		"movl %%edi,20(%%rax)\n\t"
> -		"popq %%rdx\n\t"
> -		"movl %%edx,0(%%rax)\n\t"
> -		"pushfq\n\t"
> -		"popq %%rax\n\t"
> -		"andl $1,%%eax\n"
> -		: "=a"(rc)
> -		:    "a"(regs)
> -		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
> -#else
> -	asm volatile("pushl %%eax\n\t"
> -	    "movl 0(%%eax),%%edx\n\t"
> -	    "push %%edx\n\t"
> -	    "movl 4(%%eax),%%ebx\n\t"
> -	    "movl 8(%%eax),%%ecx\n\t"
> -	    "movl 12(%%eax),%%edx\n\t"
> -	    "movl 16(%%eax),%%esi\n\t"
> -	    "movl 20(%%eax),%%edi\n\t"
> -	    "popl %%eax\n\t"
> -	    "out %%al,$0xb2\n\t"
> -	    "out %%al,$0x84\n\t"
> -	    "xchgl %%eax,(%%esp)\n\t"
> -	    "movl %%ebx,4(%%eax)\n\t"
> -	    "movl %%ecx,8(%%eax)\n\t"
> -	    "movl %%edx,12(%%eax)\n\t"
> -	    "movl %%esi,16(%%eax)\n\t"
> -	    "movl %%edi,20(%%eax)\n\t"
> -	    "popl %%edx\n\t"
> -	    "movl %%edx,0(%%eax)\n\t"
> -	    "lahf\n\t"
> -	    "shrl $8,%%eax\n\t"
> -	    "andl $1,%%eax\n"
> -	    : "=a"(rc)
> -	    :    "a"(regs)
> -	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
> -#endif
> -	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
> -		rc = -EINVAL;
> -
> -out:
> -	set_cpus_allowed_ptr(current, old_mask);
> -	free_cpumask_var(old_mask);
> -	return rc;
> -}
> -
> -/*
> - * Read the Fn key status.
> - */
> -static int i8k_get_fn_status(void)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
> -	int rc;
> -
> -	rc = i8k_smm(&regs);
> -	if (rc < 0)
> -		return rc;
> -
> -	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
> -	case I8K_FN_UP:
> -		return I8K_VOL_UP;
> -	case I8K_FN_DOWN:
> -		return I8K_VOL_DOWN;
> -	case I8K_FN_MUTE:
> -		return I8K_VOL_MUTE;
> -	default:
> -		return 0;
> -	}
> -}
> -
> -/*
> - * Read the power status.
> - */
> -static int i8k_get_power_status(void)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
> -	int rc;
> -
> -	rc = i8k_smm(&regs);
> -	if (rc < 0)
> -		return rc;
> -
> -	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
> -}
> -
> -/*
> - * Read the fan status.
> - */
> -static int i8k_get_fan_status(int fan)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
> -
> -	regs.ebx = fan & 0xff;
> -	return i8k_smm(&regs) ? : regs.eax & 0xff;
> -}
> -
> -/*
> - * Read the fan speed in RPM.
> - */
> -static int i8k_get_fan_speed(int fan)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
> -
> -	regs.ebx = fan & 0xff;
> -	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
> -}
> -
> -/*
> - * Read the fan type.
> - */
> -static int i8k_get_fan_type(int fan)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
> -
> -	regs.ebx = fan & 0xff;
> -	return i8k_smm(&regs) ? : regs.eax & 0xff;
> -}
> -
> -/*
> - * Read the fan nominal rpm for specific fan speed.
> - */
> -static int i8k_get_fan_nominal_speed(int fan, int speed)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
> -
> -	regs.ebx = (fan & 0xff) | (speed << 8);
> -	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
> -}
> -
> -/*
> - * Set the fan speed (off, low, high). Returns the new fan status.
> - */
> -static int i8k_set_fan(int fan, int speed)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
> -
> -	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
> -	regs.ebx = (fan & 0xff) | (speed << 8);
> -
> -	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
> -}
> -
> -static int i8k_get_temp_type(int sensor)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
> -
> -	regs.ebx = sensor & 0xff;
> -	return i8k_smm(&regs) ? : regs.eax & 0xff;
> -}
> -
> -/*
> - * Read the cpu temperature.
> - */
> -static int _i8k_get_temp(int sensor)
> -{
> -	struct smm_regs regs = {
> -		.eax = I8K_SMM_GET_TEMP,
> -		.ebx = sensor & 0xff,
> -	};
> -
> -	return i8k_smm(&regs) ? : regs.eax & 0xff;
> -}
> -
> -static int i8k_get_temp(int sensor)
> -{
> -	int temp = _i8k_get_temp(sensor);
> -
> -	/*
> -	 * Sometimes the temperature sensor returns 0x99, which is out of range.
> -	 * In this case we retry (once) before returning an error.
> -	 # 1003655137 00000058 00005a4b
> -	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
> -	 # 1003655139 00000054 00005c52
> -	 */
> -	if (temp == 0x99) {
> -		msleep(100);
> -		temp = _i8k_get_temp(sensor);
> -	}
> -	/*
> -	 * Return -ENODATA for all invalid temperatures.
> -	 *
> -	 * Known instances are the 0x99 value as seen above as well as
> -	 * 0xc1 (193), which may be returned when trying to read the GPU
> -	 * temperature if the system supports a GPU and it is currently
> -	 * turned off.
> -	 */
> -	if (temp > I8K_MAX_TEMP)
> -		return -ENODATA;
> -
> -	return temp;
> -}
> -
> -static int i8k_get_dell_signature(int req_fn)
> -{
> -	struct smm_regs regs = { .eax = req_fn, };
> -	int rc;
> -
> -	rc = i8k_smm(&regs);
> -	if (rc < 0)
> -		return rc;
> -
> -	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
> -}
> -
> -static int
> -i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
> -{
> -	int val = 0;
> -	int speed;
> -	unsigned char buff[16];
> -	int __user *argp = (int __user *)arg;
> -
> -	if (!argp)
> -		return -EINVAL;
> -
> -	switch (cmd) {
> -	case I8K_BIOS_VERSION:
> -		val = (bios_version[0] << 16) |
> -				(bios_version[1] << 8) | bios_version[2];
> -		break;
> -
> -	case I8K_MACHINE_ID:
> -		memset(buff, 0, 16);
> -		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
> -			sizeof(buff));
> -		break;
> -
> -	case I8K_FN_STATUS:
> -		val = i8k_get_fn_status();
> -		break;
> -
> -	case I8K_POWER_STATUS:
> -		val = i8k_get_power_status();
> -		break;
> -
> -	case I8K_GET_TEMP:
> -		val = i8k_get_temp(0);
> -		break;
> -
> -	case I8K_GET_SPEED:
> -		if (copy_from_user(&val, argp, sizeof(int)))
> -			return -EFAULT;
> -
> -		val = i8k_get_fan_speed(val);
> -		break;
> -
> -	case I8K_GET_FAN:
> -		if (copy_from_user(&val, argp, sizeof(int)))
> -			return -EFAULT;
> -
> -		val = i8k_get_fan_status(val);
> -		break;
> -
> -	case I8K_SET_FAN:
> -		if (restricted && !capable(CAP_SYS_ADMIN))
> -			return -EPERM;
> -
> -		if (copy_from_user(&val, argp, sizeof(int)))
> -			return -EFAULT;
> -
> -		if (copy_from_user(&speed, argp + 1, sizeof(int)))
> -			return -EFAULT;
> -
> -		val = i8k_set_fan(val, speed);
> -		break;
> -
> -	default:
> -		return -EINVAL;
> -	}
> -
> -	if (val < 0)
> -		return val;
> -
> -	switch (cmd) {
> -	case I8K_BIOS_VERSION:
> -		if (copy_to_user(argp, &val, 4))
> -			return -EFAULT;
> -
> -		break;
> -	case I8K_MACHINE_ID:
> -		if (copy_to_user(argp, buff, 16))
> -			return -EFAULT;
> -
> -		break;
> -	default:
> -		if (copy_to_user(argp, &val, sizeof(int)))
> -			return -EFAULT;
> -
> -		break;
> -	}
> -
> -	return 0;
> -}
> -
> -static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
> -{
> -	long ret;
> -
> -	mutex_lock(&i8k_mutex);
> -	ret = i8k_ioctl_unlocked(fp, cmd, arg);
> -	mutex_unlock(&i8k_mutex);
> -
> -	return ret;
> -}
> -
> -/*
> - * Print the information for /proc/i8k.
> - */
> -static int i8k_proc_show(struct seq_file *seq, void *offset)
> -{
> -	int fn_key, cpu_temp, ac_power;
> -	int left_fan, right_fan, left_speed, right_speed;
> -
> -	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
> -	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
> -	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
> -	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
> -	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
> -	fn_key		= i8k_get_fn_status();			/*   750 µs */
> -	if (power_status)
> -		ac_power = i8k_get_power_status();		/* 14700 µs */
> -	else
> -		ac_power = -1;
> -
> -	/*
> -	 * Info:
> -	 *
> -	 * 1)  Format version (this will change if format changes)
> -	 * 2)  BIOS version
> -	 * 3)  BIOS machine ID
> -	 * 4)  Cpu temperature
> -	 * 5)  Left fan status
> -	 * 6)  Right fan status
> -	 * 7)  Left fan speed
> -	 * 8)  Right fan speed
> -	 * 9)  AC power
> -	 * 10) Fn Key status
> -	 */
> -	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
> -			  I8K_PROC_FMT,
> -			  bios_version,
> -			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
> -			  cpu_temp,
> -			  left_fan, right_fan, left_speed, right_speed,
> -			  ac_power, fn_key);
> -}
> -
> -static int i8k_open_fs(struct inode *inode, struct file *file)
> -{
> -	return single_open(file, i8k_proc_show, NULL);
> -}
> -
> -
> -/*
> - * Hwmon interface
> - */
> -
> -static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
> -					 struct device_attribute *devattr,
> -					 char *buf)
> -{
> -	static const char * const labels[] = {
> -		"CPU",
> -		"GPU",
> -		"SODIMM",
> -		"Other",
> -		"Ambient",
> -		"Other",
> -	};
> -	int index = to_sensor_dev_attr(devattr)->index;
> -	int type;
> -
> -	type = i8k_get_temp_type(index);
> -	if (type < 0)
> -		return type;
> -	if (type >= ARRAY_SIZE(labels))
> -		type = ARRAY_SIZE(labels) - 1;
> -	return sprintf(buf, "%s\n", labels[type]);
> -}
> -
> -static ssize_t i8k_hwmon_show_temp(struct device *dev,
> -				   struct device_attribute *devattr,
> -				   char *buf)
> -{
> -	int index = to_sensor_dev_attr(devattr)->index;
> -	int temp;
> -
> -	temp = i8k_get_temp(index);
> -	if (temp < 0)
> -		return temp;
> -	return sprintf(buf, "%d\n", temp * 1000);
> -}
> -
> -static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
> -					struct device_attribute *devattr,
> -					char *buf)
> -{
> -	static const char * const labels[] = {
> -		"Processor Fan",
> -		"Motherboard Fan",
> -		"Video Fan",
> -		"Power Supply Fan",
> -		"Chipset Fan",
> -		"Other Fan",
> -	};
> -	int index = to_sensor_dev_attr(devattr)->index;
> -	bool dock = false;
> -	int type;
> -
> -	type = i8k_get_fan_type(index);
> -	if (type < 0)
> -		return type;
> -
> -	if (type & 0x10) {
> -		dock = true;
> -		type &= 0x0F;
> -	}
> -
> -	if (type >= ARRAY_SIZE(labels))
> -		type = (ARRAY_SIZE(labels) - 1);
> -
> -	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
> -}
> -
> -static ssize_t i8k_hwmon_show_fan(struct device *dev,
> -				  struct device_attribute *devattr,
> -				  char *buf)
> -{
> -	int index = to_sensor_dev_attr(devattr)->index;
> -	int fan_speed;
> -
> -	fan_speed = i8k_get_fan_speed(index);
> -	if (fan_speed < 0)
> -		return fan_speed;
> -	return sprintf(buf, "%d\n", fan_speed);
> -}
> -
> -static ssize_t i8k_hwmon_show_pwm(struct device *dev,
> -				  struct device_attribute *devattr,
> -				  char *buf)
> -{
> -	int index = to_sensor_dev_attr(devattr)->index;
> -	int status;
> -
> -	status = i8k_get_fan_status(index);
> -	if (status < 0)
> -		return -EIO;
> -	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
> -}
> -
> -static ssize_t i8k_hwmon_set_pwm(struct device *dev,
> -				 struct device_attribute *attr,
> -				 const char *buf, size_t count)
> -{
> -	int index = to_sensor_dev_attr(attr)->index;
> -	unsigned long val;
> -	int err;
> -
> -	err = kstrtoul(buf, 10, &val);
> -	if (err)
> -		return err;
> -	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
> -
> -	mutex_lock(&i8k_mutex);
> -	err = i8k_set_fan(index, val);
> -	mutex_unlock(&i8k_mutex);
> -
> -	return err < 0 ? -EIO : count;
> -}
> -
> -static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
> -static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> -			  0);
> -static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
> -static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> -			  1);
> -static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
> -static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> -			  2);
> -static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
> -static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> -			  3);
> -static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
> -static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
> -			  0);
> -static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
> -			  i8k_hwmon_set_pwm, 0);
> -static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
> -			  1);
> -static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
> -			  1);
> -static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
> -			  i8k_hwmon_set_pwm, 1);
> -
> -static struct attribute *i8k_attrs[] = {
> -	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
> -	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
> -	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
> -	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
> -	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
> -	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
> -	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
> -	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
> -	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
> -	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
> -	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
> -	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
> -	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
> -	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
> -	NULL
> -};
> -
> -static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
> -			      int index)
> -{
> -	if (index >= 0 && index <= 1 &&
> -	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
> -		return 0;
> -	if (index >= 2 && index <= 3 &&
> -	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
> -		return 0;
> -	if (index >= 4 && index <= 5 &&
> -	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
> -		return 0;
> -	if (index >= 6 && index <= 7 &&
> -	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
> -		return 0;
> -	if (index >= 8 && index <= 10 &&
> -	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
> -		return 0;
> -	if (index >= 11 && index <= 13 &&
> -	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
> -		return 0;
> -
> -	return attr->mode;
> -}
> -
> -static const struct attribute_group i8k_group = {
> -	.attrs = i8k_attrs,
> -	.is_visible = i8k_is_visible,
> -};
> -__ATTRIBUTE_GROUPS(i8k);
> -
> -static int __init i8k_init_hwmon(void)
> -{
> -	int err;
> -
> -	i8k_hwmon_flags = 0;
> -
> -	/* CPU temperature attributes, if temperature type is OK */
> -	err = i8k_get_temp_type(0);
> -	if (err >= 0)
> -		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
> -	/* check for additional temperature sensors */
> -	err = i8k_get_temp_type(1);
> -	if (err >= 0)
> -		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
> -	err = i8k_get_temp_type(2);
> -	if (err >= 0)
> -		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
> -	err = i8k_get_temp_type(3);
> -	if (err >= 0)
> -		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
> -
> -	/* First fan attributes, if fan type is OK */
> -	err = i8k_get_fan_type(0);
> -	if (err >= 0)
> -		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
> -
> -	/* Second fan attributes, if fan type is OK */
> -	err = i8k_get_fan_type(1);
> -	if (err >= 0)
> -		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
> -
> -	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
> -							  i8k_groups);
> -	if (IS_ERR(i8k_hwmon_dev)) {
> -		err = PTR_ERR(i8k_hwmon_dev);
> -		i8k_hwmon_dev = NULL;
> -		pr_err("hwmon registration failed (%d)\n", err);
> -		return err;
> -	}
> -	return 0;
> -}
> -
> -struct i8k_config_data {
> -	uint fan_mult;
> -	uint fan_max;
> -};
> -
> -enum i8k_configs {
> -	DELL_LATITUDE_D520,
> -	DELL_PRECISION_490,
> -	DELL_STUDIO,
> -	DELL_XPS,
> -};
> -
> -static const struct i8k_config_data i8k_config_data[] = {
> -	[DELL_LATITUDE_D520] = {
> -		.fan_mult = 1,
> -		.fan_max = I8K_FAN_TURBO,
> -	},
> -	[DELL_PRECISION_490] = {
> -		.fan_mult = 1,
> -		.fan_max = I8K_FAN_TURBO,
> -	},
> -	[DELL_STUDIO] = {
> -		.fan_mult = 1,
> -		.fan_max = I8K_FAN_HIGH,
> -	},
> -	[DELL_XPS] = {
> -		.fan_mult = 1,
> -		.fan_max = I8K_FAN_HIGH,
> -	},
> -};
> -
> -static struct dmi_system_id i8k_dmi_table[] __initdata = {
> -	{
> -		.ident = "Dell Inspiron",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Latitude",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Inspiron 2",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Latitude D520",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
> -		},
> -		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
> -	},
> -	{
> -		.ident = "Dell Latitude 2",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
> -		},
> -	},
> -	{	/* UK Inspiron 6400  */
> -		.ident = "Dell Inspiron 3",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Inspiron 3",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Precision 490",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME,
> -				  "Precision WorkStation 490"),
> -		},
> -		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
> -	},
> -	{
> -		.ident = "Dell Precision",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Vostro",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
> -		},
> -	},
> -	{
> -		.ident = "Dell XPS421",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
> -		},
> -	},
> -	{
> -		.ident = "Dell Studio",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
> -		},
> -		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
> -	},
> -	{
> -		.ident = "Dell XPS 13",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
> -		},
> -		.driver_data = (void *)&i8k_config_data[DELL_XPS],
> -	},
> -	{
> -		.ident = "Dell XPS M140",
> -		.matches = {
> -			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> -			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
> -		},
> -		.driver_data = (void *)&i8k_config_data[DELL_XPS],
> -	},
> -	{ }
> -};
> -
> -MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
> -
> -/*
> - * Probe for the presence of a supported laptop.
> - */
> -static int __init i8k_probe(void)
> -{
> -	const struct dmi_system_id *id;
> -	int fan, ret;
> -
> -	/*
> -	 * Get DMI information
> -	 */
> -	if (!dmi_check_system(i8k_dmi_table)) {
> -		if (!ignore_dmi && !force)
> -			return -ENODEV;
> -
> -		pr_info("not running on a supported Dell system.\n");
> -		pr_info("vendor=%s, model=%s, version=%s\n",
> -			i8k_get_dmi_data(DMI_SYS_VENDOR),
> -			i8k_get_dmi_data(DMI_PRODUCT_NAME),
> -			i8k_get_dmi_data(DMI_BIOS_VERSION));
> -	}
> -
> -	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
> -		sizeof(bios_version));
> -
> -	/*
> -	 * Get SMM Dell signature
> -	 */
> -	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
> -	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
> -		pr_err("unable to get SMM Dell signature\n");
> -		if (!force)
> -			return -ENODEV;
> -	}
> -
> -	/*
> -	 * Set fan multiplier and maximal fan speed from dmi config
> -	 * Values specified in module parameters override values from dmi
> -	 */
> -	id = dmi_first_match(i8k_dmi_table);
> -	if (id && id->driver_data) {
> -		const struct i8k_config_data *conf = id->driver_data;
> -		if (!fan_mult && conf->fan_mult)
> -			fan_mult = conf->fan_mult;
> -		if (!fan_max && conf->fan_max)
> -			fan_max = conf->fan_max;
> -	}
> -
> -	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
> -	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
> -
> -	if (!fan_mult) {
> -		/*
> -		 * Autodetect fan multiplier based on nominal rpm
> -		 * If fan reports rpm value too high then set multiplier to 1
> -		 */
> -		for (fan = 0; fan < 2; ++fan) {
> -			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
> -			if (ret < 0)
> -				continue;
> -			if (ret > I8K_FAN_MAX_RPM)
> -				i8k_fan_mult = 1;
> -			break;
> -		}
> -	} else {
> -		/* Fan multiplier was specified in module param or in dmi */
> -		i8k_fan_mult = fan_mult;
> -	}
> -
> -	return 0;
> -}
> -
> -static int __init i8k_init(void)
> -{
> -	struct proc_dir_entry *proc_i8k;
> -	int err;
> -
> -	/* Are we running on an supported laptop? */
> -	if (i8k_probe())
> -		return -ENODEV;
> -
> -	/* Register the proc entry */
> -	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
> -	if (!proc_i8k)
> -		return -ENOENT;
> -
> -	err = i8k_init_hwmon();
> -	if (err)
> -		goto exit_remove_proc;
> -
> -	return 0;
> -
> - exit_remove_proc:
> -	remove_proc_entry("i8k", NULL);
> -	return err;
> -}
> -
> -static void __exit i8k_exit(void)
> -{
> -	hwmon_device_unregister(i8k_hwmon_dev);
> -	remove_proc_entry("i8k", NULL);
> -}
> -
> -module_init(i8k_init);
> -module_exit(i8k_exit);
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 6c94147..1c3e458 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -155,6 +155,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
>   obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
>   obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
>   obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
> +obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
>
>   obj-$(CONFIG_PMBUS)		+= pmbus/
>
> diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
> new file mode 100644
> index 0000000..2b04e4f
> --- /dev/null
> +++ b/drivers/hwmon/dell-smm-hwmon.c
> @@ -0,0 +1,1007 @@
> +/*
> + * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
> + *
> + * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
> + *
> + * Hwmon integration:
> + * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
> + * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
> + * Copyright (C) 2014, 2015  Pali Rohár <pali.rohar@gmail.com>
> + *
> + * This program is free software; you can redistribute it and/or modify it
> + * under the terms of the GNU General Public License as published by the
> + * Free Software Foundation; either version 2, or (at your option) any
> + * later version.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * General Public License for more details.
> + */
> +
> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> +
> +#include <linux/delay.h>
> +#include <linux/module.h>
> +#include <linux/types.h>
> +#include <linux/init.h>
> +#include <linux/proc_fs.h>
> +#include <linux/seq_file.h>
> +#include <linux/dmi.h>
> +#include <linux/capability.h>
> +#include <linux/mutex.h>
> +#include <linux/hwmon.h>
> +#include <linux/hwmon-sysfs.h>
> +#include <linux/uaccess.h>
> +#include <linux/io.h>
> +#include <linux/sched.h>
> +
> +#include <linux/i8k.h>
> +
> +#define I8K_SMM_FN_STATUS	0x0025
> +#define I8K_SMM_POWER_STATUS	0x0069
> +#define I8K_SMM_SET_FAN		0x01a3
> +#define I8K_SMM_GET_FAN		0x00a3
> +#define I8K_SMM_GET_SPEED	0x02a3
> +#define I8K_SMM_GET_FAN_TYPE	0x03a3
> +#define I8K_SMM_GET_NOM_SPEED	0x04a3
> +#define I8K_SMM_GET_TEMP	0x10a3
> +#define I8K_SMM_GET_TEMP_TYPE	0x11a3
> +#define I8K_SMM_GET_DELL_SIG1	0xfea3
> +#define I8K_SMM_GET_DELL_SIG2	0xffa3
> +
> +#define I8K_FAN_MULT		30
> +#define I8K_FAN_MAX_RPM		30000
> +#define I8K_MAX_TEMP		127
> +
> +#define I8K_FN_NONE		0x00
> +#define I8K_FN_UP		0x01
> +#define I8K_FN_DOWN		0x02
> +#define I8K_FN_MUTE		0x04
> +#define I8K_FN_MASK		0x07
> +#define I8K_FN_SHIFT		8
> +
> +#define I8K_POWER_AC		0x05
> +#define I8K_POWER_BATTERY	0x01
> +
> +static DEFINE_MUTEX(i8k_mutex);
> +static char bios_version[4];
> +static struct device *i8k_hwmon_dev;
> +static u32 i8k_hwmon_flags;
> +static uint i8k_fan_mult = I8K_FAN_MULT;
> +static uint i8k_pwm_mult;
> +static uint i8k_fan_max = I8K_FAN_HIGH;
> +
> +#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
> +#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
> +#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
> +#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
> +#define I8K_HWMON_HAVE_FAN1	(1 << 4)
> +#define I8K_HWMON_HAVE_FAN2	(1 << 5)
> +
> +MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
> +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
> +MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("i8k");
> +
> +static bool force;
> +module_param(force, bool, 0);
> +MODULE_PARM_DESC(force, "Force loading without checking for supported models");
> +
> +static bool ignore_dmi;
> +module_param(ignore_dmi, bool, 0);
> +MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
> +
> +static bool restricted;
> +module_param(restricted, bool, 0);
> +MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
> +
> +static bool power_status;
> +module_param(power_status, bool, 0600);
> +MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
> +
> +static uint fan_mult;
> +module_param(fan_mult, uint, 0);
> +MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
> +
> +static uint fan_max;
> +module_param(fan_max, uint, 0);
> +MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
> +
> +static int i8k_open_fs(struct inode *inode, struct file *file);
> +static long i8k_ioctl(struct file *, unsigned int, unsigned long);
> +
> +static const struct file_operations i8k_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= i8k_open_fs,
> +	.read		= seq_read,
> +	.llseek		= seq_lseek,
> +	.release	= single_release,
> +	.unlocked_ioctl	= i8k_ioctl,
> +};
> +
> +struct smm_regs {
> +	unsigned int eax;
> +	unsigned int ebx __packed;
> +	unsigned int ecx __packed;
> +	unsigned int edx __packed;
> +	unsigned int esi __packed;
> +	unsigned int edi __packed;
> +};
> +
> +static inline const char *i8k_get_dmi_data(int field)
> +{
> +	const char *dmi_data = dmi_get_system_info(field);
> +
> +	return dmi_data && *dmi_data ? dmi_data : "?";
> +}
> +
> +/*
> + * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
> + */
> +static int i8k_smm(struct smm_regs *regs)
> +{
> +	int rc;
> +	int eax = regs->eax;
> +	cpumask_var_t old_mask;
> +
> +	/* SMM requires CPU 0 */
> +	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
> +		return -ENOMEM;
> +	cpumask_copy(old_mask, &current->cpus_allowed);
> +	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
> +	if (rc)
> +		goto out;
> +	if (smp_processor_id() != 0) {
> +		rc = -EBUSY;
> +		goto out;
> +	}
> +
> +#if defined(CONFIG_X86_64)
> +	asm volatile("pushq %%rax\n\t"
> +		"movl 0(%%rax),%%edx\n\t"
> +		"pushq %%rdx\n\t"
> +		"movl 4(%%rax),%%ebx\n\t"
> +		"movl 8(%%rax),%%ecx\n\t"
> +		"movl 12(%%rax),%%edx\n\t"
> +		"movl 16(%%rax),%%esi\n\t"
> +		"movl 20(%%rax),%%edi\n\t"
> +		"popq %%rax\n\t"
> +		"out %%al,$0xb2\n\t"
> +		"out %%al,$0x84\n\t"
> +		"xchgq %%rax,(%%rsp)\n\t"
> +		"movl %%ebx,4(%%rax)\n\t"
> +		"movl %%ecx,8(%%rax)\n\t"
> +		"movl %%edx,12(%%rax)\n\t"
> +		"movl %%esi,16(%%rax)\n\t"
> +		"movl %%edi,20(%%rax)\n\t"
> +		"popq %%rdx\n\t"
> +		"movl %%edx,0(%%rax)\n\t"
> +		"pushfq\n\t"
> +		"popq %%rax\n\t"
> +		"andl $1,%%eax\n"
> +		: "=a"(rc)
> +		:    "a"(regs)
> +		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
> +#else
> +	asm volatile("pushl %%eax\n\t"
> +	    "movl 0(%%eax),%%edx\n\t"
> +	    "push %%edx\n\t"
> +	    "movl 4(%%eax),%%ebx\n\t"
> +	    "movl 8(%%eax),%%ecx\n\t"
> +	    "movl 12(%%eax),%%edx\n\t"
> +	    "movl 16(%%eax),%%esi\n\t"
> +	    "movl 20(%%eax),%%edi\n\t"
> +	    "popl %%eax\n\t"
> +	    "out %%al,$0xb2\n\t"
> +	    "out %%al,$0x84\n\t"
> +	    "xchgl %%eax,(%%esp)\n\t"
> +	    "movl %%ebx,4(%%eax)\n\t"
> +	    "movl %%ecx,8(%%eax)\n\t"
> +	    "movl %%edx,12(%%eax)\n\t"
> +	    "movl %%esi,16(%%eax)\n\t"
> +	    "movl %%edi,20(%%eax)\n\t"
> +	    "popl %%edx\n\t"
> +	    "movl %%edx,0(%%eax)\n\t"
> +	    "lahf\n\t"
> +	    "shrl $8,%%eax\n\t"
> +	    "andl $1,%%eax\n"
> +	    : "=a"(rc)
> +	    :    "a"(regs)
> +	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
> +#endif
> +	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
> +		rc = -EINVAL;
> +
> +out:
> +	set_cpus_allowed_ptr(current, old_mask);
> +	free_cpumask_var(old_mask);
> +	return rc;
> +}
> +
> +/*
> + * Read the Fn key status.
> + */
> +static int i8k_get_fn_status(void)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
> +	int rc;
> +
> +	rc = i8k_smm(&regs);
> +	if (rc < 0)
> +		return rc;
> +
> +	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
> +	case I8K_FN_UP:
> +		return I8K_VOL_UP;
> +	case I8K_FN_DOWN:
> +		return I8K_VOL_DOWN;
> +	case I8K_FN_MUTE:
> +		return I8K_VOL_MUTE;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +/*
> + * Read the power status.
> + */
> +static int i8k_get_power_status(void)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
> +	int rc;
> +
> +	rc = i8k_smm(&regs);
> +	if (rc < 0)
> +		return rc;
> +
> +	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
> +}
> +
> +/*
> + * Read the fan status.
> + */
> +static int i8k_get_fan_status(int fan)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
> +
> +	regs.ebx = fan & 0xff;
> +	return i8k_smm(&regs) ? : regs.eax & 0xff;
> +}
> +
> +/*
> + * Read the fan speed in RPM.
> + */
> +static int i8k_get_fan_speed(int fan)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
> +
> +	regs.ebx = fan & 0xff;
> +	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
> +}
> +
> +/*
> + * Read the fan type.
> + */
> +static int i8k_get_fan_type(int fan)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
> +
> +	regs.ebx = fan & 0xff;
> +	return i8k_smm(&regs) ? : regs.eax & 0xff;
> +}
> +
> +/*
> + * Read the fan nominal rpm for specific fan speed.
> + */
> +static int i8k_get_fan_nominal_speed(int fan, int speed)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
> +
> +	regs.ebx = (fan & 0xff) | (speed << 8);
> +	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
> +}
> +
> +/*
> + * Set the fan speed (off, low, high). Returns the new fan status.
> + */
> +static int i8k_set_fan(int fan, int speed)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
> +
> +	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
> +	regs.ebx = (fan & 0xff) | (speed << 8);
> +
> +	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
> +}
> +
> +static int i8k_get_temp_type(int sensor)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
> +
> +	regs.ebx = sensor & 0xff;
> +	return i8k_smm(&regs) ? : regs.eax & 0xff;
> +}
> +
> +/*
> + * Read the cpu temperature.
> + */
> +static int _i8k_get_temp(int sensor)
> +{
> +	struct smm_regs regs = {
> +		.eax = I8K_SMM_GET_TEMP,
> +		.ebx = sensor & 0xff,
> +	};
> +
> +	return i8k_smm(&regs) ? : regs.eax & 0xff;
> +}
> +
> +static int i8k_get_temp(int sensor)
> +{
> +	int temp = _i8k_get_temp(sensor);
> +
> +	/*
> +	 * Sometimes the temperature sensor returns 0x99, which is out of range.
> +	 * In this case we retry (once) before returning an error.
> +	 # 1003655137 00000058 00005a4b
> +	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
> +	 # 1003655139 00000054 00005c52
> +	 */
> +	if (temp == 0x99) {
> +		msleep(100);
> +		temp = _i8k_get_temp(sensor);
> +	}
> +	/*
> +	 * Return -ENODATA for all invalid temperatures.
> +	 *
> +	 * Known instances are the 0x99 value as seen above as well as
> +	 * 0xc1 (193), which may be returned when trying to read the GPU
> +	 * temperature if the system supports a GPU and it is currently
> +	 * turned off.
> +	 */
> +	if (temp > I8K_MAX_TEMP)
> +		return -ENODATA;
> +
> +	return temp;
> +}
> +
> +static int i8k_get_dell_signature(int req_fn)
> +{
> +	struct smm_regs regs = { .eax = req_fn, };
> +	int rc;
> +
> +	rc = i8k_smm(&regs);
> +	if (rc < 0)
> +		return rc;
> +
> +	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
> +}
> +
> +static int
> +i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
> +{
> +	int val = 0;
> +	int speed;
> +	unsigned char buff[16];
> +	int __user *argp = (int __user *)arg;
> +
> +	if (!argp)
> +		return -EINVAL;
> +
> +	switch (cmd) {
> +	case I8K_BIOS_VERSION:
> +		val = (bios_version[0] << 16) |
> +				(bios_version[1] << 8) | bios_version[2];
> +		break;
> +
> +	case I8K_MACHINE_ID:
> +		memset(buff, 0, 16);
> +		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
> +			sizeof(buff));
> +		break;
> +
> +	case I8K_FN_STATUS:
> +		val = i8k_get_fn_status();
> +		break;
> +
> +	case I8K_POWER_STATUS:
> +		val = i8k_get_power_status();
> +		break;
> +
> +	case I8K_GET_TEMP:
> +		val = i8k_get_temp(0);
> +		break;
> +
> +	case I8K_GET_SPEED:
> +		if (copy_from_user(&val, argp, sizeof(int)))
> +			return -EFAULT;
> +
> +		val = i8k_get_fan_speed(val);
> +		break;
> +
> +	case I8K_GET_FAN:
> +		if (copy_from_user(&val, argp, sizeof(int)))
> +			return -EFAULT;
> +
> +		val = i8k_get_fan_status(val);
> +		break;
> +
> +	case I8K_SET_FAN:
> +		if (restricted && !capable(CAP_SYS_ADMIN))
> +			return -EPERM;
> +
> +		if (copy_from_user(&val, argp, sizeof(int)))
> +			return -EFAULT;
> +
> +		if (copy_from_user(&speed, argp + 1, sizeof(int)))
> +			return -EFAULT;
> +
> +		val = i8k_set_fan(val, speed);
> +		break;
> +
> +	default:
> +		return -EINVAL;
> +	}
> +
> +	if (val < 0)
> +		return val;
> +
> +	switch (cmd) {
> +	case I8K_BIOS_VERSION:
> +		if (copy_to_user(argp, &val, 4))
> +			return -EFAULT;
> +
> +		break;
> +	case I8K_MACHINE_ID:
> +		if (copy_to_user(argp, buff, 16))
> +			return -EFAULT;
> +
> +		break;
> +	default:
> +		if (copy_to_user(argp, &val, sizeof(int)))
> +			return -EFAULT;
> +
> +		break;
> +	}
> +
> +	return 0;
> +}
> +
> +static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
> +{
> +	long ret;
> +
> +	mutex_lock(&i8k_mutex);
> +	ret = i8k_ioctl_unlocked(fp, cmd, arg);
> +	mutex_unlock(&i8k_mutex);
> +
> +	return ret;
> +}
> +
> +/*
> + * Print the information for /proc/i8k.
> + */
> +static int i8k_proc_show(struct seq_file *seq, void *offset)
> +{
> +	int fn_key, cpu_temp, ac_power;
> +	int left_fan, right_fan, left_speed, right_speed;
> +
> +	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
> +	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
> +	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
> +	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
> +	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
> +	fn_key		= i8k_get_fn_status();			/*   750 µs */
> +	if (power_status)
> +		ac_power = i8k_get_power_status();		/* 14700 µs */
> +	else
> +		ac_power = -1;
> +
> +	/*
> +	 * Info:
> +	 *
> +	 * 1)  Format version (this will change if format changes)
> +	 * 2)  BIOS version
> +	 * 3)  BIOS machine ID
> +	 * 4)  Cpu temperature
> +	 * 5)  Left fan status
> +	 * 6)  Right fan status
> +	 * 7)  Left fan speed
> +	 * 8)  Right fan speed
> +	 * 9)  AC power
> +	 * 10) Fn Key status
> +	 */
> +	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
> +			  I8K_PROC_FMT,
> +			  bios_version,
> +			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
> +			  cpu_temp,
> +			  left_fan, right_fan, left_speed, right_speed,
> +			  ac_power, fn_key);
> +}
> +
> +static int i8k_open_fs(struct inode *inode, struct file *file)
> +{
> +	return single_open(file, i8k_proc_show, NULL);
> +}
> +
> +
> +/*
> + * Hwmon interface
> + */
> +
> +static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
> +					 struct device_attribute *devattr,
> +					 char *buf)
> +{
> +	static const char * const labels[] = {
> +		"CPU",
> +		"GPU",
> +		"SODIMM",
> +		"Other",
> +		"Ambient",
> +		"Other",
> +	};
> +	int index = to_sensor_dev_attr(devattr)->index;
> +	int type;
> +
> +	type = i8k_get_temp_type(index);
> +	if (type < 0)
> +		return type;
> +	if (type >= ARRAY_SIZE(labels))
> +		type = ARRAY_SIZE(labels) - 1;
> +	return sprintf(buf, "%s\n", labels[type]);
> +}
> +
> +static ssize_t i8k_hwmon_show_temp(struct device *dev,
> +				   struct device_attribute *devattr,
> +				   char *buf)
> +{
> +	int index = to_sensor_dev_attr(devattr)->index;
> +	int temp;
> +
> +	temp = i8k_get_temp(index);
> +	if (temp < 0)
> +		return temp;
> +	return sprintf(buf, "%d\n", temp * 1000);
> +}
> +
> +static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
> +					struct device_attribute *devattr,
> +					char *buf)
> +{
> +	static const char * const labels[] = {
> +		"Processor Fan",
> +		"Motherboard Fan",
> +		"Video Fan",
> +		"Power Supply Fan",
> +		"Chipset Fan",
> +		"Other Fan",
> +	};
> +	int index = to_sensor_dev_attr(devattr)->index;
> +	bool dock = false;
> +	int type;
> +
> +	type = i8k_get_fan_type(index);
> +	if (type < 0)
> +		return type;
> +
> +	if (type & 0x10) {
> +		dock = true;
> +		type &= 0x0F;
> +	}
> +
> +	if (type >= ARRAY_SIZE(labels))
> +		type = (ARRAY_SIZE(labels) - 1);
> +
> +	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
> +}
> +
> +static ssize_t i8k_hwmon_show_fan(struct device *dev,
> +				  struct device_attribute *devattr,
> +				  char *buf)
> +{
> +	int index = to_sensor_dev_attr(devattr)->index;
> +	int fan_speed;
> +
> +	fan_speed = i8k_get_fan_speed(index);
> +	if (fan_speed < 0)
> +		return fan_speed;
> +	return sprintf(buf, "%d\n", fan_speed);
> +}
> +
> +static ssize_t i8k_hwmon_show_pwm(struct device *dev,
> +				  struct device_attribute *devattr,
> +				  char *buf)
> +{
> +	int index = to_sensor_dev_attr(devattr)->index;
> +	int status;
> +
> +	status = i8k_get_fan_status(index);
> +	if (status < 0)
> +		return -EIO;
> +	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
> +}
> +
> +static ssize_t i8k_hwmon_set_pwm(struct device *dev,
> +				 struct device_attribute *attr,
> +				 const char *buf, size_t count)
> +{
> +	int index = to_sensor_dev_attr(attr)->index;
> +	unsigned long val;
> +	int err;
> +
> +	err = kstrtoul(buf, 10, &val);
> +	if (err)
> +		return err;
> +	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
> +
> +	mutex_lock(&i8k_mutex);
> +	err = i8k_set_fan(index, val);
> +	mutex_unlock(&i8k_mutex);
> +
> +	return err < 0 ? -EIO : count;
> +}
> +
> +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
> +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> +			  0);
> +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
> +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> +			  1);
> +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
> +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> +			  2);
> +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
> +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
> +			  3);
> +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
> +static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
> +			  0);
> +static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
> +			  i8k_hwmon_set_pwm, 0);
> +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
> +			  1);
> +static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
> +			  1);
> +static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
> +			  i8k_hwmon_set_pwm, 1);
> +
> +static struct attribute *i8k_attrs[] = {
> +	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
> +	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
> +	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
> +	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
> +	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
> +	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
> +	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
> +	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
> +	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
> +	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
> +	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
> +	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
> +	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
> +	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
> +	NULL
> +};
> +
> +static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
> +			      int index)
> +{
> +	if (index >= 0 && index <= 1 &&
> +	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
> +		return 0;
> +	if (index >= 2 && index <= 3 &&
> +	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
> +		return 0;
> +	if (index >= 4 && index <= 5 &&
> +	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
> +		return 0;
> +	if (index >= 6 && index <= 7 &&
> +	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
> +		return 0;
> +	if (index >= 8 && index <= 10 &&
> +	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
> +		return 0;
> +	if (index >= 11 && index <= 13 &&
> +	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
> +		return 0;
> +
> +	return attr->mode;
> +}
> +
> +static const struct attribute_group i8k_group = {
> +	.attrs = i8k_attrs,
> +	.is_visible = i8k_is_visible,
> +};
> +__ATTRIBUTE_GROUPS(i8k);
> +
> +static int __init i8k_init_hwmon(void)
> +{
> +	int err;
> +
> +	i8k_hwmon_flags = 0;
> +
> +	/* CPU temperature attributes, if temperature type is OK */
> +	err = i8k_get_temp_type(0);
> +	if (err >= 0)
> +		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
> +	/* check for additional temperature sensors */
> +	err = i8k_get_temp_type(1);
> +	if (err >= 0)
> +		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
> +	err = i8k_get_temp_type(2);
> +	if (err >= 0)
> +		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
> +	err = i8k_get_temp_type(3);
> +	if (err >= 0)
> +		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
> +
> +	/* First fan attributes, if fan type is OK */
> +	err = i8k_get_fan_type(0);
> +	if (err >= 0)
> +		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
> +
> +	/* Second fan attributes, if fan type is OK */
> +	err = i8k_get_fan_type(1);
> +	if (err >= 0)
> +		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
> +
> +	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
> +							  i8k_groups);
> +	if (IS_ERR(i8k_hwmon_dev)) {
> +		err = PTR_ERR(i8k_hwmon_dev);
> +		i8k_hwmon_dev = NULL;
> +		pr_err("hwmon registration failed (%d)\n", err);
> +		return err;
> +	}
> +	return 0;
> +}
> +
> +struct i8k_config_data {
> +	uint fan_mult;
> +	uint fan_max;
> +};
> +
> +enum i8k_configs {
> +	DELL_LATITUDE_D520,
> +	DELL_PRECISION_490,
> +	DELL_STUDIO,
> +	DELL_XPS,
> +};
> +
> +static const struct i8k_config_data i8k_config_data[] = {
> +	[DELL_LATITUDE_D520] = {
> +		.fan_mult = 1,
> +		.fan_max = I8K_FAN_TURBO,
> +	},
> +	[DELL_PRECISION_490] = {
> +		.fan_mult = 1,
> +		.fan_max = I8K_FAN_TURBO,
> +	},
> +	[DELL_STUDIO] = {
> +		.fan_mult = 1,
> +		.fan_max = I8K_FAN_HIGH,
> +	},
> +	[DELL_XPS] = {
> +		.fan_mult = 1,
> +		.fan_max = I8K_FAN_HIGH,
> +	},
> +};
> +
> +static struct dmi_system_id i8k_dmi_table[] __initdata = {
> +	{
> +		.ident = "Dell Inspiron",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Latitude",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Inspiron 2",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Latitude D520",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
> +		},
> +		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
> +	},
> +	{
> +		.ident = "Dell Latitude 2",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
> +		},
> +	},
> +	{	/* UK Inspiron 6400  */
> +		.ident = "Dell Inspiron 3",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Inspiron 3",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Precision 490",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME,
> +				  "Precision WorkStation 490"),
> +		},
> +		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
> +	},
> +	{
> +		.ident = "Dell Precision",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Vostro",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
> +		},
> +	},
> +	{
> +		.ident = "Dell XPS421",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
> +		},
> +	},
> +	{
> +		.ident = "Dell Studio",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
> +		},
> +		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
> +	},
> +	{
> +		.ident = "Dell XPS 13",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
> +		},
> +		.driver_data = (void *)&i8k_config_data[DELL_XPS],
> +	},
> +	{
> +		.ident = "Dell XPS M140",
> +		.matches = {
> +			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
> +			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
> +		},
> +		.driver_data = (void *)&i8k_config_data[DELL_XPS],
> +	},
> +	{ }
> +};
> +
> +MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
> +
> +/*
> + * Probe for the presence of a supported laptop.
> + */
> +static int __init i8k_probe(void)
> +{
> +	const struct dmi_system_id *id;
> +	int fan, ret;
> +
> +	/*
> +	 * Get DMI information
> +	 */
> +	if (!dmi_check_system(i8k_dmi_table)) {
> +		if (!ignore_dmi && !force)
> +			return -ENODEV;
> +
> +		pr_info("not running on a supported Dell system.\n");
> +		pr_info("vendor=%s, model=%s, version=%s\n",
> +			i8k_get_dmi_data(DMI_SYS_VENDOR),
> +			i8k_get_dmi_data(DMI_PRODUCT_NAME),
> +			i8k_get_dmi_data(DMI_BIOS_VERSION));
> +	}
> +
> +	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
> +		sizeof(bios_version));
> +
> +	/*
> +	 * Get SMM Dell signature
> +	 */
> +	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
> +	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
> +		pr_err("unable to get SMM Dell signature\n");
> +		if (!force)
> +			return -ENODEV;
> +	}
> +
> +	/*
> +	 * Set fan multiplier and maximal fan speed from dmi config
> +	 * Values specified in module parameters override values from dmi
> +	 */
> +	id = dmi_first_match(i8k_dmi_table);
> +	if (id && id->driver_data) {
> +		const struct i8k_config_data *conf = id->driver_data;
> +		if (!fan_mult && conf->fan_mult)
> +			fan_mult = conf->fan_mult;
> +		if (!fan_max && conf->fan_max)
> +			fan_max = conf->fan_max;
> +	}
> +
> +	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
> +	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
> +
> +	if (!fan_mult) {
> +		/*
> +		 * Autodetect fan multiplier based on nominal rpm
> +		 * If fan reports rpm value too high then set multiplier to 1
> +		 */
> +		for (fan = 0; fan < 2; ++fan) {
> +			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
> +			if (ret < 0)
> +				continue;
> +			if (ret > I8K_FAN_MAX_RPM)
> +				i8k_fan_mult = 1;
> +			break;
> +		}
> +	} else {
> +		/* Fan multiplier was specified in module param or in dmi */
> +		i8k_fan_mult = fan_mult;
> +	}
> +
> +	return 0;
> +}
> +
> +static int __init i8k_init(void)
> +{
> +	struct proc_dir_entry *proc_i8k;
> +	int err;
> +
> +	/* Are we running on an supported laptop? */
> +	if (i8k_probe())
> +		return -ENODEV;
> +
> +	/* Register the proc entry */
> +	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
> +	if (!proc_i8k)
> +		return -ENOENT;
> +
> +	err = i8k_init_hwmon();
> +	if (err)
> +		goto exit_remove_proc;
> +
> +	return 0;
> +
> + exit_remove_proc:
> +	remove_proc_entry("i8k", NULL);
> +	return err;
> +}
> +
> +static void __exit i8k_exit(void)
> +{
> +	hwmon_device_unregister(i8k_hwmon_dev);
> +	remove_proc_entry("i8k", NULL);
> +}
> +
> +module_init(i8k_init);
> +module_exit(i8k_exit);
>


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 11:04   ` Paul Bolle
  2015-03-28 12:54     ` Steven Honeyman
@ 2015-03-28 21:55     ` Pali Rohár
  2015-03-28 22:06       ` Paul Bolle
  1 sibling, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 21:55 UTC (permalink / raw)
  To: Paul Bolle
  Cc: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 663 bytes --]

On Saturday 28 March 2015 12:04:20 Paul Bolle wrote:
> On Sat, 2015-03-28 at 11:24 +0100, Pali Rohár wrote:
> > --- a/drivers/hwmon/Kconfig
> > +++ b/drivers/hwmon/Kconfig
> > @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
> > 
> >  	  This driver provides support for the Ultra45 workstation
> >  	  environmental sensors.
> > 
> > +config SENSORS_DELL_SMM
> > +	tristate "Dell laptop SMM BIOS hwmon driver"
> > +	depends ON X86
> 
> How did this past your testing?
> 

Hello,

I have tested compilation only with out of tree make command with 
manual CONFIG_SENSORS_DELL_SMM and CONFIG_I8K options.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 12:54     ` Steven Honeyman
  2015-03-28 14:13       ` Guenter Roeck
@ 2015-03-28 22:00       ` Pali Rohár
  2015-03-28 22:20         ` Guenter Roeck
  1 sibling, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 22:00 UTC (permalink / raw)
  To: Steven Honeyman
  Cc: Paul Bolle, Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman,
	Jean Delvare, Valdis Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 3028 bytes --]

On Saturday 28 March 2015 13:54:51 Steven Honeyman wrote:
> On 28 March 2015 at 11:04, Paul Bolle <pebolle@tiscali.nl> 
wrote:
> > On Sat, 2015-03-28 at 11:24 +0100, Pali Rohár wrote:
> >> --- a/drivers/hwmon/Kconfig
> >> +++ b/drivers/hwmon/Kconfig
> >> @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
> >> 
> >>         This driver provides support for the Ultra45
> >>         workstation environmental sensors.
> >> 
> >> +config SENSORS_DELL_SMM
> >> +     tristate "Dell laptop SMM BIOS hwmon driver"
> >> +     depends ON X86
> > 
> > How did this past your testing?
> > 
> >> +     ---help---
> >> +       This hwmon driver adds support for reporting
> >> temperature of different +       sensors and controls the
> >> fans on Dell laptops via System Management +       Mode
> >> provided by Dell BIOS.
> >> +
> >> +       When option I8K is also enabled this driver
> >> provides legacy /proc/i8k +       userspace interface for
> >> i8kutils package.
> >> +
> 
> It's working OK for me (after fixing the mistake pointed out
> above).
> 
> [Latitude e6540]
> -----
> coretemp-isa-0000
> Adapter: ISA adapter
> Physical id 0:  +53.0°C  (high = +84.0°C, crit = +100.0°C)
> Core 0:         +51.0°C  (high = +84.0°C, crit = +100.0°C)
> Core 1:         +53.0°C  (high = +84.0°C, crit = +100.0°C)
> 
> i8k-virtual-0
> Adapter: Virtual device
> Processor Fan: 3171 RPM
> CPU:            +52.0°C
> Ambient:        +46.0°C
> SODIMM:         +40.0°C
> -----
> 
> Ambient and SODIMM look swapped - but it's probably just
> another Dell error. I really need to look at getting coreboot
> on this thing.
> 

Both patches do not change any code. So there should not be any 
type swapped with or without my patch.

Cpu type is requested via SMM in same way as it is doing old DOS 
executable. So there can be maybe problem in DELL SMM handler too 
or maybe just I did not understand correctly assembler dump of 
DOS executable...

> On 28 March 2015 at 10:24, Pali Rohár <pali.rohar@gmail.com>
> wrote: ...
> 
> >  config I8K
> > 
> > -       tristate "Dell laptop support"
> > -       select HWMON
> > +       bool "Dell i8k legacy laptop support"
> > +       select SENSORS_DELL_SMM
> 
> ...
> 
> > +config SENSORS_DELL_SMM
> > +       tristate "Dell laptop SMM BIOS hwmon driver"
> 
> The only change I'd suggest is not to change the tristate to
> bool for I8K. Forcing that to bool means that
> SENSORS_DELL_SMM can't be compiled as a module if the user
> wishes to keep i8k hanging around "just in case". It'll
> probably annoy distro kernel packagers too.
> 
> 
> Thanks,
> Steven

My idea was:

SENSORS_DELL_SMM enable/disable compilation of kernel driver 
(ether statically link into kernel image or as external module)

CONFIG_I8K just enable /proc/i8k code in SENSORS_DELL_SMM -- no 
additional driver, just boolean switch which has effect only iff 
SENSORS_DELL_SMM is Y or M

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 14:23   ` Guenter Roeck
@ 2015-03-28 22:04     ` Pali Rohár
  0 siblings, 0 replies; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 22:04 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 2050 bytes --]

On Saturday 28 March 2015 15:23:15 Guenter Roeck wrote:
> > +	---help---
> > +	  This hwmon driver adds support for reporting temperature
> > of different +	  sensors and controls the fans on Dell
> > laptops via System Management +	  Mode provided by Dell
> > BIOS.
> > +
> > +	  When option I8K is also enabled this driver provides
> > legacy /proc/i8k +	  userspace interface for i8kutils
> > package.
> > +
> 
> Please add this in alphabetic order.
> 

ok

> >   if ACPI
> >   
> >   comment "ACPI drivers"
> > 
> > diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> > index 1c3e458..9eec614 100644
> > --- a/drivers/hwmon/Makefile
> > +++ b/drivers/hwmon/Makefile
> > @@ -155,7 +155,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+=
> > w83l785ts.o
> > 
> >   obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
> >   obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
> >   obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
> > 
> > -obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
> > +obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o
> 
> Same here.
> 

ok

> > -	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
> > -	if (!proc_i8k)
> > +	if (!proc_create("i8k", 0, NULL, &i8k_fops))
> > 
> >   		return -ENOENT;
> 
> I would prefer not to fail here but report a warning.
> This is no longer a fatal condition.
> 

ok, no problem

> 
> Can you move all the conditional functions and global
> variables together under a single #ifdef ? That should
> include functions to create the proc entries, and shim
> functions for the same if I8K is not configured.
> 

ok, I will move procfs code to one location.

> Also, the #ifdef would not cover the case where I8K is
> configured as module (there is no reason to force it to
> bool). You should use "#if IS_ENABLED(CONFIG_I8K)" instead.
> 

I think that setting CONFIG_I8K to tristate (Y/M/N) does not make 
sense... CONFIG_I8K just control if /proc/i8k will be compiled 
into dell-smm-hwmon or not.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 21:55     ` Pali Rohár
@ 2015-03-28 22:06       ` Paul Bolle
  2015-03-28 22:19         ` Steven Honeyman
  0 siblings, 1 reply; 40+ messages in thread
From: Paul Bolle @ 2015-03-28 22:06 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

On Sat, 2015-03-28 at 22:55 +0100, Pali Rohár wrote:
> I have tested compilation only with out of tree make command with 
> manual CONFIG_SENSORS_DELL_SMM and CONFIG_I8K options.

Not sure what you mean here. I can't get things past this error:
    drivers/hwmon/Kconfig:1721: syntax error
    drivers/hwmon/Kconfig:1720: invalid option
    make[1]: *** [oldconfig] Error 1
    make: *** [oldconfig] Error 2

Thanks,


Paul Bolle



^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 22:06       ` Paul Bolle
@ 2015-03-28 22:19         ` Steven Honeyman
  2015-03-28 22:33           ` Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Steven Honeyman @ 2015-03-28 22:19 UTC (permalink / raw)
  To: Paul Bolle
  Cc: Pali Rohár, Guenter Roeck, Arnd Bergmann,
	Greg Kroah-Hartman, Jean Delvare, Valdis Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

On 28 March 2015 at 22:00, Pali Rohár <pali.rohar@gmail.com> wrote:
> On Saturday 28 March 2015 13:54:51 Steven Honeyman wrote:
>> i8k-virtual-0
>> Adapter: Virtual device
>> Processor Fan: 3171 RPM
>> CPU:            +52.0°C
>> Ambient:        +46.0°C
>> SODIMM:         +40.0°C
>> -----
>>
>> Ambient and SODIMM look swapped - but it's probably just
>> another Dell error. I really need to look at getting coreboot
>> on this thing.
>>
>
> Both patches do not change any code. So there should not be any
> type swapped with or without my patch.
>
> Cpu type is requested via SMM in same way as it is doing old DOS
> executable. So there can be maybe problem in DELL SMM handler too
> or maybe just I did not understand correctly assembler dump of
> DOS executable...
>

Is there some way that I can find out for you? I've got a Windows
partition somewhere, or DOS boot disk should be easy enough if this
program works on new hardware.
If I knew where the sensors were placed I could cool one of them.


On 28 March 2015 at 22:04, Pali Rohár <pali.rohar@gmail.com> wrote:
> I think that setting CONFIG_I8K to tristate (Y/M/N) does not make
> sense... CONFIG_I8K just control if /proc/i8k will be compiled
> into dell-smm-hwmon or not.

Try and select dell-smm-hwmon as a module, with i8k enabled as well...


On 28 March 2015 at 22:06, Paul Bolle <pebolle@tiscali.nl> wrote:
> On Sat, 2015-03-28 at 22:55 +0100, Pali Rohár wrote:
>> I have tested compilation only with out of tree make command with
>> manual CONFIG_SENSORS_DELL_SMM and CONFIG_I8K options.
>
> Not sure what you mean here. I can't get things past this error:
>     drivers/hwmon/Kconfig:1721: syntax error
>     drivers/hwmon/Kconfig:1720: invalid option

--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1705,8 +1705,8 @@

 config SENSORS_DELL_SMM
     tristate "Dell laptop SMM BIOS hwmon driver"
-    depends ON X86
-    ---help---
+    depends on X86
+    help
       This hwmon driver adds support for reporting temperature of different
       sensors and controls the fans on Dell laptops via System Management
       Mode provided by Dell BIOS.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 22:00       ` Pali Rohár
@ 2015-03-28 22:20         ` Guenter Roeck
  2015-03-28 22:44           ` Paul Bolle
  0 siblings, 1 reply; 40+ messages in thread
From: Guenter Roeck @ 2015-03-28 22:20 UTC (permalink / raw)
  To: Pali Rohár, Steven Honeyman
  Cc: Paul Bolle, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare,
	Valdis Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On 03/28/2015 03:00 PM, Pali Rohár wrote:
> On Saturday 28 March 2015 13:54:51 Steven Honeyman wrote:
>> On 28 March 2015 at 11:04, Paul Bolle <pebolle@tiscali.nl>
> wrote:
>>> On Sat, 2015-03-28 at 11:24 +0100, Pali Rohár wrote:
>>>> --- a/drivers/hwmon/Kconfig
>>>> +++ b/drivers/hwmon/Kconfig
>>>> @@ -1703,6 +1703,17 @@ config SENSORS_ULTRA45
>>>>
>>>>          This driver provides support for the Ultra45
>>>>          workstation environmental sensors.
>>>>
>>>> +config SENSORS_DELL_SMM
>>>> +     tristate "Dell laptop SMM BIOS hwmon driver"
>>>> +     depends ON X86
>>>
>>> How did this past your testing?
>>>
>>>> +     ---help---
>>>> +       This hwmon driver adds support for reporting
>>>> temperature of different +       sensors and controls the
>>>> fans on Dell laptops via System Management +       Mode
>>>> provided by Dell BIOS.
>>>> +
>>>> +       When option I8K is also enabled this driver
>>>> provides legacy /proc/i8k +       userspace interface for
>>>> i8kutils package.
>>>> +
>>
>> It's working OK for me (after fixing the mistake pointed out
>> above).
>>
>> [Latitude e6540]
>> -----
>> coretemp-isa-0000
>> Adapter: ISA adapter
>> Physical id 0:  +53.0°C  (high = +84.0°C, crit = +100.0°C)
>> Core 0:         +51.0°C  (high = +84.0°C, crit = +100.0°C)
>> Core 1:         +53.0°C  (high = +84.0°C, crit = +100.0°C)
>>
>> i8k-virtual-0
>> Adapter: Virtual device
>> Processor Fan: 3171 RPM
>> CPU:            +52.0°C
>> Ambient:        +46.0°C
>> SODIMM:         +40.0°C
>> -----
>>
>> Ambient and SODIMM look swapped - but it's probably just
>> another Dell error. I really need to look at getting coreboot
>> on this thing.
>>
>
> Both patches do not change any code. So there should not be any
> type swapped with or without my patch.
>
> Cpu type is requested via SMM in same way as it is doing old DOS
> executable. So there can be maybe problem in DELL SMM handler too
> or maybe just I did not understand correctly assembler dump of
> DOS executable...
>
>> On 28 March 2015 at 10:24, Pali Rohár <pali.rohar@gmail.com>
>> wrote: ...
>>
>>>   config I8K
>>>
>>> -       tristate "Dell laptop support"
>>> -       select HWMON
>>> +       bool "Dell i8k legacy laptop support"
>>> +       select SENSORS_DELL_SMM
>>
>> ...
>>
>>> +config SENSORS_DELL_SMM
>>> +       tristate "Dell laptop SMM BIOS hwmon driver"
>>
>> The only change I'd suggest is not to change the tristate to
>> bool for I8K. Forcing that to bool means that
>> SENSORS_DELL_SMM can't be compiled as a module if the user
>> wishes to keep i8k hanging around "just in case". It'll
>> probably annoy distro kernel packagers too.
>>
>>
>> Thanks,
>> Steven
>
> My idea was:
>
> SENSORS_DELL_SMM enable/disable compilation of kernel driver
> (ether statically link into kernel image or as external module)
>
> CONFIG_I8K just enable /proc/i8k code in SENSORS_DELL_SMM -- no
> additional driver, just boolean switch which has effect only iff
> SENSORS_DELL_SMM is Y or M
>

Maybe that is the plan, but it is not what is happening.
I8K selects SENSORS_DELL_SMM, so a boolean I8K forces SENSORS_DELL_SMM
to be built into the kernel.

What you suggest would work if I8K would depend on SENSORS_DELL_SMM,
but then the symbols would not be backward-compatible.

Guenter


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 22:19         ` Steven Honeyman
@ 2015-03-28 22:33           ` Pali Rohár
  2015-03-30  7:44             ` Jean Delvare
  0 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-03-28 22:33 UTC (permalink / raw)
  To: Steven Honeyman
  Cc: Paul Bolle, Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman,
	Jean Delvare, Valdis Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 3284 bytes --]

On Saturday 28 March 2015 23:19:04 Steven Honeyman wrote:
> On 28 March 2015 at 22:00, Pali Rohár <pali.rohar@gmail.com>
> wrote:
> > On Saturday 28 March 2015 13:54:51 Steven Honeyman wrote:
> >> i8k-virtual-0
> >> Adapter: Virtual device
> >> Processor Fan: 3171 RPM
> >> CPU:            +52.0°C
> >> Ambient:        +46.0°C
> >> SODIMM:         +40.0°C
> >> -----
> >> 
> >> Ambient and SODIMM look swapped - but it's probably just
> >> another Dell error. I really need to look at getting
> >> coreboot on this thing.
> > 
> > Both patches do not change any code. So there should not be
> > any type swapped with or without my patch.
> > 
> > Cpu type is requested via SMM in same way as it is doing old
> > DOS executable. So there can be maybe problem in DELL SMM
> > handler too or maybe just I did not understand correctly
> > assembler dump of DOS executable...
> 
> Is there some way that I can find out for you? I've got a
> Windows partition somewhere, or DOS boot disk should be easy
> enough if this program works on new hardware.
> If I knew where the sensors were placed I could cool one of
> them.
> 

Maybe DOS executable is just too old... and need to use new one. 
I extracted diagnostic application from BIOS package (EFI binary 
which show temperature in BIOS) for my laptop, but decoding 
instructions of this big graphic binary is beyond my power. But 
it looks like it calls same SMM inb/outb calls, so SMM code in 
BIOS handler was not changed.

So if you want to fix this problem, next step is to disassemble 
or understand new modern EFI diag binary...

I think it would be better to locate on which bus are sensors 
connected (smbus? isa? pci?), find out HW chips and wrote native 
kernel drivers for them...

I scanned smbus (intel controller connected on pci) on my E6440 
but there is no additional/unknown device. I have no idea where 
else could be sensor device connected and accessible (from SMM or 
kernel mode).

> On 28 March 2015 at 22:04, Pali Rohár <pali.rohar@gmail.com>
> wrote:
> > I think that setting CONFIG_I8K to tristate (Y/M/N) does not
> > make sense... CONFIG_I8K just control if /proc/i8k will be
> > compiled into dell-smm-hwmon or not.
> 
> Try and select dell-smm-hwmon as a module, with i8k enabled as
> well...
> 

Got it.

> On 28 March 2015 at 22:06, Paul Bolle <pebolle@tiscali.nl>
> wrote:
> > On Sat, 2015-03-28 at 22:55 +0100, Pali Rohár wrote:
> >> I have tested compilation only with out of tree make
> >> command with manual CONFIG_SENSORS_DELL_SMM and CONFIG_I8K
> >> options.
> > 
> > Not sure what you mean here. I can't get things past this
> > error:
> >     drivers/hwmon/Kconfig:1721: syntax error
> >     drivers/hwmon/Kconfig:1720: invalid option
> 
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -1705,8 +1705,8 @@
> 
>  config SENSORS_DELL_SMM
>      tristate "Dell laptop SMM BIOS hwmon driver"
> -    depends ON X86
> -    ---help---
> +    depends on X86
> +    help
>        This hwmon driver adds support for reporting
> temperature of different sensors and controls the fans on
> Dell laptops via System Management Mode provided by Dell
> BIOS.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 22:20         ` Guenter Roeck
@ 2015-03-28 22:44           ` Paul Bolle
  2015-03-29  0:55             ` Guenter Roeck
  0 siblings, 1 reply; 40+ messages in thread
From: Paul Bolle @ 2015-03-28 22:44 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Pali Rohár, Steven Honeyman, Arnd Bergmann,
	Greg Kroah-Hartman, Jean Delvare, Valdis Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

Guenter Roeck schreef op za 28-03-2015 om 15:20 [-0700]:
> Maybe that is the plan, but it is not what is happening.
> I8K selects SENSORS_DELL_SMM, so a boolean I8K forces SENSORS_DELL_SMM
> to be built into the kernel.
> 
> What you suggest would work if I8K would depend on SENSORS_DELL_SMM,
> but then the symbols would not be backward-compatible.

I haven't actually looked into this part of the patch, since as far as
I'm concerned this patch is broken.

Having said that, if using a second Kconfig symbol is worth the effort,
perhaps there's also enough reason to break Kconfig backward
compatibility. People shouldn't alter Kconfig symbols just because they
can, but as far as I'm aware we're on the record to not guarantee
backward compatibility for Kconfig symbols.


Paul Bolle


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 22:44           ` Paul Bolle
@ 2015-03-29  0:55             ` Guenter Roeck
  2015-03-30  8:01               ` Paul Bolle
  0 siblings, 1 reply; 40+ messages in thread
From: Guenter Roeck @ 2015-03-29  0:55 UTC (permalink / raw)
  To: Paul Bolle
  Cc: Pali Rohár, Steven Honeyman, Arnd Bergmann,
	Greg Kroah-Hartman, Jean Delvare, Valdis Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

On 03/28/2015 03:44 PM, Paul Bolle wrote:
> Guenter Roeck schreef op za 28-03-2015 om 15:20 [-0700]:
>> Maybe that is the plan, but it is not what is happening.
>> I8K selects SENSORS_DELL_SMM, so a boolean I8K forces SENSORS_DELL_SMM
>> to be built into the kernel.
>>
>> What you suggest would work if I8K would depend on SENSORS_DELL_SMM,
>> but then the symbols would not be backward-compatible.
>
> I haven't actually looked into this part of the patch, since as far as
> I'm concerned this patch is broken.
>

You mean beyond the "ON" and the above problem ? Can you elaborate ?

Guenter


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-03-28 10:24 [PATCH 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
  2015-03-28 10:24 ` [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
  2015-03-28 10:24 ` [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
@ 2015-03-29 12:57 ` Pali Rohár
  2015-03-29 12:57   ` [PATCH v2 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
                     ` (2 more replies)
  2 siblings, 3 replies; 40+ messages in thread
From: Pali Rohár @ 2015-03-29 12:57 UTC (permalink / raw)
  To: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors, Pali Rohár

This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
and allows to compile hwmon driver without /proc/i8k file.

Pali Rohár (2):
  hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k

 MAINTAINERS                    |    4 +-
 arch/x86/Kconfig               |   25 +-
 drivers/char/Makefile          |    1 -
 drivers/char/i8k.c             | 1005 ---------------------------------------
 drivers/hwmon/Kconfig          |   11 +
 drivers/hwmon/Makefile         |    1 +
 drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
 7 files changed, 1049 insertions(+), 1023 deletions(-)
 delete mode 100644 drivers/char/i8k.c
 create mode 100644 drivers/hwmon/dell-smm-hwmon.c

-- 
1.7.9.5


^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v2 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  2015-03-29 12:57 ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
@ 2015-03-29 12:57   ` Pali Rohár
  2015-03-29 12:57   ` [PATCH v2 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
  2015-03-31  3:32   ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Guenter Roeck
  2 siblings, 0 replies; 40+ messages in thread
From: Pali Rohár @ 2015-03-29 12:57 UTC (permalink / raw)
  To: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors, Pali Rohár

This commit moves i8k driver to hwmon tree under name dell-smm-hwmon which is
better name then abbreviation i8k. For backward compatibility is added macro
MODULE_ALIAS("i8k") so modprobe will load driver also old name i8k. CONFIG_I8K
compile option was not changed.

This commit also adds me as maintainer of this new dell-smm-hwmon driver and
remove Guenter Roeck from list who is implicit maintainer all hwmon drivers.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
---
 MAINTAINERS                    |    4 +-
 drivers/char/Makefile          |    1 -
 drivers/char/i8k.c             | 1005 ---------------------------------------
 drivers/hwmon/Makefile         |    1 +
 drivers/hwmon/dell-smm-hwmon.c | 1007 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1010 insertions(+), 1008 deletions(-)
 delete mode 100644 drivers/char/i8k.c
 create mode 100644 drivers/hwmon/dell-smm-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 88c09ca..e54a07e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3063,9 +3063,9 @@ S:	Maintained
 F:	drivers/platform/x86/dell-laptop.c
 
 DELL LAPTOP SMM DRIVER
-M:	Guenter Roeck <linux@roeck-us.net>
+M:	Pali Rohár <pali.rohar@gmail.com>
 S:	Maintained
-F:	drivers/char/i8k.c
+F:	drivers/hwmon/dell-smm-hwmon.c
 F:	include/uapi/linux/i8k.h
 
 DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas)
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index d06cde26..1d9cf00 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -36,7 +36,6 @@ else
   obj-$(CONFIG_NVRAM)	+= nvram.o
 endif
 obj-$(CONFIG_TOSHIBA)		+= toshiba.o
-obj-$(CONFIG_I8K)		+= i8k.o
 obj-$(CONFIG_DS1620)		+= ds1620.o
 obj-$(CONFIG_HW_RANDOM)		+= hw_random/
 obj-$(CONFIG_PPDEV)		+= ppdev.o
diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c
deleted file mode 100644
index 24cc4ed..0000000
--- a/drivers/char/i8k.c
+++ /dev/null
@@ -1,1005 +0,0 @@
-/*
- * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
- *
- * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
- *
- * Hwmon integration:
- * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
- * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
- * Copyright (C) 2014  Pali Rohár <pali.rohar@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/delay.h>
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/init.h>
-#include <linux/proc_fs.h>
-#include <linux/seq_file.h>
-#include <linux/dmi.h>
-#include <linux/capability.h>
-#include <linux/mutex.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/uaccess.h>
-#include <linux/io.h>
-#include <linux/sched.h>
-
-#include <linux/i8k.h>
-
-#define I8K_SMM_FN_STATUS	0x0025
-#define I8K_SMM_POWER_STATUS	0x0069
-#define I8K_SMM_SET_FAN		0x01a3
-#define I8K_SMM_GET_FAN		0x00a3
-#define I8K_SMM_GET_SPEED	0x02a3
-#define I8K_SMM_GET_FAN_TYPE	0x03a3
-#define I8K_SMM_GET_NOM_SPEED	0x04a3
-#define I8K_SMM_GET_TEMP	0x10a3
-#define I8K_SMM_GET_TEMP_TYPE	0x11a3
-#define I8K_SMM_GET_DELL_SIG1	0xfea3
-#define I8K_SMM_GET_DELL_SIG2	0xffa3
-
-#define I8K_FAN_MULT		30
-#define I8K_FAN_MAX_RPM		30000
-#define I8K_MAX_TEMP		127
-
-#define I8K_FN_NONE		0x00
-#define I8K_FN_UP		0x01
-#define I8K_FN_DOWN		0x02
-#define I8K_FN_MUTE		0x04
-#define I8K_FN_MASK		0x07
-#define I8K_FN_SHIFT		8
-
-#define I8K_POWER_AC		0x05
-#define I8K_POWER_BATTERY	0x01
-
-static DEFINE_MUTEX(i8k_mutex);
-static char bios_version[4];
-static struct device *i8k_hwmon_dev;
-static u32 i8k_hwmon_flags;
-static uint i8k_fan_mult = I8K_FAN_MULT;
-static uint i8k_pwm_mult;
-static uint i8k_fan_max = I8K_FAN_HIGH;
-
-#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
-#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
-#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
-#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
-#define I8K_HWMON_HAVE_FAN1	(1 << 4)
-#define I8K_HWMON_HAVE_FAN2	(1 << 5)
-
-MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
-MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
-MODULE_LICENSE("GPL");
-
-static bool force;
-module_param(force, bool, 0);
-MODULE_PARM_DESC(force, "Force loading without checking for supported models");
-
-static bool ignore_dmi;
-module_param(ignore_dmi, bool, 0);
-MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
-
-static bool restricted;
-module_param(restricted, bool, 0);
-MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
-
-static bool power_status;
-module_param(power_status, bool, 0600);
-MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
-
-static uint fan_mult;
-module_param(fan_mult, uint, 0);
-MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
-
-static uint fan_max;
-module_param(fan_max, uint, 0);
-MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
-
-static int i8k_open_fs(struct inode *inode, struct file *file);
-static long i8k_ioctl(struct file *, unsigned int, unsigned long);
-
-static const struct file_operations i8k_fops = {
-	.owner		= THIS_MODULE,
-	.open		= i8k_open_fs,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= single_release,
-	.unlocked_ioctl	= i8k_ioctl,
-};
-
-struct smm_regs {
-	unsigned int eax;
-	unsigned int ebx __packed;
-	unsigned int ecx __packed;
-	unsigned int edx __packed;
-	unsigned int esi __packed;
-	unsigned int edi __packed;
-};
-
-static inline const char *i8k_get_dmi_data(int field)
-{
-	const char *dmi_data = dmi_get_system_info(field);
-
-	return dmi_data && *dmi_data ? dmi_data : "?";
-}
-
-/*
- * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
- */
-static int i8k_smm(struct smm_regs *regs)
-{
-	int rc;
-	int eax = regs->eax;
-	cpumask_var_t old_mask;
-
-	/* SMM requires CPU 0 */
-	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
-		return -ENOMEM;
-	cpumask_copy(old_mask, &current->cpus_allowed);
-	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
-	if (rc)
-		goto out;
-	if (smp_processor_id() != 0) {
-		rc = -EBUSY;
-		goto out;
-	}
-
-#if defined(CONFIG_X86_64)
-	asm volatile("pushq %%rax\n\t"
-		"movl 0(%%rax),%%edx\n\t"
-		"pushq %%rdx\n\t"
-		"movl 4(%%rax),%%ebx\n\t"
-		"movl 8(%%rax),%%ecx\n\t"
-		"movl 12(%%rax),%%edx\n\t"
-		"movl 16(%%rax),%%esi\n\t"
-		"movl 20(%%rax),%%edi\n\t"
-		"popq %%rax\n\t"
-		"out %%al,$0xb2\n\t"
-		"out %%al,$0x84\n\t"
-		"xchgq %%rax,(%%rsp)\n\t"
-		"movl %%ebx,4(%%rax)\n\t"
-		"movl %%ecx,8(%%rax)\n\t"
-		"movl %%edx,12(%%rax)\n\t"
-		"movl %%esi,16(%%rax)\n\t"
-		"movl %%edi,20(%%rax)\n\t"
-		"popq %%rdx\n\t"
-		"movl %%edx,0(%%rax)\n\t"
-		"pushfq\n\t"
-		"popq %%rax\n\t"
-		"andl $1,%%eax\n"
-		: "=a"(rc)
-		:    "a"(regs)
-		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
-#else
-	asm volatile("pushl %%eax\n\t"
-	    "movl 0(%%eax),%%edx\n\t"
-	    "push %%edx\n\t"
-	    "movl 4(%%eax),%%ebx\n\t"
-	    "movl 8(%%eax),%%ecx\n\t"
-	    "movl 12(%%eax),%%edx\n\t"
-	    "movl 16(%%eax),%%esi\n\t"
-	    "movl 20(%%eax),%%edi\n\t"
-	    "popl %%eax\n\t"
-	    "out %%al,$0xb2\n\t"
-	    "out %%al,$0x84\n\t"
-	    "xchgl %%eax,(%%esp)\n\t"
-	    "movl %%ebx,4(%%eax)\n\t"
-	    "movl %%ecx,8(%%eax)\n\t"
-	    "movl %%edx,12(%%eax)\n\t"
-	    "movl %%esi,16(%%eax)\n\t"
-	    "movl %%edi,20(%%eax)\n\t"
-	    "popl %%edx\n\t"
-	    "movl %%edx,0(%%eax)\n\t"
-	    "lahf\n\t"
-	    "shrl $8,%%eax\n\t"
-	    "andl $1,%%eax\n"
-	    : "=a"(rc)
-	    :    "a"(regs)
-	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
-#endif
-	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
-		rc = -EINVAL;
-
-out:
-	set_cpus_allowed_ptr(current, old_mask);
-	free_cpumask_var(old_mask);
-	return rc;
-}
-
-/*
- * Read the Fn key status.
- */
-static int i8k_get_fn_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
-	case I8K_FN_UP:
-		return I8K_VOL_UP;
-	case I8K_FN_DOWN:
-		return I8K_VOL_DOWN;
-	case I8K_FN_MUTE:
-		return I8K_VOL_MUTE;
-	default:
-		return 0;
-	}
-}
-
-/*
- * Read the power status.
- */
-static int i8k_get_power_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
-}
-
-/*
- * Read the fan status.
- */
-static int i8k_get_fan_status(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the fan speed in RPM.
- */
-static int i8k_get_fan_speed(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
-}
-
-/*
- * Read the fan type.
- */
-static int i8k_get_fan_type(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the fan nominal rpm for specific fan speed.
- */
-static int i8k_get_fan_nominal_speed(int fan, int speed)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
-
-	regs.ebx = (fan & 0xff) | (speed << 8);
-	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
-}
-
-/*
- * Set the fan speed (off, low, high). Returns the new fan status.
- */
-static int i8k_set_fan(int fan, int speed)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
-
-	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
-	regs.ebx = (fan & 0xff) | (speed << 8);
-
-	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
-}
-
-static int i8k_get_temp_type(int sensor)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
-
-	regs.ebx = sensor & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the cpu temperature.
- */
-static int _i8k_get_temp(int sensor)
-{
-	struct smm_regs regs = {
-		.eax = I8K_SMM_GET_TEMP,
-		.ebx = sensor & 0xff,
-	};
-
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-static int i8k_get_temp(int sensor)
-{
-	int temp = _i8k_get_temp(sensor);
-
-	/*
-	 * Sometimes the temperature sensor returns 0x99, which is out of range.
-	 * In this case we retry (once) before returning an error.
-	 # 1003655137 00000058 00005a4b
-	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
-	 # 1003655139 00000054 00005c52
-	 */
-	if (temp == 0x99) {
-		msleep(100);
-		temp = _i8k_get_temp(sensor);
-	}
-	/*
-	 * Return -ENODATA for all invalid temperatures.
-	 *
-	 * Known instances are the 0x99 value as seen above as well as
-	 * 0xc1 (193), which may be returned when trying to read the GPU
-	 * temperature if the system supports a GPU and it is currently
-	 * turned off.
-	 */
-	if (temp > I8K_MAX_TEMP)
-		return -ENODATA;
-
-	return temp;
-}
-
-static int i8k_get_dell_signature(int req_fn)
-{
-	struct smm_regs regs = { .eax = req_fn, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
-}
-
-static int
-i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
-{
-	int val = 0;
-	int speed;
-	unsigned char buff[16];
-	int __user *argp = (int __user *)arg;
-
-	if (!argp)
-		return -EINVAL;
-
-	switch (cmd) {
-	case I8K_BIOS_VERSION:
-		val = (bios_version[0] << 16) |
-				(bios_version[1] << 8) | bios_version[2];
-		break;
-
-	case I8K_MACHINE_ID:
-		memset(buff, 0, 16);
-		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-			sizeof(buff));
-		break;
-
-	case I8K_FN_STATUS:
-		val = i8k_get_fn_status();
-		break;
-
-	case I8K_POWER_STATUS:
-		val = i8k_get_power_status();
-		break;
-
-	case I8K_GET_TEMP:
-		val = i8k_get_temp(0);
-		break;
-
-	case I8K_GET_SPEED:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_get_fan_speed(val);
-		break;
-
-	case I8K_GET_FAN:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_get_fan_status(val);
-		break;
-
-	case I8K_SET_FAN:
-		if (restricted && !capable(CAP_SYS_ADMIN))
-			return -EPERM;
-
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		if (copy_from_user(&speed, argp + 1, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_set_fan(val, speed);
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
-	if (val < 0)
-		return val;
-
-	switch (cmd) {
-	case I8K_BIOS_VERSION:
-		if (copy_to_user(argp, &val, 4))
-			return -EFAULT;
-
-		break;
-	case I8K_MACHINE_ID:
-		if (copy_to_user(argp, buff, 16))
-			return -EFAULT;
-
-		break;
-	default:
-		if (copy_to_user(argp, &val, sizeof(int)))
-			return -EFAULT;
-
-		break;
-	}
-
-	return 0;
-}
-
-static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
-{
-	long ret;
-
-	mutex_lock(&i8k_mutex);
-	ret = i8k_ioctl_unlocked(fp, cmd, arg);
-	mutex_unlock(&i8k_mutex);
-
-	return ret;
-}
-
-/*
- * Print the information for /proc/i8k.
- */
-static int i8k_proc_show(struct seq_file *seq, void *offset)
-{
-	int fn_key, cpu_temp, ac_power;
-	int left_fan, right_fan, left_speed, right_speed;
-
-	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
-	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
-	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
-	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
-	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
-	fn_key		= i8k_get_fn_status();			/*   750 µs */
-	if (power_status)
-		ac_power = i8k_get_power_status();		/* 14700 µs */
-	else
-		ac_power = -1;
-
-	/*
-	 * Info:
-	 *
-	 * 1)  Format version (this will change if format changes)
-	 * 2)  BIOS version
-	 * 3)  BIOS machine ID
-	 * 4)  Cpu temperature
-	 * 5)  Left fan status
-	 * 6)  Right fan status
-	 * 7)  Left fan speed
-	 * 8)  Right fan speed
-	 * 9)  AC power
-	 * 10) Fn Key status
-	 */
-	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
-			  I8K_PROC_FMT,
-			  bios_version,
-			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-			  cpu_temp,
-			  left_fan, right_fan, left_speed, right_speed,
-			  ac_power, fn_key);
-}
-
-static int i8k_open_fs(struct inode *inode, struct file *file)
-{
-	return single_open(file, i8k_proc_show, NULL);
-}
-
-
-/*
- * Hwmon interface
- */
-
-static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
-					 struct device_attribute *devattr,
-					 char *buf)
-{
-	static const char * const labels[] = {
-		"CPU",
-		"GPU",
-		"SODIMM",
-		"Other",
-		"Ambient",
-		"Other",
-	};
-	int index = to_sensor_dev_attr(devattr)->index;
-	int type;
-
-	type = i8k_get_temp_type(index);
-	if (type < 0)
-		return type;
-	if (type >= ARRAY_SIZE(labels))
-		type = ARRAY_SIZE(labels) - 1;
-	return sprintf(buf, "%s\n", labels[type]);
-}
-
-static ssize_t i8k_hwmon_show_temp(struct device *dev,
-				   struct device_attribute *devattr,
-				   char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int temp;
-
-	temp = i8k_get_temp(index);
-	if (temp < 0)
-		return temp;
-	return sprintf(buf, "%d\n", temp * 1000);
-}
-
-static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
-					struct device_attribute *devattr,
-					char *buf)
-{
-	static const char * const labels[] = {
-		"Processor Fan",
-		"Motherboard Fan",
-		"Video Fan",
-		"Power Supply Fan",
-		"Chipset Fan",
-		"Other Fan",
-	};
-	int index = to_sensor_dev_attr(devattr)->index;
-	bool dock = false;
-	int type;
-
-	type = i8k_get_fan_type(index);
-	if (type < 0)
-		return type;
-
-	if (type & 0x10) {
-		dock = true;
-		type &= 0x0F;
-	}
-
-	if (type >= ARRAY_SIZE(labels))
-		type = (ARRAY_SIZE(labels) - 1);
-
-	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
-}
-
-static ssize_t i8k_hwmon_show_fan(struct device *dev,
-				  struct device_attribute *devattr,
-				  char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int fan_speed;
-
-	fan_speed = i8k_get_fan_speed(index);
-	if (fan_speed < 0)
-		return fan_speed;
-	return sprintf(buf, "%d\n", fan_speed);
-}
-
-static ssize_t i8k_hwmon_show_pwm(struct device *dev,
-				  struct device_attribute *devattr,
-				  char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int status;
-
-	status = i8k_get_fan_status(index);
-	if (status < 0)
-		return -EIO;
-	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
-}
-
-static ssize_t i8k_hwmon_set_pwm(struct device *dev,
-				 struct device_attribute *attr,
-				 const char *buf, size_t count)
-{
-	int index = to_sensor_dev_attr(attr)->index;
-	unsigned long val;
-	int err;
-
-	err = kstrtoul(buf, 10, &val);
-	if (err)
-		return err;
-	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
-
-	mutex_lock(&i8k_mutex);
-	err = i8k_set_fan(index, val);
-	mutex_unlock(&i8k_mutex);
-
-	return err < 0 ? -EIO : count;
-}
-
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
-static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  0);
-static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  2);
-static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
-static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  3);
-static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
-static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
-			  0);
-static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
-			  i8k_hwmon_set_pwm, 0);
-static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
-			  i8k_hwmon_set_pwm, 1);
-
-static struct attribute *i8k_attrs[] = {
-	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
-	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
-	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
-	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
-	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
-	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
-	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
-	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
-	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
-	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
-	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
-	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
-	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
-	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
-	NULL
-};
-
-static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
-			      int index)
-{
-	if (index >= 0 && index <= 1 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
-		return 0;
-	if (index >= 2 && index <= 3 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
-		return 0;
-	if (index >= 4 && index <= 5 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
-		return 0;
-	if (index >= 6 && index <= 7 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
-		return 0;
-	if (index >= 8 && index <= 10 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
-		return 0;
-	if (index >= 11 && index <= 13 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
-		return 0;
-
-	return attr->mode;
-}
-
-static const struct attribute_group i8k_group = {
-	.attrs = i8k_attrs,
-	.is_visible = i8k_is_visible,
-};
-__ATTRIBUTE_GROUPS(i8k);
-
-static int __init i8k_init_hwmon(void)
-{
-	int err;
-
-	i8k_hwmon_flags = 0;
-
-	/* CPU temperature attributes, if temperature type is OK */
-	err = i8k_get_temp_type(0);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
-	/* check for additional temperature sensors */
-	err = i8k_get_temp_type(1);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
-	err = i8k_get_temp_type(2);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
-	err = i8k_get_temp_type(3);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
-
-	/* First fan attributes, if fan type is OK */
-	err = i8k_get_fan_type(0);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
-
-	/* Second fan attributes, if fan type is OK */
-	err = i8k_get_fan_type(1);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
-
-	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
-							  i8k_groups);
-	if (IS_ERR(i8k_hwmon_dev)) {
-		err = PTR_ERR(i8k_hwmon_dev);
-		i8k_hwmon_dev = NULL;
-		pr_err("hwmon registration failed (%d)\n", err);
-		return err;
-	}
-	return 0;
-}
-
-struct i8k_config_data {
-	uint fan_mult;
-	uint fan_max;
-};
-
-enum i8k_configs {
-	DELL_LATITUDE_D520,
-	DELL_PRECISION_490,
-	DELL_STUDIO,
-	DELL_XPS,
-};
-
-static const struct i8k_config_data i8k_config_data[] = {
-	[DELL_LATITUDE_D520] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_TURBO,
-	},
-	[DELL_PRECISION_490] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_TURBO,
-	},
-	[DELL_STUDIO] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_HIGH,
-	},
-	[DELL_XPS] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_HIGH,
-	},
-};
-
-static struct dmi_system_id i8k_dmi_table[] __initdata = {
-	{
-		.ident = "Dell Inspiron",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
-		},
-	},
-	{
-		.ident = "Dell Latitude",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
-		},
-	},
-	{
-		.ident = "Dell Inspiron 2",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
-		},
-	},
-	{
-		.ident = "Dell Latitude D520",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
-	},
-	{
-		.ident = "Dell Latitude 2",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
-		},
-	},
-	{	/* UK Inspiron 6400  */
-		.ident = "Dell Inspiron 3",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
-		},
-	},
-	{
-		.ident = "Dell Inspiron 3",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
-		},
-	},
-	{
-		.ident = "Dell Precision 490",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME,
-				  "Precision WorkStation 490"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
-	},
-	{
-		.ident = "Dell Precision",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
-		},
-	},
-	{
-		.ident = "Dell Vostro",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
-		},
-	},
-	{
-		.ident = "Dell XPS421",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
-		},
-	},
-	{
-		.ident = "Dell Studio",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
-	},
-	{
-		.ident = "Dell XPS 13",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_XPS],
-	},
-	{
-		.ident = "Dell XPS M140",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_XPS],
-	},
-	{ }
-};
-
-MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
-
-/*
- * Probe for the presence of a supported laptop.
- */
-static int __init i8k_probe(void)
-{
-	const struct dmi_system_id *id;
-	int fan, ret;
-
-	/*
-	 * Get DMI information
-	 */
-	if (!dmi_check_system(i8k_dmi_table)) {
-		if (!ignore_dmi && !force)
-			return -ENODEV;
-
-		pr_info("not running on a supported Dell system.\n");
-		pr_info("vendor=%s, model=%s, version=%s\n",
-			i8k_get_dmi_data(DMI_SYS_VENDOR),
-			i8k_get_dmi_data(DMI_PRODUCT_NAME),
-			i8k_get_dmi_data(DMI_BIOS_VERSION));
-	}
-
-	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
-		sizeof(bios_version));
-
-	/*
-	 * Get SMM Dell signature
-	 */
-	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
-	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
-		pr_err("unable to get SMM Dell signature\n");
-		if (!force)
-			return -ENODEV;
-	}
-
-	/*
-	 * Set fan multiplier and maximal fan speed from dmi config
-	 * Values specified in module parameters override values from dmi
-	 */
-	id = dmi_first_match(i8k_dmi_table);
-	if (id && id->driver_data) {
-		const struct i8k_config_data *conf = id->driver_data;
-		if (!fan_mult && conf->fan_mult)
-			fan_mult = conf->fan_mult;
-		if (!fan_max && conf->fan_max)
-			fan_max = conf->fan_max;
-	}
-
-	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
-	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
-
-	if (!fan_mult) {
-		/*
-		 * Autodetect fan multiplier based on nominal rpm
-		 * If fan reports rpm value too high then set multiplier to 1
-		 */
-		for (fan = 0; fan < 2; ++fan) {
-			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
-			if (ret < 0)
-				continue;
-			if (ret > I8K_FAN_MAX_RPM)
-				i8k_fan_mult = 1;
-			break;
-		}
-	} else {
-		/* Fan multiplier was specified in module param or in dmi */
-		i8k_fan_mult = fan_mult;
-	}
-
-	return 0;
-}
-
-static int __init i8k_init(void)
-{
-	struct proc_dir_entry *proc_i8k;
-	int err;
-
-	/* Are we running on an supported laptop? */
-	if (i8k_probe())
-		return -ENODEV;
-
-	/* Register the proc entry */
-	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
-	if (!proc_i8k)
-		return -ENOENT;
-
-	err = i8k_init_hwmon();
-	if (err)
-		goto exit_remove_proc;
-
-	return 0;
-
- exit_remove_proc:
-	remove_proc_entry("i8k", NULL);
-	return err;
-}
-
-static void __exit i8k_exit(void)
-{
-	hwmon_device_unregister(i8k_hwmon_dev);
-	remove_proc_entry("i8k", NULL);
-}
-
-module_init(i8k_init);
-module_exit(i8k_exit);
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 6c94147..1c3e458 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -155,6 +155,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
+obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
new file mode 100644
index 0000000..2b04e4f
--- /dev/null
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -0,0 +1,1007 @@
+/*
+ * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
+ *
+ * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
+ *
+ * Hwmon integration:
+ * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
+ * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
+ * Copyright (C) 2014, 2015  Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/dmi.h>
+#include <linux/capability.h>
+#include <linux/mutex.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+
+#include <linux/i8k.h>
+
+#define I8K_SMM_FN_STATUS	0x0025
+#define I8K_SMM_POWER_STATUS	0x0069
+#define I8K_SMM_SET_FAN		0x01a3
+#define I8K_SMM_GET_FAN		0x00a3
+#define I8K_SMM_GET_SPEED	0x02a3
+#define I8K_SMM_GET_FAN_TYPE	0x03a3
+#define I8K_SMM_GET_NOM_SPEED	0x04a3
+#define I8K_SMM_GET_TEMP	0x10a3
+#define I8K_SMM_GET_TEMP_TYPE	0x11a3
+#define I8K_SMM_GET_DELL_SIG1	0xfea3
+#define I8K_SMM_GET_DELL_SIG2	0xffa3
+
+#define I8K_FAN_MULT		30
+#define I8K_FAN_MAX_RPM		30000
+#define I8K_MAX_TEMP		127
+
+#define I8K_FN_NONE		0x00
+#define I8K_FN_UP		0x01
+#define I8K_FN_DOWN		0x02
+#define I8K_FN_MUTE		0x04
+#define I8K_FN_MASK		0x07
+#define I8K_FN_SHIFT		8
+
+#define I8K_POWER_AC		0x05
+#define I8K_POWER_BATTERY	0x01
+
+static DEFINE_MUTEX(i8k_mutex);
+static char bios_version[4];
+static struct device *i8k_hwmon_dev;
+static u32 i8k_hwmon_flags;
+static uint i8k_fan_mult = I8K_FAN_MULT;
+static uint i8k_pwm_mult;
+static uint i8k_fan_max = I8K_FAN_HIGH;
+
+#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
+#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
+#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
+#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
+#define I8K_HWMON_HAVE_FAN1	(1 << 4)
+#define I8K_HWMON_HAVE_FAN2	(1 << 5)
+
+MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("i8k");
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force loading without checking for supported models");
+
+static bool ignore_dmi;
+module_param(ignore_dmi, bool, 0);
+MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
+
+static bool restricted;
+module_param(restricted, bool, 0);
+MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
+
+static bool power_status;
+module_param(power_status, bool, 0600);
+MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+
+static uint fan_mult;
+module_param(fan_mult, uint, 0);
+MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
+
+static uint fan_max;
+module_param(fan_max, uint, 0);
+MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
+
+static int i8k_open_fs(struct inode *inode, struct file *file);
+static long i8k_ioctl(struct file *, unsigned int, unsigned long);
+
+static const struct file_operations i8k_fops = {
+	.owner		= THIS_MODULE,
+	.open		= i8k_open_fs,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.unlocked_ioctl	= i8k_ioctl,
+};
+
+struct smm_regs {
+	unsigned int eax;
+	unsigned int ebx __packed;
+	unsigned int ecx __packed;
+	unsigned int edx __packed;
+	unsigned int esi __packed;
+	unsigned int edi __packed;
+};
+
+static inline const char *i8k_get_dmi_data(int field)
+{
+	const char *dmi_data = dmi_get_system_info(field);
+
+	return dmi_data && *dmi_data ? dmi_data : "?";
+}
+
+/*
+ * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
+ */
+static int i8k_smm(struct smm_regs *regs)
+{
+	int rc;
+	int eax = regs->eax;
+	cpumask_var_t old_mask;
+
+	/* SMM requires CPU 0 */
+	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
+		return -ENOMEM;
+	cpumask_copy(old_mask, &current->cpus_allowed);
+	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
+	if (rc)
+		goto out;
+	if (smp_processor_id() != 0) {
+		rc = -EBUSY;
+		goto out;
+	}
+
+#if defined(CONFIG_X86_64)
+	asm volatile("pushq %%rax\n\t"
+		"movl 0(%%rax),%%edx\n\t"
+		"pushq %%rdx\n\t"
+		"movl 4(%%rax),%%ebx\n\t"
+		"movl 8(%%rax),%%ecx\n\t"
+		"movl 12(%%rax),%%edx\n\t"
+		"movl 16(%%rax),%%esi\n\t"
+		"movl 20(%%rax),%%edi\n\t"
+		"popq %%rax\n\t"
+		"out %%al,$0xb2\n\t"
+		"out %%al,$0x84\n\t"
+		"xchgq %%rax,(%%rsp)\n\t"
+		"movl %%ebx,4(%%rax)\n\t"
+		"movl %%ecx,8(%%rax)\n\t"
+		"movl %%edx,12(%%rax)\n\t"
+		"movl %%esi,16(%%rax)\n\t"
+		"movl %%edi,20(%%rax)\n\t"
+		"popq %%rdx\n\t"
+		"movl %%edx,0(%%rax)\n\t"
+		"pushfq\n\t"
+		"popq %%rax\n\t"
+		"andl $1,%%eax\n"
+		: "=a"(rc)
+		:    "a"(regs)
+		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+#else
+	asm volatile("pushl %%eax\n\t"
+	    "movl 0(%%eax),%%edx\n\t"
+	    "push %%edx\n\t"
+	    "movl 4(%%eax),%%ebx\n\t"
+	    "movl 8(%%eax),%%ecx\n\t"
+	    "movl 12(%%eax),%%edx\n\t"
+	    "movl 16(%%eax),%%esi\n\t"
+	    "movl 20(%%eax),%%edi\n\t"
+	    "popl %%eax\n\t"
+	    "out %%al,$0xb2\n\t"
+	    "out %%al,$0x84\n\t"
+	    "xchgl %%eax,(%%esp)\n\t"
+	    "movl %%ebx,4(%%eax)\n\t"
+	    "movl %%ecx,8(%%eax)\n\t"
+	    "movl %%edx,12(%%eax)\n\t"
+	    "movl %%esi,16(%%eax)\n\t"
+	    "movl %%edi,20(%%eax)\n\t"
+	    "popl %%edx\n\t"
+	    "movl %%edx,0(%%eax)\n\t"
+	    "lahf\n\t"
+	    "shrl $8,%%eax\n\t"
+	    "andl $1,%%eax\n"
+	    : "=a"(rc)
+	    :    "a"(regs)
+	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+#endif
+	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+		rc = -EINVAL;
+
+out:
+	set_cpus_allowed_ptr(current, old_mask);
+	free_cpumask_var(old_mask);
+	return rc;
+}
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+	case I8K_FN_UP:
+		return I8K_VOL_UP;
+	case I8K_FN_DOWN:
+		return I8K_VOL_DOWN;
+	case I8K_FN_MUTE:
+		return I8K_VOL_MUTE;
+	default:
+		return 0;
+	}
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
+}
+
+/*
+ * Read the fan status.
+ */
+static int i8k_get_fan_status(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the fan speed in RPM.
+ */
+static int i8k_get_fan_speed(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+}
+
+/*
+ * Read the fan type.
+ */
+static int i8k_get_fan_type(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the fan nominal rpm for specific fan speed.
+ */
+static int i8k_get_fan_nominal_speed(int fan, int speed)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
+
+	regs.ebx = (fan & 0xff) | (speed << 8);
+	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+}
+
+/*
+ * Set the fan speed (off, low, high). Returns the new fan status.
+ */
+static int i8k_set_fan(int fan, int speed)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
+
+	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
+	regs.ebx = (fan & 0xff) | (speed << 8);
+
+	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
+}
+
+static int i8k_get_temp_type(int sensor)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
+
+	regs.ebx = sensor & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the cpu temperature.
+ */
+static int _i8k_get_temp(int sensor)
+{
+	struct smm_regs regs = {
+		.eax = I8K_SMM_GET_TEMP,
+		.ebx = sensor & 0xff,
+	};
+
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+static int i8k_get_temp(int sensor)
+{
+	int temp = _i8k_get_temp(sensor);
+
+	/*
+	 * Sometimes the temperature sensor returns 0x99, which is out of range.
+	 * In this case we retry (once) before returning an error.
+	 # 1003655137 00000058 00005a4b
+	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
+	 # 1003655139 00000054 00005c52
+	 */
+	if (temp == 0x99) {
+		msleep(100);
+		temp = _i8k_get_temp(sensor);
+	}
+	/*
+	 * Return -ENODATA for all invalid temperatures.
+	 *
+	 * Known instances are the 0x99 value as seen above as well as
+	 * 0xc1 (193), which may be returned when trying to read the GPU
+	 * temperature if the system supports a GPU and it is currently
+	 * turned off.
+	 */
+	if (temp > I8K_MAX_TEMP)
+		return -ENODATA;
+
+	return temp;
+}
+
+static int i8k_get_dell_signature(int req_fn)
+{
+	struct smm_regs regs = { .eax = req_fn, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
+}
+
+static int
+i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	int val = 0;
+	int speed;
+	unsigned char buff[16];
+	int __user *argp = (int __user *)arg;
+
+	if (!argp)
+		return -EINVAL;
+
+	switch (cmd) {
+	case I8K_BIOS_VERSION:
+		val = (bios_version[0] << 16) |
+				(bios_version[1] << 8) | bios_version[2];
+		break;
+
+	case I8K_MACHINE_ID:
+		memset(buff, 0, 16);
+		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+			sizeof(buff));
+		break;
+
+	case I8K_FN_STATUS:
+		val = i8k_get_fn_status();
+		break;
+
+	case I8K_POWER_STATUS:
+		val = i8k_get_power_status();
+		break;
+
+	case I8K_GET_TEMP:
+		val = i8k_get_temp(0);
+		break;
+
+	case I8K_GET_SPEED:
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_get_fan_speed(val);
+		break;
+
+	case I8K_GET_FAN:
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_get_fan_status(val);
+		break;
+
+	case I8K_SET_FAN:
+		if (restricted && !capable(CAP_SYS_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		if (copy_from_user(&speed, argp + 1, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_set_fan(val, speed);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (val < 0)
+		return val;
+
+	switch (cmd) {
+	case I8K_BIOS_VERSION:
+		if (copy_to_user(argp, &val, 4))
+			return -EFAULT;
+
+		break;
+	case I8K_MACHINE_ID:
+		if (copy_to_user(argp, buff, 16))
+			return -EFAULT;
+
+		break;
+	default:
+		if (copy_to_user(argp, &val, sizeof(int)))
+			return -EFAULT;
+
+		break;
+	}
+
+	return 0;
+}
+
+static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	long ret;
+
+	mutex_lock(&i8k_mutex);
+	ret = i8k_ioctl_unlocked(fp, cmd, arg);
+	mutex_unlock(&i8k_mutex);
+
+	return ret;
+}
+
+/*
+ * Print the information for /proc/i8k.
+ */
+static int i8k_proc_show(struct seq_file *seq, void *offset)
+{
+	int fn_key, cpu_temp, ac_power;
+	int left_fan, right_fan, left_speed, right_speed;
+
+	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
+	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
+	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
+	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
+	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
+	fn_key		= i8k_get_fn_status();			/*   750 µs */
+	if (power_status)
+		ac_power = i8k_get_power_status();		/* 14700 µs */
+	else
+		ac_power = -1;
+
+	/*
+	 * Info:
+	 *
+	 * 1)  Format version (this will change if format changes)
+	 * 2)  BIOS version
+	 * 3)  BIOS machine ID
+	 * 4)  Cpu temperature
+	 * 5)  Left fan status
+	 * 6)  Right fan status
+	 * 7)  Left fan speed
+	 * 8)  Right fan speed
+	 * 9)  AC power
+	 * 10) Fn Key status
+	 */
+	return seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
+			  I8K_PROC_FMT,
+			  bios_version,
+			  i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+			  cpu_temp,
+			  left_fan, right_fan, left_speed, right_speed,
+			  ac_power, fn_key);
+}
+
+static int i8k_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, i8k_proc_show, NULL);
+}
+
+
+/*
+ * Hwmon interface
+ */
+
+static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
+					 struct device_attribute *devattr,
+					 char *buf)
+{
+	static const char * const labels[] = {
+		"CPU",
+		"GPU",
+		"SODIMM",
+		"Other",
+		"Ambient",
+		"Other",
+	};
+	int index = to_sensor_dev_attr(devattr)->index;
+	int type;
+
+	type = i8k_get_temp_type(index);
+	if (type < 0)
+		return type;
+	if (type >= ARRAY_SIZE(labels))
+		type = ARRAY_SIZE(labels) - 1;
+	return sprintf(buf, "%s\n", labels[type]);
+}
+
+static ssize_t i8k_hwmon_show_temp(struct device *dev,
+				   struct device_attribute *devattr,
+				   char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int temp;
+
+	temp = i8k_get_temp(index);
+	if (temp < 0)
+		return temp;
+	return sprintf(buf, "%d\n", temp * 1000);
+}
+
+static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
+					struct device_attribute *devattr,
+					char *buf)
+{
+	static const char * const labels[] = {
+		"Processor Fan",
+		"Motherboard Fan",
+		"Video Fan",
+		"Power Supply Fan",
+		"Chipset Fan",
+		"Other Fan",
+	};
+	int index = to_sensor_dev_attr(devattr)->index;
+	bool dock = false;
+	int type;
+
+	type = i8k_get_fan_type(index);
+	if (type < 0)
+		return type;
+
+	if (type & 0x10) {
+		dock = true;
+		type &= 0x0F;
+	}
+
+	if (type >= ARRAY_SIZE(labels))
+		type = (ARRAY_SIZE(labels) - 1);
+
+	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
+}
+
+static ssize_t i8k_hwmon_show_fan(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int fan_speed;
+
+	fan_speed = i8k_get_fan_speed(index);
+	if (fan_speed < 0)
+		return fan_speed;
+	return sprintf(buf, "%d\n", fan_speed);
+}
+
+static ssize_t i8k_hwmon_show_pwm(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int status;
+
+	status = i8k_get_fan_status(index);
+	if (status < 0)
+		return -EIO;
+	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
+}
+
+static ssize_t i8k_hwmon_set_pwm(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(attr)->index;
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err)
+		return err;
+	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
+
+	mutex_lock(&i8k_mutex);
+	err = i8k_set_fan(index, val);
+	mutex_unlock(&i8k_mutex);
+
+	return err < 0 ? -EIO : count;
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  2);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  3);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
+			  0);
+static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
+			  i8k_hwmon_set_pwm, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
+			  i8k_hwmon_set_pwm, 1);
+
+static struct attribute *i8k_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
+	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
+	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
+	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
+	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
+	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
+	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
+	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
+	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
+	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
+	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
+	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
+	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
+	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
+	NULL
+};
+
+static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
+			      int index)
+{
+	if (index >= 0 && index <= 1 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
+		return 0;
+	if (index >= 2 && index <= 3 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
+		return 0;
+	if (index >= 4 && index <= 5 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
+		return 0;
+	if (index >= 6 && index <= 7 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
+		return 0;
+	if (index >= 8 && index <= 10 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
+		return 0;
+	if (index >= 11 && index <= 13 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
+		return 0;
+
+	return attr->mode;
+}
+
+static const struct attribute_group i8k_group = {
+	.attrs = i8k_attrs,
+	.is_visible = i8k_is_visible,
+};
+__ATTRIBUTE_GROUPS(i8k);
+
+static int __init i8k_init_hwmon(void)
+{
+	int err;
+
+	i8k_hwmon_flags = 0;
+
+	/* CPU temperature attributes, if temperature type is OK */
+	err = i8k_get_temp_type(0);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
+	/* check for additional temperature sensors */
+	err = i8k_get_temp_type(1);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
+	err = i8k_get_temp_type(2);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
+	err = i8k_get_temp_type(3);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
+
+	/* First fan attributes, if fan type is OK */
+	err = i8k_get_fan_type(0);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
+
+	/* Second fan attributes, if fan type is OK */
+	err = i8k_get_fan_type(1);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
+
+	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
+							  i8k_groups);
+	if (IS_ERR(i8k_hwmon_dev)) {
+		err = PTR_ERR(i8k_hwmon_dev);
+		i8k_hwmon_dev = NULL;
+		pr_err("hwmon registration failed (%d)\n", err);
+		return err;
+	}
+	return 0;
+}
+
+struct i8k_config_data {
+	uint fan_mult;
+	uint fan_max;
+};
+
+enum i8k_configs {
+	DELL_LATITUDE_D520,
+	DELL_PRECISION_490,
+	DELL_STUDIO,
+	DELL_XPS,
+};
+
+static const struct i8k_config_data i8k_config_data[] = {
+	[DELL_LATITUDE_D520] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_TURBO,
+	},
+	[DELL_PRECISION_490] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_TURBO,
+	},
+	[DELL_STUDIO] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_HIGH,
+	},
+	[DELL_XPS] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_HIGH,
+	},
+};
+
+static struct dmi_system_id i8k_dmi_table[] __initdata = {
+	{
+		.ident = "Dell Inspiron",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+		},
+	},
+	{
+		.ident = "Dell Latitude",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
+		},
+	},
+	{
+		.ident = "Dell Inspiron 2",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+		},
+	},
+	{
+		.ident = "Dell Latitude D520",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
+	},
+	{
+		.ident = "Dell Latitude 2",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
+		},
+	},
+	{	/* UK Inspiron 6400  */
+		.ident = "Dell Inspiron 3",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
+		},
+	},
+	{
+		.ident = "Dell Inspiron 3",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
+		},
+	},
+	{
+		.ident = "Dell Precision 490",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME,
+				  "Precision WorkStation 490"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
+	},
+	{
+		.ident = "Dell Precision",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
+		},
+	},
+	{
+		.ident = "Dell Vostro",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
+		},
+	},
+	{
+		.ident = "Dell XPS421",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
+		},
+	},
+	{
+		.ident = "Dell Studio",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
+	},
+	{
+		.ident = "Dell XPS 13",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_XPS],
+	},
+	{
+		.ident = "Dell XPS M140",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_XPS],
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static int __init i8k_probe(void)
+{
+	const struct dmi_system_id *id;
+	int fan, ret;
+
+	/*
+	 * Get DMI information
+	 */
+	if (!dmi_check_system(i8k_dmi_table)) {
+		if (!ignore_dmi && !force)
+			return -ENODEV;
+
+		pr_info("not running on a supported Dell system.\n");
+		pr_info("vendor=%s, model=%s, version=%s\n",
+			i8k_get_dmi_data(DMI_SYS_VENDOR),
+			i8k_get_dmi_data(DMI_PRODUCT_NAME),
+			i8k_get_dmi_data(DMI_BIOS_VERSION));
+	}
+
+	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
+		sizeof(bios_version));
+
+	/*
+	 * Get SMM Dell signature
+	 */
+	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
+	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
+		pr_err("unable to get SMM Dell signature\n");
+		if (!force)
+			return -ENODEV;
+	}
+
+	/*
+	 * Set fan multiplier and maximal fan speed from dmi config
+	 * Values specified in module parameters override values from dmi
+	 */
+	id = dmi_first_match(i8k_dmi_table);
+	if (id && id->driver_data) {
+		const struct i8k_config_data *conf = id->driver_data;
+		if (!fan_mult && conf->fan_mult)
+			fan_mult = conf->fan_mult;
+		if (!fan_max && conf->fan_max)
+			fan_max = conf->fan_max;
+	}
+
+	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
+	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
+
+	if (!fan_mult) {
+		/*
+		 * Autodetect fan multiplier based on nominal rpm
+		 * If fan reports rpm value too high then set multiplier to 1
+		 */
+		for (fan = 0; fan < 2; ++fan) {
+			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
+			if (ret < 0)
+				continue;
+			if (ret > I8K_FAN_MAX_RPM)
+				i8k_fan_mult = 1;
+			break;
+		}
+	} else {
+		/* Fan multiplier was specified in module param or in dmi */
+		i8k_fan_mult = fan_mult;
+	}
+
+	return 0;
+}
+
+static int __init i8k_init(void)
+{
+	struct proc_dir_entry *proc_i8k;
+	int err;
+
+	/* Are we running on an supported laptop? */
+	if (i8k_probe())
+		return -ENODEV;
+
+	/* Register the proc entry */
+	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
+	if (!proc_i8k)
+		return -ENOENT;
+
+	err = i8k_init_hwmon();
+	if (err)
+		goto exit_remove_proc;
+
+	return 0;
+
+ exit_remove_proc:
+	remove_proc_entry("i8k", NULL);
+	return err;
+}
+
+static void __exit i8k_exit(void)
+{
+	hwmon_device_unregister(i8k_hwmon_dev);
+	remove_proc_entry("i8k", NULL);
+}
+
+module_init(i8k_init);
+module_exit(i8k_exit);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v2 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-29 12:57 ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
  2015-03-29 12:57   ` [PATCH v2 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
@ 2015-03-29 12:57   ` Pali Rohár
  2015-04-28 12:38     ` Greg Kroah-Hartman
  2015-03-31  3:32   ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Guenter Roeck
  2 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-03-29 12:57 UTC (permalink / raw)
  To: Guenter Roeck, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors, Pali Rohár

This patch splits CONFIG_I8K compile option to SENSORS_DELL_SMM and CONFIG_I8K.
Option SENSORS_DELL_SMM is now used to enable compilation of dell-smm-hwmon
driver and old CONFIG_I8K option to enable /proc/i8k interface in driver.

So this change allows to compile dell-smm-hwmon driver without legacy /proc/i8k
interface which is needed only for old Dell Inspirion models or for userspace
i8kutils package.

For backward compatibility when CONFIG_I8K is enabled then also SENSORS_DELL_SMM
is enabled and so driver dell-smm-hwmon (with /proc/i8k) is compiled.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
---
 arch/x86/Kconfig               |   25 +++----
 drivers/hwmon/Kconfig          |   11 +++
 drivers/hwmon/Makefile         |    2 +-
 drivers/hwmon/dell-smm-hwmon.c |  150 ++++++++++++++++++++++------------------
 4 files changed, 106 insertions(+), 82 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index b7d31ca..0f73afd 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1063,24 +1063,19 @@ config TOSHIBA
 	  Say N otherwise.
 
 config I8K
-	tristate "Dell laptop support"
+	tristate "Dell i8k legacy laptop support"
 	select HWMON
+	select SENSORS_DELL_SMM
 	---help---
-	  This adds a driver to safely access the System Management Mode
-	  of the CPU on the Dell Inspiron 8000. The System Management Mode
-	  is used to read cpu temperature and cooling fan status and to
-	  control the fans on the I8K portables.
+	  This option enables legacy /proc/i8k userspace interface in hwmon
+	  dell-smm-hwmon driver. Character file /proc/i8k reports bios version,
+	  temperature and allows controlling fan speeds of Dell laptops via
+	  System Management Mode. For old Dell laptops (like Dell Inspiron 8000)
+	  it reports also power and hotkey status. For fan speed control is
+	  needed userspace package i8kutils.
 
-	  This driver has been tested only on the Inspiron 8000 but it may
-	  also work with other Dell laptops. You can force loading on other
-	  models by passing the parameter `force=1' to the module. Use at
-	  your own risk.
-
-	  For information on utilities to make use of this driver see the
-	  I8K Linux utilities web site at:
-	  <http://people.debian.org/~dz/i8k/>
-
-	  Say Y if you intend to run this kernel on a Dell Inspiron 8000.
+	  Say Y if you intend to run this kernel on old Dell laptops or want to
+	  use userspace package i8kutils.
 	  Say N otherwise.
 
 config X86_REBOOTFIXUPS
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 110fade..9090790 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -371,6 +371,17 @@ config SENSORS_DS1621
 	  This driver can also be built as a module.  If so, the module
 	  will be called ds1621.
 
+config SENSORS_DELL_SMM
+	tristate "Dell laptop SMM BIOS hwmon driver"
+	depends on X86 && DMI
+	help
+	  This hwmon driver adds support for reporting temperature of different
+	  sensors and controls the fans on Dell laptops via System Management
+	  Mode provided by Dell BIOS.
+
+	  When option I8K is also enabled this driver provides legacy /proc/i8k
+	  userspace interface for i8kutils package.
+
 config SENSORS_DA9052_ADC
 	tristate "Dialog DA9052/DA9053 ADC"
 	depends on PMIC_DA9052
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 1c3e458..89a00e9 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
 obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
+obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
@@ -155,7 +156,6 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
-obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 2b04e4f..378793a 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -81,7 +81,7 @@ static uint i8k_fan_max = I8K_FAN_HIGH;
 
 MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
 MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
-MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("i8k");
 
@@ -93,6 +93,7 @@ static bool ignore_dmi;
 module_param(ignore_dmi, bool, 0);
 MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
 
+#if IS_ENABLED(CONFIG_I8K)
 static bool restricted;
 module_param(restricted, bool, 0);
 MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
@@ -100,6 +101,7 @@ MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
 static bool power_status;
 module_param(power_status, bool, 0600);
 MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+#endif
 
 static uint fan_mult;
 module_param(fan_mult, uint, 0);
@@ -109,18 +111,6 @@ static uint fan_max;
 module_param(fan_max, uint, 0);
 MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
 
-static int i8k_open_fs(struct inode *inode, struct file *file);
-static long i8k_ioctl(struct file *, unsigned int, unsigned long);
-
-static const struct file_operations i8k_fops = {
-	.owner		= THIS_MODULE,
-	.open		= i8k_open_fs,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= single_release,
-	.unlocked_ioctl	= i8k_ioctl,
-};
-
 struct smm_regs {
 	unsigned int eax;
 	unsigned int ebx __packed;
@@ -221,45 +211,6 @@ out:
 }
 
 /*
- * Read the Fn key status.
- */
-static int i8k_get_fn_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
-	case I8K_FN_UP:
-		return I8K_VOL_UP;
-	case I8K_FN_DOWN:
-		return I8K_VOL_DOWN;
-	case I8K_FN_MUTE:
-		return I8K_VOL_MUTE;
-	default:
-		return 0;
-	}
-}
-
-/*
- * Read the power status.
- */
-static int i8k_get_power_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
-}
-
-/*
  * Read the fan status.
  */
 static int i8k_get_fan_status(int fan)
@@ -378,6 +329,51 @@ static int i8k_get_dell_signature(int req_fn)
 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
 }
 
+#if IS_ENABLED(CONFIG_I8K)
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+	case I8K_FN_UP:
+		return I8K_VOL_UP;
+	case I8K_FN_DOWN:
+		return I8K_VOL_DOWN;
+	case I8K_FN_MUTE:
+		return I8K_VOL_MUTE;
+	default:
+		return 0;
+	}
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
+}
+
+/*
+ * Procfs interface
+ */
+
 static int
 i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
 {
@@ -526,6 +522,37 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
 	return single_open(file, i8k_proc_show, NULL);
 }
 
+static const struct file_operations i8k_fops = {
+	.owner		= THIS_MODULE,
+	.open		= i8k_open_fs,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.unlocked_ioctl	= i8k_ioctl,
+};
+
+static void __init i8k_init_procfs(void)
+{
+	/* Register the proc entry */
+	proc_create("i8k", 0, NULL, &i8k_fops);
+}
+
+static void __exit i8k_exit_procfs(void)
+{
+	remove_proc_entry("i8k", NULL);
+}
+
+#else
+
+static inline void __init i8k_init_procfs(void)
+{
+}
+
+static inline void __exit i8k_exit_procfs(void)
+{
+}
+
+#endif
 
 /*
  * Hwmon interface
@@ -748,8 +775,8 @@ static int __init i8k_init_hwmon(void)
 	if (err >= 0)
 		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
 
-	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
-							  i8k_groups);
+	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell-smm",
+							  NULL, i8k_groups);
 	if (IS_ERR(i8k_hwmon_dev)) {
 		err = PTR_ERR(i8k_hwmon_dev);
 		i8k_hwmon_dev = NULL;
@@ -974,33 +1001,24 @@ static int __init i8k_probe(void)
 
 static int __init i8k_init(void)
 {
-	struct proc_dir_entry *proc_i8k;
 	int err;
 
 	/* Are we running on an supported laptop? */
 	if (i8k_probe())
 		return -ENODEV;
 
-	/* Register the proc entry */
-	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
-	if (!proc_i8k)
-		return -ENOENT;
-
 	err = i8k_init_hwmon();
 	if (err)
-		goto exit_remove_proc;
+		return err;
 
+	i8k_init_procfs();
 	return 0;
-
- exit_remove_proc:
-	remove_proc_entry("i8k", NULL);
-	return err;
 }
 
 static void __exit i8k_exit(void)
 {
 	hwmon_device_unregister(i8k_hwmon_dev);
-	remove_proc_entry("i8k", NULL);
+	i8k_exit_procfs();
 }
 
 module_init(i8k_init);
-- 
1.7.9.5


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-28 22:33           ` Pali Rohár
@ 2015-03-30  7:44             ` Jean Delvare
  0 siblings, 0 replies; 40+ messages in thread
From: Jean Delvare @ 2015-03-30  7:44 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Steven Honeyman, Paul Bolle, Guenter Roeck, Arnd Bergmann,
	Greg Kroah-Hartman, Valdis Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

On Sat, 28 Mar 2015 23:33:46 +0100, Pali Rohár wrote:
> I think it would be better to locate on which bus are sensors 
> connected (smbus? isa? pci?), find out HW chips and wrote native 
> kernel drivers for them...
> 
> I scanned smbus (intel controller connected on pci) on my E6440 
> but there is no additional/unknown device. I have no idea where 
> else could be sensor device connected and accessible (from SMM or 
> kernel mode).

As much as I hate SMM, native access to a BIOS-owned device is not a
good idea, as is it dangerously racy. If anything, we are currently
trying to move _away_ from native drivers on a number of systems
(basically all systems where acpi_enforce_resources=lax is needed.)

On x86, ideally ACPI would offer a standard interface to the hardware
monitoring chip and the OS would need a single driver for all boards
out there. Failing that, ACPI should provide a clean and safe way to
access the chip's registers (read: some mutex to avoid concurrent
access to the registers by the BIOS and the OS.)

-- 
Jean Delvare
SUSE L3 Support

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-29  0:55             ` Guenter Roeck
@ 2015-03-30  8:01               ` Paul Bolle
  0 siblings, 0 replies; 40+ messages in thread
From: Paul Bolle @ 2015-03-30  8:01 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Pali Rohár, Steven Honeyman, Arnd Bergmann,
	Greg Kroah-Hartman, Jean Delvare, Valdis Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

On Sat, 2015-03-28 at 17:55 -0700, Guenter Roeck wrote:
> On 03/28/2015 03:44 PM, Paul Bolle wrote:
> > I haven't actually looked into this part of the patch, since as far as
> > I'm concerned this patch is broken.
> 
> You mean beyond the "ON" and the above problem ? Can you elaborate ?

No, I was talking about the ON typo. Because a patch that mainly juggles
Kconfig symbols but adds a Kconfig syntax error while doing that is a
broken patch.


Paul Bolle



^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-03-29 12:57 ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
  2015-03-29 12:57   ` [PATCH v2 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
  2015-03-29 12:57   ` [PATCH v2 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
@ 2015-03-31  3:32   ` Guenter Roeck
  2015-03-31 13:56     ` Greg Kroah-Hartman
  2 siblings, 1 reply; 40+ messages in thread
From: Guenter Roeck @ 2015-03-31  3:32 UTC (permalink / raw)
  To: Pali Rohár, Arnd Bergmann, Greg Kroah-Hartman, Jean Delvare
  Cc: Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger,
	Gabriele Mazzotta, linux-kernel, lm-sensors

On 03/29/2015 05:57 AM, Pali Rohár wrote:
> This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> and allows to compile hwmon driver without /proc/i8k file.
>
> Pali Rohár (2):
>    hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
>    hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
>
>   MAINTAINERS                    |    4 +-
>   arch/x86/Kconfig               |   25 +-
>   drivers/char/Makefile          |    1 -
>   drivers/char/i8k.c             | 1005 ---------------------------------------
>   drivers/hwmon/Kconfig          |   11 +
>   drivers/hwmon/Makefile         |    1 +
>   drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
>   7 files changed, 1049 insertions(+), 1023 deletions(-)
>   delete mode 100644 drivers/char/i8k.c
>   create mode 100644 drivers/hwmon/dell-smm-hwmon.c
>
I am ok with the series and would take it for 4.1 if Greg agrees.

Greg, any comments ?

Thanks,
Guenter


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-03-31  3:32   ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Guenter Roeck
@ 2015-03-31 13:56     ` Greg Kroah-Hartman
  2015-04-09 14:02       ` Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Greg Kroah-Hartman @ 2015-03-31 13:56 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Pali Rohár, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
> On 03/29/2015 05:57 AM, Pali Rohár wrote:
> >This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> >and allows to compile hwmon driver without /proc/i8k file.
> >
> >Pali Rohár (2):
> >   hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
> >   hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
> >
> >  MAINTAINERS                    |    4 +-
> >  arch/x86/Kconfig               |   25 +-
> >  drivers/char/Makefile          |    1 -
> >  drivers/char/i8k.c             | 1005 ---------------------------------------
> >  drivers/hwmon/Kconfig          |   11 +
> >  drivers/hwmon/Makefile         |    1 +
> >  drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
> >  7 files changed, 1049 insertions(+), 1023 deletions(-)
> >  delete mode 100644 drivers/char/i8k.c
> >  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> >
> I am ok with the series and would take it for 4.1 if Greg agrees.
> 
> Greg, any comments ?

Let me review it, it's in my queue...

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-03-31 13:56     ` Greg Kroah-Hartman
@ 2015-04-09 14:02       ` Pali Rohár
  2015-04-21 13:30         ` Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-04-09 14:02 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Guenter Roeck, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman wrote:
> On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
> > On 03/29/2015 05:57 AM, Pali Rohár wrote:
> > >This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> > >and allows to compile hwmon driver without /proc/i8k file.
> > >
> > >Pali Rohár (2):
> > >   hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
> > >   hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
> > >
> > >  MAINTAINERS                    |    4 +-
> > >  arch/x86/Kconfig               |   25 +-
> > >  drivers/char/Makefile          |    1 -
> > >  drivers/char/i8k.c             | 1005 ---------------------------------------
> > >  drivers/hwmon/Kconfig          |   11 +
> > >  drivers/hwmon/Makefile         |    1 +
> > >  drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
> > >  7 files changed, 1049 insertions(+), 1023 deletions(-)
> > >  delete mode 100644 drivers/char/i8k.c
> > >  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> > >
> > I am ok with the series and would take it for 4.1 if Greg agrees.
> > 
> > Greg, any comments ?
> 
> Let me review it, it's in my queue...

Hi Greg! Have you looked at it?

-- 
Pali Rohár
pali.rohar@gmail.com

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-04-09 14:02       ` Pali Rohár
@ 2015-04-21 13:30         ` Pali Rohár
  2015-04-21 13:40           ` Guenter Roeck
  0 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-04-21 13:30 UTC (permalink / raw)
  To: Greg Kroah-Hartman, Guenter Roeck
  Cc: Arnd Bergmann, Jean Delvare, Steven Honeyman, Valdis.Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

On Thursday 09 April 2015 16:02:24 Pali Rohár wrote:
> On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman wrote:
> > On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
> > > On 03/29/2015 05:57 AM, Pali Rohár wrote:
> > > >This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> > > >and allows to compile hwmon driver without /proc/i8k file.
> > > >
> > > >Pali Rohár (2):
> > > >   hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
> > > >   hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
> > > >
> > > >  MAINTAINERS                    |    4 +-
> > > >  arch/x86/Kconfig               |   25 +-
> > > >  drivers/char/Makefile          |    1 -
> > > >  drivers/char/i8k.c             | 1005 ---------------------------------------
> > > >  drivers/hwmon/Kconfig          |   11 +
> > > >  drivers/hwmon/Makefile         |    1 +
> > > >  drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
> > > >  7 files changed, 1049 insertions(+), 1023 deletions(-)
> > > >  delete mode 100644 drivers/char/i8k.c
> > > >  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> > > >
> > > I am ok with the series and would take it for 4.1 if Greg agrees.
> > > 
> > > Greg, any comments ?
> > 
> > Let me review it, it's in my queue...
> 
> Hi Greg! Have you looked at it?
> 

Greg & Guenter: ping. Will you include it into 4.1?

-- 
Pali Rohár
pali.rohar@gmail.com

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-04-21 13:30         ` Pali Rohár
@ 2015-04-21 13:40           ` Guenter Roeck
  2015-04-21 13:52             ` Greg Kroah-Hartman
  0 siblings, 1 reply; 40+ messages in thread
From: Guenter Roeck @ 2015-04-21 13:40 UTC (permalink / raw)
  To: Pali Rohár, Greg Kroah-Hartman
  Cc: Arnd Bergmann, Jean Delvare, Steven Honeyman, Valdis.Kletnieks,
	Jochen Eisinger, Gabriele Mazzotta, linux-kernel, lm-sensors

On 04/21/2015 06:30 AM, Pali Rohár wrote:
> On Thursday 09 April 2015 16:02:24 Pali Rohár wrote:
>> On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman wrote:
>>> On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
>>>> On 03/29/2015 05:57 AM, Pali Rohár wrote:
>>>>> This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
>>>>> and allows to compile hwmon driver without /proc/i8k file.
>>>>>
>>>>> Pali Rohár (2):
>>>>>    hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
>>>>>    hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
>>>>>
>>>>>   MAINTAINERS                    |    4 +-
>>>>>   arch/x86/Kconfig               |   25 +-
>>>>>   drivers/char/Makefile          |    1 -
>>>>>   drivers/char/i8k.c             | 1005 ---------------------------------------
>>>>>   drivers/hwmon/Kconfig          |   11 +
>>>>>   drivers/hwmon/Makefile         |    1 +
>>>>>   drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
>>>>>   7 files changed, 1049 insertions(+), 1023 deletions(-)
>>>>>   delete mode 100644 drivers/char/i8k.c
>>>>>   create mode 100644 drivers/hwmon/dell-smm-hwmon.c
>>>>>
>>>> I am ok with the series and would take it for 4.1 if Greg agrees.
>>>>
>>>> Greg, any comments ?
>>>
>>> Let me review it, it's in my queue...
>>
>> Hi Greg! Have you looked at it?
>>
>
> Greg & Guenter: ping. Will you include it into 4.1?
>
I don't see it in Greg's pull request, so presumably no (unless I missed it).

Guenter


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-04-21 13:40           ` Guenter Roeck
@ 2015-04-21 13:52             ` Greg Kroah-Hartman
  2015-04-21 15:24               ` Guenter Roeck
  0 siblings, 1 reply; 40+ messages in thread
From: Greg Kroah-Hartman @ 2015-04-21 13:52 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Pali Rohár, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Tue, Apr 21, 2015 at 06:40:57AM -0700, Guenter Roeck wrote:
> On 04/21/2015 06:30 AM, Pali Rohár wrote:
> >On Thursday 09 April 2015 16:02:24 Pali Rohár wrote:
> >>On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman wrote:
> >>>On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
> >>>>On 03/29/2015 05:57 AM, Pali Rohár wrote:
> >>>>>This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> >>>>>and allows to compile hwmon driver without /proc/i8k file.
> >>>>>
> >>>>>Pali Rohár (2):
> >>>>>   hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
> >>>>>   hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
> >>>>>
> >>>>>  MAINTAINERS                    |    4 +-
> >>>>>  arch/x86/Kconfig               |   25 +-
> >>>>>  drivers/char/Makefile          |    1 -
> >>>>>  drivers/char/i8k.c             | 1005 ---------------------------------------
> >>>>>  drivers/hwmon/Kconfig          |   11 +
> >>>>>  drivers/hwmon/Makefile         |    1 +
> >>>>>  drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
> >>>>>  7 files changed, 1049 insertions(+), 1023 deletions(-)
> >>>>>  delete mode 100644 drivers/char/i8k.c
> >>>>>  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> >>>>>
> >>>>I am ok with the series and would take it for 4.1 if Greg agrees.
> >>>>
> >>>>Greg, any comments ?
> >>>
> >>>Let me review it, it's in my queue...
> >>
> >>Hi Greg! Have you looked at it?
> >>
> >
> >Greg & Guenter: ping. Will you include it into 4.1?
> >
> I don't see it in Greg's pull request, so presumably no (unless I missed it).

No, I missed it for this rc cycle, sorry.  If you want to include it in
yours, by all means please do so.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-04-21 13:52             ` Greg Kroah-Hartman
@ 2015-04-21 15:24               ` Guenter Roeck
  2015-04-21 15:30                 ` Greg Kroah-Hartman
  0 siblings, 1 reply; 40+ messages in thread
From: Guenter Roeck @ 2015-04-21 15:24 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Pali Rohár, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Tue, Apr 21, 2015 at 03:52:06PM +0200, Greg Kroah-Hartman wrote:
> On Tue, Apr 21, 2015 at 06:40:57AM -0700, Guenter Roeck wrote:
> > On 04/21/2015 06:30 AM, Pali Rohár wrote:
> > >On Thursday 09 April 2015 16:02:24 Pali Rohár wrote:
> > >>On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman wrote:
> > >>>On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
> > >>>>On 03/29/2015 05:57 AM, Pali Rohár wrote:
> > >>>>>This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> > >>>>>and allows to compile hwmon driver without /proc/i8k file.
> > >>>>>
> > >>>>>Pali Rohár (2):
> > >>>>>   hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
> > >>>>>   hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
> > >>>>>
> > >>>>>  MAINTAINERS                    |    4 +-
> > >>>>>  arch/x86/Kconfig               |   25 +-
> > >>>>>  drivers/char/Makefile          |    1 -
> > >>>>>  drivers/char/i8k.c             | 1005 ---------------------------------------
> > >>>>>  drivers/hwmon/Kconfig          |   11 +
> > >>>>>  drivers/hwmon/Makefile         |    1 +
> > >>>>>  drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
> > >>>>>  7 files changed, 1049 insertions(+), 1023 deletions(-)
> > >>>>>  delete mode 100644 drivers/char/i8k.c
> > >>>>>  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> > >>>>>
> > >>>>I am ok with the series and would take it for 4.1 if Greg agrees.
> > >>>>
> > >>>>Greg, any comments ?
> > >>>
> > >>>Let me review it, it's in my queue...
> > >>
> > >>Hi Greg! Have you looked at it?
> > >>
> > >
> > >Greg & Guenter: ping. Will you include it into 4.1?
> > >
> > I don't see it in Greg's pull request, so presumably no (unless I missed it).
> 
> No, I missed it for this rc cycle, sorry.  If you want to include it in
> yours, by all means please do so.
> 
Too late; I sent my pull request early last week. Also, since I don't have
the patch in my queue, it would not follow my own process, and I really
dislike doing that.

Guenter

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-04-21 15:24               ` Guenter Roeck
@ 2015-04-21 15:30                 ` Greg Kroah-Hartman
  2015-04-27 18:39                   ` Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Greg Kroah-Hartman @ 2015-04-21 15:30 UTC (permalink / raw)
  To: Guenter Roeck
  Cc: Pali Rohár, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Tue, Apr 21, 2015 at 08:24:00AM -0700, Guenter Roeck wrote:
> On Tue, Apr 21, 2015 at 03:52:06PM +0200, Greg Kroah-Hartman wrote:
> > On Tue, Apr 21, 2015 at 06:40:57AM -0700, Guenter Roeck wrote:
> > > On 04/21/2015 06:30 AM, Pali Rohár wrote:
> > > >On Thursday 09 April 2015 16:02:24 Pali Rohár wrote:
> > > >>On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman wrote:
> > > >>>On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter Roeck wrote:
> > > >>>>On 03/29/2015 05:57 AM, Pali Rohár wrote:
> > > >>>>>This patch series moves drivers/char/i8k.c to drivers/hwmon/dell-smm-hwmon.c
> > > >>>>>and allows to compile hwmon driver without /proc/i8k file.
> > > >>>>>
> > > >>>>>Pali Rohár (2):
> > > >>>>>   hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
> > > >>>>>   hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
> > > >>>>>
> > > >>>>>  MAINTAINERS                    |    4 +-
> > > >>>>>  arch/x86/Kconfig               |   25 +-
> > > >>>>>  drivers/char/Makefile          |    1 -
> > > >>>>>  drivers/char/i8k.c             | 1005 ---------------------------------------
> > > >>>>>  drivers/hwmon/Kconfig          |   11 +
> > > >>>>>  drivers/hwmon/Makefile         |    1 +
> > > >>>>>  drivers/hwmon/dell-smm-hwmon.c | 1025 ++++++++++++++++++++++++++++++++++++++++
> > > >>>>>  7 files changed, 1049 insertions(+), 1023 deletions(-)
> > > >>>>>  delete mode 100644 drivers/char/i8k.c
> > > >>>>>  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> > > >>>>>
> > > >>>>I am ok with the series and would take it for 4.1 if Greg agrees.
> > > >>>>
> > > >>>>Greg, any comments ?
> > > >>>
> > > >>>Let me review it, it's in my queue...
> > > >>
> > > >>Hi Greg! Have you looked at it?
> > > >>
> > > >
> > > >Greg & Guenter: ping. Will you include it into 4.1?
> > > >
> > > I don't see it in Greg's pull request, so presumably no (unless I missed it).
> > 
> > No, I missed it for this rc cycle, sorry.  If you want to include it in
> > yours, by all means please do so.
> > 
> Too late; I sent my pull request early last week. Also, since I don't have
> the patch in my queue, it would not follow my own process, and I really
> dislike doing that.

Ok, as this is "just" a rename, I'll see about doing it for -rc2, that
way we avoid the merge issues.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c
  2015-04-21 15:30                 ` Greg Kroah-Hartman
@ 2015-04-27 18:39                   ` Pali Rohár
  0 siblings, 0 replies; 40+ messages in thread
From: Pali Rohár @ 2015-04-27 18:39 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Guenter Roeck, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 2778 bytes --]

On Tuesday 21 April 2015 17:30:16 Greg Kroah-Hartman wrote:
> On Tue, Apr 21, 2015 at 08:24:00AM -0700, Guenter Roeck wrote:
> > On Tue, Apr 21, 2015 at 03:52:06PM +0200, Greg Kroah-Hartman
> > wrote:
> > > On Tue, Apr 21, 2015 at 06:40:57AM -0700, Guenter Roeck
> > > wrote:
> > > > On 04/21/2015 06:30 AM, Pali Rohár wrote:
> > > > >On Thursday 09 April 2015 16:02:24 Pali Rohár wrote:
> > > > >>On Tuesday 31 March 2015 15:56:01 Greg Kroah-Hartman
> > > > >>wrote:
> > > > >>>On Mon, Mar 30, 2015 at 08:32:36PM -0700, Guenter
> > > > >>>Roeck wrote:
> > > > >>>>On 03/29/2015 05:57 AM, Pali Rohár wrote:
> > > > >>>>>This patch series moves drivers/char/i8k.c to
> > > > >>>>>drivers/hwmon/dell-smm-hwmon.c and allows to
> > > > >>>>>compile hwmon driver without /proc/i8k file.
> > > > >>>>>
> > > > >>>>>Pali Rohár (2):
> > > > >>>>>   hwmon: Rename i8k driver to dell-smm-hwmon and
> > > > >>>>>   move it to hwmon tree hwmon: Allow to compile
> > > > >>>>>   dell-smm-hwmon driver without /proc/i8k
> > > > >>>>>  
> > > > >>>>>  MAINTAINERS                    |    4 +-
> > > > >>>>>  arch/x86/Kconfig               |   25 +-
> > > > >>>>>  drivers/char/Makefile          |    1 -
> > > > >>>>>  drivers/char/i8k.c             | 1005
> > > > >>>>>  ---------------------------------------
> > > > >>>>>  drivers/hwmon/Kconfig          |   11 +
> > > > >>>>>  drivers/hwmon/Makefile         |    1 +
> > > > >>>>>  drivers/hwmon/dell-smm-hwmon.c | 1025
> > > > >>>>>  ++++++++++++++++++++++++++++++++++++++++ 7 files
> > > > >>>>>  changed, 1049 insertions(+), 1023 deletions(-)
> > > > >>>>>  delete mode 100644 drivers/char/i8k.c
> > > > >>>>>  create mode 100644 drivers/hwmon/dell-smm-hwmon.c
> > > > >>>>
> > > > >>>>I am ok with the series and would take it for 4.1 if
> > > > >>>>Greg agrees.
> > > > >>>>
> > > > >>>>Greg, any comments ?
> > > > >>>
> > > > >>>Let me review it, it's in my queue...
> > > > >>
> > > > >>Hi Greg! Have you looked at it?
> > > > >
> > > > >Greg & Guenter: ping. Will you include it into 4.1?
> > > > 
> > > > I don't see it in Greg's pull request, so presumably no
> > > > (unless I missed it).
> > > 
> > > No, I missed it for this rc cycle, sorry.  If you want to
> > > include it in yours, by all means please do so.
> > 
> > Too late; I sent my pull request early last week. Also,
> > since I don't have the patch in my queue, it would not
> > follow my own process, and I really dislike doing that.
> 
> Ok, as this is "just" a rename, I'll see about doing it for
> -rc2, that way we avoid the merge issues.
> 
> thanks,
> 
> greg k-h

Hi Greg, -rc1 was released. Do not forget to include it for -rc2 
pull request.

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v2 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-03-29 12:57   ` [PATCH v2 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
@ 2015-04-28 12:38     ` Greg Kroah-Hartman
  2015-04-29 11:41       ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Greg Kroah-Hartman @ 2015-04-28 12:38 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Guenter Roeck, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Sun, Mar 29, 2015 at 02:57:39PM +0200, Pali Rohár wrote:
> This patch splits CONFIG_I8K compile option to SENSORS_DELL_SMM and CONFIG_I8K.
> Option SENSORS_DELL_SMM is now used to enable compilation of dell-smm-hwmon
> driver and old CONFIG_I8K option to enable /proc/i8k interface in driver.
> 
> So this change allows to compile dell-smm-hwmon driver without legacy /proc/i8k
> interface which is needed only for old Dell Inspirion models or for userspace
> i8kutils package.
> 
> For backward compatibility when CONFIG_I8K is enabled then also SENSORS_DELL_SMM
> is enabled and so driver dell-smm-hwmon (with /proc/i8k) is compiled.
> 
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
> ---
>  arch/x86/Kconfig               |   25 +++----
>  drivers/hwmon/Kconfig          |   11 +++
>  drivers/hwmon/Makefile         |    2 +-
>  drivers/hwmon/dell-smm-hwmon.c |  150 ++++++++++++++++++++++------------------
>  4 files changed, 106 insertions(+), 82 deletions(-)

This doesn't apply to 4.1-rc1, so I can't apply this series now.  Can
you respin it and resend?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 40+ messages in thread

* [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  2015-04-28 12:38     ` Greg Kroah-Hartman
@ 2015-04-29 11:41       ` Pali Rohár
  2015-04-29 11:41         ` [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
  2015-04-29 12:07         ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Greg Kroah-Hartman
  0 siblings, 2 replies; 40+ messages in thread
From: Pali Rohár @ 2015-04-29 11:41 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Guenter Roeck, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors, Pali Rohár

This commit moves i8k driver to hwmon tree under name dell-smm-hwmon which is
better name then abbreviation i8k. For backward compatibility is added macro
MODULE_ALIAS("i8k") so modprobe will load driver also old name i8k. CONFIG_I8K
compile option was not changed.

This commit also adds me as maintainer of this new dell-smm-hwmon driver and
remove Guenter Roeck from list who is implicit maintainer all hwmon drivers.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
---
 MAINTAINERS                    |    4 +-
 drivers/char/Makefile          |    1 -
 drivers/char/i8k.c             | 1007 ---------------------------------------
 drivers/hwmon/Makefile         |    1 +
 drivers/hwmon/dell-smm-hwmon.c | 1009 ++++++++++++++++++++++++++++++++++++++++
 5 files changed, 1012 insertions(+), 1010 deletions(-)
 delete mode 100644 drivers/char/i8k.c
 create mode 100644 drivers/hwmon/dell-smm-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 2e5bbc0..014c15f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -3077,9 +3077,9 @@ S:	Maintained
 F:	drivers/platform/x86/dell-smo8800.c
 
 DELL LAPTOP SMM DRIVER
-M:	Guenter Roeck <linux@roeck-us.net>
+M:	Pali Rohár <pali.rohar@gmail.com>
 S:	Maintained
-F:	drivers/char/i8k.c
+F:	drivers/hwmon/dell-smm-hwmon.c
 F:	include/uapi/linux/i8k.h
 
 DELL SYSTEMS MANAGEMENT BASE DRIVER (dcdbas)
diff --git a/drivers/char/Makefile b/drivers/char/Makefile
index d06cde26..1d9cf00 100644
--- a/drivers/char/Makefile
+++ b/drivers/char/Makefile
@@ -36,7 +36,6 @@ else
   obj-$(CONFIG_NVRAM)	+= nvram.o
 endif
 obj-$(CONFIG_TOSHIBA)		+= toshiba.o
-obj-$(CONFIG_I8K)		+= i8k.o
 obj-$(CONFIG_DS1620)		+= ds1620.o
 obj-$(CONFIG_HW_RANDOM)		+= hw_random/
 obj-$(CONFIG_PPDEV)		+= ppdev.o
diff --git a/drivers/char/i8k.c b/drivers/char/i8k.c
deleted file mode 100644
index a43048b..0000000
--- a/drivers/char/i8k.c
+++ /dev/null
@@ -1,1007 +0,0 @@
-/*
- * i8k.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
- *
- * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
- *
- * Hwmon integration:
- * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
- * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
- * Copyright (C) 2014  Pali Rohár <pali.rohar@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2, or (at your option) any
- * later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * General Public License for more details.
- */
-
-#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
-
-#include <linux/delay.h>
-#include <linux/module.h>
-#include <linux/types.h>
-#include <linux/init.h>
-#include <linux/proc_fs.h>
-#include <linux/seq_file.h>
-#include <linux/dmi.h>
-#include <linux/capability.h>
-#include <linux/mutex.h>
-#include <linux/hwmon.h>
-#include <linux/hwmon-sysfs.h>
-#include <linux/uaccess.h>
-#include <linux/io.h>
-#include <linux/sched.h>
-
-#include <linux/i8k.h>
-
-#define I8K_SMM_FN_STATUS	0x0025
-#define I8K_SMM_POWER_STATUS	0x0069
-#define I8K_SMM_SET_FAN		0x01a3
-#define I8K_SMM_GET_FAN		0x00a3
-#define I8K_SMM_GET_SPEED	0x02a3
-#define I8K_SMM_GET_FAN_TYPE	0x03a3
-#define I8K_SMM_GET_NOM_SPEED	0x04a3
-#define I8K_SMM_GET_TEMP	0x10a3
-#define I8K_SMM_GET_TEMP_TYPE	0x11a3
-#define I8K_SMM_GET_DELL_SIG1	0xfea3
-#define I8K_SMM_GET_DELL_SIG2	0xffa3
-
-#define I8K_FAN_MULT		30
-#define I8K_FAN_MAX_RPM		30000
-#define I8K_MAX_TEMP		127
-
-#define I8K_FN_NONE		0x00
-#define I8K_FN_UP		0x01
-#define I8K_FN_DOWN		0x02
-#define I8K_FN_MUTE		0x04
-#define I8K_FN_MASK		0x07
-#define I8K_FN_SHIFT		8
-
-#define I8K_POWER_AC		0x05
-#define I8K_POWER_BATTERY	0x01
-
-static DEFINE_MUTEX(i8k_mutex);
-static char bios_version[4];
-static struct device *i8k_hwmon_dev;
-static u32 i8k_hwmon_flags;
-static uint i8k_fan_mult = I8K_FAN_MULT;
-static uint i8k_pwm_mult;
-static uint i8k_fan_max = I8K_FAN_HIGH;
-
-#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
-#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
-#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
-#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
-#define I8K_HWMON_HAVE_FAN1	(1 << 4)
-#define I8K_HWMON_HAVE_FAN2	(1 << 5)
-
-MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
-MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
-MODULE_LICENSE("GPL");
-
-static bool force;
-module_param(force, bool, 0);
-MODULE_PARM_DESC(force, "Force loading without checking for supported models");
-
-static bool ignore_dmi;
-module_param(ignore_dmi, bool, 0);
-MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
-
-static bool restricted;
-module_param(restricted, bool, 0);
-MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
-
-static bool power_status;
-module_param(power_status, bool, 0600);
-MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
-
-static uint fan_mult;
-module_param(fan_mult, uint, 0);
-MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
-
-static uint fan_max;
-module_param(fan_max, uint, 0);
-MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
-
-static int i8k_open_fs(struct inode *inode, struct file *file);
-static long i8k_ioctl(struct file *, unsigned int, unsigned long);
-
-static const struct file_operations i8k_fops = {
-	.owner		= THIS_MODULE,
-	.open		= i8k_open_fs,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= single_release,
-	.unlocked_ioctl	= i8k_ioctl,
-};
-
-struct smm_regs {
-	unsigned int eax;
-	unsigned int ebx __packed;
-	unsigned int ecx __packed;
-	unsigned int edx __packed;
-	unsigned int esi __packed;
-	unsigned int edi __packed;
-};
-
-static inline const char *i8k_get_dmi_data(int field)
-{
-	const char *dmi_data = dmi_get_system_info(field);
-
-	return dmi_data && *dmi_data ? dmi_data : "?";
-}
-
-/*
- * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
- */
-static int i8k_smm(struct smm_regs *regs)
-{
-	int rc;
-	int eax = regs->eax;
-	cpumask_var_t old_mask;
-
-	/* SMM requires CPU 0 */
-	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
-		return -ENOMEM;
-	cpumask_copy(old_mask, &current->cpus_allowed);
-	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
-	if (rc)
-		goto out;
-	if (smp_processor_id() != 0) {
-		rc = -EBUSY;
-		goto out;
-	}
-
-#if defined(CONFIG_X86_64)
-	asm volatile("pushq %%rax\n\t"
-		"movl 0(%%rax),%%edx\n\t"
-		"pushq %%rdx\n\t"
-		"movl 4(%%rax),%%ebx\n\t"
-		"movl 8(%%rax),%%ecx\n\t"
-		"movl 12(%%rax),%%edx\n\t"
-		"movl 16(%%rax),%%esi\n\t"
-		"movl 20(%%rax),%%edi\n\t"
-		"popq %%rax\n\t"
-		"out %%al,$0xb2\n\t"
-		"out %%al,$0x84\n\t"
-		"xchgq %%rax,(%%rsp)\n\t"
-		"movl %%ebx,4(%%rax)\n\t"
-		"movl %%ecx,8(%%rax)\n\t"
-		"movl %%edx,12(%%rax)\n\t"
-		"movl %%esi,16(%%rax)\n\t"
-		"movl %%edi,20(%%rax)\n\t"
-		"popq %%rdx\n\t"
-		"movl %%edx,0(%%rax)\n\t"
-		"pushfq\n\t"
-		"popq %%rax\n\t"
-		"andl $1,%%eax\n"
-		: "=a"(rc)
-		:    "a"(regs)
-		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
-#else
-	asm volatile("pushl %%eax\n\t"
-	    "movl 0(%%eax),%%edx\n\t"
-	    "push %%edx\n\t"
-	    "movl 4(%%eax),%%ebx\n\t"
-	    "movl 8(%%eax),%%ecx\n\t"
-	    "movl 12(%%eax),%%edx\n\t"
-	    "movl 16(%%eax),%%esi\n\t"
-	    "movl 20(%%eax),%%edi\n\t"
-	    "popl %%eax\n\t"
-	    "out %%al,$0xb2\n\t"
-	    "out %%al,$0x84\n\t"
-	    "xchgl %%eax,(%%esp)\n\t"
-	    "movl %%ebx,4(%%eax)\n\t"
-	    "movl %%ecx,8(%%eax)\n\t"
-	    "movl %%edx,12(%%eax)\n\t"
-	    "movl %%esi,16(%%eax)\n\t"
-	    "movl %%edi,20(%%eax)\n\t"
-	    "popl %%edx\n\t"
-	    "movl %%edx,0(%%eax)\n\t"
-	    "lahf\n\t"
-	    "shrl $8,%%eax\n\t"
-	    "andl $1,%%eax\n"
-	    : "=a"(rc)
-	    :    "a"(regs)
-	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
-#endif
-	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
-		rc = -EINVAL;
-
-out:
-	set_cpus_allowed_ptr(current, old_mask);
-	free_cpumask_var(old_mask);
-	return rc;
-}
-
-/*
- * Read the Fn key status.
- */
-static int i8k_get_fn_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
-	case I8K_FN_UP:
-		return I8K_VOL_UP;
-	case I8K_FN_DOWN:
-		return I8K_VOL_DOWN;
-	case I8K_FN_MUTE:
-		return I8K_VOL_MUTE;
-	default:
-		return 0;
-	}
-}
-
-/*
- * Read the power status.
- */
-static int i8k_get_power_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
-}
-
-/*
- * Read the fan status.
- */
-static int i8k_get_fan_status(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the fan speed in RPM.
- */
-static int i8k_get_fan_speed(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
-}
-
-/*
- * Read the fan type.
- */
-static int i8k_get_fan_type(int fan)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
-
-	regs.ebx = fan & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the fan nominal rpm for specific fan speed.
- */
-static int i8k_get_fan_nominal_speed(int fan, int speed)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
-
-	regs.ebx = (fan & 0xff) | (speed << 8);
-	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
-}
-
-/*
- * Set the fan speed (off, low, high). Returns the new fan status.
- */
-static int i8k_set_fan(int fan, int speed)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
-
-	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
-	regs.ebx = (fan & 0xff) | (speed << 8);
-
-	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
-}
-
-static int i8k_get_temp_type(int sensor)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
-
-	regs.ebx = sensor & 0xff;
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-/*
- * Read the cpu temperature.
- */
-static int _i8k_get_temp(int sensor)
-{
-	struct smm_regs regs = {
-		.eax = I8K_SMM_GET_TEMP,
-		.ebx = sensor & 0xff,
-	};
-
-	return i8k_smm(&regs) ? : regs.eax & 0xff;
-}
-
-static int i8k_get_temp(int sensor)
-{
-	int temp = _i8k_get_temp(sensor);
-
-	/*
-	 * Sometimes the temperature sensor returns 0x99, which is out of range.
-	 * In this case we retry (once) before returning an error.
-	 # 1003655137 00000058 00005a4b
-	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
-	 # 1003655139 00000054 00005c52
-	 */
-	if (temp == 0x99) {
-		msleep(100);
-		temp = _i8k_get_temp(sensor);
-	}
-	/*
-	 * Return -ENODATA for all invalid temperatures.
-	 *
-	 * Known instances are the 0x99 value as seen above as well as
-	 * 0xc1 (193), which may be returned when trying to read the GPU
-	 * temperature if the system supports a GPU and it is currently
-	 * turned off.
-	 */
-	if (temp > I8K_MAX_TEMP)
-		return -ENODATA;
-
-	return temp;
-}
-
-static int i8k_get_dell_signature(int req_fn)
-{
-	struct smm_regs regs = { .eax = req_fn, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
-}
-
-static int
-i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
-{
-	int val = 0;
-	int speed;
-	unsigned char buff[16];
-	int __user *argp = (int __user *)arg;
-
-	if (!argp)
-		return -EINVAL;
-
-	switch (cmd) {
-	case I8K_BIOS_VERSION:
-		val = (bios_version[0] << 16) |
-				(bios_version[1] << 8) | bios_version[2];
-		break;
-
-	case I8K_MACHINE_ID:
-		memset(buff, 0, 16);
-		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-			sizeof(buff));
-		break;
-
-	case I8K_FN_STATUS:
-		val = i8k_get_fn_status();
-		break;
-
-	case I8K_POWER_STATUS:
-		val = i8k_get_power_status();
-		break;
-
-	case I8K_GET_TEMP:
-		val = i8k_get_temp(0);
-		break;
-
-	case I8K_GET_SPEED:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_get_fan_speed(val);
-		break;
-
-	case I8K_GET_FAN:
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_get_fan_status(val);
-		break;
-
-	case I8K_SET_FAN:
-		if (restricted && !capable(CAP_SYS_ADMIN))
-			return -EPERM;
-
-		if (copy_from_user(&val, argp, sizeof(int)))
-			return -EFAULT;
-
-		if (copy_from_user(&speed, argp + 1, sizeof(int)))
-			return -EFAULT;
-
-		val = i8k_set_fan(val, speed);
-		break;
-
-	default:
-		return -EINVAL;
-	}
-
-	if (val < 0)
-		return val;
-
-	switch (cmd) {
-	case I8K_BIOS_VERSION:
-		if (copy_to_user(argp, &val, 4))
-			return -EFAULT;
-
-		break;
-	case I8K_MACHINE_ID:
-		if (copy_to_user(argp, buff, 16))
-			return -EFAULT;
-
-		break;
-	default:
-		if (copy_to_user(argp, &val, sizeof(int)))
-			return -EFAULT;
-
-		break;
-	}
-
-	return 0;
-}
-
-static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
-{
-	long ret;
-
-	mutex_lock(&i8k_mutex);
-	ret = i8k_ioctl_unlocked(fp, cmd, arg);
-	mutex_unlock(&i8k_mutex);
-
-	return ret;
-}
-
-/*
- * Print the information for /proc/i8k.
- */
-static int i8k_proc_show(struct seq_file *seq, void *offset)
-{
-	int fn_key, cpu_temp, ac_power;
-	int left_fan, right_fan, left_speed, right_speed;
-
-	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
-	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
-	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
-	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
-	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
-	fn_key		= i8k_get_fn_status();			/*   750 µs */
-	if (power_status)
-		ac_power = i8k_get_power_status();		/* 14700 µs */
-	else
-		ac_power = -1;
-
-	/*
-	 * Info:
-	 *
-	 * 1)  Format version (this will change if format changes)
-	 * 2)  BIOS version
-	 * 3)  BIOS machine ID
-	 * 4)  Cpu temperature
-	 * 5)  Left fan status
-	 * 6)  Right fan status
-	 * 7)  Left fan speed
-	 * 8)  Right fan speed
-	 * 9)  AC power
-	 * 10) Fn Key status
-	 */
-	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
-		   I8K_PROC_FMT,
-		   bios_version,
-		   i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
-		   cpu_temp,
-		   left_fan, right_fan, left_speed, right_speed,
-		   ac_power, fn_key);
-
-	return 0;
-}
-
-static int i8k_open_fs(struct inode *inode, struct file *file)
-{
-	return single_open(file, i8k_proc_show, NULL);
-}
-
-
-/*
- * Hwmon interface
- */
-
-static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
-					 struct device_attribute *devattr,
-					 char *buf)
-{
-	static const char * const labels[] = {
-		"CPU",
-		"GPU",
-		"SODIMM",
-		"Other",
-		"Ambient",
-		"Other",
-	};
-	int index = to_sensor_dev_attr(devattr)->index;
-	int type;
-
-	type = i8k_get_temp_type(index);
-	if (type < 0)
-		return type;
-	if (type >= ARRAY_SIZE(labels))
-		type = ARRAY_SIZE(labels) - 1;
-	return sprintf(buf, "%s\n", labels[type]);
-}
-
-static ssize_t i8k_hwmon_show_temp(struct device *dev,
-				   struct device_attribute *devattr,
-				   char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int temp;
-
-	temp = i8k_get_temp(index);
-	if (temp < 0)
-		return temp;
-	return sprintf(buf, "%d\n", temp * 1000);
-}
-
-static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
-					struct device_attribute *devattr,
-					char *buf)
-{
-	static const char * const labels[] = {
-		"Processor Fan",
-		"Motherboard Fan",
-		"Video Fan",
-		"Power Supply Fan",
-		"Chipset Fan",
-		"Other Fan",
-	};
-	int index = to_sensor_dev_attr(devattr)->index;
-	bool dock = false;
-	int type;
-
-	type = i8k_get_fan_type(index);
-	if (type < 0)
-		return type;
-
-	if (type & 0x10) {
-		dock = true;
-		type &= 0x0F;
-	}
-
-	if (type >= ARRAY_SIZE(labels))
-		type = (ARRAY_SIZE(labels) - 1);
-
-	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
-}
-
-static ssize_t i8k_hwmon_show_fan(struct device *dev,
-				  struct device_attribute *devattr,
-				  char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int fan_speed;
-
-	fan_speed = i8k_get_fan_speed(index);
-	if (fan_speed < 0)
-		return fan_speed;
-	return sprintf(buf, "%d\n", fan_speed);
-}
-
-static ssize_t i8k_hwmon_show_pwm(struct device *dev,
-				  struct device_attribute *devattr,
-				  char *buf)
-{
-	int index = to_sensor_dev_attr(devattr)->index;
-	int status;
-
-	status = i8k_get_fan_status(index);
-	if (status < 0)
-		return -EIO;
-	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
-}
-
-static ssize_t i8k_hwmon_set_pwm(struct device *dev,
-				 struct device_attribute *attr,
-				 const char *buf, size_t count)
-{
-	int index = to_sensor_dev_attr(attr)->index;
-	unsigned long val;
-	int err;
-
-	err = kstrtoul(buf, 10, &val);
-	if (err)
-		return err;
-	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
-
-	mutex_lock(&i8k_mutex);
-	err = i8k_set_fan(index, val);
-	mutex_unlock(&i8k_mutex);
-
-	return err < 0 ? -EIO : count;
-}
-
-static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
-static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  0);
-static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
-static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
-static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  2);
-static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
-static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
-			  3);
-static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
-static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
-			  0);
-static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
-			  i8k_hwmon_set_pwm, 0);
-static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
-			  1);
-static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
-			  i8k_hwmon_set_pwm, 1);
-
-static struct attribute *i8k_attrs[] = {
-	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
-	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
-	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
-	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
-	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
-	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
-	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
-	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
-	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
-	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
-	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
-	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
-	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
-	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
-	NULL
-};
-
-static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
-			      int index)
-{
-	if (index >= 0 && index <= 1 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
-		return 0;
-	if (index >= 2 && index <= 3 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
-		return 0;
-	if (index >= 4 && index <= 5 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
-		return 0;
-	if (index >= 6 && index <= 7 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
-		return 0;
-	if (index >= 8 && index <= 10 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
-		return 0;
-	if (index >= 11 && index <= 13 &&
-	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
-		return 0;
-
-	return attr->mode;
-}
-
-static const struct attribute_group i8k_group = {
-	.attrs = i8k_attrs,
-	.is_visible = i8k_is_visible,
-};
-__ATTRIBUTE_GROUPS(i8k);
-
-static int __init i8k_init_hwmon(void)
-{
-	int err;
-
-	i8k_hwmon_flags = 0;
-
-	/* CPU temperature attributes, if temperature type is OK */
-	err = i8k_get_temp_type(0);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
-	/* check for additional temperature sensors */
-	err = i8k_get_temp_type(1);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
-	err = i8k_get_temp_type(2);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
-	err = i8k_get_temp_type(3);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
-
-	/* First fan attributes, if fan type is OK */
-	err = i8k_get_fan_type(0);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
-
-	/* Second fan attributes, if fan type is OK */
-	err = i8k_get_fan_type(1);
-	if (err >= 0)
-		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
-
-	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
-							  i8k_groups);
-	if (IS_ERR(i8k_hwmon_dev)) {
-		err = PTR_ERR(i8k_hwmon_dev);
-		i8k_hwmon_dev = NULL;
-		pr_err("hwmon registration failed (%d)\n", err);
-		return err;
-	}
-	return 0;
-}
-
-struct i8k_config_data {
-	uint fan_mult;
-	uint fan_max;
-};
-
-enum i8k_configs {
-	DELL_LATITUDE_D520,
-	DELL_PRECISION_490,
-	DELL_STUDIO,
-	DELL_XPS,
-};
-
-static const struct i8k_config_data i8k_config_data[] = {
-	[DELL_LATITUDE_D520] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_TURBO,
-	},
-	[DELL_PRECISION_490] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_TURBO,
-	},
-	[DELL_STUDIO] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_HIGH,
-	},
-	[DELL_XPS] = {
-		.fan_mult = 1,
-		.fan_max = I8K_FAN_HIGH,
-	},
-};
-
-static struct dmi_system_id i8k_dmi_table[] __initdata = {
-	{
-		.ident = "Dell Inspiron",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
-		},
-	},
-	{
-		.ident = "Dell Latitude",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
-		},
-	},
-	{
-		.ident = "Dell Inspiron 2",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
-		},
-	},
-	{
-		.ident = "Dell Latitude D520",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
-	},
-	{
-		.ident = "Dell Latitude 2",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
-		},
-	},
-	{	/* UK Inspiron 6400  */
-		.ident = "Dell Inspiron 3",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
-		},
-	},
-	{
-		.ident = "Dell Inspiron 3",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
-		},
-	},
-	{
-		.ident = "Dell Precision 490",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME,
-				  "Precision WorkStation 490"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
-	},
-	{
-		.ident = "Dell Precision",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
-		},
-	},
-	{
-		.ident = "Dell Vostro",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
-		},
-	},
-	{
-		.ident = "Dell XPS421",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
-		},
-	},
-	{
-		.ident = "Dell Studio",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
-	},
-	{
-		.ident = "Dell XPS 13",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_XPS],
-	},
-	{
-		.ident = "Dell XPS M140",
-		.matches = {
-			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
-			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
-		},
-		.driver_data = (void *)&i8k_config_data[DELL_XPS],
-	},
-	{ }
-};
-
-MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
-
-/*
- * Probe for the presence of a supported laptop.
- */
-static int __init i8k_probe(void)
-{
-	const struct dmi_system_id *id;
-	int fan, ret;
-
-	/*
-	 * Get DMI information
-	 */
-	if (!dmi_check_system(i8k_dmi_table)) {
-		if (!ignore_dmi && !force)
-			return -ENODEV;
-
-		pr_info("not running on a supported Dell system.\n");
-		pr_info("vendor=%s, model=%s, version=%s\n",
-			i8k_get_dmi_data(DMI_SYS_VENDOR),
-			i8k_get_dmi_data(DMI_PRODUCT_NAME),
-			i8k_get_dmi_data(DMI_BIOS_VERSION));
-	}
-
-	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
-		sizeof(bios_version));
-
-	/*
-	 * Get SMM Dell signature
-	 */
-	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
-	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
-		pr_err("unable to get SMM Dell signature\n");
-		if (!force)
-			return -ENODEV;
-	}
-
-	/*
-	 * Set fan multiplier and maximal fan speed from dmi config
-	 * Values specified in module parameters override values from dmi
-	 */
-	id = dmi_first_match(i8k_dmi_table);
-	if (id && id->driver_data) {
-		const struct i8k_config_data *conf = id->driver_data;
-		if (!fan_mult && conf->fan_mult)
-			fan_mult = conf->fan_mult;
-		if (!fan_max && conf->fan_max)
-			fan_max = conf->fan_max;
-	}
-
-	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
-	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
-
-	if (!fan_mult) {
-		/*
-		 * Autodetect fan multiplier based on nominal rpm
-		 * If fan reports rpm value too high then set multiplier to 1
-		 */
-		for (fan = 0; fan < 2; ++fan) {
-			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
-			if (ret < 0)
-				continue;
-			if (ret > I8K_FAN_MAX_RPM)
-				i8k_fan_mult = 1;
-			break;
-		}
-	} else {
-		/* Fan multiplier was specified in module param or in dmi */
-		i8k_fan_mult = fan_mult;
-	}
-
-	return 0;
-}
-
-static int __init i8k_init(void)
-{
-	struct proc_dir_entry *proc_i8k;
-	int err;
-
-	/* Are we running on an supported laptop? */
-	if (i8k_probe())
-		return -ENODEV;
-
-	/* Register the proc entry */
-	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
-	if (!proc_i8k)
-		return -ENOENT;
-
-	err = i8k_init_hwmon();
-	if (err)
-		goto exit_remove_proc;
-
-	return 0;
-
- exit_remove_proc:
-	remove_proc_entry("i8k", NULL);
-	return err;
-}
-
-static void __exit i8k_exit(void)
-{
-	hwmon_device_unregister(i8k_hwmon_dev);
-	remove_proc_entry("i8k", NULL);
-}
-
-module_init(i8k_init);
-module_exit(i8k_exit);
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b4a40f1..51533ac 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -156,6 +156,7 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
+obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
new file mode 100644
index 0000000..65d314b
--- /dev/null
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -0,0 +1,1009 @@
+/*
+ * dell-smm-hwmon.c -- Linux driver for accessing the SMM BIOS on Dell laptops.
+ *
+ * Copyright (C) 2001  Massimo Dal Zotto <dz@debian.org>
+ *
+ * Hwmon integration:
+ * Copyright (C) 2011  Jean Delvare <jdelvare@suse.de>
+ * Copyright (C) 2013, 2014  Guenter Roeck <linux@roeck-us.net>
+ * Copyright (C) 2014, 2015  Pali Rohár <pali.rohar@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2, or (at your option) any
+ * later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/dmi.h>
+#include <linux/capability.h>
+#include <linux/mutex.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+
+#include <linux/i8k.h>
+
+#define I8K_SMM_FN_STATUS	0x0025
+#define I8K_SMM_POWER_STATUS	0x0069
+#define I8K_SMM_SET_FAN		0x01a3
+#define I8K_SMM_GET_FAN		0x00a3
+#define I8K_SMM_GET_SPEED	0x02a3
+#define I8K_SMM_GET_FAN_TYPE	0x03a3
+#define I8K_SMM_GET_NOM_SPEED	0x04a3
+#define I8K_SMM_GET_TEMP	0x10a3
+#define I8K_SMM_GET_TEMP_TYPE	0x11a3
+#define I8K_SMM_GET_DELL_SIG1	0xfea3
+#define I8K_SMM_GET_DELL_SIG2	0xffa3
+
+#define I8K_FAN_MULT		30
+#define I8K_FAN_MAX_RPM		30000
+#define I8K_MAX_TEMP		127
+
+#define I8K_FN_NONE		0x00
+#define I8K_FN_UP		0x01
+#define I8K_FN_DOWN		0x02
+#define I8K_FN_MUTE		0x04
+#define I8K_FN_MASK		0x07
+#define I8K_FN_SHIFT		8
+
+#define I8K_POWER_AC		0x05
+#define I8K_POWER_BATTERY	0x01
+
+static DEFINE_MUTEX(i8k_mutex);
+static char bios_version[4];
+static struct device *i8k_hwmon_dev;
+static u32 i8k_hwmon_flags;
+static uint i8k_fan_mult = I8K_FAN_MULT;
+static uint i8k_pwm_mult;
+static uint i8k_fan_max = I8K_FAN_HIGH;
+
+#define I8K_HWMON_HAVE_TEMP1	(1 << 0)
+#define I8K_HWMON_HAVE_TEMP2	(1 << 1)
+#define I8K_HWMON_HAVE_TEMP3	(1 << 2)
+#define I8K_HWMON_HAVE_TEMP4	(1 << 3)
+#define I8K_HWMON_HAVE_FAN1	(1 << 4)
+#define I8K_HWMON_HAVE_FAN2	(1 << 5)
+
+MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("i8k");
+
+static bool force;
+module_param(force, bool, 0);
+MODULE_PARM_DESC(force, "Force loading without checking for supported models");
+
+static bool ignore_dmi;
+module_param(ignore_dmi, bool, 0);
+MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
+
+static bool restricted;
+module_param(restricted, bool, 0);
+MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
+
+static bool power_status;
+module_param(power_status, bool, 0600);
+MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+
+static uint fan_mult;
+module_param(fan_mult, uint, 0);
+MODULE_PARM_DESC(fan_mult, "Factor to multiply fan speed with (default: autodetect)");
+
+static uint fan_max;
+module_param(fan_max, uint, 0);
+MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
+
+static int i8k_open_fs(struct inode *inode, struct file *file);
+static long i8k_ioctl(struct file *, unsigned int, unsigned long);
+
+static const struct file_operations i8k_fops = {
+	.owner		= THIS_MODULE,
+	.open		= i8k_open_fs,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.unlocked_ioctl	= i8k_ioctl,
+};
+
+struct smm_regs {
+	unsigned int eax;
+	unsigned int ebx __packed;
+	unsigned int ecx __packed;
+	unsigned int edx __packed;
+	unsigned int esi __packed;
+	unsigned int edi __packed;
+};
+
+static inline const char *i8k_get_dmi_data(int field)
+{
+	const char *dmi_data = dmi_get_system_info(field);
+
+	return dmi_data && *dmi_data ? dmi_data : "?";
+}
+
+/*
+ * Call the System Management Mode BIOS. Code provided by Jonathan Buzzard.
+ */
+static int i8k_smm(struct smm_regs *regs)
+{
+	int rc;
+	int eax = regs->eax;
+	cpumask_var_t old_mask;
+
+	/* SMM requires CPU 0 */
+	if (!alloc_cpumask_var(&old_mask, GFP_KERNEL))
+		return -ENOMEM;
+	cpumask_copy(old_mask, &current->cpus_allowed);
+	rc = set_cpus_allowed_ptr(current, cpumask_of(0));
+	if (rc)
+		goto out;
+	if (smp_processor_id() != 0) {
+		rc = -EBUSY;
+		goto out;
+	}
+
+#if defined(CONFIG_X86_64)
+	asm volatile("pushq %%rax\n\t"
+		"movl 0(%%rax),%%edx\n\t"
+		"pushq %%rdx\n\t"
+		"movl 4(%%rax),%%ebx\n\t"
+		"movl 8(%%rax),%%ecx\n\t"
+		"movl 12(%%rax),%%edx\n\t"
+		"movl 16(%%rax),%%esi\n\t"
+		"movl 20(%%rax),%%edi\n\t"
+		"popq %%rax\n\t"
+		"out %%al,$0xb2\n\t"
+		"out %%al,$0x84\n\t"
+		"xchgq %%rax,(%%rsp)\n\t"
+		"movl %%ebx,4(%%rax)\n\t"
+		"movl %%ecx,8(%%rax)\n\t"
+		"movl %%edx,12(%%rax)\n\t"
+		"movl %%esi,16(%%rax)\n\t"
+		"movl %%edi,20(%%rax)\n\t"
+		"popq %%rdx\n\t"
+		"movl %%edx,0(%%rax)\n\t"
+		"pushfq\n\t"
+		"popq %%rax\n\t"
+		"andl $1,%%eax\n"
+		: "=a"(rc)
+		:    "a"(regs)
+		:    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+#else
+	asm volatile("pushl %%eax\n\t"
+	    "movl 0(%%eax),%%edx\n\t"
+	    "push %%edx\n\t"
+	    "movl 4(%%eax),%%ebx\n\t"
+	    "movl 8(%%eax),%%ecx\n\t"
+	    "movl 12(%%eax),%%edx\n\t"
+	    "movl 16(%%eax),%%esi\n\t"
+	    "movl 20(%%eax),%%edi\n\t"
+	    "popl %%eax\n\t"
+	    "out %%al,$0xb2\n\t"
+	    "out %%al,$0x84\n\t"
+	    "xchgl %%eax,(%%esp)\n\t"
+	    "movl %%ebx,4(%%eax)\n\t"
+	    "movl %%ecx,8(%%eax)\n\t"
+	    "movl %%edx,12(%%eax)\n\t"
+	    "movl %%esi,16(%%eax)\n\t"
+	    "movl %%edi,20(%%eax)\n\t"
+	    "popl %%edx\n\t"
+	    "movl %%edx,0(%%eax)\n\t"
+	    "lahf\n\t"
+	    "shrl $8,%%eax\n\t"
+	    "andl $1,%%eax\n"
+	    : "=a"(rc)
+	    :    "a"(regs)
+	    :    "%ebx", "%ecx", "%edx", "%esi", "%edi", "memory");
+#endif
+	if (rc != 0 || (regs->eax & 0xffff) == 0xffff || regs->eax == eax)
+		rc = -EINVAL;
+
+out:
+	set_cpus_allowed_ptr(current, old_mask);
+	free_cpumask_var(old_mask);
+	return rc;
+}
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+	case I8K_FN_UP:
+		return I8K_VOL_UP;
+	case I8K_FN_DOWN:
+		return I8K_VOL_DOWN;
+	case I8K_FN_MUTE:
+		return I8K_VOL_MUTE;
+	default:
+		return 0;
+	}
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
+}
+
+/*
+ * Read the fan status.
+ */
+static int i8k_get_fan_status(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the fan speed in RPM.
+ */
+static int i8k_get_fan_speed(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_SPEED, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+}
+
+/*
+ * Read the fan type.
+ */
+static int i8k_get_fan_type(int fan)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_FAN_TYPE, };
+
+	regs.ebx = fan & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the fan nominal rpm for specific fan speed.
+ */
+static int i8k_get_fan_nominal_speed(int fan, int speed)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_NOM_SPEED, };
+
+	regs.ebx = (fan & 0xff) | (speed << 8);
+	return i8k_smm(&regs) ? : (regs.eax & 0xffff) * i8k_fan_mult;
+}
+
+/*
+ * Set the fan speed (off, low, high). Returns the new fan status.
+ */
+static int i8k_set_fan(int fan, int speed)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_SET_FAN, };
+
+	speed = (speed < 0) ? 0 : ((speed > i8k_fan_max) ? i8k_fan_max : speed);
+	regs.ebx = (fan & 0xff) | (speed << 8);
+
+	return i8k_smm(&regs) ? : i8k_get_fan_status(fan);
+}
+
+static int i8k_get_temp_type(int sensor)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_GET_TEMP_TYPE, };
+
+	regs.ebx = sensor & 0xff;
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+/*
+ * Read the cpu temperature.
+ */
+static int _i8k_get_temp(int sensor)
+{
+	struct smm_regs regs = {
+		.eax = I8K_SMM_GET_TEMP,
+		.ebx = sensor & 0xff,
+	};
+
+	return i8k_smm(&regs) ? : regs.eax & 0xff;
+}
+
+static int i8k_get_temp(int sensor)
+{
+	int temp = _i8k_get_temp(sensor);
+
+	/*
+	 * Sometimes the temperature sensor returns 0x99, which is out of range.
+	 * In this case we retry (once) before returning an error.
+	 # 1003655137 00000058 00005a4b
+	 # 1003655138 00000099 00003a80 <--- 0x99 = 153 degrees
+	 # 1003655139 00000054 00005c52
+	 */
+	if (temp == 0x99) {
+		msleep(100);
+		temp = _i8k_get_temp(sensor);
+	}
+	/*
+	 * Return -ENODATA for all invalid temperatures.
+	 *
+	 * Known instances are the 0x99 value as seen above as well as
+	 * 0xc1 (193), which may be returned when trying to read the GPU
+	 * temperature if the system supports a GPU and it is currently
+	 * turned off.
+	 */
+	if (temp > I8K_MAX_TEMP)
+		return -ENODATA;
+
+	return temp;
+}
+
+static int i8k_get_dell_signature(int req_fn)
+{
+	struct smm_regs regs = { .eax = req_fn, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
+}
+
+static int
+i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	int val = 0;
+	int speed;
+	unsigned char buff[16];
+	int __user *argp = (int __user *)arg;
+
+	if (!argp)
+		return -EINVAL;
+
+	switch (cmd) {
+	case I8K_BIOS_VERSION:
+		val = (bios_version[0] << 16) |
+				(bios_version[1] << 8) | bios_version[2];
+		break;
+
+	case I8K_MACHINE_ID:
+		memset(buff, 0, 16);
+		strlcpy(buff, i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+			sizeof(buff));
+		break;
+
+	case I8K_FN_STATUS:
+		val = i8k_get_fn_status();
+		break;
+
+	case I8K_POWER_STATUS:
+		val = i8k_get_power_status();
+		break;
+
+	case I8K_GET_TEMP:
+		val = i8k_get_temp(0);
+		break;
+
+	case I8K_GET_SPEED:
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_get_fan_speed(val);
+		break;
+
+	case I8K_GET_FAN:
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_get_fan_status(val);
+		break;
+
+	case I8K_SET_FAN:
+		if (restricted && !capable(CAP_SYS_ADMIN))
+			return -EPERM;
+
+		if (copy_from_user(&val, argp, sizeof(int)))
+			return -EFAULT;
+
+		if (copy_from_user(&speed, argp + 1, sizeof(int)))
+			return -EFAULT;
+
+		val = i8k_set_fan(val, speed);
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	if (val < 0)
+		return val;
+
+	switch (cmd) {
+	case I8K_BIOS_VERSION:
+		if (copy_to_user(argp, &val, 4))
+			return -EFAULT;
+
+		break;
+	case I8K_MACHINE_ID:
+		if (copy_to_user(argp, buff, 16))
+			return -EFAULT;
+
+		break;
+	default:
+		if (copy_to_user(argp, &val, sizeof(int)))
+			return -EFAULT;
+
+		break;
+	}
+
+	return 0;
+}
+
+static long i8k_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+	long ret;
+
+	mutex_lock(&i8k_mutex);
+	ret = i8k_ioctl_unlocked(fp, cmd, arg);
+	mutex_unlock(&i8k_mutex);
+
+	return ret;
+}
+
+/*
+ * Print the information for /proc/i8k.
+ */
+static int i8k_proc_show(struct seq_file *seq, void *offset)
+{
+	int fn_key, cpu_temp, ac_power;
+	int left_fan, right_fan, left_speed, right_speed;
+
+	cpu_temp	= i8k_get_temp(0);			/* 11100 µs */
+	left_fan	= i8k_get_fan_status(I8K_FAN_LEFT);	/*   580 µs */
+	right_fan	= i8k_get_fan_status(I8K_FAN_RIGHT);	/*   580 µs */
+	left_speed	= i8k_get_fan_speed(I8K_FAN_LEFT);	/*   580 µs */
+	right_speed	= i8k_get_fan_speed(I8K_FAN_RIGHT);	/*   580 µs */
+	fn_key		= i8k_get_fn_status();			/*   750 µs */
+	if (power_status)
+		ac_power = i8k_get_power_status();		/* 14700 µs */
+	else
+		ac_power = -1;
+
+	/*
+	 * Info:
+	 *
+	 * 1)  Format version (this will change if format changes)
+	 * 2)  BIOS version
+	 * 3)  BIOS machine ID
+	 * 4)  Cpu temperature
+	 * 5)  Left fan status
+	 * 6)  Right fan status
+	 * 7)  Left fan speed
+	 * 8)  Right fan speed
+	 * 9)  AC power
+	 * 10) Fn Key status
+	 */
+	seq_printf(seq, "%s %s %s %d %d %d %d %d %d %d\n",
+		   I8K_PROC_FMT,
+		   bios_version,
+		   i8k_get_dmi_data(DMI_PRODUCT_SERIAL),
+		   cpu_temp,
+		   left_fan, right_fan, left_speed, right_speed,
+		   ac_power, fn_key);
+
+	return 0;
+}
+
+static int i8k_open_fs(struct inode *inode, struct file *file)
+{
+	return single_open(file, i8k_proc_show, NULL);
+}
+
+
+/*
+ * Hwmon interface
+ */
+
+static ssize_t i8k_hwmon_show_temp_label(struct device *dev,
+					 struct device_attribute *devattr,
+					 char *buf)
+{
+	static const char * const labels[] = {
+		"CPU",
+		"GPU",
+		"SODIMM",
+		"Other",
+		"Ambient",
+		"Other",
+	};
+	int index = to_sensor_dev_attr(devattr)->index;
+	int type;
+
+	type = i8k_get_temp_type(index);
+	if (type < 0)
+		return type;
+	if (type >= ARRAY_SIZE(labels))
+		type = ARRAY_SIZE(labels) - 1;
+	return sprintf(buf, "%s\n", labels[type]);
+}
+
+static ssize_t i8k_hwmon_show_temp(struct device *dev,
+				   struct device_attribute *devattr,
+				   char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int temp;
+
+	temp = i8k_get_temp(index);
+	if (temp < 0)
+		return temp;
+	return sprintf(buf, "%d\n", temp * 1000);
+}
+
+static ssize_t i8k_hwmon_show_fan_label(struct device *dev,
+					struct device_attribute *devattr,
+					char *buf)
+{
+	static const char * const labels[] = {
+		"Processor Fan",
+		"Motherboard Fan",
+		"Video Fan",
+		"Power Supply Fan",
+		"Chipset Fan",
+		"Other Fan",
+	};
+	int index = to_sensor_dev_attr(devattr)->index;
+	bool dock = false;
+	int type;
+
+	type = i8k_get_fan_type(index);
+	if (type < 0)
+		return type;
+
+	if (type & 0x10) {
+		dock = true;
+		type &= 0x0F;
+	}
+
+	if (type >= ARRAY_SIZE(labels))
+		type = (ARRAY_SIZE(labels) - 1);
+
+	return sprintf(buf, "%s%s\n", (dock ? "Docking " : ""), labels[type]);
+}
+
+static ssize_t i8k_hwmon_show_fan(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int fan_speed;
+
+	fan_speed = i8k_get_fan_speed(index);
+	if (fan_speed < 0)
+		return fan_speed;
+	return sprintf(buf, "%d\n", fan_speed);
+}
+
+static ssize_t i8k_hwmon_show_pwm(struct device *dev,
+				  struct device_attribute *devattr,
+				  char *buf)
+{
+	int index = to_sensor_dev_attr(devattr)->index;
+	int status;
+
+	status = i8k_get_fan_status(index);
+	if (status < 0)
+		return -EIO;
+	return sprintf(buf, "%d\n", clamp_val(status * i8k_pwm_mult, 0, 255));
+}
+
+static ssize_t i8k_hwmon_set_pwm(struct device *dev,
+				 struct device_attribute *attr,
+				 const char *buf, size_t count)
+{
+	int index = to_sensor_dev_attr(attr)->index;
+	unsigned long val;
+	int err;
+
+	err = kstrtoul(buf, 10, &val);
+	if (err)
+		return err;
+	val = clamp_val(DIV_ROUND_CLOSEST(val, i8k_pwm_mult), 0, i8k_fan_max);
+
+	mutex_lock(&i8k_mutex);
+	err = i8k_set_fan(index, val);
+	mutex_unlock(&i8k_mutex);
+
+	return err < 0 ? -EIO : count;
+}
+
+static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 0);
+static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  0);
+static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 1);
+static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 2);
+static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  2);
+static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, i8k_hwmon_show_temp, NULL, 3);
+static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, i8k_hwmon_show_temp_label, NULL,
+			  3);
+static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, i8k_hwmon_show_fan, NULL, 0);
+static SENSOR_DEVICE_ATTR(fan1_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
+			  0);
+static SENSOR_DEVICE_ATTR(pwm1, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
+			  i8k_hwmon_set_pwm, 0);
+static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, i8k_hwmon_show_fan, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(fan2_label, S_IRUGO, i8k_hwmon_show_fan_label, NULL,
+			  1);
+static SENSOR_DEVICE_ATTR(pwm2, S_IRUGO | S_IWUSR, i8k_hwmon_show_pwm,
+			  i8k_hwmon_set_pwm, 1);
+
+static struct attribute *i8k_attrs[] = {
+	&sensor_dev_attr_temp1_input.dev_attr.attr,	/* 0 */
+	&sensor_dev_attr_temp1_label.dev_attr.attr,	/* 1 */
+	&sensor_dev_attr_temp2_input.dev_attr.attr,	/* 2 */
+	&sensor_dev_attr_temp2_label.dev_attr.attr,	/* 3 */
+	&sensor_dev_attr_temp3_input.dev_attr.attr,	/* 4 */
+	&sensor_dev_attr_temp3_label.dev_attr.attr,	/* 5 */
+	&sensor_dev_attr_temp4_input.dev_attr.attr,	/* 6 */
+	&sensor_dev_attr_temp4_label.dev_attr.attr,	/* 7 */
+	&sensor_dev_attr_fan1_input.dev_attr.attr,	/* 8 */
+	&sensor_dev_attr_fan1_label.dev_attr.attr,	/* 9 */
+	&sensor_dev_attr_pwm1.dev_attr.attr,		/* 10 */
+	&sensor_dev_attr_fan2_input.dev_attr.attr,	/* 11 */
+	&sensor_dev_attr_fan2_label.dev_attr.attr,	/* 12 */
+	&sensor_dev_attr_pwm2.dev_attr.attr,		/* 13 */
+	NULL
+};
+
+static umode_t i8k_is_visible(struct kobject *kobj, struct attribute *attr,
+			      int index)
+{
+	if (index >= 0 && index <= 1 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP1))
+		return 0;
+	if (index >= 2 && index <= 3 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP2))
+		return 0;
+	if (index >= 4 && index <= 5 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP3))
+		return 0;
+	if (index >= 6 && index <= 7 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_TEMP4))
+		return 0;
+	if (index >= 8 && index <= 10 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN1))
+		return 0;
+	if (index >= 11 && index <= 13 &&
+	    !(i8k_hwmon_flags & I8K_HWMON_HAVE_FAN2))
+		return 0;
+
+	return attr->mode;
+}
+
+static const struct attribute_group i8k_group = {
+	.attrs = i8k_attrs,
+	.is_visible = i8k_is_visible,
+};
+__ATTRIBUTE_GROUPS(i8k);
+
+static int __init i8k_init_hwmon(void)
+{
+	int err;
+
+	i8k_hwmon_flags = 0;
+
+	/* CPU temperature attributes, if temperature type is OK */
+	err = i8k_get_temp_type(0);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP1;
+	/* check for additional temperature sensors */
+	err = i8k_get_temp_type(1);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP2;
+	err = i8k_get_temp_type(2);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP3;
+	err = i8k_get_temp_type(3);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_TEMP4;
+
+	/* First fan attributes, if fan type is OK */
+	err = i8k_get_fan_type(0);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN1;
+
+	/* Second fan attributes, if fan type is OK */
+	err = i8k_get_fan_type(1);
+	if (err >= 0)
+		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
+
+	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
+							  i8k_groups);
+	if (IS_ERR(i8k_hwmon_dev)) {
+		err = PTR_ERR(i8k_hwmon_dev);
+		i8k_hwmon_dev = NULL;
+		pr_err("hwmon registration failed (%d)\n", err);
+		return err;
+	}
+	return 0;
+}
+
+struct i8k_config_data {
+	uint fan_mult;
+	uint fan_max;
+};
+
+enum i8k_configs {
+	DELL_LATITUDE_D520,
+	DELL_PRECISION_490,
+	DELL_STUDIO,
+	DELL_XPS,
+};
+
+static const struct i8k_config_data i8k_config_data[] = {
+	[DELL_LATITUDE_D520] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_TURBO,
+	},
+	[DELL_PRECISION_490] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_TURBO,
+	},
+	[DELL_STUDIO] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_HIGH,
+	},
+	[DELL_XPS] = {
+		.fan_mult = 1,
+		.fan_max = I8K_FAN_HIGH,
+	},
+};
+
+static struct dmi_system_id i8k_dmi_table[] __initdata = {
+	{
+		.ident = "Dell Inspiron",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+		},
+	},
+	{
+		.ident = "Dell Latitude",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Computer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
+		},
+	},
+	{
+		.ident = "Dell Inspiron 2",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Inspiron"),
+		},
+	},
+	{
+		.ident = "Dell Latitude D520",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude D520"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_LATITUDE_D520],
+	},
+	{
+		.ident = "Dell Latitude 2",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Latitude"),
+		},
+	},
+	{	/* UK Inspiron 6400  */
+		.ident = "Dell Inspiron 3",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MM061"),
+		},
+	},
+	{
+		.ident = "Dell Inspiron 3",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MP061"),
+		},
+	},
+	{
+		.ident = "Dell Precision 490",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME,
+				  "Precision WorkStation 490"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_PRECISION_490],
+	},
+	{
+		.ident = "Dell Precision",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Precision"),
+		},
+	},
+	{
+		.ident = "Dell Vostro",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Vostro"),
+		},
+	},
+	{
+		.ident = "Dell XPS421",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS L421X"),
+		},
+	},
+	{
+		.ident = "Dell Studio",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Studio"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_STUDIO],
+	},
+	{
+		.ident = "Dell XPS 13",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "XPS13"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_XPS],
+	},
+	{
+		.ident = "Dell XPS M140",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+			DMI_MATCH(DMI_PRODUCT_NAME, "MXC051"),
+		},
+		.driver_data = (void *)&i8k_config_data[DELL_XPS],
+	},
+	{ }
+};
+
+MODULE_DEVICE_TABLE(dmi, i8k_dmi_table);
+
+/*
+ * Probe for the presence of a supported laptop.
+ */
+static int __init i8k_probe(void)
+{
+	const struct dmi_system_id *id;
+	int fan, ret;
+
+	/*
+	 * Get DMI information
+	 */
+	if (!dmi_check_system(i8k_dmi_table)) {
+		if (!ignore_dmi && !force)
+			return -ENODEV;
+
+		pr_info("not running on a supported Dell system.\n");
+		pr_info("vendor=%s, model=%s, version=%s\n",
+			i8k_get_dmi_data(DMI_SYS_VENDOR),
+			i8k_get_dmi_data(DMI_PRODUCT_NAME),
+			i8k_get_dmi_data(DMI_BIOS_VERSION));
+	}
+
+	strlcpy(bios_version, i8k_get_dmi_data(DMI_BIOS_VERSION),
+		sizeof(bios_version));
+
+	/*
+	 * Get SMM Dell signature
+	 */
+	if (i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG1) &&
+	    i8k_get_dell_signature(I8K_SMM_GET_DELL_SIG2)) {
+		pr_err("unable to get SMM Dell signature\n");
+		if (!force)
+			return -ENODEV;
+	}
+
+	/*
+	 * Set fan multiplier and maximal fan speed from dmi config
+	 * Values specified in module parameters override values from dmi
+	 */
+	id = dmi_first_match(i8k_dmi_table);
+	if (id && id->driver_data) {
+		const struct i8k_config_data *conf = id->driver_data;
+		if (!fan_mult && conf->fan_mult)
+			fan_mult = conf->fan_mult;
+		if (!fan_max && conf->fan_max)
+			fan_max = conf->fan_max;
+	}
+
+	i8k_fan_max = fan_max ? : I8K_FAN_HIGH;	/* Must not be 0 */
+	i8k_pwm_mult = DIV_ROUND_UP(255, i8k_fan_max);
+
+	if (!fan_mult) {
+		/*
+		 * Autodetect fan multiplier based on nominal rpm
+		 * If fan reports rpm value too high then set multiplier to 1
+		 */
+		for (fan = 0; fan < 2; ++fan) {
+			ret = i8k_get_fan_nominal_speed(fan, i8k_fan_max);
+			if (ret < 0)
+				continue;
+			if (ret > I8K_FAN_MAX_RPM)
+				i8k_fan_mult = 1;
+			break;
+		}
+	} else {
+		/* Fan multiplier was specified in module param or in dmi */
+		i8k_fan_mult = fan_mult;
+	}
+
+	return 0;
+}
+
+static int __init i8k_init(void)
+{
+	struct proc_dir_entry *proc_i8k;
+	int err;
+
+	/* Are we running on an supported laptop? */
+	if (i8k_probe())
+		return -ENODEV;
+
+	/* Register the proc entry */
+	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
+	if (!proc_i8k)
+		return -ENOENT;
+
+	err = i8k_init_hwmon();
+	if (err)
+		goto exit_remove_proc;
+
+	return 0;
+
+ exit_remove_proc:
+	remove_proc_entry("i8k", NULL);
+	return err;
+}
+
+static void __exit i8k_exit(void)
+{
+	hwmon_device_unregister(i8k_hwmon_dev);
+	remove_proc_entry("i8k", NULL);
+}
+
+module_init(i8k_init);
+module_exit(i8k_exit);
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-04-29 11:41       ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
@ 2015-04-29 11:41         ` Pali Rohár
  2015-06-27 11:34           ` Gabriele Mazzotta
  2015-04-29 12:07         ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Greg Kroah-Hartman
  1 sibling, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-04-29 11:41 UTC (permalink / raw)
  To: Greg Kroah-Hartman
  Cc: Guenter Roeck, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors, Pali Rohár

This patch splits CONFIG_I8K compile option to SENSORS_DELL_SMM and CONFIG_I8K.
Option SENSORS_DELL_SMM is now used to enable compilation of dell-smm-hwmon
driver and old CONFIG_I8K option to enable /proc/i8k interface in driver.

So this change allows to compile dell-smm-hwmon driver without legacy /proc/i8k
interface which is needed only for old Dell Inspirion models or for userspace
i8kutils package.

For backward compatibility when CONFIG_I8K is enabled then also SENSORS_DELL_SMM
is enabled and so driver dell-smm-hwmon (with /proc/i8k) is compiled.

Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
---
 arch/x86/Kconfig               |   25 +++----
 drivers/hwmon/Kconfig          |   11 +++
 drivers/hwmon/Makefile         |    2 +-
 drivers/hwmon/dell-smm-hwmon.c |  150 ++++++++++++++++++++++------------------
 4 files changed, 106 insertions(+), 82 deletions(-)

diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
index 226d569..7b756b3 100644
--- a/arch/x86/Kconfig
+++ b/arch/x86/Kconfig
@@ -1055,24 +1055,19 @@ config TOSHIBA
 	  Say N otherwise.
 
 config I8K
-	tristate "Dell laptop support"
+	tristate "Dell i8k legacy laptop support"
 	select HWMON
+	select SENSORS_DELL_SMM
 	---help---
-	  This adds a driver to safely access the System Management Mode
-	  of the CPU on the Dell Inspiron 8000. The System Management Mode
-	  is used to read cpu temperature and cooling fan status and to
-	  control the fans on the I8K portables.
+	  This option enables legacy /proc/i8k userspace interface in hwmon
+	  dell-smm-hwmon driver. Character file /proc/i8k reports bios version,
+	  temperature and allows controlling fan speeds of Dell laptops via
+	  System Management Mode. For old Dell laptops (like Dell Inspiron 8000)
+	  it reports also power and hotkey status. For fan speed control is
+	  needed userspace package i8kutils.
 
-	  This driver has been tested only on the Inspiron 8000 but it may
-	  also work with other Dell laptops. You can force loading on other
-	  models by passing the parameter `force=1' to the module. Use at
-	  your own risk.
-
-	  For information on utilities to make use of this driver see the
-	  I8K Linux utilities web site at:
-	  <http://people.debian.org/~dz/i8k/>
-
-	  Say Y if you intend to run this kernel on a Dell Inspiron 8000.
+	  Say Y if you intend to run this kernel on old Dell laptops or want to
+	  use userspace package i8kutils.
 	  Say N otherwise.
 
 config X86_REBOOTFIXUPS
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 25d9e72..714f92a 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -371,6 +371,17 @@ config SENSORS_DS1621
 	  This driver can also be built as a module.  If so, the module
 	  will be called ds1621.
 
+config SENSORS_DELL_SMM
+	tristate "Dell laptop SMM BIOS hwmon driver"
+	depends on X86 && DMI
+	help
+	  This hwmon driver adds support for reporting temperature of different
+	  sensors and controls the fans on Dell laptops via System Management
+	  Mode provided by Dell BIOS.
+
+	  When option I8K is also enabled this driver provides legacy /proc/i8k
+	  userspace interface for i8kutils package.
+
 config SENSORS_DA9052_ADC
 	tristate "Dialog DA9052/DA9053 ADC"
 	depends on PMIC_DA9052
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 51533ac..6cacd0f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
 obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
+obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
 obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
 obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
@@ -156,7 +157,6 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
 obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
 obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
 obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
-obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
 
 obj-$(CONFIG_PMBUS)		+= pmbus/
 
diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
index 65d314b..2a80882 100644
--- a/drivers/hwmon/dell-smm-hwmon.c
+++ b/drivers/hwmon/dell-smm-hwmon.c
@@ -81,7 +81,7 @@ static uint i8k_fan_max = I8K_FAN_HIGH;
 
 MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
 MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
-MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
+MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
 MODULE_LICENSE("GPL");
 MODULE_ALIAS("i8k");
 
@@ -93,6 +93,7 @@ static bool ignore_dmi;
 module_param(ignore_dmi, bool, 0);
 MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
 
+#if IS_ENABLED(CONFIG_I8K)
 static bool restricted;
 module_param(restricted, bool, 0);
 MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
@@ -100,6 +101,7 @@ MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
 static bool power_status;
 module_param(power_status, bool, 0600);
 MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
+#endif
 
 static uint fan_mult;
 module_param(fan_mult, uint, 0);
@@ -109,18 +111,6 @@ static uint fan_max;
 module_param(fan_max, uint, 0);
 MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
 
-static int i8k_open_fs(struct inode *inode, struct file *file);
-static long i8k_ioctl(struct file *, unsigned int, unsigned long);
-
-static const struct file_operations i8k_fops = {
-	.owner		= THIS_MODULE,
-	.open		= i8k_open_fs,
-	.read		= seq_read,
-	.llseek		= seq_lseek,
-	.release	= single_release,
-	.unlocked_ioctl	= i8k_ioctl,
-};
-
 struct smm_regs {
 	unsigned int eax;
 	unsigned int ebx __packed;
@@ -221,45 +211,6 @@ out:
 }
 
 /*
- * Read the Fn key status.
- */
-static int i8k_get_fn_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
-	case I8K_FN_UP:
-		return I8K_VOL_UP;
-	case I8K_FN_DOWN:
-		return I8K_VOL_DOWN;
-	case I8K_FN_MUTE:
-		return I8K_VOL_MUTE;
-	default:
-		return 0;
-	}
-}
-
-/*
- * Read the power status.
- */
-static int i8k_get_power_status(void)
-{
-	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
-	int rc;
-
-	rc = i8k_smm(&regs);
-	if (rc < 0)
-		return rc;
-
-	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
-}
-
-/*
  * Read the fan status.
  */
 static int i8k_get_fan_status(int fan)
@@ -378,6 +329,51 @@ static int i8k_get_dell_signature(int req_fn)
 	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
 }
 
+#if IS_ENABLED(CONFIG_I8K)
+
+/*
+ * Read the Fn key status.
+ */
+static int i8k_get_fn_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
+	case I8K_FN_UP:
+		return I8K_VOL_UP;
+	case I8K_FN_DOWN:
+		return I8K_VOL_DOWN;
+	case I8K_FN_MUTE:
+		return I8K_VOL_MUTE;
+	default:
+		return 0;
+	}
+}
+
+/*
+ * Read the power status.
+ */
+static int i8k_get_power_status(void)
+{
+	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
+	int rc;
+
+	rc = i8k_smm(&regs);
+	if (rc < 0)
+		return rc;
+
+	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
+}
+
+/*
+ * Procfs interface
+ */
+
 static int
 i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
 {
@@ -528,6 +524,37 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
 	return single_open(file, i8k_proc_show, NULL);
 }
 
+static const struct file_operations i8k_fops = {
+	.owner		= THIS_MODULE,
+	.open		= i8k_open_fs,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+	.unlocked_ioctl	= i8k_ioctl,
+};
+
+static void __init i8k_init_procfs(void)
+{
+	/* Register the proc entry */
+	proc_create("i8k", 0, NULL, &i8k_fops);
+}
+
+static void __exit i8k_exit_procfs(void)
+{
+	remove_proc_entry("i8k", NULL);
+}
+
+#else
+
+static inline void __init i8k_init_procfs(void)
+{
+}
+
+static inline void __exit i8k_exit_procfs(void)
+{
+}
+
+#endif
 
 /*
  * Hwmon interface
@@ -750,8 +777,8 @@ static int __init i8k_init_hwmon(void)
 	if (err >= 0)
 		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
 
-	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
-							  i8k_groups);
+	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell-smm",
+							  NULL, i8k_groups);
 	if (IS_ERR(i8k_hwmon_dev)) {
 		err = PTR_ERR(i8k_hwmon_dev);
 		i8k_hwmon_dev = NULL;
@@ -976,33 +1003,24 @@ static int __init i8k_probe(void)
 
 static int __init i8k_init(void)
 {
-	struct proc_dir_entry *proc_i8k;
 	int err;
 
 	/* Are we running on an supported laptop? */
 	if (i8k_probe())
 		return -ENODEV;
 
-	/* Register the proc entry */
-	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
-	if (!proc_i8k)
-		return -ENOENT;
-
 	err = i8k_init_hwmon();
 	if (err)
-		goto exit_remove_proc;
+		return err;
 
+	i8k_init_procfs();
 	return 0;
-
- exit_remove_proc:
-	remove_proc_entry("i8k", NULL);
-	return err;
 }
 
 static void __exit i8k_exit(void)
 {
 	hwmon_device_unregister(i8k_hwmon_dev);
-	remove_proc_entry("i8k", NULL);
+	i8k_exit_procfs();
 }
 
 module_init(i8k_init);
-- 
1.7.10.4


^ permalink raw reply related	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree
  2015-04-29 11:41       ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
  2015-04-29 11:41         ` [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
@ 2015-04-29 12:07         ` Greg Kroah-Hartman
  1 sibling, 0 replies; 40+ messages in thread
From: Greg Kroah-Hartman @ 2015-04-29 12:07 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Guenter Roeck, Arnd Bergmann, Jean Delvare, Steven Honeyman,
	Valdis.Kletnieks, Jochen Eisinger, Gabriele Mazzotta,
	linux-kernel, lm-sensors

On Wed, Apr 29, 2015 at 01:41:25PM +0200, Pali Rohár wrote:
> This commit moves i8k driver to hwmon tree under name dell-smm-hwmon which is
> better name then abbreviation i8k. For backward compatibility is added macro
> MODULE_ALIAS("i8k") so modprobe will load driver also old name i8k. CONFIG_I8K
> compile option was not changed.
> 
> This commit also adds me as maintainer of this new dell-smm-hwmon driver and
> remove Guenter Roeck from list who is implicit maintainer all hwmon drivers.
> 
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
> ---
>  MAINTAINERS                    |    4 +-
>  drivers/char/Makefile          |    1 -
>  drivers/char/i8k.c             | 1007 ---------------------------------------
>  drivers/hwmon/Makefile         |    1 +
>  drivers/hwmon/dell-smm-hwmon.c | 1009 ++++++++++++++++++++++++++++++++++++++++
>  5 files changed, 1012 insertions(+), 1010 deletions(-)
>  delete mode 100644 drivers/char/i8k.c
>  create mode 100644 drivers/hwmon/dell-smm-hwmon.c

In the future, please create patches with -M to show the file is moved,
which makes it much easier to review.

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-04-29 11:41         ` [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
@ 2015-06-27 11:34           ` Gabriele Mazzotta
  2015-06-27 12:47             ` Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Gabriele Mazzotta @ 2015-06-27 11:34 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Greg Kroah-Hartman, Guenter Roeck, Arnd Bergmann, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger, linux-kernel,
	lm-sensors

Hi Pali,

I've just noticed an issue with this patch. See the comment here below.

Gabriele

On Wednesday 29 April 2015 13:41:26 Pali Rohár wrote:
> This patch splits CONFIG_I8K compile option to SENSORS_DELL_SMM and CONFIG_I8K.
> Option SENSORS_DELL_SMM is now used to enable compilation of dell-smm-hwmon
> driver and old CONFIG_I8K option to enable /proc/i8k interface in driver.
> 
> So this change allows to compile dell-smm-hwmon driver without legacy /proc/i8k
> interface which is needed only for old Dell Inspirion models or for userspace
> i8kutils package.
> 
> For backward compatibility when CONFIG_I8K is enabled then also SENSORS_DELL_SMM
> is enabled and so driver dell-smm-hwmon (with /proc/i8k) is compiled.
> 
> Signed-off-by: Pali Rohár <pali.rohar@gmail.com>
> ---
>  arch/x86/Kconfig               |   25 +++----
>  drivers/hwmon/Kconfig          |   11 +++
>  drivers/hwmon/Makefile         |    2 +-
>  drivers/hwmon/dell-smm-hwmon.c |  150 ++++++++++++++++++++++------------------
>  4 files changed, 106 insertions(+), 82 deletions(-)
> 
> diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig
> index 226d569..7b756b3 100644
> --- a/arch/x86/Kconfig
> +++ b/arch/x86/Kconfig
> @@ -1055,24 +1055,19 @@ config TOSHIBA
>  	  Say N otherwise.
>  
>  config I8K
> -	tristate "Dell laptop support"
> +	tristate "Dell i8k legacy laptop support"
>  	select HWMON
> +	select SENSORS_DELL_SMM
>  	---help---
> -	  This adds a driver to safely access the System Management Mode
> -	  of the CPU on the Dell Inspiron 8000. The System Management Mode
> -	  is used to read cpu temperature and cooling fan status and to
> -	  control the fans on the I8K portables.
> +	  This option enables legacy /proc/i8k userspace interface in hwmon
> +	  dell-smm-hwmon driver. Character file /proc/i8k reports bios version,
> +	  temperature and allows controlling fan speeds of Dell laptops via
> +	  System Management Mode. For old Dell laptops (like Dell Inspiron 8000)
> +	  it reports also power and hotkey status. For fan speed control is
> +	  needed userspace package i8kutils.
>  
> -	  This driver has been tested only on the Inspiron 8000 but it may
> -	  also work with other Dell laptops. You can force loading on other
> -	  models by passing the parameter `force=1' to the module. Use at
> -	  your own risk.
> -
> -	  For information on utilities to make use of this driver see the
> -	  I8K Linux utilities web site at:
> -	  <http://people.debian.org/~dz/i8k/>
> -
> -	  Say Y if you intend to run this kernel on a Dell Inspiron 8000.
> +	  Say Y if you intend to run this kernel on old Dell laptops or want to
> +	  use userspace package i8kutils.
>  	  Say N otherwise.
>  
>  config X86_REBOOTFIXUPS
> diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
> index 25d9e72..714f92a 100644
> --- a/drivers/hwmon/Kconfig
> +++ b/drivers/hwmon/Kconfig
> @@ -371,6 +371,17 @@ config SENSORS_DS1621
>  	  This driver can also be built as a module.  If so, the module
>  	  will be called ds1621.
>  
> +config SENSORS_DELL_SMM
> +	tristate "Dell laptop SMM BIOS hwmon driver"
> +	depends on X86 && DMI
> +	help
> +	  This hwmon driver adds support for reporting temperature of different
> +	  sensors and controls the fans on Dell laptops via System Management
> +	  Mode provided by Dell BIOS.
> +
> +	  When option I8K is also enabled this driver provides legacy /proc/i8k
> +	  userspace interface for i8kutils package.
> +
>  config SENSORS_DA9052_ADC
>  	tristate "Dialog DA9052/DA9053 ADC"
>  	depends on PMIC_DA9052
> diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
> index 51533ac..6cacd0f 100644
> --- a/drivers/hwmon/Makefile
> +++ b/drivers/hwmon/Makefile
> @@ -49,6 +49,7 @@ obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
>  obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
>  obj-$(CONFIG_SENSORS_DA9052_ADC)+= da9052-hwmon.o
>  obj-$(CONFIG_SENSORS_DA9055)+= da9055-hwmon.o
> +obj-$(CONFIG_SENSORS_DELL_SMM)	+= dell-smm-hwmon.o
>  obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
>  obj-$(CONFIG_SENSORS_DS620)	+= ds620.o
>  obj-$(CONFIG_SENSORS_DS1621)	+= ds1621.o
> @@ -156,7 +157,6 @@ obj-$(CONFIG_SENSORS_W83L785TS)	+= w83l785ts.o
>  obj-$(CONFIG_SENSORS_W83L786NG)	+= w83l786ng.o
>  obj-$(CONFIG_SENSORS_WM831X)	+= wm831x-hwmon.o
>  obj-$(CONFIG_SENSORS_WM8350)	+= wm8350-hwmon.o
> -obj-$(CONFIG_I8K)		+= dell-smm-hwmon.o
>  
>  obj-$(CONFIG_PMBUS)		+= pmbus/
>  
> diff --git a/drivers/hwmon/dell-smm-hwmon.c b/drivers/hwmon/dell-smm-hwmon.c
> index 65d314b..2a80882 100644
> --- a/drivers/hwmon/dell-smm-hwmon.c
> +++ b/drivers/hwmon/dell-smm-hwmon.c
> @@ -81,7 +81,7 @@ static uint i8k_fan_max = I8K_FAN_HIGH;
>  
>  MODULE_AUTHOR("Massimo Dal Zotto (dz@debian.org)");
>  MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
> -MODULE_DESCRIPTION("Driver for accessing SMM BIOS on Dell laptops");
> +MODULE_DESCRIPTION("Dell laptop SMM BIOS hwmon driver");
>  MODULE_LICENSE("GPL");
>  MODULE_ALIAS("i8k");
>  
> @@ -93,6 +93,7 @@ static bool ignore_dmi;
>  module_param(ignore_dmi, bool, 0);
>  MODULE_PARM_DESC(ignore_dmi, "Continue probing hardware even if DMI data does not match");
>  
> +#if IS_ENABLED(CONFIG_I8K)
>  static bool restricted;
>  module_param(restricted, bool, 0);
>  MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
> @@ -100,6 +101,7 @@ MODULE_PARM_DESC(restricted, "Allow fan control if SYS_ADMIN capability set");
>  static bool power_status;
>  module_param(power_status, bool, 0600);
>  MODULE_PARM_DESC(power_status, "Report power status in /proc/i8k");
> +#endif
>  
>  static uint fan_mult;
>  module_param(fan_mult, uint, 0);
> @@ -109,18 +111,6 @@ static uint fan_max;
>  module_param(fan_max, uint, 0);
>  MODULE_PARM_DESC(fan_max, "Maximum configurable fan speed (default: autodetect)");
>  
> -static int i8k_open_fs(struct inode *inode, struct file *file);
> -static long i8k_ioctl(struct file *, unsigned int, unsigned long);
> -
> -static const struct file_operations i8k_fops = {
> -	.owner		= THIS_MODULE,
> -	.open		= i8k_open_fs,
> -	.read		= seq_read,
> -	.llseek		= seq_lseek,
> -	.release	= single_release,
> -	.unlocked_ioctl	= i8k_ioctl,
> -};
> -
>  struct smm_regs {
>  	unsigned int eax;
>  	unsigned int ebx __packed;
> @@ -221,45 +211,6 @@ out:
>  }
>  
>  /*
> - * Read the Fn key status.
> - */
> -static int i8k_get_fn_status(void)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
> -	int rc;
> -
> -	rc = i8k_smm(&regs);
> -	if (rc < 0)
> -		return rc;
> -
> -	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
> -	case I8K_FN_UP:
> -		return I8K_VOL_UP;
> -	case I8K_FN_DOWN:
> -		return I8K_VOL_DOWN;
> -	case I8K_FN_MUTE:
> -		return I8K_VOL_MUTE;
> -	default:
> -		return 0;
> -	}
> -}
> -
> -/*
> - * Read the power status.
> - */
> -static int i8k_get_power_status(void)
> -{
> -	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
> -	int rc;
> -
> -	rc = i8k_smm(&regs);
> -	if (rc < 0)
> -		return rc;
> -
> -	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
> -}
> -
> -/*
>   * Read the fan status.
>   */
>  static int i8k_get_fan_status(int fan)
> @@ -378,6 +329,51 @@ static int i8k_get_dell_signature(int req_fn)
>  	return regs.eax == 1145651527 && regs.edx == 1145392204 ? 0 : -1;
>  }
>  
> +#if IS_ENABLED(CONFIG_I8K)
> +
> +/*
> + * Read the Fn key status.
> + */
> +static int i8k_get_fn_status(void)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_FN_STATUS, };
> +	int rc;
> +
> +	rc = i8k_smm(&regs);
> +	if (rc < 0)
> +		return rc;
> +
> +	switch ((regs.eax >> I8K_FN_SHIFT) & I8K_FN_MASK) {
> +	case I8K_FN_UP:
> +		return I8K_VOL_UP;
> +	case I8K_FN_DOWN:
> +		return I8K_VOL_DOWN;
> +	case I8K_FN_MUTE:
> +		return I8K_VOL_MUTE;
> +	default:
> +		return 0;
> +	}
> +}
> +
> +/*
> + * Read the power status.
> + */
> +static int i8k_get_power_status(void)
> +{
> +	struct smm_regs regs = { .eax = I8K_SMM_POWER_STATUS, };
> +	int rc;
> +
> +	rc = i8k_smm(&regs);
> +	if (rc < 0)
> +		return rc;
> +
> +	return (regs.eax & 0xff) == I8K_POWER_AC ? I8K_AC : I8K_BATTERY;
> +}
> +
> +/*
> + * Procfs interface
> + */
> +
>  static int
>  i8k_ioctl_unlocked(struct file *fp, unsigned int cmd, unsigned long arg)
>  {
> @@ -528,6 +524,37 @@ static int i8k_open_fs(struct inode *inode, struct file *file)
>  	return single_open(file, i8k_proc_show, NULL);
>  }
>  
> +static const struct file_operations i8k_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= i8k_open_fs,
> +	.read		= seq_read,
> +	.llseek		= seq_lseek,
> +	.release	= single_release,
> +	.unlocked_ioctl	= i8k_ioctl,
> +};
> +
> +static void __init i8k_init_procfs(void)
> +{
> +	/* Register the proc entry */
> +	proc_create("i8k", 0, NULL, &i8k_fops);
> +}
> +
> +static void __exit i8k_exit_procfs(void)
> +{
> +	remove_proc_entry("i8k", NULL);
> +}
> +
> +#else
> +
> +static inline void __init i8k_init_procfs(void)
> +{
> +}
> +
> +static inline void __exit i8k_exit_procfs(void)
> +{
> +}
> +
> +#endif
>  
>  /*
>   * Hwmon interface
> @@ -750,8 +777,8 @@ static int __init i8k_init_hwmon(void)
>  	if (err >= 0)
>  		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
>  
> -	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k", NULL,
> -							  i8k_groups);
> +	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "dell-smm",
> +							  NULL, i8k_groups);

dell-smm is not a valid name, see hwmon_device_register_with_groups() for
more info (dash not allowed). Because of this, the driver can't be loaded.

>  	if (IS_ERR(i8k_hwmon_dev)) {
>  		err = PTR_ERR(i8k_hwmon_dev);
>  		i8k_hwmon_dev = NULL;
> @@ -976,33 +1003,24 @@ static int __init i8k_probe(void)
>  
>  static int __init i8k_init(void)
>  {
> -	struct proc_dir_entry *proc_i8k;
>  	int err;
>  
>  	/* Are we running on an supported laptop? */
>  	if (i8k_probe())
>  		return -ENODEV;
>  
> -	/* Register the proc entry */
> -	proc_i8k = proc_create("i8k", 0, NULL, &i8k_fops);
> -	if (!proc_i8k)
> -		return -ENOENT;
> -
>  	err = i8k_init_hwmon();
>  	if (err)
> -		goto exit_remove_proc;
> +		return err;
>  
> +	i8k_init_procfs();
>  	return 0;
> -
> - exit_remove_proc:
> -	remove_proc_entry("i8k", NULL);
> -	return err;
>  }
>  
>  static void __exit i8k_exit(void)
>  {
>  	hwmon_device_unregister(i8k_hwmon_dev);
> -	remove_proc_entry("i8k", NULL);
> +	i8k_exit_procfs();
>  }
>  
>  module_init(i8k_init);
> 


^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-06-27 11:34           ` Gabriele Mazzotta
@ 2015-06-27 12:47             ` Pali Rohár
  2015-06-27 12:55               ` Gabriele Mazzotta
  0 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-06-27 12:47 UTC (permalink / raw)
  To: Gabriele Mazzotta
  Cc: Greg Kroah-Hartman, Guenter Roeck, Arnd Bergmann, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger, linux-kernel,
	lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 1084 bytes --]

On Saturday 27 June 2015 13:34:30 Gabriele Mazzotta wrote:
> Hi Pali,
> 
> I've just noticed an issue with this patch. See the comment here
> below.
> 
> Gabriele
> 
> On Wednesday 29 April 2015 13:41:26 Pali Rohár wrote:
> > @@ -750,8 +777,8 @@ static int __init i8k_init_hwmon(void)
> > 
> >  	if (err >= 0)
> >  	
> >  		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
> > 
> > -	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k",
> > NULL, -							  i8k_groups);
> > +	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL,
> > "dell-smm", +							  NULL, i8k_groups);
> 
> dell-smm is not a valid name, see hwmon_device_register_with_groups()
> for more info (dash not allowed). Because of this, the driver can't
> be loaded.
> 

How it is possible? It worked fine on my tested dell machine...

And now I see, I probably tested this change with older kernel version 
(ubuntu/3.13) which did not have that check for invalid characters...

Renaming dell-smm to dell_smm should fix this problem, right?

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-06-27 12:47             ` Pali Rohár
@ 2015-06-27 12:55               ` Gabriele Mazzotta
  2015-06-27 13:01                 ` Pali Rohár
  0 siblings, 1 reply; 40+ messages in thread
From: Gabriele Mazzotta @ 2015-06-27 12:55 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Greg Kroah-Hartman, Guenter Roeck, Arnd Bergmann, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger, linux-kernel,
	lm-sensors

On Saturday 27 June 2015 14:47:16 Pali Rohár wrote:
> On Saturday 27 June 2015 13:34:30 Gabriele Mazzotta wrote:
> > Hi Pali,
> > 
> > I've just noticed an issue with this patch. See the comment here
> > below.
> > 
> > Gabriele
> > 
> > On Wednesday 29 April 2015 13:41:26 Pali Rohár wrote:
> > > @@ -750,8 +777,8 @@ static int __init i8k_init_hwmon(void)
> > > 
> > >  	if (err >= 0)
> > >  	
> > >  		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
> > > 
> > > -	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL, "i8k",
> > > NULL, -							  i8k_groups);
> > > +	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL,
> > > "dell-smm", +							  NULL, i8k_groups);
> > 
> > dell-smm is not a valid name, see hwmon_device_register_with_groups()
> > for more info (dash not allowed). Because of this, the driver can't
> > be loaded.
> > 
> 
> How it is possible? It worked fine on my tested dell machine...
> 
> And now I see, I probably tested this change with older kernel version 
> (ubuntu/3.13) which did not have that check for invalid characters...
> 
> Renaming dell-smm to dell_smm should fix this problem, right?

Yes, everything works just fine with the underscore.

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-06-27 12:55               ` Gabriele Mazzotta
@ 2015-06-27 13:01                 ` Pali Rohár
  2015-06-27 13:21                   ` Gabriele Mazzotta
  0 siblings, 1 reply; 40+ messages in thread
From: Pali Rohár @ 2015-06-27 13:01 UTC (permalink / raw)
  To: Gabriele Mazzotta
  Cc: Greg Kroah-Hartman, Guenter Roeck, Arnd Bergmann, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger, linux-kernel,
	lm-sensors

[-- Attachment #1: Type: Text/Plain, Size: 1427 bytes --]

On Saturday 27 June 2015 14:55:40 Gabriele Mazzotta wrote:
> On Saturday 27 June 2015 14:47:16 Pali Rohár wrote:
> > On Saturday 27 June 2015 13:34:30 Gabriele Mazzotta wrote:
> > > Hi Pali,
> > > 
> > > I've just noticed an issue with this patch. See the comment here
> > > below.
> > > 
> > > Gabriele
> > > 
> > > On Wednesday 29 April 2015 13:41:26 Pali Rohár wrote:
> > > > @@ -750,8 +777,8 @@ static int __init i8k_init_hwmon(void)
> > > > 
> > > >  	if (err >= 0)
> > > >  	
> > > >  		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
> > > > 
> > > > -	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL,
> > > > "i8k", NULL, -							  i8k_groups);
> > > > +	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL,
> > > > "dell-smm", +							  NULL, i8k_groups);
> > > 
> > > dell-smm is not a valid name, see
> > > hwmon_device_register_with_groups() for more info (dash not
> > > allowed). Because of this, the driver can't be loaded.
> > 
> > How it is possible? It worked fine on my tested dell machine...
> > 
> > And now I see, I probably tested this change with older kernel
> > version (ubuntu/3.13) which did not have that check for invalid
> > characters...
> > 
> > Renaming dell-smm to dell_smm should fix this problem, right?
> 
> Yes, everything works just fine with the underscore.

Ok, will you send patch for this small fix?

-- 
Pali Rohár
pali.rohar@gmail.com

[-- Attachment #2: This is a digitally signed message part. --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 40+ messages in thread

* Re: [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k
  2015-06-27 13:01                 ` Pali Rohár
@ 2015-06-27 13:21                   ` Gabriele Mazzotta
  0 siblings, 0 replies; 40+ messages in thread
From: Gabriele Mazzotta @ 2015-06-27 13:21 UTC (permalink / raw)
  To: Pali Rohár
  Cc: Greg Kroah-Hartman, Guenter Roeck, Arnd Bergmann, Jean Delvare,
	Steven Honeyman, Valdis.Kletnieks, Jochen Eisinger, linux-kernel,
	lm-sensors

On Saturday 27 June 2015 15:01:34 Pali Rohár wrote:
> On Saturday 27 June 2015 14:55:40 Gabriele Mazzotta wrote:
> > On Saturday 27 June 2015 14:47:16 Pali Rohár wrote:
> > > On Saturday 27 June 2015 13:34:30 Gabriele Mazzotta wrote:
> > > > Hi Pali,
> > > > 
> > > > I've just noticed an issue with this patch. See the comment here
> > > > below.
> > > > 
> > > > Gabriele
> > > > 
> > > > On Wednesday 29 April 2015 13:41:26 Pali Rohár wrote:
> > > > > @@ -750,8 +777,8 @@ static int __init i8k_init_hwmon(void)
> > > > > 
> > > > >  	if (err >= 0)
> > > > >  	
> > > > >  		i8k_hwmon_flags |= I8K_HWMON_HAVE_FAN2;
> > > > > 
> > > > > -	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL,
> > > > > "i8k", NULL, -							  i8k_groups);
> > > > > +	i8k_hwmon_dev = hwmon_device_register_with_groups(NULL,
> > > > > "dell-smm", +							  NULL, i8k_groups);
> > > > 
> > > > dell-smm is not a valid name, see
> > > > hwmon_device_register_with_groups() for more info (dash not
> > > > allowed). Because of this, the driver can't be loaded.
> > > 
> > > How it is possible? It worked fine on my tested dell machine...
> > > 
> > > And now I see, I probably tested this change with older kernel
> > > version (ubuntu/3.13) which did not have that check for invalid
> > > characters...
> > > 
> > > Renaming dell-smm to dell_smm should fix this problem, right?
> > 
> > Yes, everything works just fine with the underscore.
> 
> Ok, will you send patch for this small fix?

OK, I'll do it shortly.

^ permalink raw reply	[flat|nested] 40+ messages in thread

end of thread, other threads:[~2015-06-27 13:22 UTC | newest]

Thread overview: 40+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-28 10:24 [PATCH 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
2015-03-28 10:24 ` [PATCH 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
2015-03-28 14:31   ` Guenter Roeck
2015-03-28 10:24 ` [PATCH 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
2015-03-28 11:04   ` Paul Bolle
2015-03-28 12:54     ` Steven Honeyman
2015-03-28 14:13       ` Guenter Roeck
2015-03-28 22:00       ` Pali Rohár
2015-03-28 22:20         ` Guenter Roeck
2015-03-28 22:44           ` Paul Bolle
2015-03-29  0:55             ` Guenter Roeck
2015-03-30  8:01               ` Paul Bolle
2015-03-28 21:55     ` Pali Rohár
2015-03-28 22:06       ` Paul Bolle
2015-03-28 22:19         ` Steven Honeyman
2015-03-28 22:33           ` Pali Rohár
2015-03-30  7:44             ` Jean Delvare
2015-03-28 14:23   ` Guenter Roeck
2015-03-28 22:04     ` Pali Rohár
2015-03-29 12:57 ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Pali Rohár
2015-03-29 12:57   ` [PATCH v2 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
2015-03-29 12:57   ` [PATCH v2 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
2015-04-28 12:38     ` Greg Kroah-Hartman
2015-04-29 11:41       ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Pali Rohár
2015-04-29 11:41         ` [PATCH v3 2/2] hwmon: Allow to compile dell-smm-hwmon driver without /proc/i8k Pali Rohár
2015-06-27 11:34           ` Gabriele Mazzotta
2015-06-27 12:47             ` Pali Rohár
2015-06-27 12:55               ` Gabriele Mazzotta
2015-06-27 13:01                 ` Pali Rohár
2015-06-27 13:21                   ` Gabriele Mazzotta
2015-04-29 12:07         ` [PATCH v3 1/2] hwmon: Rename i8k driver to dell-smm-hwmon and move it to hwmon tree Greg Kroah-Hartman
2015-03-31  3:32   ` [PATCH v2 0/2] i8k.c => dell-smm-hwmon.c Guenter Roeck
2015-03-31 13:56     ` Greg Kroah-Hartman
2015-04-09 14:02       ` Pali Rohár
2015-04-21 13:30         ` Pali Rohár
2015-04-21 13:40           ` Guenter Roeck
2015-04-21 13:52             ` Greg Kroah-Hartman
2015-04-21 15:24               ` Guenter Roeck
2015-04-21 15:30                 ` Greg Kroah-Hartman
2015-04-27 18:39                   ` Pali Rohár

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).