一、整體初始化流程
我們知道外部串行NOR Flash是接到i.MXRT的FlexSPI外設引腳上,有時串行NOR Flash啟動也叫FlexSPI NOR啟動。關于FlexSPI NOR啟動流程,i.MXRT1050參考手冊System Boot章節有如下流圖,藍框之外的流程屬于常規i.MXRT啟動XIP App流程,是個通用流程。藍框之內才是具體FlexSPI初始化步驟,這個步驟概括得比較精煉。
為了讓大家對FlexSPI NOR設備啟動初始化流程有個更具體的概念,痞子衡重新畫了一張更詳細的流程圖,圖中灰底框里描述得是FlexSPI初始化流程,痞子衡將其分解成了六步,我們有必要深入這六步初始化流程。
二、分解初始化流程
2.1 復位Flash芯片(可選)
第一步是嘗試復位Flash芯片,這步是可選的,在fuse_0x6e0[7]里配置,默認是不使能的。復位Flash目的是為了讓Flash處于一個確定的初始狀態,方便i.MXRT BootROM去配置訪問。為什么要強調Flash的初始狀態,因為很多時候i.MXRT未必是冷啟動(上電啟動),也有可能是軟復位啟動(比如調用NVIC_SystemReset),這時候外部Flash已經被軟復位前執行過的BootROM甚至用戶App配置過,因此Flash的狀態可能不是上電初始狀態(一般來說板級設計里Flash的RESET#引腳要么懸空,要么連接i.MXRT的POR#引腳),這可能會影響軟復位后BootROM去再次配置啟動這塊不定態的Flash。
fuse 0x6e0[7] - FLEXSPI_RESET_PIN_EN
正常的Flash都提供了RESET#引腳來實現跟上電復位一樣的功能,對于普通8-pin的QSPI Flash,這個RESET#引腳往往是跟信號線IO3復用的(僅在QE bit沒使能情況下有效),而對于16-pin的QSPI Flash或者HyperFlash,其RESET#引腳都是獨立的。
BootROM就是借助了Flash的RESET#引腳來實現的復位操作,實現代碼比較簡單,i.MXRT1050 BootROM直接指定了GPIO1[9]當做復位信號線,板級設計里需要你將GPIO1[9]連到Flash的RESET#引腳,然后BootROM就是簡單地拉低GPIO1[9]即可。RESET#信號都是低電平有效,BootROM直接拉低這個信號持續250us,這個低電平持續時間對于復位來說是夠夠的,很多Flash數據手冊里其實僅要求幾us即可。
- 備注:對于BootROM的Flash復位功能來說,主要適用有獨立RESET#引腳的Flash。
#define RESET_PAD_IDX kIOMUXC_SW_MUX_CTL_PAD_GPIO_AD_B0_09
#define RESET_PIN_MUX IOMUXC_SW_MUX_CTL_PAD_MUX_MODE(5)
#define RESET_PIN_GPIO GPIO1
#define RESET_PIN_INDEX 9
if ((OCOTP- >MISC_CONF1 & 0x80) > > 7)
{
// Set pinmux as GPIO
IOMUXC- >SW_MUX_CTL_PAD[RESET_PAD_IDX] = RESET_PIN_MUX;
// Set GPIO to output mode
RESET_PIN_GPIO- >GDIR |= (1U<
2.2 準備初始FDCB配置塊
第二步是準備一個初始的FDCB配置塊(即flexspi_nor_config_t,大小為512字節),這個初始FDCB配置塊將被用來做FlexSPI外設的第一次初始化,目的是為了能夠保證FlexSPI初始化之后CPU能夠使用AHB方式正常讀取Flash(訪問性能不要求最高,但求穩定訪問)。這個初始FDCB并不是一個完全定死的配置塊,部分值也是根據fuse來配置的,一共有三處fuse位置,其中最重要的是FLASH_TYPE:
fuse 0x440[20] - QSPI_2ND_BOOTPIN_ENABLE,決定是否啟動第二組FlexSPI pinmux
fuse 0x450[10:8] - FLASH_TYPE,決定當前連接的Flash類型
fuse 0x470[30:24] - DELAY_CELL_NUM,設置Flash讀訪問時序數據線有效時間
初始FDCB配置塊中僅給memConfig設了值,這個memConfig才是用于配置FlexSPI外設本身。如下部分賦值是固定的FDCB設置,不受fuse影響,從這個固定配置你可以看到,BootROM假定了所有外接Flash都是128MB,且訪問時鐘(SCK)速度能支持30MHz,不要對這個假定感到焦慮,它只是用于FlexSPI第一次初始化,目的只求能正常訪問Flash前4KB即可:
flexspi_nor_config_t config;
memset(config, 0, sizeof(config));
// 公共的FDCB配置
config.memConfig.tag = FLEXSPI_CFG_BLK_TAG;
config.memConfig.version = FLEXSPI_CFG_BLK_VERSION;
config.memConfig.deviceType = kFlexSpiDeviceType_SerialNOR;
config.memConfig.sflashA1Size = 128UL*1024*1024;
config.memConfig.serialClkFreq = kFlexSpiSerialClk_30MHz;
config.memConfig.dataHoldTime = 3;
config.memConfig.dataSetupTime = 3;
config.memConfig.timeoutInMs = 1000;
然后便是從fuse里獲取flashType,根據具體flashType來對初始FDCB配置塊做進一步動態賦值,這進一步賦值才用于區分不同Flash種類(Pad數量、DQS信號屬性、最重要的lookupTable等)。
// 從fuse里獲取flash類型
uint32_t flashType;
if ((OCOTP- >CFG3 & 0x100000) > > 20)
{
flashType = 7;
}
else
{
flashType = (OCOTP- >CFG4 & 0x700) > > 8;
}
上圖中最重要的FDCB賦值是config.memConfig.lookupTable,它是FlexSPI外設需要的核心配置,有了這個配置,CPU便可以直接從AHB總線讀取Flash的內容,因為FlexSPI會自動解析AHB總線讀請求然后翻譯成具體FlexSPI讀時序,底層讀時序需要的命令、地址字節數、DUMMY周期都在lookupTable里。BootROM預存了如下6大類Flash的lookupTable:
// Dedicated 3Byte Address Read(0x03), 24bit address
static const uint32_t s_dedicated3bRead[4] = {
FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x18),
FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0),
0,
0
};
// Dedicated 4Byte Address Read(0x13), 32 bit address
static const uint32_t s_dedicated4bRead[4] = {
FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x13, RADDR_SDR, FLEXSPI_1PAD, 0x20),
FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0),
0,
0
};
// HyperFlash Read
static const uint32_t s_hyperflashRead[4] = {
FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xA0, RADDR_DDR, FLEXSPI_8PAD, 0x18),
FLEXSPI_LUT_SEQ(CADDR_DDR, FLEXSPI_8PAD, 0x10, DUMMY_RWDS_DDR, FLEXSPI_8PAD, 0x0c),
FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_8PAD, 0),
0
};
// MXIC Octal DDR read
static const uint32_t s_mxicOctDdrRead[4] = {
FLEXSPI_LUT_SEQ(CMD_DDR, FLEXSPI_8PAD, 0xEE, CMD_DDR, FLEXSPI_8PAD, 0x11),
FLEXSPI_LUT_SEQ(RADDR_DDR, FLEXSPI_8PAD, 0x20, DUMMY_DDR, FLEXSPI_8PAD, 0xc),
FLEXSPI_LUT_SEQ(READ_DDR, FLEXSPI_8PAD, 0x04, STOP, FLEXSPI_8PAD, 0),
0
};
// Micron Octal DDR read
static const uint32_t s_micronOctDdrRead[4] = {
FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_8PAD, 0xFD, RADDR_DDR, FLEXSPI_8PAD, 0x20),
FLEXSPI_LUT_SEQ(DUMMY_DDR, FLEXSPI_8PAD, 0x8, READ_DDR, FLEXSPI_8PAD, 0x04),
0,
0
};
// Adesto Octal DDR read
static const uint32_t s_adestoOctDdrRead[4] = {
FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_8PAD, 0x0B, RADDR_DDR, FLEXSPI_8PAD, 0x20),
FLEXSPI_LUT_SEQ(DUMMY_DDR, FLEXSPI_8PAD, 0x8, READ_DDR, FLEXSPI_8PAD, 0x04),
0,
0
};
2.3 第一次FlexSPI初始化
第三步就是利用上述配置完成的初始FDCB塊對FlexSPI外設進行第一次初始化,就是下面代碼,這個流程跟官方SDK里的flexspi_nor_flash_init()大同小異,這里不予具體展開。如果在這里初始化就返回失敗(這里一般不會失敗,因為僅僅是FlexSPI外設自身初始化,并不涉及操作外部Flash芯片的動作),BootROM則直接退出FlexSPI NOR設備啟動,轉入SDP下載。
#define FLEXSPI_INSTANCE 0
uint32_t instance = FLEXSPI_INSTANCE;
status_t status = flexspi_init(instance, (flexspi_mem_config_t *)(&config));
if (status != kStatus_Success)
{
return status;
}
flexspi_update_lut(instance, 0, &config.memConfig.lookupTable, 1);
2.4 若干善后工作
上述第一次FlexSPI初始化一般都會成功的,但這并不代表fuse里的flashType等配置跟板子上Flash型號是匹配的,也就是說初始FDCB配置塊此時還沒有被充分驗證其是否適用板載Flash型號。
FlexSPI第一次初始化結束后,為了保證后續能正常AHB訪問,BootROM里做了一些善后工作,主要是兩件事:
- 做一些訪問前的延時:根絕fuse 0x450[3:2] - HOLD TIME來調用microseconds_delay()做延時,以使FlexSPI外設完全準備好。
- 做一次無效AHB訪問:類似這樣的代碼 volatile uint32_t dummy = *(uint32_t *)0x60000000;,無效AHB讀可以使Flash退出continuous read模式
2.5 獲取用戶FDCB配置塊
善后工作結束之后,此時CPU應該可以通過AHB正常訪問Flash了,這個階段我們只需要從Flash的偏移0地址處讀取用戶FDCB,驗證用戶FDCB是否存在,這里才是對前面初始FDCB配置塊以及第一次FlexSPI外設初始化的真正考驗。
驗證用戶FDCB是否存在就是簡單讀取FDCB的前四個字節(tag),驗證這個tag是否合法。如果第一次驗證tag不成功(有可能是FlexSPI配置不正確,也有可能是用戶FDCB不存在),會嘗試做一次三字節地址切換到四字節地址的LUT更新(僅適用QSPI Flash),然后做第二次tag讀取驗證,如果此時還是驗證失敗(大概率是不存在用戶FDCB了),BootROM則直接退出FlexSPI NOR設備啟動,轉入SDP下載。
#define FlexSPI_AMBA_BASE (0x60000000U)
#define FLASH_BASE FlexSPI_AMBA_BASE
// 使用三字節地址的LUT對Flash進行初次AHB訪問
flexspi_clear_cache(FLEXSPI_INSTANCE);
flexspi_nor_config_t *pConfig = (flexspi_nor_config_t *)FLASH_BASE;
if (pConfig- >memConfig.tag != FLEXSPI_CFG_BLK_TAG)
{
// 因為拿不到用戶FDCB的tag,嘗試切換使用四字節地址的LUT
if (flashType == 0)
{
flexspi_update_lut(FLEXSPI_INSTANCE, 0, s_basic4bRead, 1);
}
flexspi_clear_cache(FLEXSPI_INSTANCE);
pConfig = (flexspi_nor_config_t *)FLASH_BASE;
}
// 對Flash進行第二次AHB訪問,再次確認能否拿到用戶FDCB的tag
if (pConfig- >memConfig.tag != FLEXSPI_CFG_BLK_TAG)
{
return kStatus_Fail;
}
上面代碼里有flexspi_clear_cache()操作,這個其實就是利用FLEXSPI0->MCR0[SWRESET]做一個外設級別的軟復位,另外代碼里還涉及到一個四字節地址QSPI Flash的LUT表,即如下所示:
// Basic read with 32bit address
static const uint32_t s_basic4bRead[4] = {
FLEXSPI_LUT_SEQ(CMD_SDR, FLEXSPI_1PAD, 0x03, RADDR_SDR, FLEXSPI_1PAD, 0x20),
FLEXSPI_LUT_SEQ(READ_SDR, FLEXSPI_1PAD, 0x04, STOP, FLEXSPI_1PAD, 0),
0,
0
};
2.6 第二次FlexSPI初始化
到了這里,基本代表第一次FlexSPI初始化是正確且可用的,并且能夠拿到有效的用戶FDCB配置塊。這時候就是利用用戶FDCB配置塊對FlexSPI外設做第二次初始化,初始化代碼流程跟第一次初始化是一模一樣的。
這個第二次初始化是非常有必要的,因為它反映了用戶的真實需求,用戶FDCB配置塊里會準確描述板載Flash的全面特性(訪問速度,真實存儲空間大小,特殊定制LUT等等),這些信息必須由用戶來提供。
需要注意的是,第二次FlexSPI初始化返回成功并不代表用戶FDCB配置塊一定就是正確的,還是那句話,這僅僅是對FlexSPI外設自身的初始化。后續常規App解析流程里才是對這個用戶FDCB配置塊的真正考驗。
-
GPIO
+關注
關注
16文章
1196瀏覽量
51934 -
AHB總線
+關注
關注
0文章
18瀏覽量
9470 -
QSPI接口
+關注
關注
0文章
14瀏覽量
3339
發布評論請先 登錄
相關推薦
評論