aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDmitry Torokhov <dtor_core@ameritech.net>2005-11-20 00:50:06 -0500
committerDmitry Torokhov <dtor_core@ameritech.net>2005-11-20 00:50:06 -0500
commit5fc1468029e2a4da90ded1e0e2cdd94fbdf83bac (patch)
tree38cbda2acfd20e4f71da4ef62374797e2f55aed5
parent0d4c859734a818721b2d5ac712283ba8f92bd23a (diff)
Input: add Wistron driver
A driver for laptop buttons using an x86 BIOS interface that is apparently used on quite a few laptops and seems to be originating from Wistron. This driver currently "knows" only about Fujitsu-Siemens Amilo Pro V2000 (i.e. it can detect the laptop using DMI and it contains the keycode->key meaning mapping for this laptop) and Xeron SonicPro X 155G (probably can't be reliably autodetected, requires a module parameter), adding other laptops should be easy. In addition to reporting button presses to the input layer the driver also allows enabling/disabling the embedded wireless NIC (using the "Wifi" button); this is done using the same BIOS interface, so it seems only logical to keep the implementation together. Any flexibility possibly gained by allowing users to remap the function of the "Wifi" button is IMHO not worth it when weighted against the necessity to run an user-space daemon to convert button presses to wifi state changes. Signed-off-by: Miloslav Trmac <mitr@volny.cz> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
-rw-r--r--MAINTAINERS5
-rw-r--r--drivers/input/misc/Kconfig10
-rw-r--r--drivers/input/misc/Makefile1
-rw-r--r--drivers/input/misc/wistron_btns.c443
4 files changed, 459 insertions, 0 deletions
diff --git a/MAINTAINERS b/MAINTAINERS
index f239ac4762dd..c5cf7d7e58b2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2907,6 +2907,11 @@ M: zaga@fly.cc.fer.hr
2907L: linux-scsi@vger.kernel.org 2907L: linux-scsi@vger.kernel.org
2908S: Maintained 2908S: Maintained
2909 2909
2910WISTRON LAPTOP BUTTON DRIVER
2911P: Miloslav Trmac
2912M: mitr@volny.cz
2913S: Maintained
2914
2910WL3501 WIRELESS PCMCIA CARD DRIVER 2915WL3501 WIRELESS PCMCIA CARD DRIVER
2911P: Arnaldo Carvalho de Melo 2916P: Arnaldo Carvalho de Melo
2912M: acme@conectiva.com.br 2917M: acme@conectiva.com.br
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index b3eaac1b35b6..06e91c856b35 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -40,6 +40,16 @@ config INPUT_M68K_BEEP
40 tristate "M68k Beeper support" 40 tristate "M68k Beeper support"
41 depends on M68K 41 depends on M68K
42 42
43config INPUT_WISTRON_BTNS
44 tristate "x86 Wistron laptop button interface"
45 depends on X86
46 help
47 Say Y here for support of Winstron laptop button interface, used on
48 laptops of various brands, including Acer and Fujitsu-Siemens.
49
50 To compile this driver as a module, choose M here: the module will
51 be called wistron_btns.
52
43config INPUT_UINPUT 53config INPUT_UINPUT
44 tristate "User level driver support" 54 tristate "User level driver support"
45 help 55 help
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index f8d01c69f349..ce44cce01285 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -9,4 +9,5 @@ obj-$(CONFIG_INPUT_PCSPKR) += pcspkr.o
9obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o 9obj-$(CONFIG_INPUT_M68K_BEEP) += m68kspkr.o
10obj-$(CONFIG_INPUT_98SPKR) += 98spkr.o 10obj-$(CONFIG_INPUT_98SPKR) += 98spkr.o
11obj-$(CONFIG_INPUT_UINPUT) += uinput.o 11obj-$(CONFIG_INPUT_UINPUT) += uinput.o
12obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o
12obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o 13obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
diff --git a/drivers/input/misc/wistron_btns.c b/drivers/input/misc/wistron_btns.c
new file mode 100644
index 000000000000..76d32e365b5d
--- /dev/null
+++ b/drivers/input/misc/wistron_btns.c
@@ -0,0 +1,443 @@
1/*
2 * Wistron laptop button driver
3 * Copyright (C) 2005 Miloslav Trmac <mitr@volny.cz>
4 *
5 * You can redistribute and/or modify this program under the terms of the
6 * GNU General Public License version 2 as published by the Free Software
7 * Foundation.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 * Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 59 Temple Place Suite 330, Boston, MA 02111-1307, USA.
17 */
18#include <asm/io.h>
19#include <linux/dmi.h>
20#include <linux/init.h>
21#include <linux/input.h>
22#include <linux/interrupt.h>
23#include <linux/kernel.h>
24#include <linux/mc146818rtc.h>
25#include <linux/module.h>
26#include <linux/preempt.h>
27#include <linux/string.h>
28#include <linux/timer.h>
29#include <linux/types.h>
30
31/*
32 * Number of attempts to read data from queue per poll;
33 * the queue can hold up to 31 entries
34 */
35#define MAX_POLL_ITERATIONS 64
36
37#define POLL_FREQUENCY 10 /* Number of polls per second */
38
39#if POLL_FREQUENCY > HZ
40#error "POLL_FREQUENCY too high"
41#endif
42
43MODULE_AUTHOR("Miloslav Trmac <mitr@volny.cz>");
44MODULE_DESCRIPTION("Wistron laptop button driver");
45MODULE_LICENSE("GPL v2");
46MODULE_VERSION("0.1");
47
48static int force; /* = 0; */
49module_param(force, bool, 0);
50MODULE_PARM_DESC(force, "Load even if computer is not in database");
51
52static char *keymap_name; /* = NULL; */
53module_param_named(keymap, keymap_name, charp, 0);
54MODULE_PARM_DESC(keymap, "Keymap name, if it can't be autodetected");
55
56 /* BIOS interface implementation */
57
58static void __iomem *bios_entry_point; /* BIOS routine entry point */
59static void __iomem *bios_code_map_base;
60static void __iomem *bios_data_map_base;
61
62static u8 cmos_address;
63
64struct regs {
65 u32 eax, ebx, ecx;
66};
67
68static void call_bios(struct regs *regs)
69{
70 unsigned long flags;
71
72 preempt_disable();
73 local_irq_save(flags);
74 asm volatile ("pushl %%ebp;"
75 "movl %7, %%ebp;"
76 "call *%6;"
77 "popl %%ebp"
78 : "=a" (regs->eax), "=b" (regs->ebx), "=c" (regs->ecx)
79 : "0" (regs->eax), "1" (regs->ebx), "2" (regs->ecx),
80 "m" (bios_entry_point), "m" (bios_data_map_base)
81 : "edx", "edi", "esi", "memory");
82 local_irq_restore(flags);
83 preempt_enable();
84}
85
86static size_t __init locate_wistron_bios(void __iomem *base)
87{
88 static const unsigned char __initdata signature[] =
89 { 0x42, 0x21, 0x55, 0x30 };
90 size_t offset;
91
92 for (offset = 0; offset < 0x10000; offset += 0x10) {
93 if (check_signature(base + offset, signature,
94 sizeof(signature)) != 0)
95 return offset;
96 }
97 return -1;
98}
99
100static int __init map_bios(void)
101{
102 void __iomem *base;
103 size_t offset;
104 u32 entry_point;
105
106 base = ioremap(0xF0000, 0x10000); /* Can't fail */
107 offset = locate_wistron_bios(base);
108 if (offset < 0) {
109 printk(KERN_ERR "wistron_btns: BIOS entry point not found\n");
110 iounmap(base);
111 return -ENODEV;
112 }
113
114 entry_point = readl(base + offset + 5);
115 printk(KERN_DEBUG
116 "wistron_btns: BIOS signature found at %p, entry point %08X\n",
117 base + offset, entry_point);
118
119 if (entry_point >= 0xF0000) {
120 bios_code_map_base = base;
121 bios_entry_point = bios_code_map_base + (entry_point & 0xFFFF);
122 } else {
123 iounmap(base);
124 bios_code_map_base = ioremap(entry_point & ~0x3FFF, 0x4000);
125 if (bios_code_map_base == NULL) {
126 printk(KERN_ERR
127 "wistron_btns: Can't map BIOS code at %08X\n",
128 entry_point & ~0x3FFF);
129 goto err;
130 }
131 bios_entry_point = bios_code_map_base + (entry_point & 0x3FFF);
132 }
133 /* The Windows driver maps 0x10000 bytes, we keep only one page... */
134 bios_data_map_base = ioremap(0x400, 0xc00);
135 if (bios_data_map_base == NULL) {
136 printk(KERN_ERR "wistron_btns: Can't map BIOS data\n");
137 goto err_code;
138 }
139 return 0;
140
141err_code:
142 iounmap(bios_code_map_base);
143err:
144 return -ENOMEM;
145}
146
147static void __exit unmap_bios(void)
148{
149 iounmap(bios_code_map_base);
150 iounmap(bios_data_map_base);
151}
152
153 /* BIOS calls */
154
155static u16 bios_pop_queue(void)
156{
157 struct regs regs;
158
159 memset(&regs, 0, sizeof (regs));
160 regs.eax = 0x9610;
161 regs.ebx = 0x061C;
162 regs.ecx = 0x0000;
163 call_bios(&regs);
164
165 return regs.eax;
166}
167
168static void __init bios_attach(void)
169{
170 struct regs regs;
171
172 memset(&regs, 0, sizeof (regs));
173 regs.eax = 0x9610;
174 regs.ebx = 0x012E;
175 call_bios(&regs);
176}
177
178static void __exit bios_detach(void)
179{
180 struct regs regs;
181
182 memset(&regs, 0, sizeof (regs));
183 regs.eax = 0x9610;
184 regs.ebx = 0x002E;
185 call_bios(&regs);
186}
187
188static u8 __init bios_get_cmos_address(void)
189{
190 struct regs regs;
191
192 memset(&regs, 0, sizeof (regs));
193 regs.eax = 0x9610;
194 regs.ebx = 0x051C;
195 call_bios(&regs);
196
197 return regs.ecx;
198}
199
200static u16 __init bios_wifi_get_default_setting(void)
201{
202 struct regs regs;
203
204 memset(&regs, 0, sizeof (regs));
205 regs.eax = 0x9610;
206 regs.ebx = 0x0235;
207 call_bios(&regs);
208
209 return regs.eax;
210}
211
212static void bios_wifi_set_state(int enable)
213{
214 struct regs regs;
215
216 memset(&regs, 0, sizeof (regs));
217 regs.eax = 0x9610;
218 regs.ebx = enable ? 0x0135 : 0x0035;
219 call_bios(&regs);
220}
221
222 /* Hardware database */
223
224struct key_entry {
225 char type; /* See KE_* below */
226 u8 code;
227 unsigned keycode; /* For KE_KEY */
228};
229
230enum { KE_END, KE_KEY, KE_WIFI };
231
232static const struct key_entry *keymap; /* = NULL; Current key map */
233static int have_wifi;
234
235static int __init dmi_matched(struct dmi_system_id *dmi)
236{
237 const struct key_entry *key;
238
239 keymap = dmi->driver_data;
240 for (key = keymap; key->type != KE_END; key++) {
241 if (key->type == KE_WIFI) {
242 have_wifi = 1;
243 break;
244 }
245 }
246 return 1;
247}
248
249static struct key_entry keymap_empty[] = {
250 { KE_END, 0 }
251};
252
253static struct key_entry keymap_fs_amilo_pro_v2000[] = {
254 { KE_KEY, 0x01, KEY_HELP },
255 { KE_KEY, 0x11, KEY_PROG1 },
256 { KE_KEY, 0x12, KEY_PROG2 },
257 { KE_WIFI, 0x30, 0 },
258 { KE_KEY, 0x31, KEY_MAIL },
259 { KE_KEY, 0x36, KEY_WWW },
260 { KE_END, 0 }
261};
262
263static struct key_entry keymap_wistron_ms2141[] = {
264 { KE_KEY, 0x11, KEY_PROG1 },
265 { KE_KEY, 0x12, KEY_PROG2 },
266 { KE_WIFI, 0x30, 0 },
267 { KE_KEY, 0x22, KEY_REWIND },
268 { KE_KEY, 0x23, KEY_FORWARD },
269 { KE_KEY, 0x24, KEY_PLAYPAUSE },
270 { KE_KEY, 0x25, KEY_STOPCD },
271 { KE_KEY, 0x31, KEY_MAIL },
272 { KE_KEY, 0x36, KEY_WWW },
273 { KE_END, 0 }
274};
275
276/*
277 * If your machine is not here (which is currently rather likely), please send
278 * a list of buttons and their key codes (reported when loading this module
279 * with force=1) and the output of dmidecode to $MODULE_AUTHOR.
280 */
281static struct dmi_system_id dmi_ids[] = {
282 {
283 .callback = dmi_matched,
284 .ident = "Fujitsu-Siemens Amilo Pro V2000",
285 .matches = {
286 DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
287 DMI_MATCH(DMI_PRODUCT_NAME, "AMILO Pro V2000"),
288 },
289 .driver_data = keymap_fs_amilo_pro_v2000
290 },
291 { 0, }
292};
293
294static int __init select_keymap(void)
295{
296 if (keymap_name != NULL) {
297 if (strcmp (keymap_name, "1557/MS2141") == 0)
298 keymap = keymap_wistron_ms2141;
299 else {
300 printk(KERN_ERR "wistron_btns: Keymap unknown\n");
301 return -EINVAL;
302 }
303 }
304 dmi_check_system(dmi_ids);
305 if (keymap == NULL) {
306 if (!force) {
307 printk(KERN_ERR "wistron_btns: System unknown\n");
308 return -ENODEV;
309 }
310 keymap = keymap_empty;
311 }
312 return 0;
313}
314
315 /* Input layer interface */
316
317static struct input_dev input_dev = {
318 .name = "Wistron laptop buttons",
319};
320
321static void __init setup_input_dev(void)
322{
323 const struct key_entry *key;
324
325 for (key = keymap; key->type != KE_END; key++) {
326 if (key->type == KE_KEY) {
327 input_dev.evbit[LONG(EV_KEY)] = BIT(EV_KEY);
328 input_dev.keybit[LONG(key->keycode)]
329 |= BIT(key->keycode);
330 }
331 }
332
333 input_register_device(&input_dev);
334}
335
336static void report_key(unsigned keycode)
337{
338 input_report_key(&input_dev, keycode, 1);
339 input_sync(&input_dev);
340 input_report_key(&input_dev, keycode, 0);
341 input_sync(&input_dev);
342}
343
344 /* Driver core */
345
346static int wifi_enabled;
347
348static void poll_bios(unsigned long);
349
350static struct timer_list poll_timer = TIMER_INITIALIZER(poll_bios, 0, 0);
351
352static void handle_key(u8 code)
353{
354 const struct key_entry *key;
355
356 for (key = keymap; key->type != KE_END; key++) {
357 if (code == key->code) {
358 switch (key->type) {
359 case KE_KEY:
360 report_key(key->keycode);
361 break;
362
363 case KE_WIFI:
364 if (have_wifi) {
365 wifi_enabled = !wifi_enabled;
366 bios_wifi_set_state(wifi_enabled);
367 }
368 break;
369
370 case KE_END:
371 default:
372 BUG();
373 }
374 return;
375 }
376 }
377 printk(KERN_NOTICE "wistron_btns: Unknown key code %02X\n", code);
378}
379
380static void poll_bios(unsigned long discard)
381{
382 u8 qlen;
383 u16 val;
384
385 for (;;) {
386 qlen = CMOS_READ(cmos_address);
387 if (qlen == 0)
388 break;
389 val = bios_pop_queue();
390 if (val != 0 && !discard)
391 handle_key((u8)val);
392 }
393
394 mod_timer(&poll_timer, jiffies + HZ / POLL_FREQUENCY);
395}
396
397static int __init wb_module_init(void)
398{
399 int err;
400
401 err = select_keymap();
402 if (err)
403 return err;
404 err = map_bios();
405 if (err)
406 return err;
407 bios_attach();
408 cmos_address = bios_get_cmos_address();
409 if (have_wifi) {
410 switch (bios_wifi_get_default_setting()) {
411 case 0x01:
412 wifi_enabled = 0;
413 break;
414
415 case 0x03:
416 wifi_enabled = 1;
417 break;
418
419 default:
420 have_wifi = 0;
421 break;
422 }
423 if (have_wifi)
424 bios_wifi_set_state(wifi_enabled);
425 }
426
427 setup_input_dev();
428
429 poll_bios(1); /* Flush stale event queue and arm timer */
430
431 return 0;
432}
433
434static void __exit wb_module_exit(void)
435{
436 del_timer_sync(&poll_timer);
437 input_unregister_device(&input_dev);
438 bios_detach();
439 unmap_bios();
440}
441
442module_init(wb_module_init);
443module_exit(wb_module_exit);