|
@@ -103,6 +103,8 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem);
|
|
|
|
|
|
static void hub_release(struct kref *kref);
|
|
|
static int usb_reset_and_verify_device(struct usb_device *udev);
|
|
|
+static void hub_usb3_port_prepare_disable(struct usb_hub *hub,
|
|
|
+ struct usb_port *port_dev);
|
|
|
|
|
|
static inline char *portspeed(struct usb_hub *hub, int portstatus)
|
|
|
{
|
|
@@ -901,82 +903,28 @@ static int hub_set_port_link_state(struct usb_hub *hub, int port1,
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
- * 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.
|
|
|
+ * USB-3 does not have a similar link state as USB-2 that will avoid negotiating
|
|
|
+ * a connection with a plugged-in cable but will signal the host when the cable
|
|
|
+ * is unplugged. Disable remote wake and set link state to U3 for USB-3 devices
|
|
|
*/
|
|
|
-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_port_status(hub, port1, &portstatus, &portchange);
|
|
|
- if (ret < 0)
|
|
|
- return ret;
|
|
|
-
|
|
|
- /*
|
|
|
- * USB controller Advanced Micro Devices, Inc. [AMD] FCH USB XHCI
|
|
|
- * Controller [1022:7814] will have spurious result making the following
|
|
|
- * usb 3.0 device hotplugging route to the 2.0 root hub and recognized
|
|
|
- * as high-speed device if we set the usb 3.0 port link state to
|
|
|
- * Disabled. Since it's already in USB_SS_PORT_LS_RX_DETECT state, we
|
|
|
- * check the state here to avoid the bug.
|
|
|
- */
|
|
|
- if ((portstatus & USB_PORT_STAT_LINK_STATE) ==
|
|
|
- USB_SS_PORT_LS_RX_DETECT) {
|
|
|
- dev_dbg(&hub->ports[port1 - 1]->dev,
|
|
|
- "Not disabling port; link state is RxDetect\n");
|
|
|
- return ret;
|
|
|
- }
|
|
|
-
|
|
|
- ret = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED);
|
|
|
- if (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->ports[port1 - 1]->dev,
|
|
|
- "Could not disable after %d ms\n", 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_port *port_dev = hub->ports[port1 - 1];
|
|
|
struct usb_device *hdev = hub->hdev;
|
|
|
int ret = 0;
|
|
|
|
|
|
- if (port_dev->child && set_state)
|
|
|
- usb_set_device_state(port_dev->child, USB_STATE_NOTATTACHED);
|
|
|
if (!hub->error) {
|
|
|
- if (hub_is_superspeed(hub->hdev))
|
|
|
- ret = hub_usb3_port_disable(hub, port1);
|
|
|
- else
|
|
|
+ if (hub_is_superspeed(hub->hdev)) {
|
|
|
+ hub_usb3_port_prepare_disable(hub, port_dev);
|
|
|
+ ret = hub_set_port_link_state(hub, port_dev->portnum,
|
|
|
+ USB_SS_PORT_LS_U3);
|
|
|
+ } else {
|
|
|
ret = usb_clear_port_feature(hdev, port1,
|
|
|
USB_PORT_FEAT_ENABLE);
|
|
|
+ }
|
|
|
}
|
|
|
+ if (port_dev->child && set_state)
|
|
|
+ usb_set_device_state(port_dev->child, USB_STATE_NOTATTACHED);
|
|
|
if (ret && ret != -ENODEV)
|
|
|
dev_err(&port_dev->dev, "cannot disable (err = %d)\n", ret);
|
|
|
return ret;
|
|
@@ -4142,6 +4090,26 @@ void usb_unlocked_enable_lpm(struct usb_device *udev)
|
|
|
}
|
|
|
EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
|
|
|
|
|
|
+/* usb3 devices use U3 for disabled, make sure remote wakeup is disabled */
|
|
|
+static void hub_usb3_port_prepare_disable(struct usb_hub *hub,
|
|
|
+ struct usb_port *port_dev)
|
|
|
+{
|
|
|
+ struct usb_device *udev = port_dev->child;
|
|
|
+ int ret;
|
|
|
+
|
|
|
+ if (udev && udev->port_is_suspended && udev->do_remote_wakeup) {
|
|
|
+ ret = hub_set_port_link_state(hub, port_dev->portnum,
|
|
|
+ USB_SS_PORT_LS_U0);
|
|
|
+ if (!ret) {
|
|
|
+ msleep(USB_RESUME_TIMEOUT);
|
|
|
+ ret = usb_disable_remote_wakeup(udev);
|
|
|
+ }
|
|
|
+ if (ret)
|
|
|
+ dev_warn(&udev->dev,
|
|
|
+ "Port disable: can't disable remote wake\n");
|
|
|
+ udev->do_remote_wakeup = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
#else /* CONFIG_PM */
|
|
|
|
|
@@ -4149,6 +4117,9 @@ EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
|
|
|
#define hub_resume NULL
|
|
|
#define hub_reset_resume NULL
|
|
|
|
|
|
+static inline void hub_usb3_port_prepare_disable(struct usb_hub *hub,
|
|
|
+ struct usb_port *port_dev) { }
|
|
|
+
|
|
|
int usb_disable_lpm(struct usb_device *udev)
|
|
|
{
|
|
|
return 0;
|