/* * Copyright 2017-2020 NXP * * SPDX-License-Identifier: BSD-3-Clause */ #include #include "mflash_drv.h" #include "fsl_flexspi.h" #include "fsl_cache.h" #include "board.h" #define FLASH_PORT kFLEXSPI_PortB1 #if !defined(BOARD_FLASH_RESET_GPIO) || !defined(BOARD_FLASH_RESET_GPIO_PORT) || !defined(BOARD_FLASH_RESET_GPIO_PIN) #error FLASH reset pin needs to be specified. Please define BOARD_FLASH_RESET_GPIO, BOARD_FLASH_RESET_GPIO_PORT and BOARD_FLASH_RESET_GPIO_PIN in your board.h file. #endif #define NOR_CMD_LUT_SEQ_IDX_READ 0 #define NOR_CMD_LUT_SEQ_IDX_READSTATUS 1 #define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE 2 #define NOR_CMD_LUT_SEQ_IDX_READID_OPI 3 #define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_OPI 4 #define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 5 #define NOR_CMD_LUT_SEQ_IDX_CHIPERASE 6 #define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM 7 #define NOR_CMD_LUT_SEQ_IDX_ENTEROPI 8 /* NOTE: Workaround for debugger. Must define AHB write FlexSPI sequence index to 9 to avoid debugger issue. Debugger can attach to the CM33 core only when ROM executes to certain place. At that point, AHB write FlexSPI sequence index is set to 9, but in LUT, the command is not filled by ROM. If the debugger sets software breakpoint at flash after reset/attachment, FlexSPI AHB write command will be triggered. It may cause AHB bus hang if the command in LUT sequence index 9 is any read opeartion. So we need to ensure at any time, the FlexSPI LUT sequence 9 for the flash must be set to STOP command to avoid unexpected debugger behaivor. */ #define NOR_CMD_LUT_SEQ_IDX_WRITE 9 #define NOR_CMD_LUT_SEQ_IDX_READSTATUS_OPI 10 #define CUSTOM_LUT_LENGTH 60 #define FLASH_BUSY_STATUS_POL 1 #define FLASH_BUSY_STATUS_OFFSET 0 #define FLASH_ERROR_STATUS_MASK 0x0e #define FLASH_ENABLE_OCTAL_CMD 0x02 #define CACHE_MAINTAIN 1 #ifndef XIP_EXTERNAL_FLASH static flexspi_device_config_t deviceconfig = { .flexspiRootClk = 99000000, .flashSize = FLASH_SIZE / 1024, /* flash size in KB */ .CSIntervalUnit = kFLEXSPI_CsIntervalUnit1SckCycle, .CSInterval = 2, .CSHoldTime = 3, .CSSetupTime = 3, .dataValidTime = 2, .columnspace = 0, .enableWordAddress = 0, .AWRSeqIndex = NOR_CMD_LUT_SEQ_IDX_WRITE, .AWRSeqNumber = 1, .ARDSeqIndex = NOR_CMD_LUT_SEQ_IDX_READ, .ARDSeqNumber = 1, .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle, .AHBWriteWaitInterval = 0, }; #endif static uint32_t customLUT[CUSTOM_LUT_LENGTH] = { /* OPI DDR read */ [4 * NOR_CMD_LUT_SEQ_IDX_READ + 0] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0xEE, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x11), [4 * NOR_CMD_LUT_SEQ_IDX_READ + 1] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_RADDR_DDR, kFLEXSPI_8PAD, 0x20, kFLEXSPI_Command_DUMMY_DDR, kFLEXSPI_8PAD, 0x29), [4 * NOR_CMD_LUT_SEQ_IDX_READ + 2] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_DDR, kFLEXSPI_8PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x0), /* Read status register */ [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04), /* Write Enable */ [4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Read ID */ [4 * NOR_CMD_LUT_SEQ_IDX_READID_OPI] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x9F, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x60), [4 * NOR_CMD_LUT_SEQ_IDX_READID_OPI + 1] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_RADDR_DDR, kFLEXSPI_8PAD, 0x20, kFLEXSPI_Command_DUMMY_DDR, kFLEXSPI_8PAD, 0x16), [4 * NOR_CMD_LUT_SEQ_IDX_READID_OPI + 2] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_DDR, kFLEXSPI_8PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x0), /* Write Enable */ [4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_OPI] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x06, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0xF9), /* Erase Sector */ [4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x21, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0xDE), [4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_RADDR_DDR, kFLEXSPI_8PAD, 0x20, kFLEXSPI_Command_STOP, kFLEXSPI_8PAD, 0), /* Erase Chip */ [4 * NOR_CMD_LUT_SEQ_IDX_CHIPERASE] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x60, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x9F), /* Program */ [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x12, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0xED), [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM + 1] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_RADDR_DDR, kFLEXSPI_8PAD, 0x20, kFLEXSPI_Command_WRITE_DDR, kFLEXSPI_8PAD, 0x04), /* Enter OPI mode */ [4 * NOR_CMD_LUT_SEQ_IDX_ENTEROPI] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x72, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x20), [4 * NOR_CMD_LUT_SEQ_IDX_ENTEROPI + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Dummy write, do nothing when AHB write command is triggered. */ [4 * NOR_CMD_LUT_SEQ_IDX_WRITE] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x0, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x0), /* Read status register using Octal DDR read */ [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS_OPI] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0x05, kFLEXSPI_Command_DDR, kFLEXSPI_8PAD, 0xFA), [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS_OPI + 1] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_RADDR_DDR, kFLEXSPI_8PAD, 0x20, kFLEXSPI_Command_DUMMY_DDR, kFLEXSPI_8PAD, 0x20), [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUS_OPI + 2] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_DDR, kFLEXSPI_8PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0x0), }; static status_t flexspi_nor_wait_bus_busy(FLEXSPI_Type *base, bool enableOctal) { /* Wait status ready. */ bool isBusy; uint32_t readValue; status_t status; flexspi_transfer_t flashXfer; flashXfer.deviceAddress = 0; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Read; flashXfer.SeqNumber = 1; if (enableOctal) { flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READSTATUS_OPI; } else { flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READSTATUS; } flashXfer.data = &readValue; flashXfer.dataSize = 1; do { status = FLEXSPI_TransferBlocking(base, &flashXfer); if (status != kStatus_Success) { return status; } if (FLASH_BUSY_STATUS_POL) { if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET)) { isBusy = true; } else { isBusy = false; } } else { if (readValue & (1U << FLASH_BUSY_STATUS_OFFSET)) { isBusy = false; } else { isBusy = true; } } } while (isBusy); return status; } static status_t flexspi_nor_write_enable(FLEXSPI_Type *base, uint32_t baseAddr, bool enableOctal) { flexspi_transfer_t flashXfer; status_t status; /* Write enable */ flashXfer.deviceAddress = baseAddr; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Command; flashXfer.SeqNumber = 1; if (enableOctal) { flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE_OPI; } else { flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE; } status = FLEXSPI_TransferBlocking(base, &flashXfer); return status; } static status_t flexspi_nor_enable_octal_mode(FLEXSPI_Type *base) { flexspi_transfer_t flashXfer; status_t status; uint32_t writeValue = FLASH_ENABLE_OCTAL_CMD; /* Write enable */ status = flexspi_nor_write_enable(base, 0, false); if (status != kStatus_Success) { return status; } /* Enable quad mode. */ flashXfer.deviceAddress = 0; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Write; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ENTEROPI; flashXfer.data = &writeValue; flashXfer.dataSize = 1; status = FLEXSPI_TransferBlocking(base, &flashXfer); if (status != kStatus_Success) { return status; } status = flexspi_nor_wait_bus_busy(base, true); return status; } /* Internal - erase single sector */ static status_t flexspi_nor_flash_sector_erase(FLEXSPI_Type *base, uint32_t address) { status_t status; flexspi_transfer_t flashXfer; /* Write enable */ status = flexspi_nor_write_enable(base, 0, true); if (status != kStatus_Success) { return status; } flashXfer.deviceAddress = address; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Command; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_ERASESECTOR; status = FLEXSPI_TransferBlocking(base, &flashXfer); if (status != kStatus_Success) { return status; } status = flexspi_nor_wait_bus_busy(base, true); return status; } /* Program page into serial flash using QSPI polling way */ static status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t address, const uint32_t *src) { status_t status; flexspi_transfer_t flashXfer; /* Write neable */ status = flexspi_nor_write_enable(base, address, true); if (status != kStatus_Success) { return status; } /* Prepare page program command */ flashXfer.deviceAddress = address; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Write; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM; flashXfer.data = (uint32_t *)src; flashXfer.dataSize = MFLASH_PAGE_SIZE; status = FLEXSPI_TransferBlocking(base, &flashXfer); if (status != kStatus_Success) { return status; } status = flexspi_nor_wait_bus_busy(base, true); return status; } static int32_t mflash_drv_init_internal(void) { uint64_t delay = USEC_TO_COUNT(20, SystemCoreClock); uint32_t primask = __get_PRIMASK(); __asm("cpsid i"); #ifndef XIP_EXTERNAL_FLASH flexspi_config_t config; /*Get FLEXSPI default settings and configure the flexspi. */ FLEXSPI_GetDefaultConfig(&config); /*Set AHB buffer size for reading data through AHB bus. */ config.ahbConfig.enableAHBPrefetch = true; config.rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackInternally; config.enableCombination = true; config.ahbConfig.enableAHBBufferable = true; config.ahbConfig.enableAHBCachable = true; FLEXSPI_Init(MFLASH_FLEXSPI, &config); /* Configure flash settings according to serial flash feature. */ FLEXSPI_SetFlashConfig(MFLASH_FLEXSPI, &deviceconfig, FLASH_PORT); #endif /* Update LUT table. */ FLEXSPI_UpdateLUT(MFLASH_FLEXSPI, 0, customLUT, CUSTOM_LUT_LENGTH); /* Hardware reset of external FLASH */ do { /* activate OSPI nRESET */ BOARD_FLASH_RESET_GPIO->B[BOARD_FLASH_RESET_GPIO_PORT][BOARD_FLASH_RESET_GPIO_PIN] = 0; for (uint64_t i = 0; i < delay; i++) { __asm("nop"); } /* deactivate OSPI nRESET */ BOARD_FLASH_RESET_GPIO->B[BOARD_FLASH_RESET_GPIO_PORT][BOARD_FLASH_RESET_GPIO_PIN] = 1; flexspi_nor_enable_octal_mode(MFLASH_FLEXSPI); } while (0); /* Do software reset. */ // FLEXSPI_SoftwareReset(MFLASH_FLEXSPI); //#if defined(EXAMPLE_INVALIDATE_FLEXSPI_CACHE) // EXAMPLE_INVALIDATE_FLEXSPI_CACHE(); //#endif if (primask == 0) { __asm("cpsie i"); } return kStatus_Success; } /* API - initialize 'mflash' */ int32_t mflash_drv_init(void) { /* Necessary to have double wrapper call in non_xip memory */ return mflash_drv_init_internal(); } /* Internal - erase single sector */ static int32_t mflash_drv_sector_erase_internal(uint32_t sector_addr) { status_t status; uint32_t primask = __get_PRIMASK(); __asm("cpsid i"); status = flexspi_nor_flash_sector_erase(MFLASH_FLEXSPI, sector_addr); /* Do software reset. */ // FLEXSPI_SoftwareReset(MFLASH_FLEXSPI); DCACHE_InvalidateByRange(MFLASH_BASE_ADDRESS + sector_addr, MFLASH_SECTOR_SIZE); if (primask == 0) { __asm("cpsie i"); } /* Flush pipeline to allow pending interrupts take place * before starting next loop */ __ISB(); return status; } /* Calling wrapper for 'mflash_drv_sector_erase_internal'. * Erase one sector starting at 'sector_addr' - must be sector aligned. */ int32_t mflash_drv_sector_erase(uint32_t sector_addr) { if (0 == mflash_drv_is_sector_aligned(sector_addr)) return kStatus_InvalidArgument; return mflash_drv_sector_erase_internal(sector_addr); } /* Internal - write single page */ static int32_t mflash_drv_page_program_internal(uint32_t page_addr, uint32_t *data) { uint32_t primask = __get_PRIMASK(); __asm("cpsid i"); status_t status; status = flexspi_nor_flash_page_program(MFLASH_FLEXSPI, page_addr, data); /* Do software reset. */ // FLEXSPI_SoftwareReset(MFLASH_FLEXSPI); DCACHE_InvalidateByRange(MFLASH_BASE_ADDRESS + page_addr, MFLASH_PAGE_SIZE); if (primask == 0) { __asm("cpsie i"); } /* Flush pipeline to allow pending interrupts take place * before starting next loop */ __ISB(); return status; } /* Calling wrapper for 'mflash_drv_page_program_internal'. * Write 'data' to 'page_addr' - must be page aligned. * NOTE: Don't try to store constant data that are located in XIP !! */ int32_t mflash_drv_page_program(uint32_t page_addr, uint32_t *data) { if (0 == mflash_drv_is_page_aligned(page_addr)) return kStatus_InvalidArgument; return mflash_drv_page_program_internal(page_addr, data); } /* API - Read data */ int32_t mflash_drv_read(uint32_t addr, uint32_t *buffer, uint32_t len) { memcpy(buffer, (void *)(addr + MFLASH_BASE_ADDRESS), len); return kStatus_Success; } /* API - Get pointer to FLASH region */ void *mflash_drv_phys2log(uint32_t addr, uint32_t len) { /* FLASH starts at MFLASH_BASE_ADDRESS */ return (void *)(addr + MFLASH_BASE_ADDRESS); } /* API - Get pointer to FLASH region */ uint32_t mflash_drv_log2phys(void *ptr, uint32_t len) { if ((uint32_t)ptr < MFLASH_BASE_ADDRESS) return kStatus_InvalidArgument; /* FLASH starts at MFLASH_BASE_ADDRESS */ return ((uint32_t)ptr - MFLASH_BASE_ADDRESS); } /* Temporary sector shadow buffer. Use uint32_t type to force 4B alignment and * improve copy operation */ static uint32_t g_flashm_sector[MFLASH_SECTOR_SIZE / sizeof(uint32_t)]; /* Internal - write data of 'data_len' to single sector 'sector_addr', starting from 'sect_off' */ static int32_t mflash_drv_sector_update(uint32_t sector_addr, uint32_t sect_off, const uint8_t *data, uint32_t data_len) { int sector_erase_req = 0; uint32_t page_program_map = 0; /* Current implementation is limited to 32 pages per sector */ /* Address not aligned to sector boundary */ if (false == mflash_drv_is_sector_aligned(sector_addr)) return -1; /* Offset + length exceeed sector size */ if (sect_off + data_len > MFLASH_SECTOR_SIZE) return -1; if (0 != mflash_drv_read(sector_addr, &g_flashm_sector[0], sizeof(g_flashm_sector))) { return -2; } /* Diff the data to determine pages to be programed */ for (uint32_t i = 0; i < data_len; i++) { uint8_t cur_value = ((uint8_t *)(g_flashm_sector))[sect_off + i]; uint8_t new_value = data[i]; if ((cur_value | new_value) != cur_value) { /* A bit needs to be flipped from 0 to 1, the whole sector has to be erased */ sector_erase_req = 1; break; } if (cur_value != new_value) { /* There is a change, the page has to be programmed for sure */ page_program_map |= 1 << ((sect_off + i) / MFLASH_PAGE_SIZE); } } #if !defined(MFLASH_INC_WRITES) || !MFLASH_INC_WRITES /* Perform blank check page by page until decission for sector erase is made or we reach last page of the sector */ for (int page_idx = 0; (0 == sector_erase_req) && page_idx < MFLASH_SECTOR_SIZE / MFLASH_PAGE_SIZE; page_idx++) { /* Check only pages which need to be programed */ if (page_program_map & (1 << page_idx)) { int page_word_start = page_idx * (MFLASH_PAGE_SIZE / sizeof(g_flashm_sector[0])); int page_word_end = page_word_start + (MFLASH_PAGE_SIZE / sizeof(g_flashm_sector[0])); for (int i = page_word_start; i < page_word_end; i++) { if (g_flashm_sector[i] != 0xFFFFFFFF) { /* Mark sector to be erased and quit */ sector_erase_req = 1; break; } } } } #endif /* Copy data to be programmed byte by byte to shadow buffer at proper position */ for (uint32_t i = 0; i < data_len; i++) { ((uint8_t *)g_flashm_sector)[sect_off + i] = data[i]; } /* If sector is to be erased, update page program map according to non-blank areas in the shadow buffer */ if (0 != sector_erase_req) { for (int page_idx = 0; page_idx < MFLASH_SECTOR_SIZE / MFLASH_PAGE_SIZE; page_idx++) { int page_word_start = page_idx * (MFLASH_PAGE_SIZE / sizeof(g_flashm_sector[0])); int page_word_end = page_word_start + (MFLASH_PAGE_SIZE / sizeof(g_flashm_sector[0])); for (int i = page_word_start; i < page_word_end; i++) { if (g_flashm_sector[i] != 0xFFFFFFFF) { /* Mark the page for programming and go for next one */ page_program_map |= (1 << page_idx); break; } } } } /* Erase the sector if required */ if (0 != sector_erase_req) { if (0 != mflash_drv_sector_erase(sector_addr)) { return -2; } } /* Program the required pages */ for (int page_idx = 0; page_idx < MFLASH_SECTOR_SIZE / MFLASH_PAGE_SIZE; page_idx++) { if (page_program_map & (1 << page_idx)) { if (0 != mflash_drv_page_program(sector_addr + page_idx * MFLASH_PAGE_SIZE, g_flashm_sector + page_idx * (MFLASH_PAGE_SIZE / sizeof(g_flashm_sector[0])))) { return -3; } } } return 0; } /* Write data to flash, cannot be invoked directly, requires calling wrapper in non XIP memory */ int32_t mflash_drv_write_internal(uint32_t addr, const uint8_t *data, uint32_t data_len) { /* Interval <0, sector_size) */ uint32_t to_write = 0; /* Interval (data_len, 0> */ uint32_t to_remain = data_len; /* Physical address in external FLASH device */ int32_t result = 0; for ( /* Calculate address of first sector */ uint32_t sect_a = (addr / MFLASH_SECTOR_SIZE) * MFLASH_SECTOR_SIZE, /* and first sector offset */ sect_of = addr % MFLASH_SECTOR_SIZE, /* and set first data offset to 0*/ data_of = 0; /* Continue until sector address exceed target adddress + data_length */ sect_a < addr + data_len; /* Move to next sector */ sect_a += MFLASH_SECTOR_SIZE, /* and move pointer to data */ data_of += to_write) { /* If remaining data is exceed 'sector_size', write 'sector_size' length */ if (to_remain > MFLASH_SECTOR_SIZE - sect_of) { to_write = MFLASH_SECTOR_SIZE - sect_of; to_remain = to_remain - to_write; } /* else write remaining data length */ else { to_write = to_remain; to_remain = 0; } /* Write at 'sect_a' sector, starting at 'sect_of' using '&data[data_of]' of length 'to_write' */ result = mflash_drv_sector_update(sect_a, sect_of, data + data_of, to_write); if (0 != result) return -1; /* Only first sector is allowed to have an offset */ sect_of = 0; } return 0; } /* Calling wrapper for 'mflash_drv_write_internal'. * Write 'data' of 'data_len' to 'any_addr' - which doesn't have to be sector aligned. * NOTE: Don't try to store constant data that are located in XIP !! */ int32_t mflash_drv_write(uint32_t addr, const uint8_t *data, uint32_t data_len) { volatile int32_t result; result = mflash_drv_write_internal(addr, data, data_len); return result; }