ibm_newemac: MAL support for PowerPC 405EZ
[pandora-kernel.git] / drivers / net / ibm_newemac / mal.c
index 5885411..1839d3f 100644 (file)
@@ -3,6 +3,11 @@
  *
  * Memory Access Layer (MAL) support
  *
+ * Copyright 2007 Benjamin Herrenschmidt, IBM Corp.
+ *                <benh@kernel.crashing.org>
+ *
+ * Based on the arch/ppc version of the driver:
+ *
  * Copyright (c) 2004, 2005 Zultys Technologies.
  * Eugene Surovegin <eugene.surovegin@zultys.com> or <ebs@ebshome.net>
  *
@@ -23,6 +28,7 @@
 #include <linux/delay.h>
 
 #include "core.h"
+#include <asm/dcr-regs.h>
 
 static int mal_count;
 
@@ -45,6 +51,8 @@ int __devinit mal_register_commac(struct mal_instance *mal,
                return -EBUSY;
        }
 
+       if (list_empty(&mal->list))
+               napi_enable(&mal->napi);
        mal->tx_chan_mask |= commac->tx_chan_mask;
        mal->rx_chan_mask |= commac->rx_chan_mask;
        list_add(&commac->list, &mal->list);
@@ -54,8 +62,8 @@ int __devinit mal_register_commac(struct mal_instance *mal,
        return 0;
 }
 
-void __devexit mal_unregister_commac(struct mal_instance       *mal,
-                                    struct mal_commac          *commac)
+void mal_unregister_commac(struct mal_instance *mal,
+               struct mal_commac *commac)
 {
        unsigned long flags;
 
@@ -67,6 +75,8 @@ void __devexit mal_unregister_commac(struct mal_instance      *mal,
        mal->tx_chan_mask &= ~commac->tx_chan_mask;
        mal->rx_chan_mask &= ~commac->rx_chan_mask;
        list_del_init(&commac->list);
+       if (list_empty(&mal->list))
+               napi_disable(&mal->napi);
 
        spin_unlock_irqrestore(&mal->lock, flags);
 }
@@ -127,6 +137,14 @@ void mal_enable_rx_channel(struct mal_instance *mal, int channel)
 {
        unsigned long flags;
 
+       /*
+        * On some 4xx PPC's (e.g. 460EX/GT), the rx channel is a multiple
+        * of 8, but enabling in MAL_RXCASR needs the divided by 8 value
+        * for the bitmask
+        */
+       if (!(channel % 8))
+               channel >>= 3;
+
        spin_lock_irqsave(&mal->lock, flags);
 
        MAL_DBG(mal, "enable_rx(%d)" NL, channel);
@@ -139,6 +157,14 @@ void mal_enable_rx_channel(struct mal_instance *mal, int channel)
 
 void mal_disable_rx_channel(struct mal_instance *mal, int channel)
 {
+       /*
+        * On some 4xx PPC's (e.g. 460EX/GT), the rx channel is a multiple
+        * of 8, but enabling in MAL_RXCASR needs the divided by 8 value
+        * for the bitmask
+        */
+       if (!(channel % 8))
+               channel >>= 3;
+
        set_mal_dcrn(mal, MAL_RXCARR, MAL_CHAN_MASK(channel));
 
        MAL_DBG(mal, "disable_rx(%d)" NL, channel);
@@ -182,7 +208,7 @@ static inline void mal_enable_eob_irq(struct mal_instance *mal)
        set_mal_dcrn(mal, MAL_CFG, get_mal_dcrn(mal, MAL_CFG) | MAL_CFG_EOPIE);
 }
 
-/* synchronized by __LINK_STATE_RX_SCHED bit in ndev->state */
+/* synchronized by NAPI state */
 static inline void mal_disable_eob_irq(struct mal_instance *mal)
 {
        // XXX might want to cache MAL_CFG as the DCR read can be slooooow
@@ -254,6 +280,10 @@ static irqreturn_t mal_txeob(int irq, void *dev_instance)
        mal_schedule_poll(mal);
        set_mal_dcrn(mal, MAL_TXEOBISR, r);
 
+       if (mal_has_feature(mal, MAL_FTR_CLEAR_ICINTSTAT))
+               mtdcri(SDR0, DCRN_SDR_ICINTSTAT,
+                               (mfdcri(SDR0, DCRN_SDR_ICINTSTAT) | ICINTSTAT_ICTX));
+
        return IRQ_HANDLED;
 }
 
@@ -268,6 +298,10 @@ static irqreturn_t mal_rxeob(int irq, void *dev_instance)
        mal_schedule_poll(mal);
        set_mal_dcrn(mal, MAL_RXEOBISR, r);
 
+       if (mal_has_feature(mal, MAL_FTR_CLEAR_ICINTSTAT))
+               mtdcri(SDR0, DCRN_SDR_ICINTSTAT,
+                               (mfdcri(SDR0, DCRN_SDR_ICINTSTAT) | ICINTSTAT_ICRX));
+
        return IRQ_HANDLED;
 }
 
@@ -311,14 +345,33 @@ static irqreturn_t mal_rxde(int irq, void *dev_instance)
        return IRQ_HANDLED;
 }
 
+static irqreturn_t mal_int(int irq, void *dev_instance)
+{
+       struct mal_instance *mal = dev_instance;
+       u32 esr = get_mal_dcrn(mal, MAL_ESR);
+
+       if (esr & MAL_ESR_EVB) {
+               /* descriptor error */
+               if (esr & MAL_ESR_DE) {
+                       if (esr & MAL_ESR_CIDT)
+                               return mal_rxde(irq, dev_instance);
+                       else
+                               return mal_txde(irq, dev_instance);
+               } else { /* SERR */
+                       return mal_serr(irq, dev_instance);
+               }
+       }
+       return IRQ_HANDLED;
+}
+
 void mal_poll_disable(struct mal_instance *mal, struct mal_commac *commac)
 {
        /* Spinlock-type semantics: only one caller disable poll at a time */
        while (test_and_set_bit(MAL_COMMAC_POLL_DISABLED, &commac->flags))
                msleep(1);
 
-       /* Synchronize with the MAL NAPI poller. */
-       napi_disable(&mal->napi);
+       /* Synchronize with the MAL NAPI poller */
+       napi_synchronize(&mal->napi);
 }
 
 void mal_poll_enable(struct mal_instance *mal, struct mal_commac *commac)
@@ -326,7 +379,12 @@ void mal_poll_enable(struct mal_instance *mal, struct mal_commac *commac)
        smp_wmb();
        clear_bit(MAL_COMMAC_POLL_DISABLED, &commac->flags);
 
-       // XXX might want to kick a poll now...
+       /* Feels better to trigger a poll here to catch up with events that
+        * may have happened on this channel while disabled. It will most
+        * probably be delayed until the next interrupt but that's mostly a
+        * non-issue in the context where this is called.
+        */
+       napi_schedule(&mal->napi);
 }
 
 static int mal_poll(struct napi_struct *napi, int budget)
@@ -336,8 +394,7 @@ static int mal_poll(struct napi_struct *napi, int budget)
        int received = 0;
        unsigned long flags;
 
-       MAL_DBG2(mal, "poll(%d) %d ->" NL, *budget,
-                rx_work_limit);
+       MAL_DBG2(mal, "poll(%d)" NL, budget);
  again:
        /* Process TX skbs */
        list_for_each(l, &mal->poll_list) {
@@ -461,8 +518,11 @@ static int __devinit mal_probe(struct of_device *ofdev,
        struct mal_instance *mal;
        int err = 0, i, bd_size;
        int index = mal_count++;
+       unsigned int dcr_base;
        const u32 *prop;
        u32 cfg;
+       unsigned long irqflags;
+       irq_handler_t hdlr_serr, hdlr_txde, hdlr_rxde;
 
        mal = kzalloc(sizeof(struct mal_instance), GFP_KERNEL);
        if (!mal) {
@@ -497,14 +557,14 @@ static int __devinit mal_probe(struct of_device *ofdev,
        }
        mal->num_rx_chans = prop[0];
 
-       mal->dcr_base = dcr_resource_start(ofdev->node, 0);
-       if (mal->dcr_base == 0) {
+       dcr_base = dcr_resource_start(ofdev->node, 0);
+       if (dcr_base == 0) {
                printk(KERN_ERR
                       "mal%d: can't find DCR resource!\n", index);
                err = -ENODEV;
                goto fail;
        }
-        mal->dcr_host = dcr_map(ofdev->node, mal->dcr_base, 0x100);
+       mal->dcr_host = dcr_map(ofdev->node, dcr_base, 0x100);
        if (!DCR_MAP_OK(mal->dcr_host)) {
                printk(KERN_ERR
                       "mal%d: failed to map DCRs !\n", index);
@@ -512,11 +572,21 @@ static int __devinit mal_probe(struct of_device *ofdev,
                goto fail;
        }
 
+       if (of_device_is_compatible(ofdev->node, "ibm,mcmal-405ez"))
+               mal->features |= (MAL_FTR_CLEAR_ICINTSTAT |
+                               MAL_FTR_COMMON_ERR_INT);
+
        mal->txeob_irq = irq_of_parse_and_map(ofdev->node, 0);
        mal->rxeob_irq = irq_of_parse_and_map(ofdev->node, 1);
        mal->serr_irq = irq_of_parse_and_map(ofdev->node, 2);
-       mal->txde_irq = irq_of_parse_and_map(ofdev->node, 3);
-       mal->rxde_irq = irq_of_parse_and_map(ofdev->node, 4);
+
+       if (mal_has_feature(mal, MAL_FTR_COMMON_ERR_INT)) {
+               mal->txde_irq = mal->rxde_irq = mal->serr_irq;
+       } else {
+               mal->txde_irq = irq_of_parse_and_map(ofdev->node, 3);
+               mal->rxde_irq = irq_of_parse_and_map(ofdev->node, 4);
+       }
+
        if (mal->txeob_irq == NO_IRQ || mal->rxeob_irq == NO_IRQ ||
            mal->serr_irq == NO_IRQ || mal->txde_irq == NO_IRQ ||
            mal->rxde_irq == NO_IRQ) {
@@ -527,11 +597,12 @@ static int __devinit mal_probe(struct of_device *ofdev,
        }
 
        INIT_LIST_HEAD(&mal->poll_list);
-       mal->napi.weight = CONFIG_IBM_NEW_EMAC_POLL_WEIGHT;
-       mal->napi.poll = mal_poll;
        INIT_LIST_HEAD(&mal->list);
        spin_lock_init(&mal->lock);
 
+       netif_napi_add(NULL, &mal->napi, mal_poll,
+                      CONFIG_IBM_NEW_EMAC_POLL_WEIGHT);
+
        /* Load power-on reset defaults */
        mal_reset(mal);
 
@@ -577,16 +648,26 @@ static int __devinit mal_probe(struct of_device *ofdev,
                             sizeof(struct mal_descriptor) *
                             mal_rx_bd_offset(mal, i));
 
-       err = request_irq(mal->serr_irq, mal_serr, 0, "MAL SERR", mal);
+       if (mal_has_feature(mal, MAL_FTR_COMMON_ERR_INT)) {
+               irqflags = IRQF_SHARED;
+               hdlr_serr = hdlr_txde = hdlr_rxde = mal_int;
+       } else {
+               irqflags = 0;
+               hdlr_serr = mal_serr;
+               hdlr_txde = mal_txde;
+               hdlr_rxde = mal_rxde;
+       }
+
+       err = request_irq(mal->serr_irq, hdlr_serr, irqflags, "MAL SERR", mal);
        if (err)
                goto fail2;
-       err = request_irq(mal->txde_irq, mal_txde, 0, "MAL TX DE", mal);
+       err = request_irq(mal->txde_irq, hdlr_txde, irqflags, "MAL TX DE", mal);
        if (err)
                goto fail3;
        err = request_irq(mal->txeob_irq, mal_txeob, 0, "MAL TX EOB", mal);
        if (err)
                goto fail4;
-       err = request_irq(mal->rxde_irq, mal_rxde, 0, "MAL RX DE", mal);
+       err = request_irq(mal->rxde_irq, hdlr_rxde, irqflags, "MAL RX DE", mal);
        if (err)
                goto fail5;
        err = request_irq(mal->rxeob_irq, mal_rxeob, 0, "MAL RX EOB", mal);
@@ -626,7 +707,7 @@ static int __devinit mal_probe(struct of_device *ofdev,
  fail2:
        dma_free_coherent(&ofdev->dev, bd_size, mal->bd_virt, mal->bd_dma);
  fail_unmap:
-       dcr_unmap(mal->dcr_host, mal->dcr_base, 0x100);
+       dcr_unmap(mal->dcr_host, 0x100);
  fail:
        kfree(mal);