4. 硬件平台适配(一):SoC启动ROM分析、时钟与PLL初始化、DDR内存控制器配置实战
各位同学,咱们今天聊点硬核的。Bootloader移植,说白了就是让一块陌生的板子能跑起来。你拿到一块新板子,第一件事是什么?不是急着写代码,而是先把SoC的启动流程摸清楚。我这些年踩过的坑,十有八九都跟这一步没做透有关。
4.1 SoC启动ROM分析——第一口奶从哪吃?
SoC上电后,CPU核心从复位向量取第一条指令。这个地址通常是0x00000000或者0xFFFF0000。但问题是,这时候DDR还没初始化,代码放哪?
答案就在芯片内部的BootROM里。这是一块只读存储器,出厂就固化好了。它的任务很简单:根据硬件管脚的电平状态,决定从哪个介质加载下一级代码。
| 启动介质 | 典型管脚配置 | 加载方式 | 注意事项 |
|---|---|---|---|
| NAND Flash | BOOT[1:0]=00 | 硬件ECC + 页读取 | 需要关注坏块管理 |
| eMMC/SD卡 | BOOT[1:0]=01 | SD/MMC协议初始化 | 分区表解析要提前做好 |
| SPI NOR Flash | BOOT[1:0]=10 | SPI模式读取 | 地址映射方式不同 |
| USB/UART下载 | BOOT[1:0]=11 | 串口/USB协议接收 | 通常用于工厂烧录 |
我个人习惯,拿到一个新SoC,第一件事就是去翻它的Reference Manual里的"Boot ROM"章节。别嫌枯燥,这里藏着很多细节。比如,有些芯片的BootROM会先初始化内部SRAM,然后把代码从Flash拷贝到SRAM里执行。这个SRAM的大小,直接决定了你的SPL(Secondary Program Loader)能写多大。
CONFIG_SPL_FIT支持,才搞定。
4.2 时钟与PLL初始化——给系统一颗稳定的心脏
时钟,是数字系统的脉搏。SoC上电后,默认用的是内部低速振荡器,频率可能只有几十MHz。你要跑DDR、跑外设,必须把PLL(锁相环)配置好,把频率提上去。
时钟树的结构,每个SoC都不一样。但万变不离其宗,核心思路就三步:
- 选择时钟源:外部晶振还是内部RC振荡器?外部晶振精度高,但需要起振时间。
- 配置PLL倍频:通过M、N、K、OD等参数,算出目标频率。
- 分配时钟域:CPU、总线、外设各走哪条路,分频系数是多少。
公式很简单:Fout = Fin × M / (N × K × OD)。但实际写代码时,你得注意PLL的锁定时间。我记得有一次,PLL配置完立马就去初始化DDR,结果DDR控制器读到的时钟还没稳定,数据全是乱的。后来加了200微秒的延时,问题就解决了。
/* 以某款Cortex-A7 SoC为例,配置PLL1到1.2GHz */
void pll_init(void)
{
/* 1. 选择外部24MHz晶振作为参考时钟 */
writel(0x01, &clk_base->osc_sel);
/* 2. 配置PLL1参数:M=100, N=2, K=2, OD=1 */
/* Fout = 24 * 100 / (2 * 2 * 1) = 600MHz */
writel((100 << 16) | (2 << 8) | (2 << 4) | 1, &pll1_base->cfg);
/* 3. 等待PLL锁定 */
while (!(readl(&pll1_base->status) & PLL_LOCK_BIT))
;
/* 4. 切换CPU时钟源到PLL1 */
writel(0x02, &clk_base->cpu_clk_sel);
/* 5. 稳定后,关闭旁路模式 */
writel(readl(&pll1_base->cfg) & ~PLL_BYPASS_BIT, &pll1_base->cfg);
}
4.3 DDR内存控制器配置——让代码有个家
DDR初始化,是Bootloader移植里最复杂、也最容易出问题的一步。为什么?因为DDR的时序参数太多了,而且跟PCB布局、走线长度、颗粒型号都强相关。
DDR控制器配置的核心,就是告诉它:你的内存颗粒是什么类型?容量多大?时序参数是多少?
| 配置项 | 典型值 | 说明 |
|---|---|---|
| DRAM类型 | DDR3/DDR4/LPDDR4 | 不同代际,电气特性不同 |
| 数据位宽 | 16/32/64 bit | 跟SoC的DDR控制器支持有关 |
| 行地址/列地址 | R=15, C=10 | 决定了单颗颗粒的容量 |
| tRCD | 13.75ns (DDR3-1600) | 行地址到列地址的延迟 |
| tCL | 11 (DDR3-1600) | CAS潜伏期 |
| tRP | 13.75ns (DDR3-1600) | 行预充电时间 |
配置流程一般是这样的:
- 复位DDR控制器和PHY:让它们回到初始状态。
- 配置时序参数:把上面表格里的值,写到对应的寄存器里。
- 初始化DDR颗粒:发送MRS(模式寄存器设置)命令,设置颗粒的工作模式。
- ZQ校准:DDR3/DDR4需要做阻抗校准,保证信号质量。
- 训练DDR PHY:调整读写数据眼图,找到最佳采样点。
- 验证读写:写一个模式,读回来对比,确认DDR工作正常。
嗯,这里要注意。DDR PHY的训练,是很多工程师容易忽略的环节。你以为配置完时序参数就完事了?其实不是。PCB走线的微小差异,会导致数据线和时钟线的延迟不一样。如果不做训练,高速读写时就会出现数据错误。
4.4 知识体系总览
为了让你更直观地理解这三者的关系,我画了一张图。你可以把它当作本章的"地图"。
你看,从BootROM到时钟初始化,再到DDR配置,最后才能加载真正的Bootloader。每一步都依赖上一步的正确执行。这就是为什么我总说,硬件平台适配是Bootloader移植的"第一道坎"。