Commit | Line | Data |
---|---|---|
c1dcad2d BS |
1 | /* |
2 | * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint | |
3 | * | |
4 | * Copyright (c) 2012 Bernhard Seibold | |
5 | */ | |
6 | ||
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 | ||
14 | #include <linux/module.h> | |
15 | #include <linux/sysfs.h> | |
16 | #include <linux/device.h> | |
17 | #include <linux/usb.h> | |
18 | #include <linux/hid.h> | |
19 | #include <linux/input.h> | |
20 | #include <linux/leds.h> | |
21 | #include "usbhid/usbhid.h" | |
22 | ||
23 | #include "hid-ids.h" | |
24 | ||
25 | /* This is only used for the trackpoint part of the driver, hence _tp */ | |
26 | struct tpkbd_data_pointer { | |
27 | int led_state; | |
28 | struct led_classdev led_mute; | |
29 | struct led_classdev led_micmute; | |
30 | int press_to_select; | |
31 | int dragging; | |
32 | int release_to_select; | |
33 | int select_right; | |
34 | int sensitivity; | |
35 | int press_speed; | |
36 | }; | |
37 | ||
38 | #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) | |
39 | ||
40 | static int tpkbd_input_mapping(struct hid_device *hdev, | |
41 | struct hid_input *hi, struct hid_field *field, | |
42 | struct hid_usage *usage, unsigned long **bit, int *max) | |
43 | { | |
44 | struct usbhid_device *uhdev; | |
45 | ||
46 | uhdev = (struct usbhid_device *) hdev->driver_data; | |
47 | if (uhdev->ifnum == 1 && usage->hid == (HID_UP_BUTTON | 0x0010)) { | |
48 | map_key_clear(KEY_MICMUTE); | |
49 | return 1; | |
50 | } | |
51 | return 0; | |
52 | } | |
53 | ||
54 | #undef map_key_clear | |
55 | ||
56 | static int tpkbd_features_set(struct hid_device *hdev) | |
57 | { | |
58 | struct hid_report *report; | |
59 | struct tpkbd_data_pointer *data_pointer; | |
60 | ||
61 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
62 | report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; | |
63 | ||
64 | report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; | |
65 | report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; | |
66 | report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; | |
67 | report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; | |
68 | report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver | |
69 | report->field[2]->value[0] = data_pointer->sensitivity; | |
70 | report->field[3]->value[0] = data_pointer->press_speed; | |
71 | ||
72 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | |
73 | return 0; | |
74 | } | |
75 | ||
76 | static ssize_t pointer_press_to_select_show(struct device *dev, | |
77 | struct device_attribute *attr, | |
78 | char *buf) | |
79 | { | |
80 | struct hid_device *hdev; | |
81 | struct tpkbd_data_pointer *data_pointer; | |
82 | ||
83 | hdev = container_of(dev, struct hid_device, dev); | |
84 | if (hdev == NULL) | |
85 | return -ENODEV; | |
86 | ||
87 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
88 | ||
89 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); | |
90 | } | |
91 | ||
92 | static ssize_t pointer_press_to_select_store(struct device *dev, | |
93 | struct device_attribute *attr, | |
94 | const char *buf, | |
95 | size_t count) | |
96 | { | |
97 | struct hid_device *hdev; | |
98 | struct tpkbd_data_pointer *data_pointer; | |
99 | int value; | |
100 | ||
101 | hdev = container_of(dev, struct hid_device, dev); | |
102 | if (hdev == NULL) | |
103 | return -ENODEV; | |
104 | ||
105 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
106 | ||
107 | if (kstrtoint(buf, 10, &value)) | |
108 | return -EINVAL; | |
109 | if (value < 0 || value > 1) | |
110 | return -EINVAL; | |
111 | ||
112 | data_pointer->press_to_select = value; | |
113 | tpkbd_features_set(hdev); | |
114 | ||
115 | return count; | |
116 | } | |
117 | ||
118 | static ssize_t pointer_dragging_show(struct device *dev, | |
119 | struct device_attribute *attr, | |
120 | char *buf) | |
121 | { | |
122 | struct hid_device *hdev; | |
123 | struct tpkbd_data_pointer *data_pointer; | |
124 | ||
125 | hdev = container_of(dev, struct hid_device, dev); | |
126 | if (hdev == NULL) | |
127 | return -ENODEV; | |
128 | ||
129 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
130 | ||
131 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); | |
132 | } | |
133 | ||
134 | static ssize_t pointer_dragging_store(struct device *dev, | |
135 | struct device_attribute *attr, | |
136 | const char *buf, | |
137 | size_t count) | |
138 | { | |
139 | struct hid_device *hdev; | |
140 | struct tpkbd_data_pointer *data_pointer; | |
141 | int value; | |
142 | ||
143 | hdev = container_of(dev, struct hid_device, dev); | |
144 | if (hdev == NULL) | |
145 | return -ENODEV; | |
146 | ||
147 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
148 | ||
149 | if (kstrtoint(buf, 10, &value)) | |
150 | return -EINVAL; | |
151 | if (value < 0 || value > 1) | |
152 | return -EINVAL; | |
153 | ||
154 | data_pointer->dragging = value; | |
155 | tpkbd_features_set(hdev); | |
156 | ||
157 | return count; | |
158 | } | |
159 | ||
160 | static ssize_t pointer_release_to_select_show(struct device *dev, | |
161 | struct device_attribute *attr, | |
162 | char *buf) | |
163 | { | |
164 | struct hid_device *hdev; | |
165 | struct tpkbd_data_pointer *data_pointer; | |
166 | ||
167 | hdev = container_of(dev, struct hid_device, dev); | |
168 | if (hdev == NULL) | |
169 | return -ENODEV; | |
170 | ||
171 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
172 | ||
173 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); | |
174 | } | |
175 | ||
176 | static ssize_t pointer_release_to_select_store(struct device *dev, | |
177 | struct device_attribute *attr, | |
178 | const char *buf, | |
179 | size_t count) | |
180 | { | |
181 | struct hid_device *hdev; | |
182 | struct tpkbd_data_pointer *data_pointer; | |
183 | int value; | |
184 | ||
185 | hdev = container_of(dev, struct hid_device, dev); | |
186 | if (hdev == NULL) | |
187 | return -ENODEV; | |
188 | ||
189 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
190 | ||
191 | if (kstrtoint(buf, 10, &value)) | |
192 | return -EINVAL; | |
193 | if (value < 0 || value > 1) | |
194 | return -EINVAL; | |
195 | ||
196 | data_pointer->release_to_select = value; | |
197 | tpkbd_features_set(hdev); | |
198 | ||
199 | return count; | |
200 | } | |
201 | ||
202 | static ssize_t pointer_select_right_show(struct device *dev, | |
203 | struct device_attribute *attr, | |
204 | char *buf) | |
205 | { | |
206 | struct hid_device *hdev; | |
207 | struct tpkbd_data_pointer *data_pointer; | |
208 | ||
209 | hdev = container_of(dev, struct hid_device, dev); | |
210 | if (hdev == NULL) | |
211 | return -ENODEV; | |
212 | ||
213 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
214 | ||
215 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); | |
216 | } | |
217 | ||
218 | static ssize_t pointer_select_right_store(struct device *dev, | |
219 | struct device_attribute *attr, | |
220 | const char *buf, | |
221 | size_t count) | |
222 | { | |
223 | struct hid_device *hdev; | |
224 | struct tpkbd_data_pointer *data_pointer; | |
225 | int value; | |
226 | ||
227 | hdev = container_of(dev, struct hid_device, dev); | |
228 | if (hdev == NULL) | |
229 | return -ENODEV; | |
230 | ||
231 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
232 | ||
233 | if (kstrtoint(buf, 10, &value)) | |
234 | return -EINVAL; | |
235 | if (value < 0 || value > 1) | |
236 | return -EINVAL; | |
237 | ||
238 | data_pointer->select_right = value; | |
239 | tpkbd_features_set(hdev); | |
240 | ||
241 | return count; | |
242 | } | |
243 | ||
244 | static ssize_t pointer_sensitivity_show(struct device *dev, | |
245 | struct device_attribute *attr, | |
246 | char *buf) | |
247 | { | |
248 | struct hid_device *hdev; | |
249 | struct tpkbd_data_pointer *data_pointer; | |
250 | ||
251 | hdev = container_of(dev, struct hid_device, dev); | |
252 | if (hdev == NULL) | |
253 | return -ENODEV; | |
254 | ||
255 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
256 | ||
257 | return snprintf(buf, PAGE_SIZE, "%u\n", | |
258 | data_pointer->sensitivity); | |
259 | } | |
260 | ||
261 | static ssize_t pointer_sensitivity_store(struct device *dev, | |
262 | struct device_attribute *attr, | |
263 | const char *buf, | |
264 | size_t count) | |
265 | { | |
266 | struct hid_device *hdev; | |
267 | struct tpkbd_data_pointer *data_pointer; | |
268 | int value; | |
269 | ||
270 | hdev = container_of(dev, struct hid_device, dev); | |
271 | if (hdev == NULL) | |
272 | return -ENODEV; | |
273 | ||
274 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
275 | ||
276 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) | |
277 | return -EINVAL; | |
278 | ||
279 | data_pointer->sensitivity = value; | |
280 | tpkbd_features_set(hdev); | |
281 | ||
282 | return count; | |
283 | } | |
284 | ||
285 | static ssize_t pointer_press_speed_show(struct device *dev, | |
286 | struct device_attribute *attr, | |
287 | char *buf) | |
288 | { | |
289 | struct hid_device *hdev; | |
290 | struct tpkbd_data_pointer *data_pointer; | |
291 | ||
292 | hdev = container_of(dev, struct hid_device, dev); | |
293 | if (hdev == NULL) | |
294 | return -ENODEV; | |
295 | ||
296 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
297 | ||
298 | return snprintf(buf, PAGE_SIZE, "%u\n", | |
299 | data_pointer->press_speed); | |
300 | } | |
301 | ||
302 | static ssize_t pointer_press_speed_store(struct device *dev, | |
303 | struct device_attribute *attr, | |
304 | const char *buf, | |
305 | size_t count) | |
306 | { | |
307 | struct hid_device *hdev; | |
308 | struct tpkbd_data_pointer *data_pointer; | |
309 | int value; | |
310 | ||
311 | hdev = container_of(dev, struct hid_device, dev); | |
312 | if (hdev == NULL) | |
313 | return -ENODEV; | |
314 | ||
315 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
316 | ||
317 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) | |
318 | return -EINVAL; | |
319 | ||
320 | data_pointer->press_speed = value; | |
321 | tpkbd_features_set(hdev); | |
322 | ||
323 | return count; | |
324 | } | |
325 | ||
326 | static struct device_attribute dev_attr_pointer_press_to_select = | |
327 | __ATTR(press_to_select, S_IWUSR | S_IRUGO, | |
328 | pointer_press_to_select_show, | |
329 | pointer_press_to_select_store); | |
330 | ||
331 | static struct device_attribute dev_attr_pointer_dragging = | |
332 | __ATTR(dragging, S_IWUSR | S_IRUGO, | |
333 | pointer_dragging_show, | |
334 | pointer_dragging_store); | |
335 | ||
336 | static struct device_attribute dev_attr_pointer_release_to_select = | |
337 | __ATTR(release_to_select, S_IWUSR | S_IRUGO, | |
338 | pointer_release_to_select_show, | |
339 | pointer_release_to_select_store); | |
340 | ||
341 | static struct device_attribute dev_attr_pointer_select_right = | |
342 | __ATTR(select_right, S_IWUSR | S_IRUGO, | |
343 | pointer_select_right_show, | |
344 | pointer_select_right_store); | |
345 | ||
346 | static struct device_attribute dev_attr_pointer_sensitivity = | |
347 | __ATTR(sensitivity, S_IWUSR | S_IRUGO, | |
348 | pointer_sensitivity_show, | |
349 | pointer_sensitivity_store); | |
350 | ||
351 | static struct device_attribute dev_attr_pointer_press_speed = | |
352 | __ATTR(press_speed, S_IWUSR | S_IRUGO, | |
353 | pointer_press_speed_show, | |
354 | pointer_press_speed_store); | |
355 | ||
356 | static struct attribute *tpkbd_attributes_pointer[] = { | |
357 | &dev_attr_pointer_press_to_select.attr, | |
358 | &dev_attr_pointer_dragging.attr, | |
359 | &dev_attr_pointer_release_to_select.attr, | |
360 | &dev_attr_pointer_select_right.attr, | |
361 | &dev_attr_pointer_sensitivity.attr, | |
362 | &dev_attr_pointer_press_speed.attr, | |
363 | NULL | |
364 | }; | |
365 | ||
366 | static const struct attribute_group tpkbd_attr_group_pointer = { | |
367 | .attrs = tpkbd_attributes_pointer, | |
368 | }; | |
369 | ||
370 | static enum led_brightness tpkbd_led_brightness_get( | |
371 | struct led_classdev *led_cdev) | |
372 | { | |
373 | struct device *dev; | |
374 | struct hid_device *hdev; | |
375 | struct tpkbd_data_pointer *data_pointer; | |
376 | int led_nr = 0; | |
377 | ||
378 | dev = led_cdev->dev->parent; | |
379 | hdev = container_of(dev, struct hid_device, dev); | |
380 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
381 | ||
382 | if (led_cdev == &data_pointer->led_micmute) | |
383 | led_nr = 1; | |
384 | ||
385 | return data_pointer->led_state & (1 << led_nr) | |
386 | ? LED_FULL | |
387 | : LED_OFF; | |
388 | } | |
389 | ||
390 | static void tpkbd_led_brightness_set(struct led_classdev *led_cdev, | |
391 | enum led_brightness value) | |
392 | { | |
393 | struct device *dev; | |
394 | struct hid_device *hdev; | |
395 | struct hid_report *report; | |
396 | struct tpkbd_data_pointer *data_pointer; | |
397 | int led_nr = 0; | |
398 | ||
399 | dev = led_cdev->dev->parent; | |
400 | hdev = container_of(dev, struct hid_device, dev); | |
401 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
402 | ||
403 | if (led_cdev == &data_pointer->led_micmute) | |
404 | led_nr = 1; | |
405 | ||
406 | if (value == LED_OFF) | |
407 | data_pointer->led_state &= ~(1 << led_nr); | |
408 | else | |
409 | data_pointer->led_state |= 1 << led_nr; | |
410 | ||
411 | report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; | |
412 | report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; | |
413 | report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; | |
414 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | |
415 | } | |
416 | ||
417 | static int tpkbd_probe_tp(struct hid_device *hdev) | |
418 | { | |
419 | struct device *dev = &hdev->dev; | |
420 | struct tpkbd_data_pointer *data_pointer; | |
421 | size_t name_sz = strlen(dev_name(dev)) + 16; | |
422 | char *name_mute, *name_micmute; | |
423 | int ret; | |
424 | ||
425 | if (sysfs_create_group(&hdev->dev.kobj, | |
426 | &tpkbd_attr_group_pointer)) { | |
427 | hid_warn(hdev, "Could not create sysfs group\n"); | |
428 | } | |
429 | ||
430 | data_pointer = kzalloc(sizeof(struct tpkbd_data_pointer), GFP_KERNEL); | |
431 | if (data_pointer == NULL) { | |
432 | hid_err(hdev, "Could not allocate memory for driver data\n"); | |
433 | return -ENOMEM; | |
434 | } | |
435 | ||
436 | // set same default values as windows driver | |
437 | data_pointer->sensitivity = 0xa0; | |
438 | data_pointer->press_speed = 0x38; | |
439 | ||
440 | name_mute = kzalloc(name_sz, GFP_KERNEL); | |
441 | if (name_mute == NULL) { | |
442 | hid_err(hdev, "Could not allocate memory for led data\n"); | |
443 | ret = -ENOMEM; | |
444 | goto err; | |
445 | } | |
446 | snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); | |
447 | ||
448 | name_micmute = kzalloc(name_sz, GFP_KERNEL); | |
449 | if (name_micmute == NULL) { | |
450 | hid_err(hdev, "Could not allocate memory for led data\n"); | |
451 | ret = -ENOMEM; | |
452 | goto err2; | |
453 | } | |
454 | snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); | |
455 | ||
456 | hid_set_drvdata(hdev, data_pointer); | |
457 | ||
458 | data_pointer->led_mute.name = name_mute; | |
459 | data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get; | |
460 | data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set; | |
461 | data_pointer->led_mute.dev = dev; | |
462 | led_classdev_register(dev, &data_pointer->led_mute); | |
463 | ||
464 | data_pointer->led_micmute.name = name_micmute; | |
465 | data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get; | |
466 | data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set; | |
467 | data_pointer->led_micmute.dev = dev; | |
468 | led_classdev_register(dev, &data_pointer->led_micmute); | |
469 | ||
470 | tpkbd_features_set(hdev); | |
471 | ||
472 | return 0; | |
473 | ||
474 | err2: | |
475 | kfree(name_mute); | |
476 | err: | |
477 | kfree(data_pointer); | |
478 | return ret; | |
479 | } | |
480 | ||
481 | static int tpkbd_probe(struct hid_device *hdev, | |
482 | const struct hid_device_id *id) | |
483 | { | |
484 | int ret; | |
485 | struct usbhid_device *uhdev; | |
486 | ||
487 | ret = hid_parse(hdev); | |
488 | if (ret) { | |
489 | hid_err(hdev, "hid_parse failed\n"); | |
490 | goto err_free; | |
491 | } | |
492 | ||
493 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | |
494 | if (ret) { | |
495 | hid_err(hdev, "hid_hw_start failed\n"); | |
496 | goto err_free; | |
497 | } | |
498 | ||
499 | uhdev = (struct usbhid_device *) hdev->driver_data; | |
500 | ||
501 | if (uhdev->ifnum == 1) | |
502 | return tpkbd_probe_tp(hdev); | |
503 | ||
504 | return 0; | |
505 | err_free: | |
506 | return ret; | |
507 | } | |
508 | ||
509 | static void tpkbd_remove_tp(struct hid_device *hdev) | |
510 | { | |
511 | struct tpkbd_data_pointer *data_pointer; | |
512 | ||
513 | sysfs_remove_group(&hdev->dev.kobj, | |
514 | &tpkbd_attr_group_pointer); | |
515 | ||
516 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | |
517 | ||
518 | led_classdev_unregister(&data_pointer->led_micmute); | |
519 | led_classdev_unregister(&data_pointer->led_mute); | |
520 | ||
521 | hid_set_drvdata(hdev, NULL); | |
522 | kfree(data_pointer); | |
523 | } | |
524 | ||
525 | static void tpkbd_remove(struct hid_device *hdev) | |
526 | { | |
527 | struct usbhid_device *uhdev; | |
528 | ||
529 | uhdev = (struct usbhid_device *) hdev->driver_data; | |
530 | if (uhdev->ifnum == 1) | |
531 | tpkbd_remove_tp(hdev); | |
532 | ||
533 | hid_hw_stop(hdev); | |
534 | } | |
535 | ||
536 | static const struct hid_device_id tpkbd_devices[] = { | |
537 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, | |
538 | { } | |
539 | }; | |
540 | ||
541 | MODULE_DEVICE_TABLE(hid, tpkbd_devices); | |
542 | ||
543 | static struct hid_driver tpkbd_driver = { | |
544 | .name = "lenovo_tpkbd", | |
545 | .id_table = tpkbd_devices, | |
546 | .input_mapping = tpkbd_input_mapping, | |
547 | .probe = tpkbd_probe, | |
548 | .remove = tpkbd_remove, | |
549 | }; | |
550 | ||
551 | static int __init tpkbd_init(void) | |
552 | { | |
553 | return hid_register_driver(&tpkbd_driver); | |
554 | } | |
555 | ||
556 | static void __exit tpkbd_exit(void) | |
557 | { | |
558 | hid_unregister_driver(&tpkbd_driver); | |
559 | } | |
560 | ||
561 | module_init(tpkbd_init); | |
562 | module_exit(tpkbd_exit); | |
563 | ||
564 | MODULE_LICENSE("GPL"); |