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