xref: /openbmc/linux/drivers/usb/misc/ehset.c (revision c900529f3d9161bfde5cca0754f83b4d3c3e0220)
15fd54aceSGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
21353aa53SManu Gautam /*
31353aa53SManu Gautam  * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
41353aa53SManu Gautam  */
51353aa53SManu Gautam 
61353aa53SManu Gautam #include <linux/kernel.h>
71353aa53SManu Gautam #include <linux/errno.h>
81353aa53SManu Gautam #include <linux/module.h>
91353aa53SManu Gautam #include <linux/slab.h>
101353aa53SManu Gautam #include <linux/usb.h>
111353aa53SManu Gautam #include <linux/usb/ch11.h>
121353aa53SManu Gautam 
131353aa53SManu Gautam #define TEST_SE0_NAK_PID			0x0101
141353aa53SManu Gautam #define TEST_J_PID				0x0102
151353aa53SManu Gautam #define TEST_K_PID				0x0103
161353aa53SManu Gautam #define TEST_PACKET_PID				0x0104
171353aa53SManu Gautam #define TEST_HS_HOST_PORT_SUSPEND_RESUME	0x0106
181353aa53SManu Gautam #define TEST_SINGLE_STEP_GET_DEV_DESC		0x0107
191353aa53SManu Gautam #define TEST_SINGLE_STEP_SET_FEATURE		0x0108
201353aa53SManu Gautam 
21f2b42379SRazvan Heghedus extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
22f2b42379SRazvan Heghedus 						const struct usb_device_id *id);
23f2b42379SRazvan Heghedus 
24f2b42379SRazvan Heghedus /*
25f2b42379SRazvan Heghedus  * A list of USB hubs which requires to disable the power
26f2b42379SRazvan Heghedus  * to the port before starting the testing procedures.
27f2b42379SRazvan Heghedus  */
28f2b42379SRazvan Heghedus static const struct usb_device_id ehset_hub_list[] = {
29f2b42379SRazvan Heghedus 	{ USB_DEVICE(0x0424, 0x4502) },
30f2b42379SRazvan Heghedus 	{ USB_DEVICE(0x0424, 0x4913) },
31f2b42379SRazvan Heghedus 	{ USB_DEVICE(0x0451, 0x8027) },
32f2b42379SRazvan Heghedus 	{ }
33f2b42379SRazvan Heghedus };
34f2b42379SRazvan Heghedus 
ehset_prepare_port_for_testing(struct usb_device * hub_udev,u16 portnum)35f2b42379SRazvan Heghedus static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum)
36f2b42379SRazvan Heghedus {
37f2b42379SRazvan Heghedus 	int ret = 0;
38f2b42379SRazvan Heghedus 
39f2b42379SRazvan Heghedus 	/*
40f2b42379SRazvan Heghedus 	 * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is
41f2b42379SRazvan Heghedus 	 * going under test needs to be put in suspend before sending the
42f2b42379SRazvan Heghedus 	 * test command. Most hubs don't enforce this precondition, but there
43f2b42379SRazvan Heghedus 	 * are some hubs which needs to disable the power to the port before
44f2b42379SRazvan Heghedus 	 * starting the test.
45f2b42379SRazvan Heghedus 	 */
46f2b42379SRazvan Heghedus 	if (usb_device_match_id(hub_udev, ehset_hub_list)) {
47f2b42379SRazvan Heghedus 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
48f2b42379SRazvan Heghedus 					   USB_RT_PORT, USB_PORT_FEAT_ENABLE,
49f2b42379SRazvan Heghedus 					   portnum, NULL, 0, 1000, GFP_KERNEL);
50f2b42379SRazvan Heghedus 		/*
51f2b42379SRazvan Heghedus 		 * Wait for the port to be disabled. It's an arbitrary value
52f2b42379SRazvan Heghedus 		 * which worked every time.
53f2b42379SRazvan Heghedus 		 */
54f2b42379SRazvan Heghedus 		msleep(100);
55f2b42379SRazvan Heghedus 	} else {
56f2b42379SRazvan Heghedus 		/*
57f2b42379SRazvan Heghedus 		 * For the hubs which are compliant with the spec,
58f2b42379SRazvan Heghedus 		 * put the port in SUSPEND.
59f2b42379SRazvan Heghedus 		 */
60f2b42379SRazvan Heghedus 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
61f2b42379SRazvan Heghedus 					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
62f2b42379SRazvan Heghedus 					   portnum, NULL, 0, 1000, GFP_KERNEL);
63f2b42379SRazvan Heghedus 	}
64f2b42379SRazvan Heghedus 	return ret;
65f2b42379SRazvan Heghedus }
66f2b42379SRazvan Heghedus 
ehset_probe(struct usb_interface * intf,const struct usb_device_id * id)671353aa53SManu Gautam static int ehset_probe(struct usb_interface *intf,
681353aa53SManu Gautam 		       const struct usb_device_id *id)
691353aa53SManu Gautam {
701353aa53SManu Gautam 	int ret = -EINVAL;
711353aa53SManu Gautam 	struct usb_device *dev = interface_to_usbdev(intf);
721353aa53SManu Gautam 	struct usb_device *hub_udev = dev->parent;
73f5ffdd3bSAnant Thazhemadam 	struct usb_device_descriptor buf;
741353aa53SManu Gautam 	u8 portnum = dev->portnum;
751353aa53SManu Gautam 	u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);
761353aa53SManu Gautam 
771353aa53SManu Gautam 	switch (test_pid) {
781353aa53SManu Gautam 	case TEST_SE0_NAK_PID:
79f2b42379SRazvan Heghedus 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
80*7f232766SXu Yang 		if (ret < 0)
81f2b42379SRazvan Heghedus 			break;
82f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
83f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
8462fb45d3SGreg Kroah-Hartman 					   (USB_TEST_SE0_NAK << 8) | portnum,
85f5ffdd3bSAnant Thazhemadam 					   NULL, 0, 1000, GFP_KERNEL);
861353aa53SManu Gautam 		break;
871353aa53SManu Gautam 	case TEST_J_PID:
88f2b42379SRazvan Heghedus 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
89*7f232766SXu Yang 		if (ret < 0)
90f2b42379SRazvan Heghedus 			break;
91f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
92f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
93f5ffdd3bSAnant Thazhemadam 					   (USB_TEST_J << 8) | portnum, NULL, 0,
94f5ffdd3bSAnant Thazhemadam 					   1000, GFP_KERNEL);
951353aa53SManu Gautam 		break;
961353aa53SManu Gautam 	case TEST_K_PID:
97f2b42379SRazvan Heghedus 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
98*7f232766SXu Yang 		if (ret < 0)
99f2b42379SRazvan Heghedus 			break;
100f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
101f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
102f5ffdd3bSAnant Thazhemadam 					   (USB_TEST_K << 8) | portnum, NULL, 0,
103f5ffdd3bSAnant Thazhemadam 					   1000, GFP_KERNEL);
1041353aa53SManu Gautam 		break;
1051353aa53SManu Gautam 	case TEST_PACKET_PID:
106f2b42379SRazvan Heghedus 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
107*7f232766SXu Yang 		if (ret < 0)
108f2b42379SRazvan Heghedus 			break;
109f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
110f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
11162fb45d3SGreg Kroah-Hartman 					   (USB_TEST_PACKET << 8) | portnum,
112f5ffdd3bSAnant Thazhemadam 					   NULL, 0, 1000, GFP_KERNEL);
1131353aa53SManu Gautam 		break;
1141353aa53SManu Gautam 	case TEST_HS_HOST_PORT_SUSPEND_RESUME:
1151353aa53SManu Gautam 		/* Test: wait for 15secs -> suspend -> 15secs delay -> resume */
1161353aa53SManu Gautam 		msleep(15 * 1000);
117f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
118f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
119f5ffdd3bSAnant Thazhemadam 					   portnum, NULL, 0, 1000, GFP_KERNEL);
1201353aa53SManu Gautam 		if (ret < 0)
1211353aa53SManu Gautam 			break;
1221353aa53SManu Gautam 
1231353aa53SManu Gautam 		msleep(15 * 1000);
124f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
125f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
126f5ffdd3bSAnant Thazhemadam 					   portnum, NULL, 0, 1000, GFP_KERNEL);
1271353aa53SManu Gautam 		break;
1281353aa53SManu Gautam 	case TEST_SINGLE_STEP_GET_DEV_DESC:
1291353aa53SManu Gautam 		/* Test: wait for 15secs -> GetDescriptor request */
1301353aa53SManu Gautam 		msleep(15 * 1000);
1311353aa53SManu Gautam 
132f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR,
133f5ffdd3bSAnant Thazhemadam 					   USB_DIR_IN, USB_DT_DEVICE << 8, 0,
134f5ffdd3bSAnant Thazhemadam 					   &buf, USB_DT_DEVICE_SIZE,
135f5ffdd3bSAnant Thazhemadam 					   USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
1361353aa53SManu Gautam 		break;
1371353aa53SManu Gautam 	case TEST_SINGLE_STEP_SET_FEATURE:
1389841f37aSManu Gautam 		/*
1399841f37aSManu Gautam 		 * GetDescriptor SETUP request -> 15secs delay -> IN & STATUS
1409841f37aSManu Gautam 		 *
1419841f37aSManu Gautam 		 * Note, this test is only supported on root hubs since the
1429841f37aSManu Gautam 		 * SetPortFeature handling can only be done inside the HCD's
1439841f37aSManu Gautam 		 * hub_control callback function.
1449841f37aSManu Gautam 		 */
1459841f37aSManu Gautam 		if (hub_udev != dev->bus->root_hub) {
1469841f37aSManu Gautam 			dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n");
1479841f37aSManu Gautam 			break;
1489841f37aSManu Gautam 		}
1499841f37aSManu Gautam 
150f5ffdd3bSAnant Thazhemadam 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
151f5ffdd3bSAnant Thazhemadam 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
152f5ffdd3bSAnant Thazhemadam 					   (6 << 8) | portnum, NULL, 0,
153f5ffdd3bSAnant Thazhemadam 					   60 * 1000, GFP_KERNEL);
1549841f37aSManu Gautam 
1559841f37aSManu Gautam 		break;
1561353aa53SManu Gautam 	default:
1571353aa53SManu Gautam 		dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n",
1581353aa53SManu Gautam 			__func__, test_pid);
1591353aa53SManu Gautam 	}
1601353aa53SManu Gautam 
161f5ffdd3bSAnant Thazhemadam 	return ret;
1621353aa53SManu Gautam }
1631353aa53SManu Gautam 
ehset_disconnect(struct usb_interface * intf)1641353aa53SManu Gautam static void ehset_disconnect(struct usb_interface *intf)
1651353aa53SManu Gautam {
1661353aa53SManu Gautam }
1671353aa53SManu Gautam 
1681353aa53SManu Gautam static const struct usb_device_id ehset_id_table[] = {
1691353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
1701353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_J_PID) },
1711353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_K_PID) },
1721353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
1731353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
1741353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
1751353aa53SManu Gautam 	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
1761353aa53SManu Gautam 	{ }			/* Terminating entry */
1771353aa53SManu Gautam };
1781353aa53SManu Gautam MODULE_DEVICE_TABLE(usb, ehset_id_table);
1791353aa53SManu Gautam 
1801353aa53SManu Gautam static struct usb_driver ehset_driver = {
1811353aa53SManu Gautam 	.name =		"usb_ehset_test",
1821353aa53SManu Gautam 	.probe =	ehset_probe,
1831353aa53SManu Gautam 	.disconnect =	ehset_disconnect,
1841353aa53SManu Gautam 	.id_table =	ehset_id_table,
1851353aa53SManu Gautam };
1861353aa53SManu Gautam 
1871353aa53SManu Gautam module_usb_driver(ehset_driver);
1881353aa53SManu Gautam 
1891353aa53SManu Gautam MODULE_DESCRIPTION("USB Driver for EHSET Test Fixture");
1901353aa53SManu Gautam MODULE_LICENSE("GPL v2");
191