Commit 6838c576 authored by Fin Maaß's avatar Fin Maaß Committed by Henrik Brix Andersen
Browse files

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



Adds support for disabling auto-negotiation.

Signed-off-by: default avatarFin Maaß <f.maass@vogl-electronic.com>
parent 89006fbe
Loading
Loading
Loading
Loading
+67 −27
Original line number Diff line number Diff line
@@ -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,23 +384,27 @@ 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;
	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;
		}
	}

	} 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 */
@@ -393,12 +415,30 @@ static int phy_mii_cfg_link(const struct device *dev, enum phy_link_speed adv_sp
			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;
			}
		}
	}

	if (data->restart_autoneg && data->state.is_up) {
		k_work_reschedule(&data->monitor_work, K_NO_WAIT);
+68 −0
Original line number Diff line number Diff line
@@ -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_ */