]> www.pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/net/gianfar.c
gianfar: Fix race in TBI/SerDes configuration
[linux-2.6-omap-h63xx.git] / drivers / net / gianfar.c
index 999d69168277f7190bb2d4e0367ea56a5ba473d8..249541a1814b6823e98b079b6b0ae061723aa3bf 100644 (file)
@@ -105,6 +105,7 @@ const char gfar_driver_version[] = "1.3";
 
 static int gfar_enet_open(struct net_device *dev);
 static int gfar_start_xmit(struct sk_buff *skb, struct net_device *dev);
+static void gfar_reset_task(struct work_struct *work);
 static void gfar_timeout(struct net_device *dev);
 static int gfar_close(struct net_device *dev);
 struct sk_buff *gfar_new_skb(struct net_device *dev);
@@ -160,7 +161,7 @@ static int gfar_probe(struct platform_device *pdev)
        struct gfar_private *priv = NULL;
        struct gianfar_platform_data *einfo;
        struct resource *r;
-       int err = 0;
+       int err = 0, irq;
        DECLARE_MAC_BUF(mac);
 
        einfo = (struct gianfar_platform_data *) pdev->dev.platform_data;
@@ -186,15 +187,25 @@ static int gfar_probe(struct platform_device *pdev)
 
        /* fill out IRQ fields */
        if (einfo->device_flags & FSL_GIANFAR_DEV_HAS_MULTI_INTR) {
-               priv->interruptTransmit = platform_get_irq_byname(pdev, "tx");
-               priv->interruptReceive = platform_get_irq_byname(pdev, "rx");
-               priv->interruptError = platform_get_irq_byname(pdev, "error");
-               if (priv->interruptTransmit < 0 || priv->interruptReceive < 0 || priv->interruptError < 0)
+               irq = platform_get_irq_byname(pdev, "tx");
+               if (irq < 0)
+                       goto regs_fail;
+               priv->interruptTransmit = irq;
+
+               irq = platform_get_irq_byname(pdev, "rx");
+               if (irq < 0)
+                       goto regs_fail;
+               priv->interruptReceive = irq;
+
+               irq = platform_get_irq_byname(pdev, "error");
+               if (irq < 0)
                        goto regs_fail;
+               priv->interruptError = irq;
        } else {
-               priv->interruptTransmit = platform_get_irq(pdev, 0);
-               if (priv->interruptTransmit < 0)
+               irq = platform_get_irq(pdev, 0);
+               if (irq < 0)
                        goto regs_fail;
+               priv->interruptTransmit = irq;
        }
 
        /* get a pointer to the register memory */
@@ -209,6 +220,7 @@ static int gfar_probe(struct platform_device *pdev)
        spin_lock_init(&priv->txlock);
        spin_lock_init(&priv->rxlock);
        spin_lock_init(&priv->bflock);
+       INIT_WORK(&priv->reset_task, gfar_reset_task);
 
        platform_set_drvdata(pdev, dev);
 
@@ -337,6 +349,9 @@ static int gfar_probe(struct platform_device *pdev)
        /* Enable most messages by default */
        priv->msg_enable = (NETIF_MSG_IFUP << 1 ) - 1;
 
+       /* Carrier starts down, phylib will bring it up */
+       netif_carrier_off(dev);
+
        err = register_netdev(dev);
 
        if (err) {
@@ -571,6 +586,10 @@ static void gfar_configure_serdes(struct net_device *dev)
        struct gfar_mii __iomem *regs =
                        (void __iomem *)&priv->regs->gfar_mii_regs;
        int tbipa = gfar_read(&priv->regs->tbipa);
+       struct mii_bus *bus = gfar_get_miibus(priv);
+
+       if (bus)
+               mutex_lock(&bus->mdio_lock);
 
        /* Single clk mode, mii mode off(for serdes communication) */
        gfar_local_mdio_write(regs, tbipa, MII_TBICON, TBICON_CLK_SELECT);
@@ -581,6 +600,9 @@ static void gfar_configure_serdes(struct net_device *dev)
 
        gfar_local_mdio_write(regs, tbipa, MII_BMCR, BMCR_ANENABLE |
                        BMCR_ANRESTART | BMCR_FULLDPLX | BMCR_SPEED1000);
+
+       if (bus)
+               mutex_unlock(&bus->mdio_lock);
 }
 
 static void init_registers(struct net_device *dev)
@@ -1212,6 +1234,7 @@ static int gfar_close(struct net_device *dev)
 
        napi_disable(&priv->napi);
 
+       cancel_work_sync(&priv->reset_task);
        stop_gfar(dev);
 
        /* Disconnect from the PHY */
@@ -1326,13 +1349,16 @@ static int gfar_change_mtu(struct net_device *dev, int new_mtu)
        return 0;
 }
 
-/* gfar_timeout gets called when a packet has not been
+/* gfar_reset_task gets scheduled when a packet has not been
  * transmitted after a set amount of time.
  * For now, assume that clearing out all the structures, and
- * starting over will fix the problem. */
-static void gfar_timeout(struct net_device *dev)
+ * starting over will fix the problem.
+ */
+static void gfar_reset_task(struct work_struct *work)
 {
-       dev->stats.tx_errors++;
+       struct gfar_private *priv = container_of(work, struct gfar_private,
+                       reset_task);
+       struct net_device *dev = priv->dev;
 
        if (dev->flags & IFF_UP) {
                stop_gfar(dev);
@@ -1342,6 +1368,14 @@ static void gfar_timeout(struct net_device *dev)
        netif_tx_schedule_all(dev);
 }
 
+static void gfar_timeout(struct net_device *dev)
+{
+       struct gfar_private *priv = netdev_priv(dev);
+
+       dev->stats.tx_errors++;
+       schedule_work(&priv->reset_task);
+}
+
 /* Interrupt Handler for Transmit complete */
 static int gfar_clean_tx_ring(struct net_device *dev)
 {