瀏覽代碼

ethernet: support dm9051

1. move resource allocation from xxx_init to xxx_new
2. fix enabling tx checksum insertion by mistake
3. iperf example: enlarge max arguments
4. add examples for spi-ethernet

Closes https://github.com/espressif/esp-idf/issues/3715
Closes https://github.com/espressif/esp-idf/issues/3711
suda-morris 6 年之前
父節點
當前提交
cb42c29252

+ 5 - 0
components/esp_eth/CMakeLists.txt

@@ -8,6 +8,11 @@ if(CONFIG_IDF_TARGET_ESP32)
     list(APPEND esp_eth_srcs "src/esp_eth_mac_esp32.c")
 endif()
 
+if(CONFIG_ETH_SPI_ETHERNET_DM9051)
+    list(APPEND esp_eth_srcs "src/esp_eth_mac_dm9051.c"
+                             "src/esp_eth_phy_dm9051.c")
+endif()
+
 idf_component_register(SRCS "${esp_eth_srcs}"
                        INCLUDE_DIRS "include"
                        LDFRAGMENTS "linker.lf"

+ 26 - 1
components/esp_eth/Kconfig

@@ -2,7 +2,7 @@ menu "Ethernet"
 
     menuconfig ETH_USE_ESP32_EMAC
         depends on IDF_TARGET_ESP32
-        bool "Use ESP32 internal EMAC controller"
+        bool "Support ESP32 internal EMAC controller"
         default y
         help
             ESP32 integrates a 10/100M Ethernet MAC controller.
@@ -128,4 +128,29 @@ menu "Ethernet"
                 Number of DMA transmit buffers. Each buffer's size is ETH_DMA_BUFFER_SIZE.
                 Larger number of buffers could increase throughput somehow.
     endif
+
+    menuconfig ETH_USE_SPI_ETHERNET
+        bool "Support SPI to Ethernet Module"
+        default y
+        help
+            ESP-IDF can also support some SPI-Ethernet module.
+
+    if ETH_USE_SPI_ETHERNET
+        menuconfig ETH_SPI_ETHERNET_DM9051
+            bool "Use DM9051"
+            default y
+            help
+                DM9051 is a fast Ethernet controller with an SPI interface.
+                It's also integrated with a 10/100M PHY and MAC.
+                Set true to enable DM9051 driver.
+
+        if ETH_SPI_ETHERNET_DM9051
+            config ETH_DM9051_INT_GPIO
+                int "DM9051 Interrupt GPIO number"
+                default 4
+                range 0 33
+                help
+                    Set the GPIO number used by DM9051's Interrupt pin.
+        endif
+    endif
 endmenu

+ 4 - 0
components/esp_eth/component.mk

@@ -8,3 +8,7 @@ COMPONENT_ADD_LDFRAGMENTS += linker.lf
 ifndef CONFIG_IDF_TARGET_ESP32
     COMPONENT_OBJEXCLUDE += src/esp_eth_mac_esp32.o
 endif
+
+ifndef CONFIG_ETH_SPI_ETHERNET_DM9051
+    COMPONENT_OBJEXCLUDE += src/esp_eth_mac_dm9051.o src/esp_eth_phy_dm9051.o
+endif

+ 25 - 0
components/esp_eth/include/esp_eth_mac.h

@@ -20,6 +20,11 @@ extern "C" {
 #include <stdbool.h>
 #include "esp_eth_com.h"
 #include "sdkconfig.h"
+#if CONFIG_ETH_USE_SPI_ETHERNET
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#endif
+
 
 /**
 * @brief Ethernet MAC
@@ -115,6 +120,7 @@ struct esp_eth_mac_s {
     *      - ESP_OK: read PHY register successfully
     *      - ESP_ERR_INVALID_ARG: read PHY register failed because of invalid argument
     *      - ESP_ERR_INVALID_STATE: read PHY register failed because of wrong state of MAC
+    *      - ESP_ERR_TIMEOUT: read PHY register failed because of timeout
     *      - ESP_FAIL: read PHY register failed because some other error occurred
     *
     */
@@ -131,6 +137,7 @@ struct esp_eth_mac_s {
     * @return
     *      - ESP_OK: write PHY register successfully
     *      - ESP_ERR_INVALID_STATE: write PHY register failed because of wrong state of MAC
+    *      - ESP_ERR_TIMEOUT: write PHY register failed because of timeout
     *      - ESP_FAIL: write PHY register failed because some other error occurred
     *
     */
@@ -241,6 +248,10 @@ typedef struct {
     uint32_t rx_task_stack_size;  /*!< Stack size of the receive task */
     uint32_t rx_task_prio;        /*!< Priority of the receive task */
     uint32_t queue_len;           /*!< Length of the transaction queue */
+#if CONFIG_ETH_USE_SPI_ETHERNET
+    spi_device_handle_t spi_hdl;  /*!< Handle of spi device */
+#endif
+
 } eth_mac_config_t;
 
 /**
@@ -255,6 +266,7 @@ typedef struct {
         .queue_len = 100,           \
     }
 
+#if CONFIG_ETH_USE_ESP32_EMAC
 /**
 * @brief Create ESP32 Ethernet MAC instance
 *
@@ -265,7 +277,20 @@ typedef struct {
 *      - NULL: create MAC instance failed because some error occurred
 */
 esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_mac_config_t *config);
+#endif
 
+#if CONFIG_ETH_SPI_ETHERNET_DM9051
+/**
+* @brief Create DM9051 Ethernet MAC instance
+*
+* @param config: Ethernet MAC configuration
+*
+* @return
+*      - instance: create MAC instance successfully
+*      - NULL: create MAC instance failed because some error occurred
+*/
+esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_mac_config_t *config);
+#endif
 #ifdef __cplusplus
 }
 #endif

+ 12 - 0
components/esp_eth/include/esp_eth_phy.h

@@ -222,6 +222,18 @@ esp_eth_phy_t *esp_eth_phy_new_lan8720(const eth_phy_config_t *config);
 */
 esp_eth_phy_t *esp_eth_phy_new_dp83848(const eth_phy_config_t *config);
 
+#if CONFIG_ETH_SPI_ETHERNET_DM9051
+/**
+* @brief Create a PHY instance of DM9051
+*
+* @param[in] config: configuration of PHY
+*
+* @return
+*      - instance: create PHY instance successfully
+*      - NULL: create PHY instance failed because some error occurred
+*/
+esp_eth_phy_t *esp_eth_phy_new_dm9051(const eth_phy_config_t *config);
+#endif
 #ifdef __cplusplus
 }
 #endif

+ 3 - 0
components/esp_eth/linker.lf

@@ -1,8 +1,11 @@
 [mapping:esp_eth]
 archive: libesp_eth.a
 entries:
+  if ETH_USE_ESP32_EMAC = y:
     esp_eth_mac_esp32:emac_hal_tx_complete_cb (noflash_text)
     esp_eth_mac_esp32:emac_hal_tx_unavail_cb (noflash_text)
     esp_eth_mac_esp32:emac_hal_rx_complete_cb (noflash_text)
     esp_eth_mac_esp32:emac_hal_rx_early_cb (noflash_text)
     esp_eth_mac_esp32:emac_hal_rx_unavail_cb (noflash_text)
+  if ETH_SPI_ETHERNET_DM9051 = y:
+    esp_eth_mac_dm9051:dm9051_isr_handler (noflash_text)

+ 852 - 0
components/esp_eth/src/esp_eth_mac_dm9051.c

@@ -0,0 +1,852 @@
+// Copyright 2019 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 <string.h>
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#include "esp_log.h"
+#include "esp_eth.h"
+#include "esp_system.h"
+#include "esp_intr_alloc.h"
+#include "esp_heap_caps.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+#include "sdkconfig.h"
+
+static const char *TAG = "emac_dm9051";
+#define MAC_CHECK(a, str, goto_tag, ret_value, ...)                               \
+    do                                                                            \
+    {                                                                             \
+        if (!(a))                                                                 \
+        {                                                                         \
+            ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
+            ret = ret_value;                                                      \
+            goto goto_tag;                                                        \
+        }                                                                         \
+    } while (0)
+
+#define RX_QUEUE_WAIT_MS (100)
+#define DM9051_SPI_LOCK_TIMEOUT_MS (50)
+#define DM9051_PHY_OPERATION_TIMEOUT_US (1000)
+
+/**
+ * @brief Registers in DM9051
+ *
+ */
+#define DM9051_NCR (0x00)     // Network Control Register
+#define DM9051_NSR (0x01)     // Network Status Register
+#define DM9051_TCR (0x02)     // Tx Control Register
+#define DM9051_TSR1 (0x03)    // Tx Status Register I
+#define DM9051_TSR2 (0x04)    // Tx Status Register II
+#define DM9051_RCR (0x05)     // Rx Control Register
+#define DM9051_RSR (0x06)     // Rx Status Register
+#define DM9051_ROCR (0x07)    // Receive Overflow Counter Register
+#define DM9051_BPTR (0x08)    // Back Pressure Threshold Register
+#define DM9051_FCTR (0x09)    // Flow Control Threshold Register
+#define DM9051_FCR (0x0A)     // Rx/Tx Flow Control Register
+#define DM9051_EPCR (0x0B)    // EEPROM & PHY Control Register
+#define DM9051_EPAR (0x0C)    // EEPROM & PHY Address Register
+#define DM9051_EPDRL (0x0D)   // EEPROM & PHY Data Register Low
+#define DM9051_EPDRH (0x0E)   // EEPROM & PHY Data Register High
+#define DM9051_WCR (0x0F)     // Wake Up Control Register
+#define DM9051_PAR (0x10)     // Physical Address Register
+#define DM9051_MAR (0x16)     // Multicast Address Hash Table Register
+#define DM9051_GPCR (0x1E)    // General Purpose Control Register
+#define DM9051_GPR (0x1F)     // General Purpose Register
+#define DM9051_TRPAL (0x22)   // Tx Memory Read Pointer Address Low Byte
+#define DM9051_TRPAH (0x23)   // Tx Memory Read Pointer Address High Byte
+#define DM9051_RWPAL (0x24)   // Rx Memory Read Pointer Address Low Byte
+#define DM9051_RWPAH (0x25)   // Rx Memory Read Pointer Address High Byte
+#define DM9051_VIDL (0x28)    // Vendor ID Low Byte
+#define DM9051_VIDH (0x29)    // Vendor ID High Byte
+#define DM9051_PIDL (0x2A)    // Product ID Low Byte
+#define DM9051_PIDH (0x2B)    // Product ID High Byte
+#define DM9051_CHIPR (0x2C)   // CHIP Revision
+#define DM9051_TCR2 (0x2D)    // Transmit Control Register 2
+#define DM9051_ATCR (0x30)    // Auto-Transmit Control Register
+#define DM9051_TCSCR (0x31)   // Transmit Check Sum Control Register
+#define DM9051_RCSCSR (0x32)  // Receive Check Sum Control Status Register
+#define DM9051_SBCR (0x38)    // SPI Bus Control Register
+#define DM9051_INTCR (0x39)   // INT Pin Control Register
+#define DM9051_PPCSR (0x3D)   // Pause Packet Control Status Register
+#define DM9051_EEE_IN (0x3E)  // IEEE 802.3az Enter Counter Register
+#define DM9051_EEE_OUT (0x3F) // IEEE 802.3az Leave Counter Register
+#define DM9051_ALNCR (0x4A)   // SPI Byte Align Error Counter Register
+#define DM9051_RLENCR (0x52)  // Rx Packet Length Control Register
+#define DM9051_BCASTCR (0x53) // RX Broadcast Control Register
+#define DM9051_INTCKCR (0x54) // INT Pin Clock Output Control Register
+#define DM9051_MPTRCR (0x55)  // Memory Pointer Control Register
+#define DM9051_MLEDCR (0x57)  // More LED Control Register
+#define DM9051_MEMSCR (0x59)  // Memory Control Register
+#define DM9051_TMEMR (0x5A)   // Transmit Memory Size Register
+#define DM9051_MBSR (0x5D)    // Memory BIST Status Register
+#define DM9051_MRCMDX (0x70)  // Memory Data Pre-Fetch Read Command Without Address Increment Register
+#define DM9051_MRCMDX1 (0x71) // Memory Read Command Without Pre-Fetch and Without Address Increment Register
+#define DM9051_MRCMD (0x72)   // Memory Data Read Command With Address Increment Register
+#define DM9051_SDR_DLY (0x73) // SPI Data Read Delay Counter Register
+#define DM9051_MRRL (0x74)    // Memory Data Read Address Register Low Byte
+#define DM9051_MRRH (0x75)    // Memory Data Read Address Register High Byte
+#define DM9051_MWCMDX (0x76)  // Memory Data Write Command Without Address Increment Register
+#define DM9051_MWCMD (0x78)   // Memory Data Write Command With Address Increment Register
+#define DM9051_MWRL (0x7A)    // Memory Data Write Address Register Low Byte
+#define DM9051_MWRH (0x7B)    // Memory Data Write Address Register High Byte
+#define DM9051_TXPLL (0x7C)   // TX Packet Length Low Byte Register
+#define DM9051_TXPLH (0x7D)   // TX Packet Length High Byte Register
+#define DM9051_ISR (0x7E)     // Interrupt Status Register
+#define DM9051_IMR (0x7F)     // Interrupt Mask Register
+
+/**
+ * @brief status and flag of DM9051 specific registers
+ *
+ */
+#define DM9051_SPI_RD (0) // Burst Read Command
+#define DM9051_SPI_WR (1) // Burst Write Command
+
+#define NCR_WAKEEN (1 << 6) // Enable Wakeup Function
+#define NCR_FDX (1 << 3)    // Duplex Mode of the Internal PHY
+#define NCR_RST (1 << 0)    // Software Reset and Auto-Clear after 10us
+
+#define NSR_SPEED (1 << 7)  // Speed of Internal PHY
+#define NSR_LINKST (1 << 6) // Link Status of Internal PHY
+#define NSR_WAKEST (1 << 5) // Wakeup Event Status
+#define NSR_TX2END (1 << 3) // TX Packet Index II Complete Status
+#define NSR_TX1END (1 << 2) // TX Packet Index I Complete Status
+#define NSR_RXOV (1 << 1)   // RX Memory Overflow Status
+#define NSR_RXRDY (1 << 0)  // RX Packet Ready
+
+#define TCR_TXREQ (1 << 0) // TX Request. Auto-Clear after Sending Completely
+
+#define RCR_WTDIS (1 << 6)    // Watchdog Timer Disable
+#define RCR_DIS_LONG (1 << 5) // Discard Long Packet
+#define RCR_DIS_CRC (1 << 4)  // Discard CRC Error Packet
+#define RCR_ALL (1 << 3)      // Receive All Multicast
+#define RCR_RUNT (1 << 2)     // Receive Runt Packet
+#define RCR_PRMSC (1 << 1)    // Promiscuous Mode
+#define RCR_RXEN (1 << 0)     // RX Enable
+
+#define RSR_RF (1 << 7)   // Runt Frame
+#define RSR_MF (1 << 6)   // Multicast Frame
+#define RSR_LCS (1 << 5)  // Late Collision Seen
+#define RSR_RWTO (1 << 4) // Receive Watchdog Time-Out
+#define RSR_PLE (1 << 3)  // Physical Layer Error
+#define RSR_AE (1 << 2)   //  Alignment Error
+#define RSR_CE (1 << 1)   // CRC Error
+#define RSR_FOE (1 << 0)  // RX Memory Overflow Error
+
+#define FCR_FLOW_ENABLE (0x39) // Enable Flow Control
+
+#define EPCR_REEP (1 << 5)  // Reload EEPROM
+#define EPCR_WEP (1 << 4)   // Write EEPROM Enable
+#define EPCR_EPOS (1 << 3)  // EEPROM or PHY Operation Select
+#define EPCR_ERPRR (1 << 2) // EEPROM Read or PHY Register Read Command
+#define EPCR_ERPRW (1 << 1) // EEPROM Write or PHY Register Write Command
+#define EPCR_ERRE (1 << 0)  // EEPROM Access Status or PHY Access Status
+
+#define TCR2_RLCP (1 << 6) // Retry Late Collision Packet
+
+#define ATCR_AUTO_TX (1 << 7) // Auto-Transmit Control
+
+#define TCSCR_UDPCSE (1 << 2) // UDP CheckSum Generation
+#define TCSCR_TCPCSE (1 << 1) // TCP CheckSum Generation
+#define TCSCR_IPCSE (1 << 0)  // IPv4 CheckSum Generation
+
+#define MPTRCR_RST_TX (1 << 1) // Reset TX Memory Pointer
+#define MPTRCR_RST_RX (1 << 0) // Reset RX Memory Pointer
+
+#define ISR_LNKCHGS (1 << 5) // Link Status Change
+#define ISR_ROO (1 << 3)     // Receive Overflow Counter Overflow
+#define ISR_ROS (1 << 2)     // Receive Overflow
+#define ISR_PT (1 << 1)      // Packet Transmitted
+#define ISR_PR (1 << 0)      // Packet Received
+#define ISR_CLR_STATUS (ISR_LNKCHGS | ISR_ROO | ISR_ROS | ISR_PT | ISR_PR)
+
+#define IMR_PAR (1 << 7)     // Pointer Auto-Return Mode
+#define IMR_LNKCHGI (1 << 5) // Enable Link Status Change Interrupt
+#define IMR_ROOI (1 << 3)    // Enable Receive Overflow Counter Overflow Interrupt
+#define IMR_ROI (1 << 2)     // Enable Receive Overflow Interrupt
+#define IMR_PTI (1 << 1)     // Enable Packet Transmitted Interrupt
+#define IMR_PRI (1 << 0)     // Enable Packet Received Interrupt
+#define IMR_ALL (IMR_PAR | IMR_LNKCHGI | IMR_ROOI | IMR_ROI | IMR_PTI | IMR_PRI)
+
+typedef struct {
+    uint8_t flag;
+    uint8_t status;
+    uint8_t length_low;
+    uint8_t length_high;
+} dm9051_rx_header_t;
+
+typedef struct {
+    esp_eth_mac_t parent;
+    esp_eth_mediator_t *eth;
+    spi_device_handle_t spi_hdl;
+    SemaphoreHandle_t spi_lock;
+    TaskHandle_t rx_task_hdl;
+    uint32_t sw_reset_timeout_ms;
+    uint8_t addr[6];
+    bool packets_remain;
+} emac_dm9051_t;
+
+static inline bool dm9051_lock(emac_dm9051_t *emac)
+{
+    return xSemaphoreTake(emac->spi_lock, DM9051_SPI_LOCK_TIMEOUT_MS) == pdTRUE;
+}
+
+static inline bool dm9051_unlock(emac_dm9051_t *emac)
+{
+    return xSemaphoreGive(emac->spi_lock) == pdTRUE;
+}
+
+/**
+ * @brief write value to dm9051 internal register
+ */
+static esp_err_t dm9051_register_write(emac_dm9051_t *emac, uint8_t reg_addr, uint8_t value)
+{
+    esp_err_t ret = ESP_OK;
+    spi_transaction_t trans = {
+        .cmd = DM9051_SPI_WR,
+        .addr = reg_addr,
+        .length = 8,
+        .flags = SPI_TRANS_USE_TXDATA
+    };
+    trans.tx_data[0] = value;
+    if (dm9051_lock(emac)) {
+        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+            ret = ESP_FAIL;
+        }
+        dm9051_unlock(emac);
+    } else {
+        ret = ESP_ERR_TIMEOUT;
+    }
+    return ret;
+}
+
+/**
+ * @brief read value from dm9051 internal register
+ */
+static esp_err_t dm9051_register_read(emac_dm9051_t *emac, uint8_t reg_addr, uint8_t *value)
+{
+    esp_err_t ret = ESP_OK;
+    spi_transaction_t trans = {
+        .cmd = DM9051_SPI_RD,
+        .addr = reg_addr,
+        .length = 8,
+        .flags = SPI_TRANS_USE_TXDATA | SPI_TRANS_USE_RXDATA
+    };
+    if (dm9051_lock(emac)) {
+        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+            ret = ESP_FAIL;
+        } else {
+            *value = trans.rx_data[0];
+        }
+        dm9051_unlock(emac);
+    } else {
+        ret = ESP_ERR_TIMEOUT;
+    }
+    return ret;
+}
+
+/**
+ * @brief write buffer to dm9051 internal memory
+ */
+static esp_err_t dm9051_memory_write(emac_dm9051_t *emac, uint8_t *buffer, uint32_t len)
+{
+    esp_err_t ret = ESP_OK;
+    spi_transaction_t trans = {
+        .cmd = DM9051_SPI_WR,
+        .addr = DM9051_MWCMD,
+        .length = len * 8,
+        .tx_buffer = buffer
+    };
+    if (dm9051_lock(emac)) {
+        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+            ret = ESP_FAIL;
+        }
+        dm9051_unlock(emac);
+    } else {
+        ret = ESP_ERR_TIMEOUT;
+    }
+    return ret;
+}
+
+/**
+ * @brief read buffer from dm9051 internal memory
+ */
+static esp_err_t dm9051_memory_read(emac_dm9051_t *emac, uint8_t *buffer, uint32_t len)
+{
+    esp_err_t ret = ESP_OK;
+    spi_transaction_t trans = {
+        .cmd = DM9051_SPI_RD,
+        .addr = DM9051_MRCMD,
+        .length = len * 8,
+        .rx_buffer = buffer
+    };
+    if (dm9051_lock(emac)) {
+        if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) {
+            ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__);
+            ret = ESP_FAIL;
+        }
+        dm9051_unlock(emac);
+    } else {
+        ret = ESP_ERR_TIMEOUT;
+    }
+    return ret;
+}
+
+/**
+ * @brief read mac address from internal registers
+ */
+static esp_err_t dm9051_get_mac_addr(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    for (int i = 0; i < 6; i++) {
+        MAC_CHECK(dm9051_register_read(emac, DM9051_PAR + i, &emac->addr[i]) == ESP_OK, "read PAR failed", err, ESP_FAIL);
+    }
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief set new mac address to internal registers
+ */
+static esp_err_t dm9051_set_mac_addr(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    for (int i = 0; i < 6; i++) {
+        MAC_CHECK(dm9051_register_write(emac, DM9051_PAR + i, emac->addr[i]) == ESP_OK, "write PAR failed", err, ESP_FAIL);
+    }
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief clear multicast hash table
+ */
+static esp_err_t dm9051_clear_multicast_table(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    /* rx broadcast packet control by bit7 of MAC register 1DH */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_BCASTCR, 0x00) == ESP_OK, "write BCASTCR failed", err, ESP_FAIL);
+    for (int i = 0; i < 7; i++) {
+        MAC_CHECK(dm9051_register_write(emac, DM9051_MAR + i, 0x00) == ESP_OK, "write MAR failed", err, ESP_FAIL);
+    }
+    /* enable receive broadcast paclets */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_MAR + 7, 0x80) == ESP_OK, "write MAR failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief software reset dm9051 internal register
+ */
+static esp_err_t dm9051_reset(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    /* power on phy */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_GPR, 0x00) == ESP_OK, "write GPR failed", err, ESP_FAIL);
+    /* mac and phy register won't be accesable within at least 1ms */
+    vTaskDelay(pdMS_TO_TICKS(10));
+    /* software reset */
+    uint8_t ncr = NCR_RST;
+    MAC_CHECK(dm9051_register_write(emac, DM9051_NCR, ncr) == ESP_OK, "write NCR failed", err, ESP_FAIL);
+    uint32_t to = 0;
+    for (to = 0; to < emac->sw_reset_timeout_ms / 10; to++) {
+        MAC_CHECK(dm9051_register_read(emac, DM9051_NCR, &ncr) == ESP_OK, "read NCR failed", err, ESP_FAIL);
+        if (!(ncr & NCR_RST)) {
+            break;
+        }
+        vTaskDelay(pdMS_TO_TICKS(10));
+    }
+    MAC_CHECK(to < emac->sw_reset_timeout_ms / 10, "reset timeout", err, ESP_ERR_TIMEOUT);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief verify dm9051 chip ID
+ */
+static esp_err_t dm9051_verify_id(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    uint8_t id[2];
+    MAC_CHECK(dm9051_register_read(emac, DM9051_VIDL, &id[0]) == ESP_OK, "read VIDL failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_read(emac, DM9051_VIDH, &id[1]) == ESP_OK, "read VIDH failed", err, ESP_FAIL);
+    MAC_CHECK(0x0A46 == *(uint16_t *)id, "wrong Vendor ID", err, ESP_ERR_INVALID_VERSION);
+    MAC_CHECK(dm9051_register_read(emac, DM9051_PIDL, &id[0]) == ESP_OK, "read PIDL failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_read(emac, DM9051_PIDH, &id[1]) == ESP_OK, "read PIDH failed", err, ESP_FAIL);
+    MAC_CHECK(0x9051 == *(uint16_t *)id, "wrong Product ID", err, ESP_ERR_INVALID_VERSION);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief default setup for dm9051 internal registers
+ */
+static esp_err_t dm9051_setup_default(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    /* disable wakeup */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_NCR, 0x00) == ESP_OK, "write NCR failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_WCR, 0x00) == ESP_OK, "write WCR failed", err, ESP_FAIL);
+    /* stop transmitting, enable appending pad, crc for packets */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_TCR, 0x00) == ESP_OK, "write TCR failed", err, ESP_FAIL);
+    /* stop receiving, no promiscuous mode, no runt packet(size < 64bytes), not all multicast packets*/
+    /* discard long packet(size > 1522bytes) and crc error packet, enable watchdog */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_RCR, RCR_DIS_LONG | RCR_DIS_CRC) == ESP_OK, "write RCR failed", err, ESP_FAIL);
+    /* send jam pattern (duration time = 1.15ms) when rx free space < 3k bytes */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_BPTR, 0x3F) == ESP_OK, "write BPTR failed", err, ESP_FAIL);
+    /* flow control: high water threshold = 3k bytes, low water threshold = 8k bytes */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_FCTR, 0x38) == ESP_OK, "write FCTR failed", err, ESP_FAIL);
+    /* enable flow control */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_FCR, FCR_FLOW_ENABLE) == ESP_OK, "write FCR failed", err, ESP_FAIL);
+    /* retry late collision packet, at most two transmit command can be issued before transmit complete */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_TCR2, TCR2_RLCP) == ESP_OK, "write TCR2 failed", err, ESP_FAIL);
+    /* enable auto transmit */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_ATCR, ATCR_AUTO_TX) == ESP_OK, "write ATCR failed", err, ESP_FAIL);
+    /* generate checksum for UDP, TCP and IPv4 packets */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_TCSCR, TCSCR_IPCSE | TCSCR_TCPCSE | TCSCR_UDPCSE) == ESP_OK,
+              "write TCSCR failed", err, ESP_FAIL);
+    /* disable check sum for receive packets */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_RCSCSR, 0x00) == ESP_OK, "write RCSCSR failed", err, ESP_FAIL);
+    /* interrupt pin config: push-pull output, active high */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_INTCR, 0x00) == ESP_OK, "write INTCR failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_INTCKCR, 0x00) == ESP_OK, "write INTCKCR failed", err, ESP_FAIL);
+    /* no length limitation for rx packets */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_RLENCR, 0x00) == ESP_OK, "write RLENCR failed", err, ESP_FAIL);
+    /* 3K-byte for TX and 13K-byte for RX */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_MEMSCR, 0x00) == ESP_OK, "write MEMSCR failed", err, ESP_FAIL);
+    /* reset tx and rx memory pointer */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_MPTRCR, MPTRCR_RST_RX | MPTRCR_RST_TX) == ESP_OK,
+              "write MPTRCR failed", err, ESP_FAIL);
+    /* clear network status: wakeup event, tx complete */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END) == ESP_OK, "write NSR failed", err, ESP_FAIL);
+    /* clear interrupt status */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_ISR, ISR_CLR_STATUS) == ESP_OK, "write ISR failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief start dm9051: enable interrupt and start receive
+ */
+static esp_err_t dm9051_start(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    /* enable interrupt */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_IMR, IMR_ALL) == ESP_OK, "write IMR failed", err, ESP_FAIL);
+    /* enable rx */
+    uint8_t rcr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_RCR, &rcr) == ESP_OK, "read RCR failed", err, ESP_FAIL);
+    rcr |= RCR_RXEN;
+    MAC_CHECK(dm9051_register_write(emac, DM9051_RCR, rcr) == ESP_OK, "write RCR failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+/**
+ * @brief stop dm9051: disable interrupt and stop receive
+ */
+static esp_err_t dm9051_stop(emac_dm9051_t *emac)
+{
+    esp_err_t ret = ESP_OK;
+    /* disable interrupt */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_IMR, 0x00) == ESP_OK, "write IMR failed", err, ESP_FAIL);
+    /* disable rx */
+    uint8_t rcr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_RCR, &rcr) == ESP_OK, "read RCR failed", err, ESP_FAIL);
+    rcr &= ~RCR_RXEN;
+    MAC_CHECK(dm9051_register_write(emac, DM9051_RCR, rcr) == ESP_OK, "write RCR failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static void dm9051_isr_handler(void *arg)
+{
+    emac_dm9051_t *emac = (emac_dm9051_t *)arg;
+    BaseType_t high_task_wakeup = pdFALSE;
+    /* notify dm9051 task */
+    vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup);
+    if (high_task_wakeup != pdFALSE) {
+        portYIELD_FROM_ISR();
+    }
+}
+
+static void emac_dm9051_task(void *arg)
+{
+    emac_dm9051_t *emac = (emac_dm9051_t *)arg;
+    uint8_t status = 0;
+    uint8_t *buffer = NULL;
+    uint32_t length = 0;
+    while (1) {
+        if (ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(RX_QUEUE_WAIT_MS))) {
+            /* clear interrupt status */
+            dm9051_register_read(emac, DM9051_ISR, &status);
+            dm9051_register_write(emac, DM9051_ISR, status);
+            /* packet received */
+            if (status & ISR_PR) {
+                do {
+                    buffer = (uint8_t *)heap_caps_malloc(ETH_MAX_PACKET_SIZE, MALLOC_CAP_DMA);
+                    if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) {
+                        /* pass the buffer to stack (e.g. TCP/IP layer) */
+                        emac->eth->stack_input(emac->eth, buffer, length);
+                    } else {
+                        free(buffer);
+                    }
+                } while (emac->packets_remain);
+            }
+        }
+    }
+    vTaskDelete(NULL);
+}
+
+static esp_err_t emac_dm9051_set_mediator(esp_eth_mac_t *mac, esp_eth_mediator_t *eth)
+{
+    esp_err_t ret = ESP_OK;
+    MAC_CHECK(eth, "can't set mac's mediator to null", err, ESP_ERR_INVALID_ARG);
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    emac->eth = eth;
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_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_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    /* check if phy access is in progress */
+    uint8_t epcr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_EPCR, &epcr) == ESP_OK, "read EPCR failed", err, ESP_FAIL);
+    MAC_CHECK(!(epcr & EPCR_ERRE), "phy is busy", err, ESP_ERR_INVALID_STATE);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_EPAR, (uint8_t)(((phy_addr << 6) & 0xFF) | phy_reg)) == ESP_OK,
+              "write EPAR failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_EPDRL, (uint8_t)(reg_value & 0xFF)) == ESP_OK,
+              "write EPDRL failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_EPDRH, (uint8_t)((reg_value >> 8) & 0xFF)) == ESP_OK,
+              "write EPDRH failed", err, ESP_FAIL);
+    /* select PHY and select write operation */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_EPCR, EPCR_EPOS | EPCR_ERPRW) == ESP_OK, "write EPCR failed", err, ESP_FAIL);
+    /* polling the busy flag */
+    uint32_t to = 0;
+    do {
+        ets_delay_us(100);
+        MAC_CHECK(dm9051_register_read(emac, DM9051_EPCR, &epcr) == ESP_OK, "read EPCR failed", err, ESP_FAIL);
+        to += 100;
+    } while ((epcr & EPCR_ERRE) && to < DM9051_PHY_OPERATION_TIMEOUT_US);
+    MAC_CHECK(!(epcr & EPCR_ERRE), "phy is busy", err, ESP_ERR_TIMEOUT);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_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;
+    MAC_CHECK(reg_value, "can't set reg_value to null", err, ESP_ERR_INVALID_ARG);
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    /* check if phy access is in progress */
+    uint8_t epcr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_EPCR, &epcr) == ESP_OK, "read EPCR failed", err, ESP_FAIL);
+    MAC_CHECK(!(epcr & 0x01), "phy is busy", err, ESP_ERR_INVALID_STATE);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_EPAR, (uint8_t)(((phy_addr << 6) & 0xFF) | phy_reg)) == ESP_OK,
+              "write EPAR failed", err, ESP_FAIL);
+    /* Select PHY and select read operation */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_EPCR, 0x0C) == ESP_OK, "write EPCR failed", err, ESP_FAIL);
+    /* polling the busy flag */
+    uint32_t to = 0;
+    do {
+        ets_delay_us(100);
+        MAC_CHECK(dm9051_register_read(emac, DM9051_EPCR, &epcr) == ESP_OK, "read EPCR failed", err, ESP_FAIL);
+        to += 100;
+    } while ((epcr & EPCR_ERRE) && to < DM9051_PHY_OPERATION_TIMEOUT_US);
+    MAC_CHECK(!(epcr & EPCR_ERRE), "phy is busy", err, ESP_ERR_TIMEOUT);
+    uint8_t value_h = 0;
+    uint8_t value_l = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_EPDRH, &value_h) == ESP_OK, "read EPDRH failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_read(emac, DM9051_EPDRL, &value_l) == ESP_OK, "read EPDRL failed", err, ESP_FAIL);
+    *reg_value = (value_h << 8) | value_l;
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_set_addr(esp_eth_mac_t *mac, uint8_t *addr)
+{
+    esp_err_t ret = ESP_OK;
+    MAC_CHECK(addr, "can't set mac addr to null", err, ESP_ERR_INVALID_ARG);
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    memcpy(emac->addr, addr, 6);
+    MAC_CHECK(dm9051_set_mac_addr(emac) == ESP_OK, "set mac address failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_get_addr(esp_eth_mac_t *mac, uint8_t *addr)
+{
+    esp_err_t ret = ESP_OK;
+    MAC_CHECK(addr, "can't set mac addr to null", err, ESP_ERR_INVALID_ARG);
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    memcpy(addr, emac->addr, 6);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_set_link(esp_eth_mac_t *mac, eth_link_t link)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    uint8_t nsr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_NSR, &nsr) == ESP_OK, "read NSR failed", err, ESP_FAIL);
+    switch (link) {
+    case ETH_LINK_UP:
+        MAC_CHECK(nsr & NSR_LINKST, "phy is not link up", err, ESP_ERR_INVALID_STATE);
+        MAC_CHECK(dm9051_start(emac) == ESP_OK, "dm9051 start failed", err, ESP_FAIL);
+        break;
+    case ETH_LINK_DOWN:
+        MAC_CHECK(!(nsr & NSR_LINKST), "phy is not link down", err, ESP_ERR_INVALID_STATE);
+        MAC_CHECK(dm9051_stop(emac) == ESP_OK, "dm9051 stop failed", err, ESP_FAIL);
+        break;
+    default:
+        MAC_CHECK(false, "unknown link status", err, ESP_ERR_INVALID_ARG);
+        break;
+    }
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_set_speed(esp_eth_mac_t *mac, eth_speed_t speed)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    uint8_t nsr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_NSR, &nsr) == ESP_OK, "read NSR failed", err, ESP_FAIL);
+    switch (speed) {
+    case ETH_SPEED_10M:
+        MAC_CHECK(nsr & NSR_SPEED, "phy speed is not at 10Mbps", err, ESP_ERR_INVALID_STATE);
+        break;
+    case ETH_SPEED_100M:
+        MAC_CHECK(!(nsr & NSR_SPEED), "phy speed is not at 100Mbps", err, ESP_ERR_INVALID_STATE);
+        break;
+    default:
+        MAC_CHECK(false, "unknown speed", err, ESP_ERR_INVALID_ARG);
+        break;
+    }
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    uint8_t ncr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_NCR, &ncr) == ESP_OK, "read NCR failed", err, ESP_FAIL);
+    switch (duplex) {
+    case ETH_DUPLEX_HALF:
+        MAC_CHECK(!(ncr & NCR_FDX), "phy is not at half duplex", err, ESP_ERR_INVALID_STATE);
+        break;
+    case ETH_DUPLEX_FULL:
+        MAC_CHECK(ncr & NCR_FDX, "phy is not at full duplex", err, ESP_ERR_INVALID_STATE);
+        break;
+    default:
+        MAC_CHECK(false, "unknown duplex", err, ESP_ERR_INVALID_ARG);
+        break;
+    }
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_set_promiscuous(esp_eth_mac_t *mac, bool enable)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    uint8_t rcr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_EPDRL, &rcr) == ESP_OK, "read RCR failed", err, ESP_FAIL);
+    if (enable) {
+        rcr |= RCR_PRMSC;
+    } else {
+        rcr &= ~RCR_PRMSC;
+    }
+    MAC_CHECK(dm9051_register_write(emac, DM9051_RCR, rcr) == ESP_OK, "write RCR failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    MAC_CHECK(buf, "can't set buf to null", err, ESP_ERR_INVALID_ARG);
+    MAC_CHECK(length, "buf length can't be zero", err, ESP_ERR_INVALID_ARG);
+    /* Check if last transmit complete */
+    uint8_t tcr = 0;
+    MAC_CHECK(dm9051_register_read(emac, DM9051_TCR, &tcr) == ESP_OK, "read TCR failed", err, ESP_FAIL);
+    MAC_CHECK(!(tcr & TCR_TXREQ), "last transmit still in progress", err, ESP_ERR_INVALID_STATE);
+    /* set tx length */
+    MAC_CHECK(dm9051_register_write(emac, DM9051_TXPLL, length & 0xFF) == ESP_OK, "write TXPLL failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_write(emac, DM9051_TXPLH, (length >> 8) & 0xFF) == ESP_OK, "write TXPLH failed", err, ESP_FAIL);
+    /* copy data to tx memory */
+    MAC_CHECK(dm9051_memory_write(emac, buf, length) == ESP_OK, "write memory failed", err, ESP_FAIL);
+    /* issue tx polling command */
+    tcr |= TCR_TXREQ;
+    MAC_CHECK(dm9051_register_write(emac, DM9051_TCR, tcr) == ESP_OK, "write TCR failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    MAC_CHECK(buf && length, "can't set buf and length to null", err, ESP_ERR_INVALID_ARG);
+    uint8_t rxbyte = 0;
+    uint16_t rx_len = 0;
+    __attribute__((aligned(4))) dm9051_rx_header_t header; // SPI driver needs the rx buffer 4 byte align
+    emac->packets_remain = false;
+    /* dummy read, get the most updated data */
+    MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL);
+    MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL);
+    /* rxbyte must be 0xFF, 0 or 1 */
+    if (rxbyte > 1) {
+        MAC_CHECK(dm9051_stop(emac) == ESP_OK, "stop dm9051 failed", err, ESP_FAIL);
+        /* reset rx fifo pointer */
+        MAC_CHECK(dm9051_register_write(emac, DM9051_MPTRCR, MPTRCR_RST_RX) == ESP_OK, "write MPTRCR failed", err, ESP_FAIL);
+        ets_delay_us(10);
+        MAC_CHECK(dm9051_start(emac) == ESP_OK, "start dm9051 failed", err, ESP_FAIL);
+        MAC_CHECK(false, "reset rx fifo pointer", err, ESP_FAIL);
+    } else if (rxbyte) {
+        MAC_CHECK(dm9051_memory_read(emac, (uint8_t *)&header, sizeof(header)) == ESP_OK, "read rx header failed", err, ESP_FAIL);
+        rx_len = header.length_low + (header.length_high << 8);
+        MAC_CHECK(dm9051_memory_read(emac, buf, rx_len) == ESP_OK, "read rx data failed", err, ESP_FAIL);
+        MAC_CHECK(!(header.status & 0xBF), "receive status error: %xH", err, ESP_FAIL, header.status);
+        *length = rx_len - 4; // substract the CRC length (4Bytes)
+        /* dummy read, get the most updated data */
+        MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL);
+        MAC_CHECK(dm9051_register_read(emac, DM9051_MRCMDX, &rxbyte) == ESP_OK, "read MRCMDX failed", err, ESP_FAIL);
+        emac->packets_remain = rxbyte > 0;
+    }
+    return ESP_OK;
+err:
+    return ret;
+}
+
+static esp_err_t emac_dm9051_init(esp_eth_mac_t *mac)
+{
+    esp_err_t ret = ESP_OK;
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    esp_eth_mediator_t *eth = emac->eth;
+    /* init gpio used by spi-ethernet interrupt */
+    gpio_pad_select_gpio(CONFIG_ETH_DM9051_INT_GPIO);
+    gpio_set_direction(CONFIG_ETH_DM9051_INT_GPIO, GPIO_MODE_INPUT);
+    gpio_set_pull_mode(CONFIG_ETH_DM9051_INT_GPIO, GPIO_PULLDOWN_ONLY);
+    gpio_set_intr_type(CONFIG_ETH_DM9051_INT_GPIO, GPIO_INTR_POSEDGE);
+    gpio_intr_enable(CONFIG_ETH_DM9051_INT_GPIO);
+    gpio_isr_handler_add(CONFIG_ETH_DM9051_INT_GPIO, dm9051_isr_handler, emac);
+    MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL) == ESP_OK, "lowlevel init failed", err, ESP_FAIL);
+    /* reset dm9051 */
+    MAC_CHECK(dm9051_reset(emac) == ESP_OK, "reset dm9051 failed", err, ESP_FAIL);
+    /* verify chip id */
+    MAC_CHECK(dm9051_verify_id(emac) == ESP_OK, "vefiry chip ID failed", err, ESP_FAIL);
+    /* default setup of internal registers */
+    MAC_CHECK(dm9051_setup_default(emac) == ESP_OK, "dm9051 default setup failed", err, ESP_FAIL);
+    /* clear multicast hash table */
+    MAC_CHECK(dm9051_clear_multicast_table(emac) == ESP_OK, "clear multicast table failed", err, ESP_FAIL);
+    /* get emac address from eeprom */
+    MAC_CHECK(dm9051_get_mac_addr(emac) == ESP_OK, "fetch ethernet mac address failed", err, ESP_FAIL);
+    return ESP_OK;
+err:
+    gpio_isr_handler_remove(CONFIG_ETH_DM9051_INT_GPIO);
+    gpio_reset_pin(CONFIG_ETH_DM9051_INT_GPIO);
+    eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL);
+    return ret;
+}
+
+static esp_err_t emac_dm9051_deinit(esp_eth_mac_t *mac)
+{
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    esp_eth_mediator_t *eth = emac->eth;
+    dm9051_stop(emac);
+    gpio_isr_handler_remove(CONFIG_ETH_DM9051_INT_GPIO);
+    gpio_reset_pin(CONFIG_ETH_DM9051_INT_GPIO);
+    eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL);
+    return ESP_OK;
+}
+
+static esp_err_t emac_dm9051_del(esp_eth_mac_t *mac)
+{
+    emac_dm9051_t *emac = __containerof(mac, emac_dm9051_t, parent);
+    vTaskDelete(emac->rx_task_hdl);
+    vSemaphoreDelete(emac->spi_lock);
+    free(emac);
+    return ESP_OK;
+}
+
+esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_mac_config_t *config)
+{
+    esp_eth_mac_t *ret = NULL;
+    MAC_CHECK(config, "can't set mac config to null", err, NULL);
+    MAC_CHECK(config->spi_hdl, "can't set spi handle to null", err, NULL);
+    emac_dm9051_t *emac = calloc(1, sizeof(emac_dm9051_t));
+    MAC_CHECK(emac, "calloc emac failed", err, NULL);
+    /* bind methods and attributes */
+    emac->sw_reset_timeout_ms = config->sw_reset_timeout_ms;
+    emac->spi_hdl = config->spi_hdl;
+    emac->parent.set_mediator = emac_dm9051_set_mediator;
+    emac->parent.init = emac_dm9051_init;
+    emac->parent.deinit = emac_dm9051_deinit;
+    emac->parent.del = emac_dm9051_del;
+    emac->parent.write_phy_reg = emac_dm9051_write_phy_reg;
+    emac->parent.read_phy_reg = emac_dm9051_read_phy_reg;
+    emac->parent.set_addr = emac_dm9051_set_addr;
+    emac->parent.get_addr = emac_dm9051_get_addr;
+    emac->parent.set_speed = emac_dm9051_set_speed;
+    emac->parent.set_duplex = emac_dm9051_set_duplex;
+    emac->parent.set_link = emac_dm9051_set_link;
+    emac->parent.set_promiscuous = emac_dm9051_set_promiscuous;
+    emac->parent.transmit = emac_dm9051_transmit;
+    emac->parent.receive = emac_dm9051_receive;
+    /* create mutex */
+    emac->spi_lock = xSemaphoreCreateMutex();
+    MAC_CHECK(emac->spi_lock, "create lock failed", err_lock, NULL);
+    /* create dm9051 task */
+    BaseType_t xReturned = xTaskCreate(emac_dm9051_task, "dm9051_tsk", config->rx_task_stack_size, emac,
+                                       config->rx_task_prio, &emac->rx_task_hdl);
+    MAC_CHECK(xReturned == pdPASS, "create dm9051 task failed", err_tsk, NULL);
+    return &(emac->parent);
+err_tsk:
+    vSemaphoreDelete(emac->spi_lock);
+err_lock:
+    free(emac);
+err:
+    return ret;
+}

+ 47 - 40
components/esp_eth/src/esp_eth_mac_esp32.c

@@ -41,6 +41,7 @@ static const char *TAG = "emac_esp32";
     } while (0)
 
 #define RX_QUEUE_WAIT_MS (20)
+#define PHY_OPERATION_TIMEOUT_US (1000)
 
 typedef struct {
     esp_eth_mac_t parent;
@@ -49,12 +50,8 @@ typedef struct {
     intr_handle_t intr_hdl;
     SemaphoreHandle_t rx_counting_sem;
     TaskHandle_t rx_task_hdl;
-    const char *name;
     uint32_t sw_reset_timeout_ms;
     uint32_t frames_remain;
-    uint32_t rx_task_stack_size;
-    uint32_t rx_task_prio;
-    uint32_t queue_len;
     uint8_t addr[6];
     uint8_t *rx_buf[CONFIG_ETH_DMA_RX_BUFFER_NUM];
     uint8_t *tx_buf[CONFIG_ETH_DMA_TX_BUFFER_NUM];
@@ -78,9 +75,15 @@ static esp_err_t emac_esp32_write_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr,
     MAC_CHECK(!emac_hal_is_mii_busy(emac->hal), "phy is busy", err, ESP_ERR_INVALID_STATE);
     emac_hal_set_phy_data(emac->hal, reg_value);
     emac_hal_set_phy_cmd(emac->hal, phy_addr, phy_reg, true);
-    /* Delay some time and Check for the Busy flag */
-    ets_delay_us(1000);
-    MAC_CHECK(!emac_hal_is_mii_busy(emac->hal), "phy is busy", err, ESP_ERR_INVALID_STATE);
+    /* polling the busy flag */
+    uint32_t to = 0;
+    bool busy = true;
+    do {
+        ets_delay_us(100);
+        busy = emac_hal_is_mii_busy(emac->hal);
+        to += 100;
+    } while (busy && to < PHY_OPERATION_TIMEOUT_US);
+    MAC_CHECK(!busy, "phy is busy", err, ESP_ERR_TIMEOUT);
     return ESP_OK;
 err:
     return ret;
@@ -93,9 +96,15 @@ static esp_err_t emac_esp32_read_phy_reg(esp_eth_mac_t *mac, uint32_t phy_addr,
     emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
     MAC_CHECK(!emac_hal_is_mii_busy(emac->hal), "phy is busy", err, ESP_ERR_INVALID_STATE);
     emac_hal_set_phy_cmd(emac->hal, phy_addr, phy_reg, false);
-    /* Delay some time and Check for the Busy flag */
-    ets_delay_us(1000);
-    MAC_CHECK(!emac_hal_is_mii_busy(emac->hal), "phy is busy", err, ESP_ERR_INVALID_STATE);
+    /* polling the busy flag */
+    uint32_t to = 0;
+    bool busy = true;
+    do {
+        ets_delay_us(100);
+        busy = emac_hal_is_mii_busy(emac->hal);
+        to += 100;
+    } while (busy && to < PHY_OPERATION_TIMEOUT_US);
+    MAC_CHECK(!busy, "phy is busy", err, ESP_ERR_TIMEOUT);
     /* Store value */
     *reg_value = emac_hal_get_phy_data(emac->hal);
     return ESP_OK;
@@ -224,16 +233,14 @@ err:
 static void emac_esp32_rx_task(void *arg)
 {
     emac_esp32_t *emac = (emac_esp32_t *)arg;
-    esp_eth_mediator_t *eth = emac->eth;
     uint8_t *buffer = NULL;
     uint32_t length = 0;
-
     while (1) {
         if (xSemaphoreTake(emac->rx_counting_sem, pdMS_TO_TICKS(RX_QUEUE_WAIT_MS)) == pdTRUE) {
             buffer = (uint8_t *)malloc(ETH_MAX_PACKET_SIZE);
             if (emac_esp32_receive(&emac->parent, buffer, &length) == ESP_OK) {
                 /* pass the buffer to stack (e.g. TCP/IP layer) */
-                eth->stack_input(eth, buffer, length);
+                emac->eth->stack_input(emac->eth, buffer, length);
             } else {
                 free(buffer);
             }
@@ -298,50 +305,32 @@ static esp_err_t emac_esp32_init(esp_eth_mac_t *mac)
     MAC_CHECK(esp_read_mac(emac->addr, ESP_MAC_ETH) == ESP_OK, "fetch ethernet mac address failed", err, ESP_FAIL);
     /* set MAC address to emac register */
     emac_hal_set_address(emac->hal, emac->addr);
-    /* Interrupt configuration */
-    MAC_CHECK(esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, ESP_INTR_FLAG_IRAM, emac_hal_isr,
-                             &emac->hal, &(emac->intr_hdl)) == ESP_OK,
-              "alloc emac interrupt failed", err, ESP_FAIL);
-    /* create counting semaphore */
-    emac->rx_counting_sem = xSemaphoreCreateCounting(emac->queue_len, 0);
-    MAC_CHECK(emac->rx_counting_sem, "create semaphore failed", err_sem, ESP_FAIL);
-    /* create rx task */
-    BaseType_t xReturned = xTaskCreate(emac_esp32_rx_task, "emac_rx", emac->rx_task_stack_size, emac,
-                                       emac->rx_task_prio, &emac->rx_task_hdl);
-    MAC_CHECK(xReturned == pdPASS, "create emac_rx task failed", err_task, ESP_FAIL);
     return ESP_OK;
-err_task:
-    vSemaphoreDelete(emac->rx_counting_sem);
-err_sem:
-    esp_intr_free(emac->intr_hdl);
 err:
-    periph_module_disable(PERIPH_EMAC_MODULE);
     eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL);
+    periph_module_disable(PERIPH_EMAC_MODULE);
     return ret;
 }
 
 static esp_err_t emac_esp32_deinit(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;
 #if CONFIG_ETH_PHY_USE_RST
     gpio_set_level(CONFIG_ETH_PHY_RST_GPIO, 0);
 #endif
     emac_hal_stop(emac->hal);
-    MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL) == ESP_OK, "lowlevel deinit failed", err, ESP_FAIL);
-    MAC_CHECK(esp_intr_free(emac->intr_hdl) == ESP_OK, "free emac interrupt failed", err, ESP_FAIL);
-    vTaskDelete(emac->rx_task_hdl);
-    vSemaphoreDelete(emac->rx_counting_sem);
+    eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL);
     periph_module_disable(PERIPH_EMAC_MODULE);
     return ESP_OK;
-err:
-    return ret;
 }
 
 static esp_err_t emac_esp32_del(esp_eth_mac_t *mac)
 {
     emac_esp32_t *emac = __containerof(mac, emac_esp32_t, parent);
+    esp_intr_free(emac->intr_hdl);
+    vTaskDelete(emac->rx_task_hdl);
+    vSemaphoreDelete(emac->rx_counting_sem);
     int i = 0;
     for (i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
         free(emac->hal->rx_buf[i]);
@@ -400,10 +389,6 @@ esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_mac_config_t *config)
     /* initialize hal layer driver */
     emac_hal_init(emac->hal, descriptors, emac->rx_buf, emac->tx_buf);
     emac->sw_reset_timeout_ms = config->sw_reset_timeout_ms;
-    emac->rx_task_stack_size = config->rx_task_stack_size;
-    emac->rx_task_prio = config->rx_task_prio;
-    emac->queue_len = config->queue_len;
-    emac->name = "emac_esp32";
     emac->parent.set_mediator = emac_esp32_set_mediator;
     emac->parent.init = emac_esp32_init;
     emac->parent.deinit = emac_esp32_deinit;
@@ -418,7 +403,29 @@ esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_mac_config_t *config)
     emac->parent.set_promiscuous = emac_esp32_set_promiscuous;
     emac->parent.transmit = emac_esp32_transmit;
     emac->parent.receive = emac_esp32_receive;
+    /* Interrupt configuration */
+    MAC_CHECK(esp_intr_alloc(ETS_ETH_MAC_INTR_SOURCE, ESP_INTR_FLAG_IRAM, emac_hal_isr,
+                             &emac->hal, &(emac->intr_hdl)) == ESP_OK,
+              "alloc emac interrupt failed", err_intr, NULL);
+    /* create counting semaphore */
+    emac->rx_counting_sem = xSemaphoreCreateCounting(config->queue_len, 0);
+    MAC_CHECK(emac->rx_counting_sem, "create semaphore failed", err_sem, NULL);
+    /* create rx task */
+    BaseType_t xReturned = xTaskCreate(emac_esp32_rx_task, "emac_rx", config->rx_task_stack_size, emac,
+                                       config->rx_task_prio, &emac->rx_task_hdl);
+    MAC_CHECK(xReturned == pdPASS, "create emac_rx task failed", err_task, NULL);
     return &(emac->parent);
+err_task:
+    vSemaphoreDelete(emac->rx_counting_sem);
+err_sem:
+    esp_intr_free(emac->intr_hdl);
+err_intr:
+    for (int i = 0; i < CONFIG_ETH_DMA_TX_BUFFER_NUM; i++) {
+        free(emac->tx_buf[i]);
+    }
+    for (int i = 0; i < CONFIG_ETH_DMA_RX_BUFFER_NUM; i++) {
+        free(emac->rx_buf[i]);
+    }
 err_buffer:
     free(emac->hal);
 err_hal:

+ 302 - 0
components/esp_eth/src/esp_eth_phy_dm9051.c

@@ -0,0 +1,302 @@
+// Copyright 2019 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 <string.h>
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include "esp_log.h"
+#include "esp_eth.h"
+#include "eth_phy_regs_struct.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+static const char *TAG = "dm9051";
+#define PHY_CHECK(a, str, goto_tag, ...)                                          \
+    do                                                                            \
+    {                                                                             \
+        if (!(a))                                                                 \
+        {                                                                         \
+            ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
+            goto goto_tag;                                                        \
+        }                                                                         \
+    } while (0)
+
+/***************Vendor Specific Register***************/
+
+/**
+ * @brief DSCR(DAVICOM Specified Configuration Register)
+ *
+ */
+typedef union {
+    struct {
+        uint32_t reserved1 : 1; /* Reserved */
+        uint32_t sleep : 1;     /* Set 1 to enable PHY into sleep mode */
+        uint32_t mfpsc : 1;     /* MII frame preamble suppression control bit */
+        uint32_t smrst : 1;     /* Set 1 to reset all state machines of PHY */
+        uint32_t rpdctr_en : 1; /* Set 1 to enable automatic reduced power down */
+        uint32_t reserved2 : 2; /* Reserved */
+        uint32_t flink100 : 1;  /* Force Good Link in 100Mbps */
+        uint32_t reserved3 : 2; /* Reserved */
+        uint32_t tx_fx : 1;     /* 100BASE-TX or FX Mode Control */
+        uint32_t reserved4 : 1; /* Reserved */
+        uint32_t bp_adpok : 1;  /* BYPASS ADPOK */
+        uint32_t bp_align : 1;  /* Bypass Symbol Alignment Function */
+        uint32_t bp_scr : 1;    /* Bypass Scrambler/Descrambler Function */
+        uint32_t bp_4b5b : 1;   /* Bypass 4B5B Encoding and 5B4B Decoding */
+    };
+    uint32_t val;
+} dscr_reg_t;
+#define ETH_PHY_DSCR_REG_ADDR (0x10)
+
+/**
+ * @brief DSCSR(DAVICOM Specified Configuration and Status Register)
+ *
+ */
+typedef union {
+    struct {
+        uint32_t anmb : 4;     /* Auto-Negotiation Monitor Bits */
+        uint32_t phy_addr : 5; /* PHY Address */
+        uint32_t reserved : 3; /* Reserved */
+        uint32_t hdx10 : 1;    /* 10M Half-Duplex Operation Mode */
+        uint32_t fdx10 : 1;    /* 10M Full-Duplex Operation Mode */
+        uint32_t hdx100 : 1;   /* 100M Half-Duplex Operation Mode */
+        uint32_t fdx100 : 1;   /* 100M Full-Duplex Operation Mode */
+    };
+    uint32_t val;
+} dscsr_reg_t;
+#define ETH_PHY_DSCSR_REG_ADDR (0x11)
+
+typedef struct {
+    esp_eth_phy_t parent;
+    esp_eth_mediator_t *eth;
+    const char *name;
+    uint32_t addr;
+    uint32_t reset_timeout_ms;
+    uint32_t autonego_timeout_ms;
+    eth_link_t link_status;
+} phy_dm9051_t;
+
+static esp_err_t dm9051_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth)
+{
+    PHY_CHECK(eth, "can't set mediator for dm9051 to null", err);
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    dm9051->eth = eth;
+    return ESP_OK;
+err:
+    return ESP_ERR_INVALID_ARG;
+}
+
+static esp_err_t dm9051_get_link(esp_eth_phy_t *phy)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    esp_eth_mediator_t *eth = dm9051->eth;
+    bmsr_reg_t bmsr;
+
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed", err);
+    eth_link_t link = bmsr.link_status ? ETH_LINK_UP : ETH_LINK_DOWN;
+    if (dm9051->link_status != link) {
+        if (link == ETH_LINK_UP) {
+            phy->negotiate(phy);
+        } else {
+            PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link) == ESP_OK, "send link event failed", err);
+            dm9051->link_status = link;
+        }
+    }
+    return ESP_OK;
+err:
+    return ESP_FAIL;
+}
+
+static esp_err_t dm9051_reset(esp_eth_phy_t *phy)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    esp_eth_mediator_t *eth = dm9051->eth;
+    dscr_reg_t dscr;
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_DSCR_REG_ADDR, &(dscr.val)) == ESP_OK, "read DSCR failed", err);
+    dscr.smrst = 1;
+    PHY_CHECK(eth->phy_reg_write(eth, dm9051->addr, ETH_PHY_DSCR_REG_ADDR, dscr.val) == ESP_OK, "write DSCR failed", err);
+    bmcr_reg_t bmcr = {.reset = 1};
+    PHY_CHECK(eth->phy_reg_write(eth, dm9051->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
+    /* Wait for reset complete */
+    uint32_t to = 0;
+    for (to = 0; to < dm9051->reset_timeout_ms / 10; to++) {
+        vTaskDelay(pdMS_TO_TICKS(10));
+        PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed", err);
+        PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_DSCR_REG_ADDR, &(dscr.val)) == ESP_OK, "read DSCR failed", err);
+        if (!bmcr.reset && !dscr.smrst) {
+            break;
+        }
+    }
+    PHY_CHECK(to < dm9051->reset_timeout_ms / 10, "PHY reset timeout", err);
+    return ESP_OK;
+err:
+    return ESP_FAIL;
+}
+
+static esp_err_t dm9051_negotiate(esp_eth_phy_t *phy)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    esp_eth_mediator_t *eth = dm9051->eth;
+    /* Start auto negotiation */
+    bmcr_reg_t bmcr = {
+        .speed_select = 1,     /* 100Mbps */
+        .duplex_mode = 1,      /* Full Duplex */
+        .en_auto_nego = 1,     /* Auto Negotiation */
+        .restart_auto_nego = 1 /* Restart Auto Negotiation */
+    };
+    PHY_CHECK(eth->phy_reg_write(eth, dm9051->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
+    /* Wait for auto negotiation complete */
+    bmsr_reg_t bmsr;
+    dscsr_reg_t dscsr;
+    uint32_t to = 0;
+    for (to = 0; to < dm9051->autonego_timeout_ms / 10; to++) {
+        vTaskDelay(pdMS_TO_TICKS(10));
+        PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed", err);
+        PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_DSCSR_REG_ADDR, &(dscsr.val)) == ESP_OK, "read DSCSR failed", err);
+        if (bmsr.auto_nego_complete && dscsr.anmb & 0x08) {
+            break;
+        }
+    }
+    if (to >= dm9051->autonego_timeout_ms / 10) {
+        ESP_LOGW(TAG, "Ethernet PHY auto negotiation timeout");
+    }
+    /* Updata information about link, speed, duplex */
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed", err);
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_DSCSR_REG_ADDR, &(dscsr.val)) == ESP_OK, "read DSCSR failed", err);
+    eth_link_t link = bmsr.link_status ? ETH_LINK_UP : ETH_LINK_DOWN;
+    eth_speed_t speed = ETH_SPEED_10M;
+    eth_duplex_t duplex = ETH_DUPLEX_HALF;
+    if (dscsr.fdx100 || dscsr.hdx100) {
+        speed = ETH_SPEED_100M;
+    } else {
+        speed = ETH_SPEED_10M;
+    }
+    if (dscsr.fdx100 || dscsr.fdx10) {
+        duplex = ETH_DUPLEX_FULL;
+    } else {
+        duplex = ETH_DUPLEX_HALF;
+    }
+    PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)speed) == ESP_OK, "send speed event failed", err);
+    PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)duplex) == ESP_OK, "send duplex event failed", err);
+    if (dm9051->link_status != link) {
+        PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link) == ESP_OK, "send link event failed", err);
+        dm9051->link_status = link;
+    }
+    return ESP_OK;
+err:
+    return ESP_FAIL;
+}
+
+static esp_err_t dm9051_pwrctl(esp_eth_phy_t *phy, bool enable)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    esp_eth_mediator_t *eth = dm9051->eth;
+    bmcr_reg_t bmcr;
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed", err);
+    if (!enable) {
+        /* Enable IEEE Power Down Mode */
+        bmcr.power_down = 1;
+    } else {
+        /* Disable IEEE Power Down Mode */
+        bmcr.power_down = 0;
+    }
+    PHY_CHECK(eth->phy_reg_write(eth, dm9051->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed", err);
+    if (!enable) {
+        PHY_CHECK(bmcr.power_down == 1, "power down failed", err);
+    } else {
+        PHY_CHECK(bmcr.power_down == 0, "power up failed", err);
+    }
+    return ESP_OK;
+err:
+    return ESP_FAIL;
+}
+
+static esp_err_t dm9051_set_addr(esp_eth_phy_t *phy, uint32_t addr)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    dm9051->addr = addr;
+    return ESP_OK;
+}
+
+static esp_err_t dm9051_get_addr(esp_eth_phy_t *phy, uint32_t *addr)
+{
+    PHY_CHECK(addr, "get phy address failed", err);
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    *addr = dm9051->addr;
+    return ESP_OK;
+err:
+    return ESP_ERR_INVALID_ARG;
+}
+
+static esp_err_t dm9051_del(esp_eth_phy_t *phy)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    free(dm9051);
+    return ESP_OK;
+}
+
+static esp_err_t dm9051_init(esp_eth_phy_t *phy)
+{
+    phy_dm9051_t *dm9051 = __containerof(phy, phy_dm9051_t, parent);
+    esp_eth_mediator_t *eth = dm9051->eth;
+    /* Power on Ethernet PHY */
+    PHY_CHECK(dm9051_pwrctl(phy, true) == ESP_OK, "power on Ethernet PHY failed", err);
+    /* Reset Ethernet PHY */
+    PHY_CHECK(dm9051_reset(phy) == ESP_OK, "reset Ethernet PHY failed", err);
+    /* Check PHY ID */
+    phyidr1_reg_t id1;
+    phyidr2_reg_t id2;
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_IDR1_REG_ADDR, &(id1.val)) == ESP_OK, "read ID1 failed", err);
+    PHY_CHECK(eth->phy_reg_read(eth, dm9051->addr, ETH_PHY_IDR2_REG_ADDR, &(id2.val)) == ESP_OK, "read ID2 failed", err);
+    PHY_CHECK(id1.oui_msb == 0x0181 && id2.oui_lsb == 0x2E && id2.vendor_model == 0x0A, "wrong PHY chip ID", err);
+    return ESP_OK;
+err:
+    return ESP_FAIL;
+}
+
+static esp_err_t dm9051_deinit(esp_eth_phy_t *phy)
+{
+    /* Power off Ethernet PHY */
+    PHY_CHECK(dm9051_pwrctl(phy, false) == ESP_OK, "power off Ethernet PHY failed", err);
+    return ESP_OK;
+err:
+    return ESP_FAIL;
+}
+
+esp_eth_phy_t *esp_eth_phy_new_dm9051(const eth_phy_config_t *config)
+{
+    PHY_CHECK(config, "can't set phy config to null", err);
+    PHY_CHECK(config->phy_addr == 1, "dm9051's phy address can only set to 1", err);
+    phy_dm9051_t *dm9051 = calloc(1, sizeof(phy_dm9051_t));
+    PHY_CHECK(dm9051, "calloc dm9051 object failed", err);
+    dm9051->name = "dm9051";
+    dm9051->addr = config->phy_addr;
+    dm9051->reset_timeout_ms = config->reset_timeout_ms;
+    dm9051->link_status = ETH_LINK_DOWN;
+    dm9051->autonego_timeout_ms = config->autonego_timeout_ms;
+    dm9051->parent.reset = dm9051_reset;
+    dm9051->parent.init = dm9051_init;
+    dm9051->parent.deinit = dm9051_deinit;
+    dm9051->parent.set_mediator = dm9051_set_mediator;
+    dm9051->parent.negotiate = dm9051_negotiate;
+    dm9051->parent.get_link = dm9051_get_link;
+    dm9051->parent.pwrctl = dm9051_pwrctl;
+    dm9051->parent.get_addr = dm9051_get_addr;
+    dm9051->parent.set_addr = dm9051_set_addr;
+    dm9051->parent.del = dm9051_del;
+
+    return &(dm9051->parent);
+err:
+    return NULL;
+}

+ 46 - 0
components/esp_eth/test/test_emac.c

@@ -8,6 +8,7 @@
 #include "test_utils.h"
 #include "esp_eth.h"
 #include "esp_log.h"
+#include "sdkconfig.h"
 
 static const char *TAG = "esp_eth_test";
 /** Event handler for Ethernet events */
@@ -89,3 +90,48 @@ TEST_CASE("ethernet tcpip_adapter", "[ethernet][ignore]")
     TEST_ESP_OK(esp_eth_driver_install(&config, &eth_handle));
     vTaskDelay(portMAX_DELAY);
 }
+
+#if CONFIG_ETH_USE_SPI_ETHERNET
+TEST_CASE("dm9051 io test", "[ethernet][ignore]")
+{
+    spi_device_handle_t spi_handle = NULL;
+    spi_bus_config_t buscfg = {
+        .miso_io_num = 25,
+        .mosi_io_num = 23,
+        .sclk_io_num = 19,
+        .quadwp_io_num = -1,
+        .quadhd_io_num = -1,
+    };
+    TEST_ESP_OK(spi_bus_initialize(HSPI_HOST, &buscfg, 1));
+    spi_device_interface_config_t devcfg = {
+        .command_bits = 1,
+        .address_bits = 7,
+        .mode = 0,
+        .clock_speed_hz = 20 * 1000 * 1000,
+        .spics_io_num = 22,
+        .queue_size = 20
+    };
+    TEST_ESP_OK(spi_bus_add_device(HSPI_HOST, &devcfg, &spi_handle));
+    gpio_install_isr_service(0);
+    tcpip_adapter_init();
+    TEST_ESP_OK(esp_event_loop_create_default());
+    TEST_ESP_OK(tcpip_adapter_set_default_eth_handlers());
+    TEST_ESP_OK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, NULL));
+    TEST_ESP_OK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
+    eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
+    mac_config.spi_hdl = spi_handle;
+    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&mac_config);
+    eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
+    esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
+    esp_eth_handle_t eth_handle = NULL;
+    TEST_ESP_OK(esp_eth_driver_install(&config, &eth_handle));
+    vTaskDelay(pdMS_TO_TICKS(portMAX_DELAY));
+    TEST_ESP_OK(esp_eth_driver_uninstall(eth_handle));
+    TEST_ESP_OK(phy->del(phy));
+    TEST_ESP_OK(mac->del(mac));
+    TEST_ESP_OK(esp_event_loop_delete_default());
+    TEST_ESP_OK(spi_bus_remove_device(spi_handle));
+    TEST_ESP_OK(spi_bus_free(HSPI_HOST));
+}
+#endif

+ 0 - 2
components/soc/esp32/emac_hal.c

@@ -163,8 +163,6 @@ void emac_hal_reset_desc_chain(emac_hal_context_t *hal)
         hal->tx_desc[i].TDES1.TransmitBuffer1Size = CONFIG_ETH_DMA_BUFFER_SIZE;
         /* Enable Ethernet DMA Tx Descriptor interrupt */
         hal->tx_desc[1].TDES0.InterruptOnComplete = 1;
-        /* Set the DMA Tx descriptors checksum insertion */
-        hal->tx_desc[i].TDES0.ChecksumInsertControl = EMAC_DMATXDESC_CHECKSUM_TCPUDPICMPFULL;
         /* Enable Transmit Timestamp */
         hal->tx_desc[i].TDES0.TransmitTimestampEnable = 1;
         /* point to the buffer */

+ 107 - 42
examples/common_components/protocol_examples_common/Kconfig.projbuild

@@ -1,5 +1,4 @@
 menu "Example Connection Configuration"
-
     choice EXAMPLE_CONNECT_INTERFACE
         prompt "Connect using"
         default EXAMPLE_CONNECT_WIFI
@@ -15,53 +14,120 @@ menu "Example Connection Configuration"
 
     endchoice
 
-    config EXAMPLE_WIFI_SSID
-        depends on EXAMPLE_CONNECT_WIFI
-        string "WiFi SSID"
-        default "myssid"
-        help
-            SSID (network name) for the example to connect to.
-
-    config EXAMPLE_WIFI_PASSWORD
-        depends on EXAMPLE_CONNECT_WIFI
-        string "WiFi Password"
-        default "mypassword"
-        help
-            WiFi password (WPA or WPA2) for the example to use.
-            Can be left blank if the network has no security set.
-
-    choice EXAMPLE_ETH_PHY_MODEL
-        depends on EXAMPLE_CONNECT_ETHERNET
-        prompt "Ethernet PHY Device"
-        default EXAMPLE_ETH_PHY_IP101
-        help
-            Select the PHY driver to use for the example.
-
-        config EXAMPLE_ETH_PHY_IP101
-            bool "IP101"
+    if EXAMPLE_CONNECT_WIFI
+        config EXAMPLE_WIFI_SSID
+            string "WiFi SSID"
+            default "myssid"
             help
-                IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
-                Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+                SSID (network name) for the example to connect to.
 
-        config EXAMPLE_ETH_PHY_RTL8201
-            bool "RTL8201/SR8201"
+        config EXAMPLE_WIFI_PASSWORD
+            string "WiFi Password"
+            default "mypassword"
             help
-                    RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
-                    Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+                WiFi password (WPA or WPA2) for the example to use.
+                Can be left blank if the network has no security set.
+    endif
 
-        config EXAMPLE_ETH_PHY_LAN8720
-            bool "LAN8720"
+    if EXAMPLE_CONNECT_ETHERNET
+        choice EXAMPLE_USE_ETHERNET
+            prompt "Ethernet Type"
+            default EXAMPLE_USE_INTERNAL_ETHERNET if IDF_TARGET_ESP32
+            default EXAMPLE_USE_SPI_ETHERNET if !IDF_TARGET_ESP32
             help
-                    LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
-                    Goto https://www.microchip.com/LAN8720A for more information about it.
+                Select which kind of Ethernet will be used in the example.
 
-        config EXAMPLE_ETH_PHY_DP83848
-            bool "DP83848"
-            help
-                    DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
-                    Goto http://www.ti.com/product/DP83848J for more information about it.
+            config EXAMPLE_USE_INTERNAL_ETHERNET
+                depends on IDF_TARGET_ESP32
+                select ETH_USE_ESP32_EMAC
+                bool "Internal EMAC"
+                help
+                    Select internal Ethernet MAC controller.
 
-    endchoice
+            config EXAMPLE_USE_SPI_ETHERNET
+                bool "SPI Ethernet Module"
+                select ETH_USE_SPI_ETHERNET
+                help
+                    Select external SPI-Ethernet module.
+        endchoice
+
+        if EXAMPLE_USE_INTERNAL_ETHERNET
+            choice EXAMPLE_ETH_PHY_MODEL
+                prompt "Ethernet PHY Device"
+                default EXAMPLE_ETH_PHY_IP101
+                help
+                    Select the Ethernet PHY device to use in the example.
+
+                config EXAMPLE_ETH_PHY_IP101
+                    bool "IP101"
+                    help
+                        IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
+                        Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+
+                config EXAMPLE_ETH_PHY_RTL8201
+                    bool "RTL8201/SR8201"
+                    help
+                        RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
+                        Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+
+                config EXAMPLE_ETH_PHY_LAN8720
+                    bool "LAN8720"
+                    help
+                        LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
+                        Goto https://www.microchip.com/LAN8720A for more information about it.
+
+                config EXAMPLE_ETH_PHY_DP83848
+                    bool "DP83848"
+                    help
+                        DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
+                        Goto http://www.ti.com/product/DP83848J for more information about it.
+            endchoice
+        endif
+
+        if EXAMPLE_USE_SPI_ETHERNET
+            config EXAMPLE_ETH_SPI_HOST
+                int "SPI Host Number"
+                range 0 2
+                default 1
+                help
+                    Set the SPI host used to communicate with DM9051.
+
+            config EXAMPLE_ETH_SCLK_GPIO
+                int "SPI SCLK GPIO number"
+                range 0 33
+                default 19
+                help
+                    Set the GPIO number used by SPI SCLK.
+
+            config EXAMPLE_ETH_MOSI_GPIO
+                int "SPI MOSI GPIO number"
+                range 0 33
+                default 23
+                help
+                    Set the GPIO number used by SPI MOSI.
+
+            config EXAMPLE_ETH_MISO_GPIO
+                int "SPI MISO GPIO number"
+                range 0 33
+                default 25
+                help
+                    Set the GPIO number used by SPI MISO.
+
+            config EXAMPLE_ETH_CS_GPIO
+                int "SPI CS GPIO number"
+                range 0 33
+                default 22
+                help
+                    Set the GPIO number used by SPI CS.
+
+            config EXAMPLE_ETH_SPI_CLOCK_MHZ
+                int "SPI clock speed (MHz)"
+                range 20 80
+                default 20
+                help
+                    Set the clock speed (MHz) of SPI interface.
+        endif
+    endif
 
     config EXAMPLE_CONNECT_IPV6
         bool "Obtain IPv6 link-local address"
@@ -69,5 +135,4 @@ menu "Example Connection Configuration"
         help
             By default, examples will wait until IPv4 and IPv6 addresses are obtained.
             Disable this option if the network does not support IPv6.
-
 endmenu

+ 27 - 2
examples/common_components/protocol_examples_common/connect.c

@@ -187,8 +187,9 @@ static void start()
     ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_GOT_IP6, &on_got_ipv6, NULL));
 #endif
     eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
-    s_mac = esp_eth_mac_new_esp32(&mac_config);
     eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
+    s_mac = esp_eth_mac_new_esp32(&mac_config);
 #if CONFIG_EXAMPLE_ETH_PHY_IP101
     s_phy = esp_eth_phy_new_ip101(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
@@ -197,9 +198,33 @@ static void start()
     s_phy = esp_eth_phy_new_lan8720(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_DP83848
     s_phy = esp_eth_phy_new_dp83848(&phy_config);
+#endif
+#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
+    gpio_install_isr_service(0);
+    spi_device_handle_t spi_handle = NULL;
+    spi_bus_config_t buscfg = {
+        .miso_io_num = CONFIG_EXAMPLE_ETH_MISO_GPIO,
+        .mosi_io_num = CONFIG_EXAMPLE_ETH_MOSI_GPIO,
+        .sclk_io_num = CONFIG_EXAMPLE_ETH_SCLK_GPIO,
+        .quadwp_io_num = -1,
+        .quadhd_io_num = -1,
+    };
+    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
+    spi_device_interface_config_t devcfg = {
+        .command_bits = 1,
+        .address_bits = 7,
+        .mode = 0,
+        .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
+        .spics_io_num = CONFIG_EXAMPLE_ETH_CS_GPIO,
+        .queue_size = 20
+    };
+    ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
+    /* dm9051 ethernet driver is based on spi driver, so need to specify the spi handle */
+    mac_config.spi_hdl = spi_handle;
+    s_mac = esp_eth_mac_new_dm9051(&mac_config);
+    s_phy = esp_eth_phy_new_dm9051(&phy_config);
 #endif
     esp_eth_config_t config = ETH_DEFAULT_CONFIG(s_mac, s_phy);
-
     ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle));
     s_connection_name = "Ethernet";
 }

+ 37 - 25
examples/ethernet/basic/README.md

@@ -15,35 +15,47 @@ If you have a new Ethernet application to go (for example, connect to IoT cloud
 
 ### Hardware Required
 
-To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
+To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports up to four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
+
+`esp_eth` component not only supports ESP32 internal Ethernet MAC controller, but also can drive third-party Ethernet module which integrates MAC and PHY and provides SPI interface. This example also take the **DM9051** as an example, illustrating how to install the Ethernet driver with only a little different configuration.
 
 ### Project configuration in menuconfig
 
 Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you' are using CMake based build system.
 
 1. In the `Example Configuration` menu:
+    * Choose the kind of Ethernet this example will run on under `Ethernet Type`.
+    * If `Internal EMAC` is selected:
+        * Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
 
-* Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
+    * If `SPI Ethernet Module` is selected:
+        * Set SPI specific configuration, including GPIO and clock speed.
 
 2. In the `Component config > Ethernet` menu:
+    * If `Internal EMAC` is selected:
+        * Enable `Use ESP32 internal EMAC controller`, and then go into this menu.
+        * In the `PHY interface`, it's highly recommended that you choose `Reduced Media Independent Interface (RMII)` which will cost fewer pins.
+        * In the `RMII clock mode`, you can choose the source of RMII clock (50MHz): `Input RMII clock from external` or `Output RMII clock from internal`.
+        * Once `Output RMII clock from internal` is enabled, you also have to set the number of the GPIO used for outputting the RMII clock under `RMII clock GPIO number`. In this case, you can set the GPIO number to 16 or 17.
+        * Once `Output RMII clock from GPIO0 (Experimental!)` is enabled, then you have no choice but GPIO0 to output the RMII clock.
+        * Set SMI MDC/MDIO GPIO number according to board schematic, by default these two GPIOs are set as below:
+
+            | Default Example GPIO | RMII Signal | Notes         |
+            | -------------------- | ----------- | ------------- |
+            | GPIO23               | MDC         | Output to PHY |
+            | GPIO18               | MDIO        | Bidirectional |
 
-* Enable `Use ESP32 internal EMAC controller`, and then go into this menu.
-* In the `PHY interface`, it's highly recommended that you choose `Reduced Media Independent Interface (RMII)` which will cost fewer pins.
-* In the `RMII clock mode`, you can choose the source of RMII clock (50MHz): `Input RMII clock from external` or `Output RMII clock from internal`.
-* Once `Output RMII clock from internal` is enabled, you also have to set the number of the GPIO used for outputting the RMII clock under `RMII clock GPIO number`. In this case, you can set the GPIO number to 16 or 17.
-* Once `Output RMII clock from GPIO0 (Experimental!)` is enabled, then you have no choice but GPIO0 to output the RMII clock.
-* Set SMI MDC/MDIO GPIO number according to board schematic, by default these two GPIOs are set as below:
+        * If you have connect a GPIO to the PHY chip's RST pin, then you need to enable `Use Reset Pin of PHY Chip` and set the GPIO number under `PHY RST GPIO number`.
 
-  | Default Example GPIO | RMII Signal | Notes         |
-  | -------------------- | ----------- | ------------- |
-  | GPIO23               | MDC         | Output to PHY |
-  | GPIO18               | MDIO        | Bidirectional |
+    * If `SPI Ethernet Module` is selected:
+        * Set the GPIO number used by interrupt pin under `DM9051 Interrupt GPIO number`.
 
-* If you have connect a GPIO to the PHY chip's RST pin, then you need to enable `Use Reset Pin of PHY Chip` and set the GPIO number under `PHY RST GPIO number`.
 
 ### Extra configuration in the code (Optional)
 
 * By default Ethernet driver will assume the PHY address to `1`, but you can alway reconfigure this value after `eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();`. The actual PHY address should depend on the hardware you use, so make sure to consult the schematic and datasheet.
+
+**Note:** DM9051 has a fixed PHY address `1`, which cannot be modified.
   
 ### Build and Flash
 
@@ -56,20 +68,20 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l
 ## Example Output
 
 ```bash
-I (336) cpu_start: Starting scheduler on PRO CPU.
-I (0) cpu_start: Starting scheduler on APP CPU.
-I (382) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
-I (392) eth_example: Ethernet Started
-I (3922) eth_example: Ethernet Link Up
-I (5862) tcpip_adapter: eth ip: 192.168.2.151, mask: 255.255.255.0, gw: 192.168.2.2
-I (5862) eth_example: Ethernet Got IP Address
-I (5862) eth_example: ~~~~~~~~~~~
-I (5862) eth_example: ETHIP:192.168.2.151
-I (5872) eth_example: ETHMASK:255.255.255.0
-I (5872) eth_example: ETHGW:192.168.2.2
-I (5882) eth_example: ~~~~~~~~~~~
+I (394) eth_example: Ethernet Started
+I (3934) eth_example: Ethernet Link Up
+I (3934) eth_example: Ethernet HW Addr 30:ae:a4:c6:87:5b
+I (5864) tcpip_adapter: eth ip: 192.168.2.151, mask: 255.255.255.0, gw: 192.168.2.2
+I (5864) eth_example: Ethernet Got IP Address
+I (5864) eth_example: ~~~~~~~~~~~
+I (5864) eth_example: ETHIP:192.168.2.151
+I (5874) eth_example: ETHMASK:255.255.255.0
+I (5874) eth_example: ETHGW:192.168.2.2
+I (5884) eth_example: ~~~~~~~~~~~
 ```
 
+Now you can ping your ESP32 in the terminal by entering `ping 192.168.2.151` (it depends on the actual IP address you get).
+
 ## Troubleshooting
 
 * RMII Clock

+ 87 - 22
examples/ethernet/basic/main/Kconfig.projbuild

@@ -1,34 +1,99 @@
 menu "Example Configuration"
-
-    choice EXAMPLE_ETH_PHY_MODEL
-        prompt "Ethernet PHY Device"
-        default EXAMPLE_ETH_PHY_IP101
+    choice EXAMPLE_USE_ETHERNET
+        prompt "Ethernet Type"
+        default EXAMPLE_USE_INTERNAL_ETHERNET if IDF_TARGET_ESP32
+        default EXAMPLE_USE_SPI_ETHERNET if !IDF_TARGET_ESP32
         help
-            Select the Ethernet PHY device to use in the example.
+            Select which kind of Ethernet will be used in the example.
 
-        config EXAMPLE_ETH_PHY_IP101
-            bool "IP101"
+        config EXAMPLE_USE_INTERNAL_ETHERNET
+            depends on IDF_TARGET_ESP32
+            select ETH_USE_ESP32_EMAC
+            bool "Internal EMAC"
             help
-                IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
-                Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+                Select internal Ethernet MAC controller.
 
-        config EXAMPLE_ETH_PHY_RTL8201
-            bool "RTL8201/SR8201"
+        config EXAMPLE_USE_SPI_ETHERNET
+            bool "SPI Ethernet Module"
+            select ETH_USE_SPI_ETHERNET
             help
-                RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
-                Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+                Select external SPI-Ethernet module.
+    endchoice
 
-        config EXAMPLE_ETH_PHY_LAN8720
-            bool "LAN8720"
+    if EXAMPLE_USE_INTERNAL_ETHERNET
+        choice EXAMPLE_ETH_PHY_MODEL
+            prompt "Ethernet PHY Device"
+            default EXAMPLE_ETH_PHY_IP101
             help
-                LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
-                Goto https://www.microchip.com/LAN8720A for more information about it.
+                Select the Ethernet PHY device to use in the example.
+
+            config EXAMPLE_ETH_PHY_IP101
+                bool "IP101"
+                help
+                    IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
+                    Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+
+            config EXAMPLE_ETH_PHY_RTL8201
+                bool "RTL8201/SR8201"
+                help
+                    RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
+                    Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
 
-        config EXAMPLE_ETH_PHY_DP83848
-            bool "DP83848"
+            config EXAMPLE_ETH_PHY_LAN8720
+                bool "LAN8720"
+                help
+                    LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
+                    Goto https://www.microchip.com/LAN8720A for more information about it.
+
+            config EXAMPLE_ETH_PHY_DP83848
+                bool "DP83848"
+                help
+                    DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
+                    Goto http://www.ti.com/product/DP83848J for more information about it.
+        endchoice
+    endif
+
+    if EXAMPLE_USE_SPI_ETHERNET
+        config EXAMPLE_ETH_SPI_HOST
+            int "SPI Host Number"
+            range 0 2
+            default 1
             help
-                DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
-                Goto http://www.ti.com/product/DP83848J for more information about it.
-    endchoice
+                Set the SPI host used to communicate with DM9051.
+
+        config EXAMPLE_ETH_SCLK_GPIO
+            int "SPI SCLK GPIO number"
+            range 0 33
+            default 19
+            help
+                Set the GPIO number used by SPI SCLK.
+
+        config EXAMPLE_ETH_MOSI_GPIO
+            int "SPI MOSI GPIO number"
+            range 0 33
+            default 23
+            help
+                Set the GPIO number used by SPI MOSI.
+
+        config EXAMPLE_ETH_MISO_GPIO
+            int "SPI MISO GPIO number"
+            range 0 33
+            default 25
+            help
+                Set the GPIO number used by SPI MISO.
+
+        config EXAMPLE_ETH_CS_GPIO
+            int "SPI CS GPIO number"
+            range 0 33
+            default 22
+            help
+                Set the GPIO number used by SPI CS.
 
+        config EXAMPLE_ETH_SPI_CLOCK_MHZ
+            int "SPI clock speed (MHz)"
+            range 20 80
+            default 20
+            help
+                Set the clock speed (MHz) of SPI interface.
+    endif
 endmenu

+ 36 - 3
examples/ethernet/basic/main/ethernet_example_main.c

@@ -17,15 +17,21 @@
 #include "sdkconfig.h"
 
 static const char *TAG = "eth_example";
-static esp_eth_handle_t s_eth_handle = NULL;
 
 /** Event handler for Ethernet events */
 static void eth_event_handler(void *arg, esp_event_base_t event_base,
                               int32_t event_id, void *event_data)
 {
+    uint8_t mac_addr[6] = {0};
+    /* we can get the ethernet driver handle from event data */
+    esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data;
+
     switch (event_id) {
     case ETHERNET_EVENT_CONNECTED:
+        esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr);
         ESP_LOGI(TAG, "Ethernet Link Up");
+        ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x",
+                 mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
         break;
     case ETHERNET_EVENT_DISCONNECTED:
         ESP_LOGI(TAG, "Ethernet Link Down");
@@ -66,8 +72,9 @@ void app_main()
     ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
 
     eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
-    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
     eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
+    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
 #if CONFIG_EXAMPLE_ETH_PHY_IP101
     esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
@@ -76,7 +83,33 @@ void app_main()
     esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_DP83848
     esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
+#endif
+#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
+    gpio_install_isr_service(0);
+    spi_device_handle_t spi_handle = NULL;
+    spi_bus_config_t buscfg = {
+        .miso_io_num = CONFIG_EXAMPLE_ETH_MISO_GPIO,
+        .mosi_io_num = CONFIG_EXAMPLE_ETH_MOSI_GPIO,
+        .sclk_io_num = CONFIG_EXAMPLE_ETH_SCLK_GPIO,
+        .quadwp_io_num = -1,
+        .quadhd_io_num = -1,
+    };
+    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
+    spi_device_interface_config_t devcfg = {
+        .command_bits = 1,
+        .address_bits = 7,
+        .mode = 0,
+        .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
+        .spics_io_num = CONFIG_EXAMPLE_ETH_CS_GPIO,
+        .queue_size = 20
+    };
+    ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
+    /* dm9051 ethernet driver is based on spi driver, so need to specify the spi handle */
+    mac_config.spi_hdl = spi_handle;
+    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&mac_config);
+    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
 #endif
     esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
-    ESP_ERROR_CHECK(esp_eth_driver_install(&config, &s_eth_handle));
+    esp_eth_handle_t eth_handle = NULL;
+    ESP_ERROR_CHECK(esp_eth_driver_install(&config, &eth_handle));
 }

+ 25 - 15
examples/ethernet/eth2ap/README.md

@@ -12,38 +12,48 @@ The similarities on MAC layer between Ethernet and Wi-Fi make it easy to forward
 
 ### Hardware Required
 
-To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
+To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports up to four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
+
+`esp_eth` component not only supports ESP32 internal Ethernet MAC controller, but also can drive third-party Ethernet module which integrates MAC and PHY and provides SPI interface. This example also take the **DM9051** as an example, illustrating how to install the Ethernet driver with only a little different configuration.
 
 ### Project configuration in menuconfig
 
 Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you' are using CMake based build system.
 
 1. In the `Example Configuration` menu:
+    * Set the SSID and password for Wi-Fi ap interface under `Wi-Fi SSID` and `Wi-Fi Password`.
+    * Set the maximum connection number under `Maximum STA connections`.
+    * Choose the kind of Ethernet this example will run on under `Ethernet Type`.
+    * If `Internal EMAC` is selected:
+        * Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
 
-* Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
-* Set the SSID and password for Wi-Fi ap interface under `Wi-Fi SSID` and `Wi-Fi Password`.
-* Set the maximum connection number under `Maximum STA connections`.
+    * If `SPI Ethernet Module` is selected:
+        * Set SPI specific configuration, including GPIO and clock speed.
 
 2. In the `Component config > Ethernet` menu:
+    * If `Internal EMAC` is selected:
+        * Enable `Use ESP32 internal EMAC controller`, and then go into this menu.
+        * In the `PHY interface`, it's highly recommended that you choose `Reduced Media Independent Interface (RMII)` which will cost fewer pins.
+        * In the `RMII clock mode`, you can choose the source of RMII clock (50MHz): `Input RMII clock from external` or `Output RMII clock from internal`.
+        * Once `Output RMII clock from internal` is enabled, you also have to set the number of the GPIO used for outputting the RMII clock under `RMII clock GPIO number`. In this case, you can set the GPIO number to 16 or 17.
+        * Once `Output RMII clock from GPIO0 (Experimental!)` is enabled, then you have no choice but GPIO0 to output the RMII clock.
+        * Set SMI MDC/MDIO GPIO number according to board schematic, by default these two GPIOs are set as below:
 
-* Enable `Use ESP32 internal EMAC controller`, and then go into this menu.
-* In the `PHY interface`, it's highly recommended that you choose `Reduced Media Independent Interface (RMII)` which will cost fewer pins.
-* In the `RMII clock mode`, you can choose the source of RMII clock (50MHz): `Input RMII clock from external` or `Output RMII clock from internal`.
-* Once `Output RMII clock from internal` is enabled, you also have to set the number of the GPIO used for outputting the RMII clock under `RMII clock GPIO number`. In this case, you can set the GPIO number to 16 or 17.
-* Once `Output RMII clock from GPIO0 (Experimental!)` is enabled, then you have no choice but GPIO0 to output the RMII clock.
-* Set SMI MDC/MDIO GPIO number according to board schematic, by default these two GPIOs are set as below:
+            | Default Example GPIO | RMII Signal | Notes         |
+            | -------------------- | ----------- | ------------- |
+            | GPIO23               | MDC         | Output to PHY |
+            | GPIO18               | MDIO        | Bidirectional |
 
-  | Default Example GPIO | RMII Signal | Notes         |
-  | -------------------- | ----------- | ------------- |
-  | GPIO23               | MDC         | Output to PHY |
-  | GPIO18               | MDIO        | Bidirectional |
+        * If you have connect a GPIO to the PHY chip's RST pin, then you need to enable `Use Reset Pin of PHY Chip` and set the GPIO number under `PHY RST GPIO number`.
 
-* If you have connect a GPIO to the PHY chip's RST pin, then you need to enable `Use Reset Pin of PHY Chip` and set the GPIO number under `PHY RST GPIO number`.
+    * If `SPI Ethernet Module` is selected:
+        * Set the GPIO number used by interrupt pin under `DM9051 Interrupt GPIO number`.
 
 ### Extra configuration in the code (Optional)
 
 * By default Ethernet driver will assume the PHY address to `1`, but you can alway reconfigure this value after `eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();`. The actual PHY address should depend on the hardware you use, so make sure to consult the schematic and datasheet.peripheral (e.g. I²S), you'd better choose the external clock.
 
+**Note:** DM9051 has a fixed PHY address `1`, which cannot be modified.
 
 ### Build and Flash
 

+ 88 - 22
examples/ethernet/eth2ap/main/Kconfig.projbuild

@@ -1,35 +1,101 @@
 menu "Example Configuration"
-
-    choice EXAMPLE_ETH_PHY_MODEL
-        prompt "Ethernet PHY Device"
-        default EXAMPLE_ETH_PHY_IP101
+    choice EXAMPLE_USE_ETHERNET
+        prompt "Ethernet Type"
+        default EXAMPLE_USE_INTERNAL_ETHERNET if IDF_TARGET_ESP32
+        default EXAMPLE_USE_SPI_ETHERNET if !IDF_TARGET_ESP32
         help
-            Select the Ethernet PHY device to use in the example.
+            Select which kind of Ethernet will be used in the example.
 
-        config EXAMPLE_ETH_PHY_IP101
-            bool "IP101"
+        config EXAMPLE_USE_INTERNAL_ETHERNET
+            depends on IDF_TARGET_ESP32
+            select ETH_USE_ESP32_EMAC
+            bool "Internal EMAC"
             help
-                IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
-                Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+                Select internal Ethernet MAC controller.
 
-        config EXAMPLE_ETH_PHY_RTL8201
-            bool "RTL8201/SR8201"
+        config EXAMPLE_USE_SPI_ETHERNET
+            bool "SPI Ethernet Module"
+            select ETH_USE_SPI_ETHERNET
             help
-                RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
-                Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+                Select external SPI-Ethernet module.
+    endchoice
 
-        config EXAMPLE_ETH_PHY_LAN8720
-            bool "LAN8720"
+    if EXAMPLE_USE_INTERNAL_ETHERNET
+        choice EXAMPLE_ETH_PHY_MODEL
+            prompt "Ethernet PHY Device"
+            default EXAMPLE_ETH_PHY_IP101
             help
-                LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
-                Goto https://www.microchip.com/LAN8720A for more information about it.
+                Select the Ethernet PHY device to use in the example.
+
+            config EXAMPLE_ETH_PHY_IP101
+                bool "IP101"
+                help
+                    IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
+                    Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+
+            config EXAMPLE_ETH_PHY_RTL8201
+                bool "RTL8201/SR8201"
+                help
+                    RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
+                    Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+
+            config EXAMPLE_ETH_PHY_LAN8720
+                bool "LAN8720"
+                help
+                    LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
+                    Goto https://www.microchip.com/LAN8720A for more information about it.
 
-        config EXAMPLE_ETH_PHY_DP83848
-            bool "DP83848"
+            config EXAMPLE_ETH_PHY_DP83848
+                bool "DP83848"
+                help
+                    DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
+                    Goto http://www.ti.com/product/DP83848J for more information about it.
+        endchoice
+    endif
+
+    if EXAMPLE_USE_SPI_ETHERNET
+        config EXAMPLE_ETH_SPI_HOST
+            int "SPI Host Number"
+            range 0 2
+            default 1
             help
-                DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
-                Goto http://www.ti.com/product/DP83848J for more information about it.
-    endchoice
+                Set the SPI host used to communicate with DM9051.
+
+        config EXAMPLE_ETH_SCLK_GPIO
+            int "SPI SCLK GPIO number"
+            range 0 33
+            default 19
+            help
+                Set the GPIO number used by SPI SCLK.
+
+        config EXAMPLE_ETH_MOSI_GPIO
+            int "SPI MOSI GPIO number"
+            range 0 33
+            default 23
+            help
+                Set the GPIO number used by SPI MOSI.
+
+        config EXAMPLE_ETH_MISO_GPIO
+            int "SPI MISO GPIO number"
+            range 0 33
+            default 25
+            help
+                Set the GPIO number used by SPI MISO.
+
+        config EXAMPLE_ETH_CS_GPIO
+            int "SPI CS GPIO number"
+            range 0 33
+            default 22
+            help
+                Set the GPIO number used by SPI CS.
+
+        config EXAMPLE_ETH_SPI_CLOCK_MHZ
+            int "SPI clock speed (MHz)"
+            range 20 80
+            default 20
+            help
+                Set the clock speed (MHz) of SPI interface.
+    endif
 
     config EXAMPLE_WIFI_SSID
         string "Wi-Fi SSID"

+ 27 - 1
examples/ethernet/eth2ap/main/ethernet_example_main.c

@@ -145,8 +145,9 @@ static void initialize_ethernet(void)
 {
     ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
     eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
-    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
     eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
+    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
 #if CONFIG_EXAMPLE_ETH_PHY_IP101
     esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
@@ -155,6 +156,31 @@ static void initialize_ethernet(void)
     esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_DP83848
     esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
+#endif
+#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
+    gpio_install_isr_service(0);
+    spi_device_handle_t spi_handle = NULL;
+    spi_bus_config_t buscfg = {
+        .miso_io_num = CONFIG_EXAMPLE_ETH_MISO_GPIO,
+        .mosi_io_num = CONFIG_EXAMPLE_ETH_MOSI_GPIO,
+        .sclk_io_num = CONFIG_EXAMPLE_ETH_SCLK_GPIO,
+        .quadwp_io_num = -1,
+        .quadhd_io_num = -1,
+    };
+    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
+    spi_device_interface_config_t devcfg = {
+        .command_bits = 1,
+        .address_bits = 7,
+        .mode = 0,
+        .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
+        .spics_io_num = CONFIG_EXAMPLE_ETH_CS_GPIO,
+        .queue_size = 20
+    };
+    ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
+    /* dm9051 ethernet driver is based on spi driver, so need to specify the spi handle */
+    mac_config.spi_hdl = spi_handle;
+    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&mac_config);
+    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
 #endif
     esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
     config.stack_input = pkt_eth2wifi;

+ 65 - 48
examples/ethernet/iperf/README.md

@@ -10,9 +10,13 @@ The cli environment in the example is based on the [console component](https://d
 
 ## How to use example
 
-To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
+### Hardware Required
 
-### Prepare work
+To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports up to four Ethernet PHY: `LAN8720`, `IP101`, `DP83848` and `RTL8201`, additional PHY drivers should be implemented by users themselves.
+
+`esp_eth` component not only supports ESP32 internal Ethernet MAC controller, but also can drive third-party Ethernet module which integrates MAC and PHY and provides SPI interface. This example also take the **DM9051** as an example, illustrating how to install the Ethernet driver with only a little different configuration.
+
+### Other Preparation
 
 1. Install iperf tool on PC
    * Debian/Ubuntu: `sudo apt-get install iperf` 
@@ -24,30 +28,39 @@ To run this example, it's recommended that you have an official ESP32 Ethernet d
 Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you' are using CMake based build system.
 
 1. In the `Example Configuration` menu:
+    * Enable storing history commands in flash under `Store command history in flash`.
+    * Choose the kind of Ethernet this example will run on under `Ethernet Type`.
+    * If `Internal EMAC` is selected:
+        * Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
 
-* Enable storing history commands in flash under `Store command history in flash`.
-* Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
+    * If `SPI Ethernet Module` is selected:
+        * Set SPI specific configuration, including GPIO and clock speed.
 
 2. In the `Component config > Ethernet` menu:
+    * If `Internal EMAC` is selected:
+        * Enable `Use ESP32 internal EMAC controller`, and then go into this menu.
+        * In the `PHY interface`, it's highly recommended that you choose `Reduced Media Independent Interface (RMII)` which will cost fewer pins.
+        * In the `RMII clock mode`, you can choose the source of RMII clock (50MHz): `Input RMII clock from external` or `Output RMII clock from internal`.
+        * Once `Output RMII clock from internal` is enabled, you also have to set the number of the GPIO used for outputting the RMII clock under `RMII clock GPIO number`. In this case, you can set the GPIO number to 16 or 17.
+        * Once `Output RMII clock from GPIO0 (Experimental!)` is enabled, then you have no choice but GPIO0 to output the RMII clock.
+        * Set SMI MDC/MDIO GPIO number according to board schematic, by default these two GPIOs are set as below:
 
-* Enable `Use ESP32 internal EMAC controller`, and then go into this menu.
-* In the `PHY interface`, it's highly recommended that you choose `Reduced Media Independent Interface (RMII)` which will cost fewer pins.
-* In the `RMII clock mode`, you can choose the source of RMII clock (50MHz): `Input RMII clock from external` or `Output RMII clock from internal`.
-* Once `Output RMII clock from internal` is enabled, you also have to set the number of the GPIO used for outputting the RMII clock under `RMII clock GPIO number`. In this case, you can set the GPIO number to 16 or 17.
-* Once `Output RMII clock from GPIO0 (Experimental!)` is enabled, then you have no choice but GPIO0 to output the RMII clock.
-* Set SMI MDC/MDIO GPIO number according to board schematic, by default these two GPIOs are set as below:
+            | Default Example GPIO | RMII Signal | Notes         |
+            | -------------------- | ----------- | ------------- |
+            | GPIO23               | MDC         | Output to PHY |
+            | GPIO18               | MDIO        | Bidirectional |
 
-  | Default Example GPIO | RMII Signal | Notes         |
-  | -------------------- | ----------- | ------------- |
-  | GPIO23               | MDC         | Output to PHY |
-  | GPIO18               | MDIO        | Bidirectional |
+        * If you have connect a GPIO to the PHY chip's RST pin, then you need to enable `Use Reset Pin of PHY Chip` and set the GPIO number under `PHY RST GPIO number`.
 
-* If you have connect a GPIO to the PHY chip's RST pin, then you need to enable `Use Reset Pin of PHY Chip` and set the GPIO number under `PHY RST GPIO number`.
+    * If `SPI Ethernet Module` is selected:
+        * Set the GPIO number used by interrupt pin under `DM9051 Interrupt GPIO number`.
 
 ### Extra configuration in the code (Optional)
 
 * By default Ethernet driver will assume the PHY address to `1`, but you can alway reconfigure this value after `eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();`. The actual PHY address should depend on the hardware you use, so make sure to consult the schematic and datasheet.
 
+**Note:** DM9051 has a fixed PHY address `1`, which cannot be modified.
+
 ### Build and Flash
 
 Enter `make -j4 flash monitor` if you are using GNU Make based build system or enter `idf.py build flash monitor` if you' are using CMake based build system.
@@ -73,15 +86,15 @@ UDP buffer size:  208 KByte (default)
 ------------------------------------------------------------
 [  3] local 192.168.2.160 port 5001 connected with 192.168.2.156 port 49154
 [ ID] Interval       Transfer     Bandwidth        Jitter   Lost/Total Datagrams
-[  3]  0.0- 3.0 sec  24.8 MBytes  69.5 Mbits/sec   0.172 ms    1/17721 (0.0056%)
-[  3]  3.0- 6.0 sec  24.8 MBytes  69.5 Mbits/sec   0.169 ms    0/17719 (0%)
-[  3]  6.0- 9.0 sec  24.8 MBytes  69.5 Mbits/sec   0.170 ms    0/17719 (0%)
-[  3]  9.0-12.0 sec  24.8 MBytes  69.5 Mbits/sec   0.170 ms    0/17718 (0%)
-[  3] 12.0-15.0 sec  24.8 MBytes  69.5 Mbits/sec   0.169 ms    0/17717 (0%)
-[  3] 15.0-18.0 sec  24.8 MBytes  69.5 Mbits/sec   0.170 ms    0/17720 (0%)
-[  3] 18.0-21.0 sec  24.8 MBytes  69.5 Mbits/sec   0.170 ms    0/17721 (0%)
-[  3] 21.0-24.0 sec  24.8 MBytes  69.5 Mbits/sec   0.169 ms    0/17720 (0%)
-[  3] 24.0-27.0 sec  24.8 MBytes  69.5 Mbits/sec   0.169 ms    0/17723 (0%)
+[  3]  0.0- 3.0 sec  26.1 MBytes  72.8 Mbits/sec   0.198 ms    1/18585 (0.0054%)
+[  3]  3.0- 6.0 sec  26.3 MBytes  73.7 Mbits/sec   0.192 ms    0/18792 (0%)
+[  3]  6.0- 9.0 sec  26.3 MBytes  73.5 Mbits/sec   0.189 ms    0/18741 (0%)
+[  3]  9.0-12.0 sec  26.2 MBytes  73.3 Mbits/sec   0.191 ms   43/18739 (0.23%)
+[  3] 12.0-15.0 sec  26.3 MBytes  73.5 Mbits/sec   0.194 ms    0/18739 (0%)
+[  3] 15.0-18.0 sec  26.3 MBytes  73.5 Mbits/sec   0.191 ms    0/18741 (0%)
+[  3] 18.0-21.0 sec  26.3 MBytes  73.5 Mbits/sec   0.187 ms    0/18752 (0%)
+[  3] 21.0-24.0 sec  26.3 MBytes  73.4 Mbits/sec   0.192 ms    0/18737 (0%)
+[  3] 24.0-27.0 sec  26.3 MBytes  73.5 Mbits/sec   0.188 ms    0/18739 (0%)
 ```
 
 #### ESP32 output
@@ -89,16 +102,17 @@ UDP buffer size:  208 KByte (default)
 ```bash
 mode=udp-client sip=192.168.2.156:5001, dip=192.168.2.160:5001, interval=3, time=30
     Interval           Bandwidth
-   0-   3 sec       69.34 Mbits/sec
-   3-   6 sec       69.55 Mbits/sec
-   6-   9 sec       69.55 Mbits/sec
-   9-  12 sec       69.55 Mbits/sec
-  12-  15 sec       69.55 Mbits/sec
-  15-  18 sec       69.56 Mbits/sec
-  18-  21 sec       69.56 Mbits/sec
-  21-  24 sec       69.56 Mbits/sec
-  24-  27 sec       69.56 Mbits/sec
-  27-  30 sec       69.56 Mbits/sec
+   0-   3 sec       72.92 Mbits/sec
+   3-   6 sec       73.76 Mbits/sec
+   6-   9 sec       73.56 Mbits/sec
+   9-  12 sec       73.56 Mbits/sec
+  12-  15 sec       73.56 Mbits/sec
+  15-  18 sec       73.56 Mbits/sec
+  18-  21 sec       73.61 Mbits/sec
+  21-  24 sec       73.55 Mbits/sec
+  24-  27 sec       73.56 Mbits/sec
+  27-  30 sec       73.56 Mbits/sec
+   0-  30 sec       73.52 Mbits/sec
 ```
 
 ### Test downlink bandwidth
@@ -119,11 +133,13 @@ UDP buffer size:  208 KByte (default)
 [  3]  3.0- 6.0 sec  28.6 MBytes  80.0 Mbits/sec
 [  3]  6.0- 9.0 sec  28.6 MBytes  80.0 Mbits/sec
 [  3]  9.0-12.0 sec  28.6 MBytes  80.0 Mbits/sec
-[  3] 12.0-15.0 sec  28.6 MBytes  79.9 Mbits/sec
-[  3] 15.0-18.0 sec  28.6 MBytes  80.0 Mbits/sec
-[  3] 18.0-21.0 sec  28.6 MBytes  80.0 Mbits/sec
-[  3] 21.0-24.0 sec  28.6 MBytes  80.0 Mbits/sec
+[  3] 12.0-15.0 sec  28.4 MBytes  79.5 Mbits/sec
+[  3] 15.0-18.0 sec  28.6 MBytes  79.9 Mbits/sec
+[  3] 18.0-21.0 sec  28.6 MBytes  79.9 Mbits/sec
+[  3] 21.0-24.0 sec  28.6 MBytes  79.9 Mbits/sec
 [  3] 24.0-27.0 sec  28.6 MBytes  80.0 Mbits/sec
+[  3] 27.0-30.0 sec  28.5 MBytes  79.7 Mbits/sec
+[  3]  0.0-30.0 sec   286 MBytes  79.9 Mbits/sec
 ```
 
 #### ESP32 output
@@ -131,16 +147,17 @@ UDP buffer size:  208 KByte (default)
 mode=udp-server sip=192.168.2.156:5001, dip=0.0.0.0:5001, interval=3, time=30
     Interval           Bandwidth
 I (2534456) iperf: want recv=16384
-   0-  30 sec       72.67 Mbits/sec
-   3-   6 sec       74.18 Mbits/sec
-   6-   9 sec       73.14 Mbits/sec
-   9-  12 sec       73.65 Mbits/sec
-  12-  15 sec       72.87 Mbits/sec
-  15-  18 sec       73.29 Mbits/sec
-  18-  21 sec       74.35 Mbits/sec
-  21-  24 sec       72.28 Mbits/sec
-  24-  27 sec       73.39 Mbits/sec
-  27-  30 sec       73.49 Mbits/sec
+   0-   3 sec       79.36 Mbits/sec
+   3-   6 sec       79.56 Mbits/sec
+   6-   9 sec       79.51 Mbits/sec
+   9-  12 sec       79.24 Mbits/sec
+  12-  15 sec       77.33 Mbits/sec
+  15-  18 sec       79.01 Mbits/sec
+  18-  21 sec       78.58 Mbits/sec
+  21-  24 sec       78.24 Mbits/sec
+  24-  27 sec       79.56 Mbits/sec
+  27-  30 sec       77.20 Mbits/sec
+   0-  30 sec       78.76 Mbits/sec
 ```
 
 ## Suggestions of getting higher bandwidth

+ 87 - 22
examples/ethernet/iperf/main/Kconfig.projbuild

@@ -1,5 +1,4 @@
 menu "Example Configuration"
-
     config EXAMPLE_STORE_HISTORY
         bool "Store command history in flash"
         default y
@@ -8,35 +7,101 @@ menu "Example Configuration"
             command history. If this option is enabled, initalizes a FAT filesystem
             and uses it to store command history.
 
-    choice EXAMPLE_ETH_PHY_MODEL
-        prompt "Ethernet PHY Device"
-        default EXAMPLE_ETH_PHY_IP101
+    choice EXAMPLE_USE_ETHERNET
+        prompt "Ethernet Type"
+        default EXAMPLE_USE_INTERNAL_ETHERNET if IDF_TARGET_ESP32
+        default EXAMPLE_USE_SPI_ETHERNET if !IDF_TARGET_ESP32
         help
-            Select the Ethernet PHY device to use in the example.
+            Select which kind of Ethernet will be used in the example.
 
-        config EXAMPLE_ETH_PHY_IP101
-            bool "IP101"
+        config EXAMPLE_USE_INTERNAL_ETHERNET
+            depends on IDF_TARGET_ESP32
+            select ETH_USE_ESP32_EMAC
+            bool "Internal EMAC"
             help
-                IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
-                Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+                Select internal Ethernet MAC controller.
 
-        config EXAMPLE_ETH_PHY_RTL8201
-            bool "RTL8201/SR8201"
+        config EXAMPLE_USE_SPI_ETHERNET
+            bool "SPI Ethernet Module"
+            select ETH_USE_SPI_ETHERNET
             help
-                RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
-                Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
+                Select external SPI-Ethernet module.
+    endchoice
 
-        config EXAMPLE_ETH_PHY_LAN8720
-            bool "LAN8720"
+    if EXAMPLE_USE_INTERNAL_ETHERNET
+        choice EXAMPLE_ETH_PHY_MODEL
+            prompt "Ethernet PHY Device"
+            default EXAMPLE_ETH_PHY_IP101
             help
-                LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
-                Goto https://www.microchip.com/LAN8720A for more information about it.
+                Select the Ethernet PHY device to use in the example.
+
+            config EXAMPLE_ETH_PHY_IP101
+                bool "IP101"
+                help
+                    IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
+                    Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
+
+            config EXAMPLE_ETH_PHY_RTL8201
+                bool "RTL8201/SR8201"
+                help
+                    RTL8201F/SR8201F is a single port 10/100Mb Ethernet Transceiver with auto MDIX.
+                    Goto http://www.corechip-sz.com/productsview.asp?id=22 for more information about it.
 
-        config EXAMPLE_ETH_PHY_DP83848
-            bool "DP83848"
+            config EXAMPLE_ETH_PHY_LAN8720
+                bool "LAN8720"
+                help
+                    LAN8720A is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
+                    Goto https://www.microchip.com/LAN8720A for more information about it.
+
+            config EXAMPLE_ETH_PHY_DP83848
+                bool "DP83848"
+                help
+                    DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
+                    Goto http://www.ti.com/product/DP83848J for more information about it.
+        endchoice
+    endif
+
+    if EXAMPLE_USE_SPI_ETHERNET
+        config EXAMPLE_ETH_SPI_HOST
+            int "SPI Host Number"
+            range 0 2
+            default 1
             help
-                DP83848 is a single port 10/100Mb/s Ethernet Physical Layer Transceiver.
-                Goto http://www.ti.com/product/DP83848J for more information about it.
-    endchoice
+                Set the SPI host used to communicate with DM9051.
+
+        config EXAMPLE_ETH_SCLK_GPIO
+            int "SPI SCLK GPIO number"
+            range 0 33
+            default 19
+            help
+                Set the GPIO number used by SPI SCLK.
+
+        config EXAMPLE_ETH_MOSI_GPIO
+            int "SPI MOSI GPIO number"
+            range 0 33
+            default 23
+            help
+                Set the GPIO number used by SPI MOSI.
+
+        config EXAMPLE_ETH_MISO_GPIO
+            int "SPI MISO GPIO number"
+            range 0 33
+            default 25
+            help
+                Set the GPIO number used by SPI MISO.
+
+        config EXAMPLE_ETH_CS_GPIO
+            int "SPI CS GPIO number"
+            range 0 33
+            default 22
+            help
+                Set the GPIO number used by SPI CS.
 
+        config EXAMPLE_ETH_SPI_CLOCK_MHZ
+            int "SPI clock speed (MHz)"
+            range 20 80
+            default 20
+            help
+                Set the clock speed (MHz) of SPI interface.
+    endif
 endmenu

+ 27 - 1
examples/ethernet/iperf/main/cmd_ethernet.c

@@ -183,8 +183,9 @@ void register_ethernet()
     ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &event_handler, NULL));
 
     eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();
-    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
     eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();
+#if CONFIG_EXAMPLE_USE_INTERNAL_ETHERNET
+    esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config);
 #if CONFIG_EXAMPLE_ETH_PHY_IP101
     esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_RTL8201
@@ -193,6 +194,31 @@ void register_ethernet()
     esp_eth_phy_t *phy = esp_eth_phy_new_lan8720(&phy_config);
 #elif CONFIG_EXAMPLE_ETH_PHY_DP83848
     esp_eth_phy_t *phy = esp_eth_phy_new_dp83848(&phy_config);
+#endif
+#elif CONFIG_EXAMPLE_USE_SPI_ETHERNET
+    gpio_install_isr_service(0);
+    spi_device_handle_t spi_handle = NULL;
+    spi_bus_config_t buscfg = {
+        .miso_io_num = CONFIG_EXAMPLE_ETH_MISO_GPIO,
+        .mosi_io_num = CONFIG_EXAMPLE_ETH_MOSI_GPIO,
+        .sclk_io_num = CONFIG_EXAMPLE_ETH_SCLK_GPIO,
+        .quadwp_io_num = -1,
+        .quadhd_io_num = -1,
+    };
+    ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
+    spi_device_interface_config_t devcfg = {
+        .command_bits = 1,
+        .address_bits = 7,
+        .mode = 0,
+        .clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,
+        .spics_io_num = CONFIG_EXAMPLE_ETH_CS_GPIO,
+        .queue_size = 20
+    };
+    ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ETH_SPI_HOST, &devcfg, &spi_handle));
+    /* dm9051 ethernet driver is based on spi driver, so need to specify the spi handle */
+    mac_config.spi_hdl = spi_handle;
+    esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&mac_config);
+    esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);
 #endif
     esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy);
     ESP_ERROR_CHECK(esp_eth_driver_install(&config, &eth_handle));

+ 1 - 1
examples/ethernet/iperf/main/ethernet_example_main.c

@@ -85,7 +85,7 @@ static void initialize_console()
 
     /* Initialize the console */
     esp_console_config_t console_config = {
-        .max_cmdline_args = 8,
+        .max_cmdline_args = 16,
         .max_cmdline_length = 256,
 #if CONFIG_LOG_COLORS
         .hint_color = atoi(LOG_COLOR_CYAN)