From 886ee36e7205a7b850e2e5c2298a479f581f9b3b Mon Sep 17 00:00:00 2001
From: Felipe Balbi <felipe.balbi@linux.intel.com>
Date: Thu, 14 Dec 2017 09:50:39 +0200
Subject: usb: core: add support for USB_REQ_SET_ISOCH_DELAY

USB SS and SSP hubs provide wHubDelay values on their hub descriptor
which we should inform the USB Device about.

The USB Specification 3.0 explains, on section 9.4.11, how to
calculate the value and how to issue the request. Note that a
USB_REQ_SET_ISOCH_DELAY is valid on all device states (Default,
Address, Configured), we just *chose* to issue it from Address state
right after successfully fetching the USB Device Descriptor.

Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 drivers/usb/core/hub.c     | 30 ++++++++++++++++++++++++++++++
 drivers/usb/core/message.c | 24 ++++++++++++++++++++++++
 drivers/usb/core/usb.h     |  1 +
 3 files changed, 55 insertions(+)

(limited to 'drivers')

diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 916bcd539afa..09bd3c3ad8b1 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -38,6 +38,9 @@
 #define USB_VENDOR_GENESYS_LOGIC		0x05e3
 #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND	0x01
 
+#define USB_TP_TRANSMISSION_DELAY	40	/* ns */
+#define USB_TP_TRANSMISSION_DELAY_MAX	65535	/* ns */
+
 /* Protect struct usb_device->state and ->children members
  * Note: Both are also protected by ->dev.sem, except that ->state can
  * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */
@@ -1350,6 +1353,20 @@ static int hub_configure(struct usb_hub *hub,
 		goto fail;
 	}
 
+	/*
+	 * Accumulate wHubDelay + 40ns for every hub in the tree of devices.
+	 * The resulting value will be used for SetIsochDelay() request.
+	 */
+	if (hub_is_superspeed(hdev) || hub_is_superspeedplus(hdev)) {
+		u32 delay = __le16_to_cpu(hub->descriptor->u.ss.wHubDelay);
+
+		if (hdev->parent)
+			delay += hdev->parent->hub_delay;
+
+		delay += USB_TP_TRANSMISSION_DELAY;
+		hdev->hub_delay = min_t(u32, delay, USB_TP_TRANSMISSION_DELAY_MAX);
+	}
+
 	maxchild = hub->descriptor->bNbrPorts;
 	dev_info(hub_dev, "%d port%s detected\n", maxchild,
 			(maxchild == 1) ? "" : "s");
@@ -4597,7 +4614,20 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
 			if (retval >= 0)
 				retval = -EMSGSIZE;
 		} else {
+			u32 delay;
+
 			retval = 0;
+
+			delay = udev->parent->hub_delay;
+			udev->hub_delay = min_t(u32, delay,
+						USB_TP_TRANSMISSION_DELAY_MAX);
+			retval = usb_set_isoch_delay(udev);
+			if (retval) {
+				dev_dbg(&udev->dev,
+					"Failed set isoch delay, error %d\n",
+					retval);
+				retval = 0;
+			}
 			break;
 		}
 	}
diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c
index dac4adeec213..c64cf6c4a83d 100644
--- a/drivers/usb/core/message.c
+++ b/drivers/usb/core/message.c
@@ -916,6 +916,30 @@ int usb_get_device_descriptor(struct usb_device *dev, unsigned int size)
 	return ret;
 }
 
+/*
+ * usb_set_isoch_delay - informs the device of the packet transmit delay
+ * @dev: the device whose delay is to be informed
+ * Context: !in_interrupt()
+ *
+ * Since this is an optional request, we don't bother if it fails.
+ */
+int usb_set_isoch_delay(struct usb_device *dev)
+{
+	/* skip hub devices */
+	if (dev->descriptor.bDeviceClass == USB_CLASS_HUB)
+		return 0;
+
+	/* skip non-SS/non-SSP devices */
+	if (dev->speed < USB_SPEED_SUPER)
+		return 0;
+
+	return usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+			USB_REQ_SET_ISOCH_DELAY,
+			USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE,
+			cpu_to_le16(dev->hub_delay), 0, NULL, 0,
+			USB_CTRL_SET_TIMEOUT);
+}
+
 /**
  * usb_get_status - issues a GET_STATUS call
  * @dev: the device whose status is being checked
diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h
index 2bee08d084ae..149cc7480971 100644
--- a/drivers/usb/core/usb.h
+++ b/drivers/usb/core/usb.h
@@ -40,6 +40,7 @@ extern int usb_remove_device(struct usb_device *udev);
 
 extern int usb_get_device_descriptor(struct usb_device *dev,
 		unsigned int size);
+extern int usb_set_isoch_delay(struct usb_device *dev);
 extern int usb_get_bos_descriptor(struct usb_device *dev);
 extern void usb_release_bos_descriptor(struct usb_device *dev);
 extern char *usb_cache_string(struct usb_device *udev, int index);
-- 
cgit v1.2.3