Hi,
On 03/09/2015 04:04 PM, Mian Yousaf Kaukab wrote:
> From: Gregory Herrero <[email protected]>
>
> 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 <[email protected]>
> ---
> 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 [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html