Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit eb91f61b authored by Ayaz Abdulla's avatar Ayaz Abdulla Committed by Jeff Garzik
Browse files

[PATCH] forcedeth: add support for flow control



This patch adds flow control support for tx and rx pause frames in
forcedeth.

Signed-Off-By: default avatarAyaz Abdulla <aabdulla@nvidia.com>

Signed-off-by: default avatarJeff Garzik <jeff@garzik.org>
parent 48cf270e
Loading
Loading
Loading
Loading
+104 −35
Original line number Diff line number Diff line
@@ -107,6 +107,7 @@
 *	0.52: 20 Jan 2006: Add MSI/MSIX support.
 *	0.53: 19 Mar 2006: Fix init from low power mode and add hw reset.
 *	0.54: 21 Mar 2006: Fix spin locks for multi irqs and cleanup.
 *	0.55: 22 Mar 2006: Add flow control (pause frame).
 *
 * Known bugs:
 * We suspect that on some hardware no TX done interrupts are generated.
@@ -118,7 +119,7 @@
 * DEV_NEED_TIMERIRQ will not harm you on sane hardware, only generating a few
 * superfluous timer interrupts from the nic.
 */
#define FORCEDETH_VERSION		"0.54"
#define FORCEDETH_VERSION		"0.55"
#define DRV_NAME			"forcedeth"

#include <linux/module.h>
@@ -163,6 +164,7 @@
#define DEV_HAS_MSI             0x0040  /* device supports MSI */
#define DEV_HAS_MSI_X           0x0080  /* device supports MSI-X */
#define DEV_HAS_POWER_CNTRL     0x0100  /* device supports power savings */
#define DEV_HAS_PAUSEFRAME_TX   0x0200  /* device supports tx pause frames */

enum {
	NvRegIrqStatus = 0x000,
@@ -203,6 +205,7 @@ enum {
	NvRegMSIIrqMask = 0x030,
#define NVREG_MSI_VECTOR_0_ENABLED 0x01
	NvRegMisc1 = 0x080,
#define NVREG_MISC1_PAUSE_TX	0x01
#define NVREG_MISC1_HD		0x02
#define NVREG_MISC1_FORCE	0x3b0f3c

@@ -214,7 +217,8 @@ enum {
#define NVREG_XMITSTAT_BUSY	0x01

	NvRegPacketFilterFlags = 0x8c,
#define NVREG_PFF_ALWAYS	0x7F0008
#define NVREG_PFF_PAUSE_RX	0x08
#define NVREG_PFF_ALWAYS	0x7F0000
#define NVREG_PFF_PROMISC	0x80
#define NVREG_PFF_MYADDR	0x20

@@ -277,6 +281,9 @@ enum {
#define NVREG_TXRXCTL_VLANINS	0x00080
	NvRegTxRingPhysAddrHigh = 0x148,
	NvRegRxRingPhysAddrHigh = 0x14C,
	NvRegTxPauseFrame = 0x170,
#define NVREG_TX_PAUSEFRAME_DISABLE	0x1ff0080
#define NVREG_TX_PAUSEFRAME_ENABLE	0x0c00030
	NvRegMIIStatus = 0x180,
#define NVREG_MIISTAT_ERROR		0x0001
#define NVREG_MIISTAT_LINKCHANGE	0x0008
@@ -506,13 +513,10 @@ typedef union _ring_type {
#define PHY_1000	0x2
#define PHY_HALF	0x100

/* FIXME: MII defines that should be added to <linux/mii.h> */
#define MII_1000BT_CR	0x09
#define MII_1000BT_SR	0x0a
#define ADVERTISE_1000FULL	0x0200
#define ADVERTISE_1000HALF	0x0100
#define LPA_1000FULL	0x0800
#define LPA_1000HALF	0x0400
#define NV_PAUSEFRAME_RX_CAPABLE 0x0001
#define NV_PAUSEFRAME_TX_CAPABLE 0x0002
#define NV_PAUSEFRAME_RX_ENABLE  0x0004
#define NV_PAUSEFRAME_TX_ENABLE  0x0008

/* MSI/MSI-X defines */
#define NV_MSI_X_MAX_VECTORS  8
@@ -602,6 +606,9 @@ struct fe_priv {
	/* msi/msi-x fields */
	u32 msi_flags;
	struct msix_entry msi_x_entry[NV_MSI_X_MAX_VECTORS];

	/* flow control */
	u32 pause_flags;
};

/*
@@ -860,7 +867,7 @@ static int phy_init(struct net_device *dev)

	/* set advertise register */
	reg = mii_rw(dev, np->phyaddr, MII_ADVERTISE, MII_READ);
	reg |= (ADVERTISE_10HALF|ADVERTISE_10FULL|ADVERTISE_100HALF|ADVERTISE_100FULL|0x800|0x400);
	reg |= (ADVERTISE_10HALF|ADVERTISE_10FULL|ADVERTISE_100HALF|ADVERTISE_100FULL|ADVERTISE_PAUSE_ASYM|ADVERTISE_PAUSE_CAP);
	if (mii_rw(dev, np->phyaddr, MII_ADVERTISE, reg)) {
		printk(KERN_INFO "%s: phy write to advertise failed.\n", pci_name(np->pci_dev));
		return PHY_ERROR;
@@ -873,14 +880,14 @@ static int phy_init(struct net_device *dev)
	mii_status = mii_rw(dev, np->phyaddr, MII_BMSR, MII_READ);
	if (mii_status & PHY_GIGABIT) {
		np->gigabit = PHY_GIGABIT;
		mii_control_1000 = mii_rw(dev, np->phyaddr, MII_1000BT_CR, MII_READ);
		mii_control_1000 = mii_rw(dev, np->phyaddr, MII_CTRL1000, MII_READ);
		mii_control_1000 &= ~ADVERTISE_1000HALF;
		if (phyinterface & PHY_RGMII)
			mii_control_1000 |= ADVERTISE_1000FULL;
		else
			mii_control_1000 &= ~ADVERTISE_1000FULL;

		if (mii_rw(dev, np->phyaddr, MII_1000BT_CR, mii_control_1000)) {
		if (mii_rw(dev, np->phyaddr, MII_CTRL1000, mii_control_1000)) {
			printk(KERN_INFO "%s: phy init failed.\n", pci_name(np->pci_dev));
			return PHY_ERROR;
		}
@@ -918,6 +925,8 @@ static int phy_init(struct net_device *dev)
			return PHY_ERROR;
		}
	}
	/* some phys clear out pause advertisment on reset, set it back */
	mii_rw(dev, np->phyaddr, MII_ADVERTISE, reg);

	/* restart auto negotiation */
	mii_control = mii_rw(dev, np->phyaddr, MII_BMCR, MII_READ);
@@ -1550,7 +1559,6 @@ static void nv_rx_process(struct net_device *dev)
	u32 Flags;
	u32 vlanflags = 0;


	for (;;) {
		struct sk_buff *skb;
		int len;
@@ -1901,7 +1909,9 @@ static int nv_update_linkspeed(struct net_device *dev)
{
	struct fe_priv *np = netdev_priv(dev);
	u8 __iomem *base = get_hwbase(dev);
	int adv, lpa;
	int adv = 0;
	int lpa = 0;
	int adv_lpa, adv_pause, lpa_pause;
	int newls = np->linkspeed;
	int newdup = np->duplex;
	int mii_status;
@@ -1954,8 +1964,8 @@ static int nv_update_linkspeed(struct net_device *dev)

	retval = 1;
	if (np->gigabit == PHY_GIGABIT) {
		control_1000 = mii_rw(dev, np->phyaddr, MII_1000BT_CR, MII_READ);
		status_1000 = mii_rw(dev, np->phyaddr, MII_1000BT_SR, MII_READ);
		control_1000 = mii_rw(dev, np->phyaddr, MII_CTRL1000, MII_READ);
		status_1000 = mii_rw(dev, np->phyaddr, MII_STAT1000, MII_READ);

		if ((control_1000 & ADVERTISE_1000FULL) &&
			(status_1000 & LPA_1000FULL)) {
@@ -1973,21 +1983,21 @@ static int nv_update_linkspeed(struct net_device *dev)
				dev->name, adv, lpa);

	/* FIXME: handle parallel detection properly */
	lpa = lpa & adv;
	if (lpa & LPA_100FULL) {
	adv_lpa = lpa & adv;
	if (adv_lpa & LPA_100FULL) {
		newls = NVREG_LINKSPEED_FORCE|NVREG_LINKSPEED_100;
		newdup = 1;
	} else if (lpa & LPA_100HALF) {
	} else if (adv_lpa & LPA_100HALF) {
		newls = NVREG_LINKSPEED_FORCE|NVREG_LINKSPEED_100;
		newdup = 0;
	} else if (lpa & LPA_10FULL) {
	} else if (adv_lpa & LPA_10FULL) {
		newls = NVREG_LINKSPEED_FORCE|NVREG_LINKSPEED_10;
		newdup = 1;
	} else if (lpa & LPA_10HALF) {
	} else if (adv_lpa & LPA_10HALF) {
		newls = NVREG_LINKSPEED_FORCE|NVREG_LINKSPEED_10;
		newdup = 0;
	} else {
		dprintk(KERN_DEBUG "%s: bad ability %04x - falling back to 10HD.\n", dev->name, lpa);
		dprintk(KERN_DEBUG "%s: bad ability %04x - falling back to 10HD.\n", dev->name, adv_lpa);
		newls = NVREG_LINKSPEED_FORCE|NVREG_LINKSPEED_10;
		newdup = 0;
	}
@@ -2030,6 +2040,56 @@ set_speed:
	writel(np->linkspeed, base + NvRegLinkSpeed);
	pci_push(base);

	/* setup pause frame based on advertisement and link partner */
	np->pause_flags &= ~(NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE);

	if (np->duplex != 0) {
		adv_pause = adv & (ADVERTISE_PAUSE_CAP| ADVERTISE_PAUSE_ASYM);
		lpa_pause = lpa & (LPA_PAUSE_CAP| LPA_PAUSE_ASYM);

		switch (adv_pause) {
		case (ADVERTISE_PAUSE_CAP):
			if (lpa_pause & LPA_PAUSE_CAP) {
				np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE;
			}
			break;
		case (ADVERTISE_PAUSE_ASYM):
			if (lpa_pause == (LPA_PAUSE_CAP| LPA_PAUSE_ASYM))
			{
				np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE;
			}
			break;
		case (ADVERTISE_PAUSE_CAP| ADVERTISE_PAUSE_ASYM):
			if (lpa_pause & LPA_PAUSE_CAP)
			{
				np->pause_flags |= NV_PAUSEFRAME_TX_ENABLE | NV_PAUSEFRAME_RX_ENABLE;
			}
			if (lpa_pause == LPA_PAUSE_ASYM)
			{
				np->pause_flags |= NV_PAUSEFRAME_RX_ENABLE;
			} 
			break;
		}
	}

	if (np->pause_flags & NV_PAUSEFRAME_RX_CAPABLE) {
		u32 pff = readl(base + NvRegPacketFilterFlags) & ~NVREG_PFF_PAUSE_RX;
		if (np->pause_flags & NV_PAUSEFRAME_RX_ENABLE)
			writel(pff|NVREG_PFF_PAUSE_RX, base + NvRegPacketFilterFlags);
		else
			writel(pff, base + NvRegPacketFilterFlags);
	}
	if (np->pause_flags & NV_PAUSEFRAME_TX_CAPABLE) {
		u32 regmisc = readl(base + NvRegMisc1) & ~NVREG_MISC1_PAUSE_TX;
		if (np->pause_flags & NV_PAUSEFRAME_TX_ENABLE) {
			writel(NVREG_TX_PAUSEFRAME_ENABLE,  base + NvRegTxPauseFrame);
			writel(regmisc|NVREG_MISC1_PAUSE_TX, base + NvRegMisc1);
		} else {
			writel(NVREG_TX_PAUSEFRAME_DISABLE,  base + NvRegTxPauseFrame);
			writel(regmisc, base + NvRegMisc1);			
		}
	}

	return retval;
}

@@ -2441,7 +2501,7 @@ static int nv_get_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
	if (adv & ADVERTISE_100FULL)
		ecmd->advertising |= ADVERTISED_100baseT_Full;
	if (np->autoneg && np->gigabit == PHY_GIGABIT) {
		adv = mii_rw(dev, np->phyaddr, MII_1000BT_CR, MII_READ);
		adv = mii_rw(dev, np->phyaddr, MII_CTRL1000, MII_READ);
		if (adv & ADVERTISE_1000FULL)
			ecmd->advertising |= ADVERTISED_1000baseT_Full;
	}
@@ -2505,23 +2565,23 @@ static int nv_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)

		/* advertise only what has been requested */
		adv = mii_rw(dev, np->phyaddr, MII_ADVERTISE, MII_READ);
		adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
		adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
		if (ecmd->advertising & ADVERTISED_10baseT_Half)
			adv |= ADVERTISE_10HALF;
		if (ecmd->advertising & ADVERTISED_10baseT_Full)
			adv |= ADVERTISE_10FULL;
			adv |= ADVERTISE_10FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
		if (ecmd->advertising & ADVERTISED_100baseT_Half)
			adv |= ADVERTISE_100HALF;
		if (ecmd->advertising & ADVERTISED_100baseT_Full)
			adv |= ADVERTISE_100FULL;
			adv |= ADVERTISE_100FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
		mii_rw(dev, np->phyaddr, MII_ADVERTISE, adv);

		if (np->gigabit == PHY_GIGABIT) {
			adv = mii_rw(dev, np->phyaddr, MII_1000BT_CR, MII_READ);
			adv = mii_rw(dev, np->phyaddr, MII_CTRL1000, MII_READ);
			adv &= ~ADVERTISE_1000FULL;
			if (ecmd->advertising & ADVERTISED_1000baseT_Full)
				adv |= ADVERTISE_1000FULL;
			mii_rw(dev, np->phyaddr, MII_1000BT_CR, adv);
			mii_rw(dev, np->phyaddr, MII_CTRL1000, adv);
		}

		bmcr = mii_rw(dev, np->phyaddr, MII_BMCR, MII_READ);
@@ -2534,22 +2594,22 @@ static int nv_set_settings(struct net_device *dev, struct ethtool_cmd *ecmd)
		np->autoneg = 0;

		adv = mii_rw(dev, np->phyaddr, MII_ADVERTISE, MII_READ);
		adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4);
		adv &= ~(ADVERTISE_ALL | ADVERTISE_100BASE4 | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM);
		if (ecmd->speed == SPEED_10 && ecmd->duplex == DUPLEX_HALF)
			adv |= ADVERTISE_10HALF;
		if (ecmd->speed == SPEED_10 && ecmd->duplex == DUPLEX_FULL)
			adv |= ADVERTISE_10FULL;
			adv |= ADVERTISE_10FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
		if (ecmd->speed == SPEED_100 && ecmd->duplex == DUPLEX_HALF)
			adv |= ADVERTISE_100HALF;
		if (ecmd->speed == SPEED_100 && ecmd->duplex == DUPLEX_FULL)
			adv |= ADVERTISE_100FULL;
			adv |= ADVERTISE_100FULL | ADVERTISE_PAUSE_CAP | ADVERTISE_PAUSE_ASYM;
		mii_rw(dev, np->phyaddr, MII_ADVERTISE, adv);
		np->fixed_mode = adv;

		if (np->gigabit == PHY_GIGABIT) {
			adv = mii_rw(dev, np->phyaddr, MII_1000BT_CR, MII_READ);
			adv = mii_rw(dev, np->phyaddr, MII_CTRL1000, MII_READ);
			adv &= ~ADVERTISE_1000FULL;
			mii_rw(dev, np->phyaddr, MII_1000BT_CR, adv);
			mii_rw(dev, np->phyaddr, MII_CTRL1000, adv);
		}

		bmcr = mii_rw(dev, np->phyaddr, MII_BMCR, MII_READ);
@@ -2813,6 +2873,9 @@ static int nv_open(struct net_device *dev)

	writel(0, base + NvRegAdapterControl);

	if (np->pause_flags & NV_PAUSEFRAME_TX_CAPABLE)
		writel(NVREG_TX_PAUSEFRAME_DISABLE,  base + NvRegTxPauseFrame);

	/* 2) initialize descriptor rings */
	set_bufsize(dev);
	oom = nv_init_ring(dev);
@@ -3098,6 +3161,12 @@ static int __devinit nv_probe(struct pci_dev *pci_dev, const struct pci_device_i
		np->msi_flags |= NV_MSI_X_CAPABLE;
	}

	np->pause_flags = NV_PAUSEFRAME_RX_CAPABLE;
	if (id->driver_data & DEV_HAS_PAUSEFRAME_TX) {
		np->pause_flags |= NV_PAUSEFRAME_TX_CAPABLE;
	}
	

	err = -ENOMEM;
	np->base = ioremap(addr, np->register_size);
	if (!np->base)
@@ -3358,11 +3427,11 @@ static struct pci_device_id pci_tbl[] = {
	},
	{	/* MCP55 Ethernet Controller */
		PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NVENET_14),
		.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER|DEV_HAS_LARGEDESC|DEV_HAS_CHECKSUM|DEV_HAS_HIGH_DMA|DEV_HAS_VLAN|DEV_HAS_MSI|DEV_HAS_MSI_X|DEV_HAS_POWER_CNTRL,
		.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER|DEV_HAS_LARGEDESC|DEV_HAS_CHECKSUM|DEV_HAS_HIGH_DMA|DEV_HAS_VLAN|DEV_HAS_MSI|DEV_HAS_MSI_X|DEV_HAS_POWER_CNTRL|DEV_HAS_PAUSEFRAME_TX,
	},
	{	/* MCP55 Ethernet Controller */
		PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_DEVICE_ID_NVIDIA_NVENET_15),
		.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER|DEV_HAS_LARGEDESC|DEV_HAS_CHECKSUM|DEV_HAS_HIGH_DMA|DEV_HAS_VLAN|DEV_HAS_MSI|DEV_HAS_MSI_X|DEV_HAS_POWER_CNTRL,
		.driver_data = DEV_NEED_TIMERIRQ|DEV_NEED_LINKTIMER|DEV_HAS_LARGEDESC|DEV_HAS_CHECKSUM|DEV_HAS_HIGH_DMA|DEV_HAS_VLAN|DEV_HAS_MSI|DEV_HAS_MSI_X|DEV_HAS_POWER_CNTRL|DEV_HAS_PAUSEFRAME_TX,
	},
	{0,},
};