*/
memset(port_removable, 0, sizeof(port_removable));
for (i = 0; i < ports; i++) {
- portsc = xhci_readl(xhci, xhci->usb3_ports[i]);
+ portsc = xhci_readl(xhci, xhci->usb2_ports[i]);
/* If a device is removable, PORTSC reports a 0, same as in the
* hub descriptor DeviceRemovable bits.
*/
ret = 0;
virt_dev = xhci->devs[slot_id];
+ if (!virt_dev)
+ return -ENODEV;
+
cmd = xhci_alloc_command(xhci, false, true, GFP_NOIO);
if (!cmd) {
xhci_dbg(xhci, "Couldn't allocate command structure.\n");
status = PORT_PLC;
port_change_bit = "link state";
break;
+ case USB_PORT_FEAT_C_PORT_CONFIG_ERROR:
+ status = PORT_CEC;
+ port_change_bit = "config error";
+ break;
default:
/* Should never happen */
return;
}
}
+/* Updates Link Status for super Speed port */
+static void xhci_hub_report_link_state(struct xhci_hcd *xhci,
+ u32 *status, u32 status_reg)
+{
+ u32 pls = status_reg & PORT_PLS_MASK;
+
+ /* resume state is a xHCI internal state.
+ * Do not report it to usb core, instead, pretend to be U3,
+ * thus usb core knows it's not ready for transfer
+ */
+ if (pls == XDEV_RESUME) {
+ *status |= USB_SS_PORT_LS_U3;
+ return;
+ }
+
+ /* When the CAS bit is set then warm reset
+ * should be performed on port
+ */
+ if (status_reg & PORT_CAS) {
+ /* The CAS bit can be set while the port is
+ * in any link state.
+ * Only roothubs have CAS bit, so we
+ * pretend to be in compliance mode
+ * unless we're already in compliance
+ * or the inactive state.
+ */
+ if (pls != USB_SS_PORT_LS_COMP_MOD &&
+ pls != USB_SS_PORT_LS_SS_INACTIVE) {
+ pls = USB_SS_PORT_LS_COMP_MOD;
+ }
+ /* Return also connection bit -
+ * hub state machine resets port
+ * when this bit is set.
+ */
+ pls |= USB_PORT_STAT_CONNECTION;
+ } else {
+ /*
+ * If CAS bit isn't set but the Port is already at
+ * Compliance Mode, fake a connection so the USB core
+ * notices the Compliance state and resets the port.
+ * This resolves an issue generated by the SN65LVPE502CP
+ * in which sometimes the port enters compliance mode
+ * caused by a delay on the host-device negotiation.
+ */
+ if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) &&
+ (pls == USB_SS_PORT_LS_COMP_MOD))
+ pls |= USB_PORT_STAT_CONNECTION;
+ }
+
+ /* update status field */
+ *status |= pls;
+}
+
+/*
+ * Function for Compliance Mode Quirk.
+ *
+ * This Function verifies if all xhc USB3 ports have entered U0, if so,
+ * the compliance mode timer is deleted. A port won't enter
+ * compliance mode if it has previously entered U0.
+ */
+void xhci_del_comp_mod_timer(struct xhci_hcd *xhci, u32 status, u16 wIndex)
+{
+ u32 all_ports_seen_u0 = ((1 << xhci->num_usb3_ports)-1);
+ bool port_in_u0 = ((status & PORT_PLS_MASK) == XDEV_U0);
+
+ if (!(xhci->quirks & XHCI_COMP_MODE_QUIRK))
+ return;
+
+ if ((xhci->port_status_u0 != all_ports_seen_u0) && port_in_u0) {
+ xhci->port_status_u0 |= 1 << wIndex;
+ if (xhci->port_status_u0 == all_ports_seen_u0) {
+ del_timer_sync(&xhci->comp_mode_recovery_timer);
+ xhci_dbg(xhci, "All USB3 ports have entered U0 already!\n");
+ xhci_dbg(xhci, "Compliance Mode Recovery Timer Deleted.\n");
+ }
+ }
+}
+
int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buf, u16 wLength)
{
status |= USB_PORT_STAT_C_RESET << 16;
/* USB3.0 only */
if (hcd->speed == HCD_USB3) {
- if ((temp & PORT_PLC))
+ /* Port link change with port in resume state should not be
+ * reported to usbcore, as this is an internal state to be
+ * handled by xhci driver. Reporting PLC to usbcore may
+ * cause usbcore clearing PLC first and port change event
+ * irq won't be generated.
+ */
+ if ((temp & PORT_PLC) &&
+ (temp & PORT_PLS_MASK) != XDEV_RESUME)
status |= USB_PORT_STAT_C_LINK_STATE << 16;
if ((temp & PORT_WRC))
status |= USB_PORT_STAT_C_BH_RESET << 16;
+ if ((temp & PORT_CEC))
+ status |= USB_PORT_STAT_C_CONFIG_ERROR << 16;
}
if (hcd->speed != HCD_USB3) {
else
status |= USB_PORT_STAT_POWER;
}
- /* Port Link State */
+ /* Update Port Link State for super speed ports*/
if (hcd->speed == HCD_USB3) {
- /* resume state is a xHCI internal state.
- * Do not report it to usb core.
+ xhci_hub_report_link_state(xhci, &status, temp);
+ /*
+ * Verify if all USB3 Ports Have entered U0 already.
+ * Delete Compliance Mode Timer if so.
*/
- if ((temp & PORT_PLS_MASK) != XDEV_RESUME)
- status |= (temp & PORT_PLS_MASK);
+ xhci_del_comp_mod_timer(xhci, temp, wIndex);
}
if (bus_state->port_c_suspend & (1 << wIndex))
status |= 1 << USB_PORT_FEAT_C_SUSPEND;
break;
case USB_PORT_FEAT_LINK_STATE:
temp = xhci_readl(xhci, port_array[wIndex]);
+
+ /* Disable port */
+ if (link_state == USB_SS_PORT_LS_SS_DISABLED) {
+ xhci_dbg(xhci, "Disable port %d\n", wIndex);
+ temp = xhci_port_state_to_neutral(temp);
+ /*
+ * Clear all change bits, so that we get a new
+ * connection event.
+ */
+ temp |= PORT_CSC | PORT_PEC | PORT_WRC |
+ PORT_OCC | PORT_RC | PORT_PLC |
+ PORT_CEC;
+ xhci_writel(xhci, temp | PORT_PE,
+ port_array[wIndex]);
+ temp = xhci_readl(xhci, port_array[wIndex]);
+ break;
+ }
+
+ /* Put link in RxDetect (enable port) */
+ if (link_state == USB_SS_PORT_LS_RX_DETECT) {
+ xhci_dbg(xhci, "Enable port %d\n", wIndex);
+ xhci_set_link_state(xhci, port_array, wIndex,
+ link_state);
+ temp = xhci_readl(xhci, port_array[wIndex]);
+ break;
+ }
+
/* Software should not attempt to set
- * port link state above '5' (Rx.Detect) and the port
+ * port link state above '3' (U3) and the port
* must be enabled.
*/
if ((temp & PORT_PE) == 0 ||
- (link_state > USB_SS_PORT_LS_RX_DETECT)) {
+ (link_state > USB_SS_PORT_LS_U3)) {
xhci_warn(xhci, "Cannot set link state.\n");
goto error;
}
case USB_PORT_FEAT_C_OVER_CURRENT:
case USB_PORT_FEAT_C_ENABLE:
case USB_PORT_FEAT_C_PORT_LINK_STATE:
+ case USB_PORT_FEAT_C_PORT_CONFIG_ERROR:
xhci_clear_port_change_bit(xhci, wValue, wIndex,
port_array[wIndex], temp);
break;
int max_ports;
__le32 __iomem **port_array;
struct xhci_bus_state *bus_state;
+ bool reset_change = false;
max_ports = xhci_get_ports(hcd, &port_array);
bus_state = &xhci->bus_state[hcd_index(hcd)];
memset(buf, 0, retval);
status = 0;
- mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC;
+ mask = PORT_CSC | PORT_PEC | PORT_OCC | PORT_PLC | PORT_WRC | PORT_CEC;
spin_lock_irqsave(&xhci->lock, flags);
/* For each port, did anything change? If so, set that bit in buf. */
buf[(i + 1) / 8] |= 1 << (i + 1) % 8;
status = 1;
}
+ if ((temp & PORT_RC))
+ reset_change = true;
+ }
+ if (!status && !reset_change) {
+ xhci_dbg(xhci, "%s: stopping port polling.\n", __func__);
+ clear_bit(HCD_FLAG_POLL_RH, &hcd->flags);
}
spin_unlock_irqrestore(&xhci->lock, flags);
return status ? retval : 0;
t1 = xhci_port_state_to_neutral(t1);
if (t1 != t2)
xhci_writel(xhci, t2, port_array[port_index]);
-
- if (hcd->speed != HCD_USB3) {
- /* enable remote wake up for USB 2.0 */
- __le32 __iomem *addr;
- u32 tmp;
-
- /* Add one to the port status register address to get
- * the port power control register address.
- */
- addr = port_array[port_index] + 1;
- tmp = xhci_readl(xhci, addr);
- tmp |= PORT_RWE;
- xhci_writel(xhci, tmp, addr);
- }
}
hcd->state = HC_STATE_SUSPENDED;
bus_state->next_statechange = jiffies + msecs_to_jiffies(10);
xhci_ring_device(xhci, slot_id);
} else
xhci_writel(xhci, temp, port_array[port_index]);
-
- if (hcd->speed != HCD_USB3) {
- /* disable remote wake up for USB 2.0 */
- __le32 __iomem *addr;
- u32 tmp;
-
- /* Add one to the port status register address to get
- * the port power control register address.
- */
- addr = port_array[port_index] + 1;
- tmp = xhci_readl(xhci, addr);
- tmp &= ~PORT_RWE;
- xhci_writel(xhci, tmp, addr);
- }
}
(void) xhci_readl(xhci, &xhci->op_regs->command);