• 正文
    • 一、整體初始化流程
    • 二、分解初始化流程
  • 相關推薦
申請入駐 產業(yè)圖譜

i.MXRT1050是怎樣的串行NOR Flash啟動初始化流程?

2020/12/10
167
加入交流群
掃碼加入
獲取工程師必備禮包
參與熱點資訊討論

大家好,我是痞子衡,是正經搞技術的痞子。今天痞子衡給大家分享的是深入 i.MXRT1050 系列 ROM 中串行 NOR Flash 啟動初始化流程。

從外部串行 NOR Flash 啟動問題是 i.MXRT 系列開發(fā)最高頻的話題,無論是開發(fā)調試 XIP 應用程序階段還是最終產品量產階段都繞不開 NOR Flash 選型以及為它設計一個匹配的 FDCB 配置塊。如果不了解 FDCB 是什么,先去看痞子衡之前的文章 《Bootable image 格式與加載》。

實際開發(fā)過程中,影響串行 NOR Flash 正常下載 / 啟動的因素有很多,痞子衡已經寫過三篇:《16MB 以上使用不當因素》、《SFDP 因素》、《QE bit 因素》,列舉了三個不同因素,當然這都是出了問題,具體調試分析才定位出來的,顯然還有很多未知因素等待陸續(xù)被發(fā)掘。

如果總是被動去解決問題,那問題是解不完的。不如我們主動出擊,摸清 i.MXRT 啟動串行 NOR Flash 設備到底是怎樣的初始化流程,搞清這個流程,將來定位啟動問題才能游刃有余,話不多說,開始今天的主題。

  • 備注:本文主角是 i.MXRT1050,但內容也同樣適用 i.MXRT1020/1015,對于 i.MXRT1010 也算適用但有兩處微小差別(冗余 App 啟動支持,F(xiàn)lash 上電等待時間處理)。

?

一、整體初始化流程

我們知道外部串行 NOR Flash 是接到 i.MXRT 的 FlexSPI 外設引腳上,有時串行 NOR Flash 啟動也叫 FlexSPI NOR 啟動。關于 FlexSPI NOR 啟動流程,i.MXRT1050 參考手冊 System Boot 章節(jié)有如下流圖,藍框之外的流程屬于常規(guī) i.MXRT 啟動 XIP App 流程,是個通用流程。藍框之內才是具體 FlexSPI 初始化步驟,這個步驟概括得比較精煉。

為了讓大家對 FlexSPI NOR 設備啟動初始化流程有個更具體的概念,痞子衡重新畫了一張更詳細的流程圖,圖中灰底框里描述得是 FlexSPI 初始化流程,痞子衡將其分解成了六步,我們有必要深入這六步初始化流程。

?

二、分解初始化流程

2.1 復位 Flash 芯片(可選)

第一步是嘗試復位 Flash 芯片,這步是可選的,在 fuse_0x6e0[7]里配置,默認是不使能的。復位 Flash 目的是為了讓 Flash 處于一個確定的初始狀態(tài),方便 i.MXRT BootROM 去配置訪問。為什么要強調 Flash 的初始狀態(tài),因為很多時候 i.MXRT 未必是冷啟動(上電啟動),也有可能是軟復位啟動(比如調用 NVIC_SystemReset),這時候外部 Flash 已經被軟復位前執(zhí)行過的 BootROM 甚至用戶 App 配置過,因此 Flash 的狀態(tài)可能不是上電初始狀態(tài)(一般來說板級設計里 Flash 的 RESET#引腳要么懸空,要么連接 i.MXRT 的 POR#引腳),這可能會影響軟復位后 BootROM 去再次配置啟動這塊不定態(tài)的 Flash。

fuse 0x6e0[7] - FLEXSPI_RESET_PIN_EN

正常的 Flash 都提供了 RESET#引腳來實現(xiàn)跟上電復位一樣的功能,對于普通 8-pin 的 QSPI Flash,這個 RESET#引腳往往是跟信號線 IO3 復用的(僅在 QE bit 沒使能情況下有效),而對于 16-pin 的 QSPI Flash 或者 HyperFlash,其 RESET#引腳都是獨立的。

BootROM 就是借助了 Flash 的 RESET#引腳來實現(xiàn)的復位操作,實現(xiàn)代碼比較簡單,i.MXRT1050 BootROM 直接指定了 GPIO1[9]當做復位信號線,板級設計里需要你將 GPIO1[9]連到 Flash 的 RESET#引腳,然后 BootROM 就是簡單地拉低 GPIO1[9]即可。RESET#信號都是低電平有效,BootROM 直接拉低這個信號持續(xù) 250us,這個低電平持續(xù)時間對于復位來說是夠夠的,很多 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<
????//?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 準備初始 FDCB 配置塊

第二步是準備一個初始的 FDCB 配置塊(即 flexspi_nor_config_t,大小為 512 字節(jié)),這個初始 FDCB 配置塊將被用來做 FlexSPI 外設的第一次初始化,目的是為了能夠保證 FlexSPI 初始化之后 CPU 能夠使用 AHB 方式正常讀取 Flash(訪問性能不要求最高,但求穩(wěn)定訪問)。這個初始 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 配置塊做進一步動態(tài)賦值,這進一步賦值才用于區(qū)分不同 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 讀時序,底層讀時序需要的命令、地址字節(jié)數、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 第一次初始化結束后,為了保證后續(xù)能正常 AHB 訪問,BootROM 里做了一些善后工作,主要是兩件事:

  1. 做一些訪問前的延時:根絕 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 的前四個字節(jié)(tag),驗證這個 tag 是否合法。如果第一次驗證 tag 不成功(有可能是 FlexSPI 配置不正確,也有可能是用戶 FDCB 不存在),會嘗試做一次三字節(jié)地址切換到四字節(jié)地址的 LUT 更新(僅適用 QSPI Flash),然后做第二次 tag 讀取驗證,如果此時還是驗證失?。ù蟾怕适遣淮嬖谟脩?FDCB 了),BootROM 則直接退出 FlexSPI NOR 設備啟動,轉入 SDP 下載。

#define?FlexSPI_AMBA_BASE??????(0x60000000U)
#define?FLASH_BASE?????????????FlexSPI_AMBA_BASE

//?使用三字節(jié)地址的 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,嘗試切換使用四字節(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 進行第二次 AHB 訪問,再次確認能否拿到用戶 FDCB 的 tag
if?(pConfig->memConfig.tag?!=?FLEXSPI_CFG_BLK_TAG)
{
????return?kStatus_Fail;
}

上面代碼里有 flexspi_clear_cache()操作,這個其實就是利用 FLEXSPI0->MCR0[SWRESET]做一個外設級別的軟復位,另外代碼里還涉及到一個四字節(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 外設做第二次初始化,初始化代碼流程跟第一次初始化是一模一樣的。

這個第二次初始化是非常有必要的,因為它反映了用戶的真實需求,用戶 FDCB 配置塊里會準確描述板載 Flash 的全面特性(訪問速度,真實存儲空間大小,特殊定制 LUT 等等),這些信息必須由用戶來提供。

需要注意的是,第二次 FlexSPI 初始化返回成功并不代表用戶 FDCB 配置塊一定就是正確的,還是那句話,這僅僅是對 FlexSPI 外設自身的初始化。后續(xù)常規(guī) App 解析流程里才是對這個用戶 FDCB 配置塊的真正考驗。

至此,深入 i.MXRT1050 系列 ROM 中串行 NOR Flash 啟動初始化流程痞子衡便介紹完畢了,掌聲在哪里~~~

相關推薦

登錄即可解鎖
  • 海量技術文章
  • 設計資源下載
  • 產業(yè)鏈客戶資源
  • 寫文章/發(fā)需求
立即登錄

碩士畢業(yè)于蘇州大學電子信息學院,目前就職于恩智浦(NXP)半導體MCU系統(tǒng)部門,擔任嵌入式系統(tǒng)應用工程師。痞子衡會定期分享嵌入式相關文章