/**
  ******************************************************************************
  * @file    spi_flash.c
  *
  * @version     V1.0.0
  *
  * @date        2023-03-30
  * 
  * @brief   This file provides a set of functions needed to manage the SPI W25Qxxx
  *          FLASH memory.
  *          It implements a high level communication layer for read and write 
  *          from/to this memory. The needed APM32 hardware resources, and the 
  *          initialization is performed in sFLASH_LowLevel_Config() function.
  *          You can easily tailor this driver to any other development board, 
  *          by just adapting the defines for hardware resources and 
  *          sFLASH_LowLevel_Config() function.
  *            

  * @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 "spi_flash.h"


/** @addtogroup Examples
  @{
*/
  
/** @addtogroup SPI_FLASH_Polling
  * @brief      This file includes the W25Qxxx SPI FLASH driver of APM32A091 MINI boards.
  * @{
  */  


/** @defgroup SPI_FLASH_Polling_Functions Functions
  * @{
  */ 

/*!
  * @brief  DeInitializes the peripherals used by the SPI FLASH driver
  *
  * @param  None
  *
  * @retval None
  *
  */
void sFLASH_LowLevelReset(void)
{
    GPIO_Config_T gpioConfig;

    SPI_Disable(sFLASH_SPI);
  
    SPI_Reset(sFLASH_SPI);
  
    RCM_DisableAPB2PeriphClock(sFLASH_SPI_CLK);
  
    /* Configure SCK pin */
    gpioConfig.pin = sFLASH_SPI_SCK_PIN;
    gpioConfig.mode = GPIO_MODE_IN;
    GPIO_Config(sFLASH_SPI_SCK_GPIO_PORT, &gpioConfig);

    /* Configure MISO pin */
    gpioConfig.pin = sFLASH_SPI_MISO_PIN;
    GPIO_Config(sFLASH_SPI_MISO_GPIO_PORT, &gpioConfig);

    /* Configure MOSI pin */
    gpioConfig.pin = sFLASH_SPI_MOSI_PIN;
    GPIO_Config(sFLASH_SPI_MOSI_GPIO_PORT, &gpioConfig);

    /* Configure CS pin */
    gpioConfig.pin = sFLASH_CS_PIN;
    GPIO_Config(sFLASH_CS_GPIO_PORT, &gpioConfig);
}

/*!
  * @brief  Initializes the peripherals used by the SPI FLASH driver
  *
  * @param  None
  *
  * @retval None
  *
  */
void sFLASH_LowLevel_Config(void)
{
    GPIO_Config_T gpioConfig;

    RCM_EnableAHBPeriphClock(sFLASH_CS_GPIO_CLK | sFLASH_SPI_MOSI_GPIO_CLK | sFLASH_SPI_MISO_GPIO_CLK |
                              sFLASH_SPI_SCK_GPIO_CLK );
 
    RCM_EnableAPB2PeriphClock(sFLASH_SPI_CLK);
    RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_SYSCFG);

    GPIO_ConfigPinAF(GPIOA,GPIO_PIN_SOURCE_5,GPIO_AF_PIN0);

    GPIO_ConfigPinAF(GPIOA,GPIO_PIN_SOURCE_6,GPIO_AF_PIN0);

    GPIO_ConfigPinAF(GPIOA,GPIO_PIN_SOURCE_7,GPIO_AF_PIN0);

    /* config PIN_5->SCK , PIN_7->MOSI*/
    gpioConfig.pin =  GPIO_PIN_5 | GPIO_PIN_7;
    gpioConfig.mode = GPIO_MODE_AF;
    gpioConfig.outtype = GPIO_OUT_TYPE_PP;
    gpioConfig.speed = GPIO_SPEED_50MHz;
    gpioConfig.pupd = GPIO_PUPD_PU;
    GPIO_Config(GPIOA, &gpioConfig);

    /* config PIN_4->NSS*/
    gpioConfig.pin =  GPIO_PIN_4;
    gpioConfig.outtype = GPIO_OUT_TYPE_PP;
    gpioConfig.speed = GPIO_SPEED_50MHz;
    gpioConfig.pupd = GPIO_PUPD_PU;
    gpioConfig.mode = GPIO_MODE_OUT;
    GPIO_Config(GPIOA, &gpioConfig);
    GPIO_SetBit(GPIOA, GPIO_PIN_4);

    /* config PIN_6  MISO*/
    gpioConfig.pin = GPIO_PIN_6;
    gpioConfig.mode = GPIO_MODE_AF;
    gpioConfig.pupd = GPIO_PUPD_PU;
    gpioConfig.speed = GPIO_SPEED_50MHz;
    GPIO_Config(GPIOA, &gpioConfig);
}    

/*!
  * @brief  DeInitializes the peripherals used by the SPI FLASH driver.
  *
  * @param  None
  *
  * @retval None
  */
void sFLASH_Reset(void)
{
    sFLASH_LowLevelReset();
}

/*!
  * @brief  Initializes the peripherals used by the SPI FLASH driver.
  *
  * @param  None
  *
  * @retval None
  */
void sFLASH_Config(void)
{
    SPI_Config_T  spiConfig;

    sFLASH_LowLevel_Config();
    
    sFLASH_CS_HIGH;


    SPI_Reset(SPI1);
    SPI_ConfigStructInit(&spiConfig);

    /* SPI configuration*/
    /* Set Clock polarity is High*/
    spiConfig.polarity = SPI_CLKPOL_HIGH;

    /* select master mode*/
    spiConfig.mode = SPI_MODE_MASTER;

    /* SPI Clock Phase is wEDGE*/
    spiConfig.phase = SPI_CLKPHA_2EDGE;

    /* Enable Software slave control */
    spiConfig.slaveSelect = SPI_SSC_ENABLE;

    /* Set SPI BaudRate divider*/
    spiConfig.baudrateDiv = SPI_BAUDRATE_DIV_256;

    /* SPI data length*/
    spiConfig.length = SPI_DATA_LENGTH_8B;

    /* Set internal slave*/
    SPI_EnableInternalSlave(SPI1);
    SPI_Config(SPI1, &spiConfig);
    SPI_ConfigFIFOThreshold(SPI1, SPI_RXFIFO_QUARTER);

    SPI_Enable(SPI1);
}

/*!
  * @brief  Erases the specified FLASH sector
  *
  * @param  SectorAddr: address of the sector to erase.
  *
  * @retval None
  */
void sFLASH_SectorErase(uint32_t SectorAddr)
{
    sFLASH_WriteEnable();

    sFLASH_CS_LOW;
    sFLASH_TxOneByte(sFLASH_CMD_SE);
    sFLASH_TxOneByte((SectorAddr & 0xFF0000) >> 16);
    sFLASH_TxOneByte((SectorAddr & 0xFF00) >> 8);
    sFLASH_TxOneByte(SectorAddr & 0xFF);
    sFLASH_CS_HIGH;

    /* Wait FLASH completed */
    sFLASH_WaitForWriteEnd();
}

/*!
  * @brief  Erases the entire FLASH
  *
  * @param  None
  *
  * @retval None
  */
void sFLASH_FullErase(void)
{
    sFLASH_WriteEnable();
  
    sFLASH_CS_LOW;
    sFLASH_TxOneByte(sFLASH_CMD_BE);
    sFLASH_CS_HIGH;
  
    sFLASH_WaitForWriteEnd();
}

/*!
  * @brief  Writes more than one byte to the FLASH with a single WRITE cycle 
  *         (Page WRITE sequence)
  *
  * @note   The number of byte can't exceed the FLASH page size
  *
  * @param  pBuffer: pointer to the buffer  containing the data to be written
  *         to the FLASH
  *
  * @param  WriteAddr: FLASH's internal address to write to
  *
  * @param  NumByteToWrite: number of bytes to write to the FLASH, must be equal
  *         or less than "sFLASH_PAGESIZE" value
  *
  * @retval None
  */
void sFLASH_PageProgram(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    sFLASH_WriteEnable();

    sFLASH_CS_LOW;
  
    sFLASH_TxOneByte(sFLASH_CMD_WRITE);
    sFLASH_TxOneByte((WriteAddr & 0xFF0000) >> 16);
    sFLASH_TxOneByte((WriteAddr & 0xFF00) >> 8);
    sFLASH_TxOneByte(WriteAddr & 0xFF);

    while (NumByteToWrite--)
    {
        sFLASH_TxOneByte(*pBuffer);
        pBuffer++;
    }

    sFLASH_CS_HIGH;

    sFLASH_WaitForWriteEnd();
}

/*!
  * @brief  Writes block of data to the FLASH. In this function, the number of
  *         WRITE cycles are reduced, using Page WRITE sequence
  *
  * @param  pBuffer: pointer to the buffer  containing the data to be written
  *         to the FLASH
  *
  * @param  WriteAddr: FLASH's internal address to write to
  *
  * @param  NumByteToWrite: number of bytes to write to the FLASH
  *
  * @retval None
  */
void sFLASH_WriteData(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
    uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;

    Addr = WriteAddr % sFLASH_SPI_PAGESIZE;
    count = sFLASH_SPI_PAGESIZE - Addr;
    NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
    NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

    if (Addr == 0) 
    {
        if (NumOfPage == 0) 
        {
            sFLASH_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
        }
        else 
        {
            while (NumOfPage--)
            {
                sFLASH_PageProgram(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
                WriteAddr +=  sFLASH_SPI_PAGESIZE;
                pBuffer += sFLASH_SPI_PAGESIZE;
            }

            sFLASH_PageProgram(pBuffer, WriteAddr, NumOfSingle);
        }
    }
    else 
    {
        if (NumOfPage == 0) 
        {
            if (NumOfSingle > count) 
            {
                temp = NumOfSingle - count;

                sFLASH_PageProgram(pBuffer, WriteAddr, count);
                WriteAddr +=  count;
                pBuffer += count;

                sFLASH_PageProgram(pBuffer, WriteAddr, temp);
            }
            else
            {
                sFLASH_PageProgram(pBuffer, WriteAddr, NumByteToWrite);
            }
        }
        else 
        {
            NumByteToWrite -= count;
            NumOfPage =  NumByteToWrite / sFLASH_SPI_PAGESIZE;
            NumOfSingle = NumByteToWrite % sFLASH_SPI_PAGESIZE;

            sFLASH_PageProgram(pBuffer, WriteAddr, count);
            WriteAddr +=  count;
            pBuffer += count;

            while (NumOfPage--)
            {
                sFLASH_PageProgram(pBuffer, WriteAddr, sFLASH_SPI_PAGESIZE);
                WriteAddr +=  sFLASH_SPI_PAGESIZE;
                pBuffer += sFLASH_SPI_PAGESIZE;
            }

            if (NumOfSingle != 0)
            {
                sFLASH_PageProgram(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
}

/*!
  * @brief  Reads a block of data from the FLASH
  *
  * @param  pBuffer: pointer to the buffer that receives the data read from the FLASH
  *
  * @param  ReadAddr: FLASH's internal address to read from
  *
  * @param  NumByteToRead: number of bytes to read from the FLASH
  *
  * @retval None
  */
void sFLASH_ReadData(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
    sFLASH_CS_LOW;

    sFLASH_TxOneByte(sFLASH_CMD_READ);
    sFLASH_TxOneByte((ReadAddr & 0xFF0000) >> 16);
    sFLASH_TxOneByte((ReadAddr& 0xFF00) >> 8);
    sFLASH_TxOneByte(ReadAddr & 0xFF);

    while (NumByteToRead--) 
    {
        *pBuffer = sFLASH_TxOneByte(sFLASH_DUMMY_BYTE);
        pBuffer++;
    }

    sFLASH_CS_HIGH;
}

/*!
  * @brief  Reads FLASH identification
  *
  * @param  None
  *
  * @retval FLASH identification
  */
uint32_t sFLASH_ReadID(void)
{
    uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;

    sFLASH_CS_LOW;

    sFLASH_TxOneByte(0x9F);

    Temp0 = sFLASH_TxOneByte(sFLASH_DUMMY_BYTE);
    Temp1 = sFLASH_TxOneByte(sFLASH_DUMMY_BYTE);
    Temp2 = sFLASH_TxOneByte(sFLASH_DUMMY_BYTE);
  
    sFLASH_CS_HIGH;

    Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;

     return Temp;
}

/*!
  * @brief  Initiates a read data byte (READ) sequence from the Flash.
  *   This is done by driving the /CS line low to select the device, then the READ
  *   instruction is transmitted followed by 3 bytes address. This function exit
  *   and keep the /CS line low, so the Flash still being selected. With this
  *   technique the whole content of the Flash is read with a single READ instruction
  *
  * @param  ReadAddr: FLASH's internal address to read from
  *
  * @retval None
  */
void sFLASH_ReadSequenceStart(uint32_t ReadAddr)
{
    sFLASH_CS_LOW;

    sFLASH_TxOneByte(sFLASH_CMD_READ);
    sFLASH_TxOneByte((ReadAddr & 0xFF0000) >> 16);
    sFLASH_TxOneByte((ReadAddr& 0xFF00) >> 8);
    sFLASH_TxOneByte(ReadAddr & 0xFF);
}

/*!
  * @brief  Reads a byte from the SPI Flash
  *
  * @note   This function must be used only if the Start_Read_Sequence function
  *         has been previously called
  *
  * @param  None
  *
  * @retval Byte Read from the SPI Flash
  */
uint8_t sFLASH_ReadOneByte(void)
{
    return (sFLASH_TxOneByte(sFLASH_DUMMY_BYTE));
}

/*!
  * @brief  Sends a byte through the SPI interface and return the byte received
  *         from the SPI bus
  *
  * @param  byte: byte to send
  *
  * @retval The value of the received byte
  *
  */
uint8_t sFLASH_TxOneByte(uint8_t byte)
{
    while (SPI_ReadStatusFlag(sFLASH_SPI, SPI_FLAG_TXBE) == RESET);

    SPI_TxData8(sFLASH_SPI, byte);

    while (SPI_ReadStatusFlag(sFLASH_SPI, SPI_FLAG_RXBNE) == RESET);

    return SPI_RxData8(sFLASH_SPI);
}

/*!
  * @brief  Sends a Half Word through the SPI interface and return the Half Word
  *         received from the SPI bus
  *
  * @param  HalfWord: Half Word to send
  *
  * @retval The value of the received Half Word
  */
uint16_t sFLASH_TxOneHalfWord(uint16_t HalfWord)
{

    while (SPI_ReadStatusFlag(sFLASH_SPI, SPI_FLAG_TXBE) == RESET);

    SPI_TxData8(sFLASH_SPI, HalfWord);

    while (SPI_ReadStatusFlag(sFLASH_SPI, SPI_FLAG_RXBNE) == RESET);

    return SPI_RxData8(sFLASH_SPI);
}

/*!
  * @brief  Enables the write access to the FLASH
  *
  * @param  None
  *
  * @retval None
  */
void sFLASH_WriteEnable(void)
{
    sFLASH_CS_LOW;

    sFLASH_TxOneByte(sFLASH_CMD_WREN);

    sFLASH_CS_HIGH;
}

/*!
  * @brief  Polls the status of the Write In Progress (WIP) flag in the FLASH's
  *         status register and loop until write opertaion has completed.
  *
  * @param  None
  *
  * @retval None
  */
void sFLASH_WaitForWriteEnd(void)
{
    uint8_t flashstatus = 0;

    sFLASH_CS_LOW;
 
    sFLASH_TxOneByte(sFLASH_CMD_RDSR);

    do
    {
        flashstatus = sFLASH_TxOneByte(sFLASH_DUMMY_BYTE);
 
    }
    while ((flashstatus & sFLASH_WIP_FLAG) == SET);

    sFLASH_CS_HIGH;
}

/**
  * @}
  */

/**
  * @}
  */

/**
  * @}
  */


