summaryrefslogtreecommitdiff
path: root/drivers/hid/hid-uclogic-core.c
diff options
context:
space:
mode:
authorNikolai Kondrashov <spbnick@gmail.com>2019-02-10 12:13:51 +0200
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>2019-02-21 12:00:53 +0100
commit9614219e9310ef19e66719bf37f9f68919bac08e (patch)
treee658e86c165670b6dbc7e49f44ef460415721d3e /drivers/hid/hid-uclogic-core.c
parentff0c13d6d2edc9c4952c668f4503a51b5f101ab3 (diff)
HID: uclogic: Extract tablet parameter discovery into a module
Refactor and extract UC-Logic tablet initialization and parameter discovery into a module. For these tablets, the major part of parameter discovery cannot be separated from initialization so they have to be in the same module. Define explicitly and clearly what possible quirks the tablets may have to make the driver implementation easier and simpler. Signed-off-by: Nikolai Kondrashov <spbnick@gmail.com> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Diffstat (limited to 'drivers/hid/hid-uclogic-core.c')
-rw-r--r--drivers/hid/hid-uclogic-core.c428
1 files changed, 102 insertions, 326 deletions
diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c
index 4042183ee9a3..72a3a43766cc 100644
--- a/drivers/hid/hid-uclogic-core.c
+++ b/drivers/hid/hid-uclogic-core.c
@@ -16,126 +16,48 @@
#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
-#include <linux/usb.h>
#include "usbhid/usbhid.h"
-#include "hid-uclogic-rdesc.h"
+#include "hid-uclogic-params.h"
#include "hid-ids.h"
-/* Parameter indices */
-enum uclogic_prm {
- UCLOGIC_PRM_X_LM = 1,
- UCLOGIC_PRM_Y_LM = 2,
- UCLOGIC_PRM_PRESSURE_LM = 4,
- UCLOGIC_PRM_RESOLUTION = 5,
- UCLOGIC_PRM_NUM
-};
-
/* Driver data */
struct uclogic_drvdata {
- __u8 *rdesc;
- unsigned int rsize;
- bool invert_pen_inrange;
- bool ignore_pen_usage;
- bool has_virtual_pad_interface;
+ /* Interface parameters */
+ struct uclogic_params params;
+ /* Pointer to the replacement report descriptor. NULL if none. */
+ __u8 *desc_ptr;
+ /*
+ * Size of the replacement report descriptor.
+ * Only valid if desc_ptr is not NULL
+ */
+ unsigned int desc_size;
};
static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
{
- struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
- __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber;
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
- if (drvdata->rdesc != NULL) {
- rdesc = drvdata->rdesc;
- *rsize = drvdata->rsize;
- return rdesc;
- }
-
- switch (hdev->product) {
- case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209:
- if (*rsize == UCLOGIC_RDESC_PF1209_ORIG_SIZE) {
- rdesc = uclogic_rdesc_pf1209_fixed_arr;
- *rsize = uclogic_rdesc_pf1209_fixed_size;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U:
- if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) {
- rdesc = uclogic_rdesc_wp4030u_fixed_arr;
- *rsize = uclogic_rdesc_wp4030u_fixed_size;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U:
- if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) {
- rdesc = uclogic_rdesc_wp5540u_fixed_arr;
- *rsize = uclogic_rdesc_wp5540u_fixed_size;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U:
- if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) {
- rdesc = uclogic_rdesc_wp8060u_fixed_arr;
- *rsize = uclogic_rdesc_wp8060u_fixed_size;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_WP1062:
- if (*rsize == UCLOGIC_RDESC_WP1062_ORIG_SIZE) {
- rdesc = uclogic_rdesc_wp1062_fixed_arr;
- *rsize = uclogic_rdesc_wp1062_fixed_size;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850:
- switch (iface_num) {
- case 0:
- if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG0_SIZE) {
- rdesc = uclogic_rdesc_twhl850_fixed0_arr;
- *rsize = uclogic_rdesc_twhl850_fixed0_size;
- }
- break;
- case 1:
- if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG1_SIZE) {
- rdesc = uclogic_rdesc_twhl850_fixed1_arr;
- *rsize = uclogic_rdesc_twhl850_fixed1_size;
- }
- break;
- case 2:
- if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG2_SIZE) {
- rdesc = uclogic_rdesc_twhl850_fixed2_arr;
- *rsize = uclogic_rdesc_twhl850_fixed2_size;
- }
- break;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60:
- switch (iface_num) {
- case 0:
- if (*rsize == UCLOGIC_RDESC_TWHA60_ORIG0_SIZE) {
- rdesc = uclogic_rdesc_twha60_fixed0_arr;
- *rsize = uclogic_rdesc_twha60_fixed0_size;
- }
- break;
- case 1:
- if (*rsize == UCLOGIC_RDESC_TWHA60_ORIG1_SIZE) {
- rdesc = uclogic_rdesc_twha60_fixed1_arr;
- *rsize = uclogic_rdesc_twha60_fixed1_size;
- }
- break;
- }
- break;
+ if (drvdata->desc_ptr != NULL) {
+ rdesc = drvdata->desc_ptr;
+ *rsize = drvdata->desc_size;
}
-
return rdesc;
}
-static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi,
- struct hid_field *field, struct hid_usage *usage,
- unsigned long **bit, int *max)
+static int uclogic_input_mapping(struct hid_device *hdev,
+ struct hid_input *hi,
+ struct hid_field *field,
+ struct hid_usage *usage,
+ unsigned long **bit,
+ int *max)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct uclogic_params *params = &drvdata->params;
/* discard the unused pen interface */
- if ((drvdata->ignore_pen_usage) &&
- (field->application == HID_DG_PEN))
+ if (params->pen_unused && (field->application == HID_DG_PEN))
return -1;
/* let hid-core decide what to do */
@@ -189,160 +111,12 @@ static int uclogic_input_configured(struct hid_device *hdev,
return 0;
}
-/**
- * Enable fully-functional tablet mode and determine device parameters.
- *
- * @hdev: HID device
- */
-static int uclogic_tablet_enable(struct hid_device *hdev)
-{
- int rc;
- struct usb_device *usb_dev = hid_to_usb_dev(hdev);
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
- __le16 *buf = NULL;
- size_t len;
- s32 params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
- s32 resolution;
-
- /*
- * Read string descriptor containing tablet parameters. The specific
- * string descriptor and data were discovered by sniffing the Windows
- * driver traffic.
- * NOTE: This enables fully-functional tablet mode.
- */
- len = UCLOGIC_PRM_NUM * sizeof(*buf);
- buf = kmalloc(len, GFP_KERNEL);
- if (buf == NULL) {
- rc = -ENOMEM;
- goto cleanup;
- }
- rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0),
- USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
- (USB_DT_STRING << 8) + 0x64,
- 0x0409, buf, len,
- USB_CTRL_GET_TIMEOUT);
- if (rc == -EPIPE) {
- hid_err(hdev, "device parameters not found\n");
- rc = -ENODEV;
- goto cleanup;
- } else if (rc < 0) {
- hid_err(hdev, "failed to get device parameters: %d\n", rc);
- rc = -ENODEV;
- goto cleanup;
- } else if (rc != len) {
- hid_err(hdev, "invalid device parameters\n");
- rc = -ENODEV;
- goto cleanup;
- }
-
- /* Extract device parameters */
- params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
- le16_to_cpu(buf[UCLOGIC_PRM_X_LM]);
- params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
- le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]);
- params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
- le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]);
- resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]);
- if (resolution == 0) {
- params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
- params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
- } else {
- params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
- params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] *
- 1000 / resolution;
- params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
- params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] *
- 1000 / resolution;
- }
-
- /* Format fixed report descriptor */
- drvdata->rdesc = uclogic_rdesc_template_apply(
- uclogic_rdesc_pen_template_arr,
- uclogic_rdesc_pen_template_size,
- params, ARRAY_SIZE(params));
- if (drvdata->rdesc == NULL) {
- rc = -ENOMEM;
- goto cleanup;
- }
- drvdata->rsize = uclogic_rdesc_pen_template_size;
-
- rc = 0;
-
-cleanup:
- kfree(buf);
- return rc;
-}
-
-/**
- * Enable actual button mode.
- *
- * @hdev: HID device
- */
-static int uclogic_button_enable(struct hid_device *hdev)
-{
- int rc;
- struct usb_device *usb_dev = hid_to_usb_dev(hdev);
- struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
- char *str_buf;
- size_t str_len = 16;
- unsigned char *rdesc;
- size_t rdesc_len;
-
- str_buf = kzalloc(str_len, GFP_KERNEL);
- if (str_buf == NULL) {
- rc = -ENOMEM;
- goto cleanup;
- }
-
- /* Enable abstract keyboard mode */
- rc = usb_string(usb_dev, 0x7b, str_buf, str_len);
- if (rc == -EPIPE) {
- hid_info(hdev, "button mode setting not found\n");
- rc = 0;
- goto cleanup;
- } else if (rc < 0) {
- hid_err(hdev, "failed to enable abstract keyboard\n");
- goto cleanup;
- } else if (strncmp(str_buf, "HK On", rc)) {
- hid_info(hdev, "invalid answer when requesting buttons: '%s'\n",
- str_buf);
- rc = -EINVAL;
- goto cleanup;
- }
-
- /* Re-allocate fixed report descriptor */
- rdesc_len = drvdata->rsize + uclogic_rdesc_buttonpad_size;
- rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL);
- if (!rdesc) {
- rc = -ENOMEM;
- goto cleanup;
- }
-
- memcpy(rdesc, drvdata->rdesc, drvdata->rsize);
-
- /* Append the buttonpad descriptor */
- memcpy(rdesc + drvdata->rsize, uclogic_rdesc_buttonpad_arr,
- uclogic_rdesc_buttonpad_size);
-
- /* clean up old rdesc and use the new one */
- drvdata->rsize = rdesc_len;
- devm_kfree(&hdev->dev, drvdata->rdesc);
- drvdata->rdesc = rdesc;
-
- rc = 0;
-
-cleanup:
- kfree(str_buf);
- return rc;
-}
-
static int uclogic_probe(struct hid_device *hdev,
const struct hid_device_id *id)
{
int rc;
- struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
- struct usb_device *udev = hid_to_usb_dev(hdev);
- struct uclogic_drvdata *drvdata;
+ struct uclogic_drvdata *drvdata = NULL;
+ bool params_initialized = false;
/*
* libinput requires the pad interface to be on a different node
@@ -352,104 +126,97 @@ static int uclogic_probe(struct hid_device *hdev,
/* Allocate and assign driver data */
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (drvdata == NULL)
- return -ENOMEM;
-
+ if (drvdata == NULL) {
+ rc = -ENOMEM;
+ goto failure;
+ }
hid_set_drvdata(hdev, drvdata);
- switch (id->product) {
- case USB_DEVICE_ID_HUION_TABLET:
- case USB_DEVICE_ID_YIYNOVA_TABLET:
- case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81:
- case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3:
- case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45:
- /* If this is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
- rc = uclogic_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- drvdata->invert_pen_inrange = true;
-
- rc = uclogic_button_enable(hdev);
- drvdata->has_virtual_pad_interface = !rc;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- break;
- case USB_DEVICE_ID_UGTIZER_TABLET_GP0610:
- case USB_DEVICE_ID_UGEE_TABLET_EX07S:
- /* If this is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 1) {
- rc = uclogic_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- drvdata->invert_pen_inrange = true;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- break;
- case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60:
- /*
- * If it is the three-interface version, which is known to
- * respond to initialization.
- */
- if (udev->config->desc.bNumInterfaces == 3) {
- /* If it is the pen interface */
- if (intf->cur_altsetting->desc.bInterfaceNumber == 0) {
- rc = uclogic_tablet_enable(hdev);
- if (rc) {
- hid_err(hdev, "tablet enabling failed\n");
- return rc;
- }
- drvdata->invert_pen_inrange = true;
+ /* Initialize the device and retrieve interface parameters */
+ rc = uclogic_params_init(&drvdata->params, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "failed probing parameters: %d\n", rc);
+ goto failure;
+ }
+ params_initialized = true;
+ hid_dbg(hdev, "parameters:\n" UCLOGIC_PARAMS_FMT_STR,
+ UCLOGIC_PARAMS_FMT_ARGS(&drvdata->params));
+ if (drvdata->params.invalid) {
+ hid_info(hdev, "interface is invalid, ignoring\n");
+ rc = -ENODEV;
+ goto failure;
+ }
- rc = uclogic_button_enable(hdev);
- drvdata->has_virtual_pad_interface = !rc;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- }
- break;
+ /* Generate replacement report descriptor */
+ rc = uclogic_params_get_desc(&drvdata->params,
+ &drvdata->desc_ptr,
+ &drvdata->desc_size);
+ if (rc) {
+ hid_err(hdev,
+ "failed generating replacement report descriptor: %d\n",
+ rc);
+ goto failure;
}
rc = hid_parse(hdev);
if (rc) {
hid_err(hdev, "parse failed\n");
- return rc;
+ goto failure;
}
rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (rc) {
hid_err(hdev, "hw start failed\n");
- return rc;
+ goto failure;
}
return 0;
+failure:
+ /* Assume "remove" might not be called if "probe" failed */
+ if (params_initialized)
+ uclogic_params_cleanup(&drvdata->params);
+ return rc;
}
-static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int size)
+static int uclogic_raw_event(struct hid_device *hdev,
+ struct hid_report *report,
+ u8 *data, int size)
{
struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+ struct uclogic_params *params = &drvdata->params;
- if ((report->type == HID_INPUT_REPORT) &&
- (report->id == UCLOGIC_RDESC_PEN_ID) &&
+ /* Tweak pen reports, if necessary */
+ if (!params->pen_unused &&
+ (report->type == HID_INPUT_REPORT) &&
+ (report->id == params->pen.id) &&
(size >= 2)) {
- if (drvdata->has_virtual_pad_interface && (data[1] & 0x20))
- /* Change to virtual frame button report ID */
- data[0] = 0xf7;
- else if (drvdata->invert_pen_inrange)
+ /* If it's the "virtual" frame controls report */
+ if (params->frame.id != 0 &&
+ data[1] & params->pen_frame_flag) {
+ /* Change to virtual frame controls report ID */
+ data[0] = params->frame.id;
+ return 0;
+ }
+ /* If in-range reports are inverted */
+ if (params->pen.inrange ==
+ UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) {
/* Invert the in-range bit */
data[1] ^= 0x40;
+ }
}
return 0;
}
+static void uclogic_remove(struct hid_device *hdev)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ hid_hw_stop(hdev);
+ kfree(drvdata->desc_ptr);
+ uclogic_params_cleanup(&drvdata->params);
+}
+
static const struct hid_device_id uclogic_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
@@ -465,14 +232,22 @@ static const struct hid_device_id uclogic_devices[] = {
USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) },
{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) },
- { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
- { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_HUION,
+ USB_DEVICE_ID_HUION_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_HUION_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_YIYNOVA_TABLET) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER,
+ USB_DEVICE_ID_UGTIZER_TABLET_GP0610) },
+ { HID_USB_DEVICE(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_TABLET_EX07S) },
{ }
};
MODULE_DEVICE_TABLE(hid, uclogic_devices);
@@ -481,6 +256,7 @@ static struct hid_driver uclogic_driver = {
.name = "uclogic",
.id_table = uclogic_devices,
.probe = uclogic_probe,
+ .remove = uclogic_remove,
.report_fixup = uclogic_report_fixup,
.raw_event = uclogic_raw_event,
.input_mapping = uclogic_input_mapping,