[TG3]: Add 5761 APE support

This patch adds support for the new APE block, present in 5761 chips.
APE stands for Application Processing Engine.  The primary function of
the APE is to process manageability traffic, such as ASF.

Signed-off-by: Matt Carlson <mcarlson@broadcom.com>
Signed-off-by: Michael Chan <mchan@broadcom.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/drivers/net/tg3.c b/drivers/net/tg3.c
index 3200c9c..5b6c1b2 100644
--- a/drivers/net/tg3.c
+++ b/drivers/net/tg3.c
@@ -314,6 +314,16 @@
 	return (readl(tp->regs + off));
 }
 
+static void tg3_ape_write32(struct tg3 *tp, u32 off, u32 val)
+{
+	writel(val, tp->aperegs + off);
+}
+
+static u32 tg3_ape_read32(struct tg3 *tp, u32 off)
+{
+	return (readl(tp->aperegs + off));
+}
+
 static void tg3_write_indirect_reg32(struct tg3 *tp, u32 off, u32 val)
 {
 	unsigned long flags;
@@ -500,6 +510,73 @@
 	spin_unlock_irqrestore(&tp->indirect_lock, flags);
 }
 
+static void tg3_ape_lock_init(struct tg3 *tp)
+{
+	int i;
+
+	/* Make sure the driver hasn't any stale locks. */
+	for (i = 0; i < 8; i++)
+		tg3_ape_write32(tp, TG3_APE_LOCK_GRANT + 4 * i,
+				APE_LOCK_GRANT_DRIVER);
+}
+
+static int tg3_ape_lock(struct tg3 *tp, int locknum)
+{
+	int i, off;
+	int ret = 0;
+	u32 status;
+
+	if (!(tp->tg3_flags3 & TG3_FLG3_ENABLE_APE))
+		return 0;
+
+	switch (locknum) {
+		case TG3_APE_LOCK_MEM:
+			break;
+		default:
+			return -EINVAL;
+	}
+
+	off = 4 * locknum;
+
+	tg3_ape_write32(tp, TG3_APE_LOCK_REQ + off, APE_LOCK_REQ_DRIVER);
+
+	/* Wait for up to 1 millisecond to acquire lock. */
+	for (i = 0; i < 100; i++) {
+		status = tg3_ape_read32(tp, TG3_APE_LOCK_GRANT + off);
+		if (status == APE_LOCK_GRANT_DRIVER)
+			break;
+		udelay(10);
+	}
+
+	if (status != APE_LOCK_GRANT_DRIVER) {
+		/* Revoke the lock request. */
+		tg3_ape_write32(tp, TG3_APE_LOCK_GRANT + off,
+				APE_LOCK_GRANT_DRIVER);
+
+		ret = -EBUSY;
+	}
+
+	return ret;
+}
+
+static void tg3_ape_unlock(struct tg3 *tp, int locknum)
+{
+	int off;
+
+	if (!(tp->tg3_flags3 & TG3_FLG3_ENABLE_APE))
+		return;
+
+	switch (locknum) {
+		case TG3_APE_LOCK_MEM:
+			break;
+		default:
+			return;
+	}
+
+	off = 4 * locknum;
+	tg3_ape_write32(tp, TG3_APE_LOCK_GRANT + off, APE_LOCK_GRANT_DRIVER);
+}
+
 static void tg3_disable_ints(struct tg3 *tp)
 {
 	tw32(TG3PCI_MISC_HOST_CTRL,
@@ -1448,7 +1525,8 @@
 	}
 
 	if (!(tp->tg3_flags & TG3_FLAG_WOL_ENABLE) &&
-	    !(tp->tg3_flags & TG3_FLAG_ENABLE_ASF))
+	    !(tp->tg3_flags & TG3_FLAG_ENABLE_ASF) &&
+	    !(tp->tg3_flags3 & TG3_FLG3_ENABLE_APE))
 		tg3_power_down_phy(tp);
 
 	tg3_frob_aux_power(tp);
@@ -4726,6 +4804,80 @@
 	}
 }
 
+static void tg3_ape_send_event(struct tg3 *tp, u32 event)
+{
+	int i;
+	u32 apedata;
+
+	apedata = tg3_ape_read32(tp, TG3_APE_SEG_SIG);
+	if (apedata != APE_SEG_SIG_MAGIC)
+		return;
+
+	apedata = tg3_ape_read32(tp, TG3_APE_FW_STATUS);
+	if (apedata != APE_FW_STATUS_READY)
+		return;
+
+	/* Wait for up to 1 millisecond for APE to service previous event. */
+	for (i = 0; i < 10; i++) {
+		if (tg3_ape_lock(tp, TG3_APE_LOCK_MEM))
+			return;
+
+		apedata = tg3_ape_read32(tp, TG3_APE_EVENT_STATUS);
+
+		if (!(apedata & APE_EVENT_STATUS_EVENT_PENDING))
+			tg3_ape_write32(tp, TG3_APE_EVENT_STATUS,
+					event | APE_EVENT_STATUS_EVENT_PENDING);
+
+		tg3_ape_unlock(tp, TG3_APE_LOCK_MEM);
+
+		if (!(apedata & APE_EVENT_STATUS_EVENT_PENDING))
+			break;
+
+		udelay(100);
+	}
+
+	if (!(apedata & APE_EVENT_STATUS_EVENT_PENDING))
+		tg3_ape_write32(tp, TG3_APE_EVENT, APE_EVENT_1);
+}
+
+static void tg3_ape_driver_state_change(struct tg3 *tp, int kind)
+{
+	u32 event;
+	u32 apedata;
+
+	if (!(tp->tg3_flags3 & TG3_FLG3_ENABLE_APE))
+		return;
+
+	switch (kind) {
+		case RESET_KIND_INIT:
+			tg3_ape_write32(tp, TG3_APE_HOST_SEG_SIG,
+					APE_HOST_SEG_SIG_MAGIC);
+			tg3_ape_write32(tp, TG3_APE_HOST_SEG_LEN,
+					APE_HOST_SEG_LEN_MAGIC);
+			apedata = tg3_ape_read32(tp, TG3_APE_HOST_INIT_COUNT);
+			tg3_ape_write32(tp, TG3_APE_HOST_INIT_COUNT, ++apedata);
+			tg3_ape_write32(tp, TG3_APE_HOST_DRIVER_ID,
+					APE_HOST_DRIVER_ID_MAGIC);
+			tg3_ape_write32(tp, TG3_APE_HOST_BEHAVIOR,
+					APE_HOST_BEHAV_NO_PHYLOCK);
+
+			event = APE_EVENT_STATUS_STATE_START;
+			break;
+		case RESET_KIND_SHUTDOWN:
+			event = APE_EVENT_STATUS_STATE_UNLOAD;
+			break;
+		case RESET_KIND_SUSPEND:
+			event = APE_EVENT_STATUS_STATE_SUSPEND;
+			break;
+		default:
+			return;
+	}
+
+	event |= APE_EVENT_STATUS_DRIVER_EVNT | APE_EVENT_STATUS_STATE_CHNGE;
+
+	tg3_ape_send_event(tp, event);
+}
+
 /* tp->lock is held. */
 static void tg3_write_sig_pre_reset(struct tg3 *tp, int kind)
 {
@@ -4753,6 +4905,10 @@
 			break;
 		};
 	}
+
+	if (kind == RESET_KIND_INIT ||
+	    kind == RESET_KIND_SUSPEND)
+		tg3_ape_driver_state_change(tp, kind);
 }
 
 /* tp->lock is held. */
@@ -4774,6 +4930,9 @@
 			break;
 		};
 	}
+
+	if (kind == RESET_KIND_SHUTDOWN)
+		tg3_ape_driver_state_change(tp, kind);
 }
 
 /* tp->lock is held. */
@@ -4864,6 +5023,10 @@
 	if (tp->pci_chip_rev_id == CHIPREV_ID_5704_A0 &&
 	    (tp->tg3_flags & TG3_FLAG_PCIX_MODE))
 		val |= PCISTATE_RETRY_SAME_DMA;
+	/* Allow reads and writes to the APE register and memory space. */
+	if (tp->tg3_flags3 & TG3_FLG3_ENABLE_APE)
+		val |= PCISTATE_ALLOW_APE_CTLSPC_WR |
+		       PCISTATE_ALLOW_APE_SHMEM_WR;
 	pci_write_config_dword(tp->pdev, TG3PCI_PCISTATE, val);
 
 	pci_write_config_dword(tp->pdev, TG3PCI_COMMAND, tp->pci_cmd);
@@ -5092,7 +5255,8 @@
 /* tp->lock is held. */
 static void tg3_stop_fw(struct tg3 *tp)
 {
-	if (tp->tg3_flags & TG3_FLAG_ENABLE_ASF) {
+	if ((tp->tg3_flags & TG3_FLAG_ENABLE_ASF) &&
+	   !(tp->tg3_flags3 & TG3_FLG3_ENABLE_APE)) {
 		u32 val;
 		int i;
 
@@ -6173,6 +6337,16 @@
 		tw32(TG3PCI_PCISTATE, val);
 	}
 
+	if (tp->tg3_flags3 & TG3_FLG3_ENABLE_APE) {
+		/* Allow reads and writes to the
+		 * APE register and memory space.
+		 */
+		val = tr32(TG3PCI_PCISTATE);
+		val |= PCISTATE_ALLOW_APE_CTLSPC_WR |
+		       PCISTATE_ALLOW_APE_SHMEM_WR;
+		tw32(TG3PCI_PCISTATE, val);
+	}
+
 	if (GET_CHIP_REV(tp->pci_chip_rev_id) == CHIPREV_5704_BX) {
 		/* Enable some hw fixes.  */
 		val = tr32(TG3PCI_MSI_DATA);
@@ -6780,6 +6954,10 @@
 		break;
 	};
 
+	/* Write our heartbeat update interval to APE. */
+	tg3_ape_write32(tp, TG3_APE_HOST_HEARTBEAT_INT_MS,
+			APE_HOST_HEARTBEAT_INT_DISABLE);
+
 	tg3_write_sig_post_reset(tp, RESET_KIND_INIT);
 
 	return 0;
@@ -10302,6 +10480,8 @@
 			if (tp->tg3_flags2 & TG3_FLG2_5750_PLUS)
 				tp->tg3_flags2 |= TG3_FLG2_ASF_NEW_HANDSHAKE;
 		}
+		if (nic_cfg & NIC_SRAM_DATA_CFG_APE_ENABLE)
+			tp->tg3_flags3 |= TG3_FLG3_ENABLE_APE;
 		if (tp->tg3_flags2 & TG3_FLG2_ANY_SERDES &&
 		    !(nic_cfg & NIC_SRAM_DATA_CFG_FIBER_WOL))
 			tp->tg3_flags &= ~TG3_FLAG_WOL_CAP;
@@ -10334,7 +10514,8 @@
 	 * firwmare access to the PHY hardware.
 	 */
 	err = 0;
-	if (tp->tg3_flags & TG3_FLAG_ENABLE_ASF) {
+	if ((tp->tg3_flags & TG3_FLAG_ENABLE_ASF) ||
+	    (tp->tg3_flags3 & TG3_FLG3_ENABLE_APE)) {
 		hw_phy_id = hw_phy_id_masked = PHY_ID_INVALID;
 	} else {
 		/* Now read the physical PHY_ID from the chip and verify
@@ -10381,6 +10562,7 @@
 	}
 
 	if (!(tp->tg3_flags2 & TG3_FLG2_ANY_SERDES) &&
+	    !(tp->tg3_flags3 & TG3_FLG3_ENABLE_APE) &&
 	    !(tp->tg3_flags & TG3_FLAG_ENABLE_ASF)) {
 		u32 bmsr, adv_reg, tg3_ctrl, mask;
 
@@ -10972,6 +11154,16 @@
 	 */
 	tg3_get_eeprom_hw_cfg(tp);
 
+	if (tp->tg3_flags3 & TG3_FLG3_ENABLE_APE) {
+		/* Allow reads and writes to the
+		 * APE register and memory space.
+		 */
+		pci_state_reg |= PCISTATE_ALLOW_APE_CTLSPC_WR |
+				 PCISTATE_ALLOW_APE_SHMEM_WR;
+		pci_write_config_dword(tp->pdev, TG3PCI_PCISTATE,
+				       pci_state_reg);
+	}
+
 	if (GET_ASIC_REV(tp->pci_chip_rev_id) == ASIC_REV_5784)
 		tp->tg3_flags |= TG3_FLAG_CPMU_PRESENT;
 
@@ -12165,13 +12357,35 @@
 
 	tg3_init_coal(tp);
 
+	if (tp->tg3_flags3 & TG3_FLG3_ENABLE_APE) {
+		if (!(pci_resource_flags(pdev, 2) & IORESOURCE_MEM)) {
+			printk(KERN_ERR PFX "Cannot find proper PCI device "
+			       "base address for APE, aborting.\n");
+			err = -ENODEV;
+			goto err_out_iounmap;
+		}
+
+		tg3reg_base = pci_resource_start(pdev, 2);
+		tg3reg_len = pci_resource_len(pdev, 2);
+
+		tp->aperegs = ioremap_nocache(tg3reg_base, tg3reg_len);
+		if (tp->aperegs == 0UL) {
+			printk(KERN_ERR PFX "Cannot map APE registers, "
+			       "aborting.\n");
+			err = -ENOMEM;
+			goto err_out_iounmap;
+		}
+
+		tg3_ape_lock_init(tp);
+	}
+
 	pci_set_drvdata(pdev, dev);
 
 	err = register_netdev(dev);
 	if (err) {
 		printk(KERN_ERR PFX "Cannot register net device, "
 		       "aborting.\n");
-		goto err_out_iounmap;
+		goto err_out_apeunmap;
 	}
 
 	printk(KERN_INFO "%s: Tigon3 [partno(%s) rev %04x PHY(%s)] (%s) %s Ethernet ",
@@ -12204,6 +12418,12 @@
 
 	return 0;
 
+err_out_apeunmap:
+	if (tp->aperegs) {
+		iounmap(tp->aperegs);
+		tp->aperegs = NULL;
+	}
+
 err_out_iounmap:
 	if (tp->regs) {
 		iounmap(tp->regs);
@@ -12231,6 +12451,10 @@
 
 		flush_scheduled_work();
 		unregister_netdev(dev);
+		if (tp->aperegs) {
+			iounmap(tp->aperegs);
+			tp->aperegs = NULL;
+		}
 		if (tp->regs) {
 			iounmap(tp->regs);
 			tp->regs = NULL;