/**
 * @file        flash.c
 *
 * @brief       This file provides application support for flash
 *
 * @version     V1.0.0
 *
 * @date        2023-12-01
 *
 * @attention
 *
 *  Copyright (C) 2023 Geehy Semiconductor
 *
 *  You may not use this file except in compliance with the
 *  GEEHY COPYRIGHT NOTICE (GEEHY SOFTWARE PACKAGE LICENSE).
 *
 *  The program is only for reference, which is distributed in the hope
 *  that it will be useful and instructional for customers to develop
 *  their software. Unless required by applicable law or agreed to in
 *  writing, the program is distributed on an "AS IS" BASIS, WITHOUT
 *  ANY WARRANTY OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the GEEHY SOFTWARE PACKAGE LICENSE for the governing permissions
 *  and limitations under the License.
 */

/* Includes ***************************************************************/
#include "flash.h"

/* Private includes *******************************************************/
#include "apm32f4xx_device_cfg.h"

/* Private macro **********************************************************/
#define FLASH_TIMEOUT_DEFAULT_VALUE        5000U
#define FLASH_ERASE_TIMEOUT                100000U

/* Private typedef ********************************************************/

/* Private variables ******************************************************/
FLASH_INFO_T flashInfo;

/* Private function prototypes ********************************************/
static uint32_t FLASH_Config(void);

/* External variables *****************************************************/
extern QSPI_HandleTypeDef hqspi;
extern volatile uint32_t qspiEventFlag;

/* External functions *****************************************************/

/**
 * @brief   Initializes flash
 *
 * @param   None
 *
 * @retval  Flash status
 */
uint32_t FLASH_Init(void)
{
    /* Configure flash */
    FLASH_Config();

    /* Read W25QXX device ID */
    FLASH_ReadDeviceID();
    if (flashInfo.deviceID != W25Q64_DEVICE_ID)
    {
        return FLASH_STATUS_ERROR;
    }
    else
    {
        flashInfo.sectorSize = 4096;
    }

    return FLASH_STATUS_OK;
}

/**
 * @brief   De-initializes flash
 * 
 * @param   None
 * 
 * @retval  Flash status
 */
uint32_t FLASH_DeInit(void)
{
    return FLASH_STATUS_OK;
}

/**
 * @brief   Write enable
 * 
 * @param   None
 * 
 * @retval  Flash status
 */
uint32_t FLASH_WriteEnable(void)
{
    QSPI_CommandTypeDef CmdConfig_Struct = {0};

    FLASH_ChipSelect(ENABLE);

    /* Send command */
    CmdConfig_Struct.Instruction       = W25QXX_INS_WRITE_ENABLE;
    CmdConfig_Struct.Address           = 0x00000000U;
    CmdConfig_Struct.DummyCycles       = 6U;
    CmdConfig_Struct.AddressSize       = QSPI_ADDRESS_SIZE_NONE;
    CmdConfig_Struct.InstructionMode   = QSPI_INSTRUCTION_STANDARD_INS;
    CmdConfig_Struct.InstructionSize   = QSPI_INSTRUCTION_SIZE_8_BITS;
    CmdConfig_Struct.NbData            = 0U;

    CmdConfig_Struct.TransferMode      = QSPI_TRANSFER_MODE_TX;
    CmdConfig_Struct.FrameFormat       = QSPI_FRAME_FORMAT_STANDARD;
    CmdConfig_Struct.DataFrameSize     = QSPI_DATA_FRAME_SIZE_8BITS;
    if (DAL_QSPI_Command_IT(&hqspi, &CmdConfig_Struct) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    FLASH_ChipSelect(DISABLE);

    return FLASH_STATUS_OK;
}

/**
 * @brief   Read status register
 * 
 * @param   reg: register
 *          This parameter can be one of the following values:
 *           @arg W25QXX_STATUS_REG1: status register 1
 *           @arg W25QXX_STATUS_REG2: status register 2
 *           @arg W25QXX_STATUS_REG3: status register 3
 * 
 * @retval  Register value
 */
uint8_t FLASH_ReadSR(uint32_t reg)
{
    uint8_t cmd;
    uint8_t status = 0;
    uint8_t temp = 0xFF;

    switch (reg)
    {
        case W25QXX_STATUS_REG1:
            cmd = W25QXX_INS_READ_STATUS_REG1;
            break;

        case W25QXX_STATUS_REG2:
            cmd = W25QXX_INS_READ_STATUS_REG2;
            break;

        case W25QXX_STATUS_REG3:
            cmd = W25QXX_INS_READ_STATUS_REG3;
            break;

        default:
            return 0;
    }

    FLASH_ChipSelect(ENABLE);
    DAL_QSPI_TransmitReceive_IT(&hqspi, &cmd, &status, 1);

    /* Wait for the end of QSPI send */
    while (qspiEventFlag != 1U)
    {
        
    }
    qspiEventFlag = 0U;

    DAL_QSPI_TransmitReceive_IT(&hqspi, &temp, &status, 1);

    /* Wait for the end of QSPI send */
    while (qspiEventFlag != 1U)
    {
        
    }
    qspiEventFlag = 0U;

    FLASH_ChipSelect(DISABLE);

    return status;
}

/**
 * @brief   Write status register
 * 
 * @param   reg: register
 *          This parameter can be one of the following values:
 *           @arg W25QXX_STATUS_REG1: status register 1
 *           @arg W25QXX_STATUS_REG2: status register 2
 *           @arg W25QXX_STATUS_REG3: status register 3
 * 
 * @param   sr: status register value
 * 
 * @retval  Flash status
 */
uint32_t FLASH_WriteSR(uint32_t reg, uint8_t sr)
{
    uint32_t status = FLASH_STATUS_OK;
    uint8_t cmd;
    uint8_t data[2];

    switch (reg)
    {
        case W25QXX_STATUS_REG1:
            cmd = W25QXX_INS_WRITE_STATUS_REG1;
            break;

        case W25QXX_STATUS_REG2:
            cmd = W25QXX_INS_WRITE_STATUS_REG2;
            break;

        case W25QXX_STATUS_REG3:
            cmd = W25QXX_INS_WRITE_STATUS_REG3;
            break;

        default:
            return FLASH_STATUS_ERROR;
    }

    data[0] = cmd;
    data[1] = sr;

    /* Enable write */
    FLASH_WriteEnable();
    status = FLASH_WaitForWriteReady(FLASH_TIMEOUT_DEFAULT_VALUE);
    if (status != FLASH_STATUS_OK)
    {
        return status;
    }

    /* Wait for flash ready */
    status = FLASH_WaitForReady(FLASH_TIMEOUT_DEFAULT_VALUE);
    if (status != FLASH_STATUS_OK)
    {
        return status;
    }

    /* Erase sector */
    FLASH_ChipSelect(ENABLE);

    /* Set transfer mode for standard transmission */
    DAL_QSPI_SetTransferMode(&hqspi, QSPI_TRANSFER_MODE_TX);
    DAL_QSPI_SetFrameFormat(&hqspi, QSPI_FRAME_FORMAT_STANDARD);
    DAL_QSPI_SetDataFrameSize(&hqspi, QSPI_DATA_FRAME_SIZE_8BITS);
    DAL_QSPI_SetFrameNbData(&hqspi, 2U);

    /* Send data */
    if (DAL_QSPI_Transmit_DMA(&hqspi, data) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Wait for the end of QSPI receive */
    while (qspiEventFlag != 1U)
    {
        
    }

    qspiEventFlag = 0U;

    FLASH_ChipSelect(DISABLE);

    /* Wait for flash ready */
    status = FLASH_WaitForReady(FLASH_ERASE_TIMEOUT);

    return FLASH_STATUS_OK;
}

/**
 * @brief   Wait for flash ready
 * 
 * @param   timeout: timeout
 * 
 * @retval  Flash status
 */
uint32_t FLASH_WaitForReady(uint32_t timeout)
{
    uint32_t status = FLASH_STATUS_OK;
    uint32_t sr1;

    /* Wait for flash ready */
    while (1)
    {
        sr1 = FLASH_ReadSR(W25QXX_STATUS_REG1);
        if ((sr1 & W25QXX_SR1_BUSY) != W25QXX_SR1_BUSY)
        {
            break;
        }

        if (timeout == 0U)
        {
            status = FLASH_STATUS_TIMEOUT;
            break;
        }
        timeout--;
    }

    return status;
}

/**
 * @brief  Wait for flash ready to write
 * 
 * @param  timeout: timeout
 * 
 * @retval Flash status
 */
uint32_t FLASH_WaitForWriteReady(uint32_t timeout)
{
    uint32_t status = FLASH_STATUS_OK;
    uint32_t sr1;

    /* Wait for flash ready */
    while (1)
    {
        sr1 = FLASH_ReadSR(W25QXX_STATUS_REG1);
        if ((sr1 & W25QXX_SR1_WEL) == W25QXX_SR1_WEL)
        {
            break;
        }

        if (timeout == 0U)
        {
            status = FLASH_STATUS_TIMEOUT;
            break;
        }
        timeout--;
    }

    return status;
}

/**
 * @brief  Read device ID
 * 
 * @param  None
 * 
 * @retval Flash status
 */
uint32_t FLASH_ReadDeviceID(void)
{
    QSPI_CommandTypeDef CmdConfig_Struct = {0};
    uint8_t temp[2];

    FLASH_ChipSelect(ENABLE);

    /* Send command */
    CmdConfig_Struct.Instruction       = W25QXX_INS_MANUFACTURER_ID_QUAD;
    CmdConfig_Struct.Address           = 0x00000000U;
    CmdConfig_Struct.DummyCycles       = 6U;
    CmdConfig_Struct.AddressSize       = QSPI_ADDRESS_SIZE_24_BITS;
    CmdConfig_Struct.InstructionMode   = QSPI_INSTRUCTION_STANDARD_INS;
    CmdConfig_Struct.InstructionSize   = QSPI_INSTRUCTION_SIZE_8_BITS;
    CmdConfig_Struct.NbData            = 2U;

    CmdConfig_Struct.TransferMode      = QSPI_TRANSFER_MODE_RX;
    CmdConfig_Struct.FrameFormat       = QSPI_FRAME_FORMAT_QUAD;
    CmdConfig_Struct.DataFrameSize     = QSPI_DATA_FRAME_SIZE_8BITS;
    if (DAL_QSPI_Command_IT(&hqspi, &CmdConfig_Struct) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Read W25QXX device ID */
    if (DAL_QSPI_Receive_DMA(&hqspi, temp) != 0)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Wait for the end of QSPI receive */
    while (qspiEventFlag != 1U)
    {
        
    }

    qspiEventFlag = 0U;

    FLASH_ChipSelect(DISABLE);

    flashInfo.deviceID = temp[0] << 8 | temp[1];

    return FLASH_STATUS_OK;
}

/**
 * @brief   Erase flash sector
 * 
 * @param   addr: address
 * 
 * @retval  Flash status
 */
uint32_t FLASH_EraseSector(uint32_t addr)
{
    uint32_t status = FLASH_STATUS_OK;
    uint8_t data[4];

    data[0] = W25QXX_INS_SECTOR_ERASE;
    data[1] = (addr >> 16) & 0xFF;
    data[2] = (addr >> 8) & 0xFF;
    data[3] = addr & 0xFF;

    /* Enable write */
    FLASH_WriteEnable();
    status = FLASH_WaitForWriteReady(FLASH_TIMEOUT_DEFAULT_VALUE);
    if (status != FLASH_STATUS_OK)
    {
        return status;
    }

    /* Wait for flash ready */
    status = FLASH_WaitForReady(FLASH_TIMEOUT_DEFAULT_VALUE);
    if (status != FLASH_STATUS_OK)
    {
        return status;
    }

    /* Erase sector */
    FLASH_ChipSelect(ENABLE);

    /* Set transfer mode for standard transmission */
    DAL_QSPI_SetTransferMode(&hqspi, QSPI_TRANSFER_MODE_TX);
    DAL_QSPI_SetFrameFormat(&hqspi, QSPI_FRAME_FORMAT_STANDARD);
    DAL_QSPI_SetDataFrameSize(&hqspi, QSPI_DATA_FRAME_SIZE_8BITS);
    DAL_QSPI_SetFrameNbData(&hqspi, 4U);

    /* Send data */
    if (DAL_QSPI_Transmit_DMA(&hqspi, data) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Wait for the end of QSPI receive */
    while (qspiEventFlag != 1U)
    {
        
    }

    qspiEventFlag = 0U;

    FLASH_ChipSelect(DISABLE);

    /* Wait for flash ready */
    status = FLASH_WaitForReady(FLASH_ERASE_TIMEOUT);

    return status;
}

/**
 * @brief   FLASH read data
 * 
 * @param   addr: address
 * 
 * @param   data: pointer to data
 * 
 * @param   len: length of data
 * 
 * @retval  Flash status
 */
uint32_t FLASH_ReadData(uint32_t addr, uint8_t *data, uint32_t len)
{
    uint32_t status = FLASH_STATUS_OK;
    QSPI_CommandTypeDef CmdConfig_Struct = {0};

    FLASH_ChipSelect(ENABLE);

    /* Send command */
    CmdConfig_Struct.Instruction       = W25QXX_INS_FAST_READ_QUAD;
    CmdConfig_Struct.Address           = addr;
    CmdConfig_Struct.DummyCycles       = 8U;
    CmdConfig_Struct.AddressSize       = QSPI_ADDRESS_SIZE_24_BITS;
    CmdConfig_Struct.InstructionMode   = QSPI_INSTRUCTION_STANDARD_INS_ADDR;
    CmdConfig_Struct.InstructionSize   = QSPI_INSTRUCTION_SIZE_8_BITS;
    CmdConfig_Struct.NbData            = len;

    CmdConfig_Struct.TransferMode      = QSPI_TRANSFER_MODE_RX;
    CmdConfig_Struct.FrameFormat       = QSPI_FRAME_FORMAT_QUAD;
    CmdConfig_Struct.DataFrameSize     = QSPI_DATA_FRAME_SIZE_8BITS;
    if (DAL_QSPI_Command_IT(&hqspi, &CmdConfig_Struct) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Read W25QXX data */
    if (DAL_QSPI_Receive_DMA(&hqspi, data) != 0)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Wait for the end of QSPI receive */
    while (qspiEventFlag != 1U)
    {
        
    }

    qspiEventFlag = 0U;

    FLASH_ChipSelect(DISABLE);

    return status;
}

/**
 * @brief   FLASH write data
 * 
 * @param   addr: address
 * 
 * @param   data: pointer to data
 * 
 * @param   len: length of data
 * 
 * @retval  Flash status
 */
uint32_t FLASH_WriteData(uint32_t addr, uint8_t *data, uint32_t len)
{
    uint32_t status = FLASH_STATUS_OK;

    QSPI_CommandTypeDef CmdConfig_Struct = {0};

    /* Enable write */
    FLASH_WriteEnable();
    status = FLASH_WaitForWriteReady(FLASH_TIMEOUT_DEFAULT_VALUE);
    if (status != FLASH_STATUS_OK)
    {
        return status;
    }

    /* Wait for flash ready */
    status = FLASH_WaitForReady(FLASH_TIMEOUT_DEFAULT_VALUE);
    if (status != FLASH_STATUS_OK)
    {
        return status;
    }

    /* Write data */
    FLASH_ChipSelect(ENABLE);

    /* Send command */
    CmdConfig_Struct.Instruction       = W25QXX_INS_PAGE_PROGRAM_QUAD;
    CmdConfig_Struct.Address           = addr;
    CmdConfig_Struct.DummyCycles       = 0U;
    CmdConfig_Struct.AddressSize       = QSPI_ADDRESS_SIZE_24_BITS;
    CmdConfig_Struct.InstructionMode   = QSPI_INSTRUCTION_STANDARD_INS_ADDR;
    CmdConfig_Struct.InstructionSize   = QSPI_INSTRUCTION_SIZE_8_BITS;
    CmdConfig_Struct.NbData            = len;

    CmdConfig_Struct.TransferMode      = QSPI_TRANSFER_MODE_TX;
    CmdConfig_Struct.FrameFormat       = QSPI_FRAME_FORMAT_QUAD;
    CmdConfig_Struct.DataFrameSize     = QSPI_DATA_FRAME_SIZE_8BITS;
    if (DAL_QSPI_Command_IT(&hqspi, &CmdConfig_Struct) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Send data */
    if (DAL_QSPI_Transmit_DMA(&hqspi, data) != DAL_OK)
    {
        return FLASH_STATUS_ERROR;
    }

    /* Wait for the end of QSPI receive */
    while (qspiEventFlag != 1U)
    {
        
    }

    qspiEventFlag = 0U;

    FLASH_ChipSelect(DISABLE);

    /* Wait for flash ready */
    status = FLASH_WaitForReady(FLASH_TIMEOUT_DEFAULT_VALUE);

    return status;
}

/**
 * @brief   FLASH delay
 *
 * @param   delay: delay time
 * 
 * @retval  None
 */
void FLASH_Delay(uint32_t delay)
{
    DAL_Delay(delay);
}

/**
 * @brief   FLASH chip select
 * 
 * @param   state: chip select state
 *          This parameter can be one of the following values:
 *          @arg ENABLE: chip select enable
 *          @arg DISABLE: chip select disable
 * 
 * @retval  None
 */
void FLASH_ChipSelect(FunctionalState state)
{
    if (state == ENABLE)
    {
        DAL_GPIO_WritePin(QSPI_CS_GPIO_PORT, QSPI_CS_PIN, GPIO_PIN_RESET);
    }
    else
    {
        DAL_GPIO_WritePin(QSPI_CS_GPIO_PORT, QSPI_CS_PIN, GPIO_PIN_SET);
    }
}

/**
 * @brief   Configures flash
 *
 * @param   None
 *
 * @retval  Flash status
 */
static uint32_t FLASH_Config(void)
{
    uint32_t status = FLASH_STATUS_OK;
    uint32_t sr1;
    uint32_t sr2;

    /* Initialize W25QXX */
    DAL_QSPI_Config();

    /* Read W25QXX status register */
    sr2 = FLASH_ReadSR(W25QXX_STATUS_REG2);

    if ((sr2 & W25QXX_SR2_QE) != W25QXX_SR2_QE)
    {
        /* Enable Quad mode */
        FLASH_WriteEnable();

        /* Read W25QXX status register */
        sr1 = FLASH_ReadSR(W25QXX_STATUS_REG1);
        if ((sr1 & W25QXX_SR1_WEL) != W25QXX_SR1_WEL)
        {
            return FLASH_STATUS_ERROR;
        }

        FLASH_WriteSR(W25QXX_STATUS_REG2, sr2 | W25QXX_SR2_QE);
    }

    return status;
}
