Commit | Line | Data |
---|---|---|
bc774b8c WNH |
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 | ||
eb1aac4c | 16 | #include <linux/acpi.h> |
bc774b8c | 17 | #include <linux/hid.h> |
a9d67299 | 18 | #include <linux/input/vivaldi-fmap.h> |
bc774b8c WNH |
19 | #include <linux/leds.h> |
20 | #include <linux/module.h> | |
8dcaa046 | 21 | #include <linux/of.h> |
840d9f13 EBS |
22 | #include <linux/platform_data/cros_ec_commands.h> |
23 | #include <linux/platform_data/cros_ec_proto.h> | |
eb1aac4c DT |
24 | #include <linux/platform_device.h> |
25 | #include <linux/pm_wakeup.h> | |
26 | #include <asm/unaligned.h> | |
bc774b8c WNH |
27 | |
28 | #include "hid-ids.h" | |
a9d67299 | 29 | #include "hid-vivaldi-common.h" |
bc774b8c | 30 | |
eb1aac4c DT |
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; | |
38e57f06 | 41 | bool base_folded; |
eb1aac4c DT |
42 | struct notifier_block notifier; |
43 | }; | |
bc774b8c | 44 | |
eb1aac4c DT |
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(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 | ||
01f1269f | 63 | msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))), |
eb1aac4c DT |
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(msg->data); | |
85 | ret = 0; | |
86 | } | |
87 | } | |
88 | ||
89 | kfree(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 | &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(cbas_ec.dev) || | |
109 | !queued_during_suspend) { | |
110 | ||
111 | pm_wakeup_event(cbas_ec.dev, 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(cbas_ec.input, | |
124 | SW_TABLET_MODE, | |
125 | !base_present); | |
126 | input_sync(cbas_ec.input); | |
127 | cbas_ec.base_present = base_present; | |
128 | } | |
129 | ||
130 | spin_unlock_irqrestore(&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->parent); | |
140 | bool base_present; | |
141 | int error; | |
142 | ||
143 | error = cbas_ec_query_base(ec, true, &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(&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(cbas_ec.input, SW_TABLET_MODE, 1); | |
159 | input_sync(cbas_ec.input); | |
160 | } | |
161 | ||
162 | spin_unlock_irq(&cbas_ec_lock); | |
163 | } | |
164 | ||
165 | return 0; | |
166 | } | |
167 | ||
8f35260e | 168 | static SIMPLE_DEV_PM_OPS(cbas_ec_pm_ops, NULL, cbas_ec_resume); |
eb1aac4c DT |
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(&cbas_ec_lock); | |
174 | cbas_ec.input = input; | |
175 | spin_unlock_irq(&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(pdev->dev.parent); | |
181 | struct input_dev *input; | |
182 | bool base_supported; | |
183 | int error; | |
184 | ||
185 | error = cbas_ec_query_base(ec, false, &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(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, true, &cbas_ec.base_present); | |
210 | if (error) { | |
211 | dev_err(&pdev->dev, "cannot query base state: %d\n", error); | |
212 | return error; | |
213 | } | |
214 | ||
38e57f06 DT |
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(input, SW_TABLET_MODE, | |
222 | !cbas_ec.base_present || cbas_ec.base_folded); | |
eb1aac4c DT |
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(&ec->event_notifier, | |
229 | &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(&pdev->dev, 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(&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(pdev->dev.parent); | |
261 | ||
262 | mutex_lock(&cbas_ec_reglock); | |
263 | ||
264 | blocking_notifier_chain_unregister(&ec->event_notifier, | |
265 | &cbas_ec.notifier); | |
266 | cbas_ec_set_input(NULL); | |
267 | ||
268 | mutex_unlock(&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 | ||
8dcaa046 IJ |
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 | ||
eb1aac4c DT |
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), | |
8dcaa046 | 292 | .of_match_table = of_match_ptr(cbas_ec_of_match), |
eb1aac4c DT |
293 | .pm = &cbas_ec_pm_ops, |
294 | }, | |
295 | }; | |
296 | ||
297 | #define MAX_BRIGHTNESS 100 | |
bc774b8c WNH |
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 | ||
7d3d8840 HK |
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(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 | ||
bc774b8c WNH |
326 | ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf)); |
327 | if (ret == -ENOSYS) | |
328 | ret = hid_hw_raw_request(led->hdev, 0, led->buf, | |
329 | sizeof(led->buf), | |
330 | HID_OUTPUT_REPORT, | |
331 | HID_REQ_SET_REPORT); | |
332 | if (ret < 0) | |
333 | hid_err(led->hdev, "failed to set keyboard backlight: %d\n", | |
334 | ret); | |
7d3d8840 HK |
335 | |
336 | /* Request USB HID device back to Normal Mode. */ | |
337 | hid_hw_power(led->hdev, PM_HINT_NORMAL); | |
338 | ||
bc774b8c WNH |
339 | return ret; |
340 | } | |
341 | ||
342 | static int hammer_register_leds(struct hid_device *hdev) | |
343 | { | |
344 | struct hammer_kbd_leds *kbd_backlight; | |
345 | ||
d950db3f DT |
346 | kbd_backlight = devm_kzalloc(&hdev->dev, sizeof(*kbd_backlight), |
347 | GFP_KERNEL); | |
bc774b8c WNH |
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(&kbd_backlight->cdev, 0); | |
360 | ||
d950db3f | 361 | return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev); |
bc774b8c WNH |
362 | } |
363 | ||
eb1aac4c DT |
364 | #define HID_UP_GOOGLEVENDOR 0xffd10000 |
365 | #define HID_VD_KBD_FOLDED 0x00000019 | |
20c55f25 | 366 | #define HID_USAGE_KBD_FOLDED (HID_UP_GOOGLEVENDOR | HID_VD_KBD_FOLDED) |
eb1aac4c DT |
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) | |
bc774b8c | 375 | { |
20c55f25 | 376 | if (usage->hid == HID_USAGE_KBD_FOLDED) { |
eb1aac4c DT |
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 | ||
df7b6229 | 388 | static void hammer_folded_event(struct hid_device *hdev, bool folded) |
eb1aac4c DT |
389 | { |
390 | unsigned long flags; | |
391 | ||
df7b6229 | 392 | spin_lock_irqsave(&cbas_ec_lock, flags); |
eb1aac4c | 393 | |
df7b6229 NB |
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); | |
eb1aac4c | 402 | |
df7b6229 NB |
403 | if (cbas_ec.input) { |
404 | input_report_switch(cbas_ec.input, SW_TABLET_MODE, folded); | |
405 | input_sync(cbas_ec.input); | |
406 | } | |
407 | ||
408 | spin_unlock_irqrestore(&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(hid, value); | |
eb1aac4c DT |
416 | return 1; /* We handled this event */ |
417 | } | |
418 | ||
419 | return 0; | |
420 | } | |
421 | ||
20c55f25 NB |
422 | static bool hammer_has_usage(struct hid_device *hdev, unsigned int report_type, |
423 | unsigned application, unsigned usage) | |
eb1aac4c | 424 | { |
20c55f25 | 425 | struct hid_report_enum *re = &hdev->report_enum[report_type]; |
eb1aac4c DT |
426 | struct hid_report *report; |
427 | int i, j; | |
bc774b8c | 428 | |
eb1aac4c | 429 | list_for_each_entry(report, &re->report_list, list) { |
20c55f25 | 430 | if (report->application != application) |
eb1aac4c | 431 | continue; |
bc774b8c | 432 | |
eb1aac4c DT |
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++) | |
20c55f25 | 437 | if (field->usage[j].hid == usage) |
eb1aac4c DT |
438 | return true; |
439 | } | |
440 | } | |
441 | ||
442 | return false; | |
443 | } | |
444 | ||
20c55f25 NB |
445 | static bool hammer_has_folded_event(struct hid_device *hdev) |
446 | { | |
447 | return hammer_has_usage(hdev, 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, HID_OUTPUT_REPORT, | |
454 | HID_GD_KEYBOARD, HID_AD_BRIGHTNESS); | |
455 | } | |
456 | ||
df7b6229 NB |
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(len, GFP_KERNEL); | |
472 | if (!buf) | |
473 | return; | |
474 | ||
475 | rlen = hid_hw_raw_request(hdev, report->id, buf, len, report->type, 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(hdev, buf+1, | |
487 | field->report_offset, field->report_size); | |
488 | ||
489 | hammer_folded_event(hdev, value); | |
490 | break; | |
491 | } | |
492 | } | |
493 | ||
494 | out: | |
495 | kfree(buf); | |
496 | } | |
497 | ||
d950db3f DT |
498 | static void hammer_stop(void *hdev) |
499 | { | |
500 | hid_hw_stop(hdev); | |
501 | } | |
502 | ||
eb1aac4c DT |
503 | static int hammer_probe(struct hid_device *hdev, |
504 | const struct hid_device_id *id) | |
505 | { | |
a9d67299 | 506 | struct vivaldi_data *vdata; |
eb1aac4c DT |
507 | int error; |
508 | ||
a9d67299 SB |
509 | vdata = devm_kzalloc(&hdev->dev, sizeof(*vdata), GFP_KERNEL); |
510 | if (!vdata) | |
511 | return -ENOMEM; | |
512 | ||
513 | hid_set_drvdata(hdev, vdata); | |
514 | ||
eb1aac4c DT |
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 | ||
d950db3f DT |
523 | error = devm_add_action(&hdev->dev, hammer_stop, hdev); |
524 | if (error) | |
525 | return error; | |
526 | ||
eb1aac4c DT |
527 | /* |
528 | * We always want to poll for, and handle tablet mode events from | |
20c55f25 NB |
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. | |
eb1aac4c | 532 | */ |
20c55f25 | 533 | if (hammer_has_folded_event(hdev)) { |
eb1aac4c | 534 | hdev->quirks |= HID_QUIRK_ALWAYS_POLL; |
38e57f06 DT |
535 | error = hid_hw_open(hdev); |
536 | if (error) | |
537 | return error; | |
df7b6229 NB |
538 | |
539 | hammer_get_folded_state(hdev); | |
38e57f06 | 540 | } |
eb1aac4c DT |
541 | |
542 | if (hammer_has_backlight_control(hdev)) { | |
543 | error = hammer_register_leds(hdev); | |
544 | if (error) | |
bc774b8c WNH |
545 | hid_warn(hdev, |
546 | "Failed to register keyboard backlight: %d\n", | |
eb1aac4c | 547 | error); |
bc774b8c WNH |
548 | } |
549 | ||
550 | return 0; | |
551 | } | |
552 | ||
38e57f06 DT |
553 | static void hammer_remove(struct hid_device *hdev) |
554 | { | |
79085c7d DT |
555 | unsigned long flags; |
556 | ||
20c55f25 | 557 | if (hammer_has_folded_event(hdev)) { |
38e57f06 DT |
558 | hid_hw_close(hdev); |
559 | ||
79085c7d DT |
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(cbas_ec.input, SW_TABLET_MODE, 1); | |
573 | input_sync(cbas_ec.input); | |
574 | } | |
575 | cbas_ec.base_present = false; | |
576 | spin_unlock_irqrestore(&cbas_ec_lock, flags); | |
577 | } | |
578 | ||
d950db3f | 579 | /* Unregistering LEDs and stopping the hardware is done via devm */ |
38e57f06 | 580 | } |
eb1aac4c | 581 | |
bc774b8c | 582 | static const struct hid_device_id hammer_devices[] = { |
36b87cf3 SCH |
583 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
584 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_DON) }, | |
8a3e634d | 585 | { HID_DEVICE(BUS_USB, HID_GROUP_VIVALDI, |
caff0090 | 586 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_EEL) }, |
bc774b8c WNH |
587 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
588 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, | |
9e4dbc46 NB |
589 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
590 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MAGNEMITE) }, | |
591 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | |
592 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MASTERBALL) }, | |
58322a15 CTH |
593 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
594 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_MOONBALL) }, | |
bc774b8c WNH |
595 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
596 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) }, | |
597 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | |
598 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) }, | |
3e84c765 NB |
599 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
600 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) }, | |
bc774b8c WNH |
601 | { } |
602 | }; | |
603 | MODULE_DEVICE_TABLE(hid, hammer_devices); | |
604 | ||
605 | static struct hid_driver hammer_driver = { | |
606 | .name = "hammer", | |
607 | .id_table = hammer_devices, | |
eb1aac4c | 608 | .probe = hammer_probe, |
38e57f06 | 609 | .remove = hammer_remove, |
a9d67299 | 610 | .feature_mapping = vivaldi_feature_mapping, |
eb1aac4c DT |
611 | .input_mapping = hammer_input_mapping, |
612 | .event = hammer_event, | |
9f4441fc GKH |
613 | .driver = { |
614 | .dev_groups = vivaldi_attribute_groups, | |
615 | }, | |
bc774b8c | 616 | }; |
eb1aac4c DT |
617 | |
618 | static int __init hammer_init(void) | |
619 | { | |
620 | int error; | |
621 | ||
622 | error = platform_driver_register(&cbas_ec_driver); | |
623 | if (error) | |
624 | return error; | |
625 | ||
626 | error = hid_register_driver(&hammer_driver); | |
627 | if (error) { | |
628 | platform_driver_unregister(&cbas_ec_driver); | |
629 | return error; | |
630 | } | |
631 | ||
632 | return 0; | |
633 | } | |
634 | module_init(hammer_init); | |
635 | ||
636 | static void __exit hammer_exit(void) | |
637 | { | |
638 | hid_unregister_driver(&hammer_driver); | |
639 | platform_driver_unregister(&cbas_ec_driver); | |
640 | } | |
641 | module_exit(hammer_exit); | |
bc774b8c WNH |
642 | |
643 | MODULE_LICENSE("GPL"); |