// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
//
// 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.

#include <stdio.h>
#include <string.h>

#include "rom/ets_sys.h"
#include "rom/gpio.h"
#include "soc/dport_reg.h"
#include "soc/io_mux_reg.h"
#include "soc/rtc_cntl_reg.h"
#include "soc/gpio_reg.h"
#include "soc/dport_reg.h"
#include "soc/emac_ex_reg.h"
#include "soc/emac_reg_v2.h"
#include "soc/soc.h"

#include "tcpip_adapter.h"
#include "sdkconfig.h"

#include "esp_task_wdt.h"
#include "esp_event.h"
#include "esp_system.h"
#include "esp_err.h"
#include "esp_log.h"
#include "esp_eth.h"
#include "esp_intr_alloc.h"

#include "emac_common.h"
#include "emac_desc.h"

#include "freertos/xtensa_api.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "freertos/timers.h"

#include "lwip/err.h"

#define EMAC_EVT_QNUM 200
#define EMAC_SIG_MAX 50

static struct emac_config_data emac_config;

static uint8_t emac_dma_rx_chain_buf[32 * DMA_RX_BUF_NUM];
static uint8_t emac_dma_tx_chain_buf[32 * DMA_TX_BUF_NUM];
static uint8_t emac_dma_rx_buf[DMA_RX_BUF_SIZE * DMA_RX_BUF_NUM];
static uint8_t emac_dma_tx_buf[DMA_TX_BUF_SIZE * DMA_TX_BUF_NUM];

static SemaphoreHandle_t emac_g_sem;
static portMUX_TYPE g_emac_mux = portMUX_INITIALIZER_UNLOCKED;
static xTaskHandle emac_task_hdl;
static xQueueHandle emac_xqueue;
static uint8_t emac_sig_cnt[EMAC_SIG_MAX] = {0};
static TimerHandle_t emac_timer = NULL;
static SemaphoreHandle_t emac_rx_xMutex = NULL;
static SemaphoreHandle_t emac_tx_xMutex = NULL;
static const char *TAG = "emac";
static bool pause_send = false;

static esp_err_t emac_ioctl(emac_sig_t sig, emac_par_t par);
esp_err_t emac_post(emac_sig_t sig, emac_par_t par);

static void emac_macaddr_init(void)
{
    esp_read_mac(&(emac_config.macaddr[0]), ESP_MAC_ETH);
}

void esp_eth_get_mac(uint8_t mac[6])
{
    memcpy(mac, &(emac_config.macaddr[0]), 6);
}

static void emac_setup_tx_desc(struct dma_extended_desc *tx_desc , uint32_t size)
{
    tx_desc->basic.desc1 = size & 0xfff;
    tx_desc->basic.desc0 = EMAC_DESC_INT_COMPL | EMAC_DESC_LAST_SEGMENT | EMAC_DESC_FIRST_SEGMENT | EMAC_DESC_SECOND_ADDR_CHAIN;
    tx_desc->basic.desc0 = EMAC_DESC_TX_OWN | EMAC_DESC_INT_COMPL | EMAC_DESC_LAST_SEGMENT | EMAC_DESC_FIRST_SEGMENT | EMAC_DESC_SECOND_ADDR_CHAIN;
}

static void emac_clean_tx_desc(struct dma_extended_desc *tx_desc)
{
    tx_desc->basic.desc1 = 0;
    tx_desc->basic.desc0 = 0;
}

static void emac_clean_rx_desc(struct dma_extended_desc *rx_desc , uint32_t buf_ptr)
{
    if (buf_ptr != 0) {
        rx_desc->basic.desc2 = buf_ptr;
    }
    rx_desc->basic.desc1 = EMAC_DESC_RX_SECOND_ADDR_CHAIN | DMA_RX_BUF_SIZE;
    rx_desc->basic.desc0 = EMAC_DESC_RX_OWN;
}

static void emac_set_tx_base_reg(void)
{
    REG_WRITE(EMAC_DMATXBASEADDR_REG, (uint32_t)(emac_config.dma_etx));
}

static void emac_set_rx_base_reg(void)
{
    REG_WRITE(EMAC_DMARXBASEADDR_REG, (uint32_t)(emac_config.dma_erx));
}

static void emac_reset_dma_chain(void)
{
    emac_config.cnt_tx = 0;
    emac_config.cur_tx = 0;
    emac_config.dirty_tx = 0;

    emac_config.cnt_rx = 0;
    emac_config.cur_rx = 0;
    emac_config.dirty_rx = 0;
}

static void emac_init_dma_chain(void)
{
    int i;
    uint32_t dma_phy;
    struct dma_extended_desc *p = NULL;

    //init tx chain
    emac_config.dma_etx = (struct dma_extended_desc *)(&emac_dma_tx_chain_buf[0]);
    emac_config.cnt_tx = 0;
    emac_config.cur_tx = 0;
    emac_config.dirty_tx = 0;

    dma_phy = (uint32_t)(emac_config.dma_etx);
    p = emac_config.dma_etx;

    for (i = 0; i < (DMA_TX_BUF_NUM - 1); i++ ) {
        dma_phy += sizeof(struct dma_extended_desc);
        emac_clean_tx_desc(p);
        p->basic.desc3 = dma_phy;
        p->basic.desc2 = (uint32_t)(&emac_dma_tx_buf[0]) + (i * DMA_TX_BUF_SIZE);
        p++;
    }
    p->basic.desc3 = (uint32_t)(emac_config.dma_etx);
    p->basic.desc2 = (uint32_t)(&emac_dma_tx_buf[0]) + (i * DMA_TX_BUF_SIZE);

    //init desc0 desc1
    emac_clean_tx_desc(p);

    //init rx chain
    emac_config.dma_erx = (struct dma_extended_desc *)(&emac_dma_rx_chain_buf[0]);
    emac_config.cnt_rx = 0;
    emac_config.cur_rx = 0;
    emac_config.dirty_rx = 0;

    dma_phy = (uint32_t)(emac_config.dma_erx);
    p = emac_config.dma_erx;

    for (i = 0; i < (DMA_RX_BUF_NUM - 1); i++ ) {
        dma_phy += sizeof(struct dma_extended_desc);
        emac_clean_rx_desc(p, (uint32_t)(&emac_dma_rx_buf[0]) + (i * DMA_RX_BUF_SIZE));
        p->basic.desc3 = dma_phy;
        p++;
    }

    emac_clean_rx_desc(p, (uint32_t)(&emac_dma_rx_buf[0]) + (i * DMA_RX_BUF_SIZE));
    p->basic.desc3 = (uint32_t)(emac_config.dma_erx);
}

void esp_eth_smi_write(uint32_t reg_num, uint16_t value)
{
    uint32_t phy_num = emac_config.phy_addr;

    while (REG_GET_BIT(EMAC_GMACGMIIADDR_REG, EMAC_GMIIBUSY) == 1 ) {
    }

    REG_WRITE(EMAC_GMACGMIIDATA_REG, value);
    REG_WRITE(EMAC_GMACGMIIADDR_REG, 0x3 | ((reg_num & 0x1f) << 6) | ((phy_num & 0x1f) << 11) | ((0x3) << 2));

    while (REG_GET_BIT(EMAC_GMACGMIIADDR_REG, EMAC_GMIIBUSY) == 1 ) {
    }
}

uint16_t esp_eth_smi_read(uint32_t reg_num)
{
    uint32_t phy_num = emac_config.phy_addr;
    uint16_t value = 0;

    while (REG_GET_BIT(EMAC_GMACGMIIADDR_REG, EMAC_GMIIBUSY) == 1 ) {
    }

    REG_WRITE(EMAC_GMACGMIIADDR_REG, 0x1 | ((reg_num & 0x1f) << 6) | ((phy_num & 0x1f) << 11) | (0x3 << 2));
    while (REG_GET_BIT(EMAC_GMACGMIIADDR_REG, EMAC_GMIIBUSY) == 1 ) {
    }
    value = (REG_READ(EMAC_GMACGMIIDATA_REG) & 0xffff);

    return value;
}

esp_err_t esp_eth_smi_wait_value(uint32_t reg_num, uint16_t value, uint16_t value_mask, int timeout_ms)
{
    unsigned start = xTaskGetTickCount();
    unsigned timeout_ticks = (timeout_ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS;
    uint16_t current_value = 0;

    while (timeout_ticks == 0 || (xTaskGetTickCount() - start < timeout_ticks)) {
        current_value = esp_eth_smi_read(reg_num);
        if ((current_value & value_mask) == (value & value_mask)) {
            return ESP_OK;
        }
        vTaskDelay(1);
    }
    ESP_LOGE(TAG, "Timed out waiting for PHY register 0x%x to have value 0x%04x (mask 0x%04x). Current value 0x%04x",
             reg_num, value, value_mask, current_value);
    return ESP_ERR_TIMEOUT;
}


static void emac_set_user_config_data(eth_config_t *config )
{
    emac_config.phy_addr = config->phy_addr;
    emac_config.mac_mode = config->mac_mode;
    emac_config.phy_init = config->phy_init;
    emac_config.emac_tcpip_input = config->tcpip_input;
    emac_config.emac_gpio_config = config->gpio_config;
    emac_config.emac_phy_check_link = config->phy_check_link;
    emac_config.emac_phy_check_init = config->phy_check_init;
    emac_config.emac_phy_get_speed_mode = config->phy_get_speed_mode;
    emac_config.emac_phy_get_duplex_mode = config->phy_get_duplex_mode;
#if DMA_RX_BUF_NUM > 9
    emac_config.emac_flow_ctrl_enable = config->flow_ctrl_enable;
#else 
    if(config->flow_ctrl_enable == true) {
        ESP_LOGE(TAG, "eth flow ctrl init err!!! Please run make menuconfig and make sure DMA_RX_BUF_NUM > 9 .");
    }
    emac_config.emac_flow_ctrl_enable = false;
#endif
    emac_config.emac_phy_get_partner_pause_enable = config->phy_get_partner_pause_enable;
    emac_config.emac_phy_power_enable = config->phy_power_enable;
}

static void emac_enable_intr()
{
    REG_WRITE(EMAC_DMAINTERRUPT_EN_REG, EMAC_INTR_ENABLE_BIT);
}

static void emac_disable_intr()
{
    REG_WRITE(EMAC_DMAINTERRUPT_EN_REG, 0);
}

static esp_err_t emac_verify_args(void)
{
    esp_err_t ret = ESP_OK;

    if (emac_config.phy_addr > PHY31) {
        ESP_LOGE(TAG, "phy addr err");
        ret = ESP_FAIL;
    }

    if (emac_config.mac_mode != ETH_MODE_RMII) {
        ESP_LOGE(TAG, "mac mode err,now only support RMII");
        ret = ESP_FAIL;
    }

    if (emac_config.phy_init == NULL) {
        ESP_LOGE(TAG, "phy_init func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_tcpip_input == NULL) {
        ESP_LOGE(TAG, "tcpip_input func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_gpio_config == NULL) {
        ESP_LOGE(TAG, "gpio config func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_phy_check_link == NULL) {
        ESP_LOGE(TAG, "phy check link func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_phy_check_init == NULL) {
        ESP_LOGE(TAG, "phy check init func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_phy_get_speed_mode == NULL) {
        ESP_LOGE(TAG, "phy get speed mode func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_phy_get_duplex_mode == NULL) {
        ESP_LOGE(TAG, "phy get duplex mode func is null");
        ret = ESP_FAIL;
    }

    if (emac_config.emac_flow_ctrl_enable == true && emac_config.emac_phy_get_partner_pause_enable == NULL) {
        ESP_LOGE(TAG, "phy get partner pause enable func is null");
        ret = ESP_FAIL;
    }

    if(emac_config.emac_phy_power_enable == NULL) {
        ESP_LOGE(TAG, "phy power enable func is null");
        ret = ESP_FAIL;
    }

    return ret;
}

//TODO for mac filter
void emac_set_mac_addr(void)
{
}

//TODO
void emac_check_mac_addr(void)
{
}

static void emac_process_tx(void)
{
    uint32_t cur_tx_desc = emac_read_tx_cur_reg();

    if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
        return;
    }

    xSemaphoreTakeRecursive( emac_tx_xMutex, ( TickType_t ) portMAX_DELAY );

    while (((uint32_t) & (emac_config.dma_etx[emac_config.dirty_tx].basic.desc0) != cur_tx_desc)) {
        emac_clean_tx_desc(&(emac_config.dma_etx[emac_config.dirty_tx]));
        emac_config.dirty_tx = (emac_config.dirty_tx + 1) % DMA_TX_BUF_NUM;
        emac_config.cnt_tx --;

        if (emac_config.cnt_tx < 0) {
            ESP_LOGE(TAG, "emac tx chain err");
        }
        cur_tx_desc = emac_read_tx_cur_reg();
    }

    xSemaphoreGiveRecursive( emac_tx_xMutex );
}

void esp_eth_free_rx_buf(void *buf)
{
    xSemaphoreTakeRecursive( emac_rx_xMutex, ( TickType_t ) portMAX_DELAY );

    emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.cur_rx]), (uint32_t) buf);
    emac_config.cur_rx = (emac_config.cur_rx + 1) % DMA_RX_BUF_NUM;
    emac_config.cnt_rx--;
    if (emac_config.cnt_rx < 0) {
        ESP_LOGE(TAG, "emac rx buf err!!\n");
    }
    emac_poll_rx_cmd();

    xSemaphoreGiveRecursive( emac_rx_xMutex );

    if (emac_config.emac_flow_ctrl_partner_support == true) {
        portENTER_CRITICAL(&g_emac_mux);
        if (pause_send == true && emac_config.cnt_rx < FLOW_CONTROL_LOW_WATERMARK) {
            emac_send_pause_zero_frame_enable();
            pause_send = false;
        }
        portEXIT_CRITICAL(&g_emac_mux);
    }
}

static uint32_t IRAM_ATTR emac_get_rxbuf_count_in_intr(void)
{
    uint32_t cnt = 0;
    uint32_t cur_rx_desc = emac_read_rx_cur_reg();
    struct dma_extended_desc *cur_desc = (struct dma_extended_desc *)cur_rx_desc;

    while (cur_desc->basic.desc0 == EMAC_DESC_RX_OWN) {
        cnt++;
        cur_desc = (struct dma_extended_desc *)cur_desc->basic.desc3;
    }
    return cnt;
}

#if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE
static void emac_process_rx(void)
{
    if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
        return;
    }
    uint32_t cur_rx_desc = emac_read_rx_cur_reg();

    while (((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc)) {
        //copy data to lwip
        emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2),
                                     (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL);

        emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.dirty_rx]), (emac_config.dma_erx[emac_config.dirty_rx].basic.desc2));
        emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;

        //if open this ,one intr can do many intrs ?
        cur_rx_desc = emac_read_rx_cur_reg();
    }

    emac_enable_rx_intr();
}

static void emac_process_rx_unavail(void)
{
    if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
        return;
    }

    uint32_t dirty_cnt = 0;
    while (dirty_cnt < DMA_RX_BUF_NUM) {

        if (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 == EMAC_DESC_RX_OWN) {
            break;
        }

        dirty_cnt ++;
        //copy data to lwip
        emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2),
                                     (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL);

        emac_clean_rx_desc(&(emac_config.dma_erx[emac_config.dirty_rx]), (emac_config.dma_erx[emac_config.dirty_rx].basic.desc2));
        emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
    }
    emac_enable_rx_intr();
    emac_enable_rx_unavail_intr();
    emac_poll_rx_cmd();
}

#else
static void emac_process_rx_unavail(void)
{
    if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
        return;
    }

    xSemaphoreTakeRecursive( emac_rx_xMutex, ( TickType_t ) portMAX_DELAY );

    while (emac_config.cnt_rx < DMA_RX_BUF_NUM) {

        //copy data to lwip
        emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2),
                                     (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL);
        emac_config.cnt_rx++;
        if (emac_config.cnt_rx > DMA_RX_BUF_NUM) {
            ESP_LOGE(TAG, "emac rx unavail buf err !!\n");
        }
        emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
    }
    emac_enable_rx_intr();
    emac_enable_rx_unavail_intr();
    xSemaphoreGiveRecursive( emac_rx_xMutex );
}

static void emac_process_rx(void)
{
    if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
        return;
    }

    uint32_t cur_rx_desc = emac_read_rx_cur_reg();

    xSemaphoreTakeRecursive( emac_rx_xMutex, ( TickType_t ) portMAX_DELAY );

    if (((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc)) {

        while (((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) != cur_rx_desc) && emac_config.cnt_rx < DMA_RX_BUF_NUM ) {
            //copy data to lwip
            emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2),
                                         (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL);

            emac_config.cnt_rx++;

            if (emac_config.cnt_rx > DMA_RX_BUF_NUM ) {
                ESP_LOGE(TAG, "emac rx buf err!!\n");
            }
            emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;

            cur_rx_desc = emac_read_rx_cur_reg();
        }
    } else {
        if (emac_config.cnt_rx < DMA_RX_BUF_NUM) {
            if ((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 & EMAC_DESC_RX_OWN) == 0) {
                while (emac_config.cnt_rx < DMA_RX_BUF_NUM) {

                    //copy data to lwip
                    emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[emac_config.dirty_rx].basic.desc2),
                                                 (((emac_config.dma_erx[emac_config.dirty_rx].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL);
                    emac_config.cnt_rx++;
                    if (emac_config.cnt_rx > DMA_RX_BUF_NUM) {
                        ESP_LOGE(TAG, "emac rx buf err!!!\n");
                    }

                    emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM;
                }
            }
        }
    }
    emac_enable_rx_intr();
    xSemaphoreGiveRecursive( emac_rx_xMutex );
}
#endif

//TODO other events need to do something
static void IRAM_ATTR emac_process_intr(void *arg)
{
    uint32_t event;
    event = REG_READ(EMAC_DMASTATUS_REG);

    //clr intrs
    REG_WRITE(EMAC_DMASTATUS_REG, event);

    if (event & EMAC_RECV_INT) {
        emac_disable_rx_intr();
        if (emac_config.emac_flow_ctrl_partner_support == true) {
            if (emac_get_rxbuf_count_in_intr() < FLOW_CONTROL_HIGH_WATERMARK && pause_send == false ) {
                pause_send = true;
                emac_send_pause_frame_enable();
            }
        }
        emac_post(SIG_EMAC_RX_DONE, 0);
    }

    if (event & EMAC_RECV_BUF_UNAVAIL) {
        emac_disable_rx_unavail_intr();
        emac_post(SIG_EMAC_RX_UNAVAIL, 0);
    }

    if (event & EMAC_TRANS_INT) {
        emac_post(SIG_EMAC_TX_DONE, 0);
    }
}

static void emac_set_macaddr_reg(void)
{
    REG_SET_FIELD(EMAC_GMACADDR0HIGH_REG, EMAC_MAC_ADDRESS0_HI, (emac_config.macaddr[0] << 8) | (emac_config.macaddr[1]));
    REG_WRITE(EMAC_GMACADDR0LOW_REG, (emac_config.macaddr[2] << 24) |  (emac_config.macaddr[3] << 16) | (emac_config.macaddr[4] << 8) | (emac_config.macaddr[5]));
}

static void emac_check_phy_init(void)
{
    emac_config.emac_phy_check_init();
    if (emac_config.emac_phy_get_duplex_mode() == ETH_MODE_FULLDUPLEX) {
        REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACDUPLEX);
    } else {
        REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACDUPLEX);
    }
    if (emac_config.emac_phy_get_speed_mode() == ETH_SPEED_MODE_100M) {
        REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED);
    } else {
        REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED);
    }
#if CONFIG_EMAC_L2_TO_L3_RX_BUF_MODE
    emac_disable_flowctrl();
    emac_config.emac_flow_ctrl_partner_support = false;
#else
    if (emac_config.emac_flow_ctrl_enable == true) {
        if (emac_config.emac_phy_get_partner_pause_enable() == true && emac_config.emac_phy_get_duplex_mode() == ETH_MODE_FULLDUPLEX) {
            emac_enable_flowctrl();
            emac_config.emac_flow_ctrl_partner_support = true;
        } else {
            emac_disable_flowctrl();
            emac_config.emac_flow_ctrl_partner_support = false;
        }
    } else {
        emac_disable_flowctrl();
        emac_config.emac_flow_ctrl_partner_support = false;
    }
#endif
    emac_mac_enable_txrx();
}
static void emac_process_link_updown(bool link_status)
{
    system_event_t evt;
    uint8_t i = 0;

    emac_config.phy_link_up = link_status;

    if (link_status == true) {
        emac_check_phy_init();
        ESP_LOGI(TAG, "eth link_up!!!");
        emac_enable_dma_tx();
        emac_enable_dma_rx();
        for (i = 0; i < PHY_LINK_CHECK_NUM; i++) {
            emac_check_phy_init();
        }

        evt.event_id = SYSTEM_EVENT_ETH_CONNECTED;
    } else {
        ESP_LOGI(TAG, "eth link_down!!!");
        emac_disable_dma_tx();
        emac_disable_dma_rx();
        evt.event_id = SYSTEM_EVENT_ETH_DISCONNECTED;
    }

    esp_event_send(&evt);
}

static void emac_hw_init(void)
{
    //init chain
    emac_init_dma_chain();

    //get hw features TODO

    //ipc TODO
}

esp_err_t esp_eth_tx(uint8_t *buf, uint16_t size)
{
    esp_err_t ret = ESP_OK;

    if (emac_config.emac_status != EMAC_RUNTIME_START || emac_config.emac_status == EMAC_RUNTIME_NOT_INIT) {
        ESP_LOGI(TAG, "tx netif close");
        ret = ERR_IF;
        return ret;
    }

    xSemaphoreTakeRecursive( emac_tx_xMutex, ( TickType_t ) portMAX_DELAY );
    if (emac_config.cnt_tx == DMA_TX_BUF_NUM - 1) {
        ESP_LOGD(TAG, "tx buf full");
        ret = ERR_MEM;
        goto _exit;
    }

    memcpy((uint8_t *)(emac_config.dma_etx[emac_config.cur_tx].basic.desc2), (uint8_t *)buf, size);

    emac_setup_tx_desc(&(emac_config.dma_etx[emac_config.cur_tx]), size);

    emac_config.cnt_tx ++;
    emac_config.cur_tx = (emac_config.cur_tx + 1) % DMA_TX_BUF_NUM ;

    emac_poll_tx_cmd();

_exit:

    xSemaphoreGiveRecursive( emac_tx_xMutex );
    return ret;
}

static void emac_init_default_data(void)
{
    memset((uint8_t *)&emac_config, 0, sizeof(struct emac_config_data));
}

void emac_process_link_check(void)
{
    if (emac_config.emac_status != EMAC_RUNTIME_START ||
            emac_config.emac_status == EMAC_RUNTIME_NOT_INIT) {
        return;
    }

    if (emac_config.emac_phy_check_link() == true ) {
        if (emac_config.phy_link_up == false) {
            emac_process_link_updown(true);
        }
    } else {
        if (emac_config.phy_link_up == true) {
            emac_process_link_updown(false);
        }
    }
}

void emac_link_check_func(void *pv_parameters)
{
    emac_post(SIG_EMAC_CHECK_LINK, 0);
}

static bool emac_link_check_timer_init(void)
{
    emac_timer = xTimerCreate("emac_timer", (2000 / portTICK_PERIOD_MS),
                              pdTRUE, (void *)rand(), emac_link_check_func);
    if (emac_timer == NULL) {
        return false;
    } else {
        return true;
    }
}

static bool emac_link_check_timer_start(void)
{
    if (xTimerStart(emac_timer, portMAX_DELAY) != pdPASS) {
        return false;
    } else {
        return true;
    }
}

static bool emac_link_check_timer_stop(void)
{
    if (xTimerStop(emac_timer, portMAX_DELAY) != pdPASS) {
        return false;
    } else {
        return true;
    }
}

static bool emac_link_check_timer_delete(void)
{
    xTimerDelete(emac_timer, portMAX_DELAY);
    emac_timer = NULL;
    return true;
}

static void emac_start(void *param)
{
    struct emac_post_cmd  *post_cmd = (struct emac_post_cmd *)param;
    struct emac_open_cmd *cmd = (struct emac_open_cmd *)(post_cmd->cmd);

    ESP_LOGI(TAG , "emac start !!!\n");
    cmd->err = EMAC_CMD_OK;
    emac_enable_clk(true);

    emac_reset();
    emac_macaddr_init();

    emac_check_mac_addr();

    emac_set_mac_addr();
    emac_set_macaddr_reg();

    emac_set_tx_base_reg();
    emac_set_rx_base_reg();

    emac_mac_init();

    emac_config.phy_init();

    //ptp TODO

    emac_enable_intr();

    emac_config.emac_status = EMAC_RUNTIME_START;

    system_event_t evt;
    evt.event_id = SYSTEM_EVENT_ETH_START;
    esp_event_send(&evt);

    //set a timer to check link up status
    if (emac_link_check_timer_init() == true) {
        if (emac_link_check_timer_start() != true) {
            cmd->err = EMAC_CMD_FAIL;
            emac_link_check_timer_delete();
        }
    } else {
        cmd->err = EMAC_CMD_FAIL;
    }

    if (post_cmd->post_type == EMAC_POST_SYNC) {
        xSemaphoreGive(emac_g_sem);
    }

    ESP_LOGI(TAG, "emac start success !!!");
}

esp_err_t esp_eth_enable(void)
{
    struct emac_post_cmd post_cmd;
    struct emac_open_cmd open_cmd;

    post_cmd.cmd = (void *)(&open_cmd);
    open_cmd.err = EMAC_CMD_OK;

    if (emac_config.emac_status == EMAC_RUNTIME_START) {
        open_cmd.err = EMAC_CMD_OK;
        return open_cmd.err;
    }

    if (emac_config.emac_status != EMAC_RUNTIME_NOT_INIT) {
        if (emac_ioctl(SIG_EMAC_START, (emac_par_t)(&post_cmd)) != 0) {
            open_cmd.err = EMAC_CMD_FAIL;
        }
    } else {
        open_cmd.err = EMAC_CMD_FAIL;
    }
    return open_cmd.err;
}

static void emac_stop(void *param)
{
    struct emac_post_cmd  *post_cmd = (struct emac_post_cmd *)param;
    ESP_LOGI(TAG, "emac stop");

    emac_link_check_timer_stop();
    emac_link_check_timer_delete();

    emac_process_link_updown(false);

    emac_disable_intr();
    emac_reset_dma_chain();
    emac_reset();
    emac_enable_clk(false);

    emac_config.emac_status = EMAC_RUNTIME_STOP;
    system_event_t evt;
    evt.event_id = SYSTEM_EVENT_ETH_STOP;
    esp_event_send(&evt);

    if (post_cmd->post_type == EMAC_POST_SYNC) {
        xSemaphoreGive(emac_g_sem);
    }

    ESP_LOGI(TAG, "emac stop success !!!");
}

esp_err_t esp_eth_disable(void)
{
    struct emac_post_cmd post_cmd;
    struct emac_close_cmd close_cmd;

    post_cmd.cmd = (void *)(&close_cmd);
    close_cmd.err = EMAC_CMD_OK;

    if (emac_config.emac_status == EMAC_RUNTIME_STOP) {
        close_cmd.err = EMAC_CMD_OK;
        return close_cmd.err;
    }

    if (emac_config.emac_status == EMAC_RUNTIME_START) {
        if (emac_ioctl(SIG_EMAC_STOP, (emac_par_t)(&post_cmd)) != 0) {
            close_cmd.err = EMAC_CMD_FAIL;
        }
    } else {
        close_cmd.err = EMAC_CMD_FAIL;
    }
    return close_cmd.err;
}

static esp_err_t emac_ioctl(emac_sig_t sig, emac_par_t par)
{
    esp_err_t ret = ESP_OK;
    struct emac_post_cmd  *post_cmd = (struct emac_post_cmd *)par;
    xTaskHandle task_hdl = xTaskGetCurrentTaskHandle();

    if (emac_task_hdl != task_hdl) {
        post_cmd->post_type = EMAC_POST_SYNC;
        if (emac_post(sig, par) != ESP_OK) {
            ret = ESP_FAIL;
            return ret;
        };

        if (xSemaphoreTake(emac_g_sem, portMAX_DELAY) == pdTRUE) {
            return ret;
        }
    } else {
        post_cmd->post_type = EMAC_POST_ASYNC;
        switch (sig) {
        case SIG_EMAC_RX_DONE:
            emac_process_rx();
            break;
        case SIG_EMAC_TX_DONE:
            emac_process_tx();
            break;
        case SIG_EMAC_START:
            emac_start((void *)par);
            break;
        case SIG_EMAC_STOP:
            emac_stop((void *)par);
            break;
        default:
            ESP_LOGE(TAG, "unexpect sig %d", sig);
            break;
        }
    }

    return ret;
}

void emac_task(void *pv)
{
    emac_event_t e;

    for (;;) {
        if (xQueueReceive(emac_xqueue, &e, (portTickType)portMAX_DELAY) == pdTRUE) {
            portENTER_CRITICAL(&g_emac_mux);
            emac_sig_cnt[e.sig]--;
            portEXIT_CRITICAL(&g_emac_mux);
            switch (e.sig) {
            case SIG_EMAC_RX_DONE:
                emac_process_rx();
                break;
            case SIG_EMAC_RX_UNAVAIL:
                emac_process_rx_unavail();
                break;
            case SIG_EMAC_TX_DONE:
                emac_process_tx();
                break;
            case SIG_EMAC_START:
                emac_start((void *)e.par);
                break;
            case SIG_EMAC_STOP:
                emac_stop((void *)e.par);
                break;
            case SIG_EMAC_CHECK_LINK:
                emac_process_link_check();
                break;
            default:
                ESP_LOGE(TAG, "unexpect sig %d", e.sig);
                break;
            }
        }
    }
}

esp_err_t IRAM_ATTR emac_post(emac_sig_t sig, emac_par_t par)
{
    if (sig <= SIG_EMAC_RX_DONE) {
        if (emac_sig_cnt[sig]) {
            return ESP_OK;
        } else {
            emac_sig_cnt[sig]++;
            emac_event_t evt;
            signed portBASE_TYPE ret;
            evt.sig = sig;
            evt.par = par;
            portBASE_TYPE tmp;

            ret = xQueueSendFromISR(emac_xqueue, &evt, &tmp);

            if (tmp != pdFALSE) {
                portYIELD_FROM_ISR();
            }

            if (ret != pdPASS) {
                return ESP_FAIL;
            }
        }
    } else {
        portENTER_CRITICAL(&g_emac_mux);
        emac_sig_cnt[sig]++;
        portEXIT_CRITICAL(&g_emac_mux);
        emac_event_t evt;
        evt.sig = sig;
        evt.par = par;

        if (xQueueSend(emac_xqueue, &evt, 10 / portTICK_PERIOD_MS) != pdTRUE) {
            return ESP_FAIL;
        }
    }

    return ESP_OK;
}

esp_err_t esp_eth_init(eth_config_t *config)
{
    esp_err_t ret = ESP_OK;
#if !CONFIG_ETHERNET
    ESP_LOGI(TAG, "eth driver init fail,please make menuconfig and enable ethernet .");
    ret = ESP_FAIL;
    goto _exit;
#endif

    emac_init_default_data();

    if (config != NULL ) {
        emac_set_user_config_data(config);
    }

    ret = emac_verify_args();

    if (ret != ESP_OK) {
        goto _exit;
    }

    emac_config.emac_phy_power_enable(true);

    //before set emac reg must enable clk
    emac_enable_clk(true);
    REG_SET_FIELD(EMAC_EX_PHYINF_CONF_REG, EMAC_EX_PHY_INTF_SEL, EMAC_EX_PHY_INTF_RMII);

    emac_dma_init();
    if (emac_config.mac_mode == ETH_MODE_RMII) {
        emac_set_clk_rmii();
    } else {
        emac_set_clk_mii();
    }

    emac_config.emac_gpio_config();

    ESP_LOGI(TAG, "mac version %04xa", emac_read_mac_version());
    emac_hw_init();

    //watchdog  TODO

    //init task for emac
    emac_g_sem = xSemaphoreCreateBinary();
    emac_rx_xMutex = xSemaphoreCreateRecursiveMutex();
    emac_tx_xMutex = xSemaphoreCreateRecursiveMutex();
    emac_xqueue = xQueueCreate(EMAC_EVT_QNUM, sizeof(emac_event_t));
    xTaskCreate(emac_task, "emacT", 2048, NULL, EMAC_TASK_PRIORITY, &emac_task_hdl);

    emac_enable_clk(false);
    esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, 0, emac_process_intr, NULL, NULL);

    emac_config.emac_status = EMAC_RUNTIME_INIT;

_exit:
    return ret;
}

