注:本文是学习朱老师课程整理的笔记,基于uboot-1.3.4和s5pc11x分析。
在start.s中通过这样一段代码来调用lowlevel_init的代码:
/* Go setup Memory and board specific bits prior to relocation.*/
ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */
sub sp, sp, #12 /* set stack */
mov fp, #0
bl lowlevel_init /* go setup pll,mux,memory */
下面我们就开始分析lowlevel_init的相关内容:
lowlevel_init:
push {lr}
/* check reset status */
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
ldr r1, [r0]
bic r1, r1, #0xfff6ffff
cmp r1, #0x10000
beq wakeup_reset_pre
cmp r1, #0x80000
beq wakeup_reset_from_didle
一开始要将lr的值(函数返回地址)压栈,接着检查复位状态。复杂CPU允许多种复位情况。譬如直接冷上电(从关机状态到上电)、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以在复位代码中要去检测复位状态,来判断到底是哪种情况。
判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。
/* IO Retention release */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
ldr r1, [r0]
ldr r2, =IO_RET_REL
orr r1, r1, r2
str r1, [r0]
IO状态恢复,这个和检查复位状态和主线启动代码都无关,因此不用去管他。
- 关看门狗
/* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */
mov r1, #0
str r1, [r0]
- 一些SRAM SROM相关GPIO设置
/* SRAM(2MB) init for SMDKC110 */
/* GPJ1 SROM_ADDR_16to21 */
ldr r0, =ELFIN_GPIO_BASE
……
/* GPJ4 SROM_ADDR_16to21 */
ldr r1, [r0, #GPJ4CON_OFFSET]
……
/* CS0 - 16bit sram, enable nBE, Byte base address */
ldr r0, =ELFIN_SROM_BASE /* 0xE8000000 */
mov r1, #0x1
str r1, [r0]
这些代码与主线启动代码无关,不用管。
- 供电锁存
/* PS_HOLD pin(GPH0_0) set to high */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
0x3001是非法立即数,所以上面的orr分了两步操作。
总结:在以上的lowlevel_init.S中并没有做太多有意义的事情(除了关看门狗、供电锁存外),然后下面才开始进行有意义的操作。
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq 1f /* r0 == r1 then skip sdram init */
这几行代码的作用就是判定当前代码执行的位置在SRAM中还是在DDR中。
为什么要做这个判定?
- BL1(uboot的前一部分)在SRAM中有一份,在DDR中也有一份,因此如果是冷启动那么当前代码应该是在SRAM中运行的BL1,如果是低功耗状态的复位这时候应该就是在DDR中运行的。
- 判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明冷启动,那么时钟和DDR都需要初始化;如果当前代码是在DDR中,那么说明是热启动则时钟和DDR都不用再次初始化。
bic r1, pc, r0
这句代码相当于:r1 = pc & ~(ff000fff)。 ldr r2, _TEXT_BASE
加载链接地址到r2,然后将r2的相应位清0剩下特定位。
最后比较r1和r2。
为什么是0xff000fff呢?
如果当前运行在SRAM中,它运行在:0xD0020XXX的地址处。如果运行在DDR中,地址在0xc3e00XXX处。链接地址是:0xc3e00000,通过上面的方法就可以断定程序运行在哪里。
总结:这一段代码是通过读取当前运行地址和链接地址,然后处理两个地址后对比是否相等,来判定当前运行是在SRAM中(不相等)还是DDR中(相等)。从而决定是否跳过下面的时钟和DDR初始化。
- 时钟初始化
/* init system clock */
bl system_clock_init
在x210_sd.h中有时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。
- DDR初始化
/* Memory initialize */
bl mem_ctrl_asm_init
函数位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S文件中。
内存配置值在x210_sd.h。分析的时候要注意条件编译,配置头文件中考虑了不同时钟配置下的内存配置值,这个的主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。
x210_sd.h中相关的配置如下:
//#define CONFIG_CLK_667_166_166_133
//#define CONFIG_CLK_533_133_100_100
//#define CONFIG_CLK_800_200_166_133
//#define CONFIG_CLK_800_100_166_133
#define CONFIG_CLK_1000_200_166_133
//#define CONFIG_CLK_400_200_166_133
//#define CONFIG_CLK_400_100_166_133
……
#elif defined(CONFIG_CLK_800_200_166_133) || \
defined(CONFIG_CLK_1000_200_166_133) || \
defined(CONFIG_CLK_800_100_166_133) || \
defined(CONFIG_CLK_400_200_166_133) || \
defined(CONFIG_CLK_400_100_166_133)
#if defined(CONFIG_MCP_SINGLE)
#define DMC0_MEMCONTROL 0x00212400
//#define DMC0_MEMCONFIG_0 0x30F01323
//#define DMC0_MEMCONFIG_1 0x00F01323 // MemConfig1
#define DMC0_MEMCONFIG_0 0x20E01323
#define DMC0_MEMCONFIG_1 0x40F01323 // MemConfig1
- 串口初始化
/* for UART */
bl uart_asm_init
……
uart_asm_init:
……
ldr r1, =0x4f4f4f4f
str r1, [r0, #UTXH_OFFSET] @'O'
0x4f就是字母’O’的ASCII码,初始化完了后通过串口发送了一个’O’。
- 可信任区域
bl tzpc_init
具体什么用途,有待学习……
- pop {pc}已返回
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
pop {pc}
返回前通过串口打印’K’。
lowlevel_init.S执行完如果没错那么就会串口打印出”OK”字样。这是uboot中看到的最早的输出信息。这些信息有助于我们调试,帮我们找到出错的地方。
总结:lowlevel_init.S中值得关注的:关看门狗、开发板供电锁存、时钟初始化、DDR初始化、串口打印”OK”。