Commit | Line | Data |
---|---|---|
68e353fe MR |
1 | /* |
2 | * HID driver for Huion devices not fully compliant with HID standard | |
3 | * | |
4 | * Copyright (c) 2013 Martin Rusko | |
e917e98f | 5 | * Copyright (c) 2014 Nikolai Kondrashov |
68e353fe MR |
6 | */ |
7 | ||
8 | /* | |
9 | * This program is free software; you can redistribute it and/or modify it | |
10 | * under the terms of the GNU General Public License as published by the Free | |
11 | * Software Foundation; either version 2 of the License, or (at your option) | |
12 | * any later version. | |
13 | */ | |
14 | ||
15 | #include <linux/device.h> | |
16 | #include <linux/hid.h> | |
17 | #include <linux/module.h> | |
18 | #include <linux/usb.h> | |
f8dd5cb2 | 19 | #include <asm/unaligned.h> |
68e353fe MR |
20 | #include "usbhid/usbhid.h" |
21 | ||
22 | #include "hid-ids.h" | |
23 | ||
f8dd5cb2 NK |
24 | /* Report descriptor template placeholder head */ |
25 | #define HUION_PH_HEAD 0xFE, 0xED, 0x1D | |
26 | ||
27 | /* Report descriptor template placeholder IDs */ | |
28 | enum huion_ph_id { | |
29 | HUION_PH_ID_X_LM, | |
30 | HUION_PH_ID_X_PM, | |
31 | HUION_PH_ID_Y_LM, | |
32 | HUION_PH_ID_Y_PM, | |
33 | HUION_PH_ID_PRESSURE_LM, | |
34 | HUION_PH_ID_NUM | |
35 | }; | |
36 | ||
37 | /* Report descriptor template placeholder */ | |
38 | #define HUION_PH(_ID) HUION_PH_HEAD, HUION_PH_ID_##_ID | |
39 | ||
40 | /* Fixed report descriptor template */ | |
41 | static const __u8 huion_tablet_rdesc_template[] = { | |
42 | 0x05, 0x0D, /* Usage Page (Digitizer), */ | |
43 | 0x09, 0x02, /* Usage (Pen), */ | |
44 | 0xA1, 0x01, /* Collection (Application), */ | |
45 | 0x85, 0x07, /* Report ID (7), */ | |
46 | 0x09, 0x20, /* Usage (Stylus), */ | |
47 | 0xA0, /* Collection (Physical), */ | |
48 | 0x14, /* Logical Minimum (0), */ | |
49 | 0x25, 0x01, /* Logical Maximum (1), */ | |
50 | 0x75, 0x01, /* Report Size (1), */ | |
51 | 0x09, 0x42, /* Usage (Tip Switch), */ | |
52 | 0x09, 0x44, /* Usage (Barrel Switch), */ | |
53 | 0x09, 0x46, /* Usage (Tablet Pick), */ | |
54 | 0x95, 0x03, /* Report Count (3), */ | |
55 | 0x81, 0x02, /* Input (Variable), */ | |
56 | 0x95, 0x03, /* Report Count (3), */ | |
57 | 0x81, 0x03, /* Input (Constant, Variable), */ | |
58 | 0x09, 0x32, /* Usage (In Range), */ | |
59 | 0x95, 0x01, /* Report Count (1), */ | |
60 | 0x81, 0x02, /* Input (Variable), */ | |
61 | 0x95, 0x01, /* Report Count (1), */ | |
62 | 0x81, 0x03, /* Input (Constant, Variable), */ | |
63 | 0x75, 0x10, /* Report Size (16), */ | |
64 | 0x95, 0x01, /* Report Count (1), */ | |
65 | 0xA4, /* Push, */ | |
66 | 0x05, 0x01, /* Usage Page (Desktop), */ | |
67 | 0x65, 0x13, /* Unit (Inch), */ | |
68 | 0x55, 0xFD, /* Unit Exponent (-3), */ | |
69 | 0x34, /* Physical Minimum (0), */ | |
70 | 0x09, 0x30, /* Usage (X), */ | |
71 | 0x27, HUION_PH(X_LM), /* Logical Maximum (PLACEHOLDER), */ | |
72 | 0x47, HUION_PH(X_PM), /* Physical Maximum (PLACEHOLDER), */ | |
73 | 0x81, 0x02, /* Input (Variable), */ | |
74 | 0x09, 0x31, /* Usage (Y), */ | |
75 | 0x27, HUION_PH(Y_LM), /* Logical Maximum (PLACEHOLDER), */ | |
76 | 0x47, HUION_PH(Y_PM), /* Physical Maximum (PLACEHOLDER), */ | |
77 | 0x81, 0x02, /* Input (Variable), */ | |
78 | 0xB4, /* Pop, */ | |
79 | 0x09, 0x30, /* Usage (Tip Pressure), */ | |
80 | 0x27, | |
81 | HUION_PH(PRESSURE_LM), /* Logical Maximum (PLACEHOLDER), */ | |
82 | 0x81, 0x02, /* Input (Variable), */ | |
83 | 0xC0, /* End Collection, */ | |
84 | 0xC0 /* End Collection */ | |
85 | }; | |
86 | ||
6498d023 NK |
87 | /* Parameter indices */ |
88 | enum huion_prm { | |
89 | HUION_PRM_X_LM = 1, | |
90 | HUION_PRM_Y_LM = 2, | |
91 | HUION_PRM_PRESSURE_LM = 4, | |
92 | HUION_PRM_RESOLUTION = 5, | |
93 | HUION_PRM_NUM | |
94 | }; | |
95 | ||
f8dd5cb2 NK |
96 | /* Driver data */ |
97 | struct huion_drvdata { | |
98 | __u8 *rdesc; | |
99 | unsigned int rsize; | |
68e353fe MR |
100 | }; |
101 | ||
102 | static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc, | |
103 | unsigned int *rsize) | |
104 | { | |
f8dd5cb2 | 105 | struct huion_drvdata *drvdata = hid_get_drvdata(hdev); |
68e353fe | 106 | switch (hdev->product) { |
e917e98f | 107 | case USB_DEVICE_ID_HUION_TABLET: |
f8dd5cb2 NK |
108 | if (drvdata->rdesc != NULL) { |
109 | rdesc = drvdata->rdesc; | |
110 | *rsize = drvdata->rsize; | |
68e353fe MR |
111 | } |
112 | break; | |
113 | } | |
114 | return rdesc; | |
115 | } | |
116 | ||
117 | /** | |
f8dd5cb2 | 118 | * Enable fully-functional tablet mode and determine device parameters. |
68e353fe MR |
119 | * |
120 | * @hdev: HID device | |
68e353fe MR |
121 | */ |
122 | static int huion_tablet_enable(struct hid_device *hdev) | |
123 | { | |
124 | int rc; | |
f8dd5cb2 NK |
125 | struct usb_device *usb_dev = hid_to_usb_dev(hdev); |
126 | struct huion_drvdata *drvdata = hid_get_drvdata(hdev); | |
6498d023 NK |
127 | __le16 *buf = NULL; |
128 | size_t len; | |
657d6dc4 NK |
129 | s32 params[HUION_PH_ID_NUM]; |
130 | s32 resolution; | |
131 | __u8 *p; | |
132 | s32 v; | |
68e353fe | 133 | |
f8dd5cb2 NK |
134 | /* |
135 | * Read string descriptor containing tablet parameters. The specific | |
136 | * string descriptor and data were discovered by sniffing the Windows | |
137 | * driver traffic. | |
138 | * NOTE: This enables fully-functional tablet mode. | |
139 | */ | |
6498d023 NK |
140 | len = HUION_PRM_NUM * sizeof(*buf); |
141 | buf = kmalloc(len, GFP_KERNEL); | |
142 | if (buf == NULL) { | |
143 | hid_err(hdev, "failed to allocate parameter buffer\n"); | |
144 | rc = -ENOMEM; | |
145 | goto cleanup; | |
146 | } | |
f8dd5cb2 NK |
147 | rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), |
148 | USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, | |
149 | (USB_DT_STRING << 8) + 0x64, | |
6498d023 | 150 | 0x0409, buf, len, |
f8dd5cb2 | 151 | USB_CTRL_GET_TIMEOUT); |
657d6dc4 NK |
152 | if (rc == -EPIPE) { |
153 | hid_err(hdev, "device parameters not found\n"); | |
6498d023 NK |
154 | rc = -ENODEV; |
155 | goto cleanup; | |
657d6dc4 NK |
156 | } else if (rc < 0) { |
157 | hid_err(hdev, "failed to get device parameters: %d\n", rc); | |
6498d023 NK |
158 | rc = -ENODEV; |
159 | goto cleanup; | |
160 | } else if (rc != len) { | |
657d6dc4 | 161 | hid_err(hdev, "invalid device parameters\n"); |
6498d023 NK |
162 | rc = -ENODEV; |
163 | goto cleanup; | |
657d6dc4 | 164 | } |
f8dd5cb2 | 165 | |
657d6dc4 | 166 | /* Extract device parameters */ |
6498d023 NK |
167 | params[HUION_PH_ID_X_LM] = le16_to_cpu(buf[HUION_PRM_X_LM]); |
168 | params[HUION_PH_ID_Y_LM] = le16_to_cpu(buf[HUION_PRM_Y_LM]); | |
169 | params[HUION_PH_ID_PRESSURE_LM] = | |
170 | le16_to_cpu(buf[HUION_PRM_PRESSURE_LM]); | |
171 | resolution = le16_to_cpu(buf[HUION_PRM_RESOLUTION]); | |
657d6dc4 NK |
172 | if (resolution == 0) { |
173 | params[HUION_PH_ID_X_PM] = 0; | |
174 | params[HUION_PH_ID_Y_PM] = 0; | |
175 | } else { | |
176 | params[HUION_PH_ID_X_PM] = params[HUION_PH_ID_X_LM] * | |
177 | 1000 / resolution; | |
178 | params[HUION_PH_ID_Y_PM] = params[HUION_PH_ID_Y_LM] * | |
179 | 1000 / resolution; | |
180 | } | |
181 | ||
182 | /* Allocate fixed report descriptor */ | |
183 | drvdata->rdesc = devm_kmalloc(&hdev->dev, | |
184 | sizeof(huion_tablet_rdesc_template), | |
185 | GFP_KERNEL); | |
186 | if (drvdata->rdesc == NULL) { | |
187 | hid_err(hdev, "failed to allocate fixed rdesc\n"); | |
6498d023 NK |
188 | rc = -ENOMEM; |
189 | goto cleanup; | |
657d6dc4 NK |
190 | } |
191 | drvdata->rsize = sizeof(huion_tablet_rdesc_template); | |
192 | ||
193 | /* Format fixed report descriptor */ | |
194 | memcpy(drvdata->rdesc, huion_tablet_rdesc_template, | |
195 | drvdata->rsize); | |
196 | for (p = drvdata->rdesc; | |
197 | p <= drvdata->rdesc + drvdata->rsize - 4;) { | |
198 | if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D && | |
199 | p[3] < sizeof(params)) { | |
200 | v = params[p[3]]; | |
201 | put_unaligned(cpu_to_le32(v), (s32 *)p); | |
202 | p += 4; | |
203 | } else { | |
204 | p++; | |
f8dd5cb2 NK |
205 | } |
206 | } | |
68e353fe | 207 | |
6498d023 NK |
208 | rc = 0; |
209 | ||
210 | cleanup: | |
211 | kfree(buf); | |
212 | return rc; | |
68e353fe MR |
213 | } |
214 | ||
215 | static int huion_probe(struct hid_device *hdev, const struct hid_device_id *id) | |
216 | { | |
f8dd5cb2 NK |
217 | int rc; |
218 | struct usb_interface *intf = to_usb_interface(hdev->dev.parent); | |
219 | struct huion_drvdata *drvdata; | |
68e353fe | 220 | |
f8dd5cb2 NK |
221 | /* Allocate and assign driver data */ |
222 | drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); | |
223 | if (drvdata == NULL) { | |
224 | hid_err(hdev, "failed to allocate driver data\n"); | |
225 | return -ENOMEM; | |
68e353fe | 226 | } |
f8dd5cb2 | 227 | hid_set_drvdata(hdev, drvdata); |
68e353fe MR |
228 | |
229 | switch (id->product) { | |
e917e98f | 230 | case USB_DEVICE_ID_HUION_TABLET: |
f8dd5cb2 NK |
231 | /* If this is the pen interface */ |
232 | if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { | |
233 | rc = huion_tablet_enable(hdev); | |
234 | if (rc) { | |
235 | hid_err(hdev, "tablet enabling failed\n"); | |
236 | return rc; | |
237 | } | |
68e353fe MR |
238 | } |
239 | break; | |
240 | } | |
241 | ||
f8dd5cb2 NK |
242 | rc = hid_parse(hdev); |
243 | if (rc) { | |
244 | hid_err(hdev, "parse failed\n"); | |
245 | return rc; | |
246 | } | |
247 | ||
248 | rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | |
249 | if (rc) { | |
250 | hid_err(hdev, "hw start failed\n"); | |
251 | return rc; | |
252 | } | |
253 | ||
68e353fe | 254 | return 0; |
68e353fe MR |
255 | } |
256 | ||
257 | static int huion_raw_event(struct hid_device *hdev, struct hid_report *report, | |
258 | u8 *data, int size) | |
259 | { | |
fb853296 NK |
260 | struct usb_interface *intf = to_usb_interface(hdev->dev.parent); |
261 | ||
262 | /* If this is a pen input report */ | |
263 | if (intf->cur_altsetting->desc.bInterfaceNumber == 0 && | |
264 | report->type == HID_INPUT_REPORT && | |
265 | report->id == 0x07 && size >= 2) | |
266 | /* Invert the in-range bit */ | |
68e353fe MR |
267 | data[1] ^= 0x40; |
268 | ||
269 | return 0; | |
270 | } | |
271 | ||
272 | static const struct hid_device_id huion_devices[] = { | |
e917e98f | 273 | { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, |
3f1f3332 | 274 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, |
68e353fe MR |
275 | { } |
276 | }; | |
277 | MODULE_DEVICE_TABLE(hid, huion_devices); | |
278 | ||
279 | static struct hid_driver huion_driver = { | |
280 | .name = "huion", | |
281 | .id_table = huion_devices, | |
282 | .probe = huion_probe, | |
283 | .report_fixup = huion_report_fixup, | |
284 | .raw_event = huion_raw_event, | |
285 | }; | |
286 | module_hid_driver(huion_driver); | |
287 | ||
288 | MODULE_AUTHOR("Martin Rusko"); | |
289 | MODULE_DESCRIPTION("Huion HID driver"); | |
290 | MODULE_LICENSE("GPL"); |