| .. | .. |
|---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
|---|
| 1 | 2 | /* |
|---|
| 2 | 3 | * HID driver for Lenovo: |
|---|
| 3 | 4 | * - ThinkPad USB Keyboard with TrackPoint (tpkbd) |
|---|
| .. | .. |
|---|
| 20 | 21 | */ |
|---|
| 21 | 22 | |
|---|
| 22 | 23 | /* |
|---|
| 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 | 24 | */ |
|---|
| 28 | 25 | |
|---|
| 29 | 26 | #include <linux/module.h> |
|---|
| .. | .. |
|---|
| 32 | 29 | #include <linux/hid.h> |
|---|
| 33 | 30 | #include <linux/input.h> |
|---|
| 34 | 31 | #include <linux/leds.h> |
|---|
| 32 | +#include <linux/workqueue.h> |
|---|
| 35 | 33 | |
|---|
| 36 | 34 | #include "hid-ids.h" |
|---|
| 37 | 35 | |
|---|
| 38 | | -struct lenovo_drvdata_tpkbd { |
|---|
| 36 | +/* Userspace expects F20 for mic-mute KEY_MICMUTE does not work */ |
|---|
| 37 | +#define LENOVO_KEY_MICMUTE KEY_F20 |
|---|
| 38 | + |
|---|
| 39 | +struct lenovo_drvdata { |
|---|
| 40 | + u8 led_report[3]; /* Must be first for proper alignment */ |
|---|
| 39 | 41 | int led_state; |
|---|
| 42 | + struct mutex led_report_mutex; |
|---|
| 40 | 43 | struct led_classdev led_mute; |
|---|
| 41 | 44 | struct led_classdev led_micmute; |
|---|
| 45 | + struct work_struct fn_lock_sync_work; |
|---|
| 46 | + struct hid_device *hdev; |
|---|
| 42 | 47 | int press_to_select; |
|---|
| 43 | 48 | int dragging; |
|---|
| 44 | 49 | int release_to_select; |
|---|
| 45 | 50 | int select_right; |
|---|
| 46 | 51 | int sensitivity; |
|---|
| 47 | 52 | int press_speed; |
|---|
| 48 | | -}; |
|---|
| 49 | | - |
|---|
| 50 | | -struct lenovo_drvdata_cptkbd { |
|---|
| 51 | 53 | u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */ |
|---|
| 52 | 54 | bool fn_lock; |
|---|
| 53 | | - int sensitivity; |
|---|
| 54 | 55 | }; |
|---|
| 55 | 56 | |
|---|
| 56 | 57 | #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) |
|---|
| 58 | + |
|---|
| 59 | +#define TP10UBKBD_LED_OUTPUT_REPORT 9 |
|---|
| 60 | + |
|---|
| 61 | +#define TP10UBKBD_FN_LOCK_LED 0x54 |
|---|
| 62 | +#define TP10UBKBD_MUTE_LED 0x64 |
|---|
| 63 | +#define TP10UBKBD_MICMUTE_LED 0x74 |
|---|
| 64 | + |
|---|
| 65 | +#define TP10UBKBD_LED_OFF 1 |
|---|
| 66 | +#define TP10UBKBD_LED_ON 2 |
|---|
| 67 | + |
|---|
| 68 | +static int lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code, |
|---|
| 69 | + enum led_brightness value) |
|---|
| 70 | +{ |
|---|
| 71 | + struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
|---|
| 72 | + int ret; |
|---|
| 73 | + |
|---|
| 74 | + mutex_lock(&data->led_report_mutex); |
|---|
| 75 | + |
|---|
| 76 | + data->led_report[0] = TP10UBKBD_LED_OUTPUT_REPORT; |
|---|
| 77 | + data->led_report[1] = led_code; |
|---|
| 78 | + data->led_report[2] = value ? TP10UBKBD_LED_ON : TP10UBKBD_LED_OFF; |
|---|
| 79 | + ret = hid_hw_raw_request(hdev, data->led_report[0], data->led_report, 3, |
|---|
| 80 | + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); |
|---|
| 81 | + if (ret != 3) { |
|---|
| 82 | + if (ret != -ENODEV) |
|---|
| 83 | + hid_err(hdev, "Set LED output report error: %d\n", ret); |
|---|
| 84 | + |
|---|
| 85 | + ret = ret < 0 ? ret : -EIO; |
|---|
| 86 | + } else { |
|---|
| 87 | + ret = 0; |
|---|
| 88 | + } |
|---|
| 89 | + |
|---|
| 90 | + mutex_unlock(&data->led_report_mutex); |
|---|
| 91 | + |
|---|
| 92 | + return ret; |
|---|
| 93 | +} |
|---|
| 94 | + |
|---|
| 95 | +static void lenovo_tp10ubkbd_sync_fn_lock(struct work_struct *work) |
|---|
| 96 | +{ |
|---|
| 97 | + struct lenovo_drvdata *data = |
|---|
| 98 | + container_of(work, struct lenovo_drvdata, fn_lock_sync_work); |
|---|
| 99 | + |
|---|
| 100 | + lenovo_led_set_tp10ubkbd(data->hdev, TP10UBKBD_FN_LOCK_LED, |
|---|
| 101 | + data->fn_lock); |
|---|
| 102 | +} |
|---|
| 57 | 103 | |
|---|
| 58 | 104 | static const __u8 lenovo_pro_dock_need_fixup_collection[] = { |
|---|
| 59 | 105 | 0x05, 0x88, /* Usage Page (Vendor Usage Page 0x88) */ |
|---|
| .. | .. |
|---|
| 91 | 137 | if (usage->hid == (HID_UP_BUTTON | 0x0010)) { |
|---|
| 92 | 138 | /* This sub-device contains trackpoint, mark it */ |
|---|
| 93 | 139 | hid_set_drvdata(hdev, (void *)1); |
|---|
| 94 | | - map_key_clear(KEY_MICMUTE); |
|---|
| 140 | + map_key_clear(LENOVO_KEY_MICMUTE); |
|---|
| 95 | 141 | return 1; |
|---|
| 96 | 142 | } |
|---|
| 97 | 143 | return 0; |
|---|
| .. | .. |
|---|
| 106 | 152 | (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) { |
|---|
| 107 | 153 | switch (usage->hid & HID_USAGE) { |
|---|
| 108 | 154 | case 0x00f1: /* Fn-F4: Mic mute */ |
|---|
| 109 | | - map_key_clear(KEY_MICMUTE); |
|---|
| 155 | + map_key_clear(LENOVO_KEY_MICMUTE); |
|---|
| 110 | 156 | return 1; |
|---|
| 111 | 157 | case 0x00f2: /* Fn-F5: Brightness down */ |
|---|
| 112 | 158 | map_key_clear(KEY_BRIGHTNESSDOWN); |
|---|
| .. | .. |
|---|
| 182 | 228 | return 0; |
|---|
| 183 | 229 | } |
|---|
| 184 | 230 | |
|---|
| 231 | +static int lenovo_input_mapping_tp10_ultrabook_kbd(struct hid_device *hdev, |
|---|
| 232 | + struct hid_input *hi, struct hid_field *field, |
|---|
| 233 | + struct hid_usage *usage, unsigned long **bit, int *max) |
|---|
| 234 | +{ |
|---|
| 235 | + /* |
|---|
| 236 | + * The ThinkPad 10 Ultrabook Keyboard uses 0x000c0001 usage for |
|---|
| 237 | + * a bunch of keys which have no standard consumer page code. |
|---|
| 238 | + */ |
|---|
| 239 | + if (usage->hid == 0x000c0001) { |
|---|
| 240 | + switch (usage->usage_index) { |
|---|
| 241 | + case 8: /* Fn-Esc: Fn-lock toggle */ |
|---|
| 242 | + map_key_clear(KEY_FN_ESC); |
|---|
| 243 | + return 1; |
|---|
| 244 | + case 9: /* Fn-F4: Mic mute */ |
|---|
| 245 | + map_key_clear(LENOVO_KEY_MICMUTE); |
|---|
| 246 | + return 1; |
|---|
| 247 | + case 10: /* Fn-F7: Control panel */ |
|---|
| 248 | + map_key_clear(KEY_CONFIG); |
|---|
| 249 | + return 1; |
|---|
| 250 | + case 11: /* Fn-F8: Search (magnifier glass) */ |
|---|
| 251 | + map_key_clear(KEY_SEARCH); |
|---|
| 252 | + return 1; |
|---|
| 253 | + case 12: /* Fn-F10: Open My computer (6 boxes) */ |
|---|
| 254 | + map_key_clear(KEY_FILE); |
|---|
| 255 | + return 1; |
|---|
| 256 | + } |
|---|
| 257 | + } |
|---|
| 258 | + |
|---|
| 259 | + /* |
|---|
| 260 | + * The Ultrabook Keyboard sends a spurious F23 key-press when resuming |
|---|
| 261 | + * from suspend and it does not actually have a F23 key, ignore it. |
|---|
| 262 | + */ |
|---|
| 263 | + if (usage->hid == 0x00070072) |
|---|
| 264 | + return -1; |
|---|
| 265 | + |
|---|
| 266 | + return 0; |
|---|
| 267 | +} |
|---|
| 268 | + |
|---|
| 185 | 269 | static int lenovo_input_mapping(struct hid_device *hdev, |
|---|
| 186 | 270 | struct hid_input *hi, struct hid_field *field, |
|---|
| 187 | 271 | struct hid_usage *usage, unsigned long **bit, int *max) |
|---|
| .. | .. |
|---|
| 202 | 286 | case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL: |
|---|
| 203 | 287 | return lenovo_input_mapping_scrollpoint(hdev, hi, field, |
|---|
| 204 | 288 | usage, bit, max); |
|---|
| 289 | + case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
|---|
| 290 | + return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field, |
|---|
| 291 | + usage, bit, max); |
|---|
| 205 | 292 | default: |
|---|
| 206 | 293 | return 0; |
|---|
| 207 | 294 | } |
|---|
| .. | .. |
|---|
| 245 | 332 | static void lenovo_features_set_cptkbd(struct hid_device *hdev) |
|---|
| 246 | 333 | { |
|---|
| 247 | 334 | int ret; |
|---|
| 248 | | - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 335 | + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 249 | 336 | |
|---|
| 250 | 337 | ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); |
|---|
| 251 | 338 | if (ret) |
|---|
| .. | .. |
|---|
| 256 | 343 | hid_err(hdev, "Sensitivity setting failed: %d\n", ret); |
|---|
| 257 | 344 | } |
|---|
| 258 | 345 | |
|---|
| 259 | | -static ssize_t attr_fn_lock_show_cptkbd(struct device *dev, |
|---|
| 346 | +static ssize_t attr_fn_lock_show(struct device *dev, |
|---|
| 260 | 347 | struct device_attribute *attr, |
|---|
| 261 | 348 | char *buf) |
|---|
| 262 | 349 | { |
|---|
| 263 | 350 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 264 | | - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 351 | + struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
|---|
| 265 | 352 | |
|---|
| 266 | | - return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock); |
|---|
| 353 | + return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock); |
|---|
| 267 | 354 | } |
|---|
| 268 | 355 | |
|---|
| 269 | | -static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, |
|---|
| 356 | +static ssize_t attr_fn_lock_store(struct device *dev, |
|---|
| 270 | 357 | struct device_attribute *attr, |
|---|
| 271 | 358 | const char *buf, |
|---|
| 272 | 359 | size_t count) |
|---|
| 273 | 360 | { |
|---|
| 274 | 361 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 275 | | - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 276 | | - int value; |
|---|
| 362 | + struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
|---|
| 363 | + int value, ret; |
|---|
| 277 | 364 | |
|---|
| 278 | 365 | if (kstrtoint(buf, 10, &value)) |
|---|
| 279 | 366 | return -EINVAL; |
|---|
| 280 | 367 | if (value < 0 || value > 1) |
|---|
| 281 | 368 | return -EINVAL; |
|---|
| 282 | 369 | |
|---|
| 283 | | - cptkbd_data->fn_lock = !!value; |
|---|
| 284 | | - lenovo_features_set_cptkbd(hdev); |
|---|
| 370 | + data->fn_lock = !!value; |
|---|
| 371 | + |
|---|
| 372 | + switch (hdev->product) { |
|---|
| 373 | + case USB_DEVICE_ID_LENOVO_CUSBKBD: |
|---|
| 374 | + case USB_DEVICE_ID_LENOVO_CBTKBD: |
|---|
| 375 | + lenovo_features_set_cptkbd(hdev); |
|---|
| 376 | + break; |
|---|
| 377 | + case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
|---|
| 378 | + ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value); |
|---|
| 379 | + if (ret) |
|---|
| 380 | + return ret; |
|---|
| 381 | + break; |
|---|
| 382 | + } |
|---|
| 285 | 383 | |
|---|
| 286 | 384 | return count; |
|---|
| 287 | 385 | } |
|---|
| .. | .. |
|---|
| 291 | 389 | char *buf) |
|---|
| 292 | 390 | { |
|---|
| 293 | 391 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 294 | | - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 392 | + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 295 | 393 | |
|---|
| 296 | 394 | return snprintf(buf, PAGE_SIZE, "%u\n", |
|---|
| 297 | 395 | cptkbd_data->sensitivity); |
|---|
| .. | .. |
|---|
| 303 | 401 | size_t count) |
|---|
| 304 | 402 | { |
|---|
| 305 | 403 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 306 | | - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 404 | + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 307 | 405 | int value; |
|---|
| 308 | 406 | |
|---|
| 309 | 407 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
|---|
| .. | .. |
|---|
| 316 | 414 | } |
|---|
| 317 | 415 | |
|---|
| 318 | 416 | |
|---|
| 319 | | -static struct device_attribute dev_attr_fn_lock_cptkbd = |
|---|
| 417 | +static struct device_attribute dev_attr_fn_lock = |
|---|
| 320 | 418 | __ATTR(fn_lock, S_IWUSR | S_IRUGO, |
|---|
| 321 | | - attr_fn_lock_show_cptkbd, |
|---|
| 322 | | - attr_fn_lock_store_cptkbd); |
|---|
| 419 | + attr_fn_lock_show, |
|---|
| 420 | + attr_fn_lock_store); |
|---|
| 323 | 421 | |
|---|
| 324 | 422 | static struct device_attribute dev_attr_sensitivity_cptkbd = |
|---|
| 325 | 423 | __ATTR(sensitivity, S_IWUSR | S_IRUGO, |
|---|
| .. | .. |
|---|
| 328 | 426 | |
|---|
| 329 | 427 | |
|---|
| 330 | 428 | static struct attribute *lenovo_attributes_cptkbd[] = { |
|---|
| 331 | | - &dev_attr_fn_lock_cptkbd.attr, |
|---|
| 429 | + &dev_attr_fn_lock.attr, |
|---|
| 332 | 430 | &dev_attr_sensitivity_cptkbd.attr, |
|---|
| 333 | 431 | NULL |
|---|
| 334 | 432 | }; |
|---|
| .. | .. |
|---|
| 357 | 455 | return 0; |
|---|
| 358 | 456 | } |
|---|
| 359 | 457 | |
|---|
| 458 | +static int lenovo_event_tp10ubkbd(struct hid_device *hdev, |
|---|
| 459 | + struct hid_field *field, struct hid_usage *usage, __s32 value) |
|---|
| 460 | +{ |
|---|
| 461 | + struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
|---|
| 462 | + |
|---|
| 463 | + if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) { |
|---|
| 464 | + /* |
|---|
| 465 | + * The user has toggled the Fn-lock state. Toggle our own |
|---|
| 466 | + * cached value of it and sync our value to the keyboard to |
|---|
| 467 | + * ensure things are in sync (the sycning should be a no-op). |
|---|
| 468 | + */ |
|---|
| 469 | + data->fn_lock = !data->fn_lock; |
|---|
| 470 | + schedule_work(&data->fn_lock_sync_work); |
|---|
| 471 | + } |
|---|
| 472 | + |
|---|
| 473 | + return 0; |
|---|
| 474 | +} |
|---|
| 475 | + |
|---|
| 360 | 476 | static int lenovo_event_cptkbd(struct hid_device *hdev, |
|---|
| 361 | 477 | struct hid_field *field, struct hid_usage *usage, __s32 value) |
|---|
| 362 | 478 | { |
|---|
| 363 | | - struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 479 | + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); |
|---|
| 364 | 480 | |
|---|
| 365 | 481 | /* "wheel" scroll events */ |
|---|
| 366 | 482 | if (usage->type == EV_REL && (usage->code == REL_WHEEL || |
|---|
| .. | .. |
|---|
| 395 | 511 | static int lenovo_event(struct hid_device *hdev, struct hid_field *field, |
|---|
| 396 | 512 | struct hid_usage *usage, __s32 value) |
|---|
| 397 | 513 | { |
|---|
| 514 | + if (!hid_get_drvdata(hdev)) |
|---|
| 515 | + return 0; |
|---|
| 516 | + |
|---|
| 398 | 517 | switch (hdev->product) { |
|---|
| 399 | 518 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
|---|
| 400 | 519 | case USB_DEVICE_ID_LENOVO_CBTKBD: |
|---|
| 401 | 520 | return lenovo_event_cptkbd(hdev, field, usage, value); |
|---|
| 521 | + case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
|---|
| 522 | + return lenovo_event_tp10ubkbd(hdev, field, usage, value); |
|---|
| 402 | 523 | default: |
|---|
| 403 | 524 | return 0; |
|---|
| 404 | 525 | } |
|---|
| .. | .. |
|---|
| 407 | 528 | static int lenovo_features_set_tpkbd(struct hid_device *hdev) |
|---|
| 408 | 529 | { |
|---|
| 409 | 530 | struct hid_report *report; |
|---|
| 410 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 531 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 411 | 532 | |
|---|
| 412 | 533 | report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; |
|---|
| 413 | 534 | |
|---|
| .. | .. |
|---|
| 428 | 549 | char *buf) |
|---|
| 429 | 550 | { |
|---|
| 430 | 551 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 431 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 552 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 432 | 553 | |
|---|
| 433 | 554 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); |
|---|
| 434 | 555 | } |
|---|
| .. | .. |
|---|
| 439 | 560 | size_t count) |
|---|
| 440 | 561 | { |
|---|
| 441 | 562 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 442 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 563 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 443 | 564 | int value; |
|---|
| 444 | 565 | |
|---|
| 445 | 566 | if (kstrtoint(buf, 10, &value)) |
|---|
| .. | .. |
|---|
| 458 | 579 | char *buf) |
|---|
| 459 | 580 | { |
|---|
| 460 | 581 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 461 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 582 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 462 | 583 | |
|---|
| 463 | 584 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); |
|---|
| 464 | 585 | } |
|---|
| .. | .. |
|---|
| 469 | 590 | size_t count) |
|---|
| 470 | 591 | { |
|---|
| 471 | 592 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 472 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 593 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 473 | 594 | int value; |
|---|
| 474 | 595 | |
|---|
| 475 | 596 | if (kstrtoint(buf, 10, &value)) |
|---|
| .. | .. |
|---|
| 488 | 609 | char *buf) |
|---|
| 489 | 610 | { |
|---|
| 490 | 611 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 491 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 612 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 492 | 613 | |
|---|
| 493 | 614 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); |
|---|
| 494 | 615 | } |
|---|
| .. | .. |
|---|
| 499 | 620 | size_t count) |
|---|
| 500 | 621 | { |
|---|
| 501 | 622 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 502 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 623 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 503 | 624 | int value; |
|---|
| 504 | 625 | |
|---|
| 505 | 626 | if (kstrtoint(buf, 10, &value)) |
|---|
| .. | .. |
|---|
| 518 | 639 | char *buf) |
|---|
| 519 | 640 | { |
|---|
| 520 | 641 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 521 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 642 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 522 | 643 | |
|---|
| 523 | 644 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); |
|---|
| 524 | 645 | } |
|---|
| .. | .. |
|---|
| 529 | 650 | size_t count) |
|---|
| 530 | 651 | { |
|---|
| 531 | 652 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 532 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 653 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 533 | 654 | int value; |
|---|
| 534 | 655 | |
|---|
| 535 | 656 | if (kstrtoint(buf, 10, &value)) |
|---|
| .. | .. |
|---|
| 548 | 669 | char *buf) |
|---|
| 549 | 670 | { |
|---|
| 550 | 671 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 551 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 672 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 552 | 673 | |
|---|
| 553 | 674 | return snprintf(buf, PAGE_SIZE, "%u\n", |
|---|
| 554 | 675 | data_pointer->sensitivity); |
|---|
| .. | .. |
|---|
| 560 | 681 | size_t count) |
|---|
| 561 | 682 | { |
|---|
| 562 | 683 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 563 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 684 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 564 | 685 | int value; |
|---|
| 565 | 686 | |
|---|
| 566 | 687 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
|---|
| .. | .. |
|---|
| 577 | 698 | char *buf) |
|---|
| 578 | 699 | { |
|---|
| 579 | 700 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 580 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 701 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 581 | 702 | |
|---|
| 582 | 703 | return snprintf(buf, PAGE_SIZE, "%u\n", |
|---|
| 583 | 704 | data_pointer->press_speed); |
|---|
| .. | .. |
|---|
| 589 | 710 | size_t count) |
|---|
| 590 | 711 | { |
|---|
| 591 | 712 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 592 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 713 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 593 | 714 | int value; |
|---|
| 594 | 715 | |
|---|
| 595 | 716 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) |
|---|
| .. | .. |
|---|
| 645 | 766 | .attrs = lenovo_attributes_tpkbd, |
|---|
| 646 | 767 | }; |
|---|
| 647 | 768 | |
|---|
| 648 | | -static enum led_brightness lenovo_led_brightness_get_tpkbd( |
|---|
| 769 | +static void lenovo_led_set_tpkbd(struct hid_device *hdev) |
|---|
| 770 | +{ |
|---|
| 771 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 772 | + struct hid_report *report; |
|---|
| 773 | + |
|---|
| 774 | + report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; |
|---|
| 775 | + report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; |
|---|
| 776 | + report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; |
|---|
| 777 | + hid_hw_request(hdev, report, HID_REQ_SET_REPORT); |
|---|
| 778 | +} |
|---|
| 779 | + |
|---|
| 780 | +static enum led_brightness lenovo_led_brightness_get( |
|---|
| 649 | 781 | struct led_classdev *led_cdev) |
|---|
| 650 | 782 | { |
|---|
| 651 | 783 | struct device *dev = led_cdev->dev->parent; |
|---|
| 652 | 784 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 653 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 785 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 654 | 786 | int led_nr = 0; |
|---|
| 655 | 787 | |
|---|
| 656 | 788 | if (led_cdev == &data_pointer->led_micmute) |
|---|
| .. | .. |
|---|
| 661 | 793 | : LED_OFF; |
|---|
| 662 | 794 | } |
|---|
| 663 | 795 | |
|---|
| 664 | | -static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, |
|---|
| 796 | +static int lenovo_led_brightness_set(struct led_classdev *led_cdev, |
|---|
| 665 | 797 | enum led_brightness value) |
|---|
| 666 | 798 | { |
|---|
| 667 | 799 | struct device *dev = led_cdev->dev->parent; |
|---|
| 668 | 800 | struct hid_device *hdev = to_hid_device(dev); |
|---|
| 669 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 670 | | - struct hid_report *report; |
|---|
| 801 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 802 | + u8 tp10ubkbd_led[] = { TP10UBKBD_MUTE_LED, TP10UBKBD_MICMUTE_LED }; |
|---|
| 671 | 803 | int led_nr = 0; |
|---|
| 804 | + int ret = 0; |
|---|
| 672 | 805 | |
|---|
| 673 | 806 | if (led_cdev == &data_pointer->led_micmute) |
|---|
| 674 | 807 | led_nr = 1; |
|---|
| .. | .. |
|---|
| 678 | 811 | else |
|---|
| 679 | 812 | data_pointer->led_state |= 1 << led_nr; |
|---|
| 680 | 813 | |
|---|
| 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; |
|---|
| 684 | | - hid_hw_request(hdev, report, HID_REQ_SET_REPORT); |
|---|
| 814 | + switch (hdev->product) { |
|---|
| 815 | + case USB_DEVICE_ID_LENOVO_TPKBD: |
|---|
| 816 | + lenovo_led_set_tpkbd(hdev); |
|---|
| 817 | + break; |
|---|
| 818 | + case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
|---|
| 819 | + ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); |
|---|
| 820 | + break; |
|---|
| 821 | + } |
|---|
| 822 | + |
|---|
| 823 | + return ret; |
|---|
| 824 | +} |
|---|
| 825 | + |
|---|
| 826 | +static int lenovo_register_leds(struct hid_device *hdev) |
|---|
| 827 | +{ |
|---|
| 828 | + struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
|---|
| 829 | + size_t name_sz = strlen(dev_name(&hdev->dev)) + 16; |
|---|
| 830 | + char *name_mute, *name_micm; |
|---|
| 831 | + int ret; |
|---|
| 832 | + |
|---|
| 833 | + name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); |
|---|
| 834 | + name_micm = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); |
|---|
| 835 | + if (name_mute == NULL || name_micm == NULL) { |
|---|
| 836 | + hid_err(hdev, "Could not allocate memory for led data\n"); |
|---|
| 837 | + return -ENOMEM; |
|---|
| 838 | + } |
|---|
| 839 | + snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(&hdev->dev)); |
|---|
| 840 | + snprintf(name_micm, name_sz, "%s:amber:micmute", dev_name(&hdev->dev)); |
|---|
| 841 | + |
|---|
| 842 | + data->led_mute.name = name_mute; |
|---|
| 843 | + data->led_mute.brightness_get = lenovo_led_brightness_get; |
|---|
| 844 | + data->led_mute.brightness_set_blocking = lenovo_led_brightness_set; |
|---|
| 845 | + data->led_mute.flags = LED_HW_PLUGGABLE; |
|---|
| 846 | + data->led_mute.dev = &hdev->dev; |
|---|
| 847 | + ret = led_classdev_register(&hdev->dev, &data->led_mute); |
|---|
| 848 | + if (ret < 0) |
|---|
| 849 | + return ret; |
|---|
| 850 | + |
|---|
| 851 | + data->led_micmute.name = name_micm; |
|---|
| 852 | + data->led_micmute.brightness_get = lenovo_led_brightness_get; |
|---|
| 853 | + data->led_micmute.brightness_set_blocking = lenovo_led_brightness_set; |
|---|
| 854 | + data->led_micmute.flags = LED_HW_PLUGGABLE; |
|---|
| 855 | + data->led_micmute.dev = &hdev->dev; |
|---|
| 856 | + ret = led_classdev_register(&hdev->dev, &data->led_micmute); |
|---|
| 857 | + if (ret < 0) { |
|---|
| 858 | + led_classdev_unregister(&data->led_mute); |
|---|
| 859 | + return ret; |
|---|
| 860 | + } |
|---|
| 861 | + |
|---|
| 862 | + return 0; |
|---|
| 685 | 863 | } |
|---|
| 686 | 864 | |
|---|
| 687 | 865 | static int lenovo_probe_tpkbd(struct hid_device *hdev) |
|---|
| 688 | 866 | { |
|---|
| 689 | | - struct device *dev = &hdev->dev; |
|---|
| 690 | | - struct lenovo_drvdata_tpkbd *data_pointer; |
|---|
| 691 | | - size_t name_sz = strlen(dev_name(dev)) + 16; |
|---|
| 692 | | - char *name_mute, *name_micmute; |
|---|
| 693 | | - int i; |
|---|
| 694 | | - int ret; |
|---|
| 867 | + struct lenovo_drvdata *data_pointer; |
|---|
| 868 | + int i, ret; |
|---|
| 695 | 869 | |
|---|
| 696 | 870 | /* |
|---|
| 697 | 871 | * Only register extra settings against subdevice where input_mapping |
|---|
| .. | .. |
|---|
| 715 | 889 | hid_warn(hdev, "Could not create sysfs group: %d\n", ret); |
|---|
| 716 | 890 | |
|---|
| 717 | 891 | data_pointer = devm_kzalloc(&hdev->dev, |
|---|
| 718 | | - sizeof(struct lenovo_drvdata_tpkbd), |
|---|
| 892 | + sizeof(struct lenovo_drvdata), |
|---|
| 719 | 893 | GFP_KERNEL); |
|---|
| 720 | 894 | if (data_pointer == NULL) { |
|---|
| 721 | 895 | hid_err(hdev, "Could not allocate memory for driver data\n"); |
|---|
| .. | .. |
|---|
| 727 | 901 | data_pointer->sensitivity = 0xa0; |
|---|
| 728 | 902 | data_pointer->press_speed = 0x38; |
|---|
| 729 | 903 | |
|---|
| 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) { |
|---|
| 733 | | - hid_err(hdev, "Could not allocate memory for led data\n"); |
|---|
| 734 | | - ret = -ENOMEM; |
|---|
| 735 | | - goto err; |
|---|
| 736 | | - } |
|---|
| 737 | | - snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); |
|---|
| 738 | | - snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); |
|---|
| 739 | | - |
|---|
| 740 | 904 | hid_set_drvdata(hdev, data_pointer); |
|---|
| 741 | 905 | |
|---|
| 742 | | - data_pointer->led_mute.name = name_mute; |
|---|
| 743 | | - data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd; |
|---|
| 744 | | - data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd; |
|---|
| 745 | | - data_pointer->led_mute.dev = dev; |
|---|
| 746 | | - ret = led_classdev_register(dev, &data_pointer->led_mute); |
|---|
| 747 | | - if (ret < 0) |
|---|
| 906 | + ret = lenovo_register_leds(hdev); |
|---|
| 907 | + if (ret) |
|---|
| 748 | 908 | goto err; |
|---|
| 749 | | - |
|---|
| 750 | | - data_pointer->led_micmute.name = name_micmute; |
|---|
| 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; |
|---|
| 755 | | - data_pointer->led_micmute.dev = dev; |
|---|
| 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 | | - } |
|---|
| 761 | 909 | |
|---|
| 762 | 910 | lenovo_features_set_tpkbd(hdev); |
|---|
| 763 | 911 | |
|---|
| .. | .. |
|---|
| 770 | 918 | static int lenovo_probe_cptkbd(struct hid_device *hdev) |
|---|
| 771 | 919 | { |
|---|
| 772 | 920 | int ret; |
|---|
| 773 | | - struct lenovo_drvdata_cptkbd *cptkbd_data; |
|---|
| 921 | + struct lenovo_drvdata *cptkbd_data; |
|---|
| 774 | 922 | |
|---|
| 775 | 923 | /* All the custom action happens on the USBMOUSE device for USB */ |
|---|
| 776 | 924 | if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD |
|---|
| .. | .. |
|---|
| 814 | 962 | return 0; |
|---|
| 815 | 963 | } |
|---|
| 816 | 964 | |
|---|
| 965 | +static struct attribute *lenovo_attributes_tp10ubkbd[] = { |
|---|
| 966 | + &dev_attr_fn_lock.attr, |
|---|
| 967 | + NULL |
|---|
| 968 | +}; |
|---|
| 969 | + |
|---|
| 970 | +static const struct attribute_group lenovo_attr_group_tp10ubkbd = { |
|---|
| 971 | + .attrs = lenovo_attributes_tp10ubkbd, |
|---|
| 972 | +}; |
|---|
| 973 | + |
|---|
| 974 | +static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) |
|---|
| 975 | +{ |
|---|
| 976 | + struct lenovo_drvdata *data; |
|---|
| 977 | + int ret; |
|---|
| 978 | + |
|---|
| 979 | + /* All the custom action happens on the USBMOUSE device for USB */ |
|---|
| 980 | + if (hdev->type != HID_TYPE_USBMOUSE) |
|---|
| 981 | + return 0; |
|---|
| 982 | + |
|---|
| 983 | + data = devm_kzalloc(&hdev->dev, sizeof(*data), GFP_KERNEL); |
|---|
| 984 | + if (!data) |
|---|
| 985 | + return -ENOMEM; |
|---|
| 986 | + |
|---|
| 987 | + mutex_init(&data->led_report_mutex); |
|---|
| 988 | + INIT_WORK(&data->fn_lock_sync_work, lenovo_tp10ubkbd_sync_fn_lock); |
|---|
| 989 | + data->hdev = hdev; |
|---|
| 990 | + |
|---|
| 991 | + hid_set_drvdata(hdev, data); |
|---|
| 992 | + |
|---|
| 993 | + /* |
|---|
| 994 | + * The Thinkpad 10 ultrabook USB kbd dock's Fn-lock defaults to on. |
|---|
| 995 | + * We cannot read the state, only set it, so we force it to on here |
|---|
| 996 | + * (which should be a no-op) to make sure that our state matches the |
|---|
| 997 | + * keyboard's FN-lock state. This is the same as what Windows does. |
|---|
| 998 | + */ |
|---|
| 999 | + data->fn_lock = true; |
|---|
| 1000 | + lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock); |
|---|
| 1001 | + |
|---|
| 1002 | + ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); |
|---|
| 1003 | + if (ret) |
|---|
| 1004 | + return ret; |
|---|
| 1005 | + |
|---|
| 1006 | + ret = lenovo_register_leds(hdev); |
|---|
| 1007 | + if (ret) |
|---|
| 1008 | + goto err; |
|---|
| 1009 | + |
|---|
| 1010 | + return 0; |
|---|
| 1011 | +err: |
|---|
| 1012 | + sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); |
|---|
| 1013 | + return ret; |
|---|
| 1014 | +} |
|---|
| 1015 | + |
|---|
| 817 | 1016 | static int lenovo_probe(struct hid_device *hdev, |
|---|
| 818 | 1017 | const struct hid_device_id *id) |
|---|
| 819 | 1018 | { |
|---|
| .. | .. |
|---|
| 839 | 1038 | case USB_DEVICE_ID_LENOVO_CBTKBD: |
|---|
| 840 | 1039 | ret = lenovo_probe_cptkbd(hdev); |
|---|
| 841 | 1040 | break; |
|---|
| 1041 | + case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
|---|
| 1042 | + ret = lenovo_probe_tp10ubkbd(hdev); |
|---|
| 1043 | + break; |
|---|
| 842 | 1044 | default: |
|---|
| 843 | 1045 | ret = 0; |
|---|
| 844 | 1046 | break; |
|---|
| .. | .. |
|---|
| 855 | 1057 | |
|---|
| 856 | 1058 | static void lenovo_remove_tpkbd(struct hid_device *hdev) |
|---|
| 857 | 1059 | { |
|---|
| 858 | | - struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); |
|---|
| 1060 | + struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); |
|---|
| 859 | 1061 | |
|---|
| 860 | 1062 | /* |
|---|
| 861 | 1063 | * Only the trackpoint half of the keyboard has drvdata and stuff that |
|---|
| .. | .. |
|---|
| 869 | 1071 | |
|---|
| 870 | 1072 | led_classdev_unregister(&data_pointer->led_micmute); |
|---|
| 871 | 1073 | led_classdev_unregister(&data_pointer->led_mute); |
|---|
| 872 | | - |
|---|
| 873 | | - hid_set_drvdata(hdev, NULL); |
|---|
| 874 | 1074 | } |
|---|
| 875 | 1075 | |
|---|
| 876 | 1076 | static void lenovo_remove_cptkbd(struct hid_device *hdev) |
|---|
| 877 | 1077 | { |
|---|
| 878 | 1078 | sysfs_remove_group(&hdev->dev.kobj, |
|---|
| 879 | 1079 | &lenovo_attr_group_cptkbd); |
|---|
| 1080 | +} |
|---|
| 1081 | + |
|---|
| 1082 | +static void lenovo_remove_tp10ubkbd(struct hid_device *hdev) |
|---|
| 1083 | +{ |
|---|
| 1084 | + struct lenovo_drvdata *data = hid_get_drvdata(hdev); |
|---|
| 1085 | + |
|---|
| 1086 | + if (data == NULL) |
|---|
| 1087 | + return; |
|---|
| 1088 | + |
|---|
| 1089 | + led_classdev_unregister(&data->led_micmute); |
|---|
| 1090 | + led_classdev_unregister(&data->led_mute); |
|---|
| 1091 | + |
|---|
| 1092 | + sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); |
|---|
| 1093 | + cancel_work_sync(&data->fn_lock_sync_work); |
|---|
| 880 | 1094 | } |
|---|
| 881 | 1095 | |
|---|
| 882 | 1096 | static void lenovo_remove(struct hid_device *hdev) |
|---|
| .. | .. |
|---|
| 888 | 1102 | case USB_DEVICE_ID_LENOVO_CUSBKBD: |
|---|
| 889 | 1103 | case USB_DEVICE_ID_LENOVO_CBTKBD: |
|---|
| 890 | 1104 | lenovo_remove_cptkbd(hdev); |
|---|
| 1105 | + break; |
|---|
| 1106 | + case USB_DEVICE_ID_LENOVO_TP10UBKBD: |
|---|
| 1107 | + lenovo_remove_tp10ubkbd(hdev); |
|---|
| 891 | 1108 | break; |
|---|
| 892 | 1109 | } |
|---|
| 893 | 1110 | |
|---|
| .. | .. |
|---|
| 925 | 1142 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) }, |
|---|
| 926 | 1143 | { HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) }, |
|---|
| 927 | 1144 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) }, |
|---|
| 1145 | + { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TP10UBKBD) }, |
|---|
| 928 | 1146 | { } |
|---|
| 929 | 1147 | }; |
|---|
| 930 | 1148 | |
|---|