diff options
Diffstat (limited to 'drivers/usb/dwc2/gadget.c')
| -rw-r--r-- | drivers/usb/dwc2/gadget.c | 223 | 
1 files changed, 215 insertions, 8 deletions
| diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index ad4c94366dad..e6bb1bdb2760 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3689,10 +3689,10 @@ irq_retry:  		dwc2_writel(hsotg, GINTSTS_RESETDET, GINTSTS);  		/* This event must be used only if controller is suspended */ -		if (hsotg->lx_state == DWC2_L2) { -			dwc2_exit_partial_power_down(hsotg, true); -			hsotg->lx_state = DWC2_L0; -		} +		if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) +			dwc2_exit_partial_power_down(hsotg, 0, true); + +		hsotg->lx_state = DWC2_L0;  	}  	if (gintsts & (GINTSTS_USBRST | GINTSTS_RESETDET)) { @@ -4615,11 +4615,15 @@ static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active)  	spin_lock_irqsave(&hsotg->lock, flags);  	/* -	 * If controller is hibernated, it must exit from power_down -	 * before being initialized / de-initialized +	 * If controller is in partial power down state, it must exit from +	 * that state before being initialized / de-initialized  	 */ -	if (hsotg->lx_state == DWC2_L2) -		dwc2_exit_partial_power_down(hsotg, false); +	if (hsotg->lx_state == DWC2_L2 && hsotg->in_ppd) +		/* +		 * No need to check the return value as +		 * registers are not being restored. +		 */ +		dwc2_exit_partial_power_down(hsotg, 0, false);  	if (is_active) {  		hsotg->op_state = OTG_STATE_B_PERIPHERAL; @@ -5301,6 +5305,10 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,  	dwc2_writel(hsotg, dr->dcfg, DCFG);  	dwc2_writel(hsotg, dr->dctl, DCTL); +	/* On USB Reset, reset device address to zero */ +	if (reset) +		dwc2_clear_bit(hsotg, DCFG, DCFG_DEVADDR_MASK); +  	/* De-assert Wakeup Logic */  	gpwrdn = dwc2_readl(hsotg, GPWRDN);  	gpwrdn &= ~GPWRDN_PMUACTV; @@ -5351,3 +5359,202 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg,  	return ret;  } + +/** + * dwc2_gadget_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 device partial power down. + * + * This function is for entering device mode partial power down. + */ +int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ +	u32 pcgcctl; +	int ret = 0; + +	dev_dbg(hsotg->dev, "Entering device partial power down started.\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_device_registers(hsotg); +	if (ret) { +		dev_err(hsotg->dev, "%s: failed to backup device 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; + +	dev_dbg(hsotg->dev, "Entering device partial power down completed.\n"); + +	return ret; +} + +/* + * dwc2_gadget_exit_partial_power_down() - Exit controller from device partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit device partial power down. + * + * This function is for exiting from device mode partial power down. + */ +int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, +					bool restore) +{ +	u32 pcgcctl; +	u32 dctl; +	struct dwc2_dregs_backup *dr; +	int ret = 0; + +	dr = &hsotg->dr_backup; + +	dev_dbg(hsotg->dev, "Exiting device partial Power Down started.\n"); + +	pcgcctl = dwc2_readl(hsotg, PCGCTL); +	pcgcctl &= ~PCGCTL_STOPPCLK; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); + +	pcgcctl = dwc2_readl(hsotg, PCGCTL); +	pcgcctl &= ~PCGCTL_PWRCLMP; +	dwc2_writel(hsotg, pcgcctl, PCGCTL); + +	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; +		} +		/* Restore DCFG */ +		dwc2_writel(hsotg, dr->dcfg, DCFG); + +		ret = dwc2_restore_device_registers(hsotg, 0); +		if (ret) { +			dev_err(hsotg->dev, "%s: failed to restore device registers\n", +				__func__); +			return ret; +		} +	} + +	/* Set the Power-On Programming done bit */ +	dctl = dwc2_readl(hsotg, DCTL); +	dctl |= DCTL_PWRONPRGDONE; +	dwc2_writel(hsotg, dctl, DCTL); + +	/* Set in_ppd flag to 0 as here core exits from suspend. */ +	hsotg->in_ppd = 0; +	hsotg->lx_state = DWC2_L0; + +	dev_dbg(hsotg->dev, "Exiting device partial Power Down completed.\n"); +	return ret; +} + +/** + * dwc2_gadget_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter device partial power down. + * + * This function is for entering device mode clock gating. + */ +void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ +	u32 pcgctl; + +	dev_dbg(hsotg->dev, "Entering device clock gating.\n"); + +	/* 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->lx_state = DWC2_L2; +	hsotg->bus_suspended = true; +} + +/* + * dwc2_gadget_exit_clock_gating() - Exit controller from device clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether remote wake up is enabled. + * + * This function is for exiting from device mode clock gating. + */ +void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ +	u32 pcgctl; +	u32 dctl; + +	dev_dbg(hsotg->dev, "Exiting device 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); + +	if (rem_wakeup) { +		/* Set Remote Wakeup Signaling */ +		dctl = dwc2_readl(hsotg, DCTL); +		dctl |= DCTL_RMTWKUPSIG; +		dwc2_writel(hsotg, dctl, DCTL); +	} + +	/* Change to L0 state */ +	call_gadget(hsotg, resume); +	hsotg->lx_state = DWC2_L0; +	hsotg->bus_suspended = false; +} | 
