想不到一篇既然写不完lowlevel_init,有点失算,只要是要把时钟系统介绍好,涉及到很多知识,如果想好好了解时钟系统的话,可以直接去看数据手册,不过总感觉我拿到的数据手册应该少了一些东西,就是寄存器有点不全。反正影响也不大,接下来继续分析lowlevel_init。
1、开始移植ddr
本来官网的2015版的uboot是没有初始化ddr内存的,所以就找了一个三星移植过的uboot,里面就有ddr内存的初始化,这个文件就是cpu/s5pc11x/s5pc110/cpu_init.S,这个是三星已经移植好的了,要我自己从0开始写ddr汇编程序,功力还是不够,所以只能移植,这个文件拷贝过来放在哪里呢?三星官网是放到cpu下的,我是放到board/samsung/goni,具体放两个位置都可以,就看你怎么决定。
文件已经拷贝过来了,然后就是添加Makefile。让这个文件参与编译和链接,Makefile就在文件放置的目录下的,打开Makefile,我已经添加好了,直接上程序,按照写就可以了。
#
# (C) Copyright 2000, 2001, 2002
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
#
# (C) Copyright 2008
# Guennadi Liakhovetki, DENX Software Engineering, <lg@denx.de>
#
# SPDX-License-Identifier: GPL-2.0+
#
obj-y := goni.o onenand.o
obj-y += lowlevel_init.o
obj-y += cpu_init.o
obj-y += led.o
obj-y += movi.o
并且还在这个函数返回后,添加一个点灯程序,测试一下这个程序是否有问题。
bl mem_ctrl_asm_init //ddr内存初始化函数
bl led3 //自己实现的一个点灯程序,其实里面就是操作gpio的几个寄存器,上一节好像有讲
bl test
添加好了,就可以编译链接,烧录到sd卡,启动板子,结果灯不亮,没事,不要怕,我们进入到mem_ctrl_asm_init这个函数里面,把点灯程序加入,结果发现灯还没亮,重点来了,请注意:
按照正常情况,我们函数调用了,就应该会执行啊,那为什么灯不会亮,这就涉及到s5pv210启动有关的程序了,反正下面也是要准备做重定位,可以再这里讲一讲
首先,启动顺序我们在第一篇的时候已经讲到了,210启动首先执行内部的iROM(也就是BL0),BL0会判断OMpin(这个值文档没有给出,不过可以通过代码来看)来决定从哪个设备启动,如果启动设备是mmc,就会去找对应的mmc。
其实后面还是有一些操作的,之前是没有讲,现在可以补上了
这个也是三星的数据手册,跟硬件有关的可以有空没空看看数据手册,图中显示
第一步,iROM初始化和启动
第二步,iROM把BL1拷贝到SRAM中执行。(这一段就是我们写的uboot的前16k)
第三步,BL1把剩下的启动程序BL2也拷贝到SRAM中运行。(不过我们uboot直接忽略了这步)
第四步,BL2初始化DDR和把OS操作系统加载到DDR中。(其实我们这个是在BL1做的)
第五步,拷贝完成,实现远跳转。(就是从SRAM中的地址跳转到DDR中的地址)
可以来一个具体的流程图,好好了解了解
我们现在做的就是第三步,初始化DDR,结果没初始化成功,好了,说了这么多,我们回归原来问题,为什么灯不亮,是因为我们BL1大小太小了,我们在第一篇就有写过制作镜像的脚本,脚本是会把前面的8k的程序提取出来做为BL1,mem_ctrl_asm_init 有可能已经超出我们的8K了,怎么认证呢?
我们通过打开u-boot.map,这个文件是记录每一个函数的地址,我们在这个文件中找到了mem_ctrl_asm_init 这个函数:
.text 0x3480198c 0x8e8 board/samsung/goni/built-in.o
0x3480198c lowlevel_init
0x34801e8c mem_ctrl_asm_init
地址为0x34801e8c,这个我们uboot的起始地址为0x34800000,这个值定义在s5p_goni.h上,
/* Text Base */
#define CONFIG_SYS_TEXT_BASE 0x34800000 //uboot在DDR中的起始地址
我们可以算出,mem_ctrl_asm_init函数在BL1中7.63671875K的位置上,所以说这个函数很有可能超出范围了,所以我们只要把制作镜像脚本改为10K,再试试
#<BL1 fusing>
bl1_position=1
uboot_position=49
echo "BL1 fusing"
./mkbl1 ../u-boot.bin SD-bl1-8k.bin 10192 //改为10K
dd iflag=dsync oflag=dsync if=SD-bl1-8k.bin of=$1 seek=$bl1_position
rm SD-bl1-8k.bin
####################################
再次编译,烧录到板子上,就可以看到灯已经亮了起来,说明我们之前的分析是对的。
2、分析内存代码
简单过一篇代码就可以了,比较初始化内存是比较复杂的一件事。
/* DMC0 Drive Strength (Setting 2X) */
//从数据手册我们可以看出
//MP1_0~8: 71 DRAM1 ports (in/out port is not used)
//MP2_0~8: 71 DRAM2 ports (in/out port is not used)
//MP1 多有端口就是给DRAM1使用的,MP2就是给DRAM2使用的
//把MP1和MP2所有的gpio的输出能力设为2x
ldr r0, =ELFIN_GPIO_BASE // 0xE0200000
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_0DRV_SR_OFFSET] // 0x3CC
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_1DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_2DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_3DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_4DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_5DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_6DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP1_7DRV_SR_OFFSET]
ldr r1, =0x00002AAA
str r1, [r0, #MP1_8DRV_SR_OFFSET]
/* DMC1 Drive Strength (Setting 2X) */
ldr r0, =ELFIN_GPIO_BASE
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_0DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_1DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_2DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_3DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_4DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_5DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_6DRV_SR_OFFSET]
ldr r1, =0x0000AAAA
str r1, [r0, #MP2_7DRV_SR_OFFSET]
ldr r1, =0x00002AAA
str r1, [r0, #MP2_8DRV_SR_OFFSET]
可以在数据手册的107页看到gpio口的复用功能,所以也就能看到MP1是不是真的是DRAM1要使用的引脚
gpio设置好了之后,就可以开始初始化DRAM了,初始化DRAM是有一定的顺序的,并且我使用的这块板是DDR2,所以要找到DDR2有关的初始化步骤。DDR2有27个步骤,接下来就分析一下下。(有一些我也不懂,哈哈哈)
/* DMC0 initialization at single Type*/
ldr r0, =APB_DMC_0_BASE
ldr r1, =0x00101000 @PhyControl0 DLL parameter setting, manual 0x00101000
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x00000086 @PhyControl1 DLL parameter setting, LPDDR/LPDDR2 Case
str r1, [r0, #DMC_PHYCONTROL1]
ldr r1, =0x00101002 @PhyControl0 DLL on
str r1, [r0, #DMC_PHYCONTROL0]
ldr r1, =0x00101003 @PhyControl0 DLL start
str r1, [r0, #DMC_PHYCONTROL0]
find_lock_val:
ldr r1, [r0, #DMC_PHYSTATUS] @Load Phystatus register value
and r2, r1, #0x7
cmp r2, #0x7 @Loop until DLL is locked
bne find_lock_val
and r1, #0x3fc0
mov r2, r1, LSL #18
orr r2, r2, #0x100000
orr r2 ,r2, #0x1000
orr r1, r2, #0x3 @Force Value locking
str r1, [r0, #DMC_PHYCONTROL0]
这几句代码,对应的是前4个步骤:
- To provide stable power for controller and memory device, the controller must assert and hold CKE to a logic
low level. Then apply stable clock. Note: XDDR2SEL should be High level to hold CKE to low. - Set the PhyControl0.ctrl_start_point and PhyControl0.ctrl_inc bit-fields to correct value according to clock
frequency. Set the PhyControl0.ctrl_dll_on bit-field to ‘1’ to turn on the PHY DLL. - DQS Cleaning: Set the PhyControl1.ctrl_shiftc and PhyControl1.ctrl_offsetc bit-fields to correct value
according to clock frequency and memory tAC parameters. - Set the PhyControl0.ctrl_start bit-field to ‘1’.
感觉是要打开DLL时钟有关的,并且在最后循环等待这个时钟稳定,详细可以看上面英文步骤
/* setting DDR2 */
ldr r1, =0x0FFF2010 @ConControl auto refresh off
str r1, [r0, #DMC_CONCONTROL] //对应第5步
ldr r1, =DMC0_MEMCONTROL //0x00212400
//MemControl BL=4, 1 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off
str r1, [r0, #DMC_MEMCONTROL] //对应第6步
ldr r1, =DMC0_MEMCONFIG_0 //0x30F01323
//MemConfig0 256MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed
str r1, [r0, #DMC_MEMCONFIG0] //对应第7步
ldr r1, =DMC0_MEMCONFIG_1 //0x40F01313
//MemConfig1
str r1, [r0, #DMC_MEMCONFIG1] //对应第7步
ldr r1, =0xFF000000 @PrechConfig
str r1, [r0, #DMC_PRECHCONFIG] //对应第8步,想自己了解的可以自己研究
- Set the ConControl. At this moment, an auto refresh counter should be off.
- Set the MemControl. At this moment, all power down modes should be off.
- Set the MemConfig0 register. If there are two external memory chips, set the MemConfig1 register.
- Set the PrechConfig and PwrdnConfig registers.
第6步比较重要的,涉及到我们设置的几个参数,可以简单的分析一下:
我这块板子是接了4片内存芯片,每块128M,4块一共512M,每一块内存芯片的数据是16位但是我们硬件上是用并联的方式接的,两块16位的内存芯片拼接成一个32位的内存芯片,所以下面的寄存器我们选择32bit,芯片个数选择1chip,虽然物理上的是两颗芯片,但是逻辑上是一颗32位256M的内存芯片。
第7步也很重要,可以分析一下:
chip_base:是可以指定内存地址的寄存器,我们这里设置的DMC1=0x30,就是从0x30000000开始
chip_mask:对应的是计算内存芯片大小的寄存器,这里是使用掩码的方式来计算的,256M的大小转化成16进制是0x10000000
对应的内存就是0x30000000-0x3FFFFFFF,所以这里的掩码应该是0xF0,这样反过来就对应0x0FFFFFFF,所以这个寄存器应该填0xF0
chip_map:我也不知道,按照写就可以了。
chip_col chip_row chip_bank 这三个寄存器跟内存芯片有关了
接下来简单介绍一下内存芯片寻址方式:
我现在用的芯片就是这一款芯片,128M,一共有8blank,一个blank有128M/8=16M
左边的Address Register输入有A0-A13和BA0-BA2,其中的BA0-BA2就是blank选择引脚,一共有3个引脚,23=8
接下来的A0-A13就是地址线,地址线到内存芯片中会分为Column Address Bits 和Row Address Bits,我们看图片可以得出Column Address Bits 等于10位,Row Address Bits为14位,可以计算一下
210214=224=2410241024=16M
这个刚好就得到我们一个blank的小,一共有8个,就是16M8=128M
因为我们这块板只接了一个内存芯片,所以DMC0_MEMCONFIG_1可以不设置,不过还是尽量保持默认值。
ldr r1, =DMC0_TIMINGA_REF
//TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E)
str r1, [r0, #DMC_TIMINGAREF]
ldr r1, =DMC0_TIMING_ROW //TimingRow for @200MHz
str r1, [r0, #DMC_TIMINGROW]
ldr r1, =DMC0_TIMING_DATA //TimingData CL=3
str r1, [r0, #DMC_TIMINGDATA]
ldr r1, =DMC0_TIMING_PWR //TimingPower
str r1, [r0, #DMC_TIMINGPOWER]
这是第九步,Set the TimingAref, TimingRow, TimingData and TimingPower registers according to memory AC
parameters.这些都是设置时序有关的寄存器,感兴趣的可以研究研究。
- If QoS scheme is required, set the QosControl0-15 and QosConfig0-15 registers.我也不清楚这一步在那
- Wait for the PhyStatus0.ctrl_locked bit-fields to change to ‘1’. Check whether PHY DLL is locked.
- PHY DLL compensates the changes of delay amount caused by Process, Voltage and Temperature (PVT)
variation during memory operation. Therefore, PHY DLL should not be off for reliable operation. It can be off
except runs at low frequency. If off mode is used, set the PhyControl0.ctrl_force bit-field to correct value
according to the PhyStatus0.ctrl_lock_value[9:2] bit-field to fix delay amount. Clear the
PhyControl0.ctrl_dll_on bit-field to turn off PHY DLL. - Confirm whether stable clock is issued minimum 200us after power on
第11、12步,在上面已经做了,就是设置了DLL的时候,已经在循环等待了,
ldr r1, =0x07000000 @DirectCmd chip0 Deselect
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x01000000 @DirectCmd chip0 PALL
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00020000 @DirectCmd chip0 EMRS2
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00030000 @DirectCmd chip0 EMRS3
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00010400 @DirectCmd chip0 EMRS1 (MEM DLL on, DQS# disable)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00000542 @DirectCmd chip0 MRS (MEM DLL reset) CL=4, BL=4
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x01000000 @DirectCmd chip0 PALL
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x05000000 @DirectCmd chip0 REFA
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x05000000 @DirectCmd chip0 REFA
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00000442 @DirectCmd chip0 MRS (MEM DLL unreset)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00010780 @DirectCmd chip0 EMRS1 (OCD default)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00010400 @DirectCmd chip0 EMRS1 (OCD exit)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x07100000 @DirectCmd chip1 Deselect
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x01100000 @DirectCmd chip1 PALL
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00120000 @DirectCmd chip1 EMRS2
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00130000 @DirectCmd chip1 EMRS3
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00110400 @DirectCmd chip1 EMRS1 (MEM DLL on, DQS# disable)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00100542 @DirectCmd chip1 MRS (MEM DLL reset) CL=4, BL=4
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x01100000 @DirectCmd chip1 PALL
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x05100000 @DirectCmd chip1 REFA
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x05100000 @DirectCmd chip1 REFA
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00100442 @DirectCmd chip1 MRS (MEM DLL unreset)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00110780 @DirectCmd chip1 EMRS1 (OCD default)
str r1, [r0, #DMC_DIRECTCMD]
ldr r1, =0x00110400 @DirectCmd chip1 EMRS1 (OCD exit)
str r1, [r0, #DMC_DIRECTCMD]
接下来这一批都是控制命令,就是初始化的时候需要按照顺序输入那些命令
14. Issue a NOP command using the DirectCmd register to assert and to hold CKE to a logic high level.
15. Wait for minimum 400ns
16. Issue a PALL command using the DirectCmd register
17. Issue an EMRS2 command using the DirectCmd register to program the operating parameters.
18. Issue an EMRS3 command using the DirectCmd register to program the operating parameters.
19. Issue an EMRS command using the DirectCmd register to enable the memory DLLs.
20. Issue a MRS command using the DirectCmd register to reset the memory DLL.
21. Issue a PALL command using the DirectCmd register
22. Issue two Auto Refresh commands using the DirectCmd register.
23. Issue a MRS command using the DirectCmd register to program the operating parameters without resetting
the memory DLL.
24.Wait for minimum 200 clock cycles.
25.Issue an EMRS command using the DirectCmd register to program the operating parameters. If OCD
calibration is not used, issue an EMRS command to set OCD Calibration Default. After that, issue an EMRS
command to exit OCD Calibration Mode and to program the operating parameters.
ldr r1, =0x0FF02030 //ConControl auto refresh on
str r1, [r0, #DMC_CONCONTROL]
ldr r1, =0xFFFF00FF //PwrdnConfig
str r1, [r0, #DMC_PWRDNCONFIG]
ldr r1, =0x00202400
//MemControl BL=4, 2 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off
str r1, [r0, #DMC_MEMCONTROL]
最后的就是对应最后两步了
26.If there are two external memory chips, perform steps 14~25 for chip1 memory device。这个是说chip1的
27.Set the ConControl to turn on an auto refresh counter
28. If power down modes is required, set the MemControl registers
自此内存芯片0就初始化完成,后面的是初始化内存芯片1,基本上跟内存芯片0差不多的步骤。
3、重定位
内存初始化确实麻烦,接下来我们继续往下,
cmp r7, r8
/* Clear wakeup status register */
//清除唤醒标志
ldreq r0, =S5PC100_WAKEUP_STAT
ldrne r0, =S5PC110_WAKEUP_STAT
ldr r1, [r0]
str r1, [r0]
/* IO retension release */
//这个数据手册没看到,只有三星的才知道吧
ldreq r0, =S5PC100_OTHERS @ 0xE0108200
ldrne r0, =S5PC110_OTHERS @ 0xE010E000
ldr r1, [r0]
ldreq r2, =(1 << 31) @ IO_RET_REL
ldrne r2, =((1 << 31) | (1 << 30) | (1 << 29) | (1 << 28))
orr r1, r1, r2
str r1, [r0]
接下到重点了,重定位
这里只要是实现了一个拷贝的函数(其实也不是自己实现的,是三星内部已经有了,具体的可以往下看)
void movi_bl2_copy(void) //就是这个函数实现了重定位
{
ulong ch;
#if 1
ch = *(volatile u32 *)(0xD0037488); //这一个地址里面存的是一个有意义的数据,我们看一下
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
#else
ch = *(volatile u32 *)(0xD003A508);
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008));
#endif
u32 ret;
if (ch == 0xEB000000) {
ret = copy_bl2(0, SD_START_BLOCK, SD_BLOCK_CNT,
CONFIG_SYS_TEXT_BASE, 0);
}
else if (ch == 0xEB200000) {
ret = copy_bl2(2, SD_START_BLOCK, SD_BLOCK_CNT,
CONFIG_SYS_TEXT_BASE, 0);
}
if (ret == 0)
while (1)
;
else
return;
}
在手册上,我们可以查到0xD0037488,这个地址的数据的作用,
Current boot channel:当前启动的通道。我们知道三星启动介质有几种(mmc,Nand,eSSD,eMMC等),不同的启动介质对应不同的通道,这个在手册上没写,只有三星的才知道,不过我们知道0xEB000000和0xEB200000是对应的mmc的通道0和通道2,所以我们要判断一下这个通道是不是mmc或sd卡。
**0xD0037F98 *这个地址其实也有特定的意义:
这几个是三星内部集成的设备拷贝函数,所以我们看到0xD0037F98 这个地址对应的是CopySDMMCtoMem,其实这个地址就是存放这个函数的地址,也就是一个函数指针,所以我们需要做一次转化。
这个指针的类型数据手册里面也给出了:
#define CopySDMMCtoMem(z,a,b,c,e)(((bool(*)(int, unsigned int, unsigned short, unsigned int*, bool))(*((unsigned
int *)0xD0037F98)))(z,a,b,c,e))
参数1:s32 ch:是通道号
参数2:u32 StartBlkAddress :开始块的地址(下面会介绍)
参数3:u16 blockSize:块的个数
参数4:u32 memoryPtr:需要拷贝到的内存地址
参数5:bool with_init:我也不清楚
返回值:bool(u8) - Success or failure;
既然三星都内部集成了拷贝函数,我们就不用自己写了,所以就直接调用即可。
ret = copy_bl2(2, SD_START_BLOCK, SD_BLOCK_CNT, CFG_PHY_UBOOT_BASE, 0);
1.因为我们是外插sd卡,所以使用的是通道2,第一个参数写2
2.开始的块,我们利用脚本把程序写入sd卡的时候,是指定了一个地址的,
我们来看一下烧录脚本
#<BL1 fusing>
bl1_position=1
uboot_position=49
echo "BL1 fusing"
./mkbl1 ../u-boot.bin SD-bl1-8k.bin 10192 //最后一个参数指定BL1大小
dd iflag=dsync oflag=dsync if=SD-bl1-8k.bin of=$1 seek=$bl1_position
rm SD-bl1-8k.bin
####################################
#<u-boot fusing>
echo "u-boot fusing"
dd iflag=dsync oflag=dsync if=../u-boot.bin of=$1 seek=$uboot_position
####################################
#<Message Display>
echo "U-boot image is fused successfully."
echo "Eject SD card and insert it again."
我们看到烧录脚本里面有两个变量:
bl1_position=1 这个就是bl1的起始地址
uboot_position=49 这个就是整个uboot的其实地址
这个烧录脚本的原理是这样的,它首先会获取uboot前面的10k(这个是参数的我们指定的,但是最大值为16K)作为BL1,按照bl1_position的其实地址烧录进去,然后再把整个uboot的烧录到uboot_position这个地址。
为什么BL1的地址不是从0开始,这是有原因的。
因为这个是手册上写明了,要保留第一个block,所以从Block1开始。这里也注明了1Block=512B
3.块的个数,我们假设uboot的大小为512K(当然不会有这么大的,应该有个200多k),然后每一个block大小为512B,所以块的个数:(512 * 1024 / 512)
4.需要拷贝的内存地址,这个就是整个uboot的链接地址,我们知道整个uboot的链接地址是由s5p_goni.h里面定义的,
#define CONFIG_SYS_TEXT_BASE 0x34800000
所以我们指定链接地址为CONFIG_SYS_TEXT_BASE
5.最后一个参数就默认写就可以了
这样我们就把这个uboot重定位到ddr中,其实地址就是CONFIG_SYS_TEXT_BASE
最后为了表示我们ddr内存初始化成功和重定位成功,我们在这里加一个打印,打印一个K,
/* Print 'K' */
ldr r0, =ELFIN_UART_CONSOLE_BASE
ldr r1, =0x4b4b4b4b
str r1, [r0, #UTXH_OFFSET]
这样就跟我们之前打印的一个O,拼接起来就是一个“OK”
4、长跳转
其实前面说了这么多,就是为了这里的长跳转,在刚开始我们已经看到了我们的程序已经超过了10K,也就是要超过了BL1,所以我们做了ddr内存初始化和代码重定位,就是为了在这里进行长跳转,把运行程序的内存从iRAM中跳转到ddr中,这个长跳转只有一条代码,我把它放到了start.S中。
ldr pc, =_main
这样就长跳转到了_main这个函数中了,有关_main这个函数的介绍,放到下一节。
由于水平有限,讲的哪里有错的地方,随时联系我,好让我及时改正。