drivers: ethernet: phy_mii: add support for disabling auto-neg

Adds support for disabling auto-negotiation.

Signed-off-by: Fin Maaß <f.maass@vogl-electronic.com>
This commit is contained in:
Fin Maaß 2025-06-10 13:55:47 +02:00 committed by Henrik Brix Andersen
commit 6838c57679
2 changed files with 135 additions and 27 deletions

View file

@ -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) {

View file

@ -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_ */