Skip to content

Commit 33a5c27

Browse files
jwrdegoedeJiri Kosina
authored andcommitted
HID: Add new Letsketch tablet driver
Add a new driver for the LetSketch / VSON WP9620N drawing tablet. This drawing tablet is also sold under other brand names such as Case U, presumably this driver will work for all of them. But it has only been tested with a LetSketch WP9620N model. These tablets also work without a special HID driver, but then only part of the active area works and both the pad and stylus buttons are hardwired to special key-combos. E.g. the 2 stylus buttons send right mouse clicks / resp. "e" key presses. BugLink: https://bugzilla.redhat.com/show_bug.cgi?id=2005575 BugLink: DIGImend/digimend-kernel-drivers#528 Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Jiri Kosina <jkosina@suse.cz>
1 parent 1c3e979 commit 33a5c27

5 files changed

Lines changed: 347 additions & 0 deletions

File tree

MAINTAINERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10745,6 +10745,13 @@ S: Maintained
1074510745
W: http://legousb.sourceforge.net/
1074610746
F: drivers/usb/misc/legousbtower.c
1074710747

10748+
LETSKETCH HID TABLET DRIVER
10749+
M: Hans de Goede <hdegoede@redhat.com>
10750+
L: linux-input@vger.kernel.org
10751+
S: Maintained
10752+
T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git
10753+
F: drivers/hid/hid-letsketch.c
10754+
1074810755
LG LAPTOP EXTRAS
1074910756
M: Matan Ziv-Av <matan@svgalib.org>
1075010757
L: platform-driver-x86@vger.kernel.org

drivers/hid/Kconfig

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,20 @@ config HID_LENOVO
558558
- ThinkPad Compact Bluetooth Keyboard with TrackPoint (supports Fn keys)
559559
- ThinkPad Compact USB Keyboard with TrackPoint (supports Fn keys)
560560

561+
config HID_LETSKETCH
562+
tristate "Letsketch WP9620N tablets"
563+
depends on USB_HID
564+
help
565+
Driver for the LetSketch / VSON WP9620N drawing tablet. This
566+
drawing tablet is also sold under other brand names such as Case U,
567+
presumably this driver will work for all of them. But it has only been
568+
tested with a LetSketch WP9620N model.
569+
570+
These tablets also work without a special HID driver, but then only
571+
part of the active area works and both the pad and stylus buttons are
572+
hardwired to special key-combos. E.g. the 2 stylus buttons send right
573+
mouse clicks / resp. "e" key presses.
574+
561575
config HID_LOGITECH
562576
tristate "Logitech devices"
563577
depends on USB_HID

drivers/hid/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o
6666
obj-$(CONFIG_HID_KYE) += hid-kye.o
6767
obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o
6868
obj-$(CONFIG_HID_LENOVO) += hid-lenovo.o
69+
obj-$(CONFIG_HID_LETSKETCH) += hid-letsketch.o
6970
obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o
7071
obj-$(CONFIG_HID_LOGITECH) += hid-lg-g15.o
7172
obj-$(CONFIG_HID_LOGITECH_DJ) += hid-logitech-dj.o

drivers/hid/hid-ids.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,9 @@
763763
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_602E 0x602e
764764
#define USB_DEVICE_ID_LENOVO_PIXART_USB_MOUSE_6093 0x6093
765765

766+
#define USB_VENDOR_ID_LETSKETCH 0x6161
767+
#define USB_DEVICE_ID_WP9620N 0x4d15
768+
766769
#define USB_VENDOR_ID_LG 0x1fd2
767770
#define USB_DEVICE_ID_LG_MULTITOUCH 0x0064
768771
#define USB_DEVICE_ID_LG_MELFAS_MT 0x6007

drivers/hid/hid-letsketch.c

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* Copyright (c) 2021 Hans de Goede <hdegoede@redhat.com>
4+
*
5+
* Driver for the LetSketch / VSON WP9620N drawing tablet.
6+
* This drawing tablet is also sold under other brand names such as Case U,
7+
* presumably this driver will work for all of them. But it has only been
8+
* tested with a LetSketch WP9620N model.
9+
*
10+
* These tablets also work without a special HID driver, but then only part
11+
* of the active area works and both the pad and stylus buttons are hardwired
12+
* to special key-combos. E.g. the 2 stylus buttons send right mouse clicks /
13+
* resp. "e" key presses.
14+
*
15+
* This device has 4 USB interfaces:
16+
*
17+
* Interface 0 EP 0x81 bootclass mouse, rdesc len 18, report id 0x08,
18+
* Application(ff00.0001)
19+
* This interface sends raw event input reports in a custom format, but only
20+
* after doing the special dance from letsketch_probe(). After enabling this
21+
* interface the other 3 interfaces are disabled.
22+
*
23+
* Interface 1 EP 0x82 bootclass mouse, rdesc len 83, report id 0x0a, Tablet
24+
* This interface sends absolute events for the pen, including pressure,
25+
* but only for some part of the active area due to special "aspect ratio"
26+
* correction and only half by default since it assumes it will be used
27+
* with a phone in portraid mode, while using the tablet in landscape mode.
28+
* Also stylus + pad button events are not reported here.
29+
*
30+
* Interface 2 EP 0x83 bootclass keybd, rdesc len 64, report id none, Std Kbd
31+
* This interfaces send various hard-coded key-combos for the pad buttons
32+
* and "e" keypresses for the 2nd stylus button
33+
*
34+
* Interface 3 EP 0x84 bootclass mouse, rdesc len 75, report id 0x01, Std Mouse
35+
* This reports right-click mouse-button events for the 1st stylus button
36+
*/
37+
#include <linux/device.h>
38+
#include <linux/input.h>
39+
#include <linux/hid.h>
40+
#include <linux/module.h>
41+
#include <linux/timer.h>
42+
#include <linux/usb.h>
43+
44+
#include <asm/unaligned.h>
45+
46+
#include "hid-ids.h"
47+
48+
#define LETSKETCH_RAW_IF 0
49+
50+
#define LETSKETCH_RAW_DATA_LEN 12
51+
#define LETSKETCH_RAW_REPORT_ID 8
52+
53+
#define LETSKETCH_PAD_BUTTONS 5
54+
55+
#define LETSKETCH_INFO_STR_IDX_BEGIN 0xc8
56+
#define LETSKETCH_INFO_STR_IDX_END 0xca
57+
58+
#define LETSKETCH_GET_STRING_RETRIES 5
59+
60+
struct letsketch_data {
61+
struct hid_device *hdev;
62+
struct input_dev *input_tablet;
63+
struct input_dev *input_tablet_pad;
64+
struct timer_list inrange_timer;
65+
};
66+
67+
static int letsketch_open(struct input_dev *dev)
68+
{
69+
struct letsketch_data *data = input_get_drvdata(dev);
70+
71+
return hid_hw_open(data->hdev);
72+
}
73+
74+
static void letsketch_close(struct input_dev *dev)
75+
{
76+
struct letsketch_data *data = input_get_drvdata(dev);
77+
78+
hid_hw_close(data->hdev);
79+
}
80+
81+
static struct input_dev *letsketch_alloc_input_dev(struct letsketch_data *data)
82+
{
83+
struct input_dev *input;
84+
85+
input = devm_input_allocate_device(&data->hdev->dev);
86+
if (!input)
87+
return NULL;
88+
89+
input->id.bustype = data->hdev->bus;
90+
input->id.vendor = data->hdev->vendor;
91+
input->id.product = data->hdev->product;
92+
input->id.version = data->hdev->bus;
93+
input->phys = data->hdev->phys;
94+
input->uniq = data->hdev->uniq;
95+
input->open = letsketch_open;
96+
input->close = letsketch_close;
97+
98+
input_set_drvdata(input, data);
99+
100+
return input;
101+
}
102+
103+
static int letsketch_setup_input_tablet(struct letsketch_data *data)
104+
{
105+
struct input_dev *input;
106+
107+
input = letsketch_alloc_input_dev(data);
108+
if (!input)
109+
return -ENOMEM;
110+
111+
input_set_abs_params(input, ABS_X, 0, 50800, 0, 0);
112+
input_set_abs_params(input, ABS_Y, 0, 31750, 0, 0);
113+
input_set_abs_params(input, ABS_PRESSURE, 0, 8192, 0, 0);
114+
input_abs_set_res(input, ABS_X, 240);
115+
input_abs_set_res(input, ABS_Y, 225);
116+
input_set_capability(input, EV_KEY, BTN_TOUCH);
117+
input_set_capability(input, EV_KEY, BTN_TOOL_PEN);
118+
input_set_capability(input, EV_KEY, BTN_STYLUS);
119+
input_set_capability(input, EV_KEY, BTN_STYLUS2);
120+
121+
/* All known brands selling this tablet use WP9620[N] as model name */
122+
input->name = "WP9620 Tablet";
123+
124+
data->input_tablet = input;
125+
126+
return input_register_device(data->input_tablet);
127+
}
128+
129+
static int letsketch_setup_input_tablet_pad(struct letsketch_data *data)
130+
{
131+
struct input_dev *input;
132+
int i;
133+
134+
input = letsketch_alloc_input_dev(data);
135+
if (!input)
136+
return -ENOMEM;
137+
138+
for (i = 0; i < LETSKETCH_PAD_BUTTONS; i++)
139+
input_set_capability(input, EV_KEY, BTN_0 + i);
140+
141+
/*
142+
* These are never send on the pad input_dev, but must be set
143+
* on the Pad to make udev / libwacom happy.
144+
*/
145+
input_set_abs_params(input, ABS_X, 0, 1, 0, 0);
146+
input_set_abs_params(input, ABS_Y, 0, 1, 0, 0);
147+
input_set_capability(input, EV_KEY, BTN_STYLUS);
148+
149+
input->name = "WP9620 Pad";
150+
151+
data->input_tablet_pad = input;
152+
153+
return input_register_device(data->input_tablet_pad);
154+
}
155+
156+
static void letsketch_inrange_timeout(struct timer_list *t)
157+
{
158+
struct letsketch_data *data = from_timer(data, t, inrange_timer);
159+
struct input_dev *input = data->input_tablet;
160+
161+
input_report_key(input, BTN_TOOL_PEN, 0);
162+
input_sync(input);
163+
}
164+
165+
static int letsketch_raw_event(struct hid_device *hdev,
166+
struct hid_report *report,
167+
u8 *raw_data, int size)
168+
{
169+
struct letsketch_data *data = hid_get_drvdata(hdev);
170+
struct input_dev *input;
171+
int i;
172+
173+
if (size != LETSKETCH_RAW_DATA_LEN || raw_data[0] != LETSKETCH_RAW_REPORT_ID)
174+
return 0;
175+
176+
switch (raw_data[1] & 0xf0) {
177+
case 0x80: /* Pen data */
178+
input = data->input_tablet;
179+
input_report_key(input, BTN_TOOL_PEN, 1);
180+
input_report_key(input, BTN_TOUCH, raw_data[1] & 0x01);
181+
input_report_key(input, BTN_STYLUS, raw_data[1] & 0x02);
182+
input_report_key(input, BTN_STYLUS2, raw_data[1] & 0x04);
183+
input_report_abs(input, ABS_X,
184+
get_unaligned_le16(raw_data + 2));
185+
input_report_abs(input, ABS_Y,
186+
get_unaligned_le16(raw_data + 4));
187+
input_report_abs(input, ABS_PRESSURE,
188+
get_unaligned_le16(raw_data + 6));
189+
/*
190+
* There is no out of range event, so use a timer for this
191+
* when in range we get an event approx. every 8 ms.
192+
*/
193+
mod_timer(&data->inrange_timer, jiffies + msecs_to_jiffies(100));
194+
break;
195+
case 0xe0: /* Pad data */
196+
input = data->input_tablet_pad;
197+
for (i = 0; i < LETSKETCH_PAD_BUTTONS; i++)
198+
input_report_key(input, BTN_0 + i, raw_data[4] == (i + 1));
199+
break;
200+
default:
201+
hid_warn(data->hdev, "Warning unknown data header: 0x%02x\n",
202+
raw_data[0]);
203+
return 0;
204+
}
205+
206+
input_sync(input);
207+
return 0;
208+
}
209+
210+
/*
211+
* The tablets magic handshake to put it in raw mode relies on getting
212+
* string descriptors. But the firmware is buggy and does not like it if
213+
* we do this too fast. Even if we go slow sometimes the usb_string() call
214+
* fails. Ignore errors and retry it a couple of times if necessary.
215+
*/
216+
static int letsketch_get_string(struct usb_device *udev, int index, char *buf, int size)
217+
{
218+
int i, ret;
219+
220+
for (i = 0; i < LETSKETCH_GET_STRING_RETRIES; i++) {
221+
usleep_range(5000, 7000);
222+
ret = usb_string(udev, index, buf, size);
223+
if (ret > 0)
224+
return 0;
225+
}
226+
227+
dev_err(&udev->dev, "Max retries (%d) exceeded reading string descriptor %d\n",
228+
LETSKETCH_GET_STRING_RETRIES, index);
229+
return ret ? ret : -EIO;
230+
}
231+
232+
static int letsketch_probe(struct hid_device *hdev, const struct hid_device_id *id)
233+
{
234+
struct device *dev = &hdev->dev;
235+
struct letsketch_data *data;
236+
struct usb_interface *intf;
237+
struct usb_device *udev;
238+
char buf[256];
239+
int i, ret;
240+
241+
if (!hid_is_using_ll_driver(hdev, &usb_hid_driver))
242+
return -ENODEV;
243+
244+
intf = to_usb_interface(hdev->dev.parent);
245+
if (intf->altsetting->desc.bInterfaceNumber != LETSKETCH_RAW_IF)
246+
return -ENODEV; /* Ignore the other interfaces */
247+
248+
udev = interface_to_usbdev(intf);
249+
250+
/*
251+
* Instead of using a set-feature request, or even a custom USB ctrl
252+
* message the tablet needs this elaborate magic reading of USB
253+
* string descriptors to kick it into raw mode. This is what the
254+
* Windows drivers are seen doing in an USB trace under Windows.
255+
*/
256+
for (i = LETSKETCH_INFO_STR_IDX_BEGIN; i <= LETSKETCH_INFO_STR_IDX_END; i++) {
257+
ret = letsketch_get_string(udev, i, buf, sizeof(buf));
258+
if (ret)
259+
return ret;
260+
261+
hid_info(hdev, "Device info: %s\n", buf);
262+
}
263+
264+
for (i = 1; i <= 250; i++) {
265+
ret = letsketch_get_string(udev, i, buf, sizeof(buf));
266+
if (ret)
267+
return ret;
268+
}
269+
270+
ret = letsketch_get_string(udev, 0x64, buf, sizeof(buf));
271+
if (ret)
272+
return ret;
273+
274+
ret = letsketch_get_string(udev, LETSKETCH_INFO_STR_IDX_BEGIN, buf, sizeof(buf));
275+
if (ret)
276+
return ret;
277+
278+
/*
279+
* The tablet should be in raw mode now, end with a final delay before
280+
* doing further IO to the device.
281+
*/
282+
usleep_range(5000, 7000);
283+
284+
ret = hid_parse(hdev);
285+
if (ret)
286+
return ret;
287+
288+
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
289+
if (!data)
290+
return -ENOMEM;
291+
292+
data->hdev = hdev;
293+
timer_setup(&data->inrange_timer, letsketch_inrange_timeout, 0);
294+
hid_set_drvdata(hdev, data);
295+
296+
ret = letsketch_setup_input_tablet(data);
297+
if (ret)
298+
return ret;
299+
300+
ret = letsketch_setup_input_tablet_pad(data);
301+
if (ret)
302+
return ret;
303+
304+
return hid_hw_start(hdev, HID_CONNECT_HIDRAW);
305+
}
306+
307+
static const struct hid_device_id letsketch_devices[] = {
308+
{ HID_USB_DEVICE(USB_VENDOR_ID_LETSKETCH, USB_DEVICE_ID_WP9620N) },
309+
{ }
310+
};
311+
MODULE_DEVICE_TABLE(hid, letsketch_devices);
312+
313+
static struct hid_driver letsketch_driver = {
314+
.name = "letsketch",
315+
.id_table = letsketch_devices,
316+
.probe = letsketch_probe,
317+
.raw_event = letsketch_raw_event,
318+
};
319+
module_hid_driver(letsketch_driver);
320+
321+
MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
322+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)