summary refs log tree commit diff
path: root/drivers/hwmon
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2009-04-07 08:44:43 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2009-04-07 08:44:43 -0700
commit10a0d912898ac2fe49094acf2c1339d0fb4c2bc6 (patch)
treeca87b31c5b958599b313e40cc141748664687c51 /drivers/hwmon
parent51dcdfec6a274afc1c6fce180d582add9ff512c0 (diff)
parent2c03d07ad54db03b813bb98c469790c07ca9f5dd (diff)
downloadlinux-10a0d912898ac2fe49094acf2c1339d0fb4c2bc6.tar.gz
Merge branch 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6
* 'hwmon-for-linus' of git://jdelvare.pck.nerim.net/jdelvare-2.6:
  hwmon: Add Asus ATK0110 support
  hwmon: (lm95241) Convert to new-style i2c driver
Diffstat (limited to 'drivers/hwmon')
-rw-r--r--drivers/hwmon/Kconfig12
-rw-r--r--drivers/hwmon/Makefile1
-rw-r--r--drivers/hwmon/asus_atk0110.c1009
-rw-r--r--drivers/hwmon/lm95241.c112
4 files changed, 1069 insertions, 65 deletions
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 9b17dffc1c01..0e8a9185f676 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -248,6 +248,18 @@ config SENSORS_ASB100
 	  This driver can also be built as a module.  If so, the module
 	  will be called asb100.
 
+config SENSORS_ATK0110
+	tristate "ASUS ATK0110 ACPI hwmon"
+	depends on X86 && ACPI && EXPERIMENTAL
+	help
+	  If you say yes here you get support for the ACPI hardware
+	  monitoring interface found in many ASUS motherboards. This
+	  driver will provide readings of fans, voltages and temperatures
+	  through the system firmware.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called asus_atk0110.
+
 config SENSORS_ATXP1
 	tristate "Attansic ATXP1 VID controller"
 	depends on I2C && EXPERIMENTAL
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 96dc7f9439d2..1d3757837b4f 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_SENSORS_ADT7475)	+= adt7475.o
 
 obj-$(CONFIG_SENSORS_APPLESMC)	+= applesmc.o
 obj-$(CONFIG_SENSORS_AMS)	+= ams/
+obj-$(CONFIG_SENSORS_ATK0110)	+= asus_atk0110.o
 obj-$(CONFIG_SENSORS_ATXP1)	+= atxp1.o
 obj-$(CONFIG_SENSORS_CORETEMP)	+= coretemp.o
 obj-$(CONFIG_SENSORS_DME1737)	+= dme1737.o
diff --git a/drivers/hwmon/asus_atk0110.c b/drivers/hwmon/asus_atk0110.c
new file mode 100644
index 000000000000..0897edef2574
--- /dev/null
+++ b/drivers/hwmon/asus_atk0110.c
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2007-2009 Luca Tettamanti <kronos.it@gmail.com>
+ *
+ * This file is released under the GPLv2
+ * See COPYING in the top level directory of the kernel tree.
+ */
+
+#include <linux/kernel.h>
+#include <linux/hwmon.h>
+#include <linux/list.h>
+#include <linux/module.h>
+
+#include <acpi/acpi.h>
+#include <acpi/acpixf.h>
+#include <acpi/acpi_drivers.h>
+#include <acpi/acpi_bus.h>
+
+
+#define ATK_HID "ATK0110"
+
+/* Minimum time between readings, enforced in order to avoid
+ * hogging the CPU.
+ */
+#define CACHE_TIME		HZ
+
+#define BOARD_ID		"MBIF"
+#define METHOD_ENUMERATE	"GGRP"
+#define METHOD_READ		"GITM"
+#define METHOD_WRITE		"SITM"
+#define METHOD_OLD_READ_TMP	"RTMP"
+#define METHOD_OLD_READ_VLT	"RVLT"
+#define METHOD_OLD_READ_FAN	"RFAN"
+#define METHOD_OLD_ENUM_TMP	"TSIF"
+#define METHOD_OLD_ENUM_VLT	"VSIF"
+#define METHOD_OLD_ENUM_FAN	"FSIF"
+
+#define ATK_MUX_HWMON		0x00000006ULL
+
+#define ATK_CLASS_MASK		0xff000000ULL
+#define ATK_CLASS_FREQ_CTL	0x03000000ULL
+#define ATK_CLASS_FAN_CTL	0x04000000ULL
+#define ATK_CLASS_HWMON		0x06000000ULL
+
+#define ATK_TYPE_MASK		0x00ff0000ULL
+#define HWMON_TYPE_VOLT		0x00020000ULL
+#define HWMON_TYPE_TEMP		0x00030000ULL
+#define HWMON_TYPE_FAN		0x00040000ULL
+
+#define HWMON_SENSOR_ID_MASK	0x0000ffffULL
+
+enum atk_pack_member {
+	HWMON_PACK_FLAGS,
+	HWMON_PACK_NAME,
+	HWMON_PACK_LIMIT1,
+	HWMON_PACK_LIMIT2,
+	HWMON_PACK_ENABLE
+};
+
+/* New package format */
+#define _HWMON_NEW_PACK_SIZE	7
+#define _HWMON_NEW_PACK_FLAGS	0
+#define _HWMON_NEW_PACK_NAME	1
+#define _HWMON_NEW_PACK_UNK1	2
+#define _HWMON_NEW_PACK_UNK2	3
+#define _HWMON_NEW_PACK_LIMIT1	4
+#define _HWMON_NEW_PACK_LIMIT2	5
+#define _HWMON_NEW_PACK_ENABLE	6
+
+/* Old package format */
+#define _HWMON_OLD_PACK_SIZE	5
+#define _HWMON_OLD_PACK_FLAGS	0
+#define _HWMON_OLD_PACK_NAME	1
+#define _HWMON_OLD_PACK_LIMIT1	2
+#define _HWMON_OLD_PACK_LIMIT2	3
+#define _HWMON_OLD_PACK_ENABLE	4
+
+
+struct atk_data {
+	struct device *hwmon_dev;
+	acpi_handle atk_handle;
+	struct acpi_device *acpi_dev;
+
+	bool old_interface;
+
+	/* old interface */
+	acpi_handle rtmp_handle;
+	acpi_handle rvlt_handle;
+	acpi_handle rfan_handle;
+	/* new inteface */
+	acpi_handle enumerate_handle;
+	acpi_handle read_handle;
+
+	int voltage_count;
+	int temperature_count;
+	int fan_count;
+	struct list_head sensor_list;
+};
+
+
+typedef ssize_t (*sysfs_show_func)(struct device *dev,
+			struct device_attribute *attr, char *buf);
+
+static const struct acpi_device_id atk_ids[] = {
+	{ATK_HID, 0},
+	{"", 0},
+};
+MODULE_DEVICE_TABLE(acpi, atk_ids);
+
+#define ATTR_NAME_SIZE 16 /* Worst case is "tempN_input" */
+
+struct atk_sensor_data {
+	struct list_head list;
+	struct atk_data *data;
+	struct device_attribute label_attr;
+	struct device_attribute input_attr;
+	struct device_attribute limit1_attr;
+	struct device_attribute limit2_attr;
+	char label_attr_name[ATTR_NAME_SIZE];
+	char input_attr_name[ATTR_NAME_SIZE];
+	char limit1_attr_name[ATTR_NAME_SIZE];
+	char limit2_attr_name[ATTR_NAME_SIZE];
+	u64 id;
+	u64 type;
+	u64 limit1;
+	u64 limit2;
+	u64 cached_value;
+	unsigned long last_updated; /* in jiffies */
+	bool is_valid;
+	char const *acpi_name;
+};
+
+struct atk_acpi_buffer_u64 {
+	union acpi_object buf;
+	u64 value;
+};
+
+static int atk_add(struct acpi_device *device);
+static int atk_remove(struct acpi_device *device, int type);
+static void atk_print_sensor(struct atk_data *data, union acpi_object *obj);
+static int atk_read_value(struct atk_sensor_data *sensor, u64 *value);
+static void atk_free_sensors(struct atk_data *data);
+
+static struct acpi_driver atk_driver = {
+	.name	= ATK_HID,
+	.class	= "hwmon",
+	.ids	= atk_ids,
+	.ops	= {
+		.add	= atk_add,
+		.remove	= atk_remove,
+	},
+};
+
+#define input_to_atk_sensor(attr) \
+	container_of(attr, struct atk_sensor_data, input_attr)
+
+#define label_to_atk_sensor(attr) \
+	container_of(attr, struct atk_sensor_data, label_attr)
+
+#define limit1_to_atk_sensor(attr) \
+	container_of(attr, struct atk_sensor_data, limit1_attr)
+
+#define limit2_to_atk_sensor(attr) \
+	container_of(attr, struct atk_sensor_data, limit2_attr)
+
+static ssize_t atk_input_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct atk_sensor_data *s = input_to_atk_sensor(attr);
+	u64 value;
+	int err;
+
+	err = atk_read_value(s, &value);
+	if (err)
+		return err;
+
+	if (s->type == HWMON_TYPE_TEMP)
+		/* ACPI returns decidegree */
+		value *= 100;
+
+	return sprintf(buf, "%llu\n", value);
+}
+
+static ssize_t atk_label_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct atk_sensor_data *s = label_to_atk_sensor(attr);
+
+	return sprintf(buf, "%s\n", s->acpi_name);
+}
+
+static ssize_t atk_limit1_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct atk_sensor_data *s = limit1_to_atk_sensor(attr);
+	u64 value = s->limit1;
+
+	if (s->type == HWMON_TYPE_TEMP)
+		value *= 100;
+
+	return sprintf(buf, "%lld\n", value);
+}
+
+static ssize_t atk_limit2_show(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct atk_sensor_data *s = limit2_to_atk_sensor(attr);
+	u64 value = s->limit2;
+
+	if (s->type == HWMON_TYPE_TEMP)
+		value *= 100;
+
+	return sprintf(buf, "%lld\n", value);
+}
+
+static ssize_t atk_name_show(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	return sprintf(buf, "atk0110\n");
+}
+static struct device_attribute atk_name_attr =
+		__ATTR(name, 0444, atk_name_show, NULL);
+
+static void atk_init_attribute(struct device_attribute *attr, char *name,
+		sysfs_show_func show)
+{
+	attr->attr.name = name;
+	attr->attr.mode = 0444;
+	attr->show = show;
+	attr->store = NULL;
+}
+
+
+static union acpi_object *atk_get_pack_member(struct atk_data *data,
+						union acpi_object *pack,
+						enum atk_pack_member m)
+{
+	bool old_if = data->old_interface;
+	int offset;
+
+	switch (m) {
+	case HWMON_PACK_FLAGS:
+		offset = old_if ? _HWMON_OLD_PACK_FLAGS : _HWMON_NEW_PACK_FLAGS;
+		break;
+	case HWMON_PACK_NAME:
+		offset = old_if ? _HWMON_OLD_PACK_NAME : _HWMON_NEW_PACK_NAME;
+		break;
+	case HWMON_PACK_LIMIT1:
+		offset = old_if ? _HWMON_OLD_PACK_LIMIT1 :
+				  _HWMON_NEW_PACK_LIMIT1;
+		break;
+	case HWMON_PACK_LIMIT2:
+		offset = old_if ? _HWMON_OLD_PACK_LIMIT2 :
+				  _HWMON_NEW_PACK_LIMIT2;
+		break;
+	case HWMON_PACK_ENABLE:
+		offset = old_if ? _HWMON_OLD_PACK_ENABLE :
+				  _HWMON_NEW_PACK_ENABLE;
+		break;
+	default:
+		return NULL;
+	}
+
+	return &pack->package.elements[offset];
+}
+
+
+/* New package format is:
+ * - flag (int)
+ *	class - used for de-muxing the request to the correct GITn
+ *	type (volt, temp, fan)
+ *	sensor id |
+ *	sensor id - used for de-muxing the request _inside_ the GITn
+ * - name (str)
+ * - unknown (int)
+ * - unknown (int)
+ * - limit1 (int)
+ * - limit2 (int)
+ * - enable (int)
+ *
+ * The old package has the same format but it's missing the two unknown fields.
+ */
+static int validate_hwmon_pack(struct atk_data *data, union acpi_object *obj)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	union acpi_object *tmp;
+	bool old_if = data->old_interface;
+	int const expected_size = old_if ? _HWMON_OLD_PACK_SIZE :
+					   _HWMON_NEW_PACK_SIZE;
+
+	if (obj->type != ACPI_TYPE_PACKAGE) {
+		dev_warn(dev, "Invalid type: %d\n", obj->type);
+		return -EINVAL;
+	}
+
+	if (obj->package.count != expected_size) {
+		dev_warn(dev, "Invalid package size: %d, expected: %d\n",
+				obj->package.count, expected_size);
+		return -EINVAL;
+	}
+
+	tmp = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS);
+	if (tmp->type != ACPI_TYPE_INTEGER) {
+		dev_warn(dev, "Invalid type (flag): %d\n", tmp->type);
+		return -EINVAL;
+	}
+
+	tmp = atk_get_pack_member(data, obj, HWMON_PACK_NAME);
+	if (tmp->type != ACPI_TYPE_STRING) {
+		dev_warn(dev, "Invalid type (name): %d\n", tmp->type);
+		return -EINVAL;
+	}
+
+	/* Don't check... we don't know what they're useful for anyway */
+#if 0
+	tmp = &obj->package.elements[HWMON_PACK_UNK1];
+	if (tmp->type != ACPI_TYPE_INTEGER) {
+		dev_warn(dev, "Invalid type (unk1): %d\n", tmp->type);
+		return -EINVAL;
+	}
+
+	tmp = &obj->package.elements[HWMON_PACK_UNK2];
+	if (tmp->type != ACPI_TYPE_INTEGER) {
+		dev_warn(dev, "Invalid type (unk2): %d\n", tmp->type);
+		return -EINVAL;
+	}
+#endif
+
+	tmp = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1);
+	if (tmp->type != ACPI_TYPE_INTEGER) {
+		dev_warn(dev, "Invalid type (limit1): %d\n", tmp->type);
+		return -EINVAL;
+	}
+
+	tmp = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2);
+	if (tmp->type != ACPI_TYPE_INTEGER) {
+		dev_warn(dev, "Invalid type (limit2): %d\n", tmp->type);
+		return -EINVAL;
+	}
+
+	tmp = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE);
+	if (tmp->type != ACPI_TYPE_INTEGER) {
+		dev_warn(dev, "Invalid type (enable): %d\n", tmp->type);
+		return -EINVAL;
+	}
+
+	atk_print_sensor(data, obj);
+
+	return 0;
+}
+
+static char const *atk_sensor_type(union acpi_object *flags)
+{
+	u64 type = flags->integer.value & ATK_TYPE_MASK;
+	char const *what;
+
+	switch (type) {
+	case HWMON_TYPE_VOLT:
+		what = "voltage";
+		break;
+	case HWMON_TYPE_TEMP:
+		what = "temperature";
+		break;
+	case HWMON_TYPE_FAN:
+		what = "fan";
+		break;
+	default:
+		what = "unknown";
+		break;
+	}
+
+	return what;
+}
+
+static void atk_print_sensor(struct atk_data *data, union acpi_object *obj)
+{
+#ifdef DEBUG
+	struct device *dev = &data->acpi_dev->dev;
+	union acpi_object *flags;
+	union acpi_object *name;
+	union acpi_object *limit1;
+	union acpi_object *limit2;
+	union acpi_object *enable;
+	char const *what;
+
+	flags = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS);
+	name = atk_get_pack_member(data, obj, HWMON_PACK_NAME);
+	limit1 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1);
+	limit2 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2);
+	enable = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE);
+
+	what = atk_sensor_type(flags);
+
+	dev_dbg(dev, "%s: %#llx %s [%llu-%llu] %s\n", what,
+			flags->integer.value,
+			name->string.pointer,
+			limit1->integer.value, limit2->integer.value,
+			enable->integer.value ? "enabled" : "disabled");
+#endif
+}
+
+static int atk_read_value_old(struct atk_sensor_data *sensor, u64 *value)
+{
+	struct atk_data *data = sensor->data;
+	struct device *dev = &data->acpi_dev->dev;
+	struct acpi_object_list params;
+	union acpi_object id;
+	acpi_status status;
+	acpi_handle method;
+
+	switch (sensor->type) {
+	case HWMON_TYPE_VOLT:
+		method = data->rvlt_handle;
+		break;
+	case HWMON_TYPE_TEMP:
+		method = data->rtmp_handle;
+		break;
+	case HWMON_TYPE_FAN:
+		method = data->rfan_handle;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	id.type = ACPI_TYPE_INTEGER;
+	id.integer.value = sensor->id;
+
+	params.count = 1;
+	params.pointer = &id;
+
+	status = acpi_evaluate_integer(method, NULL, &params, value);
+	if (status != AE_OK) {
+		dev_warn(dev, "%s: ACPI exception: %s\n", __func__,
+				acpi_format_exception(status));
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int atk_read_value_new(struct atk_sensor_data *sensor, u64 *value)
+{
+	struct atk_data *data = sensor->data;
+	struct device *dev = &data->acpi_dev->dev;
+	struct acpi_object_list params;
+	struct acpi_buffer ret;
+	union acpi_object id;
+	struct atk_acpi_buffer_u64 tmp;
+	acpi_status status;
+
+	id.type = ACPI_TYPE_INTEGER;
+	id.integer.value = sensor->id;
+
+	params.count = 1;
+	params.pointer = &id;
+
+	tmp.buf.type = ACPI_TYPE_BUFFER;
+	tmp.buf.buffer.pointer = (u8 *)&tmp.value;
+	tmp.buf.buffer.length = sizeof(u64);
+	ret.length = sizeof(tmp);
+	ret.pointer = &tmp;
+
+	status = acpi_evaluate_object_typed(data->read_handle, NULL, &params,
+			&ret, ACPI_TYPE_BUFFER);
+	if (status != AE_OK) {
+		dev_warn(dev, "%s: ACPI exception: %s\n", __func__,
+				acpi_format_exception(status));
+		return -EIO;
+	}
+
+	/* Return buffer format:
+	 * [0-3] "value" is valid flag
+	 * [4-7] value
+	 */
+	if (!(tmp.value & 0xffffffff)) {
+		/* The reading is not valid, possible causes:
+		 * - sensor failure
+		 * - enumeration was FUBAR (and we didn't notice)
+		 */
+		dev_info(dev, "Failure: %#llx\n", tmp.value);
+		return -EIO;
+	}
+
+	*value = (tmp.value & 0xffffffff00000000ULL) >> 32;
+
+	return 0;
+}
+
+static int atk_read_value(struct atk_sensor_data *sensor, u64 *value)
+{
+	int err;
+
+	if (!sensor->is_valid ||
+	    time_after(jiffies, sensor->last_updated + CACHE_TIME)) {
+		if (sensor->data->old_interface)
+			err = atk_read_value_old(sensor, value);
+		else
+			err = atk_read_value_new(sensor, value);
+
+		sensor->is_valid = true;
+		sensor->last_updated = jiffies;
+		sensor->cached_value = *value;
+	} else {
+		*value = sensor->cached_value;
+		err = 0;
+	}
+
+	return err;
+}
+
+static int atk_add_sensor(struct atk_data *data, union acpi_object *obj)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	union acpi_object *flags;
+	union acpi_object *name;
+	union acpi_object *limit1;
+	union acpi_object *limit2;
+	union acpi_object *enable;
+	struct atk_sensor_data *sensor;
+	char const *base_name;
+	char const *limit1_name;
+	char const *limit2_name;
+	u64 type;
+	int err;
+	int *num;
+	int start;
+
+	if (obj->type != ACPI_TYPE_PACKAGE) {
+		/* wft is this? */
+		dev_warn(dev, "Unknown type for ACPI object: (%d)\n",
+				obj->type);
+		return -EINVAL;
+	}
+
+	err = validate_hwmon_pack(data, obj);
+	if (err)
+		return err;
+
+	/* Ok, we have a valid hwmon package */
+	type = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS)->integer.value
+	       & ATK_TYPE_MASK;
+
+	switch (type) {
+	case HWMON_TYPE_VOLT:
+		base_name = "in";
+		limit1_name = "min";
+		limit2_name = "max";
+		num = &data->voltage_count;
+		start = 0;
+		break;
+	case HWMON_TYPE_TEMP:
+		base_name = "temp";
+		limit1_name = "max";
+		limit2_name = "crit";
+		num = &data->temperature_count;
+		start = 1;
+		break;
+	case HWMON_TYPE_FAN:
+		base_name = "fan";
+		limit1_name = "min";
+		limit2_name = "max";
+		num = &data->fan_count;
+		start = 1;
+		break;
+	default:
+		dev_warn(dev, "Unknown sensor type: %#llx\n", type);
+		return -EINVAL;
+	}
+
+	enable = atk_get_pack_member(data, obj, HWMON_PACK_ENABLE);
+	if (!enable->integer.value)
+		/* sensor is disabled */
+		return 0;
+
+	flags = atk_get_pack_member(data, obj, HWMON_PACK_FLAGS);
+	name = atk_get_pack_member(data, obj, HWMON_PACK_NAME);
+	limit1 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT1);
+	limit2 = atk_get_pack_member(data, obj, HWMON_PACK_LIMIT2);
+
+	sensor = kzalloc(sizeof(*sensor), GFP_KERNEL);
+	if (!sensor)
+		return -ENOMEM;
+
+	sensor->acpi_name = kstrdup(name->string.pointer, GFP_KERNEL);
+	if (!sensor->acpi_name) {
+		err = -ENOMEM;
+		goto out;
+	}
+
+	INIT_LIST_HEAD(&sensor->list);
+	sensor->type = type;
+	sensor->data = data;
+	sensor->id = flags->integer.value;
+	sensor->limit1 = limit1->integer.value;
+	sensor->limit2 = limit2->integer.value;
+
+	snprintf(sensor->input_attr_name, ATTR_NAME_SIZE,
+			"%s%d_input", base_name, start + *num);
+	atk_init_attribute(&sensor->input_attr,
+			sensor->input_attr_name,
+			atk_input_show);
+
+	snprintf(sensor->label_attr_name, ATTR_NAME_SIZE,
+			"%s%d_label", base_name, start + *num);
+	atk_init_attribute(&sensor->label_attr,
+			sensor->label_attr_name,
+			atk_label_show);
+
+	snprintf(sensor->limit1_attr_name, ATTR_NAME_SIZE,
+			"%s%d_%s", base_name, start + *num, limit1_name);
+	atk_init_attribute(&sensor->limit1_attr,
+			sensor->limit1_attr_name,
+			atk_limit1_show);
+
+	snprintf(sensor->limit2_attr_name, ATTR_NAME_SIZE,
+			"%s%d_%s", base_name, start + *num, limit2_name);
+	atk_init_attribute(&sensor->limit2_attr,
+			sensor->limit2_attr_name,
+			atk_limit2_show);
+
+	list_add(&sensor->list, &data->sensor_list);
+	(*num)++;
+
+	return 1;
+out:
+	kfree(sensor->acpi_name);
+	kfree(sensor);
+	return err;
+}
+
+static int atk_enumerate_old_hwmon(struct atk_data *data)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	struct acpi_buffer buf;
+	union acpi_object *pack;
+	acpi_status status;
+	int i, ret;
+	int count = 0;
+
+	/* Voltages */
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	status = acpi_evaluate_object_typed(data->atk_handle,
+			METHOD_OLD_ENUM_VLT, NULL, &buf, ACPI_TYPE_PACKAGE);
+	if (status != AE_OK) {
+		dev_warn(dev, METHOD_OLD_ENUM_VLT ": ACPI exception: %s\n",
+				acpi_format_exception(status));
+
+		return -ENODEV;
+	}
+
+	pack = buf.pointer;
+	for (i = 1; i < pack->package.count; i++) {
+		union acpi_object *obj = &pack->package.elements[i];
+
+		ret = atk_add_sensor(data, obj);
+		if (ret > 0)
+			count++;
+	}
+	ACPI_FREE(buf.pointer);
+
+	/* Temperatures */
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	status = acpi_evaluate_object_typed(data->atk_handle,
+			METHOD_OLD_ENUM_TMP, NULL, &buf, ACPI_TYPE_PACKAGE);
+	if (status != AE_OK) {
+		dev_warn(dev, METHOD_OLD_ENUM_TMP ": ACPI exception: %s\n",
+				acpi_format_exception(status));
+
+		ret = -ENODEV;
+		goto cleanup;
+	}
+
+	pack = buf.pointer;
+	for (i = 1; i < pack->package.count; i++) {
+		union acpi_object *obj = &pack->package.elements[i];
+
+		ret = atk_add_sensor(data, obj);
+		if (ret > 0)
+			count++;
+	}
+	ACPI_FREE(buf.pointer);
+
+	/* Fans */
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	status = acpi_evaluate_object_typed(data->atk_handle,
+			METHOD_OLD_ENUM_FAN, NULL, &buf, ACPI_TYPE_PACKAGE);
+	if (status != AE_OK) {
+		dev_warn(dev, METHOD_OLD_ENUM_FAN ": ACPI exception: %s\n",
+				acpi_format_exception(status));
+
+		ret = -ENODEV;
+		goto cleanup;
+	}
+
+	pack = buf.pointer;
+	for (i = 1; i < pack->package.count; i++) {
+		union acpi_object *obj = &pack->package.elements[i];
+
+		ret = atk_add_sensor(data, obj);
+		if (ret > 0)
+			count++;
+	}
+	ACPI_FREE(buf.pointer);
+
+	return count;
+cleanup:
+	atk_free_sensors(data);
+	return ret;
+}
+
+static int atk_enumerate_new_hwmon(struct atk_data *data)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	struct acpi_buffer buf;
+	acpi_status ret;
+	struct acpi_object_list params;
+	union acpi_object id;
+	union acpi_object *pack;
+	int err;
+	int i;
+
+	dev_dbg(dev, "Enumerating hwmon sensors\n");
+
+	id.type = ACPI_TYPE_INTEGER;
+	id.integer.value = ATK_MUX_HWMON;
+	params.count = 1;
+	params.pointer = &id;
+
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	ret = acpi_evaluate_object_typed(data->enumerate_handle, NULL, &params,
+			&buf, ACPI_TYPE_PACKAGE);
+	if (ret != AE_OK) {
+		dev_warn(dev, METHOD_ENUMERATE ": ACPI exception: %s\n",
+				acpi_format_exception(ret));
+		return -ENODEV;
+	}
+
+	/* Result must be a package */
+	pack = buf.pointer;
+
+	if (pack->package.count < 1) {
+		dev_dbg(dev, "%s: hwmon package is too small: %d\n", __func__,
+				pack->package.count);
+		err = -EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < pack->package.count; i++) {
+		union acpi_object *obj = &pack->package.elements[i];
+
+		atk_add_sensor(data, obj);
+	}
+
+	err = data->voltage_count + data->temperature_count + data->fan_count;
+
+out:
+	ACPI_FREE(buf.pointer);
+	return err;
+}
+
+static int atk_create_files(struct atk_data *data)
+{
+	struct atk_sensor_data *s;
+	int err;
+
+	list_for_each_entry(s, &data->sensor_list, list) {
+		err = device_create_file(data->hwmon_dev, &s->input_attr);
+		if (err)
+			return err;
+		err = device_create_file(data->hwmon_dev, &s->label_attr);
+		if (err)
+			return err;
+		err = device_create_file(data->hwmon_dev, &s->limit1_attr);
+		if (err)
+			return err;
+		err = device_create_file(data->hwmon_dev, &s->limit2_attr);
+		if (err)
+			return err;
+	}
+
+	err = device_create_file(data->hwmon_dev, &atk_name_attr);
+
+	return err;
+}
+
+static void atk_remove_files(struct atk_data *data)
+{
+	struct atk_sensor_data *s;
+
+	list_for_each_entry(s, &data->sensor_list, list) {
+		device_remove_file(data->hwmon_dev, &s->input_attr);
+		device_remove_file(data->hwmon_dev, &s->label_attr);
+		device_remove_file(data->hwmon_dev, &s->limit1_attr);
+		device_remove_file(data->hwmon_dev, &s->limit2_attr);
+	}
+	device_remove_file(data->hwmon_dev, &atk_name_attr);
+}
+
+static void atk_free_sensors(struct atk_data *data)
+{
+	struct list_head *head = &data->sensor_list;
+	struct atk_sensor_data *s, *tmp;
+
+	list_for_each_entry_safe(s, tmp, head, list) {
+		kfree(s->acpi_name);
+		kfree(s);
+	}
+}
+
+static int atk_register_hwmon(struct atk_data *data)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	int err;
+
+	dev_dbg(dev, "registering hwmon device\n");
+	data->hwmon_dev = hwmon_device_register(dev);
+	if (IS_ERR(data->hwmon_dev))
+		return PTR_ERR(data->hwmon_dev);
+
+	dev_dbg(dev, "populating sysfs directory\n");
+	err = atk_create_files(data);
+	if (err)
+		goto remove;
+
+	return 0;
+remove:
+	/* Cleanup the registered files */
+	atk_remove_files(data);
+	hwmon_device_unregister(data->hwmon_dev);
+	return err;
+}
+
+static int atk_check_old_if(struct atk_data *data)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	acpi_handle ret;
+	acpi_status status;
+
+	/* RTMP: read temperature */
+	status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_TMP, &ret);
+	if (status != AE_OK) {
+		dev_dbg(dev, "method " METHOD_OLD_READ_TMP " not found: %s\n",
+				acpi_format_exception(status));
+		return -ENODEV;
+	}
+	data->rtmp_handle = ret;
+
+	/* RVLT: read voltage */
+	status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_VLT, &ret);
+	if (status != AE_OK) {
+		dev_dbg(dev, "method " METHOD_OLD_READ_VLT " not found: %s\n",
+				acpi_format_exception(status));
+		return -ENODEV;
+	}
+	data->rvlt_handle = ret;
+
+	/* RFAN: read fan status */
+	status = acpi_get_handle(data->atk_handle, METHOD_OLD_READ_FAN, &ret);
+	if (status != AE_OK) {
+		dev_dbg(dev, "method " METHOD_OLD_READ_FAN " not found: %s\n",
+				acpi_format_exception(status));
+		return -ENODEV;
+	}
+	data->rfan_handle = ret;
+
+	return 0;
+}
+
+static int atk_check_new_if(struct atk_data *data)
+{
+	struct device *dev = &data->acpi_dev->dev;
+	acpi_handle ret;
+	acpi_status status;
+
+	/* Enumeration */
+	status = acpi_get_handle(data->atk_handle, METHOD_ENUMERATE, &ret);
+	if (status != AE_OK) {
+		dev_dbg(dev, "method " METHOD_ENUMERATE " not found: %s\n",
+				acpi_format_exception(status));
+		return -ENODEV;
+	}
+	data->enumerate_handle = ret;
+
+	/* De-multiplexer (read) */
+	status = acpi_get_handle(data->atk_handle, METHOD_READ, &ret);
+	if (status != AE_OK) {
+		dev_dbg(dev, "method " METHOD_READ " not found: %s\n",
+				acpi_format_exception(status));
+		return -ENODEV;
+	}
+	data->read_handle = ret;
+
+	return 0;
+}
+
+static int atk_add(struct acpi_device *device)
+{
+	acpi_status ret;
+	int err;
+	struct acpi_buffer buf;
+	union acpi_object *obj;
+	struct atk_data *data;
+
+	dev_dbg(&device->dev, "adding...\n");
+
+	data = kzalloc(sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	data->acpi_dev = device;
+	data->atk_handle = device->handle;
+	INIT_LIST_HEAD(&data->sensor_list);
+
+	buf.length = ACPI_ALLOCATE_BUFFER;
+	ret = acpi_evaluate_object_typed(data->atk_handle, BOARD_ID, NULL,
+			&buf, ACPI_TYPE_PACKAGE);
+	if (ret != AE_OK) {
+		dev_dbg(&device->dev, "atk: method MBIF not found\n");
+		err = -ENODEV;
+		goto out;
+	}
+
+	obj = buf.pointer;
+	if (obj->package.count >= 2 &&
+			obj->package.elements[1].type == ACPI_TYPE_STRING) {
+		dev_dbg(&device->dev, "board ID = %s\n",
+				obj->package.elements[1].string.pointer);
+	}
+	ACPI_FREE(buf.pointer);
+
+	/* Check for hwmon methods: first check "old" style methods; note that
+	 * both may be present: in this case we stick to the old interface;
+	 * analysis of multiple DSDTs indicates that when both interfaces
+	 * are present the new one (GGRP/GITM) is not functional.
+	 */
+	err = atk_check_old_if(data);
+	if (!err) {
+		dev_dbg(&device->dev, "Using old hwmon interface\n");
+		data->old_interface = true;
+	} else {
+		err = atk_check_new_if(data);
+		if (err)
+			goto out;
+
+		dev_dbg(&device->dev, "Using new hwmon interface\n");
+		data->old_interface = false;
+	}
+
+	if (data->old_interface)
+		err = atk_enumerate_old_hwmon(data);
+	else
+		err = atk_enumerate_new_hwmon(data);
+	if (err < 0)
+		goto out;
+	if (err == 0) {
+		dev_info(&device->dev,
+			 "No usable sensor detected, bailing out\n");
+		err = -ENODEV;
+		goto out;
+	}
+
+	err = atk_register_hwmon(data);
+	if (err)
+		goto cleanup;
+
+	device->driver_data = data;
+	return 0;
+cleanup:
+	atk_free_sensors(data);
+out:
+	kfree(data);
+	return err;
+}
+
+static int atk_remove(struct acpi_device *device, int type)
+{
+	struct atk_data *data = device->driver_data;
+	dev_dbg(&device->dev, "removing...\n");
+
+	device->driver_data = NULL;
+
+	atk_remove_files(data);
+	atk_free_sensors(data);
+	hwmon_device_unregister(data->hwmon_dev);
+
+	kfree(data);
+
+	return 0;
+}
+
+static int __init atk0110_init(void)
+{
+	int ret;
+
+	ret = acpi_bus_register_driver(&atk_driver);
+	if (ret)
+		pr_info("atk: acpi_bus_register_driver failed: %d\n", ret);
+
+	return ret;
+}
+
+static void __exit atk0110_exit(void)
+{
+	acpi_bus_unregister_driver(&atk_driver);
+}
+
+module_init(atk0110_init);
+module_exit(atk0110_exit);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hwmon/lm95241.c b/drivers/hwmon/lm95241.c
index 091d95f38aaa..e34f9e402a2c 100644
--- a/drivers/hwmon/lm95241.c
+++ b/drivers/hwmon/lm95241.c
@@ -87,25 +87,11 @@ I2C_CLIENT_INSMOD_1(lm95241);
     (val_h)) * 1000 + (val_l) * 1000 / 256)
 
 /* Functions declaration */
-static int lm95241_attach_adapter(struct i2c_adapter *adapter);
-static int lm95241_detect(struct i2c_adapter *adapter, int address,
-			  int kind);
 static void lm95241_init_client(struct i2c_client *client);
-static int lm95241_detach_client(struct i2c_client *client);
 static struct lm95241_data *lm95241_update_device(struct device *dev);
 
-/* Driver data (common to all clients) */
-static struct i2c_driver lm95241_driver = {
-	.driver = {
-		.name   = "lm95241",
-	},
-	.attach_adapter = lm95241_attach_adapter,
-	.detach_client  = lm95241_detach_client,
-};
-
 /* Client data (each client gets its own) */
 struct lm95241_data {
-	struct i2c_client client;
 	struct device *hwmon_dev;
 	struct mutex update_lock;
 	unsigned long last_updated, rate; /* in jiffies */
@@ -323,42 +309,16 @@ static const struct attribute_group lm95241_group = {
 	.attrs = lm95241_attributes,
 };
 
-/* Init/exit code */
-static int lm95241_attach_adapter(struct i2c_adapter *adapter)
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int lm95241_detect(struct i2c_client *new_client, int kind,
+			  struct i2c_board_info *info)
 {
-	if (!(adapter->class & I2C_CLASS_HWMON))
-		return 0;
-	return i2c_probe(adapter, &addr_data, lm95241_detect);
-}
-
-/*
- * The following function does more than just detection. If detection
- * succeeds, it also registers the new chip.
- */
-static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind)
-{
-	struct i2c_client *new_client;
-	struct lm95241_data *data;
-	int err = 0;
+	struct i2c_adapter *adapter = new_client->adapter;
+	int address = new_client->addr;
 	const char *name = "";
 
 	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
-		goto exit;
-
-	data = kzalloc(sizeof(struct lm95241_data), GFP_KERNEL);
-	if (!data) {
-		err = -ENOMEM;
-		goto exit;
-	}
-
-	/* The common I2C client data is placed right before the
-	   LM95241-specific data. */
-	new_client = &data->client;
-	i2c_set_clientdata(new_client, data);
-	new_client->addr = address;
-	new_client->adapter = adapter;
-	new_client->driver = &lm95241_driver;
-	new_client->flags = 0;
+		return -ENODEV;
 
 	/*
 	 * Now we do the remaining detection. A negative kind means that
@@ -378,7 +338,7 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind)
 			dev_dbg(&adapter->dev,
 				"LM95241 detection failed at 0x%02x.\n",
 				address);
-			goto exit_free;
+			return -ENODEV;
 		}
 	}
 
@@ -392,23 +352,32 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind)
 
 			if (kind <= 0) { /* identification failed */
 				dev_info(&adapter->dev, "Unsupported chip\n");
-				goto exit_free;
+				return -ENODEV;
 			}
 		}
 	}
 
+	/* Fill the i2c board info */
 	if (kind == lm95241)
 		name = "lm95241";
+	strlcpy(info->type, name, I2C_NAME_SIZE);
+	return 0;
+}
 
-	/* We can fill in the remaining client fields */
-	strlcpy(new_client->name, name, I2C_NAME_SIZE);
-	data->valid = 0;
-	mutex_init(&data->update_lock);
+static int lm95241_probe(struct i2c_client *new_client,
+			 const struct i2c_device_id *id)
+{
+	struct lm95241_data *data;
+	int err;
 
-	/* Tell the I2C layer a new client has arrived */
-	err = i2c_attach_client(new_client);
-	if (err)
-		goto exit_free;
+	data = kzalloc(sizeof(struct lm95241_data), GFP_KERNEL);
+	if (!data) {
+		err = -ENOMEM;
+		goto exit;
+	}
+
+	i2c_set_clientdata(new_client, data);
+	mutex_init(&data->update_lock);
 
 	/* Initialize the LM95241 chip */
 	lm95241_init_client(new_client);
@@ -416,7 +385,7 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind)
 	/* Register sysfs hooks */
 	err = sysfs_create_group(&new_client->dev.kobj, &lm95241_group);
 	if (err)
-		goto exit_detach;
+		goto exit_free;
 
 	data->hwmon_dev = hwmon_device_register(&new_client->dev);
 	if (IS_ERR(data->hwmon_dev)) {
@@ -428,8 +397,6 @@ static int lm95241_detect(struct i2c_adapter *adapter, int address, int kind)
 
 exit_remove_files:
 	sysfs_remove_group(&new_client->dev.kobj, &lm95241_group);
-exit_detach:
-	i2c_detach_client(new_client);
 exit_free:
 	kfree(data);
 exit:
@@ -456,18 +423,14 @@ static void lm95241_init_client(struct i2c_client *client)
 				  data->model);
 }
 
-static int lm95241_detach_client(struct i2c_client *client)
+static int lm95241_remove(struct i2c_client *client)
 {
 	struct lm95241_data *data = i2c_get_clientdata(client);
-	int err;
 
 	hwmon_device_unregister(data->hwmon_dev);
 	sysfs_remove_group(&client->dev.kobj, &lm95241_group);
 
-	err = i2c_detach_client(client);
-	if (err)
-		return err;
-
+	i2c_set_clientdata(client, NULL);
 	kfree(data);
 	return 0;
 }
@@ -509,6 +472,25 @@ static struct lm95241_data *lm95241_update_device(struct device *dev)
 	return data;
 }
 
+/* Driver data (common to all clients) */
+static const struct i2c_device_id lm95241_id[] = {
+	{ "lm95241", lm95241 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, lm95241_id);
+
+static struct i2c_driver lm95241_driver = {
+	.class		= I2C_CLASS_HWMON,
+	.driver = {
+		.name   = "lm95241",
+	},
+	.probe		= lm95241_probe,
+	.remove		= lm95241_remove,
+	.id_table	= lm95241_id,
+	.detect		= lm95241_detect,
+	.address_data	= &addr_data,
+};
+
 static int __init sensors_lm95241_init(void)
 {
 	return i2c_add_driver(&lm95241_driver);