赞
踩
上一章我们讲解了如何对代码进行重定位,但是将代码重定位到只有256K IRAM中作用不大。
正确的做法是将代码重定位到容量更大的主存中,即DRAM。Exynos4412中有两个独立的DRAM控制器,分别叫DMC0和DMC1。DMC0和DMC1分别支持最大1.5G的DRAM,它们都支持DDR2/DDR3和LPDDR2等,512 Mb, 1 Gb, 2 Gb, 4 Gb and 8 Gbit的内存设备,支持16/32bit的位宽。DRAM0 对应的地址是0x4000_0000~0xAFFF_FFF共1.5GB,DRAM1 对应的地址是0xA000_000~0x0000_0000共1.5GB。
图7-1、DRAM控制地址图
查阅Tiny4412的原理图:
图7-2、DRAM电路原理样图
Tiny4412的512M的DRAM是由4片大小为128M的DDR3芯片组合而成(上图仅为其中一片),观察片选引脚可知4片DRAM芯片都是挂接到DMC0处。
如何才能使用DRAM?对应Tiny4412而言,由于它只用到了DMC0,所有我们只需要初始化DMC0和DDR3 DRAM芯片即可,本实验所有的初始化代码全部来自于U-BOOT程序。需要说明一点的,我在写这个裸机程序文档之前,已经看了一段时间U-BOOT程序,U-BOOT程序中对DRAM也做了相应的初始化工作,我就直接拿来用了,在参考学习U-BOOT过程中,我也记录了一份学习文档,里面有比较详细的DRAM初始化说明,等我U-BOOT实验成功后,我会整理一份相应文档。
完整代码见目录6_sdram,与前一章的代码相比,本章的代码有了较大的修改。首先整个工程
分为BL2和USER两个目录,目录BL2下的代码会被编译链接成一个名为BL2.bin的文件,而目录user下的代码会被编译链接成一个名为user_bin.bin的文件。其中,BL2.bin文件的链接地址是0(使用的是位置无关码,程序可以在任意可用的内存中运行),user_bin.bin 文件的链接地址是0x43E00000(使用的并不是位置无关码,所有程序必须位于该地址处才能正常运行)。BL2.bin需被烧写到sd卡的扇区17,user_bin.bin需被烧写到sd卡的扇区49处,BL1去哪了?这个我没有写,还是用了U-Boot里找来的由三星提供的E4412_N.bl1.bin,烧写的SD卡扇区就是Seek1,为什么要这样烧写的原因将在后面的程序讲解中给出,完整的烧写过程可参考本章第三节的烧写步骤。整个程序的运行过程大致如下:系统上电后,首先将sd卡扇区1处的E4412_N.bl1.bin拷贝到IRAM的0x02020000地址处,然后运行该部分代码,该部分代码首先又会加载BL2.bin,BL2.bin会进行时钟和DRAM初始化,然后把位于sd卡中扇区49处的user_bin.bin拷贝到DRAM的0x43E00000地址处,最后跳转到该地址处继续运行。
相比上一章的代码,本章的start.S在多了如下三个步骤:
第一步 调用system_clock_init函数初始化时钟,函数的实现位于clock_init_tiny4412.S,这个文件是从U-BOOT中Copy过来进行了适当修改得到的。
第二步 调用mem_ctrl_asm_init函数初始化内存,函数的实现位于mem_init_tiny4412.S,这个文件是从U-BOOT中Copy过来进行了适当修改得到的。
第三步 调用uart_asm_init函数初始化串口0,函数的实现位于mem_init_tiny4412.S,这个文件是从U-BOOT中Copy过来的。我copy了这个函数是为了在调试时使用的,说实话,这个程序有时是不能正常运行历功了,我现在也没有找到原因所在,我现在主要觉得内存初始化有问题,以及下一步的拷贝函数有问题。
第四步 调用copy_code_to_dram将user_bin.bin从SD 卡拷贝到DRAM 的0x43E00000 处,
copy_code_to_dram的实现位于文件mmc_relocate.c;
clock_init_tiny4412.S是从从U-BOOT中Copy过来的,我进行了一点点修改,我之所以把这个文件也Copy过来是因为我觉得我将要Copy的mem_init_tiny4412.S中mem_ctrl_asm_init初始化函数,与时钟相关的寄存器设置是建立在U-BOOT的时钟基础上,如果不要clock_init_tiny4412.S文件,DRAM可能初始化不成功。
关于这个文件的解释,大家可以参考我随后的U-BOOT的移植说明文档。这里由于设置的寄存器过多且繁杂,不做过多说明。
Exynos4412已经告诉我们如何初始化DDR2类型的DRAM,主要分为初始化PHY DLL、初始化控制寄存器,和初始化DRAM 三大步骤,具体细分共21个小步骤,相当繁琐,由于涉及到的寄存器过多,如果一一介绍将需要非常大的篇幅,所有这个文档并不会一一解释寄存器的设置,DRAM的简要初始化步骤见下图7-3,此图是Copy于芯片手册。
图7-3、DRAM初始化简要过程
现在开始关注一下芯片手册上关于DDR3的初始化流程,找到手册 1046页,可以看到其有一段关于LPDDR2-S4的初始化步骤,LPDDR2表示低功耗DDR2,DDR3的初始化过程应和这个差不多,我们就按这个过程来初始化DDR3:
18.3.1 LPDDR2-S4
Use the sequence given here to initialize LPDDR2 devices. Unlessspecified otherwise, these steps are
mandatory. Note that the memory CK/CKn must be less than or equal to50 MHz before you initialize the
LPDDR2-S4 device.
1. DMC must assert and holdCKE to a logic low level to provide stable power for memory device and thenapply
stable clock.
2. Set thePhyControl0.ctrl_start_point and PhyControl0.ctrl_inc bit-fields to a correctvalue according to clock
frequency. Set the PhyControl0.ctrl_dll_on bit-field to"1" to activate the PHY DLL.
3. DQS cleaning: Set thePhyControl1.ctrl_shiftc and PhyControl1.ctrl_offsetc bit-fields to theappropriate value
according to clock frequency, board delay, and memory tDQSCKparameter.
4. Set thePhyControl0.ctrl_start bit-field to "1".
5. Set the ConControl. Atthis moment, an auto-refresh counter should be disabled.
6. Set the MemControl. Atthis moment, all power down modes should be disabled.
7. Set the MemConfig0register. When there are two external memory chips, set the MemConfig1register.
8. Set the PrechConfig andPwrdnConfig registers.
9. Set the TimingAref,TimingRow, TimingData, and TimingPower registers according to memory AC
parameters.
10. Set the QosControl0 to 15and QosConfig0 to 15 registers when a certain bus master requires QoS scheme.
11. Wait for thePhyStatus0.ctrl_locked bit-fields to change to "1". Verify whetherPHY DLL is locked.
PHY DLL compensates the changes of delay amount that pressure,volume, and temperature variation
causes during memory operation. Therefore, you should not power offPHY DLL for reliable operation.
It can be in power-off mode except when it runs at low frequency.When you use the power-off mode,
set the PhyControl0.ctrl_force bit-field to the correct valueaccording to the PhyStatus0.ctrl_lock_value[9:2]
bit-field for fix delay amount. Clear the PhyControl0.ctrl_dll_onbit-field to turn off PHY DLL.
12. Set thePhyControl1.fp_resync bit-field to "1" to update DLL information.
13. Confirm that Clock Enable(CKE) is in a logic low level at least 100ns after power on.
14. Issue a NOP command byusing the DirectCmd register to assert and hold CKE to a logic high level.
15. Wait for a minimum of 200s.
16. Issue a MRS command byusing the DirectCmd register to reset memory devices and program the operating
parameters.
17. Wait for minimum of 1 s.
18. Issue a MRR command byusing the DirectCmd register to poll the DAI bit of the MRStatus register.
This is to know whether or not Device Auto-Initialization iscomplete.
19. If there are two externalmemory chips, execute step 14 to 19 for chip1 memory device.
20. Set the ConControl toturn on an auto-refresh counter.
21. Set MemControl registerwhen you require power-down modes.
翻译上面的步骤:
1、DMC功能必须设置,并且要保持CKE为低电平,以便可以提供稳定的电源和时钟给DDR
2、根据时钟频率设置PhyControl0.ctrl_start_point 和PhyControl0.ctrl_inc bit-fields,并且设置PhyControl0.ctrl_dll_on bit-field 为 "1" 已启动PHY DLL。
3、DQS 清除,根据时钟频率、板子延时和芯片的tDQSCK参数设置PhyControl1.ctrl_shiftc 和PhyControl1.ctrl_offsetcbit-fields。
4、设置 PhyControl0.ctrl_start bit-field 为"1"。
5、设置ConControl,此时,不能使能自动刷新计数器(auto-refresh counter )。
6. 设置MemControl,此时,所有的power down模式应关闭。
7、设置MemConfig0 寄存器,当外面有两片存储芯片时,设置MemConfig1寄存器。
8、设置 PrechConfig和 PwrdnConfig寄存器。
9、根据DDR3的 AC参数设置TimingAref, TimingRow, TimingData, 和 TimingPower寄存器。
10、当总线主控者需要QoS时序参数时,设置QosControl0 到15寄存器和QosConfig0 到 15寄存器。
11、等待 PhyStatus0.ctrl_locked bit-fields 变成 "1",以确定 PHY DLL 是否锁定。PHY DLL 可以补偿压力、体积?和温度等环境的变化,因为在芯片工作期间,我们不能关闭了PHY DLL的电源,只有当他在低的时钟频率时才可以切换到Power-off 模式,当我们用power-off 模式,参考PhyStatus0.ctrl_lock_value[9:2]的延时参数来设置PhyControl0.ctrl_forcebit-field,清楚PhyControl0.ctrl_dll_on bit-field来关闭 PHY DLL。
12、设置PhyControl1.fp_resync bit-field 为 "1" 来更新 DLL的设置。
13、确保在电源上电后至少保持Clock Enable (CKE)在低电平100ns。
14、用DirectCmd 寄存器来执行一条NOP指令且保持CKE 为高电平。
15、至少等待200us。
16、发出MRS指令来重新设置存储芯片的操作参数。
17、至少等待1us。
18、用MRR指令来查询MRStatus的寄存器的DAI位,用这们来确定自动初始化过程是否完成。
19、如果外部有别的存储芯片,重复执行14到19步来设置芯片1。
20、设置ConControl来启动auto-refresh counter.
21、当我们要进行power-down模式,设置MemControl寄存器。
mem_init_tiny4412.S就是参考了上述的步骤进行内存的初始化,大家可参考上面步骤,对照代码来进行查看,这里不做详细说明,有时候学东西就是知道如何查看设置过程就好,太多细节我们是没有时间来一一挖出来的,其实,这样的代码流程,芯片公司都应提供好,用户顶多就是参考自己电路板进行必要参数修改,让其能正常运行即可,如有自需,自已去深挖吧。
这个程序为什么这么写,我自己不好给大家说清楚了,因为我没有办法参考着《Linux平台下Mini210S裸机程序开发指南》文档从我现有资料中找到他所说的类似的芯片说明。我只能参考着他的说明,从U-BOOT中找到一个函数,从他那里COPY了一些代码,来实现这个函数。大家先来一遍《Linux平台下Mini210S裸机程序开发指南》文档关于此函数的说明 ,我在说一下我是怎么写的这个函数的。
经过mem_init函数对DRAM的初始化后,我们就可以拷贝代码到DRAM中然后跳转到DRAM中继续运行了,由BL1目录下的mmc_relocate.c来实现这部分功能,mmc_relocate.c的代码如下:
void copy_code_to_dram(void)
{
unsigned long ch;
void (*BL2)(void);
ch = *(volatile unsigned int *)(0xD0037488);
copy_sd_sd_to_mem copy_bl2 =(copy_sd_sd_to_mem) (*(unsigned int *) (0xD0037F98));
unsigned int ret;
// 通道0
if (ch == 0xEB000000)
{
// 0:channel 0
// 49:源,代码位于扇区49,1 sector = 512 bytes
// 32:长度,拷贝32 sector,既16K
// 0x23E00000:目的,链接地址0x23E00000
ret = copy_bl2(0, 49, 32,(unsigned int*)0x23E00000, 0);
}
// 通道2
else if (ch == 0xEB200000)
{
ret = copy_bl2(2, 49, 32,(unsigned int*)0x23E00000, 0);
}
else
return;
// 跳转到DRAM中
BL2 = (void *)0x23E00000;
(*BL2)();
}
首先我们定义了一个函数指针copy_bl2,将其赋值为0xD0037F98。为什么要这么做,是因为IROM内部固化的代码已经帮我们实现了一类拷贝函数,其中就包括从sd卡拷贝内容到DRAM的函数,这类函数所位于的地址见下图:
由上图可知,External Copy Function位于0xD0037F80~0xD0038000处,其中sd卡拷贝内容到DRAM的函数就位于地址0xD0037F98,其函数原型如下:
其中:
StartBlkAddress:从第几个扇区开始拷贝,一个扇区为512byte
blockSize:拷贝多少个扇区
memoryPtr:拷贝到DRAM的哪个地址上
with_init:是否需要初始化sd卡
有了上面这些知识,也就很容易看懂copy_code_to_dram 函数了。通过读地址0xD0037488上的值来确定是使用通道0还是通道1,芯片手册上明确指出“sd/MMC/eMMC boot – MMC Channel 0 is used for first boot. And Channel 2 is used forSecond boot”,我们的BL1.bin就是first boot,所以会使用通道0,调用CopysdMMCtoMem函数将BL2.bin从sd卡的扇区49拷贝到DRAM的0x23E00000处,拷贝的长度是16K。最后给BL2这个函数指针赋值0x23E0000,然后调用BL2函数即可跳转到0x23E0000处运行BL2.bin里的代码了。
大家看明白了,一句话三星的芯片内部已经有一个函数是用为从外部存储COPY东西内部iRAM或者DRAM中了,但是我在现有三星手册上没有找到类似的函数说明,没办法,我们这些学习者,是很难拿全芯片厂商的资料的。但也不是就没有办法实现这个实验了,U-BOOT中总会干这么个事吧,里面肯定有相关代码,所以我只能去分析U-BOOT的代码了,里面有一个文件叫/arch/arm/cpu/armv7/exynos/irom_copy.c的文件,里面的一个函数movi_uboot_copy(),就是将U-BOOT复制到DRAM中的,好了从这里复制必要代码实现自己的函数吧,我们的函数主要内容如下:
下面内容是定义了我们将要使用的COPY函数,这个函数就是实现了Mini210S中的copy_bl2的功能。
#defineISRAM_ADDRESS 0x02020000
#defineSECURE_CONTEXT_BASE 0x02023000
#defineEXTERNAL_FUNC_ADDRESS (ISRAM_ADDRESS +0x0030)
#defineEXT_eMMC43_BL2_ByCPU_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x4)
#defineMSH_ReadFromFIFO_eMMC_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x14)
#defineMSH_EndBootOp_eMMC_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x18)
#defineLoadImageFromUsb_ADDRESS (EXTERNAL_FUNC_ADDRESS+ 0x40)
#defineSDMMC_ReadBlocks(uStartBlk, uNumOfBlks, uDstAddr)
\\定义COPY函数的原型。
(((void(*)(unsigned int, unsigned int,unsigned int*))(*((unsigned int *)EXTERNAL_FUNC_ADDRESS)))(uStartBlk,uNumOfBlks, uDstAddr))
下面开始复制代码到DRAM中,首先声明了两个串口输出函数,实现这两个函数是为了调试用的。因为我说过,我现在程序不一定能执行成功,有时LED灯闪烁了,有时又没有。所以我打出了必要东西来调试用,大家也当学习嘛。
externvoid uart_asm_putc(int c);
externvoid uart_asm_putx(int x);
voidcopy_code_to_dram(void)
{
void (*user_bin)(void);
//这里怕DRAM没有初始化完成,等待了一会
volatile unsigned long count=0x100000;
while(count>0){
count--;}
uart_asm_putc('C');
uart_asm_putc('O');
uart_asm_putc('P');
uart_asm_putc('Y');
uart_asm_putc('\r');
uart_asm_putc('\n');
//从SD卡扇区49处复制32个扇区的内容到内存地址0x43e00000处
SDMMC_ReadBlocks(49,32,0x43e00000);
uart_asm_putc('O');
uart_asm_putc('V');
uart_asm_putc('E');
uart_asm_putc('R');
uart_asm_putc('\r');
uart_asm_putc('\n');
unsigned int *p;
int i;
//输出内存地址0x40000000处50个字的内容
p = (unsigned int *) 0x40000000;
for (i = 0; i < 50; i++) {
uart_asm_putx(*(p+i));
uart_asm_putc(' ');
}
uart_asm_putc('\r');
uart_asm_putc('\n');
uart_asm_putc('\r');
uart_asm_putc('\n');
//输出内存地址0x43dfffF0处100个字的内容
p = (unsigned int *) 0x43dfffF0;
for (i = 0; i < 100; i++) {
uart_asm_putx(*(p+i));
uart_asm_putc(' ');
}
uart_asm_putc('\r');
uart_asm_putc('\n');
uart_asm_putc('\r');
uart_asm_putc('\n');
//输出内存地址0x43e00000处100个字的内容
p = (unsigned int *) 0x43e00000;
for (i = 0; i < 100; i++) {
uart_asm_putx(*(p+i));
uart_asm_putc(' ');
}
// 跳转到DRAM处执行
user_bin = (void *)0x43e00000;
(*user_bin)();
}
Makefile的内容如下所示,其实总的内容还是和以前几章下的Makefile没有太多区别,主要的区别是我将前面几章中提到的sd_fuse目录下的V310-EVT1-mkbl2.c放到了BL2目录下,所以这里在Makefile里增加两行话,给生成的u-boot.bin文件增加较验和尾部。所以后面的烧写操作也和前几章的有一所不同了。
sdram.bin: start.o clock_init_tiny4412.o mem_init_tiny4412.ommc_relocate.o
arm-linux-ld-Tsdram.lds -o sdram.elf $^
arm-linux-objcopy-O binary sdram.elf u-boot.bin
arm-linux-objdump-D sdram.elf > sdram_elf.dis
gcc V310-EVT1-mkbl2.c -o mkbl2
./mkbl2 u-boot.bin bl2.bin 14336
%.o : %.S
arm-linux-gcc -o $@$< -c
%.o : %.c
arm-linux-gcc -o $@$< -c
clean:
rm *.o *.elf *.bin*.dis -f
SECTIONS
{
. = 0x43E00000;
.text : {
start.o
* (.text)
}
.bss : {
* (.bss)
}
.data : {
* (.data)
}
}
从上可以看出user_bin.bin的链接地址是0x43E00000。
BL2.bin跳转到0x43E00000后,实际上运行的就是USER/start.S里的代码,因为user_bin.bin的链接脚本sdram.lds里指定了代码段的最开始放的是start.o。在USER/start.S中只做了一件事,就是使用一条位置相关的指令:ldr PC, =main来调用main函数,main函数的作用与上一章的代码一样化,都是LED闪烁。
fast_fuse.sh主要的变动是增加了烧写user_bin.bin的dd命令,如下所示,烧写的位置是49.
uboot_position=49
#<user_bin fusing>
echo "---------------------------------------"
echo "user_bin fusing"
dd iflag=dsync oflag=dsync if=./USER/user_bin.bin of=$1seek=$uboot_position
已将SD卡插入电脑,假设linux识别了SD卡,其识别号为sdb。执行下面命令:
# chmod 777 –R 6_sdram
# cd 6_sdram
# make
# ./ fast_fuse /dev/sdb
将sd卡插入Tiny4412中,选择sd卡启动,和电脑能过串口0连接好,打开一个串口调试助手,然后上电,可以看到以下现象:
串口助手中会显示一些信息,LED会正常闪烁,该现象与上一章的代码运行效果一模一样,但是程序的运行过程却有了很大的区别。到此为止,我们就基本了解重定位的相关知识了。
如果没有正常闪烁,大家可以对比串口助手打印的0x43e00000位置处二制值指令是否和USER/ user_bin_elf.dis的反汇编指令一样,如果不一样就是问题所在,估计没有拷贝成功代码,原因可能有时钟、内存初始化不对,复制代码函数有问题等。还有一个可能就是你的SD卡有问题,建议重新换一张来试试,我两张SD卡,有一张时好时坏,我调试了很久也没有找到原因,后来我重新换了一张程序就能很正常的运行了。
按理说下面一章本来应参考着Mini210S的文档,用Minitools进行实验了,但当我用 Tiny4412的所MiniToolS来实验裸机程序时,才发现不能像Mini210S说的那样运行程序,我也没有找到方法使MiniToolS能下载裸机程序,所以《Linux平台下Mini210S裸机程序开发指南》后面关于使用Minitools的下载程序实验我也没有去深究了,如果谁找到方法,请分享了来吧。我急于去实现U-BOOT下LCD显示功能,下一章就用本章的方法来实验LCD裸机程序的实验。为什么现在才开始做LCD裸机程序,是由于LCD显示需要一块大的Bufffer来存储图像数据,前面在Exynos4412芯片内部RAM是没有这么大的空间的,所以只能到现在,将外部DRAM初始化了,且能实现代码的得定向了,就能随心所欲的运行一些稍大的程序了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。