Commit 2bff3916 authored by Tony Lindgren's avatar Tony Lindgren Committed by Greg Kroah-Hartman

usb: musb: Fix PM for hub disconnect

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: 467d5c98 ("usb: musb: Implement session bit based runtime
PM for musb-core")
Fixes: 65b3f50e ("usb: musb: Add PM runtime support for MUSB DSPS
Tested-by: default avatarLadislav Michl <ladis@linux-mips.org>
Tested-by: default avatarLaurent Pinchart <laurent.pinchart@ideasonboard.com>
Signed-off-by: default avatarTony Lindgren <tony@atomide.com>
Signed-off-by: default avatarBin Liu <b-liu@ti.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent ea2f35c0
...@@ -986,7 +986,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb, ...@@ -986,7 +986,7 @@ static irqreturn_t musb_stage0_irq(struct musb *musb, u8 int_usb,
} }
#endif #endif
schedule_work(&musb->irq_work); schedule_delayed_work(&musb->irq_work, 0);
return handled; return handled;
} }
...@@ -1855,14 +1855,23 @@ static void musb_pm_runtime_check_session(struct musb *musb) ...@@ -1855,14 +1855,23 @@ static void musb_pm_runtime_check_session(struct musb *musb)
MUSB_DEVCTL_HR; MUSB_DEVCTL_HR;
switch (devctl & ~s) { switch (devctl & ~s) {
case MUSB_QUIRK_B_INVALID_VBUS_91: case MUSB_QUIRK_B_INVALID_VBUS_91:
if (!musb->session && !musb->quirk_invalid_vbus) { if (musb->quirk_retries--) {
musb->quirk_invalid_vbus = true;
musb_dbg(musb, 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; return;
} }
break;
case MUSB_QUIRK_A_DISCONNECT_19: 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) if (!musb->session)
break; break;
musb_dbg(musb, "Allow PM on possible host mode disconnect"); musb_dbg(musb, "Allow PM on possible host mode disconnect");
...@@ -1886,9 +1895,9 @@ static void musb_pm_runtime_check_session(struct musb *musb) ...@@ -1886,9 +1895,9 @@ static void musb_pm_runtime_check_session(struct musb *musb)
if (error < 0) if (error < 0)
dev_err(musb->controller, "Could not enable: %i\n", dev_err(musb->controller, "Could not enable: %i\n",
error); error);
musb->quirk_retries = 3;
} else { } else {
musb_dbg(musb, "Allow PM with no session: %02x", devctl); musb_dbg(musb, "Allow PM with no session: %02x", devctl);
musb->quirk_invalid_vbus = false;
pm_runtime_mark_last_busy(musb->controller); pm_runtime_mark_last_busy(musb->controller);
pm_runtime_put_autosuspend(musb->controller); pm_runtime_put_autosuspend(musb->controller);
} }
...@@ -1899,7 +1908,7 @@ static void musb_pm_runtime_check_session(struct musb *musb) ...@@ -1899,7 +1908,7 @@ static void musb_pm_runtime_check_session(struct musb *musb)
/* Only used to provide driver mode change events */ /* Only used to provide driver mode change events */
static void musb_irq_work(struct work_struct *data) 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); musb_pm_runtime_check_session(musb);
...@@ -2288,7 +2297,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) ...@@ -2288,7 +2297,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
musb_generic_disable(musb); musb_generic_disable(musb);
/* Init IRQ workqueue before request_irq */ /* 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->deassert_reset_work, musb_deassert_reset);
INIT_DELAYED_WORK(&musb->finish_resume_work, musb_host_finish_resume); INIT_DELAYED_WORK(&musb->finish_resume_work, musb_host_finish_resume);
...@@ -2385,7 +2394,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl) ...@@ -2385,7 +2394,7 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
musb_host_cleanup(musb); musb_host_cleanup(musb);
fail3: 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->finish_resume_work);
cancel_delayed_work_sync(&musb->deassert_reset_work); cancel_delayed_work_sync(&musb->deassert_reset_work);
if (musb->dma_controller) if (musb->dma_controller)
...@@ -2452,7 +2461,7 @@ static int musb_remove(struct platform_device *pdev) ...@@ -2452,7 +2461,7 @@ static int musb_remove(struct platform_device *pdev)
*/ */
musb_exit_debugfs(musb); 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->finish_resume_work);
cancel_delayed_work_sync(&musb->deassert_reset_work); cancel_delayed_work_sync(&musb->deassert_reset_work);
pm_runtime_get_sync(musb->controller); pm_runtime_get_sync(musb->controller);
......
...@@ -310,7 +310,7 @@ struct musb { ...@@ -310,7 +310,7 @@ struct musb {
struct musb_context_registers context; struct musb_context_registers context;
irqreturn_t (*isr)(int, void *); irqreturn_t (*isr)(int, void *);
struct work_struct irq_work; struct delayed_work irq_work;
struct delayed_work deassert_reset_work; struct delayed_work deassert_reset_work;
struct delayed_work finish_resume_work; struct delayed_work finish_resume_work;
struct delayed_work gadget_work; struct delayed_work gadget_work;
...@@ -381,7 +381,7 @@ struct musb { ...@@ -381,7 +381,7 @@ struct musb {
int port_mode; /* MUSB_PORT_MODE_* */ int port_mode; /* MUSB_PORT_MODE_* */
bool session; bool session;
bool quirk_invalid_vbus; unsigned long quirk_retries;
bool is_host; bool is_host;
int a_wait_bcon; /* VBUS timeout in msecs */ int a_wait_bcon; /* VBUS timeout in msecs */
......
...@@ -1114,7 +1114,7 @@ static int musb_gadget_enable(struct usb_ep *ep, ...@@ -1114,7 +1114,7 @@ static int musb_gadget_enable(struct usb_ep *ep,
musb_ep->dma ? "dma, " : "", musb_ep->dma ? "dma, " : "",
musb_ep->packet_sz); musb_ep->packet_sz);
schedule_work(&musb->irq_work); schedule_delayed_work(&musb->irq_work, 0);
fail: fail:
spin_unlock_irqrestore(&musb->lock, flags); spin_unlock_irqrestore(&musb->lock, flags);
...@@ -1158,7 +1158,7 @@ static int musb_gadget_disable(struct usb_ep *ep) ...@@ -1158,7 +1158,7 @@ static int musb_gadget_disable(struct usb_ep *ep)
musb_ep->desc = NULL; musb_ep->desc = NULL;
musb_ep->end_point.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); spin_unlock_irqrestore(&(musb->lock), flags);
...@@ -1994,7 +1994,7 @@ static int musb_gadget_stop(struct usb_gadget *g) ...@@ -1994,7 +1994,7 @@ static int musb_gadget_stop(struct usb_gadget *g)
*/ */
/* Force check of devctl register for PM runtime */ /* 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_mark_last_busy(musb->controller);
pm_runtime_put_autosuspend(musb->controller); pm_runtime_put_autosuspend(musb->controller);
......
...@@ -724,7 +724,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase) ...@@ -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", dev_dbg(musb->controller, "vbus change, %s, otg %03x\n",
usb_otg_state_string(musb->xceiv->otg->state), otg_stat); usb_otg_state_string(musb->xceiv->otg->state), otg_stat);
idle_timeout = jiffies + (1 * HZ); idle_timeout = jiffies + (1 * HZ);
schedule_work(&musb->irq_work); schedule_delayed_work(&musb->irq_work, 0);
} else /* A-dev state machine */ { } else /* A-dev state machine */ {
dev_dbg(musb->controller, "vbus change, %s, otg %03x\n", 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) ...@@ -814,7 +814,7 @@ tusb_otg_ints(struct musb *musb, u32 int_src, void __iomem *tbase)
break; break;
} }
} }
schedule_work(&musb->irq_work); schedule_delayed_work(&musb->irq_work, 0);
return idle_timeout; return idle_timeout;
} }
...@@ -864,7 +864,7 @@ static irqreturn_t tusb_musb_interrupt(int irq, void *__hci) ...@@ -864,7 +864,7 @@ static irqreturn_t tusb_musb_interrupt(int irq, void *__hci)
musb_writel(tbase, TUSB_PRCM_WAKEUP_CLEAR, reg); musb_writel(tbase, TUSB_PRCM_WAKEUP_CLEAR, reg);
if (reg & ~TUSB_PRCM_WNORCS) { if (reg & ~TUSB_PRCM_WNORCS) {
musb->is_active = 1; musb->is_active = 1;
schedule_work(&musb->irq_work); schedule_delayed_work(&musb->irq_work, 0);
} }
dev_dbg(musb->controller, "wake %sactive %02x\n", dev_dbg(musb->controller, "wake %sactive %02x\n",
musb->is_active ? "" : "in", reg); musb->is_active ? "" : "in", reg);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment