dwc3: gadget: Improve remote wakeup functionality

Current available remote wakeup functionality is not robust
enough as most of time it failed to wait for host to complete
resuming signle and move USB device to U0 state. Hence enhance
this functionality by using link state change interrupt and
waiting for max 16 sec for host to resume.

Change-Id: I375d7812336ce0ecc49bfeea859bc76d90a4e0b9
Signed-off-by: Mayank Rana <mrana@codeaurora.org>
This commit is contained in:
Mayank Rana 2020-06-10 18:01:52 -07:00
commit bf131dca49
3 changed files with 148 additions and 28 deletions

View file

@ -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);

View file

@ -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

View file

@ -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);