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