drivers: ethernet: phy: phy_mii: start autoneg in cfg_link

already (re-)start autonegotiation in phy_mii_cfg_link.

Signed-off-by: Fin Maaß <f.maass@vogl-electronic.com>
This commit is contained in:
Fin Maaß 2025-06-16 17:06:58 +02:00 committed by Henrik Brix Andersen
commit 66329deb9c
3 changed files with 81 additions and 78 deletions

View file

@ -41,7 +41,6 @@ struct phy_mii_dev_data {
#if ANY_DYNAMIC_LINK
struct k_work_delayable monitor_work;
bool gigabit_supported;
bool restart_autoneg;
bool autoneg_in_progress;
k_timepoint_t autoneg_timeout;
#endif
@ -179,32 +178,27 @@ static int update_link_state(const struct device *dev)
link_up = (bmsr_reg & MII_BMSR_LINK_STATUS) != 0U;
/* If there is no change in link state don't proceed. */
if ((link_up == data->state.is_up) && !data->restart_autoneg) {
return -EAGAIN;
}
data->state.is_up = link_up;
if (!data->state.is_up) {
/* If link is down, we can stop here. */
if (!link_up) {
data->state.speed = 0;
LOG_INF("PHY (%d) is down", cfg->phy_addr);
/* If not restarting auto-negotiation, just return */
if (!data->restart_autoneg) {
if (link_up != data->state.is_up) {
data->state.is_up = false;
LOG_INF("PHY (%d) is down", cfg->phy_addr);
return 0;
}
return -EAGAIN;
}
if (phy_mii_reg_read(dev, MII_BMCR, &bmcr_reg) < 0) {
return -EIO;
}
if (!(bmcr_reg & MII_BMCR_AUTONEG_ENABLE)) {
/* If auto-negotiation is not enabled, we only need to check the link speed */
if ((bmcr_reg & MII_BMCR_AUTONEG_ENABLE) == 0U) {
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) {
if ((data->state.speed != new_speed) || !data->state.is_up) {
data->state.is_up = true;
data->state.speed = new_speed;
LOG_INF("PHY (%d) Link speed %s Mb, %s duplex",
@ -218,22 +212,18 @@ static int update_link_state(const struct device *dev)
return -EAGAIN;
}
/**
* Perform auto-negotiation sequence.
/* If auto-negotiation is enabled and the link was already up last time we checked,
* we can return immediately, as the link state has not changed.
* If the link was down, we will start the 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;
if (data->state.is_up) {
return -EAGAIN;
}
data->restart_autoneg = false;
data->state.is_up = true;
LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence", cfg->phy_addr);
/* We have to wait for the auto-negotiation process to complete */
data->autoneg_timeout = sys_timepoint_calc(K_MSEC(CONFIG_PHY_AUTONEG_TIMEOUT_MS));
return -EINPROGRESS;
}
@ -249,10 +239,6 @@ static int check_autonegotiation_completion(const struct device *dev)
uint16_t c1kt_reg = 0;
uint16_t s1kt_reg = 0;
if (data->restart_autoneg) {
return -EAGAIN;
}
/* On some PHY chips, the BMSR bits are latched, so the first read may
* show incorrect status. A second read ensures correct values.
*/
@ -267,7 +253,7 @@ static int check_autonegotiation_completion(const struct device *dev)
if ((bmsr_reg & MII_BMSR_AUTONEG_COMPLETE) == 0U) {
if (sys_timepoint_expired(data->autoneg_timeout)) {
LOG_DBG("PHY (%d) auto-negotiate timedout", cfg->phy_addr);
LOG_DBG("PHY (%d) auto-negotiate timeout", cfg->phy_addr);
return -ETIMEDOUT;
}
return -EINPROGRESS;
@ -371,7 +357,6 @@ static int phy_mii_cfg_link(const struct device *dev, enum phy_link_speed adv_sp
{
struct phy_mii_dev_data *const data = dev->data;
const struct phy_mii_dev_config *const cfg = dev->config;
uint16_t bmcr_reg;
int ret = 0;
/* if there is no mdio (fixed-link) it is not supported to configure link */
@ -397,52 +382,24 @@ static int phy_mii_cfg_link(const struct device *dev, enum phy_link_speed adv_sp
}
ret = phy_mii_set_bmcr_reg_autoneg_disabled(dev, adv_speeds);
if (ret == -EALREADY) {
/* 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;
if (ret >= 0) {
data->autoneg_in_progress = false;
k_work_reschedule(&data->monitor_work, K_NO_WAIT);
}
} else {
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 (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;
}
ret = phy_mii_cfg_link_autoneg(dev, adv_speeds, data->gigabit_supported);
if (ret >= 0) {
LOG_DBG("PHY (%d) Starting MII PHY auto-negotiate sequence", cfg->phy_addr);
data->autoneg_in_progress = true;
data->autoneg_timeout =
sys_timepoint_calc(K_MSEC(CONFIG_PHY_AUTONEG_TIMEOUT_MS));
k_work_reschedule(&data->monitor_work,
K_MSEC(MII_AUTONEG_POLL_INTERVAL_MS));
}
}
if (data->restart_autoneg && data->state.is_up) {
k_work_reschedule(&data->monitor_work, K_NO_WAIT);
if (ret == -EALREADY) {
LOG_DBG("PHY (%d) Link already configured", cfg->phy_addr);
}
cfg_link_end:
@ -461,11 +418,11 @@ static int phy_mii_get_link_state(const struct device *dev,
memcpy(state, &data->state, sizeof(struct phy_link_state));
if (data->state.speed == 0) {
if (state->speed == 0) {
/* If speed is 0, then link is also down, happens when autonegotiation is in
* progress
*/
data->state.is_up = false;
state->is_up = false;
}
k_sem_give(&data->sem);
@ -573,7 +530,8 @@ static int phy_mii_initialize_dynamic_link(const struct device *dev)
/* Advertise default speeds */
phy_mii_cfg_link(dev, cfg->default_speeds, 0);
monitor_work_handler(&data->monitor_work.work);
/* This will schedule the monitor work, if not already scheduled by phy_mii_cfg_link(). */
k_work_schedule(&data->monitor_work, K_NO_WAIT);
return 0;
}

View file

@ -61,6 +61,50 @@ static inline int phy_mii_set_c1kt_reg(const struct device *dev, enum phy_link_s
return 0;
}
static inline int phy_mii_cfg_link_autoneg(const struct device *dev, enum phy_link_speed adv_speeds,
bool gigabit_supported)
{
uint32_t bmcr_reg = 0U;
uint32_t bmcr_reg_old;
int ret = 0;
if (phy_read(dev, MII_BMCR, &bmcr_reg) < 0) {
return -EIO;
}
bmcr_reg_old = bmcr_reg;
/* Disable isolation */
bmcr_reg &= ~MII_BMCR_ISOLATE;
/* Enable auto-negotiation */
bmcr_reg |= MII_BMCR_AUTONEG_ENABLE;
ret = phy_mii_set_anar_reg(dev, adv_speeds);
if (ret >= 0) {
bmcr_reg |= MII_BMCR_AUTONEG_RESTART;
} else if (ret != -EALREADY) {
return ret;
}
if (gigabit_supported) {
ret = phy_mii_set_c1kt_reg(dev, adv_speeds);
if (ret >= 0) {
bmcr_reg |= MII_BMCR_AUTONEG_RESTART;
} else if (ret != -EALREADY) {
return ret;
}
}
if (bmcr_reg != bmcr_reg_old) {
if (phy_write(dev, MII_BMCR, bmcr_reg) < 0) {
return -EIO;
}
return 0;
}
return -EALREADY;
}
static inline int phy_mii_set_bmcr_reg_autoneg_disabled(const struct device *dev,
enum phy_link_speed adv_speeds)
{

View file

@ -228,6 +228,7 @@ __subsystem struct ethphy_driver_api {
* @retval 0 If successful.
* @retval -EIO If communication with PHY failed.
* @retval -ENOTSUP If not supported.
* @retval -EALREADY If already configured with the same speeds and flags.
*/
static inline int phy_configure_link(const struct device *dev, enum phy_link_speed speeds,
enum phy_cfg_link_flag flags)