Files
CCU621M/BSP/externalflash/exflash_spi4_gd25qxx.c
T

551 lines
17 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "exflash_spi4_gd25qxx.h"
#include "gd32h7xx_dma.h"
#include <stddef.h>
/* NOR Flash 标准指令 */
#define EXFLASH_CMD_WRITE (0x02u) /* Page Program */
#define EXFLASH_CMD_WREN (0x06u) /* Write Enable */
#define EXFLASH_CMD_READ (0x03u) /* Read Data */
#define EXFLASH_CMD_RDSR (0x05u) /* Read Status Register */
#define EXFLASH_CMD_RDID (0x9Fu) /* Read JEDEC ID */
#define EXFLASH_CMD_SE (0x20u) /* 4KB Sector Erase */
#define EXFLASH_CMD_BE (0xC7u) /* Bulk Erase */
#define EXFLASH_CMD_EN4B (0xB7u) /* Enter 4-byte address mode */
/* 状态寄存器位定义 */
#define EXFLASH_SR_WIP_MASK (0x01u) /* WIP=1 表示写/擦进行中 */
/* 读操作发送的占位字节 */
#define EXFLASH_DUMMY_BYTE (0xA5u)
#define EXFLASH_DMA_PERIPH (DMA0)
#define EXFLASH_DMA_RX_CH (DMA_CH1)
#define EXFLASH_DMA_TX_CH (DMA_CH2)
#define EXFLASH_DMA_WAIT_MAX_LOOP (0x00FFFFFFu)
/* Cortex-M7 D-Cache line size = 32 bytes */
#define EXFLASH_DCACHE_LINE_SIZE (32u)
/* DMA 中断置位标志:1=该通道传输完成 */
static volatile uint8_t s_spi4_dma_rx_done = 0u;
static volatile uint8_t s_spi4_dma_tx_done = 0u;
/* 读阶段 TX 使用的固定哑字节;DMA 配置为 memory_inc disable */
static uint8_t s_spi4_dma_dummy_tx = EXFLASH_DUMMY_BYTE;
/* 写阶段 RX 丢弃字节;DMA 配置为 memory_inc disable */
static uint8_t s_spi4_dma_dummy_rx = 0u;
static uint32_t cache_addr_down_align(uint32_t addr, uint32_t align)
{
return addr & ~(align - 1u);
}
static uint32_t cache_len_up_align(uint32_t addr, uint32_t len, uint32_t align)
{
uint32_t end = addr + len;
uint32_t end_aligned = (end + (align - 1u)) & ~(align - 1u);
return end_aligned - cache_addr_down_align(addr, align);
}
static void dcache_clean_by_addr(uint32_t addr, uint32_t len)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t a = cache_addr_down_align(addr, EXFLASH_DCACHE_LINE_SIZE);
uint32_t l = cache_len_up_align(addr, len, EXFLASH_DCACHE_LINE_SIZE);
SCB_CleanDCache_by_Addr((uint32_t *)a, (int32_t)l);
#else
(void)addr;
(void)len;
#endif
}
static void dcache_invalidate_by_addr(uint32_t addr, uint32_t len)
{
#if defined (__DCACHE_PRESENT) && (__DCACHE_PRESENT == 1U)
uint32_t a = cache_addr_down_align(addr, EXFLASH_DCACHE_LINE_SIZE);
uint32_t l = cache_len_up_align(addr, len, EXFLASH_DCACHE_LINE_SIZE);
SCB_InvalidateDCache_by_Addr((uint32_t *)a, (int32_t)l);
#else
(void)addr;
(void)len;
#endif
}
/**
* @brief 发送 32bit 地址(A31~A0)。
* @param addr 线性地址。
*/
static void spi_flash_send_addr(uint32_t addr)
{
spi_flash_send_byte((uint8_t)((addr >> 24) & 0xFFu));
spi_flash_send_byte((uint8_t)((addr >> 16) & 0xFFu));
spi_flash_send_byte((uint8_t)((addr >> 8) & 0xFFu));
spi_flash_send_byte((uint8_t)(addr & 0xFFu));
}
/**
* @brief 进入 4-byte 地址模式(支持 32MB 地址空间访问)。
*/
static void spi_flash_enter_4byte_addr_mode(void)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_EN4B);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待 DMA 收发双通道完成。
* @return 1 成功,0 超时。
*/
static uint8_t spi4_dma_wait_done(void)
{
uint32_t timeout = EXFLASH_DMA_WAIT_MAX_LOOP;
while (timeout-- > 0u) {
if ((s_spi4_dma_rx_done == 1u) && (s_spi4_dma_tx_done == 1u)) {
return 1u;
}
}
return 0u;
}
/**
* @brief 启动 SPI4 DMA 全双工传输(同时配置 RX/TX)。
* @param tx_buf TX 源地址。
* @param tx_inc TX 地址是否自增。
* @param rx_buf RX 目标地址。
* @param rx_inc RX 地址是否自增。
* @param length 传输字节数。
* @return 1 启动并完成成功,0 超时/失败。
*/
static uint8_t spi4_dma_transfer(uint8_t *tx_buf,
uint32_t tx_inc,
uint8_t *rx_buf,
uint32_t rx_inc,
uint16_t length)
{
dma_single_data_parameter_struct dma_init_struct;
if ((tx_buf == NULL) || (rx_buf == NULL) || (length == 0u)) {
return 0u;
}
s_spi4_dma_rx_done = 0u;
s_spi4_dma_tx_done = 0u;
dma_deinit(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_deinit(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
dma_single_data_para_struct_init(&dma_init_struct);
/* DMA 读 TX 前清 cacheDMA 写 RX 后需要 invalidate cache */
if (tx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_clean_by_addr((uint32_t)tx_buf, (uint32_t)length);
}
if (rx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_invalidate_by_addr((uint32_t)rx_buf, (uint32_t)length);
}
/* RX: SPI4_RDATA -> memory */
dma_init_struct.request = DMA_REQUEST_SPI4_RX;
dma_init_struct.periph_addr = (uint32_t)&SPI_RDATA(SPI4);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)rx_buf;
dma_init_struct.memory_inc = rx_inc;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_struct.direction = DMA_PERIPH_TO_MEMORY;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init_struct.number = length;
dma_single_data_mode_init(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, &dma_init_struct);
dma_interrupt_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FTF);
/* TX: memory -> SPI4_TDATA */
dma_init_struct.request = DMA_REQUEST_SPI4_TX;
dma_init_struct.periph_addr = (uint32_t)&SPI_TDATA(SPI4);
dma_init_struct.periph_inc = DMA_MEMORY_INCREASE_DISABLE;
dma_init_struct.memory0_addr = (uint32_t)tx_buf;
dma_init_struct.memory_inc = tx_inc;
dma_init_struct.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_init_struct.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_init_struct.direction = DMA_MEMORY_TO_PERIPH;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init_struct.number = length;
dma_single_data_mode_init(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, &dma_init_struct);
dma_interrupt_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FTF);
spi_dma_enable(SPI4, SPI_DMA_RECEIVE);
spi_dma_enable(SPI4, SPI_DMA_TRANSMIT);
/* 显式启动一次主机传输,保证时钟输出 */
spi_master_transfer_start(SPI4, SPI_TRANS_START);
dma_channel_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_enable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
if (spi4_dma_wait_done() == 0u) {
spi_dma_disable(SPI4, SPI_DMA_RECEIVE);
spi_dma_disable(SPI4, SPI_DMA_TRANSMIT);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
return 0u;
}
spi_dma_disable(SPI4, SPI_DMA_RECEIVE);
spi_dma_disable(SPI4, SPI_DMA_TRANSMIT);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH);
dma_channel_disable(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH);
/* DMA 写完 RX 后,CPU 读取前再次 invalidate,避免 cache 仍是旧数据 */
if (rx_inc == DMA_MEMORY_INCREASE_ENABLE) {
dcache_invalidate_by_addr((uint32_t)rx_buf, (uint32_t)length);
}
return 1u;
}
/**
* @brief 初始化 SPI4 与外部 Flash 片选 GPIO。
* @details
* - CS: PJ9(软件控制)
* - SCK: PK0MOSI: PJ10MISO: PJ11
* - SPI Mode08bitMSB first
*/
void spi_flash_init(void)
{
spi_parameter_struct spi_init_struct;
/* 1) 时钟使能 */
rcu_periph_clock_enable(RCU_GPIOJ);
rcu_periph_clock_enable(RCU_GPIOK);
rcu_periph_clock_enable(RCU_SPI4);
rcu_periph_clock_enable(RCU_DMA0);
rcu_periph_clock_enable(RCU_DMAMUX);
rcu_spi_clock_config(IDX_SPI4, RCU_SPISRC_APB2);
/* DMA 中断配置放在 DMA 时钟就绪后,避免早期访问 DMA 寄存器引发异常 */
nvic_irq_enable(DMA0_Channel1_IRQn, 4U, 0U);
nvic_irq_enable(DMA0_Channel2_IRQn, 4U, 0U);
/* 2) 片选脚初始化:默认拉高(未选中) */
gpio_mode_set(GPIOJ, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_9);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_9);
SPI_FLASH_CS_HIGH();
/* 3) SPI4 复用脚初始化 */
gpio_af_set(GPIOK, GPIO_AF_5, GPIO_PIN_0); /* SCK */
gpio_af_set(GPIOJ, GPIO_AF_5, GPIO_PIN_10 | GPIO_PIN_11); /* MOSI/MISO */
gpio_mode_set(GPIOK, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_0);
gpio_output_options_set(GPIOK, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_0);
gpio_mode_set(GPIOJ, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_11);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_11);
gpio_mode_set(GPIOJ, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_PIN_10);
gpio_output_options_set(GPIOJ, GPIO_OTYPE_PP, GPIO_OSPEED_60MHZ, GPIO_PIN_10);
/* 4) SPI4 参数初始化 */
spi_i2s_deinit(SPI4);
spi_struct_para_init(&spi_init_struct);
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; /* 全双工 */
spi_init_struct.device_mode = SPI_MASTER; /* 主机 */
spi_init_struct.data_size = SPI_DATASIZE_8BIT; /* 8bit 帧 */
spi_init_struct.clock_polarity_phase = SPI_CK_PL_LOW_PH_1EDGE; /* Mode 0 */
spi_init_struct.nss = SPI_NSS_SOFT; /* 软件 NSS */
spi_init_struct.prescale = SPI_PSC_32; /* 分频,保证稳定 */
spi_init_struct.endian = SPI_ENDIAN_MSB; /* 高位先发 */
spi_init(SPI4, &spi_init_struct);
/* 5) 使能 SPI 外设 */
spi_byte_access_enable(SPI4);
spi_nss_output_enable(SPI4);
spi_enable(SPI4);
/* 清理可能残留的 DMA 中断完成标志,防止首次传输误触发 */
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF);
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF);
/* 启用 4-byte 地址模式,支持访问 0x00000000~0x01FFFFFF32MB */
spi_flash_enter_4byte_addr_mode();
}
/**
* @brief 擦除指定地址所在扇区(4KB)。
* @param sector_addr 32bit 扇区地址(建议 4KB 对齐)。
*/
void spi_flash_sector_erase(uint32_t sector_addr)
{
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_SE);
spi_flash_send_addr(sector_addr);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 全片擦除。
* @warning 全片擦除耗时很长,仅在必要时调用。
*/
void spi_flash_bulk_erase(void)
{
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_BE);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 页内编程(不跨页)。
* @param pbuffer 写入源缓冲区。
* @param write_addr 写起始地址。
* @param num_byte_to_write 页内写入字节数(建议 <=256 且不跨页)。
*/
void spi_flash_page_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
if ((pbuffer == NULL) || (num_byte_to_write == 0u)) {
return;
}
spi_flash_write_enable();
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_WRITE);
spi_flash_send_addr(write_addr);
(void)spi4_dma_transfer(pbuffer,
DMA_MEMORY_INCREASE_ENABLE,
&s_spi4_dma_dummy_rx,
DMA_MEMORY_INCREASE_DISABLE,
num_byte_to_write);
SPI_FLASH_CS_HIGH();
spi_flash_wait_for_write_end();
}
/**
* @brief 连续写(自动按页拆分)。
* @param pbuffer 写入源缓冲区。
* @param write_addr 写起始地址。
* @param num_byte_to_write 写入总字节数。
* @details
* 原始实现将分页计数变量定义为 uint8_t,在数据量 >255 页时会溢出;
* 此处统一使用 uint32_t,避免分页计算错误。
*/
void spi_flash_buffer_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t num_byte_to_write)
{
uint32_t addr_offset;
uint32_t first_page_space;
uint32_t remain;
uint32_t page_bytes;
if ((pbuffer == NULL) || (num_byte_to_write == 0u)) {
return;
}
addr_offset = write_addr % SPI_FLASH_PAGE_SIZE;
first_page_space = SPI_FLASH_PAGE_SIZE - addr_offset;
remain = num_byte_to_write;
/* 若首地址非页对齐,先补齐当前页 */
if ((addr_offset != 0u) && (remain != 0u)) {
page_bytes = (remain < first_page_space) ? remain : first_page_space;
spi_flash_page_write(pbuffer, write_addr, (uint16_t)page_bytes);
write_addr += page_bytes;
pbuffer += page_bytes;
remain -= page_bytes;
}
/* 整页写 */
while (remain >= SPI_FLASH_PAGE_SIZE) {
spi_flash_page_write(pbuffer, write_addr, SPI_FLASH_PAGE_SIZE);
write_addr += SPI_FLASH_PAGE_SIZE;
pbuffer += SPI_FLASH_PAGE_SIZE;
remain -= SPI_FLASH_PAGE_SIZE;
}
/* 尾包写 */
if (remain > 0u) {
spi_flash_page_write(pbuffer, write_addr, (uint16_t)remain);
}
}
/**
* @brief 连续读取数据。
* @param pbuffer 读出目标缓冲区。
* @param read_addr 读取起始地址。
* @param num_byte_to_read 读取字节数。
*/
void spi_flash_buffer_read(uint8_t *pbuffer, uint32_t read_addr, uint16_t num_byte_to_read)
{
if ((pbuffer == NULL) || (num_byte_to_read == 0u)) {
return;
}
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_READ);
spi_flash_send_addr(read_addr);
(void)spi4_dma_transfer(&s_spi4_dma_dummy_tx,
DMA_MEMORY_INCREASE_DISABLE,
pbuffer,
DMA_MEMORY_INCREASE_ENABLE,
num_byte_to_read);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 读取 JEDEC ID(三字节)。
* @return 24bit IDManufacturer[23:16] | MemoryType[15:8] | Capacity[7:0]。
*/
uint32_t spi_flash_read_id(void)
{
uint32_t id0;
uint32_t id1;
uint32_t id2;
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_RDID);
id0 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
id1 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
id2 = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
SPI_FLASH_CS_HIGH();
return (id0 << 16) | (id1 << 8) | id2;
}
/**
* @brief 发起连续读序列(保持片选有效)。
* @param read_addr 读取起始地址。
*/
void spi_flash_start_read_sequence(uint32_t read_addr)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_READ);
spi_flash_send_addr(read_addr);
}
/**
* @brief 在连续读序列中读取 1 字节。
* @return 读到的数据字节。
*/
uint8_t spi_flash_read_byte(void)
{
return spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
}
/**
* @brief SPI4 全双工收发一个字节。
* @param byte 发送字节。
* @return 同步接收字节。
*/
uint8_t spi_flash_send_byte(uint8_t byte)
{
uint32_t timeout = 0x00FFFFFFu;
/* GD32H7 SPI 主机在部分场景需要显式启动传输,否则时钟不输出,RP 永远不置位 */
spi_master_transfer_start(SPI4, SPI_TRANS_START);
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TP)) && (timeout-- > 0u)) {
/* 等待发送缓冲区可写 */
}
if (timeout == 0u) {
return 0xFFu;
}
spi_i2s_data_transmit(SPI4, byte);
timeout = 0x00FFFFFFu;
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RP)) && (timeout-- > 0u)) {
/* 等待接收缓冲区有数据 */
}
if (timeout == 0u) {
return 0xFFu;
}
return (uint8_t)spi_i2s_data_receive(SPI4);
}
/**
* @brief SPI4 全双工收发半字。
* @param half_word 发送半字。
* @return 同步接收半字。
*/
uint16_t spi_flash_send_halfword(uint16_t half_word)
{
uint32_t timeout = 0x00FFFFFFu;
spi_master_transfer_start(SPI4, SPI_TRANS_START);
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_TP)) && (timeout-- > 0u)) {
/* 等待发送缓冲区可写 */
}
if (timeout == 0u) {
return 0xFFFFu;
}
spi_i2s_data_transmit(SPI4, half_word);
timeout = 0x00FFFFFFu;
while ((RESET == spi_i2s_flag_get(SPI4, SPI_FLAG_RP)) && (timeout-- > 0u)) {
/* 等待接收缓冲区有数据 */
}
if (timeout == 0u) {
return 0xFFFFu;
}
return (uint16_t)spi_i2s_data_receive(SPI4);
}
/**
* @brief 发送 Write Enable 指令(WREN)。
*/
void spi_flash_write_enable(void)
{
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_WREN);
SPI_FLASH_CS_HIGH();
}
/**
* @brief 等待写/擦操作结束(轮询 SR.WIP)。
*/
void spi_flash_wait_for_write_end(void)
{
uint8_t flash_status;
SPI_FLASH_CS_LOW();
spi_flash_send_byte(EXFLASH_CMD_RDSR);
do {
flash_status = spi_flash_send_byte(EXFLASH_DUMMY_BYTE);
} while ((flash_status & EXFLASH_SR_WIP_MASK) != 0u);
SPI_FLASH_CS_HIGH();
}
/**
* @brief DMA0 Channel1 IRQSPI4 RX 完成中断。
*/
void DMA0_Channel1_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_RX_CH, DMA_INT_FLAG_FTF);
s_spi4_dma_rx_done = 1u;
}
}
/**
* @brief DMA0 Channel2 IRQSPI4 TX 完成中断。
*/
void DMA0_Channel2_IRQHandler(void)
{
if (SET == dma_interrupt_flag_get(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF)) {
dma_interrupt_flag_clear(EXFLASH_DMA_PERIPH, EXFLASH_DMA_TX_CH, DMA_INT_FLAG_FTF);
s_spi4_dma_tx_done = 1u;
}
}