aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/dwc3/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/dwc3/core.c')
-rw-r--r--drivers/usb/dwc3/core.c233
1 files changed, 198 insertions, 35 deletions
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index c91596efa3e8..ab319aaddb6e 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -25,6 +25,7 @@
#include <linux/of.h>
#include <linux/acpi.h>
#include <linux/pinctrl/consumer.h>
+#include <linux/of_address.h>
#include <linux/reset.h>
#include <linux/usb/ch9.h>
@@ -245,6 +246,9 @@ static int dwc3_core_soft_reset(struct dwc3 *dwc)
* XHCI driver will reset the host block. If dwc3 was configured for
* host-only mode, then we can return early.
*/
+ if (dwc->dr_mode == USB_DR_MODE_HOST || dwc->is_hibernated == true)
+ return 0;
+
if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_HOST)
return 0;
@@ -289,7 +293,7 @@ static const struct clk_bulk_data dwc3_core_clks[] = {
*/
static void dwc3_frame_length_adjustment(struct dwc3 *dwc)
{
- u32 reg;
+ u32 reg, gfladj;
u32 dft;
if (dwc->revision < DWC3_REVISION_250A)
@@ -298,13 +302,27 @@ static void dwc3_frame_length_adjustment(struct dwc3 *dwc)
if (dwc->fladj == 0)
return;
+ /* Save the initial DWC3_GFLADJ register value */
reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
+ gfladj = reg;
+
+ if (dwc->refclk_fladj) {
+ if ((reg & DWC3_GFLADJ_REFCLK_FLADJ) !=
+ (dwc->fladj & DWC3_GFLADJ_REFCLK_FLADJ)) {
+ reg &= ~DWC3_GFLADJ_REFCLK_FLADJ;
+ reg |= (dwc->fladj & DWC3_GFLADJ_REFCLK_FLADJ);
+ }
+ }
+
dft = reg & DWC3_GFLADJ_30MHZ_MASK;
if (dft != dwc->fladj) {
reg &= ~DWC3_GFLADJ_30MHZ_MASK;
reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | dwc->fladj;
- dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
}
+
+ /* Update DWC3_GFLADJ if there is any change from initial value */
+ if (reg != gfladj)
+ dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
}
/**
@@ -353,7 +371,7 @@ static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc,
* dwc3_free_event_buffers - frees all allocated event buffers
* @dwc: Pointer to our controller context structure
*/
-static void dwc3_free_event_buffers(struct dwc3 *dwc)
+void dwc3_free_event_buffers(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
@@ -370,7 +388,7 @@ static void dwc3_free_event_buffers(struct dwc3 *dwc)
* Returns 0 on success otherwise negative errno. In the error case, dwc
* may contain some buffers allocated but not all which were requested.
*/
-static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
+int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length)
{
struct dwc3_event_buffer *evt;
@@ -394,6 +412,9 @@ int dwc3_event_buffers_setup(struct dwc3 *dwc)
{
struct dwc3_event_buffer *evt;
+ if (dwc->dr_mode == USB_DR_MODE_HOST)
+ return 0;
+
evt = dwc->ev_buf;
evt->lpos = 0;
dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(0),
@@ -424,26 +445,46 @@ void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc)
{
+ u32 size;
+
+ if (dwc->dr_mode == USB_DR_MODE_HOST)
+ return 0;
+
if (!dwc->has_hibernation)
return 0;
if (!dwc->nr_scratch)
return 0;
- dwc->scratchbuf = kmalloc_array(dwc->nr_scratch,
- DWC3_SCRATCHBUF_SIZE, GFP_KERNEL);
+ /* Allocate only if scratchbuf is NULL */
+ if (dwc->scratchbuf)
+ return 0;
+
+ size = dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE;
+
+ dwc->scratchbuf = kzalloc(size, GFP_KERNEL);
+
if (!dwc->scratchbuf)
return -ENOMEM;
+ dwc->scratch_addr = dma_map_single(dwc->dev, dwc->scratchbuf, size,
+ DMA_BIDIRECTIONAL);
+ if (dma_mapping_error(dwc->dev, dwc->scratch_addr)) {
+ dev_err(dwc->dev, "failed to map scratch buffer\n");
+ return -EFAULT;
+ }
+
return 0;
}
static int dwc3_setup_scratch_buffers(struct dwc3 *dwc)
{
- dma_addr_t scratch_addr;
u32 param;
int ret;
+ if (dwc->dr_mode == USB_DR_MODE_HOST)
+ return 0;
+
if (!dwc->has_hibernation)
return 0;
@@ -451,28 +492,17 @@ static int dwc3_setup_scratch_buffers(struct dwc3 *dwc)
return 0;
/* should never fall here */
- if (!WARN_ON(dwc->scratchbuf))
+ if (WARN_ON(!dwc->scratchbuf))
return 0;
- scratch_addr = dma_map_single(dwc->sysdev, dwc->scratchbuf,
- dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE,
- DMA_BIDIRECTIONAL);
- if (dma_mapping_error(dwc->sysdev, scratch_addr)) {
- dev_err(dwc->sysdev, "failed to map scratch buffer\n");
- ret = -EFAULT;
- goto err0;
- }
-
- dwc->scratch_addr = scratch_addr;
-
- param = lower_32_bits(scratch_addr);
+ param = lower_32_bits(dwc->scratch_addr);
ret = dwc3_send_gadget_generic_command(dwc,
DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO, param);
if (ret < 0)
goto err1;
- param = upper_32_bits(scratch_addr);
+ param = upper_32_bits(dwc->scratch_addr);
ret = dwc3_send_gadget_generic_command(dwc,
DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI, param);
@@ -485,7 +515,6 @@ err1:
dma_unmap_single(dwc->sysdev, dwc->scratch_addr, dwc->nr_scratch *
DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL);
-err0:
return ret;
}
@@ -498,7 +527,7 @@ static void dwc3_free_scratch_buffers(struct dwc3 *dwc)
return;
/* should never fall here */
- if (!WARN_ON(dwc->scratchbuf))
+ if (WARN_ON(!dwc->scratchbuf))
return;
dma_unmap_single(dwc->sysdev, dwc->scratch_addr, dwc->nr_scratch *
@@ -528,6 +557,45 @@ static void dwc3_cache_hwparams(struct dwc3 *dwc)
parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8);
}
+static int dwc3_config_soc_bus(struct dwc3 *dwc)
+{
+ int ret;
+
+ /*
+ * Check if CCI is enabled for USB. Returns true
+ * if the node has property 'dma-coherent'. Otherwise
+ * returns false.
+ */
+ if (of_dma_is_coherent(dwc->dev->of_node)) {
+ u32 reg;
+
+ reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0);
+ reg |= DWC3_GSBUSCFG0_DATRDREQINFO |
+ DWC3_GSBUSCFG0_DESRDREQINFO |
+ DWC3_GSBUSCFG0_DATWRREQINFO |
+ DWC3_GSBUSCFG0_DESWRREQINFO;
+ dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, reg);
+ }
+
+ /*
+ * This routes the usb dma traffic to go through CCI path instead
+ * of reaching DDR directly. This traffic routing is needed to
+ * to make SMMU and CCI work with USB dma.
+ */
+ if (of_dma_is_coherent(dwc->dev->of_node) || dwc->dev->iommu_group) {
+ ret = dwc3_enable_hw_coherency(dwc->dev);
+ if (ret)
+ return ret;
+ }
+
+ /* Send struct dwc3 to dwc3-of-simple for configuring VBUS
+ * during suspend/resume
+ */
+ dwc3_set_simple_data(dwc);
+
+ return 0;
+}
+
static int dwc3_core_ulpi_init(struct dwc3 *dwc)
{
int intf;
@@ -674,8 +742,6 @@ static int dwc3_phy_setup(struct dwc3 *dwc)
static void dwc3_core_exit(struct dwc3 *dwc)
{
- dwc3_event_buffers_cleanup(dwc);
-
usb_phy_shutdown(dwc->usb2_phy);
usb_phy_shutdown(dwc->usb3_phy);
phy_exit(dwc->usb2_generic_phy);
@@ -743,8 +809,15 @@ static void dwc3_core_setup_global_control(struct dwc3 *dwc)
reg &= ~DWC3_GCTL_DSBLCLKGTNG;
break;
case DWC3_GHWPARAMS1_EN_PWROPT_HIB:
- /* enable hibernation here */
- dwc->nr_scratch = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4);
+ if (!device_property_read_bool(dwc->dev,
+ "snps,enable-hibernation")) {
+ dev_dbg(dwc->dev, "Hibernation not enabled\n");
+ } else {
+ /* enable hibernation here */
+ dwc->nr_scratch =
+ DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4);
+ dwc->has_hibernation = 1;
+ }
/*
* REVISIT Enabling this bit so that host-mode hibernation
@@ -890,7 +963,7 @@ static void dwc3_set_incr_burst_type(struct dwc3 *dwc)
*
* Returns 0 on success otherwise negative errno.
*/
-static int dwc3_core_init(struct dwc3 *dwc)
+int dwc3_core_init(struct dwc3 *dwc)
{
u32 reg;
int ret;
@@ -933,15 +1006,30 @@ static int dwc3_core_init(struct dwc3 *dwc)
dwc3_core_setup_global_control(dwc);
dwc3_core_num_eps(dwc);
+ if (dwc->scratchbuf == NULL) {
+ ret = dwc3_alloc_scratch_buffers(dwc);
+ if (ret) {
+ dev_err(dwc->dev,
+ "Not enough memory for scratch buffers\n");
+ goto err1;
+ }
+ }
+
ret = dwc3_setup_scratch_buffers(dwc);
- if (ret)
+ if (ret) {
+ dev_err(dwc->dev, "Failed to setup scratch buffers: %d\n", ret);
goto err1;
+ }
/* Adjust Frame Length */
dwc3_frame_length_adjustment(dwc);
dwc3_set_incr_burst_type(dwc);
+ ret = dwc3_config_soc_bus(dwc);
+ if (ret)
+ goto err1;
+
usb_phy_set_suspend(dwc->usb2_phy, 0);
usb_phy_set_suspend(dwc->usb3_phy, 0);
ret = phy_power_on(dwc->usb2_generic_phy);
@@ -958,6 +1046,21 @@ static int dwc3_core_init(struct dwc3 *dwc)
goto err4;
}
+ switch (dwc->dr_mode) {
+ case USB_DR_MODE_PERIPHERAL:
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_DEVICE);
+ break;
+ case USB_DR_MODE_HOST:
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_HOST);
+ break;
+ case USB_DR_MODE_OTG:
+ dwc3_set_prtcap(dwc, DWC3_GCTL_PRTCAP_OTG);
+ break;
+ default:
+ dev_warn(dwc->dev, "Unsupported mode %d\n", dwc->dr_mode);
+ break;
+ }
+
/*
* ENDXFER polling is available on version 3.10a and later of
* the DWC_usb3 controller. It is NOT available in the
@@ -969,6 +1072,32 @@ static int dwc3_core_init(struct dwc3 *dwc)
dwc3_writel(dwc->regs, DWC3_GUCTL2, reg);
}
+ /* When configured in HOST mode, after issuing U3/L2 exit controller
+ * fails to send proper CRC checksum in CRC5 feild. Because of this
+ * behaviour Transaction Error is generated, resulting in reset and
+ * re-enumeration of usb device attached. Enabling bit 10 of GUCTL1
+ * will correct this problem
+ */
+ if (dwc->enable_guctl1_resume_quirk) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUCTL1);
+ reg |= DWC3_GUCTL1_RESUME_QUIRK;
+ dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
+ }
+
+ /* SNPS controller when configureed in HOST mode maintains Inter Packet
+ * Delay (IPD) of ~380ns which works with most of the super-speed hubs
+ * except VIA-LAB hubs. When IPD is ~380ns HOST controller fails to
+ * enumerate FS/LS devices when connected behind VIA-LAB hubs.
+ * Enabling bit 9 of GUCTL1 enables the workaround in HW to reduce the
+ * ULPI clock latency by 1 cycle, thus reducing the IPD (~360ns) and
+ * making controller enumerate FS/LS devices connected behind VIA-LAB.
+ */
+ if (dwc->enable_guctl1_ipd_quirk) {
+ reg = dwc3_readl(dwc->regs, DWC3_GUCTL1);
+ reg |= DWC3_GUCTL1_IPD_QUIRK;
+ dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
+ }
+
if (dwc->revision >= DWC3_REVISION_250A) {
reg = dwc3_readl(dwc->regs, DWC3_GUCTL1);
@@ -1178,6 +1307,11 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
dev_err(dev, "failed to initialize dual-role\n");
return ret;
}
+
+#if IS_ENABLED(CONFIG_USB_DWC3_OTG)
+ dwc->current_dr_role = 0;
+ dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
+#endif
break;
default:
dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -1307,6 +1441,12 @@ static void dwc3_get_properties(struct dwc3 *dwc)
device_property_read_u32(dev, "snps,quirk-frame-length-adjustment",
&dwc->fladj);
+ dwc->refclk_fladj = device_property_read_bool(dev,
+ "snps,refclk_fladj");
+ dwc->enable_guctl1_resume_quirk = device_property_read_bool(dev,
+ "snps,enable_guctl1_resume_quirk");
+ dwc->enable_guctl1_ipd_quirk = device_property_read_bool(dev,
+ "snps,enable_guctl1_ipd_quirk");
dwc->dis_metastability_quirk = device_property_read_bool(dev,
"snps,dis_metastability_quirk");
@@ -1316,6 +1456,9 @@ static void dwc3_get_properties(struct dwc3 *dwc)
dwc->hird_threshold = hird_threshold
| (dwc->is_utmi_l1_suspend << 4);
+ /* Check if extra quirks to be added */
+ dwc3_simple_check_quirks(dwc);
+
dwc->rx_thr_num_pkt_prd = rx_thr_num_pkt_prd;
dwc->rx_max_burst_prd = rx_max_burst_prd;
@@ -1388,9 +1531,8 @@ static int dwc3_probe(struct platform_device *pdev)
struct device *dev = &pdev->dev;
struct resource *res, dwc_res;
struct dwc3 *dwc;
-
int ret;
-
+ u32 mdwidth;
void __iomem *regs;
dwc = devm_kzalloc(dev, sizeof(*dwc), GFP_KERNEL);
@@ -1473,6 +1615,11 @@ static int dwc3_probe(struct platform_device *pdev)
spin_lock_init(&dwc->lock);
+ /* Set dma coherent mask to DMA BUS data width */
+ mdwidth = DWC3_GHWPARAMS0_MDWIDTH(dwc->hwparams.hwparams0);
+ dev_dbg(dev, "Enabling %d-bit DMA addresses.\n", mdwidth);
+ dma_set_coherent_mask(dev, DMA_BIT_MASK(mdwidth));
+
pm_runtime_set_active(dev);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_autosuspend_delay(dev, DWC3_DEFAULT_AUTOSUSPEND_DELAY);
@@ -1494,10 +1641,6 @@ static int dwc3_probe(struct platform_device *pdev)
if (ret)
goto err3;
- ret = dwc3_alloc_scratch_buffers(dwc);
- if (ret)
- goto err3;
-
ret = dwc3_core_init(dwc);
if (ret) {
if (ret != -EPROBE_DEFER)
@@ -1554,6 +1697,7 @@ static int dwc3_remove(struct platform_device *pdev)
dwc3_debugfs_exit(dwc);
dwc3_core_exit_mode(dwc);
+ dwc3_event_buffers_cleanup(dwc);
dwc3_core_exit(dwc);
dwc3_ulpi_exit(dwc);
@@ -1655,6 +1799,18 @@ static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
break;
}
+ dwc3_event_buffers_cleanup(dwc);
+
+ /* Put the core into D3 state */
+ dwc3_set_usb_core_power(dwc, false);
+
+ /*
+ * To avoid reinit of phy during resume, prevent calling the
+ * dwc3_core_exit() when in D3 state
+ */
+ if (!dwc->is_d3)
+ dwc3_core_exit(dwc);
+
return 0;
}
@@ -1664,6 +1820,13 @@ static int dwc3_resume_common(struct dwc3 *dwc, pm_message_t msg)
int ret;
u32 reg;
+ /* Bring core to D0 state */
+ dwc3_set_usb_core_power(dwc, true);
+
+ ret = dwc3_core_init(dwc);
+ if (ret)
+ return ret;
+
switch (dwc->current_dr_role) {
case DWC3_GCTL_PRTCAP_DEVICE:
ret = dwc3_core_init_for_resume(dwc);