1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * HID driver for Google Hammer device. |
4 | * |
5 | * Copyright (c) 2017 Google Inc. |
6 | * Author: Wei-Ning Huang <wnhuang@google.com> |
7 | */ |
8 | |
9 | /* |
10 | * This program is free software; you can redistribute it and/or modify it |
11 | * under the terms of the GNU General Public License as published by the Free |
12 | * Software Foundation; either version 2 of the License, or (at your option) |
13 | * any later version. |
14 | */ |
15 | |
16 | #include <linux/acpi.h> |
17 | #include <linux/hid.h> |
18 | #include <linux/input/vivaldi-fmap.h> |
19 | #include <linux/leds.h> |
20 | #include <linux/module.h> |
21 | #include <linux/of.h> |
22 | #include <linux/platform_data/cros_ec_commands.h> |
23 | #include <linux/platform_data/cros_ec_proto.h> |
24 | #include <linux/platform_device.h> |
25 | #include <linux/pm_wakeup.h> |
26 | #include <asm/unaligned.h> |
27 | |
28 | #include "hid-ids.h" |
29 | #include "hid-vivaldi-common.h" |
30 | |
31 | /* |
32 | * C(hrome)B(ase)A(ttached)S(witch) - switch exported by Chrome EC and reporting |
33 | * state of the "Whiskers" base - attached or detached. Whiskers USB device also |
34 | * reports position of the keyboard - folded or not. Combining base state and |
35 | * position allows us to generate proper "Tablet mode" events. |
36 | */ |
37 | struct cbas_ec { |
38 | struct device *dev; /* The platform device (EC) */ |
39 | struct input_dev *input; |
40 | bool base_present; |
41 | bool base_folded; |
42 | struct notifier_block notifier; |
43 | }; |
44 | |
45 | static struct cbas_ec cbas_ec; |
46 | static DEFINE_SPINLOCK(cbas_ec_lock); |
47 | static DEFINE_MUTEX(cbas_ec_reglock); |
48 | |
49 | static bool cbas_parse_base_state(const void *data) |
50 | { |
51 | u32 switches = get_unaligned_le32(p: data); |
52 | |
53 | return !!(switches & BIT(EC_MKBP_BASE_ATTACHED)); |
54 | } |
55 | |
56 | static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, |
57 | bool *state) |
58 | { |
59 | struct ec_params_mkbp_info *params; |
60 | struct cros_ec_command *msg; |
61 | int ret; |
62 | |
63 | msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))), |
64 | GFP_KERNEL); |
65 | if (!msg) |
66 | return -ENOMEM; |
67 | |
68 | msg->command = EC_CMD_MKBP_INFO; |
69 | msg->version = 1; |
70 | msg->outsize = sizeof(*params); |
71 | msg->insize = sizeof(u32); |
72 | params = (struct ec_params_mkbp_info *)msg->data; |
73 | params->info_type = get_state ? |
74 | EC_MKBP_INFO_CURRENT : EC_MKBP_INFO_SUPPORTED; |
75 | params->event_type = EC_MKBP_EVENT_SWITCH; |
76 | |
77 | ret = cros_ec_cmd_xfer_status(ec_dev, msg); |
78 | if (ret >= 0) { |
79 | if (ret != sizeof(u32)) { |
80 | dev_warn(ec_dev->dev, "wrong result size: %d != %zu\n" , |
81 | ret, sizeof(u32)); |
82 | ret = -EPROTO; |
83 | } else { |
84 | *state = cbas_parse_base_state(data: msg->data); |
85 | ret = 0; |
86 | } |
87 | } |
88 | |
89 | kfree(objp: msg); |
90 | |
91 | return ret; |
92 | } |
93 | |
94 | static int cbas_ec_notify(struct notifier_block *nb, |
95 | unsigned long queued_during_suspend, |
96 | void *_notify) |
97 | { |
98 | struct cros_ec_device *ec = _notify; |
99 | unsigned long flags; |
100 | bool base_present; |
101 | |
102 | if (ec->event_data.event_type == EC_MKBP_EVENT_SWITCH) { |
103 | base_present = cbas_parse_base_state( |
104 | data: &ec->event_data.data.switches); |
105 | dev_dbg(cbas_ec.dev, |
106 | "%s: base: %d\n" , __func__, base_present); |
107 | |
108 | if (device_may_wakeup(dev: cbas_ec.dev) || |
109 | !queued_during_suspend) { |
110 | |
111 | pm_wakeup_event(dev: cbas_ec.dev, msec: 0); |
112 | |
113 | spin_lock_irqsave(&cbas_ec_lock, flags); |
114 | |
115 | /* |
116 | * While input layer dedupes the events, we do not want |
117 | * to disrupt the state reported by the base by |
118 | * overriding it with state reported by the LID. Only |
119 | * report changes, as we assume that on attach the base |
120 | * is not folded. |
121 | */ |
122 | if (base_present != cbas_ec.base_present) { |
123 | input_report_switch(dev: cbas_ec.input, |
124 | SW_TABLET_MODE, |
125 | value: !base_present); |
126 | input_sync(dev: cbas_ec.input); |
127 | cbas_ec.base_present = base_present; |
128 | } |
129 | |
130 | spin_unlock_irqrestore(lock: &cbas_ec_lock, flags); |
131 | } |
132 | } |
133 | |
134 | return NOTIFY_OK; |
135 | } |
136 | |
137 | static __maybe_unused int cbas_ec_resume(struct device *dev) |
138 | { |
139 | struct cros_ec_device *ec = dev_get_drvdata(dev: dev->parent); |
140 | bool base_present; |
141 | int error; |
142 | |
143 | error = cbas_ec_query_base(ec_dev: ec, get_state: true, state: &base_present); |
144 | if (error) { |
145 | dev_warn(dev, "failed to fetch base state on resume: %d\n" , |
146 | error); |
147 | } else { |
148 | spin_lock_irq(lock: &cbas_ec_lock); |
149 | |
150 | cbas_ec.base_present = base_present; |
151 | |
152 | /* |
153 | * Only report if base is disconnected. If base is connected, |
154 | * it will resend its state on resume, and we'll update it |
155 | * in hammer_event(). |
156 | */ |
157 | if (!cbas_ec.base_present) { |
158 | input_report_switch(dev: cbas_ec.input, SW_TABLET_MODE, value: 1); |
159 | input_sync(dev: cbas_ec.input); |
160 | } |
161 | |
162 | spin_unlock_irq(lock: &cbas_ec_lock); |
163 | } |
164 | |
165 | return 0; |
166 | } |
167 | |
168 | static SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume); |
169 | |
170 | static void cbas_ec_set_input(struct input_dev *input) |
171 | { |
172 | /* Take the lock so hammer_event() does not race with us here */ |
173 | spin_lock_irq(lock: &cbas_ec_lock); |
174 | cbas_ec.input = input; |
175 | spin_unlock_irq(lock: &cbas_ec_lock); |
176 | } |
177 | |
178 | static int __cbas_ec_probe(struct platform_device *pdev) |
179 | { |
180 | struct cros_ec_device *ec = dev_get_drvdata(dev: pdev->dev.parent); |
181 | struct input_dev *input; |
182 | bool base_supported; |
183 | int error; |
184 | |
185 | error = cbas_ec_query_base(ec_dev: ec, get_state: false, state: &base_supported); |
186 | if (error) |
187 | return error; |
188 | |
189 | if (!base_supported) |
190 | return -ENXIO; |
191 | |
192 | input = devm_input_allocate_device(&pdev->dev); |
193 | if (!input) |
194 | return -ENOMEM; |
195 | |
196 | input->name = "Whiskers Tablet Mode Switch" ; |
197 | input->id.bustype = BUS_HOST; |
198 | |
199 | input_set_capability(dev: input, EV_SW, SW_TABLET_MODE); |
200 | |
201 | error = input_register_device(input); |
202 | if (error) { |
203 | dev_err(&pdev->dev, "cannot register input device: %d\n" , |
204 | error); |
205 | return error; |
206 | } |
207 | |
208 | /* Seed the state */ |
209 | error = cbas_ec_query_base(ec_dev: ec, get_state: true, state: &cbas_ec.base_present); |
210 | if (error) { |
211 | dev_err(&pdev->dev, "cannot query base state: %d\n" , error); |
212 | return error; |
213 | } |
214 | |
215 | if (!cbas_ec.base_present) |
216 | cbas_ec.base_folded = false; |
217 | |
218 | dev_dbg(&pdev->dev, "%s: base: %d, folded: %d\n" , __func__, |
219 | cbas_ec.base_present, cbas_ec.base_folded); |
220 | |
221 | input_report_switch(dev: input, SW_TABLET_MODE, |
222 | value: !cbas_ec.base_present || cbas_ec.base_folded); |
223 | |
224 | cbas_ec_set_input(input); |
225 | |
226 | cbas_ec.dev = &pdev->dev; |
227 | cbas_ec.notifier.notifier_call = cbas_ec_notify; |
228 | error = blocking_notifier_chain_register(nh: &ec->event_notifier, |
229 | nb: &cbas_ec.notifier); |
230 | if (error) { |
231 | dev_err(&pdev->dev, "cannot register notifier: %d\n" , error); |
232 | cbas_ec_set_input(NULL); |
233 | return error; |
234 | } |
235 | |
236 | device_init_wakeup(dev: &pdev->dev, enable: true); |
237 | return 0; |
238 | } |
239 | |
240 | static int cbas_ec_probe(struct platform_device *pdev) |
241 | { |
242 | int retval; |
243 | |
244 | mutex_lock(&cbas_ec_reglock); |
245 | |
246 | if (cbas_ec.input) { |
247 | retval = -EBUSY; |
248 | goto out; |
249 | } |
250 | |
251 | retval = __cbas_ec_probe(pdev); |
252 | |
253 | out: |
254 | mutex_unlock(lock: &cbas_ec_reglock); |
255 | return retval; |
256 | } |
257 | |
258 | static int cbas_ec_remove(struct platform_device *pdev) |
259 | { |
260 | struct cros_ec_device *ec = dev_get_drvdata(dev: pdev->dev.parent); |
261 | |
262 | mutex_lock(&cbas_ec_reglock); |
263 | |
264 | blocking_notifier_chain_unregister(nh: &ec->event_notifier, |
265 | nb: &cbas_ec.notifier); |
266 | cbas_ec_set_input(NULL); |
267 | |
268 | mutex_unlock(lock: &cbas_ec_reglock); |
269 | return 0; |
270 | } |
271 | |
272 | static const struct acpi_device_id cbas_ec_acpi_ids[] = { |
273 | { "GOOG000B" , 0 }, |
274 | { } |
275 | }; |
276 | MODULE_DEVICE_TABLE(acpi, cbas_ec_acpi_ids); |
277 | |
278 | #ifdef CONFIG_OF |
279 | static const struct of_device_id cbas_ec_of_match[] = { |
280 | { .compatible = "google,cros-cbas" }, |
281 | { }, |
282 | }; |
283 | MODULE_DEVICE_TABLE(of, cbas_ec_of_match); |
284 | #endif |
285 | |
286 | static struct platform_driver cbas_ec_driver = { |
287 | .probe = cbas_ec_probe, |
288 | .remove = cbas_ec_remove, |
289 | .driver = { |
290 | .name = "cbas_ec" , |
291 | .acpi_match_table = ACPI_PTR(cbas_ec_acpi_ids), |
292 | .of_match_table = of_match_ptr(cbas_ec_of_match), |
293 | .pm = &cbas_ec_pm_ops, |
294 | }, |
295 | }; |
296 | |
297 | #define MAX_BRIGHTNESS 100 |
298 | |
299 | struct hammer_kbd_leds { |
300 | struct led_classdev cdev; |
301 | struct hid_device *hdev; |
302 | u8 buf[2] ____cacheline_aligned; |
303 | }; |
304 | |
305 | static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev, |
306 | enum led_brightness br) |
307 | { |
308 | struct hammer_kbd_leds *led = container_of(cdev, |
309 | struct hammer_kbd_leds, |
310 | cdev); |
311 | int ret; |
312 | |
313 | led->buf[0] = 0; |
314 | led->buf[1] = br; |
315 | |
316 | /* |
317 | * Request USB HID device to be in Full On mode, so that sending |
318 | * hardware output report and hardware raw request won't fail. |
319 | */ |
320 | ret = hid_hw_power(hdev: led->hdev, PM_HINT_FULLON); |
321 | if (ret < 0) { |
322 | hid_err(led->hdev, "failed: device not resumed %d\n" , ret); |
323 | return ret; |
324 | } |
325 | |
326 | ret = hid_hw_output_report(hdev: led->hdev, buf: led->buf, len: sizeof(led->buf)); |
327 | if (ret == -ENOSYS) |
328 | ret = hid_hw_raw_request(hdev: led->hdev, reportnum: 0, buf: led->buf, |
329 | len: sizeof(led->buf), |
330 | rtype: HID_OUTPUT_REPORT, |
331 | reqtype: HID_REQ_SET_REPORT); |
332 | if (ret < 0) |
333 | hid_err(led->hdev, "failed to set keyboard backlight: %d\n" , |
334 | ret); |
335 | |
336 | /* Request USB HID device back to Normal Mode. */ |
337 | hid_hw_power(hdev: led->hdev, PM_HINT_NORMAL); |
338 | |
339 | return ret; |
340 | } |
341 | |
342 | static int hammer_register_leds(struct hid_device *hdev) |
343 | { |
344 | struct hammer_kbd_leds *kbd_backlight; |
345 | |
346 | kbd_backlight = devm_kzalloc(dev: &hdev->dev, size: sizeof(*kbd_backlight), |
347 | GFP_KERNEL); |
348 | if (!kbd_backlight) |
349 | return -ENOMEM; |
350 | |
351 | kbd_backlight->hdev = hdev; |
352 | kbd_backlight->cdev.name = "hammer::kbd_backlight" ; |
353 | kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS; |
354 | kbd_backlight->cdev.brightness_set_blocking = |
355 | hammer_kbd_brightness_set_blocking; |
356 | kbd_backlight->cdev.flags = LED_HW_PLUGGABLE; |
357 | |
358 | /* Set backlight to 0% initially. */ |
359 | hammer_kbd_brightness_set_blocking(cdev: &kbd_backlight->cdev, br: 0); |
360 | |
361 | return devm_led_classdev_register(parent: &hdev->dev, led_cdev: &kbd_backlight->cdev); |
362 | } |
363 | |
364 | #define HID_UP_GOOGLEVENDOR 0xffd10000 |
365 | #define HID_VD_KBD_FOLDED 0x00000019 |
366 | #define HID_USAGE_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED) |
367 | |
368 | /* HID usage for keyboard backlight (Alphanumeric display brightness) */ |
369 | #define HID_AD_BRIGHTNESS 0x00140046 |
370 | |
371 | static int hammer_input_mapping(struct hid_device *hdev, struct hid_input *hi, |
372 | struct hid_field *field, |
373 | struct hid_usage *usage, |
374 | unsigned long **bit, int *max) |
375 | { |
376 | if (usage->hid == HID_USAGE_KBD_FOLDED) { |
377 | /* |
378 | * We do not want to have this usage mapped as it will get |
379 | * mixed in with "base attached" signal and delivered over |
380 | * separate input device for tablet switch mode. |
381 | */ |
382 | return -1; |
383 | } |
384 | |
385 | return 0; |
386 | } |
387 | |
388 | static void hammer_folded_event(struct hid_device *hdev, bool folded) |
389 | { |
390 | unsigned long flags; |
391 | |
392 | spin_lock_irqsave(&cbas_ec_lock, flags); |
393 | |
394 | /* |
395 | * If we are getting events from Whiskers that means that it |
396 | * is attached to the lid. |
397 | */ |
398 | cbas_ec.base_present = true; |
399 | cbas_ec.base_folded = folded; |
400 | hid_dbg(hdev, "%s: base: %d, folded: %d\n" , __func__, |
401 | cbas_ec.base_present, cbas_ec.base_folded); |
402 | |
403 | if (cbas_ec.input) { |
404 | input_report_switch(dev: cbas_ec.input, SW_TABLET_MODE, value: folded); |
405 | input_sync(dev: cbas_ec.input); |
406 | } |
407 | |
408 | spin_unlock_irqrestore(lock: &cbas_ec_lock, flags); |
409 | } |
410 | |
411 | static int hammer_event(struct hid_device *hid, struct hid_field *field, |
412 | struct hid_usage *usage, __s32 value) |
413 | { |
414 | if (usage->hid == HID_USAGE_KBD_FOLDED) { |
415 | hammer_folded_event(hdev: hid, folded: value); |
416 | return 1; /* We handled this event */ |
417 | } |
418 | |
419 | return 0; |
420 | } |
421 | |
422 | static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type, |
423 | unsigned application, unsigned usage) |
424 | { |
425 | struct hid_report_enum *re = &hdev->report_enum[report_type]; |
426 | struct hid_report *report; |
427 | int i, j; |
428 | |
429 | list_for_each_entry(report, &re->report_list, list) { |
430 | if (report->application != application) |
431 | continue; |
432 | |
433 | for (i = 0; i < report->maxfield; i++) { |
434 | struct hid_field *field = report->field[i]; |
435 | |
436 | for (j = 0; j < field->maxusage; j++) |
437 | if (field->usage[j].hid == usage) |
438 | return true; |
439 | } |
440 | } |
441 | |
442 | return false; |
443 | } |
444 | |
445 | static bool hammer_has_folded_event(struct hid_device *hdev) |
446 | { |
447 | return hammer_has_usage(hdev, report_type: HID_INPUT_REPORT, |
448 | HID_GD_KEYBOARD, HID_USAGE_KBD_FOLDED); |
449 | } |
450 | |
451 | static bool hammer_has_backlight_control(struct hid_device *hdev) |
452 | { |
453 | return hammer_has_usage(hdev, report_type: HID_OUTPUT_REPORT, |
454 | HID_GD_KEYBOARD, HID_AD_BRIGHTNESS); |
455 | } |
456 | |
457 | static void hammer_get_folded_state(struct hid_device *hdev) |
458 | { |
459 | struct hid_report *report; |
460 | char *buf; |
461 | int len, rlen; |
462 | int a; |
463 | |
464 | report = hdev->report_enum[HID_INPUT_REPORT].report_id_hash[0x0]; |
465 | |
466 | if (!report || report->maxfield < 1) |
467 | return; |
468 | |
469 | len = hid_report_len(report) + 1; |
470 | |
471 | buf = kmalloc(size: len, GFP_KERNEL); |
472 | if (!buf) |
473 | return; |
474 | |
475 | rlen = hid_hw_raw_request(hdev, reportnum: report->id, buf, len, rtype: report->type, reqtype: HID_REQ_GET_REPORT); |
476 | |
477 | if (rlen != len) { |
478 | hid_warn(hdev, "Unable to read base folded state: %d (expected %d)\n" , rlen, len); |
479 | goto out; |
480 | } |
481 | |
482 | for (a = 0; a < report->maxfield; a++) { |
483 | struct hid_field *field = report->field[a]; |
484 | |
485 | if (field->usage->hid == HID_USAGE_KBD_FOLDED) { |
486 | u32 value = hid_field_extract(hid: hdev, report: buf+1, |
487 | offset: field->report_offset, n: field->report_size); |
488 | |
489 | hammer_folded_event(hdev, folded: value); |
490 | break; |
491 | } |
492 | } |
493 | |
494 | out: |
495 | kfree(objp: buf); |
496 | } |
497 | |
498 | static void hammer_stop(void *hdev) |
499 | { |
500 | hid_hw_stop(hdev); |
501 | } |
502 | |
503 | static int hammer_probe(struct hid_device *hdev, |
504 | const struct hid_device_id *id) |
505 | { |
506 | struct vivaldi_data *vdata; |
507 | int error; |
508 | |
509 | vdata = devm_kzalloc(dev: &hdev->dev, size: sizeof(*vdata), GFP_KERNEL); |
510 | if (!vdata) |
511 | return -ENOMEM; |
512 | |
513 | hid_set_drvdata(hdev, data: vdata); |
514 | |
515 | error = hid_parse(hdev); |
516 | if (error) |
517 | return error; |
518 | |
519 | error = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
520 | if (error) |
521 | return error; |
522 | |
523 | error = devm_add_action(&hdev->dev, hammer_stop, hdev); |
524 | if (error) |
525 | return error; |
526 | |
527 | /* |
528 | * We always want to poll for, and handle tablet mode events from |
529 | * devices that have folded usage, even when nobody has opened the input |
530 | * device. This also prevents the hid core from dropping early tablet |
531 | * mode events from the device. |
532 | */ |
533 | if (hammer_has_folded_event(hdev)) { |
534 | hdev->quirks |= HID_QUIRK_ALWAYS_POLL; |
535 | error = hid_hw_open(hdev); |
536 | if (error) |
537 | return error; |
538 | |
539 | hammer_get_folded_state(hdev); |
540 | } |
541 | |
542 | if (hammer_has_backlight_control(hdev)) { |
543 | error = hammer_register_leds(hdev); |
544 | if (error) |
545 | hid_warn(hdev, |
546 | "Failed to register keyboard backlight: %d\n" , |
547 | error); |
548 | } |
549 | |
550 | return 0; |
551 | } |
552 | |
553 | static void hammer_remove(struct hid_device *hdev) |
554 | { |
555 | unsigned long flags; |
556 | |
557 | if (hammer_has_folded_event(hdev)) { |
558 | hid_hw_close(hdev); |
559 | |
560 | /* |
561 | * If we are disconnecting then most likely Whiskers is |
562 | * being removed. Even if it is not removed, without proper |
563 | * keyboard we should not stay in clamshell mode. |
564 | * |
565 | * The reason for doing it here and not waiting for signal |
566 | * from EC, is that on some devices there are high leakage |
567 | * on Whiskers pins and we do not detect disconnect reliably, |
568 | * resulting in devices being stuck in clamshell mode. |
569 | */ |
570 | spin_lock_irqsave(&cbas_ec_lock, flags); |
571 | if (cbas_ec.input && cbas_ec.base_present) { |
572 | input_report_switch(dev: cbas_ec.input, SW_TABLET_MODE, value: 1); |
573 | input_sync(dev: cbas_ec.input); |
574 | } |
575 | cbas_ec.base_present = false; |
576 | spin_unlock_irqrestore(lock: &cbas_ec_lock, flags); |
577 | } |
578 | |
579 | /* Unregistering LEDs and stopping the hardware is done via devm */ |
580 | } |
581 | |
582 | static const struct hid_device_id hammer_devices[] = { |
583 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
584 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_DON) }, |
585 | { HID_DEVICE(BUS_USB, HID_GROUP_VIVALDI, |
586 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_EEL) }, |
587 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
588 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, |
589 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
590 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_JEWEL) }, |
591 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
592 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MAGNEMITE) }, |
593 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
594 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MASTERBALL) }, |
595 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
596 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MOONBALL) }, |
597 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
598 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) }, |
599 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
600 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) }, |
601 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
602 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) }, |
603 | { } |
604 | }; |
605 | MODULE_DEVICE_TABLE(hid, hammer_devices); |
606 | |
607 | static struct hid_driver hammer_driver = { |
608 | .name = "hammer" , |
609 | .id_table = hammer_devices, |
610 | .probe = hammer_probe, |
611 | .remove = hammer_remove, |
612 | .feature_mapping = vivaldi_feature_mapping, |
613 | .input_mapping = hammer_input_mapping, |
614 | .event = hammer_event, |
615 | .driver = { |
616 | .dev_groups = vivaldi_attribute_groups, |
617 | }, |
618 | }; |
619 | |
620 | static int __init hammer_init(void) |
621 | { |
622 | int error; |
623 | |
624 | error = platform_driver_register(&cbas_ec_driver); |
625 | if (error) |
626 | return error; |
627 | |
628 | error = hid_register_driver(&hammer_driver); |
629 | if (error) { |
630 | platform_driver_unregister(&cbas_ec_driver); |
631 | return error; |
632 | } |
633 | |
634 | return 0; |
635 | } |
636 | module_init(hammer_init); |
637 | |
638 | static void __exit hammer_exit(void) |
639 | { |
640 | hid_unregister_driver(&hammer_driver); |
641 | platform_driver_unregister(&cbas_ec_driver); |
642 | } |
643 | module_exit(hammer_exit); |
644 | |
645 | MODULE_LICENSE("GPL" ); |
646 | |