USB: u132-hcd: host controller driver for ELAN U132 adapter
[powerpc.git] / drivers / usb / host / ohci-hub.c
index e01e77b..0b89933 100644 (file)
 
 /*-------------------------------------------------------------------------*/
 
+/* hcd->hub_irq_enable() */
+static void ohci_rhsc_enable (struct usb_hcd *hcd)
+{
+       struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
+
+       ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
+}
+
 #ifdef CONFIG_PM
 
 #define OHCI_SCHED_ENABLES \
@@ -53,6 +61,11 @@ static int ohci_bus_suspend (struct usb_hcd *hcd)
 
        spin_lock_irqsave (&ohci->lock, flags);
 
+       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) {
+               spin_unlock_irqrestore (&ohci->lock, flags);
+               return -ESHUTDOWN;
+       }
+
        ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
        switch (ohci->hc_control & OHCI_CTRL_HCFS) {
        case OHCI_USB_RESUME:
@@ -102,7 +115,7 @@ static int ohci_bus_suspend (struct usb_hcd *hcd)
                        &ohci->regs->intrstatus);
 
        /* maybe resume can wake root hub */
-       if (hcd->remote_wakeup)
+       if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev))
                ohci->hc_control |= OHCI_CTRL_RWE;
        else
                ohci->hc_control &= ~OHCI_CTRL_RWE;
@@ -118,10 +131,10 @@ static int ohci_bus_suspend (struct usb_hcd *hcd)
        /* no resumes until devices finish suspending */
        ohci->next_statechange = jiffies + msecs_to_jiffies (5);
 
+       /* no timer polling */
+       hcd->poll_rh = 0;
+
 done:
-       /* external suspend vs self autosuspend ... same effect */
-       if (status == 0)
-               usb_hcd_suspend_root_hub(hcd);
        spin_unlock_irqrestore (&ohci->lock, flags);
        return status;
 }
@@ -140,11 +153,19 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
        struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
        u32                     temp, enables;
        int                     status = -EINPROGRESS;
+       unsigned long           flags;
 
        if (time_before (jiffies, ohci->next_statechange))
                msleep(5);
 
-       spin_lock_irq (&ohci->lock);
+       spin_lock_irqsave (&ohci->lock, flags);
+
+       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))) {
+               spin_unlock_irqrestore (&ohci->lock, flags);
+               return -ESHUTDOWN;
+       }
+
+
        ohci->hc_control = ohci_readl (ohci, &ohci->regs->control);
 
        if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) {
@@ -179,7 +200,7 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
                ohci_dbg (ohci, "lost power\n");
                status = -EBUSY;
        }
-       spin_unlock_irq (&ohci->lock);
+       spin_unlock_irqrestore (&ohci->lock, flags);
        if (status == -EBUSY) {
                (void) ohci_init (ohci);
                return ohci_restart (ohci);
@@ -233,9 +254,9 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
        (void) ohci_readl (ohci, &ohci->regs->control);
        msleep (3);
 
-       temp = OHCI_CONTROL_INIT | OHCI_USB_OPER;
-       if (hcd->can_wakeup)
-               temp |= OHCI_CTRL_RWC;
+       temp = ohci->hc_control;
+       temp &= OHCI_CTRL_RWC;
+       temp |= OHCI_CONTROL_INIT | OHCI_USB_OPER;
        ohci->hc_control = temp;
        ohci_writel (ohci, temp, &ohci->regs->control);
        (void) ohci_readl (ohci, &ohci->regs->control);
@@ -243,8 +264,8 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
        /* TRSMRCY */
        msleep (10);
 
-       /* keep it alive for ~5x suspend + resume costs */
-       ohci->next_statechange = jiffies + msecs_to_jiffies (250);
+       /* keep it alive for more than ~5x suspend + resume costs */
+       ohci->next_statechange = jiffies + STATECHANGE_DELAY;
 
        /* maybe turn schedules back on */
        enables = 0;
@@ -289,16 +310,17 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
 {
        struct ohci_hcd *ohci = hcd_to_ohci (hcd);
        int             i, changed = 0, length = 1;
-       int             can_suspend = hcd->can_wakeup;
+       int             can_suspend;
        unsigned long   flags;
 
+       can_suspend = device_may_wakeup(&hcd->self.root_hub->dev);
        spin_lock_irqsave (&ohci->lock, flags);
 
        /* handle autosuspended root:  finish resuming before
         * letting khubd or root hub timer see state changes.
         */
-       if ((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER
-                       || !HC_IS_RUNNING(hcd->state)) {
+       if (unlikely((ohci->hc_control & OHCI_CTRL_HCFS) != OHCI_USB_OPER
+                    || !HC_IS_RUNNING(hcd->state))) {
                can_suspend = 0;
                goto done;
        }
@@ -326,6 +348,10 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
        for (i = 0; i < ohci->num_ports; i++) {
                u32     status = roothub_portstatus (ohci, i);
 
+               /* can't autosuspend with active ports */
+               if ((status & RH_PS_PES) && !(status & RH_PS_PSS))
+                       can_suspend = 0;
+
                if (status & (RH_PS_CSC | RH_PS_PESC | RH_PS_PSSC
                                | RH_PS_OCIC | RH_PS_PRSC)) {
                        changed = 1;
@@ -335,32 +361,41 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
                            buf [1] |= 1 << (i - 7);
                        continue;
                }
+       }
 
-               /* can suspend if no ports are enabled; or if all all
-                * enabled ports are suspended AND remote wakeup is on.
-                */
-               if (!(status & RH_PS_CCS))
-                       continue;
-               if ((status & RH_PS_PSS) && hcd->remote_wakeup)
-                       continue;
+       /* after root hub changes, stop polling after debouncing
+        * for a while and maybe kicking in autosuspend
+        */
+       if (changed) {
+               ohci->next_statechange = jiffies + STATECHANGE_DELAY;
+               can_suspend = 0;
+       } else if (time_before (jiffies, ohci->next_statechange)) {
                can_suspend = 0;
+       } else {
+#ifdef CONFIG_PM
+               can_suspend = can_suspend
+                       && !ohci->ed_rm_list
+                       && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES)
+                                       & ohci->hc_control)
+                               == OHCI_USB_OPER;
+#endif
+               if (hcd->uses_new_polling) {
+                       hcd->poll_rh = 0;
+                       /* use INTR_RHSC iff INTR_RD won't apply */
+                       if (!can_suspend)
+                               ohci_writel (ohci, OHCI_INTR_RHSC,
+                                               &ohci->regs->intrenable);
+               }
        }
+
 done:
        spin_unlock_irqrestore (&ohci->lock, flags);
 
-#ifdef CONFIG_PM
-       /* save power by suspending idle root hubs;
+#ifdef CONFIG_PM
+       /* save power by autosuspending idle root hubs;
         * INTR_RD wakes us when there's work
         */
-       if (can_suspend
-                       && !changed
-                       && !ohci->ed_rm_list
-                       && ((OHCI_CTRL_HCFS | OHCI_SCHED_ENABLES)
-                                       & ohci->hc_control)
-                               == OHCI_USB_OPER
-                       && time_after (jiffies, ohci->next_statechange)
-                       && usb_trylock_device (hcd->self.root_hub)
-                       ) {
+       if (can_suspend && usb_trylock_device (hcd->self.root_hub) == 0) {
                ohci_vdbg (ohci, "autosuspend\n");
                (void) ohci_bus_suspend (hcd);
                usb_unlock_device (hcd->self.root_hub);
@@ -508,6 +543,9 @@ static int ohci_hub_control (
        u32             temp;
        int             retval = 0;
 
+       if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)))
+               return -ESHUTDOWN;
+
        switch (typeReq) {
        case ClearHubFeature:
                switch (wValue) {
@@ -565,14 +603,14 @@ static int ohci_hub_control (
                break;
        case GetHubStatus:
                temp = roothub_status (ohci) & ~(RH_HS_CRWE | RH_HS_DRWE);
-               *(__le32 *) buf = cpu_to_le32 (temp);
+               put_unaligned(cpu_to_le32 (temp), (__le32 *) buf);
                break;
        case GetPortStatus:
                if (!wIndex || wIndex > ports)
                        goto error;
                wIndex--;
                temp = roothub_portstatus (ohci, wIndex);
-               *(__le32 *) buf = cpu_to_le32 (temp);
+               put_unaligned(cpu_to_le32 (temp), (__le32 *) buf);
 
 #ifndef        OHCI_VERBOSE_DEBUG
        if (*(u16*)(buf+2))     /* only if wPortChange is interesting */