diff options
Diffstat (limited to 'drivers/usb/dwc2/gadget.c')
-rw-r--r-- | drivers/usb/dwc2/gadget.c | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c index aa7447c9b4..b8ec37be78 100644 --- a/drivers/usb/dwc2/gadget.c +++ b/drivers/usb/dwc2/gadget.c @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-2.0+ #include <dma.h> #include <usb/gadget.h> +#include <linux/iopoll.h> #include "dwc2.h" #define to_dwc2 gadget_to_dwc2 @@ -2660,6 +2661,144 @@ static int dwc2_eps_alloc(struct dwc2 *dwc2) return 0; } +/** + * dwc2_wait_for_mode() - Waits for the controller mode. + * @dwc2: 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 *dwc2, bool host_mode) +{ + int val, ret; + + dev_vdbg(dwc2->dev, "Waiting for %s mode\n", + host_mode ? "host" : "device"); + + ret = readx_poll_timeout(dwc2_is_host_mode, dwc2, val, + val == host_mode, 110 * USEC_PER_MSEC); + if (ret) + dev_err(dwc2->dev, "%s: Couldn't set %s mode\n", + __func__, host_mode ? "host" : "device"); + + dev_vdbg(dwc2->dev, "%s mode set\n", + host_mode ? "Host" : "Device"); +} + +/** + * dwc2_iddig_filter_enabled() - Returns true if the IDDIG debounce + * filter is enabled. + * + * @hsotg: Programming view of DWC_otg controller + */ +static bool dwc2_iddig_filter_enabled(struct dwc2 *dwc2) +{ + u32 gsnpsid; + u32 ghwcfg4; + + /* Check if core configuration includes the IDDIG filter. */ + ghwcfg4 = dwc2_readl(dwc2, 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(dwc2, GSNPSID); + if (gsnpsid >= DWC2_CORE_REV_3_10a) { + u32 gotgctl = dwc2_readl(dwc2, GOTGCTL); + + if (gotgctl & GOTGCTL_DBNCE_FLTR_BYPASS) + return false; + } + + return true; +} + +/** + * dwc2_force_mode() - Force the mode of the controller. + * + * Forcing the mode is needed for two cases: + * + * 1) If the dr_mode is set to either HOST or PERIPHERAL we force the + * controller to stay in a particular mode regardless of ID pin + * changes. We do this once during probe. + * + * 2) During probe we want to read reset values of the hw + * configuration registers that are only available in either host or + * device mode. We may need to force the mode if the current mode does + * not allow us to access the register in the mode that we want. + * + * In either case it only makes sense to force the mode if the + * controller hardware is OTG capable. + * + * 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 IDDIG debounce filter delay if + * the filter is configured and enabled. We poll the current mode of + * the controller to account for this delay. + * + * @dwc2: Programming view of DWC_otg controller + * @host: Host mode flag + */ +static void dwc2_force_mode(struct dwc2 *dwc2, bool host) +{ + u32 gusbcfg; + u32 set; + u32 clear; + + dev_dbg(dwc2->dev, "Forcing mode to %s\n", host ? "host" : "device"); + + /* + * If dr_mode is either peripheral or host only, there is no + * need to ever force the mode to the opposite mode. + */ + if (WARN_ON(host && dwc2->dr_mode == USB_DR_MODE_PERIPHERAL)) + return; + + if (WARN_ON(!host && dwc2->dr_mode == USB_DR_MODE_HOST)) + return; + + gusbcfg = dwc2_readl(dwc2, GUSBCFG); + + set = host ? GUSBCFG_FORCEHOSTMODE : GUSBCFG_FORCEDEVMODE; + clear = host ? GUSBCFG_FORCEDEVMODE : GUSBCFG_FORCEHOSTMODE; + + gusbcfg &= ~clear; + gusbcfg |= set; + dwc2_writel(dwc2, gusbcfg, GUSBCFG); + + dwc2_wait_for_mode(dwc2, host); + + return; +} + +/** + * 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. + * + * @dwc2: Programming view of DWC_otg controller + */ +static void dwc2_clear_force_mode(struct dwc2 *dwc2) +{ + u32 gusbcfg; + + dev_dbg(dwc2->dev, "Clearing force mode bits\n"); + + gusbcfg = dwc2_readl(dwc2, GUSBCFG); + gusbcfg &= ~GUSBCFG_FORCEHOSTMODE; + gusbcfg &= ~GUSBCFG_FORCEDEVMODE; + dwc2_writel(dwc2, gusbcfg, GUSBCFG); + + if (dwc2_iddig_filter_enabled(dwc2)) + mdelay(100); +} + int dwc2_gadget_init(struct dwc2 *dwc2) { u32 dctl; @@ -2684,6 +2823,11 @@ int dwc2_gadget_init(struct dwc2 *dwc2) dwc2->gadget.is_otg = (dwc2->dr_mode == USB_DR_MODE_OTG) ? 1 : 0; + if (dwc2->gadget.is_otg) + dwc2_clear_force_mode(dwc2); + else + dwc2_force_mode(dwc2, false); + ret = dwc2_eps_alloc(dwc2); if (ret) { dwc2_err(dwc2, "Endpoints allocation failed: %d\n", ret); |