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