/* * Copyright (c) 1998-2004 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@ */ #include "3C90x.h" /************************************************************************** * * Low-level management interface methods, specific to the 3C90x. * **************************************************************************/ //--------------------------------------------------------------------------- // physicalMgmtWriteWord // // Write 'bits' number of bits to the MII management interface // starting with the most significant bit in 'word'. void Apple3Com3C90x::physicalMgmtWriteWord( UInt32 word, UInt8 bits ) { UInt16 reg; for ( int shifts = 31; (shifts >= 0) && bits; shifts--, bits-- ) { reg = kPhysicalMgmtDirWrite | ( (word & (1 << shifts)) ? kPhysicalMgmtDataMask : 0 ); setPhysicalMgmt( reg ); IODelay( kMIIDelay ); reg |= kPhysicalMgmtClkMask; setPhysicalMgmt( reg ); IODelay( kMIIDelay ); reg &= ~kPhysicalMgmtClkMask; setPhysicalMgmt( reg ); IODelay( kMIIDelay ); } } //--------------------------------------------------------------------------- // physicalMgmtReadBit // // Read one bit from the MII management interface. Also used to // tri-state the STA's MDIO pin. UInt8 Apple3Com3C90x::physicalMgmtReadBit() { UInt16 reg; reg = kPhysicalMgmtDirRead; setPhysicalMgmt( reg ); IODelay( kMIIDelay ); reg = kPhysicalMgmtDirRead | kPhysicalMgmtClkMask; setPhysicalMgmt( reg ); IODelay( kMIIDelay ); reg = kPhysicalMgmtDirRead; setPhysicalMgmt( reg ); IODelay( kMIIDelay ); reg = getPhysicalMgmt(); return ( reg & kPhysicalMgmtDataMask ) ? 1 : 0; } //--------------------------------------------------------------------------- // miiReadWord bool Apple3Com3C90x::miiReadWord( PHYAddress phyAddr, PHYRegAddr phyReg, PHYWord * phyData ) { UInt32 miiFrame; PHYWord value = 0; bool ret = true; // Write preamble. physicalMgmtWriteWord( kMIIFramePreamble, kMIIFrameSize ); // Prepare command frame. miiFrame = kMIIFrameStart | kMIIFrameOpcodeRead | SetBitField( MIIFrame, RegAddr, phyReg ) | SetBitField( MIIFrame, PhyAddr, phyAddr ); // Write ST, OP, PHYAD, REGAD in the MII command frame. physicalMgmtWriteWord( miiFrame, 14 ); // Hi-Z state // Make sure the PHY generated a zero bit after the 2nd Hi-Z bit. if ( physicalMgmtReadBit() ) ret = false; // read 16-bit data. for ( int i = 0; i < 16; i++ ) { value = physicalMgmtReadBit() | (value << 1); } if ( phyData ) *phyData = value; physicalMgmtReadBit(); // Hi-Z state return ret; } //--------------------------------------------------------------------------- // miiWriteWord void Apple3Com3C90x::miiWriteWord( PHYAddress phyAddr, PHYRegAddr phyReg, PHYWord phyData ) { UInt32 miiFrame; // Write preamble. physicalMgmtWriteWord( kMIIFramePreamble, kMIIFrameSize ); // Prepare command frame. miiFrame = kMIIFrameStart | kMIIFrameOpcodeWrite | kMIIFrameTAWrite | SetBitField( MIIFrame, RegAddr, phyReg ) | SetBitField( MIIFrame, PhyAddr, phyAddr ) | SetBitField( MIIFrame, Data, phyData ); // Write command frame. physicalMgmtWriteWord( miiFrame, kMIIFrameSize ); // Hi-Z state physicalMgmtReadBit(); } /************************************************************************** * * Generic MII/PHY management interface methods. Hardware independent but * rely on the low-level MII methods. * **************************************************************************/ //--------------------------------------------------------------------------- // phyProbe // // Return true if a PHY is detected at the given PHY address. bool Apple3Com3C90x::phyProbe( PHYAddress phy ) { if ( phy > kPHYAddrMax ) return false; return( miiReadWord( phy, kMIIRegisterStatus ) ); } //--------------------------------------------------------------------------- // phyReset // // Reset the PHY device. Returns true for a successful reset cycle. bool Apple3Com3C90x::phyReset( PHYAddress phy ) { bool success = false; PHYWord control; LOG_PHY("%s::%s\n", getName(), __FUNCTION__); if ( miiReadWord( phy, kMIIRegisterControl, &control ) == false ) return false; // Set the reset bit in the PHY Control register miiWriteWord( phy, kMIIRegisterControl, (control | kMIIControlResetMask) ); // Wait till reset is complete, kMIIControlReset bit // will auto clear to zero. for ( SInt32 timeout = kPHYResetTimeout; timeout > 0; timeout -= kPHYResetDelay ) { if ( miiReadWord( phy, kMIIRegisterControl, &control ) == false ) break; if ( (control & kMIIControlResetMask) == 0 ) { /* * Onboard implementation on Dell Optiplex GX1 needs this delay. * Without it, the first PHY register read after a reset will be * read as all zeroes. */ IOSleep(10); success = true; break; } IOSleep( kPHYResetDelay ); } return success; } //--------------------------------------------------------------------------- // phyWaitForValidLink // // Wait and return true if a valid link was detected, // false on time out. bool Apple3Com3C90x::phyWaitForValidLink( PHYAddress phy ) { PHYWord status; LOG_PHY("%s::%s\n", getName(), __FUNCTION__); for ( SInt32 timeout = kPHYLinkTimeout; timeout > 0; timeout -= kPHYLinkDelay ) { if ( miiReadWord( phy, kMIIRegisterStatus, &status ) == false ) return false; if ( status & kMIIStatusLinkValidMask ) return true; IOSleep( kPHYLinkDelay ); } return false; } //--------------------------------------------------------------------------- // phyWaitForNegotiation // // Wait and return true if Nway auto-negotiation is complete. bool Apple3Com3C90x::phyWaitForNegotiation( PHYAddress phy ) { PHYWord status; LOG_PHY("%s::%s\n", getName(), __FUNCTION__); for ( SInt32 timeout = kPHYLinkTimeout; timeout > 0; timeout -= kPHYLinkDelay ) { if ( miiReadWord( phy, kMIIRegisterStatus, &status ) == false ) return false; if ( status & kMIIStatusNegotiationCompleteMask ) return true; IOSleep( kPHYLinkDelay ); } return false; } //--------------------------------------------------------------------------- // phyGetSupportedLinks // // Return a mask of all MII links supported by the PHY. UInt32 Apple3Com3C90x::phyGetSupportedLinks( PHYAddress phy ) { PHYWord status; UInt32 mask = 0; if ( miiReadWord( phy, kMIIRegisterStatus, &status ) == true ) { mask = GetBitField( MIIStatus, Links, status ); } return mask; } //--------------------------------------------------------------------------- // phyGetBestNegotiatedLink // // Return the best mode negotiated by the station and the remote link partner. MIILink Apple3Com3C90x::phyGetBestNegotiatedLink( PHYAddress phy, MIILink phyLink, MIILink reportedLink, PHYWord * reportedStatus ) { MIILink newLink = kMIILinkNone; PHYWord status; // Read status register. miiReadWord( phy, kMIIRegisterStatus, &status ); // Minimize the amount of work if status is unchanged. if ( reportedStatus ) { if ( ( ( status ^ *reportedStatus ) & ( kMIIStatusNegotiationCompleteMask | kMIIStatusLinkValidMask ) ) == 0 ) { // LOG_PHY("%s: status unchanged: 0x%04x\n", getName(), status); return reportedLink; } *reportedStatus = status; // Update with current status. } if ( phyLink & kMIILinkNway ) { // PHY was configured with auto-negotiation enabled. if (( status & ( kMIIStatusLinkValidMask | kMIIStatusNegotiationCompleteMask )) == ( kMIIStatusLinkValidMask | kMIIStatusNegotiationCompleteMask )) { PHYWord local, remote; if ( miiReadWord( phy, kMIIRegisterAdvertisement, &local ) && miiReadWord( phy, kMIIRegisterLinkPartner, &remote ) ) { UInt32 commonLinks; LOG_PHY("%s: local links: 0x%04x\n", getName(), GetBitField( MIIAdvertisement, Links, local )); LOG_PHY("%s: remote links: 0x%04x\n", getName(), GetBitField( MIILinkPartner, Links, remote )); commonLinks = GetBitField( MIIAdvertisement, Links, local ) & GetBitField( MIILinkPartner, Links, remote ); // Ranked according to 802.3u priority. if ( commonLinks & kMIILink100TX_FD ) newLink = kMIILink100TX_FD; else if ( commonLinks & kMIILink100T4 ) newLink = kMIILink100T4; else if ( commonLinks & kMIILink100TX ) newLink = kMIILink100TX; else if ( commonLinks & kMIILink10BT_FD ) newLink = kMIILink10BT_FD; else if ( commonLinks & kMIILink10BT ) newLink = kMIILink10BT; } } } else { // Auto-negotiation not enabled. if ( status & kMIIStatusLinkValidMask ) newLink = phyLink; } // Preserve the kMIILinkNway flag. newLink |= ( phyLink & kMIILinkNway ); return newLink; } //--------------------------------------------------------------------------- // phyGetID // // Return the 32-bit PHY ID. UInt32 Apple3Com3C90x::phyGetIdentifier( PHYAddress phy ) { UInt32 id = kPHYIDInvalid; UInt16 id_hi; UInt16 id_lo; if ( miiReadWord( phy, kMIIRegisterID0, &id_hi ) && miiReadWord( phy, kMIIRegisterID1, &id_lo ) ) { id = ((id_hi << 16) & 0xffff0000) | id_lo; } LOG_PHY("%s: PHY ID = %08lx\n", getName(), id); return id; } //--------------------------------------------------------------------------- // phyForceLink // // Disable Nway and force the PHY to the given link. // Returns true on success, false otherwise. bool Apple3Com3C90x::phyForceMIILink( PHYAddress phy, MIILink link ) { PHYWord reg; UInt32 linkMask; LOG_PHY("%s::%s(%lx)\n", getName(), __FUNCTION__, link); // Refuse to handle Nway or invalid links. if ( link & kMIILinkNway ) return false; if ( ( link & kMIILinkMask ) == kMIILinkNone ) return false; // Verify that the link is supported by the PHY. linkMask = phyGetSupportedLinks( phy ); if ( ( link & linkMask ) == 0 ) { LOG_PHY("%s::%s PHY does not support link %lx\n", getName(), __FUNCTION__, link); return false; } // Reset the PHY. if ( phyReset( phy ) == false ) { LOG_PHY("%s::%s PHY reset error\n", getName(), __FUNCTION__); return false; } // Advertise a single supported link, though Nway is going to // get turned off very shortly. if ( miiReadWord( phy, kMIIRegisterAdvertisement, ® ) ) { reg &= ~( kMIIAdvertisementProtocolMask | kMIIAdvertisementLinksMask | kMIIAdvertisementPauseCapableMask ); reg |= SetBitField( MIIAdvertisement, Links, link ); reg |= SetBitField( MIIAdvertisement, Protocol, 0x01 ); if (_flowControlEnabled) reg |= SetBitField( MIIAdvertisement, PauseCapable, 0x01 ); miiWriteWord( phy, kMIIRegisterAdvertisement, reg ); phyWaitForNegotiation( phy ); } // Disable Nway auto-negotiation and set the desired link. if ( miiReadWord( phy, kMIIRegisterControl, ® ) == false ) { LOG_PHY("%s::%s CONTROL read error\n", getName(), __FUNCTION__); return false; } reg &= ~( kMIIControlSpeedSelectionMask | kMIIControlFullDuplexMask | kMIIControlIsolateMask | kMIIControlAutoNegotiationMask ); if ( link >= kMIILink100TX ) reg |= kMIIControlSpeedSelectionMask; // 100 Mb/s if ( link & ( kMIILink100TX_FD | kMIILink10BT_FD ) ) reg |= kMIIControlFullDuplexMask; // Full-Duplex // Update the PHY Control register. miiWriteWord( phy, kMIIRegisterControl, reg ); LOG_PHY("%s::%s CONTROL set to %04x\n", getName(), __FUNCTION__, reg); return true; } //--------------------------------------------------------------------------- // phyStartNegotiation // // Start Nway auto-negotiation. bool Apple3Com3C90x::phyStartNegotiation( PHYAddress phy ) { PHYWord reg; UInt32 linkMask; LOG_PHY("%s::%s\n", getName(), __FUNCTION__); // Reset PHY. if ( phyReset( phy ) == false ) { LOG_PHY("%s::%s PHY reset error\n", getName(), __FUNCTION__); return false; } // Verify that the PHY supports auto-negotiation. if ( miiReadWord( phy, kMIIRegisterStatus, ® ) == false ) { LOG_PHY("%s::%s STATUS read error\n", getName(), __FUNCTION__); return false; } if ( ( reg & kMIIStatusNegotiationAbilityMask ) == 0 ) { LOG_PHY("%s::%s no Nway support\n", getName(), __FUNCTION__); return false; } // Get local PHY capabilities. linkMask = phyGetSupportedLinks( phy ); if ( linkMask == 0 ) { LOG_PHY("%s::%s zero link support\n", getName(), __FUNCTION__); return false; } // Make sure the PHY is advertising all the available links. if ( miiReadWord( phy, kMIIRegisterAdvertisement, ® ) == false ) { LOG_PHY("%s::%s: ANAR read error\n", getName(), __FUNCTION__); return false; } reg &= ~( kMIIAdvertisementProtocolMask | kMIIAdvertisementLinksMask | kMIIAdvertisementPauseCapableMask ); reg |= SetBitField( MIIAdvertisement, Links, linkMask ); reg |= SetBitField( MIIAdvertisement, Protocol, 0x01 ); if (_flowControlEnabled) reg |= SetBitField( MIIAdvertisement, PauseCapable, 0x01 ); miiWriteWord( phy, kMIIRegisterAdvertisement, reg ); // Set MII CONTROL to auto-negotiate and restart negotiation. if ( miiReadWord( phy, kMIIRegisterControl, ® ) == false ) { LOG_PHY("%s::%s CONTROL read error\n", getName(), __FUNCTION__); return false; } reg |= ( kMIIControlAutoNegotiationMask | kMIIControlRestartNegotiationMask ); miiWriteWord( phy, kMIIRegisterControl, reg ); return true; }