usb: musb: finish suspend/reset work independently from musb_hub_control()
authorDaniel Mack <zonque@gmail.com>
Wed, 18 Dec 2013 19:23:46 +0000 (20:23 +0100)
committerFelipe Balbi <balbi@ti.com>
Thu, 19 Dec 2013 16:31:11 +0000 (10:31 -0600)
Currently, resume and reset is completed when the USB core calls back
the root hub, asking for the port's state. This results in
unpredictable timing of state assertion, which in turn renders some
USB devices unusable after resume.

Fix this by moving the logic to end the reset and suspend state out of
musb_hub_control() into separate functions called from delayed workers.
GetPortStatus only reports the current state now, without taking any
real action.

The rh_timeout variable is kept in order to define a minimum time gap
between reset and resume only.

FWIW, in my case, a Verbatim "STORE N GO" mass storage device won't
resume cleanly without this patch.

Signed-off-by: Daniel Mack <zonque@gmail.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/musb/musb_core.c
drivers/usb/musb/musb_core.h
drivers/usb/musb/musb_host.h
drivers/usb/musb/musb_virthub.c

index 1ecdb94f38128e78e614da805c04e13b6f59818f..0e7f4a00ca7ba722a6099ee0c0206270eac0bbc5 100644 (file)
@@ -478,8 +478,8 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
                                musb->port1_status |=
                                                (USB_PORT_STAT_C_SUSPEND << 16)
                                                | MUSB_PORT_STAT_RESUME;
-                               musb->rh_timer = jiffies
-                                               + msecs_to_jiffies(20);
+                               schedule_delayed_work(
+                                       &musb->finish_resume_work, 20);
 
                                musb->xceiv->state = OTG_STATE_A_HOST;
                                musb->is_active = 1;
@@ -1813,6 +1813,21 @@ static void musb_free(struct musb *musb)
        musb_host_free(musb);
 }
 
+static void musb_deassert_reset(struct work_struct *work)
+{
+       struct musb *musb;
+       unsigned long flags;
+
+       musb = container_of(work, struct musb, deassert_reset_work.work);
+
+       spin_lock_irqsave(&musb->lock, flags);
+
+       if (musb->port1_status & USB_PORT_STAT_RESET)
+               musb_port_reset(musb, false);
+
+       spin_unlock_irqrestore(&musb->lock, flags);
+}
+
 /*
  * Perform generic per-controller initialization.
  *
@@ -1897,6 +1912,8 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
 
        /* Init IRQ workqueue before request_irq */
        INIT_WORK(&musb->irq_work, musb_irq_work);
+       INIT_DELAYED_WORK(&musb->deassert_reset_work, musb_deassert_reset);
+       INIT_DELAYED_WORK(&musb->finish_resume_work, musb_host_finish_resume);
 
        /* setup musb parts of the core (especially endpoints) */
        status = musb_core_init(plat->config->multipoint
@@ -1990,6 +2007,8 @@ fail4:
 
 fail3:
        cancel_work_sync(&musb->irq_work);
+       cancel_delayed_work_sync(&musb->finish_resume_work);
+       cancel_delayed_work_sync(&musb->deassert_reset_work);
        if (musb->dma_controller)
                dma_controller_destroy(musb->dma_controller);
 fail2_5:
@@ -2053,6 +2072,8 @@ static int musb_remove(struct platform_device *pdev)
                dma_controller_destroy(musb->dma_controller);
 
        cancel_work_sync(&musb->irq_work);
+       cancel_delayed_work_sync(&musb->finish_resume_work);
+       cancel_delayed_work_sync(&musb->deassert_reset_work);
        musb_free(musb);
        device_init_wakeup(dev, 0);
        return 0;
index 29f7cd7c7964e9bf78607de0031396393e84bd44..7083e82776ff63012e3de51fda1b50484808f40c 100644 (file)
@@ -47,6 +47,7 @@
 #include <linux/usb/otg.h>
 #include <linux/usb/musb.h>
 #include <linux/phy/phy.h>
+#include <linux/workqueue.h>
 
 struct musb;
 struct musb_hw_ep;
@@ -295,6 +296,8 @@ struct musb {
 
        irqreturn_t             (*isr)(int, void *);
        struct work_struct      irq_work;
+       struct delayed_work     deassert_reset_work;
+       struct delayed_work     finish_resume_work;
        u16                     hwvers;
 
        u16                     intrrxe;
index 7436c24af0ffb6e1c5e932a2780f2e962ebd0288..909ba49ef069b6be138592df3db8a12e09857571 100644 (file)
@@ -94,6 +94,7 @@ extern void musb_host_resume_root_hub(struct musb *musb);
 extern void musb_host_poke_root_hub(struct musb *musb);
 extern void musb_port_suspend(struct musb *musb, bool do_suspend);
 extern void musb_port_reset(struct musb *musb, bool do_reset);
+extern void musb_host_finish_resume(struct work_struct *work);
 #else
 static inline struct musb *hcd_to_musb(struct usb_hcd *hcd)
 {
@@ -125,6 +126,7 @@ static inline void musb_host_poll_rh_status(struct musb *musb)      {}
 static inline void musb_host_poke_root_hub(struct musb *musb)  {}
 static inline void musb_port_suspend(struct musb *musb, bool do_suspend) {}
 static inline void musb_port_reset(struct musb *musb)          {}
+static inline void musb_host_finish_resume(struct work_struct *work) {}
 #endif
 
 struct usb_hcd;
index 24e46c0c84b5281c7689ac219ef12402ae032610..ad417fd0e1a7fdaf997f6b3fa38cbc59ef5a4dfe 100644 (file)
 
 #include "musb_core.h"
 
+void musb_host_finish_resume(struct work_struct *work)
+{
+       struct musb *musb;
+       unsigned long flags;
+       u8 power;
+
+       musb = container_of(work, struct musb, deassert_reset_work.work);
+
+       spin_lock_irqsave(&musb->lock, flags);
+
+       power = musb_readb(musb->mregs, MUSB_POWER);
+       power &= ~MUSB_POWER_RESUME;
+       dev_dbg(musb->controller, "root port resume stopped, power %02x\n",
+               power);
+       musb_writeb(musb->mregs, MUSB_POWER, power);
+
+       /*
+        * ISSUE:  DaVinci (RTL 1.300) disconnects after
+        * resume of high speed peripherals (but not full
+        * speed ones).
+        */
+       musb->is_active = 1;
+       musb->port1_status &= ~(USB_PORT_STAT_SUSPEND | MUSB_PORT_STAT_RESUME);
+       musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
+       usb_hcd_poll_rh_status(musb->hcd);
+       /* NOTE: it might really be A_WAIT_BCON ... */
+       musb->xceiv->state = OTG_STATE_A_HOST;
+
+       spin_unlock_irqrestore(&musb->lock, flags);
+}
+
 void musb_port_suspend(struct musb *musb, bool do_suspend)
 {
        struct usb_otg  *otg = musb->xceiv->otg;
@@ -105,7 +136,7 @@ void musb_port_suspend(struct musb *musb, bool do_suspend)
 
                /* later, GetPortStatus will stop RESUME signaling */
                musb->port1_status |= MUSB_PORT_STAT_RESUME;
-               musb->rh_timer = jiffies + msecs_to_jiffies(20);
+               schedule_delayed_work(&musb->finish_resume_work, 20);
        }
 }
 
@@ -150,7 +181,7 @@ void musb_port_reset(struct musb *musb, bool do_reset)
 
                musb->port1_status |= USB_PORT_STAT_RESET;
                musb->port1_status &= ~USB_PORT_STAT_ENABLE;
-               musb->rh_timer = jiffies + msecs_to_jiffies(50);
+               schedule_delayed_work(&musb->deassert_reset_work, 50);
        } else {
                dev_dbg(musb->controller, "root port reset stopped\n");
                musb_writeb(mbase, MUSB_POWER,
@@ -325,36 +356,6 @@ int musb_hub_control(
                if (wIndex != 1)
                        goto error;
 
-               /* finish RESET signaling? */
-               if ((musb->port1_status & USB_PORT_STAT_RESET)
-                               && time_after_eq(jiffies, musb->rh_timer))
-                       musb_port_reset(musb, false);
-
-               /* finish RESUME signaling? */
-               if ((musb->port1_status & MUSB_PORT_STAT_RESUME)
-                               && time_after_eq(jiffies, musb->rh_timer)) {
-                       u8              power;
-
-                       power = musb_readb(musb->mregs, MUSB_POWER);
-                       power &= ~MUSB_POWER_RESUME;
-                       dev_dbg(musb->controller, "root port resume stopped, power %02x\n",
-                                       power);
-                       musb_writeb(musb->mregs, MUSB_POWER, power);
-
-                       /* ISSUE:  DaVinci (RTL 1.300) disconnects after
-                        * resume of high speed peripherals (but not full
-                        * speed ones).
-                        */
-
-                       musb->is_active = 1;
-                       musb->port1_status &= ~(USB_PORT_STAT_SUSPEND
-                                       | MUSB_PORT_STAT_RESUME);
-                       musb->port1_status |= USB_PORT_STAT_C_SUSPEND << 16;
-                       usb_hcd_poll_rh_status(musb->hcd);
-                       /* NOTE: it might really be A_WAIT_BCON ... */
-                       musb->xceiv->state = OTG_STATE_A_HOST;
-               }
-
                put_unaligned(cpu_to_le32(musb->port1_status
                                        & ~MUSB_PORT_STAT_RESUME),
                                (__le32 *) buf);