Hi, On 03/09/2015 04:04 PM, Mian Yousaf Kaukab wrote: > From: Gregory Herrero <gregory.herrero@xxxxxxxxx> > > When suspending usb bus, phy driver may disable controller power. > In this case, registers need to be saved on suspend and restored > on resume. > > Signed-off-by: Gregory Herrero <gregory.herrero@xxxxxxxxx> > --- > drivers/usb/dwc2/core.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++++ > drivers/usb/dwc2/core.h | 84 +++++++++++ > 2 files changed, 460 insertions(+) > > diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c > index d5197d4..e858062 100644 > --- a/drivers/usb/dwc2/core.c > +++ b/drivers/usb/dwc2/core.c > @@ -56,6 +56,382 @@ > #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 hregs_backup *hr; > + int i; > + > + dev_dbg(hsotg->dev, "%s\n", __func__); > + > + /* Backup Host regs */ > + hr = hsotg->hr_backup; > + if (!hr) { > + hr = kmalloc(sizeof(*hr), GFP_KERNEL); Where is kfree(hr)? > + 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 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 dregs_backup *dr; > + int i; > + > + dev_dbg(hsotg->dev, "%s\n", __func__); > + > + /* Backup dev regs */ > + dr = hsotg->dr_backup; > + if (!dr) { > + dr = kmalloc(sizeof(*dr), GFP_KERNEL); Ditto, kfree(dr) needed. > + 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 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 gregs_backup *gr; > + int i; > + > + /* Backup global regs */ > + gr = hsotg->gr_backup; > + if (!gr) { > + gr = kmalloc(sizeof(*gr), GFP_KERNEL); Ditto, kfree(gr) needed. -- Best regards, Robert > + 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 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 > diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h > index e60655f..d62b904 100644 > --- a/drivers/usb/dwc2/core.h > +++ b/drivers/usb/dwc2/core.h > @@ -452,6 +452,82 @@ struct dwc2_hw_params { > #define DWC2_CTRL_BUFF_SIZE 8 > > /** > + * struct 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 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 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 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 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 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: > * > @@ -611,6 +690,9 @@ struct dwc2_hsotg { > struct work_struct wf_otg; > struct timer_list wkp_timer; > enum dwc2_lx_state lx_state; > + struct gregs_backup *gr_backup; > + struct dregs_backup *dr_backup; > + struct hregs_backup *hr_backup; > > struct dentry *debug_root; > struct debugfs_regset32 *regset; > @@ -747,6 +829,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. > -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html