Flash

Flash is non-volatile storage. Compared to RAM, which can be written or read directly, flash must be emptied or erased before being written. Flash supports at least 100K Program-Erase cycles. It can also develop bad blocks when the same blocks are erased frequently.

This document guides on how to use Flash, covering the following topics:

  • Flash Basic Operations

    • Introduction to Flash APIs.

  • Flash Section

    • Help users place data/functions in different areas.

  • Flash Translation Layer

    • Introduction on how to use the FTL, which is designed for lightweight dynamic parameter storage.

  • External Flash

    • Introduces the external Flash from the architecture perspective to the application level.

It is recommended to use the FTL rather than the flash driver for user data storage. If it is really needed to operate flash directly, APIs with the fmc_ prefix can be used.

External flash is supported. Multiple choices are available for basic functions like single-mode read, write and erase. However, the flash driver also provides some advanced functions, such as quad mode, deep power-down, and flash block protection. Not all flash supports these functions, so if needed, Realtek can provide an Approved Vendor List.

Basic Operations

Flash provides a set of interfaces in sdk\inc\hal\platform\fmc_api.h.
This chapter introduces the standard functions: read, write and erase.

Flash ensures task safety for these functions. This means that all flash basic operations cannot be executed at the same time. That’s because there is only one resource (flash) and only one path (flash controller) to access it. This helps prevent multiple tasks or interrupts from destroying the atomicity of the SPIC command sequence.

Read

Function

fmc_flash_nor_read

Prototype

bool fmc_flash_nor_read(uint32_t addr, void *data, uint32_t len);

Description

Read flash data.

Instruction

This function reads data from the flash in user mode.

Auto DMA Read

Function

fmc_flash_nor_auto_dma_read

Prototype

bool fmc_flash_nor_auto_dma_read(uint32_t src, uint32_t dst, uint32_t len, FMC_FLASH_NOR_ASYNC_CB cb);

Description

Read flash data by DMA.

Instruction

This function reads data from the flash in auto mode by DMA.

If users need to read a large amount of data (such as 1K Bytes), this API is recommended. DMA read is faster than fmc_flash_nor_read().

Write

Function

fmc_flash_nor_write

Prototype

bool fmc_flash_nor_write(uint32_t addr, void *data, uint32_t len);

Description

Write data to flash.

Instruction

This function writes data to the flash in user mode.

This function modifies the flash data through SPIC rather than the bus command, so this operation will not affect the data in the Cache. In this case, auto_read will return the old data in the Cache. Users can flush the Cache or use fmc_flash_nor_read() to get the correct data after running fmc_flash_nor_write().

Erase

Function

fmc_flash_nor_erase

Prototype

bool fmc_flash_nor_erase(uint32_t addr, FMC_FLASH_NOR_ERASE_MODE mode);

Description

Erase flash.

Instruction

Users should call flash erase before write.

This function supports three erase modes:

  1. FMC_FLASH_NOR_ERASE_CHIP: Erase whole chip.

  2. FMC_FLASH_NOR_ERASE_SECTOR: Erase 4K Bytes. The input address must be 4K aligned.

  3. FMC_FLASH_NOR_ERASE_BLOCK: Erase 64K Bytes. The input address must be 64K aligned.

Section Usage

Functions are placed on flash by default. Section is provided for users to move them to the ram area. See sdk\inc\platform\section.h.

#define FLASH_HEADER     __attribute__((section(".flash.header")))     __attribute__((used))
#define FLASH_HEADER_EXT __attribute__((section(".flash.header_ext"))) __attribute__((used))
#define RAM_TEXT_SECTION __attribute__((section(".ram_text")))
#define SHM_DATA_SECTION __attribute__((section(".shm.data")))
#define ISR_TEXT_SECTION __attribute__((section(".isr.text"))) /*not very urgent isr*/
  • FLASH_HEADER & FLASH_HEADER_EXT
    FLASH_HEADER & FLASH_HEADER_EXT are provided for the image header.

    FLASH_HEADER const T_IMG_HEADER_FORMAT img_header =
    {
    	...
    }
    
  • RAM_TEXT_SECTION
    Functions with the RAM_TEXT_SECTION prefix will be placed in RAM.

    RAM_TEXT_SECTION bool app_io_msg_send(T_IO_MSG *io_msg)
    {
    	T_EVENT_TYPE event = EVENT_IO_TO_APP;
    	bool ret = false;
    
    	if (os_msg_send(audio_io_queue_handle, io_msg, 0) == true)
    	{
    		ret = os_msg_send(audio_evt_queue_handle, &event, 0);
    	}
    
    	if (ret == false)
    	{
    		APP_PRINT_ERROR3("app_io_msg_send failed, type = %x, subtype = %x, param = %x",
    						 io_msg->type, io_msg->subtype, io_msg->u.param);
    	}
    
    	return ret;
    }
    
  • SHM_DATA_SECTION
    Functions with the SHM_DATA_SECTION prefix will be placed in DSP share memory. Please make sure DSP share memory is ready first.

    SHM_DATA_SECTION uint8_t name_buf[NAME_BUF_SIZE];
    
  • ISR_TEXT_SECTION
    ISR functions need to be placed in RAM because if the flash is being erased, XIP would fail to execute properly, leading to a hard fault.
    The ISR_TEXT_SECTION is provided for users to run XIP in ISR. However, there is a limitation: users must ensure that the ISR handler includes this prefix.

    static ISR_TEXT_SECTION void GPIOA0_Handler(void)
    {
    	imp_gpio_handler(GPIOA, 0, BIT0, hal_gpio_sw_context.gpio_a0_callback);
    }
    
    static ISR_TEXT_SECTION void imp_gpio_handler(GPIO_TypeDef *GPIOx, uint8_t gpio_num, uint32_t gpio_pin, P_GPIO_CBACK callback)
    {
    	GPIOx_ClearINTPendingBit(GPIOx, gpio_pin);
    	if (callback)
    	{
    		callback(hal_gpio_sw_context.context[gpio_num]);
    	}
    	else
    	{
    		IO_PRINT_ERROR1("empty isr callback for gpioa%u", gpio_num);
    	}
    }
    

    If the RAM is enough, users could put the ISR_TEXT_SECTION to RAM in the .sct file without modifying any code.

    Put functions with ISR_TEXT_SECTION prefix on Flash:

    	FLASH_TEXT +0;
    	{	
    		startup_rtl87x3e.o (RESET, +First)
    #if (FEATURE_RAM_CODE == 0)
    		* (+RO)
    #endif
    		* (.isr.text)
    	}
    

    Move functions with ISR_TEXT_SECTION prefix to RAM:

    	RAM_TEXT APP_BUFFER_ON_ADR APP_BUFFER_ON_SIZE
    	{
    #if FEATURE_RAM_CODE
    		.ANY (+RO)
    #endif
    		* (.ram_text)
    		* (.isr.text)
    	}
    

Flash Transport Layer

Flash Transport Layer is used as an abstraction layer for users to read & write data on flash. The location of FTL POOL in flash can be found in Flash Layout.

The purpose of FTL is to make it simpler to change the data stored in Flash:

Traditionally, each time users want to change data on flash, the flash needs to be erased first. If flash is modified frequently, a lot of time will be wasted on erasing. FTL replaces the physical address with the logical address to access flash. FTL records data and its logical address on flash. If a user wants to modify the data, FTL writes a new record rather than modifying the old record. In this way, users do not need to erase flash. When the FTL detects that there is not enough space to write a new record, it will clear some expired records. This process is called Garbage Collection.

Since FTL is designed for lightweight dynamic parameter storage, heavyweight and read-only data should not use it.

FTL uses modules to distinguish different data. FTL has a default module to ensure basic functionality. Users can save data in the default module, or apply for a new module.

Default FTL Module

The data in the default module can be divided into the following regions. FTL layout is shown in the following figure:

  • Local stack information storage space
    Range: 0x0000 ~ 0x003b
    This region is used to store local stack information including device name, device appearance, and lock IRK.

  • Legacy key storage space
    Range: 0x003c ~ (0x50 + max_legacy_paired_device * 24 - 1)
    This region is used to store legacy key information. max_legacy_paired_device: It can be configured by MCUConfig Tool, and the default value is 8.

  • LE key storage space
    Range: (0x50 + max_legacy_paired_device * 24) ~ (0x50 + max_legacy_paired_device * 24 + 0x14 + max_le_paired_device * (148 + 4 * gatt_server_ccc_bits_count) - 1)
    This region is used to store LE key information. max_le_paired_device: It can be configured by MCUConfig Tool, and the default value is 4. gatt_server_ccc_bits_count: It can be configured by MCUConfig Tool, and the default value is 16.

  • Reserved space
    Range: (0x50 + max_legacy_paired_device * 24 + 0x14 + max_le_paired_device * (148 + 4 * gatt_server_ccc_bits_count)) ~ 0xA17
    This region is reserved space. Because the variables max_legacy_paired_device and max_le_paired_device are configurable, this region can be used to expand Legacy key storage space and LE key storage space. When variables max_legacy_paired_device and max_le_paired_device are determined, this region can be used by APP.

  • FTL module info space
    Range: 0x0A18 ~ 0x0A5B
    This region records the FTL EXT module’s info.

  • GFPS finder space
    Range: 0x0A5C ~ 0x0A8B
    This region can be used by APP to store GFPS Finder information.

  • GFPS storage space
    Range: 0x0A8C ~ 0x0BFF
    This region can be used by APP to store GFPS information.

  • APP storage space
    Range: 0x0C00 ~ the maximum size of the default FTL module
    This region can be used by APP to store information.

The maximum size of the default module should be set by the interface: ftl_init() . An easy way to calculate the flash area size is (logical_size * 2). FTL uses mapping_table to save time, default FTL module takes nearly (logical_size / 2) sram.

FTL APIs of Default FTL Module

Default FTL APIs could be found in sdk\inc\hal\platform\ftl.h.

Read

Function

ftl_load_from_storage

Prototype

extern uint32_t(*ftl_load_from_storage)(void *pdata_tmp, uint16_t offset, uint16_t size);

Description

Read data from the default FTL module

Instruction

The offset that can be used by the APP starts from 0x0C00 and must be 4 Bytes aligned.

Write

Function

ftl_save_to_storage

Prototype

extern uint32_t(*ftl_save_to_storage)(void *pdata_tmp, uint16_t offset, uint16_t size);

Description

Write data to the default FTL module

Instruction

The offset that can be used by the APP starts from 0x0C00 and must be 4 Bytes aligned.

A brief demo:

static void app_cfg_load(void)
{
    ...
    if (app_cfg_nv.hdr.sync_word != DATA_SYNC_WORD)
    {
        // Load factory reset bit first when MPPG Tool factory reset
        if (app_cfg_nv.hdr.length == 0)
        {
            ftl_load_from_storage(&app_cfg_nv.eq_idx_anc_mode_record, APP_RW_DATA_ADDR + FACTORY_RESET_OFFSET,
                                  4);
        }



       app_cfg_reset();
    }
    ...
}



uint32_t app_cfg_store(void *pdata, uint16_t size)
{
...

   return ftl_save_to_storage(pdata, offset + APP_RW_DATA_ADDR, size);
}


How to Add an FTL Module

FTL pool supports up to 7 modules. The default FTL module takes up one.

FTL module APIs are provided in sdk\inc\hal\platform\ftl.h.

Create an FTL module

Function

ftl_init_module

Prototype

int32_t ftl_init_module(char *module_name, uint16_t malloc_size, uint8_t block_len)

Description

Create an FTL module

Instruction

The malloc_size is a logical size. And the block_len must be an integer multiple of 4 and cannot exceed 128.

Before adding a new FTL module, please ensure that the flash size of the FTL POOL is sufficient. Refer to How to Estimate the Min Flash Size of FTL POOL . If (((block_len + 4) % 8) == 0), the new module will occupy (malloc_size / block_len * (block_len + 4)) flash area. Otherwise, it will occupy (malloc_size / block_len * (block_len + 8)) flash area. The mapping_table for this new FTL module will occupy (malloc_size / block_len * 2) SRAM.

Note

The first 4Bytes of module_name should be unique.

Read

Function

ftl_load_from_module

Prototype

int32_t ftl_load_from_module(char *module_name, void *pdata, uint16_t offset, uint16_t size);

Description

Read data from an FTL module

Instruction

The offset that can be used starts from 0

Write

Function

ftl_save_to_module

Prototype

int32_t ftl_save_to_module(char *module_name, void *pdata, uint16_t offset, uint16_t size);

Description

Write data to an FTL module

Instruction

The offset that can be used starts from 0

The block_len in ftl_init_module() is the smallest unit for reading and writing data in the current FTL module. Applications may need additional operations on the read/write data if necessary.

Demo:

#define EQ_EXT_FTL_PARTITION_NAME "EQ_FTL"
#define EQ_SIZE     (0x2800)
#define EQ_EXT_FTL_BLOCK_LEN     68

int32_t eq_ext_ftl_partition_init(const T_STORAGE_PARTITION_INFO *info)
{
    return ftl_init_module(EQ_EXT_FTL_PARTITION_NAME, EQ_SIZE, EQ_EXT_FTL_BLOCK_LEN);
}

int32_t eq_ext_ftl_partition_write(const T_STORAGE_PARTITION_INFO *info, uint32_t offset,
                                   uint32_t len, void *buf)
{
    return ftl_save_to_module(EQ_EXT_FTL_PARTITION_NAME, (void *)buf, offset, len);
}

int32_t eq_ext_ftl_partition_read(const T_STORAGE_PARTITION_INFO *info, uint32_t offset,
                                  uint32_t len, void *buf)
{
	return ftl_load_from_module(EQ_EXT_FTL_PARTITION_NAME, (void *)buf, offset, len);
}

void eq_ext_ftl_storage_init(void)
{
    static const T_STORAGE_PARTITION_INFO eq_partitions[] =
    {
        {
            .name = EQ_EXT_FTL_PARTITION_NAME,
            .address = NULL,
            .size = NULL,
            .perm = STORAGE_PERMISSION_READ | STORAGE_PERMISSION_WRITE,
            .media_type = STORAGE_MEDIA_TYPE_NOR,
            .content_type = STORAGE_CONTENT_TYPE_RW_DATA,
            .init = eq_ext_ftl_partition_init,
            .read = eq_ext_ftl_partition_read,
            .write = eq_ext_ftl_partition_write,
            .erase = NULL,
            .async_read = NULL,
            .async_write = NULL,
            .async_erase = NULL,
        },
    };

    storage_partition_init(eq_partitions, sizeof(eq_partitions) / sizeof(eq_partitions[0]));
}

FTL POOL

All FTL modules save data in the same flash area (FTL POOL), which can be found in flash_map.h. Users can modify it directly, or use the Flash Map Generate Tool. So the address and size of the FTL pool are determined by APP and cannot be modified by any other bins or tools.

In principle, the new FTL POOL must include all areas of the old FTL POOL. Otherwise, FTL data would be lost. In these conditions, users need to do a Factory Reset.

#define FTL_ADDR                        0x021F9000
#define FTL_SIZE                        0x00007000  // 28K Bytes

How to Estimate the Min Flash Size of FTL POOL

  • Garbage collection
    Use 8K to do GC.

  • Default FTL module
    Takes up (logical_size * 2) flash area.

  • EXT FTL module
    Takes up (logical_size / block_len * (block_len + (block_len % 8) ? 8 : 4)) flash area.

The size of the FTL POOL should be larger than the total size above, must be 4K aligned.

FTL Cache

FTL has a cache that is on the heap. Users can enable the FTL cache by the interface bool ftl_cache_init(uint16_t cache_size).

The parameter in ftl_cache_init() specifies the size of the FTL cache and is recommended to be set to at least 128 bytes.

example:

ftl_cache_init(128);

After enabling FTL cache, FTL saves data in the FTL cache area when there is not enough space to write a new record.

  • Advantages:
    Reduces current write’s time cost.

    If there is no space for a new record, FTL needs to execute GC. GC calls flash write & erase operation, this costs at least 50ms+. So if the current flow is time sensitive, it is suggested to enable FTL cache.

  • Disadvantages:
    Data loss in an abnormal case.

    Because data is in the FTL cache, not on flash, it will be lost if power is lost or an abnormal reboot happens.

External Flash

Besides the MCM flash embedded in the SoC, some Realtek Bluetooth SoCs support additional external flash if the desired Flash size is larger than the embedded one. Please contact Realtek to check whether specific ICs support it.

The chapter will take RTL8773D as an example to introduce it from the architecture perspective to the application level when the SDK user wants to add more flashes into the system.

Note

The material in this chapter is captured from the latest RTL8773D document for external customers, but this may be subject to change. So please refer to the latest ones in case any changes are made.

System Architecture

The system architecture of RTL87x3D is shown in the following figure.

The picture above is captured from the system overview of RTL8773D in the data sheet, and it clearly shows that SPIC0 is reserved for the MCM flash and there are still 3 SPICs left for external storage connections.

SPIC

SPIC is designed to transmit/receive data to/from SPI flash memories. Typically, to operate on the flash, users must set/read registers and transmit serial data to the flash. The registers may vary across different flash models from different vendors.

As mentioned in the data sheet, the default priority order among all these SPICs is SPIC3>SPIC2>SPIC1. In the demo code, SPIC3 is used by default for external flash connections, as it is better suited for high-speed applications requiring up to 80 MHz and a larger address space of up to 256 MB.

#if F_APP_EXT_FLASH_SUPPORT
static bool ext_flash_init(void)
{
    return fmc_flash_nor_init(FMC_FLASH_NOR_IDX3);
}
#endif

Pinmux

Pins for these SPICs are predefined and cannot be changed. So once any SPIC is assigned to any external storage usage, the corresponding pins have to be reserved and connected to the external storage. Here is the pinmux setting for SPIC3, and if wanting to use any SPIC other than SPIC3, please refer to the data sheet for its pre-allocated pinmux.

All these pins will be automatically initialized to these specified pinmux once the flash is initialized as shown in the following table.

Pin Name

Function

P3_2

SPIC3_CLK (master only)

P3_3

SPIC3_CSN (master only)

P3_4

SPIC3_SIO0 (master 4-bit mode)

P3_5

SPIC3_SIO1 (master 4-bit mode)

P3_6

SPIC3_SIO2 (master 4-bit mode)

P3_7

SPIC3_SIO3 (master 4-bit mode)

Note

All these pins are in VDDIO3, so make sure VDDIO3 is powered.

Flash Address

Once the external storage connects to the SoC via SPIC, the flash address will be allocated, and here is the flash space allocated for each SPIC:

#define FMC_MAIN0_ADDR                                      (0x02000000) //for spic0
#define FMC_MAIN0_UNCACHEABLE_ADDR                          (0x04000000)
#define FMC_MAIN1_ADDR                                      (0x06000000) //for spic1
#define FMC_MAIN1_UNCACHEABLE_ADDR                          (0x06000000)
#define FMC_MAIN2_ADDR                                      (0x08000000) //for spic2
#define FMC_MAIN2_UNCACHEABLE_ADDR                          (0x08000000)
#define FMC_MAIN3_ADDR                                      (0x10000000) //for spic3
#define FMC_MAIN3_UNCACHEABLE_ADDR                          (0x10000000)

#define FMC_MAIN0_SIZE                                      ( 32 * 1024 * 1024)
#define FMC_MAIN1_SIZE                                      ( 32 * 1024 * 1024)
#define FMC_MAIN2_SIZE                                      (  8 * 1024 * 1024)
#define FMC_MAIN3_SIZE                                      (256 * 1024 * 1024)

As mentioned in the definition above, each SPIC has both a cache address and a non-cache address. However, they are the same for all SPICs except SPIC0. This means that only SPIC0 has cache enabled for the MCM flash, and currently, none of the external storage options have cache enabled.

Flash address can also help specify which SPIC is to be accessed. Here is the explanation with the example code:

  1. Read access MCM flash via cache which would be more efficient if cache is hit.

       fmc_flash_nor_read(FMC_MAIN0_ADDR + 0x1000, buffer, 0x1000)
    
  2. Write access MCM flash without cache.

       fmc_flash_nor_write(FMC_MAIN0_UNCACHEABLE_ADDR + 0x1000, buffer, 0x1000)
    
  3. Read access SPIC3 flash.

       fmc_flash_nor_read(FMC_MAIN3_ADDR + 0x1000, buffer, 0x1000)
    

Note

  1. Whether it supports cache in other Bluetooth SoC series, and how it supports cache, please refer to their datasheet.

  2. Cache is not available when accessing other SPICs.

  3. DMA can be used as well to access flash to reduce CPU load.

Conclusion

To summarize, to support external flash, the following steps should be followed:

  1. Reserve SPIC pre-allocated pins.

  2. Call the initialization API mentioned above.

  3. Use the flash API with a specified address within the SPIC3 range to access external flash.