Using AHB to Write NOR Flash on i.MXRT

Hello everyone, I am Pi Zi Heng, a serious technical person. Today, I will introduce how to modify the FlexSPI driver on i.MXRT to write NOR Flash using AHB.

Recently, I wrote an article titled “The Two Key Characteristics of Serial NAND Flash Preventing XiP under i.MXRT FlexSPI”, where I discussed the Page Read waiting characteristics of NAND Flash (after sending the Read command, it is necessary to read back the Busy bit of the Flash internal status register to determine if the Page data is ready) that prevent it from being easily accessed via AHB like NOR Flash, allowing only AHB reads within a single Page space (provided that the read command is sent in IPG mode and the status register is read to ensure the data is ready).

Returning to NOR Flash, we can easily read Flash data via AHB, but writing to Flash is generally accomplished by calling the FlexSPI driver (i.e., IPG mode). So, is it possible to “write Flash using AHB”? The answer is yes, but why did I add quotes? Read on to find out:

This article takes the NXP MIMXRT1170-EVK development board with the main chip i.MXRT1176 and its onboard Flash chip - Winbond IS25WP128 as an example.

1. Flash Write Operation Process

Winbond IS25WP128 is a very typical four-wire QSPI NOR Flash, and its write (programming) timing conforms to the JEDEC216 standard. In simple terms, a complete write timing consists of three independent sub-timings: Write Enable timing + Page Program timing + Read Status timing.

First, let’s look at the Write Enable sub-timing. NOR Flash has a bit in its internal status register called WEL (Write Enable Latch), which controls the Flash’s erase/write permissions, with a default value of 0 (i.e., not allowed to erase/write). If you want to write to Flash, you must first temporarily set the WEL bit to 1 using the Write Enable command (this bit will automatically revert to 0 after the current erase/write command ends).

Using AHB to Write NOR Flash on i.MXRT

Using AHB to Write NOR Flash on i.MXRT

After setting the WEL, you can transmit Page data to Flash; this sub-timing is called Page Program. Page Program can be divided into three types based on command address and data transfer method: single-line SPI, four-line SPI, and QPI. Below is the timing diagram for the commonly used four-line SPI, where commands and addresses are transmitted via IO0, and data is transmitted via IO[3:0].

Generally speaking, in this timing, the incoming address should be the Page’s starting address, and the length of the data to be written should be the size of a complete Page. However, it is also possible to write data of less than a Page length from a non-Page starting address, but one important point to note is to avoid crossing pages during this timing (if this occurs, data exceeding the current page will be returned to the starting address of that page).

Using AHB to Write NOR Flash on i.MXRT

After the Page Program sub-timing ends, the data has not yet been truly written into the Flash memory; the Flash internal controller only begins processing the data. At this point, there will be a waiting time (approximately 0.2ms); there is a bit in the Flash internal status register called WIP (Write In Progress), which indicates the data writing status (default value is 0; after the Page Program sub-timing ends, WIP immediately jumps to 1). Users need to read the status register in real-time through the Read Status sub-timing to know the data processing situation.

When the WIP bit in the Flash internal status register jumps back from 1 to 0, it means that a complete write timing has ended, and the host can initiate the next write timing.

Using AHB to Write NOR Flash on i.MXRT

Using AHB to Write NOR Flash on i.MXRT

Using AHB to Write NOR Flash on i.MXRT

2. FlexSPI Support for Write Timing

In my previous article “Understanding the Lookup Table in the i.MXRT Startup Header FDCB from Scratch”, I introduced the read timing of FlexSPI in great detail, especially the implementation of AHB read support. Now, I will introduce the support of FlexSPI for write timing.

The three sub-timings of Flash write operations introduced in the first section are, of course, supported in the FlexSPI peripheral; the SEQ_CTL component has already implemented these sub-timings. For example, below is the sequence for Page Program:

Using AHB to Write NOR Flash on i.MXRT

Because Flash write operations require three sub-sequences, they are much more complex than the single sequence of Flash read operations. The most critical point is that write operations also include an uncertain waiting period (the Read Status sub-timing interacts with Flash), which leads to the FlexSPI peripheral being unable to perfectly support AHB write operations. This is also why writing to Flash is completed through IPG mode, as in IPG mode, sub-sequences can be freely combined and manually scheduled by user code.

In principle, the three write operation sub-sequences can be placed in any sequence position in the LUT, because even if they are placed together in order, we cannot automatically complete the full write of Page data by specifying the position (index) of the first sub-sequence of the write operation in the LUT using the AWRSEQID bit in the FlexSPI->FLSHxCR2 register (where x can be A1/A2/B1/B2, depending on the Flash pin connection).

However, do not give up; the Page Program sub-sequence can still be replaced with AHB write, which allows us to experience writing to Flash using AHB. However, it is necessary to assist the Write Enable and Read Status actions in IPG mode before and after the AHB write operation. The next section will demonstrate this with code.

Using AHB to Write NOR Flash on i.MXRT

3. FlexSPI Driver Usage

Example Path: \SDK_2.10.0_MIMXRT1170-EVK\boards\evkmimxrt1170\driver_examples\flexspi\nor\polling_transfer\cm7\iar

3.1 Initialization

First, let’s take a look at the FlexSPI initialization function flexspi_nor_flash_init(). This function requires three configuration variables: flexspi_config_t type configuration for the FlexSPI peripheral layer flexspiconfig, flexspi_device_config_t type configuration for the Flash device deviceconfig, and a very core customLUT (only the timing related to Flash read/write operations is listed here).

#define NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD     0
#define NOR_CMD_LUT_SEQ_IDX_WRITEENABLE        2
#define NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD   4
#define NOR_CMD_LUT_SEQ_IDX_READSTATUSREG      12

#define CUSTOM_LUT_LENGTH        60
const uint32_t customLUT[CUSTOM_LUT_LENGTH] = {
    /* 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),

    /* Page Program - quad 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, 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),
};

flexspi_device_config_t deviceconfig = {
    .flexspiRootClk       = 12000000,
    .flashSize            = 0x4000, /* 16Mb/KByte */
    .CSIntervalUnit       = kFLEXSPI_CsIntervalUnit1SckCycle,
    .CSInterval           = 2,
    .CSHoldTime           = 3,
    .CSSetupTime          = 3,
    .dataValidTime        = 0,
    .columnspace          = 0,
    .enableWordAddress    = 0,
    .AWRSeqIndex          = 0,
    .AWRSeqNumber         = 0,
    // Key configuration for AHB read support
    .ARDSeqIndex          = NOR_CMD_LUT_SEQ_IDX_READ_FAST_QUAD,
    .ARDSeqNumber         = 1,
    .AHBWriteWaitUnit     = kFLEXSPI_AhbWriteWaitUnit2AhbCycle,
    .AHBWriteWaitInterval = 0,
};

void flexspi_nor_flash_init(FLEXSPI_Type *base)
{
    CLOCK_SetRootClockDiv(kCLOCK_Root_Flexspi1, 2);
    CLOCK_SetRootClockMux(kCLOCK_Root_Flexspi1, 0);

    /*Get FLEXSPI default settings and configure the flexspi. */
    flexspi_config_t flexspiconfig;
    FLEXSPI_GetDefaultConfig(&flexspiconfig);

    /*Set AHB buffer size for reading data through AHB bus. */
    flexspiconfig.ahbConfig.enableAHBPrefetch    = true;
    flexspiconfig.ahbConfig.enableAHBBufferable  = true;
    flexspiconfig.ahbConfig.enableReadAddressOpt = true;
    flexspiconfig.ahbConfig.enableAHBCachable    = true;
    flexspiconfig.rxSampleClock                  = kFLEXSPI_ReadSampleClkLoopbackFromDqsPad;
    FLEXSPI_Init(base, &flexspiconfig);

    /* Configure flash settings according to serial flash feature. */
    FLEXSPI_SetFlashConfig(base, &deviceconfig, kFLEXSPI_PortA1);

    /* Update LUT table. */
    FLEXSPI_UpdateLUT(base, 0, customLUT, CUSTOM_LUT_LENGTH);

    /* Do software reset. */
    FLEXSPI_SoftwareReset(base);
}

3.2 General Usage (IPG Write)

First, let’s look at the Flash write function in IPG mode, where the Page Program sub-timing is completed using the FLEXSPI_TransferBlocking() function. This function writes data from src to the IP TX FIFO of size 256 bytes (under the default condition where FlexSPI->MCR0[ATDFEN] = 0). The SEQ_CTL component processes the data cached in the IP TX FIFO and sends it all to the Flash side.

void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{
    // Write Enable sub-timing
    flexspi_nor_write_enable(base, dstAddr);

    flexspi_transfer_t flashXfer;
    flashXfer.deviceAddress = dstAddr;
    flashXfer.port          = kFLEXSPI_PortA1;
    flashXfer.cmdType       = kFLEXSPI_Write;
    flashXfer.SeqNumber     = 1;
    flashXfer.seqIndex      = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD;
    flashXfer.data          = (uint32_t *)(void *)src;
    flashXfer.dataSize      = size;
    // Page program sub-timing
    FLEXSPI_TransferBlocking(base, &flashXfer);

    // Read Status sub-timing
    flexspi_nor_wait_bus_busy(base);

    FLEXSPI_SoftwareReset(base);
}

3.3 Special Usage (AHB Write)

Now let’s modify the Flash write function in IPG mode. First, we need to modify the deviceconfig variable to point the AWRSeqIndex to the PAGEPROGRAM_QUAD position in the LUT, and then replace the FLEXSPI_TransferBlocking() function with AHB write code (using memcpy or pointer assignment). At this point, the data in src will be automatically placed in the AHB TX Buffer of size 64 bytes, and the SEQ_CTL component will process the data cached in the AHB TX Buffer and send it all to the Flash side.

However, there are some limitations. Through testing, it has been found that using memcpy for AHB write can only write data of valid lengths of 1/2/3/4/8 bytes at a time; other data lengths do not meet expectations (for example, copying 5-7 bytes actually only writes the first 4 bytes; copying more than 8 bytes actually only writes the first 8 bytes). This is related to the AHB Burst strategy mentioned in the article “Support for AHB Burst Read Characteristics of the FlexSPI Peripheral in i.MXRT”. FlexSPI will only cache AHB Burst write data into the AHB TX Buffer once per operation, while SEQ_CTL will enable the Flash device’s data processing flow (entering Busy state) each time it operates. Therefore, consecutive AHB burst writes will not produce actual Flash write effects for the latter operation.

flexspi_device_config_t deviceconfig = {
    // Key configuration for AHB write support
    .AWRSeqIndex          = NOR_CMD_LUT_SEQ_IDX_PAGEPROGRAM_QUAD,
    .AWRSeqNumber         = 1,
    // ... other omitted
};

void flexspi_nor_flash_program(FLEXSPI_Type *base, uint32_t dstAddr, const uint32_t *src, uint32_t size)
{
    while (size)
    {
        // Write Enable sub-timing
        flexspi_nor_write_enable(base, 0);

        uint32_t cpyBytes = 0;
        if (size >= 8)      { cpyBytes = 8; }
        else if (size >= 4) { cpyBytes = 4; }
        else                { cpyBytes = size; }
        memcpy((void *)dstAddr, (void *)src, cpyBytes);
        __DSB();

        // Read Status sub-timing
        flexspi_nor_wait_bus_busy(base);

        dstAddr += cpyBytes;
        src += cpyBytes / 4;
        size -= cpyBytes;
    }

    FLEXSPI_SoftwareReset(base);
}

What seems to be a trivial AHB write to Flash actually has its uses. Below, I will discuss the XECC module, and you will understand its significance.

This concludes the introduction to modifying the FlexSPI driver to write NOR Flash using AHB on i.MXRT. Where’s the applause~~~

Leave a Comment