xref: /linux/drivers/hid/hid-lenovo.c (revision 7ebdfaa52d15b947503f76474477f92854796d96)
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