]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - drivers/net/ethoc.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jmorris...
[karo-tx-linux.git] / drivers / net / ethoc.c
index c5a2fe099a8d772d6ef158bfac61bbe903f54aaa..b79d7e1555d5e5e45b3d7475b47029f814bd856f 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/platform_device.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/of.h>
 #include <net/ethoc.h>
 
 static int buffer_size = 0x8000; /* 32 KBytes */
@@ -184,7 +185,6 @@ MODULE_PARM_DESC(buffer_size, "DMA buffer allocation size");
  * @netdev:    pointer to network device structure
  * @napi:      NAPI structure
  * @msg_enable:        device state flags
- * @rx_lock:   receive lock
  * @lock:      device lock
  * @phy:       attached PHY
  * @mdio:      MDIO bus for PHY access
@@ -209,7 +209,6 @@ struct ethoc {
        struct napi_struct napi;
        u32 msg_enable;
 
-       spinlock_t rx_lock;
        spinlock_t lock;
 
        struct phy_device *phy;
@@ -413,10 +412,21 @@ static int ethoc_rx(struct net_device *dev, int limit)
                unsigned int entry;
                struct ethoc_bd bd;
 
-               entry = priv->num_tx + (priv->cur_rx % priv->num_rx);
+               entry = priv->num_tx + priv->cur_rx;
                ethoc_read_bd(priv, entry, &bd);
-               if (bd.stat & RX_BD_EMPTY)
-                       break;
+               if (bd.stat & RX_BD_EMPTY) {
+                       ethoc_ack_irq(priv, INT_MASK_RX);
+                       /* If packet (interrupt) came in between checking
+                        * BD_EMTPY and clearing the interrupt source, then we
+                        * risk missing the packet as the RX interrupt won't
+                        * trigger right away when we reenable it; hence, check
+                        * BD_EMTPY here again to make sure there isn't such a
+                        * packet waiting for us...
+                        */
+                       ethoc_read_bd(priv, entry, &bd);
+                       if (bd.stat & RX_BD_EMPTY)
+                               break;
+               }
 
                if (ethoc_update_rx_stats(priv, &bd) == 0) {
                        int size = bd.stat >> 16;
@@ -446,13 +456,14 @@ static int ethoc_rx(struct net_device *dev, int limit)
                bd.stat &= ~RX_BD_STATS;
                bd.stat |=  RX_BD_EMPTY;
                ethoc_write_bd(priv, entry, &bd);
-               priv->cur_rx++;
+               if (++priv->cur_rx == priv->num_rx)
+                       priv->cur_rx = 0;
        }
 
        return count;
 }
 
-static int ethoc_update_tx_stats(struct ethoc *dev, struct ethoc_bd *bd)
+static void ethoc_update_tx_stats(struct ethoc *dev, struct ethoc_bd *bd)
 {
        struct net_device *netdev = dev->netdev;
 
@@ -482,32 +493,44 @@ static int ethoc_update_tx_stats(struct ethoc *dev, struct ethoc_bd *bd)
        netdev->stats.collisions += (bd->stat >> 4) & 0xf;
        netdev->stats.tx_bytes += bd->stat >> 16;
        netdev->stats.tx_packets++;
-       return 0;
 }
 
-static void ethoc_tx(struct net_device *dev)
+static int ethoc_tx(struct net_device *dev, int limit)
 {
        struct ethoc *priv = netdev_priv(dev);
+       int count;
+       struct ethoc_bd bd;
 
-       spin_lock(&priv->lock);
+       for (count = 0; count < limit; ++count) {
+               unsigned int entry;
 
-       while (priv->dty_tx != priv->cur_tx) {
-               unsigned int entry = priv->dty_tx % priv->num_tx;
-               struct ethoc_bd bd;
+               entry = priv->dty_tx & (priv->num_tx-1);
 
                ethoc_read_bd(priv, entry, &bd);
-               if (bd.stat & TX_BD_READY)
-                       break;
 
-               entry = (++priv->dty_tx) % priv->num_tx;
-               (void)ethoc_update_tx_stats(priv, &bd);
+               if (bd.stat & TX_BD_READY || (priv->dty_tx == priv->cur_tx)) {
+                       ethoc_ack_irq(priv, INT_MASK_TX);
+                       /* If interrupt came in between reading in the BD
+                        * and clearing the interrupt source, then we risk
+                        * missing the event as the TX interrupt won't trigger
+                        * right away when we reenable it; hence, check
+                        * BD_EMPTY here again to make sure there isn't such an
+                        * event pending...
+                        */
+                       ethoc_read_bd(priv, entry, &bd);
+                       if (bd.stat & TX_BD_READY ||
+                           (priv->dty_tx == priv->cur_tx))
+                               break;
+               }
+
+               ethoc_update_tx_stats(priv, &bd);
+               priv->dty_tx++;
        }
 
        if ((priv->cur_tx - priv->dty_tx) <= (priv->num_tx / 2))
                netif_wake_queue(dev);
 
-       ethoc_ack_irq(priv, INT_MASK_TX);
-       spin_unlock(&priv->lock);
+       return count;
 }
 
 static irqreturn_t ethoc_interrupt(int irq, void *dev_id)
@@ -515,32 +538,38 @@ static irqreturn_t ethoc_interrupt(int irq, void *dev_id)
        struct net_device *dev = dev_id;
        struct ethoc *priv = netdev_priv(dev);
        u32 pending;
-
-       ethoc_disable_irq(priv, INT_MASK_ALL);
+       u32 mask;
+
+       /* Figure out what triggered the interrupt...
+        * The tricky bit here is that the interrupt source bits get
+        * set in INT_SOURCE for an event irregardless of whether that
+        * event is masked or not.  Thus, in order to figure out what
+        * triggered the interrupt, we need to remove the sources
+        * for all events that are currently masked.  This behaviour
+        * is not particularly well documented but reasonable...
+        */
+       mask = ethoc_read(priv, INT_MASK);
        pending = ethoc_read(priv, INT_SOURCE);
+       pending &= mask;
+
        if (unlikely(pending == 0)) {
-               ethoc_enable_irq(priv, INT_MASK_ALL);
                return IRQ_NONE;
        }
 
        ethoc_ack_irq(priv, pending);
 
+       /* We always handle the dropped packet interrupt */
        if (pending & INT_MASK_BUSY) {
                dev_err(&dev->dev, "packet dropped\n");
                dev->stats.rx_dropped++;
        }
 
-       if (pending & INT_MASK_RX) {
-               if (napi_schedule_prep(&priv->napi))
-                       __napi_schedule(&priv->napi);
-       } else {
-               ethoc_enable_irq(priv, INT_MASK_RX);
+       /* Handle receive/transmit event by switching to polling */
+       if (pending & (INT_MASK_TX | INT_MASK_RX)) {
+               ethoc_disable_irq(priv, INT_MASK_TX | INT_MASK_RX);
+               napi_schedule(&priv->napi);
        }
 
-       if (pending & INT_MASK_TX)
-               ethoc_tx(dev);
-
-       ethoc_enable_irq(priv, INT_MASK_ALL & ~INT_MASK_RX);
        return IRQ_HANDLED;
 }
 
@@ -566,26 +595,29 @@ static int ethoc_get_mac_address(struct net_device *dev, void *addr)
 static int ethoc_poll(struct napi_struct *napi, int budget)
 {
        struct ethoc *priv = container_of(napi, struct ethoc, napi);
-       int work_done = 0;
+       int rx_work_done = 0;
+       int tx_work_done = 0;
+
+       rx_work_done = ethoc_rx(priv->netdev, budget);
+       tx_work_done = ethoc_tx(priv->netdev, budget);
 
-       work_done = ethoc_rx(priv->netdev, budget);
-       if (work_done < budget) {
-               ethoc_enable_irq(priv, INT_MASK_RX);
+       if (rx_work_done < budget && tx_work_done < budget) {
                napi_complete(napi);
+               ethoc_enable_irq(priv, INT_MASK_TX | INT_MASK_RX);
        }
 
-       return work_done;
+       return rx_work_done;
 }
 
 static int ethoc_mdio_read(struct mii_bus *bus, int phy, int reg)
 {
-       unsigned long timeout = jiffies + ETHOC_MII_TIMEOUT;
        struct ethoc *priv = bus->priv;
+       int i;
 
        ethoc_write(priv, MIIADDRESS, MIIADDRESS_ADDR(phy, reg));
        ethoc_write(priv, MIICOMMAND, MIICOMMAND_READ);
 
-       while (time_before(jiffies, timeout)) {
+       for (i=0; i < 5; i++) {
                u32 status = ethoc_read(priv, MIISTATUS);
                if (!(status & MIISTATUS_BUSY)) {
                        u32 data = ethoc_read(priv, MIIRX_DATA);
@@ -593,8 +625,7 @@ static int ethoc_mdio_read(struct mii_bus *bus, int phy, int reg)
                        ethoc_write(priv, MIICOMMAND, 0);
                        return data;
                }
-
-               schedule();
+               usleep_range(100,200);
        }
 
        return -EBUSY;
@@ -602,22 +633,21 @@ static int ethoc_mdio_read(struct mii_bus *bus, int phy, int reg)
 
 static int ethoc_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val)
 {
-       unsigned long timeout = jiffies + ETHOC_MII_TIMEOUT;
        struct ethoc *priv = bus->priv;
+       int i;
 
        ethoc_write(priv, MIIADDRESS, MIIADDRESS_ADDR(phy, reg));
        ethoc_write(priv, MIITX_DATA, val);
        ethoc_write(priv, MIICOMMAND, MIICOMMAND_WRITE);
 
-       while (time_before(jiffies, timeout)) {
+       for (i=0; i < 5; i++) {
                u32 stat = ethoc_read(priv, MIISTATUS);
                if (!(stat & MIISTATUS_BUSY)) {
                        /* reset MII command register */
                        ethoc_write(priv, MIICOMMAND, 0);
                        return 0;
                }
-
-               schedule();
+               usleep_range(100,200);
        }
 
        return -EBUSY;
@@ -971,9 +1001,17 @@ static int __devinit ethoc_probe(struct platform_device *pdev)
        /* calculate the number of TX/RX buffers, maximum 128 supported */
        num_bd = min_t(unsigned int,
                128, (netdev->mem_end - netdev->mem_start + 1) / ETHOC_BUFSIZ);
-       priv->num_tx = max(2, num_bd / 4);
+       if (num_bd < 4) {
+               ret = -ENODEV;
+               goto error;
+       }
+       /* num_tx must be a power of two */
+       priv->num_tx = rounddown_pow_of_two(num_bd >> 1);
        priv->num_rx = num_bd - priv->num_tx;
 
+       dev_dbg(&pdev->dev, "ethoc: num_tx: %d num_rx: %d\n",
+               priv->num_tx, priv->num_rx);
+
        priv->vma = devm_kzalloc(&pdev->dev, num_bd*sizeof(void*), GFP_KERNEL);
        if (!priv->vma) {
                ret = -ENOMEM;
@@ -982,10 +1020,23 @@ static int __devinit ethoc_probe(struct platform_device *pdev)
 
        /* Allow the platform setup code to pass in a MAC address. */
        if (pdev->dev.platform_data) {
-               struct ethoc_platform_data *pdata =
-                       (struct ethoc_platform_data *)pdev->dev.platform_data;
+               struct ethoc_platform_data *pdata = pdev->dev.platform_data;
                memcpy(netdev->dev_addr, pdata->hwaddr, IFHWADDRLEN);
                priv->phy_id = pdata->phy_id;
+       } else {
+               priv->phy_id = -1;
+
+#ifdef CONFIG_OF
+               {
+               const uint8_t* mac;
+
+               mac = of_get_property(pdev->dev.of_node,
+                                     "local-mac-address",
+                                     NULL);
+               if (mac)
+                       memcpy(netdev->dev_addr, mac, IFHWADDRLEN);
+               }
+#endif
        }
 
        /* Check that the given MAC address is valid. If it isn't, read the
@@ -1046,7 +1097,6 @@ static int __devinit ethoc_probe(struct platform_device *pdev)
        /* setup NAPI */
        netif_napi_add(netdev, &priv->napi, ethoc_poll, 64);
 
-       spin_lock_init(&priv->rx_lock);
        spin_lock_init(&priv->lock);
 
        ret = register_netdev(netdev);
@@ -1113,6 +1163,16 @@ static int ethoc_resume(struct platform_device *pdev)
 # define ethoc_resume  NULL
 #endif
 
+#ifdef CONFIG_OF
+static struct of_device_id ethoc_match[] = {
+       {
+               .compatible = "opencores,ethoc",
+       },
+       {},
+};
+MODULE_DEVICE_TABLE(of, ethoc_match);
+#endif
+
 static struct platform_driver ethoc_driver = {
        .probe   = ethoc_probe,
        .remove  = __devexit_p(ethoc_remove),
@@ -1120,6 +1180,10 @@ static struct platform_driver ethoc_driver = {
        .resume  = ethoc_resume,
        .driver  = {
                .name = "ethoc",
+               .owner = THIS_MODULE,
+#ifdef CONFIG_OF
+               .of_match_table = ethoc_match,
+#endif
        },
 };