feat(esp_eth): a new folder structure of the driver and other improvements

Fixed memory leak in emac_esp_new_dma function.

Polished ESP EMAC cache management.

Added emac_periph definitions based on SoC features and improved(generalized) ESP EMAC GPIO
initialization.

Added ESP EMAC GPIO reservation.

Added check for frame error condition indicated by EMAC DMA and created a target test.
This commit is contained in:
Ondrej Kosta
2024-04-26 12:27:54 +02:00
parent ee8a9e8410
commit d15a9c2c48
63 changed files with 2068 additions and 1442 deletions
@@ -0,0 +1,725 @@
/*
* SPDX-FileCopyrightText: 2019-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <stdlib.h>
#include <sys/cdefs.h>
#include <stdarg.h>
#include <inttypes.h>
#include "esp_private/periph_ctrl.h"
#include "esp_attr.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_pm.h"
#include "esp_mac.h"
#include "esp_cpu.h"
#include "esp_heap_caps.h"
#include "esp_intr_alloc.h"
#ifdef CONFIG_IDF_TARGET_ESP32
#include "esp_clock_output.h"
#endif // CONFIG_IDF_TARGET_ESP32
#include "hal/clk_tree_ll.h"
#include "esp_private/esp_clk.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "hal/emac_hal.h"
#include "soc/soc.h"
#include "clk_ctrl_os.h"
#include "sdkconfig.h"
#include "esp_rom_sys.h"
#include "esp_private/eth_mac_esp_dma.h"
#include "esp_private/eth_mac_esp_gpio.h"
static const char *TAG = "esp.emac";
#define PHY_OPERATION_TIMEOUT_US (1000)
#define MAC_STOP_TIMEOUT_US (2500) // this is absolute maximum for 10Mbps, it is 10 times faster for 100Mbps
#define FLOW_CONTROL_LOW_WATER_MARK (CONFIG_ETH_DMA_RX_BUFFER_NUM / 3)
#define FLOW_CONTROL_HIGH_WATER_MARK (FLOW_CONTROL_LOW_WATER_MARK * 2)
#define EMAC_ALLOW_INTR_PRIORITY_MASK ESP_INTR_FLAG_LOWMED
#define RMII_CLK_HZ (50000000)
#define RMII_10M_SPEED_RX_TX_CLK_DIV (19)
#define RMII_100M_SPEED_RX_TX_CLK_DIV (1)
#if CONFIG_IDF_TARGET_ESP32P4
// ESP32P4 EMAC interface clock configuration is shared among other modules in registers
#define EMAC_IF_RCC_ATOMIC() PERIPH_RCC_ATOMIC()
#else
#define EMAC_IF_RCC_ATOMIC()
#endif
typedef struct {
esp_eth_mac_t parent;
esp_eth_mediator_t *eth;
emac_hal_context_t hal;
intr_handle_t intr_hdl;
TaskHandle_t rx_task_hdl;
emac_esp_dma_handle_t emac_dma_hndl;
uint32_t sw_reset_timeout_ms;
uint32_t frames_remain;
uint32_t free_rx_descriptor;
uint32_t flow_control_high_water_mark;
uint32_t flow_control_low_water_mark;
uint8_t addr[ETH_ADDR_LEN];
bool isr_need_yield;
bool flow_ctrl_enabled; // indicates whether the user want to do flow control
bool do_flow_ctrl; // indicates whether we need to do software flow control
bool use_pll; // Only use (A/M)PLL in EMAC_DATA_INTERFACE_RMII && EMAC_CLK_OUT
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_handle_t pm_lock;
#endif
eth_mac_dma_burst_len_t dma_burst_len;
// ---- Chip specifics ----
#ifdef CONFIG_IDF_TARGET_ESP32
esp_clock_output_mapping_handle_t rmii_clk_hdl; // we use the esp_clock_output driver to output a pre-configured APLL clock as the RMII reference clock
#endif
} emac_esp32_t;
static esp_err_t emac_esp_alloc_driver_obj(const eth_mac_config_t *config, emac_esp32_t **emac_out_hdl);
static void emac_esp_free_driver_obj(emac_esp32_t *emac);
static esp_err_t emac_esp32_start(esp_eth_mac_t *mac);
static esp_err_t emac_esp32_stop(esp_eth_mac_t *mac);
static esp_err_t emac_esp32_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(eth, ESP_ERR_INVALID_ARG, err, TAG, "can't set mac's mediator to null");
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
emac->eth = eth;
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t reg_value)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
ESP_GOTO_ON_FALSE(!emac_hal_is_mii_busy(&emac->hal), ESP_ERR_INVALID_STATE, err, TAG, "phy is busy");
emac_hal_set_phy_data(&emac->hal, reg_value);
emac_hal_set_phy_cmd(&emac->hal, phy_addr, phy_reg, true);
/* polling the busy flag */
uint32_t to = 0;
bool busy = true;
do {
esp_rom_delay_us(100);
busy = emac_hal_is_mii_busy(&emac->hal);
to += 100;
} while (busy && to < PHY_OPERATION_TIMEOUT_US);
ESP_GOTO_ON_FALSE(!busy, ESP_ERR_TIMEOUT, err, TAG, "phy is busy");
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr, uint32_t phy_reg, uint32_t *reg_value)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(reg_value, ESP_ERR_INVALID_ARG, err, TAG, "can't set reg_value to null");
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
ESP_GOTO_ON_FALSE(!emac_hal_is_mii_busy(&emac->hal), ESP_ERR_INVALID_STATE, err, TAG, "phy is busy");
emac_hal_set_phy_cmd(&emac->hal, phy_addr, phy_reg, false);
/* polling the busy flag */
uint32_t to = 0;
bool busy = true;
do {
esp_rom_delay_us(100);
busy = emac_hal_is_mii_busy(&emac->hal);
to += 100;
} while (busy && to < PHY_OPERATION_TIMEOUT_US);
ESP_GOTO_ON_FALSE(!busy, ESP_ERR_TIMEOUT, err, TAG, "phy is busy");
/* Store value */
*reg_value = emac_hal_get_phy_data(&emac->hal);
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_set_addr(esp_eth_mac_t *mac, uint8_t *addr)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "can't set mac addr to null");
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
memcpy(emac->addr, addr, 6);
emac_hal_set_address(&emac->hal, emac->addr);
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_get_addr(esp_eth_mac_t *mac, uint8_t *addr)
{
esp_err_t ret = ESP_OK;
ESP_GOTO_ON_FALSE(addr, ESP_ERR_INVALID_ARG, err, TAG, "can't set mac addr to null");
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
memcpy(addr, emac->addr, 6);
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_set_link(esp_eth_mac_t *mac, eth_link_t link)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
switch (link) {
case ETH_LINK_UP:
ESP_GOTO_ON_ERROR(esp_intr_enable(emac->intr_hdl), err, TAG, "enable interrupt failed");
emac_esp32_start(mac);
ESP_LOGD(TAG, "emac started");
break;
case ETH_LINK_DOWN:
ESP_GOTO_ON_ERROR(esp_intr_disable(emac->intr_hdl), err, TAG, "disable interrupt failed");
emac_esp32_stop(mac);
ESP_LOGD(TAG, "emac stopped");
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "unknown link status");
break;
}
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_set_speed(esp_eth_mac_t *mac, eth_speed_t speed)
{
esp_err_t ret = ESP_ERR_INVALID_ARG;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
if (speed >= ETH_SPEED_10M && speed < ETH_SPEED_MAX) {
#ifdef CONFIG_IDF_TARGET_ESP32P4
// Set RMII clk_rx/clk_tx divider to get 25MHz for 100mbps mode or 2.5MHz for 10mbps mode
if (emac_hal_get_phy_intf(&emac->hal) == EMAC_DATA_INTERFACE_RMII) {
if (speed == ETH_SPEED_10M) {
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_rmii_rx_tx_div(&emac->hal, RMII_10M_SPEED_RX_TX_CLK_DIV);
}
} else {
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_rmii_rx_tx_div(&emac->hal, RMII_100M_SPEED_RX_TX_CLK_DIV);
}
}
}
#endif
emac_hal_set_speed(&emac->hal, speed);
ESP_LOGD(TAG, "working in %iMbps", speed == ETH_SPEED_10M ? 10 : 100);
return ESP_OK;
}
return ret;
}
static esp_err_t emac_esp32_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex)
{
esp_err_t ret = ESP_ERR_INVALID_ARG;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
if (duplex == ETH_DUPLEX_HALF || duplex == ETH_DUPLEX_FULL) {
emac_hal_set_duplex(&emac->hal, duplex);
ESP_LOGD(TAG, "working in %s duplex", duplex == ETH_DUPLEX_HALF ? "half" : "full");
return ESP_OK;
}
return ret;
}
static esp_err_t emac_esp32_set_promiscuous(esp_eth_mac_t *mac, bool enable)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
emac_hal_set_promiscuous(&emac->hal, enable);
return ESP_OK;
}
static esp_err_t emac_esp32_enable_flow_ctrl(esp_eth_mac_t *mac, bool enable)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
emac->flow_ctrl_enabled = enable;
return ESP_OK;
}
static esp_err_t emac_esp32_set_peer_pause_ability(esp_eth_mac_t *mac, uint32_t ability)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
// we want to enable flow control, and peer does support pause function
// then configure the MAC layer to enable flow control feature
if (emac->flow_ctrl_enabled && ability) {
emac_hal_enable_flow_ctrl(&emac->hal, true);
emac->do_flow_ctrl = true;
} else {
emac_hal_enable_flow_ctrl(&emac->hal, false);
emac->do_flow_ctrl = false;
ESP_LOGD(TAG, "Flow control not enabled for the link");
}
return ESP_OK;
}
esp_err_t emac_esp_custom_ioctl(esp_eth_mac_t *mac, int cmd, void *data)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
switch (cmd)
{
case ETH_MAC_ESP_CMD_PTP_ENABLE:
return ESP_ERR_NOT_SUPPORTED;
case ETH_MAC_ESP_CMD_SET_TDES0_CFG_BITS:
ESP_RETURN_ON_FALSE(data != NULL, ESP_ERR_INVALID_ARG, TAG, "cannot set DMA tx desc flag to null");
emac_esp_dma_set_tdes0_ctrl_bits(emac->emac_dma_hndl, *(uint32_t *)data);
break;
case ETH_MAC_ESP_CMD_CLEAR_TDES0_CFG_BITS:
ESP_RETURN_ON_FALSE(data != NULL, ESP_ERR_INVALID_ARG, TAG, "cannot clear DMA tx desc flag with null");
emac_esp_dma_clear_tdes0_ctrl_bits(emac->emac_dma_hndl, *(uint32_t *)data);
break;
default:
ESP_RETURN_ON_ERROR(ESP_ERR_INVALID_ARG, TAG, "unknown io command: %i", cmd);
}
return ESP_OK;
}
static esp_err_t emac_esp32_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
uint32_t sent_len = emac_esp_dma_transmit_frame(emac->emac_dma_hndl, buf, length);
ESP_GOTO_ON_FALSE(sent_len == length, ESP_ERR_NO_MEM, err, TAG, "insufficient TX buffer size");
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_transmit_multiple_bufs(esp_eth_mac_t *mac, uint32_t argc, va_list args)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
uint8_t *bufs[argc];
uint32_t len[argc];
uint32_t exp_len = 0;
for (int i = 0; i < argc; i++) {
bufs[i] = va_arg(args, uint8_t *);
len[i] = va_arg(args, uint32_t);
exp_len += len[i];
}
uint32_t sent_len = emac_esp_dma_transmit_multiple_buf_frame(emac->emac_dma_hndl, bufs, len, argc);
ESP_GOTO_ON_FALSE(sent_len == exp_len, ESP_ERR_INVALID_SIZE, err, TAG, "insufficient TX buffer size");
return ESP_OK;
err:
return ret;
}
static esp_err_t emac_esp32_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length)
{
esp_err_t ret = ESP_OK;
uint32_t expected_len = *length;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
ESP_GOTO_ON_FALSE(buf && length, ESP_ERR_INVALID_ARG, err, TAG, "can't set buf and length to null");
uint32_t receive_len = emac_esp_dma_receive_frame(emac->emac_dma_hndl, buf, expected_len);
emac_esp_dma_get_remain_frames(emac->emac_dma_hndl, &emac->frames_remain, &emac->free_rx_descriptor);
/* we need to check the return value in case the buffer size is not enough */
ESP_GOTO_ON_FALSE(expected_len >= receive_len, ESP_ERR_INVALID_SIZE, err, TAG, "received buffer longer than expected");
*length = receive_len;
return ESP_OK;
err:
*length = expected_len;
return ret;
}
static void emac_esp32_rx_task(void *arg)
{
emac_esp32_t *emac = (emac_esp32_t *)arg;
uint8_t *buffer = NULL;
while (1) {
// block indefinitely until got notification from underlay event
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
do {
/* set max expected frame len */
uint32_t frame_len = ETH_MAX_PACKET_SIZE;
buffer = emac_esp_dma_alloc_recv_buf(emac->emac_dma_hndl, &frame_len);
/* we have memory to receive the frame of maximal size previously defined */
if (buffer != NULL) {
uint32_t recv_len = emac_esp_dma_receive_frame(emac->emac_dma_hndl, buffer, EMAC_DMA_BUF_SIZE_AUTO);
if (recv_len == 0) {
ESP_LOGE(TAG, "frame copy error");
free(buffer);
/* ensure that interface to EMAC does not get stuck with unprocessed frames */
emac_esp_dma_flush_recv_frame(emac->emac_dma_hndl);
} else if (frame_len > recv_len) {
ESP_LOGE(TAG, "received frame was truncated");
free(buffer);
} else {
ESP_LOGD(TAG, "receive len= %" PRIu32, recv_len);
emac->eth->stack_input(emac->eth, buffer, recv_len);
}
/* if allocation failed and there is a waiting frame */
} else if (frame_len) {
ESP_LOGE(TAG, "no mem for receive buffer");
/* ensure that interface to EMAC does not get stuck with unprocessed frames */
emac_esp_dma_flush_recv_frame(emac->emac_dma_hndl);
}
emac_esp_dma_get_remain_frames(emac->emac_dma_hndl, &emac->frames_remain, &emac->free_rx_descriptor);
#if CONFIG_ETH_SOFT_FLOW_CONTROL
// we need to do extra checking of remained frames in case there are no unhandled frames left, but pause frame is still undergoing
if ((emac->free_rx_descriptor < emac->flow_control_low_water_mark) && emac->do_flow_ctrl && emac->frames_remain) {
emac_hal_send_pause_frame(&emac->hal, true);
} else if ((emac->free_rx_descriptor > emac->flow_control_high_water_mark) || !emac->frames_remain) {
emac_hal_send_pause_frame(&emac->hal, false);
}
#endif
} while (emac->frames_remain);
}
vTaskDelete(NULL);
}
static esp_err_t emac_config_pll_clock(emac_esp32_t *emac)
{
uint32_t expt_freq = RMII_CLK_HZ; // 50 MHz
uint32_t real_freq = 0;
#if CONFIG_IDF_TARGET_ESP32
// the RMII reference comes from the APLL
periph_rtc_apll_acquire();
emac->use_pll = true;
esp_err_t ret = periph_rtc_apll_freq_set(expt_freq, &real_freq);
ESP_RETURN_ON_FALSE(ret != ESP_ERR_INVALID_ARG, ESP_FAIL, TAG, "Set APLL clock coefficients failed");
if (ret == ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "APLL is occupied already, it is working at %" PRIu32 " Hz", real_freq);
}
#elif CONFIG_IDF_TARGET_ESP32P4
// the RMII reference comes from the MPLL
periph_rtc_mpll_acquire();
emac->use_pll = true;
esp_err_t ret = periph_rtc_mpll_freq_set(expt_freq * 2, &real_freq); // cannot set 50MHz at MPLL, the nearest possible freq is 100 MHz
if (ret == ESP_ERR_INVALID_STATE) {
ESP_LOGW(TAG, "MPLL is occupied already, it is working at %" PRIu32 " Hz", real_freq);
}
// Set divider of MPLL clock
if (real_freq > RMII_CLK_HZ) {
int32_t div = real_freq / RMII_CLK_HZ - 1;
clk_ll_pll_f50m_set_divider(div);
// compute real RMII CLK frequency
real_freq /= div + 1;
}
#endif
// If the difference of real RMII CLK frequency is not within 50 ppm, i.e. 2500 Hz, the (A/M)PLL is unusable
ESP_RETURN_ON_FALSE(abs((int)real_freq - (int)expt_freq) <= 2500,
ESP_ERR_INVALID_STATE, TAG, "The (A/M)PLL is working at an unusable frequency %" PRIu32 " Hz", real_freq);
return ESP_OK;
}
static esp_err_t emac_esp32_init(esp_eth_mac_t *mac)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
esp_eth_mediator_t *eth = emac->eth;
ESP_GOTO_ON_ERROR(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL), err, TAG, "lowlevel init failed");
/* software reset */
emac_hal_reset(&emac->hal);
uint32_t to = 0;
for (to = 0; to < emac->sw_reset_timeout_ms / 10; to++) {
if (emac_hal_is_reset_done(&emac->hal)) {
break;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
ESP_GOTO_ON_FALSE(to < emac->sw_reset_timeout_ms / 10, ESP_ERR_TIMEOUT, err, TAG, "reset timeout");
/* set smi clock */
emac_hal_set_csr_clock_range(&emac->hal, esp_clk_apb_freq());
/* init mac registers by default */
emac_hal_init_mac_default(&emac->hal);
/* init dma registers with selected EMAC-DMA configuration */
emac_hal_dma_config_t dma_config = { .dma_burst_len = emac->dma_burst_len };
emac_hal_init_dma_default(&emac->hal, &dma_config);
/* get emac address from efuse */
ESP_GOTO_ON_ERROR(esp_read_mac(emac->addr, ESP_MAC_ETH), err, TAG, "fetch ethernet mac address failed");
/* set MAC address to emac register */
emac_hal_set_address(&emac->hal, emac->addr);
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_acquire(emac->pm_lock);
#endif
return ESP_OK;
err:
eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL);
return ret;
}
static esp_err_t emac_esp32_deinit(esp_eth_mac_t *mac)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
esp_eth_mediator_t *eth = emac->eth;
#ifdef CONFIG_PM_ENABLE
esp_pm_lock_release(emac->pm_lock);
#endif
emac_hal_stop(&emac->hal);
eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL);
return ESP_OK;
}
static esp_err_t emac_esp32_start(esp_eth_mac_t *mac)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
/* reset descriptor chain */
emac_esp_dma_reset(emac->emac_dma_hndl);
emac_hal_start(&emac->hal);
return ESP_OK;
}
static esp_err_t emac_esp32_stop(esp_eth_mac_t *mac)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
esp_err_t ret = ESP_OK;
int32_t to = 0;
do {
if ((ret = emac_hal_stop(&emac->hal)) == ESP_OK) {
break;
}
to += 25;
esp_rom_delay_us(25);
} while (to < MAC_STOP_TIMEOUT_US);
return ret;
}
static esp_err_t emac_esp32_del(esp_eth_mac_t *mac)
{
emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
emac_esp_free_driver_obj(emac);
emac_esp_gpio_deinit_all();
// disable bus clock
PERIPH_RCC_ATOMIC() {
emac_ll_enable_bus_clock(0, false);
}
return ESP_OK;
}
// To achieve a better performance, we put the ISR always in IRAM
IRAM_ATTR void emac_isr_default_handler(void *args)
{
emac_hal_context_t *hal = (emac_hal_context_t *)args;
uint32_t intr_stat = emac_hal_get_intr_status(hal);
emac_hal_clear_corresponding_intr(hal, intr_stat);
#if EMAC_LL_CONFIG_ENABLE_INTR_MASK & EMAC_LL_INTR_RECEIVE_ENABLE
if (intr_stat & EMAC_LL_DMA_RECEIVE_FINISH_INTR) {
emac_esp32_t *emac = __containerof(hal, emac_esp32_t, hal);
BaseType_t high_task_wakeup = pdFALSE;
/* notify receive task */
vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup);
if (high_task_wakeup == pdTRUE) {
portYIELD_FROM_ISR();
}
}
#endif
}
static void emac_esp_free_driver_obj(emac_esp32_t *emac)
{
if (emac) {
if (emac->rx_task_hdl) {
vTaskDelete(emac->rx_task_hdl);
}
if (emac->intr_hdl) {
esp_intr_free(emac->intr_hdl);
}
if (emac->use_pll) {
#if CONFIG_IDF_TARGET_ESP32
periph_rtc_apll_release();
#elif CONFIG_IDF_TARGET_ESP32P4
periph_rtc_mpll_release();
#endif
}
#ifdef CONFIG_IDF_TARGET_ESP32
if (emac->rmii_clk_hdl) {
esp_clock_output_stop(emac->rmii_clk_hdl);
}
#endif // CONFIG_IDF_TARGET_ESP32
#ifdef CONFIG_PM_ENABLE
if (emac->pm_lock) {
esp_pm_lock_delete(emac->pm_lock);
}
#endif // CONFIG_PM_ENABLE
emac_esp_del_dma(emac->emac_dma_hndl);
free(emac);
}
}
static esp_err_t emac_esp_alloc_driver_obj(const eth_mac_config_t *config, emac_esp32_t **emac_out_hdl)
{
esp_err_t ret = ESP_OK;
emac_esp32_t *emac = NULL;
if (config->flags & ETH_MAC_FLAG_WORK_WITH_CACHE_DISABLE) {
emac = heap_caps_calloc(1, sizeof(emac_esp32_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
} else {
emac = calloc(1, sizeof(emac_esp32_t));
}
ESP_GOTO_ON_FALSE(emac, ESP_ERR_NO_MEM, err, TAG, "no mem for esp emac object");
ESP_GOTO_ON_ERROR(emac_esp_new_dma(NULL, &emac->emac_dma_hndl), err, TAG, "create EMAC DMA object failed");
/* alloc PM lock */
#ifdef CONFIG_PM_ENABLE
ESP_GOTO_ON_ERROR(esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "emac_esp32", &emac->pm_lock), err, TAG, "create pm lock failed");
#endif
/* create rx task */
BaseType_t core_num = tskNO_AFFINITY;
if (config->flags & ETH_MAC_FLAG_PIN_TO_CORE) {
core_num = esp_cpu_get_core_id();
}
BaseType_t xReturned = xTaskCreatePinnedToCore(emac_esp32_rx_task, "emac_rx", config->rx_task_stack_size, emac,
config->rx_task_prio, &emac->rx_task_hdl, core_num);
ESP_GOTO_ON_FALSE(xReturned == pdPASS, ESP_FAIL, err, TAG, "create emac_rx task failed");
*emac_out_hdl = emac;
err:
return ret;
}
static esp_err_t emac_esp_config_data_interface(const eth_esp32_emac_config_t *esp32_emac_config, emac_esp32_t *emac)
{
esp_err_t ret = ESP_OK;
switch (esp32_emac_config->interface) {
case EMAC_DATA_INTERFACE_MII:
/* MII interface GPIO initialization */
#if SOC_EMAC_MII_USE_GPIO_MATRIX
ESP_GOTO_ON_ERROR(emac_esp_gpio_matrix_init_mii(&esp32_emac_config->emac_dataif_gpio.mii), err, TAG, "failed to initialize EMAC MII GPIO Matrix");
#else
eth_mac_mii_gpio_config_t *mii_data_gpio = NULL;
#if SOC_EMAC_USE_MULTI_IO_MUX
mii_data_gpio = &esp32_emac_config->emac_dataif_gpio.mii;
#endif // SOC_EMAC_USE_MULTI_IO_MUX
ESP_GOTO_ON_ERROR(emac_esp_iomux_init_mii(mii_data_gpio), err, TAG, "invalid EMAC MII data plane GPIO");
#endif // SOC_EMAC_MII_USE_GPIO_MATRIX
/* Enable MII clock */
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_enable_mii(&emac->hal);
}
break;
case EMAC_DATA_INTERFACE_RMII:
/* RMII interface GPIO initialization */
const eth_mac_rmii_gpio_config_t *rmii_data_gpio = NULL;
#if SOC_EMAC_USE_MULTI_IO_MUX
rmii_data_gpio = &esp32_emac_config->emac_dataif_gpio.rmii;
#endif // SOC_EMAC_USE_MULTI_IO_MUX
ESP_GOTO_ON_ERROR(emac_esp_iomux_init_rmii(rmii_data_gpio), err, TAG, "invalid EMAC RMII data plane GPIO");
/* If ref_clk is configured as input */
if (esp32_emac_config->clock_config.rmii.clock_mode == EMAC_CLK_EXT_IN) {
ESP_GOTO_ON_ERROR(emac_esp_iomux_rmii_clk_input(esp32_emac_config->clock_config.rmii.clock_gpio), err, TAG, "invalid EMAC RMII clock input GPIO");
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_enable_rmii_input(&emac->hal);
}
} else if (esp32_emac_config->clock_config.rmii.clock_mode == EMAC_CLK_OUT) {
ESP_GOTO_ON_ERROR(emac_config_pll_clock(emac), err, TAG, "Configure (A/M)PLL for RMII failed");
#if CONFIG_IDF_TARGET_ESP32P4
/* Output RMII clock is routed back to input externally */
ESP_GOTO_ON_FALSE(esp32_emac_config->clock_config_out_in.rmii.clock_mode == EMAC_CLK_EXT_IN && esp32_emac_config->clock_config_out_in.rmii.clock_gpio >= 0,
ESP_ERR_INVALID_ARG, err, TAG, "invalid EMAC input of output clock mode");
ESP_GOTO_ON_ERROR(emac_esp_iomux_rmii_clk_input(esp32_emac_config->clock_config_out_in.rmii.clock_gpio), err, TAG, "invalid EMAC RMII clock input GPIO");
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_enable_rmii_input(&emac->hal);
}
#elif CONFIG_IDF_TARGET_ESP32
// we can also use the IOMUX to route the APLL clock to specific GPIO
if (esp32_emac_config->clock_config.rmii.clock_gpio == EMAC_APPL_CLK_OUT_GPIO) {
ESP_GOTO_ON_ERROR(esp_clock_output_start(CLKOUT_SIG_APLL, EMAC_APPL_CLK_OUT_GPIO, &emac->rmii_clk_hdl),
err, TAG, "start APLL clock output failed");
} else
#endif
{
ESP_GOTO_ON_ERROR(emac_esp_iomux_rmii_clk_ouput(esp32_emac_config->clock_config.rmii.clock_gpio), err, TAG, "invalid EMAC RMII clock output GPIO");
}
/* Enable RMII Output clock */
EMAC_IF_RCC_ATOMIC() {
emac_hal_clock_enable_rmii_output(&emac->hal);
}
} else {
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid EMAC clock mode");
}
break;
default:
ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid EMAC Data Interface:%i", esp32_emac_config->interface);
}
err:
return ret;
}
esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_esp32_emac_config_t *esp32_config, const eth_mac_config_t *config)
{
esp_err_t ret_code = ESP_OK;
esp_eth_mac_t *ret = NULL;
emac_esp32_t *emac = NULL;
ESP_RETURN_ON_FALSE(config, NULL, TAG, "can't set mac config to null");
if (esp32_config->intr_priority > 0) {
ESP_RETURN_ON_FALSE(1 << (esp32_config->intr_priority) & EMAC_ALLOW_INTR_PRIORITY_MASK, NULL,
TAG, "invalid interrupt priority: %d", esp32_config->intr_priority);
}
ret_code = emac_esp_alloc_driver_obj(config, &emac);
ESP_RETURN_ON_FALSE(ret_code == ESP_OK, NULL, TAG, "alloc driver object failed");
// enable bus clock for the EMAC module, and reset the registers into default state
// this must be called before HAL layer initialization
PERIPH_RCC_ATOMIC() {
emac_ll_enable_bus_clock(0, true);
emac_ll_reset_register(0);
}
/* initialize hal layer driver */
emac_hal_init(&emac->hal);
/* alloc interrupt */
int isr_flags = 0;
if (esp32_config->intr_priority > 0) {
isr_flags |= 1 << (esp32_config->intr_priority);
} else {
isr_flags |= ESP_INTR_FLAG_LOWMED;
}
if (config->flags & ETH_MAC_FLAG_WORK_WITH_CACHE_DISABLE) {
isr_flags |= ESP_INTR_FLAG_IRAM;
}
ret_code = esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, isr_flags,
emac_isr_default_handler, &emac->hal, &(emac->intr_hdl));
ESP_GOTO_ON_FALSE(ret_code == ESP_OK, NULL, err, TAG, "alloc emac interrupt failed");
/* init GPIO used by SMI interface */
ret_code = emac_esp_gpio_init_smi(&esp32_config->smi_gpio);
ESP_GOTO_ON_FALSE(ret_code == ESP_OK, NULL, err, TAG, "SMI GPIO init failed");
/* init GPIO and CLK for data interface */
ret_code = emac_esp_config_data_interface(esp32_config, emac);
ESP_GOTO_ON_FALSE(ret_code == ESP_OK, NULL, err, TAG, "config emac interface failed");
emac->dma_burst_len = esp32_config->dma_burst_len;
emac->sw_reset_timeout_ms = config->sw_reset_timeout_ms;
emac->flow_control_high_water_mark = FLOW_CONTROL_HIGH_WATER_MARK;
emac->flow_control_low_water_mark = FLOW_CONTROL_LOW_WATER_MARK;
emac->parent.set_mediator = emac_esp32_set_mediator;
emac->parent.init = emac_esp32_init;
emac->parent.deinit = emac_esp32_deinit;
emac->parent.start = emac_esp32_start;
emac->parent.stop = emac_esp32_stop;
emac->parent.del = emac_esp32_del;
emac->parent.write_phy_reg = emac_esp32_write_phy_reg;
emac->parent.read_phy_reg = emac_esp32_read_phy_reg;
emac->parent.set_addr = emac_esp32_set_addr;
emac->parent.get_addr = emac_esp32_get_addr;
emac->parent.set_speed = emac_esp32_set_speed;
emac->parent.set_duplex = emac_esp32_set_duplex;
emac->parent.set_link = emac_esp32_set_link;
emac->parent.set_promiscuous = emac_esp32_set_promiscuous;
emac->parent.set_peer_pause_ability = emac_esp32_set_peer_pause_ability;
emac->parent.enable_flow_ctrl = emac_esp32_enable_flow_ctrl;
emac->parent.transmit = emac_esp32_transmit;
emac->parent.transmit_vargs = emac_esp32_transmit_multiple_bufs;
emac->parent.receive = emac_esp32_receive;
emac->parent.custom_ioctl = emac_esp_custom_ioctl;
return &(emac->parent);
err:
emac_esp_free_driver_obj(emac);
emac_esp_gpio_deinit_all();
return ret;
}
@@ -0,0 +1,484 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_check.h"
#include "esp_dma_utils.h"
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#include "esp_cache.h"
#endif
#include "hal/emac_hal.h"
#include "esp_private/eth_mac_esp_dma.h"
#define ETH_CRC_LENGTH (4)
#define EMAC_ALLOC_BUF_MAGIC_ID 0x1E1C8416
#define EMAC_TDES0_FS_CTRL_FLAGS_MASK 0x0FCC0000 // modifiable bits mask associated with the First Segment
#define EMAC_TDES0_LS_CTRL_FLAGS_MASK 0x40000000 // modifiable bits mask associated with the Last Segment
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#define DMA_CACHE_WB(addr, size) do { \
esp_err_t msync_ret = esp_cache_msync((void *)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_C2M); \
assert(msync_ret == ESP_OK); \
} while(0)
#else
#define DMA_CACHE_WB(addr, size)
#endif
#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
#define DMA_CACHE_INVALIDATE(addr, size) do { \
esp_err_t msync_ret = esp_cache_msync((void *)addr, size, ESP_CACHE_MSYNC_FLAG_DIR_M2C); \
assert(msync_ret == ESP_OK); \
} while(0)
#else
#define DMA_CACHE_INVALIDATE(addr, size)
#endif
static const char *TAG = "esp.emac.dma";
struct emac_esp_dma_t
{
emac_hal_context_t hal;
uint32_t tx_desc_flags;
uint32_t rx_desc_flags;
void *descriptors;
eth_dma_rx_descriptor_t *rx_desc;
eth_dma_tx_descriptor_t *tx_desc;
uint8_t *rx_buf[CONFIG_ETH_DMA_RX_BUFFER_NUM];
uint8_t *tx_buf[CONFIG_ETH_DMA_TX_BUFFER_NUM];
};
typedef struct {
#ifndef NDEBUG
uint32_t magic_id;
#endif // NDEBUG
uint32_t copy_len;
}__attribute__((packed)) emac_esp_dma_auto_buf_info_t;
void emac_esp_dma_reset(emac_esp_dma_handle_t emac_esp_dma)
{
/* reset DMA descriptors */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(emac_esp_dma->descriptors);
emac_esp_dma->tx_desc = (eth_dma_tx_descriptor_t *)(emac_esp_dma->descriptors +
sizeof(eth_dma_rx_descriptor_t) * CONFIG_ETH_DMA_RX_BUFFER_NUM);
/* init rx chain */
for (int i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
/* Set Own bit of the Rx descriptor Status: DMA */
emac_esp_dma->rx_desc[i].RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
/* Set Buffer1 size and Second Address Chained bit */
emac_esp_dma->rx_desc[i].RDES1.SecondAddressChained = 1;
emac_esp_dma->rx_desc[i].RDES1.ReceiveBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
/* Enable Ethernet DMA Rx Descriptor interrupt */
emac_esp_dma->rx_desc[i].RDES1.DisableInterruptOnComplete = 0;
/* point to the buffer */
emac_esp_dma->rx_desc[i].Buffer1Addr = (uint32_t)(emac_esp_dma->rx_buf[i]);
/* point to next descriptor */
emac_esp_dma->rx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->rx_desc + i + 1);
/* For last descriptor, set next descriptor address register equal to the first descriptor base address */
if (i == CONFIG_ETH_DMA_RX_BUFFER_NUM - 1) {
emac_esp_dma->rx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->rx_desc);
}
DMA_CACHE_WB(&emac_esp_dma->rx_desc[i], EMAC_HAL_DMA_DESC_SIZE);
}
/* init tx chain */
for (int i = 0; i < CONFIG_ETH_DMA_TX_BUFFER_NUM; i++) {
/* Set Own bit of the Tx descriptor Status: CPU */
emac_esp_dma->tx_desc[i].TDES0.Own = EMAC_LL_DMADESC_OWNER_CPU;
emac_esp_dma->tx_desc[i].TDES0.SecondAddressChained = 1;
emac_esp_dma->tx_desc[i].TDES1.TransmitBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
/* point to the buffer */
emac_esp_dma->tx_desc[i].Buffer1Addr = (uint32_t)(emac_esp_dma->tx_buf[i]);
/* point to next descriptor */
emac_esp_dma->tx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->tx_desc + i + 1);
/* For last descriptor, set next descriptor address register equal to the first descriptor base address */
if (i == CONFIG_ETH_DMA_TX_BUFFER_NUM - 1) {
emac_esp_dma->tx_desc[i].Buffer2NextDescAddr = (uint32_t)(emac_esp_dma->tx_desc);
}
DMA_CACHE_WB(&emac_esp_dma->tx_desc[i], EMAC_HAL_DMA_DESC_SIZE);
}
/* set base address of the first descriptor */
emac_hal_set_rx_tx_desc_addr(&emac_esp_dma->hal, emac_esp_dma->rx_desc, emac_esp_dma->tx_desc);
}
void emac_esp_dma_set_tdes0_ctrl_bits(emac_esp_dma_handle_t emac_esp_dma, uint32_t flag)
{
emac_esp_dma->tx_desc_flags |= flag;
}
void emac_esp_dma_clear_tdes0_ctrl_bits(emac_esp_dma_handle_t emac_esp_dma, uint32_t flag)
{
emac_esp_dma->tx_desc_flags &= ~flag;
}
uint32_t emac_esp_dma_transmit_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t length)
{
/* Get the number of Tx buffers to use for the frame */
uint32_t bufcount = 0;
uint32_t lastlen = length;
uint32_t sentout = 0;
while (lastlen > CONFIG_ETH_DMA_BUFFER_SIZE) {
lastlen -= CONFIG_ETH_DMA_BUFFER_SIZE;
bufcount++;
}
if (lastlen) {
bufcount++;
}
if (bufcount > CONFIG_ETH_DMA_TX_BUFFER_NUM) {
goto err;
}
eth_dma_tx_descriptor_t *desc_iter = emac_esp_dma->tx_desc;
/* A frame is transmitted in multiple descriptor */
for (size_t i = 0; i < bufcount; i++) {
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Check if the descriptor is owned by the Ethernet DMA (when 1) or CPU (when 0) */
if (desc_iter->TDES0.Own != EMAC_LL_DMADESC_OWNER_CPU) {
goto err;
}
/* Clear FIRST and LAST segment bits */
desc_iter->TDES0.FirstSegment = 0;
desc_iter->TDES0.LastSegment = 0;
desc_iter->TDES0.Value &= ~(EMAC_TDES0_FS_CTRL_FLAGS_MASK | EMAC_TDES0_LS_CTRL_FLAGS_MASK);
if (i == 0) {
/* Setting the first segment bit */
desc_iter->TDES0.FirstSegment = 1;
desc_iter->TDES0.Value |= emac_esp_dma->tx_desc_flags & EMAC_TDES0_FS_CTRL_FLAGS_MASK;
}
if (i == (bufcount - 1)) {
/* Setting the last segment bit */
desc_iter->TDES0.LastSegment = 1;
desc_iter->TDES0.Value |= emac_esp_dma->tx_desc_flags & EMAC_TDES0_LS_CTRL_FLAGS_MASK;
/* Program size */
desc_iter->TDES1.TransmitBuffer1Size = lastlen;
/* copy data from uplayer stack buffer */
memcpy((void *)(desc_iter->Buffer1Addr), buf + i * CONFIG_ETH_DMA_BUFFER_SIZE, lastlen);
sentout += lastlen;
} else {
/* Program size */
desc_iter->TDES1.TransmitBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
/* copy data from uplayer stack buffer */
memcpy((void *)(desc_iter->Buffer1Addr), buf + i * CONFIG_ETH_DMA_BUFFER_SIZE, CONFIG_ETH_DMA_BUFFER_SIZE);
sentout += CONFIG_ETH_DMA_BUFFER_SIZE;
}
DMA_CACHE_WB(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
/* Point to next descriptor */
desc_iter = (eth_dma_tx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
}
/* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
for (size_t i = 0; i < bufcount; i++) {
emac_esp_dma->tx_desc->TDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(emac_esp_dma->tx_desc, EMAC_HAL_DMA_DESC_SIZE);
emac_esp_dma->tx_desc = (eth_dma_tx_descriptor_t *)(emac_esp_dma->tx_desc->Buffer2NextDescAddr);
}
emac_hal_transmit_poll_demand(&emac_esp_dma->hal);
return sentout;
err:
return 0;
}
uint32_t emac_esp_dma_transmit_multiple_buf_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t **buffs, uint32_t *lengths, uint32_t buffs_cnt)
{
/* Get the number of Tx buffers to use for the frame */
uint32_t dma_bufcount = 0;
uint32_t sentout = 0;
uint8_t *ptr = buffs[0];
uint32_t lastlen = lengths[0];
uint32_t avail_len = CONFIG_ETH_DMA_BUFFER_SIZE;
eth_dma_tx_descriptor_t *desc_iter = emac_esp_dma->tx_desc;
/* A frame is transmitted in multiple descriptor */
while (dma_bufcount < CONFIG_ETH_DMA_TX_BUFFER_NUM) {
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Check if the descriptor is owned by the Ethernet DMA (when 1) or CPU (when 0) */
if (desc_iter->TDES0.Own != EMAC_LL_DMADESC_OWNER_CPU) {
goto err;
}
/* Clear FIRST and LAST segment bits */
desc_iter->TDES0.FirstSegment = 0;
desc_iter->TDES0.LastSegment = 0;
desc_iter->TDES0.Value &= ~(EMAC_TDES0_FS_CTRL_FLAGS_MASK | EMAC_TDES0_LS_CTRL_FLAGS_MASK);
desc_iter->TDES1.TransmitBuffer1Size = 0;
if (dma_bufcount == 0) {
/* Setting the first segment bit */
desc_iter->TDES0.FirstSegment = 1;
desc_iter->TDES0.Value |= emac_esp_dma->tx_desc_flags & EMAC_TDES0_FS_CTRL_FLAGS_MASK;
}
while (buffs_cnt > 0) {
/* Check if input buff data fits to currently available space in the descriptor */
if (lastlen < avail_len) {
/* copy data from uplayer stack buffer */
memcpy((void *)(desc_iter->Buffer1Addr + (CONFIG_ETH_DMA_BUFFER_SIZE - avail_len)), ptr, lastlen);
sentout += lastlen;
avail_len -= lastlen;
desc_iter->TDES1.TransmitBuffer1Size += lastlen;
/* Update processed input buffers info */
buffs_cnt--;
ptr = *(++buffs);
lastlen = *(++lengths);
/* There is only limited available space in the current descriptor, use it all */
} else {
/* copy data from uplayer stack buffer */
memcpy((void *)(desc_iter->Buffer1Addr + (CONFIG_ETH_DMA_BUFFER_SIZE - avail_len)), ptr, avail_len);
sentout += avail_len;
lastlen -= avail_len;
/* If lastlen is not zero, input buff will be fragmented over multiple descriptors */
if (lastlen > 0) {
ptr += avail_len;
/* Input buff fully fits the descriptor, move to the next input buff */
} else {
/* Update processed input buffers info */
buffs_cnt--;
ptr = *(++buffs);
lastlen = *(++lengths);
}
avail_len = CONFIG_ETH_DMA_BUFFER_SIZE;
desc_iter->TDES1.TransmitBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
/* The descriptor is full here so exit and use the next descriptor */
break;
}
}
DMA_CACHE_WB(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
/* Increase counter of utilized DMA buffers */
dma_bufcount++;
/* If all input buffers processed, mark as LAST segment and finish the coping */
if (buffs_cnt == 0) {
/* Setting the last segment bit */
desc_iter->TDES0.LastSegment = 1;
desc_iter->TDES0.Value |= emac_esp_dma->tx_desc_flags & EMAC_TDES0_LS_CTRL_FLAGS_MASK;
break;
}
/* Point to next descriptor */
desc_iter = (eth_dma_tx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
}
/* Set Own bit of the Tx descriptor Status: gives the buffer back to ETHERNET DMA */
for (size_t i = 0; i < dma_bufcount; i++) {
emac_esp_dma->tx_desc->TDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(emac_esp_dma->tx_desc, EMAC_HAL_DMA_DESC_SIZE);
emac_esp_dma->tx_desc = (eth_dma_tx_descriptor_t *)(emac_esp_dma->tx_desc->Buffer2NextDescAddr);
}
emac_hal_transmit_poll_demand(&emac_esp_dma->hal);
return sentout;
err:
return 0;
}
static esp_err_t emac_esp_dma_get_valid_recv_len(emac_esp_dma_handle_t emac_esp_dma, uint32_t *ret_len)
{
eth_dma_rx_descriptor_t *desc_iter = emac_esp_dma->rx_desc;
uint32_t used_descs = 0;
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Traverse descriptors owned by CPU */
while ((desc_iter->RDES0.Own == EMAC_LL_DMADESC_OWNER_CPU) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) {
used_descs++;
/* Last segment in frame */
if (desc_iter->RDES0.LastDescriptor) {
/* Since Store Forward must be disabled on some targets, DMA descriptors may contain erroneous frames */
/* In addition, "Descriptor Error" (no free descriptors) may truncate a frame even if Store Forward is enabled */
if (desc_iter->RDES0.ErrSummary) {
emac_esp_dma_flush_recv_frame(emac_esp_dma);
*ret_len = 0;
return ESP_FAIL;
}
/* Get the Frame Length of the received packet: substruct 4 bytes of the CRC */
*ret_len = desc_iter->RDES0.FrameLength - ETH_CRC_LENGTH;
break;
}
/* First segment in frame */
if (desc_iter->RDES0.FirstDescriptor) {
emac_esp_dma->rx_desc = desc_iter;
}
/* point to next descriptor */
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
}
return ESP_OK;
}
void emac_esp_dma_get_remain_frames(emac_esp_dma_handle_t emac_esp_dma, uint32_t *remain_frames, uint32_t *free_descs)
{
eth_dma_rx_descriptor_t *desc_iter = emac_esp_dma->rx_desc;
*remain_frames = 0;
uint32_t used_descs = 0;
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* Traverse descriptors owned by CPU */
while ((desc_iter->RDES0.Own == EMAC_LL_DMADESC_OWNER_CPU) && (used_descs < CONFIG_ETH_DMA_RX_BUFFER_NUM)) {
used_descs++;
/* Last segment in frame */
if (desc_iter->RDES0.LastDescriptor) {
(*remain_frames)++;
}
/* point to next descriptor */
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
}
*free_descs = CONFIG_ETH_DMA_RX_BUFFER_NUM - used_descs;
}
uint8_t *emac_esp_dma_alloc_recv_buf(emac_esp_dma_handle_t emac_esp_dma, uint32_t *size)
{
uint32_t ret_len = 0;
uint32_t copy_len = 0;
uint8_t *buf = NULL;
if (emac_esp_dma_get_valid_recv_len(emac_esp_dma, &ret_len) != ESP_OK) {
*size = 0;
return NULL;
}
/* packets larger than expected will be truncated */
copy_len = ret_len > *size ? *size : ret_len;
if (copy_len > 0) {
buf = malloc(copy_len);
if (buf != NULL) {
emac_esp_dma_auto_buf_info_t *buff_info = (emac_esp_dma_auto_buf_info_t *)buf;
/* no need to check allocated buffer min length prior writing since we know that EMAC DMA is configured to
not forward erroneous or undersized frames (less than 64B) on ESP32, see emac_hal_init_dma_default */
#ifndef NDEBUG
buff_info->magic_id = EMAC_ALLOC_BUF_MAGIC_ID;
#endif // NDEBUG
buff_info->copy_len = copy_len;
}
}
/* indicate actual size of received frame */
*size = ret_len;
return buf;
}
uint32_t emac_esp_dma_receive_frame(emac_esp_dma_handle_t emac_esp_dma, uint8_t *buf, uint32_t size)
{
uint32_t ret_len = 0;
uint32_t copy_len = 0;
if (size != EMAC_DMA_BUF_SIZE_AUTO) {
if (emac_esp_dma_get_valid_recv_len(emac_esp_dma, &ret_len) != ESP_OK) {
return 0;
}
/* packets larger than expected will be truncated */
copy_len = ret_len > size ? size : ret_len;
} else {
emac_esp_dma_auto_buf_info_t *buff_info = (emac_esp_dma_auto_buf_info_t *)buf;
#ifndef NDEBUG
/* check that buffer was allocated by emac_esp_dma_alloc_recv_buf */
assert(buff_info->magic_id == EMAC_ALLOC_BUF_MAGIC_ID);
#endif // NDEBUG
copy_len = buff_info->copy_len;
ret_len = copy_len;
}
if (copy_len) {
eth_dma_rx_descriptor_t *desc_iter = emac_esp_dma->rx_desc;
while(copy_len > CONFIG_ETH_DMA_BUFFER_SIZE) {
DMA_CACHE_INVALIDATE(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
memcpy(buf, (void *)(desc_iter->Buffer1Addr), CONFIG_ETH_DMA_BUFFER_SIZE);
buf += CONFIG_ETH_DMA_BUFFER_SIZE;
copy_len -= CONFIG_ETH_DMA_BUFFER_SIZE;
/* Set Own bit in Rx descriptors: gives the buffers back to DMA */
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
}
DMA_CACHE_INVALIDATE(desc_iter->Buffer1Addr, CONFIG_ETH_DMA_BUFFER_SIZE);
memcpy(buf, (void *)(desc_iter->Buffer1Addr), copy_len);
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* `copy_len` does not include CRC (which may be stored in separate buffer), hence check if we reached the last descriptor */
while (!desc_iter->RDES0.LastDescriptor) {
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
}
/* update rxdesc */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
/* poll rx demand */
emac_hal_receive_poll_demand(&emac_esp_dma->hal);
}
return ret_len;
}
void emac_esp_dma_flush_recv_frame(emac_esp_dma_handle_t emac_esp_dma)
{
eth_dma_rx_descriptor_t *desc_iter = emac_esp_dma->rx_desc;
DMA_CACHE_INVALIDATE(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* While not last descriptor => return back to DMA */
while (!desc_iter->RDES0.LastDescriptor) {
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
desc_iter = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
}
/* the last descriptor */
desc_iter->RDES0.Own = EMAC_LL_DMADESC_OWNER_DMA;
DMA_CACHE_WB(desc_iter, EMAC_HAL_DMA_DESC_SIZE);
/* update rxdesc */
emac_esp_dma->rx_desc = (eth_dma_rx_descriptor_t *)(desc_iter->Buffer2NextDescAddr);
/* poll rx demand */
emac_hal_receive_poll_demand(&emac_esp_dma->hal);
}
esp_err_t emac_esp_del_dma(emac_esp_dma_handle_t emac_esp_dma)
{
if (emac_esp_dma) {
for (int i = 0; i < CONFIG_ETH_DMA_TX_BUFFER_NUM; i++) {
free(emac_esp_dma->tx_buf[i]);
}
for (int i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
free(emac_esp_dma->rx_buf[i]);
}
free(emac_esp_dma->descriptors);
free(emac_esp_dma);
}
return ESP_OK;
}
esp_err_t emac_esp_new_dma(const emac_esp_dma_config_t* config, emac_esp_dma_handle_t *ret_handle)
{
esp_err_t ret = ESP_OK;
*ret_handle = NULL;
struct emac_esp_dma_t *emac_esp_dma = calloc(1, sizeof(struct emac_esp_dma_t));
ESP_GOTO_ON_FALSE(emac_esp_dma, ESP_ERR_NO_MEM, err, TAG, "no mem for esp emac_esp_dma object");
/* alloc memory for ethernet dma descriptor */
uint32_t desc_size = CONFIG_ETH_DMA_RX_BUFFER_NUM * sizeof(eth_dma_rx_descriptor_t) +
CONFIG_ETH_DMA_TX_BUFFER_NUM * sizeof(eth_dma_tx_descriptor_t);
esp_dma_mem_info_t dma_mem_info = {
.extra_heap_caps = MALLOC_CAP_INTERNAL,
.dma_alignment_bytes = 4,
};
esp_dma_capable_calloc(1, desc_size, &dma_mem_info, (void*)&emac_esp_dma->descriptors, NULL);
ESP_GOTO_ON_FALSE(emac_esp_dma->descriptors, ESP_ERR_NO_MEM, err, TAG, "no mem for descriptors");
/* alloc memory for ethernet dma buffer */
for (int i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
esp_dma_capable_calloc(1, CONFIG_ETH_DMA_BUFFER_SIZE, &dma_mem_info, (void*)&emac_esp_dma->rx_buf[i], NULL);
ESP_GOTO_ON_FALSE(emac_esp_dma->rx_buf[i], ESP_ERR_NO_MEM, err, TAG, "no mem for RX DMA buffers");
}
for (int i = 0; i < CONFIG_ETH_DMA_TX_BUFFER_NUM; i++) {
esp_dma_capable_calloc(1, CONFIG_ETH_DMA_BUFFER_SIZE, &dma_mem_info, (void*)&emac_esp_dma->tx_buf[i], NULL);
ESP_GOTO_ON_FALSE(emac_esp_dma->tx_buf[i], ESP_ERR_NO_MEM, err, TAG, "no mem for TX DMA buffers");
}
emac_hal_init(&emac_esp_dma->hal);
*ret_handle = emac_esp_dma;
return ESP_OK;
err:
emac_esp_del_dma(emac_esp_dma);
return ret;
}
@@ -0,0 +1,265 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <inttypes.h>
#include "esp_check.h"
#include "sdkconfig.h"
#include "esp_rom_gpio.h"
#include "driver/gpio.h"
#include "soc/soc_caps.h"
#include "soc/gpio_sig_map.h"
#include "soc/io_mux_reg.h"
#include "soc/gpio_periph.h"
#include "soc/emac_periph.h"
#include "esp_private/gpio.h"
#include "esp_private/eth_mac_esp_gpio.h"
#include "esp_private/esp_gpio_reserve.h"
#include "esp_log.h"
#define GET_GPIO_OR_SINGLE(cfg, num) cfg == NULL ? GPIO_NUM_MAX : cfg->num
static const char *TAG = "esp.emac.gpio";
static uint64_t s_emac_esp_used_gpio_mask = 0x0;
static esp_err_t emac_esp_gpio_matrix_init(gpio_num_t gpio_num, uint32_t signal_in_idx, uint32_t signal_out_idx, gpio_mode_t mode)
{
// silently skip when user don't want to connect the signal to GPIO pad
if (gpio_num == GPIO_NUM_NC) {
ESP_LOGD(TAG, "%s skipping signal in_idx %" PRIu32 ", out_idx %" PRIu32, __func__, signal_in_idx, signal_out_idx);
return ESP_OK;
}
ESP_RETURN_ON_ERROR(gpio_set_direction(gpio_num, mode), TAG, "failed to set direction %i at GPIO #%i", mode, gpio_num);
switch(mode) {
case GPIO_MODE_INPUT:
ESP_RETURN_ON_FALSE(signal_in_idx != SIG_GPIO_OUT_IDX, ESP_ERR_NOT_SUPPORTED,
TAG, "requested periph signal cannot be connect via GPIO Matrix");
ESP_RETURN_ON_FALSE(esp_gpio_is_reserved(BIT64(gpio_num)) == false, ESP_ERR_INVALID_STATE,
TAG, "GPIO %i is reserved", gpio_num);
esp_rom_gpio_connect_in_signal(gpio_num, signal_in_idx, false);
break;
case GPIO_MODE_OUTPUT:
ESP_RETURN_ON_FALSE(signal_out_idx != SIG_GPIO_OUT_IDX, ESP_ERR_NOT_SUPPORTED,
TAG, "requested periph signal cannot be connect via GPIO Matrix");
ESP_RETURN_ON_FALSE((esp_gpio_reserve(BIT64(gpio_num)) & BIT64(gpio_num)) == 0, ESP_ERR_INVALID_STATE,
TAG, "GPIO %i is already reserved", gpio_num);
esp_rom_gpio_connect_out_signal(gpio_num, signal_out_idx, false, false);
break;
case GPIO_MODE_INPUT_OUTPUT:
ESP_RETURN_ON_FALSE(signal_in_idx != SIG_GPIO_OUT_IDX, ESP_ERR_NOT_SUPPORTED,
TAG, "requested periph signal cannot be connect via GPIO Matrix");
ESP_RETURN_ON_FALSE(signal_out_idx != SIG_GPIO_OUT_IDX, ESP_ERR_NOT_SUPPORTED,
TAG, "requested periph signal cannot be connect via GPIO Matrix");
ESP_RETURN_ON_FALSE((esp_gpio_reserve(BIT64(gpio_num)) & BIT64(gpio_num)) == 0, ESP_ERR_INVALID_STATE,
TAG, "GPIO %i is already reserved", gpio_num);
esp_rom_gpio_connect_out_signal(gpio_num, signal_out_idx, false, false);
esp_rom_gpio_connect_in_signal(gpio_num, signal_in_idx, false);
break;
default:
return ESP_ERR_INVALID_ARG;
}
s_emac_esp_used_gpio_mask |= BIT64(gpio_num);
ESP_RETURN_ON_ERROR(gpio_set_pull_mode(gpio_num, GPIO_FLOATING), TAG, "failed to set pull mode at GPIO %i", gpio_num);
ESP_RETURN_ON_ERROR(gpio_func_sel(gpio_num, PIN_FUNC_GPIO), TAG, "failed to set GPIO function at GPIO #%i", gpio_num);
return ESP_OK;
}
static esp_err_t emac_esp_iomux_init(gpio_num_t gpio_num, const emac_iomux_info_t *iomux_info, bool is_input)
{
// silently skip undefined iomux functions (for example, ESP32 does not use MII COL_IN/CRS_IN)
if (iomux_info == NULL) {
ESP_LOGD(TAG, "%s skipping target undefined iomux periph function", __func__);
return ESP_OK;
}
// loop over target iomux_info until reached end of list indicated by invalid GPIO num
while (iomux_info->gpio_num != GPIO_NUM_MAX) {
// if requested GPIO number can be IO muxed or select single pad that can be muxed on the target
if(gpio_num == iomux_info->gpio_num || gpio_num == GPIO_NUM_MAX) {
ESP_RETURN_ON_FALSE((esp_gpio_reserve(BIT64(iomux_info->gpio_num)) & BIT64(iomux_info->gpio_num)) == 0, ESP_ERR_INVALID_STATE,
TAG, "GPIO %i is already reserved", iomux_info->gpio_num);
s_emac_esp_used_gpio_mask |= BIT64(iomux_info->gpio_num);
ESP_RETURN_ON_ERROR(gpio_func_sel(iomux_info->gpio_num, iomux_info->func), TAG, "failed to set GPIO function at GPIO %i", iomux_info->gpio_num);
if (is_input) {
PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[iomux_info->gpio_num]);
} else {
PIN_INPUT_DISABLE(GPIO_PIN_MUX_REG[iomux_info->gpio_num]);
}
ESP_RETURN_ON_ERROR(gpio_set_pull_mode(iomux_info->gpio_num, GPIO_FLOATING),
TAG, "failed to set pull mode at GPIO %i", iomux_info->gpio_num);
return ESP_OK;
}
iomux_info++;
}
return ESP_FAIL;
}
esp_err_t emac_esp_gpio_init_smi(const emac_esp_smi_gpio_config_t *smi_gpio)
{
if (smi_gpio->mdc_num >= 0) {
/* Setup SMI MDC GPIO */
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(smi_gpio->mdc_num, 0, emac_io_idx.mdc_idx, GPIO_MODE_OUTPUT),
TAG, "MDC GPIO matrix config failed");
}
if (smi_gpio->mdio_num >= 0) {
/* Setup SMI MDIO GPIO */
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(smi_gpio->mdio_num, emac_io_idx.mdi_idx, emac_io_idx.mdo_idx, GPIO_MODE_INPUT_OUTPUT),
TAG, "MDIO GPIO matrix config failed");
}
return ESP_OK;
}
esp_err_t emac_esp_gpio_matrix_init_mii(const eth_mac_mii_gpio_config_t *mii_gpio)
{
ESP_RETURN_ON_FALSE(mii_gpio != NULL, ESP_ERR_INVALID_ARG, TAG, "MII IO matrix config cannot be NULL");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->tx_clk_num, emac_io_idx.mii_tx_clk_i_idx, 0, GPIO_MODE_INPUT),
TAG, "TX_CLK GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->tx_en_num, 0, emac_io_idx.mii_tx_en_o_idx, GPIO_MODE_OUTPUT),
TAG, "TX_EN GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->txd0_num, 0, emac_io_idx.mii_txd0_o_idx, GPIO_MODE_OUTPUT),
TAG, "TDX0 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->txd1_num, 0, emac_io_idx.mii_txd1_o_idx, GPIO_MODE_OUTPUT),
TAG, "TDX1 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->txd2_num, 0, emac_io_idx.mii_txd2_o_idx, GPIO_MODE_OUTPUT),
TAG, "TDX2 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->txd3_num, 0, emac_io_idx.mii_txd3_o_idx, GPIO_MODE_OUTPUT),
TAG, "TDX3 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->rx_clk_num, emac_io_idx.mii_rx_clk_i_idx, 0, GPIO_MODE_INPUT),
TAG, "RX_CLK GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->rxd0_num, emac_io_idx.mii_rxd0_i_idx, 0, GPIO_MODE_INPUT),
TAG, "RXD0 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->rxd1_num, emac_io_idx.mii_rxd1_i_idx, 0, GPIO_MODE_INPUT),
TAG, "RXD1 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->rxd2_num, emac_io_idx.mii_rxd2_i_idx, 0, GPIO_MODE_INPUT),
TAG, "RXD2 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->rxd3_num, emac_io_idx.mii_rxd3_i_idx, 0, GPIO_MODE_INPUT),
TAG, "RXD3 GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->col_in_num, emac_io_idx.mii_col_i_idx, 0, GPIO_MODE_INPUT),
TAG, "COL_IN GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->crs_in_num, emac_io_idx.mii_crs_i_idx, 0, GPIO_MODE_INPUT),
TAG, "CRS_IN GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->tx_er_num, 0, emac_io_idx.mii_tx_er_o_idx, GPIO_MODE_OUTPUT),
TAG, "TX_ER GPIO matrix config failed");
ESP_RETURN_ON_ERROR(emac_esp_gpio_matrix_init(mii_gpio->rx_er_num, emac_io_idx.mii_rx_er_i_idx, 0, GPIO_MODE_INPUT),
TAG, "RX_ER GPIO matrix config failed");
return ESP_OK;
}
esp_err_t emac_esp_iomux_init_mii(const eth_mac_mii_gpio_config_t *mii_gpio)
{
ESP_RETURN_ON_FALSE(emac_mii_iomux_pins.clk_tx != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support MII IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, tx_clk_num), emac_mii_iomux_pins.clk_tx, true),
TAG, "invalid TX_CLK GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, tx_en_num), emac_mii_iomux_pins.tx_en, false),
TAG, "invalid TX_EN GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, txd0_num), emac_mii_iomux_pins.txd0, false),
TAG, "invalid TXD0 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, txd1_num), emac_mii_iomux_pins.txd1, false),
TAG, "invalid TXD1 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, txd2_num), emac_mii_iomux_pins.txd2, false),
TAG, "invalid TXD2 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, txd3_num), emac_mii_iomux_pins.txd3, false),
TAG, "invalid TXD3 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, rx_clk_num), emac_mii_iomux_pins.clk_rx, true),
TAG, "invalid RX_CLK GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, rx_dv_num), emac_mii_iomux_pins.rx_dv, true),
TAG, "invalid RX_DV GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, rxd0_num), emac_mii_iomux_pins.rxd0, true),
TAG, "invalid RXD0 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, rxd1_num), emac_mii_iomux_pins.rxd1, true),
TAG, "invalid RXD1 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, rxd2_num), emac_mii_iomux_pins.rxd2, true),
TAG, "invalid RXD2 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, rxd3_num), emac_mii_iomux_pins.rxd3, true),
TAG, "invalid RXD3 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, col_in_num), emac_mii_iomux_pins.col_in, true),
TAG, "invalid COL_IN GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(mii_gpio, crs_in_num), emac_mii_iomux_pins.crs_in, true),
TAG, "invalid CRS_IN GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_rmii_clk_input(int num)
{
ESP_RETURN_ON_FALSE(emac_rmii_iomux_pins.clki != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support RMII CLKI IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(num, emac_rmii_iomux_pins.clki, true), TAG, "invalid RMII CLK input GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_rmii_clk_ouput(int num)
{
ESP_RETURN_ON_FALSE(emac_rmii_iomux_pins.clko != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support RMII CLKO IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(num, emac_rmii_iomux_pins.clko, false), TAG, "invalid RMII CLK output GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_init_rmii(const eth_mac_rmii_gpio_config_t *rmii_gpio)
{
ESP_RETURN_ON_FALSE(emac_rmii_iomux_pins.clki != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support RMII IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(rmii_gpio, tx_en_num), emac_rmii_iomux_pins.tx_en, false),
TAG, "invalid TX_EN GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(rmii_gpio, txd0_num), emac_rmii_iomux_pins.txd0, false),
TAG, "invalid TXD0 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(rmii_gpio, txd1_num), emac_rmii_iomux_pins.txd1, false),
TAG, "invalid TXD1 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(rmii_gpio, crs_dv_num), emac_rmii_iomux_pins.crs_dv, true),
TAG,"invalid CRS_DV GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(rmii_gpio, rxd0_num), emac_rmii_iomux_pins.rxd0, true),
TAG,"invalid RXD0 GPIO number");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(GET_GPIO_OR_SINGLE(rmii_gpio, rxd1_num), emac_rmii_iomux_pins.rxd1, true),
TAG,"invalid RXD1 GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_rmii_init_tx_er(int num)
{
ESP_RETURN_ON_FALSE(emac_rmii_iomux_pins.tx_er != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support RMII TX_ER IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(num, emac_rmii_iomux_pins.tx_er, false), TAG, "invalid TX_ER GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_rmii_init_rx_er(int num)
{
ESP_RETURN_ON_FALSE(emac_rmii_iomux_pins.rx_er != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support RMII RX_ER IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(num, emac_rmii_iomux_pins.rx_er, true), TAG, "invalid RX_ER GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_mii_init_tx_er(int num)
{
ESP_RETURN_ON_FALSE(emac_mii_iomux_pins.tx_er != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support MII TX_ER IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(num, emac_mii_iomux_pins.tx_er, false), TAG, "invalid TX_ER GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_iomux_mii_init_rx_er(int num)
{
ESP_RETURN_ON_FALSE(emac_mii_iomux_pins.rx_er != NULL, ESP_ERR_NOT_SUPPORTED, TAG, "target does not support RMII RX_ER IOMUX");
ESP_RETURN_ON_ERROR(emac_esp_iomux_init(num, emac_mii_iomux_pins.rx_er, true), TAG, "invalid RX_ER GPIO number");
return ESP_OK;
}
esp_err_t emac_esp_gpio_deinit_all(void)
{
for (int gpio_num = 0; gpio_num < 64; gpio_num++) {
if (BIT64(gpio_num) & s_emac_esp_used_gpio_mask) {
gpio_reset_pin(gpio_num);
esp_gpio_revoke(BIT64(gpio_num));
}
s_emac_esp_used_gpio_mask &= ~BIT64(gpio_num);
}
return ESP_OK;
}