| From foo@baz Sun Jun 17 12:07:34 CEST 2018 |
| From: "Daniel Glöckner" <dg@emlix.com> |
| Date: Mon, 14 May 2018 09:40:05 -0500 |
| Subject: usb: musb: fix remote wakeup racing with suspend |
| |
| From: "Daniel Glöckner" <dg@emlix.com> |
| |
| [ Upstream commit ebc3dd688cd988754a304147753b13e58de1b5a1 ] |
| |
| It has been observed that writing 0xF2 to the power register while it |
| reads as 0xF4 results in the register having the value 0xF0, i.e. clearing |
| RESUME and setting SUSPENDM in one go does not work. It might also violate |
| the USB spec to transition directly from resume to suspend, especially |
| when not taking T_DRSMDN into account. But this is what happens when a |
| remote wakeup occurs between SetPortFeature USB_PORT_FEAT_SUSPEND on the |
| root hub and musb_bus_suspend being called. |
| |
| This commit returns -EBUSY when musb_bus_suspend is called while remote |
| wakeup is signalled and thus avoids to reset the RESUME bit. Ignoring |
| this error when musb_port_suspend is called from musb_hub_control is ok. |
| |
| Signed-off-by: Daniel Glöckner <dg@emlix.com> |
| Signed-off-by: Bin Liu <b-liu@ti.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Sasha Levin <alexander.levin@microsoft.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| drivers/usb/musb/musb_host.c | 5 ++++- |
| drivers/usb/musb/musb_host.h | 7 +++++-- |
| drivers/usb/musb/musb_virthub.c | 25 +++++++++++++++---------- |
| 3 files changed, 24 insertions(+), 13 deletions(-) |
| |
| --- a/drivers/usb/musb/musb_host.c |
| +++ b/drivers/usb/musb/musb_host.c |
| @@ -2530,8 +2530,11 @@ static int musb_bus_suspend(struct usb_h |
| { |
| struct musb *musb = hcd_to_musb(hcd); |
| u8 devctl; |
| + int ret; |
| |
| - musb_port_suspend(musb, true); |
| + ret = musb_port_suspend(musb, true); |
| + if (ret) |
| + return ret; |
| |
| if (!is_host_active(musb)) |
| return 0; |
| --- a/drivers/usb/musb/musb_host.h |
| +++ b/drivers/usb/musb/musb_host.h |
| @@ -67,7 +67,7 @@ extern void musb_host_rx(struct musb *, |
| extern void musb_root_disconnect(struct musb *musb); |
| 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 int 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 |
| @@ -99,7 +99,10 @@ static inline void musb_root_disconnect( |
| static inline void musb_host_resume_root_hub(struct musb *musb) {} |
| 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 int musb_port_suspend(struct musb *musb, bool do_suspend) |
| +{ |
| + return 0; |
| +} |
| static inline void musb_port_reset(struct musb *musb, bool do_reset) {} |
| static inline void musb_host_finish_resume(struct work_struct *work) {} |
| #endif |
| --- a/drivers/usb/musb/musb_virthub.c |
| +++ b/drivers/usb/musb/musb_virthub.c |
| @@ -48,14 +48,14 @@ void musb_host_finish_resume(struct work |
| spin_unlock_irqrestore(&musb->lock, flags); |
| } |
| |
| -void musb_port_suspend(struct musb *musb, bool do_suspend) |
| +int musb_port_suspend(struct musb *musb, bool do_suspend) |
| { |
| struct usb_otg *otg = musb->xceiv->otg; |
| u8 power; |
| void __iomem *mbase = musb->mregs; |
| |
| if (!is_host_active(musb)) |
| - return; |
| + return 0; |
| |
| /* NOTE: this doesn't necessarily put PHY into low power mode, |
| * turning off its clock; that's a function of PHY integration and |
| @@ -66,16 +66,20 @@ void musb_port_suspend(struct musb *musb |
| if (do_suspend) { |
| int retries = 10000; |
| |
| - power &= ~MUSB_POWER_RESUME; |
| - power |= MUSB_POWER_SUSPENDM; |
| - musb_writeb(mbase, MUSB_POWER, power); |
| + if (power & MUSB_POWER_RESUME) |
| + return -EBUSY; |
| + |
| + if (!(power & MUSB_POWER_SUSPENDM)) { |
| + power |= MUSB_POWER_SUSPENDM; |
| + musb_writeb(mbase, MUSB_POWER, power); |
| |
| - /* Needed for OPT A tests */ |
| - power = musb_readb(mbase, MUSB_POWER); |
| - while (power & MUSB_POWER_SUSPENDM) { |
| + /* Needed for OPT A tests */ |
| power = musb_readb(mbase, MUSB_POWER); |
| - if (retries-- < 1) |
| - break; |
| + while (power & MUSB_POWER_SUSPENDM) { |
| + power = musb_readb(mbase, MUSB_POWER); |
| + if (retries-- < 1) |
| + break; |
| + } |
| } |
| |
| musb_dbg(musb, "Root port suspended, power %02x", power); |
| @@ -111,6 +115,7 @@ void musb_port_suspend(struct musb *musb |
| schedule_delayed_work(&musb->finish_resume_work, |
| msecs_to_jiffies(USB_RESUME_TIMEOUT)); |
| } |
| + return 0; |
| } |
| |
| void musb_port_reset(struct musb *musb, bool do_reset) |