# bsp_qspi_flash: in progress
This commit is contained in:
parent
0a45805350
commit
e4571c2d3b
11 changed files with 1772 additions and 19 deletions
|
|
@ -17,6 +17,7 @@ add_subdirectory(can)
|
||||||
add_subdirectory(button)
|
add_subdirectory(button)
|
||||||
add_subdirectory(usb_cdc)
|
add_subdirectory(usb_cdc)
|
||||||
add_subdirectory(sdram)
|
add_subdirectory(sdram)
|
||||||
|
add_subdirectory(qspi_flash)
|
||||||
|
|
||||||
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
# Подавляем предупреждения при компиляции собственных .c файлов библиотеки
|
||||||
target_compile_options(bsp_board PRIVATE -w)
|
target_compile_options(bsp_board PRIVATE -w)
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,7 @@ static void board_mpu_init(void)
|
||||||
|
|
||||||
void board_hw_init(void)
|
void board_hw_init(void)
|
||||||
{
|
{
|
||||||
|
|
||||||
BOARD_InitPins();
|
BOARD_InitPins();
|
||||||
BOARD_BootClockRUN();
|
BOARD_BootClockRUN();
|
||||||
board_mpu_init();
|
board_mpu_init();
|
||||||
|
|
|
||||||
17
bsp/qspi_flash/CMakeLists.txt
Normal file
17
bsp/qspi_flash/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# bsp/qspi_flash/CMakeLists.txt BSP QSPI — W25Q64/128/256/512 через FlexSPI1.
|
||||||
|
|
||||||
|
if(BUILD_TESTS_HOST)
|
||||||
|
return()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
add_library(bsp_qspi_flash STATIC src/qspi_flash.c)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
bsp_qspi_flash
|
||||||
|
PUBLIC include/
|
||||||
|
PRIVATE src/)
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
bsp_qspi_flash
|
||||||
|
PUBLIC bsp_status
|
||||||
|
PRIVATE bsp_board sdk_flexspi)
|
||||||
174
bsp/qspi_flash/README.md
Normal file
174
bsp/qspi_flash/README.md
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
# bsp_qspi_flash — QSPI Flash W25Q64/128/256/512
|
||||||
|
|
||||||
|
> Расположение: `bsp/qspi_flash/`
|
||||||
|
> Публичный заголовок: `bsp/qspi_flash/include/bsp/qspi_flash.h`
|
||||||
|
> Реализация: `bsp/qspi_flash/src/qspi_flash.c`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Поддерживаемое железо
|
||||||
|
|
||||||
|
| Чип | JEDEC mfr | JEDEC cap | Размер | LUT-таблица |
|
||||||
|
|---------|-----------|-----------|--------|-------------|
|
||||||
|
| W25Q64 | 0xEF | 0x17 | 8 MB | `K_LUT_3B` |
|
||||||
|
| W25Q128 | 0xEF | 0x18 | 16 MB | `K_LUT_3B` |
|
||||||
|
| W25Q256 | 0xEF | 0x19 | 32 MB | `K_LUT_4B` |
|
||||||
|
| W25Q512 | 0xEF | 0x20 | 64 MB | `K_LUT_4B` |
|
||||||
|
|
||||||
|
Интерфейс MCU: **FlexSPI1, порт A1** (`FLEXSPI_PortA1`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## XIP-безопасность
|
||||||
|
|
||||||
|
### Проблема
|
||||||
|
|
||||||
|
Прошивка исполняется XIP из того же Flash через FlexSPI AHB-интерфейс.
|
||||||
|
CPU непрерывно читает инструкции через AHB, который маршрутизируется по
|
||||||
|
LUT-слоту 0. Любая IP-команда FlexSPI прерывает AHB-путь на время
|
||||||
|
выполнения команды. Если в момент IP-команды CPU попытается фетчить
|
||||||
|
инструкцию из Flash — HardFault.
|
||||||
|
|
||||||
|
### Решение
|
||||||
|
|
||||||
|
Все функции, обращающиеся к регистрам FlexSPI (IPCMD, IPCR0/1, RFDR, TFDR,
|
||||||
|
LUT, INTR, …), размещены в **ITCM** через `AT_QUICKACCESS_SECTION_CODE`.
|
||||||
|
Инструкции этих функций загружаются в ITCM при старте и CPU не обращается
|
||||||
|
к Flash во время их выполнения.
|
||||||
|
|
||||||
|
AHB prefetch отключается (`AHBCR.PREFETCHEN = 0`) перед каждой серией
|
||||||
|
IP-транзакций и восстанавливается после (`qspi_ahb_disable` / `qspi_ahb_enable`).
|
||||||
|
Это дополнительно предотвращает спекулятивные AHB-чтения Flash во время
|
||||||
|
IP-команд.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Стратегия адресации W25Q256/512
|
||||||
|
|
||||||
|
### Почему не Enter 4-Byte Mode (0xB7)
|
||||||
|
|
||||||
|
FDCB задаёт XIP слот 0 с 24-bit адресацией для **всех** чипов. Если
|
||||||
|
переключить чип в 4-byte mode командой 0xB7, следующий AHB-фетч
|
||||||
|
инструкции из Flash пойдёт через слот 0 с 24-bit адресом, а чип теперь
|
||||||
|
ждёт 32-bit — **HardFault**.
|
||||||
|
|
||||||
|
### Dedicated 4-byte address opcodes
|
||||||
|
|
||||||
|
W25Q256/512 имеют отдельный набор opcodes, принимающих 32-bit адрес
|
||||||
|
**независимо от текущего режима адресации чипа**:
|
||||||
|
|
||||||
|
| Операция | 3-byte opcode (W25Q64/128) | 4-byte opcode (W25Q256/512) |
|
||||||
|
|-------------------|----------------------------|-----------------------------|
|
||||||
|
| Sector Erase 4KB | `0x20` | `0x21` |
|
||||||
|
| Block Erase 32KB | `0x52` | `0x5C` |
|
||||||
|
| Block Erase 64KB | `0xD8` | `0xDC` |
|
||||||
|
| Quad Page Program | `0x32` | `0x34` |
|
||||||
|
| Quad Output Read | `0x6B` | `0x6C` |
|
||||||
|
|
||||||
|
Слот 0 (XIP) **не изменяется**. Чип остаётся в 24-bit режиме. XIP работает
|
||||||
|
непрерывно на всех чипах.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## LUT-слоты
|
||||||
|
|
||||||
|
| Слот | Константа | Команда | Зависит от чипа |
|
||||||
|
|------|----------------|----------------------------|-----------------|
|
||||||
|
| 0 | (XIP, FDCB) | Quad Read | Нет (не трогаем)|
|
||||||
|
| 1 | LSEQ_READ_SR1 | Read SR1 (0x05) | Нет |
|
||||||
|
| 2 | LSEQ_WR_EN | Write Enable (0x06) | Нет |
|
||||||
|
| 3 | LSEQ_ERASE_4K | Sector Erase (0x20/0x21) | Да |
|
||||||
|
| 4 | LSEQ_PP_QUAD | Quad Page Prog (0x32/0x34) | Да |
|
||||||
|
| 5 | LSEQ_JEDEC | Read JEDEC (0x9F) | Нет |
|
||||||
|
| 6 | LSEQ_READ_SR2 | Read SR2 (0x35) | Нет |
|
||||||
|
| 7 | LSEQ_WR_SR2 | Write SR2 (0x31) | Нет |
|
||||||
|
| 8 | LSEQ_READ_SR3 | Read SR3 (0x15) | Нет |
|
||||||
|
| 9 | LSEQ_ERASE_32K | Block Erase 32KB (0x52/0x5C)| Да |
|
||||||
|
| 10 | LSEQ_ERASE_64K | Block Erase 64KB (0xD8/0xDC)| Да |
|
||||||
|
| 11 | LSEQ_IP_READ | Quad Out Read (0x6B/0x6C) | Да |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Публичный API
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* Инициализация — первый вызов */
|
||||||
|
bsp_status_t bsp_qspi_init(void);
|
||||||
|
|
||||||
|
/* Чтение JEDEC ID */
|
||||||
|
bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec);
|
||||||
|
|
||||||
|
/* Стирание */
|
||||||
|
bsp_status_t bsp_qspi_erase_sector(uint32_t addr); /* 4KB, ~45 мс */
|
||||||
|
bsp_status_t bsp_qspi_erase_block_32k(uint32_t addr); /* 32KB, ~120 мс */
|
||||||
|
bsp_status_t bsp_qspi_erase_block_64k(uint32_t addr); /* 64KB, ~150 мс */
|
||||||
|
|
||||||
|
/* Запись одной страницы */
|
||||||
|
bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data); /* ~3 мс */
|
||||||
|
|
||||||
|
/* Чтение произвольного числа байт (через IP-команду, не AHB) */
|
||||||
|
bsp_status_t bsp_qspi_read(uint32_t addr, uint8_t *p_buf, size_t size);
|
||||||
|
|
||||||
|
/* Размер Flash — доступен после init */
|
||||||
|
uint32_t bsp_qspi_flash_size(void);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Выбор операции стирания для массовой очистки
|
||||||
|
|
||||||
|
| Объём очистки | Рекомендуемая операция | Примерное время |
|
||||||
|
|------------------|------------------------|-----------------|
|
||||||
|
| < 32 KB | `erase_sector` (4KB) | пропорционально |
|
||||||
|
| 32 KB — 1 MB | `erase_block_32k` | 120 мс / 32KB |
|
||||||
|
| > 1 MB | `erase_block_64k` | 150 мс / 64KB |
|
||||||
|
|
||||||
|
Пример: очистка 4 MB через 64KB блоки = 64 операции × 150 мс ≈ **9.6 с**
|
||||||
|
против 1024 × 45 мс ≈ **46 с** через 4KB секторы.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Порядок вызова
|
||||||
|
|
||||||
|
```c
|
||||||
|
/* 1. Инициализация (один раз) */
|
||||||
|
if (bsp_qspi_init() != BSP_OK) { /* обработка ошибки */ }
|
||||||
|
|
||||||
|
/* 2. Операции */
|
||||||
|
bsp_qspi_erase_sector(addr); /* блокирует ~45 мс */
|
||||||
|
|
||||||
|
|
||||||
|
bsp_qspi_write_page(addr, buf); /* блокирует ~3 мс */
|
||||||
|
|
||||||
|
bsp_qspi_read(addr, buf, 256); /* быстро */
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CMake
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
# bsp/qspi/CMakeLists.txt
|
||||||
|
target_link_libraries(bsp_qspi
|
||||||
|
PUBLIC bsp_status
|
||||||
|
PRIVATE bsp_board sdk_flexspi
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
Потребители (например, `firmware_test`) линкуют `bsp_qspi`:
|
||||||
|
|
||||||
|
```cmake
|
||||||
|
target_link_libraries(firmware_test PRIVATE ... bsp_qspi)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Известные ограничения
|
||||||
|
|
||||||
|
- `bsp_qspi_write_page()` записывает строго одну страницу (256 байт).
|
||||||
|
Запись через границу страницы не поддерживается — вызывающий код
|
||||||
|
обязан выровнять адрес.
|
||||||
|
- Нет timeout в `qspi_wait_not_busy()`. Зависание из-за дефектного чипа
|
||||||
|
потребует watchdog reset. Для диагностической прошивки это приемлемо.
|
||||||
|
- Chip Erase (0xC7) не реализован — слишком деструктивно для XIP-прошивки.
|
||||||
|
Используй `erase_block_64k` в цикле по всему адресному пространству.
|
||||||
169
bsp/qspi_flash/include/bsp/qspi_flash.h
Normal file
169
bsp/qspi_flash/include/bsp/qspi_flash.h
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
/**
|
||||||
|
* @file qspi_flash.h
|
||||||
|
* @brief Публичный API BSP QSPI — W25Q64/128/256/512 через FlexSPI1.
|
||||||
|
*
|
||||||
|
* Поддерживаемые чипы Winbond:
|
||||||
|
*
|
||||||
|
* | Чип | JEDEC cap | Размер | Адресация в IP-командах |
|
||||||
|
* |---------|-----------|--------|-------------------------|
|
||||||
|
* | W25Q64 | 0x17 | 8 MB | 3-byte opcodes |
|
||||||
|
* | W25Q128 | 0x18 | 16 MB | 3-byte opcodes |
|
||||||
|
* | W25Q256 | 0x19 | 32 MB | Dedicated 4-byte opcodes |
|
||||||
|
* | W25Q512 | 0x20 | 64 MB | Dedicated 4-byte opcodes |
|
||||||
|
*
|
||||||
|
* Порядок вызова:
|
||||||
|
* 1. bsp_qspi_init() — детекция чипа, загрузка LUT.
|
||||||
|
* 2. bsp_qspi_*() — операции с Flash.
|
||||||
|
*
|
||||||
|
* XIP-безопасность:
|
||||||
|
* Слот 0 FlexSPI LUT (XIP read, задаётся FDCB) никогда не изменяется.
|
||||||
|
* Для W25Q256/512 используются dedicated 4-byte address opcodes —
|
||||||
|
* без переключения режима адресации чипа (0xB7), что гарантирует
|
||||||
|
* непрерывность XIP на всех поддерживаемых чипах.
|
||||||
|
*
|
||||||
|
* @note Этот заголовок не включает NXP SDK headers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef BSP_QSPI_H_
|
||||||
|
#define BSP_QSPI_H_
|
||||||
|
|
||||||
|
#include "bsp/status.h"
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ── Производитель ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief JEDEC Manufacturer ID: Winbond Electronics. */
|
||||||
|
#define BSP_QSPI_MFR_WINBOND 0xEFU
|
||||||
|
|
||||||
|
/* ── Коды ёмкости (JEDEC device_id, младший байт) ─────────────────────── */
|
||||||
|
|
||||||
|
#define BSP_QSPI_CAP_64MBIT 0x17U /**< W25Q64, 8 MB */
|
||||||
|
#define BSP_QSPI_CAP_128MBIT 0x18U /**< W25Q128, 16 MB */
|
||||||
|
#define BSP_QSPI_CAP_256MBIT 0x19U /**< W25Q256, 32 MB */
|
||||||
|
#define BSP_QSPI_CAP_512MBIT 0x20U /**< W25Q512, 64 MB */
|
||||||
|
|
||||||
|
/* ── Геометрия Flash ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief Размер страницы, байт. */
|
||||||
|
#define BSP_QSPI_PAGE_SIZE 256U
|
||||||
|
|
||||||
|
/** @brief Размер сектора 4 KB, байт. */
|
||||||
|
#define BSP_QSPI_SECTOR_SIZE 0x1000U
|
||||||
|
|
||||||
|
/** @brief Размер блока 32 KB, байт. */
|
||||||
|
#define BSP_QSPI_BLOCK_32K_SIZE 0x8000U
|
||||||
|
|
||||||
|
/** @brief Размер блока 64 KB, байт. */
|
||||||
|
#define BSP_QSPI_BLOCK_64K_SIZE 0x10000U
|
||||||
|
|
||||||
|
/* ── Типы ──────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief JEDEC ID, прочитанный командой 0x9F.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
uint8_t manufacturer_id; /**< Производитель (0xEF = Winbond). */
|
||||||
|
uint16_t device_id; /**< [15:8]=тип памяти, [7:0]=код ёмкости. */
|
||||||
|
} bsp_qspi_jedec_t;
|
||||||
|
|
||||||
|
/* ── Публичный API ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Инициализация QSPI-подсистемы.
|
||||||
|
*
|
||||||
|
* Читает JEDEC ID (0x9F), определяет чип Winbond, загружает LUT-слоты 1-11
|
||||||
|
* с opcode'ами, соответствующими режиму адресации чипа. Активирует Quad-
|
||||||
|
* режим (SR2.QE). Слот 0 (XIP, задан FDCB) не изменяется.
|
||||||
|
*
|
||||||
|
* Должна быть вызвана до любой другой bsp_qspi_* функции.
|
||||||
|
*
|
||||||
|
* @retval BSP_OK Чип опознан, LUT загружен, Quad включён.
|
||||||
|
* @retval BSP_ERR Ошибка FlexSPI или неизвестный/неподдерживаемый чип.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Чтение JEDEC ID (команда 0x9F).
|
||||||
|
*
|
||||||
|
* @param[out] p_jedec Результат. Не NULL.
|
||||||
|
* @retval BSP_OK / BSP_ERR.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Стирание сектора 4 KB.
|
||||||
|
*
|
||||||
|
* Блокирующая операция (~45 мс типичное). Во время стирания Flash BUSY —
|
||||||
|
* IP-команды записи/стирания недоступны, XIP-чтения работают.
|
||||||
|
* Вызывающий код должен вызвать bsp_usb_cdc_poll() до и после.
|
||||||
|
*
|
||||||
|
* @param[in] addr Байтовый flash-адрес. Должен быть выровнен на
|
||||||
|
* BSP_QSPI_SECTOR_SIZE.
|
||||||
|
* @retval BSP_OK / BSP_ERR.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_erase_sector(uint32_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Стирание блока 32 KB.
|
||||||
|
*
|
||||||
|
* Блокирующая операция (~120 мс типичное, 1600 мс макс).
|
||||||
|
* Оптимален для очистки областей кратных 32 KB.
|
||||||
|
* Вызывающий код должен вызвать bsp_usb_cdc_poll() до и после.
|
||||||
|
*
|
||||||
|
* @param[in] addr Байтовый flash-адрес. Должен быть выровнен на
|
||||||
|
* BSP_QSPI_BLOCK_32K_SIZE.
|
||||||
|
* @retval BSP_OK / BSP_ERR.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_erase_block_32k(uint32_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Стирание блока 64 KB.
|
||||||
|
*
|
||||||
|
* Блокирующая операция (~150 мс типичное, 2000 мс макс).
|
||||||
|
* Оптимальный выбор для очистки многомегабайтных областей:
|
||||||
|
* 1 MB = 16 блоков × 150 мс ≈ 2.4 с (vs ~11.5 с через 4 KB секторы).
|
||||||
|
* Вызывающий код должен вызвать bsp_usb_cdc_poll() до и после.
|
||||||
|
*
|
||||||
|
* @param[in] addr Байтовый flash-адрес. Должен быть выровнен на
|
||||||
|
* BSP_QSPI_BLOCK_64K_SIZE.
|
||||||
|
* @retval BSP_OK / BSP_ERR.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_erase_block_64k(uint32_t addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Запись одной страницы (256 байт).
|
||||||
|
*
|
||||||
|
* Блокирующая операция (~3 мс типичное).
|
||||||
|
* Сектор должен быть предварительно стёрт.
|
||||||
|
*
|
||||||
|
* @param[in] addr Байтовый flash-адрес. Должен быть выровнен на
|
||||||
|
* BSP_QSPI_PAGE_SIZE.
|
||||||
|
* @param[in] p_data Указатель на BSP_QSPI_PAGE_SIZE байт. Не NULL.
|
||||||
|
* @retval BSP_OK / BSP_ERR.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Чтение произвольного числа байт через IP-команду.
|
||||||
|
*
|
||||||
|
* Не использует AHB/XIP-путь. Корректно обрабатывает адреса выше 16 MB
|
||||||
|
* для W25Q256/512 через dedicated 4-byte read opcode (0x6C).
|
||||||
|
*
|
||||||
|
* @param[in] addr Байтовый flash-адрес.
|
||||||
|
* @param[out] p_buf Буфер приёма. Не NULL.
|
||||||
|
* @param[in] size Количество байт. > 0.
|
||||||
|
* @retval BSP_OK / BSP_ERR.
|
||||||
|
*/
|
||||||
|
bsp_status_t bsp_qspi_read(uint32_t addr, uint8_t *p_buf, size_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Возвращает размер Flash-чипа, определённый в bsp_qspi_init().
|
||||||
|
*
|
||||||
|
* @return Размер Flash в байтах, или 0 если init не была вызвана.
|
||||||
|
*/
|
||||||
|
uint32_t bsp_qspi_flash_size(void);
|
||||||
|
|
||||||
|
#endif /* BSP_QSPI_H_ */
|
||||||
822
bsp/qspi_flash/src/qspi_flash.c
Normal file
822
bsp/qspi_flash/src/qspi_flash.c
Normal file
|
|
@ -0,0 +1,822 @@
|
||||||
|
/**
|
||||||
|
* @file qspi.c
|
||||||
|
* @brief BSP QSPI — W25Q64/128/256/512 через FlexSPI1.
|
||||||
|
*
|
||||||
|
* ## XIP-безопасность
|
||||||
|
*
|
||||||
|
* Прошивка исполняется XIP из Flash через FlexSPI AHB-интерфейс (слот 0).
|
||||||
|
* Любая IP-команда FlexSPI прерывает AHB-путь на время выполнения.
|
||||||
|
* Чтобы CPU не пытался фетчить инструкции из Flash во время IP-команды,
|
||||||
|
* все функции, обращающиеся к регистрам FlexSPI, размещаются в ITCM через
|
||||||
|
* QSPI_RAMFUNC. AHB prefetch отключается перед каждой
|
||||||
|
* серией IP-транзакций и восстанавливается после.
|
||||||
|
*
|
||||||
|
* ## Стратегия адресации W25Q256/512
|
||||||
|
*
|
||||||
|
* Слот 0 FDCB использует 24-bit адресацию на всех чипах. Чтобы не сломать
|
||||||
|
* XIP, режим адресации чипа (команда 0xB7) не переключается. Вместо этого
|
||||||
|
* для W25Q256/512 загружаются dedicated 4-byte address opcodes:
|
||||||
|
* - Sector Erase: 0x20 → 0x21
|
||||||
|
* - Block Erase 32KB: 0x52 → 0x5C
|
||||||
|
* - Block Erase 64KB: 0xD8 → 0xDC
|
||||||
|
* - Quad Page Program: 0x32 → 0x34
|
||||||
|
* - IP Quad Output Read: 0x6B → 0x6C
|
||||||
|
* Эти opcodes принимают 32-bit адрес независимо от текущего режима чипа.
|
||||||
|
*
|
||||||
|
* ## LUT-слоты
|
||||||
|
*
|
||||||
|
* Slot 0 — XIP quad read (FDCB, НЕ трогается)
|
||||||
|
* Slot 1 — Read Status Reg-1 (0x05)
|
||||||
|
* Slot 2 — Write Enable (0x06)
|
||||||
|
* Slot 3 — Sector Erase 4KB (0x20 / 0x21)
|
||||||
|
* Slot 4 — Quad Page Program (0x32 / 0x34)
|
||||||
|
* Slot 5 — Read JEDEC ID (0x9F)
|
||||||
|
* Slot 6 — Read Status Reg-2 (0x35)
|
||||||
|
* Slot 7 — Write Status Reg-2 (0x31)
|
||||||
|
* Slot 8 — Read Status Reg-3 (0x15)
|
||||||
|
* Slot 9 — Block Erase 32KB (0x52 / 0x5C)
|
||||||
|
* Slot 10 — Block Erase 64KB (0xD8 / 0xDC)
|
||||||
|
* Slot 11 — IP Quad Output Read (0x6B / 0x6C)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bsp/qspi_flash.h"
|
||||||
|
|
||||||
|
#include "fsl_common.h"
|
||||||
|
#include "fsl_flexspi.h"
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ── Привязка к железу ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief FlexSPI instance для W25Qxx. */
|
||||||
|
#define QSPI_BASE FLEXSPI
|
||||||
|
/** @brief FlexSPI порт, к которому подключён W25Qxx. */
|
||||||
|
#define QSPI_PORT kFLEXSPI_PortA1
|
||||||
|
|
||||||
|
/* ── Индексы LUT-слотов ─────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define LSEQ_READ_SR1 1U /**< Read Status Register 1 (0x05) */
|
||||||
|
#define LSEQ_WR_EN 2U /**< Write Enable (0x06) */
|
||||||
|
#define LSEQ_ERASE_4K 3U /**< Sector Erase 4KB */
|
||||||
|
#define LSEQ_PP_QUAD 4U /**< Quad Page Program */
|
||||||
|
#define LSEQ_JEDEC 5U /**< Read JEDEC ID (0x9F) */
|
||||||
|
#define LSEQ_READ_SR2 6U /**< Read Status Register 2 (0x35) */
|
||||||
|
#define LSEQ_WR_SR2 7U /**< Write Status Register 2(0x31) */
|
||||||
|
#define LSEQ_READ_SR3 8U /**< Read Status Register 3 (0x15) */
|
||||||
|
#define LSEQ_ERASE_32K 9U /**< Block Erase 32KB */
|
||||||
|
#define LSEQ_ERASE_64K 10U /**< Block Erase 64KB */
|
||||||
|
#define LSEQ_IP_READ 11U /**< IP Quad Output Read */
|
||||||
|
|
||||||
|
/** @brief Первый слот BSP (слот 0 = XIP FDCB). */
|
||||||
|
#define LUT_BSP_FIRST_SLOT 1U
|
||||||
|
/** @brief Количество слотов BSP (1..11). */
|
||||||
|
#define LUT_BSP_SLOT_COUNT 11U
|
||||||
|
/** @brief Слов на одну LUT-последовательность. */
|
||||||
|
#define LUT_WORDS_PER_SEQ 4U
|
||||||
|
/** @brief Всего слов в таблице (слоты 0-11). */
|
||||||
|
#define LUT_TOTAL_WORDS ((LSEQ_IP_READ + 1U) * LUT_WORDS_PER_SEQ)
|
||||||
|
|
||||||
|
/* ── Адресация ──────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief 3-byte адресация: 24-bit (W25Q64/128, стандартные opcodes). */
|
||||||
|
#define ADDR_BITS_24 0x18U
|
||||||
|
/**
|
||||||
|
* @brief 4-byte адресация: 32-bit (W25Q256/512, dedicated opcodes).
|
||||||
|
*
|
||||||
|
* Используется с opcode-вариантами, принимающими 32-bit адрес без
|
||||||
|
* переключения режима адресации чипа командой 0xB7.
|
||||||
|
*/
|
||||||
|
#define ADDR_BITS_32 0x20U
|
||||||
|
|
||||||
|
/* ── Регистровые константы ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define QSPI_LUT_KEY 0x5AF05AF0UL /**< Ключ для разблокировки LUT. */
|
||||||
|
#define QSPI_LUTCR_UNLOCK 0x02U /**< LUTCR: unlock. */
|
||||||
|
#define QSPI_LUTCR_LOCK 0x01U /**< LUTCR: lock. */
|
||||||
|
|
||||||
|
/* ── Биты статусных регистров ───────────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define SR1_BUSY_BIT 0x01U /**< SR1.WIP — Write In Progress. */
|
||||||
|
#define SR2_QE_BIT 0x02U /**< SR2.QE — Quad Enable. */
|
||||||
|
|
||||||
|
/* ── Параметры FIFO ─────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief Байт на watermark-unit (TXWMRK=0 / RXWMRK=0 → 1 unit = 8 bytes). */
|
||||||
|
#define FIFO_WM_BYTES 8U
|
||||||
|
/** @brief 32-bit слов в одном watermark-unit. */
|
||||||
|
#define FIFO_WM_WORDS (FIFO_WM_BYTES / QSPI_RFDR_WORD_BYTES)
|
||||||
|
/** @brief Байт в одном слове RFDR/TFDR. */
|
||||||
|
#define QSPI_RFDR_WORD_BYTES 4U
|
||||||
|
|
||||||
|
/* ── Параметры SR-чтения ────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Размер читаемого буфера для однобайтных SR-команд.
|
||||||
|
*
|
||||||
|
* FlexSPI FIFO работает минимальными единицами в 4 байта (RXWMRK=0, 1 FILL
|
||||||
|
* unit = 4 bytes). Читаем 4 байта, используем только byte[0].
|
||||||
|
*/
|
||||||
|
#define SR_READ_LEN 4U
|
||||||
|
|
||||||
|
/* ── Размеры Flash по capacity byte ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
#define QSPI_SIZE_8MB 0x00800000UL /**< W25Q64: 8 MB */
|
||||||
|
#define QSPI_SIZE_16MB 0x01000000UL /**< W25Q128: 16 MB */
|
||||||
|
#define QSPI_SIZE_32MB 0x02000000UL /**< W25Q256: 32 MB */
|
||||||
|
#define QSPI_SIZE_64MB 0x04000000UL /**< W25Q512: 64 MB */
|
||||||
|
|
||||||
|
/* ── Macro: построение LUT-таблицы ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Параметры:
|
||||||
|
* E4K — opcode Sector Erase 4KB (0x20 | 0x21)
|
||||||
|
* E32K — opcode Block Erase 32KB (0x52 | 0x5C)
|
||||||
|
* E64K — opcode Block Erase 64KB (0xD8 | 0xDC)
|
||||||
|
* PP — opcode Quad Page Program (0x32 | 0x34)
|
||||||
|
* RD — opcode Quad Output Read (0x6B | 0x6C)
|
||||||
|
* ABITS — биты адреса (ADDR_BITS_24 | ADDR_BITS_32)
|
||||||
|
*
|
||||||
|
* Слот 0 = нули (XIP FDCB, не записывается в HW).
|
||||||
|
* Слоты без data-фазы (Erase, WE) используют 1 word; PP и IP_READ — 2 words;
|
||||||
|
* остальные words в слоте остаются нулями (STOP).
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
#define BUILD_LUT(E4K, E32K, E64K, PP, RD, ABITS) \
|
||||||
|
{ \
|
||||||
|
[LUT_WORDS_PER_SEQ * \
|
||||||
|
LSEQ_READ_SR1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05U, \
|
||||||
|
kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_WR_EN] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06U, kFLEXSPI_Command_STOP, \
|
||||||
|
kFLEXSPI_1PAD, 0x00U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_ERASE_4K] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, (E4K), \
|
||||||
|
kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, (ABITS)), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_PP_QUAD] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, (PP), \
|
||||||
|
kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, (ABITS)), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_PP_QUAD + 1U] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04U, \
|
||||||
|
kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x00U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_JEDEC] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x9FU, \
|
||||||
|
kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_READ_SR2] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x35U, \
|
||||||
|
kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_WR_SR2] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x31U, \
|
||||||
|
kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_READ_SR3] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x15U, \
|
||||||
|
kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04U), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_ERASE_32K] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, (E32K), \
|
||||||
|
kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, (ABITS)), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_ERASE_64K] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, (E64K), \
|
||||||
|
kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, (ABITS)), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_IP_READ] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, (RD), \
|
||||||
|
kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, (ABITS)), \
|
||||||
|
[LUT_WORDS_PER_SEQ * LSEQ_IP_READ + 1U] = \
|
||||||
|
FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x08U, \
|
||||||
|
kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 0x04U), \
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @brief LUT для W25Q64/128 — стандартные 3-byte opcodes. */
|
||||||
|
static const uint32_t K_LUT_3B[LUT_TOTAL_WORDS] =
|
||||||
|
BUILD_LUT(0x20U, 0x52U, 0xD8U, 0x32U, 0x6BU, ADDR_BITS_24);
|
||||||
|
|
||||||
|
/** @brief LUT для W25Q256/512 — dedicated 4-byte opcodes. */
|
||||||
|
static const uint32_t K_LUT_4B[LUT_TOTAL_WORDS] =
|
||||||
|
BUILD_LUT(0x21U, 0x5CU, 0xDCU, 0x34U, 0x6CU, ADDR_BITS_32);
|
||||||
|
|
||||||
|
/* ── Состояние модуля ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static bool g_s_init = false; /**< true после успешного init(). */
|
||||||
|
static uint32_t g_s_flash_size = 0U; /**< Размер Flash в байтах. */
|
||||||
|
|
||||||
|
/* ── ITCM: базовые примитивы FlexSPI ───────────────────────────────────── */
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static uint32_t qspi_irq_lock(void))
|
||||||
|
{
|
||||||
|
const uint32_t primask = __get_PRIMASK();
|
||||||
|
__disable_irq();
|
||||||
|
__DSB();
|
||||||
|
__ISB();
|
||||||
|
return primask;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_irq_unlock(uint32_t primask))
|
||||||
|
{
|
||||||
|
__DSB();
|
||||||
|
__ISB();
|
||||||
|
if ((primask & 1U) == 0U)
|
||||||
|
{
|
||||||
|
__enable_irq();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_ahb_disable(void))
|
||||||
|
{
|
||||||
|
QSPI_BASE->AHBCR &= ~FLEXSPI_AHBCR_PREFETCHEN_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_ahb_enable(void))
|
||||||
|
{
|
||||||
|
QSPI_BASE->AHBCR |= FLEXSPI_AHBCR_PREFETCHEN_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_sw_reset(void))
|
||||||
|
{
|
||||||
|
QSPI_BASE->MCR0 |= FLEXSPI_MCR0_SWRESET_MASK;
|
||||||
|
while ((QSPI_BASE->MCR0 & FLEXSPI_MCR0_SWRESET_MASK) != 0U)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_wait_idle(void))
|
||||||
|
{
|
||||||
|
while (((QSPI_BASE->STS0 & FLEXSPI_STS0_ARBIDLE_MASK) == 0U) ||
|
||||||
|
((QSPI_BASE->STS0 & FLEXSPI_STS0_SEQIDLE_MASK) == 0U))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_check_error(uint32_t intr))
|
||||||
|
{
|
||||||
|
const uint32_t ERR_FLAGS = (uint32_t) kFLEXSPI_SequenceExecutionTimeoutFlag |
|
||||||
|
(uint32_t) kFLEXSPI_IpCommandSequenceErrorFlag |
|
||||||
|
(uint32_t) kFLEXSPI_IpCommandGrantTimeoutFlag;
|
||||||
|
|
||||||
|
const uint32_t ERR = intr & ERR_FLAGS;
|
||||||
|
status_t result = kStatus_Success;
|
||||||
|
|
||||||
|
if (ERR != 0U)
|
||||||
|
{
|
||||||
|
if ((ERR & (uint32_t) kFLEXSPI_SequenceExecutionTimeoutFlag) != 0U)
|
||||||
|
{
|
||||||
|
result = kStatus_FLEXSPI_SequenceExecutionTimeout;
|
||||||
|
}
|
||||||
|
else if ((ERR & (uint32_t) kFLEXSPI_IpCommandSequenceErrorFlag) != 0U)
|
||||||
|
{
|
||||||
|
result = kStatus_FLEXSPI_IpCommandSequenceError;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = kStatus_FLEXSPI_IpCommandGrantTimeout;
|
||||||
|
}
|
||||||
|
QSPI_BASE->INTR = ERR;
|
||||||
|
QSPI_BASE->IPTXFCR |= FLEXSPI_IPTXFCR_CLRIPTXF_MASK;
|
||||||
|
QSPI_BASE->IPRXFCR |= FLEXSPI_IPRXFCR_CLRIPRXF_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ITCM: RX FIFO ──────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Читает FIFO_WM_BYTES (8 байт) из RX FIFO в p_dst.
|
||||||
|
*
|
||||||
|
* Ожидает флага watermark-available, читает 2 слова, делает pop.
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_chunk(uint8_t *p_dst))
|
||||||
|
{
|
||||||
|
uint32_t intr;
|
||||||
|
status_t err;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
intr = QSPI_BASE->INTR;
|
||||||
|
err = qspi_check_error(intr);
|
||||||
|
if (err != kStatus_Success)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
} while ((intr & (uint32_t) kFLEXSPI_IpRxFifoWatermarkAvailableFlag) == 0U);
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */
|
||||||
|
for (uint32_t w = 0U; w < FIFO_WM_WORDS; w++)
|
||||||
|
{
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */
|
||||||
|
const uint32_t WORD = QSPI_BASE->RFDR[w];
|
||||||
|
const uint32_t BASE = w * QSPI_RFDR_WORD_BYTES;
|
||||||
|
p_dst[BASE + 0U] = (uint8_t) (WORD & 0xFFU);
|
||||||
|
p_dst[BASE + 1U] = (uint8_t) ((WORD >> 8U) & 0xFFU);
|
||||||
|
p_dst[BASE + 2U] = (uint8_t) ((WORD >> 16U) & 0xFFU);
|
||||||
|
p_dst[BASE + 3U] = (uint8_t) ((WORD >> 24U) & 0xFFU);
|
||||||
|
}
|
||||||
|
QSPI_BASE->INTR = (uint32_t) kFLEXSPI_IpRxFifoWatermarkAvailableFlag;
|
||||||
|
return kStatus_Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Читает tail-байты (< FIFO_WM_BYTES) из RX FIFO.
|
||||||
|
*
|
||||||
|
* Ожидает через IPRXFSTS.FILL. Вызывать когда remain < FIFO_WM_BYTES.
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_tail(uint8_t *p_dst, uint32_t remain))
|
||||||
|
{
|
||||||
|
const uint32_t WORDS_NEEDED = (remain + QSPI_RFDR_WORD_BYTES - 1U) / QSPI_RFDR_WORD_BYTES;
|
||||||
|
bool done = false;
|
||||||
|
|
||||||
|
while (!done)
|
||||||
|
{
|
||||||
|
const uint32_t FILL =
|
||||||
|
(QSPI_BASE->IPRXFSTS & FLEXSPI_IPRXFSTS_FILL_MASK) >> FLEXSPI_IPRXFSTS_FILL_SHIFT;
|
||||||
|
if (FILL >= WORDS_NEEDED)
|
||||||
|
{
|
||||||
|
done = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const status_t ERR = qspi_check_error(QSPI_BASE->INTR);
|
||||||
|
if (ERR != kStatus_Success)
|
||||||
|
{
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t bytes_left = remain;
|
||||||
|
for (uint32_t w = 0U; w < WORDS_NEEDED; w++)
|
||||||
|
{
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */
|
||||||
|
const uint32_t WORD = QSPI_BASE->RFDR[w];
|
||||||
|
const uint32_t BYTES =
|
||||||
|
(bytes_left < QSPI_RFDR_WORD_BYTES) ? bytes_left : QSPI_RFDR_WORD_BYTES;
|
||||||
|
const uint32_t BASE = w * QSPI_RFDR_WORD_BYTES;
|
||||||
|
for (uint32_t b = 0U; b < BYTES; b++)
|
||||||
|
{
|
||||||
|
p_dst[BASE + b] = (uint8_t) ((WORD >> (8U * b)) & 0xFFU);
|
||||||
|
}
|
||||||
|
bytes_left -= BYTES;
|
||||||
|
}
|
||||||
|
QSPI_BASE->INTR = (uint32_t) kFLEXSPI_IpRxFifoWatermarkAvailableFlag;
|
||||||
|
return kStatus_Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_fifo(uint8_t *p_buf, uint32_t len))
|
||||||
|
{
|
||||||
|
uint32_t offset = 0U;
|
||||||
|
|
||||||
|
while ((len - offset) >= FIFO_WM_BYTES)
|
||||||
|
{
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||||
|
const status_t ERR = qspi_read_chunk(p_buf + offset);
|
||||||
|
if (ERR != kStatus_Success)
|
||||||
|
{
|
||||||
|
return ERR;
|
||||||
|
}
|
||||||
|
offset += FIFO_WM_BYTES;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset < len)
|
||||||
|
{
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) */
|
||||||
|
return qspi_read_tail(p_buf + offset, len - offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return kStatus_Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ITCM: TX FIFO ──────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_write_fifo(const uint8_t *p_buf, uint32_t len))
|
||||||
|
{
|
||||||
|
const uint8_t *p = p_buf;
|
||||||
|
uint32_t remain = len;
|
||||||
|
|
||||||
|
while (remain > 0U)
|
||||||
|
{
|
||||||
|
uint32_t intr;
|
||||||
|
status_t err;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
intr = QSPI_BASE->INTR;
|
||||||
|
err = qspi_check_error(intr);
|
||||||
|
if (err != kStatus_Success)
|
||||||
|
{
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
} while ((intr & (uint32_t) kFLEXSPI_IpTxFifoWatermarkEmptyFlag) == 0U);
|
||||||
|
|
||||||
|
const uint32_t CHUNK = (remain > FIFO_WM_BYTES) ? FIFO_WM_BYTES : remain;
|
||||||
|
const uint32_t WR_WORDS = (CHUNK + QSPI_RFDR_WORD_BYTES - 1U) / QSPI_RFDR_WORD_BYTES;
|
||||||
|
|
||||||
|
for (uint32_t w = 0U; w < WR_WORDS; w++)
|
||||||
|
{
|
||||||
|
const uint32_t OFF = w * QSPI_RFDR_WORD_BYTES;
|
||||||
|
const uint32_t BYTES =
|
||||||
|
((CHUNK - OFF) < QSPI_RFDR_WORD_BYTES) ? (CHUNK - OFF) : QSPI_RFDR_WORD_BYTES;
|
||||||
|
uint32_t word = 0U;
|
||||||
|
for (uint32_t b = 0U; b < BYTES; b++)
|
||||||
|
{
|
||||||
|
word |= ((uint32_t) *p) << (8U * b);
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-constant-array-index) */
|
||||||
|
QSPI_BASE->TFDR[w] = word;
|
||||||
|
}
|
||||||
|
remain -= CHUNK;
|
||||||
|
QSPI_BASE->INTR = (uint32_t) kFLEXSPI_IpTxFifoWatermarkEmptyFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
return kStatus_Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ITCM: LUT update ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Записывает num_slots LUT-слотов начиная с first_slot.
|
||||||
|
*
|
||||||
|
* p_lut индексируется от слота 0 (слот 0 не записывается).
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_lut_update(uint32_t first_slot, const uint32_t *p_lut,
|
||||||
|
uint32_t num_slots))
|
||||||
|
{
|
||||||
|
qspi_wait_idle();
|
||||||
|
|
||||||
|
QSPI_BASE->LUTKEY = QSPI_LUT_KEY;
|
||||||
|
QSPI_BASE->LUTCR = QSPI_LUTCR_UNLOCK;
|
||||||
|
|
||||||
|
const uint32_t FIRST_WORD = first_slot * LUT_WORDS_PER_SEQ;
|
||||||
|
const uint32_t WORD_COUNT = num_slots * LUT_WORDS_PER_SEQ;
|
||||||
|
|
||||||
|
for (uint32_t i = 0U; i < WORD_COUNT; i++)
|
||||||
|
{
|
||||||
|
QSPI_BASE->LUT[FIRST_WORD + i] = p_lut[FIRST_WORD + i];
|
||||||
|
}
|
||||||
|
|
||||||
|
QSPI_BASE->LUTKEY = QSPI_LUT_KEY;
|
||||||
|
QSPI_BASE->LUTCR = QSPI_LUTCR_LOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ITCM: IP-команды ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Подготавливает IP-транзакцию (общая часть для read/write/cmd).
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static void qspi_ip_setup(uint32_t seq_idx, uint32_t addr,
|
||||||
|
uint32_t data_len))
|
||||||
|
{
|
||||||
|
QSPI_BASE->FLSHCR2[QSPI_PORT] |= FLEXSPI_FLSHCR2_CLRINSTRPTR_MASK;
|
||||||
|
QSPI_BASE->INTR = FLEXSPI_INTR_AHBCMDERR_MASK | FLEXSPI_INTR_IPCMDERR_MASK |
|
||||||
|
FLEXSPI_INTR_AHBCMDGE_MASK | FLEXSPI_INTR_IPCMDGE_MASK;
|
||||||
|
QSPI_BASE->IPCR0 = addr;
|
||||||
|
QSPI_BASE->IPTXFCR |= FLEXSPI_IPTXFCR_CLRIPTXF_MASK;
|
||||||
|
QSPI_BASE->IPRXFCR |= FLEXSPI_IPRXFCR_CLRIPRXF_MASK;
|
||||||
|
QSPI_BASE->IPCR1 =
|
||||||
|
FLEXSPI_IPCR1_IDATSZ(data_len) | FLEXSPI_IPCR1_ISEQID(seq_idx) | FLEXSPI_IPCR1_ISEQNUM(0U);
|
||||||
|
QSPI_BASE->IPCMD |= FLEXSPI_IPCMD_TRG_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_ip_read(uint32_t seq_idx, uint32_t addr,
|
||||||
|
uint8_t *p_rx, uint32_t data_len))
|
||||||
|
{
|
||||||
|
qspi_ip_setup(seq_idx, addr, data_len);
|
||||||
|
const status_t RESULT = qspi_read_fifo(p_rx, data_len);
|
||||||
|
qspi_wait_idle();
|
||||||
|
return RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_ip_write(uint32_t seq_idx, uint32_t addr,
|
||||||
|
const uint8_t *p_tx, uint32_t data_len))
|
||||||
|
{
|
||||||
|
qspi_ip_setup(seq_idx, addr, data_len);
|
||||||
|
const status_t RESULT = qspi_write_fifo(p_tx, data_len);
|
||||||
|
qspi_wait_idle();
|
||||||
|
return RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_ip_cmd(uint32_t seq_idx, uint32_t addr))
|
||||||
|
{
|
||||||
|
qspi_ip_setup(seq_idx, addr, 0U);
|
||||||
|
qspi_wait_idle();
|
||||||
|
return qspi_check_error(QSPI_BASE->INTR);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── ITCM: Flash management ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Ожидает снятия SR1.BUSY. Выполняется из ITCM на время erase/program.
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_wait_not_busy(void))
|
||||||
|
{
|
||||||
|
uint8_t sr1_buf[SR_READ_LEN];
|
||||||
|
status_t result;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = qspi_ip_read(LSEQ_READ_SR1, 0U, sr1_buf, SR_READ_LEN);
|
||||||
|
if (result != kStatus_Success)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
} while ((sr1_buf[0U] & SR1_BUSY_BIT) != 0U);
|
||||||
|
|
||||||
|
return kStatus_Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_write_enable(uint32_t addr))
|
||||||
|
{
|
||||||
|
return qspi_ip_cmd(LSEQ_WR_EN, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_read_sr(uint32_t seq_idx, uint8_t *p_val))
|
||||||
|
{
|
||||||
|
uint8_t buf[SR_READ_LEN];
|
||||||
|
const status_t RESULT = qspi_ip_read(seq_idx, 0U, buf, SR_READ_LEN);
|
||||||
|
if (RESULT == kStatus_Success)
|
||||||
|
{
|
||||||
|
*p_val = buf[0U];
|
||||||
|
}
|
||||||
|
return RESULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_write_sr2(uint8_t val))
|
||||||
|
{
|
||||||
|
status_t result = qspi_write_enable(0U);
|
||||||
|
if (result != kStatus_Success)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const uint8_t BUF[1U] = { val };
|
||||||
|
result = qspi_ip_write(LSEQ_WR_SR2, 0U, BUF, 1U);
|
||||||
|
if (result != kStatus_Success)
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return qspi_wait_not_busy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Гарантирует установку SR2.QE (Quad Enable).
|
||||||
|
*
|
||||||
|
* Если бит не установлен — записывает SR2 через команду 0x31.
|
||||||
|
*/
|
||||||
|
/* NOLINTNEXTLINE(cppcoreguidelines-macro-usage) */
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static status_t qspi_ensure_quad_enabled(void))
|
||||||
|
{
|
||||||
|
uint8_t sr2 = 0U;
|
||||||
|
const status_t RES = qspi_read_sr(LSEQ_READ_SR2, &sr2);
|
||||||
|
if (RES != kStatus_Success)
|
||||||
|
{
|
||||||
|
return RES;
|
||||||
|
}
|
||||||
|
if ((sr2 & SR2_QE_BIT) != 0U)
|
||||||
|
{
|
||||||
|
return kStatus_Success; /* Уже установлен */
|
||||||
|
}
|
||||||
|
return qspi_write_sr2((uint8_t) (sr2 | SR2_QE_BIT));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Вспомогательные функции (не ITCM) ─────────────────────────────────── */
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static bsp_status_t to_bsp(status_t s))
|
||||||
|
{
|
||||||
|
return (s == (status_t) kStatus_Success) ? BSP_OK : BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Определяет параметры чипа по capacity byte JEDEC ID.
|
||||||
|
*
|
||||||
|
* @param[in] cap_byte Байт ёмкости (jedec_buf[2]).
|
||||||
|
* @param[out] p_flash_size Размер Flash в байтах.
|
||||||
|
* @param[out] p_lut Указатель на подходящую LUT-таблицу.
|
||||||
|
*/
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static bsp_status_t qspi_detect_chip(uint8_t cap_byte,
|
||||||
|
uint32_t *p_flash_size,
|
||||||
|
const uint32_t **p_lut))
|
||||||
|
{
|
||||||
|
switch (cap_byte)
|
||||||
|
{
|
||||||
|
case BSP_QSPI_CAP_64MBIT:
|
||||||
|
*p_flash_size = QSPI_SIZE_8MB;
|
||||||
|
*p_lut = K_LUT_3B;
|
||||||
|
break;
|
||||||
|
case BSP_QSPI_CAP_128MBIT:
|
||||||
|
*p_flash_size = QSPI_SIZE_16MB;
|
||||||
|
*p_lut = K_LUT_3B;
|
||||||
|
break;
|
||||||
|
case BSP_QSPI_CAP_256MBIT:
|
||||||
|
*p_flash_size = QSPI_SIZE_32MB;
|
||||||
|
*p_lut = K_LUT_4B;
|
||||||
|
break;
|
||||||
|
case BSP_QSPI_CAP_512MBIT:
|
||||||
|
*p_flash_size = QSPI_SIZE_64MB;
|
||||||
|
*p_lut = K_LUT_4B;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Общая реализация erase-операций.
|
||||||
|
*
|
||||||
|
* Паттерн: AHBDisable → WriteEnable → IP cmd → WaitBusy → Reset → AHBEnable.
|
||||||
|
*/
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(static bsp_status_t qspi_do_erase(uint32_t seq_idx, uint32_t addr))
|
||||||
|
{
|
||||||
|
if (!g_s_init)
|
||||||
|
{
|
||||||
|
return BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
const uint32_t primask = qspi_irq_lock();
|
||||||
|
qspi_ahb_disable();
|
||||||
|
|
||||||
|
status_t result = qspi_write_enable(addr);
|
||||||
|
if (result == kStatus_Success)
|
||||||
|
{
|
||||||
|
result = qspi_ip_cmd(seq_idx, addr);
|
||||||
|
}
|
||||||
|
if (result == kStatus_Success)
|
||||||
|
{
|
||||||
|
result = qspi_wait_not_busy();
|
||||||
|
}
|
||||||
|
|
||||||
|
qspi_sw_reset();
|
||||||
|
qspi_ahb_enable();
|
||||||
|
qspi_irq_unlock(primask);
|
||||||
|
return to_bsp(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Публичный API ──────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_init(void))
|
||||||
|
{
|
||||||
|
const uint32_t PRIMASK = qspi_irq_lock();
|
||||||
|
bsp_status_t bsp_result;
|
||||||
|
qspi_ahb_disable();
|
||||||
|
|
||||||
|
/* Шаг 1: записываем JEDEC-слот (одинаков для всех чипов) */
|
||||||
|
qspi_lut_update(LSEQ_JEDEC, K_LUT_3B, 1U);
|
||||||
|
|
||||||
|
/* Шаг 2: читаем JEDEC ID */
|
||||||
|
uint8_t jedec_buf[SR_READ_LEN] = { 0U };
|
||||||
|
status_t result = qspi_ip_read(LSEQ_JEDEC, 0U, jedec_buf, SR_READ_LEN);
|
||||||
|
if (result != kStatus_Success)
|
||||||
|
{
|
||||||
|
bsp_result = BSP_ERR_HW;
|
||||||
|
goto DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Шаг 3: проверяем производителя */
|
||||||
|
if (jedec_buf[0U] != BSP_QSPI_MFR_WINBOND)
|
||||||
|
{
|
||||||
|
bsp_result = BSP_ERR_HW;
|
||||||
|
goto DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Шаг 4: определяем чип и выбираем LUT */
|
||||||
|
uint32_t flash_size = 0U;
|
||||||
|
const uint32_t *p_lut = K_LUT_3B;
|
||||||
|
const bsp_status_t DETECT = qspi_detect_chip(jedec_buf[2U], &flash_size, &p_lut);
|
||||||
|
|
||||||
|
if (DETECT != BSP_OK)
|
||||||
|
{
|
||||||
|
bsp_result = BSP_ERR_HW;
|
||||||
|
goto DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Шаг 5: загружаем полный LUT (слоты 1-11) */
|
||||||
|
qspi_lut_update(LUT_BSP_FIRST_SLOT, p_lut, LUT_BSP_SLOT_COUNT);
|
||||||
|
|
||||||
|
/* Шаг 6: активируем Quad-режим */
|
||||||
|
result = qspi_ensure_quad_enabled();
|
||||||
|
|
||||||
|
if (result != kStatus_Success)
|
||||||
|
{
|
||||||
|
bsp_result = BSP_ERR_HW;
|
||||||
|
goto DONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_s_flash_size = flash_size;
|
||||||
|
g_s_init = true;
|
||||||
|
bsp_result = BSP_OK;
|
||||||
|
|
||||||
|
DONE:
|
||||||
|
qspi_sw_reset();
|
||||||
|
qspi_ahb_enable();
|
||||||
|
qspi_irq_unlock(PRIMASK);
|
||||||
|
return bsp_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_read_jedec_id(bsp_qspi_jedec_t *p_jedec))
|
||||||
|
{
|
||||||
|
if (p_jedec == NULL)
|
||||||
|
{
|
||||||
|
return BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
const uint32_t primask = qspi_irq_lock();
|
||||||
|
qspi_ahb_disable();
|
||||||
|
|
||||||
|
uint8_t buf[SR_READ_LEN] = { 0U };
|
||||||
|
const status_t RESULT = qspi_ip_read(LSEQ_JEDEC, 0U, buf, SR_READ_LEN);
|
||||||
|
|
||||||
|
qspi_sw_reset();
|
||||||
|
qspi_ahb_enable();
|
||||||
|
qspi_irq_unlock(primask);
|
||||||
|
|
||||||
|
if (RESULT != kStatus_Success)
|
||||||
|
{
|
||||||
|
return BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
p_jedec->manufacturer_id = buf[0U];
|
||||||
|
p_jedec->device_id = ((uint16_t) buf[1U] << 8U) | (uint16_t) buf[2U];
|
||||||
|
return BSP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_erase_sector(uint32_t addr))
|
||||||
|
{
|
||||||
|
return qspi_do_erase(LSEQ_ERASE_4K, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_erase_block_32k(uint32_t addr))
|
||||||
|
{
|
||||||
|
return qspi_do_erase(LSEQ_ERASE_32K, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_erase_block_64k(uint32_t addr))
|
||||||
|
{
|
||||||
|
return qspi_do_erase(LSEQ_ERASE_64K, addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_write_page(uint32_t addr, const uint8_t *p_data))
|
||||||
|
{
|
||||||
|
if (!g_s_init || (p_data == NULL))
|
||||||
|
{
|
||||||
|
return BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
const uint32_t PRIMASK = qspi_irq_lock();
|
||||||
|
qspi_ahb_disable();
|
||||||
|
|
||||||
|
status_t result = qspi_write_enable(addr);
|
||||||
|
if (result == kStatus_Success)
|
||||||
|
{
|
||||||
|
result = qspi_ip_write(LSEQ_PP_QUAD, addr, p_data, BSP_QSPI_PAGE_SIZE);
|
||||||
|
}
|
||||||
|
if (result == kStatus_Success)
|
||||||
|
{
|
||||||
|
result = qspi_wait_not_busy();
|
||||||
|
}
|
||||||
|
|
||||||
|
qspi_sw_reset();
|
||||||
|
qspi_ahb_enable();
|
||||||
|
qspi_irq_unlock(PRIMASK);
|
||||||
|
return to_bsp(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
AT_QUICKACCESS_SECTION_CODE(bsp_status_t bsp_qspi_read(uint32_t addr, uint8_t *p_buf, size_t size))
|
||||||
|
{
|
||||||
|
if (!g_s_init || (p_buf == NULL) || (size == 0U))
|
||||||
|
{
|
||||||
|
return BSP_ERR_HW;
|
||||||
|
}
|
||||||
|
const uint32_t PRIMASK = qspi_irq_lock();
|
||||||
|
qspi_ahb_disable();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Используем слот LSEQ_IP_READ (11), настроенный с корректным opcode и
|
||||||
|
* ABITS для данного чипа. Слот 0 (XIP FDCB) не используется — его
|
||||||
|
* opcode и режим адресации зафиксированы FDCB и могут не совпадать
|
||||||
|
* с тем, что нужно для IP-чтений (особенно для W25Q256/512).
|
||||||
|
*/
|
||||||
|
const status_t RESULT = qspi_ip_read(LSEQ_IP_READ, addr, p_buf, (uint32_t) size);
|
||||||
|
|
||||||
|
qspi_sw_reset();
|
||||||
|
qspi_ahb_enable();
|
||||||
|
qspi_irq_unlock(PRIMASK);
|
||||||
|
return to_bsp(RESULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t bsp_qspi_flash_size(void)
|
||||||
|
{
|
||||||
|
return g_s_flash_size;
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ add_executable(
|
||||||
src/protocol.c
|
src/protocol.c
|
||||||
src/test_runner.c
|
src/test_runner.c
|
||||||
src/tests/test_sdram.c
|
src/tests/test_sdram.c
|
||||||
|
src/tests/test_qspi.c
|
||||||
${BSP_GENERATED}/clock_config.c
|
${BSP_GENERATED}/clock_config.c
|
||||||
${BSP_STARTUP_FILE}
|
${BSP_STARTUP_FILE}
|
||||||
${BSP_SYSCALLS_FILE})
|
${BSP_SYSCALLS_FILE})
|
||||||
|
|
@ -17,14 +18,25 @@ add_executable(
|
||||||
target_include_directories(firmware_test PRIVATE src/)
|
target_include_directories(firmware_test PRIVATE src/)
|
||||||
|
|
||||||
target_compile_definitions(
|
target_compile_definitions(
|
||||||
${TARGET_NAME} PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512 BOARD_MPU_SDRAM=1)
|
${TARGET_NAME}
|
||||||
|
PRIVATE BSP_UART_HOST_RX_BUFFER_SIZE=512
|
||||||
|
BOARD_MPU_SDRAM=1
|
||||||
|
__STARTUP_INITIALIZE_RAMFUNCTION
|
||||||
|
__STARTUP_CLEAR_BSS)
|
||||||
#
|
#
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
# Зависимости — только то что нужно для входного контроля bsp_board транзитивно
|
||||||
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
|
# даёт: sdk_device, sdk_clock, sdk_common, CPU_MIMXRT1052CVJ5B, XIP_* дефайны
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
target_link_libraries(${TARGET_NAME} PRIVATE bsp_board bsp_led bsp_tick
|
target_link_libraries(
|
||||||
bsp_boot_xip bsp_usb_cdc bsp_sdram)
|
${TARGET_NAME}
|
||||||
|
PRIVATE bsp_board
|
||||||
|
bsp_led
|
||||||
|
bsp_tick
|
||||||
|
bsp_boot_xip
|
||||||
|
bsp_usb_cdc
|
||||||
|
bsp_sdram
|
||||||
|
bsp_qspi_flash)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# Linker script
|
# Linker script
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@
|
||||||
*/
|
*/
|
||||||
#include "board.h"
|
#include "board.h"
|
||||||
#include "bsp/led.h"
|
#include "bsp/led.h"
|
||||||
|
#include "bsp/qspi_flash.h"
|
||||||
#include "bsp/tick.h"
|
#include "bsp/tick.h"
|
||||||
#include "bsp/usb_cdc.h"
|
#include "bsp/usb_cdc.h"
|
||||||
#include "cli.h"
|
#include "cli.h"
|
||||||
|
|
@ -31,12 +32,19 @@
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
const uint32_t CONNECT_BLINK_MS = 200U;
|
const uint32_t CONNECT_BLINK_MS = 200U;
|
||||||
const uint32_t ERROR_BLINK_MS = 50;
|
const uint32_t ERROR_BLINK_MS = 250;
|
||||||
board_hw_init();
|
board_hw_init();
|
||||||
|
|
||||||
bsp_led_init();
|
bsp_led_init();
|
||||||
|
bsp_status_t status = bsp_qspi_init();
|
||||||
bsp_tick_init();
|
bsp_tick_init();
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
|
bsp_delay(ERROR_BLINK_MS);
|
||||||
|
}
|
||||||
|
#if 0
|
||||||
if (bsp_usb_cdc_init() != BSP_OK)
|
if (bsp_usb_cdc_init() != BSP_OK)
|
||||||
{
|
{
|
||||||
bsp_led_toggle(LED_HEARTBEAT);
|
bsp_led_toggle(LED_HEARTBEAT);
|
||||||
|
|
@ -62,4 +70,5 @@ int main(void)
|
||||||
cli_process();
|
cli_process();
|
||||||
test_runner_process();
|
test_runner_process();
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,12 @@
|
||||||
|
|
||||||
#ifndef UNIT_TEST
|
#ifndef UNIT_TEST
|
||||||
extern const test_module_t K_TEST_SDRAM;
|
extern const test_module_t K_TEST_SDRAM;
|
||||||
|
extern const test_module_t K_TEST_QSPI;
|
||||||
|
|
||||||
static const test_module_t *const k_registry[] = {
|
static const test_module_t *const k_registry[] = {
|
||||||
/* populated starting from Этап 2 */
|
/* populated starting from Этап 2 */
|
||||||
&K_TEST_SDRAM,
|
&K_TEST_SDRAM,
|
||||||
|
&K_TEST_QSPI,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))
|
#define REGISTRY_SIZE (sizeof(k_registry) / sizeof(k_registry[0]))
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
- [Как читать эту таблицу](#как-читать-эту-таблицу)
|
- [Как читать эту таблицу](#как-читать-эту-таблицу)
|
||||||
- [test_sdram — SDRAM 32 MB](#test_sdram--sdram-32-mb)
|
- [test_sdram — SDRAM 32 MB](#test_sdram--sdram-32-mb)
|
||||||
- [test_qspi — QSPI Flash 8 MB](#test_qspi--qspi-flash-8-mb) *(запланирован)*
|
- [test_qspi — QSPI Flash W25Qxx](#test_qspi--qspi-flash-w25qxx)
|
||||||
- [test_usd — uSD SDIO](#test_usd--usd-sdio) *(запланирован)*
|
- [test_usd — uSD SDIO](#test_usd--usd-sdio) *(запланирован)*
|
||||||
- [test_display — Display RGB888](#test_display--display-rgb888) *(запланирован)*
|
- [test_display — Display RGB888](#test_display--display-rgb888) *(запланирован)*
|
||||||
- [test_buttons — Test_But_1 / Test_But_2](#test_buttons--test_but_1--test_but_2) *(запланирован)*
|
- [test_buttons — Test_But_1 / Test_But_2](#test_buttons--test_but_1--test_but_2) *(запланирован)*
|
||||||
|
|
@ -186,9 +186,9 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## test_qspi — QSPI Flash 8 MB
|
## test_qspi — QSPI Flash W25Qxx
|
||||||
|
|
||||||
**Файл:** `test_qspi.c` *(не реализован — Этап 3)*
|
**Файл:** `test_qspi.c`
|
||||||
**ID:** `qspi`
|
**ID:** `qspi`
|
||||||
**Critical:** ✅ | **HIL:** ❌ | **Confirm:** —
|
**Critical:** ✅ | **HIL:** ❌ | **Confirm:** —
|
||||||
|
|
||||||
|
|
@ -196,22 +196,177 @@ Address pattern на 256 KB: запись → `flush_dcache` → ожидани
|
||||||
|
|
||||||
| Параметр | Значение |
|
| Параметр | Значение |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Чип | W25Q64FVSSIG |
|
| Чипы | W25Q64 / W25Q128 / W25Q256 / W25Q512 |
|
||||||
| Объём | 8 MB |
|
| Объём | 8 / 16 / 32 / 64 MB |
|
||||||
| Интерфейс | FlexSPI |
|
| Интерфейс MCU | FlexSPI1, порт A1 |
|
||||||
| BSP | `bsp_qspi` |
|
| BSP | `bsp_qspi` |
|
||||||
|
| Тестовый сектор | `flash_size - 4KB` (последний, динамически) |
|
||||||
|
|
||||||
### Что будет тестировать
|
### Инварианты выполнения
|
||||||
|
|
||||||
- JEDEC ID верификация: manufacturer=`0xEF`, device=`0x4017`.
|
- `bsp_qspi_init()` вызывается в `init()` — если чип не опознан, `run()` возвращает FAIL.
|
||||||
- Erase последнего сектора (4 KB, не затрагивает прошивку).
|
- Слот 0 FlexSPI LUT (XIP read, задан FDCB) **никогда не изменяется** — прошивка
|
||||||
- Write 256 байт → Read → Compare.
|
выполняется XIP на протяжении всего теста.
|
||||||
|
- Во время операций stирания/записи (~45–150 мс) Flash находится в состоянии BUSY.
|
||||||
|
W25Q64/128/256/512 допускают READ-команды в состоянии BUSY — XIP-фетчи инструкций
|
||||||
|
продолжают работать.
|
||||||
|
- Тестовый сектор расположен в конце Flash и **не пересекается** с прошивкой
|
||||||
|
(прошивка занимает первые несколько MB).
|
||||||
|
- `bsp_usb_cdc_poll()` вызывается вокруг каждой блокирующей операции — USB CDC
|
||||||
|
остаётся отзывчивым во время теста.
|
||||||
|
|
||||||
### Гарантии при PASS
|
### Что тестирует — четыре шага
|
||||||
|
|
||||||
- FlexSPI контроллер инициализирован.
|
#### Шаг 1: JEDEC ID (~1 мс)
|
||||||
- Чип отвечает с ожидаемым JEDEC ID.
|
|
||||||
- Базовые операции чтения/записи/стирания работают.
|
Команда 0x9F. Читает manufacturer ID и device ID.
|
||||||
|
|
||||||
|
**Проверяет:**
|
||||||
|
|
||||||
|
- `manufacturer_id == 0xEF` (Winbond).
|
||||||
|
- `capacity_byte` принадлежит одному из: 0x17 (64Mbit), 0x18 (128Mbit),
|
||||||
|
0x19 (256Mbit), 0x20 (512Mbit).
|
||||||
|
|
||||||
|
По результату вычисляется адрес тестового сектора:
|
||||||
|
`test_addr = bsp_qspi_flash_size() - BSP_QSPI_SECTOR_SIZE`.
|
||||||
|
|
||||||
|
**Ловит:**
|
||||||
|
|
||||||
|
- FlexSPI контроллер не инициализирован или не отвечает.
|
||||||
|
- Неизвестный / неподдерживаемый чип.
|
||||||
|
- Обрыв или неправильное подключение QSPI-шины.
|
||||||
|
|
||||||
|
**Не ловит:**
|
||||||
|
|
||||||
|
- Деградацию конкретных ячеек памяти.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Шаг 2: Erase + Verify (~500 мс)
|
||||||
|
|
||||||
|
Стирает тестовый сектор (4KB). Читает первые 256 байт сектора.
|
||||||
|
Все байты должны быть `0xFF`.
|
||||||
|
|
||||||
|
**Покрытие:** способность чипа выполнить Sector Erase и корректно сообщить о завершении.
|
||||||
|
|
||||||
|
**Ловит:**
|
||||||
|
|
||||||
|
- Erase command не принимается чипом (неправильный opcode / режим адресации).
|
||||||
|
- SR1.WIP не снимается после стирания (erase не завершился).
|
||||||
|
- Данные после стирания не `0xFF` (ячейки застряли в `0x00`).
|
||||||
|
|
||||||
|
**Не ловит:**
|
||||||
|
|
||||||
|
- Проблемы с ячейками вне первой страницы тестового сектора.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Шаг 3: Write + Read + Compare (~200 мс)
|
||||||
|
|
||||||
|
Паттерн `byte[i] = i & 0xFF` (256 байт). Записывает первую страницу
|
||||||
|
тестового сектора командой Quad Page Program. Читает обратно командой
|
||||||
|
IP Quad Output Read (слот 11). Сравнивает побайтово.
|
||||||
|
|
||||||
|
**Покрытие:** полный цикл write → read на 256 байтах.
|
||||||
|
|
||||||
|
**Ловит:**
|
||||||
|
|
||||||
|
- Ошибки Page Program (неправильный opcode / адрес / данные).
|
||||||
|
- Ошибки IP Read (неправильный opcode / адрес).
|
||||||
|
- Stuck-at bits в ячейках тестовой страницы.
|
||||||
|
- Шина данных Flash (все 4 линии Quad-режима).
|
||||||
|
|
||||||
|
**Не ловит:**
|
||||||
|
|
||||||
|
- Деградацию ячеек вне первой страницы тестового сектора.
|
||||||
|
- Aliasing адресов (это проверяет Шаг 4).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### Шаг 4: Address range — только W25Q256/512 (~100 мс)
|
||||||
|
|
||||||
|
Для чипов с `flash_size > 16 MB`.
|
||||||
|
Для W25Q64/128 шаг пропускается (24-bit покрывает весь чип).
|
||||||
|
|
||||||
|
**Проблема, которую ловит:**
|
||||||
|
|
||||||
|
При 24-bit адресации адрес обрезается до 24 бит. Для W25Q256/512
|
||||||
|
last-sector находится выше 16 MB:
|
||||||
|
|
||||||
|
```
|
||||||
|
W25Q256: last sector = 0x1FFF000, 0x1FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
|
W25Q512: last sector = 0x3FFF000, 0x3FFF000 & 0xFFFFFF = 0xFFF000
|
||||||
|
```
|
||||||
|
|
||||||
|
Если dedicated 4-byte opcodes не работают (чип игнорирует старший байт
|
||||||
|
адреса), Шаг 3 **формально проходит** — запись и чтение ошибаются в одно и
|
||||||
|
то же место (`0xFFF000`) одинаково. Алиасирование остаётся незамеченным.
|
||||||
|
|
||||||
|
**Метод:**
|
||||||
|
|
||||||
|
После Шага 3 `test_addr` содержит паттерн `i & 0xFF`. Записываем `0xAA`
|
||||||
|
в anchor-сектор (`0x00FFF000`). Читаем `test_addr`:
|
||||||
|
|
||||||
|
- Получаем `i & 0xFF` → `test_addr` — это физически другой сектор → `PASS`.
|
||||||
|
- Получаем `0xAA` → `test_addr` физически совпадает с anchor → алиасирование
|
||||||
|
подтверждено → dedicated 4-byte opcodes не работают → `FAIL`.
|
||||||
|
|
||||||
|
**Ловит:**
|
||||||
|
|
||||||
|
- 24-bit алиасирование адресов для W25Q256/512.
|
||||||
|
- Несовместимость LUT dedicated 4-byte opcodes с конкретным чипом.
|
||||||
|
|
||||||
|
**Не ловит:**
|
||||||
|
|
||||||
|
- Алиасирование на W25Q64/128 (невозможно по архитектуре).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Гарантии теста при PASS
|
||||||
|
|
||||||
|
- FlexSPI1 инициализирован и отвечает.
|
||||||
|
- Подключён поддерживаемый Winbond Flash с ожидаемым JEDEC ID.
|
||||||
|
- Команды Sector Erase, Quad Page Program, IP Read работают корректно.
|
||||||
|
- Для W25Q256/512: dedicated 4-byte address opcodes функционируют и
|
||||||
|
адресуют физически разные секторы выше и ниже 16 MB.
|
||||||
|
|
||||||
|
### Интерпретация FAIL
|
||||||
|
|
||||||
|
`detail` содержит текстовое описание шага и причины:
|
||||||
|
|
||||||
|
| Паттерн `detail` | Шаг | Вероятный диагноз |
|
||||||
|
|---|---|---|
|
||||||
|
| `JEDEC: read failed` | 1 | FlexSPI не инициализирован / нет связи |
|
||||||
|
| `JEDEC: mfr=0xXX exp=0xEF` | 1 | Неизвестный производитель чипа |
|
||||||
|
| `JEDEC: unknown cap=0xXX` | 1 | Незнакомая ёмкость, чип не в списке поддержки |
|
||||||
|
| `erase: sector erase failed` | 2 | FlexSPI ошибка при erase command |
|
||||||
|
| `erase verify failed at 0x…` | 2 | Сектор не стёрся, ячейки застряли |
|
||||||
|
| `rw: page write failed` | 3 | FlexSPI ошибка при Page Program |
|
||||||
|
| `rw mismatch at 0x… exp=… got=…` | 3 | Stuck-at bit или ошибка шины данных |
|
||||||
|
| `addr alias: 0x… mirrors 0x…` | 4 | Алиасирование — 4-byte opcodes не работают |
|
||||||
|
| `addr range mismatch at 0x…` | 4 | Неожиданные данные при чтении last sector |
|
||||||
|
|
||||||
|
**Анализ `rw mismatch`:** `exp XOR got` — биты, где `(exp ^ got) != 0`,
|
||||||
|
соответствуют сбойным линиям Quad-шины.
|
||||||
|
|
||||||
|
### Ограничения
|
||||||
|
|
||||||
|
- Тестирует только **1 страницу** (256 байт) — не покрывает весь объём чипа.
|
||||||
|
- Не проверяет endurance (многократные write/erase циклы).
|
||||||
|
- Не является заменой полного тестирования Flash (march-алгоритмы).
|
||||||
|
- Шаг 4 не проверяет адреса в диапазоне 16–32 MB (только крайние точки).
|
||||||
|
|
||||||
|
### Типичное время выполнения
|
||||||
|
|
||||||
|
| Шаг | Чип | ~Время |
|
||||||
|
|---|---|---|
|
||||||
|
| 1: JEDEC ID | все | < 1 мс |
|
||||||
|
| 2: Erase + Verify | все | ~500 мс |
|
||||||
|
| 3: Write + Read | все | ~200 мс |
|
||||||
|
| 4: Address range | W25Q64/128 | 0 мс (skip) |
|
||||||
|
| 4: Address range | W25Q256/512 | ~100 мс |
|
||||||
|
| **Итого** | W25Q64/128 | **~700 мс** |
|
||||||
|
| **Итого** | W25Q256/512 | **~800 мс** |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -415,4 +570,4 @@ static const test_module_t *const k_registry[] = {
|
||||||
```
|
```
|
||||||
|
|
||||||
Critical тесты идут первыми — при их провале HIL и интерактивные тесты
|
Critical тесты идут первыми — при их провале HIL и интерактивные тесты
|
||||||
пропускаются автоматически, экономя время диагностики.
|
пропускаются автоматически, экономя время диагностики.
|
||||||
390
firmware/test/src/tests/test_qspi.c
Normal file
390
firmware/test/src/tests/test_qspi.c
Normal file
|
|
@ -0,0 +1,390 @@
|
||||||
|
/**
|
||||||
|
* @file test_qspi.c
|
||||||
|
* @brief Тест-модуль firmware_test: QSPI Flash W25Q64/128/256/512.
|
||||||
|
*
|
||||||
|
* Четыре шага:
|
||||||
|
*
|
||||||
|
* Шаг 1 — JEDEC ID (~1 мс)
|
||||||
|
* Команда 0x9F. Проверяем manufacturer=0xEF (Winbond) и что capacity
|
||||||
|
* byte принадлежит известному чипу. По результату вычисляем адрес
|
||||||
|
* тестового сектора (последний 4KB в Flash) и размер чипа.
|
||||||
|
*
|
||||||
|
* Шаг 2 — Erase + Verify (~500 мс)
|
||||||
|
* Стираем последний сектор. Читаем 256 байт, все должны быть 0xFF.
|
||||||
|
*
|
||||||
|
* Шаг 3 — Write + Read + Compare (~200 мс)
|
||||||
|
* Паттерн: byte[i] = i & 0xFF, 256 байт. Пишем на первую страницу
|
||||||
|
* тестового сектора. Читаем и сравниваем побайтово.
|
||||||
|
*
|
||||||
|
* Шаг 4 — Address range (только W25Q256/512, ~100 мс)
|
||||||
|
* Детектирует алиасирование адресов при неработающих dedicated 4-byte
|
||||||
|
* opcodes. Пишем 0xAA в anchor сектор (0x00FFF000), читаем test_addr.
|
||||||
|
* Если читаем 0xAA — test_addr физически алиасирует на anchor_addr —
|
||||||
|
* IP-команды работают в 3-byte режиме вместо 4-byte → FAIL.
|
||||||
|
*
|
||||||
|
* Тестовый сектор = bsp_qspi_flash_size() - BSP_QSPI_SECTOR_SIZE.
|
||||||
|
* Адрес вычисляется после Шага 1 динамически — поддерживает все чипы.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "bsp/qspi_flash.h"
|
||||||
|
#include "bsp/usb_cdc.h"
|
||||||
|
#include "test_module.h"
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/* ── Константы ─────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/** @brief Число байт верификации после erase (первая страница сектора). */
|
||||||
|
#define QSPI_ERASE_VERIFY_LEN BSP_QSPI_PAGE_SIZE
|
||||||
|
|
||||||
|
/** @brief Ожидаемый байт в стёртом секторе. */
|
||||||
|
#define QSPI_ERASED_BYTE 0xFFU
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Граница 16 MB — максимум 24-bit адресного пространства.
|
||||||
|
*
|
||||||
|
* Для чипов с flash_size > 16 MB (W25Q256/512) проверяется алиасирование.
|
||||||
|
*/
|
||||||
|
#define QSPI_ADDR_16MB 0x01000000UL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Адрес anchor-сектора для проверки алиасирования.
|
||||||
|
*
|
||||||
|
* При 24-bit алиасировании:
|
||||||
|
* W25Q256: 0x1FFF000 & 0xFFFFFF = 0xFFF000 ← этот адрес
|
||||||
|
* W25Q512: 0x3FFF000 & 0xFFFFFF = 0xFFF000 ← тот же адрес
|
||||||
|
*
|
||||||
|
* Если dedicated 4-byte opcodes не работают, запись/чтение last-sector
|
||||||
|
* фактически попадает на этот адрес.
|
||||||
|
*/
|
||||||
|
#define QSPI_ALIAS_ANCHOR_ADDR 0x00FFF000UL
|
||||||
|
|
||||||
|
/** @brief Паттерн anchor-страницы. Отличается от основного (i & 0xFF). */
|
||||||
|
#define QSPI_ANCHOR_PATTERN 0xAAU
|
||||||
|
|
||||||
|
/* ── Буферы ─────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static uint8_t g_s_write_buf[BSP_QSPI_PAGE_SIZE]; /**< Данные для записи. */
|
||||||
|
static uint8_t g_s_read_buf[BSP_QSPI_PAGE_SIZE]; /**< Данные после чтения. */
|
||||||
|
static uint8_t g_s_anchor_buf[BSP_QSPI_PAGE_SIZE]; /**< Anchor для Шага 4. */
|
||||||
|
|
||||||
|
/* ── Состояние модуля ───────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static bool g_s_ready = false;
|
||||||
|
|
||||||
|
/* ── Вспомогательные функции ────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static test_result_t make_fail(const char *p_detail)
|
||||||
|
{
|
||||||
|
test_result_t result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U };
|
||||||
|
(void) snprintf(result.detail, TEST_DETAIL_SIZE, "%s", p_detail);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Шаг 1: JEDEC ID ────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Читает JEDEC ID, верифицирует производителя и chip capacity.
|
||||||
|
*
|
||||||
|
* @param[out] p_test_addr Адрес тестового сектора (последний 4KB).
|
||||||
|
* @param[out] p_result FAIL-результат при ошибке.
|
||||||
|
* @return true при успехе.
|
||||||
|
*/
|
||||||
|
static bool step_jedec(uint32_t *p_test_addr, test_result_t *p_result)
|
||||||
|
{
|
||||||
|
bsp_qspi_jedec_t jedec = { .manufacturer_id = 0U, .device_id = 0U };
|
||||||
|
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
if (bsp_qspi_read_jedec_id(&jedec) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("JEDEC: read failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (jedec.manufacturer_id != BSP_QSPI_MFR_WINBOND)
|
||||||
|
{
|
||||||
|
char detail[TEST_DETAIL_SIZE];
|
||||||
|
(void) snprintf(detail, sizeof(detail), "JEDEC: mfr=0x%02X exp=0x%02X",
|
||||||
|
(unsigned int) jedec.manufacturer_id, (unsigned int) BSP_QSPI_MFR_WINBOND);
|
||||||
|
*p_result = make_fail(detail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t CAP = (uint8_t) (jedec.device_id & 0xFFU);
|
||||||
|
|
||||||
|
switch (CAP)
|
||||||
|
{
|
||||||
|
case BSP_QSPI_CAP_64MBIT:
|
||||||
|
case BSP_QSPI_CAP_128MBIT:
|
||||||
|
case BSP_QSPI_CAP_256MBIT:
|
||||||
|
case BSP_QSPI_CAP_512MBIT:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
char detail[TEST_DETAIL_SIZE];
|
||||||
|
(void) snprintf(detail, sizeof(detail), "JEDEC: unknown cap=0x%02X dev=0x%04X",
|
||||||
|
(unsigned int) CAP, (unsigned int) jedec.device_id);
|
||||||
|
*p_result = make_fail(detail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*p_test_addr = bsp_qspi_flash_size() - BSP_QSPI_SECTOR_SIZE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Шаг 2: Erase + Verify ──────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Стирает тестовый сектор и верифицирует (все байты = 0xFF).
|
||||||
|
*
|
||||||
|
* @param[in] test_addr Адрес тестового сектора.
|
||||||
|
* @param[out] p_result FAIL-результат при ошибке.
|
||||||
|
* @return true при успехе.
|
||||||
|
*/
|
||||||
|
static bool step_erase(uint32_t test_addr, test_result_t *p_result)
|
||||||
|
{
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
if (bsp_qspi_erase_sector(test_addr) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("erase: sector erase failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
(void) memset(g_s_read_buf, 0x00, sizeof(g_s_read_buf));
|
||||||
|
|
||||||
|
if (bsp_qspi_read(test_addr, g_s_read_buf, QSPI_ERASE_VERIFY_LEN) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("erase verify: read failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
for (uint32_t i = 0U; i < QSPI_ERASE_VERIFY_LEN; i++)
|
||||||
|
{
|
||||||
|
if (g_s_read_buf[i] != QSPI_ERASED_BYTE)
|
||||||
|
{
|
||||||
|
char detail[TEST_DETAIL_SIZE];
|
||||||
|
(void) snprintf(detail, sizeof(detail),
|
||||||
|
"erase verify failed at 0x%08" PRIX32 " got=0x%02X", test_addr + i,
|
||||||
|
(unsigned int) g_s_read_buf[i]);
|
||||||
|
*p_result = make_fail(detail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Шаг 3: Write + Read + Compare ─────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Пишет паттерн (i & 0xFF) и верифицирует побайтово.
|
||||||
|
*
|
||||||
|
* @param[in] test_addr Адрес тестового сектора.
|
||||||
|
* @param[out] p_result FAIL-результат при ошибке.
|
||||||
|
* @return true при успехе.
|
||||||
|
*/
|
||||||
|
static bool step_write_read(uint32_t test_addr, test_result_t *p_result)
|
||||||
|
{
|
||||||
|
for (uint32_t i = 0U; i < BSP_QSPI_PAGE_SIZE; i++)
|
||||||
|
{
|
||||||
|
g_s_write_buf[i] = (uint8_t) (i & 0xFFU);
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
if (bsp_qspi_write_page(test_addr, g_s_write_buf) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("rw: page write failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
(void) memset(g_s_read_buf, 0x00, sizeof(g_s_read_buf));
|
||||||
|
|
||||||
|
if (bsp_qspi_read(test_addr, g_s_read_buf, BSP_QSPI_PAGE_SIZE) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("rw: page read failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
for (uint32_t i = 0U; i < BSP_QSPI_PAGE_SIZE; i++)
|
||||||
|
{
|
||||||
|
if (g_s_read_buf[i] != g_s_write_buf[i])
|
||||||
|
{
|
||||||
|
char detail[TEST_DETAIL_SIZE];
|
||||||
|
(void) snprintf(detail, sizeof(detail),
|
||||||
|
"rw mismatch at 0x%08" PRIX32 " exp=0x%02X got=0x%02X", test_addr + i,
|
||||||
|
(unsigned int) g_s_write_buf[i], (unsigned int) g_s_read_buf[i]);
|
||||||
|
*p_result = make_fail(detail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Шаг 4: Address range ───────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Верифицирует отсутствие адресного алиасирования (W25Q256/512).
|
||||||
|
*
|
||||||
|
* После step_write_read: test_addr содержит паттерн i & 0xFF.
|
||||||
|
* Записываем 0xAA в anchor_addr (0x00FFF000). Если test_addr физически
|
||||||
|
* совпадает с anchor_addr (24-bit wrap), чтение test_addr вернёт 0xAA.
|
||||||
|
*
|
||||||
|
* Для W25Q64/128 (flash_size ≤ 16 MB): шаг пропускается.
|
||||||
|
*
|
||||||
|
* @param[in] test_addr Адрес тестового сектора.
|
||||||
|
* @param[in] flash_size Полный размер Flash.
|
||||||
|
* @param[out] p_result FAIL-результат при ошибке.
|
||||||
|
* @return true если адресация верна или тест неприменим.
|
||||||
|
*/
|
||||||
|
static bool step_address_range(uint32_t test_addr, uint32_t flash_size, test_result_t *p_result)
|
||||||
|
{
|
||||||
|
if (flash_size <= QSPI_ADDR_16MB)
|
||||||
|
{
|
||||||
|
return true; /* W25Q64/128: 24-bit покрывает весь чип */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Шаг A: стереть anchor сектор */
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
if (bsp_qspi_erase_sector(QSPI_ALIAS_ANCHOR_ADDR) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("addr range: anchor erase failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
/* Шаг B: записать 0xAA на anchor_addr */
|
||||||
|
(void) memset(g_s_anchor_buf, QSPI_ANCHOR_PATTERN, sizeof(g_s_anchor_buf));
|
||||||
|
if (bsp_qspi_write_page(QSPI_ALIAS_ANCHOR_ADDR, g_s_anchor_buf) != BSP_OK)
|
||||||
|
{
|
||||||
|
*p_result = make_fail("addr range: anchor write failed");
|
||||||
|
(void) bsp_qspi_erase_sector(QSPI_ALIAS_ANCHOR_ADDR);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
/* Шаг C: прочитать test_addr — должен содержать паттерн i & 0xFF */
|
||||||
|
(void) memset(g_s_read_buf, 0x00, sizeof(g_s_read_buf));
|
||||||
|
if (bsp_qspi_read(test_addr, g_s_read_buf, BSP_QSPI_PAGE_SIZE) != BSP_OK)
|
||||||
|
{
|
||||||
|
(void) bsp_qspi_erase_sector(QSPI_ALIAS_ANCHOR_ADDR);
|
||||||
|
*p_result = make_fail("addr range: high addr read failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
/* Шаг D: верификация */
|
||||||
|
bool ok = true;
|
||||||
|
char detail[TEST_DETAIL_SIZE];
|
||||||
|
detail[0U] = '\0';
|
||||||
|
|
||||||
|
for (uint32_t i = 0U; i < BSP_QSPI_PAGE_SIZE; i++)
|
||||||
|
{
|
||||||
|
const uint8_t EXPECTED = (uint8_t) (i & 0xFFU);
|
||||||
|
const uint8_t GOT = g_s_read_buf[i];
|
||||||
|
|
||||||
|
if (GOT != EXPECTED)
|
||||||
|
{
|
||||||
|
if (GOT == QSPI_ANCHOR_PATTERN)
|
||||||
|
{
|
||||||
|
(void) snprintf(detail, sizeof(detail),
|
||||||
|
"addr alias: 0x%08" PRIX32 " mirrors 0x%08" PRIX32 " (3-byte wrap)",
|
||||||
|
test_addr, QSPI_ALIAS_ANCHOR_ADDR);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(void) snprintf(detail, sizeof(detail),
|
||||||
|
"addr range mismatch at 0x%08" PRIX32 " exp=0x%02X got=0x%02X",
|
||||||
|
test_addr + i, (unsigned int) EXPECTED, (unsigned int) GOT);
|
||||||
|
}
|
||||||
|
ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Шаг E: cleanup anchor */
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
(void) bsp_qspi_erase_sector(QSPI_ALIAS_ANCHOR_ADDR);
|
||||||
|
bsp_usb_cdc_poll();
|
||||||
|
|
||||||
|
if (!ok)
|
||||||
|
{
|
||||||
|
*p_result = make_fail(detail);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Реализация тест-модуля ─────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
static void qspi_test_init(void)
|
||||||
|
{
|
||||||
|
g_s_ready = (bsp_qspi_init() == BSP_OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
static test_result_t qspi_test_run(void)
|
||||||
|
{
|
||||||
|
if (!g_s_ready)
|
||||||
|
{
|
||||||
|
return make_fail("init failed — FlexSPI or unknown chip");
|
||||||
|
}
|
||||||
|
|
||||||
|
test_result_t fail_result = { .status = TEST_STATUS_FAIL, .duration_ms = 0U };
|
||||||
|
uint32_t test_addr = 0U;
|
||||||
|
|
||||||
|
if (!step_jedec(&test_addr, &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
if (!step_erase(test_addr, &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
if (!step_write_read(test_addr, &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
if (!step_address_range(test_addr, bsp_qspi_flash_size(), &fail_result))
|
||||||
|
{
|
||||||
|
return fail_result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (test_result_t){
|
||||||
|
.status = TEST_STATUS_PASS,
|
||||||
|
.duration_ms = 0U,
|
||||||
|
.detail = { 0 },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static void qspi_test_deinit(void)
|
||||||
|
{
|
||||||
|
g_s_ready = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Дескриптор модуля ──────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
const test_module_t K_TEST_QSPI = {
|
||||||
|
.id = "qspi",
|
||||||
|
.name = "QSPI Flash W25Qxx",
|
||||||
|
.critical = true,
|
||||||
|
.requires_hil = false,
|
||||||
|
.pre_confirm_prompt = NULL,
|
||||||
|
.init = qspi_test_init,
|
||||||
|
.run = qspi_test_run,
|
||||||
|
.deinit = qspi_test_deinit,
|
||||||
|
};
|
||||||
Loading…
Reference in a new issue