diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index 95ed745ce6a3..b04bbed7f29b 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1622,6 +1622,7 @@ static int dwc3_probe(struct platform_device *pdev) platform_set_drvdata(pdev, dwc); + init_waitqueue_head(&dwc->wait_linkstate); spin_lock_init(&dwc->lock); pm_runtime_no_callbacks(dev); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index aec7ad30aebc..2f4f9cf44ad9 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1151,6 +1151,8 @@ struct dwc3_scratchpad_array { * @bh_dbg_index: index for capturing bh_completion_time and bh_handled_evt_cnt * @last_run_stop: timestamp denoting the last run_stop update * @is_remote_wakeup_enabled: remote wakeup status from host perspective + * @wait_linkstate: waitqueue for waiting LINK to move into required state + * @remote_wakeup_work: use to perform remote wakeup from this context */ struct dwc3 { struct work_struct drd_work; @@ -1397,6 +1399,8 @@ struct dwc3 { u32 gen2_tx_de_emph3; ktime_t last_run_stop; bool is_remote_wakeup_enabled; + wait_queue_head_t wait_linkstate; + struct work_struct remote_wakeup_work; }; #define INCRX_BURST_MODE 0 diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 8db92f03d551..b09e35cb206e 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -32,6 +32,7 @@ static int __dwc3_gadget_start(struct dwc3 *dwc); static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc); +static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup); /** * dwc3_gadget_set_test_mode - enables usb2 test modes @@ -1924,15 +1925,17 @@ static int dwc3_gadget_get_frame(struct usb_gadget *g) return __dwc3_gadget_get_frame(dwc); } -static int __dwc3_gadget_wakeup(struct dwc3 *dwc) +static int dwc3_gadget_remote_wakeup(struct dwc3 *dwc) { - int retries; - - int ret; + int ret = 0; u32 reg; - u8 link_state; + unsigned long flags; + bool link_recover_only = false; + dev_dbg(dwc->dev, "%s(): Entry\n", __func__); + disable_irq(dwc->irq); + spin_lock_irqsave(&dwc->lock, flags); /* * According to the Databook Remote wakeup request should * be issued only when the device is in early suspend state. @@ -1942,21 +1945,45 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc) reg = dwc3_readl(dwc->regs, DWC3_DSTS); link_state = DWC3_DSTS_USBLNKST(reg); - switch (link_state) { case DWC3_LINK_STATE_RESET: case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */ case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ case DWC3_LINK_STATE_RESUME: break; + case DWC3_LINK_STATE_U1: + if (dwc->gadget.speed != USB_SPEED_SUPER) { + link_recover_only = true; + break; + } + fallthrough; default: - return -EINVAL; + dev_dbg(dwc->dev, "can't wakeup from link state %d\n", + link_state); + ret = -EINVAL; + goto out; } + /* Enable LINK STATUS change event */ + reg = dwc3_readl(dwc->regs, DWC3_DEVTEN); + reg |= DWC3_DEVTEN_ULSTCNGEN; + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); + /* + * memory barrier is required to make sure that required events + * with core is enabled before performing RECOVERY mechnism. + */ + mb(); + ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV); if (ret < 0) { dev_err(dwc->dev, "failed to put link in Recovery\n"); - return ret; + /* Disable LINK STATUS change */ + reg = dwc3_readl(dwc->regs, DWC3_DEVTEN); + reg &= ~DWC3_DEVTEN_ULSTCNGEN; + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); + /* Required to complete this operation before returning */ + mb(); + goto out; } /* Recent versions do this automatically */ @@ -1967,30 +1994,115 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_DCTL, reg); } - /* poll until Link State changes to ON */ - retries = 20000; + spin_unlock_irqrestore(&dwc->lock, flags); + enable_irq(dwc->irq); - while (retries--) { - reg = dwc3_readl(dwc->regs, DWC3_DSTS); + /* + * Have bigger value (16 sec) for timeout since some host PCs driving + * resume for very long time (e.g. 8 sec) + */ + ret = wait_event_interruptible_timeout(dwc->wait_linkstate, + (dwc->link_state < DWC3_LINK_STATE_U3) || + (dwc->link_state == DWC3_LINK_STATE_SS_DIS), + msecs_to_jiffies(16000)); - /* in HS, means ON */ - if (DWC3_DSTS_USBLNKST(reg) == DWC3_LINK_STATE_U0) - break; + spin_lock_irqsave(&dwc->lock, flags); + /* Disable link status change event */ + reg = dwc3_readl(dwc->regs, DWC3_DEVTEN); + reg &= ~DWC3_DEVTEN_ULSTCNGEN; + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); + /* + * Complete this write before we go ahead and perform resume + * as we don't need link status change notificaiton anymore. + */ + mb(); + + if (!ret) { + dev_dbg(dwc->dev, "Timeout moving into state(%d)\n", + dwc->link_state); + ret = -EINVAL; + spin_unlock_irqrestore(&dwc->lock, flags); + goto out1; + } else { + ret = 0; + /* + * If USB is disconnected OR received RESET from host, + * don't perform resume + */ + if (dwc->link_state == DWC3_LINK_STATE_SS_DIS || + dwc->gadget.state == USB_STATE_DEFAULT) + link_recover_only = true; } - if (DWC3_DSTS_USBLNKST(reg) != DWC3_LINK_STATE_U0) { - dev_err(dwc->dev, "failed to send remote wakeup\n"); - return -EINVAL; - } + /* + * According to DWC3 databook, the controller does not + * trigger a wakeup event when remote-wakeup is used. + * Hence, after remote-wakeup sequence is complete, and + * the device is back at U0 state, it is required that + * the resume sequence is initiated by SW. + */ + if (!link_recover_only) + dwc3_gadget_wakeup_interrupt(dwc, true); - return 0; + spin_unlock_irqrestore(&dwc->lock, flags); + dev_dbg(dwc->dev, "%s: Exit\n", __func__); + return ret; + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + enable_irq(dwc->irq); + +out1: + return ret; +} + +#define DWC3_PM_RESUME_RETRIES 20 /* Max Number of retries */ +#define DWC3_PM_RESUME_DELAY 100 /* 100 msec */ +static void dwc3_gadget_remote_wakeup_work(struct work_struct *w) +{ + struct dwc3 *dwc; + int ret; + static int retry_count; + + dwc = container_of(w, struct dwc3, remote_wakeup_work); + + ret = pm_runtime_get_sync(dwc->dev); + if (ret) { + /* pm_runtime_get_sync returns -EACCES error between + * late_suspend and early_resume, wait for system resume to + * finish and queue work again + */ + dev_dbg(dwc->dev, "PM runtime get sync failed, ret %d\n", ret); + if (ret == -EACCES) { + pm_runtime_put_noidle(dwc->dev); + if (retry_count == DWC3_PM_RESUME_RETRIES) { + retry_count = 0; + dev_err(dwc->dev, "pm_runtime_get_sync timed out\n"); + return; + } + msleep(DWC3_PM_RESUME_DELAY); + retry_count++; + schedule_work(&dwc->remote_wakeup_work); + return; + } + } + retry_count = 0; + dbg_event(0xFF, "Gdgwake gsyn", + atomic_read(&dwc->dev->power.usage_count)); + + ret = dwc3_gadget_remote_wakeup(dwc); + if (ret) + dev_err(dwc->dev, "Remote wakeup failed. ret = %d\n", ret); + + pm_runtime_put_noidle(dwc->dev); + dbg_event(0xFF, "Gdgwake put", + atomic_read(&dwc->dev->power.usage_count)); } static int dwc3_gadget_wakeup(struct usb_gadget *g) { struct dwc3 *dwc = gadget_to_dwc(g); - unsigned long flags; - int ret; + unsigned long flags; spin_lock_irqsave(&dwc->lock, flags); if (!dwc->is_remote_wakeup_enabled) { @@ -1999,10 +2111,9 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g) return -EINVAL; } - ret = __dwc3_gadget_wakeup(dwc); spin_unlock_irqrestore(&dwc->lock, flags); - - return ret; + schedule_work(&dwc->remote_wakeup_work); + return 0; } static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, @@ -3286,6 +3397,7 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED); dwc->connected = false; + wake_up_interruptible(&dwc->wait_linkstate); } static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) @@ -3371,6 +3483,7 @@ static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) dwc->gadget.speed = USB_SPEED_UNKNOWN; dwc->link_state = DWC3_LINK_STATE_U0; dwc->is_remote_wakeup_enabled = false; + wake_up_interruptible(&dwc->wait_linkstate); } static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) @@ -3503,7 +3616,7 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) */ } -static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc) +static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup) { enum dwc3_link_state link_state = dwc->link_state; @@ -3511,7 +3624,7 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc) dwc->link_state = DWC3_LINK_STATE_U0; /* For L1 resume case, don't perform resume */ - if (link_state != DWC3_LINK_STATE_U3) + if (!remote_wakeup && link_state != DWC3_LINK_STATE_U3) return; /* Handle bus resume case */ @@ -3617,7 +3730,9 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, } dev_dbg(dwc->dev, "Going from (%d)--->(%d)\n", dwc->link_state, next); + dbg_log_string("link state from %d to %d", dwc->link_state, next); dwc->link_state = next; + wake_up_interruptible(&dwc->wait_linkstate); } static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc, @@ -3694,7 +3809,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, dwc->dbg_gadget_events.connect++; break; case DWC3_DEVICE_EVENT_WAKEUP: - dwc3_gadget_wakeup_interrupt(dwc); + dwc3_gadget_wakeup_interrupt(dwc, false); dwc->dbg_gadget_events.wakeup++; break; case DWC3_DEVICE_EVENT_HIBER_REQ: @@ -3982,7 +4097,7 @@ int dwc3_gadget_init(struct dwc3 *dwc) } dwc->irq_gadget = irq; - + INIT_WORK(&dwc->remote_wakeup_work, dwc3_gadget_remote_wakeup_work); dwc->ep0_trb = dma_alloc_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2, &dwc->ep0_trb_addr, GFP_KERNEL);