xref: /openbmc/linux/drivers/usb/misc/ehset.c (revision bc33f5e5)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
4  */
5 
6 #include <linux/kernel.h>
7 #include <linux/errno.h>
8 #include <linux/module.h>
9 #include <linux/slab.h>
10 #include <linux/usb.h>
11 #include <linux/usb/ch11.h>
12 
13 #define TEST_SE0_NAK_PID			0x0101
14 #define TEST_J_PID				0x0102
15 #define TEST_K_PID				0x0103
16 #define TEST_PACKET_PID				0x0104
17 #define TEST_HS_HOST_PORT_SUSPEND_RESUME	0x0106
18 #define TEST_SINGLE_STEP_GET_DEV_DESC		0x0107
19 #define TEST_SINGLE_STEP_SET_FEATURE		0x0108
20 
21 extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
22 						const struct usb_device_id *id);
23 
24 /*
25  * A list of USB hubs which requires to disable the power
26  * to the port before starting the testing procedures.
27  */
28 static const struct usb_device_id ehset_hub_list[] = {
29 	{ USB_DEVICE(0x0424, 0x4502) },
30 	{ USB_DEVICE(0x0424, 0x4913) },
31 	{ USB_DEVICE(0x0451, 0x8027) },
32 	{ }
33 };
34 
35 static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum)
36 {
37 	int ret = 0;
38 
39 	/*
40 	 * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is
41 	 * going under test needs to be put in suspend before sending the
42 	 * test command. Most hubs don't enforce this precondition, but there
43 	 * are some hubs which needs to disable the power to the port before
44 	 * starting the test.
45 	 */
46 	if (usb_device_match_id(hub_udev, ehset_hub_list)) {
47 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
48 					   USB_RT_PORT, USB_PORT_FEAT_ENABLE,
49 					   portnum, NULL, 0, 1000, GFP_KERNEL);
50 		/*
51 		 * Wait for the port to be disabled. It's an arbitrary value
52 		 * which worked every time.
53 		 */
54 		msleep(100);
55 	} else {
56 		/*
57 		 * For the hubs which are compliant with the spec,
58 		 * put the port in SUSPEND.
59 		 */
60 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
61 					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
62 					   portnum, NULL, 0, 1000, GFP_KERNEL);
63 	}
64 	return ret;
65 }
66 
67 static int ehset_probe(struct usb_interface *intf,
68 		       const struct usb_device_id *id)
69 {
70 	int ret = -EINVAL;
71 	struct usb_device *dev = interface_to_usbdev(intf);
72 	struct usb_device *hub_udev = dev->parent;
73 	struct usb_device_descriptor buf;
74 	u8 portnum = dev->portnum;
75 	u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);
76 
77 	switch (test_pid) {
78 	case TEST_SE0_NAK_PID:
79 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
80 		if (!ret)
81 			break;
82 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
83 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
84 					   (USB_TEST_SE0_NAK << 8) | portnum,
85 					   NULL, 0, 1000, GFP_KERNEL);
86 		break;
87 	case TEST_J_PID:
88 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
89 		if (!ret)
90 			break;
91 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
92 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
93 					   (USB_TEST_J << 8) | portnum, NULL, 0,
94 					   1000, GFP_KERNEL);
95 		break;
96 	case TEST_K_PID:
97 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
98 		if (!ret)
99 			break;
100 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
101 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
102 					   (USB_TEST_K << 8) | portnum, NULL, 0,
103 					   1000, GFP_KERNEL);
104 		break;
105 	case TEST_PACKET_PID:
106 		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
107 		if (!ret)
108 			break;
109 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
110 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
111 					   (USB_TEST_PACKET << 8) | portnum,
112 					   NULL, 0, 1000, GFP_KERNEL);
113 		break;
114 	case TEST_HS_HOST_PORT_SUSPEND_RESUME:
115 		/* Test: wait for 15secs -> suspend -> 15secs delay -> resume */
116 		msleep(15 * 1000);
117 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
118 					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
119 					   portnum, NULL, 0, 1000, GFP_KERNEL);
120 		if (ret < 0)
121 			break;
122 
123 		msleep(15 * 1000);
124 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
125 					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
126 					   portnum, NULL, 0, 1000, GFP_KERNEL);
127 		break;
128 	case TEST_SINGLE_STEP_GET_DEV_DESC:
129 		/* Test: wait for 15secs -> GetDescriptor request */
130 		msleep(15 * 1000);
131 
132 		ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR,
133 					   USB_DIR_IN, USB_DT_DEVICE << 8, 0,
134 					   &buf, USB_DT_DEVICE_SIZE,
135 					   USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
136 		break;
137 	case TEST_SINGLE_STEP_SET_FEATURE:
138 		/*
139 		 * GetDescriptor SETUP request -> 15secs delay -> IN & STATUS
140 		 *
141 		 * Note, this test is only supported on root hubs since the
142 		 * SetPortFeature handling can only be done inside the HCD's
143 		 * hub_control callback function.
144 		 */
145 		if (hub_udev != dev->bus->root_hub) {
146 			dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n");
147 			break;
148 		}
149 
150 		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
151 					   USB_RT_PORT, USB_PORT_FEAT_TEST,
152 					   (6 << 8) | portnum, NULL, 0,
153 					   60 * 1000, GFP_KERNEL);
154 
155 		break;
156 	default:
157 		dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n",
158 			__func__, test_pid);
159 	}
160 
161 	return ret;
162 }
163 
164 static void ehset_disconnect(struct usb_interface *intf)
165 {
166 }
167 
168 static const struct usb_device_id ehset_id_table[] = {
169 	{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
170 	{ USB_DEVICE(0x1a0a, TEST_J_PID) },
171 	{ USB_DEVICE(0x1a0a, TEST_K_PID) },
172 	{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
173 	{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
174 	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
175 	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
176 	{ }			/* Terminating entry */
177 };
178 MODULE_DEVICE_TABLE(usb, ehset_id_table);
179 
180 static struct usb_driver ehset_driver = {
181 	.name =		"usb_ehset_test",
182 	.probe =	ehset_probe,
183 	.disconnect =	ehset_disconnect,
184 	.id_table =	ehset_id_table,
185 };
186 
187 module_usb_driver(ehset_driver);
188 
189 MODULE_DESCRIPTION("USB Driver for EHSET Test Fixture");
190 MODULE_LICENSE("GPL v2");
191