From bfd1e910139be73fb0783a0b3171fc79e6afa031 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Fri, 19 Oct 2012 11:03:39 -0400 Subject: USB: speed up usb_bus_resume() This patch (as1620) speeds up USB root-hub resumes in the common case where every enabled port has its suspend feature set (which currently will be true for every runtime resume of the root hub). If all the enabled ports are suspended then resuming the root hub won't resume any of the downstream devices. In this case there's no need for a Resume Recovery delay, because that delay is meant to give devices a chance to get ready for active use. To keep track of the port suspend features, the patch adds a "port_is_suspended" flag to struct usb_device. This has to be tracked separately from the device's state; it's entirely possible for a USB-2 device to be suspended while the suspend feature on its parent port is clear. The reason is that devices will go into suspend whenever their parent hub does. Signed-off-by: Alan Stern Reviewed-by: Peter Chen Tested-by: Peter Chen Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 64854d76f52..e729e94cb75 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2876,6 +2876,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg) (PMSG_IS_AUTO(msg) ? "auto-" : ""), udev->do_remote_wakeup); usb_set_device_state(udev, USB_STATE_SUSPENDED); + udev->port_is_suspended = 1; msleep(10); } usb_mark_last_busy(hub->hdev); @@ -3040,6 +3041,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) SuspendCleared: if (status == 0) { + udev->port_is_suspended = 0; if (hub_is_superspeed(hub->hdev)) { if (portchange & USB_PORT_STAT_C_LINK_STATE) clear_port_feature(hub->hdev, port1, -- cgit v1.2.3-70-g09d2 From e6f30deafe61179df048ac9040fe2bdf73a996c3 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 24 Oct 2012 11:59:24 +0800 Subject: USB: check port changes before hub runtime suspend for some bug device The hub status endpoint has a long 'bInterval', which is 255ms for FS/LS device and 256ms for HS device according to USB 2.0 spec, so the device connection change may be reported later more than 255ms via status pipe. The connection change in hub may have been happened already on the downstream ports, but no status URB completes when it is killed in hub_suspend(auto), so the connection change may be missed by some buggy hub devices, which won't generate remote wakeup signal after their remote wakeup is enabled and they are put into suspend state. The problem can be observed at least on the below Genesys Logic, Inc. hub devices: 0x05e3,0x0606 0x05e3,0x0608 In theory, there is no way to fix the problem completely, but we can make it less likely to occur by this patch. This patch introduces one quirk of HUB_QUIRK_CHECK_PORTS_AUTOSUSPEND to check ports' change during hub_suspend(auto) for the buggy devices. If ports' change is found, terminate the auto suspend and return to working state. So for the buggy hubs, if the connection change happend before the ports' check, it can be handled correctly. If it happens between the ports' check and enabling remote wakeup/entering suspend, it will be missed. Considered the interval is quite short, it is very less likely to happen during the window. Acked-by: Alan Stern Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index e729e94cb75..ff86355d0df 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -39,6 +39,9 @@ #endif #endif +#define USB_VENDOR_GENESYS_LOGIC 0x05e3 +#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01 + struct usb_port { struct usb_device *child; struct device dev; @@ -86,6 +89,8 @@ struct usb_hub { unsigned quiescing:1; unsigned disconnected:1; + unsigned quirk_check_port_auto_suspend:1; + unsigned has_indicators:1; u8 indicator[USB_MAXCHILDREN]; struct delayed_work leds; @@ -1667,6 +1672,9 @@ descriptor_error: if (hdev->speed == USB_SPEED_HIGH) highspeed_hubs++; + if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND) + hub->quirk_check_port_auto_suspend = 1; + if (hub_configure(hub, endpoint) >= 0) return 0; @@ -3125,6 +3133,21 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg) #endif +static int check_ports_changed(struct usb_hub *hub) +{ + int port1; + + for (port1 = 1; port1 <= hub->hdev->maxchild; ++port1) { + u16 portstatus, portchange; + int status; + + status = hub_port_status(hub, port1, &portstatus, &portchange); + if (!status && portchange) + return 1; + } + return 0; +} + static int hub_suspend(struct usb_interface *intf, pm_message_t msg) { struct usb_hub *hub = usb_get_intfdata (intf); @@ -3143,6 +3166,16 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) return -EBUSY; } } + + if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) { + /* check if there are changes pending on hub ports */ + if (check_ports_changed(hub)) { + if (PMSG_IS_AUTO(msg)) + return -EBUSY; + pm_wakeup_event(&hdev->dev, 2000); + } + } + if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) { /* Enable hub to send remote wakeup for all ports. */ for (port1 = 1; port1 <= hdev->maxchild; port1++) { @@ -4647,6 +4680,11 @@ static int hub_thread(void *__unused) } static const struct usb_device_id hub_id_table[] = { + { .match_flags = USB_DEVICE_ID_MATCH_VENDOR + | USB_DEVICE_ID_MATCH_INT_CLASS, + .idVendor = USB_VENDOR_GENESYS_LOGIC, + .bInterfaceClass = USB_CLASS_HUB, + .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND}, { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, .bDeviceClass = USB_CLASS_HUB}, { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, -- cgit v1.2.3-70-g09d2 From 596d789a211d134dc5f94d1e5957248c204ef850 Mon Sep 17 00:00:00 2001 From: Ming Lei Date: Wed, 24 Oct 2012 11:59:25 +0800 Subject: USB: set hub's default autosuspend delay as 0 This patch sets hub device's default autosuspend delay as 0 to speedup bus suspend, see comments in code for details. Acked-by: Alan Stern Signed-off-by: Ming Lei Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ff86355d0df..43ce1467f8c 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1614,6 +1614,41 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) desc = intf->cur_altsetting; hdev = interface_to_usbdev(intf); + /* + * Set default autosuspend delay as 0 to speedup bus suspend, + * based on the below considerations: + * + * - Unlike other drivers, the hub driver does not rely on the + * autosuspend delay to provide enough time to handle a wakeup + * event, and the submitted status URB is just to check future + * change on hub downstream ports, so it is safe to do it. + * + * - The patch might cause one or more auto supend/resume for + * below very rare devices when they are plugged into hub + * first time: + * + * devices having trouble initializing, and disconnect + * themselves from the bus and then reconnect a second + * or so later + * + * devices just for downloading firmware, and disconnects + * themselves after completing it + * + * For these quite rare devices, their drivers may change the + * autosuspend delay of their parent hub in the probe() to one + * appropriate value to avoid the subtle problem if someone + * does care it. + * + * - The patch may cause one or more auto suspend/resume on + * hub during running 'lsusb', but it is probably too + * infrequent to worry about. + * + * - Change autosuspend delay of hub can avoid unnecessary auto + * suspend timer for hub, also may decrease power consumption + * of USB bus. + */ + pm_runtime_set_autosuspend_delay(&hdev->dev, 0); + /* Hubs have proper suspend/resume support. */ usb_enable_autosuspend(hdev); -- cgit v1.2.3-70-g09d2 From f2ec522e78ef5cdc12d1dbecf254cdf31eb2b6d3 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Sun, 28 Oct 2012 01:05:51 -0700 Subject: usb: Convert dev_printk(KERN_ to dev_( dev_ calls take less code than dev_printk(KERN_ and reducing object size is good. Signed-off-by: Joe Perches Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 5b131b6477d..fbaf3c3dbf0 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2055,7 +2055,7 @@ static void show_string(struct usb_device *udev, char *id, char *string) { if (!string) return; - dev_printk(KERN_INFO, &udev->dev, "%s: %s\n", id, string); + dev_info(&udev->dev, "%s: %s\n", id, string); } static void announce_device(struct usb_device *udev) -- cgit v1.2.3-70-g09d2 From 571e41214e988bc38c99d804e6d8e1ea1d016342 Mon Sep 17 00:00:00 2001 From: Alan Stern Date: Wed, 31 Oct 2012 13:09:07 -0400 Subject: USB: remove iteration limit in hub_tt_work() This patch (as1621) removes the limit on the number of loops allowed in hub_tt_work(). The value is arbitrary, and it's silly to have a limit in the first place -- anything beyond the limit would not get handled. Besides, it's most unlikely that we'll ever need to clear more than a couple of TT buffers at any time. Signed-off-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 4 ---- 1 file changed, 4 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index fbaf3c3dbf0..90accdefdc7 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -741,7 +741,6 @@ static void hub_tt_work(struct work_struct *work) struct usb_hub *hub = container_of(work, struct usb_hub, tt.clear_work); unsigned long flags; - int limit = 100; spin_lock_irqsave (&hub->tt.lock, flags); while (!list_empty(&hub->tt.clear_list)) { @@ -751,9 +750,6 @@ static void hub_tt_work(struct work_struct *work) const struct hc_driver *drv; int status; - if (!hub->quiescing && --limit < 0) - break; - next = hub->tt.clear_list.next; clear = list_entry (next, struct usb_tt_clear, clear_list); list_del (&clear->clear_list); -- cgit v1.2.3-70-g09d2 From b76baa8154335a906be85f2b32d6bd1876900133 Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 9 Nov 2012 09:44:43 +0800 Subject: usb: refine phy notify operation during connection and disconnection At commit 925aa46ba963a4da6d8ee6ab1d04a02ffa8db62b, Richard Zhao adds the phy notification callback when port change occurs. In fact, this phy notification should be added according to below rules: 1. Only set HW_USBPHY_CTRL.ENHOSTDISCONDETECT during high speed host mode. 2. Do not set HW_USBPHY_CTRL.ENHOSTDISCONDETECT during the reset and speed negotiation period. 3. Do not set HW_USBPHY_CTRL.ENHOSTDISCONDETECT during host suspend/resume sequence. Please refer: i.mx23RM(page: 413) for below rules. http://www.freescale.com/files/dsp/doc/ref_manual/IMX23RM.pdf Freescale i.MX SoC, i.mx23, i.mx28 and i.mx6(i.mx6SL does not need to follow the 3rd rule) need to follow above rules. Current code set connect notification (HW_USBPHY_CTRL.ENHOSTDISCONDETECT) at hub_port_connect_change, it conflicts with above the 2th rule. The correct notification setting method should be: 1. Set connect notify after the second bus reset. 2. Set disconnect notify after disconnection. Signed-off-by: Peter Chen Tested-by: Mike Thompson Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 90accdefdc7..8391538d688 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4038,6 +4038,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, if (retval) goto fail; + if (hcd->phy && !hdev->parent) + usb_phy_notify_connect(hcd->phy, port1); + /* * Some superspeed devices have finished the link training process * and attached to a superspeed hub port, but the device descriptor @@ -4232,8 +4235,12 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, } /* Disconnect any existing devices under this port */ - if (udev) + if (udev) { + if (hcd->phy && !hdev->parent && + !(portstatus & USB_PORT_STAT_CONNECTION)) + usb_phy_notify_disconnect(hcd->phy, port1); usb_disconnect(&hub->ports[port1 - 1]->child); + } clear_bit(port1, hub->change_bits); /* We can forget about a "removed" device when there's a physical @@ -4256,13 +4263,6 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, } } - if (hcd->phy && !hdev->parent) { - if (portstatus & USB_PORT_STAT_CONNECTION) - usb_phy_notify_connect(hcd->phy, port1); - else - usb_phy_notify_disconnect(hcd->phy, port1); - } - /* Return now if debouncing failed or nothing is connected or * the device was "removed". */ -- cgit v1.2.3-70-g09d2 From ac96511bb5cf92bad97ffc2249f75e45eb70301d Mon Sep 17 00:00:00 2001 From: Peter Chen Date: Fri, 9 Nov 2012 09:44:44 +0800 Subject: usb: phy: change phy notify connect/disconnect API The old parameter "port" is useless for phy notify, as one usb phy is only for one usb port. New parameter "speed" stands for the device's speed which is on the port, this "speed" parameter is needed at some platforms which will do some phy operations according to device's speed. Signed-off-by: Peter Chen Tested-by: Mike Thompson Acked-by: Alan Stern Signed-off-by: Greg Kroah-Hartman --- drivers/usb/core/hub.c | 4 ++-- drivers/usb/otg/mxs-phy.c | 22 ++++++++++++++-------- include/linux/usb/phy.h | 15 +++++++++------ 3 files changed, 25 insertions(+), 16 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 8391538d688..a815fd2cc5e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4039,7 +4039,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, goto fail; if (hcd->phy && !hdev->parent) - usb_phy_notify_connect(hcd->phy, port1); + usb_phy_notify_connect(hcd->phy, udev->speed); /* * Some superspeed devices have finished the link training process @@ -4238,7 +4238,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, if (udev) { if (hcd->phy && !hdev->parent && !(portstatus & USB_PORT_STAT_CONNECTION)) - usb_phy_notify_disconnect(hcd->phy, port1); + usb_phy_notify_disconnect(hcd->phy, udev->speed); usb_disconnect(&hub->ports[port1 - 1]->child); } clear_bit(port1, hub->change_bits); diff --git a/drivers/usb/otg/mxs-phy.c b/drivers/usb/otg/mxs-phy.c index 5b09f3339de..9a3caeecc50 100644 --- a/drivers/usb/otg/mxs-phy.c +++ b/drivers/usb/otg/mxs-phy.c @@ -76,22 +76,28 @@ static void mxs_phy_shutdown(struct usb_phy *phy) clk_disable_unprepare(mxs_phy->clk); } -static int mxs_phy_on_connect(struct usb_phy *phy, int port) +static int mxs_phy_on_connect(struct usb_phy *phy, + enum usb_device_speed speed) { - dev_dbg(phy->dev, "Connect on port %d\n", port); + dev_dbg(phy->dev, "%s speed device has connected\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); - writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, - phy->io_priv + HW_USBPHY_CTRL_SET); + if (speed == USB_SPEED_HIGH) + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_SET); return 0; } -static int mxs_phy_on_disconnect(struct usb_phy *phy, int port) +static int mxs_phy_on_disconnect(struct usb_phy *phy, + enum usb_device_speed speed) { - dev_dbg(phy->dev, "Disconnect on port %d\n", port); + dev_dbg(phy->dev, "%s speed device has disconnected\n", + (speed == USB_SPEED_HIGH) ? "high" : "non-high"); - writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, - phy->io_priv + HW_USBPHY_CTRL_CLR); + if (speed == USB_SPEED_HIGH) + writel_relaxed(BM_USBPHY_CTRL_ENHOSTDISCONDETECT, + phy->io_priv + HW_USBPHY_CTRL_CLR); return 0; } diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h index 06b5bae35b2..a29ae1eb934 100644 --- a/include/linux/usb/phy.h +++ b/include/linux/usb/phy.h @@ -10,6 +10,7 @@ #define __LINUX_USB_PHY_H #include +#include enum usb_phy_events { USB_EVENT_NONE, /* no events or cable disconnected */ @@ -99,8 +100,10 @@ struct usb_phy { int suspend); /* notify phy connect status change */ - int (*notify_connect)(struct usb_phy *x, int port); - int (*notify_disconnect)(struct usb_phy *x, int port); + int (*notify_connect)(struct usb_phy *x, + enum usb_device_speed speed); + int (*notify_disconnect)(struct usb_phy *x, + enum usb_device_speed speed); }; @@ -189,19 +192,19 @@ usb_phy_set_suspend(struct usb_phy *x, int suspend) } static inline int -usb_phy_notify_connect(struct usb_phy *x, int port) +usb_phy_notify_connect(struct usb_phy *x, enum usb_device_speed speed) { if (x->notify_connect) - return x->notify_connect(x, port); + return x->notify_connect(x, speed); else return 0; } static inline int -usb_phy_notify_disconnect(struct usb_phy *x, int port) +usb_phy_notify_disconnect(struct usb_phy *x, enum usb_device_speed speed) { if (x->notify_disconnect) - return x->notify_disconnect(x, port); + return x->notify_disconnect(x, speed); else return 0; } -- cgit v1.2.3-70-g09d2