From 9e59be1aabc8b4d9cef700d4051ebc74ab02670b Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 9 Jan 2020 19:32:51 +0800 Subject: [PATCH] ethernet: add enc28j60 example --- components/esp_eth/CMakeLists.txt | 10 +- components/esp_eth/Kconfig | 7 - components/esp_eth/include/esp_eth_mac.h | 21 +- components/esp_eth/include/esp_eth_phy.h | 13 - components/esp_eth/src/esp_eth_mac_ENC28J60.c | 1256 ----------------- components/esp_eth/src/esp_eth_mac_dm9051.c | 6 +- components/esp_eth/src/esp_eth_mac_esp32.c | 4 + components/esp_eth/src/esp_eth_phy_ENC28J60.c | 250 ---- components/esp_eth/test/test_dm9051.c | 5 +- .../ethernet/basic/main/Kconfig.projbuild | 6 - .../basic/main/ethernet_example_main.c | 14 - examples/ethernet/enc28j60/CMakeLists.txt | 6 + examples/ethernet/enc28j60/Makefile | 9 + examples/ethernet/enc28j60/README.md | 85 ++ .../ethernet/enc28j60/main/CMakeLists.txt | 6 + .../ethernet/enc28j60/main/Kconfig.projbuild | 50 + examples/ethernet/enc28j60/main/component.mk | 4 + examples/ethernet/enc28j60/main/enc28j60.h | 284 ++++ .../enc28j60/main/enc28j60_example_main.c | 130 ++ .../enc28j60/main/esp_eth_mac_enc28j60.c | 936 ++++++++++++ .../enc28j60/main/esp_eth_phy_enc28j60.c | 304 ++++ examples/ethernet/enc28j60/sdkconfig.defaults | 1 + 22 files changed, 1842 insertions(+), 1565 deletions(-) delete mode 100644 components/esp_eth/src/esp_eth_mac_ENC28J60.c delete mode 100644 components/esp_eth/src/esp_eth_phy_ENC28J60.c create mode 100644 examples/ethernet/enc28j60/CMakeLists.txt create mode 100644 examples/ethernet/enc28j60/Makefile create mode 100644 examples/ethernet/enc28j60/README.md create mode 100644 examples/ethernet/enc28j60/main/CMakeLists.txt create mode 100644 examples/ethernet/enc28j60/main/Kconfig.projbuild create mode 100644 examples/ethernet/enc28j60/main/component.mk create mode 100644 examples/ethernet/enc28j60/main/enc28j60.h create mode 100644 examples/ethernet/enc28j60/main/enc28j60_example_main.c create mode 100644 examples/ethernet/enc28j60/main/esp_eth_mac_enc28j60.c create mode 100644 examples/ethernet/enc28j60/main/esp_eth_phy_enc28j60.c create mode 100644 examples/ethernet/enc28j60/sdkconfig.defaults diff --git a/components/esp_eth/CMakeLists.txt b/components/esp_eth/CMakeLists.txt index 90e69186ad..faced5b780 100644 --- a/components/esp_eth/CMakeLists.txt +++ b/components/esp_eth/CMakeLists.txt @@ -10,10 +10,12 @@ if(CONFIG_ETH_ENABLED) set(include "include") set(priv_requires "driver" "log") # require "driver" for using some GPIO APIs - # esp_netif related - if(esp_netif IN_LIST components_to_build) - list(APPEND srcs "src/esp_eth_netif_glue.c") - list(APPEND priv_requires "esp_netif") + if(NOT CMAKE_BUILD_EARLY_EXPANSION) + # esp_netif related + if(esp_netif IN_LIST components_to_build) + list(APPEND srcs "src/esp_eth_netif_glue.c") + list(APPEND priv_requires "esp_netif") + endif() endif() if(CONFIG_ETH_USE_ESP32_EMAC) diff --git a/components/esp_eth/Kconfig b/components/esp_eth/Kconfig index 5be2c4978e..3ce358749b 100644 --- a/components/esp_eth/Kconfig +++ b/components/esp_eth/Kconfig @@ -118,13 +118,6 @@ menu "Ethernet" DM9051 is a fast Ethernet controller with an SPI interface. It's also integrated with a 10/100M PHY and MAC. Select to enable DM9051 driver. - - config ETH_SPI_ETHERNET_ENC28J60 - bool "Use ENC28J60" - help - ENC28J60 is a Stand-Alone Ethernet Controller with SPI Interface. - It has an integrated MAC and 10Base-T PHY. - Select to enable ENC28J60 driver. endif menuconfig ETH_USE_OPENETH diff --git a/components/esp_eth/include/esp_eth_mac.h b/components/esp_eth/include/esp_eth_mac.h index 65e71a2ef5..608b66c20b 100644 --- a/components/esp_eth/include/esp_eth_mac.h +++ b/components/esp_eth/include/esp_eth_mac.h @@ -281,7 +281,7 @@ 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 +#endif // CONFIG_ETH_USE_ESP32_EMAC #if CONFIG_ETH_SPI_ETHERNET_DM9051 /** @@ -314,17 +314,18 @@ typedef struct { * - NULL: create MAC instance failed because some error occurred */ esp_eth_mac_t *esp_eth_mac_new_dm9051(const eth_dm9051_config_t *dm9051_config, const eth_mac_config_t *mac_config); -#endif -#if CONFIG_ETH_SPI_ETHERNET_ENC28J60 -/** - * @brief ENC28J60 specific configuration - * - */ -typedef struct { - spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */ -} eth_ENC28J60_config_t; +#endif // CONFIG_ETH_SPI_ETHERNET_DM9051 #if CONFIG_ETH_USE_OPENETH +/** +* @brief Create OpenCores 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_openeth(const eth_mac_config_t *config); #endif // CONFIG_ETH_USE_OPENETH diff --git a/components/esp_eth/include/esp_eth_phy.h b/components/esp_eth/include/esp_eth_phy.h index 1cd22dfd76..4aedff1112 100644 --- a/components/esp_eth/include/esp_eth_phy.h +++ b/components/esp_eth/include/esp_eth_phy.h @@ -252,19 +252,6 @@ esp_eth_phy_t *esp_eth_phy_new_dp83848(const eth_phy_config_t *config); */ esp_eth_phy_t *esp_eth_phy_new_dm9051(const eth_phy_config_t *config); #endif - -#if CONFIG_ETH_SPI_ETHERNET_ENC28J60 -/** -* @brief Create a PHY instance of ENC28J60 -* -* @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_ENC28J60(const eth_phy_config_t *config); -#endif #ifdef __cplusplus } #endif diff --git a/components/esp_eth/src/esp_eth_mac_ENC28J60.c b/components/esp_eth/src/esp_eth_mac_ENC28J60.c deleted file mode 100644 index 6da799f22f..0000000000 --- a/components/esp_eth/src/esp_eth_mac_ENC28J60.c +++ /dev/null @@ -1,1256 +0,0 @@ -#include -#include -#include -#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" -#include "esp_err.h" - - -// Register Masks -#define ADDR_MASK 0x1F -#define BANK_MASK 0x60 -#define SPRD_MASK 0x80 - -// All Banks Registers -#define EIE 0x1B -#define EIR 0x1C -#define ESTAT 0x1D -#define ECON2 0x1E -#define ECON1 0x1F - -// Bank 0 Registers -#define ERDPTL 0x00 -#define ERDPTH 0x01 -#define EWRPTL 0x02 -#define EWRPTH 0x03 -#define ETXSTL 0x04 -#define ETXSTH 0x05 -#define ETXNDL 0x06 -#define ETXNDH 0x07 -#define ERXSTL 0x08 -#define ERXSTH 0x09 -#define ERXNDL 0x0A -#define ERXNDH 0x0B -#define ERXRDPTL 0x0C -#define ERXRDPTH 0x0D -#define ERXWRPTL 0x0E -#define ERXWRPTH 0x0F -#define EDMASTL 0x10 -#define EDMASTH 0x11 -#define EDMANDL 0x12 -#define EDMANDH 0x13 -#define EDMADSTL 0x14 -#define EDMADSTH 0x15 -#define EDMACSL 0x16 -#define EDMACSH 0x17 - -// Bank 1 Registers -#define EHT0 0x20 -#define EHT1 0x21 -#define EHT2 0x22 -#define EHT3 0x23 -#define EHT4 0x24 -#define EHT5 0x25 -#define EHT6 0x26 -#define EHT7 0x27 -#define EPMM0 0x28 -#define EPMM1 0x29 -#define EPMM2 0x2A -#define EPMM3 0x2B -#define EPMM4 0x2C -#define EPMM5 0x2D -#define EPMM6 0x2E -#define EPMM7 0x2F -#define EPMCSL 0x30 -#define EPMCSH 0x31 -#define EPMOL 0x34 -#define EPMOH 0x35 -#define EWOLIE 0x36 -#define EWOLIR 0x37 -#define ERXFCON 0x38 -#define EPKTCNT 0x39 - -// Bank 2 Register -#define MACON1 0xC0 -#define MACON2 0xC1 -#define MACON3 0xC2 -#define MACON4 0xC3 -#define MABBIPG 0xC4 -#define MAIPGL 0xC6 -#define MAIPGH 0xC7 -#define MACLCON1 0xC8 -#define MACLCON2 0xC9 -#define MAMXFLL 0xCA -#define MAMXFLH 0xCB -#define MAPHSUP 0xCD -#define MICMD 0xD2 -#define MIREGADR 0xD4 -#define MIWRL 0xD6 -#define MIWRH 0xD7 -#define MIRDL 0xD8 -#define MIRDH 0xD9 - -// Bank 3 Registers -#define MAADR5 0xE0 -#define MAADR6 0xE1 -#define MAADR3 0xE2 -#define MAADR4 0xE3 -#define MAADR1 0xE4 -#define MAADR2 0xE5 -#define EBSTSD 0x66 -#define EBSTCON 0x67 -#define EBSTCSL 0x68 -#define EBSTCSH 0x69 -#define MISTAT 0xEA -#define EREVID 0x72 -#define ECOCON 0x75 -#define EFLOCON 0x77 -#define EPAUSL 0x78 -#define EPAUSH 0x79 - -// PHY Registers -#define PHCON1 0x00 -#define PHSTAT1 0x01 -#define PHHID1 0x02 -#define PHHID2 0x03 -#define PHCON2 0x10 -#define PHSTAT2 0x11 -#define PHIE 0x12 -#define PHIR 0x13 -#define PHLCON 0x14 - -// ERXFCON bit definitions -#define UCEN 0x80 -#define ANDOR 0x40 -#define CRCEN 0x20 -#define PMEN 0x10 -#define MPEN 0x08 -#define HTEN 0x04 -#define MCEN 0x02 -#define BCEN 0x01 - -// EIE bit definitions -#define INTIE 0x80 -#define PKTIE 0x40 -#define DMAIE 0x20 -#define LINKIE 0x10 -#define TXIE 0x08 -#define WOLIE 0x04 -#define TXERIE 0x02 -#define RXERIE 0x01 - -// EIR bit definitions -#define PKTIF 0x40 -#define DMAIF 0x20 -#define LINKIF 0x10 -#define TXIF 0x08 -#define WOLIF 0x04 -#define TXERIF 0x02 -#define RXERIF 0x01 - -// ESTAT bit definitions -#define INT 0x80 -#define LATECOL 0x10 -#define RXBUSY 0x04 -#define TXABRT 0x02 -#define CLKRDY 0x01 - -// ECON2 bit definitions -#define AUTOINC 0x80 -#define PKTDEC 0x40 -#define PWRSV 0x20 -#define VRPS 0x08 - -// ECON1 bit definitions -#define TXRST 0x80 -#define RXRST 0x40 -#define DMAST 0x20 -#define CSUMEN 0x10 -#define TXRTS 0x08 -#define RXEN 0x04 -#define BSEL1 0x02 -#define BSEL0 0x01 - -// MACON1 bit definitions -#define LOOPBK 0x10 -#define TXPAUS 0x08 -#define RXPAUS 0x04 -#define PASSALL 0x02 -#define MARXEN 0x01 - -// MACON2 bit definitions -#define MARST 0x80 -#define RNDRST 0x40 -#define MARXRST 0x08 -#define RFUNRST 0x04 -#define MATXRST 0x02 -#define TFUNRST 0x01 - -// MACON3 bit definitions -#define PADCFG2 0x80 -#define PADCFG1 0x40 -#define PADCFG0 0x20 -#define TXCRCEN 0x10 -#define PHDRLEN 0x08 -#define HFRMLEN 0x04 -#define FRMLNEN 0x02 -#define FULDPX 0x01 - -// MICMD bit definitions -#define MIISCAN 0x02 -#define MIIRD 0x01 - -// MISTAT bit definitions -#define NVALID 0x04 -#define SCAN 0x02 -#define BUSY 0x01 - -// PHCON1 bit definitions -#define PRST 0x8000 -#define PLOOPBK 0x4000 -#define PPWRSV 0x0800 -#define PDPXMD 0x0100 - -// PHSTAT1 bit definitions -#define PFDPX 0x1000 -#define PHDPX 0x0800 -#define LLSTAT 0x0004 -#define JBSTAT 0x0002 - -// PHCON2 bit definitions -#define FRCLINK 0x4000 -#define TXDIS 0x2000 -#define JABBER 0x0400 -#define HDLDIS 0x0100 - -// Packet Control bit Definitions -#define PHUGEEN 0x08 -#define PPADEN 0x04 -#define PCRCEN 0x02 -#define POVERRIDE 0x01 - -// SPI Instruction Set -#define RCR 0x00 // Read Control Register -#define RBM 0x3A // Read Buffer Memory -#define WCR 0x40 // Write Control Register -#define WBM 0x7A // Write Buffer Memory -#define BFS 0x80 // Bit Field Set -#define BFC 0xA0 // Bit Field Clear -#define SC 0xFF // Soft Reset - -// Buffer -#define MAX_FRAMELEN 1500 -const unsigned RXSTART_INIT = 0x0000; -const unsigned RXSTOP_INIT = 0x19FE; -const unsigned TXSTART_INIT = 0x19FF; -const unsigned TXSTOP_INIT = 0x1FFF; - - - -static const char *TAG = "emac_ENC28J60"; -#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 ENC28J60_SPI_LOCK_TIMEOUT_MS (50) -#define ENC28J60_PHY_OPERATION_TIMEOUT_US (1000) -static uint8_t ENC28J60_Bank = 0xFF; - -typedef struct { - uint8_t next_packet_low; - uint8_t next_packet_high; - uint8_t length_low; - uint8_t length_high; - uint8_t status_low; - uint8_t status_high; -} ENC28J60_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_ENC28J60_t; - - -static inline bool ENC28J60_lock(emac_ENC28J60_t *emac) -{ - return xSemaphoreTake(emac->spi_lock, ENC28J60_SPI_LOCK_TIMEOUT_MS) == pdTRUE; -} - -static inline bool ENC28J60_unlock(emac_ENC28J60_t *emac) -{ - return xSemaphoreGive(emac->spi_lock) == pdTRUE; -} - -/** - * @brief Set the register bank to which you are reading/writing - * Used by register_read, register_write, BFS, BFC - * @param eth: ENC28J60 Ethernet mediator - * reg_addr: register address to which you want to access - */ -static esp_err_t ENC28J60_set_bank(emac_ENC28J60_t *emac, uint8_t reg_addr) -{ - esp_err_t ret = ESP_OK; - //check if we need to set the bank - if ((reg_addr & BANK_MASK) != ENC28J60_Bank) { - spi_transaction_t trans = { - .cmd = 0b101, - .addr = ECON1, - .length = 8, - .flags = SPI_TRANS_USE_TXDATA, - .tx_data[0] = 0x03 - }; - - if (ENC28J60_lock(emac)) { - //First, clear the bank select bits using a BFC on the last two bits of the ECON1 register - if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { - ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); - ret = ESP_FAIL; - } - //Then, set the bank select bits using a BFS on the last two bits of the ECON1 register - trans.cmd = 0b100; - trans.tx_data[0] = ((reg_addr & BANK_MASK) >> 5); - if (spi_device_polling_transmit(emac->spi_hdl, &trans) != ESP_OK) { - ESP_LOGE(TAG, "%s(%d): spi transmit failed", __FUNCTION__, __LINE__); - ret = ESP_FAIL; - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - ENC28J60_Bank = (reg_addr & BANK_MASK); - } - if (ret != ESP_OK) { - ESP_LOGE(TAG, "ERROR: COULD NOT SET THE BANK!"); - } - return ret; -} - -/** - * @brief Bit Field Set - * @param eth: ENC28J60 Ethernet mediator - * reg_addr: register address to set - * value: Which bits should be set - * @return ESP_OK: sucess - * ESP_FAIL: SPI command fail - * ESP_ERR_TIMEOUT: timeout - * - * - * BFS function performs a bit-wise OR operation to set - * all bits that are a 1 in the argument to be 1 in the register - * - * CAN ONLY BE USED FOR ETH REGISTERS (not MAC, MII, PHY registers or buffer) - * - * Example: if the register has value 01110110 - * and the argument has value 00110011 - * the specified register will be set to: 01110111 - */ -static esp_err_t ENC28J60_BFS(emac_ENC28J60_t *emac, uint8_t reg_addr, uint8_t value) -{ - esp_err_t ret = ESP_OK; - ret = ENC28J60_set_bank(emac, reg_addr); - - spi_transaction_t trans = { - .cmd = 0b100, - .addr = reg_addr, - .length = 8, - .flags = SPI_TRANS_USE_TXDATA - }; - trans.tx_data[0] = value; - if (ENC28J60_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; - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief Bit Field Clear - * @param eth: ENC28J60 Ethernet mediator - * reg_addr: register address to clear - * value: Which bits should be clear - * @return ESP_OK: sucess - * ESP_FAIL: SPI command fail - * ESP_ERR_TIMEOUT: timeout - * - * Sets all bits that are a 1 in the argument to be 0 in the register - * - * CAN ONLY BE USED FOR ETH REGISTERS (not MAC, MII, PHY registers or buffer) - * - * Example: if the register has value 11110001 - * and the argument has value 00010111 - * the specified register will be set to: 11100000 - * - */ -static esp_err_t ENC28J60_BFC(emac_ENC28J60_t *emac, uint8_t reg_addr, uint8_t value) -{ - esp_err_t ret = ESP_OK; - ret = ENC28J60_set_bank(emac, reg_addr); - - spi_transaction_t trans = { - .cmd = 0b101, - .addr = reg_addr, - .length = 8, - .flags = SPI_TRANS_USE_TXDATA - }; - trans.tx_data[0] = value; - if (ENC28J60_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; - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief write value to ENC28J60 internal register - * @param eth: ENC28J60 Ethernet mediator - * reg_addr: Address of register being read - * value: Value written to register - * @return ESP_OK: sucess - * ESP_FAIL: SPI command fail - * ESP_ERR_TIMEOUT: timeout - */ -static esp_err_t ENC28J60_register_write(emac_ENC28J60_t *emac, uint8_t reg_addr, uint8_t value) -{ - esp_err_t ret = ESP_OK; - ret = ENC28J60_set_bank(emac, reg_addr); - - spi_transaction_t trans = { - .cmd = 0b010, - .addr = reg_addr, - .length = 8, - .flags = SPI_TRANS_USE_TXDATA - }; - trans.tx_data[0] = value; - - if (ENC28J60_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; - } - ENC28J60_unlock(emac); - ets_delay_us(10); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief read value from ENC28J60 internal register - * - * @param mac: ENC28J60 MAC instance - * is_eth_reg: 0 if the register is of type ETH, 1 if register is not of type ETH. - * reg_addr: Address of register being read - * ESP_FAIL: SPI command fail - * ESP_ERR_TIMEOUT: timeout - */ -static esp_err_t ENC28J60_register_read(emac_ENC28J60_t *emac, uint8_t is_eth_reg, uint8_t reg_addr, uint8_t *value) -{ - esp_err_t ret = ESP_OK; - - ret = ENC28J60_set_bank(emac, reg_addr); - - spi_transaction_t trans = { - .cmd = 0b000, - .addr = reg_addr, - .length = 8, - .rxlength = 8, - .flags = SPI_TRANS_USE_RXDATA - }; - if (!is_eth_reg) { - trans.length = 16; - trans.rxlength = 16; - } - if (ENC28J60_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 { - if (is_eth_reg) { - *value = trans.rx_data[0]; - } else { - *value = trans.rx_data[1]; - } - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief verify ENC28J60 chip ID - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Device ID returns non-zero value - * ESP_ERR_INVALID_VERSION: Cannot read device ID (communication error) - */ -static esp_err_t ENC28J60_verify_id(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - uint8_t id; - MAC_CHECK(ENC28J60_register_read(emac, 1, EREVID, &id) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(id, "wrong Vendor ID", err, ESP_ERR_INVALID_VERSION); - - ESP_LOGI(TAG, "ENC28J60 Device revision: %d", id); - - return ESP_OK; -err: - return ret; -} - -/** - * @brief Write 16-bit value to ENC28J60 PHY register - * @param mac: ENC28J60 MAC instance - * NOT_USED: empty (to conform with number of MAC params) - * reg_addr: PHY register being written to - * reg_value: Value being written to PHY reg - * @return ESP_OK: Write success - * ESP_FAIL: Cannot write to PHY reg - */ -static esp_err_t ENC28J60_PHY_write(esp_eth_mac_t *mac, uint32_t NOT_USED, uint32_t reg_addr, uint32_t reg_value) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - - uint8_t data[2]; - uint8_t busy = 1; - uint8_t MICMD_holder; - - data[0] = (uint8_t)(reg_value & 0x00FF); - data[1] = (uint8_t)((reg_value & 0xFF00) >> 8); - - //check if the PHY is being read/written to allready - while (busy) { - MAC_CHECK(ENC28J60_register_read(emac, 0, MISTAT, &busy) == ESP_OK, "read register failed", err, ESP_FAIL); - busy &= BUSY; - } - - MAC_CHECK(ENC28J60_register_write(emac, MIREGADR, reg_addr) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MIWRL, data[0]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MIWRH, data[1]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MICMD, &MICMD_holder) == ESP_OK, "read register failed", err, ESP_FAIL); - MICMD_holder |= MIIRD; - MAC_CHECK(ENC28J60_register_write(emac, MICMD, MICMD_holder) == ESP_OK, "write register failed", err, ESP_FAIL); - - //wait for task to complete - ets_delay_us(11); - busy = 1; - while (busy) { - MAC_CHECK(ENC28J60_register_read(emac, 0, MISTAT, &busy) == ESP_OK, "read register failed", err, ESP_FAIL); - busy &= BUSY; - } - - return ESP_OK; -err: - return ret; -} - -/** - * @brief read value from ENC28J60 PHY register - * @param mac: device handle - * NOT_USED: empty (to conform with number of MAC params) - * reg_addr: PHY register being read - * reg_value: Pointer to where read value is stored - * @return ESP_OK: read success - * ESP_FAIL: Cannot read PHY reg - */ -static esp_err_t ENC28J60_PHY_read(esp_eth_mac_t *mac, uint32_t NOT_USED, uint32_t reg_addr, uint32_t *reg_value) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - - uint8_t data[2]; - uint8_t busy = 1; - uint8_t MICMD_holder; - - //check if the PHY is being read/written to allready - while (busy) { - MAC_CHECK(ENC28J60_register_read(emac, 0, MISTAT, &busy) == ESP_OK, "read register failed", err, ESP_FAIL); - busy &= BUSY; - } - MAC_CHECK(ENC28J60_register_write(emac, MIREGADR, reg_addr) == ESP_OK, "write register failed", err, ESP_FAIL); - //set MICMD.MIIRD bit to begin read operation - MAC_CHECK(ENC28J60_register_read(emac, 0, MICMD, &MICMD_holder) == ESP_OK, "read register failed", err, ESP_FAIL); - MICMD_holder |= MIIRD; - MAC_CHECK(ENC28J60_register_write(emac, MICMD, MICMD_holder) == ESP_OK, "write register failed", err, ESP_FAIL); - //wait for task to complete - busy = 1; - while (busy) { - MAC_CHECK(ENC28J60_register_read(emac, 0, MISTAT, &busy) == ESP_OK, "read register failed", err, ESP_FAIL); - busy &= BUSY; - } - //clear MICMD.MIIRD bit - MICMD_holder &= (~MIIRD); - MAC_CHECK(ENC28J60_register_write(emac, MICMD, MICMD_holder) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MIRDH, &data[1]) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MIRDL, &data[0]) == ESP_OK, "read register failed", err, ESP_FAIL); - *reg_value = ((uint16_t)data[1] << 8) | data[0]; - - return ESP_OK; -err: - return ret; -} - -/** - * @brief write buffer to ENC28J60 internal memory (to transmit ETH packets) - * @param eth: ENC28J60 Ethernet mediator - * buffer: pointer to where the data to be written is stored - * len: length of data to write - * @return ESP_OK: Write to buffer success - * ESP_FAIL: Cannot write to buffer - */ -static esp_err_t ENC28J60_memory_write(emac_ENC28J60_t *emac, uint8_t *buffer, uint32_t len) -{ - esp_err_t ret = ESP_OK; - spi_transaction_t trans = { - .cmd = 0b011, - .addr = 0b11010, - .length = len * 8, - .tx_buffer = buffer - }; - if (ENC28J60_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; - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief read buffer from ENC28J60 internal memory (to receive ETH packets) - * @param eth: ENC28J60 Ethernet mediator - * buffer: pointer to where the read data is stored - * len: length of read data - * @return ESP_OK: Read buffer success - * ESP_FAIL: Cannot read buffer - * - */ -static esp_err_t ENC28J60_memory_read(emac_ENC28J60_t *emac, uint8_t *buffer, uint32_t len) -{ - esp_err_t ret = ESP_OK; - spi_transaction_t trans = { - .cmd = 0b001, - .addr = 0b11010, - .length = len * 8, - .rxlength = len * 8, - .rx_buffer = buffer - }; - - if (ENC28J60_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; - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief Store MAC address to the Ethernet mediator struct - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Read mac address success - * ESP_FAIL: Cannot read mac address - */ -static esp_err_t ENC28J60_get_mac_addr(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - - MAC_CHECK(ENC28J60_register_read(emac, 0, MAADR6, &emac->addr[5]) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MAADR5, &emac->addr[4]) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MAADR4, &emac->addr[3]) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MAADR3, &emac->addr[2]) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MAADR2, &emac->addr[1]) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 0, MAADR1, &emac->addr[0]) == ESP_OK, "read register failed", err, ESP_FAIL); - - return ESP_OK; -err: - return ret; -} - -/** - * @brief set new mac address (from ESP32) to internal ENC28J60 registers - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Write mac address success - * ESP_FAIL: Cannot write mac address - */ -static esp_err_t ENC28J60_set_mac_addr(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - esp_read_mac(mac_addr, 3); - assert(mac_addr[0] != 0); - - MAC_CHECK(ENC28J60_register_write(emac, MAADR6, mac_addr[5]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MAADR5, mac_addr[4]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MAADR4, mac_addr[3]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MAADR3, mac_addr[2]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MAADR2, mac_addr[1]) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MAADR1, mac_addr[0]) == ESP_OK, "write register failed", err, ESP_FAIL); - - return ESP_OK; -err: - - return ret; -} - -/** - * @brief Clear multicast hash table - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Clear multicast hash table success - * ESP_FAIL: Clear multicast hash table failure - */ -static esp_err_t ENC28J60_clear_multicast_table(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - - for (int i = 0; i < 7; i++) { - MAC_CHECK(ENC28J60_BFC(emac, EHT0 + i, 0xFF) == ESP_OK, "BFC failed", err, ESP_FAIL); - } - return ESP_OK; -err: - return ret; -} - -/** - * @brief ENC28J60 software reset - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Reset success - * ESP_FAIL: Reset failure - * - */ -static esp_err_t ENC28J60_reset(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - - spi_transaction_t trans = { - .cmd = 0b111, - .addr = 0b11111, - .length = 0, - }; - if (ENC28J60_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 { - ENC28J60_Bank = 0xFF; - ets_delay_us(100); - } - ENC28J60_unlock(emac); - } else { - ret = ESP_ERR_TIMEOUT; - } - return ret; -} - -/** - * @brief default setup for ENC28J60 internal registers - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Setup success - * ESP_FAIL: Setup failure - */ -static esp_err_t ENC28J60_setup_default(esp_eth_mac_t *mac) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - - //set up buffer boundaries - MAC_CHECK(ENC28J60_register_write(emac, ERXSTL, RXSTART_INIT) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERXSTH, RXSTART_INIT >> 8) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, ERXRDPTL, RXSTART_INIT) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERXRDPTH, RXSTART_INIT >> 8) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, ERXNDL, RXSTOP_INIT) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERXNDH, RXSTOP_INIT >> 8) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, ETXSTL, TXSTART_INIT) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ETXSTH, TXSTART_INIT >> 8) == ESP_OK, "write register failed", err, ESP_FAIL); - - //poll ESTAT.CLKRDY to make sure that the MAC and MII registers are ready to be programmed. - - uint8_t holder; - MAC_CHECK(ENC28J60_register_read(emac, 1, ESTAT, &holder) == ESP_OK, "read ESTAT.CLKRDY failed", err, ESP_FAIL); - - while ((holder & CLKRDY) == 0) { - MAC_CHECK(ENC28J60_register_read(emac, 1, ESTAT, &holder) == ESP_OK, "read ESTAT.CLKRDY failed", err, ESP_FAIL); - } - - MAC_CHECK(ENC28J60_register_write(emac, ERXFCON, 0xE0) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, EPMM0, 0x3f) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, EPMM1, 0x30) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, EPMCSL, 0xf9) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, EPMCSH, 0xf7) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, MACON1, 0x0D) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MACON3, 0xF2) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MACON4, 0x40) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MABBIPG, 0x12) == ESP_OK, "write register failed", err, ESP_FAIL); - - // Stretch pulses for LED, LED_A=Link, LED_B=activity - MAC_CHECK(ENC28J60_PHY_write(mac, 1, PHLCON, 0x0476) == ESP_OK, "write register failed", err, ESP_FAIL); - //set PHCON registers - MAC_CHECK(ENC28J60_PHY_write(mac, 1, PHCON1, 0x0000) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_PHY_write(mac, 1, PHCON2, HDLDIS) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, MAIPGL, 0x12) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, MAIPGH, 0x0C) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, MABBIPG, 0x12) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_set_mac_addr(emac) == ESP_OK, "set MAC ADDR failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_BFS(emac, ECON2, AUTOINC) == ESP_OK, "BFS failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_BFS(emac, EIE, INTIE | PKTIE) == ESP_OK, "BFS failed", err, ESP_FAIL); - return ESP_OK; -err: - return ret; -} - -/** - * @brief Start ENC28J60: enable interrupt pin and start ETH receiving packets - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Start success - * ESP_FAIL: Start failure - */ -static esp_err_t ENC28J60_start(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - - MAC_CHECK(ENC28J60_register_write(emac, ERXFCON, (UCEN | BCEN | CRCEN)) == ESP_OK, "write register failed", err, ESP_FAIL); - /* enable interrupt */ - MAC_CHECK(ENC28J60_BFS(emac, EIE, (PKTIE | INTIE)) == ESP_OK, "BFS failed", err, ESP_FAIL); - /* enable rx */ - MAC_CHECK(ENC28J60_BFS(emac, ECON1, RXEN) == ESP_OK, "BFS failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, ERDPTL, 0x00) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERDPTH, 0x00) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_BFS(emac, ECON2, AUTOINC) == ESP_OK, "BFS failed", err, ESP_FAIL); - - return ESP_OK; -err: - return ret; -} - -/** - * @brief Stop ENC28J60: disable interrupt pin and stop receiving packets - * @param eth: ENC28J60 Ethernet mediator - * @return ESP_OK: Stop success - * ESP_FAIL: Stop failure - */ -static esp_err_t ENC28J60_stop(emac_ENC28J60_t *emac) -{ - esp_err_t ret = ESP_OK; - /* disable interrupt */ - MAC_CHECK(ENC28J60_BFC(emac, EIE, (PKTIE | INTIE)) == ESP_OK, "BFC failed", err, ESP_FAIL); - /* disable rx */ - MAC_CHECK(ENC28J60_BFC(emac, ECON1, RXEN) == ESP_OK, "BFC failed", err, ESP_FAIL); - - return ESP_OK; -err: - return ret; -} - -/** - * @brief Interrupt handler - * @param arg: Used to pass ENC28J60 Ethernet mediator - */ -static void ENC28J60_isr_handler(void *arg) -{ - emac_ENC28J60_t *emac = (emac_ENC28J60_t *)arg; - BaseType_t high_task_wakeup = pdFALSE; - /* notify ENC28J60 task */ - vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); - if (high_task_wakeup != pdFALSE) { - portYIELD_FROM_ISR(); - } -} - -/** - * @brief Function to receive the Ethernet packets (called by ENC28J60_isr_handler) - * @param *arg: Used to pass ENC28J60 Ethernet mediator - */ -static void emac_ENC28J60_task(void *arg) -{ - emac_ENC28J60_t *emac = (emac_ENC28J60_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 */ - ENC28J60_register_read(emac, 1, EIR, &status); - /* packet received */ - if (status & PKTIF) { - 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 { - printf("\n\n\n***FREE***\n\n\n"); - free(buffer); - } - } while (emac->packets_remain); - } - } - } - vTaskDelete(NULL); -} - -/** - * @brief Set mediator for Ethernet MAC - * @param mac: ENC28J60 Ethernet MAC instance - * eth: ENC28J60 Ethernet mediator - * @return ESP_OK: set mediator for Ethernet MAC successfully - * ESP_ERR_INVALID_ARG: set mediator for Ethernet MAC failed because of invalid argument - */ -static esp_err_t emac_ENC28J60_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_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - emac->eth = eth; - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_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_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - memcpy(emac->addr, addr, 6); - MAC_CHECK(ENC28J60_set_mac_addr(emac) == ESP_OK, "set mac address failed", err, ESP_FAIL); - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_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_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - memcpy(addr, emac->addr, 6); - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_set_link(esp_eth_mac_t *mac, eth_link_t link) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - uint32_t lstat = 0; - MAC_CHECK(ENC28J60_PHY_read(mac, 1, PHSTAT2, &lstat) == ESP_OK, "read PHY register failed", err, ESP_FAIL); - switch (link) { - case ETH_LINK_UP: - MAC_CHECK(ENC28J60_start(emac) == ESP_OK, "ENC28J60 start failed", err, ESP_FAIL); - break; - case ETH_LINK_DOWN: - MAC_CHECK(!(lstat & 0x0400), "phy is not link down", err, ESP_ERR_INVALID_STATE); - MAC_CHECK(ENC28J60_stop(emac) == ESP_OK, "ENC28J60 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; -} - - -/* - * The ENC28J60 can only support 10 Base-T. - * - * Therefore, this command will return an error - * if the speed is set to anything OTHER than 10 Base-T. - */ -static esp_err_t emac_ENC28J60_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) -{ - esp_err_t ret = ESP_OK; - switch (speed) { - case ETH_SPEED_10M: - break; - default: - ESP_LOGE(TAG, "SPEED MUST BE SET TO 10M\n"); - ret = ESP_ERR_INVALID_STATE; - break; - } - return ret; -} - -static esp_err_t emac_ENC28J60_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - uint8_t mac3 = 0; - uint32_t phc1 = 0; - - MAC_CHECK(ENC28J60_register_read(emac, 0, MACON3, &mac3) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_PHY_read(mac, 1, PHCON1, &phc1) == ESP_OK, "read PHY register failed", err, ESP_FAIL); - - switch (duplex) { - case ETH_DUPLEX_HALF: - mac3 &= (~FULDPX); - phc1 &= (~PDPXMD); - - MAC_CHECK(ENC28J60_register_write(emac, MACON3, mac3) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_PHY_write(mac, 1, PHCON1, phc1) == ESP_OK, "write PHY register failed", err, ESP_FAIL); - break; - case ETH_DUPLEX_FULL: - mac3 |= FULDPX; - phc1 |= PDPXMD; - - MAC_CHECK(ENC28J60_register_write(emac, MACON3, mac3) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_PHY_write(mac, 1, PHCON1, phc1) == ESP_OK, "write PHY register failed", err, ESP_FAIL); - break; - default: - MAC_CHECK(false, "unknown duplex", err, ESP_ERR_INVALID_ARG); - break; - } - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_set_promiscuous(esp_eth_mac_t *mac, bool enable) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - MAC_CHECK(ENC28J60_BFC(emac, ERXFCON, 0xFF) == ESP_OK, "BFC failed", err, ESP_FAIL); - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - uint8_t busy = 1; - uint8_t pkt_rcvd; - - 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 - MAC_CHECK(ENC28J60_register_read(emac, 1, ECON1, &busy) == ESP_OK, "read register failed", err, ESP_FAIL); - busy &= TXRTS; - while (busy) { - MAC_CHECK(ENC28J60_register_read(emac, 1, ECON1, &busy) == ESP_OK, "read register failed", err, ESP_FAIL); - busy &= TXRTS; - ESP_LOGW(TAG, "TRANSMIT DELAY, last transmit still in progress"); - MAC_CHECK(ENC28J60_register_read(emac, 1, EIR, &pkt_rcvd) == ESP_OK, "read register failed", err, ESP_FAIL); - if (pkt_rcvd & TXERIF) { - MAC_CHECK(ENC28J60_BFS(emac, ECON1, TXRST) == ESP_OK, "BFS failed", err, ESP_FAIL); - //MAC_CHECK(ENC28J60_BFC(emac, ECON1, TXRST) == ESP_OK, "BFC ECON1 failed", err, ESP_FAIL); - ESP_LOGE(TAG, "TRANSMIT ERROR!"); - } - } - /* set tx length */ - MAC_CHECK(ENC28J60_register_write(emac, EWRPTL, (TXSTART_INIT + 1)) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, EWRPTH, (TXSTART_INIT + 1) >> 8) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_write(emac, ETXNDL, (TXSTART_INIT + length) & 0xFF) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ETXNDH, (TXSTART_INIT + length) >> 8) == ESP_OK, "write register failed", err, ESP_FAIL); - /* copy data to tx memory */ - MAC_CHECK(ENC28J60_memory_write(emac, buf, length) == ESP_OK, "buffer memory write failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_BFS(emac, ECON1, TXRTS) == ESP_OK, "BFS failed", err, ESP_FAIL); - - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_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; - uint16_t current_packet_ptr = 0; - __attribute__((aligned(4))) ENC28J60_rx_header_t header; // SPI driver needs the rx buffer 4 byte align - emac->packets_remain = false; - - MAC_CHECK(ENC28J60_BFS(emac, ECON2, PKTDEC) == ESP_OK, "BFS failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 1, ERXFCON, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 1, EPKTCNT, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_read(emac, 1, ERDPTH, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - current_packet_ptr = rxbyte << 8; - MAC_CHECK(ENC28J60_register_read(emac, 1, ERDPTL, &rxbyte) == ESP_OK, "read failed", err, ESP_FAIL); - current_packet_ptr |= rxbyte; - MAC_CHECK(ENC28J60_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(ENC28J60_memory_read(emac, buf, rx_len) == ESP_OK, "read rx data failed", err, ESP_FAIL); - *length = rx_len - 4; // substract the CRC length - - MAC_CHECK(ENC28J60_register_write(emac, ERXRDPTL, (current_packet_ptr && 0x00FF)) == ESP_OK, "write register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERXRDPTH, (current_packet_ptr >> 8)) == ESP_OK, "write register failed", err, ESP_FAIL); - - MAC_CHECK(ENC28J60_register_read(emac, 1, ERDPTL, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - if (rxbyte & 1) { - MAC_CHECK(ENC28J60_register_read(emac, 1, ERDPTL, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERDPTL, (rxbyte + 1)) == ESP_OK, "write register failed", err, ESP_FAIL); - if (rxbyte == 0xFF) { - MAC_CHECK(ENC28J60_register_read(emac, 1, ERDPTH, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_register_write(emac, ERDPTH, (rxbyte + 1)) == ESP_OK, "write register failed", err, ESP_FAIL); - } - } - - MAC_CHECK(ENC28J60_register_read(emac, 1, EPKTCNT, &rxbyte) == ESP_OK, "read register failed", err, ESP_FAIL); - - emac->packets_remain = (rxbyte > 0); - return ESP_OK; -err: - return ret; -} - -static esp_err_t emac_ENC28J60_init(esp_eth_mac_t *mac) -{ - esp_err_t ret = ESP_OK; - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - esp_eth_mediator_t *eth = emac->eth; - - /* init gpio used by spi-ethernet interrupt */ - gpio_pad_select_gpio(CONFIG_ENC28J60_INT_GPIO); - gpio_set_direction(CONFIG_ENC28J60_INT_GPIO, GPIO_MODE_INPUT); - gpio_set_pull_mode(CONFIG_ENC28J60_INT_GPIO, GPIO_PULLUP_ONLY); - gpio_set_intr_type(CONFIG_ENC28J60_INT_GPIO, GPIO_INTR_NEGEDGE); - gpio_intr_enable(CONFIG_ENC28J60_INT_GPIO); - gpio_isr_handler_add(CONFIG_ENC28J60_INT_GPIO, ENC28J60_isr_handler, emac); - MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL) == ESP_OK, "lowlevel init failed", err, ESP_FAIL); - - //delay 500 us to wait for the clock to be ready - ets_delay_us(500); - /* reset ENC28J60 */ - MAC_CHECK(ENC28J60_reset(emac) == ESP_OK, "reset ENC28J60 failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_reset(emac) == ESP_OK, "reset ENC28J60 failed", err, ESP_FAIL); - /* verify chip id */ - MAC_CHECK(ENC28J60_verify_id(emac) == ESP_OK, "vefiry chip ID failed", err, ESP_FAIL); - /* default setup of internal registers */ - MAC_CHECK(ENC28J60_setup_default(mac) == ESP_OK, "ENC28J60 default setup failed", err, ESP_FAIL); - /* clear multicast hash table */ - MAC_CHECK(ENC28J60_clear_multicast_table(emac) == ESP_OK, "clear multicast table failed", err, ESP_FAIL); - /* get emac address from eeprom */ - MAC_CHECK(ENC28J60_set_mac_addr(emac) == ESP_OK, "fetch ethernet mac address failed", err, ESP_FAIL); - MAC_CHECK(ENC28J60_get_mac_addr(emac) == ESP_OK, "fetch ethernet mac address failed", err, ESP_FAIL); - - - return ESP_OK; -err: - gpio_isr_handler_remove(CONFIG_ENC28J60_INT_GPIO); - gpio_reset_pin(CONFIG_ENC28J60_INT_GPIO); - eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); - return ret; -} - -static esp_err_t emac_ENC28J60_deinit(esp_eth_mac_t *mac) -{ - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - esp_eth_mediator_t *eth = emac->eth; - ENC28J60_stop(emac); - gpio_isr_handler_remove(CONFIG_ENC28J60_INT_GPIO); - gpio_reset_pin(CONFIG_ENC28J60_INT_GPIO); - eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); - return ESP_OK; -} - -static esp_err_t emac_ENC28J60_del(esp_eth_mac_t *mac) -{ - emac_ENC28J60_t *emac = __containerof(mac, emac_ENC28J60_t, parent); - vTaskDelete(emac->rx_task_hdl); - vSemaphoreDelete(emac->spi_lock); - free(emac); - return ESP_OK; -} - -esp_eth_mac_t *esp_eth_mac_new_ENC28J60(const eth_ENC28J60_config_t *ENC28J60_config, const eth_mac_config_t *mac_config) -{ - esp_eth_mac_t *ret = NULL; - MAC_CHECK(mac_config, "can't set mac config to null", err, NULL); - MAC_CHECK(ENC28J60_config->spi_hdl, "can't set spi handle to null", err, NULL); - emac_ENC28J60_t *emac = calloc(1, sizeof(emac_ENC28J60_t)); - MAC_CHECK(emac, "calloc emac failed", err, NULL); - /* bind methods and attributes */ - emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms; - emac->spi_hdl = ENC28J60_config->spi_hdl; - emac->parent.set_mediator = emac_ENC28J60_set_mediator; - emac->parent.init = emac_ENC28J60_init; - emac->parent.deinit = emac_ENC28J60_deinit; - emac->parent.del = emac_ENC28J60_del; - emac->parent.write_phy_reg = ENC28J60_PHY_write; - emac->parent.read_phy_reg = ENC28J60_PHY_read; - emac->parent.set_addr = emac_ENC28J60_set_addr; - emac->parent.get_addr = emac_ENC28J60_get_addr; - emac->parent.set_speed = emac_ENC28J60_set_speed; - emac->parent.set_duplex = emac_ENC28J60_set_duplex; - emac->parent.set_link = emac_ENC28J60_set_link; - emac->parent.set_promiscuous = emac_ENC28J60_set_promiscuous; - emac->parent.transmit = emac_ENC28J60_transmit; - emac->parent.receive = emac_ENC28J60_receive; - /* create mutex */ - emac->spi_lock = xSemaphoreCreateMutex(); - MAC_CHECK(emac->spi_lock, "create lock failed", err_lock, NULL); - /* create ENC28J60 task */ - BaseType_t xReturned = xTaskCreate(emac_ENC28J60_task, "ENC28J60_tsk", mac_config->rx_task_stack_size, emac, - mac_config->rx_task_prio, &emac->rx_task_hdl); - MAC_CHECK(xReturned == pdPASS, "create ENC28J60 task failed", err_tsk, NULL); - return &(emac->parent); -err_tsk: - vSemaphoreDelete(emac->spi_lock); -err_lock: - free(emac); -err: - return ret; -} diff --git a/components/esp_eth/src/esp_eth_mac_dm9051.c b/components/esp_eth/src/esp_eth_mac_dm9051.c index 79eb2aaad6..976465b0a7 100644 --- a/components/esp_eth/src/esp_eth_mac_dm9051.c +++ b/components/esp_eth/src/esp_eth_mac_dm9051.c @@ -64,7 +64,7 @@ typedef struct { static inline bool dm9051_lock(emac_dm9051_t *emac) { - return xSemaphoreTake(emac->spi_lock, DM9051_SPI_LOCK_TIMEOUT_MS) == pdTRUE; + return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(DM9051_SPI_LOCK_TIMEOUT_MS)) == pdTRUE; } static inline bool dm9051_unlock(emac_dm9051_t *emac) @@ -543,9 +543,11 @@ static esp_err_t emac_dm9051_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) switch (speed) { case ETH_SPEED_10M: MAC_CHECK(nsr & NSR_SPEED, "phy speed is not at 10Mbps", err, ESP_ERR_INVALID_STATE); + ESP_LOGD(TAG, "working in 10Mbps"); break; case ETH_SPEED_100M: MAC_CHECK(!(nsr & NSR_SPEED), "phy speed is not at 100Mbps", err, ESP_ERR_INVALID_STATE); + ESP_LOGD(TAG, "working in 100Mbps"); break; default: MAC_CHECK(false, "unknown speed", err, ESP_ERR_INVALID_ARG); @@ -564,9 +566,11 @@ static esp_err_t emac_dm9051_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) MAC_CHECK(dm9051_register_read(emac, DM9051_NCR, &ncr) == ESP_OK, "read NCR failed", err, ESP_FAIL); switch (duplex) { case ETH_DUPLEX_HALF: + ESP_LOGD(TAG, "working in half duplex"); MAC_CHECK(!(ncr & NCR_FDX), "phy is not at half duplex", err, ESP_ERR_INVALID_STATE); break; case ETH_DUPLEX_FULL: + ESP_LOGD(TAG, "working in full duplex"); MAC_CHECK(ncr & NCR_FDX, "phy is not at full duplex", err, ESP_ERR_INVALID_STATE); break; default: diff --git a/components/esp_eth/src/esp_eth_mac_esp32.c b/components/esp_eth/src/esp_eth_mac_esp32.c index b1d28e0e53..4dcfea932f 100644 --- a/components/esp_eth/src/esp_eth_mac_esp32.c +++ b/components/esp_eth/src/esp_eth_mac_esp32.c @@ -170,9 +170,11 @@ static esp_err_t emac_esp32_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) switch (speed) { case ETH_SPEED_10M: emac_hal_set_speed(&emac->hal, EMAC_SPEED_10M); + ESP_LOGD(TAG, "working in 10Mbps"); break; case ETH_SPEED_100M: emac_hal_set_speed(&emac->hal, EMAC_SPEED_100M); + ESP_LOGD(TAG, "working in 100Mbps"); break; default: MAC_CHECK(false, "unknown speed", err, ESP_ERR_INVALID_ARG); @@ -190,9 +192,11 @@ static esp_err_t emac_esp32_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) switch (duplex) { case ETH_DUPLEX_HALF: emac_hal_set_duplex(&emac->hal, EMAC_DUPLEX_HALF); + ESP_LOGD(TAG, "working in half duplex"); break; case ETH_DUPLEX_FULL: emac_hal_set_duplex(&emac->hal, EMAC_DUPLEX_FULL); + ESP_LOGD(TAG, "working in full duplex"); break; default: MAC_CHECK(false, "unknown duplex", err, ESP_ERR_INVALID_ARG); diff --git a/components/esp_eth/src/esp_eth_phy_ENC28J60.c b/components/esp_eth/src/esp_eth_phy_ENC28J60.c deleted file mode 100644 index a5a882234d..0000000000 --- a/components/esp_eth/src/esp_eth_phy_ENC28J60.c +++ /dev/null @@ -1,250 +0,0 @@ -#include -#include -#include -#include "esp_log.h" -#include "esp_eth.h" -#include "eth_phy_regs_struct.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_err.h" - - -static const char *TAG = "ENC28J60"; -#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) - -/** - * @brief PHYIDR1(PHY Identifier Register 1) - * - */ -typedef union { - struct { - uint32_t PHY_ID_MSB : 16; /*!< Organizationally Unique Identifier(OUI) most significant bits */ - }; - uint32_t val; -} phy_id1_reg_t; -#define ETH_PHYID1_ADDR (0x02) - -/** - * @brief PHYIDR2(PHY Identifier Register 2) - * - */ -typedef union { - struct { - uint32_t PHY_ID_LSB : 6; /*!< Model revision number */ - uint32_t PHY_PN : 6; /*!< Vendor model number */ - uint32_t PHY_rev : 4; /*!< Organizationally Unique Identifier(OUI) least significant bits */ - }; - uint32_t val; -} phy_id2_reg_t; -#define ETH_PHYID2_ADDR (0x03) - - -/** - * @brief PHSTAT2(PHY Configuration and Status Register) - * - */ -typedef union { - struct { - uint32_t reserved_4_0 : 5; - uint32_t PLRITY : 1; - uint32_t reserved_8_6 : 3; - uint32_t DPXSTAT : 1; - uint32_t LSTAT : 1; - uint32_t COLSTAT : 1; - uint32_t RXSTAT : 1; - uint32_t TXSTAT : 1; - uint32_t reserved_15_14 : 2; - }; - uint32_t val; -} phstat2_reg_t; -#define ETH_PHY_PHSTAT2_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_ENC28J60_t; - -static esp_err_t ENC28J60_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) -{ - PHY_CHECK(eth, "can't set mediator for ENC28J60 to null", err); - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - ENC28J60->eth = eth; - return ESP_OK; -err: - return ESP_ERR_INVALID_ARG; -} - - - - -static esp_err_t ENC28J60_get_link(esp_eth_phy_t *phy) -{ - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - esp_eth_mediator_t *eth = ENC28J60->eth; - bmsr_reg_t PHstat1; - phstat2_reg_t PHstat2; - - PHY_CHECK(eth->phy_reg_read(eth, ENC28J60->addr, ETH_PHY_BMSR_REG_ADDR, &(PHstat1.val)) == ESP_OK, "read PHSTAT1 failed", err); - - PHY_CHECK(eth->phy_reg_read(eth, ENC28J60->addr, ETH_PHY_PHSTAT2_REG_ADDR, &(PHstat2.val)) == ESP_OK, "read PHSTAT1 failed", err); - eth_link_t link = PHstat2.LSTAT ? ETH_LINK_UP : ETH_LINK_DOWN; - - if (ENC28J60->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); - ENC28J60->link_status = link; - } - } - return ESP_OK; -err: - return ESP_FAIL; -} - -static esp_err_t ENC28J60_reset(esp_eth_phy_t *phy) -{ - /*ENC28J60 should be reset using the MAC reset function*/ - return ESP_OK; - -} - -static esp_err_t ENC28J60_negotiate(esp_eth_phy_t *phy) -{ - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - esp_eth_mediator_t *eth = ENC28J60->eth; - /* Start auto negotiation */ - bmcr_reg_t PHCON1 = { - .speed_select = 0, /* 10Mbps */ - .duplex_mode = 0, /* Half Duplex */ - .en_auto_nego = 0, /* no Auto Negotiation */ - .restart_auto_nego = 0 /* dont Restart Auto Negotiation */ - }; - PHY_CHECK(eth->phy_reg_write(eth, ENC28J60->addr, ETH_PHY_BMCR_REG_ADDR, PHCON1.val) == ESP_OK, "write PHCON1 failed", err); - - bmsr_reg_t PHSTAT1; - phstat2_reg_t PHSTAT2; - - /* Update information about link, speed, duplex */ - PHY_CHECK(eth->phy_reg_read(eth, ENC28J60->addr, ETH_PHY_BMSR_REG_ADDR, &(PHSTAT1.val)) == ESP_OK, "read PHSTAT1 failed", err); - PHY_CHECK(eth->phy_reg_read(eth, ENC28J60->addr, 0x11, &(PHSTAT2.val)) == ESP_OK, "read PHSTAT2 failed", err); - eth_link_t link = PHSTAT2.LSTAT ? ETH_LINK_UP : ETH_LINK_DOWN; - - eth_speed_t speed = ETH_SPEED_10M; - eth_duplex_t 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 (ENC28J60->link_status != link) { - PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link) == ESP_OK, "send link event failed", err); - ENC28J60->link_status = link; - } - - return ESP_OK; -err: - return ESP_FAIL; -} - -static esp_err_t ENC28J60_pwrctl(esp_eth_phy_t *phy, bool enable) -{ - /*ToDo: Add power control function*/ - return ESP_OK; -} - -static esp_err_t ENC28J60_set_addr(esp_eth_phy_t *phy, uint32_t addr) -{ - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - ENC28J60->addr = addr; - return ESP_OK; -} - -static esp_err_t ENC28J60_get_addr(esp_eth_phy_t *phy, uint32_t *addr) -{ - PHY_CHECK(addr, "get phy address failed", err); - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - *addr = ENC28J60->addr; - return ESP_OK; -err: - return ESP_ERR_INVALID_ARG; -} - -static esp_err_t ENC28J60_del(esp_eth_phy_t *phy) -{ - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - free(ENC28J60); - return ESP_OK; -} - -static esp_err_t ENC28J60_init(esp_eth_phy_t *phy) -{ - phy_ENC28J60_t *ENC28J60 = __containerof(phy, phy_ENC28J60_t, parent); - esp_eth_mediator_t *eth = ENC28J60->eth; - /* Power on Ethernet PHY */ - PHY_CHECK(ENC28J60_pwrctl(phy, true) == ESP_OK, "power on Ethernet PHY failed", err); - /* Reset Ethernet PHY */ - PHY_CHECK(ENC28J60_reset(phy) == ESP_OK, "reset Ethernet PHY failed", err); - /* Check PHY ID */ - phy_id1_reg_t id1; - phy_id2_reg_t id2; - uint32_t id3; - - PHY_CHECK(eth->phy_reg_read(eth, ENC28J60->addr, ETH_PHYID1_ADDR, &(id1.val)) == ESP_OK, "read ID1 failed", err); - PHY_CHECK(eth->phy_reg_read(eth, ENC28J60->addr, ETH_PHYID2_ADDR, &(id2.val)) == ESP_OK, "read ID2 failed", err); - - id3 = ((id1.PHY_ID_MSB << 16) | id2.PHY_ID_LSB); - PHY_CHECK(id3, "wrong PHY chip ID", err); - - return ESP_OK; -err: - return ESP_FAIL; -} - -static esp_err_t ENC28J60_deinit(esp_eth_phy_t *phy) -{ - /* Power off Ethernet PHY */ - PHY_CHECK(ENC28J60_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_ENC28J60(const eth_phy_config_t *config) -{ - PHY_CHECK(config, "can't set phy config to null", err); - PHY_CHECK(config->phy_addr == 1, "ENC28J60's phy address can only set to 1", err); - phy_ENC28J60_t *ENC28J60 = calloc(1, sizeof(phy_ENC28J60_t)); - PHY_CHECK(ENC28J60, "calloc ENC28J60 object failed", err); - ENC28J60->name = "ENC28J60"; - ENC28J60->addr = config->phy_addr; - ENC28J60->reset_timeout_ms = config->reset_timeout_ms; - ENC28J60->link_status = ETH_LINK_DOWN; - ENC28J60->autonego_timeout_ms = config->autonego_timeout_ms; - ENC28J60->parent.reset = ENC28J60_reset; - ENC28J60->parent.init = ENC28J60_init; - ENC28J60->parent.deinit = ENC28J60_deinit; - ENC28J60->parent.set_mediator = ENC28J60_set_mediator; - ENC28J60->parent.negotiate = ENC28J60_negotiate; - ENC28J60->parent.get_link = ENC28J60_get_link; - ENC28J60->parent.pwrctl = ENC28J60_pwrctl; - ENC28J60->parent.get_addr = ENC28J60_get_addr; - ENC28J60->parent.set_addr = ENC28J60_set_addr; - ENC28J60->parent.del = ENC28J60_del; - - return &(ENC28J60->parent); -err: - return NULL; -} diff --git a/components/esp_eth/test/test_dm9051.c b/components/esp_eth/test/test_dm9051.c index 2698290013..a10693e207 100644 --- a/components/esp_eth/test/test_dm9051.c +++ b/components/esp_eth/test/test_dm9051.c @@ -11,7 +11,7 @@ #include "esp_log.h" #include "driver/gpio.h" -#if !TEMPORARY_DISABLED_FOR_TARGETS(ESP32S2) +#if CONFIG_ETH_SPI_ETHERNET_DM9051 static const char *TAG = "dm9051_test"; @@ -67,7 +67,6 @@ static void got_ip_event_handler(void *arg, esp_event_base_t event_base, xEventGroupSetBits(eth_event_group, ETH_GOT_IP_BIT); } -#if CONFIG_ETH_USE_SPI_ETHERNET TEST_CASE("dm9051 io test", "[ethernet][dm9051][ignore]") { spi_device_handle_t spi_handle = NULL; @@ -130,5 +129,3 @@ TEST_CASE("dm9051 io test", "[ethernet][dm9051][ignore]") TEST_ESP_OK(spi_bus_free(HSPI_HOST)); } #endif - -#endif \ No newline at end of file diff --git a/examples/ethernet/basic/main/Kconfig.projbuild b/examples/ethernet/basic/main/Kconfig.projbuild index 8e150ac9db..99e573e49e 100644 --- a/examples/ethernet/basic/main/Kconfig.projbuild +++ b/examples/ethernet/basic/main/Kconfig.projbuild @@ -20,12 +20,6 @@ menu "Example Configuration" help Select external SPI-Ethernet module (DM9051). - config EXAMPLE_USE_ENC28J60 - bool "ENC28J60 Module" - select ETH_USE_SPI_ETHERNET - select ETH_SPI_ETHERNET_ENC28J60 - help - Select external SPI-Ethernet module (ENC28J60). endchoice if EXAMPLE_USE_INTERNAL_ETHERNET diff --git a/examples/ethernet/basic/main/ethernet_example_main.c b/examples/ethernet/basic/main/ethernet_example_main.c index 0f33266136..59c81ef996 100644 --- a/examples/ethernet/basic/main/ethernet_example_main.c +++ b/examples/ethernet/basic/main/ethernet_example_main.c @@ -119,20 +119,6 @@ void app_main(void) dm9051_config.int_gpio_num = CONFIG_EXAMPLE_DM9051_INT_GPIO; esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config); esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config); -#elif CONFIG_EXAMPLE_ETH_PHY_ENC28J60 - /* ENC28J60 ethernet driver is based on spi driver */ - spi_device_interface_config_t devcfg = { - .command_bits = 3, - .address_bits = 5, - .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)); - eth_ENC28J60_config_t ENC28J60_config = ETH_ENC28J60_DEFAULT_CONFIG(spi_handle); - esp_eth_mac_t *mac = esp_eth_mac_new_ENC28J60(&ENC28J60_config, &mac_config); - esp_eth_phy_t *phy = esp_eth_phy_new_ENC28J60(&phy_config); #endif esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); esp_eth_handle_t eth_handle = NULL; diff --git a/examples/ethernet/enc28j60/CMakeLists.txt b/examples/ethernet/enc28j60/CMakeLists.txt new file mode 100644 index 0000000000..b3da695497 --- /dev/null +++ b/examples/ethernet/enc28j60/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(enc28j60) diff --git a/examples/ethernet/enc28j60/Makefile b/examples/ethernet/enc28j60/Makefile new file mode 100644 index 0000000000..a358eeae0b --- /dev/null +++ b/examples/ethernet/enc28j60/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := enc28j60 + +include $(IDF_PATH)/make/project.mk + diff --git a/examples/ethernet/enc28j60/README.md b/examples/ethernet/enc28j60/README.md new file mode 100644 index 0000000000..d69de6a4a6 --- /dev/null +++ b/examples/ethernet/enc28j60/README.md @@ -0,0 +1,85 @@ +# ENC28J60 Example +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +## Overview + +ENC28J60 is a standalone Ethernet controller with a standard SPI interface. This example demonstrates how to drive this controller as an SPI device and then attach to TCP/IP stack. + +This is also an example of how to integrate a new Ethernet MAC driver into the `esp_eth` component, without needing to modify the ESP-IDF component. + +If you have a more complicated application to go (for example, connect to some IoT cloud via MQTT), you can always reuse the initialization codes in this example. + +## How to use example + +### Hardware Required + +To run this example, you need to prepare following hardwares: +* [ESP32 board](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/modules-and-boards.html) (e.g. ESP32-PICO, ESP32 DevKitC, etc) +* ENC28J60 module (the latest revision should be 6) + +#### Pin Assignment + +* ENC28J60 Ethernet module consumes one SPI interface plus an interrupt GPIO. By default they're connected as follows: + +| GPIO | ENC28J60 | +| ------ | ----------- | +| GPIO19 | SPI_CLK | +| GPIO23 | SPI_MOSI | +| GPIO25 | SPI_MISO | +| GPIO22 | SPI_CS | +| GPIO4 | Interrupt | + +### Configure the project + +``` +idf.py menuconfig +``` + +In the `Example Configuration` menu, set SPI specific configuration, such as SPI host number, GPIO used for MISO/MOSI/CS signal, GPIO for interrupt event and the SPI clock rate. + +**Note:** According to ENC28J60 data sheet, SPI clock could reach up to 20MHz, but in practice, the clock speed will depend on your PCB layout (in this example, the default clock rate is set to 6MHz, just to make sure that most modules on the market can work at this speed). + +### Build, Flash, and Run + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT build flash monitor +``` + +(Replace PORT with the name of the serial port to use.) + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +```bash +I (0) cpu_start: Starting scheduler on APP CPU. +I (401) enc28j60: revision: 6 +I (411) esp_eth.netif.glue: 00:04:a3:12:34:56 +I (411) esp_eth.netif.glue: ethernet attached to netif +I (421) eth_example: Ethernet Started +I (2421) enc28j60: working in 10Mbps +I (2421) enc28j60: working in half duplex +I (2421) eth_example: Ethernet Link Up +I (2421) eth_example: Ethernet HW Addr 00:04:a3:12:34:56 +I (4391) esp_netif_handlers: eth ip: 192.168.2.34, mask: 255.255.255.0, gw: 192.168.2.2 +I (4391) eth_example: Ethernet Got IP Address +I (4391) eth_example: ~~~~~~~~~~~ +I (4391) eth_example: ETHIP:192.168.2.34 +I (4401) eth_example: ETHMASK:255.255.255.0 +I (4401) eth_example: ETHGW:192.168.2.2 +I (4411) eth_example: ~~~~~~~~~~~ +``` + +Now you can ping your ESP32 in the terminal by entering `ping 192.168.2.34` (it depends on the actual IP address you get). + +**Notes:** +1. ENC28J60 hasn't burned any valid MAC address in the chip, you need to write an unique MAC address into its internal MAC address register before any traffic happened on TX and RX line. +2. ENC28J60 does not support automatic duplex negotiation. If it is connected to an automatic duplex negotiation enabled network switch or Ethernet controller, then ENC28J60 will be detected as a half-duplex device. To communicate in Full-Duplex mode, ENC28J60 and the remote node (switch, router or Ethernet controller) must be manually configured for full-duplex operation. + +## Troubleshooting + +(For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you as soon as possible.) diff --git a/examples/ethernet/enc28j60/main/CMakeLists.txt b/examples/ethernet/enc28j60/main/CMakeLists.txt new file mode 100644 index 0000000000..a2437d4e97 --- /dev/null +++ b/examples/ethernet/enc28j60/main/CMakeLists.txt @@ -0,0 +1,6 @@ +set(srcs "enc28j60_example_main.c" + "esp_eth_mac_enc28j60.c" + "esp_eth_phy_enc28j60.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS ".") diff --git a/examples/ethernet/enc28j60/main/Kconfig.projbuild b/examples/ethernet/enc28j60/main/Kconfig.projbuild new file mode 100644 index 0000000000..94245c6777 --- /dev/null +++ b/examples/ethernet/enc28j60/main/Kconfig.projbuild @@ -0,0 +1,50 @@ +menu "Example Configuration" + + config EXAMPLE_ENC28J60_SPI_HOST + int "SPI Host Number" + range 0 2 + default 1 + help + Set the SPI host used to communicate with the SPI Ethernet Controller. + + config EXAMPLE_ENC28J60_SCLK_GPIO + int "SPI SCLK GPIO number" + range 0 33 + default 19 + help + Set the GPIO number used by SPI SCLK. + + config EXAMPLE_ENC28J60_MOSI_GPIO + int "SPI MOSI GPIO number" + range 0 33 + default 23 + help + Set the GPIO number used by SPI MOSI. + + config EXAMPLE_ENC28J60_MISO_GPIO + int "SPI MISO GPIO number" + range 0 33 + default 25 + help + Set the GPIO number used by SPI MISO. + + config EXAMPLE_ENC28J60_CS_GPIO + int "SPI CS GPIO number" + range 0 33 + default 22 + help + Set the GPIO number used by SPI CS. + + config EXAMPLE_ENC28J60_SPI_CLOCK_MHZ + int "SPI clock speed (MHz)" + range 5 20 + default 6 + help + Set the clock speed (MHz) of SPI interface. + + config EXAMPLE_ENC28J60_INT_GPIO + int "Interrupt GPIO number" + default 4 + help + Set the GPIO number used by ENC28J60 interrupt. +endmenu diff --git a/examples/ethernet/enc28j60/main/component.mk b/examples/ethernet/enc28j60/main/component.mk new file mode 100644 index 0000000000..a98f634eae --- /dev/null +++ b/examples/ethernet/enc28j60/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/examples/ethernet/enc28j60/main/enc28j60.h b/examples/ethernet/enc28j60/main/enc28j60.h new file mode 100644 index 0000000000..a685417f90 --- /dev/null +++ b/examples/ethernet/enc28j60/main/enc28j60.h @@ -0,0 +1,284 @@ +// 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. + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include "esp_eth_mac.h" +#include "esp_eth_phy.h" +#include "driver/spi_master.h" + +/** + * @brief SPI Instruction Set + * + */ +#define ENC28J60_SPI_CMD_RCR (0x00) // Read Control Register +#define ENC28J60_SPI_CMD_RBM (0x01) // Read Buffer Memory +#define ENC28J60_SPI_CMD_WCR (0x02) // Write Control Register +#define ENC28J60_SPI_CMD_WBM (0x03) // Write Buffer Memory +#define ENC28J60_SPI_CMD_BFS (0x04) // Bit Field Set +#define ENC28J60_SPI_CMD_BFC (0x05) // Bit Field Clear +#define ENC28J60_SPI_CMD_SRC (0x07) // Soft Reset + +/** + * @brief Shared Registers in ENC28J60 (accessible on each bank) + * + */ +#define ENC28J60_EIE (0x1B) // Ethernet Interrupt Enable +#define ENC28J60_EIR (0x1C) // Ethernet Interrupt flags +#define ENC28J60_ESTAT (0x1D) // Ethernet Status +#define ENC28J60_ECON2 (0x1E) // Ethernet Control Register2 +#define ENC28J60_ECON1 (0x1F) // Ethernet Control Register1 + +/** + * @brief Per-bank Registers in ENC28J60 + * @note Address[15:12]: Register Type, 0 -> ETH, 1 -> MII/MAC + * Address[11:8] : Bank address + * Address[7:0] : Register Index + */ +// Bank 0 Registers +#define ENC28J60_ERDPTL (0x0000) // Read Pointer Low Byte ERDPT<7:0>) +#define ENC28J60_ERDPTH (0x0001) // Read Pointer High Byte (ERDPT<12:8>) +#define ENC28J60_EWRPTL (0x0002) // Write Pointer Low Byte (EWRPT<7:0>) +#define ENC28J60_EWRPTH (0x0003) // Write Pointer High Byte (EWRPT<12:8>) +#define ENC28J60_ETXSTL (0x0004) // TX Start Low Byte (ETXST<7:0>) +#define ENC28J60_ETXSTH (0x0005) // TX Start High Byte (ETXST<12:8>) +#define ENC28J60_ETXNDL (0x0006) // TX End Low Byte (ETXND<7:0>) +#define ENC28J60_ETXNDH (0x0007) // TX End High Byte (ETXND<12:8>) +#define ENC28J60_ERXSTL (0x0008) // RX Start Low Byte (ERXST<7:0>) +#define ENC28J60_ERXSTH (0x0009) // RX Start High Byte (ERXST<12:8>) +#define ENC28J60_ERXNDL (0x000A) // RX End Low Byte (ERXND<7:0>) +#define ENC28J60_ERXNDH (0x000B) // RX End High Byte (ERXND<12:8>) +#define ENC28J60_ERXRDPTL (0x000C) // RX RD Pointer Low Byte (ERXRDPT<7:0>) +#define ENC28J60_ERXRDPTH (0x000D) // RX RD Pointer High Byte (ERXRDPT<12:8>) +#define ENC28J60_ERXWRPTL (0x000E) // RX WR Pointer Low Byte (ERXWRPT<7:0>) +#define ENC28J60_ERXWRPTH (0x000F) // RX WR Pointer High Byte (ERXWRPT<12:8>) +#define ENC28J60_EDMASTL (0x0010) // DMA Start Low Byte (EDMAST<7:0>) +#define ENC28J60_EDMASTH (0x0011) // DMA Start High Byte (EDMAST<12:8>) +#define ENC28J60_EDMANDL (0x0012) // DMA End Low Byte (EDMAND<7:0>) +#define ENC28J60_EDMANDH (0x0013) // DMA End High Byte (EDMAND<12:8>) +#define ENC28J60_EDMADSTL (0x0014) // DMA Destination Low Byte (EDMADST<7:0>) +#define ENC28J60_EDMADSTH (0x0015) // DMA Destination High Byte (EDMADST<12:8>) +#define ENC28J60_EDMACSL (0x0016) // DMA Checksum Low Byte (EDMACS<7:0>) +#define ENC28J60_EDMACSH (0x0017) // DMA Checksum High Byte (EDMACS<15:8>) + +// Bank 1 Registers +#define ENC28J60_EHT0 (0x0100) // Hash Table Byte 0 (EHT<7:0>) +#define ENC28J60_EHT1 (0x0101) // Hash Table Byte 1 (EHT<15:8>) +#define ENC28J60_EHT2 (0x0102) // Hash Table Byte 2 (EHT<23:16>) +#define ENC28J60_EHT3 (0x0103) // Hash Table Byte 3 (EHT<31:24>) +#define ENC28J60_EHT4 (0x0104) // Hash Table Byte 4 (EHT<39:32>) +#define ENC28J60_EHT5 (0x0105) // Hash Table Byte 5 (EHT<47:40>) +#define ENC28J60_EHT6 (0x0106) // Hash Table Byte 6 (EHT<55:48>) +#define ENC28J60_EHT7 (0x0107) // Hash Table Byte 7 (EHT<63:56>) +#define ENC28J60_EPMM0 (0x0108) // Pattern Match Mask Byte 0 (EPMM<7:0>) +#define ENC28J60_EPMM1 (0x0109) // Pattern Match Mask Byte 1 (EPMM<15:8>) +#define ENC28J60_EPMM2 (0x010A) // Pattern Match Mask Byte 2 (EPMM<23:16>) +#define ENC28J60_EPMM3 (0x010B) // Pattern Match Mask Byte 3 (EPMM<31:24>) +#define ENC28J60_EPMM4 (0x010C) // Pattern Match Mask Byte 4 (EPMM<39:32>) +#define ENC28J60_EPMM5 (0x010D) // Pattern Match Mask Byte 5 (EPMM<47:40>) +#define ENC28J60_EPMM6 (0x010E) // Pattern Match Mask Byte 6 (EPMM<55:48>) +#define ENC28J60_EPMM7 (0x010F) // Pattern Match Mask Byte 7 (EPMM<63:56>) +#define ENC28J60_EPMCSL (0x0110) // Pattern Match Checksum Low Byte (EPMCS<7:0>) +#define ENC28J60_EPMCSH (0x0111) // Pattern Match Checksum High Byte (EPMCS<15:0>) +#define ENC28J60_EPMOL (0x0114) // Pattern Match Offset Low Byte (EPMO<7:0>) +#define ENC28J60_EPMOH (0x0115) // Pattern Match Offset High Byte (EPMO<12:8>) +#define ENC28J60_ERXFCON (0x0118) // Receive Fileter Control +#define ENC28J60_EPKTCNT (0x0119) // Ethernet Packet Count + +// Bank 2 Register +#define ENC28J60_MACON1 (0x1200) // MAC Control Register 1 +#define ENC28J60_MACON2 (0x1201) // MAC Control Register 2 +#define ENC28J60_MACON3 (0x1202) // MAC Control Register 3 +#define ENC28J60_MACON4 (0x1203) // MAC Control Register 4 +#define ENC28J60_MABBIPG (0x1204) // Back-to-Back Inter-Packet Gap (BBIPG<6:0>) +#define ENC28J60_MAIPGL (0x1206) // Non-Back-to-Back Inter-Packet Gap Low Byte (MAIPGL<6:0>) +#define ENC28J60_MAIPGH (0x1207) // Non-Back-to-Back Inter-Packet Gap High Byte (MAIPGH<6:0>) +#define ENC28J60_MACLCON1 (0x1208) // Retransmission Maximum (RETMAX<3:0>) +#define ENC28J60_MACLCON2 (0x1209) // Collision Window (COLWIN<5:0>) +#define ENC28J60_MAMXFLL (0x120A) // Maximum Frame Length Low Byte (MAMXFL<7:0>) +#define ENC28J60_MAMXFLH (0x120B) // Maximum Frame Length High Byte (MAMXFL<15:8>) +#define ENC28J60_MICMD (0x1212) // MII Command Register +#define ENC28J60_MIREGADR (0x1214) // MII Register Address (MIREGADR<4:0>) +#define ENC28J60_MIWRL (0x1216) // MII Write Data Low Byte (MIWR<7:0>) +#define ENC28J60_MIWRH (0x1217) // MII Write Data High Byte (MIWR<15:8>) +#define ENC28J60_MIRDL (0x1218) // MII Read Data Low Byte (MIRD<7:0>) +#define ENC28J60_MIRDH (0x1219) // MII Read Data High Byte(MIRD<15:8>) + +// Bank 3 Registers +#define ENC28J60_MAADR5 (0x1300) // MAC Address Byte 5 (MAADR<15:8>) +#define ENC28J60_MAADR6 (0x1301) // MAC Address Byte 6 (MAADR<7:0>) +#define ENC28J60_MAADR3 (0x1302) // MAC Address Byte 3 (MAADR<31:24>), OUI Byte 3 +#define ENC28J60_MAADR4 (0x1303) // MAC Address Byte 4 (MAADR<23:16>) +#define ENC28J60_MAADR1 (0x1304) // MAC Address Byte 1 (MAADR<47:40>), OUI Byte 1 +#define ENC28J60_MAADR2 (0x1305) // MAC Address Byte 2 (MAADR<39:32>), OUI Byte 2 +#define ENC28J60_EBSTSD (0x0306) // Built-in Self-Test Fill Seed (EBSTSD<7:0>) +#define ENC28J60_EBSTCON (0x0307) // Built-in Self-Test Control +#define ENC28J60_EBSTCSL (0x0308) // Built-in Self-Test Checksum Low Byte (EBSTCS<7:0>) +#define ENC28J60_EBSTCSH (0x0309) // Built-in Self-Test Checksum High Byte (EBSTCS<15:8>) +#define ENC28J60_MISTAT (0x130A) // MII Status Register +#define ENC28J60_EREVID (0x0312) // Ethernet Revision ID (EREVID<4:0>) +#define ENC28J60_ECOCON (0x0315) // Clock Output Control Register +#define ENC28J60_EFLOCON (0x0317) // Ethernet Flow Control +#define ENC28J60_EPAUSL (0x0318) // Pause Timer Value Low Byte (EPAUS<7:0>) +#define ENC28J60_EPAUSH (0x0319) // Pause Timer Value High Byte (EPAUS<15:8>) + +/** + * @brief status and flag of ENC28J60 specific registers + * + */ +// EIE bit definitions +#define EIE_INTIE (1<<7) // Global INT Interrupt Enable +#define EIE_PKTIE (1<<6) // Receive Packet Pending Interrupt Enable +#define EIE_DMAIE (1<<5) // DMA Interrupt Enable +#define EIE_LINKIE (1<<4) // Link Status Change Interrupt Enable +#define EIE_TXIE (1<<3) // Transmit Enable +#define EIE_TXERIE (1<<1) // Transmit Error Interrupt Enable +#define EIE_RXERIE (1<<0) // Receive Error Interrupt Enable + +// EIR bit definitions +#define EIR_PKTIF (1<<6) // Receive Packet Pending Interrupt Flag +#define EIR_DMAIF (1<<5) // DMA Interrupt Flag +#define EIR_LINKIF (1<<4) // Link Change Interrupt Flag +#define EIR_TXIF (1<<3) // Transmit Interrupt Flag +#define EIR_TXERIF (1<<1) // Transmit Error Interrupt Flag +#define EIR_RXERIF (1<<0) // Receive Error Interrupt Flag + +// ESTAT bit definitions +#define ESTAT_INT (1<<7) // INT Interrupt Flag +#define ESTAT_BUFER (1<<6) // Buffer Error Status +#define ESTAT_LATECOL (1<<4) // Late Collision Error +#define ESTAT_RXBUSY (1<<2) // Receive Busy +#define ESTAT_TXABRT (1<<1) // Transmit Abort Error +#define ESTAT_CLKRDY (1<<0) // Clock Ready + +// ECON2 bit definitions +#define ECON2_AUTOINC (1<<7) // Automatic Buffer Pointer Increment Enable +#define ECON2_PKTDEC (1<<6) // Packet Decrement +#define ECON2_PWRSV (1<<5) // Power Save Enable +#define ECON2_VRPS (1<<3) // Voltage Regulator Power Save Enable + +// ECON1 bit definitions +#define ECON1_TXRST (1<<7) // Transmit Logic Reset +#define ECON1_RXRST (1<<6) // Receive Logic Reset +#define ECON1_DMAST (1<<5) // DMA Start and Busy Status +#define ECON1_CSUMEN (1<<4) // DMA Checksum Enable +#define ECON1_TXRTS (1<<3) // Transmit Request to Send +#define ECON1_RXEN (1<<2) // Receive Enable +#define ECON1_BSEL1 (1<<1) // Bank Select1 +#define ECON1_BSEL0 (1<<0) // Bank Select0 + +// ERXFCON bit definitions +#define ERXFCON_UCEN (1<<7) // Unicast Filter Enable +#define ERXFCON_ANDOR (1<<6) // AND/OR Filter Select +#define ERXFCON_CRCEN (1<<5) // Post-Filter CRC Check Enable +#define ERXFCON_PMEN (1<<4) // Pattern Match Filter Enable +#define ERXFCON_MPEN (1<<3) // Magic Packet Filter Enable +#define ERXFCON_HTEN (1<<2) // Hash Table Filter Enable +#define ERXFCON_MCEN (1<<1) // Multicast Filter Enable +#define ERXFCON_BCEN (1<<0) // Broadcast Filter Enable + +// MACON1 bit definitions +#define MACON1_TXPAUS (1<<3) // Pause Control Frame Transmission Enable +#define MACON1_RXPAUS (1<<2) // Pause Control Frame Reception Enable +#define MACON1_PASSALL (1<<1) // Pass All Received Frames Enable +#define MACON1_MARXEN (1<<0) // MAC Receive Enable + +// MACON3 bit definitions +#define MACON3_PADCFG2 (1<<7) // Automatic Pad and CRC Configuration bit 2 +#define MACON3_PADCFG1 (1<<6) // Automatic Pad and CRC Configuration bit 1 +#define MACON3_PADCFG0 (1<<5) // Automatic Pad and CRC Configuration bit 0 +#define MACON3_TXCRCEN (1<<4) // Transmit CRC Enable +#define MACON3_PHDRLEN (1<<3) // Proprietary Header Enable +#define MACON3_HFRMLEN (1<<2) // Huge Frame Enable +#define MACON3_FRMLNEN (1<<1) // Frame Length Checking Enable +#define MACON3_FULDPX (1<<0) // MAC Full-Duplex Enable + +// MACON4 bit definitions +#define MACON4_DEFER (1<<6) // Defer Transmission Enable +#define MACON4_BPEN (1<<5) // No Backoff During Backpressure Enable +#define MACON4_NOBKFF (1<<4) // No Backoff Enable + +// MICMD bit definitions +#define MICMD_MIISCAN (1<<1) // MII Scan Enable +#define MICMD_MIIRD (1<<0) // MII Read Enable + +// EBSTCON bit definitions +#define EBSTCON_PSV2 (1<<7) // Pattern Shift Value 2 +#define EBSTCON_PSV1 (1<<6) // Pattern Shift Value 1 +#define EBSTCON_PSV0 (1<<5) // Pattern Shift Value 0 +#define EBSTCON_PSEL (1<<4) // Port Select +#define EBSTCON_TMSEL1 (1<<3) // Test Mode Select 1 +#define EBSTCON_TMSEL0 (1<<2) // Test Mode Select 0 +#define EBSTCON_TME (1<<1) // Test Mode Enable +#define EBSTCON_BISTST (1<<0) // Built-in Self-Test Start/Busy + +// MISTAT bit definitions +#define MISTAT_NVALID (1<<2) // MII Management Read Data Not Valid +#define MISTAT_SCAN (1<<1) // MII Management Scan Operation in Progress +#define MISTAT_BUSY (1<<0) // MII Management Busy + +// EFLOCON bit definitions +#define EFLOCON_FULDPXS (1<<2) // Full-Duplex Shadown +#define EFLOCON_FCEN1 (1<<1) // Flow Control Enable 1 +#define EFLOCON_FCEN0 (1<<0) // Flow Control Enable 0 + +/** + * @brief ENC28J60 specific configuration + * + */ +typedef struct { + spi_device_handle_t spi_hdl; /*!< Handle of SPI device driver */ + int int_gpio_num; /*!< Interrupt GPIO number */ +} eth_enc28j60_config_t; + +/** + * @brief Default ENC28J60 specific configuration + * + */ +#define ETH_ENC28J60_DEFAULT_CONFIG(spi_device) \ + { \ + .spi_hdl = spi_device, \ + .int_gpio_num = 4, \ + } + +/** +* @brief Create ENC28J60 Ethernet MAC instance +* +* @param[in] enc28j60_config: ENC28J60 specific configuration +* @param[in] mac_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_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config); + +/** +* @brief Create a PHY instance of ENC28J60 +* +* @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_enc28j60(const eth_phy_config_t *config); + +#ifdef __cplusplus +} +#endif diff --git a/examples/ethernet/enc28j60/main/enc28j60_example_main.c b/examples/ethernet/enc28j60/main/enc28j60_example_main.c new file mode 100644 index 0000000000..66df6dcdd1 --- /dev/null +++ b/examples/ethernet/enc28j60/main/enc28j60_example_main.c @@ -0,0 +1,130 @@ +/* ENC28J60 Example + + This example code is in the Public Domain (or CC0 licensed, at your option.) + + Unless required by applicable law or agreed to in writing, this + software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. +*/ +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_netif.h" +#include "esp_eth.h" +#include "esp_event.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "enc28j60.h" +#include "sdkconfig.h" + +static const char *TAG = "eth_example"; + +/** 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"); + break; + case ETHERNET_EVENT_START: + ESP_LOGI(TAG, "Ethernet Started"); + break; + case ETHERNET_EVENT_STOP: + ESP_LOGI(TAG, "Ethernet Stopped"); + break; + default: + break; + } +} + +/** Event handler for IP_EVENT_ETH_GOT_IP */ +static void got_ip_event_handler(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; + const esp_netif_ip_info_t *ip_info = &event->ip_info; + + ESP_LOGI(TAG, "Ethernet Got IP Address"); + ESP_LOGI(TAG, "~~~~~~~~~~~"); + ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip)); + ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask)); + ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw)); + ESP_LOGI(TAG, "~~~~~~~~~~~"); +} + +void app_main(void) +{ + ESP_ERROR_CHECK(gpio_install_isr_service(0)); + // Initialize TCP/IP network interface (should be called only once in application) + ESP_ERROR_CHECK(esp_netif_init()); + // Create default event loop that running in background + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); + esp_netif_t *eth_netif = esp_netif_new(&netif_cfg); + // Set default handlers to process TCP/IP stuffs + ESP_ERROR_CHECK(esp_eth_set_default_handlers(eth_netif)); + // Register user defined event handers + ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL)); + + spi_bus_config_t buscfg = { + .miso_io_num = CONFIG_EXAMPLE_ENC28J60_MISO_GPIO, + .mosi_io_num = CONFIG_EXAMPLE_ENC28J60_MOSI_GPIO, + .sclk_io_num = CONFIG_EXAMPLE_ENC28J60_SCLK_GPIO, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + }; + ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &buscfg, 1)); + /* ENC28J60 ethernet driver is based on spi driver */ + spi_device_interface_config_t devcfg = { + .command_bits = 3, + .address_bits = 5, + .mode = 0, + .clock_speed_hz = 5 * 1000 * 1000, + .spics_io_num = CONFIG_EXAMPLE_ENC28J60_CS_GPIO, + .queue_size = 20 + }; + spi_device_handle_t spi_handle = NULL; + ESP_ERROR_CHECK(spi_bus_add_device(CONFIG_EXAMPLE_ENC28J60_SPI_HOST, &devcfg, &spi_handle)); + + eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(spi_handle); + enc28j60_config.int_gpio_num = CONFIG_EXAMPLE_ENC28J60_INT_GPIO; + + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + mac_config.smi_mdc_gpio_num = -1; // ENC28J60 doesn't have SMI interface + mac_config.smi_mdio_gpio_num = -1; + esp_eth_mac_t *mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config); + + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + phy_config.autonego_timeout_ms = 0; // ENC28J60 doesn't support auto-negotiation + phy_config.reset_gpio_num = -1; // ENC28J60 doesn't have a pin to reset internal PHY + esp_eth_phy_t *phy = esp_eth_phy_new_enc28j60(&phy_config); + + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); + esp_eth_handle_t eth_handle = NULL; + ESP_ERROR_CHECK(esp_eth_driver_install(ð_config, ð_handle)); + + /* ENC28J60 doesn't burn any factory MAC address, we need to set it manually. + 02:00:00 is a Locally Administered OUI range so should not be used except when testing on a LAN under your control. + */ + mac->set_addr(mac, (uint8_t[]) { + 0x02, 0x00, 0x00, 0x12, 0x34, 0x56 + }); + + /* attach Ethernet driver to TCP/IP stack */ + ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle))); + /* start Ethernet driver state machine */ + ESP_ERROR_CHECK(esp_eth_start(eth_handle)); +} diff --git a/examples/ethernet/enc28j60/main/esp_eth_mac_enc28j60.c b/examples/ethernet/enc28j60/main/esp_eth_mac_enc28j60.c new file mode 100644 index 0000000000..710e906bd8 --- /dev/null +++ b/examples/ethernet/enc28j60/main/esp_eth_mac_enc28j60.c @@ -0,0 +1,936 @@ +// 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 +#include +#include +#include "driver/gpio.h" +#include "esp_attr.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 "enc28j60.h" +#include "sdkconfig.h" + +static const char *TAG = "enc28j60"; +#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 ENC28J60_SPI_LOCK_TIMEOUT_MS (50) +#define ENC28J60_PHY_OPERATION_TIMEOUT_US (1000) +#define ENC28J60_SYSTEM_RESET_ADDITION_TIME_US (1000) + +#define ENC28J60_BUFFER_SIZE (0x2000) // 8KB built-in buffer +/** + * ______ + * |__TX__| TX: 2 KB : [0x1800, 0x2000) + * | | + * | RX | RX: 6 KB : [0x0000, 0x1800) + * |______| + * + */ +#define ENC28J60_BUF_RX_START (0) +#define ENC28J60_BUF_RX_END (ENC28J60_BUF_TX_START - 1) +#define ENC28J60_BUF_TX_START ((ENC28J60_BUFFER_SIZE / 4) * 3) +#define ENC28J60_BUF_TX_END (ENC28J60_BUFFER_SIZE - 1) + +#define ENC28J60_RSV_SIZE (6) // Receive Status Vector Size + +typedef struct { + uint8_t next_packet_low; + uint8_t next_packet_high; + uint8_t length_low; + uint8_t length_high; + uint8_t status_low; + uint8_t status_high; +} enc28j60_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; + uint32_t next_packet_ptr; + int int_gpio_num; + uint8_t addr[6]; + uint8_t last_bank; + bool packets_remain; +} emac_enc28j60_t; + +static inline bool enc28j60_lock(emac_enc28j60_t *emac) +{ + return xSemaphoreTake(emac->spi_lock, pdMS_TO_TICKS(ENC28J60_SPI_LOCK_TIMEOUT_MS)) == pdTRUE; +} + +static inline bool enc28j60_unlock(emac_enc28j60_t *emac) +{ + return xSemaphoreGive(emac->spi_lock) == pdTRUE; +} + +/** + * @brief ERXRDPT need to be set always at odd addresses + */ +static inline uint32_t enc28j60_next_ptr_align_odd(uint32_t next_packet_ptr, uint32_t start, uint32_t end) +{ + uint32_t erxrdpt; + + if ((next_packet_ptr - 1 < start) || (next_packet_ptr - 1 > end)) { + erxrdpt = end; + } else { + erxrdpt = next_packet_ptr - 1; + } + + return erxrdpt; +} + +/** + * @brief Calculate wrap around when reading beyond the end of the RX buffer + */ +static inline uint32_t enc28j60_rx_packet_start(uint32_t start_addr, uint32_t off) +{ + if (start_addr + off > ENC28J60_BUF_RX_END) { + return (start_addr + off) - (ENC28J60_BUF_RX_END - ENC28J60_BUF_RX_START + 1); + } else { + return start_addr + off; + } +} + +/** + * @brief SPI operation wrapper for writing ENC28J60 internal register + */ +static esp_err_t enc28j60_do_register_write(emac_enc28j60_t *emac, uint8_t reg_addr, uint8_t value) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_WCR, // Write control register + .addr = reg_addr, + .length = 8, + .flags = SPI_TRANS_USE_TXDATA, + .tx_data = { + [0] = value + } + }; + if (enc28j60_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; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +/** + * @brief SPI operation wrapper for reading ENC28J60 internal register + */ +static esp_err_t enc28j60_do_register_read(emac_enc28j60_t *emac, bool is_eth_reg, uint8_t reg_addr, uint8_t *value) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_RCR, // Read control register + .addr = reg_addr, + .length = is_eth_reg ? 8 : 16, // read operation is different for ETH register and non-ETH register + .flags = SPI_TRANS_USE_RXDATA + }; + if (enc28j60_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 = is_eth_reg ? trans.rx_data[0] : trans.rx_data[1]; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +/** + * @brief SPI operation wrapper for bitwise setting ENC28J60 internal register + * @note can only be used for ETH registers + */ +static esp_err_t enc28j60_do_bitwise_set(emac_enc28j60_t *emac, uint8_t reg_addr, uint8_t mask) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_BFS, // Bit field set + .addr = reg_addr, + .length = 8, + .flags = SPI_TRANS_USE_TXDATA, + .tx_data = { + [0] = mask + } + }; + if (enc28j60_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; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +/** + * @brief SPI operation wrapper for bitwise clearing ENC28J60 internal register + * @note can only be used for ETH registers + */ +static esp_err_t enc28j60_do_bitwise_clr(emac_enc28j60_t *emac, uint8_t reg_addr, uint8_t mask) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_BFC, // Bit field clear + .addr = reg_addr, + .length = 8, + .flags = SPI_TRANS_USE_TXDATA, + .tx_data = { + [0] = mask + } + }; + if (enc28j60_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; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +/** + * @brief SPI operation wrapper for writing ENC28J60 internal memory + */ +static esp_err_t enc28j60_do_memory_write(emac_enc28j60_t *emac, uint8_t *buffer, uint32_t len) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_WBM, // Write buffer memory + .addr = 0x1A, + .length = len * 8, + .tx_buffer = buffer + }; + if (enc28j60_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; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +/** + * @brief SPI operation wrapper for reading ENC28J60 internal memory + */ +static esp_err_t enc28j60_do_memory_read(emac_enc28j60_t *emac, uint8_t *buffer, uint32_t len) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_RBM, // Read buffer memory + .addr = 0x1A, + .length = len * 8, + .rx_buffer = buffer + }; + + if (enc28j60_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; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + return ret; +} + +/** + * @brief SPI operation wrapper for resetting ENC28J60 + */ +static esp_err_t enc28j60_do_reset(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + spi_transaction_t trans = { + .cmd = ENC28J60_SPI_CMD_SRC, // Soft reset + .addr = 0x1F, + }; + if (enc28j60_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; + } + enc28j60_unlock(emac); + } else { + ret = ESP_ERR_TIMEOUT; + } + + // After reset, wait at least 1ms for the device to be ready + ets_delay_us(ENC28J60_SYSTEM_RESET_ADDITION_TIME_US); + + return ret; +} + +/** + * @brief Switch ENC28J60 register bank + */ +static esp_err_t enc28j60_switch_register_bank(emac_enc28j60_t *emac, uint8_t bank) +{ + esp_err_t ret = ESP_OK; + if (bank != emac->last_bank) { + MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, 0x03) == ESP_OK, + "clear ECON1[1:0] failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, bank & 0x03) == ESP_OK, + "set ECON1[1:0] failed", out, ESP_FAIL); + emac->last_bank = bank; + } +out: + return ret; +} + +/** + * @brief Write ENC28J60 register + */ +static esp_err_t enc28j60_register_write(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t value) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK, + "switch bank failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_do_register_write(emac, reg_addr & 0xFF, value) == ESP_OK, + "write register failed", out, ESP_FAIL); +out: + return ret; +} + +/** + * @brief Read ENC28J60 register + */ +static esp_err_t enc28j60_register_read(emac_enc28j60_t *emac, uint16_t reg_addr, uint8_t *value) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(enc28j60_switch_register_bank(emac, (reg_addr & 0xF00) >> 8) == ESP_OK, + "switch bank failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_do_register_read(emac, !(reg_addr & 0xF000), reg_addr & 0xFF, value) == ESP_OK, + "read register failed", out, ESP_FAIL); +out: + return ret; +} + +/** + * @brief Read ENC28J60 internal memroy + */ +static esp_err_t enc28j60_read_packet(emac_enc28j60_t *emac, uint32_t addr, uint8_t *packet, uint32_t len) +{ + esp_err_t ret = ESP_OK; + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTL, addr & 0xFF) == ESP_OK, + "write ERDPTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTH, (addr & 0xFF00) >> 8) == ESP_OK, + "write ERDPTH failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_do_memory_read(emac, packet, len) == ESP_OK, + "read memory failed", out, ESP_FAIL); +out: + return ret; +} + +/** + * @brief Write ENC28J60 internal PHY register + */ +static esp_err_t emac_enc28j60_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_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + uint8_t mii_status; + + /* check if phy access is in progress */ + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, + "read MISTAT failed", out, ESP_FAIL); + MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_INVALID_STATE); + + /* tell the PHY address to write */ + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIREGADR, phy_reg & 0xFF) == ESP_OK, + "write MIREGADR failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIWRL, reg_value & 0xFF) == ESP_OK, + "write MIWRL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIWRH, (reg_value & 0xFF00) >> 8) == ESP_OK, + "write MIWRH failed", out, ESP_FAIL); + + /* polling the busy flag */ + uint32_t to = 0; + do { + ets_delay_us(100); + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, + "read MISTAT failed", out, ESP_FAIL); + to += 100; + } while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US); + MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT); +out: + return ret; +} + +/** + * @brief Read ENC28J60 internal PHY register + */ +static esp_err_t emac_enc28j60_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", out, ESP_ERR_INVALID_ARG); + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + uint8_t mii_status; + uint8_t mii_cmd; + + /* check if phy access is in progress */ + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, + "read MISTAT failed", out, ESP_FAIL); + MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_INVALID_STATE); + + /* tell the PHY address to read */ + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MIREGADR, phy_reg & 0xFF) == ESP_OK, + "write MIREGADR failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MICMD, &mii_cmd) == ESP_OK, + "read MICMD failed", out, ESP_FAIL); + mii_cmd |= MICMD_MIIRD; + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MICMD, mii_cmd) == ESP_OK, + "write MICMD failed", out, ESP_FAIL); + + /* polling the busy flag */ + uint32_t to = 0; + do { + ets_delay_us(100); + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MISTAT, &mii_status) == ESP_OK, + "read MISTAT failed", out, ESP_FAIL); + to += 100; + } while ((mii_status & MISTAT_BUSY) && to < ENC28J60_PHY_OPERATION_TIMEOUT_US); + MAC_CHECK(!(mii_status & MISTAT_BUSY), "phy is busy", out, ESP_ERR_TIMEOUT); + + mii_cmd &= (~MICMD_MIIRD); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MICMD, mii_cmd) == ESP_OK, + "write MICMD failed", out, ESP_FAIL); + + uint8_t value_l = 0; + uint8_t value_h = 0; + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MIRDL, &value_l) == ESP_OK, + "read MIRDL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MIRDH, &value_h) == ESP_OK, + "read MIRDH failed", out, ESP_FAIL); + *reg_value = (value_h << 8) | value_l; +out: + return ret; +} + +/** + * @brief Set mediator for Ethernet MAC + */ +static esp_err_t emac_enc28j60_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", out, ESP_ERR_INVALID_ARG); + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + emac->eth = eth; +out: + return ret; +} + +/** + * @brief Verify chip ID + */ +static esp_err_t enc28j60_verify_id(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + uint8_t id; + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EREVID, &id) == ESP_OK, + "read EREVID failed", out, ESP_FAIL); + ESP_LOGI(TAG, "revision: %d", id); + MAC_CHECK(id > 0 && id < 7, "wrong chip ID", out, ESP_ERR_INVALID_VERSION); +out: + return ret; +} + +/** + * @brief Write mac address to internal registers + */ +static esp_err_t enc28j60_set_mac_addr(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR6, emac->addr[5]) == ESP_OK, + "write MAADR6 failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR5, emac->addr[4]) == ESP_OK, + "write MAADR5 failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR4, emac->addr[3]) == ESP_OK, + "write MAADR4 failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR3, emac->addr[2]) == ESP_OK, + "write MAADR3 failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR2, emac->addr[1]) == ESP_OK, + "write MAADR2 failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAADR1, emac->addr[0]) == ESP_OK, + "write MAADR1 failed", out, ESP_FAIL); +out: + return ret; +} + +/** + * @brief Clear multicast hash table + */ +static esp_err_t enc28j60_clear_multicast_table(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + + for (int i = 0; i < 7; i++) { + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_EHT0 + i, 0x00) == ESP_OK, + "write ENC28J60_EHT%d failed", out, ESP_FAIL, i); + } +out: + return ret; +} + +/** + * @brief Default setup for ENC28J60 internal registers + */ +static esp_err_t enc28j60_setup_default(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + + // set up receive buffer start + end + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXSTL, ENC28J60_BUF_RX_START & 0xFF) == ESP_OK, + "write ERXSTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXSTH, (ENC28J60_BUF_RX_START & 0xFF00) >> 8) == ESP_OK, + "write ERXSTH failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXNDL, ENC28J60_BUF_RX_END & 0xFF) == ESP_OK, + "write ERXNDL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXNDH, (ENC28J60_BUF_RX_END & 0xFF00) >> 8) == ESP_OK, + "write ERXNDH failed", out, ESP_FAIL); + uint32_t erxrdpt = enc28j60_next_ptr_align_odd(ENC28J60_BUF_RX_START, ENC28J60_BUF_RX_START, ENC28J60_BUF_RX_END); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTL, erxrdpt & 0xFF) == ESP_OK, + "write ERXRDPTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTH, (erxrdpt & 0xFF00) >> 8) == ESP_OK, + "write ERXRDPTH failed", out, ESP_FAIL); + + // set up transmit buffer start + end + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXSTL, ENC28J60_BUF_TX_START & 0xFF) == ESP_OK, + "write ETXSTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXSTH, (ENC28J60_BUF_TX_START & 0xFF00) >> 8) == ESP_OK, + "write ETXSTH failed", out, ESP_FAIL); + + // set up default filter mode: (unicast OR broadcast) AND crc valid + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_BCEN) == ESP_OK, + "write ERXFCON failed", out, ESP_FAIL); + + // enable MAC receive, enable pause control frame on Tx and Rx path + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON1, MACON1_MARXEN | MACON1_RXPAUS | MACON1_TXPAUS) == ESP_OK, + "write MACON1 failed", out, ESP_FAIL); + // enable automatic padding, append CRC, check frame length, half duplex by default (can update at runtime) + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON3, MACON3_PADCFG0 | MACON3_TXCRCEN | MACON3_FRMLNEN) == ESP_OK, "write MACON3 failed", out, ESP_FAIL); + // enable defer transmission (effective only in half duplex) + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON4, MACON4_DEFER) == ESP_OK, + "write MACON4 failed", out, ESP_FAIL); + // set inter-frame gap (back-to-back) + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MABBIPG, 0x12) == ESP_OK, + "write MABBIPG failed", out, ESP_FAIL); + // set inter-frame gap (non-back-to-back) + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAIPGL, 0x12) == ESP_OK, + "write MAIPGL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MAIPGH, 0x0C) == ESP_OK, + "write MAIPGH failed", out, ESP_FAIL); + +out: + return ret; +} + +/** + * @brief Start enc28j60: enable interrupt and start receive + */ +static esp_err_t enc28j60_start(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + + /* enable interrupt */ + MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIR, 0xFF) == ESP_OK, + "clear EIR failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_EIE, EIE_PKTIE | EIE_INTIE) == ESP_OK, + "set EIE.[PKTIE|INTIE] failed", out, ESP_FAIL); + /* enable rx logic */ + MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_RXEN) == ESP_OK, + "set ECON1.RXEN failed", out, ESP_FAIL); + + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTL, 0x00) == ESP_OK, + "write ERDPTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERDPTH, 0x00) == ESP_OK, + "write ERDPTH failed", out, ESP_FAIL); +out: + return ret; +} + +/** + * @brief Stop enc28j60: disable interrupt and stop receiving packets + */ +static esp_err_t enc28j60_stop(emac_enc28j60_t *emac) +{ + esp_err_t ret = ESP_OK; + /* disable interrupt */ + MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_EIE, 0xFF) == ESP_OK, + "clear EIE failed", out, ESP_FAIL); + /* disable rx */ + MAC_CHECK(enc28j60_do_bitwise_clr(emac, ENC28J60_ECON1, ECON1_RXEN) == ESP_OK, + "clear ECON1.RXEN failed", out, ESP_FAIL); +out: + return ret; +} + +static esp_err_t emac_enc28j60_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", out, ESP_ERR_INVALID_ARG); + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + memcpy(emac->addr, addr, 6); + MAC_CHECK(enc28j60_set_mac_addr(emac) == ESP_OK, "set mac address failed", out, ESP_FAIL); +out: + return ret; +} + +static esp_err_t emac_enc28j60_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", out, ESP_ERR_INVALID_ARG); + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + memcpy(addr, emac->addr, 6); +out: + return ret; +} + +static void enc28j60_isr_handler(void *arg) +{ + emac_enc28j60_t *emac = (emac_enc28j60_t *)arg; + BaseType_t high_task_wakeup = pdFALSE; + /* notify enc28j60 task */ + vTaskNotifyGiveFromISR(emac->rx_task_hdl, &high_task_wakeup); + if (high_task_wakeup != pdFALSE) { + portYIELD_FROM_ISR(); + } +} + +static void emac_enc28j60_task(void *arg) +{ + emac_enc28j60_t *emac = (emac_enc28j60_t *)arg; + uint8_t status = 0; + uint8_t *buffer = NULL; + uint32_t length = 0; + + while (1) { + // block indefinitely until some task notifies me + ulTaskNotifyTake(pdFALSE, portMAX_DELAY); + /* clear interrupt status */ + enc28j60_do_register_read(emac, true, ENC28J60_EIR, &status); + /* packet received */ + if (status & EIR_PKTIF) { + do { + length = ETH_MAX_PACKET_SIZE; + buffer = heap_caps_malloc(length, MALLOC_CAP_DMA); + if (!buffer) { + ESP_LOGE(TAG, "no mem for receive buffer"); + } else if (emac->parent.receive(&emac->parent, buffer, &length) == ESP_OK) { + /* pass the buffer to stack (e.g. TCP/IP layer) */ + if (length) { + emac->eth->stack_input(emac->eth, buffer, length); + } else { + free(buffer); + } + } else { + free(buffer); + } + } while (emac->packets_remain); + } + } + vTaskDelete(NULL); +} + +static esp_err_t emac_enc28j60_set_link(esp_eth_mac_t *mac, eth_link_t link) +{ + esp_err_t ret = ESP_OK; + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + switch (link) { + case ETH_LINK_UP: + MAC_CHECK(enc28j60_start(emac) == ESP_OK, "enc28j60 start failed", out, ESP_FAIL); + break; + case ETH_LINK_DOWN: + MAC_CHECK(enc28j60_stop(emac) == ESP_OK, "enc28j60 stop failed", out, ESP_FAIL); + break; + default: + MAC_CHECK(false, "unknown link status", out, ESP_ERR_INVALID_ARG); + break; + } +out: + return ret; +} + +static esp_err_t emac_enc28j60_set_speed(esp_eth_mac_t *mac, eth_speed_t speed) +{ + esp_err_t ret = ESP_OK; + switch (speed) { + case ETH_SPEED_10M: + ESP_LOGI(TAG, "working in 10Mbps"); + break; + default: + MAC_CHECK(false, "100Mbps unsupported", out, ESP_ERR_NOT_SUPPORTED); + break; + } +out: + return ret; +} + +static esp_err_t emac_enc28j60_set_duplex(esp_eth_mac_t *mac, eth_duplex_t duplex) +{ + esp_err_t ret = ESP_OK; + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + uint8_t mac3 = 0; + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_MACON3, &mac3) == ESP_OK, + "read MACON3 failed", out, ESP_FAIL); + switch (duplex) { + case ETH_DUPLEX_HALF: + mac3 &= ~MACON3_FULDPX; + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MABBIPG, 0x12) == ESP_OK, + "write MABBIPG failed", out, ESP_FAIL); + ESP_LOGI(TAG, "working in half duplex"); + break; + case ETH_DUPLEX_FULL: + mac3 |= MACON3_FULDPX; + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MABBIPG, 0x15) == ESP_OK, + "write MABBIPG failed", out, ESP_FAIL); + ESP_LOGI(TAG, "working in full duplex"); + break; + default: + MAC_CHECK(false, "unknown duplex", out, ESP_ERR_INVALID_ARG); + break; + } + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_MACON3, mac3) == ESP_OK, + "write MACON3 failed", out, ESP_FAIL); +out: + return ret; +} + +static esp_err_t emac_enc28j60_set_promiscuous(esp_eth_mac_t *mac, bool enable) +{ + esp_err_t ret = ESP_OK; + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + if (enable) { + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXFCON, 0x00) == ESP_OK, + "write ERXFCON failed", out, ESP_FAIL); + } +out: + return ret; +} + +static esp_err_t emac_enc28j60_transmit(esp_eth_mac_t *mac, uint8_t *buf, uint32_t length) +{ + esp_err_t ret = ESP_OK; + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + uint8_t econ1 = 0; + + /* Check if last transmit complete */ + MAC_CHECK(enc28j60_do_register_read(emac, true, ENC28J60_ECON1, &econ1) == ESP_OK, + "read ECON1 failed", out, ESP_FAIL); + MAC_CHECK(!(econ1 & ECON1_TXRTS), "last transmit still in progress", out, ESP_ERR_INVALID_STATE); + + /* Set the write pointer to start of transmit buffer area */ + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_EWRPTL, ENC28J60_BUF_TX_START & 0xFF) == ESP_OK, + "write EWRPTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_EWRPTH, (ENC28J60_BUF_TX_START & 0xFF00) >> 8) == ESP_OK, + "write EWRPTH failed", out, ESP_FAIL); + + /* Set the end pointer to correspond to the packet size given */ + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXNDL, (ENC28J60_BUF_TX_START + length) & 0xFF) == ESP_OK, + "write ETXNDL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ETXNDH, ((ENC28J60_BUF_TX_START + length) & 0xFF00) >> 8) == ESP_OK, + "write ETXNDH failed", out, ESP_FAIL); + + /* copy data to tx memory */ + uint8_t per_pkt_control = 0; // MACON3 will be used to determine how the packet will be transmitted + MAC_CHECK(enc28j60_do_memory_write(emac, &per_pkt_control, 1) == ESP_OK, + "write packet control byte failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_do_memory_write(emac, buf, length) == ESP_OK, + "buffer memory write failed", out, ESP_FAIL); + /* issue tx polling command */ + MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON1, ECON1_TXRTS) == ESP_OK, + "set ECON1.TXRTS failed", out, ESP_FAIL); +out: + return ret; +} + +static esp_err_t emac_enc28j60_receive(esp_eth_mac_t *mac, uint8_t *buf, uint32_t *length) +{ + esp_err_t ret = ESP_OK; + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + uint8_t pk_counter = 0; + uint16_t rx_len = 0; + uint32_t next_packet_addr = 0; + __attribute__((aligned(4))) enc28j60_rx_header_t header; // SPI driver needs the rx buffer 4 byte align + + // read packet header + MAC_CHECK(enc28j60_read_packet(emac, emac->next_packet_ptr, (uint8_t *)&header, sizeof(header)) == ESP_OK, + "read header failed", out, ESP_FAIL); + + // get packets' length, address + rx_len = header.length_low + (header.length_high << 8); + next_packet_addr = header.next_packet_low + (header.next_packet_high << 8); + + // read packet content + MAC_CHECK(enc28j60_read_packet(emac, enc28j60_rx_packet_start(emac->next_packet_ptr, ENC28J60_RSV_SIZE), buf, rx_len) == ESP_OK, + "read packet content failed", out, ESP_FAIL); + + // free receive buffer space + uint32_t erxrdpt = enc28j60_next_ptr_align_odd(next_packet_addr, ENC28J60_BUF_RX_START, ENC28J60_BUF_RX_END); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTL, (erxrdpt & 0xFF)) == ESP_OK, + "write ERXRDPTL failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_write(emac, ENC28J60_ERXRDPTH, (erxrdpt & 0xFF00) >> 8) == ESP_OK, + "write ERXRDPTH failed", out, ESP_FAIL); + emac->next_packet_ptr = next_packet_addr; + + MAC_CHECK(enc28j60_do_bitwise_set(emac, ENC28J60_ECON2, ECON2_PKTDEC) == ESP_OK, + "set ECON2.PKTDEC failed", out, ESP_FAIL); + MAC_CHECK(enc28j60_register_read(emac, ENC28J60_EPKTCNT, &pk_counter) == ESP_OK, + "read EPKTCNT failed", out, ESP_FAIL); + + *length = rx_len - 4; // substract the CRC length + emac->packets_remain = pk_counter > 0; +out: + return ret; +} + +static esp_err_t emac_enc28j60_init(esp_eth_mac_t *mac) +{ + esp_err_t ret = ESP_OK; + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + esp_eth_mediator_t *eth = emac->eth; + + /* init gpio used for reporting enc28j60 interrupt */ + gpio_pad_select_gpio(emac->int_gpio_num); + gpio_set_direction(emac->int_gpio_num, GPIO_MODE_INPUT); + gpio_set_pull_mode(emac->int_gpio_num, GPIO_PULLUP_ONLY); + gpio_set_intr_type(emac->int_gpio_num, GPIO_INTR_NEGEDGE); + gpio_intr_enable(emac->int_gpio_num); + gpio_isr_handler_add(emac->int_gpio_num, enc28j60_isr_handler, emac); + MAC_CHECK(eth->on_state_changed(eth, ETH_STATE_LLINIT, NULL) == ESP_OK, + "lowlevel init failed", out, ESP_FAIL); + + /* reset enc28j60 */ + MAC_CHECK(enc28j60_do_reset(emac) == ESP_OK, "reset enc28j60 failed", out, ESP_FAIL); + /* verify chip id */ + MAC_CHECK(enc28j60_verify_id(emac) == ESP_OK, "vefiry chip ID failed", out, ESP_FAIL); + /* default setup of internal registers */ + MAC_CHECK(enc28j60_setup_default(emac) == ESP_OK, "enc28j60 default setup failed", out, ESP_FAIL); + /* clear multicast hash table */ + MAC_CHECK(enc28j60_clear_multicast_table(emac) == ESP_OK, "clear multicast table failed", out, ESP_FAIL); + + return ESP_OK; +out: + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ret; +} + +static esp_err_t emac_enc28j60_deinit(esp_eth_mac_t *mac) +{ + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + esp_eth_mediator_t *eth = emac->eth; + enc28j60_stop(emac); + gpio_isr_handler_remove(emac->int_gpio_num); + gpio_reset_pin(emac->int_gpio_num); + eth->on_state_changed(eth, ETH_STATE_DEINIT, NULL); + return ESP_OK; +} + +static esp_err_t emac_enc28j60_del(esp_eth_mac_t *mac) +{ + emac_enc28j60_t *emac = __containerof(mac, emac_enc28j60_t, parent); + vTaskDelete(emac->rx_task_hdl); + vSemaphoreDelete(emac->spi_lock); + free(emac); + return ESP_OK; +} + +esp_eth_mac_t *esp_eth_mac_new_enc28j60(const eth_enc28j60_config_t *enc28j60_config, const eth_mac_config_t *mac_config) +{ + esp_eth_mac_t *ret = NULL; + emac_enc28j60_t *emac = NULL; + MAC_CHECK(enc28j60_config, "can't set enc28j60 specific config to null", err, NULL); + MAC_CHECK(mac_config, "can't set mac config to null", err, NULL); + emac = calloc(1, sizeof(emac_enc28j60_t)); + MAC_CHECK(emac, "calloc emac failed", err, NULL); + /* enc28j60 driver is interrupt driven */ + MAC_CHECK(enc28j60_config->int_gpio_num >= 0, "error interrupt gpio number", err, NULL); + emac->last_bank = 0xFF; + emac->next_packet_ptr = ENC28J60_BUF_RX_START; + /* bind methods and attributes */ + emac->sw_reset_timeout_ms = mac_config->sw_reset_timeout_ms; + emac->int_gpio_num = enc28j60_config->int_gpio_num; + emac->spi_hdl = enc28j60_config->spi_hdl; + emac->parent.set_mediator = emac_enc28j60_set_mediator; + emac->parent.init = emac_enc28j60_init; + emac->parent.deinit = emac_enc28j60_deinit; + emac->parent.del = emac_enc28j60_del; + emac->parent.write_phy_reg = emac_enc28j60_write_phy_reg; + emac->parent.read_phy_reg = emac_enc28j60_read_phy_reg; + emac->parent.set_addr = emac_enc28j60_set_addr; + emac->parent.get_addr = emac_enc28j60_get_addr; + emac->parent.set_speed = emac_enc28j60_set_speed; + emac->parent.set_duplex = emac_enc28j60_set_duplex; + emac->parent.set_link = emac_enc28j60_set_link; + emac->parent.set_promiscuous = emac_enc28j60_set_promiscuous; + emac->parent.transmit = emac_enc28j60_transmit; + emac->parent.receive = emac_enc28j60_receive; + /* create mutex */ + emac->spi_lock = xSemaphoreCreateMutex(); + MAC_CHECK(emac->spi_lock, "create lock failed", err, NULL); + /* create enc28j60 task */ + BaseType_t xReturned = xTaskCreate(emac_enc28j60_task, "enc28j60_tsk", mac_config->rx_task_stack_size, emac, + mac_config->rx_task_prio, &emac->rx_task_hdl); + MAC_CHECK(xReturned == pdPASS, "create enc28j60 task failed", err, NULL); + + return &(emac->parent); +err: + if (emac) { + if (emac->rx_task_hdl) { + vTaskDelete(emac->rx_task_hdl); + } + if (emac->spi_lock) { + vSemaphoreDelete(emac->spi_lock); + } + free(emac); + } + return ret; +} diff --git a/examples/ethernet/enc28j60/main/esp_eth_phy_enc28j60.c b/examples/ethernet/enc28j60/main/esp_eth_phy_enc28j60.c new file mode 100644 index 0000000000..b7a456b431 --- /dev/null +++ b/examples/ethernet/enc28j60/main/esp_eth_phy_enc28j60.c @@ -0,0 +1,304 @@ +// 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 +#include +#include +#include "esp_log.h" +#include "esp_eth.h" +#include "eth_phy_regs_struct.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/gpio.h" + +static const char *TAG = "enc28j60"; +#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 PHCON2(PHY Control Register 2) + * + */ +typedef union { + struct { + uint32_t reserved_7_0 : 8; // Reserved + uint32_t hdldis : 1; // Half-Duplex Loopback Disable + uint32_t reserved_9: 1; // Reserved + uint32_t jabber: 1; // Disable Jabber Correction + uint32_t reserved_12_11: 2; // Reserved + uint32_t txdis: 1; // Disable Twist-Pair Transmitter + uint32_t frclnk: 1; // Force Linkup + uint32_t reserved_15: 1; //Reserved + }; + uint32_t val; +} phcon2_reg_t; +#define ETH_PHY_PHCON2_REG_ADDR (0x10) + +/** + * @brief PHSTAT2(PHY Status Register 2) + * + */ +typedef union { + struct { + uint32_t reserved_4_0 : 5; // Reserved + uint32_t plrity : 1; // Polarity Status + uint32_t reserved_8_6 : 3; // Reserved + uint32_t dpxstat : 1; // PHY Duplex Status + uint32_t lstat : 1; // PHY Link Status (non-latching) + uint32_t colstat : 1; // PHY Collision Status + uint32_t rxstat : 1; // PHY Receive Status + uint32_t txstat : 1; // PHY Transmit Status + uint32_t reserved_15_14 : 2; // Reserved + }; + uint32_t val; +} phstat2_reg_t; +#define ETH_PHY_PHSTAT2_REG_ADDR (0x11) + +typedef struct { + esp_eth_phy_t parent; + esp_eth_mediator_t *eth; + uint32_t addr; + uint32_t reset_timeout_ms; + eth_link_t link_status; + int reset_gpio_num; +} phy_enc28j60_t; + +static esp_err_t enc28j60_update_link_duplex_speed(phy_enc28j60_t *enc28j60) +{ + esp_eth_mediator_t *eth = enc28j60->eth; + eth_speed_t speed = ETH_SPEED_10M; // enc28j60 speed is fixed to 10Mbps + eth_duplex_t duplex = ETH_DUPLEX_HALF; + phstat2_reg_t phstat; + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_PHSTAT2_REG_ADDR, &(phstat.val)) == ESP_OK, + "read PHSTAT2 failed", err); + eth_link_t link = phstat.lstat ? ETH_LINK_UP : ETH_LINK_DOWN; + /* check if link status changed */ + if (enc28j60->link_status != link) { + /* when link up, read result */ + if (link == ETH_LINK_UP) { + if (phstat.dpxstat) { + duplex = ETH_DUPLEX_FULL; + } else { + duplex = ETH_DUPLEX_HALF; + } + PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *)speed) == ESP_OK, + "change speed failed", err); + PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *)duplex) == ESP_OK, + "change duplex failed", err); + } + PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *)link) == ESP_OK, + "change link failed", err); + enc28j60->link_status = link; + } + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t enc28j60_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) +{ + PHY_CHECK(eth, "can't set mediator for enc28j60 to null", err); + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + enc28j60->eth = eth; + return ESP_OK; +err: + return ESP_ERR_INVALID_ARG; +} + +static esp_err_t enc28j60_get_link(esp_eth_phy_t *phy) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + /* Updata information about link, speed, duplex */ + PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t enc28j60_reset(esp_eth_phy_t *phy) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + enc28j60->link_status = ETH_LINK_DOWN; + esp_eth_mediator_t *eth = enc28j60->eth; + bmcr_reg_t bmcr = {.reset = 1}; + PHY_CHECK(eth->phy_reg_write(eth, enc28j60->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 < enc28j60->reset_timeout_ms / 10; to++) { + vTaskDelay(pdMS_TO_TICKS(10)); + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, + "read BMCR failed", err); + if (!bmcr.reset) { + break; + } + } + PHY_CHECK(to < enc28j60->reset_timeout_ms / 10, "PHY reset timeout", err); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t enc28j60_reset_hw(esp_eth_phy_t *phy) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + // set reset_gpio_num minus zero can skip hardware reset phy chip + if (enc28j60->reset_gpio_num >= 0) { + gpio_pad_select_gpio(enc28j60->reset_gpio_num); + gpio_set_direction(enc28j60->reset_gpio_num, GPIO_MODE_OUTPUT); + gpio_set_level(enc28j60->reset_gpio_num, 0); + gpio_set_level(enc28j60->reset_gpio_num, 1); + } + return ESP_OK; +} + +static esp_err_t enc28j60_negotiate(esp_eth_phy_t *phy) +{ + /** + * ENC28J60 does not support automatic duplex negotiation. + * If it is connected to an automatic duplex negotiation enabled network switch, + * ENC28J60 will be detected as a half-duplex device. + * To communicate in Full-Duplex mode, ENC28J60 and the remote node + * must be manually configured for full-duplex operation. + */ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + /* Updata information about link, speed, duplex */ + PHY_CHECK(enc28j60_update_link_duplex_speed(enc28j60) == ESP_OK, "update link duplex speed failed", err); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t enc28j60_pwrctl(esp_eth_phy_t *phy, bool enable) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + esp_eth_mediator_t *eth = enc28j60->eth; + bmcr_reg_t bmcr; + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->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, enc28j60->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, + "write BMCR failed", err); + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->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 enc28j60_set_addr(esp_eth_phy_t *phy, uint32_t addr) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + enc28j60->addr = addr; + return ESP_OK; +} + +static esp_err_t enc28j60_get_addr(esp_eth_phy_t *phy, uint32_t *addr) +{ + PHY_CHECK(addr, "addr can't be null", err); + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + *addr = enc28j60->addr; + return ESP_OK; +err: + return ESP_ERR_INVALID_ARG; +} + +static esp_err_t enc28j60_del(esp_eth_phy_t *phy) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + free(enc28j60); + return ESP_OK; +} + +static esp_err_t enc28j60_init(esp_eth_phy_t *phy) +{ + phy_enc28j60_t *enc28j60 = __containerof(phy, phy_enc28j60_t, parent); + esp_eth_mediator_t *eth = enc28j60->eth; + /* Power on Ethernet PHY */ + PHY_CHECK(enc28j60_pwrctl(phy, true) == ESP_OK, "power control failed", err); + /* Reset Ethernet PHY */ + PHY_CHECK(enc28j60_reset(phy) == ESP_OK, "reset failed", err); + /* Check PHY ID */ + phyidr1_reg_t id1; + phyidr2_reg_t id2; + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_IDR1_REG_ADDR, &(id1.val)) == ESP_OK, + "read ID1 failed", err); + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_IDR2_REG_ADDR, &(id2.val)) == ESP_OK, + "read ID2 failed", err); + PHY_CHECK(id1.oui_msb == 0x0083 && id2.oui_lsb == 0x05 && id2.vendor_model == 0x00, + "wrong chip ID", err); + /* Disable half duplex loopback */ + phcon2_reg_t phcon2; + PHY_CHECK(eth->phy_reg_read(eth, enc28j60->addr, ETH_PHY_PHCON2_REG_ADDR, &(phcon2.val)) == ESP_OK, + "read PHCON2 failed", err); + phcon2.hdldis = 1; + PHY_CHECK(eth->phy_reg_write(eth, enc28j60->addr, ETH_PHY_PHCON2_REG_ADDR, phcon2.val) == ESP_OK, + "write PHCON2 failed", err); + return ESP_OK; +err: + return ESP_FAIL; +} + +static esp_err_t enc28j60_deinit(esp_eth_phy_t *phy) +{ + /* Power off Ethernet PHY */ + PHY_CHECK(enc28j60_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_enc28j60(const eth_phy_config_t *config) +{ + PHY_CHECK(config, "can't set phy config to null", err); + phy_enc28j60_t *enc28j60 = calloc(1, sizeof(phy_enc28j60_t)); + PHY_CHECK(enc28j60, "calloc enc28j60 failed", err); + enc28j60->addr = config->phy_addr; // although PHY addr is meaningless to ENC28J60 + enc28j60->reset_timeout_ms = config->reset_timeout_ms; + enc28j60->reset_gpio_num = config->reset_gpio_num; + enc28j60->link_status = ETH_LINK_DOWN; + enc28j60->parent.reset = enc28j60_reset; + enc28j60->parent.reset_hw = enc28j60_reset_hw; + enc28j60->parent.init = enc28j60_init; + enc28j60->parent.deinit = enc28j60_deinit; + enc28j60->parent.set_mediator = enc28j60_set_mediator; + enc28j60->parent.negotiate = enc28j60_negotiate; + enc28j60->parent.get_link = enc28j60_get_link; + enc28j60->parent.pwrctl = enc28j60_pwrctl; + enc28j60->parent.get_addr = enc28j60_get_addr; + enc28j60->parent.set_addr = enc28j60_set_addr; + enc28j60->parent.del = enc28j60_del; + return &(enc28j60->parent); +err: + return NULL; +} diff --git a/examples/ethernet/enc28j60/sdkconfig.defaults b/examples/ethernet/enc28j60/sdkconfig.defaults new file mode 100644 index 0000000000..2facc67ab6 --- /dev/null +++ b/examples/ethernet/enc28j60/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_ESP_NETIF_TCPIP_ADAPTER_COMPATIBLE_LAYER=n