Re: [PATCH 7/7 v2] usb: dwc3: add the hibernation code itself

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

 



On Wed, Feb 15, 2012 at 06:57:02PM -0800, Paul Zimmerman wrote:
> This patch adds the actual hibernation code, and allows it to be
> enabled in the Kconfig.
> 
> When hibernation support is enabled, the controller h/w (except
> for a small portion that needs to remain active) will be powered
> down when the host puts the USB bus into U3/L1/L2 state, or when
> the device is disconnected from the host. Unless the device has
> been disconnected, the controller state must be saved to RAM
> before the power is removed.
> 
> When the USB bus returns to the U0/ON state, or the device is
> reconnected, the controller will be powered on, its state
> restored from RAM (unless the device was disconnected), and any
> endpoints that had active transfers will be restarted.
> 
> The code currently only supports the Synopsys HAPS platform.
> There is some platform-specific code that will need to be
> reimplemented to support other platforms.
> 
> There are a few things that remain to be done:
> 
> - Implement restart of isochronous endpoints after hibernation.
>   Currently hibernation will cause those endpoints to fail after
>   resume (so don't enable hibernation yet if your device has
>   isoc endpoints!)
> 
> - Put the controller into hibernation immediately after the driver
>   is loaded, if the device is not connected to a host. Currently
>   this can sometimes cause the controller to hang, but I'm not
>   sure why.
> 
> - Implement function remote wake from hibernation. This is
>   different from the normal case, because the controller must be
>   powered up and restored before sending the remote wake
>   notification.
> 
> Signed-off-by: Paul Zimmerman <paulz@xxxxxxxxxxxx>
> ---
>  drivers/usb/dwc3/Kconfig       |    7 +
>  drivers/usb/dwc3/Makefile      |    2 +
>  drivers/usb/dwc3/core.c        |  112 +++++-
>  drivers/usb/dwc3/core.h        |   41 ++
>  drivers/usb/dwc3/dwc3-pci.c    |   13 +-
>  drivers/usb/dwc3/ep0.c         |    5 +
>  drivers/usb/dwc3/gadget.c      |  103 ++++-
>  drivers/usb/dwc3/haps-pwrctl.c |  200 +++++++++
>  drivers/usb/dwc3/haps-pwrctl.h |   46 ++
>  drivers/usb/dwc3/hibernate.c   |  930 ++++++++++++++++++++++++++++++++++++++++
>  drivers/usb/dwc3/hibernate.h   |  112 +++++
>  11 files changed, 1557 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
> index d8f741f..eff37d6 100644
> --- a/drivers/usb/dwc3/Kconfig
> +++ b/drivers/usb/dwc3/Kconfig
> @@ -25,4 +25,11 @@ config USB_DWC3_VERBOSE
>  	help
>  	  Say Y here to enable verbose debugging messages on DWC3 Driver.
>  
> +config USB_DWC3_HIBERNATION
> +	bool "Enable support for Hibernation feature (EXPERIMENTAL)"
> +	depends on EXPERIMENTAL
> +	help
> +	  Say Y here to enable support for the special Hibernation feature
> +	  of the DWC USB3 controller.
> +
>  endif
> diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
> index 4502648..42a32d5 100644
> --- a/drivers/usb/dwc3/Makefile
> +++ b/drivers/usb/dwc3/Makefile
> @@ -6,6 +6,8 @@ obj-$(CONFIG_USB_DWC3)			+= dwc3.o
>  dwc3-y					:= core.o
>  dwc3-y					+= host.o
>  dwc3-y					+= gadget.o ep0.o
> +dwc3-$(CONFIG_USB_DWC3_HIBERNATION)	+= hibernate.o
> +dwc3-$(CONFIG_USB_DWC3_HIBERNATION)	+= haps-pwrctl.o
>  
>  ifneq ($(CONFIG_DEBUG_FS),)
>  	dwc3-y				+= debugfs.o
> diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
> index 829c0d2..8b2dbce 100644
> --- a/drivers/usb/dwc3/core.c
> +++ b/drivers/usb/dwc3/core.c
> @@ -56,6 +56,7 @@
>  
>  #include "core.h"
>  #include "gadget.h"
> +#include "hibernate.h"
>  #include "io.h"
>  
>  #include "debug.h"
> @@ -309,6 +310,21 @@ static void __devinit dwc3_cache_hwparams(struct dwc3 *dwc)
>  	parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
>  	parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7);
>  	parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8);
> +
> +	if (!dwc3_hiber_enabled(dwc) &&
> +			DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)
> +				== DWC3_GHWPARAMS1_EN_PWROPT_HIB) {
> +		/*
> +		 * If the core supports hibernation, but the user has
> +		 * not enabled it, remove it from the hwparams so we
> +		 * won't try to enable it. But do keep clock gating
> +		 * enabled.
> +		 */
> +		parms->hwparams1 &= ~DWC3_GHWPARAMS1_PWROPT_MASK;
> +		parms->hwparams1 |=
> +			DWC3_GHWPARAMS1_PWROPT(DWC3_GHWPARAMS1_EN_PWROPT_CLK);
> +		dev_dbg(dwc->dev, "Hibernation disabled\n");
> +	}
>  }
>  
>  /**
> @@ -358,8 +374,14 @@ static int __devinit dwc3_core_init(struct dwc3 *dwc)
>  	reg &= ~DWC3_GCTL_DISSCRAMBLE;
>  
>  	switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) {
> +	case DWC3_GHWPARAMS1_EN_PWROPT_HIB:
> +		if (dwc3_hiber_enabled(dwc)) {
> +			dev_dbg(dwc->dev, "Hibernation enabled\n");
> +			reg |= DWC3_GCTL_GBLHIBERNATIONEN;
> +		}
>  	case DWC3_GHWPARAMS1_EN_PWROPT_CLK:
>  		reg &= ~DWC3_GCTL_DSBLCLKGTNG;
> +		dev_dbg(dwc->dev, "Clock gating enabled\n");
>  		break;
>  	default:
>  		dev_dbg(dwc->dev, "No power optimization available\n");
> @@ -376,6 +398,12 @@ static int __devinit dwc3_core_init(struct dwc3 *dwc)
>  
>  	dwc3_writel(dwc->regs, DWC3_GCTL, reg);
>  
> +	ret = dwc3_alloc_scratchpad_buffers(dwc);
> +	if (ret) {
> +		dev_err(dwc->dev, "failed to allocate scratchpad buffers\n");
> +		goto err0;
> +	}
> +
>  	ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
>  	if (ret) {
>  		dev_err(dwc->dev, "failed to allocate event buffers\n");
> @@ -393,6 +421,7 @@ static int __devinit dwc3_core_init(struct dwc3 *dwc)
>  
>  err1:
>  	dwc3_free_event_buffers(dwc);
> +	dwc3_free_scratchpad_buffers(dwc);
>  
>  err0:
>  	return ret;
> @@ -402,6 +431,7 @@ static void dwc3_core_exit(struct dwc3 *dwc)
>  {
>  	dwc3_event_buffers_cleanup(dwc);
>  	dwc3_free_event_buffers(dwc);
> +	dwc3_free_scratchpad_buffers(dwc);
>  }
>  
>  #define DWC3_ALIGN_MASK		(16 - 1)
> @@ -428,6 +458,13 @@ static int __devinit dwc3_probe(struct platform_device *pdev)
>  	dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1);
>  	dwc->mem = mem;
>  
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
> +			"dwc_usb3_haps");
> +	if (res) {
> +		dev_vdbg(&pdev->dev, "This is HAPS platform\n");
> +		dwc->is_haps = true;
> +	}
> +
>  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  	if (!res) {
>  		dev_err(&pdev->dev, "missing resource\n");
> @@ -477,9 +514,8 @@ static int __devinit dwc3_probe(struct platform_device *pdev)
>  	if (of_get_property(node, "tx-fifo-resize", NULL))
>  		dwc->needs_fifo_resize = true;
>  
> +	pm_runtime_set_active(&pdev->dev);
>  	pm_runtime_enable(&pdev->dev);
> -	pm_runtime_get_sync(&pdev->dev);
> -	pm_runtime_forbid(&pdev->dev);
>  
>  	ret = dwc3_core_init(dwc);
>  	if (ret) {
> @@ -576,7 +612,6 @@ static int __devexit dwc3_remove(struct platform_device *pdev)
>  
>  	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>  
> -	pm_runtime_put(&pdev->dev);
>  	pm_runtime_disable(&pdev->dev);
>  
>  	dwc3_debugfs_exit(dwc);
> @@ -605,11 +640,82 @@ static int __devexit dwc3_remove(struct platform_device *pdev)
>  	return 0;
>  }
>  
> +#ifdef CONFIG_PM_RUNTIME
> +
> +static int dwc3_runtime_suspend(struct device *dev)
> +{
> +	struct platform_device	*pdev = to_platform_device(dev);
> +	struct dwc3		*dwc = platform_get_drvdata(pdev);
> +
> +	dev_vdbg(dev, "%s()\n", __func__);
> +
> +	if (dwc3_try_enter_hibernation(dwc))
> +		/* Must not access hardware after this */
> +		return 0;
> +
> +	return -EBUSY;
> +}
> +
> +static int dwc3_runtime_resume(struct device *dev)
> +{
> +	struct platform_device	*pdev = to_platform_device(dev);
> +	struct dwc3		*dwc = platform_get_drvdata(pdev);
> +	int			state;
> +
> +	dev_vdbg(dev, "%s()\n", __func__);
> +
> +	if (dwc3_in_hibernation(dwc))
> +		return -EBUSY;
> +
> +	state = dwc3_hiber_poll(dwc);
> +
> +	/* OK to access hardware now */
> +	return 0;
> +}
> +
> +static int dwc3_runtime_idle(struct device *dev)
> +{
> +	struct platform_device	*pdev = to_platform_device(dev);
> +	struct dwc3		*dwc = platform_get_drvdata(pdev);
> +	int			state;
> +
> +	dev_vdbg(dev, "%s()\n", __func__);
> +
> +	state = dwc3_hiber_poll(dwc);
> +
> +	if (state >= 0)
> +		/* Returning 0 from idle causes runtime suspend to be called */
> +		return 0;
> +
> +	return 1;
> +}
> +
> +#else
> +
> +#define dwc3_runtime_suspend	NULL
> +#define dwc3_runtime_resume	NULL
> +#define dwc3_runtime_idle	NULL
> +
> +#endif
> +
> +#ifdef CONFIG_PM
> +
> +const struct dev_pm_ops dwc3_pm_ops = {
> +	.runtime_suspend = dwc3_runtime_suspend,
> +	.runtime_resume = dwc3_runtime_resume,
> +	.runtime_idle = dwc3_runtime_idle,
> +};
> +
> +#endif
> +
>  static struct platform_driver dwc3_driver = {
>  	.probe		= dwc3_probe,
>  	.remove		= __devexit_p(dwc3_remove),
>  	.driver		= {
>  		.name	= "dwc3",
> +#ifdef CONFIG_PM
> +		.pm	= &dwc3_pm_ops
> +#endif
>  	},
>  };
>  
> diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
> index b300c30..ab2bec9 100644
> --- a/drivers/usb/dwc3/core.h
> +++ b/drivers/usb/dwc3/core.h
> @@ -396,6 +396,7 @@ struct dwc3_event_buffer {
>   * @res_trans_idx: Resource transfer index
>   * @interval: the intervall on which the ISOC transfer is started
>   * @saved_state: ep state saved during hibernation
> + * @hiber_trb_idx: index of trb to restart after hibernation (isoc eps only)
>   * @name: a human readable name e.g. ep1out-bulk
>   * @direction: true for TX, false for RX
>   * @stream_capable: true when streams are enabled
> @@ -431,6 +432,10 @@ struct dwc3_ep {
>  	u32			interval;
>  	u32			saved_state;
>  
> +#ifdef CONFIG_USB_DWC3_HIBERNATION
> +	int			hiber_trb_idx;
> +#endif
> +
>  	char			name[20];
>  
>  	unsigned		direction:1;
> @@ -621,6 +626,18 @@ struct dwc3_scratchpad_array {
>   * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround
>   * @needs_fifo_resize: not all users might want fifo resizing, flag it
>   * @resize_fifos: tells us it's ok to reconfigure our TxFIFO sizes.
> + * @is_haps: true if on a HAPS platform, and need special hibernate handling
> + * @hiber_wait_connect: true if waiting for connect before exiting hibernation
> + * @hiber_wait_u0: true if waiting for U0 link state before exiting hibernation
> + * @hibernate: contains the current hibernation state
> + * @scratchpad_array: scratchpad array for saving hibernation state
> + * @scratchpad_array_dma: dma address of the scratchpad array
> + * @scratchpad: buffers that make up the scratchpad array
> + * @gctl_save: saves state of GCTL register during hibernation
> + * @dcfg_save: saves state of DCFG register during hibernation
> + * @dctl_save: saves state of DCTL register during hibernation
> + * @rxfifosz_save: saves state of GRXFIFOSIZ[0] register during hibernation
> + * @txfifosz_save: saves state of GTXFIFOSIZ registers during hibernation
>   * @ep0_next_event: hold the next expected event
>   * @ep0state: state of endpoint zero
>   * @link_state: link state
> @@ -681,6 +698,30 @@ struct dwc3 {
>  	unsigned		delayed_status:1;
>  	unsigned		needs_fifo_resize:1;
>  	unsigned		resize_fifos:1;
> +	unsigned		is_haps:1;
> +
> +#ifdef CONFIG_USB_DWC3_HIBERNATION
> +	unsigned		hiber_wait_connect:1;
> +	unsigned		hiber_wait_u0:1;
> +
> +	int			hibernate;
> +#define DWC3_HIBER_AWAKE	0	/* Not in hibernation */
> +#define DWC3_HIBER_ENTER_NOSAVE	1	/* Waiting to enter hibernation */
> +#define DWC3_HIBER_ENTER_SAVE	2	/* Waiting to save state & enter hib */
> +#define DWC3_HIBER_SLEEPING	3	/* In hibernation */
> +#define DWC3_HIBER_WAIT_LINK_UP	4	/* Exiting hib, waiting for link up */
> +#define DWC3_HIBER_SS_DIS_QUIRK	5	/* Exiting hib, handling link disable */
> +
> +	struct dwc3_scratchpad_array *scratchpad_array;
> +	dma_addr_t		scratchpad_array_dma;
> +	void			*scratchpad[DWC3_MAX_HIBER_SCRATCHBUFS];
> +
> +	u32			gctl_save;
> +	u32			dcfg_save;
> +	u32			dctl_save;
> +	u32			rxfifosz_save;
> +	u32			txfifosz_save[DWC3_ENDPOINTS_NUM / 2];
> +#endif
>  
>  	enum dwc3_ep0_next	ep0_next_event;
>  	enum dwc3_ep0_state	ep0state;
> diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c
> index 1c64d6d..17d59b7 100644
> --- a/drivers/usb/dwc3/dwc3-pci.c
> +++ b/drivers/usb/dwc3/dwc3-pci.c
> @@ -44,7 +44,6 @@
>  
>  #include "core.h"
>  
> -/* FIXME define these in <linux/pci_ids.h> */
>  #define PCI_VENDOR_ID_SYNOPSYS		0x16c3
>  #define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3	0xabcd
>  
> @@ -56,7 +55,7 @@ struct dwc3_pci {
>  static int __devinit dwc3_pci_probe(struct pci_dev *pci,
>  		const struct pci_device_id *id)
>  {
> -	struct resource		res[2];
> +	struct resource		res[3];
>  	struct platform_device	*dwc3;
>  	struct dwc3_pci		*glue;
>  	int			ret = -ENOMEM;
> @@ -102,6 +101,16 @@ static int __devinit dwc3_pci_probe(struct pci_dev *pci,
>  	res[1].name	= "dwc_usb3";
>  	res[1].flags	= IORESOURCE_IRQ;
>  
> +	if (pci->vendor == PCI_VENDOR_ID_SYNOPSYS &&
> +			pci->device == PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3) {
> +		/* Add dummy resource to tell dwc3 driver this is HAPS */
> +		dev_vdbg(&pci->dev, "adding dummy resource for HAPS\n");
> +		res[2].start	= 0;
> +		res[2].end	= 1;
> +		res[2].name	= "dwc_usb3_haps";
> +		res[2].flags	= IORESOURCE_MEM;
> +	}

instead, you need to add a dev_pm_ops on this file too (as I said on the
last mail), then you can move this check to dwc3-pci.c's
runtime_resume/suspend and call haps specific stuff from there. Just
make a big code comment stating that HAPS is a special situation because
it's non-standard FPGA-only platform.

This will also let you remove the "is_haps" flag from core.c which
doesn't make much sense anyway. If I let "is_haps" go in, soon we will
have
is_my_perfect_special_architecture_which_is_better_then_the_rest_of_you
for every single user and that makes frightens me quite a lot ;-)

Other than that, we're getting there :-) Thanks for the efforts

-- 
balbi

Attachment: signature.asc
Description: Digital signature


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

  Powered by Linux