Files
templates/c/spi-nor/Src/spi_nor.c

473 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 "spi_nor.h"
#include <string.h>
/* Стандартные команды одинаковы для проверенных W25Q и SST25. */
#define SPI_NOR_COMMAND_WRITE_ENABLE 0x06U
#define SPI_NOR_COMMAND_READ_STATUS 0x05U
#define SPI_NOR_COMMAND_READ_DATA 0x03U
#define SPI_NOR_COMMAND_PAGE_PROGRAM 0x02U
#define SPI_NOR_COMMAND_SECTOR_ERASE 0x20U
#define SPI_NOR_COMMAND_READ_JEDEC_ID 0x9FU
#define SPI_NOR_COMMAND_RELEASE_POWER_DOWN 0xABU
#define SPI_NOR_STATUS_BUSY 0x01U
#define SPI_NOR_SECTOR_SIZE 4096UL
#define SPI_NOR_MAX_PAGE_SIZE 256U
#define SPI_NOR_VERIFY_CHUNK_SIZE 32U
#define SPI_NOR_24BIT_CAPACITY_LIMIT 0x01000000UL
/* Преобразует результат физического обмена в ошибку протокольного слоя. */
static spi_nor_status_t map_io_status(spi_nor_io_status_t status)
{
if (status == SPI_NOR_IO_OK) {
return SPI_NOR_OK;
}
if (status == SPI_NOR_IO_TIMEOUT) {
return SPI_NOR_E_TIMEOUT;
}
return SPI_NOR_E_IO;
}
/* Захватывает весь составной вызов, чтобы другой поток не вклинился после WREN. */
static spi_nor_status_t begin_operation(spi_nor_t *device)
{
spi_nor_io_status_t lock_status;
if ((device == NULL) || (device->initialized == 0U)) {
return SPI_NOR_E_ARGUMENT;
}
/* Рекурсивный вызов отклоняется до mutex, чтобы не ждать самого себя. */
if (device->busy != 0U) {
return SPI_NOR_E_BUSY;
}
if (device->io.lock != NULL) {
lock_status = device->io.lock(device->io.context,
device->config.lock_timeout_ms);
if (lock_status != SPI_NOR_IO_OK) {
return (lock_status == SPI_NOR_IO_TIMEOUT) ?
SPI_NOR_E_TIMEOUT : SPI_NOR_E_BUSY;
}
}
if (device->busy != 0U) {
if (device->io.unlock != NULL) {
device->io.unlock(device->io.context);
}
return SPI_NOR_E_BUSY;
}
device->busy = 1U;
return SPI_NOR_OK;
}
/* Всегда освобождает как локальный флаг, так и необязательный mutex платформы. */
static void end_operation(spi_nor_t *device)
{
device->busy = 0U;
if (device->io.unlock != NULL) {
device->io.unlock(device->io.context);
}
}
/* Обмен выполняется только при активном CS, а ошибка не оставляет CS в нуле. */
static spi_nor_status_t transaction(spi_nor_t *device,
const uint8_t *command,
size_t command_size,
const uint8_t *tx_data,
uint8_t *rx_data,
size_t data_size)
{
spi_nor_io_status_t io_status;
device->io.chip_select(device->io.context, 1U);
io_status = device->io.transfer(device->io.context, command, NULL,
command_size,
device->config.io_timeout_ms);
if ((io_status == SPI_NOR_IO_OK) && (data_size != 0U)) {
io_status = device->io.transfer(device->io.context, tx_data, rx_data,
data_size,
device->config.io_timeout_ms);
}
device->io.chip_select(device->io.context, 0U);
return map_io_status(io_status);
}
/* Формирует opcode и 24-битный big-endian адрес независимо от endian CPU. */
static void make_address_command(uint8_t command[4], uint8_t opcode,
uint32_t address)
{
command[0] = opcode;
command[1] = (uint8_t)(address >> 16U);
command[2] = (uint8_t)(address >> 8U);
command[3] = (uint8_t)address;
}
/* Проверяет весь полуоткрытый диапазон без переполнения address + size. */
static spi_nor_status_t validate_range(const spi_nor_t *device,
uint32_t address,
size_t size)
{
if ((device->detected == 0U) || (size == 0U)) {
return SPI_NOR_E_ARGUMENT;
}
if ((address >= device->info.capacity_bytes) ||
(size > (size_t)(device->info.capacity_bytes - address)) ||
(device->info.capacity_bytes > SPI_NOR_24BIT_CAPACITY_LIMIT)) {
return SPI_NOR_E_BOUNDS;
}
return SPI_NOR_OK;
}
/* Читает регистр состояния в отдельной законченной SPI-транзакции. */
static spi_nor_status_t read_status(spi_nor_t *device, uint8_t *status)
{
uint8_t command = SPI_NOR_COMMAND_READ_STATUS;
return transaction(device, &command, 1U, NULL, status, 1U);
}
/* Публичное чтение статуса не допускает пересечения с program/erase/read. */
spi_nor_status_t spi_nor_read_status_register(spi_nor_t *device,
uint8_t *status)
{
spi_nor_status_t result;
if ((device == NULL) || (status == NULL) || (device->detected == 0U)) {
return SPI_NOR_E_ARGUMENT;
}
result = begin_operation(device);
if (result != SPI_NOR_OK) {
return result;
}
result = read_status(device, status);
end_operation(device);
return result;
}
/* Ожидает снятия BUSY с wrap-safe арифметикой tick и конечным тайм-аутом. */
static spi_nor_status_t wait_ready(spi_nor_t *device, uint32_t timeout_ms)
{
uint32_t started_ms = device->io.tick_ms(device->io.context);
uint8_t status = SPI_NOR_STATUS_BUSY;
spi_nor_status_t result;
while ((status & SPI_NOR_STATUS_BUSY) != 0U) {
result = read_status(device, &status);
if (result != SPI_NOR_OK) {
return result;
}
if ((status & SPI_NOR_STATUS_BUSY) == 0U) {
return SPI_NOR_OK;
}
if ((uint32_t)(device->io.tick_ms(device->io.context) - started_ms) >=
timeout_ms) {
return SPI_NOR_E_TIMEOUT;
}
if (device->config.ready_poll_delay_ms != 0U) {
device->io.delay_ms(device->io.context,
device->config.ready_poll_delay_ms);
}
}
return SPI_NOR_OK;
}
/* Перед каждой изменяющей командой устанавливает volatile Write Enable Latch. */
static spi_nor_status_t write_enable(spi_nor_t *device)
{
uint8_t command = SPI_NOR_COMMAND_WRITE_ENABLE;
return transaction(device, &command, 1U, NULL, NULL, 0U);
}
/* Внутреннее чтение не захватывает mutex повторно и используется verify-кодом. */
static spi_nor_status_t raw_read(spi_nor_t *device, uint32_t address,
void *data, size_t size)
{
uint8_t command[4];
make_address_command(command, SPI_NOR_COMMAND_READ_DATA, address);
return transaction(device, command, sizeof(command), NULL, data, size);
}
/* Программирует один page/byte chunk и немедленно проверяет его чтением. */
static spi_nor_status_t program_chunk(spi_nor_t *device,
uint32_t address,
const uint8_t *data,
size_t size)
{
uint8_t command[4];
uint8_t verify[SPI_NOR_MAX_PAGE_SIZE];
spi_nor_status_t result;
result = write_enable(device);
if (result != SPI_NOR_OK) {
return result;
}
make_address_command(command, SPI_NOR_COMMAND_PAGE_PROGRAM, address);
result = transaction(device, command, sizeof(command), data, NULL, size);
if (result != SPI_NOR_OK) {
return result;
}
result = wait_ready(device, device->config.program_timeout_ms);
if (result != SPI_NOR_OK) {
return result;
}
result = raw_read(device, address, verify, size);
if (result != SPI_NOR_OK) {
return result;
}
return (memcmp(verify, data, size) == 0) ?
SPI_NOR_OK : SPI_NOR_E_VERIFY;
}
/* Проверяет каждый байт сектора после erase, а не только первый word. */
static spi_nor_status_t verify_erased_sector(spi_nor_t *device,
uint32_t address)
{
uint8_t verify[SPI_NOR_VERIFY_CHUNK_SIZE];
uint32_t offset;
size_t index;
spi_nor_status_t result;
for (offset = 0U; offset < SPI_NOR_SECTOR_SIZE;
offset += sizeof(verify)) {
result = raw_read(device, address + offset, verify, sizeof(verify));
if (result != SPI_NOR_OK) {
return result;
}
for (index = 0U; index < sizeof(verify); ++index) {
if (verify[index] != 0xFFU) {
return SPI_NOR_E_VERIFY;
}
}
}
return SPI_NOR_OK;
}
/* Сопоставляет только проверенные JEDEC; неизвестную ёмкость не угадывает. */
static spi_nor_status_t identify_device(spi_nor_t *device)
{
const uint8_t manufacturer = device->info.manufacturer_id;
const uint8_t memory_type = device->info.memory_type;
const uint8_t capacity = device->info.capacity_code;
device->info.sector_size = SPI_NOR_SECTOR_SIZE;
if ((manufacturer == 0xEFU) && (memory_type == 0x40U) &&
(capacity == 0x17U)) {
device->info.model = SPI_NOR_MODEL_W25Q64;
device->info.capacity_bytes = 8UL * 1024UL * 1024UL;
device->info.page_size = 256U;
return SPI_NOR_OK;
}
if ((manufacturer == 0xEFU) && (memory_type == 0x40U) &&
(capacity == 0x18U)) {
device->info.model = SPI_NOR_MODEL_W25Q128;
device->info.capacity_bytes = 16UL * 1024UL * 1024UL;
device->info.page_size = 256U;
return SPI_NOR_OK;
}
if ((manufacturer == 0xBFU) && (memory_type == 0x25U) &&
(capacity == 0x41U)) {
device->info.model = SPI_NOR_MODEL_SST25VF016B;
device->info.capacity_bytes = 2UL * 1024UL * 1024UL;
/* Byte Program 0x02 не требует SST AAI sequencing. */
device->info.page_size = 1U;
return SPI_NOR_OK;
}
if (((manufacturer == 0x00U) && (memory_type == 0x00U) &&
(capacity == 0x00U)) ||
((manufacturer == 0xFFU) && (memory_type == 0xFFU) &&
(capacity == 0xFFU))) {
return SPI_NOR_E_NOT_FOUND;
}
return SPI_NOR_E_UNSUPPORTED;
}
/* Публичные defaults сохраняют тайм-ауты исходного HAL-драйвера. */
spi_nor_config_t spi_nor_default_config(void)
{
spi_nor_config_t config;
config.io_timeout_ms = 100U;
config.lock_timeout_ms = 100U;
config.program_timeout_ms = 1000U;
config.erase_timeout_ms = 5000U;
config.ready_poll_delay_ms = 1U;
return config;
}
/* Проверяет обязательные callbacks и сохраняет независимую копию контракта. */
spi_nor_status_t spi_nor_init(spi_nor_t *device, const spi_nor_io_t *io,
const spi_nor_config_t *config)
{
if ((device == NULL) || (io == NULL) || (io->transfer == NULL) ||
(io->chip_select == NULL) || (io->tick_ms == NULL) ||
(io->delay_ms == NULL) ||
((io->lock == NULL) != (io->unlock == NULL))) {
return SPI_NOR_E_ARGUMENT;
}
memset(device, 0, sizeof(*device));
device->io = *io;
device->config = (config != NULL) ? *config : spi_nor_default_config();
if ((device->config.io_timeout_ms == 0U) ||
(device->config.program_timeout_ms == 0U) ||
(device->config.erase_timeout_ms == 0U)) {
memset(device, 0, sizeof(*device));
return SPI_NOR_E_ARGUMENT;
}
device->io.chip_select(device->io.context, 0U);
device->initialized = 1U;
return SPI_NOR_OK;
}
/* Будит устройство и сохраняет raw JEDEC даже при неизвестной модели. */
spi_nor_status_t spi_nor_probe(spi_nor_t *device)
{
uint8_t command = SPI_NOR_COMMAND_RELEASE_POWER_DOWN;
uint8_t jedec[3] = {0U, 0U, 0U};
spi_nor_status_t result;
result = begin_operation(device);
if (result != SPI_NOR_OK) {
return result;
}
device->detected = 0U;
memset(&device->info, 0, sizeof(device->info));
result = transaction(device, &command, 1U, NULL, NULL, 0U);
if (result == SPI_NOR_OK) {
device->io.delay_ms(device->io.context, 1U);
command = SPI_NOR_COMMAND_READ_JEDEC_ID;
result = transaction(device, &command, 1U, NULL, jedec,
sizeof(jedec));
}
if (result == SPI_NOR_OK) {
device->info.manufacturer_id = jedec[0];
device->info.memory_type = jedec[1];
device->info.capacity_code = jedec[2];
result = identify_device(device);
if (result == SPI_NOR_OK) {
device->detected = 1U;
}
}
end_operation(device);
return result;
}
/* Чтение проверяет адрес до активации CS и не использует внутренний RAM-кэш. */
spi_nor_status_t spi_nor_read(spi_nor_t *device, uint32_t address,
void *data, size_t size)
{
spi_nor_status_t result;
if (data == NULL) {
return SPI_NOR_E_ARGUMENT;
}
result = begin_operation(device);
if (result != SPI_NOR_OK) {
return result;
}
result = validate_range(device, address, size);
if (result == SPI_NOR_OK) {
result = raw_read(device, address, data, size);
}
end_operation(device);
return result;
}
/* Запись разбивается по границам страниц; SST получает byte-program chunks. */
spi_nor_status_t spi_nor_program(spi_nor_t *device, uint32_t address,
const void *data, size_t size)
{
const uint8_t *source = data;
size_t remaining = size;
spi_nor_status_t result;
if (data == NULL) {
return SPI_NOR_E_ARGUMENT;
}
result = begin_operation(device);
if (result != SPI_NOR_OK) {
return result;
}
result = validate_range(device, address, size);
while ((result == SPI_NOR_OK) && (remaining != 0U)) {
size_t page_remaining = device->info.page_size -
(address % device->info.page_size);
size_t chunk = (remaining < page_remaining) ?
remaining : page_remaining;
result = program_chunk(device, address, source, chunk);
address += (uint32_t)chunk;
source += chunk;
remaining -= chunk;
}
end_operation(device);
return result;
}
/* Erase принимает только целые выровненные 4-КиБ секторы в пределах памяти. */
spi_nor_status_t spi_nor_erase(spi_nor_t *device, uint32_t address,
size_t size)
{
size_t remaining = size;
spi_nor_status_t result;
result = begin_operation(device);
if (result != SPI_NOR_OK) {
return result;
}
if (((address % SPI_NOR_SECTOR_SIZE) != 0U) ||
((size % SPI_NOR_SECTOR_SIZE) != 0U)) {
result = SPI_NOR_E_ALIGNMENT;
} else {
result = validate_range(device, address, size);
}
while ((result == SPI_NOR_OK) && (remaining != 0U)) {
uint8_t command[4];
result = write_enable(device);
if (result == SPI_NOR_OK) {
make_address_command(command, SPI_NOR_COMMAND_SECTOR_ERASE,
address);
result = transaction(device, command, sizeof(command), NULL,
NULL, 0U);
}
if (result == SPI_NOR_OK) {
result = wait_ready(device, device->config.erase_timeout_ms);
}
if (result == SPI_NOR_OK) {
result = verify_erased_sector(device, address);
}
address += SPI_NOR_SECTOR_SIZE;
remaining -= SPI_NOR_SECTOR_SIZE;
}
end_operation(device);
return result;
}
/* Копия info не позволяет внешнему коду менять геометрию активного контекста. */
spi_nor_status_t spi_nor_get_info(const spi_nor_t *device,
spi_nor_info_t *info)
{
if ((device == NULL) || (info == NULL)) {
return SPI_NOR_E_ARGUMENT;
}
if (device->detected == 0U) {
return SPI_NOR_E_NOT_FOUND;
}
*info = device->info;
return SPI_NOR_OK;
}
/* Raw JEDEC остаётся диагностически доступен после NOT_FOUND/UNSUPPORTED. */
spi_nor_status_t spi_nor_get_last_jedec(const spi_nor_t *device,
uint8_t jedec[3])
{
if ((device == NULL) || (jedec == NULL) ||
(device->initialized == 0U)) {
return SPI_NOR_E_ARGUMENT;
}
jedec[0] = device->info.manufacturer_id;
jedec[1] = device->info.memory_type;
jedec[2] = device->info.capacity_code;
return SPI_NOR_OK;
}