/**************************************************************************//**
 * @copyright (C) 2019 Nuvoton Technology Corp. All rights reserved.
 *
 * 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. Neither the name of Nuvoton Technology Corp. nor the names of its contributors
 *      may be used to endorse or promote products derived from this software
 *      without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 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.
 *****************************************************************************/
#include "FreeRTOS.h"
#include "list.h"
#include "FreeRTOS_IP.h"

#include "m480_eth.h"

#define ETH_TRIGGER_RX()    do { EMAC->RXST = 0; } while( 0 )
#define ETH_TRIGGER_TX()    do { EMAC->TXST = 0; } while( 0 )
#define ETH_ENABLE_TX()     do { EMAC->CTL |= EMAC_CTL_TXON; } while( 0 )
#define ETH_ENABLE_RX()     do { EMAC->CTL |= EMAC_CTL_RXON; } while( 0 )
#define ETH_DISABLE_TX()    do { EMAC->CTL &= ~EMAC_CTL_TXON; } while( 0 )
#define ETH_DISABLE_RX()    do { EMAC->CTL &= ~EMAC_CTL_RXON; } while( 0 )


struct eth_descriptor rx_desc[ RX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) );
struct eth_descriptor tx_desc[ TX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) );
#ifdef __ICCARM__
    #pragma data_alignment=4
    struct eth_descriptor rx_desc[ RX_DESCRIPTOR_NUM ];
    struct eth_descriptor tx_desc[ TX_DESCRIPTOR_NUM ];
    uint8_t rx_buf[ RX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ];
    uint8_t tx_buf[ TX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ];
#else
    struct eth_descriptor rx_desc[ RX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) );
    struct eth_descriptor tx_desc[ TX_DESCRIPTOR_NUM ] __attribute__( ( aligned( 4 ) ) );
    uint8_t rx_buf[ RX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ]  __attribute__( ( aligned( 4 ) ) );
    uint8_t tx_buf[ TX_DESCRIPTOR_NUM ][ PACKET_BUFFER_SIZE ]  __attribute__( ( aligned( 4 ) ) );
#endif
struct eth_descriptor volatile * cur_tx_desc_ptr, * cur_rx_desc_ptr, * fin_tx_desc_ptr;


/* PTP source clock is 84MHz (Real chip using PLL). Each tick is 11.90ns */
/* Assume we want to set each tick to 100ns. */
/* Increase register = (100 * 2^31) / (10^9) = 214.71 =~ 215 = 0xD7 */
/* Addend register = 2^32 * tick_freq / (84MHz), where tick_freq = (2^31 / 215) MHz */
/* From above equation, addend register = 2^63 / (84M * 215) ~= 510707200 = 0x1E70C600 */



static void mdio_write( uint8_t addr,
                        uint8_t reg,
                        uint16_t val )
{
    EMAC->MIIMDAT = val;
    EMAC->MIIMCTL = ( addr << EMAC_MIIMCTL_PHYADDR_Pos ) | reg | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_WRITE_Msk | EMAC_MIIMCTL_MDCON_Msk;

    while( EMAC->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk )
    {
    }
}


static uint16_t mdio_read( uint8_t addr,
                           uint8_t reg )
{
    EMAC->MIIMCTL = ( addr << EMAC_MIIMCTL_PHYADDR_Pos ) | reg | EMAC_MIIMCTL_BUSY_Msk | EMAC_MIIMCTL_MDCON_Msk;

    while( EMAC->MIIMCTL & EMAC_MIIMCTL_BUSY_Msk )
    {
    }

    return( EMAC->MIIMDAT );
}

static int reset_phy( void )
{
    uint16_t reg;
    uint32_t delayCnt;


    mdio_write( CONFIG_PHY_ADDR, MII_BMCR, BMCR_RESET );

    delayCnt = 2000;

    while( delayCnt-- > 0 )
    {
        if( ( mdio_read( CONFIG_PHY_ADDR, MII_BMCR ) & BMCR_RESET ) == 0 )
        {
            break;
        }
    }

    if( delayCnt == 0 )
    {
        NU_DEBUGF( ( "Reset phy failed\n" ) );
        return( -1 );
    }

    mdio_write( CONFIG_PHY_ADDR, MII_ADVERTISE, ADVERTISE_CSMA |
                ADVERTISE_10HALF |
                ADVERTISE_10FULL |
                ADVERTISE_100HALF |
                ADVERTISE_100FULL );

    reg = mdio_read( CONFIG_PHY_ADDR, MII_BMCR );
    mdio_write( CONFIG_PHY_ADDR, MII_BMCR, reg | BMCR_ANRESTART );

    delayCnt = 200000;

    while( delayCnt-- > 0 )
    {
        if( ( mdio_read( CONFIG_PHY_ADDR, MII_BMSR ) & ( BMSR_ANEGCOMPLETE | BMSR_LSTATUS ) )
            == ( BMSR_ANEGCOMPLETE | BMSR_LSTATUS ) )
        {
            break;
        }
    }

    if( delayCnt == 0 )
    {
        NU_DEBUGF( ( "AN failed. Set to 100 FULL\n" ) );
        EMAC->CTL |= ( EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk );
        return( -1 );
    }
    else
    {
        reg = mdio_read( CONFIG_PHY_ADDR, MII_LPA );

        if( reg & ADVERTISE_100FULL )
        {
            NU_DEBUGF( ( "100 full\n" ) );
            EMAC->CTL |= ( EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk );
        }
        else if( reg & ADVERTISE_100HALF )
        {
            NU_DEBUGF( ( "100 half\n" ) );
            EMAC->CTL = ( EMAC->CTL & ~EMAC_CTL_FUDUP_Msk ) | EMAC_CTL_OPMODE_Msk;
        }
        else if( reg & ADVERTISE_10FULL )
        {
            NU_DEBUGF( ( "10 full\n" ) );
            EMAC->CTL = ( EMAC->CTL & ~EMAC_CTL_OPMODE_Msk ) | EMAC_CTL_FUDUP_Msk;
        }
        else
        {
            NU_DEBUGF( ( "10 half\n" ) );
            EMAC->CTL &= ~( EMAC_CTL_OPMODE_Msk | EMAC_CTL_FUDUP_Msk );
        }
    }

    FreeRTOS_printf( ( "PHY ID 1:0x%x\r\n", mdio_read( CONFIG_PHY_ADDR, MII_PHYSID1 ) ) );
    FreeRTOS_printf( ( "PHY ID 2:0x%x\r\n", mdio_read( CONFIG_PHY_ADDR, MII_PHYSID2 ) ) );

    return( 0 );
}


static void init_tx_desc( void )
{
    uint32_t i;


    cur_tx_desc_ptr = fin_tx_desc_ptr = &tx_desc[ 0 ];

    for( i = 0; i < TX_DESCRIPTOR_NUM; i++ )
    {
        tx_desc[ i ].status1 = TXFD_PADEN | TXFD_CRCAPP | TXFD_INTEN;
        tx_desc[ i ].buf = &tx_buf[ i ][ 0 ];
        tx_desc[ i ].status2 = 0;
        tx_desc[ i ].next = &tx_desc[ ( i + 1 ) % TX_DESCRIPTOR_NUM ];
    }

    EMAC->TXDSA = ( unsigned int ) &tx_desc[ 0 ];
}

static void init_rx_desc( void )
{
    uint32_t i;


    cur_rx_desc_ptr = &rx_desc[ 0 ];

    for( i = 0; i < RX_DESCRIPTOR_NUM; i++ )
    {
        rx_desc[ i ].status1 = OWNERSHIP_EMAC;
        rx_desc[ i ].buf = &rx_buf[ i ][ 0 ];
        rx_desc[ i ].status2 = 0;
        rx_desc[ i ].next = &rx_desc[ ( i + 1 ) % TX_DESCRIPTOR_NUM ];
    }

    EMAC->RXDSA = ( unsigned int ) &rx_desc[ 0 ];
}

void numaker_set_mac_addr( uint8_t * addr )
{
    EMAC->CAM0M = ( addr[ 0 ] << 24 ) |
                  ( addr[ 1 ] << 16 ) |
                  ( addr[ 2 ] << 8 ) |
                  addr[ 3 ];

    EMAC->CAM0L = ( addr[ 4 ] << 24 ) |
                  ( addr[ 5 ] << 16 );
}

static void __eth_clk_pin_init()
{
    /* Unlock protected registers */
    SYS_UnlockReg();

    /* Enable IP clock */
    CLK_EnableModuleClock( EMAC_MODULE );

    /* Configure MDC clock rate to HCLK / (127 + 1) = 1.25 MHz if system is running at 160 MH */
    CLK_SetModuleClock( EMAC_MODULE, 0, CLK_CLKDIV3_EMAC( 127 ) );

    /* Update System Core Clock */
    SystemCoreClockUpdate();

    /*---------------------------------------------------------------------------------------------------------*/
    /* Init I/O Multi-function                                                                                 */
    /*---------------------------------------------------------------------------------------------------------*/
    /* Configure RMII pins */
    SYS->GPA_MFPL &= ~( SYS_GPA_MFPL_PA6MFP_Msk | SYS_GPA_MFPL_PA7MFP_Msk );
    SYS->GPA_MFPL |= SYS_GPA_MFPL_PA6MFP_EMAC_RMII_RXERR | SYS_GPA_MFPL_PA7MFP_EMAC_RMII_CRSDV;
    SYS->GPC_MFPL &= ~( SYS_GPC_MFPL_PC6MFP_Msk | SYS_GPC_MFPL_PC7MFP_Msk );
    SYS->GPC_MFPL |= SYS_GPC_MFPL_PC6MFP_EMAC_RMII_RXD1 | SYS_GPC_MFPL_PC7MFP_EMAC_RMII_RXD0;
    SYS->GPC_MFPH &= ~SYS_GPC_MFPH_PC8MFP_Msk;
    SYS->GPC_MFPH |= SYS_GPC_MFPH_PC8MFP_EMAC_RMII_REFCLK;
    SYS->GPE_MFPH &= ~( SYS_GPE_MFPH_PE8MFP_Msk | SYS_GPE_MFPH_PE9MFP_Msk | SYS_GPE_MFPH_PE10MFP_Msk |
                        SYS_GPE_MFPH_PE11MFP_Msk | SYS_GPE_MFPH_PE12MFP_Msk );
    SYS->GPE_MFPH |= SYS_GPE_MFPH_PE8MFP_EMAC_RMII_MDC |
                     SYS_GPE_MFPH_PE9MFP_EMAC_RMII_MDIO |
                     SYS_GPE_MFPH_PE10MFP_EMAC_RMII_TXD0 |
                     SYS_GPE_MFPH_PE11MFP_EMAC_RMII_TXD1 |
                     SYS_GPE_MFPH_PE12MFP_EMAC_RMII_TXEN;

    /* Enable high slew rate on all RMII TX output pins */
    PE->SLEWCTL = ( GPIO_SLEWCTL_HIGH << GPIO_SLEWCTL_HSREN10_Pos ) |
                  ( GPIO_SLEWCTL_HIGH << GPIO_SLEWCTL_HSREN11_Pos ) |
                  ( GPIO_SLEWCTL_HIGH << GPIO_SLEWCTL_HSREN12_Pos );


    /* Lock protected registers */
    SYS_LockReg();
}

int numaker_eth_init( uint8_t * mac_addr )
{
    int ret = 0;

    /* init CLK & pins */
    __eth_clk_pin_init();

    /* Reset MAC */
    EMAC->CTL = EMAC_CTL_RST_Msk;

    while( EMAC->CTL & EMAC_CTL_RST_Msk )
    {
    }

    init_tx_desc();
    init_rx_desc();

    numaker_set_mac_addr( mac_addr ); /* need to reconfigure hardware address because we just RESET EMAC... */


    /* Configure the MAC interrupt enable register. */
    EMAC->INTEN = EMAC_INTEN_RXIEN_Msk |
                  EMAC_INTEN_TXIEN_Msk |
                  EMAC_INTEN_RXGDIEN_Msk |
                  EMAC_INTEN_TXCPIEN_Msk |
                  EMAC_INTEN_RXBEIEN_Msk |
                  EMAC_INTEN_TXBEIEN_Msk |
                  EMAC_INTEN_RDUIEN_Msk |
                  EMAC_INTEN_TSALMIEN_Msk |
                  EMAC_INTEN_WOLIEN_Msk;

    /* Configure the MAC control register. */
    EMAC->CTL = EMAC_CTL_STRIPCRC_Msk | EMAC_CTL_RMIIEN_Msk;

    /* Accept packets for us and all broadcast and multicast packets */
    EMAC->CAMCTL = EMAC_CAMCTL_CMPEN_Msk |
                   EMAC_CAMCTL_AMP_Msk |
                   EMAC_CAMCTL_ABP_Msk;
    EMAC->CAMEN = 1; /* Enable CAM entry 0 */

    ret = reset_phy();

    EMAC_ENABLE_RX();
    EMAC_ENABLE_TX();
    return ret;
}



void ETH_halt( void )
{
    EMAC->CTL &= ~( EMAC_CTL_RXON_Msk | EMAC_CTL_TXON_Msk );
}

unsigned int m_status;

void EMAC_RX_IRQHandler( void )
{
/*    NU_DEBUGF(("%s ... \r\n", __FUNCTION__)); */
    m_status = EMAC->INTSTS & 0xFFFF;
    EMAC->INTSTS = m_status;

    if( m_status & EMAC_INTSTS_RXBEIF_Msk )
    {
        /* Shouldn't goes here, unless descriptor corrupted */
        NU_DEBUGF( ( "RX descriptor corrupted \r\n" ) );
        /*return; */
    }

    /* FIX ME: for rx-event, to ack rx_isr into event queue */
    xNetworkCallback( 'R' );
}


void numaker_eth_trigger_rx( void )
{
    ETH_TRIGGER_RX();
}

int numaker_eth_get_rx_buf( uint16_t * len,
                            uint8_t ** buf )
{
    unsigned int cur_entry, status;

    cur_entry = EMAC->CRXDSA;

    if( ( cur_entry == ( uint32_t ) cur_rx_desc_ptr ) && ( !( m_status & EMAC_INTSTS_RDUIF_Msk ) ) ) /* cur_entry may equal to cur_rx_desc_ptr if RDU occurred */
    {
        return -1;
    }

    status = cur_rx_desc_ptr->status1;

    if( status & OWNERSHIP_EMAC )
    {
        return -1;
    }

    if( status & RXFD_RXGD )
    {
        *buf = cur_rx_desc_ptr->buf;
        *len = status & 0xFFFF;
    }

    return 0;
}

void numaker_eth_rx_next( void )
{
    cur_rx_desc_ptr->status1 = OWNERSHIP_EMAC;
    cur_rx_desc_ptr = cur_rx_desc_ptr->next;
}

void EMAC_TX_IRQHandler( void )
{
    unsigned int cur_entry, status;

    status = EMAC->INTSTS & 0xFFFF0000;
    EMAC->INTSTS = status;

    if( status & EMAC_INTSTS_TXBEIF_Msk )
    {
        /* Shouldn't goes here, unless descriptor corrupted */
        return;
    }

    cur_entry = EMAC->CTXDSA;

    while( cur_entry != ( uint32_t ) fin_tx_desc_ptr )
    {
        fin_tx_desc_ptr = fin_tx_desc_ptr->next;
    }

    /* FIX ME: for tx-event, no-op at this stage */
    xNetworkCallback( 'T' );
}

uint8_t * numaker_eth_get_tx_buf( void )
{
    if( cur_tx_desc_ptr->status1 & OWNERSHIP_EMAC )
    {
        return( NULL );
    }
    else
    {
        return( cur_tx_desc_ptr->buf );
    }
}

void numaker_eth_trigger_tx( uint16_t length,
                             void * p )
{
    struct eth_descriptor volatile * desc;

    cur_tx_desc_ptr->status2 = ( unsigned int ) length;
    desc = cur_tx_desc_ptr->next; /* in case TX is transmitting and overwrite next pointer before we can update cur_tx_desc_ptr */
    cur_tx_desc_ptr->status1 |= OWNERSHIP_EMAC;
    cur_tx_desc_ptr = desc;

    ETH_TRIGGER_TX();
}

int numaker_eth_link_ok( void )
{
    /* first, a dummy read to latch */
    mdio_read( CONFIG_PHY_ADDR, MII_BMSR );

    if( mdio_read( CONFIG_PHY_ADDR, MII_BMSR ) & BMSR_LSTATUS )
    {
        return 1;
    }

    return 0;
}

/*void numaker_eth_set_cb(eth_callback_t eth_cb, void *userData) */
/*{ */
/*    nu_eth_txrx_cb =  eth_cb; */
/*    nu_userData = userData; */
/*} */

/* Provide ethernet devices with a semi-unique MAC address */
void numaker_mac_address( uint8_t * mac )
{
    uint32_t uID1;
    /* Fetch word 0 */
    uint32_t word0 = *( uint32_t * ) 0x7F804; /* 2KB Data Flash at 0x7F800 */
    /* Fetch word 1 */
    /* we only want bottom 16 bits of word1 (MAC bits 32-47) */
    /* and bit 9 forced to 1, bit 8 forced to 0 */
    /* Locally administered MAC, reduced conflicts */
    /* http://en.wikipedia.org/wiki/MAC_address */
    uint32_t word1 = *( uint32_t * ) 0x7F800; /* 2KB Data Flash at 0x7F800 */

    if( word0 == 0xFFFFFFFF )                 /* Not burn any mac address at 1st 2 words of Data Flash */
    {
        /* with a semi-unique MAC address from the UUID */
        /* Enable FMC ISP function */
        SYS_UnlockReg();
        FMC_Open();
        /* = FMC_ReadUID(0); */
        uID1 = FMC_ReadUID( 1 );
        word1 = ( uID1 & 0x003FFFFF ) | ( ( uID1 & 0x030000 ) << 6 ) >> 8;
        word0 = ( ( FMC_ReadUID( 0 ) >> 4 ) << 20 ) | ( ( uID1 & 0xFF ) << 12 ) | ( FMC_ReadUID( 2 ) & 0xFFF );
        /* Disable FMC ISP function */
        FMC_Close();
        /* Lock protected registers */
        SYS_LockReg();
    }

    word1 |= 0x00000200;
    word1 &= 0x0000FEFF;

    mac[ 0 ] = ( word1 & 0x0000ff00 ) >> 8;
    mac[ 1 ] = ( word1 & 0x000000ff );
    mac[ 2 ] = ( word0 & 0xff000000 ) >> 24;
    mac[ 3 ] = ( word0 & 0x00ff0000 ) >> 16;
    mac[ 4 ] = ( word0 & 0x0000ff00 ) >> 8;
    mac[ 5 ] = ( word0 & 0x000000ff );

    NU_DEBUGF( ( "mac address %02x-%02x-%02x-%02x-%02x-%02x \r\n", mac[ 0 ], mac[ 1 ], mac[ 2 ], mac[ 3 ], mac[ 4 ], mac[ 5 ] ) );
}

void numaker_eth_enable_interrupts( void )
{
    EMAC->INTEN |= EMAC_INTEN_RXIEN_Msk |
                   EMAC_INTEN_TXIEN_Msk;
    NVIC_EnableIRQ( EMAC_RX_IRQn );
    NVIC_EnableIRQ( EMAC_TX_IRQn );
}

void numaker_eth_disable_interrupts( void )
{
    NVIC_DisableIRQ( EMAC_RX_IRQn );
    NVIC_DisableIRQ( EMAC_TX_IRQn );
}
