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