Skip to content
Snippets Groups Projects
3c505.c 47.4 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
	 */
	adapter->tx_pcb.command = CMD_TRANSMIT_PACKET;
	adapter->tx_pcb.length = sizeof(struct Xmit_pkt);
	adapter->tx_pcb.data.xmit_pkt.buf_ofs
	    = adapter->tx_pcb.data.xmit_pkt.buf_seg = 0;	/* Unused */
	adapter->tx_pcb.data.xmit_pkt.pkt_len = nlen;

	if (!send_pcb(dev, &adapter->tx_pcb)) {
		adapter->busy = 0;
Linus Torvalds's avatar
Linus Torvalds committed
	}
	/* if this happens, we die */
	if (test_and_set_bit(0, (void *) &adapter->dmaing))
		pr_debug("%s: tx: DMA %d in progress\n", dev->name, adapter->current_dma.direction);
Linus Torvalds's avatar
Linus Torvalds committed

	adapter->current_dma.direction = 1;
	adapter->current_dma.start_time = jiffies;

	if ((unsigned long)(skb->data + nlen) >= MAX_DMA_ADDRESS || nlen != skb->len) {
		skb_copy_from_linear_data(skb, adapter->dma_buffer, nlen);
Linus Torvalds's avatar
Linus Torvalds committed
		memset(adapter->dma_buffer+skb->len, 0, nlen-skb->len);
		target = isa_virt_to_bus(adapter->dma_buffer);
	}
	else {
		target = isa_virt_to_bus(skb->data);
	}
	adapter->current_dma.skb = skb;

	flags=claim_dma_lock();
	disable_dma(dev->dma);
	clear_dma_ff(dev->dma);
	set_dma_mode(dev->dma, 0x48);	/* dma memory -> io */
	set_dma_addr(dev->dma, target);
	set_dma_count(dev->dma, nlen);
	outb_control(adapter->hcr_val | DMAE | TCEN, dev);
	enable_dma(dev->dma);
	release_dma_lock(flags);
Linus Torvalds's avatar
Linus Torvalds committed
	if (elp_debug >= 3)
		pr_debug("%s: DMA transfer started\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed

Linus Torvalds's avatar
Linus Torvalds committed
}

/*
 *	The upper layer thinks we timed out
 */
Linus Torvalds's avatar
Linus Torvalds committed
static void elp_timeout(struct net_device *dev)
{
	int stat;

	stat = inb_status(dev->base_addr);
	pr_warning("%s: transmit timed out, lost %s?\n", dev->name,
		   (stat & ACRF) ? "interrupt" : "command");
Linus Torvalds's avatar
Linus Torvalds committed
	if (elp_debug >= 1)
		pr_debug("%s: status %#02x\n", dev->name, stat);
Eric Dumazet's avatar
Eric Dumazet committed
	dev->trans_start = jiffies; /* prevent tx timeout */
	dev->stats.tx_dropped++;
Linus Torvalds's avatar
Linus Torvalds committed
	netif_wake_queue(dev);
}

/******************************************************
 *
 * start the transmitter
 *    return 0 if sent OK, else return 1
 *
 ******************************************************/

static netdev_tx_t elp_start_xmit(struct sk_buff *skb, struct net_device *dev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	unsigned long flags;
	elp_device *adapter = netdev_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed
	spin_lock_irqsave(&adapter->lock, flags);
	check_3c505_dma(dev);

	if (elp_debug >= 3)
		pr_debug("%s: request to send packet of length %d\n", dev->name, (int) skb->len);
Linus Torvalds's avatar
Linus Torvalds committed

	netif_stop_queue(dev);
Linus Torvalds's avatar
Linus Torvalds committed
	/*
	 * send the packet at skb->data for skb->len
	 */
	if (!send_packet(dev, skb)) {
		if (elp_debug >= 2) {
			pr_debug("%s: failed to transmit packet\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
		}
		spin_unlock_irqrestore(&adapter->lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed
	}
	if (elp_debug >= 3)
		pr_debug("%s: packet of length %d sent\n", dev->name, (int) skb->len);
Linus Torvalds's avatar
Linus Torvalds committed

	prime_rx(dev);
	spin_unlock_irqrestore(&adapter->lock, flags);
	netif_start_queue(dev);
Linus Torvalds's avatar
Linus Torvalds committed
}

/******************************************************
 *
 * return statistics on the board
 *
 ******************************************************/

static struct net_device_stats *elp_get_stats(struct net_device *dev)
{
	elp_device *adapter = netdev_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	if (elp_debug >= 3)
		pr_debug("%s: request for stats\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed

	/* If the device is closed, just return the latest stats we have,
	   - we cannot ask from the adapter without interrupts */
	if (!netif_running(dev))
		return &dev->stats;
Linus Torvalds's avatar
Linus Torvalds committed

	/* send a get statistics command to the board */
	adapter->tx_pcb.command = CMD_NETWORK_STATISTICS;
	adapter->tx_pcb.length = 0;
	adapter->got[CMD_NETWORK_STATISTICS] = 0;
	if (!send_pcb(dev, &adapter->tx_pcb))
		pr_err("%s: couldn't send get statistics command\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
	else {
		unsigned long timeout = jiffies + TIMEOUT;
		while (adapter->got[CMD_NETWORK_STATISTICS] == 0 && time_before(jiffies, timeout));
		if (time_after_eq(jiffies, timeout)) {
			TIMEOUT_MSG(__LINE__);
			return &dev->stats;
Linus Torvalds's avatar
Linus Torvalds committed
		}
	}

	/* statistics are now up to date */
	return &dev->stats;
Linus Torvalds's avatar
Linus Torvalds committed
}


static void netdev_get_drvinfo(struct net_device *dev,
			       struct ethtool_drvinfo *info)
{
	strcpy(info->driver, DRV_NAME);
	strcpy(info->version, DRV_VERSION);
	sprintf(info->bus_info, "ISA 0x%lx", dev->base_addr);
}

static u32 netdev_get_msglevel(struct net_device *dev)
{
	return debug;
}

static void netdev_set_msglevel(struct net_device *dev, u32 level)
{
	debug = level;
}

static const struct ethtool_ops netdev_ethtool_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
	.get_drvinfo		= netdev_get_drvinfo,
	.get_msglevel		= netdev_get_msglevel,
	.set_msglevel		= netdev_set_msglevel,
};

/******************************************************
 *
 * close the board
 *
 ******************************************************/

static int elp_close(struct net_device *dev)
{
	elp_device *adapter = netdev_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	if (elp_debug >= 3)
		pr_debug("%s: request to close device\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed

	netif_stop_queue(dev);

	/* Someone may request the device statistic information even when
	 * the interface is closed. The following will update the statistics
	 * structure in the driver, so we'll be able to give current statistics.
	 */
	(void) elp_get_stats(dev);

	/*
	 * disable interrupts on the board
	 */
	outb_control(0, dev);

	/*
	 * release the IRQ
	 */
	free_irq(dev->irq, dev);

	free_dma(dev->dma);
	free_pages((unsigned long) adapter->dma_buffer, get_order(DMA_BUFFER_SIZE));

	return 0;
}


/************************************************************
 *
 * Set multicast list
 * num_addrs==0: clear mc_list
 * num_addrs==-1: set promiscuous mode
 * num_addrs>0: set mc_list
 *
 ************************************************************/

static void elp_set_mc_list(struct net_device *dev)
{
	elp_device *adapter = netdev_priv(dev);
	struct netdev_hw_addr *ha;
Linus Torvalds's avatar
Linus Torvalds committed
	int i;
	unsigned long flags;

	if (elp_debug >= 3)
		pr_debug("%s: request to set multicast list\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock_irqsave(&adapter->lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!(dev->flags & (IFF_PROMISC | IFF_ALLMULTI))) {
		/* send a "load multicast list" command to the board, max 10 addrs/cmd */
		/* if num_addrs==0 the list will be cleared */
		adapter->tx_pcb.command = CMD_LOAD_MULTICAST_LIST;
		adapter->tx_pcb.length = 6 * netdev_mc_count(dev);
		i = 0;
		netdev_for_each_mc_addr(ha, dev)
			memcpy(adapter->tx_pcb.data.multicast[i++],
			       ha->addr, 6);
Linus Torvalds's avatar
Linus Torvalds committed
		adapter->got[CMD_LOAD_MULTICAST_LIST] = 0;
		if (!send_pcb(dev, &adapter->tx_pcb))
			pr_err("%s: couldn't send set_multicast command\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
		else {
			unsigned long timeout = jiffies + TIMEOUT;
			while (adapter->got[CMD_LOAD_MULTICAST_LIST] == 0 && time_before(jiffies, timeout));
			if (time_after_eq(jiffies, timeout)) {
				TIMEOUT_MSG(__LINE__);
			}
		}
		if (!netdev_mc_empty(dev))
Linus Torvalds's avatar
Linus Torvalds committed
			adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_BROAD | RECV_MULTI;
		else		/* num_addrs == 0 */
			adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_BROAD;
	} else
		adapter->tx_pcb.data.configure = NO_LOOPBACK | RECV_PROMISC;
	/*
	 * configure adapter to receive messages (as specified above)
	 * and wait for response
	 */
	if (elp_debug >= 3)
		pr_debug("%s: sending 82586 configure command\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
	adapter->tx_pcb.command = CMD_CONFIGURE_82586;
	adapter->tx_pcb.length = 2;
	adapter->got[CMD_CONFIGURE_82586] = 0;
	if (!send_pcb(dev, &adapter->tx_pcb))
	{
		spin_unlock_irqrestore(&adapter->lock, flags);
		pr_err("%s: couldn't send 82586 configure command\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
	}
	else {
		unsigned long timeout = jiffies + TIMEOUT;
		spin_unlock_irqrestore(&adapter->lock, flags);
		while (adapter->got[CMD_CONFIGURE_82586] == 0 && time_before(jiffies, timeout));
		if (time_after_eq(jiffies, timeout))
			TIMEOUT_MSG(__LINE__);
	}
}

/************************************************************
 *
 * A couple of tests to see if there's 3C505 or not
 * Called only by elp_autodetect
 ************************************************************/

static int __init elp_sense(struct net_device *dev)
{
	int addr = dev->base_addr;
	const char *name = dev->name;
	byte orig_HSR;

	if (!request_region(addr, ELP_IO_EXTENT, "3c505"))
		return -ENODEV;

	orig_HSR = inb_status(addr);

	if (elp_debug > 0)
		pr_debug(search_msg, name, addr);
Linus Torvalds's avatar
Linus Torvalds committed

	if (orig_HSR == 0xff) {
		if (elp_debug > 0)
			pr_cont(notfound_msg, 1);
Linus Torvalds's avatar
Linus Torvalds committed
		goto out;
	}

	/* Wait for a while; the adapter may still be booting up */
	if (elp_debug > 0)
		pr_cont(stilllooking_msg);
Linus Torvalds's avatar
Linus Torvalds committed

	if (orig_HSR & DIR) {
		/* If HCR.DIR is up, we pull it down. HSR.DIR should follow. */
		outb(0, dev->base_addr + PORT_CONTROL);
Linus Torvalds's avatar
Linus Torvalds committed
		if (inb_status(addr) & DIR) {
			if (elp_debug > 0)
				pr_cont(notfound_msg, 2);
Linus Torvalds's avatar
Linus Torvalds committed
			goto out;
		}
	} else {
		/* If HCR.DIR is down, we pull it up. HSR.DIR should follow. */
		outb(DIR, dev->base_addr + PORT_CONTROL);
Linus Torvalds's avatar
Linus Torvalds committed
		if (!(inb_status(addr) & DIR)) {
			if (elp_debug > 0)
				pr_cont(notfound_msg, 3);
Linus Torvalds's avatar
Linus Torvalds committed
			goto out;
		}
	}
	/*
	 * It certainly looks like a 3c505.
	 */
	if (elp_debug > 0)
		pr_cont(found_msg);
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;
out:
	release_region(addr, ELP_IO_EXTENT);
	return -ENODEV;
}

/*************************************************************
 *
 * Search through addr_list[] and try to find a 3C505
 * Called only by eplus_probe
 *************************************************************/

static int __init elp_autodetect(struct net_device *dev)
{
	int idx = 0;

	/* if base address set, then only check that address
	   otherwise, run through the table */
	if (dev->base_addr != 0) {	/* dev->base_addr == 0 ==> plain autodetect */
		if (elp_sense(dev) == 0)
			return dev->base_addr;
	} else
		while ((dev->base_addr = addr_list[idx++])) {
			if (elp_sense(dev) == 0)
				return dev->base_addr;
		}

	/* could not find an adapter */
	if (elp_debug > 0)
		pr_debug(couldnot_msg, dev->name);
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;		/* Because of this, the layer above will return -ENODEV */
}

static const struct net_device_ops elp_netdev_ops = {
	.ndo_open		= elp_open,
	.ndo_stop		= elp_close,
	.ndo_get_stats 		= elp_get_stats,
	.ndo_start_xmit		= elp_start_xmit,
	.ndo_tx_timeout 	= elp_timeout,
	.ndo_set_multicast_list = elp_set_mc_list,
	.ndo_change_mtu		= eth_change_mtu,
	.ndo_set_mac_address 	= eth_mac_addr,
	.ndo_validate_addr	= eth_validate_addr,
};
Linus Torvalds's avatar
Linus Torvalds committed

/******************************************************
 *
 * probe for an Etherlink Plus board at the specified address
 *
 ******************************************************/

/* There are three situations we need to be able to detect here:

 *  a) the card is idle
 *  b) the card is still booting up
 *  c) the card is stuck in a strange state (some DOS drivers do this)
 *
 * In case (a), all is well.  In case (b), we wait 10 seconds to see if the
 * card finishes booting, and carry on if so.  In case (c), we do a hard reset,
 * loop round, and hope for the best.
 *
 * This is all very unpleasant, but hopefully avoids the problems with the old
 * probe code (which had a 15-second delay if the card was idle, and didn't
 * work at all if it was in a weird state).
 */

static int __init elplus_setup(struct net_device *dev)
{
	elp_device *adapter = netdev_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed
	int i, tries, tries1, okay;
	unsigned long timeout;
	unsigned long cookie = 0;
	int err = -ENODEV;

	/*
	 *  setup adapter structure
	 */

	dev->base_addr = elp_autodetect(dev);
	if (!dev->base_addr)
		return -ENODEV;

	adapter->send_pcb_semaphore = 0;

	for (tries1 = 0; tries1 < 3; tries1++) {
		outb_control((adapter->hcr_val | CMDE) & ~DIR, dev);
		/* First try to write just one byte, to see if the card is
		 * responding at all normally.
		 */
		timeout = jiffies + 5*HZ/100;
		okay = 0;
		while (time_before(jiffies, timeout) && !(inb_status(dev->base_addr) & HCRE));
		if ((inb_status(dev->base_addr) & HCRE)) {
			outb_command(0, dev->base_addr);	/* send a spurious byte */
			timeout = jiffies + 5*HZ/100;
			while (time_before(jiffies, timeout) && !(inb_status(dev->base_addr) & HCRE));
			if (inb_status(dev->base_addr) & HCRE)
				okay = 1;
		}
		if (!okay) {
			/* Nope, it's ignoring the command register.  This means that
			 * either it's still booting up, or it's died.
			 */
			pr_err("%s: command register wouldn't drain, ", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
			if ((inb_status(dev->base_addr) & 7) == 3) {
				/* If the adapter status is 3, it *could* still be booting.
				 * Give it the benefit of the doubt for 10 seconds.
				 */
				pr_cont("assuming 3c505 still starting\n");
Linus Torvalds's avatar
Linus Torvalds committed
				timeout = jiffies + 10*HZ;
				while (time_before(jiffies, timeout) && (inb_status(dev->base_addr) & 7));
				if (inb_status(dev->base_addr) & 7) {
					pr_err("%s: 3c505 failed to start\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
				} else {
					okay = 1;  /* It started */
				}
			} else {
				/* Otherwise, it must just be in a strange
				 * state.  We probably need to kick it.
				 */
				pr_cont("3c505 is sulking\n");
Linus Torvalds's avatar
Linus Torvalds committed
			}
		}
		for (tries = 0; tries < 5 && okay; tries++) {

			/*
			 * Try to set the Ethernet address, to make sure that the board
			 * is working.
			 */
			adapter->tx_pcb.command = CMD_STATION_ADDRESS;
			adapter->tx_pcb.length = 0;
			cookie = probe_irq_on();
			if (!send_pcb(dev, &adapter->tx_pcb)) {
				pr_err("%s: could not send first PCB\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
				probe_irq_off(cookie);
				continue;
			}
			if (!receive_pcb(dev, &adapter->rx_pcb)) {
				pr_err("%s: could not read first PCB\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
				probe_irq_off(cookie);
				continue;
			}
			if ((adapter->rx_pcb.command != CMD_ADDRESS_RESPONSE) ||
			    (adapter->rx_pcb.length != 6)) {
				pr_err("%s: first PCB wrong (%d, %d)\n", dev->name,
					adapter->rx_pcb.command, adapter->rx_pcb.length);
Linus Torvalds's avatar
Linus Torvalds committed
				probe_irq_off(cookie);
				continue;
			}
			goto okay;
		}
		/* It's broken.  Do a hard reset to re-initialise the board,
		 * and try again.
		 */
		pr_info("%s: resetting adapter\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
		outb_control(adapter->hcr_val | FLSH | ATTN, dev);
		outb_control(adapter->hcr_val & ~(FLSH | ATTN), dev);
	}
	pr_err("%s: failed to initialise 3c505\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
	goto out;

      okay:
	if (dev->irq) {		/* Is there a preset IRQ? */
		int rpt = probe_irq_off(cookie);
		if (dev->irq != rpt) {
			pr_warning("%s: warning, irq %d configured but %d detected\n", dev->name, dev->irq, rpt);
Linus Torvalds's avatar
Linus Torvalds committed
		}
		/* if dev->irq == probe_irq_off(cookie), all is well */
	} else		       /* No preset IRQ; just use what we can detect */
		dev->irq = probe_irq_off(cookie);
	switch (dev->irq) {    /* Legal, sane? */
	case 0:
		pr_err("%s: IRQ probe failed: check 3c505 jumpers.\n",
Linus Torvalds's avatar
Linus Torvalds committed
		       dev->name);
		goto out;
	case 1:
	case 6:
	case 8:
	case 13:
		pr_err("%s: Impossible IRQ %d reported by probe_irq_off().\n",
Linus Torvalds's avatar
Linus Torvalds committed
		       dev->name, dev->irq);
		       goto out;
	}
	/*
	 *  Now we have the IRQ number so we can disable the interrupts from
	 *  the board until the board is opened.
	 */
	outb_control(adapter->hcr_val & ~CMDE, dev);

	/*
	 * copy Ethernet address into structure
	 */
	for (i = 0; i < 6; i++)
		dev->dev_addr[i] = adapter->rx_pcb.data.eth_addr[i];

	/* find a DMA channel */
	if (!dev->dma) {
		if (dev->mem_start) {
			dev->dma = dev->mem_start & 7;
		}
		else {
			pr_warning("%s: warning, DMA channel not specified, using default\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
			dev->dma = ELP_DMA;
		}
	}

	/*
	 * print remainder of startup message
	 */
	pr_info("%s: 3c505 at %#lx, irq %d, dma %d, addr %pM, ",
		dev->name, dev->base_addr, dev->irq, dev->dma, dev->dev_addr);
Linus Torvalds's avatar
Linus Torvalds committed
	/*
	 * read more information from the adapter
	 */

	adapter->tx_pcb.command = CMD_ADAPTER_INFO;
	adapter->tx_pcb.length = 0;
	if (!send_pcb(dev, &adapter->tx_pcb) ||
	    !receive_pcb(dev, &adapter->rx_pcb) ||
	    (adapter->rx_pcb.command != CMD_ADAPTER_INFO_RESPONSE) ||
	    (adapter->rx_pcb.length != 10)) {
		pr_cont("not responding to second PCB\n");
Linus Torvalds's avatar
Linus Torvalds committed
	}
	pr_cont("rev %d.%d, %dk\n", adapter->rx_pcb.data.info.major_vers,
		adapter->rx_pcb.data.info.minor_vers, adapter->rx_pcb.data.info.RAM_sz);
Linus Torvalds's avatar
Linus Torvalds committed

	/*
	 * reconfigure the adapter memory to better suit our purposes
	 */
	adapter->tx_pcb.command = CMD_CONFIGURE_ADAPTER_MEMORY;
	adapter->tx_pcb.length = 12;
	adapter->tx_pcb.data.memconf.cmd_q = 8;
	adapter->tx_pcb.data.memconf.rcv_q = 8;
	adapter->tx_pcb.data.memconf.mcast = 10;
	adapter->tx_pcb.data.memconf.frame = 10;
	adapter->tx_pcb.data.memconf.rcv_b = 10;
	adapter->tx_pcb.data.memconf.progs = 0;
	if (!send_pcb(dev, &adapter->tx_pcb) ||
	    !receive_pcb(dev, &adapter->rx_pcb) ||
	    (adapter->rx_pcb.command != CMD_CONFIGURE_ADAPTER_RESPONSE) ||
	    (adapter->rx_pcb.length != 2)) {
		pr_err("%s: could not configure adapter memory\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
	}
	if (adapter->rx_pcb.data.configure) {
		pr_err("%s: adapter configuration failed\n", dev->name);
	dev->netdev_ops = &elp_netdev_ops;
Linus Torvalds's avatar
Linus Torvalds committed
	dev->watchdog_timeo = 10*HZ;
	dev->ethtool_ops = &netdev_ethtool_ops;		/* local */

	dev->mem_start = dev->mem_end = 0;

	err = register_netdev(dev);
	if (err)
		goto out;

	return 0;
out:
	release_region(dev->base_addr, ELP_IO_EXTENT);
	return err;
}

#ifndef MODULE
struct net_device * __init elplus_probe(int unit)
{
	struct net_device *dev = alloc_etherdev(sizeof(elp_device));
	int err;
	if (!dev)
		return ERR_PTR(-ENOMEM);

	sprintf(dev->name, "eth%d", unit);
	netdev_boot_setup_check(dev);

	err = elplus_setup(dev);
	if (err) {
		free_netdev(dev);
		return ERR_PTR(err);
	}
	return dev;
}

#else
static struct net_device *dev_3c505[ELP_MAX_CARDS];
static int io[ELP_MAX_CARDS];
static int irq[ELP_MAX_CARDS];
static int dma[ELP_MAX_CARDS];
module_param_array(io, int, NULL, 0);
module_param_array(irq, int, NULL, 0);
module_param_array(dma, int, NULL, 0);
MODULE_PARM_DESC(io, "EtherLink Plus I/O base address(es)");
MODULE_PARM_DESC(irq, "EtherLink Plus IRQ number(s) (assigned)");
MODULE_PARM_DESC(dma, "EtherLink Plus DMA channel(s)");

int __init init_module(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int this_dev, found = 0;

	for (this_dev = 0; this_dev < ELP_MAX_CARDS; this_dev++) {
		struct net_device *dev = alloc_etherdev(sizeof(elp_device));
		if (!dev)
			break;

		dev->irq = irq[this_dev];
		dev->base_addr = io[this_dev];
		if (dma[this_dev]) {
			dev->dma = dma[this_dev];
		} else {
			dev->dma = ELP_DMA;
			pr_warning("3c505.c: warning, using default DMA channel,\n");
Linus Torvalds's avatar
Linus Torvalds committed
		}
		if (io[this_dev] == 0) {
			if (this_dev) {
				free_netdev(dev);
				break;
			}
			pr_notice("3c505.c: module autoprobe not recommended, give io=xx.\n");
Linus Torvalds's avatar
Linus Torvalds committed
		}
		if (elplus_setup(dev) != 0) {
			pr_warning("3c505.c: Failed to register card at 0x%x.\n", io[this_dev]);
Linus Torvalds's avatar
Linus Torvalds committed
			free_netdev(dev);
			break;
		}
		dev_3c505[this_dev] = dev;
		found++;
	}
	if (!found)
		return -ENODEV;
	return 0;
}

void __exit cleanup_module(void)
Linus Torvalds's avatar
Linus Torvalds committed
{
	int this_dev;

	for (this_dev = 0; this_dev < ELP_MAX_CARDS; this_dev++) {
		struct net_device *dev = dev_3c505[this_dev];
		if (dev) {
			unregister_netdev(dev);
			release_region(dev->base_addr, ELP_IO_EXTENT);
			free_netdev(dev);
		}
	}
}

#endif				/* MODULE */
MODULE_LICENSE("GPL");