net: mvmdio: enhance driver to support SMI error/done interrupts
authorFlorian Fainelli <florian@openwrt.org>
Fri, 22 Mar 2013 03:39:27 +0000 (03:39 +0000)
committerDavid S. Miller <davem@davemloft.net>
Fri, 22 Mar 2013 14:25:15 +0000 (10:25 -0400)
This patch enhances the "mvmdio" to support a SMI error/done interrupt
line which can be used along with a wait queue instead of doing
busy-waiting on the registers. This is a feature which is available in
the mv643xx_eth SMI code and thus reduces again the gap between the two.

Signed-off-by: Florian Fainelli <florian@openwrt.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Documentation/devicetree/bindings/net/marvell-orion-mdio.txt
drivers/net/ethernet/marvell/mvmdio.c

index 34e7aafa321c126af500ce998339db8259496d2f..052b5f28a62493758c9ec734e760b7030d722a6a 100644 (file)
@@ -9,6 +9,9 @@ Required properties:
 - compatible: "marvell,orion-mdio"
 - reg: address and length of the SMI register
 
+Optional properties:
+- interrupts: interrupt line number for the SMI error/done interrupt
+
 The child nodes of the MDIO driver are the individual PHY devices
 connected to this MDIO bus. They must have a "reg" property given the
 PHY address on the MDIO bus.
index 3e2711d22451b973ba3a063c99e9553678534153..3472574602b27f4ea06dc542627b3c005063f49a 100644 (file)
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/phy.h>
+#include <linux/interrupt.h>
 #include <linux/platform_device.h>
 #include <linux/delay.h>
 #include <linux/io.h>
 #include <linux/of_mdio.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
 
 #define MVMDIO_SMI_DATA_SHIFT              0
 #define MVMDIO_SMI_PHY_ADDR_SHIFT          16
 #define MVMDIO_SMI_WRITE_OPERATION         0
 #define MVMDIO_SMI_READ_VALID              BIT(27)
 #define MVMDIO_SMI_BUSY                    BIT(28)
+#define MVMDIO_ERR_INT_CAUSE              0x007C
+#define  MVMDIO_ERR_INT_SMI_DONE          0x00000010
+#define MVMDIO_ERR_INT_MASK               0x0080
 
 struct orion_mdio_dev {
        struct mutex lock;
        void __iomem *regs;
+       /*
+        * 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;
 };
 
+static int orion_mdio_smi_is_done(struct orion_mdio_dev *dev)
+{
+       return !(readl(dev->regs) & MVMDIO_SMI_BUSY);
+}
+
 /* Wait for the SMI unit to be ready for another operation
  */
 static int orion_mdio_wait_ready(struct mii_bus *bus)
 {
        struct orion_mdio_dev *dev = bus->priv;
        int count;
-       u32 val;
 
-       count = 0;
-       while (1) {
-               val = readl(dev->regs);
-               if (!(val & MVMDIO_SMI_BUSY))
-                       break;
+       if (dev->err_interrupt <= 0) {
+               count = 0;
+               while (1) {
+                       if (orion_mdio_smi_is_done(dev))
+                               break;
 
-               if (count > 100) {
-                       dev_err(bus->parent, "Timeout: SMI busy for too long\n");
-                       return -ETIMEDOUT;
-               }
+                       if (count > 100) {
+                               dev_err(bus->parent,
+                                       "Timeout: SMI busy for too long\n");
+                               return -ETIMEDOUT;
+                       }
 
-               udelay(10);
-               count++;
+                       udelay(10);
+                       count++;
+               }
+       } else {
+               if (!orion_mdio_smi_is_done(dev)) {
+                       wait_event_timeout(dev->smi_busy_wait,
+                               orion_mdio_smi_is_done(dev),
+                               msecs_to_jiffies(100));
+                       if (!orion_mdio_smi_is_done(dev))
+                               return -ETIMEDOUT;
+               }
        }
 
        return 0;
@@ -141,6 +169,21 @@ static int orion_mdio_reset(struct mii_bus *bus)
        return 0;
 }
 
+static irqreturn_t orion_mdio_err_irq(int irq, void *dev_id)
+{
+       struct orion_mdio_dev *dev = dev_id;
+
+       if (readl(dev->regs + MVMDIO_ERR_INT_CAUSE) &
+                       MVMDIO_ERR_INT_SMI_DONE) {
+               writel(~MVMDIO_ERR_INT_SMI_DONE,
+                               dev->regs + MVMDIO_ERR_INT_CAUSE);
+               wake_up(&dev->smi_busy_wait);
+               return IRQ_HANDLED;
+       }
+
+       return IRQ_NONE;
+}
+
 static int orion_mdio_probe(struct platform_device *pdev)
 {
        struct resource *r;
@@ -181,9 +224,22 @@ static int orion_mdio_probe(struct platform_device *pdev)
        dev->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
        if (!dev->regs) {
                dev_err(&pdev->dev, "Unable to remap SMI register\n");
-               kfree(bus->irq);
-               mdiobus_free(bus);
-               return -ENODEV;
+               ret = -ENODEV;
+               goto out_mdio;
+       }
+
+       init_waitqueue_head(&dev->smi_busy_wait);
+
+       dev->err_interrupt = platform_get_irq(pdev, 0);
+       if (dev->err_interrupt != -ENXIO) {
+               ret = devm_request_irq(&pdev->dev, dev->err_interrupt,
+                                       orion_mdio_err_irq,
+                                       IRQF_SHARED, pdev->name, dev);
+               if (ret)
+                       goto out_mdio;
+
+               writel(MVMDIO_ERR_INT_SMI_DONE,
+                       dev->regs + MVMDIO_ERR_INT_MASK);
        }
 
        mutex_init(&dev->lock);
@@ -194,19 +250,25 @@ static int orion_mdio_probe(struct platform_device *pdev)
                ret = mdiobus_register(bus);
        if (ret < 0) {
                dev_err(&pdev->dev, "Cannot register MDIO bus (%d)\n", ret);
-               kfree(bus->irq);
-               mdiobus_free(bus);
-               return ret;
+               goto out_mdio;
        }
 
        platform_set_drvdata(pdev, bus);
 
        return 0;
+
+out_mdio:
+       kfree(bus->irq);
+       mdiobus_free(bus);
+       return ret;
 }
 
 static int orion_mdio_remove(struct platform_device *pdev)
 {
        struct mii_bus *bus = platform_get_drvdata(pdev);
+       struct orion_mdio_dev *dev = bus->priv;
+
+       writel(0, dev->regs + MVMDIO_ERR_INT_MASK);
        mdiobus_unregister(bus);
        kfree(bus->irq);
        mdiobus_free(bus);