269 lines
7.3 KiB
C
269 lines
7.3 KiB
C
|
/* smc-ultra.c: A SMC Ultra ethernet driver for linux. */
|
|||
|
/*
|
|||
|
Written 1993 by Donald Becker. If released, this code will be
|
|||
|
Copyright 1993 United States Government as represented by the
|
|||
|
Director, National Security Agency. This software may be used and
|
|||
|
distributed according to the terms of the GNU Public License,
|
|||
|
incorporated herein by reference.
|
|||
|
|
|||
|
This is a driver for the SMC Ultra ethercard.
|
|||
|
|
|||
|
The Author may be reached as becker@super.org or
|
|||
|
C/O Supercomputing Research Ctr., 17100 Science Dr., Bowie MD 20715
|
|||
|
|
|||
|
*/
|
|||
|
|
|||
|
static char *version =
|
|||
|
"smc-ultra.c:v0.05 12/21/93 Donald Becker (becker@super.org)\n";
|
|||
|
|
|||
|
#include <linux/config.h>
|
|||
|
#include <linux/kernel.h>
|
|||
|
#include <linux/sched.h>
|
|||
|
#include <linux/errno.h>
|
|||
|
#include <linux/string.h>
|
|||
|
#include <asm/io.h>
|
|||
|
#include <asm/system.h>
|
|||
|
|
|||
|
#include "dev.h"
|
|||
|
#include "8390.h"
|
|||
|
|
|||
|
/* Compatibility definitions for earlier kernel versions. */
|
|||
|
#ifndef HAVE_PORTRESERVE
|
|||
|
#define check_region(ioaddr, size) 0
|
|||
|
#define snarf_region(ioaddr, size); do ; while (0)
|
|||
|
#endif
|
|||
|
|
|||
|
int ultraprobe(int ioaddr, struct device *dev);
|
|||
|
int ultraprobe1(int ioaddr, struct device *dev);
|
|||
|
|
|||
|
static int ultra_open(struct device *dev);
|
|||
|
static void ultra_reset_8390(struct device *dev);
|
|||
|
static int ultra_block_input(struct device *dev, int count,
|
|||
|
char *buf, int ring_offset);
|
|||
|
static void ultra_block_output(struct device *dev, int count,
|
|||
|
const unsigned char *buf, const start_page);
|
|||
|
static int ultra_close_card(struct device *dev);
|
|||
|
|
|||
|
|
|||
|
#define START_PG 0x00 /* First page of TX buffer */
|
|||
|
|
|||
|
#define ULTRA_CMDREG 0 /* Offset to ASIC command register. */
|
|||
|
#define ULTRA_RESET 0x80 /* Board reset, in ULTRA_CMDREG. */
|
|||
|
#define ULTRA_MEMENB 0x40 /* Enable the shared memory. */
|
|||
|
#define ULTRA_NIC_OFFSET 16 /* NIC register offset from the base_addr. */
|
|||
|
|
|||
|
/* Probe for the Ultra. This looks like a 8013 with the station
|
|||
|
address PROM at I/O ports <base>+8 to <base>+13, with a checksum
|
|||
|
following.
|
|||
|
*/
|
|||
|
|
|||
|
int ultra_probe(struct device *dev)
|
|||
|
{
|
|||
|
int *port, ports[] = {0x200, 0x220, 0x240, 0x280, 0x300, 0x340, 0x380, 0};
|
|||
|
unsigned short ioaddr = dev->base_addr;
|
|||
|
|
|||
|
if (ioaddr > 0x1ff)
|
|||
|
return ! ultraprobe1(ioaddr, dev);
|
|||
|
else if (ioaddr > 0)
|
|||
|
return ENXIO; /* Don't probe at all. */
|
|||
|
|
|||
|
for (port = &ports[0]; *port; port++) {
|
|||
|
if (check_region(*port, 32))
|
|||
|
continue;
|
|||
|
if ((inb(*port + 7) & 0xF0) == 0x20 /* Check chip ID nibble. */
|
|||
|
&& ultraprobe1(*port, dev) == 0)
|
|||
|
return 0;
|
|||
|
}
|
|||
|
dev->base_addr = ioaddr;
|
|||
|
return ENODEV;
|
|||
|
}
|
|||
|
|
|||
|
int ultraprobe1(int ioaddr, struct device *dev)
|
|||
|
{
|
|||
|
int i;
|
|||
|
unsigned char *station_addr = dev->dev_addr;
|
|||
|
int checksum = 0;
|
|||
|
char *model_name;
|
|||
|
unsigned char eeprom_irq = 0;
|
|||
|
/* Values from various config regs. */
|
|||
|
unsigned char num_pages, irqreg, addr, reg4 = inb(ioaddr + 4) & 0x7f;
|
|||
|
|
|||
|
|
|||
|
/* Select the station address register set. */
|
|||
|
outb(reg4, ioaddr + 4);
|
|||
|
|
|||
|
for (i = 0; i < 8; i++)
|
|||
|
checksum += inb(ioaddr + 8 + i);
|
|||
|
if ((checksum & 0xff) != 0xFF)
|
|||
|
return ENODEV;
|
|||
|
|
|||
|
printk("%s: SMC Ultra at %#3x,", dev->name, ioaddr);
|
|||
|
for (i = 0; i < 6; i++)
|
|||
|
printk(" %2.2X", station_addr[i] = inb(ioaddr + 8 + i));
|
|||
|
|
|||
|
/* Switch from the station address to the alternate register set and
|
|||
|
read the useful registers there. */
|
|||
|
outb(0x80 | reg4, ioaddr + 4);
|
|||
|
|
|||
|
/* Enabled FINE16 mode to avoid BIOS ROM width mismatches during reboot. */
|
|||
|
outb(0x80 | inb(ioaddr + 0x0c), ioaddr + 0x0c);
|
|||
|
irqreg = inb(ioaddr + 0xd);
|
|||
|
addr = inb(ioaddr + 0xb);
|
|||
|
|
|||
|
/* Switch back to the station address register set so that the MS-DOS driver
|
|||
|
can find the card after a warm boot. */
|
|||
|
outb(reg4, ioaddr + 4);
|
|||
|
|
|||
|
model_name = "SMC Ultra";
|
|||
|
|
|||
|
if (dev->irq < 2) {
|
|||
|
unsigned char irqmap[] = {0, 9, 3, 5, 7, 10, 11, 15};
|
|||
|
int irq;
|
|||
|
|
|||
|
/* The IRQ bits are split. */
|
|||
|
irq = irqmap[((irqreg & 0x40) >> 4) + ((irqreg & 0x0c) >> 2)];
|
|||
|
|
|||
|
if (irq == 0) {
|
|||
|
printk(", failed to detect IRQ line.\n");
|
|||
|
return -EAGAIN;
|
|||
|
}
|
|||
|
dev->irq = irq;
|
|||
|
eeprom_irq = 1;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* OK, were are certain this is going to work. Setup the device. */
|
|||
|
snarf_region(ioaddr, 32);
|
|||
|
|
|||
|
/* The 8390 isn't at the base address, so fake the offset */
|
|||
|
dev->base_addr = ioaddr+ULTRA_NIC_OFFSET;
|
|||
|
|
|||
|
{
|
|||
|
int addr_tbl[4] = {0x0C0000, 0x0D0000, 0xFC0000, 0xFD0000};
|
|||
|
short num_pages_tbl[4] = {0x20, 0x40, 0x80, 0xff};
|
|||
|
|
|||
|
dev->mem_start = ((addr & 0x0f) << 13) + addr_tbl[(addr >> 6) & 3] ;
|
|||
|
num_pages = num_pages_tbl[(addr >> 4) & 3];
|
|||
|
}
|
|||
|
|
|||
|
ethdev_init(dev);
|
|||
|
|
|||
|
ei_status.name = model_name;
|
|||
|
ei_status.word16 = 1;
|
|||
|
ei_status.tx_start_page = START_PG;
|
|||
|
ei_status.rx_start_page = START_PG + TX_PAGES;
|
|||
|
ei_status.stop_page = num_pages;
|
|||
|
|
|||
|
dev->rmem_start = dev->mem_start + TX_PAGES*256;
|
|||
|
dev->mem_end = dev->rmem_end
|
|||
|
= dev->mem_start + (ei_status.stop_page - START_PG)*256;
|
|||
|
|
|||
|
printk(",%s IRQ %d memory %#x-%#x.\n", eeprom_irq ? "" : "assigned ",
|
|||
|
dev->irq, dev->mem_start, dev->mem_end-1);
|
|||
|
if (ei_debug > 0)
|
|||
|
printk(version);
|
|||
|
|
|||
|
ei_status.reset_8390 = &ultra_reset_8390;
|
|||
|
ei_status.block_input = &ultra_block_input;
|
|||
|
ei_status.block_output = &ultra_block_output;
|
|||
|
dev->open = &ultra_open;
|
|||
|
dev->stop = &ultra_close_card;
|
|||
|
NS8390_init(dev, 0);
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ultra_open(struct device *dev)
|
|||
|
{
|
|||
|
int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC addr */
|
|||
|
|
|||
|
if (irqaction(dev->irq, &ei_sigaction))
|
|||
|
return -EAGAIN;
|
|||
|
|
|||
|
outb(ULTRA_MEMENB, ioaddr); /* Enable memory, 16 bit mode. */
|
|||
|
outb(0x80, ioaddr + 5);
|
|||
|
outb(0x01, ioaddr + 6); /* Enable interrupts and memory. */
|
|||
|
return ei_open(dev);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
ultra_reset_8390(struct device *dev)
|
|||
|
{
|
|||
|
int cmd_port = dev->base_addr - ULTRA_NIC_OFFSET; /* ASIC base addr */
|
|||
|
|
|||
|
outb(ULTRA_RESET, cmd_port);
|
|||
|
if (ei_debug > 1) printk("resetting Ultra, t=%d...", jiffies);
|
|||
|
ei_status.txing = 0;
|
|||
|
|
|||
|
outb(ULTRA_MEMENB, cmd_port);
|
|||
|
|
|||
|
if (ei_debug > 1) printk("reset done\n");
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/* Block input and output are easy on shared memory ethercards, the only
|
|||
|
complication is when the ring buffer wraps. */
|
|||
|
|
|||
|
static int
|
|||
|
ultra_block_input(struct device *dev, int count, char *buf, int ring_offset)
|
|||
|
{
|
|||
|
void *xfer_start = (void *)(dev->mem_start + ring_offset
|
|||
|
- (START_PG<<8));
|
|||
|
|
|||
|
if (xfer_start + count > (void*) dev->rmem_end) {
|
|||
|
/* We must wrap the input move. */
|
|||
|
int semi_count = (void*)dev->rmem_end - xfer_start;
|
|||
|
memcpy(buf, xfer_start, semi_count);
|
|||
|
count -= semi_count;
|
|||
|
memcpy(buf + semi_count, (char *)dev->rmem_start, count);
|
|||
|
return dev->rmem_start + count;
|
|||
|
}
|
|||
|
memcpy(buf, xfer_start, count);
|
|||
|
|
|||
|
return ring_offset + count;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
ultra_block_output(struct device *dev, int count, const unsigned char *buf,
|
|||
|
int start_page)
|
|||
|
{
|
|||
|
unsigned char *shmem
|
|||
|
= (unsigned char *)dev->mem_start + ((start_page - START_PG)<<8);
|
|||
|
|
|||
|
memcpy(shmem, buf, count);
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
ultra_close_card(struct device *dev)
|
|||
|
{
|
|||
|
int ioaddr = dev->base_addr - ULTRA_NIC_OFFSET; /* CMDREG */
|
|||
|
|
|||
|
dev->start = 0;
|
|||
|
dev->tbusy = 1;
|
|||
|
|
|||
|
if (ei_debug > 1)
|
|||
|
printk("%s: Shutting down ethercard.\n", dev->name);
|
|||
|
|
|||
|
outb(0x00, ioaddr + 6); /* Disable interrupts. */
|
|||
|
free_irq(dev->irq);
|
|||
|
irq2dev_map[dev->irq] = 0;
|
|||
|
|
|||
|
NS8390_init(dev, 0);
|
|||
|
|
|||
|
/* We should someday disable shared memory and change to 8-bit mode
|
|||
|
"just in case"... */
|
|||
|
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* Local variables:
|
|||
|
* compile-command: "gcc -D__KERNEL__ -Wall -O6 -I/usr/src/linux/net/inet -c smc-ultra.c"
|
|||
|
* version-control: t
|
|||
|
* kept-new-versions: 5
|
|||
|
* End:
|
|||
|
*/
|