/**
 *******************************************************************************
 * @file    w25q80.c
 *
 * @brief   w25q80 program body.
 *
 * @author  DPM
 *
 * @version V1.0.0
 *
 * @date    2024-5-6
 *
 *******************************************************************************/

/*
    Copyright (c) 2024, DP Microelectronics Inc.

    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 "w25q80.h"
#include "dpm32m0xx.h"
#include "spi.h"

////////////////////////////////////////////// 用户配置区域开始 /////////////////////////////////

#define EXFLASH_PAGE_SIZE ((uint32_t)256)
#define EXFLASH_SECTOR_SIZE ((uint32_t)4 * 1024)
#define EXFLASH_BLOCK_32K ((uint32_t)32 * 1024)
#define EXFLASH_BLOCK_64K ((uint32_t)64 * 1024)
#define EXFLASH_CHIP_SIZE ((uint32_t)16 * 1024 * 1024)

#define W25QXX_SPI_Handle SPI1

#define W25QXX_CS_L()       GPIO_ResetBit(GPIOB, GPIO_PIN_12)
#define W25QXX_CS_H()       GPIO_SetBit(GPIOB, GPIO_PIN_12)

/**
 *******************************************************************************
 * @brief   spi发送读取数据
 * @param   [in]  TxData          spi发送的数据
 * @retval  uint8_t               spi读取的数据
 ******************************************************************************/
static uint8_t W25QxxReadWriteByte(uint8_t TxData)
{
  return SPI_TransmitReceive(W25QXX_SPI_Handle, TxData);
}

////////////////////////////////////////////// 用户配置区域结束 /////////////////////////////////

/**
 *******************************************************************************
 * @brief   外部flash初始化
 * @retval  None.
 ******************************************************************************/
void W25QxxInit(void)
{
  uint8_t UID[8] = {0};
  W25QXX_CS_L(); /* 拉低选中 */
  W25QxxReadWriteByte(0xFF);
  W25QXX_CS_H(); /* 拉高取消 */

  W25QxxReadUniqueID(UID); // 读取唯一ID

  printf("W25Qxx ID: 0x%04x\r\n", W25QxxReadID());
  printf("W25Qxx Capacity: 0x%x\r\n", W25QxxReadCapacity());
  printf("W25Qxx UID: 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x \r\n",
         UID[0], UID[1], UID[2], UID[3], UID[4], UID[5], UID[6], UID[7]);
}

/**
 *******************************************************************************
 * @brief   外部flash写使能
 * @retval  None.
 ******************************************************************************/
void W25QxxWriteEnable(void)
{
  W25QXX_CS_L();
  W25QxxReadWriteByte(0x06);
  W25QXX_CS_H();
}

/**
 *******************************************************************************
 * @brief   外部flash写失能
 * @retval  None.
 ******************************************************************************/
void W25QXX_Write_Disable(void)
{
  W25QXX_CS_L();
  W25QxxReadWriteByte(0x04);
  W25QXX_CS_H();
}

/**
 *******************************************************************************
 * @brief   读取芯片型号
 * @retval  uint16_t              芯片型号
 *                                @arg 0XEF13,表示芯片型号为W25Q80
 *                                @arg 0XEF14,表示芯片型号为W25Q16
 *                                @arg 0XEF15,表示芯片型号为W25Q32
 *                                @arg 0XEF16,表示芯片型号为W25Q64
 *                                @arg 0XEF17,表示芯片型号为W25Q128
 ******************************************************************************/
uint16_t W25QxxReadID(void)
{
  uint8_t id_h = 0;
  uint8_t id_l = 0;

  W25QXX_CS_L();

  W25QxxReadWriteByte(0x90);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x00);

  id_h = W25QxxReadWriteByte(0xff);
  id_l = W25QxxReadWriteByte(0xff);

  W25QXX_CS_H();

  return (id_h << 8) | id_l;
}

/**
 *******************************************************************************
 * @brief   读取芯片容量
 * @retval  uint32_t              芯片容量
 ******************************************************************************/
uint32_t W25QxxReadCapacity(void)
{
  int i = 0;
  uint8_t arr[4] = {0, 0, 0, 0};

  W25QXX_CS_L();
  W25QxxReadWriteByte(0x5A);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x84);
  W25QxxReadWriteByte(0x00);

  for(i = 0; i < sizeof(arr); i++)
  {
    arr[i] = W25QxxReadWriteByte(0xFF);
  }

  W25QXX_CS_H();

  return ((((*(uint32_t *)arr)) + 1) >> 3);
}

/**
 *******************************************************************************
 * @brief   读取芯片唯一ID
 * @param   [out]  UID            唯一ID
 * @retval  None.
 ******************************************************************************/
void W25QxxReadUniqueID(uint8_t *UID)
{
  int i = 0;
  W25QXX_CS_L();
  W25QxxReadWriteByte(0x4B);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x00);
  W25QxxReadWriteByte(0x00);

  for(i = 0; i < 8; i++)
  {
    *UID++ = W25QxxReadWriteByte(0xFF);
  }

  W25QXX_CS_H();
}

/**
 *******************************************************************************
 * @brief   读取寄存器状态
 * @retval  None.
 ******************************************************************************/
static void W25QxxReadStatue(void)
{
  uint8_t statue;

  do
  {
    W25QXX_CS_L();
    W25QxxReadWriteByte(0x05);
    statue = W25QxxReadWriteByte(0xff);
    W25QXX_CS_H();

    DelayMs(1);
  }
  while(statue & (1 << 0));
}

/**
 *******************************************************************************
 * @brief   外部flash写数据
 * @param   [in]  add             写的数据地址
 * @param   [in]  data            数据
 * @param   [in]  len             数据长度, 小于256字节
 * @retval  None.
 ******************************************************************************/
void W25QxxPageWrite(uint32_t add, uint8_t *data, uint16_t len)
{

  W25QxxWriteEnable();

  W25QXX_CS_L();
  W25QxxReadWriteByte(0x02);
  W25QxxReadWriteByte((add & 0xff0000) >> 16);
  W25QxxReadWriteByte((add & 0x00ff00) >> 8);
  W25QxxReadWriteByte(add & 0x0000ff);

  uint16_t i;

  for(i = 0; i < len; i++)
  {
    W25QxxReadWriteByte(*data);
    data++;
  }

  W25QXX_CS_H();

  W25QxxReadStatue();
}

/**
 *******************************************************************************
 * @brief   外部flash读数据
 * @param   [in]  add             读取数据地址
 * @param   [out] ref_buf         数据缓存buf
 * @param   [in]  len             数据长度
 * @retval  None.
 ******************************************************************************/
void W25QxxReadData(uint32_t add, uint8_t *ref_buf, uint16_t len)
{

  W25QXX_CS_L();
  W25QxxReadWriteByte(0x03);
  W25QxxReadWriteByte((add & 0xff0000) >> 16);
  W25QxxReadWriteByte((add & 0x00ff00) >> 8);
  W25QxxReadWriteByte(add & 0x0000ff);

  uint16_t i = 0;

  for(i = 0; i < len; i++)
  {
    *ref_buf = W25QxxReadWriteByte(0xff);
    ref_buf++;
  }

  W25QXX_CS_H();
}

/**
 *******************************************************************************
 * @brief   外部flash扇区擦除,4k
 * @param   [in]  add             擦除地址
 * @retval  None.
 ******************************************************************************/
void W25QxxSector4kErase(uint32_t add)
{
  W25QxxWriteEnable();

  W25QXX_CS_L();
  W25QxxReadWriteByte(0x20);
  W25QxxReadWriteByte((add & 0xff0000) >> 16);
  W25QxxReadWriteByte((add & 0x00ff00) >> 8);
  W25QxxReadWriteByte(add & 0x0000ff);
  W25QXX_CS_H();

  W25QxxReadStatue();
}

/**
 *******************************************************************************
 * @brief   外部flash块擦除,32k
 * @param   [in]  add             擦除地址
 * @retval  None.
 ******************************************************************************/
void W25QxxBlock32Erase(uint32_t add)
{
  W25QxxWriteEnable();

  W25QXX_CS_L();
  W25QxxReadWriteByte(0x52);
  W25QxxReadWriteByte((add & 0xff0000) >> 16);
  W25QxxReadWriteByte((add & 0x00ff00) >> 8);
  W25QxxReadWriteByte(add & 0x0000ff);
  W25QXX_CS_H();

  W25QxxReadStatue();
}

/**
 *******************************************************************************
 * @brief   外部flash块擦除,64k
 * @param   [in]  add             擦除地址
 * @retval  None.
 ******************************************************************************/
void W25QxxBlock64Erase(uint32_t add)
{
  W25QxxWriteEnable();

  W25QXX_CS_L();
  W25QxxReadWriteByte(0xD8);
  W25QxxReadWriteByte((add & 0xff0000) >> 16);
  W25QxxReadWriteByte((add & 0x00ff00) >> 8);
  W25QxxReadWriteByte(add & 0x0000ff);
  W25QXX_CS_H();

  W25QxxReadStatue();
}

/**
 *******************************************************************************
 * @brief   外部flash芯片擦除
 * @retval  None.
 ******************************************************************************/
void W25QxxChipErase(void)
{
  W25QxxWriteEnable();
  W25QXX_CS_L();
  W25QxxReadWriteByte(0x60);
  W25QXX_CS_H();

  DelayMs(50);

  W25QxxReadStatue();
}

/**
 *******************************************************************************
 * @brief   外部flash进入低功耗模式
 * @retval  None.
 ******************************************************************************/
void W25QxxDeepPowerDown(void)
{
  W25QxxWriteEnable();
  W25QXX_CS_L();
  W25QxxReadWriteByte(0xB9);
  W25QXX_CS_H();
}

/**
 *******************************************************************************
 * @brief   外部flash唤醒
 * @retval  None.
 ******************************************************************************/
void W25QxxWakeUp(void)
{
  W25QxxWriteEnable();
  W25QXX_CS_L();
  W25QxxReadWriteByte(0xAB);
  W25QXX_CS_H();

  W25QxxReadStatue();
}

/**
 *******************************************************************************
 * @brief   外部flash连续写数据
 * @param   [in]  add             写入数据地址
 * @param   [in]  buf             数据缓存buf
 * @param   [in]  len             数据长度
 * @retval  uint8_t               写入状态.
 ******************************************************************************/
uint8_t W25QxxContinueWriteData(uint32_t addr, uint8_t *buf, uint32_t len)
{
  uint16_t pageremain;

  if((addr + len) > EXFLASH_CHIP_SIZE)
  {
    return 1;
  }

  pageremain = EXFLASH_PAGE_SIZE - addr % EXFLASH_PAGE_SIZE; // 单页剩余的字节数

  if(len <= pageremain)
  {
    pageremain = len; // 不大于256个字节
  }

  while(1)
  {
    W25QxxPageWrite(addr, buf, pageremain);

    if(len == pageremain)
    {
      break; // 写入结束了
    }
    else // len>pageremain
    {
      buf += pageremain;
      addr += pageremain;
      len -= pageremain; // 减去已经写入了的字节数

      if(len > EXFLASH_PAGE_SIZE)
      {
        pageremain = EXFLASH_PAGE_SIZE; // 一次可以写入256个字节
      }
      else
      {
        pageremain = len; // 不够256个字节了
      }
    }
  }

  return 0;
}

/**
 *******************************************************************************
 * @brief   外部flash连续读数据
 * @param   [in]  add             写入数据地址
 * @param   [out] buf             数据缓存buf
 * @param   [in]  len             数据长度
 * @retval  uint8_t               读取状态.
 ******************************************************************************/
uint8_t W25QxxContinueReadData(uint32_t addr, uint8_t *buf, uint32_t len)
{
  uint32_t l_len = len;
  uint32_t l_index = 0;

  if((addr + len) > EXFLASH_CHIP_SIZE)
  {
    return 1;
  }

  while(l_len > 0)
  {
    if(l_len > EXFLASH_PAGE_SIZE)
    {
      W25QxxReadData(addr + l_index, buf + l_index, EXFLASH_PAGE_SIZE);
      l_len -= EXFLASH_PAGE_SIZE;
      l_index += EXFLASH_PAGE_SIZE;
    }
    else
    {
      W25QxxReadData(addr + l_index, buf + l_index, l_len);
      l_len -= l_len;
      l_index += l_len;
    }
  }

  return 0;
}
