From f324458b6ad26d31a5ee5309ea1ac52c5d5654f6 Mon Sep 17 00:00:00 2001 From: Frank Sautter Date: Tue, 17 Oct 2017 05:05:27 +0800 Subject: [PATCH] feat(emac): add support for emac to use internal (APLL) clock outputs. --- components/ethernet/emac_common.h | 1 + components/ethernet/emac_dev.c | 20 ------ components/ethernet/emac_dev.h | 3 - components/ethernet/emac_main.c | 58 ++++++++++++--- components/ethernet/eth_phy/phy_lan8720.c | 1 + components/ethernet/eth_phy/phy_tlk110.c | 1 + components/ethernet/include/esp_eth.h | 13 +++- examples/ethernet/ethernet/README.md | 72 ++++++++++++------- .../ethernet/ethernet/main/Kconfig.projbuild | 44 ++++++++++++ .../ethernet/main/ethernet_example_main.c | 5 ++ 10 files changed, 156 insertions(+), 62 deletions(-) diff --git a/components/ethernet/emac_common.h b/components/ethernet/emac_common.h index d70abdd77a..4b20c6aca3 100644 --- a/components/ethernet/emac_common.h +++ b/components/ethernet/emac_common.h @@ -53,6 +53,7 @@ enum { struct emac_config_data { eth_phy_base_t phy_addr; eth_mode_t mac_mode; + eth_clock_mode_t clock_mode; struct dma_extended_desc *dma_etx; uint32_t cur_tx; uint32_t dirty_tx; diff --git a/components/ethernet/emac_dev.c b/components/ethernet/emac_dev.c index e4896af34c..5a8b61cba0 100644 --- a/components/ethernet/emac_dev.c +++ b/components/ethernet/emac_dev.c @@ -101,18 +101,6 @@ void emac_enable_clk(bool enable) } } -void emac_set_clk_mii(void) -{ - //select ex clock source - REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN); - //ex clk enable - REG_SET_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL); - - //set mii mode rx/tx clk enable - REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_RX_EN); - REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_TX_EN); -} - void emac_dma_init(void) { REG_SET_BIT(EMAC_DMAOPERATION_MODE_REG, EMAC_FORWARD_UNDERSIZED_GOOD_FRAMES); @@ -134,11 +122,3 @@ void emac_mac_init(void) REG_CLR_BIT(EMAC_GMACCONFIG_REG, EMAC_GMACFESPEED); REG_SET_BIT(EMAC_GMACFRAMEFILTER_REG, EMAC_PROMISCUOUS_MODE); } - -void emac_set_clk_rmii(void) -{ - //select ex clock source - REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN); - //ex clk enable - REG_SET_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL); -} diff --git a/components/ethernet/emac_dev.h b/components/ethernet/emac_dev.h index dc1045b92f..d29ab07534 100644 --- a/components/ethernet/emac_dev.h +++ b/components/ethernet/emac_dev.h @@ -40,8 +40,6 @@ struct dma_extended_desc { }; void emac_enable_clk(bool enable); -void emac_set_clk_rmii(void); -void emac_set_clk_mii(void); void emac_reset(void); void emac_set_gpio_pin_rmii(void); void emac_set_gpio_pin_mii(void); @@ -113,4 +111,3 @@ void inline emac_send_pause_zero_frame_enable(void) #endif #endif - diff --git a/components/ethernet/emac_main.c b/components/ethernet/emac_main.c index e0a5428294..044c0337bd 100644 --- a/components/ethernet/emac_main.c +++ b/components/ethernet/emac_main.c @@ -19,6 +19,7 @@ #include "rom/gpio.h" #include "soc/dport_reg.h" #include "soc/io_mux_reg.h" +#include "soc/rtc.h" #include "soc/rtc_cntl_reg.h" #include "soc/gpio_reg.h" #include "soc/dport_reg.h" @@ -130,10 +131,10 @@ static void emac_set_rx_base_reg(void) * * (1) Initializing the Linked List. Connect the numerable nodes to a circular linked list, appoint one of the nodes as the head node, mark* the dirty_rx and cur_rx into the node, and mount the node on the hardware base address. Initialize cnt_rx into 0. * -* (2) When hardware receives packets, nodes of linked lists will be fed with data packets from the base address by turns, marks the node +* (2) When hardware receives packets, nodes of linked lists will be fed with data packets from the base address by turns, marks the node * of linked lists as “HARDWARE UNUSABLE” and reports interrupts. * -* (3) When the software receives the interrupts, it will handle the linked lists by turns from dirty_rx, send data packets to protocol +* (3) When the software receives the interrupts, it will handle the linked lists by turns from dirty_rx, send data packets to protocol * stack. dirty_rx will deviate backwards by turns and cnt_rx will by turns ++. * * (4) After the protocol stack handles all the data and calls the free function, it will deviate backwards by turns from cur_rx, mark the * node of linked lists as “HARDWARE USABLE” and cnt_rx will by turns --. @@ -252,6 +253,7 @@ static void emac_set_user_config_data(eth_config_t *config ) { emac_config.phy_addr = config->phy_addr; emac_config.mac_mode = config->mac_mode; + emac_config.clock_mode = config->clock_mode; emac_config.phy_init = config->phy_init; emac_config.emac_tcpip_input = config->tcpip_input; emac_config.emac_gpio_config = config->gpio_config; @@ -291,7 +293,12 @@ static esp_err_t emac_verify_args(void) } if (emac_config.mac_mode != ETH_MODE_RMII) { - ESP_LOGE(TAG, "mac mode err,now only support RMII"); + ESP_LOGE(TAG, "mac mode err, currently only support for RMII"); + ret = ESP_FAIL; + } + + if (emac_config.clock_mode > ETH_CLOCK_GPIO17_OUT) { + ESP_LOGE(TAG, "emac clock mode err"); ret = ESP_FAIL; } @@ -480,7 +487,7 @@ static void emac_process_rx_unavail(void) } uint32_t tmp_dirty = emac_config.dirty_rx; emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; - + //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[tmp_dirty].basic.desc2), (((emac_config.dma_erx[tmp_dirty].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); @@ -529,7 +536,7 @@ static void emac_process_rx(void) } uint32_t tmp_dirty = emac_config.dirty_rx; emac_config.dirty_rx = (emac_config.dirty_rx + 1) % DMA_RX_BUF_NUM; - + //copy data to lwip emac_config.emac_tcpip_input((void *)(emac_config.dma_erx[tmp_dirty].basic.desc2), (((emac_config.dma_erx[tmp_dirty].basic.desc0) >> EMAC_DESC_FRAME_LENGTH_S) & EMAC_DESC_FRAME_LENGTH) , NULL); @@ -1036,14 +1043,46 @@ esp_err_t esp_eth_init_internal(eth_config_t *config) //before set emac reg must enable clk periph_module_enable(PERIPH_EMAC_MODULE); + + if (emac_config.clock_mode != ETH_CLOCK_GPIO0_IN) { + // 50 MHz = 40MHz * (6 + 4) / (2 * (2 + 2) = 400MHz / 8 + rtc_clk_apll_enable(1, 0, 0, 6, 2); + // the next to values have to be set AFTER "periph_module_enable" is called + REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_H_DIV_NUM, 0); + REG_SET_FIELD(EMAC_EX_CLKOUT_CONF_REG, EMAC_EX_CLK_OUT_DIV_NUM, 0); + + if (emac_config.clock_mode == ETH_CLOCK_GPIO0_OUT) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1); + REG_WRITE(PIN_CTRL, 6); + ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO0"); + } else if (emac_config.clock_mode == ETH_CLOCK_GPIO16_OUT) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO16_U, FUNC_GPIO16_EMAC_CLK_OUT); + ESP_LOGD(TAG, "EMAC 50MHz clock output on GPIO16"); + } else if (emac_config.clock_mode == ETH_CLOCK_GPIO17_OUT) { + PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO17_U, FUNC_GPIO17_EMAC_CLK_OUT_180); + ESP_LOGD(TAG, "EMAC 50MHz inverted clock output on GPIO17"); + } + } + emac_enable_clk(true); REG_SET_FIELD(EMAC_EX_PHYINF_CONF_REG, EMAC_EX_PHY_INTF_SEL, EMAC_EX_PHY_INTF_RMII); - emac_dma_init(); - if (emac_config.mac_mode == ETH_MODE_RMII) { - emac_set_clk_rmii(); + + if (emac_config.clock_mode == ETH_CLOCK_GPIO0_IN) { + // external clock on GPIO0 + REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN); + REG_CLR_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_INT_OSC_EN); + REG_SET_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL); + ESP_LOGD(TAG, "External clock input 50MHz on GPIO0"); + if (emac_config.mac_mode == ETH_MODE_MII) { + REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_RX_EN); + REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_MII_CLK_TX_EN); + } } else { - emac_set_clk_mii(); + // internal clock by APLL + REG_CLR_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_EXT_OSC_EN); + REG_SET_BIT(EMAC_EX_CLK_CTRL_REG, EMAC_EX_INT_OSC_EN); + REG_CLR_BIT(EMAC_EX_OSCCLK_CONF_REG, EMAC_EX_OSC_CLK_SEL); } emac_config.emac_gpio_config(); @@ -1068,4 +1107,3 @@ esp_err_t esp_eth_init_internal(eth_config_t *config) _exit: return ret; } - diff --git a/components/ethernet/eth_phy/phy_lan8720.c b/components/ethernet/eth_phy/phy_lan8720.c index b0f793b33d..0914ce1dc7 100644 --- a/components/ethernet/eth_phy/phy_lan8720.c +++ b/components/ethernet/eth_phy/phy_lan8720.c @@ -118,6 +118,7 @@ const eth_config_t phy_lan8720_default_ethernet_config = { // for defaults. .phy_addr = 0, .mac_mode = ETH_MODE_RMII, + .clock_mode = ETH_CLOCK_GPIO0_IN, //Only FULLDUPLEX mode support flow ctrl now! .flow_ctrl_enable = true, .phy_init = phy_lan8720_init, diff --git a/components/ethernet/eth_phy/phy_tlk110.c b/components/ethernet/eth_phy/phy_tlk110.c index 020329a267..2f9cf50b6d 100644 --- a/components/ethernet/eth_phy/phy_tlk110.c +++ b/components/ethernet/eth_phy/phy_tlk110.c @@ -122,6 +122,7 @@ const eth_config_t phy_tlk110_default_ethernet_config = { // is used if all pins are unconnected. .phy_addr = 0x1, .mac_mode = ETH_MODE_RMII, + .clock_mode = ETH_CLOCK_GPIO0_IN, //Only FULLDUPLEX mode support flow ctrl now! .flow_ctrl_enable = true, .phy_init = phy_tlk110_init, diff --git a/components/ethernet/include/esp_eth.h b/components/ethernet/include/esp_eth.h index 01cd0e29fa..0a9f3e770f 100644 --- a/components/ethernet/include/esp_eth.h +++ b/components/ethernet/include/esp_eth.h @@ -28,6 +28,13 @@ typedef enum { ETH_MODE_MII, } eth_mode_t; +typedef enum { + ETH_CLOCK_GPIO0_IN = 0, + ETH_CLOCK_GPIO0_OUT = 1, + ETH_CLOCK_GPIO16_OUT = 2, + ETH_CLOCK_GPIO17_OUT = 3 +} eth_clock_mode_t; + typedef enum { ETH_SPEED_MODE_10M = 0, ETH_SPEED_MODE_100M, @@ -90,8 +97,9 @@ typedef void (*eth_phy_power_enable_func)(bool enable); typedef struct { eth_phy_base_t phy_addr; /*!< phy base addr (0~31) */ eth_mode_t mac_mode; /*!< mac mode only support RMII now */ - eth_tcpip_input_func tcpip_input; /*!< tcpip input func */ - eth_phy_func phy_init; /*!< phy init func */ + eth_clock_mode_t clock_mode; /*!< external/internal clock mode selecton */ + eth_tcpip_input_func tcpip_input; /*!< tcpip input func */ + eth_phy_func phy_init; /*!< phy init func */ eth_phy_check_link_func phy_check_link; /*!< phy check link func */ eth_phy_check_init_func phy_check_init; /*!< phy check init func */ eth_phy_get_speed_mode_func phy_get_speed_mode; /*!< phy check init func */ @@ -247,4 +255,3 @@ void esp_eth_free_rx_buf(void *buf); #endif #endif - diff --git a/examples/ethernet/ethernet/README.md b/examples/ethernet/ethernet/README.md index e4a6e5cc7e..cedff71d01 100644 --- a/examples/ethernet/ethernet/README.md +++ b/examples/ethernet/ethernet/README.md @@ -1,36 +1,56 @@ # Ethernet Example - -Initialises the ethernet interface and enables it, then sends DHCP requests and tries to obtain a DHCP lease. If successful then you will be able to ping the device. +Initialises the Ethernet interface and enables it, then sends DHCP requests and tries to obtain a DHCP lease. If successful then you will be able to ping the device. # PHY Configuration - -Use "make menuconfig" to set the PHY model and the PHY address, and configure the SMI I/O pins (see below). These configuration items will vary depending on the hardware configuration you are using. +Use `make menuconfig` to set the PHY model and the PHY address, and configure the SMI I/O pins (see below). These configuration items will vary depending on the hardware configuration you are using. The default example configuration is correct for Espressif's Ethernet board with TLK110 PHY. Other hardware will require different configuration and/or changes to the example. ## PHY Address - The PHY address depends on the hardware and the PHY configuration. Consult the documentation/datasheet for the PHY hardware you have. -* Default address 31 is correct for Espressif's Ethernet board with TLK110 PHY. -* Address 1 is correct for the common Waveshare LAN8720 PHY breakout. -* Other LAN8720 breakouts may take address 0. +* Address 31 (default) for Espressif's Ethernet board with TLK110 PHY +* Address 1 for the common Waveshare LAN8720 PHY breakout +* Address 0 for other LAN8720 breakouts -If the PHY address is incorrect then the EMAC will initialise but all attempts to read/write configuration registers on the PHY will fail. +If the PHY address is incorrect then the EMAC will initialise, but all attempts to read/write configuration registers on the PHY will fail. + +## PHY Clock Wiring +The ESP32 and the Ethernet PHY need a common 50MHz reference clock. This clock can either be be provided externally by a crystal oscillator (e.g. crystal connected to the PHY or a seperate crystal oscillator) or internally by using the EPS32's APLL. + +Because of its freqency the signal integrity has to be observed (ringing, capacitive load, resisitive load, skew, length of PCB trace). It is recommended to add a 33Ω resistor in series to reduce ringing. + +Possible configurations of the 50MHz clock signal: + +| Mode | GPIO Pin | Signal name | Notes | +| -------- | -------- | -------------- | -------------------------------------------------------------------------------------------------- | +| external | `GPIO0` | `EMAC_TX_CLK` | Input of 50MHz PHY clock | +| internal | `GPIO0` | `CLK_OUT1` | Output of 50MHz APLL clock. Signal quality might be an issue. | +| internal | `GPIO16` | `EMAC_CLK_OUT` | Output of 50MHz APLL clock. | +| internal | `GPIO17` | `EMAC_CLK_180` | Inverted output of 50MHz APLL clock. Found to be best suitable for LAN8720 with long signal lines. | + + +#### External PHY Clock +The external reference clock of 50MHz must be supplied on `GPIO0`. See note about `GPIO0` below. + +#### Internal PHY Clock +The ESP32 can generate a 50MHz clock using its APLL. When the APLL is already used as clock source for other purposes (most likely I²S) external PHY has to be used. + +On different test setups clock output on `GPIO0` was found unstable because in most designs the signal path is not ideal for this high frequency (the PCB trace has several devices added to it and therefore the capacitive load is relatively high) + +The inverted clock signal `EMAC_CLK_180` was found working best with a LAN8720 PHY. ## RMII PHY Wiring +The following PHY connections are required for RMII PHY data connections. These `GPIO` pin assignments cannot be changed. -The following PHY connections are required for RMII PHY data connections. These GPIO pin assignments cannot be changed. - -| GPIO | RMII Signal | ESP32 EMAC Function | Notes | -| ------- | ----------- | ------------------- | ----- | -| 0 | REF_CLK | EMAC_TX_CLK | Currently this must be a 50MHz reference clock input from the PHY (ext_osc configuration). | -| 21 | TX_EN | EMAC_TX_EN | | -| 19 | TX0 | EMAC_TXD0 | | -| 22 | TX1 | EMAC_TXD1 | | -| 25 | RX0 | EMAC_RXD0 | | -| 26 | RX1 | EMAC_RXD1 | | -| 27 | CRS_DV | EMAC_RX_DRV | | +| GPIO | RMII Signal | ESP32 EMAC Function | Notes | +| -------- | ----------- | ------------------- | ----- | +| `GPIO21` | `TX_EN` | `EMAC_TX_EN` | | +| `GPIO19` | `TX0` | `EMAC_TXD0` | | +| `GPIO22` | `TX1` | `EMAC_TXD1` | | +| `GPIO25` | `RX0` | `EMAC_RXD0` | | +| `GPIO26` | `RX1` | `EMAC_RXD1` | | +| `GPIO27` | `CRS_DV` | `EMAC_RX_DRV` | | ## RMII PHY SMI Wiring @@ -40,17 +60,17 @@ For the example, these pins are configured via `make menuconfig` under the Examp | Default Example GPIO | RMII Signal | Notes | | -------------------- | ----------- | ------------- | -| 23 | MDC | Output to PHY | -| 18 | MDIO | Bidirectional | +| `GPIO23` | `MDC` | Output to PHY | +| `GPIO18` | `MDIO` | Bidirectional | The defaults in the example are correct for Espressif's Ethernet development board. -## Note about GPIO0 +## Note about `GPIO0` -Because GPIO0 is a strapping pin for entering UART flashing mode on reset, care must be taken when also using this pin as EMAC_TX_CLK. If the clock output from the PHY is oscillating during reset, the ESP32 may randomly enter UART flashing mode. +Because `GPIO0` is a strapping pin for entering UART flashing mode on reset, care must be taken when also using this pin as `EMAC_TX_CLK`. If the clock output from the PHY is oscillating during reset, the ESP32 may randomly enter UART flashing mode. -One solution is to use an additional GPIO as a "power pin", which either powers the PHY on/off or enables/disables the PHY's own oscillator. This prevents the clock signal from being active during a system reset. For this configuration to work, GPIO0 also needs a pullup resistor and the "power pin" GPIO will need a pullup/pulldown resistor - as appropriate in order to keep the PHY clock disabled when the ESP32 is in reset. +One solution is to use an additional GPIO as a "power pin", which either powers the PHY on/off or enables/disables the PHY's own oscillator. This prevents the clock signal from being active during a system reset. For this configuration to work, `GPIO0` also needs a pullup resistor and the "power pin" GPIO will need a pullup/pulldown resistor - as appropriate in order to keep the PHY clock disabled when the ESP32 is in reset. See the example source code to see how the "power pin" GPIO can be managed in software. -The example defaults to using GPIO 17 for this function, but it can be overriden. On Espressif's Ethernet development board, GPIO 17 is the power pin used to enable/disable the PHY oscillator. +The example defaults to using `GPIO17` for this function, but it can be overriden. On Espressif's Ethernet development board, `GPIO17` is the power pin used to enable/disable the PHY oscillator. diff --git a/examples/ethernet/ethernet/main/Kconfig.projbuild b/examples/ethernet/ethernet/main/Kconfig.projbuild index f6c46b54d6..b762dc54ac 100644 --- a/examples/ethernet/ethernet/main/Kconfig.projbuild +++ b/examples/ethernet/ethernet/main/Kconfig.projbuild @@ -18,12 +18,53 @@ config PHY_LAN8720 endchoice + config PHY_ADDRESS int "PHY Address (0-31)" default 31 range 0 31 help Select the PHY Address (0-31) for the hardware configuration and PHY model. + TLK110 default 31 + LAN8720 default 1 or 0 + + +choice PHY_CLOCK_MODE + prompt "EMAC clock mode" + default PHY_CLOCK_GPIO0_IN + help + Select external (input on GPIO0) or internal (output on GPIO0, GPIO16 or GPIO17) clock + + +config PHY_CLOCK_GPIO0_IN + bool "GPIO0 input" + help + Input of 50MHz refclock on GPIO0 + +config PHY_CLOCK_GPIO0_OUT + bool "GPIO0 output" + help + Output the internal 50MHz APLL clock on GPIO0 + +config PHY_CLOCK_GPIO16_OUT + bool "GPIO16 output" + help + Output the internal 50MHz APLL clock on GPIO16 + +config PHY_CLOCK_GPIO17_OUT + bool "GPIO17 output (inverted)" + help + Output the internal 50MHz APLL clock on GPIO17 (inverted signal) + +endchoice + +config PHY_CLOCK_MODE + int + default 0 if PHY_CLOCK_GPIO0_IN + default 1 if PHY_CLOCK_GPIO0_OUT + default 2 if PHY_CLOCK_GPIO16_OUT + default 3 if PHY_CLOCK_GPIO17_OUT + config PHY_USE_POWER_PIN bool "Use PHY Power (enable/disable) pin" @@ -35,6 +76,7 @@ config PHY_USE_POWER_PIN config PHY_POWER_PIN int "PHY Power GPIO" default 17 + range 0 33 depends on PHY_USE_POWER_PIN help GPIO number to use for powering on/off the PHY. @@ -42,12 +84,14 @@ config PHY_POWER_PIN config PHY_SMI_MDC_PIN int "SMI MDC Pin" default 23 + range 0 33 help GPIO number to use for SMI clock output MDC to PHY. config PHY_SMI_MDIO_PIN int "SMI MDIO Pin" default 18 + range 0 33 help GPIO number to use for SMI data pin MDIO to/from PHY. diff --git a/examples/ethernet/ethernet/main/ethernet_example_main.c b/examples/ethernet/ethernet/main/ethernet_example_main.c index 8345ab6da4..832be191ee 100644 --- a/examples/ethernet/ethernet/main/ethernet_example_main.c +++ b/examples/ethernet/ethernet/main/ethernet_example_main.c @@ -33,6 +33,10 @@ #include "nvs_flash.h" #include "driver/gpio.h" +#include "soc/emac_ex_reg.h" +#include "driver/periph_ctrl.h" + + #ifdef CONFIG_PHY_LAN8720 #include "eth_phy/phy_lan8720.h" #define DEFAULT_ETHERNET_PHY_CONFIG phy_lan8720_default_ethernet_config @@ -129,6 +133,7 @@ void app_main() config.phy_addr = CONFIG_PHY_ADDRESS; config.gpio_config = eth_gpio_config_rmii; config.tcpip_input = tcpip_adapter_eth_input; + config.clock_mode = CONFIG_PHY_CLOCK_MODE; #ifdef CONFIG_PHY_USE_POWER_PIN /* Replace the default 'power enable' function with an example-specific