/*
 *  Copyright (C) 2023 Texas Instruments Incorporated
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *    Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 *    Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 *    Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <stdlib.h>
#include "ti_drivers_config.h"
#include "ti_drivers_open_close.h"
#include "ti_board_open_close.h"
#include <drivers/device_manager/sciclient.h>
#include <drivers/bootloader.h>
#include <drivers/pinmux.h>
#include <drivers/gtc.h>
#include <sdl/include/sdl_types.h>
#include <sdl/dpl/sdl_dpl.h>
#include <sdl/sdl_pbist.h>
#include <sdl/sdl_lbist.h>

/*  In this sample bootloader, we load appimages for RTOS/Baremetal and Linux at different offset
    i.e the appimage for Linux (for A53) and RTOS/Baremetal (for R5, MCU R5) is flashed at different offset in flash

    Here at one flash offset, there is a multi-core .appimage that holds RPRC for MCU R5 and R5
    and another .appimage that holds the linux binaries(ATF, OPTEE, A53-SPL) at another offset.

    When flashing make sure to flash images to below offset using the flash tool.

    RTOS/Baremetal appimage (MCU R5 cores) flash at offset 0x100000 of flash
    Linux appimage (for A53) flash at offset 0xC00000 of flash
*/

/*
 * Timeout for the PBIST/LBIST completion
 */
#define SDL_BIST_MAX_TIMEOUT_VALUE       (10000000u)

/* Enable/Disable MCU LBIST on SBL */
#define ENABLE_MCU_LBIST                 (0u)

void flashFixUpOspiBoot(OSPI_Handle oHandle, Flash_Handle fHandle);

/* This buffer needs to be defined for OSPI nand boot in case of HS device for
   image authentication
   The size of the buffer should be large enough to accomodate the appimage */
uint8_t gAppimage[0x800000] __attribute__ ((section (".bss.app"), aligned (128)));

/* call this API to stop the booting process and spin, do that you can connect
 * debugger, load symbols and then make the 'loop' variable as 0 to continue execution
 * with debugger connected.
 */

#include <drivers/gpio.h>
/* GPIO PIN Macros */
#define CONFIG_GPIO0_BASE_ADDR (CSL_MCU_GPIO0_BASE)
#define CONFIG_GPIO0_PIN (17)
#define CONFIG_GPIO0_DIR (GPIO_DIRECTION_OUTPUT)
#define CONFIG_GPIO0_TRIG_TYPE (GPIO_TRIG_TYPE_NONE)
#define CONFIG_GPIO_NUM_INSTANCES (1U)

static Pinmux_PerCfg_t gPinMuxMcuCfg[] = {
    /* MCU_GPIO0 pin config MCU_GPIO0_17 -> MCU_I2C0_SCL (E11) */
    {
        PIN_MCU_I2C0_SCL,
        ( PIN_MODE(7) | PIN_INPUT_ENABLE | PIN_PULL_DISABLE )
    },
    {PINMUX_END, PINMUX_END}
};

void loop_forever()
{
    volatile uint32_t loop = 1;
    while(loop)
        ;
}

int32_t App_loadSelfcoreImage(Bootloader_LoadImageParams *bootLoadParams)
{
	int32_t status = SystemP_FAILURE;

    if(bootLoadParams->bootHandle != NULL)
    {
        status = Bootloader_parseMultiCoreAppImage(bootLoadParams->bootHandle, &bootLoadParams->bootImageInfo);

        if(status == SystemP_SUCCESS)
        {
            (&bootLoadParams->bootImageInfo)->cpuInfo[CSL_CORE_ID_WKUP_R5FSS0_0].clkHz = Bootloader_socCpuGetClkDefault(CSL_CORE_ID_WKUP_R5FSS0_0);
            Bootloader_profileAddCore(CSL_CORE_ID_WKUP_R5FSS0_0);
            status = Bootloader_loadSelfCpu(bootLoadParams->bootHandle, &((&bootLoadParams->bootImageInfo)->cpuInfo[CSL_CORE_ID_WKUP_R5FSS0_0]));
        }
    }

    return status;
}

int32_t App_boardDriversOpen()
{
    int32_t status = SystemP_SUCCESS;

    gFlashHandle[CONFIG_FLASH_SBL] = Flash_open(CONFIG_FLASH_SBL, &gFlashParams[CONFIG_FLASH_SBL]);
    if(NULL == gFlashHandle[CONFIG_FLASH_SBL])
    {
        DebugP_logError("FLASH open failed for instance %d !!!\r\n", CONFIG_FLASH_SBL);
        status = SystemP_FAILURE;
    }

    return status;
}

void App_driversOpen()
{
    gOspiHandle[CONFIG_OSPI_SBL] = OSPI_open(CONFIG_OSPI_SBL, &gOspiParams[CONFIG_OSPI_SBL]);
    if(NULL == gOspiHandle[CONFIG_OSPI_SBL])
    {
        DebugP_logError("OSPI open failed for instance %d !!!\r\n", CONFIG_OSPI_SBL);
    }

    gUartHandle[CONFIG_UART_SBL] = UART_open(CONFIG_UART_SBL, &gUartParams[CONFIG_UART_SBL]);
    if(NULL == gUartHandle[CONFIG_UART_SBL])
    {
        DebugP_logError("UART open failed for instance %d !!!\r\n", CONFIG_UART_SBL);
    }
}

int32_t App_waitForMcuPbist()
{
    int32_t status = SystemP_FAILURE;
    int32_t timeoutCount = 0;

    if (!Bootloader_socIsMCUResetIsoEnabled())
    {
        /* wait for the PBIST to be completed */
        while(timeoutCount < SDL_BIST_MAX_TIMEOUT_VALUE)
        {
            if(PBIST_DONE == SDL_SBL_PBIST_checkDone(SDL_PBIST_INST_MCU))
            {
                status = SystemP_SUCCESS;
                break;
            }
            timeoutCount++;
        }
    }
    else
    {
        status = SystemP_SUCCESS;
    }

    return status;
}


int32_t App_startMcuLbist()
{
    int32_t status = SystemP_FAILURE;

    /* Start LBIST if MCU reset isolation is not enabled */
    if (!Bootloader_socIsMCUResetIsoEnabled())
    {
        if (SDL_LBIST_selfTest(LBIST_MCU_R5F, SDL_LBIST_TEST) == SDL_PASS)
        {
            status = SystemP_SUCCESS;
        }
    }
    else
    {
        status = SystemP_SUCCESS;
    }
    return status;
}

int32_t App_waitForMcuLbist()
{
    int32_t status = SystemP_FAILURE;
    int32_t timeoutCount = 0;

    if (!Bootloader_socIsMCUResetIsoEnabled())
    {
        /* wait for the LBIST to be completed */
        while(timeoutCount < SDL_BIST_MAX_TIMEOUT_VALUE)
        {
            if(LBIST_DONE == SDL_LBIST_checkDone(LBIST_MCU_R5F))
            {
                status = SystemP_SUCCESS;
                break;
            }
            timeoutCount++;
        }

        if (status == SystemP_SUCCESS)
        {
            status = SDL_LBIST_selfTest(LBIST_MCU_R5F, SDL_LBIST_TEST_RELEASE);
            timeoutCount = 0;
            while(timeoutCount < SDL_BIST_MAX_TIMEOUT_VALUE)
            {
                if(LBIST_DONE == SDL_LBIST_checkDone(LBIST_MCU_R5F))
                {
                    status = SystemP_SUCCESS;
                    break;
                }
                timeoutCount++;
            }
        }
    }
    else
    {
        status = SystemP_SUCCESS;
    }

    return status;
}

int main()
{
    Pinmux_config(gPinMuxMcuCfg, PINMUX_DOMAIN_ID_MCU); // Configure PinMux
    GPIO_setDirMode(CONFIG_GPIO0_BASE_ADDR, CONFIG_GPIO0_PIN, CONFIG_GPIO0_DIR);    //Set GPIO direction
    GPIO_pinWriteLow(CONFIG_GPIO0_BASE_ADDR, CONFIG_GPIO0_PIN); // Set GPIO state to LOW
    
    int32_t status;

    Bootloader_profileReset();

    Bootloader_socWaitForFWBoot();
    status = Bootloader_socOpenFirewalls();

    DebugP_assertNoLog(status == SystemP_SUCCESS);

    System_init();
    Module_clockSBLEnable();
    Module_clockSBLSetFrequency();
    Bootloader_profileAddProfilePoint("System_init");


    /* wait for PBIST completion */
    status = App_waitForMcuPbist();
    Bootloader_profileAddProfilePoint("App_waitForMcuPbist");

#if (ENABLE_MCU_LBIST == 1u)
    /* start MCU LBIST*/
    status = App_startMcuLbist();
#endif
    Board_init();
    Bootloader_profileAddProfilePoint("Board_init");

    Drivers_open();
    Bootloader_profileAddProfilePoint("Drivers_open");

    App_driversOpen();
    Bootloader_profileAddProfilePoint("SBL Drivers_open");

    flashFixUpOspiBoot(gOspiHandle[CONFIG_OSPI_SBL], gFlashHandle[CONFIG_FLASH_SBL]);

    status = Board_driversOpen();
    DebugP_assert(status == SystemP_SUCCESS);
    Bootloader_profileAddProfilePoint("Board_driversOpen");

    status = App_boardDriversOpen();
    DebugP_assert(status == SystemP_SUCCESS);
    Bootloader_profileAddProfilePoint("SBL Board_driversOpen");

    if(SystemP_SUCCESS == status)
    {
        Bootloader_openDma();

        Bootloader_LoadImageParams bootDM;

        Bootloader_Params_init(&bootDM.bootParams);

        Bootloader_BootImageInfo_init(&bootDM.bootImageInfo);

        bootDM.bootHandle = Bootloader_open(CONFIG_BOOTLOADER_FLASH_SBL, &bootDM.bootParams);

        if(SystemP_SUCCESS == status)
		{
            if(bootDM.bootHandle != NULL)
            {
                ((Bootloader_Config *)bootDM.bootHandle)->scratchMemPtr = gAppimage;
                status = App_loadSelfcoreImage(&bootDM);
                Bootloader_profileAddProfilePoint("App_loadSelfcoreImage");
            }
        }

        Bootloader_profileUpdateAppimageSize(Bootloader_getMulticoreImageSize(bootDM.bootHandle));
        Bootloader_profileUpdateMediaAndClk(BOOTLOADER_MEDIA_FLASH, OSPI_getInputClk(gOspiHandle[CONFIG_OSPI_SBL]));

#if (ENABLE_MCU_LBIST == 1u)
        /* wait for LBIST completion */
        status = App_waitForMcuLbist();
        Bootloader_profileAddProfilePoint("App_waitForMcuLbist");
#endif

		if(SystemP_SUCCESS == status)
		{
			/* Print SBL log as Linux prints log to the same UART port */
			Bootloader_profilePrintProfileLog();
			DebugP_log("Image loading done, switching to application ...\r\n");
			DebugP_log("Starting 2nd stage bootloader\r\n");
			UART_flushTxFifo(gUartHandle[CONFIG_UART_SBL]);
		}

        Bootloader_close(bootDM.bootHandle);

        Bootloader_closeDma();
    }

    if(status != SystemP_SUCCESS )
    {
        DebugP_log("SBL stage 1 failed!!\r\n");
    }

    Board_driversClose();
    Drivers_close();

    /* Call DPL deinit to close the tick timer and disable interrupts before jumping to Stage2*/
    Dpl_deinit();

    Bootloader_socWriteSBLBootMagicNum();

    Bootloader_JumpSelfCpu();

    Board_deinit();
    System_deinit();

    return 0;
}

void flashFixUpOspiBoot(OSPI_Handle oHandle, Flash_Handle fHandle)
{
    /*
     *  In Fast XSPI mode, reintialization is not required unless
     *  user configures it or PHY configuration failed
     */
    if(SystemP_SUCCESS != OSPI_skipTuning(oHandle))
    {
        OSPI_setProtocol(oHandle, OSPI_FLASH_PROTOCOL(8,8,8,1));
        OSPI_enableDDR(oHandle);
        OSPI_setDualOpCodeMode(oHandle);
        Flash_reset(fHandle);
        OSPI_enableSDR(oHandle);
        OSPI_clearDualOpCodeMode(oHandle);
        OSPI_setProtocol(oHandle, OSPI_FLASH_PROTOCOL(1,1,1,0));
    }

}
