/*****************************************************************************
 * file    Core/Src/flash_if.c 
 * author  WTOE
 * version 1.0.0
 * date    13-Jun-2022
 * brief   This file provides all the memory related operation functions.
 ******************************************************************************
 * Attention
 *
 * <h2><center>&copy; COPYRIGHT(c) 2022 LandPower</center></h2>
 *
 * 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 WTOE 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.
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "cmsis_os.h"
#include "spi.h"

#include "common.h"
#include "flash_if.h"

/* Private define ------------------------------------------------------------*/
/* flash id 长度. */
#define SPI_FLASH_ID_LEN 3
/* 一般命令长度, 1byte 命令字, 3byte地址. */
#define SPI_CMD_HEADER_LEN 4

/* spi总线超时时间. */
#define SPI_BUS_TIMEOUT 1000

/* spi flash状态寄存器bit mask. */
#define SPI_FLASH_BUSY_MASK 0x01
#define SPI_FLASH_WEL_MASK 0x02

/* 片选使能与去使能. */
#define SPI_FLASH_CS_ENABLE() HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_RESET)
#define SPI_FLASH_CS_DISABLE() HAL_GPIO_WritePin(SPI_FLASH_CS_GPIO_Port, SPI_FLASH_CS_Pin, GPIO_PIN_SET)

#define FLASH_E_RETURN_MUTEX(_f_) \
    do { \
        HAL_StatusTypeDef _rv_ = HAL_ERROR; \
        if ((_rv_ = _f_) != HAL_OK) \
        { \
            ERROR_PRINT(_rv_); \
            _spi_flash_mutex_unlock(); \
            return _rv_; \
        } \
    } while(0)

/* Private typedef -----------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
static osMutexId_t flash_mutex;
static const osMutexAttr_t flash_mutex_attr = {
    .name = "flash"
};
static SPI_HandleTypeDef *flash_spi = &hspi3;


/* Private function prototypes -----------------------------------------------*/
/* Internal functions --------------------------------------------------------*/
/* 获取互斥锁.  */
static void _spi_flash_mutex_lock(void)
{
    osMutexAcquire(flash_mutex, osWaitForever);
}

/* 释放互斥锁. */
static void _spi_flash_mutex_unlock(void)
{
    osMutexRelease(flash_mutex);
}

/* internal flash写使能. */
static HAL_StatusTypeDef _spi_flash_write_enable(void)
{
    uint8_t cmd = SPI_CMD_WRITE_ENABLE;

    SPI_FLASH_CS_ENABLE();

    /* 发送学是能命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(flash_spi, &cmd, 1, SPI_BUS_TIMEOUT));

    SPI_FLASH_CS_DISABLE();

    return HAL_OK;
}

static HAL_StatusTypeDef _spi_flash_RDSR(uint8_t *status)
{
    uint8_t cmd = SPI_CMD_STATE_READ;

    SPI_FLASH_CS_ENABLE();

    /* 发送读flash状态寄存器命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(flash_spi, &cmd, 1, SPI_BUS_TIMEOUT));

    /* 读取spi状态寄存器. */    
    HAL_E_RETURN(HAL_SPI_Receive(flash_spi, status, 1, SPI_BUS_TIMEOUT));

    SPI_FLASH_CS_DISABLE();

    return HAL_OK;
}

/* 等待flash busy. */
static HAL_StatusTypeDef _spi_flash_wait_complete(void)
{ 
    uint8_t status = 0xFF;
    uint32_t tickstart = HAL_GetTick();
    
    do
    {
        HAL_E_RETURN(_spi_flash_RDSR(&status));
        /* 超时处理. */
        if ((HAL_GetTick() - tickstart) > SPI_BUS_TIMEOUT)
        {
            ERROR_PRINT(HAL_TIMEOUT);
            return HAL_TIMEOUT;
        }
    } while(SPI_FLASH_BUSY_MASK == (status & SPI_FLASH_BUSY_MASK));

    return HAL_OK;
}

/* 等待flash写使能. */
static HAL_StatusTypeDef _spi_flash_wait_write(void)
{ 
    uint8_t status = 0xFF;
    uint32_t tickstart = HAL_GetTick();
    
    do
    {
        HAL_E_RETURN(_spi_flash_RDSR(&status));
        /* 超时处理. */
        if ((HAL_GetTick() - tickstart) > SPI_BUS_TIMEOUT)
        {
            ERROR_PRINT(HAL_TIMEOUT);
            return HAL_TIMEOUT;
        }
    } while (0 == (status & SPI_FLASH_WEL_MASK));

    return HAL_OK;
}

/* spi flash页写入操作, size最大为页大小(256Byte). */
static HAL_StatusTypeDef _spi_flash_page_write(uint32_t addr, uint8_t *data, uint16_t size) 
{
    uint8_t cmd[SPI_CMD_HEADER_LEN] = {0};

    /* 写使能. */ 
    HAL_E_RETURN(_spi_flash_write_enable());

    /* 等待写使能完成. */
    HAL_E_RETURN(_spi_flash_wait_write());

    SPI_FLASH_CS_ENABLE();

    /* 页写入命令初始化. */
    cmd[0] = SPI_CMD_PAGE_WRITE;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    /* 发送页写入命令. */
    HAL_E_RETURN(HAL_SPI_Transmit(flash_spi, cmd, SPI_CMD_HEADER_LEN, SPI_BUS_TIMEOUT));
    
    /* 确保写入大小不超过页(256Byte)大小. */ 
    if (size > SPI_FLASH_PAGE_SIZE) 
        size = SPI_FLASH_PAGE_SIZE; 

    /* 发送数据. */ 
    HAL_E_RETURN(HAL_SPI_Transmit(flash_spi, data, size, SPI_BUS_TIMEOUT));
    
    SPI_FLASH_CS_DISABLE();
    
    /* 等待flash busy状态. */
    HAL_E_RETURN(_spi_flash_wait_complete());

    return HAL_OK;
}

/* Interface functions -------------------------------------------------------*/
/* 在进入rtos线程调度前,需要的初始化. */
void spi_flash_init_os(void)
{
    /* 初始化互斥锁. */
    flash_mutex = osMutexNew(&flash_mutex_attr);
    /* 致命错误,此处不应该被运行,记录错误log,打印错误信息. */
    if (NULL == flash_mutex)
        ERROR_PRINT(-1);
}

/* 读 spi flash id. */
uint32_t spi_flash_RDJDID(void)
{ 
    uint8_t cmd = SPI_CMD_READ_ID;
    uint8_t data[SPI_FLASH_ID_LEN] = {0};

    _spi_flash_mutex_lock();

    SPI_FLASH_CS_ENABLE();

    /* 发送read id命令. */
    HAL_SPI_Transmit(flash_spi, &cmd, 1, SPI_BUS_TIMEOUT);

    /* 读取3byte flash id. */
    HAL_SPI_Receive(flash_spi, data, SPI_FLASH_ID_LEN, SPI_BUS_TIMEOUT);

    SPI_FLASH_CS_DISABLE();

    _spi_flash_mutex_unlock();
    return (data[0] << 16) | (data[1] << 8) | (data[2]);
}

/* 读spi flash状态寄存器. */
HAL_StatusTypeDef spi_flash_RDSR(uint8_t *status)
{
    _spi_flash_mutex_lock();

    /* 读取spi flash状态寄存器. */
    FLASH_E_RETURN_MUTEX(_spi_flash_RDSR(status));
    
    _spi_flash_mutex_unlock();
    return HAL_OK;
}

/* 擦除spi flash,根据cmd传的不一样,可以擦除4K,32K,64K数据.
   注意: 这里不检查addr和cmd的正确性,请调用者自行保证. */
HAL_StatusTypeDef spi_flash_erase(uint32_t addr, uint8_t erase_cmd)
{
    uint8_t cmd[SPI_CMD_HEADER_LEN] = {0};

    _spi_flash_mutex_lock();
    
    /* 写使能. */ 
    FLASH_E_RETURN_MUTEX(_spi_flash_write_enable()); 

    /* 等待写使能完成. */
    FLASH_E_RETURN_MUTEX(_spi_flash_wait_write());

    /* 片选使能. */
    SPI_FLASH_CS_ENABLE();

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

    /* 发送擦除扇区命令. */
    FLASH_E_RETURN_MUTEX(HAL_SPI_Transmit(flash_spi, cmd, SPI_CMD_HEADER_LEN, SPI_BUS_TIMEOUT));

    /* 片选去使能. */
    SPI_FLASH_CS_DISABLE();
    
    /* 等待flash busy状态. */
    FLASH_E_RETURN_MUTEX(_spi_flash_wait_complete());

    _spi_flash_mutex_unlock();
    return HAL_OK;
} 

/* 从spi flash中读取数据. */
HAL_StatusTypeDef spi_flash_read(uint32_t addr, uint8_t* data, uint16_t size)
{
    uint8_t cmd[SPI_CMD_HEADER_LEN] = {0};

    _spi_flash_mutex_lock();

    /* 设备刚启动的时候,读flash是全0,因为刚上电flash自己还没有初始化完成,
       所以在这里等flash busy状态,主要是保证读的时候flash是准备好的. */
    FLASH_E_RETURN_MUTEX(_spi_flash_wait_complete());

    SPI_FLASH_CS_ENABLE();
    
    cmd[0] = SPI_CMD_FLASH_READ;
    cmd[1] = (addr >> 16) & 0xFF;
    cmd[2] = (addr >> 8) & 0xFF;
    cmd[3] = addr & 0xFF;

    /* 发送spi flash读命令. */
    FLASH_E_RETURN_MUTEX(HAL_SPI_Transmit(flash_spi, cmd, SPI_CMD_HEADER_LEN, SPI_BUS_TIMEOUT));

    /* 读取size大小的数据到data. */
    FLASH_E_RETURN_MUTEX(HAL_SPI_Receive(flash_spi, data, size, SPI_BUS_TIMEOUT));
    
    SPI_FLASH_CS_DISABLE();

    _spi_flash_mutex_unlock();
    return HAL_OK;
}

HAL_StatusTypeDef spi_flash_write(uint32_t addr, uint8_t *data, uint16_t size)
{
    uint8_t pages = 0;
    uint8_t first_bytes = 0;
    uint8_t last_bytes = 0;
    
    /* 这里划分好数据需要写多少页,写大小. */
    first_bytes = SPI_FLASH_PAGE_SIZE - (addr & (SPI_FLASH_PAGE_SIZE - 1));
    if (first_bytes > size)
        first_bytes = size;

    last_bytes = (size - first_bytes) & (SPI_FLASH_PAGE_SIZE - 1);
    pages = (size - first_bytes - last_bytes) / SPI_FLASH_PAGE_SIZE;
    
    _spi_flash_mutex_lock();

    /* 写第一页. */
    if (first_bytes > 0)
    {
        FLASH_E_RETURN_MUTEX(_spi_flash_page_write(addr, data, first_bytes));
        addr += first_bytes;
        data += first_bytes;
    }

    /* 写中间页. */
    while(pages--)
    {
        FLASH_E_RETURN_MUTEX(_spi_flash_page_write(addr, data, SPI_FLASH_PAGE_SIZE));
        addr += SPI_FLASH_PAGE_SIZE;
        data += SPI_FLASH_PAGE_SIZE;
    }

    /* 剩余不足一页的部分. */
    if (last_bytes > 0)
    {
        FLASH_E_RETURN_MUTEX(_spi_flash_page_write(addr, data, last_bytes));
    }

    _spi_flash_mutex_unlock();
    return HAL_OK;
}

/**
  * @brief  Unlocks Flash for write access
  * @param  None
  * @retval None
  */
void FLASH_If_Init(void)
{
  /* Unlock the Program memory */
  HAL_FLASH_Unlock();

  /* Clear all FLASH flags */
  __HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_PGSERR | FLASH_FLAG_WRPERR | FLASH_FLAG_OPTVERR);
  /* Unlock the Program memory */
  HAL_FLASH_Lock();
}

/**
  * @brief  This function does an erase of all user flash area
  * @param  start: start of user flash area
  * @retval FLASHIF_OK : user flash area successfully erased
  *         FLASHIF_ERASEKO : error occurred
  */
uint32_t FLASH_If_Erase(void)
{
  uint32_t PageError = 0;
  FLASH_EraseInitTypeDef pEraseInit;
  HAL_StatusTypeDef status = HAL_OK;

  /* Unlock the Flash to enable the flash control register access *************/ 
  HAL_FLASH_Unlock();
  pEraseInit.Banks = FLASH_BANK_1;
  pEraseInit.NbPages = 16;
  pEraseInit.Page = 0;
  pEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
  status = HAL_FLASHEx_Erase(&pEraseInit, &PageError);
    
  /* Lock the Flash to disable the flash control register access (recommended
     to protect the FLASH memory against possible unwanted operation) *********/
  HAL_FLASH_Lock();

  return status;
}

/**
  * @brief  This function writes a data buffer in flash (data are 32-bit aligned).
  * @note   After writing data buffer, the flash content is checked.
  * @param  destination: start address for target location
  * @param  p_source: pointer on buffer with data to write
  * @param  length: length of data buffer (unit is 32-bit word)
  * @retval uint32_t 0: Data successfully written to Flash memory
  *         1: Error occurred while writing data in Flash memory
  *         2: Written Data in flash memory is different from expected one
  */
uint32_t FLASH_If_Write(uint32_t destination, uint32_t *p_source, uint32_t length)
{
  uint32_t status = HAL_OK;
  uint32_t i = 0;

  /* Unlock the Flash to enable the flash control register access *************/
  HAL_FLASH_Unlock();

  /* DataLength must be a multiple of 64 bit */
  for (i = 0; (i < length/2) && (destination <= (USER_FLASH_END_ADDRESS-8)); i++)
  {
    /* Device voltage range supposed to be [2.7V to 3.6V], the operation will
       be done by word */ 
    if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_DOUBLEWORD, destination, *((uint64_t *)(p_source+2*i))) == HAL_OK)      
    {
     /* Check the written value */
      if (*(uint64_t*)destination != *(uint64_t *)(p_source+2*i))
      {
        /* Flash content doesn't match SRAM content */
        status = HAL_ERROR;
        break;
      }
      /* Increment FLASH destination address */
      destination += 8;
    }
    else
    {
      /* Error occurred while writing data in Flash memory */
      status = HAL_ERROR;
      break;
    }
  }

  /* Lock the Flash to disable the flash control register access (recommended
     to protect the FLASH memory against possible unwanted operation) *********/
  HAL_FLASH_Lock();

  return status;
}
/****************** (C) COPYRIGHT LandPower ***** END OF FILE *****************/
