Commit | Line | Data |
---|---|---|
9c92ab61 | 1 | // SPDX-License-Identifier: GPL-2.0-only |
ad8ddc57 BC |
2 | /* |
3 | * HID driver for CMedia CM6533 audio jack controls | |
bebf8820 | 4 | * and HS100B mute buttons |
ad8ddc57 BC |
5 | * |
6 | * Copyright (C) 2015 Ben Chen <ben_chen@bizlinktech.com> | |
bebf8820 | 7 | * Copyright (C) 2021 Thomas Weißschuh <linux@weissschuh.net> |
ad8ddc57 BC |
8 | */ |
9 | ||
10 | #include <linux/device.h> | |
11 | #include <linux/hid.h> | |
12 | #include <linux/module.h> | |
13 | #include "hid-ids.h" | |
14 | ||
15 | MODULE_AUTHOR("Ben Chen"); | |
bebf8820 TW |
16 | MODULE_AUTHOR("Thomas Weißschuh"); |
17 | MODULE_DESCRIPTION("CM6533 HID jack controls and HS100B mute button"); | |
ad8ddc57 BC |
18 | MODULE_LICENSE("GPL"); |
19 | ||
20 | #define CM6533_JD_TYPE_COUNT 1 | |
21 | #define CM6533_JD_RAWEV_LEN 16 | |
22 | #define CM6533_JD_SFX_OFFSET 8 | |
23 | ||
bebf8820 TW |
24 | #define HS100B_RDESC_ORIG_SIZE 60 |
25 | ||
26 | /* Fixed report descriptor of HS-100B audio chip | |
27 | * Bit 4 is an abolute Microphone mute usage instead of being unassigned. | |
28 | */ | |
29 | static __u8 hs100b_rdesc_fixed[] = { | |
30 | 0x05, 0x0C, /* Usage Page (Consumer), */ | |
31 | 0x09, 0x01, /* Usage (Consumer Control), */ | |
32 | 0xA1, 0x01, /* Collection (Application), */ | |
33 | 0x15, 0x00, /* Logical Minimum (0), */ | |
34 | 0x25, 0x01, /* Logical Maximum (1), */ | |
35 | 0x09, 0xE9, /* Usage (Volume Inc), */ | |
36 | 0x09, 0xEA, /* Usage (Volume Dec), */ | |
37 | 0x75, 0x01, /* Report Size (1), */ | |
38 | 0x95, 0x02, /* Report Count (2), */ | |
39 | 0x81, 0x02, /* Input (Variable), */ | |
40 | 0x09, 0xE2, /* Usage (Mute), */ | |
41 | 0x95, 0x01, /* Report Count (1), */ | |
42 | 0x81, 0x06, /* Input (Variable, Relative), */ | |
43 | 0x05, 0x0B, /* Usage Page (Telephony), */ | |
44 | 0x09, 0x2F, /* Usage (2Fh), */ | |
45 | 0x81, 0x02, /* Input (Variable), */ | |
46 | 0x09, 0x20, /* Usage (20h), */ | |
47 | 0x81, 0x06, /* Input (Variable, Relative), */ | |
48 | 0x05, 0x0C, /* Usage Page (Consumer), */ | |
49 | 0x09, 0x00, /* Usage (00h), */ | |
50 | 0x95, 0x03, /* Report Count (3), */ | |
51 | 0x81, 0x02, /* Input (Variable), */ | |
52 | 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ | |
53 | 0x09, 0x00, /* Usage (00h), */ | |
54 | 0x75, 0x08, /* Report Size (8), */ | |
55 | 0x95, 0x03, /* Report Count (3), */ | |
56 | 0x81, 0x02, /* Input (Variable), */ | |
57 | 0x09, 0x00, /* Usage (00h), */ | |
58 | 0x95, 0x04, /* Report Count (4), */ | |
59 | 0x91, 0x02, /* Output (Variable), */ | |
60 | 0xC0 /* End Collection */ | |
61 | }; | |
62 | ||
ad8ddc57 BC |
63 | /* |
64 | * | |
65 | *CM6533 audio jack HID raw events: | |
66 | * | |
67 | *Plug in: | |
68 | *01000600 002083xx 080008c0 10000000 | |
69 | *about 3 seconds later... | |
70 | *01000a00 002083xx 08000380 10000000 | |
71 | *01000600 002083xx 08000380 10000000 | |
72 | * | |
73 | *Plug out: | |
74 | *01000400 002083xx 080008c0 x0000000 | |
75 | */ | |
76 | ||
77 | static const u8 ji_sfx[] = { 0x08, 0x00, 0x08, 0xc0 }; | |
78 | static const u8 ji_in[] = { 0x01, 0x00, 0x06, 0x00 }; | |
79 | static const u8 ji_out[] = { 0x01, 0x00, 0x04, 0x00 }; | |
80 | ||
81 | static int jack_switch_types[CM6533_JD_TYPE_COUNT] = { | |
82 | SW_HEADPHONE_INSERT, | |
83 | }; | |
84 | ||
85 | struct cmhid { | |
86 | struct input_dev *input_dev; | |
87 | struct hid_device *hid; | |
88 | unsigned short switch_map[CM6533_JD_TYPE_COUNT]; | |
89 | }; | |
90 | ||
91 | static void hp_ev(struct hid_device *hid, struct cmhid *cm, int value) | |
92 | { | |
93 | input_report_switch(cm->input_dev, SW_HEADPHONE_INSERT, value); | |
94 | input_sync(cm->input_dev); | |
95 | } | |
96 | ||
97 | static int cmhid_raw_event(struct hid_device *hid, struct hid_report *report, | |
98 | u8 *data, int len) | |
99 | { | |
100 | struct cmhid *cm = hid_get_drvdata(hid); | |
101 | ||
102 | if (len != CM6533_JD_RAWEV_LEN) | |
103 | goto out; | |
104 | if (memcmp(data+CM6533_JD_SFX_OFFSET, ji_sfx, sizeof(ji_sfx))) | |
105 | goto out; | |
106 | ||
107 | if (!memcmp(data, ji_out, sizeof(ji_out))) { | |
108 | hp_ev(hid, cm, 0); | |
109 | goto out; | |
110 | } | |
111 | if (!memcmp(data, ji_in, sizeof(ji_in))) { | |
112 | hp_ev(hid, cm, 1); | |
113 | goto out; | |
114 | } | |
115 | ||
116 | out: | |
117 | return 0; | |
118 | } | |
119 | ||
120 | static int cmhid_input_configured(struct hid_device *hid, | |
121 | struct hid_input *hidinput) | |
122 | { | |
123 | struct input_dev *input_dev = hidinput->input; | |
124 | struct cmhid *cm = hid_get_drvdata(hid); | |
125 | int i; | |
126 | ||
127 | cm->input_dev = input_dev; | |
128 | memcpy(cm->switch_map, jack_switch_types, sizeof(cm->switch_map)); | |
129 | input_dev->evbit[0] = BIT(EV_SW); | |
130 | for (i = 0; i < CM6533_JD_TYPE_COUNT; i++) | |
131 | input_set_capability(cm->input_dev, | |
132 | EV_SW, jack_switch_types[i]); | |
133 | return 0; | |
134 | } | |
135 | ||
136 | static int cmhid_input_mapping(struct hid_device *hid, | |
137 | struct hid_input *hi, struct hid_field *field, | |
138 | struct hid_usage *usage, unsigned long **bit, int *max) | |
139 | { | |
140 | return -1; | |
141 | } | |
142 | ||
143 | static int cmhid_probe(struct hid_device *hid, const struct hid_device_id *id) | |
144 | { | |
145 | int ret; | |
146 | struct cmhid *cm; | |
147 | ||
148 | cm = kzalloc(sizeof(struct cmhid), GFP_KERNEL); | |
149 | if (!cm) { | |
150 | ret = -ENOMEM; | |
151 | goto allocfail; | |
152 | } | |
153 | ||
154 | cm->hid = hid; | |
155 | ||
156 | hid->quirks |= HID_QUIRK_HIDINPUT_FORCE; | |
157 | hid_set_drvdata(hid, cm); | |
158 | ||
159 | ret = hid_parse(hid); | |
160 | if (ret) { | |
161 | hid_err(hid, "parse failed\n"); | |
162 | goto fail; | |
163 | } | |
164 | ||
165 | ret = hid_hw_start(hid, HID_CONNECT_DEFAULT | HID_CONNECT_HIDDEV_FORCE); | |
166 | if (ret) { | |
167 | hid_err(hid, "hw start failed\n"); | |
168 | goto fail; | |
169 | } | |
170 | ||
171 | return 0; | |
172 | fail: | |
173 | kfree(cm); | |
174 | allocfail: | |
175 | return ret; | |
176 | } | |
177 | ||
178 | static void cmhid_remove(struct hid_device *hid) | |
179 | { | |
180 | struct cmhid *cm = hid_get_drvdata(hid); | |
181 | ||
182 | hid_hw_stop(hid); | |
183 | kfree(cm); | |
184 | } | |
185 | ||
186 | static const struct hid_device_id cmhid_devices[] = { | |
187 | { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CM6533) }, | |
188 | { } | |
189 | }; | |
190 | MODULE_DEVICE_TABLE(hid, cmhid_devices); | |
191 | ||
192 | static struct hid_driver cmhid_driver = { | |
193 | .name = "cm6533_jd", | |
194 | .id_table = cmhid_devices, | |
195 | .raw_event = cmhid_raw_event, | |
196 | .input_configured = cmhid_input_configured, | |
197 | .probe = cmhid_probe, | |
198 | .remove = cmhid_remove, | |
199 | .input_mapping = cmhid_input_mapping, | |
200 | }; | |
ad8ddc57 | 201 | |
bebf8820 TW |
202 | static __u8 *cmhid_hs100b_report_fixup(struct hid_device *hid, __u8 *rdesc, |
203 | unsigned int *rsize) | |
204 | { | |
205 | if (*rsize == HS100B_RDESC_ORIG_SIZE) { | |
206 | hid_info(hid, "Fixing CMedia HS-100B report descriptor\n"); | |
207 | rdesc = hs100b_rdesc_fixed; | |
208 | *rsize = sizeof(hs100b_rdesc_fixed); | |
209 | } | |
210 | return rdesc; | |
211 | } | |
212 | ||
213 | static const struct hid_device_id cmhid_hs100b_devices[] = { | |
214 | { HID_USB_DEVICE(USB_VENDOR_ID_CMEDIA, USB_DEVICE_ID_CMEDIA_HS100B) }, | |
215 | { } | |
216 | }; | |
217 | MODULE_DEVICE_TABLE(hid, cmhid_hs100b_devices); | |
218 | ||
219 | static struct hid_driver cmhid_hs100b_driver = { | |
220 | .name = "cmedia_hs100b", | |
221 | .id_table = cmhid_hs100b_devices, | |
222 | .report_fixup = cmhid_hs100b_report_fixup, | |
223 | }; | |
224 | ||
225 | static int cmedia_init(void) | |
226 | { | |
227 | int ret; | |
228 | ||
229 | ret = hid_register_driver(&cmhid_driver); | |
230 | if (ret) | |
231 | return ret; | |
232 | ||
233 | ret = hid_register_driver(&cmhid_hs100b_driver); | |
234 | if (ret) | |
235 | hid_unregister_driver(&cmhid_driver); | |
236 | ||
237 | return ret; | |
238 | } | |
239 | module_init(cmedia_init); | |
240 | ||
241 | static void cmedia_exit(void) | |
242 | { | |
243 | hid_unregister_driver(&cmhid_driver); | |
244 | hid_unregister_driver(&cmhid_hs100b_driver); | |
245 | } | |
246 | module_exit(cmedia_exit); |