blob: 72a3a43766ccc77495a0ab347454c9b36eba2903 [file] [log] [blame]
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +02001// SPDX-License-Identifier: GPL-2.0+
2/*
3 * HID driver for UC-Logic devices not fully compliant with HID standard
4 *
5 * Copyright (c) 2010-2014 Nikolai Kondrashov
6 * Copyright (c) 2013 Martin Rusko
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/device.h>
17#include <linux/hid.h>
18#include <linux/module.h>
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020019#include "usbhid/usbhid.h"
Nikolai Kondrashov96142192019-02-10 12:13:51 +020020#include "hid-uclogic-params.h"
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020021
22#include "hid-ids.h"
23
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020024/* Driver data */
25struct uclogic_drvdata {
Nikolai Kondrashov96142192019-02-10 12:13:51 +020026 /* Interface parameters */
27 struct uclogic_params params;
28 /* Pointer to the replacement report descriptor. NULL if none. */
29 __u8 *desc_ptr;
30 /*
31 * Size of the replacement report descriptor.
32 * Only valid if desc_ptr is not NULL
33 */
34 unsigned int desc_size;
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020035};
36
37static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
38 unsigned int *rsize)
39{
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020040 struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
41
Nikolai Kondrashov96142192019-02-10 12:13:51 +020042 if (drvdata->desc_ptr != NULL) {
43 rdesc = drvdata->desc_ptr;
44 *rsize = drvdata->desc_size;
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020045 }
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020046 return rdesc;
47}
48
Nikolai Kondrashov96142192019-02-10 12:13:51 +020049static int uclogic_input_mapping(struct hid_device *hdev,
50 struct hid_input *hi,
51 struct hid_field *field,
52 struct hid_usage *usage,
53 unsigned long **bit,
54 int *max)
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020055{
56 struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
Nikolai Kondrashov96142192019-02-10 12:13:51 +020057 struct uclogic_params *params = &drvdata->params;
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020058
59 /* discard the unused pen interface */
Nikolai Kondrashov96142192019-02-10 12:13:51 +020060 if (params->pen_unused && (field->application == HID_DG_PEN))
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +020061 return -1;
62
63 /* let hid-core decide what to do */
64 return 0;
65}
66
67static int uclogic_input_configured(struct hid_device *hdev,
68 struct hid_input *hi)
69{
70 char *name;
71 const char *suffix = NULL;
72 struct hid_field *field;
73 size_t len;
74
75 /* no report associated (HID_QUIRK_MULTI_INPUT not set) */
76 if (!hi->report)
77 return 0;
78
79 field = hi->report->field[0];
80
81 switch (field->application) {
82 case HID_GD_KEYBOARD:
83 suffix = "Keyboard";
84 break;
85 case HID_GD_MOUSE:
86 suffix = "Mouse";
87 break;
88 case HID_GD_KEYPAD:
89 suffix = "Pad";
90 break;
91 case HID_DG_PEN:
92 suffix = "Pen";
93 break;
94 case HID_CP_CONSUMER_CONTROL:
95 suffix = "Consumer Control";
96 break;
97 case HID_GD_SYSTEM_CONTROL:
98 suffix = "System Control";
99 break;
100 }
101
102 if (suffix) {
103 len = strlen(hdev->name) + 2 + strlen(suffix);
104 name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL);
105 if (name) {
106 snprintf(name, len, "%s %s", hdev->name, suffix);
107 hi->input->name = name;
108 }
109 }
110
111 return 0;
112}
113
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200114static int uclogic_probe(struct hid_device *hdev,
115 const struct hid_device_id *id)
116{
117 int rc;
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200118 struct uclogic_drvdata *drvdata = NULL;
119 bool params_initialized = false;
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200120
121 /*
122 * libinput requires the pad interface to be on a different node
123 * than the pen, so use QUIRK_MULTI_INPUT for all tablets.
124 */
125 hdev->quirks |= HID_QUIRK_MULTI_INPUT;
126
127 /* Allocate and assign driver data */
128 drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200129 if (drvdata == NULL) {
130 rc = -ENOMEM;
131 goto failure;
132 }
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200133 hid_set_drvdata(hdev, drvdata);
134
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200135 /* Initialize the device and retrieve interface parameters */
136 rc = uclogic_params_init(&drvdata->params, hdev);
137 if (rc != 0) {
138 hid_err(hdev, "failed probing parameters: %d\n", rc);
139 goto failure;
140 }
141 params_initialized = true;
142 hid_dbg(hdev, "parameters:\n" UCLOGIC_PARAMS_FMT_STR,
143 UCLOGIC_PARAMS_FMT_ARGS(&drvdata->params));
144 if (drvdata->params.invalid) {
145 hid_info(hdev, "interface is invalid, ignoring\n");
146 rc = -ENODEV;
147 goto failure;
148 }
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200149
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200150 /* Generate replacement report descriptor */
151 rc = uclogic_params_get_desc(&drvdata->params,
152 &drvdata->desc_ptr,
153 &drvdata->desc_size);
154 if (rc) {
155 hid_err(hdev,
156 "failed generating replacement report descriptor: %d\n",
157 rc);
158 goto failure;
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200159 }
160
161 rc = hid_parse(hdev);
162 if (rc) {
163 hid_err(hdev, "parse failed\n");
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200164 goto failure;
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200165 }
166
167 rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
168 if (rc) {
169 hid_err(hdev, "hw start failed\n");
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200170 goto failure;
171 }
172
173 return 0;
174failure:
175 /* Assume "remove" might not be called if "probe" failed */
176 if (params_initialized)
177 uclogic_params_cleanup(&drvdata->params);
178 return rc;
179}
180
181static int uclogic_raw_event(struct hid_device *hdev,
182 struct hid_report *report,
183 u8 *data, int size)
184{
185 struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
186 struct uclogic_params *params = &drvdata->params;
187
188 /* Tweak pen reports, if necessary */
189 if (!params->pen_unused &&
190 (report->type == HID_INPUT_REPORT) &&
191 (report->id == params->pen.id) &&
192 (size >= 2)) {
193 /* If it's the "virtual" frame controls report */
194 if (params->frame.id != 0 &&
195 data[1] & params->pen_frame_flag) {
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) {
203 /* Invert the in-range bit */
204 data[1] ^= 0x40;
205 }
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200206 }
207
208 return 0;
209}
210
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200211static void uclogic_remove(struct hid_device *hdev)
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200212{
213 struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
214
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200215 hid_hw_stop(hdev);
216 kfree(drvdata->desc_ptr);
217 uclogic_params_cleanup(&drvdata->params);
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200218}
219
220static const struct hid_device_id uclogic_devices[] = {
221 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
222 USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
223 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
224 USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) },
225 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
226 USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) },
227 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
228 USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) },
229 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
230 USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) },
231 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
232 USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
233 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
234 USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200235 { HID_USB_DEVICE(USB_VENDOR_ID_HUION,
236 USB_DEVICE_ID_HUION_TABLET) },
237 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
238 USB_DEVICE_ID_HUION_TABLET) },
239 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
240 USB_DEVICE_ID_YIYNOVA_TABLET) },
241 { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
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) },
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200251 { }
252};
253MODULE_DEVICE_TABLE(hid, uclogic_devices);
254
255static struct hid_driver uclogic_driver = {
256 .name = "uclogic",
257 .id_table = uclogic_devices,
258 .probe = uclogic_probe,
Nikolai Kondrashov96142192019-02-10 12:13:51 +0200259 .remove = uclogic_remove,
Nikolai Kondrashovff0c13d2019-02-10 12:13:50 +0200260 .report_fixup = uclogic_report_fixup,
261 .raw_event = uclogic_raw_event,
262 .input_mapping = uclogic_input_mapping,
263 .input_configured = uclogic_input_configured,
264};
265module_hid_driver(uclogic_driver);
266
267MODULE_AUTHOR("Martin Rusko");
268MODULE_AUTHOR("Nikolai Kondrashov");
269MODULE_LICENSE("GPL");