Commit | Line | Data |
---|---|---|
9caeb532 HRK |
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> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
9caeb532 HRK |
20 | #include <linux/acpi.h> |
21 | #include <linux/input.h> | |
22 | ||
23 | #define ACPI_TOPSTAR_CLASS "topstar" | |
24 | ||
25 | struct topstar_hkey { | |
26 | struct input_dev *inputdev; | |
27 | }; | |
28 | ||
29 | struct tps_key_entry { | |
30 | u8 code; | |
31 | u16 keycode; | |
32 | }; | |
33 | ||
34 | static 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 | ||
58b93995 | 50 | static struct tps_key_entry *tps_get_key_by_scancode(unsigned int code) |
9caeb532 HRK |
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 | ||
58b93995 | 61 | static struct tps_key_entry *tps_get_key_by_keycode(unsigned int code) |
9caeb532 HRK |
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 | ||
72 | static 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 | ||
112 | static 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 | ||
58b93995 DT |
130 | static int topstar_getkeycode(struct input_dev *dev, |
131 | unsigned int scancode, unsigned int *keycode) | |
9caeb532 HRK |
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 | ||
58b93995 DT |
142 | static int topstar_setkeycode(struct input_dev *dev, |
143 | unsigned int scancode, unsigned int keycode) | |
9caeb532 HRK |
144 | { |
145 | struct tps_key_entry *key; | |
146 | int old_keycode; | |
147 | ||
9caeb532 HRK |
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 | ||
161 | static 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 | ||
188 | static 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 | ||
208 | add_err: | |
209 | kfree(tps_hkey); | |
210 | return -ENODEV; | |
211 | } | |
212 | ||
213 | static 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 | ||
225 | static const struct acpi_device_id topstar_device_ids[] = { | |
226 | { "TPSACPI01", 0 }, | |
227 | { "", 0 }, | |
228 | }; | |
229 | MODULE_DEVICE_TABLE(acpi, topstar_device_ids); | |
230 | ||
231 | static 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 | ||
242 | static 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 | ||
255 | static void __exit topstar_laptop_exit(void) | |
256 | { | |
257 | acpi_bus_unregister_driver(&acpi_topstar_driver); | |
258 | } | |
259 | ||
260 | module_init(topstar_laptop_init); | |
261 | module_exit(topstar_laptop_exit); | |
262 | ||
263 | MODULE_AUTHOR("Herton Ronaldo Krzesinski"); | |
264 | MODULE_DESCRIPTION("Topstar Laptop ACPI Extras driver"); | |
265 | MODULE_LICENSE("GPL"); |