Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c5c403dc authored by Vardan Mikayelyan's avatar Vardan Mikayelyan Committed by Felipe Balbi
Browse files

usb: dwc2: Add host/device hibernation functions



Add host/device hibernation functions which must be wrapped
by core's  dwc2_enter_hibernation()/dwc2_exit_hibernation()
functions.

Make dwc2_backup_global_registers dwc2_restore_global_register
non-static to use them in both host/gadget sides.

Added function names:
dwc2_gadget_enter_hibernation()
dwc2_gadget_exit_hibernation()
dwc2_host_enter_hibernation()
dwc2_host_exit_hibernation()

Signed-off-by: default avatarVardan Mikayelyan <mvardan@synopsys.com>
Signed-off-by: default avatarJohn Youn <johnyoun@synopsys.com>
Signed-off-by: default avatarArtur Petrosyan <arturp@synopsys.com>
Signed-off-by: default avatarMinas Harutyunyan <hminas@synopsys.com>
Signed-off-by: default avatarGrigor Tovmasyan <tovmasya@synopsys.com>
Signed-off-by: default avatarFelipe Balbi <felipe.balbi@linux.intel.com>
parent 94d2666c
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -64,7 +64,7 @@
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg)
int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_gregs_backup *gr;

@@ -96,7 +96,7 @@ static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg)
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
static int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg)
int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg)
{
	struct dwc2_gregs_backup *gr;

+18 −0
Original line number Diff line number Diff line
@@ -1155,6 +1155,8 @@ void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd);

void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup,
			     int is_host);
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);

@@ -1224,6 +1226,9 @@ int dwc2_hsotg_set_test_mode(struct dwc2_hsotg *hsotg, int testmode);
#define dwc2_is_device_connected(hsotg) (hsotg->connected)
int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg);
int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup);
int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg);
int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
				 int rem_wakeup, int reset);
int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg);
int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg);
int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg);
@@ -1250,6 +1255,11 @@ static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg)
static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg,
						int remote_wakeup)
{ return 0; }
static inline int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
					       int rem_wakeup, int reset)
{ return 0; }
static inline int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg)
@@ -1267,6 +1277,9 @@ void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force);
void dwc2_hcd_start(struct dwc2_hsotg *hsotg);
int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg);
int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg);
int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg);
int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg,
			       int rem_wakeup, int reset);
#else
static inline int dwc2_hcd_get_frame_number(struct dwc2_hsotg *hsotg)
{ return 0; }
@@ -1283,6 +1296,11 @@ static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg)
{ return 0; }
static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg,
					     int rem_wakeup, int reset)
{ return 0; }

#endif

+176 −0
Original line number Diff line number Diff line
@@ -4913,3 +4913,179 @@ void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg)
	dev_dbg(hsotg->dev, "GLPMCFG=0x%08x\n", dwc2_readl(hsotg->regs
		+ GLPMCFG));
}

/**
 * dwc2_gadget_enter_hibernation() - Put controller in Hibernation.
 *
 * @hsotg: Programming view of the DWC_otg controller
 *
 * Return non-zero if failed to enter to hibernation.
 */
int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg)
{
	u32 gpwrdn;
	int ret = 0;

	/* Change to L2(suspend) state */
	hsotg->lx_state = DWC2_L2;
	dev_dbg(hsotg->dev, "Start of hibernation completed\n");
	ret = dwc2_backup_global_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to backup global registers\n",
			__func__);
		return ret;
	}
	ret = dwc2_backup_device_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to backup device registers\n",
			__func__);
		return ret;
	}

	gpwrdn = GPWRDN_PWRDNRSTN;
	gpwrdn |= GPWRDN_PMUACTV;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Set flag to indicate that we are in hibernation */
	hsotg->hibernated = 1;

	/* Enable interrupts from wake up logic */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_PMUINTSEL;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Unmask device mode interrupts in GPWRDN */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_RST_DET_MSK;
	gpwrdn |= GPWRDN_LNSTSCHG_MSK;
	gpwrdn |= GPWRDN_STS_CHGINT_MSK;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Enable Power Down Clamp */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_PWRDNCLMP;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Switch off VDD */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_PWRDNSWTCH;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Save gpwrdn register for further usage if stschng interrupt */
	hsotg->gr_backup.gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	dev_dbg(hsotg->dev, "Hibernation completed\n");

	return ret;
}

/**
 * dwc2_gadget_exit_hibernation()
 * This function is for exiting from Device mode hibernation by host initiated
 * resume/reset and device initiated remote-wakeup.
 *
 * @hsotg: Programming view of the DWC_otg controller
 * @rem_wakeup: indicates whether resume is initiated by Device or Host.
 * @param reset: indicates whether resume is initiated by Reset.
 *
 * Return non-zero if failed to exit from hibernation.
 */
int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,
				 int rem_wakeup, int reset)
{
	u32 pcgcctl;
	u32 gpwrdn;
	u32 dctl;
	int ret = 0;
	struct dwc2_gregs_backup *gr;
	struct dwc2_dregs_backup *dr;

	gr = &hsotg->gr_backup;
	dr = &hsotg->dr_backup;

	if (!hsotg->hibernated) {
		dev_dbg(hsotg->dev, "Already exited from Hibernation\n");
		return 1;
	}
	dev_dbg(hsotg->dev,
		"%s: called with rem_wakeup = %d reset = %d\n",
		__func__, rem_wakeup, reset);

	dwc2_hib_restore_common(hsotg, rem_wakeup, 0);

	if (!reset) {
		/* Clear all pending interupts */
		dwc2_writel(0xffffffff, hsotg->regs + GINTSTS);
	}

	/* De-assert Restore */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn &= ~GPWRDN_RESTORE;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	if (!rem_wakeup) {
		pcgcctl = dwc2_readl(hsotg->regs + PCGCTL);
		pcgcctl &= ~PCGCTL_RSTPDWNMODULE;
		dwc2_writel(pcgcctl, hsotg->regs + PCGCTL);
	}

	/* Restore GUSBCFG, DCFG and DCTL */
	dwc2_writel(gr->gusbcfg, hsotg->regs + GUSBCFG);
	dwc2_writel(dr->dcfg, hsotg->regs + DCFG);
	dwc2_writel(dr->dctl, hsotg->regs + DCTL);

	/* De-assert Wakeup Logic */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn &= ~GPWRDN_PMUACTV;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);

	if (rem_wakeup) {
		udelay(10);
		/* Start Remote Wakeup Signaling */
		dwc2_writel(dr->dctl | DCTL_RMTWKUPSIG, hsotg->regs + DCTL);
	} else {
		udelay(50);
		/* Set Device programming done bit */
		dctl = dwc2_readl(hsotg->regs + DCTL);
		dctl |= DCTL_PWRONPRGDONE;
		dwc2_writel(dctl, hsotg->regs + DCTL);
	}
	/* Wait for interrupts which must be cleared */
	mdelay(2);
	/* Clear all pending interupts */
	dwc2_writel(0xffffffff, hsotg->regs + GINTSTS);

	/* Restore global registers */
	ret = dwc2_restore_global_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to restore registers\n",
			__func__);
		return ret;
	}

	/* Restore device registers */
	ret = dwc2_restore_device_registers(hsotg, rem_wakeup);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to restore device registers\n",
			__func__);
		return ret;
	}

	if (rem_wakeup) {
		mdelay(10);
		dctl = dwc2_readl(hsotg->regs + DCTL);
		dctl &= ~DCTL_RMTWKUPSIG;
		dwc2_writel(dctl, hsotg->regs + DCTL);
	}

	hsotg->hibernated = 0;
	hsotg->lx_state = DWC2_L0;
	dev_dbg(hsotg->dev, "Hibernation recovery completes here\n");

	return ret;
}
+223 −0
Original line number Diff line number Diff line
@@ -5360,3 +5360,226 @@ int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg)

	return 0;
}

/**
 * dwc2_host_enter_hibernation() - Put controller in Hibernation.
 *
 * @hsotg: Programming view of the DWC_otg controller
 */
int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg)
{
	unsigned long flags;
	int ret = 0;
	u32 hprt0;
	u32 pcgcctl;
	u32 gusbcfg;
	u32 gpwrdn;

	dev_dbg(hsotg->dev, "Preparing host for hibernation\n");
	ret = dwc2_backup_global_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to backup global registers\n",
			__func__);
		return ret;
	}
	ret = dwc2_backup_host_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to backup host registers\n",
			__func__);
		return ret;
	}

	/* Enter USB Suspend Mode */
	hprt0 = dwc2_readl(hsotg->regs + HPRT0);
	hprt0 |= HPRT0_SUSP;
	hprt0 &= ~HPRT0_ENA;
	dwc2_writel(hprt0, hsotg->regs + HPRT0);

	/* Wait for the HPRT0.PrtSusp register field to be set */
	if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 300))
		dev_warn(hsotg->dev, "Suspend wasn't genereted\n");

	/*
	 * We need to disable interrupts to prevent servicing of any IRQ
	 * during going to hibernation
	 */
	spin_lock_irqsave(&hsotg->lock, flags);
	hsotg->lx_state = DWC2_L2;

	gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG);
	if (gusbcfg & GUSBCFG_ULPI_UTMI_SEL) {
		/* ULPI interface */
		/* Suspend the Phy Clock */
		pcgcctl = dwc2_readl(hsotg->regs + PCGCTL);
		pcgcctl |= PCGCTL_STOPPCLK;
		dwc2_writel(pcgcctl, hsotg->regs + PCGCTL);
		udelay(10);

		gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
		gpwrdn |= GPWRDN_PMUACTV;
		dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
		udelay(10);
	} else {
		/* UTMI+ Interface */
		gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
		gpwrdn |= GPWRDN_PMUACTV;
		dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
		udelay(10);

		pcgcctl = dwc2_readl(hsotg->regs + PCGCTL);
		pcgcctl |= PCGCTL_STOPPCLK;
		dwc2_writel(pcgcctl, hsotg->regs + PCGCTL);
		udelay(10);
	}

	/* Enable interrupts from wake up logic */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_PMUINTSEL;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Unmask host mode interrupts in GPWRDN */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_DISCONN_DET_MSK;
	gpwrdn |= GPWRDN_LNSTSCHG_MSK;
	gpwrdn |= GPWRDN_STS_CHGINT_MSK;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Enable Power Down Clamp */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_PWRDNCLMP;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Switch off VDD */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn |= GPWRDN_PWRDNSWTCH;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);

	hsotg->hibernated = 1;
	hsotg->bus_suspended = 1;
	dev_dbg(hsotg->dev, "Host hibernation completed\n");
	spin_unlock_irqrestore(&hsotg->lock, flags);
	return ret;
}

/*
 * dwc2_host_exit_hibernation()
 *
 * @hsotg: Programming view of the DWC_otg controller
 * @rem_wakeup: indicates whether resume is initiated by Device or Host.
 * @param reset: indicates whether resume is initiated by Reset.
 *
 * Return: non-zero if failed to enter to hibernation.
 *
 * This function is for exiting from Host mode hibernation by
 * Host Initiated Resume/Reset and Device Initiated Remote-Wakeup.
 */
int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,
			       int reset)
{
	u32 gpwrdn;
	u32 hprt0;
	int ret = 0;
	struct dwc2_gregs_backup *gr;
	struct dwc2_hregs_backup *hr;

	gr = &hsotg->gr_backup;
	hr = &hsotg->hr_backup;

	dev_dbg(hsotg->dev,
		"%s: called with rem_wakeup = %d reset = %d\n",
		__func__, rem_wakeup, reset);

	dwc2_hib_restore_common(hsotg, rem_wakeup, 1);
	hsotg->hibernated = 0;

	/*
	 * This step is not described in functional spec but if not wait for
	 * this delay, mismatch interrupts occurred because just after restore
	 * core is in Device mode(gintsts.curmode == 0)
	 */
	mdelay(100);

	/* Clear all pending interupts */
	dwc2_writel(0xffffffff, hsotg->regs + GINTSTS);

	/* De-assert Restore */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn &= ~GPWRDN_RESTORE;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	/* Restore GUSBCFG, HCFG */
	dwc2_writel(gr->gusbcfg, hsotg->regs + GUSBCFG);
	dwc2_writel(hr->hcfg, hsotg->regs + HCFG);

	/* De-assert Wakeup Logic */
	gpwrdn = dwc2_readl(hsotg->regs + GPWRDN);
	gpwrdn &= ~GPWRDN_PMUACTV;
	dwc2_writel(gpwrdn, hsotg->regs + GPWRDN);
	udelay(10);

	hprt0 = hr->hprt0;
	hprt0 |= HPRT0_PWR;
	hprt0 &= ~HPRT0_ENA;
	hprt0 &= ~HPRT0_SUSP;
	dwc2_writel(hprt0, hsotg->regs + HPRT0);

	hprt0 = hr->hprt0;
	hprt0 |= HPRT0_PWR;
	hprt0 &= ~HPRT0_ENA;
	hprt0 &= ~HPRT0_SUSP;

	if (reset) {
		hprt0 |= HPRT0_RST;
		dwc2_writel(hprt0, hsotg->regs + HPRT0);

		/* Wait for Resume time and then program HPRT again */
		mdelay(60);
		hprt0 &= ~HPRT0_RST;
		dwc2_writel(hprt0, hsotg->regs + HPRT0);
	} else {
		hprt0 |= HPRT0_RES;
		dwc2_writel(hprt0, hsotg->regs + HPRT0);

		/* Wait for Resume time and then program HPRT again */
		mdelay(100);
		hprt0 &= ~HPRT0_RES;
		dwc2_writel(hprt0, hsotg->regs + HPRT0);
	}
	/* Clear all interrupt status */
	hprt0 = dwc2_readl(hsotg->regs + HPRT0);
	hprt0 |= HPRT0_CONNDET;
	hprt0 |= HPRT0_ENACHG;
	hprt0 &= ~HPRT0_ENA;
	dwc2_writel(hprt0, hsotg->regs + HPRT0);

	hprt0 = dwc2_readl(hsotg->regs + HPRT0);

	/* Clear all pending interupts */
	dwc2_writel(0xffffffff, hsotg->regs + GINTSTS);

	/* Restore global registers */
	ret = dwc2_restore_global_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to restore registers\n",
			__func__);
		return ret;
	}

	/* Restore host registers */
	ret = dwc2_restore_host_registers(hsotg);
	if (ret) {
		dev_err(hsotg->dev, "%s: failed to restore host registers\n",
			__func__);
		return ret;
	}

	hsotg->hibernated = 0;
	hsotg->bus_suspended = 0;
	hsotg->lx_state = DWC2_L0;
	dev_dbg(hsotg->dev, "Host hibernation restore complete\n");
	return ret;
}