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 | ||
16 | #include <linux/hid.h> | |
17 | #include <linux/leds.h> | |
18 | #include <linux/module.h> | |
19 | ||
20 | #include "hid-ids.h" | |
21 | ||
22 | #define MAX_BRIGHTNESS 100 | |
23 | ||
24 | /* HID usage for keyboard backlight (Alphanumeric display brightness) */ | |
25 | #define HID_AD_BRIGHTNESS 0x00140046 | |
26 | ||
27 | struct hammer_kbd_leds { | |
28 | struct led_classdev cdev; | |
29 | struct hid_device *hdev; | |
30 | u8 buf[2] ____cacheline_aligned; | |
31 | }; | |
32 | ||
33 | static int hammer_kbd_brightness_set_blocking(struct led_classdev *cdev, | |
34 | enum led_brightness br) | |
35 | { | |
36 | struct hammer_kbd_leds *led = container_of(cdev, | |
37 | struct hammer_kbd_leds, | |
38 | cdev); | |
39 | int ret; | |
40 | ||
41 | led->buf[0] = 0; | |
42 | led->buf[1] = br; | |
43 | ||
7d3d8840 HK |
44 | /* |
45 | * Request USB HID device to be in Full On mode, so that sending | |
46 | * hardware output report and hardware raw request won't fail. | |
47 | */ | |
48 | ret = hid_hw_power(led->hdev, PM_HINT_FULLON); | |
49 | if (ret < 0) { | |
50 | hid_err(led->hdev, "failed: device not resumed %d\n", ret); | |
51 | return ret; | |
52 | } | |
53 | ||
bc774b8c WNH |
54 | ret = hid_hw_output_report(led->hdev, led->buf, sizeof(led->buf)); |
55 | if (ret == -ENOSYS) | |
56 | ret = hid_hw_raw_request(led->hdev, 0, led->buf, | |
57 | sizeof(led->buf), | |
58 | HID_OUTPUT_REPORT, | |
59 | HID_REQ_SET_REPORT); | |
60 | if (ret < 0) | |
61 | hid_err(led->hdev, "failed to set keyboard backlight: %d\n", | |
62 | ret); | |
7d3d8840 HK |
63 | |
64 | /* Request USB HID device back to Normal Mode. */ | |
65 | hid_hw_power(led->hdev, PM_HINT_NORMAL); | |
66 | ||
bc774b8c WNH |
67 | return ret; |
68 | } | |
69 | ||
70 | static int hammer_register_leds(struct hid_device *hdev) | |
71 | { | |
72 | struct hammer_kbd_leds *kbd_backlight; | |
73 | ||
74 | kbd_backlight = devm_kzalloc(&hdev->dev, | |
75 | sizeof(*kbd_backlight), | |
76 | GFP_KERNEL); | |
77 | if (!kbd_backlight) | |
78 | return -ENOMEM; | |
79 | ||
80 | kbd_backlight->hdev = hdev; | |
81 | kbd_backlight->cdev.name = "hammer::kbd_backlight"; | |
82 | kbd_backlight->cdev.max_brightness = MAX_BRIGHTNESS; | |
83 | kbd_backlight->cdev.brightness_set_blocking = | |
84 | hammer_kbd_brightness_set_blocking; | |
85 | kbd_backlight->cdev.flags = LED_HW_PLUGGABLE; | |
86 | ||
87 | /* Set backlight to 0% initially. */ | |
88 | hammer_kbd_brightness_set_blocking(&kbd_backlight->cdev, 0); | |
89 | ||
90 | return devm_led_classdev_register(&hdev->dev, &kbd_backlight->cdev); | |
91 | } | |
92 | ||
93 | static int hammer_input_configured(struct hid_device *hdev, | |
94 | struct hid_input *hi) | |
95 | { | |
96 | struct list_head *report_list = | |
97 | &hdev->report_enum[HID_OUTPUT_REPORT].report_list; | |
98 | struct hid_report *report; | |
99 | ||
100 | if (list_empty(report_list)) | |
101 | return 0; | |
102 | ||
103 | report = list_first_entry(report_list, struct hid_report, list); | |
104 | ||
105 | if (report->maxfield == 1 && | |
106 | report->field[0]->application == HID_GD_KEYBOARD && | |
107 | report->field[0]->maxusage == 1 && | |
108 | report->field[0]->usage[0].hid == HID_AD_BRIGHTNESS) { | |
109 | int err = hammer_register_leds(hdev); | |
110 | ||
111 | if (err) | |
112 | hid_warn(hdev, | |
113 | "Failed to register keyboard backlight: %d\n", | |
114 | err); | |
115 | } | |
116 | ||
117 | return 0; | |
118 | } | |
119 | ||
120 | static const struct hid_device_id hammer_devices[] = { | |
121 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | |
122 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_HAMMER) }, | |
123 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | |
124 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_STAFF) }, | |
125 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, | |
126 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WAND) }, | |
3e84c765 NB |
127 | { HID_DEVICE(BUS_USB, HID_GROUP_GENERIC, |
128 | USB_VENDOR_ID_GOOGLE, USB_DEVICE_ID_GOOGLE_WHISKERS) }, | |
bc774b8c WNH |
129 | { } |
130 | }; | |
131 | MODULE_DEVICE_TABLE(hid, hammer_devices); | |
132 | ||
133 | static struct hid_driver hammer_driver = { | |
134 | .name = "hammer", | |
135 | .id_table = hammer_devices, | |
136 | .input_configured = hammer_input_configured, | |
137 | }; | |
138 | module_hid_driver(hammer_driver); | |
139 | ||
140 | MODULE_LICENSE("GPL"); |