diff options
-rw-r--r-- | MAINTAINERS | 7 | ||||
-rw-r--r-- | drivers/input/misc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/input/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/input/misc/ideapad_slidebar.c | 358 | ||||
-rw-r--r-- | drivers/input/serio/i8042.h | 24 | ||||
-rw-r--r-- | include/linux/i8042.h | 24 |
6 files changed, 400 insertions, 24 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index 250dc970c62d..bd1c48ebce69 100644 --- a/MAINTAINERS +++ b/MAINTAINERS | |||
@@ -4038,6 +4038,13 @@ W: http://launchpad.net/ideapad-laptop | |||
4038 | S: Maintained | 4038 | S: Maintained |
4039 | F: drivers/platform/x86/ideapad-laptop.c | 4039 | F: drivers/platform/x86/ideapad-laptop.c |
4040 | 4040 | ||
4041 | IDEAPAD LAPTOP SLIDEBAR DRIVER | ||
4042 | M: Andrey Moiseev <o2g.org.ru@gmail.com> | ||
4043 | L: linux-input@vger.kernel.org | ||
4044 | W: https://github.com/o2genum/ideapad-slidebar | ||
4045 | S: Maintained | ||
4046 | F: drivers/input/misc/ideapad_slidebar.c | ||
4047 | |||
4041 | IDE/ATAPI DRIVERS | 4048 | IDE/ATAPI DRIVERS |
4042 | M: Borislav Petkov <bp@alien8.de> | 4049 | M: Borislav Petkov <bp@alien8.de> |
4043 | L: linux-ide@vger.kernel.org | 4050 | L: linux-ide@vger.kernel.org |
diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig index 0b541cdf9b8e..aa51baaa9b1e 100644 --- a/drivers/input/misc/Kconfig +++ b/drivers/input/misc/Kconfig | |||
@@ -647,4 +647,14 @@ config INPUT_SIRFSOC_ONKEY | |||
647 | 647 | ||
648 | If unsure, say N. | 648 | If unsure, say N. |
649 | 649 | ||
650 | config INPUT_IDEAPAD_SLIDEBAR | ||
651 | tristate "IdeaPad Laptop Slidebar" | ||
652 | depends on INPUT | ||
653 | depends on SERIO_I8042 | ||
654 | help | ||
655 | Say Y here if you have an IdeaPad laptop with a slidebar. | ||
656 | |||
657 | To compile this driver as a module, choose M here: the | ||
658 | module will be called ideapad_slidebar. | ||
659 | |||
650 | endif | 660 | endif |
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile index 829de43a2427..0ebfb6dbf0f7 100644 --- a/drivers/input/misc/Makefile +++ b/drivers/input/misc/Makefile | |||
@@ -61,3 +61,4 @@ obj-$(CONFIG_INPUT_WISTRON_BTNS) += wistron_btns.o | |||
61 | obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o | 61 | obj-$(CONFIG_INPUT_WM831X_ON) += wm831x-on.o |
62 | obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o | 62 | obj-$(CONFIG_INPUT_XEN_KBDDEV_FRONTEND) += xen-kbdfront.o |
63 | obj-$(CONFIG_INPUT_YEALINK) += yealink.o | 63 | obj-$(CONFIG_INPUT_YEALINK) += yealink.o |
64 | obj-$(CONFIG_INPUT_IDEAPAD_SLIDEBAR) += ideapad_slidebar.o | ||
diff --git a/drivers/input/misc/ideapad_slidebar.c b/drivers/input/misc/ideapad_slidebar.c new file mode 100644 index 000000000000..edfd6239f131 --- /dev/null +++ b/drivers/input/misc/ideapad_slidebar.c | |||
@@ -0,0 +1,358 @@ | |||
1 | /* | ||
2 | * Input driver for slidebars on some Lenovo IdeaPad laptops | ||
3 | * | ||
4 | * Copyright (C) 2013 Andrey Moiseev <o2g.org.ru@gmail.com> | ||
5 | * | ||
6 | * Reverse-engineered from Lenovo SlideNav software (SBarHook.dll). | ||
7 | * | ||
8 | * This program is free software; you can redistribute it and/or modify it | ||
9 | * under the terms of the GNU General Public License as published by the Free | ||
10 | * Software Foundation; either version 2 of the License, or (at your option) | ||
11 | * any later version. | ||
12 | * | ||
13 | * Trademarks are the property of their respective owners. | ||
14 | */ | ||
15 | |||
16 | /* | ||
17 | * Currently tested and works on: | ||
18 | * Lenovo IdeaPad Y550 | ||
19 | * Lenovo IdeaPad Y550P | ||
20 | * | ||
21 | * Other models can be added easily. To test, | ||
22 | * load with 'force' parameter set 'true'. | ||
23 | * | ||
24 | * LEDs blinking and input mode are managed via sysfs, | ||
25 | * (hex, unsigned byte value): | ||
26 | * /sys/devices/platform/ideapad_slidebar/slidebar_mode | ||
27 | * | ||
28 | * The value is in byte range, however, I only figured out | ||
29 | * how bits 0b10011001 work. Some other bits, probably, | ||
30 | * are meaningfull too. | ||
31 | * | ||
32 | * Possible states: | ||
33 | * | ||
34 | * STD_INT, ONMOV_INT, OFF_INT, LAST_POLL, OFF_POLL | ||
35 | * | ||
36 | * Meaning: | ||
37 | * released touched | ||
38 | * STD 'heartbeat' lights follow the finger | ||
39 | * ONMOV no lights lights follow the finger | ||
40 | * LAST at last pos lights follow the finger | ||
41 | * OFF no lights no lights | ||
42 | * | ||
43 | * INT all input events are generated, interrupts are used | ||
44 | * POLL no input events by default, to get them, | ||
45 | * send 0b10000000 (read below) | ||
46 | * | ||
47 | * Commands: write | ||
48 | * | ||
49 | * All | 0b01001 -> STD_INT | ||
50 | * possible | 0b10001 -> ONMOV_INT | ||
51 | * states | 0b01000 -> OFF_INT | ||
52 | * | ||
53 | * | 0b0 -> LAST_POLL | ||
54 | * STD_INT or ONMOV_INT | | ||
55 | * | 0b1 -> STD_INT | ||
56 | * | ||
57 | * | 0b0 -> OFF_POLL | ||
58 | * OFF_INT or OFF_POLL | | ||
59 | * | 0b1 -> OFF_INT | ||
60 | * | ||
61 | * Any state | 0b10000000 -> if the slidebar has updated data, | ||
62 | * produce one input event (last position), | ||
63 | * switch to respective POLL mode | ||
64 | * (like 0x0), if not in POLL mode yet. | ||
65 | * | ||
66 | * Get current state: read | ||
67 | * | ||
68 | * masked by 0x11 read value means: | ||
69 | * | ||
70 | * 0x00 LAST | ||
71 | * 0x01 STD | ||
72 | * 0x10 OFF | ||
73 | * 0x11 ONMOV | ||
74 | */ | ||
75 | |||
76 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
77 | |||
78 | #include <linux/module.h> | ||
79 | #include <linux/kernel.h> | ||
80 | #include <linux/dmi.h> | ||
81 | #include <linux/spinlock.h> | ||
82 | #include <linux/platform_device.h> | ||
83 | #include <linux/input.h> | ||
84 | #include <linux/io.h> | ||
85 | #include <linux/ioport.h> | ||
86 | #include <linux/i8042.h> | ||
87 | #include <linux/serio.h> | ||
88 | |||
89 | #define IDEAPAD_BASE 0xff29 | ||
90 | |||
91 | static bool force; | ||
92 | module_param(force, bool, 0); | ||
93 | MODULE_PARM_DESC(force, "Force driver load, ignore DMI data"); | ||
94 | |||
95 | static DEFINE_SPINLOCK(io_lock); | ||
96 | |||
97 | static struct input_dev *slidebar_input_dev; | ||
98 | static struct platform_device *slidebar_platform_dev; | ||
99 | |||
100 | static u8 slidebar_pos_get(void) | ||
101 | { | ||
102 | u8 res; | ||
103 | unsigned long flags; | ||
104 | |||
105 | spin_lock_irqsave(&io_lock, flags); | ||
106 | outb(0xf4, 0xff29); | ||
107 | outb(0xbf, 0xff2a); | ||
108 | res = inb(0xff2b); | ||
109 | spin_unlock_irqrestore(&io_lock, flags); | ||
110 | |||
111 | return res; | ||
112 | } | ||
113 | |||
114 | static u8 slidebar_mode_get(void) | ||
115 | { | ||
116 | u8 res; | ||
117 | unsigned long flags; | ||
118 | |||
119 | spin_lock_irqsave(&io_lock, flags); | ||
120 | outb(0xf7, 0xff29); | ||
121 | outb(0x8b, 0xff2a); | ||
122 | res = inb(0xff2b); | ||
123 | spin_unlock_irqrestore(&io_lock, flags); | ||
124 | |||
125 | return res; | ||
126 | } | ||
127 | |||
128 | static void slidebar_mode_set(u8 mode) | ||
129 | { | ||
130 | unsigned long flags; | ||
131 | |||
132 | spin_lock_irqsave(&io_lock, flags); | ||
133 | outb(0xf7, 0xff29); | ||
134 | outb(0x8b, 0xff2a); | ||
135 | outb(mode, 0xff2b); | ||
136 | spin_unlock_irqrestore(&io_lock, flags); | ||
137 | } | ||
138 | |||
139 | static bool slidebar_i8042_filter(unsigned char data, unsigned char str, | ||
140 | struct serio *port) | ||
141 | { | ||
142 | static bool extended = false; | ||
143 | |||
144 | /* We are only interested in data coming form KBC port */ | ||
145 | if (str & I8042_STR_AUXDATA) | ||
146 | return false; | ||
147 | |||
148 | /* Scancodes: e03b on move, e0bb on release. */ | ||
149 | if (data == 0xe0) { | ||
150 | extended = true; | ||
151 | return true; | ||
152 | } | ||
153 | |||
154 | if (!extended) | ||
155 | return false; | ||
156 | |||
157 | extended = false; | ||
158 | |||
159 | if (likely((data & 0x7f) != 0x3b)) { | ||
160 | serio_interrupt(port, 0xe0, 0); | ||
161 | return false; | ||
162 | } | ||
163 | |||
164 | if (data & 0x80) { | ||
165 | input_report_key(slidebar_input_dev, BTN_TOUCH, 0); | ||
166 | } else { | ||
167 | input_report_key(slidebar_input_dev, BTN_TOUCH, 1); | ||
168 | input_report_abs(slidebar_input_dev, ABS_X, slidebar_pos_get()); | ||
169 | } | ||
170 | input_sync(slidebar_input_dev); | ||
171 | |||
172 | return true; | ||
173 | } | ||
174 | |||
175 | static ssize_t show_slidebar_mode(struct device *dev, | ||
176 | struct device_attribute *attr, | ||
177 | char *buf) | ||
178 | { | ||
179 | return sprintf(buf, "%x\n", slidebar_mode_get()); | ||
180 | } | ||
181 | |||
182 | static ssize_t store_slidebar_mode(struct device *dev, | ||
183 | struct device_attribute *attr, | ||
184 | const char *buf, size_t count) | ||
185 | { | ||
186 | u8 mode; | ||
187 | int error; | ||
188 | |||
189 | error = kstrtou8(buf, 0, &mode); | ||
190 | if (error) | ||
191 | return error; | ||
192 | |||
193 | slidebar_mode_set(mode); | ||
194 | |||
195 | return count; | ||
196 | } | ||
197 | |||
198 | static DEVICE_ATTR(slidebar_mode, S_IWUSR | S_IRUGO, | ||
199 | show_slidebar_mode, store_slidebar_mode); | ||
200 | |||
201 | static struct attribute *ideapad_attrs[] = { | ||
202 | &dev_attr_slidebar_mode.attr, | ||
203 | NULL | ||
204 | }; | ||
205 | |||
206 | static struct attribute_group ideapad_attr_group = { | ||
207 | .attrs = ideapad_attrs | ||
208 | }; | ||
209 | |||
210 | static const struct attribute_group *ideapad_attr_groups[] = { | ||
211 | &ideapad_attr_group, | ||
212 | NULL | ||
213 | }; | ||
214 | |||
215 | static int __init ideapad_probe(struct platform_device* pdev) | ||
216 | { | ||
217 | int err; | ||
218 | |||
219 | if (!request_region(IDEAPAD_BASE, 3, "ideapad_slidebar")) { | ||
220 | dev_err(&pdev->dev, "IO ports are busy\n"); | ||
221 | return -EBUSY; | ||
222 | } | ||
223 | |||
224 | slidebar_input_dev = input_allocate_device(); | ||
225 | if (!slidebar_input_dev) { | ||
226 | dev_err(&pdev->dev, "Failed to allocate input device\n"); | ||
227 | err = -ENOMEM; | ||
228 | goto err_release_ports; | ||
229 | } | ||
230 | |||
231 | slidebar_input_dev->name = "IdeaPad Slidebar"; | ||
232 | slidebar_input_dev->id.bustype = BUS_HOST; | ||
233 | slidebar_input_dev->dev.parent = &pdev->dev; | ||
234 | input_set_capability(slidebar_input_dev, EV_KEY, BTN_TOUCH); | ||
235 | input_set_capability(slidebar_input_dev, EV_ABS, ABS_X); | ||
236 | input_set_abs_params(slidebar_input_dev, ABS_X, 0, 0xff, 0, 0); | ||
237 | |||
238 | err = i8042_install_filter(slidebar_i8042_filter); | ||
239 | if (err) { | ||
240 | dev_err(&pdev->dev, | ||
241 | "Failed to install i8042 filter: %d\n", err); | ||
242 | goto err_free_dev; | ||
243 | } | ||
244 | |||
245 | err = input_register_device(slidebar_input_dev); | ||
246 | if (err) { | ||
247 | dev_err(&pdev->dev, | ||
248 | "Failed to register input device: %d\n", err); | ||
249 | goto err_remove_filter; | ||
250 | } | ||
251 | |||
252 | return 0; | ||
253 | |||
254 | err_remove_filter: | ||
255 | i8042_remove_filter(slidebar_i8042_filter); | ||
256 | err_free_dev: | ||
257 | input_free_device(slidebar_input_dev); | ||
258 | err_release_ports: | ||
259 | release_region(IDEAPAD_BASE, 3); | ||
260 | return err; | ||
261 | } | ||
262 | |||
263 | static int ideapad_remove(struct platform_device *pdev) | ||
264 | { | ||
265 | i8042_remove_filter(slidebar_i8042_filter); | ||
266 | input_unregister_device(slidebar_input_dev); | ||
267 | release_region(IDEAPAD_BASE, 3); | ||
268 | |||
269 | return 0; | ||
270 | } | ||
271 | |||
272 | static struct platform_driver slidebar_drv = { | ||
273 | .driver = { | ||
274 | .name = "ideapad_slidebar", | ||
275 | .owner = THIS_MODULE, | ||
276 | }, | ||
277 | .remove = ideapad_remove, | ||
278 | }; | ||
279 | |||
280 | static int __init ideapad_dmi_check(const struct dmi_system_id *id) | ||
281 | { | ||
282 | pr_info("Laptop model '%s'\n", id->ident); | ||
283 | return 1; | ||
284 | } | ||
285 | |||
286 | static const struct dmi_system_id ideapad_dmi[] __initconst = { | ||
287 | { | ||
288 | .ident = "Lenovo IdeaPad Y550", | ||
289 | .matches = { | ||
290 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | ||
291 | DMI_MATCH(DMI_PRODUCT_NAME, "20017"), | ||
292 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550") | ||
293 | }, | ||
294 | .callback = ideapad_dmi_check | ||
295 | }, | ||
296 | { | ||
297 | .ident = "Lenovo IdeaPad Y550P", | ||
298 | .matches = { | ||
299 | DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), | ||
300 | DMI_MATCH(DMI_PRODUCT_NAME, "20035"), | ||
301 | DMI_MATCH(DMI_PRODUCT_VERSION, "Lenovo IdeaPad Y550P") | ||
302 | }, | ||
303 | .callback = ideapad_dmi_check | ||
304 | }, | ||
305 | { NULL, } | ||
306 | }; | ||
307 | MODULE_DEVICE_TABLE(dmi, ideapad_dmi); | ||
308 | |||
309 | static int __init slidebar_init(void) | ||
310 | { | ||
311 | int err; | ||
312 | |||
313 | if (!force && !dmi_check_system(ideapad_dmi)) { | ||
314 | pr_err("DMI does not match\n"); | ||
315 | return -ENODEV; | ||
316 | } | ||
317 | |||
318 | slidebar_platform_dev = platform_device_alloc("ideapad_slidebar", -1); | ||
319 | if (!slidebar_platform_dev) { | ||
320 | pr_err("Not enough memory\n"); | ||
321 | return -ENOMEM; | ||
322 | } | ||
323 | |||
324 | slidebar_platform_dev->dev.groups = ideapad_attr_groups; | ||
325 | |||
326 | err = platform_device_add(slidebar_platform_dev); | ||
327 | if (err) { | ||
328 | pr_err("Failed to register platform device\n"); | ||
329 | goto err_free_dev; | ||
330 | } | ||
331 | |||
332 | err = platform_driver_probe(&slidebar_drv, ideapad_probe); | ||
333 | if (err) { | ||
334 | pr_err("Failed to register platform driver\n"); | ||
335 | goto err_delete_dev; | ||
336 | } | ||
337 | |||
338 | return 0; | ||
339 | |||
340 | err_delete_dev: | ||
341 | platform_device_del(slidebar_platform_dev); | ||
342 | err_free_dev: | ||
343 | platform_device_put(slidebar_platform_dev); | ||
344 | return err; | ||
345 | } | ||
346 | |||
347 | static void __exit slidebar_exit(void) | ||
348 | { | ||
349 | platform_device_unregister(slidebar_platform_dev); | ||
350 | platform_driver_unregister(&slidebar_drv); | ||
351 | } | ||
352 | |||
353 | module_init(slidebar_init); | ||
354 | module_exit(slidebar_exit); | ||
355 | |||
356 | MODULE_AUTHOR("Andrey Moiseev <o2g.org.ru@gmail.com>"); | ||
357 | MODULE_DESCRIPTION("Slidebar input support for some Lenovo IdeaPad laptops"); | ||
358 | MODULE_LICENSE("GPL"); | ||
diff --git a/drivers/input/serio/i8042.h b/drivers/input/serio/i8042.h index 3452708fbe3b..fc080beffedc 100644 --- a/drivers/input/serio/i8042.h +++ b/drivers/input/serio/i8042.h | |||
@@ -41,30 +41,6 @@ | |||
41 | #define I8042_CTL_TIMEOUT 10000 | 41 | #define I8042_CTL_TIMEOUT 10000 |
42 | 42 | ||
43 | /* | 43 | /* |
44 | * Status register bits. | ||
45 | */ | ||
46 | |||
47 | #define I8042_STR_PARITY 0x80 | ||
48 | #define I8042_STR_TIMEOUT 0x40 | ||
49 | #define I8042_STR_AUXDATA 0x20 | ||
50 | #define I8042_STR_KEYLOCK 0x10 | ||
51 | #define I8042_STR_CMDDAT 0x08 | ||
52 | #define I8042_STR_MUXERR 0x04 | ||
53 | #define I8042_STR_IBF 0x02 | ||
54 | #define I8042_STR_OBF 0x01 | ||
55 | |||
56 | /* | ||
57 | * Control register bits. | ||
58 | */ | ||
59 | |||
60 | #define I8042_CTR_KBDINT 0x01 | ||
61 | #define I8042_CTR_AUXINT 0x02 | ||
62 | #define I8042_CTR_IGNKEYLOCK 0x08 | ||
63 | #define I8042_CTR_KBDDIS 0x10 | ||
64 | #define I8042_CTR_AUXDIS 0x20 | ||
65 | #define I8042_CTR_XLATE 0x40 | ||
66 | |||
67 | /* | ||
68 | * Return codes. | 44 | * Return codes. |
69 | */ | 45 | */ |
70 | 46 | ||
diff --git a/include/linux/i8042.h b/include/linux/i8042.h index a986ff588944..0f9bafa17a02 100644 --- a/include/linux/i8042.h +++ b/include/linux/i8042.h | |||
@@ -31,6 +31,30 @@ | |||
31 | #define I8042_CMD_MUX_PFX 0x0090 | 31 | #define I8042_CMD_MUX_PFX 0x0090 |
32 | #define I8042_CMD_MUX_SEND 0x1090 | 32 | #define I8042_CMD_MUX_SEND 0x1090 |
33 | 33 | ||
34 | /* | ||
35 | * Status register bits. | ||
36 | */ | ||
37 | |||
38 | #define I8042_STR_PARITY 0x80 | ||
39 | #define I8042_STR_TIMEOUT 0x40 | ||
40 | #define I8042_STR_AUXDATA 0x20 | ||
41 | #define I8042_STR_KEYLOCK 0x10 | ||
42 | #define I8042_STR_CMDDAT 0x08 | ||
43 | #define I8042_STR_MUXERR 0x04 | ||
44 | #define I8042_STR_IBF 0x02 | ||
45 | #define I8042_STR_OBF 0x01 | ||
46 | |||
47 | /* | ||
48 | * Control register bits. | ||
49 | */ | ||
50 | |||
51 | #define I8042_CTR_KBDINT 0x01 | ||
52 | #define I8042_CTR_AUXINT 0x02 | ||
53 | #define I8042_CTR_IGNKEYLOCK 0x08 | ||
54 | #define I8042_CTR_KBDDIS 0x10 | ||
55 | #define I8042_CTR_AUXDIS 0x20 | ||
56 | #define I8042_CTR_XLATE 0x40 | ||
57 | |||
34 | struct serio; | 58 | struct serio; |
35 | 59 | ||
36 | #if defined(CONFIG_SERIO_I8042) || defined(CONFIG_SERIO_I8042_MODULE) | 60 | #if defined(CONFIG_SERIO_I8042) || defined(CONFIG_SERIO_I8042_MODULE) |