17.初始化SDRAM和代码重定位

    xiaoxiao2021-03-25  68


    17.1.代码重定位原理分析 (1)在SRAM中将代码从0xd0020010重定位到0xd0024000,本来代码是运行在0xd0020010的,但是我们又希望代码实际是在0xd0024000位置运行,此刻就需要重定位了,此次重定位实践纯粹是为了练习重定位技能,但某些情况重定位就是必须的,譬如在uboot中。 (2)通过链接脚本将代码链接到0xd0024000;通过dnw下载时将bin文件下载到0xd0020010/BL0直接从SD卡中读取bin文件到0xd0020010处;保证代码实际下载运行在0xd0020010,但一开始就被链接在0xd0024000,从而实现简单的重定位。 (3)我们把代码链接地址设置为0xd0024000(该段代码将来必须放在0xd0024000位置才能正确执行,若实际运行地址不是该地址就要出事(除非代码是PIC位置无关码));重定位代码的实质->在PIC执行完之前(在代码中第1句位置有关码执行之前)必须将整个代码搬移到0xd0024000位置去执行(代码执行时通过代码前段的少量位置无关码将整个代码搬移到0xd0024000;然后使用1个长跳转指令跳转到0xd0024000处的代码继续执行,重定位完成)。 (4)长跳转->首先该行代码是1句跳转指令(ARM中的跳转指令作用类似于分支指令B和BL),跳转指令通过给PC(r15)赋1个新值来完成代码段的跳转执行,长跳转指的是跳转到的地址和当前地址差异比较大,跳转的范围比较宽广;当我们执行完代码重定位后,实际上在SRAM中有2份代码的镜像(1份是我们下载到0xd0020010处开头的,另1份是重定位代码复制到0xd0024000处开头的),这两份内容完全相同,仅仅代码起始地址不同。 (5)短跳转和长跳转的区别->重定位之后使用”ldr-pc,=water_lights”该句长跳转指令从0xd0020010开头的代码处直接跳转到0xd0024000开头的代码的water_lights函数处去执行(实际上在SRAM中有2个water_lights函数镜像,两个都能执行,如果短跳转”bl-water_lights”则执行的是0xd0020010开头的这1份,如果长跳转”ldr-pc,=water_lights”则执行的是0xd0024000开头处的这1份)。 (6)当链接地址和运行地址相同时,短跳转和长跳转实际效果是一样的;但是当链接地址不等于运行地址时,短跳转实际执行的是运行地址处的那1份代码,而长跳转执行的是链接地址处那1份代码。重定位实际就是在运行地址处执行1段位置无关码PIC,让这段PIC(重定位代码)从运行地址处把整个程序镜像拷贝1份到链接地址处,然后使用1句长跳转指令从运行地址处直接跳转到链接地址处去执行同1个函数(water_lights)。


    17.2.代码重定位实践 (1)adr与ldr伪指令的区别->ldr和adr都是伪指令,区别是ldr是长加载,adr是短加载(adr指令加载符号地址,加载的是运行时地址;ldr指令加载符号地址,加载的是链接地址)。 (2)重定位(代码拷贝)->重定位的实现即汇编代码中的copy_loop函数,该函数的作用是使用循环结构来逐句复制代码到链接地址;复制的源地址是SRAM的0xd0020010,复制目标地址是SRAM的0xd0024000,复制长度是bss_start减去_start;则复制的长度是整个重定位需要重定位的代码长度,即整个程序中代码段+数据段的长度,bss段(bss段中就是0初始化的全局变量)不需要重定位。 (3)清bss段->清除bss段是为了满足C语言的运行时要求(C语言要求显式初始化为0的全局变量,或者未显式初始化的全局变量的值为0,实际上C语言编译器就是通过清bss段来实现C语言的该特性的);我们的程序在平常是不需要负责清零bss段的(C语言编译器和链接器会帮我们的程序自动添加1段头程序,该段头程序会在我们的main函数之前运行,该段代码就负责清除bss);在我们代码重定位了之后,因为编译器帮我们附加的代码只是帮我们清除了运行地址那1份代码中的bss,而未清除重定位地址处开头的那1份代码的bss,则重定位之后需要自己去清除bss。 (4)长跳转->清理完bss段后重定位就结束了;此时当前运行地址还在0xd0020010开头的(重定位前的)那1份代码中运行着;此时SRAM中已经有了2份代码,1份在d0020010开头,另1份在d0024000开头的位置;然后就要长跳转了(ldr-pc,=water_lights)。


    17.3.SDRAM初步引入 (1)SDRAM(同步动态随机存储器);DDR(双倍速度的SDRAM,是SDRAM的升级版,DDR有好多代->DDR1+DDR2+DDR3+DDR4+LPDDR);SDRAM的特性(容量大+价格低+掉电易失性+随机读写+总线式访问);SDRAM/DDR都属于动态内存(相对于静态内存SRAM),都需要先运行初始化代码进行初始化OK后才能正常使用,不像SRAM开机上电后就可以直接运行(类似于SDRAM和SRAM的区别的,还有NorFlash和NandFlash(硬盘))。 (2)SDRAM在系统中属于SoC外接设备;现在还长期在SoC外部的外设为Flash+SDRAM/DDR+网卡芯片如DM9000+音频Codec;SDRAM通过地址总线接口和数据总线接口与SoC通信。 (3)GEC210开发板上的DDR的丝印型号为”K4T1G164QE”(128MB字节);全球做SDRAM的厂商不多,二线厂家做的产品参数都是向一线厂家(三星/KingSton)看齐,目的是兼容一线厂家的设计,然后让在意成本的厂商选择它的内存芯片替代一线厂家的内存芯片;SDRAM的市场特征导致其中比较标准化,大部分时候细节参数官方(芯片原厂)都会提供参考值给你。 (4)K4T1G164QE->K表示三星产品;4表示是DRAM;T表示产品号码;1G表示容量(1Gb=128MB,GEC210开发板上共用了4片相同的内存,总容量=128MB×4=512MB字节);16表示单芯片是16位宽的;4表示是8bank。 (5)核心板原理图中SDRAM相关部分->S5PV210共有2个内存端口(Memory_Port1和Memory_Port2);再结合查阅数据手册中内存映射部分->两个内存端口分别叫DRAM0(内存地址范围0x20000000~0x3FFFFFFF(512MB),对应引脚是Xm1xxxx)和DRAM1(内存地址范围0x40000000~0x7FFFFFFF(1024MB),对应引脚是Xm2xxxx)。 (6)整个210最多支持内存为1.5GB,如果给210更多的内存CPU就无法识别;GEC210开发板上512MB内存的连接方法是在DRAM0端口分布256MB,在DRAM1端口分布256MB;GEC210开发板上内存合法地址为0x20000000~0x2FFFFFFF(256MB)+0x40000000~0x4FFFFFFF(256MB);当板子上DDR初始化完成之后,这些地址都是可以正常使用的。


    17.4.SDRAM矩阵式寻址方式 (1)在核心板上的SoC的内存端口(MemoryPort)外设的原理图->每个DDR端口都由3类总线构成=地址总线(XMnADDR0~XMnADDR12共13根地址线)+控制总线+数据总线(XMnDATA0~XMnDATA31共32根数据线)。 (2)GEC210开发板共使用了4片内存(每片1Gb=128MB);每片内存的数据总线都是16位的(单芯片是16位内存);在原理图上横向的2颗内存芯片是并联连接的,并联时地址总线接法一样,但是数据总线要加起来,在逻辑上即可以把这2颗16位的内存芯片看成是1颗32位的内存芯片连接在端口上。 (3)在SDRAM中寻址是首先通过bank寻址线寻址到某个具体的bank;然后在具体的某个bank中再通过行地址(row-address)和列地址(column-address)寻址到某个具体的存储单元(一般的存储单元的大小为4bit/8bit/16bit);行列地址值由相应的地址线引脚分时复用得到。 (4)譬如GEC210中的某块”K4T1G164QE”内存芯片;此芯片有容量为128MB=8bank*16MB=8bank*64Mb*16bit(存储单元为16bit);210的DDR端口信号中有BA0~BA2,接在内存芯片的BA0~BA2上(该3个引脚即”bank寻址线”,用来选择bank);每个bank通过行地址(13位)+列地址(10位)的方式来综合寻址(矩阵式寻址);则一共能寻址的范围->2^13*2^10*16bit=2的24次方;即2^7Mbit=2^4MB(对应16MB(128Mbit)内存)。 (5)参考链接->http://www.crifan.com/summary_embedded_peripherals_sdram/。


    17.5.汇编初始化SDRAM (1)汇编初始化SDRAM是通过sdram_asm_init汇编函数在sdram_init.S文件中实现的(汇编实现的函数在返回时需要明确使用返回指令mov-pc,lr);DDR初始化和SoC中的DDR控制器有关;也和开发板使用的DDR芯片有关;也和开发板设计时DDR的连接方式有关。 (2)S5PV210的DDR初始化步骤在SoC数据手册(1.2.1.3-DDR2章节)可知初始化DDR共需27个步骤;GEC210的内存连接方式->在DRAM0上连接256MB+在DRAM1上连接了256MB,则第1部分初始化DRAM0+第2部分初始化DRAM1;我们的代码来源=GEC210开发板官方的uboot源码+参考了GEC210裸机教程中对DDR的初始化+根据自己的理解修改某些参数。 (3)设置IO端口驱动强度->因为DDR芯片和S5PV210之间是通过很多总线连接的,总线的物理表现就是很多个引脚,也就是说DDR芯片和S5PV210芯片是通过一些引脚连接的,DDR芯片工作时需要一定的驱动信号,这个驱动信号需要一定的电平水平才能抗干扰,所以需要设置这些引脚的驱动能力,使DDR正常工作(S5PV210中的DRAM控制器对应的引脚设置为驱动强度2X,这些参数由DDR芯片厂商或者SoC厂商提供,我们一般是参考原厂给的代码)。 (4)DDR配置过程比较复杂,基本上是按照DDR控制器的时序要求来做的,其中很多参数要结合DDR芯片本身的参数来定,还有些参数是时序参数,要去详细计算,所以DDR配置非常繁琐+细致+专业,所以我们只需要学会这种思路和方法,结合文档和代码能看懂,会算一些常见的参数即可(见图1和图3)。





    17.relocate_sram/start.S /* * 公司:XXXX * 作者:Rston * 博客:http://blog.csdn.net/rston * GitHub:https://github.com/rston * 项目:初始化SDRAM和代码重定位 * 功能:演示在SRAM中实现重定位。 */ #define WTCON 0xE2700000 #define SVC_STACK 0xD0037D80 .global _start // 把_start链接属性改为外部,则外部其它文件可看见_start _start: // 关闭看门狗 ldr r0, =WTCON ldr r1, =0x0 str r1, [r0] // 设置SVC_STACK栈 ldr sp, =SVC_STACK // 关闭或开的icache mrc p15,0,r0,c1,c0,0; // 读出协处理器C1 //bic r0, r0, #(1<<12) // 第12位置0,关闭icache orr r0, r0, #(1<<12) // 第12位置1,打开icache mcr p15,0,r0,c1,c0,0; // 给C1赋值 // 重定位 adr r0, _start // adr指令加载_start运行时地址 ldr r1, =_start // ldr指令用于加载_start的链接地址 ldr r2, =bss_start // bss段的起始地址,此地址为链接地址 cmp r0, r1 // 比较_start的运行时地址和链接地址是否相等 beq clean_bss // 若相等则不需重定位,则跳过copy_loop,直接clean_bss copy_loop: ldr r3, [r0], #4 // 运行时地址的源代码内容,即_start运行时地址的内容 str r3, [r1], #4 // 拷贝到链接地址处形成第2份源代码,完成4个字节内容的拷贝 cmp r1, r2 // r1r2都是用ldr加载的,都是链接地址,所以r1不断+4总能等于r2 bne copy_loop clean_bss: // 清bss段,其实就是在链接地址处把bss段全部清零 ldr r0, =bss_start ldr r1, =bss_end cmp r0, r1 // 如果r0等于r1,说明bss段为空,直接下去 beq run_on_dram // 清除bss完之后的地址 mov r2, #0 clear_loop: str r2, [r0], #4 // 先将r2中的值放入r0所指向的内存地址,然后r0 = r0 + 4 cmp r0, r1 bne clear_loop run_on_dram: ldr pc, =water_lights // ldr指令实现长跳转 // 汇编最后的这个死循环不能丢 b .
    17.sdram_init/sdram_init.S // 汇编实现DRAM0的初始化 #include "s5pv210.h" // 各种配置参数的宏名化 #if 1 #define DMC0_MEMCONTROL 0x00202400 // MemControl BL=4, 1Chip, DDR2 Type, dynamic self refresh, force precharge, dynamic power down off #define DMC0_MEMCONFIG_0 0x20F00313 // MemConfig0 Row Address Bits 13 bits,Linear ({bank, row, column, width}) #define DMC0_MEMCONFIG_1 0x30F00312 // MemConfig1 默认值 #define DMC0_TIMINGA_REF 0x00000618 // TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4E) #define DMC0_TIMING_ROW 0x28233287 // TimingRow for @200MHz #define DMC0_TIMING_DATA 0x23240304 // TimingData CL=3 #define DMC0_TIMING_PWR 0x09C80232 // TimingPower #define DMC1_MEMCONTROL 0x00202400 // MemControl BL=4, 2 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off #define DMC1_MEMCONFIG_0 0x40F01323 // MemConfig0 512MB config, 8 banks,Mapping Method[12:15]0:linear, 1:linterleaved, 2:Mixed #define DMC1_MEMCONFIG_1 0x60E00312 // MemConfig1 #define DMC1_TIMINGA_REF 0x00000618 // TimingAref 7.8us*133MHz=1038(0x40E), 100MHz=780(0x30C), 20MHz=156(0x9C), 10MHz=78(0x4 #define DMC1_TIMING_ROW 0x28233289 // TimingRow for @200MHz #define DMC1_TIMING_DATA 0x23240304 // TimingData CL=3 #define DMC1_TIMING_PWR 0x08280232 // TimingPower #endif // 声明sdram_asm_init函数能被外部调用 .global sdram_asm_init sdram_asm_init: // 估计只有三星原厂工程师知道,直接照抄 ldr r0, =0xf1e00000 ldr r1, =0x0 str r1, [r0, #0x0] // 设置IO总线驱动电平保证DRAM驱动信号能够正常传输,起到抗干扰的作用 // 默认设置,不用管,照抄即可,注意不可删除DMC1的驱动电平设置,若删除,则程序无法正常运行 /* DMC0 Drive Strength (Setting 2X) */ ldr r0, =ELFIN_GPIO_BASE ldr r1, =0x0000AAAA str r1, [r0, #MP1_0DRV_SR_OFFSET] 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] // 设置IO总线驱动电平保证DRAM驱动信号能够正常传输,起到抗干扰的作用 // 默认设置,不用管,照抄即可,注意不可删除DMC1的驱动电平设置,若删除,则程序无法正常运行 /* 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] // 开启dll(时钟倍频器)然后等待锁存,默认设置,不用管,照抄即可 /* 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] // 设置初始化DDR2相应的寄存器 /* setting DDR2 */ // 设置DRAM相关时序的寄存器,估计除了原厂的人没有人能搞懂,直接参考原厂设置即可 ldr r1, =0x0FFF2010 @ConControl auto refresh off str r1, [r0, #DMC_CONCONTROL] // 设置DRAM规格的控制器,GEC210开发板和X210开发板设置相同 ldr r1, =DMC0_MEMCONTROL @MemControl BL=4, 1 chip, DDR2 type, dynamic self refresh, force precharge, dynamic power down off str r1, [r0, #DMC_MEMCONTROL] // 设置DRAM起始地址+内存容量大小+bank数目+行列地址 // 此项设置与开发板使用的DDR芯片有关+也和开发板设计时DDR的连接方式有关 // GEC210开发板和X210开发板在行列地址的位数设置不同 ldr r1, =DMC0_MEMCONFIG_0 @MemConfig0 Row Address Bits 13 bits,Linear ({bank, row, column, width}) 1:linterleaved, 2:Mixed str r1, [r0, #DMC_MEMCONFIG0] ldr r1, =DMC0_MEMCONFIG_1 @MemConfig1 str r1, [r0, #DMC_MEMCONFIG1] // 估计只有三星原厂工程师知道,直接照抄 ldr r1, =0xFF000000 @PrechConfig str r1, [r0, #DMC_PRECHCONFIG] // 需参考数据手册和时钟系统计算得到相应的参数 // TimingAref 7.8us*200MHz=1560(0x618) 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] // 以下设置时钟的代码,一般默认不改变,可参考时钟体系得到DDR的时钟来源为200MHz 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] // 以下代码均为给DRAM发送相应的命令,可参看210手册中的27步命令,默认不改变 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] // 以下为扫尾的代码,一般不去动,可适当注意下DMC_MEMCONTROL设置是否与前面设置相同 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] // 函数返回 mov pc, lr
    转载请注明原文地址: https://ju.6miu.com/read-13670.html

    最新回复(0)