This commit was manufactured by cvs2svn to create branch 'vserver'.
[linux-2.6.git] / drivers / net / ibm_emac / ibm_emac_mal.c
diff --git a/drivers/net/ibm_emac/ibm_emac_mal.c b/drivers/net/ibm_emac/ibm_emac_mal.c
new file mode 100644 (file)
index 0000000..02d847c
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+ * ibm_ocp_mal.c
+ *
+ *      Armin Kuster akuster@mvista.com
+ *      Juen, 2002
+ *
+ * Copyright 2002 MontaVista Softare Inc.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/netdevice.h>
+#include <linux/init.h>
+#include <linux/dma-mapping.h>
+
+#include <asm/io.h>
+#include <asm/irq.h>
+#include <asm/ocp.h>
+
+#include "ibm_emac_mal.h"
+
+// Locking: Should we share a lock with the client ? The client could provide
+// a lock pointer (optionally) in the commac structure... I don't think this is
+// really necessary though
+
+/* This lock protects the commac list. On today UP implementations, it's
+ * really only used as IRQ protection in mal_{register,unregister}_commac()
+ */
+static rwlock_t mal_list_lock = RW_LOCK_UNLOCKED;
+
+int mal_register_commac(struct ibm_ocp_mal *mal, struct mal_commac *commac)
+{
+       unsigned long flags;
+
+       write_lock_irqsave(&mal_list_lock, flags);
+
+       /* Don't let multiple commacs claim the same channel */
+       if ((mal->tx_chan_mask & commac->tx_chan_mask) ||
+           (mal->rx_chan_mask & commac->rx_chan_mask)) {
+               write_unlock_irqrestore(&mal_list_lock, flags);
+               return -EBUSY;
+       }
+
+       mal->tx_chan_mask |= commac->tx_chan_mask;
+       mal->rx_chan_mask |= commac->rx_chan_mask;
+
+       list_add(&commac->list, &mal->commac);
+
+       write_unlock_irqrestore(&mal_list_lock, flags);
+
+       MOD_INC_USE_COUNT;
+
+       return 0;
+}
+
+int mal_unregister_commac(struct ibm_ocp_mal *mal, struct mal_commac *commac)
+{
+       unsigned long flags;
+
+       write_lock_irqsave(&mal_list_lock, flags);
+
+       mal->tx_chan_mask &= ~commac->tx_chan_mask;
+       mal->rx_chan_mask &= ~commac->rx_chan_mask;
+
+       list_del_init(&commac->list);
+
+       write_unlock_irqrestore(&mal_list_lock, flags);
+
+       MOD_DEC_USE_COUNT;
+
+       return 0;
+}
+
+int mal_set_rcbs(struct ibm_ocp_mal *mal, int channel, unsigned long size)
+{
+       switch (channel) {
+       case 0:
+               set_mal_dcrn(mal, DCRN_MALRCBS0, size);
+               break;
+#ifdef DCRN_MALRCBS1
+       case 1:
+               set_mal_dcrn(mal, DCRN_MALRCBS1, size);
+               break;
+#endif
+#ifdef DCRN_MALRCBS2
+       case 2:
+               set_mal_dcrn(mal, DCRN_MALRCBS2, size);
+               break;
+#endif
+#ifdef DCRN_MALRCBS3
+       case 3:
+               set_mal_dcrn(mal, DCRN_MALRCBS3, size);
+               break;
+#endif
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static irqreturn_t mal_serr(int irq, void *dev_instance, struct pt_regs *regs)
+{
+       struct ibm_ocp_mal *mal = dev_instance;
+       unsigned long mal_error;
+
+       /*
+        * This SERR applies to one of the devices on the MAL, here we charge
+        * it against the first EMAC registered for the MAL.
+        */
+
+       mal_error = get_mal_dcrn(mal, DCRN_MALESR);
+
+       printk(KERN_ERR "%s: System Error (MALESR=%lx)\n",
+              "MAL" /* FIXME: get the name right */ , mal_error);
+
+       /* FIXME: decipher error */
+       /* DIXME: distribute to commacs, if possible */
+
+       /* Clear the error status register */
+       set_mal_dcrn(mal, DCRN_MALESR, mal_error);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t mal_txeob(int irq, void *dev_instance, struct pt_regs *regs)
+{
+       struct ibm_ocp_mal *mal = dev_instance;
+       struct list_head *l;
+       unsigned long isr;
+
+       isr = get_mal_dcrn(mal, DCRN_MALTXEOBISR);
+       set_mal_dcrn(mal, DCRN_MALTXEOBISR, isr);
+
+       read_lock(&mal_list_lock);
+       list_for_each(l, &mal->commac) {
+               struct mal_commac *mc = list_entry(l, struct mal_commac, list);
+
+               if (isr & mc->tx_chan_mask) {
+                       mc->ops->txeob(mc->dev, isr & mc->tx_chan_mask);
+               }
+       }
+       read_unlock(&mal_list_lock);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t mal_rxeob(int irq, void *dev_instance, struct pt_regs *regs)
+{
+       struct ibm_ocp_mal *mal = dev_instance;
+       struct list_head *l;
+       unsigned long isr;
+
+       isr = get_mal_dcrn(mal, DCRN_MALRXEOBISR);
+       set_mal_dcrn(mal, DCRN_MALRXEOBISR, isr);
+
+       read_lock(&mal_list_lock);
+       list_for_each(l, &mal->commac) {
+               struct mal_commac *mc = list_entry(l, struct mal_commac, list);
+
+               if (isr & mc->rx_chan_mask) {
+                       mc->ops->rxeob(mc->dev, isr & mc->rx_chan_mask);
+               }
+       }
+       read_unlock(&mal_list_lock);
+
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t mal_txde(int irq, void *dev_instance, struct pt_regs *regs)
+{
+       struct ibm_ocp_mal *mal = dev_instance;
+       struct list_head *l;
+       unsigned long deir;
+
+       deir = get_mal_dcrn(mal, DCRN_MALTXDEIR);
+
+       /* FIXME: print which MAL correctly */
+       printk(KERN_WARNING "%s: Tx descriptor error (MALTXDEIR=%lx)\n",
+              "MAL", deir);
+
+       read_lock(&mal_list_lock);
+       list_for_each(l, &mal->commac) {
+               struct mal_commac *mc = list_entry(l, struct mal_commac, list);
+
+               if (deir & mc->tx_chan_mask) {
+                       mc->ops->txde(mc->dev, deir & mc->tx_chan_mask);
+               }
+       }
+       read_unlock(&mal_list_lock);
+
+       return IRQ_HANDLED;
+}
+
+/*
+ * This interrupt should be very rare at best.  This occurs when
+ * the hardware has a problem with the receive descriptors.  The manual
+ * states that it occurs when the hardware cannot the receive descriptor
+ * empty bit is not set.  The recovery mechanism will be to
+ * traverse through the descriptors, handle any that are marked to be
+ * handled and reinitialize each along the way.  At that point the driver
+ * will be restarted.
+ */
+static irqreturn_t mal_rxde(int irq, void *dev_instance, struct pt_regs *regs)
+{
+       struct ibm_ocp_mal *mal = dev_instance;
+       struct list_head *l;
+       unsigned long deir;
+
+       deir = get_mal_dcrn(mal, DCRN_MALRXDEIR);
+
+       /*
+        * This really is needed.  This case encountered in stress testing.
+        */
+       if (deir == 0)
+               return IRQ_HANDLED;
+
+       /* FIXME: print which MAL correctly */
+       printk(KERN_WARNING "%s: Rx descriptor error (MALRXDEIR=%lx)\n",
+              "MAL", deir);
+
+       read_lock(&mal_list_lock);
+       list_for_each(l, &mal->commac) {
+               struct mal_commac *mc = list_entry(l, struct mal_commac, list);
+
+               if (deir & mc->rx_chan_mask) {
+                       mc->ops->rxde(mc->dev, deir & mc->rx_chan_mask);
+               }
+       }
+       read_unlock(&mal_list_lock);
+
+       return IRQ_HANDLED;
+}
+
+static int __init mal_probe(struct ocp_device *ocpdev)
+{
+       struct ibm_ocp_mal *mal = NULL;
+       struct ocp_func_mal_data *maldata;
+       int err = 0;
+
+       maldata = (struct ocp_func_mal_data *)ocpdev->def->additions;
+       if (maldata == NULL) {
+               printk(KERN_ERR "mal%d: Missing additional datas !\n",
+                      ocpdev->def->index);
+               return -ENODEV;
+       }
+
+       mal = kmalloc(sizeof(struct ibm_ocp_mal), GFP_KERNEL);
+       if (mal == NULL) {
+               printk(KERN_ERR
+                      "mal%d: Out of memory allocating MAL structure !\n",
+                      ocpdev->def->index);
+               return -ENOMEM;
+       }
+       memset(mal, 0, sizeof(*mal));
+
+       switch (ocpdev->def->index) {
+       case 0:
+               mal->dcrbase = DCRN_MAL_BASE;
+               break;
+#ifdef DCRN_MAL1_BASE
+       case 1:
+               mal->dcrbase = DCRN_MAL1_BASE;
+               break;
+#endif
+       default:
+               BUG();
+       }
+
+       /**************************/
+
+       INIT_LIST_HEAD(&mal->commac);
+
+       set_mal_dcrn(mal, DCRN_MALRXCARR, 0xFFFFFFFF);
+       set_mal_dcrn(mal, DCRN_MALTXCARR, 0xFFFFFFFF);
+
+       set_mal_dcrn(mal, DCRN_MALCR, MALCR_MMSR);      /* 384 */
+       /* FIXME: Add delay */
+
+       /* Set the MAL configuration register */
+       set_mal_dcrn(mal, DCRN_MALCR,
+                    MALCR_PLBB | MALCR_OPBBL | MALCR_LEA |
+                    MALCR_PLBLT_DEFAULT);
+
+       /* It would be nice to allocate buffers separately for each
+        * channel, but we can't because the channels share the upper
+        * 13 bits of address lines.  Each channels buffer must also
+        * be 4k aligned, so we allocate 4k for each channel.  This is
+        * inefficient FIXME: do better, if possible */
+       mal->tx_virt_addr = dma_alloc_coherent(&ocpdev->dev,
+                                              MAL_DT_ALIGN *
+                                              maldata->num_tx_chans,
+                                              &mal->tx_phys_addr, GFP_KERNEL);
+       if (mal->tx_virt_addr == NULL) {
+               printk(KERN_ERR
+                      "mal%d: Out of memory allocating MAL descriptors !\n",
+                      ocpdev->def->index);
+               err = -ENOMEM;
+               goto fail;
+       }
+
+       /* God, oh, god, I hate DCRs */
+       set_mal_dcrn(mal, DCRN_MALTXCTP0R, mal->tx_phys_addr);
+#ifdef DCRN_MALTXCTP1R
+       if (maldata->num_tx_chans > 1)
+               set_mal_dcrn(mal, DCRN_MALTXCTP1R,
+                            mal->tx_phys_addr + MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP1R */
+#ifdef DCRN_MALTXCTP2R
+       if (maldata->num_tx_chans > 2)
+               set_mal_dcrn(mal, DCRN_MALTXCTP2R,
+                            mal->tx_phys_addr + 2 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP2R */
+#ifdef DCRN_MALTXCTP3R
+       if (maldata->num_tx_chans > 3)
+               set_mal_dcrn(mal, DCRN_MALTXCTP3R,
+                            mal->tx_phys_addr + 3 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP3R */
+#ifdef DCRN_MALTXCTP4R
+       if (maldata->num_tx_chans > 4)
+               set_mal_dcrn(mal, DCRN_MALTXCTP4R,
+                            mal->tx_phys_addr + 4 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP4R */
+#ifdef DCRN_MALTXCTP5R
+       if (maldata->num_tx_chans > 5)
+               set_mal_dcrn(mal, DCRN_MALTXCTP5R,
+                            mal->tx_phys_addr + 5 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP5R */
+#ifdef DCRN_MALTXCTP6R
+       if (maldata->num_tx_chans > 6)
+               set_mal_dcrn(mal, DCRN_MALTXCTP6R,
+                            mal->tx_phys_addr + 6 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP6R */
+#ifdef DCRN_MALTXCTP7R
+       if (maldata->num_tx_chans > 7)
+               set_mal_dcrn(mal, DCRN_MALTXCTP7R,
+                            mal->tx_phys_addr + 7 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALTXCTP7R */
+
+       mal->rx_virt_addr = dma_alloc_coherent(&ocpdev->dev,
+                                              MAL_DT_ALIGN *
+                                              maldata->num_rx_chans,
+                                              &mal->rx_phys_addr, GFP_KERNEL);
+
+       set_mal_dcrn(mal, DCRN_MALRXCTP0R, mal->rx_phys_addr);
+#ifdef DCRN_MALRXCTP1R
+       if (maldata->num_rx_chans > 1)
+               set_mal_dcrn(mal, DCRN_MALRXCTP1R,
+                            mal->rx_phys_addr + MAL_DT_ALIGN);
+#endif                         /* DCRN_MALRXCTP1R */
+#ifdef DCRN_MALRXCTP2R
+       if (maldata->num_rx_chans > 2)
+               set_mal_dcrn(mal, DCRN_MALRXCTP2R,
+                            mal->rx_phys_addr + 2 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALRXCTP2R */
+#ifdef DCRN_MALRXCTP3R
+       if (maldata->num_rx_chans > 3)
+               set_mal_dcrn(mal, DCRN_MALRXCTP3R,
+                            mal->rx_phys_addr + 3 * MAL_DT_ALIGN);
+#endif                         /* DCRN_MALRXCTP3R */
+
+       err = request_irq(maldata->serr_irq, mal_serr, 0, "MAL SERR", mal);
+       if (err)
+               goto fail;
+       err = request_irq(maldata->txde_irq, mal_txde, 0, "MAL TX DE ", mal);
+       if (err)
+               goto fail;
+       err = request_irq(maldata->txeob_irq, mal_txeob, 0, "MAL TX EOB", mal);
+       if (err)
+               goto fail;
+       err = request_irq(maldata->rxde_irq, mal_rxde, 0, "MAL RX DE", mal);
+       if (err)
+               goto fail;
+       err = request_irq(maldata->rxeob_irq, mal_rxeob, 0, "MAL RX EOB", mal);
+       if (err)
+               goto fail;
+
+       set_mal_dcrn(mal, DCRN_MALIER,
+                    MALIER_DE | MALIER_NE | MALIER_TE |
+                    MALIER_OPBE | MALIER_PLBE);
+
+       /* Advertise me to the rest of the world */
+       ocp_set_drvdata(ocpdev, mal);
+
+       printk(KERN_INFO "mal%d: Initialized, %d tx channels, %d rx channels\n",
+              ocpdev->def->index, maldata->num_tx_chans,
+              maldata->num_rx_chans);
+
+       return 0;
+
+      fail:
+       /* FIXME: dispose requested IRQs ! */
+       if (err && mal)
+               kfree(mal);
+       return err;
+}
+
+static void __exit mal_remove(struct ocp_device *ocpdev)
+{
+       struct ibm_ocp_mal *mal = ocp_get_drvdata(ocpdev);
+       struct ocp_func_mal_data *maldata = ocpdev->def->additions;
+
+       BUG_ON(!maldata);
+
+       ocp_set_drvdata(ocpdev, NULL);
+
+       /* FIXME: shut down the MAL, deal with dependency with emac */
+       free_irq(maldata->serr_irq, mal);
+       free_irq(maldata->txde_irq, mal);
+       free_irq(maldata->txeob_irq, mal);
+       free_irq(maldata->rxde_irq, mal);
+       free_irq(maldata->rxeob_irq, mal);
+
+       if (mal->tx_virt_addr)
+               dma_free_coherent(&ocpdev->dev,
+                                 MAL_DT_ALIGN * maldata->num_tx_chans,
+                                 mal->tx_virt_addr, mal->tx_phys_addr);
+
+       if (mal->rx_virt_addr)
+               dma_free_coherent(&ocpdev->dev,
+                                 MAL_DT_ALIGN * maldata->num_rx_chans,
+                                 mal->rx_virt_addr, mal->rx_phys_addr);
+
+       kfree(mal);
+}
+
+/* Structure for a device driver */
+static struct ocp_device_id mal_ids[] = {
+       {.vendor = OCP_ANY_ID,.function = OCP_FUNC_MAL},
+       {.vendor = OCP_VENDOR_INVALID}
+};
+
+static struct ocp_driver mal_driver = {
+       .name = "mal",
+       .id_table = mal_ids,
+
+       .probe = mal_probe,
+       .remove = mal_remove,
+};
+
+static int __init init_mals(void)
+{
+       int rc;
+
+       rc = ocp_register_driver(&mal_driver);
+       if (rc < 0) {
+               ocp_unregister_driver(&mal_driver);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+static void __exit exit_mals(void)
+{
+       ocp_unregister_driver(&mal_driver);
+}
+
+module_init(init_mals);
+module_exit(exit_mals);