static int xhci_create_usb3x_bos_desc()

in host/xhci-hub.c [35:253]


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);
}