diff options
Diffstat (limited to 'drivers/hid/hid-lenovo.c')
| -rw-r--r-- | drivers/hid/hid-lenovo.c | 298 |
1 files changed, 246 insertions, 52 deletions
diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c index 44763c0da444..9cc3e029e9f6 100644 --- a/drivers/hid/hid-lenovo.c +++ b/drivers/hid/hid-lenovo.c @@ -37,6 +37,13 @@ /* Userspace expects F20 for mic-mute KEY_MICMUTE does not work */ #define LENOVO_KEY_MICMUTE KEY_F20 +/* HID raw events for ThinkPad X12 Tabs*/ +#define TP_X12_RAW_HOTKEY_FN_F4 0x00020003 +#define TP_X12_RAW_HOTKEY_FN_F8 0x38001003 +#define TP_X12_RAW_HOTKEY_FN_F10 0x00000803 +#define TP_X12_RAW_HOTKEY_FN_F12 0x00000403 +#define TP_X12_RAW_HOTKEY_FN_SPACE 0x18001003 + struct lenovo_drvdata { u8 led_report[3]; /* Must be first for proper alignment */ int led_state; @@ -51,8 +58,13 @@ struct lenovo_drvdata { int select_right; int sensitivity; int press_speed; - u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */ + /* 0: Up + * 1: Down (undecided) + * 2: Scrolling + */ + u8 middlebutton_state; bool fn_lock; + bool middleclick_workaround_cptkbd; }; #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) @@ -66,6 +78,14 @@ struct lenovo_drvdata { #define TP10UBKBD_LED_OFF 1 #define TP10UBKBD_LED_ON 2 +/* Function to report raw_events as key events*/ +static inline void report_key_event(struct input_dev *input, int keycode) +{ + input_report_key(input, keycode, 1); + input_report_key(input, keycode, 0); + input_sync(input); +} + static int lenovo_led_set_tp10ubkbd(struct hid_device *hdev, u8 led_code, enum led_brightness value) { @@ -128,7 +148,15 @@ static const __u8 lenovo_tpIIbtkbd_need_fixup_collection[] = { 0x81, 0x01, /* Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) */ }; -static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, +static const __u8 lenovo_yoga7x_kbd_need_fixup_collection[] = { + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x65, // Logical Maximum (101) + 0x05, 0x07, // Usage Page (Keyboard) + 0x19, 0x00, // Usage Minimum (0) + 0x29, 0xDD, // Usage Maximum (221) +}; + +static const __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) { switch (hdev->product) { @@ -157,6 +185,13 @@ static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc, rdesc[260] = 0x01; /* report count (2) = 0x01 */ } break; + case I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD: + if (*rsize == 176 && + memcmp(&rdesc[52], lenovo_yoga7x_kbd_need_fixup_collection, + sizeof(lenovo_yoga7x_kbd_need_fixup_collection)) == 0) { + rdesc[55] = rdesc[61]; // logical maximum = usage maximum + } + break; } return rdesc; } @@ -467,7 +502,11 @@ static int lenovo_input_mapping(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_TP10UBKBD: return lenovo_input_mapping_tp10_ultrabook_kbd(hdev, hi, field, usage, bit, max); + case USB_DEVICE_ID_LENOVO_X12_TAB: + case USB_DEVICE_ID_LENOVO_X12_TAB2: case USB_DEVICE_ID_LENOVO_X1_TAB: + case USB_DEVICE_ID_LENOVO_X1_TAB2: + case USB_DEVICE_ID_LENOVO_X1_TAB3: return lenovo_input_mapping_x1_tab_kbd(hdev, hi, field, usage, bit, max); default: return 0; @@ -521,6 +560,22 @@ static void lenovo_features_set_cptkbd(struct hid_device *hdev) int ret; struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); + /* + * Tell the keyboard a driver understands it, and turn F7, F9, F11 into + * regular keys (Compact only) + */ + if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD || + hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD) { + ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); + if (ret) + hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); + } + + /* Switch middle button to native mode */ + ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01); + if (ret) + hid_warn(hdev, "Failed to switch middle button: %d\n", ret); + ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); if (ret) hid_err(hdev, "Fn-lock setting failed: %d\n", ret); @@ -537,7 +592,7 @@ static ssize_t attr_fn_lock_show(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", data->fn_lock); + return sysfs_emit(buf, "%u\n", data->fn_lock); } static ssize_t attr_fn_lock_store(struct device *dev, @@ -563,8 +618,12 @@ static ssize_t attr_fn_lock_store(struct device *dev, case USB_DEVICE_ID_LENOVO_TPIIBTKBD: lenovo_features_set_cptkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_X12_TAB: + case USB_DEVICE_ID_LENOVO_X12_TAB2: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: + case USB_DEVICE_ID_LENOVO_X1_TAB2: + case USB_DEVICE_ID_LENOVO_X1_TAB3: ret = lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, value); if (ret) return ret; @@ -581,8 +640,7 @@ static ssize_t attr_sensitivity_show_cptkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", - cptkbd_data->sensitivity); + return sysfs_emit(buf, "%u\n", cptkbd_data->sensitivity); } static ssize_t attr_sensitivity_store_cptkbd(struct device *dev, @@ -603,6 +661,36 @@ static ssize_t attr_sensitivity_store_cptkbd(struct device *dev, return count; } +static ssize_t attr_middleclick_workaround_show_cptkbd(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); + + return sysfs_emit(buf, "%u\n", + cptkbd_data->middleclick_workaround_cptkbd); +} + +static ssize_t attr_middleclick_workaround_store_cptkbd(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t count) +{ + struct hid_device *hdev = to_hid_device(dev); + struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); + int value; + + if (kstrtoint(buf, 10, &value)) + return -EINVAL; + if (value < 0 || value > 1) + return -EINVAL; + + cptkbd_data->middleclick_workaround_cptkbd = !!value; + + return count; +} + static struct device_attribute dev_attr_fn_lock = __ATTR(fn_lock, S_IWUSR | S_IRUGO, @@ -614,10 +702,16 @@ static struct device_attribute dev_attr_sensitivity_cptkbd = attr_sensitivity_show_cptkbd, attr_sensitivity_store_cptkbd); +static struct device_attribute dev_attr_middleclick_workaround_cptkbd = + __ATTR(middleclick_workaround, S_IWUSR | S_IRUGO, + attr_middleclick_workaround_show_cptkbd, + attr_middleclick_workaround_store_cptkbd); + static struct attribute *lenovo_attributes_cptkbd[] = { &dev_attr_fn_lock.attr, &dev_attr_sensitivity_cptkbd.attr, + &dev_attr_middleclick_workaround_cptkbd.attr, NULL }; @@ -625,6 +719,57 @@ static const struct attribute_group lenovo_attr_group_cptkbd = { .attrs = lenovo_attributes_cptkbd, }; +/* Function to handle Lenovo Thinkpad TAB X12's HID raw inputs for fn keys*/ +static int lenovo_raw_event_TP_X12_tab(struct hid_device *hdev, u32 raw_data) +{ + struct hid_input *hidinput; + struct input_dev *input = NULL; + + /* Iterate through all associated input devices */ + list_for_each_entry(hidinput, &hdev->inputs, list) { + input = hidinput->input; + if (!input) + continue; + + switch (raw_data) { + /* fn-F20 being used here for MIC mute*/ + case TP_X12_RAW_HOTKEY_FN_F4: + report_key_event(input, LENOVO_KEY_MICMUTE); + return 1; + /* Power-mode or Airplane mode will be called based on the device*/ + case TP_X12_RAW_HOTKEY_FN_F8: + /* + * TP X12 TAB uses Fn-F8 calls Airplanemode + * Whereas TP X12 TAB2 uses Fn-F8 for toggling + * Power modes + */ + if (hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) { + report_key_event(input, KEY_RFKILL); + return 1; + } + report_key_event(input, KEY_PERFORMANCE); + return 1; + case TP_X12_RAW_HOTKEY_FN_F10: + /* TAB1 has PICKUP Phone and TAB2 use Snipping tool*/ + (hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB) ? + report_key_event(input, KEY_PICKUP_PHONE) : + report_key_event(input, KEY_SELECTIVE_SCREENSHOT); + return 1; + case TP_X12_RAW_HOTKEY_FN_F12: + /* BookMarks/STAR key*/ + report_key_event(input, KEY_BOOKMARKS); + return 1; + case TP_X12_RAW_HOTKEY_FN_SPACE: + /* Keyboard LED backlight toggle*/ + report_key_event(input, KEY_KBDILLUMTOGGLE); + return 1; + default: + break; + } + } + return 0; +} + static int lenovo_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { @@ -642,6 +787,15 @@ static int lenovo_raw_event(struct hid_device *hdev, data[2] = 0x01; } + /* + * Lenovo TP X12 Tab KBD's Fn+XX is HID raw data defined. Report ID is 0x03 + * e.g.: Raw data received for MIC mute is 0x00020003. + */ + if (unlikely((hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB + || hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2) + && size >= 3 && report->id == 0x03)) + return lenovo_raw_event_TP_X12_tab(hdev, le32_to_cpu(*(__le32 *)data)); + return 0; } @@ -668,31 +822,33 @@ static int lenovo_event_cptkbd(struct hid_device *hdev, { struct lenovo_drvdata *cptkbd_data = hid_get_drvdata(hdev); - /* "wheel" scroll events */ - if (usage->type == EV_REL && (usage->code == REL_WHEEL || - usage->code == REL_HWHEEL)) { - /* Scroll events disable middle-click event */ - cptkbd_data->middlebutton_state = 2; - return 0; - } + if (cptkbd_data->middleclick_workaround_cptkbd) { + /* "wheel" scroll events */ + if (usage->type == EV_REL && (usage->code == REL_WHEEL || + usage->code == REL_HWHEEL)) { + /* Scroll events disable middle-click event */ + cptkbd_data->middlebutton_state = 2; + return 0; + } - /* Middle click events */ - if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) { - if (value == 1) { - cptkbd_data->middlebutton_state = 1; - } else if (value == 0) { - if (cptkbd_data->middlebutton_state == 1) { - /* No scrolling inbetween, send middle-click */ - input_event(field->hidinput->input, - EV_KEY, BTN_MIDDLE, 1); - input_sync(field->hidinput->input); - input_event(field->hidinput->input, - EV_KEY, BTN_MIDDLE, 0); - input_sync(field->hidinput->input); + /* Middle click events */ + if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) { + if (value == 1) { + cptkbd_data->middlebutton_state = 1; + } else if (value == 0) { + if (cptkbd_data->middlebutton_state == 1) { + /* No scrolling inbetween, send middle-click */ + input_event(field->hidinput->input, + EV_KEY, BTN_MIDDLE, 1); + input_sync(field->hidinput->input); + input_event(field->hidinput->input, + EV_KEY, BTN_MIDDLE, 0); + input_sync(field->hidinput->input); + } + cptkbd_data->middlebutton_state = 0; } - cptkbd_data->middlebutton_state = 0; + return 1; } - return 1; } if (usage->type == EV_KEY && usage->code == KEY_FN_ESC && value == 1) { @@ -719,8 +875,12 @@ static int lenovo_event(struct hid_device *hdev, struct hid_field *field, case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: case USB_DEVICE_ID_LENOVO_TPIIBTKBD: return lenovo_event_cptkbd(hdev, field, usage, value); + case USB_DEVICE_ID_LENOVO_X12_TAB: + case USB_DEVICE_ID_LENOVO_X12_TAB2: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: + case USB_DEVICE_ID_LENOVO_X1_TAB2: + case USB_DEVICE_ID_LENOVO_X1_TAB3: return lenovo_event_tp10ubkbd(hdev, field, usage, value); default: return 0; @@ -753,7 +913,7 @@ static ssize_t attr_press_to_select_show_tpkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); + return sysfs_emit(buf, "%u\n", data_pointer->press_to_select); } static ssize_t attr_press_to_select_store_tpkbd(struct device *dev, @@ -783,7 +943,7 @@ static ssize_t attr_dragging_show_tpkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); + return sysfs_emit(buf, "%u\n", data_pointer->dragging); } static ssize_t attr_dragging_store_tpkbd(struct device *dev, @@ -813,7 +973,7 @@ static ssize_t attr_release_to_select_show_tpkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); + return sysfs_emit(buf, "%u\n", data_pointer->release_to_select); } static ssize_t attr_release_to_select_store_tpkbd(struct device *dev, @@ -843,7 +1003,7 @@ static ssize_t attr_select_right_show_tpkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); + return sysfs_emit(buf, "%u\n", data_pointer->select_right); } static ssize_t attr_select_right_store_tpkbd(struct device *dev, @@ -873,8 +1033,7 @@ static ssize_t attr_sensitivity_show_tpkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", - data_pointer->sensitivity); + return sysfs_emit(buf, "%u\n", data_pointer->sensitivity); } static ssize_t attr_sensitivity_store_tpkbd(struct device *dev, @@ -902,8 +1061,7 @@ static ssize_t attr_press_speed_show_tpkbd(struct device *dev, struct hid_device *hdev = to_hid_device(dev); struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); - return snprintf(buf, PAGE_SIZE, "%u\n", - data_pointer->press_speed); + return sysfs_emit(buf, "%u\n", data_pointer->press_speed); } static ssize_t attr_press_speed_store_tpkbd(struct device *dev, @@ -1001,8 +1159,12 @@ static int lenovo_led_brightness_set(struct led_classdev *led_cdev, case USB_DEVICE_ID_LENOVO_TPKBD: lenovo_led_set_tpkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_X12_TAB: + case USB_DEVICE_ID_LENOVO_X12_TAB2: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: + case USB_DEVICE_ID_LENOVO_X1_TAB2: + case USB_DEVICE_ID_LENOVO_X1_TAB3: ret = lenovo_led_set_tp10ubkbd(hdev, tp10ubkbd_led[led_nr], value); break; } @@ -1126,26 +1288,11 @@ static int lenovo_probe_cptkbd(struct hid_device *hdev) } hid_set_drvdata(hdev, cptkbd_data); - /* - * Tell the keyboard a driver understands it, and turn F7, F9, F11 into - * regular keys (Compact only) - */ - if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD || - hdev->product == USB_DEVICE_ID_LENOVO_CBTKBD) { - ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); - if (ret) - hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); - } - - /* Switch middle button to native mode */ - ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01); - if (ret) - hid_warn(hdev, "Failed to switch middle button: %d\n", ret); - /* Set keyboard settings to known state */ cptkbd_data->middlebutton_state = 0; cptkbd_data->fn_lock = true; cptkbd_data->sensitivity = 0x05; + cptkbd_data->middleclick_workaround_cptkbd = true; lenovo_features_set_cptkbd(hdev); ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd); @@ -1201,8 +1348,15 @@ static int lenovo_probe_tp10ubkbd(struct hid_device *hdev) * We cannot read the state, only set it, so we force it to on here * (which should be a no-op) to make sure that our state matches the * keyboard's FN-lock state. This is the same as what Windows does. + * + * For X12 TAB and TAB2, the default windows behaviour Fn-lock Off. + * Adding additional check to ensure the behaviour in case of + * Thinkpad X12 Tabs. */ - data->fn_lock = true; + + data->fn_lock = !(hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB || + hdev->product == USB_DEVICE_ID_LENOVO_X12_TAB2); + lenovo_led_set_tp10ubkbd(hdev, TP10UBKBD_FN_LOCK_LED, data->fn_lock); ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tp10ubkbd); @@ -1246,8 +1400,12 @@ static int lenovo_probe(struct hid_device *hdev, case USB_DEVICE_ID_LENOVO_TPIIBTKBD: ret = lenovo_probe_cptkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_X12_TAB: + case USB_DEVICE_ID_LENOVO_X12_TAB2: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: + case USB_DEVICE_ID_LENOVO_X1_TAB2: + case USB_DEVICE_ID_LENOVO_X1_TAB3: ret = lenovo_probe_tp10ubkbd(hdev); break; default: @@ -1264,6 +1422,24 @@ err: return ret; } +#ifdef CONFIG_PM +static int lenovo_reset_resume(struct hid_device *hdev) +{ + switch (hdev->product) { + case USB_DEVICE_ID_LENOVO_CUSBKBD: + case USB_DEVICE_ID_LENOVO_TPIIUSBKBD: + if (hdev->type == HID_TYPE_USBMOUSE) + lenovo_features_set_cptkbd(hdev); + + break; + default: + break; + } + + return 0; +} +#endif + static void lenovo_remove_tpkbd(struct hid_device *hdev) { struct lenovo_drvdata *data_pointer = hid_get_drvdata(hdev); @@ -1314,8 +1490,12 @@ static void lenovo_remove(struct hid_device *hdev) case USB_DEVICE_ID_LENOVO_TPIIBTKBD: lenovo_remove_cptkbd(hdev); break; + case USB_DEVICE_ID_LENOVO_X12_TAB: + case USB_DEVICE_ID_LENOVO_X12_TAB2: case USB_DEVICE_ID_LENOVO_TP10UBKBD: case USB_DEVICE_ID_LENOVO_X1_TAB: + case USB_DEVICE_ID_LENOVO_X1_TAB2: + case USB_DEVICE_ID_LENOVO_X1_TAB3: lenovo_remove_tp10ubkbd(hdev); break; } @@ -1365,6 +1545,16 @@ static const struct hid_device_id lenovo_devices[] = { */ { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB) }, + { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, + USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB2) }, + { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, + USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_TAB3) }, + { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, + USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB) }, + { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, + USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X12_TAB2) }, + { HID_DEVICE(BUS_I2C, HID_GROUP_GENERIC, + USB_VENDOR_ID_ITE, I2C_DEVICE_ID_ITE_LENOVO_YOGA_SLIM_7X_KEYBOARD) }, { } }; @@ -1380,7 +1570,11 @@ static struct hid_driver lenovo_driver = { .raw_event = lenovo_raw_event, .event = lenovo_event, .report_fixup = lenovo_report_fixup, +#ifdef CONFIG_PM + .reset_resume = lenovo_reset_resume, +#endif }; module_hid_driver(lenovo_driver); +MODULE_DESCRIPTION("HID driver for IBM/Lenovo"); MODULE_LICENSE("GPL"); |
