usb: dwc2: add controller hibernation support
authorGregory Herrero <gregory.herrero@intel.com>
Wed, 29 Apr 2015 20:09:01 +0000 (22:09 +0200)
committerFelipe Balbi <balbi@ti.com>
Wed, 29 Apr 2015 20:18:37 +0000 (15:18 -0500)
When suspending usb bus, phy driver may disable controller power.
In this case, registers need to be saved on suspend and restored
on resume.

Acked-by: John Youn <johnyoun@synopsys.com>
Signed-off-by: Gregory Herrero <gregory.herrero@intel.com>
Signed-off-by: Felipe Balbi <balbi@ti.com>
drivers/usb/dwc2/core.c
drivers/usb/dwc2/core.h

index d5197d492e2179a28809d55ed4364b4152caf7ad..889dc5fad47c9ce41d21fdb09a268a11f9935285 100644 (file)
 #include "core.h"
 #include "hcd.h"
 
+#if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
+/**
+ * dwc2_backup_host_registers() - Backup controller host registers.
+ * When suspending usb bus, registers needs to be backuped
+ * if controller power is disabled once suspended.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+static int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_hregs_backup *hr;
+       int i;
+
+       dev_dbg(hsotg->dev, "%s\n", __func__);
+
+       /* Backup Host regs */
+       hr = hsotg->hr_backup;
+       if (!hr) {
+               hr = devm_kzalloc(hsotg->dev, sizeof(*hr), GFP_KERNEL);
+               if (!hr) {
+                       dev_err(hsotg->dev, "%s: can't allocate host regs\n",
+                                       __func__);
+                       return -ENOMEM;
+               }
+
+               hsotg->hr_backup = hr;
+       }
+       hr->hcfg = readl(hsotg->regs + HCFG);
+       hr->haintmsk = readl(hsotg->regs + HAINTMSK);
+       for (i = 0; i < hsotg->core_params->host_channels; ++i)
+               hr->hcintmsk[i] = readl(hsotg->regs + HCINTMSK(i));
+
+       hr->hprt0 = readl(hsotg->regs + HPRT0);
+       hr->hfir = readl(hsotg->regs + HFIR);
+
+       return 0;
+}
+
+/**
+ * dwc2_restore_host_registers() - Restore controller host registers.
+ * When resuming usb bus, device registers needs to be restored
+ * if controller power were disabled.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+static int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_hregs_backup *hr;
+       int i;
+
+       dev_dbg(hsotg->dev, "%s\n", __func__);
+
+       /* Restore host regs */
+       hr = hsotg->hr_backup;
+       if (!hr) {
+               dev_err(hsotg->dev, "%s: no host registers to restore\n",
+                               __func__);
+               return -EINVAL;
+       }
+
+       writel(hr->hcfg, hsotg->regs + HCFG);
+       writel(hr->haintmsk, hsotg->regs + HAINTMSK);
+
+       for (i = 0; i < hsotg->core_params->host_channels; ++i)
+               writel(hr->hcintmsk[i], hsotg->regs + HCINTMSK(i));
+
+       writel(hr->hprt0, hsotg->regs + HPRT0);
+       writel(hr->hfir, hsotg->regs + HFIR);
+
+       return 0;
+}
+#else
+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; }
+#endif
+
+#if IS_ENABLED(CONFIG_USB_DWC2_PERIPHERAL) || \
+       IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE)
+/**
+ * dwc2_backup_device_registers() - Backup controller device registers.
+ * When suspending usb bus, registers needs to be backuped
+ * if controller power is disabled once suspended.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+static int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_dregs_backup *dr;
+       int i;
+
+       dev_dbg(hsotg->dev, "%s\n", __func__);
+
+       /* Backup dev regs */
+       dr = hsotg->dr_backup;
+       if (!dr) {
+               dr = devm_kzalloc(hsotg->dev, sizeof(*dr), GFP_KERNEL);
+               if (!dr) {
+                       dev_err(hsotg->dev, "%s: can't allocate device regs\n",
+                                       __func__);
+                       return -ENOMEM;
+               }
+
+               hsotg->dr_backup = dr;
+       }
+
+       dr->dcfg = readl(hsotg->regs + DCFG);
+       dr->dctl = readl(hsotg->regs + DCTL);
+       dr->daintmsk = readl(hsotg->regs + DAINTMSK);
+       dr->diepmsk = readl(hsotg->regs + DIEPMSK);
+       dr->doepmsk = readl(hsotg->regs + DOEPMSK);
+
+       for (i = 0; i < hsotg->num_of_eps; i++) {
+               /* Backup IN EPs */
+               dr->diepctl[i] = readl(hsotg->regs + DIEPCTL(i));
+
+               /* Ensure DATA PID is correctly configured */
+               if (dr->diepctl[i] & DXEPCTL_DPID)
+                       dr->diepctl[i] |= DXEPCTL_SETD1PID;
+               else
+                       dr->diepctl[i] |= DXEPCTL_SETD0PID;
+
+               dr->dieptsiz[i] = readl(hsotg->regs + DIEPTSIZ(i));
+               dr->diepdma[i] = readl(hsotg->regs + DIEPDMA(i));
+
+               /* Backup OUT EPs */
+               dr->doepctl[i] = readl(hsotg->regs + DOEPCTL(i));
+
+               /* Ensure DATA PID is correctly configured */
+               if (dr->doepctl[i] & DXEPCTL_DPID)
+                       dr->doepctl[i] |= DXEPCTL_SETD1PID;
+               else
+                       dr->doepctl[i] |= DXEPCTL_SETD0PID;
+
+               dr->doeptsiz[i] = readl(hsotg->regs + DOEPTSIZ(i));
+               dr->doepdma[i] = readl(hsotg->regs + DOEPDMA(i));
+       }
+
+       return 0;
+}
+
+/**
+ * dwc2_restore_device_registers() - Restore controller device registers.
+ * When resuming usb bus, device registers needs to be restored
+ * if controller power were disabled.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+static int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_dregs_backup *dr;
+       u32 dctl;
+       int i;
+
+       dev_dbg(hsotg->dev, "%s\n", __func__);
+
+       /* Restore dev regs */
+       dr = hsotg->dr_backup;
+       if (!dr) {
+               dev_err(hsotg->dev, "%s: no device registers to restore\n",
+                               __func__);
+               return -EINVAL;
+       }
+
+       writel(dr->dcfg, hsotg->regs + DCFG);
+       writel(dr->dctl, hsotg->regs + DCTL);
+       writel(dr->daintmsk, hsotg->regs + DAINTMSK);
+       writel(dr->diepmsk, hsotg->regs + DIEPMSK);
+       writel(dr->doepmsk, hsotg->regs + DOEPMSK);
+
+       for (i = 0; i < hsotg->num_of_eps; i++) {
+               /* Restore IN EPs */
+               writel(dr->diepctl[i], hsotg->regs + DIEPCTL(i));
+               writel(dr->dieptsiz[i], hsotg->regs + DIEPTSIZ(i));
+               writel(dr->diepdma[i], hsotg->regs + DIEPDMA(i));
+
+               /* Restore OUT EPs */
+               writel(dr->doepctl[i], hsotg->regs + DOEPCTL(i));
+               writel(dr->doeptsiz[i], hsotg->regs + DOEPTSIZ(i));
+               writel(dr->doepdma[i], hsotg->regs + DOEPDMA(i));
+       }
+
+       /* Set the Power-On Programming done bit */
+       dctl = readl(hsotg->regs + DCTL);
+       dctl |= DCTL_PWRONPRGDONE;
+       writel(dctl, hsotg->regs + DCTL);
+
+       return 0;
+}
+#else
+static inline int dwc2_backup_device_registers(struct dwc2_hsotg *hsotg)
+{ return 0; }
+
+static inline int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg)
+{ return 0; }
+#endif
+
+/**
+ * dwc2_backup_global_registers() - Backup global controller registers.
+ * When suspending usb bus, registers needs to be backuped
+ * if controller power is disabled once suspended.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+static int dwc2_backup_global_registers(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_gregs_backup *gr;
+       int i;
+
+       /* Backup global regs */
+       gr = hsotg->gr_backup;
+       if (!gr) {
+               gr = devm_kzalloc(hsotg->dev, sizeof(*gr), GFP_KERNEL);
+               if (!gr) {
+                       dev_err(hsotg->dev, "%s: can't allocate global regs\n",
+                                       __func__);
+                       return -ENOMEM;
+               }
+
+               hsotg->gr_backup = gr;
+       }
+
+       gr->gotgctl = readl(hsotg->regs + GOTGCTL);
+       gr->gintmsk = readl(hsotg->regs + GINTMSK);
+       gr->gahbcfg = readl(hsotg->regs + GAHBCFG);
+       gr->gusbcfg = readl(hsotg->regs + GUSBCFG);
+       gr->grxfsiz = readl(hsotg->regs + GRXFSIZ);
+       gr->gnptxfsiz = readl(hsotg->regs + GNPTXFSIZ);
+       gr->hptxfsiz = readl(hsotg->regs + HPTXFSIZ);
+       gr->gdfifocfg = readl(hsotg->regs + GDFIFOCFG);
+       for (i = 0; i < MAX_EPS_CHANNELS; i++)
+               gr->dtxfsiz[i] = readl(hsotg->regs + DPTXFSIZN(i));
+
+       return 0;
+}
+
+/**
+ * dwc2_restore_global_registers() - Restore controller global registers.
+ * When resuming usb bus, device registers needs to be restored
+ * if controller power were disabled.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+static int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg)
+{
+       struct dwc2_gregs_backup *gr;
+       int i;
+
+       dev_dbg(hsotg->dev, "%s\n", __func__);
+
+       /* Restore global regs */
+       gr = hsotg->gr_backup;
+       if (!gr) {
+               dev_err(hsotg->dev, "%s: no global registers to restore\n",
+                               __func__);
+               return -EINVAL;
+       }
+
+       writel(0xffffffff, hsotg->regs + GINTSTS);
+       writel(gr->gotgctl, hsotg->regs + GOTGCTL);
+       writel(gr->gintmsk, hsotg->regs + GINTMSK);
+       writel(gr->gusbcfg, hsotg->regs + GUSBCFG);
+       writel(gr->gahbcfg, hsotg->regs + GAHBCFG);
+       writel(gr->grxfsiz, hsotg->regs + GRXFSIZ);
+       writel(gr->gnptxfsiz, hsotg->regs + GNPTXFSIZ);
+       writel(gr->hptxfsiz, hsotg->regs + HPTXFSIZ);
+       writel(gr->gdfifocfg, hsotg->regs + GDFIFOCFG);
+       for (i = 0; i < MAX_EPS_CHANNELS; i++)
+               writel(gr->dtxfsiz[i], hsotg->regs + DPTXFSIZN(i));
+
+       return 0;
+}
+
+/**
+ * dwc2_exit_hibernation() - Exit controller from Partial Power Down.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ * @restore: Controller registers need to be restored
+ */
+int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore)
+{
+       u32 pcgcctl;
+       int ret = 0;
+
+       pcgcctl = readl(hsotg->regs + PCGCTL);
+       pcgcctl &= ~PCGCTL_STOPPCLK;
+       writel(pcgcctl, hsotg->regs + PCGCTL);
+
+       pcgcctl = readl(hsotg->regs + PCGCTL);
+       pcgcctl &= ~PCGCTL_PWRCLMP;
+       writel(pcgcctl, hsotg->regs + PCGCTL);
+
+       pcgcctl = readl(hsotg->regs + PCGCTL);
+       pcgcctl &= ~PCGCTL_RSTPDWNMODULE;
+       writel(pcgcctl, hsotg->regs + 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;
+               }
+               if (dwc2_is_host_mode(hsotg)) {
+                       ret = dwc2_restore_host_registers(hsotg);
+                       if (ret) {
+                               dev_err(hsotg->dev, "%s: failed to restore host registers\n",
+                                               __func__);
+                               return ret;
+                       }
+               } else {
+                       ret = dwc2_restore_device_registers(hsotg);
+                       if (ret) {
+                               dev_err(hsotg->dev, "%s: failed to restore device registers\n",
+                                               __func__);
+                               return ret;
+                       }
+               }
+       }
+
+       return ret;
+}
+
+/**
+ * dwc2_enter_hibernation() - Put controller in Partial Power Down.
+ *
+ * @hsotg: Programming view of the DWC_otg controller
+ */
+int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg)
+{
+       u32 pcgcctl;
+       int ret = 0;
+
+       /* 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;
+       }
+
+       if (dwc2_is_host_mode(hsotg)) {
+               ret = dwc2_backup_host_registers(hsotg);
+               if (ret) {
+                       dev_err(hsotg->dev, "%s: failed to backup host registers\n",
+                                       __func__);
+                       return ret;
+               }
+       } else {
+               ret = dwc2_backup_device_registers(hsotg);
+               if (ret) {
+                       dev_err(hsotg->dev, "%s: failed to backup device registers\n",
+                                       __func__);
+                       return ret;
+               }
+       }
+
+       /* Put the controller in low power state */
+       pcgcctl = readl(hsotg->regs + PCGCTL);
+
+       pcgcctl |= PCGCTL_PWRCLMP;
+       writel(pcgcctl, hsotg->regs + PCGCTL);
+       ndelay(20);
+
+       pcgcctl |= PCGCTL_RSTPDWNMODULE;
+       writel(pcgcctl, hsotg->regs + PCGCTL);
+       ndelay(20);
+
+       pcgcctl |= PCGCTL_STOPPCLK;
+       writel(pcgcctl, hsotg->regs + PCGCTL);
+
+       return ret;
+}
+
 /**
  * dwc2_enable_common_interrupts() - Initializes the commmon interrupts,
  * used in both device and host modes
index 1fd8d2bc0dc9a454f5e61f9dd196f0a2b419555c..b0ee9511dc92ac6b5425ce3694513d73be079979 100644 (file)
@@ -451,6 +451,82 @@ struct dwc2_hw_params {
 /* Size of control and EP0 buffers */
 #define DWC2_CTRL_BUFF_SIZE 8
 
+/**
+ * struct dwc2_gregs_backup - Holds global registers state before entering partial
+ * power down
+ * @gotgctl:           Backup of GOTGCTL register
+ * @gintmsk:           Backup of GINTMSK register
+ * @gahbcfg:           Backup of GAHBCFG register
+ * @gusbcfg:           Backup of GUSBCFG register
+ * @grxfsiz:           Backup of GRXFSIZ register
+ * @gnptxfsiz:         Backup of GNPTXFSIZ register
+ * @gi2cctl:           Backup of GI2CCTL register
+ * @hptxfsiz:          Backup of HPTXFSIZ register
+ * @gdfifocfg:         Backup of GDFIFOCFG register
+ * @dtxfsiz:           Backup of DTXFSIZ registers for each endpoint
+ * @gpwrdn:            Backup of GPWRDN register
+ */
+struct dwc2_gregs_backup {
+       u32 gotgctl;
+       u32 gintmsk;
+       u32 gahbcfg;
+       u32 gusbcfg;
+       u32 grxfsiz;
+       u32 gnptxfsiz;
+       u32 gi2cctl;
+       u32 hptxfsiz;
+       u32 pcgcctl;
+       u32 gdfifocfg;
+       u32 dtxfsiz[MAX_EPS_CHANNELS];
+       u32 gpwrdn;
+};
+
+/**
+ * struct  dwc2_dregs_backup - Holds device registers state before entering partial
+ * power down
+ * @dcfg:              Backup of DCFG register
+ * @dctl:              Backup of DCTL register
+ * @daintmsk:          Backup of DAINTMSK register
+ * @diepmsk:           Backup of DIEPMSK register
+ * @doepmsk:           Backup of DOEPMSK register
+ * @diepctl:           Backup of DIEPCTL register
+ * @dieptsiz:          Backup of DIEPTSIZ register
+ * @diepdma:           Backup of DIEPDMA register
+ * @doepctl:           Backup of DOEPCTL register
+ * @doeptsiz:          Backup of DOEPTSIZ register
+ * @doepdma:           Backup of DOEPDMA register
+ */
+struct dwc2_dregs_backup {
+       u32 dcfg;
+       u32 dctl;
+       u32 daintmsk;
+       u32 diepmsk;
+       u32 doepmsk;
+       u32 diepctl[MAX_EPS_CHANNELS];
+       u32 dieptsiz[MAX_EPS_CHANNELS];
+       u32 diepdma[MAX_EPS_CHANNELS];
+       u32 doepctl[MAX_EPS_CHANNELS];
+       u32 doeptsiz[MAX_EPS_CHANNELS];
+       u32 doepdma[MAX_EPS_CHANNELS];
+};
+
+/**
+ * struct  dwc2_hregs_backup - Holds host registers state before entering partial
+ * power down
+ * @hcfg:              Backup of HCFG register
+ * @haintmsk:          Backup of HAINTMSK register
+ * @hcintmsk:          Backup of HCINTMSK register
+ * @hptr0:             Backup of HPTR0 register
+ * @hfir:              Backup of HFIR register
+ */
+struct dwc2_hregs_backup {
+       u32 hcfg;
+       u32 haintmsk;
+       u32 hcintmsk[MAX_EPS_CHANNELS];
+       u32 hprt0;
+       u32 hfir;
+};
+
 /**
  * struct dwc2_hsotg - Holds the state of the driver, including the non-periodic
  * and periodic schedules
@@ -481,6 +557,9 @@ struct dwc2_hw_params {
  *                      interrupt
  * @wkp_timer:          Timer object for handling Wakeup Detected interrupt
  * @lx_state:           Lx state of connected device
+ * @gregs_backup: Backup of global registers during suspend
+ * @dregs_backup: Backup of device registers during suspend
+ * @hregs_backup: Backup of host registers during suspend
  *
  * These are for host mode:
  *
@@ -613,6 +692,9 @@ struct dwc2_hsotg {
        struct work_struct wf_otg;
        struct timer_list wkp_timer;
        enum dwc2_lx_state lx_state;
+       struct dwc2_gregs_backup *gr_backup;
+       struct dwc2_dregs_backup *dr_backup;
+       struct dwc2_hregs_backup *hr_backup;
 
        struct dentry *debug_root;
        struct debugfs_regset32 *regset;
@@ -749,6 +831,8 @@ enum dwc2_halt_status {
  * and the DWC_otg controller
  */
 extern void dwc2_core_host_init(struct dwc2_hsotg *hsotg);
+extern int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg);
+extern int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, bool restore);
 
 /*
  * Host core Functions.