Merge tag 'iwlwifi-next-for-kalle-2014-12-30' of https://git.kernel.org/pub/scm/linux...
[cascardo/linux.git] / drivers / hid / hid-huion.c
1 /*
2  *  HID driver for Huion devices not fully compliant with HID standard
3  *
4  *  Copyright (c) 2013 Martin Rusko
5  *  Copyright (c) 2014 Nikolai Kondrashov
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>
19 #include <asm/unaligned.h>
20 #include "usbhid/usbhid.h"
21
22 #include "hid-ids.h"
23
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
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
96 /* Driver data */
97 struct huion_drvdata {
98         __u8 *rdesc;
99         unsigned int rsize;
100 };
101
102 static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,
103                 unsigned int *rsize)
104 {
105         struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
106         switch (hdev->product) {
107         case USB_DEVICE_ID_HUION_TABLET:
108                 if (drvdata->rdesc != NULL) {
109                         rdesc = drvdata->rdesc;
110                         *rsize = drvdata->rsize;
111                 }
112                 break;
113         }
114         return rdesc;
115 }
116
117 /**
118  * Enable fully-functional tablet mode and determine device parameters.
119  *
120  * @hdev:       HID device
121  */
122 static int huion_tablet_enable(struct hid_device *hdev)
123 {
124         int rc;
125         struct usb_device *usb_dev = hid_to_usb_dev(hdev);
126         struct huion_drvdata *drvdata = hid_get_drvdata(hdev);
127         __le16 *buf = NULL;
128         size_t len;
129         s32 params[HUION_PH_ID_NUM];
130         s32 resolution;
131         __u8 *p;
132         s32 v;
133
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          */
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         }
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,
150                                 0x0409, buf, len,
151                                 USB_CTRL_GET_TIMEOUT);
152         if (rc == -EPIPE) {
153                 hid_err(hdev, "device parameters not found\n");
154                 rc = -ENODEV;
155                 goto cleanup;
156         } else if (rc < 0) {
157                 hid_err(hdev, "failed to get device parameters: %d\n", rc);
158                 rc = -ENODEV;
159                 goto cleanup;
160         } else if (rc != len) {
161                 hid_err(hdev, "invalid device parameters\n");
162                 rc = -ENODEV;
163                 goto cleanup;
164         }
165
166         /* Extract device parameters */
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]);
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");
188                 rc = -ENOMEM;
189                 goto cleanup;
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++;
205                 }
206         }
207
208         rc = 0;
209
210 cleanup:
211         kfree(buf);
212         return rc;
213 }
214
215 static int huion_probe(struct hid_device *hdev, const struct hid_device_id *id)
216 {
217         int rc;
218         struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
219         struct huion_drvdata *drvdata;
220
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;
226         }
227         hid_set_drvdata(hdev, drvdata);
228
229         switch (id->product) {
230         case USB_DEVICE_ID_HUION_TABLET:
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                         }
238                 }
239                 break;
240         }
241
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
254         return 0;
255 }
256
257 static int huion_raw_event(struct hid_device *hdev, struct hid_report *report,
258                         u8 *data, int size)
259 {
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 */
267                 data[1] ^= 0x40;
268
269         return 0;
270 }
271
272 static const struct hid_device_id huion_devices[] = {
273         { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
274         { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
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");