Merge from Quantenna SDK v37.4.15.62.

Change-Id: I8f8d15eee38a23c606cfdeb082b8924f103f532e
diff --git a/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.c b/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.c
index 09c8112..20ffa0a 100644
--- a/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.c
+++ b/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.c
@@ -84,12 +84,19 @@
 pci_ers_result_t qdpc_pcie_slot_reset(struct pci_dev *dev);
 static void qdpc_pcie_shutdown(struct pci_dev *pdev);
 
+#ifdef QTN_LINK_MONITOR
+static bool is_ep_reset = false;
+static int link_monitor(void *data);
+static struct task_struct *link_monitor_thread = NULL;
+#endif
 
 char qdpc_pcie_driver_name[] = "qdpc_host";
 
+#ifdef PCIE_HOTPLUG_SUPPORTED
 static struct pci_error_handlers qdpc_err_hdl = {
         .slot_reset = qdpc_pcie_slot_reset,
 };
+#endif
 
 static struct pci_driver qdpc_pcie_driver = {
 	.name     = qdpc_pcie_driver_name,
@@ -100,7 +107,9 @@
 	.suspend  = qdpc_pcie_suspend,
 	.resume  = qdpc_pcie_resume,
 #endif
+#ifdef PCIE_HOTPLUG_SUPPORTED
         .err_handler = &qdpc_err_hdl,
+#endif
 	.shutdown = qdpc_pcie_shutdown,
 };
 
@@ -604,7 +613,8 @@
 		msleep(5000);
 	}
 
-	if ( (suspend_mode == EP_SUSPEND_MODE_PWR_OFF) && 
+#ifdef PCIE_HOTPLUG_SUPPORTED
+	if ( (suspend_mode == EP_SUSPEND_MODE_PWR_OFF) &&
 	     (pdev->driver && pdev->driver->err_handler && pdev->driver->err_handler->slot_reset) ) {
 		printk("slot_reset in %s(), Device name: %s\n", __FUNCTION__, dev_name(&pdev->dev));
 		if(pdev->driver->err_handler->slot_reset(pdev) == PCI_ERS_RESULT_RECOVERED)
@@ -615,6 +625,7 @@
 			goto out;
 		}
 	}
+#endif
 
 	/* Set ep_ready to resume tx traffic */
 	priv->ep_ready = 1;
@@ -645,6 +656,10 @@
 		return ret;
 	}
 
+#ifdef QTN_LINK_MONITOR
+	link_monitor_thread = kthread_run(link_monitor, NULL, "link_monitor");
+#endif
+
 	return ret;
 }
 
@@ -653,6 +668,11 @@
 	/* Release netlink */
 	qdpc_platform_exit();
 
+#ifdef QTN_LINK_MONITOR
+	kthread_stop(link_monitor_thread);
+	link_monitor_thread = NULL;
+#endif
+
 	/* Unregister the pci driver with the device */
 	pci_unregister_driver(&qdpc_pcie_driver);
 
@@ -771,6 +791,99 @@
 	return;
 }
 
+#ifdef QTN_LINK_MONITOR
+static inline bool is_pcie_linkup(struct pci_dev *pdev)
+{
+	uint32_t cs = 0;
+
+	pci_read_config_dword(pdev, QDPC_VENDOR_ID_OFFSET, &cs);
+	if (cs == QDPC_LINK_UP) {
+		msleep(10000);
+		printk("%s: PCIe link up!\n", __func__);
+		return true;
+	}
+
+	return false;
+}
+
+static inline void qdpc_pcie_print_config_space(struct pci_dev *pdev)
+{
+	int i = 0;
+	uint32_t cs = 0;
+
+	/* Read PCIe configuration space header */
+	for (i = QDPC_VENDOR_ID_OFFSET; i <= QDPC_INT_LINE_OFFSET; i += QDPC_ROW_INCR_OFFSET) {
+		pci_read_config_dword(pdev, i, &cs);
+		printk("%s: pdev:0x%p config_space offset:0x%02x value:0x%08x\n", __func__, pdev, i, cs);
+	}
+	printk("\n");
+}
+
+static inline void qdpc_pcie_check_link(struct pci_dev *pdev, struct vmac_priv *priv)
+{
+	__iomem qdpc_pcie_bda_t *bda = priv->bda;
+	uint32_t cs = 0;
+
+	pci_read_config_dword(pdev, QDPC_VENDOR_ID_OFFSET, &cs);
+	/* Endian value will be all 1s if link went down */
+	if (readl(&bda->bda_pci_endian) == QDPC_LINK_DOWN) {
+		is_ep_reset = true;
+		printk("Reset detected\n");
+	}
+}
+
+static int link_monitor(void *data)
+{
+	struct net_device *ndev = NULL;
+	struct vmac_priv *priv = NULL;
+	__iomem qdpc_pcie_bda_t *bda = NULL;
+	struct pci_dev *pdev = NULL;
+	uint32_t cs = 0;
+
+	set_current_state(TASK_RUNNING);
+	while (!kthread_should_stop()) {
+		__set_current_state(TASK_INTERRUPTIBLE);
+		schedule();
+		set_current_state(TASK_RUNNING);
+
+		ndev = g_ndev;
+		priv = netdev_priv(ndev);
+		bda = priv->bda;
+		pdev = priv->pdev;
+
+#ifdef QDPC_CS_DEBUG
+		qdpc_pcie_print_config_space(pdev);
+		msleep(5000);
+#endif
+		/* Check if reset to EP occurred */
+		while (!pci_read_config_dword(pdev, QDPC_VENDOR_ID_OFFSET, &cs)) {
+
+			if (kthread_should_stop())
+				do_exit(0);
+
+			qdpc_pcie_check_link(pdev, priv);
+			if (is_ep_reset) {
+				is_ep_reset = false;
+				qdpc_pcie_remove(pdev);
+				printk("%s: Attempting to recover from EP reset\n", __func__);
+				break;
+			}
+			msleep(500);
+		}
+
+		while(!is_pcie_linkup(pdev)) {
+		}
+
+#ifdef QDPC_CS_DEBUG
+		qdpc_pcie_print_config_space(pdev);
+#endif
+
+		qdpc_pcie_probe(pdev, NULL);
+	}
+	do_exit(0);
+}
+#endif
+
 static int qdpc_bringup_fw(struct vmac_priv *priv)
 {
 	__iomem qdpc_pcie_bda_t  *bda = priv->bda;
@@ -838,6 +951,11 @@
 
 	PRINT_INFO("Connection established with Target BBIC4 board\n");
 
+#ifdef QTN_LINK_MONITOR
+	if (link_monitor_thread)
+		wake_up_process(link_monitor_thread);
+#endif
+
 	priv->init_thread = NULL;
 	do_exit(0);
 }
diff --git a/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.h b/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.h
index 3d39181..13c2548 100644
--- a/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.h
+++ b/drivers/net/wireless/quantenna/pcie2/host/common/qdpc_init.h
@@ -35,6 +35,8 @@
 #define  QDPC_PCIE_NUM_BARS               6
 
 /* PCIe Configuration Space Defines */
+/* Used to indicate CS is valid and link is up */
+#define	QDPC_LINK_UP	((QDPC_DEVICE_ID << 16) | QDPC_VENDOR_ID)
 #define	QDPC_LINK_DOWN	0xffffffff /* Used to indicate link went down */
 #define	QDPC_VENDOR_ID_OFFSET	0x00
 #define	QDPC_INT_LINE_OFFSET	0x3C
diff --git a/drivers/net/wireless/quantenna/pcie2/include/qdpc_version.h b/drivers/net/wireless/quantenna/pcie2/include/qdpc_version.h
index 3e81707..c99bfeb 100644
--- a/drivers/net/wireless/quantenna/pcie2/include/qdpc_version.h
+++ b/drivers/net/wireless/quantenna/pcie2/include/qdpc_version.h
@@ -1 +1 @@
-#define DRV_VERSION "v37.4.0.46"
+#define DRV_VERSION "v37.4.15.62"