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 26struct topstar_hkey { 27 struct input_dev *inputdev; 28}; 29 30static 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 60static 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 80static 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 94static 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_keymap; 117 } 118 119 hkey->inputdev = input; 120 return 0; 121 122 err_free_keymap: 123 sparse_keymap_free(input); 124 err_free_dev: 125 input_free_device(input); 126 return error; 127} 128 129static int acpi_topstar_add(struct acpi_device *device) 130{ 131 struct topstar_hkey *tps_hkey; 132 133 tps_hkey = kzalloc(sizeof(struct topstar_hkey), GFP_KERNEL); 134 if (!tps_hkey) 135 return -ENOMEM; 136 137 strcpy(acpi_device_name(device), "Topstar TPSACPI"); 138 strcpy(acpi_device_class(device), ACPI_TOPSTAR_CLASS); 139 140 if (acpi_topstar_fncx_switch(device, true)) 141 goto add_err; 142 143 if (acpi_topstar_init_hkey(tps_hkey)) 144 goto add_err; 145 146 device->driver_data = tps_hkey; 147 return 0; 148 149add_err: 150 kfree(tps_hkey); 151 return -ENODEV; 152} 153 154static int acpi_topstar_remove(struct acpi_device *device) 155{ 156 struct topstar_hkey *tps_hkey = acpi_driver_data(device); 157 158 acpi_topstar_fncx_switch(device, false); 159 160 sparse_keymap_free(tps_hkey->inputdev); 161 input_unregister_device(tps_hkey->inputdev); 162 kfree(tps_hkey); 163 164 return 0; 165} 166 167static const struct acpi_device_id topstar_device_ids[] = { 168 { "TPSACPI01", 0 }, 169 { "", 0 }, 170}; 171MODULE_DEVICE_TABLE(acpi, topstar_device_ids); 172 173static struct acpi_driver acpi_topstar_driver = { 174 .name = "Topstar laptop ACPI driver", 175 .class = ACPI_TOPSTAR_CLASS, 176 .ids = topstar_device_ids, 177 .ops = { 178 .add = acpi_topstar_add, 179 .remove = acpi_topstar_remove, 180 .notify = acpi_topstar_notify, 181 }, 182}; 183module_acpi_driver(acpi_topstar_driver); 184 185MODULE_AUTHOR("Herton Ronaldo Krzesinski"); 186MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); 187MODULE_LICENSE("GPL"); 188