/* lan_adapter.c: A SEGA LAN adapter (HIT-0300) device driver for Linux. */ /* A Linux device driver for HIT-0300 LAN adapter (SEGA Dreamcast). This software may be used and distributed according to the terms of the GNU General Public License (GPL), incorporated herein by reference. Drivers based on or derived from this code fall under the GPL and must retain the authorship, copyright and license notice. This file is not a complete program and may only be used when the entire operating system is licensed under the GPL. See the file "COPYING" in the main directory of this archive for more details. This driver is for the SEGA LAN adapter, based on the MB86967, a multi-purpose ethernet chip. Contributors and Contacts: The author may be reached as c.berger@tu-braunschweig.de . Support and updates available at http://linuxdc.sourceforge.net Thanks for many good documentation and sources from Dan Potter (dcload-ip, Kallistios), Christian Groessler (if_mbe_g2.c patch for NetBSD) and many other OpenSource authors for their sources that gave me a good overview for writing a device driver. Acknowledgments: Dreamcast is a registered trademark of Sega, Inc. ChangeLog: 02-28-2003 Christian Berger First public release. */ #include "lan_adapter.h" /* Debugging switch: 0=no debug messages ... 5=mess your logs. */ static unsigned int hit_debug = HIT_NO_DEBUG; /* Space for the packet. */ static unsigned char current_pkt[1514]; static struct net_device hit_dev; struct hit_dev_priv { struct net_device_stats stats; int status; spinlock_t lock; }; /* Along the Fujitsu data sheet, "Shift clocks to EEPROM are generated by * repeatedly writing 1 or 0 to this bit." * We need this for the EEPROM reader. */ static void strobe_eeprom(void) { outb(HIT_B16_SELECT, HIT_REG(HIT_BMPR16)); /* Assert EEPROM chip select signal. */ outb(HIT_B16_SELECT | HIT_B16_CLOCK, HIT_REG(HIT_BMPR16)); /* See above. */ outb(HIT_B16_SELECT | HIT_B16_CLOCK, HIT_REG(HIT_BMPR16)); outb(HIT_B16_SELECT, HIT_REG(HIT_BMPR16)); /* Assert EEPROM chip select signal. */ } /* This EEPROM reader is inspired by Dan Potter's KOS. */ static void read_eeprom(uint8 *data) { uint8 val; int n, bit; /* Read bytes from EEPROM, two per iteration. */ for (n=0; n<3; n++) { outb(0x00, HIT_REG(HIT_BMPR16)); /* Reset the EEPROM interface. */ outb(0x00, HIT_REG(HIT_BMPR17)); outb(HIT_B16_SELECT, HIT_REG(HIT_BMPR16)); /* Start EEPROM access. */ outb(HIT_B17_DATA, HIT_REG(HIT_BMPR17)); strobe_eeprom(); /* Pass the iteration count as well as a READ command. */ val = 0x80 | n; for (bit=0x80; bit != 0x00; bit >>= 1) { outb((val & bit) ? HIT_B17_DATA : 0x00, HIT_REG(HIT_BMPR17)); strobe_eeprom(); } outb(0x00, HIT_REG(HIT_BMPR17)); /* Read a byte. */ val = 0; for (bit = 0x80; bit != 0x00; bit >>= 1) { strobe_eeprom(); if (inb(HIT_REG(HIT_BMPR17)) & HIT_B17_DATA) val |= bit; } *data++ = val; /* Read one more byte. */ val = 0; for (bit = 0x80; bit != 0x00; bit >>=1) { strobe_eeprom(); if (inb(HIT_REG(HIT_BMPR17)) & HIT_B17_DATA) val |= bit; } *data++ = val; } } /* Fujitsu bundles three internal register sets called register banks. * To switch between them, you need to set bit 2 and 3 * of DLCR7 to the values below: * * RBS1 RBS2 Address 00h - 07h Address 08h to 0fh * 0 0 DLCR[0..7] DLCR[8..15] * 0 1 DLCR[0..7] MAR[8..15] * 1 0 DLCR[0..7] BMPR[8..15] * 1 1 DLCR[0..7] Reserved (for future use ;-). * * So, be careful to operate on the according register set! */ static void switch_bank(int bank) { int new_bank = (inb(HIT_REG(HIT_DLCR7)) & ~HIT_D7_RBMASK) | (bank << 2); outb(new_bank, HIT_REG(HIT_DLCR7)); } /* Startup the adapter and inform upper layers. */ static int net_open(struct net_device *dev) { switch_bank(2); /* Switch to bank 2 for normal operation. */ outb( ((inb(HIT_REG(HIT_DLCR5)) & ~HIT_D5_AM_PROM) | HIT_D5_AM_OTHER) , HIT_REG(HIT_DLCR5)); /* Set normal receive mode. */ netif_start_queue(dev); return 0; } /* Sends one packet. */ static int net_send_packet(struct sk_buff *skb, struct net_device *dev) { struct hit_dev_priv *localptr = dev->priv; short int len = (ETH_ZLEN < skb->len) ? skb->len : ETH_ZLEN; int i, timeout = 50; unsigned char *buf = skb->data; unsigned long flags; if (len > ETH_FRAME_LEN) { if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: Attempting to send a large packet (%d bytes).\n", dev->name, len); return 1; } if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: Transmitting a packet of length %lu.\n", dev->name, (unsigned long)skb->len); /* Wait for queue to become empty. timeout avoids infinitely blocking. */ while ((inb(HIT_REG(HIT_BMPR10)) & 0x7f) && ((--timeout) > 0 )) udelay(2); if (timeout == 0) { if (hit_debug) printk(KERN_WARNING "HIT-0300: %s timed out while waiting for previous tx.\n", dev->name); return 1; } spin_lock_irqsave(&localptr->lock, flags); /* Inform the DLC about the length of the sending packet. */ outb( (len & 0x00ff) , HIT_REG(HIT_BMPR8)); outb( ((len & 0xff00) >> 8) , HIT_REG(HIT_BMPR8)); for (i = 0; i < len; i++) outb( buf[i] , HIT_REG(HIT_BMPR8)); /* Start the transmitter. */ outb( (1 | HIT_B10_TX) , HIT_REG(HIT_BMPR10)); /* Update statistics. */ localptr->stats.tx_packets++; localptr->stats.tx_bytes += len; spin_unlock_irqrestore(&localptr->lock, flags); dev_kfree_skb(skb); return 0; } /* Interrupt handling routine: We are called by the kernel to handle "the problems" of our crying baby "HIT-0300" :-). */ static void net_interrupt(int irq, void *dev_id, struct pt_regs *regs) { struct net_device *dev = dev_id; struct hit_dev_priv *localptr = dev->priv; int intr_rx, intr_tx; /* Prevent multiple invocations of net_rx(..) */ spin_lock(&localptr->lock); /* Acknowlegde LAN adapter interrupts. */ intr_tx = inb(HIT_REG(HIT_DLCR0)); /* ACK transmit interrupt... */ outb(intr_tx, HIT_REG(HIT_DLCR0)); intr_rx = inb(HIT_REG(HIT_DLCR1)); /* ..as well as a receive interrupt. */ outb(intr_rx, HIT_REG(HIT_DLCR1)); if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: Interrupt with status TX:%04x RX:%04x.\n", dev->name, intr_tx, intr_rx); /* Is the calling interrupt valid (bit 7 set to 1) ? */ if (intr_rx & HIT_D3_PKT_RDY) { outb(0x00, HIT_REG(HIT_DLCR3)); /* Disable interrupts for Rx. */ net_rx(dev); /* Get packet.*/ outb(HIT_D3_PKT_RDY, HIT_REG(HIT_DLCR3)); /* Enable interrupt for TMT OK (bit 7). */ } else if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: spurious interrupt for %04x/%04x.\n", dev->name, intr_tx, intr_rx); spin_unlock(&localptr->lock); return; } /* We have good packet(s) in our buffer. Let's go to work and fetch it/them. */ static void net_rx(struct net_device *dev) { struct hit_dev_priv *localptr = dev->priv; struct sk_buff *skb; int count = 0, status, len, i; for (count = 0; ; count++) { /* Is the buffer empty? (bit 6 of DLCR5) */ if (inb(HIT_REG(HIT_DLCR5)) & HIT_D5_BUF_EMP) break; /* Get the receive status byte (two reads of register HIT_BMPR8). */ status = inb(HIT_REG(HIT_BMPR8)); (void)inb(HIT_REG(HIT_BMPR8)); /* Get the packet length. */ len = inb(HIT_REG(HIT_BMPR8)); len |= (inb(HIT_REG(HIT_BMPR8)) << 8); /* Check for errors. */ if ( (status & 0xf0) != 0x20 ) { if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: receive error occured.\n", dev->name); break; } /* Read the packet. */ if (len > 1514) { if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: big packet received (size %d).\n", dev->name, len); break; } /* Allocate space for packet. */ skb = dev_alloc_skb(len + 3); if (skb == NULL) { if (hit_debug) printk(KERN_WARNING "HIT-0300: %s: low memory. No space left for receiving!\n", dev->name); localptr->stats.rx_dropped++; break; } for (i = 0; i < len; i++) current_pkt[i] = inb(HIT_REG(HIT_BMPR8)); /* Submit packet to the next layer. */ skb->dev = dev; skb_reserve(skb, 2); memcpy(skb_put(skb, len), current_pkt, len); skb->protocol = eth_type_trans(skb, dev); /* Pass packet on next layer. */ netif_rx(skb); /* Update statistics. */ dev->last_rx = jiffies; localptr->stats.rx_packets++; localptr->stats.rx_bytes += len; } return; } /* This function is called, if our adapter timed out. */ static void net_timeout(struct net_device *dev) { struct hit_dev_priv *localptr = dev->priv; unsigned long flags; printk(KERN_WARNING "HIT-0300: %s: transmit timed out with status %04x, %s?\n", dev->name, htons(inw(HIT_REG(HIT_DLCR0))), (inb(HIT_REG(HIT_DLCR0)) & HIT_D0_NET_BSY) ? "network cable problem" : "IRQ conflict"); if (hit_debug >= HIT_FULL_DEBUG) { printk(KERN_WARNING "HIT-0300: %s: timeout register: %04x %04x %04x %04x %04x %04x %04x %04x\n", dev->name, htons(inw(HIT_REG(HIT_DLCR0))), htons(inw(HIT_REG(HIT_DLCR1))), htons(inw(HIT_REG(HIT_DLCR2))), htons(inw(HIT_REG(HIT_DLCR3))), htons(inw(HIT_REG(HIT_DLCR4))), htons(inw(HIT_REG(HIT_DLCR5))), htons(inw(HIT_REG(HIT_DLCR6))), htons(inw(HIT_REG(HIT_DLCR7)))); } localptr->stats.tx_errors++; /* Try to restart the adapter. */ spin_lock_irqsave(&localptr->lock, flags); /* Reset the Data Link Controller. */ outb(HIT_D6_ENA_DLC, HIT_REG(HIT_DLCR6)); /* Enable the Data Link Controller. */ outb( (inb(HIT_REG(HIT_DLCR6)) & ~HIT_D6_ENA_DLC) , HIT_REG(HIT_DLCR6)); /* Enable transmitter / receiver. */ /* Re-open the device. */ net_open(dev); spin_unlock_irqrestore(&localptr->lock, flags); return; } /* Shutdown the adapter. */ static int net_close(struct net_device *dev) { int timeout = 50; netif_stop_queue(dev); /* Ensure we aren't transmitting currently or wait for timeout for correct module unloading. */ while ((inb(HIT_REG(HIT_BMPR10)) & 0x7f) && ((--timeout) > 0 )) udelay(2); /* Disable normal receive mode. */ outb( ((inb(HIT_REG(HIT_DLCR5)) & ~HIT_D5_AM_PROM) | 0x00) , HIT_REG(HIT_DLCR5)); return 0; } /* Get the current statistics. This may be called with the card open or closed. */ static struct net_device_stats *net_get_stats(struct net_device *dev) { struct hit_dev_priv *localptr = dev->priv; return &localptr->stats; } /* Prepare adapter for multicasting. */ static void set_multicast_list(struct net_device *dev) { if (dev->mc_count || dev->flags&(IFF_PROMISC|IFF_ALLMULTI)) { /* We must the kernel realise we had to move into promiscuous * mode or we start all out war on the cable. */ dev->flags |= IFF_PROMISC; outb( (inb(HIT_REG(HIT_DLCR5)) | HIT_D5_AM_PROM) , HIT_REG(HIT_DLCR5)); /* Enable promiscuous mode. */ } else outb( ((inb(HIT_REG(HIT_DLCR5)) & ~HIT_D5_AM_PROM) | HIT_D5_AM_OTHER) , HIT_REG(HIT_DLCR5)); /* Disable promiscuous mode and enable non-promiscuous mode. */ } static void exit_HIT(struct net_device *dev) { struct hit_dev_priv *localptr = dev->priv; /* Power down chip. */ if (localptr->status == HIT_DETECTED) { udelay(2); outb( (inb(HIT_REG(HIT_DLCR7)) & ~HIT_D7_STBY) , HIT_REG(HIT_DLCR7)); udelay(2); } } /* Detects the interface. */ static int detect_HIT() { /* First, we try to reset the complete G2 bus. */ if (hit_debug >= HIT_FULL_DEBUG) printk(KERN_WARNING "HIT-0300: Trying to reset interface for testing chip.\n"); hit_g2reset[G2_8BP_RESET] = 0; hit_g2reset[G2_8BP_RESET] = 1; hit_g2reset[G2_8BP_RESET] = 0; udelay(100); /* Next, we try to read the controller - ID (DLCR7, bit 6 and 7). */ if (hit_debug >= HIT_FULL_DEBUG) printk(KERN_WARNING "HIT-0300: Reading controller type.\n"); return (inb(HIT_REG(HIT_DLCR7)) >> 6) & 0x03; /* LAN adapter (HIT-0300) is binary 10. */ } /* Reading MAC and setting up some essential things. */ static int init_HIT(struct net_device *dev) { uint8 mac[6]; int retval, i; struct hit_dev_priv *localptr; /* Allocate memory for private data. */ dev->priv = kmalloc(sizeof(struct hit_dev_priv), GFP_KERNEL); if (dev->priv == NULL) { if (hit_debug >= HIT_FULL_DEBUG) printk(KERN_WARNING "HIT-0300: Allocation of memory failed.\n"); goto HIT_memory_error; } memset(dev->priv, 0, sizeof(struct hit_dev_priv)); localptr = dev->priv; /* Set status to not detected for the beginning. */ localptr->status = HIT_NOT_DETECTED; /* Request and lock memory region. */ if (hit_debug >= HIT_FULL_DEBUG) printk(KERN_WARNING "HIT-0300: Trying to request I/O region.\n"); retval = check_region(HIT_BASE, HIT_IO_EXTENT); if (retval < 0) { printk(KERN_WARNING "HIT-0300: Couldn't request I/O region."); return -ENODEV; } else request_region(HIT_BASE, HIT_IO_EXTENT, dev->name); /* Reset G2 bus and read controller type. */ if (detect_HIT() != 2) { printk(KERN_WARNING "HIT-0300: Controller type unknown: Extension slot empty or modem (HKT-3030) plugged in?\n"); goto HIT_error; } else printk(KERN_WARNING "HIT-0300: Controller type (MB86967)\n"); /* Clear interrupt status. */ outb(0xff, HIT_REG(HIT_DLCR0)); outb(0xff, HIT_REG(HIT_DLCR1)); /* Power down chip. */ udelay(2); outb( (inb(HIT_REG(HIT_DLCR7)) & ~HIT_D7_STBY) , HIT_REG(HIT_DLCR7)); udelay(2); /* Reset Data Link Control. */ udelay(2); outb( (inb(HIT_REG(HIT_DLCR6)) | HIT_D6_ENA_DLC) , HIT_REG(HIT_DLCR6)); udelay(2); /* Power up chip. */ udelay(2); outb( (inb(HIT_REG(HIT_DLCR7)) | HIT_D7_STBY) , HIT_REG(HIT_DLCR7)); udelay(2); /* Read the MAC address. */ if (hit_debug) printk(KERN_WARNING "HIT-0300: Reading MAC.\n"); read_eeprom(mac); udelay(100); /* Print MAC to screen and set it into register DLCR8. */ switch_bank(0); /* First, switch to register bank 0. */ printk(KERN_WARNING "HIT-0300: MAC: "); for (i=0; i<6; i++) { printk("%02x", mac[i]); if (i != 5) printk(":"); /* Fill up register set HIT_DLCRx (where 8 <= x < 16) with MAC. */ outb(mac[i], HIT_REG(i + HIT_DLCR8)); /* Fill up net_device structure with MAC. */ dev->dev_addr[i] = mac[i]; } printk("\n"); /* Next, switch to register bank 1 and clear multicast address. */ switch_bank(1); for (i=0; i<6; i++) outb(0x00, HIT_REG(i + HIT_MAR8)); /* Select the BMPR bank for normal operation. */ switch_bank(2); outb(0x00, HIT_REG(HIT_BMPR10)); /* Reset transmit packet count */ outb(HIT_B11_AUTO, HIT_REG(HIT_BMPR11)); /* Skip packet which cause 16COLs (Auto Mode). */ outb(0x00, HIT_REG(HIT_BMPR12)); /* Disable DMA mode. */ outb(0x00, HIT_REG(HIT_BMPR13)); /* Disable DMA burst mode. */ outb(0x00, HIT_REG(HIT_BMPR14)); /* Disable receiver control/transceiver interrupt. */ outb(HIT_B15_CLR_ALL, HIT_REG(HIT_BMPR15)); /* Respect the prohibited bits and enable LinkFailureDetection, PolarityReversal, SQE error. */ outb( ((inb(HIT_REG(HIT_DLCR5)) & ~HIT_D5_AM_PROM) | HIT_D5_AM_OTHER) , HIT_REG(HIT_DLCR5)); /* Disable promiscuous mode and enable non-promiscuous mode. */ /* Do some etherdevice initialization stuff. */ SET_MODULE_OWNER(dev); dev->base_addr = HIT_BASE; /* Set IO base. */ dev->irq = HIT_IRQ; /* Is this the correct IRQ for the LAN - adapter? */ dev->if_port = IF_PORT_10BASET; /* Dreamcast LAN adapter - connector is 10baseT. */ /* Next we're trying to register our interrupt. */ if (hit_debug >= HIT_FULL_DEBUG) printk(KERN_WARNING "HIT-0300: Requesting interrupt %d.\n", dev->irq); retval = request_irq(dev->irq, &net_interrupt, 0, dev->name, dev); if (retval) { printk(KERN_WARNING "HIT-0300: Registering of interrupt failed.\n"); goto HIT_error; } /* Now, enable spin_lock for the interface. */ spin_lock_init(&localptr->lock); /* Registering handlers. */ dev->open = net_open; dev->stop = net_close; dev->hard_start_xmit = net_send_packet; dev->tx_timeout = net_timeout; dev->get_stats = net_get_stats; dev->set_multicast_list = set_multicast_list; ether_setup(&hit_dev); /* Trigger setup. */ /* Now, we are sure everything's okay and the controller is present. */ localptr->status = HIT_DETECTED; outb(HIT_D3_PKT_RDY, HIT_REG(HIT_DLCR3)); /* Enable interrupt for TMT OK (bit 7). */ outb( (inb(HIT_REG(HIT_DLCR6)) & ~HIT_D6_ENA_DLC) , HIT_REG(HIT_DLCR6)); /* Enable transmitter / receiver. */ return 0; HIT_error: release_region(HIT_BASE, HIT_IO_EXTENT); return -ENODEV; HIT_memory_error: return -ENOMEM; } int __init init_module_HIT(void) { printk(KERN_WARNING "SEGA LAN adapter device driver (" DRV_NAME ".c, v" DRV_VERSION " " DRV_RELDATE" C. Berger).\n"); if (hit_debug >= HIT_INFO) printk(KERN_WARNING "HIT-0300: Initialization of device.\n"); hit_dev.init = init_HIT; strcpy(hit_dev.name, "eth%d"); /* Name for userspace of the interface. */ if (register_netdev(&hit_dev) != 0) { printk(KERN_WARNING "HIT-0300: register_netdev() returned non-zero.\n"); return -EIO; } if (hit_debug >= HIT_INFO) printk(KERN_WARNING "HIT-0300: Registered as \"%s\".\n", hit_dev.name); return 0; } void __exit cleanup_module_HIT(void) { unregister_netdev(&hit_dev); exit_HIT(&hit_dev); /* Shutdown controller. */ kfree(hit_dev.priv); /* Free previously allocated memory. */ hit_dev.priv = NULL; free_irq(hit_dev.irq, &hit_dev); release_region(HIT_BASE, HIT_IO_EXTENT); printk(KERN_WARNING "Unload SEGA LAN adapter device driver.\n"); } /* Module-depended stuff. */ #ifdef MODULE MODULE_PARM(hit_debug, "i"); MODULE_PARM_DESC(hit_debug, "HIT-0300 debug level 0,2,5"); MODULE_AUTHOR("Christian Berger "); MODULE_DESCRIPTION("Device driver for proprietary HIT-0300 (MB86967 based) Sega LAN adapter."); MODULE_LICENSE("GPL"); #endif module_init(init_module_HIT); module_exit(cleanup_module_HIT);