/*
 * Copyright The Zephyr Project Contributors
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#ifndef ZEPHYR_PHY_MII_H_
#define ZEPHYR_PHY_MII_H_

#include <zephyr/device.h>
#include <zephyr/net/phy.h>
#include <zephyr/net/mii.h>

static inline int phy_mii_set_anar_reg(const struct device *dev, enum phy_link_speed adv_speeds)
{
	uint32_t anar_reg = 0U;
	uint32_t anar_reg_old;

	if (phy_read(dev, MII_ANAR, &anar_reg) < 0) {
		return -EIO;
	}
	anar_reg_old = anar_reg;

	WRITE_BIT(anar_reg, MII_ADVERTISE_10_FULL_BIT, (adv_speeds & LINK_FULL_10BASE) != 0U);
	WRITE_BIT(anar_reg, MII_ADVERTISE_10_HALF_BIT, (adv_speeds & LINK_HALF_10BASE) != 0U);
	WRITE_BIT(anar_reg, MII_ADVERTISE_100_FULL_BIT, (adv_speeds & LINK_FULL_100BASE) != 0U);
	WRITE_BIT(anar_reg, MII_ADVERTISE_100_HALF_BIT, (adv_speeds & LINK_HALF_100BASE) != 0U);

	if (anar_reg == anar_reg_old) {
		return -EALREADY;
	}

	if (phy_write(dev, MII_ANAR, anar_reg) < 0) {
		return -EIO;
	}

	return 0;
}

static inline int phy_mii_set_c1kt_reg(const struct device *dev, enum phy_link_speed adv_speeds)
{
	uint32_t c1kt_reg = 0U;
	uint32_t c1kt_reg_old;

	if (phy_read(dev, MII_1KTCR, &c1kt_reg) < 0) {
		return -EIO;
	}
	c1kt_reg_old = c1kt_reg;

	WRITE_BIT(c1kt_reg, MII_ADVERTISE_1000_FULL_BIT, (adv_speeds & LINK_FULL_1000BASE) != 0U);
	WRITE_BIT(c1kt_reg, MII_ADVERTISE_1000_HALF_BIT, (adv_speeds & LINK_HALF_1000BASE) != 0U);

	if (c1kt_reg == c1kt_reg_old) {
		return -EALREADY;
	}

	if (phy_write(dev, MII_1KTCR, c1kt_reg) < 0) {
		return -EIO;
	}

	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)
{
	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_ */
