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 return -ENOMEM; 102 103 input->name = "Topstar Laptop extra buttons"; 104 input->phys = "topstar/input0"; 105 input->id.bustype = BUS_HOST; 106 107 error = sparse_keymap_setup(input, topstar_keymap, NULL); 108 if (error) { 109 pr_err("Unable to setup input device keymap\n"); 110 goto err_free_dev; 111 } 112 113 error = input_register_device(input); 114 if (error) { 115 pr_err("Unable to register input device\n"); 116 goto err_free_dev; 117 } 118 119 hkey->inputdev = input; 120 return 0; 121 122 err_free_dev: 123 input_free_device(input); 124 return error; 125 } 126 127 static int acpi_topstar_add(struct acpi_device *device) 128 { 129 struct topstar_hkey *tps_hkey; 130 131 tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); 132 if (!tps_hkey) 133 return -ENOMEM; 134 135 strcpy(acpi_device_name(device), "Topstar TPSACPI"); 136 strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); 137 138 if (acpi_topstar_fncx_switch(device, true)) 139 goto add_err; 140 141 if (acpi_topstar_init_hkey(tps_hkey)) 142 goto add_err; 143 144 device->driver_data = tps_hkey; 145 return 0; 146 147 add_err: 148 kfree(tps_hkey); 149 return -ENODEV; 150 } 151 152 static int acpi_topstar_remove(struct acpi_device *device) 153 { 154 struct topstar_hkey *tps_hkey = acpi_driver_data(device); 155 156 acpi_topstar_fncx_switch(device, false); 157 158 input_unregister_device(tps_hkey->inputdev); 159 kfree(tps_hkey); 160 161 return 0; 162 } 163 164 static const struct acpi_device_id topstar_device_ids[] = { 165 { "TPS0001", 0 }, 166 { "TPSACPI01", 0 }, 167 { "", 0 }, 168 }; 169 MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 170 171 static struct acpi_driver acpi_topstar_driver = { 172 .name = "Topstar laptop ACPI driver", 173 .class = ACPI_TOPSTAR_CLASS, 174 .ids = topstar_device_ids, 175 .ops = { 176 .add = acpi_topstar_add, 177 .remove = acpi_topstar_remove, 178 .notify = acpi_topstar_notify, 179 }, 180 }; 181 module_acpi_driver(acpi_topstar_driver); 182 183 MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 184 MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 185 MODULE_LICENSE("GPL"); 186