summaryrefslogtreecommitdiff
path: root/drivers/usb/dwc2/gadget.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc2/gadget.c')
-rw-r--r--drivers/usb/dwc2/gadget.c223
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;
+}