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 union acpi_object fncx_params[1] = { 84 { .type = ACPI_TYPE_INTEGER } 85 }; 86 struct acpi_object_list fncx_arg_list = { 1, &fncx_params[0] }; 87 88 fncx_params[0].integer.value = state ? 0x86 : 0x87; 89 status = acpi_evaluate_object(device->handle, "FNCX", &fncx_arg_list, NULL); 90 if (ACPI_FAILURE(status)) { 91 pr_err("Unable to switch FNCX notifications\n"); 92 return -ENODEV; 93 } 94 95 return 0; 96 } 97 98 static int acpi_topstar_init_hkey(struct topstar_hkey *hkey) 99 { 100 struct input_dev *input; 101 int error; 102 103 input = input_allocate_device(); 104 if (!input) { 105 pr_err("Unable to allocate input device\n"); 106 return -ENOMEM; 107 } 108 109 input->name = "Topstar Laptop extra buttons"; 110 input->phys = "topstar/input0"; 111 input->id.bustype = BUS_HOST; 112 113 error = sparse_keymap_setup(input, topstar_keymap, NULL); 114 if (error) { 115 pr_err("Unable to setup input device keymap\n"); 116 goto err_free_dev; 117 } 118 119 error = input_register_device(input); 120 if (error) { 121 pr_err("Unable to register input device\n"); 122 goto err_free_keymap; 123 } 124 125 hkey->inputdev = input; 126 return 0; 127 128 err_free_keymap: 129 sparse_keymap_free(input); 130 err_free_dev: 131 input_free_device(input); 132 return error; 133 } 134 135 static int acpi_topstar_add(struct acpi_device *device) 136 { 137 struct topstar_hkey *tps_hkey; 138 139 tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); 140 if (!tps_hkey) 141 return -ENOMEM; 142 143 strcpy(acpi_device_name(device), "Topstar TPSACPI"); 144 strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); 145 146 if (acpi_topstar_fncx_switch(device, true)) 147 goto add_err; 148 149 if (acpi_topstar_init_hkey(tps_hkey)) 150 goto add_err; 151 152 device->driver_data = tps_hkey; 153 return 0; 154 155 add_err: 156 kfree(tps_hkey); 157 return -ENODEV; 158 } 159 160 static int acpi_topstar_remove(struct acpi_device *device) 161 { 162 struct topstar_hkey *tps_hkey = acpi_driver_data(device); 163 164 acpi_topstar_fncx_switch(device, false); 165 166 sparse_keymap_free(tps_hkey->inputdev); 167 input_unregister_device(tps_hkey->inputdev); 168 kfree(tps_hkey); 169 170 return 0; 171 } 172 173 static const struct acpi_device_id topstar_device_ids[] = { 174 { "TPSACPI01", 0 }, 175 { "", 0 }, 176 }; 177 MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 178 179 static struct acpi_driver acpi_topstar_driver = { 180 .name = "Topstar laptop ACPI driver", 181 .class = ACPI_TOPSTAR_CLASS, 182 .ids = topstar_device_ids, 183 .ops = { 184 .add = acpi_topstar_add, 185 .remove = acpi_topstar_remove, 186 .notify = acpi_topstar_notify, 187 }, 188 }; 189 module_acpi_driver(acpi_topstar_driver); 190 191 MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 192 MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 193 MODULE_LICENSE("GPL"); 194