diff options
Diffstat (limited to 'drivers/input/misc/wistron_btns.c')
-rw-r--r-- | drivers/input/misc/wistron_btns.c | 602 |
1 files changed, 602 insertions, 0 deletions
diff --git a/drivers/input/misc/wistron_btns.c b/drivers/input/misc/wistron_btns.c new file mode 100644 index 00000000000..4b415d9b012 --- /dev/null +++ b/drivers/input/misc/wistron_btns.c @@ -0,0 +1,602 @@ +/* + * Wistron laptop button driver + * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz> + * Copyright (C) 2005 Bernhard Rosenkraenzer <bero@arklinux.org> + * Copyright (C) 2005 Dmitry Torokhov <dtor@mail.ru> + * + * You can redistribute and/or modify this program under the terms of the + * GNU General Public License version 2 as published by the Free Software + * Foundation. + * + * 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 <asm/io.h> +#include <linux/dmi.h> +#include <linux/init.h> +#include <linux/input.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/mc146818rtc.h> +#include <linux/module.h> +#include <linux/preempt.h> +#include <linux/string.h> +#include <linux/timer.h> +#include <linux/types.h> +#include <linux/platform_device.h> + +/* + * Number of attempts to read data from queue per poll; + * the queue can hold up to 31 entries + */ +#define MAX_POLL_ITERATIONS 64 + +#define POLL_FREQUENCY 10 /* Number of polls per second */ + +#if POLL_FREQUENCY > HZ +#error "POLL_FREQUENCY too high" +#endif + +/* BIOS subsystem IDs */ +#define WIFI 0x35 +#define BLUETOOTH 0x34 + +MODULE_AUTHOR("Miloslav Trmac <mitr@volny.cz>"); +MODULE_DESCRIPTION("Wistron laptop button driver"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1"); + +static int force; /* = 0; */ +module_param(force, bool, 0); +MODULE_PARM_DESC(force, "Load even if computer is not in database"); + +static char *keymap_name; /* = NULL; */ +module_param_named(keymap, keymap_name, charp, 0); +MODULE_PARM_DESC(keymap, "Keymap name, if it can't be autodetected"); + +static struct platform_device *wistron_device; + + /* BIOS interface implementation */ + +static void __iomem *bios_entry_point; /* BIOS routine entry point */ +static void __iomem *bios_code_map_base; +static void __iomem *bios_data_map_base; + +static u8 cmos_address; + +struct regs { + u32 eax, ebx, ecx; +}; + +static void call_bios(struct regs *regs) +{ + unsigned long flags; + + preempt_disable(); + local_irq_save(flags); + asm volatile ("pushl %%ebp;" + "movl %7, %%ebp;" + "call *%6;" + "popl %%ebp" + : "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx) + : "0" (regs->eax), "1" (regs->ebx), "2" (regs->ecx), + "m" (bios_entry_point), "m" (bios_data_map_base) + : "edx", "edi", "esi", "memory"); + local_irq_restore(flags); + preempt_enable(); +} + +static ssize_t __init locate_wistron_bios(void __iomem *base) +{ + static const unsigned char __initdata signature[] = + { 0x42, 0x21, 0x55, 0x30 }; + ssize_t offset; + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(base + offset, signature, + sizeof(signature)) != 0) + return offset; + } + return -1; +} + +static int __init map_bios(void) +{ + void __iomem *base; + ssize_t offset; + u32 entry_point; + + base = ioremap(0xF0000, 0x10000); /* Can't fail */ + offset = locate_wistron_bios(base); + if (offset < 0) { + printk(KERN_ERR "wistron_btns: BIOS entry point not found\n"); + iounmap(base); + return -ENODEV; + } + + entry_point = readl(base + offset + 5); + printk(KERN_DEBUG + "wistron_btns: BIOS signature found at %p, entry point %08X\n", + base + offset, entry_point); + + if (entry_point >= 0xF0000) { + bios_code_map_base = base; + bios_entry_point = bios_code_map_base + (entry_point & 0xFFFF); + } else { + iounmap(base); + bios_code_map_base = ioremap(entry_point & ~0x3FFF, 0x4000); + if (bios_code_map_base == NULL) { + printk(KERN_ERR + "wistron_btns: Can't map BIOS code at %08X\n", + entry_point & ~0x3FFF); + goto err; + } + bios_entry_point = bios_code_map_base + (entry_point & 0x3FFF); + } + /* The Windows driver maps 0x10000 bytes, we keep only one page... */ + bios_data_map_base = ioremap(0x400, 0xc00); + if (bios_data_map_base == NULL) { + printk(KERN_ERR "wistron_btns: Can't map BIOS data\n"); + goto err_code; + } + return 0; + +err_code: + iounmap(bios_code_map_base); +err: + return -ENOMEM; +} + +static inline void unmap_bios(void) +{ + iounmap(bios_code_map_base); + iounmap(bios_data_map_base); +} + + /* BIOS calls */ + +static u16 bios_pop_queue(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x061C; + regs.ecx = 0x0000; + call_bios(®s); + + return regs.eax; +} + +static void __devinit bios_attach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x012E; + call_bios(®s); +} + +static void bios_detach(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x002E; + call_bios(®s); +} + +static u8 __devinit bios_get_cmos_address(void) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x051C; + call_bios(®s); + + return regs.ecx; +} + +static u16 __devinit bios_get_default_setting(u8 subsys) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = 0x0200 | subsys; + call_bios(®s); + + return regs.eax; +} + +static void bios_set_state(u8 subsys, int enable) +{ + struct regs regs; + + memset(®s, 0, sizeof (regs)); + regs.eax = 0x9610; + regs.ebx = (enable ? 0x0100 : 0x0000) | subsys; + call_bios(®s); +} + +/* Hardware database */ + +struct key_entry { + char type; /* See KE_* below */ + u8 code; + unsigned keycode; /* For KE_KEY */ +}; + +enum { KE_END, KE_KEY, KE_WIFI, KE_BLUETOOTH }; + +static const struct key_entry *keymap; /* = NULL; Current key map */ +static int have_wifi; +static int have_bluetooth; + +static int __init dmi_matched(struct dmi_system_id *dmi) +{ + const struct key_entry *key; + + keymap = dmi->driver_data; + for (key = keymap; key->type != KE_END; key++) { + if (key->type == KE_WIFI) { + have_wifi = 1; + break; + } else if (key->type == KE_BLUETOOTH) { + have_bluetooth = 1; + break; + } + } + return 1; +} + +static struct key_entry keymap_empty[] = { + { KE_END, 0 } +}; + +static struct key_entry keymap_fs_amilo_pro_v2000[] = { + { KE_KEY, 0x01, KEY_HELP }, + { KE_KEY, 0x11, KEY_PROG1 }, + { KE_KEY, 0x12, KEY_PROG2 }, + { KE_WIFI, 0x30, 0 }, + { KE_KEY, 0x31, KEY_MAIL }, + { KE_KEY, 0x36, KEY_WWW }, + { KE_END, 0 } +}; + +static struct key_entry keymap_wistron_ms2141[] = { + { KE_KEY, 0x11, KEY_PROG1 }, + { KE_KEY, 0x12, KEY_PROG2 }, + { KE_WIFI, 0x30, 0 }, + { KE_KEY, 0x22, KEY_REWIND }, + { KE_KEY, 0x23, KEY_FORWARD }, + { KE_KEY, 0x24, KEY_PLAYPAUSE }, + { KE_KEY, 0x25, KEY_STOPCD }, + { KE_KEY, 0x31, KEY_MAIL }, + { KE_KEY, 0x36, KEY_WWW }, + { KE_END, 0 } +}; + +static struct key_entry keymap_acer_aspire_1500[] = { + { KE_KEY, 0x11, KEY_PROG1 }, + { KE_KEY, 0x12, KEY_PROG2 }, + { KE_WIFI, 0x30, 0 }, + { KE_KEY, 0x31, KEY_MAIL }, + { KE_KEY, 0x36, KEY_WWW }, + { KE_BLUETOOTH, 0x44, 0 }, + { KE_END, 0 } +}; + +static struct key_entry keymap_acer_travelmate_240[] = { + { KE_KEY, 0x31, KEY_MAIL }, + { KE_KEY, 0x36, KEY_WWW }, + { KE_KEY, 0x11, KEY_PROG1 }, + { KE_KEY, 0x12, KEY_PROG2 }, + { KE_BLUETOOTH, 0x44, 0 }, + { KE_WIFI, 0x30, 0 }, + { KE_END, 0 } +}; + +/* + * If your machine is not here (which is currently rather likely), please send + * a list of buttons and their key codes (reported when loading this module + * with force=1) and the output of dmidecode to $MODULE_AUTHOR. + */ +static struct dmi_system_id dmi_ids[] = { + { + .callback = dmi_matched, + .ident = "Fujitsu-Siemens Amilo Pro V2000", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"), + DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2000"), + }, + .driver_data = keymap_fs_amilo_pro_v2000 + }, + { + .callback = dmi_matched, + .ident = "Acer Aspire 1500", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire 1500"), + }, + .driver_data = keymap_acer_aspire_1500 + }, + { + .callback = dmi_matched, + .ident = "Acer TravelMate 240", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "TravelMate 240"), + }, + .driver_data = keymap_acer_travelmate_240 + }, + { NULL, } +}; + +static int __init select_keymap(void) +{ + if (keymap_name != NULL) { + if (strcmp (keymap_name, "1557/MS2141") == 0) + keymap = keymap_wistron_ms2141; + else { + printk(KERN_ERR "wistron_btns: Keymap unknown\n"); + return -EINVAL; + } + } + dmi_check_system(dmi_ids); + if (keymap == NULL) { + if (!force) { + printk(KERN_ERR "wistron_btns: System unknown\n"); + return -ENODEV; + } + keymap = keymap_empty; + } + return 0; +} + + /* Input layer interface */ + +static struct input_dev *input_dev; + +static int __devinit setup_input_dev(void) +{ + const struct key_entry *key; + int error; + + input_dev = input_allocate_device(); + if (!input_dev) + return -ENOMEM; + + input_dev->name = "Wistron laptop buttons"; + input_dev->phys = "wistron/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->cdev.dev = &wistron_device->dev; + + for (key = keymap; key->type != KE_END; key++) { + if (key->type == KE_KEY) { + input_dev->evbit[LONG(EV_KEY)] = BIT(EV_KEY); + set_bit(key->keycode, input_dev->keybit); + } + } + + error = input_register_device(input_dev); + if (error) { + input_free_device(input_dev); + return error; + } + + return 0; +} + +static void report_key(unsigned keycode) +{ + input_report_key(input_dev, keycode, 1); + input_sync(input_dev); + input_report_key(input_dev, keycode, 0); + input_sync(input_dev); +} + + /* Driver core */ + +static int wifi_enabled; +static int bluetooth_enabled; + +static void poll_bios(unsigned long); + +static struct timer_list poll_timer = TIMER_INITIALIZER(poll_bios, 0, 0); + +static void handle_key(u8 code) +{ + const struct key_entry *key; + + for (key = keymap; key->type != KE_END; key++) { + if (code == key->code) { + switch (key->type) { + case KE_KEY: + report_key(key->keycode); + break; + + case KE_WIFI: + if (have_wifi) { + wifi_enabled = !wifi_enabled; + bios_set_state(WIFI, wifi_enabled); + } + break; + + case KE_BLUETOOTH: + if (have_bluetooth) { + bluetooth_enabled = !bluetooth_enabled; + bios_set_state(BLUETOOTH, bluetooth_enabled); + } + break; + + case KE_END: + default: + BUG(); + } + return; + } + } + printk(KERN_NOTICE "wistron_btns: Unknown key code %02X\n", code); +} + +static void poll_bios(unsigned long discard) +{ + u8 qlen; + u16 val; + + for (;;) { + qlen = CMOS_READ(cmos_address); + if (qlen == 0) + break; + val = bios_pop_queue(); + if (val != 0 && !discard) + handle_key((u8)val); + } + + mod_timer(&poll_timer, jiffies + HZ / POLL_FREQUENCY); +} + +static int __devinit wistron_probe(struct platform_device *dev) +{ + int err = setup_input_dev(); + if (err) + return err; + + bios_attach(); + cmos_address = bios_get_cmos_address(); + + if (have_wifi) { + u16 wifi = bios_get_default_setting(WIFI); + if (wifi & 1) + wifi_enabled = (wifi & 2) ? 1 : 0; + else + have_wifi = 0; + + if (have_wifi) + bios_set_state(WIFI, wifi_enabled); + } + + if (have_bluetooth) { + u16 bt = bios_get_default_setting(BLUETOOTH); + if (bt & 1) + bluetooth_enabled = (bt & 2) ? 1 : 0; + else + have_bluetooth = 0; + + if (have_bluetooth) + bios_set_state(BLUETOOTH, bluetooth_enabled); + } + + poll_bios(1); /* Flush stale event queue and arm timer */ + + return 0; +} + +static int __devexit wistron_remove(struct platform_device *dev) +{ + del_timer_sync(&poll_timer); + input_unregister_device(input_dev); + bios_detach(); + + return 0; +} + +#ifdef CONFIG_PM +static int wistron_suspend(struct platform_device *dev, pm_message_t state) +{ + del_timer_sync(&poll_timer); + + if (have_wifi) + bios_set_state(WIFI, 0); + + if (have_bluetooth) + bios_set_state(BLUETOOTH, 0); + + return 0; +} + +static int wistron_resume(struct platform_device *dev) +{ + if (have_wifi) + bios_set_state(WIFI, wifi_enabled); + + if (have_bluetooth) + bios_set_state(BLUETOOTH, bluetooth_enabled); + + poll_bios(1); + + return 0; +} +#else +#define wistron_suspend NULL +#define wistron_resume NULL +#endif + +static struct platform_driver wistron_driver = { + .driver = { + .name = "wistron-bios", + .owner = THIS_MODULE, + }, + .probe = wistron_probe, + .remove = __devexit_p(wistron_remove), + .suspend = wistron_suspend, + .resume = wistron_resume, +}; + +static int __init wb_module_init(void) +{ + int err; + + err = select_keymap(); + if (err) + return err; + + err = map_bios(); + if (err) + return err; + + err = platform_driver_register(&wistron_driver); + if (err) + goto err_unmap_bios; + + wistron_device = platform_device_alloc("wistron-bios", -1); + if (!wistron_device) { + err = -ENOMEM; + goto err_unregister_driver; + } + + err = platform_device_add(wistron_device); + if (err) + goto err_free_device; + + return 0; + + err_free_device: + platform_device_put(wistron_device); + err_unregister_driver: + platform_driver_unregister(&wistron_driver); + err_unmap_bios: + unmap_bios(); + + return err; +} + +static void __exit wb_module_exit(void) +{ + platform_device_unregister(wistron_device); + platform_driver_unregister(&wistron_driver); + unmap_bios(); +} + +module_init(wb_module_init); +module_exit(wb_module_exit); |