/* * Copyright 2017-2021 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 #define NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD 0 #define NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG 1 #define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE 2 #define NOR_CMD_LUT_SEQ_IDX_ERASESECTOR 3 #define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD 4 #define NOR_CMD_LUT_SEQ_IDX_ERASECHIP 5 #define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE 6 #define NOR_CMD_LUT_SEQ_IDX_READ_NORMAL 7 #define NOR_CMD_LUT_SEQ_IDX_READID 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_ENTERQPI 10 #define NOR_CMD_LUT_SEQ_IDX_EXITQPI 11 #define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG 12 #define NOR_CMD_LUT_SEQ_IDX_READ_FAST 13 #define CUSTOM_LUT_LENGTH 60 #define FLASH_QUAD_ENABLE 0x40 #define FLASH_BUSY_STATUS_POL 1 #define FLASH_BUSY_STATUS_OFFSET 0 #ifndef XIP_EXTERNAL_FLASH static flexspi_device_config_t deviceconfig = { .flexspiRootClk = 49500000, .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_FAST_QUAD, .ARDSeqNumber = 1, .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle, .AHBWriteWaitInterval = 0, }; #endif static uint32_t customLUT[CUSTOM_LUT_LENGTH] = { /* Normal read mode -SDR */ [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x03, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), [4 * NOR_CMD_LUT_SEQ_IDX_READ_NORMAL + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Fast read mode - SDR */ [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x0B, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST + 1] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_1PAD, 0x08, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04), /* Fast read quad mode - SDR */ [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xEB, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 0x18), [4 * NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD + 1] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_DUMMY_SDR, kFLEXSPI_4PAD, 0x06, kFLEXSPI_Command_READ_SDR, kFLEXSPI_4PAD, 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), /* Erase Sector */ [4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x20, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), /* Page Program - single mode */ [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x02, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18), [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_SINGLE + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Page Program - quad mode */ [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x38, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_4PAD, 0x18), [4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x04, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Read ID */ [4 * NOR_CMD_LUT_SEQ_IDX_READID] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x9F, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04), /* Enable Quad mode */ [4 * NOR_CMD_LUT_SEQ_IDX_WRITESTATUSREG] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x01, kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_1PAD, 0x04), /* 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), /* Enter QPI mode */ [4 * NOR_CMD_LUT_SEQ_IDX_ENTERQPI] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x35, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Exit QPI mode */ [4 * NOR_CMD_LUT_SEQ_IDX_EXITQPI] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_4PAD, 0xF5, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), /* Read status register */ [4 * NOR_CMD_LUT_SEQ_IDX_READSTATUSREG] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x05, kFLEXSPI_Command_READ_SDR, kFLEXSPI_1PAD, 0x04), /* Erase whole chip */ [4 * NOR_CMD_LUT_SEQ_IDX_ERASECHIP] = FLEXSPI_LUT_SEQ(kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xC7, kFLEXSPI_Command_STOP, kFLEXSPI_1PAD, 0), }; static status_t flexspi_nor_wait_bus_busy(FLEXSPI_Type *base) { /* 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; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READSTATUSREG; 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) { flexspi_transfer_t flashXfer; status_t status; /* Write enable */ flashXfer.deviceAddress = baseAddr; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Command; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE; status = FLEXSPI_TransferBlocking(base, &flashXfer); return status; } static status_t flexspi_nor_enable_quad_mode(FLEXSPI_Type *base) { flexspi_transfer_t flashXfer; status_t status; uint32_t writeValue = FLASH_QUAD_ENABLE; /* Write enable */ status = flexspi_nor_write_enable(base, 0); 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_WRITESTATUSREG; flashXfer.data = &writeValue; flashXfer.dataSize = 1; status = FLEXSPI_TransferBlocking(base, &flashXfer); if (status != kStatus_Success) { return status; } status = flexspi_nor_wait_bus_busy(base); /* Do software reset. */ FLEXSPI_SoftwareReset(base); 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 */ flashXfer.deviceAddress = address; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Command; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE; status = FLEXSPI_TransferBlocking(base, &flashXfer); 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); /* Do software reset. */ FLEXSPI_SoftwareReset(base); return status; } static status_t flexspi_nor_flash_page_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src) { status_t status; flexspi_transfer_t flashXfer; /* To make sure external flash be in idle status, added wait for busy before program data for an external flash without RWW(read while write) attribute.*/ status = flexspi_nor_wait_bus_busy(base); if (kStatus_Success != status) { return status; } /* Write enable. */ status = flexspi_nor_write_enable(base, dstAddr); if (status != kStatus_Success) { return status; } /* Prepare page program command */ flashXfer.deviceAddress = dstAddr; flashXfer.port = FLASH_PORT; flashXfer.cmdType = kFLEXSPI_Write; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD; 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); /* Do software reset or clear AHB buffer directly. */ #if defined(FSL_FEATURE_SOC_OTFAD_COUNT) && defined(FLEXSPI_AHBCR_CLRAHBRXBUF_MASK) && \ defined(FLEXSPI_AHBCR_CLRAHBTXBUF_MASK) base->AHBCR |= FLEXSPI_AHBCR_CLRAHBRXBUF_MASK | FLEXSPI_AHBCR_CLRAHBTXBUF_MASK; base->AHBCR &= ~(FLEXSPI_AHBCR_CLRAHBRXBUF_MASK | FLEXSPI_AHBCR_CLRAHBTXBUF_MASK); #else FLEXSPI_SoftwareReset(base); #endif return status; } static int32_t mflash_drv_init_internal(void) { 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; #if !(defined(FSL_FEATURE_FLEXSPI_HAS_NO_MCR0_COMBINATIONEN) && FSL_FEATURE_FLEXSPI_HAS_NO_MCR0_COMBINATIONEN) config.enableCombination = true; #endif 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); flexspi_nor_enable_quad_mode(MFLASH_FLEXSPI); /* Invalidate cache. */ do { CACHE64->CCR |= CACHE64_CTRL_CCR_INVW0_MASK | CACHE64_CTRL_CCR_INVW1_MASK | CACHE64_CTRL_CCR_GO_MASK; while (CACHE64->CCR & CACHE64_CTRL_CCR_GO_MASK) { } CACHE64->CCR &= ~(CACHE64_CTRL_CCR_INVW0_MASK | CACHE64_CTRL_CCR_INVW1_MASK); } while (0); 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); 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); }