|
@@ -877,6 +877,60 @@ static int hub_hub_status(struct usb_hub *hub,
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
+static int hub_set_port_link_state(struct usb_hub *hub, int port1,
|
|
|
+ unsigned int link_status)
|
|
|
+{
|
|
|
+ return set_port_feature(hub->hdev,
|
|
|
+ port1 | (link_status << 3),
|
|
|
+ USB_PORT_FEAT_LINK_STATE);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * If USB 3.0 ports are placed into the Disabled state, they will no longer
|
|
|
+ * detect any device connects or disconnects. This is generally not what the
|
|
|
+ * USB core wants, since it expects a disabled port to produce a port status
|
|
|
+ * change event when a new device connects.
|
|
|
+ *
|
|
|
+ * Instead, set the link state to Disabled, wait for the link to settle into
|
|
|
+ * that state, clear any change bits, and then put the port into the RxDetect
|
|
|
+ * state.
|
|
|
+ */
|
|
|
+static int hub_usb3_port_disable(struct usb_hub *hub, int port1)
|
|
|
+{
|
|
|
+ int ret;
|
|
|
+ int total_time;
|
|
|
+ u16 portchange, portstatus;
|
|
|
+
|
|
|
+ if (!hub_is_superspeed(hub->hdev))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ ret = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED);
|
|
|
+ if (ret) {
|
|
|
+ dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
|
|
|
+ port1, ret);
|
|
|
+ return ret;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Wait for the link to enter the disabled state. */
|
|
|
+ for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) {
|
|
|
+ ret = hub_port_status(hub, port1, &portstatus, &portchange);
|
|
|
+ if (ret < 0)
|
|
|
+ return ret;
|
|
|
+
|
|
|
+ if ((portstatus & USB_PORT_STAT_LINK_STATE) ==
|
|
|
+ USB_SS_PORT_LS_SS_DISABLED)
|
|
|
+ break;
|
|
|
+ if (total_time >= HUB_DEBOUNCE_TIMEOUT)
|
|
|
+ break;
|
|
|
+ msleep(HUB_DEBOUNCE_STEP);
|
|
|
+ }
|
|
|
+ if (total_time >= HUB_DEBOUNCE_TIMEOUT)
|
|
|
+ dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n",
|
|
|
+ port1, total_time);
|
|
|
+
|
|
|
+ return hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_RX_DETECT);
|
|
|
+}
|
|
|
+
|
|
|
static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
|
|
|
{
|
|
|
struct usb_device *hdev = hub->hdev;
|
|
@@ -885,8 +939,13 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)
|
|
|
if (hub->ports[port1 - 1]->child && set_state)
|
|
|
usb_set_device_state(hub->ports[port1 - 1]->child,
|
|
|
USB_STATE_NOTATTACHED);
|
|
|
- if (!hub->error && !hub_is_superspeed(hub->hdev))
|
|
|
- ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE);
|
|
|
+ if (!hub->error) {
|
|
|
+ if (hub_is_superspeed(hub->hdev))
|
|
|
+ ret = hub_usb3_port_disable(hub, port1);
|
|
|
+ else
|
|
|
+ ret = clear_port_feature(hdev, port1,
|
|
|
+ USB_PORT_FEAT_ENABLE);
|
|
|
+ }
|
|
|
if (ret)
|
|
|
dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",
|
|
|
port1, ret);
|