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

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

 



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;
+	}
+
 	ret = platform_device_add_resources(dwc3, res, ARRAY_SIZE(res));
 	if (ret) {
 		dev_err(&pci->dev, "couldn't add resources to dwc3 device\n");
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index 25910e2..4a63a8d 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -52,6 +52,7 @@
 
 #include "core.h"
 #include "gadget.h"
+#include "hibernate.h"
 #include "io.h"
 
 static void dwc3_ep0_do_control_status(struct dwc3 *dwc, u32 epnum);
@@ -178,7 +179,10 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
 
 	int				ret;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
+
 	if (!dep->desc) {
 		dev_dbg(dwc->dev, "trying to queue request %p to disabled %s\n",
 				request, dep->name);
@@ -200,6 +204,7 @@ int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
 
 out:
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 93eb673..d208fc8 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -52,6 +52,7 @@
 
 #include "core.h"
 #include "gadget.h"
+#include "hibernate.h"
 #include "io.h"
 
 /**
@@ -725,9 +726,12 @@ static int dwc3_gadget_ep_enable(struct usb_ep *ep,
 
 	dev_vdbg(dwc->dev, "Enabling %s\n", dep->name);
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 	ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false);
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -757,9 +761,12 @@ static int dwc3_gadget_ep_disable(struct usb_ep *ep)
 			dep->number >> 1,
 			(dep->number & 1) ? "in" : "out");
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 	ret = __dwc3_gadget_ep_disable(dep);
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -1134,9 +1141,12 @@ static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request,
 	dev_vdbg(dwc->dev, "queing request %p to %s length %d\n",
 			request, ep->name, request->length);
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 	ret = __dwc3_gadget_ep_queue(dep, req);
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -1153,7 +1163,9 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
 	unsigned long			flags;
 	int				ret = 0;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 
 	list_for_each_entry(r, &dep->request_list, list) {
 		if (r == req)
@@ -1181,6 +1193,7 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
 
 out0:
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -1237,7 +1250,9 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value)
 
 	int				ret;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 
 	if (usb_endpoint_xfer_isoc(dep->desc)) {
 		dev_err(dwc->dev, "%s is of Isochronous type\n", dep->name);
@@ -1248,6 +1263,7 @@ static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value)
 	ret = __dwc3_gadget_ep_set_halt(dep, value);
 out:
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -1300,9 +1316,15 @@ static const struct usb_ep_ops dwc3_gadget_ep_ops = {
 static int dwc3_gadget_get_frame(struct usb_gadget *g)
 {
 	struct dwc3		*dwc = gadget_to_dwc(g);
+	unsigned long		flags;
 	u32			reg;
 
+	pm_runtime_get(dwc->dev);
+	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 	reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 	return DWC3_DSTS_SOFFN(reg);
 }
 
@@ -1320,7 +1342,9 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g)
 	u8			link_state;
 	u8			speed;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 
 	/*
 	 * According to the Databook Remote wakeup request should
@@ -1385,6 +1409,7 @@ static int dwc3_gadget_wakeup(struct usb_gadget *g)
 
 out:
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -1413,8 +1438,12 @@ void dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on)
 			reg &= ~DWC3_DCTL_TRGTULST_MASK;
 			reg |= DWC3_DCTL_TRGTULST_RX_DET;
 		}
-		if (dwc->revision >= DWC3_REVISION_194A)
-			reg &= ~DWC3_DCTL_KEEP_CONNECT;
+		if (dwc->revision >= DWC3_REVISION_194A) {
+			if (dwc3_hiber_enabled(dwc))
+				reg |= DWC3_DCTL_KEEP_CONNECT;
+			else
+				reg &= ~DWC3_DCTL_KEEP_CONNECT;
+		}
 		reg |= DWC3_DCTL_RUN_STOP;
 	} else {
 		reg &= ~DWC3_DCTL_RUN_STOP;
@@ -1450,9 +1479,12 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
 
 	is_on = !!is_on;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 	dwc3_gadget_run_stop(dwc, is_on);
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return 0;
 }
@@ -1501,7 +1533,9 @@ static int dwc3_gadget_start(struct usb_gadget *g,
 	u32			reg;
 	int			ret;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 
 	if (dwc->gadget_driver) {
 		dev_err(dwc->dev, "%s is already bound to %s\n",
@@ -1526,6 +1560,7 @@ static int dwc3_gadget_start(struct usb_gadget *g,
 
 out:
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return ret;
 }
@@ -1536,7 +1571,9 @@ static int dwc3_gadget_stop(struct usb_gadget *g,
 	struct dwc3		*dwc = gadget_to_dwc(g);
 	unsigned long		flags;
 
+	pm_runtime_get(dwc->dev);
 	spin_lock_irqsave(&dwc->lock, flags);
+	dwc3_wait_if_hibernating(dwc);
 
 	__dwc3_gadget_ep_disable(dwc->eps[0]);
 	__dwc3_gadget_ep_disable(dwc->eps[1]);
@@ -1545,6 +1582,7 @@ static int dwc3_gadget_stop(struct usb_gadget *g,
 	dwc->gadget.dev.driver	= NULL;
 
 	spin_unlock_irqrestore(&dwc->lock, flags);
+	pm_runtime_put(dwc->dev);
 
 	return 0;
 }
@@ -1983,6 +2021,10 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
 
 	dwc->gadget.speed = USB_SPEED_UNKNOWN;
 	dwc->setup_packet_pending = false;
+
+	/* Maybe enter hibernation */
+	if (dwc3_request_enter_hiber(dwc, false))
+		pm_request_idle(dwc->dev);
 }
 
 static void dwc3_gadget_usb3_suspend_phy(struct dwc3 *dwc, int enable)
@@ -2202,6 +2244,12 @@ void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
 	 *
 	 * In both cases reset values should be sufficient.
 	 */
+
+	/*
+	 * Have now done 'Perform the steps in Section 9.1.3
+	 * "Initialization on Connect Done" in databook'.
+	 */
+	dwc3_try_exit_hiber_after_connect(dwc);
 }
 
 static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
@@ -2216,10 +2264,22 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
 	dwc->gadget_driver->resume(&dwc->gadget);
 }
 
+static void dwc3_gadget_hiber_interrupt(struct dwc3 *dwc,
+		unsigned int evtinfo)
+{
+	dev_vdbg(dwc->dev, "%s\n", __func__);
+
+	/* Maybe save state and enter hibernation */
+	if (dwc3_request_enter_hiber(dwc, true))
+		pm_request_idle(dwc->dev);
+}
+
 static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
 		unsigned int evtinfo)
 {
 	enum dwc3_link_state	next = evtinfo & DWC3_LINK_STATE_MASK;
+	u32			reg;
+	u8			speed;
 
 	/*
 	 * WORKAROUND: DWC3 Revisions <1.83a have an issue which, depending
@@ -2267,6 +2327,14 @@ static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
 		}
 	}
 
+	/* must update speed after link status intr */
+	reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+	speed = reg & DWC3_DSTS_CONNECTSPD;
+	dwc->speed = speed;
+
+	/* do hibernation remote wake if needed */
+	dwc3_hiber_remote_wake(dwc);
+
 	dwc->link_state = next;
 
 	dev_vdbg(dwc->dev, "%s link %d\n", __func__, dwc->link_state);
@@ -2288,6 +2356,9 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc,
 	case DWC3_DEVICE_EVENT_WAKEUP:
 		dwc3_gadget_wakeup_interrupt(dwc);
 		break;
+	case DWC3_DEVICE_EVENT_HIBER_REQ:
+		dwc3_gadget_hiber_interrupt(dwc, event->event_info);
+		break;
 	case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE:
 		dwc3_gadget_linksts_change_interrupt(dwc, event->event_info);
 		break;
@@ -2374,6 +2445,12 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc)
 
 	spin_lock(&dwc->lock);
 
+	/* If in hibernation, we must not touch the hardware */
+	if (dwc3_hiber_irq_hook(dwc)) {
+		ret = IRQ_HANDLED;
+		goto out;
+	}
+
 	for (i = 0; i < dwc->num_event_buffers; i++) {
 		irqreturn_t status;
 
@@ -2381,7 +2458,7 @@ static irqreturn_t dwc3_interrupt(int irq, void *_dwc)
 		if (status == IRQ_HANDLED)
 			ret = status;
 	}
-
+out:
 	spin_unlock(&dwc->lock);
 
 	return ret;
@@ -2475,20 +2552,28 @@ int __devinit dwc3_gadget_init(struct dwc3 *dwc)
 			DWC3_DEVTEN_CONNECTDONEEN |
 			DWC3_DEVTEN_USBRSTEN |
 			DWC3_DEVTEN_DISCONNEVTEN);
+	if (dwc3_hiber_enabled(dwc))
+		reg |= DWC3_DEVTEN_HIBERNATIONREQEVTEN;
 	dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
 
-	/* Enable USB2 LPM and automatic phy suspend only on recent versions */
-	if (dwc->revision >= DWC3_REVISION_194A) {
-		reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+	/* Reset device address to zero */
+	reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+	reg &= ~DWC3_DCFG_DEVADDR_MASK;
+
+	/* Enable USB2 LPM only on recent versions */
+	if (dwc->revision >= DWC3_REVISION_194A)
 		reg |= DWC3_DCFG_LPM_CAP;
-		dwc3_writel(dwc->regs, DWC3_DCFG, reg);
 
+	dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+	/* Enable USB2 LPM and automatic phy suspend only on recent versions */
+	if (dwc->revision >= DWC3_REVISION_194A) {
 		reg = dwc3_readl(dwc->regs, DWC3_DCTL);
 		reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN);
-
 		/* TODO: This should be configurable */
 		reg |= DWC3_DCTL_HIRD_THRES(31);
-
+		if (dwc3_hiber_enabled(dwc))
+			reg |= DWC3_DCTL_L1_HIBER_EN;
 		dwc3_writel(dwc->regs, DWC3_DCTL, reg);
 
 		dwc3_gadget_usb2_suspend_phy(dwc, true);
diff --git a/drivers/usb/dwc3/haps-pwrctl.c b/drivers/usb/dwc3/haps-pwrctl.c
new file mode 100644
index 0000000..91c95f6
--- /dev/null
+++ b/drivers/usb/dwc3/haps-pwrctl.c
@@ -0,0 +1,200 @@
+/**
+ * haps-pwrctl.c - DesignWare USB3 DRD Controller Power Control for HAPS
+ *
+ * Copyright (C) 2011-2012 Synopsys Incorporated
+ *
+ * Author: Paul Zimmerman <paulz@xxxxxxxxxxxx>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+
+#include "core.h"
+#include "hibernate.h"
+#include "io.h"
+
+#define DWC3_HAPS_GASKET_POWER_CTL		0x80004
+#define DWC3_HAPS_GASKET_POWER_DSTATE_MASK	0x3000
+#define DWC3_HAPS_GASKET_POWER_DSTATE_D3	0x3000
+#define DWC3_HAPS_GASKET_POWER_DSTATE_D0	0x0000
+
+#define DWC3_HAPS_GASKET_DEBUG			0x80010
+#define DWC3_HAPS_GASKET_DEBUG_USB_CONN_MASK	0x88
+#define DWC3_HAPS_GASKET_DEBUG_PME_INTR_MASK	0x44
+#define DWC3_HAPS_GASKET_DEBUG_DSTATE_MASK	0x33
+#define DWC3_HAPS_GASKET_DEBUG_DSTATE_D3	0x33
+#define DWC3_HAPS_GASKET_DEBUG_DSTATE_D0	0x00
+
+/**
+ * dwc3_haps_power_ctl - platform-specific power control routine, for HAPS board
+ *
+ * @dwc: pointer to our controller context structure
+ * @on: 1 to turn the power on, 0 to turn it off
+ */
+void dwc3_haps_power_ctl(struct dwc3 *dwc, int on)
+{
+	u32 reg;
+	int cnt = 0;
+
+	if (on) {
+		/*
+		 * Enable core well power - this is "faked" by the FPGA
+		 */
+
+		/*
+		 * Communicate with power controller to set power state to D0
+		 */
+
+		reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL);
+		dev_vdbg(dwc->dev, "R1=0x%08x before write\n", reg);
+
+		if ((reg & DWC3_HAPS_GASKET_POWER_DSTATE_MASK) ==
+					DWC3_HAPS_GASKET_DEBUG_DSTATE_D0)
+			/* Already in D0 */
+			return;
+
+		reg = (reg & ~DWC3_HAPS_GASKET_POWER_DSTATE_MASK) |
+				DWC3_HAPS_GASKET_POWER_DSTATE_D0;
+		dwc3_writel(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL, reg);
+		reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL);
+		dev_vdbg(dwc->dev, "R1=0x%08x after write\n", reg);
+
+		/*
+		 * Wait until both PMUs confirm that they have entered D0
+		 */
+
+		dev_vdbg(dwc->dev, "Asked for D0 state, awaiting response\n");
+
+		do {
+			udelay(1);
+			reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_DEBUG);
+			if (++cnt > 10000000) {
+				cnt = 0;
+				dev_vdbg(dwc->dev, "0x%08x\n", reg);
+			}
+		} while ((reg & DWC3_HAPS_GASKET_DEBUG_DSTATE_MASK) !=
+				DWC3_HAPS_GASKET_DEBUG_DSTATE_D0);
+
+		dev_info(dwc->dev, "In D0\n");
+	} else {
+		/*
+		 * Communicate with power controller to set power state to D3
+		 */
+
+		reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL);
+		dev_vdbg(dwc->dev, "R1=0x%08x before write\n", reg);
+
+		if ((reg & DWC3_HAPS_GASKET_POWER_DSTATE_MASK) ==
+					DWC3_HAPS_GASKET_DEBUG_DSTATE_D3)
+			/* Already in D3 */
+			return;
+
+		reg = (reg & ~DWC3_HAPS_GASKET_POWER_DSTATE_MASK) |
+				DWC3_HAPS_GASKET_POWER_DSTATE_D3;
+		dwc3_writel(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL, reg);
+		reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL);
+		dev_vdbg(dwc->dev, "R1=0x%08x after write\n", reg);
+
+		/*
+		 * Wait until both PMUs confirm that they have entered D3
+		 */
+
+		dev_vdbg(dwc->dev, "Asked for D3 state, awaiting response\n");
+
+		do {
+			udelay(1);
+			reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_DEBUG);
+			if (++cnt >= 10000000) {
+				cnt = 0;
+				dev_vdbg(dwc->dev, "0x%08x\n", reg);
+			}
+		} while ((reg & DWC3_HAPS_GASKET_DEBUG_DSTATE_MASK) !=
+				DWC3_HAPS_GASKET_DEBUG_DSTATE_D3);
+
+		dev_info(dwc->dev, "In D3\n");
+
+		/*
+		 * Disable core well power - this is "faked" by the FPGA
+		 */
+	}
+}
+
+/**
+ * dwc3_haps_irq_hook - platform-specific interrupt handling, for HAPS board
+ *
+ * If this routine returns false, the caller should handle the interrupt
+ * as usual.
+ *
+ * If this routine returns true, the caller should exit the interrupt handler
+ * immediately, without touching the dwc3 registers.
+ *
+ * @dwc: pointer to our controller context structure
+ */
+bool dwc3_haps_irq_hook(struct dwc3 *dwc)
+{
+	u32 reg;
+	int ret;
+
+	if (!dwc->is_haps)
+		return false;
+
+	reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_POWER_CTL);
+	if ((reg & DWC3_HAPS_GASKET_POWER_DSTATE_MASK) ==
+				DWC3_HAPS_GASKET_DEBUG_DSTATE_D0)
+		/* Already in D0 */
+		return false;
+
+	reg = dwc3_readl(dwc->regs, DWC3_HAPS_GASKET_DEBUG);
+	dev_vdbg(dwc->dev, "HAPS debug reg=0x%08x\n", reg);
+
+	/* Is there a PME interrupt? */
+	if ((reg & DWC3_HAPS_GASKET_DEBUG_PME_INTR_MASK) != 0) {
+		/* Turn on power */
+		dwc3_haps_power_ctl(dwc, 1);
+
+		/* Start hibernation exit processing */
+		ret = dwc3_exit_hibernation(dwc);
+		if (ret)
+			dev_vdbg(dwc->dev,
+				"dwc3_exit_hibernation() returned %d\n", ret);
+
+		/* Request the runtime_resume callback to get called */
+		ret = pm_request_resume(dwc->dev);
+		dev_vdbg(dwc->dev, "pm_request_resume() returned %d\n", ret);
+
+		return true;
+	}
+
+	return true;
+}
diff --git a/drivers/usb/dwc3/haps-pwrctl.h b/drivers/usb/dwc3/haps-pwrctl.h
new file mode 100644
index 0000000..f71ce1d
--- /dev/null
+++ b/drivers/usb/dwc3/haps-pwrctl.h
@@ -0,0 +1,46 @@
+/**
+ * haps-pwrctl.h - DesignWare USB3 DRD Controller Power Control for HAPS Header
+ *
+ * Copyright (C) 2011-2012 Synopsys Incorporated
+ *
+ * Author: Paul Zimmerman <paulz@xxxxxxxxxxxx>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "core.h"
+
+#ifndef __DRIVERS_USB_DWC3_HAPS_PWRCTL_H
+#define __DRIVERS_USB_DWC3_HAPS_PWRCTL_H
+
+void dwc3_haps_power_ctl(struct dwc3 *dwc, int on);
+bool dwc3_haps_irq_hook(struct dwc3 *dwc);
+
+#endif /* __DRIVERS_USB_DWC3_HAPS_PWRCTL_H */
diff --git a/drivers/usb/dwc3/hibernate.c b/drivers/usb/dwc3/hibernate.c
new file mode 100644
index 0000000..2322259
--- /dev/null
+++ b/drivers/usb/dwc3/hibernate.c
@@ -0,0 +1,930 @@
+/**
+ * hibernate.c - DesignWare USB3 DRD Controller Hibernation Support
+ *
+ * Copyright (C) 2011-2012 Synopsys Incorporated
+ *
+ * Author: Paul Zimmerman <paulz@xxxxxxxxxxxx>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/kernel.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/list.h>
+#include <linux/dma-mapping.h>
+
+#include "core.h"
+#include "gadget.h"
+#include "hibernate.h"
+#include "haps-pwrctl.h"
+#include "io.h"
+
+/**
+ * dwc3_alloc_scratchpad_buffers - allocate the buffers needed for saving the
+ * controller's internal state during hibernation
+ *
+ * @dwc: pointer to our controller context structure
+ */
+int dwc3_alloc_scratchpad_buffers(struct dwc3 *dwc)
+{
+	dma_addr_t	dma_adr;
+	int		hiberbufs, i;
+
+	if (!dwc3_hiber_enabled(dwc))
+		return 0;
+
+	hiberbufs = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(dwc->hwparams.hwparams4);
+	if (hiberbufs) {
+		/* Allocate scratchpad buffer address array */
+		dwc->scratchpad_array = dma_alloc_coherent(NULL,
+			sizeof(*dwc->scratchpad_array),
+			&dwc->scratchpad_array_dma, GFP_KERNEL);
+		if (!dwc->scratchpad_array)
+			goto err;
+	}
+
+	/* Allocate scratchpad buffers */
+	for (i = 0; i < hiberbufs; i++) {
+		dwc->scratchpad[i] = dma_alloc_coherent(NULL, 4096, &dma_adr,
+							GFP_KERNEL);
+		if (!dwc->scratchpad[i]) {
+			while (--i >= 0) {
+				dma_adr = (dma_addr_t)
+					dwc->scratchpad_array->dma_adr[i];
+				dma_free_coherent(NULL, 4096,
+					dwc->scratchpad[i], dma_adr);
+				dwc->scratchpad[i] = NULL;
+			}
+
+			goto err_free;
+		}
+
+		dwc->scratchpad_array->dma_adr[i] = cpu_to_le64(dma_adr);
+	}
+
+	return 0;
+
+err_free:
+	dma_free_coherent(NULL, sizeof(*dwc->scratchpad_array),
+			dwc->scratchpad_array, dwc->scratchpad_array_dma);
+	dwc->scratchpad_array = NULL;
+err:
+	return -ENOMEM;
+}
+
+/**
+ * dwc3_free_scratchpad_buffers - free the buffers allocated by
+ * dwc3_alloc_scratchpad_buffers
+ *
+ * @dwc: pointer to our controller context structure
+ */
+void dwc3_free_scratchpad_buffers(struct dwc3 *dwc)
+{
+	dma_addr_t	dma_adr;
+	int		hiberbufs, i;
+
+	if (!dwc3_hiber_enabled(dwc))
+		return;
+
+	hiberbufs = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(dwc->hwparams.hwparams4);
+	for (i = 0; i < hiberbufs; i++) {
+		if (dwc->scratchpad[i] != NULL) {
+			dma_adr = (dma_addr_t)
+				dwc->scratchpad_array->dma_adr[i];
+			dma_free_coherent(NULL, 4096, dwc->scratchpad[i],
+					dma_adr);
+			dwc->scratchpad[i] = NULL;
+		}
+	}
+
+	if (dwc->scratchpad_array) {
+		dma_free_coherent(NULL, sizeof(*dwc->scratchpad_array),
+			dwc->scratchpad_array, dwc->scratchpad_array_dma);
+		dwc->scratchpad_array = NULL;
+	}
+}
+
+/**
+ * dwc3_set_scratchpad_buf_array - invoke the SET_SCRATCHPAD_ADDR commands
+ * to register the scratchpad buffers with the controller
+ *
+ * @dwc: pointer to our controller context structure
+ */
+static void dwc3_set_scratchpad_buf_array(struct dwc3 *dwc)
+{
+	unsigned	cmd;
+	u32		param;
+	int		ret;
+
+	cmd = DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO;
+	param = lower_32_bits(dwc->scratchpad_array_dma);
+	ret = dwc3_send_gadget_dev_cmd(dwc, cmd, param);
+	WARN_ON_ONCE(ret);
+
+	cmd = DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI;
+	param = upper_32_bits(dwc->scratchpad_array_dma);
+	ret = dwc3_send_gadget_dev_cmd(dwc, cmd, param);
+	WARN_ON_ONCE(ret);
+}
+
+/**
+ * dwc3_save_ep_state - invoke the GETEPSTATE command to save an endpoint's
+ * state before entering hibernation
+ *
+ * @dwc: pointer to our controller context structure
+ * @dep: pointer to the endpoint context structure
+ */
+static void dwc3_save_ep_state(struct dwc3 *dwc, struct dwc3_ep *dep)
+{
+	struct dwc3_gadget_ep_cmd_params	params;
+	u32					cmd;
+	int					ret;
+
+	memset(&params, 0, sizeof(params));
+	cmd = DWC3_DEPCMD_GETEPSTATE;
+	ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, &params);
+	if (ret)
+		dev_err(dwc->dev, "failed to get EP state on %s\n", dep->name);
+	else
+		/* State is returned in DEPCMDPAR2 */
+		dep->saved_state = dwc3_readl(dwc->regs,
+					DWC3_DEPCMDPAR2(dep->number));
+}
+
+/**
+ * dwc3_save_reg_state - save the values of any registers whose runtime state
+ * must be preserved during hibernation
+ *
+ * Read the D* registers (DCTL, DCFG, DEVTEN) and G* registers
+ * (GSBUSCFG0/1, GCTL, GTXTHRCFG, GRXTHRCFG, GTXFIFOSIZn,
+ * GRXFIFOSIZ0, GUSB3PIPECTL0, GUSB2PHYCFG0) and save their
+ * state. Any of these registers whose runtime state will be
+ * restored by the normal initialization sequence do not need
+ * to be saved.
+ *
+ * @dwc: pointer to our controller context structure
+ */
+static void dwc3_save_reg_state(struct dwc3 *dwc)
+{
+	struct dwc3_ep	*dep;
+	u32		reg;
+	int		epnum;
+
+	dev_vdbg(dwc->dev, "%s()\n", __func__);
+
+	dwc->dcfg_save = dwc3_readl(dwc->regs, DWC3_DCFG);
+	dev_vdbg(dwc->dev, "DCFG=%08x\n", dwc->dcfg_save);
+	dwc->dctl_save = dwc3_readl(dwc->regs, DWC3_DCTL);
+	dwc->gctl_save = dwc3_readl(dwc->regs, DWC3_GCTL);
+
+	for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+		dep = dwc->eps[epnum];
+		if (!(epnum & 1))
+			continue;
+		if (!(dep->flags & DWC3_EP_ENABLED))
+			continue;
+		reg = dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1));
+		dwc->txfifosz_save[epnum] = reg;
+	}
+
+	dwc->rxfifosz_save = dwc3_readl(dwc->regs, DWC3_GRXFIFOSIZ(0));
+}
+
+/**
+ * dwc3_restart_xfer - restart a transfer on an endpoint if it had a transfer
+ * active when hibernation was entered
+ *
+ * When software issued the EndXfer command with the ForceRM field set
+ * to '0' prior to entering hibernation, the core may have written back
+ * an active TRB for the transfer, setting the HWO bit to '0'. Software
+ * must ensure that the TRB is valid and set the HWO bit back to '1'
+ * prior to re-starting the transfer in this step.
+ *
+ * @dwc: pointer to our controller context structure
+ * @dep: pointer to the endpoint context structure
+ */
+static void dwc3_restart_xfer(struct dwc3 *dwc, struct dwc3_ep *dep)
+{
+	struct dwc3_gadget_ep_cmd_params	params;
+	struct dwc3_trb				*trb;
+	dma_addr_t				trb_dma;
+	u32					cmd;
+	int					i, owned, ret;
+
+	dev_vdbg(dwc->dev, "%s()\n", __func__);
+	trb = dep->trb_pool;
+
+	/* Find the first non-hw-owned TRB */
+	for (i = 0; i < DWC3_TRB_NUM; i++, trb++) {
+		if (!(trb->ctrl & DWC3_TRB_CTRL_HWO) &&
+				!(DWC3_TRB_SIZE_TRBSTS(trb->size) &
+						DWC3_TRB_STS_XFER_IN_PROG)) {
+			dev_vdbg(dwc->dev, "Found non-hw-owned TRB at %d\n", i);
+			break;
+		}
+	}
+
+	if (i == DWC3_TRB_NUM)
+		trb = dep->trb_pool;
+
+	/*
+	 * Find the next TRB that is hw-owned. This should be the one that was
+	 * active when hibernation was entered.
+	 */
+	for (i = 0, owned = -1; i < DWC3_TRB_NUM; i++) {
+		/*
+		 * If status == 4, this TRB's xfer was in progress prior to
+		 * entering hibernation
+		 */
+		if (DWC3_TRB_SIZE_TRBSTS(trb->size) &
+				DWC3_TRB_STS_XFER_IN_PROG) {
+			dev_vdbg(dwc->dev, "Found in-progress TRB at %d\n", i);
+
+			/* Set HWO back to 1 so the xfer can continue */
+			trb->ctrl |= DWC3_TRB_CTRL_HWO;
+			owned = trb - dep->trb_pool;
+			break;
+		}
+
+		/* Save the index of the first TRB with the HWO bit set */
+		if (trb->ctrl & DWC3_TRB_CTRL_HWO) {
+			dev_vdbg(dwc->dev, "Found hw-owned TRB at %d\n", i);
+			owned = trb - dep->trb_pool;
+			break;
+		}
+
+		trb++;
+		if (trb == dep->trb_pool + DWC3_TRB_NUM)
+			trb = dep->trb_pool;
+	}
+
+	wmb();
+	dep->hiber_trb_idx = 0;
+
+	if (owned < 0)
+		/* No TRB had HWO bit set, fine */
+		return;
+
+	dev_vdbg(dwc->dev, "idx=%d trb=%p\n", owned, trb);
+
+	/* Restart of Isoc EPs is deferred until the host polls the EP */
+	if (usb_endpoint_type(dep->desc) == USB_ENDPOINT_XFER_ISOC) {
+		dev_vdbg(dwc->dev, "Deferring restart until host polls\n");
+		dep->hiber_trb_idx = owned + 1;
+		return;
+	}
+
+	dev_vdbg(dwc->dev, "%08x %08x %08x %08x\n",
+			*((unsigned *)trb), *((unsigned *)trb + 1),
+			*((unsigned *)trb + 2), *((unsigned *)trb + 3));
+
+	/* Now restart at the first TRB with the HWO bit set */
+	trb_dma = dep->trb_pool_dma + owned * 16;
+
+	memset(&params, 0, sizeof(params));
+	params.param0 = upper_32_bits(trb_dma);
+	params.param1 = lower_32_bits(trb_dma);
+
+	cmd = DWC3_DEPCMD_STARTTRANSFER;
+
+	ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, &params);
+	if (ret < 0) {
+		dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n");
+		dep->res_trans_idx = 0;
+		dep->flags &= ~DWC3_EP_BUSY;
+		/*
+		 * FIXME anything else to be done here?
+		 */
+		return;
+	}
+
+	dep->res_trans_idx = dwc3_gadget_ep_get_transfer_index(dwc,
+			dep->number);
+	WARN_ON_ONCE(!dep->res_trans_idx);
+}
+
+/**
+ * dwc3_restore_state - restore the state of the controller as it was before
+ * entering hibernation
+ *
+ * If the reset value of GSBUSCFG0/1 is not correct, write this
+ * register with the desired value.
+ *
+ * Issue a "Set Scratchpad Buffer Array" device generic command
+ * and wait for completion by polling the DGCMD.CmdAct bit.
+ *
+ * Write '1' to DCTL.CRS to start the restore process and wait
+ * for completion by polling the DSTS.RSS bit.
+ *
+ * Restore the remaining D* and G* registers.
+ *
+ * @dwc: pointer to our controller context structure
+ */
+static void dwc3_restore_state(struct dwc3 *dwc)
+{
+	struct dwc3_ep	*dep;
+	u32		reg;
+	int		epnum;
+
+	dev_vdbg(dwc->dev, "%s()\n", __func__);
+
+	dwc3_set_scratchpad_buf_array(dwc);
+
+	reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+	dev_vdbg(dwc->dev, "DSTS before=%1x\n", reg);
+	reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+	dev_vdbg(dwc->dev, "DCTL before=%1x\n", reg);
+	reg |= DWC3_DCTL_CRS;
+	dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+	reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+	dev_vdbg(dwc->dev, "DCTL after=%1x\n", reg);
+
+	dev_vdbg(dwc->dev, "Set CRS bit, waiting for RSS bit clear\n");
+
+	do {
+		udelay(1);
+		reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+	} while (reg & DWC3_DSTS_RSS);
+
+	dev_vdbg(dwc->dev, "DSTS after=%1x\n", reg);
+
+	dwc3_writel(dwc->regs, DWC3_GCTL, dwc->gctl_save);
+	dwc3_writel(dwc->regs, DWC3_DCFG, dwc->dcfg_save);
+	dev_vdbg(dwc->dev, "DCFG=%08x\n", dwc->dcfg_save);
+	reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+	dev_vdbg(dwc->dev, "DCFG read back %08x\n", reg);
+	dwc3_writel(dwc->regs, DWC3_DCTL, dwc->dctl_save);
+
+	for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+		dep = dwc->eps[epnum];
+		if (!(epnum & 1))
+			continue;
+		if (!(dep->flags & DWC3_EP_ENABLED))
+			continue;
+		reg = dwc->txfifosz_save[epnum];
+		dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(epnum >> 1), reg);
+	}
+
+	dwc3_writel(dwc->regs, DWC3_GRXFIFOSIZ(0), dwc->rxfifosz_save);
+}
+
+/**
+ * dwc3_reinit_ctlr - reinitialize the controller after exiting hibernation
+ *
+ * Configure the core as described in databook Section 9.1.1 "Device
+ * Power-On or Soft Reset," excluding the first step (Soft Reset).
+ *
+ * @dwc: pointer to our controller context structure
+ * @restore_state: true if the controller's state should be restored
+ */
+static void dwc3_reinit_ctlr(struct dwc3 *dwc, bool restore_state)
+{
+	struct dwc3_ep	*dep;
+	u32		reg;
+	int		ret;
+
+	dev_vdbg(dwc->dev, "%s()\n", __func__);
+
+	dwc3_event_buffers_setup(dwc);
+
+	reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
+	reg |= DWC3_GUSB3PIPECTL_SUSPHY;
+	dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
+
+	reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
+	reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+	dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
+
+	/* Enable all but Start and End of Frame IRQs */
+	reg = DWC3_DEVTEN_VNDRDEVTSTRCVEDEN |
+		DWC3_DEVTEN_EVNTOVERFLOWEN |
+		DWC3_DEVTEN_CMDCMPLTEN |
+		DWC3_DEVTEN_ERRTICERREN |
+		DWC3_DEVTEN_HIBERNATIONREQEVTEN |
+		DWC3_DEVTEN_WKUPEVTEN |
+		DWC3_DEVTEN_ULSTCNGEN |
+		DWC3_DEVTEN_CONNECTDONEEN |
+		DWC3_DEVTEN_USBRSTEN |
+		DWC3_DEVTEN_DISCONNEVTEN;
+	dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
+
+	dep = dwc->eps[0];
+	dep->flags &= ~(DWC3_EP_ENABLED | DWC3_EP_BUSY);
+	dep->res_trans_idx = 0;
+	dep = dwc->eps[1];
+	dep->flags &= ~DWC3_EP_ENABLED;
+
+	ret = __dwc3_gadget_start(dwc, restore_state);
+	WARN_ON_ONCE(ret);
+
+	dwc3_gadget_run_stop(dwc, 1);
+}
+
+/**
+ * dwc3_enter_hibernation - put the controller into hibernation
+ *
+ * This routine sends the core into hibernation, saving the core's runtime
+ * state if requested. The caller is responsible for turning off power to the
+ * core after this routine returns.
+ *
+ * @dwc: pointer to our controller context structure
+ * @save_state: true if the controller's state should be saved
+ */
+static void dwc3_enter_hibernation(struct dwc3 *dwc, bool save_state)
+{
+	struct dwc3_ep	*dep;
+	unsigned long	flags;
+	u32		reg;
+	int		epnum;
+
+	dev_vdbg(dwc->dev, "%s(%d)\n", __func__, save_state);
+
+	spin_lock_irqsave(&dwc->lock, flags);
+	dwc->hiber_wait_u0 = 0;
+
+	/*
+	 * Issue an "End Transfer" command for all active transfers, with the
+	 * ForceRM field set to 0, including the default control endpoint 0.
+	 */
+
+	for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+		dep = dwc->eps[epnum];
+		if (!(dep->flags & DWC3_EP_ENABLED) || !dep->res_trans_idx)
+			continue;
+		dev_vdbg(dwc->dev, "Stop xfer on phys EP%d\n", epnum);
+		dwc3_stop_active_transfer(dwc, epnum, false);
+	}
+
+	if (save_state) {
+		dev_vdbg(dwc->dev, "Saving EP state\n");
+
+		/*
+		 * Issue a "Get Endpoint State" endpoint command for each active
+		 * endpoint, and save the bits that are returned for use after
+		 * coming out of hibernation.
+		 *
+		 * In addition, software must remember if the endpoint is
+		 * currently in a Halted state. The endpoint is in a Halted
+		 * state if software has issued a "Set STALL" command and has
+		 * not issued a "Clear STALL" command.
+		 */
+
+		for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+			dep = dwc->eps[epnum];
+			if (!dep->flags & DWC3_EP_ENABLED)
+				continue;
+
+			dev_vdbg(dwc->dev, "Save state of phys EP%d\n", epnum);
+			dwc3_save_ep_state(dwc, dep);
+		}
+	}
+
+	/*
+	 * Set DCTL.RunStop to 0, DCTL.KeepConnect to 1 (or 0 if disconnected),
+	 * and wait for DSTS.Halted to be set to 1. Software must service any
+	 * events that are generated while it is waiting for Halted to be set
+	 * to 1.
+	 */
+
+	reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+	reg &= ~DWC3_DCTL_RUN_STOP;
+	if (!save_state)
+		reg &= ~DWC3_DCTL_KEEP_CONNECT;
+	dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+	/* We must be able to handle event interrupts while waiting */
+	spin_unlock_irqrestore(&dwc->lock, flags);
+
+	dev_vdbg(dwc->dev, "Cleared Run/Stop bit, waiting for Halted bit\n");
+
+	do {
+		udelay(1);
+		reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+	} while (!(reg & DWC3_DSTS_DEVCTRLHLT));
+
+	spin_lock_irqsave(&dwc->lock, flags);
+
+	if (save_state) {
+		dwc3_save_reg_state(dwc);
+
+		dwc3_set_scratchpad_buf_array(dwc);
+
+		/*
+		 * Set the DCTL.CSS bit and wait for the save state process to
+		 * complete by polling for DSTS.SSS to equal 0.
+		 */
+
+		reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+		reg |= DWC3_DCTL_CSS;
+		dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		dev_vdbg(dwc->dev, "Set CSS bit, waiting for SSS bit clear\n");
+
+		do {
+			udelay(1);
+			reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+		} while (reg & DWC3_DSTS_SSS);
+
+		dev_vdbg(dwc->dev, "DSTS after=%08x\n", reg);
+		spin_lock_irqsave(&dwc->lock, flags);
+	}
+
+	dwc->hibernate = DWC3_HIBER_SLEEPING;
+
+	spin_unlock_irqrestore(&dwc->lock, flags);
+}
+
+/**
+ * dwc3_exit_hiber_after_connect - finish exiting from hibernation once the
+ * device is connected
+ *
+ * @dwc: pointer to our controller context structure
+ * @connected: true if the link is in a connected state
+ */
+static void dwc3_exit_hiber_after_connect(struct dwc3 *dwc, bool connected)
+{
+	struct dwc3_gadget_ep_cmd_params	params;
+	struct dwc3_ep				*dep;
+	u32					reg;
+	int					epnum, ret;
+
+	dev_vdbg(dwc->dev, "%s(%d)\n", __func__, connected);
+
+	/*
+	 * Perform the steps in Section 9.1.5 "Initialization on
+	 * SetConfiguration or SetInterface Command" in databook.
+	 *
+	 * While issuing the DEPCFG commands, set each endpoint's sequence
+	 * number and flow control state to the value read during the save.
+	 *
+	 * If the endpoint was in the Halted state prior to entering
+	 * hibernation, software must issue the "Set STALL" endpoint command
+	 * to put the endpoint back into the Halted state.
+	 */
+
+	memset(&params, 0, sizeof(params));
+
+	for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+		dep = dwc->eps[epnum];
+		if (!(dep->flags & DWC3_EP_ENABLED))
+			continue;
+		if (!dep->desc)
+			continue;
+		dev_vdbg(dwc->dev, "Enabling phys EP%d\n", epnum);
+
+		dep->flags &= ~DWC3_EP_ENABLED;
+		ret = __dwc3_gadget_ep_enable(dep, dep->desc, dep->comp_desc,
+						true);
+		if (ret)
+			dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+
+		if (dep->flags & DWC3_EP_STALL) {
+			ret = dwc3_send_gadget_ep_cmd(dwc, epnum,
+						DWC3_DEPCMD_SETSTALL, &params);
+			if (ret)
+				dev_err(dwc->dev, "failed to set STALL on %s\n",
+						dep->name);
+		}
+	}
+
+	/*
+	 * In this step, software re-configures the existing endpoints and
+	 * starts their transfers.
+	 */
+	for (epnum = 1; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+		dep = dwc->eps[epnum];
+		if (!(dep->flags & DWC3_EP_ENABLED))
+			continue;
+		if (!(dep->flags & DWC3_EP_BUSY) || !dep->res_trans_idx)
+			continue;
+		dwc3_restart_xfer(dwc, dep);
+	}
+
+	/*
+	 * If the core is already connected to the host (DSTS.USBLnkSt == 0, 2,
+	 * 3, 14, or 15), set DCTL.ULStChngReq to '8' as described in Table 7-47
+	 * of databook.
+	 *
+	 * If the host initiated resume, this step completes the transition to
+	 * U0. If the host did not initiate resume, this step causes the device
+	 * to initiate resume (remote wakeup).
+	 */
+
+	if (connected) {
+		dev_vdbg(dwc->dev,
+			"Already connected, requesting link state Recovery\n");
+		dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
+		dwc->hiber_wait_u0 = 1;
+		reg = dwc3_gadget_get_link_state(dwc);
+		dev_vdbg(dwc->dev, "Link state %d\n", reg);
+	} else {
+		dwc->hiber_wait_u0 = 0;
+	}
+}
+
+/**
+ * dwc3_exit_hibernation - wake the controller from hibernation
+ *
+ * Power to the core must be turned on prior to calling this routine.
+ *
+ * @dwc: pointer to our controller context structure
+ */
+int dwc3_exit_hibernation(struct dwc3 *dwc)
+{
+	int		state;
+	int		restore_state = 0;
+
+	dev_vdbg(dwc->dev, "%s()\n", __func__);
+
+	if (!dwc3_hiber_enabled(dwc))
+		return 0;
+
+	dwc->hibernate = DWC3_HIBER_AWAKE;
+
+	/* Read the DSTS register to see the current link state. */
+	state = dwc3_gadget_get_link_state(dwc);
+
+	if (state <= DWC3_LINK_STATE_U3 || state == DWC3_LINK_STATE_RX_DET ||
+			state == DWC3_LINK_STATE_RESUME)
+		restore_state = 1;
+
+	if (restore_state)
+		dwc3_restore_state(dwc);
+
+	dwc3_reinit_ctlr(dwc, restore_state);
+
+	/*
+	 * If the core is not connected to the host, wait for a Connect Done
+	 * event.
+	 */
+	dwc->hiber_wait_connect = 0;
+	if (!restore_state) {
+		if (state != DWC3_LINK_STATE_RESET) {
+			dwc->hiber_wait_connect = 1;
+
+			if (state == DWC3_LINK_STATE_SS_DIS &&
+					dwc->speed != USB_SPEED_SUPER) {
+				dwc->hibernate = DWC3_HIBER_SS_DIS_QUIRK;
+				return DWC3_HIBER_SS_DIS_QUIRK;
+			}
+
+			dev_vdbg(dwc->dev, "Link state %d, waiting for connect"
+				" before exiting from hibernation\n", state);
+
+		/*
+		 * If the DSTS.USBLnkSt equals 14, it means a USB reset was
+		 * received while the core was entering or exiting hibernation.
+		 * Prior to performing the steps in sections 9.1.2 and 9.1.3,
+		 * software must write Resume (8) into the DCTL.ULStChngReq
+		 * field.
+		 */
+		} else {
+			dev_vdbg(dwc->dev,
+				"USB Reset received during hibernation,"
+				" requesting link state Recovery\n");
+			dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
+			dwc->hibernate = DWC3_HIBER_WAIT_LINK_UP;
+			return DWC3_HIBER_WAIT_LINK_UP;
+		}
+	} else {
+		dev_vdbg(dwc->dev, "Link state %d, exiting from hibernation\n",
+				state);
+		/*
+		 * Perform the steps in Section 9.1.3 "Initialization on
+		 * Connect Done" in databook.
+		 */
+		dwc3_gadget_conndone_interrupt(dwc);
+		dwc3_exit_hiber_after_connect(dwc, 1);
+	}
+
+	return 0;
+}
+
+/**
+ * dwc3_wait_for_link_up - wait for up to 100ms for the link to become active
+ * before finishing the exit from hibernation
+ *
+ * @dwc: pointer to our controller context structure
+ */
+static void dwc3_wait_for_link_up(struct dwc3 *dwc)
+{
+	unsigned long	flags;
+	u32		state;
+	int		count = 5;
+
+	while (--count >= 0) {
+		msleep(20);
+		spin_lock_irqsave(&dwc->lock, flags);
+		state = dwc3_gadget_get_link_state(dwc);
+		if (state == 0) {
+			dev_vdbg(dwc->dev, "Exiting from hibernation\n");
+			dwc->hiber_wait_connect = 0;
+
+			/*
+			 * Perform the steps in Section 9.1.3 "Initialization on
+			 * Connect Done" in databook.
+			 */
+			dwc3_gadget_conndone_interrupt(dwc);
+			dwc3_exit_hiber_after_connect(dwc, 0);
+			count = 0;
+		}
+
+		spin_unlock_irqrestore(&dwc->lock, flags);
+	}
+}
+
+/**
+ * dwc3_hiber_poll - hibernation polling routine
+ *
+ * This routine is called from runtime_idle. It checks the dev->hibernate
+ * variable to determine when to put the core into hibernation. dev->hibernate
+ * is set to 1 or 2 from the interrupt handler when the core is requesting to
+ * enter hibernation. This routine will be called when the pm system knows it
+ * is safe to enter hibernation, see that dev->hibernate is 1 or 2, and return
+ * >= 0 to tell the caller to put the core into hibernation. Once in
+ * hibernation, dev->hibernate will be set to 3 (external to this routine) to
+ * tell the rest of the driver that it cannot access the hardware.
+ *
+ * After the core exits hibernation, this routine also handles a couple of
+ * special cases - waiting for the link to come back up if it was reset during
+ * hibernation, and putting the core back into hibernation if it should wake
+ * up while the link is disconnected.
+ *
+ * @dwc: pointer to our controller context structure
+ */
+int dwc3_hiber_poll(struct dwc3 *dwc)
+{
+	unsigned long	flags;
+	u32		state;
+	int		i;
+	int		ret = -EBUSY;
+
+	dev_vdbg(dwc->dev, "%s()\n", __func__);
+
+	if (!dwc3_hiber_enabled(dwc))
+		return ret;
+
+	spin_lock_irqsave(&dwc->lock, flags);
+	state = dwc->hibernate;
+
+	if (state == DWC3_HIBER_ENTER_NOSAVE ||
+			state == DWC3_HIBER_ENTER_SAVE) {
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		ret = (state == DWC3_HIBER_ENTER_SAVE);
+		goto out;
+	}
+
+	if (state == DWC3_HIBER_WAIT_LINK_UP) {
+		dwc->hibernate = DWC3_HIBER_AWAKE;
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		dwc3_wait_for_link_up(dwc);
+		goto out;
+	}
+
+	if (state != DWC3_HIBER_SS_DIS_QUIRK)
+		goto locked_out;
+
+	for (i = 0; i < 25; i++) {
+		if (dwc->hibernate != DWC3_HIBER_SS_DIS_QUIRK) {
+			dev_info(dwc->dev, "breaking loop after %d ms\n", i);
+			goto locked_out;
+		}
+
+		spin_unlock_irqrestore(&dwc->lock, flags);
+		msleep(20);
+		spin_lock_irqsave(&dwc->lock, flags);
+	}
+
+	if (dwc->hibernate == DWC3_HIBER_SS_DIS_QUIRK) {
+		if (dwc3_gadget_get_link_state(dwc) == DWC3_LINK_STATE_SS_DIS) {
+			dwc->hibernate = DWC3_HIBER_ENTER_NOSAVE;
+			spin_unlock_irqrestore(&dwc->lock, flags);
+			ret = 0;
+			goto out;
+		}
+
+		dwc->hibernate = DWC3_HIBER_AWAKE;
+	}
+
+locked_out:
+	spin_unlock_irqrestore(&dwc->lock, flags);
+out:
+	dev_vdbg(dwc->dev, "returning %d\n", ret);
+	return ret;
+}
+
+/**
+ * dwc3_try_enter_hibernation - called from the main part of the driver, to put
+ * the controller into hibernation if the conditions are right
+ *
+ * @dwc: pointer to our controller context structure
+ */
+bool dwc3_try_enter_hibernation(struct dwc3 *dwc)
+{
+	u32 state = dwc->hibernate;
+
+	if (state == DWC3_HIBER_ENTER_NOSAVE ||
+			state == DWC3_HIBER_ENTER_SAVE) {
+		dev_vdbg(dwc->dev, "entering hibernation\n");
+		dwc3_enter_hibernation(dwc, (state == DWC3_HIBER_ENTER_SAVE));
+		if (dwc->is_haps)
+			/* Turn off power */
+			dwc3_haps_power_ctl(dwc, 0);
+		return true;
+	}
+
+	return false;
+}
+
+/**
+ * dwc3_try_exit_hiber_after_connect - called from the connect-done interrupt
+ * handler to finish exiting from hibernation
+ *
+ * @dwc: pointer to our controller context structure
+ */
+void dwc3_try_exit_hiber_after_connect(struct dwc3 *dwc)
+{
+	if (dwc->hiber_wait_connect) {
+		dev_vdbg(dwc->dev, "Hibernation wait connect\n");
+		dwc->hiber_wait_connect = 0;
+		dwc3_exit_hiber_after_connect(dwc, 0);
+	}
+}
+
+/**
+ * dwc3_hiber_remote_wake - send a remote wakeup to the host
+ *
+ * @dwc: pointer to our controller context structure
+ */
+void dwc3_hiber_remote_wake(struct dwc3 *dwc)
+{
+	if (dwc3_hiber_enabled(dwc) && dwc->hiber_wait_u0) {
+		if (dwc3_gadget_get_link_state(dwc) == DWC3_LINK_STATE_U0) {
+			/* TODO: Handle remote wakeup */
+			dwc->hiber_wait_u0 = 0;
+		}
+	}
+}
+
+/**
+ * dwc3_hiber_irq_hook - hibernation-specific interrupt handling
+ *
+ * If this routine returns false, the caller should handle the interrupt
+ * as usual.
+ *
+ * If this routine returns true, the caller should exit the interrupt handler
+ * immediately, without touching the dwc3 registers.
+ *
+ * @dwc: pointer to our controller context structure
+ */
+bool dwc3_hiber_irq_hook(struct dwc3 *dwc)
+{
+	/*
+	 * HAPS platform needs an additional hook here due to its
+	 * non-standard PME interrupt implementation
+	 */
+	if (dwc3_haps_irq_hook(dwc))
+		return true;
+
+	if (dwc->hibernate < DWC3_HIBER_SLEEPING)
+		return false;
+
+	if (dwc->hibernate == DWC3_HIBER_SS_DIS_QUIRK) {
+		dwc->hibernate = DWC3_HIBER_AWAKE;
+		return false;
+	}
+
+	return true;
+}
diff --git a/drivers/usb/dwc3/hibernate.h b/drivers/usb/dwc3/hibernate.h
new file mode 100644
index 0000000..4dcf898
--- /dev/null
+++ b/drivers/usb/dwc3/hibernate.h
@@ -0,0 +1,112 @@
+/**
+ * hibernate.h - DesignWare USB3 DRD Controller Hibernation Support Header
+ *
+ * Copyright (C) 2011-2012 Synopsys Incorporated
+ *
+ * Author: Paul Zimmerman <paulz@xxxxxxxxxxxx>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions, and the following disclaimer,
+ *    without modification.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The names of the above-listed copyright holders may not be used
+ *    to endorse or promote products derived from this software without
+ *    specific prior written permission.
+ *
+ * ALTERNATIVELY, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2, as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __DRIVERS_USB_DWC3_HIBERNATE_H
+#define __DRIVERS_USB_DWC3_HIBERNATE_H
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/spinlock.h>
+
+#ifdef CONFIG_USB_DWC3_HIBERNATION
+
+static inline bool dwc3_hiber_enabled(struct dwc3 *dwc)
+{
+	return DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1) ==
+		DWC3_GHWPARAMS1_EN_PWROPT_HIB;
+}
+
+static inline bool dwc3_request_enter_hiber(struct dwc3 *dwc, bool save)
+{
+	if (dwc3_hiber_enabled(dwc)) {
+		if (save)
+			dwc->hibernate = DWC3_HIBER_ENTER_SAVE;
+		else
+			dwc->hibernate = DWC3_HIBER_ENTER_NOSAVE;
+		return true;
+	}
+
+	return false;
+}
+
+static inline bool dwc3_in_hibernation(struct dwc3 *dwc)
+{
+	return dwc->hibernate >= DWC3_HIBER_SLEEPING &&
+		dwc->hibernate < DWC3_HIBER_WAIT_LINK_UP;
+}
+
+static inline void dwc3_wait_if_hibernating(struct dwc3 *dwc)
+{
+	do {
+		int temp = dwc->hibernate;
+
+		if (temp == DWC3_HIBER_AWAKE)
+			break;
+		local_irq_enable();
+		msleep(20);
+		local_irq_disable();
+	} while (true);
+}
+
+bool dwc3_hiber_irq_hook(struct dwc3 *dwc);
+int dwc3_hiber_poll(struct dwc3 *dwc);
+int dwc3_alloc_scratchpad_buffers(struct dwc3 *dwc);
+void dwc3_free_scratchpad_buffers(struct dwc3 *dwc);
+void dwc3_hiber_remote_wake(struct dwc3 *dwc);
+bool dwc3_try_enter_hibernation(struct dwc3 *dwc);
+int dwc3_exit_hibernation(struct dwc3 *dwc);
+void dwc3_try_exit_hiber_after_connect(struct dwc3 *dwc);
+
+#else
+
+static inline bool dwc3_hiber_enabled(struct dwc3 *d)	{ return false; }
+static inline bool dwc3_request_enter_hiber(struct dwc3 *d, bool s)
+	{ return false; }
+static inline bool dwc3_in_hibernation(struct dwc3 *d)	{ return false; }
+static inline void dwc3_wait_if_hibernating(struct dwc3 *d)		{}
+
+static inline bool dwc3_hiber_irq_hook(struct dwc3 *d)	{ return false; }
+static inline int dwc3_hiber_poll(struct dwc3 *d)	{ return -EBUSY; }
+static inline int dwc3_alloc_scratchpad_buffers(struct dwc3 *d)	{ return 0; }
+static inline void dwc3_free_scratchpad_buffers(struct dwc3 *d)		{}
+static inline void dwc3_hiber_remote_wake(struct dwc3 *d)		{}
+static inline bool dwc3_try_enter_hibernation(struct dwc3 *d) { return false; }
+static inline int dwc3_exit_hibernation(struct dwc3 *d)	{ return 0; }
+static inline void dwc3_try_exit_hiber_after_connect(struct dwc3 *d)	{}
+
+#endif
+#endif /* __DRIVERS_USB_DWC3_HIBERNATE_H */
-- 
1.7.4.4

--
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