Skip to content

Commit

Permalink
usb: dwc2: add controller hibernation support
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Gregory Herrero authored and Felipe Balbi committed Apr 29, 2015
1 parent 563cf01 commit d17ee77
Show file tree
Hide file tree
Showing 2 changed files with 461 additions and 0 deletions.
377 changes: 377 additions & 0 deletions drivers/usb/dwc2/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,383 @@
#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
Expand Down
Loading

0 comments on commit d17ee77

Please sign in to comment.