Skip to content

Commit

Permalink
usb: dwc2: gadget: LPM flow fix
Browse files Browse the repository at this point in the history
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: 273d576 ("usb: dwc2: gadget: Add functionality to exit from LPM L1 state")
Fixes: 88b02f2 ("usb: dwc2: Add core state checking")
CC: stable@vger.kernel.org
Signed-off-by: Minas Harutyunyan <Minas.Harutyunyan@synopsys.com>
Link: https://lore.kernel.org/r/b4d9de5382375dddbf7ef6049d9a82066ad87d5d.1710166393.git.Minas.Harutyunyan@synopsys.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
  • Loading branch information
Minas Harutyunyan authored and Greg Kroah-Hartman committed Mar 26, 2024
1 parent 31f42da commit 5d69a3b
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 21 deletions.
1 change: 1 addition & 0 deletions drivers/usb/dwc2/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,7 @@ int dwc2_backup_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_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg, bool remotewakeup);

/* This function should be called on every hardware interrupt. */
irqreturn_t dwc2_handle_common_intr(int irq, void *dev);
Expand Down
63 changes: 42 additions & 21 deletions drivers/usb/dwc2/core_intr.c
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,11 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg)
* @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 i = 0;
u32 pcgctl;
u32 dctl;

if (hsotg->lx_state != DWC2_L1) {
dev_err(hsotg->dev, "Core isn't in DWC2_L1 state\n");
Expand All @@ -335,37 +336,57 @@ static void dwc2_wakeup_from_lpm_l1(struct dwc2_hsotg *hsotg)

glpmcfg = dwc2_readl(hsotg, GLPMCFG);
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_HIRD_THRES_EN;
glpmcfg &= ~GLPMCFG_HIRD_THRES_MASK;
dwc2_writel(hsotg, glpmcfg, GLPMCFG);

do {
glpmcfg = dwc2_readl(hsotg, GLPMCFG);
pcgctl = dwc2_readl(hsotg, PCGCTL);
pcgctl &= ~PCGCTL_ENBL_SLEEP_GATING;
dwc2_writel(hsotg, pcgctl, PCGCTL);

if (!(glpmcfg & (GLPMCFG_COREL1RES_MASK |
GLPMCFG_L1RESUMEOK | GLPMCFG_SLPSTS)))
break;
glpmcfg = dwc2_readl(hsotg, GLPMCFG);
if (glpmcfg & GLPMCFG_ENBESL) {
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);
} while (++i < 200);
if (dwc2_hsotg_wait_bit_set(hsotg, GINTSTS, GINTSTS_WKUPINT, 1000)) {
dev_warn(hsotg->dev, "%s: timeout GINTSTS_WKUPINT\n", __func__);
goto fail;
return;
}
}

if (i == 200) {
dev_err(hsotg->dev, "Failed to exit L1 sleep state in 200us.\n");
glpmcfg = dwc2_readl(hsotg, GLPMCFG);
if (glpmcfg & GLPMCFG_COREL1RES_MASK || glpmcfg & GLPMCFG_SLPSTS ||
glpmcfg & GLPMCFG_L1RESUMEOK) {
goto fail;
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 {
/* TODO */
dev_err(hsotg->dev, "Host side LPM is not supported.\n");
return;
}

/* Change to L0 state */
hsotg->lx_state = DWC2_L0;

/* Inform gadget to exit from L1 */
call_gadget(hsotg, resume);
}

/*
Expand All @@ -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);

if (hsotg->lx_state == DWC2_L1) {
dwc2_wakeup_from_lpm_l1(hsotg);
dwc2_wakeup_from_lpm_l1(hsotg, false);
return;
}

Expand Down
4 changes: 4 additions & 0 deletions drivers/usb/dwc2/gadget.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
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 */
if (hs->lx_state != DWC2_L0) {
dev_dbg(hs->dev, "%s: submit request only in active state\n",
Expand Down

0 comments on commit 5d69a3b

Please sign in to comment.