1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2006-2012 Robert Gerlach <khnz@gmx.de> |
4 | * Copyright (C) 2005-2006 Jan Rychter <jan@rychter.com> |
5 | */ |
6 | |
7 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
8 | |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | #include <linux/init.h> |
12 | #include <linux/bitops.h> |
13 | #include <linux/io.h> |
14 | #include <linux/ioport.h> |
15 | #include <linux/acpi.h> |
16 | #include <linux/device.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/input.h> |
19 | #include <linux/delay.h> |
20 | #include <linux/dmi.h> |
21 | |
22 | #define MODULENAME "fujitsu-tablet" |
23 | |
24 | #define ACPI_FUJITSU_CLASS "fujitsu" |
25 | |
26 | #define INVERT_TABLET_MODE_BIT 0x01 |
27 | #define INVERT_DOCK_STATE_BIT 0x02 |
28 | #define FORCE_TABLET_MODE_IF_UNDOCK 0x04 |
29 | |
30 | #define KEYMAP_LEN 16 |
31 | |
32 | static const struct acpi_device_id fujitsu_ids[] = { |
33 | { .id = "FUJ02BD" }, |
34 | { .id = "FUJ02BF" }, |
35 | { .id = "" } |
36 | }; |
37 | |
38 | struct fujitsu_config { |
39 | unsigned short keymap[KEYMAP_LEN]; |
40 | unsigned int quirks; |
41 | }; |
42 | |
43 | static unsigned short keymap_Lifebook_Tseries[KEYMAP_LEN] __initdata = { |
44 | KEY_RESERVED, |
45 | KEY_RESERVED, |
46 | KEY_RESERVED, |
47 | KEY_RESERVED, |
48 | KEY_SCROLLDOWN, |
49 | KEY_SCROLLUP, |
50 | KEY_ROTATE_DISPLAY, |
51 | KEY_LEFTCTRL, |
52 | KEY_BRIGHTNESSUP, |
53 | KEY_BRIGHTNESSDOWN, |
54 | KEY_BRIGHTNESS_ZERO, |
55 | KEY_RESERVED, |
56 | KEY_RESERVED, |
57 | KEY_RESERVED, |
58 | KEY_RESERVED, |
59 | KEY_LEFTALT |
60 | }; |
61 | |
62 | static unsigned short keymap_Lifebook_T901[KEYMAP_LEN] __initdata = { |
63 | KEY_RESERVED, |
64 | KEY_RESERVED, |
65 | KEY_RESERVED, |
66 | KEY_RESERVED, |
67 | KEY_SCROLLDOWN, |
68 | KEY_SCROLLUP, |
69 | KEY_CYCLEWINDOWS, |
70 | KEY_LEFTCTRL, |
71 | KEY_RESERVED, |
72 | KEY_RESERVED, |
73 | KEY_RESERVED, |
74 | KEY_RESERVED, |
75 | KEY_RESERVED, |
76 | KEY_RESERVED, |
77 | KEY_RESERVED, |
78 | KEY_LEFTMETA |
79 | }; |
80 | |
81 | static unsigned short keymap_Lifebook_T902[KEYMAP_LEN] __initdata = { |
82 | KEY_RESERVED, |
83 | KEY_VOLUMEDOWN, |
84 | KEY_VOLUMEUP, |
85 | KEY_CYCLEWINDOWS, |
86 | KEY_PROG1, |
87 | KEY_PROG2, |
88 | KEY_LEFTMETA, |
89 | KEY_RESERVED, |
90 | KEY_RESERVED, |
91 | KEY_RESERVED, |
92 | KEY_RESERVED, |
93 | KEY_RESERVED, |
94 | KEY_RESERVED, |
95 | KEY_RESERVED, |
96 | KEY_RESERVED, |
97 | KEY_RESERVED, |
98 | }; |
99 | |
100 | static unsigned short keymap_Lifebook_U810[KEYMAP_LEN] __initdata = { |
101 | KEY_RESERVED, |
102 | KEY_RESERVED, |
103 | KEY_RESERVED, |
104 | KEY_RESERVED, |
105 | KEY_PROG1, |
106 | KEY_PROG2, |
107 | KEY_ROTATE_DISPLAY, |
108 | KEY_RESERVED, |
109 | KEY_RESERVED, |
110 | KEY_RESERVED, |
111 | KEY_UP, |
112 | KEY_DOWN, |
113 | KEY_RESERVED, |
114 | KEY_RESERVED, |
115 | KEY_LEFTCTRL, |
116 | KEY_LEFTALT |
117 | }; |
118 | |
119 | static unsigned short keymap_Stylistic_Tseries[KEYMAP_LEN] __initdata = { |
120 | KEY_RESERVED, |
121 | KEY_RESERVED, |
122 | KEY_RESERVED, |
123 | KEY_RESERVED, |
124 | KEY_PRINT, |
125 | KEY_BACKSPACE, |
126 | KEY_SPACE, |
127 | KEY_ENTER, |
128 | KEY_BRIGHTNESSUP, |
129 | KEY_BRIGHTNESSDOWN, |
130 | KEY_DOWN, |
131 | KEY_UP, |
132 | KEY_SCROLLUP, |
133 | KEY_SCROLLDOWN, |
134 | KEY_LEFTCTRL, |
135 | KEY_LEFTALT |
136 | }; |
137 | |
138 | static unsigned short keymap_Stylistic_ST5xxx[KEYMAP_LEN] __initdata = { |
139 | KEY_RESERVED, |
140 | KEY_RESERVED, |
141 | KEY_RESERVED, |
142 | KEY_RESERVED, |
143 | KEY_MAIL, |
144 | KEY_ROTATE_DISPLAY, |
145 | KEY_ESC, |
146 | KEY_ENTER, |
147 | KEY_BRIGHTNESSUP, |
148 | KEY_BRIGHTNESSDOWN, |
149 | KEY_DOWN, |
150 | KEY_UP, |
151 | KEY_SCROLLUP, |
152 | KEY_SCROLLDOWN, |
153 | KEY_LEFTCTRL, |
154 | KEY_LEFTALT |
155 | }; |
156 | |
157 | static struct { |
158 | struct input_dev *idev; |
159 | struct fujitsu_config config; |
160 | unsigned long prev_keymask; |
161 | |
162 | char phys[21]; |
163 | |
164 | int irq; |
165 | int io_base; |
166 | int io_length; |
167 | } fujitsu; |
168 | |
169 | static u8 fujitsu_ack(void) |
170 | { |
171 | return inb(port: fujitsu.io_base + 2); |
172 | } |
173 | |
174 | static u8 fujitsu_status(void) |
175 | { |
176 | return inb(port: fujitsu.io_base + 6); |
177 | } |
178 | |
179 | static u8 fujitsu_read_register(const u8 addr) |
180 | { |
181 | outb(value: addr, port: fujitsu.io_base); |
182 | return inb(port: fujitsu.io_base + 4); |
183 | } |
184 | |
185 | static void fujitsu_send_state(void) |
186 | { |
187 | int state; |
188 | int dock, tablet_mode; |
189 | |
190 | state = fujitsu_read_register(addr: 0xdd); |
191 | |
192 | dock = state & 0x02; |
193 | if (fujitsu.config.quirks & INVERT_DOCK_STATE_BIT) |
194 | dock = !dock; |
195 | |
196 | if ((fujitsu.config.quirks & FORCE_TABLET_MODE_IF_UNDOCK) && (!dock)) { |
197 | tablet_mode = 1; |
198 | } else{ |
199 | tablet_mode = state & 0x01; |
200 | if (fujitsu.config.quirks & INVERT_TABLET_MODE_BIT) |
201 | tablet_mode = !tablet_mode; |
202 | } |
203 | |
204 | input_report_switch(dev: fujitsu.idev, SW_DOCK, value: dock); |
205 | input_report_switch(dev: fujitsu.idev, SW_TABLET_MODE, value: tablet_mode); |
206 | input_sync(dev: fujitsu.idev); |
207 | } |
208 | |
209 | static void fujitsu_reset(void) |
210 | { |
211 | int timeout = 50; |
212 | |
213 | fujitsu_ack(); |
214 | |
215 | while ((fujitsu_status() & 0x02) && (--timeout)) |
216 | msleep(msecs: 20); |
217 | |
218 | fujitsu_send_state(); |
219 | } |
220 | |
221 | static int input_fujitsu_setup(struct device *parent, const char *name, |
222 | const char *phys) |
223 | { |
224 | struct input_dev *idev; |
225 | int error; |
226 | int i; |
227 | |
228 | idev = input_allocate_device(); |
229 | if (!idev) |
230 | return -ENOMEM; |
231 | |
232 | idev->dev.parent = parent; |
233 | idev->phys = phys; |
234 | idev->name = name; |
235 | idev->id.bustype = BUS_HOST; |
236 | idev->id.vendor = 0x1734; /* Fujitsu Siemens Computer GmbH */ |
237 | idev->id.product = 0x0001; |
238 | idev->id.version = 0x0101; |
239 | |
240 | idev->keycode = fujitsu.config.keymap; |
241 | idev->keycodesize = sizeof(fujitsu.config.keymap[0]); |
242 | idev->keycodemax = ARRAY_SIZE(fujitsu.config.keymap); |
243 | |
244 | __set_bit(EV_REP, idev->evbit); |
245 | |
246 | for (i = 0; i < ARRAY_SIZE(fujitsu.config.keymap); i++) |
247 | if (fujitsu.config.keymap[i]) |
248 | input_set_capability(dev: idev, EV_KEY, code: fujitsu.config.keymap[i]); |
249 | |
250 | input_set_capability(dev: idev, EV_MSC, MSC_SCAN); |
251 | |
252 | input_set_capability(dev: idev, EV_SW, SW_DOCK); |
253 | input_set_capability(dev: idev, EV_SW, SW_TABLET_MODE); |
254 | |
255 | error = input_register_device(idev); |
256 | if (error) { |
257 | input_free_device(dev: idev); |
258 | return error; |
259 | } |
260 | |
261 | fujitsu.idev = idev; |
262 | return 0; |
263 | } |
264 | |
265 | static void input_fujitsu_remove(void) |
266 | { |
267 | input_unregister_device(fujitsu.idev); |
268 | } |
269 | |
270 | static irqreturn_t fujitsu_interrupt(int irq, void *dev_id) |
271 | { |
272 | unsigned long keymask, changed; |
273 | unsigned int keycode; |
274 | int pressed; |
275 | int i; |
276 | |
277 | if (unlikely(!(fujitsu_status() & 0x01))) |
278 | return IRQ_NONE; |
279 | |
280 | fujitsu_send_state(); |
281 | |
282 | keymask = fujitsu_read_register(addr: 0xde); |
283 | keymask |= fujitsu_read_register(addr: 0xdf) << 8; |
284 | keymask ^= 0xffff; |
285 | |
286 | changed = keymask ^ fujitsu.prev_keymask; |
287 | if (changed) { |
288 | fujitsu.prev_keymask = keymask; |
289 | |
290 | for_each_set_bit(i, &changed, KEYMAP_LEN) { |
291 | keycode = fujitsu.config.keymap[i]; |
292 | pressed = keymask & changed & BIT(i); |
293 | |
294 | if (pressed) |
295 | input_event(dev: fujitsu.idev, EV_MSC, MSC_SCAN, value: i); |
296 | |
297 | input_report_key(dev: fujitsu.idev, code: keycode, value: pressed); |
298 | input_sync(dev: fujitsu.idev); |
299 | } |
300 | } |
301 | |
302 | fujitsu_ack(); |
303 | return IRQ_HANDLED; |
304 | } |
305 | |
306 | static void __init fujitsu_dmi_common(const struct dmi_system_id *dmi) |
307 | { |
308 | pr_info("%s\n" , dmi->ident); |
309 | memcpy(fujitsu.config.keymap, dmi->driver_data, |
310 | sizeof(fujitsu.config.keymap)); |
311 | } |
312 | |
313 | static int __init fujitsu_dmi_lifebook(const struct dmi_system_id *dmi) |
314 | { |
315 | fujitsu_dmi_common(dmi); |
316 | fujitsu.config.quirks |= INVERT_TABLET_MODE_BIT; |
317 | return 1; |
318 | } |
319 | |
320 | static int __init fujitsu_dmi_stylistic(const struct dmi_system_id *dmi) |
321 | { |
322 | fujitsu_dmi_common(dmi); |
323 | fujitsu.config.quirks |= FORCE_TABLET_MODE_IF_UNDOCK; |
324 | fujitsu.config.quirks |= INVERT_DOCK_STATE_BIT; |
325 | return 1; |
326 | } |
327 | |
328 | static const struct dmi_system_id dmi_ids[] __initconst = { |
329 | { |
330 | .callback = fujitsu_dmi_lifebook, |
331 | .ident = "Fujitsu Lifebook T901" , |
332 | .matches = { |
333 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
334 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T901" ) |
335 | }, |
336 | .driver_data = keymap_Lifebook_T901 |
337 | }, |
338 | { |
339 | .callback = fujitsu_dmi_lifebook, |
340 | .ident = "Fujitsu Lifebook T901" , |
341 | .matches = { |
342 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
343 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T901" ) |
344 | }, |
345 | .driver_data = keymap_Lifebook_T901 |
346 | }, |
347 | { |
348 | .callback = fujitsu_dmi_lifebook, |
349 | .ident = "Fujitsu Lifebook T902" , |
350 | .matches = { |
351 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
352 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK T902" ) |
353 | }, |
354 | .driver_data = keymap_Lifebook_T902 |
355 | }, |
356 | { |
357 | .callback = fujitsu_dmi_lifebook, |
358 | .ident = "Fujitsu Siemens P/T Series" , |
359 | .matches = { |
360 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
361 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK" ) |
362 | }, |
363 | .driver_data = keymap_Lifebook_Tseries |
364 | }, |
365 | { |
366 | .callback = fujitsu_dmi_lifebook, |
367 | .ident = "Fujitsu Lifebook T Series" , |
368 | .matches = { |
369 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
370 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook T" ) |
371 | }, |
372 | .driver_data = keymap_Lifebook_Tseries |
373 | }, |
374 | { |
375 | .callback = fujitsu_dmi_stylistic, |
376 | .ident = "Fujitsu Siemens Stylistic T Series" , |
377 | .matches = { |
378 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
379 | DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic T" ) |
380 | }, |
381 | .driver_data = keymap_Stylistic_Tseries |
382 | }, |
383 | { |
384 | .callback = fujitsu_dmi_lifebook, |
385 | .ident = "Fujitsu LifeBook U810" , |
386 | .matches = { |
387 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
388 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook U810" ) |
389 | }, |
390 | .driver_data = keymap_Lifebook_U810 |
391 | }, |
392 | { |
393 | .callback = fujitsu_dmi_stylistic, |
394 | .ident = "Fujitsu Siemens Stylistic ST5xxx Series" , |
395 | .matches = { |
396 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
397 | DMI_MATCH(DMI_PRODUCT_NAME, "STYLISTIC ST5" ) |
398 | }, |
399 | .driver_data = keymap_Stylistic_ST5xxx |
400 | }, |
401 | { |
402 | .callback = fujitsu_dmi_stylistic, |
403 | .ident = "Fujitsu Siemens Stylistic ST5xxx Series" , |
404 | .matches = { |
405 | DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU" ), |
406 | DMI_MATCH(DMI_PRODUCT_NAME, "Stylistic ST5" ) |
407 | }, |
408 | .driver_data = keymap_Stylistic_ST5xxx |
409 | }, |
410 | { |
411 | .callback = fujitsu_dmi_lifebook, |
412 | .ident = "Unknown (using defaults)" , |
413 | .matches = { |
414 | DMI_MATCH(DMI_SYS_VENDOR, "" ), |
415 | DMI_MATCH(DMI_PRODUCT_NAME, "" ) |
416 | }, |
417 | .driver_data = keymap_Lifebook_Tseries |
418 | }, |
419 | { NULL } |
420 | }; |
421 | |
422 | static acpi_status fujitsu_walk_resources(struct acpi_resource *res, void *data) |
423 | { |
424 | switch (res->type) { |
425 | case ACPI_RESOURCE_TYPE_IRQ: |
426 | fujitsu.irq = res->data.irq.interrupts[0]; |
427 | return AE_OK; |
428 | |
429 | case ACPI_RESOURCE_TYPE_IO: |
430 | fujitsu.io_base = res->data.io.minimum; |
431 | fujitsu.io_length = res->data.io.address_length; |
432 | return AE_OK; |
433 | |
434 | case ACPI_RESOURCE_TYPE_END_TAG: |
435 | if (fujitsu.irq && fujitsu.io_base) |
436 | return AE_OK; |
437 | else |
438 | return AE_NOT_FOUND; |
439 | |
440 | default: |
441 | return AE_ERROR; |
442 | } |
443 | } |
444 | |
445 | static int acpi_fujitsu_add(struct acpi_device *adev) |
446 | { |
447 | acpi_status status; |
448 | int error; |
449 | |
450 | if (!adev) |
451 | return -EINVAL; |
452 | |
453 | status = acpi_walk_resources(device: adev->handle, METHOD_NAME__CRS, |
454 | user_function: fujitsu_walk_resources, NULL); |
455 | if (ACPI_FAILURE(status) || !fujitsu.irq || !fujitsu.io_base) |
456 | return -ENODEV; |
457 | |
458 | sprintf(acpi_device_name(adev), fmt: "Fujitsu %s" , acpi_device_hid(device: adev)); |
459 | sprintf(acpi_device_class(adev), fmt: "%s" , ACPI_FUJITSU_CLASS); |
460 | |
461 | snprintf(buf: fujitsu.phys, size: sizeof(fujitsu.phys), |
462 | fmt: "%s/input0" , acpi_device_hid(device: adev)); |
463 | |
464 | error = input_fujitsu_setup(parent: &adev->dev, |
465 | acpi_device_name(adev), phys: fujitsu.phys); |
466 | if (error) |
467 | return error; |
468 | |
469 | if (!request_region(fujitsu.io_base, fujitsu.io_length, MODULENAME)) { |
470 | input_fujitsu_remove(); |
471 | return -EBUSY; |
472 | } |
473 | |
474 | fujitsu_reset(); |
475 | |
476 | error = request_irq(irq: fujitsu.irq, handler: fujitsu_interrupt, |
477 | IRQF_SHARED, MODULENAME, dev: fujitsu_interrupt); |
478 | if (error) { |
479 | release_region(fujitsu.io_base, fujitsu.io_length); |
480 | input_fujitsu_remove(); |
481 | return error; |
482 | } |
483 | |
484 | return 0; |
485 | } |
486 | |
487 | static void acpi_fujitsu_remove(struct acpi_device *adev) |
488 | { |
489 | free_irq(fujitsu.irq, fujitsu_interrupt); |
490 | release_region(fujitsu.io_base, fujitsu.io_length); |
491 | input_fujitsu_remove(); |
492 | } |
493 | |
494 | #ifdef CONFIG_PM_SLEEP |
495 | static int acpi_fujitsu_resume(struct device *dev) |
496 | { |
497 | fujitsu_reset(); |
498 | return 0; |
499 | } |
500 | #endif |
501 | |
502 | static SIMPLE_DEV_PM_OPS(acpi_fujitsu_pm, NULL, acpi_fujitsu_resume); |
503 | |
504 | static struct acpi_driver acpi_fujitsu_driver = { |
505 | .name = MODULENAME, |
506 | .class = "hotkey" , |
507 | .ids = fujitsu_ids, |
508 | .ops = { |
509 | .add = acpi_fujitsu_add, |
510 | .remove = acpi_fujitsu_remove, |
511 | }, |
512 | .drv.pm = &acpi_fujitsu_pm, |
513 | }; |
514 | |
515 | static int __init fujitsu_module_init(void) |
516 | { |
517 | int error; |
518 | |
519 | dmi_check_system(list: dmi_ids); |
520 | |
521 | error = acpi_bus_register_driver(driver: &acpi_fujitsu_driver); |
522 | if (error) |
523 | return error; |
524 | |
525 | return 0; |
526 | } |
527 | |
528 | static void __exit fujitsu_module_exit(void) |
529 | { |
530 | acpi_bus_unregister_driver(driver: &acpi_fujitsu_driver); |
531 | } |
532 | |
533 | module_init(fujitsu_module_init); |
534 | module_exit(fujitsu_module_exit); |
535 | |
536 | MODULE_AUTHOR("Robert Gerlach <khnz@gmx.de>" ); |
537 | MODULE_DESCRIPTION("Fujitsu tablet pc extras driver" ); |
538 | MODULE_LICENSE("GPL" ); |
539 | MODULE_VERSION("2.5" ); |
540 | |
541 | MODULE_DEVICE_TABLE(acpi, fujitsu_ids); |
542 | |