aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorStephen Hemminger <shemminger@linux-foundation.org>2007-12-14 11:08:37 -0500
committerDmitry Torokhov <dmitry.torokhov@gmail.com>2008-01-21 01:11:07 -0500
commit52fe0cdb090a344cad9d95461ad06239e0c28712 (patch)
tree27c2831dae2ec6d595e24644d3f3552e83c6fe43 /drivers
parentfbb38e30e414c9ccd8b5d04344264522551008bc (diff)
Input: add driver for Fujitsu application buttons
This driver supports the application buttons on some Fujitsu Lifebook laptops. It is based on the earlier apanel driver done by Jochen Eisenger, but with many changes. The original driver used ioctl's and a separate user space program (see http://apanel.sourceforge.net). This driver hooks into the input subsystem so that the normal keys act as expected without a daemon. In addition to buttons, the Mail Led is handled via LEDs class device. The driver now supports redefinable keymaps and no longer has to have a DMI table for all Fujitsu laptops. I thought about mixing this driver should be integrated into the Fujitsu laptop extras driver that handles backlight, but rejected the idea because it wasn't clear if all the Fujitsu laptops supported both. Signed-off-by: Stephen Hemminger <shemminger@linux-foundation.org> Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/input/misc/Kconfig14
-rw-r--r--drivers/input/misc/Makefile1
-rw-r--r--drivers/input/misc/apanel.c378
3 files changed, 393 insertions, 0 deletions
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 8f5c7b90187d..8b10d9f23bef 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -40,6 +40,20 @@ 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_APANEL
44 tristate "Fujitsu Lifebook Application Panel buttons"
45 depends on X86
46 select I2C_I801
47 select INPUT_POLLDEV
48 select CHECK_SIGNATURE
49 help
50 Say Y here for support of the Application Panel buttons, used on
51 Fujitsu Lifebook. These are attached to the mainboard through
52 an SMBus interface managed by the I2C Intel ICH (i801) driver.
53
54 To compile this driver as a module, choose M here: the module will
55 be called apanel.
56
43config INPUT_IXP4XX_BEEPER 57config INPUT_IXP4XX_BEEPER
44 tristate "IXP4XX Beeper support" 58 tristate "IXP4XX Beeper support"
45 depends on ARCH_IXP4XX 59 depends on ARCH_IXP4XX
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 3585b5038418..ebd39f291d25 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -18,3 +18,4 @@ obj-$(CONFIG_INPUT_POWERMATE) += powermate.o
18obj-$(CONFIG_INPUT_YEALINK) += yealink.o 18obj-$(CONFIG_INPUT_YEALINK) += yealink.o
19obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o 19obj-$(CONFIG_HP_SDC_RTC) += hp_sdc_rtc.o
20obj-$(CONFIG_INPUT_UINPUT) += uinput.o 20obj-$(CONFIG_INPUT_UINPUT) += uinput.o
21obj-$(CONFIG_INPUT_APANEL) += apanel.o
diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c
new file mode 100644
index 000000000000..9531d8c7444f
--- /dev/null
+++ b/drivers/input/misc/apanel.c
@@ -0,0 +1,378 @@
1/*
2 * Fujitsu Lifebook Application Panel button drive
3 *
4 * Copyright (C) 2007 Stephen Hemminger <shemminger@linux-foundation.org>
5 * Copyright (C) 2001-2003 Jochen Eisinger <jochen@penguin-breeder.org>
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License version 2 as published by
9 * the Free Software Foundation.
10 *
11 * Many Fujitsu Lifebook laptops have a small panel of buttons that are
12 * accessible via the i2c/smbus interface. This driver polls those
13 * buttons and generates input events.
14 *
15 * For more details see:
16 * http://apanel.sourceforge.net/tech.php
17 */
18
19#include <linux/kernel.h>
20#include <linux/module.h>
21#include <linux/ioport.h>
22#include <linux/io.h>
23#include <linux/module.h>
24#include <linux/input-polldev.h>
25#include <linux/i2c.h>
26#include <linux/workqueue.h>
27#include <linux/leds.h>
28
29#define APANEL_NAME "Fujitsu Application Panel"
30#define APANEL_VERSION "1.3.1"
31#define APANEL "apanel"
32
33/* How often we poll keys - msecs */
34#define POLL_INTERVAL_DEFAULT 1000
35
36/* Magic constants in BIOS that tell about buttons */
37enum apanel_devid {
38 APANEL_DEV_NONE = 0,
39 APANEL_DEV_APPBTN = 1,
40 APANEL_DEV_CDBTN = 2,
41 APANEL_DEV_LCD = 3,
42 APANEL_DEV_LED = 4,
43
44 APANEL_DEV_MAX,
45};
46
47enum apanel_chip {
48 CHIP_NONE = 0,
49 CHIP_OZ992C = 1,
50 CHIP_OZ163T = 2,
51 CHIP_OZ711M3 = 4,
52};
53
54/* Result of BIOS snooping/probing -- what features are supported */
55static enum apanel_chip device_chip[APANEL_DEV_MAX];
56
57#define MAX_PANEL_KEYS 12
58
59struct apanel {
60 struct input_polled_dev *ipdev;
61 struct i2c_client client;
62 unsigned short keymap[MAX_PANEL_KEYS];
63 u16 nkeys;
64 u16 led_bits;
65 struct work_struct led_work;
66 struct led_classdev mail_led;
67};
68
69
70static int apanel_probe(struct i2c_adapter *, int, int);
71
72/* for now, we only support one address */
73static unsigned short normal_i2c[] = {0, I2C_CLIENT_END};
74static unsigned short ignore = I2C_CLIENT_END;
75static struct i2c_client_address_data addr_data = {
76 .normal_i2c = normal_i2c,
77 .probe = &ignore,
78 .ignore = &ignore,
79};
80
81static void report_key(struct input_dev *input, unsigned keycode)
82{
83 pr_debug(APANEL ": report key %#x\n", keycode);
84 input_report_key(input, keycode, 1);
85 input_sync(input);
86
87 input_report_key(input, keycode, 0);
88 input_sync(input);
89}
90
91/* Poll for key changes
92 *
93 * Read Application keys via SMI
94 * A (0x4), B (0x8), Internet (0x2), Email (0x1).
95 *
96 * CD keys:
97 * Forward (0x100), Rewind (0x200), Stop (0x400), Pause (0x800)
98 */
99static void apanel_poll(struct input_polled_dev *ipdev)
100{
101 struct apanel *ap = ipdev->private;
102 struct input_dev *idev = ipdev->input;
103 u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8;
104 s32 data;
105 int i;
106
107 data = i2c_smbus_read_word_data(&ap->client, cmd);
108 if (data < 0)
109 return; /* ignore errors (due to ACPI??) */
110
111 /* write back to clear latch */
112 i2c_smbus_write_word_data(&ap->client, cmd, 0);
113
114 if (!data)
115 return;
116
117 dev_dbg(&idev->dev, APANEL ": data %#x\n", data);
118 for (i = 0; i < idev->keycodemax; i++)
119 if ((1u << i) & data)
120 report_key(idev, ap->keymap[i]);
121}
122
123/* Track state changes of LED */
124static void led_update(struct work_struct *work)
125{
126 struct apanel *ap = container_of(work, struct apanel, led_work);
127
128 i2c_smbus_write_word_data(&ap->client, 0x10, ap->led_bits);
129}
130
131static void mail_led_set(struct led_classdev *led,
132 enum led_brightness value)
133{
134 struct apanel *ap = container_of(led, struct apanel, mail_led);
135
136 if (value != LED_OFF)
137 ap->led_bits |= 0x8000;
138 else
139 ap->led_bits &= ~0x8000;
140
141 schedule_work(&ap->led_work);
142}
143
144static int apanel_detach_client(struct i2c_client *client)
145{
146 struct apanel *ap = i2c_get_clientdata(client);
147
148 if (device_chip[APANEL_DEV_LED] != CHIP_NONE)
149 led_classdev_unregister(&ap->mail_led);
150
151 input_unregister_polled_device(ap->ipdev);
152 i2c_detach_client(&ap->client);
153 input_free_polled_device(ap->ipdev);
154
155 return 0;
156}
157
158/* Function is invoked for every i2c adapter. */
159static int apanel_attach_adapter(struct i2c_adapter *adap)
160{
161 dev_dbg(&adap->dev, APANEL ": attach adapter id=%d\n", adap->id);
162
163 /* Our device is connected only to i801 on laptop */
164 if (adap->id != I2C_HW_SMBUS_I801)
165 return -ENODEV;
166
167 return i2c_probe(adap, &addr_data, apanel_probe);
168}
169
170static void apanel_shutdown(struct i2c_client *client)
171{
172 apanel_detach_client(client);
173}
174
175static struct i2c_driver apanel_driver = {
176 .driver = {
177 .name = APANEL,
178 },
179 .attach_adapter = &apanel_attach_adapter,
180 .detach_client = &apanel_detach_client,
181 .shutdown = &apanel_shutdown,
182};
183
184static struct apanel apanel = {
185 .client = {
186 .driver = &apanel_driver,
187 .name = APANEL,
188 },
189 .keymap = {
190 [0] = KEY_MAIL,
191 [1] = KEY_WWW,
192 [2] = KEY_PROG2,
193 [3] = KEY_PROG1,
194
195 [8] = KEY_FORWARD,
196 [9] = KEY_REWIND,
197 [10] = KEY_STOPCD,
198 [11] = KEY_PLAYPAUSE,
199
200 },
201 .mail_led = {
202 .name = "mail:blue",
203 .brightness_set = mail_led_set,
204 },
205};
206
207/* NB: Only one panel on the i2c. */
208static int apanel_probe(struct i2c_adapter *bus, int address, int kind)
209{
210 struct apanel *ap;
211 struct input_polled_dev *ipdev;
212 struct input_dev *idev;
213 u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8;
214 int i, err = -ENOMEM;
215
216 dev_dbg(&bus->dev, APANEL ": probe adapter %p addr %d kind %d\n",
217 bus, address, kind);
218
219 ap = &apanel;
220
221 ipdev = input_allocate_polled_device();
222 if (!ipdev)
223 goto out1;
224
225 ap->ipdev = ipdev;
226 ap->client.adapter = bus;
227 ap->client.addr = address;
228
229 i2c_set_clientdata(&ap->client, ap);
230
231 err = i2c_attach_client(&ap->client);
232 if (err)
233 goto out2;
234
235 err = i2c_smbus_write_word_data(&ap->client, cmd, 0);
236 if (err) {
237 dev_warn(&ap->client.dev, APANEL ": smbus write error %d\n",
238 err);
239 goto out3;
240 }
241
242 ipdev->poll = apanel_poll;
243 ipdev->poll_interval = POLL_INTERVAL_DEFAULT;
244 ipdev->private = ap;
245
246 idev = ipdev->input;
247 idev->name = APANEL_NAME " buttons";
248 idev->phys = "apanel/input0";
249 idev->id.bustype = BUS_HOST;
250 idev->dev.parent = &ap->client.dev;
251
252 set_bit(EV_KEY, idev->evbit);
253
254 idev->keycode = ap->keymap;
255 idev->keycodesize = sizeof(ap->keymap[0]);
256 idev->keycodemax = (device_chip[APANEL_DEV_CDBTN] != CHIP_NONE) ? 12 : 4;
257
258 for (i = 0; i < idev->keycodemax; i++)
259 if (ap->keymap[i])
260 set_bit(ap->keymap[i], idev->keybit);
261
262 err = input_register_polled_device(ipdev);
263 if (err)
264 goto out3;
265
266 INIT_WORK(&ap->led_work, led_update);
267 if (device_chip[APANEL_DEV_LED] != CHIP_NONE) {
268 err = led_classdev_register(&ap->client.dev, &ap->mail_led);
269 if (err)
270 goto out4;
271 }
272
273 return 0;
274out4:
275 input_unregister_polled_device(ipdev);
276out3:
277 i2c_detach_client(&ap->client);
278out2:
279 input_free_polled_device(ipdev);
280out1:
281 return err;
282}
283
284/* Scan the system ROM for the signature "FJKEYINF" */
285static __init const void __iomem *bios_signature(const void __iomem *bios)
286{
287 ssize_t offset;
288 const unsigned char signature[] = "FJKEYINF";
289
290 for (offset = 0; offset < 0x10000; offset += 0x10) {
291 if (check_signature(bios + offset, signature,
292 sizeof(signature)-1))
293 return bios + offset;
294 }
295 pr_notice(APANEL ": Fujitsu BIOS signature '%s' not found...\n",
296 signature);
297 return NULL;
298}
299
300static int __init apanel_init(void)
301{
302 void __iomem *bios;
303 const void __iomem *p;
304 u8 devno;
305 int found = 0;
306
307 bios = ioremap(0xF0000, 0x10000); /* Can't fail */
308
309 p = bios_signature(bios);
310 if (!p) {
311 iounmap(bios);
312 return -ENODEV;
313 }
314
315 /* just use the first address */
316 p += 8;
317 normal_i2c[0] = readb(p+3) >> 1;
318
319 for ( ; (devno = readb(p)) & 0x7f; p += 4) {
320 unsigned char method, slave, chip;
321
322 method = readb(p + 1);
323 chip = readb(p + 2);
324 slave = readb(p + 3) >> 1;
325
326 if (slave != normal_i2c[0]) {
327 pr_notice(APANEL ": only one SMBus slave "
328 "address supported, skiping device...\n");
329 continue;
330 }
331
332 /* translate alternative device numbers */
333 switch (devno) {
334 case 6:
335 devno = APANEL_DEV_APPBTN;
336 break;
337 case 7:
338 devno = APANEL_DEV_LED;
339 break;
340 }
341
342 if (devno >= APANEL_DEV_MAX)
343 pr_notice(APANEL ": unknown device %u found\n", devno);
344 else if (device_chip[devno] != CHIP_NONE)
345 pr_warning(APANEL ": duplicate entry for devno %u\n", devno);
346
347 else if (method != 1 && method != 2 && method != 4) {
348 pr_notice(APANEL ": unknown method %u for devno %u\n",
349 method, devno);
350 } else {
351 device_chip[devno] = (enum apanel_chip) chip;
352 ++found;
353 }
354 }
355 iounmap(bios);
356
357 if (found == 0) {
358 pr_info(APANEL ": no input devices reported by BIOS\n");
359 return -EIO;
360 }
361
362 return i2c_add_driver(&apanel_driver);
363}
364module_init(apanel_init);
365
366static void __exit apanel_cleanup(void)
367{
368 i2c_del_driver(&apanel_driver);
369}
370module_exit(apanel_cleanup);
371
372MODULE_AUTHOR("Stephen Hemminger <shemminger@linux-foundation.org>");
373MODULE_DESCRIPTION(APANEL_NAME " driver");
374MODULE_LICENSE("GPL");
375MODULE_VERSION(APANEL_VERSION);
376
377MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifeBook*:pvr*:rvnFUJITSU:*");
378MODULE_ALIAS("dmi:*:svnFUJITSU:pnLifebook*:pvr*:rvnFUJITSU:*");