/**
  ******************************************************************************
  * @file    kpm32xx_ddl_flash.c
  * @author  Kiwi Software Team
  * @brief   FLASH DDL module driver.
  *          This file provides firmware functions to manage the following
  *          functionalities of the internal FLASH memory:
  *          + Program operations functions
  *          + Erase operation functions
  * @note
  *          V1.0.0, 2024/12/20.
  *
  * Copyright (c) 2024, Kiwi Instruments Co,. Ltd.
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *
  *   2. 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.
  *
  *   3. Neither the name of the copyright holder 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 HOLDER 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 <string.h>
#include "kpm32xx_ddl.h"


#ifdef DDL_FLASH_MODULE_ENABLED


#define FLASH_TIMEOUT_VALUE       500U  /* 500us */



/**
  * @brief  Enable ICACHE.
  * @retval None
  */
void DDL_ICACHE_Enable(void)
{
	__DDL_ICACHE_ENABLE();
	/* Wait cache enable */
	while ((ICACHE->ST & ICACHE_ST_CACHESTATUS_Mask) != ICACHE_ST_CACHESTATUS_ENABLED)
	{
		;
	}
}


/**
  * @brief  Disable ICACHE.
  * @retval None
  */
void DDL_ICACHE_Disable(void)
{
	__DDL_ICACHE_DISABLE();
	/* Wait till cache line clear */
	while ((ICACHE->ST & ICACHE_ST_CACHESTATUS_Mask) != ICACHE_ST_CACHESTATUS_CLOSE)
	{
		;
	}
}


/**
  * @brief  Enable DCACHE.
  * @retval None
  */
void DDL_DCACHE_Enable(void)
{
	__DDL_DCACHE_ENABLE();
	/* Wait cache enable */
	while ((DCACHE->ST & DCACHE_ST_CACHESTATUS_Mask) != DCACHE_ST_CACHESTATUS_ENABLED)
	{
		;
	}
}


/**
  * @brief  Disable DCACHE.
  * @retval None
  */
void DDL_DCACHE_Disable(void)
{
	__DDL_DCACHE_DISABLE();
	/* Wait till cache line clear */
	while ((DCACHE->ST & DCACHE_ST_CACHESTATUS_Mask) != DCACHE_ST_CACHESTATUS_CLOSE)
	{
		;
	}
}


/**
  * @brief  Initialize ICACHE according to specified parameters.
  * @param  init  Point to CACHE initial parameters.
  * @retval None
  */
void DDL_ICACHE_Init(CacheInit_T *init)
{
	DDL_ICACHE_Disable();
	if (init->prefetchEnable)
	{
		SET_BIT(ICACHE->CTRL, ICACHE_CTRL_PREFETCH);
	}
	if (init->statisticEnable)
	{
		SET_BIT(ICACHE->CTRL, ICACHE_CTRL_STATIS);
	}
	DDL_ICACHE_Enable();
}


/**
  * @brief  Initialize DCACHE according to specified parameters.
  * @param  init  Point to CACHE initial parameters.
  * @retval None
  */
void DDL_DCACHE_Init(CacheInit_T *init)
{
	DDL_DCACHE_Disable();
	if (init->prefetchEnable)
	{
		SET_BIT(DCACHE->CTRL, DCACHE_CTRL_PREFETCH);
	}
	if (init->statisticEnable)
	{
		SET_BIT(DCACHE->CTRL, DCACHE_CTRL_STATIS);
	}
	DDL_DCACHE_Enable();
}


/**
  * @brief  Enable Flash interrupt.
  * @retval None.
  */
void DDL_Flash_IntEnable(void)
{
	DDL_FLASH_Unlock();
	__DDL_FLASH_ENABLE_IT(FLASH_CTRL_FINISHINTEN | FLASH_CTRL_ERRINTEN);
	DDL_FLASH_Lock();
}


/**
  * @brief  Disable Flash interrupt.
  * @retval None.
  */
void DDL_Flash_IntDisable(void)
{
	DDL_FLASH_Unlock();
	__DDL_FLASH_DISABLE_IT(FLASH_CTRL_FINISHINTEN | FLASH_CTRL_ERRINTEN);
	DDL_FLASH_Lock();
}


/**
  * @brief  Unlock the FLASH control register access
  * @retval None
  */
void DDL_FLASH_Unlock(void)
{
	WRITE_REG(FLASHCTRL->KEY, FLASH_KEY);
}


/**
  * @brief  Locks the FLASH control register access
  * @retval None
  */
void DDL_FLASH_Lock(void)
{
	SET_BIT(FLASHCTRL->CMD, FLASH_CMD_LOCKEN);
}


/**
  * @brief  Set the FLASH Latency
  * @retval None
  */
void DDL_FLASH_SetLatency(uint32_t latency)
{
	SET_BITMASK(FLASHCTRL->CTRL, FLASH_CTRL_LATENCY_Mask, latency);
}


/**
  * @brief  Get the FLASH Latency
  * @retval latency.
  */
uint32_t DDL_FLASH_GetLatency(void)
{
	uint32_t latency;

	latency = GET_BITS(FLASHCTRL->CTRL, FLASH_CTRL_LATENCY_Mask);
	return latency;
}


/**
  * @brief  Get the specific FLASH error flag.
  * @retval FLASH_ErrorCode.
  */
uint32_t DDL_FLASH_GetErrorCode(void)
{
	uint32_t err = 0;

	err = __DDL_FLASH_GET_FLAG(FLASH_ST_PROGERR | FLASH_ST_ERASEERR | FLASH_ST_PRVERYERR | FLASH_ST_ADDRERR);
	if (err != RESET)
	{
		DDL_FLASH_Unlock();
		__DDL_FLASH_CLEAR_FLAG(err);
		DDL_FLASH_Lock();
	}

	return err;
}


/**
  * @brief  Wait for a FLASH operation to complete.
  * @param  flag  the waiting flag to be set.
  * @retval DDL Status
  */
static DDL_Status_T FLASH_WaitForLastOperation(uint32_t flag)
{
	uint32_t tickStart = 0U;

	tickStart = DDL_GetTick();
	while (__DDL_FLASH_GET_FLAG(flag) != flag)
	{
		if ((DDL_GetTick() - tickStart ) > FLASH_TIMEOUT_VALUE)
		{
			return DDL_TIMEOUT;
		}
	}

	return DDL_OK;
}


/**
  * @brief  Program word (32-bit) at a specified address.
  * @param  address  specifies the address to be programmed.
  * @param  data     specifies the data to be programmed.
  * @retval DDL Status
  */
DDL_Status_T DDL_FLASH_ProgramWord(uint32_t address, uint32_t data)
{
	DDL_FLASH_Unlock();
	WRITE_REG(FLASHCTRL->ADDR, address);
	WRITE_REG(FLASHCTRL->DATA, data);
	__DDL_FLASH_SET_PROG_MODE(FLASH_CTRL_PGOGMODE_WORD);
	WRITE_REG(FLASHCTRL->CMD, FLASH_CMD_OP_PROG | FLASH_CMD_START);
	if (FLASH_WaitForLastOperation(FLASH_ST_PROGFINISH) == DDL_TIMEOUT)
	{
		return DDL_TIMEOUT;
	}

	__DDL_FLASH_CLEAR_FLAG(FLASH_ST_PROGFINISH);
	DDL_FLASH_Lock();

	/* Flush dcache */
	DDL_DCACHE_Disable();
	DDL_DCACHE_Enable();

	return DDL_OK;
}


/**
  * @brief  Program a half-word (16-bit) at a specified address.
  * @param  address  specifies the address to be programmed.
  * @param  data     specifies the data to be programmed.
  * @retval DDL Status
  */
DDL_Status_T DDL_FLASH_ProgramHalfWord(uint32_t address, uint16_t data)
{
	DDL_FLASH_Unlock();
	WRITE_REG(FLASHCTRL->ADDR, address);
	WRITE_REG(FLASHCTRL->DATA, data);
	__DDL_FLASH_SET_PROG_MODE(FLASH_CTRL_PGOGMODE_HALFWORD);
	WRITE_REG(FLASHCTRL->CMD, FLASH_CMD_OP_PROG | FLASH_CMD_START);
	if (FLASH_WaitForLastOperation(FLASH_ST_PROGFINISH) == DDL_TIMEOUT)
	{
		return DDL_TIMEOUT;
	}

	__DDL_FLASH_CLEAR_FLAG(FLASH_ST_PROGFINISH);
	DDL_FLASH_Lock();

	/* Flush dcache */
	DDL_DCACHE_Disable();
	DDL_DCACHE_Enable();

	return DDL_OK;
}


/**
  * @brief  Program byte (8-bit) at a specified address.
  * @param  address  specifies the address to be programmed.
  * @param  data     specifies the data to be programmed.
  * @retval DDL Status
  */
DDL_Status_T DDL_FLASH_ProgramByte(uint32_t address, uint8_t data)
{
	DDL_FLASH_Unlock();
	WRITE_REG(FLASHCTRL->ADDR, address);
	WRITE_REG(FLASHCTRL->DATA, data);
	__DDL_FLASH_SET_PROG_MODE(FLASH_CTRL_PGOGMODE_BYTE);
	WRITE_REG(FLASHCTRL->CMD, FLASH_CMD_OP_PROG | FLASH_CMD_START);
	if (FLASH_WaitForLastOperation(FLASH_ST_PROGFINISH) == DDL_TIMEOUT)
	{
		return DDL_TIMEOUT;
	}

	__DDL_FLASH_CLEAR_FLAG(FLASH_ST_PROGFINISH);
	DDL_FLASH_Lock();

	/* Flush dcache */
	DDL_DCACHE_Disable();
	DDL_DCACHE_Enable();

	return DDL_OK;
}


/**
  * @brief  Erase the specified FLASH memory sector
  * @param  sector  FLASH sector to erase
  * @retval DDL Status
  */
DDL_Status_T DDL_FLASH_EraseSector(uint32_t sector)
{
	uint32_t address = FLASH_CODE_BASE + sector * FLASH_CODE_PAGESIZE;

	DDL_FLASH_Unlock();
	WRITE_REG(FLASHCTRL->ADDR, address);
	WRITE_REG(FLASHCTRL->CMD, (FLASH_CMD_OP_PAGE_ERASE | FLASH_CMD_START));
	if (FLASH_WaitForLastOperation(FLASH_ST_ERASEFINISH) == DDL_TIMEOUT)
	{
		return DDL_TIMEOUT;
	}

	__DDL_FLASH_CLEAR_FLAG(FLASH_ST_ERASEFINISH);
	DDL_FLASH_Lock();

	/* Flush dcache */
	DDL_DCACHE_Disable();
	DDL_DCACHE_Enable();

	return DDL_OK;
}


/**
  * @brief  Read FLASH data
  * @param  address  specifies the address to read data.
  * @param  data     specifies the buffer to store data.
  * @param  cnt      specifies the size to read back.
  * @retval DDL Status
  */
DDL_Status_T DDL_FLASH_Read(uint32_t address, uint8_t *data, uint32_t cnt)
{
	DDL_Status_T status = DDL_OK;

	memcpy(data, (uint8_t *)address, cnt);

	return status;
}


#endif /* DDL_FLASH_MODULE_ENABLED */

