diff --git a/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
new file mode 100644
index 0000000000000000000000000000000000000000..57b92cbdceae9c75ca0855f9079c67593f77956b
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-lenovo-tpkbd
@@ -0,0 +1,38 @@
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_to_select
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This controls if mouse clicks should be generated if the trackpoint is quickly pressed. How fast this press has to be
+		is being controlled by press_speed.
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/dragging
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	If this setting is enabled, it is possible to do dragging by pressing the trackpoint. This requires press_to_select to be enabled.
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/release_to_select
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	For details regarding this setting please refer to http://www.pc.ibm.com/ww/healthycomputing/trkpntb.html
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/select_right
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This setting controls if the mouse click events generated by pressing the trackpoint (if press_to_select is enabled) generate
+		a left or right mouse button click.
+		Values are 0 or 1.
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/sensitivity
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This file contains the trackpoint sensitivity.
+		Values are decimal integers from 1 (lowest sensitivity) to 255 (highest sensitivity).
+
+What:		/sys/bus/usb/devices/<busnum>-<devnum>:<config num>.<interface num>/<hid-bus>:<vendor-id>:<product-id>.<num>/press_speed
+Date:		July 2011
+Contact:	linux-input@vger.kernel.org
+Description:	This setting controls how fast the trackpoint needs to be pressed to generate a mouse click if press_to_select is enabled.
+		Values are decimal integers from 1 (slowest) to 255 (fastest).
+
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index e9c68fedfcff0b90222cf6a48886fe5229f53d94..ca7e76cab83d699f5b90d1f0b35180a49efd51e6 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -268,6 +268,18 @@ config HID_LCPOWER
 	---help---
 	Support for LC-Power RC1000MCE RF remote control.
 
+config HID_LENOVO_TPKBD
+	tristate "Lenovo ThinkPad USB Keyboard with TrackPoint"
+	depends on USB_HID
+	select LEDS_CLASS
+	---help---
+	Support for the Lenovo ThinkPad USB Keyboard with TrackPoint.
+
+	Say Y here if you have a Lenovo ThinkPad USB Keyboard with TrackPoint
+	and would like to use device-specific features like changing the
+	sensitivity of the trackpoint, using the microphone mute button or
+	controlling the mute and microphone mute LEDs.
+
 config HID_LOGITECH
 	tristate "Logitech devices" if EXPERT
 	depends on USB_HID
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index ca6cc9f0485c90d22fb11d0c6deeb6560ac36158..05913c533ec569cfe3ad1e9836d699506a10dbe4 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -54,6 +54,7 @@ obj-$(CONFIG_HID_KENSINGTON)	+= hid-kensington.o
 obj-$(CONFIG_HID_KEYTOUCH)	+= hid-keytouch.o
 obj-$(CONFIG_HID_KYE)		+= hid-kye.o
 obj-$(CONFIG_HID_LCPOWER)       += hid-lcpower.o
+obj-$(CONFIG_HID_LENOVO_TPKBD)	+= hid-lenovo-tpkbd.o
 obj-$(CONFIG_HID_LOGITECH)	+= hid-logitech.o
 obj-$(CONFIG_HID_LOGITECH_DJ)	+= hid-logitech-dj.o
 obj-$(CONFIG_HID_MAGICMOUSE)    += hid-magicmouse.o
diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 8e3a6b261477d9866ec45c83df4b2766098d281d..f69568032c04bd8e707fd8f2a51b3a9644c12931 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -1544,6 +1544,7 @@ static const struct hid_device_id hid_have_special_driver[] = {
 	{ HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_EASYPEN_M610X) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) },
+ 	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) },
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 9373f535dfe9908c14b8ff04afbe90a981693a54..c607e6f33953f8838532d28a6d6d5106330640e2 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -473,6 +473,9 @@
 #define USB_DEVICE_ID_LD_HYBRID		0x2090
 #define USB_DEVICE_ID_LD_HEATCONTROL	0x20A0
 
+#define USB_VENDOR_ID_LENOVO		0x17ef
+#define USB_DEVICE_ID_LENOVO_TPKBD	0x6009
+
 #define USB_VENDOR_ID_LG		0x1fd2
 #define USB_DEVICE_ID_LG_MULTITOUCH	0x0064
 
diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c
new file mode 100644
index 0000000000000000000000000000000000000000..77d2df04c97b0dbe6c34eb11b9e566ae07d5ae28
--- /dev/null
+++ b/drivers/hid/hid-lenovo-tpkbd.c
@@ -0,0 +1,564 @@
+/*
+ *  HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
+ *
+ *  Copyright (c) 2012 Bernhard Seibold
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/module.h>
+#include <linux/sysfs.h>
+#include <linux/device.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+#include <linux/input.h>
+#include <linux/leds.h>
+#include "usbhid/usbhid.h"
+
+#include "hid-ids.h"
+
+/* This is only used for the trackpoint part of the driver, hence _tp */
+struct tpkbd_data_pointer {
+	int led_state;
+	struct led_classdev led_mute;
+	struct led_classdev led_micmute;
+	int press_to_select;
+	int dragging;
+	int release_to_select;
+	int select_right;
+	int sensitivity;
+	int press_speed;
+};
+
+#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
+
+static int tpkbd_input_mapping(struct hid_device *hdev,
+		struct hid_input *hi, struct hid_field *field,
+		struct hid_usage *usage, unsigned long **bit, int *max)
+{
+	struct usbhid_device *uhdev;
+
+	uhdev = (struct usbhid_device *) hdev->driver_data;
+	if (uhdev->ifnum == 1 && usage->hid == (HID_UP_BUTTON | 0x0010)) {
+		map_key_clear(KEY_MICMUTE);
+		return 1;
+	}
+	return 0;
+}
+
+#undef map_key_clear
+
+static int tpkbd_features_set(struct hid_device *hdev)
+{
+	struct hid_report *report;
+	struct tpkbd_data_pointer *data_pointer;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+	report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4];
+
+	report->field[0]->value[0]  = data_pointer->press_to_select   ? 0x01 : 0x02;
+	report->field[0]->value[0] |= data_pointer->dragging          ? 0x04 : 0x08;
+	report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20;
+	report->field[0]->value[0] |= data_pointer->select_right      ? 0x80 : 0x40;
+	report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver
+	report->field[2]->value[0] = data_pointer->sensitivity;
+	report->field[3]->value[0] = data_pointer->press_speed;
+
+	usbhid_submit_report(hdev, report, USB_DIR_OUT);
+	return 0;
+}
+
+static ssize_t pointer_press_to_select_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select);
+}
+
+static ssize_t pointer_press_to_select_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->press_to_select = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_dragging_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging);
+}
+
+static ssize_t pointer_dragging_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->dragging = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_release_to_select_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select);
+}
+
+static ssize_t pointer_release_to_select_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->release_to_select = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_select_right_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right);
+}
+
+static ssize_t pointer_select_right_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value))
+		return -EINVAL;
+	if (value < 0 || value > 1)
+		return -EINVAL;
+
+	data_pointer->select_right = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_sensitivity_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		data_pointer->sensitivity);
+}
+
+static ssize_t pointer_sensitivity_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	data_pointer->sensitivity = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static ssize_t pointer_press_speed_show(struct device *dev,
+		struct device_attribute *attr,
+		char *buf)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	return snprintf(buf, PAGE_SIZE, "%u\n",
+		data_pointer->press_speed);
+}
+
+static ssize_t pointer_press_speed_store(struct device *dev,
+		struct device_attribute *attr,
+		const char *buf,
+		size_t count)
+{
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int value;
+
+	hdev = container_of(dev, struct hid_device, dev);
+	if (hdev == NULL)
+		return -ENODEV;
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
+		return -EINVAL;
+
+	data_pointer->press_speed = value;
+	tpkbd_features_set(hdev);
+
+	return count;
+}
+
+static struct device_attribute dev_attr_pointer_press_to_select =
+	__ATTR(press_to_select, S_IWUSR | S_IRUGO,
+			pointer_press_to_select_show,
+			pointer_press_to_select_store);
+
+static struct device_attribute dev_attr_pointer_dragging =
+	__ATTR(dragging, S_IWUSR | S_IRUGO,
+			pointer_dragging_show,
+			pointer_dragging_store);
+
+static struct device_attribute dev_attr_pointer_release_to_select =
+	__ATTR(release_to_select, S_IWUSR | S_IRUGO,
+			pointer_release_to_select_show,
+			pointer_release_to_select_store);
+
+static struct device_attribute dev_attr_pointer_select_right =
+	__ATTR(select_right, S_IWUSR | S_IRUGO,
+			pointer_select_right_show,
+			pointer_select_right_store);
+
+static struct device_attribute dev_attr_pointer_sensitivity =
+	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
+			pointer_sensitivity_show,
+			pointer_sensitivity_store);
+
+static struct device_attribute dev_attr_pointer_press_speed =
+	__ATTR(press_speed, S_IWUSR | S_IRUGO,
+			pointer_press_speed_show,
+			pointer_press_speed_store);
+
+static struct attribute *tpkbd_attributes_pointer[] = {
+	&dev_attr_pointer_press_to_select.attr,
+	&dev_attr_pointer_dragging.attr,
+	&dev_attr_pointer_release_to_select.attr,
+	&dev_attr_pointer_select_right.attr,
+	&dev_attr_pointer_sensitivity.attr,
+	&dev_attr_pointer_press_speed.attr,
+	NULL
+};
+
+static const struct attribute_group tpkbd_attr_group_pointer = {
+	.attrs = tpkbd_attributes_pointer,
+};
+
+static enum led_brightness tpkbd_led_brightness_get(
+			struct led_classdev *led_cdev)
+{
+	struct device *dev;
+	struct hid_device *hdev;
+	struct tpkbd_data_pointer *data_pointer;
+	int led_nr = 0;
+
+	dev = led_cdev->dev->parent;
+	hdev = container_of(dev, struct hid_device, dev);
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (led_cdev == &data_pointer->led_micmute)
+		led_nr = 1;
+
+	return data_pointer->led_state & (1 << led_nr)
+				? LED_FULL
+				: LED_OFF;
+}
+
+static void tpkbd_led_brightness_set(struct led_classdev *led_cdev,
+			enum led_brightness value)
+{
+	struct device *dev;
+	struct hid_device *hdev;
+	struct hid_report *report;
+	struct tpkbd_data_pointer *data_pointer;
+	int led_nr = 0;
+
+	dev = led_cdev->dev->parent;
+	hdev = container_of(dev, struct hid_device, dev);
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	if (led_cdev == &data_pointer->led_micmute)
+		led_nr = 1;
+
+	if (value == LED_OFF)
+		data_pointer->led_state &= ~(1 << led_nr);
+	else
+		data_pointer->led_state |= 1 << led_nr;
+
+	report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3];
+	report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1;
+	report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1;
+	usbhid_submit_report(hdev, report, USB_DIR_OUT);
+}
+
+static int tpkbd_probe_tp(struct hid_device *hdev)
+{
+	struct device *dev = &hdev->dev;
+	struct tpkbd_data_pointer *data_pointer;
+	size_t name_sz = strlen(dev_name(dev)) + 16;
+	char *name_mute, *name_micmute;
+	int ret;
+
+	if (sysfs_create_group(&hdev->dev.kobj,
+				&tpkbd_attr_group_pointer)) {
+		hid_warn(hdev, "Could not create sysfs group\n");
+	}
+
+	data_pointer = kzalloc(sizeof(struct tpkbd_data_pointer), GFP_KERNEL);
+	if (data_pointer == NULL) {
+		hid_err(hdev, "Could not allocate memory for driver data\n");
+		return -ENOMEM;
+	}
+
+	// set same default values as windows driver
+	data_pointer->sensitivity = 0xa0;
+	data_pointer->press_speed = 0x38;
+
+	name_mute = kzalloc(name_sz, GFP_KERNEL);
+	if (name_mute == NULL) {
+		hid_err(hdev, "Could not allocate memory for led data\n");
+		ret = -ENOMEM;
+		goto err;
+	}
+	snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
+
+	name_micmute = kzalloc(name_sz, GFP_KERNEL);
+	if (name_micmute == NULL) {
+		hid_err(hdev, "Could not allocate memory for led data\n");
+		ret = -ENOMEM;
+		goto err2;
+	}
+	snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
+
+	hid_set_drvdata(hdev, data_pointer);
+
+	data_pointer->led_mute.name = name_mute;
+	data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get;
+	data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set;
+	data_pointer->led_mute.dev = dev;
+	led_classdev_register(dev, &data_pointer->led_mute);
+
+	data_pointer->led_micmute.name = name_micmute;
+	data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get;
+	data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set;
+	data_pointer->led_micmute.dev = dev;
+	led_classdev_register(dev, &data_pointer->led_micmute);
+
+	tpkbd_features_set(hdev);
+
+	return 0;
+
+err2:
+	kfree(name_mute);
+err:
+	kfree(data_pointer);
+	return ret;
+}
+
+static int tpkbd_probe(struct hid_device *hdev,
+		const struct hid_device_id *id)
+{
+	int ret;
+	struct usbhid_device *uhdev;
+
+	ret = hid_parse(hdev);
+	if (ret) {
+		hid_err(hdev, "hid_parse failed\n");
+		goto err_free;
+	}
+
+	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
+	if (ret) {
+		hid_err(hdev, "hid_hw_start failed\n");
+		goto err_free;
+	}
+
+	uhdev = (struct usbhid_device *) hdev->driver_data;
+
+	if (uhdev->ifnum == 1)
+		return tpkbd_probe_tp(hdev);
+
+	return 0;
+err_free:
+	return ret;
+}
+
+static void tpkbd_remove_tp(struct hid_device *hdev)
+{
+	struct tpkbd_data_pointer *data_pointer;
+
+	sysfs_remove_group(&hdev->dev.kobj,
+			&tpkbd_attr_group_pointer);
+
+	data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev);
+
+	led_classdev_unregister(&data_pointer->led_micmute);
+	led_classdev_unregister(&data_pointer->led_mute);
+
+	hid_set_drvdata(hdev, NULL);
+	kfree(data_pointer);
+}
+
+static void tpkbd_remove(struct hid_device *hdev)
+{
+	struct usbhid_device *uhdev;
+
+	uhdev = (struct usbhid_device *) hdev->driver_data;
+	if (uhdev->ifnum == 1)
+		tpkbd_remove_tp(hdev);
+
+	hid_hw_stop(hdev);
+}
+
+static const struct hid_device_id tpkbd_devices[] = {
+	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
+	{ }
+};
+
+MODULE_DEVICE_TABLE(hid, tpkbd_devices);
+
+static struct hid_driver tpkbd_driver = {
+	.name = "lenovo_tpkbd",
+	.id_table = tpkbd_devices,
+	.input_mapping = tpkbd_input_mapping,
+	.probe = tpkbd_probe,
+	.remove = tpkbd_remove,
+};
+
+static int __init tpkbd_init(void)
+{
+	return hid_register_driver(&tpkbd_driver);
+}
+
+static void __exit tpkbd_exit(void)
+{
+	hid_unregister_driver(&tpkbd_driver);
+}
+
+module_init(tpkbd_init);
+module_exit(tpkbd_exit);
+
+MODULE_LICENSE("GPL");