USB 3.0 Hub Changes

Update the USB core to deal with USB 3.0 hubs.  These hubs have a slightly
different hub descriptor than USB 2.0 hubs, with a fixed (rather than
variable length) size.  Change the USB core's hub descriptor to have a
union for the last fields that differ.  Change the host controller drivers
that access those last fields (DeviceRemovable and PortPowerCtrlMask) to
use the union.

Translate the new version of the hub port status field into the old
version that khubd understands.  (Note: we need to fix it to translate the
roothub's port status once we stop converting it to USB 2.0 hub status
internally.)

Add new code to handle link state change status.  Send out new control
messages that are needed for USB 3.0 hubs, like Set Hub Depth.

This patch is a modified version of the original patch submitted by John
Youn.  It's updated to reflect the removal of the "bitmap" #define, and
change the hub descriptor accesses of a couple new host controller
drivers.

Signed-off-by: John Youn <johnyoun@synopsys.com>
Signed-off-by: Sarah Sharp <sarah.a.sharp@linux.intel.com>
Cc: Nobuhiro Iwamatsu <nobuhiro.iwamatsu.yj@renesas.com>
Cc: Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com>
Cc: Tony Olech <tony.olech@elandigitalsystems.com>
Cc: "Robert P. J. Day" <rpjday@crashcourse.ca>
Cc: Max Vozeler <mvz@vozeler.com>
Cc: Tejun Heo <tj@kernel.org>
Cc: Yoshihiro Shimoda <yoshihiro.shimoda.uh@renesas.com>
Cc: Rodolfo Giometti <giometti@linux.it>
Cc: Mike Frysinger <vapier@gentoo.org>
Cc: Anton Vorontsov <avorontsov@mvista.com>
Cc: Sebastian Siewior <bigeasy@linutronix.de>
Cc: Lothar Wassmann <LW@KARO-electronics.de>
Cc: Olav Kongas <ok@artecdesign.ee>
Cc: Martin Fuzzey <mfuzzey@gmail.com>
Cc: Alan Stern <stern@rowland.harvard.edu>
Cc: David Brownell <dbrownell@users.sourceforge.net>
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index b574f91..feb6e59 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -82,6 +82,10 @@
 	void			**port_owners;
 };
 
+static inline int hub_is_superspeed(struct usb_device *hdev)
+{
+	return (hdev->descriptor.bDeviceProtocol == 3);
+}
 
 /* Protect struct usb_device->state and ->children members
  * Note: Both are also protected by ->dev.sem, except that ->state can
@@ -172,14 +176,23 @@
 }
 
 /* USB 2.0 spec Section 11.24.4.5 */
-static int get_hub_descriptor(struct usb_device *hdev, void *data, int size)
+static int get_hub_descriptor(struct usb_device *hdev, void *data)
 {
-	int i, ret;
+	int i, ret, size;
+	unsigned dtype;
+
+	if (hub_is_superspeed(hdev)) {
+		dtype = USB_DT_SS_HUB;
+		size = USB_DT_SS_HUB_SIZE;
+	} else {
+		dtype = USB_DT_HUB;
+		size = sizeof(struct usb_hub_descriptor);
+	}
 
 	for (i = 0; i < 3; i++) {
 		ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
 			USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
-			USB_DT_HUB << 8, 0, data, size,
+			dtype << 8, 0, data, size,
 			USB_CTRL_GET_TIMEOUT);
 		if (ret >= (USB_DT_HUB_NONVAR_SIZE + 2))
 			return ret;
@@ -365,6 +378,19 @@
 	} else {
 		*status = le16_to_cpu(hub->status->port.wPortStatus);
 		*change = le16_to_cpu(hub->status->port.wPortChange);
+
+		if ((hub->hdev->parent != NULL) &&
+				hub_is_superspeed(hub->hdev)) {
+			/* Translate the USB 3 port status */
+			u16 tmp = *status & USB_SS_PORT_STAT_MASK;
+			if (*status & USB_SS_PORT_STAT_POWER)
+				tmp |= USB_PORT_STAT_POWER;
+			if ((*status & USB_SS_PORT_STAT_SPEED) ==
+					USB_PORT_STAT_SPEED_5GBPS)
+				tmp |= USB_PORT_STAT_SUPER_SPEED;
+			*status = tmp;
+		}
+
 		ret = 0;
 	}
 	mutex_unlock(&hub->status_mutex);
@@ -607,7 +633,7 @@
 	if (hdev->children[port1-1] && set_state)
 		usb_set_device_state(hdev->children[port1-1],
 				USB_STATE_NOTATTACHED);
-	if (!hub->error)
+	if (!hub->error && !hub_is_superspeed(hub->hdev))
 		ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
 	if (ret)
 		dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
@@ -795,6 +821,11 @@
 			clear_port_feature(hub->hdev, port1,
 					USB_PORT_FEAT_C_ENABLE);
 		}
+		if (portchange & USB_PORT_STAT_C_LINK_STATE) {
+			need_debounce_delay = true;
+			clear_port_feature(hub->hdev, port1,
+					USB_PORT_FEAT_C_PORT_LINK_STATE);
+		}
 
 		/* We can forget about a "removed" device when there's a
 		 * physical disconnect or the connect status changes.
@@ -964,12 +995,23 @@
 		goto fail;
 	}
 
+	if (hub_is_superspeed(hdev) && (hdev->parent != NULL)) {
+		ret = usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+				HUB_SET_DEPTH, USB_RT_HUB,
+				hdev->level - 1, 0, NULL, 0,
+				USB_CTRL_SET_TIMEOUT);
+
+		if (ret < 0) {
+			message = "can't set hub depth";
+			goto fail;
+		}
+	}
+
 	/* Request the entire hub descriptor.
 	 * hub->descriptor can handle USB_MAXCHILDREN ports,
 	 * but the hub can/will return fewer bytes here.
 	 */
-	ret = get_hub_descriptor(hdev, hub->descriptor,
-			sizeof(*hub->descriptor));
+	ret = get_hub_descriptor(hdev, hub->descriptor);
 	if (ret < 0) {
 		message = "can't read hub descriptor";
 		goto fail;
@@ -991,12 +1033,14 @@
 
 	wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
 
-	if (wHubCharacteristics & HUB_CHAR_COMPOUND) {
+	/* FIXME for USB 3.0, skip for now */
+	if ((wHubCharacteristics & HUB_CHAR_COMPOUND) &&
+			!(hub_is_superspeed(hdev))) {
 		int	i;
 		char	portstr [USB_MAXCHILDREN + 1];
 
 		for (i = 0; i < hdev->maxchild; i++)
-			portstr[i] = hub->descriptor->DeviceRemovable
+			portstr[i] = hub->descriptor->u.hs.DeviceRemovable
 				    [((i + 1) / 8)] & (1 << ((i + 1) % 8))
 				? 'F' : 'R';
 		portstr[hdev->maxchild] = 0;
@@ -2029,6 +2073,8 @@
 				udev->speed = USB_SPEED_HIGH;
 			else if (portstatus & USB_PORT_STAT_LOW_SPEED)
 				udev->speed = USB_SPEED_LOW;
+			else if (portstatus & USB_PORT_STAT_SUPER_SPEED)
+				udev->speed = USB_SPEED_SUPER;
 			else
 				udev->speed = USB_SPEED_FULL;
 			return 0;
@@ -3430,6 +3476,17 @@
 				clear_port_feature(hdev, i,
 					USB_PORT_FEAT_C_RESET);
 			}
+			if (portchange & USB_PORT_STAT_C_LINK_STATE) {
+				clear_port_feature(hub->hdev, i,
+						USB_PORT_FEAT_C_PORT_LINK_STATE);
+			}
+			if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) {
+				dev_warn(hub_dev,
+					"config error on port %d\n",
+					i);
+				clear_port_feature(hub->hdev, i,
+						USB_PORT_FEAT_C_PORT_CONFIG_ERROR);
+			}
 
 			if (connect_change)
 				hub_port_connect_change(hub, i,