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 <jm@lentin.co.uk> |
c1dcad2d BS |
9 | */ |
10 | ||
11 | /* | |
12 | * This program is free software; you can redistribute it and/or modify it | |
13 | * under the terms of the GNU General Public License as published by the Free | |
14 | * Software Foundation; either version 2 of the License, or (at your option) | |
15 | * any later version. | |
16 | */ | |
17 | ||
18 | #include <linux/module.h> | |
19 | #include <linux/sysfs.h> | |
20 | #include <linux/device.h> | |
c1dcad2d BS |
21 | #include <linux/hid.h> |
22 | #include <linux/input.h> | |
23 | #include <linux/leds.h> | |
c1dcad2d BS |
24 | |
25 | #include "hid-ids.h" | |
26 | ||
94723bfa | 27 | struct lenovo_drvdata_tpkbd { |
c1dcad2d BS |
28 | int led_state; |
29 | struct led_classdev led_mute; | |
30 | struct led_classdev led_micmute; | |
31 | int press_to_select; | |
32 | int dragging; | |
33 | int release_to_select; | |
34 | int select_right; | |
35 | int sensitivity; | |
36 | int press_speed; | |
37 | }; | |
38 | ||
f3d4ff0e JL |
39 | struct lenovo_drvdata_cptkbd { |
40 | bool fn_lock; | |
41 | }; | |
42 | ||
c1dcad2d BS |
43 | #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) |
44 | ||
94723bfa | 45 | static int lenovo_input_mapping_tpkbd(struct hid_device *hdev, |
c1dcad2d BS |
46 | struct hid_input *hi, struct hid_field *field, |
47 | struct hid_usage *usage, unsigned long **bit, int *max) | |
48 | { | |
0c521836 | 49 | if (usage->hid == (HID_UP_BUTTON | 0x0010)) { |
6a5b414b | 50 | /* This sub-device contains trackpoint, mark it */ |
0c521836 | 51 | hid_set_drvdata(hdev, (void *)1); |
c1dcad2d BS |
52 | map_key_clear(KEY_MICMUTE); |
53 | return 1; | |
54 | } | |
55 | return 0; | |
56 | } | |
57 | ||
f3d4ff0e JL |
58 | static int lenovo_input_mapping_cptkbd(struct hid_device *hdev, |
59 | struct hid_input *hi, struct hid_field *field, | |
60 | struct hid_usage *usage, unsigned long **bit, int *max) | |
61 | { | |
62 | /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */ | |
63 | if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR || | |
64 | (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) { | |
65 | set_bit(EV_REP, hi->input->evbit); | |
66 | switch (usage->hid & HID_USAGE) { | |
67 | case 0x00f1: /* Fn-F4: Mic mute */ | |
68 | map_key_clear(KEY_MICMUTE); | |
69 | return 1; | |
70 | case 0x00f2: /* Fn-F5: Brightness down */ | |
71 | map_key_clear(KEY_BRIGHTNESSDOWN); | |
72 | return 1; | |
73 | case 0x00f3: /* Fn-F6: Brightness up */ | |
74 | map_key_clear(KEY_BRIGHTNESSUP); | |
75 | return 1; | |
76 | case 0x00f4: /* Fn-F7: External display (projector) */ | |
77 | map_key_clear(KEY_SWITCHVIDEOMODE); | |
78 | return 1; | |
79 | case 0x00f5: /* Fn-F8: Wireless */ | |
80 | map_key_clear(KEY_WLAN); | |
81 | return 1; | |
82 | case 0x00f6: /* Fn-F9: Control panel */ | |
83 | map_key_clear(KEY_CONFIG); | |
84 | return 1; | |
85 | case 0x00f8: /* Fn-F11: View open applications (3 boxes) */ | |
86 | map_key_clear(KEY_SCALE); | |
87 | return 1; | |
88 | case 0x00fa: /* Fn-Esc: Fn-lock toggle */ | |
89 | map_key_clear(KEY_FN_ESC); | |
90 | return 1; | |
91 | case 0x00fb: /* Fn-F12: Open My computer (6 boxes) USB-only */ | |
92 | /* NB: This mapping is invented in raw_event below */ | |
93 | map_key_clear(KEY_FILE); | |
94 | return 1; | |
95 | } | |
96 | } | |
97 | ||
98 | return 0; | |
99 | } | |
100 | ||
6a5b414b JL |
101 | static int lenovo_input_mapping(struct hid_device *hdev, |
102 | struct hid_input *hi, struct hid_field *field, | |
103 | struct hid_usage *usage, unsigned long **bit, int *max) | |
104 | { | |
105 | switch (hdev->product) { | |
106 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
107 | return lenovo_input_mapping_tpkbd(hdev, hi, field, | |
108 | usage, bit, max); | |
f3d4ff0e JL |
109 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
110 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
111 | return lenovo_input_mapping_cptkbd(hdev, hi, field, | |
112 | usage, bit, max); | |
6a5b414b JL |
113 | default: |
114 | return 0; | |
115 | } | |
116 | } | |
117 | ||
c1dcad2d BS |
118 | #undef map_key_clear |
119 | ||
f3d4ff0e JL |
120 | /* Send a config command to the keyboard */ |
121 | static int lenovo_send_cmd_cptkbd(struct hid_device *hdev, | |
122 | unsigned char byte2, unsigned char byte3) | |
123 | { | |
124 | int ret; | |
125 | unsigned char buf[] = {0x18, byte2, byte3}; | |
126 | ||
127 | switch (hdev->product) { | |
128 | case USB_DEVICE_ID_LENOVO_CUSBKBD: | |
129 | ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf), | |
130 | HID_FEATURE_REPORT, HID_REQ_SET_REPORT); | |
131 | break; | |
132 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
133 | ret = hid_hw_output_report(hdev, buf, sizeof(buf)); | |
134 | break; | |
135 | default: | |
136 | ret = -EINVAL; | |
137 | break; | |
138 | } | |
139 | ||
140 | return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */ | |
141 | } | |
142 | ||
143 | static void lenovo_features_set_cptkbd(struct hid_device *hdev) | |
144 | { | |
145 | int ret; | |
146 | struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); | |
147 | ||
148 | ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); | |
149 | if (ret) | |
150 | hid_err(hdev, "Fn-lock setting failed: %d\n", ret); | |
151 | } | |
152 | ||
153 | static ssize_t attr_fn_lock_show_cptkbd(struct device *dev, | |
154 | struct device_attribute *attr, | |
155 | char *buf) | |
156 | { | |
157 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); | |
158 | struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); | |
159 | ||
160 | return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock); | |
161 | } | |
162 | ||
163 | static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, | |
164 | struct device_attribute *attr, | |
165 | const char *buf, | |
166 | size_t count) | |
167 | { | |
168 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); | |
169 | struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); | |
170 | int value; | |
171 | ||
172 | if (kstrtoint(buf, 10, &value)) | |
173 | return -EINVAL; | |
174 | if (value < 0 || value > 1) | |
175 | return -EINVAL; | |
176 | ||
177 | cptkbd_data->fn_lock = !!value; | |
178 | lenovo_features_set_cptkbd(hdev); | |
179 | ||
180 | return count; | |
181 | } | |
182 | ||
183 | static struct device_attribute dev_attr_fn_lock_cptkbd = | |
184 | __ATTR(fn_lock, S_IWUSR | S_IRUGO, | |
185 | attr_fn_lock_show_cptkbd, | |
186 | attr_fn_lock_store_cptkbd); | |
187 | ||
188 | static struct attribute *lenovo_attributes_cptkbd[] = { | |
189 | &dev_attr_fn_lock_cptkbd.attr, | |
190 | NULL | |
191 | }; | |
192 | ||
193 | static const struct attribute_group lenovo_attr_group_cptkbd = { | |
194 | .attrs = lenovo_attributes_cptkbd, | |
195 | }; | |
196 | ||
197 | static int lenovo_raw_event(struct hid_device *hdev, | |
198 | struct hid_report *report, u8 *data, int size) | |
199 | { | |
200 | /* | |
201 | * Compact USB keyboard's Fn-F12 report holds down many other keys, and | |
202 | * its own key is outside the usage page range. Remove extra | |
203 | * keypresses and remap to inside usage page. | |
204 | */ | |
205 | if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD | |
206 | && size == 3 | |
207 | && data[0] == 0x15 | |
208 | && data[1] == 0x94 | |
209 | && data[2] == 0x01)) { | |
210 | data[1] = 0x0; | |
211 | data[2] = 0x4; | |
212 | } | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
94723bfa | 217 | static int lenovo_features_set_tpkbd(struct hid_device *hdev) |
c1dcad2d BS |
218 | { |
219 | struct hid_report *report; | |
94723bfa | 220 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 221 | |
c1dcad2d BS |
222 | report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; |
223 | ||
224 | report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; | |
225 | report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; | |
226 | report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; | |
227 | report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; | |
228 | report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver | |
229 | report->field[2]->value[0] = data_pointer->sensitivity; | |
230 | report->field[3]->value[0] = data_pointer->press_speed; | |
231 | ||
d8814272 | 232 | hid_hw_request(hdev, report, HID_REQ_SET_REPORT); |
c1dcad2d BS |
233 | return 0; |
234 | } | |
235 | ||
94723bfa | 236 | static ssize_t attr_press_to_select_show_tpkbd(struct device *dev, |
c1dcad2d BS |
237 | struct device_attribute *attr, |
238 | char *buf) | |
239 | { | |
832fbeae | 240 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 241 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
242 | |
243 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); | |
244 | } | |
245 | ||
94723bfa | 246 | static ssize_t attr_press_to_select_store_tpkbd(struct device *dev, |
c1dcad2d BS |
247 | struct device_attribute *attr, |
248 | const char *buf, | |
249 | size_t count) | |
250 | { | |
832fbeae | 251 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 252 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
253 | int value; |
254 | ||
c1dcad2d BS |
255 | if (kstrtoint(buf, 10, &value)) |
256 | return -EINVAL; | |
257 | if (value < 0 || value > 1) | |
258 | return -EINVAL; | |
259 | ||
260 | data_pointer->press_to_select = value; | |
94723bfa | 261 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
262 | |
263 | return count; | |
264 | } | |
265 | ||
94723bfa | 266 | static ssize_t attr_dragging_show_tpkbd(struct device *dev, |
c1dcad2d BS |
267 | struct device_attribute *attr, |
268 | char *buf) | |
269 | { | |
832fbeae | 270 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 271 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
272 | |
273 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); | |
274 | } | |
275 | ||
94723bfa | 276 | static ssize_t attr_dragging_store_tpkbd(struct device *dev, |
c1dcad2d BS |
277 | struct device_attribute *attr, |
278 | const char *buf, | |
279 | size_t count) | |
280 | { | |
832fbeae | 281 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 282 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
283 | int value; |
284 | ||
c1dcad2d BS |
285 | if (kstrtoint(buf, 10, &value)) |
286 | return -EINVAL; | |
287 | if (value < 0 || value > 1) | |
288 | return -EINVAL; | |
289 | ||
290 | data_pointer->dragging = value; | |
94723bfa | 291 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
292 | |
293 | return count; | |
294 | } | |
295 | ||
94723bfa | 296 | static ssize_t attr_release_to_select_show_tpkbd(struct device *dev, |
c1dcad2d BS |
297 | struct device_attribute *attr, |
298 | char *buf) | |
299 | { | |
832fbeae | 300 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 301 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
302 | |
303 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); | |
304 | } | |
305 | ||
94723bfa | 306 | static ssize_t attr_release_to_select_store_tpkbd(struct device *dev, |
c1dcad2d BS |
307 | struct device_attribute *attr, |
308 | const char *buf, | |
309 | size_t count) | |
310 | { | |
832fbeae | 311 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 312 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
313 | int value; |
314 | ||
c1dcad2d BS |
315 | if (kstrtoint(buf, 10, &value)) |
316 | return -EINVAL; | |
317 | if (value < 0 || value > 1) | |
318 | return -EINVAL; | |
319 | ||
320 | data_pointer->release_to_select = value; | |
94723bfa | 321 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
322 | |
323 | return count; | |
324 | } | |
325 | ||
94723bfa | 326 | static ssize_t attr_select_right_show_tpkbd(struct device *dev, |
c1dcad2d BS |
327 | struct device_attribute *attr, |
328 | char *buf) | |
329 | { | |
832fbeae | 330 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 331 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
332 | |
333 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); | |
334 | } | |
335 | ||
94723bfa | 336 | static ssize_t attr_select_right_store_tpkbd(struct device *dev, |
c1dcad2d BS |
337 | struct device_attribute *attr, |
338 | const char *buf, | |
339 | size_t count) | |
340 | { | |
832fbeae | 341 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 342 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
343 | int value; |
344 | ||
c1dcad2d BS |
345 | if (kstrtoint(buf, 10, &value)) |
346 | return -EINVAL; | |
347 | if (value < 0 || value > 1) | |
348 | return -EINVAL; | |
349 | ||
350 | data_pointer->select_right = value; | |
94723bfa | 351 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
352 | |
353 | return count; | |
354 | } | |
355 | ||
94723bfa | 356 | static ssize_t attr_sensitivity_show_tpkbd(struct device *dev, |
c1dcad2d BS |
357 | struct device_attribute *attr, |
358 | char *buf) | |
359 | { | |
832fbeae | 360 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 361 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
362 | |
363 | return snprintf(buf, PAGE_SIZE, "%u\n", | |
364 | data_pointer->sensitivity); | |
365 | } | |
366 | ||
94723bfa | 367 | static ssize_t attr_sensitivity_store_tpkbd(struct device *dev, |
c1dcad2d BS |
368 | struct device_attribute *attr, |
369 | const char *buf, | |
370 | size_t count) | |
371 | { | |
832fbeae | 372 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 373 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
374 | int value; |
375 | ||
c1dcad2d BS |
376 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
377 | return -EINVAL; | |
378 | ||
379 | data_pointer->sensitivity = value; | |
94723bfa | 380 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
381 | |
382 | return count; | |
383 | } | |
384 | ||
94723bfa | 385 | static ssize_t attr_press_speed_show_tpkbd(struct device *dev, |
c1dcad2d BS |
386 | struct device_attribute *attr, |
387 | char *buf) | |
388 | { | |
832fbeae | 389 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 390 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 391 | |
c1dcad2d BS |
392 | return snprintf(buf, PAGE_SIZE, "%u\n", |
393 | data_pointer->press_speed); | |
394 | } | |
395 | ||
94723bfa | 396 | static ssize_t attr_press_speed_store_tpkbd(struct device *dev, |
c1dcad2d BS |
397 | struct device_attribute *attr, |
398 | const char *buf, | |
399 | size_t count) | |
400 | { | |
832fbeae | 401 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); |
94723bfa | 402 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
403 | int value; |
404 | ||
c1dcad2d BS |
405 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
406 | return -EINVAL; | |
407 | ||
408 | data_pointer->press_speed = value; | |
94723bfa | 409 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
410 | |
411 | return count; | |
412 | } | |
413 | ||
94723bfa | 414 | static struct device_attribute dev_attr_press_to_select_tpkbd = |
c1dcad2d | 415 | __ATTR(press_to_select, S_IWUSR | S_IRUGO, |
94723bfa JL |
416 | attr_press_to_select_show_tpkbd, |
417 | attr_press_to_select_store_tpkbd); | |
c1dcad2d | 418 | |
94723bfa | 419 | static struct device_attribute dev_attr_dragging_tpkbd = |
c1dcad2d | 420 | __ATTR(dragging, S_IWUSR | S_IRUGO, |
94723bfa JL |
421 | attr_dragging_show_tpkbd, |
422 | attr_dragging_store_tpkbd); | |
c1dcad2d | 423 | |
94723bfa | 424 | static struct device_attribute dev_attr_release_to_select_tpkbd = |
c1dcad2d | 425 | __ATTR(release_to_select, S_IWUSR | S_IRUGO, |
94723bfa JL |
426 | attr_release_to_select_show_tpkbd, |
427 | attr_release_to_select_store_tpkbd); | |
c1dcad2d | 428 | |
94723bfa | 429 | static struct device_attribute dev_attr_select_right_tpkbd = |
c1dcad2d | 430 | __ATTR(select_right, S_IWUSR | S_IRUGO, |
94723bfa JL |
431 | attr_select_right_show_tpkbd, |
432 | attr_select_right_store_tpkbd); | |
c1dcad2d | 433 | |
94723bfa | 434 | static struct device_attribute dev_attr_sensitivity_tpkbd = |
c1dcad2d | 435 | __ATTR(sensitivity, S_IWUSR | S_IRUGO, |
94723bfa JL |
436 | attr_sensitivity_show_tpkbd, |
437 | attr_sensitivity_store_tpkbd); | |
c1dcad2d | 438 | |
94723bfa | 439 | static struct device_attribute dev_attr_press_speed_tpkbd = |
c1dcad2d | 440 | __ATTR(press_speed, S_IWUSR | S_IRUGO, |
94723bfa JL |
441 | attr_press_speed_show_tpkbd, |
442 | attr_press_speed_store_tpkbd); | |
443 | ||
444 | static struct attribute *lenovo_attributes_tpkbd[] = { | |
445 | &dev_attr_press_to_select_tpkbd.attr, | |
446 | &dev_attr_dragging_tpkbd.attr, | |
447 | &dev_attr_release_to_select_tpkbd.attr, | |
448 | &dev_attr_select_right_tpkbd.attr, | |
449 | &dev_attr_sensitivity_tpkbd.attr, | |
450 | &dev_attr_press_speed_tpkbd.attr, | |
c1dcad2d BS |
451 | NULL |
452 | }; | |
453 | ||
94723bfa JL |
454 | static const struct attribute_group lenovo_attr_group_tpkbd = { |
455 | .attrs = lenovo_attributes_tpkbd, | |
c1dcad2d BS |
456 | }; |
457 | ||
94723bfa | 458 | static enum led_brightness lenovo_led_brightness_get_tpkbd( |
c1dcad2d BS |
459 | struct led_classdev *led_cdev) |
460 | { | |
832fbeae AL |
461 | struct device *dev = led_cdev->dev->parent; |
462 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); | |
94723bfa | 463 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d BS |
464 | int led_nr = 0; |
465 | ||
c1dcad2d BS |
466 | if (led_cdev == &data_pointer->led_micmute) |
467 | led_nr = 1; | |
468 | ||
469 | return data_pointer->led_state & (1 << led_nr) | |
470 | ? LED_FULL | |
471 | : LED_OFF; | |
472 | } | |
473 | ||
94723bfa | 474 | static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, |
c1dcad2d BS |
475 | enum led_brightness value) |
476 | { | |
832fbeae AL |
477 | struct device *dev = led_cdev->dev->parent; |
478 | struct hid_device *hdev = container_of(dev, struct hid_device, dev); | |
94723bfa | 479 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 480 | struct hid_report *report; |
c1dcad2d BS |
481 | int led_nr = 0; |
482 | ||
c1dcad2d BS |
483 | if (led_cdev == &data_pointer->led_micmute) |
484 | led_nr = 1; | |
485 | ||
486 | if (value == LED_OFF) | |
487 | data_pointer->led_state &= ~(1 << led_nr); | |
488 | else | |
489 | data_pointer->led_state |= 1 << led_nr; | |
490 | ||
491 | report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; | |
492 | report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; | |
493 | report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; | |
d8814272 | 494 | hid_hw_request(hdev, report, HID_REQ_SET_REPORT); |
c1dcad2d BS |
495 | } |
496 | ||
94723bfa | 497 | static int lenovo_probe_tpkbd(struct hid_device *hdev) |
c1dcad2d BS |
498 | { |
499 | struct device *dev = &hdev->dev; | |
94723bfa | 500 | struct lenovo_drvdata_tpkbd *data_pointer; |
c1dcad2d BS |
501 | size_t name_sz = strlen(dev_name(dev)) + 16; |
502 | char *name_mute, *name_micmute; | |
01ab35f1 | 503 | int i; |
e0a6aad6 | 504 | int ret; |
0a9cd0a8 | 505 | |
6a5b414b JL |
506 | /* |
507 | * Only register extra settings against subdevice where input_mapping | |
508 | * set drvdata to 1, i.e. the trackpoint. | |
509 | */ | |
510 | if (!hid_get_drvdata(hdev)) | |
511 | return 0; | |
512 | ||
513 | hid_set_drvdata(hdev, NULL); | |
514 | ||
0a9cd0a8 KC |
515 | /* Validate required reports. */ |
516 | for (i = 0; i < 4; i++) { | |
517 | if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1)) | |
518 | return -ENODEV; | |
519 | } | |
520 | if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2)) | |
521 | return -ENODEV; | |
c1dcad2d | 522 | |
e0a6aad6 JL |
523 | ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd); |
524 | if (ret) | |
525 | hid_warn(hdev, "Could not create sysfs group: %d\n", ret); | |
c1dcad2d | 526 | |
01ab35f1 | 527 | data_pointer = devm_kzalloc(&hdev->dev, |
94723bfa | 528 | sizeof(struct lenovo_drvdata_tpkbd), |
01ab35f1 | 529 | GFP_KERNEL); |
c1dcad2d BS |
530 | if (data_pointer == NULL) { |
531 | hid_err(hdev, "Could not allocate memory for driver data\n"); | |
532 | return -ENOMEM; | |
533 | } | |
534 | ||
535 | // set same default values as windows driver | |
536 | data_pointer->sensitivity = 0xa0; | |
537 | data_pointer->press_speed = 0x38; | |
538 | ||
01ab35f1 BT |
539 | name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); |
540 | name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); | |
541 | if (name_mute == NULL || name_micmute == NULL) { | |
c1dcad2d | 542 | hid_err(hdev, "Could not allocate memory for led data\n"); |
01ab35f1 | 543 | return -ENOMEM; |
c1dcad2d BS |
544 | } |
545 | snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); | |
c1dcad2d BS |
546 | snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); |
547 | ||
548 | hid_set_drvdata(hdev, data_pointer); | |
549 | ||
550 | data_pointer->led_mute.name = name_mute; | |
94723bfa JL |
551 | data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd; |
552 | data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd; | |
c1dcad2d BS |
553 | data_pointer->led_mute.dev = dev; |
554 | led_classdev_register(dev, &data_pointer->led_mute); | |
555 | ||
556 | data_pointer->led_micmute.name = name_micmute; | |
94723bfa JL |
557 | data_pointer->led_micmute.brightness_get = |
558 | lenovo_led_brightness_get_tpkbd; | |
559 | data_pointer->led_micmute.brightness_set = | |
560 | lenovo_led_brightness_set_tpkbd; | |
c1dcad2d BS |
561 | data_pointer->led_micmute.dev = dev; |
562 | led_classdev_register(dev, &data_pointer->led_micmute); | |
563 | ||
94723bfa | 564 | lenovo_features_set_tpkbd(hdev); |
c1dcad2d BS |
565 | |
566 | return 0; | |
c1dcad2d BS |
567 | } |
568 | ||
f3d4ff0e JL |
569 | static int lenovo_probe_cptkbd(struct hid_device *hdev) |
570 | { | |
571 | int ret; | |
572 | struct lenovo_drvdata_cptkbd *cptkbd_data; | |
573 | ||
574 | /* All the custom action happens on the USBMOUSE device for USB */ | |
575 | if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD | |
576 | && hdev->type != HID_TYPE_USBMOUSE) { | |
577 | hid_dbg(hdev, "Ignoring keyboard half of device\n"); | |
578 | return 0; | |
579 | } | |
580 | ||
581 | cptkbd_data = devm_kzalloc(&hdev->dev, | |
582 | sizeof(*cptkbd_data), | |
583 | GFP_KERNEL); | |
584 | if (cptkbd_data == NULL) { | |
585 | hid_err(hdev, "can't alloc keyboard descriptor\n"); | |
586 | return -ENOMEM; | |
587 | } | |
588 | hid_set_drvdata(hdev, cptkbd_data); | |
589 | ||
590 | /* | |
591 | * Tell the keyboard a driver understands it, and turn F7, F9, F11 into | |
592 | * regular keys | |
593 | */ | |
594 | ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); | |
595 | if (ret) | |
596 | hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); | |
597 | ||
598 | /* Turn Fn-Lock on by default */ | |
599 | cptkbd_data->fn_lock = true; | |
600 | lenovo_features_set_cptkbd(hdev); | |
601 | ||
602 | ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd); | |
603 | if (ret) | |
604 | hid_warn(hdev, "Could not create sysfs group: %d\n", ret); | |
605 | ||
606 | return 0; | |
607 | } | |
608 | ||
94723bfa | 609 | static int lenovo_probe(struct hid_device *hdev, |
c1dcad2d BS |
610 | const struct hid_device_id *id) |
611 | { | |
612 | int ret; | |
c1dcad2d BS |
613 | |
614 | ret = hid_parse(hdev); | |
615 | if (ret) { | |
616 | hid_err(hdev, "hid_parse failed\n"); | |
0ccdd9e7 | 617 | goto err; |
c1dcad2d BS |
618 | } |
619 | ||
620 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | |
621 | if (ret) { | |
622 | hid_err(hdev, "hid_hw_start failed\n"); | |
0ccdd9e7 | 623 | goto err; |
c1dcad2d BS |
624 | } |
625 | ||
6a5b414b JL |
626 | switch (hdev->product) { |
627 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
94723bfa | 628 | ret = lenovo_probe_tpkbd(hdev); |
6a5b414b | 629 | break; |
f3d4ff0e JL |
630 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
631 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
632 | ret = lenovo_probe_cptkbd(hdev); | |
633 | break; | |
6a5b414b JL |
634 | default: |
635 | ret = 0; | |
636 | break; | |
0ccdd9e7 | 637 | } |
6a5b414b JL |
638 | if (ret) |
639 | goto err_hid; | |
c1dcad2d BS |
640 | |
641 | return 0; | |
0ccdd9e7 BT |
642 | err_hid: |
643 | hid_hw_stop(hdev); | |
644 | err: | |
c1dcad2d BS |
645 | return ret; |
646 | } | |
647 | ||
94723bfa | 648 | static void lenovo_remove_tpkbd(struct hid_device *hdev) |
c1dcad2d | 649 | { |
94723bfa | 650 | struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
c1dcad2d | 651 | |
6a5b414b JL |
652 | /* |
653 | * Only the trackpoint half of the keyboard has drvdata and stuff that | |
654 | * needs unregistering. | |
655 | */ | |
656 | if (data_pointer == NULL) | |
657 | return; | |
658 | ||
c1dcad2d | 659 | sysfs_remove_group(&hdev->dev.kobj, |
94723bfa | 660 | &lenovo_attr_group_tpkbd); |
c1dcad2d | 661 | |
c1dcad2d BS |
662 | led_classdev_unregister(&data_pointer->led_micmute); |
663 | led_classdev_unregister(&data_pointer->led_mute); | |
664 | ||
665 | hid_set_drvdata(hdev, NULL); | |
c1dcad2d BS |
666 | } |
667 | ||
f3d4ff0e JL |
668 | static void lenovo_remove_cptkbd(struct hid_device *hdev) |
669 | { | |
670 | sysfs_remove_group(&hdev->dev.kobj, | |
671 | &lenovo_attr_group_cptkbd); | |
672 | } | |
673 | ||
94723bfa | 674 | static void lenovo_remove(struct hid_device *hdev) |
c1dcad2d | 675 | { |
6a5b414b JL |
676 | switch (hdev->product) { |
677 | case USB_DEVICE_ID_LENOVO_TPKBD: | |
94723bfa | 678 | lenovo_remove_tpkbd(hdev); |
6a5b414b | 679 | break; |
f3d4ff0e JL |
680 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
681 | case USB_DEVICE_ID_LENOVO_CBTKBD: | |
682 | lenovo_remove_cptkbd(hdev); | |
683 | break; | |
6a5b414b | 684 | } |
c1dcad2d BS |
685 | |
686 | hid_hw_stop(hdev); | |
687 | } | |
688 | ||
94723bfa | 689 | static const struct hid_device_id lenovo_devices[] = { |
c1dcad2d | 690 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, |
f3d4ff0e JL |
691 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) }, |
692 | { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) }, | |
c1dcad2d BS |
693 | { } |
694 | }; | |
695 | ||
94723bfa | 696 | MODULE_DEVICE_TABLE(hid, lenovo_devices); |
c1dcad2d | 697 | |
94723bfa JL |
698 | static struct hid_driver lenovo_driver = { |
699 | .name = "lenovo", | |
700 | .id_table = lenovo_devices, | |
6a5b414b | 701 | .input_mapping = lenovo_input_mapping, |
94723bfa JL |
702 | .probe = lenovo_probe, |
703 | .remove = lenovo_remove, | |
f3d4ff0e | 704 | .raw_event = lenovo_raw_event, |
c1dcad2d | 705 | }; |
94723bfa | 706 | module_hid_driver(lenovo_driver); |
c1dcad2d BS |
707 | |
708 | MODULE_LICENSE("GPL"); |