diff options
Diffstat (limited to 'drivers/net/macmace.c')
-rw-r--r-- | drivers/net/macmace.c | 710 |
1 files changed, 710 insertions, 0 deletions
diff --git a/drivers/net/macmace.c b/drivers/net/macmace.c new file mode 100644 index 000000000000..79a6fc139757 --- /dev/null +++ b/drivers/net/macmace.c @@ -0,0 +1,710 @@ +/* + * Driver for the Macintosh 68K onboard MACE controller with PSC + * driven DMA. The MACE driver code is derived from mace.c. The + * Mac68k theory of operation is courtesy of the MacBSD wizards. + * + * 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. + * + * Copyright (C) 1996 Paul Mackerras. + * Copyright (C) 1998 Alan Cox <alan@redhat.com> + * + * Modified heavily by Joshua M. Thompson based on Dave Huang's NetBSD driver + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/crc32.h> +#include <asm/io.h> +#include <asm/pgtable.h> +#include <asm/irq.h> +#include <asm/macintosh.h> +#include <asm/macints.h> +#include <asm/mac_psc.h> +#include <asm/page.h> +#include "mace.h" + +#define N_TX_RING 1 +#define N_RX_RING 8 +#define N_RX_PAGES ((N_RX_RING * 0x0800 + PAGE_SIZE - 1) / PAGE_SIZE) +#define TX_TIMEOUT HZ + +/* Bits in transmit DMA status */ +#define TX_DMA_ERR 0x80 + +/* The MACE is simply wired down on a Mac68K box */ + +#define MACE_BASE (void *)(0x50F1C000) +#define MACE_PROM (void *)(0x50F08001) + +struct mace_data { + volatile struct mace *mace; + volatile unsigned char *tx_ring; + volatile unsigned char *tx_ring_phys; + volatile unsigned char *rx_ring; + volatile unsigned char *rx_ring_phys; + int dma_intr; + struct net_device_stats stats; + int rx_slot, rx_tail; + int tx_slot, tx_sloti, tx_count; +}; + +struct mace_frame { + u16 len; + u16 status; + u16 rntpc; + u16 rcvcc; + u32 pad1; + u32 pad2; + u8 data[1]; + /* And frame continues.. */ +}; + +#define PRIV_BYTES sizeof(struct mace_data) + +extern void psc_debug_dump(void); + +static int mace_open(struct net_device *dev); +static int mace_close(struct net_device *dev); +static int mace_xmit_start(struct sk_buff *skb, struct net_device *dev); +static struct net_device_stats *mace_stats(struct net_device *dev); +static void mace_set_multicast(struct net_device *dev); +static int mace_set_address(struct net_device *dev, void *addr); +static irqreturn_t mace_interrupt(int irq, void *dev_id, struct pt_regs *regs); +static irqreturn_t mace_dma_intr(int irq, void *dev_id, struct pt_regs *regs); +static void mace_tx_timeout(struct net_device *dev); + +/* Bit-reverse one byte of an ethernet hardware address. */ + +static int bitrev(int b) +{ + int d = 0, i; + + for (i = 0; i < 8; ++i, b >>= 1) { + d = (d << 1) | (b & 1); + } + + return d; +} + +/* + * Load a receive DMA channel with a base address and ring length + */ + +static void mace_load_rxdma_base(struct net_device *dev, int set) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + + psc_write_word(PSC_ENETRD_CMD + set, 0x0100); + psc_write_long(PSC_ENETRD_ADDR + set, (u32) mp->rx_ring_phys); + psc_write_long(PSC_ENETRD_LEN + set, N_RX_RING); + psc_write_word(PSC_ENETRD_CMD + set, 0x9800); + mp->rx_tail = 0; +} + +/* + * Reset the receive DMA subsystem + */ + +static void mace_rxdma_reset(struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mace = mp->mace; + u8 maccc = mace->maccc; + + mace->maccc = maccc & ~ENRCV; + + psc_write_word(PSC_ENETRD_CTL, 0x8800); + mace_load_rxdma_base(dev, 0x00); + psc_write_word(PSC_ENETRD_CTL, 0x0400); + + psc_write_word(PSC_ENETRD_CTL, 0x8800); + mace_load_rxdma_base(dev, 0x10); + psc_write_word(PSC_ENETRD_CTL, 0x0400); + + mace->maccc = maccc; + mp->rx_slot = 0; + + psc_write_word(PSC_ENETRD_CMD + PSC_SET0, 0x9800); + psc_write_word(PSC_ENETRD_CMD + PSC_SET1, 0x9800); +} + +/* + * Reset the transmit DMA subsystem + */ + +static void mace_txdma_reset(struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mace = mp->mace; + u8 maccc; + + psc_write_word(PSC_ENETWR_CTL, 0x8800); + + maccc = mace->maccc; + mace->maccc = maccc & ~ENXMT; + + mp->tx_slot = mp->tx_sloti = 0; + mp->tx_count = N_TX_RING; + + psc_write_word(PSC_ENETWR_CTL, 0x0400); + mace->maccc = maccc; +} + +/* + * Disable DMA + */ + +static void mace_dma_off(struct net_device *dev) +{ + psc_write_word(PSC_ENETRD_CTL, 0x8800); + psc_write_word(PSC_ENETRD_CTL, 0x1000); + psc_write_word(PSC_ENETRD_CMD + PSC_SET0, 0x1100); + psc_write_word(PSC_ENETRD_CMD + PSC_SET1, 0x1100); + + psc_write_word(PSC_ENETWR_CTL, 0x8800); + psc_write_word(PSC_ENETWR_CTL, 0x1000); + psc_write_word(PSC_ENETWR_CMD + PSC_SET0, 0x1100); + psc_write_word(PSC_ENETWR_CMD + PSC_SET1, 0x1100); +} + +/* + * Not really much of a probe. The hardware table tells us if this + * model of Macintrash has a MACE (AV macintoshes) + */ + +struct net_device *mace_probe(int unit) +{ + int j; + struct mace_data *mp; + unsigned char *addr; + struct net_device *dev; + unsigned char checksum = 0; + static int found = 0; + int err; + + if (found || macintosh_config->ether_type != MAC_ETHER_MACE) + return ERR_PTR(-ENODEV); + + found = 1; /* prevent 'finding' one on every device probe */ + + dev = alloc_etherdev(PRIV_BYTES); + if (!dev) + return ERR_PTR(-ENOMEM); + + if (unit >= 0) + sprintf(dev->name, "eth%d", unit); + + mp = (struct mace_data *) dev->priv; + dev->base_addr = (u32)MACE_BASE; + mp->mace = (volatile struct mace *) MACE_BASE; + + dev->irq = IRQ_MAC_MACE; + mp->dma_intr = IRQ_MAC_MACE_DMA; + + /* + * The PROM contains 8 bytes which total 0xFF when XOR'd + * together. Due to the usual peculiar apple brain damage + * the bytes are spaced out in a strange boundary and the + * bits are reversed. + */ + + addr = (void *)MACE_PROM; + + for (j = 0; j < 6; ++j) { + u8 v=bitrev(addr[j<<4]); + checksum ^= v; + dev->dev_addr[j] = v; + } + for (; j < 8; ++j) { + checksum ^= bitrev(addr[j<<4]); + } + + if (checksum != 0xFF) { + free_netdev(dev); + return ERR_PTR(-ENODEV); + } + + memset(&mp->stats, 0, sizeof(mp->stats)); + + dev->open = mace_open; + dev->stop = mace_close; + dev->hard_start_xmit = mace_xmit_start; + dev->tx_timeout = mace_tx_timeout; + dev->watchdog_timeo = TX_TIMEOUT; + dev->get_stats = mace_stats; + dev->set_multicast_list = mace_set_multicast; + dev->set_mac_address = mace_set_address; + + printk(KERN_INFO "%s: 68K MACE, hardware address %.2X", dev->name, dev->dev_addr[0]); + for (j = 1 ; j < 6 ; j++) printk(":%.2X", dev->dev_addr[j]); + printk("\n"); + + err = register_netdev(dev); + if (!err) + return dev; + + free_netdev(dev); + return ERR_PTR(err); +} + +/* + * Load the address on a mace controller. + */ + +static int mace_set_address(struct net_device *dev, void *addr) +{ + unsigned char *p = addr; + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mb = mp->mace; + int i; + unsigned long flags; + u8 maccc; + + local_irq_save(flags); + + maccc = mb->maccc; + + /* load up the hardware address */ + mb->iac = ADDRCHG | PHYADDR; + while ((mb->iac & ADDRCHG) != 0); + + for (i = 0; i < 6; ++i) { + mb->padr = dev->dev_addr[i] = p[i]; + } + + mb->maccc = maccc; + local_irq_restore(flags); + + return 0; +} + +/* + * Open the Macintosh MACE. Most of this is playing with the DMA + * engine. The ethernet chip is quite friendly. + */ + +static int mace_open(struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mb = mp->mace; +#if 0 + int i; + + i = 200; + while (--i) { + mb->biucc = SWRST; + if (mb->biucc & SWRST) { + udelay(10); + continue; + } + break; + } + if (!i) { + printk(KERN_ERR "%s: software reset failed!!\n", dev->name); + return -EAGAIN; + } +#endif + + mb->biucc = XMTSP_64; + mb->fifocc = XMTFW_16 | RCVFW_64 | XMTFWU | RCVFWU | XMTBRST | RCVBRST; + mb->xmtfc = AUTO_PAD_XMIT; + mb->plscc = PORTSEL_AUI; + /* mb->utr = RTRD; */ + + if (request_irq(dev->irq, mace_interrupt, 0, dev->name, dev)) { + printk(KERN_ERR "%s: can't get irq %d\n", dev->name, dev->irq); + return -EAGAIN; + } + if (request_irq(mp->dma_intr, mace_dma_intr, 0, dev->name, dev)) { + printk(KERN_ERR "%s: can't get irq %d\n", dev->name, mp->dma_intr); + free_irq(dev->irq, dev); + return -EAGAIN; + } + + /* Allocate the DMA ring buffers */ + + mp->rx_ring = (void *) __get_free_pages(GFP_KERNEL | GFP_DMA, N_RX_PAGES); + mp->tx_ring = (void *) __get_free_pages(GFP_KERNEL | GFP_DMA, 0); + + if (mp->tx_ring==NULL || mp->rx_ring==NULL) { + if (mp->rx_ring) free_pages((u32) mp->rx_ring, N_RX_PAGES); + if (mp->tx_ring) free_pages((u32) mp->tx_ring, 0); + free_irq(dev->irq, dev); + free_irq(mp->dma_intr, dev); + printk(KERN_ERR "%s: unable to allocate DMA buffers\n", dev->name); + return -ENOMEM; + } + + mp->rx_ring_phys = (unsigned char *) virt_to_bus((void *)mp->rx_ring); + mp->tx_ring_phys = (unsigned char *) virt_to_bus((void *)mp->tx_ring); + + /* We want the Rx buffer to be uncached and the Tx buffer to be writethrough */ + + kernel_set_cachemode((void *)mp->rx_ring, N_RX_PAGES * PAGE_SIZE, IOMAP_NOCACHE_NONSER); + kernel_set_cachemode((void *)mp->tx_ring, PAGE_SIZE, IOMAP_WRITETHROUGH); + + mace_dma_off(dev); + + /* Not sure what these do */ + + psc_write_word(PSC_ENETWR_CTL, 0x9000); + psc_write_word(PSC_ENETRD_CTL, 0x9000); + psc_write_word(PSC_ENETWR_CTL, 0x0400); + psc_write_word(PSC_ENETRD_CTL, 0x0400); + +#if 0 + /* load up the hardware address */ + + mb->iac = ADDRCHG | PHYADDR; + + while ((mb->iac & ADDRCHG) != 0); + + for (i = 0; i < 6; ++i) + mb->padr = dev->dev_addr[i]; + + /* clear the multicast filter */ + mb->iac = ADDRCHG | LOGADDR; + + while ((mb->iac & ADDRCHG) != 0); + + for (i = 0; i < 8; ++i) + mb->ladrf = 0; + + mb->plscc = PORTSEL_GPSI + ENPLSIO; + + mb->maccc = ENXMT | ENRCV; + mb->imr = RCVINT; +#endif + + mace_rxdma_reset(dev); + mace_txdma_reset(dev); + + return 0; +} + +/* + * Shut down the mace and its interrupt channel + */ + +static int mace_close(struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mb = mp->mace; + + mb->maccc = 0; /* disable rx and tx */ + mb->imr = 0xFF; /* disable all irqs */ + mace_dma_off(dev); /* disable rx and tx dma */ + + free_irq(dev->irq, dev); + free_irq(IRQ_MAC_MACE_DMA, dev); + + free_pages((u32) mp->rx_ring, N_RX_PAGES); + free_pages((u32) mp->tx_ring, 0); + + return 0; +} + +/* + * Transmit a frame + */ + +static int mace_xmit_start(struct sk_buff *skb, struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + + /* Stop the queue if the buffer is full */ + + if (!mp->tx_count) { + netif_stop_queue(dev); + return 1; + } + mp->tx_count--; + + mp->stats.tx_packets++; + mp->stats.tx_bytes += skb->len; + + /* We need to copy into our xmit buffer to take care of alignment and caching issues */ + + memcpy((void *) mp->tx_ring, skb->data, skb->len); + + /* load the Tx DMA and fire it off */ + + psc_write_long(PSC_ENETWR_ADDR + mp->tx_slot, (u32) mp->tx_ring_phys); + psc_write_long(PSC_ENETWR_LEN + mp->tx_slot, skb->len); + psc_write_word(PSC_ENETWR_CMD + mp->tx_slot, 0x9800); + + mp->tx_slot ^= 0x10; + + dev_kfree_skb(skb); + + return 0; +} + +static struct net_device_stats *mace_stats(struct net_device *dev) +{ + struct mace_data *p = (struct mace_data *) dev->priv; + return &p->stats; +} + +static void mace_set_multicast(struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mb = mp->mace; + int i, j; + u32 crc; + u8 maccc; + + maccc = mb->maccc; + mb->maccc &= ~PROM; + + if (dev->flags & IFF_PROMISC) { + mb->maccc |= PROM; + } else { + unsigned char multicast_filter[8]; + struct dev_mc_list *dmi = dev->mc_list; + + if (dev->flags & IFF_ALLMULTI) { + for (i = 0; i < 8; i++) { + multicast_filter[i] = 0xFF; + } + } else { + for (i = 0; i < 8; i++) + multicast_filter[i] = 0; + for (i = 0; i < dev->mc_count; i++) { + crc = ether_crc_le(6, dmi->dmi_addr); + j = crc >> 26; /* bit number in multicast_filter */ + multicast_filter[j >> 3] |= 1 << (j & 7); + dmi = dmi->next; + } + } + + mb->iac = ADDRCHG | LOGADDR; + while (mb->iac & ADDRCHG); + + for (i = 0; i < 8; ++i) { + mb->ladrf = multicast_filter[i]; + } + } + + mb->maccc = maccc; +} + +/* + * Miscellaneous interrupts are handled here. We may end up + * having to bash the chip on the head for bad errors + */ + +static void mace_handle_misc_intrs(struct mace_data *mp, int intr) +{ + volatile struct mace *mb = mp->mace; + static int mace_babbles, mace_jabbers; + + if (intr & MPCO) { + mp->stats.rx_missed_errors += 256; + } + mp->stats.rx_missed_errors += mb->mpc; /* reading clears it */ + + if (intr & RNTPCO) { + mp->stats.rx_length_errors += 256; + } + mp->stats.rx_length_errors += mb->rntpc; /* reading clears it */ + + if (intr & CERR) { + ++mp->stats.tx_heartbeat_errors; + } + if (intr & BABBLE) { + if (mace_babbles++ < 4) { + printk(KERN_DEBUG "mace: babbling transmitter\n"); + } + } + if (intr & JABBER) { + if (mace_jabbers++ < 4) { + printk(KERN_DEBUG "mace: jabbering transceiver\n"); + } + } +} + +/* + * A transmit error has occurred. (We kick the transmit side from + * the DMA completion) + */ + +static void mace_xmit_error(struct net_device *dev) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mb = mp->mace; + u8 xmtfs, xmtrc; + + xmtfs = mb->xmtfs; + xmtrc = mb->xmtrc; + + if (xmtfs & XMTSV) { + if (xmtfs & UFLO) { + printk("%s: DMA underrun.\n", dev->name); + mp->stats.tx_errors++; + mp->stats.tx_fifo_errors++; + mace_txdma_reset(dev); + } + if (xmtfs & RTRY) { + mp->stats.collisions++; + } + } +} + +/* + * A receive interrupt occurred. + */ + +static void mace_recv_interrupt(struct net_device *dev) +{ +/* struct mace_data *mp = (struct mace_data *) dev->priv; */ +// volatile struct mace *mb = mp->mace; +} + +/* + * Process the chip interrupt + */ + +static irqreturn_t mace_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct mace_data *mp = (struct mace_data *) dev->priv; + volatile struct mace *mb = mp->mace; + u8 ir; + + ir = mb->ir; + mace_handle_misc_intrs(mp, ir); + + if (ir & XMTINT) { + mace_xmit_error(dev); + } + if (ir & RCVINT) { + mace_recv_interrupt(dev); + } + return IRQ_HANDLED; +} + +static void mace_tx_timeout(struct net_device *dev) +{ +/* struct mace_data *mp = (struct mace_data *) dev->priv; */ +// volatile struct mace *mb = mp->mace; +} + +/* + * Handle a newly arrived frame + */ + +static void mace_dma_rx_frame(struct net_device *dev, struct mace_frame *mf) +{ + struct mace_data *mp = (struct mace_data *) dev->priv; + struct sk_buff *skb; + + if (mf->status & RS_OFLO) { + printk("%s: fifo overflow.\n", dev->name); + mp->stats.rx_errors++; + mp->stats.rx_fifo_errors++; + } + if (mf->status&(RS_CLSN|RS_FRAMERR|RS_FCSERR)) + mp->stats.rx_errors++; + + if (mf->status&RS_CLSN) { + mp->stats.collisions++; + } + if (mf->status&RS_FRAMERR) { + mp->stats.rx_frame_errors++; + } + if (mf->status&RS_FCSERR) { + mp->stats.rx_crc_errors++; + } + + skb = dev_alloc_skb(mf->len+2); + if (!skb) { + mp->stats.rx_dropped++; + return; + } + skb_reserve(skb,2); + memcpy(skb_put(skb, mf->len), mf->data, mf->len); + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + netif_rx(skb); + dev->last_rx = jiffies; + mp->stats.rx_packets++; + mp->stats.rx_bytes += mf->len; +} + +/* + * The PSC has passed us a DMA interrupt event. + */ + +static irqreturn_t mace_dma_intr(int irq, void *dev_id, struct pt_regs *regs) +{ + struct net_device *dev = (struct net_device *) dev_id; + struct mace_data *mp = (struct mace_data *) dev->priv; + int left, head; + u16 status; + u32 baka; + + /* Not sure what this does */ + + while ((baka = psc_read_long(PSC_MYSTERY)) != psc_read_long(PSC_MYSTERY)); + if (!(baka & 0x60000000)) return IRQ_NONE; + + /* + * Process the read queue + */ + + status = psc_read_word(PSC_ENETRD_CTL); + + if (status & 0x2000) { + mace_rxdma_reset(dev); + } else if (status & 0x0100) { + psc_write_word(PSC_ENETRD_CMD + mp->rx_slot, 0x1100); + + left = psc_read_long(PSC_ENETRD_LEN + mp->rx_slot); + head = N_RX_RING - left; + + /* Loop through the ring buffer and process new packages */ + + while (mp->rx_tail < head) { + mace_dma_rx_frame(dev, (struct mace_frame *) (mp->rx_ring + (mp->rx_tail * 0x0800))); + mp->rx_tail++; + } + + /* If we're out of buffers in this ring then switch to */ + /* the other set, otherwise just reactivate this one. */ + + if (!left) { + mace_load_rxdma_base(dev, mp->rx_slot); + mp->rx_slot ^= 0x10; + } else { + psc_write_word(PSC_ENETRD_CMD + mp->rx_slot, 0x9800); + } + } + + /* + * Process the write queue + */ + + status = psc_read_word(PSC_ENETWR_CTL); + + if (status & 0x2000) { + mace_txdma_reset(dev); + } else if (status & 0x0100) { + psc_write_word(PSC_ENETWR_CMD + mp->tx_sloti, 0x0100); + mp->tx_sloti ^= 0x10; + mp->tx_count++; + netif_wake_queue(dev); + } + return IRQ_HANDLED; +} + +MODULE_LICENSE("GPL"); |