The Lenovo X1 Tablet Cover is connected via USB. It consists of
1 device with 3 usb interfaces. Interface 0 represents keyboard,
interface 1 the function / special keys and LED control, interface 2
is the Synaptics touchpad and pointing stick.

This driver will bind to interfaces 0 and 1 and handle function / special keys
including LED control.

Signed-off-by: Dennis Wassenberg <dennis.wassenb...@secunet.com>
---
 drivers/hid/hid-lenovo.c   | 549 +++++++++++++++++++++++++++++++++++++++++++++
 include/linux/hid-lenovo.h |  15 ++
 2 files changed, 564 insertions(+)
 create mode 100644 include/linux/hid-lenovo.h

diff --git a/drivers/hid/hid-lenovo.c b/drivers/hid/hid-lenovo.c
index 1ac4ff4..4251aac 100644
--- a/drivers/hid/hid-lenovo.c
+++ b/drivers/hid/hid-lenovo.c
@@ -3,9 +3,11 @@
  *  - ThinkPad USB Keyboard with TrackPoint (tpkbd)
  *  - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd)
  *  - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd)
+ *  - ThinkPad X1 Cover USB Keyboard with TrackPoint and Touchpad (tpx1cover)
  *
  *  Copyright (c) 2012 Bernhard Seibold
  *  Copyright (c) 2014 Jamie Lentin <j...@lentin.co.uk>
+ *  Copyright (c) 2016 Dennis Wassenberg <dennis.wassenb...@secunet.com>
  */
 
 /*
@@ -19,11 +21,19 @@
 #include <linux/sysfs.h>
 #include <linux/device.h>
 #include <linux/hid.h>
+#include <linux/hid-lenovo.h>
 #include <linux/input.h>
 #include <linux/leds.h>
 
 #include "hid-ids.h"
 
+struct led_table_entry {
+       struct led_classdev *dev;
+       uint8_t state;
+};
+
+static struct led_table_entry hid_lenovo_led_table[HID_LENOVO_LED_MAX];
+
 struct lenovo_drvdata_tpkbd {
        int led_state;
        struct led_classdev led_mute;
@@ -42,6 +52,37 @@ struct lenovo_drvdata_cptkbd {
        int sensitivity;
 };
 
+struct lenovo_drvdata_tpx1cover {
+       uint16_t led_state;
+       uint8_t fnlock_state;
+       uint8_t led_present;
+       struct led_classdev led_mute;
+       struct led_classdev led_micmute;
+       struct led_classdev led_fnlock;
+};
+
+int hid_lenovo_led_set(int led_num, bool on)
+{
+       struct led_classdev *dev;
+
+       if (led_num >= HID_LENOVO_LED_MAX)
+               return -EINVAL;
+
+       dev = hid_lenovo_led_table[led_num].dev;
+       hid_lenovo_led_table[led_num].state = on;
+
+       if (!dev)
+               return -ENODEV;
+
+       if (!dev->brightness_set)
+               return -ENODEV;
+
+       dev->brightness_set(dev, on ? LED_FULL : LED_OFF);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(hid_lenovo_led_set);
+
 #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
 
 static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
@@ -86,6 +127,84 @@ static int lenovo_input_mapping_tpkbd(struct hid_device 
*hdev,
        return 0;
 }
 
+static int lenovo_input_mapping_tpx1cover(struct hid_device *hdev,
+               struct hid_input *hi, struct hid_field *field,
+               struct hid_usage *usage, unsigned long **bit, int *max)
+{
+       if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER) {
+               switch (usage->hid & HID_USAGE) {
+               case 0x0001: // Unknown keys -> Idenditied by usage index!
+                       map_key_clear(KEY_UNKNOWN);
+                       switch (usage->usage_index) {
+                       case 0x8:
+                               input_set_capability(hi->input, EV_KEY, KEY_FN);
+                               break;
+
+                       case 0x9:
+                               input_set_capability(hi->input, EV_KEY, 
KEY_MICMUTE);
+                               break;
+
+                       case 0xa:
+                               input_set_capability(hi->input, EV_KEY, 
KEY_CONFIG);
+                               break;
+
+                       case 0xb:
+                               input_set_capability(hi->input, EV_KEY, 
KEY_SEARCH);
+                               break;
+
+                       case 0xc:
+                               input_set_capability(hi->input, EV_KEY, 
KEY_SETUP);
+                               break;
+
+                       case 0xd:
+                               input_set_capability(hi->input, EV_KEY, 
KEY_SWITCHVIDEOMODE);
+                               break;
+
+                       case 0xe:
+                               input_set_capability(hi->input, EV_KEY, 
KEY_RFKILL);
+                               break;
+
+                       default:
+                               return -1;
+                       }
+
+                       return 1;
+
+               case 0x006f: // Consumer.006f ---> Key.BrightnessUp
+                       map_key_clear(KEY_BRIGHTNESSUP);
+                       return 1;
+
+               case 0x0070: // Consumer.0070 ---> Key.BrightnessDown
+                       map_key_clear(KEY_BRIGHTNESSDOWN);
+                       return 1;
+
+               case 0x00b7:// Consumer.00b7 ---> Key.StopCD
+                       map_key_clear(KEY_STOPCD);
+                       return 1;
+
+               case 0x00cd: // Consumer.00cd ---> Key.PlayPause
+                       map_key_clear(KEY_PLAYPAUSE);
+                       return 1;
+
+               case 0x00e0: // Consumer.00e0 ---> Absolute.Volume
+                       return 0;
+               case 0x00e2: // Consumer.00e2 ---> Key.Mute
+                       map_key_clear(KEY_MUTE);
+                       return 1;
+
+               case 0x00e9: // Consumer.00e9 ---> Key.VolumeUp
+                       map_key_clear(KEY_VOLUMEUP);
+                       return 1;
+
+               case 0x00ea: // Consumer.00ea ---> Key.VolumeDown
+                       map_key_clear(KEY_VOLUMEDOWN);
+                       return 1;
+               }
+       }
+
+       return 0;
+}
+
 static int lenovo_input_mapping_cptkbd(struct hid_device *hdev,
                struct hid_input *hi, struct hid_field *field,
                struct hid_usage *usage, unsigned long **bit, int *max)
@@ -172,6 +291,9 @@ static int lenovo_input_mapping(struct hid_device *hdev,
        case USB_DEVICE_ID_LENOVO_CBTKBD:
                return lenovo_input_mapping_cptkbd(hdev, hi, field,
                                                        usage, bit, max);
+       case USB_DEVICE_ID_LENOVO_X1_COVER:
+               return lenovo_input_mapping_tpx1cover(hdev, hi, field,
+                                                       usage, bit, max);
        default:
                return 0;
        }
@@ -362,6 +484,143 @@ static int lenovo_event_cptkbd(struct hid_device *hdev,
        return 0;
 }
 
+static enum led_brightness lenovo_led_brightness_get_tpx1cover(
+                       struct led_classdev *led_cdev)
+{
+       struct device *dev = led_cdev->dev->parent;
+       struct hid_device *hdev = to_hid_device(dev);
+       struct lenovo_drvdata_tpx1cover *drv_data = hid_get_drvdata(hdev);
+       int led_nr = 0;
+
+       if (led_cdev == &drv_data->led_mute)
+               led_nr = 0;
+       else if (led_cdev == &drv_data->led_micmute)
+               led_nr = 1;
+       else if (led_cdev == &drv_data->led_fnlock)
+               led_nr = 2;
+       else
+               return LED_OFF;
+
+       return drv_data->led_state & (1 << led_nr)
+                               ? LED_FULL
+                               : LED_OFF;
+}
+
+static void lenovo_led_brightness_set_tpx1cover(struct led_classdev *led_cdev,
+                       enum led_brightness value)
+{
+       struct device *dev = led_cdev->dev->parent;
+       struct hid_device *hdev = to_hid_device(dev);
+       struct lenovo_drvdata_tpx1cover *drv_data = hid_get_drvdata(hdev);
+       struct hid_report *report;
+       int led_nr = -1;
+       int led_nr_hw = -1;
+
+       if (led_cdev == &drv_data->led_mute) {
+               led_nr = 0;
+               led_nr_hw = 0x64;
+       } else if (led_cdev == &drv_data->led_micmute) {
+               led_nr = 1;
+               led_nr_hw = 0x74;
+       } else if (led_cdev == &drv_data->led_fnlock) {
+               led_nr = 2;
+               led_nr_hw = 0x54;
+       } else {
+               hid_warn(hdev, "Invalid LED to set.\n");
+               return;
+       }
+
+       if (value == LED_OFF)
+               drv_data->led_state &= ~(1 << led_nr);
+       else
+               drv_data->led_state |= 1 << led_nr;
+
+       report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[9];
+       if (report) {
+               report->field[0]->value[0] = led_nr_hw;
+               report->field[0]->value[1] = (drv_data->led_state & (1 << 
led_nr))
+                       ? 0x02 : 0x01;
+               hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+       }
+}
+
+static int lenovo_event_tpx1cover(struct hid_device *hdev,
+               struct hid_field *field, struct hid_usage *usage, __s32 value)
+{
+       int ret = 0;
+
+       if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER
+               && (usage->hid & HID_USAGE) == 0x0001) {
+
+               if (usage->usage_index == 0x8 && value == 1) {
+                       struct lenovo_drvdata_tpx1cover *drv_data = 
hid_get_drvdata(hdev);
+
+                       if (drv_data && drv_data->led_present) {
+                               drv_data->fnlock_state = 
lenovo_led_brightness_get_tpx1cover(
+                                               &drv_data->led_fnlock) == 
LED_OFF ? 1 : 0;
+                               lenovo_led_brightness_set_tpx1cover(
+                                       &drv_data->led_fnlock,
+                                       drv_data->fnlock_state ? LED_FULL : 
LED_OFF);
+                       }
+               }
+
+               if (usage->usage_index == 0x9 && value == 1) {
+                       input_event(field->hidinput->input, EV_KEY, 
KEY_MICMUTE, 1);
+                       input_sync(field->hidinput->input);
+                       input_event(field->hidinput->input, EV_KEY, 
KEY_MICMUTE, 0);
+                       input_sync(field->hidinput->input);
+                       ret = 1;
+               }
+
+               if (usage->usage_index == 0xa && value == 1) {
+                       input_event(field->hidinput->input, EV_KEY, KEY_CONFIG, 
1);
+                       input_sync(field->hidinput->input);
+                       input_event(field->hidinput->input, EV_KEY, KEY_CONFIG, 
0);
+                       input_sync(field->hidinput->input);
+
+                       ret = 1;
+               }
+
+               if (usage->usage_index == 0xb && value == 1) {
+                       input_event(field->hidinput->input, EV_KEY, KEY_SEARCH, 
1);
+                       input_sync(field->hidinput->input);
+                       input_event(field->hidinput->input, EV_KEY, KEY_SEARCH, 
0);
+                       input_sync(field->hidinput->input);
+
+                       ret = 1;
+               }
+
+               if (usage->usage_index == 0xc && value == 1) {
+                       input_event(field->hidinput->input, EV_KEY, KEY_SETUP, 
1);
+                       input_sync(field->hidinput->input);
+                       input_event(field->hidinput->input, EV_KEY, KEY_SETUP, 
0);
+                       input_sync(field->hidinput->input);
+
+                       ret = 1;
+               }
+
+               if (usage->usage_index == 0xd && value == 1) {
+                       input_event(field->hidinput->input, EV_KEY, 
KEY_SWITCHVIDEOMODE, 1);
+                       input_sync(field->hidinput->input);
+                       input_event(field->hidinput->input, EV_KEY, 
KEY_SWITCHVIDEOMODE, 0);
+                       input_sync(field->hidinput->input);
+
+                       ret = 1;
+               }
+
+               if (usage->usage_index == 0xe && value == 1) {
+                       input_event(field->hidinput->input, EV_KEY, KEY_RFKILL, 
1);
+                       input_sync(field->hidinput->input);
+                       input_event(field->hidinput->input, EV_KEY, KEY_RFKILL, 
0);
+                       input_sync(field->hidinput->input);
+
+                       ret = 1;
+               }
+       }
+
+       return ret;
+}
+
 static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
                struct hid_usage *usage, __s32 value)
 {
@@ -369,6 +628,8 @@ static int lenovo_event(struct hid_device *hdev, struct 
hid_field *field,
        case USB_DEVICE_ID_LENOVO_CUSBKBD:
        case USB_DEVICE_ID_LENOVO_CBTKBD:
                return lenovo_event_cptkbd(hdev, field, usage, value);
+       case USB_DEVICE_ID_LENOVO_X1_COVER:
+               return lenovo_event_tpx1cover(hdev, field, usage, value);
        default:
                return 0;
        }
@@ -731,6 +992,251 @@ static int lenovo_probe_tpkbd(struct hid_device *hdev)
        return ret;
 }
 
+static int lenovo_probe_tpx1cover_configure(struct hid_device *hdev)
+{
+       struct hid_report *report = 
hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[9];
+       struct lenovo_drvdata_tpx1cover *drv_data = hid_get_drvdata(hdev);
+
+       if (!drv_data)
+               return -ENODEV;
+
+       if (!report)
+               return -ENOENT;
+
+       report->field[0]->value[0] = 0x54;
+       report->field[0]->value[1] = 0x20;
+       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+       hid_hw_wait(hdev);
+
+       report->field[0]->value[0] = 0x54;
+       report->field[0]->value[1] = 0x08;
+       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+       hid_hw_wait(hdev);
+
+       report->field[0]->value[0] = 0xA0;
+       report->field[0]->value[1] = 0x02;
+       hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
+       hid_hw_wait(hdev);
+
+       lenovo_led_brightness_set_tpx1cover(&drv_data->led_mute,
+               hid_lenovo_led_table[HID_LENOVO_LED_MUTE].state ? LED_FULL : 
LED_OFF);
+       hid_hw_wait(hdev);
+
+       lenovo_led_brightness_set_tpx1cover(&drv_data->led_micmute,
+               hid_lenovo_led_table[HID_LENOVO_LED_MICMUTE].state ? LED_FULL : 
LED_OFF);
+       hid_hw_wait(hdev);
+
+       lenovo_led_brightness_set_tpx1cover(&drv_data->led_fnlock, LED_FULL);
+
+       return 0;
+}
+
+static int lenovo_probe_tpx1cover_special_functions(struct hid_device *hdev)
+{
+       struct device *dev = &hdev->dev;
+       struct lenovo_drvdata_tpx1cover *drv_data = NULL;
+
+       size_t name_sz = strlen(dev_name(dev)) + 16;
+       char *name_led = NULL;
+
+       struct hid_report *report;
+       bool report_match = 1;
+
+       int ret = 0;
+
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 2, 0, 3);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 3, 0, 16);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_OUTPUT_REPORT, 9, 0, 2);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 32, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 84, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 100, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 116, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 132, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 144, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 162, 0, 1);
+       report_match &= report ? 1 : 0;
+
+       if (!report_match) {
+               ret = -ENODEV;
+               goto err;
+       }
+
+       drv_data = devm_kzalloc(&hdev->dev,
+                       sizeof(struct lenovo_drvdata_tpx1cover),
+                       GFP_KERNEL);
+
+       if (!drv_data) {
+               hid_err(hdev,
+                       "Could not allocate memory for tpx1cover driver 
data\n");
+               ret = -ENOMEM;
+               goto err;
+       }
+
+       name_led = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+       if (!name_led) {
+               hid_err(hdev, "Could not allocate memory for mute led data\n");
+               ret = -ENOMEM;
+               goto err_cleanup;
+       }
+       snprintf(name_led, name_sz, "%s:amber:mute", dev_name(dev));
+
+       drv_data->led_mute.name = name_led;
+       drv_data->led_mute.brightness_get = lenovo_led_brightness_get_tpx1cover;
+       drv_data->led_mute.brightness_set = lenovo_led_brightness_set_tpx1cover;
+       drv_data->led_mute.dev = dev;
+       hid_lenovo_led_table[HID_LENOVO_LED_MUTE].dev = &drv_data->led_mute;
+       led_classdev_register(dev, &drv_data->led_mute);
+
+
+       name_led = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+       if (!name_led) {
+               hid_err(hdev,
+                       "Could not allocate memory for mic mute led data\n");
+               ret = -ENOMEM;
+               goto err_cleanup;
+       }
+       snprintf(name_led, name_sz, "%s:amber:micmute", dev_name(dev));
+
+       drv_data->led_micmute.name = name_led;
+       drv_data->led_micmute.brightness_get = 
lenovo_led_brightness_get_tpx1cover;
+       drv_data->led_micmute.brightness_set = 
lenovo_led_brightness_set_tpx1cover;
+       drv_data->led_micmute.dev = dev;
+       hid_lenovo_led_table[HID_LENOVO_LED_MICMUTE].dev = 
&drv_data->led_micmute;
+       led_classdev_register(dev, &drv_data->led_micmute);
+
+
+       name_led = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
+       if (!name_led) {
+               hid_err(hdev,
+                       "Could not allocate memory for FN lock led data\n");
+               ret = -ENOMEM;
+               goto err_cleanup;
+       }
+
+       snprintf(name_led, name_sz, "%s:amber:fnlock", dev_name(dev));
+
+       drv_data->led_fnlock.name = name_led;
+       drv_data->led_fnlock.brightness_get = 
lenovo_led_brightness_get_tpx1cover;
+       drv_data->led_fnlock.brightness_set = 
lenovo_led_brightness_set_tpx1cover;
+       drv_data->led_fnlock.dev = dev;
+       hid_lenovo_led_table[HID_LENOVO_LED_FNLOCK].dev = &drv_data->led_fnlock;
+       led_classdev_register(dev, &drv_data->led_fnlock);
+
+       drv_data->led_state = 0;
+       drv_data->fnlock_state = 1;
+       drv_data->led_present = 1;
+
+       hid_set_drvdata(hdev, drv_data);
+
+       return lenovo_probe_tpx1cover_configure(hdev);
+
+err_cleanup:
+       if (drv_data->led_fnlock.name) {
+               led_classdev_unregister(&drv_data->led_fnlock);
+               devm_kfree(&hdev->dev, (void *) drv_data->led_fnlock.name);
+       }
+
+       if (drv_data->led_micmute.name) {
+               led_classdev_unregister(&drv_data->led_micmute);
+               devm_kfree(&hdev->dev, (void *) drv_data->led_micmute.name);
+       }
+
+       if (drv_data->led_mute.name) {
+               led_classdev_unregister(&drv_data->led_mute);
+               devm_kfree(&hdev->dev, (void *) drv_data->led_mute.name);
+       }
+
+       if (drv_data)
+               kfree(drv_data);
+
+err:
+       return ret;
+}
+
+static int lenovo_probe_tpx1cover_touch(struct hid_device *hdev)
+{
+       struct hid_report *report;
+       bool report_match = 1;
+       int ret = 0;
+
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 2, 0, 2);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 2, 1, 2);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 11, 0, 61);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 12, 0, 61);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 16, 0, 3);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_INPUT_REPORT, 16, 1, 2);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_OUTPUT_REPORT, 9, 0, 20);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_OUTPUT_REPORT, 10, 0, 20);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 14, 0, 1);
+       report_match &= report ? 1 : 0;
+       report = hid_validate_values(hdev, HID_FEATURE_REPORT, 15, 0, 3);
+       report_match &= report ? 1 : 0;
+
+       if (!report_match)
+               ret = -ENODEV;
+
+       return ret;
+}
+
+static int lenovo_probe_tpx1cover(struct hid_device *hdev)
+{
+       int ret = 0;
+
+       /*
+        * Probing for special function keys and LED control -> usb intf 1
+        * Probing for touch input -> usb intf 2 (handled by rmi4 driver)
+        * Other (keyboard) -> usb intf 0
+        */
+       if (!lenovo_probe_tpx1cover_special_functions(hdev)) {
+               // special function keys and LED control
+               ret = 0;
+       } else if (!lenovo_probe_tpx1cover_touch(hdev)) {
+               // handled by rmi
+               ret = -ENODEV;
+       } else {
+               // keyboard
+               struct lenovo_drvdata_tpx1cover *drv_data;
+
+               drv_data = devm_kzalloc(&hdev->dev,
+                                       sizeof(struct lenovo_drvdata_tpx1cover),
+                                       GFP_KERNEL);
+
+               if (!drv_data) {
+                       hid_err(hdev,
+                               "Could not allocate memory for tpx1cover driver 
data\n");
+                       ret = -ENOMEM;
+                       goto out;
+               }
+
+               drv_data->led_state = 0;
+               drv_data->led_present = 0;
+               drv_data->fnlock_state = 0;
+               hid_set_drvdata(hdev, drv_data);
+
+               ret = 0;
+       }
+
+out:
+       return ret;
+}
+
 static int lenovo_probe_cptkbd(struct hid_device *hdev)
 {
        int ret;
@@ -803,6 +1309,9 @@ static int lenovo_probe(struct hid_device *hdev,
        case USB_DEVICE_ID_LENOVO_CBTKBD:
                ret = lenovo_probe_cptkbd(hdev);
                break;
+       case USB_DEVICE_ID_LENOVO_X1_COVER:
+               ret = lenovo_probe_tpx1cover(hdev);
+               break;
        default:
                ret = 0;
                break;
@@ -843,6 +1352,42 @@ static void lenovo_remove_cptkbd(struct hid_device *hdev)
                        &lenovo_attr_group_cptkbd);
 }
 
+static void lenovo_remove_tpx1cover(struct hid_device *hdev)
+{
+       struct lenovo_drvdata_tpx1cover *drv_data = hid_get_drvdata(hdev);
+
+       if (!drv_data)
+               return;
+
+       if (drv_data->led_present) {
+               if (drv_data->led_fnlock.name) {
+                       hid_lenovo_led_table[HID_LENOVO_LED_FNLOCK].dev = NULL;
+
+                       led_classdev_unregister(&drv_data->led_fnlock);
+                       devm_kfree(&hdev->dev, (void *) 
drv_data->led_fnlock.name);
+               }
+
+               if (drv_data->led_micmute.name) {
+                       hid_lenovo_led_table[HID_LENOVO_LED_MICMUTE].dev = NULL;
+
+                       led_classdev_unregister(&drv_data->led_micmute);
+                       devm_kfree(&hdev->dev, (void *) 
drv_data->led_micmute.name);
+               }
+
+               if (drv_data->led_mute.name) {
+                       hid_lenovo_led_table[HID_LENOVO_LED_MUTE].dev = NULL;
+
+                       led_classdev_unregister(&drv_data->led_mute);
+                       devm_kfree(&hdev->dev, (void *) 
drv_data->led_mute.name);
+               }
+       }
+
+       if (drv_data)
+               devm_kfree(&hdev->dev, drv_data);
+
+       hid_set_drvdata(hdev, NULL);
+}
+
 static void lenovo_remove(struct hid_device *hdev)
 {
        switch (hdev->product) {
@@ -853,6 +1398,9 @@ static void lenovo_remove(struct hid_device *hdev)
        case USB_DEVICE_ID_LENOVO_CBTKBD:
                lenovo_remove_cptkbd(hdev);
                break;
+       case USB_DEVICE_ID_LENOVO_X1_COVER:
+               lenovo_remove_tpx1cover(hdev);
+               break;
        }
 
        hid_hw_stop(hdev);
@@ -883,6 +1431,7 @@ static int lenovo_input_configured(struct hid_device *hdev,
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
        { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, 
USB_DEVICE_ID_LENOVO_CBTKBD) },
        { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) 
},
+       { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_X1_COVER) },
        { }
 };
 
diff --git a/include/linux/hid-lenovo.h b/include/linux/hid-lenovo.h
new file mode 100644
index 0000000..d0b0145
--- /dev/null
+++ b/include/linux/hid-lenovo.h
@@ -0,0 +1,15 @@
+
+#ifndef __HID_LENOVO_H__
+#define __HID_LENOVO_H__
+
+
+enum {
+       HID_LENOVO_LED_MUTE,
+       HID_LENOVO_LED_MICMUTE,
+       HID_LENOVO_LED_FNLOCK,
+       HID_LENOVO_LED_MAX,
+};
+
+int hid_lenovo_led_set(int led_num, bool on);
+
+#endif /* __HID_LENOVO_H_ */
-- 
1.9.1
--
To unsubscribe from this list: send the line "unsubscribe linux-sound" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to