]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/usb/host/ohci-hub.c
USB: EHCI: suppress unwanted error messages
[karo-tx-linux.git] / drivers / usb / host / ohci-hub.c
index 6995ea36f2e874499ae407e9b699931ceba117bd..79a78029f896869892fa2696bd8f1764cc79f9e7 100644 (file)
@@ -1,9 +1,9 @@
 /*
  * OHCI HCD (Host Controller Driver) for USB.
- * 
+ *
  * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
  * (C) Copyright 2000-2004 David Brownell <dbrownell@users.sourceforge.net>
- * 
+ *
  * This file is licenced under GPL
  */
 
                (temp & RH_PS_PSSC) ? " PSSC" : "", \
                (temp & RH_PS_PESC) ? " PESC" : "", \
                (temp & RH_PS_CSC) ? " CSC" : "", \
-               \
+               \
                (temp & RH_PS_LSDA) ? " LSDA" : "", \
                (temp & RH_PS_PPS) ? " PPS" : "", \
                (temp & RH_PS_PRS) ? " PRS" : "", \
                (temp & RH_PS_POCI) ? " POCI" : "", \
                (temp & RH_PS_PSS) ? " PSS" : "", \
-               \
+               \
                (temp & RH_PS_PES) ? " PES" : "", \
                (temp & RH_PS_CCS) ? " CCS" : "" \
                );
 
 /*-------------------------------------------------------------------------*/
 
-/* 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);
-}
-
 #define OHCI_SCHED_ENABLES \
        (OHCI_CTRL_CLE|OHCI_CTRL_BLE|OHCI_CTRL_PLE|OHCI_CTRL_IE)
 
 static void dl_done_list (struct ohci_hcd *);
 static void finish_unlinks (struct ohci_hcd *, u16);
 
+#ifdef CONFIG_PM
 static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop)
 __releases(ohci->lock)
 __acquires(ohci->lock)
@@ -98,11 +91,11 @@ __acquires(ohci->lock)
        finish_unlinks (ohci, ohci_frame_no(ohci));
 
        /* maybe resume can wake root hub */
-       if (device_may_wakeup(&ohci_to_hcd(ohci)->self.root_hub->dev) ||
-                       autostop)
+       if (ohci_to_hcd(ohci)->self.root_hub->do_remote_wakeup || autostop) {
                ohci->hc_control |= OHCI_CTRL_RWE;
-       else {
-               ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable);
+       } else {
+               ohci_writel(ohci, OHCI_INTR_RHSC | OHCI_INTR_RD,
+                               &ohci->regs->intrdisable);
                ohci->hc_control &= ~OHCI_CTRL_RWE;
        }
 
@@ -132,8 +125,6 @@ static inline struct ed *find_head (struct ed *ed)
        return ed;
 }
 
-static int ohci_restart (struct ohci_hcd *ohci);
-
 /* caller has locked the root hub */
 static int ohci_rh_resume (struct ohci_hcd *ohci)
 __releases(ohci->lock)
@@ -169,7 +160,7 @@ __acquires(ohci->lock)
                break;
        case OHCI_USB_RESUME:
                /* HCFS changes sometime after INTR_RD */
-               ohci_info(ohci, "%swakeup\n",
+               ohci_dbg(ohci, "%swakeup root hub\n",
                                autostopped ? "auto-" : "");
                break;
        case OHCI_USB_OPER:
@@ -181,35 +172,24 @@ __acquires(ohci->lock)
                ohci_dbg (ohci, "lost power\n");
                status = -EBUSY;
        }
-#ifdef CONFIG_PM
        if (status == -EBUSY) {
                if (!autostopped) {
                        spin_unlock_irq (&ohci->lock);
                        (void) ohci_init (ohci);
                        status = ohci_restart (ohci);
+
+                       usb_root_hub_lost_power(hcd->self.root_hub);
+
                        spin_lock_irq (&ohci->lock);
                }
                return status;
        }
-#endif
        if (status != -EINPROGRESS)
                return status;
        if (autostopped)
                goto skip_resume;
        spin_unlock_irq (&ohci->lock);
 
-       temp = ohci->num_ports;
-       while (temp--) {
-               u32 stat = ohci_readl (ohci,
-                                      &ohci->regs->roothub.portstatus [temp]);
-
-               /* force global, not selective, resume */
-               if (!(stat & RH_PS_PSS))
-                       continue;
-               ohci_writel (ohci, RH_PS_POCI,
-                               &ohci->regs->roothub.portstatus [temp]);
-       }
-
        /* Some controllers (lucent erratum) need extra-long delays */
        msleep (20 /* usb 11.5.1.10 */ + 12 /* 32 msec counter */ + 1);
 
@@ -217,6 +197,7 @@ __acquires(ohci->lock)
        temp &= OHCI_CTRL_HCFS;
        if (temp != OHCI_USB_RESUME) {
                ohci_err (ohci, "controller won't resume\n");
+               spin_lock_irq(&ohci->lock);
                return -EBUSY;
        }
 
@@ -296,8 +277,6 @@ skip_resume:
        return 0;
 }
 
-#ifdef CONFIG_PM
-
 static int ohci_bus_suspend (struct usb_hcd *hcd)
 {
        struct ohci_hcd         *ohci = hcd_to_ohci (hcd);
@@ -335,6 +314,147 @@ static int ohci_bus_resume (struct usb_hcd *hcd)
        return rc;
 }
 
+/* Carry out the final steps of resuming the controller device */
+static void ohci_finish_controller_resume(struct usb_hcd *hcd)
+{
+       struct ohci_hcd         *ohci = hcd_to_ohci(hcd);
+       int                     port;
+       bool                    need_reinit = false;
+
+       /* See if the controller is already running or has been reset */
+       ohci->hc_control = ohci_readl(ohci, &ohci->regs->control);
+       if (ohci->hc_control & (OHCI_CTRL_IR | OHCI_SCHED_ENABLES)) {
+               need_reinit = true;
+       } else {
+               switch (ohci->hc_control & OHCI_CTRL_HCFS) {
+               case OHCI_USB_OPER:
+               case OHCI_USB_RESET:
+                       need_reinit = true;
+               }
+       }
+
+       /* If needed, reinitialize and suspend the root hub */
+       if (need_reinit) {
+               spin_lock_irq(&ohci->lock);
+               hcd->state = HC_STATE_RESUMING;
+               ohci_rh_resume(ohci);
+               hcd->state = HC_STATE_QUIESCING;
+               ohci_rh_suspend(ohci, 0);
+               hcd->state = HC_STATE_SUSPENDED;
+               spin_unlock_irq(&ohci->lock);
+       }
+
+       /* Normally just turn on port power and enable interrupts */
+       else {
+               ohci_dbg(ohci, "powerup ports\n");
+               for (port = 0; port < ohci->num_ports; port++)
+                       ohci_writel(ohci, RH_PS_PPS,
+                                       &ohci->regs->roothub.portstatus[port]);
+
+               ohci_writel(ohci, OHCI_INTR_MIE, &ohci->regs->intrenable);
+               ohci_readl(ohci, &ohci->regs->intrenable);
+               msleep(20);
+       }
+}
+
+/* Carry out polling-, autostop-, and autoresume-related state changes */
+static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
+               int any_connected)
+{
+       int     poll_rh = 1;
+       int     rhsc;
+
+       rhsc = ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC;
+       switch (ohci->hc_control & OHCI_CTRL_HCFS) {
+
+       case OHCI_USB_OPER:
+               /* If no status changes are pending, enable status-change
+                * interrupts.
+                */
+               if (!rhsc && !changed) {
+                       rhsc = OHCI_INTR_RHSC;
+                       ohci_writel(ohci, rhsc, &ohci->regs->intrenable);
+               }
+
+               /* Keep on polling until we know a device is connected
+                * and RHSC is enabled, or until we autostop.
+                */
+               if (!ohci->autostop) {
+                       if (any_connected ||
+                                       !device_may_wakeup(&ohci_to_hcd(ohci)
+                                               ->self.root_hub->dev)) {
+                               if (rhsc)
+                                       poll_rh = 0;
+                       } else {
+                               ohci->autostop = 1;
+                               ohci->next_statechange = jiffies + HZ;
+                       }
+
+               /* if no devices have been attached for one second, autostop */
+               } else {
+                       if (changed || any_connected) {
+                               ohci->autostop = 0;
+                               ohci->next_statechange = jiffies +
+                                               STATECHANGE_DELAY;
+                       } else if (rhsc && time_after_eq(jiffies,
+                                               ohci->next_statechange)
+                                       && !ohci->ed_rm_list
+                                       && !(ohci->hc_control &
+                                               OHCI_SCHED_ENABLES)) {
+                               ohci_rh_suspend(ohci, 1);
+                               poll_rh = 0;
+                       }
+               }
+               break;
+
+       /* if there is a port change, autostart or ask to be resumed */
+       case OHCI_USB_SUSPEND:
+       case OHCI_USB_RESUME:
+               if (changed) {
+                       if (ohci->autostop)
+                               ohci_rh_resume(ohci);
+                       else
+                               usb_hcd_resume_root_hub(ohci_to_hcd(ohci));
+               } else {
+                       if (!rhsc && (ohci->autostop ||
+                                       ohci_to_hcd(ohci)->self.root_hub->
+                                               do_remote_wakeup))
+                               ohci_writel(ohci, OHCI_INTR_RHSC,
+                                               &ohci->regs->intrenable);
+
+                       /* everything is idle, no need for polling */
+                       poll_rh = 0;
+               }
+               break;
+       }
+       return poll_rh;
+}
+
+#else  /* CONFIG_PM */
+
+static inline int ohci_rh_resume(struct ohci_hcd *ohci)
+{
+       return 0;
+}
+
+/* Carry out polling-related state changes.
+ * autostop isn't used when CONFIG_PM is turned off.
+ */
+static int ohci_root_hub_state_changes(struct ohci_hcd *ohci, int changed,
+               int any_connected)
+{
+       /* If RHSC is enabled, don't poll */
+       if (ohci_readl(ohci, &ohci->regs->intrenable) & OHCI_INTR_RHSC)
+               return 0;
+
+       /* If no status changes are pending, enable status-change interrupts */
+       if (!changed) {
+               ohci_writel(ohci, OHCI_INTR_RHSC, &ohci->regs->intrenable);
+               return 0;
+       }
+       return 1;
+}
+
 #endif /* CONFIG_PM */
 
 /*-------------------------------------------------------------------------*/
@@ -346,10 +466,12 @@ 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             any_connected = 0, rhsc_enabled = 1;
+       int             any_connected = 0;
        unsigned long   flags;
 
        spin_lock_irqsave (&ohci->lock, flags);
+       if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags))
+               goto done;
 
        /* undocumented erratum seen on at least rev D */
        if ((ohci->flags & OHCI_QUIRK_AMD756)
@@ -387,67 +509,8 @@ ohci_hub_status_data (struct usb_hcd *hcd, char *buf)
                }
        }
 
-       /* NOTE:  vendors didn't always make the same implementation
-        * choices for RHSC.  Sometimes it triggers on an edge (like
-        * setting and maybe clearing a port status change bit); and
-        * it's level-triggered on other silicon, active until khubd
-        * clears all active port status change bits.  If it's still
-        * set (level-triggered) we must disable it and rely on
-        * polling until khubd re-enables it.
-        */
-       if (ohci_readl (ohci, &ohci->regs->intrstatus) & OHCI_INTR_RHSC) {
-               ohci_writel (ohci, OHCI_INTR_RHSC, &ohci->regs->intrdisable);
-               (void) ohci_readl (ohci, &ohci->regs->intrdisable);
-               rhsc_enabled = 0;
-       }
-       hcd->poll_rh = 1;
-
-       /* carry out appropriate state changes */
-       switch (ohci->hc_control & OHCI_CTRL_HCFS) {
-
-       case OHCI_USB_OPER:
-               /* keep on polling until we know a device is connected
-                * and RHSC is enabled */
-               if (!ohci->autostop) {
-                       if (any_connected) {
-                               if (rhsc_enabled)
-                                       hcd->poll_rh = 0;
-                       } else {
-                               ohci->autostop = 1;
-                               ohci->next_statechange = jiffies + HZ;
-                       }
-
-               /* if no devices have been attached for one second, autostop */
-               } else {
-                       if (changed || any_connected) {
-                               ohci->autostop = 0;
-                               ohci->next_statechange = jiffies +
-                                               STATECHANGE_DELAY;
-                       } else if (device_may_wakeup(&hcd->self.root_hub->dev)
-                                       && time_after_eq(jiffies,
-                                               ohci->next_statechange)
-                                       && !ohci->ed_rm_list
-                                       && !(ohci->hc_control &
-                                               OHCI_SCHED_ENABLES)) {
-                               ohci_rh_suspend (ohci, 1);
-                       }
-               }
-               break;
-
-       /* if there is a port change, autostart or ask to be resumed */
-       case OHCI_USB_SUSPEND:
-       case OHCI_USB_RESUME:
-               if (changed) {
-                       if (ohci->autostop)
-                               ohci_rh_resume (ohci);
-                       else
-                               usb_hcd_resume_root_hub (hcd);
-               } else {
-                       /* everything is idle, no need for polling */
-                       hcd->poll_rh = 0;
-               }
-               break;
-       }
+       hcd->poll_rh = ohci_root_hub_state_changes(ohci, changed,
+                       any_connected);
 
 done:
        spin_unlock_irqrestore (&ohci->lock, flags);
@@ -476,7 +539,7 @@ ohci_hub_descriptor (
        temp = 0;
        if (rh & RH_A_NPS)              /* no power switching? */
            temp |= 0x0002;
-       if (rh & RH_A_PSM)              /* per-port power switching? */
+       if (rh & RH_A_PSM)              /* per-port power switching? */
            temp |= 0x0001;
        if (rh & RH_A_NOCP)             /* no overcurrent reporting? */
            temp |= 0x0010;
@@ -547,25 +610,43 @@ static void start_hnp(struct ohci_hcd *ohci);
 #define tick_before(t1,t2) ((s16)(((s16)(t1))-((s16)(t2))) < 0)
 
 /* called from some task, normally khubd */
-static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port)
+static inline int root_port_reset (struct ohci_hcd *ohci, unsigned port)
 {
        __hc32 __iomem *portstat = &ohci->regs->roothub.portstatus [port];
-       u32     temp;
+       u32     temp = 0;
        u16     now = ohci_readl(ohci, &ohci->regs->fmnumber);
        u16     reset_done = now + PORT_RESET_MSEC;
+       int     limit_1 = DIV_ROUND_UP(PORT_RESET_MSEC, PORT_RESET_HW_MSEC);
 
        /* build a "continuous enough" reset signal, with up to
         * 3msec gap between pulses.  scheduler HZ==100 must work;
         * this might need to be deadline-scheduled.
         */
        do {
+               int limit_2;
+
                /* spin until any current reset finishes */
-               for (;;) {
+               limit_2 = PORT_RESET_HW_MSEC * 2;
+               while (--limit_2 >= 0) {
                        temp = ohci_readl (ohci, portstat);
+                       /* handle e.g. CardBus eject */
+                       if (temp == ~(u32)0)
+                               return -ESHUTDOWN;
                        if (!(temp & RH_PS_PRS))
                                break;
                        udelay (500);
-               } 
+               }
+
+               /* timeout (a hardware error) has been observed when
+                * EHCI sets CF while this driver is resetting a port;
+                * presumably other disconnect paths might do it too.
+                */
+               if (limit_2 < 0) {
+                       ohci_dbg(ohci,
+                               "port[%d] reset timeout, stat %08x\n",
+                               port, temp);
+                       break;
+               }
 
                if (!(temp & RH_PS_CCS))
                        break;
@@ -576,8 +657,13 @@ static inline void root_port_reset (struct ohci_hcd *ohci, unsigned port)
                ohci_writel (ohci, RH_PS_PRS, portstat);
                msleep(PORT_RESET_HW_MSEC);
                now = ohci_readl(ohci, &ohci->regs->fmnumber);
-       } while (tick_before(now, reset_done));
-       /* caller synchronizes using PRSC */
+       } while (tick_before(now, reset_done) && --limit_1 >= 0);
+
+       /* caller synchronizes using PRSC ... and handles PRS
+        * still being set when this returns.
+        */
+
+       return 0;
 }
 
 static int ohci_hub_control (
@@ -650,14 +736,14 @@ static int ohci_hub_control (
                break;
        case GetHubStatus:
                temp = roothub_status (ohci) & ~(RH_HS_CRWE | RH_HS_DRWE);
-               put_unaligned(cpu_to_le32 (temp), (__le32 *) buf);
+               put_unaligned_le32(temp, buf);
                break;
        case GetPortStatus:
                if (!wIndex || wIndex > ports)
                        goto error;
                wIndex--;
                temp = roothub_portstatus (ohci, wIndex);
-               put_unaligned(cpu_to_le32 (temp), (__le32 *) buf);
+               put_unaligned_le32(temp, buf);
 
 #ifndef        OHCI_VERBOSE_DEBUG
        if (*(u16*)(buf+2))     /* only if wPortChange is interesting */
@@ -694,7 +780,7 @@ static int ohci_hub_control (
                                &ohci->regs->roothub.portstatus [wIndex]);
                        break;
                case USB_PORT_FEAT_RESET:
-                       root_port_reset (ohci, wIndex);
+                       retval = root_port_reset (ohci, wIndex);
                        break;
                default:
                        goto error;