162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
462306a36Sopenharmony_ci */
562306a36Sopenharmony_ci
662306a36Sopenharmony_ci#include <linux/kernel.h>
762306a36Sopenharmony_ci#include <linux/errno.h>
862306a36Sopenharmony_ci#include <linux/module.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/usb.h>
1162306a36Sopenharmony_ci#include <linux/usb/ch11.h>
1262306a36Sopenharmony_ci
1362306a36Sopenharmony_ci#define TEST_SE0_NAK_PID			0x0101
1462306a36Sopenharmony_ci#define TEST_J_PID				0x0102
1562306a36Sopenharmony_ci#define TEST_K_PID				0x0103
1662306a36Sopenharmony_ci#define TEST_PACKET_PID				0x0104
1762306a36Sopenharmony_ci#define TEST_HS_HOST_PORT_SUSPEND_RESUME	0x0106
1862306a36Sopenharmony_ci#define TEST_SINGLE_STEP_GET_DEV_DESC		0x0107
1962306a36Sopenharmony_ci#define TEST_SINGLE_STEP_SET_FEATURE		0x0108
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciextern const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
2262306a36Sopenharmony_ci						const struct usb_device_id *id);
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * A list of USB hubs which requires to disable the power
2662306a36Sopenharmony_ci * to the port before starting the testing procedures.
2762306a36Sopenharmony_ci */
2862306a36Sopenharmony_cistatic const struct usb_device_id ehset_hub_list[] = {
2962306a36Sopenharmony_ci	{ USB_DEVICE(0x0424, 0x4502) },
3062306a36Sopenharmony_ci	{ USB_DEVICE(0x0424, 0x4913) },
3162306a36Sopenharmony_ci	{ USB_DEVICE(0x0451, 0x8027) },
3262306a36Sopenharmony_ci	{ }
3362306a36Sopenharmony_ci};
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_cistatic int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum)
3662306a36Sopenharmony_ci{
3762306a36Sopenharmony_ci	int ret = 0;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	/*
4062306a36Sopenharmony_ci	 * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is
4162306a36Sopenharmony_ci	 * going under test needs to be put in suspend before sending the
4262306a36Sopenharmony_ci	 * test command. Most hubs don't enforce this precondition, but there
4362306a36Sopenharmony_ci	 * are some hubs which needs to disable the power to the port before
4462306a36Sopenharmony_ci	 * starting the test.
4562306a36Sopenharmony_ci	 */
4662306a36Sopenharmony_ci	if (usb_device_match_id(hub_udev, ehset_hub_list)) {
4762306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
4862306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_ENABLE,
4962306a36Sopenharmony_ci					   portnum, NULL, 0, 1000, GFP_KERNEL);
5062306a36Sopenharmony_ci		/*
5162306a36Sopenharmony_ci		 * Wait for the port to be disabled. It's an arbitrary value
5262306a36Sopenharmony_ci		 * which worked every time.
5362306a36Sopenharmony_ci		 */
5462306a36Sopenharmony_ci		msleep(100);
5562306a36Sopenharmony_ci	} else {
5662306a36Sopenharmony_ci		/*
5762306a36Sopenharmony_ci		 * For the hubs which are compliant with the spec,
5862306a36Sopenharmony_ci		 * put the port in SUSPEND.
5962306a36Sopenharmony_ci		 */
6062306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
6162306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
6262306a36Sopenharmony_ci					   portnum, NULL, 0, 1000, GFP_KERNEL);
6362306a36Sopenharmony_ci	}
6462306a36Sopenharmony_ci	return ret;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic int ehset_probe(struct usb_interface *intf,
6862306a36Sopenharmony_ci		       const struct usb_device_id *id)
6962306a36Sopenharmony_ci{
7062306a36Sopenharmony_ci	int ret = -EINVAL;
7162306a36Sopenharmony_ci	struct usb_device *dev = interface_to_usbdev(intf);
7262306a36Sopenharmony_ci	struct usb_device *hub_udev = dev->parent;
7362306a36Sopenharmony_ci	struct usb_device_descriptor buf;
7462306a36Sopenharmony_ci	u8 portnum = dev->portnum;
7562306a36Sopenharmony_ci	u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	switch (test_pid) {
7862306a36Sopenharmony_ci	case TEST_SE0_NAK_PID:
7962306a36Sopenharmony_ci		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
8062306a36Sopenharmony_ci		if (ret < 0)
8162306a36Sopenharmony_ci			break;
8262306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
8362306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_TEST,
8462306a36Sopenharmony_ci					   (USB_TEST_SE0_NAK << 8) | portnum,
8562306a36Sopenharmony_ci					   NULL, 0, 1000, GFP_KERNEL);
8662306a36Sopenharmony_ci		break;
8762306a36Sopenharmony_ci	case TEST_J_PID:
8862306a36Sopenharmony_ci		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
8962306a36Sopenharmony_ci		if (ret < 0)
9062306a36Sopenharmony_ci			break;
9162306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
9262306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_TEST,
9362306a36Sopenharmony_ci					   (USB_TEST_J << 8) | portnum, NULL, 0,
9462306a36Sopenharmony_ci					   1000, GFP_KERNEL);
9562306a36Sopenharmony_ci		break;
9662306a36Sopenharmony_ci	case TEST_K_PID:
9762306a36Sopenharmony_ci		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
9862306a36Sopenharmony_ci		if (ret < 0)
9962306a36Sopenharmony_ci			break;
10062306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
10162306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_TEST,
10262306a36Sopenharmony_ci					   (USB_TEST_K << 8) | portnum, NULL, 0,
10362306a36Sopenharmony_ci					   1000, GFP_KERNEL);
10462306a36Sopenharmony_ci		break;
10562306a36Sopenharmony_ci	case TEST_PACKET_PID:
10662306a36Sopenharmony_ci		ret = ehset_prepare_port_for_testing(hub_udev, portnum);
10762306a36Sopenharmony_ci		if (ret < 0)
10862306a36Sopenharmony_ci			break;
10962306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
11062306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_TEST,
11162306a36Sopenharmony_ci					   (USB_TEST_PACKET << 8) | portnum,
11262306a36Sopenharmony_ci					   NULL, 0, 1000, GFP_KERNEL);
11362306a36Sopenharmony_ci		break;
11462306a36Sopenharmony_ci	case TEST_HS_HOST_PORT_SUSPEND_RESUME:
11562306a36Sopenharmony_ci		/* Test: wait for 15secs -> suspend -> 15secs delay -> resume */
11662306a36Sopenharmony_ci		msleep(15 * 1000);
11762306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
11862306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
11962306a36Sopenharmony_ci					   portnum, NULL, 0, 1000, GFP_KERNEL);
12062306a36Sopenharmony_ci		if (ret < 0)
12162306a36Sopenharmony_ci			break;
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci		msleep(15 * 1000);
12462306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
12562306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
12662306a36Sopenharmony_ci					   portnum, NULL, 0, 1000, GFP_KERNEL);
12762306a36Sopenharmony_ci		break;
12862306a36Sopenharmony_ci	case TEST_SINGLE_STEP_GET_DEV_DESC:
12962306a36Sopenharmony_ci		/* Test: wait for 15secs -> GetDescriptor request */
13062306a36Sopenharmony_ci		msleep(15 * 1000);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR,
13362306a36Sopenharmony_ci					   USB_DIR_IN, USB_DT_DEVICE << 8, 0,
13462306a36Sopenharmony_ci					   &buf, USB_DT_DEVICE_SIZE,
13562306a36Sopenharmony_ci					   USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
13662306a36Sopenharmony_ci		break;
13762306a36Sopenharmony_ci	case TEST_SINGLE_STEP_SET_FEATURE:
13862306a36Sopenharmony_ci		/*
13962306a36Sopenharmony_ci		 * GetDescriptor SETUP request -> 15secs delay -> IN & STATUS
14062306a36Sopenharmony_ci		 *
14162306a36Sopenharmony_ci		 * Note, this test is only supported on root hubs since the
14262306a36Sopenharmony_ci		 * SetPortFeature handling can only be done inside the HCD's
14362306a36Sopenharmony_ci		 * hub_control callback function.
14462306a36Sopenharmony_ci		 */
14562306a36Sopenharmony_ci		if (hub_udev != dev->bus->root_hub) {
14662306a36Sopenharmony_ci			dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n");
14762306a36Sopenharmony_ci			break;
14862306a36Sopenharmony_ci		}
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci		ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
15162306a36Sopenharmony_ci					   USB_RT_PORT, USB_PORT_FEAT_TEST,
15262306a36Sopenharmony_ci					   (6 << 8) | portnum, NULL, 0,
15362306a36Sopenharmony_ci					   60 * 1000, GFP_KERNEL);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		break;
15662306a36Sopenharmony_ci	default:
15762306a36Sopenharmony_ci		dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n",
15862306a36Sopenharmony_ci			__func__, test_pid);
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	return ret;
16262306a36Sopenharmony_ci}
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_cistatic void ehset_disconnect(struct usb_interface *intf)
16562306a36Sopenharmony_ci{
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic const struct usb_device_id ehset_id_table[] = {
16962306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
17062306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_J_PID) },
17162306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_K_PID) },
17262306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
17362306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
17462306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
17562306a36Sopenharmony_ci	{ USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
17662306a36Sopenharmony_ci	{ }			/* Terminating entry */
17762306a36Sopenharmony_ci};
17862306a36Sopenharmony_ciMODULE_DEVICE_TABLE(usb, ehset_id_table);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_cistatic struct usb_driver ehset_driver = {
18162306a36Sopenharmony_ci	.name =		"usb_ehset_test",
18262306a36Sopenharmony_ci	.probe =	ehset_probe,
18362306a36Sopenharmony_ci	.disconnect =	ehset_disconnect,
18462306a36Sopenharmony_ci	.id_table =	ehset_id_table,
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_cimodule_usb_driver(ehset_driver);
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ciMODULE_DESCRIPTION("USB Driver for EHSET Test Fixture");
19062306a36Sopenharmony_ciMODULE_LICENSE("GPL v2");
191