HID: uclogic: Support v2 protocol
authorNikolai Kondrashov <spbnick@gmail.com>
Sun, 10 Feb 2019 10:13:56 +0000 (12:13 +0200)
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>
Thu, 21 Feb 2019 11:00:53 +0000 (12:00 +0100)
Add support for UC-Logic v2 protocol to hid-uclogic.
This adds support for a bunch of new Huion models.

Signed-off-by: Nikolai Kondrashov <spbnick@gmail.com>
Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
drivers/hid/hid-uclogic-params.c
drivers/hid/hid-uclogic-rdesc.c
drivers/hid/hid-uclogic-rdesc.h

index b5e4d99..132663a 100644 (file)
@@ -232,6 +232,151 @@ cleanup:
 }
 
 /**
+ * uclogic_params_get_le24() - get a 24-bit little-endian number from a
+ * buffer.
+ *
+ * @p: The pointer to the number buffer.
+ *
+ * Returns:
+ *     The retrieved number
+ */
+static s32 uclogic_params_get_le24(const void *p)
+{
+       const __u8 *b = p;
+       return b[0] | (b[1] << 8UL) | (b[2] << 16UL);
+}
+
+/**
+ * uclogic_params_pen_init_v2() - initialize tablet interface pen
+ * input and retrieve its parameters from the device, using v2 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_v2(struct uclogic_params_pen *pen,
+                                       bool *pfound,
+                                       struct hid_device *hdev)
+{
+       int rc;
+       bool found = false;
+       /* Buffer for (part of) the string descriptor */
+       __u8 *buf = NULL;
+       /* Descriptor length required */
+       const int len = 18;
+       s32 resolution;
+       /* Pen report descriptor template parameters */
+       s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
+       __u8 *desc_ptr = NULL;
+
+       /* Check arguments */
+       if (pen == NULL || pfound == NULL || hdev == NULL) {
+               rc = -EINVAL;
+               goto cleanup;
+       }
+
+       /*
+        * Read string descriptor containing pen input parameters.
+        * The specific string descriptor and data were discovered by sniffing
+        * the Windows driver traffic.
+        * NOTE: This enables fully-functional tablet mode.
+        */
+       rc = uclogic_params_get_str_desc(&buf, hdev, 200, len);
+       if (rc == -EPIPE) {
+               hid_dbg(hdev,
+                       "string descriptor with pen parameters not found, assuming not compatible\n");
+               goto finish;
+       } else if (rc < 0) {
+               hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
+               goto cleanup;
+       } else if (rc != len) {
+               hid_dbg(hdev,
+                       "string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
+                       rc, len);
+               goto finish;
+       } else {
+               size_t i;
+               /*
+                * Check it's not just a catch-all UTF-16LE-encoded ASCII
+                * string (such as the model name) some tablets put into all
+                * unknown string descriptors.
+                */
+               for (i = 2;
+                    i < len &&
+                       (buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0);
+                    i += 2);
+               if (i >= len) {
+                       hid_dbg(hdev,
+                               "string descriptor with pen parameters seems to contain only text, assuming not compatible\n");
+                       goto finish;
+               }
+       }
+
+       /*
+        * Fill report descriptor parameters from the string descriptor
+        */
+       desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
+               uclogic_params_get_le24(buf + 2);
+       desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
+               uclogic_params_get_le24(buf + 5);
+       desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
+               get_unaligned_le16(buf + 8);
+       resolution = get_unaligned_le16(buf + 10);
+       if (resolution == 0) {
+               desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
+               desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
+       } else {
+               desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
+                       desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
+                       resolution;
+               desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
+                       desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
+                       resolution;
+       }
+       kfree(buf);
+       buf = NULL;
+
+       /*
+        * Generate pen report descriptor
+        */
+       desc_ptr = uclogic_rdesc_template_apply(
+                               uclogic_rdesc_pen_v2_template_arr,
+                               uclogic_rdesc_pen_v2_template_size,
+                               desc_params, ARRAY_SIZE(desc_params));
+       if (desc_ptr == NULL) {
+               rc = -ENOMEM;
+               goto cleanup;
+       }
+
+       /*
+        * Fill-in the parameters
+        */
+       memset(pen, 0, sizeof(*pen));
+       pen->desc_ptr = desc_ptr;
+       desc_ptr = NULL;
+       pen->desc_size = uclogic_rdesc_pen_v2_template_size;
+       pen->id = UCLOGIC_RDESC_PEN_V2_ID;
+       pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE;
+       pen->fragmented_hires = true;
+       found = true;
+finish:
+       *pfound = found;
+       rc = 0;
+cleanup:
+       kfree(desc_ptr);
+       kfree(buf);
+       return rc;
+}
+
+/**
  * uclogic_params_frame_cleanup - free resources used by struct
  * uclogic_params_frame (tablet interface's frame controls input parameters).
  * Can be called repeatedly.
@@ -560,11 +705,15 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
                                     struct hid_device *hdev)
 {
        int rc;
+       struct usb_device *udev = hid_to_usb_dev(hdev);
        struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
        __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
        bool found;
        /* The resulting parameters (noop) */
        struct uclogic_params p = {0, };
+       static const char transition_ver[] = "HUION_T153_160607";
+       char *ver_ptr = NULL;
+       const size_t ver_len = sizeof(transition_ver) + 1;
 
        /* Check arguments */
        if (params == NULL || hdev == NULL) {
@@ -579,6 +728,57 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
                goto output;
        }
 
+       /* Try to get firmware version */
+       ver_ptr = kzalloc(ver_len, GFP_KERNEL);
+       if (ver_ptr == NULL) {
+               rc = -ENOMEM;
+               goto cleanup;
+       }
+       rc = usb_string(udev, 201, ver_ptr, ver_len);
+       if (ver_ptr == NULL) {
+               rc = -ENOMEM;
+               goto cleanup;
+       }
+       if (rc == -EPIPE) {
+               *ver_ptr = '\0';
+       } else if (rc < 0) {
+               hid_err(hdev,
+                       "failed retrieving Huion firmware version: %d\n", rc);
+               goto cleanup;
+       }
+
+       /* If this is a transition firmware */
+       if (strcmp(ver_ptr, transition_ver) == 0) {
+               hid_dbg(hdev,
+                       "transition firmware detected, not probing pen v2 parameters\n");
+       } else {
+               /* Try to probe v2 pen parameters */
+               rc = uclogic_params_pen_init_v2(&p.pen, &found, hdev);
+               if (rc != 0) {
+                       hid_err(hdev,
+                               "failed probing pen v2 parameters: %d\n", rc);
+                       goto cleanup;
+               } else if (found) {
+                       hid_dbg(hdev, "pen v2 parameters found\n");
+                       /* Create v2 buttonpad parameters */
+                       rc = uclogic_params_frame_init_with_desc(
+                                       &p.frame,
+                                       uclogic_rdesc_buttonpad_v2_arr,
+                                       uclogic_rdesc_buttonpad_v2_size,
+                                       UCLOGIC_RDESC_BUTTONPAD_V2_ID);
+                       if (rc != 0) {
+                               hid_err(hdev,
+                                       "failed creating v2 buttonpad parameters: %d\n",
+                                       rc);
+                               goto cleanup;
+                       }
+                       /* Set bitmask marking frame reports in pen reports */
+                       p.pen_frame_flag = 0x20;
+                       goto output;
+               }
+               hid_dbg(hdev, "pen v2 parameters not found\n");
+       }
+
        /* Try to probe v1 pen parameters */
        rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
        if (rc != 0) {
@@ -613,6 +813,7 @@ output:
        memset(&p, 0, sizeof(p));
        rc = 0;
 cleanup:
+       kfree(ver_ptr);
        uclogic_params_cleanup(&p);
        return rc;
 }
index 359e723..ef1d3cf 100644 (file)
@@ -585,6 +585,62 @@ const __u8 uclogic_rdesc_pen_v1_template_arr[] = {
 const size_t uclogic_rdesc_pen_v1_template_size =
                        sizeof(uclogic_rdesc_pen_v1_template_arr);
 
+/* Fixed report descriptor template for (tweaked) v2 pen reports */
+const __u8 uclogic_rdesc_pen_v2_template_arr[] = {
+       0x05, 0x0D,             /*  Usage Page (Digitizer),                 */
+       0x09, 0x02,             /*  Usage (Pen),                            */
+       0xA1, 0x01,             /*  Collection (Application),               */
+       0x85, 0x08,             /*      Report ID (8),                      */
+       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, 0x18,             /*          Report Size (24),               */
+       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),               */
+       0x81, 0x03,             /*          Input (Constant, Variable),     */
+       0xC0,                   /*      End Collection,                     */
+       0xC0                    /*  End Collection                          */
+};
+
+const size_t uclogic_rdesc_pen_v2_template_size =
+                       sizeof(uclogic_rdesc_pen_v2_template_arr);
+
 /**
  * Expand to the contents of a generic buttonpad report descriptor.
  *
@@ -638,6 +694,13 @@ const __u8 uclogic_rdesc_buttonpad_v1_arr[] = {
 const size_t uclogic_rdesc_buttonpad_v1_size =
                        sizeof(uclogic_rdesc_buttonpad_v1_arr);
 
+/* Fixed report descriptor for (tweaked) v2 buttonpad reports */
+const __u8 uclogic_rdesc_buttonpad_v2_arr[] = {
+       UCLOGIC_RDESC_BUTTONPAD_BYTES(52)
+};
+const size_t uclogic_rdesc_buttonpad_v2_size =
+                       sizeof(uclogic_rdesc_buttonpad_v2_arr);
+
 /**
  * uclogic_rdesc_template_apply() - apply report descriptor parameters to a
  * report descriptor template, creating a report descriptor. Copies the
index cf03b98..f205254 100644 (file)
@@ -107,6 +107,13 @@ enum uclogic_rdesc_pen_ph_id {
 extern const __u8 uclogic_rdesc_pen_v1_template_arr[];
 extern const size_t uclogic_rdesc_pen_v1_template_size;
 
+/* Report ID for v2 pen reports */
+#define UCLOGIC_RDESC_PEN_V2_ID        0x08
+
+/* Fixed report descriptor template for (tweaked) v2 pen reports */
+extern const __u8 uclogic_rdesc_pen_v2_template_arr[];
+extern const size_t uclogic_rdesc_pen_v2_template_size;
+
 /* Fixed report descriptor for (tweaked) v1 buttonpad reports */
 extern const __u8 uclogic_rdesc_buttonpad_v1_arr[];
 extern const size_t uclogic_rdesc_buttonpad_v1_size;
@@ -114,4 +121,11 @@ extern const size_t uclogic_rdesc_buttonpad_v1_size;
 /* Report ID for tweaked v1 buttonpad reports */
 #define UCLOGIC_RDESC_BUTTONPAD_V1_ID 0xf7
 
+/* Fixed report descriptor for (tweaked) v2 buttonpad reports */
+extern const __u8 uclogic_rdesc_buttonpad_v2_arr[];
+extern const size_t uclogic_rdesc_buttonpad_v2_size;
+
+/* Report ID for tweaked v2 buttonpad reports */
+#define UCLOGIC_RDESC_BUTTONPAD_V2_ID 0xf7
+
 #endif /* _HID_UCLOGIC_RDESC_H */