usb: dwc3: add functions to set force mode
authorWu Liang feng <wulf@rock-chips.com>
Thu, 7 Apr 2016 10:31:50 +0000 (18:31 +0800)
committerHuang, Tao <huangtao@rock-chips.com>
Tue, 12 Apr 2016 03:34:59 +0000 (11:34 +0800)
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 <wulf@rock-chips.com>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/debugfs.c
drivers/usb/dwc3/gadget.c

index ca8e7257e13dbff993a1e17108e10e0cc343a812..c2e55ee60176eade2c1394c9867a2ab33d32cb54 100644 (file)
@@ -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)
index c5d7a5e799623389e93ec2dfbbe2429ce3b9a7bb..142e117d4ac52b6ebad3c442b3491397a252ebb8 100644 (file)
 #include <linux/usb/ch9.h>
 #include <linux/usb/gadget.h>
 #include <linux/usb/otg.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/platform_device.h>
+#include <linux/version.h>
 #include <linux/ulpi/interface.h>
 
 #include <linux/phy/phy.h>
 #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);
index 26eb34ce014425c447c1f75ef4772ecb400c88fd..0f7f702bcbeb0dc33be2e858f10add7a36d9aaf6 100644 (file)
@@ -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;
index 2363bad45af865dd2e7ee0c28e009fff1ae7a7d5..54d0a3e6b3cfa4a21591dcbb7e084086e5d16ddf 100644 (file)
@@ -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;
+}