// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2021 Hans de Goede <hdegoede@redhat.com> * * 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. * * This device has 4 USB interfaces: * * Interface 0 EP 0x81 bootclass mouse, rdesc len 18, report id 0x08, * Application(ff00.0001) * This interface sends raw event input reports in a custom format, but only * after doing the special dance from letsketch_probe(). After enabling this * interface the other 3 interfaces are disabled. * * Interface 1 EP 0x82 bootclass mouse, rdesc len 83, report id 0x0a, Tablet * This interface sends absolute events for the pen, including pressure, * but only for some part of the active area due to special "aspect ratio" * correction and only half by default since it assumes it will be used * with a phone in portraid mode, while using the tablet in landscape mode. * Also stylus + pad button events are not reported here. * * Interface 2 EP 0x83 bootclass keybd, rdesc len 64, report id none, Std Kbd * This interfaces send various hard-coded key-combos for the pad buttons * and "e" keypresses for the 2nd stylus button * * Interface 3 EP 0x84 bootclass mouse, rdesc len 75, report id 0x01, Std Mouse * This reports right-click mouse-button events for the 1st stylus button
*/ #include <linux/device.h> #include <linux/input.h> #include <linux/hid.h> #include <linux/module.h> #include <linux/timer.h> #include <linux/usb.h>
staticint letsketch_setup_input_tablet_pad(struct letsketch_data *data)
{ struct input_dev *input; int i;
input = letsketch_alloc_input_dev(data); if (!input) return -ENOMEM;
for (i = 0; i < LETSKETCH_PAD_BUTTONS; i++)
input_set_capability(input, EV_KEY, BTN_0 + i);
/* * These are never send on the pad input_dev, but must be set * on the Pad to make udev / libwacom happy.
*/
input_set_abs_params(input, ABS_X, 0, 1, 0, 0);
input_set_abs_params(input, ABS_Y, 0, 1, 0, 0);
input_set_capability(input, EV_KEY, BTN_STYLUS);
if (size != LETSKETCH_RAW_DATA_LEN || raw_data[0] != LETSKETCH_RAW_REPORT_ID) return 0;
switch (raw_data[1] & 0xf0) { case 0x80: /* Pen data */
input = data->input_tablet;
input_report_key(input, BTN_TOOL_PEN, 1);
input_report_key(input, BTN_TOUCH, raw_data[1] & 0x01);
input_report_key(input, BTN_STYLUS, raw_data[1] & 0x02);
input_report_key(input, BTN_STYLUS2, raw_data[1] & 0x04);
input_report_abs(input, ABS_X,
get_unaligned_le16(raw_data + 2));
input_report_abs(input, ABS_Y,
get_unaligned_le16(raw_data + 4));
input_report_abs(input, ABS_PRESSURE,
get_unaligned_le16(raw_data + 6)); /* * There is no out of range event, so use a timer for this * when in range we get an event approx. every 8 ms.
*/
mod_timer(&data->inrange_timer, jiffies + msecs_to_jiffies(100)); break; case 0xe0: /* Pad data */
input = data->input_tablet_pad; for (i = 0; i < LETSKETCH_PAD_BUTTONS; i++)
input_report_key(input, BTN_0 + i, raw_data[4] == (i + 1)); break; default:
hid_warn(data->hdev, "Warning unknown data header: 0x%02x\n",
raw_data[0]); return 0;
}
input_sync(input); return 0;
}
/* * The tablets magic handshake to put it in raw mode relies on getting * string descriptors. But the firmware is buggy and does not like it if * we do this too fast. Even if we go slow sometimes the usb_string() call * fails. Ignore errors and retry it a couple of times if necessary.
*/ staticint letsketch_get_string(struct usb_device *udev, int index, char *buf, int size)
{ int i, ret;
for (i = 0; i < LETSKETCH_GET_STRING_RETRIES; i++) {
usleep_range(5000, 7000);
ret = usb_string(udev, index, buf, size); if (ret > 0) return 0;
}
dev_err(&udev->dev, "Max retries (%d) exceeded reading string descriptor %d\n",
LETSKETCH_GET_STRING_RETRIES, index); return ret ? ret : -EIO;
}
intf = to_usb_interface(hdev->dev.parent); if (intf->altsetting->desc.bInterfaceNumber != LETSKETCH_RAW_IF) return -ENODEV; /* Ignore the other interfaces */
udev = interface_to_usbdev(intf);
/* * Instead of using a set-feature request, or even a custom USB ctrl * message the tablet needs this elaborate magic reading of USB * string descriptors to kick it into raw mode. This is what the * Windows drivers are seen doing in an USB trace under Windows.
*/ for (i = LETSKETCH_INFO_STR_IDX_BEGIN; i <= LETSKETCH_INFO_STR_IDX_END; i++) {
ret = letsketch_get_string(udev, i, buf, sizeof(buf)); if (ret) return ret;
hid_info(hdev, "Device info: %s\n", buf);
}
for (i = 1; i <= 250; i++) {
ret = letsketch_get_string(udev, i, buf, sizeof(buf)); if (ret) return ret;
}
ret = letsketch_get_string(udev, 0x64, buf, sizeof(buf)); if (ret) return ret;
ret = letsketch_get_string(udev, LETSKETCH_INFO_STR_IDX_BEGIN, buf, sizeof(buf)); if (ret) return ret;
/* * The tablet should be in raw mode now, end with a final delay before * doing further IO to the device.
*/
usleep_range(5000, 7000);
ret = hid_parse(hdev); if (ret) return ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.