// Copyright 2015-2017 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 #include #include #include "rom/ets_sys.h" #include "rom/gpio.h" #include "soc/dport_reg.h" #include "soc/io_mux_reg.h" #include "soc/rtc.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 "esp_pm.h" #include "esp_spiram.h" #include "driver/periph_ctrl.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 dma_extended_desc_t *emac_dma_rx_chain_buf; static dma_extended_desc_t *emac_dma_tx_chain_buf; static uint8_t *emac_dma_rx_buf[DMA_RX_BUF_NUM]; static uint8_t *emac_dma_tx_buf[DMA_TX_BUF_NUM]; static SemaphoreHandle_t emac_g_sem = NULL; static portMUX_TYPE g_emac_mux = portMUX_INITIALIZER_UNLOCKED; static xTaskHandle emac_task_hdl = NULL; static xQueueHandle emac_xqueue = NULL; 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 intr_handle_t eth_intr_handle = NULL; static const char *TAG = "emac"; static bool pause_send = false; #ifdef CONFIG_PM_ENABLE static esp_pm_lock_handle_t s_pm_lock; #endif 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); } esp_err_t esp_eth_set_mac(const uint8_t mac[6]) { if (!(mac[0] & 0x01)) { memcpy(&(emac_config.macaddr[0]), mac, 6); return ESP_OK; } else { return ESP_ERR_INVALID_MAC; } } eth_speed_mode_t esp_eth_get_speed(void) { return emac_config.emac_phy_get_speed_mode(); } static void emac_setup_tx_desc(dma_extended_desc_t *tx_desc, uint32_t size) { tx_desc->basic.desc1 = size & 0xfff; 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(dma_extended_desc_t *tx_desc) { tx_desc->basic.desc1 = 0; tx_desc->basic.desc0 = 0; } static void emac_clean_rx_desc(dma_extended_desc_t *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)); } /* * dirty_rx indicates the hardware has been fed with data packets and is the * first node software needs to handle; * * cur_rx indicates the completion of software handling and is the last node * hardware could use; * * cnt_rx is to count the numbers of packets handled by software, passed to * protocol stack and not been freed. * * (1) Initializing the Linked List. Connect the numerable nodes to a circular * linked list, appoint one of the nodes as the head node, mark* the dirty_rx * and cur_rx into the node, and mount the node on the hardware base address. * Initialize cnt_rx into 0. * * (2) When hardware receives packets, nodes of linked lists will be fed with * data packets from the base address by turns, marks the node * of linked lists as “HARDWARE UNUSABLE” and reports interrupts. * * (3) When the software receives the interrupts, it will handle the linked * lists by turns from dirty_rx, send data packets to protocol * stack. dirty_rx will deviate backwards by turns and cnt_rx will by turns ++. * * (4) After the protocol stack handles all the data and calls the free function, * it will deviate backwards by turns from cur_rx, mark the * node of linked * lists as “HARDWARE USABLE” and cnt_rx will by turns --. * * (5) Cycle from Step 2 to Step 4 without break and build up circular linked * list handling. */ 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; dma_extended_desc_t *p = NULL; //init tx chain emac_config.dma_etx = emac_dma_tx_chain_buf; 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(dma_extended_desc_t); emac_clean_tx_desc(p); p->basic.desc2 = (uint32_t)(emac_dma_tx_buf[i]); p->basic.desc3 = dma_phy; p++; } emac_clean_tx_desc(p); p->basic.desc2 = (uint32_t)(emac_dma_tx_buf[i]); p->basic.desc3 = (uint32_t)(emac_config.dma_etx); //init rx chain emac_config.dma_erx = emac_dma_rx_chain_buf; 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(dma_extended_desc_t); emac_clean_rx_desc(p, (uint32_t)(emac_dma_rx_buf[i])); p->basic.desc3 = dma_phy; p++; } emac_clean_rx_desc(p, (uint32_t)(emac_dma_rx_buf[i])); 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_GMIIADDR_REG, EMAC_MIIBUSY) == 1) { } REG_WRITE(EMAC_MIIDATA_REG, value); REG_WRITE(EMAC_GMIIADDR_REG, 0x3 | ((reg_num & 0x1f) << 6) | ((phy_num & 0x1f) << 11) | ((0x3) << 2)); while (REG_GET_BIT(EMAC_GMIIADDR_REG, EMAC_MIIBUSY) == 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_GMIIADDR_REG, EMAC_MIIBUSY) == 1) { } REG_WRITE(EMAC_GMIIADDR_REG, 0x1 | ((reg_num & 0x1f) << 6) | ((phy_num & 0x1f) << 11) | (0x3 << 2)); while (REG_GET_BIT(EMAC_GMIIADDR_REG, EMAC_MIIBUSY) == 1) { } value = (REG_READ(EMAC_MIIDATA_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; } esp_err_t emac_reset(void) { REG_SET_BIT(EMAC_DMABUSMODE_REG, EMAC_SW_RST); if (emac_config.reset_timeout_ms) { int start = xTaskGetTickCount(); uint32_t timeout_ticks = (emac_config.reset_timeout_ms + portTICK_PERIOD_MS - 1) / portTICK_PERIOD_MS; while (timeout_ticks == 0 || (xTaskGetTickCount() - start < timeout_ticks)) { if (REG_GET_BIT(EMAC_DMABUSMODE_REG, EMAC_SW_RST) != EMAC_SW_RST) { goto reset_ok; } vTaskDelay(1); } ESP_LOGE(TAG, "Reset EMAC Timeout"); return ESP_ERR_TIMEOUT; } /* infinite wait loop */ else { while (REG_GET_BIT(EMAC_DMABUSMODE_REG, EMAC_SW_RST) == EMAC_SW_RST) { //nothing to do ,if stop here,maybe emac have not clk input. ESP_LOGI(TAG, "emac resetting ...."); } } reset_ok: ESP_LOGI(TAG, "emac reset done"); return ESP_OK; } 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.clock_mode = config->clock_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; emac_config.reset_timeout_ms = config->reset_timeout_ms; #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, "Can only configure flow_ctrl_enable==true if DMA_RX_BUF_NUM in menuconfig is >9. Disabling flow control."); } 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; emac_config.promiscuous_enable = config->promiscuous_enable; } static void emac_enable_intr() { REG_WRITE(EMAC_DMAIN_EN_REG, EMAC_INTR_ENABLE_BIT); } static void emac_disable_intr() { REG_WRITE(EMAC_DMAIN_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, currently only support for RMII"); ret = ESP_FAIL; } if (emac_config.clock_mode > ETH_CLOCK_GPIO17_OUT) { ESP_LOGE(TAG, "emac clock mode err"); 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 && !emac_config.emac_phy_get_partner_pause_enable) { 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; } 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, portMAX_DELAY); while (((uint32_t) & (emac_config.dma_etx[emac_config.dirty_tx])) != 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, 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"); } emac_poll_rx_cmd(); xSemaphoreGiveRecursive(emac_rx_xMutex); if (emac_config.emac_flow_ctrl_partner_support) { portENTER_CRITICAL(&g_emac_mux); if (pause_send && 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(); dma_extended_desc_t *cur_desc = (dma_extended_desc_t *)cur_rx_desc; while (cur_desc->basic.desc0 == EMAC_DESC_RX_OWN && cnt < DMA_RX_BUF_NUM) { cnt++; cur_desc = (dma_extended_desc_t *)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])) != 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; 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, portMAX_DELAY); while (emac_config.cnt_rx < DMA_RX_BUF_NUM) { if (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 & EMAC_DESC_RX_OWN) { break; } emac_config.cnt_rx++; if (emac_config.cnt_rx > DMA_RX_BUF_NUM) { ESP_LOGE(TAG, "emac rx buf full"); } uint32_t tmp_dirty = emac_config.dirty_rx; emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[tmp_dirty].basic.desc2), (((emac_config.dma_erx[tmp_dirty].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH), NULL); } 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, portMAX_DELAY); if ((((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx])) != cur_rx_desc)) { while ((((uint32_t) & (emac_config.dma_erx[emac_config.dirty_rx])) != cur_rx_desc) && emac_config.cnt_rx < DMA_RX_BUF_NUM) { emac_config.cnt_rx++; if (emac_config.cnt_rx > DMA_RX_BUF_NUM) { ESP_LOGE(TAG, "emac rx buf full"); } uint32_t tmp_dirty = emac_config.dirty_rx; emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[tmp_dirty].basic.desc2), (((emac_config.dma_erx[tmp_dirty].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH), NULL); 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)) { while (emac_config.cnt_rx < DMA_RX_BUF_NUM) { if (emac_config.dma_erx[emac_config.dirty_rx].basic.desc0 & EMAC_DESC_RX_OWN) { break; } emac_config.cnt_rx++; if (emac_config.cnt_rx > DMA_RX_BUF_NUM) { ESP_LOGE(TAG, "emac rx buf full"); } uint32_t tmp_dirty = emac_config.dirty_rx; emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[tmp_dirty].basic.desc2), (((emac_config.dma_erx[tmp_dirty].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH), NULL); } } } } 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) { if (emac_get_rxbuf_count_in_intr() < FLOW_CONTROL_HIGH_WATERMARK && !pause_send) { 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_ADDR0HIGH_REG, EMAC_ADDRESS0_HI, (emac_config.macaddr[5] << 8) | (emac_config.macaddr[4])); REG_WRITE(EMAC_ADDR0LOW_REG, (emac_config.macaddr[3] << 24) | (emac_config.macaddr[2] << 16) | (emac_config.macaddr[1] << 8) | (emac_config.macaddr[0])); } 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_EMACDUPLEX); } else { REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACDUPLEX); } if (emac_config.emac_phy_get_speed_mode() == ETH_SPEED_MODE_100M) { REG_SET_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACFESPEED); } else { REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_EMACFESPEED); } #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) { if (emac_config.emac_phy_get_partner_pause_enable() && 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) { emac_check_phy_init(); ESP_LOGD(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_LOGD(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) { ESP_LOGE(TAG, "tx netif is not ready, emac_status=%d", emac_config.emac_status); ret = ESP_ERR_INVALID_STATE; return ret; } xSemaphoreTakeRecursive(emac_tx_xMutex, portMAX_DELAY); if (emac_config.cnt_tx == DMA_TX_BUF_NUM - 1) { ESP_LOGD(TAG, "tx buf full"); ret = ESP_ERR_NO_MEM; goto _exit; } memcpy((void *)(emac_config.dma_etx[emac_config.cur_tx].basic.desc2), 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((void *)&emac_config, 0, sizeof(struct emac_config_data)); } void emac_process_link_check(void) { if (emac_config.emac_status != EMAC_RUNTIME_START) { return; } if (emac_config.emac_phy_check_link()) { if (!emac_config.phy_link_up) { emac_process_link_updown(true); } } else { if (emac_config.phy_link_up) { 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", (CONFIG_EMAC_CHECK_LINK_PERIOD_MS / portTICK_PERIOD_MS), pdTRUE, NULL, 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_LOGD(TAG, "emac start"); cmd->err = EMAC_CMD_OK; if (emac_reset() != ESP_OK) { return; } emac_reset_dma_chain(); emac_dma_init(); emac_set_macaddr_reg(); emac_set_tx_base_reg(); emac_set_rx_base_reg(); emac_mac_init(); /* check if enable promiscuous mode */ if (emac_config.promiscuous_enable) { emac_enable_promiscuous(); } else { emac_disable_promiscuous(); } 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()) { if (!emac_link_check_timer_start()) { 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_LOGD(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; } #ifdef CONFIG_PM_ENABLE esp_err_t err = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "ethernet", &s_pm_lock); if (err != ESP_OK) { return err; } esp_pm_lock_acquire(s_pm_lock); #endif //CONFIG_PM_ENABLE emac_enable_clk(true); /* init phy device */ if (emac_config.phy_init() != ESP_OK) { ESP_LOGE(TAG, "Initialise PHY device Timeout"); return ESP_FAIL; } if (emac_config.emac_status != EMAC_RUNTIME_NOT_INIT) { if (emac_ioctl(SIG_EMAC_START, (emac_par_t)(&post_cmd))) { open_cmd.err = EMAC_CMD_FAIL; goto cleanup; } } else { open_cmd.err = EMAC_CMD_FAIL; goto cleanup; } return EMAC_CMD_OK; cleanup: #ifdef CONFIG_PM_ENABLE esp_pm_lock_release(s_pm_lock); esp_pm_lock_delete(s_pm_lock); s_pm_lock = NULL; #endif //CONFIG_PM_ENABLE return open_cmd.err; } static void emac_stop(void *param) { struct emac_post_cmd *post_cmd = (struct emac_post_cmd *)param; ESP_LOGD(TAG, "emac stop"); emac_link_check_timer_stop(); emac_link_check_timer_delete(); emac_process_link_updown(false); emac_disable_intr(); 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_LOGD(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; } #ifdef CONFIG_PM_ENABLE esp_pm_lock_release(s_pm_lock); esp_pm_lock_delete(s_pm_lock); s_pm_lock = NULL; #endif // CONFIG_PM_ENABLE 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, 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_event_set_default_eth_handlers(); return esp_eth_init_internal(config); } esp_err_t esp_eth_init_internal(eth_config_t *config) { int i = 0; esp_err_t ret = ESP_OK; if (emac_config.emac_status != EMAC_RUNTIME_NOT_INIT) { goto _initialised; } /* dynamically alloc memory for ethernet dma */ emac_dma_rx_chain_buf = (dma_extended_desc_t *)heap_caps_malloc(sizeof(dma_extended_desc_t) * DMA_RX_BUF_NUM, MALLOC_CAP_DMA); emac_dma_tx_chain_buf = (dma_extended_desc_t *)heap_caps_malloc(sizeof(dma_extended_desc_t) * DMA_TX_BUF_NUM, MALLOC_CAP_DMA); for (i = 0; i < DMA_RX_BUF_NUM; i++) { emac_dma_rx_buf[i] = (uint8_t *)heap_caps_malloc(DMA_RX_BUF_SIZE, MALLOC_CAP_DMA); } for (i = 0; i < DMA_TX_BUF_NUM; i++) { emac_dma_tx_buf[i] = (uint8_t *)heap_caps_malloc(DMA_TX_BUF_SIZE, MALLOC_CAP_DMA); } emac_init_default_data(); if (config) { emac_set_user_config_data(config); } ret = emac_verify_args(); if (ret != ESP_OK) { goto _verify_err; } emac_config.emac_phy_power_enable(true); //before set emac reg must enable clk periph_module_enable(PERIPH_EMAC_MODULE); if (emac_config.clock_mode != ETH_CLOCK_GPIO0_IN) { #if CONFIG_SPIRAM_SUPPORT // make sure Ethernet won't have conflict with PSRAM if (emac_config.clock_mode >= ETH_CLOCK_GPIO16_OUT) { if (esp_spiram_is_initialized()) { ESP_LOGE(TAG, "GPIO16 and GPIO17 are occupied by PSRAM, please switch to ETH_CLOCK_GPIO_IN or ETH_CLOCK_GPIO_OUT mode"); ret = ESP_FAIL; goto _verify_err; } else { ESP_LOGW(TAG, "Using GPIO16/17 to output Ethernet RMII clock, make sure you don't have PSRAM on board"); } } #endif // 50 MHz = 40MHz * (6 + 4) / (2 * (2 + 2) = 400MHz / 8 rtc_clk_apll_enable(1, 0, 0, 6, 2); REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_H_DIV_NUM, 0); REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_DIV_NUM, 0); if (emac_config.clock_mode == ETH_CLOCK_GPIO0_OUT) { PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); REG_WRITE(PIN_CTRL, 6); ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO0"); } else if (emac_config.clock_mode == ETH_CLOCK_GPIO16_OUT) { PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO16_U, FUNC_GPIO16_EMAC_CLK_OUT); ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO16"); } else if (emac_config.clock_mode == ETH_CLOCK_GPIO17_OUT) { PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO17_U, FUNC_GPIO17_EMAC_CLK_OUT_180); ESP_LOGD(TAG, "EMAC 50MHz inverted clock output on GPIO17"); } } 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.clock_mode == ETH_CLOCK_GPIO0_IN) { // external clock on GPIO0 REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN); REG_CLR_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_INT_OSC_EN); REG_SET_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL); ESP_LOGD(TAG, "External clock input 50MHz on GPIO0"); if (emac_config.mac_mode == ETH_MODE_MII) { REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_RX_EN); REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_TX_EN); } } else { // internal clock by APLL REG_CLR_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN); REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_INT_OSC_EN); REG_CLR_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL); } emac_config.emac_gpio_config(); emac_hw_init(); emac_macaddr_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", EMAC_TASK_STACK_SIZE, NULL, EMAC_TASK_PRIORITY, &emac_task_hdl); esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, 0, emac_process_intr, NULL, ð_intr_handle); emac_config.emac_status = EMAC_RUNTIME_INIT; return ESP_OK; _verify_err: free(emac_dma_rx_chain_buf); free(emac_dma_tx_chain_buf); emac_dma_rx_chain_buf = NULL; emac_dma_tx_chain_buf = NULL; for (i = 0; i < DMA_RX_BUF_NUM; i++) { free(emac_dma_rx_buf[i]); emac_dma_rx_buf[i] = NULL; } for (i = 0; i < DMA_TX_BUF_NUM; i++) { free(emac_dma_tx_buf[i]); emac_dma_tx_buf[i] = NULL; } _initialised: return ret; } esp_err_t esp_eth_deinit(void) { esp_err_t ret = ESP_OK; int i = 0; if (emac_config.emac_status == EMAC_RUNTIME_NOT_INIT) { goto _exit; } if (emac_config.emac_status == EMAC_RUNTIME_START) { esp_eth_disable(); } if (!emac_task_hdl) { ret = ESP_ERR_INVALID_STATE; goto _exit; } vTaskDelete(emac_task_hdl); emac_task_hdl = NULL; vQueueDelete(emac_xqueue); vSemaphoreDelete(emac_tx_xMutex); vSemaphoreDelete(emac_rx_xMutex); vSemaphoreDelete(emac_g_sem); emac_reset_dma_chain(); emac_config.emac_phy_power_enable(false); periph_module_disable(PERIPH_EMAC_MODULE); emac_config.emac_status = EMAC_RUNTIME_NOT_INIT; /* free memory that dynamically allocted */ free(emac_dma_rx_chain_buf); free(emac_dma_tx_chain_buf); emac_dma_rx_chain_buf = NULL; emac_dma_tx_chain_buf = NULL; for (i = 0; i < DMA_RX_BUF_NUM; i++) { free(emac_dma_rx_buf[i]); emac_dma_rx_buf[i] = NULL; } for (i = 0; i < DMA_TX_BUF_NUM; i++) { free(emac_dma_tx_buf[i]); emac_dma_tx_buf[i] = NULL; } esp_intr_free(eth_intr_handle); _exit: return ret; }