Re: [PATCH 03/19] usb: dwc2: add controller hibernation support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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




[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux