深入淺析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 <config.h>
#include <version.h>

#include <s5pc110.h>
#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}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章