Source to iokit/Drivers/network/drvIntel82557/i82557PHY.cpp
/*
* 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, ®);
/*
* 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, ®);
*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, ®);
*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;
}