548 lines
12 KiB
C
548 lines
12 KiB
C
/*++
|
|
|
|
Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
Module Name:
|
|
|
|
phy.c
|
|
|
|
Abstract:
|
|
|
|
Ethernet transceiver code inside the ROM
|
|
|
|
Revision History:
|
|
|
|
04//5/2001 davidx
|
|
Created it.
|
|
|
|
--*/
|
|
|
|
#include "precomp.h"
|
|
#include "phy.h"
|
|
#include "nettypes.h"
|
|
#include "netutil.h"
|
|
#include "ethernet.h"
|
|
#ifdef SILVER
|
|
#include "i82558.h"
|
|
#else
|
|
#include "xnic.h"
|
|
#endif
|
|
|
|
//
|
|
// Global variables
|
|
// NOTE: The init count persists across quick reboots.
|
|
//
|
|
DECLSPEC_STICKY DWORD PhyInitFlag;
|
|
DWORD PhyLinkState;
|
|
LONG PhyLockFlag;
|
|
|
|
#define PhyLock() InterlockedCompareExchange(&PhyLockFlag, 1, 0)
|
|
#define PhyUnlock() (PhyLockFlag = 0)
|
|
|
|
//
|
|
// Macro for spewing debug message
|
|
//
|
|
#if DBG
|
|
BOOL PhyVerboseMode = TRUE;
|
|
#define WARNING_ DbgPrint
|
|
#define VERBOSE_ !PhyVerboseMode ? (void)0 : (void)DbgPrint
|
|
#else !DBG
|
|
#define WARNING_ 1 ? (void)0 : (void)
|
|
#define VERBOSE_ 1 ? (void)0 : (void)
|
|
#endif
|
|
|
|
//
|
|
// Don't declaration private functions as static
|
|
//
|
|
#ifdef PRIVATE
|
|
#undef PRIVATE
|
|
#define PRIVATE
|
|
#endif
|
|
|
|
PRIVATE BOOL PhyWriteReg(PNIC_CSR csr, DWORD phyreg, DWORD val);
|
|
PRIVATE BOOL PhyReadReg(PNIC_CSR csr, DWORD phyreg, DWORD* val);
|
|
|
|
PRIVATE BOOL
|
|
PhyUpdateLinkState(
|
|
PNIC_CSR csr
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Update PHY link state information
|
|
(read the information from the PHY registers)
|
|
|
|
Arguments:
|
|
|
|
csr - Points to the NIC registers
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD anar, lpanar, miiStatus, state = 0;
|
|
|
|
if (!PhyReadReg(csr, MIIREG_ANAR, &anar) ||
|
|
!PhyReadReg(csr, MIIREG_LPANAR, &lpanar) ||
|
|
!PhyReadReg(csr, MIIREG_STATUS, &miiStatus))
|
|
return FALSE;
|
|
|
|
anar &= lpanar;
|
|
if (anar & (MII4_100BASE_T_FULL_DUPLEX | MII4_100BASE_T_HALF_DUPLEX))
|
|
state |= XNET_LINK_100MBPS;
|
|
else if (anar & (MII4_10BASE_T_FULL_DUPLEX | MII4_10BASE_T_HALF_DUPLEX))
|
|
state |= XNET_LINK_10MBPS;
|
|
|
|
if (anar & (MII4_10BASE_T_FULL_DUPLEX | MII4_100BASE_T_FULL_DUPLEX))
|
|
state |= XNET_LINK_FULL_DUPLEX;
|
|
else if (anar & (MII4_10BASE_T_HALF_DUPLEX | MII4_100BASE_T_HALF_DUPLEX))
|
|
state |= XNET_LINK_HALF_DUPLEX;
|
|
|
|
if (miiStatus & MIISTATUS_LINK_IS_UP)
|
|
state |= XNET_LINK_IS_UP;
|
|
|
|
PhyLinkState = state;
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//
|
|
// Wait for up to 500ms until the link to be up.
|
|
//
|
|
INLINE DWORD PhyWaitForLinkUp(PNIC_CSR csr) {
|
|
DWORD miiStatus = 0;
|
|
INT timeout = 1000;
|
|
while (timeout-- && !(miiStatus & MIISTATUS_LINK_IS_UP)) {
|
|
KeStallExecutionProcessor(500);
|
|
if (!PhyReadReg(csr, MIIREG_STATUS, &miiStatus)) break;
|
|
}
|
|
return miiStatus;
|
|
}
|
|
|
|
|
|
#ifndef SILVER
|
|
|
|
// Clear MDIOADR_LOCK bit
|
|
PRIVATE VOID PhyClearMDIOLOCK(PNIC_CSR csr)
|
|
{
|
|
INT timeout;
|
|
|
|
csr->mdio_adr = MDIOADR_LOCK;
|
|
WARNING_("PHY: MDIOADR_LOCK is set\n");
|
|
|
|
timeout = PHYRW_TIMEOUT;
|
|
do {
|
|
KeStallExecutionProcessor(50);
|
|
timeout -= 50;
|
|
} while (timeout > 0 && (csr->mdio_adr & MDIOADR_LOCK));
|
|
}
|
|
|
|
|
|
PRIVATE BOOL
|
|
PhyReadReg(
|
|
PNIC_CSR csr,
|
|
DWORD phyreg,
|
|
DWORD* val
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the value of a PHY register
|
|
|
|
Arguments:
|
|
|
|
csr - Points to the NIC registers
|
|
phyreg - Specifies the PHY register to be read
|
|
val - Return the PHY register value
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD mdioadr;
|
|
INT timeout;
|
|
|
|
// The lock bit shouldn't be set.
|
|
// Clear it just in case it's stuck.
|
|
|
|
if (csr->mdio_adr & MDIOADR_LOCK) {
|
|
PhyClearMDIOLOCK(csr);
|
|
}
|
|
|
|
// Write the PHY register address
|
|
|
|
mdioadr = (PHY_ADDR << MDIOADR_PHYSHIFT) | (phyreg << MDIOADR_REGSHIFT);
|
|
csr->mdio_adr = mdioadr;
|
|
|
|
mdioadr |= MDIOADR_LOCK;
|
|
for (timeout=PHYRW_TIMEOUT; timeout > 0 && (mdioadr & MDIOADR_LOCK); timeout -= 50) {
|
|
KeStallExecutionProcessor(50);
|
|
mdioadr = csr->mdio_adr;
|
|
}
|
|
|
|
// Read the PHY register value
|
|
*val = csr->mdio_data;
|
|
|
|
if (mdioadr & MDIOADR_LOCK) {
|
|
WARNING_("PHY read failed: reg %d.\n", phyreg);
|
|
ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
PRIVATE BOOL
|
|
PhyWriteReg(
|
|
PNIC_CSR csr,
|
|
DWORD phyreg,
|
|
DWORD val
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Write the specified value to a PHY register
|
|
|
|
Arguments:
|
|
|
|
csr - Points to the NIC registers
|
|
phyreg - Specifies the PHY register to be written
|
|
val - Specifies the value for the PHY register
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD mdioadr;
|
|
INT timeout;
|
|
|
|
// The lock bit shouldn't be set.
|
|
// Clear it just in case it's stuck.
|
|
|
|
if (csr->mdio_adr & MDIOADR_LOCK) {
|
|
PhyClearMDIOLOCK(csr);
|
|
}
|
|
|
|
// Write the data first
|
|
|
|
csr->mdio_data = val;
|
|
|
|
// Write the PHY register address
|
|
|
|
mdioadr = (PHY_ADDR << MDIOADR_PHYSHIFT) | (phyreg << MDIOADR_REGSHIFT) | MDIOADR_WRITE;
|
|
csr->mdio_adr = mdioadr;
|
|
|
|
mdioadr |= MDIOADR_LOCK;
|
|
for (timeout=PHYRW_TIMEOUT; timeout > 0 && (mdioadr & MDIOADR_LOCK); timeout -= 50) {
|
|
KeStallExecutionProcessor(50);
|
|
mdioadr = csr->mdio_adr;
|
|
}
|
|
|
|
if (mdioadr & MDIOADR_LOCK) {
|
|
WARNING_("PHY write failed: reg %d.\n", phyreg);
|
|
ASSERT(FALSE);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
NTSTATUS
|
|
PhyInitialize(
|
|
BOOL forceReset,
|
|
VOID* param OPTIONAL
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Initialize the Ethernet PHY interface
|
|
|
|
Arguments:
|
|
|
|
forceReset - Whether to force a PHY reset
|
|
param -optional parameters
|
|
|
|
Return Value:
|
|
|
|
Status code
|
|
|
|
--*/
|
|
|
|
{
|
|
PNIC_CSR csr = NicCsr;
|
|
DWORD miiControl, miiStatus;
|
|
INT timeout;
|
|
NTSTATUS status = NETERR_HARDWARE;
|
|
|
|
if (PhyLock() != 0)
|
|
return NETERR(ERROR_BUSY);
|
|
|
|
if (forceReset) {
|
|
PhyInitFlag = 0;
|
|
PhyLinkState = 0;
|
|
|
|
//
|
|
// Force the PHY to reset
|
|
//
|
|
miiControl = MIICONTROL_RESET;
|
|
if (!PhyWriteReg(csr, MIIREG_CONTROL, miiControl)) goto err;
|
|
|
|
// Wait for up to 500ms
|
|
timeout = 1000;
|
|
while (timeout-- && (miiControl & MIICONTROL_RESET)) {
|
|
KeStallExecutionProcessor(500);
|
|
if (!PhyReadReg(csr, MIIREG_CONTROL, &miiControl)) goto err;
|
|
}
|
|
|
|
// If the reset is still asserted, return error
|
|
if (miiControl & MIICONTROL_RESET) goto err;
|
|
} else if (PhyInitFlag) {
|
|
//
|
|
// If PHY is already initialized, just update the link state
|
|
//
|
|
PhyUpdateLinkState(csr);
|
|
status = NETERR_OK;
|
|
goto exit;
|
|
}
|
|
|
|
// The auto-negotiation should be started by now.
|
|
// Wait for a max of 3 seconds for it to complete.
|
|
timeout = 6000;
|
|
miiStatus = 0;
|
|
while (timeout-- && !(miiStatus & MIISTATUS_AUTO_NEGOTIATION_COMPLETE)) {
|
|
KeStallExecutionProcessor(500);
|
|
if (!PhyReadReg(csr, MIIREG_STATUS, &miiStatus)) goto err;
|
|
}
|
|
|
|
// NOTE: Workaround for ICS PHY problems with some 10base-t hubs
|
|
// e.g. Garret Communications Magnum Personal Hub H50
|
|
if (XboxHardwareInfo.McpRevision != 0xa1) {
|
|
DWORD icshack;
|
|
// clear bit 8 of undocumented register 0x18
|
|
if (PhyReadReg(csr, 0x18, &icshack)) {
|
|
icshack &= ~0x0100;
|
|
PhyWriteReg(csr, 0x18, icshack);
|
|
}
|
|
}
|
|
|
|
//
|
|
// Use auto-negotiation
|
|
//
|
|
if (!PhyReadReg(csr, MIIREG_CONTROL, &miiControl)) goto err;
|
|
|
|
if (miiControl & MIICONTROL_RESTART_AUTO_NEGOTIATION) {
|
|
// If the restart-auto-negotiation bit is set,
|
|
// default to the highest available speed in half-duplex mode.
|
|
WARNING_("Auto-negotiation didn't succeed.\n");
|
|
|
|
if (miiStatus & (MIISTATUS_100MBS_T4_CAPABLE |
|
|
MIISTATUS_100MBS_X_HALF_DUPLEX_CAPABLE |
|
|
MIISTATUS_100MBS_T2_HALF_DUPLEX_CAPABLE)) {
|
|
// We can do 100Mbps
|
|
miiControl |= MIICONTROL_SPEED_SELECTION_BIT1;
|
|
miiControl &= ~MIICONTROL_SPEED_SELECTION_BIT0;
|
|
PhyLinkState |= XNET_LINK_100MBPS;
|
|
} else if (miiStatus & MIISTATUS_10MBS_HALF_DUPLEX_CAPABLE) {
|
|
// We can do 10Mbps
|
|
miiControl &= ~MIICONTROL_SPEED_SELECTION_BIT1;
|
|
miiControl |= MIICONTROL_SPEED_SELECTION_BIT0;
|
|
PhyLinkState |= XNET_LINK_10MBPS;
|
|
} else
|
|
goto err;
|
|
|
|
PhyLinkState |= XNET_LINK_HALF_DUPLEX;
|
|
|
|
// Set the desired speed if the auto-negotiation never completed
|
|
PhyWriteReg(csr, MIIREG_CONTROL, miiControl);
|
|
|
|
miiStatus = PhyWaitForLinkUp(csr);
|
|
if (miiStatus & MIISTATUS_LINK_IS_UP)
|
|
PhyLinkState |= XNET_LINK_IS_UP;
|
|
} else {
|
|
// Auto-negotiation worked.
|
|
PhyWaitForLinkUp(csr);
|
|
if (!PhyUpdateLinkState(csr)) goto err;
|
|
}
|
|
|
|
PhyInitFlag = 1;
|
|
status = NETERR_OK;
|
|
|
|
exit:
|
|
PhyUnlock();
|
|
return status;
|
|
|
|
err:
|
|
WARNING_("Ethernet PHY initialization failed.\n");
|
|
goto exit;
|
|
}
|
|
|
|
|
|
//
|
|
// Public function for retrieving link state information
|
|
//
|
|
DWORD PhyGetLinkState(BOOL update)
|
|
{
|
|
if ((!PhyLinkState || update) && PhyLock() == 0) {
|
|
PhyUpdateLinkState(NicCsr);
|
|
PhyUnlock();
|
|
}
|
|
return PhyLinkState;
|
|
}
|
|
|
|
#else // !SILVER
|
|
|
|
PRIVATE BOOL
|
|
PhyReadReg(
|
|
PNIC_CSR csr,
|
|
DWORD phyreg,
|
|
DWORD* val
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Read the value of a PHY register
|
|
|
|
Arguments:
|
|
|
|
csr - Points to the NIC registers
|
|
phyreg - Specifies the PHY register to be read
|
|
val - Return the PHY register value
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD mdictrl;
|
|
INT timeout;
|
|
|
|
if (!(csr->mdiCtrl & MDI_READY)) {
|
|
WARNING_("PHY isn't ready.\n");
|
|
}
|
|
|
|
// Initiate the PHY read
|
|
mdictrl = MDI_PHY_REG_ADDR(phyreg) | MDI_PHY_ADDR(1) | MDIOP_READ;
|
|
csr->mdiCtrl = mdictrl;
|
|
|
|
// Wait for max 10msecs
|
|
for (timeout = 2000; timeout > 0 && !(mdictrl & MDI_READY); timeout -= 50) {
|
|
KeStallExecutionProcessor(50);
|
|
mdictrl = csr->mdiCtrl;
|
|
}
|
|
|
|
if (mdictrl & MDI_READY) {
|
|
*val = (mdictrl & 0xffff);
|
|
return TRUE;
|
|
}
|
|
|
|
WARNING_("PHY read failed: reg %d.\n", phyreg);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
PRIVATE BOOL
|
|
PhyWriteReg(
|
|
PNIC_CSR csr,
|
|
DWORD phyreg,
|
|
DWORD val
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
Write the specified value to a PHY register
|
|
|
|
Arguments:
|
|
|
|
csr - Points to the NIC registers
|
|
phyreg - Specifies the PHY register to be written
|
|
val - Specifies the value for the PHY register
|
|
|
|
Return Value:
|
|
|
|
TRUE if successful, FALSE if there is an error
|
|
|
|
--*/
|
|
|
|
{
|
|
DWORD mdictrl;
|
|
INT timeout;
|
|
|
|
if (!(csr->mdiCtrl & MDI_READY)) {
|
|
WARNING_("PHY isn't ready.\n");
|
|
}
|
|
|
|
// Initiate the PHY write
|
|
mdictrl = MDI_PHY_REG_ADDR(phyreg) | MDI_PHY_ADDR(1) | MDIOP_WRITE | val;
|
|
csr->mdiCtrl = mdictrl;
|
|
|
|
// Wait for max 10msecs
|
|
for (timeout = 2000; timeout > 0 && !(mdictrl & MDI_READY); timeout -= 50) {
|
|
KeStallExecutionProcessor(50);
|
|
mdictrl = csr->mdiCtrl;
|
|
}
|
|
|
|
if (mdictrl & MDI_READY)
|
|
return TRUE;
|
|
|
|
WARNING_("PHY write failed: reg %d.\n", phyreg);
|
|
return FALSE;
|
|
}
|
|
|
|
//
|
|
// NOTE: PHY related functions are not fully implemented on the silver xdk box.
|
|
// It's going away anyhow.
|
|
//
|
|
DWORD PhyGetLinkState(BOOL update)
|
|
{
|
|
return PhyLinkState;
|
|
}
|
|
|
|
NTSTATUS PhyInitialize(BOOL forceReset, VOID* param)
|
|
{
|
|
PNIC_CSR csr;
|
|
|
|
if (PhyLock() != 0)
|
|
return NETERR(ERROR_BUSY);
|
|
|
|
// On silver xdk box, param points to the NIC CSR register space
|
|
ASSERT(param != NULL && !forceReset);
|
|
csr = (PNIC_CSR) param;
|
|
|
|
// Just wait for up to half a second until the link is up
|
|
// and then update the link status
|
|
PhyWaitForLinkUp(csr);
|
|
PhyUpdateLinkState(csr);
|
|
|
|
PhyInitFlag = 1;
|
|
PhyUnlock();
|
|
return NETERR_OK;
|
|
}
|
|
|
|
#endif // !SILVER
|
|
|