summaryrefslogtreecommitdiffstats
path: root/drivers/usb/dwc2/gadget.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc2/gadget.c')
-rw-r--r--drivers/usb/dwc2/gadget.c144
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);