/* * Copyright 2018-2021 NXP * * SPDX * License-Identifier: BSD-3-Clause */ #include #include "fsl_flexspi.h" #include "fsl_cache.h" #include "pin_mux.h" #include "mflash_drv.h" #include "flexspi_nor_flash_ops.h" #ifndef XIP_EXTERNAL_FLASH #define FLASH_SIZE 0x8000 flexspi_device_config_t deviceconfig = { .flexspiRootClk = 100000000, .flashSize = FLASH_SIZE, .CSIntervalUnit = kFLEXSPI_CsIntervalUnit1SckCycle, .CSInterval = 2, .CSHoldTime = 3, .CSSetupTime = 3, .dataValidTime = 0, .columnspace = 0, .enableWordAddress = 0, .AWRSeqIndex = 0, .AWRSeqNumber = 0, .ARDSeqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD, .ARDSeqNumber = 1, .AHBWriteWaitUnit = kFLEXSPI_AhbWriteWaitUnit2AhbCycle, .AHBWriteWaitInterval = 0, }; #endif /* ifndef XIP_EXTERNAL_FLASH */ /* Custom LUT Table for ISSI_IS25WPxxxA used in RT1060. */ static uint32_t customLUT[ CUSTOM_LUT_LENGTH ] = { [ 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 ), /* Erase Sector */ [ 4 * NOR_CMD_LUT_SEQ_IDX_ERASESECTOR ] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0xD7, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18 ), /* 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 ), /* Page Program - quad mode or Octal mode */ [ 4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD ] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x32, kFLEXSPI_Command_RADDR_SDR, kFLEXSPI_1PAD, 0x18 ), [ 4 * NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD + 1 ] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_WRITE_SDR, kFLEXSPI_4PAD, 0x01 /*0x04*/, 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 ), /* Write Enable */ [ 4 * NOR_CMD_LUT_SEQ_IDX_WRITEENABLE ] = FLEXSPI_LUT_SEQ( kFLEXSPI_Command_SDR, kFLEXSPI_1PAD, 0x06, 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 = kFLEXSPI_PortA1; 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 address ) { flexspi_transfer_t flashXfer; status_t status; /* Write neable */ flashXfer.deviceAddress = address; flashXfer.port = kFLEXSPI_PortA1; flashXfer.cmdType = kFLEXSPI_Command; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_WRITEENABLE; status = FLEXSPI_TransferBlocking( base, &flashXfer ); return status; } status_t flexspi_nor_enable_quad_mode( FLEXSPI_Type * base ) { flexspi_transfer_t flashXfer; status_t status; uint32_t writeValue = 0x40; /* Write neable */ status = flexspi_nor_write_enable( base, 0 ); if( status != kStatus_Success ) { return status; } /* Enable quad mode. */ flashXfer.deviceAddress = 0; flashXfer.port = kFLEXSPI_PortA1; 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 ); return status; } 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, address ); if( status != kStatus_Success ) { return status; } flashXfer.deviceAddress = address; flashXfer.port = kFLEXSPI_PortA1; 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 ); return status; } status_t flexspi_nor_read_data( FLEXSPI_Type * base, uint32_t startAddress, uint32_t * buffer, uint32_t length ) { status_t status; flexspi_transfer_t flashXfer; uint32_t readAddress = startAddress; /* Read page. */ flashXfer.deviceAddress = readAddress; flashXfer.port = kFLEXSPI_PortA1; flashXfer.cmdType = kFLEXSPI_Read; flashXfer.SeqNumber = 1; flashXfer.seqIndex = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD; flashXfer.data = buffer; flashXfer.dataSize = length; status = FLEXSPI_TransferBlocking( base, &flashXfer ); return status; } /* Initialize flash peripheral, * cannot be invoked directly, requires calling wrapper in non XIP memory */ static int32_t mflash_drv_init_internal( void ) { /* NOTE: Multithread access is not supported for SRAM target. * XIP target MUST be protected by disabling global interrupts * since all ISR (and API that is used inside) is placed at XIP. * It is necessary to place at least "mflash_drv.o", "fsl_flexspi.o" to SRAM */ /* disable interrupts when running from XIP */ uint32_t primask = __get_PRIMASK(); __asm( "cpsid i" ); status_t status = kStatus_Success; #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.ahbConfig.enableAHBBufferable = true; config.ahbConfig.enableAHBCachable = true; config.rxSampleClock = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad; FLEXSPI_Init( MFLASH_FLEXSPI, &config ); /* AHB Read Address option bit. This option bit is intend to remove AHB burst start address alignment limitation */ MFLASH_FLEXSPI->AHBCR |= FLEXSPI_AHBCR_READADDROPT_MASK; /* Configure flash settings according to serial flash feature. */ FLEXSPI_SetFlashConfig( MFLASH_FLEXSPI, &deviceconfig, kFLEXSPI_PortA1 ); #endif /* ifndef XIP_EXTERNAL_FLASH */ /* Update LUT table. */ FLEXSPI_UpdateLUT( MFLASH_FLEXSPI, 0, customLUT, CUSTOM_LUT_LENGTH ); /*FLEXSPI_SoftwareReset(MFLASH_FLEXSPI); */ #ifndef XIP_EXTERNAL_FLASH /* Enter quad mode. */ status = flexspi_nor_enable_quad_mode( MFLASH_FLEXSPI ); #endif if( primask == 0 ) { __asm( "cpsie i" ); } return status; } /* 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, MFLASH_PAGE_SIZE ); /* 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 ); } /* Internal - read data */ static int32_t mflash_drv_read_internal( uint32_t addr, uint32_t * buffer, uint32_t len ) { uint32_t primask = __get_PRIMASK(); __asm( "cpsid i" ); status_t status; status = flexspi_nor_read_data( MFLASH_FLEXSPI, addr, buffer, len ); /* Do software reset. */ FLEXSPI_SoftwareReset( MFLASH_FLEXSPI ); 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_read_internal'. */ int32_t mflash_drv_read( uint32_t addr, uint32_t * buffer, uint32_t len ) { /* Check alignment */ if( ( ( uint32_t ) buffer % 4 ) || ( len % 4 ) ) { return kStatus_InvalidArgument; } return mflash_drv_read_internal( addr, buffer, len ); } /* Returns pointer (AHB address) to memory area where the specified region of FLASH is mapped, NULL on failure (could * not map continuous block) */ void * mflash_drv_phys2log( uint32_t addr, uint32_t len ) { /* take FLEXSPI remapping into account */ uint32_t remap_offset = IOMUXC_GPR->GPR32 & 0xFFFFF000; uint32_t remap_start = IOMUXC_GPR->GPR30 & 0xFFFFF000; uint32_t remap_end = IOMUXC_GPR->GPR31 & 0xFFFFF000; /* calculate the bus address where the requested FLASH region is expected to be available */ uint32_t bus_addr = addr + MFLASH_BASE_ADDRESS; if( ( remap_offset == 0 ) || ( remap_end <= remap_start ) ) { /* remapping is not active */ return ( void * ) bus_addr; } if( ( remap_start >= bus_addr + len ) || ( remap_end <= bus_addr ) ) { /* remapping window does not collide with bus addresses normally assigned for requested range of FLASH */ return ( void * ) bus_addr; } if( ( remap_start + remap_offset <= bus_addr ) && ( remap_end + remap_offset >= bus_addr + len ) ) { /* remapping window covers the whole requested range of FLASH, return address adjusted by negative offset */ return ( void * ) ( bus_addr - remap_offset ); } /* the bus address region normally assigned for requested range of FLASH is partially or completely shadowed by * remapping, fail */ return NULL; } /* Returns address of physical memory where the area accessible by given pointer is actually stored, UINT32_MAX on * failure (could not map as continuous block) */ uint32_t mflash_drv_log2phys( void * ptr, uint32_t len ) { /* take FLEXSPI remapping into account */ uint32_t remap_offset = IOMUXC_GPR->GPR32 & 0xFFFFF000; uint32_t remap_start = IOMUXC_GPR->GPR30 & 0xFFFFF000; uint32_t remap_end = IOMUXC_GPR->GPR31 & 0xFFFFF000; /* calculate the bus address where the requested FLASH region is expected to be available */ uint32_t bus_addr = ( uint32_t ) ptr; if( bus_addr < MFLASH_BASE_ADDRESS ) { /* the pointer points outside of the flash memory area */ return UINT32_MAX; } if( ( remap_offset == 0 ) || ( remap_end <= remap_start ) ) { /* remapping is not active */ return( bus_addr - MFLASH_BASE_ADDRESS ); } if( ( remap_start >= bus_addr + len ) || ( remap_end <= bus_addr ) ) { /* remapping window does not affect the requested memory area */ return( bus_addr - MFLASH_BASE_ADDRESS ); } if( ( remap_start <= bus_addr ) && ( remap_end >= bus_addr + len ) ) { /* remapping window covers the whole address range, return address adjusted by offset */ return( bus_addr + remap_offset - MFLASH_BASE_ADDRESS ); } /* the bus address region partially collides with the remapping window, hence the range is not mapped to continuous * block in the FLASH, fail */ return UINT32_MAX; } /* 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 decision 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 /* if !defined( MFLASH_INC_WRITES ) || !MFLASH_INC_WRITES */ /* 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; } } } /* mflash_drv_read_mode(); */ 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 address + 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; }