Commit 5d69a3b5 authored by Minas Harutyunyan's avatar Minas Harutyunyan Committed by Greg Kroah-Hartman

usb: dwc2: gadget: LPM flow fix

Added functionality to exit from L1 state by device initiation
using remote wakeup signaling, in case when function driver queuing
request while core in L1 state.

Fixes: 273d576c ("usb: dwc2: gadget: Add functionality to exit from LPM L1 state")
Fixes: 88b02f2c ("usb: dwc2: Add core state checking")
CC: stable@vger.kernel.org
Signed-off-by: default avatarMinas Harutyunyan <Minas.Harutyunyan@synopsys.com>
Link: https://lore.kernel.org/r/b4d9de5382375dddbf7ef6049d9a82066ad87d5d.1710166393.git.Minas.Harutyunyan@synopsys.comSigned-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 31f42da3
...@@ -1336,6 +1336,7 @@ int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg); ...@@ -1336,6 +1336,7 @@ int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg);
int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg); int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg);
void dwc2_enable_acg(struct dwc2_hsotg *hsotg); void dwc2_enable_acg(struct dwc2_hsotg *hsotg);
void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup);
/* This function should be called on every hardware interrupt. */ /* This function should be called on every hardware interrupt. */
irqreturn_t dwc2_handle_common_intr(int irq, void *dev); irqreturn_t dwc2_handle_common_intr(int irq, void *dev);
......
...@@ -323,10 +323,11 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) ...@@ -323,10 +323,11 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg)
* @hsotg: Programming view of DWC_otg controller * @hsotg: Programming view of DWC_otg controller
* *
*/ */
static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg) void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup)
{ {
u32 glpmcfg; u32 glpmcfg;
u32 i = 0; u32 pcgctl;
u32 dctl;
if (hsotg->lx_state != DWC2_L1) { if (hsotg->lx_state != DWC2_L1) {
dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n"); dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n");
...@@ -335,37 +336,57 @@ static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg) ...@@ -335,37 +336,57 @@ static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg)
glpmcfg = dwc2_readl(hsotg, GLPMCFG); glpmcfg = dwc2_readl(hsotg, GLPMCFG);
if (dwc2_is_device_mode(hsotg)) { if (dwc2_is_device_mode(hsotg)) {
dev_dbg(hsotg->dev, "Exit from L1 state\n"); dev_dbg(hsotg->dev, "Exit from L1 state, remotewakeup=%d\n", remotewakeup);
glpmcfg &= ~GLPMCFG_ENBLSLPM; glpmcfg &= ~GLPMCFG_ENBLSLPM;
glpmcfg &= ~GLPMCFG_HIRD_THRES_EN; glpmcfg &= ~GLPMCFG_HIRD_THRES_MASK;
dwc2_writel(hsotg, glpmcfg, GLPMCFG); dwc2_writel(hsotg, glpmcfg, GLPMCFG);
do { pcgctl = dwc2_readl(hsotg, PCGCTL);
glpmcfg = dwc2_readl(hsotg, GLPMCFG); pcgctl &= ~PCGCTL_ENBL_SLEEP_GATING;
dwc2_writel(hsotg, pcgctl, PCGCTL);
if (!(glpmcfg & (GLPMCFG_COREL1RES_MASK | glpmcfg = dwc2_readl(hsotg, GLPMCFG);
GLPMCFG_L1RESUMEOK | GLPMCFG_SLPSTS))) if (glpmcfg & GLPMCFG_ENBESL) {
break; glpmcfg |= GLPMCFG_RSTRSLPSTS;
dwc2_writel(hsotg, glpmcfg, GLPMCFG);
}
if (remotewakeup) {
if (dwc2_hsotg_wait_bit_set(hsotg, GLPMCFG, GLPMCFG_L1RESUMEOK, 1000)) {
dev_warn(hsotg->dev, "%s: timeout GLPMCFG_L1RESUMEOK\n", __func__);
goto fail;
return;
}
dctl = dwc2_readl(hsotg, DCTL);
dctl |= DCTL_RMTWKUPSIG;
dwc2_writel(hsotg, dctl, DCTL);
udelay(1); if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_WKUPINT, 1000)) {
} while (++i < 200); dev_warn(hsotg->dev, "%s: timeout GINTSTS_WKUPINT\n", __func__);
goto fail;
return;
}
}
if (i == 200) { glpmcfg = dwc2_readl(hsotg, GLPMCFG);
dev_err(hsotg->dev, "Failed to exit L1 sleep state in 200us.\n"); if (glpmcfg & GLPMCFG_COREL1RES_MASK || glpmcfg & GLPMCFG_SLPSTS ||
glpmcfg & GLPMCFG_L1RESUMEOK) {
goto fail;
return; return;
} }
dwc2_gadget_init_lpm(hsotg);
/* Inform gadget to exit from L1 */
call_gadget(hsotg, resume);
/* Change to L0 state */
hsotg->lx_state = DWC2_L0;
hsotg->bus_suspended = false;
fail: dwc2_gadget_init_lpm(hsotg);
} else { } else {
/* TODO */ /* TODO */
dev_err(hsotg->dev, "Host side LPM is not supported.\n"); dev_err(hsotg->dev, "Host side LPM is not supported.\n");
return; return;
} }
/* Change to L0 state */
hsotg->lx_state = DWC2_L0;
/* Inform gadget to exit from L1 */
call_gadget(hsotg, resume);
} }
/* /*
...@@ -386,7 +407,7 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) ...@@ -386,7 +407,7 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg)
dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state); dev_dbg(hsotg->dev, "%s lxstate = %d\n", __func__, hsotg->lx_state);
if (hsotg->lx_state == DWC2_L1) { if (hsotg->lx_state == DWC2_L1) {
dwc2_wakeup_from_lpm_l1(hsotg); dwc2_wakeup_from_lpm_l1(hsotg, false);
return; return;
} }
......
...@@ -1415,6 +1415,10 @@ static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req, ...@@ -1415,6 +1415,10 @@ static int dwc2_hsotg_ep_queue(struct usb_ep *ep, struct usb_request *req,
ep->name, req, req->length, req->buf, req->no_interrupt, ep->name, req, req->length, req->buf, req->no_interrupt,
req->zero, req->short_not_ok); req->zero, req->short_not_ok);
if (hs->lx_state == DWC2_L1) {
dwc2_wakeup_from_lpm_l1(hs, true);
}
/* Prevent new request submission when controller is suspended */ /* Prevent new request submission when controller is suspended */
if (hs->lx_state != DWC2_L0) { if (hs->lx_state != DWC2_L0) {
dev_dbg(hs->dev, "%s: submit request only in active state\n", dev_dbg(hs->dev, "%s: submit request only in active state\n",
......
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