From fbb9a10fc2691243f5204cbcb0da9aef210a447a Mon Sep 17 00:00:00 2001 From: William Wu Date: Wed, 24 Mar 2021 10:50:55 +0800 Subject: [PATCH] usb: dwc3: improve gadget wakeup from resume signal The dwc3 wakeup and suspend interrupt handler are not perfect, and it can't support usb gadget auto suspend function to save power. For UVC device, the auto suspend function is necessary and helpful. With this patch, it enable DWC3_DEVTEN_EOPFEN by default for software to handle suspend interrupt. And for Rockchip platforms, they usually power down DRAM when system enter deep sleep, so this patch disable the dwc3 irq in dwc3_suspend to avoid dwc3 controller access the DRAM for handling dwc3 event if wakeup from USB Host resume signal. By default, the gadget wakeup from system suspend is disabled. The user can add "wakeup-source" property in DTS dwc3 node to enable it like this: &usbdrd_dwc3 { status = "okay"; wakeup-source; }; Signed-off-by: William Wu Change-Id: Iaf9d642ae1ef6ed12e66a15158706de6d73d5124 --- drivers/usb/dwc3/core.c | 19 +++++++++++++++++++ drivers/usb/dwc3/core.h | 8 ++++++++ drivers/usb/dwc3/gadget.c | 29 ++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 7 deletions(-) diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index d4f2caac39d8..bc0a1114852f 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -1267,6 +1267,9 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) dev_err(dev, "failed to initialize gadget\n"); return ret; } + + if (dwc->uwk_en) + device_init_wakeup(dev, true); break; case USB_DR_MODE_HOST: /* @@ -1317,6 +1320,9 @@ static int dwc3_core_init_mode(struct dwc3 *dwc) dev_err(dev, "failed to initialize dual-role\n"); return ret; } + + if (dwc->uwk_en) + device_init_wakeup(dev, true); break; default: dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); @@ -1460,6 +1466,8 @@ static void dwc3_get_properties(struct dwc3 *dwc) "snps,tx-fifo-resize"); dwc->xhci_warm_reset_on_suspend_quirk = device_property_read_bool(dev, "snps,xhci-warm-reset-on-suspend-quirk"); + dwc->uwk_en = device_property_read_bool(dev, + "wakeup-source"); dwc->lpm_nyet_threshold = lpm_nyet_threshold; dwc->tx_de_emphasis = tx_de_emphasis; @@ -2014,6 +2022,12 @@ static int dwc3_suspend(struct device *dev) struct dwc3 *dwc = dev_get_drvdata(dev); int ret; + if (dwc->uwk_en) { + dwc3_gadget_disable_irq(dwc); + synchronize_irq(dwc->irq_gadget); + return 0; + } + if (pm_runtime_suspended(dwc->dev)) return 0; @@ -2053,6 +2067,11 @@ static int dwc3_resume(struct device *dev) struct dwc3 *dwc = dev_get_drvdata(dev); int ret; + if (dwc->uwk_en) { + dwc3_gadget_enable_irq(dwc); + return 0; + } + if (pm_runtime_suspended(dwc->dev)) return 0; diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index b2a489510336..c98fbfe14478 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -1056,6 +1056,7 @@ struct dwc3_scratchpad_array { * false otherwise. * @en_runtime: true when need runtime PM management. For example, RK3399 need * reset dwc3 and usb3phy to support typec interface. + * @uwk_en: true when enable usb wakeup from host resume signal. * @imod_interval: set the interrupt moderation interval in 250ns * increments or 0 to disable. */ @@ -1251,6 +1252,7 @@ struct dwc3 { unsigned fifo_resize_status:1; unsigned drd_connected:1; unsigned en_runtime:1; + unsigned uwk_en:1; u16 imod_interval; }; @@ -1453,6 +1455,8 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param); +void dwc3_gadget_disable_irq(struct dwc3 *dwc); +void dwc3_gadget_enable_irq(struct dwc3 *dwc); #else static inline int dwc3_gadget_init(struct dwc3 *dwc) { return 0; } @@ -1472,6 +1476,10 @@ static inline int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd, static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, int cmd, u32 param) { return 0; } +static inline void dwc3_gadget_enable_irq(struct dwc3 *dwc) +{ } +static inline void dwc3_gadget_disable_irq(struct dwc3 *dwc) +{ } #endif #if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 534a3bf4c679..bf8d854e93bf 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -2001,6 +2001,7 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc) link_state = DWC3_DSTS_USBLNKST(reg); switch (link_state) { + case DWC3_LINK_STATE_U0: case DWC3_LINK_STATE_RESET: case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */ case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ @@ -2010,6 +2011,15 @@ static int __dwc3_gadget_wakeup(struct dwc3 *dwc) return -EINVAL; } + /* + * dwc3 gadget wakeup from host resume signal + * when the whole system enter suspend. + */ + if (link_state == DWC3_LINK_STATE_U0) { + dwc->link_state = link_state; + return 0; + } + ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV); if (ret < 0) { dev_err(dwc->dev, "failed to put link in Recovery\n"); @@ -2145,7 +2155,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) return ret; } -static void dwc3_gadget_enable_irq(struct dwc3 *dwc) +void dwc3_gadget_enable_irq(struct dwc3 *dwc) { u32 reg; @@ -2157,7 +2167,8 @@ static void dwc3_gadget_enable_irq(struct dwc3 *dwc) DWC3_DEVTEN_WKUPEVTEN | DWC3_DEVTEN_CONNECTDONEEN | DWC3_DEVTEN_USBRSTEN | - DWC3_DEVTEN_DISCONNEVTEN); + DWC3_DEVTEN_DISCONNEVTEN | + DWC3_DEVTEN_EOPFEN); if (dwc->revision < DWC3_REVISION_250A) reg |= DWC3_DEVTEN_ULSTCNGEN; @@ -2165,7 +2176,7 @@ static void dwc3_gadget_enable_irq(struct dwc3 *dwc) dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); } -static void dwc3_gadget_disable_irq(struct dwc3 *dwc) +void dwc3_gadget_disable_irq(struct dwc3 *dwc) { /* mask all interrupts */ dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00); @@ -3296,18 +3307,21 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) */ } -static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc) +static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, unsigned int evtinfo) { + enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; /* * TODO take core out of low power mode when that's * implemented. */ - if (dwc->gadget_driver && dwc->gadget_driver->resume) { + if (dwc->gadget_driver && dwc->gadget_driver->resume && dwc->uwk_en) { spin_unlock(&dwc->lock); dwc->gadget_driver->resume(&dwc->gadget); spin_lock(&dwc->lock); } + + dwc->link_state = next; } static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, @@ -3413,7 +3427,8 @@ static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc, { enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; - if (dwc->link_state != next && next == DWC3_LINK_STATE_U3) + if (dwc->link_state != next && next == DWC3_LINK_STATE_U3 && + dwc->uwk_en) dwc3_suspend_gadget(dwc); dwc->link_state = next; @@ -3460,7 +3475,7 @@ static void dwc3_gadget_interrupt(struct dwc3 *dwc, break; case DWC3_DEVICE_EVENT_WAKEUP: dev_info(dwc->dev, "device wakeup\n"); - dwc3_gadget_wakeup_interrupt(dwc); + dwc3_gadget_wakeup_interrupt(dwc, event->event_info); break; case DWC3_DEVICE_EVENT_HIBER_REQ: if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation,