patch-2.4.20 linux-2.4.20/drivers/net/e100/e100_phy.c

Next file: linux-2.4.20/drivers/net/e100/e100_phy.h
Previous file: linux-2.4.20/drivers/net/e100/e100_main.c
Back to the patch index
Back to the overall index

diff -urN linux-2.4.19/drivers/net/e100/e100_phy.c linux-2.4.20/drivers/net/e100/e100_phy.c
@@ -0,0 +1,1126 @@
+/*******************************************************************************
+
+  
+  Copyright(c) 1999 - 2002 Intel Corporation. All rights reserved.
+  
+  This program is free software; you can redistribute it and/or modify it 
+  under the terms of the GNU General Public License as published by the Free 
+  Software Foundation; either version 2 of the License, or (at your option) 
+  any later version.
+  
+  This program is distributed in the hope that it will be useful, but WITHOUT 
+  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
+  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for 
+  more details.
+  
+  You should have received a copy of the GNU General Public License along with
+  this program; if not, write to the Free Software Foundation, Inc., 59 
+  Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+  
+  The full GNU General Public License is included in this distribution in the
+  file called LICENSE.
+  
+  Contact Information:
+  Linux NICS <linux.nics@intel.com>
+  Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
+*******************************************************************************/
+
+#include "e100_phy.h"
+
+void e100_handle_zlock(struct e100_private *bdp);
+
+/* 
+ * Procedure:	e100_mdi_write
+ *
+ * Description: This routine will write a value to the specified MII register
+ *		of an external MDI compliant device (e.g. PHY 100).  The
+ *		command will execute in polled mode.
+ *
+ * Arguments:
+ *	bdp - Ptr to this card's e100_bdconfig structure
+ *	reg_addr - The MII register that we are writing to
+ *	phy_addr - The MDI address of the Phy component.
+ *	data - The value that we are writing to the MII register.
+ *
+ * Returns:
+ *	NOTHING
+ */
+int
+e100_mdi_write(struct e100_private *bdp, u32 reg_addr, u32 phy_addr, u16 data)
+{
+	int e100_retry;
+	u32 temp_val;
+	unsigned int mdi_cntrl;
+
+	spin_lock_bh(&bdp->mdi_access_lock);
+	temp_val = (((u32) data) | (reg_addr << 16) |
+		    (phy_addr << 21) | (MDI_WRITE << 26));
+	writel(temp_val, &bdp->scb->scb_mdi_cntrl);
+	readw(&bdp->scb->scb_status);
+
+	/* wait 20usec before checking status */
+	udelay(20);
+
+	/* poll for the mdi write to complete */
+	e100_retry = E100_CMD_WAIT;
+	while ((!((mdi_cntrl = readl(&bdp->scb->scb_mdi_cntrl)) & MDI_PHY_READY)) && (e100_retry)) {
+
+		udelay(20);
+		e100_retry--;
+	}
+	spin_unlock_bh(&bdp->mdi_access_lock);
+	if (mdi_cntrl & MDI_PHY_READY) 
+		return 0;
+	else {
+		printk(KERN_ERR "e100: MDI write timeout\n");
+		return 1;
+	}
+}
+
+/* 
+ * Procedure:	e100_mdi_read
+ *
+ * Description: This routine will read a value from the specified MII register
+ *		of an external MDI compliant device (e.g. PHY 100), and return
+ *		it to the calling routine.  The command will execute in polled
+ *		mode.
+ *
+ * Arguments:
+ *	bdp - Ptr to this card's e100_bdconfig structure
+ *	reg_addr - The MII register that we are reading from
+ *	phy_addr - The MDI address of the Phy component.
+ *
+ * Results:
+ *	data - The value that we read from the MII register.
+ *
+ * Returns:
+ *	NOTHING
+ */
+int
+e100_mdi_read(struct e100_private *bdp, u32 reg_addr, u32 phy_addr, u16 *data)
+{
+	int e100_retry;
+	u32 temp_val;
+	unsigned int mdi_cntrl;
+
+	spin_lock_bh(&bdp->mdi_access_lock);
+	/* Issue the read command to the MDI control register. */
+	temp_val = ((reg_addr << 16) | (phy_addr << 21) | (MDI_READ << 26));
+	writel(temp_val, &bdp->scb->scb_mdi_cntrl);
+	readw(&bdp->scb->scb_status);
+
+	/* wait 20usec before checking status */
+	udelay(20);
+
+	/* poll for the mdi read to complete */
+	e100_retry = E100_CMD_WAIT;
+	while ((!((mdi_cntrl = readl(&bdp->scb->scb_mdi_cntrl)) & MDI_PHY_READY)) && (e100_retry)) {
+
+		udelay(20);
+		e100_retry--;
+	}
+
+	spin_unlock_bh(&bdp->mdi_access_lock);
+	if (mdi_cntrl & MDI_PHY_READY) {
+		/* return the lower word */
+		*data = (u16) mdi_cntrl;
+		return 0;
+	}
+	else {
+		printk(KERN_ERR "e100: MDI read timeout\n");
+		return 1;
+	}
+}
+
+static unsigned char __devinit
+e100_phy_valid(struct e100_private *bdp, unsigned int phy_address)
+{
+	u16 ctrl_reg, stat_reg;
+
+	/* Read the MDI control register */
+	e100_mdi_read(bdp, MII_BMCR, phy_address, &ctrl_reg);
+
+	/* Read the status register twice, bacause of sticky bits */
+	e100_mdi_read(bdp, MII_BMSR, phy_address, &stat_reg);
+	e100_mdi_read(bdp, MII_BMSR, phy_address, &stat_reg);
+
+	if ((ctrl_reg == 0xffff) || ((stat_reg == 0) && (ctrl_reg == 0)))
+		return false;
+
+	return true;
+}
+
+static void __devinit
+e100_phy_address_detect(struct e100_private *bdp)
+{
+	unsigned int addr;
+	unsigned char valid_phy_found = false;
+
+	if (IS_NC3133(bdp)) {
+		bdp->phy_addr = 0;
+		return;
+	}
+
+	if (e100_phy_valid(bdp, PHY_DEFAULT_ADDRESS)) {
+		bdp->phy_addr = PHY_DEFAULT_ADDRESS;
+		valid_phy_found = true;
+
+	} else {
+		for (addr = MIN_PHY_ADDR; addr <= MAX_PHY_ADDR; addr++) {
+			if (e100_phy_valid(bdp, addr)) {
+				bdp->phy_addr = addr;
+				valid_phy_found = true;
+				break;
+			}
+		}
+	}
+
+	if (!valid_phy_found) {
+		bdp->phy_addr = PHY_ADDRESS_503;
+	}
+}
+
+static void __devinit
+e100_phy_id_detect(struct e100_private *bdp)
+{
+	u16 low_id_reg, high_id_reg;
+
+	if (bdp->phy_addr == PHY_ADDRESS_503) {
+		bdp->PhyId = PHY_503;
+		return;
+	}
+	if (!(bdp->flags & IS_ICH)) {
+		if (bdp->rev_id >= D102_REV_ID) {
+			bdp->PhyId = PHY_82562ET;
+			return;
+		}
+	}
+
+	/* Read phy id from the MII register */
+	e100_mdi_read(bdp, MII_PHYSID1, bdp->phy_addr, &low_id_reg);
+	e100_mdi_read(bdp, MII_PHYSID2, bdp->phy_addr, &high_id_reg);
+
+	bdp->PhyId = ((unsigned int) low_id_reg |
+		      ((unsigned int) high_id_reg << 16));
+}
+
+static void __devinit
+e100_phy_isolate(struct e100_private *bdp)
+{
+	unsigned int phy_address;
+	u16 ctrl_reg;
+
+	/* Go over all phy addresses. Deisolate the selected one, and isolate
+	 * all the rest */
+	for (phy_address = 0; phy_address <= MAX_PHY_ADDR; phy_address++) {
+		if (phy_address != bdp->phy_addr) {
+			e100_mdi_write(bdp, MII_BMCR, phy_address,
+				       BMCR_ISOLATE);
+
+		} else {
+			e100_mdi_read(bdp, MII_BMCR, bdp->phy_addr, &ctrl_reg);
+			ctrl_reg &= ~BMCR_ISOLATE;
+			e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr, ctrl_reg);
+		}
+
+		udelay(100);
+	}
+}
+
+static unsigned char __devinit
+e100_phy_specific_setup(struct e100_private *bdp)
+{
+	u16 misc_reg;
+
+	if (bdp->phy_addr == PHY_ADDRESS_503) {
+		switch (bdp->params.e100_speed_duplex) {
+		case E100_AUTONEG:
+			/* The adapter can't autoneg. so set to 10/HALF */
+			printk(KERN_INFO
+			       "e100: 503 serial component detected which "
+			       "cannot autonegotiate\n");
+			printk(KERN_INFO
+			       "e100: speed/duplex forced to "
+			       "10Mbps / Half duplex\n");
+			bdp->params.e100_speed_duplex = E100_SPEED_10_HALF;
+			break;
+
+		case E100_SPEED_100_HALF:
+		case E100_SPEED_100_FULL:
+			printk(KERN_ERR
+			       "e100: 503 serial component detected "
+			       "which does not support 100Mbps\n");
+			printk(KERN_ERR
+			       "e100: Change the forced speed/duplex "
+			       "to a supported setting\n");
+			return false;
+		}
+
+		return true;
+	}
+
+	if (IS_NC3133(bdp)) {
+		u16 int_reg;
+
+		/* enable 100BASE fiber interface */
+		e100_mdi_write(bdp, MDI_NC3133_CONFIG_REG, bdp->phy_addr,
+			       MDI_NC3133_100FX_ENABLE);
+
+		if ((bdp->params.e100_speed_duplex != E100_AUTONEG) &&
+		    (bdp->params.e100_speed_duplex != E100_SPEED_100_FULL)) {
+			/* just inform user about 100 full */
+			printk(KERN_ERR "e100: NC3133 NIC can only run "
+			       "at 100Mbps full duplex\n");
+		}
+
+		bdp->params.e100_speed_duplex = E100_SPEED_100_FULL;
+
+		/* enable interrupts */
+		e100_mdi_read(bdp, MDI_NC3133_INT_ENABLE_REG,
+			      bdp->phy_addr, &int_reg);
+		int_reg |= MDI_NC3133_INT_ENABLE;
+		e100_mdi_write(bdp, MDI_NC3133_INT_ENABLE_REG,
+			       bdp->phy_addr, int_reg);
+	}
+
+	/* Handle the National TX */
+	if ((bdp->PhyId & PHY_MODEL_REV_ID_MASK) == PHY_NSC_TX) {
+		e100_mdi_read(bdp, NSC_CONG_CONTROL_REG,
+			      bdp->phy_addr, &misc_reg);
+
+		misc_reg |= NSC_TX_CONG_TXREADY;
+
+		/* disable the congestion control bit in the National Phy */
+		misc_reg &= ~NSC_TX_CONG_ENABLE;
+
+		e100_mdi_write(bdp, NSC_CONG_CONTROL_REG,
+			       bdp->phy_addr, misc_reg);
+	}
+
+	return true;
+}
+
+/* 
+ * Procedure:	e100_phy_fix_squelch
+ *
+ * Description:
+ *	Help find link on certain rare scenarios.
+ *	NOTE: This routine must be called once per watchdog,
+ *	      and *after* setting the current link state.
+ *
+ * Arguments:
+ *	bdp - Ptr to this card's e100_bdconfig structure
+ *
+ * Returns:
+ *	NOTHING
+ */
+static void
+e100_phy_fix_squelch(struct e100_private *bdp)
+{
+	if ((bdp->PhyId != PHY_82555_TX) || (bdp->flags & DF_SPEED_FORCED))
+		return;
+
+	if (netif_carrier_ok(bdp->device)) {
+		switch (bdp->PhyState) {
+		case 0:
+			break;
+		case 1:
+			e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL,
+				       bdp->phy_addr, 0x0000);
+			break;
+		case 2:
+			e100_mdi_write(bdp, PHY_82555_MDI_EQUALIZER_CSR,
+				       bdp->phy_addr, 0x3000);
+			break;
+		}
+		bdp->PhyState = 0;
+		bdp->PhyDelay = 0;
+
+	} else if (!bdp->PhyDelay--) {
+		switch (bdp->PhyState) {
+		case 0:
+			e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL,
+				       bdp->phy_addr, EXTENDED_SQUELCH_BIT);
+			bdp->PhyState = 1;
+			break;
+		case 1:
+			e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL,
+				       bdp->phy_addr, 0x0000);
+			e100_mdi_write(bdp, PHY_82555_MDI_EQUALIZER_CSR,
+				       bdp->phy_addr, 0x2010);
+			bdp->PhyState = 2;
+			break;
+		case 2:
+			e100_mdi_write(bdp, PHY_82555_MDI_EQUALIZER_CSR,
+				       bdp->phy_addr, 0x3000);
+			bdp->PhyState = 0;
+			break;
+		}
+
+		e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr,
+			       BMCR_ANENABLE | BMCR_ANRESTART);
+		bdp->PhyDelay = 3;
+	}
+}
+
+/* 
+ * Procedure:	e100_fix_polarity
+ *
+ * Description:
+ *	Fix for 82555 auto-polarity toggle problem. With a short cable 
+ *	connecting an 82555 with an 840A link partner, if the medium is noisy,
+ *	the 82555 sometime thinks that the polarity might be wrong and so 
+ *	toggles polarity. This happens repeatedly and results in a high bit 
+ *	error rate.
+ *	NOTE: This happens only at 10 Mbps
+ *
+ * Arguments:
+ *	bdp - Ptr to this card's e100_bdconfig structure
+ *
+ * Returns:
+ *	NOTHING
+ */
+static void __devinit
+e100_fix_polarity(struct e100_private *bdp)
+{
+	u16 status;
+	u16 errors;
+	u16 misc_reg;
+	int speed;
+
+	if ((bdp->PhyId != PHY_82555_TX) && (bdp->PhyId != PHY_82562ET) &&
+	    (bdp->PhyId != PHY_82562EM))
+		return;
+
+	/* If the user wants auto-polarity disabled, do only that and nothing *
+	 * else. * e100_autopolarity == 0 means disable --- we do just the
+	 * disabling * e100_autopolarity == 1 means enable  --- we do nothing at
+	 * all * e100_autopolarity >= 2 means we do the workaround code. */
+	/* Change for 82558 enhancement */
+	switch (E100_AUTOPOLARITY) {
+	case 0:
+		e100_mdi_read(bdp, PHY_82555_SPECIAL_CONTROL,
+			      bdp->phy_addr, &misc_reg);
+		e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL, bdp->phy_addr,
+			       (u16) (misc_reg | DISABLE_AUTO_POLARITY));
+		break;
+
+	case 1:
+		e100_mdi_read(bdp, PHY_82555_SPECIAL_CONTROL,
+			      bdp->phy_addr, &misc_reg);
+		e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL, bdp->phy_addr,
+			       (u16) (misc_reg & ~DISABLE_AUTO_POLARITY));
+		break;
+
+	case 2:
+		/* we do this only if link is up */
+		if (!netif_carrier_ok(bdp->device)) {
+			break;
+		}
+
+		e100_mdi_read(bdp, PHY_82555_CSR, bdp->phy_addr, &status);
+		speed = (status & PHY_82555_SPEED_BIT) ? 100 : 10;
+
+		/* we need to do this only if speed is 10 */
+		if (speed != 10) {
+			break;
+		}
+
+		/* see if we have any end of frame errors */
+		e100_mdi_read(bdp, PHY_82555_EOF_COUNTER,
+			      bdp->phy_addr, &errors);
+
+		/* if non-zero, wait for 100 ms before reading again */
+		if (errors) {
+			udelay(200);
+			e100_mdi_read(bdp, PHY_82555_EOF_COUNTER,
+				      bdp->phy_addr, &errors);
+
+			/* if non-zero again, we disable polarity */
+			if (errors) {
+				e100_mdi_read(bdp, PHY_82555_SPECIAL_CONTROL,
+					      bdp->phy_addr, &misc_reg);
+				e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL,
+					       bdp->phy_addr,
+					       (u16) (misc_reg |
+						      DISABLE_AUTO_POLARITY));
+			}
+		}
+
+		if (!errors) {
+			/* it is safe to read the polarity now */
+			e100_mdi_read(bdp, PHY_82555_CSR,
+				      bdp->phy_addr, &status);
+
+			/* if polarity is normal, disable polarity */
+			if (!(status & PHY_82555_POLARITY_BIT)) {
+				e100_mdi_read(bdp, PHY_82555_SPECIAL_CONTROL,
+					      bdp->phy_addr, &misc_reg);
+				e100_mdi_write(bdp, PHY_82555_SPECIAL_CONTROL,
+					       bdp->phy_addr,
+					       (u16) (misc_reg |
+						      DISABLE_AUTO_POLARITY));
+			}
+		}
+		break;
+
+	default:
+		break;
+	}
+}
+
+/* 
+ * Procedure:	e100_find_speed_duplex
+ *
+ * Description: This routine will figure out what line speed and duplex mode
+ *		the PHY is currently using.
+ *
+ * Arguments:
+ *	bdp - Ptr to this card's e100_bdconfig structure
+ *
+ * Returns:
+ *	NOTHING
+ */
+static void
+e100_find_speed_duplex(struct e100_private *bdp)
+{
+	unsigned int PhyId;
+	u16 stat_reg, misc_reg;
+	u16 ad_reg, lp_ad_reg;
+
+	PhyId = bdp->PhyId & PHY_MODEL_REV_ID_MASK;
+
+	/* First we should check to see if we have link */
+	/* If we don't have a link no reason to print a speed and duplex */
+	if (!e100_update_link_state(bdp)) {
+		bdp->cur_line_speed = 0;
+		bdp->cur_dplx_mode = 0;
+		return;
+	}
+
+	/* On the 82559 and later controllers, speed/duplex is part of the *
+	 * SCB. So, we save an mdi_read and get these from the SCB. * */
+	if (bdp->rev_id >= D101MA_REV_ID) {
+		/* Read speed */
+		if (readb(&bdp->scb->scb_ext.d101m_scb.scb_gen_stat) & BIT_1)
+			bdp->cur_line_speed = 100;
+		else
+			bdp->cur_line_speed = 10;
+
+		/* Read duplex */
+		if (readb(&bdp->scb->scb_ext.d101m_scb.scb_gen_stat) & BIT_2)
+			bdp->cur_dplx_mode = FULL_DUPLEX;
+		else
+			bdp->cur_dplx_mode = HALF_DUPLEX;
+
+		return;
+	}
+
+	/* If this is a Phy 100, then read bits 1 and 0 of extended register 0,
+	 * to get the current speed and duplex settings. */
+	if ((PhyId == PHY_100_A) || (PhyId == PHY_100_C) ||
+	    (PhyId == PHY_82555_TX)) {
+
+		/* Read Phy 100 extended register 0 */
+		e100_mdi_read(bdp, EXTENDED_REG_0, bdp->phy_addr, &misc_reg);
+
+		/* Get current speed setting */
+		if (misc_reg & PHY_100_ER0_SPEED_INDIC)
+			bdp->cur_line_speed = 100;
+		else
+			bdp->cur_line_speed = 10;
+
+		/* Get current duplex setting -- FDX enabled if bit is set */
+		if (misc_reg & PHY_100_ER0_FDX_INDIC)
+			bdp->cur_dplx_mode = FULL_DUPLEX;
+		else
+			bdp->cur_dplx_mode = HALF_DUPLEX;
+
+		return;
+	}
+
+	/* See if link partner is capable of Auto-Negotiation (bit 0, reg 6) */
+	e100_mdi_read(bdp, MII_EXPANSION, bdp->phy_addr, &misc_reg);
+
+	/* See if Auto-Negotiation was complete (bit 5, reg 1) */
+	e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &stat_reg);
+
+	/* If a True NWAY connection was made, then we can detect speed/dplx
+	 * by ANDing our adapter's advertised abilities with our link partner's
+	 * advertised ablilities, and then assuming that the highest common
+	 * denominator was chosed by NWAY. */
+	if ((misc_reg & EXPANSION_NWAY) && (stat_reg & BMSR_ANEGCOMPLETE)) {
+
+		/* Read our advertisement register */
+		e100_mdi_read(bdp, MII_ADVERTISE, bdp->phy_addr, &ad_reg);
+
+		/* Read our link partner's advertisement register */
+		e100_mdi_read(bdp, MII_LPA, bdp->phy_addr, &lp_ad_reg);
+
+		/* AND the two advertisement registers together, and get rid
+		 * of any extraneous bits. */
+		ad_reg &= (lp_ad_reg & NWAY_LP_ABILITY);
+
+		/* Get speed setting */
+		if (ad_reg &
+		    (ADVERTISE_100HALF | ADVERTISE_100FULL |
+		     ADVERTISE_100BASE4))
+
+			bdp->cur_line_speed = 100;
+		else
+			bdp->cur_line_speed = 10;
+
+		/* Get duplex setting -- use priority resolution algorithm */
+		if (ad_reg & ADVERTISE_100BASE4) {
+			bdp->cur_dplx_mode = HALF_DUPLEX;
+		} else if (ad_reg & ADVERTISE_100FULL) {
+			bdp->cur_dplx_mode = FULL_DUPLEX;
+		} else if (ad_reg & ADVERTISE_100HALF) {
+			bdp->cur_dplx_mode = HALF_DUPLEX;
+		} else if (ad_reg & ADVERTISE_10FULL) {
+			bdp->cur_dplx_mode = FULL_DUPLEX;
+		} else {
+			bdp->cur_dplx_mode = HALF_DUPLEX;
+		}
+
+		return;
+	}
+
+	/* If we are connected to a dumb (non-NWAY) repeater or hub, and the
+	 * line speed was determined automatically by parallel detection, then
+	 * we have no way of knowing exactly what speed the PHY is set to
+	 * unless that PHY has a propietary register which indicates speed in
+	 * this situation. The NSC TX PHY does have such a register. Also,
+	 * since NWAY didn't establish the connection, the duplex setting
+	 * should HALF duplex. */
+	bdp->cur_dplx_mode = HALF_DUPLEX;
+
+	if (PhyId == PHY_NSC_TX) {
+		/* Read register 25 to get the SPEED_10 bit */
+		e100_mdi_read(bdp, NSC_SPEED_IND_REG, bdp->phy_addr, &misc_reg);
+
+		/* If bit 6 was set then we're at 10Mbps */
+		if (misc_reg & NSC_TX_SPD_INDC_SPEED)
+			bdp->cur_line_speed = 10;
+		else
+			bdp->cur_line_speed = 100;
+
+	} else {
+		/* If we don't know the line speed, default to 10Mbps */
+		bdp->cur_line_speed = 10;
+	}
+}
+
+/* 
+ * Procedure: e100_force_speed_duplex
+ *
+ * Description: This routine forces line speed and duplex mode of the
+ * adapter based on the values the user has set in e100.c.
+ *
+ * Arguments:  bdp - Pointer to the e100_private structure for the board
+ *
+ * Returns: void
+ *
+ */
+void
+e100_force_speed_duplex(struct e100_private *bdp)
+{
+	u16 control;
+	unsigned long expires;
+
+	e100_phy_reset(bdp);
+
+	bdp->flags |= DF_SPEED_FORCED;
+
+	e100_mdi_read(bdp, MII_BMCR, bdp->phy_addr, &control);
+	control &= ~BMCR_ANENABLE;
+	control &= ~BMCR_LOOPBACK;
+
+	/* Check e100.c values */
+	switch (bdp->params.e100_speed_duplex) {
+	case E100_SPEED_10_HALF:
+		control &= ~BMCR_SPEED100;
+		control &= ~BMCR_FULLDPLX;
+		bdp->cur_line_speed = 10;
+		bdp->cur_dplx_mode = HALF_DUPLEX;
+		break;
+
+	case E100_SPEED_10_FULL:
+		control &= ~BMCR_SPEED100;
+		control |= BMCR_FULLDPLX;
+		bdp->cur_line_speed = 10;
+		bdp->cur_dplx_mode = FULL_DUPLEX;
+		break;
+
+	case E100_SPEED_100_HALF:
+		control |= BMCR_SPEED100;
+		control &= ~BMCR_FULLDPLX;
+		bdp->cur_line_speed = 100;
+		bdp->cur_dplx_mode = HALF_DUPLEX;
+		break;
+
+	case E100_SPEED_100_FULL:
+		control |= BMCR_SPEED100;
+		control |= BMCR_FULLDPLX;
+		bdp->cur_line_speed = 100;
+		bdp->cur_dplx_mode = FULL_DUPLEX;
+		break;
+	}
+
+	e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr, control);
+
+	/* loop must run at least once */
+	expires = jiffies + 2 * HZ;
+	do {
+		if (e100_update_link_state(bdp) || 
+		    time_after(jiffies, expires)) {
+			break;
+		} else {
+			yield();
+		}
+
+	} while (true);
+}
+
+/* 
+ * Procedure: e100_set_fc
+ *
+ * Description: Checks the link's capability for flow control.
+ * 
+ * Arguments:  bdp - Pointer to the e100_private structure for the board
+ *		    
+ * Returns: void
+ *
+ */
+static void
+e100_set_fc(struct e100_private *bdp)
+{
+	u16 ad_reg;
+	u16 lp_ad_reg;
+	u16 exp_reg;
+
+	/* no flow control for 82557, forced links or half duplex */
+	if (!netif_carrier_ok(bdp->device) || (bdp->flags & DF_SPEED_FORCED) ||
+	    (bdp->cur_dplx_mode == HALF_DUPLEX) ||
+	    !(bdp->flags & IS_BACHELOR)) {
+
+		bdp->flags &= ~DF_LINK_FC_CAP;
+		return;
+	}
+
+	/* See if link partner is capable of Auto-Negotiation (bit 0, reg 6) */
+	e100_mdi_read(bdp, MII_EXPANSION, bdp->phy_addr, &exp_reg);
+
+	if (exp_reg & EXPANSION_NWAY) {
+		/* Read our advertisement register */
+		e100_mdi_read(bdp, MII_ADVERTISE, bdp->phy_addr, &ad_reg);
+
+		/* Read our link partner's advertisement register */
+		e100_mdi_read(bdp, MII_LPA, bdp->phy_addr, &lp_ad_reg);
+
+		ad_reg &= lp_ad_reg;	/* AND the 2 ad registers */
+
+		if (ad_reg & NWAY_AD_FC_SUPPORTED)
+			bdp->flags |= DF_LINK_FC_CAP;
+		else
+			/* If link partner is capable of autoneg, but  */
+			/* not capable of flow control, Received PAUSE */
+			/* frames are still honored, i.e.,             */
+		        /* transmitted frames would be paused */
+			/* by incoming PAUSE frames           */
+			bdp->flags |= DF_LINK_FC_TX_ONLY;
+
+	} else {
+		bdp->flags &= ~DF_LINK_FC_CAP;
+	}
+}
+
+/* 
+ * Procedure: e100_phy_check
+ * 
+ * Arguments:  bdp - Pointer to the e100_private structure for the board
+ *
+ * Returns: true if link state was changed
+ *	   false otherwise
+ *
+ */
+unsigned char
+e100_phy_check(struct e100_private *bdp)
+{
+	unsigned char old_link;
+	unsigned char changed = false;
+
+	old_link = netif_carrier_ok(bdp->device) ? 1 : 0;
+	e100_find_speed_duplex(bdp);
+
+	if (!old_link && netif_carrier_ok(bdp->device)) {
+		e100_set_fc(bdp);
+		changed = true;
+	}
+
+	if (old_link && !netif_carrier_ok(bdp->device)) {
+		/* reset the zero lock state */
+		bdp->zlock_state = ZLOCK_INITIAL;
+
+		// set auto lock for phy auto-negotiation on link up
+		if ((bdp->PhyId & PHY_MODEL_REV_ID_MASK) == PHY_82555_TX)
+			e100_mdi_write(bdp, PHY_82555_MDI_EQUALIZER_CSR,
+				       bdp->phy_addr, 0);
+		changed = true;
+	}
+
+	e100_phy_fix_squelch(bdp);
+	e100_handle_zlock(bdp);
+
+	return changed;
+}
+
+/* 
+ * Procedure:	e100_auto_neg
+ *
+ * Description: This routine will start autonegotiation and wait
+ *		     for it to complete
+ *
+ * Arguments:
+ *	bdp		- pointer to this card's e100_bdconfig structure
+ *	force_restart	- defines if autoneg should be restarted even if it
+ *			has been completed before
+ * Returns:
+ *	NOTHING
+ */
+static void
+e100_auto_neg(struct e100_private *bdp, unsigned char force_restart)
+{
+	u16 stat_reg;
+	unsigned long expires;
+
+	bdp->flags &= ~DF_SPEED_FORCED;
+
+	e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &stat_reg);
+	e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &stat_reg);
+
+	/* if we are capable of performing autoneg then we restart if needed */
+	if ((stat_reg != 0xFFFF) && (stat_reg & BMSR_ANEGCAPABLE)) {
+
+		if ((!force_restart) &&
+		    (stat_reg & BMSR_ANEGCOMPLETE)) {
+			goto exit;
+		}
+
+		e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr,
+			       BMCR_ANENABLE | BMCR_ANRESTART);
+
+		/* wait for autoneg to complete (up to 3 seconds) */
+		expires = jiffies + HZ * 3;
+		do {
+			/* now re-read the value. Sticky so read twice */
+			e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &stat_reg);
+			e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &stat_reg);
+
+			if ((stat_reg & BMSR_ANEGCOMPLETE) ||
+			    time_after(jiffies, expires) ) {
+				goto exit;
+			} else {
+				yield();
+			}
+		} while (true);
+	}
+
+exit:
+	e100_find_speed_duplex(bdp);
+}
+
+void
+e100_phy_set_speed_duplex(struct e100_private *bdp, unsigned char force_restart)
+{
+	if (bdp->params.e100_speed_duplex == E100_AUTONEG) {
+        	if (bdp->rev_id >= D102_REV_ID) 
+			/* Enable MDI/MDI-X auto switching */
+                	e100_mdi_write(bdp, MII_NCONFIG, bdp->phy_addr,
+		                       MDI_MDIX_AUTO_SWITCH_ENABLE);
+		e100_auto_neg(bdp, force_restart);
+
+	} else {
+        	if (bdp->rev_id >= D102_REV_ID) 
+			/* Disable MDI/MDI-X auto switching */
+                	e100_mdi_write(bdp, MII_NCONFIG, bdp->phy_addr,
+		                       MDI_MDIX_RESET_ALL_MASK);
+		e100_force_speed_duplex(bdp);
+	}
+
+	e100_set_fc(bdp);
+}
+
+void
+e100_phy_autoneg(struct e100_private *bdp)
+{
+	u16 ctrl_reg;
+
+	ctrl_reg = BMCR_ANENABLE | BMCR_ANRESTART | BMCR_RESET;
+
+	e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr, ctrl_reg);
+
+	udelay(100);
+}
+
+void
+e100_phy_set_loopback(struct e100_private *bdp)
+{
+	u16 ctrl_reg;
+	ctrl_reg = BMCR_LOOPBACK;
+	e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr, ctrl_reg);
+		udelay(100);
+}
+	
+void
+e100_phy_reset(struct e100_private *bdp)
+{
+	u16 ctrl_reg;
+	ctrl_reg = BMCR_RESET;
+	e100_mdi_write(bdp, MII_BMCR, bdp->phy_addr, ctrl_reg);
+}
+
+unsigned char __devinit
+e100_phy_init(struct e100_private *bdp)
+{
+	e100_phy_address_detect(bdp);
+	e100_phy_isolate(bdp);
+	e100_phy_id_detect(bdp);
+
+	if (!e100_phy_specific_setup(bdp))
+		return false;
+
+	bdp->PhyState = 0;
+	bdp->PhyDelay = 0;
+	bdp->zlock_state = ZLOCK_INITIAL;
+
+	e100_phy_set_speed_duplex(bdp, false);
+	e100_fix_polarity(bdp);
+
+	return true;
+}
+
+/* 
+ * Procedure: e100_get_link_state
+ * 
+ * Description: This routine checks the link status of the adapter
+ *
+ * Arguments:  bdp - Pointer to the e100_private structure for the board
+ *		    
+ *
+ * Returns: true - If a link is found
+ *		false - If there is no link
+ *
+ */
+unsigned char
+e100_get_link_state(struct e100_private *bdp)
+{
+	unsigned char link = false;
+	u16 status;
+
+	/* Check link status */
+	/* If the controller is a 82559 or later one, link status is available
+	 * from the CSR. This avoids the mdi_read. */
+	if (bdp->rev_id >= D101MA_REV_ID) {
+		if (readb(&bdp->scb->scb_ext.d101m_scb.scb_gen_stat) & BIT_0) {
+			link = true;
+		} else {
+			link = false;
+		}
+
+	} else {
+		/* Read the status register twice because of sticky bits */
+		e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &status);
+		e100_mdi_read(bdp, MII_BMSR, bdp->phy_addr, &status);
+
+		if (status & BMSR_LSTATUS) {
+			link = true;
+		} else {
+			link = false;
+		}
+	}
+
+	return link;
+}
+
+/* 
+ * Procedure: e100_update_link_state
+ * 
+ * Description: This routine updates the link status of the adapter,
+ * 		also considering netif_running
+ *
+ * Arguments:  bdp - Pointer to the e100_private structure for the board
+ *		    
+ *
+ * Returns: true - If a link is found
+ *		false - If there is no link
+ *
+ */
+unsigned char
+e100_update_link_state(struct e100_private *bdp)
+{
+	unsigned char link;
+
+	/* Logical AND PHY link & netif_running */
+	link = e100_get_link_state(bdp) && netif_running(bdp->device);
+
+	if (link) {
+		if (!netif_carrier_ok(bdp->device))
+			netif_carrier_on(bdp->device);
+	} else {
+		if (netif_carrier_ok(bdp->device))
+			netif_carrier_off(bdp->device);
+	}
+
+	return link;
+}
+
+/**************************************************************************\
+ **
+ ** PROC NAME:     e100_handle_zlock
+ **    This function manages a state machine that controls
+ **    the driver's zero locking algorithm.
+ **    This function is called by e100_watchdog() every ~2 second.
+ ** States:
+ **    The current link handling state is stored in 
+ **    bdp->zlock_state, and is one of:
+ **    ZLOCK_INITIAL, ZLOCK_READING, ZLOCK_SLEEPING
+ **    Detailed description of the states and the transitions
+ **    between states is found below.
+ **    Note that any time the link is down / there is a reset
+ **    state will be changed outside this function to ZLOCK_INITIAL
+ ** Algorithm:
+ **    1. If link is up & 100 Mbps continue else stay in #1:
+ **    2. Set 'auto lock'
+ **    3. Read & Store 100 times 'Zero' locked in 1 sec interval
+ **    4. If max zero read >= 0xB continue else goto 1
+ **    5. Set most popular 'Zero' read in #3
+ **    6. Sleep 5 minutes
+ **    7. Read number of errors, if it is > 300 goto 2 else goto 6
+ ** Data Structures (in DRIVER_DATA):
+ **    zlock_state           - current state of the algorithm
+ **    zlock_read_cnt        - counts number of reads (up to 100)
+ **    zlock_read_data[i]    - counts number of times 'Zero' read was i, 0 <= i <= 15
+ **    zlock_sleep_cnt       - keeps track of "sleep" time (up to 300 secs = 5 minutes)
+ **                                
+ ** Parameters:    DRIVER_DATA    *bdp
+ **
+ **                bdp  - Pointer to HSM's adapter data space
+ **
+ ** Return Value:  NONE
+ **
+ ** See Also:      e100_watchdog()
+ **
+ \**************************************************************************/
+void
+e100_handle_zlock(struct e100_private *bdp)
+{
+	u16 pos;
+	u16 eq_reg;
+	u16 err_cnt;
+	u8 mpz;			/* Most Popular Zero */
+
+	switch (bdp->zlock_state) {
+	case ZLOCK_INITIAL:
+
+		if (((u8) bdp->rev_id <= D102_REV_ID) ||
+		    !(bdp->cur_line_speed == 100) ||
+		    !netif_carrier_ok(bdp->device)) {
+			break;
+		}
+
+		/* initialize hw and sw and start reading */
+		e100_mdi_write(bdp, PHY_82555_MDI_EQUALIZER_CSR,
+			       bdp->phy_addr, 0);
+		/* reset read counters: */
+		bdp->zlock_read_cnt = 0;
+		for (pos = 0; pos < 16; pos++)
+			bdp->zlock_read_data[pos] = 0;
+		/* start reading in the next call back: */
+		bdp->zlock_state = ZLOCK_READING;
+
+		/* FALL THROUGH !! */
+
+	case ZLOCK_READING:
+		/* state: reading (100 times) zero locked in 1 sec interval
+		 * prev states: ZLOCK_INITIAL
+		 * next states: ZLOCK_INITIAL, ZLOCK_SLEEPING */
+
+		e100_mdi_read(bdp, PHY_82555_MDI_EQUALIZER_CSR,
+			      bdp->phy_addr, &eq_reg);
+		pos = (eq_reg & ZLOCK_ZERO_MASK) >> 4;
+		bdp->zlock_read_data[pos]++;
+		bdp->zlock_read_cnt++;
+
+		if (bdp->zlock_read_cnt == ZLOCK_MAX_READS) {
+			/* check if we read a 'Zero' value of 0xB or greater */
+			if ((bdp->zlock_read_data[0xB]) ||
+			    (bdp->zlock_read_data[0xC]) ||
+			    (bdp->zlock_read_data[0xD]) ||
+			    (bdp->zlock_read_data[0xE]) ||
+			    (bdp->zlock_read_data[0xF])) {
+
+				/* we've read 'Zero' value of 0xB or greater,
+				 * find most popular 'Zero' value and lock it */
+				mpz = 0;
+				/* this loop finds the most popular 'Zero': */
+				for (pos = 1; pos < 16; pos++) {
+					if (bdp->zlock_read_data[pos] >
+					    bdp->zlock_read_data[mpz])
+
+						mpz = pos;
+				}
+				/* now lock the most popular 'Zero': */
+				eq_reg = (ZLOCK_SET_ZERO | mpz);
+				e100_mdi_write(bdp,
+					       PHY_82555_MDI_EQUALIZER_CSR,
+					       bdp->phy_addr, eq_reg);
+
+				/* sleep for 5 minutes: */
+				bdp->zlock_sleep_cnt = jiffies;
+				bdp->zlock_state = ZLOCK_SLEEPING;
+				/* we will be reading the # of errors after 5
+				 * minutes, so we need to reset the error
+				 * counters - these registers are self clearing
+				 * on read, so read them */
+				e100_mdi_read(bdp, PHY_82555_SYMBOL_ERR,
+					      bdp->phy_addr, &err_cnt);
+
+			} else {
+				/* we did not read a 'Zero' value of 0xB or
+				 * above. go back to the start */
+				bdp->zlock_state = ZLOCK_INITIAL;
+			}
+
+		}
+		break;
+
+	case ZLOCK_SLEEPING:
+		/* state: sleeping for 5 minutes
+		 * prev states: ZLOCK_READING
+		 * next states: ZLOCK_READING, ZLOCK_SLEEPING */
+
+		/* if 5 minutes have passed: */
+		if ((jiffies - bdp->zlock_sleep_cnt) >= ZLOCK_MAX_SLEEP) {
+			/* read and sum up the number of errors:  */
+			e100_mdi_read(bdp, PHY_82555_SYMBOL_ERR,
+				      bdp->phy_addr, &err_cnt);
+			/* if we've more than 300 errors (this number was
+			 * calculated according to the spec max allowed errors
+			 * (80 errors per 1 million frames) for 5 minutes in
+			 * 100 Mbps (or the user specified max BER number) */
+			if (err_cnt > bdp->params.ber) {
+				/* start again in the next callback: */
+				bdp->zlock_state = ZLOCK_INITIAL;
+			} else {
+				/* we don't have more errors than allowed,
+				 * sleep for 5 minutes */
+				bdp->zlock_sleep_cnt = jiffies;
+			}
+		}
+		break;
+
+	default:
+		break;
+	}
+}

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)