/*
 * FreeRTOS+TCP V3.1.0
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/**
 * @brief
 * Handling of Ethernet PHY's
 * PHY's communicate with an EMAC either through
 * a Media-Independent Interface (MII), or a Reduced Media-Independent Interface (RMII).
 * The EMAC can poll for PHY ports on 32 different addresses. Each of the PHY ports
 * shall be treated independently.
 *
 */

/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"

#include "phyHandling.h"

#define phyMIN_PHY_ADDRESS    0
#define phyMAX_PHY_ADDRESS    31

#if defined( PHY_LS_HIGH_CHECK_TIME_MS ) || defined( PHY_LS_LOW_CHECK_TIME_MS )
    #warning please use the new defines with 'ipconfig' prefix
#endif

#ifndef ipconfigPHY_LS_HIGH_CHECK_TIME_MS

/* Check if the LinkStatus in the PHY is still high after 15 seconds of not
 * receiving packets. */
    #define ipconfigPHY_LS_HIGH_CHECK_TIME_MS    15000U
#endif

#ifndef ipconfigPHY_LS_LOW_CHECK_TIME_MS
    /* Check if the LinkStatus in the PHY is still low every second. */
    #define ipconfigPHY_LS_LOW_CHECK_TIME_MS    1000U
#endif

/* As the following 3 macro's are OK in most situations, and so they're not
 * included in 'FreeRTOSIPConfigDefaults.h'.
 * Users can change their values in the project's 'FreeRTOSIPConfig.h'. */
#ifndef phyPHY_MAX_RESET_TIME_MS
    #define phyPHY_MAX_RESET_TIME_MS    1000U
#endif

#ifndef phyPHY_MAX_NEGOTIATE_TIME_MS
    #define phyPHY_MAX_NEGOTIATE_TIME_MS    3000U
#endif

#ifndef phySHORT_DELAY_MS
    #define phySHORT_DELAY_MS    50U
#endif

/* Naming and numbering of basic PHY registers. */
#define phyREG_00_BMCR             0x00U    /* Basic Mode Control Register. */
#define phyREG_01_BMSR             0x01U    /* Basic Mode Status Register. */
#define phyREG_02_PHYSID1          0x02U    /* PHYS ID 1 */
#define phyREG_03_PHYSID2          0x03U    /* PHYS ID 2 */
#define phyREG_04_ADVERTISE        0x04U    /* Advertisement control reg */

/* Naming and numbering of extended PHY registers. */
#define PHYREG_10_PHYSTS           0x10U    /* 16 PHY status register Offset */
#define phyREG_19_PHYCR            0x19U    /* 25 RW PHY Control Register */
#define phyREG_1F_PHYSPCS          0x1FU    /* 31 RW PHY Special Control Status */

/* Bit fields for 'phyREG_00_BMCR', the 'Basic Mode Control Register'. */
#define phyBMCR_FULL_DUPLEX        0x0100U  /* Full duplex. */
#define phyBMCR_AN_RESTART         0x0200U  /* Auto negotiation restart. */
#define phyBMCR_ISOLATE            0x0400U  /* 1 = Isolates 0 = Normal operation. */
#define phyBMCR_AN_ENABLE          0x1000U  /* Enable auto negotiation. */
#define phyBMCR_SPEED_100          0x2000U  /* Select 100Mbps. */
#define phyBMCR_RESET              0x8000U  /* Reset the PHY. */

/* Bit fields for 'phyREG_19_PHYCR', the 'PHY Control Register'. */
#define PHYCR_MDIX_EN              0x8000U  /* Enable Auto MDIX. */
#define PHYCR_MDIX_FORCE           0x4000U  /* Force MDIX crossed. */

#define phyBMSR_AN_COMPLETE        0x0020U  /* Auto-Negotiation process completed */

#define phyBMSR_LINK_STATUS        0x0004U

#define phyPHYSTS_LINK_STATUS      0x0001U  /* PHY Link mask */
#define phyPHYSTS_SPEED_STATUS     0x0002U  /* PHY Speed mask */
#define phyPHYSTS_DUPLEX_STATUS    0x0004U  /* PHY Duplex mask */

/* Bit fields for 'phyREG_1F_PHYSPCS
 *  001 = 10BASE-T half-duplex
 *  101 = 10BASE-T full-duplex
 *  010 = 100BASE-TX half-duplex
 *  110 = 100BASE-TX full-duplex
 */
#define phyPHYSPCS_SPEED_MASK      0x000CU
#define phyPHYSPCS_SPEED_10        0x0004U
#define phyPHYSPCS_FULL_DUPLEX     0x0010U

/*
 * Description of all capabilities that can be advertised to
 * the peer (usually a switch or router).
 */

#define phyADVERTISE_CSMA       0x0001U     /* Supports IEEE 802.3u: Fast Ethernet at 100 Mbit/s */
#define phyADVERTISE_10HALF     0x0020U     /* Try for 10mbps half-duplex. */
#define phyADVERTISE_10FULL     0x0040U     /* Try for 10mbps full-duplex. */
#define phyADVERTISE_100HALF    0x0080U     /* Try for 100mbps half-duplex. */
#define phyADVERTISE_100FULL    0x0100U     /* Try for 100mbps full-duplex. */

#define phyADVERTISE_ALL                            \
    ( phyADVERTISE_10HALF | phyADVERTISE_10FULL |   \
      phyADVERTISE_100HALF | phyADVERTISE_100FULL | \
      phyADVERTISE_CSMA )

/* Send a reset command to a set of PHY-ports. */
static uint32_t xPhyReset( EthernetPhy_t * pxPhyObject,
                           uint32_t ulPhyMask );

static BaseType_t xHas_1F_PHYSPCS( uint32_t ulPhyID )
{
    BaseType_t xResult = pdFALSE;

    switch( ulPhyID )
    {
        case PHY_ID_LAN8720:
        case PHY_ID_LAN8742A:
        case PHY_ID_KSZ8041:

        /*
         *      case PHY_ID_KSZ8051: // same ID as 8041
         *      case PHY_ID_KSZ8081: // same ID as 8041
         */
        case PHY_ID_KSZ8081MNXIA:

        case PHY_ID_KSZ8863:
        default:
            /* Most PHY's have a 1F_PHYSPCS */
            xResult = pdTRUE;
            break;

        case PHY_ID_DP83848I:
        case PHY_ID_DP83TC811S:
        case PHY_ID_TM4C129X:
        case PHY_ID_MV88E6071:
            /* Has no 0x1F register "PHY Special Control Status". */
            break;
    }

    return xResult;
}
/*-----------------------------------------------------------*/

static BaseType_t xHas_19_PHYCR( uint32_t ulPhyID )
{
    BaseType_t xResult = pdFALSE;

    switch( ulPhyID )
    {
        case PHY_ID_LAN8742A:
        case PHY_ID_DP83848I:
        case PHY_ID_TM4C129X:
            xResult = pdTRUE;
            break;

        case PHY_ID_MV88E6071: /* Marvell 88E6071 */
        default:
            /* Most PHY's do not have a 19_PHYCR */
            break;
    }

    return xResult;
}
/*-----------------------------------------------------------*/

/* Initialise the struct and assign a PHY-read and -write function. */
void vPhyInitialise( EthernetPhy_t * pxPhyObject,
                     xApplicationPhyReadHook_t fnPhyRead,
                     xApplicationPhyWriteHook_t fnPhyWrite )
{
    memset( ( void * ) pxPhyObject, 0, sizeof( *pxPhyObject ) );

    pxPhyObject->fnPhyRead = fnPhyRead;
    pxPhyObject->fnPhyWrite = fnPhyWrite;
}
/*-----------------------------------------------------------*/

/* Discover all PHY's connected by polling 32 indexes ( zero-based ) */
BaseType_t xPhyDiscover( EthernetPhy_t * pxPhyObject )
{
    BaseType_t xPhyAddress;

    pxPhyObject->xPortCount = 0;

    for( xPhyAddress = phyMIN_PHY_ADDRESS; xPhyAddress <= phyMAX_PHY_ADDRESS; xPhyAddress++ )
    {
        uint32_t ulLowerID = 0U;

        pxPhyObject->fnPhyRead( xPhyAddress, phyREG_03_PHYSID2, &ulLowerID );

        /* A valid PHY id can not be all zeros or all ones. */
        if( ( ulLowerID != ( uint16_t ) ~0U ) && ( ulLowerID != ( uint16_t ) 0U ) )
        {
            uint32_t ulUpperID;
            uint32_t ulPhyID;

            pxPhyObject->fnPhyRead( xPhyAddress, phyREG_02_PHYSID1, &ulUpperID );
            ulPhyID = ( ( ( uint32_t ) ulUpperID ) << 16 ) | ( ulLowerID & 0xFFF0U );

            pxPhyObject->ucPhyIndexes[ pxPhyObject->xPortCount ] = xPhyAddress;
            pxPhyObject->ulPhyIDs[ pxPhyObject->xPortCount ] = ulPhyID;

            pxPhyObject->xPortCount++;

            /* See if there is more storage space. */
            if( pxPhyObject->xPortCount == ipconfigPHY_MAX_PORTS )
            {
                break;
            }
        }
    }

    if( pxPhyObject->xPortCount > 0 )
    {
        FreeRTOS_printf( ( "PHY ID %lX\n", pxPhyObject->ulPhyIDs[ 0 ] ) );
    }

    return pxPhyObject->xPortCount;
}
/*-----------------------------------------------------------*/

/* Send a reset command to a set of PHY-ports. */
static uint32_t xPhyReset( EthernetPhy_t * pxPhyObject,
                           uint32_t ulPhyMask )
{
    uint32_t ulDoneMask, ulConfig;
    TickType_t xRemainingTime;
    TimeOut_t xTimer;
    BaseType_t xPhyIndex;

    /* A bit-mask of PHY ports that are ready. */
    ulDoneMask = 0U;

    /* Set the RESET bits high. */
    for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++ )
    {
        BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

        /* Read Control register. */
        pxPhyObject->fnPhyRead( xPhyAddress, phyREG_00_BMCR, &ulConfig );
        pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_00_BMCR, ulConfig | phyBMCR_RESET );
    }

    xRemainingTime = ( TickType_t ) pdMS_TO_TICKS( phyPHY_MAX_RESET_TIME_MS );
    vTaskSetTimeOutState( &xTimer );

    /* The reset should last less than a second. */
    for( ; ; )
    {
        for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++ )
        {
            BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

            pxPhyObject->fnPhyRead( xPhyAddress, phyREG_00_BMCR, &ulConfig );

            if( ( ulConfig & phyBMCR_RESET ) == 0 )
            {
                FreeRTOS_printf( ( "xPhyReset: phyBMCR_RESET %d ready\n", ( int ) xPhyIndex ) );
                ulDoneMask |= ( 1U << xPhyIndex );
            }
        }

        if( ulDoneMask == ulPhyMask )
        {
            break;
        }

        if( xTaskCheckForTimeOut( &xTimer, &xRemainingTime ) != pdFALSE )
        {
            FreeRTOS_printf( ( "xPhyReset: phyBMCR_RESET timed out ( done 0x%02lX )\n", ulDoneMask ) );
            break;
        }

        /* Block for a while */
        vTaskDelay( pdMS_TO_TICKS( phySHORT_DELAY_MS ) );
    }

    /* Clear the reset bits. */
    for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++ )
    {
        if( ( ulDoneMask & ( 1U << xPhyIndex ) ) == 0U )
        {
            BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

            /* The reset operation timed out, clear the bit manually. */
            pxPhyObject->fnPhyRead( xPhyAddress, phyREG_00_BMCR, &ulConfig );
            pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_00_BMCR, ulConfig & ~phyBMCR_RESET );
        }
    }

    vTaskDelay( pdMS_TO_TICKS( phySHORT_DELAY_MS ) );

    return ulDoneMask;
}
/*-----------------------------------------------------------*/

BaseType_t xPhyConfigure( EthernetPhy_t * pxPhyObject,
                          const PhyProperties_t * pxPhyProperties )
{
    uint32_t ulConfig, ulAdvertise;
    BaseType_t xPhyIndex;

    if( pxPhyObject->xPortCount < 1 )
    {
        FreeRTOS_printf( ( "xPhyConfigure: No PHY's detected.\n" ) );
        return -1;
    }

    /* The expected ID for the 'LAN8742A'  is 0x0007c130. */
    /* The expected ID for the 'LAN8720'   is 0x0007c0f0. */
    /* The expected ID for the 'DP83848I'  is 0x20005C90. */

    /* Set advertise register. */
    if( ( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_AUTO ) && ( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_AUTO ) )
    {
        ulAdvertise = phyADVERTISE_ALL;
        /* Reset auto-negotiation capability. */
    }
    else
    {
        /* Always select protocol 802.3u. */
        ulAdvertise = phyADVERTISE_CSMA;

        if( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_AUTO )
        {
            if( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_FULL )
            {
                ulAdvertise |= phyADVERTISE_10FULL | phyADVERTISE_100FULL;
            }
            else
            {
                ulAdvertise |= phyADVERTISE_10HALF | phyADVERTISE_100HALF;
            }
        }
        else if( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_AUTO )
        {
            if( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_10 )
            {
                ulAdvertise |= phyADVERTISE_10FULL | phyADVERTISE_10HALF;
            }
            else
            {
                ulAdvertise |= phyADVERTISE_100FULL | phyADVERTISE_100HALF;
            }
        }
        else if( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_100 )
        {
            if( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_FULL )
            {
                ulAdvertise |= phyADVERTISE_100FULL;
            }
            else
            {
                ulAdvertise |= phyADVERTISE_100HALF;
            }
        }
        else
        {
            if( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_FULL )
            {
                ulAdvertise |= phyADVERTISE_10FULL;
            }
            else
            {
                ulAdvertise |= phyADVERTISE_10HALF;
            }
        }
    }

    /* Send a reset command to a set of PHY-ports. */
    xPhyReset( pxPhyObject, xPhyGetMask( pxPhyObject ) );

    for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++ )
    {
        BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];
        uint32_t ulPhyID = pxPhyObject->ulPhyIDs[ xPhyIndex ];

        /* Write advertise register. */
        pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_04_ADVERTISE, ulAdvertise );

        /*
         *      AN_EN        AN1         AN0       Forced Mode
         *        0           0           0        10BASE-T, Half-Duplex
         *        0           0           1        10BASE-T, Full-Duplex
         *        0           1           0        100BASE-TX, Half-Duplex
         *        0           1           1        100BASE-TX, Full-Duplex
         *      AN_EN        AN1         AN0       Advertised Mode
         *        1           0           0        10BASE-T, Half/Full-Duplex
         *        1           0           1        100BASE-TX, Half/Full-Duplex
         *        1           1           0        10BASE-T Half-Duplex
         *                                         100BASE-TX, Half-Duplex
         *        1           1           1        10BASE-T, Half/Full-Duplex
         *                                         100BASE-TX, Half/Full-Duplex
         */

        /* Read Control register. */
        pxPhyObject->fnPhyRead( xPhyAddress, phyREG_00_BMCR, &ulConfig );

        ulConfig &= ~( phyBMCR_SPEED_100 | phyBMCR_FULL_DUPLEX );

        ulConfig |= phyBMCR_AN_ENABLE;

        if( ( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_100 ) || ( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_AUTO ) )
        {
            ulConfig |= phyBMCR_SPEED_100;
        }
        else if( pxPhyProperties->ucSpeed == ( uint8_t ) PHY_SPEED_10 )
        {
            ulConfig &= ~phyBMCR_SPEED_100;
        }

        if( ( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_FULL ) || ( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_AUTO ) )
        {
            ulConfig |= phyBMCR_FULL_DUPLEX;
        }
        else if( pxPhyProperties->ucDuplex == ( uint8_t ) PHY_DUPLEX_HALF )
        {
            ulConfig &= ~phyBMCR_FULL_DUPLEX;
        }

        if( xHas_19_PHYCR( ulPhyID ) )
        {
            uint32_t ulPhyControl;
            /* Read PHY Control register. */
            pxPhyObject->fnPhyRead( xPhyAddress, phyREG_19_PHYCR, &ulPhyControl );

            /* Clear bits which might get set: */
            ulPhyControl &= ~( PHYCR_MDIX_EN | PHYCR_MDIX_FORCE );

            if( pxPhyProperties->ucMDI_X == PHY_MDIX_AUTO )
            {
                ulPhyControl |= PHYCR_MDIX_EN;
            }
            else if( pxPhyProperties->ucMDI_X == PHY_MDIX_CROSSED )
            {
                /* Force direct link = Use crossed RJ45 cable. */
                ulPhyControl &= ~PHYCR_MDIX_FORCE;
            }
            else
            {
                /* Force crossed link = Use direct RJ45 cable. */
                ulPhyControl |= PHYCR_MDIX_FORCE;
            }

            /* update PHY Control Register. */
            pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_19_PHYCR, ulPhyControl );
        }

        FreeRTOS_printf( ( "+TCP: advertise: %04lX config %04lX\n", ulAdvertise, ulConfig ) );
    }

    /* Keep these values for later use. */
    pxPhyObject->ulBCRValue = ulConfig & ~phyBMCR_ISOLATE;
    pxPhyObject->ulACRValue = ulAdvertise;

    return 0;
}
/*-----------------------------------------------------------*/

/* xPhyFixedValue(): this function is called in case auto-negotiation is disabled.
 * The caller has set the values in 'xPhyPreferences' (ucDuplex and ucSpeed).
 * The PHY register phyREG_00_BMCR will be set for every connected PHY that matches
 * with ulPhyMask. */
BaseType_t xPhyFixedValue( EthernetPhy_t * pxPhyObject,
                           uint32_t ulPhyMask )
{
    BaseType_t xPhyIndex;
    uint32_t ulValue, ulBitMask = ( uint32_t ) 1U;

    ulValue = ( uint32_t ) 0U;

    if( pxPhyObject->xPhyPreferences.ucDuplex == PHY_DUPLEX_FULL )
    {
        ulValue |= phyBMCR_FULL_DUPLEX;
    }

    if( pxPhyObject->xPhyPreferences.ucSpeed == PHY_SPEED_100 )
    {
        ulValue |= phyBMCR_SPEED_100;
    }

    for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
    {
        if( ( ulPhyMask & ulBitMask ) != 0lu )
        {
            BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

            /* Enable Auto-Negotiation. */
            pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_00_BMCR, ulValue );
        }
    }

    return 0;
}
/*-----------------------------------------------------------*/

/* xPhyStartAutoNegotiation() is the alternative xPhyFixedValue():
 * It sets the BMCR_AN_RESTART bit and waits for the auto-negotiation completion
 * ( phyBMSR_AN_COMPLETE ). */
BaseType_t xPhyStartAutoNegotiation( EthernetPhy_t * pxPhyObject,
                                     uint32_t ulPhyMask )
{
    uint32_t xPhyIndex, ulDoneMask, ulBitMask;
    uint32_t ulPHYLinkStatus, ulRegValue;
    TickType_t xRemainingTime;
    TimeOut_t xTimer;

    if( ulPhyMask == ( uint32_t ) 0U )
    {
        return 0;
    }

    for( xPhyIndex = 0; xPhyIndex < ( uint32_t ) pxPhyObject->xPortCount; xPhyIndex++ )
    {
        if( ( ulPhyMask & ( 1lu << xPhyIndex ) ) != 0lu )
        {
            BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

            /* Enable Auto-Negotiation. */
            pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_04_ADVERTISE, pxPhyObject->ulACRValue );
            pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_00_BMCR, pxPhyObject->ulBCRValue | phyBMCR_AN_RESTART );
        }
    }

    xRemainingTime = ( TickType_t ) pdMS_TO_TICKS( phyPHY_MAX_NEGOTIATE_TIME_MS );
    vTaskSetTimeOutState( &xTimer );
    ulDoneMask = 0;

    /* Wait until the auto-negotiation will be completed */
    for( ; ; )
    {
        ulBitMask = ( uint32_t ) 1U;

        for( xPhyIndex = 0; xPhyIndex < ( uint32_t ) pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
        {
            if( ( ulPhyMask & ulBitMask ) != 0lu )
            {
                if( ( ulDoneMask & ulBitMask ) == 0lu )
                {
                    BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

                    pxPhyObject->fnPhyRead( xPhyAddress, phyREG_01_BMSR, &ulRegValue );

                    if( ( ulRegValue & phyBMSR_AN_COMPLETE ) != 0 )
                    {
                        ulDoneMask |= ulBitMask;
                    }
                }
            }
        }

        if( ulPhyMask == ulDoneMask )
        {
            break;
        }

        if( xTaskCheckForTimeOut( &xTimer, &xRemainingTime ) != pdFALSE )
        {
            FreeRTOS_printf( ( "xPhyStartAutoNegotiation: phyBMSR_AN_COMPLETE timed out ( done 0x%02lX )\n", ulDoneMask ) );
            break;
        }

        vTaskDelay( pdMS_TO_TICKS( phySHORT_DELAY_MS ) );
    }

    if( ulDoneMask != ( uint32_t ) 0U )
    {
        ulBitMask = ( uint32_t ) 1U;
        pxPhyObject->ulLinkStatusMask &= ~( ulDoneMask );

        for( xPhyIndex = 0; xPhyIndex < ( uint32_t ) pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
        {
            BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];
            uint32_t ulPhyID = pxPhyObject->ulPhyIDs[ xPhyIndex ];

            if( ( ulDoneMask & ulBitMask ) == ( uint32_t ) 0U )
            {
                continue;
            }

            /* Clear the 'phyBMCR_AN_RESTART'  bit. */
            pxPhyObject->fnPhyWrite( xPhyAddress, phyREG_00_BMCR, pxPhyObject->ulBCRValue );

            pxPhyObject->fnPhyRead( xPhyAddress, phyREG_01_BMSR, &ulRegValue );

            if( ( ulRegValue & phyBMSR_LINK_STATUS ) != 0 )
            {
                ulPHYLinkStatus |= phyBMSR_LINK_STATUS;
                pxPhyObject->ulLinkStatusMask |= ulBitMask;
            }
            else
            {
                ulPHYLinkStatus &= ~( phyBMSR_LINK_STATUS );
            }

            if( ulPhyID == PHY_ID_KSZ8081MNXIA )
            {
                uint32_t ulControlStatus;

                pxPhyObject->fnPhyRead( xPhyAddress, 0x1E, &ulControlStatus );

                switch( ulControlStatus & 0x07 )
                {
                    case 0x01:
                    case 0x05:
/*	[001] = 10BASE-T half-duplex */
/*	[101] = 10BASE-T full-duplex */
                        /* 10 Mbps. */
                        ulRegValue |= phyPHYSTS_SPEED_STATUS;
                        break;

                    case 0x02:
                    case 0x06:
/*	[010] = 100BASE-TX half-duplex */
/*	[110] = 100BASE-TX full-duplex */
                        break;
                }

                switch( ulControlStatus & 0x07 )
                {
                    case 0x05:
                    case 0x06:
/*	[101] = 10BASE-T full-duplex */
/*	[110] = 100BASE-TX full-duplex */
                        /* Full duplex. */
                        ulRegValue |= phyPHYSTS_DUPLEX_STATUS;
                        break;

                    case 0x01:
                    case 0x02:
/*	[001] = 10BASE-T half-duplex */
/*	[010] = 100BASE-TX half-duplex */
                        break;
                }
            }
            else if( ulPhyID == PHY_ID_KSZ8795 )
            {
                /* KSZ8795 has a different mapping for the Port Operation Mode Indication field
                 *   in the phyREG_1F_PHYSPCS than other similar PHYs:
                 *     010 = 10BASE-T half-duplex
                 *     101 = 10BASE-T full-duplex
                 *     011 = 100BASE-TX half-duplex
                 *     110 = 100BASE-TX full-duplex
                 */
                uint32_t ulControlStatus = 0u;
                uint32_t ulPortOperationMode = 0u;
                pxPhyObject->fnPhyRead( xPhyAddress, phyREG_1F_PHYSPCS, &ulControlStatus );
                ulPortOperationMode = ( ulControlStatus >> 8u ) & 0x07u;

                ulRegValue = 0;

                /* Detect 10baseT operation */
                if( ( 0x02u == ulPortOperationMode ) || ( 0x05u == ulPortOperationMode ) )
                {
                    ulRegValue |= phyPHYSTS_SPEED_STATUS;
                }

                /* Detect full duplex operation */
                if( ( 0x05u == ulPortOperationMode ) || ( 0x06u == ulPortOperationMode ) )
                {
                    ulRegValue |= phyPHYSTS_DUPLEX_STATUS;
                }
            }
            else if( xHas_1F_PHYSPCS( ulPhyID ) )
            {
                /* 31 RW PHY Special Control Status */
                uint32_t ulControlStatus;

                pxPhyObject->fnPhyRead( xPhyAddress, phyREG_1F_PHYSPCS, &ulControlStatus );
                ulRegValue = 0;

                if( ( ulControlStatus & phyPHYSPCS_FULL_DUPLEX ) != 0 )
                {
                    ulRegValue |= phyPHYSTS_DUPLEX_STATUS;
                }

                if( ( ulControlStatus & phyPHYSPCS_SPEED_MASK ) == phyPHYSPCS_SPEED_10 )
                {
                    ulRegValue |= phyPHYSTS_SPEED_STATUS;
                }
            }
            else
            {
                /* Read the result of the auto-negotiation. */
                pxPhyObject->fnPhyRead( xPhyAddress, PHYREG_10_PHYSTS, &ulRegValue );
            }

            FreeRTOS_printf( ( "Autonego ready: %08lx: %s duplex %u mbit %s status\n",
                               ulRegValue,
                               ( ulRegValue & phyPHYSTS_DUPLEX_STATUS ) ? "full" : "half",
                               ( ulRegValue & phyPHYSTS_SPEED_STATUS ) ? 10 : 100,
                               ( ( ulPHYLinkStatus |= phyBMSR_LINK_STATUS ) != 0 ) ? "high" : "low" ) );

            if( ( ulRegValue & phyPHYSTS_DUPLEX_STATUS ) != ( uint32_t ) 0U )
            {
                pxPhyObject->xPhyProperties.ucDuplex = PHY_DUPLEX_FULL;
            }
            else
            {
                pxPhyObject->xPhyProperties.ucDuplex = PHY_DUPLEX_HALF;
            }

            if( ( ulRegValue & phyPHYSTS_SPEED_STATUS ) != 0 )
            {
                pxPhyObject->xPhyProperties.ucSpeed = PHY_SPEED_10;
            }
            else
            {
                pxPhyObject->xPhyProperties.ucSpeed = PHY_SPEED_100;
            }
        }
    } /* if( ulDoneMask != ( uint32_t) 0U ) */

    return 0;
}
/*-----------------------------------------------------------*/

BaseType_t xPhyCheckLinkStatus( EthernetPhy_t * pxPhyObject,
                                BaseType_t xHadReception )
{
    uint32_t ulStatus, ulBitMask = 1U;
    BaseType_t xPhyIndex;
    BaseType_t xNeedCheck = pdFALSE;

    if( xHadReception > 0 )
    {
        /* A packet was received. No need to check for the PHY status now,
         * but set a timer to check it later on. */
        vTaskSetTimeOutState( &( pxPhyObject->xLinkStatusTimer ) );
        pxPhyObject->xLinkStatusRemaining = pdMS_TO_TICKS( ipconfigPHY_LS_HIGH_CHECK_TIME_MS );

        for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
        {
            if( ( pxPhyObject->ulLinkStatusMask & ulBitMask ) == 0UL )
            {
                pxPhyObject->ulLinkStatusMask |= ulBitMask;
                FreeRTOS_printf( ( "xPhyCheckLinkStatus: PHY LS now %02lX\n", pxPhyObject->ulLinkStatusMask ) );
                xNeedCheck = pdTRUE;
            }
        }
    }
    else if( xTaskCheckForTimeOut( &( pxPhyObject->xLinkStatusTimer ), &( pxPhyObject->xLinkStatusRemaining ) ) != pdFALSE )
    {
        /* Frequent checking the PHY Link Status can affect for the performance of Ethernet controller.
         * As long as packets are received, no polling is needed.
         * Otherwise, polling will be done when the 'xLinkStatusTimer' expires. */
        for( xPhyIndex = 0; xPhyIndex < pxPhyObject->xPortCount; xPhyIndex++, ulBitMask <<= 1 )
        {
            BaseType_t xPhyAddress = pxPhyObject->ucPhyIndexes[ xPhyIndex ];

            if( pxPhyObject->fnPhyRead( xPhyAddress, phyREG_01_BMSR, &ulStatus ) == 0 )
            {
                if( !!( pxPhyObject->ulLinkStatusMask & ulBitMask ) != !!( ulStatus & phyBMSR_LINK_STATUS ) )
                {
                    if( ( ulStatus & phyBMSR_LINK_STATUS ) != 0 )
                    {
                        pxPhyObject->ulLinkStatusMask |= ulBitMask;
                    }
                    else
                    {
                        pxPhyObject->ulLinkStatusMask &= ~( ulBitMask );
                    }

                    FreeRTOS_printf( ( "xPhyCheckLinkStatus: PHY LS now %02lX\n", pxPhyObject->ulLinkStatusMask ) );
                    xNeedCheck = pdTRUE;
                }
            }
        }

        vTaskSetTimeOutState( &( pxPhyObject->xLinkStatusTimer ) );

        if( ( pxPhyObject->ulLinkStatusMask & ( ulBitMask >> 1 ) ) != 0 )
        {
            /* The link status is high, so don't poll the PHY too often. */
            pxPhyObject->xLinkStatusRemaining = pdMS_TO_TICKS( ipconfigPHY_LS_HIGH_CHECK_TIME_MS );
        }
        else
        {
            /* The link status is low, polling may be done more frequently. */
            pxPhyObject->xLinkStatusRemaining = pdMS_TO_TICKS( ipconfigPHY_LS_LOW_CHECK_TIME_MS );
        }
    }

    return xNeedCheck;
}
/*-----------------------------------------------------------*/
