diff options
Diffstat (limited to 'drivers/usb/host/xhci-hub.c')
-rw-r--r-- | drivers/usb/host/xhci-hub.c | 237 |
1 files changed, 235 insertions, 2 deletions
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c index 74c497fd3476..eddd139c2260 100644 --- a/drivers/usb/host/xhci-hub.c +++ b/drivers/usb/host/xhci-hub.c @@ -11,6 +11,7 @@ #include <linux/slab.h> #include <asm/unaligned.h> +#include <linux/bitfield.h> #include "xhci.h" #include "xhci-trace.h" @@ -52,7 +53,239 @@ static u8 usb_bos_descriptor [] = { 0xb5, 0x40, 0x0a, 0x00, /* 10Gbps, SSP, symmetric, tx, ID = 5 */ }; -static int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf, +/* Default sublink speed attribute of each lane */ +static u32 ssp_cap_default_ssa[] = { + 0x00050034, /* USB 3.0 SS Gen1x1 id:4 symmetric rx 5Gbps */ + 0x000500b4, /* USB 3.0 SS Gen1x1 id:4 symmetric tx 5Gbps */ + 0x000a4035, /* USB 3.1 SSP Gen2x1 id:5 symmetric rx 10Gbps */ + 0x000a40b5, /* USB 3.1 SSP Gen2x1 id:5 symmetric tx 10Gbps */ + 0x00054036, /* USB 3.2 SSP Gen1x2 id:6 symmetric rx 5Gbps */ + 0x000540b6, /* USB 3.2 SSP Gen1x2 id:6 symmetric tx 5Gbps */ + 0x000a4037, /* USB 3.2 SSP Gen2x2 id:7 symmetric rx 10Gbps */ + 0x000a40b7, /* USB 3.2 SSP Gen2x2 id:7 symmetric tx 10Gbps */ +}; + +static int xhci_create_usb3x_bos_desc(struct xhci_hcd *xhci, char *buf, + u16 wLength) +{ + struct usb_bos_descriptor *bos; + struct usb_ss_cap_descriptor *ss_cap; + struct usb_ssp_cap_descriptor *ssp_cap; + struct xhci_port_cap *port_cap = NULL; + u16 bcdUSB; + u32 reg; + u32 min_rate = 0; + u8 min_ssid; + u8 ssac; + u8 ssic; + int offset; + int i; + + /* BOS descriptor */ + bos = (struct usb_bos_descriptor *)buf; + bos->bLength = USB_DT_BOS_SIZE; + bos->bDescriptorType = USB_DT_BOS; + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE + + USB_DT_USB_SS_CAP_SIZE); + bos->bNumDeviceCaps = 1; + + /* Create the descriptor for port with the highest revision */ + for (i = 0; i < xhci->num_port_caps; i++) { + u8 major = xhci->port_caps[i].maj_rev; + u8 minor = xhci->port_caps[i].min_rev; + u16 rev = (major << 8) | minor; + + if (i == 0 || bcdUSB < rev) { + bcdUSB = rev; + port_cap = &xhci->port_caps[i]; + } + } + + if (bcdUSB >= 0x0310) { + if (port_cap->psi_count) { + u8 num_sym_ssa = 0; + + for (i = 0; i < port_cap->psi_count; i++) { + if ((port_cap->psi[i] & PLT_MASK) == PLT_SYM) + num_sym_ssa++; + } + + ssac = port_cap->psi_count + num_sym_ssa - 1; + ssic = port_cap->psi_uid_count - 1; + } else { + if (bcdUSB >= 0x0320) + ssac = 7; + else + ssac = 3; + + ssic = (ssac + 1) / 2 - 1; + } + + bos->bNumDeviceCaps++; + bos->wTotalLength = cpu_to_le16(USB_DT_BOS_SIZE + + USB_DT_USB_SS_CAP_SIZE + + USB_DT_USB_SSP_CAP_SIZE(ssac)); + } + + if (wLength < USB_DT_BOS_SIZE + USB_DT_USB_SS_CAP_SIZE) + return wLength; + + /* SuperSpeed USB Device Capability */ + ss_cap = (struct usb_ss_cap_descriptor *)&buf[USB_DT_BOS_SIZE]; + ss_cap->bLength = USB_DT_USB_SS_CAP_SIZE; + ss_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ss_cap->bDevCapabilityType = USB_SS_CAP_TYPE; + ss_cap->bmAttributes = 0; /* set later */ + ss_cap->wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION); + ss_cap->bFunctionalitySupport = USB_LOW_SPEED_OPERATION; + ss_cap->bU1devExitLat = 0; /* set later */ + ss_cap->bU2DevExitLat = 0; /* set later */ + + reg = readl(&xhci->cap_regs->hcc_params); + if (HCC_LTC(reg)) + ss_cap->bmAttributes |= USB_LTM_SUPPORT; + + if ((xhci->quirks & XHCI_LPM_SUPPORT)) { + reg = readl(&xhci->cap_regs->hcs_params3); + ss_cap->bU1devExitLat = HCS_U1_LATENCY(reg); + ss_cap->bU2DevExitLat = cpu_to_le16(HCS_U2_LATENCY(reg)); + } + + if (wLength < le16_to_cpu(bos->wTotalLength)) + return wLength; + + if (bcdUSB < 0x0310) + return le16_to_cpu(bos->wTotalLength); + + ssp_cap = (struct usb_ssp_cap_descriptor *)&buf[USB_DT_BOS_SIZE + + USB_DT_USB_SS_CAP_SIZE]; + ssp_cap->bLength = USB_DT_USB_SSP_CAP_SIZE(ssac); + ssp_cap->bDescriptorType = USB_DT_DEVICE_CAPABILITY; + ssp_cap->bDevCapabilityType = USB_SSP_CAP_TYPE; + ssp_cap->bReserved = 0; + ssp_cap->wReserved = 0; + ssp_cap->bmAttributes = + cpu_to_le32(FIELD_PREP(USB_SSP_SUBLINK_SPEED_ATTRIBS, ssac) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_IDS, ssic)); + + if (!port_cap->psi_count) { + for (i = 0; i < ssac + 1; i++) + ssp_cap->bmSublinkSpeedAttr[i] = + cpu_to_le32(ssp_cap_default_ssa[i]); + + min_ssid = 4; + goto out; + } + + offset = 0; + for (i = 0; i < port_cap->psi_count; i++) { + u32 psi; + u32 attr; + u8 ssid; + u8 lp; + u8 lse; + u8 psie; + u16 lane_mantissa; + u16 psim; + u16 plt; + + psi = port_cap->psi[i]; + ssid = XHCI_EXT_PORT_PSIV(psi); + lp = XHCI_EXT_PORT_LP(psi); + psie = XHCI_EXT_PORT_PSIE(psi); + psim = XHCI_EXT_PORT_PSIM(psi); + plt = psi & PLT_MASK; + + lse = psie; + lane_mantissa = psim; + + /* Shift to Gbps and set SSP Link Protocol if 10Gpbs */ + for (; psie < USB_SSP_SUBLINK_SPEED_LSE_GBPS; psie++) + psim /= 1000; + + if (!min_rate || psim < min_rate) { + min_ssid = ssid; + min_rate = psim; + } + + /* Some host controllers don't set the link protocol for SSP */ + if (psim >= 10) + lp = USB_SSP_SUBLINK_SPEED_LP_SSP; + + /* + * PSIM and PSIE represent the total speed of PSI. The BOS + * descriptor SSP sublink speed attribute lane mantissa + * describes the lane speed. E.g. PSIM and PSIE for gen2x2 + * is 20Gbps, but the BOS descriptor lane speed mantissa is + * 10Gbps. Check and modify the mantissa value to match the + * lane speed. + */ + if (bcdUSB == 0x0320 && plt == PLT_SYM) { + /* + * The PSI dword for gen1x2 and gen2x1 share the same + * values. But the lane speed for gen1x2 is 5Gbps while + * gen2x1 is 10Gbps. If the previous PSI dword SSID is + * 5 and the PSIE and PSIM match with SSID 6, let's + * assume that the controller follows the default speed + * id with SSID 6 for gen1x2. + */ + if (ssid == 6 && psie == 3 && psim == 10 && i) { + u32 prev = port_cap->psi[i - 1]; + + if ((prev & PLT_MASK) == PLT_SYM && + XHCI_EXT_PORT_PSIV(prev) == 5 && + XHCI_EXT_PORT_PSIE(prev) == 3 && + XHCI_EXT_PORT_PSIM(prev) == 10) { + lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS; + lane_mantissa = 5; + } + } + + if (psie == 3 && psim > 10) { + lse = USB_SSP_SUBLINK_SPEED_LSE_GBPS; + lane_mantissa = 10; + } + } + + attr = (FIELD_PREP(USB_SSP_SUBLINK_SPEED_SSID, ssid) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LP, lp) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSE, lse) | + FIELD_PREP(USB_SSP_SUBLINK_SPEED_LSM, lane_mantissa)); + + switch (plt) { + case PLT_SYM: + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_SYM_RX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + + attr &= ~USB_SSP_SUBLINK_SPEED_ST; + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_SYM_TX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + break; + case PLT_ASYM_RX: + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_ASYM_RX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + break; + case PLT_ASYM_TX: + attr |= FIELD_PREP(USB_SSP_SUBLINK_SPEED_ST, + USB_SSP_SUBLINK_SPEED_ST_ASYM_TX); + ssp_cap->bmSublinkSpeedAttr[offset++] = cpu_to_le32(attr); + break; + } + } +out: + ssp_cap->wFunctionalitySupport = + cpu_to_le16(FIELD_PREP(USB_SSP_MIN_SUBLINK_SPEED_ATTRIBUTE_ID, + min_ssid) | + FIELD_PREP(USB_SSP_MIN_RX_LANE_COUNT, 1) | + FIELD_PREP(USB_SSP_MIN_TX_LANE_COUNT, 1)); + + return le16_to_cpu(bos->wTotalLength); +} + +static __maybe_unused int xhci_create_usb3_bos_desc(struct xhci_hcd *xhci, char *buf, u16 wLength) { struct xhci_port_cap *port_cap = NULL; @@ -1137,7 +1370,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, if (hcd->speed < HCD_USB3) goto error; - retval = xhci_create_usb3_bos_desc(xhci, buf, wLength); + retval = xhci_create_usb3x_bos_desc(xhci, buf, wLength); spin_unlock_irqrestore(&xhci->lock, flags); return retval; case GetPortStatus: |