/*****************************************************************************

INTEL CONFIDENTIAL
Copyright 2007,2008,2009 Intel Corporation All Rights Reserved.

The source code contained or described herein and all documents related to the
source code ("Material") are owned by Intel Corporation or its suppliers or
licensors. Title to the Material remains with Intel Corporation or its
suppliers and licensors. The Material may contain trade secrets and proprietary
and confidential information of Intel Corporation and its suppliers and
licensors, and is protected by worldwide copyright and trade secret laws and
treaty provisions. No part of the Material may be used, copied, reproduced,
modified, published, uploaded, posted, transmitted, distributed, or disclosed
in any way without Intels prior express written permission.
No license under any patent, copyright, trade secret or other intellectual
property right is granted to or conferred upon you by disclosure or delivery
of the Materials, either expressly, by implication, inducement, estoppel or
otherwise. Any license under such intellectual property rights must be
express and approved by Intel in writing.

Include any supplier copyright notices as supplier requires Intel to use.
Include supplier trademarks or logos as supplier requires Intel to use,
preceded by an asterisk.
An asterisked footnote can be added as follows: 
  *Third Party trademarks are the property of their respective owners.

Unless otherwise agreed by Intel in writing, you may not remove or alter this
notice or any other notice embedded in Materials by Intel or Intels suppliers
or licensors in any way.

 version: Embedded.X.1.0.3-127

  Contact Information:

  Intel Corporation, 5000 W Chandler Blvd, Chandler, AZ 85226 

*****************************************************************************/
/*****************************************************************************
 * @ingroup OEM_PHY_GENERAL
 *
 * @file phyoem.c
 *
 * @description
 *   This file contains oem PHY specific functions 
 *****************************************************************************/

#include "phyEx.h"
#include "..\public\public.h"
#include "phyoem.h"

#include "M88E1118.h" /* Marvel PHY 1118 */

/******************************************************************************
 * @description
 * This function registers OEM implemented callback functions. These functions 
 * would be called by iegb.sys driver. Some for the functions are optional 
 * and need not be implemented, in this case callback function pointer needs to
 * be set to NULL.
 *
 * @param phyif - structure defines standard driver interface
 *
 * @return - void
 *
 * @notes - runs at IRQL <= IRQL_APC_LEVEL
 *****************************************************************************/
void 
OemRegisterCallback(IN PPHYDEV_INTERFACE_V1 phyif)
{
    DBGPRINT(("OemRegisterCallback\n"));

    phyif->OemDbgPrint              = /* optional */
                                      OemDbgPrint;
    phyif->OemPhyIsMediaCopper      = OemPhyIsMediaCopper;
    phyif->OemPhyCheckPolarity      = OemPhyCheckPolarity;
    phyif->OemPhyIsAutoNegEnabled   = OemPhyIsAutoNegEnabled;
    phyif->OemPhyIsSpeedDuplex      = OemPhyIsSpeedDuplex;
    phyif->OemPhyIsSpeed1000        = OemPhyIsSpeed1000;
    phyif->OemPhyIsSpeed100         = OemPhyIsSpeed100;
    phyif->OemPhyForceSpeedDuplex   = OemPhyForceSpeedDuplex;
    phyif->OemPhyGetRxErrorCnt      = OemPhyGetRxErrorCnt;
    phyif->OemPhyLinkSetup          = OemPhyLinkSetup;
    phyif->OemPhyNeedsResetWithMac  = /* optional */
                                      OemPhyNeedsResetWithMac;
    phyif->OemPhyHwReset            = OemPhyHwReset;
    phyif->OemPhyIsLinkUp           = OemPhyIsLinkUp;
    phyif->OemPhyCheckDownshift     = /* optional */
                                      OemPhyCheckDownshift;
    phyif->OemPhyGetCableLength     = /* optional */
                                      OemPhyGetCableLength;
    return;
}

/******************************************************************************
 * @description
 *   Optional function, prints debug messages which can be viewed from Windows
 *   kernel debugger. iegb.sys driver calls this function to print critical 
 *   messages which can be used for debugging this PHY driver.
 *
 * @param msg - NULL terminated character message
 *
 * @return - void
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
void 
OemDbgPrint(IN PCCH msg,...)
{
#undef TEMP_BUFFER_SIZE
#define TEMP_BUFFER_SIZE        1024

    va_list    list;
    UCHAR      debugMessageBuffer[TEMP_BUFFER_SIZE];
    NTSTATUS status;
    
    va_start(list, msg);
    
    if( msg ) {
        status = RtlStringCbVPrintfA(
                    debugMessageBuffer, sizeof(debugMessageBuffer), 
                    msg, list);
        if(!NT_SUCCESS(status)) {
            return;
        }
        DbgPrint(debugMessageBuffer);
    }

    va_end(list);

    return;
}
 
/******************************************************************************
 * @description
 *  This function will perform a software-initiated reset of the PHY. Some 
 *  PHYs require the PHY to be reset in order for PHY register values to 
 *  be updated after changes. This is true for Marvell PHYs. When the PHY 
 *  is reset, values that are strapped return to default values. Consult 
 *  OEM PHY datasheet to determine whether PHY reset is required for updates 
 *  to registers.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - NTSTATUS - STATUS_SUCCESS if successful
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS 
OemPhyHwReset(IN PVOID     callerCtx, 
            IN ULONG            phyId,
            IN PIEGB_PHY_RD     iegbPhyRead,
            IN PIEGB_PHY_WR     iegbPhyWrite
            )
{
    NTSTATUS    status;
    USHORT      phy_data;

#define PHY_CTRL         0x00   /* Control Register */
#define MII_CR_RESET     0x8000 /* 0 = normal, 1 = PHY reset */

    DBGPRINT(("OemPhyHwReset\n"));

    status = iegbPhyRead(callerCtx, PHY_CTRL, &phy_data);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    DBGPRINT(("Phy Data %X\n", phy_data));

    phy_data |= MII_CR_RESET;
    status = iegbPhyWrite(callerCtx, PHY_CTRL, phy_data);
    if (!NT_SUCCESS(status)) {
        return status;
    }

    /*
     * micro sec delay
     */
    KeStallExecutionProcessor(1);

    return status;
}

/*
 * For function description pl. see OemPhyLinkSetup() function
 */
static NTSTATUS
OemPhyM88E1118LinkSetup(IN PVOID     callerCtx, 
            IN ULONG            phyId,
            IN PIEGB_PHY_RD     iegbPhyRead,
            IN PIEGB_PHY_WR     iegbPhyWrite,
            IN MDI_MODE         mdiMode,
            IN ULONG            polarityReversalDisable
            )
{
    NTSTATUS        status;
    USHORT          phy_data;

    DBGPRINT(("OemPhyM88E1118LinkSetup\n"));

    /* set page address to zero */
    iegbPhyWrite(callerCtx, M88E1118_PAGE_ADDRESS, 0);

    status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_CTRL, &phy_data);
    if (!NT_SUCCESS(status))
        return status;

    /* Options:
     *   MDI/MDI-X = 0 (default)
     *   0 - Auto for all speeds
     *   1 - MDI mode
     *   2 - MDI-X mode
     *   3 - Auto for 1000Base-T only (MDI-X for 10/100Base-T modes)
     */
    phy_data &= ~M88E1118_PHY_SPEC_CTRL_MDI_X_MODE_MASK;
    switch (mdiMode) {
    case 1:
        phy_data |= M88E1118_PHY_SPEC_CTRL_MDI_X_MAN_MDI_CONFIG;
        break;
    case 2:
        phy_data |= M88E1118_PHY_SPEC_CTRL_MDI_X_MAN_MDIX_CONFIG;
        break;
    case 3:
        phy_data |= M88E1118_PHY_SPEC_CTRL_MDI_X_AUTO_CONFIG;
        break;
    case 0:
    default:
        phy_data |= M88E1118_PHY_SPEC_CTRL_MDI_X_AUTO_CONFIG;
        break;
    }

    /* Options:
     *   disable_polarity_correction = 0 (default)
     *       Automatic Correction for Reversed Cable Polarity
     *   0 - Disabled
     *   1 - Enabled
     */
    phy_data &= ~M88E1118_PHY_SPEC_CTRL_POL_REV_DISABLE;
    if (polarityReversalDisable == 0)
        phy_data |= M88E1118_PHY_SPEC_CTRL_POL_REV_DISABLE;
    status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_CTRL, &phy_data);
    if (!NT_SUCCESS(status))
        return status;

    /* SW Reset the PHY so all changes take effect */
    status = OemPhyHwReset(callerCtx,
                        phyId,
                        iegbPhyRead,
                        iegbPhyWrite
                        );
    if (!NT_SUCCESS(status))
        return status;

    return STATUS_SUCCESS;
}

/******************************************************************************
 * @description
 *   Performs OEM transceiver specific link setup
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param mdiMode - desired MDI mode
 * @param polarityReversalDisable - enable or disable polarity reversal
 *
 * @return - NTSTATUS - STATUS_SUCCESS if successful
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS
OemPhyLinkSetup(IN PVOID     callerCtx, 
            IN ULONG            phyId,
            IN PIEGB_PHY_RD     iegbPhyRead,
            IN PIEGB_PHY_WR     iegbPhyWrite,
            IN MDI_MODE         mdiMode,
            IN ULONG            polarityReversalDisable
            )
{
    DBGPRINT(("OemPhyLinkSetup\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            return OemPhyM88E1118LinkSetup(callerCtx,
                                phyId,
                                iegbPhyRead,
                                iegbPhyWrite,
                                mdiMode,
                                polarityReversalDisable);
        /*case XXX:*/
        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}

/******************************************************************************
 * @description
 *   Indicate whether phy needs reset when MAC is rest.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - TRUE if phy reset is required after MAC reset
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
BOOLEAN 
OemPhyNeedsResetWithMac( 
            IN PVOID           callerCtx,
            IN ULONG           phyId,
            IN PIEGB_PHY_RD    iegbPhyRead,
            IN PIEGB_PHY_WR    iegbPhyWrite
            )
{
    DBGPRINT(("OemPhyNeedsResetWithMac\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            return FALSE;
        /*case XXX:*/
        default:
            break;
    }

    return FALSE;
}

/******************************************************************************
 * @description
 *   Indicate current realtime link state.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param linkup - TRUE is link state is active
 *
 * @return - STATUS_SUCCESS is no errors encountered
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS 
OemPhyIsLinkUp(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite,
                OUT BOOLEAN*       linkup
                )
{
    NTSTATUS        status;
    USHORT          phy_data=0;

    DBGPRINT(("OemPhyIsLinkUp\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
          status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_STATUS,
                                   &phy_data);
          *linkup = ((phy_data & M88E1118_PSSR_LINK) > 0);
          return status;

        /*case XXX:*/
        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}

/******************************************************************************
 * @description
 *   Check media type of copper.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param copper - TRUE if media type is copper
 *
 * @return - STATUS_SUCCESS is no errors encountered
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS 
OemPhyIsMediaCopper(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite,
                OUT BOOLEAN*       copper
                )
{
    NTSTATUS        status=STATUS_SUCCESS;
    USHORT          phy_data=0;

    DBGPRINT(("OemPhyIsMediaCopper\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
          *copper = TRUE;
          return status;

        /*case XXX:*/
        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}


/******************************************************************************
 * @description
 *   This function checks the PHY to determine its polarity.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param polarity - TRUE if link polarity reversal is detected
 *
 * @return - STATUS_SUCCESS is no errors encountered
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS 
OemPhyCheckPolarity(
                    IN PVOID           callerCtx,
                    IN ULONG           phyId,
                    IN PIEGB_PHY_RD    iegbPhyRead,
                    IN PIEGB_PHY_WR    iegbPhyWrite,
                    OUT BOOLEAN*       polarity
                    )
{
    NTSTATUS        status;
    USHORT          phy_data=0;

    DBGPRINT(("OemPhyCheckPolarity\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
        status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_STATUS,
                                     &phy_data);
        *polarity = (phy_data & M88E1118_PSSR_REV_POLARITY) >>
                     M88E1118_PSSR_REV_POLARITY_SHIFT;
        return status;

        /*case XXX:*/
        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}

/*
 * For comments pl. see OemPhyGetCableLength() function
 */
NTSTATUS 
OemPhyM88E1118GetCableLength(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite,
                OUT PUSHORT        min_length,
                OUT PUSHORT        max_length
                )
{
    NTSTATUS        status;
    USHORT          cable_length;
    USHORT          i;
    USHORT          vct=0;
    LARGE_INTEGER   interval;

    DBGPRINT(("OemPhyM88E1118GetCableLength\n"));

    //
    // Ref: Marvell 88E1118 phy spec. pg. 34
    //

    /* set pg addr to 5 */
    iegbPhyWrite(callerCtx, M88E1118_PAGE_ADDRESS, 5);

    /* start test to get cable length */
    status = iegbPhyRead(callerCtx, M88E1118_ADV_VCT_CONTROL_PG5, &vct);
    if( !NT_SUCCESS(status) ) {
        goto restore_pg_addr;
    }

    vct |= M88E1118_ADV_VCT_CONTROL_PG5_ENABLE_TEST;
    status = iegbPhyWrite(callerCtx, M88E1118_ADV_VCT_CONTROL_PG5, vct);
    if( !NT_SUCCESS(status) ) {
        goto restore_pg_addr;
    }

    /* wait for test to complete; bailout if takes too long */
    for( i = 0; i < 10; i++ ) {
        interval.QuadPart = (100 * DELAY_ONE_MILLISECOND);
        KeDelayExecutionThread( KernelMode, FALSE, &interval );

        status = iegbPhyRead(callerCtx, M88E1118_ADV_VCT_CONTROL_PG5, &vct);
        if( !NT_SUCCESS(status) ) {
            goto restore_pg_addr;
        }

        if( !(vct & M88E1118_ADV_VCT_CONTROL_PG5_ENABLE_TEST) ) {
            if( vct & M88E1118_ADV_VCT_CONTROL_PG5_TEST_STATUS ) {
                i = 0;
                break; /* test complete */
            }
        }
    }
    if( i == 10 ) {
        status = STATUS_UNSUCCESSFUL;
        goto restore_pg_addr;
    }

    /* read the cable length */
    status = iegbPhyRead(callerCtx, M88E1118_ADV_VCT_TX_TO_MDI0_RX_COUPLING_PG5, &vct);
    if( !NT_SUCCESS(status) ) {
        goto restore_pg_addr;
    }

    vct &= M88E1118_ADV_VCT_TX_TO_MDI0_RX_COUPLING_PG5_DISTANCE;
    cable_length = vct;

    /* ref: pg 34 */
    if( cable_length >= 224 ) {
        *min_length = M88E1118_cable_length_160;
        *max_length = M88E1118_cable_length_180;
    }
    else if( cable_length >= 192 ) {
        *min_length = M88E1118_cable_length_140;
        *max_length = M88E1118_cable_length_160;
    }
    else if( cable_length >= 160 ) {
        *min_length = M88E1118_cable_length_120;
        *max_length = M88E1118_cable_length_140;
    }
    else if( cable_length >= 128 ) {
        *min_length = M88E1118_cable_length_100;
        *max_length = M88E1118_cable_length_120;
    }
    else if( cable_length >= 96 ) {
        *min_length = M88E1118_cable_length_80;
        *max_length = M88E1118_cable_length_100;
    }
    else if( cable_length >= 64 ) {
        *min_length = M88E1118_cable_length_60;
        *max_length = M88E1118_cable_length_80;
    }
    else if( cable_length >= 32 ) {
        *min_length = M88E1118_cable_length_40;
        *max_length = M88E1118_cable_length_60;
    }
    else if( cable_length >= 1 ) {
        *min_length = M88E1118_cable_length_20;
        *max_length = M88E1118_cable_length_40;
    }
    else {
        *min_length = M88E1118_cable_length_10;
        *max_length = M88E1118_cable_length_20;
    }

restore_pg_addr:
    /* set back the pg addr to 0 */
    status = iegbPhyWrite(callerCtx, M88E1118_PAGE_ADDRESS, 0);

    return status;
}

/******************************************************************************
 * @description
 *   Returns cable length in meters 
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param minLen - approx minimum length in meters
 * @param maxLen - approx maximum length in meters
 *
 * @return - STATUS_SUCCESS is no errors encountered
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS 
OemPhyGetCableLength(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite,
                OUT PUSHORT        minLen,
                OUT PUSHORT        maxLen
                )
{
    DBGPRINT(("OemPhyGetCableLength\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            return OemPhyM88E1118GetCableLength(
                        callerCtx,
                        phyId,
                        iegbPhyRead,
                        iegbPhyWrite,
                        minLen,
                        maxLen
                        );
        /*case XXX:*/
        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}

/******************************************************************************
 * @description
 *   Function check if auto negotiation is enabled.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - TRUE if auto negotiation is enabled
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
BOOLEAN 
OemPhyIsAutoNegEnabled(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite
                )
{
    USHORT      phy_data=0;

    DBGPRINT(("OemPhyIsAutoNegEnabled\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_CTRL, &phy_data);

            return (phy_data & M88E1118_PSSR_SPD_DPLX_RESOLVED) && 
                    (phy_data & M88E1118_PSSR_MDIX);
        default:
            break;
    }

    return TRUE; /* default */
}

/******************************************************************************
 * @description
 *   This function force the PHY to full-duplex speed mode.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - STATUS_SUCCESS is no errors encountered
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS 
OemPhyForceSpeedDuplex(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite
                )
{
    NTSTATUS        status;
    USHORT          phy_data;

    DBGPRINT(("OemPhyForceSpeedDuplex\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_CTRL, &phy_data);
            if (!NT_SUCCESS(status))
                return status;
            
            phy_data &= ~M88E1118_PHY_SPEC_CTRL_MDI_X_AUTO_CONFIG;
            status = iegbPhyWrite(callerCtx, M88E1118_PHY_SPEC_CTRL, phy_data);
            if (!NT_SUCCESS(status))
                return status;

            return status;

        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}

/******************************************************************************
 * @description
 *   This function check if PHY speed is in full-duplex speed mode.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - TRUE if speed duplex
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
BOOLEAN 
OemPhyIsSpeedDuplex(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite
                )
{
    NTSTATUS        status;
    USHORT          phy_data;
    BOOLEAN         duplex=TRUE; /* default */

    DBGPRINT(("OemPhyIsSpeedDuplex\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_STATUS, &phy_data);
            if (!NT_SUCCESS(status))
                return duplex;

            return ((phy_data & M88E1118_PSSR_DPLX)>0);

        default:
            break;
    }

    return duplex;
}

/******************************************************************************
 * @description
 *   This function checks the PHY to see if the speed is set to 1 Gbps.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - TRUE if speed 1 Gbps
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
BOOLEAN 
OemPhyIsSpeed1000(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite
                )
{
    NTSTATUS        status;
    USHORT          phy_data;
    BOOLEAN         bSpeed1000=TRUE;

    DBGPRINT(("OemPhyIsSpeed1000\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_STATUS, &phy_data);
            if (!NT_SUCCESS(status))
                return bSpeed1000;

            return ((phy_data & M88E1118_PSSR_1000MBS)>0);

        default:
            break;
    }

    return bSpeed1000;
}

/******************************************************************************
 * @description
 *   This function checks the PHY to see if the speed is set to 100 Mbps.
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 *
 * @return - TRUE if speed 100 Mbps
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
BOOLEAN
OemPhyIsSpeed100(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite
                )
{
    NTSTATUS        status;
    USHORT          phy_data;
    BOOLEAN         bSpeed100=TRUE;

    DBGPRINT(("OemPhyIsSpeed100\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            status = iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_STATUS, &phy_data);
            if (!NT_SUCCESS(status))
                return bSpeed100;

            return ((phy_data & M88E1118_PSSR_100MBS)>0);

        default:
            break;
    }

    return bSpeed100;
}


/******************************************************************************
 * @description
 *   This function returns Receive error count
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param pRxErrCnt - Receive error count
 *
 * @return - TRUE if speed 100 Mbps
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
NTSTATUS
OemPhyGetRxErrorCnt(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite,
                IN PUSHORT         pRxErrCnt
                )
{
    NTSTATUS        status;
    USHORT          phy_data;

    DBGPRINT(("OemPhyGetRxErrorCnt\n"));
    switch( phyId )
    {
        case PHY_ID_M88E1118:
            status = iegbPhyRead(callerCtx, M88E1118_RX_ERR_CNTR, pRxErrCnt);
            return status;
        default:
            break;
    }

    return STATUS_INVALID_PARAMETER;
}


/******************************************************************************
 * @description
 *   Checks the PHY to see if it is running at a speed slower than its maximum
 *
 * @param phyId - Phy ID (same as read from phy register)
 * @param iegbPhyRead - callback funtion to read phy register
 * @param iegbPhyWrite - callback funtion to write to phy register
 * @param pRxErrCnt - Receive error count
 *
 * @return - TRUE if speed 100 Mbps
 *
 * @notes - runs at IRQL == ANY
 *****************************************************************************/
BOOLEAN
OemPhyCheckDownshift(
                IN PVOID           callerCtx,
                IN ULONG           phyId,
                IN PIEGB_PHY_RD    iegbPhyRead,
                IN PIEGB_PHY_WR    iegbPhyWrite
                )
{
    USHORT      phy_data=0;

    DBGPRINT(("OemPhyCheckDownshift\n"));

    switch( phyId )
    {
        case PHY_ID_M88E1118:
            iegbPhyRead(callerCtx, M88E1118_PHY_SPEC_STATUS, &phy_data);

            return (phy_data & M88E1118_PSSR_DOWNSHIFT) >>
                                M88E1118_PSSR_DOWNSHIFT_SHIFT;
        default:
            break;
    }

    return TRUE; /* default */
}
