diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 99 | 
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);  			} | 
