Merge tag 'usb-6.1-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
[platform/kernel/linux-rpi.git] / drivers / thunderbolt / nhi.c
index 9c38035..4dce2ed 100644 (file)
 #define RING_TYPE(ring) ((ring)->is_tx ? "TX ring" : "RX ring")
 
 #define RING_FIRST_USABLE_HOPID        1
-
+/*
+ * Used with QUIRK_E2E to specify an unused HopID the Rx credits are
+ * transferred.
+ */
+#define RING_E2E_RESERVED_HOPID        RING_FIRST_USABLE_HOPID
 /*
  * Minimal number of vectors when we use MSI-X. Two for control channel
  * Rx/Tx and the rest four are for cross domain DMA paths.
@@ -38,7 +42,9 @@
 
 #define NHI_MAILBOX_TIMEOUT    500 /* ms */
 
+/* Host interface quirks */
 #define QUIRK_AUTO_CLEAR_INT   BIT(0)
+#define QUIRK_E2E              BIT(1)
 
 static int ring_interrupt_index(struct tb_ring *ring)
 {
@@ -458,8 +464,18 @@ static void ring_release_msix(struct tb_ring *ring)
 
 static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
 {
+       unsigned int start_hop = RING_FIRST_USABLE_HOPID;
        int ret = 0;
 
+       if (nhi->quirks & QUIRK_E2E) {
+               start_hop = RING_FIRST_USABLE_HOPID + 1;
+               if (ring->flags & RING_FLAG_E2E && !ring->is_tx) {
+                       dev_dbg(&nhi->pdev->dev, "quirking E2E TX HopID %u -> %u\n",
+                               ring->e2e_tx_hop, RING_E2E_RESERVED_HOPID);
+                       ring->e2e_tx_hop = RING_E2E_RESERVED_HOPID;
+               }
+       }
+
        spin_lock_irq(&nhi->lock);
 
        if (ring->hop < 0) {
@@ -469,7 +485,7 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
                 * Automatically allocate HopID from the non-reserved
                 * range 1 .. hop_count - 1.
                 */
-               for (i = RING_FIRST_USABLE_HOPID; i < nhi->hop_count; i++) {
+               for (i = start_hop; i < nhi->hop_count; i++) {
                        if (ring->is_tx) {
                                if (!nhi->tx_rings[i]) {
                                        ring->hop = i;
@@ -484,6 +500,11 @@ static int nhi_alloc_hop(struct tb_nhi *nhi, struct tb_ring *ring)
                }
        }
 
+       if (ring->hop > 0 && ring->hop < start_hop) {
+               dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop);
+               ret = -EINVAL;
+               goto err_unlock;
+       }
        if (ring->hop < 0 || ring->hop >= nhi->hop_count) {
                dev_warn(&nhi->pdev->dev, "invalid hop: %d\n", ring->hop);
                ret = -EINVAL;
@@ -1097,12 +1118,26 @@ static void nhi_shutdown(struct tb_nhi *nhi)
 
 static void nhi_check_quirks(struct tb_nhi *nhi)
 {
-       /*
-        * Intel hardware supports auto clear of the interrupt status
-        * reqister right after interrupt is being issued.
-        */
-       if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL)
+       if (nhi->pdev->vendor == PCI_VENDOR_ID_INTEL) {
+               /*
+                * Intel hardware supports auto clear of the interrupt
+                * status register right after interrupt is being
+                * issued.
+                */
                nhi->quirks |= QUIRK_AUTO_CLEAR_INT;
+
+               switch (nhi->pdev->device) {
+               case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_2C_NHI:
+               case PCI_DEVICE_ID_INTEL_FALCON_RIDGE_4C_NHI:
+                       /*
+                        * Falcon Ridge controller needs the end-to-end
+                        * flow control workaround to avoid losing Rx
+                        * packets when RING_FLAG_E2E is set.
+                        */
+                       nhi->quirks |= QUIRK_E2E;
+                       break;
+               }
+       }
 }
 
 static int nhi_check_iommu_pdev(struct pci_dev *pdev, void *data)