From: Stefan Berzl <stefanberzl@gmail.com>
To: linux-input@vger.kernel.org
Subject: [PATCH] hid: Add support for the xp-pen deco mini7 tablet
Date: Fri, 18 Mar 2022 19:37:41 +0100 [thread overview]
Message-ID: <b401e453-9c66-15e3-1a1d-21f33b7a64e8@gmail.com> (raw)
From f9bb82e400effd3aea37b2be710add9e2bb832da Mon Sep 17 00:00:00 2001
From: Stefan Berzl <stefanberzl@gmail.com>
Date: Fri, 11 Mar 2022 04:04:30 +0100
Subject: [PATCH] hid: Add support for the xp-pen deco mini7 tablet
This device is a bit special in that normal reporting of buttons needs to
be activated through sending a special key packet to a vendor defined
usage (and page). Then we receive sorta an ack packet which must be
caught to prevent it from registering as a button input. The person who
sniffed this out says that a lot of newer xppen tablets act like this,
so I try to make the code reusable and add "xppen_new" to the names.
Signed-off-by: Stefan Berzl <stefanberzl@gmail.com>
---
drivers/hid/hid-ids.h | 1 +
drivers/hid/hid-uclogic-core.c | 2 +
drivers/hid/hid-uclogic-params.c | 239 +++++++++++++++++++++++++++++--
drivers/hid/hid-uclogic-rdesc.c | 96 +++++++++++++
drivers/hid/hid-uclogic-rdesc.h | 14 ++
5 files changed, 343 insertions(+), 9 deletions(-)
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 78bd3ddda442..8d4e28da4449 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -1260,6 +1260,7 @@
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540 0x0075
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640 0x0094
#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01 0x0042
+#define USB_DEVICE_ID_UGEE_XPPEN_TABLET_MINI7 0x0928
#define USB_DEVICE_ID_UGEE_TABLET_G5 0x0074
#define USB_DEVICE_ID_UGEE_TABLET_EX07S 0x0071
#define USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720 0x0055
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
index d8ab0139e5cd..dc76c0424289 100644
--- a/drivers/hid/hid-uclogic-core.c
+++ b/drivers/hid/hid-uclogic-core.c
@@ -404,6 +404,8 @@ static const struct hid_device_id uclogic_devices[] = {
USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_MINI7) },
{ }
};
MODULE_DEVICE_TABLE(hid, uclogic_devices);
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index 3e70f969fb84..81087e6a0700 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -122,8 +122,9 @@ static void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen)
}
/**
- * uclogic_params_pen_init_v1() - initialize tablet interface pen
- * input and retrieve its parameters from the device, using v1 protocol.
+ * uclogic_params_pen_init_v1_with_desc() - initialize tablet interface pen
+ * input and retrieve its parameters from the device, using v1 protocol
+ * and a given report descriptor.
*
* @pen: Pointer to the pen parameters to initialize (to be
* cleaned up with uclogic_params_pen_cleanup()). Not modified in
@@ -133,13 +134,20 @@ static void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen)
* incompatible). Not modified in case of error. Cannot be NULL.
* @hdev: The HID device of the tablet interface to initialize and get
* parameters from. Cannot be NULL.
+ * @desc_ptr: Report descriptor pointer.
+ * @desc_size: Report descriptor size.
+ * @id: Report ID used for frame reports, if they should be tweaked,
+ * zero if not.
*
* Returns:
* Zero, if successful. A negative errno code on error.
*/
-static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
- bool *pfound,
- struct hid_device *hdev)
+static int uclogic_params_pen_init_v1_with_desc(struct uclogic_params_pen *pen,
+ bool *pfound,
+ struct hid_device *hdev,
+ const __u8 *template_desc_ptr,
+ size_t template_desc_size,
+ unsigned int template_id)
{
int rc;
bool found = false;
@@ -207,8 +215,7 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
* Generate pen report descriptor
*/
desc_ptr = uclogic_rdesc_template_apply(
- uclogic_rdesc_pen_v1_template_arr,
- uclogic_rdesc_pen_v1_template_size,
+ template_desc_ptr, template_desc_size,
desc_params, ARRAY_SIZE(desc_params));
if (desc_ptr == NULL) {
rc = -ENOMEM;
@@ -221,8 +228,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
memset(pen, 0, sizeof(*pen));
pen->desc_ptr = desc_ptr;
desc_ptr = NULL;
- pen->desc_size = uclogic_rdesc_pen_v1_template_size;
- pen->id = UCLOGIC_RDESC_PEN_V1_ID;
+ pen->desc_size = template_desc_size;
+ pen->id = template_id;
pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED;
found = true;
finish:
@@ -234,6 +241,33 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
return rc;
}
+/**
+ * uclogic_params_pen_init_v1() - initialize tablet interface pen
+ * input and retrieve its parameters from the device, using v1 protocol.
+ *
+ * @pen: Pointer to the pen parameters to initialize (to be
+ * cleaned up with uclogic_params_pen_cleanup()). Not modified in
+ * case of error, or if parameters are not found. Cannot be NULL.
+ * @pfound: Location for a flag which is set to true if the parameters
+ * were found, and to false if not (e.g. device was
+ * incompatible). Not modified in case of error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
+ bool *pfound,
+ struct hid_device *hdev)
+{
+ return uclogic_params_pen_init_v1_with_desc(
+ pen, pfound, hdev,
+ uclogic_rdesc_pen_v1_template_arr,
+ uclogic_rdesc_pen_v1_template_size,
+ UCLOGIC_RDESC_PEN_V1_ID);
+}
+
/**
* uclogic_params_get_le24() - get a 24-bit little-endian number from a
* buffer.
@@ -824,6 +858,187 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
return rc;
}
+/**
+ * uclogic_xppen_new_snd_urb_completion() - The urb completion function for
+ * sending a key to newer xppen tablets.
+ *
+ * @urb: The urb that has been completed.
+ */
+void uclogic_xppen_new_snd_urb_completion(struct urb *urb)
+{
+ struct hid_device *hdev = urb->context;
+
+ if (urb->status != 0)
+ hid_err(hdev, "sending the xppen key failed, tablet may not work: %d\n", urb->status);
+}
+
+/**
+ * uclogic_xppen_new_rcv_urb_completion() - The urb completion function for
+ * receiving a response after sending a key to newer xppen tablets.
+ *
+ * @urb: The urb that has been completed.
+ */
+void uclogic_xppen_new_rcv_urb_completion(struct urb *urb)
+{
+ struct hid_device *hdev = urb->context;
+
+ if (urb->status != 0)
+ hid_err(hdev, "receiving the xppen key failed, tablet may not work: %d\n", urb->status);
+}
+
+/**
+ * uclogic_xppen_new_send_key_urb() - Send a certain vendor defined key to the
+ * tablet to switch reports to a more useful structure. Also the tablet will
+ * send a response after receiving the key on the same interface and endpoint
+ * that pen and button reports are sent henceforth. So we need to catch
+ * this first report.
+ *
+ * @hdev: The HID device of the tablet interface.
+ * @key_ptr: Pointer to the key.
+ * @key_size: Size of the key.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+int uclogic_xppen_new_send_key_urb(struct hid_device *hdev,
+ __u8 *key_ptr,
+ unsigned int key_size)
+{
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+ struct urb *sndUrb = NULL;
+ struct urb *rcvUrb = NULL;
+ __u8 *sndBuf = NULL;
+ __u8 *rcvBuf = NULL;
+ int rc;
+
+ sndBuf = devm_kzalloc(&hdev->dev, key_size, GFP_KERNEL);
+ rcvBuf = devm_kzalloc(&hdev->dev, 12, GFP_KERNEL);
+ if (sndBuf == NULL || rcvBuf == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ sndUrb = usb_alloc_urb(0, GFP_KERNEL);
+ rcvUrb = usb_alloc_urb(0, GFP_KERNEL);
+ if (sndUrb == NULL || rcvUrb == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ memcpy(sndBuf, key_ptr, key_size);
+ usb_fill_int_urb(sndUrb, udev,
+ usb_sndintpipe(udev, 3),
+ sndBuf, key_size,
+ uclogic_xppen_new_snd_urb_completion, hdev,
+ 10);
+
+ /*
+ * TODO: Setting a low interval value in the receive interrupt
+ * will slow down the interval persistently and make the device
+ * less responsive, even though it's only used once for the initial
+ * tablet-key response. Is there a workaround for this?
+ * This is the correct value though, so everything is fine.
+ */
+ usb_fill_int_urb(rcvUrb, udev,
+ usb_rcvintpipe(udev, 3),
+ rcvBuf, 12,
+ uclogic_xppen_new_rcv_urb_completion, hdev,
+ 3);
+
+ rc = usb_submit_urb(rcvUrb, GFP_KERNEL);
+ if (rc != 0)
+ goto cleanup;
+
+ rc = usb_submit_urb(sndUrb, GFP_KERNEL);
+
+cleanup:
+ usb_free_urb(sndUrb);
+ usb_free_urb(rcvUrb);
+ return rc;
+}
+
+/**
+ * uclogic_params_xppen_new_init() - initialize a newer xppen tablet interface
+ * and discover its parameters.
+ *
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_xppen_new_init(struct uclogic_params *params,
+ struct hid_device *hdev)
+{
+ struct uclogic_params p = {0, };
+ bool found;
+ struct usb_device *udev;
+ struct usb_interface *iface;
+ __u8 bInterfaceNumber;
+ int rc;
+ __u8 key[] = {
+ 0x02, 0xB0, 0x04, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ udev = hid_to_usb_dev(hdev);
+ iface = to_usb_interface(hdev->dev.parent);
+ bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+
+ /* If it's not the vendor interface */
+ if (bInterfaceNumber != 2) {
+ uclogic_params_init_invalid(&p);
+ goto output;
+ }
+
+ rc = uclogic_xppen_new_send_key_urb(hdev, key, ARRAY_SIZE(key));
+ if (rc != 0) {
+ hid_err(hdev, "transmission of xppen key failed: %d\n", rc);
+ goto cleanup;
+ }
+
+ rc = uclogic_params_pen_init_v1_with_desc(
+ &p.pen, &found, hdev,
+ uclogic_rdesc_xppen_new_pen_arr,
+ uclogic_rdesc_xppen_new_pen_size,
+ UCLOGIC_RDESC_XPPEN_NEW_PEN_ID);
+
+ if (rc != 0) {
+ hid_err(hdev, "pen probing failed: %d\n", rc);
+ goto cleanup;
+ }
+
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame,
+ uclogic_rdesc_xppen_new_frame_arr,
+ uclogic_rdesc_xppen_new_frame_size,
+ UCLOGIC_RDESC_XPPEN_NEW_FRAME_ID);
+
+ if (rc != 0) {
+ hid_err(hdev, "failed creating buttonpad parameters: %d\n", rc);
+ goto cleanup;
+ }
+
+ p.pen_frame_flag = 0x10;
+
+ if (!found) {
+ hid_warn(hdev, "pen parameters not found");
+ uclogic_params_init_invalid(&p);
+ }
+
+output:
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+ rc = 0;
+cleanup:
+ uclogic_params_cleanup(&p);
+ return rc;
+}
+
/**
* uclogic_params_init() - initialize a tablet interface and discover its
* parameters.
@@ -1059,6 +1274,12 @@ int uclogic_params_init(struct uclogic_params *params,
uclogic_params_init_with_pen_unused(&p);
}
break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_MINI7):
+ rc = uclogic_params_xppen_new_init(&p, hdev);
+ if (rc != 0)
+ goto cleanup;
+ break;
case VID_PID(USB_VENDOR_ID_TRUST,
USB_DEVICE_ID_TRUST_PANORA_TABLET):
case VID_PID(USB_VENDOR_ID_UGEE,
diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c
index 6dd6dcd09c8b..4bfbaa0ca99f 100644
--- a/drivers/hid/hid-uclogic-rdesc.c
+++ b/drivers/hid/hid-uclogic-rdesc.c
@@ -817,6 +817,102 @@ const __u8 uclogic_rdesc_xppen_deco01_frame_arr[] = {
const size_t uclogic_rdesc_xppen_deco01_frame_size =
sizeof(uclogic_rdesc_xppen_deco01_frame_arr);
+/* pen report descriptor for newer xppen tablets */
+__u8 uclogic_rdesc_xppen_new_pen_arr[] = {
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x02, /* Usage (Pen), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0x02, /* Report ID (2), */
+ 0x09, 0x20, /* Usage (Stylus), */
+ 0xA0, /* Collection (Physical), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x09, 0x42, /* Usage (Tip Switch), */
+ 0x09, 0x44, /* Usage (Barrel Switch), */
+ 0x09, 0x46, /* Usage (Tablet Pick), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x03, /* Report Count (3), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x09, 0x32, /* Usage (In Range), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0xA4, /* Push, */
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x65, 0x13, /* Unit (Inch), */
+ 0x55, 0xFD, /* Unit Exponent (-3), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x34, /* Physical Minimum (0), */
+ 0x09, 0x30, /* Usage (X), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(X_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(X_PM),
+ /* Physical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x09, 0x31, /* Usage (Y), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM),
+ /* Physical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xB4, /* Pop, */
+ 0x09, 0x30, /* Usage (Tip Pressure), */
+ 0x75, 0x10, /* Report Size (16), */
+ 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM),
+ /* Logical Maximum (PLACEHOLDER), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x54, /* Unit Exponent (0), */
+ 0x65, 0x14, /* Unit (Degrees), */
+ 0x35, 0xC4, /* Physical Minimum (-60), */
+ 0x45, 0x3C, /* Physical Maximum (60), */
+ 0x15, 0xC4, /* Logical Minimum (-60), */
+ 0x25, 0x3C, /* Logical Maximum (60), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x09, 0x3D, /* Usage (X Tilt), */
+ 0x09, 0x3E, /* Usage (Y Tilt), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x02, /* Report Count (2), */
+ 0x81, 0x03, /* Input (Constant, Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
+};
+
+const size_t uclogic_rdesc_xppen_new_pen_size =
+ sizeof(uclogic_rdesc_xppen_new_pen_arr);
+
+/* button report descriptor for newer xppen tablets */
+__u8 uclogic_rdesc_xppen_new_frame_arr[] = {
+ 0x05, 0x01, /* Usage Page (Desktop), */
+ 0x09, 0x07, /* Usage (Keypad), */
+ 0xA1, 0x01, /* Collection (Application), */
+ 0x85, 0xf7, /* Report ID (247), */
+ 0x14, /* Logical Minimum (0), */
+ 0x25, 0x01, /* Logical Maximum (1), */
+ 0x05, 0x0D, /* Usage Page (Digitizer), */
+ 0x09, 0x39, /* Usage (Tablet Function Keys), */
+ 0xA0, /* Collection (Physical), */
+ 0x75, 0x08, /* Report Size (8), */
+ 0x95, 0x01, /* Report Count (1), */
+ 0x81, 0x01, /* Input (Constant), */
+ 0x75, 0x01, /* Report Size (1), */
+ 0x05, 0x09, /* Usage Page (Button), */
+ 0x19, 0x01, /* Usage Minimum (01h), */
+ 0x29, 0x08, /* Usage Maximum (08h), */
+ 0x95, 0x08, /* Report Count (8), */
+ 0x81, 0x02, /* Input (Variable), */
+ 0xC0, /* End Collection, */
+ 0xC0 /* End Collection */
+};
+
+const size_t uclogic_rdesc_xppen_new_frame_size =
+ sizeof(uclogic_rdesc_xppen_new_frame_arr);
+
/**
* uclogic_rdesc_template_apply() - apply report descriptor parameters to a
* report descriptor template, creating a report descriptor. Copies the
diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h
index c5da51055af3..1405a4ccffce 100644
--- a/drivers/hid/hid-uclogic-rdesc.h
+++ b/drivers/hid/hid-uclogic-rdesc.h
@@ -139,6 +139,20 @@ extern const size_t uclogic_rdesc_ugee_ex07_buttonpad_size;
extern const __u8 uclogic_rdesc_xppen_deco01_frame_arr[];
extern const size_t uclogic_rdesc_xppen_deco01_frame_size;
+/* Report ID for newer xppen pen reports (only mini7 now) */
+#define UCLOGIC_RDESC_XPPEN_NEW_PEN_ID 0x02
+
+/* Report ID for newer xppen frame reports (only mini7 now) */
+#define UCLOGIC_RDESC_XPPEN_NEW_FRAME_ID 0xf7
+
+/* Fixed report descriptor for newer xppen tablet pens (only mini7 now) */
+extern __u8 uclogic_rdesc_xppen_new_pen_arr[];
+extern const size_t uclogic_rdesc_xppen_new_pen_size;
+
+/* Fixed report desc for newer xppen tablet frame controls (only mini7 now) */
+extern __u8 uclogic_rdesc_xppen_new_frame_arr[];
+extern const size_t uclogic_rdesc_xppen_new_frame_size;
+
/* Fixed report descriptor for Ugee G5 frame controls */
extern const __u8 uclogic_rdesc_ugee_g5_frame_arr[];
extern const size_t uclogic_rdesc_ugee_g5_frame_size;
--
2.35.1
next reply other threads:[~2022-03-18 18:37 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-03-18 18:37 Stefan Berzl [this message]
[not found] <85312611-797f-2dd2-f864-f7c13cb889f9@gmail.com>
[not found] ` <nycvar.YFH.7.76.2206091058530.14340@cbobk.fhfr.pm>
2022-06-22 21:31 ` [PATCH] hid: Add support for the xp-pen deco mini7 tablet Stefan Berzl
2022-06-23 17:51 ` José Expósito
2022-06-23 18:01 ` Nikolai Kondrashov
2022-06-23 22:46 ` Stefan Berzl
2022-06-24 6:08 ` Nikolai Kondrashov
2022-06-24 6:08 ` Nikolai Kondrashov
2022-06-25 15:24 ` José Expósito
2022-06-23 22:24 ` Stefan Berzl
2022-06-25 15:30 ` José Expósito
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=b401e453-9c66-15e3-1a1d-21f33b7a64e8@gmail.com \
--to=stefanberzl@gmail.com \
--cc=linux-input@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).