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: Vardan Mikayelyan <mvar...@synopsys.com>
Signed-off-by: John Youn <johny...@synopsys.com>
---
 drivers/usb/dwc2/core.c   |   4 +-
 drivers/usb/dwc2/core.h   |  18 ++++
 drivers/usb/dwc2/gadget.c | 196 ++++++++++++++++++++++++++++++++++++++
 drivers/usb/dwc2/hcd.c    | 233 ++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 449 insertions(+), 2 deletions(-)

diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c
index f478cdd..a37dfc2 100644
--- a/drivers/usb/dwc2/core.c
+++ b/drivers/usb/dwc2/core.c
@@ -63,7 +63,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;
 
@@ -94,7 +94,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;
 
diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
index 950915b..9d17f4a 100644
--- a/drivers/usb/dwc2/core.h
+++ b/drivers/usb/dwc2/core.h
@@ -1131,6 +1131,8 @@ static inline bool dwc2_is_hs_iot(struct dwc2_hsotg 
*hsotg)
 void dwc2_disable_global_interrupts(struct dwc2_hsotg *hcd);
 
 void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup);
+int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg);
+int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg);
 
 /* This function should be called on every hardware interrupt. */
 irqreturn_t dwc2_handle_common_intr(int irq, void *dev);
@@ -1193,6 +1195,9 @@ void dwc2_hsotg_core_init_disconnected(struct dwc2_hsotg 
*dwc2,
 #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);
@@ -1218,6 +1223,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)
@@ -1234,6 +1244,9 @@ static inline int dwc2_hsotg_tx_fifo_average_depth(struct 
dwc2_hsotg *hsotg)
 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; }
@@ -1250,6 +1263,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
 
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index fd274a8..e82e1db 100644
--- a/drivers/usb/dwc2/gadget.c
+++ b/drivers/usb/dwc2/gadget.c
@@ -4870,3 +4870,199 @@ int dwc2_restore_device_registers(struct dwc2_hsotg 
*hsotg, int remote_wakeup)
 
        return 0;
 }
+
+/**
+ * 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 pcgcctl;
+       u32 gpwrdn;
+       u32 gusbcfg;
+       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;
+       }
+       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);
+       } 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);
+       }
+
+       /* 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;
+       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);
+
+       /* 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);
+
+       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;
+}
diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c
index e8adfeb..53d0cca 100644
--- a/drivers/usb/dwc2/hcd.c
+++ b/drivers/usb/dwc2/hcd.c
@@ -5388,3 +5388,236 @@ 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)
+{
+       int timeout;
+       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 */
+       timeout = 300;
+       do {
+               hprt0 = dwc2_readl(hsotg->regs + HPRT0);
+               if (hprt0 & HPRT0_SUSP)
+                       break;
+               mdelay(1);
+       } while (--timeout);
+
+       if (!timeout)
+               dev_warn(hsotg->dev, "Suspend wasn't genereted\n");
+
+       udelay(10);
+
+       /*
+        * 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);
+       } 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);
+       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;
+}
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to