usb: dwc3: Implement interrupt moderation
authorJohn Youn <johnyoun@synopsys.com>
Mon, 14 Nov 2016 20:32:43 +0000 (12:32 -0800)
committerFelipe Balbi <felipe.balbi@linux.intel.com>
Fri, 18 Nov 2016 11:54:51 +0000 (13:54 +0200)
Implement interrupt moderation which allows the interrupt rate to be
throttled. To enable this feature the dwc->imod_interval must be set to
1 or greater. This value specifies the minimum inter-interrupt interval,
in 250 ns increments. A value of 0 disables interrupt moderation.

This applies for DWC_usb3 version 3.00a and higher and for DWC_usb31
version 1.20a and higher.

Signed-off-by: John Youn <johnyoun@synopsys.com>
Signed-off-by: Felipe Balbi <felipe.balbi@linux.intel.com>
drivers/usb/dwc3/core.c
drivers/usb/dwc3/core.h
drivers/usb/dwc3/gadget.c

index 87d0cfb..889dbab 100644 (file)
@@ -982,12 +982,28 @@ static void dwc3_get_properties(struct dwc3 *dwc)
        dwc->hird_threshold = hird_threshold
                | (dwc->is_utmi_l1_suspend << 4);
 
+       dwc->imod_interval = 0;
+}
+
+/* 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 void dwc3_check_params(struct dwc3 *dwc)
 {
        struct device *dev = dwc->dev;
 
+       /* Check for proper value of imod_interval */
+       if (dwc->imod_interval && !dwc3_has_imod(dwc)) {
+               dev_warn(dwc->dev, "Interrupt moderation not supported\n");
+               dwc->imod_interval = 0;
+       }
+
        /* Check the maximum_speed parameter */
        switch (dwc->maximum_speed) {
        case USB_SPEED_LOW:
index bf63756..ef81fa5 100644 (file)
@@ -67,6 +67,7 @@
 #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
 
 #define DWC3_DEPCMDPAR0                0x08
 #define DWC3_DEPCMD            0x0c
 
+#define DWC3_DEV_IMOD(n)       (0xca00 + (n * 0x4))
+
 /* OTG Registers */
 #define DWC3_OCFG              0xcc00
 #define DWC3_OCTL              0xcc04
 #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;
@@ -846,6 +854,8 @@ struct dwc3_scratchpad_array {
  *     1       - -3.5dB de-emphasis
  *     2       - No de-emphasis
  *     3       - Reserved
+ * @imod_interval: set the interrupt moderation interval in 250ns
+ *                 increments or 0 to disable.
  */
 struct dwc3 {
        struct usb_ctrlrequest  *ctrl_req;
@@ -933,6 +943,7 @@ struct dwc3 {
  */
 #define DWC3_REVISION_IS_DWC31         0x80000000
 #define DWC3_USB31_REVISION_110A       (0x3131302a | DWC3_REVISION_IS_DWC31)
+#define DWC3_USB31_REVISION_120A       (0x3132302a | DWC3_REVISION_IS_DWC31)
 
        enum dwc3_ep0_next      ep0_next_event;
        enum dwc3_ep0_state     ep0state;
@@ -991,6 +1002,8 @@ struct dwc3 {
 
        unsigned                tx_de_emphasis_quirk:1;
        unsigned                tx_de_emphasis:2;
+
+       u16                     imod_interval;
 };
 
 /* -------------------------------------------------------------------------- */
@@ -1162,6 +1175,8 @@ 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);
index 3d5ba41..e2416de 100644 (file)
@@ -1685,6 +1685,17 @@ static int __dwc3_gadget_start(struct dwc3 *dwc)
        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);
 
@@ -2847,6 +2858,11 @@ static irqreturn_t dwc3_process_event_buf(struct dwc3_event_buffer *evt)
        reg &= ~DWC3_GEVNTSIZ_INTMASK;
        dwc3_writel(dwc->regs, DWC3_GEVNTSIZ(0), reg);
 
+       if (dwc->imod_interval) {
+               dwc3_writel(dwc->regs, DWC3_GEVNTCOUNT(0), DWC3_GEVNTCOUNT_EHB);
+               dwc3_writel(dwc->regs, DWC3_DEV_IMOD(0), dwc->imod_interval);
+       }
+
        return ret;
 }