Skip to content
Snippets Groups Projects
3c509.c 41.9 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
	 *	stuff.
	 */
Linus Torvalds's avatar
Linus Torvalds committed
	spin_lock_irqsave(&lp->lock, flags);
	update_stats(dev);
	spin_unlock_irqrestore(&lp->lock, flags);
	return &dev->stats;
Linus Torvalds's avatar
Linus Torvalds committed
}

/*  Update statistics.  We change to register window 6, so this should be run
	single-threaded if the device is active. This is expected to be a rare
	operation, and it's simpler for the rest of the driver to assume that
	window 1 is always valid rather than use a special window-state variable.
	*/
static void update_stats(struct net_device *dev)
{
	int ioaddr = dev->base_addr;

	if (el3_debug > 5)
		pr_debug("   Updating the statistics.\n");
Linus Torvalds's avatar
Linus Torvalds committed
	/* Turn off statistics updates while reading. */
	outw(StatsDisable, ioaddr + EL3_CMD);
	/* Switch to the stats window, and read everything. */
	EL3WINDOW(6);
	dev->stats.tx_carrier_errors 	+= inb(ioaddr + 0);
	dev->stats.tx_heartbeat_errors	+= inb(ioaddr + 1);
Linus Torvalds's avatar
Linus Torvalds committed
	/* Multiple collisions. */	   inb(ioaddr + 2);
	dev->stats.collisions		+= inb(ioaddr + 3);
	dev->stats.tx_window_errors	+= inb(ioaddr + 4);
	dev->stats.rx_fifo_errors	+= inb(ioaddr + 5);
	dev->stats.tx_packets		+= inb(ioaddr + 6);
Linus Torvalds's avatar
Linus Torvalds committed
	/* Rx packets	*/		   inb(ioaddr + 7);
	/* Tx deferrals */		   inb(ioaddr + 8);
	inw(ioaddr + 10);	/* Total Rx and Tx octets. */
	inw(ioaddr + 12);

	/* Back to window 1, and turn statistics back on. */
	EL3WINDOW(1);
	outw(StatsEnable, ioaddr + EL3_CMD);
}

static int
el3_rx(struct net_device *dev)
{
	int ioaddr = dev->base_addr;
	short rx_status;

	if (el3_debug > 5)
		pr_debug("   In rx_packet(), status %4.4x, rx_status %4.4x.\n",
Linus Torvalds's avatar
Linus Torvalds committed
			   inw(ioaddr+EL3_STATUS), inw(ioaddr+RX_STATUS));
	while ((rx_status = inw(ioaddr + RX_STATUS)) > 0) {
		if (rx_status & 0x4000) { /* Error, update stats. */
			short error = rx_status & 0x3800;

			outw(RxDiscard, ioaddr + EL3_CMD);
			dev->stats.rx_errors++;
Linus Torvalds's avatar
Linus Torvalds committed
			switch (error) {
			case 0x0000:		dev->stats.rx_over_errors++; break;
			case 0x0800:		dev->stats.rx_length_errors++; break;
			case 0x1000:		dev->stats.rx_frame_errors++; break;
			case 0x1800:		dev->stats.rx_length_errors++; break;
			case 0x2000:		dev->stats.rx_frame_errors++; break;
			case 0x2800:		dev->stats.rx_crc_errors++; break;
Linus Torvalds's avatar
Linus Torvalds committed
			}
		} else {
			short pkt_len = rx_status & 0x7ff;
			struct sk_buff *skb;

			skb = dev_alloc_skb(pkt_len+5);
			if (el3_debug > 4)
				pr_debug("Receiving packet size %d status %4.4x.\n",
Linus Torvalds's avatar
Linus Torvalds committed
					   pkt_len, rx_status);
			if (skb != NULL) {
				skb_reserve(skb, 2);     /* Align IP on 16 byte */

				/* 'skb->data' points to the start of sk_buff data area. */
				insl(ioaddr + RX_FIFO, skb_put(skb,pkt_len),
					 (pkt_len + 3) >> 2);

				outw(RxDiscard, ioaddr + EL3_CMD); /* Pop top Rx packet. */
				skb->protocol = eth_type_trans(skb,dev);
				netif_rx(skb);
				dev->stats.rx_bytes += pkt_len;
				dev->stats.rx_packets++;
Linus Torvalds's avatar
Linus Torvalds committed
				continue;
			}
			outw(RxDiscard, ioaddr + EL3_CMD);
			dev->stats.rx_dropped++;
Linus Torvalds's avatar
Linus Torvalds committed
			if (el3_debug)
				pr_debug("%s: Couldn't allocate a sk_buff of size %d.\n",
Linus Torvalds's avatar
Linus Torvalds committed
					   dev->name, pkt_len);
		}
		inw(ioaddr + EL3_STATUS); 				/* Delay. */
		while (inw(ioaddr + EL3_STATUS) & 0x1000)
			pr_debug("	Waiting for 3c509 to discard packet, status %x.\n",
Linus Torvalds's avatar
Linus Torvalds committed
				   inw(ioaddr + EL3_STATUS) );
	}

	return 0;
}

/*
 *     Set or clear the multicast filter for this adaptor.
 */
static void
set_multicast_list(struct net_device *dev)
{
	unsigned long flags;
	struct el3_private *lp = netdev_priv(dev);
	int ioaddr = dev->base_addr;
	int mc_count = netdev_mc_count(dev);
Linus Torvalds's avatar
Linus Torvalds committed

	if (el3_debug > 1) {
		static int old;
		if (old != mc_count) {
			old = mc_count;
			pr_debug("%s: Setting Rx mode to %d addresses.\n",
				 dev->name, mc_count);
Linus Torvalds's avatar
Linus Torvalds committed
		}
	}
	spin_lock_irqsave(&lp->lock, flags);
	if (dev->flags&IFF_PROMISC) {
		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast | RxProm,
			 ioaddr + EL3_CMD);
	}
	else if (mc_count || (dev->flags&IFF_ALLMULTI)) {
Linus Torvalds's avatar
Linus Torvalds committed
		outw(SetRxFilter | RxStation | RxMulticast | RxBroadcast, ioaddr + EL3_CMD);
	}
	else
		outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD);
	spin_unlock_irqrestore(&lp->lock, flags);
}

static int
el3_close(struct net_device *dev)
{
	int ioaddr = dev->base_addr;
	struct el3_private *lp = netdev_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed
	if (el3_debug > 2)
		pr_debug("%s: Shutting down ethercard.\n", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed

	el3_down(dev);

	free_irq(dev->irq, dev);
	/* Switching back to window 0 disables the IRQ. */
	EL3WINDOW(0);
	if (lp->type != EL3_EISA) {
		/* But we explicitly zero the IRQ line select anyway. Don't do
		 * it on EISA cards, it prevents the module from getting an
		 * IRQ after unload+reload... */
		outw(0x0f00, ioaddr + WN0_IRQ);
	}

	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
el3_link_ok(struct net_device *dev)
{
	int ioaddr = dev->base_addr;
	u16 tmp;

	EL3WINDOW(4);
	tmp = inw(ioaddr + WN4_MEDIA);
	EL3WINDOW(1);
	return tmp & (1<<11);
}

static int
el3_netdev_get_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd)
{
	u16 tmp;
	int ioaddr = dev->base_addr;
Linus Torvalds's avatar
Linus Torvalds committed
	EL3WINDOW(0);
	/* obtain current transceiver via WN4_MEDIA? */
Linus Torvalds's avatar
Linus Torvalds committed
	tmp = inw(ioaddr + WN0_ADDR_CONF);
	ecmd->transceiver = XCVR_INTERNAL;
	switch (tmp >> 14) {
	case 0:
		ecmd->port = PORT_TP;
		break;
	case 1:
		ecmd->port = PORT_AUI;
		ecmd->transceiver = XCVR_EXTERNAL;
		break;
	case 3:
		ecmd->port = PORT_BNC;
	default:
		break;
	}

	ecmd->duplex = DUPLEX_HALF;
	ecmd->supported = 0;
	tmp = inw(ioaddr + WN0_CONF_CTRL);
	if (tmp & (1<<13))
		ecmd->supported |= SUPPORTED_AUI;
	if (tmp & (1<<12))
		ecmd->supported |= SUPPORTED_BNC;
	if (tmp & (1<<9)) {
		ecmd->supported |= SUPPORTED_TP | SUPPORTED_10baseT_Half |
				SUPPORTED_10baseT_Full;	/* hmm... */
		EL3WINDOW(4);
		tmp = inw(ioaddr + WN4_NETDIAG);
		if (tmp & FD_ENABLE)
			ecmd->duplex = DUPLEX_FULL;
	}

	ethtool_cmd_speed_set(ecmd, SPEED_10);
Linus Torvalds's avatar
Linus Torvalds committed
	EL3WINDOW(1);
	return 0;
}

static int
el3_netdev_set_ecmd(struct net_device *dev, struct ethtool_cmd *ecmd)
{
	u16 tmp;
	int ioaddr = dev->base_addr;

	if (ecmd->speed != SPEED_10)
		return -EINVAL;
	if ((ecmd->duplex != DUPLEX_HALF) && (ecmd->duplex != DUPLEX_FULL))
		return -EINVAL;
	if ((ecmd->transceiver != XCVR_INTERNAL) && (ecmd->transceiver != XCVR_EXTERNAL))
		return -EINVAL;

	/* change XCVR type */
	EL3WINDOW(0);
	tmp = inw(ioaddr + WN0_ADDR_CONF);
	switch (ecmd->port) {
	case PORT_TP:
		tmp &= ~(3<<14);
		dev->if_port = 0;
		break;
	case PORT_AUI:
		tmp |= (1<<14);
		dev->if_port = 1;
		break;
	case PORT_BNC:
		tmp |= (3<<14);
		dev->if_port = 3;
		break;
	default:
		return -EINVAL;
	}

	outw(tmp, ioaddr + WN0_ADDR_CONF);
	if (dev->if_port == 3) {
		/* fire up the DC-DC convertor if BNC gets enabled */
		tmp = inw(ioaddr + WN0_ADDR_CONF);
		if (tmp & (3 << 14)) {
			outw(StartCoax, ioaddr + EL3_CMD);
			udelay(800);
		} else
			return -EIO;
	}

	EL3WINDOW(4);
	tmp = inw(ioaddr + WN4_NETDIAG);
	if (ecmd->duplex == DUPLEX_FULL)
		tmp |= FD_ENABLE;
	else
		tmp &= ~FD_ENABLE;
	outw(tmp, ioaddr + WN4_NETDIAG);
	EL3WINDOW(1);

	return 0;
}

static void el3_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info)
{
	strcpy(info->driver, DRV_NAME);
	strcpy(info->version, DRV_VERSION);
}

static int el3_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
{
	struct el3_private *lp = netdev_priv(dev);
	int ret;

	spin_lock_irq(&lp->lock);
	ret = el3_netdev_get_ecmd(dev, ecmd);
	spin_unlock_irq(&lp->lock);
	return ret;
}

static int el3_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
{
	struct el3_private *lp = netdev_priv(dev);
	int ret;

	spin_lock_irq(&lp->lock);
	ret = el3_netdev_set_ecmd(dev, ecmd);
	spin_unlock_irq(&lp->lock);
	return ret;
}

static u32 el3_get_link(struct net_device *dev)
{
	struct el3_private *lp = netdev_priv(dev);
	u32 ret;

	spin_lock_irq(&lp->lock);
	ret = el3_link_ok(dev);
	spin_unlock_irq(&lp->lock);
	return ret;
}

static u32 el3_get_msglevel(struct net_device *dev)
{
	return el3_debug;
}

static void el3_set_msglevel(struct net_device *dev, u32 v)
{
	el3_debug = v;
}

static const struct ethtool_ops ethtool_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
	.get_drvinfo = el3_get_drvinfo,
	.get_settings = el3_get_settings,
	.set_settings = el3_set_settings,
	.get_link = el3_get_link,
	.get_msglevel = el3_get_msglevel,
	.set_msglevel = el3_set_msglevel,
};

static void
el3_down(struct net_device *dev)
{
	int ioaddr = dev->base_addr;

	netif_stop_queue(dev);

	/* Turn off statistics ASAP.  We update lp->stats below. */
	outw(StatsDisable, ioaddr + EL3_CMD);

	/* Disable the receiver and transmitter. */
	outw(RxDisable, ioaddr + EL3_CMD);
	outw(TxDisable, ioaddr + EL3_CMD);

	if (dev->if_port == 3)
		/* Turn off thinnet power.  Green! */
		outw(StopCoax, ioaddr + EL3_CMD);
	else if (dev->if_port == 0) {
		/* Disable link beat and jabber, if_port may change here next open(). */
		EL3WINDOW(4);
		outw(inw(ioaddr + WN4_MEDIA) & ~MEDIA_TP, ioaddr + WN4_MEDIA);
	}

	outw(SetIntrEnb | 0x0000, ioaddr + EL3_CMD);

	update_stats(dev);
}

static void
el3_up(struct net_device *dev)
{
	int i, sw_info, net_diag;
	int ioaddr = dev->base_addr;
Linus Torvalds's avatar
Linus Torvalds committed
	/* Activating the board required and does no harm otherwise */
	outw(0x0001, ioaddr + 4);

	/* Set the IRQ line. */
	outw((dev->irq << 12) | 0x0f00, ioaddr + WN0_IRQ);

	/* Set the station address in window 2 each time opened. */
	EL3WINDOW(2);

	for (i = 0; i < 6; i++)
		outb(dev->dev_addr[i], ioaddr + i);

	if ((dev->if_port & 0x03) == 3) /* BNC interface */
		/* Start the thinnet transceiver. We should really wait 50ms...*/
		outw(StartCoax, ioaddr + EL3_CMD);
	else if ((dev->if_port & 0x03) == 0) { /* 10baseT interface */
		/* Combine secondary sw_info word (the adapter level) and primary
			sw_info word (duplex setting plus other useless bits) */
		EL3WINDOW(0);
		sw_info = (read_eeprom(ioaddr, 0x14) & 0x400f) |
Linus Torvalds's avatar
Linus Torvalds committed
			(read_eeprom(ioaddr, 0x0d) & 0xBff0);

		EL3WINDOW(4);
		net_diag = inw(ioaddr + WN4_NETDIAG);
		net_diag = (net_diag | FD_ENABLE); /* temporarily assume full-duplex will be set */
		pr_info("%s: ", dev->name);
Linus Torvalds's avatar
Linus Torvalds committed
		switch (dev->if_port & 0x0c) {
			case 12:
				/* force full-duplex mode if 3c5x9b */
				if (sw_info & 0x000f) {
					pr_cont("Forcing 3c5x9b full-duplex mode");
Linus Torvalds's avatar
Linus Torvalds committed
					break;
				}
			case 8:
				/* set full-duplex mode based on eeprom config setting */
				if ((sw_info & 0x000f) && (sw_info & 0x8000)) {
					pr_cont("Setting 3c5x9b full-duplex mode (from EEPROM configuration bit)");
Linus Torvalds's avatar
Linus Torvalds committed
					break;
				}
			default:
				/* xcvr=(0 || 4) OR user has an old 3c5x9 non "B" model */
				pr_cont("Setting 3c5x9/3c5x9B half-duplex mode");
Linus Torvalds's avatar
Linus Torvalds committed
				net_diag = (net_diag & ~FD_ENABLE); /* disable full duplex */
		}

		outw(net_diag, ioaddr + WN4_NETDIAG);
		pr_cont(" if_port: %d, sw_info: %4.4x\n", dev->if_port, sw_info);
Linus Torvalds's avatar
Linus Torvalds committed
		if (el3_debug > 3)
			pr_debug("%s: 3c5x9 net diag word is now: %4.4x.\n", dev->name, net_diag);
Linus Torvalds's avatar
Linus Torvalds committed
		/* Enable link beat and jabber check. */
		outw(inw(ioaddr + WN4_MEDIA) | MEDIA_TP, ioaddr + WN4_MEDIA);
	}

	/* Switch to the stats window, and clear all stats by reading. */
	outw(StatsDisable, ioaddr + EL3_CMD);
	EL3WINDOW(6);
	for (i = 0; i < 9; i++)
		inb(ioaddr + i);
	inw(ioaddr + 10);
	inw(ioaddr + 12);

	/* Switch to register set 1 for normal use. */
	EL3WINDOW(1);

	/* Accept b-case and phys addr only. */
	outw(SetRxFilter | RxStation | RxBroadcast, ioaddr + EL3_CMD);
	outw(StatsEnable, ioaddr + EL3_CMD); /* Turn on statistics. */

	outw(RxEnable, ioaddr + EL3_CMD); /* Enable the receiver. */
	outw(TxEnable, ioaddr + EL3_CMD); /* Enable transmitter. */
	/* Allow status bits to be seen. */
	outw(SetStatusEnb | 0xff, ioaddr + EL3_CMD);
	/* Ack all pending events, and set active indicator mask. */
	outw(AckIntr | IntLatch | TxAvailable | RxEarly | IntReq,
		 ioaddr + EL3_CMD);
	outw(SetIntrEnb | IntLatch|TxAvailable|TxComplete|RxComplete|StatsFull,
		 ioaddr + EL3_CMD);

	netif_start_queue(dev);
}

/* Power Management support functions */
#ifdef CONFIG_PM
Linus Torvalds's avatar
Linus Torvalds committed

static int
el3_suspend(struct device *pdev, pm_message_t state)
Linus Torvalds's avatar
Linus Torvalds committed
{
	unsigned long flags;
	struct net_device *dev;
	struct el3_private *lp;
	int ioaddr;
	dev = dev_get_drvdata(pdev);
Linus Torvalds's avatar
Linus Torvalds committed
	lp = netdev_priv(dev);
	ioaddr = dev->base_addr;

	spin_lock_irqsave(&lp->lock, flags);

	if (netif_running(dev))
		netif_device_detach(dev);

	el3_down(dev);
	outw(PowerDown, ioaddr + EL3_CMD);

	spin_unlock_irqrestore(&lp->lock, flags);
	return 0;
}

static int
el3_resume(struct device *pdev)
Linus Torvalds's avatar
Linus Torvalds committed
{
	unsigned long flags;
	struct net_device *dev;
	struct el3_private *lp;
	int ioaddr;
	dev = dev_get_drvdata(pdev);
Linus Torvalds's avatar
Linus Torvalds committed
	lp = netdev_priv(dev);
	ioaddr = dev->base_addr;

	spin_lock_irqsave(&lp->lock, flags);

	outw(PowerUp, ioaddr + EL3_CMD);
Linus Torvalds's avatar
Linus Torvalds committed
	el3_up(dev);

	if (netif_running(dev))
		netif_device_attach(dev);
Linus Torvalds's avatar
Linus Torvalds committed
	spin_unlock_irqrestore(&lp->lock, flags);
	return 0;
}

#endif /* CONFIG_PM */
Linus Torvalds's avatar
Linus Torvalds committed

module_param(debug,int, 0);
module_param_array(irq, int, NULL, 0);
module_param(max_interrupt_work, int, 0);
MODULE_PARM_DESC(debug, "debug level (0-6)");
MODULE_PARM_DESC(irq, "IRQ number(s) (assigned)");
MODULE_PARM_DESC(max_interrupt_work, "maximum events handled per interrupt");
#ifdef CONFIG_PNP
Linus Torvalds's avatar
Linus Torvalds committed
module_param(nopnp, int, 0);
MODULE_PARM_DESC(nopnp, "disable ISA PnP support (0-1)");
#endif	/* CONFIG_PNP */
MODULE_DESCRIPTION("3Com Etherlink III (3c509, 3c509B, 3c529, 3c579) ethernet driver");
Linus Torvalds's avatar
Linus Torvalds committed
MODULE_LICENSE("GPL");

static int __init el3_init_module(void)
{
	int ret = 0;
Linus Torvalds's avatar
Linus Torvalds committed

	if (debug >= 0)
		el3_debug = debug;

#ifdef CONFIG_PNP
	if (!nopnp) {
		ret = pnp_register_driver(&el3_pnp_driver);
		if (!ret)
			pnp_registered = 1;
	}
#endif
	/* Select an open I/O location at 0x1*0 to do ISA contention select. */
	/* Start with 0x110 to avoid some sound cards.*/
	for (id_port = 0x110 ; id_port < 0x200; id_port += 0x10) {
		if (!request_region(id_port, 1, "3c509-control"))
			continue;
		outb(0x00, id_port);
		outb(0xff, id_port);
		if (inb(id_port) & 0x01)
			break;
		else
			release_region(id_port, 1);
	}
	if (id_port >= 0x200) {
		id_port = 0;
		pr_err("No I/O port available for 3c509 activation.\n");
	} else {
		ret = isa_register_driver(&el3_isa_driver, EL3_MAX_CARDS);
		if (!ret)
			isa_registered = 1;
Linus Torvalds's avatar
Linus Torvalds committed
	}
#ifdef CONFIG_EISA
	ret = eisa_driver_register(&el3_eisa_driver);
	if (!ret)
		eisa_registered = 1;
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef CONFIG_MCA
	ret = mca_register_driver(&el3_mca_driver);
	if (!ret)
		mca_registered = 1;
#endif

#ifdef CONFIG_PNP
	if (pnp_registered)
		ret = 0;
#endif
	if (isa_registered)
		ret = 0;
#ifdef CONFIG_EISA
	if (eisa_registered)
		ret = 0;
#endif
#ifdef CONFIG_MCA
	if (mca_registered)
		ret = 0;
Linus Torvalds's avatar
Linus Torvalds committed
#endif
	return ret;
Linus Torvalds's avatar
Linus Torvalds committed
}

static void __exit el3_cleanup_module(void)
{
#ifdef CONFIG_PNP
	if (pnp_registered)
		pnp_unregister_driver(&el3_pnp_driver);
#endif
	if (isa_registered)
		isa_unregister_driver(&el3_isa_driver);
	if (id_port)
		release_region(id_port, 1);
Linus Torvalds's avatar
Linus Torvalds committed
#ifdef CONFIG_EISA
	if (eisa_registered)
		eisa_driver_unregister(&el3_eisa_driver);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
#ifdef CONFIG_MCA
	if (mca_registered)
		mca_unregister_driver(&el3_mca_driver);
Linus Torvalds's avatar
Linus Torvalds committed
#endif
}

module_init (el3_init_module);
module_exit (el3_cleanup_module);