/* * ACPI driver for Topstar notebooks (hotkeys support only) * * Copyright (c) 2009 Herton Ronaldo Krzesinski * * Implementation inspired by existing x86 platform drivers, in special * asus/eepc/fujitsu-laptop, thanks to their authors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include #include #include #include #include #include #define ACPI_TOPSTAR_CLASS "topstar" struct topstar_hkey { struct input_dev *inputdev; }; struct tps_key_entry { u8 code; u16 keycode; }; static struct tps_key_entry topstar_keymap[] = { { 0x80, KEY_BRIGHTNESSUP }, { 0x81, KEY_BRIGHTNESSDOWN }, { 0x83, KEY_VOLUMEUP }, { 0x84, KEY_VOLUMEDOWN }, { 0x85, KEY_MUTE }, { 0x86, KEY_SWITCHVIDEOMODE }, { 0x87, KEY_F13 }, /* touchpad enable/disable key */ { 0x88, KEY_WLAN }, { 0x8a, KEY_WWW }, { 0x8b, KEY_MAIL }, { 0x8c, KEY_MEDIA }, { 0x96, KEY_F14 }, /* G key? */ { } }; static struct tps_key_entry *tps_get_key_by_scancode(unsigned int code) { struct tps_key_entry *key; for (key = topstar_keymap; key->code; key++) if (code == key->code) return key; return NULL; } static struct tps_key_entry *tps_get_key_by_keycode(unsigned int code) { struct tps_key_entry *key; for (key = topstar_keymap; key->code; key++) if (code == key->keycode) return key; return NULL; } static void acpi_topstar_notify(struct acpi_device *device, u32 event) { struct tps_key_entry *key; static bool dup_evnt[2]; bool *dup; struct topstar_hkey *hkey = acpi_driver_data(device); /* 0x83 and 0x84 key events comes duplicated... */ if (event == 0x83 || event == 0x84) { dup = &dup_evnt[event - 0x83]; if (*dup) { *dup = false; return; } *dup = true; } /* * 'G key' generate two event codes, convert to only * one event/key code for now (3G switch?) */ if (event == 0x97) event = 0x96; key = tps_get_key_by_scancode(event); if (key) { input_report_key(hkey->inputdev, key->keycode, 1); input_sync(hkey->inputdev); input_report_key(hkey->inputdev, key->keycode, 0); input_sync(hkey->inputdev); return; } /* Known non hotkey events don't handled or that we don't care yet */ if (event == 0x8e || event == 0x8f || event == 0x90) return; pr_info("unknown event = 0x%02x\n", event); } static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) { acpi_status status; union acpi_object fncx_params[1] = { { .type = ACPI_TYPE_INTEGER } }; struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] }; fncx_params[0].integer.value = state ? 0x86 : 0x87; status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL); if (ACPI_FAILURE(status)) { pr_err("Unable to switch FNCX notifications\n"); return -ENODEV; } return 0; } static int topstar_getkeycode(struct input_dev *dev, unsigned int scancode, unsigned int *keycode) { struct tps_key_entry *key = tps_get_key_by_scancode(scancode); if (!key) return -EINVAL; *keycode = key->keycode; return 0; } static int topstar_setkeycode(struct input_dev *dev, unsigned int scancode, unsigned int keycode) { struct tps_key_entry *key; int old_keycode; key = tps_get_key_by_scancode(scancode); if (!key) return -EINVAL; old_keycode = key->keycode; key->keycode = keycode; set_bit(keycode, dev->keybit); if (!tps_get_key_by_keycode(old_keycode)) clear_bit(old_keycode, dev->keybit); return 0; } static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) { struct tps_key_entry *key; hkey->inputdev = input_allocate_device(); if (!hkey->inputdev) { pr_err("Unable to allocate input device\n"); return -ENODEV; } hkey->inputdev->name = "Topstar Laptop extra buttons"; hkey->inputdev->phys = "topstar/input0"; hkey->inputdev->id.bustype = BUS_HOST; hkey->inputdev->getkeycode = topstar_getkeycode; hkey->inputdev->setkeycode = topstar_setkeycode; for (key = topstar_keymap; key->code; key++) { set_bit(EV_KEY, hkey->inputdev->evbit); set_bit(key->keycode, hkey->inputdev->keybit); } if (input_register_device(hkey->inputdev)) { pr_err("Unable to register input device\n"); input_free_device(hkey->inputdev); return -ENODEV; } return 0; } static int acpi_topstar_add(struct acpi_device *device) { struct topstar_hkey *tps_hkey; tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); if (!tps_hkey) return -ENOMEM; strcpy(acpi_device_name(device), "Topstar TPSACPI"); strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); if (acpi_topstar_fncx_switch(device, true)) goto add_err; if (acpi_topstar_init_hkey(tps_hkey)) goto add_err; device->driver_data = tps_hkey; return 0; add_err: kfree(tps_hkey); return -ENODEV; } static int acpi_topstar_remove(struct acpi_device *device, int type) { struct topstar_hkey *tps_hkey = acpi_driver_data(device); acpi_topstar_fncx_switch(device, false); input_unregister_device(tps_hkey->inputdev); kfree(tps_hkey); return 0; } static const struct acpi_device_id topstar_device_ids[] = { { "TPSACPI01", 0 }, { "", 0 }, }; MODULE_DEVICE_TABLE(acpi, topstar_device_ids); static struct acpi_driver acpi_topstar_driver = { .name = "Topstar laptop ACPI driver", .class = ACPI_TOPSTAR_CLASS, .ids = topstar_device_ids, .ops = { .add = acpi_topstar_add, .remove = acpi_topstar_remove, .notify = acpi_topstar_notify, }, }; static int __init topstar_laptop_init(void) { int ret; ret = acpi_bus_register_driver(&acpi_topstar_driver); if (ret < 0) return ret; printk(KERN_INFO "Topstar Laptop ACPI extras driver loaded\n"); return 0; } static void __exit topstar_laptop_exit(void) { acpi_bus_unregister_driver(&acpi_topstar_driver); } module_init(topstar_laptop_init); module_exit(topstar_laptop_exit); MODULE_AUTHOR("Herton Ronaldo Krzesinski"); MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); MODULE_LICENSE("GPL");