Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
c1dcad2d | 2 | /* |
6a5b414b JL |
3 | * HID driver for Lenovo: |
4 | * - ThinkPad USB Keyboard with TrackPoint (tpkbd) | |
f3d4ff0e JL |
5 | * - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd) |
6 | * - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd) | |
c1dcad2d BS |
7 | * |
8 | * Copyright (c) 2012 Bernhard Seibold | |
f3d4ff0e | 9 | * Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk> |
a230cd52 | 10 | * |
11 | * Linux IBM/Lenovo Scrollpoint mouse driver: | |
12 | * - IBM Scrollpoint III | |
13 | * - IBM Scrollpoint Pro | |
14 | * - IBM Scrollpoint Optical | |
15 | * - IBM Scrollpoint Optical 800dpi | |
16 | * - IBM Scrollpoint Optical 800dpi Pro | |
17 | * - Lenovo Scrollpoint Optical | |
18 | * | |
19 | * Copyright (c) 2012 Peter De Wachter <pdewacht@gmail.com> | |
20 | * Copyright (c) 2018 Peter Ganzhorn <peter.ganzhorn@gmail.com> | |
c1dcad2d BS |
21 | */ |
22 | ||
23 | /* | |
c1dcad2d BS |
24 | */ |
25 | ||
26 | #include <linux/module.h> | |
27 | #include <linux/sysfs.h> | |
28 | #include <linux/device.h> | |
c1dcad2d BS |
29 | #include <linux/hid.h> |
30 | #include <linux/input.h> | |
31 | #include <linux/leds.h> | |
c1dcad2d BS |
32 | |
33 | #include "hid-ids.h" | |
34 | ||
b72cdfa8 | 35 | struct lenovo_drvdata { |
bc04b37e | 36 | u8 led_report[3]; /* Must be first for proper alignment */ |
c1dcad2d | 37 | int led_state; |
bc04b37e | 38 | struct mutex led_report_mutex; |
c1dcad2d BS |
39 | struct led_classdev led_mute; |
40 | struct led_classdev led_micmute; | |
41 | int press_to_select; | |
42 | int dragging; | |
43 | int release_to_select; | |
44 | int select_right; | |
45 | int sensitivity; | |
46 | int press_speed; | |
3cb5ff02 | 47 | u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */ |
f3d4ff0e JL |
48 | bool fn_lock; |
49 | }; | |
50 | ||
c1dcad2d BS |
51 | #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) |
52 | ||
bc04b37e HG |
53 | #define TP10UBKBD_LED_OUTPUT_REPORT 9 |
54 | ||
55 | #define TP10UBKBD_FN_LOCK_LED 0x54 | |
56 | #define TP10UBKBD_MUTE_LED 0x64 | |
57 | #define TP10UBKBD_MICMUTE_LED 0x74 | |
58 | ||
59 | #define TP10UBKBD_LED_OFF 1 | |
60 | #define TP10UBKBD_LED_ON 2 | |
61 | ||
62 | static void lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code, | |
63 | enum led_brightness value) | |
64 | { | |
65 | struct lenovo_drvdata *data = hid_get_drvdata(hdev); | |
66 | int ret; | |
67 | ||
68 | mutex_lock(&data->led_report_mutex); | |
69 | ||
70 | data->led_report[0] = TP10UBKBD_LED_OUTPUT_REPORT; | |
71 | data->led_report[1] = led_code; | |
72 | data->led_report[2] = value ? TP10UBKBD_LED_ON : TP10UBKBD_LED_OFF; | |
73 | ret = hid_hw_raw_request(hdev, data->led_report[0], data->led_report, 3, | |
74 | HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); | |
75 | if (ret) | |
76 | hid_err(hdev, "Set LED output report error: %d\n", ret); | |
77 | ||
78 | mutex_unlock(&data->led_report_mutex); | |
79 | } | |
80 | ||
181a8b91 BT |
81 | static const __u8 lenovo_pro_dock_need_fixup_collection[] = { |
82 | 0x05, 0x88, /* Usage Page (Vendor Usage Page 0x88) */ | |
83 | 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ | |
84 | 0xa1, 0x01, /* Collection (Application) */ | |
85 | 0x85, 0x04, /* Report ID (4) */ | |
86 | 0x19, 0x00, /* Usage Minimum (0) */ | |
87 | 0x2a, 0xff, 0xff, /* Usage Maximum (65535) */ | |
88 | }; | |
89 | ||
90 | static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, | |
91 | unsigned int *rsize) | |
92 | { | |
93 | switch (hdev->product) { | |
94 | case USB_DEVICE_ID_LENOVO_TPPRODOCK: | |
95 | /* the fixups that need to be done: | |
96 | * - get a reasonable usage max for the vendor collection | |
97 | * 0x8801 from the report ID 4 | |
98 | */ | |
99 | if (*rsize >= 153 && | |
100 | memcmp(&rdesc[140], lenovo_pro_dock_need_fixup_collection, | |
101 | sizeof(lenovo_pro_dock_need_fixup_collection)) == 0) { | |
102 | rdesc[151] = 0x01; | |
103 | rdesc[152] = 0x00; | |
104 | } | |
105 | break; | |
106 | } | |
107 | return rdesc; | |
108 | } | |
109 | ||
94723bfa | 110 | static int lenovo_input_mapping_tpkbd(struct hid_device *hdev, |
c1dcad2d BS |
111 | struct hid_input *hi, struct hid_field *field, |
112 | struct hid_usage *usage, unsigned long **bit, int *max) | |
113 | { | |
0c521836 | 114 | if (usage->hid == (HID_UP_BUTTON | 0x0010)) { |
6a5b414b | 115 | /* This sub-device contains trackpoint, mark it */ |
0c521836 | 116 | hid_set_drvdata(hdev, (void *)1); |
c1dcad2d BS |
117 | map_key_clear(KEY_MICMUTE); |
118 | return 1; | |
119 | } | |
120 | return 0; | |
121 | } | |
122 | ||
f3d4ff0e JL |
123 | static int lenovo_input_mapping_cptkbd(struct hid_device *hdev, |
124 | struct hid_input *hi, struct hid_field *field, | |
125 | struct hid_usage *usage, unsigned long **bit, int *max) | |
126 | { | |
127 | /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */ | |
128 | if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR || | |
129 | (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) { | |
f3d4ff0e JL |
130 | switch (usage->hid & HID_USAGE) { |
131 | case 0x00f1: /* Fn-F4: Mic mute */ | |
132 | map_key_clear(KEY_MICMUTE); | |
133 | return 1; | |
134 | case 0x00f2: /* Fn-F5: Brightness down */ | |
135 | map_key_clear(KEY_BRIGHTNESSDOWN); | |
136 | return 1; | |
137 | case 0x00f3: /* Fn-F6: Brightness up */ | |
138 | map_key_clear(KEY_BRIGHTNESSUP); | |
139 | return 1; | |
140 | case 0x00f4: /* Fn-F7: External display (projector) */ | |
141 | map_key_clear(KEY_SWITCHVIDEOMODE); | |
142 | return 1; | |
143 | case 0x00f5: /* Fn-F8: Wireless */ | |
144 | map_key_clear(KEY_WLAN); | |
145 | return 1; | |
146 | case 0x00f6: /* Fn-F9: Control panel */ | |
147 | map_key_clear(KEY_CONFIG); | |
148 | return 1; | |
149 | case 0x00f8: /* Fn-F11: View open applications (3 boxes) */ | |
150 | map_key_clear(KEY_SCALE); | |
151 | return 1; | |
5556eb14 | 152 | case 0x00f9: /* Fn-F12: Open My computer (6 boxes) USB-only */ |
f3d4ff0e JL |
153 | /* NB: This mapping is invented in raw_event below */ |
154 | map_key_clear(KEY_FILE); | |
155 | return 1; | |
5556eb14 JL |
156 | case 0x00fa: /* Fn-Esc: Fn-lock toggle */ |
157 | map_key_clear(KEY_FN_ESC); | |
158 | return 1; | |
94eefa27 JL |
159 | case 0x00fb: /* Middle mouse button (in native mode) */ |
160 | map_key_clear(BTN_MIDDLE); | |
161 | return 1; | |
162 | } | |
163 | } | |
164 | ||
165 | /* Compatibility middle/wheel mappings should be ignored */ | |
166 | if (usage->hid == HID_GD_WHEEL) | |
167 | return -1; | |
168 | if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON && | |
169 | (usage->hid & HID_USAGE) == 0x003) | |
170 | return -1; | |
171 | if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER && | |
172 | (usage->hid & HID_USAGE) == 0x238) | |
173 | return -1; | |
174 | ||
175 | /* Map wheel emulation reports: 0xffa1 = USB, 0xff10 = BT */ | |
176 | if ((usage->hid & HID_USAGE_PAGE) == 0xff100000 || | |
177 | (usage->hid & HID_USAGE_PAGE) == 0xffa10000) { | |
178 | field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE; | |
179 | field->logical_minimum = -127; | |
180 | field->logical_maximum = 127; | |
181 | ||
182 | switch (usage->hid & HID_USAGE) { | |
183 | case 0x0000: | |
7f65068f | 184 | hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL); |
94eefa27 JL |
185 | return 1; |
186 | case 0x0001: | |
7f65068f | 187 | hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL); |
94eefa27 JL |
188 | return 1; |
189 | default: | |
190 | return -1; | |
f3d4ff0e JL |
191 | } |
192 | } | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
a230cd52 | 197 | static int lenovo_input_mapping_scrollpoint(struct hid_device *hdev, |
198 | struct hid_input *hi, struct hid_field *field, | |
199 | struct hid_usage *usage, unsigned long **bit, int *max) | |
200 | { | |
201 | if (usage->hid == HID_GD_Z) { | |
202 | hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL); | |
203 | return 1; | |
204 | } | |
205 | return 0; | |
206 | } | |
207 | ||
bc04b37e HG |
208 | static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev, |
209 | struct hid_input *hi, struct hid_field *field, | |
210 | struct hid_usage *usage, unsigned long **bit, int *max) | |
211 | { | |
212 | /* | |
213 | * The ThinkPad 10 Ultrabook Keyboard uses 0x000c0001 usage for | |
214 | * a bunch of keys which have no standard consumer page code. | |
215 | */ | |
216 | if (usage->hid == 0x000c0001) { | |
217 | switch (usage->usage_index) { | |
218 | case 8: /* Fn-Esc: Fn-lock toggle */ | |
219 | map_key_clear(KEY_FN_ESC); | |
220 | return 1; | |
221 | case 9: /* Fn-F4: Mic mute */ | |
222 | map_key_clear(KEY_MICMUTE); | |
223 | return 1; | |
224 | case 10: /* Fn-F7: Control panel */ | |
225 | map_key_clear(KEY_CONFIG); | |
226 | return 1; | |
227 | case 11: /* Fn-F8: Search (magnifier glass) */ | |
228 | map_key_clear(KEY_SEARCH); | |
229 | return 1; | |
230 | case 12: /* Fn-F10: Open My computer (6 boxes) */ | |
231 | map_key_clear(KEY_FILE); | |
232 | return 1; | |
233 | } | |
234 | } | |
235 | ||
236 | return 0; | |
237 | } | |
238 | ||
6a5b414b JL |
239 | static int lenovo_input_mapping(struct hid_device *hdev, |
240 | struct hid_input *hi, struct hid_field *field, | |
241 | struct hid_usage *usage, unsigned long **bit, int *max) | |
242 | { | |
243 | switch (hdev->product) { | |
244 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
245 | return lenovo_input_mapping_tpkbd(hdev, hi, field, | |
246 | usage, bit, max); | |
f3d4ff0e JL |
247 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
248 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
249 | return lenovo_input_mapping_cptkbd(hdev, hi, field, | |
250 | usage, bit, max); | |
a230cd52 | 251 | case USB_DEVICE_ID_IBM_SCROLLPOINT_III: |
252 | case USB_DEVICE_ID_IBM_SCROLLPOINT_PRO: | |
253 | case USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL: | |
254 | case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL: | |
255 | case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO: | |
256 | case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL: | |
257 | return lenovo_input_mapping_scrollpoint(hdev, hi, field, | |
258 | usage, bit, max); | |
bc04b37e HG |
259 | case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
260 | return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field, | |
261 | usage, bit, max); | |
6a5b414b JL |
262 | default: |
263 | return 0; | |
264 | } | |
265 | } | |
266 | ||
c1dcad2d BS |
267 | #undef map_key_clear |
268 | ||
f3d4ff0e JL |
269 | /* Send a config command to the keyboard */ |
270 | static int lenovo_send_cmd_cptkbd(struct hid_device *hdev, | |
271 | unsigned char byte2, unsigned char byte3) | |
272 | { | |
273 | int ret; | |
ea36ae09 JB |
274 | unsigned char *buf; |
275 | ||
276 | buf = kzalloc(3, GFP_KERNEL); | |
277 | if (!buf) | |
278 | return -ENOMEM; | |
279 | ||
280 | buf[0] = 0x18; | |
281 | buf[1] = byte2; | |
282 | buf[2] = byte3; | |
f3d4ff0e JL |
283 | |
284 | switch (hdev->product) { | |
285 | case USB_DEVICE_ID_LENOVO_CUSBKBD: | |
ea36ae09 | 286 | ret = hid_hw_raw_request(hdev, 0x13, buf, 3, |
f3d4ff0e JL |
287 | HID_FEATURE_REPORT, HID_REQ_SET_REPORT); |
288 | break; | |
289 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
ea36ae09 | 290 | ret = hid_hw_output_report(hdev, buf, 3); |
f3d4ff0e JL |
291 | break; |
292 | default: | |
293 | ret = -EINVAL; | |
294 | break; | |
295 | } | |
296 | ||
ea36ae09 JB |
297 | kfree(buf); |
298 | ||
f3d4ff0e JL |
299 | return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */ |
300 | } | |
301 | ||
302 | static void lenovo_features_set_cptkbd(struct hid_device *hdev) | |
303 | { | |
304 | int ret; | |
b72cdfa8 | 305 | struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
f3d4ff0e JL |
306 | |
307 | ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); | |
308 | if (ret) | |
309 | hid_err(hdev, "Fn-lock setting failed: %d\n", ret); | |
dbfebb44 JL |
310 | |
311 | ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity); | |
312 | if (ret) | |
313 | hid_err(hdev, "Sensitivity setting failed: %d\n", ret); | |
f3d4ff0e JL |
314 | } |
315 | ||
ef550c5d | 316 | static ssize_t attr_fn_lock_show(struct device *dev, |
f3d4ff0e JL |
317 | struct device_attribute *attr, |
318 | char *buf) | |
319 | { | |
ee79a8f8 | 320 | struct hid_device *hdev = to_hid_device(dev); |
ef550c5d | 321 | struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
f3d4ff0e | 322 | |
ef550c5d | 323 | return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock); |
f3d4ff0e JL |
324 | } |
325 | ||
ef550c5d | 326 | static ssize_t attr_fn_lock_store(struct device *dev, |
f3d4ff0e JL |
327 | struct device_attribute *attr, |
328 | const char *buf, | |
329 | size_t count) | |
330 | { | |
ee79a8f8 | 331 | struct hid_device *hdev = to_hid_device(dev); |
ef550c5d | 332 | struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
f3d4ff0e JL |
333 | int value; |
334 | ||
335 | if (kstrtoint(buf, 10, &value)) | |
336 | return -EINVAL; | |
337 | if (value < 0 || value > 1) | |
338 | return -EINVAL; | |
339 | ||
ef550c5d HG |
340 | data->fn_lock = !!value; |
341 | ||
342 | switch (hdev->product) { | |
343 | case USB_DEVICE_ID_LENOVO_CUSBKBD: | |
344 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
345 | lenovo_features_set_cptkbd(hdev); | |
346 | break; | |
347 | } | |
f3d4ff0e JL |
348 | |
349 | return count; | |
350 | } | |
351 | ||
e3cb0acd JL |
352 | static ssize_t attr_sensitivity_show_cptkbd(struct device *dev, |
353 | struct device_attribute *attr, | |
354 | char *buf) | |
355 | { | |
ee79a8f8 | 356 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 357 | struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
e3cb0acd JL |
358 | |
359 | return snprintf(buf, PAGE_SIZE, "%u\n", | |
360 | cptkbd_data->sensitivity); | |
361 | } | |
362 | ||
363 | static ssize_t attr_sensitivity_store_cptkbd(struct device *dev, | |
364 | struct device_attribute *attr, | |
365 | const char *buf, | |
366 | size_t count) | |
367 | { | |
ee79a8f8 | 368 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 369 | struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
e3cb0acd JL |
370 | int value; |
371 | ||
372 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) | |
373 | return -EINVAL; | |
374 | ||
375 | cptkbd_data->sensitivity = value; | |
376 | lenovo_features_set_cptkbd(hdev); | |
377 | ||
378 | return count; | |
379 | } | |
380 | ||
381 | ||
ef550c5d | 382 | static struct device_attribute dev_attr_fn_lock = |
f3d4ff0e | 383 | __ATTR(fn_lock, S_IWUSR | S_IRUGO, |
ef550c5d HG |
384 | attr_fn_lock_show, |
385 | attr_fn_lock_store); | |
f3d4ff0e | 386 | |
e3cb0acd JL |
387 | static struct device_attribute dev_attr_sensitivity_cptkbd = |
388 | __ATTR(sensitivity, S_IWUSR | S_IRUGO, | |
389 | attr_sensitivity_show_cptkbd, | |
390 | attr_sensitivity_store_cptkbd); | |
391 | ||
392 | ||
f3d4ff0e | 393 | static struct attribute *lenovo_attributes_cptkbd[] = { |
ef550c5d | 394 | &dev_attr_fn_lock.attr, |
e3cb0acd | 395 | &dev_attr_sensitivity_cptkbd.attr, |
f3d4ff0e JL |
396 | NULL |
397 | }; | |
398 | ||
399 | static const struct attribute_group lenovo_attr_group_cptkbd = { | |
400 | .attrs = lenovo_attributes_cptkbd, | |
401 | }; | |
402 | ||
403 | static int lenovo_raw_event(struct hid_device *hdev, | |
404 | struct hid_report *report, u8 *data, int size) | |
405 | { | |
406 | /* | |
407 | * Compact USB keyboard's Fn-F12 report holds down many other keys, and | |
408 | * its own key is outside the usage page range. Remove extra | |
409 | * keypresses and remap to inside usage page. | |
410 | */ | |
411 | if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD | |
412 | && size == 3 | |
413 | && data[0] == 0x15 | |
414 | && data[1] == 0x94 | |
415 | && data[2] == 0x01)) { | |
5556eb14 JL |
416 | data[1] = 0x00; |
417 | data[2] = 0x01; | |
f3d4ff0e JL |
418 | } |
419 | ||
420 | return 0; | |
421 | } | |
422 | ||
3cb5ff02 JL |
423 | static int lenovo_event_cptkbd(struct hid_device *hdev, |
424 | struct hid_field *field, struct hid_usage *usage, __s32 value) | |
425 | { | |
b72cdfa8 | 426 | struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
3cb5ff02 JL |
427 | |
428 | /* "wheel" scroll events */ | |
429 | if (usage->type == EV_REL && (usage->code == REL_WHEEL || | |
430 | usage->code == REL_HWHEEL)) { | |
431 | /* Scroll events disable middle-click event */ | |
432 | cptkbd_data->middlebutton_state = 2; | |
433 | return 0; | |
434 | } | |
435 | ||
436 | /* Middle click events */ | |
437 | if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) { | |
438 | if (value == 1) { | |
439 | cptkbd_data->middlebutton_state = 1; | |
440 | } else if (value == 0) { | |
441 | if (cptkbd_data->middlebutton_state == 1) { | |
442 | /* No scrolling inbetween, send middle-click */ | |
443 | input_event(field->hidinput->input, | |
444 | EV_KEY, BTN_MIDDLE, 1); | |
445 | input_sync(field->hidinput->input); | |
446 | input_event(field->hidinput->input, | |
447 | EV_KEY, BTN_MIDDLE, 0); | |
448 | input_sync(field->hidinput->input); | |
449 | } | |
450 | cptkbd_data->middlebutton_state = 0; | |
451 | } | |
452 | return 1; | |
453 | } | |
454 | ||
455 | return 0; | |
456 | } | |
457 | ||
458 | static int lenovo_event(struct hid_device *hdev, struct hid_field *field, | |
459 | struct hid_usage *usage, __s32 value) | |
460 | { | |
461 | switch (hdev->product) { | |
462 | case USB_DEVICE_ID_LENOVO_CUSBKBD: | |
463 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
464 | return lenovo_event_cptkbd(hdev, field, usage, value); | |
465 | default: | |
466 | return 0; | |
467 | } | |
468 | } | |
469 | ||
94723bfa | 470 | static int lenovo_features_set_tpkbd(struct hid_device *hdev) |
c1dcad2d BS |
471 | { |
472 | struct hid_report *report; | |
b72cdfa8 | 473 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 474 | |
c1dcad2d BS |
475 | report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; |
476 | ||
477 | report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; | |
478 | report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; | |
479 | report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; | |
480 | report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; | |
481 | report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver | |
482 | report->field[2]->value[0] = data_pointer->sensitivity; | |
483 | report->field[3]->value[0] = data_pointer->press_speed; | |
484 | ||
d8814272 | 485 | hid_hw_request(hdev, report, HID_REQ_SET_REPORT); |
c1dcad2d BS |
486 | return 0; |
487 | } | |
488 | ||
94723bfa | 489 | static ssize_t attr_press_to_select_show_tpkbd(struct device *dev, |
c1dcad2d BS |
490 | struct device_attribute *attr, |
491 | char *buf) | |
492 | { | |
ee79a8f8 | 493 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 494 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
495 | |
496 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); | |
497 | } | |
498 | ||
94723bfa | 499 | static ssize_t attr_press_to_select_store_tpkbd(struct device *dev, |
c1dcad2d BS |
500 | struct device_attribute *attr, |
501 | const char *buf, | |
502 | size_t count) | |
503 | { | |
ee79a8f8 | 504 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 505 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
506 | int value; |
507 | ||
c1dcad2d BS |
508 | if (kstrtoint(buf, 10, &value)) |
509 | return -EINVAL; | |
510 | if (value < 0 || value > 1) | |
511 | return -EINVAL; | |
512 | ||
513 | data_pointer->press_to_select = value; | |
94723bfa | 514 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
515 | |
516 | return count; | |
517 | } | |
518 | ||
94723bfa | 519 | static ssize_t attr_dragging_show_tpkbd(struct device *dev, |
c1dcad2d BS |
520 | struct device_attribute *attr, |
521 | char *buf) | |
522 | { | |
ee79a8f8 | 523 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 524 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
525 | |
526 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); | |
527 | } | |
528 | ||
94723bfa | 529 | static ssize_t attr_dragging_store_tpkbd(struct device *dev, |
c1dcad2d BS |
530 | struct device_attribute *attr, |
531 | const char *buf, | |
532 | size_t count) | |
533 | { | |
ee79a8f8 | 534 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 535 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
536 | int value; |
537 | ||
c1dcad2d BS |
538 | if (kstrtoint(buf, 10, &value)) |
539 | return -EINVAL; | |
540 | if (value < 0 || value > 1) | |
541 | return -EINVAL; | |
542 | ||
543 | data_pointer->dragging = value; | |
94723bfa | 544 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
545 | |
546 | return count; | |
547 | } | |
548 | ||
94723bfa | 549 | static ssize_t attr_release_to_select_show_tpkbd(struct device *dev, |
c1dcad2d BS |
550 | struct device_attribute *attr, |
551 | char *buf) | |
552 | { | |
ee79a8f8 | 553 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 554 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
555 | |
556 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); | |
557 | } | |
558 | ||
94723bfa | 559 | static ssize_t attr_release_to_select_store_tpkbd(struct device *dev, |
c1dcad2d BS |
560 | struct device_attribute *attr, |
561 | const char *buf, | |
562 | size_t count) | |
563 | { | |
ee79a8f8 | 564 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 565 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
566 | int value; |
567 | ||
c1dcad2d BS |
568 | if (kstrtoint(buf, 10, &value)) |
569 | return -EINVAL; | |
570 | if (value < 0 || value > 1) | |
571 | return -EINVAL; | |
572 | ||
573 | data_pointer->release_to_select = value; | |
94723bfa | 574 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
575 | |
576 | return count; | |
577 | } | |
578 | ||
94723bfa | 579 | static ssize_t attr_select_right_show_tpkbd(struct device *dev, |
c1dcad2d BS |
580 | struct device_attribute *attr, |
581 | char *buf) | |
582 | { | |
ee79a8f8 | 583 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 584 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
585 | |
586 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); | |
587 | } | |
588 | ||
94723bfa | 589 | static ssize_t attr_select_right_store_tpkbd(struct device *dev, |
c1dcad2d BS |
590 | struct device_attribute *attr, |
591 | const char *buf, | |
592 | size_t count) | |
593 | { | |
ee79a8f8 | 594 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 595 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
596 | int value; |
597 | ||
c1dcad2d BS |
598 | if (kstrtoint(buf, 10, &value)) |
599 | return -EINVAL; | |
600 | if (value < 0 || value > 1) | |
601 | return -EINVAL; | |
602 | ||
603 | data_pointer->select_right = value; | |
94723bfa | 604 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
605 | |
606 | return count; | |
607 | } | |
608 | ||
94723bfa | 609 | static ssize_t attr_sensitivity_show_tpkbd(struct device *dev, |
c1dcad2d BS |
610 | struct device_attribute *attr, |
611 | char *buf) | |
612 | { | |
ee79a8f8 | 613 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 614 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
615 | |
616 | return snprintf(buf, PAGE_SIZE, "%u\n", | |
617 | data_pointer->sensitivity); | |
618 | } | |
619 | ||
94723bfa | 620 | static ssize_t attr_sensitivity_store_tpkbd(struct device *dev, |
c1dcad2d BS |
621 | struct device_attribute *attr, |
622 | const char *buf, | |
623 | size_t count) | |
624 | { | |
ee79a8f8 | 625 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 626 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
627 | int value; |
628 | ||
c1dcad2d BS |
629 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
630 | return -EINVAL; | |
631 | ||
632 | data_pointer->sensitivity = value; | |
94723bfa | 633 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
634 | |
635 | return count; | |
636 | } | |
637 | ||
94723bfa | 638 | static ssize_t attr_press_speed_show_tpkbd(struct device *dev, |
c1dcad2d BS |
639 | struct device_attribute *attr, |
640 | char *buf) | |
641 | { | |
ee79a8f8 | 642 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 643 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 644 | |
c1dcad2d BS |
645 | return snprintf(buf, PAGE_SIZE, "%u\n", |
646 | data_pointer->press_speed); | |
647 | } | |
648 | ||
94723bfa | 649 | static ssize_t attr_press_speed_store_tpkbd(struct device *dev, |
c1dcad2d BS |
650 | struct device_attribute *attr, |
651 | const char *buf, | |
652 | size_t count) | |
653 | { | |
ee79a8f8 | 654 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 655 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
656 | int value; |
657 | ||
c1dcad2d BS |
658 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
659 | return -EINVAL; | |
660 | ||
661 | data_pointer->press_speed = value; | |
94723bfa | 662 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
663 | |
664 | return count; | |
665 | } | |
666 | ||
94723bfa | 667 | static struct device_attribute dev_attr_press_to_select_tpkbd = |
c1dcad2d | 668 | __ATTR(press_to_select, S_IWUSR | S_IRUGO, |
94723bfa JL |
669 | attr_press_to_select_show_tpkbd, |
670 | attr_press_to_select_store_tpkbd); | |
c1dcad2d | 671 | |
94723bfa | 672 | static struct device_attribute dev_attr_dragging_tpkbd = |
c1dcad2d | 673 | __ATTR(dragging, S_IWUSR | S_IRUGO, |
94723bfa JL |
674 | attr_dragging_show_tpkbd, |
675 | attr_dragging_store_tpkbd); | |
c1dcad2d | 676 | |
94723bfa | 677 | static struct device_attribute dev_attr_release_to_select_tpkbd = |
c1dcad2d | 678 | __ATTR(release_to_select, S_IWUSR | S_IRUGO, |
94723bfa JL |
679 | attr_release_to_select_show_tpkbd, |
680 | attr_release_to_select_store_tpkbd); | |
c1dcad2d | 681 | |
94723bfa | 682 | static struct device_attribute dev_attr_select_right_tpkbd = |
c1dcad2d | 683 | __ATTR(select_right, S_IWUSR | S_IRUGO, |
94723bfa JL |
684 | attr_select_right_show_tpkbd, |
685 | attr_select_right_store_tpkbd); | |
c1dcad2d | 686 | |
94723bfa | 687 | static struct device_attribute dev_attr_sensitivity_tpkbd = |
c1dcad2d | 688 | __ATTR(sensitivity, S_IWUSR | S_IRUGO, |
94723bfa JL |
689 | attr_sensitivity_show_tpkbd, |
690 | attr_sensitivity_store_tpkbd); | |
c1dcad2d | 691 | |
94723bfa | 692 | static struct device_attribute dev_attr_press_speed_tpkbd = |
c1dcad2d | 693 | __ATTR(press_speed, S_IWUSR | S_IRUGO, |
94723bfa JL |
694 | attr_press_speed_show_tpkbd, |
695 | attr_press_speed_store_tpkbd); | |
696 | ||
697 | static struct attribute *lenovo_attributes_tpkbd[] = { | |
698 | &dev_attr_press_to_select_tpkbd.attr, | |
699 | &dev_attr_dragging_tpkbd.attr, | |
700 | &dev_attr_release_to_select_tpkbd.attr, | |
701 | &dev_attr_select_right_tpkbd.attr, | |
702 | &dev_attr_sensitivity_tpkbd.attr, | |
703 | &dev_attr_press_speed_tpkbd.attr, | |
c1dcad2d BS |
704 | NULL |
705 | }; | |
706 | ||
94723bfa JL |
707 | static const struct attribute_group lenovo_attr_group_tpkbd = { |
708 | .attrs = lenovo_attributes_tpkbd, | |
c1dcad2d BS |
709 | }; |
710 | ||
484921f5 HG |
711 | static void lenovo_led_set_tpkbd(struct hid_device *hdev) |
712 | { | |
713 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); | |
714 | struct hid_report *report; | |
715 | ||
716 | report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; | |
717 | report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; | |
718 | report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; | |
719 | hid_hw_request(hdev, report, HID_REQ_SET_REPORT); | |
720 | } | |
721 | ||
722 | static enum led_brightness lenovo_led_brightness_get( | |
c1dcad2d BS |
723 | struct led_classdev *led_cdev) |
724 | { | |
832fbeae | 725 | struct device *dev = led_cdev->dev->parent; |
ee79a8f8 | 726 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 727 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
728 | int led_nr = 0; |
729 | ||
c1dcad2d BS |
730 | if (led_cdev == &data_pointer->led_micmute) |
731 | led_nr = 1; | |
732 | ||
733 | return data_pointer->led_state & (1 << led_nr) | |
734 | ? LED_FULL | |
735 | : LED_OFF; | |
736 | } | |
737 | ||
484921f5 | 738 | static void lenovo_led_brightness_set(struct led_classdev *led_cdev, |
c1dcad2d BS |
739 | enum led_brightness value) |
740 | { | |
832fbeae | 741 | struct device *dev = led_cdev->dev->parent; |
ee79a8f8 | 742 | struct hid_device *hdev = to_hid_device(dev); |
b72cdfa8 | 743 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
bc04b37e | 744 | u8 tp10ubkbd_led[] = { TP10UBKBD_MUTE_LED, TP10UBKBD_MICMUTE_LED }; |
c1dcad2d BS |
745 | int led_nr = 0; |
746 | ||
c1dcad2d BS |
747 | if (led_cdev == &data_pointer->led_micmute) |
748 | led_nr = 1; | |
749 | ||
750 | if (value == LED_OFF) | |
751 | data_pointer->led_state &= ~(1 << led_nr); | |
752 | else | |
753 | data_pointer->led_state |= 1 << led_nr; | |
754 | ||
484921f5 HG |
755 | switch (hdev->product) { |
756 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
757 | lenovo_led_set_tpkbd(hdev); | |
758 | break; | |
bc04b37e HG |
759 | case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
760 | lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); | |
761 | break; | |
484921f5 HG |
762 | } |
763 | } | |
764 | ||
765 | static int lenovo_register_leds(struct hid_device *hdev) | |
766 | { | |
767 | struct lenovo_drvdata *data = hid_get_drvdata(hdev); | |
768 | size_t name_sz = strlen(dev_name(&hdev->dev)) + 16; | |
769 | char *name_mute, *name_micm; | |
770 | int ret; | |
771 | ||
772 | name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); | |
773 | name_micm = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); | |
774 | if (name_mute == NULL || name_micm == NULL) { | |
775 | hid_err(hdev, "Could not allocate memory for led data\n"); | |
776 | return -ENOMEM; | |
777 | } | |
778 | snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(&hdev->dev)); | |
779 | snprintf(name_micm, name_sz, "%s:amber:micmute", dev_name(&hdev->dev)); | |
780 | ||
781 | data->led_mute.name = name_mute; | |
782 | data->led_mute.brightness_get = lenovo_led_brightness_get; | |
783 | data->led_mute.brightness_set = lenovo_led_brightness_set; | |
784 | data->led_mute.dev = &hdev->dev; | |
785 | ret = led_classdev_register(&hdev->dev, &data->led_mute); | |
786 | if (ret < 0) | |
787 | return ret; | |
788 | ||
789 | data->led_micmute.name = name_micm; | |
790 | data->led_micmute.brightness_get = lenovo_led_brightness_get; | |
791 | data->led_micmute.brightness_set = lenovo_led_brightness_set; | |
792 | data->led_micmute.dev = &hdev->dev; | |
793 | ret = led_classdev_register(&hdev->dev, &data->led_micmute); | |
794 | if (ret < 0) { | |
795 | led_classdev_unregister(&data->led_mute); | |
796 | return ret; | |
797 | } | |
798 | ||
799 | return 0; | |
c1dcad2d BS |
800 | } |
801 | ||
94723bfa | 802 | static int lenovo_probe_tpkbd(struct hid_device *hdev) |
c1dcad2d | 803 | { |
b72cdfa8 | 804 | struct lenovo_drvdata *data_pointer; |
484921f5 | 805 | int i, ret; |
0a9cd0a8 | 806 | |
6a5b414b JL |
807 | /* |
808 | * Only register extra settings against subdevice where input_mapping | |
809 | * set drvdata to 1, i.e. the trackpoint. | |
810 | */ | |
811 | if (!hid_get_drvdata(hdev)) | |
812 | return 0; | |
813 | ||
814 | hid_set_drvdata(hdev, NULL); | |
815 | ||
0a9cd0a8 KC |
816 | /* Validate required reports. */ |
817 | for (i = 0; i < 4; i++) { | |
818 | if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1)) | |
819 | return -ENODEV; | |
820 | } | |
821 | if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2)) | |
822 | return -ENODEV; | |
c1dcad2d | 823 | |
e0a6aad6 JL |
824 | ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd); |
825 | if (ret) | |
826 | hid_warn(hdev, "Could not create sysfs group: %d\n", ret); | |
c1dcad2d | 827 | |
01ab35f1 | 828 | data_pointer = devm_kzalloc(&hdev->dev, |
b72cdfa8 | 829 | sizeof(struct lenovo_drvdata), |
01ab35f1 | 830 | GFP_KERNEL); |
c1dcad2d BS |
831 | if (data_pointer == NULL) { |
832 | hid_err(hdev, "Could not allocate memory for driver data\n"); | |
b7212600 AK |
833 | ret = -ENOMEM; |
834 | goto err; | |
c1dcad2d BS |
835 | } |
836 | ||
837 | // set same default values as windows driver | |
838 | data_pointer->sensitivity = 0xa0; | |
839 | data_pointer->press_speed = 0x38; | |
840 | ||
c1dcad2d BS |
841 | hid_set_drvdata(hdev, data_pointer); |
842 | ||
484921f5 HG |
843 | ret = lenovo_register_leds(hdev); |
844 | if (ret) | |
6ae16dfb | 845 | goto err; |
c1dcad2d | 846 | |
94723bfa | 847 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
848 | |
849 | return 0; | |
b7212600 AK |
850 | err: |
851 | sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd); | |
852 | return ret; | |
c1dcad2d BS |
853 | } |
854 | ||
f3d4ff0e JL |
855 | static int lenovo_probe_cptkbd(struct hid_device *hdev) |
856 | { | |
857 | int ret; | |
b72cdfa8 | 858 | struct lenovo_drvdata *cptkbd_data; |
f3d4ff0e JL |
859 | |
860 | /* All the custom action happens on the USBMOUSE device for USB */ | |
861 | if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD | |
862 | && hdev->type != HID_TYPE_USBMOUSE) { | |
863 | hid_dbg(hdev, "Ignoring keyboard half of device\n"); | |
864 | return 0; | |
865 | } | |
866 | ||
867 | cptkbd_data = devm_kzalloc(&hdev->dev, | |
868 | sizeof(*cptkbd_data), | |
869 | GFP_KERNEL); | |
870 | if (cptkbd_data == NULL) { | |
871 | hid_err(hdev, "can't alloc keyboard descriptor\n"); | |
872 | return -ENOMEM; | |
873 | } | |
874 | hid_set_drvdata(hdev, cptkbd_data); | |
875 | ||
876 | /* | |
877 | * Tell the keyboard a driver understands it, and turn F7, F9, F11 into | |
878 | * regular keys | |
879 | */ | |
880 | ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); | |
881 | if (ret) | |
882 | hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); | |
883 | ||
94eefa27 JL |
884 | /* Switch middle button to native mode */ |
885 | ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01); | |
886 | if (ret) | |
887 | hid_warn(hdev, "Failed to switch middle button: %d\n", ret); | |
888 | ||
e3cb0acd | 889 | /* Set keyboard settings to known state */ |
3cb5ff02 | 890 | cptkbd_data->middlebutton_state = 0; |
f3d4ff0e | 891 | cptkbd_data->fn_lock = true; |
e3cb0acd | 892 | cptkbd_data->sensitivity = 0x05; |
f3d4ff0e JL |
893 | lenovo_features_set_cptkbd(hdev); |
894 | ||
895 | ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd); | |
896 | if (ret) | |
897 | hid_warn(hdev, "Could not create sysfs group: %d\n", ret); | |
898 | ||
899 | return 0; | |
900 | } | |
901 | ||
bc04b37e HG |
902 | static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) |
903 | { | |
904 | struct lenovo_drvdata *data; | |
905 | ||
906 | /* All the custom action happens on the USBMOUSE device for USB */ | |
907 | if (hdev->type != HID_TYPE_USBMOUSE) | |
908 | return 0; | |
909 | ||
910 | data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL); | |
911 | if (!data) | |
912 | return -ENOMEM; | |
913 | ||
914 | mutex_init(&data->led_report_mutex); | |
915 | ||
916 | hid_set_drvdata(hdev, data); | |
917 | ||
918 | return lenovo_register_leds(hdev); | |
919 | } | |
920 | ||
94723bfa | 921 | static int lenovo_probe(struct hid_device *hdev, |
c1dcad2d BS |
922 | const struct hid_device_id *id) |
923 | { | |
924 | int ret; | |
c1dcad2d BS |
925 | |
926 | ret = hid_parse(hdev); | |
927 | if (ret) { | |
928 | hid_err(hdev, "hid_parse failed\n"); | |
0ccdd9e7 | 929 | goto err; |
c1dcad2d BS |
930 | } |
931 | ||
932 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | |
933 | if (ret) { | |
934 | hid_err(hdev, "hid_hw_start failed\n"); | |
0ccdd9e7 | 935 | goto err; |
c1dcad2d BS |
936 | } |
937 | ||
6a5b414b JL |
938 | switch (hdev->product) { |
939 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
94723bfa | 940 | ret = lenovo_probe_tpkbd(hdev); |
6a5b414b | 941 | break; |
f3d4ff0e JL |
942 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
943 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
944 | ret = lenovo_probe_cptkbd(hdev); | |
945 | break; | |
bc04b37e HG |
946 | case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
947 | ret = lenovo_probe_tp10ubkbd(hdev); | |
948 | break; | |
6a5b414b JL |
949 | default: |
950 | ret = 0; | |
951 | break; | |
0ccdd9e7 | 952 | } |
6a5b414b JL |
953 | if (ret) |
954 | goto err_hid; | |
c1dcad2d BS |
955 | |
956 | return 0; | |
0ccdd9e7 BT |
957 | err_hid: |
958 | hid_hw_stop(hdev); | |
959 | err: | |
c1dcad2d BS |
960 | return ret; |
961 | } | |
962 | ||
94723bfa | 963 | static void lenovo_remove_tpkbd(struct hid_device *hdev) |
c1dcad2d | 964 | { |
b72cdfa8 | 965 | struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 966 | |
6a5b414b JL |
967 | /* |
968 | * Only the trackpoint half of the keyboard has drvdata and stuff that | |
969 | * needs unregistering. | |
970 | */ | |
971 | if (data_pointer == NULL) | |
972 | return; | |
973 | ||
c1dcad2d | 974 | sysfs_remove_group(&hdev->dev.kobj, |
94723bfa | 975 | &lenovo_attr_group_tpkbd); |
c1dcad2d | 976 | |
c1dcad2d BS |
977 | led_classdev_unregister(&data_pointer->led_micmute); |
978 | led_classdev_unregister(&data_pointer->led_mute); | |
c1dcad2d BS |
979 | } |
980 | ||
f3d4ff0e JL |
981 | static void lenovo_remove_cptkbd(struct hid_device *hdev) |
982 | { | |
983 | sysfs_remove_group(&hdev->dev.kobj, | |
984 | &lenovo_attr_group_cptkbd); | |
985 | } | |
986 | ||
bc04b37e HG |
987 | static void lenovo_remove_tp10ubkbd(struct hid_device *hdev) |
988 | { | |
989 | struct lenovo_drvdata *data = hid_get_drvdata(hdev); | |
990 | ||
991 | if (data == NULL) | |
992 | return; | |
993 | ||
994 | led_classdev_unregister(&data->led_micmute); | |
995 | led_classdev_unregister(&data->led_mute); | |
996 | } | |
997 | ||
94723bfa | 998 | static void lenovo_remove(struct hid_device *hdev) |
c1dcad2d | 999 | { |
6a5b414b JL |
1000 | switch (hdev->product) { |
1001 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
94723bfa | 1002 | lenovo_remove_tpkbd(hdev); |
6a5b414b | 1003 | break; |
f3d4ff0e JL |
1004 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
1005 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
1006 | lenovo_remove_cptkbd(hdev); | |
1007 | break; | |
bc04b37e HG |
1008 | case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
1009 | lenovo_remove_tp10ubkbd(hdev); | |
1010 | break; | |
6a5b414b | 1011 | } |
c1dcad2d BS |
1012 | |
1013 | hid_hw_stop(hdev); | |
1014 | } | |
1015 | ||
9154301a | 1016 | static int lenovo_input_configured(struct hid_device *hdev, |
d92189eb AF |
1017 | struct hid_input *hi) |
1018 | { | |
1019 | switch (hdev->product) { | |
1020 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
1021 | case USB_DEVICE_ID_LENOVO_CUSBKBD: | |
1022 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
1023 | if (test_bit(EV_REL, hi->input->evbit)) { | |
1024 | /* set only for trackpoint device */ | |
1025 | __set_bit(INPUT_PROP_POINTER, hi->input->propbit); | |
1026 | __set_bit(INPUT_PROP_POINTING_STICK, | |
1027 | hi->input->propbit); | |
1028 | } | |
1029 | break; | |
1030 | } | |
9154301a DT |
1031 | |
1032 | return 0; | |
d92189eb AF |
1033 | } |
1034 | ||
1035 | ||
94723bfa | 1036 | static const struct hid_device_id lenovo_devices[] = { |
c1dcad2d | 1037 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, |
f3d4ff0e JL |
1038 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) }, |
1039 | { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) }, | |
181a8b91 | 1040 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) }, |
a230cd52 | 1041 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_III) }, |
1042 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_PRO) }, | |
1043 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL) }, | |
1044 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) }, | |
1045 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) }, | |
1046 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) }, | |
bc04b37e | 1047 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TP10UBKBD) }, |
c1dcad2d BS |
1048 | { } |
1049 | }; | |
1050 | ||
94723bfa | 1051 | MODULE_DEVICE_TABLE(hid, lenovo_devices); |
c1dcad2d | 1052 | |
94723bfa JL |
1053 | static struct hid_driver lenovo_driver = { |
1054 | .name = "lenovo", | |
1055 | .id_table = lenovo_devices, | |
d92189eb | 1056 | .input_configured = lenovo_input_configured, |
6a5b414b | 1057 | .input_mapping = lenovo_input_mapping, |
94723bfa JL |
1058 | .probe = lenovo_probe, |
1059 | .remove = lenovo_remove, | |
f3d4ff0e | 1060 | .raw_event = lenovo_raw_event, |
3cb5ff02 | 1061 | .event = lenovo_event, |
181a8b91 | 1062 | .report_fixup = lenovo_report_fixup, |
c1dcad2d | 1063 | }; |
94723bfa | 1064 | module_hid_driver(lenovo_driver); |
c1dcad2d BS |
1065 | |
1066 | MODULE_LICENSE("GPL"); |