aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/chipidea/ci_hdrc_usb2.c5
-rw-r--r--drivers/usb/chipidea/host.c9
-rw-r--r--drivers/usb/chipidea/otg_fsm.c9
-rw-r--r--drivers/usb/dwc3/Kconfig8
-rw-r--r--drivers/usb/dwc3/Makefile10
-rw-r--r--drivers/usb/dwc3/core.c233
-rw-r--r--drivers/usb/dwc3/core.h97
-rw-r--r--drivers/usb/dwc3/debugfs.c50
-rw-r--r--drivers/usb/dwc3/dwc3-of-simple.c357
-rw-r--r--drivers/usb/dwc3/ep0.c43
-rw-r--r--drivers/usb/dwc3/gadget.c330
-rw-r--r--drivers/usb/dwc3/gadget.h18
-rw-r--r--drivers/usb/dwc3/gadget_hibernation.c567
-rw-r--r--drivers/usb/dwc3/host.c23
-rw-r--r--drivers/usb/dwc3/otg.c2199
-rw-r--r--drivers/usb/dwc3/otg.h252
-rw-r--r--drivers/usb/dwc3/platform_data.h54
-rw-r--r--drivers/usb/gadget/configfs.c45
-rw-r--r--drivers/usb/gadget/function/f_tcm.c311
-rw-r--r--drivers/usb/gadget/function/tcm.h7
-rw-r--r--drivers/usb/gadget/function/uvc_video.c5
-rw-r--r--drivers/usb/gadget/udc/udc-xilinx.c81
-rw-r--r--drivers/usb/host/ehci-xilinx-of.c12
-rw-r--r--drivers/usb/host/xhci-hub.c7
-rw-r--r--drivers/usb/host/xhci-mem.c14
-rw-r--r--drivers/usb/host/xhci-plat.c59
-rw-r--r--drivers/usb/host/xhci-ring.c107
-rw-r--r--drivers/usb/host/xhci.c30
-rw-r--r--drivers/usb/host/xhci.h5
-rw-r--r--drivers/usb/phy/Kconfig1
-rw-r--r--drivers/usb/phy/phy-ulpi.c99
-rw-r--r--drivers/usb/storage/uas.c307
-rw-r--r--drivers/usb/storage/unusual_uas.h6
33 files changed, 5164 insertions, 196 deletions
diff --git a/drivers/usb/chipidea/ci_hdrc_usb2.c b/drivers/usb/chipidea/ci_hdrc_usb2.c
index c044fba463e4..83238293e5be 100644
--- a/drivers/usb/chipidea/ci_hdrc_usb2.c
+++ b/drivers/usb/chipidea/ci_hdrc_usb2.c
@@ -30,6 +30,7 @@ static const struct ci_hdrc_platform_data ci_default_pdata = {
static struct ci_hdrc_platform_data ci_zynq_pdata = {
.capoffset = DEF_CAPOFFSET,
+ .flags = CI_HDRC_PHY_VBUS_CONTROL,
};
static const struct of_device_id ci_hdrc_usb2_of_match[] = {
@@ -58,6 +59,10 @@ static int ci_hdrc_usb2_probe(struct platform_device *pdev)
if (match && match->data) {
/* struct copy */
*ci_pdata = *(struct ci_hdrc_platform_data *)match->data;
+ ci_pdata->usb_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy",
+ 0);
+ if (IS_ERR(ci_pdata->usb_phy))
+ return PTR_ERR(ci_pdata->usb_phy);
}
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
diff --git a/drivers/usb/chipidea/host.c b/drivers/usb/chipidea/host.c
index 48e4a5ca1835..b49edda341ea 100644
--- a/drivers/usb/chipidea/host.c
+++ b/drivers/usb/chipidea/host.c
@@ -57,6 +57,14 @@ static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
priv->enabled = enable;
}
+ if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL &&
+ ci->usb_phy && ci->usb_phy->set_vbus) {
+ if (enable)
+ ci->usb_phy->set_vbus(ci->usb_phy, 1);
+ else
+ ci->usb_phy->set_vbus(ci->usb_phy, 0);
+ }
+
if (enable && (ci->platdata->phy_mode == USBPHY_INTERFACE_MODE_HSIC)) {
/*
* Marvell 28nm HSIC PHY requires forcing the port to HS mode.
@@ -65,6 +73,7 @@ static int ehci_ci_portpower(struct usb_hcd *hcd, int portnum, bool enable)
hw_port_test_set(ci, 5);
hw_port_test_set(ci, 0);
}
+
return 0;
};
diff --git a/drivers/usb/chipidea/otg_fsm.c b/drivers/usb/chipidea/otg_fsm.c
index 6ed4b00dba96..ec02ea0ab20d 100644
--- a/drivers/usb/chipidea/otg_fsm.c
+++ b/drivers/usb/chipidea/otg_fsm.c
@@ -471,6 +471,11 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
return;
}
}
+
+ if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL &&
+ ci->usb_phy && ci->usb_phy->set_vbus)
+ ci->usb_phy->set_vbus(ci->usb_phy, 1);
+
/* Disable data pulse irq */
hw_write_otgsc(ci, OTGSC_DPIE, 0);
@@ -480,6 +485,10 @@ static void ci_otg_drv_vbus(struct otg_fsm *fsm, int on)
if (ci->platdata->reg_vbus)
regulator_disable(ci->platdata->reg_vbus);
+ if (ci->platdata->flags & CI_HDRC_PHY_VBUS_CONTROL &&
+ ci->usb_phy && ci->usb_phy->set_vbus)
+ ci->usb_phy->set_vbus(ci->usb_phy, 0);
+
fsm->a_bus_drop = 1;
fsm->a_bus_req = 0;
}
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig
index 864cb18c609a..b15233a6ba9a 100644
--- a/drivers/usb/dwc3/Kconfig
+++ b/drivers/usb/dwc3/Kconfig
@@ -23,6 +23,7 @@ config USB_DWC3_ULPI
choice
bool "DWC3 Mode Selection"
default USB_DWC3_DUAL_ROLE if (USB && USB_GADGET)
+ default USB_DWC3_OTG if (USB && USB_GADGET && USB_OTG && USB_OTG_FSM)
default USB_DWC3_HOST if (USB && !USB_GADGET)
default USB_DWC3_GADGET if (!USB && USB_GADGET)
@@ -48,6 +49,13 @@ config USB_DWC3_DUAL_ROLE
This is the default mode of working of DWC3 controller where
both host and gadget features are enabled.
+config USB_DWC3_OTG
+ bool "Dual Role mode + OTG"
+ depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3) && USB_OTG && USB_OTG_FSM && PM)
+ help
+ This is the default mode of working of DWC3 controller where
+ both host and gadget features are enabled with OTG support.
+
endchoice
comment "Platform Glue Driver Support"
diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile
index ae86da0dc5bd..258bc4bfca87 100644
--- a/drivers/usb/dwc3/Makefile
+++ b/drivers/usb/dwc3/Makefile
@@ -10,12 +10,16 @@ ifneq ($(CONFIG_TRACING),)
dwc3-y += trace.o
endif
-ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
+ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE) $(CONFIG_USB_DWC3_OTG)),)
dwc3-y += host.o
endif
-ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),)
- dwc3-y += gadget.o ep0.o
+ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE) $(CONFIG_USB_DWC3_OTG)),)
+ dwc3-y += gadget.o ep0.o gadget_hibernation.o
+endif
+
+ifneq ($(CONFIG_USB_DWC3_OTG),)
+ dwc3-y += otg.o
endif
ifneq ($(CONFIG_USB_DWC3_DUAL_ROLE),)
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);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 679c9f25640c..7e5f6dc4c9bb 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -164,6 +164,9 @@
/* Bit fields */
+/* Global Status Register */
+#define DWC3_GSTS_CUR_MODE (1 << 0)
+
/* Global SoC Bus Configuration INCRx Register 0 */
#define DWC3_GSBUSCFG0_INCR256BRSTENA (1 << 7) /* INCR256 burst */
#define DWC3_GSBUSCFG0_INCR128BRSTENA (1 << 6) /* INCR128 burst */
@@ -196,6 +199,12 @@
#define DWC3_EVENTQ 7
#define DWC3_AUXEVENTQ 8
+/* Global SoC Bus Configuration Register */
+#define DWC3_GSBUSCFG0_DATRDREQINFO (0xf << 28)
+#define DWC3_GSBUSCFG0_DESRDREQINFO (0xf << 24)
+#define DWC3_GSBUSCFG0_DATWRREQINFO (0xf << 20)
+#define DWC3_GSBUSCFG0_DESWRREQINFO (0xf << 16)
+
/* Global RX Threshold Configuration Register */
#define DWC3_GRXTHRCFG_MAXRXBURSTSIZE(n) (((n) & 0x1f) << 19)
#define DWC3_GRXTHRCFG_RXPKTCNT(n) (((n) & 0xf) << 24)
@@ -371,6 +380,11 @@
/* Global Frame Length Adjustment Register */
#define DWC3_GFLADJ_30MHZ_SDBND_SEL BIT(7)
#define DWC3_GFLADJ_30MHZ_MASK 0x3f
+#define DWC3_GFLADJ_REFCLK_FLADJ (0x3fff << 8)
+
+/* Global User Control Register 1 */
+#define DWC3_GUCTL1_RESUME_QUIRK (1 << 10)
+#define DWC3_GUCTL1_IPD_QUIRK (1 << 9)
/* Global User Control Register 2 */
#define DWC3_GUCTL2_RST_ACTBITLATER BIT(14)
@@ -451,6 +465,7 @@
/* Device Status Register */
#define DWC3_DSTS_DCNRD BIT(29)
+#define DWC3_DSTS_SRE BIT(28)
/* This applies for core versions 1.87a and earlier */
#define DWC3_DSTS_PWRUPREQ BIT(24)
@@ -610,6 +625,9 @@
#define DWC3_OSTS_VBUSVLD BIT(1)
#define DWC3_OSTS_CONIDSTS BIT(0)
+/* Stream timer timeout value in millisecs */
+#define STREAM_TIMEOUT_MS 50
+
/* Structures */
struct dwc3_trb;
@@ -872,6 +890,11 @@ struct dwc3_hwparams {
* @epnum: endpoint number to which this request refers
* @trb: pointer to struct dwc3_trb
* @trb_dma: DMA address of @trb
+ * @stream_timeout_timer: Some endpoints may go out of sync with host and
+ * enter into deadlock. For example, stream capable endpoints may enter
+ * into deadlock where the host waits on gadget to issue ERDY and gadget
+ * waits for host to issue prime transaction. To avoid such deadlock this
+ * timer is used.
* @num_trbs: number of TRBs used by this request
* @needs_extra_trb: true when request needs one extra TRB (either due to ZLP
* or unaligned OUT)
@@ -887,6 +910,7 @@ struct dwc3_request {
unsigned num_pending_sgs;
unsigned int num_queued_sgs;
+ u8 first_trb_index;
unsigned remaining;
unsigned int status;
@@ -899,6 +923,7 @@ struct dwc3_request {
u8 epnum;
struct dwc3_trb *trb;
dma_addr_t trb_dma;
+ struct timer_list stream_timeout_timer;
unsigned num_trbs;
@@ -942,7 +967,9 @@ struct dwc3_scratchpad_array {
* @regs: base address for our registers
* @regs_size: address space size
* @fladj: frame length adjustment
+ * @refclk_fladj: boolean to update GFLADJ_REFCLK_FLADJ field also
* @irq_gadget: peripheral controller's IRQ number
+ * @otg: pointer to the dwc3_otg structure
* @otg_irq: IRQ number for OTG IRQs
* @current_otg_role: current role of operation while using the OTG block
* @desired_otg_role: desired role of operation while using the OTG block
@@ -1009,6 +1036,7 @@ struct dwc3_scratchpad_array {
* not needed for DWC_usb31 version 1.70a-ea06 and below
* @usb3_lpm_capable: set if hadrware supports Link Power Management
* @usb2_lpm_disable: set to disable usb2 lpm
+ * @remote_wakeup: set if host supports Remote Wakeup from Peripheral
* @disable_scramble_quirk: set if we enable the disable scramble quirk
* @u2exit_lfps_quirk: set if we enable u2exit lfps quirk
* @u2ss_inp3_quirk: set if we enable P3 OK for U2/SS Inactive quirk
@@ -1027,11 +1055,16 @@ struct dwc3_scratchpad_array {
* provide a free-running PHY clock.
* @dis_del_phy_power_chg_quirk: set if we disable delay phy power
* change quirk.
+ * @enable_guctl1_resume_quirk: Set if we enable quirk for fixing improper crc
+ * generation after resume from suspend.
+ * @enable_guctl1_ipd_quirk: set if we enable quirk for reducing timing of inter
+ * packet delay(ipd).
* @dis_tx_ipgap_linecheck_quirk: set if we disable u2mac linestate
* check during HS transmit.
* @parkmode_disable_ss_quirk: set if we need to disable all SuperSpeed
* instances in park mode.
* @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk
+ * @is_hibernated: true when dwc3 is hibernated; abort processing events
* @tx_de_emphasis: Tx de-emphasis value
* 0 - -6dB de-emphasis
* 1 - -3.5dB de-emphasis
@@ -1040,6 +1073,12 @@ struct dwc3_scratchpad_array {
* @dis_metastability_quirk: set to disable metastability quirk.
* @imod_interval: set the interrupt moderation interval in 250ns
* increments or 0 to disable.
+ * @is_d3: set if the controller is in d3 state
+ * @saved_regs: registers to be saved/restored during hibernation/wakeup events
+ * @irq_wakeup: wakeup IRQ number, triggered when host asks to wakeup from
+ * hibernation
+ * @force_hiber_wake: flag set when the gadget driver is forcefully triggering
+ a hibernation wakeup event
*/
struct dwc3 {
struct work_struct drd_work;
@@ -1073,6 +1112,8 @@ struct dwc3 {
struct reset_control *reset;
+ struct dwc3_otg *otg;
+
struct usb_phy *usb2_phy;
struct usb_phy *usb3_phy;
@@ -1095,6 +1136,7 @@ struct dwc3 {
enum usb_phy_interface hsphy_mode;
u32 fladj;
+ bool refclk_fladj;
u32 irq_gadget;
u32 otg_irq;
u32 current_otg_role;
@@ -1202,6 +1244,7 @@ struct dwc3 {
unsigned dis_start_transfer_quirk:1;
unsigned usb3_lpm_capable:1;
unsigned usb2_lpm_disable:1;
+ unsigned remote_wakeup:1;
unsigned disable_scramble_quirk:1;
unsigned u2exit_lfps_quirk:1;
@@ -1217,15 +1260,22 @@ struct dwc3 {
unsigned dis_rxdet_inp3_quirk:1;
unsigned dis_u2_freeclk_exists_quirk:1;
unsigned dis_del_phy_power_chg_quirk:1;
+ unsigned enable_guctl1_resume_quirk:1;
+ unsigned enable_guctl1_ipd_quirk:1;
unsigned dis_tx_ipgap_linecheck_quirk:1;
unsigned parkmode_disable_ss_quirk:1;
unsigned tx_de_emphasis_quirk:1;
unsigned tx_de_emphasis:2;
+ unsigned is_hibernated:1;
unsigned dis_metastability_quirk:1;
u16 imod_interval;
+ bool is_d3;
+ u32 *saved_regs;
+ u32 irq_wakeup;
+ bool force_hiber_wake;
};
#define INCRX_BURST_MODE 0
@@ -1402,12 +1452,31 @@ static inline bool dwc3_is_usb31(struct dwc3 *dwc)
return !!(dwc->revision & DWC3_REVISION_IS_DWC31);
}
-bool dwc3_has_imod(struct dwc3 *dwc);
+#if IS_ENABLED(CONFIG_USB_DWC3_OF_SIMPLE)
+int dwc3_enable_hw_coherency(struct device *dev);
+void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup);
+void dwc3_set_simple_data(struct dwc3 *dwc);
+void dwc3_simple_check_quirks(struct dwc3 *dwc);
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on);
+#else
+static inline int dwc3_enable_hw_coherency(struct device *dev)
+{ return 1; }
+void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup)
+{ ; }
+void dwc3_set_simple_data(struct dwc3 *dwc)
+{ ; }
+void dwc3_simple_check_quirks(struct dwc3 *dwc)
+{ ; }
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on)
+{ ; }
+#endif
+bool dwc3_has_imod(struct dwc3 *dwc);
int dwc3_event_buffers_setup(struct dwc3 *dwc);
void dwc3_event_buffers_cleanup(struct dwc3 *dwc);
-#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+#if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)\
+ || IS_ENABLED(CONFIG_USB_DWC3_OTG)
int dwc3_host_init(struct dwc3 *dwc);
void dwc3_host_exit(struct dwc3 *dwc);
#else
@@ -1417,7 +1486,8 @@ static inline void dwc3_host_exit(struct dwc3 *dwc)
{ }
#endif
-#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)\
+ || IS_ENABLED(CONFIG_USB_DWC3_OTG)
int dwc3_gadget_init(struct dwc3 *dwc);
void dwc3_gadget_exit(struct dwc3 *dwc);
int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode);
@@ -1426,6 +1496,7 @@ int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state);
int dwc3_send_gadget_ep_cmd(struct dwc3_ep *dep, unsigned cmd,
struct dwc3_gadget_ep_cmd_params *params);
int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param);
+int dwc3_core_init(struct dwc3 *dwc);
#else
static inline int dwc3_gadget_init(struct dwc3 *dwc)
{ return 0; }
@@ -1447,11 +1518,19 @@ static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc,
{ return 0; }
#endif
+#if IS_ENABLED(CONFIG_USB_DWC3_OTG) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
+void dwc3_otg_init(struct dwc3 *dwc);
+void dwc3_otg_exit(struct dwc3 *dwc);
+#else
+static inline void dwc3_otg_init(struct dwc3 *dwc)
+{ }
+static inline void dwc3_otg_exit(struct dwc3 *dwc)
+{ }
+#endif
+
#if IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)
int dwc3_drd_init(struct dwc3 *dwc);
void dwc3_drd_exit(struct dwc3 *dwc);
-void dwc3_otg_init(struct dwc3 *dwc);
-void dwc3_otg_exit(struct dwc3 *dwc);
void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus);
void dwc3_otg_host_init(struct dwc3 *dwc);
#else
@@ -1459,10 +1538,6 @@ static inline int dwc3_drd_init(struct dwc3 *dwc)
{ return 0; }
static inline void dwc3_drd_exit(struct dwc3 *dwc)
{ }
-static inline void dwc3_otg_init(struct dwc3 *dwc)
-{ }
-static inline void dwc3_otg_exit(struct dwc3 *dwc)
-{ }
static inline void dwc3_otg_update(struct dwc3 *dwc, bool ignore_idstatus)
{ }
static inline void dwc3_otg_host_init(struct dwc3 *dwc)
@@ -1500,4 +1575,8 @@ static inline void dwc3_ulpi_exit(struct dwc3 *dwc)
{ }
#endif
+int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length);
+void dwc3_free_event_buffers(struct dwc3 *dwc);
+int dwc3_event_buffers_setup(struct dwc3 *dwc);
+
#endif /* __DRIVERS_USB_DWC3_CORE_H */
diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c
index 1c792710348f..25b753635879 100644
--- a/drivers/usb/dwc3/debugfs.c
+++ b/drivers/usb/dwc3/debugfs.c
@@ -625,6 +625,53 @@ static const struct file_operations dwc3_link_state_fops = {
.release = single_release,
};
+static int dwc3_hiber_enable_show(struct seq_file *s, void *unused)
+{
+ struct dwc3 *dwc = s->private;
+
+ seq_printf(s, "%s\n", (dwc->has_hibernation ? "Enabled" : "Disabled"));
+
+ return 0;
+}
+
+static int dwc3_hiber_enable_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, dwc3_hiber_enable_show, inode->i_private);
+}
+
+static ssize_t dwc3_hiber_enable_write(struct file *file,
+ const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct dwc3 *dwc = s->private;
+ char buf[32];
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ /* Enable hibernation feature */
+ if (!strncmp(buf, "Enable", 6)) {
+ dwc3_gadget_exit(dwc);
+ dwc->has_hibernation = 1;
+ dwc3_gadget_init(dwc);
+ } else if (!strncmp(buf, "Disable", 6)) {
+ dwc3_gadget_exit(dwc);
+ dwc->has_hibernation = 0;
+ dwc3_gadget_init(dwc);
+ } else {
+ return -EINVAL;
+ }
+
+ return count;
+}
+
+static const struct file_operations dwc3_hiber_enable_fops = {
+ .open = dwc3_hiber_enable_open,
+ .write = dwc3_hiber_enable_write,
+ .read = seq_read,
+};
+
struct dwc3_ep_file_map {
const char name[25];
const struct file_operations *const fops;
@@ -935,6 +982,9 @@ void dwc3_debugfs_init(struct dwc3 *dwc)
&dwc3_testmode_fops);
debugfs_create_file("link_state", S_IRUGO | S_IWUSR, root, dwc,
&dwc3_link_state_fops);
+ debugfs_create_file("hiber_enable", S_IRUGO | S_IWUSR, root,
+ dwc, &dwc3_hiber_enable_fops);
+
dwc3_debugfs_create_endpoint_dirs(dwc, root);
}
}
diff --git a/drivers/usb/dwc3/dwc3-of-simple.c b/drivers/usb/dwc3/dwc3-of-simple.c
index bdac3e7d7b18..95d4271f57a2 100644
--- a/drivers/usb/dwc3/dwc3-of-simple.c
+++ b/drivers/usb/dwc3/dwc3-of-simple.c
@@ -21,16 +21,172 @@
#include <linux/of_platform.h>
#include <linux/pm_runtime.h>
#include <linux/reset.h>
+#include <linux/soc/xilinx/zynqmp/fw.h>
+#include <linux/slab.h>
+
+#include <linux/phy/phy-zynqmp.h>
+#include <linux/of_address.h>
+
+#include "core.h"
+
+/* Xilinx USB 3.0 IP Register */
+#define XLNX_USB_COHERENCY 0x005C
+#define XLNX_USB_COHERENCY_ENABLE 0x1
+
+/* ULPI control registers */
+#define ULPI_OTG_CTRL_SET 0xB
+#define ULPI_OTG_CTRL_CLEAR 0XC
+#define OTG_CTRL_DRVVBUS_OFFSET 5
+
+#define XLNX_USB_CUR_PWR_STATE 0x0000
+#define XLNX_CUR_PWR_STATE_D0 0x00
+#define XLNX_CUR_PWR_STATE_D3 0x0F
+#define XLNX_CUR_PWR_STATE_BITMASK 0x0F
+
+#define XLNX_USB_PME_ENABLE 0x0034
+#define XLNX_PME_ENABLE_SIG_GEN 0x01
+
+#define XLNX_USB_REQ_PWR_STATE 0x003c
+#define XLNX_REQ_PWR_STATE_D0 0x00
+#define XLNX_REQ_PWR_STATE_D3 0x03
+
+/* Number of retries for USB operations */
+#define DWC3_PWR_STATE_RETRIES 1000
+#define DWC3_PWR_TIMEOUT 100
+
+#define DWC3_OF_ADDRESS(ADDR) ((ADDR) - DWC3_GLOBALS_REGS_START)
struct dwc3_of_simple {
struct device *dev;
struct clk_bulk_data *clks;
int num_clocks;
+ void __iomem *regs;
+ struct dwc3 *dwc;
+ struct phy *phy;
+ bool wakeup_capable;
+ bool dis_u3_susphy_quirk;
+ bool enable_d3_suspend;
+ char soc_rev;
struct reset_control *resets;
bool pulse_resets;
bool need_reset;
};
+int dwc3_enable_hw_coherency(struct device *dev)
+{
+ struct device_node *node = of_get_parent(dev->of_node);
+
+ if (of_device_is_compatible(node, "xlnx,zynqmp-dwc3")) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+ void __iomem *regs;
+ u32 reg;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+ regs = simple->regs;
+
+ reg = readl(regs + XLNX_USB_COHERENCY);
+ reg |= XLNX_USB_COHERENCY_ENABLE;
+ writel(reg, regs + XLNX_USB_COHERENCY);
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(dwc3_enable_hw_coherency);
+
+void dwc3_set_simple_data(struct dwc3 *dwc)
+{
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ if (node && of_device_is_compatible(node, "xlnx,zynqmp-dwc3")) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ /* Set (struct dwc3 *) to simple->dwc for future use */
+ simple->dwc = dwc;
+ }
+}
+EXPORT_SYMBOL(dwc3_set_simple_data);
+
+void dwc3_simple_check_quirks(struct dwc3 *dwc)
+{
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ if (node && of_device_is_compatible(node, "xlnx,zynqmp-dwc3")) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ /* Add snps,dis_u3_susphy_quirk */
+ dwc->dis_u3_susphy_quirk = simple->dis_u3_susphy_quirk;
+ }
+}
+EXPORT_SYMBOL(dwc3_simple_check_quirks);
+
+void dwc3_simple_wakeup_capable(struct device *dev, bool wakeup)
+{
+ struct device_node *node = of_node_get(dev->parent->of_node);
+
+ /* check for valid parent node */
+ while (node) {
+ if (!of_device_is_compatible(node, "xlnx,zynqmp-dwc3"))
+ node = of_get_next_parent(node);
+ else
+ break;
+ }
+
+ if (node) {
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+
+ /* Set wakeup capable as true or false */
+ simple->wakeup_capable = wakeup;
+
+ /* Allow D3 state if wakeup capable only */
+ simple->enable_d3_suspend = wakeup;
+ }
+}
+EXPORT_SYMBOL(dwc3_simple_wakeup_capable);
+
+static int dwc3_simple_set_phydata(struct dwc3_of_simple *simple)
+{
+ struct device *dev = simple->dev;
+ struct device_node *np = dev->of_node;
+ struct phy *phy;
+
+ np = of_get_next_child(np, NULL);
+
+ if (np) {
+ phy = of_phy_get(np, "usb3-phy");
+ if (IS_ERR(phy)) {
+ dev_err(dev, "%s: Can't find usb3-phy\n", __func__);
+ return PTR_ERR(phy);
+ }
+
+ /* Store phy for future usage */
+ simple->phy = phy;
+
+ /* assign USB vendor regs addr to phy platform_data */
+ phy->dev.platform_data = simple->regs;
+
+ phy_put(phy);
+ } else {
+ dev_err(dev, "%s: Can't find child node\n", __func__);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static int dwc3_of_simple_probe(struct platform_device *pdev)
{
struct dwc3_of_simple *simple;
@@ -47,6 +203,52 @@ static int dwc3_of_simple_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, simple);
simple->dev = dev;
+ if (of_device_is_compatible(pdev->dev.of_node,
+ "xlnx,zynqmp-dwc3")) {
+
+ char *soc_rev;
+ struct resource *res;
+ void __iomem *regs;
+
+ res = platform_get_resource(pdev,
+ IORESOURCE_MEM, 0);
+
+ regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(regs))
+ return PTR_ERR(regs);
+
+ /* Store the usb control regs into simple for further usage */
+ simple->regs = regs;
+
+ /* read Silicon version using nvmem driver */
+ soc_rev = zynqmp_nvmem_get_silicon_version(&pdev->dev,
+ "soc_revision");
+
+ if (PTR_ERR(soc_rev) == -EPROBE_DEFER) {
+ /* Do a deferred probe */
+ return -EPROBE_DEFER;
+
+ } else if (!IS_ERR(soc_rev) &&
+ (*soc_rev < ZYNQMP_SILICON_V4)) {
+ /* Add snps,dis_u3_susphy_quirk
+ * for SOC revison less than v4
+ */
+ simple->dis_u3_susphy_quirk = true;
+ }
+
+ /* Update soc_rev to simple for future use */
+ simple->soc_rev = *soc_rev;
+
+ /* Clean soc_rev if got a valid pointer from nvmem driver
+ * else we may end up in kernel panic
+ */
+ if (!IS_ERR(soc_rev))
+ kfree(soc_rev);
+ }
+
+ /* Set phy data for future use */
+ dwc3_simple_set_phydata(simple);
+
/*
* Some controllers need to toggle the usb3-otg reset before trying to
* initialize the PHY, otherwise the PHY times out.
@@ -132,6 +334,144 @@ static int dwc3_of_simple_remove(struct platform_device *pdev)
return 0;
}
+#ifdef CONFIG_PM
+static void dwc3_simple_vbus(struct dwc3 *dwc, bool vbus_off)
+{
+ u32 reg, addr;
+ u8 val;
+
+ if (vbus_off)
+ addr = ULPI_OTG_CTRL_CLEAR;
+ else
+ addr = ULPI_OTG_CTRL_SET;
+
+ val = (1 << OTG_CTRL_DRVVBUS_OFFSET);
+
+ reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_GUSB2PHYACC_ADDR(addr);
+ reg |= DWC3_GUSB2PHYACC_WRITE | val;
+
+ addr = DWC3_OF_ADDRESS(DWC3_GUSB2PHYACC(0));
+ writel(reg, dwc->regs + addr);
+}
+
+void dwc3_usb2phycfg(struct dwc3 *dwc, bool suspend)
+{
+ u32 addr, reg;
+
+ addr = DWC3_OF_ADDRESS(DWC3_GUSB2PHYCFG(0));
+
+ if (suspend) {
+ reg = readl(dwc->regs + addr);
+ if (!(reg & DWC3_GUSB2PHYCFG_SUSPHY)) {
+ reg |= DWC3_GUSB2PHYCFG_SUSPHY;
+ writel(reg, (dwc->regs + addr));
+ }
+ } else {
+ reg = readl(dwc->regs + addr);
+ if ((reg & DWC3_GUSB2PHYCFG_SUSPHY)) {
+ reg &= ~DWC3_GUSB2PHYCFG_SUSPHY;
+ writel(reg, (dwc->regs + addr));
+ }
+ }
+}
+
+int dwc3_set_usb_core_power(struct dwc3 *dwc, bool on)
+{
+ u32 reg, retries;
+ void __iomem *reg_base;
+ struct platform_device *pdev_parent;
+ struct dwc3_of_simple *simple;
+ struct device_node *node = of_get_parent(dwc->dev->of_node);
+
+ /* this is for Xilinx devices only */
+ if (!of_device_is_compatible(node, "xlnx,zynqmp-dwc3"))
+ return 0;
+
+ pdev_parent = of_find_device_by_node(node);
+ simple = platform_get_drvdata(pdev_parent);
+ reg_base = simple->regs;
+
+ /* Check if entering into D3 state is allowed during suspend */
+ if ((simple->soc_rev < ZYNQMP_SILICON_V4) || !simple->enable_d3_suspend)
+ return 0;
+
+ if (!simple->phy)
+ return 0;
+
+ if (on) {
+ dev_dbg(dwc->dev, "trying to set power state to D0....\n");
+
+ /* Release USB core reset , which was assert during D3 entry */
+ xpsgtr_usb_crst_release(simple->phy);
+
+ /* change power state to D0 */
+ writel(XLNX_REQ_PWR_STATE_D0,
+ reg_base + XLNX_USB_REQ_PWR_STATE);
+
+ /* wait till current state is changed to D0 */
+ retries = DWC3_PWR_STATE_RETRIES;
+ do {
+ reg = readl(reg_base + XLNX_USB_CUR_PWR_STATE);
+ if ((reg & XLNX_CUR_PWR_STATE_BITMASK) ==
+ XLNX_CUR_PWR_STATE_D0)
+ break;
+
+ udelay(DWC3_PWR_TIMEOUT);
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(dwc->dev, "Failed to set power state to D0\n");
+ return -EIO;
+ }
+
+ dwc->is_d3 = false;
+
+ /* Clear Suspend PHY bit if dis_u2_susphy_quirk is set */
+ if (dwc->dis_u2_susphy_quirk)
+ dwc3_usb2phycfg(dwc, false);
+ } else {
+ dev_dbg(dwc->dev, "Trying to set power state to D3...\n");
+
+ /*
+ * Set Suspend PHY bit before entering D3 if
+ * dis_u2_susphy_quirk is set
+ */
+ if (dwc->dis_u2_susphy_quirk)
+ dwc3_usb2phycfg(dwc, true);
+
+ /* enable PME to wakeup from hibernation */
+ writel(XLNX_PME_ENABLE_SIG_GEN, reg_base + XLNX_USB_PME_ENABLE);
+
+ /* change power state to D3 */
+ writel(XLNX_REQ_PWR_STATE_D3,
+ reg_base + XLNX_USB_REQ_PWR_STATE);
+
+ /* wait till current state is changed to D3 */
+ retries = DWC3_PWR_STATE_RETRIES;
+ do {
+ reg = readl(reg_base + XLNX_USB_CUR_PWR_STATE);
+ if ((reg & XLNX_CUR_PWR_STATE_BITMASK) ==
+ XLNX_CUR_PWR_STATE_D3)
+ break;
+
+ udelay(DWC3_PWR_TIMEOUT);
+ } while (--retries);
+
+ if (!retries) {
+ dev_err(dwc->dev, "Failed to set power state to D3\n");
+ return -EIO;
+ }
+
+ /* Assert USB core reset after entering D3 state */
+ xpsgtr_usb_crst_assert(simple->phy);
+
+ dwc->is_d3 = true;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL(dwc3_set_usb_core_power);
+#endif
static int __maybe_unused dwc3_of_simple_runtime_suspend(struct device *dev)
{
@@ -153,6 +493,13 @@ static int __maybe_unused dwc3_of_simple_suspend(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
+ if (!simple->wakeup_capable && !simple->dwc->is_d3) {
+ /* Ask ULPI to turn OFF Vbus */
+ dwc3_simple_vbus(simple->dwc, true);
+
+ clk_bulk_disable(simple->num_clocks, simple->clks);
+ }
+
if (simple->need_reset)
reset_control_assert(simple->resets);
@@ -162,11 +509,18 @@ static int __maybe_unused dwc3_of_simple_suspend(struct device *dev)
static int __maybe_unused dwc3_of_simple_resume(struct device *dev)
{
struct dwc3_of_simple *simple = dev_get_drvdata(dev);
+ int ret;
+
+ if (simple->wakeup_capable || simple->dwc->is_d3)
+ return 0;
+
+ ret = clk_bulk_enable(simple->num_clocks, simple->clks);
+ dwc3_simple_vbus(simple->dwc, false);
if (simple->need_reset)
reset_control_deassert(simple->resets);
- return 0;
+ return ret;
}
static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
@@ -178,6 +532,7 @@ static const struct dev_pm_ops dwc3_of_simple_dev_pm_ops = {
static const struct of_device_id of_dwc3_simple_match[] = {
{ .compatible = "rockchip,rk3399-dwc3" },
{ .compatible = "xlnx,zynqmp-dwc3" },
+ { .compatible = "xlnx,versal-dwc3" },
{ .compatible = "cavium,octeon-7130-usb-uctl" },
{ .compatible = "sprd,sc9860-dwc3" },
{ .compatible = "amlogic,meson-axg-dwc3" },
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index e8be2049a416..47d09b1e4a57 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -336,6 +336,11 @@ static int dwc3_ep0_handle_status(struct dwc3 *dwc,
usb_status |= 1 << USB_DEV_STAT_U2_ENABLED;
}
+ /* Sends the status indicating if the remote wakeup is
+ * supported by device.
+ */
+ usb_status |= dwc->remote_wakeup << USB_DEVICE_REMOTE_WAKEUP;
+
break;
case USB_RECIP_INTERFACE:
@@ -450,7 +455,12 @@ static int dwc3_ep0_handle_device(struct dwc3 *dwc,
switch (wValue) {
case USB_DEVICE_REMOTE_WAKEUP:
+ if (set)
+ dwc->remote_wakeup = 1;
+ else
+ dwc->remote_wakeup = 0;
break;
+
/*
* 9.4.1 says only only for SS, in AddressState only for
* default control pipe
@@ -467,6 +477,34 @@ static int dwc3_ep0_handle_device(struct dwc3 *dwc,
case USB_DEVICE_TEST_MODE:
ret = dwc3_ep0_handle_test(dwc, state, wIndex, set);
break;
+ case USB_DEVICE_B_HNP_ENABLE:
+ if (set) {
+ if (dwc->gadget.host_request_flag) {
+ struct usb_phy *phy =
+ usb_get_phy(USB_PHY_TYPE_USB3);
+
+ dwc->gadget.b_hnp_enable = 0;
+ dwc->gadget.host_request_flag = 0;
+ otg_start_hnp(phy->otg);
+ usb_put_phy(phy);
+ } else {
+ dwc->gadget.b_hnp_enable = 1;
+ }
+ } else
+ return -EINVAL;
+ break;
+
+ case USB_DEVICE_A_HNP_SUPPORT:
+ /* RH port supports HNP */
+ dev_dbg(dwc->dev,
+ "SET_FEATURE: USB_DEVICE_A_HNP_SUPPORT\n");
+ break;
+
+ case USB_DEVICE_A_ALT_HNP_SUPPORT:
+ /* other RH port does */
+ dev_dbg(dwc->dev,
+ "SET_FEATURE: USB_DEVICE_A_ALT_HNP_SUPPORT\n");
+ break;
default:
ret = -EINVAL;
}
@@ -745,7 +783,10 @@ static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl)
switch (ctrl->bRequest) {
case USB_REQ_GET_STATUS:
- ret = dwc3_ep0_handle_status(dwc, ctrl);
+ if (le16_to_cpu(ctrl->wIndex) == OTG_STS_SELECTOR)
+ ret = dwc3_ep0_delegate_req(dwc, ctrl);
+ else
+ ret = dwc3_ep0_handle_status(dwc, ctrl);
break;
case USB_REQ_CLEAR_FEATURE:
ret = dwc3_ep0_handle_feature(dwc, ctrl, 0);
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index 7a28048faa3e..f9886fb9360a 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -207,6 +207,9 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
{
struct dwc3 *dwc = dep->dwc;
+ if (dep->stream_capable && timer_pending(&req->stream_timeout_timer))
+ del_timer(&req->stream_timeout_timer);
+
dwc3_gadget_del_and_unmap_request(dep, req, status);
req->status = DWC3_REQUEST_STATUS_COMPLETED;
@@ -421,8 +424,7 @@ static int dwc3_send_clear_stall_ep_cmd(struct dwc3_ep *dep)
return dwc3_send_gadget_ep_cmd(dep, cmd, &params);
}
-static dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep,
- struct dwc3_trb *trb)
+dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep, struct dwc3_trb *trb)
{
u32 offset = (char *) trb - (char *) dep->trb_pool;
@@ -537,6 +539,19 @@ static int dwc3_gadget_start_config(struct dwc3_ep *dep)
return 0;
}
+static void stream_timeout_function(struct timer_list *arg)
+{
+ struct dwc3_request *req = from_timer(req, arg, stream_timeout_timer);
+ struct dwc3_ep *dep = req->dep;
+ struct dwc3 *dwc = dep->dwc;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dwc->lock, flags);
+ dwc3_stop_active_transfer(dep, true, false);
+ __dwc3_gadget_kick_transfer(dep);
+ spin_unlock_irqrestore(&dwc->lock, flags);
+}
+
static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
{
const struct usb_ss_ep_comp_descriptor *comp_desc;
@@ -570,7 +585,8 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) {
params.param1 |= DWC3_DEPCFG_STREAM_CAPABLE
- | DWC3_DEPCFG_STREAM_EVENT_EN;
+ | DWC3_DEPCFG_STREAM_EVENT_EN
+ | DWC3_DEPCFG_XFER_COMPLETE_EN;
dep->stream_capable = true;
}
@@ -608,7 +624,7 @@ static int dwc3_gadget_set_ep_config(struct dwc3_ep *dep, unsigned int action)
* Caller should take care of locking. Execute all necessary commands to
* initialize a HW endpoint so it can be used by a gadget driver.
*/
-static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
+int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
{
const struct usb_endpoint_descriptor *desc = dep->endpoint.desc;
struct dwc3 *dwc = dep->dwc;
@@ -616,7 +632,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
u32 reg;
int ret;
- if (!(dep->flags & DWC3_EP_ENABLED)) {
+ if (!(dep->flags & DWC3_EP_ENABLED) || dwc->is_hibernated) {
ret = dwc3_gadget_start_config(dep);
if (ret)
return ret;
@@ -626,7 +642,7 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
if (ret)
return ret;
- if (!(dep->flags & DWC3_EP_ENABLED)) {
+ if (!(dep->flags & DWC3_EP_ENABLED) || dwc->is_hibernated) {
struct dwc3_trb *trb_st_hw;
struct dwc3_trb *trb_link;
@@ -640,11 +656,13 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
if (usb_endpoint_xfer_control(desc))
goto out;
- /* Initialize the TRB ring */
- dep->trb_dequeue = 0;
- dep->trb_enqueue = 0;
- memset(dep->trb_pool, 0,
- sizeof(struct dwc3_trb) * DWC3_TRB_NUM);
+ if (!dwc->is_hibernated) {
+ /* Initialize the TRB ring */
+ dep->trb_dequeue = 0;
+ dep->trb_enqueue = 0;
+ memset(dep->trb_pool, 0,
+ sizeof(struct dwc3_trb) * DWC3_TRB_NUM);
+ }
/* Link TRB. The HWO bit is never reset */
trb_st_hw = &dep->trb_pool[0];
@@ -660,8 +678,8 @@ static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action)
* Issue StartTransfer here with no-op TRB so we can always rely on No
* Response Update Transfer command.
*/
- if ((usb_endpoint_xfer_bulk(desc) && !dep->stream_capable) ||
- usb_endpoint_xfer_int(desc)) {
+ if (((usb_endpoint_xfer_bulk(desc) && !dep->stream_capable) ||
+ usb_endpoint_xfer_int(desc)) && !dwc->is_hibernated) {
struct dwc3_gadget_ep_cmd_params params;
struct dwc3_trb *trb;
dma_addr_t trb_dma;
@@ -687,8 +705,6 @@ out:
return 0;
}
-static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
- bool interrupt);
static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)
{
struct dwc3_request *req;
@@ -725,7 +741,7 @@ static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep)
*
* Caller should take care of locking.
*/
-static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
+int __dwc3_gadget_ep_disable(struct dwc3_ep *dep)
{
struct dwc3 *dwc = dep->dwc;
u32 reg;
@@ -1002,6 +1018,16 @@ static void __dwc3_prepare_one_trb(struct dwc3_ep *dep, struct dwc3_trb *trb,
if (chain)
trb->ctrl |= DWC3_TRB_CTRL_CHN;
+ /*
+ * To start transfer on another stream number endpoint need to relase
+ * previously acquired transfer resource for doing that there is two
+ * ways 1. end transfer 2. set lst bit of control trb
+ *
+ * by using lst bit in ctrl trb we will be able to save the time of
+ * ending transfer hence improved performance
+ */
+ else if (dep->stream_capable)
+ trb->ctrl |= DWC3_TRB_CTRL_LST;
if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable)
trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(stream_id);
@@ -1219,7 +1245,7 @@ static void dwc3_prepare_trbs(struct dwc3_ep *dep)
static void dwc3_gadget_ep_cleanup_cancelled_requests(struct dwc3_ep *dep);
-static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
+int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
{
struct dwc3_gadget_ep_cmd_params params;
struct dwc3_request *req;
@@ -1249,8 +1275,12 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
if (dep->stream_capable)
cmd |= DWC3_DEPCMD_PARAM(req->request.stream_id);
+ if (dep->stream_capable)
+ cmd = cmd | DWC3_DEPCMD_PARAM(req->request.stream_id);
+
if (usb_endpoint_xfer_isoc(dep->endpoint.desc))
cmd |= DWC3_DEPCMD_PARAM(dep->frame_number);
+
} else {
cmd = DWC3_DEPCMD_UPDATETRANSFER |
DWC3_DEPCMD_PARAM(dep->resource_index);
@@ -1275,6 +1305,13 @@ static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep)
return ret;
}
+ if (starting && dep->stream_capable) {
+ req->stream_timeout_timer.expires = jiffies +
+ msecs_to_jiffies(STREAM_TIMEOUT_MS);
+ mod_timer(&req->stream_timeout_timer,
+ req->stream_timeout_timer.expires);
+ }
+
return 0;
}
@@ -1428,11 +1465,13 @@ static int __dwc3_gadget_start_isoc(struct dwc3_ep *dep)
ret = __dwc3_gadget_kick_transfer(dep);
if (ret != -EAGAIN)
break;
+ dep->flags &= ~DWC3_EP_PENDING_REQUEST;
}
return ret;
}
+static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc);
static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
{
struct dwc3 *dwc = dep->dwc;
@@ -1457,11 +1496,22 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
req->request.actual = 0;
req->request.status = -EINPROGRESS;
+ if (dep->stream_capable)
+ timer_setup(&req->stream_timeout_timer,
+ stream_timeout_function, 0);
+
trace_dwc3_ep_queue(req);
list_add_tail(&req->list, &dep->pending_list);
req->status = DWC3_REQUEST_STATUS_QUEUED;
+ /* If core is hibernated, need to wakeup (remote wakeup) */
+ if (dwc->is_hibernated) {
+ dwc->force_hiber_wake = true;
+ gadget_wakeup_interrupt(dwc);
+ dwc->force_hiber_wake = false;
+ }
+
/* Start the transfer only after the END_TRANSFER is completed */
if (dep->flags & DWC3_EP_END_TRANSFER_PENDING) {
dep->flags |= DWC3_EP_DELAY_START;
@@ -1478,13 +1528,22 @@ static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req)
*/
if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) {
if (!(dep->flags & DWC3_EP_PENDING_REQUEST) &&
- !(dep->flags & DWC3_EP_TRANSFER_STARTED))
+ !(dep->flags & DWC3_EP_TRANSFER_STARTED))
return 0;
- if ((dep->flags & DWC3_EP_PENDING_REQUEST)) {
- if (!(dep->flags & DWC3_EP_TRANSFER_STARTED)) {
- return __dwc3_gadget_start_isoc(dep);
+ if (dep->flags & DWC3_EP_PENDING_REQUEST) {
+ if (dep->flags & DWC3_EP_TRANSFER_STARTED) {
+ /*
+ * If there are not entries in request list
+ * then PENDING flag would be set, so that END
+ * TRANSFER is issued when an entry is added
+ * into request list.
+ */
+ dwc3_stop_active_transfer(dep, true, true);
+ dep->flags = DWC3_EP_ENABLED;
}
+ /* Rest is taken care by DWC3_DEPEVT_XFERNOTREADY */
+ return 0;
}
}
@@ -1565,6 +1624,9 @@ static int dwc3_gadget_ep_dequeue(struct usb_ep *ep,
spin_lock_irqsave(&dwc->lock, flags);
+ if (dep->stream_capable && timer_pending(&req->stream_timeout_timer))
+ del_timer(&req->stream_timeout_timer);
+
list_for_each_entry(r, &dep->pending_list, list) {
if (r == req)
break;
@@ -1843,7 +1905,7 @@ static int dwc3_gadget_set_selfpowered(struct usb_gadget *g,
return 0;
}
-static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
+int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend)
{
u32 reg;
u32 timeout = 500;
@@ -1918,7 +1980,7 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on)
return ret;
}
-static void dwc3_gadget_enable_irq(struct dwc3 *dwc)
+void dwc3_gadget_enable_irq(struct dwc3 *dwc)
{
u32 reg;
@@ -1932,13 +1994,17 @@ static void dwc3_gadget_enable_irq(struct dwc3 *dwc)
DWC3_DEVTEN_USBRSTEN |
DWC3_DEVTEN_DISCONNEVTEN);
+ /* Enable hibernation IRQ */
+ if (dwc->has_hibernation)
+ reg |= DWC3_DEVTEN_HIBERNATIONREQEVTEN;
+
if (dwc->revision < DWC3_REVISION_250A)
reg |= DWC3_DEVTEN_ULSTCNGEN;
dwc3_writel(dwc->regs, DWC3_DEVTEN, reg);
}
-static void dwc3_gadget_disable_irq(struct dwc3 *dwc)
+void dwc3_gadget_disable_irq(struct dwc3 *dwc)
{
/* mask all interrupts */
dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00);
@@ -2022,6 +2088,16 @@ static int __dwc3_gadget_start(struct dwc3 *dwc)
dwc3_gadget_setup_nump(dwc);
+ /* For OTG mode, check if the core is currently in Host mode.
+ * This is not an error condition as there are times when the core is
+ * working as host and kernel is told to initiate bind operation with
+ * gadget class driver module.
+ * The below remaining operations are handled in OTG driver whenever
+ * required.
+ */
+ if (dwc3_readl(dwc->regs, DWC3_GSTS) & DWC3_GSTS_CUR_MODE)
+ return 0;
+
/* Start with SuperSpeed Default */
dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512);
@@ -2055,6 +2131,7 @@ err0:
return ret;
}
+static irqreturn_t wakeup_interrupt(int irq, void *_dwc);
static int dwc3_gadget_start(struct usb_gadget *g,
struct usb_gadget_driver *driver)
{
@@ -2072,6 +2149,18 @@ static int dwc3_gadget_start(struct usb_gadget *g,
goto err0;
}
+ /* look for wakeup interrupt if hibernation is supported */
+ if (dwc->has_hibernation) {
+ irq = dwc->irq_wakeup;
+ ret = devm_request_irq(dwc->dev, irq, wakeup_interrupt,
+ IRQF_SHARED, "usb-wakeup", dwc);
+ if (ret) {
+ dev_err(dwc->dev, "failed to request wakeup irq #%d --> %d\n",
+ irq, ret);
+ goto err0;
+ }
+ }
+
spin_lock_irqsave(&dwc->lock, flags);
if (dwc->gadget_driver) {
dev_err(dwc->dev, "%s is already bound to %s\n",
@@ -2092,7 +2181,10 @@ static int dwc3_gadget_start(struct usb_gadget *g,
err1:
spin_unlock_irqrestore(&dwc->lock, flags);
- free_irq(irq, dwc);
+ if (dwc->irq_gadget)
+ free_irq(dwc->irq_gadget, dwc->ev_buf);
+ if (dwc->irq_wakeup)
+ free_irq(dwc->irq_wakeup, dwc);
err0:
return ret;
@@ -2122,6 +2214,7 @@ out:
spin_unlock_irqrestore(&dwc->lock, flags);
free_irq(dwc->irq_gadget, dwc->ev_buf);
+ free_irq(dwc->irq_wakeup, dwc);
return 0;
}
@@ -2459,7 +2552,11 @@ static int dwc3_gadget_ep_reclaim_completed_trb(struct dwc3_ep *dep,
if (event->status & DEPEVT_STATUS_SHORT && !chain)
return 1;
- if ((trb->ctrl & DWC3_TRB_CTRL_IOC) ||
+ if ((event->status & DEPEVT_STATUS_IOC) &&
+ (trb->ctrl & DWC3_TRB_CTRL_IOC))
+ return 1;
+
+ if ((event->status & DEPEVT_STATUS_LST) &&
(trb->ctrl & DWC3_TRB_CTRL_LST))
return 1;
@@ -2528,9 +2625,13 @@ static int dwc3_gadget_ep_cleanup_completed_request(struct dwc3_ep *dep,
req->request.actual = req->request.length - req->remaining;
- if (!dwc3_gadget_ep_request_completed(req)) {
- __dwc3_gadget_kick_transfer(dep);
- goto out;
+ if ((!dwc3_gadget_ep_request_completed(req) &&
+ req->num_pending_sgs) || req->num_pending_sgs) {
+ if (!(event->status &
+ (DEPEVT_STATUS_SHORT | DEPEVT_STATUS_LST))) {
+ __dwc3_gadget_kick_transfer(dep);
+ goto out;
+ }
}
dwc3_gadget_giveback(dep, req, status);
@@ -2573,10 +2674,26 @@ static void dwc3_gadget_endpoint_transfer_in_progress(struct dwc3_ep *dep,
if (event->status & DEPEVT_STATUS_BUSERR)
status = -ECONNRESET;
- if (event->status & DEPEVT_STATUS_MISSED_ISOC) {
+ if ((event->status & DEPEVT_STATUS_MISSED_ISOC) &&
+ usb_endpoint_xfer_isoc(dep->endpoint.desc))
status = -EXDEV;
- if (list_empty(&dep->started_list))
+ dwc3_gadget_ep_cleanup_completed_requests(dep, event, status);
+
+ if (dep->stream_capable && !list_empty(&dep->started_list))
+ __dwc3_gadget_kick_transfer(dep);
+
+ if (usb_endpoint_xfer_isoc(dep->endpoint.desc) &&
+ list_empty(&dep->started_list)) {
+ if (list_empty(&dep->pending_list))
+ /*
+ * If there is no entry in request list then do
+ * not issue END TRANSFER now. Just set PENDING
+ * flag, so that END TRANSFER is issued when an
+ * entry is added into request list.
+ */
+ dep->flags |= DWC3_EP_PENDING_REQUEST;
+ else
stop = true;
}
@@ -2618,6 +2735,28 @@ static void dwc3_gadget_endpoint_transfer_not_ready(struct dwc3_ep *dep,
(void) __dwc3_gadget_start_isoc(dep);
}
+static void dwc3_endpoint_stream_event(struct dwc3 *dwc,
+ const struct dwc3_event_depevt *event)
+{
+ struct dwc3_ep *dep;
+ struct dwc3_request *req;
+ u8 epnum = event->endpoint_number;
+ u8 stream_id;
+
+ dep = dwc->eps[epnum];
+
+ stream_id = event->parameters;
+
+ /* Check for request matching the streamid and delete the timer */
+ list_for_each_entry(req, &dep->started_list, list) {
+ if (req->request.stream_id == stream_id) {
+ if (timer_pending(&req->stream_timeout_timer))
+ del_timer(&req->stream_timeout_timer);
+ break;
+ }
+ }
+}
+
static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
const struct dwc3_event_depevt *event)
{
@@ -2642,12 +2781,21 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
}
switch (event->endpoint_event) {
+ case DWC3_DEPEVT_XFERCOMPLETE:
+ if (!dep->stream_capable)
+ break;
+ dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
+ /* Fall Through */
case DWC3_DEPEVT_XFERINPROGRESS:
dwc3_gadget_endpoint_transfer_in_progress(dep, event);
break;
case DWC3_DEPEVT_XFERNOTREADY:
dwc3_gadget_endpoint_transfer_not_ready(dep, event);
break;
+ case DWC3_DEPEVT_STREAMEVT:
+ if (event->status == DEPEVT_STREAMEVT_FOUND)
+ dwc3_endpoint_stream_event(dwc, event);
+ break;
case DWC3_DEPEVT_EPCMDCMPLT:
cmd = DEPEVT_PARAMETER_CMD(event->parameters);
@@ -2662,8 +2810,6 @@ static void dwc3_endpoint_interrupt(struct dwc3 *dwc,
dep->flags &= ~DWC3_EP_DELAY_START;
}
break;
- case DWC3_DEPEVT_STREAMEVT:
- case DWC3_DEPEVT_XFERCOMPLETE:
case DWC3_DEPEVT_RXTXFIFOEVT:
break;
}
@@ -2708,7 +2854,7 @@ static void dwc3_reset_gadget(struct dwc3 *dwc)
}
}
-static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
+void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
bool interrupt)
{
struct dwc3 *dwc = dep->dwc;
@@ -2760,6 +2906,13 @@ static void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force,
WARN_ON_ONCE(ret);
dep->resource_index = 0;
+ /*
+ * when transfer is stopped with force rm bit false, it can be
+ * restarted by passing resource_index in params; don't loose it
+ */
+ if (force)
+ dep->resource_index = 0;
+
if (!interrupt)
dep->flags &= ~DWC3_EP_TRANSFER_STARTED;
else
@@ -2804,6 +2957,15 @@ static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc)
dwc3_disconnect_gadget(dwc);
+ /* In USB 2.0, to avoid hibernation interrupt at the time of connection
+ * clear DWC3_DCTL_KEEP_CONNECT bit.
+ */
+ if (dwc->has_hibernation) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg &= ~DWC3_DCTL_KEEP_CONNECT;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ }
+
dwc->gadget.speed = USB_SPEED_UNKNOWN;
dwc->setup_packet_pending = false;
usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED);
@@ -2977,6 +3139,16 @@ static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc)
}
/*
+ * In USB 2.0, to avoid hibernation interrupt at the time of connection
+ * set DWC3_DCTL_KEEP_CONNECT bit here
+ */
+ if (dwc->has_hibernation) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg |= DWC3_DCTL_KEEP_CONNECT;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ }
+
+ /*
* Configure PHY via GUSB3PIPECTLn if required.
*
* Update GTXFIFOSIZn
@@ -2999,6 +3171,17 @@ static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc)
}
}
+static irqreturn_t wakeup_interrupt(int irq, void *_dwc)
+{
+ struct dwc3 *dwc = (struct dwc3 *)_dwc;
+
+ spin_lock(&dwc->lock);
+ gadget_wakeup_interrupt(dwc);
+ spin_unlock(&dwc->lock);
+
+ return IRQ_HANDLED;
+}
+
static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc,
unsigned int evtinfo)
{
@@ -3126,10 +3309,12 @@ static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc,
* STAR#9000546576: Device Mode Hibernation: Issue in USB 2.0
* Device Fallback from SuperSpeed
*/
- if (is_ss ^ (dwc->speed == USB_SPEED_SUPER))
+ if ((!!is_ss ^ (dwc->speed >= DWC3_DSTS_SUPERSPEED)) &&
+ (!(dwc->has_hibernation)))
return;
/* enter hibernation here */
+ gadget_hibernation_interrupt(dwc);
}
static void dwc3_gadget_interrupt(struct dwc3 *dwc,
@@ -3223,12 +3408,18 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt)
*/
evt->lpos = (evt->lpos + 4) % evt->length;
left -= 4;
+
+ if (dwc->is_hibernated)
+ break;
}
evt->count = 0;
evt->flags &= ~DWC3_EVENT_PENDING;
ret = IRQ_HANDLED;
+ if (dwc->is_hibernated)
+ return ret;
+
/* Unmask interrupt */
reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(0));
reg &= ~DWC3_GEVNTSIZ_INTMASK;
@@ -3270,6 +3461,9 @@ static irqreturn_t dwc3_check_event_buf(struct dwc3_event_buffer *evt)
return IRQ_HANDLED;
}
+ if (dwc->is_hibernated)
+ return IRQ_HANDLED;
+
/*
* With PCIe legacy interrupt, test shows that top-half irq handler can
* be called again after HW interrupt deassertion. Check if bottom-half
@@ -3313,7 +3507,7 @@ static irqreturn_t dwc3_interrupt(int irq, void *_evt)
static int dwc3_gadget_get_irq(struct dwc3 *dwc)
{
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
- int irq;
+ int irq, irq_hiber;
irq = platform_get_irq_byname(dwc3_pdev, "peripheral");
if (irq > 0)
@@ -3331,15 +3525,23 @@ static int dwc3_gadget_get_irq(struct dwc3 *dwc)
irq = platform_get_irq(dwc3_pdev, 0);
if (irq > 0)
- goto out;
-
- if (irq != -EPROBE_DEFER)
- dev_err(dwc->dev, "missing peripheral IRQ\n");
-
- if (!irq)
- irq = -EINVAL;
+ dwc->irq_gadget = irq;
+ if (irq == -EPROBE_DEFER)
+ goto out;
out:
+ /* look for wakeup interrupt if hibernation is supported */
+ if (dwc->has_hibernation) {
+ irq_hiber = platform_get_irq_byname(dwc3_pdev, "hiber");
+ if (irq_hiber > 0) {
+ dwc->irq_wakeup = irq_hiber;
+ } else {
+ irq_hiber = platform_get_irq(dwc3_pdev, 2);
+ if (irq_hiber > 0)
+ dwc->irq_wakeup = irq_hiber;
+ }
+ }
+
return irq;
}
@@ -3433,6 +3635,28 @@ int dwc3_gadget_init(struct dwc3 *dwc)
dwc3_gadget_set_speed(&dwc->gadget, dwc->maximum_speed);
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ struct usb_phy *phy;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (!IS_ERR(phy)) {
+ if (phy && phy->otg) {
+ ret = otg_set_peripheral(phy->otg,
+ &dwc->gadget);
+ if (ret) {
+ dev_err(dwc->dev,
+ "otg_set_peripheral failed\n");
+ usb_put_phy(phy);
+ phy = NULL;
+ goto err4;
+ }
+ } else {
+ usb_put_phy(phy);
+ phy = NULL;
+ }
+ }
+ }
+
return 0;
err4:
@@ -3471,6 +3695,16 @@ int dwc3_gadget_suspend(struct dwc3 *dwc)
if (!dwc->gadget_driver)
return 0;
+ if (dwc->is_hibernated) {
+ /*
+ * As we are about to suspend, wake the controller from
+ * D3 & hibernation states
+ */
+ dwc->force_hiber_wake = true;
+ gadget_wakeup_interrupt(dwc);
+ dwc->force_hiber_wake = false;
+ }
+
dwc3_gadget_run_stop(dwc, false, false);
dwc3_disconnect_gadget(dwc);
__dwc3_gadget_stop(dwc);
@@ -3481,6 +3715,7 @@ int dwc3_gadget_suspend(struct dwc3 *dwc)
int dwc3_gadget_resume(struct dwc3 *dwc)
{
int ret;
+ u32 reg;
if (!dwc->gadget_driver)
return 0;
@@ -3493,6 +3728,15 @@ int dwc3_gadget_resume(struct dwc3 *dwc)
if (ret < 0)
goto err1;
+ /* In USB 2.0, to avoid hibernation interrupt at the time of connection
+ * set DWC3_DCTL_KEEP_CONNECT bit.
+ */
+ if (dwc->has_hibernation) {
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg |= DWC3_DCTL_KEEP_CONNECT;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ }
+
return 0;
err1:
diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h
index 3ed738e86ea7..47a275b0184f 100644
--- a/drivers/usb/dwc3/gadget.h
+++ b/drivers/usb/dwc3/gadget.h
@@ -48,6 +48,14 @@ struct dwc3;
/* DEPXFERCFG parameter 0 */
#define DWC3_DEPXFERCFG_NUM_XFER_RES(n) ((n) & 0xffff)
+/* Below used in hibernation */
+#define DWC3_NON_STICKY_RESTORE_RETRIES 500
+#define DWC3_NON_STICKY_SAVE_RETRIES 500
+#define DWC3_DEVICE_CTRL_READY_RETRIES 20000
+#define DWC3_NON_STICKY_RESTORE_DELAY 100
+#define DWC3_NON_STICKY_SAVE_DELAY 100
+#define DWC3_DEVICE_CTRL_READY_DELAY 5
+
/* -------------------------------------------------------------------------- */
#define to_dwc3_request(r) (container_of(r, struct dwc3_request, request))
@@ -100,11 +108,21 @@ void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req,
void dwc3_ep0_interrupt(struct dwc3 *dwc,
const struct dwc3_event_depevt *event);
void dwc3_ep0_out_start(struct dwc3 *dwc);
+void dwc3_gadget_enable_irq(struct dwc3 *dwc);
+void dwc3_gadget_disable_irq(struct dwc3 *dwc);
int __dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value);
int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value);
int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request,
gfp_t gfp_flags);
int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol);
+int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, unsigned int action);
+int __dwc3_gadget_ep_disable(struct dwc3_ep *dep);
+int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep);
+void dwc3_stop_active_transfer(struct dwc3_ep *dep, bool force, bool interrupt);
+int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend);
+dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep, struct dwc3_trb *trb);
+void gadget_hibernation_interrupt(struct dwc3 *dwc);
+void gadget_wakeup_interrupt(struct dwc3 *dwc);
/**
* dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW
diff --git a/drivers/usb/dwc3/gadget_hibernation.c b/drivers/usb/dwc3/gadget_hibernation.c
new file mode 100644
index 000000000000..3f6a98150764
--- /dev/null
+++ b/drivers/usb/dwc3/gadget_hibernation.c
@@ -0,0 +1,567 @@
+/**
+ * gadget_hibernation.c - DesignWare USB3 DRD Controller gadget hibernation file
+ *
+ * This file has routines to handle hibernation and wakeup events in gadget mode
+ *
+ * Author: Mayank Adesara <madesara@xilinx.com>
+ * Author: Anurag Kumar Vulisha <anuragku@xilinx.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "core.h"
+#include "gadget.h"
+#include "debug.h"
+#include "io.h"
+
+/* array of registers to save on hibernation and restore them on wakeup */
+static u32 save_reg_addr[] = {
+ DWC3_DCTL,
+ DWC3_DCFG,
+ DWC3_DEVTEN
+};
+
+/*
+ * wait_timeout - Waits until timeout
+ * @wait_time: time to wait in jiffies
+ */
+static void wait_timeout(unsigned long wait_time)
+{
+ unsigned long timeout = jiffies + wait_time;
+
+ while (!time_after_eq(jiffies, timeout))
+ cpu_relax();
+}
+
+/**
+ * save_regs - Saves registers on hibernation
+ * @dwc: pointer to our controller context structure
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+static int save_regs(struct dwc3 *dwc)
+{
+ int i;
+
+ if (!dwc->saved_regs) {
+ dwc->saved_regs = devm_kmalloc(dwc->dev,
+ sizeof(save_reg_addr),
+ GFP_KERNEL);
+ if (!dwc->saved_regs) {
+ dev_err(dwc->dev, "Not enough memory to save regs\n");
+ return -ENOMEM;
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(save_reg_addr); i++)
+ dwc->saved_regs[i] = dwc3_readl(dwc->regs,
+ save_reg_addr[i]);
+ return 0;
+}
+
+/**
+ * restore_regs - Restores registers on wakeup
+ * @dwc: pointer to our controller context structure
+ */
+static void restore_regs(struct dwc3 *dwc)
+{
+ int i;
+
+ if (!dwc->saved_regs) {
+ dev_warn(dwc->dev, "Regs not saved\n");
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(save_reg_addr); i++)
+ dwc3_writel(dwc->regs, save_reg_addr[i],
+ dwc->saved_regs[i]);
+}
+
+/**
+ * restart_ep0_trans - Restarts EP0 transfer on wakeup
+ * @dwc: pointer to our controller context structure
+ * epnum: endpoint number
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+static int restart_ep0_trans(struct dwc3 *dwc, int epnum)
+{
+ struct dwc3_ep *dep = dwc->eps[epnum];
+ struct dwc3_trb *trb = dwc->ep0_trb;
+ struct dwc3_gadget_ep_cmd_params params;
+ int ret;
+ u32 cmd;
+
+ memset(&params, 0, sizeof(params));
+ params.param0 = upper_32_bits(dwc->ep0_trb_addr);
+ params.param1 = lower_32_bits(dwc->ep0_trb_addr);
+
+ /* set HWO bit back to 1 and restart transfer */
+ trb->ctrl |= DWC3_TRB_CTRL_HWO;
+
+ /* Clear the TRBSTS feild */
+ trb->size &= ~(0x0F << 28);
+
+ cmd = DWC3_DEPCMD_STARTTRANSFER | DWC3_DEPCMD_PARAM(0);
+ ret = dwc3_send_gadget_ep_cmd(dep, cmd, &params);
+ if (ret < 0) {
+ dev_err(dwc->dev, "failed to restart transfer on %s\n",
+ dep->name);
+ return ret;
+ }
+
+ dwc3_gadget_ep_get_transfer_index(dep);
+
+ return 0;
+}
+
+extern dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep,
+ struct dwc3_trb *trb);
+/**
+ * restore_eps - Restores non EP0 eps in the same state as they were before
+ * hibernation
+ * @dwc: pointer to our controller context structure
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+static int restore_eps(struct dwc3 *dwc)
+{
+ int epnum, ret;
+
+ for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+ /* Enable the endpoint */
+ struct dwc3_ep *dep = dwc->eps[epnum];
+
+ if (!dep)
+ continue;
+
+ if (!(dep->flags & DWC3_EP_ENABLED))
+ continue;
+
+ ret = __dwc3_gadget_ep_enable(dep, true);
+ if (ret) {
+ dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+ return ret;
+ }
+ }
+
+ for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+ struct dwc3_ep *dep = dwc->eps[epnum];
+
+ if (!dep)
+ continue;
+
+ if (!(dep->flags & DWC3_EP_ENABLED))
+ continue;
+
+ if (dep->flags & DWC3_EP_STALL) {
+ /* Set stall for the endpoint */
+ struct dwc3_gadget_ep_cmd_params params;
+
+ memset(&params, 0x00, sizeof(params));
+
+ ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETSTALL,
+ &params);
+ if (ret) {
+ dev_err(dwc->dev, "failed to set STALL on %s\n",
+ dep->name);
+ return ret;
+ }
+ } else {
+ u32 cmd;
+ struct dwc3_gadget_ep_cmd_params params;
+ struct dwc3_trb *trb;
+ u8 trb_dequeue = dep->trb_dequeue;
+
+ trb = &dep->trb_pool[trb_dequeue];
+
+ /*
+ * check the last processed TRBSTS field has value
+ * 4 (TRBInProgress), if yes resubmit the same TRB
+ */
+ if (DWC3_TRB_SIZE_TRBSTS(trb->size) ==
+ DWC3_TRB_STS_XFER_IN_PROG) {
+ /* Set the HWO bit */
+ trb->ctrl |= DWC3_TRB_CTRL_HWO;
+
+ /* Clear the TRBSTS field */
+ trb->size &= ~(0x0F << 28);
+
+ memset(&params, 0, sizeof(params));
+
+ /* Issue starttransfer */
+ params.param0 =
+ upper_32_bits(dwc3_trb_dma_offset(dep,
+ trb));
+ params.param1 =
+ lower_32_bits(dwc3_trb_dma_offset(dep,
+ trb));
+
+ cmd = DWC3_DEPCMD_STARTTRANSFER |
+ DWC3_DEPCMD_PARAM(0);
+
+ dwc3_send_gadget_ep_cmd(dep, cmd, &params);
+
+ dwc3_gadget_ep_get_transfer_index(dep);
+ } else {
+ ret = __dwc3_gadget_kick_transfer(dep);
+ if (ret) {
+ dev_err(dwc->dev,
+ "%s: restart transfer failed\n",
+ dep->name);
+ return ret;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * restore_ep0 - Restores EP0 in the same state as they were before hibernation
+ * @dwc: pointer to our controller context structure
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+static int restore_ep0(struct dwc3 *dwc)
+{
+ int epnum, ret;
+
+ for (epnum = 0; epnum < 2; epnum++) {
+ struct dwc3_ep *dep = dwc->eps[epnum];
+
+ if (!dep)
+ continue;
+
+ if (!(dep->flags & DWC3_EP_ENABLED))
+ continue;
+
+ ret = __dwc3_gadget_ep_enable(dep, true);
+ if (ret) {
+ dev_err(dwc->dev, "failed to enable %s\n", dep->name);
+ return ret;
+ }
+
+ if (dep->flags & DWC3_EP_STALL) {
+ struct dwc3_gadget_ep_cmd_params params;
+
+ memset(&params, 0x00, sizeof(params));
+
+ ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETSTALL,
+ &params);
+ if (ret) {
+ dev_err(dwc->dev, "failed to set STALL on %s\n",
+ dep->name);
+ return ret;
+ }
+ } else {
+ if (!dep->resource_index && epnum)
+ continue;
+
+ ret = restart_ep0_trans(dwc, epnum);
+ if (ret) {
+ dev_err(dwc->dev,
+ "failed to restart transfer on: %s\n",
+ dep->name);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * save_endpoint_state - Saves ep state on hibernation
+ * @dep: endpoint to get state
+ *
+ * Returns 0 on success otherwise negative errno.
+ */
+static int save_endpoint_state(struct dwc3_ep *dep)
+{
+ struct dwc3 *dwc = dep->dwc;
+ struct dwc3_gadget_ep_cmd_params params;
+ int ret;
+
+ memset(&params, 0, sizeof(params));
+ ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_GETEPSTATE,
+ &params);
+ if (ret) {
+ dev_err(dwc->dev, "Failed to get endpoint state on %s\n",
+ dep->name);
+ return ret;
+ }
+
+ dep->saved_state = dwc3_readl(dep->regs, DWC3_DEPCMDPAR2);
+ return 0;
+}
+
+/**
+ * gadget_hibernation_interrupt - Interrupt handler of hibernation
+ * @dwc: pointer to our controller context structure
+ */
+void gadget_hibernation_interrupt(struct dwc3 *dwc)
+{
+ u32 epnum, reg;
+ int retries, ret;
+
+ /* Check if the link state is valid before hibernating */
+ switch (dwc3_gadget_get_link_state(dwc)) {
+ case DWC3_LINK_STATE_U3:
+ case DWC3_LINK_STATE_SS_DIS:
+ break;
+ default:
+ dev_dbg(dwc->dev,
+ "%s: Got fake hiber event\n", __func__);
+ return;
+ }
+
+ /* stop all active transfers and save endpoint status */
+ for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) {
+ struct dwc3_ep *dep = dwc->eps[epnum];
+
+ if (!dep)
+ continue;
+
+ if (!(dep->flags & DWC3_EP_ENABLED))
+ continue;
+
+ if (dep->flags & DWC3_EP_TRANSFER_STARTED)
+ dwc3_stop_active_transfer(dep, false, true);
+
+ save_endpoint_state(dep);
+ }
+
+ /* stop the controller */
+ dwc3_gadget_run_stop(dwc, false, true);
+ dwc->is_hibernated = true;
+
+ /*
+ * ack events, don't process them; h/w decrements the count by the value
+ * written
+ */
+ reg = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(0));
+ dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), reg);
+ dwc->ev_buf->count = 0;
+ dwc->ev_buf->flags &= ~DWC3_EVENT_PENDING;
+
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+
+ /* disable keep connect if we are disconnected right now */
+ if (dwc3_gadget_get_link_state(dwc) == DWC3_LINK_STATE_SS_DIS) {
+ reg &= ~DWC3_DCTL_KEEP_CONNECT;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ } else {
+ reg |= DWC3_DCTL_KEEP_CONNECT;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ }
+
+ /* save generic registers */
+ save_regs(dwc);
+
+ /* initiate controller save state */
+ reg |= DWC3_DCTL_CSS;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+ /* wait till controller saves state */
+ retries = DWC3_NON_STICKY_SAVE_RETRIES;
+ do {
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ if (!(reg & DWC3_DSTS_SSS))
+ break;
+
+ udelay(DWC3_NON_STICKY_SAVE_DELAY);
+ } while (--retries);
+
+ if (retries < 0) {
+ dev_err(dwc->dev, "USB core failed to save state\n");
+ goto err;
+ }
+
+ /* Set the controller as wakeup capable */
+ dwc3_simple_wakeup_capable(dwc->dev, true);
+
+ /* set USB core power state to D3 - power down */
+ ret = dwc3_set_usb_core_power(dwc, false);
+ if (ret < 0) {
+ dev_err(dwc->dev, "%s: Failed to hibernate\n", __func__);
+ /* call wakeup handler */
+ gadget_wakeup_interrupt(dwc);
+ return;
+ }
+
+ dev_info(dwc->dev, "Hibernated!\n");
+ return;
+
+err:
+ dev_err(dwc->dev, "Fail in handling Hibernation Interrupt\n");
+}
+
+/**
+ * gadget_wakeup_interrupt - Interrupt handler of wakeup
+ * @dwc: pointer to our controller context structure
+ */
+void gadget_wakeup_interrupt(struct dwc3 *dwc)
+{
+ u32 reg, link_state;
+ int ret, retries;
+ bool enter_hiber = false;
+
+ /* On USB 2.0 we observed back to back wakeup interrupts */
+ if (!dwc->is_hibernated) {
+ dev_err(dwc->dev, "Not in hibernated state\n");
+ goto err;
+ }
+
+ /* Restore power to USB core */
+ if (dwc3_set_usb_core_power(dwc, true)) {
+ dev_err(dwc->dev, "Failed to restore USB core power\n");
+ goto err;
+ }
+
+ /* Clear the controller wakeup capable flag */
+ dwc3_simple_wakeup_capable(dwc->dev, false);
+
+ /* Initialize the core and restore the saved registers */
+ dwc3_core_init(dwc);
+ restore_regs(dwc);
+
+ /* ask controller to save the non-sticky registers */
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg |= DWC3_DCTL_CRS;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+
+ /* Wait till non-sticky registers are restored */
+ retries = DWC3_NON_STICKY_RESTORE_RETRIES;
+ do {
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ if (!(reg & DWC3_DSTS_RSS))
+ break;
+
+ udelay(DWC3_NON_STICKY_RESTORE_DELAY);
+ } while (--retries);
+
+ if (retries < 0 || (reg & DWC3_DSTS_SRE)) {
+ dev_err(dwc->dev, "Failed to restore non-sticky regs\n");
+ goto err;
+ }
+
+ /* restore ep0 endpoints */
+ ret = restore_ep0(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "Failed in restorig EP0 states\n");
+ goto err;
+ }
+
+ /* start the controller */
+ ret = dwc3_gadget_run_stop(dwc, true, false);
+ if (ret < 0) {
+ dev_err(dwc->dev, "USB core failed to start on wakeup\n");
+ goto err;
+ }
+
+ /* Wait until device controller is ready */
+ retries = DWC3_DEVICE_CTRL_READY_RETRIES;
+ while (--retries) {
+ reg = dwc3_readl(dwc->regs, DWC3_DSTS);
+ if (reg & DWC3_DSTS_DCNRD)
+ udelay(DWC3_DEVICE_CTRL_READY_DELAY);
+ else
+ break;
+ }
+
+ if (retries < 0) {
+ dev_err(dwc->dev, "USB core failed to restore controller\n");
+ goto err;
+ }
+
+ /*
+ * As some suprious signals also cause wakeup event, wait for some time
+ * and check the link state to confirm if the wakeup signal is real
+ */
+ wait_timeout(msecs_to_jiffies(10));
+
+ link_state = dwc3_gadget_get_link_state(dwc);
+
+ /* check if the link state is in a valid state */
+ switch (link_state) {
+ case DWC3_LINK_STATE_RESET:
+ /* Reset devaddr */
+ reg = dwc3_readl(dwc->regs, DWC3_DCFG);
+ reg &= ~(DWC3_DCFG_DEVADDR_MASK);
+ dwc3_writel(dwc->regs, DWC3_DCFG, reg);
+
+ /* issue recovery on the link */
+ ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
+ if (ret < 0) {
+ dev_err(dwc->dev,
+ "Failed to set link state to Recovery\n");
+ goto err;
+ }
+
+ break;
+
+ case DWC3_LINK_STATE_SS_DIS:
+ /* Clear keep connect from reconnecting to HOST */
+ reg = dwc3_readl(dwc->regs, DWC3_DCTL);
+ reg &= ~DWC3_DCTL_KEEP_CONNECT;
+ dwc3_writel(dwc->regs, DWC3_DCTL, reg);
+ /* fall through */
+ case DWC3_LINK_STATE_U3:
+ /* Ignore wakeup event as the link is still in U3 state */
+ dev_dbg(dwc->dev, "False wakeup event %d\n", link_state);
+
+ if (!dwc->force_hiber_wake)
+ enter_hiber = true;
+ break;
+
+ default:
+ /* issue recovery on the link */
+ ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV);
+ if (ret < 0) {
+ dev_err(dwc->dev,
+ "Failed to set link state to Recovery\n");
+ goto err;
+ }
+
+ break;
+ }
+
+ if (link_state != DWC3_LINK_STATE_SS_DIS) {
+ /* Restore non EP0 EPs */
+ ret = restore_eps(dwc);
+ if (ret) {
+ dev_err(dwc->dev, "Failed restoring non-EP0 states\n");
+ goto err;
+ }
+ }
+
+ /* clear the flag */
+ dwc->is_hibernated = false;
+
+ if (enter_hiber) {
+ /*
+ * as the wakeup was because of the spurious signals,
+ * enter hibernation again
+ */
+ gadget_hibernation_interrupt(dwc);
+ return;
+ }
+
+ dev_info(dwc->dev, "We are back from hibernation!\n");
+ return;
+
+err:
+ dev_err(dwc->dev, "Fail in handling Wakeup Interrupt\n");
+}
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 4252fad1d184..56e02a561a0b 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -8,9 +8,17 @@
*/
#include <linux/platform_device.h>
+#include <linux/of_device.h>
+#include <linux/usb/xhci_pdriver.h>
#include "core.h"
+void dwc3_host_wakeup_capable(struct device *dev, bool wakeup)
+{
+ dwc3_simple_wakeup_capable(dev, wakeup);
+}
+EXPORT_SYMBOL(dwc3_host_wakeup_capable);
+
static int dwc3_host_get_irq(struct dwc3 *dwc)
{
struct platform_device *dwc3_pdev = to_platform_device(dwc->dev);
@@ -96,6 +104,10 @@ int dwc3_host_init(struct dwc3 *dwc)
if (dwc->usb2_lpm_disable)
props[prop_idx++] = PROPERTY_ENTRY_BOOL("usb2-lpm-disable");
+ if (device_property_read_bool(&dwc3_pdev->dev,
+ "snps,xhci-stream-quirk"))
+ props[prop_idx++].name = "xhci-stream-quirk";
+
/**
* WORKAROUND: dwc3 revisions <=3.00a have a limitation
* where Port Disable command doesn't work.
@@ -121,6 +133,17 @@ int dwc3_host_init(struct dwc3 *dwc)
phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy",
dev_name(dwc->dev));
+ if (dwc->dr_mode == USB_DR_MODE_OTG) {
+ struct usb_phy *phy = usb_get_phy(USB_PHY_TYPE_USB3);
+
+ if (!IS_ERR(phy)) {
+ if (phy && phy->otg)
+ otg_set_host(phy->otg,
+ (struct usb_bus *)0xdeadbeef);
+ usb_put_phy(phy);
+ }
+ }
+
ret = platform_device_add(xhci);
if (ret) {
dev_err(dwc->dev, "failed to register xHCI device\n");
diff --git a/drivers/usb/dwc3/otg.c b/drivers/usb/dwc3/otg.c
new file mode 100644
index 000000000000..247f942e7078
--- /dev/null
+++ b/drivers/usb/dwc3/otg.c
@@ -0,0 +1,2199 @@
+/**
+ * otg.c - DesignWare USB3 DRD Controller OTG file
+ *
+ * Copyright (C) 2016 Xilinx, Inc. All rights reserved.
+ *
+ * Author: Manish Narani <mnarani@xilinx.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/pci.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+#include <linux/sched.h>
+#include <linux/freezer.h>
+#include <linux/kthread.h>
+#include <linux/version.h>
+#include <linux/sysfs.h>
+
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/phy.h>
+
+#include <../drivers/usb/host/xhci.h>
+#include "platform_data.h"
+#include "core.h"
+#include "gadget.h"
+#include "io.h"
+#include "otg.h"
+
+#include <linux/ulpi/regs.h>
+#include <linux/ulpi/driver.h>
+#include "debug.h"
+
+/* Print the hardware registers' value for debugging purpose */
+static void print_debug_regs(struct dwc3_otg *otg)
+{
+ u32 gctl = otg_read(otg, DWC3_GCTL);
+ u32 gsts = otg_read(otg, DWC3_GSTS);
+ u32 gdbgltssm = otg_read(otg, DWC3_GDBGLTSSM);
+ u32 gusb2phycfg0 = otg_read(otg, DWC3_GUSB2PHYCFG(0));
+ u32 gusb3pipectl0 = otg_read(otg, DWC3_GUSB3PIPECTL(0));
+ u32 dcfg = otg_read(otg, DWC3_DCFG);
+ u32 dctl = otg_read(otg, DWC3_DCTL);
+ u32 dsts = otg_read(otg, DWC3_DSTS);
+ u32 ocfg = otg_read(otg, OCFG);
+ u32 octl = otg_read(otg, OCTL);
+ u32 oevt = otg_read(otg, OEVT);
+ u32 oevten = otg_read(otg, OEVTEN);
+ u32 osts = otg_read(otg, OSTS);
+
+ otg_info(otg, "gctl = %08x\n", gctl);
+ otg_info(otg, "gsts = %08x\n", gsts);
+ otg_info(otg, "gdbgltssm = %08x\n", gdbgltssm);
+ otg_info(otg, "gusb2phycfg0 = %08x\n", gusb2phycfg0);
+ otg_info(otg, "gusb3pipectl0 = %08x\n", gusb3pipectl0);
+ otg_info(otg, "dcfg = %08x\n", dcfg);
+ otg_info(otg, "dctl = %08x\n", dctl);
+ otg_info(otg, "dsts = %08x\n", dsts);
+ otg_info(otg, "ocfg = %08x\n", ocfg);
+ otg_info(otg, "octl = %08x\n", octl);
+ otg_info(otg, "oevt = %08x\n", oevt);
+ otg_info(otg, "oevten = %08x\n", oevten);
+ otg_info(otg, "osts = %08x\n", osts);
+}
+
+/* Check whether the hardware supports HNP or not */
+static int hnp_capable(struct dwc3_otg *otg)
+{
+ if (otg->hwparams6 & GHWPARAMS6_HNP_SUPPORT_ENABLED)
+ return 1;
+ return 0;
+}
+
+/* Check whether the hardware supports SRP or not */
+static int srp_capable(struct dwc3_otg *otg)
+{
+ if (otg->hwparams6 & GHWPARAMS6_SRP_SUPPORT_ENABLED)
+ return 1;
+ return 0;
+}
+
+/* Wakeup main thread to execute the OTG flow after an event */
+static void wakeup_main_thread(struct dwc3_otg *otg)
+{
+ if (!otg->main_thread)
+ return;
+
+ otg_vdbg(otg, "\n");
+ /* Tell the main thread that something has happened */
+ otg->main_wakeup_needed = 1;
+ wake_up_interruptible(&otg->main_wq);
+}
+
+/* Sleep main thread for 'msecs' to wait for an event to occur */
+static int sleep_main_thread_timeout(struct dwc3_otg *otg, int msecs)
+{
+ signed long jiffies;
+ int rc = msecs;
+
+ if (signal_pending(current)) {
+ otg_dbg(otg, "Main thread signal pending\n");
+ rc = -EINTR;
+ goto done;
+ }
+ if (otg->main_wakeup_needed) {
+ otg_dbg(otg, "Main thread wakeup needed\n");
+ rc = msecs;
+ goto done;
+ }
+
+ jiffies = msecs_to_jiffies(msecs);
+ rc = wait_event_freezable_timeout(otg->main_wq,
+ otg->main_wakeup_needed,
+ jiffies);
+
+ if (rc > 0)
+ rc = jiffies_to_msecs(rc);
+
+done:
+ otg->main_wakeup_needed = 0;
+ return rc;
+}
+
+/* Sleep main thread to wait for an event to occur */
+static int sleep_main_thread(struct dwc3_otg *otg)
+{
+ int rc;
+
+ do {
+ rc = sleep_main_thread_timeout(otg, 5000);
+ } while (rc == 0);
+
+ return rc;
+}
+
+static void get_events(struct dwc3_otg *otg, u32 *otg_events, u32 *user_events)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&otg->lock, flags);
+
+ if (otg_events)
+ *otg_events = otg->otg_events;
+
+ if (user_events)
+ *user_events = otg->user_events;
+
+ spin_unlock_irqrestore(&otg->lock, flags);
+}
+
+static void get_and_clear_events(struct dwc3_otg *otg, u32 *otg_events,
+ u32 *user_events)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&otg->lock, flags);
+
+ if (otg_events)
+ *otg_events = otg->otg_events;
+
+ if (user_events)
+ *user_events = otg->user_events;
+
+ otg->otg_events = 0;
+ otg->user_events = 0;
+
+ spin_unlock_irqrestore(&otg->lock, flags);
+}
+
+static int check_event(struct dwc3_otg *otg, u32 otg_mask, u32 user_mask)
+{
+ u32 otg_events;
+ u32 user_events;
+
+ get_events(otg, &otg_events, &user_events);
+ if ((otg_events & otg_mask) || (user_events & user_mask)) {
+ otg_dbg(otg, "Event occurred: otg_events=%x, otg_mask=%x, \
+ user_events=%x, user_mask=%x\n", otg_events,
+ otg_mask, user_events, user_mask);
+ return 1;
+ }
+
+ return 0;
+}
+
+static int sleep_until_event(struct dwc3_otg *otg, u32 otg_mask, u32 user_mask,
+ u32 *otg_events, u32 *user_events, int timeout)
+{
+ int rc;
+
+ /* Enable the events */
+ if (otg_mask)
+ otg_write(otg, OEVTEN, otg_mask);
+
+ /* Wait until it occurs, or timeout, or interrupt. */
+ if (timeout) {
+ otg_vdbg(otg, "Waiting for event (timeout=%d)...\n", timeout);
+ rc = sleep_main_thread_until_condition_timeout(otg,
+ check_event(otg, otg_mask, user_mask), timeout);
+ } else {
+ otg_vdbg(otg, "Waiting for event (no timeout)...\n");
+ rc = sleep_main_thread_until_condition(otg,
+ check_event(otg, otg_mask, user_mask));
+ }
+
+ /* Disable the events */
+ otg_write(otg, OEVTEN, 0);
+
+ otg_vdbg(otg, "Woke up rc=%d\n", rc);
+ if (rc >= 0)
+ get_and_clear_events(otg, otg_events, user_events);
+
+ return rc;
+}
+
+static void set_capabilities(struct dwc3_otg *otg)
+{
+ u32 ocfg = 0;
+
+ otg_dbg(otg, "\n");
+ if (srp_capable(otg))
+ ocfg |= OCFG_SRP_CAP;
+
+ if (hnp_capable(otg))
+ ocfg |= OCFG_HNP_CAP;
+
+ otg_write(otg, OCFG, ocfg);
+
+ otg_dbg(otg, "Enabled SRP and HNP capabilities in OCFG\n");
+}
+
+static int otg3_handshake(struct dwc3_otg *otg, u32 reg, u32 mask, u32 done,
+ u32 msec)
+{
+ u32 result;
+ u32 usec = msec * 1000;
+
+ otg_vdbg(otg, "reg=%08x, mask=%08x, value=%08x\n", reg, mask, done);
+ do {
+ result = otg_read(otg, reg);
+ if ((result & mask) == done)
+ return 1;
+ udelay(1);
+ usec -= 1;
+ } while (usec > 0);
+
+ return 0;
+}
+
+static int reset_port(struct dwc3_otg *otg)
+{
+ otg_dbg(otg, "\n");
+ if (!otg->otg.host)
+ return -ENODEV;
+ return usb_bus_start_enum(otg->otg.host, 1);
+}
+
+static int set_peri_mode(struct dwc3_otg *otg, int mode)
+{
+ u32 octl;
+
+ /* Set peri_mode */
+ octl = otg_read(otg, OCTL);
+ if (mode)
+ octl |= OCTL_PERI_MODE;
+ else
+ octl &= ~OCTL_PERI_MODE;
+
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set OCTL PERI_MODE = %d in OCTL\n", mode);
+
+ if (mode)
+ return otg3_handshake(otg, OSTS, OSTS_PERIP_MODE,
+ OSTS_PERIP_MODE, 100);
+ else
+ return otg3_handshake(otg, OSTS, OSTS_PERIP_MODE, 0, 100);
+
+ msleep(20);
+}
+
+static int start_host(struct dwc3_otg *otg)
+{
+ int ret = -ENODEV;
+ int flg;
+ u32 octl;
+ u32 osts;
+ u32 ocfg;
+ u32 dctl;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host)
+ return -ENODEV;
+
+ /*
+ * Prevent the host USBCMD.HCRST from resetting OTG core by setting
+ * OCFG.OTGSftRstMsk
+ */
+ ocfg = otg_read(otg, OCFG);
+ ocfg |= DWC3_OCFG_SFTRSTMASK;
+ otg_write(otg, OCFG, ocfg);
+
+ dctl = otg_read(otg, DCTL);
+ if (dctl & DWC3_DCTL_RUN_STOP) {
+ otg_dbg(otg, "Disabling the RUN/STOP bit\n");
+ dctl &= ~DWC3_DCTL_RUN_STOP;
+ otg_write(otg, DCTL, dctl);
+ }
+
+ if (!set_peri_mode(otg, PERI_MODE_HOST)) {
+ otg_err(otg, "Failed to start host\n");
+ return -EINVAL;
+ }
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ otg_dbg(otg, "hcd=%p xhci=%p\n", hcd, xhci);
+
+ if (otg->host_started) {
+ otg_info(otg, "Host already started\n");
+ goto skip;
+ }
+
+ /* Start host driver */
+
+ *(struct xhci_hcd **)hcd->hcd_priv = xhci;
+ ret = usb_add_hcd(hcd, otg->hcd_irq, IRQF_SHARED);
+ if (ret) {
+ otg_err(otg, "%s: failed to start primary hcd, ret=%d\n",
+ __func__, ret);
+ return ret;
+ }
+
+ *(struct xhci_hcd **)xhci->shared_hcd->hcd_priv = xhci;
+ if (xhci->shared_hcd) {
+ ret = usb_add_hcd(xhci->shared_hcd, otg->hcd_irq, IRQF_SHARED);
+ if (ret) {
+ otg_err(otg,
+ "%s: failed to start secondary hcd, ret=%d\n",
+ __func__, ret);
+ usb_remove_hcd(hcd);
+ return ret;
+ }
+ }
+
+ otg->host_started = 1;
+skip:
+ hcd->self.otg_port = 1;
+ if (xhci->shared_hcd)
+ xhci->shared_hcd->self.otg_port = 1;
+
+ set_capabilities(otg);
+
+ /* Power the port only for A-host */
+ if (otg->otg.state == OTG_STATE_A_WAIT_VRISE) {
+ /* Spin on xhciPrtPwr bit until it becomes 1 */
+ osts = otg_read(otg, OSTS);
+ flg = otg3_handshake(otg, OSTS,
+ OSTS_XHCI_PRT_PWR,
+ OSTS_XHCI_PRT_PWR,
+ 1000);
+ if (flg) {
+ otg_dbg(otg, "Port is powered by xhci-hcd\n");
+ /* Set port power control bit */
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_PRT_PWR_CTL;
+ otg_write(otg, OCTL, octl);
+ } else {
+ otg_dbg(otg, "Port is not powered by xhci-hcd\n");
+ }
+ }
+
+ return ret;
+}
+
+static int stop_host(struct dwc3_otg *otg)
+{
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "\n");
+
+ if (!otg->host_started) {
+ otg_info(otg, "Host already stopped\n");
+ return 1;
+ }
+
+ if (!otg->otg.host)
+ return -ENODEV;
+
+ otg_dbg(otg, "%s: turn off host %s\n",
+ __func__, otg->otg.host->bus_name);
+
+ if (work_pending(&otg->hp_work.work)) {
+ while (!cancel_delayed_work(&otg->hp_work))
+ msleep(20);
+ }
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+
+ if (xhci->shared_hcd)
+ usb_remove_hcd(xhci->shared_hcd);
+ usb_remove_hcd(hcd);
+
+ otg->host_started = 0;
+ otg->dev_enum = 0;
+ return 0;
+}
+
+int dwc3_otg_host_release(struct usb_hcd *hcd)
+{
+ struct usb_bus *bus;
+ struct usb_device *rh;
+ struct usb_device *udev;
+
+ if (!hcd)
+ return -EINVAL;
+
+ bus = &hcd->self;
+ if (!bus->otg_port)
+ return 0;
+
+ rh = bus->root_hub;
+ udev = usb_hub_find_child(rh, bus->otg_port);
+ if (!udev)
+ return 0;
+
+ if (udev->config && udev->parent == udev->bus->root_hub) {
+ struct usb_otg20_descriptor *desc;
+
+ if (__usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ USB_DT_OTG, (void **) &desc) == 0) {
+ int err;
+
+ dev_info(&udev->dev, "found OTG descriptor\n");
+ if ((desc->bcdOTG >= 0x0200) &&
+ (udev->speed == USB_SPEED_HIGH)) {
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_TEST_MODE,
+ 7 << 8,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (err < 0) {
+ dev_info(&udev->dev,
+ "can't initiate HNP from host: %d\n",
+ err);
+ return -1;
+ }
+ }
+ } else {
+ dev_info(&udev->dev, "didn't find OTG descriptor\n");
+ }
+ } else {
+ dev_info(&udev->dev,
+ "udev->config NULL or udev->parent != udev->bus->root_hub\n");
+ }
+
+ return 0;
+}
+
+/* Sends the host release set feature request */
+static void host_release(struct dwc3_otg *otg)
+{
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "\n");
+ if (!otg->otg.host)
+ return;
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ dwc3_otg_host_release(hcd);
+ if (xhci->shared_hcd)
+ dwc3_otg_host_release(xhci->shared_hcd);
+}
+
+static void dwc3_otg_setup_event_buffers(struct dwc3_otg *otg)
+{
+ if (dwc3_readl(otg->dwc->regs, DWC3_GEVNTADRLO(0)) == 0x0) {
+
+ otg_dbg(otg, "setting up event buffers\n");
+ dwc3_event_buffers_setup(otg->dwc);
+ }
+
+}
+
+static void start_peripheral(struct dwc3_otg *otg)
+{
+ struct usb_gadget *gadget = otg->otg.gadget;
+ struct dwc3 *dwc = otg->dwc;
+ u32 ocfg;
+
+ otg_dbg(otg, "\n");
+ if (!gadget)
+ return;
+
+ /*
+ * Prevent the gadget DCTL.CSFTRST from resetting OTG core by setting
+ * OCFG.OTGSftRstMsk
+ */
+ ocfg = otg_read(otg, OCFG);
+ ocfg |= DWC3_OCFG_SFTRSTMASK;
+ otg_write(otg, OCFG, ocfg);
+
+ if (!set_peri_mode(otg, PERI_MODE_PERIPHERAL))
+ otg_err(otg, "Failed to set peripheral mode\n");
+
+ if (otg->peripheral_started) {
+ otg_info(otg, "Peripheral already started\n");
+ return;
+ }
+
+ set_capabilities(otg);
+
+ dwc3_otg_setup_event_buffers(otg);
+
+ if (dwc->gadget_driver) {
+ struct dwc3_ep *dep;
+ int ret;
+
+ spin_lock(&otg->lock);
+ dep = dwc->eps[0];
+
+ ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT);
+ if (ret)
+ goto err0;
+
+ dep = dwc->eps[1];
+
+ ret = __dwc3_gadget_ep_enable(dep, DWC3_DEPCFG_ACTION_INIT);
+ if (ret)
+ goto err1;
+
+ otg_dbg(otg, "enabled ep in gadget driver\n");
+ /* begin to receive SETUP packets */
+ dwc->ep0state = EP0_SETUP_PHASE;
+ dwc3_ep0_out_start(dwc);
+
+ otg_dbg(otg, "enabled irq\n");
+ dwc3_gadget_enable_irq(dwc);
+
+ otg_write(otg, DCTL, otg_read(otg, DCTL) | DCTL_RUN_STOP);
+ otg_dbg(otg, "Setting DCTL_RUN_STOP to 1 in DCTL\n");
+ spin_unlock(&otg->lock);
+ }
+
+ gadget->b_hnp_enable = 0;
+ gadget->host_request_flag = 0;
+
+ otg->peripheral_started = 1;
+
+ /*
+ * During HNP the bus shouldn't be idle for more than 155 ms, so
+ * give enough time for the host to load the stack before start
+ * triggerring events
+ */
+ msleep(500);
+
+ return;
+err1:
+ __dwc3_gadget_ep_disable(dwc->eps[0]);
+
+err0:
+ return;
+}
+
+static void stop_peripheral(struct dwc3_otg *otg)
+{
+ struct usb_gadget *gadget = otg->otg.gadget;
+ struct dwc3 *dwc = otg->dwc;
+
+ otg_dbg(otg, "\n");
+
+ if (!otg->peripheral_started) {
+ otg_info(otg, "Peripheral already stopped\n");
+ return;
+ }
+
+ if (!gadget)
+ return;
+
+ otg_dbg(otg, "disabled ep in gadget driver\n");
+ spin_lock(&otg->lock);
+
+ dwc3_gadget_disable_irq(dwc);
+ __dwc3_gadget_ep_disable(dwc->eps[0]);
+ __dwc3_gadget_ep_disable(dwc->eps[1]);
+
+ spin_unlock(&otg->lock);
+
+ otg->peripheral_started = 0;
+ msleep(20);
+}
+
+static void set_b_host(struct dwc3_otg *otg, int val)
+{
+ otg->otg.host->is_b_host = val;
+}
+
+static enum usb_otg_state do_b_idle(struct dwc3_otg *otg);
+
+static int init_b_device(struct dwc3_otg *otg)
+{
+ otg_dbg(otg, "\n");
+ set_capabilities(otg);
+
+ if (!set_peri_mode(otg, PERI_MODE_PERIPHERAL))
+ otg_err(otg, "Failed to start peripheral\n");
+
+ return do_b_idle(otg);
+}
+
+static int init_a_device(struct dwc3_otg *otg)
+{
+ otg_write(otg, OCFG, 0);
+ otg_write(otg, OCTL, 0);
+
+ otg_dbg(otg, "Write 0 to OCFG and OCTL\n");
+ return OTG_STATE_A_IDLE;
+}
+
+static enum usb_otg_state do_connector_id_status(struct dwc3_otg *otg)
+{
+ enum usb_otg_state state;
+ u32 osts;
+
+ otg_dbg(otg, "\n");
+
+ otg_write(otg, OCFG, 0);
+ otg_write(otg, OEVTEN, 0);
+ otg_write(otg, OEVT, 0xffffffff);
+ otg_write(otg, OEVTEN, OEVT_CONN_ID_STS_CHNG_EVNT);
+
+ msleep(60);
+
+ osts = otg_read(otg, OSTS);
+ if (!(osts & OSTS_CONN_ID_STS)) {
+ otg_dbg(otg, "Connector ID is A\n");
+ state = init_a_device(otg);
+ } else {
+ otg_dbg(otg, "Connector ID is B\n");
+ stop_host(otg);
+ state = init_b_device(otg);
+ }
+
+ /* TODO: This is a workaround for latest hibernation-enabled bitfiles
+ * which have problems before initializing SRP.
+ */
+ msleep(50);
+
+ return state;
+}
+
+static void reset_hw(struct dwc3_otg *otg)
+{
+ u32 temp;
+
+ otg_dbg(otg, "\n");
+
+ otg_write(otg, OEVTEN, 0);
+ temp = otg_read(otg, OCTL);
+ temp &= OCTL_PERI_MODE;
+ otg_write(otg, OCTL, temp);
+ temp = otg_read(otg, GCTL);
+ temp |= GCTL_PRT_CAP_DIR_OTG << GCTL_PRT_CAP_DIR_SHIFT;
+ otg_write(otg, GCTL, temp);
+}
+
+#define SRP_TIMEOUT 6000
+
+static void start_srp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_SES_REQ;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set OCTL_SES_REQ in OCTL\n");
+}
+
+static void start_b_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set (OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN) in OCTL\n");
+}
+
+static void stop_b_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl &= ~(OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN);
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "Clear ~(OCTL_HNP_REQ | OCTL_DEV_SET_HNP_EN) in OCTL\n");
+}
+
+static void start_a_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl |= OCTL_HST_SET_HNP_EN;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "set OCTL_HST_SET_HNP_EN in OCTL\n");
+}
+
+static void stop_a_hnp(struct dwc3_otg *otg)
+{
+ u32 octl;
+
+ octl = otg_read(otg, OCTL);
+ octl &= ~OCTL_HST_SET_HNP_EN;
+ otg_write(otg, OCTL, octl);
+ otg_dbg(otg, "clear OCTL_HST_SET_HNP_EN in OCTL\n");
+}
+
+static enum usb_otg_state do_a_hnp_init(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_HNP_CHNG_EVNT;
+
+ start_a_hnp(otg);
+ rc = 3000;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ stop_a_hnp(otg);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_HNP_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_HNP_CHNG_EVNT\n");
+ if (otg_events & OEVT_HST_NEG_SCS) {
+ otg_dbg(otg, "A-HNP Success\n");
+ return OTG_STATE_A_PERIPHERAL;
+
+ } else {
+ otg_dbg(otg, "A-HNP Failed\n");
+ return OTG_STATE_A_WAIT_VFALL;
+ }
+
+ } else if (rc == 0) {
+ otg_dbg(otg, "A-HNP Failed (Timed out)\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else {
+ goto again;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_a_host(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT;
+ user_mask = USER_SRP_EVENT |
+ USER_HNP_EVENT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (user_events & USER_HNP_EVENT) {
+ otg_dbg(otg, "USER_HNP_EVENT\n");
+ return OTG_STATE_A_SUSPEND;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+#define A_WAIT_VFALL_TIMEOUT 1000
+
+static enum usb_otg_state do_a_wait_vfall(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_A_DEV_IDLE_EVNT;
+
+ rc = A_WAIT_VFALL_TIMEOUT;
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_A_DEV_IDLE_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_IDLE_EVNT\n");
+ return OTG_STATE_A_IDLE;
+
+ } else if (rc == 0) {
+ otg_dbg(otg, "A_WAIT_VFALL_TIMEOUT\n");
+ return OTG_STATE_A_IDLE;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+
+}
+
+#define A_WAIT_BCON_TIMEOUT 1000
+
+static enum usb_otg_state do_a_wait_bconn(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT |
+ OEVT_A_DEV_HOST_EVNT;
+
+ rc = A_WAIT_BCON_TIMEOUT;
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (otg_events & OEVT_A_DEV_HOST_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_HOST_EVNT\n");
+ return OTG_STATE_A_HOST;
+
+ } else if (rc == 0) {
+ if (otg_read(otg, OCTL) & OCTL_PRT_PWR_CTL)
+ return OTG_STATE_A_HOST;
+ else
+ return OTG_STATE_A_WAIT_VFALL;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+#define A_WAIT_VRISE_TIMEOUT 100
+
+static enum usb_otg_state do_a_wait_vrise(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 otg_events = 0;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "");
+ set_b_host(otg, 0);
+ start_host(otg);
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ usb_kick_hub_wq(hcd->self.root_hub);
+ if (xhci->shared_hcd)
+ usb_kick_hub_wq(xhci->shared_hcd->self.root_hub);
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT;
+
+ rc = A_WAIT_VRISE_TIMEOUT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &otg_events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (rc == 0) {
+ if (otg_read(otg, OCTL) & OCTL_PRT_PWR_CTL)
+ return OTG_STATE_A_WAIT_BCON;
+ else
+ return OTG_STATE_A_WAIT_VFALL;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_a_idle(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT | OEVT_A_DEV_SRP_DET_EVNT;
+ user_mask = USER_SRP_EVENT;
+
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events,
+ 0);
+
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_A_DEV_SRP_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SRP_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ } else if (user_events & USER_SRP_EVENT) {
+ otg_dbg(otg, "User initiated VBUS\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_a_peripheral(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_A_DEV_SESS_END_DET_EVNT |
+ OEVT_A_DEV_B_DEV_HOST_END_EVNT;
+ user_mask = USER_HNP_END_SESSION;
+
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+
+ } else if (otg_events & OEVT_A_DEV_SESS_END_DET_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_SESS_END_DET_EVNT\n");
+ return OTG_STATE_A_WAIT_VFALL;
+
+ } else if (otg_events & OEVT_A_DEV_B_DEV_HOST_END_EVNT) {
+ otg_dbg(otg, "OEVT_A_DEV_B_DEV_HOST_END_EVNT\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ } else if (user_events & USER_HNP_END_SESSION) {
+ otg_dbg(otg, "USER_HNP_END_SESSION\n");
+ return OTG_STATE_A_WAIT_VRISE;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+#define HNP_TIMEOUT 4000
+
+static enum usb_otg_state do_b_hnp_init(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_HNP_CHNG_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT;
+
+ start_b_hnp(otg);
+ rc = HNP_TIMEOUT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &events, NULL, rc);
+ stop_b_hnp(otg);
+
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ return OTG_STATE_B_IDLE;
+ } else if (events & OEVT_B_DEV_HNP_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_HNP_CHNG_EVNT\n");
+ if (events & OEVT_HST_NEG_SCS) {
+ otg_dbg(otg, "B-HNP Success\n");
+ return OTG_STATE_B_WAIT_ACON;
+
+ } else {
+ otg_err(otg, "B-HNP Failed\n");
+ return OTG_STATE_B_PERIPHERAL;
+ }
+ } else if (rc == 0) {
+ /* Timeout */
+ otg_err(otg, "HNP timed out!\n");
+ return OTG_STATE_B_PERIPHERAL;
+
+ } else {
+ goto again;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_peripheral(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT | OEVT_B_DEV_VBUS_CHNG_EVNT;
+ user_mask = USER_HNP_EVENT | USER_END_SESSION |
+ USER_SRP_EVENT | INITIAL_SRP;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ goto again;
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ return OTG_STATE_B_IDLE;
+ }
+
+ } else if (user_events & USER_HNP_EVENT) {
+ otg_dbg(otg, "USER_HNP_EVENT\n");
+ return do_b_hnp_init(otg);
+ } else if (user_events & USER_END_SESSION) {
+ otg_dbg(otg, "USER_END_SESSION\n");
+ return OTG_STATE_B_IDLE;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_wait_acon(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask = 0;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ otg_dbg(otg, "");
+ set_b_host(otg, 1);
+ start_host(otg);
+ otg_mask = OEVT_B_DEV_B_HOST_END_EVNT;
+ otg_write(otg, OEVTEN, otg_mask);
+ reset_port(otg);
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ usb_kick_hub_wq(hcd->self.root_hub);
+ if (xhci->shared_hcd)
+ usb_kick_hub_wq(xhci->shared_hcd->self.root_hub);
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_B_HOST_END_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT |
+ OEVT_HOST_ROLE_REQ_INIT_EVNT;
+ user_mask = USER_A_CONN_EVENT | USER_HNP_END_SESSION;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_B_DEV_B_HOST_END_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_B_HOST_END_EVNT\n");
+ return OTG_STATE_B_PERIPHERAL;
+ } else if (otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ goto again;
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ return OTG_STATE_B_IDLE;
+ }
+ } else if (user_events & USER_A_CONN_EVENT) {
+ otg_dbg(otg, "A-device connected\n");
+ return OTG_STATE_B_HOST;
+ } else if (user_events & USER_HNP_END_SESSION) {
+ otg_dbg(otg, "USER_HNP_END_SESSION\n");
+ return OTG_STATE_B_PERIPHERAL;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_host(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask = 0;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_B_HOST_END_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT |
+ OEVT_HOST_ROLE_REQ_INIT_EVNT;
+ user_mask = USER_HNP_END_SESSION;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ /* Higher priority first */
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (otg_events & OEVT_B_DEV_B_HOST_END_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_B_HOST_END_EVNT\n");
+ return OTG_STATE_B_PERIPHERAL;
+ } else if (otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ goto again;
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ return OTG_STATE_B_IDLE;
+ }
+ } else if (user_events & USER_HNP_END_SESSION) {
+ otg_dbg(otg, "USER_HNP_END_SESSION\n");
+ return OTG_STATE_B_PERIPHERAL;
+ }
+
+ /* Invalid state */
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_idle(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 user_mask;
+ u32 otg_events = 0;
+ u32 user_events = 0;
+
+ otg_dbg(otg, "");
+
+ if (!set_peri_mode(otg, PERI_MODE_PERIPHERAL))
+ otg_err(otg, "Failed to set peripheral mode\n");
+
+ dwc3_otg_setup_event_buffers(otg);
+
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_SES_VLD_DET_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT;
+ user_mask = USER_SRP_EVENT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, user_mask,
+ &otg_events, &user_events, 0);
+
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (otg_events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if ((otg_events & OEVT_B_DEV_VBUS_CHNG_EVNT) ||
+ (otg_events & OEVT_B_DEV_SES_VLD_DET_EVNT)) {
+ otg_dbg(otg, "OEVT_B_DEV_VBUS_CHNG_EVNT\n");
+ if (otg_events & OEVT_B_SES_VLD_EVT) {
+ otg_dbg(otg, "Session valid\n");
+ return OTG_STATE_B_PERIPHERAL;
+
+ } else {
+ otg_dbg(otg, "Session not valid\n");
+ goto again;
+ }
+ } else if (user_events & USER_SRP_EVENT) {
+ otg_dbg(otg, "USER_SRP_EVENT\n");
+ return OTG_STATE_B_SRP_INIT;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+static enum usb_otg_state do_b_srp_init(struct dwc3_otg *otg)
+{
+ int rc;
+ u32 otg_mask;
+ u32 events = 0;
+
+ otg_dbg(otg, "");
+ otg_mask = OEVT_CONN_ID_STS_CHNG_EVNT |
+ OEVT_B_DEV_SES_VLD_DET_EVNT |
+ OEVT_B_DEV_VBUS_CHNG_EVNT;
+
+ otg_write(otg, OEVTEN, otg_mask);
+ start_srp(otg);
+
+ rc = SRP_TIMEOUT;
+
+again:
+ rc = sleep_until_event(otg,
+ otg_mask, 0,
+ &events, NULL, rc);
+ if (rc < 0)
+ return OTG_STATE_UNDEFINED;
+
+ if (events & OEVT_CONN_ID_STS_CHNG_EVNT) {
+ otg_dbg(otg, "OEVT_CONN_ID_STS_CHNG_EVNT\n");
+ return OTG_STATE_UNDEFINED;
+ } else if (events & OEVT_B_DEV_SES_VLD_DET_EVNT) {
+ otg_dbg(otg, "OEVT_B_DEV_SES_VLD_DET_EVNT\n");
+ return OTG_STATE_B_PERIPHERAL;
+ } else if (rc == 0) {
+ otg_dbg(otg, "SRP Timeout (rc=%d)\n", rc);
+ otg_info(otg, "DEVICE NO RESPONSE FOR SRP\n");
+ return OTG_STATE_B_IDLE;
+
+ } else {
+ goto again;
+ }
+
+ return OTG_STATE_UNDEFINED;
+}
+
+int otg_main_thread(void *data)
+{
+ struct dwc3_otg *otg = (struct dwc3_otg *)data;
+ enum usb_otg_state prev = OTG_STATE_UNDEFINED;
+
+#ifdef VERBOSE_DEBUG
+ u32 snpsid = otg_read(otg, 0xc120);
+
+ otg_vdbg(otg, "io_priv=%p\n", otg->regs);
+ otg_vdbg(otg, "c120: %x\n", snpsid);
+#endif
+
+ /* Allow the thread to be killed by a signal, but set the signal mask
+ * to block everything but INT, TERM, KILL, and USR1.
+ */
+ allow_signal(SIGINT);
+ allow_signal(SIGTERM);
+ allow_signal(SIGKILL);
+ allow_signal(SIGUSR1);
+
+ /* Allow the thread to be frozen */
+ set_freezable();
+
+ /* Allow host/peripheral driver load to finish */
+ msleep(100);
+
+ reset_hw(otg);
+
+ stop_host(otg);
+ stop_peripheral(otg);
+
+ otg_dbg(otg, "Thread running\n");
+ while (1) {
+ enum usb_otg_state next = OTG_STATE_UNDEFINED;
+
+ otg_vdbg(otg, "Main thread entering state\n");
+
+ switch (otg->otg.state) {
+ case OTG_STATE_UNDEFINED:
+ otg_dbg(otg, "OTG_STATE_UNDEFINED\n");
+ next = do_connector_id_status(otg);
+ break;
+
+ case OTG_STATE_A_IDLE:
+ otg_dbg(otg, "OTG_STATE_A_IDLE\n");
+ stop_peripheral(otg);
+
+ if (prev == OTG_STATE_UNDEFINED)
+ next = OTG_STATE_A_WAIT_VRISE;
+ else
+ next = do_a_idle(otg);
+ break;
+
+ case OTG_STATE_A_WAIT_VRISE:
+ otg_dbg(otg, "OTG_STATE_A_WAIT_VRISE\n");
+ next = do_a_wait_vrise(otg);
+ break;
+
+ case OTG_STATE_A_WAIT_BCON:
+ otg_dbg(otg, "OTG_STATE_A_WAIT_BCON\n");
+ next = do_a_wait_bconn(otg);
+ break;
+
+ case OTG_STATE_A_HOST:
+ otg_dbg(otg, "OTG_STATE_A_HOST\n");
+ stop_peripheral(otg);
+ next = do_a_host(otg);
+ /* Don't stop the host here if we are going into
+ * A_SUSPEND. We need to delay that until later. It
+ * will be stopped when coming out of A_SUSPEND
+ * state.
+ */
+ if (next != OTG_STATE_A_SUSPEND)
+ stop_host(otg);
+ break;
+
+ case OTG_STATE_A_SUSPEND:
+ otg_dbg(otg, "OTG_STATE_A_SUSPEND\n");
+ next = do_a_hnp_init(otg);
+
+ /* Stop the host. */
+ stop_host(otg);
+ break;
+
+ case OTG_STATE_A_WAIT_VFALL:
+ otg_dbg(otg, "OTG_STATE_A_WAIT_VFALL\n");
+ next = do_a_wait_vfall(otg);
+ stop_host(otg);
+ break;
+
+ case OTG_STATE_A_PERIPHERAL:
+ otg_dbg(otg, "OTG_STATE_A_PERIPHERAL\n");
+ stop_host(otg);
+ start_peripheral(otg);
+ next = do_a_peripheral(otg);
+ stop_peripheral(otg);
+ break;
+
+ case OTG_STATE_B_IDLE:
+ otg_dbg(otg, "OTG_STATE_B_IDLE\n");
+ next = do_b_idle(otg);
+ break;
+
+ case OTG_STATE_B_PERIPHERAL:
+ otg_dbg(otg, "OTG_STATE_B_PERIPHERAL\n");
+ stop_host(otg);
+ start_peripheral(otg);
+ next = do_b_peripheral(otg);
+ stop_peripheral(otg);
+ break;
+
+ case OTG_STATE_B_SRP_INIT:
+ otg_dbg(otg, "OTG_STATE_B_SRP_INIT\n");
+ otg_read(otg, OSTS);
+ next = do_b_srp_init(otg);
+ break;
+
+ case OTG_STATE_B_WAIT_ACON:
+ otg_dbg(otg, "OTG_STATE_B_WAIT_ACON\n");
+ next = do_b_wait_acon(otg);
+ break;
+
+ case OTG_STATE_B_HOST:
+ otg_dbg(otg, "OTG_STATE_B_HOST\n");
+ next = do_b_host(otg);
+ stop_host(otg);
+ break;
+
+ default:
+ otg_err(otg, "Unknown state %d, sleeping...\n",
+ otg->state);
+ sleep_main_thread(otg);
+ break;
+ }
+
+ prev = otg->otg.state;
+ otg->otg.state = next;
+ if (kthread_should_stop())
+ break;
+ }
+
+ otg->main_thread = NULL;
+ otg_dbg(otg, "OTG main thread exiting....\n");
+
+ return 0;
+}
+
+static void start_main_thread(struct dwc3_otg *otg)
+{
+ if (!otg->main_thread && otg->otg.gadget && otg->otg.host) {
+ otg_dbg(otg, "Starting OTG main thread\n");
+ otg->main_thread = kthread_create(otg_main_thread, otg, "otg");
+ wake_up_process(otg->main_thread);
+ }
+}
+
+static inline struct dwc3_otg *otg_to_dwc3_otg(struct usb_otg *x)
+{
+ return container_of(x, struct dwc3_otg, otg);
+}
+
+static irqreturn_t dwc3_otg_irq(int irq, void *_otg)
+{
+ struct dwc3_otg *otg;
+ u32 oevt;
+ u32 osts;
+ u32 octl;
+ u32 ocfg;
+ u32 oevten;
+ u32 otg_mask = OEVT_ALL;
+
+ if (!_otg)
+ return 0;
+
+ otg = (struct dwc3_otg *)_otg;
+
+ oevt = otg_read(otg, OEVT);
+ osts = otg_read(otg, OSTS);
+ octl = otg_read(otg, OCTL);
+ ocfg = otg_read(otg, OCFG);
+ oevten = otg_read(otg, OEVTEN);
+
+ /* Clear handled events */
+ otg_write(otg, OEVT, oevt);
+
+ otg_vdbg(otg, "\n");
+ otg_vdbg(otg, " oevt = %08x\n", oevt);
+ otg_vdbg(otg, " osts = %08x\n", osts);
+ otg_vdbg(otg, " octl = %08x\n", octl);
+ otg_vdbg(otg, " ocfg = %08x\n", ocfg);
+ otg_vdbg(otg, " oevten = %08x\n", oevten);
+
+ otg_vdbg(otg, "oevt[DeviceMode] = %s\n",
+ oevt & OEVT_DEV_MOD_EVNT ? "Device" : "Host");
+
+ if (oevt & OEVT_CONN_ID_STS_CHNG_EVNT)
+ otg_dbg(otg, "Connector ID Status Change Event\n");
+ if (oevt & OEVT_HOST_ROLE_REQ_INIT_EVNT)
+ otg_dbg(otg, "Host Role Request Init Notification Event\n");
+ if (oevt & OEVT_HOST_ROLE_REQ_CONFIRM_EVNT)
+ otg_dbg(otg, "Host Role Request Confirm Notification Event\n");
+ if (oevt & OEVT_A_DEV_B_DEV_HOST_END_EVNT)
+ otg_dbg(otg, "A-Device B-Host End Event\n");
+ if (oevt & OEVT_A_DEV_HOST_EVNT)
+ otg_dbg(otg, "A-Device Host Event\n");
+ if (oevt & OEVT_A_DEV_HNP_CHNG_EVNT)
+ otg_dbg(otg, "A-Device HNP Change Event\n");
+ if (oevt & OEVT_A_DEV_SRP_DET_EVNT)
+ otg_dbg(otg, "A-Device SRP Detect Event\n");
+ if (oevt & OEVT_A_DEV_SESS_END_DET_EVNT)
+ otg_dbg(otg, "A-Device Session End Detected Event\n");
+ if (oevt & OEVT_B_DEV_B_HOST_END_EVNT)
+ otg_dbg(otg, "B-Device B-Host End Event\n");
+ if (oevt & OEVT_B_DEV_HNP_CHNG_EVNT)
+ otg_dbg(otg, "B-Device HNP Change Event\n");
+ if (oevt & OEVT_B_DEV_SES_VLD_DET_EVNT)
+ otg_dbg(otg, "B-Device Session Valid Detect Event\n");
+ if (oevt & OEVT_B_DEV_VBUS_CHNG_EVNT)
+ otg_dbg(otg, "B-Device VBUS Change Event\n");
+
+ if (oevt & otg_mask) {
+ /* Pass event to main thread */
+ spin_lock(&otg->lock);
+ otg->otg_events |= oevt;
+ wakeup_main_thread(otg);
+ spin_unlock(&otg->lock);
+ return 1;
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void hnp_polling_work(struct work_struct *w)
+{
+ struct dwc3_otg *otg = container_of(w, struct dwc3_otg,
+ hp_work.work);
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ struct usb_hcd *hcd;
+ u8 *otgstatus;
+ int ret;
+ int err;
+
+ hcd = container_of(otg->otg.host, struct usb_hcd, self);
+ if (!hcd)
+ return;
+
+ bus = &hcd->self;
+ if (!bus->otg_port)
+ return;
+
+ udev = usb_hub_find_child(bus->root_hub, bus->otg_port);
+ if (!udev)
+ return;
+
+ otgstatus = kmalloc(sizeof(*otgstatus), GFP_NOIO);
+ if (!otgstatus)
+ return;
+
+ ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_STATUS, USB_DIR_IN | USB_RECIP_DEVICE,
+ 0, 0xf000, otgstatus, sizeof(*otgstatus),
+ USB_CTRL_GET_TIMEOUT);
+
+ if (ret == sizeof(*otgstatus) && (*otgstatus & 0x1)) {
+ /* enable HNP before suspend, it's simpler */
+
+ udev->bus->b_hnp_enable = 1;
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ udev->bus->b_hnp_enable
+ ? USB_DEVICE_B_HNP_ENABLE
+ : USB_DEVICE_A_ALT_HNP_SUPPORT,
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+
+ if (err < 0) {
+ /* OTG MESSAGE: report errors here,
+ * customize to match your product.
+ */
+ otg_info(otg, "ERROR : Device no response\n");
+ dev_info(&udev->dev, "can't set HNP mode: %d\n",
+ err);
+ udev->bus->b_hnp_enable = 0;
+ if (le16_to_cpu(udev->descriptor.idVendor) == 0x1a0a) {
+ if (usb_port_suspend(udev, PMSG_AUTO_SUSPEND)
+ < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n",
+ err);
+ }
+ } else {
+ /* Device wants role-switch, suspend the bus. */
+ static struct usb_phy *phy;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ otg_start_hnp(phy->otg);
+ usb_put_phy(phy);
+
+ if (usb_port_suspend(udev, PMSG_AUTO_SUSPEND) < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+ }
+ } else if (ret < 0) {
+ udev->bus->b_hnp_enable = 1;
+ err = usb_control_msg(udev,
+ usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, 0,
+ USB_DEVICE_B_HNP_ENABLE,
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (usb_port_suspend(udev, PMSG_AUTO_SUSPEND) < 0)
+ dev_dbg(&udev->dev, "HNP fail, %d\n", err);
+ } else {
+ schedule_delayed_work(&otg->hp_work, 1 * HZ);
+ }
+
+ kfree(otgstatus);
+}
+
+static int dwc3_otg_notify_connect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct usb_bus *bus;
+ struct usb_device *udev;
+ struct usb_hcd *hcd;
+ struct dwc3_otg *otg;
+ int err = 0;
+
+ otg = otg_to_dwc3_otg(phy->otg);
+
+ hcd = container_of(phy->otg->host, struct usb_hcd, self);
+ if (!hcd)
+ return -EINVAL;
+
+ bus = &hcd->self;
+ if (!bus->otg_port)
+ return 0;
+
+ udev = usb_hub_find_child(bus->root_hub, bus->otg_port);
+ if (!udev)
+ return 0;
+
+ /*
+ * OTG-aware devices on OTG-capable root hubs may be able to use SRP,
+ * to wake us after we've powered off VBUS; and HNP, switching roles
+ * "host" to "peripheral". The OTG descriptor helps figure this out.
+ */
+ if (udev->config && udev->parent == udev->bus->root_hub) {
+ struct usb_otg20_descriptor *desc = NULL;
+
+ /* descriptor may appear anywhere in config */
+ err = __usb_get_extra_descriptor(udev->rawdescriptors[0],
+ le16_to_cpu(udev->config[0].desc.wTotalLength),
+ USB_DT_OTG, (void **) &desc);
+ if (err || !(desc->bmAttributes & USB_OTG_HNP))
+ return 0;
+
+ if (udev->portnum == udev->bus->otg_port) {
+ INIT_DELAYED_WORK(&otg->hp_work,
+ hnp_polling_work);
+ schedule_delayed_work(&otg->hp_work, HZ);
+ }
+
+ }
+
+ return err;
+}
+
+static int dwc3_otg_notify_disconnect(struct usb_phy *phy,
+ enum usb_device_speed speed)
+{
+ struct dwc3_otg *otg;
+
+ otg = otg_to_dwc3_otg(phy->otg);
+
+ if (work_pending(&otg->hp_work.work)) {
+ while (!cancel_delayed_work(&otg->hp_work))
+ msleep(20);
+ }
+ return 0;
+}
+
+static void dwc3_otg_set_peripheral(struct usb_otg *_otg, int yes)
+{
+ struct dwc3_otg *otg;
+
+ if (!_otg)
+ return;
+
+ otg = otg_to_dwc3_otg(_otg);
+ otg_dbg(otg, "\n");
+
+ if (yes) {
+ if (otg->hwparams6 == 0xdeadbeef)
+ otg->hwparams6 = otg_read(otg, GHWPARAMS6);
+ stop_host(otg);
+ } else {
+ stop_peripheral(otg);
+ }
+
+ set_peri_mode(otg, yes);
+}
+EXPORT_SYMBOL(dwc3_otg_set_peripheral);
+
+static int dwc3_otg_set_periph(struct usb_otg *_otg, struct usb_gadget *gadget)
+{
+ struct dwc3_otg *otg;
+
+ if (!_otg)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(_otg);
+ otg_dbg(otg, "\n");
+
+ if ((long)gadget == 1) {
+ dwc3_otg_set_peripheral(_otg, 1);
+ return 0;
+ }
+
+ if (!gadget) {
+ otg->otg.gadget = NULL;
+ return -ENODEV;
+ }
+
+ otg->otg.gadget = gadget;
+ otg->otg.gadget->hnp_polling_support = 1;
+ otg->otg.state = OTG_STATE_B_IDLE;
+
+ start_main_thread(otg);
+ return 0;
+}
+
+static int dwc3_otg_set_host(struct usb_otg *_otg, struct usb_bus *host)
+{
+ struct dwc3_otg *otg;
+ struct usb_hcd *hcd;
+ struct xhci_hcd *xhci;
+
+ if (!_otg)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(_otg);
+ otg_dbg(otg, "\n");
+
+ if (host == (struct usb_bus *)0xdeadbeef) {
+ dwc3_otg_set_peripheral(_otg, 0);
+ return 0;
+ }
+
+ if (!host) {
+ otg->otg.host = NULL;
+ otg->hcd_irq = 0;
+ return -ENODEV;
+ }
+
+ hcd = container_of(host, struct usb_hcd, self);
+ xhci = hcd_to_xhci(hcd);
+ otg_dbg(otg, "hcd=%p xhci=%p\n", hcd, xhci);
+
+ hcd->self.otg_port = 1;
+ if (xhci->shared_hcd) {
+ xhci->shared_hcd->self.otg_port = 1;
+ otg_dbg(otg, "shared_hcd=%p\n", xhci->shared_hcd);
+ }
+
+ otg->otg.host = host;
+ otg->hcd_irq = hcd->irq;
+ otg_dbg(otg, "host=%p irq=%d\n", otg->otg.host, otg->hcd_irq);
+
+
+ otg->host_started = 1;
+ otg->dev_enum = 0;
+ start_main_thread(otg);
+ return 0;
+}
+
+static int dwc3_otg_start_srp(struct usb_otg *x)
+{
+ unsigned long flags;
+ struct dwc3_otg *otg;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= USER_SRP_EVENT;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+static int dwc3_otg_start_hnp(struct usb_otg *x)
+{
+ unsigned long flags;
+ struct dwc3_otg *otg;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= USER_HNP_EVENT;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+static int dwc3_otg_end_session(struct usb_otg *x)
+{
+ unsigned long flags;
+ struct dwc3_otg *otg;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= USER_END_SESSION;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+static int otg_end_session(struct usb_otg *otg)
+{
+ return dwc3_otg_end_session(otg);
+}
+EXPORT_SYMBOL(otg_end_session);
+
+static int dwc3_otg_received_host_release(struct usb_otg *x)
+{
+ struct dwc3_otg *otg;
+ unsigned long flags;
+
+ if (!x)
+ return -ENODEV;
+
+ otg = otg_to_dwc3_otg(x);
+ otg_dbg(otg, "\n");
+
+ if (!otg->otg.host || !otg->otg.gadget)
+ return -ENODEV;
+
+ spin_lock_irqsave(&otg->lock, flags);
+ otg->user_events |= PCD_RECEIVED_HOST_RELEASE_EVENT;
+ wakeup_main_thread(otg);
+ spin_unlock_irqrestore(&otg->lock, flags);
+ return 0;
+}
+
+int otg_host_release(struct usb_otg *otg)
+{
+ return dwc3_otg_received_host_release(otg);
+}
+EXPORT_SYMBOL(otg_host_release);
+
+static void dwc3_otg_enable_irq(struct dwc3_otg *otg)
+{
+ u32 reg;
+
+ /* Enable OTG IRQs */
+ reg = OEVT_ALL;
+
+ otg_write(otg, OEVTEN, reg);
+}
+
+static ssize_t store_srp(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_phy *phy;
+ struct usb_otg *otg;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (IS_ERR(phy) || !phy) {
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg_start_srp(otg);
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(srp, 0220, NULL, store_srp);
+
+static ssize_t store_end(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_phy *phy;
+ struct usb_otg *otg;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (IS_ERR(phy) || !phy) {
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg_end_session(otg);
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(end, 0220, NULL, store_end);
+
+static ssize_t store_hnp(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct usb_phy *phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ struct usb_otg *otg;
+
+ dev_dbg(dwc->dev, "%s()\n", __func__);
+
+ if (IS_ERR(phy) || !phy) {
+ dev_info(dwc->dev, "NO PHY!!\n");
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ dev_info(dwc->dev, "NO OTG!!\n");
+ usb_put_phy(phy);
+ return count;
+ }
+
+ dev_info(dev, "b_hnp_enable is FALSE\n");
+ dwc->gadget.host_request_flag = 1;
+
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(hnp, 0220, NULL, store_hnp);
+
+static ssize_t store_hnp_end(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_phy *phy;
+ struct usb_otg *otg;
+ unsigned long flags;
+ struct dwc3_otg *dwc_otg;
+
+ phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (IS_ERR(phy) || !phy) {
+ if (!IS_ERR(phy))
+ usb_put_phy(phy);
+ return count;
+ }
+
+ otg = phy->otg;
+ if (!otg) {
+ usb_put_phy(phy);
+ return count;
+ }
+
+ dwc_otg = otg_to_dwc3_otg(otg);
+
+ spin_lock_irqsave(&dwc_otg->lock, flags);
+ dwc_otg->user_events |= USER_HNP_END_SESSION;
+ wakeup_main_thread(dwc_otg);
+ spin_unlock_irqrestore(&dwc_otg->lock, flags);
+
+ usb_put_phy(phy);
+ return count;
+}
+static DEVICE_ATTR(hnp_end, 0220, NULL, store_hnp_end);
+
+static ssize_t store_a_hnp_reqd(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_otg *otg;
+
+ otg = dwc->otg;
+ host_release(otg);
+ return count;
+}
+static DEVICE_ATTR(a_hnp_reqd, 0220, NULL, store_a_hnp_reqd);
+
+static ssize_t store_print_dbg(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct dwc3 *dwc = dev_get_drvdata(dev);
+ struct dwc3_otg *otg;
+
+ otg = dwc->otg;
+ print_debug_regs(otg);
+
+ return count;
+}
+static DEVICE_ATTR(print_dbg, 0220, NULL, store_print_dbg);
+
+void dwc_usb3_remove_dev_files(struct device *dev)
+{
+ device_remove_file(dev, &dev_attr_print_dbg);
+ device_remove_file(dev, &dev_attr_a_hnp_reqd);
+ device_remove_file(dev, &dev_attr_end);
+ device_remove_file(dev, &dev_attr_srp);
+ device_remove_file(dev, &dev_attr_hnp);
+ device_remove_file(dev, &dev_attr_hnp_end);
+}
+
+int dwc3_otg_create_dev_files(struct device *dev)
+{
+ int retval;
+
+ retval = device_create_file(dev, &dev_attr_hnp);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_hnp_end);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_srp);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_end);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_a_hnp_reqd);
+ if (retval)
+ goto fail;
+
+ retval = device_create_file(dev, &dev_attr_print_dbg);
+ if (retval)
+ goto fail;
+
+ return 0;
+
+fail:
+ dev_err(dev, "Failed to create one or more sysfs files!!\n");
+ return retval;
+}
+
+void dwc3_otg_init(struct dwc3 *dwc)
+{
+ struct dwc3_otg *otg;
+ int err;
+ u32 reg;
+
+ dev_dbg(dwc->dev, "dwc3_otg_init\n");
+
+ /*
+ * GHWPARAMS6[10] bit is SRPSupport.
+ * This bit also reflects DWC_USB3_EN_OTG
+ */
+ reg = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6);
+ if (!(reg & GHWPARAMS6_SRP_SUPPORT_ENABLED)) {
+ /*
+ * No OTG support in the HW core.
+ * We return 0 to indicate no error, since this is acceptable
+ * situation, just continue probe the dwc3 driver without otg.
+ */
+ dev_dbg(dwc->dev, "dwc3_otg address space is not supported\n");
+ return;
+ }
+
+ otg = kzalloc(sizeof(*otg), GFP_KERNEL);
+ if (!otg) {
+ dev_err(otg->dev, "failed to allocate memroy\n");
+ return;
+ }
+
+ dwc->otg = otg;
+ otg->dev = dwc->dev;
+ otg->dwc = dwc;
+
+ otg->regs = dwc->regs - DWC3_GLOBALS_REGS_START;
+ otg->otg.usb_phy = kzalloc(sizeof(struct usb_phy), GFP_KERNEL);
+ otg->otg.usb_phy->dev = otg->dev;
+ otg->otg.usb_phy->label = "dwc3_otg";
+ otg->otg.state = OTG_STATE_UNDEFINED;
+ otg->otg.usb_phy->otg = &otg->otg;
+ otg->otg.usb_phy->notify_connect = dwc3_otg_notify_connect;
+ otg->otg.usb_phy->notify_disconnect = dwc3_otg_notify_disconnect;
+
+ otg->otg.start_srp = dwc3_otg_start_srp;
+ otg->otg.start_hnp = dwc3_otg_start_hnp;
+ otg->otg.set_host = dwc3_otg_set_host;
+ otg->otg.set_peripheral = dwc3_otg_set_periph;
+
+ otg->hwparams6 = reg;
+ otg->state = OTG_STATE_UNDEFINED;
+
+ spin_lock_init(&otg->lock);
+ init_waitqueue_head(&otg->main_wq);
+
+ err = usb_add_phy(otg->otg.usb_phy, USB_PHY_TYPE_USB3);
+ if (err) {
+ dev_err(otg->dev, "can't register transceiver, err: %d\n",
+ err);
+ goto exit;
+ }
+
+ otg->irq = platform_get_irq(to_platform_device(otg->dev), 1);
+
+ dwc3_otg_create_dev_files(otg->dev);
+
+ /* Set irq handler */
+ err = request_irq(otg->irq, dwc3_otg_irq, IRQF_SHARED, "dwc3_otg", otg);
+ if (err) {
+ dev_err(otg->otg.usb_phy->dev, "failed to request irq #%d --> %d\n",
+ otg->irq, err);
+ goto exit;
+ }
+
+ dwc3_otg_enable_irq(otg);
+
+ err = dwc3_gadget_init(dwc);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(otg->otg.usb_phy->dev,
+ "failed to initialize gadget\n");
+ goto exit;
+ }
+
+ err = dwc3_host_init(dwc);
+ if (err) {
+ if (err != -EPROBE_DEFER)
+ dev_err(otg->otg.usb_phy->dev,
+ "failed to initialize host\n");
+ goto exit;
+ }
+
+ return;
+
+exit:
+ kfree(otg->otg.usb_phy);
+ kfree(otg);
+}
+
+void dwc3_otg_exit(struct dwc3 *dwc)
+{
+ struct dwc3_otg *otg = dwc->otg;
+
+ otg_dbg(otg, "\n");
+ usb_remove_phy(otg->otg.usb_phy);
+ kfree(otg->otg.usb_phy);
+ kfree(otg);
+}
diff --git a/drivers/usb/dwc3/otg.h b/drivers/usb/dwc3/otg.h
new file mode 100644
index 000000000000..25de1e5631a7
--- /dev/null
+++ b/drivers/usb/dwc3/otg.h
@@ -0,0 +1,252 @@
+/**
+ * otg.h - DesignWare USB3 DRD OTG Header
+ *
+ * Copyright (C) 2010-2011 Texas Instruments Incorporated - http://www.ti.com
+ *
+ * Authors: Felipe Balbi <balbi@ti.com>,
+ * Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define otg_dbg(d, fmt, args...) dev_dbg((d)->dev, "%s(): " fmt,\
+ __func__, ## args)
+#define otg_vdbg(d, fmt, args...) dev_vdbg((d)->dev, "%s(): " fmt,\
+ __func__, ## args)
+#define otg_err(d, fmt, args...) dev_err((d)->dev, "%s(): ERROR: " fmt,\
+ __func__, ## args)
+#define otg_warn(d, fmt, args...) dev_warn((d)->dev, "%s(): WARN: " fmt,\
+ __func__, ## args)
+#define otg_info(d, fmt, args...) dev_info((d)->dev, "%s(): INFO: " fmt,\
+ __func__, ## args)
+
+#ifdef VERBOSE_DEBUG
+#define otg_write(o, reg, val) do { \
+ otg_vdbg(o, "OTG_WRITE: reg=0x%05x, val=0x%08x\n", reg, val); \
+ writel(val, ((void *)((o)->regs)) + reg); \
+ } while (0)
+
+#define otg_read(o, reg) ({ \
+ u32 __r = readl(((void *)((o)->regs)) + reg); \
+ otg_vdbg(o, "OTG_READ: reg=0x%05x, val=0x%08x\n", reg, __r); \
+ __r; \
+ })
+#else
+#define otg_write(o, reg, val) writel(val, ((void *)((o)->regs)) + reg)
+#define otg_read(o, reg) readl(((void *)((o)->regs)) + reg)
+#endif
+
+#define sleep_main_thread_until_condition_timeout(otg, condition, msecs) ({ \
+ int __timeout = msecs; \
+ while (!(condition)) { \
+ otg_dbg(otg, " ... sleeping for %d\n", __timeout); \
+ __timeout = sleep_main_thread_timeout(otg, __timeout); \
+ if (__timeout <= 0) { \
+ break; \
+ } \
+ } \
+ __timeout; \
+ })
+
+#define sleep_main_thread_until_condition(otg, condition) ({ \
+ int __rc; \
+ do { \
+ __rc = sleep_main_thread_until_condition_timeout(otg, \
+ condition, 50000); \
+ } while (__rc == 0); \
+ __rc; \
+ })
+
+#define GHWPARAMS6 0xc158
+#define GHWPARAMS6_SRP_SUPPORT_ENABLED 0x0400
+#define GHWPARAMS6_HNP_SUPPORT_ENABLED 0x0800
+
+#define GCTL 0xc110
+#define GCTL_PRT_CAP_DIR 0x3000
+#define GCTL_PRT_CAP_DIR_SHIFT 12
+#define GCTL_PRT_CAP_DIR_HOST 1
+#define GCTL_PRT_CAP_DIR_DEV 2
+#define GCTL_PRT_CAP_DIR_OTG 3
+#define GCTL_GBL_HIBERNATION_EN 0x2
+
+#define OCFG 0xcc00
+#define OCFG_SRP_CAP 0x01
+#define OCFG_SRP_CAP_SHIFT 0
+#define OCFG_HNP_CAP 0x02
+#define OCFG_HNP_CAP_SHIFT 1
+#define OCFG_OTG_VERSION 0x04
+#define OCFG_OTG_VERSION_SHIFT 2
+
+#define OCTL 0xcc04
+#define OCTL_HST_SET_HNP_EN 0x01
+#define OCTL_HST_SET_HNP_EN_SHIFT 0
+#define OCTL_DEV_SET_HNP_EN 0x02
+#define OCTL_DEV_SET_HNP_EN_SHIFT 1
+#define OCTL_TERM_SEL_DL_PULSE 0x04
+#define OCTL_TERM_SEL_DL_PULSE_SHIFT 2
+#define OCTL_SES_REQ 0x08
+#define OCTL_SES_REQ_SHIFT 3
+#define OCTL_HNP_REQ 0x10
+#define OCTL_HNP_REQ_SHIFT 4
+#define OCTL_PRT_PWR_CTL 0x20
+#define OCTL_PRT_PWR_CTL_SHIFT 5
+#define OCTL_PERI_MODE 0x40
+#define OCTL_PERI_MODE_SHIFT 6
+
+#define OEVT 0xcc08
+#define OEVT_ERR 0x00000001
+#define OEVT_ERR_SHIFT 0
+#define OEVT_SES_REQ_SCS 0x00000002
+#define OEVT_SES_REQ_SCS_SHIFT 1
+#define OEVT_HST_NEG_SCS 0x00000004
+#define OEVT_HST_NEG_SCS_SHIFT 2
+#define OEVT_B_SES_VLD_EVT 0x00000008
+#define OEVT_B_SES_VLD_EVT_SHIFT 3
+#define OEVT_B_DEV_VBUS_CHNG_EVNT 0x00000100
+#define OEVT_B_DEV_VBUS_CHNG_EVNT_SHIFT 8
+#define OEVT_B_DEV_SES_VLD_DET_EVNT 0x00000200
+#define OEVT_B_DEV_SES_VLD_DET_EVNT_SHIFT 9
+#define OEVT_B_DEV_HNP_CHNG_EVNT 0x00000400
+#define OEVT_B_DEV_HNP_CHNG_EVNT_SHIFT 10
+#define OEVT_B_DEV_B_HOST_END_EVNT 0x00000800
+#define OEVT_B_DEV_B_HOST_END_EVNT_SHIFT 11
+#define OEVT_A_DEV_SESS_END_DET_EVNT 0x00010000
+#define OEVT_A_DEV_SESS_END_DET_EVNT_SHIFT 16
+#define OEVT_A_DEV_SRP_DET_EVNT 0x00020000
+#define OEVT_A_DEV_SRP_DET_EVNT_SHIFT 17
+#define OEVT_A_DEV_HNP_CHNG_EVNT 0x00040000
+#define OEVT_A_DEV_HNP_CHNG_EVNT_SHIFT 18
+#define OEVT_A_DEV_HOST_EVNT 0x00080000
+#define OEVT_A_DEV_HOST_EVNT_SHIFT 19
+#define OEVT_A_DEV_B_DEV_HOST_END_EVNT 0x00100000
+#define OEVT_A_DEV_B_DEV_HOST_END_EVNT_SHIFT 20
+#define OEVT_A_DEV_IDLE_EVNT 0x00200000
+#define OEVT_A_DEV_IDLE_EVNT_SHIFT 21
+#define OEVT_HOST_ROLE_REQ_INIT_EVNT 0x00400000
+#define OEVT_HOST_ROLE_REQ_INIT_EVNT_SHIFT 22
+#define OEVT_HOST_ROLE_REQ_CONFIRM_EVNT 0x00800000
+#define OEVT_HOST_ROLE_REQ_CONFIRM_EVNT_SHIFT 23
+#define OEVT_CONN_ID_STS_CHNG_EVNT 0x01000000
+#define OEVT_CONN_ID_STS_CHNG_EVNT_SHIFT 24
+#define OEVT_DEV_MOD_EVNT 0x80000000
+#define OEVT_DEV_MOD_EVNT_SHIFT 31
+
+#define OEVTEN 0xcc0c
+
+#define OEVT_ALL (OEVT_CONN_ID_STS_CHNG_EVNT | \
+ OEVT_HOST_ROLE_REQ_INIT_EVNT | \
+ OEVT_HOST_ROLE_REQ_CONFIRM_EVNT | \
+ OEVT_A_DEV_B_DEV_HOST_END_EVNT | \
+ OEVT_A_DEV_HOST_EVNT | \
+ OEVT_A_DEV_HNP_CHNG_EVNT | \
+ OEVT_A_DEV_SRP_DET_EVNT | \
+ OEVT_A_DEV_SESS_END_DET_EVNT | \
+ OEVT_B_DEV_B_HOST_END_EVNT | \
+ OEVT_B_DEV_HNP_CHNG_EVNT | \
+ OEVT_B_DEV_SES_VLD_DET_EVNT | \
+ OEVT_B_DEV_VBUS_CHNG_EVNT)
+
+#define OSTS 0xcc10
+#define OSTS_CONN_ID_STS 0x0001
+#define OSTS_CONN_ID_STS_SHIFT 0
+#define OSTS_A_SES_VLD 0x0002
+#define OSTS_A_SES_VLD_SHIFT 1
+#define OSTS_B_SES_VLD 0x0004
+#define OSTS_B_SES_VLD_SHIFT 2
+#define OSTS_XHCI_PRT_PWR 0x0008
+#define OSTS_XHCI_PRT_PWR_SHIFT 3
+#define OSTS_PERIP_MODE 0x0010
+#define OSTS_PERIP_MODE_SHIFT 4
+#define OSTS_OTG_STATES 0x0f00
+#define OSTS_OTG_STATE_SHIFT 8
+
+#define DCTL 0xc704
+#define DCTL_RUN_STOP 0x80000000
+
+#define OTG_STATE_INVALID -1
+#define OTG_STATE_EXIT 14
+#define OTG_STATE_TERMINATED 15
+
+#define PERI_MODE_HOST 0
+#define PERI_MODE_PERIPHERAL 1
+
+/** The main structure to keep track of OTG driver state. */
+struct dwc3_otg {
+
+ /** OTG PHY */
+ struct usb_otg otg;
+ struct device *dev;
+ struct dwc3 *dwc;
+
+ void __iomem *regs;
+
+ int main_wakeup_needed;
+ struct task_struct *main_thread;
+ wait_queue_head_t main_wq;
+
+ spinlock_t lock;
+
+ int otg_srp_reqd;
+
+ /* Events */
+ u32 otg_events;
+
+ u32 user_events;
+
+ /** User initiated SRP.
+ *
+ * Valid in B-device during sensing/probing. Initiates SRP signalling
+ * across the bus.
+ *
+ * Also valid as an A-device during probing. This causes the A-device to
+ * apply V-bus manually and check for a device. Can be used if the
+ * device does not support SRP and the host does not support ADP.
+ */
+#define USER_SRP_EVENT 0x1
+ /** User initiated HNP (only valid in B-peripheral) */
+#define USER_HNP_EVENT 0x2
+ /** User has ended the session (only valid in B-peripheral) */
+#define USER_END_SESSION 0x4
+ /** User initiated VBUS. This will cause the A-device to turn on the
+ * VBUS and see if a device will connect (only valid in A-device during
+ * sensing/probing)
+ */
+#define USER_VBUS_ON 0x8
+ /** User has initiated RSP */
+#define USER_RSP_EVENT 0x10
+ /** Host release event */
+#define PCD_RECEIVED_HOST_RELEASE_EVENT 0x20
+ /** Initial SRP */
+#define INITIAL_SRP 0x40
+ /** A-device connected event*/
+#define USER_A_CONN_EVENT 0x80
+ /** User initiated HNP END Session. This will make the A-device and
+ * B-device to return back to their previous roles before HNP got
+ * initiated
+ */
+#define USER_HNP_END_SESSION 0x100
+
+ /* States */
+ enum usb_otg_state prev;
+ enum usb_otg_state state;
+
+ u32 hwparams6;
+ int hcd_irq;
+ int irq;
+ int host_started;
+ int peripheral_started;
+ int dev_enum;
+
+ struct delayed_work hp_work; /* drives HNP polling */
+
+};
+
+extern int usb_port_suspend(struct usb_device *udev, pm_message_t msg);
+extern void usb_kick_hub_wq(struct usb_device *dev);
diff --git a/drivers/usb/dwc3/platform_data.h b/drivers/usb/dwc3/platform_data.h
new file mode 100644
index 000000000000..ae659e367804
--- /dev/null
+++ b/drivers/usb/dwc3/platform_data.h
@@ -0,0 +1,54 @@
+/**
+ * platform_data.h - USB DWC3 Platform Data Support
+ *
+ * Copyright (C) 2013 Texas Instruments Incorporated - http://www.ti.com
+ * Author: Felipe Balbi <balbi@ti.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 of
+ * the License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <linux/usb/ch9.h>
+#include <linux/usb/otg.h>
+
+struct dwc3_platform_data {
+ enum usb_device_speed maximum_speed;
+ enum usb_dr_mode dr_mode;
+ bool usb3_lpm_capable;
+
+ unsigned is_utmi_l1_suspend:1;
+ u8 hird_threshold;
+
+ u8 lpm_nyet_threshold;
+
+ unsigned disable_scramble_quirk:1;
+ unsigned has_lpm_erratum:1;
+ unsigned u2exit_lfps_quirk:1;
+ unsigned u2ss_inp3_quirk:1;
+ unsigned req_p1p2p3_quirk:1;
+ unsigned del_p1p2p3_quirk:1;
+ unsigned del_phy_power_chg_quirk:1;
+ unsigned lfps_filter_quirk:1;
+ unsigned rx_detect_poll_quirk:1;
+ unsigned dis_u3_susphy_quirk:1;
+ unsigned dis_u2_susphy_quirk:1;
+ unsigned dis_enblslpm_quirk:1;
+ unsigned dis_rxdet_inp3_quirk:1;
+
+ unsigned tx_de_emphasis_quirk:1;
+ unsigned tx_de_emphasis:2;
+
+ u32 fladj_value;
+ bool refclk_fladj;
+
+ const char *hsphy_interface;
+};
diff --git a/drivers/usb/gadget/configfs.c b/drivers/usb/gadget/configfs.c
index a7709d126b29..452fd36f9c10 100644
--- a/drivers/usb/gadget/configfs.c
+++ b/drivers/usb/gadget/configfs.c
@@ -231,6 +231,47 @@ static ssize_t gadget_dev_desc_bcdUSB_store(struct config_item *item,
return len;
}
+static ssize_t gadget_dev_desc_max_speed_show(struct config_item *item,
+ char *page)
+{
+ struct gadget_info *gi = to_gadget_info(item);
+ enum usb_device_speed max_speed = gi->composite.gadget_driver.max_speed;
+
+ return sprintf(page, "%s\n", usb_speed_string(max_speed));
+}
+
+static ssize_t gadget_dev_desc_max_speed_store(struct config_item *item,
+ const char *page, size_t len)
+{
+ struct gadget_info *gi = to_gadget_info(item);
+ char *name;
+ int ret;
+ const char * const speed_names[] = {
+ [USB_SPEED_UNKNOWN] = "UNKNOWN",
+ [USB_SPEED_LOW] = "low-speed",
+ [USB_SPEED_FULL] = "full-speed",
+ [USB_SPEED_HIGH] = "high-speed",
+ [USB_SPEED_WIRELESS] = "wireless",
+ [USB_SPEED_SUPER] = "super-speed",
+ [USB_SPEED_SUPER_PLUS] = "super-speed-plus",
+ };
+
+ name = kstrdup(page, GFP_KERNEL);
+ if (!name)
+ return -ENOMEM;
+ if (name[len - 1] == '\n')
+ name[len - 1] = '\0';
+
+ ret = match_string(speed_names, ARRAY_SIZE(speed_names), name);
+
+ if (ret != -EINVAL) {
+ gi->composite.gadget_driver.max_speed = ret;
+ return len;
+ }
+
+ return ret;
+}
+
static ssize_t gadget_dev_desc_UDC_show(struct config_item *item, char *page)
{
char *udc_name = to_gadget_info(item)->composite.gadget_driver.udc_name;
@@ -304,6 +345,7 @@ CONFIGFS_ATTR(gadget_dev_desc_, idVendor);
CONFIGFS_ATTR(gadget_dev_desc_, idProduct);
CONFIGFS_ATTR(gadget_dev_desc_, bcdDevice);
CONFIGFS_ATTR(gadget_dev_desc_, bcdUSB);
+CONFIGFS_ATTR(gadget_dev_desc_, max_speed);
CONFIGFS_ATTR(gadget_dev_desc_, UDC);
static struct configfs_attribute *gadget_root_attrs[] = {
@@ -315,6 +357,7 @@ static struct configfs_attribute *gadget_root_attrs[] = {
&gadget_dev_desc_attr_idProduct,
&gadget_dev_desc_attr_bcdDevice,
&gadget_dev_desc_attr_bcdUSB,
+ &gadget_dev_desc_attr_max_speed,
&gadget_dev_desc_attr_UDC,
NULL,
};
@@ -1545,7 +1588,7 @@ static struct config_group *gadgets_make(
gi->composite.unbind = configfs_do_nothing;
gi->composite.suspend = NULL;
gi->composite.resume = NULL;
- gi->composite.max_speed = USB_SPEED_SUPER;
+ gi->composite.max_speed = gi->composite.gadget_driver.max_speed;
spin_lock_init(&gi->spinlock);
mutex_init(&gi->lock);
diff --git a/drivers/usb/gadget/function/f_tcm.c b/drivers/usb/gadget/function/f_tcm.c
index 7f01f78b1d23..6ca4fc3b3a3b 100644
--- a/drivers/usb/gadget/function/f_tcm.c
+++ b/drivers/usb/gadget/function/f_tcm.c
@@ -43,14 +43,17 @@ static inline struct f_uas *to_f_uas(struct usb_function *f)
/* Start bot.c code */
+static struct usbg_cdb *acquire_cmd_request(struct f_uas *fu);
+static void release_cmd_request(struct f_uas *fu, struct usb_request *req);
static int bot_enqueue_cmd_cbw(struct f_uas *fu)
{
int ret;
+ struct usbg_cdb *cmd = acquire_cmd_request(fu);
if (fu->flags & USBG_BOT_CMD_PEND)
return 0;
- ret = usb_ep_queue(fu->ep_out, fu->cmd.req, GFP_ATOMIC);
+ ret = usb_ep_queue(fu->ep_out, cmd->req, GFP_ATOMIC);
if (!ret)
fu->flags |= USBG_BOT_CMD_PEND;
return ret;
@@ -61,6 +64,7 @@ static void bot_status_complete(struct usb_ep *ep, struct usb_request *req)
struct usbg_cmd *cmd = req->context;
struct f_uas *fu = cmd->fu;
+ release_cmd_request(fu, req);
transport_generic_free_cmd(&cmd->se_cmd, 0);
if (req->status < 0) {
pr_err("ERR %s(%d)\n", __func__, __LINE__);
@@ -136,7 +140,7 @@ static void bot_send_bad_status(struct usbg_cmd *cmd)
}
req->complete = bot_err_compl;
req->context = cmd;
- req->buf = fu->cmd.buf;
+ req->buf = fu->cmd[0]->buf;
usb_ep_queue(ep, req, GFP_KERNEL);
} else {
bot_enqueue_sense_code(fu, cmd);
@@ -245,7 +249,6 @@ static int bot_send_write_request(struct usbg_cmd *cmd)
{
struct f_uas *fu = cmd->fu;
struct se_cmd *se_cmd = &cmd->se_cmd;
- struct usb_gadget *gadget = fuas_to_gadget(fu);
int ret;
init_completion(&cmd->write_complete);
@@ -256,22 +259,6 @@ static int bot_send_write_request(struct usbg_cmd *cmd)
return -EINVAL;
}
- if (!gadget->sg_supported) {
- cmd->data_buf = kmalloc(se_cmd->data_length, GFP_KERNEL);
- if (!cmd->data_buf)
- return -ENOMEM;
-
- fu->bot_req_out->buf = cmd->data_buf;
- } else {
- fu->bot_req_out->buf = NULL;
- fu->bot_req_out->num_sgs = se_cmd->t_data_nents;
- fu->bot_req_out->sg = se_cmd->t_data_sg;
- }
-
- fu->bot_req_out->complete = usbg_data_write_cmpl;
- fu->bot_req_out->length = se_cmd->data_length;
- fu->bot_req_out->context = cmd;
-
ret = usbg_prepare_w_request(cmd, fu->bot_req_out);
if (ret)
goto cleanup;
@@ -297,11 +284,84 @@ static void bot_cmd_complete(struct usb_ep *ep, struct usb_request *req)
if (req->status < 0)
return;
+ release_cmd_request(fu, req);
ret = bot_submit_command(fu, req->buf, req->actual);
if (ret)
pr_err("%s(%d): %d\n", __func__, __LINE__, ret);
}
+static struct usbg_cdb *acquire_cmd_request(struct f_uas *fu)
+{
+ int i;
+
+ for (i = 0; i < fu->ncmd; i++) {
+ if (!fu->cmd[i]->claimed) {
+ fu->cmd[i]->claimed = true;
+ return fu->cmd[i];
+ }
+ }
+ return NULL;
+}
+
+static void release_cmd_request(struct f_uas *fu, struct usb_request *req)
+{
+ int i;
+
+ for (i = 0; i < fu->ncmd; i++) {
+ if (fu->cmd[i]->req == req)
+ fu->cmd[i]->claimed = false;
+ }
+}
+
+static void free_cmd_resource(struct f_uas *fu, struct usb_ep *ep)
+{
+ int i;
+
+ for (i = 0; i < fu->ncmd; i++) {
+ if (fu->cmd[i]->req)
+ usb_ep_free_request(ep, fu->cmd[i]->req);
+
+ kfree(fu->cmd[i]->buf);
+ fu->cmd[i]->buf = NULL;
+
+ kfree(fu->cmd[i]);
+ fu->cmd[i] = NULL;
+ }
+}
+
+static int alloc_cmd_resource(struct f_uas *fu, int num, struct usb_ep *ep,
+ void (*complete)(struct usb_ep *ep,
+ struct usb_request *req))
+{
+ int i;
+
+ fu->ncmd = num;
+ for (i = 0; i < fu->ncmd; i++) {
+ fu->cmd[i] = kcalloc(fu->ncmd, sizeof(struct usbg_cdb),
+ GFP_KERNEL);
+ if (!fu->cmd)
+ goto err_cmd;
+
+ fu->cmd[i]->req = usb_ep_alloc_request(ep, GFP_KERNEL);
+ if (!fu->cmd[i]->req)
+ goto err_cmd;
+
+ fu->cmd[i]->buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL);
+ if (!fu->cmd[i]->buf)
+ goto err_cmd;
+
+ fu->cmd[i]->req->complete = complete;
+ fu->cmd[i]->req->buf = fu->cmd[i]->buf;
+ fu->cmd[i]->req->length = fu->ep_out->maxpacket;
+ fu->cmd[i]->req->context = fu;
+ }
+
+ return 0;
+err_cmd:
+ free_cmd_resource(fu, ep);
+ return -ENOMEM;
+}
+
static int bot_prepare_reqs(struct f_uas *fu)
{
int ret;
@@ -314,10 +374,6 @@ static int bot_prepare_reqs(struct f_uas *fu)
if (!fu->bot_req_out)
goto err_out;
- fu->cmd.req = usb_ep_alloc_request(fu->ep_out, GFP_KERNEL);
- if (!fu->cmd.req)
- goto err_cmd;
-
fu->bot_status.req = usb_ep_alloc_request(fu->ep_in, GFP_KERNEL);
if (!fu->bot_status.req)
goto err_sts;
@@ -327,28 +383,20 @@ static int bot_prepare_reqs(struct f_uas *fu)
fu->bot_status.req->complete = bot_status_complete;
fu->bot_status.csw.Signature = cpu_to_le32(US_BULK_CS_SIGN);
- fu->cmd.buf = kmalloc(fu->ep_out->maxpacket, GFP_KERNEL);
- if (!fu->cmd.buf)
- goto err_buf;
-
- fu->cmd.req->complete = bot_cmd_complete;
- fu->cmd.req->buf = fu->cmd.buf;
- fu->cmd.req->length = fu->ep_out->maxpacket;
- fu->cmd.req->context = fu;
+ ret = alloc_cmd_resource(fu, BOT_MAX_COMMANDS, fu->ep_out,
+ bot_cmd_complete);
+ if (ret)
+ goto err_cmd;
ret = bot_enqueue_cmd_cbw(fu);
if (ret)
goto err_queue;
return 0;
err_queue:
- kfree(fu->cmd.buf);
- fu->cmd.buf = NULL;
-err_buf:
+ free_cmd_resource(fu, fu->ep_out);
+err_cmd:
usb_ep_free_request(fu->ep_in, fu->bot_status.req);
err_sts:
- usb_ep_free_request(fu->ep_out, fu->cmd.req);
- fu->cmd.req = NULL;
-err_cmd:
usb_ep_free_request(fu->ep_out, fu->bot_req_out);
fu->bot_req_out = NULL;
err_out:
@@ -372,16 +420,13 @@ static void bot_cleanup_old_alt(struct f_uas *fu)
usb_ep_free_request(fu->ep_in, fu->bot_req_in);
usb_ep_free_request(fu->ep_out, fu->bot_req_out);
- usb_ep_free_request(fu->ep_out, fu->cmd.req);
usb_ep_free_request(fu->ep_in, fu->bot_status.req);
- kfree(fu->cmd.buf);
+ free_cmd_resource(fu, fu->ep_out);
fu->bot_req_in = NULL;
fu->bot_req_out = NULL;
- fu->cmd.req = NULL;
fu->bot_status.req = NULL;
- fu->cmd.buf = NULL;
}
static void bot_set_alt(struct f_uas *fu)
@@ -480,14 +525,6 @@ static void uasp_cleanup_one_stream(struct f_uas *fu, struct uas_stream *stream)
stream->req_status = NULL;
}
-static void uasp_free_cmdreq(struct f_uas *fu)
-{
- usb_ep_free_request(fu->ep_cmd, fu->cmd.req);
- kfree(fu->cmd.buf);
- fu->cmd.req = NULL;
- fu->cmd.buf = NULL;
-}
-
static void uasp_cleanup_old_alt(struct f_uas *fu)
{
int i;
@@ -502,7 +539,7 @@ static void uasp_cleanup_old_alt(struct f_uas *fu)
for (i = 0; i < UASP_SS_EP_COMP_NUM_STREAMS; i++)
uasp_cleanup_one_stream(fu, &fu->stream[i]);
- uasp_free_cmdreq(fu);
+ free_cmd_resource(fu, fu->ep_cmd);
}
static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req);
@@ -565,6 +602,7 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req)
struct usbg_cmd *cmd = req->context;
struct uas_stream *stream = cmd->stream;
struct f_uas *fu = cmd->fu;
+ struct usbg_cdb *cmd_cdb;
int ret;
if (req->status < 0)
@@ -599,7 +637,8 @@ static void uasp_status_data_cmpl(struct usb_ep *ep, struct usb_request *req)
case UASP_QUEUE_COMMAND:
transport_generic_free_cmd(&cmd->se_cmd, 0);
- usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC);
+ cmd_cdb = acquire_cmd_request(fu);
+ usb_ep_queue(fu->ep_cmd, cmd_cdb->req, GFP_ATOMIC);
break;
default:
@@ -719,11 +758,13 @@ static int usbg_submit_command(struct f_uas *, void *, unsigned int);
static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req)
{
struct f_uas *fu = req->context;
+ struct usbg_cdb *cmd;
int ret;
if (req->status < 0)
return;
+ release_cmd_request(fu, req);
ret = usbg_submit_command(fu, req->buf, req->actual);
/*
* Once we tune for performance enqueue the command req here again so
@@ -733,7 +774,8 @@ static void uasp_cmd_complete(struct usb_ep *ep, struct usb_request *req)
*/
if (!ret)
return;
- usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC);
+ cmd = acquire_cmd_request(fu);
+ usb_ep_queue(fu->ep_cmd, cmd->req, GFP_ATOMIC);
}
static int uasp_alloc_stream_res(struct f_uas *fu, struct uas_stream *stream)
@@ -761,28 +803,6 @@ out:
return -ENOMEM;
}
-static int uasp_alloc_cmd(struct f_uas *fu)
-{
- fu->cmd.req = usb_ep_alloc_request(fu->ep_cmd, GFP_KERNEL);
- if (!fu->cmd.req)
- goto err;
-
- fu->cmd.buf = kmalloc(fu->ep_cmd->maxpacket, GFP_KERNEL);
- if (!fu->cmd.buf)
- goto err_buf;
-
- fu->cmd.req->complete = uasp_cmd_complete;
- fu->cmd.req->buf = fu->cmd.buf;
- fu->cmd.req->length = fu->ep_cmd->maxpacket;
- fu->cmd.req->context = fu;
- return 0;
-
-err_buf:
- usb_ep_free_request(fu->ep_cmd, fu->cmd.req);
-err:
- return -ENOMEM;
-}
-
static void uasp_setup_stream_res(struct f_uas *fu, int max_streams)
{
int i;
@@ -800,12 +820,15 @@ static int uasp_prepare_reqs(struct f_uas *fu)
{
int ret;
int i;
- int max_streams;
+ int max_streams, max_commands;
- if (fu->flags & USBG_USE_STREAMS)
+ if (fu->flags & USBG_USE_STREAMS) {
+ max_commands = UASP_MAX_COMMANDS;
max_streams = UASP_SS_EP_COMP_NUM_STREAMS;
- else
+ } else {
+ max_commands = 1;
max_streams = 1;
+ }
for (i = 0; i < max_streams; i++) {
ret = uasp_alloc_stream_res(fu, &fu->stream[i]);
@@ -813,19 +836,25 @@ static int uasp_prepare_reqs(struct f_uas *fu)
goto err_cleanup;
}
- ret = uasp_alloc_cmd(fu);
+ ret = alloc_cmd_resource(fu, max_commands, fu->ep_cmd,
+ uasp_cmd_complete);
if (ret)
goto err_free_stream;
uasp_setup_stream_res(fu, max_streams);
- ret = usb_ep_queue(fu->ep_cmd, fu->cmd.req, GFP_ATOMIC);
- if (ret)
- goto err_free_stream;
+ /* queue number of commands */
+ for (i = 0; i < fu->ncmd; i++) {
+ struct usbg_cdb *cmd = acquire_cmd_request(fu);
+
+ ret = usb_ep_queue(fu->ep_cmd, cmd->req, GFP_ATOMIC);
+ if (ret)
+ goto err_free_stream;
+ }
return 0;
err_free_stream:
- uasp_free_cmdreq(fu);
+ free_cmd_resource(fu, fu->ep_cmd);
err_cleanup:
if (i) {
@@ -838,16 +867,28 @@ err_cleanup:
return ret;
}
+#define SS_BOT_INTERFACE_DESC_NO 5
static void uasp_set_alt(struct f_uas *fu)
{
struct usb_function *f = &fu->function;
struct usb_gadget *gadget = f->config->cdev->gadget;
+ struct usb_descriptor_header **ss_uasp_backup = f->ss_descriptors;
int ret;
fu->flags = USBG_IS_UAS;
- if (gadget->speed == USB_SPEED_SUPER)
+ if (gadget->speed == USB_SPEED_SUPER) {
fu->flags |= USBG_USE_STREAMS;
+ /* If device connect in SS then comp_descriptor with stream
+ * should be attached to descriptor. Since BOT and UAS using
+ * same endpoint, config_ep_by_speed will returns first match
+ * with comp_descriptor without stream. This is just workaround
+ * proper fix need to be introduced. Here advancing descritor
+ * header ss_descriptors with number of descriptor present in
+ * BOT mode.
+ */
+ f->ss_descriptors += SS_BOT_INTERFACE_DESC_NO;
+ }
config_ep_by_speed(gadget, f, fu->ep_in);
ret = usb_ep_enable(fu->ep_in);
@@ -873,6 +914,10 @@ static void uasp_set_alt(struct f_uas *fu)
goto err_wq;
fu->flags |= USBG_ENABLED;
+ /* restore ss_descriptors */
+ if (gadget->speed == USB_SPEED_SUPER)
+ f->ss_descriptors = ss_uasp_backup;
+
pr_info("Using the UAS protocol\n");
return;
err_wq:
@@ -884,6 +929,9 @@ err_cmd:
err_b_out:
usb_ep_disable(fu->ep_in);
err_b_in:
+ /* restore ss_descriptors */
+ if (gadget->speed == USB_SPEED_SUPER)
+ f->ss_descriptors = ss_uasp_backup;
fu->flags = 0;
}
@@ -949,6 +997,56 @@ static int get_cmd_dir(const unsigned char *cdb)
return ret;
}
+static void recover_w_length_with_maxpacket(struct usbg_cmd *cmd,
+ struct usb_request *req)
+{
+ struct se_cmd *se_cmd = &cmd->se_cmd;
+ struct f_uas *fu = cmd->fu;
+ struct usb_gadget *gadget = fuas_to_gadget(fu);
+ int rem;
+
+ rem = se_cmd->data_length % fu->ep_out->maxpacket;
+ if (rem) {
+ /* recover paded data length */
+ cmd->data_len -= fu->ep_out->maxpacket - rem;
+
+ if (gadget->sg_supported) {
+ struct scatterlist *s = sg_last(se_cmd->t_data_sg,
+ se_cmd->t_data_nents);
+
+ s->length -= fu->ep_out->maxpacket - rem;
+ }
+ }
+}
+
+static void adjust_w_length_with_maxpacket(struct usbg_cmd *cmd,
+ struct usb_request *req)
+{
+ struct se_cmd *se_cmd = &cmd->se_cmd;
+ struct f_uas *fu = cmd->fu;
+ struct usb_gadget *gadget = fuas_to_gadget(fu);
+ int rem;
+
+ cmd->data_len = se_cmd->data_length;
+ rem = cmd->data_len % fu->ep_out->maxpacket;
+ if (rem) {
+ /* pad data length so that transfer size can be in multiple of
+ * max packet size
+ */
+ cmd->data_len += fu->ep_out->maxpacket - rem;
+
+ if (gadget->sg_supported) {
+ /* if sg is supported and data length in page also need
+ * to be adjusted as multiple of max packet size.
+ */
+ struct scatterlist *s = sg_last(se_cmd->t_data_sg,
+ se_cmd->t_data_nents);
+
+ s->length += fu->ep_out->maxpacket - rem;
+ }
+ }
+}
+
static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req)
{
struct usbg_cmd *cmd = req->context;
@@ -959,6 +1057,8 @@ static void usbg_data_write_cmpl(struct usb_ep *ep, struct usb_request *req)
goto cleanup;
}
+ recover_w_length_with_maxpacket(cmd, req);
+
if (req->num_sgs == 0) {
sg_copy_from_buffer(se_cmd->t_data_sg,
se_cmd->t_data_nents,
@@ -979,8 +1079,10 @@ static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req)
struct f_uas *fu = cmd->fu;
struct usb_gadget *gadget = fuas_to_gadget(fu);
+ adjust_w_length_with_maxpacket(cmd, req);
+
if (!gadget->sg_supported) {
- cmd->data_buf = kmalloc(se_cmd->data_length, GFP_ATOMIC);
+ cmd->data_buf = kmalloc(cmd->data_len, GFP_ATOMIC);
if (!cmd->data_buf)
return -ENOMEM;
@@ -992,7 +1094,7 @@ static int usbg_prepare_w_request(struct usbg_cmd *cmd, struct usb_request *req)
}
req->complete = usbg_data_write_cmpl;
- req->length = se_cmd->data_length;
+ req->length = cmd->data_len;
req->context = cmd;
return 0;
}
@@ -1185,7 +1287,8 @@ static void bot_cmd_work(struct work_struct *work)
if (target_submit_cmd(se_cmd, tv_nexus->tvn_se_sess,
cmd->cmd_buf, cmd->sense_iu.sense, cmd->unpacked_lun,
- cmd->data_len, cmd->prio_attr, dir, 0) < 0)
+ cmd->data_len, cmd->prio_attr, dir,
+ TARGET_SCF_ACK_KREF) < 0)
goto out;
return;
@@ -1674,9 +1777,11 @@ static ssize_t tcm_usbg_tpg_nexus_store(struct config_item *item,
CONFIGFS_ATTR(tcm_usbg_tpg_, enable);
CONFIGFS_ATTR(tcm_usbg_tpg_, nexus);
+static struct configfs_attribute tcm_usbg_tpg_attr_maxburst;
static struct configfs_attribute *usbg_base_attrs[] = {
&tcm_usbg_tpg_attr_enable,
&tcm_usbg_tpg_attr_nexus,
+ &tcm_usbg_tpg_attr_maxburst,
NULL,
};
@@ -1984,6 +2089,32 @@ static struct usb_gadget_strings *tcm_strings[] = {
NULL,
};
+static ssize_t tcm_usbg_tpg_maxburst_show(struct config_item *item, char *page)
+{
+ return snprintf(page, PAGE_SIZE, "%u\n", uasp_cmd_comp_desc.bMaxBurst);
+}
+
+static ssize_t tcm_usbg_tpg_maxburst_store(struct config_item *item,
+ const char *page, size_t count)
+{
+ int value;
+ int ret;
+
+ ret = kstrtouint(page, 10, &value);
+ if (ret)
+ return ret;
+
+ uasp_bi_ep_comp_desc.bMaxBurst = value;
+ uasp_bo_ep_comp_desc.bMaxBurst = value;
+ uasp_status_in_ep_comp_desc.bMaxBurst = value;
+ uasp_cmd_comp_desc.bMaxBurst = value;
+ bot_bi_ep_comp_desc.bMaxBurst = value;
+ bot_bo_ep_comp_desc.bMaxBurst = value;
+
+ return count;
+}
+CONFIGFS_ATTR(tcm_usbg_tpg_, maxburst);
+
static int tcm_bind(struct usb_configuration *c, struct usb_function *f)
{
struct f_uas *fu = to_f_uas(f);
@@ -2112,6 +2243,13 @@ static int tcm_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
return -EOPNOTSUPP;
}
+static int tcm_get_alt(struct usb_function *f, unsigned int intf)
+{
+ struct f_uas *fu = to_f_uas(f);
+
+ return fu->flags & USBG_IS_UAS ? 1 : 0;
+}
+
static void tcm_disable(struct usb_function *f)
{
struct f_uas *fu = to_f_uas(f);
@@ -2300,6 +2438,7 @@ static struct usb_function *tcm_alloc(struct usb_function_instance *fi)
fu->function.bind = tcm_bind;
fu->function.unbind = tcm_unbind;
fu->function.set_alt = tcm_set_alt;
+ fu->function.get_alt = tcm_get_alt;
fu->function.setup = tcm_setup;
fu->function.disable = tcm_disable;
fu->function.free_func = tcm_free;
diff --git a/drivers/usb/gadget/function/tcm.h b/drivers/usb/gadget/function/tcm.h
index 3cd565794ad7..54ed3bca8add 100644
--- a/drivers/usb/gadget/function/tcm.h
+++ b/drivers/usb/gadget/function/tcm.h
@@ -98,6 +98,7 @@ struct uas_stream {
struct usbg_cdb {
struct usb_request *req;
void *buf;
+ bool claimed;
};
struct bot_status {
@@ -105,6 +106,9 @@ struct bot_status {
struct bulk_cs_wrap csw;
};
+#define UASP_MAX_COMMANDS 6
+#define BOT_MAX_COMMANDS 1
+#define MAX_COMMANDS UASP_MAX_COMMANDS
struct f_uas {
struct usbg_tpg *tpg;
struct usb_function function;
@@ -117,7 +121,8 @@ struct f_uas {
#define USBG_IS_BOT (1 << 3)
#define USBG_BOT_CMD_PEND (1 << 4)
- struct usbg_cdb cmd;
+ u32 ncmd;
+ struct usbg_cdb *cmd[MAX_COMMANDS];
struct usb_ep *ep_in;
struct usb_ep *ep_out;
diff --git a/drivers/usb/gadget/function/uvc_video.c b/drivers/usb/gadget/function/uvc_video.c
index 5c042f380708..de64182964a9 100644
--- a/drivers/usb/gadget/function/uvc_video.c
+++ b/drivers/usb/gadget/function/uvc_video.c
@@ -207,8 +207,8 @@ uvc_video_complete(struct usb_ep *ep, struct usb_request *req)
video->encode(req, video, buf);
- ret = uvcg_video_ep_queue(video, req);
spin_unlock_irqrestore(&video->queue.irqlock, flags);
+ ret = uvcg_video_ep_queue(video, req);
if (ret < 0) {
uvcg_queue_cancel(queue, 0);
@@ -332,9 +332,10 @@ int uvcg_video_pump(struct uvc_video *video)
video->encode(req, video, buf);
+ spin_unlock_irqrestore(&queue->irqlock, flags);
+
/* Queue the USB request */
ret = uvcg_video_ep_queue(video, req);
- spin_unlock_irqrestore(&queue->irqlock, flags);
if (ret < 0) {
uvcg_queue_cancel(queue, 0);
diff --git a/drivers/usb/gadget/udc/udc-xilinx.c b/drivers/usb/gadget/udc/udc-xilinx.c
index b1f4104d1283..d492c51e811a 100644
--- a/drivers/usb/gadget/udc/udc-xilinx.c
+++ b/drivers/usb/gadget/udc/udc-xilinx.c
@@ -11,6 +11,7 @@
* USB peripheral controller (at91_udc.c).
*/
+#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>
@@ -171,6 +172,7 @@ struct xusb_ep {
* @addr: the usb device base address
* @lock: instance of spinlock
* @dma_enabled: flag indicating whether the dma is included in the system
+ * @clk: pointer to struct clk
* @read_fn: function pointer to read device registers
* @write_fn: function pointer to write to device registers
*/
@@ -188,8 +190,9 @@ struct xusb_udc {
void __iomem *addr;
spinlock_t lock;
bool dma_enabled;
+ struct clk *clk;
- unsigned int (*read_fn)(void __iomem *);
+ unsigned int (*read_fn)(void __iomem *reg);
void (*write_fn)(void __iomem *, u32, u32);
};
@@ -1399,7 +1402,6 @@ err:
/**
* xudc_stop - stops the device.
* @gadget: pointer to the usb gadget structure
- * @driver: pointer to usb gadget driver structure
*
* Return: zero always
*/
@@ -1732,7 +1734,7 @@ static void xudc_set_clear_feature(struct xusb_udc *udc)
*
* Process setup packet and delegate to gadget layer.
*/
-static void xudc_handle_setup(struct xusb_udc *udc)
+static void xudc_handle_setup(struct xusb_udc *udc) __must_hold(&udc->lock)
{
struct xusb_ep *ep0 = &udc->ep[0];
struct usb_ctrlrequest setup;
@@ -2094,6 +2096,26 @@ static int xudc_probe(struct platform_device *pdev)
udc->gadget.ep0 = &udc->ep[XUSB_EP_NUMBER_ZERO].ep_usb;
udc->gadget.name = driver_name;
+ udc->clk = devm_clk_get(&pdev->dev, "s_axi_aclk");
+ if (IS_ERR(udc->clk)) {
+ if (PTR_ERR(udc->clk) != -ENOENT) {
+ ret = PTR_ERR(udc->clk);
+ goto fail;
+ }
+
+ /*
+ * Clock framework support is optional, continue on,
+ * anyways if we don't find a matching clock
+ */
+ udc->clk = NULL;
+ }
+
+ ret = clk_prepare_enable(udc->clk);
+ if (ret) {
+ dev_err(&pdev->dev, "Unable to enable clock.\n");
+ return ret;
+ }
+
spin_lock_init(&udc->lock);
/* Check for IP endianness */
@@ -2149,10 +2171,62 @@ static int xudc_remove(struct platform_device *pdev)
struct xusb_udc *udc = platform_get_drvdata(pdev);
usb_del_gadget_udc(&udc->gadget);
+ clk_disable_unprepare(udc->clk);
return 0;
}
+#ifdef CONFIG_PM_SLEEP
+static int xudc_suspend(struct device *dev)
+{
+ struct xusb_udc *udc;
+ u32 crtlreg;
+ unsigned long flags;
+
+ udc = dev_get_drvdata(dev);
+
+ spin_lock_irqsave(&udc->lock, flags);
+
+ crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET);
+ crtlreg &= ~XUSB_CONTROL_USB_READY_MASK;
+
+ udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg);
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+ if (udc->driver && udc->driver->disconnect)
+ udc->driver->disconnect(&udc->gadget);
+
+ clk_disable(udc->clk);
+
+ return 0;
+}
+
+static int xudc_resume(struct device *dev)
+{
+ struct xusb_udc *udc;
+ u32 crtlreg;
+ unsigned long flags;
+
+ udc = dev_get_drvdata(dev);
+
+ clk_enable(udc->clk);
+ spin_lock_irqsave(&udc->lock, flags);
+
+ crtlreg = udc->read_fn(udc->addr + XUSB_CONTROL_OFFSET);
+ crtlreg |= XUSB_CONTROL_USB_READY_MASK;
+
+ udc->write_fn(udc->addr, XUSB_CONTROL_OFFSET, crtlreg);
+
+ spin_unlock_irqrestore(&udc->lock, flags);
+
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct dev_pm_ops xudc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(xudc_suspend, xudc_resume)
+};
+
/* Match table for of_platform binding */
static const struct of_device_id usb_of_match[] = {
{ .compatible = "xlnx,usb2-device-4.00.a", },
@@ -2164,6 +2238,7 @@ static struct platform_driver xudc_driver = {
.driver = {
.name = driver_name,
.of_match_table = usb_of_match,
+ .pm = &xudc_pm_ops,
},
.probe = xudc_probe,
.remove = xudc_remove,
diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c
index d2a27578e440..e2d0f7809c83 100644
--- a/drivers/usb/host/ehci-xilinx-of.c
+++ b/drivers/usb/host/ehci-xilinx-of.c
@@ -32,6 +32,8 @@
* There are cases when the host controller fails to enable the port due to,
* for example, insufficient power that can be supplied to the device from
* the USB bus. In those cases, the messages printed here are not helpful.
+ *
+ * Return: Always return 0
*/
static int ehci_xilinx_port_handed_over(struct usb_hcd *hcd, int portnum)
{
@@ -46,11 +48,9 @@ static int ehci_xilinx_port_handed_over(struct usb_hcd *hcd, int portnum)
dev_warn(hcd->self.controller,
"Maybe your device is not a high speed device?\n");
dev_warn(hcd->self.controller,
- "The USB host controller does not support full speed "
- "nor low speed devices\n");
+ "USB host controller doesn't support FS/LS devices\n");
dev_warn(hcd->self.controller,
- "You can reconfigure the host controller to have "
- "full speed support\n");
+ "You can reconfigure host controller to support FS\n");
}
return 0;
@@ -112,6 +112,8 @@ static const struct hc_driver ehci_xilinx_of_hc_driver = {
* host controller. Because the Xilinx USB host controller can be configured
* as HS only or HS/FS only, it checks the configuration in the device tree
* entry, and sets an appropriate value for hcd->has_tt.
+ *
+ * Return: zero on success, 'rv' value on failure
*/
static int ehci_hcd_xilinx_of_probe(struct platform_device *op)
{
@@ -196,6 +198,8 @@ err_irq:
*
* Remove the hcd structure, and release resources that has been requested
* during probe.
+ *
+ * Return: Always return 0
*/
static int ehci_hcd_xilinx_of_remove(struct platform_device *op)
{
diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c
index 4ba0576bafa1..5bfb069cd33b 100644
--- a/drivers/usb/host/xhci-hub.c
+++ b/drivers/usb/host/xhci-hub.c
@@ -1823,6 +1823,13 @@ int xhci_bus_resume(struct usb_hcd *hcd)
}
}
+ /* After resuming back from suspend, the controller may not initiate
+ * LFPS.U3_exit signalling if not given a delay after updating the
+ * link from U3->U0. So, lets wait for atleast 1ms
+ */
+ if (next_state == XDEV_U0)
+ mdelay(1);
+
/* poll for U0 link state complete, both USB2 and USB3 */
for_each_set_bit(port_index, &bus_state->bus_suspended, BITS_PER_LONG) {
sret = xhci_handshake(ports[port_index]->addr, PORT_PLC,
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 4e168de8944d..456f5d0667fe 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -685,6 +685,16 @@ struct xhci_stream_info *xhci_alloc_stream_info(struct xhci_hcd *xhci,
xhci_dbg(xhci, "Setting stream %d ring ptr to 0x%08llx\n",
cur_stream, (unsigned long long) addr);
+ if (xhci->quirks & XHCI_STREAM_QUIRK) {
+ /* dwc3 host controller has an issue where it doesn't
+ * process BULK IN stream rings even after ringing
+ * DoorBell, so setup a timer to aviod hang condition.
+ */
+ timer_setup(&cur_ring->stream_timer,
+ xhci_stream_timeout, 0);
+ cur_ring->xhci = xhci;
+ }
+
ret = xhci_update_stream_mapping(cur_ring, mem_flags);
if (ret) {
xhci_ring_free(xhci, cur_ring);
@@ -771,6 +781,10 @@ void xhci_free_stream_info(struct xhci_hcd *xhci,
for (cur_stream = 1; cur_stream < stream_info->num_streams;
cur_stream++) {
cur_ring = stream_info->stream_rings[cur_stream];
+
+ if (xhci->quirks & XHCI_STREAM_QUIRK)
+ del_timer_sync(&cur_ring->stream_timer);
+
if (cur_ring) {
xhci_ring_free(xhci, cur_ring);
stream_info->stream_rings[cur_stream] = NULL;
diff --git a/drivers/usb/host/xhci-plat.c b/drivers/usb/host/xhci-plat.c
index 19c5eee20eb4..3f8b8e3146e7 100644
--- a/drivers/usb/host/xhci-plat.c
+++ b/drivers/usb/host/xhci-plat.c
@@ -19,6 +19,8 @@
#include <linux/slab.h>
#include <linux/acpi.h>
#include <linux/usb/of.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/xhci_pdriver.h>
#include "xhci.h"
#include "xhci-plat.h"
@@ -157,6 +159,35 @@ static const struct of_device_id usb_xhci_of_match[] = {
MODULE_DEVICE_TABLE(of, usb_xhci_of_match);
#endif
+static int usb_otg_set_host(struct device *dev, struct usb_hcd *hcd, bool yes)
+{
+ int ret = 0;
+
+ hcd->usb_phy = usb_get_phy(USB_PHY_TYPE_USB3);
+ if (!IS_ERR_OR_NULL(hcd->usb_phy) && hcd->usb_phy->otg) {
+ if (yes) {
+ if (otg_set_host(hcd->usb_phy->otg, &hcd->self)) {
+ usb_put_phy(hcd->usb_phy);
+ goto disable_phy;
+ }
+ } else {
+ ret = otg_set_host(hcd->usb_phy->otg, NULL);
+ usb_put_phy(hcd->usb_phy);
+ goto disable_phy;
+ }
+
+ } else {
+ goto disable_phy;
+ }
+
+ return 0;
+
+disable_phy:
+ hcd->usb_phy = NULL;
+
+ return ret;
+}
+
static int xhci_plat_probe(struct platform_device *pdev)
{
const struct xhci_plat_priv *priv_match;
@@ -212,6 +243,9 @@ static int xhci_plat_probe(struct platform_device *pdev)
return ret;
}
+ /* Set the controller as wakeup capable */
+ device_set_wakeup_capable(&pdev->dev, true);
+
pm_runtime_set_active(&pdev->dev);
pm_runtime_enable(&pdev->dev);
pm_runtime_get_noresume(&pdev->dev);
@@ -284,6 +318,12 @@ static int xhci_plat_probe(struct platform_device *pdev)
/* Iterate over all parent nodes for finding quirks */
for (tmpdev = &pdev->dev; tmpdev; tmpdev = tmpdev->parent) {
+ if (device_property_read_bool(&pdev->dev, "xhci-stream-quirk"))
+ xhci->quirks |= XHCI_STREAM_QUIRK;
+
+ if (device_property_read_bool(&pdev->dev, "quirk-broken-port-ped"))
+ xhci->quirks |= XHCI_BROKEN_PORT_PED;
+
if (device_property_read_bool(tmpdev, "usb2-lpm-disable"))
xhci->quirks |= XHCI_HW_LPM_DISABLE;
@@ -323,6 +363,10 @@ static int xhci_plat_probe(struct platform_device *pdev)
if (ret)
goto dealloc_usb2_hcd;
+ ret = usb_otg_set_host(&pdev->dev, hcd, true);
+ if (ret)
+ goto dealloc_usb2_hcd;
+
device_enable_async_suspend(&pdev->dev);
pm_runtime_put_noidle(&pdev->dev);
@@ -375,6 +419,8 @@ static int xhci_plat_remove(struct platform_device *dev)
xhci->shared_hcd = NULL;
usb_phy_shutdown(hcd->usb_phy);
+ usb_otg_set_host(&dev->dev, hcd, false);
+
usb_remove_hcd(hcd);
usb_put_hcd(shared_hcd);
@@ -394,6 +440,16 @@ static int __maybe_unused xhci_plat_suspend(struct device *dev)
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+#if IS_ENABLED(CONFIG_USB_DWC3_OF_SIMPLE)
+ /* Inform dwc3 driver about the device wakeup capability */
+ if (device_may_wakeup(&hcd->self.root_hub->dev)) {
+ enable_irq_wake(hcd->irq);
+ dwc3_host_wakeup_capable(dev, true);
+ } else {
+ dwc3_host_wakeup_capable(dev, false);
+ }
+#endif
+
/*
* xhci_suspend() needs `do_wakeup` to know whether host is allowed
* to do wakeup during suspend. Since xhci_plat_suspend is currently
@@ -431,6 +487,9 @@ static int __maybe_unused xhci_plat_runtime_resume(struct device *dev)
struct usb_hcd *hcd = dev_get_drvdata(dev);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+ if (device_may_wakeup(&hcd->self.root_hub->dev))
+ disable_irq_wake(hcd->irq);
+
return xhci_resume(xhci, 0);
}
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 5a93a225c97e..fb4581dc43e2 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -829,9 +829,21 @@ remove_finished_td:
*/
ep_ring = xhci_urb_to_transfer_ring(xhci, cur_td->urb);
xhci_unmap_td_bounce_buffer(xhci, ep_ring, cur_td);
+
inc_td_cnt(cur_td->urb);
- if (last_td_in_urb(cur_td))
- xhci_giveback_urb_in_irq(xhci, cur_td, 0);
+
+ if (last_td_in_urb(cur_td)) {
+ if ((xhci->quirks & XHCI_STREAM_QUIRK) &&
+ (ep_ring->stream_timeout_handler == true)) {
+ /* We get here if stream timer timed out and stop
+ * command is issued. Send urb status as -EAGAIN
+ * so that the same urb can be re-submitted.
+ */
+ xhci_giveback_urb_in_irq(xhci, cur_td, -EAGAIN);
+ ep_ring->stream_timeout_handler = false;
+ } else
+ xhci_giveback_urb_in_irq(xhci, cur_td, 0);
+ }
/* Stop processing the cancelled list if the watchdog timer is
* running.
@@ -940,6 +952,84 @@ void xhci_hc_died(struct xhci_hcd *xhci)
usb_hc_died(xhci_to_hcd(xhci));
}
+/* This function is called when the stream ring timer gets timedout.
+ * dwc3 host controller has an issue where it doesn't process the BULK IN
+ * stream ring TD's(once in a while) even after ringing DoorBell for that
+ * stream ring. Because of this behaviour there will be no transfer events
+ * generated by the controller on the stream ring, resulting in the hang
+ * condition. xhci_stream_timeout() solves this issue by sending a stop
+ * command on the stream ring after stream timer gets timedout.
+ */
+void xhci_stream_timeout(struct timer_list *arg)
+{
+ struct xhci_hcd *xhci;
+ struct xhci_virt_ep *ep;
+ struct xhci_ring *ep_ring;
+ unsigned int slot_id, ep_index, stream_id;
+ struct xhci_td *td = NULL;
+ struct urb *urb = NULL;
+ struct urb_priv *urb_priv;
+ struct xhci_command *command;
+ unsigned long flags;
+ int i;
+
+ ep_ring = from_timer(ep_ring, arg, stream_timer);
+ xhci = ep_ring->xhci;
+
+ spin_lock_irqsave(&xhci->lock, flags);
+
+ if (!list_empty(&ep_ring->td_list)) {
+ td = list_entry(ep_ring->td_list.next, struct xhci_td, td_list);
+ urb = td->urb;
+ urb_priv = urb->hcpriv;
+
+ slot_id = urb->dev->slot_id;
+ ep_index = xhci_get_endpoint_index(&urb->ep->desc);
+ stream_id = ep_ring->stream_id;
+ ep = &xhci->devs[slot_id]->eps[ep_index];
+ ep_ring->stream_timeout_handler = true;
+
+ /* Delete the stream ring timer */
+ del_timer(&ep_ring->stream_timer);
+
+ for (i = 0; i < urb_priv->num_tds; i++) {
+ td = &urb_priv->td[i];
+ list_add_tail(&td->cancelled_td_list,
+ &ep->cancelled_td_list);
+ }
+
+ /* Queue a stop endpoint command, but only if this is
+ * the first cancellation to be handled.
+ */
+ if (!(ep->ep_state & EP_STOP_CMD_PENDING)) {
+ command = xhci_alloc_command(xhci, false,
+ GFP_ATOMIC);
+ if (!command) {
+ xhci_warn(xhci,
+ "%s: Failed to allocate command\n",
+ __func__);
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ return;
+ }
+
+ ep->ep_state |= EP_STOP_CMD_PENDING;
+ ep->stop_cmd_timer.expires = jiffies +
+ XHCI_STOP_EP_CMD_TIMEOUT * HZ;
+ add_timer(&ep->stop_cmd_timer);
+ xhci_queue_stop_endpoint(xhci, command,
+ urb->dev->slot_id, ep_index, 0);
+ xhci_ring_cmd_db(xhci);
+ }
+
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ return;
+ }
+
+ spin_unlock_irqrestore(&xhci->lock, flags);
+ /* let the SCSI stack take care */
+ del_timer(&ep_ring->stream_timer);
+}
+
/* Watchdog timer function for when a stop endpoint command fails to complete.
* In this case, we assume the host controller is broken or dying or dead. The
* host may still be completing some other events, so we have to be careful to
@@ -2364,6 +2454,10 @@ static int handle_tx_event(struct xhci_hcd *xhci,
td_num++;
}
+ if ((xhci->quirks & XHCI_STREAM_QUIRK) &&
+ (ep->ep_state & EP_HAS_STREAMS))
+ del_timer(&ep_ring->stream_timer);
+
/* Look for common error cases */
switch (trb_comp_code) {
/* Skip codes that require special handling depending on
@@ -3411,6 +3505,15 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
}
check_trb_math(urb, enqd_len);
+
+ if ((xhci->quirks & XHCI_STREAM_QUIRK) && (urb->stream_id > 0) &&
+ (usb_endpoint_dir_in(&urb->ep->desc) == 1)) {
+ /* Start the stream timer so that xhci_stream_timeout() can be
+ * triggered if xhci is stuck while processing BULK IN streams.
+ */
+ ring->stream_timeout_handler = false;
+ mod_timer(&ring->stream_timer, jiffies + 5 * HZ);
+ }
giveback_first_trb(xhci, slot_id, ep_index, urb->stream_id,
start_cycle, start_trb);
return 0;
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index 1f7d35b3a937..bf7da58b5751 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -183,7 +183,11 @@ int xhci_reset(struct xhci_hcd *xhci)
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "// Reset the HC");
command = readl(&xhci->op_regs->command);
+#ifdef CONFIG_USB_DWC3_OTG
+ command |= CMD_LRESET;
+#else
command |= CMD_RESET;
+#endif
writel(command, &xhci->op_regs->command);
/* Existing Intel xHCI controllers require a delay of 1 mS,
@@ -197,7 +201,12 @@ int xhci_reset(struct xhci_hcd *xhci)
udelay(1000);
ret = xhci_handshake(&xhci->op_regs->command,
- CMD_RESET, 0, 10 * 1000 * 1000);
+#ifdef CONFIG_USB_DWC3_OTG
+ CMD_LRESET,
+#else
+ CMD_RESET,
+#endif
+ 0, 10 * 1000 * 1000);
if (ret)
return ret;
@@ -718,6 +727,12 @@ static void xhci_stop(struct usb_hcd *hcd)
/* Only halt host and free memory after both hcds are removed */
if (!usb_hcd_is_primary_hcd(hcd)) {
+ /* Remove shared_hcd if no otg ports are present */
+ if (!hcd->self.otg_port) {
+ /* usb core will free this hcd shortly, unset pointer */
+ xhci->shared_hcd = NULL;
+ }
+
mutex_unlock(&xhci->mutex);
return;
}
@@ -1670,8 +1685,21 @@ static int xhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status)
goto err_giveback;
}
+ ep_index = xhci_get_endpoint_index(&urb->ep->desc);
+ ep = &xhci->devs[urb->dev->slot_id]->eps[ep_index];
+ ep_ring = xhci_urb_to_transfer_ring(xhci, urb);
+ if (!ep_ring) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Delete the stream timer */
+ if ((xhci->quirks & XHCI_STREAM_QUIRK) && (urb->stream_id > 0))
+ del_timer(&ep_ring->stream_timer);
+
i = urb_priv->num_tds_done;
if (i < urb_priv->num_tds)
+
xhci_dbg_trace(xhci, trace_xhci_dbg_cancel_urb,
"Cancel URB %p, dev %s, ep 0x%x, "
"starting at offset 0x%llx",
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index b3afc7b76662..fbed91fa590b 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1612,6 +1612,9 @@ struct xhci_ring {
enum xhci_ring_type type;
bool last_td_was_short;
struct radix_tree_root *trb_address_map;
+ struct timer_list stream_timer;
+ bool stream_timeout_handler;
+ struct xhci_hcd *xhci;
};
struct xhci_erst_entry {
@@ -1871,6 +1874,7 @@ struct xhci_hcd {
#define XHCI_DEFAULT_PM_RUNTIME_ALLOW BIT_ULL(33)
#define XHCI_RESET_PLL_ON_DISCONNECT BIT_ULL(34)
#define XHCI_SNPS_BROKEN_SUSPEND BIT_ULL(35)
+#define XHCI_STREAM_QUIRK BIT_ULL(36) /* FIXME this is wrong */
unsigned int num_active_eps;
unsigned int limit_active_eps;
@@ -2118,6 +2122,7 @@ void xhci_cleanup_stalled_ring(struct xhci_hcd *xhci, unsigned int slot_id,
unsigned int ep_index, unsigned int stream_id,
struct xhci_td *td);
void xhci_stop_endpoint_command_watchdog(struct timer_list *t);
+void xhci_stream_timeout(struct timer_list *unused);
void xhci_handle_command_timeout(struct work_struct *work);
void xhci_ring_ep_doorbell(struct xhci_hcd *xhci, unsigned int slot_id,
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index 24b4f091acb8..6b9114b2a6e6 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -173,6 +173,7 @@ config USB_TEGRA_PHY
config USB_ULPI
bool "Generic ULPI Transceiver Driver"
depends on ARM || ARM64
+ depends on USB_PHY
select USB_ULPI_VIEWPORT
help
Enable this to support ULPI connected USB OTG transceivers which
diff --git a/drivers/usb/phy/phy-ulpi.c b/drivers/usb/phy/phy-ulpi.c
index a43c49369a60..0f7f6eb16041 100644
--- a/drivers/usb/phy/phy-ulpi.c
+++ b/drivers/usb/phy/phy-ulpi.c
@@ -13,9 +13,16 @@
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/export.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/io.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
#include <linux/usb.h>
#include <linux/usb/otg.h>
#include <linux/usb/ulpi.h>
+#include <linux/usb/phy.h>
struct ulpi_info {
@@ -39,6 +46,13 @@ static struct ulpi_info ulpi_ids[] = {
ULPI_INFO(ULPI_ID(0x0451, 0x1507), "TI TUSB1210"),
};
+struct ulpi_phy {
+ struct usb_phy *usb_phy;
+ void __iomem *regs;
+ unsigned int vp_offset;
+ unsigned int flags;
+};
+
static int ulpi_set_otg_flags(struct usb_phy *phy)
{
unsigned int flags = ULPI_OTG_CTRL_DP_PULLDOWN |
@@ -240,6 +254,23 @@ static int ulpi_set_vbus(struct usb_otg *otg, bool on)
return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
}
+static int usbphy_set_vbus(struct usb_phy *phy, int on)
+{
+ unsigned int flags = usb_phy_io_read(phy, ULPI_OTG_CTRL);
+
+ flags &= ~(ULPI_OTG_CTRL_DRVVBUS | ULPI_OTG_CTRL_DRVVBUS_EXT);
+
+ if (on) {
+ if (phy->flags & ULPI_OTG_DRVVBUS)
+ flags |= ULPI_OTG_CTRL_DRVVBUS;
+
+ if (phy->flags & ULPI_OTG_DRVVBUS_EXT)
+ flags |= ULPI_OTG_CTRL_DRVVBUS_EXT;
+ }
+
+ return usb_phy_io_write(phy, flags, ULPI_OTG_CTRL);
+}
+
struct usb_phy *
otg_ulpi_create(struct usb_phy_io_ops *ops,
unsigned int flags)
@@ -262,6 +293,7 @@ otg_ulpi_create(struct usb_phy_io_ops *ops,
phy->io_ops = ops;
phy->otg = otg;
phy->init = ulpi_init;
+ phy->set_vbus = usbphy_set_vbus;
otg->usb_phy = phy;
otg->set_host = ulpi_set_host;
@@ -271,3 +303,70 @@ otg_ulpi_create(struct usb_phy_io_ops *ops,
}
EXPORT_SYMBOL_GPL(otg_ulpi_create);
+static int ulpi_phy_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct resource *res;
+ struct ulpi_phy *uphy;
+ bool flag;
+ int ret;
+
+ uphy = devm_kzalloc(&pdev->dev, sizeof(*uphy), GFP_KERNEL);
+ if (!uphy)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ uphy->regs = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+ if (IS_ERR(uphy->regs))
+ return PTR_ERR(uphy->regs);
+
+ ret = of_property_read_u32(np, "view-port", &uphy->vp_offset);
+ if (IS_ERR(uphy->regs)) {
+ dev_err(&pdev->dev, "view-port register not specified\n");
+ return PTR_ERR(uphy->regs);
+ }
+
+ flag = of_property_read_bool(np, "drv-vbus");
+ if (flag)
+ uphy->flags |= ULPI_OTG_DRVVBUS | ULPI_OTG_DRVVBUS_EXT;
+
+ uphy->usb_phy = otg_ulpi_create(&ulpi_viewport_access_ops, uphy->flags);
+
+ uphy->usb_phy->dev = &pdev->dev;
+
+ uphy->usb_phy->io_priv = uphy->regs + uphy->vp_offset;
+
+ ret = usb_add_phy_dev(uphy->usb_phy);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int ulpi_phy_remove(struct platform_device *pdev)
+{
+ struct ulpi_phy *uphy = platform_get_drvdata(pdev);
+
+ usb_remove_phy(uphy->usb_phy);
+
+ return 0;
+}
+
+static const struct of_device_id ulpi_phy_table[] = {
+ { .compatible = "ulpi-phy" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, ulpi_phy_table);
+
+static struct platform_driver ulpi_phy_driver = {
+ .probe = ulpi_phy_probe,
+ .remove = ulpi_phy_remove,
+ .driver = {
+ .name = "ulpi-phy",
+ .of_match_table = ulpi_phy_table,
+ },
+};
+module_platform_driver(ulpi_phy_driver);
+
+MODULE_DESCRIPTION("ULPI PHY driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/storage/uas.c b/drivers/usb/storage/uas.c
index de4d33fbf6b8..6bf116da11ed 100644
--- a/drivers/usb/storage/uas.c
+++ b/drivers/usb/storage/uas.c
@@ -80,6 +80,8 @@ static int uas_try_complete(struct scsi_cmnd *cmnd, const char *caller);
static void uas_free_streams(struct uas_dev_info *devinfo);
static void uas_log_cmd_state(struct scsi_cmnd *cmnd, const char *prefix,
int status);
+static struct urb *uas_alloc_cmd_urb(struct uas_dev_info *devinfo, gfp_t gfp,
+ struct scsi_cmnd *cmnd);
/*
* This driver needs its own workqueue, as we need to control memory allocation.
@@ -296,18 +298,283 @@ static bool uas_evaluate_response_iu(struct response_iu *riu, struct scsi_cmnd *
return response_code == RC_TMF_SUCCEEDED;
}
+static void dummy_scsi_done(struct scsi_cmnd *cmnd)
+{
+ struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp;
+ struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata;
+
+ devinfo->cmnd[cmdinfo->uas_tag - 1] = NULL;
+ kfree(cmnd->request);
+ kfree(cmnd);
+}
+
+static void uas_workaround_cmplt(struct urb *urb)
+{
+ struct scsi_cmnd *cmnd;
+ struct uas_cmd_info *cmdinfo;
+
+ if ((urb->context != NULL) && (urb->status == 0)) {
+ cmnd = urb->context;
+ cmdinfo = (struct uas_cmd_info *)&cmnd->SCp;
+
+ if (cmdinfo->data_in_urb != urb)
+ cmnd->scsi_done(cmnd);
+ }
+
+ usb_free_urb(urb);
+}
+
+static struct urb *uas_workaround_cmnd(struct uas_dev_info *devinfo, gfp_t gfp,
+ struct scsi_cmnd *cmnd) {
+ struct scsi_device *sdev = cmnd->device;
+ struct urb *urb;
+ int err;
+
+ urb = uas_alloc_cmd_urb(devinfo, gfp, cmnd);
+ if (!urb) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate cmnd URB\n", __func__);
+ return NULL;
+ }
+
+ err = usb_submit_urb(urb, gfp);
+ if (err) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to submit cmd, err=%d\n", __func__, err);
+ goto free;
+ }
+ usb_anchor_urb(urb, &devinfo->cmd_urbs);
+ return urb;
+
+free:
+ usb_free_urb(urb);
+ return NULL;
+
+}
+
+static struct urb *uas_workaround_data(struct uas_dev_info *devinfo, gfp_t gfp,
+ struct scsi_cmnd *cmnd) {
+ struct scsi_device *sdev = cmnd->device;
+ struct usb_device *udev = devinfo->udev;
+ struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp;
+ struct urb *urb = usb_alloc_urb(0, gfp);
+ struct scsi_data_buffer *sdb = NULL;
+ void *temp_buf;
+ unsigned int pipe;
+ int err;
+
+ if (!urb) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate URB\n", __func__);
+ return NULL;
+ }
+
+ cmdinfo->data_in_urb = urb;
+ sdb = &cmnd->sdb;
+ pipe = devinfo->data_in_pipe;
+ temp_buf = kzalloc(sdb->length, GFP_ATOMIC);
+ if (!temp_buf) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate memory\n", __func__);
+ goto free;
+ }
+
+ usb_fill_bulk_urb(urb, udev, pipe, temp_buf, sdb->length,
+ uas_workaround_cmplt, cmnd);
+ if (devinfo->use_streams)
+ urb->stream_id = cmdinfo->uas_tag;
+ urb->transfer_flags |= URB_FREE_BUFFER;
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to submit Data In urb, err = %d\n",
+ __func__, err);
+ goto free;
+ }
+
+ usb_anchor_urb(urb, &devinfo->data_urbs);
+ return urb;
+
+free:
+ usb_free_urb(urb);
+ return NULL;
+}
+
+static struct urb *uas_workaround_sense(struct uas_dev_info *devinfo, gfp_t gfp,
+ struct scsi_cmnd *cmnd) {
+ struct scsi_device *sdev = cmnd->device;
+ struct usb_device *udev = devinfo->udev;
+ struct uas_cmd_info *cmdinfo = (void *)&cmnd->SCp;
+ struct urb *urb = usb_alloc_urb(0, gfp);
+ struct sense_iu *iu;
+ int err;
+
+ if (!urb) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate URB\n", __func__);
+ return NULL;
+ }
+
+ iu = kzalloc(sizeof(*iu), gfp);
+ if (!iu) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate memory for sense_iu\n",
+ __func__);
+ goto free;
+ }
+
+ usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu),
+ uas_workaround_cmplt, cmnd);
+ if (devinfo->use_streams)
+ urb->stream_id = cmdinfo->uas_tag;
+ urb->transfer_flags |= URB_FREE_BUFFER;
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to submit Sense urb, err = %d\n",
+ __func__, err);
+ goto free;
+ }
+ usb_anchor_urb(urb, &devinfo->sense_urbs);
+ return urb;
+
+free:
+ usb_free_urb(urb);
+ return NULL;
+}
+
+/*
+ * This function is called only if the DATA IN stream timer expired, which
+ * means xhci host controller has failed to process the TRB's present in the
+ * stream ring. As a part of recovery sequence, this function re-submits the
+ * previous stopped urb on which xhci failed to process data and along with
+ * that urb it prepares & submits sense, data and cmnd urb with scsi command
+ * set to standard inquiry request containing the next free stream id tag.
+ * Doing so will make the xhci start processing the previous stopped urb
+ * along with the urb that has standard inquiry scsi command.
+ */
+static int uas_workaround(struct urb *urb)
+{
+ struct scsi_cmnd *cmnd = urb->context;
+ struct scsi_device *sdev = cmnd->device;
+ struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata;
+ struct scsi_cmnd *temp_cmnd;
+ struct uas_cmd_info *temp_cmdinfo;
+ struct urb *sense_urb, *data_urb, *cmnd_urb;
+ struct request *temp_request;
+ unsigned int idx;
+ int err;
+ char inquiry[16] = { 0x12, 0x0, 0x0, 0x0, 0x10, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
+
+
+ /* Find a free uas-tag */
+ for (idx = 0; idx < devinfo->qdepth; idx++) {
+ if (!devinfo->cmnd[idx])
+ break;
+ }
+
+ if (idx == devinfo->qdepth) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to find free tag\n", __func__);
+ err = -EINVAL;
+ goto free;
+ }
+
+ /* Create a scsi_cmnd and send dummy inquiry data on the next
+ * available tag
+ */
+ temp_cmnd = kzalloc(sizeof(struct scsi_cmnd), GFP_ATOMIC);
+ if (!temp_cmnd) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate memory for scsi_cmnd\n",
+ __func__);
+ err = -ENOMEM;
+ goto free;
+ }
+
+ temp_request = kzalloc(sizeof(struct request), GFP_ATOMIC);
+ if (!temp_cmnd) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: Failed to allocate memory for request\n",
+ __func__);
+ err = -ENOMEM;
+ goto free;
+ }
+
+ temp_cmnd->device = cmnd->device;
+ temp_cmnd->cmnd = inquiry;
+ temp_cmnd->cmd_len = 16;
+ temp_cmnd->sdb.length = 0x10;
+ temp_cmnd->scsi_done = dummy_scsi_done;
+ temp_request->tag = idx;
+ temp_cmnd->request = temp_request;
+
+ temp_cmdinfo = (struct uas_cmd_info *)&temp_cmnd->SCp;
+ memset(temp_cmdinfo, 0, sizeof(struct uas_cmd_info));
+
+ temp_cmdinfo->uas_tag = idx + 1;
+ devinfo->cmnd[idx] = temp_cmnd;
+
+ /* Submit previously stopped URB first */
+ err = usb_submit_urb(urb, GFP_ATOMIC);
+ if (err) {
+ shost_printk(KERN_INFO, sdev->host,
+ "%s: submit err %d\n", __func__, err);
+ kfree(temp_cmnd);
+ kfree(temp_request);
+ goto free;
+ }
+ usb_anchor_urb(urb, &devinfo->data_urbs);
+
+ /* Allocate and submit SENSE urb for next available tag */
+ sense_urb = uas_workaround_sense(devinfo, GFP_ATOMIC, temp_cmnd);
+ if (!sense_urb) {
+ kfree(temp_request);
+ kfree(temp_cmnd);
+ goto free;
+ }
+
+ /* Allocate and submit DATA IN urb for next available tag */
+ data_urb = uas_workaround_data(devinfo, GFP_ATOMIC, temp_cmnd);
+ if (!data_urb) {
+ /* Kill previously allocated sense urb */
+ sense_urb->context = NULL;
+ usb_kill_urb(sense_urb);
+ usb_put_urb(sense_urb);
+ kfree(temp_request);
+ kfree(temp_cmnd);
+ goto free;
+ }
+
+ /* Allocate and submit CMND urb with dummy inquiry data */
+ cmnd_urb = uas_workaround_cmnd(devinfo, GFP_ATOMIC, temp_cmnd);
+ if (!cmnd_urb) {
+ /* Kill previously allocated data urb */
+ data_urb->context = NULL;
+ usb_kill_urb(data_urb);
+ usb_put_urb(data_urb);
+ kfree(temp_request);
+ kfree(temp_cmnd);
+ }
+
+free:
+ return err;
+}
+
static void uas_stat_cmplt(struct urb *urb)
{
struct iu *iu = urb->transfer_buffer;
- struct Scsi_Host *shost = urb->context;
- struct uas_dev_info *devinfo = (struct uas_dev_info *)shost->hostdata;
+ struct scsi_cmnd *cmnd = (struct scsi_cmnd *)urb->context;
+ struct uas_dev_info *devinfo =
+ (struct uas_dev_info *)cmnd->device->hostdata;
struct urb *data_in_urb = NULL;
struct urb *data_out_urb = NULL;
- struct scsi_cmnd *cmnd;
struct uas_cmd_info *cmdinfo;
unsigned long flags;
unsigned int idx;
int status = urb->status;
+ int err;
bool success;
spin_lock_irqsave(&devinfo->lock, flags);
@@ -316,6 +583,21 @@ static void uas_stat_cmplt(struct urb *urb)
goto out;
if (status) {
+ if (status == -EAGAIN) {
+ /* We get here only if the xhci stream timer expires,
+ * call uas_workaround() with this urb as argument.
+ */
+ err = uas_workaround(urb);
+ if (err != 0) {
+ dev_err(&urb->dev->dev,
+ "%s: uas_workaround() failed, err=%d\n",
+ __func__, err);
+ goto out;
+ }
+ spin_unlock_irqrestore(&devinfo->lock, flags);
+ return;
+ }
+
if (status != -ENOENT && status != -ECONNRESET && status != -ESHUTDOWN)
dev_err(&urb->dev->dev, "stat urb: status %d\n", status);
goto out;
@@ -398,10 +680,27 @@ static void uas_data_cmplt(struct urb *urb)
struct uas_dev_info *devinfo = (void *)cmnd->device->hostdata;
struct scsi_data_buffer *sdb = &cmnd->sdb;
unsigned long flags;
+ int err;
int status = urb->status;
spin_lock_irqsave(&devinfo->lock, flags);
+ if ((status == -EAGAIN) && (!devinfo->resetting) &&
+ (cmdinfo->data_in_urb == urb)) {
+ /* We get here only if the xhci stream timer expires,
+ * call uas_workaround() with this urb as argument.
+ */
+ err = uas_workaround(urb);
+ if (err != 0) {
+ dev_err(&urb->dev->dev,
+ "%s: uas_workaround() failed, err=%d\n",
+ __func__, err);
+ goto out;
+ }
+ spin_unlock_irqrestore(&devinfo->lock, flags);
+ return;
+ }
+
if (cmdinfo->data_in_urb == urb) {
cmdinfo->state &= ~DATA_IN_URB_INFLIGHT;
cmdinfo->data_in_urb = NULL;
@@ -480,7 +779,7 @@ static struct urb *uas_alloc_sense_urb(struct uas_dev_info *devinfo, gfp_t gfp,
goto free;
usb_fill_bulk_urb(urb, udev, devinfo->status_pipe, iu, sizeof(*iu),
- uas_stat_cmplt, cmnd->device->host);
+ uas_stat_cmplt, cmnd);
if (devinfo->use_streams)
urb->stream_id = cmdinfo->uas_tag;
urb->transfer_flags |= URB_FREE_BUFFER;
diff --git a/drivers/usb/storage/unusual_uas.h b/drivers/usb/storage/unusual_uas.h
index 37157ed9a881..b080a59113c0 100644
--- a/drivers/usb/storage/unusual_uas.h
+++ b/drivers/usb/storage/unusual_uas.h
@@ -45,6 +45,12 @@ UNUSUAL_DEV(0x0984, 0x0301, 0x0128, 0x0128,
USB_SC_DEVICE, USB_PR_DEVICE, NULL,
US_FL_IGNORE_UAS),
+UNUSUAL_DEV(0x0525, 0xa4a5, 0x0000, 0x9999,
+ "Netchip",
+ "Target Product",
+ USB_SC_DEVICE, USB_PR_DEVICE, NULL,
+ US_FL_NO_ATA_1X),
+
/* Reported-by: David Webb <djw@noc.ac.uk> */
UNUSUAL_DEV(0x0bc2, 0x331a, 0x0000, 0x9999,
"Seagate",