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(¶ms, 0, sizeof(params)); + cmd = DWC3_DEPCMD_GETEPSTATE; + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + 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(¶ms, 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, ¶ms); + 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(¶ms, 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, ¶ms); + 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