diff options
Diffstat (limited to 'drivers/hid/hid-uclogic-core.c')
-rw-r--r-- | drivers/hid/hid-uclogic-core.c | 428 |
1 files changed, 102 insertions, 326 deletions
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 4042183ee9a3..72a3a43766cc 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c | |||
@@ -16,126 +16,48 @@ | |||
16 | #include <linux/device.h> | 16 | #include <linux/device.h> |
17 | #include <linux/hid.h> | 17 | #include <linux/hid.h> |
18 | #include <linux/module.h> | 18 | #include <linux/module.h> |
19 | #include <linux/usb.h> | ||
20 | #include "usbhid/usbhid.h" | 19 | #include "usbhid/usbhid.h" |
21 | #include "hid-uclogic-rdesc.h" | 20 | #include "hid-uclogic-params.h" |
22 | 21 | ||
23 | #include "hid-ids.h" | 22 | #include "hid-ids.h" |
24 | 23 | ||
25 | /* Parameter indices */ | ||
26 | enum uclogic_prm { | ||
27 | UCLOGIC_PRM_X_LM = 1, | ||
28 | UCLOGIC_PRM_Y_LM = 2, | ||
29 | UCLOGIC_PRM_PRESSURE_LM = 4, | ||
30 | UCLOGIC_PRM_RESOLUTION = 5, | ||
31 | UCLOGIC_PRM_NUM | ||
32 | }; | ||
33 | |||
34 | /* Driver data */ | 24 | /* Driver data */ |
35 | struct uclogic_drvdata { | 25 | struct uclogic_drvdata { |
36 | __u8 *rdesc; | 26 | /* Interface parameters */ |
37 | unsigned int rsize; | 27 | struct uclogic_params params; |
38 | bool invert_pen_inrange; | 28 | /* Pointer to the replacement report descriptor. NULL if none. */ |
39 | bool ignore_pen_usage; | 29 | __u8 *desc_ptr; |
40 | bool has_virtual_pad_interface; | 30 | /* |
31 | * Size of the replacement report descriptor. | ||
32 | * Only valid if desc_ptr is not NULL | ||
33 | */ | ||
34 | unsigned int desc_size; | ||
41 | }; | 35 | }; |
42 | 36 | ||
43 | static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, | 37 | static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, |
44 | unsigned int *rsize) | 38 | unsigned int *rsize) |
45 | { | 39 | { |
46 | struct usb_interface *iface = to_usb_interface(hdev->dev.parent); | ||
47 | __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; | ||
48 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | 40 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); |
49 | 41 | ||
50 | if (drvdata->rdesc != NULL) { | 42 | if (drvdata->desc_ptr != NULL) { |
51 | rdesc = drvdata->rdesc; | 43 | rdesc = drvdata->desc_ptr; |
52 | *rsize = drvdata->rsize; | 44 | *rsize = drvdata->desc_size; |
53 | return rdesc; | ||
54 | } | ||
55 | |||
56 | switch (hdev->product) { | ||
57 | case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209: | ||
58 | if (*rsize == UCLOGIC_RDESC_PF1209_ORIG_SIZE) { | ||
59 | rdesc = uclogic_rdesc_pf1209_fixed_arr; | ||
60 | *rsize = uclogic_rdesc_pf1209_fixed_size; | ||
61 | } | ||
62 | break; | ||
63 | case USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U: | ||
64 | if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) { | ||
65 | rdesc = uclogic_rdesc_wp4030u_fixed_arr; | ||
66 | *rsize = uclogic_rdesc_wp4030u_fixed_size; | ||
67 | } | ||
68 | break; | ||
69 | case USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U: | ||
70 | if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) { | ||
71 | rdesc = uclogic_rdesc_wp5540u_fixed_arr; | ||
72 | *rsize = uclogic_rdesc_wp5540u_fixed_size; | ||
73 | } | ||
74 | break; | ||
75 | case USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U: | ||
76 | if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) { | ||
77 | rdesc = uclogic_rdesc_wp8060u_fixed_arr; | ||
78 | *rsize = uclogic_rdesc_wp8060u_fixed_size; | ||
79 | } | ||
80 | break; | ||
81 | case USB_DEVICE_ID_UCLOGIC_TABLET_WP1062: | ||
82 | if (*rsize == UCLOGIC_RDESC_WP1062_ORIG_SIZE) { | ||
83 | rdesc = uclogic_rdesc_wp1062_fixed_arr; | ||
84 | *rsize = uclogic_rdesc_wp1062_fixed_size; | ||
85 | } | ||
86 | break; | ||
87 | case USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850: | ||
88 | switch (iface_num) { | ||
89 | case 0: | ||
90 | if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG0_SIZE) { | ||
91 | rdesc = uclogic_rdesc_twhl850_fixed0_arr; | ||
92 | *rsize = uclogic_rdesc_twhl850_fixed0_size; | ||
93 | } | ||
94 | break; | ||
95 | case 1: | ||
96 | if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG1_SIZE) { | ||
97 | rdesc = uclogic_rdesc_twhl850_fixed1_arr; | ||
98 | *rsize = uclogic_rdesc_twhl850_fixed1_size; | ||
99 | } | ||
100 | break; | ||
101 | case 2: | ||
102 | if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG2_SIZE) { | ||
103 | rdesc = uclogic_rdesc_twhl850_fixed2_arr; | ||
104 | *rsize = uclogic_rdesc_twhl850_fixed2_size; | ||
105 | } | ||
106 | break; | ||
107 | } | ||
108 | break; | ||
109 | case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: | ||
110 | switch (iface_num) { | ||
111 | case 0: | ||
112 | if (*rsize == UCLOGIC_RDESC_TWHA60_ORIG0_SIZE) { | ||
113 | rdesc = uclogic_rdesc_twha60_fixed0_arr; | ||
114 | *rsize = uclogic_rdesc_twha60_fixed0_size; | ||
115 | } | ||
116 | break; | ||
117 | case 1: | ||
118 | if (*rsize == UCLOGIC_RDESC_TWHA60_ORIG1_SIZE) { | ||
119 | rdesc = uclogic_rdesc_twha60_fixed1_arr; | ||
120 | *rsize = uclogic_rdesc_twha60_fixed1_size; | ||
121 | } | ||
122 | break; | ||
123 | } | ||
124 | break; | ||
125 | } | 45 | } |
126 | |||
127 | return rdesc; | 46 | return rdesc; |
128 | } | 47 | } |
129 | 48 | ||
130 | static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi, | 49 | static int uclogic_input_mapping(struct hid_device *hdev, |
131 | struct hid_field *field, struct hid_usage *usage, | 50 | struct hid_input *hi, |
132 | unsigned long **bit, int *max) | 51 | struct hid_field *field, |
52 | struct hid_usage *usage, | ||
53 | unsigned long **bit, | ||
54 | int *max) | ||
133 | { | 55 | { |
134 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | 56 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); |
57 | struct uclogic_params *params = &drvdata->params; | ||
135 | 58 | ||
136 | /* discard the unused pen interface */ | 59 | /* discard the unused pen interface */ |
137 | if ((drvdata->ignore_pen_usage) && | 60 | if (params->pen_unused && (field->application == HID_DG_PEN)) |
138 | (field->application == HID_DG_PEN)) | ||
139 | return -1; | 61 | return -1; |
140 | 62 | ||
141 | /* let hid-core decide what to do */ | 63 | /* let hid-core decide what to do */ |
@@ -189,160 +111,12 @@ static int uclogic_input_configured(struct hid_device *hdev, | |||
189 | return 0; | 111 | return 0; |
190 | } | 112 | } |
191 | 113 | ||
192 | /** | ||
193 | * Enable fully-functional tablet mode and determine device parameters. | ||
194 | * | ||
195 | * @hdev: HID device | ||
196 | */ | ||
197 | static int uclogic_tablet_enable(struct hid_device *hdev) | ||
198 | { | ||
199 | int rc; | ||
200 | struct usb_device *usb_dev = hid_to_usb_dev(hdev); | ||
201 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | ||
202 | __le16 *buf = NULL; | ||
203 | size_t len; | ||
204 | s32 params[UCLOGIC_RDESC_PEN_PH_ID_NUM]; | ||
205 | s32 resolution; | ||
206 | |||
207 | /* | ||
208 | * Read string descriptor containing tablet parameters. The specific | ||
209 | * string descriptor and data were discovered by sniffing the Windows | ||
210 | * driver traffic. | ||
211 | * NOTE: This enables fully-functional tablet mode. | ||
212 | */ | ||
213 | len = UCLOGIC_PRM_NUM * sizeof(*buf); | ||
214 | buf = kmalloc(len, GFP_KERNEL); | ||
215 | if (buf == NULL) { | ||
216 | rc = -ENOMEM; | ||
217 | goto cleanup; | ||
218 | } | ||
219 | rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), | ||
220 | USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, | ||
221 | (USB_DT_STRING << 8) + 0x64, | ||
222 | 0x0409, buf, len, | ||
223 | USB_CTRL_GET_TIMEOUT); | ||
224 | if (rc == -EPIPE) { | ||
225 | hid_err(hdev, "device parameters not found\n"); | ||
226 | rc = -ENODEV; | ||
227 | goto cleanup; | ||
228 | } else if (rc < 0) { | ||
229 | hid_err(hdev, "failed to get device parameters: %d\n", rc); | ||
230 | rc = -ENODEV; | ||
231 | goto cleanup; | ||
232 | } else if (rc != len) { | ||
233 | hid_err(hdev, "invalid device parameters\n"); | ||
234 | rc = -ENODEV; | ||
235 | goto cleanup; | ||
236 | } | ||
237 | |||
238 | /* Extract device parameters */ | ||
239 | params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = | ||
240 | le16_to_cpu(buf[UCLOGIC_PRM_X_LM]); | ||
241 | params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = | ||
242 | le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]); | ||
243 | params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] = | ||
244 | le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]); | ||
245 | resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]); | ||
246 | if (resolution == 0) { | ||
247 | params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0; | ||
248 | params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0; | ||
249 | } else { | ||
250 | params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = | ||
251 | params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * | ||
252 | 1000 / resolution; | ||
253 | params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = | ||
254 | params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * | ||
255 | 1000 / resolution; | ||
256 | } | ||
257 | |||
258 | /* Format fixed report descriptor */ | ||
259 | drvdata->rdesc = uclogic_rdesc_template_apply( | ||
260 | uclogic_rdesc_pen_template_arr, | ||
261 | uclogic_rdesc_pen_template_size, | ||
262 | params, ARRAY_SIZE(params)); | ||
263 | if (drvdata->rdesc == NULL) { | ||
264 | rc = -ENOMEM; | ||
265 | goto cleanup; | ||
266 | } | ||
267 | drvdata->rsize = uclogic_rdesc_pen_template_size; | ||
268 | |||
269 | rc = 0; | ||
270 | |||
271 | cleanup: | ||
272 | kfree(buf); | ||
273 | return rc; | ||
274 | } | ||
275 | |||
276 | /** | ||
277 | * Enable actual button mode. | ||
278 | * | ||
279 | * @hdev: HID device | ||
280 | */ | ||
281 | static int uclogic_button_enable(struct hid_device *hdev) | ||
282 | { | ||
283 | int rc; | ||
284 | struct usb_device *usb_dev = hid_to_usb_dev(hdev); | ||
285 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | ||
286 | char *str_buf; | ||
287 | size_t str_len = 16; | ||
288 | unsigned char *rdesc; | ||
289 | size_t rdesc_len; | ||
290 | |||
291 | str_buf = kzalloc(str_len, GFP_KERNEL); | ||
292 | if (str_buf == NULL) { | ||
293 | rc = -ENOMEM; | ||
294 | goto cleanup; | ||
295 | } | ||
296 | |||
297 | /* Enable abstract keyboard mode */ | ||
298 | rc = usb_string(usb_dev, 0x7b, str_buf, str_len); | ||
299 | if (rc == -EPIPE) { | ||
300 | hid_info(hdev, "button mode setting not found\n"); | ||
301 | rc = 0; | ||
302 | goto cleanup; | ||
303 | } else if (rc < 0) { | ||
304 | hid_err(hdev, "failed to enable abstract keyboard\n"); | ||
305 | goto cleanup; | ||
306 | } else if (strncmp(str_buf, "HK On", rc)) { | ||
307 | hid_info(hdev, "invalid answer when requesting buttons: '%s'\n", | ||
308 | str_buf); | ||
309 | rc = -EINVAL; | ||
310 | goto cleanup; | ||
311 | } | ||
312 | |||
313 | /* Re-allocate fixed report descriptor */ | ||
314 | rdesc_len = drvdata->rsize + uclogic_rdesc_buttonpad_size; | ||
315 | rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL); | ||
316 | if (!rdesc) { | ||
317 | rc = -ENOMEM; | ||
318 | goto cleanup; | ||
319 | } | ||
320 | |||
321 | memcpy(rdesc, drvdata->rdesc, drvdata->rsize); | ||
322 | |||
323 | /* Append the buttonpad descriptor */ | ||
324 | memcpy(rdesc + drvdata->rsize, uclogic_rdesc_buttonpad_arr, | ||
325 | uclogic_rdesc_buttonpad_size); | ||
326 | |||
327 | /* clean up old rdesc and use the new one */ | ||
328 | drvdata->rsize = rdesc_len; | ||
329 | devm_kfree(&hdev->dev, drvdata->rdesc); | ||
330 | drvdata->rdesc = rdesc; | ||
331 | |||
332 | rc = 0; | ||
333 | |||
334 | cleanup: | ||
335 | kfree(str_buf); | ||
336 | return rc; | ||
337 | } | ||
338 | |||
339 | static int uclogic_probe(struct hid_device *hdev, | 114 | static int uclogic_probe(struct hid_device *hdev, |
340 | const struct hid_device_id *id) | 115 | const struct hid_device_id *id) |
341 | { | 116 | { |
342 | int rc; | 117 | int rc; |
343 | struct usb_interface *intf = to_usb_interface(hdev->dev.parent); | 118 | struct uclogic_drvdata *drvdata = NULL; |
344 | struct usb_device *udev = hid_to_usb_dev(hdev); | 119 | bool params_initialized = false; |
345 | struct uclogic_drvdata *drvdata; | ||
346 | 120 | ||
347 | /* | 121 | /* |
348 | * libinput requires the pad interface to be on a different node | 122 | * libinput requires the pad interface to be on a different node |
@@ -352,104 +126,97 @@ static int uclogic_probe(struct hid_device *hdev, | |||
352 | 126 | ||
353 | /* Allocate and assign driver data */ | 127 | /* Allocate and assign driver data */ |
354 | drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); | 128 | drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); |
355 | if (drvdata == NULL) | 129 | if (drvdata == NULL) { |
356 | return -ENOMEM; | 130 | rc = -ENOMEM; |
357 | 131 | goto failure; | |
132 | } | ||
358 | hid_set_drvdata(hdev, drvdata); | 133 | hid_set_drvdata(hdev, drvdata); |
359 | 134 | ||
360 | switch (id->product) { | 135 | /* Initialize the device and retrieve interface parameters */ |
361 | case USB_DEVICE_ID_HUION_TABLET: | 136 | rc = uclogic_params_init(&drvdata->params, hdev); |
362 | case USB_DEVICE_ID_YIYNOVA_TABLET: | 137 | if (rc != 0) { |
363 | case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81: | 138 | hid_err(hdev, "failed probing parameters: %d\n", rc); |
364 | case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3: | 139 | goto failure; |
365 | case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45: | 140 | } |
366 | /* If this is the pen interface */ | 141 | params_initialized = true; |
367 | if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { | 142 | hid_dbg(hdev, "parameters:\n" UCLOGIC_PARAMS_FMT_STR, |
368 | rc = uclogic_tablet_enable(hdev); | 143 | UCLOGIC_PARAMS_FMT_ARGS(&drvdata->params)); |
369 | if (rc) { | 144 | if (drvdata->params.invalid) { |
370 | hid_err(hdev, "tablet enabling failed\n"); | 145 | hid_info(hdev, "interface is invalid, ignoring\n"); |
371 | return rc; | 146 | rc = -ENODEV; |
372 | } | 147 | goto failure; |
373 | drvdata->invert_pen_inrange = true; | 148 | } |
374 | |||
375 | rc = uclogic_button_enable(hdev); | ||
376 | drvdata->has_virtual_pad_interface = !rc; | ||
377 | } else { | ||
378 | drvdata->ignore_pen_usage = true; | ||
379 | } | ||
380 | break; | ||
381 | case USB_DEVICE_ID_UGTIZER_TABLET_GP0610: | ||
382 | case USB_DEVICE_ID_UGEE_TABLET_EX07S: | ||
383 | /* If this is the pen interface */ | ||
384 | if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { | ||
385 | rc = uclogic_tablet_enable(hdev); | ||
386 | if (rc) { | ||
387 | hid_err(hdev, "tablet enabling failed\n"); | ||
388 | return rc; | ||
389 | } | ||
390 | drvdata->invert_pen_inrange = true; | ||
391 | } else { | ||
392 | drvdata->ignore_pen_usage = true; | ||
393 | } | ||
394 | break; | ||
395 | case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: | ||
396 | /* | ||
397 | * If it is the three-interface version, which is known to | ||
398 | * respond to initialization. | ||
399 | */ | ||
400 | if (udev->config->desc.bNumInterfaces == 3) { | ||
401 | /* If it is the pen interface */ | ||
402 | if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { | ||
403 | rc = uclogic_tablet_enable(hdev); | ||
404 | if (rc) { | ||
405 | hid_err(hdev, "tablet enabling failed\n"); | ||
406 | return rc; | ||
407 | } | ||
408 | drvdata->invert_pen_inrange = true; | ||
409 | 149 | ||
410 | rc = uclogic_button_enable(hdev); | 150 | /* Generate replacement report descriptor */ |
411 | drvdata->has_virtual_pad_interface = !rc; | 151 | rc = uclogic_params_get_desc(&drvdata->params, |
412 | } else { | 152 | &drvdata->desc_ptr, |
413 | drvdata->ignore_pen_usage = true; | 153 | &drvdata->desc_size); |
414 | } | 154 | if (rc) { |
415 | } | 155 | hid_err(hdev, |
416 | break; | 156 | "failed generating replacement report descriptor: %d\n", |
157 | rc); | ||
158 | goto failure; | ||
417 | } | 159 | } |
418 | 160 | ||
419 | rc = hid_parse(hdev); | 161 | rc = hid_parse(hdev); |
420 | if (rc) { | 162 | if (rc) { |
421 | hid_err(hdev, "parse failed\n"); | 163 | hid_err(hdev, "parse failed\n"); |
422 | return rc; | 164 | goto failure; |
423 | } | 165 | } |
424 | 166 | ||
425 | rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); | 167 | rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); |
426 | if (rc) { | 168 | if (rc) { |
427 | hid_err(hdev, "hw start failed\n"); | 169 | hid_err(hdev, "hw start failed\n"); |
428 | return rc; | 170 | goto failure; |
429 | } | 171 | } |
430 | 172 | ||
431 | return 0; | 173 | return 0; |
174 | failure: | ||
175 | /* Assume "remove" might not be called if "probe" failed */ | ||
176 | if (params_initialized) | ||
177 | uclogic_params_cleanup(&drvdata->params); | ||
178 | return rc; | ||
432 | } | 179 | } |
433 | 180 | ||
434 | static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, | 181 | static int uclogic_raw_event(struct hid_device *hdev, |
435 | u8 *data, int size) | 182 | struct hid_report *report, |
183 | u8 *data, int size) | ||
436 | { | 184 | { |
437 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | 185 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); |
186 | struct uclogic_params *params = &drvdata->params; | ||
438 | 187 | ||
439 | if ((report->type == HID_INPUT_REPORT) && | 188 | /* Tweak pen reports, if necessary */ |
440 | (report->id == UCLOGIC_RDESC_PEN_ID) && | 189 | if (!params->pen_unused && |
190 | (report->type == HID_INPUT_REPORT) && | ||
191 | (report->id == params->pen.id) && | ||
441 | (size >= 2)) { | 192 | (size >= 2)) { |
442 | if (drvdata->has_virtual_pad_interface && (data[1] & 0x20)) | 193 | /* If it's the "virtual" frame controls report */ |
443 | /* Change to virtual frame button report ID */ | 194 | if (params->frame.id != 0 && |
444 | data[0] = 0xf7; | 195 | data[1] & params->pen_frame_flag) { |
445 | else if (drvdata->invert_pen_inrange) | 196 | /* Change to virtual frame controls report ID */ |
197 | data[0] = params->frame.id; | ||
198 | return 0; | ||
199 | } | ||
200 | /* If in-range reports are inverted */ | ||
201 | if (params->pen.inrange == | ||
202 | UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) { | ||
446 | /* Invert the in-range bit */ | 203 | /* Invert the in-range bit */ |
447 | data[1] ^= 0x40; | 204 | data[1] ^= 0x40; |
205 | } | ||
448 | } | 206 | } |
449 | 207 | ||
450 | return 0; | 208 | return 0; |
451 | } | 209 | } |
452 | 210 | ||
211 | static void uclogic_remove(struct hid_device *hdev) | ||
212 | { | ||
213 | struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); | ||
214 | |||
215 | hid_hw_stop(hdev); | ||
216 | kfree(drvdata->desc_ptr); | ||
217 | uclogic_params_cleanup(&drvdata->params); | ||
218 | } | ||
219 | |||
453 | static const struct hid_device_id uclogic_devices[] = { | 220 | static const struct hid_device_id uclogic_devices[] = { |
454 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, | 221 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, |
455 | USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) }, | 222 | USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) }, |
@@ -465,14 +232,22 @@ static const struct hid_device_id uclogic_devices[] = { | |||
465 | USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) }, | 232 | USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) }, |
466 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, | 233 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, |
467 | USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, | 234 | USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, |
468 | { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, | 235 | { HID_USB_DEVICE(USB_VENDOR_ID_HUION, |
469 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, | 236 | USB_DEVICE_ID_HUION_TABLET) }, |
470 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) }, | 237 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, |
471 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) }, | 238 | USB_DEVICE_ID_HUION_TABLET) }, |
472 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) }, | 239 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, |
473 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, | 240 | USB_DEVICE_ID_YIYNOVA_TABLET) }, |
474 | { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, | 241 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, |
475 | { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) }, | 242 | USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) }, |
243 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, | ||
244 | USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) }, | ||
245 | { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, | ||
246 | USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, | ||
247 | { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, | ||
248 | USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, | ||
249 | { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, | ||
250 | USB_DEVICE_ID_UGEE_TABLET_EX07S) }, | ||
476 | { } | 251 | { } |
477 | }; | 252 | }; |
478 | MODULE_DEVICE_TABLE(hid, uclogic_devices); | 253 | MODULE_DEVICE_TABLE(hid, uclogic_devices); |
@@ -481,6 +256,7 @@ static struct hid_driver uclogic_driver = { | |||
481 | .name = "uclogic", | 256 | .name = "uclogic", |
482 | .id_table = uclogic_devices, | 257 | .id_table = uclogic_devices, |
483 | .probe = uclogic_probe, | 258 | .probe = uclogic_probe, |
259 | .remove = uclogic_remove, | ||
484 | .report_fixup = uclogic_report_fixup, | 260 | .report_fixup = uclogic_report_fixup, |
485 | .raw_event = uclogic_raw_event, | 261 | .raw_event = uclogic_raw_event, |
486 | .input_mapping = uclogic_input_mapping, | 262 | .input_mapping = uclogic_input_mapping, |