usb: musb: Fix PM for hub disconnect
authorTony Lindgren <tony@atomide.com>
Wed, 16 Nov 2016 19:21:24 +0000 (13:21 -0600)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 17 Nov 2016 15:25:39 +0000 (16:25 +0100)
With a USB hub disconnected, devctl can be 0x19 for about a second
on am335x and will stay forever on at least omap3. And we get no
further interrupts when devctl session bit clears. This keeps
PM runtime active.

Let's fix the issue by polling devctl until the session bit clears
or times out. We can do this by making musb->irq_work into
delayed_work.

And with the polling implemented, we can now also have the quirk
for invalid VBUS it to avoid disconnecting too early while VBUS
is ramping up.

Fixes: 467d5c980709 ("usb: musb: Implement session bit based runtime
PM for musb-core")
Fixes: 65b3f50ed6fa ("usb: musb: Add PM runtime support for MUSB DSPS
Tested-by: Ladislav Michl <ladis@linux-mips.org>
Tested-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: Tony Lindgren <tony@atomide.com>
Signed-off-by: Bin Liu <b-liu@ti.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/musb/musb_core.c
drivers/usb/musb/musb_core.h
drivers/usb/musb/musb_gadget.c
drivers/usb/musb/tusb6010.c

index 384de6cd26f5e40d844b61f431259a0fcce2877b..c3e172e15ec3d9d9ec7346b972b2702fed710c65 100644 (file)
@@ -986,7 +986,7 @@ b_host:
        }
 #endif
 
-       schedule_work(&musb->irq_work);
+       schedule_delayed_work(&musb->irq_work, 0);
 
        return handled;
 }
@@ -1855,14 +1855,23 @@ static void musb_pm_runtime_check_session(struct musb *musb)
                MUSB_DEVCTL_HR;
        switch (devctl & ~s) {
        case MUSB_QUIRK_B_INVALID_VBUS_91:
-               if (!musb->session && !musb->quirk_invalid_vbus) {
-                       musb->quirk_invalid_vbus = true;
+               if (musb->quirk_retries--) {
                        musb_dbg(musb,
-                                "First invalid vbus, assume no session");
+                                "Poll devctl on invalid vbus, assume no session");
+                       schedule_delayed_work(&musb->irq_work,
+                                             msecs_to_jiffies(1000));
+
                        return;
                }
-               break;
        case MUSB_QUIRK_A_DISCONNECT_19:
+               if (musb->quirk_retries--) {
+                       musb_dbg(musb,
+                                "Poll devctl on possible host mode disconnect");
+                       schedule_delayed_work(&musb->irq_work,
+                                             msecs_to_jiffies(1000));
+
+                       return;
+               }
                if (!musb->session)
                        break;
                musb_dbg(musb, "Allow PM on possible host mode disconnect");
@@ -1886,9 +1895,9 @@ static void musb_pm_runtime_check_session(struct musb *musb)
                if (error < 0)
                        dev_err(musb->controller, "Could not enable: %i\n",
                                error);
+               musb->quirk_retries = 3;
        } else {
                musb_dbg(musb, "Allow PM with no session: %02x", devctl);
-               musb->quirk_invalid_vbus = false;
                pm_runtime_mark_last_busy(musb->controller);
                pm_runtime_put_autosuspend(musb->controller);
        }
@@ -1899,7 +1908,7 @@ static void musb_pm_runtime_check_session(struct musb *musb)
 /* Only used to provide driver mode change events */
 static void musb_irq_work(struct work_struct *data)
 {
-       struct musb *musb = container_of(data, struct musb, irq_work);
+       struct musb *musb = container_of(data, struct musb, irq_work.work);
 
        musb_pm_runtime_check_session(musb);
 
@@ -2288,7 +2297,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
        musb_generic_disable(musb);
 
        /* Init IRQ workqueue before request_irq */
-       INIT_WORK(&musb->irq_work, musb_irq_work);
+       INIT_DELAYED_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);
 
@@ -2385,7 +2394,7 @@ fail4:
        musb_host_cleanup(musb);
 
 fail3:
-       cancel_work_sync(&musb->irq_work);
+       cancel_delayed_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)
@@ -2452,7 +2461,7 @@ static int musb_remove(struct platform_device *pdev)
         */
        musb_exit_debugfs(musb);
 
-       cancel_work_sync(&musb->irq_work);
+       cancel_delayed_work_sync(&musb->irq_work);
        cancel_delayed_work_sync(&musb->finish_resume_work);
        cancel_delayed_work_sync(&musb->deassert_reset_work);
        pm_runtime_get_sync(musb->controller);
index 15b1f93c70379451ceda86ae0f9cf535e2780f58..91817d77d59c8ecd226e25fb6ce9e07b5a597d3f 100644 (file)
@@ -310,7 +310,7 @@ struct musb {
        struct musb_context_registers context;
 
        irqreturn_t             (*isr)(int, void *);
-       struct work_struct      irq_work;
+       struct delayed_work     irq_work;
        struct delayed_work     deassert_reset_work;
        struct delayed_work     finish_resume_work;
        struct delayed_work     gadget_work;
@@ -381,7 +381,7 @@ struct musb {
 
        int                     port_mode;      /* MUSB_PORT_MODE_* */
        bool                    session;
-       bool                    quirk_invalid_vbus;
+       unsigned long           quirk_retries;
        bool                    is_host;
 
        int                     a_wait_bcon;    /* VBUS timeout in msecs */
index 910f509676277ccec6162d69d26d105087647424..a55173c9e5645d6f5245c0ff78c4ccf151d75107 100644 (file)
@@ -1114,7 +1114,7 @@ static int musb_gadget_enable(struct usb_ep *ep,
                        musb_ep->dma ? "dma, " : "",
                        musb_ep->packet_sz);
 
-       schedule_work(&musb->irq_work);
+       schedule_delayed_work(&musb->irq_work, 0);
 
 fail:
        spin_unlock_irqrestore(&musb->lock, flags);
@@ -1158,7 +1158,7 @@ static int musb_gadget_disable(struct usb_ep *ep)
        musb_ep->desc = NULL;
        musb_ep->end_point.desc = NULL;
 
-       schedule_work(&musb->irq_work);
+       schedule_delayed_work(&musb->irq_work, 0);
 
        spin_unlock_irqrestore(&(musb->lock), flags);
 
@@ -1994,7 +1994,7 @@ static int musb_gadget_stop(struct usb_gadget *g)
         */
 
        /* Force check of devctl register for PM runtime */
-       schedule_work(&musb->irq_work);
+       schedule_delayed_work(&musb->irq_work, 0);
 
        pm_runtime_mark_last_busy(musb->controller);
        pm_runtime_put_autosuspend(musb->controller);
index df7c9f46be548f61800b7beaa7f182b5dc447ad5..e85cc8e4e7a9c02e32fdef579d04d8adb22469df 100644 (file)
@@ -724,7 +724,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase)
                        dev_dbg(musb->controller, "vbus change, %s, otg %03x\n",
                                usb_otg_state_string(musb->xceiv->otg->state), otg_stat);
                        idle_timeout = jiffies + (1 * HZ);
-                       schedule_work(&musb->irq_work);
+                       schedule_delayed_work(&musb->irq_work, 0);
 
                } else /* A-dev state machine */ {
                        dev_dbg(musb->controller, "vbus change, %s, otg %03x\n",
@@ -814,7 +814,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase)
                        break;
                }
        }
-       schedule_work(&musb->irq_work);
+       schedule_delayed_work(&musb->irq_work, 0);
 
        return idle_timeout;
 }
@@ -864,7 +864,7 @@ static irqreturn_t tusb_musb_interrupt(int irq, void *__hci)
                musb_writel(tbase, TUSB_PRCM_WAKEUP_CLEAR, reg);
                if (reg & ~TUSB_PRCM_WNORCS) {
                        musb->is_active = 1;
-                       schedule_work(&musb->irq_work);
+                       schedule_delayed_work(&musb->irq_work, 0);
                }
                dev_dbg(musb->controller, "wake %sactive %02x\n",
                                musb->is_active ? "" : "in", reg);