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 #include <linux/input/sparse-keymap.h> 23 24 #define ACPI_TOPSTAR_CLASS "topstar" 25 26 struct topstar_hkey { 27 struct input_dev *inputdev; 28 }; 29 30 static const struct key_entry topstar_keymap[] = { 31 { KE_KEY, 0x80, { KEY_BRIGHTNESSUP } }, 32 { KE_KEY, 0x81, { KEY_BRIGHTNESSDOWN } }, 33 { KE_KEY, 0x83, { KEY_VOLUMEUP } }, 34 { KE_KEY, 0x84, { KEY_VOLUMEDOWN } }, 35 { KE_KEY, 0x85, { KEY_MUTE } }, 36 { KE_KEY, 0x86, { KEY_SWITCHVIDEOMODE } }, 37 { KE_KEY, 0x87, { KEY_F13 } }, /* touchpad enable/disable key */ 38 { KE_KEY, 0x88, { KEY_WLAN } }, 39 { KE_KEY, 0x8a, { KEY_WWW } }, 40 { KE_KEY, 0x8b, { KEY_MAIL } }, 41 { KE_KEY, 0x8c, { KEY_MEDIA } }, 42 43 /* Known non hotkey events don't handled or that we don't care yet */ 44 { KE_IGNORE, 0x82, }, /* backlight event */ 45 { KE_IGNORE, 0x8e, }, 46 { KE_IGNORE, 0x8f, }, 47 { KE_IGNORE, 0x90, }, 48 49 /* 50 * 'G key' generate two event codes, convert to only 51 * one event/key code for now, consider replacing by 52 * a switch (3G switch - SW_3G?) 53 */ 54 { KE_KEY, 0x96, { KEY_F14 } }, 55 { KE_KEY, 0x97, { KEY_F14 } }, 56 57 { KE_END, 0 } 58 }; 59 60 static void acpi_topstar_notify(struct acpi_device *device, u32 event) 61 { 62 static bool dup_evnt[2]; 63 bool *dup; 64 struct topstar_hkey *hkey = acpi_driver_data(device); 65 66 /* 0x83 and 0x84 key events comes duplicated... */ 67 if (event == 0x83 || event == 0x84) { 68 dup = &dup_evnt[event - 0x83]; 69 if (*dup) { 70 *dup = false; 71 return; 72 } 73 *dup = true; 74 } 75 76 if (!sparse_keymap_report_event(hkey->inputdev, event, 1, true)) 77 pr_info("unknown event = 0x%02x\n", event); 78 } 79 80 static int acpi_topstar_fncx_switch(struct acpi_device *device, bool state) 81 { 82 acpi_status status; 83 84 status = acpi_execute_simple_method(device->handle, "FNCX", 85 state ? 0x86 : 0x87); 86 if (ACPI_FAILURE(status)) { 87 pr_err("Unable to switch FNCX notifications\n"); 88 return -ENODEV; 89 } 90 91 return 0; 92 } 93 94 static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) 95 { 96 struct input_dev *input; 97 int error; 98 99 input = input_allocate_device(); 100 if (!input) { 101 pr_err("Unable to allocate input device\n"); 102 return -ENOMEM; 103 } 104 105 input->name = "Topstar Laptop extra buttons"; 106 input->phys = "topstar/input0"; 107 input->id.bustype = BUS_HOST; 108 109 error = sparse_keymap_setup(input, topstar_keymap, NULL); 110 if (error) { 111 pr_err("Unable to setup input device keymap\n"); 112 goto err_free_dev; 113 } 114 115 error = input_register_device(input); 116 if (error) { 117 pr_err("Unable to register input device\n"); 118 goto err_free_keymap; 119 } 120 121 hkey->inputdev = input; 122 return 0; 123 124 err_free_keymap: 125 sparse_keymap_free(input); 126 err_free_dev: 127 input_free_device(input); 128 return error; 129 } 130 131 static int acpi_topstar_add(struct acpi_device *device) 132 { 133 struct topstar_hkey *tps_hkey; 134 135 tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); 136 if (!tps_hkey) 137 return -ENOMEM; 138 139 strcpy(acpi_device_name(device), "Topstar TPSACPI"); 140 strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); 141 142 if (acpi_topstar_fncx_switch(device, true)) 143 goto add_err; 144 145 if (acpi_topstar_init_hkey(tps_hkey)) 146 goto add_err; 147 148 device->driver_data = tps_hkey; 149 return 0; 150 151 add_err: 152 kfree(tps_hkey); 153 return -ENODEV; 154 } 155 156 static int acpi_topstar_remove(struct acpi_device *device) 157 { 158 struct topstar_hkey *tps_hkey = acpi_driver_data(device); 159 160 acpi_topstar_fncx_switch(device, false); 161 162 sparse_keymap_free(tps_hkey->inputdev); 163 input_unregister_device(tps_hkey->inputdev); 164 kfree(tps_hkey); 165 166 return 0; 167 } 168 169 static const struct acpi_device_id topstar_device_ids[] = { 170 { "TPSACPI01", 0 }, 171 { "", 0 }, 172 }; 173 MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 174 175 static struct acpi_driver acpi_topstar_driver = { 176 .name = "Topstar laptop ACPI driver", 177 .class = ACPI_TOPSTAR_CLASS, 178 .ids = topstar_device_ids, 179 .ops = { 180 .add = acpi_topstar_add, 181 .remove = acpi_topstar_remove, 182 .notify = acpi_topstar_notify, 183 }, 184 }; 185 module_acpi_driver(acpi_topstar_driver); 186 187 MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 188 MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 189 MODULE_LICENSE("GPL"); 190