mv643xx_eth: use the SMI done interrupt to wait for SMI access completion

If the platform code has passed us the IRQ number of the mv643xx_eth
top-level error interrupt, use the error interrupt to wait for SMI
access completion instead of polling the SMI busy bit, since SMI bus
accesses can take up to tens of milliseconds.

Signed-off-by: Lennert Buytenhek <buytenh@marvell.com>
diff --git a/drivers/net/mv643xx_eth.c b/drivers/net/mv643xx_eth.c
index 2d43401..b41860d 100644
--- a/drivers/net/mv643xx_eth.c
+++ b/drivers/net/mv643xx_eth.c
@@ -72,6 +72,13 @@
  */
 #define PHY_ADDR			0x0000
 #define SMI_REG				0x0004
+#define  SMI_BUSY			0x10000000
+#define  SMI_READ_VALID			0x08000000
+#define  SMI_OPCODE_READ		0x04000000
+#define  SMI_OPCODE_WRITE		0x00000000
+#define ERR_INT_CAUSE			0x0080
+#define  ERR_INT_SMI_DONE		0x00000010
+#define ERR_INT_MASK			0x0084
 #define WINDOW_BASE(w)			(0x0200 + ((w) << 3))
 #define WINDOW_SIZE(w)			(0x0204 + ((w) << 3))
 #define WINDOW_REMAP_HIGH(w)		(0x0280 + ((w) << 2))
@@ -254,6 +261,15 @@
 	struct mutex phy_lock;
 
 	/*
+	 * If we have access to the error interrupt pin (which is
+	 * somewhat misnamed as it not only reflects internal errors
+	 * but also reflects SMI completion), use that to wait for
+	 * SMI access completion instead of polling the SMI busy bit.
+	 */
+	int err_interrupt;
+	wait_queue_head_t smi_busy_wait;
+
+	/*
 	 * Per-port MBUS window access register value.
 	 */
 	u32 win_protect;
@@ -979,68 +995,103 @@
 
 
 /* mii management interface *************************************************/
-#define SMI_BUSY		0x10000000
-#define SMI_READ_VALID		0x08000000
-#define SMI_OPCODE_READ		0x04000000
-#define SMI_OPCODE_WRITE	0x00000000
-
-static void smi_reg_read(struct mv643xx_eth_private *mp, unsigned int addr,
-			 unsigned int reg, unsigned int *value)
+static irqreturn_t mv643xx_eth_err_irq(int irq, void *dev_id)
 {
-	void __iomem *smi_reg = mp->shared_smi->base + SMI_REG;
-	int i;
+	struct mv643xx_eth_shared_private *msp = dev_id;
 
-	/* the SMI register is a shared resource */
-	mutex_lock(&mp->shared_smi->phy_lock);
+	if (readl(msp->base + ERR_INT_CAUSE) & ERR_INT_SMI_DONE) {
+		writel(~ERR_INT_SMI_DONE, msp->base + ERR_INT_CAUSE);
+		wake_up(&msp->smi_busy_wait);
+		return IRQ_HANDLED;
+	}
 
-	/* wait for the SMI register to become available */
-	for (i = 0; readl(smi_reg) & SMI_BUSY; i++) {
-		if (i == 1000) {
-			printk("%s: PHY busy timeout\n", mp->dev->name);
-			goto out;
+	return IRQ_NONE;
+}
+
+static int smi_is_done(struct mv643xx_eth_shared_private *msp)
+{
+	return !(readl(msp->base + SMI_REG) & SMI_BUSY);
+}
+
+static int smi_wait_ready(struct mv643xx_eth_shared_private *msp)
+{
+	if (msp->err_interrupt == NO_IRQ) {
+		int i;
+
+		for (i = 0; !smi_is_done(msp); i++) {
+			if (i == 10)
+				return -ETIMEDOUT;
+			msleep(10);
 		}
-		udelay(10);
+
+		return 0;
+	}
+
+	if (!wait_event_timeout(msp->smi_busy_wait, smi_is_done(msp),
+				msecs_to_jiffies(100)))
+		return -ETIMEDOUT;
+
+	return 0;
+}
+
+static int smi_reg_read(struct mv643xx_eth_private *mp,
+			unsigned int addr, unsigned int reg)
+{
+	struct mv643xx_eth_shared_private *msp = mp->shared_smi;
+	void __iomem *smi_reg = msp->base + SMI_REG;
+	int ret;
+
+	mutex_lock(&msp->phy_lock);
+
+	if (smi_wait_ready(msp)) {
+		printk("%s: SMI bus busy timeout\n", mp->dev->name);
+		ret = -ETIMEDOUT;
+		goto out;
 	}
 
 	writel(SMI_OPCODE_READ | (reg << 21) | (addr << 16), smi_reg);
 
-	/* now wait for the data to be valid */
-	for (i = 0; !(readl(smi_reg) & SMI_READ_VALID); i++) {
-		if (i == 1000) {
-			printk("%s: PHY read timeout\n", mp->dev->name);
-			goto out;
-		}
-		udelay(10);
+	if (smi_wait_ready(msp)) {
+		printk("%s: SMI bus busy timeout\n", mp->dev->name);
+		ret = -ETIMEDOUT;
+		goto out;
 	}
 
-	*value = readl(smi_reg) & 0xffff;
+	ret = readl(smi_reg);
+	if (!(ret & SMI_READ_VALID)) {
+		printk("%s: SMI bus read not valid\n", mp->dev->name);
+		ret = -ENODEV;
+		goto out;
+	}
+
+	ret &= 0xffff;
+
 out:
-	mutex_unlock(&mp->shared_smi->phy_lock);
+	mutex_unlock(&msp->phy_lock);
+
+	return ret;
 }
 
-static void smi_reg_write(struct mv643xx_eth_private *mp,
-			  unsigned int addr,
-			  unsigned int reg, unsigned int value)
+static int smi_reg_write(struct mv643xx_eth_private *mp, unsigned int addr,
+			 unsigned int reg, unsigned int value)
 {
-	void __iomem *smi_reg = mp->shared_smi->base + SMI_REG;
-	int i;
+	struct mv643xx_eth_shared_private *msp = mp->shared_smi;
+	void __iomem *smi_reg = msp->base + SMI_REG;
 
-	/* the SMI register is a shared resource */
-	mutex_lock(&mp->shared_smi->phy_lock);
+	mutex_lock(&msp->phy_lock);
 
-	/* wait for the SMI register to become available */
-	for (i = 0; readl(smi_reg) & SMI_BUSY; i++) {
-		if (i == 1000) {
-			printk("%s: PHY busy timeout\n", mp->dev->name);
-			goto out;
-		}
-		udelay(10);
+	if (smi_wait_ready(msp)) {
+		printk("%s: SMI bus busy timeout\n", mp->dev->name);
+		mutex_unlock(&msp->phy_lock);
+		return -ETIMEDOUT;
 	}
 
 	writel(SMI_OPCODE_WRITE | (reg << 21) |
 		(addr << 16) | (value & 0xffff), smi_reg);
-out:
-	mutex_unlock(&mp->shared_smi->phy_lock);
+
+	mutex_unlock(&msp->phy_lock);
+
+	return 0;
 }
 
 
@@ -1877,16 +1928,19 @@
 
 static void phy_reset(struct mv643xx_eth_private *mp)
 {
-	unsigned int data;
+	int data;
 
-	smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data);
+	data = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
+	if (data < 0)
+		return;
+
 	data |= BMCR_RESET;
-	smi_reg_write(mp, mp->phy_addr, MII_BMCR, data);
+	if (smi_reg_write(mp, mp->phy_addr, MII_BMCR, data) < 0)
+		return;
 
 	do {
-		udelay(1);
-		smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data);
-	} while (data & BMCR_RESET);
+		data = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
+	} while (data >= 0 && data & BMCR_RESET);
 }
 
 static void port_start(struct mv643xx_eth_private *mp)
@@ -2214,11 +2268,7 @@
 static int mv643xx_eth_mdio_read(struct net_device *dev, int addr, int reg)
 {
 	struct mv643xx_eth_private *mp = netdev_priv(dev);
-	int val;
-
-	smi_reg_read(mp, addr, reg, &val);
-
-	return val;
+	return smi_reg_read(mp, addr, reg);
 }
 
 static void mv643xx_eth_mdio_write(struct net_device *dev, int addr, int reg, int val)
@@ -2317,6 +2367,24 @@
 
 	mutex_init(&msp->phy_lock);
 
+	msp->err_interrupt = NO_IRQ;
+	init_waitqueue_head(&msp->smi_busy_wait);
+
+	/*
+	 * Check whether the error interrupt is hooked up.
+	 */
+	res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+	if (res != NULL) {
+		int err;
+
+		err = request_irq(res->start, mv643xx_eth_err_irq,
+				  IRQF_SHARED, "mv643xx_eth", msp);
+		if (!err) {
+			writel(ERR_INT_SMI_DONE, msp->base + ERR_INT_MASK);
+			msp->err_interrupt = res->start;
+		}
+	}
+
 	/*
 	 * (Re-)program MBUS remapping windows if we are asked to.
 	 */
@@ -2343,6 +2411,8 @@
 {
 	struct mv643xx_eth_shared_private *msp = platform_get_drvdata(pdev);
 
+	if (msp->err_interrupt != NO_IRQ)
+		free_irq(msp->err_interrupt, msp);
 	iounmap(msp->base);
 	kfree(msp);
 
@@ -2431,13 +2501,20 @@
 
 static int phy_detect(struct mv643xx_eth_private *mp)
 {
-	unsigned int data;
-	unsigned int data2;
+	int data;
+	int data2;
 
-	smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data);
-	smi_reg_write(mp, mp->phy_addr, MII_BMCR, data ^ BMCR_ANENABLE);
+	data = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
+	if (data < 0)
+		return -ENODEV;
 
-	smi_reg_read(mp, mp->phy_addr, MII_BMCR, &data2);
+	if (smi_reg_write(mp, mp->phy_addr, MII_BMCR, data ^ BMCR_ANENABLE) < 0)
+		return -ENODEV;
+
+	data2 = smi_reg_read(mp, mp->phy_addr, MII_BMCR);
+	if (data2 < 0)
+		return -ENODEV;
+
 	if (((data ^ data2) & BMCR_ANENABLE) == 0)
 		return -ENODEV;