From 6838c57679682c26ef7cfe96a76276acdb1c567b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fin=20Maa=C3=9F?= Date: Tue, 10 Jun 2025 13:55:47 +0200 Subject: [PATCH] drivers: ethernet: phy_mii: add support for disabling auto-neg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds support for disabling auto-negotiation. Signed-off-by: Fin Maaß --- drivers/ethernet/phy/phy_mii.c | 94 ++++++++++++++++++++++++---------- drivers/ethernet/phy/phy_mii.h | 68 ++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 27 deletions(-) diff --git a/drivers/ethernet/phy/phy_mii.c b/drivers/ethernet/phy/phy_mii.c index e67226263d1..6b145b06eb6 100644 --- a/drivers/ethernet/phy/phy_mii.c +++ b/drivers/ethernet/phy/phy_mii.c @@ -194,20 +194,38 @@ static int update_link_state(const struct device *dev) } } - /** - * Perform auto-negotiation sequence. - */ - LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence", - cfg->phy_addr); - - /* Configure and start auto-negotiation process */ if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) { return -EIO; } - bmcr_reg |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART; + if (!(bmcr_reg & MII_BMCR_AUTONEG_ENABLE)) { + enum phy_link_speed new_speed = phy_mii_get_link_speed_bmcr_reg(dev, bmcr_reg); + + data->restart_autoneg = false; + + if ((data->state.speed != new_speed) && data->state.is_up) { + data->state.speed = new_speed; + + LOG_INF("PHY (%d) Link speed %s Mb, %s duplex", + cfg->phy_addr, + PHY_LINK_IS_SPEED_1000M(data->state.speed) ? "1000" : + (PHY_LINK_IS_SPEED_100M(data->state.speed) ? "100" : "10"), + PHY_LINK_IS_FULL_DUPLEX(data->state.speed) ? "full" : "half"); + + return 0; + } + return -EAGAIN; + } + + /** + * Perform auto-negotiation sequence. + */ + LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence", cfg->phy_addr); + + bmcr_reg |= MII_BMCR_AUTONEG_RESTART; bmcr_reg &= ~MII_BMCR_ISOLATE; /* Don't isolate the PHY */ + /* Configure and start auto-negotiation process */ if (phy_mii_reg_write(dev, MII_BMCR, bmcr_reg) < 0) { return -EIO; } @@ -366,38 +384,60 @@ static int phy_mii_cfg_link(const struct device *dev, enum phy_link_speed adv_sp k_sem_take(&data->sem, K_FOREVER); - if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) { - ret = -EIO; - goto cfg_link_end; - } + if ((flags & PHY_FLAG_AUTO_NEGOTIATION_DISABLED) != 0U) { + /* If auto-negotiation is disabled, only one speed can be selected. + * If gigabit is not supported, this speed must not be 1000M. + */ + if (!data->gigabit_supported && PHY_LINK_IS_SPEED_1000M(adv_speeds)) { + LOG_ERR("PHY (%d) Gigabit not supported, can't configure link", + cfg->phy_addr); + ret = -ENOTSUP; + goto cfg_link_end; + } - if (data->gigabit_supported) { - ret = phy_mii_set_c1kt_reg(dev, adv_speeds); + ret = phy_mii_set_bmcr_reg_autoneg_disabled(dev, adv_speeds); if (ret == -EALREADY) { - /* If the C1KT register is already set, we don't need to do anything */ + /* If the BMCR register is already set, we don't need to do anything */ ret = 0; } else if (ret < 0) { goto cfg_link_end; } else { data->restart_autoneg = true; } - } - - ret = phy_mii_set_anar_reg(dev, adv_speeds); - if (ret == -EALREADY) { - /* If the ANAR register is already set, we don't need to do anything */ - ret = 0; - } else if (ret < 0) { - goto cfg_link_end; } else { - data->restart_autoneg = true; - } + ret = phy_mii_set_anar_reg(dev, adv_speeds); + if (ret == -EALREADY) { + /* If the ANAR register is already set, we don't need to do anything */ + ret = 0; + } else if (ret < 0) { + goto cfg_link_end; + } else { + data->restart_autoneg = true; + } - if ((bmcr_reg & MII_BMCR_AUTONEG_ENABLE) == 0U) { - if (phy_mii_reg_write(dev, MII_BMCR, bmcr_reg | MII_BMCR_AUTONEG_ENABLE) < 0) { + if (data->gigabit_supported) { + ret = phy_mii_set_c1kt_reg(dev, adv_speeds); + if (ret == -EALREADY) { + /* If the C1KT register is already set, we don't need to do anything */ + ret = 0; + } else if (ret < 0) { + goto cfg_link_end; + } else { + data->restart_autoneg = true; + } + } + + if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) { ret = -EIO; goto cfg_link_end; } + + if ((bmcr_reg & MII_BMCR_AUTONEG_ENABLE) == 0U) { + if (phy_mii_reg_write(dev, MII_BMCR, bmcr_reg | MII_BMCR_AUTONEG_ENABLE) < 0) { + ret = -EIO; + goto cfg_link_end; + } + } } if (data->restart_autoneg && data->state.is_up) { diff --git a/drivers/ethernet/phy/phy_mii.h b/drivers/ethernet/phy/phy_mii.h index 5b56b4668b3..751559ac274 100644 --- a/drivers/ethernet/phy/phy_mii.h +++ b/drivers/ethernet/phy/phy_mii.h @@ -60,4 +60,72 @@ static inline int phy_mii_set_c1kt_reg(const struct device *dev, enum phy_link_s return 0; } + +static inline int phy_mii_set_bmcr_reg_autoneg_disabled(const struct device *dev, + enum phy_link_speed adv_speeds) +{ + uint32_t bmcr_reg = 0U; + uint32_t bmcr_reg_old; + + if (phy_read(dev, MII_BMCR, &bmcr_reg) < 0) { + return -EIO; + } + bmcr_reg_old = bmcr_reg; + + /* Disable auto-negotiation */ + bmcr_reg &= ~(MII_BMCR_AUTONEG_ENABLE | MII_BMCR_SPEED_LSB | MII_BMCR_SPEED_MSB); + + if (PHY_LINK_IS_SPEED_1000M(adv_speeds)) { + bmcr_reg |= MII_BMCR_SPEED_1000; + } else if (PHY_LINK_IS_SPEED_100M(adv_speeds)) { + bmcr_reg |= MII_BMCR_SPEED_100; + } else if (PHY_LINK_IS_SPEED_10M(adv_speeds)) { + bmcr_reg |= MII_BMCR_SPEED_10; + } else { + LOG_ERR("Invalid speed %d", adv_speeds); + return -EINVAL; + } + + WRITE_BIT(bmcr_reg, MII_BMCR_DUPLEX_MODE_BIT, PHY_LINK_IS_FULL_DUPLEX(adv_speeds)); + + if (bmcr_reg == bmcr_reg_old) { + return -EALREADY; + } + + if (phy_write(dev, MII_BMCR, bmcr_reg) < 0) { + return -EIO; + } + + return 0; +} + +static inline enum phy_link_speed phy_mii_get_link_speed_bmcr_reg(const struct device *dev, + uint16_t bmcr_reg) +{ + enum phy_link_speed speed; + + switch (bmcr_reg & (MII_BMCR_DUPLEX_MODE | MII_BMCR_SPEED_MASK)) { + case MII_BMCR_DUPLEX_MODE | MII_BMCR_SPEED_1000: + speed = LINK_FULL_1000BASE; + break; + case MII_BMCR_DUPLEX_MODE | MII_BMCR_SPEED_100: + speed = LINK_FULL_100BASE; + break; + case MII_BMCR_DUPLEX_MODE | MII_BMCR_SPEED_10: + speed = LINK_FULL_10BASE; + break; + case MII_BMCR_SPEED_1000: + speed = LINK_HALF_1000BASE; + break; + case MII_BMCR_SPEED_100: + speed = LINK_HALF_100BASE; + break; + case MII_BMCR_SPEED_10: + default: + speed = LINK_HALF_10BASE; + break; + } + + return speed; +} #endif /* ZEPHYR_PHY_MII_H_ */