1/* 2 * ACPI driver for Topstar notebooks (hotkeys support only) 3 * 4 * Copyright (c) 2009 Herton Ronaldo Krzesinski <herton@mandriva.com.br> 5 * 6 * Implementation inspired by existing x86 platform drivers, in special 7 * asus/eepc/fujitsu-laptop, thanks to their authors 8 * 9 * This program is free software; you can redistribute it and/or modify 10 * it under the terms of the GNU General Public License version 2 as 11 * published by the Free Software Foundation. 12 */ 13 14#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt 15 16#include <linux/kernel.h> 17#include <linux/module.h> 18#include <linux/init.h> 19#include <linux/slab.h> 20#include <linux/acpi.h> 21#include <linux/input.h> 22 23#define ACPI_TOPSTAR_CLASS "topstar" 24 25struct topstar_hkey { 26 struct input_dev *inputdev; 27}; 28 29struct tps_key_entry { 30 u8 code; 31 u16 keycode; 32}; 33 34static struct tps_key_entry topstar_keymap[] = { 35 { 0x80, KEY_BRIGHTNESSUP }, 36 { 0x81, KEY_BRIGHTNESSDOWN }, 37 { 0x83, KEY_VOLUMEUP }, 38 { 0x84, KEY_VOLUMEDOWN }, 39 { 0x85, KEY_MUTE }, 40 { 0x86, KEY_SWITCHVIDEOMODE }, 41 { 0x87, KEY_F13 }, /* touchpad enable/disable key */ 42 { 0x88, KEY_WLAN }, 43 { 0x8a, KEY_WWW }, 44 { 0x8b, KEY_MAIL }, 45 { 0x8c, KEY_MEDIA }, 46 { 0x96, KEY_F14 }, /* G key? */ 47 { } 48}; 49 50static struct tps_key_entry *tps_get_key_by_scancode(unsigned int code) 51{ 52 struct tps_key_entry *key; 53 54 for (key = topstar_keymap; key->code; key++) 55 if (code == key->code) 56 return key; 57 58 return NULL; 59} 60 61static struct tps_key_entry *tps_get_key_by_keycode(unsigned int code) 62{ 63 struct tps_key_entry *key; 64 65 for (key = topstar_keymap; key->code; key++) 66 if (code == key->keycode) 67 return key; 68 69 return NULL; 70} 71 72static void acpi_topstar_notify(struct acpi_device *device, u32 event) 73{ 74 struct tps_key_entry *key; 75 static bool dup_evnt[2]; 76 bool *dup; 77 struct topstar_hkey *hkey = acpi_driver_data(device); 78 79 /* 0x83 and 0x84 key events comes duplicated... */ 80 if (event == 0x83 || event == 0x84) { 81 dup = &dup_evnt[event - 0x83]; 82 if (*dup) { 83 *dup = false; 84 return; 85 } 86 *dup = true; 87 } 88 89 /* 90 * 'G key' generate two event codes, convert to only 91 * one event/key code for now (3G switch?) 92 */ 93 if (event == 0x97) 94 event = 0x96; 95 96 key = tps_get_key_by_scancode(event); 97 if (key) { 98 input_report_key(hkey->inputdev, key->keycode, 1); 99 input_sync(hkey->inputdev); 100 input_report_key(hkey->inputdev, key->keycode, 0); 101 input_sync(hkey->inputdev); 102 return; 103 } 104 105 /* Known non hotkey events don't handled or that we don't care yet */ 106 if (event == 0x8e || event == 0x8f || event == 0x90) 107 return; 108 109 pr_info("unknown event = 0x%02x\n", event); 110} 111 112static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) 113{ 114 acpi_status status; 115 union acpi_object fncx_params[1] = { 116 { .type = ACPI_TYPE_INTEGER } 117 }; 118 struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] }; 119 120 fncx_params[0].integer.value = state ? 0x86 : 0x87; 121 status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL); 122 if (ACPI_FAILURE(status)) { 123 pr_err("Unable to switch FNCX notifications\n"); 124 return -ENODEV; 125 } 126 127 return 0; 128} 129 130static int topstar_getkeycode(struct input_dev *dev, 131 unsigned int scancode, unsigned int *keycode) 132{ 133 struct tps_key_entry *key = tps_get_key_by_scancode(scancode); 134 135 if (!key) 136 return -EINVAL; 137 138 *keycode = key->keycode; 139 return 0; 140} 141 142static int topstar_setkeycode(struct input_dev *dev, 143 unsigned int scancode, unsigned int keycode) 144{ 145 struct tps_key_entry *key; 146 int old_keycode; 147 148 key = tps_get_key_by_scancode(scancode); 149 150 if (!key) 151 return -EINVAL; 152 153 old_keycode = key->keycode; 154 key->keycode = keycode; 155 set_bit(keycode, dev->keybit); 156 if (!tps_get_key_by_keycode(old_keycode)) 157 clear_bit(old_keycode, dev->keybit); 158 return 0; 159} 160 161static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) 162{ 163 struct tps_key_entry *key; 164 165 hkey->inputdev = input_allocate_device(); 166 if (!hkey->inputdev) { 167 pr_err("Unable to allocate input device\n"); 168 return -ENODEV; 169 } 170 hkey->inputdev->name = "Topstar Laptop extra buttons"; 171 hkey->inputdev->phys = "topstar/input0"; 172 hkey->inputdev->id.bustype = BUS_HOST; 173 hkey->inputdev->getkeycode = topstar_getkeycode; 174 hkey->inputdev->setkeycode = topstar_setkeycode; 175 for (key = topstar_keymap; key->code; key++) { 176 set_bit(EV_KEY, hkey->inputdev->evbit); 177 set_bit(key->keycode, hkey->inputdev->keybit); 178 } 179 if (input_register_device(hkey->inputdev)) { 180 pr_err("Unable to register input device\n"); 181 input_free_device(hkey->inputdev); 182 return -ENODEV; 183 } 184 185 return 0; 186} 187 188static int acpi_topstar_add(struct acpi_device *device) 189{ 190 struct topstar_hkey *tps_hkey; 191 192 tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); 193 if (!tps_hkey) 194 return -ENOMEM; 195 196 strcpy(acpi_device_name(device), "Topstar TPSACPI"); 197 strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); 198 199 if (acpi_topstar_fncx_switch(device, true)) 200 goto add_err; 201 202 if (acpi_topstar_init_hkey(tps_hkey)) 203 goto add_err; 204 205 device->driver_data = tps_hkey; 206 return 0; 207 208add_err: 209 kfree(tps_hkey); 210 return -ENODEV; 211} 212 213static int acpi_topstar_remove(struct acpi_device *device, int type) 214{ 215 struct topstar_hkey *tps_hkey = acpi_driver_data(device); 216 217 acpi_topstar_fncx_switch(device, false); 218 219 input_unregister_device(tps_hkey->inputdev); 220 kfree(tps_hkey); 221 222 return 0; 223} 224 225static const struct acpi_device_id topstar_device_ids[] = { 226 { "TPSACPI01", 0 }, 227 { "", 0 }, 228}; 229MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 230 231static struct acpi_driver acpi_topstar_driver = { 232 .name = "Topstar laptop ACPI driver", 233 .class = ACPI_TOPSTAR_CLASS, 234 .ids = topstar_device_ids, 235 .ops = { 236 .add = acpi_topstar_add, 237 .remove = acpi_topstar_remove, 238 .notify = acpi_topstar_notify, 239 }, 240}; 241 242static int __init topstar_laptop_init(void) 243{ 244 int ret; 245 246 ret = acpi_bus_register_driver(&acpi_topstar_driver); 247 if (ret < 0) 248 return ret; 249 250 printk(KERN_INFO "Topstar Laptop ACPI extras driver loaded\n"); 251 252 return 0; 253} 254 255static void __exit topstar_laptop_exit(void) 256{ 257 acpi_bus_unregister_driver(&acpi_topstar_driver); 258} 259 260module_init(topstar_laptop_init); 261module_exit(topstar_laptop_exit); 262 263MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 264MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 265MODULE_LICENSE("GPL"); 266