/* * 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 #include #include #include #include #include #include #include #include #include #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); 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); 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);