Commit b659d87e authored by David Brownell's avatar David Brownell Committed by Tony Lindgren

musb_hdrc: small fixes, mostly suspend/resume

A collection of mostly orthogonal and small fixes:

 - Update various corner cases in the side suspend/resume code,
   triggered mostly on the host side:
     * avoid spurious gadget driver resume callbacks in B_IDLE state
     * avoid spurious gadget driver suspend callbacks in A_HOST state
     * cope better with some spurious resume irqs
     * partial fix for disconnection issues in A_SUSPEND

 - Resolve some TUSB rmmod races by disabling IRQs at the proper moment,
   and killing the idle timer.

 - Only mark TUSB device as wakeup-capable if it's connected to an
   IRQ which allows that.

 - Don't idle the TUSB chip until khubd handles all pending port
   status change events; dump that status in procfs.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
parent 8e84c387
......@@ -1903,11 +1903,21 @@ EXPORT_SYMBOL(usb_gadget_unregister_driver);
void musb_g_resume(struct musb *pThis)
{
DBG(4, "<==\n");
if (pThis->pGadgetDriver && pThis->pGadgetDriver->resume) {
spin_unlock(&pThis->Lock);
pThis->pGadgetDriver->resume(&pThis->g);
spin_lock(&pThis->Lock);
switch (pThis->xceiv.state) {
case OTG_STATE_B_IDLE:
break;
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_PERIPHERAL:
pThis->is_active = 1;
if (pThis->pGadgetDriver && pThis->pGadgetDriver->resume) {
spin_unlock(&pThis->Lock);
pThis->pGadgetDriver->resume(&pThis->g);
spin_lock(&pThis->Lock);
}
break;
default:
WARN("unhandled RESUME transition (%s)\n",
otg_state_string(pThis));
}
}
......
......@@ -534,6 +534,15 @@ static int dump_header_stats(struct musb *pThis, char *buffer)
count += code;
buffer += code;
#ifdef CONFIG_USB_MUSB_HDRC_HCD
code = sprintf(buffer, "Root port status: %08x\n",
pThis->port1_status);
if (code <= 0)
goto done;
buffer += code;
count += code;
#endif
#ifdef CONFIG_ARCH_DAVINCI
code = sprintf(buffer,
"DaVinci: ctrl=%02x stat=%1x phy=%03x\n"
......
......@@ -316,45 +316,76 @@ static irqreturn_t musb_stage0_irq(struct musb * pThis, u8 bIntrUSB,
DBG(3, "<== Power=%02x, DevCtl=%02x, bIntrUSB=0x%x\n", power, devctl,
bIntrUSB);
/* in host mode when a device resume me (from power save)
* in device mode when the host resume me; it shold not change
* "identity".
/* in host mode, the peripheral may issue remote wakeup.
* in peripheral mode, the host may resume the link.
* spurious RESUME irqs happen too, paired with SUSPEND.
*/
if (bIntrUSB & MGC_M_INTR_RESUME) {
handled = IRQ_HANDLED;
DBG(3, "RESUME\n");
pThis->is_active = 1;
DBG(3, "RESUME (%s)\n", otg_state_string(pThis));
if (devctl & MGC_M_DEVCTL_HM) {
#ifdef CONFIG_USB_MUSB_HDRC_HCD
power &= ~MGC_M_POWER_SUSPENDM;
musb_writeb(pBase, MGC_O_HDRC_POWER,
power | MGC_M_POWER_RESUME);
/* later, GetPortStatus will stop RESUME signaling */
pThis->port1_status |= MUSB_PORT_STAT_RESUME;
pThis->rh_timer = jiffies + msecs_to_jiffies(20);
/* should now be A_SUSPEND */
switch (pThis->xceiv.state) {
case OTG_STATE_A_SUSPEND:
pThis->xceiv.state = OTG_STATE_A_HOST;
usb_hcd_resume_root_hub(musb_to_hcd(pThis));
/* remote wakeup? later, GetPortStatus
* will stop RESUME signaling
*/
if (power & MGC_M_POWER_RESUME) {
power &= ~MGC_M_POWER_SUSPENDM;
musb_writeb(pBase, MGC_O_HDRC_POWER,
power | MGC_M_POWER_RESUME);
pThis->port1_status |=
MUSB_PORT_STAT_RESUME
| USB_PORT_STAT_C_SUSPEND;
pThis->rh_timer = jiffies
+ msecs_to_jiffies(20);
pThis->xceiv.state = OTG_STATE_A_HOST;
pThis->is_active = 1;
usb_hcd_resume_root_hub(
musb_to_hcd(pThis));
} else if (power & MGC_M_POWER_SUSPENDM) {
/* spurious */
pThis->int_usb &= ~MGC_M_INTR_SUSPEND;
}
break;
case OTG_STATE_B_WAIT_ACON:
pThis->xceiv.state = OTG_STATE_B_PERIPHERAL;
pThis->is_active = 1;
MUSB_DEV_MODE(pThis);
break;
default:
WARN("bogus RESUME, from %s\n",
WARN("bogus %s RESUME (%s)\n",
"host",
otg_state_string(pThis));
}
#endif
} else {
switch (pThis->xceiv.state) {
#ifdef CONFIG_USB_MUSB_HDRC_HCD
case OTG_STATE_A_SUSPEND:
/* possibly DISCONNECT is upcoming */
pThis->xceiv.state = OTG_STATE_A_HOST;
usb_hcd_resume_root_hub(musb_to_hcd(pThis));
break;
#endif
#ifdef CONFIG_USB_GADGET_MUSB_HDRC
MUSB_DEV_MODE(pThis); /* unnecessary */
case OTG_STATE_B_WAIT_ACON:
case OTG_STATE_B_PERIPHERAL:
musb_g_resume(pThis);
break;
case OTG_STATE_B_IDLE:
pThis->int_usb &= ~MGC_M_INTR_SUSPEND;
break;
#endif
musb_g_resume(pThis);
default:
WARN("bogus %s RESUME (%s)\n",
"peripheral",
otg_state_string(pThis));
}
}
}
......@@ -552,18 +583,33 @@ static irqreturn_t musb_stage2_irq(struct musb * pThis, u8 bIntrUSB,
}
if (bIntrUSB & MGC_M_INTR_SUSPEND) {
DBG(1, "SUSPEND, devctl %02x\n", devctl);
DBG(1, "SUSPEND (%s) devctl %02x\n",
otg_state_string(pThis), devctl);
handled = IRQ_HANDLED;
/* peripheral suspend, may trigger HNP */
if (!(devctl & MGC_M_DEVCTL_HM)) {
switch (pThis->xceiv.state) {
case OTG_STATE_B_PERIPHERAL:
musb_g_suspend(pThis);
pThis->is_active = is_otg_enabled(pThis)
&& pThis->xceiv.gadget->b_hnp_enable;
} else
if (pThis->is_active) {
pThis->xceiv.state = OTG_STATE_B_WAIT_ACON;
/* REVISIT timeout for b_ase0_brst, etc */
}
break;
case OTG_STATE_A_HOST:
pThis->xceiv.state = OTG_STATE_A_SUSPEND;
pThis->is_active = is_otg_enabled(pThis)
&& pThis->xceiv.host->b_hnp_enable;
break;
default:
/* "should not happen" */
pThis->is_active = 0;
break;
}
}
return handled;
}
......@@ -650,11 +696,11 @@ static void musb_generic_disable(struct musb *pThis)
* with controller locked, irqs blocked
* acts as a NOP unless some role activated the hardware
*/
void musb_stop(struct musb * pThis)
void musb_stop(struct musb *musb)
{
/* stop IRQs, timers, ... */
musb_platform_disable(pThis);
musb_generic_disable(pThis);
musb_platform_disable(musb);
musb_generic_disable(musb);
DBG(3, "HDRC disabled\n");
/* FIXME
......@@ -664,14 +710,7 @@ void musb_stop(struct musb * pThis)
* OTG mode, gadget driver module rmmod/modprobe cycles that
* - ...
*/
#ifdef CONFIG_USB_MUSB_HDRC_HCD
if (is_host_enabled(pThis)) {
/* REVISIT aren't there some paths where this is wrong? */
dev_warn(pThis->controller, "%s, root hub still active\n",
__FUNCTION__);
}
#endif
musb_platform_try_idle(musb);
}
static void musb_shutdown(struct platform_device *pdev)
......@@ -1651,9 +1690,9 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)
status = -ENODEV;
goto fail2;
}
(void) enable_irq_wake(nIrq);
pThis->nIrq = nIrq;
device_init_wakeup(dev, 1);
if (enable_irq_wake(nIrq) == 0)
device_init_wakeup(dev, 1);
pr_info("%s: USB %s mode controller at %p using %s, IRQ %d\n",
musb_driver_name,
......
......@@ -277,6 +277,12 @@ static void musb_do_idle(unsigned long _musb)
if (!musb->is_active) {
u32 wakeups;
#ifdef CONFIG_USB_MUSB_HDRC_HCD
/* wait until khubd handles port change status */
if (is_host_active(musb) && (musb->port1_status >> 16))
goto done;
#endif
#ifdef CONFIG_USB_GADGET_MUSB_HDRC
if (is_peripheral_enabled(musb) && !musb->pGadgetDriver)
wakeups = 0;
......@@ -292,6 +298,7 @@ static void musb_do_idle(unsigned long _musb)
#endif
tusb_allow_idle(musb, wakeups);
}
done:
spin_unlock_irqrestore(&musb->Lock, flags);
}
......@@ -636,8 +643,18 @@ void musb_platform_enable(struct musb * musb)
*/
void musb_platform_disable(struct musb *musb)
{
void __iomem *base = musb->ctrl_base;
/* FIXME stop DMA, IRQs, timers, ... */
/* disable all IRQs */
musb_writel(base, TUSB_INT_MASK, ~TUSB_INT_MASK_RESERVED_BITS);
musb_writel(base, TUSB_USBIP_INT_MASK, 0);
musb_writel(base, TUSB_DMA_INT_MASK, 0x7fffffff);
musb_writel(base, TUSB_GPIO_INT_MASK, 0x1ff);
del_timer(&musb_idle_timer);
if (is_dma_capable() && !dma_off) {
printk(KERN_WARNING "%s %s: dma still active\n",
__FILE__, __FUNCTION__);
......@@ -811,6 +828,8 @@ int __devinit musb_platform_init(struct musb *musb)
int musb_platform_exit(struct musb *musb)
{
del_timer_sync(&musb_idle_timer);
if (musb->board_set_power)
musb->board_set_power(0);
......
......@@ -105,8 +105,7 @@ static void musb_port_reset(struct musb *musb, u8 bReset)
u8 devctl = musb_readb(pBase, MGC_O_HDRC_DEVCTL);
if (musb->bDelayPortPowerOff || !(devctl & MGC_M_DEVCTL_HM)) {
// return;
DBG(1, "what?\n");
return;
}
#endif
......@@ -158,13 +157,14 @@ void musb_root_disconnect(struct musb *musb)
switch (musb->xceiv.state) {
case OTG_STATE_A_HOST:
case OTG_STATE_A_SUSPEND:
musb->xceiv.state = OTG_STATE_A_WAIT_BCON;
break;
case OTG_STATE_A_WAIT_VFALL:
musb->xceiv.state = OTG_STATE_B_IDLE;
break;
default:
DBG(1, "host disconnect, state %d\n", musb->xceiv.state);
DBG(1, "host disconnect (%s)\n", otg_state_string(musb));
}
}
......
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