diff options
Diffstat (limited to 'drivers/hid/hid-huion.c')
| -rw-r--r-- | drivers/hid/hid-huion.c | 295 | 
1 files changed, 204 insertions, 91 deletions
diff --git a/drivers/hid/hid-huion.c b/drivers/hid/hid-huion.c index cbf4da4689ba..61b68ca27790 100644 --- a/drivers/hid/hid-huion.c +++ b/drivers/hid/hid-huion.c @@ -2,6 +2,7 @@   *  HID driver for Huion devices not fully compliant with HID standard   *   *  Copyright (c) 2013 Martin Rusko + *  Copyright (c) 2014 Nikolai Kondrashov   */  /* @@ -15,67 +16,98 @@  #include <linux/hid.h>  #include <linux/module.h>  #include <linux/usb.h> +#include <asm/unaligned.h>  #include "usbhid/usbhid.h"  #include "hid-ids.h" -/* Original Huion 580 report descriptor size */ -#define HUION_580_RDESC_ORIG_SIZE	177 - -/* Fixed Huion 580 report descriptor */ -static __u8 huion_580_rdesc_fixed[] = { -	0x05, 0x0D,         /*  Usage Page (Digitizer),             */ -	0x09, 0x02,         /*  Usage (Pen),                        */ -	0xA1, 0x01,         /*  Collection (Application),           */ -	0x85, 0x07,         /*      Report ID (7),                  */ -	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), */ -	0x75, 0x10,         /*          Report Size (16),           */ -	0x95, 0x01,         /*          Report Count (1),           */ -	0xA4,               /*          Push,                       */ -	0x05, 0x01,         /*          Usage Page (Desktop),       */ -	0x65, 0x13,         /*          Unit (Inch),                */ -	0x55, 0xFD,         /*          Unit Exponent (-3),         */ -	0x34,               /*          Physical Minimum (0),       */ -	0x09, 0x30,         /*          Usage (X),                  */ -	0x46, 0x40, 0x1F,   /*          Physical Maximum (8000),    */ -	0x26, 0x00, 0x7D,   /*          Logical Maximum (32000),    */ -	0x81, 0x02,         /*          Input (Variable),           */ -	0x09, 0x31,         /*          Usage (Y),                  */ -	0x46, 0x88, 0x13,   /*          Physical Maximum (5000),    */ -	0x26, 0x20, 0x4E,   /*          Logical Maximum (20000),    */ -	0x81, 0x02,         /*          Input (Variable),           */ -	0xB4,               /*          Pop,                        */ -	0x09, 0x30,         /*          Usage (Tip Pressure),       */ -	0x26, 0xFF, 0x07,   /*          Logical Maximum (2047),     */ -	0x81, 0x02,         /*          Input (Variable),           */ -	0xC0,               /*      End Collection,                 */ -	0xC0                /*  End Collection                      */ +/* Report descriptor template placeholder head */ +#define HUION_PH_HEAD	0xFE, 0xED, 0x1D + +/* Report descriptor template placeholder IDs */ +enum huion_ph_id { +	HUION_PH_ID_X_LM, +	HUION_PH_ID_X_PM, +	HUION_PH_ID_Y_LM, +	HUION_PH_ID_Y_PM, +	HUION_PH_ID_PRESSURE_LM, +	HUION_PH_ID_NUM +}; + +/* Report descriptor template placeholder */ +#define HUION_PH(_ID) HUION_PH_HEAD, HUION_PH_ID_##_ID + +/* Fixed report descriptor template */ +static const __u8 huion_tablet_rdesc_template[] = { +	0x05, 0x0D,             /*  Usage Page (Digitizer),                 */ +	0x09, 0x02,             /*  Usage (Pen),                            */ +	0xA1, 0x01,             /*  Collection (Application),               */ +	0x85, 0x07,             /*      Report ID (7),                      */ +	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),     */ +	0x75, 0x10,             /*          Report Size (16),               */ +	0x95, 0x01,             /*          Report Count (1),               */ +	0xA4,                   /*          Push,                           */ +	0x05, 0x01,             /*          Usage Page (Desktop),           */ +	0x65, 0x13,             /*          Unit (Inch),                    */ +	0x55, 0xFD,             /*          Unit Exponent (-3),             */ +	0x34,                   /*          Physical Minimum (0),           */ +	0x09, 0x30,             /*          Usage (X),                      */ +	0x27, HUION_PH(X_LM),   /*          Logical Maximum (PLACEHOLDER),  */ +	0x47, HUION_PH(X_PM),   /*          Physical Maximum (PLACEHOLDER), */ +	0x81, 0x02,             /*          Input (Variable),               */ +	0x09, 0x31,             /*          Usage (Y),                      */ +	0x27, HUION_PH(Y_LM),   /*          Logical Maximum (PLACEHOLDER),  */ +	0x47, HUION_PH(Y_PM),   /*          Physical Maximum (PLACEHOLDER), */ +	0x81, 0x02,             /*          Input (Variable),               */ +	0xB4,                   /*          Pop,                            */ +	0x09, 0x30,             /*          Usage (Tip Pressure),           */ +	0x27, +	HUION_PH(PRESSURE_LM),  /*          Logical Maximum (PLACEHOLDER),  */ +	0x81, 0x02,             /*          Input (Variable),               */ +	0xC0,                   /*      End Collection,                     */ +	0xC0                    /*  End Collection                          */ +}; + +/* Parameter indices */ +enum huion_prm { +	HUION_PRM_X_LM		= 1, +	HUION_PRM_Y_LM		= 2, +	HUION_PRM_PRESSURE_LM	= 4, +	HUION_PRM_RESOLUTION	= 5, +	HUION_PRM_NUM +}; + +/* Driver data */ +struct huion_drvdata { +	__u8 *rdesc; +	unsigned int rsize;  };  static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,  		unsigned int *rsize)  { +	struct huion_drvdata *drvdata = hid_get_drvdata(hdev);  	switch (hdev->product) { -	case USB_DEVICE_ID_HUION_580: -		if (*rsize == HUION_580_RDESC_ORIG_SIZE) { -			rdesc = huion_580_rdesc_fixed; -			*rsize = sizeof(huion_580_rdesc_fixed); +	case USB_DEVICE_ID_HUION_TABLET: +		if (drvdata->rdesc != NULL) { +			rdesc = drvdata->rdesc; +			*rsize = drvdata->rsize;  		}  		break;  	} @@ -83,82 +115,163 @@ static __u8 *huion_report_fixup(struct hid_device *hdev, __u8 *rdesc,  }  /** - * Enable fully-functional tablet mode by reading special string - * descriptor. + * Enable fully-functional tablet mode and determine device parameters.   *   * @hdev:	HID device - * - * The specific string descriptor and data were discovered by sniffing - * the Windows driver traffic.   */  static int huion_tablet_enable(struct hid_device *hdev)  {  	int rc; -	char buf[22]; +	struct usb_device *usb_dev = hid_to_usb_dev(hdev); +	struct huion_drvdata *drvdata = hid_get_drvdata(hdev); +	__le16 *buf = NULL; +	size_t len; +	s32 params[HUION_PH_ID_NUM]; +	s32 resolution; +	__u8 *p; +	s32 v; -	rc = usb_string(hid_to_usb_dev(hdev), 0x64, buf, sizeof(buf)); -	if (rc < 0) -		return rc; +	/* +	 * 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 = HUION_PRM_NUM * sizeof(*buf); +	buf = kmalloc(len, GFP_KERNEL); +	if (buf == NULL) { +		hid_err(hdev, "failed to allocate parameter buffer\n"); +		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; +	} -	return 0; +	/* Extract device parameters */ +	params[HUION_PH_ID_X_LM] = le16_to_cpu(buf[HUION_PRM_X_LM]); +	params[HUION_PH_ID_Y_LM] = le16_to_cpu(buf[HUION_PRM_Y_LM]); +	params[HUION_PH_ID_PRESSURE_LM] = +		le16_to_cpu(buf[HUION_PRM_PRESSURE_LM]); +	resolution = le16_to_cpu(buf[HUION_PRM_RESOLUTION]); +	if (resolution == 0) { +		params[HUION_PH_ID_X_PM] = 0; +		params[HUION_PH_ID_Y_PM] = 0; +	} else { +		params[HUION_PH_ID_X_PM] = params[HUION_PH_ID_X_LM] * +						1000 / resolution; +		params[HUION_PH_ID_Y_PM] = params[HUION_PH_ID_Y_LM] * +						1000 / resolution; +	} + +	/* Allocate fixed report descriptor */ +	drvdata->rdesc = devm_kmalloc(&hdev->dev, +				sizeof(huion_tablet_rdesc_template), +				GFP_KERNEL); +	if (drvdata->rdesc == NULL) { +		hid_err(hdev, "failed to allocate fixed rdesc\n"); +		rc = -ENOMEM; +		goto cleanup; +	} +	drvdata->rsize = sizeof(huion_tablet_rdesc_template); + +	/* Format fixed report descriptor */ +	memcpy(drvdata->rdesc, huion_tablet_rdesc_template, +		drvdata->rsize); +	for (p = drvdata->rdesc; +	     p <= drvdata->rdesc + drvdata->rsize - 4;) { +		if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D && +		    p[3] < sizeof(params)) { +			v = params[p[3]]; +			put_unaligned(cpu_to_le32(v), (s32 *)p); +			p += 4; +		} else { +			p++; +		} +	} + +	rc = 0; + +cleanup: +	kfree(buf); +	return rc;  }  static int huion_probe(struct hid_device *hdev, const struct hid_device_id *id)  { -	int ret; +	int rc;  	struct usb_interface *intf = to_usb_interface(hdev->dev.parent); +	struct huion_drvdata *drvdata; + +	/* Allocate and assign driver data */ +	drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); +	if (drvdata == NULL) { +		hid_err(hdev, "failed to allocate driver data\n"); +		return -ENOMEM; +	} +	hid_set_drvdata(hdev, drvdata); -	/* Ignore interfaces 1 (mouse) and 2 (keyboard) for Huion 580 tablet, -	 * as they are not used -	 */  	switch (id->product) { -	case USB_DEVICE_ID_HUION_580: -		if (intf->cur_altsetting->desc.bInterfaceNumber != 0x00) -			return -ENODEV; +	case USB_DEVICE_ID_HUION_TABLET: +		/* If this is the pen interface */ +		if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { +			rc = huion_tablet_enable(hdev); +			if (rc) { +				hid_err(hdev, "tablet enabling failed\n"); +				return rc; +			} +		}  		break;  	} -	ret = hid_parse(hdev); -	if (ret) { +	rc = hid_parse(hdev); +	if (rc) {  		hid_err(hdev, "parse failed\n"); -		goto err; +		return rc;  	} -	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -	if (ret) { +	rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +	if (rc) {  		hid_err(hdev, "hw start failed\n"); -		goto err; -	} - -	switch (id->product) { -	case USB_DEVICE_ID_HUION_580: -		ret = huion_tablet_enable(hdev); -		if (ret) { -			hid_err(hdev, "tablet enabling failed\n"); -			goto enabling_err; -		} -		break; +		return rc;  	}  	return 0; -enabling_err: -	hid_hw_stop(hdev); -err: -	return ret;  }  static int huion_raw_event(struct hid_device *hdev, struct hid_report *report,  			u8 *data, int size)  { -	/* If this is a pen input report then invert the in-range bit */ -	if (report->type == HID_INPUT_REPORT && report->id == 0x07 && size >= 2) +	struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + +	/* If this is a pen input report */ +	if (intf->cur_altsetting->desc.bInterfaceNumber == 0 && +	    report->type == HID_INPUT_REPORT && +	    report->id == 0x07 && size >= 2) +		/* Invert the in-range bit */  		data[1] ^= 0x40;  	return 0;  }  static const struct hid_device_id huion_devices[] = { -	{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_580) }, +	{ HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, +	{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) },  	{ }  };  MODULE_DEVICE_TABLE(hid, huion_devices);  | 
