Skip to content
Snippets Groups Projects
8139cp.c 53.3 KiB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
	cpw16(MultiIntr, 0);

	cpw16_f(IntrMask, cp_intr_mask);

	cpw8_f(Cfg9346, Cfg9346_Lock);
}

Kevin Lo's avatar
Kevin Lo committed
static int cp_refill_rx(struct cp_private *cp)
Linus Torvalds's avatar
Linus Torvalds committed
{
Kevin Lo's avatar
Kevin Lo committed
	struct net_device *dev = cp->dev;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned i;

	for (i = 0; i < CP_RX_RING_SIZE; i++) {
		struct sk_buff *skb;
Francois Romieu's avatar
Francois Romieu committed
		dma_addr_t mapping;
Linus Torvalds's avatar
Linus Torvalds committed

		skb = netdev_alloc_skb_ip_align(dev, cp->rx_buf_sz);
Linus Torvalds's avatar
Linus Torvalds committed
		if (!skb)
			goto err_out;

		mapping = dma_map_single(&cp->pdev->dev, skb->data,
					 cp->rx_buf_sz, PCI_DMA_FROMDEVICE);
		cp->rx_skb[i] = skb;
Linus Torvalds's avatar
Linus Torvalds committed

		cp->rx_ring[i].opts2 = 0;
Francois Romieu's avatar
Francois Romieu committed
		cp->rx_ring[i].addr = cpu_to_le64(mapping);
Linus Torvalds's avatar
Linus Torvalds committed
		if (i == (CP_RX_RING_SIZE - 1))
			cp->rx_ring[i].opts1 =
				cpu_to_le32(DescOwn | RingEnd | cp->rx_buf_sz);
		else
			cp->rx_ring[i].opts1 =
				cpu_to_le32(DescOwn | cp->rx_buf_sz);
	}

	return 0;

err_out:
	cp_clean_rings(cp);
	return -ENOMEM;
}

static void cp_init_rings_index (struct cp_private *cp)
{
	cp->rx_tail = 0;
	cp->tx_head = cp->tx_tail = 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
static int cp_init_rings (struct cp_private *cp)
{
	memset(cp->tx_ring, 0, sizeof(struct cp_desc) * CP_TX_RING_SIZE);
	cp->tx_ring[CP_TX_RING_SIZE - 1].opts1 = cpu_to_le32(RingEnd);

	cp_init_rings_index(cp);
Linus Torvalds's avatar
Linus Torvalds committed

	return cp_refill_rx (cp);
}

static int cp_alloc_rings (struct cp_private *cp)
{
	void *mem;

	mem = dma_alloc_coherent(&cp->pdev->dev, CP_RING_BYTES,
				 &cp->ring_dma, GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
	if (!mem)
		return -ENOMEM;

	cp->rx_ring = mem;
	cp->tx_ring = &cp->rx_ring[CP_RX_RING_SIZE];

	return cp_init_rings(cp);
}

static void cp_clean_rings (struct cp_private *cp)
{
Francois Romieu's avatar
Francois Romieu committed
	struct cp_desc *desc;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned i;

	for (i = 0; i < CP_RX_RING_SIZE; i++) {
		if (cp->rx_skb[i]) {
Francois Romieu's avatar
Francois Romieu committed
			desc = cp->rx_ring + i;
			dma_unmap_single(&cp->pdev->dev,le64_to_cpu(desc->addr),
Linus Torvalds's avatar
Linus Torvalds committed
					 cp->rx_buf_sz, PCI_DMA_FROMDEVICE);
			dev_kfree_skb(cp->rx_skb[i]);
Linus Torvalds's avatar
Linus Torvalds committed
		}
	}

	for (i = 0; i < CP_TX_RING_SIZE; i++) {
		if (cp->tx_skb[i]) {
			struct sk_buff *skb = cp->tx_skb[i];
Francois Romieu's avatar
Francois Romieu committed
			desc = cp->tx_ring + i;
			dma_unmap_single(&cp->pdev->dev,le64_to_cpu(desc->addr),
					 le32_to_cpu(desc->opts1) & 0xffff,
					 PCI_DMA_TODEVICE);
Francois Romieu's avatar
Francois Romieu committed
			if (le32_to_cpu(desc->opts1) & LastFrag)
				dev_kfree_skb(skb);
			cp->dev->stats.tx_dropped++;
	memset(cp->rx_ring, 0, sizeof(struct cp_desc) * CP_RX_RING_SIZE);
	memset(cp->tx_ring, 0, sizeof(struct cp_desc) * CP_TX_RING_SIZE);

	memset(cp->rx_skb, 0, sizeof(struct sk_buff *) * CP_RX_RING_SIZE);
	memset(cp->tx_skb, 0, sizeof(struct sk_buff *) * CP_TX_RING_SIZE);
Linus Torvalds's avatar
Linus Torvalds committed
}

static void cp_free_rings (struct cp_private *cp)
{
	cp_clean_rings(cp);
	dma_free_coherent(&cp->pdev->dev, CP_RING_BYTES, cp->rx_ring,
			  cp->ring_dma);
Linus Torvalds's avatar
Linus Torvalds committed
	cp->rx_ring = NULL;
	cp->tx_ring = NULL;
}

static int cp_open (struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	int rc;

	netif_dbg(cp, ifup, dev, "enabling interface\n");
Linus Torvalds's avatar
Linus Torvalds committed

	rc = cp_alloc_rings(cp);
	if (rc)
		return rc;

Linus Torvalds's avatar
Linus Torvalds committed
	cp_init_hw(cp);

	rc = request_irq(dev->irq, cp_interrupt, IRQF_SHARED, dev->name, dev);
Linus Torvalds's avatar
Linus Torvalds committed
	if (rc)
		goto err_out_hw;

	netif_carrier_off(dev);
	mii_check_media(&cp->mii_if, netif_msg_link(cp), true);
Linus Torvalds's avatar
Linus Torvalds committed
	netif_start_queue(dev);

	return 0;

err_out_hw:
Linus Torvalds's avatar
Linus Torvalds committed
	cp_stop_hw(cp);
	cp_free_rings(cp);
	return rc;
}

static int cp_close (struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;

	netif_dbg(cp, ifdown, dev, "disabling interface\n");
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock_irqsave(&cp->lock, flags);

	netif_stop_queue(dev);
	netif_carrier_off(dev);

	cp_stop_hw(cp);

	spin_unlock_irqrestore(&cp->lock, flags);

	free_irq(dev->irq, dev);

	cp_free_rings(cp);
	return 0;
}

static void cp_tx_timeout(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;
	int rc;

	netdev_warn(dev, "Transmit timeout, status %2x %4x %4x %4x\n",
		    cpr8(Cmd), cpr16(CpCmd),
		    cpr16(IntrStatus), cpr16(IntrMask));

	spin_lock_irqsave(&cp->lock, flags);

	cp_stop_hw(cp);
	cp_clean_rings(cp);
	rc = cp_init_rings(cp);
	cp_start_hw(cp);

	netif_wake_queue(dev);

	spin_unlock_irqrestore(&cp->lock, flags);
}

Linus Torvalds's avatar
Linus Torvalds committed
#ifdef BROKEN
static int cp_change_mtu(struct net_device *dev, int new_mtu)
{
	struct cp_private *cp = netdev_priv(dev);
	int rc;
	unsigned long flags;

	/* check for invalid MTU, according to hardware limits */
	if (new_mtu < CP_MIN_MTU || new_mtu > CP_MAX_MTU)
		return -EINVAL;

	/* if network interface not up, no need for complexity */
	if (!netif_running(dev)) {
		dev->mtu = new_mtu;
		cp_set_rxbufsize(cp);	/* set new rx buf size */
		return 0;
	}

	spin_lock_irqsave(&cp->lock, flags);

	cp_stop_hw(cp);			/* stop h/w and free rings */
	cp_clean_rings(cp);

	dev->mtu = new_mtu;
	cp_set_rxbufsize(cp);		/* set new rx buf size */

	rc = cp_init_rings(cp);		/* realloc and restart h/w */
	cp_start_hw(cp);

	spin_unlock_irqrestore(&cp->lock, flags);

	return rc;
}
#endif /* BROKEN */

static const char mii_2_8139_map[8] = {
Linus Torvalds's avatar
Linus Torvalds committed
	BasicModeCtrl,
	BasicModeStatus,
	0,
	0,
	NWayAdvert,
	NWayLPAR,
	NWayExpansion,
	0
};

static int mdio_read(struct net_device *dev, int phy_id, int location)
{
	struct cp_private *cp = netdev_priv(dev);

	return location < 8 && mii_2_8139_map[location] ?
	       readw(cp->regs + mii_2_8139_map[location]) : 0;
}


static void mdio_write(struct net_device *dev, int phy_id, int location,
		       int value)
{
	struct cp_private *cp = netdev_priv(dev);

	if (location == 0) {
		cpw8(Cfg9346, Cfg9346_Unlock);
		cpw16(BasicModeCtrl, value);
		cpw8(Cfg9346, Cfg9346_Lock);
	} else if (location < 8 && mii_2_8139_map[location])
		cpw16(mii_2_8139_map[location], value);
}

/* Set the ethtool Wake-on-LAN settings */
static int netdev_set_wol (struct cp_private *cp,
			   const struct ethtool_wolinfo *wol)
{
	u8 options;

	options = cpr8 (Config3) & ~(LinkUp | MagicPacket);
	/* If WOL is being disabled, no need for complexity */
	if (wol->wolopts) {
		if (wol->wolopts & WAKE_PHY)	options |= LinkUp;
		if (wol->wolopts & WAKE_MAGIC)	options |= MagicPacket;
	}

	cpw8 (Cfg9346, Cfg9346_Unlock);
	cpw8 (Config3, options);
	cpw8 (Cfg9346, Cfg9346_Lock);

	options = 0; /* Paranoia setting */
	options = cpr8 (Config5) & ~(UWF | MWF | BWF);
	/* If WOL is being disabled, no need for complexity */
	if (wol->wolopts) {
		if (wol->wolopts & WAKE_UCAST)  options |= UWF;
		if (wol->wolopts & WAKE_BCAST)	options |= BWF;
		if (wol->wolopts & WAKE_MCAST)	options |= MWF;
	}

	cpw8 (Config5, options);

	cp->wol_enabled = (wol->wolopts) ? 1 : 0;

	return 0;
}

/* Get the ethtool Wake-on-LAN settings */
static void netdev_get_wol (struct cp_private *cp,
	             struct ethtool_wolinfo *wol)
{
	u8 options;

	wol->wolopts   = 0; /* Start from scratch */
	wol->supported = WAKE_PHY   | WAKE_BCAST | WAKE_MAGIC |
		         WAKE_MCAST | WAKE_UCAST;
	/* We don't need to go on if WOL is disabled */
	if (!cp->wol_enabled) return;
Linus Torvalds's avatar
Linus Torvalds committed
	options        = cpr8 (Config3);
	if (options & LinkUp)        wol->wolopts |= WAKE_PHY;
	if (options & MagicPacket)   wol->wolopts |= WAKE_MAGIC;

	options        = 0; /* Paranoia setting */
	options        = cpr8 (Config5);
	if (options & UWF)           wol->wolopts |= WAKE_UCAST;
	if (options & BWF)           wol->wolopts |= WAKE_BCAST;
	if (options & MWF)           wol->wolopts |= WAKE_MCAST;
}

static void cp_get_drvinfo (struct net_device *dev, struct ethtool_drvinfo *info)
{
	struct cp_private *cp = netdev_priv(dev);

	strcpy (info->driver, DRV_NAME);
	strcpy (info->version, DRV_VERSION);
	strcpy (info->bus_info, pci_name(cp->pdev));
}

static int cp_get_regs_len(struct net_device *dev)
{
	return CP_REGS_SIZE;
}

static int cp_get_sset_count (struct net_device *dev, int sset)
Linus Torvalds's avatar
Linus Torvalds committed
{
	switch (sset) {
	case ETH_SS_STATS:
		return CP_NUM_STATS;
	default:
		return -EOPNOTSUPP;
	}
Linus Torvalds's avatar
Linus Torvalds committed
}

static int cp_get_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
	struct cp_private *cp = netdev_priv(dev);
	int rc;
	unsigned long flags;

	spin_lock_irqsave(&cp->lock, flags);
	rc = mii_ethtool_gset(&cp->mii_if, cmd);
	spin_unlock_irqrestore(&cp->lock, flags);

	return rc;
}

static int cp_set_settings(struct net_device *dev, struct ethtool_cmd *cmd)
{
	struct cp_private *cp = netdev_priv(dev);
	int rc;
	unsigned long flags;

	spin_lock_irqsave(&cp->lock, flags);
	rc = mii_ethtool_sset(&cp->mii_if, cmd);
	spin_unlock_irqrestore(&cp->lock, flags);

	return rc;
}

static int cp_nway_reset(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	return mii_nway_restart(&cp->mii_if);
}

static u32 cp_get_msglevel(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	return cp->msg_enable;
}

static void cp_set_msglevel(struct net_device *dev, u32 value)
{
	struct cp_private *cp = netdev_priv(dev);
	cp->msg_enable = value;
}

static int cp_set_features(struct net_device *dev, u32 features)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;
Linus Torvalds's avatar
Linus Torvalds committed

	if (!((dev->features ^ features) & NETIF_F_RXCSUM))
		return 0;
Linus Torvalds's avatar
Linus Torvalds committed

	spin_lock_irqsave(&cp->lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed

	if (features & NETIF_F_RXCSUM)
		cp->cpcmd |= RxChkSum;
Linus Torvalds's avatar
Linus Torvalds committed
	else
		cp->cpcmd &= ~RxChkSum;
Linus Torvalds's avatar
Linus Torvalds committed

	if (features & NETIF_F_HW_VLAN_RX)
		cp->cpcmd |= RxVlanOn;
	else
		cp->cpcmd &= ~RxVlanOn;

	cpw16_f(CpCmd, cp->cpcmd);
	spin_unlock_irqrestore(&cp->lock, flags);
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;
}

static void cp_get_regs(struct net_device *dev, struct ethtool_regs *regs,
		        void *p)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;

	if (regs->len < CP_REGS_SIZE)
		return /* -EINVAL */;

	regs->version = CP_REGS_VER;

	spin_lock_irqsave(&cp->lock, flags);
	memcpy_fromio(p, cp->regs, CP_REGS_SIZE);
	spin_unlock_irqrestore(&cp->lock, flags);
}

static void cp_get_wol (struct net_device *dev, struct ethtool_wolinfo *wol)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;

	spin_lock_irqsave (&cp->lock, flags);
	netdev_get_wol (cp, wol);
	spin_unlock_irqrestore (&cp->lock, flags);
}

static int cp_set_wol (struct net_device *dev, struct ethtool_wolinfo *wol)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned long flags;
	int rc;

	spin_lock_irqsave (&cp->lock, flags);
	rc = netdev_set_wol (cp, wol);
	spin_unlock_irqrestore (&cp->lock, flags);

	return rc;
}

static void cp_get_strings (struct net_device *dev, u32 stringset, u8 *buf)
{
	switch (stringset) {
	case ETH_SS_STATS:
		memcpy(buf, &ethtool_stats_keys, sizeof(ethtool_stats_keys));
		break;
	default:
		BUG();
		break;
	}
}

static void cp_get_ethtool_stats (struct net_device *dev,
				  struct ethtool_stats *estats, u64 *tmp_stats)
{
	struct cp_private *cp = netdev_priv(dev);
	struct cp_dma_stats *nic_stats;
	dma_addr_t dma;
Linus Torvalds's avatar
Linus Torvalds committed
	int i;

	nic_stats = dma_alloc_coherent(&cp->pdev->dev, sizeof(*nic_stats),
				       &dma, GFP_KERNEL);
Linus Torvalds's avatar
Linus Torvalds committed
	/* begin NIC statistics dump */
	cpw32(StatsAddr + 4, (u64)dma >> 32);
	cpw32(StatsAddr, ((u64)dma & DMA_BIT_MASK(32)) | DumpStats);
Linus Torvalds's avatar
Linus Torvalds committed
	cpr32(StatsAddr);

	for (i = 0; i < 1000; i++) {
Linus Torvalds's avatar
Linus Torvalds committed
		if ((cpr32(StatsAddr) & DumpStats) == 0)
			break;
Linus Torvalds's avatar
Linus Torvalds committed
	}
	cpw32(StatsAddr, 0);
	cpw32(StatsAddr + 4, 0);
Linus Torvalds's avatar
Linus Torvalds committed

	i = 0;
	tmp_stats[i++] = le64_to_cpu(nic_stats->tx_ok);
	tmp_stats[i++] = le64_to_cpu(nic_stats->rx_ok);
	tmp_stats[i++] = le64_to_cpu(nic_stats->tx_err);
	tmp_stats[i++] = le32_to_cpu(nic_stats->rx_err);
	tmp_stats[i++] = le16_to_cpu(nic_stats->rx_fifo);
	tmp_stats[i++] = le16_to_cpu(nic_stats->frame_align);
	tmp_stats[i++] = le32_to_cpu(nic_stats->tx_ok_1col);
	tmp_stats[i++] = le32_to_cpu(nic_stats->tx_ok_mcol);
	tmp_stats[i++] = le64_to_cpu(nic_stats->rx_ok_phys);
	tmp_stats[i++] = le64_to_cpu(nic_stats->rx_ok_bcast);
	tmp_stats[i++] = le32_to_cpu(nic_stats->rx_ok_mcast);
	tmp_stats[i++] = le16_to_cpu(nic_stats->tx_abort);
	tmp_stats[i++] = le16_to_cpu(nic_stats->tx_underrun);
Linus Torvalds's avatar
Linus Torvalds committed
	tmp_stats[i++] = cp->cp_stats.rx_frags;
	BUG_ON(i != CP_NUM_STATS);
	dma_free_coherent(&cp->pdev->dev, sizeof(*nic_stats), nic_stats, dma);
static const struct ethtool_ops cp_ethtool_ops = {
Linus Torvalds's avatar
Linus Torvalds committed
	.get_drvinfo		= cp_get_drvinfo,
	.get_regs_len		= cp_get_regs_len,
	.get_sset_count		= cp_get_sset_count,
Linus Torvalds's avatar
Linus Torvalds committed
	.get_settings		= cp_get_settings,
	.set_settings		= cp_set_settings,
	.nway_reset		= cp_nway_reset,
	.get_link		= ethtool_op_get_link,
	.get_msglevel		= cp_get_msglevel,
	.set_msglevel		= cp_set_msglevel,
	.get_regs		= cp_get_regs,
	.get_wol		= cp_get_wol,
	.set_wol		= cp_set_wol,
	.get_strings		= cp_get_strings,
	.get_ethtool_stats	= cp_get_ethtool_stats,
	.get_eeprom_len		= cp_get_eeprom_len,
	.get_eeprom		= cp_get_eeprom,
	.set_eeprom		= cp_set_eeprom,
Linus Torvalds's avatar
Linus Torvalds committed
};

static int cp_ioctl (struct net_device *dev, struct ifreq *rq, int cmd)
{
	struct cp_private *cp = netdev_priv(dev);
	int rc;
	unsigned long flags;

	if (!netif_running(dev))
		return -EINVAL;

	spin_lock_irqsave(&cp->lock, flags);
	rc = generic_mii_ioctl(&cp->mii_if, if_mii(rq), cmd, NULL);
	spin_unlock_irqrestore(&cp->lock, flags);
	return rc;
}

static int cp_set_mac_address(struct net_device *dev, void *p)
{
	struct cp_private *cp = netdev_priv(dev);
	struct sockaddr *addr = p;

	if (!is_valid_ether_addr(addr->sa_data))
		return -EADDRNOTAVAIL;

	memcpy(dev->dev_addr, addr->sa_data, dev->addr_len);

	spin_lock_irq(&cp->lock);

	cpw8_f(Cfg9346, Cfg9346_Unlock);
	cpw32_f(MAC0 + 0, le32_to_cpu (*(__le32 *) (dev->dev_addr + 0)));
	cpw32_f(MAC0 + 4, le32_to_cpu (*(__le32 *) (dev->dev_addr + 4)));
	cpw8_f(Cfg9346, Cfg9346_Lock);

	spin_unlock_irq(&cp->lock);

	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
/* Serial EEPROM section. */

/*  EEPROM_Ctrl bits. */
#define EE_SHIFT_CLK	0x04	/* EEPROM shift clock. */
#define EE_CS			0x08	/* EEPROM chip select. */
#define EE_DATA_WRITE	0x02	/* EEPROM chip data in. */
#define EE_WRITE_0		0x00
#define EE_WRITE_1		0x02
#define EE_DATA_READ	0x01	/* EEPROM chip data out. */
#define EE_ENB			(0x80 | EE_CS)

/* Delay between EEPROM clock transitions.
   No extra delay is needed with 33Mhz PCI, but 66Mhz may change this.
 */

#define eeprom_delay()	readl(ee_addr)

/* The EEPROM commands include the alway-set leading bit. */
#define EE_EXTEND_CMD	(4)
Linus Torvalds's avatar
Linus Torvalds committed
#define EE_WRITE_CMD	(5)
#define EE_READ_CMD		(6)
#define EE_ERASE_CMD	(7)

#define EE_EWDS_ADDR	(0)
#define EE_WRAL_ADDR	(1)
#define EE_ERAL_ADDR	(2)
#define EE_EWEN_ADDR	(3)

#define CP_EEPROM_MAGIC PCI_DEVICE_ID_REALTEK_8139
Linus Torvalds's avatar
Linus Torvalds committed

static void eeprom_cmd_start(void __iomem *ee_addr)
{
Linus Torvalds's avatar
Linus Torvalds committed
	writeb (EE_ENB & ~EE_CS, ee_addr);
	writeb (EE_ENB, ee_addr);
	eeprom_delay ();
Linus Torvalds's avatar
Linus Torvalds committed

static void eeprom_cmd(void __iomem *ee_addr, int cmd, int cmd_len)
{
	int i;

	/* Shift the command bits out. */
	for (i = cmd_len - 1; i >= 0; i--) {
		int dataval = (cmd & (1 << i)) ? EE_DATA_WRITE : 0;
Linus Torvalds's avatar
Linus Torvalds committed
		writeb (EE_ENB | dataval, ee_addr);
		eeprom_delay ();
		writeb (EE_ENB | dataval | EE_SHIFT_CLK, ee_addr);
		eeprom_delay ();
	}
	writeb (EE_ENB, ee_addr);
	eeprom_delay ();
}

static void eeprom_cmd_end(void __iomem *ee_addr)
{
	writeb (~EE_CS, ee_addr);
	eeprom_delay ();
}

static void eeprom_extend_cmd(void __iomem *ee_addr, int extend_cmd,
			      int addr_len)
{
	int cmd = (EE_EXTEND_CMD << addr_len) | (extend_cmd << (addr_len - 2));

	eeprom_cmd_start(ee_addr);
	eeprom_cmd(ee_addr, cmd, 3 + addr_len);
	eeprom_cmd_end(ee_addr);
}

static u16 read_eeprom (void __iomem *ioaddr, int location, int addr_len)
{
	int i;
	u16 retval = 0;
	void __iomem *ee_addr = ioaddr + Cfg9346;
	int read_cmd = location | (EE_READ_CMD << addr_len);

	eeprom_cmd_start(ee_addr);
	eeprom_cmd(ee_addr, read_cmd, 3 + addr_len);
Linus Torvalds's avatar
Linus Torvalds committed

	for (i = 16; i > 0; i--) {
		writeb (EE_ENB | EE_SHIFT_CLK, ee_addr);
		eeprom_delay ();
		retval =
		    (retval << 1) | ((readb (ee_addr) & EE_DATA_READ) ? 1 :
				     0);
		writeb (EE_ENB, ee_addr);
		eeprom_delay ();
	}

	eeprom_cmd_end(ee_addr);
Linus Torvalds's avatar
Linus Torvalds committed

	return retval;
}

static void write_eeprom(void __iomem *ioaddr, int location, u16 val,
			 int addr_len)
{
	int i;
	void __iomem *ee_addr = ioaddr + Cfg9346;
	int write_cmd = location | (EE_WRITE_CMD << addr_len);

	eeprom_extend_cmd(ee_addr, EE_EWEN_ADDR, addr_len);

	eeprom_cmd_start(ee_addr);
	eeprom_cmd(ee_addr, write_cmd, 3 + addr_len);
	eeprom_cmd(ee_addr, val, 16);
	eeprom_cmd_end(ee_addr);

	eeprom_cmd_start(ee_addr);
	for (i = 0; i < 20000; i++)
		if (readb(ee_addr) & EE_DATA_READ)
			break;
	eeprom_cmd_end(ee_addr);

	eeprom_extend_cmd(ee_addr, EE_EWDS_ADDR, addr_len);
}

static int cp_get_eeprom_len(struct net_device *dev)
{
	struct cp_private *cp = netdev_priv(dev);
	int size;

	spin_lock_irq(&cp->lock);
	size = read_eeprom(cp->regs, 0, 8) == 0x8129 ? 256 : 128;
	spin_unlock_irq(&cp->lock);

	return size;
}

static int cp_get_eeprom(struct net_device *dev,
			 struct ethtool_eeprom *eeprom, u8 *data)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned int addr_len;
	u16 val;
	u32 offset = eeprom->offset >> 1;
	u32 len = eeprom->len;
	u32 i = 0;

	eeprom->magic = CP_EEPROM_MAGIC;

	spin_lock_irq(&cp->lock);

	addr_len = read_eeprom(cp->regs, 0, 8) == 0x8129 ? 8 : 6;

	if (eeprom->offset & 1) {
		val = read_eeprom(cp->regs, offset, addr_len);
		data[i++] = (u8)(val >> 8);
		offset++;
	}

	while (i < len - 1) {
		val = read_eeprom(cp->regs, offset, addr_len);
		data[i++] = (u8)val;
		data[i++] = (u8)(val >> 8);
		offset++;
	}

	if (i < len) {
		val = read_eeprom(cp->regs, offset, addr_len);
		data[i] = (u8)val;
	}

	spin_unlock_irq(&cp->lock);
	return 0;
}

static int cp_set_eeprom(struct net_device *dev,
			 struct ethtool_eeprom *eeprom, u8 *data)
{
	struct cp_private *cp = netdev_priv(dev);
	unsigned int addr_len;
	u16 val;
	u32 offset = eeprom->offset >> 1;
	u32 len = eeprom->len;
	u32 i = 0;

	if (eeprom->magic != CP_EEPROM_MAGIC)
		return -EINVAL;

	spin_lock_irq(&cp->lock);

	addr_len = read_eeprom(cp->regs, 0, 8) == 0x8129 ? 8 : 6;

	if (eeprom->offset & 1) {
		val = read_eeprom(cp->regs, offset, addr_len) & 0xff;
		val |= (u16)data[i++] << 8;
		write_eeprom(cp->regs, offset, val, addr_len);
		offset++;
	}

	while (i < len - 1) {
		val = (u16)data[i++];
		val |= (u16)data[i++] << 8;
		write_eeprom(cp->regs, offset, val, addr_len);
		offset++;
	}

	if (i < len) {
		val = read_eeprom(cp->regs, offset, addr_len) & 0xff00;
		val |= (u16)data[i];
		write_eeprom(cp->regs, offset, val, addr_len);
	}

	spin_unlock_irq(&cp->lock);
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
/* Put the board into D3cold state and wait for WakeUp signal */
static void cp_set_d3_state (struct cp_private *cp)
{
	pci_enable_wake (cp->pdev, 0, 1); /* Enable PME# generation */
	pci_set_power_state (cp->pdev, PCI_D3hot);
}

static const struct net_device_ops cp_netdev_ops = {
	.ndo_open		= cp_open,
	.ndo_stop		= cp_close,
	.ndo_validate_addr	= eth_validate_addr,
	.ndo_set_mac_address 	= cp_set_mac_address,
	.ndo_set_multicast_list	= cp_set_rx_mode,
	.ndo_get_stats		= cp_get_stats,
	.ndo_do_ioctl		= cp_ioctl,
	.ndo_start_xmit		= cp_start_xmit,
	.ndo_tx_timeout		= cp_tx_timeout,
	.ndo_set_features	= cp_set_features,
#ifdef BROKEN
	.ndo_change_mtu		= cp_change_mtu,
#endif
#ifdef CONFIG_NET_POLL_CONTROLLER
	.ndo_poll_controller	= cp_poll_controller,
#endif
};

Linus Torvalds's avatar
Linus Torvalds committed
static int cp_init_one (struct pci_dev *pdev, const struct pci_device_id *ent)
{
	struct net_device *dev;
	struct cp_private *cp;
	int rc;
	void __iomem *regs;
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned int addr_len, i, pci_using_dac;

#ifndef MODULE
	static int version_printed;
	if (version_printed++ == 0)
		pr_info("%s", version);
Linus Torvalds's avatar
Linus Torvalds committed
#endif

	if (pdev->vendor == PCI_VENDOR_ID_REALTEK &&
	    pdev->device == PCI_DEVICE_ID_REALTEK_8139 && pdev->revision < 0x20) {
			 "This (id %04x:%04x rev %02x) is not an 8139C+ compatible chip, use 8139too\n",
			 pdev->vendor, pdev->device, pdev->revision);
Linus Torvalds's avatar
Linus Torvalds committed
		return -ENODEV;
	}

	dev = alloc_etherdev(sizeof(struct cp_private));
	if (!dev)
		return -ENOMEM;
	SET_NETDEV_DEV(dev, &pdev->dev);

	cp = netdev_priv(dev);
	cp->pdev = pdev;
	cp->dev = dev;
	cp->msg_enable = (debug < 0 ? CP_DEF_MSG_ENABLE : debug);
	spin_lock_init (&cp->lock);
	cp->mii_if.dev = dev;
	cp->mii_if.mdio_read = mdio_read;
	cp->mii_if.mdio_write = mdio_write;
	cp->mii_if.phy_id = CP_INTERNAL_PHY;
	cp->mii_if.phy_id_mask = 0x1f;
	cp->mii_if.reg_num_mask = 0x1f;
	cp_set_rxbufsize(cp);

	rc = pci_enable_device(pdev);
	if (rc)
		goto err_out_free;

	rc = pci_set_mwi(pdev);
	if (rc)
		goto err_out_disable;

	rc = pci_request_regions(pdev, DRV_NAME);
	if (rc)
		goto err_out_mwi;

	pciaddr = pci_resource_start(pdev, 1);
	if (!pciaddr) {
		rc = -EIO;
		dev_err(&pdev->dev, "no MMIO resource\n");
Linus Torvalds's avatar
Linus Torvalds committed
		goto err_out_res;
	}
	if (pci_resource_len(pdev, 1) < CP_REGS_SIZE) {
		rc = -EIO;
		dev_err(&pdev->dev, "MMIO resource (%llx) too small\n",
		       (unsigned long long)pci_resource_len(pdev, 1));
Linus Torvalds's avatar
Linus Torvalds committed
		goto err_out_res;
	}

	/* Configure DMA attributes. */
	if ((sizeof(dma_addr_t) > 4) &&
	    !pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(64)) &&
	    !pci_set_dma_mask(pdev, DMA_BIT_MASK(64))) {
Linus Torvalds's avatar
Linus Torvalds committed
		pci_using_dac = 1;
	} else {
		pci_using_dac = 0;

		rc = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
Linus Torvalds's avatar
Linus Torvalds committed
		if (rc) {
				"No usable DMA configuration, aborting\n");
Linus Torvalds's avatar
Linus Torvalds committed
			goto err_out_res;
		}
		rc = pci_set_consistent_dma_mask(pdev, DMA_BIT_MASK(32));
Linus Torvalds's avatar
Linus Torvalds committed
		if (rc) {
				"No usable consistent DMA configuration, aborting\n");
Linus Torvalds's avatar
Linus Torvalds committed
			goto err_out_res;
		}
	}

	cp->cpcmd = (pci_using_dac ? PCIDAC : 0) |
		    PCIMulRW | RxChkSum | CpRxOn | CpTxOn;

	dev->features |= NETIF_F_RXCSUM;
	dev->hw_features |= NETIF_F_RXCSUM;

Linus Torvalds's avatar
Linus Torvalds committed
	regs = ioremap(pciaddr, CP_REGS_SIZE);
	if (!regs) {
		rc = -EIO;
		dev_err(&pdev->dev, "Cannot map PCI MMIO (%Lx@%Lx)\n",
			(unsigned long long)pci_resource_len(pdev, 1),
Linus Torvalds's avatar
Linus Torvalds committed
		goto err_out_res;
	}
	dev->base_addr = (unsigned long) regs;
	cp->regs = regs;

	cp_stop_hw(cp);

	/* read MAC address from EEPROM */
	addr_len = read_eeprom (regs, 0, 8) == 0x8129 ? 8 : 6;
	for (i = 0; i < 3; i++)
		((__le16 *) (dev->dev_addr))[i] =
		    cpu_to_le16(read_eeprom (regs, i + 7, addr_len));
	memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len);
Linus Torvalds's avatar
Linus Torvalds committed

	dev->netdev_ops = &cp_netdev_ops;
	netif_napi_add(dev, &cp->napi, cp_rx_poll, 16);
Linus Torvalds's avatar
Linus Torvalds committed
	dev->ethtool_ops = &cp_ethtool_ops;
	dev->watchdog_timeo = TX_TIMEOUT;

	dev->features |= NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX;

	if (pci_using_dac)
		dev->features |= NETIF_F_HIGHDMA;

	/* disabled by default until verified */
	dev->hw_features |= NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
		NETIF_F_HW_VLAN_TX | NETIF_F_HW_VLAN_RX;
	dev->vlan_features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_TSO |
		NETIF_F_HIGHDMA;
Linus Torvalds's avatar
Linus Torvalds committed
	dev->irq = pdev->irq;

	rc = register_netdev(dev);
	if (rc)
		goto err_out_iomap;

	netdev_info(dev, "RTL-8139C+ at 0x%lx, %pM, IRQ %d\n",
		    dev->base_addr, dev->dev_addr, dev->irq);
Linus Torvalds's avatar
Linus Torvalds committed

	pci_set_drvdata(pdev, dev);

	/* enable busmastering and memory-write-invalidate */
	pci_set_master(pdev);

	if (cp->wol_enabled)
		cp_set_d3_state (cp);
Linus Torvalds's avatar
Linus Torvalds committed

	return 0;

err_out_iomap:
	iounmap(regs);
err_out_res:
	pci_release_regions(pdev);
err_out_mwi:
	pci_clear_mwi(pdev);
err_out_disable:
	pci_disable_device(pdev);
err_out_free:
	free_netdev(dev);
	return rc;
}

static void cp_remove_one (struct pci_dev *pdev)
{
	struct net_device *dev = pci_get_drvdata(pdev);
	struct cp_private *cp = netdev_priv(dev);

	unregister_netdev(dev);
	iounmap(cp->regs);
	if (cp->wol_enabled)
		pci_set_power_state (pdev, PCI_D0);
Linus Torvalds's avatar
Linus Torvalds committed
	pci_release_regions(pdev);
	pci_clear_mwi(pdev);
	pci_disable_device(pdev);
	pci_set_drvdata(pdev, NULL);
	free_netdev(dev);
}

#ifdef CONFIG_PM
static int cp_suspend (struct pci_dev *pdev, pm_message_t state)
Linus Torvalds's avatar
Linus Torvalds committed
{
	struct net_device *dev = pci_get_drvdata(pdev);
	struct cp_private *cp = netdev_priv(dev);
Linus Torvalds's avatar
Linus Torvalds committed
	unsigned long flags;

Linus Torvalds's avatar
Linus Torvalds committed

	netif_device_detach (dev);
	netif_stop_queue (dev);

	spin_lock_irqsave (&cp->lock, flags);

	/* Disable Rx and Tx */
	cpw16 (IntrMask, 0);
	cpw8  (Cmd, cpr8 (Cmd) & (~RxOn | ~TxOn));

	spin_unlock_irqrestore (&cp->lock, flags);