From 5b023f9369e3ce84e4f8b5717928bd9bbc1e735c Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 22 Oct 2025 16:37:50 -1000 Subject: [PATCH] [ethernet] Add RMII GPIO pin conflict validation (#11488) --- esphome/components/ethernet/__init__.py | 84 ++++++++++++++++++- tests/components/ethernet/common-dp83848.yaml | 4 +- tests/components/ethernet/common-ip101.yaml | 4 +- tests/components/ethernet/common-jl1101.yaml | 4 +- tests/components/ethernet/common-ksz8081.yaml | 4 +- .../ethernet/common-ksz8081rna.yaml | 4 +- tests/components/ethernet/common-lan8670.yaml | 4 +- tests/components/ethernet/common-lan8720.yaml | 4 +- tests/components/ethernet/common-rtl8201.yaml | 4 +- tests/components/ethernet_info/common.yaml | 4 +- 10 files changed, 98 insertions(+), 22 deletions(-) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 7384bb26d3..77f70a3630 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -32,6 +32,7 @@ from esphome.const import ( CONF_MISO_PIN, CONF_MODE, CONF_MOSI_PIN, + CONF_NUMBER, CONF_PAGE_ID, CONF_PIN, CONF_POLLING_INTERVAL, @@ -52,12 +53,36 @@ from esphome.core import ( coroutine_with_priority, ) import esphome.final_validate as fv +from esphome.types import ConfigType CONFLICTS_WITH = ["wifi"] DEPENDENCIES = ["esp32"] AUTO_LOAD = ["network"] LOGGER = logging.getLogger(__name__) +# RMII pins that are hardcoded on ESP32 classic and cannot be changed +# These pins are used by the internal Ethernet MAC when using RMII PHYs +ESP32_RMII_FIXED_PINS = { + 19: "EMAC_TXD0", + 21: "EMAC_TX_EN", + 22: "EMAC_TXD1", + 25: "EMAC_RXD0", + 26: "EMAC_RXD1", + 27: "EMAC_RX_CRS_DV", +} + +# RMII default pins for ESP32-P4 +# These are the default pins used by ESP-IDF and are configurable in principle, +# but ESPHome's ethernet component currently has no way to change them +ESP32P4_RMII_DEFAULT_PINS = { + 34: "EMAC_TXD0", + 35: "EMAC_TXD1", + 28: "EMAC_RX_CRS_DV", + 29: "EMAC_RXD0", + 30: "EMAC_RXD1", + 49: "EMAC_TX_EN", +} + ethernet_ns = cg.esphome_ns.namespace("ethernet") PHYRegister = ethernet_ns.struct("PHYRegister") CONF_PHY_ADDR = "phy_addr" @@ -273,7 +298,7 @@ CONFIG_SCHEMA = cv.All( ) -def _final_validate(config): +def _final_validate_spi(config): if config[CONF_TYPE] not in SPI_ETHERNET_TYPES: return if spi_configs := fv.full_config.get().get(CONF_SPI): @@ -292,9 +317,6 @@ def _final_validate(config): ) -FINAL_VALIDATE_SCHEMA = _final_validate - - def manual_ip(config): return cg.StructInitializer( ManualIP, @@ -383,3 +405,57 @@ async def to_code(config): if CORE.using_arduino: cg.add_library("WiFi", None) + + +def _final_validate_rmii_pins(config: ConfigType) -> None: + """Validate that RMII pins are not used by other components.""" + # Only validate for RMII-based PHYs on ESP32/ESP32P4 + if config[CONF_TYPE] in SPI_ETHERNET_TYPES or config[CONF_TYPE] == "OPENETH": + return # SPI and OPENETH don't use RMII + + variant = get_esp32_variant() + if variant == VARIANT_ESP32: + rmii_pins = ESP32_RMII_FIXED_PINS + is_configurable = False + elif variant == VARIANT_ESP32P4: + rmii_pins = ESP32P4_RMII_DEFAULT_PINS + is_configurable = True + else: + return # No RMII validation needed for other variants + + # Check all used pins against RMII reserved pins + for pin_list in pins.PIN_SCHEMA_REGISTRY.pins_used.values(): + for pin_path, _, pin_config in pin_list: + pin_num = pin_config.get(CONF_NUMBER) + if pin_num not in rmii_pins: + continue + # Found a conflict - show helpful error message + pin_function = rmii_pins[pin_num] + component_path = ".".join(str(p) for p in pin_path) + if is_configurable: + error_msg = ( + f"GPIO{pin_num} is used by Ethernet RMII " + f"({pin_function}) with the current default " + f"configuration. This conflicts with '{component_path}'. " + f"Please choose a different GPIO pin for " + f"'{component_path}'." + ) + else: + error_msg = ( + f"GPIO{pin_num} is reserved for Ethernet RMII " + f"({pin_function}) and cannot be used. This pin is " + f"hardcoded by ESP-IDF and cannot be changed when using " + f"RMII Ethernet PHYs. Please choose a different GPIO pin " + f"for '{component_path}'." + ) + raise cv.Invalid(error_msg, path=pin_path) + + +def _final_validate(config: ConfigType) -> ConfigType: + """Final validation for Ethernet component.""" + _final_validate_spi(config) + _final_validate_rmii_pins(config) + return config + + +FINAL_VALIDATE_SCHEMA = _final_validate diff --git a/tests/components/ethernet/common-dp83848.yaml b/tests/components/ethernet/common-dp83848.yaml index 7cedfeaf08..f9069c5fb9 100644 --- a/tests/components/ethernet/common-dp83848.yaml +++ b/tests/components/ethernet/common-dp83848.yaml @@ -1,12 +1,12 @@ ethernet: type: DP83848 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-ip101.yaml b/tests/components/ethernet/common-ip101.yaml index 2dece15171..cea7a5cc35 100644 --- a/tests/components/ethernet/common-ip101.yaml +++ b/tests/components/ethernet/common-ip101.yaml @@ -1,12 +1,12 @@ ethernet: type: IP101 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-jl1101.yaml b/tests/components/ethernet/common-jl1101.yaml index b6ea884102..7b0a2dfdc4 100644 --- a/tests/components/ethernet/common-jl1101.yaml +++ b/tests/components/ethernet/common-jl1101.yaml @@ -1,12 +1,12 @@ ethernet: type: JL1101 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-ksz8081.yaml b/tests/components/ethernet/common-ksz8081.yaml index f70d42319e..65541832c2 100644 --- a/tests/components/ethernet/common-ksz8081.yaml +++ b/tests/components/ethernet/common-ksz8081.yaml @@ -1,12 +1,12 @@ ethernet: type: KSZ8081 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-ksz8081rna.yaml b/tests/components/ethernet/common-ksz8081rna.yaml index 18efdae0e1..f04cba15b2 100644 --- a/tests/components/ethernet/common-ksz8081rna.yaml +++ b/tests/components/ethernet/common-ksz8081rna.yaml @@ -1,12 +1,12 @@ ethernet: type: KSZ8081RNA mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-lan8670.yaml b/tests/components/ethernet/common-lan8670.yaml index ec2f24273d..fb751ebd23 100644 --- a/tests/components/ethernet/common-lan8670.yaml +++ b/tests/components/ethernet/common-lan8670.yaml @@ -1,12 +1,12 @@ ethernet: type: LAN8670 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-lan8720.yaml b/tests/components/ethernet/common-lan8720.yaml index 204c1d9210..838d57df28 100644 --- a/tests/components/ethernet/common-lan8720.yaml +++ b/tests/components/ethernet/common-lan8720.yaml @@ -1,12 +1,12 @@ ethernet: type: LAN8720 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet/common-rtl8201.yaml b/tests/components/ethernet/common-rtl8201.yaml index 8b9f2b86f2..0e7cbe73c6 100644 --- a/tests/components/ethernet/common-rtl8201.yaml +++ b/tests/components/ethernet/common-rtl8201.yaml @@ -1,12 +1,12 @@ ethernet: type: RTL8201 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1 diff --git a/tests/components/ethernet_info/common.yaml b/tests/components/ethernet_info/common.yaml index f45f345316..b720521d10 100644 --- a/tests/components/ethernet_info/common.yaml +++ b/tests/components/ethernet_info/common.yaml @@ -1,12 +1,12 @@ ethernet: type: LAN8720 mdc_pin: 23 - mdio_pin: 25 + mdio_pin: 32 clk: pin: 0 mode: CLK_EXT_IN phy_addr: 0 - power_pin: 26 + power_pin: 33 manual_ip: static_ip: 192.168.178.56 gateway: 192.168.178.1