diff options
author | Mark Brown <broonie@kernel.org> | 2016-11-04 12:16:38 -0600 |
---|---|---|
committer | Mark Brown <broonie@kernel.org> | 2016-11-04 12:16:38 -0600 |
commit | cc9b94029e9ef51787af908e9856b1eed314bc00 (patch) | |
tree | 9675310b89d0f6fb1f7bd9423f0638c4ee5226fd /drivers/usb/dwc2/core.c | |
parent | 13bed58ce8748d430a26e353a09b89f9d613a71f (diff) | |
parent | 1b5b42216469b05ef4b5916cb40b127dfab1da88 (diff) |
Merge branch 'topic/error' of git://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator into regulator-fixed
Diffstat (limited to 'drivers/usb/dwc2/core.c')
-rw-r--r-- | drivers/usb/dwc2/core.c | 126 |
1 files changed, 109 insertions, 17 deletions
diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c index 4135a5ff67ca..fa9b26b91507 100644 --- a/drivers/usb/dwc2/core.c +++ b/drivers/usb/dwc2/core.c @@ -238,6 +238,77 @@ int dwc2_enter_hibernation(struct dwc2_hsotg *hsotg) return ret; } +/** + * dwc2_wait_for_mode() - Waits for the controller mode. + * @hsotg: Programming view of the DWC_otg controller. + * @host_mode: If true, waits for host mode, otherwise device mode. + */ +static void dwc2_wait_for_mode(struct dwc2_hsotg *hsotg, + bool host_mode) +{ + ktime_t start; + ktime_t end; + unsigned int timeout = 110; + + dev_vdbg(hsotg->dev, "Waiting for %s mode\n", + host_mode ? "host" : "device"); + + start = ktime_get(); + + while (1) { + s64 ms; + + if (dwc2_is_host_mode(hsotg) == host_mode) { + dev_vdbg(hsotg->dev, "%s mode set\n", + host_mode ? "Host" : "Device"); + break; + } + + end = ktime_get(); + ms = ktime_to_ms(ktime_sub(end, start)); + + if (ms >= (s64)timeout) { + dev_warn(hsotg->dev, "%s: Couldn't set %s mode\n", + __func__, host_mode ? "host" : "device"); + break; + } + + usleep_range(1000, 2000); + } +} + +/** + * dwc2_iddig_filter_enabled() - Returns true if the IDDIG debounce + * filter is enabled. + */ +static bool dwc2_iddig_filter_enabled(struct dwc2_hsotg *hsotg) +{ + u32 gsnpsid; + u32 ghwcfg4; + + if (!dwc2_hw_is_otg(hsotg)) + return false; + + /* Check if core configuration includes the IDDIG filter. */ + ghwcfg4 = dwc2_readl(hsotg->regs + GHWCFG4); + if (!(ghwcfg4 & GHWCFG4_IDDIG_FILT_EN)) + return false; + + /* + * Check if the IDDIG debounce filter is bypassed. Available + * in core version >= 3.10a. + */ + gsnpsid = dwc2_readl(hsotg->regs + GSNPSID); + if (gsnpsid >= DWC2_CORE_REV_3_10a) { + u32 gotgctl = dwc2_readl(hsotg->regs + GOTGCTL); + + if (gotgctl & GOTGCTL_DBNCE_FLTR_BYPASS) + return false; + } + + return true; +} + /* * Do core a soft reset of the core. Be careful with this because it * resets all the internal state machines of the core. @@ -246,9 +317,30 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) { u32 greset; int count = 0; + bool wait_for_host_mode = false; dev_vdbg(hsotg->dev, "%s()\n", __func__); + /* + * If the current mode is host, either due to the force mode + * bit being set (which persists after core reset) or the + * connector id pin, a core soft reset will temporarily reset + * the mode to device. A delay from the IDDIG debounce filter + * will occur before going back to host mode. + * + * Determine whether we will go back into host mode after a + * reset and account for this delay after the reset. + */ + if (dwc2_iddig_filter_enabled(hsotg)) { + u32 gotgctl = dwc2_readl(hsotg->regs + GOTGCTL); + u32 gusbcfg = dwc2_readl(hsotg->regs + GUSBCFG); + + if (!(gotgctl & GOTGCTL_CONID_B) || + (gusbcfg & GUSBCFG_FORCEHOSTMODE)) { + wait_for_host_mode = true; + } + } + /* Core Soft Reset */ greset = dwc2_readl(hsotg->regs + GRSTCTL); greset |= GRSTCTL_CSFTRST; @@ -277,6 +369,9 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) } } while (!(greset & GRSTCTL_AHBIDLE)); + if (wait_for_host_mode) + dwc2_wait_for_mode(hsotg, true); + return 0; } @@ -300,9 +395,9 @@ int dwc2_core_reset(struct dwc2_hsotg *hsotg) * Checks are done in this function to determine whether doing a force * would be valid or not. * - * If a force is done, it requires a 25ms delay to take effect. - * - * Returns true if the mode was forced. + * If a force is done, it requires a IDDIG debounce filter delay if + * the filter is configured and enabled. We poll the current mode of + * the controller to account for this delay. */ static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) { @@ -337,12 +432,18 @@ static bool dwc2_force_mode(struct dwc2_hsotg *hsotg, bool host) gusbcfg |= set; dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - msleep(25); + dwc2_wait_for_mode(hsotg, host); return true; } -/* - * Clears the force mode bits. +/** + * dwc2_clear_force_mode() - Clears the force mode bits. + * + * After clearing the bits, wait up to 100 ms to account for any + * potential IDDIG filter delay. We can't know if we expect this delay + * or not because the value of the connector ID status is affected by + * the force mode. We only need to call this once during probe if + * dr_mode == OTG. */ static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) { @@ -353,11 +454,8 @@ static void dwc2_clear_force_mode(struct dwc2_hsotg *hsotg) gusbcfg &= ~GUSBCFG_FORCEDEVMODE; dwc2_writel(gusbcfg, hsotg->regs + GUSBCFG); - /* - * NOTE: This long sleep is _very_ important, otherwise the core will - * not stay in host mode after a connector ID change! - */ - msleep(25); + if (dwc2_iddig_filter_enabled(hsotg)) + usleep_range(100000, 110000); } /* @@ -380,12 +478,6 @@ void dwc2_force_dr_mode(struct dwc2_hsotg *hsotg) __func__, hsotg->dr_mode); break; } - - /* - * NOTE: This is required for some rockchip soc based - * platforms. - */ - msleep(50); } /* |