/*
 * FreeRTOS+TCP V4.2.2
 * 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
 */

/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.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 "FreeRTOS_IP_Private.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"

#include "sam4e_xplained_pro.h"
#include "hr_gettime.h"
#include "conf_eth.h"
#include "ksz8851snl.h"
#include "ksz8851snl_reg.h"

/* Some files from the Atmel Software Framework */
#include <sysclk.h>
#include <pdc/pdc.h>
#include <spi/spi.h>

/*
 *  Sending a packet:
 *
 *      1) Called by UP-task, add buffer to the TX-list:
 *          xNetworkInterfaceOutput()
 *              tx_buffers[ us_tx_head ] = pxNetworkBuffer;
 *              tx_busy[ us_tx_head ] = pdTRUE;
 *              us_tx_head++;
 *
 *      2) Called by EMAC-Task: start SPI transfer
 *          ksz8851snl_update()
 *          if( ul_spi_pdc_status == SPI_PDC_IDLE )
 *          {
 *              if( ( tx_busy[ us_tx_tail ] != pdFALSE ) &&
 *                  ( us_pending_frame == 0 ) &&
 *                  ( ul_had_intn_interrupt == 0 ) )
 *              {
 *                  // disable all interrupts.
 *                  ksz8851_reg_write( REG_INT_MASK, 0 );
 *                  Bring KSZ8851SNL_CSN_GPIO low
 *                  ksz8851_fifo_write( pxNetworkBuffer->pucEthernetBuffer, xLength, xLength );
 *                  ul_spi_pdc_status = SPI_PDC_TX_START;
 *                  tx_cur_buffer = pxNetworkBuffer;
 *              }
 *          }
 *      3) Wait for SPI RXBUFF interrupt
 *          SPI_Handler()
 *              if( ul_spi_pdc_status == SPI_PDC_TX_START )
 *              {
 *                  if( SPI_Status & SPI_SR_RXBUFF )
 *                  {
 *                      ul_spi_pdc_status = SPI_PDC_TX_COMPLETE;
 *                  }
 *              }
 *
 *      4) Called by EMAC-Task: finish SPI transfer
 *          ksz8851snl_update()
 *              if( ul_spi_pdc_status == SPI_PDC_TX_COMPLETE )
 *              {
 *                  ul_spi_pdc_status = SPI_PDC_IDLE;
 *                  Bring KSZ8851SNL_CSN_GPIO high
 *                  // TX step12: disable TXQ write access.
 *                  ksz8851_reg_clrbits( REG_RXQ_CMD, RXQ_START );
 *                  // TX step12.1: enqueue frame in TXQ.
 *                  ksz8851_reg_setbits( REG_TXQ_CMD, TXQ_ENQUEUE );
 *
 *                  // RX step13: enable INT_RX flag.
 *                  ksz8851_reg_write( REG_INT_MASK, INT_RX );
 *
 *                  // Buffer sent, free the corresponding buffer and mark descriptor as owned by software.
 *                  vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
 *
 *                  tx_buffers[ us_tx_tail ] = NULL;
 *                  tx_busy[ us_tx_tail ] = pdFALSE;
 *                  us_tx_tail++
 *              }
 *
 *  Receiving a packet:
 *
 *      1) Wait for a INTN interrupt
 *          INTN_Handler()
 *              ul_had_intn_interrupt = 1
 *              vTaskNotifyGiveFromISR();	// Wake up the EMAC task
 *
 *      2) Called by EMAC-Task: check for new fragments and start SPI transfer
 *          ksz8851snl_update()
 *              if( ul_spi_pdc_status == SPI_PDC_IDLE )
 *              {
 *                  if( ( ul_had_intn_interrupt != 0 ) || ( us_pending_frame > 0 ) )
 *                  {
 *                      if( us_pending_frame == 0 )
 *                      {
 *                          us_pending_frame = ksz8851_reg_read(REG_RX_FRAME_CNT_THRES) >> 8;
 *                          if( us_pending_frame == 0 )
 *                          {
 *                              break;
 *                          }
 *                      }
 *                      // RX step2: disable all interrupts.
 *                      ksz8851_reg_write( REG_INT_MASK, 0 );
 *                      Check if there is a valid packet: REG_RX_FHR_STATUS
 *                      Read the length of the next fragment: REG_RX_FHR_BYTE_CNT
 *                      ul_spi_pdc_status = SPI_PDC_RX_START;
 *                      gpio_set_pin_low(KSZ8851SNL_CSN_GPIO);
 *                      // Start SPI data transfer
 *                      ksz8851_fifo_read( pxNetworkBuffer->pucEthernetBuffer, xReadLength );
 *                  }
 *              }
 *
 *      3) Wait for SPI RXBUFF interrupt
 *          SPI_Handler()
 *          if( ul_spi_pdc_status == SPI_PDC_RX_START:
 *          {
 *              if( ( ulCurrentSPIStatus & SPI_SR_RXBUFF ) != 0 )
 *              {
 *                  // Transfer complete, disable SPI RXBUFF interrupt.
 *                  spi_disable_interrupt( KSZ8851SNL_SPI, SPI_IDR_RXBUFF );
 *
 *                  ul_spi_pdc_status = SPI_PDC_RX_COMPLETE;
 *              }
 *          }
 *      }
 *
 *      4) Finish SPI transfer
 *          ksz8851snl_update()
 *              if( ul_spi_pdc_status == SPI_PDC_RX_COMPLETE )
 *              {
 *                  ul_spi_pdc_status = SPI_PDC_IDLE;
 *                  Bring KSZ8851SNL_CSN_GPIO high
 *                  // RX step21: end RXQ read access.
 *                  ksz8851_reg_clrbits(REG_RXQ_CMD, RXQ_START);
 *                  // RX step22-23: update frame count to be read.
 *                  us_pending_frame--
 *                  // RX step24: enable INT_RX flag if transfer complete.
 *                  if( us_pending_frame == 0 )
 *                  {
 *                      // Allow more RX interrupts.
 *                      ksz8851_reg_write( REG_INT_MASK, INT_RX );
 *                  }
 *
 *                  // Mark descriptor ready to be read.
 *                  rx_ready[ rxHead ] = pdTRUE;
 *                  rxHead++
 *              }
 */

#define PHY_REG_00_BMCR           0x00   /* Basic mode control register */
#define PHY_REG_01_BMSR           0x01   /* Basic mode status register */
#define PHY_REG_02_PHYSID1        0x02   /* PHYS ID 1 */
#define PHY_REG_03_PHYSID2        0x03   /* PHYS ID 2 */
#define PHY_REG_04_ADVERTISE      0x04   /* Advertisement control reg */
#define PHY_REG_05_LPA            0x05   /* Link partner ability reg */
#define PHY_REG_06_ANER           0x06   /*	6	RW		Auto-Negotiation Expansion Register */
#define PHY_REG_07_ANNPTR         0x07   /*	7	RW		Auto-Negotiation Next Page TX */
#define PHY_REG_08_RESERVED0      0x08   /* 0x08..0x0Fh	8-15	RW		RESERVED */

#define BMSR_LINK_STATUS          0x0004 /*!< Link status */

/* Interrupt events to process.  Currently only the Rx event is processed
 * although code for other events is included to allow for possible future
 * expansion. */
#define EMAC_IF_RX_EVENT          1UL
#define EMAC_IF_TX_EVENT          2UL
#define EMAC_IF_ERR_EVENT         4UL
#define EMAC_IF_ALL_EVENT         ( EMAC_IF_RX_EVENT | EMAC_IF_TX_EVENT | EMAC_IF_ERR_EVENT )

#define ETHERNET_CONF_PHY_ADDR    BOARD_GMAC_PHY_ADDR

#ifdef ipconfigHAS_TX_CRC_OFFLOADING
    #undef ipconfigHAS_TX_CRC_OFFLOADING
#endif
/* Override this define because the KSZ8851 is programmed to set all outgoing CRC's */
#define ipconfigHAS_TX_CRC_OFFLOADING    1

#ifndef EMAC_MAX_BLOCK_TIME_MS
    #define EMAC_MAX_BLOCK_TIME_MS       100ul
#endif

/* Default the size of the stack used by the EMAC deferred handler task to 4x
 *  the size of the stack used by the idle task - but allow this to be overridden in
 *  FreeRTOSConfig.h as configMINIMAL_STACK_SIZE is a user definable constant. */
#ifndef configEMAC_TASK_STACK_SIZE
    #define configEMAC_TASK_STACK_SIZE    ( 6 * configMINIMAL_STACK_SIZE )
#endif

#define SPI_PDC_IDLE                      0
#define SPI_PDC_RX_START                  1
#define SPI_PDC_TX_ERROR                  2
#define SPI_PDC_RX_COMPLETE               3
#define SPI_PDC_TX_START                  4
#define SPI_PDC_RX_ERROR                  5
#define SPI_PDC_TX_COMPLETE               6

/**
 * ksz8851snl driver structure.
 */
typedef struct
{
    /** Set to 1 when owner is software (ready to read), 0 for Micrel. */
    uint32_t rx_ready[ MICREL_RX_BUFFERS ];
    /** Set to 1 when owner is Micrel, 0 for software. */
    uint32_t tx_busy[ MICREL_TX_BUFFERS ];
    /** RX NetworkBufferDescriptor_t pointer list */
    NetworkBufferDescriptor_t * rx_buffers[ MICREL_RX_BUFFERS ];
    /** TX NetworkBufferDescriptor_t pointer list */
    NetworkBufferDescriptor_t * tx_buffers[ MICREL_TX_BUFFERS ];
    NetworkBufferDescriptor_t * tx_cur_buffer;

    /** Circular buffer head pointer for packet received. */
    uint32_t us_rx_head;
    /** Circular buffer tail pointer for packet to be read. */
    uint32_t us_rx_tail;
    /** Circular buffer head pointer by upper layer (buffer to be sent). */
    uint32_t us_tx_head;
    /** Circular buffer tail pointer incremented by handlers (buffer sent). */
    uint32_t us_tx_tail;

    uint32_t ul_total_tx;
    uint32_t ul_total_rx;
    uint32_t tx_space;

    /** Still experimental: hash table to allow certain multicast addresses. */
    uint16_t pusHashTable[ 4 ];

    /* ul_spi_pdc_status has "SPI_PDC_xxx" values. */
    volatile uint32_t ul_spi_pdc_status;

    /* ul_had_intn_interrupt becomes true within the INTN interrupt. */
    volatile uint32_t ul_had_intn_interrupt;

    uint16_t us_pending_frame;
} xKSZ8851_Device_t;

/* SPI PDC register base.
 * Declared in ASF\sam\components\ksz8851snl\ksz8851snl.c */
extern Pdc * g_p_spi_pdc;

/* Temporary buffer for PDC reception.
 * declared in ASF\sam\components\ksz8851snl\ksz8851snl.c */
extern uint8_t tmpbuf[ 1536 ];

COMPILER_ALIGNED( 8 )
static xKSZ8851_Device_t xMicrelDevice;

static TaskHandle_t xTransmitHandle;

/*-----------------------------------------------------------*/

/*
 * Wait a fixed time for the link status to indicate the network is up.
 */
static BaseType_t xGMACWaitLS( TickType_t xMaxTime );

/*
 * A deferred interrupt handler task that processes GMAC interrupts.
 */
static void prvEMACHandlerTask( void * pvParameters );

/*
 * Try to obtain an Rx packet from the hardware.
 */
static uint32_t prvEMACRxPoll( void );

static inline unsigned long ulReadMDIO( unsigned uAddress );

static void ksz8851snl_low_level_init( void );

static NetworkBufferDescriptor_t * ksz8851snl_low_level_input( void );

/*-----------------------------------------------------------*/

/* Bit map of outstanding ETH interrupt events for processing.  Currently only
 * the Rx interrupt is handled, although code is included for other events to
 * enable future expansion. */
static volatile uint32_t ulISREvents;

/* A copy of PHY register 1: 'PHY_REG_01_BMSR' */
static uint32_t ulPHYLinkStatus = 0;
static volatile BaseType_t xGMACSwitchRequired;

static void ksz8851snl_update( void );

static void ksz8851snl_rx_init( void );

static void ksz8851snl_tx_init( void );

/* Holds the handle of the task used as a deferred interrupt processor.  The
 * handle is used so direct notifications can be sent to the task for all EMAC/DMA
 * related interrupts. */
TaskHandle_t xEMACTaskHandle = NULL;


/*-----------------------------------------------------------*/

BaseType_t xNetworkInterfaceInitialise( void )
{
    const TickType_t x5_Seconds = 5000UL;

    if( xEMACTaskHandle == NULL )
    {
        ksz8851snl_low_level_init();

        /* Wait at most 5 seconds for a Link Status in the PHY. */
        xGMACWaitLS( pdMS_TO_TICKS( x5_Seconds ) );

        /* The handler task is created at the highest possible priority to
         * ensure the interrupt handler can return directly to it. */
        xTaskCreate( prvEMACHandlerTask, "KSZ8851", configEMAC_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xEMACTaskHandle );
        configASSERT( xEMACTaskHandle != NULL );
    }

    /* When returning non-zero, the stack will become active and
     * start DHCP (in configured) */
    ulPHYLinkStatus = ulReadMDIO( PHY_REG_01_BMSR );

    return ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0;
}
/*-----------------------------------------------------------*/

BaseType_t xGetPhyLinkStatus( void )
{
    BaseType_t xResult;

    /* This function returns true if the Link Status in the PHY is high. */
    if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
    {
        xResult = pdTRUE;
    }
    else
    {
        xResult = pdFALSE;
    }

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

BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxNetworkBuffer,
                                    BaseType_t bReleaseAfterSend )
{
    BaseType_t xResult = pdFALSE;
    int txHead = xMicrelDevice.us_tx_head;

    /* Make sure the next descriptor is free. */
    if( xMicrelDevice.tx_busy[ txHead ] != pdFALSE )
    {
        /* All TX buffers busy. */
    }
    else if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) == 0 )
    {
        /* Output: LS low. */
    }
    else
    {
        /* Pass the packet. */
        xMicrelDevice.tx_buffers[ txHead ] = pxNetworkBuffer;
        /* The descriptor is now owned by Micrel. */
        xMicrelDevice.tx_busy[ txHead ] = pdTRUE;

        /* Move the head pointer. */
        if( ++txHead == MICREL_TX_BUFFERS )
        {
            txHead = 0;
        }

        xMicrelDevice.us_tx_head = txHead;

        if( xEMACTaskHandle != NULL )
        {
            xTaskNotifyGive( xEMACTaskHandle );
        }

        #if ( ipconfigZERO_COPY_TX_DRIVER != 1 )
        #if ( ipconfigPORT_SUPPRESS_WARNING == 0 )
            {
                #warning Please ipconfigZERO_COPY_TX_DRIVER as 1
            }
            #endif
        #endif
        configASSERT( bReleaseAfterSend != pdFALSE );
        xResult = pdTRUE;
    }

    if( ( xResult == pdFALSE ) && ( bReleaseAfterSend != pdFALSE ) )
    {
        vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
    }

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

/* This Micrel has numbered it's PHY registers in a different way.
 * Translate the register index. */
static int ks8851_phy_reg( int reg )
{
    switch( reg )
    {
        case PHY_REG_00_BMCR:
            return REG_PHY_CNTL; /* P1MBCR; */

        case PHY_REG_01_BMSR:
            return REG_PHY_STATUS;

        case PHY_REG_02_PHYSID1:
            return REG_PHY_ID_LOW;

        case PHY_REG_03_PHYSID2:
            return REG_PHY_ID_HIGH;

        case PHY_REG_04_ADVERTISE:
            return REG_PHY_AUTO_NEGOTIATION;

        case PHY_REG_05_LPA:
            return REG_PHY_REMOTE_CAPABILITY;
    }

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

static inline unsigned long ulReadMDIO( unsigned uAddress )
{
    uint16_t usPHYStatus;
    int ks8851_reg = ks8851_phy_reg( uAddress );

    if( ks8851_reg != 0 )
    {
        usPHYStatus = ksz8851_reg_read( ks8851_reg );
    }
    else
    {
        /* Other addresses not yet implemented. */
        usPHYStatus = 0;
    }

    return usPHYStatus;
}
/*-----------------------------------------------------------*/

static BaseType_t xGMACWaitLS( TickType_t xMaxTime )
{
    TickType_t xStartTime = xTaskGetTickCount();
    TickType_t xEndTime;
    BaseType_t xReturn;
    const TickType_t xShortTime = pdMS_TO_TICKS( 100UL );
    const uint32_t ulHz_Per_MHz = 1000000UL;

    for( ; ; )
    {
        xEndTime = xTaskGetTickCount();

        if( ( xEndTime - xStartTime ) > xMaxTime )
        {
            /* Waited more than xMaxTime, return. */
            xReturn = pdFALSE;
            break;
        }

        /* Check the link status again. */
        ulPHYLinkStatus = ulReadMDIO( PHY_REG_01_BMSR );

        if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
        {
            /* Link is up - return. */
            xReturn = pdTRUE;
            break;
        }

        /* Link is down - wait in the Blocked state for a short while (to allow
         * other tasks to execute) before checking again. */
        vTaskDelay( xShortTime );
    }

    FreeRTOS_printf( ( "xGMACWaitLS: %ld freq %lu Mz\n",
                       xReturn,
                       sysclk_get_cpu_hz() / ulHz_Per_MHz ) );

    return xReturn;
}
/*-----------------------------------------------------------*/

static void vPioSetPinHigh( uint32_t ul_pin )
{
    Pio * p_pio = ( Pio * ) ( ( uint32_t ) PIOA + ( PIO_DELTA * ( ul_pin >> 5 ) ) );

    /* Value to be driven on the I/O line: 1. */
    p_pio->PIO_SODR = 1 << ( ul_pin & 0x1F );
}

/**
 * \brief Handler for SPI interrupt.
 */
void SPI_Handler( void )
{
    BaseType_t xDoWakeup = pdFALSE;
    BaseType_t xKSZTaskWoken = pdFALSE;
    uint32_t ulCurrentSPIStatus;
    uint32_t ulEnabledSPIStatus;

    ulCurrentSPIStatus = spi_read_status( KSZ8851SNL_SPI );
    ulEnabledSPIStatus = spi_read_interrupt_mask( KSZ8851SNL_SPI );
    ulCurrentSPIStatus &= ulEnabledSPIStatus;
    spi_disable_interrupt( KSZ8851SNL_SPI, ulCurrentSPIStatus );

    switch( xMicrelDevice.ul_spi_pdc_status )
    {
        case SPI_PDC_RX_START:

            if( ( ulCurrentSPIStatus & SPI_SR_OVRES ) != 0 )
            {
                pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
                xMicrelDevice.ul_spi_pdc_status = SPI_PDC_RX_ERROR;
                xDoWakeup = pdTRUE;
            }
            else
            {
                if( ( ulCurrentSPIStatus & SPI_SR_RXBUFF ) != 0 )
                {
                    xMicrelDevice.ul_spi_pdc_status = SPI_PDC_RX_COMPLETE;
                    xDoWakeup = pdTRUE;
                }
            }

            break;

        case SPI_PDC_TX_START:

            /* Middle of TX. */
            if( ( ulCurrentSPIStatus & SPI_SR_OVRES ) != 0 )
            {
                pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
                xMicrelDevice.ul_spi_pdc_status = SPI_PDC_TX_ERROR;
                xDoWakeup = pdTRUE;
            }
            else
            {
                if( ( ulCurrentSPIStatus & SPI_SR_ENDRX ) != 0 )
                {
                    /* Enable RX complete interrupt. */
                    spi_enable_interrupt( KSZ8851SNL_SPI, SPI_IER_RXBUFF );
                }

                /* End of TX. */
                if( ( ulCurrentSPIStatus & SPI_END_OF_TX ) != 0 )
                {
                    xMicrelDevice.ul_spi_pdc_status = SPI_PDC_TX_COMPLETE;
                    xDoWakeup = pdTRUE;
                }
            }

            break;
    } /* switch( xMicrelDevice.ul_spi_pdc_status ) */

    if( xDoWakeup != pdFALSE )
    {
        if( xEMACTaskHandle != NULL )
        {
            vTaskNotifyGiveFromISR( xEMACTaskHandle, ( BaseType_t * ) &xKSZTaskWoken );
        }
    }
    else
    {
    }

    portEND_SWITCHING_ISR( xKSZTaskWoken );
}
/*-----------------------------------------------------------*/

static void INTN_Handler( uint32_t id,
                          uint32_t mask )
{
    BaseType_t xKSZTaskWoken = pdFALSE;

    if( ( id == INTN_ID ) &&
        ( mask == INTN_PIN_MSK ) )
    {
        /* Clear the PIO interrupt flags. */
        pio_get_interrupt_status( INTN_PIO );

        /* Set the INTN flag. */
        xMicrelDevice.ul_had_intn_interrupt++;

        if( xEMACTaskHandle != NULL )
        {
            vTaskNotifyGiveFromISR( xEMACTaskHandle, &( xKSZTaskWoken ) );
        }
    }

    portEND_SWITCHING_ISR( xKSZTaskWoken );
}
/*-----------------------------------------------------------*/

/**
 * \brief Populate the RX descriptor ring buffers with pbufs.
 *
 * \param p_ksz8851snl_dev Pointer to driver data structure.
 */
static void ksz8851snl_rx_populate_queue( void )
{
    uint32_t ul_index = 0;
    NetworkBufferDescriptor_t * pxNetworkBuffer;

    /* Set up the RX descriptors */
    for( ul_index = 0; ul_index < MICREL_RX_BUFFERS; ul_index++ )
    {
        if( xMicrelDevice.rx_buffers[ ul_index ] == NULL )
        {
            /* Allocate a new NetworkBufferDescriptor_t with the maximum size. */
            pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( ipconfigNETWORK_MTU + 36, 100 );

            if( pxNetworkBuffer == NULL )
            {
                FreeRTOS_printf( ( "ksz8851snl_rx_populate_queue: NetworkBufferDescriptor_t allocation failure\n" ) );
                configASSERT( 1 == 2 );
            }

            /* Make sure lwIP is well configured so one NetworkBufferDescriptor_t can contain the maximum packet size. */
            /*LWIP_ASSERT("ksz8851snl_rx_populate_queue: NetworkBufferDescriptor_t size too small!", pbuf_clen(pxNetworkBuffer) <= 1); */

            /* Save NetworkBufferDescriptor_t pointer to be sent to lwIP upper layer. */
            xMicrelDevice.rx_buffers[ ul_index ] = pxNetworkBuffer;
            /* Pass it to Micrel for reception. */
            xMicrelDevice.rx_ready[ ul_index ] = pdFALSE;
        }
    }
}

unsigned tx_space, wait_tx_space, tx_status, fhr_status;
unsigned rx_debug = 0;

/**
 * \brief Update Micrel state machine and perform required actions.
 *
 * \param netif the lwIP network interface structure for this ethernetif.
 */
static void ksz8851snl_update()
{
    uint16_t txmir = 0;

/* Check for free PDC. */
    switch( xMicrelDevice.ul_spi_pdc_status )
    {
        case SPI_PDC_TX_ERROR:
           {
               uint32_t ulValue;
               /*		/ * TX step11: end TX transfer. * / */
               gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );

               vTaskDelay( 2 );
               gpio_set_pin_low( KSZ8851SNL_CSN_GPIO );
               vTaskDelay( 1 );
               gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );
               vTaskDelay( 1 );

               /* Disable asynchronous transfer mode. */
               xMicrelDevice.ul_spi_pdc_status = SPI_PDC_IDLE;

               /* TX step12: disable TXQ write access. */
               ksz8851_reg_clrbits( REG_RXQ_CMD, RXQ_START );

               ulValue = ksz8851snl_reset_tx();

               xMicrelDevice.tx_space = ksz8851_reg_read( REG_TX_MEM_INFO ) & TX_MEM_AVAILABLE_MASK;

               FreeRTOS_printf( ( "SPI_PDC_TX_ERROR %02X\n", ulValue ) );
           }
           break;

        case SPI_PDC_RX_ERROR:
           {
               uint32_t ulValue;
               /* TX step11: end TX transfer. */
               gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );

               vTaskDelay( 2 );
               gpio_set_pin_low( KSZ8851SNL_CSN_GPIO );
               vTaskDelay( 1 );
               gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );
               vTaskDelay( 1 );

               /* Disable asynchronous transfer mode. */
               xMicrelDevice.ul_spi_pdc_status = SPI_PDC_IDLE;

               /* TX step12: disable TXQ write access. */
               ksz8851_reg_clrbits( REG_RXQ_CMD, RXQ_START );

               /*ulValue = ksz8851snl_reset_rx(); */
               ulValue = ksz8851snl_reinit();

               xGMACWaitLS( pdMS_TO_TICKS( 5000UL ) );

               FreeRTOS_printf( ( "SPI_PDC_RX_ERROR %02X\n", ulValue ) );
           }
           break;
    }

    switch( xMicrelDevice.ul_spi_pdc_status )
    {
        case SPI_PDC_IDLE:
           {
               int txTail = xMicrelDevice.us_tx_tail;

               /*
                * ========================== Handle RX ==========================
                */
               if( ( xMicrelDevice.ul_had_intn_interrupt != 0 ) || ( xMicrelDevice.us_pending_frame > 0 ) )
               {
                   int rxHead = xMicrelDevice.us_rx_head;
                   NetworkBufferDescriptor_t * pxNetworkBuffer;
                   xMicrelDevice.ul_had_intn_interrupt = 0;

                   if( xMicrelDevice.us_pending_frame == 0 )
                   {
                       uint16_t int_status;
                       /* RX step1: read interrupt status for INT_RX flag. */
                       int_status = ksz8851_reg_read( REG_INT_STATUS );


                       /* RX step2: disable all interrupts. */
                       ksz8851_reg_write( REG_INT_MASK, 0 );

                       /* RX step3: clear INT_RX flag. */
                       ksz8851_reg_setbits( REG_INT_STATUS, INT_RX );

                       /* RX step4-5: check for received frames. */
                       xMicrelDevice.us_pending_frame = ksz8851_reg_read( REG_RX_FRAME_CNT_THRES ) >> 8;

                       if( xMicrelDevice.us_pending_frame == 0 )
                       {
                           /* RX step24: enable INT_RX flag. */
                           ksz8851_reg_write( REG_INT_MASK, INT_RX );
                           return;
                       }
                   }

                   xMicrelDevice.ul_had_intn_interrupt = 0;

                   /* Now xMicrelDevice.us_pending_frame != 0 */

                   /* Don't break Micrel state machine, wait for a free descriptor first! */
                   if( xMicrelDevice.rx_ready[ rxHead ] != pdFALSE )
                   {
                       FreeRTOS_printf( ( "ksz8851snl_update: out of free descriptor! [tail=%u head=%u]\n",
                                          xMicrelDevice.us_rx_tail, rxHead ) );
                       return;
                   }

                   pxNetworkBuffer = xMicrelDevice.rx_buffers[ rxHead ];

                   if( pxNetworkBuffer == NULL )
                   {
                       ksz8851snl_rx_populate_queue();
                       FreeRTOS_printf( ( "ksz8851snl_update: no buffer set [head=%u]\n", rxHead ) );
                       return;
                   }

                   /* RX step6: get RX packet status. */
                   fhr_status = ksz8851_reg_read( REG_RX_FHR_STATUS );

                   if( ( ( fhr_status & RX_VALID ) == 0 ) || ( ( fhr_status & RX_ERRORS ) != 0 ) )
                   {
                       ksz8851_reg_setbits( REG_RXQ_CMD, RXQ_CMD_FREE_PACKET );
                       FreeRTOS_printf( ( "ksz8851snl_update: RX packet error!\n" ) );

                       /* RX step4-5: check for received frames. */
                       xMicrelDevice.us_pending_frame = ksz8851_reg_read( REG_RX_FRAME_CNT_THRES ) >> 8;

                       if( xMicrelDevice.us_pending_frame == 0 )
                       {
                           /* RX step24: enable INT_RX flag. */
                           ksz8851_reg_write( REG_INT_MASK, INT_RX );
                       }

                       ulISREvents |= EMAC_IF_ERR_EVENT;
                   }
                   else
                   {
                       size_t xLength;
                       /* RX step7: read frame length. */
                       xLength = ksz8851_reg_read( REG_RX_FHR_BYTE_CNT ) & RX_BYTE_CNT_MASK;

                       /* RX step8: Drop packet if len is invalid or no descriptor available. */
                       if( xLength == 0 )
                       {
                           ksz8851_reg_setbits( REG_RXQ_CMD, RXQ_CMD_FREE_PACKET );
                           FreeRTOS_printf( ( "ksz8851snl_update: RX bad len!\n" ) );
                           ulISREvents |= EMAC_IF_ERR_EVENT;
                       }
                       else
                       {
                           size_t xReadLength = xLength;

                           xMicrelDevice.ul_total_rx++;
                           /* RX step9: reset RX frame pointer. */
                           ksz8851_reg_clrbits( REG_RX_ADDR_PTR, ADDR_PTR_MASK );

                           /* RX step10: start RXQ read access. */
                           ksz8851_reg_setbits( REG_RXQ_CMD, RXQ_START );
                           /* RX step11-17: start asynchronous FIFO read operation. */
                           xMicrelDevice.ul_spi_pdc_status = SPI_PDC_RX_START;
                           gpio_set_pin_low( KSZ8851SNL_CSN_GPIO );

                           if( ( xReadLength & ( sizeof( size_t ) - 1 ) ) != 0 )
                           {
                               xReadLength = ( xReadLength | ( sizeof( size_t ) - 1 ) ) + 1;
                           }

                           /* Pass the buffer minus 2 bytes, see ksz8851snl.c: RXQ_TWOBYTE_OFFSET. */
                           ksz8851_fifo_read( pxNetworkBuffer->pucEthernetBuffer - 2, xReadLength );
                           /* Remove CRC and update buffer length. */
                           xLength -= 4;
                           pxNetworkBuffer->xDataLength = xLength;
                           /* Wait for SPI interrupt to set status 'SPI_PDC_RX_COMPLETE'. */
                       }
                   }

                   break;
               } /* ul_had_intn_interrupt || us_pending_frame */

               /*
                * ========================== Handle TX ==========================
                */

               /* Fetch next packet to be sent. */
               if( ( xMicrelDevice.tx_busy[ txTail ] != pdFALSE ) &&
                   ( xMicrelDevice.us_pending_frame == 0 ) &&
                   ( xMicrelDevice.ul_had_intn_interrupt == 0 ) )
               {
                   NetworkBufferDescriptor_t * pxNetworkBuffer = xMicrelDevice.tx_buffers[ txTail ];
                   size_t xLength = pxNetworkBuffer->xDataLength;
                   int iIndex = xLength;

                   xLength = 4 * ( ( xLength + 3 ) / 4 );

                   while( iIndex < ( int ) xLength )
                   {
                       pxNetworkBuffer->pucEthernetBuffer[ iIndex ] = '\0';
                       iIndex++;
                   }

                   pxNetworkBuffer->xDataLength = xLength;

                   /* TX step1: check if TXQ memory size is available for transmit. */
                   txmir = ksz8851_reg_read( REG_TX_MEM_INFO );
                   txmir = txmir & TX_MEM_AVAILABLE_MASK;

                   if( txmir < ( xLength + 8 ) )
                   {
                       if( wait_tx_space == pdFALSE )
                       {
                           tx_status = ksz8851_reg_read( REG_TX_STATUS );
                           fhr_status = ksz8851_reg_read( REG_RX_FHR_STATUS );
                           wait_tx_space = pdTRUE;
                       }

                       /*return; */
                       rx_debug = 1;
                       tx_space = txmir;
                   }
                   else
                   {
                       tx_space = txmir;

                       /* TX step2: disable all interrupts. */
                       ksz8851_reg_write( REG_INT_MASK, 0 );

                       xMicrelDevice.tx_space -= xLength;

                       /* TX step3: enable TXQ write access. */
                       ksz8851_reg_setbits( REG_RXQ_CMD, RXQ_START );
                       /* TX step4-8: perform FIFO write operation. */
                       xMicrelDevice.ul_spi_pdc_status = SPI_PDC_TX_START;
                       xMicrelDevice.tx_cur_buffer = pxNetworkBuffer;
                       /* Bring SPI SS low. */
                       gpio_set_pin_low( KSZ8851SNL_CSN_GPIO );
                       xMicrelDevice.ul_total_tx++;

                       ksz8851_fifo_write( pxNetworkBuffer->pucEthernetBuffer, xLength, xLength );
                   }
               }
           }
           break; /* SPI_PDC_IDLE */

        case SPI_PDC_RX_COMPLETE:
           {
               int rxHead = xMicrelDevice.us_rx_head;

               /* RX step18-19: pad with dummy data to keep dword alignment.
                * Packet lengths will be rounded up to a multiple of "sizeof size_t". */

               /* xLength = xMicrelDevice.rx_buffers[ rxHead ]->xDataLength & 3;
                * if( xLength != 0 )
                * {
                * ksz8851_fifo_dummy( 4 - xLength );
                * } */

               /* RX step20: end RX transfer. */
               gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );

               /* Disable asynchronous transfer mode. */
               xMicrelDevice.ul_spi_pdc_status = SPI_PDC_IDLE;

               /* RX step21: end RXQ read access. */
               ksz8851_reg_clrbits( REG_RXQ_CMD, RXQ_START );

               /* RX step22-23: update frame count to be read. */
               xMicrelDevice.us_pending_frame -= 1;

               /* RX step24: enable INT_RX flag if transfer complete. */
               if( xMicrelDevice.us_pending_frame == 0 )
               {
                   ksz8851_reg_write( REG_INT_MASK, INT_RX );
               }

               /* Mark descriptor ready to be read. */
               xMicrelDevice.rx_ready[ rxHead ] = pdTRUE;

               if( ++rxHead == MICREL_RX_BUFFERS )
               {
                   rxHead = 0;
               }

               xMicrelDevice.us_rx_head = rxHead;

               if( rx_debug != 0 )
               {
                   uint32_t txmir;
                   rx_debug = 0;
                   txmir = ksz8851_reg_read( REG_TX_MEM_INFO );
                   txmir = txmir & TX_MEM_AVAILABLE_MASK;
               }

               /* Tell prvEMACHandlerTask that RX packets are available. */
               ulISREvents |= EMAC_IF_RX_EVENT;
           } /* case SPI_PDC_RX_COMPLETE */
           break;

        case SPI_PDC_TX_COMPLETE:
           {
               int txTail = xMicrelDevice.us_tx_tail;
               NetworkBufferDescriptor_t * pxNetworkBuffer = xMicrelDevice.tx_buffers[ txTail ];

               size_t xLength;
               /* TX step9-10: pad with dummy data to keep dword alignment. */
               /* Not necessary: length is already a multiple of 4. */
               xLength = pxNetworkBuffer->xDataLength & 3;

               if( xLength != 0 )
               {
/*				ksz8851_fifo_dummy( 4 - xLength ); */
               }

/*			/ * TX step11: end TX transfer. * / */
               gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );

               /* Disable asynchronous transfer mode. */
               xMicrelDevice.ul_spi_pdc_status = SPI_PDC_IDLE;

               /* TX step12: disable TXQ write access. */
               ksz8851_reg_clrbits( REG_RXQ_CMD, RXQ_START );

               xMicrelDevice.tx_space = ksz8851_reg_read( REG_TX_MEM_INFO ) & TX_MEM_AVAILABLE_MASK;

               /* TX step12.1: enqueue frame in TXQ. */
               ksz8851_reg_setbits( REG_TXQ_CMD, TXQ_ENQUEUE );

               /* RX step13: enable INT_RX flag. */
               /* ksz8851_reg_write( REG_INT_MASK, INT_RX ); */
               /* Buffer sent, free the corresponding buffer and mark descriptor as owned by software. */
               vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );

               xMicrelDevice.tx_buffers[ txTail ] = NULL;
               xMicrelDevice.tx_busy[ txTail ] = pdFALSE;

               if( ++txTail == MICREL_TX_BUFFERS )
               {
                   txTail = 0;
               }

               xMicrelDevice.us_tx_tail = txTail;

               /* Experiment. */
               /*xMicrelDevice.ul_had_intn_interrupt = 1; */
               if( xTransmitHandle != NULL )
               {
                   xTaskNotifyGive( xTransmitHandle );
               }

               /* moved downward */
               /* RX step13: enable INT_RX flag. */
               ksz8851_reg_write( REG_INT_MASK, INT_RX );
               /* Prevent the EMAC task from sleeping a single time. */
               ulISREvents |= EMAC_IF_TX_EVENT;
           } /* case SPI_PDC_TX_COMPLETE */
           break;
    }        /* switch( xMicrelDevice.ul_spi_pdc_status ) */
}

/**
 * \brief Set up the RX descriptor ring buffers.
 *
 * This function sets up the descriptor list used for RX packets.
 *
 */
static void ksz8851snl_rx_init()
{
    uint32_t ul_index = 0;

    /* Init pointer index. */
    xMicrelDevice.us_rx_head = 0;
    xMicrelDevice.us_rx_tail = 0;

    /* Set up the RX descriptors. */
    for( ul_index = 0; ul_index < MICREL_RX_BUFFERS; ul_index++ )
    {
        xMicrelDevice.rx_buffers[ ul_index ] = NULL;
        xMicrelDevice.rx_ready[ ul_index ] = pdFALSE;
    }

    /* Build RX buffer and descriptors. */
    ksz8851snl_rx_populate_queue();
}

/**
 * \brief Set up the TX descriptor ring buffers.
 *
 * This function sets up the descriptor list used for TX packets.
 *
 */
static void ksz8851snl_tx_init()
{
    uint32_t ul_index = 0;

    /* Init TX index pointer. */
    xMicrelDevice.us_tx_head = 0;
    xMicrelDevice.us_tx_tail = 0;

    /* Set up the TX descriptors */
    for( ul_index = 0; ul_index < MICREL_TX_BUFFERS; ul_index++ )
    {
        xMicrelDevice.tx_busy[ ul_index ] = pdFALSE;
    }

    xMicrelDevice.tx_space = 6144;
}

/**
 * \brief Initialize ksz8851snl ethernet controller.
 *
 * \note Called from ethernetif_init().
 *
 * \param netif the lwIP network interface structure for this ethernetif.
 */
static void ksz8851snl_low_level_init( void )
{
    ksz8851snl_rx_init();
    ksz8851snl_tx_init();

    /* Enable NVIC interrupts. */
    NVIC_SetPriority( SPI_IRQn, INT_PRIORITY_SPI );
    NVIC_EnableIRQ( SPI_IRQn );

    /* Initialize SPI link. */
    if( ksz8851snl_init() < 0 )
    {
        FreeRTOS_printf( ( "ksz8851snl_low_level_init: failed to initialize the Micrel driver!\n" ) );
        configASSERT( ipFALSE_BOOL );
    }

    memset( xMicrelDevice.pusHashTable, 255, sizeof( xMicrelDevice.pusHashTable ) );
    ksz8851_reg_write( REG_MAC_HASH_0, FreeRTOS_htons( xMicrelDevice.pusHashTable[ 0 ] ) );
    ksz8851_reg_write( REG_MAC_HASH_2, FreeRTOS_htons( xMicrelDevice.pusHashTable[ 1 ] ) );
    ksz8851_reg_write( REG_MAC_HASH_4, FreeRTOS_htons( xMicrelDevice.pusHashTable[ 2 ] ) );
    ksz8851_reg_write( REG_MAC_HASH_6, FreeRTOS_htons( xMicrelDevice.pusHashTable[ 3 ] ) );

    /* Initialize interrupt line INTN. */
    configure_intn( INTN_Handler );
}

/**
 * \brief Use pre-allocated pbuf as DMA source and return the incoming packet.
 *
 * \param netif the lwIP network interface structure for this ethernetif.
 *
 * \return a pbuf filled with the received packet (including MAC header).
 * 0 on memory error.
 */
static NetworkBufferDescriptor_t * ksz8851snl_low_level_input( void )
{
    NetworkBufferDescriptor_t * pxNetworkBuffer = NULL;
    int rxTail = xMicrelDevice.us_rx_tail;

    /* Check that descriptor is owned by software (ie packet received). */
    if( xMicrelDevice.rx_ready[ rxTail ] != pdFALSE )
    {
        /* Fetch pre-allocated buffer */
        pxNetworkBuffer = xMicrelDevice.rx_buffers[ rxTail ];

        /* Remove this pbuf from its descriptor. */
        xMicrelDevice.rx_buffers[ rxTail ] = NULL;

        /* Clears rx_ready and sets rx_buffers. */
        ksz8851snl_rx_populate_queue();

        if( ++rxTail == MICREL_RX_BUFFERS )
        {
            rxTail = 0;
        }

        xMicrelDevice.us_rx_tail = rxTail;
    }

    return pxNetworkBuffer;
}
/*-----------------------------------------------------------*/

static uint32_t prvEMACRxPoll( void )
{
    NetworkBufferDescriptor_t * pxNetworkBuffer;
    IPStackEvent_t xRxEvent = { eNetworkRxEvent, NULL };
    uint32_t ulReturnValue = 0;

    for( ; ; )
    {
        /* Only for logging. */
        int rxTail = xMicrelDevice.us_rx_tail;
        EthernetHeader_t * pxEthernetHeader;

        pxNetworkBuffer = ksz8851snl_low_level_input();

        if( pxNetworkBuffer == NULL )
        {
            break;
        }

        pxEthernetHeader = ( EthernetHeader_t * ) ( pxNetworkBuffer->pucEthernetBuffer );

        if( ( pxEthernetHeader->usFrameType != ipIPv4_FRAME_TYPE ) &&
            ( pxEthernetHeader->usFrameType != ipARP_FRAME_TYPE ) )
        {
            FreeRTOS_printf( ( "Frame type %02X received\n", pxEthernetHeader->usFrameType ) );
        }

        ulReturnValue++;

        xRxEvent.pvData = ( void * ) pxNetworkBuffer;

        /* Send the descriptor to the IP task for processing. */
        if( xSendEventStructToIPTask( &xRxEvent, 100UL ) != pdTRUE )
        {
            vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer );
            iptraceETHERNET_RX_EVENT_LOST();
            FreeRTOS_printf( ( "prvEMACRxPoll: Can not queue return packet!\n" ) );
        }
    }

    return ulReturnValue;
}
/*-----------------------------------------------------------*/

static void prvEMACHandlerTask( void * pvParameters )
{
    TimeOut_t xPhyTime;
    TickType_t xPhyRemTime;
    TickType_t xLoggingTime;
    UBaseType_t uxLastMinBufferCount = 0;
    UBaseType_t uxCurrentCount;
    BaseType_t xResult = 0;
    uint32_t xStatus;
    const TickType_t ulMaxBlockTime = pdMS_TO_TICKS( EMAC_MAX_BLOCK_TIME_MS );

    #if ( ipconfigCHECK_IP_QUEUE_SPACE != 0 )
        UBaseType_t uxLastMinQueueSpace = 0;
    #endif

    /* Remove compiler warnings about unused parameters. */
    ( void ) pvParameters;

    configASSERT( xEMACTaskHandle != NULL );

    vTaskSetTimeOutState( &xPhyTime );
    xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_LOW_CHECK_TIME_MS );
    xLoggingTime = xTaskGetTickCount();

    for( ; ; )
    {
        uxCurrentCount = uxGetMinimumFreeNetworkBuffers();

        if( uxLastMinBufferCount != uxCurrentCount )
        {
            /* The logging produced below may be helpful
             * while tuning +TCP: see how many buffers are in use. */
            uxLastMinBufferCount = uxCurrentCount;
            FreeRTOS_printf( ( "Network buffers: %lu lowest %lu\n",
                               uxGetNumberOfFreeNetworkBuffers(), uxCurrentCount ) );
        }

        #if ( ipconfigCHECK_IP_QUEUE_SPACE != 0 )
        {
            uxCurrentCount = uxGetMinimumIPQueueSpace();

            if( uxLastMinQueueSpace != uxCurrentCount )
            {
                /* The logging produced below may be helpful
                 * while tuning +TCP: see how many buffers are in use. */
                uxLastMinQueueSpace = uxCurrentCount;
                FreeRTOS_printf( ( "Queue space: lowest %lu\n", uxCurrentCount ) );
            }
        }
        #endif /* ipconfigCHECK_IP_QUEUE_SPACE */

        /* Run the state-machine of the ksz8851 driver. */
        ksz8851snl_update();

        if( ( ulISREvents & EMAC_IF_ALL_EVENT ) == 0 )
        {
            /* No events to process now, wait for the next. */
            ulTaskNotifyTake( pdTRUE, ulMaxBlockTime );
        }

        if( ( xTaskGetTickCount() - xLoggingTime ) > 10000 )
        {
            xLoggingTime += 10000;
            FreeRTOS_printf( ( "Now Tx/Rx %7d /%7d\n",
                               xMicrelDevice.ul_total_tx, xMicrelDevice.ul_total_rx ) );
        }

        if( ( ulISREvents & EMAC_IF_RX_EVENT ) != 0 )
        {
            ulISREvents &= ~EMAC_IF_RX_EVENT;

            /* Wait for the EMAC interrupt to indicate that another packet has been
             * received. */
            xResult = prvEMACRxPoll();
        }

        if( ( ulISREvents & EMAC_IF_TX_EVENT ) != 0 )
        {
            /* Future extension: code to release TX buffers if zero-copy is used. */
            ulISREvents &= ~EMAC_IF_TX_EVENT;
        }

        if( ( ulISREvents & EMAC_IF_ERR_EVENT ) != 0 )
        {
            /* Future extension: logging about errors that occurred. */
            ulISREvents &= ~EMAC_IF_ERR_EVENT;
        }

        if( xResult > 0 )
        {
            /* As long as packets are being received, assume that
             * the Link Status is high. */
            ulPHYLinkStatus |= BMSR_LINK_STATUS;

            /* A packet was received. No need to check for the PHY status now,
             * but set a timer to check it later on. */
            vTaskSetTimeOutState( &xPhyTime );
            xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_HIGH_CHECK_TIME_MS );
            xResult = 0;
        }
        else if( ( xTaskCheckForTimeOut( &xPhyTime, &xPhyRemTime ) != pdFALSE ) &&
                 ( xMicrelDevice.ul_spi_pdc_status == SPI_PDC_IDLE ) )
        {
            /* Check the link status again. */
            xStatus = ulReadMDIO( PHY_REG_01_BMSR );

            if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != ( xStatus & BMSR_LINK_STATUS ) )
            {
                ulPHYLinkStatus = xStatus;
                FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d\n", ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 ) );
            }

            vTaskSetTimeOutState( &xPhyTime );

            if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 )
            {
                xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_HIGH_CHECK_TIME_MS );
            }
            else
            {
                xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_LOW_CHECK_TIME_MS );
            }
        }
    }
}
/*-----------------------------------------------------------*/
