/**
 *
 * \file
 *
 * \brief KS8851SNL driver for SAM.
 *
 * Copyright (c) 2013-2015 Atmel Corporation. All rights reserved.
 *
 * \asf_license_start
 *
 * \page License
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. The name of Atmel may not be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 4. This software may only be redistributed and used in connection with an
 *    Atmel microcontroller product.
 *
 * THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
 * EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR
 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * \asf_license_stop
 *
 */

/*
 * Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
 */

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

#include "spi_master.h"
#include "ksz8851snl.h"
#include "ksz8851snl_reg.h"
#include "delay.h"
#include "pio.h"
#include "pio_handler.h"
#include "pdc.h"
#include "conf_eth.h"

/* Clock polarity. */
#define SPI_CLK_POLARITY    0

/* Clock phase. */
#define SPI_CLK_PHASE       1

/* SPI PDC register base. */
Pdc * g_p_spi_pdc = 0;

int lUDPLoggingPrintf( const char * pcFormatString,
                       ... );

/* Temporary buffer for PDC reception. */
uint8_t tmpbuf[ 1536 ] __attribute__( ( aligned( 16 ) ) );

union
{
    uint64_t ul[ 2 ];
    uint8_t uc[ 16 ];
}
cmdBuf, respBuf;

void dbg_add_line( const char * pcFormat,
                   ... );

static void spi_clear_ovres( void );

/**
 * \brief Read register content, set bitmask and write back to register.
 *
 * \param reg the register address to modify.
 * \param bits_to_set bitmask to apply.
 */
void ksz8851_reg_setbits( uint16_t reg,
                          uint16_t bits_to_set )
{
    uint16_t temp;

    temp = ksz8851_reg_read( reg );
    temp |= bits_to_set;
    ksz8851_reg_write( reg, temp );
}

/**
 * \brief Read register content, clear bitmask and write back to register.
 *
 * \param reg the register address to modify.
 * \param bits_to_set bitmask to apply.
 */
void ksz8851_reg_clrbits( uint16_t reg,
                          uint16_t bits_to_clr )
{
    uint16_t temp;

    temp = ksz8851_reg_read( reg );
    temp &= ~( uint32_t ) bits_to_clr;
    ksz8851_reg_write( reg, temp );
}

/**
 * \brief Configure the INTN interrupt.
 */
void configure_intn( void ( *p_handler )( uint32_t, uint32_t ) )
{
/*	gpio_configure_pin(KSZ8851SNL_INTN_GPIO, PIO_INPUT); */
/*	pio_set_input(PIOA, PIO_PA11_IDX, PIO_PULLUP); */

    /* Configure PIO clock. */
    pmc_enable_periph_clk( INTN_ID );

    /* Adjust PIO debounce filter parameters, uses 10 Hz filter. */
    pio_set_debounce_filter( INTN_PIO, INTN_PIN_MSK, 10 );

    /* Initialize PIO interrupt handlers, see PIO definition in board.h. */
    pio_handler_set( INTN_PIO, INTN_ID, INTN_PIN_MSK,
                     INTN_ATTR, p_handler );

    /* Enable NVIC interrupts. */
    NVIC_SetPriority( INTN_IRQn, INT_PRIORITY_PIO );
    NVIC_EnableIRQ( ( IRQn_Type ) INTN_ID );

    /* Enable PIO interrupts. */
    pio_enable_interrupt( INTN_PIO, INTN_PIN_MSK );
}

/**
 * \brief Read a register value.
 *
 * \param reg the register address to modify.
 *
 * \return the register value.
 */
uint16_t ksz8851_reg_read( uint16_t reg )
{
    pdc_packet_t g_pdc_spi_tx_packet;
    pdc_packet_t g_pdc_spi_rx_packet;
    uint16_t cmd = 0;
    uint16_t res = 0;
    int iTryCount = 3;

    while( iTryCount-- > 0 )
    {
        uint32_t ulStatus;

        spi_clear_ovres();
        /* Move register address to cmd bits 9-2, make 32-bit address. */
        cmd = ( reg << 2 ) & REG_ADDR_MASK;

        /* Last 2 bits still under "don't care bits" handled with byte enable. */
        /* Select byte enable for command. */
        if( reg & 2 )
        {
            /* Odd word address writes bytes 2 and 3 */
            cmd |= ( 0xc << 10 );
        }
        else
        {
            /* Even word address write bytes 0 and 1 */
            cmd |= ( 0x3 << 10 );
        }

        /* Add command read code. */
        cmd |= CMD_READ;
        cmdBuf.uc[ 0 ] = cmd >> 8;
        cmdBuf.uc[ 1 ] = cmd & 0xff;
        cmdBuf.uc[ 2 ] = CONFIG_SPI_MASTER_DUMMY;
        cmdBuf.uc[ 3 ] = CONFIG_SPI_MASTER_DUMMY;

        /* Prepare PDC transfer. */
        g_pdc_spi_tx_packet.ul_addr = ( uint32_t ) cmdBuf.uc;
        g_pdc_spi_tx_packet.ul_size = 4;
        g_pdc_spi_rx_packet.ul_addr = ( uint32_t ) tmpbuf;
        g_pdc_spi_rx_packet.ul_size = 4;
        pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
        pdc_tx_init( g_p_spi_pdc, &g_pdc_spi_tx_packet, NULL );
        pdc_rx_init( g_p_spi_pdc, &g_pdc_spi_rx_packet, NULL );
        gpio_set_pin_low( KSZ8851SNL_CSN_GPIO );

        spi_disable_interrupt( KSZ8851SNL_SPI, ~0ul );
        pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN );

        for( ; ; )
        {
            ulStatus = spi_read_status( KSZ8851SNL_SPI );

            if( ( ulStatus & ( SPI_SR_OVRES | SPI_SR_ENDRX ) ) != 0 )
            {
                break;
            }
        }

        gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );

        if( ( ulStatus & SPI_SR_OVRES ) == 0 )
        {
            break;
        }

        pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
        lUDPLoggingPrintf( "ksz8851_reg_read: SPI_SR_OVRES\n" );
    }

    res = ( tmpbuf[ 3 ] << 8 ) | tmpbuf[ 2 ];
    return res;
}

/**
 * \brief Write a register value.
 *
 * \param reg the register address to modify.
 * \param wrdata the new register value.
 */
void ksz8851_reg_write( uint16_t reg,
                        uint16_t wrdata )
{
    pdc_packet_t g_pdc_spi_tx_packet;
    pdc_packet_t g_pdc_spi_rx_packet;
    uint16_t cmd = 0;
    int iTryCount = 3;

    while( iTryCount-- > 0 )
    {
        uint32_t ulStatus;


        spi_clear_ovres();
        /* Move register address to cmd bits 9-2, make 32-bit address. */
        cmd = ( reg << 2 ) & REG_ADDR_MASK;

        /* Last 2 bits still under "don't care bits" handled with byte enable. */
        /* Select byte enable for command. */
        if( reg & 2 )
        {
            /* Odd word address writes bytes 2 and 3 */
            cmd |= ( 0xc << 10 );
        }
        else
        {
            /* Even word address write bytes 0 and 1 */
            cmd |= ( 0x3 << 10 );
        }

        /* Add command write code. */
        cmd |= CMD_WRITE;
        cmdBuf.uc[ 0 ] = cmd >> 8;
        cmdBuf.uc[ 1 ] = cmd & 0xff;
        cmdBuf.uc[ 2 ] = wrdata & 0xff;
        cmdBuf.uc[ 3 ] = wrdata >> 8;

        /* Prepare PDC transfer. */
        g_pdc_spi_tx_packet.ul_addr = ( uint32_t ) cmdBuf.uc;
        g_pdc_spi_tx_packet.ul_size = 4;
        g_pdc_spi_rx_packet.ul_addr = ( uint32_t ) tmpbuf;
        g_pdc_spi_rx_packet.ul_size = 4;
        pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
        pdc_tx_init( g_p_spi_pdc, &g_pdc_spi_tx_packet, NULL );
        pdc_rx_init( g_p_spi_pdc, &g_pdc_spi_rx_packet, NULL );
        gpio_set_pin_low( KSZ8851SNL_CSN_GPIO );

        spi_disable_interrupt( KSZ8851SNL_SPI, ~0ul );

        pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN );

        for( ; ; )
        {
            ulStatus = spi_read_status( KSZ8851SNL_SPI );

            if( ( ulStatus & ( SPI_SR_OVRES | SPI_SR_ENDRX ) ) != 0 )
            {
                break;
            }
        }

        gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );

        if( ( ulStatus & SPI_SR_OVRES ) == 0 )
        {
            break;
        }

        pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
        lUDPLoggingPrintf( "ksz8851_reg_write: SPI_SR_OVRES\n" );
    }
}

static void spi_clear_ovres( void )
{
    volatile uint32_t rc;

    rc = KSZ8851SNL_SPI->SPI_RDR;

    spi_read_status( KSZ8851SNL_SPI );
}

/**
 * \brief Read internal fifo buffer.
 *
 * \param buf the buffer to store the data from the fifo buffer.
 * \param len the amount of data to read.
 */
void ksz8851_fifo_read( uint8_t * buf,
                        uint32_t len )
{
    pdc_packet_t g_pdc_spi_tx_packet;
    pdc_packet_t g_pdc_spi_rx_packet;
    pdc_packet_t g_pdc_spi_tx_npacket;
    pdc_packet_t g_pdc_spi_rx_npacket;

    memset( cmdBuf.uc, '\0', sizeof cmdBuf );
    cmdBuf.uc[ 0 ] = FIFO_READ;
    spi_clear_ovres();

    /* Prepare PDC transfer. */
    g_pdc_spi_tx_packet.ul_addr = ( uint32_t ) cmdBuf.uc;
    g_pdc_spi_tx_packet.ul_size = 9;
    g_pdc_spi_rx_packet.ul_addr = ( uint32_t ) respBuf.uc;
    g_pdc_spi_rx_packet.ul_size = 9;

    g_pdc_spi_tx_npacket.ul_addr = ( uint32_t ) buf;
    g_pdc_spi_tx_npacket.ul_size = len;
    g_pdc_spi_rx_npacket.ul_addr = ( uint32_t ) buf;
    g_pdc_spi_rx_npacket.ul_size = len;
    pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
    pdc_tx_init( g_p_spi_pdc, &g_pdc_spi_tx_packet, &g_pdc_spi_tx_npacket );
    pdc_rx_init( g_p_spi_pdc, &g_pdc_spi_rx_packet, &g_pdc_spi_rx_npacket );

    spi_enable_interrupt( KSZ8851SNL_SPI, SPI_IER_RXBUFF | SPI_IER_OVRES );

    pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN );
}

/**
 * \brief Write internal fifo buffer.
 *
 * \param buf the buffer to send to the fifo buffer.
 * \param ulActualLength the total amount of data to write.
 * \param ulFIFOLength the size of the first pbuf to write from the pbuf chain.
 */
void ksz8851_fifo_write( uint8_t * buf,
                         uint32_t ulActualLength,
                         uint32_t ulFIFOLength )
{
    static uint8_t frameID = 0;

    pdc_packet_t g_pdc_spi_tx_packet;
    pdc_packet_t g_pdc_spi_rx_packet;
    pdc_packet_t g_pdc_spi_tx_npacket;
    pdc_packet_t g_pdc_spi_rx_npacket;

    /* Prepare control word and byte count. */
    cmdBuf.uc[ 0 ] = FIFO_WRITE;
    cmdBuf.uc[ 1 ] = frameID++ & 0x3f;
    cmdBuf.uc[ 2 ] = 0;
    cmdBuf.uc[ 3 ] = ulActualLength & 0xff;
    cmdBuf.uc[ 4 ] = ulActualLength >> 8;

    spi_clear_ovres();

    /* Prepare PDC transfer. */
    g_pdc_spi_tx_packet.ul_addr = ( uint32_t ) cmdBuf.uc;
    g_pdc_spi_tx_packet.ul_size = 5;

    g_pdc_spi_rx_packet.ul_addr = ( uint32_t ) respBuf.uc;
    g_pdc_spi_rx_packet.ul_size = 5;

    g_pdc_spi_tx_npacket.ul_addr = ( uint32_t ) buf;
    g_pdc_spi_tx_npacket.ul_size = ulFIFOLength;

    g_pdc_spi_rx_npacket.ul_addr = ( uint32_t ) tmpbuf;
    g_pdc_spi_rx_npacket.ul_size = ulFIFOLength;

    pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
    pdc_tx_init( g_p_spi_pdc, &g_pdc_spi_tx_packet, &g_pdc_spi_tx_npacket );
    #if ( TX_USES_RECV == 1 )
        pdc_rx_init( g_p_spi_pdc, &g_pdc_spi_rx_packet, &g_pdc_spi_rx_npacket );
        spi_enable_interrupt( KSZ8851SNL_SPI, SPI_IER_ENDRX | SPI_IER_OVRES );
        pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN );
    #else
        spi_enable_interrupt( KSZ8851SNL_SPI, SPI_SR_TXBUFE | SPI_IER_OVRES );
        pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_TXTEN );
    #endif
}

/**
 * \brief Write dummy data to the internal fifo buffer.
 *
 * \param len the amount of dummy data to write.
 */
void ksz8851_fifo_dummy( uint32_t len )
{
    pdc_packet_t g_pdc_spi_tx_packet;
    pdc_packet_t g_pdc_spi_rx_packet;

    /* Prepare PDC transfer. */
    g_pdc_spi_tx_packet.ul_addr = ( uint32_t ) tmpbuf;
    g_pdc_spi_tx_packet.ul_size = len;
    g_pdc_spi_rx_packet.ul_addr = ( uint32_t ) tmpbuf;
    g_pdc_spi_rx_packet.ul_size = len;
    pdc_disable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTDIS | PERIPH_PTCR_TXTDIS );
    pdc_tx_init( g_p_spi_pdc, &g_pdc_spi_tx_packet, NULL );
    pdc_rx_init( g_p_spi_pdc, &g_pdc_spi_rx_packet, NULL );
    pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN );

    while( !( spi_read_status( KSZ8851SNL_SPI ) & SPI_SR_ENDRX ) )
    {
    }
}

void ksz8851snl_set_registers( void )
{
    /* Init step2-4: write QMU MAC address (low, middle then high). */
    ksz8851_reg_write( REG_MAC_ADDR_0, ( ETHERNET_CONF_ETHADDR4 << 8 ) | ETHERNET_CONF_ETHADDR5 );
    ksz8851_reg_write( REG_MAC_ADDR_2, ( ETHERNET_CONF_ETHADDR2 << 8 ) | ETHERNET_CONF_ETHADDR3 );
    ksz8851_reg_write( REG_MAC_ADDR_4, ( ETHERNET_CONF_ETHADDR0 << 8 ) | ETHERNET_CONF_ETHADDR1 );

    /* Init step5: enable QMU Transmit Frame Data Pointer Auto Increment. */
    ksz8851_reg_write( REG_TX_ADDR_PTR, ADDR_PTR_AUTO_INC );

    /* Init step6: configure QMU transmit control register. */
    ksz8851_reg_write( REG_TX_CTRL,
                       TX_CTRL_ICMP_CHECKSUM |
                       TX_CTRL_UDP_CHECKSUM |
                       TX_CTRL_TCP_CHECKSUM |
                       TX_CTRL_IP_CHECKSUM |
                       TX_CTRL_FLOW_ENABLE |
                       TX_CTRL_PAD_ENABLE |
                       TX_CTRL_CRC_ENABLE
                       );

    /* Init step7: enable QMU Receive Frame Data Pointer Auto Increment. */
    ksz8851_reg_write( REG_RX_ADDR_PTR, ADDR_PTR_AUTO_INC );

    /* Init step8: configure QMU Receive Frame Threshold for one frame. */
    ksz8851_reg_write( REG_RX_FRAME_CNT_THRES, 1 );

    /* Init step9: configure QMU receive control register1. */
    ksz8851_reg_write( REG_RX_CTRL1,
                       RX_CTRL_UDP_CHECKSUM |
                       RX_CTRL_TCP_CHECKSUM |
                       RX_CTRL_IP_CHECKSUM |
                       RX_CTRL_MAC_FILTER |
                       RX_CTRL_FLOW_ENABLE |
                       RX_CTRL_BROADCAST |
                       RX_CTRL_ALL_MULTICAST |
                       RX_CTRL_UNICAST );
/*	ksz8851_reg_write(REG_RX_CTRL1, */
/*			RX_CTRL_UDP_CHECKSUM | */
/*			RX_CTRL_TCP_CHECKSUM | */
/*			RX_CTRL_IP_CHECKSUM | */
/*			RX_CTRL_FLOW_ENABLE | */
/*			RX_CTRL_PROMISCUOUS); */

    ksz8851_reg_write( REG_RX_CTRL2,
                       RX_CTRL_IPV6_UDP_NOCHECKSUM |
                       RX_CTRL_UDP_LITE_CHECKSUM |
                       RX_CTRL_ICMP_CHECKSUM |
                       RX_CTRL_BURST_LEN_FRAME );


/*#define   RXQ_TWOBYTE_OFFSET          (0x0200)    / * Enable adding 2-byte before frame header for IP aligned with DWORD * / */
    #warning Remember to try the above option to get a 2-byte offset

    /* Init step11: configure QMU receive queue: trigger INT and auto-dequeue frame. */
    ksz8851_reg_write( REG_RXQ_CMD, RXQ_CMD_CNTL | RXQ_TWOBYTE_OFFSET );

    /* Init step12: adjust SPI data output delay. */
    ksz8851_reg_write( REG_BUS_CLOCK_CTRL, BUS_CLOCK_166 | BUS_CLOCK_DIVIDEDBY_1 );

    /* Init step13: restart auto-negotiation. */
    ksz8851_reg_setbits( REG_PORT_CTRL, PORT_AUTO_NEG_RESTART );

    /* Init step13.1: force link in half duplex if auto-negotiation failed. */
    if( ( ksz8851_reg_read( REG_PORT_CTRL ) & PORT_AUTO_NEG_RESTART ) != PORT_AUTO_NEG_RESTART )
    {
        ksz8851_reg_clrbits( REG_PORT_CTRL, PORT_FORCE_FULL_DUPLEX );
    }

    /* Init step14: clear interrupt status. */
    ksz8851_reg_write( REG_INT_STATUS, 0xFFFF );

    /* Init step15: set interrupt mask. */
    ksz8851_reg_write( REG_INT_MASK, INT_RX );

    /* Init step16: enable QMU Transmit. */
    ksz8851_reg_setbits( REG_TX_CTRL, TX_CTRL_ENABLE );

    /* Init step17: enable QMU Receive. */
    ksz8851_reg_setbits( REG_RX_CTRL1, RX_CTRL_ENABLE );
}

/**
 * \brief KSZ8851SNL initialization function.
 *
 * \return 0 on success, 1 on communication error.
 */
uint32_t ksz8851snl_init( void )
{
    uint32_t count = 10;
    uint16_t dev_id = 0;
    uint8_t id_ok = 0;

    /* Configure the SPI peripheral. */
    spi_enable_clock( KSZ8851SNL_SPI );
    spi_disable( KSZ8851SNL_SPI );
    spi_reset( KSZ8851SNL_SPI );
    spi_set_master_mode( KSZ8851SNL_SPI );
    spi_disable_mode_fault_detect( KSZ8851SNL_SPI );
    spi_set_peripheral_chip_select_value( KSZ8851SNL_SPI, ~( uint32_t ) ( 1UL << KSZ8851SNL_CS_PIN ) );
    spi_set_fixed_peripheral_select( KSZ8851SNL_SPI );
/*spi_disable_peripheral_select_decode(KSZ8851SNL_SPI); */

    spi_set_clock_polarity( KSZ8851SNL_SPI, KSZ8851SNL_CS_PIN, SPI_CLK_POLARITY );
    spi_set_clock_phase( KSZ8851SNL_SPI, KSZ8851SNL_CS_PIN, SPI_CLK_PHASE );
    spi_set_bits_per_transfer( KSZ8851SNL_SPI, KSZ8851SNL_CS_PIN,
                               SPI_CSR_BITS_8_BIT );
    spi_set_baudrate_div( KSZ8851SNL_SPI, KSZ8851SNL_CS_PIN, ( sysclk_get_cpu_hz() / KSZ8851SNL_CLOCK_SPEED ) );
/*	spi_set_transfer_delay(KSZ8851SNL_SPI, KSZ8851SNL_CS_PIN, CONFIG_SPI_MASTER_DELAY_BS, */
/*			CONFIG_SPI_MASTER_DELAY_BCT); */


    spi_set_transfer_delay( KSZ8851SNL_SPI, KSZ8851SNL_CS_PIN, 0, 0 );

    spi_enable( KSZ8851SNL_SPI );

    /* Get pointer to UART PDC register base. */
    g_p_spi_pdc = spi_get_pdc_base( KSZ8851SNL_SPI );
    pdc_enable_transfer( g_p_spi_pdc, PERIPH_PTCR_RXTEN | PERIPH_PTCR_TXTEN );

    /* Control RSTN and CSN pin from the driver. */
    gpio_configure_pin( KSZ8851SNL_CSN_GPIO, KSZ8851SNL_CSN_FLAGS );
    gpio_set_pin_high( KSZ8851SNL_CSN_GPIO );
    gpio_configure_pin( KSZ8851SNL_RSTN_GPIO, KSZ8851SNL_RSTN_FLAGS );

    /* Reset the Micrel in a proper state. */
    while( count-- )
    {
        /* Perform hardware reset with respect to the reset timing from the datasheet. */
        gpio_set_pin_low( KSZ8851SNL_RSTN_GPIO );
        vTaskDelay( 2 );
        gpio_set_pin_high( KSZ8851SNL_RSTN_GPIO );
        vTaskDelay( 2 );

        /* Init step1: read chip ID. */
        dev_id = ksz8851_reg_read( REG_CHIP_ID );

        if( ( dev_id & 0xFFF0 ) == CHIP_ID_8851_16 )
        {
            id_ok = 1;
            break;
        }
    }

    if( id_ok != 0 )
    {
        ksz8851snl_set_registers();
    }

    return id_ok ? 1 : -1;
}

uint32_t ksz8851snl_reinit( void )
{
    uint32_t count = 10;
    uint16_t dev_id = 0;
    uint8_t id_ok = 0;

    /* Reset the Micrel in a proper state. */
    while( count-- )
    {
        /* Perform hardware reset with respect to the reset timing from the datasheet. */
        gpio_set_pin_low( KSZ8851SNL_RSTN_GPIO );
        vTaskDelay( 2 );
        gpio_set_pin_high( KSZ8851SNL_RSTN_GPIO );
        vTaskDelay( 2 );

        /* Init step1: read chip ID. */
        dev_id = ksz8851_reg_read( REG_CHIP_ID );

        if( ( dev_id & 0xFFF0 ) == CHIP_ID_8851_16 )
        {
            id_ok = 1;
            break;
        }
    }

    if( id_ok != 0 )
    {
        ksz8851snl_set_registers();
    }

    return id_ok ? 1 : -1;
}

uint32_t ksz8851snl_reset_rx( void )
{
    uint16_t usValue;

    usValue = ksz8851_reg_read( REG_RX_CTRL1 );

    usValue &= ~( ( uint16_t ) RX_CTRL_ENABLE | RX_CTRL_FLUSH_QUEUE );

    ksz8851_reg_write( REG_RX_CTRL1, usValue );
    vTaskDelay( 2 );
    ksz8851_reg_write( REG_RX_CTRL1, usValue | RX_CTRL_FLUSH_QUEUE );
    vTaskDelay( 1 );
    ksz8851_reg_write( REG_RX_CTRL1, usValue );
    vTaskDelay( 1 );
    ksz8851_reg_write( REG_RX_CTRL1, usValue | RX_CTRL_ENABLE );
    vTaskDelay( 1 );

    return ( uint32_t ) usValue;
}

uint32_t ksz8851snl_reset_tx( void )
{
    uint16_t usValue;

    usValue = ksz8851_reg_read( REG_TX_CTRL );

    usValue &= ~( ( uint16_t ) TX_CTRL_ENABLE | TX_CTRL_FLUSH_QUEUE );

    ksz8851_reg_write( REG_TX_CTRL, usValue );
    vTaskDelay( 2 );
    ksz8851_reg_write( REG_TX_CTRL, usValue | TX_CTRL_FLUSH_QUEUE );
    vTaskDelay( 1 );
    ksz8851_reg_write( REG_TX_CTRL, usValue );
    vTaskDelay( 1 );
    ksz8851_reg_write( REG_TX_CTRL, usValue | TX_CTRL_ENABLE );
    vTaskDelay( 1 );

    return ( uint32_t ) usValue;
}
