1c1dcad2dSBernhard Seibold /* 26a5b414bSJamie Lentin * HID driver for Lenovo: 36a5b414bSJamie Lentin * - ThinkPad USB Keyboard with TrackPoint (tpkbd) 4f3d4ff0eSJamie Lentin * - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd) 5f3d4ff0eSJamie Lentin * - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd) 6c1dcad2dSBernhard Seibold * 7c1dcad2dSBernhard Seibold * Copyright (c) 2012 Bernhard Seibold 8f3d4ff0eSJamie Lentin * Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk> 9c1dcad2dSBernhard Seibold */ 10c1dcad2dSBernhard Seibold 11c1dcad2dSBernhard Seibold /* 12c1dcad2dSBernhard Seibold * This program is free software; you can redistribute it and/or modify it 13c1dcad2dSBernhard Seibold * under the terms of the GNU General Public License as published by the Free 14c1dcad2dSBernhard Seibold * Software Foundation; either version 2 of the License, or (at your option) 15c1dcad2dSBernhard Seibold * any later version. 16c1dcad2dSBernhard Seibold */ 17c1dcad2dSBernhard Seibold 18c1dcad2dSBernhard Seibold #include <linux/module.h> 19c1dcad2dSBernhard Seibold #include <linux/sysfs.h> 20c1dcad2dSBernhard Seibold #include <linux/device.h> 21c1dcad2dSBernhard Seibold #include <linux/hid.h> 22c1dcad2dSBernhard Seibold #include <linux/input.h> 23c1dcad2dSBernhard Seibold #include <linux/leds.h> 24c1dcad2dSBernhard Seibold 25c1dcad2dSBernhard Seibold #include "hid-ids.h" 26c1dcad2dSBernhard Seibold 2794723bfaSJamie Lentin struct lenovo_drvdata_tpkbd { 28c1dcad2dSBernhard Seibold int led_state; 29c1dcad2dSBernhard Seibold struct led_classdev led_mute; 30c1dcad2dSBernhard Seibold struct led_classdev led_micmute; 31c1dcad2dSBernhard Seibold int press_to_select; 32c1dcad2dSBernhard Seibold int dragging; 33c1dcad2dSBernhard Seibold int release_to_select; 34c1dcad2dSBernhard Seibold int select_right; 35c1dcad2dSBernhard Seibold int sensitivity; 36c1dcad2dSBernhard Seibold int press_speed; 37c1dcad2dSBernhard Seibold }; 38c1dcad2dSBernhard Seibold 39f3d4ff0eSJamie Lentin struct lenovo_drvdata_cptkbd { 40f3d4ff0eSJamie Lentin bool fn_lock; 41f3d4ff0eSJamie Lentin }; 42f3d4ff0eSJamie Lentin 43c1dcad2dSBernhard Seibold #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) 44c1dcad2dSBernhard Seibold 4594723bfaSJamie Lentin static int lenovo_input_mapping_tpkbd(struct hid_device *hdev, 46c1dcad2dSBernhard Seibold struct hid_input *hi, struct hid_field *field, 47c1dcad2dSBernhard Seibold struct hid_usage *usage, unsigned long **bit, int *max) 48c1dcad2dSBernhard Seibold { 490c521836SBenjamin Tissoires if (usage->hid == (HID_UP_BUTTON | 0x0010)) { 506a5b414bSJamie Lentin /* This sub-device contains trackpoint, mark it */ 510c521836SBenjamin Tissoires hid_set_drvdata(hdev, (void *)1); 52c1dcad2dSBernhard Seibold map_key_clear(KEY_MICMUTE); 53c1dcad2dSBernhard Seibold return 1; 54c1dcad2dSBernhard Seibold } 55c1dcad2dSBernhard Seibold return 0; 56c1dcad2dSBernhard Seibold } 57c1dcad2dSBernhard Seibold 58f3d4ff0eSJamie Lentin static int lenovo_input_mapping_cptkbd(struct hid_device *hdev, 59f3d4ff0eSJamie Lentin struct hid_input *hi, struct hid_field *field, 60f3d4ff0eSJamie Lentin struct hid_usage *usage, unsigned long **bit, int *max) 61f3d4ff0eSJamie Lentin { 62f3d4ff0eSJamie Lentin /* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */ 63f3d4ff0eSJamie Lentin if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR || 64f3d4ff0eSJamie Lentin (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) { 65f3d4ff0eSJamie Lentin switch (usage->hid & HID_USAGE) { 66f3d4ff0eSJamie Lentin case 0x00f1: /* Fn-F4: Mic mute */ 67f3d4ff0eSJamie Lentin map_key_clear(KEY_MICMUTE); 68f3d4ff0eSJamie Lentin return 1; 69f3d4ff0eSJamie Lentin case 0x00f2: /* Fn-F5: Brightness down */ 70f3d4ff0eSJamie Lentin map_key_clear(KEY_BRIGHTNESSDOWN); 71f3d4ff0eSJamie Lentin return 1; 72f3d4ff0eSJamie Lentin case 0x00f3: /* Fn-F6: Brightness up */ 73f3d4ff0eSJamie Lentin map_key_clear(KEY_BRIGHTNESSUP); 74f3d4ff0eSJamie Lentin return 1; 75f3d4ff0eSJamie Lentin case 0x00f4: /* Fn-F7: External display (projector) */ 76f3d4ff0eSJamie Lentin map_key_clear(KEY_SWITCHVIDEOMODE); 77f3d4ff0eSJamie Lentin return 1; 78f3d4ff0eSJamie Lentin case 0x00f5: /* Fn-F8: Wireless */ 79f3d4ff0eSJamie Lentin map_key_clear(KEY_WLAN); 80f3d4ff0eSJamie Lentin return 1; 81f3d4ff0eSJamie Lentin case 0x00f6: /* Fn-F9: Control panel */ 82f3d4ff0eSJamie Lentin map_key_clear(KEY_CONFIG); 83f3d4ff0eSJamie Lentin return 1; 84f3d4ff0eSJamie Lentin case 0x00f8: /* Fn-F11: View open applications (3 boxes) */ 85f3d4ff0eSJamie Lentin map_key_clear(KEY_SCALE); 86f3d4ff0eSJamie Lentin return 1; 87*5556eb14SJamie Lentin case 0x00f9: /* Fn-F12: Open My computer (6 boxes) USB-only */ 88f3d4ff0eSJamie Lentin /* NB: This mapping is invented in raw_event below */ 89f3d4ff0eSJamie Lentin map_key_clear(KEY_FILE); 90f3d4ff0eSJamie Lentin return 1; 91*5556eb14SJamie Lentin case 0x00fa: /* Fn-Esc: Fn-lock toggle */ 92*5556eb14SJamie Lentin map_key_clear(KEY_FN_ESC); 93*5556eb14SJamie Lentin return 1; 94f3d4ff0eSJamie Lentin } 95f3d4ff0eSJamie Lentin } 96f3d4ff0eSJamie Lentin 97f3d4ff0eSJamie Lentin return 0; 98f3d4ff0eSJamie Lentin } 99f3d4ff0eSJamie Lentin 1006a5b414bSJamie Lentin static int lenovo_input_mapping(struct hid_device *hdev, 1016a5b414bSJamie Lentin struct hid_input *hi, struct hid_field *field, 1026a5b414bSJamie Lentin struct hid_usage *usage, unsigned long **bit, int *max) 1036a5b414bSJamie Lentin { 1046a5b414bSJamie Lentin switch (hdev->product) { 1056a5b414bSJamie Lentin case USB_DEVICE_ID_LENOVO_TPKBD: 1066a5b414bSJamie Lentin return lenovo_input_mapping_tpkbd(hdev, hi, field, 1076a5b414bSJamie Lentin usage, bit, max); 108f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CUSBKBD: 109f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CBTKBD: 110f3d4ff0eSJamie Lentin return lenovo_input_mapping_cptkbd(hdev, hi, field, 111f3d4ff0eSJamie Lentin usage, bit, max); 1126a5b414bSJamie Lentin default: 1136a5b414bSJamie Lentin return 0; 1146a5b414bSJamie Lentin } 1156a5b414bSJamie Lentin } 1166a5b414bSJamie Lentin 117c1dcad2dSBernhard Seibold #undef map_key_clear 118c1dcad2dSBernhard Seibold 119f3d4ff0eSJamie Lentin /* Send a config command to the keyboard */ 120f3d4ff0eSJamie Lentin static int lenovo_send_cmd_cptkbd(struct hid_device *hdev, 121f3d4ff0eSJamie Lentin unsigned char byte2, unsigned char byte3) 122f3d4ff0eSJamie Lentin { 123f3d4ff0eSJamie Lentin int ret; 124f3d4ff0eSJamie Lentin unsigned char buf[] = {0x18, byte2, byte3}; 125f3d4ff0eSJamie Lentin 126f3d4ff0eSJamie Lentin switch (hdev->product) { 127f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CUSBKBD: 128f3d4ff0eSJamie Lentin ret = hid_hw_raw_request(hdev, 0x13, buf, sizeof(buf), 129f3d4ff0eSJamie Lentin HID_FEATURE_REPORT, HID_REQ_SET_REPORT); 130f3d4ff0eSJamie Lentin break; 131f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CBTKBD: 132f3d4ff0eSJamie Lentin ret = hid_hw_output_report(hdev, buf, sizeof(buf)); 133f3d4ff0eSJamie Lentin break; 134f3d4ff0eSJamie Lentin default: 135f3d4ff0eSJamie Lentin ret = -EINVAL; 136f3d4ff0eSJamie Lentin break; 137f3d4ff0eSJamie Lentin } 138f3d4ff0eSJamie Lentin 139f3d4ff0eSJamie Lentin return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */ 140f3d4ff0eSJamie Lentin } 141f3d4ff0eSJamie Lentin 142f3d4ff0eSJamie Lentin static void lenovo_features_set_cptkbd(struct hid_device *hdev) 143f3d4ff0eSJamie Lentin { 144f3d4ff0eSJamie Lentin int ret; 145f3d4ff0eSJamie Lentin struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); 146f3d4ff0eSJamie Lentin 147f3d4ff0eSJamie Lentin ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock); 148f3d4ff0eSJamie Lentin if (ret) 149f3d4ff0eSJamie Lentin hid_err(hdev, "Fn-lock setting failed: %d\n", ret); 150f3d4ff0eSJamie Lentin } 151f3d4ff0eSJamie Lentin 152f3d4ff0eSJamie Lentin static ssize_t attr_fn_lock_show_cptkbd(struct device *dev, 153f3d4ff0eSJamie Lentin struct device_attribute *attr, 154f3d4ff0eSJamie Lentin char *buf) 155f3d4ff0eSJamie Lentin { 156f3d4ff0eSJamie Lentin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 157f3d4ff0eSJamie Lentin struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); 158f3d4ff0eSJamie Lentin 159f3d4ff0eSJamie Lentin return snprintf(buf, PAGE_SIZE, "%u\n", cptkbd_data->fn_lock); 160f3d4ff0eSJamie Lentin } 161f3d4ff0eSJamie Lentin 162f3d4ff0eSJamie Lentin static ssize_t attr_fn_lock_store_cptkbd(struct device *dev, 163f3d4ff0eSJamie Lentin struct device_attribute *attr, 164f3d4ff0eSJamie Lentin const char *buf, 165f3d4ff0eSJamie Lentin size_t count) 166f3d4ff0eSJamie Lentin { 167f3d4ff0eSJamie Lentin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 168f3d4ff0eSJamie Lentin struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev); 169f3d4ff0eSJamie Lentin int value; 170f3d4ff0eSJamie Lentin 171f3d4ff0eSJamie Lentin if (kstrtoint(buf, 10, &value)) 172f3d4ff0eSJamie Lentin return -EINVAL; 173f3d4ff0eSJamie Lentin if (value < 0 || value > 1) 174f3d4ff0eSJamie Lentin return -EINVAL; 175f3d4ff0eSJamie Lentin 176f3d4ff0eSJamie Lentin cptkbd_data->fn_lock = !!value; 177f3d4ff0eSJamie Lentin lenovo_features_set_cptkbd(hdev); 178f3d4ff0eSJamie Lentin 179f3d4ff0eSJamie Lentin return count; 180f3d4ff0eSJamie Lentin } 181f3d4ff0eSJamie Lentin 182f3d4ff0eSJamie Lentin static struct device_attribute dev_attr_fn_lock_cptkbd = 183f3d4ff0eSJamie Lentin __ATTR(fn_lock, S_IWUSR | S_IRUGO, 184f3d4ff0eSJamie Lentin attr_fn_lock_show_cptkbd, 185f3d4ff0eSJamie Lentin attr_fn_lock_store_cptkbd); 186f3d4ff0eSJamie Lentin 187f3d4ff0eSJamie Lentin static struct attribute *lenovo_attributes_cptkbd[] = { 188f3d4ff0eSJamie Lentin &dev_attr_fn_lock_cptkbd.attr, 189f3d4ff0eSJamie Lentin NULL 190f3d4ff0eSJamie Lentin }; 191f3d4ff0eSJamie Lentin 192f3d4ff0eSJamie Lentin static const struct attribute_group lenovo_attr_group_cptkbd = { 193f3d4ff0eSJamie Lentin .attrs = lenovo_attributes_cptkbd, 194f3d4ff0eSJamie Lentin }; 195f3d4ff0eSJamie Lentin 196f3d4ff0eSJamie Lentin static int lenovo_raw_event(struct hid_device *hdev, 197f3d4ff0eSJamie Lentin struct hid_report *report, u8 *data, int size) 198f3d4ff0eSJamie Lentin { 199f3d4ff0eSJamie Lentin /* 200f3d4ff0eSJamie Lentin * Compact USB keyboard's Fn-F12 report holds down many other keys, and 201f3d4ff0eSJamie Lentin * its own key is outside the usage page range. Remove extra 202f3d4ff0eSJamie Lentin * keypresses and remap to inside usage page. 203f3d4ff0eSJamie Lentin */ 204f3d4ff0eSJamie Lentin if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD 205f3d4ff0eSJamie Lentin && size == 3 206f3d4ff0eSJamie Lentin && data[0] == 0x15 207f3d4ff0eSJamie Lentin && data[1] == 0x94 208f3d4ff0eSJamie Lentin && data[2] == 0x01)) { 209*5556eb14SJamie Lentin data[1] = 0x00; 210*5556eb14SJamie Lentin data[2] = 0x01; 211f3d4ff0eSJamie Lentin } 212f3d4ff0eSJamie Lentin 213f3d4ff0eSJamie Lentin return 0; 214f3d4ff0eSJamie Lentin } 215f3d4ff0eSJamie Lentin 21694723bfaSJamie Lentin static int lenovo_features_set_tpkbd(struct hid_device *hdev) 217c1dcad2dSBernhard Seibold { 218c1dcad2dSBernhard Seibold struct hid_report *report; 21994723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 220c1dcad2dSBernhard Seibold 221c1dcad2dSBernhard Seibold report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; 222c1dcad2dSBernhard Seibold 223c1dcad2dSBernhard Seibold report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; 224c1dcad2dSBernhard Seibold report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; 225c1dcad2dSBernhard Seibold report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; 226c1dcad2dSBernhard Seibold report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; 227c1dcad2dSBernhard Seibold report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver 228c1dcad2dSBernhard Seibold report->field[2]->value[0] = data_pointer->sensitivity; 229c1dcad2dSBernhard Seibold report->field[3]->value[0] = data_pointer->press_speed; 230c1dcad2dSBernhard Seibold 231d8814272SBenjamin Tissoires hid_hw_request(hdev, report, HID_REQ_SET_REPORT); 232c1dcad2dSBernhard Seibold return 0; 233c1dcad2dSBernhard Seibold } 234c1dcad2dSBernhard Seibold 23594723bfaSJamie Lentin static ssize_t attr_press_to_select_show_tpkbd(struct device *dev, 236c1dcad2dSBernhard Seibold struct device_attribute *attr, 237c1dcad2dSBernhard Seibold char *buf) 238c1dcad2dSBernhard Seibold { 239832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 24094723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 241c1dcad2dSBernhard Seibold 242c1dcad2dSBernhard Seibold return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); 243c1dcad2dSBernhard Seibold } 244c1dcad2dSBernhard Seibold 24594723bfaSJamie Lentin static ssize_t attr_press_to_select_store_tpkbd(struct device *dev, 246c1dcad2dSBernhard Seibold struct device_attribute *attr, 247c1dcad2dSBernhard Seibold const char *buf, 248c1dcad2dSBernhard Seibold size_t count) 249c1dcad2dSBernhard Seibold { 250832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 25194723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 252c1dcad2dSBernhard Seibold int value; 253c1dcad2dSBernhard Seibold 254c1dcad2dSBernhard Seibold if (kstrtoint(buf, 10, &value)) 255c1dcad2dSBernhard Seibold return -EINVAL; 256c1dcad2dSBernhard Seibold if (value < 0 || value > 1) 257c1dcad2dSBernhard Seibold return -EINVAL; 258c1dcad2dSBernhard Seibold 259c1dcad2dSBernhard Seibold data_pointer->press_to_select = value; 26094723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 261c1dcad2dSBernhard Seibold 262c1dcad2dSBernhard Seibold return count; 263c1dcad2dSBernhard Seibold } 264c1dcad2dSBernhard Seibold 26594723bfaSJamie Lentin static ssize_t attr_dragging_show_tpkbd(struct device *dev, 266c1dcad2dSBernhard Seibold struct device_attribute *attr, 267c1dcad2dSBernhard Seibold char *buf) 268c1dcad2dSBernhard Seibold { 269832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 27094723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 271c1dcad2dSBernhard Seibold 272c1dcad2dSBernhard Seibold return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); 273c1dcad2dSBernhard Seibold } 274c1dcad2dSBernhard Seibold 27594723bfaSJamie Lentin static ssize_t attr_dragging_store_tpkbd(struct device *dev, 276c1dcad2dSBernhard Seibold struct device_attribute *attr, 277c1dcad2dSBernhard Seibold const char *buf, 278c1dcad2dSBernhard Seibold size_t count) 279c1dcad2dSBernhard Seibold { 280832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 28194723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 282c1dcad2dSBernhard Seibold int value; 283c1dcad2dSBernhard Seibold 284c1dcad2dSBernhard Seibold if (kstrtoint(buf, 10, &value)) 285c1dcad2dSBernhard Seibold return -EINVAL; 286c1dcad2dSBernhard Seibold if (value < 0 || value > 1) 287c1dcad2dSBernhard Seibold return -EINVAL; 288c1dcad2dSBernhard Seibold 289c1dcad2dSBernhard Seibold data_pointer->dragging = value; 29094723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 291c1dcad2dSBernhard Seibold 292c1dcad2dSBernhard Seibold return count; 293c1dcad2dSBernhard Seibold } 294c1dcad2dSBernhard Seibold 29594723bfaSJamie Lentin static ssize_t attr_release_to_select_show_tpkbd(struct device *dev, 296c1dcad2dSBernhard Seibold struct device_attribute *attr, 297c1dcad2dSBernhard Seibold char *buf) 298c1dcad2dSBernhard Seibold { 299832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 30094723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 301c1dcad2dSBernhard Seibold 302c1dcad2dSBernhard Seibold return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); 303c1dcad2dSBernhard Seibold } 304c1dcad2dSBernhard Seibold 30594723bfaSJamie Lentin static ssize_t attr_release_to_select_store_tpkbd(struct device *dev, 306c1dcad2dSBernhard Seibold struct device_attribute *attr, 307c1dcad2dSBernhard Seibold const char *buf, 308c1dcad2dSBernhard Seibold size_t count) 309c1dcad2dSBernhard Seibold { 310832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 31194723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 312c1dcad2dSBernhard Seibold int value; 313c1dcad2dSBernhard Seibold 314c1dcad2dSBernhard Seibold if (kstrtoint(buf, 10, &value)) 315c1dcad2dSBernhard Seibold return -EINVAL; 316c1dcad2dSBernhard Seibold if (value < 0 || value > 1) 317c1dcad2dSBernhard Seibold return -EINVAL; 318c1dcad2dSBernhard Seibold 319c1dcad2dSBernhard Seibold data_pointer->release_to_select = value; 32094723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 321c1dcad2dSBernhard Seibold 322c1dcad2dSBernhard Seibold return count; 323c1dcad2dSBernhard Seibold } 324c1dcad2dSBernhard Seibold 32594723bfaSJamie Lentin static ssize_t attr_select_right_show_tpkbd(struct device *dev, 326c1dcad2dSBernhard Seibold struct device_attribute *attr, 327c1dcad2dSBernhard Seibold char *buf) 328c1dcad2dSBernhard Seibold { 329832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 33094723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 331c1dcad2dSBernhard Seibold 332c1dcad2dSBernhard Seibold return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); 333c1dcad2dSBernhard Seibold } 334c1dcad2dSBernhard Seibold 33594723bfaSJamie Lentin static ssize_t attr_select_right_store_tpkbd(struct device *dev, 336c1dcad2dSBernhard Seibold struct device_attribute *attr, 337c1dcad2dSBernhard Seibold const char *buf, 338c1dcad2dSBernhard Seibold size_t count) 339c1dcad2dSBernhard Seibold { 340832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 34194723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 342c1dcad2dSBernhard Seibold int value; 343c1dcad2dSBernhard Seibold 344c1dcad2dSBernhard Seibold if (kstrtoint(buf, 10, &value)) 345c1dcad2dSBernhard Seibold return -EINVAL; 346c1dcad2dSBernhard Seibold if (value < 0 || value > 1) 347c1dcad2dSBernhard Seibold return -EINVAL; 348c1dcad2dSBernhard Seibold 349c1dcad2dSBernhard Seibold data_pointer->select_right = value; 35094723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 351c1dcad2dSBernhard Seibold 352c1dcad2dSBernhard Seibold return count; 353c1dcad2dSBernhard Seibold } 354c1dcad2dSBernhard Seibold 35594723bfaSJamie Lentin static ssize_t attr_sensitivity_show_tpkbd(struct device *dev, 356c1dcad2dSBernhard Seibold struct device_attribute *attr, 357c1dcad2dSBernhard Seibold char *buf) 358c1dcad2dSBernhard Seibold { 359832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 36094723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 361c1dcad2dSBernhard Seibold 362c1dcad2dSBernhard Seibold return snprintf(buf, PAGE_SIZE, "%u\n", 363c1dcad2dSBernhard Seibold data_pointer->sensitivity); 364c1dcad2dSBernhard Seibold } 365c1dcad2dSBernhard Seibold 36694723bfaSJamie Lentin static ssize_t attr_sensitivity_store_tpkbd(struct device *dev, 367c1dcad2dSBernhard Seibold struct device_attribute *attr, 368c1dcad2dSBernhard Seibold const char *buf, 369c1dcad2dSBernhard Seibold size_t count) 370c1dcad2dSBernhard Seibold { 371832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 37294723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 373c1dcad2dSBernhard Seibold int value; 374c1dcad2dSBernhard Seibold 375c1dcad2dSBernhard Seibold if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) 376c1dcad2dSBernhard Seibold return -EINVAL; 377c1dcad2dSBernhard Seibold 378c1dcad2dSBernhard Seibold data_pointer->sensitivity = value; 37994723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 380c1dcad2dSBernhard Seibold 381c1dcad2dSBernhard Seibold return count; 382c1dcad2dSBernhard Seibold } 383c1dcad2dSBernhard Seibold 38494723bfaSJamie Lentin static ssize_t attr_press_speed_show_tpkbd(struct device *dev, 385c1dcad2dSBernhard Seibold struct device_attribute *attr, 386c1dcad2dSBernhard Seibold char *buf) 387c1dcad2dSBernhard Seibold { 388832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 38994723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 390c1dcad2dSBernhard Seibold 391c1dcad2dSBernhard Seibold return snprintf(buf, PAGE_SIZE, "%u\n", 392c1dcad2dSBernhard Seibold data_pointer->press_speed); 393c1dcad2dSBernhard Seibold } 394c1dcad2dSBernhard Seibold 39594723bfaSJamie Lentin static ssize_t attr_press_speed_store_tpkbd(struct device *dev, 396c1dcad2dSBernhard Seibold struct device_attribute *attr, 397c1dcad2dSBernhard Seibold const char *buf, 398c1dcad2dSBernhard Seibold size_t count) 399c1dcad2dSBernhard Seibold { 400832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 40194723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 402c1dcad2dSBernhard Seibold int value; 403c1dcad2dSBernhard Seibold 404c1dcad2dSBernhard Seibold if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) 405c1dcad2dSBernhard Seibold return -EINVAL; 406c1dcad2dSBernhard Seibold 407c1dcad2dSBernhard Seibold data_pointer->press_speed = value; 40894723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 409c1dcad2dSBernhard Seibold 410c1dcad2dSBernhard Seibold return count; 411c1dcad2dSBernhard Seibold } 412c1dcad2dSBernhard Seibold 41394723bfaSJamie Lentin static struct device_attribute dev_attr_press_to_select_tpkbd = 414c1dcad2dSBernhard Seibold __ATTR(press_to_select, S_IWUSR | S_IRUGO, 41594723bfaSJamie Lentin attr_press_to_select_show_tpkbd, 41694723bfaSJamie Lentin attr_press_to_select_store_tpkbd); 417c1dcad2dSBernhard Seibold 41894723bfaSJamie Lentin static struct device_attribute dev_attr_dragging_tpkbd = 419c1dcad2dSBernhard Seibold __ATTR(dragging, S_IWUSR | S_IRUGO, 42094723bfaSJamie Lentin attr_dragging_show_tpkbd, 42194723bfaSJamie Lentin attr_dragging_store_tpkbd); 422c1dcad2dSBernhard Seibold 42394723bfaSJamie Lentin static struct device_attribute dev_attr_release_to_select_tpkbd = 424c1dcad2dSBernhard Seibold __ATTR(release_to_select, S_IWUSR | S_IRUGO, 42594723bfaSJamie Lentin attr_release_to_select_show_tpkbd, 42694723bfaSJamie Lentin attr_release_to_select_store_tpkbd); 427c1dcad2dSBernhard Seibold 42894723bfaSJamie Lentin static struct device_attribute dev_attr_select_right_tpkbd = 429c1dcad2dSBernhard Seibold __ATTR(select_right, S_IWUSR | S_IRUGO, 43094723bfaSJamie Lentin attr_select_right_show_tpkbd, 43194723bfaSJamie Lentin attr_select_right_store_tpkbd); 432c1dcad2dSBernhard Seibold 43394723bfaSJamie Lentin static struct device_attribute dev_attr_sensitivity_tpkbd = 434c1dcad2dSBernhard Seibold __ATTR(sensitivity, S_IWUSR | S_IRUGO, 43594723bfaSJamie Lentin attr_sensitivity_show_tpkbd, 43694723bfaSJamie Lentin attr_sensitivity_store_tpkbd); 437c1dcad2dSBernhard Seibold 43894723bfaSJamie Lentin static struct device_attribute dev_attr_press_speed_tpkbd = 439c1dcad2dSBernhard Seibold __ATTR(press_speed, S_IWUSR | S_IRUGO, 44094723bfaSJamie Lentin attr_press_speed_show_tpkbd, 44194723bfaSJamie Lentin attr_press_speed_store_tpkbd); 442c1dcad2dSBernhard Seibold 44394723bfaSJamie Lentin static struct attribute *lenovo_attributes_tpkbd[] = { 44494723bfaSJamie Lentin &dev_attr_press_to_select_tpkbd.attr, 44594723bfaSJamie Lentin &dev_attr_dragging_tpkbd.attr, 44694723bfaSJamie Lentin &dev_attr_release_to_select_tpkbd.attr, 44794723bfaSJamie Lentin &dev_attr_select_right_tpkbd.attr, 44894723bfaSJamie Lentin &dev_attr_sensitivity_tpkbd.attr, 44994723bfaSJamie Lentin &dev_attr_press_speed_tpkbd.attr, 450c1dcad2dSBernhard Seibold NULL 451c1dcad2dSBernhard Seibold }; 452c1dcad2dSBernhard Seibold 45394723bfaSJamie Lentin static const struct attribute_group lenovo_attr_group_tpkbd = { 45494723bfaSJamie Lentin .attrs = lenovo_attributes_tpkbd, 455c1dcad2dSBernhard Seibold }; 456c1dcad2dSBernhard Seibold 45794723bfaSJamie Lentin static enum led_brightness lenovo_led_brightness_get_tpkbd( 458c1dcad2dSBernhard Seibold struct led_classdev *led_cdev) 459c1dcad2dSBernhard Seibold { 460832fbeaeSAxel Lin struct device *dev = led_cdev->dev->parent; 461832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 46294723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 463c1dcad2dSBernhard Seibold int led_nr = 0; 464c1dcad2dSBernhard Seibold 465c1dcad2dSBernhard Seibold if (led_cdev == &data_pointer->led_micmute) 466c1dcad2dSBernhard Seibold led_nr = 1; 467c1dcad2dSBernhard Seibold 468c1dcad2dSBernhard Seibold return data_pointer->led_state & (1 << led_nr) 469c1dcad2dSBernhard Seibold ? LED_FULL 470c1dcad2dSBernhard Seibold : LED_OFF; 471c1dcad2dSBernhard Seibold } 472c1dcad2dSBernhard Seibold 47394723bfaSJamie Lentin static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev, 474c1dcad2dSBernhard Seibold enum led_brightness value) 475c1dcad2dSBernhard Seibold { 476832fbeaeSAxel Lin struct device *dev = led_cdev->dev->parent; 477832fbeaeSAxel Lin struct hid_device *hdev = container_of(dev, struct hid_device, dev); 47894723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 479c1dcad2dSBernhard Seibold struct hid_report *report; 480c1dcad2dSBernhard Seibold int led_nr = 0; 481c1dcad2dSBernhard Seibold 482c1dcad2dSBernhard Seibold if (led_cdev == &data_pointer->led_micmute) 483c1dcad2dSBernhard Seibold led_nr = 1; 484c1dcad2dSBernhard Seibold 485c1dcad2dSBernhard Seibold if (value == LED_OFF) 486c1dcad2dSBernhard Seibold data_pointer->led_state &= ~(1 << led_nr); 487c1dcad2dSBernhard Seibold else 488c1dcad2dSBernhard Seibold data_pointer->led_state |= 1 << led_nr; 489c1dcad2dSBernhard Seibold 490c1dcad2dSBernhard Seibold report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; 491c1dcad2dSBernhard Seibold report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; 492c1dcad2dSBernhard Seibold report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; 493d8814272SBenjamin Tissoires hid_hw_request(hdev, report, HID_REQ_SET_REPORT); 494c1dcad2dSBernhard Seibold } 495c1dcad2dSBernhard Seibold 49694723bfaSJamie Lentin static int lenovo_probe_tpkbd(struct hid_device *hdev) 497c1dcad2dSBernhard Seibold { 498c1dcad2dSBernhard Seibold struct device *dev = &hdev->dev; 49994723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer; 500c1dcad2dSBernhard Seibold size_t name_sz = strlen(dev_name(dev)) + 16; 501c1dcad2dSBernhard Seibold char *name_mute, *name_micmute; 50201ab35f1SBenjamin Tissoires int i; 503e0a6aad6SJamie Lentin int ret; 5040a9cd0a8SKees Cook 5056a5b414bSJamie Lentin /* 5066a5b414bSJamie Lentin * Only register extra settings against subdevice where input_mapping 5076a5b414bSJamie Lentin * set drvdata to 1, i.e. the trackpoint. 5086a5b414bSJamie Lentin */ 5096a5b414bSJamie Lentin if (!hid_get_drvdata(hdev)) 5106a5b414bSJamie Lentin return 0; 5116a5b414bSJamie Lentin 5126a5b414bSJamie Lentin hid_set_drvdata(hdev, NULL); 5136a5b414bSJamie Lentin 5140a9cd0a8SKees Cook /* Validate required reports. */ 5150a9cd0a8SKees Cook for (i = 0; i < 4; i++) { 5160a9cd0a8SKees Cook if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1)) 5170a9cd0a8SKees Cook return -ENODEV; 5180a9cd0a8SKees Cook } 5190a9cd0a8SKees Cook if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2)) 5200a9cd0a8SKees Cook return -ENODEV; 521c1dcad2dSBernhard Seibold 522e0a6aad6SJamie Lentin ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd); 523e0a6aad6SJamie Lentin if (ret) 524e0a6aad6SJamie Lentin hid_warn(hdev, "Could not create sysfs group: %d\n", ret); 525c1dcad2dSBernhard Seibold 52601ab35f1SBenjamin Tissoires data_pointer = devm_kzalloc(&hdev->dev, 52794723bfaSJamie Lentin sizeof(struct lenovo_drvdata_tpkbd), 52801ab35f1SBenjamin Tissoires GFP_KERNEL); 529c1dcad2dSBernhard Seibold if (data_pointer == NULL) { 530c1dcad2dSBernhard Seibold hid_err(hdev, "Could not allocate memory for driver data\n"); 531c1dcad2dSBernhard Seibold return -ENOMEM; 532c1dcad2dSBernhard Seibold } 533c1dcad2dSBernhard Seibold 534c1dcad2dSBernhard Seibold // set same default values as windows driver 535c1dcad2dSBernhard Seibold data_pointer->sensitivity = 0xa0; 536c1dcad2dSBernhard Seibold data_pointer->press_speed = 0x38; 537c1dcad2dSBernhard Seibold 53801ab35f1SBenjamin Tissoires name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); 53901ab35f1SBenjamin Tissoires name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL); 54001ab35f1SBenjamin Tissoires if (name_mute == NULL || name_micmute == NULL) { 541c1dcad2dSBernhard Seibold hid_err(hdev, "Could not allocate memory for led data\n"); 54201ab35f1SBenjamin Tissoires return -ENOMEM; 543c1dcad2dSBernhard Seibold } 544c1dcad2dSBernhard Seibold snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); 545c1dcad2dSBernhard Seibold snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); 546c1dcad2dSBernhard Seibold 547c1dcad2dSBernhard Seibold hid_set_drvdata(hdev, data_pointer); 548c1dcad2dSBernhard Seibold 549c1dcad2dSBernhard Seibold data_pointer->led_mute.name = name_mute; 55094723bfaSJamie Lentin data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd; 55194723bfaSJamie Lentin data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd; 552c1dcad2dSBernhard Seibold data_pointer->led_mute.dev = dev; 553c1dcad2dSBernhard Seibold led_classdev_register(dev, &data_pointer->led_mute); 554c1dcad2dSBernhard Seibold 555c1dcad2dSBernhard Seibold data_pointer->led_micmute.name = name_micmute; 55694723bfaSJamie Lentin data_pointer->led_micmute.brightness_get = 55794723bfaSJamie Lentin lenovo_led_brightness_get_tpkbd; 55894723bfaSJamie Lentin data_pointer->led_micmute.brightness_set = 55994723bfaSJamie Lentin lenovo_led_brightness_set_tpkbd; 560c1dcad2dSBernhard Seibold data_pointer->led_micmute.dev = dev; 561c1dcad2dSBernhard Seibold led_classdev_register(dev, &data_pointer->led_micmute); 562c1dcad2dSBernhard Seibold 56394723bfaSJamie Lentin lenovo_features_set_tpkbd(hdev); 564c1dcad2dSBernhard Seibold 565c1dcad2dSBernhard Seibold return 0; 566c1dcad2dSBernhard Seibold } 567c1dcad2dSBernhard Seibold 568f3d4ff0eSJamie Lentin static int lenovo_probe_cptkbd(struct hid_device *hdev) 569f3d4ff0eSJamie Lentin { 570f3d4ff0eSJamie Lentin int ret; 571f3d4ff0eSJamie Lentin struct lenovo_drvdata_cptkbd *cptkbd_data; 572f3d4ff0eSJamie Lentin 573f3d4ff0eSJamie Lentin /* All the custom action happens on the USBMOUSE device for USB */ 574f3d4ff0eSJamie Lentin if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD 575f3d4ff0eSJamie Lentin && hdev->type != HID_TYPE_USBMOUSE) { 576f3d4ff0eSJamie Lentin hid_dbg(hdev, "Ignoring keyboard half of device\n"); 577f3d4ff0eSJamie Lentin return 0; 578f3d4ff0eSJamie Lentin } 579f3d4ff0eSJamie Lentin 580f3d4ff0eSJamie Lentin cptkbd_data = devm_kzalloc(&hdev->dev, 581f3d4ff0eSJamie Lentin sizeof(*cptkbd_data), 582f3d4ff0eSJamie Lentin GFP_KERNEL); 583f3d4ff0eSJamie Lentin if (cptkbd_data == NULL) { 584f3d4ff0eSJamie Lentin hid_err(hdev, "can't alloc keyboard descriptor\n"); 585f3d4ff0eSJamie Lentin return -ENOMEM; 586f3d4ff0eSJamie Lentin } 587f3d4ff0eSJamie Lentin hid_set_drvdata(hdev, cptkbd_data); 588f3d4ff0eSJamie Lentin 589f3d4ff0eSJamie Lentin /* 590f3d4ff0eSJamie Lentin * Tell the keyboard a driver understands it, and turn F7, F9, F11 into 591f3d4ff0eSJamie Lentin * regular keys 592f3d4ff0eSJamie Lentin */ 593f3d4ff0eSJamie Lentin ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03); 594f3d4ff0eSJamie Lentin if (ret) 595f3d4ff0eSJamie Lentin hid_warn(hdev, "Failed to switch F7/9/11 mode: %d\n", ret); 596f3d4ff0eSJamie Lentin 597f3d4ff0eSJamie Lentin /* Turn Fn-Lock on by default */ 598f3d4ff0eSJamie Lentin cptkbd_data->fn_lock = true; 599f3d4ff0eSJamie Lentin lenovo_features_set_cptkbd(hdev); 600f3d4ff0eSJamie Lentin 601f3d4ff0eSJamie Lentin ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd); 602f3d4ff0eSJamie Lentin if (ret) 603f3d4ff0eSJamie Lentin hid_warn(hdev, "Could not create sysfs group: %d\n", ret); 604f3d4ff0eSJamie Lentin 605f3d4ff0eSJamie Lentin return 0; 606f3d4ff0eSJamie Lentin } 607f3d4ff0eSJamie Lentin 60894723bfaSJamie Lentin static int lenovo_probe(struct hid_device *hdev, 609c1dcad2dSBernhard Seibold const struct hid_device_id *id) 610c1dcad2dSBernhard Seibold { 611c1dcad2dSBernhard Seibold int ret; 612c1dcad2dSBernhard Seibold 613c1dcad2dSBernhard Seibold ret = hid_parse(hdev); 614c1dcad2dSBernhard Seibold if (ret) { 615c1dcad2dSBernhard Seibold hid_err(hdev, "hid_parse failed\n"); 6160ccdd9e7SBenjamin Tissoires goto err; 617c1dcad2dSBernhard Seibold } 618c1dcad2dSBernhard Seibold 619c1dcad2dSBernhard Seibold ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); 620c1dcad2dSBernhard Seibold if (ret) { 621c1dcad2dSBernhard Seibold hid_err(hdev, "hid_hw_start failed\n"); 6220ccdd9e7SBenjamin Tissoires goto err; 623c1dcad2dSBernhard Seibold } 624c1dcad2dSBernhard Seibold 6256a5b414bSJamie Lentin switch (hdev->product) { 6266a5b414bSJamie Lentin case USB_DEVICE_ID_LENOVO_TPKBD: 62794723bfaSJamie Lentin ret = lenovo_probe_tpkbd(hdev); 6286a5b414bSJamie Lentin break; 629f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CUSBKBD: 630f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CBTKBD: 631f3d4ff0eSJamie Lentin ret = lenovo_probe_cptkbd(hdev); 632f3d4ff0eSJamie Lentin break; 6336a5b414bSJamie Lentin default: 6346a5b414bSJamie Lentin ret = 0; 6356a5b414bSJamie Lentin break; 6366a5b414bSJamie Lentin } 6370ccdd9e7SBenjamin Tissoires if (ret) 6380ccdd9e7SBenjamin Tissoires goto err_hid; 639c1dcad2dSBernhard Seibold 640c1dcad2dSBernhard Seibold return 0; 6410ccdd9e7SBenjamin Tissoires err_hid: 6420ccdd9e7SBenjamin Tissoires hid_hw_stop(hdev); 6430ccdd9e7SBenjamin Tissoires err: 644c1dcad2dSBernhard Seibold return ret; 645c1dcad2dSBernhard Seibold } 646c1dcad2dSBernhard Seibold 64794723bfaSJamie Lentin static void lenovo_remove_tpkbd(struct hid_device *hdev) 648c1dcad2dSBernhard Seibold { 64994723bfaSJamie Lentin struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev); 650c1dcad2dSBernhard Seibold 6516a5b414bSJamie Lentin /* 6526a5b414bSJamie Lentin * Only the trackpoint half of the keyboard has drvdata and stuff that 6536a5b414bSJamie Lentin * needs unregistering. 6546a5b414bSJamie Lentin */ 6556a5b414bSJamie Lentin if (data_pointer == NULL) 6566a5b414bSJamie Lentin return; 6576a5b414bSJamie Lentin 658c1dcad2dSBernhard Seibold sysfs_remove_group(&hdev->dev.kobj, 65994723bfaSJamie Lentin &lenovo_attr_group_tpkbd); 660c1dcad2dSBernhard Seibold 661c1dcad2dSBernhard Seibold led_classdev_unregister(&data_pointer->led_micmute); 662c1dcad2dSBernhard Seibold led_classdev_unregister(&data_pointer->led_mute); 663c1dcad2dSBernhard Seibold 664c1dcad2dSBernhard Seibold hid_set_drvdata(hdev, NULL); 665c1dcad2dSBernhard Seibold } 666c1dcad2dSBernhard Seibold 667f3d4ff0eSJamie Lentin static void lenovo_remove_cptkbd(struct hid_device *hdev) 668f3d4ff0eSJamie Lentin { 669f3d4ff0eSJamie Lentin sysfs_remove_group(&hdev->dev.kobj, 670f3d4ff0eSJamie Lentin &lenovo_attr_group_cptkbd); 671f3d4ff0eSJamie Lentin } 672f3d4ff0eSJamie Lentin 67394723bfaSJamie Lentin static void lenovo_remove(struct hid_device *hdev) 674c1dcad2dSBernhard Seibold { 6756a5b414bSJamie Lentin switch (hdev->product) { 6766a5b414bSJamie Lentin case USB_DEVICE_ID_LENOVO_TPKBD: 67794723bfaSJamie Lentin lenovo_remove_tpkbd(hdev); 6786a5b414bSJamie Lentin break; 679f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CUSBKBD: 680f3d4ff0eSJamie Lentin case USB_DEVICE_ID_LENOVO_CBTKBD: 681f3d4ff0eSJamie Lentin lenovo_remove_cptkbd(hdev); 682f3d4ff0eSJamie Lentin break; 6836a5b414bSJamie Lentin } 684c1dcad2dSBernhard Seibold 685c1dcad2dSBernhard Seibold hid_hw_stop(hdev); 686c1dcad2dSBernhard Seibold } 687c1dcad2dSBernhard Seibold 68894723bfaSJamie Lentin static const struct hid_device_id lenovo_devices[] = { 689c1dcad2dSBernhard Seibold { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, 690f3d4ff0eSJamie Lentin { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) }, 691f3d4ff0eSJamie Lentin { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) }, 692c1dcad2dSBernhard Seibold { } 693c1dcad2dSBernhard Seibold }; 694c1dcad2dSBernhard Seibold 69594723bfaSJamie Lentin MODULE_DEVICE_TABLE(hid, lenovo_devices); 696c1dcad2dSBernhard Seibold 69794723bfaSJamie Lentin static struct hid_driver lenovo_driver = { 69894723bfaSJamie Lentin .name = "lenovo", 69994723bfaSJamie Lentin .id_table = lenovo_devices, 7006a5b414bSJamie Lentin .input_mapping = lenovo_input_mapping, 70194723bfaSJamie Lentin .probe = lenovo_probe, 70294723bfaSJamie Lentin .remove = lenovo_remove, 703f3d4ff0eSJamie Lentin .raw_event = lenovo_raw_event, 704c1dcad2dSBernhard Seibold }; 70594723bfaSJamie Lentin module_hid_driver(lenovo_driver); 706c1dcad2dSBernhard Seibold 707c1dcad2dSBernhard Seibold MODULE_LICENSE("GPL"); 708