diff options
Diffstat (limited to 'drivers/usb/dwc3')
| -rw-r--r-- | drivers/usb/dwc3/Kconfig | 107 | ||||
| -rw-r--r-- | drivers/usb/dwc3/Makefile | 42 | ||||
| -rw-r--r-- | drivers/usb/dwc3/core.c | 1505 | ||||
| -rw-r--r-- | drivers/usb/dwc3/core.h | 1307 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dbm.c | 643 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dbm.h | 75 | ||||
| -rw-r--r-- | drivers/usb/dwc3/debug.c | 32 | ||||
| -rw-r--r-- | drivers/usb/dwc3/debug.h | 248 | ||||
| -rw-r--r-- | drivers/usb/dwc3/debugfs.c | 1359 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-exynos.c | 306 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-keystone.c | 201 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-msm.c | 4356 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-omap.c | 621 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-pci.c | 237 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-qcom.c | 130 | ||||
| -rw-r--r-- | drivers/usb/dwc3/dwc3-st.c | 371 | ||||
| -rw-r--r-- | drivers/usb/dwc3/ep0.c | 1246 | ||||
| -rw-r--r-- | drivers/usb/dwc3/gadget.c | 3763 | ||||
| -rw-r--r-- | drivers/usb/dwc3/gadget.h | 136 | ||||
| -rw-r--r-- | drivers/usb/dwc3/host.c | 88 | ||||
| -rw-r--r-- | drivers/usb/dwc3/io.h | 70 | ||||
| -rw-r--r-- | drivers/usb/dwc3/platform_data.h | 53 | ||||
| -rw-r--r-- | drivers/usb/dwc3/trace.c | 19 | ||||
| -rw-r--r-- | drivers/usb/dwc3/trace.h | 257 | ||||
| -rw-r--r-- | drivers/usb/dwc3/ulpi.c | 91 |
25 files changed, 17263 insertions, 0 deletions
diff --git a/drivers/usb/dwc3/Kconfig b/drivers/usb/dwc3/Kconfig new file mode 100644 index 000000000000..5a42c4590402 --- /dev/null +++ b/drivers/usb/dwc3/Kconfig @@ -0,0 +1,107 @@ +config USB_DWC3 + tristate "DesignWare USB3 DRD Core Support" + depends on (USB || USB_GADGET) && HAS_DMA + select USB_XHCI_PLATFORM if USB_SUPPORT && USB_XHCI_HCD + help + Say Y or M here if your system has a Dual Role SuperSpeed + USB controller based on the DesignWare USB3 IP Core. + + If you choose to build this driver is a dynamically linked + module, the module will be called dwc3.ko. + +if USB_DWC3 + +config USB_DWC3_ULPI + bool "Register ULPI PHY Interface" + depends on USB_ULPI_BUS=y || USB_ULPI_BUS=USB_DWC3 + help + Select this if you have ULPI type PHY attached to your DWC3 + controller. + +choice + bool "DWC3 Mode Selection" + default USB_DWC3_DUAL_ROLE if (USB && USB_GADGET) + default USB_DWC3_HOST if (USB && !USB_GADGET) + default USB_DWC3_GADGET if (!USB && USB_GADGET) + +config USB_DWC3_HOST + bool "Host only mode" + depends on USB=y || USB=USB_DWC3 + help + Select this when you want to use DWC3 in host mode only, + thereby the gadget feature will be regressed. + +config USB_DWC3_GADGET + bool "Gadget only mode" + depends on USB_GADGET=y || USB_GADGET=USB_DWC3 + help + Select this when you want to use DWC3 in gadget mode only, + thereby the host feature will be regressed. + +config USB_DWC3_DUAL_ROLE + bool "Dual Role mode" + depends on ((USB=y || USB=USB_DWC3) && (USB_GADGET=y || USB_GADGET=USB_DWC3)) + help + This is the default mode of working of DWC3 controller where + both host and gadget features are enabled. + +endchoice + +comment "Platform Glue Driver Support" + +config USB_DWC3_OMAP + tristate "Texas Instruments OMAP5 and similar Platforms" + depends on EXTCON && (ARCH_OMAP2PLUS || COMPILE_TEST) + depends on OF + default USB_DWC3 + help + Some platforms from Texas Instruments like OMAP5, DRA7xxx and + AM437x use this IP for USB2/3 functionality. + + Say 'Y' or 'M' here if you have one such device + +config USB_DWC3_EXYNOS + tristate "Samsung Exynos Platform" + depends on ARCH_EXYNOS && OF || COMPILE_TEST + default USB_DWC3 + help + Recent Exynos5 SoCs ship with one DesignWare Core USB3 IP inside, + say 'Y' or 'M' if you have one such device. + +config USB_DWC3_PCI + tristate "PCIe-based Platforms" + depends on PCI + default USB_DWC3 + help + If you're using the DesignWare Core IP with a PCIe, please say + 'Y' or 'M' here. + + One such PCIe-based platform is Synopsys' PCIe HAPS model of + this IP. + +config USB_DWC3_KEYSTONE + tristate "Texas Instruments Keystone2 Platforms" + depends on ARCH_KEYSTONE || COMPILE_TEST + default USB_DWC3 + help + Support of USB2/3 functionality in TI Keystone2 platforms. + Say 'Y' or 'M' here if you have one such device + +config USB_DWC3_ST + tristate "STMicroelectronics Platforms" + depends on ARCH_STI && OF + default USB_DWC3 + help + STMicroelectronics SoCs with one DesignWare Core USB3 IP + inside (i.e. STiH407). + Say 'Y' or 'M' if you have one such device. + +config USB_DWC3_QCOM + tristate "Qualcomm Platforms" + depends on ARCH_QCOM || COMPILE_TEST + default USB_DWC3 + help + Recent Qualcomm SoCs ship with one DesignWare Core USB3 IP inside, + say 'Y' or 'M' if you have one such device. + +endif diff --git a/drivers/usb/dwc3/Makefile b/drivers/usb/dwc3/Makefile new file mode 100644 index 000000000000..389936296141 --- /dev/null +++ b/drivers/usb/dwc3/Makefile @@ -0,0 +1,42 @@ +# define_trace.h needs to know how to find our header +CFLAGS_trace.o := -I$(src) +CFLAGS_dwc3-msm.o := -Idrivers/usb/host -Idrivers/base/power + +obj-$(CONFIG_USB_DWC3) += dwc3.o + +dwc3-y := core.o debug.o trace.o + +ifneq ($(filter y,$(CONFIG_USB_DWC3_HOST) $(CONFIG_USB_DWC3_DUAL_ROLE)),) + dwc3-y += host.o +endif + +ifneq ($(filter y,$(CONFIG_USB_DWC3_GADGET) $(CONFIG_USB_DWC3_DUAL_ROLE)),) + dwc3-y += gadget.o ep0.o +endif + +ifneq ($(CONFIG_USB_DWC3_ULPI),) + dwc3-y += ulpi.o +endif + +ifneq ($(CONFIG_DEBUG_FS),) + dwc3-y += debugfs.o +endif + +## +# Platform-specific glue layers go here +# +# NOTICE: Make sure your glue layer doesn't depend on anything +# which is arch-specific and that it compiles on all situations. +# +# We want to keep this requirement in order to be able to compile +# the entire driver (with all its glue layers) on several architectures +# and make sure it compiles fine. This will also help with allmodconfig +# and allyesconfig builds. +## + +obj-$(CONFIG_USB_DWC3_OMAP) += dwc3-omap.o +obj-$(CONFIG_USB_DWC3_EXYNOS) += dwc3-exynos.o +obj-$(CONFIG_USB_DWC3_PCI) += dwc3-pci.o +obj-$(CONFIG_USB_DWC3_KEYSTONE) += dwc3-keystone.o +obj-$(CONFIG_USB_DWC3_QCOM) += dwc3-qcom.o dwc3-msm.o dbm.o +obj-$(CONFIG_USB_DWC3_ST) += dwc3-st.o diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c new file mode 100644 index 000000000000..3191825710af --- /dev/null +++ b/drivers/usb/dwc3/core.c @@ -0,0 +1,1505 @@ +/** + * core.c - DesignWare USB3 DRD Controller Core file + * + * 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. + * + * 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/version.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/of.h> +#include <linux/acpi.h> +#include <linux/pinctrl/consumer.h> +#include <linux/irq.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/of.h> +#include <linux/usb/otg.h> + +#include "platform_data.h" +#include "core.h" +#include "gadget.h" +#include "io.h" + +#include "debug.h" + +/* -------------------------------------------------------------------------- */ + +void dwc3_usb3_phy_suspend(struct dwc3 *dwc, int suspend) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + + if (suspend) + reg |= DWC3_GUSB3PIPECTL_SUSPHY; + else + reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; + + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); +} + +void dwc3_set_mode(struct dwc3 *dwc, u32 mode) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~(DWC3_GCTL_PRTCAPDIR(DWC3_GCTL_PRTCAP_OTG)); + reg |= DWC3_GCTL_PRTCAPDIR(mode); + /* + * Set this bit so that device attempts three more times at SS, even + * if it failed previously to operate in SS mode. + */ + reg |= DWC3_GCTL_U2RSTECN; + reg &= ~(DWC3_GCTL_SOFITPSYNC); + reg &= ~(DWC3_GCTL_PWRDNSCALEMASK); + reg |= DWC3_GCTL_PWRDNSCALE(2); + reg |= DWC3_GCTL_U2EXIT_LFPS; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + if (mode == DWC3_GCTL_PRTCAP_OTG || mode == DWC3_GCTL_PRTCAP_HOST) { + /* + * Allow ITP generated off of ref clk based counter instead + * of UTMI/ULPI clk based counter, when superspeed only is + * active so that UTMI/ULPI PHY can be suspened. + * + * Starting with revision 2.50A, GFLADJ_REFCLK_LPM_SEL is used + * instead. + */ + if (dwc->revision < DWC3_REVISION_250A) { + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg |= DWC3_GCTL_SOFITPSYNC; + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + } else { + reg = dwc3_readl(dwc->regs, DWC3_GFLADJ); + reg |= DWC3_GFLADJ_REFCLK_LPM_SEL; + dwc3_writel(dwc->regs, DWC3_GFLADJ, reg); + } + } +} + +/** + * Peforms initialization of HS and SS PHYs. + * If used as a part of POR or init sequence it is recommended + * that we should perform hard reset of the PHYs prior to invoking + * this function. + * @dwc: pointer to our context structure +*/ +static int dwc3_init_usb_phys(struct dwc3 *dwc) +{ + int ret; + + /* Bring up PHYs */ + ret = usb_phy_init(dwc->usb2_phy); + if (ret) { + pr_err("%s: usb_phy_init(dwc->usb2_phy) returned %d\n", + __func__, ret); + return ret; + } + + if (dwc->maximum_speed == USB_SPEED_HIGH) + goto generic_phy_init; + + ret = usb_phy_init(dwc->usb3_phy); + if (ret == -EBUSY) { + /* + * Setting Max speed as high when USB3 PHY initialiation + * is failing and USB superspeed can't be supported. + */ + dwc->maximum_speed = USB_SPEED_HIGH; + } else if (ret) { + pr_err("%s: usb_phy_init(dwc->usb3_phy) returned %d\n", + __func__, ret); + return ret; + } + +generic_phy_init: + ret = phy_init(dwc->usb2_generic_phy); + if (ret < 0) + return ret; + + ret = phy_init(dwc->usb3_generic_phy); + if (ret < 0) { + phy_exit(dwc->usb2_generic_phy); + return ret; + } + + return 0; +} + +/** + * dwc3_core_reset - Issues core soft reset and PHY reset + * @dwc: pointer to our context structure + */ +static int dwc3_core_reset(struct dwc3 *dwc) +{ + int ret; + u32 reg; + + /* Reset PHYs */ + usb_phy_reset(dwc->usb2_phy); + + if (dwc->maximum_speed == USB_SPEED_SUPER) + usb_phy_reset(dwc->usb3_phy); + + /* Initialize PHYs */ + ret = dwc3_init_usb_phys(dwc); + if (ret) { + pr_err("%s: dwc3_init_phys returned %d\n", + __func__, ret); + return ret; + } + + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + reg &= ~DWC3_GUSB3PIPECTL_DELAYP1TRANS; + + /* core exits U1/U2/U3 only in PHY power state P1/P2/P3 respectively */ + if (dwc->revision <= DWC3_REVISION_310A) + reg |= DWC3_GUSB3PIPECTL_UX_EXIT_IN_PX; + + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); + + dwc3_notify_event(dwc, DWC3_CONTROLLER_RESET_EVENT, 0); + + dwc3_notify_event(dwc, DWC3_CONTROLLER_POST_RESET_EVENT, 0); + + return 0; +} + +/** + * dwc3_soft_reset - Issue soft reset + * @dwc: Pointer to our controller context structure + */ +static int dwc3_soft_reset(struct dwc3 *dwc) +{ + unsigned long timeout; + u32 reg; + + timeout = jiffies + msecs_to_jiffies(500); + dwc3_writel(dwc->regs, DWC3_DCTL, DWC3_DCTL_CSFTRST); + do { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (!(reg & DWC3_DCTL_CSFTRST)) + break; + + if (time_after(jiffies, timeout)) { + dev_err(dwc->dev, "Reset Timed Out\n"); + return -ETIMEDOUT; + } + + cpu_relax(); + } while (true); + + return 0; +} + +/* + * dwc3_frame_length_adjustment - Adjusts frame length if required + * @dwc3: Pointer to our controller context structure + * @fladj: Value of GFLADJ_30MHZ to adjust frame length + */ +static void dwc3_frame_length_adjustment(struct dwc3 *dwc, u32 fladj) +{ + u32 reg; + u32 dft; + + if (dwc->revision < DWC3_REVISION_250A) + return; + + if (fladj == 0) + return; + + reg = dwc3_readl(dwc->regs, DWC3_GFLADJ); + dft = reg & DWC3_GFLADJ_30MHZ_MASK; + if (!dev_WARN_ONCE(dwc->dev, dft == fladj, + "request value same as default, ignoring\n")) { + reg &= ~DWC3_GFLADJ_30MHZ_MASK; + reg |= DWC3_GFLADJ_30MHZ_SDBND_SEL | fladj; + dwc3_writel(dwc->regs, DWC3_GFLADJ, reg); + } +} + +/** + * dwc3_free_one_event_buffer - Frees one event buffer + * @dwc: Pointer to our controller context structure + * @evt: Pointer to event buffer to be freed + */ +static void dwc3_free_one_event_buffer(struct dwc3 *dwc, + struct dwc3_event_buffer *evt) +{ + dma_free_coherent(dwc->dev, evt->length, evt->buf, evt->dma); +} + +/** + * dwc3_alloc_one_event_buffer - Allocates one event buffer structure + * @dwc: Pointer to our controller context structure + * @length: size of the event buffer + * + * Returns a pointer to the allocated event buffer structure on success + * otherwise ERR_PTR(errno). + */ +static struct dwc3_event_buffer *dwc3_alloc_one_event_buffer(struct dwc3 *dwc, + unsigned length, enum event_buf_type type) +{ + struct dwc3_event_buffer *evt; + + evt = devm_kzalloc(dwc->dev, sizeof(*evt), GFP_KERNEL); + if (!evt) + return ERR_PTR(-ENOMEM); + + evt->dwc = dwc; + evt->length = length; + evt->type = type; + evt->buf = dma_alloc_coherent(dwc->dev, length, + &evt->dma, GFP_KERNEL); + if (!evt->buf) + return ERR_PTR(-ENOMEM); + + return evt; +} + +/** + * dwc3_free_event_buffers - frees all allocated event buffers + * @dwc: Pointer to our controller context structure + */ +static void dwc3_free_event_buffers(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + int i; + + for (i = 0; i < dwc->num_event_buffers; i++) { + evt = dwc->ev_buffs[i]; + if (evt) + dwc3_free_one_event_buffer(dwc, evt); + } +} + +/** + * dwc3_alloc_event_buffers - Allocates @num event buffers of size @length + * @dwc: pointer to our controller context structure + * @length: size of event buffer + * + * Returns 0 on success otherwise negative errno. In the error case, dwc + * may contain some buffers allocated but not all which were requested. + */ +static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) +{ + int i; + int j = 0; + + dwc->num_event_buffers = dwc->num_normal_event_buffers + + dwc->num_gsi_event_buffers; + + dwc->ev_buffs = devm_kzalloc(dwc->dev, + sizeof(*dwc->ev_buffs) * dwc->num_event_buffers, + GFP_KERNEL); + if (!dwc->ev_buffs) + return -ENOMEM; + + for (i = 0; i < dwc->num_normal_event_buffers; i++) { + struct dwc3_event_buffer *evt; + + evt = dwc3_alloc_one_event_buffer(dwc, length, + EVT_BUF_TYPE_NORMAL); + if (IS_ERR(evt)) { + dev_err(dwc->dev, "can't allocate event buffer\n"); + return PTR_ERR(evt); + } + dwc->ev_buffs[j++] = evt; + } + + for (i = 0; i < dwc->num_gsi_event_buffers; i++) { + struct dwc3_event_buffer *evt; + + evt = dwc3_alloc_one_event_buffer(dwc, length, + EVT_BUF_TYPE_GSI); + if (IS_ERR(evt)) { + dev_err(dwc->dev, "can't allocate event buffer\n"); + return PTR_ERR(evt); + } + dwc->ev_buffs[j++] = evt; + } + + return 0; +} + +/** + * dwc3_event_buffers_setup - setup our allocated event buffers + * @dwc: pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +int dwc3_event_buffers_setup(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + int n; + + for (n = 0; n < dwc->num_event_buffers; n++) { + evt = dwc->ev_buffs[n]; + dev_dbg(dwc->dev, "Event buf %pK dma %08llx length %d\n", + evt->buf, (unsigned long long) evt->dma, + evt->length); + + memset(evt->buf, 0, evt->length); + + evt->lpos = 0; + + dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n), + lower_32_bits(evt->dma)); + + if (evt->type == EVT_BUF_TYPE_NORMAL) { + dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), + upper_32_bits(evt->dma)); + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), + DWC3_GEVNTSIZ_SIZE(evt->length)); + } else { + dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), + DWC3_GEVNTADRHI_EVNTADRHI_GSI_EN( + DWC3_GEVENT_TYPE_GSI) | + DWC3_GEVNTADRHI_EVNTADRHI_GSI_IDX(n)); + + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), + DWC3_GEVNTCOUNT_EVNTINTRPTMASK | + ((evt->length) & 0xffff)); + } + + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0); + } + + return 0; +} + +static void dwc3_event_buffers_cleanup(struct dwc3 *dwc) +{ + struct dwc3_event_buffer *evt; + int n; + + for (n = 0; n < dwc->num_event_buffers; n++) { + evt = dwc->ev_buffs[n]; + + evt->lpos = 0; + + dwc3_writel(dwc->regs, DWC3_GEVNTADRLO(n), 0); + dwc3_writel(dwc->regs, DWC3_GEVNTADRHI(n), 0); + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(n), DWC3_GEVNTSIZ_INTMASK + | DWC3_GEVNTSIZ_SIZE(0)); + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(n), 0); + } +} + +static int dwc3_alloc_scratch_buffers(struct dwc3 *dwc) +{ + if (!dwc->has_hibernation) + return 0; + + if (!dwc->nr_scratch) + return 0; + + dwc->scratchbuf = kmalloc_array(dwc->nr_scratch, + DWC3_SCRATCHBUF_SIZE, GFP_KERNEL); + if (!dwc->scratchbuf) + return -ENOMEM; + + return 0; +} + +static int dwc3_setup_scratch_buffers(struct dwc3 *dwc) +{ + dma_addr_t scratch_addr; + u32 param; + int ret; + + if (!dwc->has_hibernation) + return 0; + + if (!dwc->nr_scratch) + return 0; + + /* should never fall here */ + if (!WARN_ON(dwc->scratchbuf)) + return 0; + + scratch_addr = dma_map_single(dwc->dev, dwc->scratchbuf, + dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE, + DMA_BIDIRECTIONAL); + if (dma_mapping_error(dwc->dev, scratch_addr)) { + dev_err(dwc->dev, "failed to map scratch buffer\n"); + ret = -EFAULT; + goto err0; + } + + dwc->scratch_addr = scratch_addr; + + param = lower_32_bits(scratch_addr); + + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO, param); + if (ret < 0) + goto err1; + + param = upper_32_bits(scratch_addr); + + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI, param); + if (ret < 0) + goto err1; + + return 0; + +err1: + dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch * + DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL); + +err0: + return ret; +} + +static void dwc3_free_scratch_buffers(struct dwc3 *dwc) +{ + if (!dwc->has_hibernation) + return; + + if (!dwc->nr_scratch) + return; + + /* should never fall here */ + if (!WARN_ON(dwc->scratchbuf)) + return; + + dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch * + DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL); + kfree(dwc->scratchbuf); +} + +static void dwc3_core_num_eps(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + dwc->num_in_eps = DWC3_NUM_IN_EPS(parms); + dwc->num_out_eps = DWC3_NUM_EPS(parms) - dwc->num_in_eps; + + dwc3_trace(trace_dwc3_core, "found %d IN and %d OUT endpoints", + dwc->num_in_eps, dwc->num_out_eps); +} + +static void dwc3_cache_hwparams(struct dwc3 *dwc) +{ + struct dwc3_hwparams *parms = &dwc->hwparams; + + parms->hwparams0 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS0); + parms->hwparams1 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS1); + parms->hwparams2 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS2); + parms->hwparams3 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS3); + parms->hwparams4 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS4); + parms->hwparams5 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS5); + parms->hwparams6 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS6); + parms->hwparams7 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS7); + parms->hwparams8 = dwc3_readl(dwc->regs, DWC3_GHWPARAMS8); +} + +/** + * dwc3_phy_setup - Configure USB PHY Interface of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success. The USB PHY interfaces are configured but not + * initialized. The PHY interfaces and the PHYs get initialized together with + * the core in dwc3_core_init. + */ +static int dwc3_phy_setup(struct dwc3 *dwc) +{ + u32 reg; + int ret; + + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + + /* + * Above 1.94a, it is recommended to set DWC3_GUSB3PIPECTL_SUSPHY + * to '0' during coreConsultant configuration. So default value + * will be '0' when the core is reset. Application needs to set it + * to '1' after the core initialization is completed. + */ + if (dwc->revision > DWC3_REVISION_194A) + reg |= DWC3_GUSB3PIPECTL_SUSPHY; + + if (dwc->u2ss_inp3_quirk) + reg |= DWC3_GUSB3PIPECTL_U2SSINP3OK; + + if (dwc->req_p1p2p3_quirk) + reg |= DWC3_GUSB3PIPECTL_REQP1P2P3; + + if (dwc->del_p1p2p3_quirk) + reg |= DWC3_GUSB3PIPECTL_DEP1P2P3_EN; + + if (dwc->del_phy_power_chg_quirk) + reg |= DWC3_GUSB3PIPECTL_DEPOCHANGE; + + if (dwc->lfps_filter_quirk) + reg |= DWC3_GUSB3PIPECTL_LFPSFILT; + + if (dwc->rx_detect_poll_quirk) + reg |= DWC3_GUSB3PIPECTL_RX_DETOPOLL; + + if (dwc->tx_de_emphasis_quirk) + reg |= DWC3_GUSB3PIPECTL_TX_DEEPH(dwc->tx_de_emphasis); + + if (dwc->dis_u3_susphy_quirk) + reg &= ~DWC3_GUSB3PIPECTL_SUSPHY; + + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); + + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYCFG(0)); + + /* Select the HS PHY interface */ + switch (DWC3_GHWPARAMS3_HSPHY_IFC(dwc->hwparams.hwparams3)) { + case DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI: + if (dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "utmi", 4)) { + reg &= ~DWC3_GUSB2PHYCFG_ULPI_UTMI; + break; + } else if (dwc->hsphy_interface && + !strncmp(dwc->hsphy_interface, "ulpi", 4)) { + reg |= DWC3_GUSB2PHYCFG_ULPI_UTMI; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + } else { + /* Relying on default value. */ + if (!(reg & DWC3_GUSB2PHYCFG_ULPI_UTMI)) + break; + } + /* FALLTHROUGH */ + case DWC3_GHWPARAMS3_HSPHY_IFC_ULPI: + /* Making sure the interface and PHY are operational */ + ret = dwc3_soft_reset(dwc); + if (ret) + return ret; + + udelay(1); + + ret = dwc3_ulpi_init(dwc); + if (ret) + return ret; + /* FALLTHROUGH */ + default: + break; + } + + /* + * Above 1.94a, it is recommended to set DWC3_GUSB2PHYCFG_SUSPHY to + * '0' during coreConsultant configuration. So default value will + * be '0' when the core is reset. Application needs to set it to + * '1' after the core initialization is completed. + */ + if (dwc->revision > DWC3_REVISION_194A) + reg |= DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_u2_susphy_quirk) + reg &= ~DWC3_GUSB2PHYCFG_SUSPHY; + + if (dwc->dis_enblslpm_quirk) + reg &= ~DWC3_GUSB2PHYCFG_ENBLSLPM; + + dwc3_writel(dwc->regs, DWC3_GUSB2PHYCFG(0), reg); + + return 0; +} + +/** + * dwc3_core_init - Low-level initialization of DWC3 Core + * @dwc: Pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +int dwc3_core_init(struct dwc3 *dwc) +{ + u32 hwparams4 = dwc->hwparams.hwparams4; + u32 reg; + int ret; + + reg = dwc3_readl(dwc->regs, DWC3_GSNPSID); + /* This should read as U3 followed by revision number */ + if ((reg & DWC3_GSNPSID_MASK) == 0x55330000) { + /* Detected DWC_usb3 IP */ + dwc->revision = reg; + } else if ((reg & DWC3_GSNPSID_MASK) == 0x33310000) { + /* Detected DWC_usb31 IP */ + dwc->revision = dwc3_readl(dwc->regs, DWC3_VER_NUMBER); + dwc->revision |= DWC3_REVISION_IS_DWC31; + } else { + dev_err(dwc->dev, "this is not a DesignWare USB3 DRD Core\n"); + ret = -ENODEV; + goto err0; + } + + /* + * Write Linux Version Code to our GUID register so it's easy to figure + * out which kernel version a bug was found. + */ + dwc3_writel(dwc->regs, DWC3_GUID, LINUX_VERSION_CODE); + + /* Handle USB2.0-only core configuration */ + if (DWC3_GHWPARAMS3_SSPHY_IFC(dwc->hwparams.hwparams3) == + DWC3_GHWPARAMS3_SSPHY_IFC_DIS) { + if (dwc->max_hw_supp_speed == USB_SPEED_SUPER) { + dwc->max_hw_supp_speed = USB_SPEED_HIGH; + dwc->maximum_speed = dwc->max_hw_supp_speed; + } + } + + /* + * Workaround for STAR 9000961433 which affects only version + * 3.00a of the DWC_usb3 core. This prevents the controller + * interrupt from being masked while handling events. IMOD + * allows us to work around this issue. Enable it for the + * affected version. + */ + if (!dwc->imod_interval && (dwc->revision == DWC3_REVISION_300A)) + dwc->imod_interval = 1; + + ret = dwc3_core_reset(dwc); + if (ret) + goto err0; + + /* issue device SoftReset too */ + ret = dwc3_soft_reset(dwc); + if (ret) + goto err0; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg &= ~DWC3_GCTL_SCALEDOWN_MASK; + + switch (DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1)) { + case DWC3_GHWPARAMS1_EN_PWROPT_CLK: + /** + * WORKAROUND: DWC3 revisions between 2.10a and 2.50a have an + * issue which would cause xHCI compliance tests to fail. + * + * Because of that we cannot enable clock gating on such + * configurations. + * + * Refers to: + * + * STAR#9000588375: Clock Gating, SOF Issues when ref_clk-Based + * SOF/ITP Mode Used + */ + if ((dwc->dr_mode == USB_DR_MODE_HOST || + dwc->dr_mode == USB_DR_MODE_OTG) && + (dwc->revision >= DWC3_REVISION_210A && + dwc->revision <= DWC3_REVISION_250A)) + reg |= DWC3_GCTL_DSBLCLKGTNG | DWC3_GCTL_SOFITPSYNC; + else + reg &= ~DWC3_GCTL_DSBLCLKGTNG; + break; + case DWC3_GHWPARAMS1_EN_PWROPT_HIB: + /* enable hibernation here */ + dwc->nr_scratch = DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(hwparams4); + + /* + * REVISIT Enabling this bit so that host-mode hibernation + * will work. Device-mode hibernation is not yet implemented. + */ + reg |= DWC3_GCTL_GBLHIBERNATIONEN; + break; + default: + dev_dbg(dwc->dev, "No power optimization available\n"); + } + + /* check if current dwc3 is on simulation board */ + if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { + dev_dbg(dwc->dev, "it is on FPGA board\n"); + dwc->is_fpga = true; + } + + WARN_ONCE(dwc->disable_scramble_quirk && !dwc->is_fpga, + "disable_scramble cannot be used on non-FPGA builds\n"); + + if (dwc->disable_scramble_quirk && dwc->is_fpga) + reg |= DWC3_GCTL_DISSCRAMBLE; + else + reg &= ~DWC3_GCTL_DISSCRAMBLE; + + if (dwc->u2exit_lfps_quirk) + reg |= DWC3_GCTL_U2EXIT_LFPS; + + /* + * WORKAROUND: DWC3 revisions <1.90a have a bug + * where the device can fail to connect at SuperSpeed + * and falls back to high-speed mode which causes + * the device to enter a Connect/Disconnect loop + */ + if (dwc->revision < DWC3_REVISION_190A) + reg |= DWC3_GCTL_U2RSTECN; + + dwc3_core_num_eps(dwc); + + /* + * Disable clock gating to work around a known HW bug that causes the + * internal RAM clock to get stuck when entering low power modes. + */ + if (dwc->disable_clk_gating) { + dev_dbg(dwc->dev, "Disabling controller clock gating.\n"); + reg |= DWC3_GCTL_DSBLCLKGTNG; + } + + dwc3_writel(dwc->regs, DWC3_GCTL, reg); + + ret = dwc3_alloc_scratch_buffers(dwc); + if (ret) + goto err1; + + ret = dwc3_setup_scratch_buffers(dwc); + if (ret) + goto err2; + + /* + * clear Elastic buffer mode in GUSBPIPE_CTRL(0) register, otherwise + * it results in high link errors and could cause SS mode transfer + * failure. + */ + if (!dwc->nominal_elastic_buffer) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB3PIPECTL(0)); + reg &= ~DWC3_GUSB3PIPECTL_ELASTIC_BUF_MODE; + dwc3_writel(dwc->regs, DWC3_GUSB3PIPECTL(0), reg); + } + + return 0; + +err2: + dwc3_free_scratch_buffers(dwc); + +err1: + usb_phy_shutdown(dwc->usb2_phy); + usb_phy_shutdown(dwc->usb3_phy); + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); + +err0: + return ret; +} + +static void dwc3_core_exit(struct dwc3 *dwc) +{ + dwc3_free_scratch_buffers(dwc); + usb_phy_shutdown(dwc->usb2_phy); + usb_phy_shutdown(dwc->usb3_phy); + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); +} + +static int dwc3_core_get_phy(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + struct device_node *node = dev->of_node; + int ret; + + if (node) { + dwc->usb2_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 0); + dwc->usb3_phy = devm_usb_get_phy_by_phandle(dev, "usb-phy", 1); + } else { + dwc->usb2_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + dwc->usb3_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB3); + } + + if (IS_ERR(dwc->usb2_phy)) { + ret = PTR_ERR(dwc->usb2_phy); + if (ret == -ENXIO || ret == -ENODEV) { + dwc->usb2_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb2 phy configured\n"); + return ret; + } + } + + if (IS_ERR(dwc->usb3_phy)) { + ret = PTR_ERR(dwc->usb3_phy); + if (ret == -ENXIO || ret == -ENODEV) { + dwc->usb3_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb3 phy configured\n"); + return ret; + } + } + + dwc->usb2_generic_phy = devm_phy_get(dev, "usb2-phy"); + if (IS_ERR(dwc->usb2_generic_phy)) { + ret = PTR_ERR(dwc->usb2_generic_phy); + if (ret == -ENOSYS || ret == -ENODEV) { + dwc->usb2_generic_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb2 phy configured\n"); + return ret; + } + } + + dwc->usb3_generic_phy = devm_phy_get(dev, "usb3-phy"); + if (IS_ERR(dwc->usb3_generic_phy)) { + ret = PTR_ERR(dwc->usb3_generic_phy); + if (ret == -ENOSYS || ret == -ENODEV) { + dwc->usb3_generic_phy = NULL; + } else if (ret == -EPROBE_DEFER) { + return ret; + } else { + dev_err(dev, "no usb3 phy configured\n"); + return ret; + } + } + + return 0; +} + +static int dwc3_core_init_mode(struct dwc3 *dwc) +{ + struct device *dev = dwc->dev; + + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + break; + case USB_DR_MODE_HOST: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + break; + case USB_DR_MODE_OTG: + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_OTG); + break; + default: + dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode); + return -EINVAL; + } + + return 0; +} + +static void dwc3_core_exit_mode(struct dwc3 *dwc) +{ + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + dwc3_gadget_exit(dwc); + break; + case USB_DR_MODE_HOST: + dwc3_host_exit(dwc); + break; + case USB_DR_MODE_OTG: + dwc3_host_exit(dwc); + dwc3_gadget_exit(dwc); + break; + default: + /* do nothing */ + break; + } +} + +/* XHCI reset, resets other CORE registers as well, re-init those */ +void dwc3_post_host_reset_core_init(struct dwc3 *dwc) +{ + dwc3_core_init(dwc); + dwc3_gadget_restart(dwc); +} + +static void (*notify_event)(struct dwc3 *, unsigned, unsigned); +void dwc3_set_notifier(void (*notify)(struct dwc3 *, unsigned, unsigned)) +{ + notify_event = notify; +} +EXPORT_SYMBOL(dwc3_set_notifier); + +int dwc3_notify_event(struct dwc3 *dwc, unsigned event, unsigned value) +{ + int ret = 0; + + if (dwc->notify_event) + dwc->notify_event(dwc, event, value); + else + ret = -ENODEV; + + return ret; +} +EXPORT_SYMBOL(dwc3_notify_event); + +int dwc3_core_pre_init(struct dwc3 *dwc) +{ + int ret; + + dwc3_cache_hwparams(dwc); + + ret = dwc3_phy_setup(dwc); + if (ret) + goto err0; + + if (!dwc->ev_buffs) { + ret = dwc3_alloc_event_buffers(dwc, DWC3_EVENT_BUFFERS_SIZE); + if (ret) { + dev_err(dwc->dev, "failed to allocate event buffers\n"); + ret = -ENOMEM; + goto err1; + } + } + + ret = dwc3_core_init(dwc); + if (ret) { + dev_err(dwc->dev, "failed to initialize core\n"); + goto err2; + } + + ret = phy_power_on(dwc->usb2_generic_phy); + if (ret < 0) + goto err3; + + ret = phy_power_on(dwc->usb3_generic_phy); + if (ret < 0) + goto err4; + + ret = dwc3_event_buffers_setup(dwc); + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err5; + } + + ret = dwc3_core_init_mode(dwc); + if (ret) { + dev_err(dwc->dev, "failed to set mode with dwc3 core\n"); + goto err6; + } + + return ret; + +err6: + dwc3_event_buffers_cleanup(dwc); +err5: + phy_power_off(dwc->usb3_generic_phy); +err4: + phy_power_off(dwc->usb2_generic_phy); +err3: + dwc3_core_exit(dwc); +err2: + dwc3_free_event_buffers(dwc); +err1: + dwc3_ulpi_exit(dwc); +err0: + return ret; +} + +#define DWC3_ALIGN_MASK (16 - 1) + +/* check whether the core supports IMOD */ +bool dwc3_has_imod(struct dwc3 *dwc) +{ + return ((dwc3_is_usb3(dwc) && + dwc->revision >= DWC3_REVISION_300A) || + (dwc3_is_usb31(dwc) && + dwc->revision >= DWC3_USB31_REVISION_120A)); +} + +static int dwc3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dwc3_platform_data *pdata = dev_get_platdata(dev); + struct resource *res; + struct dwc3 *dwc; + u8 lpm_nyet_threshold; + u8 tx_de_emphasis; + u8 hird_threshold; + u32 fladj = 0; + u32 num_evt_buffs; + int irq; + int ret; + + void __iomem *regs; + void *mem; + + mem = devm_kzalloc(dev, sizeof(*dwc) + DWC3_ALIGN_MASK, GFP_KERNEL); + if (!mem) + return -ENOMEM; + + dwc = PTR_ALIGN(mem, DWC3_ALIGN_MASK + 1); + dwc->mem = mem; + dwc->dev = dev; + + dwc->notify_event = notify_event; + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(dev, "missing IRQ\n"); + return -ENODEV; + } + dwc->xhci_resources[1].start = res->start; + dwc->xhci_resources[1].end = res->end; + dwc->xhci_resources[1].flags = res->flags; + dwc->xhci_resources[1].name = res->name; + + irq = platform_get_irq(to_platform_device(dwc->dev), 0); + + /* will be enabled in dwc3_msm_resume() */ + irq_set_status_flags(irq, IRQ_NOAUTOEN); + ret = devm_request_irq(dev, irq, dwc3_interrupt, IRQF_SHARED, "dwc3", + dwc); + if (ret) { + dev_err(dwc->dev, "failed to request irq #%d --> %d\n", + irq, ret); + return -ENODEV; + } + + dwc->irq = irq; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(dev, "missing memory resource\n"); + return -ENODEV; + } + + dwc->reg_phys = res->start; + dwc->xhci_resources[0].start = res->start; + dwc->xhci_resources[0].end = dwc->xhci_resources[0].start + + DWC3_XHCI_REGS_END; + dwc->xhci_resources[0].flags = res->flags; + dwc->xhci_resources[0].name = res->name; + + res->start += DWC3_GLOBALS_REGS_START; + + /* + * Request memory region but exclude xHCI regs, + * since it will be requested by the xhci-plat driver. + */ + regs = devm_ioremap_resource(dev, res); + if (IS_ERR(regs)) { + ret = PTR_ERR(regs); + goto err0; + } + + dwc->regs = regs; + dwc->regs_size = resource_size(res); + + /* default to highest possible threshold */ + lpm_nyet_threshold = 0xf; + + /* default to -3.5dB de-emphasis */ + tx_de_emphasis = 1; + + /* + * default to assert utmi_sleep_n and use maximum allowed HIRD + * threshold value of 0b1100 + */ + hird_threshold = 12; + + dwc->maximum_speed = usb_get_maximum_speed(dev); + dwc->max_hw_supp_speed = dwc->maximum_speed; + dwc->dr_mode = usb_get_dr_mode(dev); + + dwc->has_lpm_erratum = device_property_read_bool(dev, + "snps,has-lpm-erratum"); + device_property_read_u8(dev, "snps,lpm-nyet-threshold", + &lpm_nyet_threshold); + dwc->is_utmi_l1_suspend = device_property_read_bool(dev, + "snps,is-utmi-l1-suspend"); + device_property_read_u8(dev, "snps,hird-threshold", + &hird_threshold); + dwc->usb3_lpm_capable = device_property_read_bool(dev, + "snps,usb3_lpm_capable"); + + dwc->needs_fifo_resize = device_property_read_bool(dev, + "tx-fifo-resize"); + + dwc->disable_scramble_quirk = device_property_read_bool(dev, + "snps,disable_scramble_quirk"); + dwc->u2exit_lfps_quirk = device_property_read_bool(dev, + "snps,u2exit_lfps_quirk"); + dwc->u2ss_inp3_quirk = device_property_read_bool(dev, + "snps,u2ss_inp3_quirk"); + dwc->req_p1p2p3_quirk = device_property_read_bool(dev, + "snps,req_p1p2p3_quirk"); + dwc->del_p1p2p3_quirk = device_property_read_bool(dev, + "snps,del_p1p2p3_quirk"); + dwc->del_phy_power_chg_quirk = device_property_read_bool(dev, + "snps,del_phy_power_chg_quirk"); + dwc->lfps_filter_quirk = device_property_read_bool(dev, + "snps,lfps_filter_quirk"); + dwc->rx_detect_poll_quirk = device_property_read_bool(dev, + "snps,rx_detect_poll_quirk"); + dwc->dis_u3_susphy_quirk = device_property_read_bool(dev, + "snps,dis_u3_susphy_quirk"); + dwc->dis_u2_susphy_quirk = device_property_read_bool(dev, + "snps,dis_u2_susphy_quirk"); + dwc->dis_enblslpm_quirk = device_property_read_bool(dev, + "snps,dis_enblslpm_quirk"); + + dwc->tx_de_emphasis_quirk = device_property_read_bool(dev, + "snps,tx_de_emphasis_quirk"); + device_property_read_u8(dev, "snps,tx_de_emphasis", + &tx_de_emphasis); + device_property_read_string(dev, "snps,hsphy_interface", + &dwc->hsphy_interface); + device_property_read_u32(dev, "snps,quirk-frame-length-adjustment", + &fladj); + + dwc->nominal_elastic_buffer = device_property_read_bool(dev, + "snps,nominal-elastic-buffer"); + dwc->usb3_u1u2_disable = device_property_read_bool(dev, + "snps,usb3-u1u2-disable"); + dwc->disable_clk_gating = device_property_read_bool(dev, + "snps,disable-clk-gating"); + dwc->enable_bus_suspend = device_property_read_bool(dev, + "snps,bus-suspend-enable"); + + dwc->num_normal_event_buffers = 1; + ret = device_property_read_u32(dev, + "snps,num-normal-evt-buffs", &num_evt_buffs); + if (!ret) + dwc->num_normal_event_buffers = num_evt_buffs; + + ret = device_property_read_u32(dev, + "snps,num-gsi-evt-buffs", &dwc->num_gsi_event_buffers); + + if (dwc->enable_bus_suspend) { + pm_runtime_set_autosuspend_delay(dev, 500); + pm_runtime_use_autosuspend(dev); + } + + if (pdata) { + dwc->maximum_speed = pdata->maximum_speed; + dwc->max_hw_supp_speed = dwc->maximum_speed; + dwc->has_lpm_erratum = pdata->has_lpm_erratum; + if (pdata->lpm_nyet_threshold) + lpm_nyet_threshold = pdata->lpm_nyet_threshold; + dwc->is_utmi_l1_suspend = pdata->is_utmi_l1_suspend; + if (pdata->hird_threshold) + hird_threshold = pdata->hird_threshold; + + dwc->needs_fifo_resize = pdata->tx_fifo_resize; + dwc->usb3_lpm_capable = pdata->usb3_lpm_capable; + dwc->dr_mode = pdata->dr_mode; + + dwc->disable_scramble_quirk = pdata->disable_scramble_quirk; + dwc->u2exit_lfps_quirk = pdata->u2exit_lfps_quirk; + dwc->u2ss_inp3_quirk = pdata->u2ss_inp3_quirk; + dwc->req_p1p2p3_quirk = pdata->req_p1p2p3_quirk; + dwc->del_p1p2p3_quirk = pdata->del_p1p2p3_quirk; + dwc->del_phy_power_chg_quirk = pdata->del_phy_power_chg_quirk; + dwc->lfps_filter_quirk = pdata->lfps_filter_quirk; + dwc->rx_detect_poll_quirk = pdata->rx_detect_poll_quirk; + dwc->dis_u3_susphy_quirk = pdata->dis_u3_susphy_quirk; + dwc->dis_u2_susphy_quirk = pdata->dis_u2_susphy_quirk; + dwc->dis_enblslpm_quirk = pdata->dis_enblslpm_quirk; + + dwc->tx_de_emphasis_quirk = pdata->tx_de_emphasis_quirk; + if (pdata->tx_de_emphasis) + tx_de_emphasis = pdata->tx_de_emphasis; + + dwc->hsphy_interface = pdata->hsphy_interface; + fladj = pdata->fladj_value; + } + + /* default to superspeed if no maximum_speed passed */ + if (dwc->maximum_speed == USB_SPEED_UNKNOWN) + dwc->max_hw_supp_speed = dwc->maximum_speed = USB_SPEED_SUPER; + + dwc->lpm_nyet_threshold = lpm_nyet_threshold; + dwc->tx_de_emphasis = tx_de_emphasis; + + dwc->hird_threshold = hird_threshold + | (dwc->is_utmi_l1_suspend << 4); + + init_waitqueue_head(&dwc->wait_linkstate); + platform_set_drvdata(pdev, dwc); + ret = dwc3_core_get_phy(dwc); + if (ret) + goto err0; + + spin_lock_init(&dwc->lock); + + dev->dma_mask = dev->parent->dma_mask; + dev->dma_parms = dev->parent->dma_parms; + dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask); + + dwc->dwc_wq = alloc_ordered_workqueue("dwc_wq", WQ_HIGHPRI); + if (!dwc->dwc_wq) { + pr_err("%s: Unable to create workqueue dwc_wq\n", __func__); + return -ENOMEM; + } + + INIT_WORK(&dwc->bh_work, dwc3_bh_work); + + pm_runtime_no_callbacks(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_runtime_forbid(dev); + + if (IS_ENABLED(CONFIG_USB_DWC3_HOST)) + dwc->dr_mode = USB_DR_MODE_HOST; + else if (IS_ENABLED(CONFIG_USB_DWC3_GADGET)) + dwc->dr_mode = USB_DR_MODE_PERIPHERAL; + + if (dwc->dr_mode == USB_DR_MODE_UNKNOWN) { + dwc->dr_mode = USB_DR_MODE_OTG; + dwc->is_drd = true; + } + + /* Adjust Frame Length */ + dwc3_frame_length_adjustment(dwc, fladj); + + /* Hardcode number of eps */ + dwc->num_in_eps = 16; + dwc->num_out_eps = 16; + + if (dwc->dr_mode == USB_DR_MODE_OTG || + dwc->dr_mode == USB_DR_MODE_PERIPHERAL) { + ret = dwc3_gadget_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize gadget\n"); + goto err0; + } + } + + if (dwc->dr_mode == USB_DR_MODE_OTG || + dwc->dr_mode == USB_DR_MODE_HOST) { + ret = dwc3_host_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize host\n"); + goto err_gadget; + } + } + + ret = dwc3_debugfs_init(dwc); + if (ret) { + dev_err(dev, "failed to initialize debugfs\n"); + goto err_host; + } + + pm_runtime_allow(dev); + + return 0; + +err_host: + if (dwc->dr_mode == USB_DR_MODE_OTG || + dwc->dr_mode == USB_DR_MODE_HOST) + dwc3_host_exit(dwc); +err_gadget: + if (dwc->dr_mode == USB_DR_MODE_OTG || + dwc->dr_mode == USB_DR_MODE_PERIPHERAL) + dwc3_gadget_exit(dwc); +err0: + /* + * restore res->start back to its original value so that, in case the + * probe is deferred, we don't end up getting error in request the + * memory region the next time probe is called. + */ + res->start -= DWC3_GLOBALS_REGS_START; + destroy_workqueue(dwc->dwc_wq); + + return ret; +} + +static int dwc3_remove(struct platform_device *pdev) +{ + struct dwc3 *dwc = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + /* + * restore res->start back to its original value so that, in case the + * probe is deferred, we don't end up getting error in request the + * memory region the next time probe is called. + */ + res->start -= DWC3_GLOBALS_REGS_START; + + dwc3_debugfs_exit(dwc); + dwc3_core_exit_mode(dwc); + dwc3_event_buffers_cleanup(dwc); + dwc3_free_event_buffers(dwc); + + phy_power_off(dwc->usb2_generic_phy); + phy_power_off(dwc->usb3_generic_phy); + + dwc3_core_exit(dwc); + dwc3_ulpi_exit(dwc); + + destroy_workqueue(dwc->dwc_wq); + + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dwc3_suspend(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + + /* Check if platform glue driver handling PM, if not then handle here */ + if (!dwc3_notify_event(dwc, DWC3_CORE_PM_SUSPEND_EVENT, 0)) + return 0; + + spin_lock_irqsave(&dwc->lock, flags); + + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + case USB_DR_MODE_OTG: + dwc3_gadget_suspend(dwc); + /* FALLTHROUGH */ + case USB_DR_MODE_HOST: + default: + dwc3_event_buffers_cleanup(dwc); + break; + } + + dwc->gctl = dwc3_readl(dwc->regs, DWC3_GCTL); + spin_unlock_irqrestore(&dwc->lock, flags); + + usb_phy_shutdown(dwc->usb3_phy); + usb_phy_shutdown(dwc->usb2_phy); + phy_exit(dwc->usb2_generic_phy); + phy_exit(dwc->usb3_generic_phy); + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int dwc3_resume(struct device *dev) +{ + struct dwc3 *dwc = dev_get_drvdata(dev); + unsigned long flags; + int ret; + + /* Check if platform glue driver handling PM, if not then handle here */ + if (!dwc3_notify_event(dwc, DWC3_CORE_PM_RESUME_EVENT, 0)) + return 0; + + pinctrl_pm_select_default_state(dev); + + usb_phy_init(dwc->usb3_phy); + usb_phy_init(dwc->usb2_phy); + ret = phy_init(dwc->usb2_generic_phy); + if (ret < 0) + return ret; + + ret = phy_init(dwc->usb3_generic_phy); + if (ret < 0) + goto err_usb2phy_init; + + spin_lock_irqsave(&dwc->lock, flags); + + dwc3_event_buffers_setup(dwc); + dwc3_writel(dwc->regs, DWC3_GCTL, dwc->gctl); + + switch (dwc->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + case USB_DR_MODE_OTG: + dwc3_gadget_resume(dwc); + /* FALLTHROUGH */ + case USB_DR_MODE_HOST: + default: + /* do nothing */ + break; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; + +err_usb2phy_init: + phy_exit(dwc->usb2_generic_phy); + + return ret; +} + +static int dwc3_pm_restore(struct device *dev) +{ + /* + * Set the core as runtime active to prevent the runtime + * PM ops being called before the PM restore is completed. + */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static const struct dev_pm_ops dwc3_dev_pm_ops = { + .suspend = dwc3_suspend, + .resume = dwc3_resume, + .freeze = dwc3_suspend, + .thaw = dwc3_pm_restore, + .poweroff = dwc3_suspend, + .restore = dwc3_pm_restore, +}; + +#define DWC3_PM_OPS &(dwc3_dev_pm_ops) +#else +#define DWC3_PM_OPS NULL +#endif + +#ifdef CONFIG_OF +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "snps,dwc3" + }, + { + .compatible = "synopsys,dwc3" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); +#endif + +#ifdef CONFIG_ACPI + +#define ACPI_ID_INTEL_BSW "808622B7" + +static const struct acpi_device_id dwc3_acpi_match[] = { + { ACPI_ID_INTEL_BSW, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(acpi, dwc3_acpi_match); +#endif + +static struct platform_driver dwc3_driver = { + .probe = dwc3_probe, + .remove = dwc3_remove, + .driver = { + .name = "dwc3", + .of_match_table = of_match_ptr(of_dwc3_match), + .acpi_match_table = ACPI_PTR(dwc3_acpi_match), + .pm = DWC3_PM_OPS, + }, +}; + +module_platform_driver(dwc3_driver); + +MODULE_ALIAS("platform:dwc3"); +MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 DRD Controller Driver"); diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h new file mode 100644 index 000000000000..2e56d167ba05 --- /dev/null +++ b/drivers/usb/dwc3/core.h @@ -0,0 +1,1307 @@ +/** + * core.h - DesignWare USB3 DRD Core 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. + */ + +#ifndef __DRIVERS_USB_DWC3_CORE_H +#define __DRIVERS_USB_DWC3_CORE_H + +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/ioport.h> +#include <linux/list.h> +#include <linux/dma-mapping.h> +#include <linux/mm.h> +#include <linux/debugfs.h> +#include <linux/workqueue.h> +#include <linux/wait.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/otg.h> +#include <linux/ulpi/interface.h> + +#include <linux/phy/phy.h> + +#define DWC3_MSG_MAX 500 + +/* Global constants */ +#define DWC3_ZLP_BUF_SIZE 1024 /* size of a superspeed bulk */ +#define DWC3_EP0_BOUNCE_SIZE 512 +#define DWC3_ENDPOINTS_NUM 32 +#define DWC3_XHCI_RESOURCES_NUM 2 + +#define DWC3_SCRATCHBUF_SIZE 4096 /* each buffer is assumed to be 4KiB */ +#define DWC3_EVENT_BUFFERS_SIZE 4096 +#define DWC3_EVENT_TYPE_MASK 0xfe + +#define DWC3_EVENT_TYPE_DEV 0 +#define DWC3_EVENT_TYPE_CARKIT 3 +#define DWC3_EVENT_TYPE_I2C 4 + +#define DWC3_DEVICE_EVENT_DISCONNECT 0 +#define DWC3_DEVICE_EVENT_RESET 1 +#define DWC3_DEVICE_EVENT_CONNECT_DONE 2 +#define DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE 3 +#define DWC3_DEVICE_EVENT_WAKEUP 4 +#define DWC3_DEVICE_EVENT_HIBER_REQ 5 +#define DWC3_DEVICE_EVENT_EOPF 6 +/* For version 2.30a and above */ +#define DWC3_DEVICE_EVENT_SUSPEND 6 +#define DWC3_DEVICE_EVENT_SOF 7 +#define DWC3_DEVICE_EVENT_ERRATIC_ERROR 9 +#define DWC3_DEVICE_EVENT_CMD_CMPL 10 +#define DWC3_DEVICE_EVENT_OVERFLOW 11 + +#define DWC3_GEVNTCOUNT_MASK 0xfffc +#define DWC3_GEVNTCOUNT_EHB (1 << 31) +#define DWC3_GSNPSID_MASK 0xffff0000 +#define DWC3_GSNPSREV_MASK 0xffff + +/* DWC3 registers memory space boundries */ +#define DWC3_XHCI_REGS_START 0x0 +#define DWC3_XHCI_REGS_END 0x7fff +#define DWC3_GLOBALS_REGS_START 0xc100 +#define DWC3_GLOBALS_REGS_END 0xc6ff +#define DWC3_DEVICE_REGS_START 0xc700 +#define DWC3_DEVICE_REGS_END 0xcbff +#define DWC3_OTG_REGS_START 0xcc00 +#define DWC3_OTG_REGS_END 0xccff + +/* Global Registers */ +#define DWC3_GSBUSCFG0 0xc100 +#define DWC3_GSBUSCFG1 0xc104 +#define DWC3_GTXTHRCFG 0xc108 +#define DWC3_GRXTHRCFG 0xc10c +#define DWC3_GCTL 0xc110 +#define DWC3_GEVTEN 0xc114 +#define DWC3_GSTS 0xc118 +#define DWC3_GSNPSID 0xc120 +#define DWC3_GGPIO 0xc124 +#define DWC3_GUID 0xc128 +#define DWC3_GUCTL 0xc12c +#define DWC3_GBUSERRADDR0 0xc130 +#define DWC3_GBUSERRADDR1 0xc134 +#define DWC3_GPRTBIMAP0 0xc138 +#define DWC3_GPRTBIMAP1 0xc13c +#define DWC3_GHWPARAMS0 0xc140 +#define DWC3_GHWPARAMS1 0xc144 +#define DWC3_GHWPARAMS2 0xc148 +#define DWC3_GHWPARAMS3 0xc14c +#define DWC3_GHWPARAMS4 0xc150 +#define DWC3_GHWPARAMS5 0xc154 +#define DWC3_GHWPARAMS6 0xc158 +#define DWC3_GHWPARAMS7 0xc15c +#define DWC3_GDBGFIFOSPACE 0xc160 +#define DWC3_GDBGLTSSM 0xc164 +#define DWC3_GPRTBIMAP_HS0 0xc180 +#define DWC3_GPRTBIMAP_HS1 0xc184 +#define DWC3_GPRTBIMAP_FS0 0xc188 +#define DWC3_GPRTBIMAP_FS1 0xc18c + +#define DWC3_VER_NUMBER 0xc1a0 +#define DWC3_VER_TYPE 0xc1a4 + +#define DWC3_GUSB2PHYCFG(n) (0xc200 + (n * 0x04)) +#define DWC3_GUSB2I2CCTL(n) (0xc240 + (n * 0x04)) + +#define DWC3_GUSB2PHYACC(n) (0xc280 + (n * 0x04)) + +#define DWC3_GUSB3PIPECTL(n) (0xc2c0 + (n * 0x04)) + +#define DWC3_GTXFIFOSIZ(n) (0xc300 + (n * 0x04)) +#define DWC3_GRXFIFOSIZ(n) (0xc380 + (n * 0x04)) + +#define DWC3_GEVNTADRLO(n) (0xc400 + (n * 0x10)) +#define DWC3_GEVNTADRHI(n) (0xc404 + (n * 0x10)) +#define DWC3_GEVNTSIZ(n) (0xc408 + (n * 0x10)) +#define DWC3_GEVNTCOUNT(n) (0xc40c + (n * 0x10)) + +#define DWC3_GEVNTCOUNT_EVNTINTRPTMASK (1 << 31) +#define DWC3_GEVNTADRHI_EVNTADRHI_GSI_EN(n) (n << 22) +#define DWC3_GEVNTADRHI_EVNTADRHI_GSI_IDX(n) (n << 16) +#define DWC3_GEVENT_TYPE_GSI 0x3 + +#define DWC3_GHWPARAMS8 0xc600 +#define DWC3_GFLADJ 0xc630 + +/* Device Registers */ +#define DWC3_DCFG 0xc700 +#define DWC3_DCTL 0xc704 +#define DWC3_DEVTEN 0xc708 +#define DWC3_DSTS 0xc70c +#define DWC3_DGCMDPAR 0xc710 +#define DWC3_DGCMD 0xc714 +#define DWC3_DALEPENA 0xc720 +#define DWC3_DEPCMDPAR2(n) (0xc800 + (n * 0x10)) +#define DWC3_DEPCMDPAR1(n) (0xc804 + (n * 0x10)) +#define DWC3_DEPCMDPAR0(n) (0xc808 + (n * 0x10)) +#define DWC3_DEPCMD(n) (0xc80c + (n * 0x10)) + +#define DWC3_DEV_IMOD(n) (0xca00 + (n * 0x4)) + +/* OTG Registers */ +#define DWC3_OCFG 0xcc00 +#define DWC3_OCTL 0xcc04 +#define DWC3_OEVT 0xcc08 +#define DWC3_OEVTEN 0xcc0C +#define DWC3_OSTS 0xcc10 + +/* Bit fields */ + +/* Global SoC Bus Configuration Register 1 */ +#define DWC3_GSBUSCFG1_PIPETRANSLIMIT_MASK (0x0f << 8) +#define DWC3_GSBUSCFG1_PIPETRANSLIMIT(n) ((n) << 8) + +/* Global Configuration Register */ +#define DWC3_GCTL_PWRDNSCALE(n) ((n) << 19) +#define DWC3_GCTL_PWRDNSCALEMASK (0xFFF80000) +#define DWC3_GCTL_U2RSTECN (1 << 16) +#define DWC3_GCTL_SOFITPSYNC (1 << 10) +#define DWC3_GCTL_U2EXIT_LFPS (1 << 2) +#define DWC3_GCTL_RAMCLKSEL(x) (((x) & DWC3_GCTL_CLK_MASK) << 6) +#define DWC3_GCTL_CLK_BUS (0) +#define DWC3_GCTL_CLK_PIPE (1) +#define DWC3_GCTL_CLK_PIPEHALF (2) +#define DWC3_GCTL_CLK_MASK (3) + +#define DWC3_GCTL_PRTCAP(n) (((n) & (3 << 12)) >> 12) +#define DWC3_GCTL_PRTCAPDIR(n) ((n) << 12) +#define DWC3_GCTL_PRTCAP_HOST 1 +#define DWC3_GCTL_PRTCAP_DEVICE 2 +#define DWC3_GCTL_PRTCAP_OTG 3 + +#define DWC3_GCTL_CORESOFTRESET (1 << 11) +#define DWC3_GCTL_SOFITPSYNC (1 << 10) +#define DWC3_GCTL_SCALEDOWN(n) ((n) << 4) +#define DWC3_GCTL_SCALEDOWN_MASK DWC3_GCTL_SCALEDOWN(3) +#define DWC3_GCTL_DISSCRAMBLE (1 << 3) +#define DWC3_GCTL_U2EXIT_LFPS (1 << 2) +#define DWC3_GCTL_GBLHIBERNATIONEN (1 << 1) +#define DWC3_GCTL_DSBLCLKGTNG (1 << 0) + +/* Global User Control Register */ +#define DWC3_GUCTL_REFCLKPER (0x3FF << 22) + +/* Global Debug LTSSM Register */ +#define DWC3_GDBGLTSSM_LINKSTATE_MASK (0xF << 22) + +/* Global USB2 PHY Configuration Register */ +#define DWC3_GUSB2PHYCFG_PHYSOFTRST (1 << 31) +#define DWC3_GUSB2PHYCFG_ENBLSLPM (1 << 8) +#define DWC3_GUSB2PHYCFG_SUSPHY (1 << 6) +#define DWC3_GUSB2PHYCFG_ULPI_UTMI (1 << 4) +#define DWC3_GUSB2PHYCFG_ENBLSLPM (1 << 8) + +/* Global USB2 PHY Vendor Control Register */ +#define DWC3_GUSB2PHYACC_NEWREGREQ (1 << 25) +#define DWC3_GUSB2PHYACC_BUSY (1 << 23) +#define DWC3_GUSB2PHYACC_WRITE (1 << 22) +#define DWC3_GUSB2PHYACC_ADDR(n) (n << 16) +#define DWC3_GUSB2PHYACC_EXTEND_ADDR(n) (n << 8) +#define DWC3_GUSB2PHYACC_DATA(n) (n & 0xff) + +/* Global USB3 PIPE Control Register */ +#define DWC3_GUSB3PIPECTL_PHYSOFTRST (1 << 31) +#define DWC3_GUSB3PIPECTL_U2SSINP3OK (1 << 29) +#define DWC3_GUSB3PIPECTL_UX_EXIT_IN_PX (1 << 27) +#define DWC3_GUSB3PIPECTL_REQP1P2P3 (1 << 24) +#define DWC3_GUSB3PIPECTL_DEP1P2P3(n) ((n) << 19) +#define DWC3_GUSB3PIPECTL_DEP1P2P3_MASK DWC3_GUSB3PIPECTL_DEP1P2P3(7) +#define DWC3_GUSB3PIPECTL_DEP1P2P3_EN DWC3_GUSB3PIPECTL_DEP1P2P3(1) +#define DWC3_GUSB3PIPECTL_DEPOCHANGE (1 << 18) +#define DWC3_GUSB3PIPECTL_SUSPHY (1 << 17) +#define DWC3_GUSB3PIPECTL_LFPSFILT (1 << 9) +#define DWC3_GUSB3PIPECTL_RX_DETOPOLL (1 << 8) +#define DWC3_GUSB3PIPECTL_TX_DEEPH_MASK DWC3_GUSB3PIPECTL_TX_DEEPH(3) +#define DWC3_GUSB3PIPECTL_TX_DEEPH(n) ((n) << 1) +#define DWC3_GUSB3PIPECTL_DELAYP1TRANS (1 << 18) +#define DWC3_GUSB3PIPECTL_ELASTIC_BUF_MODE (1 << 0) + +/* Global TX Fifo Size Register */ +#define DWC31_GTXFIFOSIZ_TXFRAMNUM BIT(15) /* DWC_usb31 only */ +#define DWC31_GTXFIFOSIZ_TXFDEF(n) ((n) & 0x7fff) /* DWC_usb31 only */ +#define DWC3_GTXFIFOSIZ_TXFDEF(n) ((n) & 0xffff) +#define DWC3_GTXFIFOSIZ_TXFSTADDR(n) ((n) & 0xffff0000) + +/* Global Event Size Registers */ +#define DWC3_GEVNTSIZ_INTMASK (1 << 31) +#define DWC3_GEVNTSIZ_SIZE(n) ((n) & 0xffff) + +/* Global HWPARAMS1 Register */ +#define DWC3_GHWPARAMS1_EN_PWROPT(n) (((n) & (3 << 24)) >> 24) +#define DWC3_GHWPARAMS1_EN_PWROPT_NO 0 +#define DWC3_GHWPARAMS1_EN_PWROPT_CLK 1 +#define DWC3_GHWPARAMS1_EN_PWROPT_HIB 2 +#define DWC3_GHWPARAMS1_PWROPT(n) ((n) << 24) +#define DWC3_GHWPARAMS1_PWROPT_MASK DWC3_GHWPARAMS1_PWROPT(3) + +/* Global HWPARAMS3 Register */ +#define DWC3_GHWPARAMS3_SSPHY_IFC(n) ((n) & 3) +#define DWC3_GHWPARAMS3_SSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_SSPHY_IFC_ENA 1 +#define DWC3_GHWPARAMS3_HSPHY_IFC(n) (((n) & (3 << 2)) >> 2) +#define DWC3_GHWPARAMS3_HSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI 1 +#define DWC3_GHWPARAMS3_HSPHY_IFC_ULPI 2 +#define DWC3_GHWPARAMS3_HSPHY_IFC_UTMI_ULPI 3 +#define DWC3_GHWPARAMS3_FSPHY_IFC(n) (((n) & (3 << 4)) >> 4) +#define DWC3_GHWPARAMS3_FSPHY_IFC_DIS 0 +#define DWC3_GHWPARAMS3_FSPHY_IFC_ENA 1 + +/* Global HWPARAMS4 Register */ +#define DWC3_GHWPARAMS4_HIBER_SCRATCHBUFS(n) (((n) & (0x0f << 13)) >> 13) +#define DWC3_MAX_HIBER_SCRATCHBUFS 15 + +/* Global HWPARAMS6 Register */ +#define DWC3_GHWPARAMS6_EN_FPGA (1 << 7) + +/* Global Frame Length Adjustment Register */ +#define DWC3_GFLADJ_30MHZ_SDBND_SEL (1 << 7) +#define DWC3_GFLADJ_30MHZ_MASK 0x3f + +/* Global Frame Length Adjustment Register */ +#define DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1 (1 << 31) +#define DWC3_GFLADJ_REFCLK_240MHZ_DECR (0x7F << 24) +#define DWC3_GFLADJ_REFCLK_LPM_SEL (1 << 23) +#define DWC3_GFLADJ_REFCLK_FLADJ (0x3FFF << 8) + +/* Device Configuration Register */ +#define DWC3_DCFG_DEVADDR(addr) ((addr) << 3) +#define DWC3_DCFG_DEVADDR_MASK DWC3_DCFG_DEVADDR(0x7f) + +#define DWC3_DCFG_SPEED_MASK (7 << 0) +#define DWC3_DCFG_SUPERSPEED (4 << 0) +#define DWC3_DCFG_HIGHSPEED (0 << 0) +#define DWC3_DCFG_FULLSPEED2 (1 << 0) +#define DWC3_DCFG_LOWSPEED (2 << 0) +#define DWC3_DCFG_FULLSPEED1 (3 << 0) + +#define DWC3_DCFG_LPM_CAP (1 << 22) + +/* Device Control Register */ +#define DWC3_DCTL_RUN_STOP (1 << 31) +#define DWC3_DCTL_CSFTRST (1 << 30) +#define DWC3_DCTL_LSFTRST (1 << 29) + +#define DWC3_DCTL_HIRD_THRES_MASK (0x1f << 24) +#define DWC3_DCTL_HIRD_THRES(n) ((n) << 24) + +#define DWC3_DCTL_APPL1RES (1 << 23) + +/* These apply for core versions 1.87a and earlier */ +#define DWC3_DCTL_TRGTULST_MASK (0x0f << 17) +#define DWC3_DCTL_TRGTULST(n) ((n) << 17) +#define DWC3_DCTL_TRGTULST_U2 (DWC3_DCTL_TRGTULST(2)) +#define DWC3_DCTL_TRGTULST_U3 (DWC3_DCTL_TRGTULST(3)) +#define DWC3_DCTL_TRGTULST_SS_DIS (DWC3_DCTL_TRGTULST(4)) +#define DWC3_DCTL_TRGTULST_RX_DET (DWC3_DCTL_TRGTULST(5)) +#define DWC3_DCTL_TRGTULST_SS_INACT (DWC3_DCTL_TRGTULST(6)) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DCTL_LPM_ERRATA_MASK DWC3_DCTL_LPM_ERRATA(0xf) +#define DWC3_DCTL_LPM_ERRATA(n) ((n) << 20) + +#define DWC3_DCTL_KEEP_CONNECT (1 << 19) +#define DWC3_DCTL_L1_HIBER_EN (1 << 18) +#define DWC3_DCTL_CRS (1 << 17) +#define DWC3_DCTL_CSS (1 << 16) + +#define DWC3_DCTL_INITU2ENA (1 << 12) +#define DWC3_DCTL_ACCEPTU2ENA (1 << 11) +#define DWC3_DCTL_INITU1ENA (1 << 10) +#define DWC3_DCTL_ACCEPTU1ENA (1 << 9) +#define DWC3_DCTL_TSTCTRL_MASK (0xf << 1) + +#define DWC3_DCTL_ULSTCHNGREQ_MASK (0x0f << 5) +#define DWC3_DCTL_ULSTCHNGREQ(n) (((n) << 5) & DWC3_DCTL_ULSTCHNGREQ_MASK) + +#define DWC3_DCTL_ULSTCHNG_NO_ACTION (DWC3_DCTL_ULSTCHNGREQ(0)) +#define DWC3_DCTL_ULSTCHNG_SS_DISABLED (DWC3_DCTL_ULSTCHNGREQ(4)) +#define DWC3_DCTL_ULSTCHNG_RX_DETECT (DWC3_DCTL_ULSTCHNGREQ(5)) +#define DWC3_DCTL_ULSTCHNG_SS_INACTIVE (DWC3_DCTL_ULSTCHNGREQ(6)) +#define DWC3_DCTL_ULSTCHNG_RECOVERY (DWC3_DCTL_ULSTCHNGREQ(8)) +#define DWC3_DCTL_ULSTCHNG_COMPLIANCE (DWC3_DCTL_ULSTCHNGREQ(10)) +#define DWC3_DCTL_ULSTCHNG_LOOPBACK (DWC3_DCTL_ULSTCHNGREQ(11)) + +/* Device Event Enable Register */ +#define DWC3_DEVTEN_VNDRDEVTSTRCVEDEN (1 << 12) +#define DWC3_DEVTEN_EVNTOVERFLOWEN (1 << 11) +#define DWC3_DEVTEN_CMDCMPLTEN (1 << 10) +#define DWC3_DEVTEN_ERRTICERREN (1 << 9) +#define DWC3_DEVTEN_SOFEN (1 << 7) +#define DWC3_DEVTEN_EOPFEN (1 << 6) +/* For version 2.30a and above*/ +#define DWC3_DEVTEN_SUSPEND (1 << 6) +#define DWC3_DEVTEN_HIBERNATIONREQEVTEN (1 << 5) +#define DWC3_DEVTEN_WKUPEVTEN (1 << 4) +#define DWC3_DEVTEN_ULSTCNGEN (1 << 3) +#define DWC3_DEVTEN_CONNECTDONEEN (1 << 2) +#define DWC3_DEVTEN_USBRSTEN (1 << 1) +#define DWC3_DEVTEN_DISCONNEVTEN (1 << 0) + +/* Device Status Register */ +#define DWC3_DSTS_DCNRD (1 << 29) + +/* This applies for core versions 1.87a and earlier */ +#define DWC3_DSTS_PWRUPREQ (1 << 24) + +/* These apply for core versions 1.94a and later */ +#define DWC3_DSTS_RSS (1 << 25) +#define DWC3_DSTS_SSS (1 << 24) + +#define DWC3_DSTS_COREIDLE (1 << 23) +#define DWC3_DSTS_DEVCTRLHLT (1 << 22) + +#define DWC3_DSTS_USBLNKST_MASK (0x0f << 18) +#define DWC3_DSTS_USBLNKST(n) (((n) & DWC3_DSTS_USBLNKST_MASK) >> 18) + +#define DWC3_DSTS_RXFIFOEMPTY (1 << 17) + +#define DWC3_DSTS_SOFFN_MASK (0x3fff << 3) +#define DWC3_DSTS_SOFFN(n) (((n) & DWC3_DSTS_SOFFN_MASK) >> 3) + +#define DWC3_DSTS_CONNECTSPD (7 << 0) + +#define DWC3_DSTS_SUPERSPEED (4 << 0) +#define DWC3_DSTS_HIGHSPEED (0 << 0) +#define DWC3_DSTS_FULLSPEED2 (1 << 0) +#define DWC3_DSTS_LOWSPEED (2 << 0) +#define DWC3_DSTS_FULLSPEED1 (3 << 0) + +/* Device Generic Command Register */ +#define DWC3_DGCMD_SET_LMP 0x01 +#define DWC3_DGCMD_SET_PERIODIC_PAR 0x02 +#define DWC3_DGCMD_XMIT_FUNCTION 0x03 +#define DWC3_DGCMD_XMIT_DEV 0x07 + +/* These apply for core versions 1.94a and later */ +#define DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO 0x04 +#define DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI 0x05 + +#define DWC3_DGCMD_SELECTED_FIFO_FLUSH 0x09 +#define DWC3_DGCMD_ALL_FIFO_FLUSH 0x0a +#define DWC3_DGCMD_SET_ENDPOINT_NRDY 0x0c +#define DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK 0x10 + +#define DWC3_DGCMD_STATUS(n) (((n) >> 12) & 0x0F) +#define DWC3_DGCMD_CMDACT (1 << 10) +#define DWC3_DGCMD_CMDIOC (1 << 8) + +/* Device Generic Command Parameter Register */ +#define DWC3_DGCMDPAR_FORCE_LINKPM_ACCEPT (1 << 0) +#define DWC3_DGCMDPAR_FIFO_NUM(n) ((n) << 0) +#define DWC3_DGCMDPAR_RX_FIFO (0 << 5) +#define DWC3_DGCMDPAR_TX_FIFO (1 << 5) +#define DWC3_DGCMDPAR_LOOPBACK_DIS (0 << 0) +#define DWC3_DGCMDPAR_LOOPBACK_ENA (1 << 0) + +/* Device Endpoint Command Register */ +#define DWC3_DEPCMD_PARAM_SHIFT 16 +#define DWC3_DEPCMD_PARAM(x) ((x) << DWC3_DEPCMD_PARAM_SHIFT) +#define DWC3_DEPCMD_GET_RSC_IDX(x) (((x) >> DWC3_DEPCMD_PARAM_SHIFT) & 0x7f) +#define DWC3_DEPCMD_STATUS(x) (((x) >> 12) & 0x0F) +#define DWC3_DEPCMD_HIPRI_FORCERM (1 << 11) +#define DWC3_DEPCMD_CMDACT (1 << 10) +#define DWC3_DEPCMD_CMDIOC (1 << 8) + +#define DWC3_DEPCMD_DEPSTARTCFG (0x09 << 0) +#define DWC3_DEPCMD_ENDTRANSFER (0x08 << 0) +#define DWC3_DEPCMD_UPDATETRANSFER (0x07 << 0) +#define DWC3_DEPCMD_STARTTRANSFER (0x06 << 0) +#define DWC3_DEPCMD_CLEARSTALL (0x05 << 0) +#define DWC3_DEPCMD_SETSTALL (0x04 << 0) +/* This applies for core versions 1.90a and earlier */ +#define DWC3_DEPCMD_GETSEQNUMBER (0x03 << 0) +/* This applies for core versions 1.94a and later */ +#define DWC3_DEPCMD_GETEPSTATE (0x03 << 0) +#define DWC3_DEPCMD_SETTRANSFRESOURCE (0x02 << 0) +#define DWC3_DEPCMD_SETEPCONFIG (0x01 << 0) + +/* The EP number goes 0..31 so ep0 is always out and ep1 is always in */ +#define DWC3_DALEPENA_EP(n) (1 << n) + +#define DWC3_DEPCMD_TYPE_CONTROL 0 +#define DWC3_DEPCMD_TYPE_ISOC 1 +#define DWC3_DEPCMD_TYPE_BULK 2 +#define DWC3_DEPCMD_TYPE_INTR 3 + +#define DWC3_DEV_IMOD_COUNT_SHIFT 16 +#define DWC3_DEV_IMOD_COUNT_MASK (0xffff << 16) +#define DWC3_DEV_IMOD_INTERVAL_SHIFT 0 +#define DWC3_DEV_IMOD_INTERVAL_MASK (0xffff << 0) + +/* Structures */ + +struct dwc3_trb; + +enum event_buf_type { + EVT_BUF_TYPE_NORMAL, + EVT_BUF_TYPE_GSI +}; + +/** + * struct dwc3_event_buffer - Software event buffer representation + * @buf: _THE_ buffer + * @length: size of this buffer + * @lpos: event offset + * @count: cache of last read event count register + * @flags: flags related to this event buffer + * @dma: dma_addr_t + * @dwc: pointer to DWC controller + */ +struct dwc3_event_buffer { + void *buf; + unsigned length; + enum event_buf_type type; + unsigned int lpos; + unsigned int count; + unsigned int flags; + +#define DWC3_EVENT_PENDING BIT(0) + + dma_addr_t dma; + + struct dwc3 *dwc; +}; + +struct dwc3_gadget_events { + unsigned int disconnect; + unsigned int reset; + unsigned int connect; + unsigned int wakeup; + unsigned int link_status_change; + unsigned int eopf; + unsigned int suspend; + unsigned int sof; + unsigned int erratic_error; + unsigned int overflow; + unsigned int vendor_dev_test_lmp; + unsigned int cmdcmplt; + unsigned int unknown_event; +}; + +struct dwc3_ep_events { + unsigned int xfercomplete; + unsigned int xfernotready; + unsigned int control_data; + unsigned int control_status; + unsigned int xferinprogress; + unsigned int rxtxfifoevent; + unsigned int streamevent; + unsigned int epcmdcomplete; + unsigned int cmdcmplt; + unsigned int unknown_event; + unsigned int total; +}; + +#define DWC3_EP_FLAG_STALLED (1 << 0) +#define DWC3_EP_FLAG_WEDGED (1 << 1) + +#define DWC3_EP_DIRECTION_TX true +#define DWC3_EP_DIRECTION_RX false + +#define DWC3_TRB_NUM 32 +#define DWC3_TRB_MASK (DWC3_TRB_NUM - 1) + +/** + * struct dwc3_ep - device side endpoint representation + * @endpoint: usb endpoint + * @request_list: list of requests for this endpoint + * @req_queued: list of requests on this ep which have TRBs setup + * @trb_dma_pool: dma pool used to get aligned trb memory pool + * @trb_pool: array of transaction buffers + * @trb_pool_dma: dma address of @trb_pool + * @num_trbs: num of trbs in the trb dma pool + * @free_slot: next slot which is going to be used + * @busy_slot: first slot which is owned by HW + * @desc: usb_endpoint_descriptor pointer + * @dwc: pointer to DWC controller + * @saved_state: ep state saved during hibernation + * @flags: endpoint flags (wedged, stalled, ...) + * @number: endpoint number (1 - 15) + * @type: set to bmAttributes & USB_ENDPOINT_XFERTYPE_MASK + * @resource_index: Resource transfer index + * @current_uf: Current uf received through last event parameter + * @interval: the interval on which the ISOC transfer is started + * @name: a human readable name e.g. ep1out-bulk + * @direction: true for TX, false for RX + * @stream_capable: true when streams are enabled + * @dbg_ep_events: different events counter for endpoint + * @dbg_ep_events_diff: differential events counter for endpoint + * @dbg_ep_events_ts: timestamp for previous event counters + * @fifo_depth: allocated TXFIFO depth + */ +struct dwc3_ep { + struct usb_ep endpoint; + struct list_head request_list; + struct list_head req_queued; + + struct dma_pool *trb_dma_pool; + struct dwc3_trb *trb_pool; + dma_addr_t trb_pool_dma; + u32 num_trbs; + u32 free_slot; + u32 busy_slot; + const struct usb_ss_ep_comp_descriptor *comp_desc; + struct dwc3 *dwc; + + u32 saved_state; + unsigned flags; +#define DWC3_EP_ENABLED (1 << 0) +#define DWC3_EP_STALL (1 << 1) +#define DWC3_EP_WEDGE (1 << 2) +#define DWC3_EP_BUSY (1 << 4) +#define DWC3_EP_PENDING_REQUEST (1 << 5) +#define DWC3_EP_MISSED_ISOC (1 << 6) + + /* This last one is specific to EP0 */ +#define DWC3_EP0_DIR_IN (1 << 31) + + u8 number; + u8 type; + u8 resource_index; + u16 current_uf; + u32 interval; + + char name[20]; + + unsigned direction:1; + unsigned stream_capable:1; + struct dwc3_ep_events dbg_ep_events; + struct dwc3_ep_events dbg_ep_events_diff; + struct timespec dbg_ep_events_ts; + int fifo_depth; +}; + +enum dwc3_phy { + DWC3_PHY_UNKNOWN = 0, + DWC3_PHY_USB3, + DWC3_PHY_USB2, +}; + +enum dwc3_ep0_next { + DWC3_EP0_UNKNOWN = 0, + DWC3_EP0_COMPLETE, + DWC3_EP0_NRDY_DATA, + DWC3_EP0_NRDY_STATUS, +}; + +enum dwc3_ep0_state { + EP0_UNCONNECTED = 0, + EP0_SETUP_PHASE, + EP0_DATA_PHASE, + EP0_STATUS_PHASE, +}; + +enum dwc3_link_state { + /* In SuperSpeed */ + DWC3_LINK_STATE_U0 = 0x00, /* in HS, means ON */ + DWC3_LINK_STATE_U1 = 0x01, + DWC3_LINK_STATE_U2 = 0x02, /* in HS, means SLEEP */ + DWC3_LINK_STATE_U3 = 0x03, /* in HS, means SUSPEND */ + DWC3_LINK_STATE_SS_DIS = 0x04, + DWC3_LINK_STATE_RX_DET = 0x05, /* in HS, means Early Suspend */ + DWC3_LINK_STATE_SS_INACT = 0x06, + DWC3_LINK_STATE_POLL = 0x07, + DWC3_LINK_STATE_RECOV = 0x08, + DWC3_LINK_STATE_HRESET = 0x09, + DWC3_LINK_STATE_CMPLY = 0x0a, + DWC3_LINK_STATE_LPBK = 0x0b, + DWC3_LINK_STATE_RESET = 0x0e, + DWC3_LINK_STATE_RESUME = 0x0f, + DWC3_LINK_STATE_MASK = 0x0f, +}; + +/* TRB Length, PCM and Status */ +#define DWC3_TRB_SIZE_MASK (0x00ffffff) +#define DWC3_TRB_SIZE_LENGTH(n) ((n) & DWC3_TRB_SIZE_MASK) +#define DWC3_TRB_SIZE_PCM1(n) (((n) & 0x03) << 24) +#define DWC3_TRB_SIZE_TRBSTS(n) (((n) & (0x0f << 28)) >> 28) + +#define DWC3_TRBSTS_OK 0 +#define DWC3_TRBSTS_MISSED_ISOC 1 +#define DWC3_TRBSTS_SETUP_PENDING 2 +#define DWC3_TRB_STS_XFER_IN_PROG 4 + +/* TRB Control */ +#define DWC3_TRB_CTRL_HWO (1 << 0) +#define DWC3_TRB_CTRL_LST (1 << 1) +#define DWC3_TRB_CTRL_CHN (1 << 2) +#define DWC3_TRB_CTRL_CSP (1 << 3) +#define DWC3_TRB_CTRL_TRBCTL(n) (((n) & 0x3f) << 4) +#define DWC3_TRB_CTRL_ISP_IMI (1 << 10) +#define DWC3_TRB_CTRL_IOC (1 << 11) +#define DWC3_TRB_CTRL_SID_SOFN(n) (((n) & 0xffff) << 14) + +#define DWC3_TRBCTL_NORMAL DWC3_TRB_CTRL_TRBCTL(1) +#define DWC3_TRBCTL_CONTROL_SETUP DWC3_TRB_CTRL_TRBCTL(2) +#define DWC3_TRBCTL_CONTROL_STATUS2 DWC3_TRB_CTRL_TRBCTL(3) +#define DWC3_TRBCTL_CONTROL_STATUS3 DWC3_TRB_CTRL_TRBCTL(4) +#define DWC3_TRBCTL_CONTROL_DATA DWC3_TRB_CTRL_TRBCTL(5) +#define DWC3_TRBCTL_ISOCHRONOUS_FIRST DWC3_TRB_CTRL_TRBCTL(6) +#define DWC3_TRBCTL_ISOCHRONOUS DWC3_TRB_CTRL_TRBCTL(7) +#define DWC3_TRBCTL_LINK_TRB DWC3_TRB_CTRL_TRBCTL(8) + +/** + * struct dwc3_trb - transfer request block (hw format) + * @bpl: DW0-3 + * @bph: DW4-7 + * @size: DW8-B + * @trl: DWC-F + */ +struct dwc3_trb { + u32 bpl; + u32 bph; + u32 size; + u32 ctrl; +} __packed; + +/** + * dwc3_hwparams - copy of HWPARAMS registers + * @hwparams0 - GHWPARAMS0 + * @hwparams1 - GHWPARAMS1 + * @hwparams2 - GHWPARAMS2 + * @hwparams3 - GHWPARAMS3 + * @hwparams4 - GHWPARAMS4 + * @hwparams5 - GHWPARAMS5 + * @hwparams6 - GHWPARAMS6 + * @hwparams7 - GHWPARAMS7 + * @hwparams8 - GHWPARAMS8 + */ +struct dwc3_hwparams { + u32 hwparams0; + u32 hwparams1; + u32 hwparams2; + u32 hwparams3; + u32 hwparams4; + u32 hwparams5; + u32 hwparams6; + u32 hwparams7; + u32 hwparams8; +}; + +/* HWPARAMS0 */ +#define DWC3_MODE(n) ((n) & 0x7) + +#define DWC3_MDWIDTH(n) (((n) & 0xff00) >> 8) + +/* HWPARAMS1 */ +#define DWC3_NUM_INT(n) (((n) & (0x3f << 15)) >> 15) + +/* HWPARAMS3 */ +#define DWC3_NUM_IN_EPS_MASK (0x1f << 18) +#define DWC3_NUM_EPS_MASK (0x3f << 12) +#define DWC3_NUM_EPS(p) (((p)->hwparams3 & \ + (DWC3_NUM_EPS_MASK)) >> 12) +#define DWC3_NUM_IN_EPS(p) (((p)->hwparams3 & \ + (DWC3_NUM_IN_EPS_MASK)) >> 18) + +/* HWPARAMS7 */ +#define DWC3_RAM1_DEPTH(n) ((n) & 0xffff) + +struct dwc3_request { + struct usb_request request; + struct list_head list; + struct dwc3_ep *dep; + u32 start_slot; + + u8 epnum; + struct dwc3_trb *trb; + dma_addr_t trb_dma; + + unsigned direction:1; + unsigned mapped:1; + unsigned queued:1; +}; + +/* + * struct dwc3_scratchpad_array - hibernation scratchpad array + * (format defined by hw) + */ +struct dwc3_scratchpad_array { + __le64 dma_adr[DWC3_MAX_HIBER_SCRATCHBUFS]; +}; + +#define DWC3_CONTROLLER_ERROR_EVENT 0 +#define DWC3_CONTROLLER_RESET_EVENT 1 +#define DWC3_CONTROLLER_POST_RESET_EVENT 2 +#define DWC3_CORE_PM_SUSPEND_EVENT 3 +#define DWC3_CORE_PM_RESUME_EVENT 4 +#define DWC3_CONTROLLER_CONNDONE_EVENT 5 +#define DWC3_CONTROLLER_NOTIFY_OTG_EVENT 6 +#define DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT 7 +#define DWC3_CONTROLLER_RESTART_USB_SESSION 8 +#define DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER 9 + +#define MAX_INTR_STATS 10 +/** + * struct dwc3 - representation of our controller + * @ctrl_req: usb control request which is used for ep0 + * @ep0_trb: trb which is used for the ctrl_req + * @ep0_bounce: bounce buffer for ep0 + * @zlp_buf: used when request->zero is set + * @setup_buf: used while precessing STD USB requests + * @ctrl_req_addr: dma address of ctrl_req + * @ep0_trb: dma address of ep0_trb + * @ep0_usb_req: dummy req used while handling STD USB requests + * @ep0_bounce_addr: dma address of ep0_bounce + * @scratch_addr: dma address of scratchbuf + * @lock: for synchronizing + * @dev: pointer to our struct device + * @xhci: pointer to our xHCI child + * @event_buffer_list: a list of event buffers + * @gadget: device side representation of the peripheral controller + * @gadget_driver: pointer to the gadget driver + * @regs: base address for our registers + * @regs_size: address space size + * @reg_phys: physical base address of dwc3 core register address space + * @nr_scratch: number of scratch buffers + * @num_event_buffers: calculated number of event buffers + * @u1u2: only used on revisions <1.83a for workaround + * @maximum_speed: maximum speed to operate as requested by sw + * @max_hw_supp_speed: maximum speed supported by hw design + * @revision: revision register contents + * @dr_mode: requested mode of operation + * @usb2_phy: pointer to USB2 PHY + * @usb3_phy: pointer to USB3 PHY + * @usb2_generic_phy: pointer to USB2 PHY + * @usb3_generic_phy: pointer to USB3 PHY + * @ulpi: pointer to ulpi interface + * @dcfg: saved contents of DCFG register + * @gctl: saved contents of GCTL register + * @isoch_delay: wValue from Set Isochronous Delay request; + * @u2sel: parameter from Set SEL request. + * @u2pel: parameter from Set SEL request. + * @u1sel: parameter from Set SEL request. + * @u1pel: parameter from Set SEL request. + * @num_out_eps: number of out endpoints + * @num_in_eps: number of in endpoints + * @ep0_next_event: hold the next expected event + * @ep0state: state of endpoint zero + * @link_state: link state + * @speed: device speed (super, high, full, low) + * @mem: points to start of memory which is used for this struct. + * @hwparams: copy of hwparams registers + * @root: debugfs root folder pointer + * @regset: debugfs pointer to regdump file + * @test_mode: true when we're entering a USB test mode + * @test_mode_nr: test feature selector + * @lpm_nyet_threshold: LPM NYET response threshold + * @hird_threshold: HIRD threshold + * @hsphy_interface: "utmi" or "ulpi" + * @delayed_status: true when gadget driver asks for delayed status + * @ep0_bounced: true when we used bounce buffer + * @ep0_expect_in: true when we expect a DATA IN transfer + * @has_hibernation: true when dwc3 was configured with Hibernation + * @has_lpm_erratum: true when core was configured with LPM Erratum. Note that + * there's now way for software to detect this in runtime. + * @is_utmi_l1_suspend: the core asserts output signal + * 0 - utmi_sleep_n + * 1 - utmi_l1_suspend_n + * @is_fpga: true when we are using the FPGA board + * @needs_fifo_resize: not all users might want fifo resizing, flag it + * @pullups_connected: true when Run/Stop bit is set + * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround + * @start_config_issued: true when StartConfig command has been issued + * @three_stage_setup: set if we perform a three phase setup + * @usb3_lpm_capable: set if hadrware supports Link Power Management + * @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 + * @req_p1p2p3_quirk: set if we enable request p1p2p3 quirk + * @del_p1p2p3_quirk: set if we enable delay p1p2p3 quirk + * @del_phy_power_chg_quirk: set if we enable delay phy power change quirk + * @lfps_filter_quirk: set if we enable LFPS filter quirk + * @rx_detect_poll_quirk: set if we enable rx_detect to polling lfps quirk + * @dis_u3_susphy_quirk: set if we disable usb3 suspend phy + * @dis_u2_susphy_quirk: set if we disable usb2 suspend phy + * @dis_enblslpm_quirk: set if we clear enblslpm in GUSB2PHYCFG, + * disabling the suspend signal to the PHY. + * @tx_de_emphasis_quirk: set if we enable Tx de-emphasis quirk + * @tx_de_emphasis: Tx de-emphasis value + * 0 - -6dB de-emphasis + * 1 - -3.5dB de-emphasis + * 2 - No de-emphasis + * 3 - Reserved + * @is_drd: device supports dual-role or not + * @err_evt_seen: previous event in queue was erratic error + * @usb3_u1u2_disable: if true, disable U1U2 low power modes in Superspeed mode. + * @in_lpm: indicates if controller is in low power mode (no clocks) + * @tx_fifo_size: Available RAM size for TX fifo allocation + * @irq: irq number + * @bh: tasklet which handles the interrupt + * @irq_cnt: total irq count + * @last_irq_cnt: last irq count + * @bh_completion_time: time taken for taklet completion + * @bh_handled_evt_cnt: no. of events handled by tasklet per interrupt + * @bh_dbg_index: index for capturing bh_completion_time and bh_handled_evt_cnt + * @wait_linkstate: waitqueue for waiting LINK to move into required state + * @vbus_draw: current to be drawn from USB + * @imod_interval: set the interrupt moderation interval in 250ns + * increments or 0 to disable. + * @create_reg_debugfs: create debugfs entry to allow dwc3 register dump + * @last_fifo_depth: total TXFIFO depth of all enabled USB IN/INT endpoints + */ +struct dwc3 { + struct usb_ctrlrequest *ctrl_req; + struct dwc3_trb *ep0_trb; + void *ep0_bounce; + void *zlp_buf; + void *scratchbuf; + u8 *setup_buf; + dma_addr_t ctrl_req_addr; + dma_addr_t ep0_trb_addr; + dma_addr_t ep0_bounce_addr; + dma_addr_t scratch_addr; + struct dwc3_request ep0_usb_req; + + /* device lock */ + spinlock_t lock; + + struct device *dev; + + struct platform_device *xhci; + struct resource xhci_resources[DWC3_XHCI_RESOURCES_NUM]; + + struct dwc3_event_buffer **ev_buffs; + struct dwc3_ep *eps[DWC3_ENDPOINTS_NUM]; + + struct usb_gadget gadget; + struct usb_gadget_driver *gadget_driver; + + struct usb_phy *usb2_phy; + struct usb_phy *usb3_phy; + + struct phy *usb2_generic_phy; + struct phy *usb3_generic_phy; + + struct ulpi *ulpi; + + void __iomem *regs; + size_t regs_size; + phys_addr_t reg_phys; + + enum usb_dr_mode dr_mode; + + /* used for suspend/resume */ + u32 dcfg; + u32 gctl; + + u32 nr_scratch; + u32 num_event_buffers; + u32 num_normal_event_buffers; + u32 num_gsi_event_buffers; + + u32 u1; + u32 u1u2; + u32 maximum_speed; + u32 max_hw_supp_speed; + + /* + * All 3.1 IP version constants are greater than the 3.0 IP + * version constants. This works for most version checks in + * dwc3. However, in the future, this may not apply as + * features may be developed on newer versions of the 3.0 IP + * that are not in the 3.1 IP. + */ + u32 revision; + +#define DWC3_REVISION_173A 0x5533173a +#define DWC3_REVISION_175A 0x5533175a +#define DWC3_REVISION_180A 0x5533180a +#define DWC3_REVISION_183A 0x5533183a +#define DWC3_REVISION_185A 0x5533185a +#define DWC3_REVISION_187A 0x5533187a +#define DWC3_REVISION_188A 0x5533188a +#define DWC3_REVISION_190A 0x5533190a +#define DWC3_REVISION_194A 0x5533194a +#define DWC3_REVISION_200A 0x5533200a +#define DWC3_REVISION_202A 0x5533202a +#define DWC3_REVISION_210A 0x5533210a +#define DWC3_REVISION_220A 0x5533220a +#define DWC3_REVISION_230A 0x5533230a +#define DWC3_REVISION_240A 0x5533240a +#define DWC3_REVISION_250A 0x5533250a +#define DWC3_REVISION_260A 0x5533260a +#define DWC3_REVISION_270A 0x5533270a +#define DWC3_REVISION_280A 0x5533280a +#define DWC3_REVISION_300A 0x5533300a +#define DWC3_REVISION_310A 0x5533310a + +/* + * NOTICE: we're using bit 31 as a "is usb 3.1" flag. This is really + * just so dwc31 revisions are always larger than dwc3. + */ +#define DWC3_REVISION_IS_DWC31 0x80000000 +#define DWC3_USB31_REVISION_110A (0x3131302a | DWC3_REVISION_IS_USB31) +#define DWC3_USB31_REVISION_120A (0x3132302a | DWC3_REVISION_IS_DWC31) + + enum dwc3_ep0_next ep0_next_event; + enum dwc3_ep0_state ep0state; + enum dwc3_link_state link_state; + + u16 isoch_delay; + u16 u2sel; + u16 u2pel; + u8 u1sel; + u8 u1pel; + + u8 speed; + + u8 num_out_eps; + u8 num_in_eps; + + void *mem; + + struct dwc3_hwparams hwparams; + struct dentry *root; + struct debugfs_regset32 *regset; + + u8 test_mode; + u8 test_mode_nr; + u8 lpm_nyet_threshold; + u8 hird_threshold; + + const char *hsphy_interface; + + void (*notify_event)(struct dwc3 *, unsigned, unsigned); + struct work_struct wakeup_work; + + unsigned delayed_status:1; + unsigned ep0_bounced:1; + unsigned ep0_expect_in:1; + unsigned has_hibernation:1; + unsigned has_lpm_erratum:1; + unsigned is_utmi_l1_suspend:1; + unsigned is_fpga:1; + unsigned needs_fifo_resize:1; + unsigned pullups_connected:1; + unsigned setup_packet_pending:1; + unsigned three_stage_setup:1; + unsigned usb3_lpm_capable:1; + + unsigned disable_scramble_quirk: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 tx_de_emphasis_quirk:1; + unsigned tx_de_emphasis:2; + + unsigned is_drd:1; + /* Indicate if the gadget was powered by the otg driver */ + unsigned vbus_active:1; + /* Indicate if software connect was issued by the usb_gadget_driver */ + unsigned softconnect:1; + unsigned nominal_elastic_buffer:1; + unsigned err_evt_seen:1; + unsigned usb3_u1u2_disable:1; + /* Indicate if need to disable controller internal clkgating */ + unsigned disable_clk_gating:1; + unsigned enable_bus_suspend:1; + + struct dwc3_gadget_events dbg_gadget_events; + + atomic_t in_lpm; + int tx_fifo_size; + bool b_suspend; + unsigned vbus_draw; + + u16 imod_interval; + + struct workqueue_struct *dwc_wq; + struct work_struct bh_work; + + /* IRQ timing statistics */ + int irq; + unsigned long irq_cnt; + unsigned long last_irq_cnt; + unsigned long ep_cmd_timeout_cnt; + unsigned bh_completion_time[MAX_INTR_STATS]; + unsigned bh_handled_evt_cnt[MAX_INTR_STATS]; + unsigned bh_dbg_index; + ktime_t irq_start_time[MAX_INTR_STATS]; + ktime_t t_pwr_evt_irq; + unsigned irq_completion_time[MAX_INTR_STATS]; + unsigned irq_event_count[MAX_INTR_STATS]; + unsigned irq_dbg_index; + + unsigned long l1_remote_wakeup_cnt; + + wait_queue_head_t wait_linkstate; + bool create_reg_debugfs; + int last_fifo_depth; +}; + +/* -------------------------------------------------------------------------- */ + +/* -------------------------------------------------------------------------- */ + +struct dwc3_event_type { + u32 is_devspec:1; + u32 type:7; + u32 reserved8_31:24; +} __packed; + +#define DWC3_DEPEVT_XFERCOMPLETE 0x01 +#define DWC3_DEPEVT_XFERINPROGRESS 0x02 +#define DWC3_DEPEVT_XFERNOTREADY 0x03 +#define DWC3_DEPEVT_RXTXFIFOEVT 0x04 +#define DWC3_DEPEVT_STREAMEVT 0x06 +#define DWC3_DEPEVT_EPCMDCMPLT 0x07 + +/** + * struct dwc3_event_depvt - Device Endpoint Events + * @one_bit: indicates this is an endpoint event (not used) + * @endpoint_number: number of the endpoint + * @endpoint_event: The event we have: + * 0x00 - Reserved + * 0x01 - XferComplete + * 0x02 - XferInProgress + * 0x03 - XferNotReady + * 0x04 - RxTxFifoEvt (IN->Underrun, OUT->Overrun) + * 0x05 - Reserved + * 0x06 - StreamEvt + * 0x07 - EPCmdCmplt + * @reserved11_10: Reserved, don't use. + * @status: Indicates the status of the event. Refer to databook for + * more information. + * @parameters: Parameters of the current event. Refer to databook for + * more information. + */ +struct dwc3_event_depevt { + u32 one_bit:1; + u32 endpoint_number:5; + u32 endpoint_event:4; + u32 reserved11_10:2; + u32 status:4; + +/* Within XferNotReady */ +#define DEPEVT_STATUS_TRANSFER_ACTIVE (1 << 3) + +/* Within XferComplete */ +#define DEPEVT_STATUS_BUSERR (1 << 0) +#define DEPEVT_STATUS_SHORT (1 << 1) +#define DEPEVT_STATUS_IOC (1 << 2) +#define DEPEVT_STATUS_LST (1 << 3) + +/* Stream event only */ +#define DEPEVT_STREAMEVT_FOUND 1 +#define DEPEVT_STREAMEVT_NOTFOUND 2 + +/* Control-only Status */ +#define DEPEVT_STATUS_CONTROL_DATA 1 +#define DEPEVT_STATUS_CONTROL_STATUS 2 + + u32 parameters:16; +} __packed; + +/** + * struct dwc3_event_devt - Device Events + * @one_bit: indicates this is a non-endpoint event (not used) + * @device_event: indicates it's a device event. Should read as 0x00 + * @type: indicates the type of device event. + * 0 - DisconnEvt + * 1 - USBRst + * 2 - ConnectDone + * 3 - ULStChng + * 4 - WkUpEvt + * 5 - Reserved + * 6 - EOPF + * 7 - SOF + * 8 - Reserved + * 9 - ErrticErr + * 10 - CmdCmplt + * 11 - EvntOverflow + * 12 - VndrDevTstRcved + * @reserved15_12: Reserved, not used + * @event_info: Information about this event + * @reserved31_25: Reserved, not used + */ +struct dwc3_event_devt { + u32 one_bit:1; + u32 device_event:7; + u32 type:4; + u32 reserved15_12:4; + u32 event_info:9; + u32 reserved31_25:7; +} __packed; + +/** + * struct dwc3_event_gevt - Other Core Events + * @one_bit: indicates this is a non-endpoint event (not used) + * @device_event: indicates it's (0x03) Carkit or (0x04) I2C event. + * @phy_port_number: self-explanatory + * @reserved31_12: Reserved, not used. + */ +struct dwc3_event_gevt { + u32 one_bit:1; + u32 device_event:7; + u32 phy_port_number:4; + u32 reserved31_12:20; +} __packed; + +/** + * union dwc3_event - representation of Event Buffer contents + * @raw: raw 32-bit event + * @type: the type of the event + * @depevt: Device Endpoint Event + * @devt: Device Event + * @gevt: Global Event + */ +union dwc3_event { + u32 raw; + struct dwc3_event_type type; + struct dwc3_event_depevt depevt; + struct dwc3_event_devt devt; + struct dwc3_event_gevt gevt; +}; + +/** + * struct dwc3_gadget_ep_cmd_params - representation of endpoint command + * parameters + * @param2: third parameter + * @param1: second parameter + * @param0: first parameter + */ +struct dwc3_gadget_ep_cmd_params { + u32 param2; + u32 param1; + u32 param0; +}; + +/* + * DWC3 Features to be used as Driver Data + */ + +#define DWC3_HAS_PERIPHERAL BIT(0) +#define DWC3_HAS_XHCI BIT(1) +#define DWC3_HAS_OTG BIT(3) + +/* prototypes */ +void dwc3_set_mode(struct dwc3 *dwc, u32 mode); +int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc, struct dwc3_ep *dep); + +/* check whether we are on the DWC_usb3 core */ +static inline bool dwc3_is_usb3(struct dwc3 *dwc) +{ + return !(dwc->revision & DWC3_REVISION_IS_DWC31); +} + +/* check whether we are on the DWC_usb31 core */ +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_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_host_init(struct dwc3 *dwc); +void dwc3_host_exit(struct dwc3 *dwc); +#else +static inline int dwc3_host_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_host_exit(struct dwc3 *dwc) +{ } +#endif + +#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +int dwc3_gadget_init(struct dwc3 *dwc); +void dwc3_gadget_exit(struct dwc3 *dwc); +void dwc3_gadget_restart(struct dwc3 *dwc); +int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode); +int dwc3_gadget_get_link_state(struct dwc3 *dwc); +int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state); +int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params); +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param); +void dwc3_gadget_enable_irq(struct dwc3 *dwc); +void dwc3_gadget_disable_irq(struct dwc3 *dwc); +#else +static inline int dwc3_gadget_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_gadget_exit(struct dwc3 *dwc) +{ } +static inline void dwc3_gadget_restart(struct dwc3 *dwc) +{ } +static inline int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) +{ return 0; } +static inline int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ return 0; } +static inline int dwc3_gadget_set_link_state(struct dwc3 *dwc, + enum dwc3_link_state state) +{ return 0; } + +static inline int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) +{ return 0; } +static inline int dwc3_send_gadget_generic_command(struct dwc3 *dwc, + int cmd, u32 param) +{ return 0; } +static inline void dwc3_gadget_enable_irq(struct dwc3 *dwc) +{ } +static inline void dwc3_gadget_disable_irq(struct dwc3 *dwc) +{ } +#endif + +/* power management interface */ +#if !IS_ENABLED(CONFIG_USB_DWC3_HOST) +int dwc3_gadget_suspend(struct dwc3 *dwc); +int dwc3_gadget_resume(struct dwc3 *dwc); +#else +static inline int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + return 0; +} + +static inline int dwc3_gadget_resume(struct dwc3 *dwc) +{ + return 0; +} +#endif /* !IS_ENABLED(CONFIG_USB_DWC3_HOST) */ + + +#if IS_ENABLED(CONFIG_USB_DWC3_ULPI) +int dwc3_ulpi_init(struct dwc3 *dwc); +void dwc3_ulpi_exit(struct dwc3 *dwc); +#else +static inline int dwc3_ulpi_init(struct dwc3 *dwc) +{ return 0; } +static inline void dwc3_ulpi_exit(struct dwc3 *dwc) +{ } +#endif + + +int dwc3_core_init(struct dwc3 *dwc); +int dwc3_core_pre_init(struct dwc3 *dwc); +void dwc3_post_host_reset_core_init(struct dwc3 *dwc); +int dwc3_event_buffers_setup(struct dwc3 *dwc); +void dwc3_usb3_phy_suspend(struct dwc3 *dwc, int suspend); + +extern void dwc3_set_notifier( + void (*notify)(struct dwc3 *dwc3, unsigned event, unsigned value)); +extern int dwc3_notify_event(struct dwc3 *dwc3, unsigned event, unsigned value); + +#endif /* __DRIVERS_USB_DWC3_CORE_H */ diff --git a/drivers/usb/dwc3/dbm.c b/drivers/usb/dwc3/dbm.c new file mode 100644 index 000000000000..cc7fb4026fb8 --- /dev/null +++ b/drivers/usb/dwc3/dbm.c @@ -0,0 +1,643 @@ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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/platform_device.h> +#include <linux/of.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include "dbm.h" + +/** +* USB DBM Hardware registers. +* +*/ +enum dbm_reg { + DBM_EP_CFG, + DBM_DATA_FIFO, + DBM_DATA_FIFO_SIZE, + DBM_DATA_FIFO_EN, + DBM_GEVNTADR, + DBM_GEVNTSIZ, + DBM_DBG_CNFG, + DBM_HW_TRB0_EP, + DBM_HW_TRB1_EP, + DBM_HW_TRB2_EP, + DBM_HW_TRB3_EP, + DBM_PIPE_CFG, + DBM_DISABLE_UPDXFER, + DBM_SOFT_RESET, + DBM_GEN_CFG, + DBM_GEVNTADR_LSB, + DBM_GEVNTADR_MSB, + DBM_DATA_FIFO_LSB, + DBM_DATA_FIFO_MSB, + DBM_DATA_FIFO_ADDR_EN, + DBM_DATA_FIFO_SIZE_EN, +}; + +struct dbm_reg_data { + u32 offset; + unsigned int ep_mult; +}; + +#define DBM_1_4_NUM_EP 4 +#define DBM_1_5_NUM_EP 8 + +struct dbm { + void __iomem *base; + const struct dbm_reg_data *reg_table; + + struct device *dev; + struct list_head head; + + int dbm_num_eps; + u8 ep_num_mapping[DBM_1_5_NUM_EP]; + bool dbm_reset_ep_after_lpm; + + bool is_1p4; +}; + +static const struct dbm_reg_data dbm_1_4_regtable[] = { + [DBM_EP_CFG] = { 0x0000, 0x4 }, + [DBM_DATA_FIFO] = { 0x0010, 0x4 }, + [DBM_DATA_FIFO_SIZE] = { 0x0020, 0x4 }, + [DBM_DATA_FIFO_EN] = { 0x0030, 0x0 }, + [DBM_GEVNTADR] = { 0x0034, 0x0 }, + [DBM_GEVNTSIZ] = { 0x0038, 0x0 }, + [DBM_DBG_CNFG] = { 0x003C, 0x0 }, + [DBM_HW_TRB0_EP] = { 0x0040, 0x4 }, + [DBM_HW_TRB1_EP] = { 0x0050, 0x4 }, + [DBM_HW_TRB2_EP] = { 0x0060, 0x4 }, + [DBM_HW_TRB3_EP] = { 0x0070, 0x4 }, + [DBM_PIPE_CFG] = { 0x0080, 0x0 }, + [DBM_SOFT_RESET] = { 0x0084, 0x0 }, + [DBM_GEN_CFG] = { 0x0088, 0x0 }, + [DBM_GEVNTADR_LSB] = { 0x0098, 0x0 }, + [DBM_GEVNTADR_MSB] = { 0x009C, 0x0 }, + [DBM_DATA_FIFO_LSB] = { 0x00A0, 0x8 }, + [DBM_DATA_FIFO_MSB] = { 0x00A4, 0x8 }, +}; + +static const struct dbm_reg_data dbm_1_5_regtable[] = { + [DBM_EP_CFG] = { 0x0000, 0x4 }, + [DBM_DATA_FIFO] = { 0x0280, 0x4 }, + [DBM_DATA_FIFO_SIZE] = { 0x0080, 0x4 }, + [DBM_DATA_FIFO_EN] = { 0x026C, 0x0 }, + [DBM_GEVNTADR] = { 0x0270, 0x0 }, + [DBM_GEVNTSIZ] = { 0x0268, 0x0 }, + [DBM_DBG_CNFG] = { 0x0208, 0x0 }, + [DBM_HW_TRB0_EP] = { 0x0220, 0x4 }, + [DBM_HW_TRB1_EP] = { 0x0230, 0x4 }, + [DBM_HW_TRB2_EP] = { 0x0240, 0x4 }, + [DBM_HW_TRB3_EP] = { 0x0250, 0x4 }, + [DBM_PIPE_CFG] = { 0x0274, 0x0 }, + [DBM_DISABLE_UPDXFER] = { 0x0298, 0x0 }, + [DBM_SOFT_RESET] = { 0x020C, 0x0 }, + [DBM_GEN_CFG] = { 0x0210, 0x0 }, + [DBM_GEVNTADR_LSB] = { 0x0260, 0x0 }, + [DBM_GEVNTADR_MSB] = { 0x0264, 0x0 }, + [DBM_DATA_FIFO_LSB] = { 0x0100, 0x8 }, + [DBM_DATA_FIFO_MSB] = { 0x0104, 0x8 }, + [DBM_DATA_FIFO_ADDR_EN] = { 0x0200, 0x0 }, + [DBM_DATA_FIFO_SIZE_EN] = { 0x0204, 0x0 }, +}; + +static LIST_HEAD(dbm_list); + +/** + * Write register masked field with debug info. + * + * @dbm - DBM specific data + * @reg - DBM register, used to look up the offset value + * @ep - endpoint number + * @mask - register bitmask. + * @val - value to write. + * + */ +static inline void msm_dbm_write_ep_reg_field(struct dbm *dbm, + enum dbm_reg reg, int ep, + const u32 mask, u32 val) +{ + u32 shift = find_first_bit((void *)&mask, 32); + u32 offset = dbm->reg_table[reg].offset + + (dbm->reg_table[reg].ep_mult * ep); + u32 tmp = ioread32(dbm->base + offset); + + tmp &= ~mask; /* clear written bits */ + val = tmp | (val << shift); + iowrite32(val, dbm->base + offset); +} + +#define msm_dbm_write_reg_field(d, r, m, v) \ + msm_dbm_write_ep_reg_field(d, r, 0, m, v) + +/** + * + * Read register with debug info. + * + * @dbm - DBM specific data + * @reg - DBM register, used to look up the offset value + * @ep - endpoint number + * + * @return u32 + */ +static inline u32 msm_dbm_read_ep_reg(struct dbm *dbm, enum dbm_reg reg, int ep) +{ + u32 offset = dbm->reg_table[reg].offset + + (dbm->reg_table[reg].ep_mult * ep); + return ioread32(dbm->base + offset); +} + +#define msm_dbm_read_reg(d, r) msm_dbm_read_ep_reg(d, r, 0) + +/** + * + * Write register with debug info. + * + * @dbm - DBM specific data + * @reg - DBM register, used to look up the offset value + * @ep - endpoint number + * + */ +static inline void msm_dbm_write_ep_reg(struct dbm *dbm, enum dbm_reg reg, + int ep, u32 val) +{ + u32 offset = dbm->reg_table[reg].offset + + (dbm->reg_table[reg].ep_mult * ep); + iowrite32(val, dbm->base + offset); +} + +#define msm_dbm_write_reg(d, r, v) msm_dbm_write_ep_reg(d, r, 0, v) + +/** + * Return DBM EP number according to usb endpoint number. + * + */ +static int find_matching_dbm_ep(struct dbm *dbm, u8 usb_ep) +{ + int i; + + for (i = 0; i < dbm->dbm_num_eps; i++) + if (dbm->ep_num_mapping[i] == usb_ep) + return i; + + pr_debug("%s: No DBM EP matches USB EP %d", __func__, usb_ep); + return -ENODEV; /* Not found */ +} + + +/** + * Reset the DBM registers upon initialization. + * + */ +int dbm_soft_reset(struct dbm *dbm, bool reset) +{ + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + pr_debug("%s DBM reset\n", (reset ? "Enter" : "Exit")); + + msm_dbm_write_reg_field(dbm, DBM_SOFT_RESET, DBM_SFT_RST_MASK, reset); + + return 0; +} + +/** + * Soft reset specific DBM ep. + * This function is called by the function driver upon events + * such as transfer aborting, USB re-enumeration and USB + * disconnection. + * + * @dbm_ep - DBM ep number. + * @enter_reset - should we enter a reset state or get out of it. + * + */ +static int ep_soft_reset(struct dbm *dbm, u8 dbm_ep, bool enter_reset) +{ + pr_debug("Setting DBM ep %d reset to %d\n", dbm_ep, enter_reset); + + if (dbm_ep >= dbm->dbm_num_eps) { + pr_err("Invalid DBM ep index %d\n", dbm_ep); + return -ENODEV; + } + + if (enter_reset) { + msm_dbm_write_reg_field(dbm, DBM_SOFT_RESET, + DBM_SFT_RST_EPS_MASK & 1 << dbm_ep, 1); + } else { + msm_dbm_write_reg_field(dbm, DBM_SOFT_RESET, + DBM_SFT_RST_EPS_MASK & 1 << dbm_ep, 0); + } + + return 0; +} + + +/** + * Soft reset specific DBM ep (by USB EP number). + * This function is called by the function driver upon events + * such as transfer aborting, USB re-enumeration and USB + * disconnection. + * + * The function relies on ep_soft_reset() for checking + * the legality of the resulting DBM ep number. + * + * @usb_ep - USB ep number. + * @enter_reset - should we enter a reset state or get out of it. + * + */ +int dbm_ep_soft_reset(struct dbm *dbm, u8 usb_ep, bool enter_reset) +{ + int dbm_ep; + + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + dbm_ep = find_matching_dbm_ep(dbm, usb_ep); + + pr_debug("Setting USB ep %d reset to %d\n", usb_ep, enter_reset); + return ep_soft_reset(dbm, dbm_ep, enter_reset); +} + +/** + * Configure a USB DBM ep to work in BAM mode. + * + * + * @usb_ep - USB physical EP number. + * @producer - producer/consumer. + * @disable_wb - disable write back to system memory. + * @internal_mem - use internal USB memory for data fifo. + * @ioc - enable interrupt on completion. + * + * @return int - DBM ep number. + */ +int dbm_ep_config(struct dbm *dbm, u8 usb_ep, u8 bam_pipe, bool producer, + bool disable_wb, bool internal_mem, bool ioc) +{ + int dbm_ep; + u32 ep_cfg; + u32 data; + + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + pr_debug("Configuring DBM ep\n"); + + dbm_ep = find_matching_dbm_ep(dbm, usb_ep); + + if (dbm_ep < 0) { + pr_err("usb ep index %d has no corresponding dbm ep\n", usb_ep); + return -ENODEV; + } + + /* Due to HW issue, EP 7 can be set as IN EP only */ + if (!dbm->is_1p4 && dbm_ep == 7 && producer) { + pr_err("last DBM EP can't be OUT EP\n"); + return -ENODEV; + } + + /* Set ioc bit for dbm_ep if needed */ + msm_dbm_write_reg_field(dbm, DBM_DBG_CNFG, + DBM_ENABLE_IOC_MASK & 1 << dbm_ep, ioc ? 1 : 0); + + ep_cfg = (producer ? DBM_PRODUCER : 0) | + (disable_wb ? DBM_DISABLE_WB : 0) | + (internal_mem ? DBM_INT_RAM_ACC : 0); + + msm_dbm_write_ep_reg_field(dbm, DBM_EP_CFG, dbm_ep, + DBM_PRODUCER | DBM_DISABLE_WB | DBM_INT_RAM_ACC, ep_cfg >> 8); + + msm_dbm_write_ep_reg_field(dbm, DBM_EP_CFG, dbm_ep, USB3_EPNUM, + usb_ep); + + if (dbm->is_1p4) { + msm_dbm_write_ep_reg_field(dbm, DBM_EP_CFG, dbm_ep, + DBM_BAM_PIPE_NUM, bam_pipe); + msm_dbm_write_reg_field(dbm, DBM_PIPE_CFG, 0x000000ff, 0xe4); + } + + msm_dbm_write_ep_reg_field(dbm, DBM_EP_CFG, dbm_ep, DBM_EN_EP, 1); + + data = msm_dbm_read_reg(dbm, DBM_DISABLE_UPDXFER); + data &= ~(0x1 << dbm_ep); + msm_dbm_write_reg(dbm, DBM_DISABLE_UPDXFER, data); + + return dbm_ep; +} + +/** + * Return number of configured DBM endpoints. + */ +int dbm_get_num_of_eps_configured(struct dbm *dbm) +{ + int i; + int count = 0; + + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + for (i = 0; i < dbm->dbm_num_eps; i++) + if (dbm->ep_num_mapping[i]) + count++; + + return count; +} + +/** + * Configure a USB DBM ep to work in normal mode. + * + * @usb_ep - USB ep number. + * + */ +int dbm_ep_unconfig(struct dbm *dbm, u8 usb_ep) +{ + int dbm_ep; + u32 data; + + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + pr_debug("Unconfiguring DB ep\n"); + + dbm_ep = find_matching_dbm_ep(dbm, usb_ep); + + if (dbm_ep < 0) { + pr_debug("usb ep index %d has no corespondng dbm ep\n", usb_ep); + return -ENODEV; + } + + dbm->ep_num_mapping[dbm_ep] = 0; + + data = msm_dbm_read_ep_reg(dbm, DBM_EP_CFG, dbm_ep); + data &= (~0x1); + msm_dbm_write_ep_reg(dbm, DBM_EP_CFG, dbm_ep, data); + + /* + * ep_soft_reset is not required during disconnect as pipe reset on + * next connect will take care of the same. + */ + return 0; +} + +/** + * Configure the DBM with the USB3 core event buffer. + * This function is called by the SNPS UDC upon initialization. + * + * @addr - address of the event buffer. + * @size - size of the event buffer. + * + */ +int dbm_event_buffer_config(struct dbm *dbm, u32 addr_lo, u32 addr_hi, int size) +{ + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + pr_debug("Configuring event buffer\n"); + + if (size < 0) { + pr_err("Invalid size. size = %d", size); + return -EINVAL; + } + + /* In case event buffer is already configured, Do nothing. */ + if (msm_dbm_read_reg(dbm, DBM_GEVNTSIZ)) + return 0; + + if (!dbm->is_1p4 || sizeof(phys_addr_t) > sizeof(u32)) { + msm_dbm_write_reg(dbm, DBM_GEVNTADR_LSB, addr_lo); + msm_dbm_write_reg(dbm, DBM_GEVNTADR_MSB, addr_hi); + } else { + msm_dbm_write_reg(dbm, DBM_GEVNTADR, addr_lo); + } + + msm_dbm_write_reg_field(dbm, DBM_GEVNTSIZ, DBM_GEVNTSIZ_MASK, size); + + return 0; +} + +/** + * Disable update xfer before queueing stop xfer command to USB3 core. + * + * @usb_ep - USB physical EP number. + * + */ +int dwc3_dbm_disable_update_xfer(struct dbm *dbm, u8 usb_ep) +{ + u32 data; + u8 dbm_ep; + + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + dbm_ep = find_matching_dbm_ep(dbm, usb_ep); + + if (dbm_ep < 0) { + pr_err("usb ep index %d has no corresponding dbm ep\n", usb_ep); + return -ENODEV; + } + + data = msm_dbm_read_reg(dbm, DBM_DISABLE_UPDXFER); + data |= (0x1 << dbm_ep); + msm_dbm_write_reg(dbm, DBM_DISABLE_UPDXFER, data); + + return 0; +} + +int dbm_data_fifo_config(struct dbm *dbm, u8 dep_num, phys_addr_t addr, + u32 size, u8 dst_pipe_idx) +{ + u8 dbm_ep = dst_pipe_idx; + u32 lo = lower_32_bits(addr); + u32 hi = upper_32_bits(addr); + + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return -EPERM; + } + + dbm->ep_num_mapping[dbm_ep] = dep_num; + + if (!dbm->is_1p4 || sizeof(addr) > sizeof(u32)) { + msm_dbm_write_ep_reg(dbm, DBM_DATA_FIFO_LSB, dbm_ep, lo); + msm_dbm_write_ep_reg(dbm, DBM_DATA_FIFO_MSB, dbm_ep, hi); + } else { + msm_dbm_write_ep_reg(dbm, DBM_DATA_FIFO, dbm_ep, addr); + } + + msm_dbm_write_ep_reg_field(dbm, DBM_DATA_FIFO_SIZE, dbm_ep, + DBM_DATA_FIFO_SIZE_MASK, size); + + return 0; +} + +void dbm_set_speed(struct dbm *dbm, bool speed) +{ + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return; + } + + msm_dbm_write_reg(dbm, DBM_GEN_CFG, speed); +} + +void dbm_enable(struct dbm *dbm) +{ + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return; + } + + if (dbm->is_1p4) /* no-op */ + return; + + msm_dbm_write_reg(dbm, DBM_DATA_FIFO_ADDR_EN, 0x000000FF); + msm_dbm_write_reg(dbm, DBM_DATA_FIFO_SIZE_EN, 0x000000FF); +} + +bool dbm_reset_ep_after_lpm(struct dbm *dbm) +{ + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return false; + } + + return dbm->dbm_reset_ep_after_lpm; +} + +bool dbm_l1_lpm_interrupt(struct dbm *dbm) +{ + if (!dbm) { + pr_err("%s: dbm pointer is NULL!\n", __func__); + return false; + } + + return !dbm->is_1p4; +} + +static const struct of_device_id msm_dbm_id_table[] = { + { .compatible = "qcom,usb-dbm-1p4", .data = &dbm_1_4_regtable }, + { .compatible = "qcom,usb-dbm-1p5", .data = &dbm_1_5_regtable }, + { }, +}; +MODULE_DEVICE_TABLE(of, msm_dbm_id_table); + +static int msm_dbm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + const struct of_device_id *match; + struct dbm *dbm; + struct resource *res; + + dbm = devm_kzalloc(&pdev->dev, sizeof(*dbm), GFP_KERNEL); + if (!dbm) + return -ENOMEM; + + match = of_match_node(msm_dbm_id_table, node); + if (!match) { + dev_err(&pdev->dev, "Unsupported DBM module\n"); + return -ENODEV; + } + dbm->reg_table = match->data; + + if (!strcmp(match->compatible, "qcom,usb-dbm-1p4")) { + dbm->dbm_num_eps = DBM_1_4_NUM_EP; + dbm->is_1p4 = true; + } else { + dbm->dbm_num_eps = DBM_1_5_NUM_EP; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "missing memory base resource\n"); + return -ENODEV; + } + + dbm->base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!dbm->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + return -ENOMEM; + } + + dbm->dbm_reset_ep_after_lpm = of_property_read_bool(node, + "qcom,reset-ep-after-lpm-resume"); + + dbm->dev = &pdev->dev; + + platform_set_drvdata(pdev, dbm); + + list_add_tail(&dbm->head, &dbm_list); + + return 0; +} + +static struct platform_driver msm_dbm_driver = { + .probe = msm_dbm_probe, + .driver = { + .name = "msm-usb-dbm", + .of_match_table = of_match_ptr(msm_dbm_id_table), + }, +}; + +module_platform_driver(msm_dbm_driver); + +static struct dbm *of_usb_find_dbm(struct device_node *node) +{ + struct dbm *dbm; + + list_for_each_entry(dbm, &dbm_list, head) { + if (node != dbm->dev->of_node) + continue; + return dbm; + } + return ERR_PTR(-ENODEV); +} + +struct dbm *usb_get_dbm_by_phandle(struct device *dev, const char *phandle) +{ + struct device_node *node; + + if (!dev->of_node) { + dev_dbg(dev, "device does not have a device node entry\n"); + return ERR_PTR(-EINVAL); + } + + node = of_parse_phandle(dev->of_node, phandle, 0); + if (!node) { + dev_dbg(dev, "failed to get %s phandle in %s node\n", phandle, + dev->of_node->full_name); + return ERR_PTR(-ENODEV); + } + + return of_usb_find_dbm(node); +} + +MODULE_DESCRIPTION("MSM USB DBM driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/dwc3/dbm.h b/drivers/usb/dwc3/dbm.h new file mode 100644 index 000000000000..bf20d7cbd454 --- /dev/null +++ b/drivers/usb/dwc3/dbm.h @@ -0,0 +1,75 @@ +/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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. + */ + +#ifndef __DBM_H +#define __DBM_H + +#include <linux/device.h> +#include <linux/types.h> + +/** + * USB DBM Hardware registers bitmask. + * + */ +/* DBM_EP_CFG */ +#define DBM_EN_EP 0x00000001 +#define USB3_EPNUM 0x0000003E +#define DBM_BAM_PIPE_NUM 0x000000C0 +#define DBM_PRODUCER 0x00000100 +#define DBM_DISABLE_WB 0x00000200 +#define DBM_INT_RAM_ACC 0x00000400 + +/* DBM_DATA_FIFO_SIZE */ +#define DBM_DATA_FIFO_SIZE_MASK 0x0000ffff + +/* DBM_GEVNTSIZ */ +#define DBM_GEVNTSIZ_MASK 0x0000ffff + +/* DBM_DBG_CNFG */ +#define DBM_ENABLE_IOC_MASK 0x0000000f + +/* DBM_SOFT_RESET */ +#define DBM_SFT_RST_EP0 0x00000001 +#define DBM_SFT_RST_EP1 0x00000002 +#define DBM_SFT_RST_EP2 0x00000004 +#define DBM_SFT_RST_EP3 0x00000008 +#define DBM_SFT_RST_EPS_MASK 0x0000000F +#define DBM_SFT_RST_MASK 0x80000000 +#define DBM_EN_MASK 0x00000002 + +/* DBM TRB configurations */ +#define DBM_TRB_BIT 0x80000000 +#define DBM_TRB_DATA_SRC 0x40000000 +#define DBM_TRB_DMA 0x20000000 +#define DBM_TRB_EP_NUM(ep) (ep<<24) + +struct dbm; + +struct dbm *usb_get_dbm_by_phandle(struct device *dev, const char *phandle); + +int dbm_soft_reset(struct dbm *dbm, bool enter_reset); +int dbm_ep_config(struct dbm *dbm, u8 usb_ep, u8 bam_pipe, bool producer, + bool disable_wb, bool internal_mem, bool ioc); +int dbm_ep_unconfig(struct dbm *dbm, u8 usb_ep); +int dbm_get_num_of_eps_configured(struct dbm *dbm); +int dbm_event_buffer_config(struct dbm *dbm, u32 addr_lo, u32 addr_hi, + int size); +int dwc3_dbm_disable_update_xfer(struct dbm *dbm, u8 usb_ep); +int dbm_data_fifo_config(struct dbm *dbm, u8 dep_num, phys_addr_t addr, + u32 size, u8 dst_pipe_idx); +void dbm_set_speed(struct dbm *dbm, bool speed); +void dbm_enable(struct dbm *dbm); +int dbm_ep_soft_reset(struct dbm *dbm, u8 usb_ep, bool enter_reset); +bool dbm_reset_ep_after_lpm(struct dbm *dbm); +bool dbm_l1_lpm_interrupt(struct dbm *dbm); + +#endif /* __DBM_H */ diff --git a/drivers/usb/dwc3/debug.c b/drivers/usb/dwc3/debug.c new file mode 100644 index 000000000000..0be6885bc370 --- /dev/null +++ b/drivers/usb/dwc3/debug.c @@ -0,0 +1,32 @@ +/** + * debug.c - DesignWare USB3 DRD Controller Debug/Trace Support + * + * Copyright (C) 2014 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. + */ + +#include "debug.h" + +void dwc3_trace(void (*trace)(struct va_format *), const char *fmt, ...) +{ + struct va_format vaf; + va_list args; + + va_start(args, fmt); + vaf.fmt = fmt; + vaf.va = &args; + + trace(&vaf); + + va_end(args); +} diff --git a/drivers/usb/dwc3/debug.h b/drivers/usb/dwc3/debug.h new file mode 100644 index 000000000000..2cafa949bb12 --- /dev/null +++ b/drivers/usb/dwc3/debug.h @@ -0,0 +1,248 @@ +/** + * debug.h - DesignWare USB3 DRD Controller Debug 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. + */ + +#ifndef __DWC3_DEBUG_H +#define __DWC3_DEBUG_H + +#include "core.h" + +/** + * dwc3_gadget_ep_cmd_string - returns endpoint command string + * @cmd: command code + */ +static inline const char * +dwc3_gadget_ep_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DEPCMD_DEPSTARTCFG: + return "Start New Configuration"; + case DWC3_DEPCMD_ENDTRANSFER: + return "End Transfer"; + case DWC3_DEPCMD_UPDATETRANSFER: + return "Update Transfer"; + case DWC3_DEPCMD_STARTTRANSFER: + return "Start Transfer"; + case DWC3_DEPCMD_CLEARSTALL: + return "Clear Stall"; + case DWC3_DEPCMD_SETSTALL: + return "Set Stall"; + case DWC3_DEPCMD_GETEPSTATE: + return "Get Endpoint State"; + case DWC3_DEPCMD_SETTRANSFRESOURCE: + return "Set Endpoint Transfer Resource"; + case DWC3_DEPCMD_SETEPCONFIG: + return "Set Endpoint Configuration"; + default: + return "UNKNOWN command"; + } +} + +/** + * dwc3_gadget_generic_cmd_string - returns generic command string + * @cmd: command code + */ +static inline const char * +dwc3_gadget_generic_cmd_string(u8 cmd) +{ + switch (cmd) { + case DWC3_DGCMD_SET_LMP: + return "Set LMP"; + case DWC3_DGCMD_SET_PERIODIC_PAR: + return "Set Periodic Parameters"; + case DWC3_DGCMD_XMIT_FUNCTION: + return "Transmit Function Wake Device Notification"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_LO: + return "Set Scratchpad Buffer Array Address Lo"; + case DWC3_DGCMD_SET_SCRATCHPAD_ADDR_HI: + return "Set Scratchpad Buffer Array Address Hi"; + case DWC3_DGCMD_SELECTED_FIFO_FLUSH: + return "Selected FIFO Flush"; + case DWC3_DGCMD_ALL_FIFO_FLUSH: + return "All FIFO Flush"; + case DWC3_DGCMD_SET_ENDPOINT_NRDY: + return "Set Endpoint NRDY"; + case DWC3_DGCMD_RUN_SOC_BUS_LOOPBACK: + return "Run SoC Bus Loopback Test"; + default: + return "UNKNOWN"; + } +} + +/** + * dwc3_gadget_link_string - returns link name + * @link_state: link state code + */ +static inline const char * +dwc3_gadget_link_string(enum dwc3_link_state link_state) +{ + switch (link_state) { + case DWC3_LINK_STATE_U0: + return "U0"; + case DWC3_LINK_STATE_U1: + return "U1"; + case DWC3_LINK_STATE_U2: + return "U2"; + case DWC3_LINK_STATE_U3: + return "U3"; + case DWC3_LINK_STATE_SS_DIS: + return "SS.Disabled"; + case DWC3_LINK_STATE_RX_DET: + return "RX.Detect"; + case DWC3_LINK_STATE_SS_INACT: + return "SS.Inactive"; + case DWC3_LINK_STATE_POLL: + return "Polling"; + case DWC3_LINK_STATE_RECOV: + return "Recovery"; + case DWC3_LINK_STATE_HRESET: + return "Hot Reset"; + case DWC3_LINK_STATE_CMPLY: + return "Compliance"; + case DWC3_LINK_STATE_LPBK: + return "Loopback"; + case DWC3_LINK_STATE_RESET: + return "Reset"; + case DWC3_LINK_STATE_RESUME: + return "Resume"; + default: + return "UNKNOWN link state\n"; + } +} + +/** + * dwc3_gadget_event_string - returns event name + * @event: the event code + */ +static inline const char *dwc3_gadget_event_string(u8 event) +{ + switch (event) { + case DWC3_DEVICE_EVENT_DISCONNECT: + return "Disconnect"; + case DWC3_DEVICE_EVENT_RESET: + return "Reset"; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + return "Connection Done"; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + return "Link Status Change"; + case DWC3_DEVICE_EVENT_WAKEUP: + return "WakeUp"; + case DWC3_DEVICE_EVENT_EOPF: + return "End-Of-Frame"; + case DWC3_DEVICE_EVENT_SOF: + return "Start-Of-Frame"; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + return "Erratic Error"; + case DWC3_DEVICE_EVENT_CMD_CMPL: + return "Command Complete"; + case DWC3_DEVICE_EVENT_OVERFLOW: + return "Overflow"; + } + + return "UNKNOWN"; +} + +/** + * dwc3_ep_event_string - returns event name + * @event: then event code + */ +static inline const char *dwc3_ep_event_string(u8 event) +{ + switch (event) { + case DWC3_DEPEVT_XFERCOMPLETE: + return "Transfer Complete"; + case DWC3_DEPEVT_XFERINPROGRESS: + return "Transfer In-Progress"; + case DWC3_DEPEVT_XFERNOTREADY: + return "Transfer Not Ready"; + case DWC3_DEPEVT_RXTXFIFOEVT: + return "FIFO"; + case DWC3_DEPEVT_STREAMEVT: + return "Stream"; + case DWC3_DEPEVT_EPCMDCMPLT: + return "Endpoint Command Complete"; + } + + return "UNKNOWN"; +} + +/** + * dwc3_gadget_event_type_string - return event name + * @event: the event code + */ +static inline const char *dwc3_gadget_event_type_string(u8 event) +{ + switch (event) { + case DWC3_DEVICE_EVENT_DISCONNECT: + return "Disconnect"; + case DWC3_DEVICE_EVENT_RESET: + return "Reset"; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + return "Connect Done"; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + return "Link Status Change"; + case DWC3_DEVICE_EVENT_WAKEUP: + return "Wake-Up"; + case DWC3_DEVICE_EVENT_HIBER_REQ: + return "Hibernation"; + case DWC3_DEVICE_EVENT_EOPF: + return "End of Periodic Frame"; + case DWC3_DEVICE_EVENT_SOF: + return "Start of Frame"; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + return "Erratic Error"; + case DWC3_DEVICE_EVENT_CMD_CMPL: + return "Command Complete"; + case DWC3_DEVICE_EVENT_OVERFLOW: + return "Overflow"; + default: + return "UNKNOWN"; + } +} + +void dwc3_trace(void (*trace)(struct va_format *), const char *fmt, ...); + +#ifdef CONFIG_DEBUG_FS +extern void dbg_event(u8, const char*, int); +extern void dbg_print(u8, const char*, int, const char*); +extern void dbg_done(u8, const u32, int); +extern void dbg_queue(u8, const struct usb_request*, int); +extern void dbg_setup(u8, const struct usb_ctrlrequest*); +extern int dwc3_debugfs_init(struct dwc3 *); +extern void dwc3_debugfs_exit(struct dwc3 *); +extern void dbg_print_reg(const char *name, int reg); +#else +static inline void dbg_event(u8 ep_num, const char *name, int status) +{ } +static inline void dbg_print(u8 ep_num, const char *name, int status, + const char *extra) +{ } +static inline void dbg_done(u8 ep_num, const u32 count, int status) +{ } +static inline void dbg_queue(u8 ep_num, const struct usb_request *req, + int status) +{ } +static inline void dbg_setup(u8 ep_num, const struct usb_ctrlrequest *req) +{ } +static inline void dbg_print_reg(const char *name, int reg) +{ } +static inline int dwc3_debugfs_init(struct dwc3 *d) +{ return 0; } +static inline void dwc3_debugfs_exit(struct dwc3 *d) +{ } +#endif +#endif /* __DWC3_DEBUG_H */ diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c new file mode 100644 index 000000000000..2c00b3596055 --- /dev/null +++ b/drivers/usb/dwc3/debugfs.c @@ -0,0 +1,1359 @@ +/** + * debugfs.c - DesignWare USB3 DRD Controller DebugFS file + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/ptrace.h> +#include <linux/types.h> +#include <linux/spinlock.h> +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#include <linux/delay.h> +#include <linux/uaccess.h> + +#include <linux/usb/ch9.h> + +#include "core.h" +#include "gadget.h" +#include "io.h" +#include "debug.h" + +#define dump_register(nm) \ +{ \ + .name = __stringify(nm), \ + .offset = DWC3_ ##nm - DWC3_GLOBALS_REGS_START, \ +} + +#define ep_event_rate(ev, c, p, dt) \ + ((dt) ? ((c.ev - p.ev) * (MSEC_PER_SEC)) / (dt) : 0) + +static const struct debugfs_reg32 dwc3_regs[] = { + dump_register(GSBUSCFG0), + dump_register(GSBUSCFG1), + dump_register(GTXTHRCFG), + dump_register(GRXTHRCFG), + dump_register(GCTL), + dump_register(GEVTEN), + dump_register(GSTS), + dump_register(GSNPSID), + dump_register(GGPIO), + dump_register(GUID), + dump_register(GUCTL), + dump_register(GBUSERRADDR0), + dump_register(GBUSERRADDR1), + dump_register(GPRTBIMAP0), + dump_register(GPRTBIMAP1), + dump_register(GHWPARAMS0), + dump_register(GHWPARAMS1), + dump_register(GHWPARAMS2), + dump_register(GHWPARAMS3), + dump_register(GHWPARAMS4), + dump_register(GHWPARAMS5), + dump_register(GHWPARAMS6), + dump_register(GHWPARAMS7), + dump_register(GDBGFIFOSPACE), + dump_register(GDBGLTSSM), + dump_register(GPRTBIMAP_HS0), + dump_register(GPRTBIMAP_HS1), + dump_register(GPRTBIMAP_FS0), + dump_register(GPRTBIMAP_FS1), + + dump_register(GUSB2PHYCFG(0)), + dump_register(GUSB2PHYCFG(1)), + dump_register(GUSB2PHYCFG(2)), + dump_register(GUSB2PHYCFG(3)), + dump_register(GUSB2PHYCFG(4)), + dump_register(GUSB2PHYCFG(5)), + dump_register(GUSB2PHYCFG(6)), + dump_register(GUSB2PHYCFG(7)), + dump_register(GUSB2PHYCFG(8)), + dump_register(GUSB2PHYCFG(9)), + dump_register(GUSB2PHYCFG(10)), + dump_register(GUSB2PHYCFG(11)), + dump_register(GUSB2PHYCFG(12)), + dump_register(GUSB2PHYCFG(13)), + dump_register(GUSB2PHYCFG(14)), + dump_register(GUSB2PHYCFG(15)), + + dump_register(GUSB2I2CCTL(0)), + dump_register(GUSB2I2CCTL(1)), + dump_register(GUSB2I2CCTL(2)), + dump_register(GUSB2I2CCTL(3)), + dump_register(GUSB2I2CCTL(4)), + dump_register(GUSB2I2CCTL(5)), + dump_register(GUSB2I2CCTL(6)), + dump_register(GUSB2I2CCTL(7)), + dump_register(GUSB2I2CCTL(8)), + dump_register(GUSB2I2CCTL(9)), + dump_register(GUSB2I2CCTL(10)), + dump_register(GUSB2I2CCTL(11)), + dump_register(GUSB2I2CCTL(12)), + dump_register(GUSB2I2CCTL(13)), + dump_register(GUSB2I2CCTL(14)), + dump_register(GUSB2I2CCTL(15)), + + dump_register(GUSB2PHYACC(0)), + dump_register(GUSB2PHYACC(1)), + dump_register(GUSB2PHYACC(2)), + dump_register(GUSB2PHYACC(3)), + dump_register(GUSB2PHYACC(4)), + dump_register(GUSB2PHYACC(5)), + dump_register(GUSB2PHYACC(6)), + dump_register(GUSB2PHYACC(7)), + dump_register(GUSB2PHYACC(8)), + dump_register(GUSB2PHYACC(9)), + dump_register(GUSB2PHYACC(10)), + dump_register(GUSB2PHYACC(11)), + dump_register(GUSB2PHYACC(12)), + dump_register(GUSB2PHYACC(13)), + dump_register(GUSB2PHYACC(14)), + dump_register(GUSB2PHYACC(15)), + + dump_register(GUSB3PIPECTL(0)), + dump_register(GUSB3PIPECTL(1)), + dump_register(GUSB3PIPECTL(2)), + dump_register(GUSB3PIPECTL(3)), + dump_register(GUSB3PIPECTL(4)), + dump_register(GUSB3PIPECTL(5)), + dump_register(GUSB3PIPECTL(6)), + dump_register(GUSB3PIPECTL(7)), + dump_register(GUSB3PIPECTL(8)), + dump_register(GUSB3PIPECTL(9)), + dump_register(GUSB3PIPECTL(10)), + dump_register(GUSB3PIPECTL(11)), + dump_register(GUSB3PIPECTL(12)), + dump_register(GUSB3PIPECTL(13)), + dump_register(GUSB3PIPECTL(14)), + dump_register(GUSB3PIPECTL(15)), + + dump_register(GTXFIFOSIZ(0)), + dump_register(GTXFIFOSIZ(1)), + dump_register(GTXFIFOSIZ(2)), + dump_register(GTXFIFOSIZ(3)), + dump_register(GTXFIFOSIZ(4)), + dump_register(GTXFIFOSIZ(5)), + dump_register(GTXFIFOSIZ(6)), + dump_register(GTXFIFOSIZ(7)), + dump_register(GTXFIFOSIZ(8)), + dump_register(GTXFIFOSIZ(9)), + dump_register(GTXFIFOSIZ(10)), + dump_register(GTXFIFOSIZ(11)), + dump_register(GTXFIFOSIZ(12)), + dump_register(GTXFIFOSIZ(13)), + dump_register(GTXFIFOSIZ(14)), + dump_register(GTXFIFOSIZ(15)), + dump_register(GTXFIFOSIZ(16)), + dump_register(GTXFIFOSIZ(17)), + dump_register(GTXFIFOSIZ(18)), + dump_register(GTXFIFOSIZ(19)), + dump_register(GTXFIFOSIZ(20)), + dump_register(GTXFIFOSIZ(21)), + dump_register(GTXFIFOSIZ(22)), + dump_register(GTXFIFOSIZ(23)), + dump_register(GTXFIFOSIZ(24)), + dump_register(GTXFIFOSIZ(25)), + dump_register(GTXFIFOSIZ(26)), + dump_register(GTXFIFOSIZ(27)), + dump_register(GTXFIFOSIZ(28)), + dump_register(GTXFIFOSIZ(29)), + dump_register(GTXFIFOSIZ(30)), + dump_register(GTXFIFOSIZ(31)), + + dump_register(GRXFIFOSIZ(0)), + dump_register(GRXFIFOSIZ(1)), + dump_register(GRXFIFOSIZ(2)), + dump_register(GRXFIFOSIZ(3)), + dump_register(GRXFIFOSIZ(4)), + dump_register(GRXFIFOSIZ(5)), + dump_register(GRXFIFOSIZ(6)), + dump_register(GRXFIFOSIZ(7)), + dump_register(GRXFIFOSIZ(8)), + dump_register(GRXFIFOSIZ(9)), + dump_register(GRXFIFOSIZ(10)), + dump_register(GRXFIFOSIZ(11)), + dump_register(GRXFIFOSIZ(12)), + dump_register(GRXFIFOSIZ(13)), + dump_register(GRXFIFOSIZ(14)), + dump_register(GRXFIFOSIZ(15)), + dump_register(GRXFIFOSIZ(16)), + dump_register(GRXFIFOSIZ(17)), + dump_register(GRXFIFOSIZ(18)), + dump_register(GRXFIFOSIZ(19)), + dump_register(GRXFIFOSIZ(20)), + dump_register(GRXFIFOSIZ(21)), + dump_register(GRXFIFOSIZ(22)), + dump_register(GRXFIFOSIZ(23)), + dump_register(GRXFIFOSIZ(24)), + dump_register(GRXFIFOSIZ(25)), + dump_register(GRXFIFOSIZ(26)), + dump_register(GRXFIFOSIZ(27)), + dump_register(GRXFIFOSIZ(28)), + dump_register(GRXFIFOSIZ(29)), + dump_register(GRXFIFOSIZ(30)), + dump_register(GRXFIFOSIZ(31)), + + dump_register(GEVNTADRLO(0)), + dump_register(GEVNTADRHI(0)), + dump_register(GEVNTSIZ(0)), + dump_register(GEVNTCOUNT(0)), + + dump_register(GHWPARAMS8), + dump_register(GFLADJ), + dump_register(DCFG), + dump_register(DCTL), + dump_register(DEVTEN), + dump_register(DSTS), + dump_register(DGCMDPAR), + dump_register(DGCMD), + dump_register(DALEPENA), + + dump_register(DEPCMDPAR2(0)), + dump_register(DEPCMDPAR2(1)), + dump_register(DEPCMDPAR2(2)), + dump_register(DEPCMDPAR2(3)), + dump_register(DEPCMDPAR2(4)), + dump_register(DEPCMDPAR2(5)), + dump_register(DEPCMDPAR2(6)), + dump_register(DEPCMDPAR2(7)), + dump_register(DEPCMDPAR2(8)), + dump_register(DEPCMDPAR2(9)), + dump_register(DEPCMDPAR2(10)), + dump_register(DEPCMDPAR2(11)), + dump_register(DEPCMDPAR2(12)), + dump_register(DEPCMDPAR2(13)), + dump_register(DEPCMDPAR2(14)), + dump_register(DEPCMDPAR2(15)), + dump_register(DEPCMDPAR2(16)), + dump_register(DEPCMDPAR2(17)), + dump_register(DEPCMDPAR2(18)), + dump_register(DEPCMDPAR2(19)), + dump_register(DEPCMDPAR2(20)), + dump_register(DEPCMDPAR2(21)), + dump_register(DEPCMDPAR2(22)), + dump_register(DEPCMDPAR2(23)), + dump_register(DEPCMDPAR2(24)), + dump_register(DEPCMDPAR2(25)), + dump_register(DEPCMDPAR2(26)), + dump_register(DEPCMDPAR2(27)), + dump_register(DEPCMDPAR2(28)), + dump_register(DEPCMDPAR2(29)), + dump_register(DEPCMDPAR2(30)), + dump_register(DEPCMDPAR2(31)), + + dump_register(DEPCMDPAR1(0)), + dump_register(DEPCMDPAR1(1)), + dump_register(DEPCMDPAR1(2)), + dump_register(DEPCMDPAR1(3)), + dump_register(DEPCMDPAR1(4)), + dump_register(DEPCMDPAR1(5)), + dump_register(DEPCMDPAR1(6)), + dump_register(DEPCMDPAR1(7)), + dump_register(DEPCMDPAR1(8)), + dump_register(DEPCMDPAR1(9)), + dump_register(DEPCMDPAR1(10)), + dump_register(DEPCMDPAR1(11)), + dump_register(DEPCMDPAR1(12)), + dump_register(DEPCMDPAR1(13)), + dump_register(DEPCMDPAR1(14)), + dump_register(DEPCMDPAR1(15)), + dump_register(DEPCMDPAR1(16)), + dump_register(DEPCMDPAR1(17)), + dump_register(DEPCMDPAR1(18)), + dump_register(DEPCMDPAR1(19)), + dump_register(DEPCMDPAR1(20)), + dump_register(DEPCMDPAR1(21)), + dump_register(DEPCMDPAR1(22)), + dump_register(DEPCMDPAR1(23)), + dump_register(DEPCMDPAR1(24)), + dump_register(DEPCMDPAR1(25)), + dump_register(DEPCMDPAR1(26)), + dump_register(DEPCMDPAR1(27)), + dump_register(DEPCMDPAR1(28)), + dump_register(DEPCMDPAR1(29)), + dump_register(DEPCMDPAR1(30)), + dump_register(DEPCMDPAR1(31)), + + dump_register(DEPCMDPAR0(0)), + dump_register(DEPCMDPAR0(1)), + dump_register(DEPCMDPAR0(2)), + dump_register(DEPCMDPAR0(3)), + dump_register(DEPCMDPAR0(4)), + dump_register(DEPCMDPAR0(5)), + dump_register(DEPCMDPAR0(6)), + dump_register(DEPCMDPAR0(7)), + dump_register(DEPCMDPAR0(8)), + dump_register(DEPCMDPAR0(9)), + dump_register(DEPCMDPAR0(10)), + dump_register(DEPCMDPAR0(11)), + dump_register(DEPCMDPAR0(12)), + dump_register(DEPCMDPAR0(13)), + dump_register(DEPCMDPAR0(14)), + dump_register(DEPCMDPAR0(15)), + dump_register(DEPCMDPAR0(16)), + dump_register(DEPCMDPAR0(17)), + dump_register(DEPCMDPAR0(18)), + dump_register(DEPCMDPAR0(19)), + dump_register(DEPCMDPAR0(20)), + dump_register(DEPCMDPAR0(21)), + dump_register(DEPCMDPAR0(22)), + dump_register(DEPCMDPAR0(23)), + dump_register(DEPCMDPAR0(24)), + dump_register(DEPCMDPAR0(25)), + dump_register(DEPCMDPAR0(26)), + dump_register(DEPCMDPAR0(27)), + dump_register(DEPCMDPAR0(28)), + dump_register(DEPCMDPAR0(29)), + dump_register(DEPCMDPAR0(30)), + dump_register(DEPCMDPAR0(31)), + + dump_register(DEPCMD(0)), + dump_register(DEPCMD(1)), + dump_register(DEPCMD(2)), + dump_register(DEPCMD(3)), + dump_register(DEPCMD(4)), + dump_register(DEPCMD(5)), + dump_register(DEPCMD(6)), + dump_register(DEPCMD(7)), + dump_register(DEPCMD(8)), + dump_register(DEPCMD(9)), + dump_register(DEPCMD(10)), + dump_register(DEPCMD(11)), + dump_register(DEPCMD(12)), + dump_register(DEPCMD(13)), + dump_register(DEPCMD(14)), + dump_register(DEPCMD(15)), + dump_register(DEPCMD(16)), + dump_register(DEPCMD(17)), + dump_register(DEPCMD(18)), + dump_register(DEPCMD(19)), + dump_register(DEPCMD(20)), + dump_register(DEPCMD(21)), + dump_register(DEPCMD(22)), + dump_register(DEPCMD(23)), + dump_register(DEPCMD(24)), + dump_register(DEPCMD(25)), + dump_register(DEPCMD(26)), + dump_register(DEPCMD(27)), + dump_register(DEPCMD(28)), + dump_register(DEPCMD(29)), + dump_register(DEPCMD(30)), + dump_register(DEPCMD(31)), + + dump_register(OCFG), + dump_register(OCTL), + dump_register(OEVT), + dump_register(OEVTEN), + dump_register(OSTS), +}; + +static int dwc3_mode_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 reg; + + if (atomic_read(&dwc->in_lpm)) { + seq_puts(s, "USB device is powered off\n"); + return 0; + } + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (DWC3_GCTL_PRTCAP(reg)) { + case DWC3_GCTL_PRTCAP_HOST: + seq_printf(s, "host\n"); + break; + case DWC3_GCTL_PRTCAP_DEVICE: + seq_printf(s, "device\n"); + break; + case DWC3_GCTL_PRTCAP_OTG: + seq_printf(s, "OTG\n"); + break; + default: + seq_printf(s, "UNKNOWN %08x\n", DWC3_GCTL_PRTCAP(reg)); + } + + return 0; +} + +static int dwc3_mode_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_mode_show, inode->i_private); +} + +static ssize_t dwc3_mode_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; + unsigned long flags; + u32 mode = 0; + char buf[32] = {}; + + if (atomic_read(&dwc->in_lpm)) { + dev_err(dwc->dev, "USB device is powered off\n"); + return count; + } + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "host", 4)) + mode |= DWC3_GCTL_PRTCAP_HOST; + + if (!strncmp(buf, "device", 6)) + mode |= DWC3_GCTL_PRTCAP_DEVICE; + + if (!strncmp(buf, "otg", 3)) + mode |= DWC3_GCTL_PRTCAP_OTG; + + if (mode) { + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, mode); + spin_unlock_irqrestore(&dwc->lock, flags); + } + return count; +} + +static const struct file_operations dwc3_mode_fops = { + .open = dwc3_mode_open, + .write = dwc3_mode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_testmode_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + u32 reg; + + + if (atomic_read(&dwc->in_lpm)) { + seq_puts(s, "USB device is powered off\n"); + return 0; + } + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= DWC3_DCTL_TSTCTRL_MASK; + reg >>= 1; + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (reg) { + case 0: + seq_printf(s, "no test\n"); + break; + case TEST_J: + seq_printf(s, "test_j\n"); + break; + case TEST_K: + seq_printf(s, "test_k\n"); + break; + case TEST_SE0_NAK: + seq_printf(s, "test_se0_nak\n"); + break; + case TEST_PACKET: + seq_printf(s, "test_packet\n"); + break; + case TEST_FORCE_EN: + seq_printf(s, "test_force_enable\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", reg); + } + + return 0; +} + +static int dwc3_testmode_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_testmode_show, inode->i_private); +} + +static ssize_t dwc3_testmode_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; + unsigned long flags; + u32 testmode = 0; + char buf[32] = {}; + + if (atomic_read(&dwc->in_lpm)) { + seq_puts(s, "USB device is powered off\n"); + return count; + } + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "test_j", 6)) + testmode = TEST_J; + else if (!strncmp(buf, "test_k", 6)) + testmode = TEST_K; + else if (!strncmp(buf, "test_se0_nak", 12)) + testmode = TEST_SE0_NAK; + else if (!strncmp(buf, "test_packet", 11)) + testmode = TEST_PACKET; + else if (!strncmp(buf, "test_force_enable", 17)) + testmode = TEST_FORCE_EN; + else + testmode = 0; + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_set_test_mode(dwc, testmode); + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static const struct file_operations dwc3_testmode_fops = { + .open = dwc3_testmode_open, + .write = dwc3_testmode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_link_state_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + unsigned long flags; + enum dwc3_link_state state; + u32 reg; + + if (atomic_read(&dwc->in_lpm)) { + seq_puts(s, "USB device is powered off\n"); + return 0; + } + + spin_lock_irqsave(&dwc->lock, flags); + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + state = DWC3_DSTS_USBLNKST(reg); + spin_unlock_irqrestore(&dwc->lock, flags); + + switch (state) { + case DWC3_LINK_STATE_U0: + seq_printf(s, "U0\n"); + break; + case DWC3_LINK_STATE_U1: + seq_printf(s, "U1\n"); + break; + case DWC3_LINK_STATE_U2: + seq_printf(s, "U2\n"); + break; + case DWC3_LINK_STATE_U3: + seq_printf(s, "U3\n"); + break; + case DWC3_LINK_STATE_SS_DIS: + seq_printf(s, "SS.Disabled\n"); + break; + case DWC3_LINK_STATE_RX_DET: + seq_printf(s, "Rx.Detect\n"); + break; + case DWC3_LINK_STATE_SS_INACT: + seq_printf(s, "SS.Inactive\n"); + break; + case DWC3_LINK_STATE_POLL: + seq_printf(s, "Poll\n"); + break; + case DWC3_LINK_STATE_RECOV: + seq_printf(s, "Recovery\n"); + break; + case DWC3_LINK_STATE_HRESET: + seq_printf(s, "HRESET\n"); + break; + case DWC3_LINK_STATE_CMPLY: + seq_printf(s, "Compliance\n"); + break; + case DWC3_LINK_STATE_LPBK: + seq_printf(s, "Loopback\n"); + break; + case DWC3_LINK_STATE_RESET: + seq_printf(s, "Reset\n"); + break; + case DWC3_LINK_STATE_RESUME: + seq_printf(s, "Resume\n"); + break; + default: + seq_printf(s, "UNKNOWN %d\n", state); + } + + return 0; +} + +static int dwc3_link_state_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_link_state_show, inode->i_private); +} + +static ssize_t dwc3_link_state_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; + unsigned long flags; + enum dwc3_link_state state = 0; + char buf[32] = {}; + + if (atomic_read(&dwc->in_lpm)) { + seq_puts(s, "USB device is powered off\n"); + return count; + } + + if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) + return -EFAULT; + + if (!strncmp(buf, "SS.Disabled", 11)) + state = DWC3_LINK_STATE_SS_DIS; + else if (!strncmp(buf, "Rx.Detect", 9)) + state = DWC3_LINK_STATE_RX_DET; + else if (!strncmp(buf, "SS.Inactive", 11)) + state = DWC3_LINK_STATE_SS_INACT; + else if (!strncmp(buf, "Recovery", 8)) + state = DWC3_LINK_STATE_RECOV; + else if (!strncmp(buf, "Compliance", 10)) + state = DWC3_LINK_STATE_CMPLY; + else if (!strncmp(buf, "Loopback", 8)) + state = DWC3_LINK_STATE_LPBK; + else + return -EINVAL; + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_set_link_state(dwc, state); + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static const struct file_operations dwc3_link_state_fops = { + .open = dwc3_link_state_open, + .write = dwc3_link_state_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int ep_num; +static ssize_t dwc3_store_ep_num(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 kbuf[10] = {}; + unsigned int num, dir, temp; + unsigned long flags; + + if (copy_from_user(kbuf, ubuf, min_t(size_t, sizeof(kbuf) - 1, count))) + return -EFAULT; + + if (sscanf(kbuf, "%u %u", &num, &dir) != 2) + return -EINVAL; + + if (dir != 0 && dir != 1) + return -EINVAL; + + temp = (num << 1) + dir; + if (temp >= (dwc->num_in_eps + dwc->num_out_eps) || + temp >= DWC3_ENDPOINTS_NUM) + return -EINVAL; + + spin_lock_irqsave(&dwc->lock, flags); + ep_num = temp; + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static int dwc3_ep_req_list_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + struct dwc3_ep *dep; + struct dwc3_request *req = NULL; + struct list_head *ptr = NULL; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dep = dwc->eps[ep_num]; + + seq_printf(s, "%s request list: flags: 0x%x\n", dep->name, dep->flags); + list_for_each(ptr, &dep->request_list) { + req = list_entry(ptr, struct dwc3_request, list); + + seq_printf(s, + "req:0x%pK len: %d sts: %d dma:0x%pa num_sgs: %d\n", + req, req->request.length, req->request.status, + &req->request.dma, req->request.num_sgs); + } + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_ep_req_list_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_ep_req_list_show, inode->i_private); +} + +static const struct file_operations dwc3_ep_req_list_fops = { + .open = dwc3_ep_req_list_open, + .write = dwc3_store_ep_num, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_ep_queued_req_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + struct dwc3_ep *dep; + struct dwc3_request *req = NULL; + struct list_head *ptr = NULL; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + dep = dwc->eps[ep_num]; + + seq_printf(s, "%s queued reqs to HW: flags:0x%x\n", dep->name, + dep->flags); + list_for_each(ptr, &dep->req_queued) { + req = list_entry(ptr, struct dwc3_request, list); + + seq_printf(s, + "req:0x%pK len:%d sts:%d dma:%pa nsg:%d trb:0x%pK\n", + req, req->request.length, req->request.status, + &req->request.dma, req->request.num_sgs, req->trb); + } + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_ep_queued_req_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_ep_queued_req_show, inode->i_private); +} + +const struct file_operations dwc3_ep_req_queued_fops = { + .open = dwc3_ep_queued_req_open, + .write = dwc3_store_ep_num, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int dwc3_ep_trbs_show(struct seq_file *s, void *unused) +{ + struct dwc3 *dwc = s->private; + struct dwc3_ep *dep; + struct dwc3_trb *trb; + unsigned long flags; + int j; + + if (!ep_num) + return 0; + + spin_lock_irqsave(&dwc->lock, flags); + dep = dwc->eps[ep_num]; + + seq_printf(s, "%s trb pool: flags:0x%x freeslot:%d busyslot:%d\n", + dep->name, dep->flags, dep->free_slot, dep->busy_slot); + for (j = 0; j < DWC3_TRB_NUM; j++) { + trb = &dep->trb_pool[j]; + seq_printf(s, "trb:0x%pK bph:0x%x bpl:0x%x size:0x%x ctrl: %x\n", + trb, trb->bph, trb->bpl, trb->size, trb->ctrl); + } + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_ep_trbs_list_open(struct inode *inode, struct file *file) +{ + return single_open(file, dwc3_ep_trbs_show, inode->i_private); +} + +const struct file_operations dwc3_ep_trb_list_fops = { + .open = dwc3_ep_trbs_list_open, + .write = dwc3_store_ep_num, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static unsigned int ep_addr_rxdbg_mask = 1; +module_param(ep_addr_rxdbg_mask, uint, S_IRUGO | S_IWUSR); +static unsigned int ep_addr_txdbg_mask = 1; +module_param(ep_addr_txdbg_mask, uint, S_IRUGO | S_IWUSR); + +/* Maximum debug message length */ +#define DBG_DATA_MSG 64UL + +/* Maximum number of messages */ +#define DBG_DATA_MAX 2048UL + +static struct { + char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ + unsigned idx; /* index */ + unsigned tty; /* print to console? */ + rwlock_t lck; /* lock */ +} dbg_dwc3_data = { + .idx = 0, + .tty = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/** + * dbg_dec: decrements debug event index + * @idx: buffer index + */ +static inline void __maybe_unused dbg_dec(unsigned *idx) +{ + *idx = (*idx - 1) % DBG_DATA_MAX; +} + +/** + * dbg_inc: increments debug event index + * @idx: buffer index + */ +static inline void dbg_inc(unsigned *idx) +{ + *idx = (*idx + 1) % DBG_DATA_MAX; +} + +#define TIME_BUF_LEN 20 +/*get_timestamp - returns time of day in us */ +static char *get_timestamp(char *tbuf) +{ + unsigned long long t; + unsigned long nanosec_rem; + + t = cpu_clock(smp_processor_id()); + nanosec_rem = do_div(t, 1000000000)/1000; + scnprintf(tbuf, TIME_BUF_LEN, "[%5lu.%06lu] ", (unsigned long)t, + nanosec_rem); + return tbuf; +} + +static int allow_dbg_print(u8 ep_num) +{ + int dir, num; + + /* allow bus wide events */ + if (ep_num == 0xff) + return 1; + + dir = ep_num & 0x1; + num = ep_num >> 1; + num = 1 << num; + + if (dir && (num & ep_addr_txdbg_mask)) + return 1; + if (!dir && (num & ep_addr_rxdbg_mask)) + return 1; + + return 0; +} + +/** + * dbg_print: prints the common part of the event + * @addr: endpoint address + * @name: event name + * @status: status + * @extra: extra information + */ +void dbg_print(u8 ep_num, const char *name, int status, const char *extra) +{ + unsigned long flags; + char tbuf[TIME_BUF_LEN]; + + if (!allow_dbg_print(ep_num)) + return; + + write_lock_irqsave(&dbg_dwc3_data.lck, flags); + + scnprintf(dbg_dwc3_data.buf[dbg_dwc3_data.idx], DBG_DATA_MSG, + "%s\t? %02X %-12.12s %4i ?\t%s\n", + get_timestamp(tbuf), ep_num, name, status, extra); + + dbg_inc(&dbg_dwc3_data.idx); + + write_unlock_irqrestore(&dbg_dwc3_data.lck, flags); + + if (dbg_dwc3_data.tty != 0) + pr_notice("%s\t? %02X %-7.7s %4i ?\t%s\n", + get_timestamp(tbuf), ep_num, name, status, extra); +} + +/** + * dbg_done: prints a DONE event + * @addr: endpoint address + * @td: transfer descriptor + * @status: status + */ +void dbg_done(u8 ep_num, const u32 count, int status) +{ + char msg[DBG_DATA_MSG]; + + if (!allow_dbg_print(ep_num)) + return; + + scnprintf(msg, sizeof(msg), "%d", count); + dbg_print(ep_num, "DONE", status, msg); +} + +/** + * dbg_event: prints a generic event + * @addr: endpoint address + * @name: event name + * @status: status + */ +void dbg_event(u8 ep_num, const char *name, int status) +{ + if (!allow_dbg_print(ep_num)) + return; + + if (name != NULL) + dbg_print(ep_num, name, status, ""); +} + +/* + * dbg_queue: prints a QUEUE event + * @addr: endpoint address + * @req: USB request + * @status: status + */ +void dbg_queue(u8 ep_num, const struct usb_request *req, int status) +{ + char msg[DBG_DATA_MSG]; + + if (!allow_dbg_print(ep_num)) + return; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%d %d", !req->no_interrupt, req->length); + dbg_print(ep_num, "QUEUE", status, msg); + } +} + +/** + * dbg_setup: prints a SETUP event + * @addr: endpoint address + * @req: setup request + */ +void dbg_setup(u8 ep_num, const struct usb_ctrlrequest *req) +{ + char msg[DBG_DATA_MSG]; + + if (!allow_dbg_print(ep_num)) + return; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%02X %02X %04X %04X %d", req->bRequestType, + req->bRequest, le16_to_cpu(req->wValue), + le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); + dbg_print(ep_num, "SETUP", 0, msg); + } +} + +/** + * dbg_print_reg: prints a reg value + * @name: reg name + * @reg: reg value to be printed + */ +void dbg_print_reg(const char *name, int reg) +{ + unsigned long flags; + + write_lock_irqsave(&dbg_dwc3_data.lck, flags); + + scnprintf(dbg_dwc3_data.buf[dbg_dwc3_data.idx], DBG_DATA_MSG, + "%s = 0x%08x\n", name, reg); + + dbg_inc(&dbg_dwc3_data.idx); + + write_unlock_irqrestore(&dbg_dwc3_data.lck, flags); + + if (dbg_dwc3_data.tty != 0) + pr_notice("%s = 0x%08x\n", name, reg); +} + +/** + * store_events: configure if events are going to be also printed to console + * + */ +static ssize_t dwc3_store_events(struct file *file, + const char __user *buf, size_t count, loff_t *ppos) +{ + int ret; + u8 tty; + + if (buf == NULL) { + pr_err("[%s] EINVAL\n", __func__); + ret = -EINVAL; + return ret; + } + + ret = kstrtou8_from_user(buf, count, 0, &tty); + if (ret < 0) { + pr_err("can't get enter value.\n"); + return ret; + } + + if (tty > 1) { + pr_err("<1|0>: enable|disable console log\n"); + ret = -EINVAL; + return ret; + } + + dbg_dwc3_data.tty = tty; + pr_info("tty = %u", dbg_dwc3_data.tty); + + return count; +} + +static int dwc3_gadget_data_events_show(struct seq_file *s, void *unused) +{ + unsigned long flags; + unsigned i; + + read_lock_irqsave(&dbg_dwc3_data.lck, flags); + + i = dbg_dwc3_data.idx; + if (strnlen(dbg_dwc3_data.buf[i], DBG_DATA_MSG)) + seq_printf(s, "%s\n", dbg_dwc3_data.buf[i]); + for (dbg_inc(&i); i != dbg_dwc3_data.idx; dbg_inc(&i)) { + if (!strnlen(dbg_dwc3_data.buf[i], DBG_DATA_MSG)) + continue; + seq_printf(s, "%s\n", dbg_dwc3_data.buf[i]); + } + + read_unlock_irqrestore(&dbg_dwc3_data.lck, flags); + + return 0; +} + +static int dwc3_gadget_data_events_open(struct inode *inode, struct file *f) +{ + return single_open(f, dwc3_gadget_data_events_show, inode->i_private); +} + +const struct file_operations dwc3_gadget_dbg_data_fops = { + .open = dwc3_gadget_data_events_open, + .read = seq_read, + .write = dwc3_store_events, + .llseek = seq_lseek, + .release = single_release, +}; + +static ssize_t dwc3_store_int_events(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + int i, ret; + unsigned long flags; + struct seq_file *s = file->private_data; + struct dwc3 *dwc = s->private; + struct dwc3_ep *dep; + struct timespec ts; + u8 clear_stats; + + if (ubuf == NULL) { + pr_err("[%s] EINVAL\n", __func__); + ret = -EINVAL; + return ret; + } + + ret = kstrtou8_from_user(ubuf, count, 0, &clear_stats); + if (ret < 0) { + pr_err("can't get enter value.\n"); + return ret; + } + + if (clear_stats != 0) { + pr_err("Wrong value. To clear stats, enter value as 0.\n"); + ret = -EINVAL; + return ret; + } + + spin_lock_irqsave(&dwc->lock, flags); + + pr_debug("%s(): clearing debug interrupt buffers\n", __func__); + ts = current_kernel_time(); + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + memset(&dep->dbg_ep_events, 0, sizeof(dep->dbg_ep_events)); + memset(&dep->dbg_ep_events_diff, 0, sizeof(dep->dbg_ep_events)); + dep->dbg_ep_events_ts = ts; + } + memset(&dwc->dbg_gadget_events, 0, sizeof(dwc->dbg_gadget_events)); + + spin_unlock_irqrestore(&dwc->lock, flags); + + return count; +} + +static int dwc3_gadget_int_events_show(struct seq_file *s, void *unused) +{ + unsigned long flags; + struct dwc3 *dwc = s->private; + struct dwc3_gadget_events *dbg_gadget_events; + struct dwc3_ep *dep; + int i; + struct timespec ts_delta; + struct timespec ts_current; + u32 ts_delta_ms; + + spin_lock_irqsave(&dwc->lock, flags); + dbg_gadget_events = &dwc->dbg_gadget_events; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + + if (dep == NULL || !(dep->flags & DWC3_EP_ENABLED)) + continue; + + ts_current = current_kernel_time(); + ts_delta = timespec_sub(ts_current, dep->dbg_ep_events_ts); + ts_delta_ms = ts_delta.tv_nsec / NSEC_PER_MSEC + + ts_delta.tv_sec * MSEC_PER_SEC; + + seq_printf(s, "\n\n===== dbg_ep_events for EP(%d) %s =====\n", + i, dep->name); + seq_printf(s, "xfercomplete:%u @ %luHz\n", + dep->dbg_ep_events.xfercomplete, + ep_event_rate(xfercomplete, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "xfernotready:%u @ %luHz\n", + dep->dbg_ep_events.xfernotready, + ep_event_rate(xfernotready, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "control_data:%u @ %luHz\n", + dep->dbg_ep_events.control_data, + ep_event_rate(control_data, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "control_status:%u @ %luHz\n", + dep->dbg_ep_events.control_status, + ep_event_rate(control_status, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "xferinprogress:%u @ %luHz\n", + dep->dbg_ep_events.xferinprogress, + ep_event_rate(xferinprogress, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "rxtxfifoevent:%u @ %luHz\n", + dep->dbg_ep_events.rxtxfifoevent, + ep_event_rate(rxtxfifoevent, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "streamevent:%u @ %luHz\n", + dep->dbg_ep_events.streamevent, + ep_event_rate(streamevent, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "epcmdcomplt:%u @ %luHz\n", + dep->dbg_ep_events.epcmdcomplete, + ep_event_rate(epcmdcomplete, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "cmdcmplt:%u @ %luHz\n", + dep->dbg_ep_events.cmdcmplt, + ep_event_rate(cmdcmplt, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "unknown:%u @ %luHz\n", + dep->dbg_ep_events.unknown_event, + ep_event_rate(unknown_event, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + seq_printf(s, "total:%u @ %luHz\n", + dep->dbg_ep_events.total, + ep_event_rate(total, dep->dbg_ep_events, + dep->dbg_ep_events_diff, ts_delta_ms)); + + dep->dbg_ep_events_ts = ts_current; + dep->dbg_ep_events_diff = dep->dbg_ep_events; + } + + seq_puts(s, "\n=== dbg_gadget events ==\n"); + seq_printf(s, "disconnect:%u\n reset:%u\n", + dbg_gadget_events->disconnect, dbg_gadget_events->reset); + seq_printf(s, "connect:%u\n wakeup:%u\n", + dbg_gadget_events->connect, dbg_gadget_events->wakeup); + seq_printf(s, "link_status_change:%u\n eopf:%u\n", + dbg_gadget_events->link_status_change, dbg_gadget_events->eopf); + seq_printf(s, "sof:%u\n suspend:%u\n", + dbg_gadget_events->sof, dbg_gadget_events->suspend); + seq_printf(s, "erratic_error:%u\n overflow:%u\n", + dbg_gadget_events->erratic_error, + dbg_gadget_events->overflow); + seq_printf(s, "vendor_dev_test_lmp:%u\n cmdcmplt:%u\n", + dbg_gadget_events->vendor_dev_test_lmp, + dbg_gadget_events->cmdcmplt); + seq_printf(s, "unknown_event:%u\n", dbg_gadget_events->unknown_event); + + seq_printf(s, "\n\t== Last %d interrupts stats ==\t\n", MAX_INTR_STATS); + seq_puts(s, "@ time (us):\t"); + for (i = 0; i < MAX_INTR_STATS; i++) + seq_printf(s, "%lld\t", ktime_to_us(dwc->irq_start_time[i])); + seq_puts(s, "\nhard irq time (us):\t"); + for (i = 0; i < MAX_INTR_STATS; i++) + seq_printf(s, "%d\t", dwc->irq_completion_time[i]); + seq_puts(s, "\nevents count:\t\t"); + for (i = 0; i < MAX_INTR_STATS; i++) + seq_printf(s, "%d\t", dwc->irq_event_count[i]); + seq_puts(s, "\nbh handled count:\t"); + for (i = 0; i < MAX_INTR_STATS; i++) + seq_printf(s, "%d\t", dwc->bh_handled_evt_cnt[i]); + seq_puts(s, "\nirq thread time (us):\t"); + for (i = 0; i < MAX_INTR_STATS; i++) + seq_printf(s, "%d\t", dwc->bh_completion_time[i]); + seq_putc(s, '\n'); + + seq_printf(s, "t_pwr evt irq : %lld\n", + ktime_to_us(dwc->t_pwr_evt_irq)); + + seq_printf(s, "l1_remote_wakeup_cnt : %lu\n", + dwc->l1_remote_wakeup_cnt); + + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; +} + +static int dwc3_gadget_events_open(struct inode *inode, struct file *f) +{ + return single_open(f, dwc3_gadget_int_events_show, inode->i_private); +} + +const struct file_operations dwc3_gadget_dbg_events_fops = { + .open = dwc3_gadget_events_open, + .read = seq_read, + .write = dwc3_store_int_events, + .llseek = seq_lseek, + .release = single_release, +}; + +int dwc3_debugfs_init(struct dwc3 *dwc) +{ + struct dentry *root; + struct dentry *file; + int ret; + + root = debugfs_create_dir(dev_name(dwc->dev), NULL); + if (!root) { + ret = -ENOMEM; + goto err0; + } + + dwc->root = root; + + dwc->regset = kzalloc(sizeof(*dwc->regset), GFP_KERNEL); + if (!dwc->regset) { + ret = -ENOMEM; + goto err1; + } + + dwc->regset->regs = dwc3_regs; + dwc->regset->nregs = ARRAY_SIZE(dwc3_regs); + dwc->regset->base = dwc->regs; + + if (dwc->create_reg_debugfs) { + file = debugfs_create_regset32("regdump", 0444, + root, dwc->regset); + if (!file) { + dev_dbg(dwc->dev, "Can't create debugfs regdump\n"); + ret = -ENOMEM; + goto err1; + } + } + + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) { + file = debugfs_create_file("mode", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_mode_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + } + + if (IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) || + IS_ENABLED(CONFIG_USB_DWC3_GADGET)) { + file = debugfs_create_file("testmode", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_testmode_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("link_state", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_link_state_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + } + + file = debugfs_create_file("trbs", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_ep_trb_list_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("requests", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_ep_req_list_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("queued_reqs", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_ep_req_queued_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("events", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_gadget_dbg_data_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + file = debugfs_create_file("int_events", S_IRUGO | S_IWUSR, root, + dwc, &dwc3_gadget_dbg_events_fops); + if (!file) { + ret = -ENOMEM; + goto err1; + } + + return 0; + +err1: + debugfs_remove_recursive(root); + +err0: + return ret; +} + +void dwc3_debugfs_exit(struct dwc3 *dwc) +{ + debugfs_remove_recursive(dwc->root); + dwc->root = NULL; +} diff --git a/drivers/usb/dwc3/dwc3-exynos.c b/drivers/usb/dwc3/dwc3-exynos.c new file mode 100644 index 000000000000..9eba51b92f72 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-exynos.c @@ -0,0 +1,306 @@ +/** + * dwc3-exynos.c - Samsung EXYNOS DWC3 Specific Glue layer + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Anton Tikhomirov <av.tikhomirov@samsung.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/kernel.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/usb/otg.h> +#include <linux/usb/usb_phy_generic.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/regulator/consumer.h> + +struct dwc3_exynos { + struct platform_device *usb2_phy; + struct platform_device *usb3_phy; + struct device *dev; + + struct clk *clk; + struct clk *susp_clk; + struct clk *axius_clk; + + struct regulator *vdd33; + struct regulator *vdd10; +}; + +static int dwc3_exynos_register_phys(struct dwc3_exynos *exynos) +{ + struct usb_phy_generic_platform_data pdata; + struct platform_device *pdev; + int ret; + + memset(&pdata, 0x00, sizeof(pdata)); + + pdev = platform_device_alloc("usb_phy_generic", PLATFORM_DEVID_AUTO); + if (!pdev) + return -ENOMEM; + + exynos->usb2_phy = pdev; + pdata.type = USB_PHY_TYPE_USB2; + pdata.gpio_reset = -1; + + ret = platform_device_add_data(exynos->usb2_phy, &pdata, sizeof(pdata)); + if (ret) + goto err1; + + pdev = platform_device_alloc("usb_phy_generic", PLATFORM_DEVID_AUTO); + if (!pdev) { + ret = -ENOMEM; + goto err1; + } + + exynos->usb3_phy = pdev; + pdata.type = USB_PHY_TYPE_USB3; + + ret = platform_device_add_data(exynos->usb3_phy, &pdata, sizeof(pdata)); + if (ret) + goto err2; + + ret = platform_device_add(exynos->usb2_phy); + if (ret) + goto err2; + + ret = platform_device_add(exynos->usb3_phy); + if (ret) + goto err3; + + return 0; + +err3: + platform_device_del(exynos->usb2_phy); + +err2: + platform_device_put(exynos->usb3_phy); + +err1: + platform_device_put(exynos->usb2_phy); + + return ret; +} + +static int dwc3_exynos_remove_child(struct device *dev, void *unused) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static int dwc3_exynos_probe(struct platform_device *pdev) +{ + struct dwc3_exynos *exynos; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node; + + int ret; + + exynos = devm_kzalloc(dev, sizeof(*exynos), GFP_KERNEL); + if (!exynos) + return -ENOMEM; + + /* + * Right now device-tree probed devices don't get dma_mask set. + * Since shared usb code relies on it, set it here for now. + * Once we move to full device tree support this will vanish off. + */ + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + platform_set_drvdata(pdev, exynos); + + exynos->dev = dev; + + exynos->clk = devm_clk_get(dev, "usbdrd30"); + if (IS_ERR(exynos->clk)) { + dev_err(dev, "couldn't get clock\n"); + return -EINVAL; + } + clk_prepare_enable(exynos->clk); + + exynos->susp_clk = devm_clk_get(dev, "usbdrd30_susp_clk"); + if (IS_ERR(exynos->susp_clk)) { + dev_info(dev, "no suspend clk specified\n"); + exynos->susp_clk = NULL; + } + clk_prepare_enable(exynos->susp_clk); + + if (of_device_is_compatible(node, "samsung,exynos7-dwusb3")) { + exynos->axius_clk = devm_clk_get(dev, "usbdrd30_axius_clk"); + if (IS_ERR(exynos->axius_clk)) { + dev_err(dev, "no AXI UpScaler clk specified\n"); + ret = -ENODEV; + goto axius_clk_err; + } + clk_prepare_enable(exynos->axius_clk); + } else { + exynos->axius_clk = NULL; + } + + exynos->vdd33 = devm_regulator_get(dev, "vdd33"); + if (IS_ERR(exynos->vdd33)) { + ret = PTR_ERR(exynos->vdd33); + goto err2; + } + ret = regulator_enable(exynos->vdd33); + if (ret) { + dev_err(dev, "Failed to enable VDD33 supply\n"); + goto err2; + } + + exynos->vdd10 = devm_regulator_get(dev, "vdd10"); + if (IS_ERR(exynos->vdd10)) { + ret = PTR_ERR(exynos->vdd10); + goto err3; + } + ret = regulator_enable(exynos->vdd10); + if (ret) { + dev_err(dev, "Failed to enable VDD10 supply\n"); + goto err3; + } + + ret = dwc3_exynos_register_phys(exynos); + if (ret) { + dev_err(dev, "couldn't register PHYs\n"); + goto err4; + } + + if (node) { + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to add dwc3 core\n"); + goto err5; + } + } else { + dev_err(dev, "no device node, failed to add dwc3 core\n"); + ret = -ENODEV; + goto err5; + } + + return 0; + +err5: + platform_device_unregister(exynos->usb2_phy); + platform_device_unregister(exynos->usb3_phy); +err4: + regulator_disable(exynos->vdd10); +err3: + regulator_disable(exynos->vdd33); +err2: + clk_disable_unprepare(exynos->axius_clk); +axius_clk_err: + clk_disable_unprepare(exynos->susp_clk); + clk_disable_unprepare(exynos->clk); + return ret; +} + +static int dwc3_exynos_remove(struct platform_device *pdev) +{ + struct dwc3_exynos *exynos = platform_get_drvdata(pdev); + + device_for_each_child(&pdev->dev, NULL, dwc3_exynos_remove_child); + platform_device_unregister(exynos->usb2_phy); + platform_device_unregister(exynos->usb3_phy); + + clk_disable_unprepare(exynos->axius_clk); + clk_disable_unprepare(exynos->susp_clk); + clk_disable_unprepare(exynos->clk); + + regulator_disable(exynos->vdd33); + regulator_disable(exynos->vdd10); + + return 0; +} + +static const struct of_device_id exynos_dwc3_match[] = { + { .compatible = "samsung,exynos5250-dwusb3" }, + { .compatible = "samsung,exynos7-dwusb3" }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_dwc3_match); + +#ifdef CONFIG_PM_SLEEP +static int dwc3_exynos_suspend(struct device *dev) +{ + struct dwc3_exynos *exynos = dev_get_drvdata(dev); + + clk_disable(exynos->axius_clk); + clk_disable(exynos->clk); + + regulator_disable(exynos->vdd33); + regulator_disable(exynos->vdd10); + + return 0; +} + +static int dwc3_exynos_resume(struct device *dev) +{ + struct dwc3_exynos *exynos = dev_get_drvdata(dev); + int ret; + + ret = regulator_enable(exynos->vdd33); + if (ret) { + dev_err(dev, "Failed to enable VDD33 supply\n"); + return ret; + } + ret = regulator_enable(exynos->vdd10); + if (ret) { + dev_err(dev, "Failed to enable VDD10 supply\n"); + return ret; + } + + clk_enable(exynos->clk); + clk_enable(exynos->axius_clk); + + /* runtime set active to reflect active state. */ + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static const struct dev_pm_ops dwc3_exynos_dev_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dwc3_exynos_suspend, dwc3_exynos_resume) +}; + +#define DEV_PM_OPS (&dwc3_exynos_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct platform_driver dwc3_exynos_driver = { + .probe = dwc3_exynos_probe, + .remove = dwc3_exynos_remove, + .driver = { + .name = "exynos-dwc3", + .of_match_table = exynos_dwc3_match, + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(dwc3_exynos_driver); + +MODULE_ALIAS("platform:exynos-dwc3"); +MODULE_AUTHOR("Anton Tikhomirov <av.tikhomirov@samsung.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 EXYNOS Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-keystone.c b/drivers/usb/dwc3/dwc3-keystone.c new file mode 100644 index 000000000000..03a926ebf34b --- /dev/null +++ b/drivers/usb/dwc3/dwc3-keystone.c @@ -0,0 +1,201 @@ +/** + * dwc3-keystone.c - Keystone Specific Glue layer + * + * Copyright (C) 2010-2013 Texas Instruments Incorporated - http://www.ti.com + * + * Author: WingMan Kwok <w-kwok2@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. + */ + +#include <linux/clk.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> +#include <linux/of_platform.h> + +/* USBSS register offsets */ +#define USBSS_REVISION 0x0000 +#define USBSS_SYSCONFIG 0x0010 +#define USBSS_IRQ_EOI 0x0018 +#define USBSS_IRQSTATUS_RAW_0 0x0020 +#define USBSS_IRQSTATUS_0 0x0024 +#define USBSS_IRQENABLE_SET_0 0x0028 +#define USBSS_IRQENABLE_CLR_0 0x002c + +/* IRQ register bits */ +#define USBSS_IRQ_EOI_LINE(n) BIT(n) +#define USBSS_IRQ_EVENT_ST BIT(0) +#define USBSS_IRQ_COREIRQ_EN BIT(0) +#define USBSS_IRQ_COREIRQ_CLR BIT(0) + +static u64 kdwc3_dma_mask; + +struct dwc3_keystone { + struct device *dev; + struct clk *clk; + void __iomem *usbss; +}; + +static inline u32 kdwc3_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void kdwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + writel(value, base + offset); +} + +static void kdwc3_enable_irqs(struct dwc3_keystone *kdwc) +{ + u32 val; + + val = kdwc3_readl(kdwc->usbss, USBSS_IRQENABLE_SET_0); + val |= USBSS_IRQ_COREIRQ_EN; + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, val); +} + +static void kdwc3_disable_irqs(struct dwc3_keystone *kdwc) +{ + u32 val; + + val = kdwc3_readl(kdwc->usbss, USBSS_IRQENABLE_SET_0); + val &= ~USBSS_IRQ_COREIRQ_EN; + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, val); +} + +static irqreturn_t dwc3_keystone_interrupt(int irq, void *_kdwc) +{ + struct dwc3_keystone *kdwc = _kdwc; + + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_CLR_0, USBSS_IRQ_COREIRQ_CLR); + kdwc3_writel(kdwc->usbss, USBSS_IRQSTATUS_0, USBSS_IRQ_EVENT_ST); + kdwc3_writel(kdwc->usbss, USBSS_IRQENABLE_SET_0, USBSS_IRQ_COREIRQ_EN); + kdwc3_writel(kdwc->usbss, USBSS_IRQ_EOI, USBSS_IRQ_EOI_LINE(0)); + + return IRQ_HANDLED; +} + +static int kdwc3_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *node = pdev->dev.of_node; + struct dwc3_keystone *kdwc; + struct resource *res; + int error, irq; + + kdwc = devm_kzalloc(dev, sizeof(*kdwc), GFP_KERNEL); + if (!kdwc) + return -ENOMEM; + + platform_set_drvdata(pdev, kdwc); + + kdwc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + kdwc->usbss = devm_ioremap_resource(dev, res); + if (IS_ERR(kdwc->usbss)) + return PTR_ERR(kdwc->usbss); + + kdwc3_dma_mask = dma_get_mask(dev); + dev->dma_mask = &kdwc3_dma_mask; + + kdwc->clk = devm_clk_get(kdwc->dev, "usb"); + if (IS_ERR(kdwc->clk)) { + dev_err(kdwc->dev, "unable to get usb clock\n"); + return PTR_ERR(kdwc->clk); + } + + error = clk_prepare_enable(kdwc->clk); + if (error < 0) { + dev_err(kdwc->dev, "unable to enable usb clock, error %d\n", + error); + return error; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(&pdev->dev, "missing irq\n"); + error = irq; + goto err_irq; + } + + error = devm_request_irq(dev, irq, dwc3_keystone_interrupt, IRQF_SHARED, + dev_name(dev), kdwc); + if (error) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", + irq, error); + goto err_irq; + } + + kdwc3_enable_irqs(kdwc); + + error = of_platform_populate(node, NULL, NULL, dev); + if (error) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + goto err_core; + } + + return 0; + +err_core: + kdwc3_disable_irqs(kdwc); +err_irq: + clk_disable_unprepare(kdwc->clk); + + return error; +} + +static int kdwc3_remove_core(struct device *dev, void *c) +{ + struct platform_device *pdev = to_platform_device(dev); + + platform_device_unregister(pdev); + + return 0; +} + +static int kdwc3_remove(struct platform_device *pdev) +{ + struct dwc3_keystone *kdwc = platform_get_drvdata(pdev); + + kdwc3_disable_irqs(kdwc); + device_for_each_child(&pdev->dev, NULL, kdwc3_remove_core); + clk_disable_unprepare(kdwc->clk); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id kdwc3_of_match[] = { + { .compatible = "ti,keystone-dwc3", }, + {}, +}; +MODULE_DEVICE_TABLE(of, kdwc3_of_match); + +static struct platform_driver kdwc3_driver = { + .probe = kdwc3_probe, + .remove = kdwc3_remove, + .driver = { + .name = "keystone-dwc3", + .of_match_table = kdwc3_of_match, + }, +}; + +module_platform_driver(kdwc3_driver); + +MODULE_ALIAS("platform:keystone-dwc3"); +MODULE_AUTHOR("WingMan Kwok <w-kwok2@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 KEYSTONE Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c new file mode 100644 index 000000000000..b6b25c75b80c --- /dev/null +++ b/drivers/usb/dwc3/dwc3-msm.c @@ -0,0 +1,4356 @@ +/* Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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/kernel.h> +#include <linux/slab.h> +#include <linux/cpu.h> +#include <linux/platform_device.h> +#include <linux/dma-mapping.h> +#include <linux/dmapool.h> +#include <linux/pm_runtime.h> +#include <linux/ratelimit.h> +#include <linux/interrupt.h> +#include <linux/ioport.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/list.h> +#include <linux/uaccess.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/of.h> +#include <linux/usb/msm_hsusb.h> +#include <linux/regulator/consumer.h> +#include <linux/pm_wakeup.h> +#include <linux/power_supply.h> +#include <linux/cdev.h> +#include <linux/completion.h> +#include <linux/clk/msm-clk.h> +#include <linux/msm-bus.h> +#include <linux/irq.h> +#include <linux/extcon.h> +#include <linux/reset.h> +#include <soc/qcom/boot_stats.h> + +#include "power.h" +#include "core.h" +#include "gadget.h" +#include "dbm.h" +#include "debug.h" +#include "xhci.h" + +#define SDP_CONNETION_CHECK_TIME 10000 /* in ms */ + +/* time out to wait for USB cable status notification (in ms)*/ +#define SM_INIT_TIMEOUT 30000 +#define DWC3_WAKEUP_SRC_TIMEOUT 5000 +/* AHB2PHY register offsets */ +#define PERIPH_SS_AHB2PHY_TOP_CFG 0x10 + +/* AHB2PHY read/write waite value */ +#define ONE_READ_WRITE_WAIT 0x11 + +/* DP_DM linestate float */ +#define DP_DM_STATE_FLOAT 0x02 + +/* cpu to fix usb interrupt */ +static int cpu_to_affin; +module_param(cpu_to_affin, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(cpu_to_affin, "affin usb irq to this cpu"); + +/* XHCI registers */ +#define USB3_HCSPARAMS1 (0x4) +#define USB3_HCCPARAMS2 (0x1c) +#define HCC_CTC(p) ((p) & (1 << 3)) +#define USB3_PORTSC (0x420) + +/** + * USB QSCRATCH Hardware registers + * + */ +#define QSCRATCH_REG_OFFSET (0x000F8800) +#define QSCRATCH_GENERAL_CFG (QSCRATCH_REG_OFFSET + 0x08) +#define CGCTL_REG (QSCRATCH_REG_OFFSET + 0x28) +#define PWR_EVNT_IRQ_STAT_REG (QSCRATCH_REG_OFFSET + 0x58) +#define PWR_EVNT_IRQ_MASK_REG (QSCRATCH_REG_OFFSET + 0x5C) + +#define PWR_EVNT_POWERDOWN_IN_P3_MASK BIT(2) +#define PWR_EVNT_POWERDOWN_OUT_P3_MASK BIT(3) +#define PWR_EVNT_LPM_IN_L2_MASK BIT(4) +#define PWR_EVNT_LPM_OUT_L2_MASK BIT(5) +#define PWR_EVNT_LPM_OUT_L1_MASK BIT(13) + +/* QSCRATCH_GENERAL_CFG register bit offset */ +#define PIPE_UTMI_CLK_SEL BIT(0) +#define PIPE3_PHYSTATUS_SW BIT(3) +#define PIPE_UTMI_CLK_DIS BIT(8) + +#define HS_PHY_CTRL_REG (QSCRATCH_REG_OFFSET + 0x10) +#define UTMI_OTG_VBUS_VALID BIT(20) +#define SW_SESSVLD_SEL BIT(28) + +#define SS_PHY_CTRL_REG (QSCRATCH_REG_OFFSET + 0x30) +#define LANE0_PWR_PRESENT BIT(24) + +/* GSI related registers */ +#define GSI_TRB_ADDR_BIT_53_MASK (1 << 21) +#define GSI_TRB_ADDR_BIT_55_MASK (1 << 23) + +#define GSI_GENERAL_CFG_REG (QSCRATCH_REG_OFFSET + 0xFC) +#define GSI_RESTART_DBL_PNTR_MASK BIT(20) +#define GSI_CLK_EN_MASK BIT(12) +#define BLOCK_GSI_WR_GO_MASK BIT(1) +#define GSI_EN_MASK BIT(0) + +#define GSI_DBL_ADDR_L(n) ((QSCRATCH_REG_OFFSET + 0x110) + (n*4)) +#define GSI_DBL_ADDR_H(n) ((QSCRATCH_REG_OFFSET + 0x120) + (n*4)) +#define GSI_RING_BASE_ADDR_L(n) ((QSCRATCH_REG_OFFSET + 0x130) + (n*4)) +#define GSI_RING_BASE_ADDR_H(n) ((QSCRATCH_REG_OFFSET + 0x140) + (n*4)) + +#define GSI_IF_STS (QSCRATCH_REG_OFFSET + 0x1A4) +#define GSI_WR_CTRL_STATE_MASK BIT(15) + +struct dwc3_msm_req_complete { + struct list_head list_item; + struct usb_request *req; + void (*orig_complete)(struct usb_ep *ep, + struct usb_request *req); +}; + +enum dwc3_drd_state { + DRD_STATE_UNDEFINED = 0, + + DRD_STATE_IDLE, + DRD_STATE_PERIPHERAL, + DRD_STATE_PERIPHERAL_SUSPEND, + + DRD_STATE_HOST_IDLE, + DRD_STATE_HOST, +}; + +static const char *const state_names[] = { + [DRD_STATE_UNDEFINED] = "undefined", + [DRD_STATE_IDLE] = "idle", + [DRD_STATE_PERIPHERAL] = "peripheral", + [DRD_STATE_PERIPHERAL_SUSPEND] = "peripheral_suspend", + [DRD_STATE_HOST_IDLE] = "host_idle", + [DRD_STATE_HOST] = "host", +}; + +static const char *dwc3_drd_state_string(enum dwc3_drd_state state) +{ + if (state < 0 || state >= ARRAY_SIZE(state_names)) + return "UNKNOWN"; + + return state_names[state]; +} + +enum dwc3_id_state { + DWC3_ID_GROUND = 0, + DWC3_ID_FLOAT, +}; + +/* for type c cable */ +enum plug_orientation { + ORIENTATION_NONE, + ORIENTATION_CC1, + ORIENTATION_CC2, +}; + +/* Input bits to state machine (mdwc->inputs) */ + +#define ID 0 +#define B_SESS_VLD 1 +#define B_SUSPEND 2 +#define WAIT_FOR_LPM 3 + +#define PM_QOS_SAMPLE_SEC 2 +#define PM_QOS_THRESHOLD 400 + +struct dwc3_msm { + struct device *dev; + void __iomem *base; + void __iomem *ahb2phy_base; + struct platform_device *dwc3; + const struct usb_ep_ops *original_ep_ops[DWC3_ENDPOINTS_NUM]; + struct list_head req_complete_list; + struct clk *xo_clk; + struct clk *core_clk; + long core_clk_rate; + long core_clk_rate_hs; + struct clk *iface_clk; + struct clk *sleep_clk; + struct clk *utmi_clk; + unsigned int utmi_clk_rate; + struct clk *utmi_clk_src; + struct clk *bus_aggr_clk; + struct clk *noc_aggr_clk; + struct clk *cfg_ahb_clk; + struct reset_control *core_reset; + struct regulator *dwc3_gdsc; + + struct usb_phy *hs_phy, *ss_phy; + + struct dbm *dbm; + + /* VBUS regulator for host mode */ + struct regulator *vbus_reg; + int vbus_retry_count; + bool resume_pending; + atomic_t pm_suspended; + int hs_phy_irq; + int ss_phy_irq; + struct work_struct resume_work; + struct work_struct restart_usb_work; + bool in_restart; + struct workqueue_struct *dwc3_wq; + struct workqueue_struct *sm_usb_wq; + struct delayed_work sm_work; + unsigned long inputs; + unsigned max_power; + bool charging_disabled; + enum dwc3_drd_state drd_state; + enum usb_chg_state chg_state; + struct work_struct bus_vote_w; + unsigned int bus_vote; + u32 bus_perf_client; + struct msm_bus_scale_pdata *bus_scale_table; + struct power_supply *usb_psy; + struct work_struct vbus_draw_work; + bool in_host_mode; + bool in_device_mode; + enum usb_device_speed max_rh_port_speed; + unsigned int tx_fifo_size; + bool vbus_active; + bool suspend; + bool disable_host_mode_pm; + enum dwc3_id_state id_state; + unsigned long lpm_flags; +#define MDWC3_SS_PHY_SUSPEND BIT(0) +#define MDWC3_ASYNC_IRQ_WAKE_CAPABILITY BIT(1) +#define MDWC3_POWER_COLLAPSE BIT(2) + + unsigned int irq_to_affin; + struct notifier_block dwc3_cpu_notifier; + struct notifier_block usbdev_nb; + bool hc_died; + bool xhci_ss_compliance_enable; + bool no_wakeup_src_in_hostmode; + bool check_for_float; + bool float_detected; + + struct extcon_dev *extcon_vbus; + struct extcon_dev *extcon_id; + struct notifier_block vbus_nb; + struct notifier_block id_nb; + + struct notifier_block host_nb; + bool host_only_mode; + + int pwr_event_irq; + atomic_t in_p3; + unsigned int lpm_to_suspend_delay; + bool init; + enum plug_orientation typec_orientation; + int pm_qos_latency; + struct pm_qos_request pm_qos_req_dma; + struct delayed_work perf_vote_work; + struct delayed_work sdp_check; + bool usb_compliance_mode; + struct mutex suspend_resume_mutex; + + enum usb_device_speed override_usb_speed; + + bool core_init_failed; +}; + +#define USB_HSPHY_3P3_VOL_MIN 3050000 /* uV */ +#define USB_HSPHY_3P3_VOL_MAX 3300000 /* uV */ +#define USB_HSPHY_3P3_HPM_LOAD 16000 /* uA */ + +#define USB_HSPHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_HSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_HSPHY_1P8_HPM_LOAD 19000 /* uA */ + +#define USB_SSPHY_1P8_VOL_MIN 1800000 /* uV */ +#define USB_SSPHY_1P8_VOL_MAX 1800000 /* uV */ +#define USB_SSPHY_1P8_HPM_LOAD 23000 /* uA */ + +#define DSTS_CONNECTSPD_SS 0x4 + + +static void dwc3_pwr_event_handler(struct dwc3_msm *mdwc); +static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned mA); + +/** + * + * Read register with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * + * @return u32 + */ +static inline u32 dwc3_msm_read_reg(void *base, u32 offset) +{ + u32 val = ioread32(base + offset); + return val; +} + +/** + * Read register masked field with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * + * @return u32 + */ +static inline u32 dwc3_msm_read_reg_field(void *base, + u32 offset, + const u32 mask) +{ + u32 shift = find_first_bit((void *)&mask, 32); + u32 val = ioread32(base + offset); + val &= mask; /* clear other bits */ + val >>= shift; + return val; +} + +/** + * + * Write register with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @val - value to write. + * + */ +static inline void dwc3_msm_write_reg(void *base, u32 offset, u32 val) +{ + iowrite32(val, base + offset); +} + +/** + * Write register masked field with debug info. + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask. + * @val - value to write. + * + */ +static inline void dwc3_msm_write_reg_field(void *base, u32 offset, + const u32 mask, u32 val) +{ + u32 shift = find_first_bit((void *)&mask, 32); + u32 tmp = ioread32(base + offset); + + tmp &= ~mask; /* clear written bits */ + val = tmp | (val << shift); + iowrite32(val, base + offset); +} + +/** + * Write register and read back masked value to confirm it is written + * + * @base - DWC3 base virtual address. + * @offset - register offset. + * @mask - register bitmask specifying what should be updated + * @val - value to write. + * + */ +static inline void dwc3_msm_write_readback(void *base, u32 offset, + const u32 mask, u32 val) +{ + u32 write_val, tmp = ioread32(base + offset); + + tmp &= ~mask; /* retain other bits */ + write_val = tmp | val; + + iowrite32(write_val, base + offset); + + /* Read back to see if val was written */ + tmp = ioread32(base + offset); + tmp &= mask; /* clear other bits */ + + if (tmp != val) + pr_err("%s: write: %x to QSCRATCH: %x FAILED\n", + __func__, val, offset); +} + +static bool dwc3_msm_is_ss_rhport_connected(struct dwc3_msm *mdwc) +{ + int i, num_ports; + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_PORTSC + i*0x10); + if ((reg & PORT_CONNECT) && DEV_SUPERSPEED(reg)) + return true; + } + + return false; +} + +static bool dwc3_msm_is_host_superspeed(struct dwc3_msm *mdwc) +{ + int i, num_ports; + u32 reg; + + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_PORTSC + i*0x10); + if ((reg & PORT_PE) && DEV_SUPERSPEED(reg)) + return true; + } + + return false; +} + +static inline bool dwc3_msm_is_dev_superspeed(struct dwc3_msm *mdwc) +{ + u8 speed; + + speed = dwc3_msm_read_reg(mdwc->base, DWC3_DSTS) & DWC3_DSTS_CONNECTSPD; + return !!(speed & DSTS_CONNECTSPD_SS); +} + +static inline bool dwc3_msm_is_superspeed(struct dwc3_msm *mdwc) +{ + if (mdwc->in_host_mode) + return dwc3_msm_is_host_superspeed(mdwc); + + return dwc3_msm_is_dev_superspeed(mdwc); +} + +int dwc3_msm_dbm_disable_updxfer(struct dwc3 *dwc, u8 usb_ep) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dev_dbg(mdwc->dev, "%s\n", __func__); + dwc3_dbm_disable_update_xfer(mdwc->dbm, usb_ep); + + return 0; +} + +#if IS_ENABLED(CONFIG_USB_DWC3_GADGET) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) +/** + * Configure the DBM with the BAM's data fifo. + * This function is called by the USB BAM Driver + * upon initialization. + * + * @ep - pointer to usb endpoint. + * @addr - address of data fifo. + * @size - size of data fifo. + * + */ +int msm_data_fifo_config(struct usb_ep *ep, phys_addr_t addr, + u32 size, u8 dst_pipe_idx) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dev_dbg(mdwc->dev, "%s\n", __func__); + + return dbm_data_fifo_config(mdwc->dbm, dep->number, addr, size, + dst_pipe_idx); +} + + +/** +* Cleanups for msm endpoint on request complete. +* +* Also call original request complete. +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to usb_request instance. +* +* @return int - 0 on success, negative on error. +*/ +static void dwc3_msm_req_complete_func(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_msm_req_complete *req_complete = NULL; + + /* Find original request complete function and remove it from list */ + list_for_each_entry(req_complete, &mdwc->req_complete_list, list_item) { + if (req_complete->req == request) + break; + } + if (!req_complete || req_complete->req != request) { + dev_err(dep->dwc->dev, "%s: could not find the request\n", + __func__); + return; + } + list_del(&req_complete->list_item); + + /* + * Release another one TRB to the pool since DBM queue took 2 TRBs + * (normal and link), and the dwc3/gadget.c :: dwc3_gadget_giveback + * released only one. + */ + dep->busy_slot++; + + /* Unconfigure dbm ep */ + dbm_ep_unconfig(mdwc->dbm, dep->number); + + /* + * If this is the last endpoint we unconfigured, than reset also + * the event buffers; unless unconfiguring the ep due to lpm, + * in which case the event buffer only gets reset during the + * block reset. + */ + if (0 == dbm_get_num_of_eps_configured(mdwc->dbm) && + !dbm_reset_ep_after_lpm(mdwc->dbm)) + dbm_event_buffer_config(mdwc->dbm, 0, 0, 0); + + /* + * Call original complete function, notice that dwc->lock is already + * taken by the caller of this function (dwc3_gadget_giveback()). + */ + request->complete = req_complete->orig_complete; + if (request->complete) + request->complete(ep, request); + + kfree(req_complete); +} + + +/** +* Helper function +* +* Reset DBM endpoint. +* +* @mdwc - pointer to dwc3_msm instance. +* @dep - pointer to dwc3_ep instance. +* +* @return int - 0 on success, negative on error. +*/ +static int __dwc3_msm_dbm_ep_reset(struct dwc3_msm *mdwc, struct dwc3_ep *dep) +{ + int ret; + + dev_dbg(mdwc->dev, "Resetting dbm endpoint %d\n", dep->number); + + /* Reset the dbm endpoint */ + ret = dbm_ep_soft_reset(mdwc->dbm, dep->number, true); + if (ret) { + dev_err(mdwc->dev, "%s: failed to assert dbm ep reset\n", + __func__); + return ret; + } + + /* + * The necessary delay between asserting and deasserting the dbm ep + * reset is based on the number of active endpoints. If there is more + * than one endpoint, a 1 msec delay is required. Otherwise, a shorter + * delay will suffice. + */ + if (dbm_get_num_of_eps_configured(mdwc->dbm) > 1) + usleep_range(1000, 1200); + else + udelay(10); + ret = dbm_ep_soft_reset(mdwc->dbm, dep->number, false); + if (ret) { + dev_err(mdwc->dev, "%s: failed to deassert dbm ep reset\n", + __func__); + return ret; + } + + return 0; +} + +/** +* Reset the DBM endpoint which is linked to the given USB endpoint. +* +* @usb_ep - pointer to usb_ep instance. +* +* @return int - 0 on success, negative on error. +*/ + +int msm_dwc3_reset_dbm_ep(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + return __dwc3_msm_dbm_ep_reset(mdwc, dep); +} +EXPORT_SYMBOL(msm_dwc3_reset_dbm_ep); + + +/** +* Helper function. +* See the header of the dwc3_msm_ep_queue function. +* +* @dwc3_ep - pointer to dwc3_ep instance. +* @req - pointer to dwc3_request instance. +* +* @return int - 0 on success, negative on error. +*/ +static int __dwc3_msm_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3_trb *trb; + struct dwc3_trb *trb_link; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret = 0; + + /* We push the request to the dep->req_queued list to indicate that + * this request is issued with start transfer. The request will be out + * from this list in 2 cases. The first is that the transfer will be + * completed (not if the transfer is endless using a circular TRBs with + * with link TRB). The second case is an option to do stop stransfer, + * this can be initiated by the function driver when calling dequeue. + */ + req->queued = true; + list_add_tail(&req->list, &dep->req_queued); + + /* First, prepare a normal TRB, point to the fake buffer */ + trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; + dep->free_slot++; + memset(trb, 0, sizeof(*trb)); + + req->trb = trb; + trb->bph = DBM_TRB_BIT | DBM_TRB_DMA | DBM_TRB_EP_NUM(dep->number); + trb->size = DWC3_TRB_SIZE_LENGTH(req->request.length); + trb->ctrl = DWC3_TRBCTL_NORMAL | DWC3_TRB_CTRL_HWO | + DWC3_TRB_CTRL_CHN | (req->direction ? 0 : DWC3_TRB_CTRL_CSP); + req->trb_dma = dwc3_trb_dma_offset(dep, trb); + + /* Second, prepare a Link TRB that points to the first TRB*/ + trb_link = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; + dep->free_slot++; + memset(trb_link, 0, sizeof *trb_link); + + trb_link->bpl = lower_32_bits(req->trb_dma); + trb_link->bph = DBM_TRB_BIT | + DBM_TRB_DMA | DBM_TRB_EP_NUM(dep->number); + trb_link->size = 0; + trb_link->ctrl = DWC3_TRBCTL_LINK_TRB | DWC3_TRB_CTRL_HWO; + + /* + * Now start the transfer + */ + memset(¶ms, 0, sizeof(params)); + params.param0 = 0; /* TDAddr High */ + params.param1 = lower_32_bits(req->trb_dma); /* DAddr Low */ + + /* DBM requires IOC to be set */ + cmd = DWC3_DEPCMD_STARTTRANSFER | DWC3_DEPCMD_CMDIOC; + ret = dwc3_send_gadget_ep_cmd(dep->dwc, dep->number, cmd, ¶ms); + if (ret < 0) { + dev_dbg(dep->dwc->dev, + "%s: failed to send STARTTRANSFER command\n", + __func__); + + list_del(&req->list); + return ret; + } + dep->flags |= DWC3_EP_BUSY; + dep->resource_index = dwc3_gadget_ep_get_transfer_index(dep->dwc, + dep->number); + + return ret; +} + +/** +* Queue a usb request to the DBM endpoint. +* This function should be called after the endpoint +* was enabled by the ep_enable. +* +* This function prepares special structure of TRBs which +* is familiar with the DBM HW, so it will possible to use +* this endpoint in DBM mode. +* +* The TRBs prepared by this function, is one normal TRB +* which point to a fake buffer, followed by a link TRB +* that points to the first TRB. +* +* The API of this function follow the regular API of +* usb_ep_queue (see usb_ep_ops in include/linuk/usb/gadget.h). +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to usb_request instance. +* @gfp_flags - possible flags. +* +* @return int - 0 on success, negative on error. +*/ +static int dwc3_msm_ep_queue(struct usb_ep *ep, + struct usb_request *request, gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_msm_req_complete *req_complete; + unsigned long flags; + int ret = 0, size; + bool superspeed; + + /* + * We must obtain the lock of the dwc3 core driver, + * including disabling interrupts, so we will be sure + * that we are the only ones that configure the HW device + * core and ensure that we queuing the request will finish + * as soon as possible so we will release back the lock. + */ + spin_lock_irqsave(&dwc->lock, flags); + if (!dep->endpoint.desc) { + dev_err(mdwc->dev, + "%s: trying to queue request %p to disabled ep %s\n", + __func__, request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + + if (!mdwc->original_ep_ops[dep->number]) { + dev_err(mdwc->dev, + "ep [%s,%d] was unconfigured as msm endpoint\n", + ep->name, dep->number); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + if (!request) { + dev_err(mdwc->dev, "%s: request is NULL\n", __func__); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + if (!(request->udc_priv & MSM_SPS_MODE)) { + dev_err(mdwc->dev, "%s: sps mode is not set\n", + __func__); + + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + /* HW restriction regarding TRB size (8KB) */ + if (req->request.length < 0x2000) { + dev_err(mdwc->dev, "%s: Min TRB size is 8KB\n", __func__); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + + if (dep->number == 0 || dep->number == 1) { + dev_err(mdwc->dev, + "%s: trying to queue dbm request %p to control ep %s\n", + __func__, request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + + if (dep->busy_slot != dep->free_slot || !list_empty(&dep->request_list) + || !list_empty(&dep->req_queued)) { + dev_err(mdwc->dev, + "%s: trying to queue dbm request %p tp ep %s\n", + __func__, request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + dep->busy_slot = 0; + dep->free_slot = 0; + + /* + * Override req->complete function, but before doing that, + * store it's original pointer in the req_complete_list. + */ + req_complete = kzalloc(sizeof(*req_complete), gfp_flags); + if (!req_complete) { + dev_err(mdwc->dev, "%s: not enough memory\n", __func__); + spin_unlock_irqrestore(&dwc->lock, flags); + return -ENOMEM; + } + req_complete->req = request; + req_complete->orig_complete = request->complete; + list_add_tail(&req_complete->list_item, &mdwc->req_complete_list); + request->complete = dwc3_msm_req_complete_func; + + dev_vdbg(dwc->dev, "%s: queing request %pK to ep %s length %d\n", + __func__, request, ep->name, request->length); + size = dwc3_msm_read_reg(mdwc->base, DWC3_GEVNTSIZ(0)); + dbm_event_buffer_config(mdwc->dbm, + dwc3_msm_read_reg(mdwc->base, DWC3_GEVNTADRLO(0)), + dwc3_msm_read_reg(mdwc->base, DWC3_GEVNTADRHI(0)), + DWC3_GEVNTSIZ_SIZE(size)); + + ret = __dwc3_msm_ep_queue(dep, req); + if (ret < 0) { + dev_err(mdwc->dev, + "error %d after calling __dwc3_msm_ep_queue\n", ret); + goto err; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + superspeed = dwc3_msm_is_dev_superspeed(mdwc); + dbm_set_speed(mdwc->dbm, (u8)superspeed); + + return 0; + +err: + list_del(&req_complete->list_item); + spin_unlock_irqrestore(&dwc->lock, flags); + kfree(req_complete); + return ret; +} + +/* +* Returns XferRscIndex for the EP. This is stored at StartXfer GSI EP OP +* +* @usb_ep - pointer to usb_ep instance. +* +* @return int - XferRscIndex +*/ +static inline int gsi_get_xfer_index(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + + return dep->resource_index; +} + +/* +* Fills up the GSI channel information needed in call to IPA driver +* for GSI channel creation. +* +* @usb_ep - pointer to usb_ep instance. +* @ch_info - output parameter with requested channel info +*/ +static void gsi_get_channel_info(struct usb_ep *ep, + struct gsi_channel_info *ch_info) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + int last_trb_index = 0; + struct dwc3 *dwc = dep->dwc; + struct usb_gsi_request *request = ch_info->ch_req; + + /* Provide physical USB addresses for DEPCMD and GEVENTCNT registers */ + ch_info->depcmd_low_addr = (u32)(dwc->reg_phys + + DWC3_DEPCMD(dep->number)); + ch_info->depcmd_hi_addr = 0; + + ch_info->xfer_ring_base_addr = dwc3_trb_dma_offset(dep, + &dep->trb_pool[0]); + /* Convert to multipled of 1KB */ + ch_info->const_buffer_size = request->buf_len/1024; + + /* IN direction */ + if (dep->direction) { + /* + * Multiply by size of each TRB for xfer_ring_len in bytes. + * 2n + 2 TRBs as per GSI h/w requirement. n Xfer TRBs + 1 + * extra Xfer TRB followed by n ZLP TRBs + 1 LINK TRB. + */ + ch_info->xfer_ring_len = (2 * request->num_bufs + 2) * 0x10; + last_trb_index = 2 * request->num_bufs + 2; + } else { /* OUT direction */ + /* + * Multiply by size of each TRB for xfer_ring_len in bytes. + * n + 1 TRBs as per GSI h/w requirement. n Xfer TRBs + 1 + * LINK TRB. + */ + ch_info->xfer_ring_len = (request->num_bufs + 1) * 0x10; + last_trb_index = request->num_bufs + 1; + } + + /* Store last 16 bits of LINK TRB address as per GSI hw requirement */ + ch_info->last_trb_addr = (dwc3_trb_dma_offset(dep, + &dep->trb_pool[last_trb_index - 1]) & 0x0000FFFF); + ch_info->gevntcount_low_addr = (u32)(dwc->reg_phys + + DWC3_GEVNTCOUNT(ep->ep_intr_num)); + ch_info->gevntcount_hi_addr = 0; + + dev_dbg(dwc->dev, + "depcmd_laddr=%x last_trb_addr=%x gevtcnt_laddr=%x gevtcnt_haddr=%x", + ch_info->depcmd_low_addr, ch_info->last_trb_addr, + ch_info->gevntcount_low_addr, ch_info->gevntcount_hi_addr); +} + +/* +* Perform StartXfer on GSI EP. Stores XferRscIndex. +* +* @usb_ep - pointer to usb_ep instance. +* +* @return int - 0 on success +*/ +static int gsi_startxfer_for_ep(struct usb_ep *ep) +{ + int ret; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + memset(¶ms, 0, sizeof(params)); + params.param0 = GSI_TRB_ADDR_BIT_53_MASK | GSI_TRB_ADDR_BIT_55_MASK; + params.param0 |= (ep->ep_intr_num << 16); + params.param1 = lower_32_bits(dwc3_trb_dma_offset(dep, + &dep->trb_pool[0])); + cmd = DWC3_DEPCMD_STARTTRANSFER; + cmd |= DWC3_DEPCMD_PARAM(0); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + + if (ret < 0) + dev_dbg(dwc->dev, "Fail StrtXfr on GSI EP#%d\n", dep->number); + dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + dev_dbg(dwc->dev, "XferRsc = %x", dep->resource_index); + return ret; +} + +/* +* Store Ring Base and Doorbell Address for GSI EP +* for GSI channel creation. +* +* @usb_ep - pointer to usb_ep instance. +* @dbl_addr - Doorbell address obtained from IPA driver +*/ +static void gsi_store_ringbase_dbl_info(struct usb_ep *ep, u32 dbl_addr) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + int n = ep->ep_intr_num - 1; + + dwc3_msm_write_reg(mdwc->base, GSI_RING_BASE_ADDR_L(n), + dwc3_trb_dma_offset(dep, &dep->trb_pool[0])); + dwc3_msm_write_reg(mdwc->base, GSI_DBL_ADDR_L(n), dbl_addr); + + dev_dbg(mdwc->dev, "Ring Base Addr %d = %x", n, + dwc3_msm_read_reg(mdwc->base, GSI_RING_BASE_ADDR_L(n))); + dev_dbg(mdwc->dev, "GSI DB Addr %d = %x", n, + dwc3_msm_read_reg(mdwc->base, GSI_DBL_ADDR_L(n))); +} + +/* +* Rings Doorbell for IN GSI Channel +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to GSI request. This is used to pass in the +* address of the GSI doorbell obtained from IPA driver +*/ +static void gsi_ring_in_db(struct usb_ep *ep, struct usb_gsi_request *request) +{ + void __iomem *gsi_dbl_address_lsb; + void __iomem *gsi_dbl_address_msb; + dma_addr_t offset; + u64 dbl_addr = *((u64 *)request->buf_base_addr); + u32 dbl_lo_addr = (dbl_addr & 0xFFFFFFFF); + u32 dbl_hi_addr = (dbl_addr >> 32); + u32 num_trbs = (request->num_bufs * 2 + 2); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + gsi_dbl_address_lsb = devm_ioremap_nocache(mdwc->dev, + dbl_lo_addr, sizeof(u32)); + if (!gsi_dbl_address_lsb) + dev_dbg(mdwc->dev, "Failed to get GSI DBL address LSB\n"); + + gsi_dbl_address_msb = devm_ioremap_nocache(mdwc->dev, + dbl_hi_addr, sizeof(u32)); + if (!gsi_dbl_address_msb) + dev_dbg(mdwc->dev, "Failed to get GSI DBL address MSB\n"); + + offset = dwc3_trb_dma_offset(dep, &dep->trb_pool[num_trbs-1]); + dev_dbg(mdwc->dev, "Writing link TRB addr: %pa to %pK (%x)\n", + &offset, gsi_dbl_address_lsb, dbl_lo_addr); + + writel_relaxed(offset, gsi_dbl_address_lsb); + writel_relaxed(0, gsi_dbl_address_msb); +} + +/* +* Sets HWO bit for TRBs and performs UpdateXfer for OUT EP. +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to GSI request. Used to determine num of TRBs for OUT EP. +* +* @return int - 0 on success +*/ +static int gsi_updatexfer_for_ep(struct usb_ep *ep, + struct usb_gsi_request *request) +{ + int i; + int ret; + u32 cmd; + int num_trbs = request->num_bufs + 1; + struct dwc3_trb *trb; + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + for (i = 0; i < num_trbs - 1; i++) { + trb = &dep->trb_pool[i]; + trb->ctrl |= DWC3_TRB_CTRL_HWO; + } + + memset(¶ms, 0, sizeof(params)); + cmd = DWC3_DEPCMD_UPDATETRANSFER; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + dep->flags |= DWC3_EP_BUSY; + if (ret < 0) + dev_dbg(dwc->dev, "UpdateXfr fail on GSI EP#%d\n", dep->number); + return ret; +} + +/* +* Perform EndXfer on particular GSI EP. +* +* @usb_ep - pointer to usb_ep instance. +*/ +static void gsi_endxfer_for_ep(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + dwc3_stop_active_transfer(dwc, dep->number, true); +} + +/* +* Allocates and configures TRBs for GSI EPs. +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to GSI request. +* +* @return int - 0 on success +*/ +static int gsi_prepare_trbs(struct usb_ep *ep, struct usb_gsi_request *req) +{ + int i = 0; + dma_addr_t buffer_addr = req->dma; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_trb *trb; + int num_trbs = (dep->direction) ? (2 * (req->num_bufs) + 2) + : (req->num_bufs + 1); + + dep->trb_dma_pool = dma_pool_create(ep->name, dwc->dev, + num_trbs * sizeof(struct dwc3_trb), + num_trbs * sizeof(struct dwc3_trb), 0); + if (!dep->trb_dma_pool) { + dev_err(dep->dwc->dev, "failed to alloc trb dma pool for %s\n", + dep->name); + return -ENOMEM; + } + + dep->num_trbs = num_trbs; + + dep->trb_pool = dma_pool_alloc(dep->trb_dma_pool, + GFP_KERNEL, &dep->trb_pool_dma); + if (!dep->trb_pool) { + dev_err(dep->dwc->dev, "failed to allocate trb pool for %s\n", + dep->name); + return -ENOMEM; + } + + /* IN direction */ + if (dep->direction) { + for (i = 0; i < num_trbs ; i++) { + trb = &dep->trb_pool[i]; + memset(trb, 0, sizeof(*trb)); + /* Set up first n+1 TRBs for ZLPs */ + if (i < (req->num_bufs + 1)) { + trb->bpl = 0; + trb->bph = 0; + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_NORMAL + | DWC3_TRB_CTRL_IOC; + continue; + } + + /* Setup n TRBs pointing to valid buffers */ + trb->bpl = lower_32_bits(buffer_addr); + trb->bph = 0; + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_NORMAL + | DWC3_TRB_CTRL_IOC; + buffer_addr += req->buf_len; + + /* Set up the Link TRB at the end */ + if (i == (num_trbs - 1)) { + trb->bpl = dwc3_trb_dma_offset(dep, + &dep->trb_pool[0]); + trb->bph = (1 << 23) | (1 << 21) + | (ep->ep_intr_num << 16); + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_LINK_TRB + | DWC3_TRB_CTRL_HWO; + } + } + } else { /* OUT direction */ + + for (i = 0; i < num_trbs ; i++) { + + trb = &dep->trb_pool[i]; + memset(trb, 0, sizeof(*trb)); + trb->bpl = lower_32_bits(buffer_addr); + trb->bph = 0; + trb->size = req->buf_len; + trb->ctrl = DWC3_TRBCTL_NORMAL | DWC3_TRB_CTRL_IOC + | DWC3_TRB_CTRL_CSP + | DWC3_TRB_CTRL_ISP_IMI; + buffer_addr += req->buf_len; + + /* Set up the Link TRB at the end */ + if (i == (num_trbs - 1)) { + trb->bpl = dwc3_trb_dma_offset(dep, + &dep->trb_pool[0]); + trb->bph = (1 << 23) | (1 << 21) + | (ep->ep_intr_num << 16); + trb->size = 0; + trb->ctrl = DWC3_TRBCTL_LINK_TRB + | DWC3_TRB_CTRL_HWO; + } + } + } + return 0; +} + +/* +* Frees TRBs for GSI EPs. +* +* @usb_ep - pointer to usb_ep instance. +* +*/ +static void gsi_free_trbs(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + + if (dep->endpoint.ep_type == EP_TYPE_NORMAL) + return; + + /* Free TRBs and TRB pool for EP */ + if (dep->trb_dma_pool) { + dma_pool_free(dep->trb_dma_pool, dep->trb_pool, + dep->trb_pool_dma); + dma_pool_destroy(dep->trb_dma_pool); + dep->trb_pool = NULL; + dep->trb_pool_dma = 0; + dep->trb_dma_pool = NULL; + } +} +/* +* Configures GSI EPs. For GSI EPs we need to set interrupter numbers. +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to GSI request. +*/ +static void gsi_configure_ep(struct usb_ep *ep, struct usb_gsi_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct dwc3_gadget_ep_cmd_params params; + const struct usb_endpoint_descriptor *desc = ep->desc; + const struct usb_ss_ep_comp_descriptor *comp_desc = ep->comp_desc; + u32 reg; + int ret; + + memset(¶ms, 0x00, sizeof(params)); + + /* Configure GSI EP */ + params.param0 = DWC3_DEPCFG_EP_TYPE(usb_endpoint_type(desc)) + | DWC3_DEPCFG_MAX_PACKET_SIZE(usb_endpoint_maxp(desc)); + + /* Burst size is only needed in SuperSpeed mode */ + if (dwc->gadget.speed == USB_SPEED_SUPER) { + u32 burst = dep->endpoint.maxburst - 1; + + params.param0 |= DWC3_DEPCFG_BURST_SIZE(burst); + } + + if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { + params.param1 |= DWC3_DEPCFG_STREAM_CAPABLE + | DWC3_DEPCFG_STREAM_EVENT_EN; + dep->stream_capable = true; + } + + /* Set EP number */ + params.param1 |= DWC3_DEPCFG_EP_NUMBER(dep->number); + + /* Set interrupter number for GSI endpoints */ + params.param1 |= DWC3_DEPCFG_INT_NUM(ep->ep_intr_num); + + /* Enable XferInProgress and XferComplete Interrupts */ + params.param1 |= DWC3_DEPCFG_XFER_COMPLETE_EN; + params.param1 |= DWC3_DEPCFG_XFER_IN_PROGRESS_EN; + params.param1 |= DWC3_DEPCFG_FIFO_ERROR_EN; + /* + * We must use the lower 16 TX FIFOs even though + * HW might have more + */ + /* Remove FIFO Number for GSI EP*/ + if (dep->direction) + params.param0 |= DWC3_DEPCFG_FIFO_NUMBER(dep->number >> 1); + + params.param0 |= DWC3_DEPCFG_ACTION_INIT; + + dev_dbg(mdwc->dev, "Set EP config to params = %x %x %x, for %s\n", + params.param0, params.param1, params.param2, dep->name); + + dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETEPCONFIG, ¶ms); + + /* Set XferRsc Index for GSI EP */ + if (!(dep->flags & DWC3_EP_ENABLED)) { + ret = dwc3_gadget_resize_tx_fifos(dwc, dep); + if (ret) + return; + + memset(¶ms, 0x00, sizeof(params)); + params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); + dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETTRANSFRESOURCE, ¶ms); + + dep->endpoint.desc = desc; + dep->comp_desc = comp_desc; + dep->type = usb_endpoint_type(desc); + dep->flags |= DWC3_EP_ENABLED; + reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); + reg |= DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + } + +} + +/* +* Enables USB wrapper for GSI +* +* @usb_ep - pointer to usb_ep instance. +*/ +static void gsi_enable(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG, GSI_CLK_EN_MASK, 1); + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG, GSI_RESTART_DBL_PNTR_MASK, 1); + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG, GSI_RESTART_DBL_PNTR_MASK, 0); + dev_dbg(mdwc->dev, "%s: Enable GSI\n", __func__); + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG, GSI_EN_MASK, 1); +} + +/* +* Block or allow doorbell towards GSI +* +* @usb_ep - pointer to usb_ep instance. +* @request - pointer to GSI request. In this case num_bufs is used as a bool +* to set or clear the doorbell bit +*/ +static void gsi_set_clear_dbell(struct usb_ep *ep, + bool block_db) +{ + + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + dwc3_msm_write_reg_field(mdwc->base, + GSI_GENERAL_CFG_REG, BLOCK_GSI_WR_GO_MASK, block_db); +} + +/* +* Performs necessary checks before stopping GSI channels +* +* @usb_ep - pointer to usb_ep instance to access DWC3 regs +*/ +static bool gsi_check_ready_to_suspend(struct usb_ep *ep, bool f_suspend) +{ + u32 timeout = 500; + u32 reg = 0; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + while (dwc3_msm_read_reg_field(mdwc->base, + GSI_IF_STS, GSI_WR_CTRL_STATE_MASK)) { + if (!timeout--) { + dev_err(mdwc->dev, + "Unable to suspend GSI ch. WR_CTRL_STATE != 0\n"); + return false; + } + usleep_range(20, 22); + } + /* Check for U3 only if we are not handling Function Suspend */ + if (!f_suspend) { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + if (DWC3_DSTS_USBLNKST(reg) != DWC3_LINK_STATE_U3) { + dev_err(mdwc->dev, "Unable to suspend GSI ch\n"); + return false; + } + } + + return true; +} + + +/** +* Performs GSI operations or GSI EP related operations. +* +* @usb_ep - pointer to usb_ep instance. +* @op_data - pointer to opcode related data. +* @op - GSI related or GSI EP related op code. +* +* @return int - 0 on success, negative on error. +* Also returns XferRscIdx for GSI_EP_OP_GET_XFER_IDX. +*/ +static int dwc3_msm_gsi_ep_op(struct usb_ep *ep, + void *op_data, enum gsi_ep_op op) +{ + u32 ret = 0; + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct usb_gsi_request *request; + struct gsi_channel_info *ch_info; + bool block_db, f_suspend; + unsigned long flags; + + switch (op) { + case GSI_EP_OP_PREPARE_TRBS: + request = (struct usb_gsi_request *)op_data; + dev_dbg(mdwc->dev, "EP_OP_PREPARE_TRBS for %s\n", ep->name); + ret = gsi_prepare_trbs(ep, request); + break; + case GSI_EP_OP_FREE_TRBS: + dev_dbg(mdwc->dev, "EP_OP_FREE_TRBS for %s\n", ep->name); + gsi_free_trbs(ep); + break; + case GSI_EP_OP_CONFIG: + request = (struct usb_gsi_request *)op_data; + dev_dbg(mdwc->dev, "EP_OP_CONFIG for %s\n", ep->name); + spin_lock_irqsave(&dwc->lock, flags); + gsi_configure_ep(ep, request); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_STARTXFER: + dev_dbg(mdwc->dev, "EP_OP_STARTXFER for %s\n", ep->name); + spin_lock_irqsave(&dwc->lock, flags); + ret = gsi_startxfer_for_ep(ep); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_GET_XFER_IDX: + dev_dbg(mdwc->dev, "EP_OP_GET_XFER_IDX for %s\n", ep->name); + ret = gsi_get_xfer_index(ep); + break; + case GSI_EP_OP_STORE_DBL_INFO: + dev_dbg(mdwc->dev, "EP_OP_STORE_DBL_INFO\n"); + gsi_store_ringbase_dbl_info(ep, *((u32 *)op_data)); + break; + case GSI_EP_OP_ENABLE_GSI: + dev_dbg(mdwc->dev, "EP_OP_ENABLE_GSI\n"); + gsi_enable(ep); + break; + case GSI_EP_OP_GET_CH_INFO: + ch_info = (struct gsi_channel_info *)op_data; + gsi_get_channel_info(ep, ch_info); + break; + case GSI_EP_OP_RING_IN_DB: + request = (struct usb_gsi_request *)op_data; + dev_dbg(mdwc->dev, "RING IN EP DB\n"); + gsi_ring_in_db(ep, request); + break; + case GSI_EP_OP_UPDATEXFER: + request = (struct usb_gsi_request *)op_data; + dev_dbg(mdwc->dev, "EP_OP_UPDATEXFER\n"); + spin_lock_irqsave(&dwc->lock, flags); + ret = gsi_updatexfer_for_ep(ep, request); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_ENDXFER: + request = (struct usb_gsi_request *)op_data; + dev_dbg(mdwc->dev, "EP_OP_ENDXFER for %s\n", ep->name); + spin_lock_irqsave(&dwc->lock, flags); + gsi_endxfer_for_ep(ep); + spin_unlock_irqrestore(&dwc->lock, flags); + break; + case GSI_EP_OP_SET_CLR_BLOCK_DBL: + block_db = *((bool *)op_data); + dev_dbg(mdwc->dev, "EP_OP_SET_CLR_BLOCK_DBL %d\n", + block_db); + gsi_set_clear_dbell(ep, block_db); + break; + case GSI_EP_OP_CHECK_FOR_SUSPEND: + dev_dbg(mdwc->dev, "EP_OP_CHECK_FOR_SUSPEND\n"); + f_suspend = *((bool *)op_data); + ret = gsi_check_ready_to_suspend(ep, f_suspend); + break; + case GSI_EP_OP_DISABLE: + dev_dbg(mdwc->dev, "EP_OP_DISABLE\n"); + ret = ep->ops->disable(ep); + break; + default: + dev_err(mdwc->dev, "%s: Invalid opcode GSI EP\n", __func__); + } + + return ret; +} + +/** + * Configure MSM endpoint. + * This function do specific configurations + * to an endpoint which need specific implementaion + * in the MSM architecture. + * + * This function should be called by usb function/class + * layer which need a support from the specific MSM HW + * which wrap the USB3 core. (like GSI or DBM specific endpoints) + * + * @ep - a pointer to some usb_ep instance + * + * @return int - 0 on success, negetive on error. + */ +int msm_ep_config(struct usb_ep *ep, struct usb_request *request) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct usb_ep_ops *new_ep_ops; + int ret = 0; + u8 bam_pipe; + bool producer; + bool disable_wb; + bool internal_mem; + bool ioc; + unsigned long flags; + + + spin_lock_irqsave(&dwc->lock, flags); + /* Save original ep ops for future restore*/ + if (mdwc->original_ep_ops[dep->number]) { + dev_err(mdwc->dev, + "ep [%s,%d] already configured as msm endpoint\n", + ep->name, dep->number); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + mdwc->original_ep_ops[dep->number] = ep->ops; + + /* Set new usb ops as we like */ + new_ep_ops = kzalloc(sizeof(struct usb_ep_ops), GFP_ATOMIC); + if (!new_ep_ops) { + dev_err(mdwc->dev, + "%s: unable to allocate mem for new usb ep ops\n", + __func__); + spin_unlock_irqrestore(&dwc->lock, flags); + return -ENOMEM; + } + (*new_ep_ops) = (*ep->ops); + new_ep_ops->queue = dwc3_msm_ep_queue; + new_ep_ops->gsi_ep_op = dwc3_msm_gsi_ep_op; + ep->ops = new_ep_ops; + + if (!mdwc->dbm || !request || (dep->endpoint.ep_type == EP_TYPE_GSI)) { + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; + } + + /* + * Configure the DBM endpoint if required. + */ + bam_pipe = request->udc_priv & MSM_PIPE_ID_MASK; + producer = ((request->udc_priv & MSM_PRODUCER) ? true : false); + disable_wb = ((request->udc_priv & MSM_DISABLE_WB) ? true : false); + internal_mem = ((request->udc_priv & MSM_INTERNAL_MEM) ? true : false); + ioc = ((request->udc_priv & MSM_ETD_IOC) ? true : false); + + ret = dbm_ep_config(mdwc->dbm, dep->number, bam_pipe, producer, + disable_wb, internal_mem, ioc); + if (ret < 0) { + dev_err(mdwc->dev, + "error %d after calling dbm_ep_config\n", ret); + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} +EXPORT_SYMBOL(msm_ep_config); + +/** + * Un-configure MSM endpoint. + * Tear down configurations done in the + * dwc3_msm_ep_config function. + * + * @ep - a pointer to some usb_ep instance + * + * @return int - 0 on success, negative on error. + */ +int msm_ep_unconfig(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + struct usb_ep_ops *old_ep_ops; + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + /* Restore original ep ops */ + if (!mdwc->original_ep_ops[dep->number]) { + dev_err(mdwc->dev, + "ep [%s,%d] was not configured as msm endpoint\n", + ep->name, dep->number); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EINVAL; + } + old_ep_ops = (struct usb_ep_ops *)ep->ops; + ep->ops = mdwc->original_ep_ops[dep->number]; + mdwc->original_ep_ops[dep->number] = NULL; + kfree(old_ep_ops); + + /* + * Do HERE more usb endpoint un-configurations + * which are specific to MSM. + */ + if (!mdwc->dbm || (dep->endpoint.ep_type == EP_TYPE_GSI)) { + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; + } + + if (dep->busy_slot == dep->free_slot && list_empty(&dep->request_list) + && list_empty(&dep->req_queued)) { + dev_dbg(mdwc->dev, + "%s: request is not queued, disable DBM ep for ep %s\n", + __func__, ep->name); + /* Unconfigure dbm ep */ + dbm_ep_unconfig(mdwc->dbm, dep->number); + + /* + * If this is the last endpoint we unconfigured, than reset also + * the event buffers; unless unconfiguring the ep due to lpm, + * in which case the event buffer only gets reset during the + * block reset. + */ + if (dbm_get_num_of_eps_configured(mdwc->dbm) == 0 && + !dbm_reset_ep_after_lpm(mdwc->dbm)) + dbm_event_buffer_config(mdwc->dbm, 0, 0, 0); + } + + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} +EXPORT_SYMBOL(msm_ep_unconfig); +#endif /* (CONFIG_USB_DWC3_GADGET) || (CONFIG_USB_DWC3_DUAL_ROLE) */ + +static void dwc3_resume_work(struct work_struct *w); + +static void dwc3_restart_usb_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + restart_usb_work); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + unsigned timeout = 50; + + dev_dbg(mdwc->dev, "%s\n", __func__); + + if (atomic_read(&dwc->in_lpm) || !dwc->is_drd) { + dev_dbg(mdwc->dev, "%s failed!!!\n", __func__); + return; + } + + /* guard against concurrent VBUS handling */ + mdwc->in_restart = true; + + if (!mdwc->vbus_active) { + dev_dbg(mdwc->dev, "%s bailing out in disconnect\n", __func__); + dwc->err_evt_seen = false; + mdwc->in_restart = false; + return; + } + + dbg_event(0xFF, "RestartUSB", 0); + + /* Reset active USB connection */ + dwc3_resume_work(&mdwc->resume_work); + + /* Make sure disconnect is processed before sending connect */ + while (--timeout && !pm_runtime_suspended(mdwc->dev)) + msleep(20); + + if (!timeout) { + dev_dbg(mdwc->dev, + "Not in LPM after disconnect, forcing suspend...\n"); + dbg_event(0xFF, "ReStart:RT SUSP", + atomic_read(&mdwc->dev->power.usage_count)); + pm_runtime_suspend(mdwc->dev); + } + + mdwc->in_restart = false; + /* Force reconnect only if cable is still connected */ + if (mdwc->vbus_active) { + if (mdwc->override_usb_speed) { + dwc->maximum_speed = mdwc->override_usb_speed; + dwc->gadget.max_speed = dwc->maximum_speed; + dbg_event(0xFF, "override_usb_speed", + mdwc->override_usb_speed); + mdwc->override_usb_speed = 0; + } + + dwc3_resume_work(&mdwc->resume_work); + } + + dwc->err_evt_seen = false; + flush_delayed_work(&mdwc->sm_work); +} + +static int msm_dwc3_usbdev_notify(struct notifier_block *self, + unsigned long action, void *priv) +{ + struct dwc3_msm *mdwc = container_of(self, struct dwc3_msm, usbdev_nb); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + struct usb_bus *bus = priv; + + /* Interested only in recovery when HC dies */ + if (action != USB_BUS_DIED) + return 0; + + dev_dbg(mdwc->dev, "%s initiate recovery from hc_died\n", __func__); + /* Recovery already under process */ + if (mdwc->hc_died) + return 0; + + if (bus->controller != &dwc->xhci->dev) { + dev_dbg(mdwc->dev, "%s event for diff HCD\n", __func__); + return 0; + } + + mdwc->hc_died = true; + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); + return 0; +} + + +/* + * Check whether the DWC3 requires resetting the ep + * after going to Low Power Mode (lpm) + */ +bool msm_dwc3_reset_ep_after_lpm(struct usb_gadget *gadget) +{ + struct dwc3 *dwc = container_of(gadget, struct dwc3, gadget); + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + + return dbm_reset_ep_after_lpm(mdwc->dbm); +} +EXPORT_SYMBOL(msm_dwc3_reset_ep_after_lpm); + +/* + * Config Global Distributed Switch Controller (GDSC) + * to support controller power collapse + */ +static int dwc3_msm_config_gdsc(struct dwc3_msm *mdwc, int on) +{ + int ret; + + if (IS_ERR_OR_NULL(mdwc->dwc3_gdsc)) + return -EPERM; + + if (on) { + ret = regulator_enable(mdwc->dwc3_gdsc); + if (ret) { + dev_err(mdwc->dev, "unable to enable usb3 gdsc\n"); + return ret; + } + } else { + ret = regulator_disable(mdwc->dwc3_gdsc); + if (ret) { + dev_err(mdwc->dev, "unable to disable usb3 gdsc\n"); + return ret; + } + } + + return ret; +} + +static int dwc3_msm_link_clk_reset(struct dwc3_msm *mdwc, bool assert) +{ + int ret = 0; + + if (assert) { + disable_irq(mdwc->pwr_event_irq); + /* Using asynchronous block reset to the hardware */ + dev_dbg(mdwc->dev, "block_reset ASSERT\n"); + clk_disable_unprepare(mdwc->utmi_clk); + clk_disable_unprepare(mdwc->sleep_clk); + clk_disable_unprepare(mdwc->core_clk); + clk_disable_unprepare(mdwc->iface_clk); + ret = reset_control_assert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "dwc3 core_reset assert failed\n"); + } else { + dev_dbg(mdwc->dev, "block_reset DEASSERT\n"); + ret = reset_control_deassert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "dwc3 core_reset deassert failed\n"); + ndelay(200); + clk_prepare_enable(mdwc->iface_clk); + clk_prepare_enable(mdwc->core_clk); + clk_prepare_enable(mdwc->sleep_clk); + clk_prepare_enable(mdwc->utmi_clk); + enable_irq(mdwc->pwr_event_irq); + } + + return ret; +} + +static void dwc3_msm_update_ref_clk(struct dwc3_msm *mdwc) +{ + u32 guctl, gfladj = 0; + + guctl = dwc3_msm_read_reg(mdwc->base, DWC3_GUCTL); + guctl &= ~DWC3_GUCTL_REFCLKPER; + + /* GFLADJ register is used starting with revision 2.50a */ + if (dwc3_msm_read_reg(mdwc->base, DWC3_GSNPSID) >= DWC3_REVISION_250A) { + gfladj = dwc3_msm_read_reg(mdwc->base, DWC3_GFLADJ); + gfladj &= ~DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1; + gfladj &= ~DWC3_GFLADJ_REFCLK_240MHZ_DECR; + gfladj &= ~DWC3_GFLADJ_REFCLK_LPM_SEL; + gfladj &= ~DWC3_GFLADJ_REFCLK_FLADJ; + } + + /* Refer to SNPS Databook Table 6-55 for calculations used */ + switch (mdwc->utmi_clk_rate) { + case 19200000: + guctl |= 52 << __ffs(DWC3_GUCTL_REFCLKPER); + gfladj |= 12 << __ffs(DWC3_GFLADJ_REFCLK_240MHZ_DECR); + gfladj |= DWC3_GFLADJ_REFCLK_240MHZDECR_PLS1; + gfladj |= DWC3_GFLADJ_REFCLK_LPM_SEL; + gfladj |= 200 << __ffs(DWC3_GFLADJ_REFCLK_FLADJ); + break; + case 24000000: + guctl |= 41 << __ffs(DWC3_GUCTL_REFCLKPER); + gfladj |= 10 << __ffs(DWC3_GFLADJ_REFCLK_240MHZ_DECR); + gfladj |= DWC3_GFLADJ_REFCLK_LPM_SEL; + gfladj |= 2032 << __ffs(DWC3_GFLADJ_REFCLK_FLADJ); + break; + default: + dev_warn(mdwc->dev, "Unsupported utmi_clk_rate: %u\n", + mdwc->utmi_clk_rate); + break; + } + + dwc3_msm_write_reg(mdwc->base, DWC3_GUCTL, guctl); + if (gfladj) + dwc3_msm_write_reg(mdwc->base, DWC3_GFLADJ, gfladj); +} + +/* Initialize QSCRATCH registers for HSPHY and SSPHY operation */ +static void dwc3_msm_qscratch_reg_init(struct dwc3_msm *mdwc) +{ + if (dwc3_msm_read_reg(mdwc->base, DWC3_GSNPSID) < DWC3_REVISION_250A) + /* On older cores set XHCI_REV bit to specify revision 1.0 */ + dwc3_msm_write_reg_field(mdwc->base, QSCRATCH_GENERAL_CFG, + BIT(2), 1); + + /* + * Enable master clock for RAMs to allow BAM to access RAMs when + * RAM clock gating is enabled via DWC3's GCTL. Otherwise issues + * are seen where RAM clocks get turned OFF in SS mode + */ + dwc3_msm_write_reg(mdwc->base, CGCTL_REG, + dwc3_msm_read_reg(mdwc->base, CGCTL_REG) | 0x18); + +} + +static void dwc3_msm_vbus_draw_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + vbus_draw_work); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + dwc3_msm_gadget_vbus_draw(mdwc, dwc->vbus_draw); +} + +static void dwc3_msm_notify_event(struct dwc3 *dwc, unsigned event, + unsigned value) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dwc->dev->parent); + u32 reg; + + if (dwc->revision < DWC3_REVISION_230A) + return; + + switch (event) { + case DWC3_CONTROLLER_ERROR_EVENT: + dev_info(mdwc->dev, + "DWC3_CONTROLLER_ERROR_EVENT received, irq cnt %lu\n", + dwc->irq_cnt); + + dwc3_gadget_disable_irq(dwc); + + /* prevent core from generating interrupts until recovery */ + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GCTL); + reg |= DWC3_GCTL_CORESOFTRESET; + dwc3_msm_write_reg(mdwc->base, DWC3_GCTL, reg); + + /* restart USB which performs full reset and reconnect */ + schedule_work(&mdwc->restart_usb_work); + break; + case DWC3_CONTROLLER_RESET_EVENT: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_RESET_EVENT received\n"); + /* HS & SSPHYs get reset as part of core soft reset */ + dwc3_msm_qscratch_reg_init(mdwc); + break; + case DWC3_CONTROLLER_POST_RESET_EVENT: + dev_dbg(mdwc->dev, + "DWC3_CONTROLLER_POST_RESET_EVENT received\n"); + + /* + * Below sequence is used when controller is working without + * having ssphy and only USB high/full speed is supported. + */ + if (dwc->maximum_speed == USB_SPEED_HIGH || + dwc->maximum_speed == USB_SPEED_FULL) { + dwc3_msm_write_reg(mdwc->base, QSCRATCH_GENERAL_CFG, + dwc3_msm_read_reg(mdwc->base, + QSCRATCH_GENERAL_CFG) + | PIPE_UTMI_CLK_DIS); + + usleep_range(2, 5); + + + dwc3_msm_write_reg(mdwc->base, QSCRATCH_GENERAL_CFG, + dwc3_msm_read_reg(mdwc->base, + QSCRATCH_GENERAL_CFG) + | PIPE_UTMI_CLK_SEL + | PIPE3_PHYSTATUS_SW); + + usleep_range(2, 5); + + dwc3_msm_write_reg(mdwc->base, QSCRATCH_GENERAL_CFG, + dwc3_msm_read_reg(mdwc->base, + QSCRATCH_GENERAL_CFG) + & ~PIPE_UTMI_CLK_DIS); + } + + dwc3_msm_update_ref_clk(mdwc); + dwc->tx_fifo_size = mdwc->tx_fifo_size; + break; + case DWC3_CONTROLLER_CONNDONE_EVENT: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_CONNDONE_EVENT received\n"); + /* + * Add power event if the dbm indicates coming out of L1 by + * interrupt + */ + if (mdwc->dbm && dbm_l1_lpm_interrupt(mdwc->dbm)) + dwc3_msm_write_reg_field(mdwc->base, + PWR_EVNT_IRQ_MASK_REG, + PWR_EVNT_LPM_OUT_L1_MASK, 1); + + atomic_set(&dwc->in_lpm, 0); + break; + case DWC3_CONTROLLER_NOTIFY_OTG_EVENT: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_NOTIFY_OTG_EVENT received\n"); + if (dwc->enable_bus_suspend) { + mdwc->suspend = dwc->b_suspend; + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + } + break; + case DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT received\n"); + schedule_work(&mdwc->vbus_draw_work); + break; + case DWC3_CONTROLLER_RESTART_USB_SESSION: + dev_dbg(mdwc->dev, "DWC3_CONTROLLER_RESTART_USB_SESSION received\n"); + schedule_work(&mdwc->restart_usb_work); + break; + case DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER: + dwc3_msm_dbm_disable_updxfer(dwc, value); + break; + default: + dev_dbg(mdwc->dev, "unknown dwc3 event\n"); + break; + } +} + +static void dwc3_msm_block_reset(struct dwc3_msm *mdwc, bool core_reset) +{ + int ret = 0; + + if (core_reset) { + ret = dwc3_msm_link_clk_reset(mdwc, 1); + if (ret) + return; + + usleep_range(1000, 1200); + ret = dwc3_msm_link_clk_reset(mdwc, 0); + if (ret) + return; + + usleep_range(10000, 12000); + } + + if (mdwc->dbm) { + /* Reset the DBM */ + dbm_soft_reset(mdwc->dbm, 1); + usleep_range(1000, 1200); + dbm_soft_reset(mdwc->dbm, 0); + + /*enable DBM*/ + dwc3_msm_write_reg_field(mdwc->base, QSCRATCH_GENERAL_CFG, + DBM_EN_MASK, 0x1); + dbm_enable(mdwc->dbm); + } +} + +static void dwc3_msm_power_collapse_por(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + u32 val; + int ret; + + /* Configure AHB2PHY for one wait state read/write */ + if (mdwc->ahb2phy_base) { + clk_prepare_enable(mdwc->cfg_ahb_clk); + val = readl_relaxed(mdwc->ahb2phy_base + + PERIPH_SS_AHB2PHY_TOP_CFG); + if (val != ONE_READ_WRITE_WAIT) { + writel_relaxed(ONE_READ_WRITE_WAIT, + mdwc->ahb2phy_base + PERIPH_SS_AHB2PHY_TOP_CFG); + /* complete above write before configuring USB PHY. */ + mb(); + } + clk_disable_unprepare(mdwc->cfg_ahb_clk); + } + + if (!mdwc->init) { + dbg_event(0xFF, "dwc3 init", + atomic_read(&mdwc->dev->power.usage_count)); + ret = dwc3_core_pre_init(dwc); + if (ret) { + dev_err(mdwc->dev, "dwc3_core_pre_init failed\n"); + mdwc->core_init_failed = true; + return; + } + mdwc->init = true; + } + + ret = dwc3_core_init(dwc); + if (ret) { + dev_err(mdwc->dev, "dwc3_core_init failed\n"); + mdwc->core_init_failed = true; + return; + } + + mdwc->core_init_failed = false; + /* Re-configure event buffers */ + dwc3_event_buffers_setup(dwc); + + /* Get initial P3 status and enable IN_P3 event */ + val = dwc3_msm_read_reg_field(mdwc->base, + DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK); + atomic_set(&mdwc->in_p3, val == DWC3_LINK_STATE_U3); + dwc3_msm_write_reg_field(mdwc->base, PWR_EVNT_IRQ_MASK_REG, + PWR_EVNT_POWERDOWN_IN_P3_MASK, 1); + if (mdwc->drd_state == DRD_STATE_HOST) { + dev_dbg(mdwc->dev, "%s: set the core in host mode\n", + __func__); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + } +} + +static int dwc3_msm_prepare_suspend(struct dwc3_msm *mdwc) +{ + unsigned long timeout; + u32 reg = 0; + + if ((mdwc->in_host_mode || mdwc->in_device_mode) + && dwc3_msm_is_superspeed(mdwc) && !mdwc->in_restart) { + if (!atomic_read(&mdwc->in_p3)) { + dev_err(mdwc->dev, "Not in P3,aborting LPM sequence\n"); + return -EBUSY; + } + } + + /* Clear previous L2 events */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_LPM_IN_L2_MASK | PWR_EVNT_LPM_OUT_L2_MASK); + + /* Prepare HSPHY for suspend */ + reg = dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)); + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), + reg | DWC3_GUSB2PHYCFG_ENBLSLPM | DWC3_GUSB2PHYCFG_SUSPHY); + + /* Wait for PHY to go into L2 */ + timeout = jiffies + msecs_to_jiffies(5); + while (!time_after(jiffies, timeout)) { + reg = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG); + if (reg & PWR_EVNT_LPM_IN_L2_MASK) + break; + usleep_range(20, 30); + } + if (!(reg & PWR_EVNT_LPM_IN_L2_MASK)) + dev_err(mdwc->dev, "could not transition HS PHY to L2\n"); + + /* Clear L2 event bit */ + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, + PWR_EVNT_LPM_IN_L2_MASK); + + return 0; +} + +static void dwc3_msm_bus_vote_w(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, bus_vote_w); + int ret; + + ret = msm_bus_scale_client_update_request(mdwc->bus_perf_client, + mdwc->bus_vote); + if (ret) + dev_err(mdwc->dev, "Failed to reset bus bw vote %d\n", ret); +} + +static void dwc3_set_phy_speed_flags(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + int i, num_ports; + u32 reg; + + mdwc->hs_phy->flags &= ~(PHY_HSFS_MODE | PHY_LS_MODE); + if (mdwc->in_host_mode) { + reg = dwc3_msm_read_reg(mdwc->base, USB3_HCSPARAMS1); + num_ports = HCS_MAX_PORTS(reg); + for (i = 0; i < num_ports; i++) { + reg = dwc3_msm_read_reg(mdwc->base, + USB3_PORTSC + i*0x10); + if (reg & PORT_PE) { + if (DEV_HIGHSPEED(reg) || DEV_FULLSPEED(reg)) + mdwc->hs_phy->flags |= PHY_HSFS_MODE; + else if (DEV_LOWSPEED(reg)) + mdwc->hs_phy->flags |= PHY_LS_MODE; + } + } + } else { + if (dwc->gadget.speed == USB_SPEED_HIGH || + dwc->gadget.speed == USB_SPEED_FULL) + mdwc->hs_phy->flags |= PHY_HSFS_MODE; + else if (dwc->gadget.speed == USB_SPEED_LOW) + mdwc->hs_phy->flags |= PHY_LS_MODE; + } +} + +static void msm_dwc3_perf_vote_update(struct dwc3_msm *mdwc, + bool perf_mode); + +static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool hibernation) +{ + int ret, i; + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + mutex_lock(&mdwc->suspend_resume_mutex); + if (atomic_read(&dwc->in_lpm)) { + dev_dbg(mdwc->dev, "%s: Already suspended\n", __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return 0; + } + + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + + if (!mdwc->in_host_mode) { + /* pending device events unprocessed */ + for (i = 0; i < dwc->num_event_buffers; i++) { + struct dwc3_event_buffer *evt = dwc->ev_buffs[i]; + if ((evt->flags & DWC3_EVENT_PENDING)) { + dev_dbg(mdwc->dev, + "%s: %d device events pending, abort suspend\n", + __func__, evt->count / 4); + dbg_print_reg("PENDING DEVICE EVENT", + *(u32 *)(evt->buf + evt->lpos)); + mutex_unlock(&mdwc->suspend_resume_mutex); + return -EBUSY; + } + } + } + + if (!mdwc->vbus_active && dwc->is_drd && + mdwc->drd_state == DRD_STATE_PERIPHERAL) { + /* + * In some cases, the pm_runtime_suspend may be called by + * usb_bam when there is pending lpm flag. However, if this is + * done when cable was disconnected and otg state has not + * yet changed to IDLE, then it means OTG state machine + * is running and we race against it. So cancel LPM for now, + * and OTG state machine will go for LPM later, after completing + * transition to IDLE state. + */ + dev_dbg(mdwc->dev, + "%s: cable disconnected while not in idle otg state\n", + __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return -EBUSY; + } + + /* + * Check if device is not in CONFIGURED state + * then check controller state of L2 and break + * LPM sequence. Check this for device bus suspend case. + */ + if ((dwc->is_drd && mdwc->drd_state == DRD_STATE_PERIPHERAL_SUSPEND) && + (dwc->gadget.state != USB_STATE_CONFIGURED)) { + pr_err("%s(): Trying to go in LPM with state:%d\n", + __func__, dwc->gadget.state); + pr_err("%s(): LPM is not performed.\n", __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return -EBUSY; + } + + ret = dwc3_msm_prepare_suspend(mdwc); + if (ret) { + mutex_unlock(&mdwc->suspend_resume_mutex); + return ret; + } + + /* Disable core irq */ + if (dwc->irq) + disable_irq(dwc->irq); + + if (work_busy(&dwc->bh_work)) + dbg_event(0xFF, "pend evt", 0); + + /* disable power event irq, hs and ss phy irq is used as wake up src */ + disable_irq(mdwc->pwr_event_irq); + + dwc3_set_phy_speed_flags(mdwc); + /* Suspend HS PHY */ + usb_phy_set_suspend(mdwc->hs_phy, 1); + + /* Suspend SS PHY */ + if (dwc->maximum_speed == USB_SPEED_SUPER) { + /* indicate phy about SS mode */ + if (dwc3_msm_is_superspeed(mdwc)) + mdwc->ss_phy->flags |= DEVICE_IN_SS_MODE; + usb_phy_set_suspend(mdwc->ss_phy, 1); + mdwc->lpm_flags |= MDWC3_SS_PHY_SUSPEND; + } + + /* make sure above writes are completed before turning off clocks */ + wmb(); + + /* Disable clocks */ + if (mdwc->bus_aggr_clk) + clk_disable_unprepare(mdwc->bus_aggr_clk); + clk_disable_unprepare(mdwc->utmi_clk); + + /* Memory core: OFF, Memory periphery: OFF */ + if (!mdwc->in_host_mode && !mdwc->vbus_active) { + clk_set_flags(mdwc->core_clk, CLKFLAG_NORETAIN_MEM); + clk_set_flags(mdwc->core_clk, CLKFLAG_NORETAIN_PERIPH); + } + + clk_set_rate(mdwc->core_clk, 19200000); + clk_disable_unprepare(mdwc->core_clk); + if (mdwc->noc_aggr_clk) + clk_disable_unprepare(mdwc->noc_aggr_clk); + /* + * Disable iface_clk only after core_clk as core_clk has FSM + * depedency on iface_clk. Hence iface_clk should be turned off + * after core_clk is turned off. + */ + clk_disable_unprepare(mdwc->iface_clk); + /* USB PHY no more requires TCXO */ + clk_disable_unprepare(mdwc->xo_clk); + + /* Perform controller power collapse */ + if ((!mdwc->in_host_mode && (!mdwc->in_device_mode || mdwc->in_restart)) + || hibernation) { + mdwc->lpm_flags |= MDWC3_POWER_COLLAPSE; + dev_dbg(mdwc->dev, "%s: power collapse\n", __func__); + dwc3_msm_config_gdsc(mdwc, 0); + clk_disable_unprepare(mdwc->sleep_clk); + } + + /* Remove bus voting */ + if (mdwc->bus_perf_client) { + mdwc->bus_vote = 0; + schedule_work(&mdwc->bus_vote_w); + } + + /* + * release wakeup source with timeout to defer system suspend to + * handle case where on USB cable disconnect, SUSPEND and DISCONNECT + * event is received. + */ + if (mdwc->lpm_to_suspend_delay) { + dev_dbg(mdwc->dev, "defer suspend with %d(msecs)\n", + mdwc->lpm_to_suspend_delay); + pm_wakeup_event(mdwc->dev, mdwc->lpm_to_suspend_delay); + } else { + pm_relax(mdwc->dev); + } + + atomic_set(&dwc->in_lpm, 1); + + /* + * with DCP or during cable disconnect, we dont require wakeup + * using HS_PHY_IRQ or SS_PHY_IRQ. Hence enable wakeup only in + * case of host bus suspend and device bus suspend. + */ + if (mdwc->in_device_mode || mdwc->in_host_mode) { + if (!mdwc->no_wakeup_src_in_hostmode) + enable_irq_wake(mdwc->hs_phy_irq); + enable_irq(mdwc->hs_phy_irq); + if (mdwc->ss_phy_irq) { + if (!mdwc->no_wakeup_src_in_hostmode) + enable_irq_wake(mdwc->ss_phy_irq); + enable_irq(mdwc->ss_phy_irq); + } + mdwc->lpm_flags |= MDWC3_ASYNC_IRQ_WAKE_CAPABILITY; + } + + dev_info(mdwc->dev, "DWC3 in low power mode\n"); + dbg_event(0xFF, "Ctl Sus", atomic_read(&dwc->in_lpm)); + + /* kick_sm if it is waiting for lpm sequence to finish */ + if (test_and_clear_bit(WAIT_FOR_LPM, &mdwc->inputs)) + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); + + mutex_unlock(&mdwc->suspend_resume_mutex); + + return 0; +} + +static int dwc3_msm_resume(struct dwc3_msm *mdwc) +{ + int ret; + long core_clk_rate; + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(mdwc->dev, "%s: exiting lpm\n", __func__); + + mutex_lock(&mdwc->suspend_resume_mutex); + if (!atomic_read(&dwc->in_lpm)) { + dev_dbg(mdwc->dev, "%s: Already resumed\n", __func__); + mutex_unlock(&mdwc->suspend_resume_mutex); + return 0; + } + + pm_stay_awake(mdwc->dev); + + /* Enable bus voting */ + if (mdwc->bus_perf_client) { + mdwc->bus_vote = 1; + schedule_work(&mdwc->bus_vote_w); + } + + /* Vote for TCXO while waking up USB HSPHY */ + ret = clk_prepare_enable(mdwc->xo_clk); + if (ret) + dev_err(mdwc->dev, "%s failed to vote TCXO buffer%d\n", + __func__, ret); + + /* Restore controller power collapse */ + if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) { + dev_dbg(mdwc->dev, "%s: exit power collapse\n", __func__); + dwc3_msm_config_gdsc(mdwc, 1); + ret = reset_control_assert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "%s:core_reset assert failed\n", + __func__); + /* HW requires a short delay for reset to take place properly */ + usleep_range(1000, 1200); + ret = reset_control_deassert(mdwc->core_reset); + if (ret) + dev_err(mdwc->dev, "%s:core_reset deassert failed\n", + __func__); + clk_prepare_enable(mdwc->sleep_clk); + } + + /* + * Enable clocks + * Turned ON iface_clk before core_clk due to FSM depedency. + */ + clk_prepare_enable(mdwc->iface_clk); + if (mdwc->noc_aggr_clk) + clk_prepare_enable(mdwc->noc_aggr_clk); + + core_clk_rate = mdwc->core_clk_rate; + if (mdwc->in_host_mode && mdwc->max_rh_port_speed == USB_SPEED_HIGH) { + core_clk_rate = mdwc->core_clk_rate_hs; + dev_dbg(mdwc->dev, "%s: set hs core clk rate %ld\n", __func__, + core_clk_rate); + } + + clk_set_rate(mdwc->core_clk, core_clk_rate); + clk_prepare_enable(mdwc->core_clk); + + /* set Memory core: ON, Memory periphery: ON */ + clk_set_flags(mdwc->core_clk, CLKFLAG_RETAIN_MEM); + clk_set_flags(mdwc->core_clk, CLKFLAG_RETAIN_PERIPH); + + clk_prepare_enable(mdwc->utmi_clk); + if (mdwc->bus_aggr_clk) + clk_prepare_enable(mdwc->bus_aggr_clk); + + /* Resume SS PHY */ + if (dwc->maximum_speed == USB_SPEED_SUPER && + mdwc->lpm_flags & MDWC3_SS_PHY_SUSPEND) { + mdwc->ss_phy->flags &= ~(PHY_LANE_A | PHY_LANE_B); + if (mdwc->typec_orientation == ORIENTATION_CC1) + mdwc->ss_phy->flags |= PHY_LANE_A; + if (mdwc->typec_orientation == ORIENTATION_CC2) + mdwc->ss_phy->flags |= PHY_LANE_B; + usb_phy_set_suspend(mdwc->ss_phy, 0); + mdwc->ss_phy->flags &= ~DEVICE_IN_SS_MODE; + mdwc->lpm_flags &= ~MDWC3_SS_PHY_SUSPEND; + } + + mdwc->hs_phy->flags &= ~(PHY_HSFS_MODE | PHY_LS_MODE); + /* Resume HS PHY */ + usb_phy_set_suspend(mdwc->hs_phy, 0); + + /* Disable HSPHY auto suspend */ + dwc3_msm_write_reg(mdwc->base, DWC3_GUSB2PHYCFG(0), + dwc3_msm_read_reg(mdwc->base, DWC3_GUSB2PHYCFG(0)) & + ~(DWC3_GUSB2PHYCFG_ENBLSLPM | + DWC3_GUSB2PHYCFG_SUSPHY)); + + /* Recover from controller power collapse */ + if (mdwc->lpm_flags & MDWC3_POWER_COLLAPSE) { + dev_dbg(mdwc->dev, "%s: exit power collapse\n", __func__); + + dwc3_msm_power_collapse_por(mdwc); + + mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE; + } + + atomic_set(&dwc->in_lpm, 0); + + /* enable power evt irq for IN P3 detection */ + enable_irq(mdwc->pwr_event_irq); + + /* Disable wakeup capable for HS_PHY IRQ & SS_PHY_IRQ if enabled */ + if (mdwc->lpm_flags & MDWC3_ASYNC_IRQ_WAKE_CAPABILITY) { + if (!mdwc->no_wakeup_src_in_hostmode) + disable_irq_wake(mdwc->hs_phy_irq); + disable_irq_nosync(mdwc->hs_phy_irq); + if (mdwc->ss_phy_irq) { + if (!mdwc->no_wakeup_src_in_hostmode) + disable_irq_wake(mdwc->ss_phy_irq); + disable_irq_nosync(mdwc->ss_phy_irq); + } + mdwc->lpm_flags &= ~MDWC3_ASYNC_IRQ_WAKE_CAPABILITY; + } + + dev_info(mdwc->dev, "DWC3 exited from low power mode\n"); + + /* Enable core irq */ + if (dwc->irq) + enable_irq(dwc->irq); + + /* + * Handle other power events that could not have been handled during + * Low Power Mode + */ + dwc3_pwr_event_handler(mdwc); + + if (pm_qos_request_active(&mdwc->pm_qos_req_dma)) + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + + dbg_event(0xFF, "Ctl Res", atomic_read(&dwc->in_lpm)); + mutex_unlock(&mdwc->suspend_resume_mutex); + + return 0; +} + +/** + * dwc3_ext_event_notify - callback to handle events from external transceiver + * + * Returns 0 on success + */ +static void dwc3_ext_event_notify(struct dwc3_msm *mdwc) +{ + /* Flush processing any pending events before handling new ones */ + flush_delayed_work(&mdwc->sm_work); + + if (mdwc->id_state == DWC3_ID_FLOAT) { + dev_dbg(mdwc->dev, "XCVR: ID set\n"); + set_bit(ID, &mdwc->inputs); + } else { + dev_dbg(mdwc->dev, "XCVR: ID clear\n"); + clear_bit(ID, &mdwc->inputs); + } + + if (mdwc->vbus_active && !mdwc->in_restart) { + dev_dbg(mdwc->dev, "XCVR: BSV set\n"); + set_bit(B_SESS_VLD, &mdwc->inputs); + } else { + dev_dbg(mdwc->dev, "XCVR: BSV clear\n"); + clear_bit(B_SESS_VLD, &mdwc->inputs); + } + + if (mdwc->suspend) { + dev_dbg(mdwc->dev, "XCVR: SUSP set\n"); + set_bit(B_SUSPEND, &mdwc->inputs); + } else { + dev_dbg(mdwc->dev, "XCVR: SUSP clear\n"); + clear_bit(B_SUSPEND, &mdwc->inputs); + } + + pm_stay_awake(mdwc->dev); + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); +} + +static void dwc3_resume_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, resume_work); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(mdwc->dev, "%s: dwc3 resume work\n", __func__); + + /* + * exit LPM first to meet resume timeline from device side. + * resume_pending flag would prevent calling + * dwc3_msm_resume() in case we are here due to system + * wide resume without usb cable connected. This flag is set + * only in case of power event irq in lpm. + */ + if (mdwc->resume_pending) { + dwc3_msm_resume(mdwc); + mdwc->resume_pending = false; + } + + if (atomic_read(&mdwc->pm_suspended)) { + dbg_event(0xFF, "RWrk PMSus", 0); + /* let pm resume kick in resume work later */ + return; + } + + dbg_event(0xFF, "RWrk", dwc->is_drd); + dwc3_ext_event_notify(mdwc); +} + +static void dwc3_pwr_event_handler(struct dwc3_msm *mdwc) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + u32 irq_stat, irq_clear = 0; + + irq_stat = dwc3_msm_read_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG); + dev_dbg(mdwc->dev, "%s irq_stat=%X\n", __func__, irq_stat); + + /* Check for P3 events */ + if ((irq_stat & PWR_EVNT_POWERDOWN_OUT_P3_MASK) && + (irq_stat & PWR_EVNT_POWERDOWN_IN_P3_MASK)) { + /* Can't tell if entered or exit P3, so check LINKSTATE */ + u32 ls = dwc3_msm_read_reg_field(mdwc->base, + DWC3_GDBGLTSSM, DWC3_GDBGLTSSM_LINKSTATE_MASK); + dev_dbg(mdwc->dev, "%s link state = 0x%04x\n", __func__, ls); + atomic_set(&mdwc->in_p3, ls == DWC3_LINK_STATE_U3); + + irq_stat &= ~(PWR_EVNT_POWERDOWN_OUT_P3_MASK | + PWR_EVNT_POWERDOWN_IN_P3_MASK); + irq_clear |= (PWR_EVNT_POWERDOWN_OUT_P3_MASK | + PWR_EVNT_POWERDOWN_IN_P3_MASK); + } else if (irq_stat & PWR_EVNT_POWERDOWN_OUT_P3_MASK) { + atomic_set(&mdwc->in_p3, 0); + irq_stat &= ~PWR_EVNT_POWERDOWN_OUT_P3_MASK; + irq_clear |= PWR_EVNT_POWERDOWN_OUT_P3_MASK; + } else if (irq_stat & PWR_EVNT_POWERDOWN_IN_P3_MASK) { + atomic_set(&mdwc->in_p3, 1); + irq_stat &= ~PWR_EVNT_POWERDOWN_IN_P3_MASK; + irq_clear |= PWR_EVNT_POWERDOWN_IN_P3_MASK; + } + + /* Clear L2 exit */ + if (irq_stat & PWR_EVNT_LPM_OUT_L2_MASK) { + irq_stat &= ~PWR_EVNT_LPM_OUT_L2_MASK; + irq_stat |= PWR_EVNT_LPM_OUT_L2_MASK; + } + + /* Handle exit from L1 events */ + if (irq_stat & PWR_EVNT_LPM_OUT_L1_MASK) { + dev_dbg(mdwc->dev, "%s: handling PWR_EVNT_LPM_OUT_L1_MASK\n", + __func__); + if (usb_gadget_wakeup(&dwc->gadget)) + dev_err(mdwc->dev, "%s failed to take dwc out of L1\n", + __func__); + irq_stat &= ~PWR_EVNT_LPM_OUT_L1_MASK; + irq_clear |= PWR_EVNT_LPM_OUT_L1_MASK; + } + + /* Unhandled events */ + if (irq_stat) + dev_dbg(mdwc->dev, "%s: unexpected PWR_EVNT, irq_stat=%X\n", + __func__, irq_stat); + + dwc3_msm_write_reg(mdwc->base, PWR_EVNT_IRQ_STAT_REG, irq_clear); +} + +static irqreturn_t msm_dwc3_pwr_irq_thread(int irq, void *_mdwc) +{ + struct dwc3_msm *mdwc = _mdwc; + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(mdwc->dev, "%s\n", __func__); + + if (atomic_read(&dwc->in_lpm)) + dwc3_resume_work(&mdwc->resume_work); + else + dwc3_pwr_event_handler(mdwc); + + dbg_event(0xFF, "PWR IRQ", atomic_read(&dwc->in_lpm)); + + return IRQ_HANDLED; +} + +static irqreturn_t msm_dwc3_pwr_irq(int irq, void *data) +{ + struct dwc3_msm *mdwc = data; + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + dwc->t_pwr_evt_irq = ktime_get(); + dev_dbg(mdwc->dev, "%s received\n", __func__); + /* + * When in Low Power Mode, can't read PWR_EVNT_IRQ_STAT_REG to acertain + * which interrupts have been triggered, as the clocks are disabled. + * Resume controller by waking up pwr event irq thread.After re-enabling + * clocks, dwc3_msm_resume will call dwc3_pwr_event_handler to handle + * all other power events. + */ + if (atomic_read(&dwc->in_lpm)) { + /* set this to call dwc3_msm_resume() */ + mdwc->resume_pending = true; + return IRQ_WAKE_THREAD; + } + + dwc3_pwr_event_handler(mdwc); + return IRQ_HANDLED; +} + +static int dwc3_cpu_notifier_cb(struct notifier_block *nfb, + unsigned long action, void *hcpu) +{ + uint32_t cpu = (uintptr_t)hcpu; + struct dwc3_msm *mdwc = + container_of(nfb, struct dwc3_msm, dwc3_cpu_notifier); + + if (cpu == cpu_to_affin && action == CPU_ONLINE) { + pr_debug("%s: cpu online:%u irq:%d\n", __func__, + cpu_to_affin, mdwc->irq_to_affin); + irq_set_affinity(mdwc->irq_to_affin, get_cpu_mask(cpu)); + } + + return NOTIFY_OK; +} + +static void dwc3_otg_sm_work(struct work_struct *w); + +static int dwc3_msm_get_clk_gdsc(struct dwc3_msm *mdwc) +{ + int ret; + + mdwc->dwc3_gdsc = devm_regulator_get(mdwc->dev, "USB3_GDSC"); + if (IS_ERR(mdwc->dwc3_gdsc)) + mdwc->dwc3_gdsc = NULL; + + mdwc->xo_clk = devm_clk_get(mdwc->dev, "xo"); + if (IS_ERR(mdwc->xo_clk)) { + dev_err(mdwc->dev, "%s unable to get TCXO buffer handle\n", + __func__); + ret = PTR_ERR(mdwc->xo_clk); + return ret; + } + clk_set_rate(mdwc->xo_clk, 19200000); + + mdwc->iface_clk = devm_clk_get(mdwc->dev, "iface_clk"); + if (IS_ERR(mdwc->iface_clk)) { + dev_err(mdwc->dev, "failed to get iface_clk\n"); + ret = PTR_ERR(mdwc->iface_clk); + return ret; + } + + /* + * DWC3 Core requires its CORE CLK (aka master / bus clk) to + * run at 125Mhz in SSUSB mode and >60MHZ for HSUSB mode. + * On newer platform it can run at 150MHz as well. + */ + mdwc->core_clk = devm_clk_get(mdwc->dev, "core_clk"); + if (IS_ERR(mdwc->core_clk)) { + dev_err(mdwc->dev, "failed to get core_clk\n"); + ret = PTR_ERR(mdwc->core_clk); + return ret; + } + + if (of_property_read_u32(mdwc->dev->of_node, "qcom,core-clk-rate", + (u32 *)&mdwc->core_clk_rate)) { + dev_err(mdwc->dev, "USB core-clk-rate is not present\n"); + return -EINVAL; + } + + mdwc->core_clk_rate = clk_round_rate(mdwc->core_clk, + mdwc->core_clk_rate); + + dev_dbg(mdwc->dev, "USB core frequency = %ld\n", + mdwc->core_clk_rate); + ret = clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); + if (ret) + dev_err(mdwc->dev, "fail to set core_clk freq:%d\n", ret); + + if (of_property_read_u32(mdwc->dev->of_node, "qcom,core-clk-rate-hs", + (u32 *)&mdwc->core_clk_rate_hs)) { + dev_dbg(mdwc->dev, "USB core-clk-rate-hs is not present\n"); + mdwc->core_clk_rate_hs = mdwc->core_clk_rate; + } + + mdwc->core_reset = devm_reset_control_get(mdwc->dev, "core_reset"); + if (IS_ERR(mdwc->core_reset)) { + dev_err(mdwc->dev, "failed to get core_reset\n"); + return PTR_ERR(mdwc->core_reset); + } + + mdwc->sleep_clk = devm_clk_get(mdwc->dev, "sleep_clk"); + if (IS_ERR(mdwc->sleep_clk)) { + dev_err(mdwc->dev, "failed to get sleep_clk\n"); + ret = PTR_ERR(mdwc->sleep_clk); + return ret; + } + + clk_set_rate(mdwc->sleep_clk, 32000); + mdwc->utmi_clk_rate = 19200000; + mdwc->utmi_clk = devm_clk_get(mdwc->dev, "utmi_clk"); + if (IS_ERR(mdwc->utmi_clk)) { + dev_err(mdwc->dev, "failed to get utmi_clk\n"); + ret = PTR_ERR(mdwc->utmi_clk); + return ret; + } + + clk_set_rate(mdwc->utmi_clk, mdwc->utmi_clk_rate); + mdwc->bus_aggr_clk = devm_clk_get(mdwc->dev, "bus_aggr_clk"); + if (IS_ERR(mdwc->bus_aggr_clk)) + mdwc->bus_aggr_clk = NULL; + + mdwc->noc_aggr_clk = devm_clk_get(mdwc->dev, "noc_aggr_clk"); + if (IS_ERR(mdwc->noc_aggr_clk)) + mdwc->noc_aggr_clk = NULL; + + if (of_property_match_string(mdwc->dev->of_node, + "clock-names", "cfg_ahb_clk") >= 0) { + mdwc->cfg_ahb_clk = devm_clk_get(mdwc->dev, "cfg_ahb_clk"); + if (IS_ERR(mdwc->cfg_ahb_clk)) { + ret = PTR_ERR(mdwc->cfg_ahb_clk); + mdwc->cfg_ahb_clk = NULL; + if (ret != -EPROBE_DEFER) + dev_err(mdwc->dev, + "failed to get cfg_ahb_clk ret %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int dwc3_msm_id_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_msm *mdwc = container_of(nb, struct dwc3_msm, id_nb); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + struct extcon_dev *edev = ptr; + enum dwc3_id_state id; + int cc_state; + int speed; + + if (!edev) { + dev_err(mdwc->dev, "%s: edev null\n", __func__); + goto done; + } + + id = event ? DWC3_ID_GROUND : DWC3_ID_FLOAT; + + dev_dbg(mdwc->dev, "host:%ld (id:%d) event received\n", event, id); + + cc_state = extcon_get_cable_state_(edev, EXTCON_USB_CC); + if (cc_state < 0) + mdwc->typec_orientation = ORIENTATION_NONE; + else + mdwc->typec_orientation = + cc_state ? ORIENTATION_CC2 : ORIENTATION_CC1; + + dbg_event(0xFF, "cc_state", mdwc->typec_orientation); + + speed = extcon_get_cable_state_(edev, EXTCON_USB_SPEED); + dwc->maximum_speed = (speed <= 0) ? USB_SPEED_HIGH : USB_SPEED_SUPER; + if (dwc->maximum_speed > dwc->max_hw_supp_speed) + dwc->maximum_speed = dwc->max_hw_supp_speed; + + if (!id && mdwc->override_usb_speed) { + dwc->maximum_speed = mdwc->override_usb_speed; + dbg_event(0xFF, "override_usb_speed", + mdwc->override_usb_speed); + mdwc->override_usb_speed = 0; + } + + if (mdwc->id_state != id) { + mdwc->id_state = id; + dbg_event(0xFF, "id_state", mdwc->id_state); + pm_stay_awake(mdwc->dev); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + } + +done: + return NOTIFY_DONE; +} + + +static void check_for_sdp_connection(struct work_struct *w) +{ + struct dwc3_msm *mdwc = + container_of(w, struct dwc3_msm, sdp_check.work); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + if (!mdwc->vbus_active) + return; + + /* USB 3.1 compliance equipment usually repoted as floating + * charger as HS dp/dm lines are never connected. Do not + * tear down USB stack if compliance parameter is set + */ + if (mdwc->usb_compliance_mode) + return; + + /* floating D+/D- lines detected */ + if (dwc->gadget.state < USB_STATE_DEFAULT && + dwc3_gadget_get_link_state(dwc) != DWC3_LINK_STATE_CMPLY) { + mdwc->vbus_active = 0; + dbg_event(0xFF, "Q RW SPD CHK", mdwc->vbus_active); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + } +} + +static int dwc3_msm_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_msm *mdwc = container_of(nb, struct dwc3_msm, vbus_nb); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + struct extcon_dev *edev = ptr; + int cc_state; + int speed; + int self_powered; + + if (!edev) { + dev_err(mdwc->dev, "%s: edev null\n", __func__); + goto done; + } + + dev_dbg(mdwc->dev, "vbus:%ld event received\n", event); + + if (mdwc->vbus_active == event) + return NOTIFY_DONE; + + mdwc->float_detected = false; + cc_state = extcon_get_cable_state_(edev, EXTCON_USB_CC); + if (cc_state < 0) + mdwc->typec_orientation = ORIENTATION_NONE; + else + mdwc->typec_orientation = + cc_state ? ORIENTATION_CC2 : ORIENTATION_CC1; + + dbg_event(0xFF, "cc_state", mdwc->typec_orientation); + + speed = extcon_get_cable_state_(edev, EXTCON_USB_SPEED); + dwc->maximum_speed = (speed <= 0) ? USB_SPEED_HIGH : USB_SPEED_SUPER; + if (dwc->maximum_speed > dwc->max_hw_supp_speed) + dwc->maximum_speed = dwc->max_hw_supp_speed; + + self_powered = extcon_get_cable_state_(edev, + EXTCON_USB_TYPEC_MED_HIGH_CURRENT); + if (self_powered < 0) + dwc->gadget.is_selfpowered = 0; + else + dwc->gadget.is_selfpowered = self_powered; + + mdwc->vbus_active = event; + if (dwc->is_drd && !mdwc->in_restart) { + dbg_event(0xFF, "Q RW (vbus)", mdwc->vbus_active); + pm_stay_awake(mdwc->dev); + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + } +done: + return NOTIFY_DONE; +} + +static int dwc3_msm_extcon_register(struct dwc3_msm *mdwc) +{ + struct device_node *node = mdwc->dev->of_node; + struct extcon_dev *edev; + struct dwc3 *dwc; + int ret = 0; + + dwc = platform_get_drvdata(mdwc->dwc3); + if (!of_property_read_bool(node, "extcon")) { + dev_dbg(mdwc->dev, "extcon property doesn't exist\n"); + if (usb_get_dr_mode(&mdwc->dwc3->dev) == USB_DR_MODE_HOST + || dwc->is_drd) + return 0; + dev_err(mdwc->dev, "Neither host nor DRD, fail probe\n"); + return -EINVAL; + } + + edev = extcon_get_edev_by_phandle(mdwc->dev, 0); + if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) + return PTR_ERR(edev); + + if (!IS_ERR(edev)) { + mdwc->extcon_vbus = edev; + mdwc->vbus_nb.notifier_call = dwc3_msm_vbus_notifier; + ret = extcon_register_notifier(edev, EXTCON_USB, + &mdwc->vbus_nb); + if (ret < 0) { + dev_err(mdwc->dev, "failed to register notifier for USB\n"); + return ret; + } + } + + /* if a second phandle was provided, use it to get a separate edev */ + if (of_count_phandle_with_args(node, "extcon", NULL) > 1) { + edev = extcon_get_edev_by_phandle(mdwc->dev, 1); + if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) { + ret = PTR_ERR(edev); + goto err; + } + } + + if (!IS_ERR(edev)) { + mdwc->extcon_id = edev; + mdwc->id_nb.notifier_call = dwc3_msm_id_notifier; + ret = extcon_register_notifier(edev, EXTCON_USB_HOST, + &mdwc->id_nb); + if (ret < 0) { + dev_err(mdwc->dev, "failed to register notifier for USB-HOST\n"); + goto err; + } + } + + return 0; +err: + if (mdwc->extcon_vbus) + extcon_unregister_notifier(mdwc->extcon_vbus, EXTCON_USB, + &mdwc->vbus_nb); + return ret; +} + +static ssize_t mode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (mdwc->vbus_active) + return snprintf(buf, PAGE_SIZE, "peripheral\n"); + if (mdwc->id_state == DWC3_ID_GROUND) + return snprintf(buf, PAGE_SIZE, "host\n"); + + return snprintf(buf, PAGE_SIZE, "none\n"); +} + +static ssize_t mode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (sysfs_streq(buf, "peripheral")) { + mdwc->vbus_active = true; + mdwc->id_state = DWC3_ID_FLOAT; + } else if (sysfs_streq(buf, "host")) { + mdwc->vbus_active = false; + mdwc->id_state = DWC3_ID_GROUND; + } else { + mdwc->vbus_active = false; + mdwc->id_state = DWC3_ID_FLOAT; + } + + dwc3_ext_event_notify(mdwc); + + return count; +} + +static DEVICE_ATTR_RW(mode); + +/* This node only shows max speed supported dwc3 and it should be + * same as what is reported in udc/core.c max_speed node. For current + * operating gadget speed, query current_speed node which is implemented + * by udc/core.c + */ +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + return snprintf(buf, PAGE_SIZE, "%s\n", + usb_speed_string(dwc->maximum_speed)); +} + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + enum usb_device_speed req_speed = USB_SPEED_UNKNOWN; + + /* DEVSPD can only have values SS(0x4), HS(0x0) and FS(0x1). + * per 3.20a data book. Allow only these settings. Note that, + * xhci does not support full-speed only mode. + */ + if (sysfs_streq(buf, "full")) + req_speed = USB_SPEED_FULL; + else if (sysfs_streq(buf, "high")) + req_speed = USB_SPEED_HIGH; + else if (sysfs_streq(buf, "super")) + req_speed = USB_SPEED_SUPER; + else + return -EINVAL; + + /* restart usb only works for device mode. Perform manual cable + * plug in/out for host mode restart. + */ + if (req_speed != dwc->maximum_speed && + req_speed <= dwc->max_hw_supp_speed) { + mdwc->override_usb_speed = req_speed; + schedule_work(&mdwc->restart_usb_work); + } + + return count; +} +static DEVICE_ATTR_RW(speed); + +static void msm_dwc3_perf_vote_work(struct work_struct *w); +static ssize_t xhci_link_compliance_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + if (mdwc->xhci_ss_compliance_enable) + return snprintf(buf, PAGE_SIZE, "y\n"); + else + return snprintf(buf, PAGE_SIZE, "n\n"); +} + +static ssize_t xhci_link_compliance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + bool value; + int ret; + + ret = strtobool(buf, &value); + if (!ret) { + mdwc->xhci_ss_compliance_enable = value; + return count; + } + + return ret; +} + +static DEVICE_ATTR_RW(xhci_link_compliance); + +static ssize_t usb_compliance_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%c\n", + mdwc->usb_compliance_mode ? 'Y' : 'N'); +} + +static ssize_t usb_compliance_mode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + int ret = 0; + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + ret = strtobool(buf, &mdwc->usb_compliance_mode); + + if (ret) + return ret; + + return count; +} +static DEVICE_ATTR_RW(usb_compliance_mode); + +static int dwc3_msm_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node, *dwc3_node; + struct device *dev = &pdev->dev; + union power_supply_propval pval = {0}; + struct dwc3_msm *mdwc; + struct dwc3 *dwc; + struct resource *res; + void __iomem *tcsr; + bool host_mode; + int ret = 0; + int ext_hub_reset_gpio; + u32 val; + char boot_marker[40]; + + mdwc = devm_kzalloc(&pdev->dev, sizeof(*mdwc), GFP_KERNEL); + if (!mdwc) + return -ENOMEM; + + if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64))) { + dev_err(&pdev->dev, "setting DMA mask to 64 failed.\n"); + if (dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32))) { + dev_err(&pdev->dev, "setting DMA mask to 32 failed.\n"); + return -EOPNOTSUPP; + } + } + + platform_set_drvdata(pdev, mdwc); + mdwc->dev = &pdev->dev; + + INIT_LIST_HEAD(&mdwc->req_complete_list); + INIT_WORK(&mdwc->resume_work, dwc3_resume_work); + INIT_WORK(&mdwc->restart_usb_work, dwc3_restart_usb_work); + INIT_WORK(&mdwc->bus_vote_w, dwc3_msm_bus_vote_w); + INIT_WORK(&mdwc->vbus_draw_work, dwc3_msm_vbus_draw_work); + INIT_DELAYED_WORK(&mdwc->sm_work, dwc3_otg_sm_work); + INIT_DELAYED_WORK(&mdwc->perf_vote_work, msm_dwc3_perf_vote_work); + INIT_DELAYED_WORK(&mdwc->sdp_check, check_for_sdp_connection); + + mdwc->sm_usb_wq = create_freezable_workqueue("k_sm_usb"); + if (!mdwc->sm_usb_wq) { + pr_err("%s: Failed to create workqueue for sm_usb\n", __func__); + return -ENOMEM; + } + + mdwc->dwc3_wq = alloc_ordered_workqueue("dwc3_wq", 0); + if (!mdwc->dwc3_wq) { + pr_err("%s: Unable to create workqueue dwc3_wq\n", __func__); + return -ENOMEM; + } + + /* Get all clks and gdsc reference */ + ret = dwc3_msm_get_clk_gdsc(mdwc); + if (ret) { + dev_err(&pdev->dev, "error getting clock or gdsc.\n"); + goto err; + } + + mdwc->id_state = DWC3_ID_FLOAT; + set_bit(ID, &mdwc->inputs); + + mdwc->charging_disabled = of_property_read_bool(node, + "qcom,charging-disabled"); + + ret = of_property_read_u32(node, "qcom,lpm-to-suspend-delay-ms", + &mdwc->lpm_to_suspend_delay); + if (ret) { + dev_dbg(&pdev->dev, "setting lpm_to_suspend_delay to zero.\n"); + mdwc->lpm_to_suspend_delay = 0; + } + + /* + * DWC3 has separate IRQ line for OTG events (ID/BSV) and for + * DP and DM linestate transitions during low power mode. + */ + mdwc->hs_phy_irq = platform_get_irq_byname(pdev, "hs_phy_irq"); + if (mdwc->hs_phy_irq < 0) { + dev_err(&pdev->dev, "pget_irq for hs_phy_irq failed\n"); + ret = -EINVAL; + goto err; + } else { + irq_set_status_flags(mdwc->hs_phy_irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, mdwc->hs_phy_irq, + msm_dwc3_pwr_irq, + msm_dwc3_pwr_irq_thread, + IRQF_TRIGGER_RISING | IRQF_EARLY_RESUME + | IRQF_ONESHOT, "hs_phy_irq", mdwc); + if (ret) { + dev_err(&pdev->dev, "irqreq hs_phy_irq failed: %d\n", + ret); + goto err; + } + } + + mdwc->ss_phy_irq = platform_get_irq_byname(pdev, "ss_phy_irq"); + if (mdwc->ss_phy_irq < 0) { + dev_dbg(&pdev->dev, "pget_irq for ss_phy_irq failed\n"); + } else { + irq_set_status_flags(mdwc->ss_phy_irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, mdwc->ss_phy_irq, + msm_dwc3_pwr_irq, + msm_dwc3_pwr_irq_thread, + IRQF_TRIGGER_HIGH | IRQ_TYPE_LEVEL_HIGH + | IRQF_EARLY_RESUME | IRQF_ONESHOT, + "ss_phy_irq", mdwc); + if (ret) { + dev_err(&pdev->dev, "irqreq ss_phy_irq failed: %d\n", + ret); + goto err; + } + } + + mdwc->pwr_event_irq = platform_get_irq_byname(pdev, "pwr_event_irq"); + if (mdwc->pwr_event_irq < 0) { + dev_err(&pdev->dev, "pget_irq for pwr_event_irq failed\n"); + ret = -EINVAL; + goto err; + } else { + /* will be enabled in dwc3_msm_resume() */ + irq_set_status_flags(mdwc->pwr_event_irq, IRQ_NOAUTOEN); + ret = devm_request_threaded_irq(&pdev->dev, mdwc->pwr_event_irq, + msm_dwc3_pwr_irq, + msm_dwc3_pwr_irq_thread, + IRQF_TRIGGER_RISING | IRQF_EARLY_RESUME, + "msm_dwc3", mdwc); + if (ret) { + dev_err(&pdev->dev, "irqreq pwr_event_irq failed: %d\n", + ret); + goto err; + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "tcsr_base"); + if (!res) { + dev_dbg(&pdev->dev, "missing TCSR memory resource\n"); + } else { + tcsr = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (IS_ERR_OR_NULL(tcsr)) { + dev_dbg(&pdev->dev, "tcsr ioremap failed\n"); + } else { + /* Enable USB3 on the primary USB port. */ + writel_relaxed(0x1, tcsr); + /* + * Ensure that TCSR write is completed before + * USB registers initialization. + */ + mb(); + } + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core_base"); + if (!res) { + dev_err(&pdev->dev, "missing memory base resource\n"); + ret = -ENODEV; + goto err; + } + + mdwc->base = devm_ioremap_nocache(&pdev->dev, res->start, + resource_size(res)); + if (!mdwc->base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -ENODEV; + goto err; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "ahb2phy_base"); + if (res) { + mdwc->ahb2phy_base = devm_ioremap_nocache(&pdev->dev, + res->start, resource_size(res)); + if (IS_ERR_OR_NULL(mdwc->ahb2phy_base)) { + dev_err(dev, "couldn't find ahb2phy_base addr.\n"); + mdwc->ahb2phy_base = NULL; + } else { + /* + * On some targets cfg_ahb_clk depends upon usb gdsc + * regulator. If cfg_ahb_clk is enabled without + * turning on usb gdsc regulator clk is stuck off. + */ + dwc3_msm_config_gdsc(mdwc, 1); + clk_prepare_enable(mdwc->cfg_ahb_clk); + /* Configure AHB2PHY for one wait state read/write*/ + val = readl_relaxed(mdwc->ahb2phy_base + + PERIPH_SS_AHB2PHY_TOP_CFG); + if (val != ONE_READ_WRITE_WAIT) { + writel_relaxed(ONE_READ_WRITE_WAIT, + mdwc->ahb2phy_base + + PERIPH_SS_AHB2PHY_TOP_CFG); + /* complete above write before using USB PHY */ + mb(); + } + clk_disable_unprepare(mdwc->cfg_ahb_clk); + dwc3_msm_config_gdsc(mdwc, 0); + } + } + + if (of_get_property(pdev->dev.of_node, "qcom,usb-dbm", NULL)) { + mdwc->dbm = usb_get_dbm_by_phandle(&pdev->dev, "qcom,usb-dbm"); + if (IS_ERR(mdwc->dbm)) { + dev_err(&pdev->dev, "unable to get dbm device\n"); + ret = -EPROBE_DEFER; + goto err; + } + /* + * Add power event if the dbm indicates coming out of L1 + * by interrupt + */ + if (dbm_l1_lpm_interrupt(mdwc->dbm)) { + if (!mdwc->pwr_event_irq) { + dev_err(&pdev->dev, + "need pwr_event_irq exiting L1\n"); + ret = -EINVAL; + goto err; + } + } + } + + ext_hub_reset_gpio = of_get_named_gpio(node, + "qcom,ext-hub-reset-gpio", 0); + + if (gpio_is_valid(ext_hub_reset_gpio) + && (!devm_gpio_request(&pdev->dev, ext_hub_reset_gpio, + "qcom,ext-hub-reset-gpio"))) { + /* reset external hub */ + gpio_direction_output(ext_hub_reset_gpio, 1); + /* + * Hub reset should be asserted for minimum 5microsec + * before deasserting. + */ + usleep_range(5, 1000); + gpio_direction_output(ext_hub_reset_gpio, 0); + } + + if (of_property_read_u32(node, "qcom,dwc-usb3-msm-tx-fifo-size", + &mdwc->tx_fifo_size)) + dev_err(&pdev->dev, + "unable to read platform data tx fifo size\n"); + + mdwc->disable_host_mode_pm = of_property_read_bool(node, + "qcom,disable-host-mode-pm"); + + mdwc->no_wakeup_src_in_hostmode = of_property_read_bool(node, + "qcom,no-wakeup-src-in-hostmode"); + if (mdwc->no_wakeup_src_in_hostmode) + dev_dbg(&pdev->dev, "dwc3 host not using wakeup source\n"); + + dwc3_set_notifier(&dwc3_msm_notify_event); + + /* Assumes dwc3 is the first DT child of dwc3-msm */ + dwc3_node = of_get_next_available_child(node, NULL); + if (!dwc3_node) { + dev_err(&pdev->dev, "failed to find dwc3 child\n"); + ret = -ENODEV; + goto err; + } + + ret = of_platform_populate(node, NULL, NULL, &pdev->dev); + if (ret) { + dev_err(&pdev->dev, + "failed to add create dwc3 core\n"); + of_node_put(dwc3_node); + goto err; + } + + mdwc->dwc3 = of_find_device_by_node(dwc3_node); + of_node_put(dwc3_node); + if (!mdwc->dwc3) { + dev_err(&pdev->dev, "failed to get dwc3 platform device\n"); + goto put_dwc3; + } + + mdwc->hs_phy = devm_usb_get_phy_by_phandle(&mdwc->dwc3->dev, + "usb-phy", 0); + if (IS_ERR(mdwc->hs_phy)) { + dev_err(&pdev->dev, "unable to get hsphy device\n"); + ret = PTR_ERR(mdwc->hs_phy); + goto put_dwc3; + } + mdwc->ss_phy = devm_usb_get_phy_by_phandle(&mdwc->dwc3->dev, + "usb-phy", 1); + if (IS_ERR(mdwc->ss_phy)) { + dev_err(&pdev->dev, "unable to get ssphy device\n"); + ret = PTR_ERR(mdwc->ss_phy); + goto put_dwc3; + } + + mdwc->bus_scale_table = msm_bus_cl_get_pdata(pdev); + if (mdwc->bus_scale_table) { + mdwc->bus_perf_client = + msm_bus_scale_register_client(mdwc->bus_scale_table); + } + + dwc = platform_get_drvdata(mdwc->dwc3); + if (!dwc) { + dev_err(&pdev->dev, "Failed to get dwc3 device\n"); + goto put_dwc3; + } + + mdwc->irq_to_affin = platform_get_irq(mdwc->dwc3, 0); + mdwc->dwc3_cpu_notifier.notifier_call = dwc3_cpu_notifier_cb; + + if (cpu_to_affin) + register_cpu_notifier(&mdwc->dwc3_cpu_notifier); + + /* + * Clocks and regulators will not be turned on until the first time + * runtime PM resume is called. This is to allow for booting up with + * charger already connected so as not to disturb PHY line states. + */ + mdwc->lpm_flags = MDWC3_POWER_COLLAPSE | MDWC3_SS_PHY_SUSPEND; + atomic_set(&dwc->in_lpm, 1); + pm_runtime_set_autosuspend_delay(mdwc->dev, 1000); + pm_runtime_use_autosuspend(mdwc->dev); + device_init_wakeup(mdwc->dev, 1); + + if (of_property_read_bool(node, "qcom,disable-dev-mode-pm")) + pm_runtime_get_noresume(mdwc->dev); + + mdwc->check_for_float = of_property_read_bool(node, + "qcom,check-for-float"); + ret = dwc3_msm_extcon_register(mdwc); + if (ret) + goto put_dwc3; + + ret = of_property_read_u32(node, "qcom,pm-qos-latency", + &mdwc->pm_qos_latency); + if (ret) { + dev_dbg(&pdev->dev, "setting pm-qos-latency to zero.\n"); + mdwc->pm_qos_latency = 0; + } + + mdwc->usb_psy = power_supply_get_by_name("usb"); + if (!mdwc->usb_psy) { + dev_warn(mdwc->dev, "Could not get usb power_supply\n"); + pval.intval = -EINVAL; + } else { + power_supply_get_property(mdwc->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + } + + mutex_init(&mdwc->suspend_resume_mutex); + /* Update initial VBUS/ID state from extcon */ + if (mdwc->extcon_vbus && extcon_get_cable_state_(mdwc->extcon_vbus, + EXTCON_USB)) + dwc3_msm_vbus_notifier(&mdwc->vbus_nb, true, mdwc->extcon_vbus); + else if (mdwc->extcon_id && extcon_get_cable_state_(mdwc->extcon_id, + EXTCON_USB_HOST)) + dwc3_msm_id_notifier(&mdwc->id_nb, true, mdwc->extcon_id); + else if (!pval.intval) { + /* USB cable is not connected */ + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, 0); + } else { + if (pval.intval > 0) + dev_info(mdwc->dev, "charger detection in progress\n"); + } + + device_create_file(&pdev->dev, &dev_attr_mode); + device_create_file(&pdev->dev, &dev_attr_speed); + device_create_file(&pdev->dev, &dev_attr_xhci_link_compliance); + device_create_file(&pdev->dev, &dev_attr_usb_compliance_mode); + + host_mode = usb_get_dr_mode(&mdwc->dwc3->dev) == USB_DR_MODE_HOST; + if (host_mode || + (dwc->is_drd && !of_property_read_bool(node, "extcon"))) { + dev_dbg(&pdev->dev, "DWC3 in default host mode\n"); + mdwc->host_only_mode = true; + mdwc->id_state = DWC3_ID_GROUND; + dwc3_ext_event_notify(mdwc); + snprintf(boot_marker, sizeof(boot_marker), + "M - DRIVER %s Host Ready", dev_name(&pdev->dev)); + } else { + snprintf(boot_marker, sizeof(boot_marker), + "M - DRIVER %s Device Ready", dev_name(&pdev->dev)); + } + + place_marker(boot_marker); + + return 0; + +put_dwc3: + if (mdwc->bus_perf_client) + msm_bus_scale_unregister_client(mdwc->bus_perf_client); + of_platform_depopulate(&pdev->dev); +err: + destroy_workqueue(mdwc->dwc3_wq); + return ret; +} + +static int dwc3_msm_remove(struct platform_device *pdev) +{ + struct dwc3_msm *mdwc = platform_get_drvdata(pdev); + int ret_pm; + + device_remove_file(&pdev->dev, &dev_attr_mode); + device_remove_file(&pdev->dev, &dev_attr_xhci_link_compliance); + + if (cpu_to_affin) + unregister_cpu_notifier(&mdwc->dwc3_cpu_notifier); + + /* + * In case of system suspend, pm_runtime_get_sync fails. + * Hence turn ON the clocks manually. + */ + ret_pm = pm_runtime_get_sync(mdwc->dev); + dbg_event(0xFF, "Remov gsyn", ret_pm); + if (ret_pm < 0) { + dev_err(mdwc->dev, + "pm_runtime_get_sync failed with %d\n", ret_pm); + if (mdwc->noc_aggr_clk) + clk_prepare_enable(mdwc->noc_aggr_clk); + clk_prepare_enable(mdwc->utmi_clk); + clk_prepare_enable(mdwc->core_clk); + clk_prepare_enable(mdwc->iface_clk); + clk_prepare_enable(mdwc->sleep_clk); + if (mdwc->bus_aggr_clk) + clk_prepare_enable(mdwc->bus_aggr_clk); + clk_prepare_enable(mdwc->xo_clk); + } + + cancel_delayed_work_sync(&mdwc->perf_vote_work); + cancel_delayed_work_sync(&mdwc->sm_work); + + if (mdwc->hs_phy) + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + of_platform_depopulate(&pdev->dev); + + dbg_event(0xFF, "Remov put", 0); + pm_runtime_disable(mdwc->dev); + pm_runtime_barrier(mdwc->dev); + pm_runtime_put_sync(mdwc->dev); + pm_runtime_set_suspended(mdwc->dev); + device_wakeup_disable(mdwc->dev); + + if (mdwc->bus_perf_client) + msm_bus_scale_unregister_client(mdwc->bus_perf_client); + + if (!IS_ERR_OR_NULL(mdwc->vbus_reg)) + regulator_disable(mdwc->vbus_reg); + + disable_irq(mdwc->hs_phy_irq); + if (mdwc->ss_phy_irq) + disable_irq(mdwc->ss_phy_irq); + disable_irq(mdwc->pwr_event_irq); + + clk_disable_unprepare(mdwc->utmi_clk); + clk_set_rate(mdwc->core_clk, 19200000); + clk_disable_unprepare(mdwc->core_clk); + clk_disable_unprepare(mdwc->iface_clk); + clk_disable_unprepare(mdwc->sleep_clk); + clk_disable_unprepare(mdwc->xo_clk); + clk_put(mdwc->xo_clk); + + dwc3_msm_config_gdsc(mdwc, 0); + + return 0; +} + +static int dwc3_msm_host_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_msm *mdwc = container_of(nb, struct dwc3_msm, host_nb); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + struct usb_device *udev = ptr; + union power_supply_propval pval; + unsigned max_power; + + if (event != USB_DEVICE_ADD && event != USB_DEVICE_REMOVE) + return NOTIFY_DONE; + + if (!mdwc->usb_psy) { + mdwc->usb_psy = power_supply_get_by_name("usb"); + if (!mdwc->usb_psy) + return NOTIFY_DONE; + } + + /* + * For direct-attach devices, new udev is direct child of root hub + * i.e. dwc -> xhci -> root_hub -> udev + * root_hub's udev->parent==NULL, so traverse struct device hierarchy + */ + if (udev->parent && !udev->parent->parent && + udev->dev.parent->parent == &dwc->xhci->dev) { + if (event == USB_DEVICE_ADD && udev->actconfig) { + if (!dwc3_msm_is_ss_rhport_connected(mdwc)) { + /* + * Core clock rate can be reduced only if root + * hub SS port is not enabled/connected. + */ + clk_set_rate(mdwc->core_clk, + mdwc->core_clk_rate_hs); + dev_dbg(mdwc->dev, + "set hs core clk rate %ld\n", + mdwc->core_clk_rate_hs); + mdwc->max_rh_port_speed = USB_SPEED_HIGH; + } else { + mdwc->max_rh_port_speed = USB_SPEED_SUPER; + } + + if (udev->speed >= USB_SPEED_SUPER) + max_power = udev->actconfig->desc.bMaxPower * 8; + else + max_power = udev->actconfig->desc.bMaxPower * 2; + + dev_dbg(mdwc->dev, "%s configured bMaxPower:%d (mA)\n", + dev_name(&udev->dev), max_power); + + /* inform PMIC of max power so it can optimize boost */ + pval.intval = max_power * 1000; + power_supply_set_property(mdwc->usb_psy, + POWER_SUPPLY_PROP_BOOST_CURRENT, &pval); + } else { + pval.intval = 0; + power_supply_set_property(mdwc->usb_psy, + POWER_SUPPLY_PROP_BOOST_CURRENT, &pval); + + /* set rate back to default core clk rate */ + clk_set_rate(mdwc->core_clk, mdwc->core_clk_rate); + dev_dbg(mdwc->dev, "set core clk rate %ld\n", + mdwc->core_clk_rate); + mdwc->max_rh_port_speed = USB_SPEED_UNKNOWN; + } + } + + return NOTIFY_DONE; +} + +static void msm_dwc3_perf_vote_update(struct dwc3_msm *mdwc, bool perf_mode) +{ + static bool curr_perf_mode; + int latency = mdwc->pm_qos_latency; + + if ((curr_perf_mode == perf_mode) || !latency) + return; + + if (perf_mode) + pm_qos_update_request(&mdwc->pm_qos_req_dma, latency); + else + pm_qos_update_request(&mdwc->pm_qos_req_dma, + PM_QOS_DEFAULT_VALUE); + + curr_perf_mode = perf_mode; + pr_debug("%s: latency updated to: %d\n", __func__, + perf_mode ? latency : PM_QOS_DEFAULT_VALUE); +} + +static void msm_dwc3_perf_vote_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, + perf_vote_work.work); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + bool in_perf_mode = false; + int latency = mdwc->pm_qos_latency; + + if (!latency) + return; + + if (dwc->irq_cnt - dwc->last_irq_cnt >= PM_QOS_THRESHOLD) + in_perf_mode = true; + + pr_debug("%s: in_perf_mode:%u, interrupts in last sample:%lu\n", + __func__, in_perf_mode, (dwc->irq_cnt - dwc->last_irq_cnt)); + + dwc->last_irq_cnt = dwc->irq_cnt; + msm_dwc3_perf_vote_update(mdwc, in_perf_mode); + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); +} + +#define VBUS_REG_CHECK_DELAY (msecs_to_jiffies(1000)) + +/** + * dwc3_otg_start_host - helper function for starting/stoping the host controller driver. + * + * @mdwc: Pointer to the dwc3_msm structure. + * @on: start / stop the host controller driver. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_start_host(struct dwc3_msm *mdwc, int on) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + int ret = 0; + + if (!dwc->xhci) + return -EINVAL; + + /* + * The vbus_reg pointer could have multiple values + * NULL: regulator_get() hasn't been called, or was previously deferred + * IS_ERR: regulator could not be obtained, so skip using it + * Valid pointer otherwise + */ + if (!mdwc->vbus_reg) { + mdwc->vbus_reg = devm_regulator_get_optional(mdwc->dev, + "vbus_dwc3"); + if (IS_ERR(mdwc->vbus_reg) && + PTR_ERR(mdwc->vbus_reg) == -EPROBE_DEFER) { + /* regulators may not be ready, so retry again later */ + mdwc->vbus_reg = NULL; + return -EPROBE_DEFER; + } + } + + if (on) { + dev_dbg(mdwc->dev, "%s: turn on host\n", __func__); + + pm_runtime_get_sync(mdwc->dev); + if (mdwc->core_init_failed) { + dev_err(mdwc->dev, "%s: Core init failed\n", __func__); + pm_runtime_put_sync_suspend(mdwc->dev); + return -EAGAIN; + } + + mdwc->hs_phy->flags |= PHY_HOST_MODE; + if (dwc->maximum_speed == USB_SPEED_SUPER) { + mdwc->ss_phy->flags |= PHY_HOST_MODE; + usb_phy_notify_connect(mdwc->ss_phy, + USB_SPEED_SUPER); + } + + usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH); + dbg_event(0xFF, "StrtHost gync", + atomic_read(&mdwc->dev->power.usage_count)); + if (!IS_ERR(mdwc->vbus_reg)) + ret = regulator_enable(mdwc->vbus_reg); + if (ret) { + dev_err(mdwc->dev, "unable to enable vbus_reg\n"); + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + mdwc->ss_phy->flags &= ~PHY_HOST_MODE; + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "vregerr psync", + atomic_read(&mdwc->dev->power.usage_count)); + return ret; + } + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_HOST); + + mdwc->host_nb.notifier_call = dwc3_msm_host_notifier; + usb_register_notify(&mdwc->host_nb); + + mdwc->usbdev_nb.notifier_call = msm_dwc3_usbdev_notify; + usb_register_atomic_notify(&mdwc->usbdev_nb); + /* + * FIXME If micro A cable is disconnected during system suspend, + * xhci platform device will be removed before runtime pm is + * enabled for xhci device. Due to this, disable_depth becomes + * greater than one and runtimepm is not enabled for next microA + * connect. Fix this by calling pm_runtime_init for xhci device. + */ + pm_runtime_init(&dwc->xhci->dev); + ret = platform_device_add(dwc->xhci); + if (ret) { + dev_err(mdwc->dev, + "%s: failed to add XHCI pdev ret=%d\n", + __func__, ret); + if (!IS_ERR(mdwc->vbus_reg)) + regulator_disable(mdwc->vbus_reg); + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + mdwc->ss_phy->flags &= ~PHY_HOST_MODE; + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "pdeverr psync", + atomic_read(&mdwc->dev->power.usage_count)); + usb_unregister_notify(&mdwc->host_nb); + return ret; + } + + /* + * If the Compliance Transition Capability(CTC) flag of + * HCCPARAMS2 register is set and xhci_link_compliance sysfs + * param has been enabled by the user for the SuperSpeed host + * controller, then write 10 (Link in Compliance Mode State) + * onto the Port Link State(PLS) field of the PORTSC register + * for 3.0 host controller which is at an offset of USB3_PORTSC + * + 0x10 from the DWC3 base address. Also, disable the runtime + * PM of 3.0 root hub (root hub of shared_hcd of xhci device) + */ + if (HCC_CTC(dwc3_msm_read_reg(mdwc->base, USB3_HCCPARAMS2)) + && mdwc->xhci_ss_compliance_enable + && dwc->maximum_speed == USB_SPEED_SUPER) { + dwc3_msm_write_reg(mdwc->base, USB3_PORTSC + 0x10, + 0x10340); + pm_runtime_disable(&hcd_to_xhci(platform_get_drvdata( + dwc->xhci))->shared_hcd->self.root_hub->dev); + } + + /* + * In some cases it is observed that USB PHY is not going into + * suspend with host mode suspend functionality. Hence disable + * XHCI's runtime PM here if disable_host_mode_pm is set. + */ + if (mdwc->disable_host_mode_pm) + pm_runtime_disable(&dwc->xhci->dev); + + mdwc->in_host_mode = true; + dwc3_usb3_phy_suspend(dwc, true); + + /* xHCI should have incremented child count as necessary */ + dbg_event(0xFF, "StrtHost psync", + atomic_read(&mdwc->dev->power.usage_count)); + pm_runtime_mark_last_busy(mdwc->dev); + pm_runtime_put_sync_autosuspend(mdwc->dev); +#ifdef CONFIG_SMP + mdwc->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ; + mdwc->pm_qos_req_dma.irq = dwc->irq; +#endif + pm_qos_add_request(&mdwc->pm_qos_req_dma, + PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + /* start in perf mode for better performance initially */ + msm_dwc3_perf_vote_update(mdwc, true); + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + } else { + dev_dbg(mdwc->dev, "%s: turn off host\n", __func__); + + usb_unregister_atomic_notify(&mdwc->usbdev_nb); + if (!IS_ERR(mdwc->vbus_reg)) + ret = regulator_disable(mdwc->vbus_reg); + if (ret) { + dev_err(mdwc->dev, "unable to disable vbus_reg\n"); + return ret; + } + + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + pm_qos_remove_request(&mdwc->pm_qos_req_dma); + + pm_runtime_get_sync(mdwc->dev); + dbg_event(0xFF, "StopHost gsync", + atomic_read(&mdwc->dev->power.usage_count)); + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); + if (mdwc->ss_phy->flags & PHY_HOST_MODE) { + usb_phy_notify_disconnect(mdwc->ss_phy, + USB_SPEED_SUPER); + mdwc->ss_phy->flags &= ~PHY_HOST_MODE; + } + + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + platform_device_del(dwc->xhci); + usb_unregister_notify(&mdwc->host_nb); + + /* + * Perform USB hardware RESET (both core reset and DBM reset) + * when moving from host to peripheral. This is required for + * peripheral mode to work. + */ + dwc3_msm_block_reset(mdwc, true); + + dwc3_usb3_phy_suspend(dwc, false); + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + + mdwc->in_host_mode = false; + + /* re-init core and OTG registers as block reset clears these */ + if (!mdwc->host_only_mode) + dwc3_post_host_reset_core_init(dwc); + + /* wait for LPM, to ensure h/w is reset after stop_host */ + set_bit(WAIT_FOR_LPM, &mdwc->inputs); + + pm_runtime_put_sync_suspend(mdwc->dev); + dbg_event(0xFF, "StopHost psync", + atomic_read(&mdwc->dev->power.usage_count)); + } + + return 0; +} + +static void dwc3_override_vbus_status(struct dwc3_msm *mdwc, bool vbus_present) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + /* Update OTG VBUS Valid from HSPHY to controller */ + dwc3_msm_write_readback(mdwc->base, HS_PHY_CTRL_REG, + vbus_present ? UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL : + UTMI_OTG_VBUS_VALID, + vbus_present ? UTMI_OTG_VBUS_VALID | SW_SESSVLD_SEL : 0); + + /* Update only if Super Speed is supported */ + if (dwc->maximum_speed == USB_SPEED_SUPER) { + /* Update VBUS Valid from SSPHY to controller */ + dwc3_msm_write_readback(mdwc->base, SS_PHY_CTRL_REG, + LANE0_PWR_PRESENT, + vbus_present ? LANE0_PWR_PRESENT : 0); + } +} + +/** + * dwc3_otg_start_peripheral - bind/unbind the peripheral controller. + * + * @mdwc: Pointer to the dwc3_msm structure. + * @on: Turn ON/OFF the gadget. + * + * Returns 0 on success otherwise negative errno. + */ +static int dwc3_otg_start_peripheral(struct dwc3_msm *mdwc, int on) +{ + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + pm_runtime_get_sync(mdwc->dev); + dbg_event(0xFF, "StrtGdgt gsync", + atomic_read(&mdwc->dev->power.usage_count)); + + if (on) { + dev_dbg(mdwc->dev, "%s: turn on gadget %s\n", + __func__, dwc->gadget.name); + + dwc3_override_vbus_status(mdwc, true); + usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH); + usb_phy_notify_connect(mdwc->ss_phy, USB_SPEED_SUPER); + + /* Core reset is not required during start peripheral. Only + * DBM reset is required, hence perform only DBM reset here */ + dwc3_msm_block_reset(mdwc, false); + + dwc3_set_mode(dwc, DWC3_GCTL_PRTCAP_DEVICE); + mdwc->in_device_mode = true; + usb_gadget_vbus_connect(&dwc->gadget); +#ifdef CONFIG_SMP + mdwc->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ; + mdwc->pm_qos_req_dma.irq = dwc->irq; +#endif + pm_qos_add_request(&mdwc->pm_qos_req_dma, + PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE); + /* start in perf mode for better performance initially */ + msm_dwc3_perf_vote_update(mdwc, true); + schedule_delayed_work(&mdwc->perf_vote_work, + msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC)); + } else { + dev_dbg(mdwc->dev, "%s: turn off gadget %s\n", + __func__, dwc->gadget.name); + cancel_delayed_work_sync(&mdwc->perf_vote_work); + msm_dwc3_perf_vote_update(mdwc, false); + pm_qos_remove_request(&mdwc->pm_qos_req_dma); + + mdwc->in_device_mode = false; + usb_gadget_vbus_disconnect(&dwc->gadget); + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); + usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); + dwc3_override_vbus_status(mdwc, false); + dwc3_usb3_phy_suspend(dwc, false); + + /* wait for LPM, to ensure h/w is reset after stop_peripheral */ + set_bit(WAIT_FOR_LPM, &mdwc->inputs); + } + + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "StopGdgt psync", + atomic_read(&mdwc->dev->power.usage_count)); + + return 0; +} + +int get_psy_type(struct dwc3_msm *mdwc) +{ + union power_supply_propval pval = {0}; + + if (mdwc->charging_disabled) + return -EINVAL; + + if (!mdwc->usb_psy) { + mdwc->usb_psy = power_supply_get_by_name("usb"); + if (!mdwc->usb_psy) { + dev_err(mdwc->dev, "Could not get usb psy\n"); + return -ENODEV; + } + } + + power_supply_get_property(mdwc->usb_psy, POWER_SUPPLY_PROP_REAL_TYPE, + &pval); + + return pval.intval; +} + +static int dwc3_msm_gadget_vbus_draw(struct dwc3_msm *mdwc, unsigned mA) +{ + union power_supply_propval pval = {0}; + int ret, psy_type; + + psy_type = get_psy_type(mdwc); + if (psy_type == POWER_SUPPLY_TYPE_USB_FLOAT + || (mdwc->check_for_float && mdwc->float_detected)) { + if (!mA) + pval.intval = -ETIMEDOUT; + else + pval.intval = 1000 * mA; + goto set_prop; + } + + if (mdwc->max_power == mA || psy_type != POWER_SUPPLY_TYPE_USB) + return 0; + + dev_info(mdwc->dev, "Avail curr from USB = %u\n", mA); + /* Set max current limit in uA */ + pval.intval = 1000 * mA; + +set_prop: + ret = power_supply_set_property(mdwc->usb_psy, + POWER_SUPPLY_PROP_SDP_CURRENT_MAX, &pval); + if (ret) { + dev_dbg(mdwc->dev, "power supply error when setting property\n"); + return ret; + } + + mdwc->max_power = mA; + return 0; +} + + +/** + * dwc3_otg_sm_work - workqueue function. + * + * @w: Pointer to the dwc3 otg workqueue + * + * NOTE: After any change in drd_state, we must reschdule the state machine. + */ +static void dwc3_otg_sm_work(struct work_struct *w) +{ + struct dwc3_msm *mdwc = container_of(w, struct dwc3_msm, sm_work.work); + struct dwc3 *dwc = NULL; + bool work = 0; + int ret = 0; + unsigned long delay = 0; + const char *state; + + if (mdwc->dwc3) + dwc = platform_get_drvdata(mdwc->dwc3); + + if (!dwc) { + dev_err(mdwc->dev, "dwc is NULL.\n"); + return; + } + + state = dwc3_drd_state_string(mdwc->drd_state); + dev_dbg(mdwc->dev, "%s state\n", state); + dbg_event(0xFF, state, 0); + + /* Check OTG state */ + switch (mdwc->drd_state) { + case DRD_STATE_UNDEFINED: + /* put controller and phy in suspend if no cable connected */ + if (test_bit(ID, &mdwc->inputs) && + !test_bit(B_SESS_VLD, &mdwc->inputs)) { + dbg_event(0xFF, "undef_id_!bsv", 0); + pm_runtime_set_active(mdwc->dev); + pm_runtime_enable(mdwc->dev); + pm_runtime_get_noresume(mdwc->dev); + dwc3_msm_resume(mdwc); + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "Undef NoUSB", + atomic_read(&mdwc->dev->power.usage_count)); + mdwc->drd_state = DRD_STATE_IDLE; + break; + } + + dbg_event(0xFF, "Exit UNDEF", 0); + mdwc->drd_state = DRD_STATE_IDLE; + pm_runtime_set_suspended(mdwc->dev); + pm_runtime_enable(mdwc->dev); + /* fall-through */ + case DRD_STATE_IDLE: + if (test_bit(WAIT_FOR_LPM, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "still not in lpm, wait.\n"); + break; + } + + if (!test_bit(ID, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "!id\n"); + mdwc->drd_state = DRD_STATE_HOST_IDLE; + work = 1; + } else if (test_bit(B_SESS_VLD, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "b_sess_vld\n"); + mdwc->float_detected = false; + if (get_psy_type(mdwc) == POWER_SUPPLY_TYPE_USB_FLOAT) + queue_delayed_work(mdwc->dwc3_wq, + &mdwc->sdp_check, + msecs_to_jiffies(SDP_CONNETION_CHECK_TIME)); + /* + * Increment pm usage count upon cable connect. Count + * is decremented in DRD_STATE_PERIPHERAL state on + * cable disconnect or in bus suspend. + */ + pm_runtime_get_sync(mdwc->dev); + dbg_event(0xFF, "BIDLE gsync", + atomic_read(&mdwc->dev->power.usage_count)); + if (mdwc->check_for_float) { + /* + * If DP_DM are found to be floating, do not + * start the peripheral mode. + */ + if (usb_phy_dpdm_with_idp_src(mdwc->hs_phy) == + DP_DM_STATE_FLOAT) { + mdwc->float_detected = true; + dwc3_msm_gadget_vbus_draw(mdwc, 0); + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "FLT sync", atomic_read( + &mdwc->dev->power.usage_count)); + break; + } + } + dwc3_otg_start_peripheral(mdwc, 1); + mdwc->drd_state = DRD_STATE_PERIPHERAL; + work = 1; + break; + } else { + dwc3_msm_gadget_vbus_draw(mdwc, 0); + pm_relax(mdwc->dev); + dev_dbg(mdwc->dev, "Cable disconnected\n"); + } + break; + + case DRD_STATE_PERIPHERAL: + if (!test_bit(B_SESS_VLD, &mdwc->inputs) || + !test_bit(ID, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "!id || !bsv\n"); + mdwc->drd_state = DRD_STATE_IDLE; + cancel_delayed_work_sync(&mdwc->sdp_check); + dwc3_otg_start_peripheral(mdwc, 0); + /* + * Decrement pm usage count upon cable disconnect + * which was incremented upon cable connect in + * DRD_STATE_IDLE state + */ + pm_runtime_put_sync(mdwc->dev); + dbg_event(0xFF, "!BSV psync", + atomic_read(&mdwc->dev->power.usage_count)); + work = 1; + } else if (test_bit(B_SUSPEND, &mdwc->inputs) && + test_bit(B_SESS_VLD, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "BPER bsv && susp\n"); + mdwc->drd_state = DRD_STATE_PERIPHERAL_SUSPEND; + /* + * Decrement pm usage count upon bus suspend. + * Count was incremented either upon cable + * connect in DRD_STATE_IDLE or host + * initiated resume after bus suspend in + * DRD_STATE_PERIPHERAL_SUSPEND state + */ + pm_runtime_mark_last_busy(mdwc->dev); + pm_runtime_put_autosuspend(mdwc->dev); + dbg_event(0xFF, "SUSP put", + atomic_read(&mdwc->dev->power.usage_count)); + } + break; + + case DRD_STATE_PERIPHERAL_SUSPEND: + if (!test_bit(B_SESS_VLD, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "BSUSP: !bsv\n"); + mdwc->drd_state = DRD_STATE_IDLE; + cancel_delayed_work_sync(&mdwc->sdp_check); + dwc3_otg_start_peripheral(mdwc, 0); + } else if (!test_bit(B_SUSPEND, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "BSUSP !susp\n"); + mdwc->drd_state = DRD_STATE_PERIPHERAL; + /* + * Increment pm usage count upon host + * initiated resume. Count was decremented + * upon bus suspend in + * DRD_STATE_PERIPHERAL state. + */ + pm_runtime_get_sync(mdwc->dev); + dbg_event(0xFF, "!SUSP gsync", + atomic_read(&mdwc->dev->power.usage_count)); + } + break; + + case DRD_STATE_HOST_IDLE: + /* Switch to A-Device*/ + if (test_bit(ID, &mdwc->inputs)) { + dev_dbg(mdwc->dev, "id\n"); + mdwc->drd_state = DRD_STATE_IDLE; + mdwc->vbus_retry_count = 0; + work = 1; + } else { + mdwc->drd_state = DRD_STATE_HOST; + ret = dwc3_otg_start_host(mdwc, 1); + if ((ret == -EPROBE_DEFER) && + mdwc->vbus_retry_count < 3) { + /* + * Get regulator failed as regulator driver is + * not up yet. Will try to start host after 1sec + */ + mdwc->drd_state = DRD_STATE_HOST_IDLE; + dev_dbg(mdwc->dev, "Unable to get vbus regulator. Retrying...\n"); + delay = VBUS_REG_CHECK_DELAY; + work = 1; + mdwc->vbus_retry_count++; + } else if (ret == -EAGAIN) { + mdwc->drd_state = DRD_STATE_HOST_IDLE; + dev_dbg(mdwc->dev, "Core init failed. Retrying...\n"); + work = 1; + } else if (ret) { + dev_err(mdwc->dev, "unable to start host\n"); + mdwc->drd_state = DRD_STATE_HOST_IDLE; + goto ret; + } + if (mdwc->no_wakeup_src_in_hostmode) { + pm_wakeup_event(mdwc->dev, + DWC3_WAKEUP_SRC_TIMEOUT); + } + } + break; + + case DRD_STATE_HOST: + if (test_bit(ID, &mdwc->inputs) || mdwc->hc_died) { + dbg_event(0xFF, "id || hc_died", 0); + dev_dbg(mdwc->dev, "%s state id || hc_died\n", state); + dwc3_otg_start_host(mdwc, 0); + mdwc->drd_state = DRD_STATE_IDLE; + mdwc->vbus_retry_count = 0; + mdwc->hc_died = false; + work = 1; + } else { + dev_dbg(mdwc->dev, "still in a_host state. Resuming root hub.\n"); + dbg_event(0xFF, "XHCIResume", 0); + if (dwc) + pm_runtime_resume(&dwc->xhci->dev); + if (mdwc->no_wakeup_src_in_hostmode) { + pm_wakeup_event(mdwc->dev, + DWC3_WAKEUP_SRC_TIMEOUT); + } + } + break; + + default: + dev_err(mdwc->dev, "%s: invalid otg-state\n", __func__); + + } + + if (work) + queue_delayed_work(mdwc->sm_usb_wq, &mdwc->sm_work, delay); + +ret: + return; +} + +static int dwc3_msm_pm_prepare(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + struct usb_hcd *hcd; + struct xhci_hcd *xhci; + + dev_dbg(dev, "dwc3-msm PM prepare,lpm:%u\n", atomic_read(&dwc->in_lpm)); + dbg_event(0xFF, "PM Prep", 0); + if (!mdwc->in_host_mode || !mdwc->no_wakeup_src_in_hostmode) + return 0; + + hcd = dev_get_drvdata(&dwc->xhci->dev); + xhci = hcd_to_xhci(hcd); + flush_delayed_work(&mdwc->sm_work); + + /* If in lpm then prevent usb core to runtime_resume from pm_suspend */ + if (atomic_read(&dwc->in_lpm)) { + hcd_to_bus(hcd)->skip_resume = true; + hcd_to_bus(xhci->shared_hcd)->skip_resume = true; + } else { + hcd_to_bus(hcd)->skip_resume = false; + hcd_to_bus(xhci->shared_hcd)->skip_resume = false; + } + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int dwc3_msm_pm_suspend(struct device *dev) +{ + int ret = 0; + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + struct dwc3 *dwc = platform_get_drvdata(mdwc->dwc3); + + dev_dbg(dev, "dwc3-msm PM suspend\n"); + dbg_event(0xFF, "PM Sus", 0); + + flush_workqueue(mdwc->dwc3_wq); + if (!atomic_read(&dwc->in_lpm) && !mdwc->no_wakeup_src_in_hostmode) { + dev_err(mdwc->dev, "Abort PM suspend!! (USB is outside LPM)\n"); + return -EBUSY; + } + + ret = dwc3_msm_suspend(mdwc, false); + if (ret) + return ret; + + flush_work(&mdwc->bus_vote_w); + atomic_set(&mdwc->pm_suspended, 1); + + return 0; +} + +static int dwc3_msm_pm_freeze(struct device *dev) +{ + int ret = 0; + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3-msm PM freeze\n"); + dbg_event(0xFF, "PM Freeze", 0); + + flush_workqueue(mdwc->dwc3_wq); + + /* Resume the core to make sure we can power collapse it */ + ret = dwc3_msm_resume(mdwc); + + /* + * PHYs also need to be power collapsed, so call the notify_disconnect + * before suspend to ensure it. + */ + usb_phy_notify_disconnect(mdwc->hs_phy, USB_SPEED_HIGH); + if (mdwc->ss_phy->flags & PHY_HOST_MODE) { + usb_phy_notify_disconnect(mdwc->ss_phy, USB_SPEED_SUPER); + mdwc->ss_phy->flags &= ~PHY_HOST_MODE; + } + + mdwc->hs_phy->flags &= ~PHY_HOST_MODE; + + ret = dwc3_msm_suspend(mdwc, true); + if (ret) + return ret; + + flush_work(&mdwc->bus_vote_w); + atomic_set(&mdwc->pm_suspended, 1); + + return 0; +} + +static int dwc3_msm_pm_resume(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3-msm PM resume\n"); + + dbg_event(0xFF, "PM Res", 0); + + /* flush to avoid race in read/write of pm_suspended */ + flush_workqueue(mdwc->dwc3_wq); + atomic_set(&mdwc->pm_suspended, 0); + + /* Resume h/w in host mode as it may not be runtime suspended */ + if (mdwc->no_wakeup_src_in_hostmode && !test_bit(ID, &mdwc->inputs)) + dwc3_msm_resume(mdwc); + + queue_work(mdwc->dwc3_wq, &mdwc->resume_work); + + return 0; +} + +static int dwc3_msm_pm_restore(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "dwc3-msm PM restore\n"); + dbg_event(0xFF, "PM Restore", 0); + + atomic_set(&mdwc->pm_suspended, 0); + + dwc3_msm_resume(mdwc); + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + /* Restore PHY flags if hibernated in host mode */ + if (mdwc->drd_state == DRD_STATE_HOST) { + mdwc->hs_phy->flags |= PHY_HOST_MODE; + if (mdwc->ss_phy) { + mdwc->ss_phy->flags |= PHY_HOST_MODE; + usb_phy_notify_connect(mdwc->ss_phy, + USB_SPEED_SUPER); + } + + usb_phy_notify_connect(mdwc->hs_phy, USB_SPEED_HIGH); + } + + return 0; +} +#endif + +#ifdef CONFIG_PM +static int dwc3_msm_runtime_idle(struct device *dev) +{ + dev_dbg(dev, "DWC3-msm runtime idle\n"); + dbg_event(0xFF, "RT Idle", 0); + + return 0; +} + +static int dwc3_msm_runtime_suspend(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "DWC3-msm runtime suspend\n"); + dbg_event(0xFF, "RT Sus", 0); + + return dwc3_msm_suspend(mdwc, false); +} + +static int dwc3_msm_runtime_resume(struct device *dev) +{ + struct dwc3_msm *mdwc = dev_get_drvdata(dev); + + dev_dbg(dev, "DWC3-msm runtime resume\n"); + dbg_event(0xFF, "RT Res", 0); + + return dwc3_msm_resume(mdwc); +} +#endif + +static const struct dev_pm_ops dwc3_msm_dev_pm_ops = { + .prepare = dwc3_msm_pm_prepare, + .suspend = dwc3_msm_pm_suspend, + .resume = dwc3_msm_pm_resume, + .freeze = dwc3_msm_pm_freeze, + .thaw = dwc3_msm_pm_restore, + .poweroff = dwc3_msm_pm_suspend, + .restore = dwc3_msm_pm_restore, + SET_RUNTIME_PM_OPS(dwc3_msm_runtime_suspend, dwc3_msm_runtime_resume, + dwc3_msm_runtime_idle) +}; + +static const struct of_device_id of_dwc3_matach[] = { + { + .compatible = "qcom,dwc-usb3-msm", + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_matach); + +static struct platform_driver dwc3_msm_driver = { + .probe = dwc3_msm_probe, + .remove = dwc3_msm_remove, + .driver = { + .name = "msm-dwc3", + .pm = &dwc3_msm_dev_pm_ops, + .of_match_table = of_dwc3_matach, + }, +}; + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 MSM Glue Layer"); + +static int dwc3_msm_init(void) +{ + return platform_driver_register(&dwc3_msm_driver); +} +module_init(dwc3_msm_init); + +static void __exit dwc3_msm_exit(void) +{ + platform_driver_unregister(&dwc3_msm_driver); +} +module_exit(dwc3_msm_exit); diff --git a/drivers/usb/dwc3/dwc3-omap.c b/drivers/usb/dwc3/dwc3-omap.c new file mode 100644 index 000000000000..9078af0ce06c --- /dev/null +++ b/drivers/usb/dwc3/dwc3-omap.c @@ -0,0 +1,621 @@ +/** + * dwc3-omap.c - OMAP Specific Glue layer + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/platform_data/dwc3-omap.h> +#include <linux/pm_runtime.h> +#include <linux/dma-mapping.h> +#include <linux/ioport.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/extcon.h> +#include <linux/regulator/consumer.h> + +#include <linux/usb/otg.h> + +/* + * All these registers belong to OMAP's Wrapper around the + * DesignWare USB3 Core. + */ + +#define USBOTGSS_REVISION 0x0000 +#define USBOTGSS_SYSCONFIG 0x0010 +#define USBOTGSS_IRQ_EOI 0x0020 +#define USBOTGSS_EOI_OFFSET 0x0008 +#define USBOTGSS_IRQSTATUS_RAW_0 0x0024 +#define USBOTGSS_IRQSTATUS_0 0x0028 +#define USBOTGSS_IRQENABLE_SET_0 0x002c +#define USBOTGSS_IRQENABLE_CLR_0 0x0030 +#define USBOTGSS_IRQ0_OFFSET 0x0004 +#define USBOTGSS_IRQSTATUS_RAW_1 0x0030 +#define USBOTGSS_IRQSTATUS_1 0x0034 +#define USBOTGSS_IRQENABLE_SET_1 0x0038 +#define USBOTGSS_IRQENABLE_CLR_1 0x003c +#define USBOTGSS_IRQSTATUS_RAW_2 0x0040 +#define USBOTGSS_IRQSTATUS_2 0x0044 +#define USBOTGSS_IRQENABLE_SET_2 0x0048 +#define USBOTGSS_IRQENABLE_CLR_2 0x004c +#define USBOTGSS_IRQSTATUS_RAW_3 0x0050 +#define USBOTGSS_IRQSTATUS_3 0x0054 +#define USBOTGSS_IRQENABLE_SET_3 0x0058 +#define USBOTGSS_IRQENABLE_CLR_3 0x005c +#define USBOTGSS_IRQSTATUS_EOI_MISC 0x0030 +#define USBOTGSS_IRQSTATUS_RAW_MISC 0x0034 +#define USBOTGSS_IRQSTATUS_MISC 0x0038 +#define USBOTGSS_IRQENABLE_SET_MISC 0x003c +#define USBOTGSS_IRQENABLE_CLR_MISC 0x0040 +#define USBOTGSS_IRQMISC_OFFSET 0x03fc +#define USBOTGSS_UTMI_OTG_STATUS 0x0080 +#define USBOTGSS_UTMI_OTG_CTRL 0x0084 +#define USBOTGSS_UTMI_OTG_OFFSET 0x0480 +#define USBOTGSS_TXFIFO_DEPTH 0x0508 +#define USBOTGSS_RXFIFO_DEPTH 0x050c +#define USBOTGSS_MMRAM_OFFSET 0x0100 +#define USBOTGSS_FLADJ 0x0104 +#define USBOTGSS_DEBUG_CFG 0x0108 +#define USBOTGSS_DEBUG_DATA 0x010c +#define USBOTGSS_DEV_EBC_EN 0x0110 +#define USBOTGSS_DEBUG_OFFSET 0x0600 + +/* SYSCONFIG REGISTER */ +#define USBOTGSS_SYSCONFIG_DMADISABLE (1 << 16) + +/* IRQ_EOI REGISTER */ +#define USBOTGSS_IRQ_EOI_LINE_NUMBER (1 << 0) + +/* IRQS0 BITS */ +#define USBOTGSS_IRQO_COREIRQ_ST (1 << 0) + +/* IRQMISC BITS */ +#define USBOTGSS_IRQMISC_DMADISABLECLR (1 << 17) +#define USBOTGSS_IRQMISC_OEVT (1 << 16) +#define USBOTGSS_IRQMISC_DRVVBUS_RISE (1 << 13) +#define USBOTGSS_IRQMISC_CHRGVBUS_RISE (1 << 12) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_RISE (1 << 11) +#define USBOTGSS_IRQMISC_IDPULLUP_RISE (1 << 8) +#define USBOTGSS_IRQMISC_DRVVBUS_FALL (1 << 5) +#define USBOTGSS_IRQMISC_CHRGVBUS_FALL (1 << 4) +#define USBOTGSS_IRQMISC_DISCHRGVBUS_FALL (1 << 3) +#define USBOTGSS_IRQMISC_IDPULLUP_FALL (1 << 0) + +/* UTMI_OTG_STATUS REGISTER */ +#define USBOTGSS_UTMI_OTG_STATUS_DRVVBUS (1 << 5) +#define USBOTGSS_UTMI_OTG_STATUS_CHRGVBUS (1 << 4) +#define USBOTGSS_UTMI_OTG_STATUS_DISCHRGVBUS (1 << 3) +#define USBOTGSS_UTMI_OTG_STATUS_IDPULLUP (1 << 0) + +/* UTMI_OTG_CTRL REGISTER */ +#define USBOTGSS_UTMI_OTG_CTRL_SW_MODE (1 << 31) +#define USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT (1 << 9) +#define USBOTGSS_UTMI_OTG_CTRL_TXBITSTUFFENABLE (1 << 8) +#define USBOTGSS_UTMI_OTG_CTRL_IDDIG (1 << 4) +#define USBOTGSS_UTMI_OTG_CTRL_SESSEND (1 << 3) +#define USBOTGSS_UTMI_OTG_CTRL_SESSVALID (1 << 2) +#define USBOTGSS_UTMI_OTG_CTRL_VBUSVALID (1 << 1) + +struct dwc3_omap { + struct device *dev; + + int irq; + void __iomem *base; + + u32 utmi_otg_ctrl; + u32 utmi_otg_offset; + u32 irqmisc_offset; + u32 irq_eoi_offset; + u32 debug_offset; + u32 irq0_offset; + + u32 dma_status:1; + + struct extcon_dev *edev; + struct notifier_block vbus_nb; + struct notifier_block id_nb; + + struct regulator *vbus_reg; +}; + +enum omap_dwc3_vbus_id_status { + OMAP_DWC3_ID_FLOAT, + OMAP_DWC3_ID_GROUND, + OMAP_DWC3_VBUS_OFF, + OMAP_DWC3_VBUS_VALID, +}; + +static inline u32 dwc3_omap_readl(void __iomem *base, u32 offset) +{ + return readl(base + offset); +} + +static inline void dwc3_omap_writel(void __iomem *base, u32 offset, u32 value) +{ + writel(value, base + offset); +} + +static u32 dwc3_omap_read_utmi_ctrl(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_UTMI_OTG_CTRL + + omap->utmi_otg_offset); +} + +static void dwc3_omap_write_utmi_ctrl(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_UTMI_OTG_CTRL + + omap->utmi_otg_offset, value); + +} + +static u32 dwc3_omap_read_irq0_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_0 - + omap->irq0_offset); +} + +static void dwc3_omap_write_irq0_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_0 - + omap->irq0_offset, value); + +} + +static u32 dwc3_omap_read_irqmisc_status(struct dwc3_omap *omap) +{ + return dwc3_omap_readl(omap->base, USBOTGSS_IRQSTATUS_MISC + + omap->irqmisc_offset); +} + +static void dwc3_omap_write_irqmisc_status(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQSTATUS_MISC + + omap->irqmisc_offset, value); + +} + +static void dwc3_omap_write_irqmisc_set(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_MISC + + omap->irqmisc_offset, value); + +} + +static void dwc3_omap_write_irq0_set(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_SET_0 - + omap->irq0_offset, value); +} + +static void dwc3_omap_write_irqmisc_clr(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_CLR_MISC + + omap->irqmisc_offset, value); +} + +static void dwc3_omap_write_irq0_clr(struct dwc3_omap *omap, u32 value) +{ + dwc3_omap_writel(omap->base, USBOTGSS_IRQENABLE_CLR_0 - + omap->irq0_offset, value); +} + +static void dwc3_omap_set_mailbox(struct dwc3_omap *omap, + enum omap_dwc3_vbus_id_status status) +{ + int ret; + u32 val; + + switch (status) { + case OMAP_DWC3_ID_GROUND: + if (omap->vbus_reg) { + ret = regulator_enable(omap->vbus_reg); + if (ret) { + dev_err(omap->dev, "regulator enable failed\n"); + return; + } + } + + val = dwc3_omap_read_utmi_ctrl(omap); + val &= ~(USBOTGSS_UTMI_OTG_CTRL_IDDIG + | USBOTGSS_UTMI_OTG_CTRL_VBUSVALID + | USBOTGSS_UTMI_OTG_CTRL_SESSEND); + val |= USBOTGSS_UTMI_OTG_CTRL_SESSVALID + | USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + case OMAP_DWC3_VBUS_VALID: + val = dwc3_omap_read_utmi_ctrl(omap); + val &= ~USBOTGSS_UTMI_OTG_CTRL_SESSEND; + val |= USBOTGSS_UTMI_OTG_CTRL_IDDIG + | USBOTGSS_UTMI_OTG_CTRL_VBUSVALID + | USBOTGSS_UTMI_OTG_CTRL_SESSVALID + | USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + case OMAP_DWC3_ID_FLOAT: + if (omap->vbus_reg) + regulator_disable(omap->vbus_reg); + + case OMAP_DWC3_VBUS_OFF: + val = dwc3_omap_read_utmi_ctrl(omap); + val &= ~(USBOTGSS_UTMI_OTG_CTRL_SESSVALID + | USBOTGSS_UTMI_OTG_CTRL_VBUSVALID + | USBOTGSS_UTMI_OTG_CTRL_POWERPRESENT); + val |= USBOTGSS_UTMI_OTG_CTRL_SESSEND + | USBOTGSS_UTMI_OTG_CTRL_IDDIG; + dwc3_omap_write_utmi_ctrl(omap, val); + break; + + default: + dev_WARN(omap->dev, "invalid state\n"); + } +} + +static irqreturn_t dwc3_omap_interrupt(int irq, void *_omap) +{ + struct dwc3_omap *omap = _omap; + u32 reg; + + reg = dwc3_omap_read_irqmisc_status(omap); + + if (reg & USBOTGSS_IRQMISC_DMADISABLECLR) + omap->dma_status = false; + + dwc3_omap_write_irqmisc_status(omap, reg); + + reg = dwc3_omap_read_irq0_status(omap); + + dwc3_omap_write_irq0_status(omap, reg); + + return IRQ_HANDLED; +} + +static void dwc3_omap_enable_irqs(struct dwc3_omap *omap) +{ + u32 reg; + + /* enable all IRQs */ + reg = USBOTGSS_IRQO_COREIRQ_ST; + dwc3_omap_write_irq0_set(omap, reg); + + reg = (USBOTGSS_IRQMISC_OEVT | + USBOTGSS_IRQMISC_DRVVBUS_RISE | + USBOTGSS_IRQMISC_CHRGVBUS_RISE | + USBOTGSS_IRQMISC_DISCHRGVBUS_RISE | + USBOTGSS_IRQMISC_IDPULLUP_RISE | + USBOTGSS_IRQMISC_DRVVBUS_FALL | + USBOTGSS_IRQMISC_CHRGVBUS_FALL | + USBOTGSS_IRQMISC_DISCHRGVBUS_FALL | + USBOTGSS_IRQMISC_IDPULLUP_FALL); + + dwc3_omap_write_irqmisc_set(omap, reg); +} + +static void dwc3_omap_disable_irqs(struct dwc3_omap *omap) +{ + u32 reg; + + /* disable all IRQs */ + reg = USBOTGSS_IRQO_COREIRQ_ST; + dwc3_omap_write_irq0_clr(omap, reg); + + reg = (USBOTGSS_IRQMISC_OEVT | + USBOTGSS_IRQMISC_DRVVBUS_RISE | + USBOTGSS_IRQMISC_CHRGVBUS_RISE | + USBOTGSS_IRQMISC_DISCHRGVBUS_RISE | + USBOTGSS_IRQMISC_IDPULLUP_RISE | + USBOTGSS_IRQMISC_DRVVBUS_FALL | + USBOTGSS_IRQMISC_CHRGVBUS_FALL | + USBOTGSS_IRQMISC_DISCHRGVBUS_FALL | + USBOTGSS_IRQMISC_IDPULLUP_FALL); + + dwc3_omap_write_irqmisc_clr(omap, reg); +} + +static u64 dwc3_omap_dma_mask = DMA_BIT_MASK(32); + +static int dwc3_omap_id_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_omap *omap = container_of(nb, struct dwc3_omap, id_nb); + + if (event) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_FLOAT); + + return NOTIFY_DONE; +} + +static int dwc3_omap_vbus_notifier(struct notifier_block *nb, + unsigned long event, void *ptr) +{ + struct dwc3_omap *omap = container_of(nb, struct dwc3_omap, vbus_nb); + + if (event) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID); + else + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_OFF); + + return NOTIFY_DONE; +} + +static void dwc3_omap_map_offset(struct dwc3_omap *omap) +{ + struct device_node *node = omap->dev->of_node; + + /* + * Differentiate between OMAP5 and AM437x. + * + * For OMAP5(ES2.0) and AM437x wrapper revision is same, even + * though there are changes in wrapper register offsets. + * + * Using dt compatible to differentiate AM437x. + */ + if (of_device_is_compatible(node, "ti,am437x-dwc3")) { + omap->irq_eoi_offset = USBOTGSS_EOI_OFFSET; + omap->irq0_offset = USBOTGSS_IRQ0_OFFSET; + omap->irqmisc_offset = USBOTGSS_IRQMISC_OFFSET; + omap->utmi_otg_offset = USBOTGSS_UTMI_OTG_OFFSET; + omap->debug_offset = USBOTGSS_DEBUG_OFFSET; + } +} + +static void dwc3_omap_set_utmi_mode(struct dwc3_omap *omap) +{ + u32 reg; + struct device_node *node = omap->dev->of_node; + int utmi_mode = 0; + + reg = dwc3_omap_read_utmi_ctrl(omap); + + of_property_read_u32(node, "utmi-mode", &utmi_mode); + + switch (utmi_mode) { + case DWC3_OMAP_UTMI_MODE_SW: + reg |= USBOTGSS_UTMI_OTG_CTRL_SW_MODE; + break; + case DWC3_OMAP_UTMI_MODE_HW: + reg &= ~USBOTGSS_UTMI_OTG_CTRL_SW_MODE; + break; + default: + dev_WARN(omap->dev, "UNKNOWN utmi mode %d\n", utmi_mode); + } + + dwc3_omap_write_utmi_ctrl(omap, reg); +} + +static int dwc3_omap_extcon_register(struct dwc3_omap *omap) +{ + int ret; + struct device_node *node = omap->dev->of_node; + struct extcon_dev *edev; + + if (of_property_read_bool(node, "extcon")) { + edev = extcon_get_edev_by_phandle(omap->dev, 0); + if (IS_ERR(edev)) { + dev_vdbg(omap->dev, "couldn't get extcon device\n"); + return -EPROBE_DEFER; + } + + omap->vbus_nb.notifier_call = dwc3_omap_vbus_notifier; + ret = extcon_register_notifier(edev, EXTCON_USB, + &omap->vbus_nb); + if (ret < 0) + dev_vdbg(omap->dev, "failed to register notifier for USB\n"); + + omap->id_nb.notifier_call = dwc3_omap_id_notifier; + ret = extcon_register_notifier(edev, EXTCON_USB_HOST, + &omap->id_nb); + if (ret < 0) + dev_vdbg(omap->dev, "failed to register notifier for USB-HOST\n"); + + if (extcon_get_cable_state_(edev, EXTCON_USB) == true) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_VBUS_VALID); + if (extcon_get_cable_state_(edev, EXTCON_USB_HOST) == true) + dwc3_omap_set_mailbox(omap, OMAP_DWC3_ID_GROUND); + + omap->edev = edev; + } + + return 0; +} + +static int dwc3_omap_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + + struct dwc3_omap *omap; + struct resource *res; + struct device *dev = &pdev->dev; + struct regulator *vbus_reg = NULL; + + int ret; + int irq; + + u32 reg; + + void __iomem *base; + + if (!node) { + dev_err(dev, "device node not found\n"); + return -EINVAL; + } + + omap = devm_kzalloc(dev, sizeof(*omap), GFP_KERNEL); + if (!omap) + return -ENOMEM; + + platform_set_drvdata(pdev, omap); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + dev_err(dev, "missing IRQ resource: %d\n", irq); + return irq; + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + if (of_property_read_bool(node, "vbus-supply")) { + vbus_reg = devm_regulator_get(dev, "vbus"); + if (IS_ERR(vbus_reg)) { + dev_err(dev, "vbus init failed\n"); + return PTR_ERR(vbus_reg); + } + } + + omap->dev = dev; + omap->irq = irq; + omap->base = base; + omap->vbus_reg = vbus_reg; + dev->dma_mask = &dwc3_omap_dma_mask; + + pm_runtime_enable(dev); + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "get_sync failed with err %d\n", ret); + goto err0; + } + + dwc3_omap_map_offset(omap); + dwc3_omap_set_utmi_mode(omap); + + /* check the DMA Status */ + reg = dwc3_omap_readl(omap->base, USBOTGSS_SYSCONFIG); + omap->dma_status = !!(reg & USBOTGSS_SYSCONFIG_DMADISABLE); + + ret = devm_request_irq(dev, omap->irq, dwc3_omap_interrupt, 0, + "dwc3-omap", omap); + if (ret) { + dev_err(dev, "failed to request IRQ #%d --> %d\n", + omap->irq, ret); + goto err1; + } + + ret = dwc3_omap_extcon_register(omap); + if (ret < 0) + goto err2; + + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(&pdev->dev, "failed to create dwc3 core\n"); + goto err3; + } + + dwc3_omap_enable_irqs(omap); + + return 0; + +err3: + extcon_unregister_notifier(omap->edev, EXTCON_USB, &omap->vbus_nb); + extcon_unregister_notifier(omap->edev, EXTCON_USB_HOST, &omap->id_nb); +err2: + dwc3_omap_disable_irqs(omap); + +err1: + pm_runtime_put_sync(dev); + +err0: + pm_runtime_disable(dev); + + return ret; +} + +static int dwc3_omap_remove(struct platform_device *pdev) +{ + struct dwc3_omap *omap = platform_get_drvdata(pdev); + + extcon_unregister_notifier(omap->edev, EXTCON_USB, &omap->vbus_nb); + extcon_unregister_notifier(omap->edev, EXTCON_USB_HOST, &omap->id_nb); + dwc3_omap_disable_irqs(omap); + of_platform_depopulate(omap->dev); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id of_dwc3_match[] = { + { + .compatible = "ti,dwc3" + }, + { + .compatible = "ti,am437x-dwc3" + }, + { }, +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); + +#ifdef CONFIG_PM_SLEEP +static int dwc3_omap_suspend(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + omap->utmi_otg_ctrl = dwc3_omap_read_utmi_ctrl(omap); + dwc3_omap_disable_irqs(omap); + + return 0; +} + +static int dwc3_omap_resume(struct device *dev) +{ + struct dwc3_omap *omap = dev_get_drvdata(dev); + + dwc3_omap_write_utmi_ctrl(omap, omap->utmi_otg_ctrl); + dwc3_omap_enable_irqs(omap); + + pm_runtime_disable(dev); + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; +} + +static const struct dev_pm_ops dwc3_omap_dev_pm_ops = { + + SET_SYSTEM_SLEEP_PM_OPS(dwc3_omap_suspend, dwc3_omap_resume) +}; + +#define DEV_PM_OPS (&dwc3_omap_dev_pm_ops) +#else +#define DEV_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + +static struct platform_driver dwc3_omap_driver = { + .probe = dwc3_omap_probe, + .remove = dwc3_omap_remove, + .driver = { + .name = "omap-dwc3", + .of_match_table = of_dwc3_match, + .pm = DEV_PM_OPS, + }, +}; + +module_platform_driver(dwc3_omap_driver); + +MODULE_ALIAS("platform:omap-dwc3"); +MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 OMAP Glue Layer"); diff --git a/drivers/usb/dwc3/dwc3-pci.c b/drivers/usb/dwc3/dwc3-pci.c new file mode 100644 index 000000000000..68230adf2449 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-pci.c @@ -0,0 +1,237 @@ +/** + * dwc3-pci.c - PCI Specific glue layer + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/pci.h> +#include <linux/platform_device.h> +#include <linux/gpio/consumer.h> +#include <linux/acpi.h> + +#include "platform_data.h" + +#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 0xabcd +#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI 0xabce +#define PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31 0xabcf +#define PCI_DEVICE_ID_INTEL_BYT 0x0f37 +#define PCI_DEVICE_ID_INTEL_MRFLD 0x119e +#define PCI_DEVICE_ID_INTEL_BSW 0x22b7 +#define PCI_DEVICE_ID_INTEL_SPTLP 0x9d30 +#define PCI_DEVICE_ID_INTEL_SPTH 0xa130 +#define PCI_DEVICE_ID_INTEL_BXT 0x0aaa +#define PCI_DEVICE_ID_INTEL_APL 0x5aaa +#define PCI_DEVICE_ID_INTEL_KBP 0xa2b0 +#define PCI_DEVICE_ID_INTEL_GLK 0x31aa + +static const struct acpi_gpio_params reset_gpios = { 0, 0, false }; +static const struct acpi_gpio_params cs_gpios = { 1, 0, false }; + +static const struct acpi_gpio_mapping acpi_dwc3_byt_gpios[] = { + { "reset-gpios", &reset_gpios, 1 }, + { "cs-gpios", &cs_gpios, 1 }, + { }, +}; + +static int dwc3_pci_quirks(struct pci_dev *pdev) +{ + if (pdev->vendor == PCI_VENDOR_ID_AMD && + pdev->device == PCI_DEVICE_ID_AMD_NL_USB) { + struct dwc3_platform_data pdata; + + memset(&pdata, 0, sizeof(pdata)); + + pdata.has_lpm_erratum = true; + pdata.lpm_nyet_threshold = 0xf; + + pdata.u2exit_lfps_quirk = true; + pdata.u2ss_inp3_quirk = true; + pdata.req_p1p2p3_quirk = true; + pdata.del_p1p2p3_quirk = true; + pdata.del_phy_power_chg_quirk = true; + pdata.lfps_filter_quirk = true; + pdata.rx_detect_poll_quirk = true; + + pdata.tx_de_emphasis_quirk = true; + pdata.tx_de_emphasis = 1; + + /* + * FIXME these quirks should be removed when AMD NL + * taps out + */ + pdata.disable_scramble_quirk = true; + pdata.dis_u3_susphy_quirk = true; + pdata.dis_u2_susphy_quirk = true; + + return platform_device_add_data(pci_get_drvdata(pdev), &pdata, + sizeof(pdata)); + } + + if (pdev->vendor == PCI_VENDOR_ID_INTEL && + pdev->device == PCI_DEVICE_ID_INTEL_BYT) { + struct gpio_desc *gpio; + + acpi_dev_add_driver_gpios(ACPI_COMPANION(&pdev->dev), + acpi_dwc3_byt_gpios); + + /* + * These GPIOs will turn on the USB2 PHY. Note that we have to + * put the gpio descriptors again here because the phy driver + * might want to grab them, too. + */ + gpio = gpiod_get_optional(&pdev->dev, "cs", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + gpiod_set_value_cansleep(gpio, 1); + gpiod_put(gpio); + + gpio = gpiod_get_optional(&pdev->dev, "reset", GPIOD_OUT_LOW); + if (IS_ERR(gpio)) + return PTR_ERR(gpio); + + if (gpio) { + gpiod_set_value_cansleep(gpio, 1); + gpiod_put(gpio); + usleep_range(10000, 11000); + } + } + + if (pdev->vendor == PCI_VENDOR_ID_SYNOPSYS && + (pdev->device == PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3 || + pdev->device == PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI || + pdev->device == PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31)) { + + struct dwc3_platform_data pdata; + + memset(&pdata, 0, sizeof(pdata)); + pdata.usb3_lpm_capable = true; + pdata.has_lpm_erratum = true; + pdata.dis_enblslpm_quirk = true; + + return platform_device_add_data(pci_get_drvdata(pdev), &pdata, + sizeof(pdata)); + } + + return 0; +} + +static int dwc3_pci_probe(struct pci_dev *pci, + const struct pci_device_id *id) +{ + struct resource res[2]; + struct platform_device *dwc3; + int ret; + struct device *dev = &pci->dev; + + ret = pcim_enable_device(pci); + if (ret) { + dev_err(dev, "failed to enable pci device\n"); + return -ENODEV; + } + + pci_set_master(pci); + + dwc3 = platform_device_alloc("dwc3", PLATFORM_DEVID_AUTO); + if (!dwc3) { + dev_err(dev, "couldn't allocate dwc3 device\n"); + return -ENOMEM; + } + + memset(res, 0x00, sizeof(struct resource) * ARRAY_SIZE(res)); + + res[0].start = pci_resource_start(pci, 0); + res[0].end = pci_resource_end(pci, 0); + res[0].name = "dwc_usb3"; + res[0].flags = IORESOURCE_MEM; + + res[1].start = pci->irq; + res[1].name = "dwc_usb3"; + res[1].flags = IORESOURCE_IRQ; + + ret = platform_device_add_resources(dwc3, res, ARRAY_SIZE(res)); + if (ret) { + dev_err(dev, "couldn't add resources to dwc3 device\n"); + goto err; + } + + pci_set_drvdata(pci, dwc3); + ret = dwc3_pci_quirks(pci); + if (ret) + goto err; + + dwc3->dev.parent = dev; + ACPI_COMPANION_SET(&dwc3->dev, ACPI_COMPANION(dev)); + + ret = platform_device_add(dwc3); + if (ret) { + dev_err(dev, "failed to register dwc3 device\n"); + goto err; + } + + return 0; +err: + platform_device_put(dwc3); + return ret; +} + +static void dwc3_pci_remove(struct pci_dev *pci) +{ + acpi_dev_remove_driver_gpios(ACPI_COMPANION(&pci->dev)); + platform_device_unregister(pci_get_drvdata(pci)); +} + +static const struct pci_device_id dwc3_pci_id_table[] = { + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3), + }, + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB3_AXI), + }, + { + PCI_DEVICE(PCI_VENDOR_ID_SYNOPSYS, + PCI_DEVICE_ID_SYNOPSYS_HAPSUSB31), + }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BSW), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BYT), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_MRFLD), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SPTLP), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_SPTH), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_BXT), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_APL), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_KBP), }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_GLK), }, + { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_NL_USB), }, + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(pci, dwc3_pci_id_table); + +static struct pci_driver dwc3_pci_driver = { + .name = "dwc3-pci", + .id_table = dwc3_pci_id_table, + .probe = dwc3_pci_probe, + .remove = dwc3_pci_remove, +}; + +MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 PCI Glue Layer"); + +module_pci_driver(dwc3_pci_driver); diff --git a/drivers/usb/dwc3/dwc3-qcom.c b/drivers/usb/dwc3/dwc3-qcom.c new file mode 100644 index 000000000000..088026048f49 --- /dev/null +++ b/drivers/usb/dwc3/dwc3-qcom.c @@ -0,0 +1,130 @@ +/* Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 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/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> + +struct dwc3_qcom { + struct device *dev; + + struct clk *core_clk; + struct clk *iface_clk; + struct clk *sleep_clk; +}; + +static int dwc3_qcom_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct dwc3_qcom *qdwc; + int ret; + + qdwc = devm_kzalloc(&pdev->dev, sizeof(*qdwc), GFP_KERNEL); + if (!qdwc) + return -ENOMEM; + + platform_set_drvdata(pdev, qdwc); + + qdwc->dev = &pdev->dev; + + qdwc->core_clk = devm_clk_get(qdwc->dev, "core"); + if (IS_ERR(qdwc->core_clk)) { + dev_err(qdwc->dev, "failed to get core clock\n"); + return PTR_ERR(qdwc->core_clk); + } + + qdwc->iface_clk = devm_clk_get(qdwc->dev, "iface"); + if (IS_ERR(qdwc->iface_clk)) { + dev_info(qdwc->dev, "failed to get optional iface clock\n"); + qdwc->iface_clk = NULL; + } + + qdwc->sleep_clk = devm_clk_get(qdwc->dev, "sleep"); + if (IS_ERR(qdwc->sleep_clk)) { + dev_info(qdwc->dev, "failed to get optional sleep clock\n"); + qdwc->sleep_clk = NULL; + } + + ret = clk_prepare_enable(qdwc->core_clk); + if (ret) { + dev_err(qdwc->dev, "failed to enable core clock\n"); + goto err_core; + } + + ret = clk_prepare_enable(qdwc->iface_clk); + if (ret) { + dev_err(qdwc->dev, "failed to enable optional iface clock\n"); + goto err_iface; + } + + ret = clk_prepare_enable(qdwc->sleep_clk); + if (ret) { + dev_err(qdwc->dev, "failed to enable optional sleep clock\n"); + goto err_sleep; + } + + ret = of_platform_populate(node, NULL, NULL, qdwc->dev); + if (ret) { + dev_err(qdwc->dev, "failed to register core - %d\n", ret); + goto err_clks; + } + + return 0; + +err_clks: + clk_disable_unprepare(qdwc->sleep_clk); +err_sleep: + clk_disable_unprepare(qdwc->iface_clk); +err_iface: + clk_disable_unprepare(qdwc->core_clk); +err_core: + return ret; +} + +static int dwc3_qcom_remove(struct platform_device *pdev) +{ + struct dwc3_qcom *qdwc = platform_get_drvdata(pdev); + + of_platform_depopulate(&pdev->dev); + + clk_disable_unprepare(qdwc->sleep_clk); + clk_disable_unprepare(qdwc->iface_clk); + clk_disable_unprepare(qdwc->core_clk); + + return 0; +} + +static const struct of_device_id of_dwc3_match[] = { + { .compatible = "qcom,dwc3" }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, of_dwc3_match); + +static struct platform_driver dwc3_qcom_driver = { + .probe = dwc3_qcom_probe, + .remove = dwc3_qcom_remove, + .driver = { + .name = "qcom-dwc3", + .of_match_table = of_dwc3_match, + }, +}; + +module_platform_driver(dwc3_qcom_driver); + +MODULE_ALIAS("platform:qcom-dwc3"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("DesignWare USB3 QCOM Glue Layer"); +MODULE_AUTHOR("Ivan T. Ivanov <iivanov@mm-sol.com>"); diff --git a/drivers/usb/dwc3/dwc3-st.c b/drivers/usb/dwc3/dwc3-st.c new file mode 100644 index 000000000000..81db2fa08cad --- /dev/null +++ b/drivers/usb/dwc3/dwc3-st.c @@ -0,0 +1,371 @@ +/** + * dwc3-st.c Support for dwc3 platform devices on ST Microelectronics platforms + * + * This is a small driver for the dwc3 to provide the glue logic + * to configure the controller. Tested on STi platforms. + * + * Copyright (C) 2014 Stmicroelectronics + * + * Author: Giuseppe Cavallaro <peppe.cavallaro@st.com> + * Contributors: Aymen Bouattay <aymen.bouattay@st.com> + * Peter Griffin <peter.griffin@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * Inspired by dwc3-omap.c and dwc3-exynos.c. + */ + +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/regmap.h> +#include <linux/reset.h> +#include <linux/usb/of.h> + +#include "core.h" +#include "io.h" + +/* glue registers */ +#define CLKRST_CTRL 0x00 +#define AUX_CLK_EN BIT(0) +#define SW_PIPEW_RESET_N BIT(4) +#define EXT_CFG_RESET_N BIT(8) +/* + * 1'b0 : The host controller complies with the xHCI revision 0.96 + * 1'b1 : The host controller complies with the xHCI revision 1.0 + */ +#define XHCI_REVISION BIT(12) + +#define USB2_VBUS_MNGMNT_SEL1 0x2C +/* + * For all fields in USB2_VBUS_MNGMNT_SEL1 + * 2’b00 : Override value from Reg 0x30 is selected + * 2’b01 : utmiotg_<signal_name> from usb3_top is selected + * 2’b10 : pipew_<signal_name> from PIPEW instance is selected + * 2’b11 : value is 1'b0 + */ +#define USB2_VBUS_REG30 0x0 +#define USB2_VBUS_UTMIOTG 0x1 +#define USB2_VBUS_PIPEW 0x2 +#define USB2_VBUS_ZERO 0x3 + +#define SEL_OVERRIDE_VBUSVALID(n) (n << 0) +#define SEL_OVERRIDE_POWERPRESENT(n) (n << 4) +#define SEL_OVERRIDE_BVALID(n) (n << 8) + +/* Static DRD configuration */ +#define USB3_CONTROL_MASK 0xf77 + +#define USB3_DEVICE_NOT_HOST BIT(0) +#define USB3_FORCE_VBUSVALID BIT(1) +#define USB3_DELAY_VBUSVALID BIT(2) +#define USB3_SEL_FORCE_OPMODE BIT(4) +#define USB3_FORCE_OPMODE(n) (n << 5) +#define USB3_SEL_FORCE_DPPULLDOWN2 BIT(8) +#define USB3_FORCE_DPPULLDOWN2 BIT(9) +#define USB3_SEL_FORCE_DMPULLDOWN2 BIT(10) +#define USB3_FORCE_DMPULLDOWN2 BIT(11) + +/** + * struct st_dwc3 - dwc3-st driver private structure + * @dev: device pointer + * @glue_base: ioaddr for the glue registers + * @regmap: regmap pointer for getting syscfg + * @syscfg_reg_off: usb syscfg control offset + * @dr_mode: drd static host/device config + * @rstc_pwrdn: rest controller for powerdown signal + * @rstc_rst: reset controller for softreset signal + */ + +struct st_dwc3 { + struct device *dev; + void __iomem *glue_base; + struct regmap *regmap; + int syscfg_reg_off; + enum usb_dr_mode dr_mode; + struct reset_control *rstc_pwrdn; + struct reset_control *rstc_rst; +}; + +static inline u32 st_dwc3_readl(void __iomem *base, u32 offset) +{ + return readl_relaxed(base + offset); +} + +static inline void st_dwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + writel_relaxed(value, base + offset); +} + +/** + * st_dwc3_drd_init: program the port + * @dwc3_data: driver private structure + * Description: this function is to program the port as either host or device + * according to the static configuration passed from devicetree. + * OTG and dual role are not yet supported! + */ +static int st_dwc3_drd_init(struct st_dwc3 *dwc3_data) +{ + u32 val; + int err; + + err = regmap_read(dwc3_data->regmap, dwc3_data->syscfg_reg_off, &val); + if (err) + return err; + + val &= USB3_CONTROL_MASK; + + switch (dwc3_data->dr_mode) { + case USB_DR_MODE_PERIPHERAL: + + val &= ~(USB3_FORCE_VBUSVALID | USB3_DELAY_VBUSVALID + | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) + | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 + | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); + + val |= USB3_DEVICE_NOT_HOST; + break; + + case USB_DR_MODE_HOST: + + val &= ~(USB3_DEVICE_NOT_HOST | USB3_FORCE_VBUSVALID + | USB3_SEL_FORCE_OPMODE | USB3_FORCE_OPMODE(0x3) + | USB3_SEL_FORCE_DPPULLDOWN2 | USB3_FORCE_DPPULLDOWN2 + | USB3_SEL_FORCE_DMPULLDOWN2 | USB3_FORCE_DMPULLDOWN2); + + /* + * USB3_DELAY_VBUSVALID is ANDed with USB_C_VBUSVALID. Thus, + * when set to ‘0‘, it can delay the arrival of VBUSVALID + * information to VBUSVLDEXT2 input of the pico PHY. + * We don't want to do that so we set the bit to '1'. + */ + + val |= USB3_DELAY_VBUSVALID; + break; + + default: + dev_err(dwc3_data->dev, "Unsupported mode of operation %d\n", + dwc3_data->dr_mode); + return -EINVAL; + } + + return regmap_write(dwc3_data->regmap, dwc3_data->syscfg_reg_off, val); +} + +/** + * st_dwc3_init: init the controller via glue logic + * @dwc3_data: driver private structure + */ +static void st_dwc3_init(struct st_dwc3 *dwc3_data) +{ + u32 reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL); + + reg |= AUX_CLK_EN | EXT_CFG_RESET_N | XHCI_REVISION; + reg &= ~SW_PIPEW_RESET_N; + st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg); + + /* configure mux for vbus, powerpresent and bvalid signals */ + reg = st_dwc3_readl(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1); + + reg |= SEL_OVERRIDE_VBUSVALID(USB2_VBUS_UTMIOTG) | + SEL_OVERRIDE_POWERPRESENT(USB2_VBUS_UTMIOTG) | + SEL_OVERRIDE_BVALID(USB2_VBUS_UTMIOTG); + + st_dwc3_writel(dwc3_data->glue_base, USB2_VBUS_MNGMNT_SEL1, reg); + + reg = st_dwc3_readl(dwc3_data->glue_base, CLKRST_CTRL); + reg |= SW_PIPEW_RESET_N; + st_dwc3_writel(dwc3_data->glue_base, CLKRST_CTRL, reg); +} + +static int st_dwc3_probe(struct platform_device *pdev) +{ + struct st_dwc3 *dwc3_data; + struct resource *res; + struct device *dev = &pdev->dev; + struct device_node *node = dev->of_node, *child; + struct platform_device *child_pdev; + struct regmap *regmap; + int ret; + + dwc3_data = devm_kzalloc(dev, sizeof(*dwc3_data), GFP_KERNEL); + if (!dwc3_data) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reg-glue"); + dwc3_data->glue_base = devm_ioremap_resource(dev, res); + if (IS_ERR(dwc3_data->glue_base)) + return PTR_ERR(dwc3_data->glue_base); + + regmap = syscon_regmap_lookup_by_phandle(node, "st,syscfg"); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + dma_set_coherent_mask(dev, dev->coherent_dma_mask); + dwc3_data->dev = dev; + dwc3_data->regmap = regmap; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "syscfg-reg"); + if (!res) { + ret = -ENXIO; + goto undo_platform_dev_alloc; + } + + dwc3_data->syscfg_reg_off = res->start; + + dev_vdbg(&pdev->dev, "glue-logic addr 0x%pK, syscfg-reg offset 0x%x\n", + dwc3_data->glue_base, dwc3_data->syscfg_reg_off); + + dwc3_data->rstc_pwrdn = devm_reset_control_get(dev, "powerdown"); + if (IS_ERR(dwc3_data->rstc_pwrdn)) { + dev_err(&pdev->dev, "could not get power controller\n"); + ret = PTR_ERR(dwc3_data->rstc_pwrdn); + goto undo_platform_dev_alloc; + } + + /* Manage PowerDown */ + reset_control_deassert(dwc3_data->rstc_pwrdn); + + dwc3_data->rstc_rst = devm_reset_control_get(dev, "softreset"); + if (IS_ERR(dwc3_data->rstc_rst)) { + dev_err(&pdev->dev, "could not get reset controller\n"); + ret = PTR_ERR(dwc3_data->rstc_rst); + goto undo_powerdown; + } + + /* Manage SoftReset */ + reset_control_deassert(dwc3_data->rstc_rst); + + child = of_get_child_by_name(node, "dwc3"); + if (!child) { + dev_err(&pdev->dev, "failed to find dwc3 core node\n"); + ret = -ENODEV; + goto undo_softreset; + } + + /* Allocate and initialize the core */ + ret = of_platform_populate(node, NULL, NULL, dev); + if (ret) { + dev_err(dev, "failed to add dwc3 core\n"); + goto undo_softreset; + } + + child_pdev = of_find_device_by_node(child); + if (!child_pdev) { + dev_err(dev, "failed to find dwc3 core device\n"); + ret = -ENODEV; + goto undo_softreset; + } + + dwc3_data->dr_mode = usb_get_dr_mode(&child_pdev->dev); + + /* + * Configure the USB port as device or host according to the static + * configuration passed from DT. + * DRD is the only mode currently supported so this will be enhanced + * as soon as OTG is available. + */ + ret = st_dwc3_drd_init(dwc3_data); + if (ret) { + dev_err(dev, "drd initialisation failed\n"); + goto undo_softreset; + } + + /* ST glue logic init */ + st_dwc3_init(dwc3_data); + + platform_set_drvdata(pdev, dwc3_data); + return 0; + +undo_softreset: + reset_control_assert(dwc3_data->rstc_rst); +undo_powerdown: + reset_control_assert(dwc3_data->rstc_pwrdn); +undo_platform_dev_alloc: + platform_device_put(pdev); + return ret; +} + +static int st_dwc3_remove(struct platform_device *pdev) +{ + struct st_dwc3 *dwc3_data = platform_get_drvdata(pdev); + + of_platform_depopulate(&pdev->dev); + + reset_control_assert(dwc3_data->rstc_pwrdn); + reset_control_assert(dwc3_data->rstc_rst); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int st_dwc3_suspend(struct device *dev) +{ + struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); + + reset_control_assert(dwc3_data->rstc_pwrdn); + reset_control_assert(dwc3_data->rstc_rst); + + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static int st_dwc3_resume(struct device *dev) +{ + struct st_dwc3 *dwc3_data = dev_get_drvdata(dev); + int ret; + + pinctrl_pm_select_default_state(dev); + + reset_control_deassert(dwc3_data->rstc_pwrdn); + reset_control_deassert(dwc3_data->rstc_rst); + + ret = st_dwc3_drd_init(dwc3_data); + if (ret) { + dev_err(dev, "drd initialisation failed\n"); + return ret; + } + + /* ST glue logic init */ + st_dwc3_init(dwc3_data); + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static SIMPLE_DEV_PM_OPS(st_dwc3_dev_pm_ops, st_dwc3_suspend, st_dwc3_resume); + +static const struct of_device_id st_dwc3_match[] = { + { .compatible = "st,stih407-dwc3" }, + { /* sentinel */ }, +}; + +MODULE_DEVICE_TABLE(of, st_dwc3_match); + +static struct platform_driver st_dwc3_driver = { + .probe = st_dwc3_probe, + .remove = st_dwc3_remove, + .driver = { + .name = "usb-st-dwc3", + .of_match_table = st_dwc3_match, + .pm = &st_dwc3_dev_pm_ops, + }, +}; + +module_platform_driver(st_dwc3_driver); + +MODULE_AUTHOR("Giuseppe Cavallaro <peppe.cavallaro@st.com>"); +MODULE_DESCRIPTION("DesignWare USB3 STi Glue Layer"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c new file mode 100644 index 000000000000..09318bceacfe --- /dev/null +++ b/drivers/usb/dwc3/ep0.c @@ -0,0 +1,1246 @@ +/** + * ep0.c - DesignWare USB3 DRD Controller Endpoint 0 Handling + * + * 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/dma-mapping.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> +#include <linux/usb/composite.h> + +#include "core.h" +#include "debug.h" +#include "gadget.h" +#include "io.h" +#include "debug.h" + + +static bool enable_dwc3_u1u2; +module_param(enable_dwc3_u1u2, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(enable_dwc3_u1u2, "Enable support for U1U2 low power modes"); + +static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep); +static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, + struct dwc3_ep *dep, struct dwc3_request *req); + +static const char *dwc3_ep0_state_string(enum dwc3_ep0_state state) +{ + switch (state) { + case EP0_UNCONNECTED: + return "Unconnected"; + case EP0_SETUP_PHASE: + return "Setup Phase"; + case EP0_DATA_PHASE: + return "Data Phase"; + case EP0_STATUS_PHASE: + return "Status Phase"; + default: + return "UNKNOWN"; + } +} + +static void dwc3_ep0_prepare_one_trb(struct dwc3 *dwc, u8 epnum, + dma_addr_t buf_dma, u32 len, u32 type, bool chain) +{ + struct dwc3_trb *trb; + struct dwc3_ep *dep; + + dep = dwc->eps[epnum]; + + trb = &dwc->ep0_trb[dep->free_slot]; + + if (chain) + dep->free_slot++; + + trb->bpl = lower_32_bits(buf_dma); + trb->bph = upper_32_bits(buf_dma); + trb->size = len; + trb->ctrl = type; + + trb->ctrl |= (DWC3_TRB_CTRL_HWO + | DWC3_TRB_CTRL_ISP_IMI); + + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + else + trb->ctrl |= (DWC3_TRB_CTRL_IOC + | DWC3_TRB_CTRL_LST); + + trace_dwc3_prepare_trb(dep, trb); +} + +static int dwc3_ep0_start_trans(struct dwc3 *dwc, u8 epnum) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_ep *dep; + int ret; + + dep = dwc->eps[epnum]; + if (dep->flags & DWC3_EP_BUSY) { + dwc3_trace(trace_dwc3_ep0, "%s still busy", dep->name); + return 0; + } + + memset(¶ms, 0, sizeof(params)); + params.param0 = upper_32_bits(dwc->ep0_trb_addr); + params.param1 = lower_32_bits(dwc->ep0_trb_addr); + + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_STARTTRANSFER, ¶ms); + if (ret < 0) { + dwc3_trace(trace_dwc3_ep0, "%s STARTTRANSFER failed", + dep->name); + return ret; + } + + dep->flags |= DWC3_EP_BUSY; + dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + + dwc->ep0_next_event = DWC3_EP0_COMPLETE; + + return 0; +} + +static int __dwc3_gadget_ep0_queue(struct dwc3_ep *dep, + struct dwc3_request *req) +{ + struct dwc3 *dwc = dep->dwc; + + req->request.actual = 0; + req->request.status = -EINPROGRESS; + req->epnum = dep->number; + + list_add_tail(&req->list, &dep->request_list); + + /* + * Gadget driver might not be quick enough to queue a request + * before we get a Transfer Not Ready event on this endpoint. + * + * In that case, we will set DWC3_EP_PENDING_REQUEST. When that + * flag is set, it's telling us that as soon as Gadget queues the + * required request, we should kick the transfer here because the + * IRQ we were waiting for is long gone. + */ + if (dep->flags & DWC3_EP_PENDING_REQUEST) { + unsigned direction; + + direction = !!(dep->flags & DWC3_EP0_DIR_IN); + + if (dwc->ep0state != EP0_DATA_PHASE) { + dev_WARN(dwc->dev, "Unexpected pending request\n"); + return 0; + } + + __dwc3_ep0_do_control_data(dwc, dwc->eps[direction], req); + + dep->flags &= ~(DWC3_EP_PENDING_REQUEST | + DWC3_EP0_DIR_IN); + + return 0; + } + + /* + * In case gadget driver asked us to delay the STATUS phase, + * handle it here. + */ + if (dwc->delayed_status) { + unsigned direction; + + direction = !dwc->ep0_expect_in; + dwc->delayed_status = false; + usb_gadget_set_state(&dwc->gadget, USB_STATE_CONFIGURED); + + if (dwc->ep0state == EP0_STATUS_PHASE) + __dwc3_ep0_do_control_status(dwc, dwc->eps[direction]); + else + dwc3_trace(trace_dwc3_ep0, + "too early for delayed status"); + + return 0; + } + + /* + * Unfortunately we have uncovered a limitation wrt the Data Phase. + * + * Section 9.4 says we can wait for the XferNotReady(DATA) event to + * come before issueing Start Transfer command, but if we do, we will + * miss situations where the host starts another SETUP phase instead of + * the DATA phase. Such cases happen at least on TD.7.6 of the Link + * Layer Compliance Suite. + * + * The problem surfaces due to the fact that in case of back-to-back + * SETUP packets there will be no XferNotReady(DATA) generated and we + * will be stuck waiting for XferNotReady(DATA) forever. + * + * By looking at tables 9-13 and 9-14 of the Databook, we can see that + * it tells us to start Data Phase right away. It also mentions that if + * we receive a SETUP phase instead of the DATA phase, core will issue + * XferComplete for the DATA phase, before actually initiating it in + * the wire, with the TRB's status set to "SETUP_PENDING". Such status + * can only be used to print some debugging logs, as the core expects + * us to go through to the STATUS phase and start a CONTROL_STATUS TRB, + * just so it completes right away, without transferring anything and, + * only then, we can go back to the SETUP phase. + * + * Because of this scenario, SNPS decided to change the programming + * model of control transfers and support on-demand transfers only for + * the STATUS phase. To fix the issue we have now, we will always wait + * for gadget driver to queue the DATA phase's struct usb_request, then + * start it right away. + * + * If we're actually in a 2-stage transfer, we will wait for + * XferNotReady(STATUS). + */ + if (dwc->three_stage_setup) { + unsigned direction; + + direction = dwc->ep0_expect_in; + dwc->ep0state = EP0_DATA_PHASE; + + __dwc3_ep0_do_control_data(dwc, dwc->eps[direction], req); + + dep->flags &= ~DWC3_EP0_DIR_IN; + } + + return 0; +} + +int dwc3_gadget_ep0_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + + int ret; + enum dwc3_link_state link_state; + u32 reg; + + spin_lock_irqsave(&dwc->lock, flags); + if (!dep->endpoint.desc) { + dwc3_trace(trace_dwc3_ep0, + "trying to queue request %pK to disabled %s", + request, dep->name); + ret = -ESHUTDOWN; + goto out; + } + + /* we share one TRB for ep0/1 */ + if (!list_empty(&dep->request_list)) { + ret = -EBUSY; + goto out; + } + + /* if link stats is in L1 initiate remote wakeup before queuing req */ + if (dwc->speed != DWC3_DSTS_SUPERSPEED) { + link_state = dwc3_get_link_state(dwc); + /* in HS this link state is same as L1 */ + if (link_state == DWC3_LINK_STATE_U2) { + dwc->l1_remote_wakeup_cnt++; + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= DWC3_DCTL_ULSTCHNG_RECOVERY; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + } + + dwc3_trace(trace_dwc3_ep0, + "queueing request %pK to %s length %d state '%s'", + request, dep->name, request->length, + dwc3_ep0_state_string(dwc->ep0state)); + + ret = __dwc3_gadget_ep0_queue(dep, req); + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +void dwc3_ep0_stall_and_restart(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + + /* reinitialize physical ep1 */ + dep = dwc->eps[1]; + dep->flags = DWC3_EP_ENABLED; + + /* stall is always issued on EP0 */ + dep = dwc->eps[0]; + __dwc3_gadget_ep_set_halt(dep, 1, false); + dep->flags = DWC3_EP_ENABLED; + dwc->delayed_status = false; + + if (!list_empty(&dep->request_list)) { + struct dwc3_request *req; + + req = next_request(&dep->request_list); + dwc3_gadget_giveback(dep, req, -ECONNRESET); + } + + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); +} + +int __dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + dbg_event(dep->number, "EP0STAL", value); + dwc3_ep0_stall_and_restart(dwc); + + return 0; +} + +int dwc3_gadget_ep0_set_halt(struct usb_ep *ep, int value) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep0_set_halt(ep, value); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +void dwc3_ep0_out_start(struct dwc3 *dwc) +{ + int ret; + + dwc3_ep0_prepare_one_trb(dwc, 0, dwc->ctrl_req_addr, 8, + DWC3_TRBCTL_CONTROL_SETUP, false); + ret = dwc3_ep0_start_trans(dwc, 0); + if (WARN_ON_ONCE(ret < 0)) + dbg_event(dwc->eps[0]->number, "EOUTSTART", ret); +} + +static struct dwc3_ep *dwc3_wIndex_to_dep(struct dwc3 *dwc, __le16 wIndex_le) +{ + struct dwc3_ep *dep; + u32 windex = le16_to_cpu(wIndex_le); + u32 epnum; + + epnum = (windex & USB_ENDPOINT_NUMBER_MASK) << 1; + if ((windex & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) + epnum |= 1; + + dep = dwc->eps[epnum]; + if (dep->flags & DWC3_EP_ENABLED) + return dep; + + return NULL; +} + +static void dwc3_ep0_status_cmpl(struct usb_ep *ep, struct usb_request *req) +{ +} + +static int dwc3_ep0_delegate_req(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + int ret; + + spin_unlock(&dwc->lock); + ret = dwc->gadget_driver->setup(&dwc->gadget, ctrl); + spin_lock(&dwc->lock); + return ret; +} + +/* + * ch 9.4.5 + */ +static int dwc3_ep0_handle_status(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl) +{ + int ret; + struct dwc3_ep *dep; + u32 recip; + u32 reg; + u16 usb_status = 0; + __le16 *response_pkt; + + recip = ctrl->bRequestType & USB_RECIP_MASK; + switch (recip) { + case USB_RECIP_DEVICE: + /* + * LTM will be set once we know how to set this in HW. + */ + usb_status |= dwc->gadget.is_selfpowered; + + if (dwc->speed == DWC3_DSTS_SUPERSPEED) { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (reg & DWC3_DCTL_INITU1ENA) + usb_status |= 1 << USB_DEV_STAT_U1_ENABLED; + if (reg & DWC3_DCTL_INITU2ENA) + usb_status |= 1 << USB_DEV_STAT_U2_ENABLED; + } else { + usb_status |= dwc->gadget.remote_wakeup << + USB_DEVICE_REMOTE_WAKEUP; + } + + break; + + case USB_RECIP_INTERFACE: + /* + * Function Remote Wake Capable D0 + * Function Remote Wakeup D1 + */ + + ret = dwc3_ep0_delegate_req(dwc, ctrl); + if (ret) + return ret; + break; + + case USB_RECIP_ENDPOINT: + dep = dwc3_wIndex_to_dep(dwc, ctrl->wIndex); + if (!dep) + return -EINVAL; + + if (dep->flags & DWC3_EP_STALL) + usb_status = 1 << USB_ENDPOINT_HALT; + break; + default: + return -EINVAL; + } + + response_pkt = (__le16 *) dwc->setup_buf; + *response_pkt = cpu_to_le16(usb_status); + + dep = dwc->eps[0]; + dwc->ep0_usb_req.dep = dep; + dwc->ep0_usb_req.request.length = sizeof(*response_pkt); + dwc->ep0_usb_req.request.buf = dwc->setup_buf; + dwc->ep0_usb_req.request.complete = dwc3_ep0_status_cmpl; + dwc->ep0_usb_req.request.dma = DMA_ERROR_CODE; + + return __dwc3_gadget_ep0_queue(dep, &dwc->ep0_usb_req); +} + +static int dwc3_ep0_handle_feature(struct dwc3 *dwc, + struct usb_ctrlrequest *ctrl, int set) +{ + struct dwc3_ep *dep; + u32 recip; + u32 wValue; + u32 wIndex; + u32 reg; + int ret; + enum usb_device_state state; + + wValue = le16_to_cpu(ctrl->wValue); + wIndex = le16_to_cpu(ctrl->wIndex); + recip = ctrl->bRequestType & USB_RECIP_MASK; + state = dwc->gadget.state; + + switch (recip) { + case USB_RECIP_DEVICE: + + switch (wValue) { + case USB_DEVICE_REMOTE_WAKEUP: + pr_debug("%s(): remote wakeup :%s\n", __func__, + (set ? "enabled" : "disabled")); + dwc->gadget.remote_wakeup = set; + break; + /* + * 9.4.1 says only only for SS, in AddressState only for + * default control pipe + */ + case USB_DEVICE_U1_ENABLE: + if (state != USB_STATE_CONFIGURED) + return -EINVAL; + if (dwc->speed != DWC3_DSTS_SUPERSPEED) + return -EINVAL; + + if (dwc->usb3_u1u2_disable && !enable_dwc3_u1u2) + return -EINVAL; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (set) + reg |= DWC3_DCTL_INITU1ENA; + else + reg &= ~DWC3_DCTL_INITU1ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + break; + + case USB_DEVICE_U2_ENABLE: + if (state != USB_STATE_CONFIGURED) + return -EINVAL; + if (dwc->speed != DWC3_DSTS_SUPERSPEED) + return -EINVAL; + + if (dwc->usb3_u1u2_disable && !enable_dwc3_u1u2) + return -EINVAL; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (set) + reg |= DWC3_DCTL_INITU2ENA; + else + reg &= ~DWC3_DCTL_INITU2ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + break; + + case USB_DEVICE_LTM_ENABLE: + return -EINVAL; + + case USB_DEVICE_TEST_MODE: + if ((wIndex & 0xff) != 0) + return -EINVAL; + if (!set) + return -EINVAL; + + dwc->test_mode_nr = wIndex >> 8; + dwc->test_mode = true; + break; + default: + return -EINVAL; + } + break; + + case USB_RECIP_INTERFACE: + switch (wValue) { + case USB_INTRF_FUNC_SUSPEND: + if (wIndex & USB_INTRF_FUNC_SUSPEND_LP) + /* XXX enable Low power suspend */ + ; + if (wIndex & USB_INTRF_FUNC_SUSPEND_RW) + /* XXX enable remote wakeup */ + ; + ret = dwc3_ep0_delegate_req(dwc, ctrl); + if (ret) + return ret; + break; + default: + return -EINVAL; + } + break; + + case USB_RECIP_ENDPOINT: + switch (wValue) { + case USB_ENDPOINT_HALT: + dep = dwc3_wIndex_to_dep(dwc, wIndex); + if (!dep) + return -EINVAL; + if (set == 0 && (dep->flags & DWC3_EP_WEDGE)) + break; + ret = __dwc3_gadget_ep_set_halt(dep, set, true); + if (ret) + return -EINVAL; + break; + default: + return -EINVAL; + } + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int dwc3_ep0_set_address(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = dwc->gadget.state; + u32 addr; + u32 reg; + + addr = le16_to_cpu(ctrl->wValue); + if (addr > 127) { + dwc3_trace(trace_dwc3_ep0, "invalid device address %d", addr); + return -EINVAL; + } + + if (state == USB_STATE_CONFIGURED) { + dwc3_trace(trace_dwc3_ep0, + "trying to set address when configured"); + return -EINVAL; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_DEVADDR_MASK); + reg |= DWC3_DCFG_DEVADDR(addr); + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + if (addr) + usb_gadget_set_state(&dwc->gadget, USB_STATE_ADDRESS); + else + usb_gadget_set_state(&dwc->gadget, USB_STATE_DEFAULT); + + return 0; +} + +static int dwc3_ep0_set_config(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + enum usb_device_state state = dwc->gadget.state; + u32 cfg; + int ret, num; + u32 reg; + struct dwc3_ep *dep; + + cfg = le16_to_cpu(ctrl->wValue); + + switch (state) { + case USB_STATE_DEFAULT: + return -EINVAL; + + case USB_STATE_ADDRESS: + /* + * If tx-fifo-resize flag is not set for the controller, then + * do not clear existing allocated TXFIFO since we do not + * allocate it again in dwc3_gadget_resize_tx_fifos + */ + if (dwc->needs_fifo_resize) { + /* Read ep0IN related TXFIFO size */ + dwc->last_fifo_depth = (dwc3_readl(dwc->regs, + DWC3_GTXFIFOSIZ(0)) & 0xFFFF); + /* Clear existing TXFIFO for all IN eps except ep0 */ + for (num = 0; num < dwc->num_in_eps; num++) { + dep = dwc->eps[(num << 1) | 1]; + if (num) { + dwc3_writel(dwc->regs, + DWC3_GTXFIFOSIZ(num), 0); + dep->fifo_depth = 0; + } else { + dep->fifo_depth = dwc->last_fifo_depth; + } + + dev_dbg(dwc->dev, "%s(): %s fifo_depth:%x\n", + __func__, dep->name, dep->fifo_depth); + dbg_event(0xFF, "fifo_reset", dep->number); + } + } + + ret = dwc3_ep0_delegate_req(dwc, ctrl); + /* if the cfg matches and the cfg is non zero */ + if (cfg && (!ret || (ret == USB_GADGET_DELAYED_STATUS))) { + + /* + * only change state if set_config has already + * been processed. If gadget driver returns + * USB_GADGET_DELAYED_STATUS, we will wait + * to change the state on the next usb_ep_queue() + */ + if (ret == 0) + usb_gadget_set_state(&dwc->gadget, + USB_STATE_CONFIGURED); + + if (!dwc->usb3_u1u2_disable || enable_dwc3_u1u2) { + /* + * Enable transition to U1/U2 state when + * nothing is pending from application. + */ + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= (DWC3_DCTL_ACCEPTU1ENA | + DWC3_DCTL_ACCEPTU2ENA); + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + } + break; + + case USB_STATE_CONFIGURED: + ret = dwc3_ep0_delegate_req(dwc, ctrl); + if (!cfg && !ret) + usb_gadget_set_state(&dwc->gadget, + USB_STATE_ADDRESS); + break; + default: + ret = -EINVAL; + } + return ret; +} + +static void dwc3_ep0_set_sel_cmpl(struct usb_ep *ep, struct usb_request *req) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + u32 param = 0; + u32 reg; + + struct timing { + u8 u1sel; + u8 u1pel; + u16 u2sel; + u16 u2pel; + } __packed timing; + + int ret; + + memcpy(&timing, req->buf, sizeof(timing)); + + dwc->u1sel = timing.u1sel; + dwc->u1pel = timing.u1pel; + dwc->u2sel = le16_to_cpu(timing.u2sel); + dwc->u2pel = le16_to_cpu(timing.u2pel); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (reg & DWC3_DCTL_INITU2ENA) + param = dwc->u2pel; + if (reg & DWC3_DCTL_INITU1ENA) + param = dwc->u1pel; + + /* + * According to Synopsys Databook, if parameter is + * greater than 125, a value of zero should be + * programmed in the register. + */ + if (param > 125) + param = 0; + + /* now that we have the time, issue DGCMD Set Sel */ + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_SET_PERIODIC_PAR, param); + if (WARN_ON_ONCE(ret < 0)) + dbg_event(dep->number, "ESET_SELCMPL", ret); +} + +static int dwc3_ep0_set_sel(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + struct dwc3_ep *dep; + enum usb_device_state state = dwc->gadget.state; + u16 wLength; + u16 wValue; + + if (state == USB_STATE_DEFAULT) + return -EINVAL; + + wValue = le16_to_cpu(ctrl->wValue); + wLength = le16_to_cpu(ctrl->wLength); + + if (wLength != 6) { + dev_err(dwc->dev, "Set SEL should be 6 bytes, got %d\n", + wLength); + return -EINVAL; + } + + /* + * To handle Set SEL we need to receive 6 bytes from Host. So let's + * queue a usb_request for 6 bytes. + * + * Remember, though, this controller can't handle non-wMaxPacketSize + * aligned transfers on the OUT direction, so we queue a request for + * wMaxPacketSize instead. + */ + dep = dwc->eps[0]; + dwc->ep0_usb_req.dep = dep; + dwc->ep0_usb_req.request.length = dep->endpoint.maxpacket; + dwc->ep0_usb_req.request.buf = dwc->setup_buf; + dwc->ep0_usb_req.request.complete = dwc3_ep0_set_sel_cmpl; + dwc->ep0_usb_req.request.dma = DMA_ERROR_CODE; + + return __dwc3_gadget_ep0_queue(dep, &dwc->ep0_usb_req); +} + +static int dwc3_ep0_set_isoch_delay(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + u16 wLength; + u16 wValue; + u16 wIndex; + + wValue = le16_to_cpu(ctrl->wValue); + wLength = le16_to_cpu(ctrl->wLength); + wIndex = le16_to_cpu(ctrl->wIndex); + + if (wIndex || wLength) + return -EINVAL; + + /* + * REVISIT It's unclear from Databook what to do with this + * value. For now, just cache it. + */ + dwc->isoch_delay = wValue; + + return 0; +} + +static int dwc3_ep0_std_request(struct dwc3 *dwc, struct usb_ctrlrequest *ctrl) +{ + int ret; + + switch (ctrl->bRequest) { + case USB_REQ_GET_STATUS: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_GET_STATUS"); + ret = dwc3_ep0_handle_status(dwc, ctrl); + break; + case USB_REQ_CLEAR_FEATURE: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_CLEAR_FEATURE"); + ret = dwc3_ep0_handle_feature(dwc, ctrl, 0); + break; + case USB_REQ_SET_FEATURE: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_FEATURE"); + ret = dwc3_ep0_handle_feature(dwc, ctrl, 1); + break; + case USB_REQ_SET_ADDRESS: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_ADDRESS"); + ret = dwc3_ep0_set_address(dwc, ctrl); + break; + case USB_REQ_SET_CONFIGURATION: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_CONFIGURATION"); + ret = dwc3_ep0_set_config(dwc, ctrl); + break; + case USB_REQ_SET_SEL: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_SEL"); + ret = dwc3_ep0_set_sel(dwc, ctrl); + break; + case USB_REQ_SET_ISOCH_DELAY: + dwc3_trace(trace_dwc3_ep0, "USB_REQ_SET_ISOCH_DELAY"); + ret = dwc3_ep0_set_isoch_delay(dwc, ctrl); + break; + default: + dwc3_trace(trace_dwc3_ep0, "Forwarding to gadget driver"); + ret = dwc3_ep0_delegate_req(dwc, ctrl); + break; + } + + return ret; +} + +static void dwc3_ep0_inspect_setup(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct usb_ctrlrequest *ctrl = dwc->ctrl_req; + int ret = -EINVAL; + u32 len; + + if (!dwc->gadget_driver) + goto out; + + trace_dwc3_ctrl_req(ctrl); + + len = le16_to_cpu(ctrl->wLength); + if (!len) { + dwc->three_stage_setup = false; + dwc->ep0_expect_in = false; + dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS; + } else { + dwc->three_stage_setup = true; + dwc->ep0_expect_in = !!(ctrl->bRequestType & USB_DIR_IN); + dwc->ep0_next_event = DWC3_EP0_NRDY_DATA; + } + + dbg_setup(0x00, ctrl); + if ((ctrl->bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD) + ret = dwc3_ep0_std_request(dwc, ctrl); + else + ret = dwc3_ep0_delegate_req(dwc, ctrl); + + if (ret == USB_GADGET_DELAYED_STATUS) + dwc->delayed_status = true; + +out: + if (ret < 0) { + dbg_event(0x0, "ERRSTAL", ret); + dwc3_ep0_stall_and_restart(dwc); + } +} + +static void dwc3_ep0_complete_data(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_request *r = NULL; + struct usb_request *ur; + struct dwc3_trb *trb; + struct dwc3_ep *ep0; + unsigned transfer_size = 0; + unsigned maxp; + unsigned remaining_ur_length; + void *buf; + u32 transferred = 0; + u32 status; + u32 length; + u8 epnum; + + epnum = event->endpoint_number; + ep0 = dwc->eps[0]; + + dwc->ep0_next_event = DWC3_EP0_NRDY_STATUS; + + trb = dwc->ep0_trb; + + trace_dwc3_complete_trb(ep0, trb); + + r = next_request(&ep0->request_list); + if (!r) + return; + + status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (status == DWC3_TRBSTS_SETUP_PENDING) { + dwc3_trace(trace_dwc3_ep0, "Setup Pending received"); + + if (r) + dwc3_gadget_giveback(ep0, r, -ECONNRESET); + + return; + } + + ur = &r->request; + buf = ur->buf; + remaining_ur_length = ur->length; + + length = trb->size & DWC3_TRB_SIZE_MASK; + + maxp = ep0->endpoint.maxpacket; + + if (dwc->ep0_bounced) { + /* + * Handle the first TRB before handling the bounce buffer if + * the request length is greater than the bounce buffer size + */ + if (ur->length > DWC3_EP0_BOUNCE_SIZE) { + transfer_size = ALIGN(ur->length - maxp, maxp); + transferred = transfer_size - length; + buf = (u8 *)buf + transferred; + ur->actual += transferred; + remaining_ur_length -= transferred; + + trb++; + length = trb->size & DWC3_TRB_SIZE_MASK; + + ep0->free_slot = 0; + } + + transfer_size = roundup((ur->length - transfer_size), + maxp); + + transferred = min_t(u32, remaining_ur_length, + transfer_size - length); + memcpy(buf, dwc->ep0_bounce, transferred); + } else { + transferred = ur->length - length; + } + + ur->actual += transferred; + + if ((epnum & 1) && ur->actual < ur->length) { + /* for some reason we did not get everything out */ + dbg_event(epnum, "INDATSTAL", 0); + dwc3_ep0_stall_and_restart(dwc); + } else { + dwc3_gadget_giveback(ep0, r, 0); + + if (IS_ALIGNED(ur->length, ep0->endpoint.maxpacket) && + ur->length && ur->zero) { + int ret; + + dwc->ep0_next_event = DWC3_EP0_COMPLETE; + + dwc3_ep0_prepare_one_trb(dwc, epnum, dwc->ctrl_req_addr, + 0, DWC3_TRBCTL_CONTROL_DATA, false); + ret = dwc3_ep0_start_trans(dwc, epnum); + if (WARN_ON_ONCE(ret < 0)) + dbg_event(epnum, "ECTRL_DATA", ret); + } + } +} + +static void dwc3_ep0_complete_status(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_request *r; + struct dwc3_ep *dep; + struct dwc3_trb *trb; + u32 status; + + dep = dwc->eps[0]; + trb = dwc->ep0_trb; + + trace_dwc3_complete_trb(dep, trb); + + if (!list_empty(&dep->request_list)) { + r = next_request(&dep->request_list); + + dwc3_gadget_giveback(dep, r, 0); + } + + if (dwc->test_mode) { + int ret; + + ret = dwc3_gadget_set_test_mode(dwc, dwc->test_mode_nr); + if (ret < 0) { + dwc3_trace(trace_dwc3_ep0, "Invalid Test #%d", + dwc->test_mode_nr); + dbg_event(0x00, "INVALTEST", ret); + dwc3_ep0_stall_and_restart(dwc); + return; + } + } + + status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (status == DWC3_TRBSTS_SETUP_PENDING) + dwc3_trace(trace_dwc3_ep0, "Setup Pending received"); + + dbg_print(dep->number, "DONE", status, "STATUS"); + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); +} + +static void dwc3_ep0_xfer_complete(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep = dwc->eps[event->endpoint_number]; + + dep->flags &= ~DWC3_EP_BUSY; + dep->resource_index = 0; + dwc->setup_packet_pending = false; + + switch (dwc->ep0state) { + case EP0_SETUP_PHASE: + dwc3_trace(trace_dwc3_ep0, "Setup Phase"); + dwc3_ep0_inspect_setup(dwc, event); + break; + + case EP0_DATA_PHASE: + dwc3_trace(trace_dwc3_ep0, "Data Phase"); + dwc3_ep0_complete_data(dwc, event); + break; + + case EP0_STATUS_PHASE: + dwc3_trace(trace_dwc3_ep0, "Status Phase"); + dwc3_ep0_complete_status(dwc, event); + break; + default: + WARN(true, "UNKNOWN ep0state %d\n", dwc->ep0state); + } +} + +static void __dwc3_ep0_do_control_data(struct dwc3 *dwc, + struct dwc3_ep *dep, struct dwc3_request *req) +{ + int ret; + + req->direction = !!dep->number; + + if (req->request.length == 0) { + dwc3_ep0_prepare_one_trb(dwc, dep->number, + dwc->ctrl_req_addr, 0, + DWC3_TRBCTL_CONTROL_DATA, false); + ret = dwc3_ep0_start_trans(dwc, dep->number); + } else if (!IS_ALIGNED(req->request.length, dep->endpoint.maxpacket) + && (dep->number == 0)) { + u32 transfer_size = 0; + u32 maxpacket; + + ret = usb_gadget_map_request(&dwc->gadget, &req->request, + dep->number); + if (ret) { + dev_dbg(dwc->dev, "failed to map request\n"); + return; + } + + maxpacket = dep->endpoint.maxpacket; + + if (req->request.length > DWC3_EP0_BOUNCE_SIZE) { + transfer_size = ALIGN(req->request.length - maxpacket, + maxpacket); + dwc3_ep0_prepare_one_trb(dwc, dep->number, + req->request.dma, + transfer_size, + DWC3_TRBCTL_CONTROL_DATA, + true); + } + + transfer_size = roundup((req->request.length - transfer_size), + maxpacket); + + dwc->ep0_bounced = true; + + dwc3_ep0_prepare_one_trb(dwc, dep->number, + dwc->ep0_bounce_addr, transfer_size, + DWC3_TRBCTL_CONTROL_DATA, false); + ret = dwc3_ep0_start_trans(dwc, dep->number); + } else { + ret = usb_gadget_map_request(&dwc->gadget, &req->request, + dep->number); + if (ret) { + dev_dbg(dwc->dev, "failed to map request\n"); + return; + } + + dwc3_ep0_prepare_one_trb(dwc, dep->number, req->request.dma, + req->request.length, DWC3_TRBCTL_CONTROL_DATA, + false); + ret = dwc3_ep0_start_trans(dwc, dep->number); + } + + dbg_queue(dep->number, &req->request, ret); +} + +static int dwc3_ep0_start_control_status(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 type; + + type = dwc->three_stage_setup ? DWC3_TRBCTL_CONTROL_STATUS3 + : DWC3_TRBCTL_CONTROL_STATUS2; + + dwc3_ep0_prepare_one_trb(dwc, dep->number, + dwc->ctrl_req_addr, 0, type, false); + return dwc3_ep0_start_trans(dwc, dep->number); +} + +static void __dwc3_ep0_do_control_status(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + int ret; + + ret = dwc3_ep0_start_control_status(dep); + if (WARN_ON_ONCE(ret)) + dbg_event(dep->number, "ECTRLSTATUS", ret); +} + +static void dwc3_ep0_do_control_status(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep = dwc->eps[event->endpoint_number]; + + __dwc3_ep0_do_control_status(dwc, dep); +} + +void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret; + + /* + * For status/DATA OUT stage, TRB will be queued on ep0 out + * endpoint for which resource index is zero. Hence allow + * queuing ENDXFER command for ep0 out endpoint. + */ + if (!dep->resource_index && dep->number) + return; + + cmd = DWC3_DEPCMD_ENDTRANSFER; + cmd |= DWC3_DEPCMD_CMDIOC; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + if (ret) { + dev_dbg(dwc->dev, "%s: send ep cmd ENDTRANSFER failed", + dep->name); + dbg_event(dep->number, "EENDXFER", ret); + } + dep->resource_index = 0; +} + +static void dwc3_ep0_xfernotready(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + u8 epnum; + struct dwc3_ep *dep; + + dwc->setup_packet_pending = true; + epnum = event->endpoint_number; + dep = dwc->eps[epnum]; + + switch (event->status) { + case DEPEVT_STATUS_CONTROL_DATA: + dwc3_trace(trace_dwc3_ep0, "Control Data"); + dep->dbg_ep_events.control_data++; + + /* + * We already have a DATA transfer in the controller's cache, + * if we receive a XferNotReady(DATA) we will ignore it, unless + * it's for the wrong direction. + * + * In that case, we must issue END_TRANSFER command to the Data + * Phase we already have started and issue SetStall on the + * control endpoint. + */ + if (dwc->ep0_expect_in != event->endpoint_number) { + struct dwc3_ep *dep = dwc->eps[dwc->ep0_expect_in]; + + dwc3_trace(trace_dwc3_ep0, + "Wrong direction for Data phase"); + dwc3_ep0_end_control_data(dwc, dep); + dbg_event(epnum, "WRONGDR", 0); + dwc3_ep0_stall_and_restart(dwc); + return; + } + + break; + + case DEPEVT_STATUS_CONTROL_STATUS: + dep->dbg_ep_events.control_status++; + if (dwc->ep0_next_event != DWC3_EP0_NRDY_STATUS) + return; + + dwc3_trace(trace_dwc3_ep0, "Control Status"); + + dwc->ep0state = EP0_STATUS_PHASE; + + if (dwc->delayed_status) { + if (event->endpoint_number != 1) + dbg_event(epnum, "EEPNUM", event->status); + dwc3_trace(trace_dwc3_ep0, "Delayed Status"); + return; + } + + dwc3_ep0_do_control_status(dwc, event); + } +} + +void dwc3_ep0_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + u8 epnum = event->endpoint_number; + struct dwc3_ep *dep; + + dwc3_trace(trace_dwc3_ep0, "%s while ep%d%s in state '%s'", + dwc3_ep_event_string(event->endpoint_event), + epnum >> 1, (epnum & 1) ? "in" : "out", + dwc3_ep0_state_string(dwc->ep0state)); + + dep = dwc->eps[epnum]; + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERCOMPLETE: + dwc3_ep0_xfer_complete(dwc, event); + dep->dbg_ep_events.xfercomplete++; + break; + + case DWC3_DEPEVT_XFERNOTREADY: + dwc3_ep0_xfernotready(dwc, event); + dep->dbg_ep_events.xfernotready++; + break; + + case DWC3_DEPEVT_XFERINPROGRESS: + dep->dbg_ep_events.xferinprogress++; + break; + case DWC3_DEPEVT_RXTXFIFOEVT: + dep->dbg_ep_events.rxtxfifoevent++; + break; + case DWC3_DEPEVT_STREAMEVT: + dep->dbg_ep_events.streamevent++; + break; + case DWC3_DEPEVT_EPCMDCMPLT: + dep->dbg_ep_events.epcmdcomplete++; + break; + } +} diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c new file mode 100644 index 000000000000..608352c70240 --- /dev/null +++ b/drivers/usb/dwc3/gadget.c @@ -0,0 +1,3763 @@ +/** + * gadget.c - DesignWare USB3 DRD Controller Gadget Framework Link + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/ratelimit.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/list.h> +#include <linux/dma-mapping.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/composite.h> +#include <linux/usb/gadget.h> + +#include "debug.h" +#include "core.h" +#include "gadget.h" +#include "debug.h" +#include "io.h" + +static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup); +static int dwc3_gadget_wakeup_int(struct dwc3 *dwc); + +/** + * dwc3_gadget_set_test_mode - Enables USB2 Test Modes + * @dwc: pointer to our context structure + * @mode: the mode to set (J, K SE0 NAK, Force Enable) + * + * Caller should take care of locking. This function will + * return 0 on success or -EINVAL if wrong Test Selector + * is passed + */ +int dwc3_gadget_set_test_mode(struct dwc3 *dwc, int mode) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_TSTCTRL_MASK; + + switch (mode) { + case TEST_J: + case TEST_K: + case TEST_SE0_NAK: + case TEST_PACKET: + case TEST_FORCE_EN: + reg |= mode << 1; + break; + default: + return -EINVAL; + } + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + return 0; +} + +/** + * dwc3_gadget_get_link_state - Gets current state of USB Link + * @dwc: pointer to our context structure + * + * Caller should take care of locking. This function will + * return the link state on success (>= 0) or -ETIMEDOUT. + */ +int dwc3_gadget_get_link_state(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + return DWC3_DSTS_USBLNKST(reg); +} + +/** + * dwc3_gadget_set_link_state - Sets USB Link to a particular State + * @dwc: pointer to our context structure + * @state: the state to put link into + * + * Caller should take care of locking. This function will + * return 0 on success or -ETIMEDOUT. + */ +int dwc3_gadget_set_link_state(struct dwc3 *dwc, enum dwc3_link_state state) +{ + int retries = 10000; + u32 reg; + + /* + * Wait until device controller is ready. Only applies to 1.94a and + * later RTL. + */ + if (dwc->revision >= DWC3_REVISION_194A) { + while (--retries) { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + if (reg & DWC3_DSTS_DCNRD) + udelay(5); + else + break; + } + + if (retries <= 0) + return -ETIMEDOUT; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + + /* set requested state */ + reg |= DWC3_DCTL_ULSTCHNGREQ(state); + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + /* + * The following code is racy when called from dwc3_gadget_wakeup, + * and is not needed, at least on newer versions + */ + if (dwc->revision >= DWC3_REVISION_194A) + return 0; + + /* wait for a change in DSTS */ + retries = 10000; + while (--retries) { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + + if (DWC3_DSTS_USBLNKST(reg) == state) + return 0; + + udelay(5); + } + + dwc3_trace(trace_dwc3_gadget, + "link state change request timed out"); + + return -ETIMEDOUT; +} + +/** + * dwc3_gadget_resize_tx_fifos - reallocate fifo spaces for current use-case + * @dwc: pointer to our context structure + * + * This function will a best effort FIFO allocation in order + * to improve FIFO usage and throughput, while still allowing + * us to enable as many endpoints as possible. + * + * Keep in mind that this operation will be highly dependent + * on the configured size for RAM1 - which contains TxFifo -, + * the amount of endpoints enabled on coreConsultant tool, and + * the width of the Master Bus. + * + * In the ideal world, we would always be able to satisfy the + * following equation: + * + * ((512 + 2 * MDWIDTH-Bytes) + (Number of IN Endpoints - 1) * \ + * (3 * (1024 + MDWIDTH-Bytes) + MDWIDTH-Bytes)) / MDWIDTH-Bytes + * + * Unfortunately, due to many variables that's not always the case. + */ +int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + int fifo_size, mdwidth, max_packet = 1024; + int tmp, mult = 1; + + if (!dwc->needs_fifo_resize) + return 0; + + /* resize IN endpoints excepts ep0 */ + if (!usb_endpoint_dir_in(dep->endpoint.desc) || + dep->endpoint.ep_num == 0) + return 0; + + /* Don't resize already resized IN endpoint */ + if (dep->fifo_depth) { + dev_dbg(dwc->dev, "%s fifo_depth:%d is already set\n", + dep->endpoint.name, dep->fifo_depth); + return 0; + } + + mdwidth = DWC3_MDWIDTH(dwc->hwparams.hwparams0); + /* MDWIDTH is represented in bits, we need it in bytes */ + mdwidth >>= 3; + + if (dep->endpoint.ep_type == EP_TYPE_GSI || dep->endpoint.endless) + mult = 3; + + if (((dep->endpoint.maxburst > 1) && + usb_endpoint_xfer_bulk(dep->endpoint.desc)) + || usb_endpoint_xfer_isoc(dep->endpoint.desc)) + mult = 3; + + tmp = ((max_packet + mdwidth) * mult) + mdwidth; + fifo_size = DIV_ROUND_UP(tmp, mdwidth); + dep->fifo_depth = fifo_size; + fifo_size |= (dwc3_readl(dwc->regs, DWC3_GTXFIFOSIZ(0)) & 0xffff0000) + + (dwc->last_fifo_depth << 16); + dwc->last_fifo_depth += (fifo_size & 0xffff); + + dev_dbg(dwc->dev, "%s ep_num:%d last_fifo_depth:%04x fifo_depth:%d\n", + dep->endpoint.name, dep->endpoint.ep_num, dwc->last_fifo_depth, + dep->fifo_depth); + + dbg_event(0xFF, "resize_fifo", dep->number); + dbg_event(0xFF, "fifo_depth", dep->fifo_depth); + /* Check fifo size allocation doesn't exceed available RAM size. */ + if (dwc->tx_fifo_size && + ((dwc->last_fifo_depth * mdwidth) >= dwc->tx_fifo_size)) { + dev_err(dwc->dev, "Fifosize(%d) > RAM size(%d) %s depth:%d\n", + (dwc->last_fifo_depth * mdwidth), dwc->tx_fifo_size, + dep->endpoint.name, fifo_size); + dwc->last_fifo_depth -= (fifo_size & 0xffff); + dep->fifo_depth = 0; + WARN_ON(1); + return -ENOMEM; + } + + dwc3_writel(dwc->regs, DWC3_GTXFIFOSIZ(dep->endpoint.ep_num), + fifo_size); + return 0; +} + +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status) +{ + struct dwc3 *dwc = dep->dwc; + unsigned int unmap_after_complete = false; + int i; + + if (req->queued) { + i = 0; + do { + dep->busy_slot++; + /* + * Skip LINK TRB. We can't use req->trb and check for + * DWC3_TRBCTL_LINK_TRB because it points the TRB we + * just completed (not the LINK TRB). + */ + if (((dep->busy_slot & DWC3_TRB_MASK) == + DWC3_TRB_NUM- 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + dep->busy_slot++; + } while(++i < req->request.num_mapped_sgs); + req->queued = false; + } + list_del(&req->list); + req->trb = NULL; + + if (req->request.status == -EINPROGRESS) + req->request.status = status; + + /* + * NOTICE we don't want to unmap before calling ->complete() if we're + * dealing with a bounced ep0 request. If we unmap it here, we would end + * up overwritting the contents of req->buf and this could confuse the + * gadget driver. + */ + if (dwc->ep0_bounced && dep->number <= 1) { + dwc->ep0_bounced = false; + unmap_after_complete = true; + } else { + usb_gadget_unmap_request(&dwc->gadget, + &req->request, req->direction); + } + + dev_dbg(dwc->dev, "request %pK from %s completed %d/%d ===> %d\n", + req, dep->name, req->request.actual, + req->request.length, status); + trace_dwc3_gadget_giveback(req); + + dbg_done(dep->number, req->request.actual, req->request.status); + spin_unlock(&dwc->lock); + usb_gadget_giveback_request(&dep->endpoint, &req->request); + spin_lock(&dwc->lock); + + if (unmap_after_complete) + usb_gadget_unmap_request(&dwc->gadget, + &req->request, req->direction); +} + +int dwc3_send_gadget_generic_command(struct dwc3 *dwc, unsigned cmd, u32 param) +{ + u32 timeout = 500; + u32 reg; + + trace_dwc3_gadget_generic_cmd(cmd, param); + + dwc3_writel(dwc->regs, DWC3_DGCMDPAR, param); + dwc3_writel(dwc->regs, DWC3_DGCMD, cmd | DWC3_DGCMD_CMDACT); + + do { + reg = dwc3_readl(dwc->regs, DWC3_DGCMD); + if (!(reg & DWC3_DGCMD_CMDACT)) { + dwc3_trace(trace_dwc3_gadget, + "Command Complete --> %d", + DWC3_DGCMD_STATUS(reg)); + if (DWC3_DGCMD_STATUS(reg)) + return -EINVAL; + return 0; + } + + /* + * We can't sleep here, because it's also called from + * interrupt context. + */ + timeout--; + if (!timeout) { + dwc3_trace(trace_dwc3_gadget, + "Command Timed Out"); + return -ETIMEDOUT; + } + udelay(1); + } while (1); +} + +int dwc3_send_gadget_ep_cmd(struct dwc3 *dwc, unsigned ep, + unsigned cmd, struct dwc3_gadget_ep_cmd_params *params) +{ + struct dwc3_ep *dep = dwc->eps[ep]; + u32 timeout = 3000; + u32 reg; + + trace_dwc3_gadget_ep_cmd(dep, cmd, params); + + dwc3_writel(dwc->regs, DWC3_DEPCMDPAR0(ep), params->param0); + dwc3_writel(dwc->regs, DWC3_DEPCMDPAR1(ep), params->param1); + dwc3_writel(dwc->regs, DWC3_DEPCMDPAR2(ep), params->param2); + + dwc3_writel(dwc->regs, DWC3_DEPCMD(ep), cmd | DWC3_DEPCMD_CMDACT); + do { + reg = dwc3_readl(dwc->regs, DWC3_DEPCMD(ep)); + if (!(reg & DWC3_DEPCMD_CMDACT)) { + dwc3_trace(trace_dwc3_gadget, + "Command Complete --> %d", + DWC3_DEPCMD_STATUS(reg)); + + /* SW issues START TRANSFER command to isochronous ep + * with future frame interval. If future interval time + * has already passed when core recieves command, core + * will respond with an error(bit13 in Command complete + * event. Hence return error in this case. + */ + if (reg & 0x2000) + return -EAGAIN; + else if (DWC3_DEPCMD_STATUS(reg)) + return -EINVAL; + return 0; + } + + /* + * We can't sleep here, because it is also called from + * interrupt context. + */ + timeout--; + if (!timeout) { + dwc3_trace(trace_dwc3_gadget, + "Command Timed Out"); + dev_err(dwc->dev, "%s command timeout for %s\n", + dwc3_gadget_ep_cmd_string(cmd), dep->name); + if (!(cmd & DWC3_DEPCMD_ENDTRANSFER)) { + dwc->ep_cmd_timeout_cnt++; + dwc3_notify_event(dwc, + DWC3_CONTROLLER_RESTART_USB_SESSION, 0); + } + return -ETIMEDOUT; + } + if ((cmd & DWC3_DEPCMD_SETTRANSFRESOURCE)) + udelay(20); + else + udelay(1); + } while (1); +} + +static int dwc3_alloc_trb_pool(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 num_trbs = DWC3_TRB_NUM; + + if (dep->trb_pool) + return 0; + + dep->trb_pool = dma_zalloc_coherent(dwc->dev, + sizeof(struct dwc3_trb) * num_trbs, + &dep->trb_pool_dma, GFP_KERNEL); + if (!dep->trb_pool) { + dev_err(dep->dwc->dev, "failed to allocate trb pool for %s\n", + dep->name); + return -ENOMEM; + } + dep->num_trbs = num_trbs; + + return 0; +} + +static void dwc3_free_trb_pool(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + + /* Freeing of GSI EP TRBs are handled by GSI EP ops. */ + if (dep->endpoint.ep_type == EP_TYPE_GSI) + return; + + /* + * Clean up ep ring to avoid getting xferInProgress due to stale trbs + * with HWO bit set from previous composition when update transfer cmd + * is issued. + */ + if (dep->number > 1 && dep->trb_pool && dep->trb_pool_dma) { + memset(&dep->trb_pool[0], 0, + sizeof(struct dwc3_trb) * dep->num_trbs); + dbg_event(dep->number, "Clr_TRB", 0); + + dma_free_coherent(dwc->dev, + sizeof(struct dwc3_trb) * DWC3_TRB_NUM, dep->trb_pool, + dep->trb_pool_dma); + + dep->trb_pool = NULL; + dep->trb_pool_dma = 0; + } +} + +static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep); + +/** + * dwc3_gadget_start_config - Configure EP resources + * @dwc: pointer to our controller context structure + * @dep: endpoint that is being enabled + * + * The assignment of transfer resources cannot perfectly follow the + * data book due to the fact that the controller driver does not have + * all knowledge of the configuration in advance. It is given this + * information piecemeal by the composite gadget framework after every + * SET_CONFIGURATION and SET_INTERFACE. Trying to follow the databook + * programming model in this scenario can cause errors. For two + * reasons: + * + * 1) The databook says to do DEPSTARTCFG for every SET_CONFIGURATION + * and SET_INTERFACE (8.1.5). This is incorrect in the scenario of + * multiple interfaces. + * + * 2) The databook does not mention doing more DEPXFERCFG for new + * endpoint on alt setting (8.1.6). + * + * The following simplified method is used instead: + * + * All hardware endpoints can be assigned a transfer resource and this + * setting will stay persistent until either a core reset or + * hibernation. So whenever we do a DEPSTARTCFG(0) we can go ahead and + * do DEPXFERCFG for every hardware endpoint as well. We are + * guaranteed that there are as many transfer resources as endpoints. + * + * This function is called for each endpoint when it is being enabled + * but is triggered only when called for EP0-out, which always happens + * first, and which should only happen in one of the above conditions. + */ +static int dwc3_gadget_start_config(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int i; + int ret; + + if (dep->number) + return 0; + + memset(¶ms, 0x00, sizeof(params)); + cmd = DWC3_DEPCMD_DEPSTARTCFG; + + ret = dwc3_send_gadget_ep_cmd(dwc, 0, cmd, ¶ms); + if (ret) + return ret; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + struct dwc3_ep *dep = dwc->eps[i]; + + if (!dep) + continue; + + ret = dwc3_gadget_set_xfer_resource(dwc, dep); + if (ret) + return ret; + } + + return 0; +} + +static int dwc3_gadget_set_ep_config(struct dwc3 *dwc, struct dwc3_ep *dep, + const struct usb_endpoint_descriptor *desc, + const struct usb_ss_ep_comp_descriptor *comp_desc, + bool ignore, bool restore) +{ + struct dwc3_gadget_ep_cmd_params params; + + memset(¶ms, 0x00, sizeof(params)); + + params.param0 = DWC3_DEPCFG_EP_TYPE(usb_endpoint_type(desc)) + | DWC3_DEPCFG_MAX_PACKET_SIZE(usb_endpoint_maxp(desc)); + + /* Burst size is only needed in SuperSpeed mode */ + if (dwc->gadget.speed == USB_SPEED_SUPER) { + u32 burst = dep->endpoint.maxburst - 1; + + params.param0 |= DWC3_DEPCFG_BURST_SIZE(burst); + } + + if (ignore) + params.param0 |= DWC3_DEPCFG_IGN_SEQ_NUM; + + if (restore) { + params.param0 |= DWC3_DEPCFG_ACTION_RESTORE; + params.param2 |= dep->saved_state; + } + + if (!dep->endpoint.endless) { + pr_debug("%s(): enable xfer_complete_int for %s\n", + __func__, dep->endpoint.name); + params.param1 = DWC3_DEPCFG_XFER_COMPLETE_EN + | DWC3_DEPCFG_XFER_NOT_READY_EN; + } else { + pr_debug("%s(): disable xfer_complete_int for %s\n", + __func__, dep->endpoint.name); + } + + if (usb_ss_max_streams(comp_desc) && usb_endpoint_xfer_bulk(desc)) { + params.param1 |= DWC3_DEPCFG_STREAM_CAPABLE + | DWC3_DEPCFG_STREAM_EVENT_EN; + dep->stream_capable = true; + } + + if (usb_endpoint_xfer_isoc(desc)) + params.param1 |= DWC3_DEPCFG_XFER_IN_PROGRESS_EN; + + /* + * We are doing 1:1 mapping for endpoints, meaning + * Physical Endpoints 2 maps to Logical Endpoint 2 and + * so on. We consider the direction bit as part of the physical + * endpoint number. So USB endpoint 0x81 is 0x03. + */ + params.param1 |= DWC3_DEPCFG_EP_NUMBER(dep->number); + + /* + * We must use the lower 16 TX FIFOs even though + * HW might have more + */ + if (dep->direction) + params.param0 |= DWC3_DEPCFG_FIFO_NUMBER(dep->number >> 1); + + if (desc->bInterval) { + params.param1 |= DWC3_DEPCFG_BINTERVAL_M1(desc->bInterval - 1); + dep->interval = 1 << (desc->bInterval - 1); + } + + return dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETEPCONFIG, ¶ms); +} + +static int dwc3_gadget_set_xfer_resource(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_gadget_ep_cmd_params params; + + memset(¶ms, 0x00, sizeof(params)); + + params.param0 = DWC3_DEPXFERCFG_NUM_XFER_RES(1); + + return dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETTRANSFRESOURCE, ¶ms); +} + +/** + * __dwc3_gadget_ep_enable - Initializes a HW endpoint + * @dep: endpoint to be initialized + * @desc: USB Endpoint Descriptor + * + * Caller should take care of locking + */ +static int __dwc3_gadget_ep_enable(struct dwc3_ep *dep, + const struct usb_endpoint_descriptor *desc, + const struct usb_ss_ep_comp_descriptor *comp_desc, + bool ignore, bool restore) +{ + struct dwc3 *dwc = dep->dwc; + u32 reg; + int ret; + + dwc3_trace(trace_dwc3_gadget, "Enabling %s", dep->name); + + if (!(dep->flags & DWC3_EP_ENABLED)) { + dep->endpoint.desc = desc; + dep->comp_desc = comp_desc; + dep->type = usb_endpoint_type(desc); + ret = dwc3_gadget_resize_tx_fifos(dwc, dep); + if (ret) { + dep->endpoint.desc = NULL; + dep->comp_desc = NULL; + dep->type = 0; + return ret; + } + + ret = dwc3_gadget_start_config(dwc, dep); + if (ret) { + dev_err(dwc->dev, "start_config() failed for %s\n", + dep->name); + return ret; + } + } + + ret = dwc3_gadget_set_ep_config(dwc, dep, desc, comp_desc, ignore, + restore); + if (ret) { + dev_err(dwc->dev, "set_ep_config() failed for %s\n", dep->name); + return ret; + } + + if (!(dep->flags & DWC3_EP_ENABLED)) { + struct dwc3_trb *trb_st_hw; + struct dwc3_trb *trb_link; + + dep->flags |= DWC3_EP_ENABLED; + + reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); + reg |= DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + + if (!usb_endpoint_xfer_isoc(desc)) + return 0; + + /* Link TRB for ISOC. The HWO bit is never reset */ + trb_st_hw = &dep->trb_pool[0]; + + trb_link = &dep->trb_pool[DWC3_TRB_NUM - 1]; + memset(trb_link, 0, sizeof(*trb_link)); + + trb_link->bpl = lower_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); + trb_link->bph = upper_32_bits(dwc3_trb_dma_offset(dep, trb_st_hw)); + trb_link->ctrl |= DWC3_TRBCTL_LINK_TRB; + trb_link->ctrl |= DWC3_TRB_CTRL_HWO; + } + + switch (usb_endpoint_type(desc)) { + case USB_ENDPOINT_XFER_CONTROL: + strlcat(dep->name, "-control", sizeof(dep->name)); + break; + case USB_ENDPOINT_XFER_ISOC: + strlcat(dep->name, "-isoc", sizeof(dep->name)); + break; + case USB_ENDPOINT_XFER_BULK: + strlcat(dep->name, "-bulk", sizeof(dep->name)); + break; + case USB_ENDPOINT_XFER_INT: + strlcat(dep->name, "-int", sizeof(dep->name)); + break; + default: + dev_err(dwc->dev, "invalid endpoint transfer type\n"); + } + + return 0; +} + +static void dwc3_remove_requests(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_request *req; + + if (!list_empty(&dep->req_queued)) { + dwc3_stop_active_transfer(dwc, dep->number, true); + + /* - giveback all requests to gadget driver */ + while (!list_empty(&dep->req_queued)) { + req = next_request(&dep->req_queued); + + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } + } + + while (!list_empty(&dep->request_list)) { + req = next_request(&dep->request_list); + + dwc3_gadget_giveback(dep, req, -ESHUTDOWN); + } +} + +/** + * __dwc3_gadget_ep_disable - Disables a HW endpoint + * @dep: the endpoint to disable + * + * This function also removes requests which are currently processed ny the + * hardware and those which are not yet scheduled. + * Caller should take care of locking. + */ +static int __dwc3_gadget_ep_disable(struct dwc3_ep *dep) +{ + struct dwc3 *dwc = dep->dwc; + u32 reg; + + dwc3_trace(trace_dwc3_gadget, "Disabling %s", dep->name); + + if (dep->endpoint.ep_type == EP_TYPE_NORMAL) + dwc3_remove_requests(dwc, dep); + else if (dep->endpoint.ep_type == EP_TYPE_GSI) + dwc3_stop_active_transfer(dwc, dep->number, true); + + /* make sure HW endpoint isn't stalled */ + if (dep->flags & DWC3_EP_STALL) + __dwc3_gadget_ep_set_halt(dep, 0, false); + + reg = dwc3_readl(dwc->regs, DWC3_DALEPENA); + reg &= ~DWC3_DALEPENA_EP(dep->number); + dwc3_writel(dwc->regs, DWC3_DALEPENA, reg); + + dep->stream_capable = false; + dep->endpoint.desc = NULL; + dep->comp_desc = NULL; + dep->type = 0; + dep->flags = 0; + + /* Keep GSI ep names with "-gsi" suffix */ + if (!strnstr(dep->name, "gsi", 10)) { + snprintf(dep->name, sizeof(dep->name), "ep%d%s", + dep->number >> 1, + (dep->number & 1) ? "in" : "out"); + } + + return 0; +} + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_ep0_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + return -EINVAL; +} + +static int dwc3_gadget_ep0_disable(struct usb_ep *ep) +{ + return -EINVAL; +} + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct dwc3_ep *dep; + struct dwc3 *dwc; + unsigned long flags; + int ret; + + if (!ep || !desc || desc->bDescriptorType != USB_DT_ENDPOINT) { + pr_debug("dwc3: invalid parameters. ep=%pK, desc=%pK, DT=%d\n", + ep, desc, desc ? desc->bDescriptorType : 0); + return -EINVAL; + } + + if (!desc->wMaxPacketSize) { + pr_debug("dwc3: missing wMaxPacketSize\n"); + return -EINVAL; + } + + dep = to_dwc3_ep(ep); + dwc = dep->dwc; + + if (dep->flags & DWC3_EP_ENABLED) { + dev_WARN_ONCE(dwc->dev, true, "%s is already enabled\n", + dep->name); + return 0; + } + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_enable(dep, desc, ep->comp_desc, false, false); + dbg_event(dep->number, "ENABLE", ret); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_ep_disable(struct usb_ep *ep) +{ + struct dwc3_ep *dep; + struct dwc3 *dwc; + unsigned long flags; + int ret; + + if (!ep) { + pr_debug("dwc3: invalid parameters\n"); + return -EINVAL; + } + + dep = to_dwc3_ep(ep); + dwc = dep->dwc; + + if (!(dep->flags & DWC3_EP_ENABLED)) { + dev_dbg(dwc->dev, "%s is already disabled\n", dep->name); + dbg_event(dep->number, "ALRDY DISABLED", dep->flags); + return 0; + } + + spin_lock_irqsave(&dwc->lock, flags); + ret = __dwc3_gadget_ep_disable(dep); + dbg_event(dep->number, "DISABLE", ret); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static struct usb_request *dwc3_gadget_ep_alloc_request(struct usb_ep *ep, + gfp_t gfp_flags) +{ + struct dwc3_request *req; + struct dwc3_ep *dep = to_dwc3_ep(ep); + + req = kzalloc(sizeof(*req), gfp_flags); + if (!req) + return NULL; + + req->epnum = dep->number; + req->dep = dep; + req->request.dma = DMA_ERROR_CODE; + + trace_dwc3_alloc_request(req); + + return &req->request; +} + +static void dwc3_gadget_ep_free_request(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_request *req = to_dwc3_request(request); + + trace_dwc3_free_request(req); + kfree(req); +} + +/** + * dwc3_prepare_one_trb - setup one TRB from one request + * @dep: endpoint for which this request is prepared + * @req: dwc3_request pointer + */ +static void dwc3_prepare_one_trb(struct dwc3_ep *dep, + struct dwc3_request *req, dma_addr_t dma, + unsigned length, unsigned last, unsigned chain, unsigned node) +{ + struct dwc3_trb *trb; + + dwc3_trace(trace_dwc3_gadget, "%s: req %pK dma %08llx length %d%s%s", + dep->name, req, (unsigned long long) dma, + length, last ? " last" : "", + chain ? " chain" : ""); + + + trb = &dep->trb_pool[dep->free_slot & DWC3_TRB_MASK]; + + if (!req->trb) { + dwc3_gadget_move_request_queued(req); + req->trb = trb; + req->trb_dma = dwc3_trb_dma_offset(dep, trb); + req->start_slot = dep->free_slot & DWC3_TRB_MASK; + } + + dep->free_slot++; + /* Skip the LINK-TRB on ISOC */ + if (((dep->free_slot & DWC3_TRB_MASK) == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + dep->free_slot++; + + trb->size = DWC3_TRB_SIZE_LENGTH(length); + trb->bpl = lower_32_bits(dma); + trb->bph = upper_32_bits(dma); + + switch (usb_endpoint_type(dep->endpoint.desc)) { + case USB_ENDPOINT_XFER_CONTROL: + trb->ctrl = DWC3_TRBCTL_CONTROL_SETUP; + break; + + case USB_ENDPOINT_XFER_ISOC: + if (!node) + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS_FIRST; + else + trb->ctrl = DWC3_TRBCTL_ISOCHRONOUS; + + if (!req->request.no_interrupt && !chain) + trb->ctrl |= DWC3_TRB_CTRL_IOC; + break; + + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_INT: + trb->ctrl = DWC3_TRBCTL_NORMAL; + if (req->request.num_mapped_sgs > 0) { + if (!last && !chain && + !req->request.no_interrupt) + trb->ctrl |= DWC3_TRB_CTRL_IOC; + } + break; + default: + /* + * This is only possible with faulty memory because we + * checked it already :) + */ + BUG(); + } + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + trb->ctrl |= DWC3_TRB_CTRL_ISP_IMI; + trb->ctrl |= DWC3_TRB_CTRL_CSP; + } else if (last) { + trb->ctrl |= DWC3_TRB_CTRL_LST; + } + + if (chain) + trb->ctrl |= DWC3_TRB_CTRL_CHN; + + if (usb_endpoint_xfer_bulk(dep->endpoint.desc) && dep->stream_capable) + trb->ctrl |= DWC3_TRB_CTRL_SID_SOFN(req->request.stream_id); + + trb->ctrl |= DWC3_TRB_CTRL_HWO; + + trace_dwc3_prepare_trb(dep, trb); +} + +/* + * dwc3_prepare_trbs - setup TRBs from requests + * @dep: endpoint for which requests are being prepared + * @starting: true if the endpoint is idle and no requests are queued. + * + * The function goes through the requests list and sets up TRBs for the + * transfers. The function returns once there are no more TRBs available or + * it runs out of requests. + */ +static void dwc3_prepare_trbs(struct dwc3_ep *dep, bool starting) +{ + struct dwc3_request *req, *n; + u32 trbs_left; + u32 max; + unsigned int last_one = 0; + + BUILD_BUG_ON_NOT_POWER_OF_2(DWC3_TRB_NUM); + + /* the first request must not be queued */ + trbs_left = (dep->busy_slot - dep->free_slot) & DWC3_TRB_MASK; + + /* Can't wrap around on a non-isoc EP since there's no link TRB */ + if (!usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + max = DWC3_TRB_NUM - (dep->free_slot & DWC3_TRB_MASK); + if (trbs_left > max) + trbs_left = max; + } + + /* + * If busy & slot are equal than it is either full or empty. If we are + * starting to process requests then we are empty. Otherwise we are + * full and don't do anything + */ + if (!trbs_left) { + if (!starting) + return; + trbs_left = DWC3_TRB_NUM; + /* + * In case we start from scratch, we queue the ISOC requests + * starting from slot 1. This is done because we use ring + * buffer and have no LST bit to stop us. Instead, we place + * IOC bit every TRB_NUM/4. We try to avoid having an interrupt + * after the first request so we start at slot 1 and have + * 7 requests proceed before we hit the first IOC. + * Other transfer types don't use the ring buffer and are + * processed from the first TRB until the last one. Since we + * don't wrap around we have to start at the beginning. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dep->busy_slot = 1; + dep->free_slot = 1; + } else { + dep->busy_slot = 0; + dep->free_slot = 0; + } + } + + /* The last TRB is a link TRB, not used for xfer */ + if ((trbs_left <= 1) && usb_endpoint_xfer_isoc(dep->endpoint.desc)) + return; + + list_for_each_entry_safe(req, n, &dep->request_list, list) { + unsigned length; + dma_addr_t dma; + last_one = false; + + if (req->request.num_mapped_sgs > 0) { + struct usb_request *request = &req->request; + struct scatterlist *sg = request->sg; + struct scatterlist *s; + int i; + + for_each_sg(sg, s, request->num_mapped_sgs, i) { + unsigned chain = true; + + length = sg_dma_len(s); + dma = sg_dma_address(s); + + if (i == (request->num_mapped_sgs - 1) || + sg_is_last(s)) { + if (list_empty(&dep->request_list)) + last_one = true; + chain = false; + } + + trbs_left--; + if (!trbs_left) + last_one = true; + + if (last_one) + chain = false; + + dwc3_prepare_one_trb(dep, req, dma, length, + last_one, chain, i); + + if (last_one) + break; + } + dbg_queue(dep->number, &req->request, trbs_left); + + if (last_one) + break; + } else { + dma = req->request.dma; + length = req->request.length; + trbs_left--; + + if (!trbs_left) + last_one = 1; + + /* Is this the last request? */ + if (list_is_last(&req->list, &dep->request_list)) + last_one = 1; + + dwc3_prepare_one_trb(dep, req, dma, length, + last_one, false, 0); + + dbg_queue(dep->number, &req->request, 0); + if (last_one) + break; + } + } +} + +static int __dwc3_gadget_kick_transfer(struct dwc3_ep *dep, u16 cmd_param, + int start_new) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3_request *req, *req1, *n; + struct dwc3 *dwc = dep->dwc; + int ret; + u32 cmd; + + if (start_new && (dep->flags & DWC3_EP_BUSY)) { + dwc3_trace(trace_dwc3_gadget, "%s: endpoint busy", dep->name); + return -EBUSY; + } + + /* + * If we are getting here after a short-out-packet we don't enqueue any + * new requests as we try to set the IOC bit only on the last request. + */ + if (start_new) { + if (list_empty(&dep->req_queued)) + dwc3_prepare_trbs(dep, start_new); + + /* req points to the first request which will be sent */ + req = next_request(&dep->req_queued); + } else { + dwc3_prepare_trbs(dep, start_new); + + /* + * req points to the first request where HWO changed from 0 to 1 + */ + req = next_request(&dep->req_queued); + } + if (!req) { + dep->flags |= DWC3_EP_PENDING_REQUEST; + dbg_event(dep->number, "NO REQ", 0); + return 0; + } + + memset(¶ms, 0, sizeof(params)); + + if (start_new) { + params.param0 = upper_32_bits(req->trb_dma); + params.param1 = lower_32_bits(req->trb_dma); + cmd = DWC3_DEPCMD_STARTTRANSFER; + } else { + cmd = DWC3_DEPCMD_UPDATETRANSFER; + } + + cmd |= DWC3_DEPCMD_PARAM(cmd_param); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + if (ret < 0) { + dev_dbg(dwc->dev, "failed to send STARTTRANSFER command\n"); + + if ((ret == -EAGAIN) && start_new && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + /* If bit13 in Command complete event is set, software + * must issue ENDTRANSFER command and wait for + * Xfernotready event to queue the requests again. + */ + if (!dep->resource_index) { + dep->resource_index = + dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + WARN_ON_ONCE(!dep->resource_index); + } + dwc3_stop_active_transfer(dwc, dep->number, true); + list_for_each_entry_safe_reverse(req1, n, + &dep->req_queued, list) { + req1->trb = NULL; + dwc3_gadget_move_request_list_front(req1); + if (req->request.num_mapped_sgs) + dep->busy_slot += + req->request.num_mapped_sgs; + else + dep->busy_slot++; + if ((dep->busy_slot & DWC3_TRB_MASK) == + DWC3_TRB_NUM - 1) + dep->busy_slot++; + } + return ret; + } + + /* + * FIXME we need to iterate over the list of requests + * here and stop, unmap, free and del each of the linked + * requests instead of what we do now. + */ + usb_gadget_unmap_request(&dwc->gadget, &req->request, + req->direction); + list_del(&req->list); + return ret; + } + + dep->flags |= DWC3_EP_BUSY; + + if (start_new) { + dep->resource_index = dwc3_gadget_ep_get_transfer_index(dwc, + dep->number); + WARN_ON_ONCE(!dep->resource_index); + } + + return 0; +} + +static void __dwc3_gadget_start_isoc(struct dwc3 *dwc, + struct dwc3_ep *dep, u32 cur_uf) +{ + u32 uf; + int ret; + + dep->current_uf = cur_uf; + + if (list_empty(&dep->request_list)) { + dwc3_trace(trace_dwc3_gadget, + "ISOC ep %s run out for requests", + dep->name); + dep->flags |= DWC3_EP_PENDING_REQUEST; + return; + } + + /* 4 micro frames in the future */ + uf = cur_uf + dep->interval * 4; + + ret = __dwc3_gadget_kick_transfer(dep, uf, 1); + if (ret < 0) + dbg_event(dep->number, "ISOC QUEUE", ret); +} + +static void dwc3_gadget_start_isoc(struct dwc3 *dwc, + struct dwc3_ep *dep, const struct dwc3_event_depevt *event) +{ + u32 cur_uf, mask; + + mask = ~(dep->interval - 1); + cur_uf = event->parameters & mask; + + __dwc3_gadget_start_isoc(dwc, dep, cur_uf); +} + +static int __dwc3_gadget_ep_queue(struct dwc3_ep *dep, struct dwc3_request *req) +{ + struct dwc3 *dwc = dep->dwc; + int ret; + + if (req->request.status == -EINPROGRESS) { + ret = -EBUSY; + dev_err(dwc->dev, "%s: %pK request already in queue", + dep->name, req); + return ret; + } + + req->request.actual = 0; + req->request.status = -EINPROGRESS; + req->direction = dep->direction; + req->epnum = dep->number; + + trace_dwc3_ep_queue(req); + + /* + * We only add to our list of requests now and + * start consuming the list once we get XferNotReady + * IRQ. + * + * That way, we avoid doing anything that we don't need + * to do now and defer it until the point we receive a + * particular token from the Host side. + * + * This will also avoid Host cancelling URBs due to too + * many NAKs. + */ + ret = usb_gadget_map_request(&dwc->gadget, &req->request, + dep->direction); + if (ret) + return ret; + + list_add_tail(&req->list, &dep->request_list); + + /* + * There are a few special cases: + * + * 1. XferNotReady with empty list of requests. We need to kick the + * transfer here in that situation, otherwise we will be NAKing + * forever. If we get XferNotReady before gadget driver has a + * chance to queue a request, we will ACK the IRQ but won't be + * able to receive the data until the next request is queued. + * The following code is handling exactly that. + * + */ + if (dep->flags & DWC3_EP_PENDING_REQUEST) { + /* + * If xfernotready is already elapsed and it is a case + * of isoc transfer, then issue END TRANSFER, so that + * you can receive xfernotready again and can have + * notion of current microframe. + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + /* If xfernotready event is recieved before issuing + * START TRANSFER command, don't issue END TRANSFER. + * Rather start queueing the requests by issuing START + * TRANSFER command. + */ + if (list_empty(&dep->req_queued) && dep->resource_index) + dwc3_stop_active_transfer(dwc, dep->number, true); + else + __dwc3_gadget_start_isoc(dwc, dep, + dep->current_uf); + dep->flags &= ~DWC3_EP_PENDING_REQUEST; + return 0; + } + + ret = __dwc3_gadget_kick_transfer(dep, 0, true); + if (!ret) + dep->flags &= ~DWC3_EP_PENDING_REQUEST; + else if (ret != -EBUSY) + dbg_event(dep->number, "XfNR QUEUE", ret); + + goto out; + } + + /* + * 2. XferInProgress on Isoc EP with an active transfer. We need to + * kick the transfer here after queuing a request, otherwise the + * core may not see the modified TRB(s). + */ + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + (dep->flags & DWC3_EP_BUSY) && + !(dep->flags & DWC3_EP_MISSED_ISOC)) { + WARN_ON_ONCE(!dep->resource_index); + ret = __dwc3_gadget_kick_transfer(dep, dep->resource_index, + false); + if (ret && ret != -EBUSY) + dbg_event(dep->number, "XfIP QUEUE", ret); + goto out; + } + + /* + * 4. Stream Capable Bulk Endpoints. We need to start the transfer + * right away, otherwise host will not know we have streams to be + * handled. + */ + if (dep->stream_capable) + ret = __dwc3_gadget_kick_transfer(dep, 0, true); + +out: + if (ret && ret != -EBUSY) { + dbg_event(dep->number, "QUEUE err", ret); + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + } + if (ret == -EBUSY) + ret = 0; + + return ret; +} + +static int dwc3_gadget_wakeup(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + schedule_work(&dwc->wakeup_work); + return 0; +} + +static bool dwc3_gadget_is_suspended(struct dwc3 *dwc) +{ + if (atomic_read(&dwc->in_lpm) || + dwc->link_state == DWC3_LINK_STATE_U3) + return true; + return false; +} + +static void __dwc3_gadget_ep_zlp_complete(struct usb_ep *ep, + struct usb_request *request) +{ + dwc3_gadget_ep_free_request(ep, request); +} + +static int __dwc3_gadget_ep_queue_zlp(struct dwc3 *dwc, struct dwc3_ep *dep) +{ + struct dwc3_request *req; + struct usb_request *request; + struct usb_ep *ep = &dep->endpoint; + + dwc3_trace(trace_dwc3_gadget, "queueing ZLP\n"); + request = dwc3_gadget_ep_alloc_request(ep, GFP_ATOMIC); + if (!request) + return -ENOMEM; + + request->length = 0; + request->buf = dwc->zlp_buf; + request->complete = __dwc3_gadget_ep_zlp_complete; + + req = to_dwc3_request(request); + + return __dwc3_gadget_ep_queue(dep, req); +} + +static int dwc3_gadget_ep_queue(struct usb_ep *ep, struct usb_request *request, + gfp_t gfp_flags) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + if (!dep->endpoint.desc) { + dev_dbg(dwc->dev, "trying to queue request %pK to disabled %s\n", + request, ep->name); + ret = -ESHUTDOWN; + goto out; + } + + if (WARN(req->dep != dep, "request %pK belongs to '%s'\n", + request, req->dep->name)) { + ret = -EINVAL; + goto out; + } + + /* + * Queuing endless request to USB endpoint through generic ep queue + * API should not be allowed. + */ + if (dep->endpoint.endless) { + dev_dbg(dwc->dev, "trying to queue endless request %p to %s\n", + request, ep->name); + spin_unlock_irqrestore(&dwc->lock, flags); + return -EPERM; + } + + if (dwc3_gadget_is_suspended(dwc)) { + if (dwc->gadget.remote_wakeup) + dwc3_gadget_wakeup(&dwc->gadget); + ret = dwc->gadget.remote_wakeup ? -EAGAIN : -ENOTSUPP; + goto out; + } + + WARN(!dep->direction && (request->length % ep->desc->wMaxPacketSize), + "trying to queue unaligned request (%d)\n", request->length); + + ret = __dwc3_gadget_ep_queue(dep, req); + + /* + * Okay, here's the thing, if gadget driver has requested for a ZLP by + * setting request->zero, instead of doing magic, we will just queue an + * extra usb_request ourselves so that it gets handled the same way as + * any other request. + */ + if (ret == 0 && request->zero && request->length && + (request->length % ep->maxpacket == 0)) + ret = __dwc3_gadget_ep_queue_zlp(dwc, dep); + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_ep_dequeue(struct usb_ep *ep, + struct usb_request *request) +{ + struct dwc3_request *req = to_dwc3_request(request); + struct dwc3_request *r = NULL; + + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + int ret = 0; + + if (atomic_read(&dwc->in_lpm)) { + dev_err(dwc->dev, "Unable to dequeue while in LPM\n"); + return -EAGAIN; + } + + trace_dwc3_ep_dequeue(req); + + spin_lock_irqsave(&dwc->lock, flags); + + list_for_each_entry(r, &dep->request_list, list) { + if (r == req) + break; + } + + if (r != req) { + list_for_each_entry(r, &dep->req_queued, list) { + if (r == req) + break; + } + if (r == req) { + /* wait until it is processed */ + dwc3_stop_active_transfer(dwc, dep->number, true); + goto out1; + } + dev_err(dwc->dev, "request %pK was not queued to %s\n", + request, ep->name); + ret = -EINVAL; + goto out0; + } + +out1: + dbg_event(dep->number, "DEQUEUE", 0); + /* giveback the request */ + dwc3_gadget_giveback(dep, req, -ECONNRESET); + +out0: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +int __dwc3_gadget_ep_set_halt(struct dwc3_ep *dep, int value, int protocol) +{ + struct dwc3_gadget_ep_cmd_params params; + struct dwc3 *dwc = dep->dwc; + int ret; + + memset(¶ms, 0x00, sizeof(params)); + + if (value) { + if (!protocol && ((dep->direction && dep->flags & DWC3_EP_BUSY) || + (!list_empty(&dep->req_queued) || + !list_empty(&dep->request_list)))) { + dev_dbg(dwc->dev, "%s: pending request, cannot halt\n", + dep->name); + return -EAGAIN; + } + + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_SETSTALL, ¶ms); + if (ret) + dev_err(dwc->dev, "failed to set STALL on %s\n", + dep->name); + else + dep->flags |= DWC3_EP_STALL; + } else { + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_CLEARSTALL, ¶ms); + if (ret) + dev_err(dwc->dev, "failed to clear STALL on %s\n", + dep->name); + else + dep->flags &= ~(DWC3_EP_STALL | DWC3_EP_WEDGE); + } + + return ret; +} + +static int dwc3_gadget_ep_set_halt(struct usb_ep *ep, int value) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + + unsigned long flags; + + int ret; + + if (!ep->desc) { + dev_err(dwc->dev, "(%s)'s desc is NULL.\n", dep->name); + return -EINVAL; + } + + spin_lock_irqsave(&dwc->lock, flags); + dbg_event(dep->number, "HALT", value); + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_err(dwc->dev, "%s is of Isochronous type\n", dep->name); + ret = -EINVAL; + goto out; + } + + ret = __dwc3_gadget_ep_set_halt(dep, value, false); +out: + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +static int dwc3_gadget_ep_set_wedge(struct usb_ep *ep) +{ + struct dwc3_ep *dep = to_dwc3_ep(ep); + struct dwc3 *dwc = dep->dwc; + unsigned long flags; + int ret; + + spin_lock_irqsave(&dwc->lock, flags); + dbg_event(dep->number, "WEDGE", 0); + dep->flags |= DWC3_EP_WEDGE; + + if (dep->number == 0 || dep->number == 1) + ret = __dwc3_gadget_ep0_set_halt(ep, 1); + else + ret = __dwc3_gadget_ep_set_halt(dep, 1, false); + spin_unlock_irqrestore(&dwc->lock, flags); + + return ret; +} + +/* -------------------------------------------------------------------------- */ + +static struct usb_endpoint_descriptor dwc3_gadget_ep0_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, +}; + +static const struct usb_ep_ops dwc3_gadget_ep0_ops = { + .enable = dwc3_gadget_ep0_enable, + .disable = dwc3_gadget_ep0_disable, + .alloc_request = dwc3_gadget_ep_alloc_request, + .free_request = dwc3_gadget_ep_free_request, + .queue = dwc3_gadget_ep0_queue, + .dequeue = dwc3_gadget_ep_dequeue, + .set_halt = dwc3_gadget_ep0_set_halt, + .set_wedge = dwc3_gadget_ep_set_wedge, +}; + +static const struct usb_ep_ops dwc3_gadget_ep_ops = { + .enable = dwc3_gadget_ep_enable, + .disable = dwc3_gadget_ep_disable, + .alloc_request = dwc3_gadget_ep_alloc_request, + .free_request = dwc3_gadget_ep_free_request, + .queue = dwc3_gadget_ep_queue, + .dequeue = dwc3_gadget_ep_dequeue, + .set_halt = dwc3_gadget_ep_set_halt, + .set_wedge = dwc3_gadget_ep_set_wedge, +}; + +/* -------------------------------------------------------------------------- */ + +static int dwc3_gadget_get_frame(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + return DWC3_DSTS_SOFFN(reg); +} + +#define DWC3_PM_RESUME_RETRIES 20 /* Max Number of retries */ +#define DWC3_PM_RESUME_DELAY 100 /* 100 msec */ + +static void dwc3_gadget_wakeup_work(struct work_struct *w) +{ + struct dwc3 *dwc; + int ret; + static int retry_count; + + dwc = container_of(w, struct dwc3, wakeup_work); + + ret = pm_runtime_get_sync(dwc->dev); + if (ret) { + /* pm_runtime_get_sync returns -EACCES error between + * late_suspend and early_resume, wait for system resume to + * finish and queue work again + */ + pr_debug("PM runtime get sync failed, ret %d\n", ret); + if (ret == -EACCES) { + pm_runtime_put_noidle(dwc->dev); + if (retry_count == DWC3_PM_RESUME_RETRIES) { + retry_count = 0; + pr_err("pm_runtime_get_sync timed out\n"); + return; + } + msleep(DWC3_PM_RESUME_DELAY); + retry_count++; + schedule_work(&dwc->wakeup_work); + return; + } + } + retry_count = 0; + dbg_event(0xFF, "Gdgwake gsyn", + atomic_read(&dwc->dev->power.usage_count)); + + ret = dwc3_gadget_wakeup_int(dwc); + + if (ret) + pr_err("Remote wakeup failed. ret = %d.\n", ret); + else + pr_debug("Remote wakeup succeeded.\n"); + + pm_runtime_put_noidle(dwc->dev); + dbg_event(0xFF, "Gdgwake put", + atomic_read(&dwc->dev->power.usage_count)); +} + +static int dwc3_gadget_wakeup_int(struct dwc3 *dwc) +{ + bool link_recover_only = false; + + u32 reg; + int ret = 0; + u8 link_state; + unsigned long flags; + + pr_debug("%s(): Entry\n", __func__); + disable_irq(dwc->irq); + spin_lock_irqsave(&dwc->lock, flags); + /* + * According to the Databook Remote wakeup request should + * be issued only when the device is in early suspend state. + * + * We can check that via USB Link State bits in DSTS register. + */ + link_state = dwc3_get_link_state(dwc); + + switch (link_state) { + case DWC3_LINK_STATE_RX_DET: /* in HS, means Early Suspend */ + case DWC3_LINK_STATE_U3: /* in HS, means SUSPEND */ + break; + case DWC3_LINK_STATE_U1: + if (dwc->gadget.speed != USB_SPEED_SUPER) { + link_recover_only = true; + break; + } + /* Intentional fallthrough */ + default: + dev_dbg(dwc->dev, "can't wakeup from link state %d\n", + link_state); + ret = -EINVAL; + goto out; + } + + /* Enable LINK STATUS change event */ + reg = dwc3_readl(dwc->regs, DWC3_DEVTEN); + reg |= DWC3_DEVTEN_ULSTCNGEN; + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); + /* + * memory barrier is required to make sure that required events + * with core is enabled before performing RECOVERY mechnism. + */ + mb(); + + ret = dwc3_gadget_set_link_state(dwc, DWC3_LINK_STATE_RECOV); + if (ret < 0) { + dev_err(dwc->dev, "failed to put link in Recovery\n"); + /* Disable LINK STATUS change */ + reg = dwc3_readl(dwc->regs, DWC3_DEVTEN); + reg &= ~DWC3_DEVTEN_ULSTCNGEN; + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); + /* Required to complete this operation before returning */ + mb(); + goto out; + } + + /* Recent versions do this automatically */ + if (dwc->revision < DWC3_REVISION_194A) { + /* write zeroes to Link Change Request */ + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_ULSTCHNGREQ_MASK; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + + spin_unlock_irqrestore(&dwc->lock, flags); + enable_irq(dwc->irq); + + /* + * Have bigger value (16 sec) for timeout since some host PCs driving + * resume for very long time (e.g. 8 sec) + */ + ret = wait_event_interruptible_timeout(dwc->wait_linkstate, + (dwc->link_state < DWC3_LINK_STATE_U3) || + (dwc->link_state == DWC3_LINK_STATE_SS_DIS), + msecs_to_jiffies(16000)); + + spin_lock_irqsave(&dwc->lock, flags); + /* Disable link status change event */ + reg = dwc3_readl(dwc->regs, DWC3_DEVTEN); + reg &= ~DWC3_DEVTEN_ULSTCNGEN; + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); + /* + * Complete this write before we go ahead and perform resume + * as we don't need link status change notificaiton anymore. + */ + mb(); + + if (!ret) { + dev_dbg(dwc->dev, "Timeout moving into state(%d)\n", + dwc->link_state); + ret = -EINVAL; + spin_unlock_irqrestore(&dwc->lock, flags); + goto out1; + } else { + ret = 0; + /* + * If USB is disconnected OR received RESET from host, + * don't perform resume + */ + if (dwc->link_state == DWC3_LINK_STATE_SS_DIS || + dwc->gadget.state == USB_STATE_DEFAULT) + link_recover_only = true; + } + + /* + * According to DWC3 databook, the controller does not + * trigger a wakeup event when remote-wakeup is used. + * Hence, after remote-wakeup sequence is complete, and + * the device is back at U0 state, it is required that + * the resume sequence is initiated by SW. + */ + if (!link_recover_only) + dwc3_gadget_wakeup_interrupt(dwc, true); + + spin_unlock_irqrestore(&dwc->lock, flags); + pr_debug("%s: Exit\n", __func__); + return ret; + +out: + spin_unlock_irqrestore(&dwc->lock, flags); + enable_irq(dwc->irq); + +out1: + return ret; +} + +static int dwc_gadget_func_wakeup(struct usb_gadget *g, int interface_id) +{ + int ret = 0; + struct dwc3 *dwc = gadget_to_dwc(g); + + if (!g || (g->speed != USB_SPEED_SUPER)) + return -ENOTSUPP; + + if (dwc3_gadget_is_suspended(dwc)) { + pr_debug("USB bus is suspended. Scheduling wakeup and returning -EAGAIN.\n"); + dwc3_gadget_wakeup(&dwc->gadget); + return -EAGAIN; + } + + if (dwc->revision < DWC3_REVISION_220A) { + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_XMIT_FUNCTION, interface_id); + } else { + ret = dwc3_send_gadget_generic_command(dwc, + DWC3_DGCMD_XMIT_DEV, 0x1 | (interface_id << 4)); + } + + if (ret) + pr_err("Function wakeup HW command failed.\n"); + else + pr_debug("Function wakeup HW command succeeded.\n"); + + return ret; +} + +static int dwc3_gadget_set_selfpowered(struct usb_gadget *g, + int is_selfpowered) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + spin_lock_irqsave(&dwc->lock, flags); + g->is_selfpowered = !!is_selfpowered; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +#define DWC3_SOFT_RESET_TIMEOUT 10 /* 10 msec */ +static int dwc3_gadget_run_stop(struct dwc3 *dwc, int is_on, int suspend) +{ + u32 reg; + u32 timeout = 500; + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + if (is_on) { + dbg_event(0xFF, "Pullup_enable", is_on); + if (dwc->revision <= DWC3_REVISION_187A) { + reg &= ~DWC3_DCTL_TRGTULST_MASK; + reg |= DWC3_DCTL_TRGTULST_RX_DET; + } + + if (dwc->revision >= DWC3_REVISION_194A) + reg &= ~DWC3_DCTL_KEEP_CONNECT; + + + dwc3_event_buffers_setup(dwc); + dwc3_gadget_restart(dwc); + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= DWC3_DCTL_RUN_STOP; + + if (dwc->has_hibernation) + reg |= DWC3_DCTL_KEEP_CONNECT; + + dwc->pullups_connected = true; + } else { + dbg_event(0xFF, "Pullup_disable", is_on); + dwc3_gadget_disable_irq(dwc); + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + + reg &= ~DWC3_DCTL_RUN_STOP; + + if (dwc->has_hibernation && !suspend) + reg &= ~DWC3_DCTL_KEEP_CONNECT; + + dwc->pullups_connected = false; + usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED); + } + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + do { + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + if (is_on) { + if (!(reg & DWC3_DSTS_DEVCTRLHLT)) + break; + } else { + if (reg & DWC3_DSTS_DEVCTRLHLT) + break; + } + timeout--; + if (!timeout) { + dev_err(dwc->dev, "failed to %s controller\n", + is_on ? "start" : "stop"); + if (is_on) + dbg_event(0xFF, "STARTTOUT", reg); + else + dbg_event(0xFF, "STOPTOUT", reg); + return -ETIMEDOUT; + } + udelay(1); + } while (1); + + dwc3_trace(trace_dwc3_gadget, "gadget %s data soft-%s", + dwc->gadget_driver + ? dwc->gadget_driver->function : "no-function", + is_on ? "connect" : "disconnect"); + + return 0; +} + +static int dwc3_gadget_vbus_draw(struct usb_gadget *g, unsigned mA) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + dwc->vbus_draw = mA; + dev_dbg(dwc->dev, "Notify controller from %s. mA = %d\n", __func__, mA); + dwc3_notify_event(dwc, DWC3_CONTROLLER_SET_CURRENT_DRAW_EVENT, 0); + return 0; +} + +static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret; + + is_on = !!is_on; + + dwc->softconnect = is_on; + + if ((dwc->is_drd && !dwc->vbus_active) || !dwc->gadget_driver) { + /* + * Need to wait for vbus_session(on) from otg driver or to + * the udc_start. + */ + return 0; + } + + pm_runtime_get_sync(dwc->dev); + dbg_event(0xFF, "Pullup gsync", + atomic_read(&dwc->dev->power.usage_count)); + + spin_lock_irqsave(&dwc->lock, flags); + + /* + * If we are here after bus suspend notify otg state machine to + * increment pm usage count of dwc to prevent pm_runtime_suspend + * during enumeration. + */ + dev_dbg(dwc->dev, "Notify OTG from %s\n", __func__); + dwc->b_suspend = false; + dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_OTG_EVENT, 0); + + ret = dwc3_gadget_run_stop(dwc, is_on, false); + spin_unlock_irqrestore(&dwc->lock, flags); + + pm_runtime_mark_last_busy(dwc->dev); + pm_runtime_put_autosuspend(dwc->dev); + dbg_event(0xFF, "Pullup put", + atomic_read(&dwc->dev->power.usage_count)); + + return ret; +} + +void dwc3_gadget_enable_irq(struct dwc3 *dwc) +{ + u32 reg; + + /* Enable all but Start and End of Frame IRQs */ + reg = (DWC3_DEVTEN_VNDRDEVTSTRCVEDEN | + DWC3_DEVTEN_EVNTOVERFLOWEN | + DWC3_DEVTEN_CMDCMPLTEN | + DWC3_DEVTEN_ERRTICERREN | + DWC3_DEVTEN_WKUPEVTEN | + DWC3_DEVTEN_CONNECTDONEEN | + DWC3_DEVTEN_USBRSTEN | + DWC3_DEVTEN_DISCONNEVTEN); + + /* + * Enable SUSPENDEVENT(BIT:6) for version 230A and above + * else enable USB Link change event (BIT:3) for older version + */ + if (dwc->revision < DWC3_REVISION_230A) + reg |= DWC3_DEVTEN_ULSTCNGEN; + else + reg |= DWC3_DEVTEN_SUSPEND; + + dwc3_writel(dwc->regs, DWC3_DEVTEN, reg); +} + +void dwc3_gadget_disable_irq(struct dwc3 *dwc) +{ + /* mask all interrupts */ + dwc3_writel(dwc->regs, DWC3_DEVTEN, 0x00); +} + +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc); +static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc); + +static int dwc3_gadget_vbus_session(struct usb_gadget *_gadget, int is_active) +{ + struct dwc3 *dwc = gadget_to_dwc(_gadget); + unsigned long flags; + + if (!dwc->is_drd) + return -EPERM; + + is_active = !!is_active; + + spin_lock_irqsave(&dwc->lock, flags); + + /* Mark that the vbus was powered */ + dwc->vbus_active = is_active; + + /* + * Check if upper level usb_gadget_driver was already registerd with + * this udc controller driver (if dwc3_gadget_start was called) + */ + if (dwc->gadget_driver && dwc->softconnect) { + if (dwc->vbus_active) { + /* + * Both vbus was activated by otg and pullup was + * signaled by the gadget driver. + */ + dwc3_gadget_run_stop(dwc, 1, false); + } else { + dwc3_gadget_run_stop(dwc, 0, false); + } + } + + /* + * Clearing run/stop bit might occur before disconnect event is seen. + * Make sure to let gadget driver know in that case. + */ + if (!dwc->vbus_active) { + dev_dbg(dwc->dev, "calling disconnect from %s\n", __func__); + dwc3_gadget_disconnect_interrupt(dwc); + } + + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; +} + +static int __dwc3_gadget_start(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret = 0; + u32 reg; + + /* + * Use IMOD if enabled via dwc->imod_interval. Otherwise, if + * the core supports IMOD, disable it. + */ + if (dwc->imod_interval) { + dwc3_writel(dwc->regs, DWC3_DEV_IMOD(0), dwc->imod_interval); + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), DWC3_GEVNTCOUNT_EHB); + } else if (dwc3_has_imod(dwc)) { + dwc3_writel(dwc->regs, DWC3_DEV_IMOD(0), 0); + } + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_SPEED_MASK); + + /** + * WORKAROUND: DWC3 revision < 2.20a have an issue + * which would cause metastability state on Run/Stop + * bit if we try to force the IP to USB2-only mode. + * + * Because of that, we cannot configure the IP to any + * speed other than the SuperSpeed + * + * Refers to: + * + * STAR#9000525659: Clock Domain Crossing on DCTL in + * USB 2.0 Mode + */ + if (dwc->revision < DWC3_REVISION_220A) { + reg |= DWC3_DCFG_SUPERSPEED; + } else { + switch (dwc->maximum_speed) { + case USB_SPEED_LOW: + reg |= DWC3_DSTS_LOWSPEED; + break; + case USB_SPEED_FULL: + reg |= DWC3_DSTS_FULLSPEED1; + break; + case USB_SPEED_HIGH: + reg |= DWC3_DSTS_HIGHSPEED; + break; + case USB_SPEED_SUPER: /* FALLTHROUGH */ + case USB_SPEED_UNKNOWN: /* FALTHROUGH */ + default: + reg |= DWC3_DSTS_SUPERSPEED; + } + } + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + /* Programs the number of outstanding pipelined transfer requests + * the AXI master pushes to the AXI slave. + */ + if (dwc->revision >= DWC3_REVISION_270A) { + reg = dwc3_readl(dwc->regs, DWC3_GSBUSCFG1); + reg &= ~DWC3_GSBUSCFG1_PIPETRANSLIMIT_MASK; + reg |= DWC3_GSBUSCFG1_PIPETRANSLIMIT(0xe); + dwc3_writel(dwc->regs, DWC3_GSBUSCFG1, reg); + } + + /* Start with SuperSpeed Default */ + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + dwc->delayed_status = false; + /* reinitialize physical ep0-1 */ + dep = dwc->eps[0]; + dep->flags = 0; + dep->endpoint.maxburst = 1; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + return ret; + } + + dep = dwc->eps[1]; + dep->flags = 0; + dep->endpoint.maxburst = 1; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + __dwc3_gadget_ep_disable(dwc->eps[0]); + return ret; + } + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc->link_state = DWC3_LINK_STATE_SS_DIS; + dwc3_ep0_out_start(dwc); + + dwc3_gadget_enable_irq(dwc); + + return ret; +} + +/* Required gadget re-initialization before switching to gadget in OTG mode */ +void dwc3_gadget_restart(struct dwc3 *dwc) +{ + __dwc3_gadget_start(dwc); +} + +static int dwc3_gadget_start(struct usb_gadget *g, + struct usb_gadget_driver *driver) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&dwc->lock, flags); + + if (dwc->gadget_driver) { + dev_err(dwc->dev, "%s is already bound to %s\n", + dwc->gadget.name, + dwc->gadget_driver->driver.name); + ret = -EBUSY; + goto err0; + } + + dwc->gadget_driver = driver; + + /* + * For DRD, this might get called by gadget driver during bootup + * even though host mode might be active. Don't actually perform + * device-specific initialization until device mode is activated. + * In that case dwc3_gadget_restart() will handle it. + */ + spin_unlock_irqrestore(&dwc->lock, flags); + return 0; + +err0: + spin_unlock_irqrestore(&dwc->lock, flags); + return ret; +} + +static int dwc3_gadget_stop(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + unsigned long flags; + + + spin_lock_irqsave(&dwc->lock, flags); + dwc->gadget_driver = NULL; + spin_unlock_irqrestore(&dwc->lock, flags); + + return 0; +} + +static int dwc3_gadget_restart_usb_session(struct usb_gadget *g) +{ + struct dwc3 *dwc = gadget_to_dwc(g); + + return dwc3_notify_event(dwc, DWC3_CONTROLLER_RESTART_USB_SESSION, 0); +} + +static const struct usb_gadget_ops dwc3_gadget_ops = { + .get_frame = dwc3_gadget_get_frame, + .wakeup = dwc3_gadget_wakeup, + .func_wakeup = dwc_gadget_func_wakeup, + .set_selfpowered = dwc3_gadget_set_selfpowered, + .vbus_session = dwc3_gadget_vbus_session, + .vbus_draw = dwc3_gadget_vbus_draw, + .pullup = dwc3_gadget_pullup, + .udc_start = dwc3_gadget_start, + .udc_stop = dwc3_gadget_stop, + .restart = dwc3_gadget_restart_usb_session, +}; + +/* -------------------------------------------------------------------------- */ + +#define NUM_GSI_OUT_EPS 1 +#define NUM_GSI_IN_EPS 2 + +static int dwc3_gadget_init_hw_endpoints(struct dwc3 *dwc, + u8 num, u32 direction) +{ + struct dwc3_ep *dep; + u8 i, gsi_ep_count, gsi_ep_index = 0; + + /* Read number of event buffers to check if we need + * to update gsi_ep_count. For non GSI targets this + * will be 0 and we will skip reservation of GSI eps. + * There is one event buffer for each GSI EP. + */ + gsi_ep_count = dwc->num_gsi_event_buffers; + /* OUT GSI EPs based on direction field */ + if (gsi_ep_count && !direction) + gsi_ep_count = NUM_GSI_OUT_EPS; + /* IN GSI EPs */ + else if (gsi_ep_count && direction) + gsi_ep_count = NUM_GSI_IN_EPS; + + for (i = 0; i < num; i++) { + u8 epnum = (i << 1) | (!!direction); + + dep = kzalloc(sizeof(*dep), GFP_KERNEL); + if (!dep) + return -ENOMEM; + + dep->dwc = dwc; + dep->number = epnum; + dep->direction = !!direction; + dwc->eps[epnum] = dep; + + /* Reserve EPs at the end for GSI based on gsi_ep_count */ + if ((gsi_ep_index < gsi_ep_count) && + (i > (num - 1 - gsi_ep_count))) { + gsi_ep_index++; + /* For GSI EPs, name eps as "gsi-epin" or "gsi-epout" */ + snprintf(dep->name, sizeof(dep->name), "%s", + (epnum & 1) ? "gsi-epin" : "gsi-epout"); + /* Set ep type as GSI */ + dep->endpoint.ep_type = EP_TYPE_GSI; + } else { + snprintf(dep->name, sizeof(dep->name), "ep%d%s", + epnum >> 1, (epnum & 1) ? "in" : "out"); + } + + dep->endpoint.ep_num = epnum >> 1; + dep->endpoint.name = dep->name; + + dwc3_trace(trace_dwc3_gadget, "initializing %s", dep->name); + + if (epnum == 0 || epnum == 1) { + usb_ep_set_maxpacket_limit(&dep->endpoint, 512); + dep->endpoint.maxburst = 1; + dep->endpoint.ops = &dwc3_gadget_ep0_ops; + if (!epnum) + dwc->gadget.ep0 = &dep->endpoint; + } else { + int ret; + + usb_ep_set_maxpacket_limit(&dep->endpoint, 1024); + dep->endpoint.max_streams = 15; + dep->endpoint.ops = &dwc3_gadget_ep_ops; + list_add_tail(&dep->endpoint.ep_list, + &dwc->gadget.ep_list); + + ret = dwc3_alloc_trb_pool(dep); + if (ret) + return ret; + } + + if (epnum == 0 || epnum == 1) { + dep->endpoint.caps.type_control = true; + } else { + dep->endpoint.caps.type_iso = true; + dep->endpoint.caps.type_bulk = true; + dep->endpoint.caps.type_int = true; + } + + dep->endpoint.caps.dir_in = !!direction; + dep->endpoint.caps.dir_out = !direction; + + INIT_LIST_HEAD(&dep->request_list); + INIT_LIST_HEAD(&dep->req_queued); + } + + return 0; +} + +static int dwc3_gadget_init_endpoints(struct dwc3 *dwc) +{ + int ret; + + INIT_LIST_HEAD(&dwc->gadget.ep_list); + + ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_out_eps, 0); + if (ret < 0) { + dwc3_trace(trace_dwc3_gadget, + "failed to allocate OUT endpoints"); + return ret; + } + + ret = dwc3_gadget_init_hw_endpoints(dwc, dwc->num_in_eps, 1); + if (ret < 0) { + dwc3_trace(trace_dwc3_gadget, + "failed to allocate IN endpoints"); + return ret; + } + + return 0; +} + +static void dwc3_gadget_free_endpoints(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + u8 epnum; + + for (epnum = 0; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + dep = dwc->eps[epnum]; + if (!dep) + continue; + /* + * Physical endpoints 0 and 1 are special; they form the + * bi-directional USB endpoint 0. + * + * For those two physical endpoints, we don't allocate a TRB + * pool nor do we add them the endpoints list. Due to that, we + * shouldn't do these two operations otherwise we would end up + * with all sorts of bugs when removing dwc3.ko. + */ + if (epnum != 0 && epnum != 1) { + dwc3_free_trb_pool(dep); + list_del(&dep->endpoint.ep_list); + } + + kfree(dep); + } +} + +/* -------------------------------------------------------------------------- */ + +static int __dwc3_cleanup_done_trbs(struct dwc3 *dwc, struct dwc3_ep *dep, + struct dwc3_request *req, struct dwc3_trb *trb, unsigned length, + const struct dwc3_event_depevt *event, int status) +{ + unsigned int count; + unsigned int s_pkt = 0; + unsigned int trb_status; + + trace_dwc3_complete_trb(dep, trb); + + if ((trb->ctrl & DWC3_TRB_CTRL_HWO) && status != -ESHUTDOWN) + /* + * We continue despite the error. There is not much we + * can do. If we don't clean it up we loop forever. If + * we skip the TRB then it gets overwritten after a + * while since we use them in a ring buffer. A BUG() + * would help. Lets hope that if this occurs, someone + * fixes the root cause instead of looking away :) + */ + dev_err(dwc->dev, "%s's TRB (%pK) still owned by HW\n", + dep->name, trb); + count = trb->size & DWC3_TRB_SIZE_MASK; + + if (dep->direction) { + if (count) { + trb_status = DWC3_TRB_SIZE_TRBSTS(trb->size); + if (trb_status == DWC3_TRBSTS_MISSED_ISOC) { + dev_dbg(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + /* + * If missed isoc occurred and there is + * no request queued then issue END + * TRANSFER, so that core generates + * next xfernotready and we will issue + * a fresh START TRANSFER. + * If there are still queued request + * then wait, do not issue either END + * or UPDATE TRANSFER, just attach next + * request in request_list during + * giveback.If any future queued request + * is successfully transferred then we + * will issue UPDATE TRANSFER for all + * request in the request_list. + */ + dep->flags |= DWC3_EP_MISSED_ISOC; + dbg_event(dep->number, "MISSED ISOC", status); + } else { + dev_err(dwc->dev, "incomplete IN transfer %s\n", + dep->name); + status = -ECONNRESET; + } + } else { + dep->flags &= ~DWC3_EP_MISSED_ISOC; + } + } else { + if (count && (event->status & DEPEVT_STATUS_SHORT)) + s_pkt = 1; + } + + /* + * We assume here we will always receive the entire data block + * which we should receive. Meaning, if we program RX to + * receive 4K but we receive only 2K, we assume that's all we + * should receive and we simply bounce the request back to the + * gadget driver for further processing. + */ + req->request.actual += length - count; + if (s_pkt) + return 1; + if ((event->status & DEPEVT_STATUS_LST) && + (trb->ctrl & (DWC3_TRB_CTRL_LST | + DWC3_TRB_CTRL_HWO))) + return 1; + if ((event->status & DEPEVT_STATUS_IOC) && + (trb->ctrl & DWC3_TRB_CTRL_IOC)) + return 1; + return 0; +} + +static int dwc3_cleanup_done_reqs(struct dwc3 *dwc, struct dwc3_ep *dep, + const struct dwc3_event_depevt *event, int status) +{ + struct dwc3_request *req; + struct dwc3_trb *trb; + unsigned int slot; + unsigned int i; + unsigned int trb_len; + int ret; + + do { + req = next_request(&dep->req_queued); + if (!req) { + dev_err(dwc->dev, "%s: evt sts %x for no req queued", + dep->name, event->status); + return 1; + } + + /* Make sure that not to queue any TRB if HWO bit is set. */ + if (req->trb->ctrl & DWC3_TRB_CTRL_HWO) + return 0; + + i = 0; + do { + slot = req->start_slot + i; + if ((slot == DWC3_TRB_NUM - 1) && + usb_endpoint_xfer_isoc(dep->endpoint.desc)) + slot++; + slot %= DWC3_TRB_NUM; + trb = &dep->trb_pool[slot]; + + if (req->request.num_mapped_sgs) + trb_len = sg_dma_len(&req->request.sg[i]); + else + trb_len = req->request.length; + + ret = __dwc3_cleanup_done_trbs(dwc, dep, req, trb, + trb_len, event, status); + if (ret) + break; + } while (++i < req->request.num_mapped_sgs); + + dwc3_gadget_giveback(dep, req, status); + + /* EP possibly disabled during giveback? */ + if (!(dep->flags & DWC3_EP_ENABLED)) { + dev_dbg(dwc->dev, "%s disabled while handling ep event\n", + dep->name); + return 0; + } + + if (ret) + break; + } while (1); + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc) && + list_empty(&dep->req_queued)) { + if (list_empty(&dep->request_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 + dwc3_stop_active_transfer(dwc, dep->number, true); + dep->flags &= ~DWC3_EP_MISSED_ISOC; + return 1; + } + + if ((event->status & DEPEVT_STATUS_IOC) && + (trb->ctrl & DWC3_TRB_CTRL_IOC)) + return 0; + return 1; +} + +static void dwc3_endpoint_transfer_complete(struct dwc3 *dwc, + struct dwc3_ep *dep, const struct dwc3_event_depevt *event) +{ + unsigned status = 0; + int clean_busy; + u32 is_xfer_complete; + + is_xfer_complete = (event->endpoint_event == DWC3_DEPEVT_XFERCOMPLETE); + + if (event->status & DEPEVT_STATUS_BUSERR) + status = -ECONNRESET; + + clean_busy = dwc3_cleanup_done_reqs(dwc, dep, event, status); + if (clean_busy && (is_xfer_complete || + usb_endpoint_xfer_isoc(dep->endpoint.desc))) + dep->flags &= ~DWC3_EP_BUSY; + + /* + * WORKAROUND: This is the 2nd half of U1/U2 -> U0 workaround. + * See dwc3_gadget_linksts_change_interrupt() for 1st half. + */ + if (dwc->revision < DWC3_REVISION_183A) { + u32 reg; + int i; + + for (i = 0; i < DWC3_ENDPOINTS_NUM; i++) { + dep = dwc->eps[i]; + + if (!(dep->flags & DWC3_EP_ENABLED)) + continue; + + if (!list_empty(&dep->req_queued)) + return; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg |= dwc->u1u2; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + dwc->u1u2 = 0; + } +} + +static void dwc3_endpoint_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event) +{ + struct dwc3_ep *dep; + u8 epnum = event->endpoint_number; + + dep = dwc->eps[epnum]; + + if (!(dep->flags & DWC3_EP_ENABLED)) + return; + + if (epnum == 0 || epnum == 1) { + dwc3_ep0_interrupt(dwc, event); + return; + } + + dep->dbg_ep_events.total++; + + switch (event->endpoint_event) { + case DWC3_DEPEVT_XFERCOMPLETE: + dep->resource_index = 0; + dep->dbg_ep_events.xfercomplete++; + + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_dbg(dwc->dev, "%s is an Isochronous endpoint\n", + dep->name); + return; + } + + dwc3_endpoint_transfer_complete(dwc, dep, event); + break; + case DWC3_DEPEVT_XFERINPROGRESS: + dep->dbg_ep_events.xferinprogress++; + if (!usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dev_dbg(dwc->dev, "%s is not an Isochronous endpoint\n", + dep->name); + return; + } + + dwc3_endpoint_transfer_complete(dwc, dep, event); + break; + case DWC3_DEPEVT_XFERNOTREADY: + dep->dbg_ep_events.xfernotready++; + if (usb_endpoint_xfer_isoc(dep->endpoint.desc)) { + dwc3_gadget_start_isoc(dwc, dep, event); + } else { + int ret; + + dwc3_trace(trace_dwc3_gadget, "%s: reason %s", + dep->name, event->status & + DEPEVT_STATUS_TRANSFER_ACTIVE + ? "Transfer Active" + : "Transfer Not Active"); + + /* + * If XFERNOTREADY interrupt is received with event + * status as TRANSFER ACTIVE, don't kick next transfer. + * otherwise data stall is seen on that endpoint. + */ + if (event->status & DEPEVT_STATUS_TRANSFER_ACTIVE) + return; + + ret = __dwc3_gadget_kick_transfer(dep, 0, 1); + if (!ret || ret == -EBUSY) + return; + + dev_dbg(dwc->dev, "%s: failed to kick transfers\n", + dep->name); + } + + break; + case DWC3_DEPEVT_STREAMEVT: + dep->dbg_ep_events.streamevent++; + if (!usb_endpoint_xfer_bulk(dep->endpoint.desc)) { + dev_err(dwc->dev, "Stream event for non-Bulk %s\n", + dep->name); + return; + } + + switch (event->status) { + case DEPEVT_STREAMEVT_FOUND: + dwc3_trace(trace_dwc3_gadget, + "Stream %d found and started", + event->parameters); + + break; + case DEPEVT_STREAMEVT_NOTFOUND: + /* FALLTHROUGH */ + default: + dev_dbg(dwc->dev, "Couldn't find suitable stream\n"); + } + break; + case DWC3_DEPEVT_RXTXFIFOEVT: + dev_dbg(dwc->dev, "%s FIFO Overrun\n", dep->name); + dep->dbg_ep_events.rxtxfifoevent++; + break; + case DWC3_DEPEVT_EPCMDCMPLT: + dwc3_trace(trace_dwc3_gadget, "Endpoint Command Complete"); + dep->dbg_ep_events.epcmdcomplete++; + break; + } +} + +static void dwc3_disconnect_gadget(struct dwc3 *dwc) +{ + struct usb_gadget_driver *gadget_driver; + + if (dwc->gadget_driver && dwc->gadget_driver->disconnect) { + gadget_driver = dwc->gadget_driver; + spin_unlock(&dwc->lock); + dbg_event(0xFF, "DISCONNECT", 0); + gadget_driver->disconnect(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_suspend_gadget(struct dwc3 *dwc) +{ + struct usb_gadget_driver *gadget_driver; + + if (dwc->gadget_driver && dwc->gadget_driver->suspend) { + gadget_driver = dwc->gadget_driver; + spin_unlock(&dwc->lock); + dbg_event(0xFF, "SUSPEND", 0); + gadget_driver->suspend(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_resume_gadget(struct dwc3 *dwc) +{ + struct usb_gadget_driver *gadget_driver; + + if (dwc->gadget_driver && dwc->gadget_driver->resume) { + gadget_driver = dwc->gadget_driver; + spin_unlock(&dwc->lock); + dbg_event(0xFF, "RESUME", 0); + gadget_driver->resume(&dwc->gadget); + spin_lock(&dwc->lock); + } +} + +static void dwc3_reset_gadget(struct dwc3 *dwc) +{ + struct usb_gadget_driver *gadget_driver; + + if (!dwc->gadget_driver) + return; + + if (dwc->gadget.speed != USB_SPEED_UNKNOWN) { + gadget_driver = dwc->gadget_driver; + spin_unlock(&dwc->lock); + dbg_event(0xFF, "UDC RESET", 0); + usb_gadget_udc_reset(&dwc->gadget, gadget_driver); + spin_lock(&dwc->lock); + } +} + +void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force) +{ + struct dwc3_ep *dep; + struct dwc3_gadget_ep_cmd_params params; + u32 cmd; + int ret; + + dep = dwc->eps[epnum]; + + if (!dep->resource_index) + return; + + if (dep->endpoint.endless) + dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_DISABLE_UPDXFER, + dep->number); + + /* + * NOTICE: We are violating what the Databook says about the + * EndTransfer command. Ideally we would _always_ wait for the + * EndTransfer Command Completion IRQ, but that's causing too + * much trouble synchronizing between us and gadget driver. + * + * We have discussed this with the IP Provider and it was + * suggested to giveback all requests here, but give HW some + * extra time to synchronize with the interconnect. We're using + * an arbitrary 100us delay for that. + * + * Note also that a similar handling was tested by Synopsys + * (thanks a lot Paul) and nothing bad has come out of it. + * In short, what we're doing is: + * + * - Issue EndTransfer WITH CMDIOC bit set + * - Wait 100us + */ + + cmd = DWC3_DEPCMD_ENDTRANSFER; + cmd |= force ? DWC3_DEPCMD_HIPRI_FORCERM : 0; + cmd |= DWC3_DEPCMD_CMDIOC; + cmd |= DWC3_DEPCMD_PARAM(dep->resource_index); + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, cmd, ¶ms); + WARN_ON_ONCE(ret); + dep->resource_index = 0; + dep->flags &= ~DWC3_EP_BUSY; + udelay(100); +} + +static void dwc3_stop_active_transfers(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 2; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + struct dwc3_ep *dep; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_ENABLED)) + continue; + + dwc3_remove_requests(dwc, dep); + } +} + +static void dwc3_clear_stall_all_ep(struct dwc3 *dwc) +{ + u32 epnum; + + for (epnum = 1; epnum < DWC3_ENDPOINTS_NUM; epnum++) { + struct dwc3_ep *dep; + struct dwc3_gadget_ep_cmd_params params; + int ret; + + dep = dwc->eps[epnum]; + if (!dep) + continue; + + if (!(dep->flags & DWC3_EP_STALL)) + continue; + + dep->flags &= ~DWC3_EP_STALL; + + memset(¶ms, 0, sizeof(params)); + ret = dwc3_send_gadget_ep_cmd(dwc, dep->number, + DWC3_DEPCMD_CLEARSTALL, ¶ms); + if (ret) { + dev_dbg(dwc->dev, "%s; send ep cmd CLEARSTALL failed", + dep->name); + dbg_event(dep->number, "ECLRSTALL", ret); + } + } +} + +static void dwc3_gadget_disconnect_interrupt(struct dwc3 *dwc) +{ + int reg; + + dev_dbg(dwc->dev, "Notify OTG from %s\n", __func__); + dwc->b_suspend = false; + dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_OTG_EVENT, 0); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_INITU1ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + reg &= ~DWC3_DCTL_INITU2ENA; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + + dbg_event(0xFF, "DISCONNECT", 0); + dwc3_disconnect_gadget(dwc); + + dwc->gadget.speed = USB_SPEED_UNKNOWN; + dwc->setup_packet_pending = false; + dwc->link_state = DWC3_LINK_STATE_SS_DIS; + usb_gadget_set_state(&dwc->gadget, USB_STATE_NOTATTACHED); + wake_up_interruptible(&dwc->wait_linkstate); +} + +static void dwc3_gadget_reset_interrupt(struct dwc3 *dwc) +{ + u32 reg; + + /* + * WORKAROUND: DWC3 revisions <1.88a have an issue which + * would cause a missing Disconnect Event if there's a + * pending Setup Packet in the FIFO. + * + * There's no suggested workaround on the official Bug + * report, which states that "unless the driver/application + * is doing any special handling of a disconnect event, + * there is no functional issue". + * + * Unfortunately, it turns out that we _do_ some special + * handling of a disconnect event, namely complete all + * pending transfers, notify gadget driver of the + * disconnection, and so on. + * + * Our suggested workaround is to follow the Disconnect + * Event steps here, instead, based on a setup_packet_pending + * flag. Such flag gets set whenever we have a XferNotReady + * event on EP0 and gets cleared on XferComplete for the + * same endpoint. + * + * Refers to: + * + * STAR#9000466709: RTL: Device : Disconnect event not + * generated if setup packet pending in FIFO + */ + if (dwc->revision < DWC3_REVISION_188A) { + if (dwc->setup_packet_pending) + dwc3_gadget_disconnect_interrupt(dwc); + } + + dev_dbg(dwc->dev, "Notify OTG from %s\n", __func__); + dwc->b_suspend = false; + dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_OTG_EVENT, 0); + + dwc3_usb3_phy_suspend(dwc, false); + usb_gadget_vbus_draw(&dwc->gadget, 100); + + dwc3_reset_gadget(dwc); + dbg_event(0xFF, "BUS RST", 0); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_TSTCTRL_MASK; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + dwc->test_mode = false; + + /* + * From SNPS databook section 8.1.2 + * the EP0 should be in setup phase. So ensure + * that EP0 is in setup phase by issuing a stall + * and restart if EP0 is not in setup phase. + */ + if (dwc->ep0state != EP0_SETUP_PHASE) { + unsigned int dir; + + dbg_event(0xFF, "CONTRPEND", dwc->ep0state); + dir = !!dwc->ep0_expect_in; + if (dwc->ep0state == EP0_DATA_PHASE) + dwc3_ep0_end_control_data(dwc, dwc->eps[dir]); + else + dwc3_ep0_end_control_data(dwc, dwc->eps[!dir]); + dwc3_ep0_stall_and_restart(dwc); + } + + dwc3_stop_active_transfers(dwc); + dwc3_clear_stall_all_ep(dwc); + + /* Reset device address to zero */ + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg &= ~(DWC3_DCFG_DEVADDR_MASK); + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + dwc->gadget.speed = USB_SPEED_UNKNOWN; + dwc->link_state = DWC3_LINK_STATE_U0; + wake_up_interruptible(&dwc->wait_linkstate); +} + +static void dwc3_update_ram_clk_sel(struct dwc3 *dwc, u32 speed) +{ + u32 reg; + u32 usb30_clock = DWC3_GCTL_CLK_BUS; + + /* + * We change the clock only at SS but I dunno why I would want to do + * this. Maybe it becomes part of the power saving plan. + */ + + if (speed != DWC3_DSTS_SUPERSPEED) + return; + + /* + * RAMClkSel is reset to 0 after USB reset, so it must be reprogrammed + * each time on Connect Done. + */ + if (!usb30_clock) + return; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + reg |= DWC3_GCTL_RAMCLKSEL(usb30_clock); + dwc3_writel(dwc->regs, DWC3_GCTL, reg); +} + +static void dwc3_gadget_conndone_interrupt(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + u32 reg; + u8 speed; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + speed = reg & DWC3_DSTS_CONNECTSPD; + dwc->speed = speed; + + dwc3_update_ram_clk_sel(dwc, speed); + + switch (speed) { + case DWC3_DCFG_SUPERSPEED: + /* + * WORKAROUND: DWC3 revisions <1.90a have an issue which + * would cause a missing USB3 Reset event. + * + * In such situations, we should force a USB3 Reset + * event by calling our dwc3_gadget_reset_interrupt() + * routine. + * + * Refers to: + * + * STAR#9000483510: RTL: SS : USB3 reset event may + * not be generated always when the link enters poll + */ + if (dwc->revision < DWC3_REVISION_190A) + dwc3_gadget_reset_interrupt(dwc); + + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + dwc->gadget.ep0->maxpacket = 512; + dwc->gadget.speed = USB_SPEED_SUPER; + break; + case DWC3_DCFG_HIGHSPEED: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + dwc->gadget.ep0->maxpacket = 64; + dwc->gadget.speed = USB_SPEED_HIGH; + break; + case DWC3_DCFG_FULLSPEED2: + case DWC3_DCFG_FULLSPEED1: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(64); + dwc->gadget.ep0->maxpacket = 64; + dwc->gadget.speed = USB_SPEED_FULL; + break; + case DWC3_DCFG_LOWSPEED: + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(8); + dwc->gadget.ep0->maxpacket = 8; + dwc->gadget.speed = USB_SPEED_LOW; + break; + } + + dwc->eps[1]->endpoint.maxpacket = dwc->gadget.ep0->maxpacket; + + /* Enable USB2 LPM Capability */ + + if ((dwc->revision > DWC3_REVISION_194A) + && (speed != DWC3_DCFG_SUPERSPEED)) { + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + dwc3_writel(dwc->regs, DWC3_DCFG, reg); + + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~(DWC3_DCTL_HIRD_THRES_MASK | DWC3_DCTL_L1_HIBER_EN); + + reg |= DWC3_DCTL_HIRD_THRES(dwc->hird_threshold); + + /* + * When dwc3 revisions >= 2.40a, LPM Erratum is enabled and + * DCFG.LPMCap is set, core responses with an ACK and the + * BESL value in the LPM token is less than or equal to LPM + * NYET threshold. + */ + WARN_ONCE(dwc->revision < DWC3_REVISION_240A + && dwc->has_lpm_erratum, + "LPM Erratum not available on dwc3 revisisions < 2.40a\n"); + + if (dwc->has_lpm_erratum && dwc->revision >= DWC3_REVISION_240A) + reg |= DWC3_DCTL_LPM_ERRATA(dwc->lpm_nyet_threshold); + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } else { + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + reg &= ~DWC3_DCTL_HIRD_THRES_MASK; + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + } + + /* + * In HS mode this allows SS phy suspend. In SS mode this allows ss phy + * suspend in P3 state and generates IN_P3 power event irq. + */ + dwc3_usb3_phy_suspend(dwc, true); + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, + false); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + return; + } + + dep = dwc->eps[1]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, true, + false); + if (ret) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + return; + } + + dwc3_notify_event(dwc, DWC3_CONTROLLER_CONNDONE_EVENT, 0); + + /* + * Configure PHY via GUSB3PIPECTLn if required. + * + * Update GTXFIFOSIZn + * + * In both cases reset values should be sufficient. + */ +} + +static void dwc3_gadget_wakeup_interrupt(struct dwc3 *dwc, bool remote_wakeup) +{ + bool perform_resume = true; + + dev_dbg(dwc->dev, "%s\n", __func__); + + /* + * Identify if it is called from wakeup_interrupt() context for bus + * resume or as part of remote wakeup. And based on that check for + * U3 state. as we need to handle case of L1 resume i.e. where we + * don't want to perform resume. + */ + if (!remote_wakeup && dwc->link_state != DWC3_LINK_STATE_U3) + perform_resume = false; + + /* Only perform resume from L2 or Early Suspend states */ + if (perform_resume) { + dbg_event(0xFF, "WAKEUP", 0); + + /* + * In case of remote wake up dwc3_gadget_wakeup_work() + * is doing pm_runtime_get_sync(). + */ + dev_dbg(dwc->dev, "Notify OTG from %s\n", __func__); + dwc->b_suspend = false; + dwc3_notify_event(dwc, + DWC3_CONTROLLER_NOTIFY_OTG_EVENT, 0); + + /* + * set state to U0 as function level resume is trying to queue + * notification over USB interrupt endpoint which would fail + * due to state is not being updated. + */ + dwc->link_state = DWC3_LINK_STATE_U0; + dwc3_resume_gadget(dwc); + return; + } + + dwc->link_state = DWC3_LINK_STATE_U0; +} + +static void dwc3_gadget_linksts_change_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; + unsigned int pwropt; + + /* + * WORKAROUND: DWC3 < 2.50a have an issue when configured without + * Hibernation mode enabled which would show up when device detects + * host-initiated U3 exit. + * + * In that case, device will generate a Link State Change Interrupt + * from U3 to RESUME which is only necessary if Hibernation is + * configured in. + * + * There are no functional changes due to such spurious event and we + * just need to ignore it. + * + * Refers to: + * + * STAR#9000570034 RTL: SS Resume event generated in non-Hibernation + * operational mode + */ + pwropt = DWC3_GHWPARAMS1_EN_PWROPT(dwc->hwparams.hwparams1); + if ((dwc->revision < DWC3_REVISION_250A) && + (pwropt != DWC3_GHWPARAMS1_EN_PWROPT_HIB)) { + if ((dwc->link_state == DWC3_LINK_STATE_U3) && + (next == DWC3_LINK_STATE_RESUME)) { + dwc3_trace(trace_dwc3_gadget, + "ignoring transition U3 -> Resume"); + return; + } + } + + /* + * WORKAROUND: DWC3 Revisions <1.83a have an issue which, depending + * on the link partner, the USB session might do multiple entry/exit + * of low power states before a transfer takes place. + * + * Due to this problem, we might experience lower throughput. The + * suggested workaround is to disable DCTL[12:9] bits if we're + * transitioning from U1/U2 to U0 and enable those bits again + * after a transfer completes and there are no pending transfers + * on any of the enabled endpoints. + * + * This is the first half of that workaround. + * + * Refers to: + * + * STAR#9000446952: RTL: Device SS : if U1/U2 ->U0 takes >128us + * core send LGO_Ux entering U0 + */ + if (dwc->revision < DWC3_REVISION_183A) { + if (next == DWC3_LINK_STATE_U0) { + u32 u1u2; + u32 reg; + + switch (dwc->link_state) { + case DWC3_LINK_STATE_U1: + case DWC3_LINK_STATE_U2: + reg = dwc3_readl(dwc->regs, DWC3_DCTL); + u1u2 = reg & (DWC3_DCTL_INITU2ENA + | DWC3_DCTL_ACCEPTU2ENA + | DWC3_DCTL_INITU1ENA + | DWC3_DCTL_ACCEPTU1ENA); + + if (!dwc->u1u2) + dwc->u1u2 = reg & u1u2; + + reg &= ~u1u2; + + dwc3_writel(dwc->regs, DWC3_DCTL, reg); + break; + default: + /* do nothing */ + break; + } + } + } + + switch (next) { + case DWC3_LINK_STATE_U1: + if (dwc->speed == USB_SPEED_SUPER) + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_U2: + case DWC3_LINK_STATE_U3: + dwc3_suspend_gadget(dwc); + break; + case DWC3_LINK_STATE_RESUME: + dwc3_resume_gadget(dwc); + break; + default: + /* do nothing */ + break; + } + + dev_dbg(dwc->dev, "Going from (%d)--->(%d)\n", dwc->link_state, next); + dwc->link_state = next; + wake_up_interruptible(&dwc->wait_linkstate); +} + +static void dwc3_gadget_hibernation_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + unsigned int is_ss = evtinfo & BIT(4); + + /** + * WORKAROUND: DWC3 revison 2.20a with hibernation support + * have a known issue which can cause USB CV TD.9.23 to fail + * randomly. + * + * Because of this issue, core could generate bogus hibernation + * events which SW needs to ignore. + * + * Refers to: + * + * STAR#9000546576: Device Mode Hibernation: Issue in USB 2.0 + * Device Fallback from SuperSpeed + */ + if (is_ss ^ (dwc->speed == USB_SPEED_SUPER)) + return; + + /* enter hibernation here */ +} + +static void dwc3_gadget_suspend_interrupt(struct dwc3 *dwc, + unsigned int evtinfo) +{ + enum dwc3_link_state next = evtinfo & DWC3_LINK_STATE_MASK; + + dev_dbg(dwc->dev, "%s Entry to %d\n", __func__, next); + + if (dwc->link_state != next && next == DWC3_LINK_STATE_U3) { + /* + * When first connecting the cable, even before the initial + * DWC3_DEVICE_EVENT_RESET or DWC3_DEVICE_EVENT_CONNECT_DONE + * events, the controller sees a DWC3_DEVICE_EVENT_SUSPEND + * event. In such a case, ignore. + * Ignore suspend event until device side usb is not into + * CONFIGURED state. + */ + if (dwc->gadget.state != USB_STATE_CONFIGURED) { + pr_err("%s(): state:%d. Ignore SUSPEND.\n", + __func__, dwc->gadget.state); + return; + } + + dwc3_suspend_gadget(dwc); + + dev_dbg(dwc->dev, "Notify OTG from %s\n", __func__); + dwc->b_suspend = true; + dwc3_notify_event(dwc, DWC3_CONTROLLER_NOTIFY_OTG_EVENT, 0); + } + + dwc->link_state = next; + dwc3_trace(trace_dwc3_gadget, "link state %d", dwc->link_state); +} + +static void dwc3_dump_reg_info(struct dwc3 *dwc) +{ + dbg_event(0xFF, "REGDUMP", 0); + + dbg_print_reg("GUSB3PIPCTL", dwc3_readl(dwc->regs, + DWC3_GUSB3PIPECTL(0))); + dbg_print_reg("GUSB2PHYCONFIG", dwc3_readl(dwc->regs, + DWC3_GUSB2PHYCFG(0))); + dbg_print_reg("GCTL", dwc3_readl(dwc->regs, DWC3_GCTL)); + dbg_print_reg("GUCTL", dwc3_readl(dwc->regs, DWC3_GUCTL)); + dbg_print_reg("GDBGLTSSM", dwc3_readl(dwc->regs, DWC3_GDBGLTSSM)); + dbg_print_reg("DCFG", dwc3_readl(dwc->regs, DWC3_DCFG)); + dbg_print_reg("DCTL", dwc3_readl(dwc->regs, DWC3_DCTL)); + dbg_print_reg("DEVTEN", dwc3_readl(dwc->regs, DWC3_DEVTEN)); + dbg_print_reg("DSTS", dwc3_readl(dwc->regs, DWC3_DSTS)); + dbg_print_reg("DALPENA", dwc3_readl(dwc->regs, DWC3_DALEPENA)); + dbg_print_reg("DGCMD", dwc3_readl(dwc->regs, DWC3_DGCMD)); + + dbg_print_reg("OCFG", dwc3_readl(dwc->regs, DWC3_OCFG)); + dbg_print_reg("OCTL", dwc3_readl(dwc->regs, DWC3_OCTL)); + dbg_print_reg("OEVT", dwc3_readl(dwc->regs, DWC3_OEVT)); + dbg_print_reg("OSTS", dwc3_readl(dwc->regs, DWC3_OSTS)); +} + +static void dwc3_gadget_interrupt(struct dwc3 *dwc, + const struct dwc3_event_devt *event) +{ + switch (event->type) { + case DWC3_DEVICE_EVENT_DISCONNECT: + dwc3_gadget_disconnect_interrupt(dwc); + dwc->dbg_gadget_events.disconnect++; + break; + case DWC3_DEVICE_EVENT_RESET: + dwc3_gadget_reset_interrupt(dwc); + dwc->dbg_gadget_events.reset++; + break; + case DWC3_DEVICE_EVENT_CONNECT_DONE: + dwc3_gadget_conndone_interrupt(dwc); + dwc->dbg_gadget_events.connect++; + break; + case DWC3_DEVICE_EVENT_WAKEUP: + dwc3_gadget_wakeup_interrupt(dwc, false); + dwc->dbg_gadget_events.wakeup++; + break; + case DWC3_DEVICE_EVENT_HIBER_REQ: + if (dev_WARN_ONCE(dwc->dev, !dwc->has_hibernation, + "unexpected hibernation event\n")) + break; + + dwc3_gadget_hibernation_interrupt(dwc, event->event_info); + break; + case DWC3_DEVICE_EVENT_LINK_STATUS_CHANGE: + dwc3_gadget_linksts_change_interrupt(dwc, event->event_info); + dwc->dbg_gadget_events.link_status_change++; + break; + case DWC3_DEVICE_EVENT_SUSPEND: + if (dwc->revision < DWC3_REVISION_230A) { + dwc3_trace(trace_dwc3_gadget, "End of Periodic Frame"); + dwc->dbg_gadget_events.eopf++; + } else { + dwc3_trace(trace_dwc3_gadget, "U3/L1-L2 Suspend Event"); + dbg_event(0xFF, "GAD SUS", 0); + dwc->dbg_gadget_events.suspend++; + + /* + * Ignore suspend event if usb cable is not connected + * and speed is not being detected. + */ + if (dwc->gadget.speed != USB_SPEED_UNKNOWN && + dwc->vbus_active) + dwc3_gadget_suspend_interrupt(dwc, + event->event_info); + } + break; + case DWC3_DEVICE_EVENT_SOF: + dwc3_trace(trace_dwc3_gadget, "Start of Periodic Frame"); + dwc->dbg_gadget_events.sof++; + break; + case DWC3_DEVICE_EVENT_ERRATIC_ERROR: + dwc3_trace(trace_dwc3_gadget, "Erratic Error"); + if (!dwc->err_evt_seen) { + dbg_event(0xFF, "ERROR", 0); + dwc3_dump_reg_info(dwc); + } + dwc->dbg_gadget_events.erratic_error++; + break; + case DWC3_DEVICE_EVENT_CMD_CMPL: + dwc3_trace(trace_dwc3_gadget, "Command Complete"); + dwc->dbg_gadget_events.cmdcmplt++; + break; + case DWC3_DEVICE_EVENT_OVERFLOW: + dwc3_trace(trace_dwc3_gadget, "Overflow"); + dbg_event(0xFF, "OVERFL", 0); + dwc->dbg_gadget_events.overflow++; + break; + default: + dev_WARN(dwc->dev, "UNKNOWN IRQ %d\n", event->type); + dwc->dbg_gadget_events.unknown_event++; + } + + dwc->err_evt_seen = (event->type == DWC3_DEVICE_EVENT_ERRATIC_ERROR); +} + +static void dwc3_process_event_entry(struct dwc3 *dwc, + const union dwc3_event *event) +{ + trace_dwc3_event(event->raw); + + /* skip event processing in absence of vbus */ + if (!dwc->vbus_active) { + dbg_print_reg("SKIP EVT", event->raw); + return; + } + + /* If run/stop is cleared don't process any more events */ + if (!dwc->pullups_connected) { + dbg_print_reg("SKIP_EVT_PULLUP", event->raw); + return; + } + + /* Endpoint IRQ, handle it and return early */ + if (event->type.is_devspec == 0) { + /* depevt */ + return dwc3_endpoint_interrupt(dwc, &event->depevt); + } + + switch (event->type.type) { + case DWC3_EVENT_TYPE_DEV: + dwc3_gadget_interrupt(dwc, &event->devt); + break; + /* REVISIT what to do with Carkit and I2C events ? */ + default: + dev_err(dwc->dev, "UNKNOWN IRQ type %d\n", event->raw); + } +} + +static irqreturn_t dwc3_process_event_buf(struct dwc3 *dwc, u32 buf) +{ + struct dwc3_event_buffer *evt; + irqreturn_t ret = IRQ_NONE; + int left; + u32 reg; + + evt = dwc->ev_buffs[buf]; + left = evt->count; + + if (!(evt->flags & DWC3_EVENT_PENDING)) + return IRQ_NONE; + + while (left > 0) { + union dwc3_event event; + + event.raw = *(u32 *) (evt->buf + evt->lpos); + + dwc3_process_event_entry(dwc, &event); + + if (dwc->err_evt_seen) { + /* + * if erratic error, skip remaining events + * while controller undergoes reset + */ + evt->lpos = (evt->lpos + left) % + DWC3_EVENT_BUFFERS_SIZE; + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), left); + if (dwc3_notify_event(dwc, + DWC3_CONTROLLER_ERROR_EVENT, 0)) + dwc->err_evt_seen = 0; + break; + } + + /* + * FIXME we wrap around correctly to the next entry as + * almost all entries are 4 bytes in size. There is one + * entry which has 12 bytes which is a regular entry + * followed by 8 bytes data. ATM I don't know how + * things are organized if we get next to the a + * boundary so I worry about that once we try to handle + * that. + */ + evt->lpos = (evt->lpos + 4) % DWC3_EVENT_BUFFERS_SIZE; + left -= 4; + } + + dwc->bh_handled_evt_cnt[dwc->bh_dbg_index] += (evt->count / 4); + + evt->count = 0; + evt->flags &= ~DWC3_EVENT_PENDING; + ret = IRQ_HANDLED; + + /* Unmask interrupt */ + reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(buf)); + reg &= ~DWC3_GEVNTSIZ_INTMASK; + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(buf), reg); + + if (dwc->imod_interval) + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), + DWC3_GEVNTCOUNT_EHB); + + return ret; +} + +void dwc3_bh_work(struct work_struct *w) +{ + struct dwc3 *dwc = container_of(w, struct dwc3, bh_work); + + pm_runtime_get_sync(dwc->dev); + dwc3_thread_interrupt(dwc->irq, dwc); + pm_runtime_put(dwc->dev); +} + +static irqreturn_t dwc3_thread_interrupt(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + unsigned long flags; + irqreturn_t ret = IRQ_NONE; + int i; + unsigned temp_time; + ktime_t start_time; + + start_time = ktime_get(); + + spin_lock_irqsave(&dwc->lock, flags); + dwc->bh_handled_evt_cnt[dwc->bh_dbg_index] = 0; + + for (i = 0; i < dwc->num_normal_event_buffers; i++) + ret |= dwc3_process_event_buf(dwc, i); + + spin_unlock_irqrestore(&dwc->lock, flags); + + temp_time = ktime_to_us(ktime_sub(ktime_get(), start_time)); + dwc->bh_completion_time[dwc->bh_dbg_index] = temp_time; + dwc->bh_dbg_index = (dwc->bh_dbg_index + 1) % 10; + + return ret; +} + +static irqreturn_t dwc3_check_event_buf(struct dwc3 *dwc, u32 buf) +{ + struct dwc3_event_buffer *evt; + u32 count; + u32 reg; + + evt = dwc->ev_buffs[buf]; + + count = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(buf)); + count &= DWC3_GEVNTCOUNT_MASK; + if (!count) + return IRQ_NONE; + + if (count > evt->length) { + dbg_event(0xFF, "HUGE_EVCNT", count); + evt->lpos = (evt->lpos + count) % DWC3_EVENT_BUFFERS_SIZE; + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), count); + return IRQ_HANDLED; + } + + evt->count = count; + evt->flags |= DWC3_EVENT_PENDING; + + /* Mask interrupt */ + reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(buf)); + reg |= DWC3_GEVNTSIZ_INTMASK; + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(buf), reg); + + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(buf), count); + + return IRQ_WAKE_THREAD; +} + +irqreturn_t dwc3_interrupt(int irq, void *_dwc) +{ + struct dwc3 *dwc = _dwc; + int i; + irqreturn_t ret = IRQ_NONE; + unsigned temp_cnt = 0; + ktime_t start_time; + + start_time = ktime_get(); + dwc->irq_cnt++; + + /* controller reset is still pending */ + if (dwc->err_evt_seen) + return IRQ_HANDLED; + + for (i = 0; i < dwc->num_normal_event_buffers; i++) { + irqreturn_t status; + + status = dwc3_check_event_buf(dwc, i); + if (status == IRQ_WAKE_THREAD) + ret = status; + + temp_cnt += dwc->ev_buffs[i]->count; + } + + dwc->irq_start_time[dwc->irq_dbg_index] = start_time; + dwc->irq_completion_time[dwc->irq_dbg_index] = + ktime_us_delta(ktime_get(), start_time); + dwc->irq_event_count[dwc->irq_dbg_index] = temp_cnt / 4; + dwc->irq_dbg_index = (dwc->irq_dbg_index + 1) % MAX_INTR_STATS; + + if (ret == IRQ_WAKE_THREAD) + queue_work(dwc->dwc_wq, &dwc->bh_work); + + return IRQ_HANDLED; +} + +/** + * dwc3_gadget_init - Initializes gadget related registers + * @dwc: pointer to our controller context structure + * + * Returns 0 on success otherwise negative errno. + */ +int dwc3_gadget_init(struct dwc3 *dwc) +{ + int ret; + + INIT_WORK(&dwc->wakeup_work, dwc3_gadget_wakeup_work); + + dwc->ctrl_req = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ctrl_req), + &dwc->ctrl_req_addr, GFP_KERNEL); + if (!dwc->ctrl_req) { + dev_err(dwc->dev, "failed to allocate ctrl request\n"); + ret = -ENOMEM; + goto err0; + } + + dwc->ep0_trb = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ep0_trb) * 2, + &dwc->ep0_trb_addr, GFP_KERNEL); + if (!dwc->ep0_trb) { + dev_err(dwc->dev, "failed to allocate ep0 trb\n"); + ret = -ENOMEM; + goto err1; + } + + dwc->setup_buf = kzalloc(DWC3_EP0_BOUNCE_SIZE, GFP_KERNEL); + if (!dwc->setup_buf) { + ret = -ENOMEM; + goto err2; + } + + dwc->ep0_bounce = dma_alloc_coherent(dwc->dev, + DWC3_EP0_BOUNCE_SIZE, &dwc->ep0_bounce_addr, + GFP_KERNEL); + if (!dwc->ep0_bounce) { + dev_err(dwc->dev, "failed to allocate ep0 bounce buffer\n"); + ret = -ENOMEM; + goto err3; + } + + dwc->zlp_buf = kzalloc(DWC3_ZLP_BUF_SIZE, GFP_KERNEL); + if (!dwc->zlp_buf) { + ret = -ENOMEM; + goto err4; + } + + dwc->gadget.ops = &dwc3_gadget_ops; + dwc->gadget.speed = USB_SPEED_UNKNOWN; + dwc->gadget.sg_supported = true; + dwc->gadget.name = "dwc3-gadget"; + + /* + * FIXME We might be setting max_speed to <SUPER, however versions + * <2.20a of dwc3 have an issue with metastability (documented + * elsewhere in this driver) which tells us we can't set max speed to + * anything lower than SUPER. + * + * Because gadget.max_speed is only used by composite.c and function + * drivers (i.e. it won't go into dwc3's registers) we are allowing this + * to happen so we avoid sending SuperSpeed Capability descriptor + * together with our BOS descriptor as that could confuse host into + * thinking we can handle super speed. + * + * Note that, in fact, we won't even support GetBOS requests when speed + * is less than super speed because we don't have means, yet, to tell + * composite.c that we are USB 2.0 + LPM ECN. + */ + if (dwc->revision < DWC3_REVISION_220A) + dwc3_trace(trace_dwc3_gadget, + "Changing max_speed on rev %08x\n", + dwc->revision); + + dwc->gadget.max_speed = dwc->maximum_speed; + + /* + * Per databook, DWC3 needs buffer size to be aligned to MaxPacketSize + * on ep out. + */ + dwc->gadget.quirk_ep_out_aligned_size = true; + + /* + * REVISIT: Here we should clear all pending IRQs to be + * sure we're starting from a well known location. + */ + + ret = dwc3_gadget_init_endpoints(dwc); + if (ret) + goto err5; + + ret = usb_add_gadget_udc(dwc->dev, &dwc->gadget); + if (ret) { + dev_err(dwc->dev, "failed to register udc\n"); + goto err5; + } + + if (!dwc->is_drd) { + pm_runtime_no_callbacks(&dwc->gadget.dev); + pm_runtime_set_active(&dwc->gadget.dev); + pm_runtime_enable(&dwc->gadget.dev); + pm_runtime_get(&dwc->gadget.dev); + } + + return 0; + +err5: + kfree(dwc->zlp_buf); + +err4: + dwc3_gadget_free_endpoints(dwc); + dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, + dwc->ep0_bounce, dwc->ep0_bounce_addr); + +err3: + kfree(dwc->setup_buf); + +err2: + dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb) * 2, + dwc->ep0_trb, dwc->ep0_trb_addr); + +err1: + dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req), + dwc->ctrl_req, dwc->ctrl_req_addr); + +err0: + return ret; +} + +/* -------------------------------------------------------------------------- */ + +void dwc3_gadget_exit(struct dwc3 *dwc) +{ + if (dwc->is_drd) { + pm_runtime_put(&dwc->gadget.dev); + pm_runtime_disable(&dwc->gadget.dev); + } + + usb_del_gadget_udc(&dwc->gadget); + + dwc3_gadget_free_endpoints(dwc); + + dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE, + dwc->ep0_bounce, dwc->ep0_bounce_addr); + + kfree(dwc->setup_buf); + kfree(dwc->zlp_buf); + + dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb) * 2, + dwc->ep0_trb, dwc->ep0_trb_addr); + + dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req), + dwc->ctrl_req, dwc->ctrl_req_addr); +} + +int dwc3_gadget_suspend(struct dwc3 *dwc) +{ + if (!dwc->gadget_driver) + return 0; + + if (dwc->pullups_connected) { + dwc3_gadget_disable_irq(dwc); + dwc3_gadget_run_stop(dwc, true, true); + } + + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + + dwc->dcfg = dwc3_readl(dwc->regs, DWC3_DCFG); + + return 0; +} + +int dwc3_gadget_resume(struct dwc3 *dwc) +{ + struct dwc3_ep *dep; + int ret; + + if (!dwc->gadget_driver) + return 0; + + /* Start with SuperSpeed Default */ + dwc3_gadget_ep0_desc.wMaxPacketSize = cpu_to_le16(512); + + dep = dwc->eps[0]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); + if (ret) + goto err0; + + dep = dwc->eps[1]; + ret = __dwc3_gadget_ep_enable(dep, &dwc3_gadget_ep0_desc, NULL, false, + false); + if (ret) + goto err1; + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); + + dwc3_writel(dwc->regs, DWC3_DCFG, dwc->dcfg); + + if (dwc->pullups_connected) { + dwc3_gadget_enable_irq(dwc); + dwc3_gadget_run_stop(dwc, true, false); + } + + return 0; + +err1: + __dwc3_gadget_ep_disable(dwc->eps[0]); + +err0: + return ret; +} diff --git a/drivers/usb/dwc3/gadget.h b/drivers/usb/dwc3/gadget.h new file mode 100644 index 000000000000..bbf9b5096846 --- /dev/null +++ b/drivers/usb/dwc3/gadget.h @@ -0,0 +1,136 @@ +/** + * gadget.h - DesignWare USB3 DRD Gadget 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. + */ + +#ifndef __DRIVERS_USB_DWC3_GADGET_H +#define __DRIVERS_USB_DWC3_GADGET_H + +#include <linux/list.h> +#include <linux/usb/gadget.h> +#include "io.h" + +struct dwc3; +#define to_dwc3_ep(ep) (container_of(ep, struct dwc3_ep, endpoint)) +#define gadget_to_dwc(g) (container_of(g, struct dwc3, gadget)) + +/* DEPCFG parameter 1 */ +#define DWC3_DEPCFG_INT_NUM(n) (((n) & 0x1f) << 0) +#define DWC3_DEPCFG_XFER_COMPLETE_EN (1 << 8) +#define DWC3_DEPCFG_XFER_IN_PROGRESS_EN (1 << 9) +#define DWC3_DEPCFG_XFER_NOT_READY_EN (1 << 10) +#define DWC3_DEPCFG_FIFO_ERROR_EN (1 << 11) +#define DWC3_DEPCFG_STREAM_EVENT_EN (1 << 13) +#define DWC3_DEPCFG_BINTERVAL_M1(n) (((n) & 0xff) << 16) +#define DWC3_DEPCFG_STREAM_CAPABLE (1 << 24) +#define DWC3_DEPCFG_EP_NUMBER(n) (((n) & 0x1f) << 25) +#define DWC3_DEPCFG_BULK_BASED (1 << 30) +#define DWC3_DEPCFG_FIFO_BASED (1 << 31) + +/* DEPCFG parameter 0 */ +#define DWC3_DEPCFG_EP_TYPE(n) (((n) & 0x3) << 1) +#define DWC3_DEPCFG_MAX_PACKET_SIZE(n) (((n) & 0x7ff) << 3) +#define DWC3_DEPCFG_FIFO_NUMBER(n) (((n) & 0x1f) << 17) +#define DWC3_DEPCFG_BURST_SIZE(n) (((n) & 0xf) << 22) +#define DWC3_DEPCFG_DATA_SEQ_NUM(n) ((n) << 26) +/* This applies for core versions earlier than 1.94a */ +#define DWC3_DEPCFG_IGN_SEQ_NUM (1 << 31) +/* These apply for core versions 1.94a and later */ +#define DWC3_DEPCFG_ACTION_INIT (0 << 30) +#define DWC3_DEPCFG_ACTION_RESTORE (1 << 30) +#define DWC3_DEPCFG_ACTION_MODIFY (2 << 30) + +/* DEPXFERCFG parameter 0 */ +#define DWC3_DEPXFERCFG_NUM_XFER_RES(n) ((n) & 0xffff) + +/* -------------------------------------------------------------------------- */ + +#define to_dwc3_request(r) (container_of(r, struct dwc3_request, request)) + +static inline struct dwc3_request *next_request(struct list_head *list) +{ + if (list_empty(list)) + return NULL; + + return list_first_entry(list, struct dwc3_request, list); +} + +static inline void dwc3_gadget_move_request_list_front(struct dwc3_request *req) +{ + struct dwc3_ep *dep = req->dep; + + req->queued = false; + list_move(&req->list, &dep->request_list); +} + +static inline void dwc3_gadget_move_request_queued(struct dwc3_request *req) +{ + struct dwc3_ep *dep = req->dep; + + req->queued = true; + list_move_tail(&req->list, &dep->req_queued); +} + +static inline enum dwc3_link_state dwc3_get_link_state(struct dwc3 *dwc) +{ + u32 reg; + + reg = dwc3_readl(dwc->regs, DWC3_DSTS); + return DWC3_DSTS_USBLNKST(reg); +} + +void dwc3_gadget_giveback(struct dwc3_ep *dep, struct dwc3_request *req, + int status); + +void dwc3_ep0_interrupt(struct dwc3 *dwc, + const struct dwc3_event_depevt *event); +void dwc3_ep0_out_start(struct dwc3 *dwc); +void dwc3_ep0_end_control_data(struct dwc3 *dwc, struct dwc3_ep *dep); +void dwc3_ep0_stall_and_restart(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); +void dwc3_stop_active_transfer(struct dwc3 *dwc, u32 epnum, bool force); +irqreturn_t dwc3_interrupt(int irq, void *_dwc); +void dwc3_bh_work(struct work_struct *w); + +static inline dma_addr_t dwc3_trb_dma_offset(struct dwc3_ep *dep, + struct dwc3_trb *trb) +{ + u32 offset = (char *) trb - (char *) dep->trb_pool; + + return dep->trb_pool_dma + offset; +} + +/** + * dwc3_gadget_ep_get_transfer_index - Gets transfer index from HW + * @dwc: DesignWare USB3 Pointer + * @number: DWC endpoint number + * + * Caller should take care of locking + */ +static inline u32 dwc3_gadget_ep_get_transfer_index(struct dwc3 *dwc, u8 number) +{ + u32 res_id; + + res_id = dwc3_readl(dwc->regs, DWC3_DEPCMD(number)); + + return DWC3_DEPCMD_GET_RSC_IDX(res_id); +} + +#endif /* __DRIVERS_USB_DWC3_GADGET_H */ diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c new file mode 100644 index 000000000000..7f1ae5cf9909 --- /dev/null +++ b/drivers/usb/dwc3/host.c @@ -0,0 +1,88 @@ +/** + * host.c - DesignWare USB3 DRD Controller Host Glue + * + * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com + * + * Authors: 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. + */ + +#include <linux/platform_device.h> +#include <linux/usb/xhci_pdriver.h> + +#include "core.h" + +int dwc3_host_init(struct dwc3 *dwc) +{ + struct platform_device *xhci; + struct usb_xhci_pdata pdata; + int ret; + struct device_node *node = dwc->dev->of_node; + + xhci = platform_device_alloc("xhci-hcd", PLATFORM_DEVID_AUTO); + if (!xhci) { + dev_err(dwc->dev, "couldn't allocate xHCI device\n"); + return -ENOMEM; + } + + arch_setup_dma_ops(&xhci->dev, 0, 0, NULL, 0); + dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask); + + xhci->dev.parent = dwc->dev; + xhci->dev.dma_mask = dwc->dev->dma_mask; + xhci->dev.dma_parms = dwc->dev->dma_parms; + + dwc->xhci = xhci; + + ret = platform_device_add_resources(xhci, dwc->xhci_resources, + DWC3_XHCI_RESOURCES_NUM); + if (ret) { + dev_err(dwc->dev, "couldn't add resources to xHCI device\n"); + goto err1; + } + + memset(&pdata, 0, sizeof(pdata)); + + pdata.usb3_lpm_capable = dwc->usb3_lpm_capable; + + ret = of_property_read_u32(node, "xhci-imod-value", + &pdata.imod_interval); + if (ret) + pdata.imod_interval = 0; /* use default xhci.c value */ + + ret = platform_device_add_data(xhci, &pdata, sizeof(pdata)); + if (ret) { + dev_err(dwc->dev, "couldn't add platform data to xHCI device\n"); + goto err1; + } + + phy_create_lookup(dwc->usb2_generic_phy, "usb2-phy", + dev_name(&xhci->dev)); + phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy", + dev_name(&xhci->dev)); + + /* Platform device gets added as part of state machine */ + return 0; + +err1: + platform_device_put(xhci); + return ret; +} + +void dwc3_host_exit(struct dwc3 *dwc) +{ + phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy", + dev_name(&dwc->xhci->dev)); + phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy", + dev_name(&dwc->xhci->dev)); + if (!dwc->is_drd) + platform_device_unregister(dwc->xhci); +} diff --git a/drivers/usb/dwc3/io.h b/drivers/usb/dwc3/io.h new file mode 100644 index 000000000000..d797eb8728de --- /dev/null +++ b/drivers/usb/dwc3/io.h @@ -0,0 +1,70 @@ +/** + * io.h - DesignWare USB3 DRD IO 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. + */ + +#ifndef __DRIVERS_USB_DWC3_IO_H +#define __DRIVERS_USB_DWC3_IO_H + +#include <linux/io.h> +#include "trace.h" +#include "debug.h" +#include "core.h" + +static inline u32 dwc3_readl(void __iomem *base, u32 offset) +{ + u32 offs = offset - DWC3_GLOBALS_REGS_START; + u32 value; + + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + value = readl(base + offs); + + /* + * When tracing we want to make it easy to find the correct address on + * documentation, so we revert it back to the proper addresses, the + * same way they are described on SNPS documentation + */ + dwc3_trace(trace_dwc3_readl, "addr %pK value %08x", + base - DWC3_GLOBALS_REGS_START + offset, value); + + return value; +} + +static inline void dwc3_writel(void __iomem *base, u32 offset, u32 value) +{ + u32 offs = offset - DWC3_GLOBALS_REGS_START; + + /* + * We requested the mem region starting from the Globals address + * space, see dwc3_probe in core.c. + * However, the offsets are given starting from xHCI address space. + */ + writel(value, base + offs); + + /* + * When tracing we want to make it easy to find the correct address on + * documentation, so we revert it back to the proper addresses, the + * same way they are described on SNPS documentation + */ + dwc3_trace(trace_dwc3_writel, "addr %pK value %08x", + base - DWC3_GLOBALS_REGS_START + offset, value); +} + +#endif /* __DRIVERS_USB_DWC3_IO_H */ diff --git a/drivers/usb/dwc3/platform_data.h b/drivers/usb/dwc3/platform_data.h new file mode 100644 index 000000000000..2bb4d3ad0e6b --- /dev/null +++ b/drivers/usb/dwc3/platform_data.h @@ -0,0 +1,53 @@ +/** + * 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 tx_fifo_resize; + 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 tx_de_emphasis_quirk:1; + unsigned tx_de_emphasis:2; + + u32 fladj_value; + + const char *hsphy_interface; +}; diff --git a/drivers/usb/dwc3/trace.c b/drivers/usb/dwc3/trace.c new file mode 100644 index 000000000000..6cd166412ad0 --- /dev/null +++ b/drivers/usb/dwc3/trace.c @@ -0,0 +1,19 @@ +/** + * trace.c - DesignWare USB3 DRD Controller Trace Support + * + * Copyright (C) 2014 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. + */ + +#define CREATE_TRACE_POINTS +#include "trace.h" diff --git a/drivers/usb/dwc3/trace.h b/drivers/usb/dwc3/trace.h new file mode 100644 index 000000000000..225b2d4f9ecd --- /dev/null +++ b/drivers/usb/dwc3/trace.h @@ -0,0 +1,257 @@ +/** + * trace.h - DesignWare USB3 DRD Controller Trace Support + * + * Copyright (C) 2014 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. + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dwc3 + +#if !defined(__DWC3_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define __DWC3_TRACE_H + +#include <linux/types.h> +#include <linux/tracepoint.h> +#include <asm/byteorder.h> +#include "core.h" +#include "debug.h" + +DECLARE_EVENT_CLASS(dwc3_log_msg, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf), + TP_STRUCT__entry(__dynamic_array(char, msg, DWC3_MSG_MAX)), + TP_fast_assign( + vsnprintf(__get_str(msg), DWC3_MSG_MAX, vaf->fmt, *vaf->va); + ), + TP_printk("%s", __get_str(msg)) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_readl, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_writel, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_gadget, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_core, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DEFINE_EVENT(dwc3_log_msg, dwc3_ep0, + TP_PROTO(struct va_format *vaf), + TP_ARGS(vaf) +); + +DECLARE_EVENT_CLASS(dwc3_log_event, + TP_PROTO(u32 event), + TP_ARGS(event), + TP_STRUCT__entry( + __field(u32, event) + ), + TP_fast_assign( + __entry->event = event; + ), + TP_printk("event %08x", __entry->event) +); + +DEFINE_EVENT(dwc3_log_event, dwc3_event, + TP_PROTO(u32 event), + TP_ARGS(event) +); + +DECLARE_EVENT_CLASS(dwc3_log_ctrl, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl), + TP_STRUCT__entry( + __field(__u8, bRequestType) + __field(__u8, bRequest) + __field(__le16, wValue) + __field(__le16, wIndex) + __field(__le16, wLength) + ), + TP_fast_assign( + __entry->bRequestType = ctrl->bRequestType; + __entry->bRequest = ctrl->bRequest; + __entry->wValue = ctrl->wValue; + __entry->wIndex = ctrl->wIndex; + __entry->wLength = ctrl->wLength; + ), + TP_printk("bRequestType %02x bRequest %02x wValue %04x wIndex %04x wLength %d", + __entry->bRequestType, __entry->bRequest, + le16_to_cpu(__entry->wValue), le16_to_cpu(__entry->wIndex), + le16_to_cpu(__entry->wLength) + ) +); + +DEFINE_EVENT(dwc3_log_ctrl, dwc3_ctrl_req, + TP_PROTO(struct usb_ctrlrequest *ctrl), + TP_ARGS(ctrl) +); + +DECLARE_EVENT_CLASS(dwc3_log_request, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req), + TP_STRUCT__entry( + __dynamic_array(char, name, DWC3_MSG_MAX) + __field(struct dwc3_request *, req) + __field(unsigned, actual) + __field(unsigned, length) + __field(int, status) + ), + TP_fast_assign( + snprintf(__get_str(name), DWC3_MSG_MAX, "%s", req->dep->name); + __entry->req = req; + __entry->actual = req->request.actual; + __entry->length = req->request.length; + __entry->status = req->request.status; + ), + TP_printk("%s: req %pK length %u/%u ==> %d", + __get_str(name), __entry->req, __entry->actual, __entry->length, + __entry->status + ) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_alloc_request, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_free_request, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_ep_queue, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_ep_dequeue, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DEFINE_EVENT(dwc3_log_request, dwc3_gadget_giveback, + TP_PROTO(struct dwc3_request *req), + TP_ARGS(req) +); + +DECLARE_EVENT_CLASS(dwc3_log_generic_cmd, + TP_PROTO(unsigned int cmd, u32 param), + TP_ARGS(cmd, param), + TP_STRUCT__entry( + __field(unsigned int, cmd) + __field(u32, param) + ), + TP_fast_assign( + __entry->cmd = cmd; + __entry->param = param; + ), + TP_printk("cmd '%s' [%d] param %08x", + dwc3_gadget_generic_cmd_string(__entry->cmd), + __entry->cmd, __entry->param + ) +); + +DEFINE_EVENT(dwc3_log_generic_cmd, dwc3_gadget_generic_cmd, + TP_PROTO(unsigned int cmd, u32 param), + TP_ARGS(cmd, param) +); + +DECLARE_EVENT_CLASS(dwc3_log_gadget_ep_cmd, + TP_PROTO(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params), + TP_ARGS(dep, cmd, params), + TP_STRUCT__entry( + __dynamic_array(char, name, DWC3_MSG_MAX) + __field(unsigned int, cmd) + __field(u32, param0) + __field(u32, param1) + __field(u32, param2) + ), + TP_fast_assign( + snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __entry->cmd = cmd; + __entry->param0 = params->param0; + __entry->param1 = params->param1; + __entry->param2 = params->param2; + ), + TP_printk("%s: cmd '%s' [%d] params %08x %08x %08x", + __get_str(name), dwc3_gadget_ep_cmd_string(__entry->cmd), + __entry->cmd, __entry->param0, + __entry->param1, __entry->param2 + ) +); + +DEFINE_EVENT(dwc3_log_gadget_ep_cmd, dwc3_gadget_ep_cmd, + TP_PROTO(struct dwc3_ep *dep, unsigned int cmd, + struct dwc3_gadget_ep_cmd_params *params), + TP_ARGS(dep, cmd, params) +); + +DECLARE_EVENT_CLASS(dwc3_log_trb, + TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), + TP_ARGS(dep, trb), + TP_STRUCT__entry( + __dynamic_array(char, name, DWC3_MSG_MAX) + __field(struct dwc3_trb *, trb) + __field(u32, bpl) + __field(u32, bph) + __field(u32, size) + __field(u32, ctrl) + ), + TP_fast_assign( + snprintf(__get_str(name), DWC3_MSG_MAX, "%s", dep->name); + __entry->trb = trb; + __entry->bpl = trb->bpl; + __entry->bph = trb->bph; + __entry->size = trb->size; + __entry->ctrl = trb->ctrl; + ), + TP_printk("%s: trb %pK bph %08x bpl %08x size %08x ctrl %08x", + __get_str(name), __entry->trb, __entry->bph, __entry->bpl, + __entry->size, __entry->ctrl + ) +); + +DEFINE_EVENT(dwc3_log_trb, dwc3_prepare_trb, + TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), + TP_ARGS(dep, trb) +); + +DEFINE_EVENT(dwc3_log_trb, dwc3_complete_trb, + TP_PROTO(struct dwc3_ep *dep, struct dwc3_trb *trb), + TP_ARGS(dep, trb) +); + +#endif /* __DWC3_TRACE_H */ + +/* this part has to be here */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . + +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +#include <trace/define_trace.h> diff --git a/drivers/usb/dwc3/ulpi.c b/drivers/usb/dwc3/ulpi.c new file mode 100644 index 000000000000..ec004c6d76f2 --- /dev/null +++ b/drivers/usb/dwc3/ulpi.c @@ -0,0 +1,91 @@ +/** + * ulpi.c - DesignWare USB3 Controller's ULPI PHY interface + * + * Copyright (C) 2015 Intel Corporation + * + * Author: Heikki Krogerus <heikki.krogerus@linux.intel.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 as + * published by the Free Software Foundation. + */ + +#include <linux/ulpi/regs.h> + +#include "core.h" +#include "io.h" + +#define DWC3_ULPI_ADDR(a) \ + ((a >= ULPI_EXT_VENDOR_SPECIFIC) ? \ + DWC3_GUSB2PHYACC_ADDR(ULPI_ACCESS_EXTENDED) | \ + DWC3_GUSB2PHYACC_EXTEND_ADDR(a) : DWC3_GUSB2PHYACC_ADDR(a)) + +static int dwc3_ulpi_busyloop(struct dwc3 *dwc) +{ + unsigned count = 1000; + u32 reg; + + while (count--) { + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYACC(0)); + if (!(reg & DWC3_GUSB2PHYACC_BUSY)) + return 0; + cpu_relax(); + } + + return -ETIMEDOUT; +} + +static int dwc3_ulpi_read(struct ulpi_ops *ops, u8 addr) +{ + struct dwc3 *dwc = dev_get_drvdata(ops->dev); + u32 reg; + int ret; + + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); + dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg); + + ret = dwc3_ulpi_busyloop(dwc); + if (ret) + return ret; + + reg = dwc3_readl(dwc->regs, DWC3_GUSB2PHYACC(0)); + + return DWC3_GUSB2PHYACC_DATA(reg); +} + +static int dwc3_ulpi_write(struct ulpi_ops *ops, u8 addr, u8 val) +{ + struct dwc3 *dwc = dev_get_drvdata(ops->dev); + u32 reg; + + reg = DWC3_GUSB2PHYACC_NEWREGREQ | DWC3_ULPI_ADDR(addr); + reg |= DWC3_GUSB2PHYACC_WRITE | val; + dwc3_writel(dwc->regs, DWC3_GUSB2PHYACC(0), reg); + + return dwc3_ulpi_busyloop(dwc); +} + +static struct ulpi_ops dwc3_ulpi_ops = { + .read = dwc3_ulpi_read, + .write = dwc3_ulpi_write, +}; + +int dwc3_ulpi_init(struct dwc3 *dwc) +{ + /* Register the interface */ + dwc->ulpi = ulpi_register_interface(dwc->dev, &dwc3_ulpi_ops); + if (IS_ERR(dwc->ulpi)) { + dev_err(dwc->dev, "failed to register ULPI interface"); + return PTR_ERR(dwc->ulpi); + } + + return 0; +} + +void dwc3_ulpi_exit(struct dwc3 *dwc) +{ + if (dwc->ulpi) { + ulpi_unregister_interface(dwc->ulpi); + dwc->ulpi = NULL; + } +} |
