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