mirror of
https://github.com/esphome/esphome.git
synced 2026-09-04 03:56:04 +00:00
[ethernet] Generic and YT8531 PHY over RGMII (gigabit) for ESP32-S31 (#17277)
This commit is contained in:
@@ -126,6 +126,8 @@ ETHERNET_TYPES = {
|
||||
"ENC28J60": EthernetType.ETHERNET_TYPE_ENC28J60,
|
||||
"W6100": EthernetType.ETHERNET_TYPE_W6100,
|
||||
"W6300": EthernetType.ETHERNET_TYPE_W6300,
|
||||
"GENERIC": EthernetType.ETHERNET_TYPE_GENERIC,
|
||||
"YT8531": EthernetType.ETHERNET_TYPE_YT8531,
|
||||
}
|
||||
|
||||
# PHY types that need compile-time defines for conditional compilation
|
||||
@@ -145,6 +147,8 @@ _PHY_TYPE_TO_DEFINE = {
|
||||
"ENC28J60": "USE_ETHERNET_ENC28J60",
|
||||
"W6100": "USE_ETHERNET_W6100",
|
||||
"W6300": "USE_ETHERNET_W6300",
|
||||
"GENERIC": "USE_ETHERNET_GENERIC",
|
||||
"YT8531": "USE_ETHERNET_YT8531",
|
||||
}
|
||||
|
||||
|
||||
@@ -309,6 +313,24 @@ def _validate(config):
|
||||
f"({CORE.target_framework} {CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION]}), "
|
||||
f"'{CONF_INTERRUPT_PIN}' is a required option for [ethernet]."
|
||||
)
|
||||
elif config[CONF_TYPE] in ("GENERIC", "YT8531"):
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32S31,
|
||||
get_esp32_variant,
|
||||
idf_version,
|
||||
)
|
||||
|
||||
eth_type = config[CONF_TYPE]
|
||||
variant = get_esp32_variant()
|
||||
if variant != VARIANT_ESP32S31:
|
||||
raise cv.Invalid(
|
||||
f"The '{eth_type}' (RGMII) PHY is only supported on gigabit-capable "
|
||||
f"variants (ESP32-S31), not {variant}"
|
||||
)
|
||||
if idf_version() < cv.Version(6, 0, 0):
|
||||
raise cv.Invalid(
|
||||
f"The '{eth_type}' (RGMII) PHY requires ESP-IDF 6.0 or newer."
|
||||
)
|
||||
elif config[CONF_TYPE] != "OPENETH":
|
||||
from esphome.components.esp32 import (
|
||||
VARIANT_ESP32,
|
||||
@@ -392,6 +414,23 @@ RMII_SCHEMA = cv.All(
|
||||
cv.only_on([Platform.ESP32]),
|
||||
)
|
||||
|
||||
# Generic IEEE 802.3 PHY over the internal EMAC RGMII interface (e.g. ESP32-S31).
|
||||
# RGMII data pins come from the IDF per-target default config.
|
||||
GENERIC_SCHEMA = cv.All(
|
||||
BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_MDC_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MDIO_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_PHY_ADDR, default=0): cv.int_range(min=0, max=31),
|
||||
cv.Optional(CONF_POWER_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_PHY_REGISTERS): cv.ensure_list(PHY_REGISTER_SCHEMA),
|
||||
}
|
||||
)
|
||||
),
|
||||
cv.only_on([Platform.ESP32]),
|
||||
)
|
||||
|
||||
SPI_SCHEMA = cv.All(
|
||||
BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
@@ -442,6 +481,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
"W6100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
|
||||
"W6300": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
|
||||
"LAN8670": RMII_SCHEMA,
|
||||
"GENERIC": GENERIC_SCHEMA,
|
||||
"YT8531": GENERIC_SCHEMA,
|
||||
},
|
||||
upper=True,
|
||||
),
|
||||
@@ -571,6 +612,20 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
elif config[CONF_TYPE] == "OPENETH":
|
||||
cg.add_define("USE_ETHERNET_OPENETH")
|
||||
add_idf_sdkconfig_option("CONFIG_ETH_USE_OPENETH", True)
|
||||
elif config[CONF_TYPE] in ("GENERIC", "YT8531"):
|
||||
# RGMII data pins come from the IDF default config; set MDC/MDIO + PHY addr.
|
||||
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||
cg.add(var.set_mdio_pin(config[CONF_MDIO_PIN]))
|
||||
if CONF_POWER_PIN in config:
|
||||
cg.add(var.set_power_pin(config[CONF_POWER_PIN]))
|
||||
for register_value in config.get(CONF_PHY_REGISTERS, []):
|
||||
reg = phy_register(
|
||||
register_value.get(CONF_ADDRESS),
|
||||
register_value.get(CONF_VALUE),
|
||||
register_value.get(CONF_PAGE_ID),
|
||||
)
|
||||
cg.add(var.add_phy_register(reg))
|
||||
else:
|
||||
cg.add(var.set_phy_addr(config[CONF_PHY_ADDR]))
|
||||
cg.add(var.set_mdc_pin(config[CONF_MDC_PIN]))
|
||||
|
||||
@@ -86,6 +86,8 @@ enum EthernetType : uint8_t {
|
||||
ETHERNET_TYPE_ENC28J60,
|
||||
ETHERNET_TYPE_W6100,
|
||||
ETHERNET_TYPE_W6300,
|
||||
ETHERNET_TYPE_GENERIC,
|
||||
ETHERNET_TYPE_YT8531,
|
||||
};
|
||||
|
||||
struct ManualIP {
|
||||
@@ -229,6 +231,11 @@ class EthernetComponent final : public Component {
|
||||
#ifdef USE_ETHERNET_KSZ8081
|
||||
/// @brief Set `RMII Reference Clock Select` bit for KSZ8081.
|
||||
void ksz8081_set_clock_reference_(esp_eth_mac_t *mac);
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_YT8531
|
||||
/// @brief Apply YT8531-specific config: re-enable auto-negotiation (disabled on
|
||||
/// reset) and set the RGMII Tx/Rx clock delays needed for reliable data sampling.
|
||||
void yt8531_phy_init_();
|
||||
#endif
|
||||
/// @brief Set arbitratry PHY registers from config.
|
||||
void write_phy_register_(esp_eth_mac_t *mac, PHYRegister register_data);
|
||||
|
||||
@@ -254,9 +254,14 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
esp32_emac_config.smi_mdc_gpio_num = this->mdc_pin_;
|
||||
esp32_emac_config.smi_mdio_gpio_num = this->mdio_pin_;
|
||||
#endif
|
||||
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
||||
esp32_emac_config.clock_config.rmii.clock_gpio =
|
||||
static_cast<decltype(esp32_emac_config.clock_config.rmii.clock_gpio)>(this->clk_pin_);
|
||||
// The RGMII types (GENERIC, YT8531) use the RGMII interface and default GPIO map from
|
||||
// eth_esp32_emac_default_config(); writing the RMII clock config would clobber that
|
||||
// union, so skip the RMII clock override for them.
|
||||
if (this->type_ != ETHERNET_TYPE_GENERIC && this->type_ != ETHERNET_TYPE_YT8531) {
|
||||
esp32_emac_config.clock_config.rmii.clock_mode = this->clk_mode_;
|
||||
esp32_emac_config.clock_config.rmii.clock_gpio =
|
||||
static_cast<decltype(esp32_emac_config.clock_config.rmii.clock_gpio)>(this->clk_pin_);
|
||||
}
|
||||
|
||||
esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config);
|
||||
#endif
|
||||
@@ -319,6 +324,20 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
// GENERIC and YT8531 both use the built-in generic 802.3 PHY driver; YT8531 gets
|
||||
// extra chip-specific tuning applied later in ethernet_lazy_init_().
|
||||
#ifdef USE_ETHERNET_GENERIC
|
||||
case ETHERNET_TYPE_GENERIC:
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_YT8531
|
||||
case ETHERNET_TYPE_YT8531:
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_GENERIC) || defined(USE_ETHERNET_YT8531)
|
||||
this->phy_ = esp_eth_phy_new_generic(&phy_config);
|
||||
break;
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
@@ -363,7 +382,30 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
for (const auto &phy_register : this->phy_registers_) {
|
||||
this->write_phy_register_(mac, phy_register);
|
||||
}
|
||||
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
#ifdef USE_ETHERNET_GENERIC
|
||||
// The generic 802.3 PHY driver only resets the PHY in its init; it never enables
|
||||
// auto-negotiation. A PHY that resets into a forced-speed mode (BMCR auto-nego bit
|
||||
// clear) therefore stays there, and esp_eth_start() skips negotiation because the
|
||||
// driver cached auto_nego_en=false at install time. Force auto-negotiation on here
|
||||
// (which also updates that cached state) so esp_eth_start() restarts a proper
|
||||
// negotiation. (YT8531 does this as part of its own chip-specific init below.)
|
||||
if (this->type_ == ETHERNET_TYPE_GENERIC) {
|
||||
bool autoneg_enable = true;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_AUTONEGO, &autoneg_enable);
|
||||
ESPHL_ERROR_CHECK(err, "Enable auto-negotiation failed");
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_YT8531
|
||||
if (this->type_ == ETHERNET_TYPE_YT8531) {
|
||||
this->yt8531_phy_init_();
|
||||
if (this->is_failed())
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif // ESP_IDF_VERSION >= 6.0.0
|
||||
#endif // !USE_ETHERNET_SPI
|
||||
|
||||
// use ESP internal eth mac
|
||||
uint8_t mac_addr[6];
|
||||
@@ -486,6 +528,16 @@ void EthernetComponent::dump_config() {
|
||||
eth_type = "LAN8670";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_GENERIC
|
||||
case ETHERNET_TYPE_GENERIC:
|
||||
eth_type = "Generic (RGMII)";
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_YT8531
|
||||
case ETHERNET_TYPE_YT8531:
|
||||
eth_type = "YT8531 (RGMII)";
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
eth_type = "Unknown";
|
||||
@@ -782,6 +834,19 @@ void EthernetComponent::dump_connect_params_() {
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
uint16_t link_speed = 10;
|
||||
switch (this->get_link_speed()) {
|
||||
case ETH_SPEED_100M:
|
||||
link_speed = 100;
|
||||
break;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)
|
||||
case ETH_SPEED_1000M:
|
||||
link_speed = 1000;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
@@ -796,7 +861,7 @@ void EthernetComponent::dump_connect_params_() {
|
||||
network::IPAddress(&ip.netmask).str_to(subnet_buf), network::IPAddress(&ip.gw).str_to(gateway_buf),
|
||||
network::IPAddress(dns_ip1).str_to(dns1_buf), network::IPAddress(dns_ip2).str_to(dns2_buf),
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf),
|
||||
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), this->get_link_speed() == ETH_SPEED_100M ? 100 : 10);
|
||||
YESNO(this->get_duplex_mode() == ETH_DUPLEX_FULL), link_speed);
|
||||
|
||||
#if USE_NETWORK_IPV6
|
||||
struct esp_ip6_addr if_ip6s[CONFIG_LWIP_IPV6_NUM_ADDRESSES];
|
||||
@@ -958,6 +1023,50 @@ void EthernetComponent::write_phy_register_(esp_eth_mac_t *mac, PHYRegister regi
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_ETHERNET_YT8531
|
||||
void EthernetComponent::yt8531_phy_init_() {
|
||||
esp_err_t err;
|
||||
|
||||
// The YT8531 disables auto-negotiation on hardware reset (undocumented behavior), and the
|
||||
// generic 802.3 driver only resets the PHY, so re-enable it (this also updates the driver's
|
||||
// cached auto-nego state used by esp_eth_start()).
|
||||
bool autoneg_enable = true;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_S_AUTONEGO, &autoneg_enable);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 enable auto-negotiation failed");
|
||||
|
||||
// RGMII needs ~2 ns Tx and Rx clock delays for reliable data sampling. These are set through
|
||||
// the YT8531 extended-register interface: write the ext-register address to 0x1E, then
|
||||
// read/modify/write its value via 0x1F.
|
||||
esp_eth_phy_reg_rw_data_t phy_reg;
|
||||
uint32_t reg_val;
|
||||
phy_reg.reg_value_p = ®_val;
|
||||
|
||||
// RX ~2 ns coarse delay: EXT_CHIP_CONFIG (0xA001), set rxc_dly_en (bit 8).
|
||||
reg_val = 0xA001;
|
||||
phy_reg.reg_addr = 0x1E;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 select Chip_Config failed");
|
||||
phy_reg.reg_addr = 0x1F;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_READ_PHY_REG, &phy_reg);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 read Chip_Config failed");
|
||||
reg_val |= (1U << 8);
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 write Chip_Config failed");
|
||||
|
||||
// TX ~2 ns delay: EXT_RGMII_CONFIG1 (0xA003), tx_delay_sel[3:0] and tx_delay_sel_fe[7:4] = 13.
|
||||
reg_val = 0xA003;
|
||||
phy_reg.reg_addr = 0x1E;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 select RGMII_Config1 failed");
|
||||
phy_reg.reg_addr = 0x1F;
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_READ_PHY_REG, &phy_reg);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 read RGMII_Config1 failed");
|
||||
reg_val = (reg_val & ~0x00FFU) | (13U << 4) | (13U << 0);
|
||||
err = esp_eth_ioctl(this->eth_handle_, ETH_CMD_WRITE_PHY_REG, &phy_reg);
|
||||
ESPHL_ERROR_CHECK(err, "YT8531 write RGMII_Config1 failed");
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
@@ -327,6 +327,8 @@
|
||||
#define USE_ETHERNET_JL1101
|
||||
#define USE_ETHERNET_KSZ8081
|
||||
#define USE_ETHERNET_LAN8670
|
||||
#define USE_ETHERNET_GENERIC
|
||||
#define USE_ETHERNET_YT8531
|
||||
#define USE_ETHERNET_SPI
|
||||
#define USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
#define USE_ETHERNET_OPENETH
|
||||
|
||||
Reference in New Issue
Block a user