
From: Erik Rigtorp <erik@rigtorp.com>

Changes since last version:
 - use the acpi handle provided by acpi_bus_register_driver, this way it
   should work on more models.
 - correct handling of return value from acpi_bus_register_driver

Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/acpi/Kconfig         |   11 +
 25-akpm/drivers/acpi/Makefile        |    1 
 25-akpm/drivers/acpi/thinkpad_acpi.c |  265 +++++++++++++++++++++++++++++++++++
 3 files changed, 277 insertions(+)

diff -puN drivers/acpi/Kconfig~thinkpad-fnfx-key-driver drivers/acpi/Kconfig
--- 25/drivers/acpi/Kconfig~thinkpad-fnfx-key-driver	2004-11-03 20:26:22.245246904 -0800
+++ 25-akpm/drivers/acpi/Kconfig	2004-11-03 20:26:22.250246144 -0800
@@ -199,6 +199,17 @@ config ACPI_ASUS
           driver is still under development, so if your laptop is unsupported or
           something works not quite as expected, please use the mailing list
           available on the above page (acpi4asus-user@lists.sourceforge.net)
+
+config ACPI_THINKPAD
+	tristate "Thinkpad Fn+Fx key driver"
+	depends on X86
+	depends on ACPI_INTERPRETER
+	default m
+	---help---
+	  This driver adds support for the Fn+Fx keys on modern Thinkpad
+	  laptops.
+
+	  If you have a IBM Thinkpad laptop say Y or M here.
           
 config ACPI_IBM
 	tristate "IBM ThinkPad Laptop Extras"
diff -puN drivers/acpi/Makefile~thinkpad-fnfx-key-driver drivers/acpi/Makefile
--- 25/drivers/acpi/Makefile~thinkpad-fnfx-key-driver	2004-11-03 20:26:22.247246600 -0800
+++ 25-akpm/drivers/acpi/Makefile	2004-11-03 20:26:22.250246144 -0800
@@ -50,4 +50,5 @@ obj-$(CONFIG_ACPI_NUMA)		+= numa.o
 obj-$(CONFIG_ACPI_ASUS)		+= asus_acpi.o
 obj-$(CONFIG_ACPI_IBM)		+= ibm_acpi.o
 obj-$(CONFIG_ACPI_TOSHIBA)	+= toshiba_acpi.o
+obj-$(CONFIG_ACPI_THINKPAD)	+= thinkpad_acpi.o
 obj-$(CONFIG_ACPI_BUS)		+= scan.o motherboard.o
diff -puN /dev/null drivers/acpi/thinkpad_acpi.c
--- /dev/null	2003-09-15 06:40:47.000000000 -0700
+++ 25-akpm/drivers/acpi/thinkpad_acpi.c	2004-11-03 20:26:22.252245840 -0800
@@ -0,0 +1,265 @@
+/*
+ * IBM Thinkpad Fn+Fx key driver
+ * (C) 2004 Erik Rigtorp <erik@rigtorp.com>
+ * All Rights Reserved
+ *
+ * This code was inspired from the acpi button and thoshiba drivers.
+ *
+ * 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 <linux/proc_fs.h>
+#include <asm/uaccess.h>
+
+#include <acpi/acpi_bus.h>
+#include <acpi/acpi_drivers.h>
+
+MODULE_AUTHOR("Erik Rigtorp");
+MODULE_DESCRIPTION("IBM Thinkpad Fn+Fx key driver");
+MODULE_LICENSE("GPL");
+
+#define LOGPREFIX "thinkpad_acpi: "
+
+/* Switch between LCD and VGA video output */
+#define METHOD_VID_VSWT  "\\_SB.PCI0.AGP.VID.VSWT"
+
+/* Toggle screen expansion */
+#define METHOD_EC_VEXP   "\\_SB.PCI0.LPC.EC.VEXP"
+
+/* device(HKEY) definitions */
+#define HKEY_HID "IBM0068"
+#define HKEY_NOTIFY 0x80
+
+#define DRIVER_VERSION "1.1"
+#define DRIVER_NAME "IBM Thinkpad Fn+Fx key driver"
+#define DEVICE_NAME "IBM Thinkpad keys"
+#define DEVICE_CLASS "hkey"
+
+static int acpi_thinkpad_add (struct acpi_device *device);
+static int acpi_thinkpad_remove (struct acpi_device *device, int type);
+
+static struct acpi_driver acpi_thinkpad_driver = {
+	.name =  DRIVER_NAME,
+	.class = DEVICE_CLASS,
+	.ids =   HKEY_HID,
+	.ops = {
+		.add = acpi_thinkpad_add,
+		.remove = acpi_thinkpad_remove,
+	},
+};
+
+struct acpi_thinkpad {
+	acpi_handle handle;
+	struct acpi_device *device;
+};
+
+/* Activate/deactivate handling of keys
+ * @val     1 to activate, 0 to deactivate */
+static int handle_keys(int val, acpi_handle handle)
+{
+	struct acpi_object_list params;
+	union acpi_object objs[1];
+	acpi_status status;
+
+	params.count = 1;
+	params.pointer = objs;
+	objs[0].type = ACPI_TYPE_INTEGER;
+	if (val)
+		objs[0].integer.value = 1;
+	else
+		objs[0].integer.value = 0;
+
+	status = acpi_evaluate_object(handle, "MHKC",
+				      &params, 0);
+	return (status == AE_OK);
+}
+
+/* Activate/deactivate Fn+Fx key
+ * @key    the x in Fx
+ * @state  1 for active, 0 for disabled/firmware */
+static int set_key(unsigned int key, unsigned int state, acpi_handle handle)
+{
+	struct acpi_object_list params;
+	union acpi_object args[2];
+	acpi_status status;
+
+	if (state)
+		state = 1;
+
+	params.count = 2;
+	params.pointer = args;
+	args[0].type = ACPI_TYPE_INTEGER;
+	args[0].integer.value = key;
+	args[1].type = ACPI_TYPE_INTEGER;
+	args[1].integer.value = state;
+
+	status = acpi_evaluate_object(handle, "MHKM", &params, 0);
+	return (status == AE_OK);
+}
+
+/* Gets value of the pressed key */
+static int get_key(acpi_handle handle)
+{
+	acpi_status status;
+	unsigned long key;
+
+	status = acpi_evaluate_integer(handle, "MHKP", NULL, &key);
+	if (ACPI_FAILURE(status)) {
+		printk(KERN_INFO LOGPREFIX "acpi evaluate error on %s\n",
+		       "HKEY.MHKP");
+		printk(KERN_INFO LOGPREFIX "error getting hotkey status\n");
+		return (-EFAULT);
+	}
+
+	return (key);
+}
+
+void acpi_thinkpad_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct acpi_thinkpad *thinkpad = (struct acpi_thinkpad *) data;
+	int status;
+
+	if (!thinkpad || !thinkpad->device)
+		return;
+
+	switch(event) {
+	case HKEY_NOTIFY:
+		status = get_key(handle);
+		if (status > 0)
+		{
+			if (status == 0x1004)
+				acpi_evaluate_object(handle, "MHKS", NULL,
+						     NULL);
+			else if (status == 0x1007)
+				acpi_evaluate_object(NULL, METHOD_VID_VSWT,
+						     NULL, NULL);
+			else if (status == 0x1005)
+				acpi_evaluate_object(handle, "DTGL", NULL,
+						     NULL);
+
+			acpi_bus_generate_event(thinkpad->device, event,
+						status);
+		}
+		break;
+	default:
+		/* nothing to do */
+		break;
+	}
+
+	return;
+}
+
+static int acpi_thinkpad_add (struct acpi_device *device)
+{
+	int result = 0;
+	acpi_status status = AE_OK;
+	struct acpi_thinkpad *thinkpad = NULL;
+
+	if (!device)
+		return (-EINVAL);
+
+	thinkpad = kmalloc(sizeof(struct acpi_thinkpad), GFP_KERNEL);
+	if (!thinkpad)
+		return (-ENOMEM);
+
+	memset(thinkpad, 0, sizeof(struct acpi_thinkpad));
+
+	thinkpad->device = device;
+	thinkpad->handle = device->handle;
+	acpi_driver_data(device) = thinkpad;
+	strcpy(acpi_device_name(device), DEVICE_NAME);
+	strcpy(acpi_device_class(device), DEVICE_CLASS);
+
+	/* activate handling of extra Fn keys (indicate driver is present) */
+	handle_keys(1, thinkpad->handle);
+
+	set_key(5, 1, thinkpad->handle);
+	set_key(7, 1, thinkpad->handle);
+	set_key(8, 1, thinkpad->handle);
+	set_key(9, 1, thinkpad->handle);
+
+	status = acpi_install_notify_handler (thinkpad->handle,
+					      ACPI_DEVICE_NOTIFY,
+					      acpi_thinkpad_notify,
+					      thinkpad);
+	if (ACPI_FAILURE(status)) {
+		printk(KERN_INFO LOGPREFIX "Error registering notify handler\n");
+		result = -ENODEV;
+	}
+
+	if (result)
+		kfree(thinkpad);
+
+	return (result);
+}
+
+static int acpi_thinkpad_remove(struct acpi_device *device, int type)
+{
+	acpi_status status = 0;
+	struct acpi_thinkpad *thinkpad = NULL;
+
+	if (!device || !acpi_driver_data(device))
+		return(-EINVAL);
+
+	thinkpad = acpi_driver_data(device);
+	status = acpi_remove_notify_handler(thinkpad->handle,
+					    ACPI_DEVICE_NOTIFY,
+					    acpi_thinkpad_notify);
+
+	/* disable handling of extra Fn keys */
+	handle_keys(0, thinkpad->handle);
+
+	/* return handling of these keys to the firmware */
+	set_key(5, 0, thinkpad->handle);
+	set_key(7, 0, thinkpad->handle);
+	set_key(8, 0, thinkpad->handle);
+	set_key(9, 0, thinkpad->handle);
+
+	if (ACPI_FAILURE(status))
+		printk(KERN_INFO LOGPREFIX "Error removing notify handler\n");
+
+	kfree(thinkpad);
+
+	return(0);
+}
+
+static int __init acpi_thinkpad_init(void)
+{
+	acpi_status result = AE_OK;
+
+	if (acpi_disabled)
+		return -ENODEV;
+
+	result = acpi_bus_register_driver(&acpi_thinkpad_driver);
+	if (result != 1)
+		return -ENODEV;
+
+	printk(KERN_INFO LOGPREFIX "ACPI IBM Thinkpad Fn+Fx key driver version %s\n", DRIVER_VERSION);
+
+	return 0;
+}
+
+static void __exit acpi_thinkpad_exit(void)
+{
+	acpi_bus_unregister_driver(&acpi_thinkpad_driver);
+	return;
+}
+
+module_init(acpi_thinkpad_init);
+module_exit(acpi_thinkpad_exit);
_
