/*
 *  Copyright 2021-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 "Mcal.h"
#include "Eth_43_PFE.h"

#include "serdes_xpcs.h"
#include "sample_app_ethswt_g2_serdes.h"

#define S32G_SERDES_0_BASE          ((uint8*)0x40400000U)
#define S32G_SERDES_1_BASE          ((uint8*)0x44100000U)

/* Seredes SS */
#define S32G_SERDES_SUB_SYS_OFF     (0x80000U)
#define SS_RW_REG_0                 (S32G_SERDES_SUB_SYS_OFF + 0xf0U)
#define CLKEN                       (1U << 23U)

/* MC RGM */
#define MC_RGM_BASE                 (0x40078000U)

#define PRST_PCIE_0_SERDES      4U
#define PRST_PCIE_0_FUNC        5U
#define PRST_PCIE_1_SERDES      16U
#define PRST_PCIE_1_FUNC        17U
#define PRST0_0                 (MC_RGM_BASE + 0x40U)
#define PSTAT0_0                (MC_RGM_BASE + 0x140U)

#define S32G_SERDES_CNT             (2U)
#define S32G_SERDES_XPCS_CNT        (2U)

/* Additional flags for serdes eg. for internal clock */
#define S32G_SERDES_0_ADD_FLAGS   (0U)/*(PHY_CLK_INT)*/
#define S32G_SERDES_1_ADD_FLAGS   (0U)/*(PHY_CLK_INT)*/

/* Reference clock for serdes option is CLK_125MHZ/CLK_100MHZ */
#define S32G_SERDES_0_REF_CLK   CLK_125MHZ
#define S32G_SERDES_1_REF_CLK   CLK_125MHZ

/* Enable SGMII AN (Note currently no action is taken based on AN)*/
#define S32G_SERDES_0_PCS0_SGMII_AN  (STD_OFF)  /*GMAC*/
#define S32G_SERDES_0_PCS1_SGMII_AN  (STD_OFF)  /*PFE2*/
#define S32G_SERDES_1_PCS0_SGMII_AN  (STD_OFF)  /*PFE0*/
#define S32G_SERDES_1_PCS1_SGMII_AN  (STD_OFF)  /*PFE1*/

#define ETH_43_PFE_START_SEC_VAR_INIT_8
#include "Eth_43_PFE_MemMap.h"

Std_ReturnType initSeqSuccesSer0 = E_NOT_OK;
Std_ReturnType initSeqSuccesSer1 = E_NOT_OK;

#define ETH_43_PFE_STOP_SEC_VAR_INIT_8
#include "Eth_43_PFE_MemMap.h"

#define ETH_43_PFE_START_SEC_CODE
#include "Eth_43_PFE_MemMap.h"

static void Serdes_InitSpeedDuplex(void *serdes_base, uint32 pcs, pfe_emac_speed_t speed, pfe_emac_duplex_t duplex)
{
    uint32 speedSelect = 0;

    if(EMAC_DUPLEX_FULL == duplex)
    {
        serdes_pcs_set_fd(serdes_base, pcs);
    }
    else
    {
        serdes_pcs_set_hd(serdes_base, pcs);
    }

    if(EMAC_SPEED_10_MBPS == speed)
    {
        speedSelect = 100U;
    }
    else if(EMAC_SPEED_100_MBPS == speed)
    {
        speedSelect = 10U;
    }
    else
    {
        /* 1G or 2.5G */
       speedSelect = 1U;
    }

    serdes_pcs_speed_select(serdes_base, pcs, speedSelect);
}

static void wait_prst_assert(int prst_reg)
{
    *(volatile uint32 *)((uintptr_t)(PRST0_0)) |= (1U << prst_reg);

    while ((*(volatile uint32 *)((uintptr_t)(PSTAT0_0)) & (1U << prst_reg)) != (uint32)(1U << prst_reg));
}

static void wait_prst_deassert(int prst_reg)
{
    *(volatile uint32 *)((uintptr_t)(PRST0_0)) &= ~(1U << prst_reg);

    while ((*(volatile uint32 *)((uintptr_t)(PSTAT0_0)) & (1U << prst_reg)) != 0U);
}

/**
* @brief        Change serdes mode and resets the SerDes
* @details      This function configures serdes mode to SGMII bifrucation and issues
                serdedes0/1 functional and PCIe reset.
* @warning      Currently only supported mode is SGMII bifurcation (Serdes mode 3).
                SGMII+PCIe bifurcation is not supported.
*/
static void Serdes_SsModeConfigure(uint8 mode, uint32 intclks0, uint32 intclks1)
{
    uint32 config0 = 0;
    uint32 config1 = 0;
    volatile uint32 wait = 80000U;
    config0 = mode;
    config1 = mode;
    if (0 != (intclks0 & PHY_CLK_INT))
    {
        config0 |= CLKEN;
    }
    if (0 != (intclks1 & PHY_CLK_INT))
    {
        config1 |= CLKEN;
    }
    /* Assert Serdes + pcie func reset*/
    wait_prst_assert(PRST_PCIE_0_SERDES);
    wait_prst_assert(PRST_PCIE_0_FUNC);
    /* Serdes mode */
    *(uint32 *)((uintptr_t)S32G_SERDES_0_BASE + SS_RW_REG_0) = config0;
    wait = 100U;
    while(wait--);
    wait_prst_deassert(PRST_PCIE_0_SERDES);
    wait_prst_deassert(PRST_PCIE_0_FUNC);
    wait_prst_assert(PRST_PCIE_1_SERDES);
    wait_prst_assert(PRST_PCIE_1_FUNC);
    *(uint32 *)((uintptr_t)S32G_SERDES_1_BASE + SS_RW_REG_0) = config1;
    wait = 100U;
    while(wait--);
    wait_prst_deassert(PRST_PCIE_1_SERDES);
    wait_prst_deassert(PRST_PCIE_1_FUNC);
}

/**
* @brief        Initialize serdes and xpcs logic
* @details      This function initializes PLLs, elecrtical parameters and xpcs logic.
* @note         Function should be called after Serdes_Reset. If some change is required (PLLs,PMA),
                Serdes_Reset should be called again before changes.
*/
void Serdes_Init(const Eth_43_PFE_ConfigType * CfgPtr)
{
    const Eth_43_PFE_ConfigType * Eth_43_PFE_CfgPtr;
    uint32 serdesInitFlags[2] = { 0U };
    uint8 u8CtrlCount;
    pfe_emac_speed_t pcsSpeed[4] = { EMAC_SPEED_1000_MBPS, EMAC_SPEED_1000_MBPS, EMAC_SPEED_1000_MBPS, EMAC_SPEED_1000_MBPS };
    pfe_emac_duplex_t pcsDuplex[4] = { EMAC_DUPLEX_FULL, EMAC_DUPLEX_FULL, EMAC_DUPLEX_FULL, EMAC_DUPLEX_FULL };

#if STD_ON == ETH_43_PFE_PRECOMPILE_SUPPORT
        Eth_43_PFE_CfgPtr = &Eth_43_PFE_Config;
        (void)CfgPtr;
#else
    if (NULL_PTR != CfgPtr)
    {
        Eth_43_PFE_CfgPtr = CfgPtr;
#endif

    /* Disable GMAC */
    serdesInitFlags[0] |= XPCS0_DIS;

    /* Disable PFE lanes */
    serdesInitFlags[0] |= XPCS1_DIS;
    serdesInitFlags[1] |= XPCS0_DIS | XPCS1_DIS;

    /* Decode PFE cfg to g2 platform serdes */
    for (u8CtrlCount = 0U; u8CtrlCount < (uint8)ETH_43_PFE_NUM_CONTROLLER_CFG; u8CtrlCount++)
    {
        if (EMAC_MODE_SGMII == Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlMiiType)
        {
            if(0U == (Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlHwPort[3]-'0'))
            {
                if(EMAC_SPEED_2500_MBPS == Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlSpeed)
                {
                    serdesInitFlags[1] |= PLLA_CAL_EN | PLLB_CAL_EN | XPCS0_2500M;
                }
                else
                {
                    serdesInitFlags[1] |= PLLA_CAL_EN | XPCS0_1000M;
                }

                /* Enable PFE0 lane */
                serdesInitFlags[1] &= ~XPCS0_DIS;
                pcsSpeed[2] = Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlSpeed;
                pcsDuplex[2] = Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlDuplex;
            }

            if(1U == (Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlHwPort[3]-'0'))
            {
                serdesInitFlags[1] |= PLLA_CAL_EN | XPCS1_1000M;
                /* Enable PFE1 lane */
                serdesInitFlags[1] &= ~XPCS1_DIS;
                pcsSpeed[3] = Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlSpeed;
                pcsDuplex[3] = Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlDuplex;
            }

            if(2U == (Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlHwPort[3]-'0'))
            {
                serdesInitFlags[0] |= PLLA_CAL_EN | XPCS1_1000M;
                /* Enable PFE2 lane */
                serdesInitFlags[0] &= ~XPCS1_DIS;
                pcsSpeed[1] = Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlSpeed;
                pcsDuplex[1] = Eth_43_PFE_CfgPtr->pController[u8CtrlCount]->EthCtrlDuplex;
            }
        }
    }

    /* in mode 3 PHY is always owned by XPCS0 */
    serdesInitFlags[0] |= S32G_SERDES_0_ADD_FLAGS | PHY_CTRL_XPCS0_OWNED;
    serdesInitFlags[1] |= S32G_SERDES_1_ADD_FLAGS | PHY_CTRL_XPCS0_OWNED;

    /* Configure SerDes Working modes and bring both SerDes out of reset */
    Serdes_SsModeConfigure(3U, serdesInitFlags[0], serdesInitFlags[1]);

    /* Check if we have clk */
    if (0 == (serdesInitFlags[0] & PHY_CLK_INT))
    {
        if (E_OK == serdes_pcs_wait_for_power_good(S32G_SERDES_0_BASE, 0U))
        {
            if (E_OK == serdes_pcs_wait_for_power_good(S32G_SERDES_0_BASE, 1U))
            {
                initSeqSuccesSer0 = E_OK;
            }
        }
    }
    else
    {
        initSeqSuccesSer0 = E_OK;
    }

    if (E_OK == initSeqSuccesSer0)
    {
        serdes_pma_loopback_enable(S32G_SERDES_0_BASE, 0U);
        serdes_pma_loopback_enable(S32G_SERDES_0_BASE, 1U);

        serdes_pcs_pma_init_gen2(S32G_SERDES_0_BASE, S32G_SERDES_0_REF_CLK, serdesInitFlags[0]);
        serdes_pcs_reset_issue(S32G_SERDES_0_BASE, 0U, 1U);
    }

        /* Check if we have clk */
    if (0 == (serdesInitFlags[1] & PHY_CLK_INT))
    {
        if (E_OK == serdes_pcs_wait_for_power_good(S32G_SERDES_1_BASE, 0U))
        {
            if (E_OK == serdes_pcs_wait_for_power_good(S32G_SERDES_1_BASE, 1U))
            {
                initSeqSuccesSer1 = E_OK;
            }
        }
    }
    else
    {
        initSeqSuccesSer1 = E_OK;
    }

    if (E_OK == initSeqSuccesSer1)
    {
        serdes_pma_loopback_enable(S32G_SERDES_1_BASE, 0U);
        serdes_pma_loopback_enable(S32G_SERDES_1_BASE, 1U);

        serdes_pcs_pma_init_gen2(S32G_SERDES_1_BASE, S32G_SERDES_1_REF_CLK, serdesInitFlags[1]);
        serdes_pcs_reset_issue(S32G_SERDES_1_BASE, 0U, 1U);
    }

    if((E_OK == initSeqSuccesSer0) &&
      ((XPCS0_DIS | XPCS1_DIS) != (serdesInitFlags[0U] & (XPCS0_DIS | XPCS1_DIS))))
    {
        initSeqSuccesSer0 = serdes_pcs_reset_finalize(S32G_SERDES_0_BASE, 0U, 1U);
    }

    if((E_OK == initSeqSuccesSer1) &&
      ((XPCS0_DIS | XPCS1_DIS) != (serdesInitFlags[1U] & (XPCS0_DIS | XPCS1_DIS))))
    {
        initSeqSuccesSer1 = serdes_pcs_reset_finalize(S32G_SERDES_1_BASE, 0U, 1U);
    }


    if (E_OK == initSeqSuccesSer0)
    {
        if (0U == (serdesInitFlags[0U] & XPCS0_DIS))
        {
            serdes_pma_loopback_disable(S32G_SERDES_0_BASE, 0U);
        }

        if (0U == (serdesInitFlags[0U] & XPCS1_DIS))
        {
            serdes_pma_loopback_disable(S32G_SERDES_0_BASE, 1U);
        }

        /* Bus width change on speed change */
        serdes_pcs_mii_bus_control_enable(S32G_SERDES_0_BASE, 0U);
        serdes_pcs_mii_bus_control_enable(S32G_SERDES_0_BASE, 1U);

        /* Disable AN*/
        serdes_pcs_an_disable(S32G_SERDES_0_BASE, 0U);
        serdes_pcs_an_disable(S32G_SERDES_0_BASE, 1U);

        /* Configure PFE speed and duplex */
        Serdes_InitSpeedDuplex(S32G_SERDES_0_BASE, 0U, pcsSpeed[0], pcsDuplex[0]);
        Serdes_InitSpeedDuplex(S32G_SERDES_0_BASE, 1U, pcsSpeed[1], pcsDuplex[1]);

        /* Additional tuning of AN can be done here */
    #if STD_ON == S32G_SERDES_0_PCS0_SGMII_AN
        serdes_pcs_an_set_link_timer(S32G_SERDES_0_BASE, 0U, 0x2fafU);
        serdes_pcs_an_auto_sw_disable(S32G_SERDES_0_BASE, 0U);
        serdes_pcs_an_enable(S32G_SERDES_0_BASE, 0U);
        serdes_pcs_an_restart(S32G_SERDES_0_BASE, 0U);
    #endif

    #if STD_ON == S32G_SERDES_0_PCS1_SGMII_AN
        serdes_pcs_an_set_link_timer(S32G_SERDES_0_BASE, 1U, 0x2fafU);
        serdes_pcs_an_auto_sw_disable(S32G_SERDES_0_BASE, 1U);
        serdes_pcs_an_enable(S32G_SERDES_0_BASE, 1U);
        serdes_pcs_an_restart(S32G_SERDES_0_BASE, 1U);
    #endif
    }

    if (E_OK == initSeqSuccesSer1)
    {
        if (0U == (serdesInitFlags[1U] & XPCS0_DIS))
        {
            serdes_pma_loopback_disable(S32G_SERDES_1_BASE, 0U);
        }

        if (0U == (serdesInitFlags[1U] & XPCS1_DIS))
        {
            serdes_pma_loopback_disable(S32G_SERDES_1_BASE, 1U);
        }

        /* Bus width change on speed change */
        serdes_pcs_mii_bus_control_enable(S32G_SERDES_1_BASE, 0U);
        serdes_pcs_mii_bus_control_enable(S32G_SERDES_1_BASE, 1U);

        /* Disable AN*/
        serdes_pcs_an_disable(S32G_SERDES_1_BASE, 0U);
        serdes_pcs_an_disable(S32G_SERDES_1_BASE, 1U);

        Serdes_InitSpeedDuplex(S32G_SERDES_1_BASE, 0U, pcsSpeed[2], pcsDuplex[2]);
        Serdes_InitSpeedDuplex(S32G_SERDES_1_BASE, 1U, pcsSpeed[3], pcsDuplex[3]);

        /* Additional tuning of AN can be done here */
    #if STD_ON == S32G_SERDES_1_PCS0_SGMII_AN
        serdes_pcs_an_set_link_timer(S32G_SERDES_1_BASE, 0U, 0x2fafU);
        serdes_pcs_an_auto_sw_disable(S32G_SERDES_1_BASE, 0U);
        serdes_pcs_an_enable(S32G_SERDES_1_BASE, 0U);
        serdes_pcs_an_restart(S32G_SERDES_1_BASE, 0U);
    #endif

    #if STD_ON == S32G_SERDES_1_PCS1_SGMII_AN
        serdes_pcs_an_set_link_timer(S32G_SERDES_1_BASE, 1U, 0x2fafU);
        serdes_pcs_an_auto_sw_disable(S32G_SERDES_1_BASE, 1U);
        serdes_pcs_an_enable(S32G_SERDES_1_BASE, 1U);
        serdes_pcs_an_restart(S32G_SERDES_1_BASE, 1U);
    #endif

    }

#if STD_OFF == ETH_43_PFE_PRECOMPILE_SUPPORT
    }
#endif
}

#define ETH_43_PFE_STOP_SEC_CODE
#include "Eth_43_PFE_MemMap.h"

