lowlevel_init函数分析
lowlevel_init函数是uboot启动阶段调用的第一个函数,主要功能就是初始化S5PV210的各个硬件部分。
#include <config.h>
#include <version.h>
#include <s5pc110.h>
#include "smdkc110_val.h"
包含头文件,主要是s5pc110.h文件,里面包含了板子的硬件寄存器的地址。
lowlevel_init:
push {lr}
压栈,保存现场,否则函数返回时返回地址就丢了。
/* check reset status */
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
//0xE0100000+0xa000
ldr r1, [r0]
bic r1, r1, #0xfff6ffff
cmp r1, #0x10000
beq wakeup_reset_pre
cmp r1, #0x80000
beq wakeup_reset_from_didle
检查复位状态,查询数据手册(section 02_system 4.10.3 RESET CONTROL REGISTER)。位清除后保留了bit16和bit19,bit16置1为睡眠模式,跳转到
wakeup_reset_pre,bit19置1,ARM将从DEEP-IDLE模式重置,因此跳转到
wakeup_reset_from_didle
/* IO Retention release */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)
//0xE0100000+0xe000
ldr r1, [r0]
ldr r2, =IO_RET_REL
//((1 << 31) | (1 << 29) | (1 << 28))
orr r1, r1, r2
str r1, [r0]
恢复I/O状态,查数据手册这3位分别对应GPIO、MMC、UART,都是禁止使能。但从描述上来看会自动清零复位。 If you want to disable RELEASE_RET_GPIO, set to 1. After RELEASE_RET_GPIO becomes OFF, this bit will be cleared to 0. 和主线的启动代码关系不大。
/* Disable Watchdog */
ldr r0, =ELFIN_WATCHDOG_BASE
/* 0xE2700000 */
mov r1, #0
str r1, [r0]
关看门狗,给WTCON寄存器写0,主要是bit5禁止看门狗定时器。
/* SRAM(2MB) init for SMDKC110 */
/* GPJ1 SROM_ADDR_16to21 */
ldr r0, =ELFIN_GPIO_BASE
//0xE0200000
ldr r1, [r0, #GPJ1CON_OFFSET]
//0x260
bic r1, r1, #0xFFFFFF
ldr r2, =0x444444
orr r1, r1, r2
str r1, [r0, #GPJ1CON_OFFSET]
给GPJ1CON寄存器写了6个0100,都是设置SROM_ADDR_16to22。
ldr r1, [r0, #GPJ1PUD_OFFSET]
//0x268
ldr r2, =0x3ff
bic r1, r1, r2
str r1, [r0, #GPJ1PUD_OFFSET]
设置GPJ1PUD寄存器,全部为Pull-up/down disable,禁止上拉或下拉。
/* GPJ4 SROM_ADDR_16to21 */
ldr r1, [r0, #GPJ4CON_OFFSET]
//0x2c0
bic r1, r1, #(0xf<<16)
ldr r2, =(0x4<<16)
orr r1, r1, r2
str r1, [r0, #GPJ4CON_OFFSET]
设置GPJ4CON,bit[19:16]为0001,OUTPUT。
ldr r1, [r0, #GPJ4PUD_OFFSET]
//0x2c8
ldr r2, =(0x3<<8)
bic r1, r1, r2
str r1, [r0, #GPJ4PUD_OFFSET]
设置GPJ4PUD的bit[9:8]为00,Pull-up/down disable。
/* CS0 - 16bit sram, enable nBE, Byte base address */
ldr r0, =ELFIN_SROM_BASE
/* 0xE8000000 */
mov r1, #0x1
str r1, [r0]
设置SROM的数据位宽,Memory Bank0为16bit。有点无意义,数据手册上写bank0只能用16bit,且不能更改。
/* PS_HOLD pin(GPH0_0) set to high */
ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET))
//0xE0100000+0xe81c
ldr r1, [r0]
orr r1, r1, #0x300
orr r1, r1, #0x1
str r1, [r0]
拉高PS_HOLD,将bit1置1使能PS_HOLD输出,bit8置1设置高电平,bit9置1设置对外输出。
从关看门狗后面的代码大部分都是和GPIO相关的,和主线启动并没有太多关联,主要和启动有关的是供电锁存。
/* when we already run in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
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 */
判断当前代码执行位置,r1存放当前代码地址,r2存放存放_TEXT_BASE链接地址,经过位清除后只比较两个地址的bit12~bit23,如果相等说明当前地址在DDR中,就跳过下面的SDRAM初始化过程。b 1f意思是跳转到标号"1"的位置,f表示向前(下)寻找(forward),向后(上)寻找则是b(backward)。
/* init system clock */
bl system_clock_init
/* Memory initialize */
bl mem_ctrl_asm_init
system_clock_init函数就在lowlevel_init.S文件中,是用来初始化时钟的。里面初始化时钟的操作都很固定,时钟的配置都在configs/x210_sd.h中,如果要更改时钟配置,只需要更改头文件中的配置值即可。
mem_ctrl_asm_init函数在cpu/s5pc11x/s5pc110/cpu_init.S文件中,用来初始化DDR内存的。
1:
/* for UART */
bl uart_asm_init
初始化串口,函数实现在当前文件中,结束函数返回调用时地址之前有这样一个操作,打印了一个‘O’,之后在lowlevel_init函数结束前也有一个类似的操作。主要目的是打印调试信息。
bl tzpc_init
设置TZPC,找到函数入口,注释说明是 Setting TZPC[TrustZone Protection Controller]。查阅资料得知TrustZone是与安全有关的技术,与启动关联不大。
#if defined(CONFIG_ONENAND)
bl onenandcon_init
#endif
#if defined(CONFIG_NAND)
/* simple init for NAND */
bl nand_asm_init
#endif
这两段代码无关,我用的板子是iNand,没有定义这两个宏。
/* check reset status */
ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
//0xE0100000+0xa000
ldr r1, [r0]
bic r1, r1, #0xfffeffff
cmp r1, #0x10000
beq wakeup_reset_pre
检查复位状态,和前面的代码几乎完全相同。这里只检测睡眠状态的。
/* ABB disable */
ldr r0, =0xE010C300
orr r1, r1, #(0x1<<23)
str r1, [r0]
不清楚,数据手册上找不到0xE010C300地址的寄存器,上网查询资料也几乎没有,google了一点资料,应该是Adaptive Body Biasing,与电压相关,和启动关联不大。
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
//0xE2900000+0x0000
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
//0x20
往串口发送0x4b,对应ASCII码就是'K',和前面初始化UART的函数最后发的'O'组成"OK"。相当于调试信息,当启动uboot时看到"OK"说明lowlevel_init函数运行成功。
pop {pc}
弹栈,返回调用时的地址。后面还有些内容,比如lowlevel_init函数内部调用的函数的实现、TTB(转换表)等内容。
从启动方面,lowlevel_init主要做了 检查复位状态、IO恢复、关看门狗、开发板供电锁存、时钟初始化、DDR初始化、串口初始化并打印'O'、打印'K'。 值得关注的是 关看门狗、开发板供电锁存、时钟初始化、DDR初始化、打印"OK"。