(7)uboot详解——初始化SDRAM
在开始这篇文章之前,我们先回顾一下(1)uboot详解——板子刚上电时都干了些什么,不管板子是从nandflash启动还是从norflash启动,它的开始代码都不是在SDRAM中运行的,要么在stepping stone中运行,要么在norflash中运行,但是stepping stone的4k片上内存显然是不够的,而且norflash可读不可写的特性也满足不了uboot的运行要求,那么就需要将程序搬运到SDRAM中运行,SDRAM不仅有足够大的空间,而且速度较快,满足读写条件,价格也实惠。但是它的掉电易失性决定了它不能够静态存储程序,它需要不断的刷新内存来保证数据的不丢失。
所以程序的存储是在flash中的,而程序的运行是在内存中,那么cpu是怎么将flash中的程序搬运到内存中执行的呢?这部分是一个重点,也是启动过程中的一个难点。
一、SDRAM内存工作原理
这里先了解一下SDRAM的工作原理,关于芯片电气和电路方面的知识,作为程序编写人员这里不太关心,我们只关心需要使用 的部分。
简单地说,SDRAM的内部 是一个存储阵列。因为如果是管道式存储(就如排队买票),就很难做到随机访问了。 阵列就如同表格一样,将数据“填”进去,你可以它想象成一张表格。和表格的检索原理一样,先指定一个行(Row),再指定一个列 (Column),我们就可以准确地找到所需要的单元格,这就是内存芯片寻址的基本原理。对于内存,这个单元格可称为存储单元, 那么这个表格(存储阵列)叫什么呢?它就是逻辑Bank(Logical Bank,下文简称L-Bank)。
由于技术、成本等原因,不可能只做一个全容量的L-Bank,而且最重要的是,由于SDRAM的工作原理限制,单一的L-Ban k将会造成非常严重的寻址冲突,大幅降低内存效率(请自行了解)。所以人们在SDRAM内部分割成多个L-Bank,较 早以前是两个,目前基本都是4个,这也是SDRAM规范中的最高L-Bank数量。到了RDRAM则最多达到了32个,在最新DDR-Ⅱ的标准中,L-Bank的数量也提高到了8个。这样,在进行寻址时就要先确定是哪个L-Bank,然后再在这个选定的L-Bank中选择相应的行与列进行寻址。
也就是说,一块32M的内存,我们不会将它当成一个整块来访问,我们会将它分成4个L-Bank,以这里使用的HY57V561620(L)T为例,它将32M的内存分成4个L-bank,每个L-bank的大小是4M,因为它的为宽是16位,两个字节,所以32=4*4*2.
上面是其中一个L-bank的示意图,因为每个L-bank的大小是4M,所以有2*2^10个单元格,每个单元格都可以写入1或0。
当要需要对内存进行操作时,首先需要确定操作的L-bank,因此需要对L-bank进行选择,在内存芯片的外部引脚上多了两个引脚BA0和BA1,用来片选4个L-bank。A0~A12是地址总线,2^13,它最多能够寻址8M的内存空间,到底使用什么机制来实现对64M内存空间的寻址呢?SDRAM的行地址线和列地址线是分时复用的,即地址需要分两次送出,先送出行地址(nSRAS行有效操作),再送出列地址(nSCAS列有效操作)。这样可以大幅减少地址线的数目,提高器件的性能和减少制作的复杂度,但是寻址过程也会因此变的复杂。实际上,现在的SDRAM都是以L-bank为基本寻址对象的,由L-bank地址线BAn控制L-bank间的选择,行地址线和列地址线贯穿连接所有的L-bank,每个l-bank的数据宽度和整个存储器的宽度相同,这样可以加快数据的存储速度。同时,BAn还可以使为被选中的L-banl工作于低功耗的模式下,从而降低器件的功耗。
从图中可以看出,使用HY57V561620(L)TSDRAM芯片时,主要需要配置的引脚有:
CKE:时钟使能
CS:芯片使能
RAS:列使能
CAS:行使能
WE:写使能
UDQM/LDQM:高低字节掩码
BA0/BA1:片选信号
数据总线和地址总线。
二、SDRAM连线方式
SDRAM的连线方式是由cpu决定的,不同cpu有不同的mmc,对应不同的寄存器,所以会有不同的连线方式。
下面我们在翻出s3c2440的内存映射图:
从图中可以看出,当不是从nandflash启动的时候,mmc可以管理8个段,分别是nGCS0~nGCS7,外加一个4k的片上内存SRAM;当从nandflash启动的时候,mmc将这4k的片上内存指定到0x00位置,占用nGCS0的地址。
从图中可以看出,段nGCS0~nGCS5只能接SROM,段nGCS6~nGCS7既可以接SROM,也可以接SRAM,所以如果需要接SRAM只能接到nGCS6或nGCS7上。
我们知道,S3C2440对外引出了27根地址线 ADDR0~ADDR26,如下图左侧所示,是不是只有27根? 27根地址线所能寻址的最大范围为2^27=128MB。
仅仅128MB显然是不够的,所以又有了BANK,什么是BANK,按我个人的理解,就是分段,注意区分L-bank和BANK,L-bank是指SDRAM中的分块,比如将64M分成4块,每块是16M,BANK是指cpu内存映射的分段,比如将1G分成8个段,每个段的大小是128M,每个段可以接RAM或者ROM。
显然,对应上面我们应该也可以看出,每个BANK的大小为128MB。有多少个BANK呢?8个,对应的CPU又引出了8根片选信号nGCS0~nGCS7。当访问某个BANKx的时候,相应的nGCSx的引脚设为低电平,选中外SDRAM,nGCS6将设为0,A0~A12与CPU的ADDR2~ADDR14相接,做为地址线。BA0、BA1与ADDR24、ADDR25连接,做为L-Bank的选择线。注意nSCS,通过连线LnSCS0我查到了与之连接的是CPU的nGCS6引脚,证明该SDRAM芯片是接在BANK6上的。如下图所示:因为HY57V561620(L)TSDRAM芯片是16位宽的,所以使用两块16位的并联,组成32位宽的内存,连线如图:
内存寻址一次,就是行(ROW)列(Column)交叉一次,得到一个存储单元,也就是说内存是以"存储单元(本例为4个字节,低16位与高16位合成一个存储单元)"为单位的,而不是以"字节"为单位的.但是,cpu寻址是以字节为单位的.也就是说,cpu移动到下一个才一个字节,而本例内存移动到下一个就是4个字节.所以我们写程序时常常说要字节对齐就是这个原因.下面我举例子具体说明一下:
cpu 寻址内存 内存返回给cpu
0000 0000 第0个单元(其实包含0000 0000 至 0000 0011 这4个字节)
0000 0100 第1个单元(其实包含0000 0100 至 0000 0111 这4个字节)
00000101 第1个单元(其实包含0000 0100 至 0000 0111 这4个字节) 因为s3c2440没接A1和A0,所以相于逻辑与0xFFFF FF00,取到内存的4个字节后再用低两位去选择
0000 1100 第3个单元(其实包含0000 1100 至 0000 1111 这4个字节)
如此类推.....
cpu 的第2位对应了内存的第0位.也就是说cpu的4,而内存看成是1,他们的单位不同而已,一个是字节,一个是存储单位,也就是位宽.
同理,16位的位宽SDRAM的A0应该接到s3c2440的ADDR1上.
结论:SDRAM的A0接到s3c2440的那一个引脚上是根据整个SDRAM的位宽决定的.
读到此时,又可引申出两个问题:
1.为什么与A0连接的是LADDR2而不是LADDR0?
答:首先我们需要知道,在CPU的寻址空间中,是按8bits即1字节为单位的。并且我们的S3C2440以32位的宽度外接SDRAM,故最小操作单位为32bits即4字节。比如当A0=1时,对应的LADDR[2:0]=100。不管地址是多少,LADDR[1:0]恒为0,所以A0接LADDR2。
同理,如果我们只有1片SDRAM,所以是16位的宽度,那么最小操作单位为2字节,对应的A0应该接LADDR1。
所以说,A0接哪根地址线是根据SDRAM的数据位宽度来定的。
2.根据什么原理判定与BA0、BA1连接的是LADDR24、LADDR25?
答:在搞清楚问题1的情况下,再来分析问题2。与BA0、BA1相连的两根地址线是寻址地址中的最高两位,故可以得出,连接LADDR24和LADDR25后,CPU总寻址2^26=64Mbyte,我的SDRAM是64Mbyte,刚刚好。
所以说,BA0、BA1接哪根地址线是根据SDRAM的容量大小来定的。
还要补充一点,首先我们将两片SDRAM看做一个整体,那么64Mbyte的SDRAM每个L-Bank大小应该为16Mbyte,怎样推算出来?每个L-Bank上有13根行地址线,9根列地址线,可寻址2^22=4M个,而由问题1我们得知,每单元大小为4Byte,所以每个L-Bank的大小为4M * 4byte = 16Mbyte。
最后,总结下CPU对SDRAM访问的几个步骤:
1.CPU发出的片选信号nSCS0有效,选中SDRAM芯片。
2.SDRAM中有四个L-Bank,需要两根地址信号来选中其中一个,本文中使用LADDR24和LADDR25作为选择信号。
3.对被选中的芯片进行统一的行/列寻址。
4.找到了存储单元后,被选中的芯片进行统一的数据传输。
三、SDRAM的读操作
SDRAM进行读操作时,先向地址线上送上要读取数据的地址,通过前面的知识了解到,地址被分成3部分,行地址,列地址,L-Bank片选信号。片选(L-Bank的定址)操作和行有效操作可以同时进行。
在CS、L-Bank定址的同时,RAS(nSRAS行地址选通信号)也处于有效状态。此时 An地址线则发送具体的行地址。A0~A12,共有13根地址线(可表示8192行),A0~A12的不同数值就确定了具体的行地址。由于行有效的同时也是相应 L-Bank有效,所以行有效也可称为L-Bank有效。
行地址确定之后,就要对列地址进行寻址了。但是,地址线仍然是行地址所用的 A0~A12。没错,在SDRAM中,行地址与列地址线是复用的。列地址复用了A0~A8,共9根(可表示512列)。那么,读/写的命令是怎么发出的呢?其实没有一个信号是发送读或写的明确命令的,而是通过芯片的可写状态的控制来达到读/写的目的。显然WE信号(nWE)就是一个关键。WE无效时,当然就是读取命令。有效时,就是写命令。
SDRAM基本操作命令,通过各种控制/地址信号的组合来完成(H代表高电平,L代表低电平,X表示高,低电平均没有影响)。此表中,除了自刷新命令外,所有命令都是默认CKE(SCKEl输入时钟频率有效)有效。列寻址信号与读写命令是同时发出的。虽然地址线与行寻址共用,但CAS(nSCAS列地址选通信号)信号则可以区分开行与列寻址的不同,配合A0~A8,A9~A11来确定具体的列地址。
读取命令与列地址一块发出(当WE为低电平是即为写命令)然而,在发送列读写命令时必须要与行有效命令有一个间隔,这个间隔被定义为 tRCD,即RAS to CAS Delay(RAS至 CAS延迟),这个很好理解,在地址线上送完行地址之后,要等到行地址稳定定位后再送出列地址,tRCD是SDRAM的一个重要时序参数,相关数值参看对应芯片硬件手册。通常tRCD以时钟周期(tCK,Clock Time)数为单位,比如笔者MINI2440所用内存芯片里面写到tRCD为20nst,如果将来内存工作在100MHz,那么RCD至少要为2个时钟周期, RCD=2。
在选定列地址后,就已经确定了具体的存储单元,剩下就是等待数据通过数据 I/O通道(DQ)输出到内存数据总线上了。但是在列地址选通信号CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为 CL(CAS Latency,CAS潜伏期)。由于CL只在读取时出现,所以CL又被称为读取潜伏期(RL,Read Latency)。CL的单位与tRCD一样,也是时钟周期数,具体耗时由时钟频率决定(笔者官方手册CL=3)。不过,CAS并不是在经过CL周期之后才送达存储单元。实际上CAS与RAS一样是瞬间到达的。由于芯片体积的原因,存储单元中的电容容量很小,所以信号要经过放大来保证其有效的识别性,这个放大/驱动工作由S-AMP负责。但它要有一个准备时间才能保证信号的发送强度,这段时间我们称之为tAC(Access Time from CLK,时钟触发后的访问时间)。
四、SDRAM预充电操作
从存储体的结构图上可以看出,原本逻辑状态为1的电容在读取操作后,会因放电而变为逻辑0。由于SDRAM的寻址具有独占性,所以在进行完读写操作后,如果要对同一L-Bank的另一行进行寻址,就要将原先操作行关闭,重新发送行/列地址。在对原先操作行进行关闭时,DRAM为了在关闭当前行时保持数据,要对存储体中原有的信息进行重写,这个充电重写和关闭操作行过程叫做预充电,发送预充电信号时,意味着先执行存储体充电,然后关闭当前L-Bank操作行。预充电中重写的操作与刷新操作(后面详细介绍)一样,只不过预充电不是定期的,而只是在读操作以后执行的。
五、SDRAM突发操作
突发(Burst)是指在同一行中相邻的存储单元连续进行数据传输的方式,连续传输所涉及到存储单元(列)数量就是突发长度(Burst Length,简称BL)。
在目前,由于内存控制器一次读/写P-Bank位宽的数据,也就是8个字节,但是在现实中小于8个字节的数据很少见,所以一般都要经过多个周期进行数据的传输,上文写到的读/写操作,都是一次对一个存储单元进行寻址,如果要连续读/写,还要对当前存储单元的下一单元进行寻址,也就是要不断的发送列地址与读/写命令(行地址不变,所以不用再对地寻址)。虽然由于读/写延迟相同可以让数据传输在I/O端是连续的,但是它占用了大量的内存控制资源,在数据进行连续传输时无法输入新的命令效率很低。为此,引入了突发传输机制,只要指定起始列地址与突发长度,内存就会依次自动对后面相应长度数据的数据存储单元进行读/写操作而不再需要控制器连续地提供列地址,这样,除了第一笔数据的传输需要若干个周期(主要是之间的延迟,一般的是tRCD + CL)外,其后每个数据只需一个周期即可。
总结下:
SDRAM的基本读操作需要控制线和地址线相配合地发出一系列命令来完成。先发出芯片有效命令(ACTIVE),并锁定相应的L-BANK地址(BA0、BA1给出)和行地址(A0~A12给出)。芯片激活命令后必须等待大于tRCD(SDRAM的RAS到CAS的延迟指标)时间后,发出读命令。CL(CAS延迟值)个时钟周期后,读出数据依次出现在数据总线上。在读操作的最后,要向SDRAM发出预充电(PRECHARGE)命令,以关闭已经激活的L-BANK。等待tRP时间(PRECHAREG命令后,相隔tRP时间,才可再次访问该行)后,可以开始下一次的读、写操作。SDRAM的读操作支持突发模式(Burst Mode),突发长度为1、2、4、8可选。
六、SDRAM写操作SDRAM的基本写操作也需要控制线和地址线相配合地发出一系列命令来完成。先发出芯片有效命令,并锁定相应的L-BANK地址(BA0、BA1给出)和行地址(A0~A12给出)。芯片有效命令发出后必须等待大于tRCD的时间后,发出写命令数据,待写入数据依次送到DQ(数据线)上。在最后一个数据写入后,延迟tWR时间。发出预充电命令,关闭已经激活的页。等待tRP时间后,可以展开下一次操作。写操作可以有突发写和非突发写两种。突发长度同读操作。
七,SDRAM的刷新
SDRAM之所以成为DRAM就是因为它要不断进行刷新(Refresh)才能保留住数据,因此它是SDRAM最重要的操作。
刷新操作与预充电中重写的操作一样,都是用S-AMP先读再写。但为什么有预充电操作还要进行刷新呢?因为预充电是对一个或所有 L-Bank中的工作行操作,并且是不定期的,而刷新则是有固定的周期,依次对所有行进行操作,以保留那些很长时间没经历重写的存储体中的数据。但与所有L-Bank预充电不同的是,这里的行是指所有L-Bank中地址相同的行,而预充电中各L-Bank中的工作行地址并不是一定是相同的。那么要隔多长时间重复一次刷新呢?目前公认的标准是,存储体中电容的数据有效保存期上限是64ms(毫秒,1/1000秒),也就是说每一行刷新的循环周期是64ms。这样刷新时间间隔就是:64m/行数s。我们在看内存规格时,经常会看到4096 Refresh Cycles/64ms或8192 Refresh Cycles/64ms的标识,这里的4096与8192就代表这个芯片中每个L-Bank的行数。刷新命令一次对一行有效,刷新间隔也是随总行数而变化,4096行时为 15.625μs(微秒,1/1000毫秒),8192行时就为 7.8125μs。刷新操作分为两种:Auto Refresh,简称AR与Self Refresh,简称SR。不论是何种刷新方式,都不需要外部提供行地址信息,因为这是一个内部的自动操作。对于 AR,SDRAM内部有一个行地址生成器(也称刷新计数器)用来自动的依次生成行地址。由于刷新是针对一行中的所有存储体进行,所以无需列寻址,或者说CAS在 RAS之前有效。所以,AR又称CBR(CAS Before RAS,列提前于行定位)式刷新。由于刷新涉及到所有L-Bank,因此在刷新过程中,所有 L-Bank都停止工作,而每次刷新所占用的时间为9个时钟周期(PC133标准),之后就可进入正常的工作状态,也就是说在这9个时钟期间内,所有工作指令只能等待而无法执行。64ms之后则再次对同一行进行刷新,如此周而复始进行循环刷新。显然,刷新操作肯定会对SDRAM的性能造成影响,但这是没办法的事情,也是DRAM相对于 SRAM(静态内存,无需刷新仍能保留数据)取得成本优势的同时所付出的代价。SR则主要用于休眠模式低功耗状态下的数据保存,这方面最著名的应用就是 STR(Suspend to RAM,休眠挂起于内存)。在发出AR命令时,将CKE置于无效状态,就进入了SR模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在SR期间除了CKE之外的所有外部信号都是无效的(无需外部提供刷新指令),只有重新使CKE有效才能退出自刷新模式并进入正常操作状态。
八、相关寄存器的配置
(1)BWSCON寄存器(BUS WIDTH & WAIT CONTROL REGISTER)
表2-17 SDRAM控制寄存器(BWSCON)
根据开发板的存储器配置和芯片型号,设置每个BANK焊接芯片的位宽和等待状态
BWSCON,每4位对应一个BANK,这4位分别表示:
l STx:启动/禁止SDRAM的数据掩码引脚(UB/LB),SDRAM没有高低位掩码引脚,此位为0,SRAM连接有UB/LB管脚,设置为1
l 注:UB/LB数据掩码引脚用来控制芯片读取/写入的高字节和低字节(对比硬件手册SDRAM和SRAM的接线图)
l WSx:是否使用存储器的WAIT信号,通常设为0
l DWx:设置焊接存储器芯片的位宽,笔者开发板使用两片容量为32M,位宽为16的SDRAM组成64M,32位存储器,因此DW7,DW6位设置为0b10,其它BANK不用设置采用默认值即可。
l BANK0对应的是系统引导BANK,这4位比较特殊,它的设置是由硬件跳线决定的,因此不用设置
l BWSCON设置结果:0x22000000
(2)BANKCON0~BANKCON5 (BANK CONTROL REGISTER)
表2-18 BANKCON0~BANKCON5控制寄存器(BANKCON0~BANKCON5)
这6个寄存器用来设置对应BANK0~BANK5的访问时序,采用默认值0x700即可
(3)BANKCON6~BANKCON7 (BANK CONTROL REGISTER)
表2-19 BANKCON6~BANKCON7控制寄存器(BANKCON6~BANKCON7)
由于内存都焊接在这两个BANK上,因此内存驱动主要是对这两个寄存器进行设置
l MT:设置BANK6~BANK7的存储器类型,
00=ROM or SRAM 01=保留
10=保留 11=SDRAM
内存为SDRAM,设置为0b11,对应的应该设置Trcd和SCAN位,其它位和SDRAM无关
l Trcd:RAS to CAS Delay行地址选通到列地址选通延迟,这个参数请看后面的内存工作原理扩展部分解释,笔者内存芯片为HY57V561620,由其芯片手册可知其Trcd为最少20ns,如果内存工作在100MHz,则该值至少要为2个时钟周期,通常设置为3个时钟周期,因此设置为0b01
l SCAN:SDRAM Column Address Number SDRAM的列地址数,笔者内存芯片为HY57V561620,列地址数为9,设置为0b01
l BANK6,BANK7设置结果为:0x18005
(4)REFRESH (REFRESH CONTROL REGISTER)
表2-20刷新频率设置寄存器(REFRESH)
SDRAM的刷新有效,刷新频率设置寄存器(刷新)
l REFEN:开启/关闭刷新功能,设置为1,开启刷新
l TREFMD:SDRAM刷新模式,0=CBR/AutoRefresh, 1=Self Refresh,设置为0,自动刷新
l Trp:行地址选通预充电时间,一般设置为0b00即可
l Tsrc:单行刷新时间,设置为0b11即可。
l Refresh Counter:内存存储单元刷新数,它通过下面公式计算出:
Refresh Counter = 2^11 + 1 – SDRAM时钟频率(MHz)* SDRAM刷新周期(uS)
SDRAM的刷新周期,也就是内存存储单元间隔需要多久进行一次刷新,前面内存工作原理分析可知电容数据保存上限为64ms,笔者使用内存芯片每个L-Bank共有8192行,因此每次刷新最大间隔为:64ms/8192 = 7.8125uS,如果内存工作在外部晶振频率12MHz下,Refresh Counter = 1955,如果内存工作在100MHz下,那么Refresh Counter = 1269(取大整数)
l REFRESH寄存器设置为:
0x8e0000 + 1269 = 0x008e04f5(HCLK = 100MHz)
0x8e0000 + 1955 = 0x008e07a3(HCLK = 12MHz)
(5)BANKSIZE寄存器(BANKSIZE REGISTER)
表2-21 BANKSIZE寄存器(BANKSIZE)
设置内存的突发传输模式,省电模式和内存容量。
l BURST_EN:是否开启突发模式, 0 = ARM内核禁止突发传输 1 =开启突发传输,设置为1,开启突发传输
l SCKE_EN:是否使用SCKE信号作为省电模式控制信号, 0 =不使用SCKE信号作为省电模式控制信号 1 = 使用SCKE信号作为省电模式控制信号,通常设置为1
l SCLK_EN: 设置向存储器输入工作频率,0 =一直输入SCLK频率,即使没有内存操作也会输入, 1 = 仅当进行内存数据操作时才输入SCLK频率,通常设置为1
l BK76MAP:设置Bank6/7的内存容量,笔者使用开发板内存为两片32M内存芯片并联成64M,它们全部都外接到Bank6上,因此选择0b001
l BANKSIZE寄存器设置为:0xb1
(6)SDRAM模式设置寄存器MRSRx (SDRAM MODE REGISTER SET REGISTER)
表2-22 SDRAM模式设置寄存器(MRSRx)
该寄存器用于设置CAS潜伏周期,可以手动设置的位只有CL[6:4]位,通过前面内存工作原理可知,笔者使用开发板CL=3,即0b011
l MRSR6,MRSR7设置为:0x00000030
九、内存驱动实验
下面是uboot中lowlevel_init.S中的内容,其中lowlevel_init函数实现SDRAM的初始化:
/*
* Memory Setup stuff - taken from blob memsetup.S
*
* Copyright (C) 1999 2000 2001 Erik Mouw (J.A.K.Mouw@its.tudelft.nl) and
* Jan-Derk Bakker (J.D.Bakker@its.tudelft.nl)
*
* Modified for the Samsung SMDK2410 by
* (C) Copyright 2002
* David Mueller, ELSOFT AG, <d.mueller@elsoft.ch>
*
* SPDX-License-Identifier: GPL-2.0+
*/
#include <config.h>
/* some parameters for the board */
/*
*
* Taken from linux/arch/arm/boot/compressed/head-s3c2410.S
*
* Copyright (C) 2002 Samsung Electronics SW.LEE <hitchcar@sec.samsung.com>
*
*/
#define BWSCON 0x48000000
/* BWSCON */
#define DW8 (0x0)
#define DW16 (0x1)
#define DW32 (0x2)
#define WAIT (0x1<<2)
#define UBLB (0x1<<3)
#define B1_BWSCON (DW32)
#define B2_BWSCON (DW16)
#define B3_BWSCON (DW16 + WAIT + UBLB)
#define B4_BWSCON (DW16)
#define B5_BWSCON (DW16)
#define B6_BWSCON (DW32)
#define B7_BWSCON (DW32)
/* BANK0CON */
#define B0_Tacs 0x0 /* 0clk */
#define B0_Tcos 0x0 /* 0clk */
#define B0_Tacc 0x7 /* 14clk */
#define B0_Tcoh 0x0 /* 0clk */
#define B0_Tah 0x0 /* 0clk */
#define B0_Tacp 0x0
#define B0_PMC 0x0 /* normal */
/* BANK1CON */
#define B1_Tacs 0x0 /* 0clk */
#define B1_Tcos 0x0 /* 0clk */
#define B1_Tacc 0x7 /* 14clk */
#define B1_Tcoh 0x0 /* 0clk */
#define B1_Tah 0x0 /* 0clk */
#define B1_Tacp 0x0
#define B1_PMC 0x0
#define B2_Tacs 0x0
#define B2_Tcos 0x0
#define B2_Tacc 0x7
#define B2_Tcoh 0x0
#define B2_Tah 0x0
#define B2_Tacp 0x0
#define B2_PMC 0x0
#define B3_Tacs 0x0 /* 0clk */
#define B3_Tcos 0x3 /* 4clk */
#define B3_Tacc 0x7 /* 14clk */
#define B3_Tcoh 0x1 /* 1clk */
#define B3_Tah 0x0 /* 0clk */
#define B3_Tacp 0x3 /* 6clk */
#define B3_PMC 0x0 /* normal */
#define B4_Tacs 0x0 /* 0clk */
#define B4_Tcos 0x0 /* 0clk */
#define B4_Tacc 0x7 /* 14clk */
#define B4_Tcoh 0x0 /* 0clk */
#define B4_Tah 0x0 /* 0clk */
#define B4_Tacp 0x0
#define B4_PMC 0x0 /* normal */
#define B5_Tacs 0x0 /* 0clk */
#define B5_Tcos 0x0 /* 0clk */
#define B5_Tacc 0x7 /* 14clk */
#define B5_Tcoh 0x0 /* 0clk */
#define B5_Tah 0x0 /* 0clk */
#define B5_Tacp 0x0
#define B5_PMC 0x0 /* normal */
#define B6_MT 0x3 /* SDRAM */
#define B6_Trcd 0x1
#define B6_SCAN 0x1 /* 9bit */
#define B7_MT 0x3 /* SDRAM */
#define B7_Trcd 0x1 /* 3clk */
#define B7_SCAN 0x1 /* 9bit */
/* REFRESH parameter */
#define REFEN 0x1 /* Refresh enable */
#define TREFMD 0x0 /* CBR(CAS before RAS)/Auto refresh */
#define Trp 0x0 /* 2clk */
#define Trc 0x3 /* 7clk */
#define Tchr 0x2 /* 3clk */
#define REFCNT 1113 /* period=15.6us, HCLK=60Mhz, (2048+1-15.6*60) */
/**************************************/
.globl lowlevel_init
lowlevel_init:
/* memory control configuration */
/* make r0 relative the current location so that it */
/* reads SMRDATA out of FLASH rather than memory ! */
ldr r0, =SMRDATA
ldr r1, =CONFIG_SYS_TEXT_BASE
sub r0, r0, r1
ldr r1, =BWSCON /* Bus Width Status Controller */
add r2, r0, #13*4
0:
ldr r3, [r0], #4
str r3, [r1], #4
cmp r2, r0
bne 0b
/* everything is fine now */
mov pc, lr
.ltorg
/* the literal pools origin */
SMRDATA:
.word (0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))
.word ((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))
.word ((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))
.word ((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))
.word ((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))
.word ((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))
.word ((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))
.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))
.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))
.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)
.word 0x32
.word 0x30
.word 0x30
其中
CONFIG_SYS_TEXT_BASE是定义在include/configs/arm2440.h中的,可以根据不同的芯片加载地址进行设置。(0x30000000)
设置完堆栈指针以后,就可以执行c程序了。
总结:
分析到这里,主要的内容都是start.S和lowlevel_init.S 中的东西,也是和平台密切相关的东西,完成这些设置以后,将进入下一个阶段,而且后面的实现都是用c代码来实现的,可以说与汇编相关的东西到这里可以告一段落了。
因为前面分析的内容都是用汇编语言来实现的,所以分析的比较独立,后面的内容将会结合uboot来进行讲解,毕竟uboot大多数功能也是用c代码实现的。
《uboot详解》系列文章将到这里告一段落了,接下来结合uboot-2015.10进行分析如何将uboot移植到一个开发板上,如果感兴趣请查看《移植uboot》系列文章。