Sync betaflight to Gitea
This commit is contained in:
@@ -0,0 +1,404 @@
|
||||
/*
|
||||
* Macronix MX66UW1G45G — 1 Gb octal STR/DTR flash driver for the
|
||||
* Betaflight N6 OpenBootloader.
|
||||
*
|
||||
* Targets the wiring on the STM32N6570-DK reference design and pin-
|
||||
* compatible boards: XSPI2 + XSPIM_P2 + GPIOM AF11 + NCS1. Boards with
|
||||
* different XSPI controllers, IO ports, or chip selects need their own
|
||||
* driver source (or this one parameterised via the OBL_FLASH_DRIVER
|
||||
* config knob).
|
||||
*
|
||||
* XSPI bring-up + 1S-1S-1S soft reset + memory-mapped engagement is
|
||||
* lifted from lib/main/STM32/n6_fsbl/main.c (proven across multiple boots
|
||||
* during the FSBL stub work). The new pieces here are the indirect-mode
|
||||
* write primitives (write enable / sector erase / block erase / page
|
||||
* program) and a memory-mapped-mode-off hook so the driver can flip
|
||||
* between read-via-memmap and write-via-indirect cleanly.
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "stm32n6xx_hal.h"
|
||||
|
||||
#include "flash_iface.h"
|
||||
|
||||
/* ---------- chip command set (1S-1S-1S, post-reset state) ----------- */
|
||||
#define MX66_CMD_RESET_ENABLE 0x66U
|
||||
#define MX66_CMD_RESET 0x99U
|
||||
#define MX66_CMD_RDSR 0x05U /* read status register */
|
||||
#define MX66_SR_WIP 0x01U /* write-in-progress bit */
|
||||
#define MX66_CMD_WREN 0x06U /* write enable */
|
||||
#define MX66_CMD_READ_4B 0x0CU /* 4-byte FAST_READ */
|
||||
#define MX66_CMD_READ_4B_DUMMY 8U
|
||||
#define MX66_CMD_PP_4B 0x12U /* 4-byte page program */
|
||||
#define MX66_CMD_SE_4B 0x21U /* 4-byte 4 KiB sector erase */
|
||||
#define MX66_CMD_BE_4B 0xDCU /* 4-byte 64 KiB block erase */
|
||||
|
||||
#define MX66_PAGE_SIZE 256U
|
||||
#define MX66_SECTOR_SIZE (4U * 1024U)
|
||||
#define MX66_TOTAL_SIZE (128U * 1024U * 1024U) /* 1 Gb = 128 MiB */
|
||||
|
||||
#define MX66_RETRIES 3
|
||||
#define SETTLE_DELAY_MS 600
|
||||
#define MX66_TIMEOUT_PROGRAM_MS 50
|
||||
#define MX66_TIMEOUT_SECTOR_MS 400
|
||||
#define MX66_TIMEOUT_BLOCK_MS 2000
|
||||
#define MX66_TIMEOUT_GENERIC_MS 1000
|
||||
|
||||
#define MX66_MEMMAP_BASE 0x70000000U
|
||||
|
||||
static XSPI_HandleTypeDef hxspi2;
|
||||
static const flash_geometry_t geometry = {
|
||||
.page_size_bytes = MX66_PAGE_SIZE,
|
||||
.sector_size_bytes = MX66_SECTOR_SIZE,
|
||||
.total_size_bytes = MX66_TOTAL_SIZE,
|
||||
};
|
||||
static bool memmap_active;
|
||||
|
||||
/* HAL_XSPI_MspInit (peripheral + IO manager + GPIO N clocks, GPION pins
|
||||
* to AF9_XSPIM_P2) lives in stm32n6xx_hal_msp.c — board-level config
|
||||
* shared with whatever flash chip happens to be wired to XSPI2. A
|
||||
* manufacturer using a different XSPI controller or pin map provides
|
||||
* their own MSP file under the per-config build. */
|
||||
|
||||
static HAL_StatusTypeDef mx66_wait_ready(uint32_t timeout_ms)
|
||||
{
|
||||
XSPI_RegularCmdTypeDef cmd = {0};
|
||||
uint8_t status;
|
||||
uint32_t tickstart = HAL_GetTick();
|
||||
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
|
||||
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
|
||||
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
|
||||
cmd.Instruction = MX66_CMD_RDSR;
|
||||
cmd.DataMode = HAL_XSPI_DATA_1_LINE;
|
||||
cmd.DataLength = 1;
|
||||
cmd.AddressMode = HAL_XSPI_ADDRESS_NONE;
|
||||
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
|
||||
cmd.DummyCycles = 0;
|
||||
|
||||
do {
|
||||
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
if (HAL_XSPI_Receive(&hxspi2, &status, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
if ((status & MX66_SR_WIP) == 0) {
|
||||
return HAL_OK;
|
||||
}
|
||||
} while ((HAL_GetTick() - tickstart) < timeout_ms);
|
||||
|
||||
return HAL_TIMEOUT;
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef mx66_simple_cmd_1s(uint8_t opcode)
|
||||
{
|
||||
XSPI_RegularCmdTypeDef cmd = {0};
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
|
||||
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
|
||||
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
|
||||
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
|
||||
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
|
||||
cmd.Instruction = opcode;
|
||||
cmd.AddressMode = HAL_XSPI_ADDRESS_NONE;
|
||||
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
|
||||
cmd.DataMode = HAL_XSPI_DATA_NONE;
|
||||
cmd.DummyCycles = 0;
|
||||
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
|
||||
return HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef mx66_cmd_8dtr(uint8_t opcode)
|
||||
{
|
||||
XSPI_RegularCmdTypeDef cmd = {0};
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
|
||||
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
|
||||
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_8_LINES;
|
||||
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_16_BITS;
|
||||
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_ENABLE;
|
||||
cmd.Instruction = ((uint16_t)opcode << 8) | (uint8_t)(~opcode);
|
||||
cmd.AddressMode = HAL_XSPI_ADDRESS_NONE;
|
||||
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
|
||||
cmd.DataMode = HAL_XSPI_DATA_NONE;
|
||||
cmd.DummyCycles = 0;
|
||||
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
|
||||
return HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef mx66_software_reset(void)
|
||||
{
|
||||
for (int i = 0; i < MX66_RETRIES; i++) {
|
||||
(void)mx66_cmd_8dtr(MX66_CMD_RESET_ENABLE);
|
||||
(void)mx66_cmd_8dtr(MX66_CMD_RESET);
|
||||
HAL_Delay(1);
|
||||
|
||||
if (mx66_simple_cmd_1s(MX66_CMD_RESET_ENABLE) == HAL_OK) {
|
||||
if (mx66_simple_cmd_1s(MX66_CMD_RESET) == HAL_OK) {
|
||||
HAL_Delay(1);
|
||||
return mx66_wait_ready(MX66_TIMEOUT_GENERIC_MS);
|
||||
}
|
||||
}
|
||||
}
|
||||
return HAL_ERROR;
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef mx66_write_enable(void)
|
||||
{
|
||||
HAL_StatusTypeDef st = mx66_simple_cmd_1s(MX66_CMD_WREN);
|
||||
return st;
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef mx66_addr_cmd_1s(uint8_t opcode, uint32_t address,
|
||||
uint32_t timeout_ms)
|
||||
{
|
||||
if (mx66_write_enable() != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
|
||||
XSPI_RegularCmdTypeDef cmd = {0};
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
|
||||
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
|
||||
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
|
||||
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
|
||||
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
|
||||
cmd.Instruction = opcode;
|
||||
cmd.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
|
||||
cmd.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;
|
||||
cmd.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
|
||||
cmd.Address = address;
|
||||
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
|
||||
cmd.DataMode = HAL_XSPI_DATA_NONE;
|
||||
cmd.DummyCycles = 0;
|
||||
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
|
||||
|
||||
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
return mx66_wait_ready(timeout_ms);
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef mx66_page_program(uint32_t address, const uint8_t *data,
|
||||
uint32_t length)
|
||||
{
|
||||
if (length == 0 || length > MX66_PAGE_SIZE) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
/* Page-program straddles must be split by the caller — chip wraps the
|
||||
* write at the page boundary, which would corrupt earlier bytes in
|
||||
* the same page. */
|
||||
if (((address & (MX66_PAGE_SIZE - 1)) + length) > MX66_PAGE_SIZE) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
|
||||
if (mx66_write_enable() != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
|
||||
XSPI_RegularCmdTypeDef cmd = {0};
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_COMMON_CFG;
|
||||
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
|
||||
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
|
||||
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
|
||||
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
|
||||
cmd.Instruction = MX66_CMD_PP_4B;
|
||||
cmd.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
|
||||
cmd.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;
|
||||
cmd.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
|
||||
cmd.Address = address;
|
||||
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
|
||||
cmd.DataMode = HAL_XSPI_DATA_1_LINE;
|
||||
cmd.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
|
||||
cmd.DataLength = length;
|
||||
cmd.DummyCycles = 0;
|
||||
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
|
||||
|
||||
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
if (HAL_XSPI_Transmit(&hxspi2, (uint8_t *)data, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
return mx66_wait_ready(MX66_TIMEOUT_PROGRAM_MS);
|
||||
}
|
||||
|
||||
static HAL_StatusTypeDef xspi2_init_controller(void)
|
||||
{
|
||||
XSPIM_CfgTypeDef mgr = {0};
|
||||
|
||||
hxspi2.Instance = XSPI2;
|
||||
hxspi2.Init.FifoThresholdByte = 4;
|
||||
hxspi2.Init.MemoryMode = HAL_XSPI_SINGLE_MEM;
|
||||
hxspi2.Init.MemoryType = HAL_XSPI_MEMTYPE_MACRONIX;
|
||||
hxspi2.Init.MemorySize = HAL_XSPI_SIZE_1GB;
|
||||
hxspi2.Init.ChipSelectHighTimeCycle = 2;
|
||||
hxspi2.Init.FreeRunningClock = HAL_XSPI_FREERUNCLK_DISABLE;
|
||||
hxspi2.Init.ClockMode = HAL_XSPI_CLOCK_MODE_0;
|
||||
hxspi2.Init.WrapSize = HAL_XSPI_WRAP_NOT_SUPPORTED;
|
||||
hxspi2.Init.ClockPrescaler = 0;
|
||||
hxspi2.Init.SampleShifting = HAL_XSPI_SAMPLE_SHIFT_NONE;
|
||||
hxspi2.Init.DelayHoldQuarterCycle = HAL_XSPI_DHQC_DISABLE;
|
||||
hxspi2.Init.ChipSelectBoundary = HAL_XSPI_BONDARYOF_NONE;
|
||||
hxspi2.Init.MaxTran = 0;
|
||||
hxspi2.Init.Refresh = 0;
|
||||
hxspi2.Init.MemorySelect = HAL_XSPI_CSSEL_NCS1;
|
||||
if (HAL_XSPI_Init(&hxspi2) != HAL_OK) {
|
||||
return HAL_ERROR;
|
||||
}
|
||||
|
||||
mgr.nCSOverride = HAL_XSPI_CSSEL_OVR_NCS1;
|
||||
mgr.IOPort = HAL_XSPIM_IOPORT_2;
|
||||
mgr.Req2AckTime = 1;
|
||||
return HAL_XSPIM_Config(&hxspi2, &mgr, HAL_XSPI_TIMEOUT_DEFAULT_VALUE);
|
||||
}
|
||||
|
||||
bool flash_init(void)
|
||||
{
|
||||
if (xspi2_init_controller() != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
HAL_Delay(SETTLE_DELAY_MS);
|
||||
|
||||
if (mx66_software_reset() != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
HAL_Delay(SETTLE_DELAY_MS);
|
||||
|
||||
memmap_active = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void flash_deinit(void)
|
||||
{
|
||||
/* Leave XSPI2 fully configured + memory-mapped so the post-reset
|
||||
* boot decision can read nor0 0x0 directly without re-init churn.
|
||||
* Callers wanting a true peripheral teardown should HAL_XSPI_DeInit
|
||||
* themselves; OBL never does. */
|
||||
}
|
||||
|
||||
const flash_geometry_t *flash_get_geometry(void)
|
||||
{
|
||||
return &geometry;
|
||||
}
|
||||
|
||||
bool flash_memmap_on(void)
|
||||
{
|
||||
if (memmap_active) {
|
||||
return true;
|
||||
}
|
||||
|
||||
XSPI_RegularCmdTypeDef cmd = {0};
|
||||
cmd.IOSelect = HAL_XSPI_SELECT_IO_7_0;
|
||||
cmd.InstructionMode = HAL_XSPI_INSTRUCTION_1_LINE;
|
||||
cmd.InstructionWidth = HAL_XSPI_INSTRUCTION_8_BITS;
|
||||
cmd.InstructionDTRMode = HAL_XSPI_INSTRUCTION_DTR_DISABLE;
|
||||
cmd.AddressMode = HAL_XSPI_ADDRESS_1_LINE;
|
||||
cmd.AddressWidth = HAL_XSPI_ADDRESS_32_BITS;
|
||||
cmd.AddressDTRMode = HAL_XSPI_ADDRESS_DTR_DISABLE;
|
||||
cmd.AlternateBytesMode = HAL_XSPI_ALT_BYTES_NONE;
|
||||
cmd.DataMode = HAL_XSPI_DATA_1_LINE;
|
||||
cmd.DataDTRMode = HAL_XSPI_DATA_DTR_DISABLE;
|
||||
cmd.DummyCycles = 0;
|
||||
cmd.DQSMode = HAL_XSPI_DQS_DISABLE;
|
||||
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_READ_CFG;
|
||||
cmd.Instruction = MX66_CMD_READ_4B;
|
||||
cmd.DummyCycles = MX66_CMD_READ_4B_DUMMY;
|
||||
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
cmd.DummyCycles = 0;
|
||||
|
||||
cmd.OperationType = HAL_XSPI_OPTYPE_WRITE_CFG;
|
||||
cmd.Instruction = MX66_CMD_PP_4B; /* placeholder — never issued */
|
||||
if (HAL_XSPI_Command(&hxspi2, &cmd, HAL_XSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
XSPI_MemoryMappedTypeDef mm = {0};
|
||||
mm.TimeOutActivation = HAL_XSPI_TIMEOUT_COUNTER_DISABLE;
|
||||
/* Prefetch off — speculative reads against a chip in an unexpected
|
||||
* mode would stall the AXI bus indefinitely. Bounded stalls only. */
|
||||
mm.NoPrefetchData = HAL_XSPI_AUTOMATIC_PREFETCH_DISABLE;
|
||||
mm.NoPrefetchAXI = HAL_XSPI_AXI_PREFETCH_DISABLE;
|
||||
if (HAL_XSPI_MemoryMapped(&hxspi2, &mm) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
memmap_active = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flash_memmap_off(void)
|
||||
{
|
||||
if (!memmap_active) {
|
||||
return true;
|
||||
}
|
||||
/* Aborting an in-flight memory-mapped session is the documented way
|
||||
* to drop the controller back to indirect command mode. */
|
||||
if (HAL_XSPI_Abort(&hxspi2) != HAL_OK) {
|
||||
return false;
|
||||
}
|
||||
memmap_active = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flash_erase_sector(uint32_t offset)
|
||||
{
|
||||
if (!flash_memmap_off()) {
|
||||
return false;
|
||||
}
|
||||
return mx66_addr_cmd_1s(MX66_CMD_SE_4B, offset, MX66_TIMEOUT_SECTOR_MS) == HAL_OK;
|
||||
}
|
||||
|
||||
bool flash_program_page(uint32_t offset, const uint8_t *data, uint32_t length)
|
||||
{
|
||||
if (!flash_memmap_off()) {
|
||||
return false;
|
||||
}
|
||||
return mx66_page_program(offset, data, length) == HAL_OK;
|
||||
}
|
||||
|
||||
bool flash_erase_range(uint32_t offset, uint32_t length)
|
||||
{
|
||||
const uint32_t mask = MX66_SECTOR_SIZE - 1;
|
||||
if (offset & mask) {
|
||||
return false;
|
||||
}
|
||||
uint32_t end = offset + length;
|
||||
/* Round end up to the next sector boundary. */
|
||||
end = (end + mask) & ~mask;
|
||||
|
||||
for (uint32_t a = offset; a < end; a += MX66_SECTOR_SIZE) {
|
||||
if (!flash_erase_sector(a)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flash_program(uint32_t offset, const uint8_t *data, uint32_t length)
|
||||
{
|
||||
while (length) {
|
||||
uint32_t chunk = MX66_PAGE_SIZE - (offset & (MX66_PAGE_SIZE - 1));
|
||||
if (chunk > length) {
|
||||
chunk = length;
|
||||
}
|
||||
if (!flash_program_page(offset, data, chunk)) {
|
||||
return false;
|
||||
}
|
||||
offset += chunk;
|
||||
data += chunk;
|
||||
length -= chunk;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flash_verify(uint32_t offset, const uint8_t *data, uint32_t length)
|
||||
{
|
||||
if (!flash_memmap_on()) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t *flash = (const uint8_t *)(MX66_MEMMAP_BASE + offset);
|
||||
return memcmp(flash, data, length) == 0;
|
||||
}
|
||||
Reference in New Issue
Block a user