xbox-kernel/private/ntos/xnet/phy/phy.c
2020-09-30 17:17:25 +02:00

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