Source to iokit/Drivers/network/drvIntel82557/i82557PHY.cpp


Enter a symbol's name here to quickly find it.

/*
 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * The contents of this file constitute Original Code as defined in and
 * are subject to the Apple Public Source License Version 1.1 (the
 * "License").  You may not use this file except in compliance with the
 * License.  Please obtain a copy of the License at
 * http://www.apple.com/publicsource and read it before using this file.
 * 
 * This Original Code and all software distributed under the License are
 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
 * License for the specific language governing rights and limitations
 * under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
/*
 * Copyright (c) 1996 NeXT Software, Inc.  All rights reserved. 
 *
 * i82557PHY.cpp
 *
 */

#include "i82557.h"
#include "i82557PHY.h"

//---------------------------------------------------------------------------
// Function: _logMDIStatus
//
// Purpose:
//   Dump the contents of the MDI status register.

static inline void 
_logMDIStatus(mdi_reg_t reg)
{
	if (reg & MDI_STATUS_T4)
		IOLog("PHY: T4 capable\n");
    if (reg & MDI_STATUS_TX_FD)
		IOLog("PHY: 100Base-TX full duplex capable\n");
    if (reg & MDI_STATUS_TX_HD)
		IOLog("PHY: 100Base-TX half duplex capable\n");
    if (reg & MDI_STATUS_10_FD)
		IOLog("PHY: 10Base-T full duplex capable\n");
    if (reg & MDI_STATUS_10_HD)
		IOLog("PHY: 10Base-T half duplex capable\n");
    if (reg & MDI_STATUS_EXTENDED_CAPABILITY)
		IOLog("PHY: has extended capability registers\n");
    if (reg & MDI_STATUS_JABBER_DETECTED)
		IOLog("PHY: jabberDetect set\n");
    if (reg & MDI_STATUS_AUTONEG_CAPABLE)
		IOLog("PHY: auto negotiation capable\n");
    IOLog("PHY: link is %s\n", (reg & MDI_STATUS_LINK_STATUS) ? "UP" : "DOWN");
    return;
}

//---------------------------------------------------------------------------
// Function: _getModelId
//
// Purpose:
//   Read the MDI ID registers and form a single 32-bit id.

UInt32 Intel82557::_phyGetID()
{
    UInt16	id1, id2;
	_mdiReadPHY(phyAddr, MDI_REG_PHYID_WORD_1, &id1);
	_mdiReadPHY(phyAddr, MDI_REG_PHYID_WORD_2, &id2);
    return ((id2 << 16) | id1);
}

//---------------------------------------------------------------------------
// Function: _phySetMedium
//
// Purpose:
//   Setup the PHY to the medium type given.
//   Returns true on success.

bool Intel82557::_phySetMedium(mediumType_t medium)
{
	mdi_reg_t       status;
	mdi_reg_t       control;
	mediumType_t    phyMedium = medium;
	UInt32          mediumCapableMask;

	// Reset PHY before changing medium selection.
	//
	_phyReset();

	// Get local capability.
	//
	_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status);

	// Create a medium capable mask.
	//
	mediumCapableMask = (status >> 11) & 0x1f;

	// Force the PHY's data rate and duplex settings if the medium type
	// chosen is not AUTO.
	//
	if (phyMedium != MEDIUM_TYPE_AUTO) {
		if ((MEDIUM_TYPE_TO_MASK(phyMedium) & mediumCapableMask) == 0) {
			// Hardware is not capable of selecting the user-selected
			// medium.
			//
			return false;
		}
		else {
			// Medium chosen is valid, go ahead and set PHY.
			//
			bool speed100   = false;
			bool fullDuplex = false;
			
			if ((medium == MEDIUM_TYPE_TX_HD) ||
				(medium == MEDIUM_TYPE_TX_FD) ||
				(medium == MEDIUM_TYPE_T4))
				speed100 = true;

			if ((medium == MEDIUM_TYPE_10_FD) || (medium == MEDIUM_TYPE_TX_FD))
				fullDuplex = true;

			// Disable auto-negotiation function and force speed + duplex.
			//
			IOSleep(300);

			control = ((speed100 ? MDI_CONTROL_100 : 0) |
			           (fullDuplex ? MDI_CONTROL_FULL_DUPLEX : 0));

			_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control);

			VPRINT("%s: user forced %s Mbit/s%s mode\n", getName(),
				speed100 ? "100" : "10",
				fullDuplex ? " full duplex" : "");

			IOSleep(50);
		}
	}
	else {
		// For MEDIUM_TYPE_AUTO, enable and restart auto-negotiation.
		//
		control = MDI_CONTROL_AUTONEG_ENABLE;
		_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control);
		IOSleep(1);
		control |= MDI_CONTROL_RESTART_AUTONEG;
		_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control);
	}

	// Some special bit twiddling for NSC83840.
	//
	if (phyID == PHY_MODEL_NSC83840) {
		/* set-up National Semiconductor 83840 specific registers */
		
		mdi_reg_t	reg;

		VPRINT("%s: setting NSC83840-specific registers\n", getName());
		_mdiReadPHY(phyAddr, NSC83840_REG_PCR, &reg);

		/*
		 * This bit MUST be set, otherwise the card may not transmit at
		 * all in 100Mb/s mode. This is specially true for 82557 cards
		 * with the DP83840 PHY.
		 *
		 * In the NSC documentation, bit 10 of PCS register is labeled
		 * as a reserved bit. What is the real function of this bit?
		 */
		reg |= (NSC83840_PCR_TXREADY | NSC83840_PCR_CIM_DIS);

		_mdiWritePHY(phyAddr, NSC83840_REG_PCR, reg);
	}

	currentMediumType = medium;

	return true;
}

//---------------------------------------------------------------------------
// Function: _phyAddMediumType
//
// Purpose:
//   Add a single medium object to the medium dictionary.
//   Also add the medium object to an array for fast lookup.

bool Intel82557::_phyAddMediumType(UInt32 type, UInt32 speed, UInt32 code)
{	
	IONetworkMedium	* medium;
	bool              ret = false;
	
	medium = IONetworkMedium::medium(type, speed, 0, code);
	if (medium) {
		ret = IONetworkMedium::addMedium(mediumDict, medium);
		if (ret)
			mediumTable[code] = medium;
		medium->release();
	}
	return ret;
}

//---------------------------------------------------------------------------
// Function: _phyPublishMedia
//
// Purpose:
//   Examine the PHY capabilities and advertise all supported medium types.
//
// FIXME: Non PHY medium types are not probed.

#define MBPS 1000000 

void Intel82557::_phyPublishMedia()
{
    mdi_reg_t   status;

	// Read the PHY's media capability.
	//
	_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status);

	_phyAddMediumType(kIOMediumEtherAuto,
                      0,
                      MEDIUM_TYPE_AUTO);

	if (status & MDI_STATUS_10_HD)
		_phyAddMediumType(kIOMediumEther10BaseT | kIOMediumHalfDuplex,
                          10 * MBPS,
                          MEDIUM_TYPE_10_HD);

	if (status & MDI_STATUS_10_FD)
		_phyAddMediumType(kIOMediumEther10BaseT | kIOMediumFullDuplex,
                          10 * MBPS,
                          MEDIUM_TYPE_10_FD);

	if (status & MDI_STATUS_TX_HD)
		_phyAddMediumType(kIOMediumEther100BaseTX | kIOMediumHalfDuplex,
                          100 * MBPS,
                          MEDIUM_TYPE_TX_HD);

	if (status & MDI_STATUS_TX_FD)
		_phyAddMediumType(kIOMediumEther100BaseTX | kIOMediumFullDuplex,
                          100 * MBPS,
                          MEDIUM_TYPE_TX_FD);

	if (status & MDI_STATUS_T4)
		_phyAddMediumType(kIOMediumEther100BaseT4,
                          100 * MBPS,
                          MEDIUM_TYPE_T4);
}

//---------------------------------------------------------------------------
// Function: _phyReset
//
// Purpose:
//   Reset the PHY.

#define PHY_RESET_TIMEOUT		100		// ms
#define PHY_RESET_DELAY			10		// ms
#define PHY_POST_RESET_DELAY	300		// us

bool Intel82557::_phyReset()
{
	int 		i = PHY_RESET_TIMEOUT;
	mdi_reg_t	control;

	if (!_mdiReadPHY(phyAddr, MDI_REG_CONTROL, &control))
		return false;
	
	// Set the reset bit in the PHY Control register
	//
	_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control | MDI_CONTROL_RESET);
	
	// Wait till reset process is complete (MDI_CONTROL_RESET returns to zero)
	//
	while (i > 0) {
		if (!_mdiReadPHY(phyAddr, MDI_REG_CONTROL, &control))
			return false;
		if ((control & MDI_CONTROL_RESET) == 0) {
			IODelay(PHY_POST_RESET_DELAY);
			return true;
		}
		IOSleep(PHY_RESET_DELAY);
		i -= PHY_RESET_DELAY;
	}
	return false;
}

//---------------------------------------------------------------------------
// Function: _phyWaitAutoNegotiation
//
// Purpose:
//   Wait until auto-negotiation is complete.

#define PHY_NWAY_TIMEOUT				5000	// ms
#define PHY_NWAY_DELAY					20		// ms

bool Intel82557::_phyWaitAutoNegotiation()
{
	int 		i = PHY_NWAY_TIMEOUT;
	mdi_reg_t	status;
	
	while (i > 0) {
		if (!_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status))
			return false;
		
		if (status & MDI_STATUS_AUTONEG_COMPLETE)
			return true;
		
		IOSleep(PHY_NWAY_DELAY);
		i -= PHY_NWAY_DELAY;
	}
	return false;
}

//---------------------------------------------------------------------------
// Function: _phyProbe
//
// Purpose:
//   Find out which PHY is active.
//
#define AUTONEGOTIATE_TIMEOUT	35

bool Intel82557::_phyProbe()
{
    bool		foundPhy1 = false;
    mdi_reg_t	control;
    mdi_reg_t	status;

    if (phyAddr == PHY_ADDRESS_I82503) {
		VPRINT("%s: overriding to use Intel 82503", getName());
		return true;
    }

    if (phyAddr > 0 && phyAddr < PHY_ADDRESS_MAX) {
		VPRINT("%s: looking for Phy 1 at address %d\n", getName(), phyAddr);
		_mdiReadPHY(phyAddr, MDI_REG_CONTROL, &control);
		_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status);	// do it twice
		_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status);
		if (control == 0xffff || (status == 0 && control == 0)) 
		{
	    	VPRINT("%s: Phy 1 at address %d does not exist\n", getName(),
		   		phyAddr);
		}
		else {
	    	VPRINT("%s: Phy 1 at address %d exists\n", getName(), phyAddr);
	    	foundPhy1 = true;
			if (status & MDI_STATUS_LINK_STATUS) {
				VPRINT("%s: found Phy 1 at address %d with link\n", 
					getName(), phyAddr);
				return true;	// use PHY1
	    	}
		}
    }

	// PHY1 does not exist, or it does not have valid link.
	// Try PHY0 at address 0.
	//
	_mdiReadPHY(PHY_ADDRESS_0, MDI_REG_CONTROL, &control);
	_mdiReadPHY(PHY_ADDRESS_0, MDI_REG_STATUS, &status);

    if (control == 0xffff || (status == 0 && control == 0)) {
		if (phyAddr == 0) { /* if address forced to 0, then fail */
			IOLog("%s: phy0 not detected\n", getName());
			return false;
		}
		if (foundPhy1 == true) {
			VPRINT("%s: no Phy at address 0, using Phy 1 without link\n", 
				getName());
			return true;	// use PHY1 without a valid link
		}
		VPRINT("%s: no Phy at address 0, defaulting to 82503\n", getName());
		phyAddr = PHY_ADDRESS_I82503;
		return true;
    }

	// must isolate PHY1 electrically before using PHY0.
	//
    if (foundPhy1 == true) {
		control = MDI_CONTROL_ISOLATE;
		_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control);
		IOSleep(1);
    }

	// Enable and restart auto-negotiation on PHY0.
	//
    VPRINT("%s: starting auto-negotiation on Phy 0", getName());
    control = MDI_CONTROL_AUTONEG_ENABLE;
	_mdiWritePHY(PHY_ADDRESS_0, MDI_REG_CONTROL, control);
    IOSleep(1);
    control |= MDI_CONTROL_RESTART_AUTONEG;
	_mdiWritePHY(PHY_ADDRESS_0, MDI_REG_CONTROL, control);

	for (int i = 0; i < AUTONEGOTIATE_TIMEOUT; i++) {
		_mdiReadPHY(PHY_ADDRESS_0, MDI_REG_STATUS, &status);
	    if (status & MDI_STATUS_AUTONEG_COMPLETE)
			break;
	    IOSleep(100);
    }
	_mdiReadPHY(PHY_ADDRESS_0, MDI_REG_STATUS, &status);
	_mdiReadPHY(PHY_ADDRESS_0, MDI_REG_STATUS, &status);
	_mdiReadPHY(PHY_ADDRESS_0, MDI_REG_STATUS, &status);
    if ((status & MDI_STATUS_LINK_STATUS) || foundPhy1 == false) {
		VPRINT("%s: using Phy 0 at address 0\n", getName());
		phyAddr = 0;
		return true;
    }

	// Isolate PHY0.
	//
    VPRINT("%s: using Phy 1 without link\n", getName());
    control = MDI_CONTROL_ISOLATE;
	_mdiWritePHY(PHY_ADDRESS_0, MDI_REG_CONTROL, control);
    IOSleep(1);

	// Enable and restart auto-negotiation on PHY1.
	//
    control = MDI_CONTROL_AUTONEG_ENABLE;
	_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control);
    IOSleep(1);
    control |= MDI_CONTROL_RESTART_AUTONEG;
	_mdiWritePHY(phyAddr, MDI_REG_CONTROL, control);

	phyID = _phyGetID();
    VPRINT("%s: PHY model id is 0x%08lx\n", getName(), phyID);
    phyID &= PHY_MODEL_MASK;

    return true;
}

//---------------------------------------------------------------------------
// Function: _phyGetMediumTypeFromBits
//
// Purpose:
//   Return the medium type that correspond to the given specifiers.

mediumType_t Intel82557::_phyGetMediumTypeFromBits(bool rate100,
                                                   bool fullDuplex,
                                                   bool t4)
{
	mediumType_t  mediumType;

	if (t4) {
		mediumType = MEDIUM_TYPE_T4;
	}
	else if (rate100) {
		if (fullDuplex)
			mediumType = MEDIUM_TYPE_TX_FD;
		else
			mediumType = MEDIUM_TYPE_TX_HD;
	}
	else {
		if (fullDuplex)
			mediumType = MEDIUM_TYPE_10_FD;
		else
			mediumType = MEDIUM_TYPE_10_HD;
	}
	
	return mediumType;
}

//---------------------------------------------------------------------------
// Function: _phyGetLinkStatus
//
// Purpose:
//   Return the current link status. If the link is active, and the
//   currently selected medium type (selectedMedium) is MEDIUM_TYPE_AUTO,
//   then probe the PHY for the current active medium type.

bool Intel82557::_phyGetLinkStatus(bool *         linkActive,
                                   mediumType_t * activeMedium,
                                   mediumType_t   selectedMedium)
{
	mdi_reg_t	reg;
	mdi_reg_t	status;

	*linkActive   = false;
	*activeMedium = MEDIUM_TYPE_INVALID;

	// If PHY was not setup properly, return immediately.
	//
	if (selectedMedium >= MEDIUM_TYPE_INVALID)
		return false;

	// Determine link status. Read PHY status register twice to clear
	// any latched link failure conditions.
	//
	_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status);
	if (!(status & MDI_STATUS_LINK_STATUS))
		_mdiReadPHY(phyAddr, MDI_REG_STATUS, &status);

    if (!(status & MDI_STATUS_LINK_STATUS))
		*linkActive = false;
	else
		*linkActive = true;

	// If the current selected medium is not AUTO, and the link is active.
	// Return the selected medium as the active medium.
	//
	if (selectedMedium != MEDIUM_TYPE_AUTO) {
		*activeMedium = selectedMedium;
		return true;        // For non-AUTO medium, we are done.
	}

	// For AUTO medium, if auto-negotiation is not complete, or a valid
	// link has not been detected, then return link status without
	// setting activeMedium.
	//
	if (!(status & MDI_STATUS_AUTONEG_COMPLETE) ||
		!(status & MDI_STATUS_LINK_STATUS)) {
		return true;        // should we restart auto-negotiation?
	}

	// i82553 has a special register for determining the speed and
	// duplex mode settings.
	//
    if ((phyID == PHY_MODEL_I82553_A_B) || (phyID == PHY_MODEL_I82553_C)) {

		_mdiReadPHY(phyAddr, I82553_REG_SCR, &reg);

		*activeMedium = _phyGetMediumTypeFromBits(reg & I82553_SCR_100,
                                                  reg & I82553_SCR_FULL_DUPLEX,
                                                  reg & I82553_SCR_T4);
		return true;
    }

	// For NSC83840, we use the 83840 specific register to determine the
	// link speed and duplex mode setting. Early 83840 devices did not
	// properly report the remote capabilities when the remote link
	// partner does not support NWay.
	//
	if (phyID == PHY_MODEL_NSC83840) {
		mdi_reg_t	exp;
		
		_mdiReadPHY(phyAddr, MDI_REG_ANEX, &exp);

		if ((exp & MDI_ANEX_LP_AUTONEGOTIABLE) == 0) {
			_mdiReadPHY(phyAddr, NSC83840_REG_PAR, &reg);
	
			*activeMedium = _phyGetMediumTypeFromBits(
                                !(reg & NSC83840_PAR_SPEED_10),
                                (reg & NSC83840_PAR_DUPLEX_STAT),
                                0);
			return true;
		}
	}

	// For generic PHY, use the standard PHY registers.
	//	
	// Use the local and remote capability words to determine the
	// current active medium.
	//		
	mdi_reg_t	lpa;
	mdi_reg_t	mya;
		
	_mdiReadPHY(phyAddr, MDI_REG_ANLP, &lpa);
	_mdiReadPHY(phyAddr, MDI_REG_ANAR, &mya);

	mya &= lpa;	// obtain common capabilities mask.

	// Observe PHY medium precedence.
	//
	if (mya & MDI_ANAR_TX_FD)
		*activeMedium = MEDIUM_TYPE_TX_FD;
	else if (mya & MDI_ANAR_T4)
		*activeMedium = MEDIUM_TYPE_T4;
	else if (mya & MDI_ANAR_TX_HD)
		*activeMedium = MEDIUM_TYPE_TX_HD;
	else if (mya & MDI_ANAR_10_FD)
		*activeMedium = MEDIUM_TYPE_10_FD;
	else
		*activeMedium = MEDIUM_TYPE_10_HD;

	return true;
}

//---------------------------------------------------------------------------
// Function: _phyGetMediumWithCode
//
// Purpose:
//   Returns the medium object associated with the given code.

IONetworkMedium * Intel82557::_phyGetMediumWithCode(UInt32 code)
{
	if (code < MEDIUM_TYPE_INVALID)
		return mediumTable[code];
	else
		return 0;
}