summaryrefslogtreecommitdiff
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c99
1 files changed, 76 insertions, 23 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 7f71218cc1e5..b2bc4b7c4289 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -31,6 +31,7 @@
#include <linux/pm_qos.h>
#include <linux/kobject.h>
+#include <linux/bitfield.h>
#include <linux/uaccess.h>
#include <asm/byteorder.h>
@@ -2668,31 +2669,79 @@ out_authorized:
return result;
}
-/*
- * Return 1 if port speed is SuperSpeedPlus, 0 otherwise
- * check it from the link protocol field of the current speed ID attribute.
- * current speed ID is got from ext port status request. Sublink speed attribute
- * table is returned with the hub BOS SSP device capability descriptor
+/**
+ * get_port_ssp_rate - Match the extended port status to SSP rate
+ * @hdev: The hub device
+ * @ext_portstatus: extended port status
+ *
+ * Match the extended port status speed id to the SuperSpeed Plus sublink speed
+ * capability attributes. Base on the number of connected lanes and speed,
+ * return the corresponding enum usb_ssp_rate.
*/
-static int port_speed_is_ssp(struct usb_device *hdev, int speed_id)
+static enum usb_ssp_rate get_port_ssp_rate(struct usb_device *hdev,
+ u32 ext_portstatus)
{
- int ssa_count;
- u32 ss_attr;
- int i;
struct usb_ssp_cap_descriptor *ssp_cap = hdev->bos->ssp_cap;
+ u32 attr;
+ u8 speed_id;
+ u8 ssac;
+ u8 lanes;
+ int i;
if (!ssp_cap)
- return 0;
+ goto out;
- ssa_count = le32_to_cpu(ssp_cap->bmAttributes) &
+ speed_id = ext_portstatus & USB_EXT_PORT_STAT_RX_SPEED_ID;
+ lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1;
+
+ ssac = le32_to_cpu(ssp_cap->bmAttributes) &
USB_SSP_SUBLINK_SPEED_ATTRIBS;
- for (i = 0; i <= ssa_count; i++) {
- ss_attr = le32_to_cpu(ssp_cap->bmSublinkSpeedAttr[i]);
- if (speed_id == (ss_attr & USB_SSP_SUBLINK_SPEED_SSID))
- return !!(ss_attr & USB_SSP_SUBLINK_SPEED_LP);
+ for (i = 0; i <= ssac; i++) {
+ u8 ssid;
+
+ attr = le32_to_cpu(ssp_cap->bmSublinkSpeedAttr[i]);
+ ssid = FIELD_GET(USB_SSP_SUBLINK_SPEED_SSID, attr);
+ if (speed_id == ssid) {
+ u16 mantissa;
+ u8 lse;
+ u8 type;
+
+ /*
+ * Note: currently asymmetric lane types are only
+ * applicable for SSIC operate in SuperSpeed protocol
+ */
+ type = FIELD_GET(USB_SSP_SUBLINK_SPEED_ST, attr);
+ if (type == USB_SSP_SUBLINK_SPEED_ST_ASYM_RX ||
+ type == USB_SSP_SUBLINK_SPEED_ST_ASYM_TX)
+ goto out;
+
+ if (FIELD_GET(USB_SSP_SUBLINK_SPEED_LP, attr) !=
+ USB_SSP_SUBLINK_SPEED_LP_SSP)
+ goto out;
+
+ lse = FIELD_GET(USB_SSP_SUBLINK_SPEED_LSE, attr);
+ mantissa = FIELD_GET(USB_SSP_SUBLINK_SPEED_LSM, attr);
+
+ /* Convert to Gbps */
+ for (; lse < USB_SSP_SUBLINK_SPEED_LSE_GBPS; lse++)
+ mantissa /= 1000;
+
+ if (mantissa >= 10 && lanes == 1)
+ return USB_SSP_GEN_2x1;
+
+ if (mantissa >= 10 && lanes == 2)
+ return USB_SSP_GEN_2x2;
+
+ if (mantissa >= 5 && lanes == 2)
+ return USB_SSP_GEN_1x2;
+
+ goto out;
+ }
}
- return 0;
+
+out:
+ return USB_SSP_GEN_UNKNOWN;
}
/* Returns 1 if @hub is a WUSB root hub, 0 otherwise */
@@ -2850,15 +2899,15 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
/* extended portstatus Rx and Tx lane count are zero based */
udev->rx_lanes = USB_EXT_PORT_RX_LANES(ext_portstatus) + 1;
udev->tx_lanes = USB_EXT_PORT_TX_LANES(ext_portstatus) + 1;
+ udev->ssp_rate = get_port_ssp_rate(hub->hdev, ext_portstatus);
} else {
udev->rx_lanes = 1;
udev->tx_lanes = 1;
+ udev->ssp_rate = USB_SSP_GEN_UNKNOWN;
}
if (hub_is_wusb(hub))
udev->speed = USB_SPEED_WIRELESS;
- else if (hub_is_superspeedplus(hub->hdev) &&
- port_speed_is_ssp(hub->hdev, ext_portstatus &
- USB_EXT_PORT_STAT_RX_SPEED_ID))
+ else if (udev->ssp_rate != USB_SSP_GEN_UNKNOWN)
udev->speed = USB_SPEED_SUPER_PLUS;
else if (hub_is_superspeed(hub->hdev))
udev->speed = USB_SPEED_SUPER;
@@ -3556,7 +3605,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
u16 portchange, portstatus;
if (!test_and_set_bit(port1, hub->child_usage_bits)) {
- status = pm_runtime_get_sync(&port_dev->dev);
+ status = pm_runtime_resume_and_get(&port_dev->dev);
if (status < 0) {
dev_dbg(&udev->dev, "can't resume usb port, status %d\n",
status);
@@ -4781,9 +4830,13 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
"%s SuperSpeed%s%s USB device number %d using %s\n",
(udev->config) ? "reset" : "new",
(udev->speed == USB_SPEED_SUPER_PLUS) ?
- "Plus Gen 2" : " Gen 1",
- (udev->rx_lanes == 2 && udev->tx_lanes == 2) ?
- "x2" : "",
+ " Plus" : "",
+ (udev->ssp_rate == USB_SSP_GEN_2x2) ?
+ " Gen 2x2" :
+ (udev->ssp_rate == USB_SSP_GEN_2x1) ?
+ " Gen 2x1" :
+ (udev->ssp_rate == USB_SSP_GEN_1x2) ?
+ " Gen 1x2" : "",
devnum, driver_name);
}