Skip to content

Commit

Permalink
usb: musb: finish suspend/reset work independently from musb_hub_cont…
Browse files Browse the repository at this point in the history
…rol()

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>
  • Loading branch information
Daniel Mack authored and Felipe Balbi committed Dec 19, 2013
1 parent 56b1b90 commit 8ed1fb7
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 34 deletions.
25 changes: 23 additions & 2 deletions drivers/usb/musb/musb_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
*
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1990,6 +2007,8 @@ musb_init_controller(struct device *dev, int nIrq, void __iomem *ctrl)

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:
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions drivers/usb/musb/musb_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
2 changes: 2 additions & 0 deletions drivers/usb/musb/musb_host.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
Expand Down
65 changes: 33 additions & 32 deletions drivers/usb/musb/musb_virthub.c
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@

#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;
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 8ed1fb7

Please sign in to comment.