diff options
Diffstat (limited to 'drivers/usb')
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, ¶ms); } -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(¶ms, 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, ¶ms); + 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(¶ms, 0x00, sizeof(params)); + + ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETSTALL, + ¶ms); + 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(¶ms, 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, ¶ms); + + 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(¶ms, 0x00, sizeof(params)); + + ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_SETSTALL, + ¶ms); + 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(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dep, DWC3_DEPCMD_GETEPSTATE, + ¶ms); + 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", |