diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2021-04-26 11:32:23 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2021-04-26 11:32:23 -0700 |
commit | ef1244124349fea36e4a7e260ecaf156b6b6b22a (patch) | |
tree | 92bf1dd4e9fc06708898a6e78de70f7ac36f5a72 /drivers/usb/dwc2 | |
parent | d08410d8c9908058a2f69b55e24edfb0d19da7a1 (diff) | |
parent | caa93d9bd2d7ca7ffe5a23df9f003b81721c8e1b (diff) |
Merge tag 'usb-5.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB and Thunderbolt updates from Greg KH:
"Here is the big set of USB and Thunderbolt driver updates for
5.13-rc1.
Lots of little things in here, with loads of tiny fixes and cleanups
over these drivers, as well as these "larger" changes:
- thunderbolt updates and new features added
- xhci driver updates and split out of a mediatek-specific xhci
driver from the main xhci module to make it easier to work with
(something that I have been wanting for a while).
- loads of typec feature additions and updates
- dwc2 driver updates
- dwc3 driver updates
- gadget driver fixes and minor updates
- loads of usb-serial cleanups and fixes and updates
- usbip documentation updates and fixes
- lots of other tiny USB driver updates
All of these have been in linux-next for a while with no reported
issues"
* tag 'usb-5.13-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (371 commits)
usb: Fix up movement of USB core kerneldoc location
usb: dwc3: gadget: Handle DEV_TXF_FLUSH_BYPASS capability
usb: dwc3: Capture new capability register GHWPARAMS9
usb: gadget: prevent a ternary sign expansion bug
usb: dwc3: core: Do core softreset when switch mode
usb: dwc2: Get rid of useless error checks in suspend interrupt
usb: dwc2: Update dwc2_handle_usb_suspend_intr function.
usb: dwc2: Add exit hibernation mode before removing drive
usb: dwc2: Add hibernation exiting flow by system resume
usb: dwc2: Add hibernation entering flow by system suspend
usb: dwc2: Allow exit hibernation in urb enqueue
usb: dwc2: Move exit hibernation to dwc2_port_resume() function
usb: dwc2: Move enter hibernation to dwc2_port_suspend() function
usb: dwc2: Clear GINTSTS_RESTOREDONE bit after restore is generated.
usb: dwc2: Clear fifo_map when resetting core.
usb: dwc2: Allow exiting hibernation from gpwrdn rst detect
usb: dwc2: Fix hibernation between host and device modes.
usb: dwc2: Fix host mode hibernation exit with remote wakeup flow.
usb: dwc2: Reset DEVADDR after exiting gadget hibernation.
usb: dwc2: Update exit hibernation when port reset is asserted
...
Diffstat (limited to 'drivers/usb/dwc2')
-rw-r--r-- | drivers/usb/dwc2/core.c | 138 | ||||
-rw-r--r-- | drivers/usb/dwc2/core.h | 49 | ||||
-rw-r--r-- | drivers/usb/dwc2/core_intr.c | 279 | ||||
-rw-r--r-- | drivers/usb/dwc2/debugfs.c | 2 | ||||
-rw-r--r-- | drivers/usb/dwc2/gadget.c | 223 | ||||
-rw-r--r-- | drivers/usb/dwc2/hcd.c | 645 | ||||
-rw-r--r-- | drivers/usb/dwc2/hcd_queue.c | 2 | ||||
-rw-r--r-- | drivers/usb/dwc2/hw.h | 1 | ||||
-rw-r--r-- | drivers/usb/dwc2/params.c | 18 | ||||
-rw-r--r-- | drivers/usb/dwc2/platform.c | 36 |
10 files changed, 1029 insertions, 364 deletions
diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index fec17a2d2447..6f70ab9577b4 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -131,54 +131,26 @@ int dwc2_restore_global_registers(struct dwc2_hsotg *hsotg) * dwc2_exit_partial_power_down() - Exit controller from Partial Power Down. * * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. * @restore: Controller registers need to be restored */ -int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore) +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup, + bool restore) { - u32 pcgcctl; - int ret = 0; - - if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL) - return -ENOTSUPP; - - pcgcctl = dwc2_readl(hsotg, PCGCTL); - pcgcctl &= ~PCGCTL_STOPPCLK; - dwc2_writel(hsotg, pcgcctl, PCGCTL); - - pcgcctl = dwc2_readl(hsotg, PCGCTL); - pcgcctl &= ~PCGCTL_PWRCLMP; - dwc2_writel(hsotg, pcgcctl, PCGCTL); - - pcgcctl = dwc2_readl(hsotg, PCGCTL); - pcgcctl &= ~PCGCTL_RSTPDWNMODULE; - dwc2_writel(hsotg, pcgcctl, PCGCTL); + struct dwc2_gregs_backup *gr; - udelay(100); - if (restore) { - ret = dwc2_restore_global_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore registers\n", - __func__); - return ret; - } - if (dwc2_is_host_mode(hsotg)) { - ret = dwc2_restore_host_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore host registers\n", - __func__); - return ret; - } - } else { - ret = dwc2_restore_device_registers(hsotg, 0); - if (ret) { - dev_err(hsotg->dev, "%s: failed to restore device registers\n", - __func__); - return ret; - } - } - } + gr = &hsotg->gr_backup; - return ret; + /* + * Restore host or device regisers with the same mode core enterted + * to partial power down by checking "GOTGCTL_CURMODE_HOST" backup + * value of the "gotgctl" register. + */ + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + return dwc2_host_exit_partial_power_down(hsotg, rem_wakeup, + restore); + else + return dwc2_gadget_exit_partial_power_down(hsotg, restore); } /** @@ -188,57 +160,10 @@ int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore) */ int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg) { - u32 pcgcctl; - int ret = 0; - - if (!hsotg->params.power_down) - return -ENOTSUPP; - - /* Backup all registers */ - ret = dwc2_backup_global_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup global registers\n", - __func__); - return ret; - } - - if (dwc2_is_host_mode(hsotg)) { - ret = dwc2_backup_host_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup host registers\n", - __func__); - return ret; - } - } else { - ret = dwc2_backup_device_registers(hsotg); - if (ret) { - dev_err(hsotg->dev, "%s: failed to backup device registers\n", - __func__); - return ret; - } - } - - /* - * Clear any pending interrupts since dwc2 will not be able to - * clear them after entering partial_power_down. - */ - dwc2_writel(hsotg, 0xffffffff, GINTSTS); - - /* Put the controller in low power state */ - pcgcctl = dwc2_readl(hsotg, PCGCTL); - - pcgcctl |= PCGCTL_PWRCLMP; - dwc2_writel(hsotg, pcgcctl, PCGCTL); - ndelay(20); - - pcgcctl |= PCGCTL_RSTPDWNMODULE; - dwc2_writel(hsotg, pcgcctl, PCGCTL); - ndelay(20); - - pcgcctl |= PCGCTL_STOPPCLK; - dwc2_writel(hsotg, pcgcctl, PCGCTL); - - return ret; + if (dwc2_is_host_mode(hsotg)) + return dwc2_host_enter_partial_power_down(hsotg); + else + return dwc2_gadget_enter_partial_power_down(hsotg); } /** @@ -374,6 +299,12 @@ void dwc2_hib_restore_common(struct dwc2_hsotg *hsotg, int rem_wakeup, __func__); } else { dev_dbg(hsotg->dev, "restore done generated here\n"); + + /* + * To avoid restore done interrupt storm after restore is + * generated clear GINTSTS_RESTOREDONE bit. + */ + dwc2_writel(hsotg, GINTSTS_RESTOREDONE, GINTSTS); } } @@ -460,9 +391,6 @@ static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg) */ int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host) { - if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_HIBERNATION) - return -ENOTSUPP; - if (is_host) return dwc2_host_enter_hibernation(hsotg); else @@ -545,6 +473,22 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait) dwc2_writel(hsotg, greset, GRSTCTL); } + /* + * Switching from device mode to host mode by disconnecting + * device cable core enters and exits form hibernation. + * However, the fifo map remains not cleared. It results + * to a WARNING (WARNING: CPU: 5 PID: 0 at drivers/usb/dwc2/ + * gadget.c:307 dwc2_hsotg_init_fifo+0x12/0x152 [dwc2]) + * if in host mode we disconnect the micro a to b host + * cable. Because core reset occurs. + * To avoid the WARNING, fifo_map should be cleared + * in dwc2_core_reset() function by taking into account configs. + * fifo_map must be cleared only if driver is configured in + * "CONFIG_USB_DWC2_PERIPHERAL" or "CONFIG_USB_DWC2_DUAL_ROLE" + * mode. + */ + dwc2_clear_fifo_map(hsotg); + /* Wait for AHB master IDLE state */ if (dwc2_hsotg_wait_bit_set(hsotg, GRSTCTL, GRSTCTL_AHBIDLE, 10000)) { dev_warn(hsotg->dev, "%s: HANG! AHB Idle timeout GRSTCTL GRSTCTL_AHBIDLE\n", diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h index 7161344c6522..da5ac4a4595b 100644 --- a/drivers/usb/dwc2/core.h +++ b/drivers/usb/dwc2/core.h @@ -38,6 +38,7 @@ #ifndef __DWC2_CORE_H__ #define __DWC2_CORE_H__ +#include <linux/acpi.h> #include <linux/phy/phy.h> #include <linux/regulator/consumer.h> #include <linux/usb/gadget.h> @@ -426,7 +427,7 @@ enum dwc2_ep0_state { * @g_tx_fifo_size: An array of TX fifo sizes in dedicated fifo * mode. Each value corresponds to one EP * starting from EP1 (max 15 values). Sizes are - * in DWORDS with possible values from from + * in DWORDS with possible values from * 16-32768 (default: 256, 256, 256, 256, 768, * 768, 768, 768, 0, 0, 0, 0, 0, 0, 0). * @change_speed_quirk: Change speed configuration to DWC2_SPEED_PARAM_FULL @@ -865,6 +866,8 @@ struct dwc2_hregs_backup { * @gadget_enabled: Peripheral mode sub-driver initialization indicator. * @ll_hw_enabled: Status of low-level hardware resources. * @hibernated: True if core is hibernated + * @in_ppd: True if core is partial power down mode. + * @bus_suspended: True if bus is suspended * @reset_phy_on_wake: Quirk saying that we should assert PHY reset on a * remote wakeup. * @phy_off_for_suspend: Status of whether we turned the PHY off at suspend. @@ -1022,7 +1025,6 @@ struct dwc2_hregs_backup { * a pointer to an array of register definitions, the * array size and the base address where the register bank * is to be found. - * @bus_suspended: True if bus is suspended * @last_frame_num: Number of last frame. Range from 0 to 32768 * @frame_num_array: Used only if CONFIG_USB_DWC2_TRACK_MISSED_SOFS is * defined, for missed SOFs tracking. Array holds that @@ -1060,6 +1062,8 @@ struct dwc2_hsotg { unsigned int gadget_enabled:1; unsigned int ll_hw_enabled:1; unsigned int hibernated:1; + unsigned int in_ppd:1; + bool bus_suspended; unsigned int reset_phy_on_wake:1; unsigned int need_phy_for_wake:1; unsigned int phy_off_for_suspend:1; @@ -1143,7 +1147,6 @@ struct dwc2_hsotg { unsigned long hs_periodic_bitmap[ DIV_ROUND_UP(DWC2_HS_SCHEDULE_US, BITS_PER_LONG)]; u16 periodic_qh_count; - bool bus_suspended; bool new_connection; u16 last_frame_num; @@ -1301,7 +1304,8 @@ static inline bool dwc2_is_hs_iot(struct dwc2_hsotg *hsotg) */ int dwc2_core_reset(struct dwc2_hsotg *hsotg, bool skip_wait); int dwc2_enter_partial_power_down(struct dwc2_hsotg *hsotg); -int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, bool restore); +int dwc2_exit_partial_power_down(struct dwc2_hsotg *hsotg, int rem_wakeup, + bool restore); int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg, int is_host); int dwc2_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, int reset, int is_host); @@ -1339,6 +1343,7 @@ irqreturn_t dwc2_handle_common_intr(int irq, void *dev); /* The device ID match table */ extern const struct of_device_id dwc2_of_match_table[]; +extern const struct acpi_device_id dwc2_acpi_match[]; int dwc2_lowlevel_hw_enable(struct dwc2_hsotg *hsotg); int dwc2_lowlevel_hw_disable(struct dwc2_hsotg *hsotg); @@ -1409,11 +1414,19 @@ int dwc2_restore_device_registers(struct dwc2_hsotg *hsotg, int remote_wakeup); int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg); int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, int reset); +int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore); +void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg); +void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup); int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg); int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg); int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg); void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg); void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg); +static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) +{ hsotg->fifo_map = 0; } #else static inline int dwc2_hsotg_remove(struct dwc2_hsotg *dwc2) { return 0; } @@ -1442,6 +1455,14 @@ static inline int dwc2_gadget_enter_hibernation(struct dwc2_hsotg *hsotg) static inline int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, int reset) { return 0; } +static inline int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore) +{ return 0; } +static inline void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup) {} static inline int dwc2_hsotg_tx_fifo_count(struct dwc2_hsotg *hsotg) { return 0; } static inline int dwc2_hsotg_tx_fifo_total_depth(struct dwc2_hsotg *hsotg) @@ -1450,6 +1471,7 @@ static inline int dwc2_hsotg_tx_fifo_average_depth(struct dwc2_hsotg *hsotg) { return 0; } static inline void dwc2_gadget_init_lpm(struct dwc2_hsotg *hsotg) {} static inline void dwc2_gadget_program_ref_clk(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_clear_fifo_map(struct dwc2_hsotg *hsotg) {} #endif #if IS_ENABLED(CONFIG_USB_DWC2_HOST) || IS_ENABLED(CONFIG_USB_DWC2_DUAL_ROLE) @@ -1459,11 +1481,18 @@ void dwc2_hcd_connect(struct dwc2_hsotg *hsotg); void dwc2_hcd_disconnect(struct dwc2_hsotg *hsotg, bool force); void dwc2_hcd_start(struct dwc2_hsotg *hsotg); int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup); +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex); +int dwc2_port_resume(struct dwc2_hsotg *hsotg); int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg); int dwc2_restore_host_registers(struct dwc2_hsotg *hsotg); int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg); int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, int reset); +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg); +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore); +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg); +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup); bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2); static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) { schedule_work(&hsotg->phy_reset_work); } @@ -1479,6 +1508,10 @@ static inline void dwc2_hcd_start(struct dwc2_hsotg *hsotg) {} static inline void dwc2_hcd_remove(struct dwc2_hsotg *hsotg) {} static inline int dwc2_core_init(struct dwc2_hsotg *hsotg, bool initial_setup) { return 0; } +static inline int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +{ return 0; } +static inline int dwc2_port_resume(struct dwc2_hsotg *hsotg) +{ return 0; } static inline int dwc2_hcd_init(struct dwc2_hsotg *hsotg) { return 0; } static inline int dwc2_backup_host_registers(struct dwc2_hsotg *hsotg) @@ -1490,6 +1523,14 @@ static inline int dwc2_host_enter_hibernation(struct dwc2_hsotg *hsotg) static inline int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, int reset) { return 0; } +static inline int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ return 0; } +static inline int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore) +{ return 0; } +static inline void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) {} +static inline void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, + int rem_wakeup) {} static inline bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2) { return false; } static inline void dwc2_host_schedule_phy_reset(struct dwc2_hsotg *hsotg) {} diff --git a/drivers/usb/dwc2/core_intr.c b/drivers/usb/dwc2/core_intr.c index 55f1d14fc414..a5ab03808da6 100644 --- a/drivers/usb/dwc2/core_intr.c +++ b/drivers/usb/dwc2/core_intr.c @@ -307,6 +307,7 @@ static void dwc2_handle_conn_id_status_change_intr(struct dwc2_hsotg *hsotg) static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) { int ret; + u32 hprt0; /* Clear interrupt */ dwc2_writel(hsotg, GINTSTS_SESSREQINT, GINTSTS); @@ -316,10 +317,18 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) if (dwc2_is_device_mode(hsotg)) { if (hsotg->lx_state == DWC2_L2) { - ret = dwc2_exit_partial_power_down(hsotg, true); - if (ret && (ret != -ENOTSUPP)) - dev_err(hsotg->dev, - "exit power_down failed\n"); + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, + true); + if (ret) + dev_err(hsotg->dev, + "exit power_down failed\n"); + } + + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_gadget_exit_clock_gating(hsotg, 0); } /* @@ -327,6 +336,13 @@ static void dwc2_handle_session_req_intr(struct dwc2_hsotg *hsotg) * established */ dwc2_hsotg_disconnect(hsotg); + } else { + /* Turn on the port power bit. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + /* Connect hcd after port power is set. */ + dwc2_hcd_connect(hsotg); } } @@ -407,32 +423,40 @@ static void dwc2_handle_wakeup_detected_intr(struct dwc2_hsotg *hsotg) dev_dbg(hsotg->dev, "DSTS=0x%0x\n", dwc2_readl(hsotg, DSTS)); if (hsotg->lx_state == DWC2_L2) { - u32 dctl = dwc2_readl(hsotg, DCTL); - - /* Clear Remote Wakeup Signaling */ - dctl &= ~DCTL_RMTWKUPSIG; - dwc2_writel(hsotg, dctl, DCTL); - ret = dwc2_exit_partial_power_down(hsotg, true); - if (ret && (ret != -ENOTSUPP)) - dev_err(hsotg->dev, "exit power_down failed\n"); + if (hsotg->in_ppd) { + u32 dctl = dwc2_readl(hsotg, DCTL); + /* Clear Remote Wakeup Signaling */ + dctl &= ~DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + ret = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + call_gadget(hsotg, resume); + } - /* Change to L0 state */ - hsotg->lx_state = DWC2_L0; - call_gadget(hsotg, resume); + /* Exit gadget mode clock gating. */ + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_gadget_exit_clock_gating(hsotg, 0); } else { /* Change to L0 state */ hsotg->lx_state = DWC2_L0; } } else { - if (hsotg->params.power_down) - return; - - if (hsotg->lx_state != DWC2_L1) { - u32 pcgcctl = dwc2_readl(hsotg, PCGCTL); + if (hsotg->lx_state == DWC2_L2) { + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } - /* Restart the Phy Clock */ - pcgcctl &= ~PCGCTL_STOPPCLK; - dwc2_writel(hsotg, pcgcctl, PCGCTL); + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_host_exit_clock_gating(hsotg, 1); /* * If we've got this quirk then the PHY is stuck upon @@ -508,31 +532,33 @@ static void dwc2_handle_usb_suspend_intr(struct dwc2_hsotg *hsotg) return; } if (dsts & DSTS_SUSPSTS) { - if (hsotg->hw_params.power_optimized) { + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: ret = dwc2_enter_partial_power_down(hsotg); - if (ret) { - if (ret != -ENOTSUPP) - dev_err(hsotg->dev, - "%s: enter partial_power_down failed\n", - __func__); - goto skip_power_saving; - } + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed\n"); udelay(100); /* Ask phy to be suspended */ if (!IS_ERR_OR_NULL(hsotg->uphy)) usb_phy_set_suspend(hsotg->uphy, true); - } - - if (hsotg->hw_params.hibernation) { + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: ret = dwc2_enter_hibernation(hsotg, 0); - if (ret && ret != -ENOTSUPP) + if (ret) dev_err(hsotg->dev, - "%s: enter hibernation failed\n", - __func__); + "enter hibernation failed\n"); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If neither hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + dwc2_gadget_enter_clock_gating(hsotg); } -skip_power_saving: + /* * Change to L2 (suspend) state before releasing * spinlock @@ -652,16 +678,82 @@ static u32 dwc2_read_common_intr(struct dwc2_hsotg *hsotg) return 0; } +/** + * dwc_handle_gpwrdn_disc_det() - Handles the gpwrdn disconnect detect. + * Exits hibernation without restoring registers. + * + * @hsotg: Programming view of DWC_otg controller + * @gpwrdn: GPWRDN register + */ +static inline void dwc_handle_gpwrdn_disc_det(struct dwc2_hsotg *hsotg, + u32 gpwrdn) +{ + u32 gpwrdn_tmp; + + /* Switch-on voltage to the core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Reset core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Disable Power Down Clamp */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Deassert reset core */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp |= GPWRDN_PWRDNRSTN; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + udelay(5); + + /* Disable PMU interrupt */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUINTSEL; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + + /* De-assert Wakeup Logic */ + gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); + gpwrdn_tmp &= ~GPWRDN_PMUACTV; + dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); + + hsotg->hibernated = 0; + hsotg->bus_suspended = 0; + + if (gpwrdn & GPWRDN_IDSTS) { + hsotg->op_state = OTG_STATE_B_PERIPHERAL; + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hsotg_core_init_disconnected(hsotg, false); + dwc2_hsotg_core_connect(hsotg); + } else { + hsotg->op_state = OTG_STATE_A_HOST; + + /* Initialize the Core for Host mode */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_start(hsotg); + } +} + /* * GPWRDN interrupt handler. * * The GPWRDN interrupts are those that occur in both Host and * Device mode while core is in hibernated state. */ -static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) +static int dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) { u32 gpwrdn; int linestate; + int ret = 0; gpwrdn = dwc2_readl(hsotg, GPWRDN); /* clear all interrupt */ @@ -673,93 +765,52 @@ static void dwc2_handle_gpwrdn_intr(struct dwc2_hsotg *hsotg) if ((gpwrdn & GPWRDN_DISCONN_DET) && (gpwrdn & GPWRDN_DISCONN_DET_MSK) && !linestate) { - u32 gpwrdn_tmp; - dev_dbg(hsotg->dev, "%s: GPWRDN_DISCONN_DET\n", __func__); - - /* Switch-on voltage to the core */ - gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); - gpwrdn_tmp &= ~GPWRDN_PWRDNSWTCH; - dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); - udelay(10); - - /* Reset core */ - gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); - gpwrdn_tmp &= ~GPWRDN_PWRDNRSTN; - dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); - udelay(10); - - /* Disable Power Down Clamp */ - gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); - gpwrdn_tmp &= ~GPWRDN_PWRDNCLMP; - dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); - udelay(10); - - /* Deassert reset core */ - gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); - gpwrdn_tmp |= GPWRDN_PWRDNRSTN; - dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); - udelay(10); - - /* Disable PMU interrupt */ - gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); - gpwrdn_tmp &= ~GPWRDN_PMUINTSEL; - dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); - - /* De-assert Wakeup Logic */ - gpwrdn_tmp = dwc2_readl(hsotg, GPWRDN); - gpwrdn_tmp &= ~GPWRDN_PMUACTV; - dwc2_writel(hsotg, gpwrdn_tmp, GPWRDN); - - hsotg->hibernated = 0; - - if (gpwrdn & GPWRDN_IDSTS) { - hsotg->op_state = OTG_STATE_B_PERIPHERAL; - dwc2_core_init(hsotg, false); - dwc2_enable_global_interrupts(hsotg); - dwc2_hsotg_core_init_disconnected(hsotg, false); - dwc2_hsotg_core_connect(hsotg); - } else { - hsotg->op_state = OTG_STATE_A_HOST; - - /* Initialize the Core for Host mode */ - dwc2_core_init(hsotg, false); - dwc2_enable_global_interrupts(hsotg); - dwc2_hcd_start(hsotg); - } - } - - if ((gpwrdn & GPWRDN_LNSTSCHG) && - (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) { + /* + * Call disconnect detect function to exit from + * hibernation + */ + dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn); + } else if ((gpwrdn & GPWRDN_LNSTSCHG) && + (gpwrdn & GPWRDN_LNSTSCHG_MSK) && linestate) { dev_dbg(hsotg->dev, "%s: GPWRDN_LNSTSCHG\n", __func__); if (hsotg->hw_params.hibernation && hsotg->hibernated) { if (gpwrdn & GPWRDN_IDSTS) { - dwc2_exit_hibernation(hsotg, 0, 0, 0); + ret = dwc2_exit_hibernation(hsotg, 0, 0, 0); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); call_gadget(hsotg, resume); } else { - dwc2_exit_hibernation(hsotg, 1, 0, 1); + ret = dwc2_exit_hibernation(hsotg, 1, 0, 1); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); } } - } - if ((gpwrdn & GPWRDN_RST_DET) && (gpwrdn & GPWRDN_RST_DET_MSK)) { + } else if ((gpwrdn & GPWRDN_RST_DET) && + (gpwrdn & GPWRDN_RST_DET_MSK)) { dev_dbg(hsotg->dev, "%s: GPWRDN_RST_DET\n", __func__); - if (!linestate && (gpwrdn & GPWRDN_BSESSVLD)) - dwc2_exit_hibernation(hsotg, 0, 1, 0); - } - if ((gpwrdn & GPWRDN_STS_CHGINT) && - (gpwrdn & GPWRDN_STS_CHGINT_MSK) && linestate) { - dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); - if (hsotg->hw_params.hibernation && - hsotg->hibernated) { - if (gpwrdn & GPWRDN_IDSTS) { - dwc2_exit_hibernation(hsotg, 0, 0, 0); - call_gadget(hsotg, resume); - } else { - dwc2_exit_hibernation(hsotg, 1, 0, 1); - } + if (!linestate) { + ret = dwc2_exit_hibernation(hsotg, 0, 1, 0); + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); } + } else if ((gpwrdn & GPWRDN_STS_CHGINT) && + (gpwrdn & GPWRDN_STS_CHGINT_MSK)) { + dev_dbg(hsotg->dev, "%s: GPWRDN_STS_CHGINT\n", __func__); + /* + * As GPWRDN_STS_CHGINT exit from hibernation flow is + * the same as in GPWRDN_DISCONN_DET flow. Call + * disconnect detect helper function to exit from + * hibernation. + */ + dwc_handle_gpwrdn_disc_det(hsotg, gpwrdn); } + + return ret; } /* diff --git a/drivers/usb/dwc2/debugfs.c b/drivers/usb/dwc2/debugfs.c index aaafd463d72a..f13eed4231e1 100644 --- a/drivers/usb/dwc2/debugfs.c +++ b/drivers/usb/dwc2/debugfs.c @@ -691,6 +691,8 @@ static int params_show(struct seq_file *seq, void *v) print_param(seq, p, ulpi_fs_ls); print_param(seq, p, host_support_fs_ls_low_power); print_param(seq, p, host_ls_low_power_phy_clk); + print_param(seq, p, activate_stm_fs_transceiver); + print_param(seq, p, activate_stm_id_vb_detection); print_param(seq, p, ts_dline); print_param(seq, p, reload_ctl); print_param_hex(seq, p, ahbcfg); diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index ad4c94366dad..e6bb1bdb2760 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -3689,10 +3689,10 @@ irq_retry: dwc2_writel(hsotg, GINTSTS_RESETDET, GINTSTS); /* This event must be used only if controller is suspended */ - if (hsotg->lx_state == DWC2_L2) { - dwc2_exit_partial_power_down(hsotg, true); - hsotg->lx_state = DWC2_L0; - } + if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) + dwc2_exit_partial_power_down(hsotg, 0, true); + + hsotg->lx_state = DWC2_L0; } if (gintsts & (GINTSTS_USBRST | GINTSTS_RESETDET)) { @@ -4615,11 +4615,15 @@ static int dwc2_hsotg_vbus_session(struct usb_gadget *gadget, int is_active) spin_lock_irqsave(&hsotg->lock, flags); /* - * If controller is hibernated, it must exit from power_down - * before being initialized / de-initialized + * If controller is in partial power down state, it must exit from + * that state before being initialized / de-initialized */ - if (hsotg->lx_state == DWC2_L2) - dwc2_exit_partial_power_down(hsotg, false); + if (hsotg->lx_state == DWC2_L2 && hsotg->in_ppd) + /* + * No need to check the return value as + * registers are not being restored. + */ + dwc2_exit_partial_power_down(hsotg, 0, false); if (is_active) { hsotg->op_state = OTG_STATE_B_PERIPHERAL; @@ -5301,6 +5305,10 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, dwc2_writel(hsotg, dr->dcfg, DCFG); dwc2_writel(hsotg, dr->dctl, DCTL); + /* On USB Reset, reset device address to zero */ + if (reset) + dwc2_clear_bit(hsotg, DCFG, DCFG_DEVADDR_MASK); + /* De-assert Wakeup Logic */ gpwrdn = dwc2_readl(hsotg, GPWRDN); gpwrdn &= ~GPWRDN_PMUACTV; @@ -5351,3 +5359,202 @@ int dwc2_gadget_exit_hibernation(struct dwc2_hsotg *hsotg, return ret; } + +/** + * dwc2_gadget_enter_partial_power_down() - Put controller in partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter device partial power down. + * + * This function is for entering device mode partial power down. + */ +int dwc2_gadget_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + u32 pcgcctl; + int ret = 0; + + dev_dbg(hsotg->dev, "Entering device partial power down started.\n"); + + /* Backup all registers */ + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + + ret = dwc2_backup_device_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup device registers\n", + __func__); + return ret; + } + + /* + * Clear any pending interrupts since dwc2 will not be able to + * clear them after entering partial_power_down. + */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Put the controller in low power state */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + + pcgcctl |= PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + /* Set in_ppd flag to 1 as here core enters suspend. */ + hsotg->in_ppd = 1; + hsotg->lx_state = DWC2_L2; + + dev_dbg(hsotg->dev, "Entering device partial power down completed.\n"); + + return ret; +} + +/* + * dwc2_gadget_exit_partial_power_down() - Exit controller from device partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit device partial power down. + * + * This function is for exiting from device mode partial power down. + */ +int dwc2_gadget_exit_partial_power_down(struct dwc2_hsotg *hsotg, + bool restore) +{ + u32 pcgcctl; + u32 dctl; + struct dwc2_dregs_backup *dr; + int ret = 0; + + dr = &hsotg->dr_backup; + + dev_dbg(hsotg->dev, "Exiting device partial Power Down started.\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + udelay(100); + if (restore) { + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + /* Restore DCFG */ + dwc2_writel(hsotg, dr->dcfg, DCFG); + + ret = dwc2_restore_device_registers(hsotg, 0); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore device registers\n", + __func__); + return ret; + } + } + + /* Set the Power-On Programming done bit */ + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_PWRONPRGDONE; + dwc2_writel(hsotg, dctl, DCTL); + + /* Set in_ppd flag to 0 as here core exits from suspend. */ + hsotg->in_ppd = 0; + hsotg->lx_state = DWC2_L0; + + dev_dbg(hsotg->dev, "Exiting device partial Power Down completed.\n"); + return ret; +} + +/** + * dwc2_gadget_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter device partial power down. + * + * This function is for entering device mode clock gating. + */ +void dwc2_gadget_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ + u32 pcgctl; + + dev_dbg(hsotg->dev, "Entering device clock gating.\n"); + + /* Set the Phy Clock bit as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Set the Gate hclk as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + hsotg->lx_state = DWC2_L2; + hsotg->bus_suspended = true; +} + +/* + * dwc2_gadget_exit_clock_gating() - Exit controller from device clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether remote wake up is enabled. + * + * This function is for exiting from device mode clock gating. + */ +void dwc2_gadget_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ + u32 pcgctl; + u32 dctl; + + dev_dbg(hsotg->dev, "Exiting device clock gating.\n"); + + /* Clear the Gate hclk. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Phy Clock bit. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + if (rem_wakeup) { + /* Set Remote Wakeup Signaling */ + dctl = dwc2_readl(hsotg, DCTL); + dctl |= DCTL_RMTWKUPSIG; + dwc2_writel(hsotg, dctl, DCTL); + } + + /* Change to L0 state */ + call_gadget(hsotg, resume); + hsotg->lx_state = DWC2_L0; + hsotg->bus_suspended = false; +} diff --git a/drivers/usb/dwc2/hcd.c b/drivers/usb/dwc2/hcd.c index 1a9789ec5847..035d4911a3c3 100644 --- a/drivers/usb/dwc2/hcd.c +++ b/drivers/usb/dwc2/hcd.c @@ -56,8 +56,6 @@ #include "core.h" #include "hcd.h" -static void dwc2_port_resume(struct dwc2_hsotg *hsotg); - /* * ========================================================================= * Host Core Layer Functions @@ -3208,6 +3206,15 @@ static void dwc2_conn_id_status_change(struct work_struct *work) if (count > 250) dev_err(hsotg->dev, "Connection id status change timed out\n"); + + /* + * Exit Partial Power Down without restoring registers. + * No need to check the return value as registers + * are not being restored. + */ + if (hsotg->in_ppd && hsotg->lx_state == DWC2_L2) + dwc2_exit_partial_power_down(hsotg, 0, false); + hsotg->op_state = OTG_STATE_B_PERIPHERAL; dwc2_core_init(hsotg, false); dwc2_enable_global_interrupts(hsotg); @@ -3277,13 +3284,23 @@ static int dwc2_host_is_b_hnp_enabled(struct dwc2_hsotg *hsotg) return hcd->self.b_hnp_enable; } -/* Must NOT be called with interrupt disabled or spinlock held */ -static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) +/** + * dwc2_port_suspend() - Put controller in suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * @windex: The control request wIndex field + * + * Return: non-zero if failed to enter suspend mode for host. + * + * This function is for entering Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) { unsigned long flags; - u32 hprt0; u32 pcgctl; u32 gotgctl; + int ret = 0; dev_dbg(hsotg->dev, "%s()\n", __func__); @@ -3296,22 +3313,33 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) hsotg->op_state = OTG_STATE_A_SUSPEND; } - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_SUSP; - dwc2_writel(hsotg, hprt0, HPRT0); - - hsotg->bus_suspended = true; - - /* - * If power_down is supported, Phy clock will be suspended - * after registers are backuped. - */ - if (!hsotg->params.power_down) { - /* Suspend the Phy Clock */ - pcgctl = dwc2_readl(hsotg, PCGCTL); - pcgctl |= PCGCTL_STOPPCLK; - dwc2_writel(hsotg, pcgctl, PCGCTL); - udelay(10); + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_enter_partial_power_down(hsotg); + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* + * Perform spin unlock and lock because in + * "dwc2_host_enter_hibernation()" function there is a spinlock + * logic which prevents servicing of any IRQ during entering + * hibernation. + */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_enter_hibernation(hsotg, 1); + if (ret) + dev_err(hsotg->dev, "enter hibernation failed.\n"); + spin_lock_irqsave(&hsotg->lock, flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + dwc2_host_enter_clock_gating(hsotg); + break; } /* For HNP the bus must be suspended for at least 200ms */ @@ -3326,44 +3354,54 @@ static void dwc2_port_suspend(struct dwc2_hsotg *hsotg, u16 windex) } else { spin_unlock_irqrestore(&hsotg->lock, flags); } + + return ret; } -/* Must NOT be called with interrupt disabled or spinlock held */ -static void dwc2_port_resume(struct dwc2_hsotg *hsotg) +/** + * dwc2_port_resume() - Exit controller from suspend mode for host. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to exit suspend mode for host. + * + * This function is for exiting Host mode suspend. + * Must NOT be called with interrupt disabled or spinlock held. + */ +int dwc2_port_resume(struct dwc2_hsotg *hsotg) { unsigned long flags; - u32 hprt0; - u32 pcgctl; + int ret = 0; spin_lock_irqsave(&hsotg->lock, flags); - /* - * If power_down is supported, Phy clock is already resumed - * after registers restore. - */ - if (!hsotg->params.power_down) { - pcgctl = dwc2_readl(hsotg, PCGCTL); - pcgctl &= ~PCGCTL_STOPPCLK; - dwc2_writel(hsotg, pcgctl, PCGCTL); + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* Exit host hibernation. */ + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + if (ret) + dev_err(hsotg->dev, "exit hibernation failed.\n"); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * port resume is done using the clock gating programming flow. + */ spin_unlock_irqrestore(&hsotg->lock, flags); - msleep(20); + dwc2_host_exit_clock_gating(hsotg, 0); spin_lock_irqsave(&hsotg->lock, flags); + break; } - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 |= HPRT0_RES; - hprt0 &= ~HPRT0_SUSP; - dwc2_writel(hsotg, hprt0, HPRT0); spin_unlock_irqrestore(&hsotg->lock, flags); - msleep(USB_RESUME_TIMEOUT); - - spin_lock_irqsave(&hsotg->lock, flags); - hprt0 = dwc2_read_hprt0(hsotg); - hprt0 &= ~(HPRT0_RES | HPRT0_SUSP); - dwc2_writel(hsotg, hprt0, HPRT0); - hsotg->bus_suspended = false; - spin_unlock_irqrestore(&hsotg->lock, flags); + return ret; } /* Handles hub class-specific requests */ @@ -3413,12 +3451,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, dev_dbg(hsotg->dev, "ClearPortFeature USB_PORT_FEAT_SUSPEND\n"); - if (hsotg->bus_suspended) { - if (hsotg->hibernated) - dwc2_exit_hibernation(hsotg, 0, 0, 1); - else - dwc2_port_resume(hsotg); - } + if (hsotg->bus_suspended) + retval = dwc2_port_resume(hsotg); break; case USB_PORT_FEAT_POWER: @@ -3629,10 +3663,8 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, "SetPortFeature - USB_PORT_FEAT_SUSPEND\n"); if (windex != hsotg->otg_port) goto error; - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_HIBERNATION) - dwc2_enter_hibernation(hsotg, 1); - else - dwc2_port_suspend(hsotg, windex); + if (!hsotg->bus_suspended) + retval = dwc2_port_suspend(hsotg, windex); break; case USB_PORT_FEAT_POWER: @@ -3647,12 +3679,30 @@ static int dwc2_hcd_hub_control(struct dwc2_hsotg *hsotg, u16 typereq, break; case USB_PORT_FEAT_RESET: - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_HIBERNATION && - hsotg->hibernated) - dwc2_exit_hibernation(hsotg, 0, 1, 1); - hprt0 = dwc2_read_hprt0(hsotg); dev_dbg(hsotg->dev, "SetPortFeature - USB_PORT_FEAT_RESET\n"); + + hprt0 = dwc2_read_hprt0(hsotg); + + if (hsotg->hibernated) { + retval = dwc2_exit_hibernation(hsotg, 0, 1, 1); + if (retval) + dev_err(hsotg->dev, + "exit hibernation failed\n"); + } + + if (hsotg->in_ppd) { + retval = dwc2_exit_partial_power_down(hsotg, 1, + true); + if (retval) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == + DWC2_POWER_DOWN_PARAM_NONE && hsotg->bus_suspended) + dwc2_host_exit_clock_gating(hsotg, 0); + pcgctl = dwc2_readl(hsotg, PCGCTL); pcgctl &= ~(PCGCTL_ENBL_SLEEP_GATING | PCGCTL_STOPPCLK); dwc2_writel(hsotg, pcgctl, PCGCTL); @@ -4305,8 +4355,6 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd) struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); unsigned long flags; int ret = 0; - u32 hprt0; - u32 pcgctl; spin_lock_irqsave(&hsotg->lock, flags); @@ -4322,47 +4370,51 @@ static int _dwc2_hcd_suspend(struct usb_hcd *hcd) if (hsotg->op_state == OTG_STATE_B_PERIPHERAL) goto unlock; - if (hsotg->params.power_down != DWC2_POWER_DOWN_PARAM_PARTIAL || - hsotg->flags.b.port_connect_status == 0) + if (hsotg->bus_suspended) goto skip_power_saving; - /* - * Drive USB suspend and disable port Power - * if usb bus is not suspended. - */ - if (!hsotg->bus_suspended) { - hprt0 = dwc2_read_hprt0(hsotg); - if (hprt0 & HPRT0_CONNSTS) { - hprt0 |= HPRT0_SUSP; - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) - hprt0 &= ~HPRT0_PWR; - dwc2_writel(hsotg, hprt0, HPRT0); - } - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { - spin_unlock_irqrestore(&hsotg->lock, flags); - dwc2_vbus_supply_exit(hsotg); - spin_lock_irqsave(&hsotg->lock, flags); - } else { - pcgctl = readl(hsotg->regs + PCGCTL); - pcgctl |= PCGCTL_STOPPCLK; - writel(pcgctl, hsotg->regs + PCGCTL); - } - } + if (hsotg->flags.b.port_connect_status == 0) + goto skip_power_saving; - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: /* Enter partial_power_down */ ret = dwc2_enter_partial_power_down(hsotg); - if (ret) { - if (ret != -ENOTSUPP) - dev_err(hsotg->dev, - "enter partial_power_down failed\n"); - goto skip_power_saving; - } + if (ret) + dev_err(hsotg->dev, + "enter partial_power_down failed\n"); + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + /* Enter hibernation */ + spin_unlock_irqrestore(&hsotg->lock, flags); + ret = dwc2_enter_hibernation(hsotg, 1); + if (ret) + dev_err(hsotg->dev, "enter hibernation failed\n"); + spin_lock_irqsave(&hsotg->lock, flags); + + /* After entering suspend, hardware is not accessible */ + clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * clock gating is used to save power. + */ + dwc2_host_enter_clock_gating(hsotg); - /* After entering partial_power_down, hardware is no more accessible */ + /* After entering suspend, hardware is not accessible */ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + default: + goto skip_power_saving; } + spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_exit(hsotg); + spin_lock_irqsave(&hsotg->lock, flags); + /* Ask phy to be suspended */ if (!IS_ERR_OR_NULL(hsotg->uphy)) { spin_unlock_irqrestore(&hsotg->lock, flags); @@ -4382,7 +4434,7 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd) { struct dwc2_hsotg *hsotg = dwc2_hcd_to_hsotg(hcd); unsigned long flags; - u32 pcgctl; + u32 hprt0; int ret = 0; spin_lock_irqsave(&hsotg->lock, flags); @@ -4393,68 +4445,102 @@ static int _dwc2_hcd_resume(struct usb_hcd *hcd) if (hsotg->lx_state != DWC2_L2) goto unlock; - if (hsotg->params.power_down > DWC2_POWER_DOWN_PARAM_PARTIAL) { + hprt0 = dwc2_read_hprt0(hsotg); + + /* + * Added port connection status checking which prevents exiting from + * Partial Power Down mode from _dwc2_hcd_resume() if not in Partial + * Power Down mode. + */ + if (hprt0 & HPRT0_CONNSTS) { hsotg->lx_state = DWC2_L0; goto unlock; } - /* - * Enable power if not already done. - * This must not be spinlocked since duration - * of this call is unknown. - */ - if (!IS_ERR_OR_NULL(hsotg->uphy)) { + switch (hsotg->params.power_down) { + case DWC2_POWER_DOWN_PARAM_PARTIAL: + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_HIBERNATION: + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + if (ret) + dev_err(hsotg->dev, "exit hibernation failed.\n"); + + /* + * Set HW accessible bit before powering on the controller + * since an interrupt may rise. + */ + set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + case DWC2_POWER_DOWN_PARAM_NONE: + /* + * If not hibernation nor partial power down are supported, + * port resume is done using the clock gating programming flow. + */ spin_unlock_irqrestore(&hsotg->lock, flags); - usb_phy_set_suspend(hsotg->uphy, false); + dwc2_host_exit_clock_gating(hsotg, 0); + + /* + * Initialize the Core for Host mode, as after system resume + * the global interrupts are disabled. + */ + dwc2_core_init(hsotg, false); + dwc2_enable_global_interrupts(hsotg); + dwc2_hcd_reinit(hsotg); spin_lock_irqsave(&hsotg->lock, flags); - } - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { /* * Set HW accessible bit before powering on the controller * since an interrupt may rise. */ set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); + break; + default: + hsotg->lx_state = DWC2_L0; + goto unlock; + } + /* Change Root port status, as port status change occurred after resume.*/ + hsotg->flags.b.port_suspend_change = 1; - /* Exit partial_power_down */ - ret = dwc2_exit_partial_power_down(hsotg, true); - if (ret && (ret != -ENOTSUPP)) - dev_err(hsotg->dev, "exit partial_power_down failed\n"); - } else { - pcgctl = readl(hsotg->regs + PCGCTL); - pcgctl &= ~PCGCTL_STOPPCLK; - writel(pcgctl, hsotg->regs + PCGCTL); + /* + * Enable power if not already done. + * This must not be spinlocked since duration + * of this call is unknown. + */ + if (!IS_ERR_OR_NULL(hsotg->uphy)) { + spin_unlock_irqrestore(&hsotg->lock, flags); + usb_phy_set_suspend(hsotg->uphy, false); + spin_lock_irqsave(&hsotg->lock, flags); } - hsotg->lx_state = DWC2_L0; - + /* Enable external vbus supply after resuming the port. */ spin_unlock_irqrestore(&hsotg->lock, flags); + dwc2_vbus_supply_init(hsotg); - if (hsotg->bus_suspended) { - spin_lock_irqsave(&hsotg->lock, flags); - hsotg->flags.b.port_suspend_change = 1; - spin_unlock_irqrestore(&hsotg->lock, flags); - dwc2_port_resume(hsotg); - } else { - if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_PARTIAL) { - dwc2_vbus_supply_init(hsotg); - - /* Wait for controller to correctly update D+/D- level */ - usleep_range(3000, 5000); - } + /* Wait for controller to correctly update D+/D- level */ + usleep_range(3000, 5000); + spin_lock_irqsave(&hsotg->lock, flags); - /* - * Clear Port Enable and Port Status changes. - * Enable Port Power. - */ - dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET | - HPRT0_ENACHG, HPRT0); - /* Wait for controller to detect Port Connect */ - usleep_range(5000, 7000); - } + /* + * Clear Port Enable and Port Status changes. + * Enable Port Power. + */ + dwc2_writel(hsotg, HPRT0_PWR | HPRT0_CONNDET | + HPRT0_ENACHG, HPRT0); - return ret; + /* Wait for controller to detect Port Connect */ + spin_unlock_irqrestore(&hsotg->lock, flags); + usleep_range(5000, 7000); + spin_lock_irqsave(&hsotg->lock, flags); unlock: spin_unlock_irqrestore(&hsotg->lock, flags); @@ -4565,12 +4651,41 @@ static int _dwc2_hcd_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, struct dwc2_qh *qh; bool qh_allocated = false; struct dwc2_qtd *qtd; + struct dwc2_gregs_backup *gr; + + gr = &hsotg->gr_backup; if (dbg_urb(urb)) { dev_vdbg(hsotg->dev, "DWC OTG HCD URB Enqueue\n"); dwc2_dump_urb_info(hcd, urb, "urb_enqueue"); } + if (hsotg->hibernated) { + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + retval = dwc2_exit_hibernation(hsotg, 0, 0, 1); + else + retval = dwc2_exit_hibernation(hsotg, 0, 0, 0); + + if (retval) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + + if (hsotg->in_ppd) { + retval = dwc2_exit_partial_power_down(hsotg, 0, true); + if (retval) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended) { + if (dwc2_is_device_mode(hsotg)) + dwc2_gadget_exit_clock_gating(hsotg, 0); + else + dwc2_host_exit_clock_gating(hsotg, 0); + } + if (!ep) return -EINVAL; @@ -5580,7 +5695,15 @@ int dwc2_host_exit_hibernation(struct dwc2_hsotg *hsotg, int rem_wakeup, return ret; } - dwc2_hcd_rem_wakeup(hsotg); + if (rem_wakeup) { + dwc2_hcd_rem_wakeup(hsotg); + /* + * Change "port_connect_status_change" flag to re-enumerate, + * because after exit from hibernation port connection status + * is not detected. + */ + hsotg->flags.b.port_connect_status_change = 1; + } hsotg->hibernated = 0; hsotg->bus_suspended = 0; @@ -5607,3 +5730,249 @@ bool dwc2_host_can_poweroff_phy(struct dwc2_hsotg *dwc2) /* No reason to keep the PHY powered, so allow poweroff */ return true; } + +/** + * dwc2_host_enter_partial_power_down() - Put controller in partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * + * Return: non-zero if failed to enter host partial power down. + * + * This function is for entering Host mode partial power down. + */ +int dwc2_host_enter_partial_power_down(struct dwc2_hsotg *hsotg) +{ + u32 pcgcctl; + u32 hprt0; + int ret = 0; + + dev_dbg(hsotg->dev, "Entering host partial power down started.\n"); + + /* Put this port in suspend mode. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + /* Wait for the HPRT0.PrtSusp register field to be set */ + if (dwc2_hsotg_wait_bit_set(hsotg, HPRT0, HPRT0_SUSP, 3000)) + dev_warn(hsotg->dev, "Suspend wasn't generated\n"); + + /* Backup all registers */ + ret = dwc2_backup_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup global registers\n", + __func__); + return ret; + } + + ret = dwc2_backup_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to backup host registers\n", + __func__); + return ret; + } + + /* + * Clear any pending interrupts since dwc2 will not be able to + * clear them after entering partial_power_down. + */ + dwc2_writel(hsotg, 0xffffffff, GINTSTS); + + /* Put the controller in low power state */ + pcgcctl = dwc2_readl(hsotg, PCGCTL); + + pcgcctl |= PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + /* Set in_ppd flag to 1 as here core enters suspend. */ + hsotg->in_ppd = 1; + hsotg->lx_state = DWC2_L2; + hsotg->bus_suspended = true; + + dev_dbg(hsotg->dev, "Entering host partial power down completed.\n"); + + return ret; +} + +/* + * dwc2_host_exit_partial_power_down() - Exit controller from host partial + * power down. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by Reset. + * @restore: indicates whether need to restore the registers or not. + * + * Return: non-zero if failed to exit host partial power down. + * + * This function is for exiting from Host mode partial power down. + */ +int dwc2_host_exit_partial_power_down(struct dwc2_hsotg *hsotg, + int rem_wakeup, bool restore) +{ + u32 pcgcctl; + int ret = 0; + u32 hprt0; + + dev_dbg(hsotg->dev, "Exiting host partial power down started.\n"); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_PWRCLMP; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + udelay(5); + + pcgcctl = dwc2_readl(hsotg, PCGCTL); + pcgcctl &= ~PCGCTL_RSTPDWNMODULE; + dwc2_writel(hsotg, pcgcctl, PCGCTL); + + udelay(100); + if (restore) { + ret = dwc2_restore_global_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore registers\n", + __func__); + return ret; + } + + ret = dwc2_restore_host_registers(hsotg); + if (ret) { + dev_err(hsotg->dev, "%s: failed to restore host registers\n", + __func__); + return ret; + } + } + + /* Drive resume signaling and exit suspend mode on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + if (!rem_wakeup) { + /* Stop driveing resume signaling on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + hsotg->bus_suspended = false; + } else { + /* Turn on the port power bit. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_PWR; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Connect hcd. */ + dwc2_hcd_connect(hsotg); + + mod_timer(&hsotg->wkp_timer, + jiffies + msecs_to_jiffies(71)); + } + + /* Set lx_state to and in_ppd to 0 as here core exits from suspend. */ + hsotg->in_ppd = 0; + hsotg->lx_state = DWC2_L0; + + dev_dbg(hsotg->dev, "Exiting host partial power down completed.\n"); + return ret; +} + +/** + * dwc2_host_enter_clock_gating() - Put controller in clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * + * This function is for entering Host mode clock gating. + */ +void dwc2_host_enter_clock_gating(struct dwc2_hsotg *hsotg) +{ + u32 hprt0; + u32 pcgctl; + + dev_dbg(hsotg->dev, "Entering host clock gating.\n"); + + /* Put this port in suspend mode. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + + /* Set the Phy Clock bit as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Set the Gate hclk as suspend is received. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl |= PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + hsotg->bus_suspended = true; + hsotg->lx_state = DWC2_L2; +} + +/** + * dwc2_host_exit_clock_gating() - Exit controller from clock gating. + * + * @hsotg: Programming view of the DWC_otg controller + * @rem_wakeup: indicates whether resume is initiated by remote wakeup + * + * This function is for exiting Host mode clock gating. + */ +void dwc2_host_exit_clock_gating(struct dwc2_hsotg *hsotg, int rem_wakeup) +{ + u32 hprt0; + u32 pcgctl; + + dev_dbg(hsotg->dev, "Exiting host clock gating.\n"); + + /* Clear the Gate hclk. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_GATEHCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Phy Clock bit. */ + pcgctl = dwc2_readl(hsotg, PCGCTL); + pcgctl &= ~PCGCTL_STOPPCLK; + dwc2_writel(hsotg, pcgctl, PCGCTL); + udelay(5); + + /* Drive resume signaling and exit suspend mode on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 |= HPRT0_RES; + hprt0 &= ~HPRT0_SUSP; + dwc2_writel(hsotg, hprt0, HPRT0); + udelay(5); + + if (!rem_wakeup) { + /* In case of port resume need to wait for 40 ms */ + msleep(USB_RESUME_TIMEOUT); + + /* Stop driveing resume signaling on the port. */ + hprt0 = dwc2_read_hprt0(hsotg); + hprt0 &= ~HPRT0_RES; + dwc2_writel(hsotg, hprt0, HPRT0); + + hsotg->bus_suspended = false; + hsotg->lx_state = DWC2_L0; + } else { + mod_timer(&hsotg->wkp_timer, + jiffies + msecs_to_jiffies(71)); + } +} diff --git a/drivers/usb/dwc2/hcd_queue.c b/drivers/usb/dwc2/hcd_queue.c index 68bbac64b753..621a4846bd05 100644 --- a/drivers/usb/dwc2/hcd_queue.c +++ b/drivers/usb/dwc2/hcd_queue.c @@ -59,7 +59,7 @@ #define DWC2_UNRESERVE_DELAY (msecs_to_jiffies(5)) /* If we get a NAK, wait this long before retrying */ -#define DWC2_RETRY_WAIT_DELAY 1*1E6L +#define DWC2_RETRY_WAIT_DELAY (1 * 1E6L) /** * dwc2_periodic_channel_available() - Checks that a channel is available for a diff --git a/drivers/usb/dwc2/hw.h b/drivers/usb/dwc2/hw.h index c3d6dde2aca4..6b16fbf98bc6 100644 --- a/drivers/usb/dwc2/hw.h +++ b/drivers/usb/dwc2/hw.h @@ -44,6 +44,7 @@ #define GOTGCTL_CHIRPEN BIT(27) #define GOTGCTL_MULT_VALID_BC_MASK (0x1f << 22) #define GOTGCTL_MULT_VALID_BC_SHIFT 22 +#define GOTGCTL_CURMODE_HOST BIT(21) #define GOTGCTL_OTGVER BIT(20) #define GOTGCTL_BSESVLD BIT(19) #define GOTGCTL_ASESVLD BIT(18) diff --git a/drivers/usb/dwc2/params.c b/drivers/usb/dwc2/params.c index 92df3d620f7d..7a6089fa81e1 100644 --- a/drivers/usb/dwc2/params.c +++ b/drivers/usb/dwc2/params.c @@ -232,6 +232,12 @@ const struct of_device_id dwc2_of_match_table[] = { }; MODULE_DEVICE_TABLE(of, dwc2_of_match_table); +const struct acpi_device_id dwc2_acpi_match[] = { + { "BCM2848", (kernel_ulong_t)dwc2_set_bcm_params }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, dwc2_acpi_match); + static void dwc2_set_param_otg_cap(struct dwc2_hsotg *hsotg) { u8 val; @@ -866,10 +872,12 @@ int dwc2_get_hwparams(struct dwc2_hsotg *hsotg) return 0; } +typedef void (*set_params_cb)(struct dwc2_hsotg *data); + int dwc2_init_params(struct dwc2_hsotg *hsotg) { const struct of_device_id *match; - void (*set_params)(struct dwc2_hsotg *data); + set_params_cb set_params; dwc2_set_default_params(hsotg); dwc2_get_device_properties(hsotg); @@ -878,6 +886,14 @@ int dwc2_init_params(struct dwc2_hsotg *hsotg) if (match && match->data) { set_params = match->data; set_params(hsotg); + } else { + const struct acpi_device_id *amatch; + + amatch = acpi_match_device(dwc2_acpi_match, hsotg->dev); + if (amatch && amatch->driver_data) { + set_params = (set_params_cb)amatch->driver_data; + set_params(hsotg); + } } dwc2_check_params(hsotg); diff --git a/drivers/usb/dwc2/platform.c b/drivers/usb/dwc2/platform.c index 5f18acac7406..3024785d84cb 100644 --- a/drivers/usb/dwc2/platform.c +++ b/drivers/usb/dwc2/platform.c @@ -316,6 +316,39 @@ static int dwc2_lowlevel_hw_init(struct dwc2_hsotg *hsotg) static int dwc2_driver_remove(struct platform_device *dev) { struct dwc2_hsotg *hsotg = platform_get_drvdata(dev); + struct dwc2_gregs_backup *gr; + int ret = 0; + + gr = &hsotg->gr_backup; + + /* Exit Hibernation when driver is removed. */ + if (hsotg->hibernated) { + if (gr->gotgctl & GOTGCTL_CURMODE_HOST) + ret = dwc2_exit_hibernation(hsotg, 0, 0, 1); + else + ret = dwc2_exit_hibernation(hsotg, 0, 0, 0); + + if (ret) + dev_err(hsotg->dev, + "exit hibernation failed.\n"); + } + + /* Exit Partial Power Down when driver is removed. */ + if (hsotg->in_ppd) { + ret = dwc2_exit_partial_power_down(hsotg, 0, true); + if (ret) + dev_err(hsotg->dev, + "exit partial_power_down failed\n"); + } + + /* Exit clock gating when driver is removed. */ + if (hsotg->params.power_down == DWC2_POWER_DOWN_PARAM_NONE && + hsotg->bus_suspended) { + if (dwc2_is_device_mode(hsotg)) + dwc2_gadget_exit_clock_gating(hsotg, 0); + else + dwc2_host_exit_clock_gating(hsotg, 0); + } dwc2_debugfs_exit(hsotg); if (hsotg->hcd_enabled) @@ -334,7 +367,7 @@ static int dwc2_driver_remove(struct platform_device *dev) reset_control_assert(hsotg->reset); reset_control_assert(hsotg->reset_ecc); - return 0; + return ret; } /** @@ -734,6 +767,7 @@ static struct platform_driver dwc2_platform_driver = { .driver = { .name = dwc2_driver_name, .of_match_table = dwc2_of_match_table, + .acpi_match_table = ACPI_PTR(dwc2_acpi_match), .pm = &dwc2_dev_pm_ops, }, .probe = dwc2_driver_probe, |