963 lines
30 KiB
C
963 lines
30 KiB
C
/*
|
||
* 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动
|
||
* 模块功能:提供 GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接口
|
||
* 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI
|
||
* 作者:王建锋
|
||
* 创建日期:2026-07-16
|
||
* 修改记录:
|
||
* 2026-07-16 王建锋 创建初始版本
|
||
* 2026-07-17 王建锋 切换硬件 SPI,修正擦除地址,参考 NuttX 驱动
|
||
* 2026-08-27 补充函数注释(代码规范 V1.0);修正工厂坏块扫描为首页;增加 BBT 持久化
|
||
*/
|
||
|
||
/* 头文件包含区 */
|
||
#include <string.h>
|
||
#include "gd5f2gq5ue.h"
|
||
|
||
/*
|
||
* 调试输出配置
|
||
*/
|
||
#define DBG_TAG "[NAND]"
|
||
#include "dbg_log.h"
|
||
|
||
/* 私有宏定义区 */
|
||
#define GD5F_SPI_TIMEOUT 100
|
||
#define GD5F_BBT_SIZE (GD5F_TOTAL_BLOCKS / 8)
|
||
|
||
/* BBT 静态数组(1 bit 表示一个块,0=好块 1=坏块) */
|
||
static uint8_t s_bbt[GD5F_BBT_SIZE];
|
||
|
||
/* BBT 持久化:保留块池(从 NAND 顶部向下挑选出厂好块,存储 BBT 副本) */
|
||
#define GD5F_BBT_HDR_SIZE 16
|
||
static uint32_t s_bbt_pool_blocks[GD5F_BBT_POOL_COUNT]; /* BBT 保留池块号列表(出厂好块) */
|
||
static uint32_t s_bbt_pool_count = 0; /* 实际可用池块数(<= GD5F_BBT_POOL_COUNT) */
|
||
static uint32_t s_usable_blocks = GD5F_TOTAL_BLOCKS; /* dhara 可见块数(= 最低池块号) */
|
||
static uint32_t s_bbt_write_slot = 0; /* 下一写入槽(轮转指针) */
|
||
static uint32_t s_bbt_version = 0; /* 当前持久化 BBT 版本号 */
|
||
static uint8_t s_bbt_slot_dead = 0; /* 池块损坏位掩码 */
|
||
static uint8_t s_bbt_page_buf[GD5F_PAGE_SIZE]; /* BBT 读写页缓冲(静态,避免占栈) */
|
||
static uint8_t s_bbt_best[GD5F_BBT_SIZE]; /* 最高版本位图缓存 */
|
||
static const uint8_t s_bbt_magic[4] = { 'G', 'B', 'B', 'T' }; /* BBT 存储魔数 "GBBT" */
|
||
|
||
/* 外部 SPI 句柄声明 */
|
||
extern SPI_HandleTypeDef hspi1;
|
||
|
||
/* DMA 完成标志(ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */
|
||
volatile uint8_t g_spi1_dma_done;
|
||
|
||
/* DMA 阈值(字节数超过此值使用 DMA 传输) */
|
||
#define GD5F_DMA_THRESHOLD 32
|
||
|
||
/*
|
||
* 函数功能:SPI1 TX DMA 完成回调(PROGRAM_LOAD 数据发送用)
|
||
* 入口参数:hspi - 触发回调的 SPI 句柄 SPI_HandleTypeDef*
|
||
* 返回值:无
|
||
* 限定条件:由 HAL DMA 中断调用
|
||
* 函数说明:仅当 SPI1 传输完成时置位 g_spi1_dma_done 标志
|
||
*/
|
||
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
|
||
if (hspi->Instance == SPI1) {
|
||
g_spi1_dma_done = 1U;
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:SPI1 RX DMA 完成回调(READ_FROM_CACHE 数据接收用)
|
||
* 函数说明:HAL_SPI_Receive_DMA() 在 2 线 master 模式下
|
||
* 内部走 HAL_SPI_TransmitReceive_DMA 但 state=BUSY_RX,
|
||
* 完成回调是 HAL_SPI_RxCpltCallback 而非 TxRxCpltCallback
|
||
*/
|
||
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
|
||
if (hspi->Instance == SPI1) {
|
||
g_spi1_dma_done = 1U;
|
||
}
|
||
}
|
||
|
||
/* ======================== 私有函数声明 ======================== */
|
||
|
||
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
|
||
const uint8_t *p_buf, size_t size);
|
||
static int gd5f_set_feature(uint8_t addr, uint8_t data);
|
||
|
||
/* BBT 持久化内部函数 */
|
||
static int gd5f_private_bbt_locate_pool(void);
|
||
static uint32_t gd5f_private_crc32(const uint8_t *p_buf, uint32_t len);
|
||
static int gd5f_private_bbt_find_best(void);
|
||
static int gd5f_bbt_load(void);
|
||
static int gd5f_bbt_save(void);
|
||
|
||
/* ======================== SPI 原语定义 ======================== */
|
||
|
||
/*
|
||
* 函数功能:等待芯片操作完成(轮询 OIP 位)
|
||
* 入口参数:timeout_ms - 超时时间 uint32_t > 0
|
||
* 返回值:0 - 操作完成,-2 - 超时
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:循环读取状态寄存器直到 OIP 位清零或超时
|
||
*/
|
||
int gd5f_wait_busy(uint32_t timeout_ms) {
|
||
uint8_t cmd = GD5F_CMD_GET_FEATURE;
|
||
uint8_t addr = GD5F_REG_STATUS;
|
||
uint8_t status = 0;
|
||
uint32_t tick_start = HAL_GetTick();
|
||
|
||
while (1) {
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, &status, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
if ((status & GD5F_STATUS_OIP) == 0) {
|
||
return GD5F_OK;
|
||
}
|
||
if ((HAL_GetTick() - tick_start) >= timeout_ms) {
|
||
return GD5F_BUSY_TIMEOUT;
|
||
}
|
||
}
|
||
}
|
||
|
||
/*
|
||
* 函数功能:发送写使能命令
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:CS# 拉低后发 06h 命令再拉高 */
|
||
int gd5f_write_enable(void) {
|
||
uint8_t cmd = GD5F_CMD_WRITE_ENABLE;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取状态寄存器
|
||
* 出口参数:p_status - 状态值输出指针 uint8_t* 不为 NULL
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发 0Fh + C0h 地址后读 1 字节状态 */
|
||
int gd5f_read_status(uint8_t *p_status) {
|
||
uint8_t cmd = GD5F_CMD_GET_FEATURE;
|
||
uint8_t addr = GD5F_REG_STATUS;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Transmit(&hspi1, &addr, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, p_status, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页读取(将数据从存储阵列加载到内部缓存)
|
||
* 入口参数:page_addr - 页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1
|
||
* 返回值:0 - 成功,其它 - 错误
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发 13h + 3字节行地址,等待 OIP 清零
|
||
*/
|
||
int gd5f_page_read(uint32_t page_addr) {
|
||
uint8_t cmd[4] = {0};
|
||
|
||
cmd[0] = GD5F_CMD_PAGE_READ;
|
||
cmd[1] = (page_addr >> 16) & 0xFF;
|
||
cmd[2] = (page_addr >> 8) & 0xFF;
|
||
cmd[3] = page_addr & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
return gd5f_wait_busy(100);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从内部缓存读取数据
|
||
* 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047
|
||
* size - 读取字节数 size_t > 0
|
||
* 出口参数:p_buf - 数据输出缓冲区 uint8_t* 不为 NULL
|
||
* 返回值:0 - 成功
|
||
* 限定条件:必须先调用 gd5f_page_read 完成数据加载
|
||
* 函数说明:发 0Bh + 2字节列地址 +1字节 dummy 后读取数据 */
|
||
int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
|
||
size_t size) {
|
||
uint8_t cmd[4] = {0};
|
||
|
||
cmd[0] = GD5F_CMD_READ_FROM_CACHE;
|
||
cmd[1] = (column >> 8) & 0xFF;
|
||
cmd[2] = column & 0xFF;
|
||
cmd[3] = 0x00;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
|
||
if (size > GD5F_DMA_THRESHOLD) {
|
||
uint32_t tick_start = HAL_GetTick();
|
||
g_spi1_dma_done = 0U;
|
||
if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) {
|
||
GD5F_CS_HIGH();
|
||
return GD5F_ERROR;
|
||
}
|
||
while (g_spi1_dma_done == 0U) {
|
||
if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) {
|
||
HAL_SPI_DMAStop(&hspi1);
|
||
GD5F_CS_HIGH();
|
||
return GD5F_BUSY_TIMEOUT;
|
||
}
|
||
}
|
||
} else {
|
||
HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT);
|
||
}
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:页编程(将数据写入指定页)
|
||
* 入口参数:page_addr - 页地址 uint32_t
|
||
* column - 列地址 uint16_t 0 - 2047
|
||
* p_buf - 数据缓冲区 const uint8_t*
|
||
* size - 写入字节数 size_t > 0
|
||
* 返回值:0 - 成功,其它 - 错误 * 限定条件:目标区域已擦除
|
||
* 函数说明:1. 写使能 - 02h 加载数据 - 10h 执行编程 - 等待完成
|
||
* 2. 编程完成后检查 P_FAIL 位 */
|
||
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
|
||
const uint8_t *p_buf, size_t size) {
|
||
int ret;
|
||
gd5f_write_enable();
|
||
ret = gd5f_program_load(column, p_buf, size);
|
||
if (ret != GD5F_OK) return ret;
|
||
return gd5f_program_exec(page_addr);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:将数据加载到内部缓存(PROGRAM LOAD)
|
||
* 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047
|
||
* p_buf - 待写入数据缓冲区 const uint8_t* 不为 NULL
|
||
* size - 写入字节数 size_t > 0
|
||
* 返回值:0 - 成功,其它 - 错误
|
||
* 限定条件:SPI 已初始化,目标区域已擦除
|
||
* 函数说明:发 02h + 2字节列地址后发送数据;超过 DMA 阈值走 DMA
|
||
*/
|
||
int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) {
|
||
uint8_t cmd[3];
|
||
cmd[0] = GD5F_CMD_PROGRAM_LOAD;
|
||
cmd[1] = (uint8_t)(column >> 8);
|
||
cmd[2] = (uint8_t)(column);
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
|
||
|
||
if (size > GD5F_DMA_THRESHOLD) {
|
||
uint32_t tick_start = HAL_GetTick();
|
||
g_spi1_dma_done = 0U;
|
||
if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)p_buf, (uint16_t)size)
|
||
!= HAL_OK) {
|
||
GD5F_CS_HIGH();
|
||
return GD5F_ERROR;
|
||
}
|
||
while (g_spi1_dma_done == 0U) {
|
||
if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) {
|
||
HAL_SPI_DMAStop(&hspi1);
|
||
GD5F_CS_HIGH();
|
||
return GD5F_BUSY_TIMEOUT;
|
||
}
|
||
}
|
||
} else {
|
||
HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size, GD5F_SPI_TIMEOUT);
|
||
}
|
||
GD5F_CS_HIGH();
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:执行页编程(PROGRAM EXECUTE)
|
||
* 入口参数:page_addr - 目标页地址 uint32_t 0 ~ GD5F_TOTAL_BLOCKS*64-1
|
||
* 返回值:0 - 成功,其它 - 错误(GD5F_PROGRAM_FAIL 等)
|
||
* 限定条件:已先调用 gd5f_program_load 加载数据
|
||
* 函数说明:发 10h + 3字节页地址触发编程,等待完成后检查 P_FAIL 位
|
||
*/
|
||
int gd5f_program_exec(uint32_t page_addr) {
|
||
int ret;
|
||
uint8_t cmd[4];
|
||
uint8_t status;
|
||
|
||
cmd[0] = GD5F_CMD_PROGRAM_EXEC;
|
||
cmd[1] = (page_addr >> 16) & 0xFF;
|
||
cmd[2] = (page_addr >> 8) & 0xFF;
|
||
cmd[3] = page_addr & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
ret = gd5f_wait_busy(1000);
|
||
if (ret != GD5F_OK) return ret;
|
||
|
||
gd5f_read_status(&status);
|
||
if (status & GD5F_STATUS_P_FAIL) return GD5F_PROGRAM_FAIL;
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:检查上次读操作的 ECC 状态
|
||
* 入口参数:无
|
||
* 返回值:0 - 无 ECC 错误,GD5F_ECC_ERROR - 存在不可纠正 ECC 错误
|
||
* 限定条件:SPI 已初始化,ECC 已使能
|
||
* 函数说明:读取状态寄存器 ECCS[5:4] 位,0x02 表示不可纠正错误
|
||
*/
|
||
int gd5f_check_ecc(void) {
|
||
uint8_t status;
|
||
gd5f_read_status(&status);
|
||
uint8_t ecc = (status >> 4) & 0x03;
|
||
if (ecc == 0x02) return GD5F_ECC_ERROR;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:设置 Feature 寄存器
|
||
* 入口参数:addr - 寄存器地址 uint8_t
|
||
* data - 写入数据 uint8_t
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:先写使能,发 1Fh + 地址 + 数据,等待操作完成 */
|
||
static int gd5f_set_feature(uint8_t addr, uint8_t data) {
|
||
uint8_t cmd[3];
|
||
|
||
gd5f_write_enable();
|
||
|
||
cmd[0] = GD5F_CMD_SET_FEATURE;
|
||
cmd[1] = addr;
|
||
cmd[2] = data;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
gd5f_wait_busy(100);
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:块擦除(擦除128KB块)
|
||
* 入口参数:block_addr - 块编号 uint32_t 0 - 2047
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已调用
|
||
* 函数说明:1. 写使能 - D8h + 3字节页地址 - 等待完成
|
||
* 2. 擦除完成后检查 E_FAIL */
|
||
int gd5f_block_erase(uint32_t block_addr) {
|
||
int ret = GD5F_OK;
|
||
uint8_t cmd[4];
|
||
uint8_t status = 0;
|
||
uint32_t page_addr = block_addr * GD5F_PAGES_PER_BLOCK;
|
||
|
||
gd5f_write_enable();
|
||
|
||
cmd[0] = GD5F_CMD_BLOCK_ERASE;
|
||
cmd[1] = (page_addr >> 16) & 0xFF;
|
||
cmd[2] = (page_addr >> 8) & 0xFF;
|
||
cmd[3] = page_addr & 0xFF;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
ret = gd5f_wait_busy(5000);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
gd5f_read_status(&status);
|
||
if (status & GD5F_STATUS_E_FAIL) {
|
||
return GD5F_ERASE_FAIL;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:扫描所有块构建 BBT(在 ECC 使能前调用)
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化,ECC 尚未使能
|
||
* 函数说明:读取每个块首页(page 0)的 spare byte 0,
|
||
* 若不为 0xFF 则为出厂坏块(符合 datasheet §12.4)
|
||
*/
|
||
static int gd5f_bbt_scan(void) {
|
||
uint32_t block;
|
||
uint8_t spare[8];
|
||
uint32_t bad_count = 0;
|
||
|
||
memset(s_bbt, 0, GD5F_BBT_SIZE);
|
||
|
||
for (block = 0; block < GD5F_TOTAL_BLOCKS; block++) {
|
||
uint32_t page = block << 6;
|
||
|
||
gd5f_page_read(page);
|
||
gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1);
|
||
|
||
if (spare[0] != 0xFF) {
|
||
s_bbt[block >> 3] |= (1 << (block & 7));
|
||
bad_count++;
|
||
}
|
||
}
|
||
|
||
DBG_INFO("BBT scan: %lu bad blocks found", bad_count);
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:清空 RAM 中的 BBT
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:无
|
||
* 函数说明:仅清空内存位图,不影响闪存中的持久化副本
|
||
*/
|
||
void gd5f2gq5ue_bbt_clear(void) {
|
||
memset(s_bbt, 0, GD5F_BBT_SIZE);
|
||
}
|
||
|
||
/*
|
||
* 函数功能:重建 BBT(重新扫描出厂坏块并持久化,覆盖运行时注入的坏块)
|
||
* 入口参数:无
|
||
* 返回值:GD5F_OK - 成功,其他 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:临时关闭 ECC 重新扫描每块 page0 spare[0] 的出厂坏块标记,覆盖 RAM BBT,
|
||
* 再写回持久化 BBT 池(版本 +1,掉电安全),从而清除 TC-STO-03 等运行时
|
||
* 标记的坏块。调用后建议再执行 nand_ftl_format() 清空 dhara map。
|
||
*/
|
||
int gd5f2gq5ue_bbt_rebuild(void) {
|
||
DBG_INFO("Rebuilding BBT from factory scan...");
|
||
|
||
/* 1. 关闭 ECC 以读取原始出厂坏块标记(与 gd5f2gq5ue_init 的扫描前提一致) */
|
||
gd5f_set_feature(0xB0, 0x00);
|
||
gd5f_wait_busy(100);
|
||
|
||
/* 2. 重新扫描出厂坏块(覆盖 RAM BBT,剔除运行时注入的坏块) */
|
||
gd5f_bbt_scan();
|
||
|
||
/* 3. 重新使能 ECC */
|
||
gd5f_set_feature(0xB0, 0x10);
|
||
gd5f_wait_busy(100);
|
||
|
||
/* 4. 持久化重建后的 BBT(覆盖持久化区中的脏副本) */
|
||
if (gd5f_bbt_save() != GD5F_OK) {
|
||
DBG_ERROR("BBT rebuild: persist failed");
|
||
return GD5F_ERROR;
|
||
}
|
||
|
||
DBG_INFO("BBT rebuilt and persisted");
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:通过调试串口打印当前 BBT 统计与坏块列表
|
||
* 入口参数:无
|
||
* 返回值:无
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:最多打印前 32 个坏块块号与偏移
|
||
*/
|
||
void gd5f2gq5ue_print_bbt(void) {
|
||
uint32_t bad_count = 0;
|
||
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS; b++) {
|
||
if ((s_bbt[b >> 3] >> (b & 7)) & 1) bad_count++;
|
||
}
|
||
DBG_INFO("BBT: %lu/%lu blocks bad", bad_count, GD5F_TOTAL_BLOCKS);
|
||
if (bad_count > 0) {
|
||
uint32_t shown = 0;
|
||
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS && shown < 32; b++) {
|
||
if ((s_bbt[b >> 3] >> (b & 7)) & 1) {
|
||
DBG_INFO(" block %lu (offset 0x%08lX)", b, b * GD5F_BLOCK_SIZE);
|
||
shown++;
|
||
}
|
||
}
|
||
if (bad_count > 32) DBG_INFO(" ... and %lu more", bad_count - 32);
|
||
}
|
||
}
|
||
|
||
/* ======================== 公共函数定义 ======================== */
|
||
|
||
/*
|
||
* 函数功能:初始化 GD5F2GQ5UE
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功,其他 - 错误码
|
||
* 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成
|
||
* 函数说明:1. 发送复位命令并等待完成
|
||
* 2. 读取芯片 ID 并校验
|
||
* 3. 扫描出厂坏块构建 BBT(ECC 使能前,读每块首页 spare[0])
|
||
* 4. 使能内部 ECC (B0h bit4)
|
||
* 5. 解除所有块保护 (A0h = 0x00)
|
||
* 6. 定位保留块池并加载持久化 BBT(ECC 使能后)
|
||
*/
|
||
int gd5f2gq5ue_init(void) {
|
||
int ret = GD5F_OK;
|
||
uint8_t mid = 0;
|
||
uint8_t did = 0;
|
||
|
||
GD5F_CS_HIGH();
|
||
GD5F_WP_HIGH();
|
||
GD5F_HOLD_HIGH();
|
||
|
||
HAL_Delay(10);
|
||
|
||
DBG_INFO("Resetting NAND...");
|
||
ret = gd5f2gq5ue_reset();
|
||
if (ret != GD5F_OK) {
|
||
DBG_ERROR("Reset failed: %d", ret);
|
||
return ret;
|
||
}
|
||
|
||
HAL_Delay(5);
|
||
|
||
DBG_INFO("Reading NAND ID...");
|
||
ret = gd5f2gq5ue_read_id(&mid, &did);
|
||
if (ret != GD5F_OK) {
|
||
DBG_ERROR("Read ID failed: %d", ret);
|
||
return ret;
|
||
}
|
||
|
||
DBG_INFO("NAND ID: MID=0x%02X, DID=0x%02X", mid, did);
|
||
|
||
if (mid != GD5F_MANUFACTURER_ID || did != GD5F_DEVICE_ID) {
|
||
DBG_ERROR("ID mismatch: expected MID=0x%02X DID=0x%02X, got MID=0x%02X DID=0x%02X",
|
||
GD5F_MANUFACTURER_ID, GD5F_DEVICE_ID, mid, did);
|
||
return GD5F_ID_MISMATCH;
|
||
}
|
||
|
||
DBG_INFO("Scanning bad blocks...");
|
||
gd5f_bbt_scan();
|
||
|
||
DBG_INFO("Enabling ECC...");
|
||
gd5f_set_feature(0xB0, 0x10);
|
||
gd5f_wait_busy(100);
|
||
|
||
DBG_INFO("Unlocking block protection...");
|
||
gd5f_set_feature(GD5F_REG_PROTECT, 0x00);
|
||
gd5f_wait_busy(100);
|
||
|
||
DBG_INFO("Locating BBT pool...");
|
||
gd5f_private_bbt_locate_pool();
|
||
|
||
DBG_INFO("Loading persisted BBT...");
|
||
gd5f_bbt_load();
|
||
|
||
DBG_INFO("NAND init OK");
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:读取芯片 ID(MID + DID)
|
||
* 出口参数:p_mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
|
||
* p_did - 设备 ID 输出指针 uint8_t* 不为 NULL
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发 9Fh 命令后接 1 字节 dummy + MID + DID
|
||
*/
|
||
int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
|
||
uint8_t cmd = GD5F_CMD_READ_ID;
|
||
uint8_t id_buf[3] = {0};
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
HAL_SPI_Receive(&hspi1, id_buf, 3, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
*p_mid = id_buf[1];
|
||
*p_did = id_buf[2];
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从 NAND 读取数据(支持跨页)
|
||
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容量-1
|
||
* size - 读取字节数 size_t > 0
|
||
* 出口参数:p_buf - 数据缓冲区 uint8_t* 不为 NULL
|
||
* 返回值:0 - 成功,其它 - 错误
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:自动处理跨页读取;offset+size 越界返回错误
|
||
*/
|
||
int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
|
||
int ret = GD5F_OK;
|
||
|
||
if (offset < 0 || (uint32_t)offset + (uint32_t)size > GD5F_TOTAL_SIZE) {
|
||
return GD5F_ERROR;
|
||
}
|
||
|
||
while (size > 0) {
|
||
uint32_t page_addr = offset / GD5F_PAGE_SIZE;
|
||
uint16_t column = offset % GD5F_PAGE_SIZE;
|
||
size_t bytes = GD5F_PAGE_SIZE - column;
|
||
|
||
if (bytes > size) {
|
||
bytes = size;
|
||
}
|
||
|
||
ret = gd5f_page_read(page_addr);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
ret = gd5f_read_from_cache(column, p_buf, bytes);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
offset += bytes;
|
||
p_buf += bytes;
|
||
size -= bytes;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:向 NAND 写入数据(支持跨页)
|
||
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容量-1
|
||
* p_buf - 数据缓冲区 const uint8_t* 不为 NULL
|
||
* size - 写入字节数 size_t > 0
|
||
* 返回值:0 - 成功,其它 - 错误
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦除
|
||
* 函数说明:自动处理跨页写入;offset+size 越界返回错误
|
||
*/
|
||
int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
|
||
int ret = GD5F_OK;
|
||
|
||
if (offset < 0 || (uint32_t)offset + (uint32_t)size > GD5F_TOTAL_SIZE) {
|
||
return GD5F_ERROR;
|
||
}
|
||
|
||
while (size > 0) {
|
||
uint32_t page_addr = offset / GD5F_PAGE_SIZE;
|
||
uint16_t column = offset % GD5F_PAGE_SIZE;
|
||
size_t bytes = GD5F_PAGE_SIZE - column;
|
||
|
||
if (bytes > size) {
|
||
bytes = size;
|
||
}
|
||
|
||
ret = gd5f_page_program(page_addr, column, p_buf, bytes);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
offset += bytes;
|
||
p_buf += bytes;
|
||
size -= bytes;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:擦除块(按块擦除,最小单位 128KB)
|
||
* 入口参数:offset - 起始字节偏移 long 必须 GD5F_BLOCK_SIZE 对齐
|
||
* size - 擦除字节数 size_t 必须 GD5F_BLOCK_SIZE 整数倍
|
||
* 返回值:0 - 成功,其它 - 错误码
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:循环擦除 [offset, offset+size) 覆盖的每个块,偏移/长度不对齐返回错误
|
||
*/
|
||
int gd5f2gq5ue_erase(long offset, size_t size) {
|
||
int ret = GD5F_OK;
|
||
|
||
if (offset % GD5F_BLOCK_SIZE != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
if (size % GD5F_BLOCK_SIZE != 0) {
|
||
return GD5F_ERROR;
|
||
}
|
||
|
||
while (size > 0) {
|
||
uint32_t block_addr = offset / GD5F_BLOCK_SIZE;
|
||
|
||
ret = gd5f_block_erase(block_addr);
|
||
if (ret != GD5F_OK) {
|
||
return ret;
|
||
}
|
||
|
||
offset += GD5F_BLOCK_SIZE;
|
||
size -= GD5F_BLOCK_SIZE;
|
||
}
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:复位芯片
|
||
* 返回值:0 - 成功
|
||
* 限定条件:SPI 已初始化
|
||
* 函数说明:发 FFh 复位命令后等待 5ms
|
||
*/
|
||
int gd5f2gq5ue_reset(void) {
|
||
uint8_t cmd = GD5F_CMD_RESET;
|
||
|
||
GD5F_CS_LOW();
|
||
HAL_SPI_Transmit(&hspi1, &cmd, 1, GD5F_SPI_TIMEOUT);
|
||
GD5F_CS_HIGH();
|
||
|
||
HAL_Delay(5);
|
||
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:查询块是否坏块
|
||
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
|
||
* 返回值:0 - 好块,1 - 坏块
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:通过 BBT 查询
|
||
*/
|
||
int gd5f2gq5ue_is_block_bad(uint32_t block) {
|
||
if (block >= GD5F_TOTAL_BLOCKS) {
|
||
return 1;
|
||
}
|
||
return (s_bbt[block >> 3] >> (block & 7)) & 1;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:标记块为坏块
|
||
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
|
||
* 返回值:无
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:更新 BBT
|
||
*/
|
||
void gd5f2gq5ue_mark_block_bad(uint32_t block) {
|
||
if (block >= GD5F_TOTAL_BLOCKS) {
|
||
return;
|
||
}
|
||
uint32_t byte = block >> 3;
|
||
uint8_t bit = (uint8_t)(1U << (block & 7));
|
||
if (s_bbt[byte] & bit) {
|
||
return; /* 已是坏块,幂等,无需重复持久化 */
|
||
}
|
||
s_bbt[byte] |= bit;
|
||
gd5f_bbt_save(); /* 持久化到保留块池(带版本号 + CRC) */
|
||
}
|
||
|
||
/* ======================== BBT 持久化实现 ======================== */
|
||
|
||
/*
|
||
* 函数功能:从 NAND 顶部向下挑选出厂好块作为 BBT 保留池
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:gd5f_bbt_scan() 已完成(s_bbt 已就绪)
|
||
* 函数说明:保留池不交给 dhara 管理;最低池块号以上的块全部排除,
|
||
* 并将可用块数 s_usable_blocks 设为最低池块号
|
||
*/
|
||
static int gd5f_private_bbt_locate_pool(void) {
|
||
uint32_t cnt = 0;
|
||
uint32_t b = GD5F_TOTAL_BLOCKS;
|
||
|
||
while (b-- > 0 && cnt < GD5F_BBT_POOL_COUNT) {
|
||
if (((s_bbt[b >> 3] >> (b & 7)) & 1) == 0) {
|
||
s_bbt_pool_blocks[cnt++] = b;
|
||
}
|
||
}
|
||
s_bbt_pool_count = cnt;
|
||
if (cnt > 0) {
|
||
s_usable_blocks = s_bbt_pool_blocks[cnt - 1]; /* 最低池块 = dhara 可见上限 */
|
||
}
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:CRC32 计算(IEEE 802.3,多项式 0xEDB88320)
|
||
* 入口参数:p_buf - 数据缓冲区 const uint8_t* 不为 NULL
|
||
* len - 数据长度 uint32_t > 0
|
||
* 返回值:32 位 CRC 校验值
|
||
* 限定条件:无
|
||
* 函数说明:采用位级(bit-by-bit)实现,初始值 0xFFFFFFFF
|
||
*/
|
||
static uint32_t gd5f_private_crc32(const uint8_t *p_buf, uint32_t len) {
|
||
uint32_t crc = 0xFFFFFFFFU;
|
||
for (uint32_t i = 0; i < len; i++) {
|
||
crc ^= p_buf[i];
|
||
for (int j = 0; j < 8; j++) {
|
||
uint32_t mask = (crc & 1U) ? 0xEDB88320U : 0U;
|
||
crc = (crc >> 1) ^ mask;
|
||
}
|
||
}
|
||
return ~crc;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:在保留池中找出版本号最高的有效 BBT 副本
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:gd5f_private_bbt_locate_pool() 已成功执行
|
||
* 函数说明:校验 magic / len / CRC;结果缓存到 s_bbt_best / s_bbt_version,
|
||
* 并设置下一轮转写入槽 s_bbt_write_slot
|
||
*/
|
||
static int gd5f_private_bbt_find_best(void) {
|
||
uint32_t best_ver = 0;
|
||
int best_slot = -1;
|
||
|
||
s_bbt_slot_dead = 0;
|
||
for (uint32_t i = 0; i < s_bbt_pool_count; i++) {
|
||
uint32_t block = s_bbt_pool_blocks[i];
|
||
uint32_t page = block * GD5F_PAGES_PER_BLOCK;
|
||
uint32_t ver, len, crc;
|
||
|
||
gd5f_page_read(page);
|
||
gd5f_read_from_cache(0, s_bbt_page_buf, GD5F_BBT_HDR_SIZE + GD5F_BBT_SIZE);
|
||
if (memcmp(s_bbt_page_buf, s_bbt_magic, 4) != 0) {
|
||
continue;
|
||
}
|
||
memcpy(&ver, s_bbt_page_buf + 4, 4);
|
||
memcpy(&len, s_bbt_page_buf + 8, 4);
|
||
memcpy(&crc, s_bbt_page_buf + 12, 4);
|
||
if (len != GD5F_BBT_SIZE) {
|
||
continue;
|
||
}
|
||
if (gd5f_private_crc32(s_bbt_page_buf + GD5F_BBT_HDR_SIZE, GD5F_BBT_SIZE) != crc) {
|
||
continue;
|
||
}
|
||
if (best_slot < 0 || (int32_t)ver > (int32_t)best_ver) {
|
||
best_ver = ver;
|
||
best_slot = (int)i;
|
||
memcpy(s_bbt_best, s_bbt_page_buf + GD5F_BBT_HDR_SIZE, GD5F_BBT_SIZE);
|
||
}
|
||
}
|
||
if (best_slot >= 0) {
|
||
s_bbt_version = best_ver;
|
||
s_bbt_write_slot = (uint32_t)(best_slot + 1) % s_bbt_pool_count;
|
||
} else {
|
||
s_bbt_write_slot = 0;
|
||
}
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:加载持久化 BBT(OR 进 RAM BBT)
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:gd5f_private_bbt_locate_pool() 已成功执行
|
||
* 函数说明:工厂扫描已填好块,持久化副本补充运行期坏块
|
||
*/
|
||
static int gd5f_bbt_load(void) {
|
||
gd5f_private_bbt_find_best();
|
||
if (s_bbt_pool_count == 0) {
|
||
return GD5F_OK;
|
||
}
|
||
for (uint32_t i = 0; i < GD5F_BBT_SIZE; i++) {
|
||
s_bbt[i] |= s_bbt_best[i];
|
||
}
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:将当前 RAM BBT 持久化到保留池(轮转写入,掉电安全)
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功,GD5F_ERROR - 写入失败
|
||
* 限定条件:gd5f_private_bbt_locate_pool() 已成功执行
|
||
* 函数说明:版本号自增;先擦除目标块再编程;损坏块标记后跳到下一槽
|
||
*/
|
||
static int gd5f_bbt_save(void) {
|
||
if (s_bbt_pool_count == 0) {
|
||
return GD5F_OK;
|
||
}
|
||
uint32_t slot = s_bbt_write_slot;
|
||
|
||
while (s_bbt_slot_dead & (1U << slot)) {
|
||
slot = (slot + 1) % s_bbt_pool_count;
|
||
if (slot == s_bbt_write_slot) {
|
||
break; /* 所有池块均损坏 */
|
||
}
|
||
}
|
||
|
||
uint32_t block = s_bbt_pool_blocks[slot];
|
||
uint32_t page = block * GD5F_PAGES_PER_BLOCK;
|
||
uint32_t ver = s_bbt_version + 1;
|
||
uint32_t len = GD5F_BBT_SIZE;
|
||
uint32_t crc = gd5f_private_crc32(s_bbt, GD5F_BBT_SIZE);
|
||
|
||
if (gd5f_block_erase(block) != GD5F_OK) {
|
||
s_bbt_slot_dead |= (1U << slot);
|
||
s_bbt_write_slot = (slot + 1) % s_bbt_pool_count;
|
||
return GD5F_ERROR;
|
||
}
|
||
|
||
memcpy(s_bbt_page_buf, s_bbt_magic, 4);
|
||
memcpy(s_bbt_page_buf + 4, &ver, 4);
|
||
memcpy(s_bbt_page_buf + 8, &len, 4);
|
||
memcpy(s_bbt_page_buf + 12, &crc, 4);
|
||
memcpy(s_bbt_page_buf + GD5F_BBT_HDR_SIZE, s_bbt, GD5F_BBT_SIZE);
|
||
|
||
gd5f_write_enable();
|
||
gd5f_program_load(0, s_bbt_page_buf, GD5F_BBT_HDR_SIZE + GD5F_BBT_SIZE);
|
||
gd5f_program_exec(page);
|
||
gd5f_wait_busy(100);
|
||
|
||
s_bbt_version = ver;
|
||
s_bbt_write_slot = (slot + 1) % s_bbt_pool_count;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:获取 dhara 可用块数(= 最低保留池块号)
|
||
* 入口参数:无
|
||
* 返回值:dhara 可见的块数量(= 保留块池起始块号)
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:保留块池用于存储持久化 BBT,不交给 dhara 管理
|
||
*/
|
||
uint32_t gd5f_get_usable_blocks(void) {
|
||
return s_usable_blocks;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从闪存读取当前生效的 BBT 位图(测试/调试用)
|
||
* 入口参数:无
|
||
* 出口参数:p_bbt - 位图输出缓冲 uint8_t* 不为 NULL,长度 >= GD5F_BBT_SIZE
|
||
* p_version - 版本号输出 uint32_t* 不为 NULL
|
||
* 返回值:0 - 成功,GD5F_ERROR - 参数非法
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:返回闪存中版本号最高的有效 BBT 拷贝(供测试验证持久化)
|
||
*/
|
||
int32_t gd5f_bbt_dump_flash(uint8_t *p_bbt, uint32_t *p_version) {
|
||
if (p_bbt == NULL || p_version == NULL) {
|
||
return GD5F_ERROR;
|
||
}
|
||
gd5f_private_bbt_find_best();
|
||
memcpy(p_bbt, s_bbt_best, GD5F_BBT_SIZE);
|
||
*p_version = s_bbt_version;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:擦除保留块池(撤销持久化 BBT,回到仅工厂扫描态)
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:仅供测试/出厂重置;调用方需自行还原 RAM BBT
|
||
*/
|
||
int32_t gd5f_bbt_wipe_pool(void) {
|
||
if (s_bbt_pool_count == 0) {
|
||
return GD5F_OK;
|
||
}
|
||
for (uint32_t i = 0; i < s_bbt_pool_count; i++) {
|
||
gd5f_block_erase(s_bbt_pool_blocks[i]);
|
||
}
|
||
s_bbt_write_slot = 0;
|
||
s_bbt_version = 0;
|
||
return GD5F_OK;
|
||
}
|
||
|
||
/*
|
||
* 函数功能:从保留块池重新加载持久化 BBT 到 RAM(测试/调试用)
|
||
* 入口参数:无
|
||
* 返回值:0 - 成功
|
||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||
* 函数说明:将闪存中版本号最高的有效副本 OR 进 RAM BBT
|
||
*/
|
||
int32_t gd5f_bbt_reload(void) {
|
||
return gd5f_bbt_load();
|
||
}
|
||
|