#ifndef PHY_CYCLONEV_SRC
#define PHY_CYCLONEV_SRC
/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (C) 2022, Intel Corporation
 * Description:
 * Driver for the PHY KSZ9021RL/RN Datasheet:(https://ww1.microchip.com/
 * downloads/en/DeviceDoc/KSZ9021RL-RN-Data-Sheet-DS00003050A.pdf)
 * specifically designed for Cyclone V SoC DevKit use only.
 */

/* PHY */
/* According to default Cyclone V DevKit Bootstrap Encoding Scheme */
#include "eth_cyclonev_priv.h"

#include <stdio.h>

#include <zephyr/kernel.h>

#include <sys/types.h>

#define PHY_ADDR (4)

/*  PHY_Read_write_Timeouts   */
#define PHY_READ_TO  ((uint32_t)0x0004FFFF)
#define PHY_WRITE_TO ((uint32_t)0x0004FFFF)

/* Speed and Duplex mask values  */
#define PHY_SPEED_100  (0x0020)
#define PHY_SPEED_1000 (0x0040)

#define PHY_CLK_AND_CONTROL_PAD_SKEW_VALUE 0xa0d0
#define PHY_RX_DATA_PAD_SKEW_VALUE	   0x0000

/* Write/read to/from extended registers */
#define MII_KSZPHY_EXTREG	0x0b
#define KSZPHY_EXTREG_WRITE	0x8000
#define MII_KSZPHY_EXTREG_WRITE 0x0c
#define MII_KSZPHY_EXTREG_READ	0x0d

/* PHY Regs */

/* Basic Control Register */
#define PHY_BCR			    (0)
#define PHY_RESET		    BIT(15) /* Do a PHY reset */
#define PHY_AUTONEGOTIATION	    BIT(12)
#define PHY_RESTART_AUTONEGOTIATION BIT(9)

/* Basic Status Register */
#define PHY_BSR		      BIT(0)
#define PHY_AUTOCAP	      BIT(3) /* Auto-negotiation capability */
#define PHY_LINKED_STATUS     BIT(2)
#define PHY_AUTONEGO_COMPLETE BIT(5)

/* Auto-Negotiation Advertisement */
#define PHY_AUTON	   (4)
#define PHYANA_10BASET	   BIT(5)
#define PHYANA_10BASETFD   BIT(6)
#define PHYANA_100BASETX   BIT(7)
#define PHYANA_100BASETXFD BIT(8)
#define PHYSYMETRIC_PAUSE  BIT(10)
#define PHYASYMETRIC_PAUSE BIT(11)

/* 1000Base-T Control */
#define PHY_1GCTL (9)
#define PHYADVERTISE_1000HALF BIT(8)
#define PHYADVERTISE_1000FULL BIT(9)
#define PHYINDICATE_PORTTYPE  BIT(10)
#define PHYCONFIG_MASTER      BIT(11)
#define PHYENABLE_MANUALCONFIG BIT(12)

/* PHY Control Register */
#define PHY_CR		      (31)
#define PHY_DUPLEX_STATUS     BIT(3)

/* Extended registers */
#define MII_KSZPHY_CLK_CONTROL_PAD_SKEW 0x104
#define MII_KSZPHY_RX_DATA_PAD_SKEW	0x105
#define MII_KSZPHY_TX_DATA_PAD_SKEW	0x106


int alt_eth_phy_write_register(uint16_t emac_instance, uint16_t phy_reg,
		uint16_t phy_value, struct eth_cyclonev_priv *p);
int alt_eth_phy_read_register(uint16_t emac_instance, uint16_t phy_reg,
		uint16_t *rdval, struct eth_cyclonev_priv *p);
int alt_eth_phy_write_register_extended(uint16_t emac_instance, uint16_t phy_reg,
		uint16_t phy_value, struct eth_cyclonev_priv *p);
int alt_eth_phy_read_register_extended(uint16_t emac_instance, uint16_t phy_reg,
		uint16_t *rdval, struct eth_cyclonev_priv *p);
int alt_eth_phy_config(uint16_t instance, struct eth_cyclonev_priv *p);
int alt_eth_phy_reset(uint16_t instance, struct eth_cyclonev_priv *p);
int alt_eth_phy_get_duplex_and_speed(uint16_t *phy_duplex_status, uint16_t *phy_speed,
				     uint16_t instance, struct eth_cyclonev_priv *p);

int alt_eth_phy_write_register(uint16_t emac_instance, uint16_t phy_reg,
		uint16_t phy_value, struct eth_cyclonev_priv *p)
{
	uint16_t tmpreg = 0;
	volatile uint32_t timeout = 0;
	uint16_t phy_addr;

	if (emac_instance > 1) {
		return -1;
	}

	phy_addr = PHY_ADDR;

	/* Prepare the MII address register value */
	tmpreg = 0;
	/* Set the PHY device address */
	tmpreg |= EMAC_GMAC_GMII_ADDR_PA_SET(phy_addr);
	/* Set the PHY register address */
	tmpreg |= EMAC_GMAC_GMII_ADDR_GR_SET(phy_reg);
	/* Set the write mode */
	tmpreg |= EMAC_GMAC_GMII_ADDR_GW_SET_MSK;
	/* Set the clock divider */
	tmpreg |= EMAC_GMAC_GMII_ADDR_CR_SET(EMAC_GMAC_GMII_ADDR_CR_E_DIV102);
	/* Set the MII Busy bit */
	tmpreg |= EMAC_GMAC_GMII_ADDR_GB_SET(EMAC_GMAC_GMII_ADDR_GB_SET_MSK);

	/* Give the value to the MII data register */
	sys_write32(phy_value & 0xffff, EMAC_GMAC_GMII_DATA_ADDR(p->base_addr));
	/* Write the result value into the MII Address register */
	sys_write32(tmpreg & 0xffff, EMAC_GMAC_GMII_ADDR_ADDR(p->base_addr));


	/* Check the Busy flag */
	do {
		timeout++;
		tmpreg = sys_read32(EMAC_GMAC_GMII_ADDR_ADDR(p->base_addr));
	} while ((tmpreg & EMAC_GMAC_GMII_ADDR_GB_SET_MSK) && (timeout < PHY_WRITE_TO));

	/* Return ERROR in case of timeout */
	if (timeout == PHY_WRITE_TO) {
		return -1;
	}

	/* Return SUCCESS */
	return 0;
}

int alt_eth_phy_read_register(uint16_t emac_instance, uint16_t phy_reg, uint16_t *rdval,
		struct eth_cyclonev_priv *p)
{
	uint16_t tmpreg = 0;
	volatile uint32_t timeout = 0;
	uint16_t phy_addr;

	if (emac_instance > 1) {
		return -1;
	}

	phy_addr = PHY_ADDR;

	/* Prepare the MII address register value */
	tmpreg = 0;
	/* Set the PHY device address */
	tmpreg |= EMAC_GMAC_GMII_ADDR_PA_SET(phy_addr);
	/* Set the PHY register address */
	tmpreg |= EMAC_GMAC_GMII_ADDR_GR_SET(phy_reg);
	/* Set the read mode */
	tmpreg &= EMAC_GMAC_GMII_ADDR_GW_CLR_MSK;
	/* Set the clock divider */
	tmpreg |= EMAC_GMAC_GMII_ADDR_CR_SET(EMAC_GMAC_GMII_ADDR_CR_E_DIV102);
	/* Set the MII Busy bit */
	tmpreg |= EMAC_GMAC_GMII_ADDR_GB_SET(EMAC_GMAC_GMII_ADDR_GB_SET_MSK);

	/* Write the result value into the MII Address register */
	sys_write32(tmpreg & 0xffff, EMAC_GMAC_GMII_ADDR_ADDR(p->base_addr));

	/* Check the Busy flag */
	do {
		timeout++;
		tmpreg = sys_read32(EMAC_GMAC_GMII_ADDR_ADDR(p->base_addr));
	} while ((tmpreg & EMAC_GMAC_GMII_ADDR_GB_SET_MSK) && (timeout < PHY_READ_TO));

	/* Return ERROR in case of timeout */
	if (timeout == PHY_READ_TO) {
		return -1;
	}

	/* Return data register value */
	*rdval = sys_read32(EMAC_GMAC_GMII_DATA_ADDR(p->base_addr));

	return 0;
}

int alt_eth_phy_write_register_extended(uint16_t emac_instance, uint16_t phy_reg,
					uint16_t phy_value, struct eth_cyclonev_priv *p)
{
	int rc;

	rc = alt_eth_phy_write_register(emac_instance, MII_KSZPHY_EXTREG,
					KSZPHY_EXTREG_WRITE | phy_reg, p);

	if (rc == -1) {
		return rc;
	}

	rc = alt_eth_phy_write_register(emac_instance, MII_KSZPHY_EXTREG_WRITE, phy_value, p);
	return rc;
}

int alt_eth_phy_read_register_extended(uint16_t emac_instance, uint16_t phy_reg, uint16_t *rdval,
		struct eth_cyclonev_priv *p)
{
	int rc;

	rc = alt_eth_phy_write_register(emac_instance, MII_KSZPHY_EXTREG, phy_reg, p);

	if (rc == -1) {
		return rc;
	}
	k_sleep(K_MSEC(1));

	rc = alt_eth_phy_read_register(emac_instance, MII_KSZPHY_EXTREG_READ, rdval, p);

	return rc;
}

int alt_eth_phy_config(uint16_t instance, struct eth_cyclonev_priv *p)
{

	int rc;
	uint16_t rdval;
	uint32_t timeout;
	/*--------------------   Configure the PHY skew values  ----------------*/

	rc = alt_eth_phy_write_register_extended(instance, MII_KSZPHY_CLK_CONTROL_PAD_SKEW,
						 PHY_CLK_AND_CONTROL_PAD_SKEW_VALUE, p);
	if (rc == -1) {
		return rc;
	}

	rc = alt_eth_phy_write_register_extended(instance, MII_KSZPHY_RX_DATA_PAD_SKEW,
						 PHY_RX_DATA_PAD_SKEW_VALUE, p);
	if (rc == -1) {
		return rc;
	}

	/* Implement Auto-negotiation Process */

	/* Check PHY Status if auto-negotiation is supported */
	rc = alt_eth_phy_read_register(instance, PHY_BSR, &rdval, p);
	if (((rdval & PHY_AUTOCAP) == 0) || (rc == -1)) {
		return -1;
	}

	/* Set Advertise capabilities for 10Base-T/
	 *10Base-T full-duplex/100Base-T/100Base-T full-duplex
	 */
	rc = alt_eth_phy_read_register(instance, PHY_AUTON, &rdval, p);
	if (rc == -1) {
		return rc;
	}

	rdval |= (PHYANA_10BASET | PHYANA_10BASETFD | PHYANA_100BASETX | PHYANA_100BASETXFD |
		  PHYSYMETRIC_PAUSE);
	rc = alt_eth_phy_write_register(instance, PHY_AUTON, rdval, p);
	if (rc == -1) {
		return rc;
	}

	/* Set Advertise capabilities for 1000 Base-T/1000 Base-T full-duplex */

	rc = alt_eth_phy_write_register(instance, PHY_1GCTL,
			PHYADVERTISE_1000FULL | PHYADVERTISE_1000HALF |
			PHYINDICATE_PORTTYPE |  PHYCONFIG_MASTER | PHYENABLE_MANUALCONFIG
					, p);
	if (rc == -1) {
		return rc;
	}

	/* Wait for linked status... */
	timeout = 0;
	do {
		timeout++;
		rc = alt_eth_phy_read_register(instance, PHY_BSR, &rdval, p);
	} while (!(rdval & PHY_LINKED_STATUS) && (timeout < PHY_READ_TO) && (rc == 0));

	/* Return ERROR in case of timeout */
	if ((timeout == PHY_READ_TO) || (rc == -1)) {
		LOG_ERR("Error Link Down\n");
		return -1;
	}
	LOG_INF("Link is up!");

	/* Configure the PHY for AutoNegotiate */
	rc = alt_eth_phy_read_register(instance, PHY_BCR, &rdval, p);
	if (rc == -1) {
		return rc;
	}

	rdval |= PHY_AUTONEGOTIATION;
	rdval |= PHY_RESTART_AUTONEGOTIATION;
	rc = alt_eth_phy_write_register(instance, PHY_BCR, rdval, p);
	if (rc == -1) {
		return rc;
	}

	/* Wait until the auto-negotiation is completed */
	timeout = 0;
	do {
		timeout++;
		rc = alt_eth_phy_read_register(instance, PHY_BSR, &rdval, p);
	} while (!(rdval & PHY_AUTONEGO_COMPLETE) && (timeout < PHY_READ_TO) && (rc == 0));

	/* Return ERROR in case of timeout */
	if ((timeout == PHY_READ_TO) || (rc == -1)) {
		alt_eth_phy_read_register(instance, PHY_BSR, &rdval, p);
		LOG_ERR("Auto Negotiation: Status reg = 0x%x\n", rdval);
		return -1;
	}
	LOG_INF("Auto Negotiation Complete!");

	return rc;
};

int alt_eth_phy_reset(uint16_t instance, struct eth_cyclonev_priv *p)
{
	int i;
	int rc;
	uint16_t rdval;

	/* Put the PHY in reset mode */
	if ((alt_eth_phy_write_register(instance, PHY_BCR, PHY_RESET, p)) != 0) {
		/* Return ERROR in case of write timeout */
		return -1;
	}

	/* Wait for the reset to clear */
	for (i = 0; i < 10; i++) {
		k_sleep(K_MSEC(10));
		rc = alt_eth_phy_read_register(instance, PHY_BCR, &rdval, p);
		if (((rdval & PHY_RESET) == 0) || (rc == -1)) {
			break;
		}
	}

	if (i == 10) {
		return -1;
	}
	/* Delay to assure PHY reset */
	k_sleep(K_MSEC(10));

	return rc;
};

int alt_eth_phy_get_duplex_and_speed(uint16_t *phy_duplex_status, uint16_t *phy_speed,
				     uint16_t instance, struct eth_cyclonev_priv *p)
{

	LOG_DBG("PHY: func_alt_eth_phy_get_duplex_and_speed\n");
	uint16_t regval = 0;
	int rc;

	rc = alt_eth_phy_read_register(instance, PHY_CR, &regval, p);

	if (regval & PHY_DUPLEX_STATUS) {
		*phy_duplex_status = 1;
	} else {
		*phy_duplex_status = 0;
	}

	if (regval & PHY_SPEED_100) {
		*phy_speed = 100;
	} else {
		if (regval & PHY_SPEED_1000) {
			*phy_speed = 1000;
		} else {
			*phy_speed = 10;
		}
	}

	return rc;
}

#endif
