summaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorSascha Hauer <s.hauer@pengutronix.de>2021-01-19 05:41:44 +0100
committerSascha Hauer <s.hauer@pengutronix.de>2021-01-19 05:41:44 +0100
commit2ea982543a2570af2f7629e08bb6d3ff28b07e3a (patch)
tree6e19d0758567a6536517eee1b1e28c6de234d33b /drivers/usb
parent5fb76871a15151cb725b2d11814f4236429a8634 (diff)
parente93d7d696e9675056775a0314ad1b57f81dd33d2 (diff)
downloadbarebox-2ea982543a2570af2f7629e08bb6d3ff28b07e3a.tar.gz
Merge branch 'for-next/usb'
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/dwc2/core.c26
-rw-r--r--drivers/usb/dwc2/core.h3
-rw-r--r--drivers/usb/dwc2/dwc2.c57
-rw-r--r--drivers/usb/dwc2/dwc2.h7
-rw-r--r--drivers/usb/dwc2/gadget.c144
5 files changed, 216 insertions, 21 deletions
diff --git a/drivers/usb/dwc2/core.c b/drivers/usb/dwc2/core.c
index c4a3cc7..cc5729e 100644
--- a/drivers/usb/dwc2/core.c
+++ b/drivers/usb/dwc2/core.c
@@ -28,7 +28,7 @@ static bool dwc2_hw_is_device(struct dwc2 *dwc2)
(op_mode == GHWCFG2_OP_MODE_NO_SRP_CAPABLE_DEVICE);
}
-void dwc2_set_param_otg_cap(struct dwc2 *dwc2)
+static void dwc2_set_param_otg_cap(struct dwc2 *dwc2)
{
u8 val;
@@ -49,7 +49,7 @@ void dwc2_set_param_otg_cap(struct dwc2 *dwc2)
dwc2->params.otg_cap = val;
}
-void dwc2_set_param_phy_type(struct dwc2 *dwc2)
+static void dwc2_set_param_phy_type(struct dwc2 *dwc2)
{
u8 val;
@@ -69,7 +69,7 @@ void dwc2_set_param_phy_type(struct dwc2 *dwc2)
dwc2->params.phy_type = val;
}
-void dwc2_set_param_speed(struct dwc2 *dwc2)
+static void dwc2_set_param_speed(struct dwc2 *dwc2)
{
if (dwc2->params.phy_type == DWC2_PHY_TYPE_PARAM_FS)
dwc2->params.speed = DWC2_SPEED_PARAM_FULL;
@@ -77,7 +77,7 @@ void dwc2_set_param_speed(struct dwc2 *dwc2)
dwc2->params.speed = DWC2_SPEED_PARAM_HIGH;
}
-void dwc2_set_param_phy_utmi_width(struct dwc2 *dwc2)
+static void dwc2_set_param_phy_utmi_width(struct dwc2 *dwc2)
{
int val;
@@ -185,12 +185,10 @@ void dwc2_set_default_params(struct dwc2 *dwc2)
}
}
-int dwc2_core_snpsid(struct dwc2 *dwc2)
+int dwc2_check_core_version(struct dwc2 *dwc2)
{
struct dwc2_hw_params *hw = &dwc2->hw_params;
- hw->snpsid = dwc2_readl(dwc2, GSNPSID);
-
/*
* Attempt to ensure this device is really a DWC2 Controller.
* Read and verify the GSNPSID register contents. The value should be
@@ -649,18 +647,16 @@ int dwc2_get_dr_mode(struct dwc2 *dwc2)
if (dwc2_hw_is_device(dwc2)) {
dwc2_dbg(dwc2, "Controller is device only\n");
- if (IS_ENABLED(CONFIG_USB_DWC2_HOST)) {
- dwc2_err(dwc2,
- "Controller does not support host mode.\n");
- return -EINVAL;
+ if (!IS_ENABLED(CONFIG_USB_DWC2_GADGET)) {
+ dwc2_err(dwc2, "gadget mode support not compiled in!\n");
+ return -ENOTSUPP;
}
mode = USB_DR_MODE_PERIPHERAL;
} else if (dwc2_hw_is_host(dwc2)) {
dwc2_dbg(dwc2, "Controller is host only\n");
- if (IS_ENABLED(CONFIG_USB_DWC2_GADGET)) {
- dwc2_err(dwc2,
- "Controller does not support device mode.\n");
- return -EINVAL;
+ if (!IS_ENABLED(CONFIG_USB_DWC2_HOST)) {
+ dwc2_err(dwc2, "host mode support not compiled in!\n");
+ return -ENOTSUPP;
}
mode = USB_DR_MODE_HOST;
} else {
diff --git a/drivers/usb/dwc2/core.h b/drivers/usb/dwc2/core.h
index 090ca15..780efb2 100644
--- a/drivers/usb/dwc2/core.h
+++ b/drivers/usb/dwc2/core.h
@@ -466,6 +466,9 @@ struct dwc2 {
struct dwc2_hw_params hw_params;
struct dwc2_core_params params;
+ struct phy *phy; /* optional */
+ struct clk *clk;
+
#ifdef CONFIG_USB_DWC2_HOST
struct usb_host host;
u8 in_data_toggle[MAX_DEVICE][MAX_ENDPOINT];
diff --git a/drivers/usb/dwc2/dwc2.c b/drivers/usb/dwc2/dwc2.c
index 282e675..9893977 100644
--- a/drivers/usb/dwc2/dwc2.c
+++ b/drivers/usb/dwc2/dwc2.c
@@ -12,6 +12,7 @@
#include <errno.h>
#include <driver.h>
#include <linux/clk.h>
+#include <linux/reset.h>
#include "dwc2.h"
@@ -19,6 +20,9 @@ static int dwc2_set_mode(void *ctx, enum usb_dr_mode mode)
{
struct dwc2 *dwc2 = ctx;
int ret = -ENODEV;
+ int oldmode = dwc2->dr_mode;
+
+ dwc2->dr_mode = mode;
if (mode == USB_DR_MODE_HOST || mode == USB_DR_MODE_OTG) {
if (IS_ENABLED(CONFIG_USB_DWC2_HOST))
@@ -33,6 +37,9 @@ static int dwc2_set_mode(void *ctx, enum usb_dr_mode mode)
dwc2_err(dwc2, "Peripheral support not available\n");
}
+ if (ret)
+ dwc2->dr_mode = oldmode;
+
return ret;
}
@@ -50,7 +57,36 @@ static int dwc2_probe(struct device_d *dev)
dwc2->regs = IOMEM(iores->start);
dwc2->dev = dev;
- ret = dwc2_core_snpsid(dwc2);
+ dwc2->clk = clk_get(dev, "otg");
+ if (IS_ERR(dwc2->clk)) {
+ ret = PTR_ERR(dwc2->clk);
+ dev_err(dev, "Failed to get USB clock %d\n", ret);
+ goto release_region;
+ }
+
+ ret = clk_enable(dwc2->clk);
+ if (ret)
+ goto clk_put;
+
+ ret = device_reset_us(dev, 2);
+ if (ret)
+ goto clk_disable;
+
+ dwc2->phy = phy_optional_get(dev, "usb2-phy");
+ if (IS_ERR(dwc2->phy)) {
+ ret = PTR_ERR(dwc2->phy);
+ goto clk_disable;
+ }
+
+ ret = phy_power_on(dwc2->phy);
+ if (ret)
+ goto clk_disable;
+
+ ret = phy_init(dwc2->phy);
+ if (ret)
+ goto phy_power_off;
+
+ ret = dwc2_check_core_version(dwc2);
if (ret)
goto error;
@@ -66,6 +102,8 @@ static int dwc2_probe(struct device_d *dev)
dwc2_get_hwparams(dwc2);
ret = dwc2_get_dr_mode(dwc2);
+ if (ret)
+ goto error;
dwc2_set_default_params(dwc2);
@@ -77,7 +115,21 @@ static int dwc2_probe(struct device_d *dev)
else
ret = dwc2_set_mode(dwc2, dwc2->dr_mode);
+ if (ret)
+ goto error;
+
+ return 0;
error:
+ phy_exit(dwc2->phy);
+phy_power_off:
+ phy_power_off(dwc2->phy);
+clk_disable:
+ clk_disable(dwc2->clk);
+clk_put:
+ clk_put(dwc2->clk);
+release_region:
+ release_region(iores);
+
return ret;
}
@@ -87,6 +139,9 @@ static void dwc2_remove(struct device_d *dev)
dwc2_host_uninit(dwc2);
dwc2_gadget_uninit(dwc2);
+
+ phy_exit(dwc2->phy);
+ phy_power_off(dwc2->phy);
}
static const struct of_device_id dwc2_platform_dt_ids[] = {
diff --git a/drivers/usb/dwc2/dwc2.h b/drivers/usb/dwc2/dwc2.h
index 30ad906..b9fdda8 100644
--- a/drivers/usb/dwc2/dwc2.h
+++ b/drivers/usb/dwc2/dwc2.h
@@ -2,17 +2,14 @@
#include <usb/usb.h>
#include <usb/usb_defs.h>
#include <usb/gadget.h>
+#include <linux/phy/phy.h>
#include "regs.h"
#include "core.h"
/* Core functions */
-void dwc2_set_param_otg_cap(struct dwc2 *dwc2);
-void dwc2_set_param_phy_type(struct dwc2 *dwc2);
-void dwc2_set_param_speed(struct dwc2 *dwc2);
-void dwc2_set_param_phy_utmi_width(struct dwc2 *dwc2);
void dwc2_set_default_params(struct dwc2 *dwc2);
-int dwc2_core_snpsid(struct dwc2 *dwc2);
+int dwc2_check_core_version(struct dwc2 *dwc2);
void dwc2_get_hwparams(struct dwc2 *dwc2);
void dwc2_init_fs_ls_pclk_sel(struct dwc2 *dwc2);
diff --git a/drivers/usb/dwc2/gadget.c b/drivers/usb/dwc2/gadget.c
index aa7447c..b8ec37b 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);