Blame view

drivers/hid/hid-lenovo.c 25.3 KB
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
1
  /*
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
2
3
   *  HID driver for Lenovo:
   *  - ThinkPad USB Keyboard with TrackPoint (tpkbd)
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
4
5
   *  - ThinkPad Compact Bluetooth Keyboard with TrackPoint (cptkbd)
   *  - ThinkPad Compact USB Keyboard with TrackPoint (cptkbd)
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
6
7
   *
   *  Copyright (c) 2012 Bernhard Seibold
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
8
   *  Copyright (c) 2014 Jamie Lentin <jm@lentin.co.uk>
2f3ca3908   pgzh   HID: lenovo: Add ...
9
10
11
12
13
14
15
16
17
18
19
   *
   * Linux IBM/Lenovo Scrollpoint mouse driver:
   * - IBM Scrollpoint III
   * - IBM Scrollpoint Pro
   * - IBM Scrollpoint Optical
   * - IBM Scrollpoint Optical 800dpi
   * - IBM Scrollpoint Optical 800dpi Pro
   * - Lenovo Scrollpoint Optical
   *
   *  Copyright (c) 2012 Peter De Wachter <pdewacht@gmail.com>
   *  Copyright (c) 2018 Peter Ganzhorn <peter.ganzhorn@gmail.com>
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
20
21
22
23
24
25
26
27
28
29
30
31
   */
  
  /*
   * 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>
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
32
33
34
  #include <linux/hid.h>
  #include <linux/input.h>
  #include <linux/leds.h>
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
35
36
  
  #include "hid-ids.h"
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
37
  struct lenovo_drvdata_tpkbd {
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
38
39
40
41
42
43
44
45
46
47
  	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;
  };
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
48
  struct lenovo_drvdata_cptkbd {
3cb5ff022   Jamie Lentin   HID: lenovo: Hide...
49
  	u8 middlebutton_state; /* 0:Up, 1:Down (undecided), 2:Scrolling */
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
50
  	bool fn_lock;
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
51
  	int sensitivity;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
52
  };
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
53
  #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
181a8b911   Benjamin Tissoires   HID: lenovo: add ...
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
  static const __u8 lenovo_pro_dock_need_fixup_collection[] = {
  	0x05, 0x88,		/* Usage Page (Vendor Usage Page 0x88)	*/
  	0x09, 0x01,		/* Usage (Vendor Usage 0x01)		*/
  	0xa1, 0x01,		/* Collection (Application)		*/
  	0x85, 0x04,		/*  Report ID (4)			*/
  	0x19, 0x00,		/*  Usage Minimum (0)			*/
  	0x2a, 0xff, 0xff,	/*  Usage Maximum (65535)		*/
  };
  
  static __u8 *lenovo_report_fixup(struct hid_device *hdev, __u8 *rdesc,
  		unsigned int *rsize)
  {
  	switch (hdev->product) {
  	case USB_DEVICE_ID_LENOVO_TPPRODOCK:
  		/* the fixups that need to be done:
  		 *   - get a reasonable usage max for the vendor collection
  		 *     0x8801 from the report ID 4
  		 */
  		if (*rsize >= 153 &&
  		    memcmp(&rdesc[140], lenovo_pro_dock_need_fixup_collection,
  			  sizeof(lenovo_pro_dock_need_fixup_collection)) == 0) {
  			rdesc[151] = 0x01;
  			rdesc[152] = 0x00;
  		}
  		break;
  	}
  	return rdesc;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
82
  static int lenovo_input_mapping_tpkbd(struct hid_device *hdev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
83
84
85
  		struct hid_input *hi, struct hid_field *field,
  		struct hid_usage *usage, unsigned long **bit, int *max)
  {
0c5218362   Benjamin Tissoires   HID: lenovo-tpkbd...
86
  	if (usage->hid == (HID_UP_BUTTON | 0x0010)) {
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
87
  		/* This sub-device contains trackpoint, mark it */
0c5218362   Benjamin Tissoires   HID: lenovo-tpkbd...
88
  		hid_set_drvdata(hdev, (void *)1);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
89
90
91
92
93
  		map_key_clear(KEY_MICMUTE);
  		return 1;
  	}
  	return 0;
  }
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
94
95
96
97
98
99
100
  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)
  {
  	/* HID_UP_LNVENDOR = USB, HID_UP_MSVENDOR = BT */
  	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR ||
  	    (usage->hid & HID_USAGE_PAGE) == HID_UP_LNVENDOR) {
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
  		switch (usage->hid & HID_USAGE) {
  		case 0x00f1: /* Fn-F4: Mic mute */
  			map_key_clear(KEY_MICMUTE);
  			return 1;
  		case 0x00f2: /* Fn-F5: Brightness down */
  			map_key_clear(KEY_BRIGHTNESSDOWN);
  			return 1;
  		case 0x00f3: /* Fn-F6: Brightness up */
  			map_key_clear(KEY_BRIGHTNESSUP);
  			return 1;
  		case 0x00f4: /* Fn-F7: External display (projector) */
  			map_key_clear(KEY_SWITCHVIDEOMODE);
  			return 1;
  		case 0x00f5: /* Fn-F8: Wireless */
  			map_key_clear(KEY_WLAN);
  			return 1;
  		case 0x00f6: /* Fn-F9: Control panel */
  			map_key_clear(KEY_CONFIG);
  			return 1;
  		case 0x00f8: /* Fn-F11: View open applications (3 boxes) */
  			map_key_clear(KEY_SCALE);
  			return 1;
5556eb14b   Jamie Lentin   HID: lenovo: Move...
123
  		case 0x00f9: /* Fn-F12: Open My computer (6 boxes) USB-only */
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
124
125
126
  			/* NB: This mapping is invented in raw_event below */
  			map_key_clear(KEY_FILE);
  			return 1;
5556eb14b   Jamie Lentin   HID: lenovo: Move...
127
128
129
  		case 0x00fa: /* Fn-Esc: Fn-lock toggle */
  			map_key_clear(KEY_FN_ESC);
  			return 1;
94eefa271   Jamie Lentin   HID: lenovo: Use ...
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
  		case 0x00fb: /* Middle mouse button (in native mode) */
  			map_key_clear(BTN_MIDDLE);
  			return 1;
  		}
  	}
  
  	/* Compatibility middle/wheel mappings should be ignored */
  	if (usage->hid == HID_GD_WHEEL)
  		return -1;
  	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_BUTTON &&
  			(usage->hid & HID_USAGE) == 0x003)
  		return -1;
  	if ((usage->hid & HID_USAGE_PAGE) == HID_UP_CONSUMER &&
  			(usage->hid & HID_USAGE) == 0x238)
  		return -1;
  
  	/* Map wheel emulation reports: 0xffa1 = USB, 0xff10 = BT */
  	if ((usage->hid & HID_USAGE_PAGE) == 0xff100000 ||
  	    (usage->hid & HID_USAGE_PAGE) == 0xffa10000) {
  		field->flags |= HID_MAIN_ITEM_RELATIVE | HID_MAIN_ITEM_VARIABLE;
  		field->logical_minimum = -127;
  		field->logical_maximum = 127;
  
  		switch (usage->hid & HID_USAGE) {
  		case 0x0000:
7f65068fb   Jamie Lentin   HID: lenovo: Use ...
155
  			hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
94eefa271   Jamie Lentin   HID: lenovo: Use ...
156
157
  			return 1;
  		case 0x0001:
7f65068fb   Jamie Lentin   HID: lenovo: Use ...
158
  			hid_map_usage(hi, usage, bit, max, EV_REL, REL_WHEEL);
94eefa271   Jamie Lentin   HID: lenovo: Use ...
159
160
161
  			return 1;
  		default:
  			return -1;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
162
163
164
165
166
  		}
  	}
  
  	return 0;
  }
2f3ca3908   pgzh   HID: lenovo: Add ...
167
168
169
170
171
172
173
174
175
176
  static int lenovo_input_mapping_scrollpoint(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_GD_Z) {
  		hid_map_usage(hi, usage, bit, max, EV_REL, REL_HWHEEL);
  		return 1;
  	}
  	return 0;
  }
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
177
178
179
180
181
182
183
184
  static int lenovo_input_mapping(struct hid_device *hdev,
  		struct hid_input *hi, struct hid_field *field,
  		struct hid_usage *usage, unsigned long **bit, int *max)
  {
  	switch (hdev->product) {
  	case USB_DEVICE_ID_LENOVO_TPKBD:
  		return lenovo_input_mapping_tpkbd(hdev, hi, field,
  							usage, bit, max);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
185
186
187
188
  	case USB_DEVICE_ID_LENOVO_CUSBKBD:
  	case USB_DEVICE_ID_LENOVO_CBTKBD:
  		return lenovo_input_mapping_cptkbd(hdev, hi, field,
  							usage, bit, max);
2f3ca3908   pgzh   HID: lenovo: Add ...
189
190
191
192
193
194
195
196
  	case USB_DEVICE_ID_IBM_SCROLLPOINT_III:
  	case USB_DEVICE_ID_IBM_SCROLLPOINT_PRO:
  	case USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL:
  	case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL:
  	case USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO:
  	case USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL:
  		return lenovo_input_mapping_scrollpoint(hdev, hi, field,
  							usage, bit, max);
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
197
198
199
200
  	default:
  		return 0;
  	}
  }
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
201
  #undef map_key_clear
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
202
203
204
205
206
  /* Send a config command to the keyboard */
  static int lenovo_send_cmd_cptkbd(struct hid_device *hdev,
  			unsigned char byte2, unsigned char byte3)
  {
  	int ret;
ea36ae091   Josh Boyer   HID: lenovo: Don'...
207
208
209
210
211
212
213
214
215
  	unsigned char *buf;
  
  	buf = kzalloc(3, GFP_KERNEL);
  	if (!buf)
  		return -ENOMEM;
  
  	buf[0] = 0x18;
  	buf[1] = byte2;
  	buf[2] = byte3;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
216
217
218
  
  	switch (hdev->product) {
  	case USB_DEVICE_ID_LENOVO_CUSBKBD:
ea36ae091   Josh Boyer   HID: lenovo: Don'...
219
  		ret = hid_hw_raw_request(hdev, 0x13, buf, 3,
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
220
221
222
  					HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
  		break;
  	case USB_DEVICE_ID_LENOVO_CBTKBD:
ea36ae091   Josh Boyer   HID: lenovo: Don'...
223
  		ret = hid_hw_output_report(hdev, buf, 3);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
224
225
226
227
228
  		break;
  	default:
  		ret = -EINVAL;
  		break;
  	}
ea36ae091   Josh Boyer   HID: lenovo: Don'...
229
  	kfree(buf);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
230
231
232
233
234
235
236
237
238
239
240
241
  	return ret < 0 ? ret : 0; /* BT returns 0, USB returns sizeof(buf) */
  }
  
  static void lenovo_features_set_cptkbd(struct hid_device *hdev)
  {
  	int ret;
  	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
  
  	ret = lenovo_send_cmd_cptkbd(hdev, 0x05, cptkbd_data->fn_lock);
  	if (ret)
  		hid_err(hdev, "Fn-lock setting failed: %d
  ", ret);
dbfebb44b   Jamie Lentin   HID: lenovo: Add ...
242
243
244
245
246
  
  	ret = lenovo_send_cmd_cptkbd(hdev, 0x02, cptkbd_data->sensitivity);
  	if (ret)
  		hid_err(hdev, "Sensitivity setting failed: %d
  ", ret);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
247
248
249
250
251
252
  }
  
  static ssize_t attr_fn_lock_show_cptkbd(struct device *dev,
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
253
  	struct hid_device *hdev = to_hid_device(dev);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
254
255
256
257
258
259
260
261
262
263
264
  	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ", cptkbd_data->fn_lock);
  }
  
  static ssize_t attr_fn_lock_store_cptkbd(struct device *dev,
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
265
  	struct hid_device *hdev = to_hid_device(dev);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
266
267
268
269
270
271
272
273
274
275
276
277
278
  	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
  	int value;
  
  	if (kstrtoint(buf, 10, &value))
  		return -EINVAL;
  	if (value < 0 || value > 1)
  		return -EINVAL;
  
  	cptkbd_data->fn_lock = !!value;
  	lenovo_features_set_cptkbd(hdev);
  
  	return count;
  }
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
279
280
281
282
  static ssize_t attr_sensitivity_show_cptkbd(struct device *dev,
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
283
  	struct hid_device *hdev = to_hid_device(dev);
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
284
285
286
287
288
289
290
291
292
293
294
295
  	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ",
  		cptkbd_data->sensitivity);
  }
  
  static ssize_t attr_sensitivity_store_cptkbd(struct device *dev,
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
296
  	struct hid_device *hdev = to_hid_device(dev);
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
297
298
299
300
301
302
303
304
305
306
307
  	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
  	int value;
  
  	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
  		return -EINVAL;
  
  	cptkbd_data->sensitivity = value;
  	lenovo_features_set_cptkbd(hdev);
  
  	return count;
  }
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
308
309
310
311
  static struct device_attribute dev_attr_fn_lock_cptkbd =
  	__ATTR(fn_lock, S_IWUSR | S_IRUGO,
  			attr_fn_lock_show_cptkbd,
  			attr_fn_lock_store_cptkbd);
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
312
313
314
315
  static struct device_attribute dev_attr_sensitivity_cptkbd =
  	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
  			attr_sensitivity_show_cptkbd,
  			attr_sensitivity_store_cptkbd);
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
316
317
  static struct attribute *lenovo_attributes_cptkbd[] = {
  	&dev_attr_fn_lock_cptkbd.attr,
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
318
  	&dev_attr_sensitivity_cptkbd.attr,
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
  	NULL
  };
  
  static const struct attribute_group lenovo_attr_group_cptkbd = {
  	.attrs = lenovo_attributes_cptkbd,
  };
  
  static int lenovo_raw_event(struct hid_device *hdev,
  			struct hid_report *report, u8 *data, int size)
  {
  	/*
  	 * Compact USB keyboard's Fn-F12 report holds down many other keys, and
  	 * its own key is outside the usage page range. Remove extra
  	 * keypresses and remap to inside usage page.
  	 */
  	if (unlikely(hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
  			&& size == 3
  			&& data[0] == 0x15
  			&& data[1] == 0x94
  			&& data[2] == 0x01)) {
5556eb14b   Jamie Lentin   HID: lenovo: Move...
339
340
  		data[1] = 0x00;
  		data[2] = 0x01;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
341
342
343
344
  	}
  
  	return 0;
  }
3cb5ff022   Jamie Lentin   HID: lenovo: Hide...
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
  static int lenovo_event_cptkbd(struct hid_device *hdev,
  		struct hid_field *field, struct hid_usage *usage, __s32 value)
  {
  	struct lenovo_drvdata_cptkbd *cptkbd_data = hid_get_drvdata(hdev);
  
  	/* "wheel" scroll events */
  	if (usage->type == EV_REL && (usage->code == REL_WHEEL ||
  			usage->code == REL_HWHEEL)) {
  		/* Scroll events disable middle-click event */
  		cptkbd_data->middlebutton_state = 2;
  		return 0;
  	}
  
  	/* Middle click events */
  	if (usage->type == EV_KEY && usage->code == BTN_MIDDLE) {
  		if (value == 1) {
  			cptkbd_data->middlebutton_state = 1;
  		} else if (value == 0) {
  			if (cptkbd_data->middlebutton_state == 1) {
  				/* No scrolling inbetween, send middle-click */
  				input_event(field->hidinput->input,
  					EV_KEY, BTN_MIDDLE, 1);
  				input_sync(field->hidinput->input);
  				input_event(field->hidinput->input,
  					EV_KEY, BTN_MIDDLE, 0);
  				input_sync(field->hidinput->input);
  			}
  			cptkbd_data->middlebutton_state = 0;
  		}
  		return 1;
  	}
  
  	return 0;
  }
  
  static int lenovo_event(struct hid_device *hdev, struct hid_field *field,
  		struct hid_usage *usage, __s32 value)
  {
  	switch (hdev->product) {
  	case USB_DEVICE_ID_LENOVO_CUSBKBD:
  	case USB_DEVICE_ID_LENOVO_CBTKBD:
  		return lenovo_event_cptkbd(hdev, field, usage, value);
  	default:
  		return 0;
  	}
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
391
  static int lenovo_features_set_tpkbd(struct hid_device *hdev)
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
392
393
  {
  	struct hid_report *report;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
394
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
395

c1dcad2d3   Bernhard Seibold   HID: Driver for L...
396
397
398
399
400
401
402
403
404
  	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;
d88142725   Benjamin Tissoires   HID: use hid_hw_r...
405
  	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
406
407
  	return 0;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
408
  static ssize_t attr_press_to_select_show_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
409
410
411
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
412
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
413
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
414
415
416
417
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ", data_pointer->press_to_select);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
418
  static ssize_t attr_press_to_select_store_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
419
420
421
422
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
423
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
424
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
425
  	int value;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
426
427
428
429
430
431
  	if (kstrtoint(buf, 10, &value))
  		return -EINVAL;
  	if (value < 0 || value > 1)
  		return -EINVAL;
  
  	data_pointer->press_to_select = value;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
432
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
433
434
435
  
  	return count;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
436
  static ssize_t attr_dragging_show_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
437
438
439
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
440
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
441
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
442
443
444
445
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ", data_pointer->dragging);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
446
  static ssize_t attr_dragging_store_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
447
448
449
450
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
451
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
452
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
453
  	int value;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
454
455
456
457
458
459
  	if (kstrtoint(buf, 10, &value))
  		return -EINVAL;
  	if (value < 0 || value > 1)
  		return -EINVAL;
  
  	data_pointer->dragging = value;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
460
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
461
462
463
  
  	return count;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
464
  static ssize_t attr_release_to_select_show_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
465
466
467
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
468
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
469
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
470
471
472
473
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ", data_pointer->release_to_select);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
474
  static ssize_t attr_release_to_select_store_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
475
476
477
478
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
479
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
480
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
481
  	int value;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
482
483
484
485
486
487
  	if (kstrtoint(buf, 10, &value))
  		return -EINVAL;
  	if (value < 0 || value > 1)
  		return -EINVAL;
  
  	data_pointer->release_to_select = value;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
488
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
489
490
491
  
  	return count;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
492
  static ssize_t attr_select_right_show_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
493
494
495
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
496
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
497
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
498
499
500
501
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ", data_pointer->select_right);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
502
  static ssize_t attr_select_right_store_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
503
504
505
506
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
507
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
508
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
509
  	int value;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
510
511
512
513
514
515
  	if (kstrtoint(buf, 10, &value))
  		return -EINVAL;
  	if (value < 0 || value > 1)
  		return -EINVAL;
  
  	data_pointer->select_right = value;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
516
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
517
518
519
  
  	return count;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
520
  static ssize_t attr_sensitivity_show_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
521
522
523
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
524
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
525
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
526
527
528
529
530
  
  	return snprintf(buf, PAGE_SIZE, "%u
  ",
  		data_pointer->sensitivity);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
531
  static ssize_t attr_sensitivity_store_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
532
533
534
535
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
536
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
537
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
538
  	int value;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
539
540
541
542
  	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
  		return -EINVAL;
  
  	data_pointer->sensitivity = value;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
543
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
544
545
546
  
  	return count;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
547
  static ssize_t attr_press_speed_show_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
548
549
550
  		struct device_attribute *attr,
  		char *buf)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
551
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
552
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
553

c1dcad2d3   Bernhard Seibold   HID: Driver for L...
554
555
556
557
  	return snprintf(buf, PAGE_SIZE, "%u
  ",
  		data_pointer->press_speed);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
558
  static ssize_t attr_press_speed_store_tpkbd(struct device *dev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
559
560
561
562
  		struct device_attribute *attr,
  		const char *buf,
  		size_t count)
  {
ee79a8f84   Geliang Tang   HID: use to_hid_d...
563
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
564
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
565
  	int value;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
566
567
568
569
  	if (kstrtoint(buf, 10, &value) || value < 1 || value > 255)
  		return -EINVAL;
  
  	data_pointer->press_speed = value;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
570
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
571
572
573
  
  	return count;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
574
  static struct device_attribute dev_attr_press_to_select_tpkbd =
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
575
  	__ATTR(press_to_select, S_IWUSR | S_IRUGO,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
576
577
  			attr_press_to_select_show_tpkbd,
  			attr_press_to_select_store_tpkbd);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
578

94723bfa7   Jamie Lentin   HID: lenovo: Rena...
579
  static struct device_attribute dev_attr_dragging_tpkbd =
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
580
  	__ATTR(dragging, S_IWUSR | S_IRUGO,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
581
582
  			attr_dragging_show_tpkbd,
  			attr_dragging_store_tpkbd);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
583

94723bfa7   Jamie Lentin   HID: lenovo: Rena...
584
  static struct device_attribute dev_attr_release_to_select_tpkbd =
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
585
  	__ATTR(release_to_select, S_IWUSR | S_IRUGO,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
586
587
  			attr_release_to_select_show_tpkbd,
  			attr_release_to_select_store_tpkbd);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
588

94723bfa7   Jamie Lentin   HID: lenovo: Rena...
589
  static struct device_attribute dev_attr_select_right_tpkbd =
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
590
  	__ATTR(select_right, S_IWUSR | S_IRUGO,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
591
592
  			attr_select_right_show_tpkbd,
  			attr_select_right_store_tpkbd);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
593

94723bfa7   Jamie Lentin   HID: lenovo: Rena...
594
  static struct device_attribute dev_attr_sensitivity_tpkbd =
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
595
  	__ATTR(sensitivity, S_IWUSR | S_IRUGO,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
596
597
  			attr_sensitivity_show_tpkbd,
  			attr_sensitivity_store_tpkbd);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
598

94723bfa7   Jamie Lentin   HID: lenovo: Rena...
599
  static struct device_attribute dev_attr_press_speed_tpkbd =
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
600
  	__ATTR(press_speed, S_IWUSR | S_IRUGO,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
601
602
603
604
605
606
607
608
609
610
  			attr_press_speed_show_tpkbd,
  			attr_press_speed_store_tpkbd);
  
  static struct attribute *lenovo_attributes_tpkbd[] = {
  	&dev_attr_press_to_select_tpkbd.attr,
  	&dev_attr_dragging_tpkbd.attr,
  	&dev_attr_release_to_select_tpkbd.attr,
  	&dev_attr_select_right_tpkbd.attr,
  	&dev_attr_sensitivity_tpkbd.attr,
  	&dev_attr_press_speed_tpkbd.attr,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
611
612
  	NULL
  };
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
613
614
  static const struct attribute_group lenovo_attr_group_tpkbd = {
  	.attrs = lenovo_attributes_tpkbd,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
615
  };
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
616
  static enum led_brightness lenovo_led_brightness_get_tpkbd(
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
617
618
  			struct led_classdev *led_cdev)
  {
832fbeaef   Axel Lin   HID: lenovo-tpkbd...
619
  	struct device *dev = led_cdev->dev->parent;
ee79a8f84   Geliang Tang   HID: use to_hid_d...
620
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
621
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
622
  	int led_nr = 0;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
623
624
625
626
627
628
629
  	if (led_cdev == &data_pointer->led_micmute)
  		led_nr = 1;
  
  	return data_pointer->led_state & (1 << led_nr)
  				? LED_FULL
  				: LED_OFF;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
630
  static void lenovo_led_brightness_set_tpkbd(struct led_classdev *led_cdev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
631
632
  			enum led_brightness value)
  {
832fbeaef   Axel Lin   HID: lenovo-tpkbd...
633
  	struct device *dev = led_cdev->dev->parent;
ee79a8f84   Geliang Tang   HID: use to_hid_d...
634
  	struct hid_device *hdev = to_hid_device(dev);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
635
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
636
  	struct hid_report *report;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
637
  	int led_nr = 0;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
638
639
640
641
642
643
644
645
646
647
648
  	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;
d88142725   Benjamin Tissoires   HID: use hid_hw_r...
649
  	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
650
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
651
  static int lenovo_probe_tpkbd(struct hid_device *hdev)
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
652
653
  {
  	struct device *dev = &hdev->dev;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
654
  	struct lenovo_drvdata_tpkbd *data_pointer;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
655
656
  	size_t name_sz = strlen(dev_name(dev)) + 16;
  	char *name_mute, *name_micmute;
01ab35f14   Benjamin Tissoires   HID: lenovo-tpkbd...
657
  	int i;
e0a6aad60   Jamie Lentin   HID: lenovo: Don'...
658
  	int ret;
0a9cd0a80   Kees Cook   HID: lenovo-tpkbd...
659

6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
660
661
662
663
664
665
666
667
  	/*
  	 * Only register extra settings against subdevice where input_mapping
  	 * set drvdata to 1, i.e. the trackpoint.
  	 */
  	if (!hid_get_drvdata(hdev))
  		return 0;
  
  	hid_set_drvdata(hdev, NULL);
0a9cd0a80   Kees Cook   HID: lenovo-tpkbd...
668
669
670
671
672
673
674
  	/* Validate required reports. */
  	for (i = 0; i < 4; i++) {
  		if (!hid_validate_values(hdev, HID_FEATURE_REPORT, 4, i, 1))
  			return -ENODEV;
  	}
  	if (!hid_validate_values(hdev, HID_OUTPUT_REPORT, 3, 0, 2))
  		return -ENODEV;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
675

e0a6aad60   Jamie Lentin   HID: lenovo: Don'...
676
677
678
679
  	ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
  	if (ret)
  		hid_warn(hdev, "Could not create sysfs group: %d
  ", ret);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
680

01ab35f14   Benjamin Tissoires   HID: lenovo-tpkbd...
681
  	data_pointer = devm_kzalloc(&hdev->dev,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
682
  				    sizeof(struct lenovo_drvdata_tpkbd),
01ab35f14   Benjamin Tissoires   HID: lenovo-tpkbd...
683
  				    GFP_KERNEL);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
684
685
686
  	if (data_pointer == NULL) {
  		hid_err(hdev, "Could not allocate memory for driver data
  ");
b72126008   Alexey Khoroshilov   HID: lenovo: Remo...
687
688
  		ret = -ENOMEM;
  		goto err;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
689
690
691
692
693
  	}
  
  	// set same default values as windows driver
  	data_pointer->sensitivity = 0xa0;
  	data_pointer->press_speed = 0x38;
01ab35f14   Benjamin Tissoires   HID: lenovo-tpkbd...
694
695
696
  	name_mute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
  	name_micmute = devm_kzalloc(&hdev->dev, name_sz, GFP_KERNEL);
  	if (name_mute == NULL || name_micmute == NULL) {
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
697
698
  		hid_err(hdev, "Could not allocate memory for led data
  ");
b72126008   Alexey Khoroshilov   HID: lenovo: Remo...
699
700
  		ret = -ENOMEM;
  		goto err;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
701
702
  	}
  	snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev));
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
703
704
705
706
707
  	snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev));
  
  	hid_set_drvdata(hdev, data_pointer);
  
  	data_pointer->led_mute.name = name_mute;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
708
709
  	data_pointer->led_mute.brightness_get = lenovo_led_brightness_get_tpkbd;
  	data_pointer->led_mute.brightness_set = lenovo_led_brightness_set_tpkbd;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
710
711
712
713
  	data_pointer->led_mute.dev = dev;
  	led_classdev_register(dev, &data_pointer->led_mute);
  
  	data_pointer->led_micmute.name = name_micmute;
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
714
715
716
717
  	data_pointer->led_micmute.brightness_get =
  		lenovo_led_brightness_get_tpkbd;
  	data_pointer->led_micmute.brightness_set =
  		lenovo_led_brightness_set_tpkbd;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
718
719
  	data_pointer->led_micmute.dev = dev;
  	led_classdev_register(dev, &data_pointer->led_micmute);
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
720
  	lenovo_features_set_tpkbd(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
721
722
  
  	return 0;
b72126008   Alexey Khoroshilov   HID: lenovo: Remo...
723
724
725
  err:
  	sysfs_remove_group(&hdev->dev.kobj, &lenovo_attr_group_tpkbd);
  	return ret;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
726
  }
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
  static int lenovo_probe_cptkbd(struct hid_device *hdev)
  {
  	int ret;
  	struct lenovo_drvdata_cptkbd *cptkbd_data;
  
  	/* All the custom action happens on the USBMOUSE device for USB */
  	if (hdev->product == USB_DEVICE_ID_LENOVO_CUSBKBD
  			&& hdev->type != HID_TYPE_USBMOUSE) {
  		hid_dbg(hdev, "Ignoring keyboard half of device
  ");
  		return 0;
  	}
  
  	cptkbd_data = devm_kzalloc(&hdev->dev,
  					sizeof(*cptkbd_data),
  					GFP_KERNEL);
  	if (cptkbd_data == NULL) {
  		hid_err(hdev, "can't alloc keyboard descriptor
  ");
  		return -ENOMEM;
  	}
  	hid_set_drvdata(hdev, cptkbd_data);
  
  	/*
  	 * Tell the keyboard a driver understands it, and turn F7, F9, F11 into
  	 * regular keys
  	 */
  	ret = lenovo_send_cmd_cptkbd(hdev, 0x01, 0x03);
  	if (ret)
  		hid_warn(hdev, "Failed to switch F7/9/11 mode: %d
  ", ret);
94eefa271   Jamie Lentin   HID: lenovo: Use ...
758
759
760
761
762
  	/* Switch middle button to native mode */
  	ret = lenovo_send_cmd_cptkbd(hdev, 0x09, 0x01);
  	if (ret)
  		hid_warn(hdev, "Failed to switch middle button: %d
  ", ret);
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
763
  	/* Set keyboard settings to known state */
3cb5ff022   Jamie Lentin   HID: lenovo: Hide...
764
  	cptkbd_data->middlebutton_state = 0;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
765
  	cptkbd_data->fn_lock = true;
e3cb0acd2   Jamie Lentin   HID: lenovo: Add ...
766
  	cptkbd_data->sensitivity = 0x05;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
767
768
769
770
771
772
773
774
775
  	lenovo_features_set_cptkbd(hdev);
  
  	ret = sysfs_create_group(&hdev->dev.kobj, &lenovo_attr_group_cptkbd);
  	if (ret)
  		hid_warn(hdev, "Could not create sysfs group: %d
  ", ret);
  
  	return 0;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
776
  static int lenovo_probe(struct hid_device *hdev,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
777
778
779
  		const struct hid_device_id *id)
  {
  	int ret;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
780
781
782
783
784
  
  	ret = hid_parse(hdev);
  	if (ret) {
  		hid_err(hdev, "hid_parse failed
  ");
0ccdd9e74   Benjamin Tissoires   HID: lenovo-tpkbd...
785
  		goto err;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
786
787
788
789
790
791
  	}
  
  	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
  	if (ret) {
  		hid_err(hdev, "hid_hw_start failed
  ");
0ccdd9e74   Benjamin Tissoires   HID: lenovo-tpkbd...
792
  		goto err;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
793
  	}
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
794
795
  	switch (hdev->product) {
  	case USB_DEVICE_ID_LENOVO_TPKBD:
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
796
  		ret = lenovo_probe_tpkbd(hdev);
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
797
  		break;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
798
799
800
801
  	case USB_DEVICE_ID_LENOVO_CUSBKBD:
  	case USB_DEVICE_ID_LENOVO_CBTKBD:
  		ret = lenovo_probe_cptkbd(hdev);
  		break;
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
802
803
804
  	default:
  		ret = 0;
  		break;
0ccdd9e74   Benjamin Tissoires   HID: lenovo-tpkbd...
805
  	}
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
806
807
  	if (ret)
  		goto err_hid;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
808
809
  
  	return 0;
0ccdd9e74   Benjamin Tissoires   HID: lenovo-tpkbd...
810
811
812
  err_hid:
  	hid_hw_stop(hdev);
  err:
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
813
814
  	return ret;
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
815
  static void lenovo_remove_tpkbd(struct hid_device *hdev)
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
816
  {
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
817
  	struct lenovo_drvdata_tpkbd *data_pointer = hid_get_drvdata(hdev);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
818

6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
819
820
821
822
823
824
  	/*
  	 * Only the trackpoint half of the keyboard has drvdata and stuff that
  	 * needs unregistering.
  	 */
  	if (data_pointer == NULL)
  		return;
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
825
  	sysfs_remove_group(&hdev->dev.kobj,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
826
  			&lenovo_attr_group_tpkbd);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
827

c1dcad2d3   Bernhard Seibold   HID: Driver for L...
828
829
830
831
  	led_classdev_unregister(&data_pointer->led_micmute);
  	led_classdev_unregister(&data_pointer->led_mute);
  
  	hid_set_drvdata(hdev, NULL);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
832
  }
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
833
834
835
836
837
  static void lenovo_remove_cptkbd(struct hid_device *hdev)
  {
  	sysfs_remove_group(&hdev->dev.kobj,
  			&lenovo_attr_group_cptkbd);
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
838
  static void lenovo_remove(struct hid_device *hdev)
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
839
  {
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
840
841
  	switch (hdev->product) {
  	case USB_DEVICE_ID_LENOVO_TPKBD:
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
842
  		lenovo_remove_tpkbd(hdev);
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
843
  		break;
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
844
845
846
847
  	case USB_DEVICE_ID_LENOVO_CUSBKBD:
  	case USB_DEVICE_ID_LENOVO_CBTKBD:
  		lenovo_remove_cptkbd(hdev);
  		break;
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
848
  	}
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
849
850
851
  
  	hid_hw_stop(hdev);
  }
9154301a4   Dmitry Torokhov   HID: hid-input: a...
852
  static int lenovo_input_configured(struct hid_device *hdev,
d92189ebb   Andreas Fleig   HID: lenovo: set ...
853
854
855
856
857
858
859
860
861
862
863
864
865
866
  		struct hid_input *hi)
  {
  	switch (hdev->product) {
  		case USB_DEVICE_ID_LENOVO_TPKBD:
  		case USB_DEVICE_ID_LENOVO_CUSBKBD:
  		case USB_DEVICE_ID_LENOVO_CBTKBD:
  			if (test_bit(EV_REL, hi->input->evbit)) {
  				/* set only for trackpoint device */
  				__set_bit(INPUT_PROP_POINTER, hi->input->propbit);
  				__set_bit(INPUT_PROP_POINTING_STICK,
  						hi->input->propbit);
  			}
  			break;
  	}
9154301a4   Dmitry Torokhov   HID: hid-input: a...
867
868
  
  	return 0;
d92189ebb   Andreas Fleig   HID: lenovo: set ...
869
  }
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
870
  static const struct hid_device_id lenovo_devices[] = {
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
871
  	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) },
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
872
873
  	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CUSBKBD) },
  	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_CBTKBD) },
181a8b911   Benjamin Tissoires   HID: lenovo: add ...
874
  	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPPRODOCK) },
2f3ca3908   pgzh   HID: lenovo: Add ...
875
876
877
878
879
880
  	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_III) },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_PRO) },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_OPTICAL) },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL) },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_IBM, USB_DEVICE_ID_IBM_SCROLLPOINT_800DPI_OPTICAL_PRO) },
  	{ HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_SCROLLPOINT_OPTICAL) },
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
881
882
  	{ }
  };
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
883
  MODULE_DEVICE_TABLE(hid, lenovo_devices);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
884

94723bfa7   Jamie Lentin   HID: lenovo: Rena...
885
886
887
  static struct hid_driver lenovo_driver = {
  	.name = "lenovo",
  	.id_table = lenovo_devices,
d92189ebb   Andreas Fleig   HID: lenovo: set ...
888
  	.input_configured = lenovo_input_configured,
6a5b414b4   Jamie Lentin   HID: lenovo: Prep...
889
  	.input_mapping = lenovo_input_mapping,
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
890
891
  	.probe = lenovo_probe,
  	.remove = lenovo_remove,
f3d4ff0e0   Jamie Lentin   HID: lenovo: Add ...
892
  	.raw_event = lenovo_raw_event,
3cb5ff022   Jamie Lentin   HID: lenovo: Hide...
893
  	.event = lenovo_event,
181a8b911   Benjamin Tissoires   HID: lenovo: add ...
894
  	.report_fixup = lenovo_report_fixup,
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
895
  };
94723bfa7   Jamie Lentin   HID: lenovo: Rena...
896
  module_hid_driver(lenovo_driver);
c1dcad2d3   Bernhard Seibold   HID: Driver for L...
897
898
  
  MODULE_LICENSE("GPL");