diff options
Diffstat (limited to 'drivers/usb/misc/ehset.c')
| -rw-r--r-- | drivers/usb/misc/ehset.c | 58 | 
1 files changed, 58 insertions, 0 deletions
| diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c index f87890f9cd26..986d6589f053 100644 --- a/drivers/usb/misc/ehset.c +++ b/drivers/usb/misc/ehset.c @@ -18,6 +18,52 @@  #define TEST_SINGLE_STEP_GET_DEV_DESC		0x0107  #define TEST_SINGLE_STEP_SET_FEATURE		0x0108 +extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev, +						const struct usb_device_id *id); + +/* + * A list of USB hubs which requires to disable the power + * to the port before starting the testing procedures. + */ +static const struct usb_device_id ehset_hub_list[] = { +	{ USB_DEVICE(0x0424, 0x4502) }, +	{ USB_DEVICE(0x0424, 0x4913) }, +	{ USB_DEVICE(0x0451, 0x8027) }, +	{ } +}; + +static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum) +{ +	int ret = 0; + +	/* +	 * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is +	 * going under test needs to be put in suspend before sending the +	 * test command. Most hubs don't enforce this precondition, but there +	 * are some hubs which needs to disable the power to the port before +	 * starting the test. +	 */ +	if (usb_device_match_id(hub_udev, ehset_hub_list)) { +		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE, +					   USB_RT_PORT, USB_PORT_FEAT_ENABLE, +					   portnum, NULL, 0, 1000, GFP_KERNEL); +		/* +		 * Wait for the port to be disabled. It's an arbitrary value +		 * which worked every time. +		 */ +		msleep(100); +	} else { +		/* +		 * For the hubs which are compliant with the spec, +		 * put the port in SUSPEND. +		 */ +		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE, +					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND, +					   portnum, NULL, 0, 1000, GFP_KERNEL); +	} +	return ret; +} +  static int ehset_probe(struct usb_interface *intf,  		       const struct usb_device_id *id)  { @@ -30,24 +76,36 @@ static int ehset_probe(struct usb_interface *intf,  	switch (test_pid) {  	case TEST_SE0_NAK_PID: +		ret = ehset_prepare_port_for_testing(hub_udev, portnum); +		if (!ret) +			break;  		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,  					   USB_RT_PORT, USB_PORT_FEAT_TEST,  					   (USB_TEST_SE0_NAK << 8) | portnum,  					   NULL, 0, 1000, GFP_KERNEL);  		break;  	case TEST_J_PID: +		ret = ehset_prepare_port_for_testing(hub_udev, portnum); +		if (!ret) +			break;  		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,  					   USB_RT_PORT, USB_PORT_FEAT_TEST,  					   (USB_TEST_J << 8) | portnum, NULL, 0,  					   1000, GFP_KERNEL);  		break;  	case TEST_K_PID: +		ret = ehset_prepare_port_for_testing(hub_udev, portnum); +		if (!ret) +			break;  		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,  					   USB_RT_PORT, USB_PORT_FEAT_TEST,  					   (USB_TEST_K << 8) | portnum, NULL, 0,  					   1000, GFP_KERNEL);  		break;  	case TEST_PACKET_PID: +		ret = ehset_prepare_port_for_testing(hub_udev, portnum); +		if (!ret) +			break;  		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,  					   USB_RT_PORT, USB_PORT_FEAT_TEST,  					   (USB_TEST_PACKET << 8) | portnum, |