From 01e88f25985d8ea5866c9a73d56b3a9a9145066f Mon Sep 17 00:00:00 2001
From: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Date: Tue, 8 Jan 2008 13:02:41 -0200
Subject: ACPI: thinkpad-acpi: add CMOS NVRAM polling for hot keys (v9)

Older ThinkPad models do not export some of the hot keys over the
event-based ACPI hot key interface.  For these models, one has to poll
the CMOS NVRAM to check the key state at a rate faster than the expected
rate at which the user might repeatedly press the same hot key.

This patch implements this functionality for many of the hotkeys in a
transparent way: hot keys will now Just Work, and the driver knows the
best approach (events or NVRAM polling) to employ, based on the
HKEY.MHKA ACPI method.

Also, the driver can turn off the polling when there are no users for
the hot keys that need such polling.

The NVRAM-based hot keys of the A3x series that have never been
implemented by later models are not supported, to avoid changes in the
keymap of the input devices that could cause headaches in the future.

There is a Kconfig option to avoid compiling the NVRAM polling code, as
it is not very small, and unlikely to be useful on any ThinkPad newer
than a T40, X31 or R52.

This feature is based on a previous effort by Richard Hughes.

Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br>
Cc: Richard Hughes <hughsient@gmail.com>
Signed-off-by: Len Brown <len.brown@intel.com>
---
 drivers/misc/Kconfig | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

(limited to 'drivers/misc/Kconfig')

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b5e67c0ff43..b1f9a405c82 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -219,6 +219,25 @@ config THINKPAD_ACPI_BAY
 
 	  If you are not sure, say Y here.
 
+config THINKPAD_ACPI_HOTKEY_POLL
+	bool "Suport NVRAM polling for hot keys"
+	depends on THINKPAD_ACPI
+	default y
+	---help---
+	  Some thinkpad models benefit from NVRAM polling to detect a few of
+	  the hot key press events.  If you know your ThinkPad model does not
+	  need to do NVRAM polling to support any of the hot keys you use,
+	  unselecting this option will save about 1kB of memory.
+
+	  ThinkPads T40 and newer, R52 and newer, and X31 and newer are
+	  unlikely to need NVRAM polling in their latest BIOS versions.
+
+	  NVRAM polling can detect at most the following keys: ThinkPad/Access
+	  IBM, Zoom, Switch Display (fn+F7), ThinkLight, Volume up/down/mute,
+	  Brightness up/down, Display Expand (fn+F8), Hibernate (fn+F12).
+
+	  If you are not sure, say Y here.  The driver enables polling only if
+	  it is strictly necessary to do so.
 
 config ATMEL_SSC
 	tristate "Device driver for Atmel SSC peripheral"
-- 
cgit v1.2.3-70-g09d2


From cc0573b3250214034062ddf8c64359596d8af521 Mon Sep 17 00:00:00 2001
From: Thomas Sujith <sujith.thomas@intel.com>
Date: Fri, 25 Jan 2008 11:45:44 +0800
Subject: intel_menlo: introduce new platform specific driver

Intel menlow platform specific driver for thermal management extension.

Signed-off-by: Thomas Sujith <sujith.thomas@intel.com>
Signed-off-by: Zhang Rui <rui.zhang@intel.com>
Signed-off-by: Len Brown <len.brown@intel.com>
---
 drivers/misc/Kconfig        |   9 +
 drivers/misc/Makefile       |   1 +
 drivers/misc/intel_menlow.c | 526 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 536 insertions(+)
 create mode 100644 drivers/misc/intel_menlow.c

(limited to 'drivers/misc/Kconfig')

diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b5e67c0ff43..d1380a5a003 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -232,4 +232,13 @@ config ATMEL_SSC
 
 	  If unsure, say N.
 
+config INTEL_MENLOW
+	tristate "Thermal Management driver for Intel menlow platform"
+	depends on ACPI_THERMAL
+	---help---
+	  ACPI thermal management enhancement driver on
+	  Intel Menlow platform.
+
+	  If unsure, say N.
+
 endif # MISC_DEVICES
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685d728..a9e8faffc1b 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -17,3 +17,4 @@ obj-$(CONFIG_SONY_LAPTOP)	+= sony-laptop.o
 obj-$(CONFIG_THINKPAD_ACPI)	+= thinkpad_acpi.o
 obj-$(CONFIG_FUJITSU_LAPTOP)	+= fujitsu-laptop.o
 obj-$(CONFIG_EEPROM_93CX6)	+= eeprom_93cx6.o
+obj-$(CONFIG_INTEL_MENLOW)	+= intel_menlow.o
diff --git a/drivers/misc/intel_menlow.c b/drivers/misc/intel_menlow.c
new file mode 100644
index 00000000000..f70984ab1e1
--- /dev/null
+++ b/drivers/misc/intel_menlow.c
@@ -0,0 +1,526 @@
+/*
+ *  intel_menlow.c - Intel menlow Driver for thermal management extension
+ *
+ *  Copyright (C) 2008 Intel Corp
+ *  Copyright (C) 2008 Sujith Thomas <sujith.thomas@intel.com>
+ *  Copyright (C) 2008 Zhang Rui <rui.zhang@intel.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; version 2 of the License.
+ *
+ *  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.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ *  This driver creates the sys I/F for programming the sensors.
+ *  It also implements the driver for intel menlow memory controller (hardware
+ *  id is INT0002) which makes use of the platform specific ACPI methods
+ *  to get/set bandwidth.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/pm.h>
+
+#include <linux/thermal.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Thomas Sujith");
+MODULE_AUTHOR("Zhang Rui");
+MODULE_DESCRIPTION("Intel Menlow platform specific driver");
+MODULE_LICENSE("GPL");
+
+/*
+ * Memory controller device control
+ */
+
+#define MEMORY_GET_BANDWIDTH "GTHS"
+#define MEMORY_SET_BANDWIDTH "STHS"
+#define MEMORY_ARG_CUR_BANDWIDTH 1
+#define MEMORY_ARG_MAX_BANDWIDTH 0
+
+static int memory_get_int_max_bandwidth(struct thermal_cooling_device *cdev,
+					unsigned long *max_state)
+{
+	struct acpi_device *device = cdev->devdata;
+	acpi_handle handle = device->handle;
+	unsigned long value;
+	struct acpi_object_list arg_list;
+	union acpi_object arg;
+	acpi_status status = AE_OK;
+
+	arg_list.count = 1;
+	arg_list.pointer = &arg;
+	arg.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = MEMORY_ARG_MAX_BANDWIDTH;
+	status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+				       &arg_list, &value);
+	if (ACPI_FAILURE(status))
+		return -EFAULT;
+
+	*max_state = value - 1;
+	return 0;
+}
+
+static int memory_get_max_bandwidth(struct thermal_cooling_device *cdev,
+				    char *buf)
+{
+	unsigned long value;
+	if (memory_get_int_max_bandwidth(cdev, &value))
+		return -EINVAL;
+
+	return sprintf(buf, "%ld\n", value);
+}
+
+static int memory_get_cur_bandwidth(struct thermal_cooling_device *cdev,
+				    char *buf)
+{
+	struct acpi_device *device = cdev->devdata;
+	acpi_handle handle = device->handle;
+	unsigned long value;
+	struct acpi_object_list arg_list;
+	union acpi_object arg;
+	acpi_status status = AE_OK;
+
+	arg_list.count = 1;
+	arg_list.pointer = &arg;
+	arg.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = MEMORY_ARG_CUR_BANDWIDTH;
+	status = acpi_evaluate_integer(handle, MEMORY_GET_BANDWIDTH,
+				       &arg_list, &value);
+	if (ACPI_FAILURE(status))
+		return -EFAULT;
+
+	return sprintf(buf, "%ld\n", value);
+}
+
+static int memory_set_cur_bandwidth(struct thermal_cooling_device *cdev,
+				    unsigned int state)
+{
+	struct acpi_device *device = cdev->devdata;
+	acpi_handle handle = device->handle;
+	struct acpi_object_list arg_list;
+	union acpi_object arg;
+	acpi_status status;
+	int temp;
+	unsigned long max_state;
+
+	if (memory_get_int_max_bandwidth(cdev, &max_state))
+		return -EFAULT;
+
+	if (max_state < 0 || state > max_state)
+		return -EINVAL;
+
+	arg_list.count = 1;
+	arg_list.pointer = &arg;
+	arg.type = ACPI_TYPE_INTEGER;
+	arg.integer.value = state;
+
+	status =
+	    acpi_evaluate_integer(handle, MEMORY_SET_BANDWIDTH, &arg_list,
+				  (unsigned long *)&temp);
+
+	printk(KERN_INFO
+	       "Bandwidth value was %d: status is %d\n", state, status);
+	if (ACPI_FAILURE(status))
+		return -EFAULT;
+
+	return 0;
+}
+
+static struct thermal_cooling_device_ops memory_cooling_ops = {
+	.get_max_state = memory_get_max_bandwidth,
+	.get_cur_state = memory_get_cur_bandwidth,
+	.set_cur_state = memory_set_cur_bandwidth,
+};
+
+/*
+ * Memory Device Management
+ */
+static int intel_menlow_memory_add(struct acpi_device *device)
+{
+	int result = -ENODEV;
+	acpi_status status = AE_OK;
+	acpi_handle dummy;
+	struct thermal_cooling_device *cdev;
+
+	if (!device)
+		return -EINVAL;
+
+	status = acpi_get_handle(device->handle, MEMORY_GET_BANDWIDTH, &dummy);
+	if (ACPI_FAILURE(status))
+		goto end;
+
+	status = acpi_get_handle(device->handle, MEMORY_SET_BANDWIDTH, &dummy);
+	if (ACPI_FAILURE(status))
+		goto end;
+
+	cdev = thermal_cooling_device_register("Memory controller", device,
+					       &memory_cooling_ops);
+	acpi_driver_data(device) = cdev;
+	if (!cdev)
+		result = -ENODEV;
+	else {
+		result = sysfs_create_link(&device->dev.kobj,
+					&cdev->device.kobj, "thermal_cooling");
+		if (result)
+			goto unregister;
+
+		result = sysfs_create_link(&cdev->device.kobj,
+					&device->dev.kobj, "device");
+		if (result) {
+			sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+			goto unregister;
+		}
+	}
+
+ end:
+	return result;
+
+ unregister:
+	thermal_cooling_device_unregister(cdev);
+	return result;
+
+}
+
+static int intel_menlow_memory_remove(struct acpi_device *device, int type)
+{
+	struct thermal_cooling_device *cdev = acpi_driver_data(device);
+
+	if (!device || !cdev)
+		return -EINVAL;
+
+	sysfs_remove_link(&device->dev.kobj, "thermal_cooling");
+	sysfs_remove_link(&cdev->device.kobj, "device");
+	thermal_cooling_device_unregister(cdev);
+
+	return 0;
+}
+
+const static struct acpi_device_id intel_menlow_memory_ids[] = {
+	{"INT0002", 0},
+	{"", 0},
+};
+
+static struct acpi_driver intel_menlow_memory_driver = {
+	.name = "intel_menlow_thermal_control",
+	.ids = intel_menlow_memory_ids,
+	.ops = {
+		.add = intel_menlow_memory_add,
+		.remove = intel_menlow_memory_remove,
+		},
+};
+
+/*
+ * Sensor control on menlow platform
+ */
+
+#define THERMAL_AUX0 0
+#define THERMAL_AUX1 1
+#define GET_AUX0 "GAX0"
+#define GET_AUX1 "GAX1"
+#define SET_AUX0 "SAX0"
+#define SET_AUX1 "SAX1"
+
+struct intel_menlow_attribute {
+	struct device_attribute attr;
+	struct device *device;
+	acpi_handle handle;
+	struct list_head node;
+};
+
+static LIST_HEAD(intel_menlow_attr_list);
+static DEFINE_MUTEX(intel_menlow_attr_lock);
+
+/*
+ * sensor_get_auxtrip - get the current auxtrip value from sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_get_auxtrip(acpi_handle handle, int index, int *value)
+{
+	acpi_status status;
+
+	if ((index != 0 && index != 1) || !value)
+		return -EINVAL;
+
+	status = acpi_evaluate_integer(handle, index ? GET_AUX1 : GET_AUX0,
+				       NULL, (unsigned long *)value);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	return 0;
+}
+
+/*
+ * sensor_set_auxtrip - set the new auxtrip value to sensor
+ * @name: Thermalzone name
+ * @auxtype : AUX0/AUX1
+ * @buf: syfs buffer
+ */
+static int sensor_set_auxtrip(acpi_handle handle, int index, int value)
+{
+	acpi_status status;
+	union acpi_object arg = {
+		ACPI_TYPE_INTEGER
+	};
+	struct acpi_object_list args = {
+		1, &arg
+	};
+	int temp;
+
+	if (index != 0 && index != 1)
+		return -EINVAL;
+
+	status = acpi_evaluate_integer(handle, index ? GET_AUX0 : GET_AUX1,
+				       NULL, (unsigned long *)&temp);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+	if ((index && value < temp) || (!index && value > temp))
+		return -EINVAL;
+
+	arg.integer.value = value;
+	status = acpi_evaluate_integer(handle, index ? SET_AUX1 : SET_AUX0,
+				       &args, (unsigned long *)&temp);
+	if (ACPI_FAILURE(status))
+		return -EIO;
+
+	/* do we need to check the return value of SAX0/SAX1 ? */
+
+	return 0;
+}
+
+#define to_intel_menlow_attr(_attr)	\
+	container_of(_attr, struct intel_menlow_attribute, attr)
+
+static ssize_t aux0_show(struct device *dev,
+			 struct device_attribute *dev_attr, char *buf)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	int value;
+	int result;
+
+	result = sensor_get_auxtrip(attr->handle, 0, &value);
+
+	return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value));
+}
+
+static ssize_t aux1_show(struct device *dev,
+			 struct device_attribute *dev_attr, char *buf)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	int value;
+	int result;
+
+	result = sensor_get_auxtrip(attr->handle, 1, &value);
+
+	return result ? result : sprintf(buf, "%lu", KELVIN_TO_CELSIUS(value));
+}
+
+static ssize_t aux0_store(struct device *dev,
+			  struct device_attribute *dev_attr,
+			  const char *buf, size_t count)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	int value;
+	int result;
+
+	/*Sanity check; should be a positive integer */
+	if (!sscanf(buf, "%d", &value))
+		return -EINVAL;
+
+	if (value < 0)
+		return -EINVAL;
+
+	result = sensor_set_auxtrip(attr->handle, 0, CELSIUS_TO_KELVIN(value));
+	return result ? result : count;
+}
+
+static ssize_t aux1_store(struct device *dev,
+			  struct device_attribute *dev_attr,
+			  const char *buf, size_t count)
+{
+	struct intel_menlow_attribute *attr = to_intel_menlow_attr(dev_attr);
+	int value;
+	int result;
+
+	/*Sanity check; should be a positive integer */
+	if (!sscanf(buf, "%d", &value))
+		return -EINVAL;
+
+	if (value < 0)
+		return -EINVAL;
+
+	result = sensor_set_auxtrip(attr->handle, 1, CELSIUS_TO_KELVIN(value));
+	return result ? result : count;
+}
+
+/* BIOS can enable/disable the thermal user application in dabney platform */
+#define BIOS_ENABLED "\\_TZ.GSTS"
+static ssize_t bios_enabled_show(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	acpi_status status;
+	unsigned long bios_enabled;
+
+	status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &bios_enabled);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	return sprintf(buf, "%s\n", bios_enabled ? "enabled" : "disabled");
+}
+
+static int intel_menlow_add_one_attribute(char *name, int mode, void *show,
+					  void *store, struct device *dev,
+					  acpi_handle handle)
+{
+	struct intel_menlow_attribute *attr;
+	int result;
+
+	attr = kzalloc(sizeof(struct intel_menlow_attribute), GFP_KERNEL);
+	if (!attr)
+		return -ENOMEM;
+
+	attr->attr.attr.name = name;
+	attr->attr.attr.mode = mode;
+	attr->attr.show = show;
+	attr->attr.store = store;
+	attr->device = dev;
+	attr->handle = handle;
+
+	result = device_create_file(dev, &attr->attr);
+	if (result)
+		return result;
+
+	mutex_lock(&intel_menlow_attr_lock);
+	list_add_tail(&attr->node, &intel_menlow_attr_list);
+	mutex_unlock(&intel_menlow_attr_lock);
+
+	return 0;
+}
+
+static acpi_status intel_menlow_register_sensor(acpi_handle handle, u32 lvl,
+						void *context, void **rv)
+{
+	acpi_status status;
+	acpi_handle dummy;
+	struct thermal_zone_device *thermal;
+	int result;
+
+	result = acpi_bus_get_private_data(handle, (void **)&thermal);
+	if (result)
+		return 0;
+
+	/* _TZ must have the AUX0/1 methods */
+	status = acpi_get_handle(handle, GET_AUX0, &dummy);
+	if (ACPI_FAILURE(status))
+		goto not_found;
+
+	status = acpi_get_handle(handle, SET_AUX0, &dummy);
+	if (ACPI_FAILURE(status))
+		goto not_found;
+
+	result = intel_menlow_add_one_attribute("aux0", 0644,
+						aux0_show, aux0_store,
+						&thermal->device, handle);
+	if (result)
+		return AE_ERROR;
+
+	status = acpi_get_handle(handle, GET_AUX1, &dummy);
+	if (ACPI_FAILURE(status))
+		goto not_found;
+
+	status = acpi_get_handle(handle, SET_AUX1, &dummy);
+	if (ACPI_FAILURE(status))
+		goto not_found;
+
+	result = intel_menlow_add_one_attribute("aux1", 0644,
+						aux1_show, aux1_store,
+						&thermal->device, handle);
+	if (result)
+		return AE_ERROR;
+
+	/*
+	 * create the "dabney_enabled" attribute which means the user app
+	 * should be loaded or not
+	 */
+
+	result = intel_menlow_add_one_attribute("bios_enabled", 0444,
+						bios_enabled_show, NULL,
+						&thermal->device, handle);
+	if (result)
+		return AE_ERROR;
+
+ not_found:
+	if (status == AE_NOT_FOUND)
+		return AE_OK;
+	else
+		return status;
+}
+
+static void intel_menlow_unregister_sensor(void)
+{
+	struct intel_menlow_attribute *pos, *next;
+
+	mutex_lock(&intel_menlow_attr_lock);
+	list_for_each_entry_safe(pos, next, &intel_menlow_attr_list, node) {
+		list_del(&pos->node);
+		device_remove_file(pos->device, &pos->attr);
+		kfree(pos);
+	}
+	mutex_unlock(&intel_menlow_attr_lock);
+
+	return;
+}
+
+static int __init intel_menlow_module_init(void)
+{
+	int result = -ENODEV;
+	acpi_status status;
+	unsigned long enable;
+
+	if (acpi_disabled)
+		return result;
+
+	/* Looking for the \_TZ.GSTS method */
+	status = acpi_evaluate_integer(NULL, BIOS_ENABLED, NULL, &enable);
+	if (ACPI_FAILURE(status) || !enable)
+		return -ENODEV;
+
+	/* Looking for ACPI device MEM0 with hardware id INT0002 */
+	result = acpi_bus_register_driver(&intel_menlow_memory_driver);
+	if (result)
+		return result;
+
+	/* Looking for sensors in each ACPI thermal zone */
+	status = acpi_walk_namespace(ACPI_TYPE_THERMAL, ACPI_ROOT_OBJECT,
+				     ACPI_UINT32_MAX,
+				     intel_menlow_register_sensor, NULL, NULL);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	return 0;
+}
+
+static void __exit intel_menlow_module_exit(void)
+{
+	acpi_bus_unregister_driver(&intel_menlow_memory_driver);
+	intel_menlow_unregister_sensor();
+}
+
+module_init(intel_menlow_module_init);
+module_exit(intel_menlow_module_exit);
-- 
cgit v1.2.3-70-g09d2


From 745a5d2126926808295742932d0e36d485efa485 Mon Sep 17 00:00:00 2001
From: Carlos Corbacho <carlos@strangeworlds.co.uk>
Date: Tue, 5 Feb 2008 02:17:10 +0000
Subject: acer-wmi: Add driver for newer Acer laptops

This is a driver for newer Acer (and Wistron) laptops. It adds wireless
radio and bluetooth control, and on some laptops, exposes the mail LED and
LCD backlight.

v1:

* Initial release

v2:

* Replace left over ACPI references with WMI
* Add GUID based autoloading (depends on future work to WMI)
* Add DMI based autoloading (backup solution until WMI sysfs/ class
  work is available)
* Checkpatch fixes

v3:

* Add new EC quirks for Aspire 3100 & 5100, and Extensa 5220

v4:

* Simplified internal handling of WMID and AMW0 devices
* Add autodetection for bluetooth and maximum brightness on AMW0 V2 and
  WMID laptops.

v5:

* Add EC quirk for Medion MD 98000
* Add autodetection for AMW0, and mail LED on AMW0 and AMW0 V2.
* Improve error handling
* Fix AMW0 V2 bluetooth and wireless, by using both WMID and AMW0 methods
  to ensure that the correct value is always set.

v6:

* Fix 'use before initialisation' bug with quirks.

v7

* Fix bug on AMW0 where acer-wmi would exit if a mail LED was not
  detected.
* Add Acer Aspire 9110 mail LED support
* Fix section mismatch warnings

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
Signed-off-by: Len Brown <len.brown@intel.com>
---
 MAINTAINERS             |    7 +
 drivers/misc/Kconfig    |   16 +
 drivers/misc/Makefile   |    1 +
 drivers/misc/acer-wmi.c | 1109 +++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1133 insertions(+)
 create mode 100644 drivers/misc/acer-wmi.c

(limited to 'drivers/misc/Kconfig')

diff --git a/MAINTAINERS b/MAINTAINERS
index 12e26d772e7..2eaeef4b2fb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -202,6 +202,13 @@ L:	linux-scsi@vger.kernel.org
 W:	http://www.adaptec.com/
 S:	Supported
 
+ACER WMI LAPTOP EXTRAS
+P:	Carlos Corbacho
+M:	carlos@strangeworlds.co.uk
+L:	aceracpi@googlegroups.com (subscribers-only)
+W:	http://code.google.com/p/aceracpi
+S:	Maintained
+
 ACPI
 P:	Len Brown
 M:	len.brown@intel.com
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index b5e67c0ff43..35d2c22c5d2 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -92,6 +92,22 @@ config TIFM_7XX1
           To compile this driver as a module, choose M here: the module will
 	  be called tifm_7xx1.
 
+config ACER_WMI
+        tristate "Acer WMI Laptop Extras (EXPERIMENTAL)"
+	depends on X86
+	depends on EXPERIMENTAL
+	depends on ACPI
+	depends on ACPI_WMI
+	depends on LEDS_CLASS
+	depends on BACKLIGHT_CLASS_DEVICE
+	---help---
+	  This is a driver for newer Acer (and Wistron) laptops. It adds
+	  wireless radio and bluetooth control, and on some laptops,
+	  exposes the mail LED and LCD backlight.
+
+	  If you have an ACPI-WMI compatible Acer/ Wistron laptop, say Y or M
+	  here.
+
 config ASUS_LAPTOP
         tristate "Asus Laptop Extras (EXPERIMENTAL)"
         depends on X86
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 87f2685d728..3da1491f662 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,6 +6,7 @@ obj- := misc.o	# Dummy rule to force built-in.o to be made
 obj-$(CONFIG_IBM_ASM)		+= ibmasm/
 obj-$(CONFIG_HDPU_FEATURES)	+= hdpuftrs/
 obj-$(CONFIG_MSI_LAPTOP)     += msi-laptop.o
+obj-$(CONFIG_ACER_WMI)     += acer-wmi.o
 obj-$(CONFIG_ASUS_LAPTOP)     += asus-laptop.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
 obj-$(CONFIG_LKDTM)		+= lkdtm.o
diff --git a/drivers/misc/acer-wmi.c b/drivers/misc/acer-wmi.c
new file mode 100644
index 00000000000..a4d67750425
--- /dev/null
+++ b/drivers/misc/acer-wmi.c
@@ -0,0 +1,1109 @@
+/*
+ *  Acer WMI Laptop Extras
+ *
+ *  Copyright (C) 2007-2008	Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *
+ *  Based on acer_acpi:
+ *    Copyright (C) 2005-2007	E.M. Smith
+ *    Copyright (C) 2007-2008	Carlos Corbacho <cathectic@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 of the License, 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.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#define ACER_WMI_VERSION	"0.1"
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/dmi.h>
+#include <linux/backlight.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+#include <linux/i8042.h>
+
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Carlos Corbacho");
+MODULE_DESCRIPTION("Acer Laptop WMI Extras Driver");
+MODULE_LICENSE("GPL");
+
+#define ACER_LOGPREFIX "acer-wmi: "
+#define ACER_ERR KERN_ERR ACER_LOGPREFIX
+#define ACER_NOTICE KERN_NOTICE ACER_LOGPREFIX
+#define ACER_INFO KERN_INFO ACER_LOGPREFIX
+
+/*
+ * The following defines quirks to get some specific functions to work
+ * which are known to not be supported over ACPI-WMI (such as the mail LED
+ * on WMID based Acer's)
+ */
+struct acer_quirks {
+	const char *vendor;
+	const char *model;
+	u16 quirks;
+};
+
+/*
+ * Magic Number
+ * Meaning is unknown - this number is required for writing to ACPI for AMW0
+ * (it's also used in acerhk when directly accessing the BIOS)
+ */
+#define ACER_AMW0_WRITE	0x9610
+
+/*
+ * Bit masks for the AMW0 interface
+ */
+#define ACER_AMW0_WIRELESS_MASK  0x35
+#define ACER_AMW0_BLUETOOTH_MASK 0x34
+#define ACER_AMW0_MAILLED_MASK   0x31
+
+/*
+ * Method IDs for WMID interface
+ */
+#define ACER_WMID_GET_WIRELESS_METHODID		1
+#define ACER_WMID_GET_BLUETOOTH_METHODID	2
+#define ACER_WMID_GET_BRIGHTNESS_METHODID	3
+#define ACER_WMID_SET_WIRELESS_METHODID		4
+#define ACER_WMID_SET_BLUETOOTH_METHODID	5
+#define ACER_WMID_SET_BRIGHTNESS_METHODID	6
+#define ACER_WMID_GET_THREEG_METHODID		10
+#define ACER_WMID_SET_THREEG_METHODID		11
+
+/*
+ * Acer ACPI method GUIDs
+ */
+#define AMW0_GUID1		"67C3371D-95A3-4C37-BB61-DD47B491DAAB"
+#define WMID_GUID1		"6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3"
+#define WMID_GUID2		"95764E09-FB56-4e83-B31A-37761F60994A"
+
+MODULE_ALIAS("wmi:67C3371D-95A3-4C37-BB61-DD47B491DAAB");
+MODULE_ALIAS("wmi:6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3");
+
+/* Temporary workaround until the WMI sysfs interface goes in */
+MODULE_ALIAS("dmi:*:*Acer*:*:");
+
+/*
+ * Interface capability flags
+ */
+#define ACER_CAP_MAILLED		(1<<0)
+#define ACER_CAP_WIRELESS		(1<<1)
+#define ACER_CAP_BLUETOOTH		(1<<2)
+#define ACER_CAP_BRIGHTNESS		(1<<3)
+#define ACER_CAP_THREEG			(1<<4)
+#define ACER_CAP_ANY			(0xFFFFFFFF)
+
+/*
+ * Interface type flags
+ */
+enum interface_flags {
+	ACER_AMW0,
+	ACER_AMW0_V2,
+	ACER_WMID,
+};
+
+#define ACER_DEFAULT_WIRELESS  0
+#define ACER_DEFAULT_BLUETOOTH 0
+#define ACER_DEFAULT_MAILLED   0
+#define ACER_DEFAULT_THREEG    0
+
+static int max_brightness = 0xF;
+
+static int wireless = -1;
+static int bluetooth = -1;
+static int mailled = -1;
+static int brightness = -1;
+static int threeg = -1;
+static int force_series;
+
+module_param(mailled, int, 0444);
+module_param(wireless, int, 0444);
+module_param(bluetooth, int, 0444);
+module_param(brightness, int, 0444);
+module_param(threeg, int, 0444);
+module_param(force_series, int, 0444);
+MODULE_PARM_DESC(wireless, "Set initial state of Wireless hardware");
+MODULE_PARM_DESC(bluetooth, "Set initial state of Bluetooth hardware");
+MODULE_PARM_DESC(mailled, "Set initial state of Mail LED");
+MODULE_PARM_DESC(brightness, "Set initial LCD backlight brightness");
+MODULE_PARM_DESC(threeg, "Set initial state of 3G hardware");
+MODULE_PARM_DESC(force_series, "Force a different laptop series");
+
+struct acer_data {
+	int mailled;
+	int wireless;
+	int bluetooth;
+	int threeg;
+	int brightness;
+};
+
+/* Each low-level interface must define at least some of the following */
+struct wmi_interface {
+	/* The WMI device type */
+	u32 type;
+
+	/* The capabilities this interface provides */
+	u32 capability;
+
+	/* Private data for the current interface */
+	struct acer_data data;
+};
+
+/* The static interface pointer, points to the currently detected interface */
+static struct wmi_interface *interface;
+
+/*
+ * Embedded Controller quirks
+ * Some laptops require us to directly access the EC to either enable or query
+ * features that are not available through WMI.
+ */
+
+struct quirk_entry {
+	u8 wireless;
+	u8 mailled;
+	u8 brightness;
+	u8 bluetooth;
+};
+
+static struct quirk_entry *quirks;
+
+static void set_quirks(void)
+{
+	if (quirks->mailled)
+		interface->capability |= ACER_CAP_MAILLED;
+
+	if (quirks->brightness)
+		interface->capability |= ACER_CAP_BRIGHTNESS;
+}
+
+static int dmi_matched(const struct dmi_system_id *dmi)
+{
+	quirks = dmi->driver_data;
+	return 0;
+}
+
+static struct quirk_entry quirk_unknown = {
+};
+
+static struct quirk_entry quirk_acer_travelmate_2490 = {
+	.mailled = 1,
+};
+
+/* This AMW0 laptop has no bluetooth */
+static struct quirk_entry quirk_medion_md_98300 = {
+	.wireless = 1,
+};
+
+static struct dmi_system_id acer_quirks[] = {
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 3100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 3100"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5100",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5100"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5630",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5630"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5650",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5650"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 5680",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 5680"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer Aspire 9110",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 9110"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Acer TravelMate 2490",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "Acer"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 2490"),
+		},
+		.driver_data = &quirk_acer_travelmate_2490,
+	},
+	{
+		.callback = dmi_matched,
+		.ident = "Medion MD 98300",
+		.matches = {
+			DMI_MATCH(DMI_SYS_VENDOR, "MEDION"),
+			DMI_MATCH(DMI_PRODUCT_NAME, "WAM2030"),
+		},
+		.driver_data = &quirk_medion_md_98300,
+	},
+	{}
+};
+
+/* Find which quirks are needed for a particular vendor/ model pair */
+static void find_quirks(void)
+{
+	if (!force_series) {
+		dmi_check_system(acer_quirks);
+	} else if (force_series == 2490) {
+		quirks = &quirk_acer_travelmate_2490;
+	}
+
+	if (quirks == NULL)
+		quirks = &quirk_unknown;
+
+	set_quirks();
+}
+
+/*
+ * General interface convenience methods
+ */
+
+static bool has_cap(u32 cap)
+{
+	if ((interface->capability & cap) != 0)
+		return 1;
+
+	return 0;
+}
+
+/*
+ * AMW0 (V1) interface
+ */
+struct wmab_args {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+};
+
+struct wmab_ret {
+	u32 eax;
+	u32 ebx;
+	u32 ecx;
+	u32 edx;
+	u32 eex;
+};
+
+static acpi_status wmab_execute(struct wmab_args *regbuf,
+struct acpi_buffer *result)
+{
+	struct acpi_buffer input;
+	acpi_status status;
+	input.length = sizeof(struct wmab_args);
+	input.pointer = (u8 *)regbuf;
+
+	status = wmi_evaluate_method(AMW0_GUID1, 1, 1, &input, result);
+
+	return status;
+}
+
+static acpi_status AMW0_get_u32(u32 *value, u32 cap,
+struct wmi_interface *iface)
+{
+	int err;
+	u8 result;
+
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		switch (quirks->mailled) {
+		default:
+			err = ec_read(0xA, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 7) & 0x1;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_WIRELESS:
+		switch (quirks->wireless) {
+		case 1:
+			err = ec_read(0x7B, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result & 0x1;
+			return AE_OK;
+		default:
+			err = ec_read(0xA, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 2) & 0x1;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_BLUETOOTH:
+		switch (quirks->bluetooth) {
+		default:
+			err = ec_read(0xA, &result);
+			if (err)
+				return AE_ERROR;
+			*value = (result >> 4) & 0x1;
+			return AE_OK;
+		}
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		switch (quirks->brightness) {
+		default:
+			err = ec_read(0x83, &result);
+			if (err)
+				return AE_ERROR;
+			*value = result;
+			return AE_OK;
+		}
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	return AE_OK;
+}
+
+static acpi_status AMW0_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
+{
+	struct wmab_args args;
+
+	args.eax = ACER_AMW0_WRITE;
+	args.ebx = value ? (1<<8) : 0;
+	args.ecx = args.edx = 0;
+
+	switch (cap) {
+	case ACER_CAP_MAILLED:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_MAILLED_MASK;
+		break;
+	case ACER_CAP_WIRELESS:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_WIRELESS_MASK;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		if (value > max_brightness)
+			return AE_BAD_PARAMETER;
+		switch (quirks->brightness) {
+		case 1:
+			return ec_write(0x83, value);
+		default:
+			return AE_BAD_ADDRESS;
+		break;
+		}
+	default:
+		return AE_BAD_ADDRESS;
+	}
+
+	/* Actually do the set */
+	return wmab_execute(&args, NULL);
+}
+
+static acpi_status AMW0_find_mailled(void)
+{
+	struct wmab_args args;
+	struct wmab_ret ret;
+	acpi_status status = AE_OK;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	args.eax = 0x86;
+	args.ebx = args.ecx = args.edx = 0;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	if (ret.eex & 0x1)
+		interface->capability |= ACER_CAP_MAILLED;
+
+	kfree(out.pointer);
+
+	return AE_OK;
+}
+
+static acpi_status AMW0_set_capabilities(void)
+{
+	struct wmab_args args;
+	struct wmab_ret ret;
+	acpi_status status = AE_OK;
+	struct acpi_buffer out = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	args.eax = ACER_AMW0_WRITE;
+	args.ecx = args.edx = 0;
+
+	args.ebx = 0xa2 << 8;
+	args.ebx |= ACER_AMW0_WIRELESS_MASK;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+	obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	if (ret.eax & 0x1)
+		interface->capability |= ACER_CAP_WIRELESS;
+
+	args.ebx = 2 << 8;
+	args.ebx |= ACER_AMW0_BLUETOOTH_MASK;
+
+	status = wmab_execute(&args, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER
+	&& obj->buffer.length == sizeof(struct wmab_ret)) {
+		ret = *((struct wmab_ret *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	if (ret.eax & 0x1)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	kfree(out.pointer);
+
+	/*
+	 * This appears to be safe to enable, since all Wistron based laptops
+	 * appear to use the same EC register for brightness, even if they
+	 * differ for wireless, etc
+	 */
+	interface->capability |= ACER_CAP_BRIGHTNESS;
+
+	return AE_OK;
+}
+
+static struct wmi_interface AMW0_interface = {
+	.type = ACER_AMW0,
+};
+
+static struct wmi_interface AMW0_V2_interface = {
+	.type = ACER_AMW0_V2,
+};
+
+/*
+ * New interface (The WMID interface)
+ */
+static acpi_status
+WMI_execute_u32(u32 method_id, u32 in, u32 *out)
+{
+	struct acpi_buffer input = { (acpi_size) sizeof(u32), (void *)(&in) };
+	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+	u32 tmp;
+	acpi_status status;
+
+	status = wmi_evaluate_method(WMID_GUID1, 1, method_id, &input, &result);
+
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) result.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+		obj->buffer.length == sizeof(u32)) {
+		tmp = *((u32 *) obj->buffer.pointer);
+	} else {
+		tmp = 0;
+	}
+
+	if (out)
+		*out = tmp;
+
+	kfree(result.pointer);
+
+	return status;
+}
+
+static acpi_status WMID_get_u32(u32 *value, u32 cap,
+struct wmi_interface *iface)
+{
+	acpi_status status;
+	u8 tmp;
+	u32 result, method_id = 0;
+
+	switch (cap) {
+	case ACER_CAP_WIRELESS:
+		method_id = ACER_WMID_GET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		method_id = ACER_WMID_GET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_BRIGHTNESS:
+		method_id = ACER_WMID_GET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		method_id = ACER_WMID_GET_THREEG_METHODID;
+		break;
+	case ACER_CAP_MAILLED:
+		if (quirks->mailled == 1) {
+			ec_read(0x9f, &tmp);
+			*value = tmp & 0x1;
+			return 0;
+		}
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	status = WMI_execute_u32(method_id, 0, &result);
+
+	if (ACPI_SUCCESS(status))
+		*value = (u8)result;
+
+	return status;
+}
+
+static acpi_status WMID_set_u32(u32 value, u32 cap, struct wmi_interface *iface)
+{
+	u32 method_id = 0;
+	char param;
+
+	switch (cap) {
+	case ACER_CAP_BRIGHTNESS:
+		if (value > max_brightness)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_BRIGHTNESS_METHODID;
+		break;
+	case ACER_CAP_WIRELESS:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_WIRELESS_METHODID;
+		break;
+	case ACER_CAP_BLUETOOTH:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_BLUETOOTH_METHODID;
+		break;
+	case ACER_CAP_THREEG:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		method_id = ACER_WMID_SET_THREEG_METHODID;
+		break;
+	case ACER_CAP_MAILLED:
+		if (value > 1)
+			return AE_BAD_PARAMETER;
+		if (quirks->mailled == 1) {
+			param = value ? 0x92 : 0x93;
+			i8042_command(&param, 0x1059);
+			return 0;
+		}
+		break;
+	default:
+		return AE_BAD_ADDRESS;
+	}
+	return WMI_execute_u32(method_id, (u32)value, NULL);
+}
+
+static acpi_status WMID_set_capabilities(void)
+{
+	struct acpi_buffer out = {ACPI_ALLOCATE_BUFFER, NULL};
+	union acpi_object *obj;
+	acpi_status status;
+	u32 devices;
+
+	status = wmi_query_block(WMID_GUID2, 1, &out);
+	if (ACPI_FAILURE(status))
+		return status;
+
+	obj = (union acpi_object *) out.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+		obj->buffer.length == sizeof(u32)) {
+		devices = *((u32 *) obj->buffer.pointer);
+	} else {
+		return AE_ERROR;
+	}
+
+	/* Not sure on the meaning of the relevant bits yet to detect these */
+	interface->capability |= ACER_CAP_WIRELESS;
+	interface->capability |= ACER_CAP_THREEG;
+
+	/* WMID always provides brightness methods */
+	interface->capability |= ACER_CAP_BRIGHTNESS;
+
+	if (devices & 0x10)
+		interface->capability |= ACER_CAP_BLUETOOTH;
+
+	if (!(devices & 0x20))
+		max_brightness = 0x9;
+
+	return status;
+}
+
+static struct wmi_interface wmid_interface = {
+	.type = ACER_WMID,
+};
+
+/*
+ * Generic Device (interface-independent)
+ */
+
+static acpi_status get_u32(u32 *value, u32 cap)
+{
+	acpi_status status = AE_BAD_ADDRESS;
+
+	switch (interface->type) {
+	case ACER_AMW0:
+		status = AMW0_get_u32(value, cap, interface);
+		break;
+	case ACER_AMW0_V2:
+		if (cap == ACER_CAP_MAILLED) {
+			status = AMW0_get_u32(value, cap, interface);
+			break;
+		}
+	case ACER_WMID:
+		status = WMID_get_u32(value, cap, interface);
+		break;
+	}
+
+	return status;
+}
+
+static acpi_status set_u32(u32 value, u32 cap)
+{
+	if (interface->capability & cap) {
+		switch (interface->type) {
+		case ACER_AMW0:
+			return AMW0_set_u32(value, cap, interface);
+		case ACER_AMW0_V2:
+		case ACER_WMID:
+			return WMID_set_u32(value, cap, interface);
+		default:
+			return AE_BAD_PARAMETER;
+		}
+	}
+	return AE_BAD_PARAMETER;
+}
+
+static void __init acer_commandline_init(void)
+{
+	/*
+	 * These will all fail silently if the value given is invalid, or the
+	 * capability isn't available on the given interface
+	 */
+	set_u32(mailled, ACER_CAP_MAILLED);
+	set_u32(wireless, ACER_CAP_WIRELESS);
+	set_u32(bluetooth, ACER_CAP_BLUETOOTH);
+	set_u32(threeg, ACER_CAP_THREEG);
+	set_u32(brightness, ACER_CAP_BRIGHTNESS);
+}
+
+/*
+ * LED device (Mail LED only, no other LEDs known yet)
+ */
+static void mail_led_set(struct led_classdev *led_cdev,
+enum led_brightness value)
+{
+	set_u32(value, ACER_CAP_MAILLED);
+}
+
+static struct led_classdev mail_led = {
+	.name = "acer-mail:green",
+	.brightness_set = mail_led_set,
+};
+
+static int __init acer_led_init(struct device *dev)
+{
+	return led_classdev_register(dev, &mail_led);
+}
+
+static void acer_led_exit(void)
+{
+	led_classdev_unregister(&mail_led);
+}
+
+/*
+ * Backlight device
+ */
+static struct backlight_device *acer_backlight_device;
+
+static int read_brightness(struct backlight_device *bd)
+{
+	u32 value;
+	get_u32(&value, ACER_CAP_BRIGHTNESS);
+	return value;
+}
+
+static int update_bl_status(struct backlight_device *bd)
+{
+	set_u32(bd->props.brightness, ACER_CAP_BRIGHTNESS);
+	return 0;
+}
+
+static struct backlight_ops acer_bl_ops = {
+	.get_brightness = read_brightness,
+	.update_status = update_bl_status,
+};
+
+static int __init acer_backlight_init(struct device *dev)
+{
+	struct backlight_device *bd;
+
+	bd = backlight_device_register("acer-wmi", dev, NULL, &acer_bl_ops);
+	if (IS_ERR(bd)) {
+		printk(ACER_ERR "Could not register Acer backlight device\n");
+		acer_backlight_device = NULL;
+		return PTR_ERR(bd);
+	}
+
+	acer_backlight_device = bd;
+
+	bd->props.max_brightness = max_brightness;
+	bd->props.brightness = read_brightness(NULL);
+	backlight_update_status(bd);
+	return 0;
+}
+
+static void __exit acer_backlight_exit(void)
+{
+	backlight_device_unregister(acer_backlight_device);
+}
+
+/*
+ * Read/ write bool sysfs macro
+ */
+#define show_set_bool(value, cap) \
+static ssize_t \
+show_bool_##value(struct device *dev, struct device_attribute *attr, \
+	char *buf) \
+{ \
+	u32 result; \
+	acpi_status status = get_u32(&result, cap); \
+	if (ACPI_SUCCESS(status)) \
+		return sprintf(buf, "%u\n", result); \
+	return sprintf(buf, "Read error\n"); \
+} \
+\
+static ssize_t \
+set_bool_##value(struct device *dev, struct device_attribute *attr, \
+	const char *buf, size_t count) \
+{ \
+	u32 tmp = simple_strtoul(buf, NULL, 10); \
+	acpi_status status = set_u32(tmp, cap); \
+		if (ACPI_FAILURE(status)) \
+			return -EINVAL; \
+	return count; \
+} \
+static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \
+	show_bool_##value, set_bool_##value);
+
+show_set_bool(wireless, ACER_CAP_WIRELESS);
+show_set_bool(bluetooth, ACER_CAP_BLUETOOTH);
+show_set_bool(threeg, ACER_CAP_THREEG);
+
+/*
+ * Read interface sysfs macro
+ */
+static ssize_t show_interface(struct device *dev, struct device_attribute *attr,
+	char *buf)
+{
+	switch (interface->type) {
+	case ACER_AMW0:
+		return sprintf(buf, "AMW0\n");
+	case ACER_AMW0_V2:
+		return sprintf(buf, "AMW0 v2\n");
+	case ACER_WMID:
+		return sprintf(buf, "WMID\n");
+	default:
+		return sprintf(buf, "Error!\n");
+	}
+}
+
+static DEVICE_ATTR(interface, S_IWUGO | S_IRUGO | S_IWUSR,
+	show_interface, NULL);
+
+/*
+ * Platform device
+ */
+static int __devinit acer_platform_probe(struct platform_device *device)
+{
+	int err;
+
+	if (has_cap(ACER_CAP_MAILLED)) {
+		err = acer_led_init(&device->dev);
+		if (err)
+			goto error_mailled;
+	}
+
+	if (has_cap(ACER_CAP_BRIGHTNESS)) {
+		err = acer_backlight_init(&device->dev);
+		if (err)
+			goto error_brightness;
+	}
+
+	return 0;
+
+error_brightness:
+	acer_led_exit();
+error_mailled:
+	return err;
+}
+
+static int acer_platform_remove(struct platform_device *device)
+{
+	if (has_cap(ACER_CAP_MAILLED))
+		acer_led_exit();
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		acer_backlight_exit();
+	return 0;
+}
+
+static int acer_platform_suspend(struct platform_device *dev,
+pm_message_t state)
+{
+	u32 value;
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return -ENOMEM;
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		get_u32(&value, ACER_CAP_WIRELESS);
+		data->wireless = value;
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		get_u32(&value, ACER_CAP_BLUETOOTH);
+		data->bluetooth = value;
+	}
+
+	if (has_cap(ACER_CAP_MAILLED)) {
+		get_u32(&value, ACER_CAP_MAILLED);
+		data->mailled = value;
+	}
+
+	if (has_cap(ACER_CAP_BRIGHTNESS)) {
+		get_u32(&value, ACER_CAP_BRIGHTNESS);
+		data->brightness = value;
+	}
+
+	return 0;
+}
+
+static int acer_platform_resume(struct platform_device *device)
+{
+	struct acer_data *data = &interface->data;
+
+	if (!data)
+		return -ENOMEM;
+
+	if (has_cap(ACER_CAP_WIRELESS))
+		set_u32(data->wireless, ACER_CAP_WIRELESS);
+
+	if (has_cap(ACER_CAP_BLUETOOTH))
+		set_u32(data->bluetooth, ACER_CAP_BLUETOOTH);
+
+	if (has_cap(ACER_CAP_THREEG))
+		set_u32(data->threeg, ACER_CAP_THREEG);
+
+	if (has_cap(ACER_CAP_MAILLED))
+		set_u32(data->mailled, ACER_CAP_MAILLED);
+
+	if (has_cap(ACER_CAP_BRIGHTNESS))
+		set_u32(data->brightness, ACER_CAP_BRIGHTNESS);
+
+	return 0;
+}
+
+static struct platform_driver acer_platform_driver = {
+	.driver = {
+		.name = "acer-wmi",
+		.owner = THIS_MODULE,
+	},
+	.probe = acer_platform_probe,
+	.remove = acer_platform_remove,
+	.suspend = acer_platform_suspend,
+	.resume = acer_platform_resume,
+};
+
+static struct platform_device *acer_platform_device;
+
+static int remove_sysfs(struct platform_device *device)
+{
+	if (has_cap(ACER_CAP_WIRELESS))
+		device_remove_file(&device->dev, &dev_attr_wireless);
+
+	if (has_cap(ACER_CAP_BLUETOOTH))
+		device_remove_file(&device->dev, &dev_attr_bluetooth);
+
+	if (has_cap(ACER_CAP_THREEG))
+		device_remove_file(&device->dev, &dev_attr_threeg);
+
+	device_remove_file(&device->dev, &dev_attr_interface);
+
+	return 0;
+}
+
+static int create_sysfs(void)
+{
+	int retval = -ENOMEM;
+
+	if (has_cap(ACER_CAP_WIRELESS)) {
+		retval = device_create_file(&acer_platform_device->dev,
+			&dev_attr_wireless);
+		if (retval)
+			goto error_sysfs;
+	}
+
+	if (has_cap(ACER_CAP_BLUETOOTH)) {
+		retval = device_create_file(&acer_platform_device->dev,
+			&dev_attr_bluetooth);
+		if (retval)
+			goto error_sysfs;
+	}
+
+	if (has_cap(ACER_CAP_THREEG)) {
+		retval = device_create_file(&acer_platform_device->dev,
+			&dev_attr_threeg);
+		if (retval)
+			goto error_sysfs;
+	}
+
+	retval = device_create_file(&acer_platform_device->dev,
+		&dev_attr_interface);
+	if (retval)
+		goto error_sysfs;
+
+	return 0;
+
+error_sysfs:
+		remove_sysfs(acer_platform_device);
+	return retval;
+}
+
+static int __init acer_wmi_init(void)
+{
+	int err;
+
+	printk(ACER_INFO "Acer Laptop ACPI-WMI Extras version %s\n",
+			ACER_WMI_VERSION);
+
+	/*
+	 * Detect which ACPI-WMI interface we're using.
+	 */
+	if (wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+		interface = &AMW0_V2_interface;
+
+	if (!wmi_has_guid(AMW0_GUID1) && wmi_has_guid(WMID_GUID1))
+		interface = &wmid_interface;
+
+	if (wmi_has_guid(WMID_GUID2) && interface) {
+		if (ACPI_FAILURE(WMID_set_capabilities())) {
+			printk(ACER_ERR "Unable to detect available devices\n");
+			return -ENODEV;
+		}
+	} else if (!wmi_has_guid(WMID_GUID2) && interface) {
+		printk(ACER_ERR "Unable to detect available devices\n");
+		return -ENODEV;
+	}
+
+	if (wmi_has_guid(AMW0_GUID1) && !wmi_has_guid(WMID_GUID1)) {
+		interface = &AMW0_interface;
+
+		if (ACPI_FAILURE(AMW0_set_capabilities())) {
+			printk(ACER_ERR "Unable to detect available devices\n");
+			return -ENODEV;
+		}
+	}
+
+	if (wmi_has_guid(AMW0_GUID1)) {
+		if (ACPI_FAILURE(AMW0_find_mailled()))
+			printk(ACER_ERR "Unable to detect mail LED\n");
+	}
+
+	find_quirks();
+
+	if (!interface) {
+		printk(ACER_ERR "No or unsupported WMI interface, unable to ");
+		printk(KERN_CONT "load.\n");
+		return -ENODEV;
+	}
+
+	if (platform_driver_register(&acer_platform_driver)) {
+		printk(ACER_ERR "Unable to register platform driver.\n");
+		goto error_platform_register;
+	}
+	acer_platform_device = platform_device_alloc("acer-wmi", -1);
+	platform_device_add(acer_platform_device);
+
+	err = create_sysfs();
+	if (err)
+		return err;
+
+	/* Override any initial settings with values from the commandline */
+	acer_commandline_init();
+
+	return 0;
+
+error_platform_register:
+	return -ENODEV;
+}
+
+static void __exit acer_wmi_exit(void)
+{
+	remove_sysfs(acer_platform_device);
+	platform_device_del(acer_platform_device);
+	platform_driver_unregister(&acer_platform_driver);
+
+	printk(ACER_INFO "Acer Laptop WMI Extras unloaded\n");
+	return;
+}
+
+module_init(acer_wmi_init);
+module_exit(acer_wmi_exit);
-- 
cgit v1.2.3-70-g09d2


From dd8cd7793781c87be47bbfee65efa3fb5110f898 Mon Sep 17 00:00:00 2001
From: Carlos Corbacho <carlos@strangeworlds.co.uk>
Date: Tue, 5 Feb 2008 02:17:15 +0000
Subject: tc1100-wmi: Add driver for HP Compaq TC1100 Tablets

This is based on the 2004 out-of-tree work of Jamey Hicks, to add
support via WMI for controlling the jog dial and wireless on these
tablets.

v1:

Original release

v2:

As per Joshua Wise's comments, change bluetooth to jogdial (an error from
the original driver).

Signed-off-by: Carlos Corbacho <carlos@strangeworlds.co.uk>
CC: Matthew Garrett <mjg59@srcf.ucam.org>
CC: Jamey Hicks <jamey.hicks@nokia.com>
CC: Joshua Wise <joshua@joshuawise.com>
Signed-off-by: Len Brown <len.brown@intel.com>
---
 MAINTAINERS               |   5 +
 drivers/misc/Kconfig      |   9 ++
 drivers/misc/Makefile     |   1 +
 drivers/misc/tc1100-wmi.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 305 insertions(+)
 create mode 100644 drivers/misc/tc1100-wmi.c

(limited to 'drivers/misc/Kconfig')

diff --git a/MAINTAINERS b/MAINTAINERS
index 2eaeef4b2fb..db4bc14dadb 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1794,6 +1794,11 @@ P:	Jaroslav Kysela
 M:	perex@perex.cz
 S:	Maintained
 
+HP COMPAQ TC1100 TABLET WMI EXTRAS DRIVER
+P:	Carlos Corbacho
+M:	carlos@strangeworlds.co.uk
+S:	Odd Fixes
+
 HPET:	High Precision Event Timers driver (hpet.c)
 P:	Clemens Ladisch
 M:	clemens@ladisch.de
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 35d2c22c5d2..05997411bc4 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -142,6 +142,15 @@ config FUJITSU_LAPTOP
 
 	  If you have a Fujitsu laptop, say Y or M here.
 
+config TC1100_WMI
+	tristate "HP Compaq TC1100 Tablet WMI Extras"
+	depends on X86 && !X86_64
+	depends on ACPI
+	depends on ACPI_WMI
+	---help---
+	  This is a driver for the WMI extensions (wireless and bluetooth power
+	  control) of the HP Compaq TC1100 tablet.
+
 config MSI_LAPTOP
         tristate "MSI Laptop Extras"
         depends on X86
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 3da1491f662..51196c09e25 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_MSI_LAPTOP)     += msi-laptop.o
 obj-$(CONFIG_ACER_WMI)     += acer-wmi.o
 obj-$(CONFIG_ASUS_LAPTOP)     += asus-laptop.o
 obj-$(CONFIG_ATMEL_SSC)		+= atmel-ssc.o
+obj-$(CONFIG_TC1100_WMI)	+= tc1100-wmi.o
 obj-$(CONFIG_LKDTM)		+= lkdtm.o
 obj-$(CONFIG_TIFM_CORE)       	+= tifm_core.o
 obj-$(CONFIG_TIFM_7XX1)       	+= tifm_7xx1.o
diff --git a/drivers/misc/tc1100-wmi.c b/drivers/misc/tc1100-wmi.c
new file mode 100644
index 00000000000..f25e4c974dc
--- /dev/null
+++ b/drivers/misc/tc1100-wmi.c
@@ -0,0 +1,290 @@
+/*
+ *  HP Compaq TC1100 Tablet WMI Extras Driver
+ *
+ *  Copyright (C) 2007 Carlos Corbacho <carlos@strangeworlds.co.uk>
+ *  Copyright (C) 2004 Jamey Hicks <jamey.hicks@hp.com>
+ *  Copyright (C) 2001, 2002 Andy Grover <andrew.grover@intel.com>
+ *  Copyright (C) 2001, 2002 Paul Diefenbaugh <paul.s.diefenbaugh@intel.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 of the License, 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.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <acpi/acpi.h>
+#include <acpi/actypes.h>
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+#include <linux/platform_device.h>
+
+#define GUID "C364AC71-36DB-495A-8494-B439D472A505"
+
+#define TC1100_INSTANCE_WIRELESS		1
+#define TC1100_INSTANCE_JOGDIAL		2
+
+#define TC1100_LOGPREFIX "tc1100-wmi: "
+#define TC1100_INFO KERN_INFO TC1100_LOGPREFIX
+
+MODULE_AUTHOR("Jamey Hicks, Carlos Corbacho");
+MODULE_DESCRIPTION("HP Compaq TC1100 Tablet WMI Extras");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("wmi:C364AC71-36DB-495A-8494-B439D472A505");
+
+static int tc1100_probe(struct platform_device *device);
+static int tc1100_remove(struct platform_device *device);
+static int tc1100_suspend(struct platform_device *device, pm_message_t state);
+static int tc1100_resume(struct platform_device *device);
+
+static struct platform_driver tc1100_driver = {
+	.driver = {
+		.name = "tc1100-wmi",
+		.owner = THIS_MODULE,
+	},
+	.probe = tc1100_probe,
+	.remove = tc1100_remove,
+	.suspend = tc1100_suspend,
+	.resume = tc1100_resume,
+};
+
+static struct platform_device *tc1100_device;
+
+struct tc1100_data {
+	u32 wireless;
+	u32 jogdial;
+};
+
+static struct tc1100_data suspend_data;
+
+/* --------------------------------------------------------------------------
+				Device Management
+   -------------------------------------------------------------------------- */
+
+static int get_state(u32 *out, u8 instance)
+{
+	u32 tmp;
+	acpi_status status;
+	struct acpi_buffer result = { ACPI_ALLOCATE_BUFFER, NULL };
+	union acpi_object *obj;
+
+	if (!out)
+		return -EINVAL;
+
+	if (instance > 2)
+		return -ENODEV;
+
+	status = wmi_query_block(GUID, instance, &result);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	obj = (union acpi_object *) result.pointer;
+	if (obj && obj->type == ACPI_TYPE_BUFFER &&
+		obj->buffer.length == sizeof(u32)) {
+		tmp = *((u32 *) obj->buffer.pointer);
+	} else {
+		tmp = 0;
+	}
+
+	if (result.length > 0 && result.pointer)
+		kfree(result.pointer);
+
+	switch (instance) {
+	case TC1100_INSTANCE_WIRELESS:
+		*out = (tmp == 3) ? 1 : 0;
+		return 0;
+	case TC1100_INSTANCE_JOGDIAL:
+		*out = (tmp == 1) ? 1 : 0;
+		return 0;
+	default:
+		return -ENODEV;
+	}
+}
+
+static int set_state(u32 *in, u8 instance)
+{
+	u32 value;
+	acpi_status status;
+	struct acpi_buffer input;
+
+	if (!in)
+		return -EINVAL;
+
+	if (instance > 2)
+		return -ENODEV;
+
+	switch (instance) {
+	case TC1100_INSTANCE_WIRELESS:
+		value = (*in) ? 1 : 2;
+		break;
+	case TC1100_INSTANCE_JOGDIAL:
+		value = (*in) ? 0 : 1;
+		break;
+	default:
+		return -ENODEV;
+	}
+
+	input.length = sizeof(u32);
+	input.pointer = &value;
+
+	status = wmi_set_block(GUID, instance, &input);
+	if (ACPI_FAILURE(status))
+		return -ENODEV;
+
+	return 0;
+}
+
+/* --------------------------------------------------------------------------
+				FS Interface (/sys)
+   -------------------------------------------------------------------------- */
+
+/*
+ * Read/ write bool sysfs macro
+ */
+#define show_set_bool(value, instance) \
+static ssize_t \
+show_bool_##value(struct device *dev, struct device_attribute *attr, \
+	char *buf) \
+{ \
+	u32 result; \
+	acpi_status status = get_state(&result, instance); \
+	if (ACPI_SUCCESS(status)) \
+		return sprintf(buf, "%d\n", result); \
+	return sprintf(buf, "Read error\n"); \
+} \
+\
+static ssize_t \
+set_bool_##value(struct device *dev, struct device_attribute *attr, \
+	const char *buf, size_t count) \
+{ \
+	u32 tmp = simple_strtoul(buf, NULL, 10); \
+	acpi_status status = set_state(&tmp, instance); \
+		if (ACPI_FAILURE(status)) \
+			return -EINVAL; \
+	return count; \
+} \
+static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \
+	show_bool_##value, set_bool_##value);
+
+show_set_bool(wireless, TC1100_INSTANCE_WIRELESS);
+show_set_bool(jogdial, TC1100_INSTANCE_JOGDIAL);
+
+static void remove_fs(void)
+{
+	device_remove_file(&tc1100_device->dev, &dev_attr_wireless);
+	device_remove_file(&tc1100_device->dev, &dev_attr_jogdial);
+}
+
+static int add_fs(void)
+{
+	int ret;
+
+	ret = device_create_file(&tc1100_device->dev, &dev_attr_wireless);
+	if (ret)
+		goto add_sysfs_error;
+
+	ret = device_create_file(&tc1100_device->dev, &dev_attr_jogdial);
+	if (ret)
+		goto add_sysfs_error;
+
+	return ret;
+
+add_sysfs_error:
+	remove_fs();
+	return ret;
+}
+
+/* --------------------------------------------------------------------------
+				Driver Model
+   -------------------------------------------------------------------------- */
+
+static int tc1100_probe(struct platform_device *device)
+{
+	int result = 0;
+
+	result = add_fs();
+	return result;
+}
+
+
+static int tc1100_remove(struct platform_device *device)
+{
+	remove_fs();
+	return 0;
+}
+
+static int tc1100_suspend(struct platform_device *dev, pm_message_t state)
+{
+	int ret;
+
+	ret = get_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+	if (ret)
+		return ret;
+
+	ret = get_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int tc1100_resume(struct platform_device *dev)
+{
+	int ret;
+
+	ret = set_state(&suspend_data.wireless, TC1100_INSTANCE_WIRELESS);
+	if (ret)
+		return ret;
+
+	ret = set_state(&suspend_data.jogdial, TC1100_INSTANCE_JOGDIAL);
+	if (ret)
+		return ret;
+
+	return ret;
+}
+
+static int __init tc1100_init(void)
+{
+	int result = 0;
+
+	if (!wmi_has_guid(GUID))
+		return -ENODEV;
+
+	result = platform_driver_register(&tc1100_driver);
+	if (result)
+		return result;
+
+	tc1100_device = platform_device_alloc("tc1100-wmi", -1);
+	platform_device_add(tc1100_device);
+
+	printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras loaded\n");
+
+	return result;
+}
+
+static void __exit tc1100_exit(void)
+{
+	platform_device_del(tc1100_device);
+	platform_driver_unregister(&tc1100_driver);
+
+	printk(TC1100_INFO "HP Compaq TC1100 Tablet WMI Extras unloaded\n");
+}
+
+module_init(tc1100_init);
+module_exit(tc1100_exit);
-- 
cgit v1.2.3-70-g09d2