From: Erik Rigtorp 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 --- 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 + * 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 +#include +#include +#include +#include +#include + +#include +#include + +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", + ¶ms, 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", ¶ms, 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); _