/*-------------------------------------------------------------------------*/
 
 /*
- * Root Hub interrupt transfers are synthesized with a timer.
- * Completions are called in_interrupt() but not in_irq().
+ * Root Hub interrupt transfers are polled using a timer if the
+ * driver requests it; otherwise the driver is responsible for
+ * calling usb_hcd_poll_rh_status() when an event occurs.
  *
- * Note: some root hubs (including common UHCI based designs) can't
- * correctly issue port change IRQs.  They're the ones that _need_ a
- * timer; most other root hubs don't.  Some systems could save a
- * lot of battery power by eliminating these root hub timer IRQs.
+ * Completions are called in_interrupt(), but they may or may not
+ * be in_irq().
  */
+void usb_hcd_poll_rh_status(struct usb_hcd *hcd)
+{
+       struct urb      *urb;
+       int             length;
+       unsigned long   flags;
+       char            buffer[4];      /* Any root hubs with > 31 ports? */
 
-static void rh_report_status (unsigned long ptr);
+       if (!hcd->uses_new_polling && !hcd->status_urb)
+               return;
 
-static int rh_status_urb (struct usb_hcd *hcd, struct urb *urb) 
-{
-       int     len = 1 + (urb->dev->maxchild / 8);
+       length = hcd->driver->hub_status_data(hcd, buffer);
+       if (length > 0) {
 
-       /* rh_timer protected by hcd_data_lock */
-       if (hcd->rh_timer.data || urb->transfer_buffer_length < len) {
-               dev_dbg (hcd->self.controller,
-                               "not queuing rh status urb, stat %d\n",
-                               urb->status);
-               return -EINVAL;
+               /* try to complete the status urb */
+               local_irq_save (flags);
+               spin_lock(&hcd_root_hub_lock);
+               urb = hcd->status_urb;
+               if (urb) {
+                       spin_lock(&urb->lock);
+                       if (urb->status == -EINPROGRESS) {
+                               hcd->poll_pending = 0;
+                               hcd->status_urb = NULL;
+                               urb->status = 0;
+                               urb->hcpriv = NULL;
+                               urb->actual_length = length;
+                               memcpy(urb->transfer_buffer, buffer, length);
+                       } else          /* urb has been unlinked */
+                               length = 0;
+                       spin_unlock(&urb->lock);
+               } else
+                       length = 0;
+               spin_unlock(&hcd_root_hub_lock);
+
+               /* local irqs are always blocked in completions */
+               if (length > 0)
+                       usb_hcd_giveback_urb (hcd, urb, NULL);
+               else
+                       hcd->poll_pending = 1;
+               local_irq_restore (flags);
        }
 
-       init_timer (&hcd->rh_timer);
-       hcd->rh_timer.function = rh_report_status;
-       hcd->rh_timer.data = (unsigned long) urb;
-       /* USB 2.0 spec says 256msec; this is close enough */
-       hcd->rh_timer.expires = jiffies + HZ/4;
-       add_timer (&hcd->rh_timer);
-       urb->hcpriv = hcd;      /* nonzero to indicate it's queued */
-       return 0;
+       /* The USB 2.0 spec says 256 ms.  This is close enough and won't
+        * exceed that limit if HZ is 100. */
+       if (hcd->uses_new_polling ? hcd->poll_rh :
+                       (length == 0 && hcd->status_urb != NULL))
+               mod_timer (&hcd->rh_timer, jiffies + msecs_to_jiffies(250));
 }
+EXPORT_SYMBOL_GPL(usb_hcd_poll_rh_status);
 
 /* timer callback */
+static void rh_timer_func (unsigned long _hcd)
+{
+       usb_hcd_poll_rh_status((struct usb_hcd *) _hcd);
+}
+
+/*-------------------------------------------------------------------------*/
 
-static void rh_report_status (unsigned long ptr)
+static int rh_queue_status (struct usb_hcd *hcd, struct urb *urb)
 {
-       struct urb      *urb;
-       struct usb_hcd  *hcd;
-       int             length = 0;
+       int             retval;
        unsigned long   flags;
+       int             len = 1 + (urb->dev->maxchild / 8);
 
-       urb = (struct urb *) ptr;
-       local_irq_save (flags);
-       spin_lock (&urb->lock);
+       spin_lock_irqsave (&hcd_root_hub_lock, flags);
+       if (urb->status != -EINPROGRESS)        /* already unlinked */
+               retval = urb->status;
+       else if (hcd->status_urb || urb->transfer_buffer_length < len) {
+               dev_dbg (hcd->self.controller, "not queuing rh status urb\n");
+               retval = -EINVAL;
+       } else {
+               hcd->status_urb = urb;
+               urb->hcpriv = hcd;      /* indicate it's queued */
 
-       /* do nothing if the urb's been unlinked */
-       if (!urb->dev
-                       || urb->status != -EINPROGRESS
-                       || (hcd = urb->dev->bus->hcpriv) == NULL) {
-               spin_unlock (&urb->lock);
-               local_irq_restore (flags);
-               return;
-       }
+               if (!hcd->uses_new_polling)
+                       mod_timer (&hcd->rh_timer, jiffies +
+                                       msecs_to_jiffies(250));
 
-       /* complete the status urb, or retrigger the timer */
-       spin_lock (&hcd_data_lock);
-       if (urb->dev->state == USB_STATE_CONFIGURED) {
-               length = hcd->driver->hub_status_data (
-                                       hcd, urb->transfer_buffer);
-               if (length > 0) {
-                       hcd->rh_timer.data = 0;
-                       urb->actual_length = length;
-                       urb->status = 0;
-                       urb->hcpriv = NULL;
-               } else
-                       mod_timer (&hcd->rh_timer, jiffies + HZ/4);
+               /* If a status change has already occurred, report it ASAP */
+               else if (hcd->poll_pending)
+                       mod_timer (&hcd->rh_timer, jiffies);
+               retval = 0;
        }
-       spin_unlock (&hcd_data_lock);
-       spin_unlock (&urb->lock);
-
-       /* local irqs are always blocked in completions */
-       if (length > 0)
-               usb_hcd_giveback_urb (hcd, urb, NULL);
-       local_irq_restore (flags);
+       spin_unlock_irqrestore (&hcd_root_hub_lock, flags);
+       return retval;
 }
 
-/*-------------------------------------------------------------------------*/
-
 static int rh_urb_enqueue (struct usb_hcd *hcd, struct urb *urb)
 {
-       if (usb_pipeint (urb->pipe)) {
-               int             retval;
-               unsigned long   flags;
-
-               spin_lock_irqsave (&hcd_data_lock, flags);
-               retval = rh_status_urb (hcd, urb);
-               spin_unlock_irqrestore (&hcd_data_lock, flags);
-               return retval;
-       }
+       if (usb_pipeint (urb->pipe))
+               return rh_queue_status (hcd, urb);
        if (usb_pipecontrol (urb->pipe))
                return rh_call_control (hcd, urb);
-       else
-               return -EINVAL;
+       return -EINVAL;
 }
 
 /*-------------------------------------------------------------------------*/
 
+/* Asynchronous unlinks of root-hub control URBs are legal, but they
+ * don't do anything.  Status URB unlinks must be made in process context
+ * with interrupts enabled.
+ */
 static int usb_rh_urb_dequeue (struct usb_hcd *hcd, struct urb *urb)
 {
-       unsigned long   flags;
-
-       /* note:  always a synchronous unlink */
-       if ((unsigned long) urb == hcd->rh_timer.data) {
-               del_timer_sync (&hcd->rh_timer);
-               hcd->rh_timer.data = 0;
-
-               local_irq_save (flags);
-               urb->hcpriv = NULL;
-               usb_hcd_giveback_urb (hcd, urb, NULL);
-               local_irq_restore (flags);
+       if (usb_pipeendpoint(urb->pipe) == 0) { /* Control URB */
+               if (in_interrupt())
+                       return 0;               /* nothing to do */
 
-       } else if (usb_pipeendpoint(urb->pipe) == 0) {
                spin_lock_irq(&urb->lock);      /* from usb_kill_urb */
                ++urb->reject;
                spin_unlock_irq(&urb->lock);
                spin_lock_irq(&urb->lock);
                --urb->reject;
                spin_unlock_irq(&urb->lock);
-       } else
-               return -EINVAL;
+
+       } else {                                /* Status URB */
+               if (!hcd->uses_new_polling)
+                       del_timer_sync (&hcd->rh_timer);
+               local_irq_disable ();
+               spin_lock (&hcd_root_hub_lock);
+               if (urb == hcd->status_urb) {
+                       hcd->status_urb = NULL;
+                       urb->hcpriv = NULL;
+               } else
+                       urb = NULL;             /* wasn't fully queued */
+               spin_unlock (&hcd_root_hub_lock);
+               if (urb)
+                       usb_hcd_giveback_urb (hcd, urb, NULL);
+               local_irq_enable ();
+       }
 
        return 0;
 }
 }
 EXPORT_SYMBOL_GPL(usb_hcd_register_root_hub);
 
+void usb_enable_root_hub_irq (struct usb_bus *bus)
+{
+       struct usb_hcd *hcd;
+
+       hcd = container_of (bus, struct usb_hcd, self);
+       if (hcd->driver->hub_irq_enable && !hcd->poll_rh &&
+                       hcd->state != HC_STATE_HALT)
+               hcd->driver->hub_irq_enable (hcd);
+}
+
 
 /*-------------------------------------------------------------------------*/
 
 
        hcd = udev->bus->hcpriv;
 
-       WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT);
+       WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT &&
+                       udev->state != USB_STATE_NOTATTACHED);
 
        local_irq_disable ();
 
 
        spin_lock_irqsave (&hcd_root_hub_lock, flags);
        if (hcd->rh_registered) {
+               hcd->poll_rh = 0;
+               del_timer(&hcd->rh_timer);
 
                /* make khubd clean up old urbs and devices */
                usb_set_device_state (hcd->self.root_hub,
        hcd->self.bus_name = bus_name;
 
        init_timer(&hcd->rh_timer);
+       hcd->rh_timer.function = rh_timer_func;
+       hcd->rh_timer.data = (unsigned long) hcd;
 
        hcd->driver = driver;
        hcd->product_desc = (driver->product_desc) ? driver->product_desc :
                goto err3;
        }
 
+       if (hcd->uses_new_polling && hcd->poll_rh)
+               usb_hcd_poll_rh_status(hcd);
        return retval;
 
  err3:
        spin_unlock_irq (&hcd_root_hub_lock);
        usb_disconnect(&hcd->self.root_hub);
 
+       hcd->poll_rh = 0;
+       del_timer_sync(&hcd->rh_timer);
+
        hcd->driver->stop(hcd);
        hcd->state = HC_STATE_HALT;
 
 
        const char              *product_desc;  /* product/vendor string */
        char                    irq_descr[24];  /* driver + bus # */
 
-       struct timer_list       rh_timer;       /* drives root hub */
+       struct timer_list       rh_timer;       /* drives root-hub polling */
+       struct urb              *status_urb;    /* the current status urb */
 
        /*
         * hardware info/state
        unsigned                remote_wakeup:1;/* sw should use wakeup? */
        unsigned                rh_registered:1;/* is root hub registered? */
 
+       /* The next flag is a stopgap, to be removed when all the HCDs
+        * support the new root-hub polling mechanism. */
+       unsigned                uses_new_polling:1;
+       unsigned                poll_rh:1;      /* poll for rh status? */
+       unsigned                poll_pending:1; /* status has changed? */
+
        int                     irq;            /* irq allocated */
        void __iomem            *regs;          /* device memory/io */
        u64                     rsrc_start;     /* memory/io resource start */
        int             (*hub_suspend)(struct usb_hcd *);
        int             (*hub_resume)(struct usb_hcd *);
        int             (*start_port_reset)(struct usb_hcd *, unsigned port_num);
+       void            (*hub_irq_enable)(struct usb_hcd *);
+               /* Needed only if port-change IRQs are level-triggered */
 };
 
 extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct pt_regs *regs);
 
 /* generic bus glue, needed for host controllers that don't use PCI */
 extern irqreturn_t usb_hcd_irq (int irq, void *__hcd, struct pt_regs *r);
+
 extern void usb_hc_died (struct usb_hcd *hcd);
+extern void usb_hcd_poll_rh_status(struct usb_hcd *hcd);
 
 /* -------------------------------------------------------------------------- */
 
 extern struct usb_bus *usb_bus_get (struct usb_bus *bus);
 extern void usb_bus_put (struct usb_bus *bus);
 
+extern void usb_enable_root_hub_irq (struct usb_bus *bus);
+
 extern int usb_find_interface_driver (struct usb_device *dev,
        struct usb_interface *interface);