diff --git a/components/esp_eth/Kconfig b/components/esp_eth/Kconfig index 78f597ab0e..ca118a8460 100644 --- a/components/esp_eth/Kconfig +++ b/components/esp_eth/Kconfig @@ -21,9 +21,6 @@ menu "Ethernet" config ETH_PHY_INTERFACE_RMII bool "Reduced Media Independent Interface (RMII)" - - config ETH_PHY_INTERFACE_MII - bool "Media Independent Interface (MII)" endchoice if ETH_PHY_INTERFACE_RMII diff --git a/components/esp_eth/include/esp_eth_mac.h b/components/esp_eth/include/esp_eth_mac.h index 042c369fc6..c725573770 100644 --- a/components/esp_eth/include/esp_eth_mac.h +++ b/components/esp_eth/include/esp_eth_mac.h @@ -287,17 +287,98 @@ struct esp_eth_mac_s { esp_err_t (*del)(esp_eth_mac_t *mac); }; +/** + * @brief RMII Clock Mode Options + * + */ +typedef enum { + /** + * @brief Default values configured using Kconfig are going to be used when "Default" selected. + * + */ + EMAC_CLK_DEFAULT, + + /** + * @brief Input RMII Clock from external. EMAC Clock GPIO number needs to be configured when this option is selected. + * + * @note MAC will get RMII clock from outside. Note that ESP32 only supports GPIO0 to input the RMII clock. + * + */ + EMAC_CLK_EXT_IN, + + /** + * @brief Output RMII Clock from internal APLL Clock. EMAC Clock GPIO number needs to be configured when this option is selected. + * + */ + EMAC_CLK_OUT +} emac_rmii_clock_mode_t; + +/** + * @brief RMII Clock GPIO number Options + * + */ +typedef enum { + /** + * @brief MAC will get RMII clock from outside at this GPIO. + * + * @note ESP32 only supports GPIO0 to input the RMII clock. + * + */ + EMAC_CLK_IN_GPIO = 0, + + /** + * @brief Output RMII Clock from internal APLL Clock available at GPIO0 + * + * @note GPIO0 can be set to output a pre-divided PLL clock (test only!). Enabling this option will configure GPIO0 to output a 50MHz clock. + * In fact this clock doesn’t have directly relationship with EMAC peripheral. Sometimes this clock won’t work well with your PHY chip. + * You might need to add some extra devices after GPIO0 (e.g. inverter). Note that outputting RMII clock on GPIO0 is an experimental practice. + * If you want the Ethernet to work with WiFi, don’t select GPIO0 output mode for stability. + * + */ + EMAC_APPL_CLK_OUT_GPIO = 0, + + /** + * @brief Output RMII Clock from internal APLL Clock available at GPIO16 + * + */ + EMAC_CLK_OUT_GPIO = 16, + + /** + * @brief Inverted Output RMII Clock from internal APLL Clock available at GPIO17 + * + */ + EMAC_CLK_OUT_180_GPIO = 17 +} emac_rmii_clock_gpio_t; + +/** + * @brief Ethernet MAC Clock Configuration + * + */ +typedef union { + struct { + // MII interface is not fully implemented... + // Reserved for GPIO number, clock source, etc. in MII mode + } mii; /*!< EMAC MII Clock Configuration */ + struct { + emac_rmii_clock_mode_t clock_mode; /*!< RMII Clock Mode Configuration */ + emac_rmii_clock_gpio_t clock_gpio; /*!< RMII Clock GPIO Configuration */ + } rmii; /*!< EMAC RMII Clock Configuration */ +} eth_mac_clock_config_t; + + /** * @brief Configuration of Ethernet MAC object * */ typedef struct { - uint32_t sw_reset_timeout_ms; /*!< Software reset timeout value (Unit: ms) */ - uint32_t rx_task_stack_size; /*!< Stack size of the receive task */ - uint32_t rx_task_prio; /*!< Priority of the receive task */ - int smi_mdc_gpio_num; /*!< SMI MDC GPIO number, set to -1 could bypass the SMI GPIO configuration */ - int smi_mdio_gpio_num; /*!< SMI MDIO GPIO number, set to -1 could bypass the SMI GPIO configuration */ - uint32_t flags; /*!< Flags that specify extra capability for mac driver */ + uint32_t sw_reset_timeout_ms; /*!< Software reset timeout value (Unit: ms) */ + uint32_t rx_task_stack_size; /*!< Stack size of the receive task */ + uint32_t rx_task_prio; /*!< Priority of the receive task */ + int smi_mdc_gpio_num; /*!< SMI MDC GPIO number, set to -1 could bypass the SMI GPIO configuration */ + int smi_mdio_gpio_num; /*!< SMI MDIO GPIO number, set to -1 could bypass the SMI GPIO configuration */ + uint32_t flags; /*!< Flags that specify extra capability for mac driver */ + eth_data_interface_t interface; /*!< EMAC Data interface to PHY (MII/RMII) */ + eth_mac_clock_config_t clock_config; /*!< EMAC Interface clock configuration */ } eth_mac_config_t; #define ETH_MAC_FLAG_WORK_WITH_CACHE_DISABLE (1 << 0) /*!< MAC driver can work when cache is disabled */ @@ -307,14 +388,16 @@ typedef struct { * @brief Default configuration for Ethernet MAC object * */ -#define ETH_MAC_DEFAULT_CONFIG() \ - { \ - .sw_reset_timeout_ms = 100, \ - .rx_task_stack_size = 4096, \ - .rx_task_prio = 15, \ - .smi_mdc_gpio_num = 23, \ - .smi_mdio_gpio_num = 18, \ - .flags = 0, \ +#define ETH_MAC_DEFAULT_CONFIG() \ + { \ + .sw_reset_timeout_ms = 100, \ + .rx_task_stack_size = 4096, \ + .rx_task_prio = 15, \ + .smi_mdc_gpio_num = 23, \ + .smi_mdio_gpio_num = 18, \ + .flags = 0, \ + .interface = EMAC_DATA_INTERFACE_RMII, \ + .clock_config.rmii.clock_mode = EMAC_CLK_DEFAULT, \ } #if CONFIG_ETH_USE_ESP32_EMAC diff --git a/components/esp_eth/src/esp_eth_mac_esp.c b/components/esp_eth/src/esp_eth_mac_esp.c index 6f49cbaebb..98d08e3429 100644 --- a/components/esp_eth/src/esp_eth_mac_esp.c +++ b/components/esp_eth/src/esp_eth_mac_esp.c @@ -57,6 +57,8 @@ typedef struct { uint32_t flow_control_low_water_mark; int smi_mdc_gpio_num; int smi_mdio_gpio_num; + eth_data_interface_t interface; + eth_mac_clock_config_t clock_config; uint8_t addr[6]; uint8_t *rx_buf[CONFIG_ETH_DMA_RX_BUFFER_NUM]; uint8_t *tx_buf[CONFIG_ETH_DMA_TX_BUFFER_NUM]; @@ -303,7 +305,6 @@ static void emac_esp32_init_smi_gpio(emac_esp32_t *emac) } } -#if CONFIG_ETH_RMII_CLK_OUTPUT static void emac_config_apll_clock(void) { /* apll_freq = xtal_freq * (4 + sdm2 + sdm1/256 + sdm0/65536)/((o_div + 2) * 2) */ @@ -329,7 +330,6 @@ static void emac_config_apll_clock(void) break; } } -#endif static esp_err_t emac_esp32_init(esp_eth_mac_t *mac) { @@ -340,40 +340,38 @@ static esp_err_t emac_esp32_init(esp_eth_mac_t *mac) periph_module_enable(PERIPH_EMAC_MODULE); /* init clock, config gpio, etc */ -#if CONFIG_ETH_PHY_INTERFACE_MII - /* MII interface GPIO initialization */ - emac_hal_iomux_init_mii(); - /* Enable MII clock */ - emac_ll_clock_enable_mii(emac->hal.ext_regs); -#elif CONFIG_ETH_PHY_INTERFACE_RMII - /* RMII interface GPIO initialization */ - emac_hal_iomux_init_rmii(); - /* If ref_clk is configured as input */ -#if CONFIG_ETH_RMII_CLK_INPUT -#if CONFIG_ETH_RMII_CLK_IN_GPIO == 0 - emac_hal_iomux_rmii_clk_input(); -#else -#error "ESP32 EMAC only support input RMII clock to GPIO0" -#endif // CONFIG_ETH_RMII_CLK_IN_GPIO == 0 - emac_ll_clock_enable_rmii_input(emac->hal.ext_regs); -#endif // CONFIG_ETH_RMII_CLK_INPUT - - /* If ref_clk is configured as output */ -#if CONFIG_ETH_RMII_CLK_OUTPUT -#if CONFIG_ETH_RMII_CLK_OUTPUT_GPIO0 - emac_hal_iomux_rmii_clk_ouput(0); - /* Choose the APLL clock1 to output on specific GPIO */ - REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 6); -#elif CONFIG_ETH_RMII_CLK_OUT_GPIO == 16 - emac_hal_iomux_rmii_clk_ouput(16); -#elif CONFIG_ETH_RMII_CLK_OUT_GPIO == 17 - emac_hal_iomux_rmii_clk_ouput(17); -#endif // CONFIG_ETH_RMII_CLK_OUTPUT_GPIO0 - /* Enable RMII clock */ - emac_ll_clock_enable_rmii_output(emac->hal.ext_regs); - emac_config_apll_clock(); -#endif // CONFIG_ETH_RMII_CLK_OUTPUT -#endif // CONFIG_ETH_PHY_INTERFACE_MII + if (emac->interface == EMAC_DATA_INTERFACE_MII) { + /* MII interface GPIO initialization */ + emac_hal_iomux_init_mii(); + /* Enable MII clock */ + emac_ll_clock_enable_mii(emac->hal.ext_regs); + } else if (emac->interface == EMAC_DATA_INTERFACE_RMII) { + /* RMII interface GPIO initialization */ + emac_hal_iomux_init_rmii(); + /* If ref_clk is configured as input */ + if (emac->clock_config.rmii.clock_mode == EMAC_CLK_EXT_IN) { + ESP_GOTO_ON_FALSE(emac->clock_config.rmii.clock_gpio == EMAC_CLK_IN_GPIO, + ESP_ERR_INVALID_ARG, err, TAG, "ESP32 EMAC only support input RMII clock to GPIO0"); + emac_hal_iomux_rmii_clk_input(); + emac_ll_clock_enable_rmii_input(emac->hal.ext_regs); + } else if (emac->clock_config.rmii.clock_mode == EMAC_CLK_OUT) { + ESP_GOTO_ON_FALSE(emac->clock_config.rmii.clock_gpio == EMAC_APPL_CLK_OUT_GPIO || + emac->clock_config.rmii.clock_gpio == EMAC_CLK_OUT_GPIO || + emac->clock_config.rmii.clock_gpio == EMAC_CLK_OUT_180_GPIO, + ESP_ERR_INVALID_ARG, err, TAG, "invalid EMAC clock output GPIO"); + emac_hal_iomux_rmii_clk_ouput(emac->clock_config.rmii.clock_gpio); + if (emac->clock_config.rmii.clock_gpio == EMAC_APPL_CLK_OUT_GPIO) { + REG_SET_FIELD(PIN_CTRL, CLK_OUT1, 6); + } + /* Enable RMII clock */ + emac_ll_clock_enable_rmii_output(emac->hal.ext_regs); + emac_config_apll_clock(); + } else { + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid EMAC clock mode"); + } + } else { + ESP_GOTO_ON_FALSE(false, ESP_ERR_INVALID_ARG, err, TAG, "invalid EMAC interface"); + } /* init gpio used by smi interface */ emac_esp32_init_smi_gpio(emac); @@ -516,6 +514,34 @@ esp_eth_mac_t *esp_eth_mac_new_esp32(const eth_mac_config_t *config) emac->sw_reset_timeout_ms = config->sw_reset_timeout_ms; emac->smi_mdc_gpio_num = config->smi_mdc_gpio_num; emac->smi_mdio_gpio_num = config->smi_mdio_gpio_num; + ESP_GOTO_ON_FALSE(config->interface == EMAC_DATA_INTERFACE_MII || config->interface == EMAC_DATA_INTERFACE_RMII, + NULL, err, TAG, "invalid EMAC Data Interface"); + emac->interface = config->interface; + + if (emac->interface == EMAC_DATA_INTERFACE_RMII) { + if (config->clock_config.rmii.clock_mode == EMAC_CLK_DEFAULT) { +#if CONFIG_ETH_RMII_CLK_INPUT && CONFIG_ETH_RMII_CLK_IN_GPIO == 0 + emac->clock_config.rmii.clock_mode = EMAC_CLK_EXT_IN; + emac->clock_config.rmii.clock_gpio = CONFIG_ETH_RMII_CLK_IN_GPIO; +#else +#error "ESP32 EMAC only support input RMII clock to GPIO0" +#endif // CONFIG_ETH_RMII_CLK_INPUT && CONFIG_ETH_RMII_CLK_IN_GPIO == 0 + + /* If ref_clk is configured as output */ +#if CONFIG_ETH_RMII_CLK_OUTPUT + emac->clock_config.rmii.clock_mode = EMAC_CLK_OUT; +#if CONFIG_ETH_RMII_CLK_OUTPUT_GPIO0 + emac->clock_config.rmii.clock_gpio = 0; +#elif CONFIG_ETH_RMII_CLK_OUT_GPIO + emac->clock_config.rmii.clock_gpio = CONFIG_ETH_RMII_CLK_OUT_GPIO; +#endif // CONFIG_ETH_RMII_CLK_OUTPUT_GPIO0 +#endif // CONFIG_ETH_RMII_CLK_OUTPUT + } else { + emac->clock_config = config->clock_config; + } + } else if (emac->interface == EMAC_DATA_INTERFACE_MII) { + emac->clock_config = config->clock_config; + } emac->flow_control_high_water_mark = FLOW_CONTROL_HIGH_WATER_MARK; emac->flow_control_low_water_mark = FLOW_CONTROL_LOW_WATER_MARK; emac->parent.set_mediator = emac_esp32_set_mediator; diff --git a/components/hal/include/hal/eth_types.h b/components/hal/include/hal/eth_types.h index 724948453d..219618950a 100644 --- a/components/hal/include/hal/eth_types.h +++ b/components/hal/include/hal/eth_types.h @@ -24,8 +24,8 @@ * */ typedef enum { - EMAC_INTERFACE_MII, /*!< Media Independent Interface */ - EMAC_INTERFACE_RMII /*!< Reduced Media Independent Interface */ + EMAC_DATA_INTERFACE_RMII, /*!< Reduced Media Independent Interface */ + EMAC_DATA_INTERFACE_MII, /*!< Media Independent Interface */ } eth_data_interface_t; /** diff --git a/docs/en/api-reference/network/esp_eth.rst b/docs/en/api-reference/network/esp_eth.rst index 758de0ef64..07b51867b3 100644 --- a/docs/en/api-reference/network/esp_eth.rst +++ b/docs/en/api-reference/network/esp_eth.rst @@ -130,6 +130,9 @@ Ethernet driver is composed of two parts: MAC and PHY. The communication between * Some PHY chip uses an external connected 50MHz crystal oscillator or other clock source, which can also be used as the ``REF_CLK`` for MAC side (as seen the option *b* in the picture). In this case, you still need to select ``CONFIG_ETH_RMII_CLK_INPUT`` in :ref:`CONFIG_ETH_RMII_CLK_MODE`. * Some EMAC controller can generate the ``REF_CLK`` using its internal high precision PLL (as seen the option *c* in the picture). In this case, you should select ``CONFIG_ETH_RMII_CLK_OUTPUT`` in :ref:`CONFIG_ETH_RMII_CLK_MODE`. + .. note:: + ``REF_CLK`` is configured via Project Configuration as described above by default. However, it can be overwritten from user application code by appropriately setting :cpp:member:`interface` and :cpp:member:`clock_config` members of :cpp:class:`eth_mac_config_t` structure. See :cpp:enum:`emac_rmii_clock_mode_t` and :cpp:enum:`emac_rmii_clock_gpio_t` for more details. + .. warning:: If the RMII clock mode is selected to ``CONFIG_ETH_RMII_CLK_OUTPUT``, then ``GPIO0`` can be used to output the ``REF_CLK`` signal. See :ref:`CONFIG_ETH_RMII_CLK_OUTPUT_GPIO0` for more information. What's more, if you're not using PSRAM in your design, GPIO16 and GPIO17 are also available to output the reference clock. See :ref:`CONFIG_ETH_RMII_CLK_OUT_GPIO` for more information. @@ -160,6 +163,8 @@ Configuration for MAC is described in :cpp:class:`eth_mac_config_t`, including: * :cpp:member:`sw_reset_timeout_ms`: software reset timeout value, in milliseconds, typically MAC reset should be finished within 100ms. * :cpp:member:`rx_task_stack_size` and :cpp:member:`rx_task_prio`: the MAC driver creates a dedicated task to process incoming packets, these two parameters are used to set the stack size and priority of the task. * :cpp:member:`smi_mdc_gpio_num` and :cpp:member:`smi_mdio_gpio_num`: the GPIO number used to connect the SMI signals. +* :cpp:member:`interface`: configuration of MAC Data interface to PHY (MII/RMII). +* :cpp:member:`clock_config`: configuration of EMAC Interface clock (``REF_CLK`` mode and GPIO number in case of RMII). * :cpp:member:`flags`: specifying extra features that the MAC driver should have, it could be useful in some special situations. The value of this field can be OR'd with macros prefixed with ``ETH_MAC_FLAG_``. For example, if the MAC driver should work when cache is disabled, then you should configure this field with :c:macro:`ETH_MAC_FLAG_WORK_WITH_CACHE_DISABLE`. Configuration for PHY is described in :cpp:class:`eth_phy_config_t`, including: @@ -199,6 +204,24 @@ Ethernet driver is implemented in an Object-Oriented style. Any operation on MAC Care should be taken, when creating MAC and PHY instance for SPI-Ethernet modules (e.g. DM9051), the constructor function must have the same suffix (e.g. `esp_eth_mac_new_dm9051` and `esp_eth_phy_new_dm9051`). This is because we don't have other choices but the integrated PHY. Besides that, we have to create an SPI device handle firstly and then pass it to the MAC constructor function. More instructions on creating SPI device handle, please refer to :doc:`SPI Master <../peripherals/spi_master>`. +Optional Runtime MAC Clock Configuration +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +EMAC ``REF_CLK`` can be optionally configured from user application code. + +.. highlight:: c + +:: + + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); // apply default MAC configuration + + // ... + + mac_config.interface = EMAC_DATA_INTERFACE_RMII; // alter EMAC Data Interface + mac_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT; // select EMAC REF_CLK mode + mac_config.clock_config.rmii.clock_gpio = EMAC_CLK_OUT_GPIO; // select GPIO number used to input/output EMAC REF_CLK + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&mac_config); // create MAC instance + Install Driver --------------