Skip to content

Commit

Permalink
usb: dwc2: add bus suspend/resume for dwc2
Browse files Browse the repository at this point in the history
Hcd controller needs bus_suspend/resume, dwc2 controller make
root hub generate suspend/resume signal with hprt0 register
when work in host mode.
After the root hub enter suspend, we can make controller enter
low power state with PCGCTL register.

We also update the lx_state for hsotg state.

This patch has tested on rk3288 with suspend/resume.

Signed-off-by: Kever Yang <kever.yang@rock-chips.com>
Acked-by: Paul Zimmerman <paulz@synopsys.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
  • Loading branch information
Kever Yang authored and Felipe Balbi committed Nov 12, 2014
1 parent d3cf6a4 commit 0cf884e
Showing 1 changed file with 77 additions and 11 deletions.
88 changes: 77 additions & 11 deletions drivers/usb/dwc2/hcd.c
Original file line number Diff line number Diff line change
Expand Up @@ -1471,6 +1471,30 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)
}
}

static void dwc2_port_resume(struct dwc2_hsotg *hsotg)
{
u32 hprt0;

/* After clear the Stop PHY clock bit, we should wait for a moment
* for PLL work stable with clock output.
*/
writel(0, hsotg->regs + PCGCTL);
usleep_range(2000, 4000);

hprt0 = dwc2_read_hprt0(hsotg);
hprt0 |= HPRT0_RES;
writel(hprt0, hsotg->regs + HPRT0);
hprt0 &= ~HPRT0_SUSP;
/* according to USB2.0 Spec 7.1.7.7, the host must send the resume
* signal for at least 20ms
*/
usleep_range(20000, 25000);

hprt0 &= ~HPRT0_RES;
writel(hprt0, hsotg->regs + HPRT0);
hsotg->lx_state = DWC2_L0;
}

/* Handles hub class-specific requests */
static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,
u16 wvalue, u16 windex, char *buf, u16 wlength)
Expand Down Expand Up @@ -1516,17 +1540,7 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,
case USB_PORT_FEAT_SUSPEND:
dev_dbg(hsotg->dev,
"ClearPortFeature USB_PORT_FEAT_SUSPEND\n");
writel(0, hsotg->regs + PCGCTL);
usleep_range(20000, 40000);

hprt0 = dwc2_read_hprt0(hsotg);
hprt0 |= HPRT0_RES;
writel(hprt0, hsotg->regs + HPRT0);
hprt0 &= ~HPRT0_SUSP;
usleep_range(100000, 150000);

hprt0 &= ~HPRT0_RES;
writel(hprt0, hsotg->regs + HPRT0);
dwc2_port_resume(hsotg);
break;

case USB_PORT_FEAT_POWER:
Expand Down Expand Up @@ -2299,6 +2313,55 @@ static void _dwc2_hcd_stop(struct usb_hcd *hcd)
usleep_range(1000, 3000);
}

static int _dwc2_hcd_suspend(struct usb_hcd *hcd)
{
struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
u32 hprt0;

if (!((hsotg->op_state == OTG_STATE_B_HOST) ||
(hsotg->op_state == OTG_STATE_A_HOST)))
return 0;

/* TODO: We get into suspend from 'on' state, maybe we need to do
* something if we get here from DWC2_L1(LPM sleep) state one day.
*/
if (hsotg->lx_state != DWC2_L0)
return 0;

hprt0 = dwc2_read_hprt0(hsotg);
if (hprt0 & HPRT0_CONNSTS) {
dwc2_port_suspend(hsotg, 1);
} else {
u32 pcgctl = readl(hsotg->regs + PCGCTL);

pcgctl |= PCGCTL_STOPPCLK;
writel(pcgctl, hsotg->regs + PCGCTL);
}

return 0;
}

static int _dwc2_hcd_resume(struct usb_hcd *hcd)
{
struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);
u32 hprt0;

if (!((hsotg->op_state == OTG_STATE_B_HOST) ||
(hsotg->op_state == OTG_STATE_A_HOST)))
return 0;

if (hsotg->lx_state != DWC2_L2)
return 0;

hprt0 = dwc2_read_hprt0(hsotg);
if ((hprt0 & HPRT0_CONNSTS) && (hprt0 & HPRT0_SUSP))
dwc2_port_resume(hsotg);
else
writel(0, hsotg->regs + PCGCTL);

return 0;
}

/* Returns the current frame number */
static int _dwc2_hcd_get_frame_number(struct usb_hcd *hcd)
{
Expand Down Expand Up @@ -2669,6 +2732,9 @@ static struct hc_driver dwc2_hc_driver = {
.hub_status_data = _dwc2_hcd_hub_status_data,
.hub_control = _dwc2_hcd_hub_control,
.clear_tt_buffer_complete = _dwc2_hcd_clear_tt_buffer_complete,

.bus_suspend = _dwc2_hcd_suspend,
.bus_resume = _dwc2_hcd_resume,
};

/*
Expand Down

0 comments on commit 0cf884e

Please sign in to comment.