diff options
Diffstat (limited to 'drivers/usb/dwc2/hcd.c')
| -rw-r--r-- | drivers/usb/dwc2/hcd.c | 646 | 
1 files changed, 508 insertions, 138 deletions
| diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index fc3269f5faf1..035d4911a3c3 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -56,8 +56,6 @@  #include "core.h"  #include "hcd.h" -static void dwc2_port_resume(struct dwc2_hsotg *hsotg); -  /*   * =========================================================================   *  Host Core Layer Functions @@ -3208,6 +3206,15 @@ static void dwc2_conn_id_status_change(struct work_struct *work)  		if (count > 250)  			dev_err(hsotg->dev,  				"Connection id status change timed out\n"); + +		/* +		 * Exit Partial Power Down without restoring registers. +		 * No need to check the return value as registers +		 * are not being restored. +		 */ +		if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) +			dwc2_exit_partial_power_down(hsotg, 0, false); +  		hsotg->op_state = OTG_STATE_B_PERIPHERAL;  		dwc2_core_init(hsotg, false);  		dwc2_enable_global_interrupts(hsotg); @@ -3277,13 +3284,23 @@ static int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg)  	return hcd->self.b_hnp_enable;  } -/* Must NOT be called with interrupt disabled or spinlock held */ -static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +/** + * dwc2_port_suspend() - Put controller in suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * @windex: The control request wIndex field + * + * Return: non-zero if failed to enter suspend mode for host. + * + * This function is for entering Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)  {  	unsigned long flags; -	u32 hprt0;  	u32 pcgctl;  	u32 gotgctl; +	int ret = 0;  	dev_dbg(hsotg->dev, "%s()\n", __func__); @@ -3296,22 +3313,33 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)  		hsotg->op_state = OTG_STATE_A_SUSPEND;  	} -	hprt0 = dwc2_read_hprt0(hsotg); -	hprt0 |= HPRT0_SUSP; -	dwc2_writel(hsotg, hprt0, HPRT0); - -	hsotg->bus_suspended = true; - -	/* -	 * If power_down is supported, Phy clock will be suspended -	 * after registers are backuped. -	 */ -	if (!hsotg->params.power_down) { -		/* Suspend the Phy Clock */ -		pcgctl = dwc2_readl(hsotg, PCGCTL); -		pcgctl |= PCGCTL_STOPPCLK; -		dwc2_writel(hsotg, pcgctl, PCGCTL); -		udelay(10); +	switch (hsotg->params.power_down) { +	case DWC2_POWER_DOWN_PARAM_PARTIAL: +		ret = dwc2_enter_partial_power_down(hsotg); +		if (ret) +			dev_err(hsotg->dev, +				"enter partial_power_down failed.\n"); +		break; +	case DWC2_POWER_DOWN_PARAM_HIBERNATION: +		/* +		 * Perform spin unlock and lock because in +		 * "dwc2_host_enter_hibernation()" function there is a spinlock +		 * logic which prevents servicing of any IRQ during entering +		 * hibernation. +		 */ +		spin_unlock_irqrestore(&hsotg->lock, flags); +		ret = dwc2_enter_hibernation(hsotg, 1); +		if (ret) +			dev_err(hsotg->dev, "enter hibernation failed.\n"); +		spin_lock_irqsave(&hsotg->lock, flags); +		break; +	case DWC2_POWER_DOWN_PARAM_NONE: +		/* +		 * If not hibernation nor partial power down are supported, +		 * clock gating is used to save power. +		 */ +		dwc2_host_enter_clock_gating(hsotg); +		break;  	}  	/* For HNP the bus must be suspended for at least 200ms */ @@ -3326,44 +3354,54 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex)  	} else {  		spin_unlock_irqrestore(&hsotg->lock, flags);  	} + +	return ret;  } -/* Must NOT be called with interrupt disabled or spinlock held */ -static void dwc2_port_resume(struct dwc2_hsotg *hsotg) +/** + * dwc2_port_resume() - Exit controller from suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to exit suspend mode for host. + * + * This function is for exiting Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_resume(struct dwc2_hsotg *hsotg)  {  	unsigned long flags; -	u32 hprt0; -	u32 pcgctl; +	int ret = 0;  	spin_lock_irqsave(&hsotg->lock, flags); -	/* -	 * If power_down is supported, Phy clock is already resumed -	 * after registers restore. -	 */ -	if (!hsotg->params.power_down) { -		pcgctl = dwc2_readl(hsotg, PCGCTL); -		pcgctl &= ~PCGCTL_STOPPCLK; -		dwc2_writel(hsotg, pcgctl, PCGCTL); +	switch (hsotg->params.power_down) { +	case DWC2_POWER_DOWN_PARAM_PARTIAL: +		ret = dwc2_exit_partial_power_down(hsotg, 0, true); +		if (ret) +			dev_err(hsotg->dev, +				"exit partial_power_down failed.\n"); +		break; +	case DWC2_POWER_DOWN_PARAM_HIBERNATION: +		/* Exit host hibernation. */ +		ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); +		if (ret) +			dev_err(hsotg->dev, "exit hibernation failed.\n"); +		break; +	case DWC2_POWER_DOWN_PARAM_NONE: +		/* +		 * If not hibernation nor partial power down are supported, +		 * port resume is done using the clock gating programming flow. +		 */  		spin_unlock_irqrestore(&hsotg->lock, flags); -		msleep(20); +		dwc2_host_exit_clock_gating(hsotg, 0);  		spin_lock_irqsave(&hsotg->lock, flags); +		break;  	} -	hprt0 = dwc2_read_hprt0(hsotg); -	hprt0 |= HPRT0_RES; -	hprt0 &= ~HPRT0_SUSP; -	dwc2_writel(hsotg, hprt0, HPRT0);  	spin_unlock_irqrestore(&hsotg->lock, flags); -	msleep(USB_RESUME_TIMEOUT); - -	spin_lock_irqsave(&hsotg->lock, flags); -	hprt0 = dwc2_read_hprt0(hsotg); -	hprt0 &= ~(HPRT0_RES | HPRT0_SUSP); -	dwc2_writel(hsotg, hprt0, HPRT0); -	hsotg->bus_suspended = false; -	spin_unlock_irqrestore(&hsotg->lock, flags); +	return ret;  }  /* Handles hub class-specific requests */ @@ -3413,12 +3451,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,  			dev_dbg(hsotg->dev,  				"ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); -			if (hsotg->bus_suspended) { -				if (hsotg->hibernated) -					dwc2_exit_hibernation(hsotg, 0, 0, 1); -				else -					dwc2_port_resume(hsotg); -			} +			if (hsotg->bus_suspended) +				retval = dwc2_port_resume(hsotg);  			break;  		case USB_PORT_FEAT_POWER: @@ -3629,10 +3663,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,  				"SetPortFeature - USB_PORT_FEAT_SUSPEND\n");  			if (windex != hsotg->otg_port)  				goto error; -			if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_HIBERNATION) -				dwc2_enter_hibernation(hsotg, 1); -			else -				dwc2_port_suspend(hsotg, windex); +			if (!hsotg->bus_suspended) +				retval = dwc2_port_suspend(hsotg, windex);  			break;  		case USB_PORT_FEAT_POWER: @@ -3647,12 +3679,30 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq,  			break;  		case USB_PORT_FEAT_RESET: -			if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_HIBERNATION && -			    hsotg->hibernated) -				dwc2_exit_hibernation(hsotg, 0, 1, 1); -			hprt0 = dwc2_read_hprt0(hsotg);  			dev_dbg(hsotg->dev,  				"SetPortFeature - USB_PORT_FEAT_RESET\n"); + +			hprt0 = dwc2_read_hprt0(hsotg); + +			if (hsotg->hibernated) { +				retval = dwc2_exit_hibernation(hsotg, 0, 1, 1); +				if (retval) +					dev_err(hsotg->dev, +						"exit hibernation failed\n"); +			} + +			if (hsotg->in_ppd) { +				retval = dwc2_exit_partial_power_down(hsotg, 1, +								      true); +				if (retval) +					dev_err(hsotg->dev, +						"exit partial_power_down failed\n"); +			} + +			if (hsotg->params.power_down == +			    DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) +				dwc2_host_exit_clock_gating(hsotg, 0); +  			pcgctl = dwc2_readl(hsotg, PCGCTL);  			pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK);  			dwc2_writel(hsotg, pcgctl, PCGCTL); @@ -4305,8 +4355,6 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)  	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);  	unsigned long flags;  	int ret = 0; -	u32 hprt0; -	u32 pcgctl;  	spin_lock_irqsave(&hsotg->lock, flags); @@ -4322,46 +4370,51 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd)  	if (hsotg->op_state == OTG_STATE_B_PERIPHERAL)  		goto unlock; -	if (hsotg->params.power_down > DWC2_POWER_DOWN_PARAM_PARTIAL) +	if (hsotg->bus_suspended)  		goto skip_power_saving; -	/* -	 * Drive USB suspend and disable port Power -	 * if usb bus is not suspended. -	 */ -	if (!hsotg->bus_suspended) { -		hprt0 = dwc2_read_hprt0(hsotg); -		if (hprt0 & HPRT0_CONNSTS) { -			hprt0 |= HPRT0_SUSP; -			if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) -				hprt0 &= ~HPRT0_PWR; -			dwc2_writel(hsotg, hprt0, HPRT0); -		} -		if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { -			spin_unlock_irqrestore(&hsotg->lock, flags); -			dwc2_vbus_supply_exit(hsotg); -			spin_lock_irqsave(&hsotg->lock, flags); -		} else { -			pcgctl = readl(hsotg->regs + PCGCTL); -			pcgctl |= PCGCTL_STOPPCLK; -			writel(pcgctl, hsotg->regs + PCGCTL); -		} -	} +	if (hsotg->flags.b.port_connect_status == 0) +		goto skip_power_saving; -	if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { +	switch (hsotg->params.power_down) { +	case DWC2_POWER_DOWN_PARAM_PARTIAL:  		/* Enter partial_power_down */  		ret = dwc2_enter_partial_power_down(hsotg); -		if (ret) { -			if (ret != -ENOTSUPP) -				dev_err(hsotg->dev, -					"enter partial_power_down failed\n"); -			goto skip_power_saving; -		} +		if (ret) +			dev_err(hsotg->dev, +				"enter partial_power_down failed\n"); +		/* After entering suspend, hardware is not accessible */ +		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +		break; +	case DWC2_POWER_DOWN_PARAM_HIBERNATION: +		/* Enter hibernation */ +		spin_unlock_irqrestore(&hsotg->lock, flags); +		ret = dwc2_enter_hibernation(hsotg, 1); +		if (ret) +			dev_err(hsotg->dev, "enter hibernation failed\n"); +		spin_lock_irqsave(&hsotg->lock, flags); + +		/* After entering suspend, hardware is not accessible */ +		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +		break; +	case DWC2_POWER_DOWN_PARAM_NONE: +		/* +		 * If not hibernation nor partial power down are supported, +		 * clock gating is used to save power. +		 */ +		dwc2_host_enter_clock_gating(hsotg); -		/* After entering partial_power_down, hardware is no more accessible */ +		/* After entering suspend, hardware is not accessible */  		clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +		break; +	default: +		goto skip_power_saving;  	} +	spin_unlock_irqrestore(&hsotg->lock, flags); +	dwc2_vbus_supply_exit(hsotg); +	spin_lock_irqsave(&hsotg->lock, flags); +  	/* Ask phy to be suspended */  	if (!IS_ERR_OR_NULL(hsotg->uphy)) {  		spin_unlock_irqrestore(&hsotg->lock, flags); @@ -4381,7 +4434,7 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)  {  	struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd);  	unsigned long flags; -	u32 pcgctl; +	u32 hprt0;  	int ret = 0;  	spin_lock_irqsave(&hsotg->lock, flags); @@ -4392,68 +4445,102 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd)  	if (hsotg->lx_state != DWC2_L2)  		goto unlock; -	if (hsotg->params.power_down > DWC2_POWER_DOWN_PARAM_PARTIAL) { +	hprt0 = dwc2_read_hprt0(hsotg); + +	/* +	 * Added port connection status checking which prevents exiting from +	 * Partial Power Down mode from _dwc2_hcd_resume() if not in Partial +	 * Power Down mode. +	 */ +	if (hprt0 & HPRT0_CONNSTS) {  		hsotg->lx_state = DWC2_L0;  		goto unlock;  	} -	/* -	 * Enable power if not already done. -	 * This must not be spinlocked since duration -	 * of this call is unknown. -	 */ -	if (!IS_ERR_OR_NULL(hsotg->uphy)) { +	switch (hsotg->params.power_down) { +	case DWC2_POWER_DOWN_PARAM_PARTIAL: +		ret = dwc2_exit_partial_power_down(hsotg, 0, true); +		if (ret) +			dev_err(hsotg->dev, +				"exit partial_power_down failed\n"); +		/* +		 * Set HW accessible bit before powering on the controller +		 * since an interrupt may rise. +		 */ +		set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +		break; +	case DWC2_POWER_DOWN_PARAM_HIBERNATION: +		ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); +		if (ret) +			dev_err(hsotg->dev, "exit hibernation failed.\n"); + +		/* +		 * Set HW accessible bit before powering on the controller +		 * since an interrupt may rise. +		 */ +		set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +		break; +	case DWC2_POWER_DOWN_PARAM_NONE: +		/* +		 * If not hibernation nor partial power down are supported, +		 * port resume is done using the clock gating programming flow. +		 */  		spin_unlock_irqrestore(&hsotg->lock, flags); -		usb_phy_set_suspend(hsotg->uphy, false); +		dwc2_host_exit_clock_gating(hsotg, 0); + +		/* +		 * Initialize the Core for Host mode, as after system resume +		 * the global interrupts are disabled. +		 */ +		dwc2_core_init(hsotg, false); +		dwc2_enable_global_interrupts(hsotg); +		dwc2_hcd_reinit(hsotg);  		spin_lock_irqsave(&hsotg->lock, flags); -	} -	if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) {  		/*  		 * Set HW accessible bit before powering on the controller  		 * since an interrupt may rise.  		 */  		set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); +		break; +	default: +		hsotg->lx_state = DWC2_L0; +		goto unlock; +	} +	/* Change Root port status, as port status change occurred after resume.*/ +	hsotg->flags.b.port_suspend_change = 1; -		/* Exit partial_power_down */ -		ret = dwc2_exit_partial_power_down(hsotg, true); -		if (ret && (ret != -ENOTSUPP)) -			dev_err(hsotg->dev, "exit partial_power_down failed\n"); -	} else { -		pcgctl = readl(hsotg->regs + PCGCTL); -		pcgctl &= ~PCGCTL_STOPPCLK; -		writel(pcgctl, hsotg->regs + PCGCTL); +	/* +	 * Enable power if not already done. +	 * This must not be spinlocked since duration +	 * of this call is unknown. +	 */ +	if (!IS_ERR_OR_NULL(hsotg->uphy)) { +		spin_unlock_irqrestore(&hsotg->lock, flags); +		usb_phy_set_suspend(hsotg->uphy, false); +		spin_lock_irqsave(&hsotg->lock, flags);  	} -	hsotg->lx_state = DWC2_L0; - +	/* Enable external vbus supply after resuming the port. */  	spin_unlock_irqrestore(&hsotg->lock, flags); +	dwc2_vbus_supply_init(hsotg); -	if (hsotg->bus_suspended) { -		spin_lock_irqsave(&hsotg->lock, flags); -		hsotg->flags.b.port_suspend_change = 1; -		spin_unlock_irqrestore(&hsotg->lock, flags); -		dwc2_port_resume(hsotg); -	} else { -		if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { -			dwc2_vbus_supply_init(hsotg); - -			/* Wait for controller to correctly update D+/D- level */ -			usleep_range(3000, 5000); -		} +	/* Wait for controller to correctly update D+/D- level */ +	usleep_range(3000, 5000); +	spin_lock_irqsave(&hsotg->lock, flags); -		/* -		 * Clear Port Enable and Port Status changes. -		 * Enable Port Power. -		 */ -		dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET | -				HPRT0_ENACHG, HPRT0); -		/* Wait for controller to detect Port Connect */ -		usleep_range(5000, 7000); -	} +	/* +	 * Clear Port Enable and Port Status changes. +	 * Enable Port Power. +	 */ +	dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET | +			HPRT0_ENACHG, HPRT0); -	return ret; +	/* Wait for controller to detect Port Connect */ +	spin_unlock_irqrestore(&hsotg->lock, flags); +	usleep_range(5000, 7000); +	spin_lock_irqsave(&hsotg->lock, flags);  unlock:  	spin_unlock_irqrestore(&hsotg->lock, flags); @@ -4564,12 +4651,41 @@ static int _dwc2_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,  	struct dwc2_qh *qh;  	bool qh_allocated = false;  	struct dwc2_qtd *qtd; +	struct dwc2_gregs_backup *gr; + +	gr = &hsotg->gr_backup;  	if (dbg_urb(urb)) {  		dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n");  		dwc2_dump_urb_info(hcd, urb, "urb_enqueue");  	} +	if (hsotg->hibernated) { +		if (gr->gotgctl & GOTGCTL_CURMODE_HOST) +			retval = dwc2_exit_hibernation(hsotg, 0, 0, 1); +		else +			retval = dwc2_exit_hibernation(hsotg, 0, 0, 0); + +		if (retval) +			dev_err(hsotg->dev, +				"exit hibernation failed.\n"); +	} + +	if (hsotg->in_ppd) { +		retval = dwc2_exit_partial_power_down(hsotg, 0, true); +		if (retval) +			dev_err(hsotg->dev, +				"exit partial_power_down failed\n"); +	} + +	if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && +	    hsotg->bus_suspended) { +		if (dwc2_is_device_mode(hsotg)) +			dwc2_gadget_exit_clock_gating(hsotg, 0); +		else +			dwc2_host_exit_clock_gating(hsotg, 0); +	} +  	if (!ep)  		return -EINVAL; @@ -5398,7 +5514,7 @@ int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg)  	dwc2_writel(hsotg, hprt0, HPRT0);  	/* Wait for the HPRT0.PrtSusp register field to be set */ -	if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000)) +	if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 5000))  		dev_warn(hsotg->dev, "Suspend wasn't generated\n");  	/* @@ -5579,7 +5695,15 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup,  		return ret;  	} -	dwc2_hcd_rem_wakeup(hsotg); +	if (rem_wakeup) { +		dwc2_hcd_rem_wakeup(hsotg); +		/* +		 * Change "port_connect_status_change" flag to re-enumerate, +		 * because after exit from hibernation port connection status +		 * is not detected. +		 */ +		hsotg->flags.b.port_connect_status_change = 1; +	}  	hsotg->hibernated = 0;  	hsotg->bus_suspended = 0; @@ -5606,3 +5730,249 @@ bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2)  	/* No reason to keep the PHY powered, so allow poweroff */  	return true;  } + +/** + * dwc2_host_enter_partial_power_down() - Put controller in partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter host partial power down. + * + * This function is for entering Host mode partial power down. + */ +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ +	u32 pcgcctl; +	u32 hprt0; +	int ret = 0; + +	dev_dbg(hsotg->dev, "Entering host partial power down started.\n"); + +	/* Put this port in suspend mode. */ +	hprt0 = dwc2_read_hprt0(hsotg); +	hprt0 |= HPRT0_SUSP; +	dwc2_writel(hsotg, hprt0, HPRT0); +	udelay(5); + +	/* Wait for the HPRT0.PrtSusp register field to be set */ +	if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000)) +		dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + +	/* Backup all registers */ +	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; +	} + +	/* +	 * Clear any pending interrupts since dwc2 will not be able to +	 * clear them after entering partial_power_down. +	 */ +	dwc2_writel(hsotg, 0xffffffff, GINTSTS); + +	/* Put the controller in low power state */ +	pcgcctl = dwc2_readl(hsotg, PCGCTL); + +	pcgcctl |= PCGCTL_PWRCLMP; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); +	udelay(5); + +	pcgcctl |= PCGCTL_RSTPDWNMODULE; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); +	udelay(5); + +	pcgcctl |= PCGCTL_STOPPCLK; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); + +	/* Set in_ppd flag to 1 as here core enters suspend. */ +	hsotg->in_ppd = 1; +	hsotg->lx_state = DWC2_L2; +	hsotg->bus_suspended = true; + +	dev_dbg(hsotg->dev, "Entering host partial power down completed.\n"); + +	return ret; +} + +/* + * dwc2_host_exit_partial_power_down() - Exit controller from host partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit host partial power down. + * + * This function is for exiting from Host mode partial power down. + */ +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, +				      int rem_wakeup, bool restore) +{ +	u32 pcgcctl; +	int ret = 0; +	u32 hprt0; + +	dev_dbg(hsotg->dev, "Exiting host partial power down started.\n"); + +	pcgcctl = dwc2_readl(hsotg, PCGCTL); +	pcgcctl &= ~PCGCTL_STOPPCLK; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); +	udelay(5); + +	pcgcctl = dwc2_readl(hsotg, PCGCTL); +	pcgcctl &= ~PCGCTL_PWRCLMP; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); +	udelay(5); + +	pcgcctl = dwc2_readl(hsotg, PCGCTL); +	pcgcctl &= ~PCGCTL_RSTPDWNMODULE; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); + +	udelay(100); +	if (restore) { +		ret = dwc2_restore_global_registers(hsotg); +		if (ret) { +			dev_err(hsotg->dev, "%s: failed to restore registers\n", +				__func__); +			return ret; +		} + +		ret = dwc2_restore_host_registers(hsotg); +		if (ret) { +			dev_err(hsotg->dev, "%s: failed to restore host registers\n", +				__func__); +			return ret; +		} +	} + +	/* Drive resume signaling and exit suspend mode on the port. */ +	hprt0 = dwc2_read_hprt0(hsotg); +	hprt0 |= HPRT0_RES; +	hprt0 &= ~HPRT0_SUSP; +	dwc2_writel(hsotg, hprt0, HPRT0); +	udelay(5); + +	if (!rem_wakeup) { +		/* Stop driveing resume signaling on the port. */ +		hprt0 = dwc2_read_hprt0(hsotg); +		hprt0 &= ~HPRT0_RES; +		dwc2_writel(hsotg, hprt0, HPRT0); + +		hsotg->bus_suspended = false; +	} else { +		/* Turn on the port power bit. */ +		hprt0 = dwc2_read_hprt0(hsotg); +		hprt0 |= HPRT0_PWR; +		dwc2_writel(hsotg, hprt0, HPRT0); + +		/* Connect hcd. */ +		dwc2_hcd_connect(hsotg); + +		mod_timer(&hsotg->wkp_timer, +			  jiffies + msecs_to_jiffies(71)); +	} + +	/* Set lx_state to and in_ppd to 0 as here core exits from suspend. */ +	hsotg->in_ppd = 0; +	hsotg->lx_state = DWC2_L0; + +	dev_dbg(hsotg->dev, "Exiting host partial power down completed.\n"); +	return ret; +} + +/** + * dwc2_host_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * This function is for entering Host mode clock gating. + */ +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ +	u32 hprt0; +	u32 pcgctl; + +	dev_dbg(hsotg->dev, "Entering host clock gating.\n"); + +	/* Put this port in suspend mode. */ +	hprt0 = dwc2_read_hprt0(hsotg); +	hprt0 |= HPRT0_SUSP; +	dwc2_writel(hsotg, hprt0, HPRT0); + +	/* Set the Phy Clock bit as suspend is received. */ +	pcgctl = dwc2_readl(hsotg, PCGCTL); +	pcgctl |= PCGCTL_STOPPCLK; +	dwc2_writel(hsotg, pcgctl, PCGCTL); +	udelay(5); + +	/* Set the Gate hclk as suspend is received. */ +	pcgctl = dwc2_readl(hsotg, PCGCTL); +	pcgctl |= PCGCTL_GATEHCLK; +	dwc2_writel(hsotg, pcgctl, PCGCTL); +	udelay(5); + +	hsotg->bus_suspended = true; +	hsotg->lx_state = DWC2_L2; +} + +/** + * dwc2_host_exit_clock_gating() - Exit controller from clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by remote wakeup + * + * This function is for exiting Host mode clock gating. + */ +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ +	u32 hprt0; +	u32 pcgctl; + +	dev_dbg(hsotg->dev, "Exiting host clock gating.\n"); + +	/* Clear the Gate hclk. */ +	pcgctl = dwc2_readl(hsotg, PCGCTL); +	pcgctl &= ~PCGCTL_GATEHCLK; +	dwc2_writel(hsotg, pcgctl, PCGCTL); +	udelay(5); + +	/* Phy Clock bit. */ +	pcgctl = dwc2_readl(hsotg, PCGCTL); +	pcgctl &= ~PCGCTL_STOPPCLK; +	dwc2_writel(hsotg, pcgctl, PCGCTL); +	udelay(5); + +	/* Drive resume signaling and exit suspend mode on the port. */ +	hprt0 = dwc2_read_hprt0(hsotg); +	hprt0 |= HPRT0_RES; +	hprt0 &= ~HPRT0_SUSP; +	dwc2_writel(hsotg, hprt0, HPRT0); +	udelay(5); + +	if (!rem_wakeup) { +		/* In case of port resume need to wait for 40 ms */ +		msleep(USB_RESUME_TIMEOUT); + +		/* Stop driveing resume signaling on the port. */ +		hprt0 = dwc2_read_hprt0(hsotg); +		hprt0 &= ~HPRT0_RES; +		dwc2_writel(hsotg, hprt0, HPRT0); + +		hsotg->bus_suspended = false; +		hsotg->lx_state = DWC2_L0; +	} else { +		mod_timer(&hsotg->wkp_timer, +			  jiffies + msecs_to_jiffies(71)); +	} +} | 
