/**
 * @file exo_osal_mem_cpy.c
 *
 * @brief This file contains function definitions for different memory.
 *
 * @copyright Copyright 2023 Antaris, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <stdint.h>
#include <stdlib.h>
#include <stdio.h>
#include "exo_osal.h"
#include "exo_osal_common.h"
#include "exo_common.h"
#include "exo_osal_mem_cpy.h"
#ifndef LINUX_TEMP_PORT
#include "flash_drive.h"
#include "qspi.h"
#include "exo_ahw_al_ext_flash_common.h"
#endif
#include "exo_types.h"


volatile uint8_t init_flag = 0;

/* Added Fixed Memery address for external memory validation*/
#define SDRAM_ADDR		((uint32_t)0xC0500000)       ///< starting address for SDRAM
#define FMC_NOR_ADDR	((uint32_t)0x60900000)       ///< starting address for FMC NOR
#define FMC_QSPI_ADDR	((uint32_t)0x00000000)       ///< starting address for FMC QSPI
#define LEN				(100)                        ///< Maximum length for all memory

#define FLASH_TYPEPROGRAM_BYTE        ((uint32_t)0x00U)  ///< Program byte (8-bit) at a specified address
#define FLASH_TYPEPROGRAM_HALFWORD    ((uint32_t)0x01U)  ///< Program a half-word (16-bit) at a specified address
#define FLASH_TYPEPROGRAM_WORD        ((uint32_t)0x02U)  ///< Program a word (32-bit) at a specified address
#define FLASH_TYPEPROGRAM_DOUBLEWORD  ((uint32_t)0x03U)  ///< Program a double word (64-bit) at a specified address
#define LOCAL_BUFF_LEN	(1*1024*1024)                    ///< Local buffer length
#define SECTOR_SIZE_NOR (64*1024)                        ///< NOR sector size
#define SECTOR_SIZE_QSPI (4*1024)                        ///< QSPI sector size
#define FLASH_SIZE (1*1024)                              ///< Flash size
obc_memcpy_memory_layout look_up_table[MAX_MEM];         ///< Maximum memcpy size
uint8_t PLACE_IN_EXT_RAM_MEM local_buff_sram[FLASH_SIZE] ={0};                ///< SRAM local buffer size
uint8_t PLACE_IN_EXT_RAM_MEM local_buff[LOCAL_BUFF_LEN];   ///< Buffer to place in SDRAM

#ifndef LINUX_TEMP_PORT
/**
 * @brief This API erase the flash sector for given length.
 */
void flash_sector_erase_by_add_n_len(uint8_t* write_addr,int len,int8_t flash_type)
{
	switch(flash_type)
	{
	case I_FLASH:
	{
		Flash_Erase_Data((uint32_t)write_addr,len>>2);
	}
	break;
	case E_QSPI_FLASH:
	{
		ahw_al_ext_mem_erase(EXT_MEM_QSPI_FLASH, (uint32_t)write_addr, (uint32_t)write_addr+len);
	}
	break;
	case E_FMC_FLASH:
		ahw_al_ext_mem_erase(EXT_MEM_MT28EW128_FMC, (uint32_t)write_addr,(uint32_t)write_addr+len);
	break;
	default:
	{
		DEBUG_CPRINT(("Invalid flash type %d ", flash_type));
	}
	}

}

/**
 * @brief This API write data to External NOR flash memory.
 */
uint8_t write_to_e_norfmc(uint8_t* write_addr,uint8_t* data,int len,uint8_t erase_flag)
{
	uint8_t sts=0;
	/*As large flash copy will be triggered in this function, momentarily disabled the nvm parameter flash write*/
	/*check if logger thread flash commit is ongoing then delay*/
    sts=ahw_al_ext_mem_erase_and_write(EXT_MEM_MT28EW128_FMC,(uint32_t)write_addr,data,len);

	return sts;
}


#if 0

/**
 * @brief This API write data to External QSPI memory
 */
uint8_t write_to_e_qspi(uint8_t* write_addr,uint8_t* data,int len, uint8_t erase_flag)
{
	uint8_t sts=0;
	uint32_t flsh_write_addr = (uint32_t)write_addr;
	while(len>0)
	{
		if(len>SECTOR_SIZE_QSPI)
		{
			if(erase_flag)
			{
				sts = qspi_erase_block(hqspi_hdl,flsh_write_addr);
				os_delay(10);
			}
			sts = qspi_write(hqspi_hdl,data, flsh_write_addr,len);
			flsh_write_addr = flsh_write_addr + SECTOR_SIZE_QSPI;
			len = len-SECTOR_SIZE_QSPI;
			data = data + SECTOR_SIZE_QSPI;
		}
		else
		{
			if(erase_flag)
			{
				sts = qspi_erase_block(hqspi_hdl,flsh_write_addr);
				os_delay(10);
			}
			sts = qspi_write(hqspi_hdl,data, flsh_write_addr,len);
			len = len-SECTOR_SIZE_QSPI;
		}
	}
	return sts;
}

#else
/**
 * @brief This API write data to External QSPI memory
 */
uint8_t write_to_e_qspi(uint8_t* write_addr,uint8_t* data,int len, uint8_t erase_flag)
{
	uint8_t ret_sts;
	ret_sts = ahw_al_ext_mem_write(EXT_MEM_QSPI_FLASH, (uint32_t)write_addr, data, len);
	return ret_sts;
}


#endif


/**
 * @brief This API write data to internal flash memory
 */
uint8_t write_to_iflash(uint8_t* write_addr,uint8_t* data,int len, uint8_t erase_flag)
{
	uint8_t sts;
	uint32_t* ptr_d = (uint32_t*)data;
	uint32_t* ptr_s = (uint32_t*)write_addr;
	if(erase_flag)
	{
//		Flash_Erase_Data((uint32_t)ptr_s,len>>2);
	}
	sts = Flash_Write_Data((uint32_t)ptr_s, (uint32_t*)ptr_d, len>>2);
	return sts;
}
#endif

/**
 * @brief This API write data to RAM.
 */
uint8_t write_to_ram(uint8_t* write_addr,uint8_t* data,int len, uint8_t erase_flag)
{
	uint8_t sts = CPY_OK;
	int size_inc;
	for(size_inc =0; size_inc < len ;size_inc++)
	{
		write_addr[size_inc] = data[size_inc];
	}
	return sts;
}

/**
 * @brief This API read data from RAM.
 */
uint8_t read_from_ram(uint8_t* read_addr,uint8_t* data,int len)
{
	uint8_t sts = CPY_OK;
	int size_inc;
	for(size_inc =0; size_inc < len ;size_inc++)
	{
		data[size_inc] = read_addr[size_inc];
	}
	return sts;
}

/**
 * @brief This API read data from internal flash.
 */
uint8_t read_from_iflash(uint8_t* read_addr,uint8_t* data,int len)
{
	uint8_t sts = CPY_OK;
	int size_inc;
	for(size_inc =0; size_inc < len ;size_inc++)
	{
		data[size_inc] = read_addr[size_inc];
	}
	return sts;
}
#ifndef LINUX_TEMP_PORT

/**
 * @brief This API read data from external NOR flash.
 */
uint8_t read_from_e_norfmc(uint8_t* read_addr,uint8_t* data,int len)
{
	uint8_t sts;
	/*DRIVER only supports 2 byte read or write*/
	sts=ahw_al_ext_mem_read(EXT_MEM_MT28EW128_FMC,(uint32_t)read_addr,data,len);
	return sts;
}



/**
 * @brief This API read data from external QSPI.
 */
#if 0
uint8_t read_from_e_qspi(uint8_t* read_addr,uint8_t* data,int len)
{
	uint8_t sts;
	/*uint32_t src_add = ((uint32_t)src/(uint32_t)look_up_table[E_QSPI_FLASH].start_addr);*/
	sts = qspi_read(hqspi_hdl,data,(uint32_t)read_addr,len);
	return sts;
}
#else
uint8_t read_from_e_qspi(uint8_t* read_addr,uint8_t* data,int len)
{
	uint8_t ret_sts;
	ret_sts = ahw_al_ext_mem_read(EXT_MEM_QSPI_FLASH, (uint32_t)read_addr, data, len);
	return ret_sts;
}
#endif


#endif

/**
 * @brief This API initialize the memcpy framework.
 */
void os_memcpy_fw_init()
{
	look_up_table[I_RAM].start_addr = (uint8_t*)0x20000000;
	look_up_table[I_RAM].len_bytes = (0.5*1024*1024);
	look_up_table[I_RAM].end_addr = look_up_table[I_RAM].start_addr+look_up_table[I_RAM].len_bytes;
	look_up_table[I_RAM].read_fun = read_from_ram;
	look_up_table[I_RAM].write_fun = write_to_ram;

#ifndef LINUX_TEMP_PORT 
	look_up_table[I_FLASH].start_addr = (uint8_t*)0x8000000;
	look_up_table[I_FLASH].len_bytes = (2*1024*1024);
	look_up_table[I_FLASH].end_addr = look_up_table[I_FLASH].start_addr+look_up_table[I_FLASH].len_bytes;
	look_up_table[I_FLASH].read_fun = read_from_iflash;
	look_up_table[I_FLASH].write_fun = write_to_iflash;

#if defined(STM32F769xx) || defined(COREBOARD)
	look_up_table[E_RAM].start_addr =(uint8_t*)0xC0000000;
	look_up_table[E_RAM].len_bytes = (32*1024*1024);
	look_up_table[E_RAM].end_addr = look_up_table[E_RAM].start_addr+look_up_table[E_RAM].len_bytes;
	look_up_table[E_RAM].read_fun = read_from_ram;
	look_up_table[E_RAM].write_fun = write_to_ram;

	look_up_table[E_QSPI_FLASH].start_addr = (uint8_t*)0x90000000;
	look_up_table[E_QSPI_FLASH].len_bytes = (64*1024*1024);
	look_up_table[E_QSPI_FLASH].end_addr = look_up_table[E_QSPI_FLASH].start_addr+look_up_table[E_QSPI_FLASH].len_bytes;
	look_up_table[E_QSPI_FLASH].read_fun = read_from_e_qspi;
	look_up_table[E_QSPI_FLASH].write_fun = write_to_e_qspi;
#endif
#if defined(COREBOARD)
	look_up_table[E_FMC_FLASH].start_addr = (uint8_t*)0x60000000;
	look_up_table[E_FMC_FLASH].len_bytes = (16*1024*1024);
	look_up_table[E_FMC_FLASH].end_addr = look_up_table[E_FMC_FLASH].start_addr+look_up_table[E_FMC_FLASH].len_bytes;
	look_up_table[E_FMC_FLASH].read_fun = read_from_e_norfmc;
	look_up_table[E_FMC_FLASH].write_fun = write_to_e_norfmc;

#endif
#endif
	os_thread_create(OBC_MEMCPY_THREAD,T_OSAL_MEM_STACK_SIZE,P_OBC_MEMCPY_THREAD,"MEMCPY THREAD",memcpy_event_thread,NULL,NULL,NULL);
	init_flag = 1;
}

/**
 * @brief This API compare both source and destination address and update
 *        the memory type info.
 */
uint8_t mem_cpy_fun_def(obc_memcpy_info *obc_cpy_info)
{
	uint8_t sts = CPY_OK;

#ifndef LINUX_TEMP_PORT
#ifdef SD_RAM_FIX
	switch(obc_cpy_info->src_mem_type)
	{
	case I_RAM:
	case E_RAM:
		if(obc_cpy_info->dst_mem_type < I_FLASH)
		{
			sts = look_up_table[obc_cpy_info->src_mem_type].read_fun(obc_cpy_info->src_addr,obc_cpy_info->dst_addr,obc_cpy_info->len);
		}
		break;
	case I_FLASH:
	case E_QSPI_FLASH:
	case E_FMC_FLASH:
		if(obc_cpy_info->dst_mem_type < I_FLASH)
		{
			sts = look_up_table[obc_cpy_info->src_mem_type].read_fun(obc_cpy_info->src_addr,obc_cpy_info->dst_addr,obc_cpy_info->len);
		}
		else
		{
			sts = look_up_table[obc_cpy_info->src_mem_type].read_fun(obc_cpy_info->src_addr,local_buff,obc_cpy_info->len);
		}
		break;
	}
	if(sts == CPY_OK)
	{
		switch(obc_cpy_info->dst_mem_type)
		{
		case I_RAM:
		case E_RAM:
			break;
		case I_FLASH:
		case E_QSPI_FLASH:
		case E_FMC_FLASH:
			if(obc_cpy_info->src_mem_type < I_FLASH)
			{
				sts = look_up_table[obc_cpy_info->dst_mem_type]\
						.write_fun(obc_cpy_info->dst_addr,obc_cpy_info->src_addr,obc_cpy_info->len,obc_cpy_info->erase_flag);
			}
			else
			{
				sts = look_up_table[obc_cpy_info->dst_mem_type]\
						.write_fun(obc_cpy_info->dst_addr,local_buff,obc_cpy_info->len,obc_cpy_info->erase_flag);
			}
			break;
		}
	}

#else
	uint8_t* src_addr = obc_cpy_info->src_addr;
	uint8_t* dst_addr = obc_cpy_info->dst_addr;
	int len = obc_cpy_info->len;
	int erase_flag  = obc_cpy_info->erase_flag;
	if((obc_cpy_info->dst_mem_type == E_FMC_FLASH))
	{
		sts = look_up_table[obc_cpy_info->dst_mem_type].write_fun(dst_addr,src_addr,len,erase_flag);
	}
	else
	{


		/* erase memory from start address till end address */
		if(erase_flag == 1)
		{
			if((obc_cpy_info->dst_mem_type == E_QSPI_FLASH))
			{
				ahw_al_ext_mem_erase(EXT_MEM_QSPI_FLASH, (uint32_t)dst_addr, (uint32_t)dst_addr + len);
				/* delay for final sector erase time */
				os_delay(50);
			}
			else if((obc_cpy_info->dst_mem_type == I_FLASH))
			{
				Flash_Erase_Data((uint32_t)dst_addr,len>>2);
			}
			else
			{

			}
		}



		while(len > 0)
		{
			if(len > FLASH_SIZE)
			{
				sts = look_up_table[obc_cpy_info->src_mem_type].read_fun(src_addr,local_buff_sram,FLASH_SIZE);
				os_delay(10);
				sts = look_up_table[obc_cpy_info->dst_mem_type].write_fun(dst_addr,local_buff_sram,FLASH_SIZE,erase_flag);
			}
			else
			{
				sts = look_up_table[obc_cpy_info->src_mem_type].read_fun(src_addr,local_buff_sram,len);
				os_delay(10);
				sts = look_up_table[obc_cpy_info->dst_mem_type].write_fun(dst_addr,local_buff_sram,len,erase_flag);
			}
			src_addr += FLASH_SIZE;
			dst_addr += FLASH_SIZE;
			len = len - FLASH_SIZE;
		}
	}
#endif
#else
	os_memcpy(obc_cpy_info->dst_addr,obc_cpy_info->src_addr,obc_cpy_info->len);
#endif
	if(obc_cpy_info->call_back_fun)
	{
		obc_cpy_info->call_back_fun(sts);
	}
	return sts;
}

/**
 * @brief This API compare both source and destination address and update
 *        the memory type info.
 *
 * Length cannot be greater than 1 MB
 */
cpy_sts os_memcpy_extmem(uint8_t* dst_addr,uint8_t* src_addr,uint32_t len,mem_cpy_cmptl_cb cb_fun,uint8_t imemdiate_flag,uint8_t erase_flag)
{
	cpy_sts sts = CPY_ERR;
	obc_memcpy_info *obc_memcpy_hdlr_info = os_malloc(sizeof(obc_memcpy_info));
	obc_memcpy_hdlr_info->src_addr = src_addr;
	obc_memcpy_hdlr_info->dst_addr = dst_addr;
	obc_memcpy_hdlr_info->len = len;
	obc_memcpy_hdlr_info->erase_flag  = erase_flag;
#ifndef LINUX_TEMP_PORT
	if(init_flag == 1)
	{
		uint8_t i;
		uint8_t src_sts=0;
                uint8_t dst_sts;
		for(i =0;i<MAX_MEM;i++)
		{
			if((obc_memcpy_hdlr_info->src_addr >= look_up_table[i].start_addr) && ((obc_memcpy_hdlr_info->src_addr+obc_memcpy_hdlr_info->len) <= look_up_table[i].end_addr))
			{
				obc_memcpy_hdlr_info->src_mem_type = i;
				src_sts = VALID_ADDR;
			}
			if((obc_memcpy_hdlr_info->dst_addr >= look_up_table[i].start_addr) && ((obc_memcpy_hdlr_info->dst_addr+obc_memcpy_hdlr_info->len) <= look_up_table[i].end_addr))
			{
				obc_memcpy_hdlr_info->dst_mem_type = i;
				dst_sts = VALID_ADDR;
			}
		}
		if(src_sts == VALID_ADDR && dst_sts == VALID_ADDR)
		{
			if(imemdiate_flag == 0)
			{
				obc_memcpy_hdlr_info->call_back_fun = cb_fun;
				os_itc_msg_handle_t send;
				send.src_entity = 1;
				send.pld_info = 0;
				send.Msg_id = 0;
				send.Msg_len = sizeof(obc_memcpy_info);
				send.pld.pld_ptr = obc_memcpy_hdlr_info;
				sts = os_itc_msg_send(&send,OBC_MEMCPY_THREAD,os_send_itc_wait);
			}
			else
			{
				obc_memcpy_hdlr_info->call_back_fun = NULL;
				sts = mem_cpy_fun_def(obc_memcpy_hdlr_info);
				os_free(obc_memcpy_hdlr_info);
			}
		}
		else
		{
			if(src_sts != VALID_ADDR)
			{
				sts = src_sts;
			}
			else
			{
				sts = dst_sts;
			}
		}
	}
#else
	if(imemdiate_flag == 0)
	{
		obc_memcpy_hdlr_info->call_back_fun = cb_fun;
		os_itc_msg_handle_t send;
		send.src_entity = 1;
		send.pld_info = 0;
		send.Msg_id = 0;
		send.Msg_len = sizeof(obc_memcpy_info);
		send.pld.pld_ptr = obc_memcpy_hdlr_info;
		sts = os_itc_msg_send(&send,OBC_MEMCPY_THREAD,os_send_itc_wait);
	}
	else
	{
		sts = CPY_OK;
		os_memcpy(obc_memcpy_hdlr_info->dst_addr,obc_memcpy_hdlr_info->src_addr,obc_memcpy_hdlr_info->len);
	}
#endif
	return sts;
}

/**
 * @brief Memory copy thread entry function.
 */
void memcpy_event_thread(void* args)
{
	while(1)
	{
		os_itc_msg_handle_t recv_info;
		if(os_itc_msg_rcv(OBC_MEMCPY_THREAD,&recv_info,os_wait_forever) == 1)
		{
			obc_memcpy_info *obc_cpy_info_rec = ((obc_memcpy_info*)recv_info.pld.pld_ptr);
			mem_cpy_fun_def(obc_cpy_info_rec);

			if(recv_info.pld.pld_ptr)
			{
				os_free(recv_info.pld.pld_ptr);
				recv_info.pld.pld_ptr = NULL;
			}
		}
	}
}

/**
 * @brief This API to load buffer with given offset.
 */
	/* Put in global buffer different values */

/**
 * @brief This API test the external memory.
 */
	/* Fill the buffer to write */
	/*##-3- Checking data integrity ############################################*/
