大家好,我是痞子衡,是正經(jīng)搞技術(shù)的痞子。今天痞子衡給大家分享的是深入 i.MXRT1050 系列 ROM 中串行 NOR Flash 啟動初始化流程。
從外部串行 NOR Flash 啟動問題是 i.MXRT 系列開發(fā)最高頻的話題,無論是開發(fā)調(diào)試 XIP 應(yīng)用程序階段還是最終產(chǎn)品量產(chǎn)階段都繞不開 NOR Flash 選型以及為它設(shè)計一個匹配的 FDCB 配置塊。如果不了解 FDCB 是什么,先去看痞子衡之前的文章 《Bootable image 格式與加載》。
實(shí)際開發(fā)過程中,影響串行 NOR Flash 正常下載 / 啟動的因素有很多,痞子衡已經(jīng)寫過三篇:《16MB 以上使用不當(dāng)因素》、《SFDP 因素》、《QE bit 因素》,列舉了三個不同因素,當(dāng)然這都是出了問題,具體調(diào)試分析才定位出來的,顯然還有很多未知因素等待陸續(xù)被發(fā)掘。
如果總是被動去解決問題,那問題是解不完的。不如我們主動出擊,摸清 i.MXRT 啟動串行 NOR Flash 設(shè)備到底是怎樣的初始化流程,搞清這個流程,將來定位啟動問題才能游刃有余,話不多說,開始今天的主題。
- 備注:本文主角是 i.MXRT1050,但內(nèi)容也同樣適用 i.MXRT1020/1015,對于 i.MXRT1010 也算適用但有兩處微小差別(冗余 App 啟動支持,F(xiàn)lash 上電等待時間處理)。
?
一、整體初始化流程
我們知道外部串行 NOR Flash 是接到 i.MXRT 的 FlexSPI 外設(shè)引腳上,有時串行 NOR Flash 啟動也叫 FlexSPI NOR 啟動。關(guān)于 FlexSPI NOR 啟動流程,i.MXRT1050 參考手冊 System Boot 章節(jié)有如下流圖,藍(lán)框之外的流程屬于常規(guī) i.MXRT 啟動 XIP App 流程,是個通用流程。藍(lán)框之內(nèi)才是具體 FlexSPI 初始化步驟,這個步驟概括得比較精煉。
為了讓大家對 FlexSPI NOR 設(shè)備啟動初始化流程有個更具體的概念,痞子衡重新畫了一張更詳細(xì)的流程圖,圖中灰底框里描述得是 FlexSPI 初始化流程,痞子衡將其分解成了六步,我們有必要深入這六步初始化流程。
?
二、分解初始化流程
2.1 復(fù)位 Flash 芯片(可選)
第一步是嘗試復(fù)位 Flash 芯片,這步是可選的,在 fuse_0x6e0[7]里配置,默認(rèn)是不使能的。復(fù)位 Flash 目的是為了讓 Flash 處于一個確定的初始狀態(tài),方便 i.MXRT BootROM 去配置訪問。為什么要強(qiáng)調(diào) Flash 的初始狀態(tài),因?yàn)楹芏鄷r候 i.MXRT 未必是冷啟動(上電啟動),也有可能是軟復(fù)位啟動(比如調(diào)用 NVIC_SystemReset),這時候外部 Flash 已經(jīng)被軟復(fù)位前執(zhí)行過的 BootROM 甚至用戶 App 配置過,因此 Flash 的狀態(tài)可能不是上電初始狀態(tài)(一般來說板級設(shè)計里 Flash 的 RESET#引腳要么懸空,要么連接 i.MXRT 的 POR#引腳),這可能會影響軟復(fù)位后 BootROM 去再次配置啟動這塊不定態(tài)的 Flash。
fuse 0x6e0[7] - FLEXSPI_RESET_PIN_EN
正常的 Flash 都提供了 RESET#引腳來實(shí)現(xiàn)跟上電復(fù)位一樣的功能,對于普通 8-pin 的 QSPI Flash,這個 RESET#引腳往往是跟信號線 IO3 復(fù)用的(僅在 QE bit 沒使能情況下有效),而對于 16-pin 的 QSPI Flash 或者 HyperFlash,其 RESET#引腳都是獨(dú)立的。
BootROM 就是借助了 Flash 的 RESET#引腳來實(shí)現(xiàn)的復(fù)位操作,實(shí)現(xiàn)代碼比較簡單,i.MXRT1050 BootROM 直接指定了 GPIO1[9]當(dāng)做復(fù)位信號線,板級設(shè)計里需要你將 GPIO1[9]連到 Flash 的 RESET#引腳,然后 BootROM 就是簡單地拉低 GPIO1[9]即可。RESET#信號都是低電平有效,BootROM 直接拉低這個信號持續(xù) 250us,這個低電平持續(xù)時間對于復(fù)位來說是夠夠的,很多 Flash 數(shù)據(jù)手冊里其實(shí)僅要求幾 us 即可。
- 備注:對于 BootROM 的 Flash 復(fù)位功能來說,主要適用有獨(dú)立 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<
????//?High
????RESET_PIN_GPIO->DR_SET?=?(1U<????sw_delay_us(250);
????//?Low
????RESET_PIN_GPIO->DR_CLR?=?(1U<????sw_delay_us(250);
????//?High
????RESET_PIN_GPIO->DR_SET?=?(1U<????sw_delay_us(500);
}
?
2.2 準(zhǔn)備初始 FDCB 配置塊
第二步是準(zhǔn)備一個初始的 FDCB 配置塊(即 flexspi_nor_config_t,大小為 512 字節(jié)),這個初始 FDCB 配置塊將被用來做 FlexSPI 外設(shè)的第一次初始化,目的是為了能夠保證 FlexSPI 初始化之后 CPU 能夠使用 AHB 方式正常讀取 Flash(訪問性能不要求最高,但求穩(wěn)定訪問)。這個初始 FDCB 并不是一個完全定死的配置塊,部分值也是根據(jù) fuse 來配置的,一共有三處 fuse 位置,其中最重要的是 FLASH_TYPE:
fuse 0x440[20] - QSPI_2ND_BOOTPIN_ENABLE,決定是否啟動第二組 FlexSPI pinmux
fuse 0x450[10:8] - FLASH_TYPE,決定當(dāng)前連接的 Flash 類型
fuse 0x470[30:24] - DELAY_CELL_NUM,設(shè)置 Flash 讀訪問時序數(shù)據(jù)線有效時間
初始 FDCB 配置塊中僅給 memConfig 設(shè)了值,這個 memConfig 才是用于配置 FlexSPI 外設(shè)本身。如下部分賦值是固定的 FDCB 設(shè)置,不受 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,根據(jù)具體 flashType 來對初始 FDCB 配置塊做進(jìn)一步動態(tài)賦值,這進(jìn)一步賦值才用于區(qū)分不同 Flash 種類(Pad 數(shù)量、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 外設(shè)需要的核心配置,有了這個配置,CPU 便可以直接從 AHB 總線讀取 Flash 的內(nèi)容,因?yàn)?FlexSPI 會自動解析 AHB 總線讀請求然后翻譯成具體 FlexSPI 讀時序,底層讀時序需要的命令、地址字節(jié)數(shù)、DUMMY 周期都在 lookupTable 里。BootROM 預(yù)存了如下 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 外設(shè)進(jìn)行第一次初始化,就是下面代碼,這個流程跟官方 SDK 里的 flexspi_nor_flash_init()大同小異,這里不予具體展開。如果在這里初始化就返回失敗(這里一般不會失敗,因?yàn)閮H僅是 FlexSPI 外設(shè)自身初始化,并不涉及操作外部 Flash 芯片的動作),BootROM 則直接退出 FlexSPI NOR 設(shè)備啟動,轉(zhuǎn)入 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 配置塊此時還沒有被充分驗(yàn)證其是否適用板載 Flash 型號。
FlexSPI 第一次初始化結(jié)束后,為了保證后續(xù)能正常 AHB 訪問,BootROM 里做了一些善后工作,主要是兩件事:
- 做一些訪問前的延時:根絕 fuse 0x450[3:2] - HOLD TIME 來調(diào)用 microseconds_delay()做延時,以使 FlexSPI 外設(shè)完全準(zhǔn)備好。做一次無效 AHB 訪問:類似這樣的代碼 volatile uint32_t dummy = *(uint32_t *)0x60000000;,無效 AHB 讀可以使 Flash 退出 continuous read 模式
?
2.5 獲取用戶 FDCB 配置塊
善后工作結(jié)束之后,此時 CPU 應(yīng)該可以通過 AHB 正常訪問 Flash 了,這個階段我們只需要從 Flash 的偏移 0 地址處讀取用戶 FDCB,驗(yàn)證用戶 FDCB 是否存在,這里才是對前面初始 FDCB 配置塊以及第一次 FlexSPI 外設(shè)初始化的真正考驗(yàn)。
驗(yàn)證用戶 FDCB 是否存在就是簡單讀取 FDCB 的前四個字節(jié)(tag),驗(yàn)證這個 tag 是否合法。如果第一次驗(yàn)證 tag 不成功(有可能是 FlexSPI 配置不正確,也有可能是用戶 FDCB 不存在),會嘗試做一次三字節(jié)地址切換到四字節(jié)地址的 LUT 更新(僅適用 QSPI Flash),然后做第二次 tag 讀取驗(yàn)證,如果此時還是驗(yàn)證失?。ù蟾怕适遣淮嬖谟脩?FDCB 了),BootROM 則直接退出 FlexSPI NOR 設(shè)備啟動,轉(zhuǎn)入 SDP 下載。
#define?FlexSPI_AMBA_BASE??????(0x60000000U)
#define?FLASH_BASE?????????????FlexSPI_AMBA_BASE
//?使用三字節(jié)地址的 LUT 對 Flash 進(jìn)行初次 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)
{
????//?因?yàn)槟貌坏接脩?FDCB 的 tag,嘗試切換使用四字節(jié)地址的 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 進(jìn)行第二次 AHB 訪問,再次確認(rèn)能否拿到用戶 FDCB 的 tag
if?(pConfig->memConfig.tag?!=?FLEXSPI_CFG_BLK_TAG)
{
????return?kStatus_Fail;
}
上面代碼里有 flexspi_clear_cache()操作,這個其實(shí)就是利用 FLEXSPI0->MCR0[SWRESET]做一個外設(shè)級別的軟復(fù)位,另外代碼里還涉及到一個四字節(jié)地址 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 外設(shè)做第二次初始化,初始化代碼流程跟第一次初始化是一模一樣的。
這個第二次初始化是非常有必要的,因?yàn)樗从沉擞脩舻恼鎸?shí)需求,用戶 FDCB 配置塊里會準(zhǔn)確描述板載 Flash 的全面特性(訪問速度,真實(shí)存儲空間大小,特殊定制 LUT 等等),這些信息必須由用戶來提供。
需要注意的是,第二次 FlexSPI 初始化返回成功并不代表用戶 FDCB 配置塊一定就是正確的,還是那句話,這僅僅是對 FlexSPI 外設(shè)自身的初始化。后續(xù)常規(guī) App 解析流程里才是對這個用戶 FDCB 配置塊的真正考驗(yàn)。
至此,深入 i.MXRT1050 系列 ROM 中串行 NOR Flash 啟動初始化流程痞子衡便介紹完畢了,掌聲在哪里~~~