summaryrefslogtreecommitdiff
path: root/drivers/hid/hid-uclogic-params.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/hid/hid-uclogic-params.c')
-rw-r--r--drivers/hid/hid-uclogic-params.c304
1 files changed, 289 insertions, 15 deletions
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
index 3c5eea3df328..e28176d9d9c9 100644
--- a/drivers/hid/hid-uclogic-params.c
+++ b/drivers/hid/hid-uclogic-params.c
@@ -19,7 +19,8 @@
#include "hid-ids.h"
#include <linux/ctype.h>
#include <linux/string.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
+#include <linux/string_choices.h>
/**
* uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
@@ -59,7 +60,7 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
size_t i;
hid_dbg(hdev, "\t.usage_invalid = %s\n",
- (pen->usage_invalid ? "true" : "false"));
+ str_true_false(pen->usage_invalid));
hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr);
hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size);
hid_dbg(hdev, "\t.id = %u\n", pen->id);
@@ -74,9 +75,9 @@ static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
hid_dbg(hdev, "\t.inrange = %s\n",
uclogic_params_pen_inrange_to_str(pen->inrange));
hid_dbg(hdev, "\t.fragmented_hires = %s\n",
- (pen->fragmented_hires ? "true" : "false"));
+ str_true_false(pen->fragmented_hires));
hid_dbg(hdev, "\t.tilt_y_flipped = %s\n",
- (pen->tilt_y_flipped ? "true" : "false"));
+ str_true_false(pen->tilt_y_flipped));
}
/**
@@ -103,6 +104,8 @@ static void uclogic_params_frame_hid_dbg(
frame->touch_flip_at);
hid_dbg(hdev, "\t\t.bitmap_dial_byte = %u\n",
frame->bitmap_dial_byte);
+ hid_dbg(hdev, "\t\t.bitmap_second_dial_destination_byte = %u\n",
+ frame->bitmap_second_dial_destination_byte);
}
/**
@@ -117,8 +120,7 @@ void uclogic_params_hid_dbg(const struct hid_device *hdev,
{
size_t i;
- hid_dbg(hdev, ".invalid = %s\n",
- params->invalid ? "true" : "false");
+ hid_dbg(hdev, ".invalid = %s\n", str_true_false(params->invalid));
hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
hid_dbg(hdev, ".pen = {\n");
@@ -616,6 +618,31 @@ cleanup:
}
/**
+ * uclogic_params_cleanup_event_hooks - free resources used by the list of raw
+ * event hooks.
+ * Can be called repeatedly.
+ *
+ * @params: Input parameters to cleanup. Cannot be NULL.
+ */
+static void uclogic_params_cleanup_event_hooks(struct uclogic_params *params)
+{
+ struct uclogic_raw_event_hook *curr, *n;
+
+ if (!params || !params->event_hooks)
+ return;
+
+ list_for_each_entry_safe(curr, n, &params->event_hooks->list, list) {
+ cancel_work_sync(&curr->work);
+ list_del(&curr->list);
+ kfree(curr->event);
+ kfree(curr);
+ }
+
+ kfree(params->event_hooks);
+ params->event_hooks = NULL;
+}
+
+/**
* uclogic_params_cleanup - free resources used by struct uclogic_params
* (tablet interface's parameters).
* Can be called repeatedly.
@@ -631,6 +658,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
uclogic_params_frame_cleanup(&params->frame_list[i]);
+ uclogic_params_cleanup_event_hooks(params);
memset(params, 0, sizeof(*params));
}
}
@@ -655,7 +683,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
* -ENOMEM, if failed to allocate memory.
*/
int uclogic_params_get_desc(const struct uclogic_params *params,
- __u8 **pdesc,
+ const __u8 **pdesc,
unsigned int *psize)
{
int rc = -ENOMEM;
@@ -743,7 +771,7 @@ static void uclogic_params_init_invalid(struct uclogic_params *params)
static int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
struct hid_device *hdev,
unsigned int orig_desc_size,
- __u8 *desc_ptr,
+ const __u8 *desc_ptr,
unsigned int desc_size)
{
__u8 *desc_copy_ptr = NULL;
@@ -816,7 +844,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
__u8 *params_ptr = NULL;
size_t params_len = 0;
/* Parameters string descriptor of a model with touch ring (HS610) */
- const __u8 touch_ring_model_params_buf[] = {
+ static const __u8 touch_ring_model_params_buf[] = {
0x13, 0x03, 0x70, 0xC6, 0x00, 0x06, 0x7C, 0x00,
0xFF, 0x1F, 0xD8, 0x13, 0x03, 0x0D, 0x10, 0x01,
0x04, 0x3C, 0x3E
@@ -858,6 +886,9 @@ static int uclogic_params_huion_init(struct uclogic_params *params,
goto cleanup;
}
+ /* The firmware is used in userspace as unique identifier */
+ strscpy(hdev->uniq, ver_ptr, sizeof(hdev->uniq));
+
/* If this is a transition firmware */
if (strcmp(ver_ptr, transition_ver) == 0) {
hid_dbg(hdev,
@@ -1021,8 +1052,8 @@ cleanup:
* Returns:
* Zero, if successful. A negative errno code on error.
*/
-static int uclogic_probe_interface(struct hid_device *hdev, u8 *magic_arr,
- int magic_size, int endpoint)
+static int uclogic_probe_interface(struct hid_device *hdev, const u8 *magic_arr,
+ size_t magic_size, int endpoint)
{
struct usb_device *udev;
unsigned int pipe = 0;
@@ -1092,6 +1123,9 @@ static int uclogic_params_parse_ugee_v2_desc(const __u8 *str_desc,
return -EINVAL;
pen_x_lm = get_unaligned_le16(str_desc + 2);
+ if (str_desc_size > 12)
+ pen_x_lm += (u8)str_desc[12] << 16;
+
pen_y_lm = get_unaligned_le16(str_desc + 4);
frame_num_buttons = str_desc[6];
*frame_type = str_desc[7];
@@ -1222,6 +1256,11 @@ static int uclogic_params_ugee_v2_init_frame_mouse(struct uclogic_params *p)
*/
static bool uclogic_params_ugee_v2_has_battery(struct hid_device *hdev)
{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ if (drvdata->quirks & UCLOGIC_BATTERY_QUIRK)
+ return true;
+
/* The XP-PEN Deco LW vendor, product and version are identical to the
* Deco L. The only difference reported by their firmware is the product
* name. Add a quirk to support battery reporting on the wireless
@@ -1276,6 +1315,74 @@ static int uclogic_params_ugee_v2_init_battery(struct hid_device *hdev,
}
/**
+ * uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses
+ * connection to the USB dongle and reconnects, either because of its physical
+ * distance or because it was switches off and on using the frame's switch,
+ * uclogic_probe_interface() needs to be called again to enable the tablet.
+ *
+ * @work: The work that triggered this function.
+ */
+static void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work)
+{
+ struct uclogic_raw_event_hook *event_hook;
+
+ event_hook = container_of(work, struct uclogic_raw_event_hook, work);
+ uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr,
+ uclogic_ugee_v2_probe_size,
+ uclogic_ugee_v2_probe_endpoint);
+}
+
+/**
+ * uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events
+ * to be hooked for UGEE v2 devices.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from.
+ * @p: Parameters to fill in, cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
+ struct uclogic_params *p)
+{
+ struct uclogic_raw_event_hook *event_hook;
+ static const __u8 reconnect_event[] = {
+ /* Event received on wireless tablet reconnection */
+ 0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ if (!p)
+ return -EINVAL;
+
+ /* The reconnection event is only received if the tablet has battery */
+ if (!uclogic_params_ugee_v2_has_battery(hdev))
+ return 0;
+
+ p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL);
+ if (!p->event_hooks)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&p->event_hooks->list);
+
+ event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL);
+ if (!event_hook)
+ return -ENOMEM;
+
+ INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work);
+ event_hook->hdev = hdev;
+ event_hook->size = ARRAY_SIZE(reconnect_event);
+ event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
+ if (!event_hook->event) {
+ kfree(event_hook);
+ return -ENOMEM;
+ }
+
+ list_add_tail(&event_hook->list, &p->event_hooks->list);
+
+ return 0;
+}
+
+/**
* uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
* discovering their parameters.
*
@@ -1298,6 +1405,7 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
struct hid_device *hdev)
{
int rc = 0;
+ struct uclogic_drvdata *drvdata;
struct usb_interface *iface;
__u8 bInterfaceNumber;
const int str_desc_len = 12;
@@ -1305,9 +1413,6 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
__u8 *rdesc_pen = NULL;
s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
enum uclogic_params_frame_type frame_type;
- __u8 magic_arr[] = {
- 0x02, 0xb0, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
/* The resulting parameters (noop) */
struct uclogic_params p = {0, };
@@ -1316,6 +1421,7 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
goto cleanup;
}
+ drvdata = hid_get_drvdata(hdev);
iface = to_usb_interface(hdev->dev.parent);
bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
@@ -1337,7 +1443,9 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
* The specific data was discovered by sniffing the Windows driver
* traffic.
*/
- rc = uclogic_probe_interface(hdev, magic_arr, sizeof(magic_arr), 0x03);
+ rc = uclogic_probe_interface(hdev, uclogic_ugee_v2_probe_arr,
+ uclogic_ugee_v2_probe_size,
+ uclogic_ugee_v2_probe_endpoint);
if (rc) {
uclogic_params_init_invalid(&p);
goto output;
@@ -1382,6 +1490,9 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
/* Initialize the frame interface */
+ if (drvdata->quirks & UCLOGIC_MOUSE_FRAME_QUIRK)
+ frame_type = UCLOGIC_PARAMS_FRAME_MOUSE;
+
switch (frame_type) {
case UCLOGIC_PARAMS_FRAME_DIAL:
case UCLOGIC_PARAMS_FRAME_MOUSE:
@@ -1407,6 +1518,13 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
}
}
+ /* Create a list of raw events to be ignored */
+ rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
+ if (rc) {
+ hid_err(hdev, "error initializing event hook list: %d\n", rc);
+ goto cleanup;
+ }
+
output:
/* Output parameters */
memcpy(params, &p, sizeof(*params));
@@ -1418,6 +1536,128 @@ cleanup:
return rc;
}
+/*
+ * uclogic_params_init_ugee_xppen_pro() - Initializes a UGEE XP-Pen Pro tablet device.
+ *
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_init_ugee_xppen_pro(struct uclogic_params *params,
+ struct hid_device *hdev,
+ const u8 rdesc_pen_arr[],
+ const size_t rdesc_pen_size,
+ const u8 rdesc_frame_arr[],
+ const size_t rdesc_frame_size,
+ size_t str_desc_len)
+{
+ int rc = 0;
+ struct usb_interface *iface;
+ __u8 bInterfaceNumber;
+ u8 *str_desc = NULL;
+ __u8 *rdesc_pen = NULL;
+ s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
+ enum uclogic_params_frame_type frame_type;
+ /* The resulting parameters (noop) */
+ struct uclogic_params p = {0, };
+
+ if (!hdev || !params) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ iface = to_usb_interface(hdev->dev.parent);
+ bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+
+ /* Ignore non-pen interfaces */
+ if (bInterfaceNumber != 2) {
+ rc = -EINVAL;
+ uclogic_params_init_invalid(&p);
+ goto cleanup;
+ }
+
+ /*
+ * Initialize the interface by sending magic data.
+ * This magic data is the same as other UGEE v2 tablets.
+ */
+ rc = uclogic_probe_interface(hdev,
+ uclogic_ugee_v2_probe_arr,
+ uclogic_ugee_v2_probe_size,
+ uclogic_ugee_v2_probe_endpoint);
+ if (rc) {
+ uclogic_params_init_invalid(&p);
+ goto cleanup;
+ }
+
+ /**
+ * Read the string descriptor containing pen and frame parameters.
+ * These are slightly different than typical UGEE v2 devices.
+ */
+ rc = uclogic_params_get_str_desc(&str_desc, hdev, 100, str_desc_len);
+ if (rc != str_desc_len) {
+ rc = (rc < 0) ? rc : -EINVAL;
+ hid_err(hdev, "failed retrieving pen and frame parameters: %d\n", rc);
+ uclogic_params_init_invalid(&p);
+ goto cleanup;
+ }
+
+ rc = uclogic_params_parse_ugee_v2_desc(str_desc, str_desc_len,
+ desc_params,
+ ARRAY_SIZE(desc_params),
+ &frame_type);
+ if (rc)
+ goto cleanup;
+
+ // str_desc doesn't report the correct amount of buttons, so manually fix it
+ desc_params[UCLOGIC_RDESC_FRAME_PH_ID_UM] = 20;
+
+ kfree(str_desc);
+ str_desc = NULL;
+
+ /* Initialize the pen interface */
+ rdesc_pen = uclogic_rdesc_template_apply(
+ rdesc_pen_arr,
+ rdesc_pen_size,
+ desc_params, ARRAY_SIZE(desc_params));
+ if (!rdesc_pen) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ p.pen.desc_ptr = rdesc_pen;
+ p.pen.desc_size = rdesc_pen_size;
+ p.pen.id = 0x02;
+ p.pen.subreport_list[0].value = 0xf0;
+ p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
+
+ /* Initialize the frame interface */
+ rc = uclogic_params_frame_init_with_desc(
+ &p.frame_list[0],
+ rdesc_frame_arr,
+ rdesc_frame_size,
+ UCLOGIC_RDESC_V1_FRAME_ID);
+ if (rc < 0) {
+ hid_err(hdev, "initializing frame params failed: %d\n", rc);
+ goto cleanup;
+ }
+
+ p.frame_list[0].bitmap_dial_byte = 7;
+ p.frame_list[0].bitmap_second_dial_destination_byte = 8;
+
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+cleanup:
+ kfree(str_desc);
+ uclogic_params_cleanup(&p);
+ return rc;
+}
+
/**
* uclogic_params_init() - initialize a tablet interface and discover its
* parameters.
@@ -1660,7 +1900,11 @@ int uclogic_params_init(struct uclogic_params *params,
case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L):
case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW):
+ case VID_PID(USB_VENDOR_ID_UGEE,
USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S):
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW):
rc = uclogic_params_ugee_v2_init(&p, hdev);
if (rc != 0)
goto cleanup;
@@ -1731,6 +1975,36 @@ int uclogic_params_init(struct uclogic_params *params,
}
break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_22R_PRO):
+ rc = uclogic_params_init_ugee_xppen_pro(&p,
+ hdev,
+ uclogic_rdesc_ugee_v2_pen_template_arr,
+ uclogic_rdesc_ugee_v2_pen_template_size,
+ uclogic_rdesc_xppen_artist_22r_pro_frame_arr,
+ uclogic_rdesc_xppen_artist_22r_pro_frame_size,
+ 12);
+ if (rc != 0)
+ goto cleanup;
+
+ break;
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_XPPEN_TABLET_24_PRO):
+ rc = uclogic_params_init_ugee_xppen_pro(&p,
+ hdev,
+ uclogic_rdesc_xppen_artist_24_pro_pen_template_arr,
+ uclogic_rdesc_xppen_artist_24_pro_pen_template_size,
+ uclogic_rdesc_xppen_artist_24_pro_frame_arr,
+ uclogic_rdesc_xppen_artist_24_pro_frame_size,
+ 14);
+
+ // The 24 Pro has a fragmented X Coord.
+ p.pen.fragmented_hires2 = true;
+
+ if (rc != 0)
+ goto cleanup;
+
+ break;
}
#undef VID_PID