/*++

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

