diff options
Diffstat (limited to 'drivers/hid/hid-lenovo-tpkbd.c')
-rw-r--r-- | drivers/hid/hid-lenovo-tpkbd.c | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/drivers/hid/hid-lenovo-tpkbd.c b/drivers/hid/hid-lenovo-tpkbd.c new file mode 100644 index 000000000000..77d2df04c97b --- /dev/null +++ b/drivers/hid/hid-lenovo-tpkbd.c | |||
@@ -0,0 +1,564 @@ | |||
1 | /* | ||
2 | * HID driver for Lenovo ThinkPad USB Keyboard with TrackPoint | ||
3 | * | ||
4 | * Copyright (c) 2012 Bernhard Seibold | ||
5 | */ | ||
6 | |||
7 | /* | ||
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) | ||
11 | * any later version. | ||
12 | */ | ||
13 | |||
14 | #include <linux/module.h> | ||
15 | #include <linux/sysfs.h> | ||
16 | #include <linux/device.h> | ||
17 | #include <linux/usb.h> | ||
18 | #include <linux/hid.h> | ||
19 | #include <linux/input.h> | ||
20 | #include <linux/leds.h> | ||
21 | #include "usbhid/usbhid.h" | ||
22 | |||
23 | #include "hid-ids.h" | ||
24 | |||
25 | /* This is only used for the trackpoint part of the driver, hence _tp */ | ||
26 | struct tpkbd_data_pointer { | ||
27 | int led_state; | ||
28 | struct led_classdev led_mute; | ||
29 | struct led_classdev led_micmute; | ||
30 | int press_to_select; | ||
31 | int dragging; | ||
32 | int release_to_select; | ||
33 | int select_right; | ||
34 | int sensitivity; | ||
35 | int press_speed; | ||
36 | }; | ||
37 | |||
38 | #define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) | ||
39 | |||
40 | static int tpkbd_input_mapping(struct hid_device *hdev, | ||
41 | struct hid_input *hi, struct hid_field *field, | ||
42 | struct hid_usage *usage, unsigned long **bit, int *max) | ||
43 | { | ||
44 | struct usbhid_device *uhdev; | ||
45 | |||
46 | uhdev = (struct usbhid_device *) hdev->driver_data; | ||
47 | if (uhdev->ifnum == 1 && usage->hid == (HID_UP_BUTTON | 0x0010)) { | ||
48 | map_key_clear(KEY_MICMUTE); | ||
49 | return 1; | ||
50 | } | ||
51 | return 0; | ||
52 | } | ||
53 | |||
54 | #undef map_key_clear | ||
55 | |||
56 | static int tpkbd_features_set(struct hid_device *hdev) | ||
57 | { | ||
58 | struct hid_report *report; | ||
59 | struct tpkbd_data_pointer *data_pointer; | ||
60 | |||
61 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
62 | report = hdev->report_enum[HID_FEATURE_REPORT].report_id_hash[4]; | ||
63 | |||
64 | report->field[0]->value[0] = data_pointer->press_to_select ? 0x01 : 0x02; | ||
65 | report->field[0]->value[0] |= data_pointer->dragging ? 0x04 : 0x08; | ||
66 | report->field[0]->value[0] |= data_pointer->release_to_select ? 0x10 : 0x20; | ||
67 | report->field[0]->value[0] |= data_pointer->select_right ? 0x80 : 0x40; | ||
68 | report->field[1]->value[0] = 0x03; // unknown setting, imitate windows driver | ||
69 | report->field[2]->value[0] = data_pointer->sensitivity; | ||
70 | report->field[3]->value[0] = data_pointer->press_speed; | ||
71 | |||
72 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | ||
73 | return 0; | ||
74 | } | ||
75 | |||
76 | static ssize_t pointer_press_to_select_show(struct device *dev, | ||
77 | struct device_attribute *attr, | ||
78 | char *buf) | ||
79 | { | ||
80 | struct hid_device *hdev; | ||
81 | struct tpkbd_data_pointer *data_pointer; | ||
82 | |||
83 | hdev = container_of(dev, struct hid_device, dev); | ||
84 | if (hdev == NULL) | ||
85 | return -ENODEV; | ||
86 | |||
87 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
88 | |||
89 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->press_to_select); | ||
90 | } | ||
91 | |||
92 | static ssize_t pointer_press_to_select_store(struct device *dev, | ||
93 | struct device_attribute *attr, | ||
94 | const char *buf, | ||
95 | size_t count) | ||
96 | { | ||
97 | struct hid_device *hdev; | ||
98 | struct tpkbd_data_pointer *data_pointer; | ||
99 | int value; | ||
100 | |||
101 | hdev = container_of(dev, struct hid_device, dev); | ||
102 | if (hdev == NULL) | ||
103 | return -ENODEV; | ||
104 | |||
105 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
106 | |||
107 | if (kstrtoint(buf, 10, &value)) | ||
108 | return -EINVAL; | ||
109 | if (value < 0 || value > 1) | ||
110 | return -EINVAL; | ||
111 | |||
112 | data_pointer->press_to_select = value; | ||
113 | tpkbd_features_set(hdev); | ||
114 | |||
115 | return count; | ||
116 | } | ||
117 | |||
118 | static ssize_t pointer_dragging_show(struct device *dev, | ||
119 | struct device_attribute *attr, | ||
120 | char *buf) | ||
121 | { | ||
122 | struct hid_device *hdev; | ||
123 | struct tpkbd_data_pointer *data_pointer; | ||
124 | |||
125 | hdev = container_of(dev, struct hid_device, dev); | ||
126 | if (hdev == NULL) | ||
127 | return -ENODEV; | ||
128 | |||
129 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
130 | |||
131 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->dragging); | ||
132 | } | ||
133 | |||
134 | static ssize_t pointer_dragging_store(struct device *dev, | ||
135 | struct device_attribute *attr, | ||
136 | const char *buf, | ||
137 | size_t count) | ||
138 | { | ||
139 | struct hid_device *hdev; | ||
140 | struct tpkbd_data_pointer *data_pointer; | ||
141 | int value; | ||
142 | |||
143 | hdev = container_of(dev, struct hid_device, dev); | ||
144 | if (hdev == NULL) | ||
145 | return -ENODEV; | ||
146 | |||
147 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
148 | |||
149 | if (kstrtoint(buf, 10, &value)) | ||
150 | return -EINVAL; | ||
151 | if (value < 0 || value > 1) | ||
152 | return -EINVAL; | ||
153 | |||
154 | data_pointer->dragging = value; | ||
155 | tpkbd_features_set(hdev); | ||
156 | |||
157 | return count; | ||
158 | } | ||
159 | |||
160 | static ssize_t pointer_release_to_select_show(struct device *dev, | ||
161 | struct device_attribute *attr, | ||
162 | char *buf) | ||
163 | { | ||
164 | struct hid_device *hdev; | ||
165 | struct tpkbd_data_pointer *data_pointer; | ||
166 | |||
167 | hdev = container_of(dev, struct hid_device, dev); | ||
168 | if (hdev == NULL) | ||
169 | return -ENODEV; | ||
170 | |||
171 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
172 | |||
173 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->release_to_select); | ||
174 | } | ||
175 | |||
176 | static ssize_t pointer_release_to_select_store(struct device *dev, | ||
177 | struct device_attribute *attr, | ||
178 | const char *buf, | ||
179 | size_t count) | ||
180 | { | ||
181 | struct hid_device *hdev; | ||
182 | struct tpkbd_data_pointer *data_pointer; | ||
183 | int value; | ||
184 | |||
185 | hdev = container_of(dev, struct hid_device, dev); | ||
186 | if (hdev == NULL) | ||
187 | return -ENODEV; | ||
188 | |||
189 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
190 | |||
191 | if (kstrtoint(buf, 10, &value)) | ||
192 | return -EINVAL; | ||
193 | if (value < 0 || value > 1) | ||
194 | return -EINVAL; | ||
195 | |||
196 | data_pointer->release_to_select = value; | ||
197 | tpkbd_features_set(hdev); | ||
198 | |||
199 | return count; | ||
200 | } | ||
201 | |||
202 | static ssize_t pointer_select_right_show(struct device *dev, | ||
203 | struct device_attribute *attr, | ||
204 | char *buf) | ||
205 | { | ||
206 | struct hid_device *hdev; | ||
207 | struct tpkbd_data_pointer *data_pointer; | ||
208 | |||
209 | hdev = container_of(dev, struct hid_device, dev); | ||
210 | if (hdev == NULL) | ||
211 | return -ENODEV; | ||
212 | |||
213 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
214 | |||
215 | return snprintf(buf, PAGE_SIZE, "%u\n", data_pointer->select_right); | ||
216 | } | ||
217 | |||
218 | static ssize_t pointer_select_right_store(struct device *dev, | ||
219 | struct device_attribute *attr, | ||
220 | const char *buf, | ||
221 | size_t count) | ||
222 | { | ||
223 | struct hid_device *hdev; | ||
224 | struct tpkbd_data_pointer *data_pointer; | ||
225 | int value; | ||
226 | |||
227 | hdev = container_of(dev, struct hid_device, dev); | ||
228 | if (hdev == NULL) | ||
229 | return -ENODEV; | ||
230 | |||
231 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
232 | |||
233 | if (kstrtoint(buf, 10, &value)) | ||
234 | return -EINVAL; | ||
235 | if (value < 0 || value > 1) | ||
236 | return -EINVAL; | ||
237 | |||
238 | data_pointer->select_right = value; | ||
239 | tpkbd_features_set(hdev); | ||
240 | |||
241 | return count; | ||
242 | } | ||
243 | |||
244 | static ssize_t pointer_sensitivity_show(struct device *dev, | ||
245 | struct device_attribute *attr, | ||
246 | char *buf) | ||
247 | { | ||
248 | struct hid_device *hdev; | ||
249 | struct tpkbd_data_pointer *data_pointer; | ||
250 | |||
251 | hdev = container_of(dev, struct hid_device, dev); | ||
252 | if (hdev == NULL) | ||
253 | return -ENODEV; | ||
254 | |||
255 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
256 | |||
257 | return snprintf(buf, PAGE_SIZE, "%u\n", | ||
258 | data_pointer->sensitivity); | ||
259 | } | ||
260 | |||
261 | static ssize_t pointer_sensitivity_store(struct device *dev, | ||
262 | struct device_attribute *attr, | ||
263 | const char *buf, | ||
264 | size_t count) | ||
265 | { | ||
266 | struct hid_device *hdev; | ||
267 | struct tpkbd_data_pointer *data_pointer; | ||
268 | int value; | ||
269 | |||
270 | hdev = container_of(dev, struct hid_device, dev); | ||
271 | if (hdev == NULL) | ||
272 | return -ENODEV; | ||
273 | |||
274 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
275 | |||
276 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) | ||
277 | return -EINVAL; | ||
278 | |||
279 | data_pointer->sensitivity = value; | ||
280 | tpkbd_features_set(hdev); | ||
281 | |||
282 | return count; | ||
283 | } | ||
284 | |||
285 | static ssize_t pointer_press_speed_show(struct device *dev, | ||
286 | struct device_attribute *attr, | ||
287 | char *buf) | ||
288 | { | ||
289 | struct hid_device *hdev; | ||
290 | struct tpkbd_data_pointer *data_pointer; | ||
291 | |||
292 | hdev = container_of(dev, struct hid_device, dev); | ||
293 | if (hdev == NULL) | ||
294 | return -ENODEV; | ||
295 | |||
296 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
297 | |||
298 | return snprintf(buf, PAGE_SIZE, "%u\n", | ||
299 | data_pointer->press_speed); | ||
300 | } | ||
301 | |||
302 | static ssize_t pointer_press_speed_store(struct device *dev, | ||
303 | struct device_attribute *attr, | ||
304 | const char *buf, | ||
305 | size_t count) | ||
306 | { | ||
307 | struct hid_device *hdev; | ||
308 | struct tpkbd_data_pointer *data_pointer; | ||
309 | int value; | ||
310 | |||
311 | hdev = container_of(dev, struct hid_device, dev); | ||
312 | if (hdev == NULL) | ||
313 | return -ENODEV; | ||
314 | |||
315 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
316 | |||
317 | if (kstrtoint(buf, 10, &value) || value < 1 || value > 255) | ||
318 | return -EINVAL; | ||
319 | |||
320 | data_pointer->press_speed = value; | ||
321 | tpkbd_features_set(hdev); | ||
322 | |||
323 | return count; | ||
324 | } | ||
325 | |||
326 | static struct device_attribute dev_attr_pointer_press_to_select = | ||
327 | __ATTR(press_to_select, S_IWUSR | S_IRUGO, | ||
328 | pointer_press_to_select_show, | ||
329 | pointer_press_to_select_store); | ||
330 | |||
331 | static struct device_attribute dev_attr_pointer_dragging = | ||
332 | __ATTR(dragging, S_IWUSR | S_IRUGO, | ||
333 | pointer_dragging_show, | ||
334 | pointer_dragging_store); | ||
335 | |||
336 | static struct device_attribute dev_attr_pointer_release_to_select = | ||
337 | __ATTR(release_to_select, S_IWUSR | S_IRUGO, | ||
338 | pointer_release_to_select_show, | ||
339 | pointer_release_to_select_store); | ||
340 | |||
341 | static struct device_attribute dev_attr_pointer_select_right = | ||
342 | __ATTR(select_right, S_IWUSR | S_IRUGO, | ||
343 | pointer_select_right_show, | ||
344 | pointer_select_right_store); | ||
345 | |||
346 | static struct device_attribute dev_attr_pointer_sensitivity = | ||
347 | __ATTR(sensitivity, S_IWUSR | S_IRUGO, | ||
348 | pointer_sensitivity_show, | ||
349 | pointer_sensitivity_store); | ||
350 | |||
351 | static struct device_attribute dev_attr_pointer_press_speed = | ||
352 | __ATTR(press_speed, S_IWUSR | S_IRUGO, | ||
353 | pointer_press_speed_show, | ||
354 | pointer_press_speed_store); | ||
355 | |||
356 | static struct attribute *tpkbd_attributes_pointer[] = { | ||
357 | &dev_attr_pointer_press_to_select.attr, | ||
358 | &dev_attr_pointer_dragging.attr, | ||
359 | &dev_attr_pointer_release_to_select.attr, | ||
360 | &dev_attr_pointer_select_right.attr, | ||
361 | &dev_attr_pointer_sensitivity.attr, | ||
362 | &dev_attr_pointer_press_speed.attr, | ||
363 | NULL | ||
364 | }; | ||
365 | |||
366 | static const struct attribute_group tpkbd_attr_group_pointer = { | ||
367 | .attrs = tpkbd_attributes_pointer, | ||
368 | }; | ||
369 | |||
370 | static enum led_brightness tpkbd_led_brightness_get( | ||
371 | struct led_classdev *led_cdev) | ||
372 | { | ||
373 | struct device *dev; | ||
374 | struct hid_device *hdev; | ||
375 | struct tpkbd_data_pointer *data_pointer; | ||
376 | int led_nr = 0; | ||
377 | |||
378 | dev = led_cdev->dev->parent; | ||
379 | hdev = container_of(dev, struct hid_device, dev); | ||
380 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
381 | |||
382 | if (led_cdev == &data_pointer->led_micmute) | ||
383 | led_nr = 1; | ||
384 | |||
385 | return data_pointer->led_state & (1 << led_nr) | ||
386 | ? LED_FULL | ||
387 | : LED_OFF; | ||
388 | } | ||
389 | |||
390 | static void tpkbd_led_brightness_set(struct led_classdev *led_cdev, | ||
391 | enum led_brightness value) | ||
392 | { | ||
393 | struct device *dev; | ||
394 | struct hid_device *hdev; | ||
395 | struct hid_report *report; | ||
396 | struct tpkbd_data_pointer *data_pointer; | ||
397 | int led_nr = 0; | ||
398 | |||
399 | dev = led_cdev->dev->parent; | ||
400 | hdev = container_of(dev, struct hid_device, dev); | ||
401 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
402 | |||
403 | if (led_cdev == &data_pointer->led_micmute) | ||
404 | led_nr = 1; | ||
405 | |||
406 | if (value == LED_OFF) | ||
407 | data_pointer->led_state &= ~(1 << led_nr); | ||
408 | else | ||
409 | data_pointer->led_state |= 1 << led_nr; | ||
410 | |||
411 | report = hdev->report_enum[HID_OUTPUT_REPORT].report_id_hash[3]; | ||
412 | report->field[0]->value[0] = (data_pointer->led_state >> 0) & 1; | ||
413 | report->field[0]->value[1] = (data_pointer->led_state >> 1) & 1; | ||
414 | usbhid_submit_report(hdev, report, USB_DIR_OUT); | ||
415 | } | ||
416 | |||
417 | static int tpkbd_probe_tp(struct hid_device *hdev) | ||
418 | { | ||
419 | struct device *dev = &hdev->dev; | ||
420 | struct tpkbd_data_pointer *data_pointer; | ||
421 | size_t name_sz = strlen(dev_name(dev)) + 16; | ||
422 | char *name_mute, *name_micmute; | ||
423 | int ret; | ||
424 | |||
425 | if (sysfs_create_group(&hdev->dev.kobj, | ||
426 | &tpkbd_attr_group_pointer)) { | ||
427 | hid_warn(hdev, "Could not create sysfs group\n"); | ||
428 | } | ||
429 | |||
430 | data_pointer = kzalloc(sizeof(struct tpkbd_data_pointer), GFP_KERNEL); | ||
431 | if (data_pointer == NULL) { | ||
432 | hid_err(hdev, "Could not allocate memory for driver data\n"); | ||
433 | return -ENOMEM; | ||
434 | } | ||
435 | |||
436 | // set same default values as windows driver | ||
437 | data_pointer->sensitivity = 0xa0; | ||
438 | data_pointer->press_speed = 0x38; | ||
439 | |||
440 | name_mute = kzalloc(name_sz, GFP_KERNEL); | ||
441 | if (name_mute == NULL) { | ||
442 | hid_err(hdev, "Could not allocate memory for led data\n"); | ||
443 | ret = -ENOMEM; | ||
444 | goto err; | ||
445 | } | ||
446 | snprintf(name_mute, name_sz, "%s:amber:mute", dev_name(dev)); | ||
447 | |||
448 | name_micmute = kzalloc(name_sz, GFP_KERNEL); | ||
449 | if (name_micmute == NULL) { | ||
450 | hid_err(hdev, "Could not allocate memory for led data\n"); | ||
451 | ret = -ENOMEM; | ||
452 | goto err2; | ||
453 | } | ||
454 | snprintf(name_micmute, name_sz, "%s:amber:micmute", dev_name(dev)); | ||
455 | |||
456 | hid_set_drvdata(hdev, data_pointer); | ||
457 | |||
458 | data_pointer->led_mute.name = name_mute; | ||
459 | data_pointer->led_mute.brightness_get = tpkbd_led_brightness_get; | ||
460 | data_pointer->led_mute.brightness_set = tpkbd_led_brightness_set; | ||
461 | data_pointer->led_mute.dev = dev; | ||
462 | led_classdev_register(dev, &data_pointer->led_mute); | ||
463 | |||
464 | data_pointer->led_micmute.name = name_micmute; | ||
465 | data_pointer->led_micmute.brightness_get = tpkbd_led_brightness_get; | ||
466 | data_pointer->led_micmute.brightness_set = tpkbd_led_brightness_set; | ||
467 | data_pointer->led_micmute.dev = dev; | ||
468 | led_classdev_register(dev, &data_pointer->led_micmute); | ||
469 | |||
470 | tpkbd_features_set(hdev); | ||
471 | |||
472 | return 0; | ||
473 | |||
474 | err2: | ||
475 | kfree(name_mute); | ||
476 | err: | ||
477 | kfree(data_pointer); | ||
478 | return ret; | ||
479 | } | ||
480 | |||
481 | static int tpkbd_probe(struct hid_device *hdev, | ||
482 | const struct hid_device_id *id) | ||
483 | { | ||
484 | int ret; | ||
485 | struct usbhid_device *uhdev; | ||
486 | |||
487 | ret = hid_parse(hdev); | ||
488 | if (ret) { | ||
489 | hid_err(hdev, "hid_parse failed\n"); | ||
490 | goto err_free; | ||
491 | } | ||
492 | |||
493 | ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | ||
494 | if (ret) { | ||
495 | hid_err(hdev, "hid_hw_start failed\n"); | ||
496 | goto err_free; | ||
497 | } | ||
498 | |||
499 | uhdev = (struct usbhid_device *) hdev->driver_data; | ||
500 | |||
501 | if (uhdev->ifnum == 1) | ||
502 | return tpkbd_probe_tp(hdev); | ||
503 | |||
504 | return 0; | ||
505 | err_free: | ||
506 | return ret; | ||
507 | } | ||
508 | |||
509 | static void tpkbd_remove_tp(struct hid_device *hdev) | ||
510 | { | ||
511 | struct tpkbd_data_pointer *data_pointer; | ||
512 | |||
513 | sysfs_remove_group(&hdev->dev.kobj, | ||
514 | &tpkbd_attr_group_pointer); | ||
515 | |||
516 | data_pointer = (struct tpkbd_data_pointer *) hid_get_drvdata(hdev); | ||
517 | |||
518 | led_classdev_unregister(&data_pointer->led_micmute); | ||
519 | led_classdev_unregister(&data_pointer->led_mute); | ||
520 | |||
521 | hid_set_drvdata(hdev, NULL); | ||
522 | kfree(data_pointer); | ||
523 | } | ||
524 | |||
525 | static void tpkbd_remove(struct hid_device *hdev) | ||
526 | { | ||
527 | struct usbhid_device *uhdev; | ||
528 | |||
529 | uhdev = (struct usbhid_device *) hdev->driver_data; | ||
530 | if (uhdev->ifnum == 1) | ||
531 | tpkbd_remove_tp(hdev); | ||
532 | |||
533 | hid_hw_stop(hdev); | ||
534 | } | ||
535 | |||
536 | static const struct hid_device_id tpkbd_devices[] = { | ||
537 | { HID_USB_DEVICE(USB_VENDOR_ID_LENOVO, USB_DEVICE_ID_LENOVO_TPKBD) }, | ||
538 | { } | ||
539 | }; | ||
540 | |||
541 | MODULE_DEVICE_TABLE(hid, tpkbd_devices); | ||
542 | |||
543 | static struct hid_driver tpkbd_driver = { | ||
544 | .name = "lenovo_tpkbd", | ||
545 | .id_table = tpkbd_devices, | ||
546 | .input_mapping = tpkbd_input_mapping, | ||
547 | .probe = tpkbd_probe, | ||
548 | .remove = tpkbd_remove, | ||
549 | }; | ||
550 | |||
551 | static int __init tpkbd_init(void) | ||
552 | { | ||
553 | return hid_register_driver(&tpkbd_driver); | ||
554 | } | ||
555 | |||
556 | static void __exit tpkbd_exit(void) | ||
557 | { | ||
558 | hid_unregister_driver(&tpkbd_driver); | ||
559 | } | ||
560 | |||
561 | module_init(tpkbd_init); | ||
562 | module_exit(tpkbd_exit); | ||
563 | |||
564 | MODULE_LICENSE("GPL"); | ||