From: Wu Liang feng Date: Thu, 7 Apr 2016 10:31:50 +0000 (+0800) Subject: usb: dwc3: add functions to set force mode X-Git-Tag: firefly_0821_release~2847 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=9607f47dfec23c5773d74e45ed561859eabce2b7;p=firefly-linux-kernel-4.4.55.git usb: dwc3: add functions to set force mode Add functions to set force mode for host and device. These functions will check the current mode and only force if needed thus avoiding unnecessary force mode delays. It's useful for dwc3 controller on some platforms which don't support otg mode but support drd mode, like rk3399 platform. TEST=config dr_mode = "otg" in dts first, and do "echo device > mode" on usb debugfs dir to force device mode, do "echo host > mode" on usb debugfs dir to force host mode. Change-Id: I1f90ac9d1ee3daa19c1046b0e52fdfb8f2d2ad62 Signed-off-by: Wu Liang feng --- diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c index ca8e7257e13d..c2e55ee60176 100644 --- a/drivers/usb/dwc3/core.c +++ b/drivers/usb/dwc3/core.c @@ -121,7 +121,7 @@ static int dwc3_core_soft_reset(struct dwc3 *dwc) * dwc3_soft_reset - Issue soft reset * @dwc: Pointer to our controller context structure */ -static int dwc3_soft_reset(struct dwc3 *dwc) +int dwc3_soft_reset(struct dwc3 *dwc) { unsigned long timeout; u32 reg; @@ -265,7 +265,7 @@ static int dwc3_alloc_event_buffers(struct dwc3 *dwc, unsigned length) * * Returns 0 on success otherwise negative errno. */ -static int dwc3_event_buffers_setup(struct dwc3 *dwc) +int dwc3_event_buffers_setup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; int n; @@ -291,7 +291,7 @@ static int dwc3_event_buffers_setup(struct dwc3 *dwc) return 0; } -static void dwc3_event_buffers_cleanup(struct dwc3 *dwc) +void dwc3_event_buffers_cleanup(struct dwc3 *dwc) { struct dwc3_event_buffer *evt; int n; @@ -821,6 +821,76 @@ static void dwc3_core_exit_mode(struct dwc3 *dwc) } } +/* Returns true if the controller is capable of DRD. */ +bool dwc3_hw_is_drd(struct dwc3 *dwc) +{ + u32 op_mode = DWC3_GHWPARAMS0_USB3_MODE(dwc->hwparams.hwparams0); + + return (op_mode == DWC3_GHWPARAMS0_USB3_DRD); +} + +bool dwc3_force_mode(struct dwc3 *dwc, u32 mode) +{ + u32 reg; + unsigned long flags; + struct usb_hcd *hcd; + + /* + * Force mode has no effect if the hardware is not drd mode. + */ + if (!dwc3_hw_is_drd(dwc)) + return false; + /* + * If dr_mode is either peripheral or host only, there is no + * need to ever force the mode to the opposite mode. + */ + if (WARN_ON(mode == DWC3_GCTL_PRTCAP_DEVICE && + dwc->dr_mode == USB_DR_MODE_HOST)) + return false; + + if (WARN_ON(mode == DWC3_GCTL_PRTCAP_HOST && + dwc->dr_mode == USB_DR_MODE_PERIPHERAL)) + return false; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + if (DWC3_GCTL_PRTCAP(reg) == mode) + return false; + + hcd = dev_get_drvdata(&dwc->xhci->dev); + + switch (mode) { + case DWC3_GCTL_PRTCAP_DEVICE: + if (hcd->state != HC_STATE_HALT) { + usb_remove_hcd(hcd->shared_hcd); + usb_remove_hcd(hcd); + } + + spin_lock_irqsave(&dwc->lock, flags); + dwc3_set_mode(dwc, mode); + dwc3_gadget_restart(dwc, true); + spin_unlock_irqrestore(&dwc->lock, flags); + + break; + case DWC3_GCTL_PRTCAP_HOST: + spin_lock_irqsave(&dwc->lock, flags); + dwc3_gadget_restart(dwc, false); + dwc3_set_mode(dwc, mode); + spin_unlock_irqrestore(&dwc->lock, flags); + + if (hcd->state == HC_STATE_HALT) { + usb_add_hcd(hcd, hcd->irq, IRQF_SHARED); + usb_add_hcd(hcd->shared_hcd, hcd->irq, IRQF_SHARED); + } + + break; + default: + /* do nothing */ + break; + } + + return true; +} + #define DWC3_ALIGN_MASK (16 - 1) static int dwc3_probe(struct platform_device *pdev) diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h index c5d7a5e79962..142e117d4ac5 100644 --- a/drivers/usb/dwc3/core.h +++ b/drivers/usb/dwc3/core.h @@ -30,6 +30,10 @@ #include #include #include +#include +#include +#include +#include #include #include @@ -219,6 +223,10 @@ #define DWC3_GEVNTSIZ_INTMASK (1 << 31) #define DWC3_GEVNTSIZ_SIZE(n) ((n) & 0xffff) +/* Global HWPARAMS0 Register */ +#define DWC3_GHWPARAMS0_USB3_MODE(n) ((n) & 7) +#define DWC3_GHWPARAMS0_USB3_DRD 2 + /* Global HWPARAMS1 Register */ #define DWC3_GHWPARAMS1_EN_PWROPT(n) (((n) & (3 << 24)) >> 24) #define DWC3_GHWPARAMS1_EN_PWROPT_NO 0 @@ -714,6 +722,7 @@ struct dwc3_scratchpad_array { * 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 + * @enabled: true when gadget driver is ready * @pullups_connected: true when Run/Stop bit is set * @resize_fifos: tells us it's ok to reconfigure our TxFIFO sizes. * @setup_packet_pending: true when there's a Setup Packet in FIFO. Workaround @@ -867,6 +876,7 @@ struct dwc3 { unsigned is_utmi_l1_suspend:1; unsigned is_fpga:1; unsigned needs_fifo_resize:1; + unsigned enabled:1; unsigned pullups_connected:1; unsigned resize_fifos:1; unsigned setup_packet_pending:1; @@ -1040,6 +1050,11 @@ struct dwc3_gadget_ep_cmd_params { /* prototypes */ void dwc3_set_mode(struct dwc3 *dwc, u32 mode); int dwc3_gadget_resize_tx_fifos(struct dwc3 *dwc); +int dwc3_soft_reset(struct dwc3 *dwc); +int dwc3_event_buffers_setup(struct dwc3 *dwc); +void dwc3_event_buffers_cleanup(struct dwc3 *dwc); +int dwc3_gadget_restart(struct dwc3 *dwc, bool start); +bool dwc3_force_mode(struct dwc3 *dwc, u32 mode); #if IS_ENABLED(CONFIG_USB_DWC3_HOST) || IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE) int dwc3_host_init(struct dwc3 *dwc); diff --git a/drivers/usb/dwc3/debugfs.c b/drivers/usb/dwc3/debugfs.c index 26eb34ce0144..0f7f702bcbeb 100644 --- a/drivers/usb/dwc3/debugfs.c +++ b/drivers/usb/dwc3/debugfs.c @@ -402,11 +402,17 @@ static ssize_t dwc3_mode_write(struct file *file, if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) return -EFAULT; - if (!strncmp(buf, "host", 4)) + if (!strncmp(buf, "host", 4)) { mode |= DWC3_GCTL_PRTCAP_HOST; + dwc3_force_mode(dwc, mode); + return count; + } - if (!strncmp(buf, "device", 6)) + if (!strncmp(buf, "device", 6)) { mode |= DWC3_GCTL_PRTCAP_DEVICE; + dwc3_force_mode(dwc, mode); + return count; + } if (!strncmp(buf, "otg", 3)) mode |= DWC3_GCTL_PRTCAP_OTG; diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c index 2363bad45af8..54d0a3e6b3cf 100644 --- a/drivers/usb/dwc3/gadget.c +++ b/drivers/usb/dwc3/gadget.c @@ -1568,12 +1568,20 @@ static int dwc3_gadget_pullup(struct usb_gadget *g, int is_on) { struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; - int ret; + int ret = 0; + u32 reg; is_on = !!is_on; spin_lock_irqsave(&dwc->lock, flags); - ret = dwc3_gadget_run_stop(dwc, is_on, false); + + dwc->enabled = is_on; + + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + + if (DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_DEVICE) + ret = dwc3_gadget_run_stop(dwc, is_on, false); + spin_unlock_irqrestore(&dwc->lock, flags); return ret; @@ -1637,6 +1645,10 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc->gadget_driver = driver; + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + if (DWC3_GCTL_PRTCAP(reg) != DWC3_GCTL_PRTCAP_DEVICE) + goto mode_mismatch; + reg = dwc3_readl(dwc->regs, DWC3_DCFG); reg &= ~(DWC3_DCFG_SPEED_MASK); @@ -1699,6 +1711,7 @@ static int dwc3_gadget_start(struct usb_gadget *g, dwc3_gadget_enable_irq(dwc); +mode_mismatch: spin_unlock_irqrestore(&dwc->lock, flags); return 0; @@ -1723,12 +1736,17 @@ static int dwc3_gadget_stop(struct usb_gadget *g) struct dwc3 *dwc = gadget_to_dwc(g); unsigned long flags; int irq; + u32 reg; spin_lock_irqsave(&dwc->lock, flags); - dwc3_gadget_disable_irq(dwc); - __dwc3_gadget_ep_disable(dwc->eps[0]); - __dwc3_gadget_ep_disable(dwc->eps[1]); + reg = dwc3_readl(dwc->regs, DWC3_GCTL); + + if (DWC3_GCTL_PRTCAP(reg) == DWC3_GCTL_PRTCAP_DEVICE) { + dwc3_gadget_disable_irq(dwc); + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + } dwc->gadget_driver = NULL; @@ -2969,3 +2987,252 @@ err1: err0: return ret; } + +static int dwc3_gadget_reinit(struct dwc3 *dwc) +{ + u32 hwparams4 = dwc->hwparams.hwparams4; + u32 reg; + int ret; + struct dwc3_ep *dep = NULL; + + 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->maximum_speed == USB_SPEED_SUPER) + dwc->maximum_speed = USB_SPEED_HIGH; + } + + /* 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: + dwc3_trace(trace_dwc3_core, + "No power optimization available\n"); + } + + /* check if current dwc3 is on simulation board */ + if (dwc->hwparams.hwparams6 & DWC3_GHWPARAMS6_EN_FPGA) { + dwc3_trace(trace_dwc3_core, + "running on FPGA platform\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_writel(dwc->regs, DWC3_GCTL, reg); + + ret = dwc3_event_buffers_setup(dwc); + + if (ret) { + dev_err(dwc->dev, "failed to setup event buffers\n"); + goto err1; + } + + reg = dwc3_readl(dwc->regs, DWC3_DCFG); + reg |= DWC3_DCFG_LPM_CAP; + 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); + + /* 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) { + dev_err(dwc->dev, "failed to enable %s\n", dep->name); + goto err1; + } + + dep = dwc->eps[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); + goto err2; + } + + /* begin to receive SETUP packets */ + dwc->ep0state = EP0_SETUP_PHASE; + dwc3_ep0_out_start(dwc); + + return 0; +err2: + __dwc3_gadget_ep_disable(dwc->eps[0]); +err1: + dwc3_event_buffers_cleanup(dwc); +err0: + return ret; +} + +int dwc3_gadget_restart(struct dwc3 *dwc, bool start) +{ + struct dwc3_event_buffer *evt; + int ret = 0; + int i; + u32 reg; + + if (start) { + ret = dwc3_gadget_reinit(dwc); + if (ret < 0) { + dev_err(dwc->dev, + "dwc3 gadget reinit error = %d\n", ret); + goto err; + } + + if (dwc->enabled) { + ret = dwc3_gadget_run_stop(dwc, start, false); + if (ret < 0) { + dev_err(dwc->dev, + "dwc3 gadget run stop err = %d\n", ret); + goto err; + } + } + dwc3_gadget_enable_irq(dwc); + } else { + /* + * Per databook, DEVCTRLHLT bit setting requires + * interrupts to be acknowledged. so acknowledge + * the events that are generated (by writing to + * GEVNTCOUNTn) first. And we also mask interrupts + * and clear SW states to avoid generating other + * interrupts after do gadget disconnnect operation. + */ + dwc3_gadget_disable_irq(dwc); + + for (i = 0; i < dwc->num_event_buffers; i++) { + evt = dwc->ev_buffs[i]; + evt->count = 0; + evt->flags &= ~DWC3_EVENT_PENDING; + reg = dwc3_readl(dwc->regs, DWC3_GEVNTSIZ(i)); + reg |= DWC3_GEVNTSIZ_INTMASK; + dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(i), reg); + reg = dwc3_readl(dwc->regs, DWC3_GEVNTCOUNT(i)); + dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(i), reg); + } + + /* + * DEVCTRLHLT bit sometimes does not get set + * even when GEVNTCOUNT is acked so do not + * care run stop function return value. + */ + dwc3_gadget_run_stop(dwc, start, false); + + if (dwc->gadget.state != USB_STATE_NOTATTACHED) + dwc3_gadget_disconnect_interrupt(dwc); + + __dwc3_gadget_ep_disable(dwc->eps[0]); + __dwc3_gadget_ep_disable(dwc->eps[1]); + } + +err: + return ret; +}