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