深入浅析Linux下uboot之(六)-----------------------:uboot 启动的第一阶段之 lowlevel_init

lowlevel_init 里面实现了cpu 相关硬件初始化:检查复位状态、IO恢复、关看门狗、开发板供电锁存、时钟初始化、DDR初始化、串口初始化并打印'O'、tzpc初始化、打印'K'。lowlevel_init 函数真正的地方,是在uboot/board/samsumg/x210/lowlevel_init.S中。

目录

检查复位状态:

IO 状态的恢复:

关看门狗:

供电锁存:

初始化时钟、初始化DDR动态内存:

串口初始化、tzpc初始化、打印 'K':


检查复位状态:

#include 
#include 

#include 
#include "smdkc110_val.h"

_TEXT_BASE:
	.word	TEXT_BASE

	.globl lowlevel_init
lowlevel_init:
	push	{lr}  //先将lr压栈保存,保存当前链接寄存器地址,等跳转回start.s时继续执行使用

	/* 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
  • lowlevel_init:直接跳到这边执行。
  • 现在复杂cpu有多种复位状态。譬如直接冷上电、热启动、睡眠(低功耗)状态下的唤醒等,这些情况都属于复位。所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。以上代码就是来检查其复位的状态。
  • 判断哪种复位的意义在于:冷上电时DDR是需要初始化才能用的;而热启动或者低功耗状态下的复位则不需要再次初始化DDR。

IO 状态的恢复:

/* 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复位:在进入低功耗之前记录 IO 的值,以便来保存IO 恢复。(这部分跟复位的部分代码跟主线无关,可以不看)

关看门狗:

	/* Disable Watchdog */
	ldr	r0, =ELFIN_WATCHDOG_BASE	/* 0xE2700000 */
	mov	r1, #0
	str	r1, [r0]
  • 在嵌入式系统中,不可避免的会碰到系统运行时出错的问题,有时候为了使系统能够自动的进行复位,就引入了看门狗的概念,实际上它就是一个计数器,到了一定的值后就会复位cpu,在程序中我们需要在计数器增加到这个值之前对这个计数器做一个复位清零的工作,俗称喂狗,使程序继续运行。
  • 在系统初始化的时候,由于我们并没有进行喂狗的工作,为了防止看门狗一直复位cpu,因此我们需要先将其关闭。

接下来是一些SRAM SROM相关GPIO设置,与主线启动代码无关,可以不用管:

	/* SRAM(2MB) init for SMDKC110 */
	/* GPJ1 SROM_ADDR_16to21 */
	ldr	r0, =ELFIN_GPIO_BASE
	
	ldr	r1, [r0, #GPJ1CON_OFFSET]
	bic	r1, r1, #0xFFFFFF
	ldr	r2, =0x444444
	orr	r1, r1, r2
	str	r1, [r0, #GPJ1CON_OFFSET]

	ldr	r1, [r0, #GPJ1PUD_OFFSET]
	ldr	r2, =0x3ff
	bic	r1, r1, r2
	str	r1, [r0, #GPJ1PUD_OFFSET]

	/* GPJ4 SROM_ADDR_16to21 */
	ldr	r1, [r0, #GPJ4CON_OFFSET]
	bic	r1, r1, #(0xf<<16)
	ldr	r2, =(0x4<<16)
	orr	r1, r1, r2
	str	r1, [r0, #GPJ4CON_OFFSET]

	ldr	r1, [r0, #GPJ4PUD_OFFSET]
	ldr	r2, =(0x3<<8)
	bic	r1, r1, r2
	str	r1, [r0, #GPJ4PUD_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]
  • 供电锁存:如果是软开关,这个开关是一个不会自锁的按钮,当按下时给芯片通电,弹起时芯片断电,想要给芯片持续供电,需要软件给供电锁存。

初始化时钟、初始化DDR动态内存:

	/* 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		/* 将pc的值中的某些bit位清0,剩下一些特殊的bit位赋值给r1(r0中为1的那些位清零)相等于:r1 = pc & ~(ff000fff) */
	ldr	r2, _TEXT_BASE		/* 加载链接地址到r2,然后将r2的相应位清0剩下特定位。 */
	bic	r2, r2, r0		/* r0 <- current base addr of code */
	cmp     r1, r2                  /* compare r0, r1                  */
	beq     1f			/* r0 == r1 then skip sdram init   */


	/* init system clock */
	bl system_clock_init

	/* Memory initialize */
	bl mem_ctrl_asm_init
	
1:
	/* for UART */
	bl uart_asm_init

	bl tzpc_init
  • cmp     r1, r2  :判定当前代码执行的位置在 SRAM 中还是在 DDR 中。为什么要做这个判定?原因1:BL1(uboot的前一部分)在SRAM中有一份,在 DDR 中也有一份,因此如果是冷启动那么当前代码应该是在 SRAM 中运行的BL1,如果是低功耗状态的复位这时候应该就是在 DDR 中运行的。原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在 lowlevel_init.S 中判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和初始化 DDR 的代码。如果当前代码是在 SRAM 中,说明冷启动,那么时钟和 DDR 都需要初始化;如果当前代码是在 DDR 中,那么说明是热启动则时钟和 DDR 都不用再次初始化。也就是说如果 r1 和 r2 相等就说明是在DDR运行,就要跳过时钟初始化和初始化DDR。如果 r1 和 r2 不相等就说明是在 SRAM 运行,就要进行 时钟初始化 和 初始化DDR。
  • beq  1f: 如果相等就跳转到 1, 1 是标号, f 是向后找, b 是往前找。
  • bl system_clock_init :初始化时钟,system_clock_init:函数就在本文件的 205 - 385行。在include\configs\x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置是多少。也就是说代码在lowlevel_init.S中都写好了,但是代码的设置值都被宏定义在x210_sd.h中了。因此,如果移植时需要更改CPU的时钟设置,根本不需要动代码,只需要在x210_sd.h中更改配置值即可。
  • bl mem_ctrl_asm_init: 初始化DDR动态内存:借鉴以下别人的 ddr 的解析:https://blog.csdn.net/wangdapao12138/article/details/79828923

串口初始化、tzpc初始化、打印 'K':

	/* for UART */
	bl uart_asm_init

	bl tzpc_init

	/* Print 'K' */
	ldr	r0, =ELFIN_UART_CONSOLE_BASE
	ldr	r1, =0x4b4b4b4b
	str	r1, [r0, #UTXH_OFFSET]

	pop	{pc}
  • uart_asm_init:串口初始化,初始化完了后通过串口发送了一个'O'。
  • tzpc_init:可信任区域的初始化。trust zone初始化
  • pop    {pc}:这边是函数的返回,lowlevel_init 已经完成。但是在函数返回前通过串口打印'K'。lowlevel_init.S 执行完如果没错那么就会串口打印出"OK"字样。这应该是我们uboot中看到的最早的输出信息。

lowlevel_init.S  中 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

	/* 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]

	/* Disable Watchdog */
	ldr	r0, =ELFIN_WATCHDOG_BASE	/* 0xE2700000 */
	mov	r1, #0
	str	r1, [r0]

	/* SRAM(2MB) init for SMDKC110 */
	/* GPJ1 SROM_ADDR_16to21 */
	ldr	r0, =ELFIN_GPIO_BASE
	
	ldr	r1, [r0, #GPJ1CON_OFFSET]
	bic	r1, r1, #0xFFFFFF
	ldr	r2, =0x444444
	orr	r1, r1, r2
	str	r1, [r0, #GPJ1CON_OFFSET]

	ldr	r1, [r0, #GPJ1PUD_OFFSET]
	ldr	r2, =0x3ff
	bic	r1, r1, r2
	str	r1, [r0, #GPJ1PUD_OFFSET]

	/* GPJ4 SROM_ADDR_16to21 */
	ldr	r1, [r0, #GPJ4CON_OFFSET]
	bic	r1, r1, #(0xf<<16)
	ldr	r2, =(0x4<<16)
	orr	r1, r1, r2
	str	r1, [r0, #GPJ4CON_OFFSET]

	ldr	r1, [r0, #GPJ4PUD_OFFSET]
	ldr	r2, =(0x3<<8)
	bic	r1, r1, r2
	str	r1, [r0, #GPJ4PUD_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]

	/* 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   */

	/* init system clock */
	bl system_clock_init

	/* Memory initialize */
	bl mem_ctrl_asm_init
	
1:
	/* for UART */
	bl uart_asm_init

	bl tzpc_init

#if defined(CONFIG_ONENAND)
	bl onenandcon_init
#endif

#if defined(CONFIG_NAND)
	/* simple init for NAND */
	bl nand_asm_init
#endif

	/* check reset status  */
	
	ldr	r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
	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]

	/* Print 'K' */
	ldr	r0, =ELFIN_UART_CONSOLE_BASE
	ldr	r1, =0x4b4b4b4b
	str	r1, [r0, #UTXH_OFFSET]

	pop	{pc}

 

你可能感兴趣的:(uboot)