/*
 *  Copyright 2019-2022 NXP
 *
 *  NXP Confidential. This software is owned or controlled by NXP and may only
 *  be used strictly in accordance with the applicable license terms. By
 *  expressly accepting such terms or by downloading, installing, activating
 *  and/or otherwise using the software, you are agreeing that you have read,
 *  and that you agree to comply with and are bound by, such license terms. If
 *  you do not agree to be bound by the applicable license terms, then you may
 *  not retain, install, activate or otherwise use the software.
 *
 *  This file contains sample code only. It is not part of the production code deliverables.
 */

#include "serdes_xpcs.h"
#include "oal_types_autosar.h"

#define XPCS_BASE(xpcs) ((xpcs == 0U)  ? (SERDES_XPCS_0_ADDR2) : (SERDES_XPCS_1_ADDR2))

#define hal_write32(val, addr)	(*(volatile uint32 *)(addr) = ((uint32)(val)))
#define hal_read32(addr)		(*(volatile uint32 *)(addr))

#ifdef DEBUG_SERDES

#define ETH_43_PFE_START_SEC_VAR_INIT_UNSPECIFIED
#include "Eth_43_PFE_MemMap.h"

static uint64_t serdes_total_wait_time = 0U;

#define ETH_43_PFE_STOP_SEC_VAR_INIT_UNSPECIFIED
#include "Eth_43_PFE_MemMap.h"

#define ENTERED_ETH_43_PFE_START_SEC_VAR_INIT_32
#include "Eth_43_PFE_MemMap.h"

static uint32 serdes_wait[16] = { 0U };
static uint32 serdes_idx = 0U;

#define ENTERED_ETH_43_PFE_STOP_SEC_VAR_INIT_32
#include "Eth_43_PFE_MemMap.h"

#endif /* DEBUG_SERDES */

#define ETH_43_PFE_START_SEC_CODE
#include "Eth_43_PFE_MemMap.h"

uint16 serdes_xpcs_read_gen2(void *base, uint32 xpcs, uint32 reg)
{
	uint32 ofsleft = (reg >> 8) & 0xffffU;
	uint32 ofsright = (reg & 0xffU);
	uint32 pcs_off = XPCS_BASE(xpcs);

	hal_write32(ofsleft, (uint32)base + pcs_off + 0x3fcU);
	return hal_read32((uint32)base + pcs_off + (4U * ofsright)) & 0xffffU;
}

void serdes_xpcs_write_gen2(void *base, uint32 xpcs, uint32 reg, uint16 val)
{
	uint32 ofsleft = (reg >> 8U) & 0xffffU;
	uint32 ofsright = (reg & 0xffU);
	uint32 pcs_off = XPCS_BASE(xpcs);

	hal_write32(ofsleft, (uint32)base + pcs_off + 0x3fcU);
	hal_write32(val, (uint32)base + pcs_off + (4U * ofsright));
}

#define PCSW16(serdes_base, pcs, reg, val) \
do {\
	serdes_xpcs_write_gen2(serdes_base, pcs, reg, val);\
} while(0)

#define PCSR16(serdes_base, pcs, reg) serdes_xpcs_read_gen2(serdes_base, pcs, reg)


#define PCSBCLR(serdes_base, pcs, reg, mask) \
do {\
	uint16 tmp = serdes_xpcs_read_gen2(serdes_base, pcs, reg);\
	serdes_xpcs_write_gen2(serdes_base, pcs, reg, (tmp) & ~(mask));\
} while(0)

#define PCSBSET(serdes_base, pcs, reg, mask) \
do {\
	uint16 tmp = serdes_xpcs_read_gen2(serdes_base, pcs, reg);\
	serdes_xpcs_write_gen2(serdes_base, pcs, reg, (tmp) | (mask));\
} while(0)

#define PCSBCLRSET(serdes_base, pcs, reg, clr_mask, mask) \
do {\
	uint16 tmp = serdes_xpcs_read_gen2(serdes_base, pcs, reg);\
	serdes_xpcs_write_gen2(serdes_base, pcs, reg, ((tmp) & ~(clr_mask)) | (mask));\
} while(0)

static Std_ReturnType serdes_pcs_wait_bits(void *base, uint32 xpcs, uint32 reg, uint16 mask, uint16 val, uint16 us, uint16 cnt)
{
	volatile uint32 tmp = 1000000U; /* Should be good enough */
	Std_ReturnType ret;
	(void)us;
	(void)cnt;

	do
	{
		if((serdes_xpcs_read_gen2(base, xpcs, reg) & mask) == val)
		{
			break;
		}
		--tmp;
	} while (tmp > 0U);

	if (tmp > 0U)
	{
		ret = E_OK;
	}
	else
	{
		ret = E_NOT_OK;
	}

#ifdef DEBUG_SERDES
	serdes_total_wait_time += 100000U - tmp;
	serdes_wait[serdes_idx++] =  100000U - tmp;
#endif

	return ret;
}

/**
* @brief        Enable PCS loopback
* @details		GMII loopback in XPCS
*/
void serdes_pcs_loopback_enable(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_DIG_CTRL1, R2TLBE);
}

/**
* @brief        Disable PCS loopback
* @details		GMII loopback in XPCS
*/
void serdes_pcs_loopback_disable(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, VR_MII_DIG_CTRL1, R2TLBE);
}

/**
* @brief        Wait for power good state
*/
Std_ReturnType serdes_pcs_wait_for_power_good(void *base, uint32 xpcs)
{
	return serdes_pcs_wait_bits(base, xpcs, VR_MII_DIG_STS,
				    0x7U << 2, 0x4U << 2U, 1000U, 1000U);
}

/**
* @brief        Issue vendor specific reset
*/
void serdes_pcs_issue_vreset(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_DIG_CTRL1, VR_RST);
}

/**
* @brief        Wait for vendor specific reset
*/
Std_ReturnType serdes_pcs_wait_for_vreset(void *base, uint32 xpcs)
{
	return serdes_pcs_wait_bits(base, xpcs, VR_MII_DIG_CTRL1,
				    VR_RST, 0U, 1000U, 1000U);
}

/**
* @brief        Configure XPCS to 2.5G mode
*/
static void serdes_pcs_set_2500M_mode(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_DIG_CTRL1, EN_2_5G_MODE);
}

/**
* @brief        Configure XPCS to 1G mode
*/
static void serdes_pcs_set_1000M_mode(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, VR_MII_DIG_CTRL1, EN_2_5G_MODE);
}

/**
* @brief        Speed select
*/
Std_ReturnType serdes_pcs_speed_select(void *base, uint32 xpcs, uint32 div)
{
	uint16 reg16 = 0U;
	Std_ReturnType ret = E_OK;

	switch (div) {
	case 100:
		break;

	case 10:
		reg16 |= MII_CTRL_SS13;
		break;

	case 1:
		reg16 |= MII_CTRL_SS6;
		break;

	default:
		/*	Unsupported value */
		ret = E_NOT_OK;
	}

	if (E_OK == ret)
	{
		PCSBCLRSET(base, xpcs, SR_MII_CTRL,
			   MII_CTRL_SS13 | MII_CTRL_SS6,
			   reg16);
	}

	return ret;
}

/**
* @brief        Configure full duplex
*/
void serdes_pcs_set_fd(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, SR_MII_CTRL, MII_CTRL_DUPLEX_MODE);
}

/**
* @brief        Configure half duplex
*/
void serdes_pcs_set_hd(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, SR_MII_CTRL, MII_CTRL_DUPLEX_MODE);
}

/**
* @brief        Enable MII bus width control in PCS
* @details		MII bus is in all speeds 8bit
*/
void serdes_pcs_mii_bus_control_disable(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_AN_CTRL, MII_AN_CTRL_MII_CTRL);
}

/**
* @brief        Enable MII bus width control in PCS
* @details		MII bus is in 1G 8bit and other speeds 4bit
*/
void serdes_pcs_mii_bus_control_enable(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, VR_MII_AN_CTRL, MII_AN_CTRL_MII_CTRL);
}

/**
* @brief        Enable SGMII AN
*/
void serdes_pcs_an_enable(void *base, uint32 xpcs)
{
	/* Select SGMII type AN, enable interrupt */
	PCSBCLRSET(base, xpcs, VR_MII_AN_CTRL,
			MII_AN_CTRL_PCS_MODE(0x3U),
			MII_AN_CTRL_PCS_MODE(PCS_MODE_SGMII) |
			MII_AN_INTR_EN);
	/* Enable SGMII AN */
	PCSBSET(base, xpcs, SR_MII_CTRL, MII_CTRL_AN_ENABLE);
}

/**
* @brief        Disable SGMII AN
*/
void serdes_pcs_an_disable(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, SR_MII_CTRL, MII_CTRL_AN_ENABLE);
	/* Disable interrupt */
	PCSBCLR(base, xpcs, VR_MII_AN_CTRL, MII_AN_INTR_EN);
}

/**
* @brief        Restart SGMII AN
*/
void serdes_pcs_an_restart(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, SR_MII_CTRL, MII_CTRL_RESTART_AN);
}

/**
* @brief        Enable automatic speed change
*/
void serdes_pcs_an_auto_sw_enable(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_DIG_CTRL1, MAC_AUTO_SW);
}

/**
* @brief        Disable automatic speed change
*/
void serdes_pcs_an_auto_sw_disable(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, VR_MII_DIG_CTRL1, MAC_AUTO_SW);
}

/**
* @brief        Configure SGMII AN link timer
* @details		This can be any value. Recommended is 0x2fafU.
*/
void serdes_pcs_an_set_link_timer(void *base, uint32 xpcs, uint16 link_timer)
{
	PCSW16(base, xpcs, VR_MII_LINK_TIMER_CTRL, link_timer);
	PCSBCLR(base, xpcs, VR_MII_DIG_CTRL1, CL37_TMR_OVR_RIDE);
	PCSBSET(base, xpcs, VR_MII_DIG_CTRL1, CL37_TMR_OVR_RIDE);
}

/**
* @brief        Decode SGMII AN result
*/
Std_ReturnType serdes_pcs_an_decode(void *base, uint32 xpcs, bool_t *link, bool_t *fduplex, uint16 *speed)
{
	Std_ReturnType ret = E_OK;
	uint16 reg16 = PCSR16(base, xpcs, VR_MII_AN_INTR_STS);

	if(reg16 & CL37_ANSGM_STS_LINK)
	{
		*link = TRUE;

		*fduplex = FALSE;
		if(reg16 & CL37_ANSGM_STS_FD)
		{
			*fduplex = TRUE;
		}

		switch(CL37_ANSGM_STS_GET_SPEED(reg16)) {
		case CL37_ANSGM_STS_SPEED_10M:
			*speed = 10U;
			break;
		case CL37_ANSGM_STS_SPEED_100M:
			*speed = 100U;
			break;
		case CL37_ANSGM_STS_SPEED_1000M:
			*speed = 1000U;
			break;
		default:
			*speed = 0U;
			ret = E_NOT_OK;
		}
	}
	else
	{
		*link = FALSE;
		ret = E_NOT_OK;
	}

	return ret;
}

void serdes_pma_high_freq_recovery(void *base, uint32 xpcs)
{
	/* PCS signal protection, PLL railout recovery */
	PCSBSET(base, xpcs, VR_MII_DBG_CTRL, SUPPRESS_LOS_DET | RX_DT_EN_CTL);
	PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_MISC_CTRL0, PLL_CTRL);
}

/**
* @brief        Wait PMA link
*/
Std_ReturnType serdes_pma_wait_link(void *base, uint32 xpcs, uint8 sec)
{
	return serdes_pcs_wait_bits(base, xpcs, SR_MII_STS,
				    MII_STS_LINK_STS, MII_STS_LINK_STS, 1000U, 1000U * sec);
}

/**
* @brief        Peform lane TX ctr config
*/
static void serdes_pma_configure_tx_ctr(void *base, uint32 xpcs)
{
	PCSBCLRSET(base, xpcs, VR_MII_Gen5_12G_16G_TX_EQ_CTRL0,
			   0x3fU << 8U,
			   0xCU << 8U);
	PCSBCLRSET(base, xpcs, VR_MII_Consumer_10G_TX_TERM_CTRL,
			   0x7U,
			   0x4U);
}

/**
* @brief        Peform lane RX reset
*/
void serdes_pma_issue_rx_reset(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_RX_GENCTRL1, RX_RST_0);
	PCSBCLR(base, xpcs, VR_MII_Gen5_12G_16G_RX_GENCTRL1, RX_RST_0);
}

/**
* @brief        Disable PMA lane
*/
void serdes_pma_lane_disable(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_TX_POWER_STATE_CTRL, TX_DISABLE_0);
	PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_RX_POWER_STATE_CTRL, RX_DISABLE_0);
}

/**
* @brief        Enable PMA lane
*/
void serdes_pma_lane_enable(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, VR_MII_Gen5_12G_16G_TX_POWER_STATE_CTRL, TX_DISABLE_0);
	PCSBCLR(base, xpcs, VR_MII_Gen5_12G_16G_RX_POWER_STATE_CTRL, RX_DISABLE_0);
}

/**
* @brief        Enable PMA loopback
* @details		Looback of analog transceiver to analog receiver.
*/
void serdes_pma_loopback_enable(void *base, uint32 xpcs)
{
	PCSBSET(base, xpcs, SR_MII_CTRL, LBE);
}

/**
* @brief        Disable PMA loopback
* @details		Looback of analog transceiver to analog receiver.
*/
void serdes_pma_loopback_disable(void *base, uint32 xpcs)
{
	PCSBCLR(base, xpcs, SR_MII_CTRL, LBE);
}

/**
* @brief        Configure VCO (for fast PLLA)
* @details      Configure VCO parameters based on PLL. Befre this call it is expected that the lane
				reference is fast PLLA.
*/
static void serdes_pma_1250Mhz_prepare(void *base, uint32 xpcs, enum serdes_clock_fmhz fmhz)
{
	uint16 vco_cal_ld, vco_cal_ref;

	if (fmhz == CLK_100MHZ)
	{
		vco_cal_ld = 1350U;
		vco_cal_ref = 27U;
	}
	else
	{
		vco_cal_ld = 1360U;
		vco_cal_ref = 17U;
	}
	/* RX VCO calibration value */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_VCO_CAL_LD0,
		   0x1fffU,
		   vco_cal_ld);

	/* VCO calibration reference */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_VCO_CAL_REF0,
		   0x3fU,
		   vco_cal_ref);

	/* TX rate baud/4 (baud 1250Mhz) */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL,
		   0x7U,
		   0x2U); /* b010 */

	/* Rx rate baud/8 (baud 1250Mhz) */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL,
		   0x3U,
		   0x3U); /* b11 */

	/* Clear low-frequency operating band */
	PCSBCLR(base, xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, VCO_LOW_FREQ_0);
}

/**
* @brief        Configure VCO (for fast PLLB)
* @details      Configure VCO parameters based on PLL. Befre this call it is expected that the lane
				reference is fast PLLB.
*/
static void serdes_pma_3125Mhz_prepare(void *base, uint32 xpcs, enum serdes_clock_fmhz fmhz)
{
	uint16 vco_cal_ld, vco_cal_ref;

	if (fmhz == CLK_100MHZ)
	{
		vco_cal_ld = 1344U;
		vco_cal_ref = 43U;
	}
	else
	{
		vco_cal_ld = 1350U;
		vco_cal_ref = 27U;
	}
	/* RX VCO calibration value */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_VCO_CAL_LD0,
		   0x1fffU,
		   vco_cal_ld);

	/* VCO calibration reference */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_VCO_CAL_REF0,
		   0x3fU,
		   vco_cal_ref);

	/* TX rate baud  */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_TX_RATE_CTRL,
		   0x7U,
		   0x0U);

	/* Rx rate baud/2 */
	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_RX_RATE_CTRL,
		   0x3U,
		   0x1U);

	/* Set low-frequency operating band */
	PCSBSET(base, xpcs, VR_MII_GEN5_12G_16G_CDR_CTRL, VCO_LOW_FREQ_0);
}

/**
* @brief        Configure slow PLLA parameters
* @details      Initize slow PLLA with either 100Mhz/125Mhz reference.
* @warning      PLL has to be configured from instace with phy control before issuing verndor reset.
*/
static void serdes_pma_mplla_start_cal(void *base, uint32 xpcs, enum serdes_clock_fmhz fmhz)
{
	if (fmhz == CLK_100MHZ)
	{
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
			   REF_RANGE(0x7U) | REF_CLK_DIV2 | REF_MPLLA_DIV2,
			   REF_RANGE(0x3U) | REF_CLK_EN);

		/* Clear multiplier and set it to 25 and enable PPL cal */
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL0,
				       MPLLA_MULTIPLIER_VALUE(0xff) | MPLLA_CAL_DISABLE,
				       MPLLA_MULTIPLIER_VALUE(25U));

	}
	else
	{
		/* Set reference range, dive reference clock by 2, divide the pll output by 2*/
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
			   REF_RANGE(0x7U),
			   REF_RANGE(2U) | REF_CLK_DIV2 | REF_MPLLA_DIV2 | REF_CLK_EN);

		/* Clear multiplier and set it to 80 and enable PPL cal */
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL0,
			   MPLLA_MULTIPLIER_VALUE(0xff) | MPLLA_CAL_DISABLE,
			   MPLLA_MULTIPLIER_VALUE(80U));
	}

	/* Clear the fraction divider todo shouldn't we divide to reach accurate clock ??*/
	PCSBCLR(base, xpcs, VR_MII_GEN5_12G_MPLLA_CTRL1, 0xffe0U);

	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL2,
		   MPLLA_TX_CLK_DIV(0x7U),
		   MPLLA_TX_CLK_DIV(1U) | MPLLA_DIV10_CLK_EN);

	if (fmhz == CLK_100MHZ)
	{
		PCSW16(base, xpcs, VR_MII_GEN5_12G_MPLLA_CTRL3, 357U);
	}
	else
	{
		PCSW16(base, xpcs, VR_MII_GEN5_12G_MPLLA_CTRL3, 43U);
	}
}

/**
* @brief        Configure fast PLLB parameters
* @details      Initize fast PLLB with either 100Mhz/125Mhz reference.
* @warning      PLL has to be configured from instace with phy control before issuing verndor reset.
*/
static void serdes_pma_mpllb_start_cal(void *base, uint32 xpcs, enum serdes_clock_fmhz fmhz)
{
	if (fmhz == CLK_100MHZ)
	{
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
			   REF_RANGE(0x7U) | REF_CLK_DIV2 | REF_MPLLB_DIV2,
			   REF_RANGE(0x3U) | REF_CLK_EN);

		/* Clear multiplier and set it to 25 and enable PPL cal */
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL0,
			   MPLLB_MULTIPLIER(0xffU) | MPPLB_CAL_DISABLE,
			   MPLLB_MULTIPLIER(0x27U));

	}
	else
	{
		/* Set reference range, dive reference clock by 2, divide the pll output by 2*/
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_REF_CLK_CTRL,
			   REF_RANGE(0x7U),
			   REF_RANGE(0x2U) | REF_MPLLB_DIV2 | REF_CLK_DIV2 | REF_CLK_EN);

		/* Clear multiplier and set it to 125 and enable PPL cal */
		PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL0,
			   MPLLB_MULTIPLIER(0xffU) | MPPLB_CAL_DISABLE,
			   MPLLB_MULTIPLIER(125U));
	}

	/* Clear the fraction divider */
	PCSBCLR(base, xpcs, VR_MII_GEN5_12G_MPLLB_CTRL1, 0xffe0U);

	PCSBCLRSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL2,
		   MPLLB_TX_CLK_DIV(0x7U),
		   MPLLB_TX_CLK_DIV(0x5U) | MPLLB_DIV10_CLK_EN);

	if (fmhz == CLK_100MHZ)
	{
		/* Set fraction divider */
		PCSBSET(base, xpcs, VR_MII_GEN5_12G_MPLLB_CTRL1, 0x414U << 5U);

		/* PLL bandwidth */
		PCSW16(base, xpcs, VR_MII_GEN5_12G_MPLLB_CTRL3, 0x66U);
	}
	else
	{

		/* PLL bandwidth */
		PCSW16(base, xpcs, VR_MII_GEN5_12G_MPLLB_CTRL3, 68U);
	}
}

/**
* @brief        Disable PLLA
*/
static void serdes_pma_mplla_stop_cal(void *base, uint32 xpcs)
{
	/* Disable PLLB calibration */
	PCSBSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLA_CTRL0, MPLLA_CAL_DISABLE);
}

/**
* @brief        Disable PLLB
*/
static void serdes_pma_mpllb_stop_cal(void *base, uint32 xpcs)
{
	/* Disable PLLB calibration */
	PCSBSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLLB_CTRL0, MPPLB_CAL_DISABLE);

}

/**
* @brief        Select PLLA as reference for XPCS
*/
static void serdes_pma_select_plla_ref(void *base, uint32 xpcs)
{
	/* Select PLLA */
	PCSBCLR(base, xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, MPLLB_SEL_0);
	/* Enable PLL */
	PCSBSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, MPLL_EN_0);
}

/**
* @brief        Select PLLB as reference for XPCS
*/
static void serdes_pma_select_pllb_ref(void *base, uint32 xpcs)
{
	/* Select PLLB */
	PCSBSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, MPLLB_SEL_0);
	/* Enable PLL */
	PCSBSET(base, xpcs, VR_MII_GEN5_12G_16G_MPLL_CMN_CTRL, MPLL_EN_0);
}

/**
* @brief        Update transceiver settings and logic
* @details      This function commits new parameters to transceiver and reinitializes XPCS.
* @warning      The PLL that is transferred to has to be initialized before vendor reset.
*/
static Std_ReturnType serdes_bifurcation_pll_transit(void *base, uint32 xpcs, bool_t plla)
{
	Std_ReturnType ret = E_OK;

	/* Note, if this seqence fails you have to reinitialize serdes */

	/* Signal that clock are not available */
	PCSBCLR(base, xpcs, VR_MII_Gen5_12G_16G_TX_GENCTRL1, TX_CLK_RDY_0);

	if(TRUE == plla)
	{
		/* Request PLLA */
		serdes_pma_select_plla_ref(base, xpcs);
	}
	else
	{
		/* Request PLLB */
		serdes_pma_select_pllb_ref(base, xpcs);
	}

	/* Initiate transmitter TX reconfiguration request */
	PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_TX_GENCTRL2, TX_REQ_0);

	/* Wait for transmitter to reconfigure */
	ret = serdes_pcs_wait_bits(base, xpcs, VR_MII_Gen5_12G_16G_TX_GENCTRL2,
				   TX_REQ_0, 0U,
				   100U, 100U);
	if(E_OK == ret)
	{
				/* Initiate transmitter RX reconfiguration request */
		PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_RX_GENCTRL2, RX_REQ_0);

		/* Wait for transmitter to reconfigure */
		ret = serdes_pcs_wait_bits(base, xpcs, VR_MII_Gen5_12G_16G_RX_GENCTRL2,
					   RX_REQ_0, 0U,
					   100U, 100U);

		if(E_OK == ret)
		{

			/* Signal that clock are available */
			PCSBSET(base, xpcs, VR_MII_Gen5_12G_16G_TX_GENCTRL1, TX_CLK_RDY_0);

			/* Flush internal logic */
			PCSBSET(base, xpcs, VR_MII_DIG_CTRL1, INIT);

			/* Wait for init */
			ret = serdes_pcs_wait_bits(base, xpcs, VR_MII_DIG_CTRL1,
						   INIT, 0U,
						   100U, 100U);
		}
	}

	return ret;
}

/**
* @brief        Transit to fast PLLB
* @details      This function allows transfer between PLLS without resting the phy.
* @warning      The PLL that is transferred to has to be initialized before vendor reset.
*/
Std_ReturnType serdes_bifurcation_pll_transit_to_3125Mhz(void *base, uint32 xpcs, enum serdes_clock_fmhz fmhz)
{
	/* Switch PCS logic to 2.5G */
	serdes_pcs_set_2500M_mode(base, xpcs);

	/* Switch PMA logic to 3.125Ghz */
	serdes_pma_3125Mhz_prepare(base, xpcs, fmhz);

	/* Do the transit to PLLB */
	return serdes_bifurcation_pll_transit(base, xpcs, FALSE);
}

/**
* @brief        Transit to slow PLLA
* @details      This function allows transfer between PLLS without resting the phy.
* @warning      The PLL that is transferred to has to be initialized before vendor reset.
*/
Std_ReturnType serdes_bifurcation_pll_transit_to_1250Mhz(void *base, uint32 xpcs, enum serdes_clock_fmhz fmhz)
{
	/* Switch PCS logic to 1G */
	serdes_pcs_set_1000M_mode(base, xpcs);

	/* Switch PMA logic to 1.250Ghz */
	serdes_pma_1250Mhz_prepare(base, xpcs, fmhz);

	/* Do the transit PLLA */
	return serdes_bifurcation_pll_transit(base, xpcs, TRUE);
}

/**
* @brief        Initialize PLLs and Electrical parameters
* @details      This function allows all supported configurations on serdes.
* @warning      This function doesnt have parameter check so, if platform has some restrictions
				it should be verified before calling this function.
				Currently only supported mode is SGMII bifurcation (Serdes mode 3).
                SGMII+PCIe bifurcation is not supported.
*/
void serdes_pcs_pma_init_gen2(void *base, enum serdes_clock_fmhz fmhz, uint32 init_flags)
{

	uint32 xpcs_phy_ctr = 0U;

	if ((init_flags & PHY_CTRL_XPCS_OWNED) != 0U)
	{
		if ((init_flags & PHY_CTRL_XPCS0_OWNED) != 0U)
		{
			xpcs_phy_ctr = 0U;
		}
		else if ((init_flags & PHY_CTRL_XPCS1_OWNED) != 0U)
		{
			xpcs_phy_ctr = 1U;
		}
	}

	/* Set bypass flag in case of internal clocks */
	if (((init_flags & PHY_CLK_INT) != 0U) && ((init_flags & (XPCS0_OWNED)) != 0U))
	{
		PCSBSET(base, 0U, VR_MII_DIG_CTRL1, EN_VSMMD1 | BYP_PWRUP);
	}
	else if ((init_flags & (XPCS0_OWNED)) != 0U)
	{
		PCSBCLRSET(base, 0U, VR_MII_DIG_CTRL1, BYP_PWRUP, EN_VSMMD1);
	}

	if (((init_flags & PHY_CLK_INT) != 0U) && ((init_flags & (XPCS1_OWNED)) != 0U))
	{
		PCSBSET(base, 1U, VR_MII_DIG_CTRL1, EN_VSMMD1 | BYP_PWRUP);
	}
	else if ((init_flags & (XPCS1_OWNED)) != 0U)
	{
		PCSBCLRSET(base, 1U, VR_MII_DIG_CTRL1, BYP_PWRUP, EN_VSMMD1);
	}

	if ((init_flags & XPCS0_2500M) != 0U)
	{
		serdes_pma_configure_tx_ctr(base, 0U);
		serdes_pcs_set_2500M_mode(base, 0U);
		serdes_pma_select_pllb_ref(base, 0U);
	}
	else if ((init_flags & XPCS0_1000M) != 0U)
	{
		serdes_pma_configure_tx_ctr(base, 0U);
		serdes_pcs_set_1000M_mode(base, 0U);
		serdes_pma_select_plla_ref(base, 0U);
	}


	if ((init_flags & XPCS1_2500M) != 0U)
	{
		serdes_pma_configure_tx_ctr(base, 1U);
		serdes_pcs_set_2500M_mode(base, 1U);
		serdes_pma_select_pllb_ref(base, 1U);
	}
	else if ((init_flags & XPCS1_1000M) != 0U)
	{
		serdes_pma_configure_tx_ctr(base, 1U);
		serdes_pcs_set_1000M_mode(base, 1U);
		serdes_pma_select_plla_ref(base, 1U);
	}

	/* Using external clock reference */
	if (((init_flags & PHY_CTRL_XPCS_OWNED) != 0U) && (init_flags & PHY_CLK_INT) == 0U)
	{
		PCSBSET(base, xpcs_phy_ctr, VR_MII_GEN5_12G_16G_REF_CLK_CTRL, REF_USE_PAD);
	}
	else if ((init_flags & PHY_CTRL_XPCS_OWNED) != 0U)
	{
		PCSBCLR(base, xpcs_phy_ctr, VR_MII_GEN5_12G_16G_REF_CLK_CTRL, REF_USE_PAD);
	}

	/* Start PLLA cal */
	if (((init_flags & PHY_CTRL_XPCS_OWNED) != 0U) && (init_flags & PLLA_CAL_EN) != 0U)
	{
		/* Configure PLLA and start calibration */
		serdes_pma_mplla_start_cal(base, xpcs_phy_ctr, fmhz);
	}

	/* Start PLLB cal */
	if (((init_flags & PHY_CTRL_XPCS_OWNED) != 0U) && (init_flags & PLLB_CAL_EN) != 0U)
	{
		serdes_pma_mpllb_start_cal(base, xpcs_phy_ctr, fmhz);
	}

	/* Disable PLLA, if requested */
	if (((init_flags & PHY_CTRL_XPCS_OWNED) != 0U) && (init_flags & PLLA_CAL_DIS) != 0U) {
		serdes_pma_mplla_stop_cal(base, xpcs_phy_ctr);
	}

	/* Disable PLLB, if requested */
	if (((init_flags & PHY_CTRL_XPCS_OWNED) != 0U) && (init_flags & PLLB_CAL_DIS) != 0U)
	{
		serdes_pma_mpllb_stop_cal(base, xpcs_phy_ctr);
	}

	/* Configure lower layers to use correct speed */
	if ((init_flags & XPCS0_2500M) != 0U)
	{
		serdes_pma_3125Mhz_prepare(base, 0U, fmhz);
	}
	else if ((init_flags & XPCS0_1000M) != 0U)
	{
		serdes_pma_1250Mhz_prepare(base, 0U, fmhz);
	}

	/* Configure lower layers to use correct speed */
	if ((init_flags & XPCS1_2500M) != 0U)
	{
		serdes_pma_3125Mhz_prepare(base, 1U, fmhz);
	}
	else if ((init_flags & XPCS1_1000M) != 0U)
	{
		serdes_pma_1250Mhz_prepare(base, 1U, fmhz);
	}

	if ((init_flags & XPCS0_DIS) != 0U)
	{
		/* Skip init, if we don't care about the lane */
		PCSBSET(base, 0U, VR_MII_DIG_CTRL1, EN_VSMMD1 | BYP_PWRUP);
		serdes_pma_lane_disable(base, 0U);
	}

	if ((init_flags & XPCS1_DIS) != 0U)
	{
		/* Skip init, if we don't care about the lane */
		PCSBSET(base, 1U, VR_MII_DIG_CTRL1, EN_VSMMD1 | BYP_PWRUP);
		serdes_pma_lane_disable(base, 1U);
	}

	/* Clear bypass flag in case of internal clocks */
	if (((init_flags & PHY_CLK_INT) != 0U) &&
	    ((init_flags & (XPCS0_OWNED)) != 0U) &&
	    ((init_flags & XPCS0_DIS) == 0U))
	{
		PCSBCLR(base, 0U, VR_MII_DIG_CTRL1, BYP_PWRUP);
	}

	if (((init_flags & PHY_CLK_INT) != 0U) &&
	    ((init_flags & (XPCS1_OWNED)) != 0U) &&
	    ((init_flags & XPCS1_DIS) == 0U))
	{
		PCSBCLR(base, 1U, VR_MII_DIG_CTRL1, BYP_PWRUP);
	}
}

/**
* @brief        Issue vendor specific reset to serdes
* @details      This function issues vendor specific reset and waits for power good.
* @warning      Currently only supported mode is SGMII bifurcation (Serdes mode 3).
                SGMII+PCIe bifurcation is not supported.
*/
void serdes_pcs_reset_issue(void *serdes_base, uint32 xpcs0_base, uint32 xpcs1_base)
{
	/* Issue vendor reset */
	serdes_pcs_issue_vreset(serdes_base, xpcs1_base);
	serdes_pcs_issue_vreset(serdes_base, xpcs0_base);
}

/**
* @brief        Wait until serdes reset is finished
* @details      This function waits for HW power up status after vendor specific reset was issued.
				After power up status is reached it issues transeiver RX reset.
* @warning      Currently only supported mode is SGMII bifurcation (Serdes mode 3).
                SGMII+PCIe bifurcation is not supported.
*/
Std_ReturnType serdes_pcs_reset_finalize(void *serdes_base, uint32 xpcs0_base, uint32 xpcs1_base)
{
	Std_ReturnType ret = E_OK;

	/* If single step fails here, the serdes is not usable. */

	/* Wait for reset clear */
	if (E_OK != serdes_pcs_wait_for_vreset(serdes_base, xpcs1_base))
	{
		ret = E_NOT_OK;
	}
	else
	{
		/*	Wait for XPCS power up */
		if (E_OK != serdes_pcs_wait_for_power_good(serdes_base, xpcs1_base)) {
			ret = E_NOT_OK;
		}
		else
		{
			/* Wait for reset clear (Phy control) */
			if (E_OK != serdes_pcs_wait_for_vreset(serdes_base, xpcs0_base))
			{
				ret = E_NOT_OK;
			}
			else
			{
				/*	Wait for XPCS power up (Phy control) */
				if (E_OK != serdes_pcs_wait_for_power_good(serdes_base, xpcs0_base))
				{
					ret = E_NOT_OK;
				}
				else
				{
					/* Issue RX reset */
					serdes_pma_issue_rx_reset(serdes_base, xpcs1_base);
					serdes_pma_issue_rx_reset(serdes_base, xpcs0_base);
				}
			}
		}
	}

	return ret;
}

#define ETH_43_PFE_STOP_SEC_CODE
#include "Eth_43_PFE_MemMap.h"

