Merge tag 'mvebu-fixes-5.14-1' of git://git.kernel.org/pub/scm/linux/kernel/git/gclem...
[linux-2.6-microblaze.git] / drivers / usb / dwc2 / hcd.c
index 1a9789e..2a78289 100644 (file)
@@ -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,34 @@ 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.
+                */
+               if (!hsotg->params.no_clock_gating)
+                       dwc2_host_enter_clock_gating(hsotg);
+               break;
        }
 
        /* For HNP the bus must be suspended for at least 200ms */
@@ -3326,44 +3355,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 +3452,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 +3664,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 +3680,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 +4356,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,47 +4371,52 @@ 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 ||
-           hsotg->flags.b.port_connect_status == 0)
+       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.
+                */
+               if (!hsotg->params.no_clock_gating)
+                       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);
@@ -4382,7 +4436,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);
@@ -4393,68 +4447,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);
 
@@ -4565,12 +4653,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;
 
@@ -5580,7 +5697,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;
@@ -5607,3 +5732,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));
+       }
+}