Newer
Older
mbed-os / connectivity / drivers / emac / TARGET_NUVOTON_EMAC / TARGET_M460 / m460_eth.c
@Jay Sridharan Jay Sridharan on 31 Dec 2022 22 KB Merge upstream changes into mbed-ce (#117)
/*
 * Copyright (c) 2022 Nuvoton Technology Corp.
 * Copyright (c) 2022 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Description:   M460 MAC driver source file
 */
//#include <stdbool.h>
#include "m460_eth.h"
#include "mbed_toolchain.h"
//#define NU_TRACE
#include "numaker_eth_hal.h"

#include "synopGMAC_network_interface.h"

#ifdef  NU_TRACE_ISR
#define NU_RAW_Debug(x) { mbed_error_printf x; }
#else
#define NU_RAW_Debug(x)
#endif

#define NU_M460_INTF            0       // Device EMAC Interface port
#define NU_M460_RMII_PIN_GRP    0       // Device EMAC RMII pin group 0 or 1

extern synopGMACdevice GMACdev[GMAC_CNT];
extern struct sk_buff tx_buf[GMAC_CNT][TRANSMIT_DESC_SIZE];
extern struct sk_buff rx_buf[GMAC_CNT][RECEIVE_DESC_SIZE];

eth_callback_t nu_eth_txrx_cb = NULL;
void *nu_userData = NULL;

extern void ack_emac_rx_isr(void);

void plat_delay(uint32_t delay)
{
    volatile uint32_t loop = delay*200;
    while (loop--);
}

static void mdio_write(int addr, int reg, int data)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    synopGMAC_write_phy_reg((u32 *) gmacdev->MacBase, addr, reg, data);
}

static int mdio_read(int addr, int reg)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    uint16_t data;
    synopGMAC_read_phy_reg((u32 *) gmacdev->MacBase, addr, reg, &data);
    return data;
}


static int reset_phy(void)
{

    uint16_t reg;
    uint32_t delayCnt;
    int retVal = 0;
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    
    mdio_write(CONFIG_PHY_ADDR, MII_BMCR, BMCR_RESET);

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

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

    delayCnt = 200000;
    while (delayCnt > 0) {
        delayCnt--;
        if (numaker_eth_link_ok()) {
            gmacdev->LinkState = LINKUP;
            NU_DEBUGF(("Link Up\n"));
            break;
        }
    }    
    if (delayCnt == 0) {
        gmacdev->LinkState = LINKDOWN;
        NU_DEBUGF(("Link Down\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) {
        delayCnt--;
        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"));
        synopGMAC_set_full_duplex(gmacdev);
        synopGMAC_set_mode(NU_M460_INTF, 1);    // Set mode 1: 100Mbps; 2: 10Mbps
        return (-1);
    } else {
        reg = mdio_read(CONFIG_PHY_ADDR, MII_LPA);
        if (reg & ADVERTISE_100FULL) {
            NU_DEBUGF(("100 full\n"));
            gmacdev->DuplexMode = FULLDUPLEX;
            gmacdev->Speed = SPEED100;
            synopGMAC_set_full_duplex(gmacdev);
            synopGMAC_set_mode(NU_M460_INTF, 1);    // Set mode 1: 100Mbps; 2: 10Mbps
        } else if (reg & ADVERTISE_100HALF) {
            NU_DEBUGF(("100 half\n"));
            gmacdev->DuplexMode = HALFDUPLEX;
            gmacdev->Speed = SPEED100;
            synopGMAC_set_half_duplex(gmacdev);
            synopGMAC_set_mode(NU_M460_INTF, 1);    // Set mode 1: 100Mbps; 2: 10Mbps
        } else if (reg & ADVERTISE_10FULL) {
            NU_DEBUGF(("10 full\n"));
            gmacdev->DuplexMode = FULLDUPLEX;
            gmacdev->Speed = SPEED10;
            synopGMAC_set_full_duplex(gmacdev);
            synopGMAC_set_mode(NU_M460_INTF, 2);    // Set mode 1: 100Mbps; 2: 10Mbps
        } else {
            NU_DEBUGF(("10 half\n"));
            gmacdev->DuplexMode = HALFDUPLEX;
            gmacdev->Speed = SPEED10;
            synopGMAC_set_half_duplex(gmacdev);
            synopGMAC_set_mode(NU_M460_INTF, 2);    // Set mode 1: 100Mbps; 2: 10Mbps
        }
    }    
    
    printf("PHY ID 1:0x%x\r\n", mdio_read(CONFIG_PHY_ADDR, MII_PHYSID1));
    printf("PHY ID 2:0x%x\r\n", mdio_read(CONFIG_PHY_ADDR, MII_PHYSID2));

    return (retVal);
    
}


void numaker_set_mac_addr(uint8_t *addr)
{

    synopGMAC_set_mac_address(NU_M460_INTF, addr);

}

static void __set_mac_pins(uint32_t group)
{
    if(group == 0)
    {
        SET_EMAC0_RMII_MDC_PE8();
        SET_EMAC0_RMII_MDIO_PE9();
        SET_EMAC0_RMII_TXD0_PE10();
        SET_EMAC0_RMII_TXD1_PE11();
        SET_EMAC0_RMII_TXEN_PE12();    
        SET_EMAC0_RMII_REFCLK_PC8();
        SET_EMAC0_RMII_RXD0_PC7();
        SET_EMAC0_RMII_RXD1_PC6();
        SET_EMAC0_RMII_CRSDV_PA7();
        SET_EMAC0_RMII_RXERR_PA6();
        
        SET_EMAC0_PPS_PB6();
    }
    else
    {
        SET_EMAC0_RMII_MDC_PB11();
        SET_EMAC0_RMII_MDIO_PB10();
        SET_EMAC0_RMII_TXD0_PB9();
        SET_EMAC0_RMII_TXD1_PB8();
        SET_EMAC0_RMII_TXEN_PB7();    
        SET_EMAC0_RMII_REFCLK_PB5();
        SET_EMAC0_RMII_RXD0_PB4();
        SET_EMAC0_RMII_RXD1_PB3();
        SET_EMAC0_RMII_CRSDV_PB2();
        SET_EMAC0_RMII_RXERR_PB1();
        
        SET_EMAC0_PPS_PE13();
    }
}

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

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

    // Configure MDC clock rate

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

    /*---------------------------------------------------------------------------------------------------------*/
    /* Init I/O Multi-function                                                                                 */
    /*---------------------------------------------------------------------------------------------------------*/
    // Configure RMII pins
    __set_mac_pins(NU_M460_RMII_PIN_GRP);
    
    /* Lock protected registers */
    SYS_LockReg();

}

void numaker_eth_init(uint8_t *mac_addr)
{
    int status = 0;
    int retval = 0;
    int i;
    uint32_t offload_needed = 0;    
    uint32_t dma_addr;
    struct sk_buff *skb;
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];

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

    /*Attach the device to MAC struct This will configure all the required base addresses
      such as Mac base, configuration base, phy base address(out of 32 possible phys )*/
    synopGMAC_attach(gmacdev, GMAC0MappedAddr + MACBASE, GMAC0MappedAddr + DMABASE, DEFAULT_PHY_BASE);
    
    // Reset MAC
    synopGMAC_reset(gmacdev);
    gmacdev->Intf = NU_M460_INTF;
    synopGMAC_read_version(gmacdev);
    
    /*Check for Phy initialization*/
    synopGMAC_set_mdc_clk_div(gmacdev,GmiiCsrClk5);
    gmacdev->ClockDivMdc = synopGMAC_get_mdc_clk_div(gmacdev);
    
    /*Reset PHY*/
    //status = synopGMAC_check_phy_init(gmacdev);
    status = reset_phy();

    /*Set up the tx and rx descriptor queue/ring*/
    synopGMAC_setup_tx_desc_queue(gmacdev,TRANSMIT_DESC_SIZE, RINGMODE);
    synopGMAC_init_tx_desc_base(gmacdev);	//Program the transmit descriptor base address in to DmaTxBase addr

    synopGMAC_setup_rx_desc_queue(gmacdev,RECEIVE_DESC_SIZE, RINGMODE);
    synopGMAC_init_rx_desc_base(gmacdev);	//Program the transmit descriptor base address in to DmaTxBase addr

    /*Initialize the dma interface*/
    synopGMAC_dma_bus_mode_init(gmacdev, DmaBurstLength32 | DmaDescriptorSkip0/*DmaDescriptorSkip2*/ | DmaDescriptor8Words ); synopGMAC_dma_control_init(gmacdev,DmaStoreAndForward |DmaTxSecondFrame|DmaRxThreshCtrl128);

    /*Initialize the mac interface*/
    synopGMAC_mac_init(gmacdev);
    synopGMAC_promisc_enable(gmacdev);

    synopGMAC_pause_control(gmacdev); // This enables the pause control in Full duplex mode of operation

#if defined(NU_USING_HW_CHECKSUM)
    /*IPC Checksum offloading is enabled for this driver. Should only be used if Full Ip checksumm offload engine is configured in the hardware*/
    offload_needed = 1;
    synopGMAC_enable_rx_chksum_offload(gmacdev);  	//Enable the offload engine in the receive path
    synopGMAC_rx_tcpip_chksum_drop_enable(gmacdev); // This is default configuration, DMA drops the packets if error in encapsulated ethernet payload
#endif

    for(i = 0; i < RECEIVE_DESC_SIZE; i ++) {
        skb = &rx_buf[NU_M460_INTF][i];
        synopGMAC_set_rx_qptr(gmacdev, (u32)((u64)(skb->data) & 0xFFFFFFFF), sizeof(skb->data), (u32)((u64)skb & 0xFFFFFFFF));
    }

    for(i = 0; i < TRANSMIT_DESC_SIZE; i ++) {
        skb = &tx_buf[NU_M460_INTF][i];
        synopGMAC_set_tx_qptr(gmacdev, (u32)((u64)(skb->data) & 0xFFFFFFFF), sizeof(skb->data), (u32)((u64)skb & 0xFFFFFFFF), offload_needed ,0);
    }    

    numaker_set_mac_addr(mac_addr);  // need to reconfigure hardware address 'cos we just RESET emc...
    
    synopGMAC_clear_interrupt(gmacdev);

    synopGMAC_enable_interrupt(gmacdev,DmaIntEnable);
    synopGMAC_enable_dma_rx(gmacdev);
    synopGMAC_enable_dma_tx(gmacdev);
    
    synopGMAC_tx_enable(gmacdev);
    synopGMAC_rx_enable(gmacdev);
    NVIC_EnableIRQ(EMAC0_TXRX_IRQn);
}



void  ETH_halt(void)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    
    synopGMAC_tx_disable(gmacdev);
    synopGMAC_rx_disable(gmacdev);
}

unsigned int m_status;

/*----------------------------------------------------------------------------
  EMAC IRQ Handler
 *----------------------------------------------------------------------------*/
void EMAC0_IRQHandler(void)
{
    synopGMACdevice * gmacdev = &GMACdev[0];
    uint32_t interrupt,dma_status_reg, mac_status_reg;
    int status;
    uint32_t dma_addr;

    // Check GMAC interrupt
    mac_status_reg = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacInterruptStatus);
    if(mac_status_reg & GmacTSIntSts) {
    	gmacdev->synopGMACNetStats.ts_int = 1;
    	status = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacTSStatus);
    	if(!(status & (1 << 1))) {
    		NU_RAW_Debug(("TS alarm flag not set??\n"));
    	} else {
    		NU_RAW_Debug(("TS alarm!!!!!!!!!!!!!!!!\n"));
        }
    }
    if(mac_status_reg & GmacLPIIntSts) {
    	//NU_RAW_Debug("LPI\n");
    	//LPIStsChange = 1;
    	//LPIReg = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacLPICtrlSts);
        ;
    }
    if(mac_status_reg & GmacRgmiiIntSts) {
    	uint32_t volatile reg;
    	reg = synopGMACReadReg((u32 *)gmacdev->MacBase, GmacRgmiiCtrlSts);

    }
    synopGMACWriteReg((u32 *)gmacdev->MacBase, GmacInterruptStatus ,mac_status_reg);

    /*Read the Dma interrupt status to know whether the interrupt got generated by our device or not*/
    dma_status_reg = synopGMACReadReg((u32 *)gmacdev->DmaBase, DmaStatus);
    NU_RAW_Debug(("i %08x %08x\n", mac_status_reg, dma_status_reg));

    if(dma_status_reg == 0)
        return;

    synopGMAC_disable_interrupt_all(gmacdev);

    NU_RAW_Debug(("%s:Dma Status Reg: 0x%08x\n",__FUNCTION__,dma_status_reg));

    if(dma_status_reg & GmacPmtIntr) {
        NU_RAW_Debug(("%s:: Interrupt due to PMT module\n",__FUNCTION__));
        synopGMAC_powerup_mac(gmacdev);
    }

    if(dma_status_reg & GmacLineIntfIntr) {
        NU_RAW_Debug(("%s:: Interrupt due to GMAC LINE module\n",__FUNCTION__));
    }

    /*Now lets handle the DMA interrupts*/
    interrupt = synopGMAC_get_interrupt_type(gmacdev);
    NU_RAW_Debug(("%s:Interrupts to be handled: 0x%08x\n",__FUNCTION__,interrupt));

    if(interrupt & synopGMACDmaError) {
        uint8_t hwaddr[6];
        mbed_mac_address((char *)hwaddr);
        NU_RAW_Debug(("%s::Fatal Bus Error Interrupt Seen\n",__FUNCTION__));
        synopGMAC_disable_dma_tx(gmacdev);
        synopGMAC_disable_dma_rx(gmacdev);

        synopGMAC_take_desc_ownership_tx(gmacdev);
        synopGMAC_take_desc_ownership_rx(gmacdev);

        synopGMAC_init_tx_rx_desc_queue(gmacdev);

        synopGMAC_reset(gmacdev);//reset the DMA engine and the GMAC ip
        synopGMAC_set_mac_address(NU_M460_INTF, (uint8_t*)hwaddr);
        synopGMAC_dma_bus_mode_init(gmacdev,DmaFixedBurstEnable| DmaBurstLength8 | DmaDescriptorSkip0/*DmaDescriptorSkip2*/ );
        synopGMAC_dma_control_init(gmacdev,DmaStoreAndForward);
        synopGMAC_init_rx_desc_base(gmacdev);
        synopGMAC_init_tx_desc_base(gmacdev);
        synopGMAC_mac_init(gmacdev);
        synopGMAC_enable_dma_rx(gmacdev);
        synopGMAC_enable_dma_tx(gmacdev);

    }

    if(interrupt & synopGMACDmaRxNormal) {
    	//NU_RAW_Debug(("rx\n"));
        NU_RAW_Debug(("%s:: Rx Normal \r\n", __FUNCTION__));
        // to handle received data
        if (nu_eth_txrx_cb != NULL) {
            nu_eth_txrx_cb('R', nu_userData);
        }
    }

    if(interrupt & synopGMACDmaRxAbnormal) {
        mbed_error_printf("%s::Abnormal Rx Interrupt Seen \r\n",__FUNCTION__);
    	gmacdev->synopGMACNetStats.rx_over_errors++;
        if(gmacdev->GMAC_Power_down == 0) {	// If Mac is not in powerdown
            synopGMAC_resume_dma_rx(gmacdev);//To handle GBPS with 12 descriptors
        }
    }

    if(interrupt & synopGMACDmaRxStopped) {
        mbed_error_printf("%s::Receiver stopped seeing Rx interrupts \r\n",__FUNCTION__); //Receiver gone in to stopped state
        if(gmacdev->GMAC_Power_down == 0) {	// If Mac is not in powerdown
        	gmacdev->synopGMACNetStats.rx_over_errors++;
            synopGMAC_enable_dma_rx(gmacdev);
        }
    }

    if(interrupt & synopGMACDmaTxNormal) {
        //NU_RAW_Debug(("rx\n"));
        //xmit function has done its job
        NU_RAW_Debug(("%s::Finished Normal Transmission \n",__FUNCTION__));
        synop_handle_transmit_over(0);//Do whatever you want after the transmission is over
        if (nu_eth_txrx_cb != NULL) {
            nu_eth_txrx_cb('T', nu_userData);
        }
    }

    if(interrupt & synopGMACDmaTxAbnormal) {
        mbed_error_printf("%s::Abnormal Tx Interrupt Seen\n",__FUNCTION__);
        if(gmacdev->GMAC_Power_down == 0) {	// If Mac is not in powerdown
            synop_handle_transmit_over(0);
            if (nu_eth_txrx_cb != NULL) {
                nu_eth_txrx_cb('T', nu_userData);
            }
        }
    }

    if(interrupt & synopGMACDmaTxStopped) {
        mbed_error_printf("%s::Transmitter stopped sending the packets\n",__FUNCTION__);
        if(gmacdev->GMAC_Power_down == 0) {	// If Mac is not in powerdown
            synopGMAC_disable_dma_tx(gmacdev);
            synopGMAC_take_desc_ownership_tx(gmacdev);

            synopGMAC_enable_dma_tx(gmacdev);
            mbed_error_printf("%s::Transmission Resumed\n",__FUNCTION__);
        }
    }

    /* Enable the interrupt before returning from ISR*/
//    if( !(interrupt & synopGMACDmaRxNormal)) {  /* RxNormal will enable INT in numaker_eth_trigger_rx */
        synopGMAC_enable_interrupt(gmacdev,DmaIntEnable);
//    }
    return;
}

static void dump_desc(DmaDesc *desc)
{
#if 1
    NU_DEBUGF(("###--- %s... Desc[0x%08x] status[0x%08x] ---###\r\n", __FUNCTION__, desc, desc->status));
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    DmaDesc *tmp_desc;
#endif
#if 0
    int i=0;
    for ( i=0; i< RECEIVE_DESC_SIZE; i++ ) {
        tmp_desc = gmacdev->RxDesc + i;
        NU_DEBUGF(("### RX %s... desc[0x%08x]  status[0x%08x]   length[0x%08x]  buffer1[0x%08x]  buffer2[0x%08x]\r\n", 
        __FUNCTION__, tmp_desc, tmp_desc->status, tmp_desc->length, tmp_desc->buffer1, tmp_desc->buffer2)); 
        //NU_DEBUGF(("### %s... length[0x%x]\r\n", __FUNCTION__, desc->length));   
        //NU_DEBUGF(("### %s... buffer1[0x%x]\r\n", __FUNCTION__, desc->buffer1));   
        //NU_DEBUGF(("### %s... buffer2[0x%x]\r\n", __FUNCTION__, desc->buffer2));   
    }
    for ( i=0; i< TRANSMIT_DESC_SIZE; i++ ) {
        tmp_desc = gmacdev->TxDesc + i;
        NU_DEBUGF(("### TX %s... desc[0x%08x]  status[0x%08x]   length[0x%08x]  buffer1[0x%08x]  buffer2[0x%08x]\r\n", 
        __FUNCTION__, tmp_desc, tmp_desc->status, tmp_desc->length, tmp_desc->buffer1, tmp_desc->buffer2));
    }        
#endif
}

void numaker_eth_trigger_rx(void)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    NU_DEBUGF(("=== %s...  ---Start---\r\n", __FUNCTION__));
    dump_desc(gmacdev->RxBusyDesc);
    /* Enable the interrupt */
    synopGMAC_enable_interrupt(gmacdev,DmaIntEnable);
    /* Trigger RX DMA */
    synopGMAC_enable_dma_rx(gmacdev);
    synopGMAC_resume_dma_rx(gmacdev);
    NU_DEBUGF(("%s... resume RX DMA\r\n", __FUNCTION__));
    NU_DEBUGF(("=== %s...  ---End---\r\n", __FUNCTION__));
}

int numaker_eth_get_rx_buf(uint16_t *len, uint8_t **buf)
{

    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];    
    NU_DEBUGF(("=== %s... ---Start---\r\n", __FUNCTION__));
    DmaDesc * rxdesc = gmacdev->RxBusyDesc;
    dump_desc(rxdesc);
    if(synopGMAC_is_desc_owned_by_dma(rxdesc))
        return -1;
    if(synopGMAC_is_desc_empty(rxdesc))
        return -1;
    
//    synopGMAC_disable_dma_rx(gmacdev);    // it will encounter DMA interrupt status as "Receiver stopped seeing Rx interrupts"
    *len = synop_handle_received_data(NU_M460_INTF, buf);
    dump_desc(gmacdev->RxBusyDesc);
    if( *len <= 0 ) return -1; /* No available RX frame */

    // length of payload should be <= 1514
    if (*len > (NU_ETH_MAX_FLEN - 4)) {
        NU_DEBUGF(("%s... unexpected long packet length=%d, buf=0x%x\r\n", __FUNCTION__, *len, *buf));
        *len = 0; // Skip this unexpected long packet
    }
    NU_DEBUGF(("=== %s...  ---End---\r\n", __FUNCTION__));  
    return 0;
}

void numaker_eth_rx_next(void)
{
    NU_DEBUGF(("=== %s... ---Start---\r\n", __FUNCTION__));
    /* Already did in synop_handle_received_data */
    /* No-op at this stage */    
#if 0    
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    DmaDesc * rxdesc = (gmacdev->RxBusyDesc - 1);

	rxdesc->status = DescOwnByDma;
#endif

}

uint8_t *numaker_eth_get_tx_buf(void)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    DmaDesc * txdesc = gmacdev->TxNextDesc;

    if(!synopGMAC_is_desc_empty(txdesc)) {
        return  (NULL);
    }
    
    if (synopGMAC_is_desc_owned_by_dma(txdesc))
    {
        return (NULL);
    } else {
        dump_desc(txdesc);
        return (uint8_t *) (txdesc->buffer1);
    }
        
}

void numaker_eth_trigger_tx(uint16_t length, void *p)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];
    DmaDesc * txdesc = gmacdev->TxNextDesc;
    uint32_t  txnext = gmacdev->TxNext;
#if defined(NU_USING_HW_CHECKSUM)
    uint32_t offload_needed = 1;
#else
    uint32_t offload_needed = 0;
#endif
    
    (gmacdev->BusyTxDesc)++; //busy tx descriptor is incremented by one as it will be handed over to DMA    
    txdesc->length |= ((length <<DescSize1Shift) & DescSize1Mask);
    txdesc->status |=  (DescTxFirst | DescTxLast | DescTxIntEnable ); //ENH_DESC

    if(offload_needed) {
        /*
         Make sure that the OS you are running supports the IP and TCP checksum offloading,
         before calling any of the functions given below.
        */
        synopGMAC_tx_checksum_offload_tcp_pseudo(gmacdev, txdesc);
    } else {
      	synopGMAC_tx_checksum_offload_bypass(gmacdev, txdesc);
    }
    __DSB();
    txdesc->status |= DescOwnByDma;//ENH_DESC

    gmacdev->TxNext = synopGMAC_is_last_tx_desc(gmacdev,txdesc) ? 0 : txnext + 1;
    gmacdev->TxNextDesc = synopGMAC_is_last_tx_desc(gmacdev,txdesc) ? gmacdev->TxDesc : (txdesc + 1);
    
    /* Enable the interrupt */
    synopGMAC_enable_interrupt(gmacdev,DmaIntEnable);    
    /* Trigger TX DMA */    
    synopGMAC_resume_dma_tx(gmacdev);
//    synopGMAC_enable_dma_tx(gmacdev);

}

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;
}

// Override mbed_mac_address of mbed_interface.c to provide ethernet devices with a semi-unique MAC address
void mbed_mac_address(char *mac)
{
    uint32_t uID1;
    // Fetch word 0
    uint32_t word0 = *(uint32_t *)0xFF804; // 2KB Data Flash at 0xFF800
    // 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 *)0xFF800; // 2KB Data Flash at 0xFF800

    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)
{
    synopGMACdevice *gmacdev = &GMACdev[NU_M460_INTF];

    /* Enable the interrupt */
    synopGMAC_enable_interrupt(gmacdev,DmaIntEnable);

    NVIC_EnableIRQ(EMAC0_TXRX_IRQn);
}

void numaker_eth_disable_interrupts(void)
{
    NVIC_DisableIRQ(EMAC0_TXRX_IRQn);
}