diff mbox series

[v2,10/23] HID: uclogic: Support v2 protocol

Message ID 20190210101409.3511-11-spbnick@gmail.com (mailing list archive)
State Mainlined
Commit 2c3a88c64cb62cc59010359dbfc97f734e98d683
Delegated to: Jiri Kosina
Headers show
Series [v2,01/23] HID: kye: Add support for EasyPen M406XE | expand

Commit Message

Nikolai Kondrashov Feb. 10, 2019, 10:13 a.m. UTC
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>
---
 drivers/hid/hid-uclogic-params.c | 201 +++++++++++++++++++++++++++++++
 drivers/hid/hid-uclogic-rdesc.c  |  63 ++++++++++
 drivers/hid/hid-uclogic-rdesc.h  |  14 +++
 3 files changed, 278 insertions(+)

Comments

Benjamin Tissoires Feb. 21, 2019, 10:41 a.m. UTC | #1
On Sun, Feb 10, 2019 at 11:15 AM Nikolai Kondrashov <spbnick@gmail.com> wrote:
>
> 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>
> ---
>  drivers/hid/hid-uclogic-params.c | 201 +++++++++++++++++++++++++++++++
>  drivers/hid/hid-uclogic-rdesc.c  |  63 ++++++++++
>  drivers/hid/hid-uclogic-rdesc.h  |  14 +++
>  3 files changed, 278 insertions(+)
>
> diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
> index b5e4d99c6771..132663a87f38 100644
> --- a/drivers/hid/hid-uclogic-params.c
> +++ b/drivers/hid/hid-uclogic-params.c
> @@ -231,6 +231,151 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
>         return rc;
>  }
>
> +/**
> + * 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);

Nitpick, I am pretty sure we already have the bits in the kernel for that.

But OTOH, not sure I'll request a v3 just for that.

Cheers,
Benjamin

> +}
> +
> +/**
> + * 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).
> @@ -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 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
>         memset(&p, 0, sizeof(p));
>         rc = 0;
>  cleanup:
> +       kfree(ver_ptr);
>         uclogic_params_cleanup(&p);
>         return rc;
>  }
> diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c
> index 359e72394d83..ef1d3cf918a4 100644
> --- a/drivers/hid/hid-uclogic-rdesc.c
> +++ b/drivers/hid/hid-uclogic-rdesc.c
> @@ -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
> diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h
> index cf03b98ae7cb..f205254a733c 100644
> --- a/drivers/hid/hid-uclogic-rdesc.h
> +++ b/drivers/hid/hid-uclogic-rdesc.h
> @@ -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 */
> --
> 2.20.1
>
Nikolai Kondrashov Feb. 21, 2019, 10:53 a.m. UTC | #2
On 2/21/19 12:41 PM, Benjamin Tissoires wrote:
> On Sun, Feb 10, 2019 at 11:15 AM Nikolai Kondrashov <spbnick@gmail.com> wrote:
>> +/**
>> + * 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);
> 
> Nitpick, I am pretty sure we already have the bits in the kernel for that.

I tried to find it and couldn't, do you have any hints where I can find it?

> But OTOH, not sure I'll request a v3 just for that.

Will be glad to fix this, if you'll need v3, or later.

Nick
Benjamin Tissoires Feb. 21, 2019, 10:59 a.m. UTC | #3
On Thu, Feb 21, 2019 at 11:54 AM Nikolai Kondrashov <spbnick@gmail.com> wrote:
>
> On 2/21/19 12:41 PM, Benjamin Tissoires wrote:
> > On Sun, Feb 10, 2019 at 11:15 AM Nikolai Kondrashov <spbnick@gmail.com> wrote:
> >> +/**
> >> + * 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);
> >
> > Nitpick, I am pretty sure we already have the bits in the kernel for that.
>
> I tried to find it and couldn't, do you have any hints where I can find it?

For non aligned bits in the middle of a report, we have
hid_field_extract()
(https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git/tree/drivers/hid/hid-core.c?h=for-next#n1301)

For other regular operations, there are the get_unaligned_le*()
operations, but they are for 16, 32 or 64 bits.
include/target/target_core_backend.h has a get_unaligned_be24() but it
requires to read p - 1, and I am not sure you can in your case.


>
> > But OTOH, not sure I'll request a v3 just for that.
>
> Will be glad to fix this, if you'll need v3, or later.

Probably later :)

Cheers,
Benjamin

>
> Nick
Nikolai Kondrashov Feb. 23, 2019, 12:25 p.m. UTC | #4
On 2/21/19 12:59 PM, Benjamin Tissoires wrote:
> On Thu, Feb 21, 2019 at 11:54 AM Nikolai Kondrashov <spbnick@gmail.com> wrote:
>>
>> On 2/21/19 12:41 PM, Benjamin Tissoires wrote:
>>> On Sun, Feb 10, 2019 at 11:15 AM Nikolai Kondrashov <spbnick@gmail.com> wrote:
>>>> +/**
>>>> + * 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);
>>>
>>> Nitpick, I am pretty sure we already have the bits in the kernel for that.
>>
>> I tried to find it and couldn't, do you have any hints where I can find it?
> 
> For non aligned bits in the middle of a report, we have
> hid_field_extract()
> (https://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git/tree/drivers/hid/hid-core.c?h=for-next#n1301)

Yeah, that could work. However, if you don't mind, I'd rather keep using my
version for now, since hid_field_extract is only available starting v4.2.
That would let digimend-kernel-drivers build on older distros without having
the code differ that much from upstream. Maybe next year.

> For other regular operations, there are the get_unaligned_le*()
> operations, but they are for 16, 32 or 64 bits.

Yes, I found these.

> include/target/target_core_backend.h has a get_unaligned_be24() but it
> requires to read p - 1, and I am not sure you can in your case.

Yeah, I'd rather not use a function which does p - 1, has no hint of that
limitation in its name, and is a part of the unrelated storage subsystem.

Thanks :)

Nick
diff mbox series

Patch

diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index b5e4d99c6771..132663a87f38 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -231,6 +231,151 @@  static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen,
 	return rc;
 }
 
+/**
+ * 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).
@@ -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 @@  static int uclogic_params_huion_init(struct uclogic_params *params,
 	memset(&p, 0, sizeof(p));
 	rc = 0;
 cleanup:
+	kfree(ver_ptr);
 	uclogic_params_cleanup(&p);
 	return rc;
 }
diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c
index 359e72394d83..ef1d3cf918a4 100644
--- a/drivers/hid/hid-uclogic-rdesc.c
+++ b/drivers/hid/hid-uclogic-rdesc.c
@@ -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
diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h
index cf03b98ae7cb..f205254a733c 100644
--- a/drivers/hid/hid-uclogic-rdesc.h
+++ b/drivers/hid/hid-uclogic-rdesc.h
@@ -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 */