]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/blobdiff - drivers/usb/host/uhci-hub.c
Merge branch 'upstream' of master.kernel.org:/pub/scm/linux/kernel/git/jgarzik/libata-dev
[mirror_ubuntu-zesty-kernel.git] / drivers / usb / host / uhci-hub.c
index ddb19cbf4b7537ad39a1cdb0475531fd0920f2c5..4eace2b19ddb830602b339059e7a953633a73d39 100644 (file)
@@ -49,9 +49,8 @@ static int any_ports_active(struct uhci_hcd *uhci)
        return 0;
 }
 
-static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
+static inline int get_hub_status_data(struct uhci_hcd *uhci, char *buf)
 {
-       struct uhci_hcd *uhci = hcd_to_uhci(hcd);
        int port;
 
        *buf = 0;
@@ -60,8 +59,6 @@ static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
                                test_bit(port, &uhci->port_c_suspend))
                        *buf |= (1 << (port + 1));
        }
-       if (*buf && uhci->state == UHCI_SUSPENDED)
-               uhci->resume_detect = 1;
        return !!*buf;
 }
 
@@ -131,6 +128,11 @@ static void uhci_check_ports(struct uhci_hcd *uhci)
                                set_bit(port, &uhci->resuming_ports);
                                uhci->ports_timeout = jiffies +
                                                msecs_to_jiffies(20);
+
+                               /* Make sure we see the port again
+                                * after the resuming period is over. */
+                               mod_timer(&uhci_to_hcd(uhci)->rh_timer,
+                                               uhci->ports_timeout);
                        } else if (time_after_eq(jiffies,
                                                uhci->ports_timeout)) {
                                uhci_finish_suspend(uhci, port, port_addr);
@@ -139,6 +141,60 @@ static void uhci_check_ports(struct uhci_hcd *uhci)
        }
 }
 
+static int uhci_hub_status_data(struct usb_hcd *hcd, char *buf)
+{
+       struct uhci_hcd *uhci = hcd_to_uhci(hcd);
+       unsigned long flags;
+       int status;
+
+       spin_lock_irqsave(&uhci->lock, flags);
+       if (uhci->hc_inaccessible) {
+               status = 0;
+               goto done;
+       }
+
+       uhci_check_ports(uhci);
+       status = get_hub_status_data(uhci, buf);
+
+       switch (uhci->rh_state) {
+           case UHCI_RH_SUSPENDING:
+           case UHCI_RH_SUSPENDED:
+               /* if port change, ask to be resumed */
+               if (status)
+                       usb_hcd_resume_root_hub(hcd);
+               break;
+
+           case UHCI_RH_AUTO_STOPPED:
+               /* if port change, auto start */
+               if (status)
+                       wakeup_rh(uhci);
+               break;
+
+           case UHCI_RH_RUNNING:
+               /* are any devices attached? */
+               if (!any_ports_active(uhci)) {
+                       uhci->rh_state = UHCI_RH_RUNNING_NODEVS;
+                       uhci->auto_stop_time = jiffies + HZ;
+               }
+               break;
+
+           case UHCI_RH_RUNNING_NODEVS:
+               /* auto-stop if nothing connected for 1 second */
+               if (any_ports_active(uhci))
+                       uhci->rh_state = UHCI_RH_RUNNING;
+               else if (time_after_eq(jiffies, uhci->auto_stop_time))
+                       suspend_rh(uhci, UHCI_RH_AUTO_STOPPED);
+               break;
+
+           default:
+               break;
+       }
+
+done:
+       spin_unlock_irqrestore(&uhci->lock, flags);
+       return status;
+}
+
 /* size of returned buffer is part of USB spec */
 static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                        u16 wIndex, char *buf, u16 wLength)
@@ -150,6 +206,9 @@ static int uhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
        u16 wPortChange, wPortStatus;
        unsigned long flags;
 
+       if (uhci->hc_inaccessible)
+               return -ETIMEDOUT;
+
        spin_lock_irqsave(&uhci->lock, flags);
        switch (typeReq) {