2 * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint
4 * Copyright (c) 2012 Bernhard Seibold
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU General Public License as published by the Free
10 * Software Foundation; either version 2 of the License, or (at your option)
14 #include <linux/module.h>
15 #include <linux/sysfs.h>
16 #include <linux/device.h>
17 #include <linux/hid.h>
18 #include <linux/input.h>
19 #include <linux/leds.h>
23 struct lenovo_drvdata_tpkbd
{
25 struct led_classdev led_mute
;
26 struct led_classdev led_micmute
;
29 int release_to_select
;
35 #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c))
37 static int lenovo_input_mapping_tpkbd(struct hid_device
*hdev
,
38 struct hid_input
*hi
, struct hid_field
*field
,
39 struct hid_usage
*usage
, unsigned long **bit
, int *max
)
41 if (usage
->hid
== (HID_UP_BUTTON
| 0x0010)) {
42 /* mark the device as pointer */
43 hid_set_drvdata(hdev
, (void *)1);
44 map_key_clear(KEY_MICMUTE
);
52 static int lenovo_features_set_tpkbd(struct hid_device
*hdev
)
54 struct hid_report
*report
;
55 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
57 report
= hdev
->report_enum
[HID_FEATURE_REPORT
].report_id_hash
[4];
59 report
->field
[0]->value
[0] = data_pointer
->press_to_select
? 0x01 : 0x02;
60 report
->field
[0]->value
[0] |= data_pointer
->dragging
? 0x04 : 0x08;
61 report
->field
[0]->value
[0] |= data_pointer
->release_to_select
? 0x10 : 0x20;
62 report
->field
[0]->value
[0] |= data_pointer
->select_right
? 0x80 : 0x40;
63 report
->field
[1]->value
[0] = 0x03; // unknown setting, imitate windows driver
64 report
->field
[2]->value
[0] = data_pointer
->sensitivity
;
65 report
->field
[3]->value
[0] = data_pointer
->press_speed
;
67 hid_hw_request(hdev
, report
, HID_REQ_SET_REPORT
);
71 static ssize_t
attr_press_to_select_show_tpkbd(struct device
*dev
,
72 struct device_attribute
*attr
,
75 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
76 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
78 return snprintf(buf
, PAGE_SIZE
, "%u\n", data_pointer
->press_to_select
);
81 static ssize_t
attr_press_to_select_store_tpkbd(struct device
*dev
,
82 struct device_attribute
*attr
,
86 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
87 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
90 if (kstrtoint(buf
, 10, &value
))
92 if (value
< 0 || value
> 1)
95 data_pointer
->press_to_select
= value
;
96 lenovo_features_set_tpkbd(hdev
);
101 static ssize_t
attr_dragging_show_tpkbd(struct device
*dev
,
102 struct device_attribute
*attr
,
105 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
106 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
108 return snprintf(buf
, PAGE_SIZE
, "%u\n", data_pointer
->dragging
);
111 static ssize_t
attr_dragging_store_tpkbd(struct device
*dev
,
112 struct device_attribute
*attr
,
116 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
117 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
120 if (kstrtoint(buf
, 10, &value
))
122 if (value
< 0 || value
> 1)
125 data_pointer
->dragging
= value
;
126 lenovo_features_set_tpkbd(hdev
);
131 static ssize_t
attr_release_to_select_show_tpkbd(struct device
*dev
,
132 struct device_attribute
*attr
,
135 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
136 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
138 return snprintf(buf
, PAGE_SIZE
, "%u\n", data_pointer
->release_to_select
);
141 static ssize_t
attr_release_to_select_store_tpkbd(struct device
*dev
,
142 struct device_attribute
*attr
,
146 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
147 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
150 if (kstrtoint(buf
, 10, &value
))
152 if (value
< 0 || value
> 1)
155 data_pointer
->release_to_select
= value
;
156 lenovo_features_set_tpkbd(hdev
);
161 static ssize_t
attr_select_right_show_tpkbd(struct device
*dev
,
162 struct device_attribute
*attr
,
165 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
166 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
168 return snprintf(buf
, PAGE_SIZE
, "%u\n", data_pointer
->select_right
);
171 static ssize_t
attr_select_right_store_tpkbd(struct device
*dev
,
172 struct device_attribute
*attr
,
176 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
177 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
180 if (kstrtoint(buf
, 10, &value
))
182 if (value
< 0 || value
> 1)
185 data_pointer
->select_right
= value
;
186 lenovo_features_set_tpkbd(hdev
);
191 static ssize_t
attr_sensitivity_show_tpkbd(struct device
*dev
,
192 struct device_attribute
*attr
,
195 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
196 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
198 return snprintf(buf
, PAGE_SIZE
, "%u\n",
199 data_pointer
->sensitivity
);
202 static ssize_t
attr_sensitivity_store_tpkbd(struct device
*dev
,
203 struct device_attribute
*attr
,
207 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
208 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
211 if (kstrtoint(buf
, 10, &value
) || value
< 1 || value
> 255)
214 data_pointer
->sensitivity
= value
;
215 lenovo_features_set_tpkbd(hdev
);
220 static ssize_t
attr_press_speed_show_tpkbd(struct device
*dev
,
221 struct device_attribute
*attr
,
224 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
225 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
227 return snprintf(buf
, PAGE_SIZE
, "%u\n",
228 data_pointer
->press_speed
);
231 static ssize_t
attr_press_speed_store_tpkbd(struct device
*dev
,
232 struct device_attribute
*attr
,
236 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
237 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
240 if (kstrtoint(buf
, 10, &value
) || value
< 1 || value
> 255)
243 data_pointer
->press_speed
= value
;
244 lenovo_features_set_tpkbd(hdev
);
249 static struct device_attribute dev_attr_press_to_select_tpkbd
=
250 __ATTR(press_to_select
, S_IWUSR
| S_IRUGO
,
251 attr_press_to_select_show_tpkbd
,
252 attr_press_to_select_store_tpkbd
);
254 static struct device_attribute dev_attr_dragging_tpkbd
=
255 __ATTR(dragging
, S_IWUSR
| S_IRUGO
,
256 attr_dragging_show_tpkbd
,
257 attr_dragging_store_tpkbd
);
259 static struct device_attribute dev_attr_release_to_select_tpkbd
=
260 __ATTR(release_to_select
, S_IWUSR
| S_IRUGO
,
261 attr_release_to_select_show_tpkbd
,
262 attr_release_to_select_store_tpkbd
);
264 static struct device_attribute dev_attr_select_right_tpkbd
=
265 __ATTR(select_right
, S_IWUSR
| S_IRUGO
,
266 attr_select_right_show_tpkbd
,
267 attr_select_right_store_tpkbd
);
269 static struct device_attribute dev_attr_sensitivity_tpkbd
=
270 __ATTR(sensitivity
, S_IWUSR
| S_IRUGO
,
271 attr_sensitivity_show_tpkbd
,
272 attr_sensitivity_store_tpkbd
);
274 static struct device_attribute dev_attr_press_speed_tpkbd
=
275 __ATTR(press_speed
, S_IWUSR
| S_IRUGO
,
276 attr_press_speed_show_tpkbd
,
277 attr_press_speed_store_tpkbd
);
279 static struct attribute
*lenovo_attributes_tpkbd
[] = {
280 &dev_attr_press_to_select_tpkbd
.attr
,
281 &dev_attr_dragging_tpkbd
.attr
,
282 &dev_attr_release_to_select_tpkbd
.attr
,
283 &dev_attr_select_right_tpkbd
.attr
,
284 &dev_attr_sensitivity_tpkbd
.attr
,
285 &dev_attr_press_speed_tpkbd
.attr
,
289 static const struct attribute_group lenovo_attr_group_tpkbd
= {
290 .attrs
= lenovo_attributes_tpkbd
,
293 static enum led_brightness
lenovo_led_brightness_get_tpkbd(
294 struct led_classdev
*led_cdev
)
296 struct device
*dev
= led_cdev
->dev
->parent
;
297 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
298 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
301 if (led_cdev
== &data_pointer
->led_micmute
)
304 return data_pointer
->led_state
& (1 << led_nr
)
309 static void lenovo_led_brightness_set_tpkbd(struct led_classdev
*led_cdev
,
310 enum led_brightness value
)
312 struct device
*dev
= led_cdev
->dev
->parent
;
313 struct hid_device
*hdev
= container_of(dev
, struct hid_device
, dev
);
314 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
315 struct hid_report
*report
;
318 if (led_cdev
== &data_pointer
->led_micmute
)
321 if (value
== LED_OFF
)
322 data_pointer
->led_state
&= ~(1 << led_nr
);
324 data_pointer
->led_state
|= 1 << led_nr
;
326 report
= hdev
->report_enum
[HID_OUTPUT_REPORT
].report_id_hash
[3];
327 report
->field
[0]->value
[0] = (data_pointer
->led_state
>> 0) & 1;
328 report
->field
[0]->value
[1] = (data_pointer
->led_state
>> 1) & 1;
329 hid_hw_request(hdev
, report
, HID_REQ_SET_REPORT
);
332 static int lenovo_probe_tpkbd(struct hid_device
*hdev
)
334 struct device
*dev
= &hdev
->dev
;
335 struct lenovo_drvdata_tpkbd
*data_pointer
;
336 size_t name_sz
= strlen(dev_name(dev
)) + 16;
337 char *name_mute
, *name_micmute
;
340 /* Validate required reports. */
341 for (i
= 0; i
< 4; i
++) {
342 if (!hid_validate_values(hdev
, HID_FEATURE_REPORT
, 4, i
, 1))
345 if (!hid_validate_values(hdev
, HID_OUTPUT_REPORT
, 3, 0, 2))
348 if (sysfs_create_group(&hdev
->dev
.kobj
,
349 &lenovo_attr_group_tpkbd
)) {
350 hid_warn(hdev
, "Could not create sysfs group\n");
353 data_pointer
= devm_kzalloc(&hdev
->dev
,
354 sizeof(struct lenovo_drvdata_tpkbd
),
356 if (data_pointer
== NULL
) {
357 hid_err(hdev
, "Could not allocate memory for driver data\n");
361 // set same default values as windows driver
362 data_pointer
->sensitivity
= 0xa0;
363 data_pointer
->press_speed
= 0x38;
365 name_mute
= devm_kzalloc(&hdev
->dev
, name_sz
, GFP_KERNEL
);
366 name_micmute
= devm_kzalloc(&hdev
->dev
, name_sz
, GFP_KERNEL
);
367 if (name_mute
== NULL
|| name_micmute
== NULL
) {
368 hid_err(hdev
, "Could not allocate memory for led data\n");
371 snprintf(name_mute
, name_sz
, "%s:amber:mute", dev_name(dev
));
372 snprintf(name_micmute
, name_sz
, "%s:amber:micmute", dev_name(dev
));
374 hid_set_drvdata(hdev
, data_pointer
);
376 data_pointer
->led_mute
.name
= name_mute
;
377 data_pointer
->led_mute
.brightness_get
= lenovo_led_brightness_get_tpkbd
;
378 data_pointer
->led_mute
.brightness_set
= lenovo_led_brightness_set_tpkbd
;
379 data_pointer
->led_mute
.dev
= dev
;
380 led_classdev_register(dev
, &data_pointer
->led_mute
);
382 data_pointer
->led_micmute
.name
= name_micmute
;
383 data_pointer
->led_micmute
.brightness_get
=
384 lenovo_led_brightness_get_tpkbd
;
385 data_pointer
->led_micmute
.brightness_set
=
386 lenovo_led_brightness_set_tpkbd
;
387 data_pointer
->led_micmute
.dev
= dev
;
388 led_classdev_register(dev
, &data_pointer
->led_micmute
);
390 lenovo_features_set_tpkbd(hdev
);
395 static int lenovo_probe(struct hid_device
*hdev
,
396 const struct hid_device_id
*id
)
400 ret
= hid_parse(hdev
);
402 hid_err(hdev
, "hid_parse failed\n");
406 ret
= hid_hw_start(hdev
, HID_CONNECT_DEFAULT
);
408 hid_err(hdev
, "hid_hw_start failed\n");
412 if (hid_get_drvdata(hdev
)) {
413 hid_set_drvdata(hdev
, NULL
);
414 ret
= lenovo_probe_tpkbd(hdev
);
426 static void lenovo_remove_tpkbd(struct hid_device
*hdev
)
428 struct lenovo_drvdata_tpkbd
*data_pointer
= hid_get_drvdata(hdev
);
430 sysfs_remove_group(&hdev
->dev
.kobj
,
431 &lenovo_attr_group_tpkbd
);
433 led_classdev_unregister(&data_pointer
->led_micmute
);
434 led_classdev_unregister(&data_pointer
->led_mute
);
436 hid_set_drvdata(hdev
, NULL
);
439 static void lenovo_remove(struct hid_device
*hdev
)
441 if (hid_get_drvdata(hdev
))
442 lenovo_remove_tpkbd(hdev
);
447 static const struct hid_device_id lenovo_devices
[] = {
448 { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO
, USB_DEVICE_ID_LENOVO_TPKBD
) },
452 MODULE_DEVICE_TABLE(hid
, lenovo_devices
);
454 static struct hid_driver lenovo_driver
= {
456 .id_table
= lenovo_devices
,
457 .input_mapping
= lenovo_input_mapping_tpkbd
,
458 .probe
= lenovo_probe
,
459 .remove
= lenovo_remove
,
461 module_hid_driver(lenovo_driver
);
463 MODULE_LICENSE("GPL");