xhci: Bad Ethernet performance plugged in ASM1042A host
authorJiahau Chang <jiahau@gmail.com>
Thu, 20 Jul 2017 11:48:27 +0000 (14:48 +0300)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 20 Jul 2017 12:40:35 +0000 (14:40 +0200)
When USB Ethernet is plugged in ASMEDIA ASM1042A xHCI host, bad
performance was manifesting in Web browser use (like download
large file such as ISO image). It is known limitation of
ASM1042A that is not compatible with driver scheduling,
As a workaround we can modify flow control handling of ASM1042A.
The register we modify is changes the behavior

[use quirk bit 28, usleep_range 40-60us, empty non-pci function -Mathias]
Cc: <stable@vger.kernel.org>
Signed-off-by: Jiahau Chang <Lars_chang@asmedia.com.tw>
Signed-off-by: Ian Pilcher <arequipeno@gmail.com>
Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/host/pci-quirks.c
drivers/usb/host/pci-quirks.h
drivers/usb/host/xhci-pci.c
drivers/usb/host/xhci.c
drivers/usb/host/xhci.h

index a9a1e4c40480cf2c5c7c7995aa5d337d3ef3ee87..c8989c62a2621b88cf8b9d0c3001a37a31d5e151 100644 (file)
 #define USB_INTEL_USB3_PSSEN   0xD8
 #define USB_INTEL_USB3PRM      0xDC
 
+/* ASMEDIA quirk use */
+#define ASMT_DATA_WRITE0_REG   0xF8
+#define ASMT_DATA_WRITE1_REG   0xFC
+#define ASMT_CONTROL_REG       0xE0
+#define ASMT_CONTROL_WRITE_BIT 0x02
+#define ASMT_WRITEREG_CMD      0x10423
+#define ASMT_FLOWCTL_ADDR      0xFA30
+#define ASMT_FLOWCTL_DATA      0xBA
+#define ASMT_PSEUDO_DATA       0
+
 /*
  * amd_chipset_gen values represent AMD different chipset generations
  */
@@ -412,6 +422,50 @@ void usb_amd_quirk_pll_disable(void)
 }
 EXPORT_SYMBOL_GPL(usb_amd_quirk_pll_disable);
 
+static int usb_asmedia_wait_write(struct pci_dev *pdev)
+{
+       unsigned long retry_count;
+       unsigned char value;
+
+       for (retry_count = 1000; retry_count > 0; --retry_count) {
+
+               pci_read_config_byte(pdev, ASMT_CONTROL_REG, &value);
+
+               if (value == 0xff) {
+                       dev_err(&pdev->dev, "%s: check_ready ERROR", __func__);
+                       return -EIO;
+               }
+
+               if ((value & ASMT_CONTROL_WRITE_BIT) == 0)
+                       return 0;
+
+               usleep_range(40, 60);
+       }
+
+       dev_warn(&pdev->dev, "%s: check_write_ready timeout", __func__);
+       return -ETIMEDOUT;
+}
+
+void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev)
+{
+       if (usb_asmedia_wait_write(pdev) != 0)
+               return;
+
+       /* send command and address to device */
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_WRITEREG_CMD);
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_FLOWCTL_ADDR);
+       pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
+
+       if (usb_asmedia_wait_write(pdev) != 0)
+               return;
+
+       /* send data to device */
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE0_REG, ASMT_FLOWCTL_DATA);
+       pci_write_config_dword(pdev, ASMT_DATA_WRITE1_REG, ASMT_PSEUDO_DATA);
+       pci_write_config_byte(pdev, ASMT_CONTROL_REG, ASMT_CONTROL_WRITE_BIT);
+}
+EXPORT_SYMBOL_GPL(usb_asmedia_modifyflowcontrol);
+
 void usb_amd_quirk_pll_enable(void)
 {
        usb_amd_quirk_pll(0);
index 0222195bd5b0e2a954ec8d2e841720b577c7282a..6559944801987728a1db6ba31f09db51b92362e3 100644 (file)
@@ -11,6 +11,7 @@ bool usb_amd_prefetch_quirk(void);
 void usb_amd_dev_put(void);
 void usb_amd_quirk_pll_disable(void);
 void usb_amd_quirk_pll_enable(void);
+void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev);
 void usb_enable_intel_xhci_ports(struct pci_dev *xhci_pdev);
 void usb_disable_xhci_ports(struct pci_dev *xhci_pdev);
 void sb800_prefetch(struct device *dev, int on);
@@ -18,6 +19,7 @@ void sb800_prefetch(struct device *dev, int on);
 struct pci_dev;
 static inline void usb_amd_quirk_pll_disable(void) {}
 static inline void usb_amd_quirk_pll_enable(void) {}
+static inline void usb_asmedia_modifyflowcontrol(struct pci_dev *pdev) {}
 static inline void usb_amd_dev_put(void) {}
 static inline void usb_disable_xhci_ports(struct pci_dev *xhci_pdev) {}
 static inline void sb800_prefetch(struct device *dev, int on) {}
index 53882e2babbb2ec3903aebdb14b9fd3a28482d47..5b0fa553c8bc940e88a6db731cf6dfeb0c9fb971 100644 (file)
@@ -59,6 +59,8 @@
 #define PCI_DEVICE_ID_AMD_PROMONTORYA_2                        0x43bb
 #define PCI_DEVICE_ID_AMD_PROMONTORYA_1                        0x43bc
 
+#define PCI_DEVICE_ID_ASMEDIA_1042A_XHCI               0x1142
+
 static const char hcd_name[] = "xhci_hcd";
 
 static struct hc_driver __read_mostly xhci_pci_hc_driver;
@@ -217,6 +219,10 @@ static void xhci_pci_quirks(struct device *dev, struct xhci_hcd *xhci)
                        pdev->device == 0x1142)
                xhci->quirks |= XHCI_TRUST_TX_LENGTH;
 
+       if (pdev->vendor == PCI_VENDOR_ID_ASMEDIA &&
+               pdev->device == PCI_DEVICE_ID_ASMEDIA_1042A_XHCI)
+               xhci->quirks |= XHCI_ASMEDIA_MODIFY_FLOWCONTROL;
+
        if (pdev->vendor == PCI_VENDOR_ID_TI && pdev->device == 0x8241)
                xhci->quirks |= XHCI_LIMIT_ENDPOINT_INTERVAL_7;
 
index 56f85df013dbd7201d48c7a0afc2e93041d6e851..51326425f9cc799c2d81d910e9d1a7575b4a8bbb 100644 (file)
@@ -198,6 +198,9 @@ int xhci_reset(struct xhci_hcd *xhci)
        if (ret)
                return ret;
 
+       if (xhci->quirks & XHCI_ASMEDIA_MODIFY_FLOWCONTROL)
+               usb_asmedia_modifyflowcontrol(to_pci_dev(xhci_to_hcd(xhci)->self.controller));
+
        xhci_dbg_trace(xhci, trace_xhci_dbg_init,
                         "Wait for controller to be ready for doorbell rings");
        /*
@@ -1085,6 +1088,9 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated)
        if ((xhci->quirks & XHCI_COMP_MODE_QUIRK) && !comp_timer_running)
                compliance_mode_recovery_timer_init(xhci);
 
+       if (xhci->quirks & XHCI_ASMEDIA_MODIFY_FLOWCONTROL)
+               usb_asmedia_modifyflowcontrol(to_pci_dev(hcd->self.controller));
+
        /* Re-enable port polling. */
        xhci_dbg(xhci, "%s: starting port polling.\n", __func__);
        set_bit(HCD_FLAG_POLL_RH, &xhci->shared_hcd->flags);
index 3c6da1f93c845a6373deffcbf81be9530b03b122..e3e935291ed6fcb57be0402000a4beac6161a9ce 100644 (file)
@@ -1820,6 +1820,7 @@ struct xhci_hcd {
 #define XHCI_BROKEN_PORT_PED   (1 << 25)
 #define XHCI_LIMIT_ENDPOINT_INTERVAL_7 (1 << 26)
 #define XHCI_U2_DISABLE_WAKE   (1 << 27)
+#define XHCI_ASMEDIA_MODIFY_FLOWCONTROL        (1 << 28)
 
        unsigned int            num_active_eps;
        unsigned int            limit_active_eps;