diff options
Diffstat (limited to 'drivers/hid/hid-uclogic-core.c')
| -rw-r--r-- | drivers/hid/hid-uclogic-core.c | 428 |
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, |
