mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-12-27 13:33:40 +00:00
f150b8d28b
[backport from linux commit d9612c2f0449e24983a8b689603210486a930c90] Enable the undefined length INCR burst type and set INCRx. Different platform may has the different burst size type. In order to get best performance, we need to tune the burst size to one special value, instead of the default value. Signed-off-by: Michael Walle <michael@walle.cc> Reviewed-by: Priyanka Jain <priyanka.jain@nxp.com>
1134 lines
27 KiB
C
1134 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/**
|
|
* core.c - DesignWare USB3 DRD Controller Core file
|
|
*
|
|
* Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com
|
|
*
|
|
* Authors: Felipe Balbi <balbi@ti.com>,
|
|
* Sebastian Andrzej Siewior <bigeasy@linutronix.de>
|
|
*
|
|
* Taken from Linux Kernel v3.19-rc1 (drivers/usb/dwc3/core.c) and ported
|
|
* to uboot.
|
|
*
|
|
* commit cd72f890d2 : usb: dwc3: core: enable phy suspend quirk on non-FPGA
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <cpu_func.h>
|
|
#include <malloc.h>
|
|
#include <dwc3-uboot.h>
|
|
#include <dm/device_compat.h>
|
|
#include <dm/devres.h>
|
|
#include <linux/bug.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/dma-mapping.h>
|
|
#include <linux/err.h>
|
|
#include <linux/ioport.h>
|
|
#include <dm.h>
|
|
#include <generic-phy.h>
|
|
#include <linux/usb/ch9.h>
|
|
#include <linux/usb/gadget.h>
|
|
|
|
#include "core.h"
|
|
#include "gadget.h"
|
|
#include "io.h"
|
|
|
|
#include "linux-compat.h"
|
|
|
|
static LIST_HEAD(dwc3_list);
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
static void dwc3_set_mode(struct dwc3 *dwc, u32 mode)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG));
|
|
reg |= DWC3_GCTL_PRTCAPDIR(mode);
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
|
}
|
|
|
|
/**
|
|
* dwc3_core_soft_reset - Issues core soft reset and PHY reset
|
|
* @dwc: pointer to our context structure
|
|
*/
|
|
static int dwc3_core_soft_reset(struct dwc3 *dwc)
|
|
{
|
|
u32 reg;
|
|
|
|
/* Before Resetting PHY, put Core in Reset */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
reg |= DWC3_GCTL_CORESOFTRESET;
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
|
|
|
/* Assert USB3 PHY reset */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
|
|
reg |= DWC3_GUSB3PIPECTL_PHYSOFTRST;
|
|
dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
|
|
|
|
/* Assert USB2 PHY reset */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
|
reg |= DWC3_GUSB2PHYCFG_PHYSOFTRST;
|
|
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
|
|
|
mdelay(100);
|
|
|
|
/* Clear USB3 PHY reset */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
|
|
reg &= ~DWC3_GUSB3PIPECTL_PHYSOFTRST;
|
|
dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
|
|
|
|
/* Clear USB2 PHY reset */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
|
reg &= ~DWC3_GUSB2PHYCFG_PHYSOFTRST;
|
|
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
|
|
|
mdelay(100);
|
|
|
|
/* After PHYs are stable we can take Core out of reset state */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
reg &= ~DWC3_GCTL_CORESOFTRESET;
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* dwc3_frame_length_adjustment - Adjusts frame length if required
|
|
* @dwc3: Pointer to our controller context structure
|
|
* @fladj: Value of GFLADJ_30MHZ to adjust frame length
|
|
*/
|
|
static void dwc3_frame_length_adjustment(struct dwc3 *dwc, u32 fladj)
|
|
{
|
|
u32 reg;
|
|
|
|
if (dwc->revision < DWC3_REVISION_250A)
|
|
return;
|
|
|
|
if (fladj == 0)
|
|
return;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GFLADJ);
|
|
reg &= ~DWC3_GFLADJ_30MHZ_MASK;
|
|
reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | fladj;
|
|
dwc3_writel(dwc->regs, DWC3_GFLADJ, reg);
|
|
}
|
|
|
|
/**
|
|
* dwc3_free_one_event_buffer - Frees one event buffer
|
|
* @dwc: Pointer to our controller context structure
|
|
* @evt: Pointer to event buffer to be freed
|
|
*/
|
|
static void dwc3_free_one_event_buffer(struct dwc3 *dwc,
|
|
struct dwc3_event_buffer *evt)
|
|
{
|
|
dma_free_coherent(evt->buf);
|
|
}
|
|
|
|
/**
|
|
* dwc3_alloc_one_event_buffer - Allocates one event buffer structure
|
|
* @dwc: Pointer to our controller context structure
|
|
* @length: size of the event buffer
|
|
*
|
|
* Returns a pointer to the allocated event buffer structure on success
|
|
* otherwise ERR_PTR(errno).
|
|
*/
|
|
static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc,
|
|
unsigned length)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = devm_kzalloc((struct udevice *)dwc->dev, sizeof(*evt),
|
|
GFP_KERNEL);
|
|
if (!evt)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
evt->dwc = dwc;
|
|
evt->length = length;
|
|
evt->buf = dma_alloc_coherent(length,
|
|
(unsigned long *)&evt->dma);
|
|
if (!evt->buf)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
dwc3_flush_cache((uintptr_t)evt->buf, evt->length);
|
|
|
|
return evt;
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
int i;
|
|
|
|
for (i = 0; i < dwc->num_event_buffers; i++) {
|
|
evt = dwc->ev_buffs[i];
|
|
if (evt)
|
|
dwc3_free_one_event_buffer(dwc, evt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dwc3_alloc_event_buffers - Allocates @num event buffers of size @length
|
|
* @dwc: pointer to our controller context structure
|
|
* @length: size of event buffer
|
|
*
|
|
* 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 num;
|
|
int i;
|
|
|
|
num = DWC3_NUM_INT(dwc->hwparams.hwparams1);
|
|
dwc->num_event_buffers = num;
|
|
|
|
dwc->ev_buffs = memalign(CONFIG_SYS_CACHELINE_SIZE,
|
|
sizeof(*dwc->ev_buffs) * num);
|
|
if (!dwc->ev_buffs)
|
|
return -ENOMEM;
|
|
|
|
for (i = 0; i < num; i++) {
|
|
struct dwc3_event_buffer *evt;
|
|
|
|
evt = dwc3_alloc_one_event_buffer(dwc, length);
|
|
if (IS_ERR(evt)) {
|
|
dev_err(dwc->dev, "can't allocate event buffer\n");
|
|
return PTR_ERR(evt);
|
|
}
|
|
dwc->ev_buffs[i] = evt;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* dwc3_event_buffers_setup - setup our allocated event buffers
|
|
* @dwc: pointer to our controller context structure
|
|
*
|
|
* Returns 0 on success otherwise negative errno.
|
|
*/
|
|
static int dwc3_event_buffers_setup(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
int n;
|
|
|
|
for (n = 0; n < dwc->num_event_buffers; n++) {
|
|
evt = dwc->ev_buffs[n];
|
|
dev_dbg(dwc->dev, "Event buf %p dma %08llx length %d\n",
|
|
evt->buf, (unsigned long long) evt->dma,
|
|
evt->length);
|
|
|
|
evt->lpos = 0;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n),
|
|
lower_32_bits(evt->dma));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n),
|
|
upper_32_bits(evt->dma));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n),
|
|
DWC3_GEVNTSIZ_SIZE(evt->length));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwc3_event_buffers_cleanup(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_event_buffer *evt;
|
|
int n;
|
|
|
|
for (n = 0; n < dwc->num_event_buffers; n++) {
|
|
evt = dwc->ev_buffs[n];
|
|
|
|
evt->lpos = 0;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n), 0);
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), 0);
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), DWC3_GEVNTSIZ_INTMASK
|
|
| DWC3_GEVNTSIZ_SIZE(0));
|
|
dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0);
|
|
}
|
|
}
|
|
|
|
static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc)
|
|
{
|
|
if (!dwc->has_hibernation)
|
|
return 0;
|
|
|
|
if (!dwc->nr_scratch)
|
|
return 0;
|
|
|
|
dwc->scratchbuf = kmalloc_array(dwc->nr_scratch,
|
|
DWC3_SCRATCHBUF_SIZE, GFP_KERNEL);
|
|
if (!dwc->scratchbuf)
|
|
return -ENOMEM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int dwc3_setup_scratch_buffers(struct dwc3 *dwc)
|
|
{
|
|
dma_addr_t scratch_addr;
|
|
u32 param;
|
|
int ret;
|
|
|
|
if (!dwc->has_hibernation)
|
|
return 0;
|
|
|
|
if (!dwc->nr_scratch)
|
|
return 0;
|
|
|
|
scratch_addr = dma_map_single(dwc->scratchbuf,
|
|
dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE,
|
|
DMA_BIDIRECTIONAL);
|
|
if (dma_mapping_error(dwc->dev, scratch_addr)) {
|
|
dev_err(dwc->dev, "failed to map scratch buffer\n");
|
|
ret = -EFAULT;
|
|
goto err0;
|
|
}
|
|
|
|
dwc->scratch_addr = scratch_addr;
|
|
|
|
param = lower_32_bits(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);
|
|
|
|
ret = dwc3_send_gadget_generic_command(dwc,
|
|
DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI, param);
|
|
if (ret < 0)
|
|
goto err1;
|
|
|
|
return 0;
|
|
|
|
err1:
|
|
dma_unmap_single(scratch_addr, dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE,
|
|
DMA_BIDIRECTIONAL);
|
|
|
|
err0:
|
|
return ret;
|
|
}
|
|
|
|
static void dwc3_free_scratch_buffers(struct dwc3 *dwc)
|
|
{
|
|
if (!dwc->has_hibernation)
|
|
return;
|
|
|
|
if (!dwc->nr_scratch)
|
|
return;
|
|
|
|
dma_unmap_single(dwc->scratch_addr, dwc->nr_scratch *
|
|
DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL);
|
|
kfree(dwc->scratchbuf);
|
|
}
|
|
|
|
static void dwc3_core_num_eps(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_hwparams *parms = &dwc->hwparams;
|
|
|
|
dwc->num_in_eps = DWC3_NUM_IN_EPS(parms);
|
|
dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps;
|
|
|
|
dev_vdbg(dwc->dev, "found %d IN and %d OUT endpoints\n",
|
|
dwc->num_in_eps, dwc->num_out_eps);
|
|
}
|
|
|
|
static void dwc3_cache_hwparams(struct dwc3 *dwc)
|
|
{
|
|
struct dwc3_hwparams *parms = &dwc->hwparams;
|
|
|
|
parms->hwparams0 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS0);
|
|
parms->hwparams1 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS1);
|
|
parms->hwparams2 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS2);
|
|
parms->hwparams3 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS3);
|
|
parms->hwparams4 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS4);
|
|
parms->hwparams5 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS5);
|
|
parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
|
|
parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7);
|
|
parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8);
|
|
}
|
|
|
|
static void dwc3_hsphy_mode_setup(struct dwc3 *dwc)
|
|
{
|
|
enum usb_phy_interface hsphy_mode = dwc->hsphy_mode;
|
|
u32 reg;
|
|
|
|
/* Set dwc3 usb2 phy config */
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
|
|
|
switch (hsphy_mode) {
|
|
case USBPHY_INTERFACE_MODE_UTMI:
|
|
reg &= ~(DWC3_GUSB2PHYCFG_PHYIF_MASK |
|
|
DWC3_GUSB2PHYCFG_USBTRDTIM_MASK);
|
|
reg |= DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_8_BIT) |
|
|
DWC3_GUSB2PHYCFG_USBTRDTIM(USBTRDTIM_UTMI_8_BIT);
|
|
break;
|
|
case USBPHY_INTERFACE_MODE_UTMIW:
|
|
reg &= ~(DWC3_GUSB2PHYCFG_PHYIF_MASK |
|
|
DWC3_GUSB2PHYCFG_USBTRDTIM_MASK);
|
|
reg |= DWC3_GUSB2PHYCFG_PHYIF(UTMI_PHYIF_16_BIT) |
|
|
DWC3_GUSB2PHYCFG_USBTRDTIM(USBTRDTIM_UTMI_16_BIT);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
|
}
|
|
|
|
/**
|
|
* dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core
|
|
* @dwc: Pointer to our controller context structure
|
|
*/
|
|
static void dwc3_phy_setup(struct dwc3 *dwc)
|
|
{
|
|
u32 reg;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0));
|
|
|
|
/*
|
|
* Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY
|
|
* to '0' during coreConsultant configuration. So default value
|
|
* will be '0' when the core is reset. Application needs to set it
|
|
* to '1' after the core initialization is completed.
|
|
*/
|
|
if (dwc->revision > DWC3_REVISION_194A)
|
|
reg |= DWC3_GUSB3PIPECTL_SUSPHY;
|
|
|
|
if (dwc->u2ss_inp3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_U2SSINP3OK;
|
|
|
|
if (dwc->req_p1p2p3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_REQP1P2P3;
|
|
|
|
if (dwc->del_p1p2p3_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_DEP1P2P3_EN;
|
|
|
|
if (dwc->del_phy_power_chg_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_DEPOCHANGE;
|
|
|
|
if (dwc->lfps_filter_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_LFPSFILT;
|
|
|
|
if (dwc->rx_detect_poll_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_RX_DETOPOLL;
|
|
|
|
if (dwc->tx_de_emphasis_quirk)
|
|
reg |= DWC3_GUSB3PIPECTL_TX_DEEPH(dwc->tx_de_emphasis);
|
|
|
|
if (dwc->dis_u3_susphy_quirk)
|
|
reg &= ~DWC3_GUSB3PIPECTL_SUSPHY;
|
|
|
|
if (dwc->dis_del_phy_power_chg_quirk)
|
|
reg &= ~DWC3_GUSB3PIPECTL_DEPOCHANGE;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg);
|
|
|
|
dwc3_hsphy_mode_setup(dwc);
|
|
|
|
mdelay(100);
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0));
|
|
|
|
/*
|
|
* Above 1.94a, it is recommended to set DWC3_GUSB2PHYCFG_SUSPHY to
|
|
* '0' during coreConsultant configuration. So default value will
|
|
* be '0' when the core is reset. Application needs to set it to
|
|
* '1' after the core initialization is completed.
|
|
*/
|
|
if (dwc->revision > DWC3_REVISION_194A)
|
|
reg |= DWC3_GUSB2PHYCFG_SUSPHY;
|
|
|
|
if (dwc->dis_u2_susphy_quirk)
|
|
reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
|
|
|
|
if (dwc->dis_enblslpm_quirk)
|
|
reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM;
|
|
|
|
if (dwc->dis_u2_freeclk_exists_quirk)
|
|
reg &= ~DWC3_GUSB2PHYCFG_U2_FREECLK_EXISTS;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg);
|
|
|
|
mdelay(100);
|
|
}
|
|
|
|
/* set global incr burst type configuration registers */
|
|
static void dwc3_set_incr_burst_type(struct dwc3 *dwc)
|
|
{
|
|
struct udevice *dev = dwc->dev;
|
|
u32 cfg;
|
|
|
|
if (!dwc->incrx_size)
|
|
return;
|
|
|
|
cfg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG0);
|
|
|
|
/* Enable Undefined Length INCR Burst and Enable INCRx Burst */
|
|
cfg &= ~DWC3_GSBUSCFG0_INCRBRST_MASK;
|
|
if (dwc->incrx_mode)
|
|
cfg |= DWC3_GSBUSCFG0_INCRBRSTENA;
|
|
switch (dwc->incrx_size) {
|
|
case 256:
|
|
cfg |= DWC3_GSBUSCFG0_INCR256BRSTENA;
|
|
break;
|
|
case 128:
|
|
cfg |= DWC3_GSBUSCFG0_INCR128BRSTENA;
|
|
break;
|
|
case 64:
|
|
cfg |= DWC3_GSBUSCFG0_INCR64BRSTENA;
|
|
break;
|
|
case 32:
|
|
cfg |= DWC3_GSBUSCFG0_INCR32BRSTENA;
|
|
break;
|
|
case 16:
|
|
cfg |= DWC3_GSBUSCFG0_INCR16BRSTENA;
|
|
break;
|
|
case 8:
|
|
cfg |= DWC3_GSBUSCFG0_INCR8BRSTENA;
|
|
break;
|
|
case 4:
|
|
cfg |= DWC3_GSBUSCFG0_INCR4BRSTENA;
|
|
break;
|
|
case 1:
|
|
break;
|
|
default:
|
|
dev_err(dev, "Invalid property\n");
|
|
break;
|
|
}
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GSBUSCFG0, cfg);
|
|
}
|
|
|
|
/**
|
|
* dwc3_core_init - Low-level initialization of DWC3 Core
|
|
* @dwc: Pointer to our controller context structure
|
|
*
|
|
* Returns 0 on success otherwise negative errno.
|
|
*/
|
|
static int dwc3_core_init(struct dwc3 *dwc)
|
|
{
|
|
unsigned long timeout;
|
|
u32 hwparams4 = dwc->hwparams.hwparams4;
|
|
u32 reg;
|
|
int ret;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GSNPSID);
|
|
/* This should read as U3 followed by revision number */
|
|
if ((reg & DWC3_GSNPSID_MASK) != 0x55330000) {
|
|
dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n");
|
|
ret = -ENODEV;
|
|
goto err0;
|
|
}
|
|
dwc->revision = reg;
|
|
|
|
/* Handle USB2.0-only core configuration */
|
|
if (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) ==
|
|
DWC3_GHWPARAMS3_SSPHY_IFC_DIS) {
|
|
if (dwc->maximum_speed == USB_SPEED_SUPER)
|
|
dwc->maximum_speed = USB_SPEED_HIGH;
|
|
}
|
|
|
|
/* issue device SoftReset too */
|
|
timeout = 5000;
|
|
dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST);
|
|
while (timeout--) {
|
|
reg = dwc3_readl(dwc->regs, DWC3_DCTL);
|
|
if (!(reg & DWC3_DCTL_CSFTRST))
|
|
break;
|
|
};
|
|
|
|
if (!timeout) {
|
|
dev_err(dwc->dev, "Reset Timed Out\n");
|
|
ret = -ETIMEDOUT;
|
|
goto err0;
|
|
}
|
|
|
|
dwc3_phy_setup(dwc);
|
|
|
|
ret = dwc3_core_soft_reset(dwc);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
reg = dwc3_readl(dwc->regs, DWC3_GCTL);
|
|
reg &= ~DWC3_GCTL_SCALEDOWN_MASK;
|
|
|
|
switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) {
|
|
case DWC3_GHWPARAMS1_EN_PWROPT_CLK:
|
|
/**
|
|
* WORKAROUND: DWC3 revisions between 2.10a and 2.50a have an
|
|
* issue which would cause xHCI compliance tests to fail.
|
|
*
|
|
* Because of that we cannot enable clock gating on such
|
|
* configurations.
|
|
*
|
|
* Refers to:
|
|
*
|
|
* STAR#9000588375: Clock Gating, SOF Issues when ref_clk-Based
|
|
* SOF/ITP Mode Used
|
|
*/
|
|
if ((dwc->dr_mode == USB_DR_MODE_HOST ||
|
|
dwc->dr_mode == USB_DR_MODE_OTG) &&
|
|
(dwc->revision >= DWC3_REVISION_210A &&
|
|
dwc->revision <= DWC3_REVISION_250A))
|
|
reg |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC;
|
|
else
|
|
reg &= ~DWC3_GCTL_DSBLCLKGTNG;
|
|
break;
|
|
case DWC3_GHWPARAMS1_EN_PWROPT_HIB:
|
|
/* enable hibernation here */
|
|
dwc->nr_scratch = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4);
|
|
|
|
/*
|
|
* REVISIT Enabling this bit so that host-mode hibernation
|
|
* will work. Device-mode hibernation is not yet implemented.
|
|
*/
|
|
reg |= DWC3_GCTL_GBLHIBERNATIONEN;
|
|
break;
|
|
default:
|
|
dev_dbg(dwc->dev, "No power optimization available\n");
|
|
}
|
|
|
|
/* check if current dwc3 is on simulation board */
|
|
if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) {
|
|
dev_dbg(dwc->dev, "it is on FPGA board\n");
|
|
dwc->is_fpga = true;
|
|
}
|
|
|
|
if(dwc->disable_scramble_quirk && !dwc->is_fpga)
|
|
WARN(true,
|
|
"disable_scramble cannot be used on non-FPGA builds\n");
|
|
|
|
if (dwc->disable_scramble_quirk && dwc->is_fpga)
|
|
reg |= DWC3_GCTL_DISSCRAMBLE;
|
|
else
|
|
reg &= ~DWC3_GCTL_DISSCRAMBLE;
|
|
|
|
if (dwc->u2exit_lfps_quirk)
|
|
reg |= DWC3_GCTL_U2EXIT_LFPS;
|
|
|
|
/*
|
|
* WORKAROUND: DWC3 revisions <1.90a have a bug
|
|
* where the device can fail to connect at SuperSpeed
|
|
* and falls back to high-speed mode which causes
|
|
* the device to enter a Connect/Disconnect loop
|
|
*/
|
|
if (dwc->revision < DWC3_REVISION_190A)
|
|
reg |= DWC3_GCTL_U2RSTECN;
|
|
|
|
dwc3_core_num_eps(dwc);
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GCTL, reg);
|
|
|
|
ret = dwc3_alloc_scratch_buffers(dwc);
|
|
if (ret)
|
|
goto err0;
|
|
|
|
ret = dwc3_setup_scratch_buffers(dwc);
|
|
if (ret)
|
|
goto err1;
|
|
|
|
/* Adjust Frame Length */
|
|
dwc3_frame_length_adjustment(dwc, dwc->fladj);
|
|
|
|
dwc3_set_incr_burst_type(dwc);
|
|
|
|
return 0;
|
|
|
|
err1:
|
|
dwc3_free_scratch_buffers(dwc);
|
|
|
|
err0:
|
|
return ret;
|
|
}
|
|
|
|
static void dwc3_core_exit(struct dwc3 *dwc)
|
|
{
|
|
dwc3_free_scratch_buffers(dwc);
|
|
}
|
|
|
|
static int dwc3_core_init_mode(struct dwc3 *dwc)
|
|
{
|
|
int ret;
|
|
|
|
switch (dwc->dr_mode) {
|
|
case USB_DR_MODE_PERIPHERAL:
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
|
|
ret = dwc3_gadget_init(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to initialize gadget\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case USB_DR_MODE_HOST:
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST);
|
|
ret = dwc3_host_init(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to initialize host\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
case USB_DR_MODE_OTG:
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG);
|
|
ret = dwc3_host_init(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to initialize host\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = dwc3_gadget_init(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to initialize gadget\n");
|
|
return ret;
|
|
}
|
|
break;
|
|
default:
|
|
dev_err(dwc->dev,
|
|
"Unsupported mode of operation %d\n", dwc->dr_mode);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void dwc3_gadget_run(struct dwc3 *dwc)
|
|
{
|
|
dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_RUN_STOP);
|
|
mdelay(100);
|
|
}
|
|
|
|
static void dwc3_core_exit_mode(struct dwc3 *dwc)
|
|
{
|
|
switch (dwc->dr_mode) {
|
|
case USB_DR_MODE_PERIPHERAL:
|
|
dwc3_gadget_exit(dwc);
|
|
break;
|
|
case USB_DR_MODE_HOST:
|
|
dwc3_host_exit(dwc);
|
|
break;
|
|
case USB_DR_MODE_OTG:
|
|
dwc3_host_exit(dwc);
|
|
dwc3_gadget_exit(dwc);
|
|
break;
|
|
default:
|
|
/* do nothing */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* switch back to peripheral mode
|
|
* This enables the phy to enter idle and then, if enabled, suspend.
|
|
*/
|
|
dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE);
|
|
dwc3_gadget_run(dwc);
|
|
}
|
|
|
|
#define DWC3_ALIGN_MASK (16 - 1)
|
|
|
|
/**
|
|
* dwc3_uboot_init - dwc3 core uboot initialization code
|
|
* @dwc3_dev: struct dwc3_device containing initialization data
|
|
*
|
|
* Entry point for dwc3 driver (equivalent to dwc3_probe in linux
|
|
* kernel driver). Pointer to dwc3_device should be passed containing
|
|
* base address and other initialization data. Returns '0' on success and
|
|
* a negative value on failure.
|
|
*
|
|
* Generally called from board_usb_init() implemented in board file.
|
|
*/
|
|
int dwc3_uboot_init(struct dwc3_device *dwc3_dev)
|
|
{
|
|
struct dwc3 *dwc;
|
|
struct device *dev = NULL;
|
|
u8 lpm_nyet_threshold;
|
|
u8 tx_de_emphasis;
|
|
u8 hird_threshold;
|
|
|
|
int ret;
|
|
|
|
void *mem;
|
|
|
|
mem = devm_kzalloc((struct udevice *)dev,
|
|
sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL);
|
|
if (!mem)
|
|
return -ENOMEM;
|
|
|
|
dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1);
|
|
dwc->mem = mem;
|
|
|
|
dwc->regs = (void *)(uintptr_t)(dwc3_dev->base +
|
|
DWC3_GLOBALS_REGS_START);
|
|
|
|
/* default to highest possible threshold */
|
|
lpm_nyet_threshold = 0xff;
|
|
|
|
/* default to -3.5dB de-emphasis */
|
|
tx_de_emphasis = 1;
|
|
|
|
/*
|
|
* default to assert utmi_sleep_n and use maximum allowed HIRD
|
|
* threshold value of 0b1100
|
|
*/
|
|
hird_threshold = 12;
|
|
|
|
dwc->maximum_speed = dwc3_dev->maximum_speed;
|
|
dwc->has_lpm_erratum = dwc3_dev->has_lpm_erratum;
|
|
if (dwc3_dev->lpm_nyet_threshold)
|
|
lpm_nyet_threshold = dwc3_dev->lpm_nyet_threshold;
|
|
dwc->is_utmi_l1_suspend = dwc3_dev->is_utmi_l1_suspend;
|
|
if (dwc3_dev->hird_threshold)
|
|
hird_threshold = dwc3_dev->hird_threshold;
|
|
|
|
dwc->needs_fifo_resize = dwc3_dev->tx_fifo_resize;
|
|
dwc->dr_mode = dwc3_dev->dr_mode;
|
|
|
|
dwc->disable_scramble_quirk = dwc3_dev->disable_scramble_quirk;
|
|
dwc->u2exit_lfps_quirk = dwc3_dev->u2exit_lfps_quirk;
|
|
dwc->u2ss_inp3_quirk = dwc3_dev->u2ss_inp3_quirk;
|
|
dwc->req_p1p2p3_quirk = dwc3_dev->req_p1p2p3_quirk;
|
|
dwc->del_p1p2p3_quirk = dwc3_dev->del_p1p2p3_quirk;
|
|
dwc->del_phy_power_chg_quirk = dwc3_dev->del_phy_power_chg_quirk;
|
|
dwc->lfps_filter_quirk = dwc3_dev->lfps_filter_quirk;
|
|
dwc->rx_detect_poll_quirk = dwc3_dev->rx_detect_poll_quirk;
|
|
dwc->dis_u3_susphy_quirk = dwc3_dev->dis_u3_susphy_quirk;
|
|
dwc->dis_u2_susphy_quirk = dwc3_dev->dis_u2_susphy_quirk;
|
|
dwc->dis_del_phy_power_chg_quirk = dwc3_dev->dis_del_phy_power_chg_quirk;
|
|
dwc->dis_tx_ipgap_linecheck_quirk = dwc3_dev->dis_tx_ipgap_linecheck_quirk;
|
|
dwc->dis_enblslpm_quirk = dwc3_dev->dis_enblslpm_quirk;
|
|
dwc->dis_u2_freeclk_exists_quirk = dwc3_dev->dis_u2_freeclk_exists_quirk;
|
|
|
|
dwc->tx_de_emphasis_quirk = dwc3_dev->tx_de_emphasis_quirk;
|
|
if (dwc3_dev->tx_de_emphasis)
|
|
tx_de_emphasis = dwc3_dev->tx_de_emphasis;
|
|
|
|
/* default to superspeed if no maximum_speed passed */
|
|
if (dwc->maximum_speed == USB_SPEED_UNKNOWN)
|
|
dwc->maximum_speed = USB_SPEED_SUPER;
|
|
|
|
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
|
|
dwc->tx_de_emphasis = tx_de_emphasis;
|
|
|
|
dwc->hird_threshold = hird_threshold
|
|
| (dwc->is_utmi_l1_suspend << 4);
|
|
|
|
dwc->hsphy_mode = dwc3_dev->hsphy_mode;
|
|
|
|
dwc->index = dwc3_dev->index;
|
|
|
|
dwc3_cache_hwparams(dwc);
|
|
|
|
ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to allocate event buffers\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (!IS_ENABLED(CONFIG_USB_DWC3_GADGET))
|
|
dwc->dr_mode = USB_DR_MODE_HOST;
|
|
else if (!IS_ENABLED(CONFIG_USB_HOST))
|
|
dwc->dr_mode = USB_DR_MODE_PERIPHERAL;
|
|
|
|
if (dwc->dr_mode == USB_DR_MODE_UNKNOWN)
|
|
dwc->dr_mode = USB_DR_MODE_OTG;
|
|
|
|
ret = dwc3_core_init(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to initialize core\n");
|
|
goto err0;
|
|
}
|
|
|
|
ret = dwc3_event_buffers_setup(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to setup event buffers\n");
|
|
goto err1;
|
|
}
|
|
|
|
ret = dwc3_core_init_mode(dwc);
|
|
if (ret)
|
|
goto err2;
|
|
|
|
list_add_tail(&dwc->list, &dwc3_list);
|
|
|
|
return 0;
|
|
|
|
err2:
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
|
|
err1:
|
|
dwc3_core_exit(dwc);
|
|
|
|
err0:
|
|
dwc3_free_event_buffers(dwc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* dwc3_uboot_exit - dwc3 core uboot cleanup code
|
|
* @index: index of this controller
|
|
*
|
|
* Performs cleanup of memory allocated in dwc3_uboot_init and other misc
|
|
* cleanups (equivalent to dwc3_remove in linux). index of _this_ controller
|
|
* should be passed and should match with the index passed in
|
|
* dwc3_device during init.
|
|
*
|
|
* Generally called from board file.
|
|
*/
|
|
void dwc3_uboot_exit(int index)
|
|
{
|
|
struct dwc3 *dwc;
|
|
|
|
list_for_each_entry(dwc, &dwc3_list, list) {
|
|
if (dwc->index != index)
|
|
continue;
|
|
|
|
dwc3_core_exit_mode(dwc);
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
dwc3_free_event_buffers(dwc);
|
|
dwc3_core_exit(dwc);
|
|
list_del(&dwc->list);
|
|
kfree(dwc->mem);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* dwc3_uboot_handle_interrupt - handle dwc3 core interrupt
|
|
* @index: index of this controller
|
|
*
|
|
* Invokes dwc3 gadget interrupts.
|
|
*
|
|
* Generally called from board file.
|
|
*/
|
|
void dwc3_uboot_handle_interrupt(int index)
|
|
{
|
|
struct dwc3 *dwc = NULL;
|
|
|
|
list_for_each_entry(dwc, &dwc3_list, list) {
|
|
if (dwc->index != index)
|
|
continue;
|
|
|
|
dwc3_gadget_uboot_handle_interrupt(dwc);
|
|
break;
|
|
}
|
|
}
|
|
|
|
MODULE_ALIAS("platform:dwc3");
|
|
MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>");
|
|
MODULE_LICENSE("GPL v2");
|
|
MODULE_DESCRIPTION("DesignWare USB3 DRD Controller Driver");
|
|
|
|
#if CONFIG_IS_ENABLED(PHY) && CONFIG_IS_ENABLED(DM_USB)
|
|
int dwc3_setup_phy(struct udevice *dev, struct phy_bulk *phys)
|
|
{
|
|
int ret;
|
|
|
|
ret = generic_phy_get_bulk(dev, phys);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = generic_phy_init_bulk(phys);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = generic_phy_power_on_bulk(phys);
|
|
if (ret)
|
|
generic_phy_exit_bulk(phys);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int dwc3_shutdown_phy(struct udevice *dev, struct phy_bulk *phys)
|
|
{
|
|
int ret;
|
|
|
|
ret = generic_phy_power_off_bulk(phys);
|
|
ret |= generic_phy_exit_bulk(phys);
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
#if CONFIG_IS_ENABLED(DM_USB)
|
|
void dwc3_of_parse(struct dwc3 *dwc)
|
|
{
|
|
const u8 *tmp;
|
|
struct udevice *dev = dwc->dev;
|
|
u8 lpm_nyet_threshold;
|
|
u8 tx_de_emphasis;
|
|
u8 hird_threshold;
|
|
u32 val;
|
|
int i;
|
|
|
|
/* default to highest possible threshold */
|
|
lpm_nyet_threshold = 0xff;
|
|
|
|
/* default to -3.5dB de-emphasis */
|
|
tx_de_emphasis = 1;
|
|
|
|
/*
|
|
* default to assert utmi_sleep_n and use maximum allowed HIRD
|
|
* threshold value of 0b1100
|
|
*/
|
|
hird_threshold = 12;
|
|
|
|
dwc->hsphy_mode = usb_get_phy_mode(dev_ofnode(dev));
|
|
|
|
dwc->has_lpm_erratum = dev_read_bool(dev,
|
|
"snps,has-lpm-erratum");
|
|
tmp = dev_read_u8_array_ptr(dev, "snps,lpm-nyet-threshold", 1);
|
|
if (tmp)
|
|
lpm_nyet_threshold = *tmp;
|
|
|
|
dwc->is_utmi_l1_suspend = dev_read_bool(dev,
|
|
"snps,is-utmi-l1-suspend");
|
|
tmp = dev_read_u8_array_ptr(dev, "snps,hird-threshold", 1);
|
|
if (tmp)
|
|
hird_threshold = *tmp;
|
|
|
|
dwc->disable_scramble_quirk = dev_read_bool(dev,
|
|
"snps,disable_scramble_quirk");
|
|
dwc->u2exit_lfps_quirk = dev_read_bool(dev,
|
|
"snps,u2exit_lfps_quirk");
|
|
dwc->u2ss_inp3_quirk = dev_read_bool(dev,
|
|
"snps,u2ss_inp3_quirk");
|
|
dwc->req_p1p2p3_quirk = dev_read_bool(dev,
|
|
"snps,req_p1p2p3_quirk");
|
|
dwc->del_p1p2p3_quirk = dev_read_bool(dev,
|
|
"snps,del_p1p2p3_quirk");
|
|
dwc->del_phy_power_chg_quirk = dev_read_bool(dev,
|
|
"snps,del_phy_power_chg_quirk");
|
|
dwc->lfps_filter_quirk = dev_read_bool(dev,
|
|
"snps,lfps_filter_quirk");
|
|
dwc->rx_detect_poll_quirk = dev_read_bool(dev,
|
|
"snps,rx_detect_poll_quirk");
|
|
dwc->dis_u3_susphy_quirk = dev_read_bool(dev,
|
|
"snps,dis_u3_susphy_quirk");
|
|
dwc->dis_u2_susphy_quirk = dev_read_bool(dev,
|
|
"snps,dis_u2_susphy_quirk");
|
|
dwc->dis_del_phy_power_chg_quirk = dev_read_bool(dev,
|
|
"snps,dis-del-phy-power-chg-quirk");
|
|
dwc->dis_tx_ipgap_linecheck_quirk = dev_read_bool(dev,
|
|
"snps,dis-tx-ipgap-linecheck-quirk");
|
|
dwc->dis_enblslpm_quirk = dev_read_bool(dev,
|
|
"snps,dis_enblslpm_quirk");
|
|
dwc->dis_u2_freeclk_exists_quirk = dev_read_bool(dev,
|
|
"snps,dis-u2-freeclk-exists-quirk");
|
|
dwc->tx_de_emphasis_quirk = dev_read_bool(dev,
|
|
"snps,tx_de_emphasis_quirk");
|
|
tmp = dev_read_u8_array_ptr(dev, "snps,tx_de_emphasis", 1);
|
|
if (tmp)
|
|
tx_de_emphasis = *tmp;
|
|
|
|
dwc->lpm_nyet_threshold = lpm_nyet_threshold;
|
|
dwc->tx_de_emphasis = tx_de_emphasis;
|
|
|
|
dwc->hird_threshold = hird_threshold
|
|
| (dwc->is_utmi_l1_suspend << 4);
|
|
|
|
dev_read_u32(dev, "snps,quirk-frame-length-adjustment", &dwc->fladj);
|
|
|
|
/*
|
|
* Handle property "snps,incr-burst-type-adjustment".
|
|
* Get the number of value from this property:
|
|
* result <= 0, means this property is not supported.
|
|
* result = 1, means INCRx burst mode supported.
|
|
* result > 1, means undefined length burst mode supported.
|
|
*/
|
|
dwc->incrx_mode = INCRX_BURST_MODE;
|
|
dwc->incrx_size = 0;
|
|
for (i = 0; i < 8; i++) {
|
|
if (dev_read_u32_index(dev, "snps,incr-burst-type-adjustment",
|
|
i, &val))
|
|
break;
|
|
|
|
dwc->incrx_mode = INCRX_UNDEF_LENGTH_BURST_MODE;
|
|
dwc->incrx_size = max(dwc->incrx_size, val);
|
|
}
|
|
}
|
|
|
|
int dwc3_init(struct dwc3 *dwc)
|
|
{
|
|
int ret;
|
|
u32 reg;
|
|
|
|
dwc3_cache_hwparams(dwc);
|
|
|
|
ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to allocate event buffers\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
ret = dwc3_core_init(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to initialize core\n");
|
|
goto core_fail;
|
|
}
|
|
|
|
ret = dwc3_event_buffers_setup(dwc);
|
|
if (ret) {
|
|
dev_err(dwc->dev, "failed to setup event buffers\n");
|
|
goto event_fail;
|
|
}
|
|
|
|
if (dwc->revision >= DWC3_REVISION_250A) {
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUCTL1);
|
|
|
|
/*
|
|
* Enable hardware control of sending remote wakeup
|
|
* in HS when the device is in the L1 state.
|
|
*/
|
|
if (dwc->revision >= DWC3_REVISION_290A)
|
|
reg |= DWC3_GUCTL1_DEV_L1_EXIT_BY_HW;
|
|
|
|
if (dwc->dis_tx_ipgap_linecheck_quirk)
|
|
reg |= DWC3_GUCTL1_TX_IPGAP_LINECHECK_DIS;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUCTL1, reg);
|
|
}
|
|
|
|
if (dwc->dr_mode == USB_DR_MODE_HOST ||
|
|
dwc->dr_mode == USB_DR_MODE_OTG) {
|
|
reg = dwc3_readl(dwc->regs, DWC3_GUCTL);
|
|
|
|
reg |= DWC3_GUCTL_HSTINAUTORETRY;
|
|
|
|
dwc3_writel(dwc->regs, DWC3_GUCTL, reg);
|
|
}
|
|
|
|
ret = dwc3_core_init_mode(dwc);
|
|
if (ret)
|
|
goto mode_fail;
|
|
|
|
return 0;
|
|
|
|
mode_fail:
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
|
|
event_fail:
|
|
dwc3_core_exit(dwc);
|
|
|
|
core_fail:
|
|
dwc3_free_event_buffers(dwc);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void dwc3_remove(struct dwc3 *dwc)
|
|
{
|
|
dwc3_core_exit_mode(dwc);
|
|
dwc3_event_buffers_cleanup(dwc);
|
|
dwc3_free_event_buffers(dwc);
|
|
dwc3_core_exit(dwc);
|
|
kfree(dwc->mem);
|
|
}
|
|
#endif
|