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