[学习资料] Tiny210(S5PV210) u-boot移植

Tiny210(S5PV210) u-boot移植
http://www.microoh.com/bbs/forum.php?mod=viewthread&tid=254&fromuid=6205
(出处: 麦可网论坛)

请大家关注原作者南山一梦

一直想开一个帖子,针对课程的Stage4系统移植阶段,把一些在视频课程中没有讲透彻的地方,和大家一起讨论交流一下,今天开一个关于u-boot移植的帖子,分享一些我u-boot移植过程中的笔记和学习心得,全当是抛砖引玉,也希望大家能指出其中的错误,对于视频中没有讲全讲透的地方,大家一起交流,共同进步。废话就不多说了,还是先从启动原理,一点一点往下说吧。

Tiny210 U-BOOT(一)--启动过程

讲解启动过程,首先的源头就是打开电源,这个相信没人人不知道。CPU上电后,此时SP指针指向0x0000_0000,从这个地址取第一条指令。但此时:PLL没有启动,CPU工作频率为外部输入晶振频率,非常低(S5PV210中晶振在CPU旁边,两颗24MHz,一颗27MHz);CPU的工作模式、中断设置等不确定;存储空间的各个BANK(包括内存)都没有驱动,内存(tiny210用的DDR2)不能使用。在这种情况下必须在第一条指令处做一些初始化工作,这段初始化程序与操作系统独立分开,称之为Bootloader。下面以S5PV210用的U-BOOT为例:

友善之臂Tiny210 SDK2用的是三星公司的S5PV210,在开始U-BOOT的移植之前,有必要弄清楚S5PV210的启动过程:
<ignore_js_op>


由图上所知,由OM(Operating Mode)pin导入到iROM中,OM是什么?打开Tiny210的原理图,搜索OM
<ignore_js_op>


查找到一个表格,有上表可知,OM1/OM2/OM3决定的是从SD卡启动还是从Nand Flash启动。
<ignore_js_op>


继续往下看,由图可知,OM1/OM2/OM3分别接在XOM1/XOM2/XOM3上面。
<ignore_js_op>


再来看旁边XOM的一个信号反相器74LVC1G04的图,你怎么知道74LVC1G04是信号反相器呢?我也不知道,我打开了74LVC1G04的芯片手册看了才知道。
<ignore_js_op>


打开74LVC1G04的芯片手册,找到pin脚图和逻辑图
<ignore_js_op>

<ignore_js_op>


<ignore_js_op>


由图可知,这是一个反相器,2中输入高电平,则4中输出低电平;2中输入低电平,则4中输出高电平,OK。
当XOM拨到1时,XOM1为NC状态,XOM1 = 0,pin4的输出为1,XOM2=XOM3=1,则为0-1-1,为SDBOOT;
当XOM拨到2时,XOM1为输入状态,XOM1 = 1,pin4的输出为0,XOM2 = XOM3 = 0,为1-0-0,Nand启动模式,至此,OM的输入判断分析结束。

继续看芯片手册,手册上关于iROM/iRAM及Bootloader描述如下:

  • The iROM code is placed in internal 64KB ROM. It initializes basic system functions such as clock, stack, and heap.
  • The iROM loads the first boot loader image from a specific booting device to internal 96KB SRAM. The booting device is selected by Operating Mode (OM) pins. According to the secure boot key values, the iROM code may do an integrity check on the first boot loader image.
  • The first boot loader loads the second boot loader then may check the integrity of the second boot loader according the secure boot key values.
  • The second boot loader initializes system clock, UART, and DRAM controller. After initializing DRAM controller, it loads OS image from the booting device to DRAM. According to the secure boot key values, the second boot loader can do an integrity check on the OS image.
  • After the booting completes, the second boot loader jumps to the operating system.


按照芯片手册上的启动分析图及上面的描述,总结启动流程为:
S5PV210上电将iROM(interal ROM)处执行固化的启动代码,它对系统时钟进行初始化,对启动设备进行判断,并从启动设备中复制BL1(最大16KB)到iRAM(0xd002_0000处,其中0xd002_0010之前的16个字节存储的的有BL1的校验信息和BL1尺寸)中,并对BL1进行校验,检验OK转入BL1进行执行;BL1执行完成后,开始执行BL2,BL2加载内核,把OS在SDRAM中运行起来。
BL0,BL1,BL2:
(1)BL0:是指S5PV210的iROM中固化的启动代码
作用:初始化系统时钟,设置看门狗,初始化堆和栈,加载BL1
(2)BL1:是批在iRAM自动从外扩存储器(nand/sd/usb)中拷贝的uboot.bin二进制文件的头最大16K代码
作用:初始化RAM,关闭Cache,设置栈,加载BL2
(3)BL2:是指在代码重定向后在内存中执行的uboot的完整代码
作用:初始化其它外设,加载OS内核
(4)三者之间的关系:(Interal ROM固化代码)BL0将BL1(bootloader的前16KB--BL1)加载到iRAM;BL1然后在iRAM中运行将BL2(剩下的bootloader)加载到SDRAM;BL2加载内核,把OS在SDRAM中运行起来,最终OS是运行在SDRAM(内存)中的。

在这个过程中,u-boot其实严格来说有三段,因为第一段是在iROM中,由三星公司给我们写好了,所以,通常,将片内的bootloader称为BL0,而片外Bootloader,也就是在NAND FLASH中的bootloader,我们称为BL1,BL2。
我们片外的u-boot是被分为了两个阶段:
1.第一阶段代码
arch/arm/cpu/armv7/start.S
1).设置异常向量地址
2).设置SVC32模式(ARM七种工作模式)
3).cpu_init_crit
清TLB(页面缓存)、关MMU及Cache等
4).转入低级初始化lowlevel_init
主要是对时钟、片外内存(DDR2)、串口、nand(这里初始化nand主要是为第二阶段搬内核到内存而准备的)等进行初始化
5).判断启动开关进行自搬移
6).跳转到C入口board_init_f( ) 

2.第二阶段代码
1).board_init_f( )
A.gd_t数据结构空间分配
B.回调一组初始化函数
C.对gd_t数据结构进行初始化
D.relocate_code(U-boot重定义代码,即自搬移)
2).board_init_r( )
A.使能Cache
B.板子初始化
C.串口初始化
D.外存初始化
E.环境变量初始化
F.控制台初始哗
G.中断使能
H.以太网初始化
I. 进入main_loop(),等待命令或自动加载内核

先看硬件,CPU时钟、NAND FLASH、DDR、串口、网卡,至少这些硬件,是我们需要在u-boo的启动过程中完成初始化的,其余的细节问题,暂且不说,至少,前面的这些硬件,是u-boot在启动过程,做自搬移,及最终引导内核前必须要用到的,OK,为了理解u-boot源码中那些硬件操作汇编代码,我们先看看这些硬件的工作原理是如何的。

Tiny210 U-BOOT(二)--配置时钟频率基本原理
OK,接着说,这次先讲讲CPU的系统时钟。U-BOOT在启动的过程中,需要配置系统时钟,没有这东西,CPU就跑不起来。
配置系统时钟,大致是以下几个步骤:

(1)设置系统PLL锁定时间
(2)配置PLL
(3)配置各模块分频系数
(4)切换到PLL时钟

以后大家经常要翻的就是S5PV210的芯片手册了,以后说Pxxx页,指的就是S5PV210的芯片手册,这里我使用的是S5PV210_UM_REV1.1的版本,以后的页数都以这个为准。OK,翻吧,骚年

1.基本原理
首先输出一个高电平,然后,通过三个晶振,输出一个频率,然后,通过倍频器(锁相环),将频率升高,然后,再通过分频,把分出来的不同的频率,提供给不同的器件,比如ARM Cotrex内核、各种设备控制器等等。

Tiny210(S5PV210)上蓝色的框标注的就是晶振的位置,最上面的是27MHz,中间和下面两颗是24MHz,顺便讲一下另外几片东西,最左边的四片是内存DDR2-800,这表示数据传输频率为800MHz,外部时钟频率200MHz,内部时钟频率为100MHz;下面的黄色框是NAND FLASH,外部时钟频率133MHz。我怎么知道的?大家看了这一章以后,自然就知道怎么查了,别急,耐心往下看

<ignore_js_op>

倍频的原理:

下图就是上电后的XXTI输出的频率变化图(XXTI引脚见P361的系统时钟流程图最左边的XXTI引脚,XXTI的详细介绍见P354的Figure 3-2 S5PV210 Top-Level Clocks),频率从小变到指定频率需要一段时间(图中标红框的部分),当CPU频率在变化的时候,比如由复位后的初始的400HZ,我要升到1000HZ, 这时,首先把CPU的频率锁定,因这此时CPU的频率是变化的,频率变化,CPU的状态就无法确定,所以,此时用PLL--phase-locked loop锁相环,将CPU频率锁定一段时间,直到我的频率输出稳定为止。芯片手册上显示APLL默认的设置时间为30us(30毫秒)。

<ignore_js_op>

锁定频率后,此时,应该设置一个倍频因子,在ARM手册中去查表10.2,P,M,S, 设置对应的位的值,然后,将频率提升,比如从晶振输出的24HZ,抬升到1000Hz(S5PV210的CPU旁边有三个晶振,两个24Hz, 一个27Hz)。

<ignore_js_op>

分频的原理:
设置不同的位,比如,设置某一位为0,那么,分频时,原来频率比如为1000HZ,那么频率就被分为1000/1=1000Hz, 这样就可以分给ARMCLK使用。

2.开始分析

下图取自芯片手册section 02_system的第3章 CLOCK CONTROLLER,在开篇的P353页的第一张图就是这个。<ignore_js_op>


S5PV210的Clock分为三个domain,意思是三个区域--MSYS,DSYS,PSYS,这三个区域分别都是AMBA总线,AMBA总线分为AHB和APB两种总线(这是不严格的分法,仅仅是便于理解),每种总线都有不同的时钟频率,AHB--HCLK/APB--PCLK
那么MSYS,DSYS,PSYS最少有6个时钟(实际上不止6个),分别为HCLK_MSYS/PCLK_MSYS、HCLK_DSYS/PCLK_DSYS、HCLK_PSYS/PCLK_PSYS,再外加一个CPU要用的时钟ARMCLK,总共7个时钟频率。MSYS,DSYS,PSYS分别管理不同的设备,为不同的设备提供不同的频率。
<ignore_js_op>


再往下翻,这些不同设备的频率是如何产生的呢?由图可知最后,总共有13个CLK提供出来。
<ignore_js_op>


由上图可知,通过XOM[0]产生频率,然后在APLL升频,然后在分频1时,有一个两级分频,然后,在分频2又有一个8级分频,最后,就可以输出一个频率,提供给S5PV210的ARM芯片使用,P356页查到常用的CLK值,这里我们的S5PV210的ARMCLK为1000MHz。
那么,在硬件上是如何实现的呢?
观察:
开发板上晶振有三颗,两个24Hz,一个27Hz。(晶振,全名晶体振荡器,成份石英(二氧化硅),晶振用于通过压电效应给CPU提出振荡频率,再通过别的电路,将例如正弦曲线波转换成方波,相当于CPU的起搏器,有了晶振,CPU才有频率输出)。
CPU输出比如24Hz频率后,此时,就需要把频率放大了(否则CPU才24Hz的频率能干吗呢?所以,CPU并不是一上电就跑到几GHz上面去了,跑车再好,跑的再快,从0加速到100,就是兰博基尼的Aventador他不也得3秒钟吗,所以没有一上电就几G这回事儿
),在频率放大的过程中,首先需要考虑的一个问题就是----phase-locked loop锁相环。简单的说,比如复位后,CPU默认工作频率在400Hz,现在需要升到1000Hz工作,那么从400-->1000Hz需要一个过程,假设为时间t1,在t1这段时间内,CPU的频率是变化的,那么CPU的状态就是不稳定的,此时,就需要把频率锁定,设置锁定时间,直到CPU稳定的输出频率。
CPU第一次启动时,PLL有一个默认的初始值,芯片手册找到P522页,找到默认的初始频率:
• APLL: M=200, P=6, S=1 FOUT = (MDIV X FIN )/ (PDIV X 2(SDIV-1))) = 800MHz 
• MPLL: M=667, P=12, S=1 FOUT = (MDIV X FIN) / (PDIV X 2SDIV) = 667MHz 
• EPLL: M=80, P=3, S=3, K=0 FOUT = ((MDIV+KDIV) X FIN) / (PDIV X 2SDIV) = 80MHz 
<ignore_js_op>


由上图查出可知,ARMCLK的默认频率为400MHz
1)查看芯片手册的P356页,查出总共有以下几种由CMU输出的时钟
有四种PLLs(APLL,MPLL,EPLL,HPLL),还包括USB_OTG PHY clock。
To generate internal clocks, the following components are used.
• APLL uses FINPLL (refer to Figure 3-1) as input to generate 30MHz ~ 1GHz.
• MPLL uses FINPLL as input to generate 50MHz ~ 2GHz.
• EPLL uses FINPLL as input to generate 10MHz ~ 600MHz.
• VPLL uses FINPLL or SCLK_HDMI27M as input to generate 10MHz ~ 600MHz. This PLL generates 54MHz video clock.
• USB OTG PHY uses XUSBXTI to generate 30MHz and 48MHz
• HDMI PHY uses XUSBXTI or XHDMIXTI to generate 54MHz
常用的APLL/MPLL/EPLL/VPLL是干什么的呢?P448页
• APLL: used to generate ARM clock
• MPLL: used to generate system bus clock and several special clocks
• EPLL: used to generate several special clocks
• VPLL: used to generate Video clocks. Usually, generates 54 MHz.

2)先查看APLL PMS的倍频表
P357 S5PV210_UM_REV1.1.pdf -- Table 3-1. APLL PMS Value 
<ignore_js_op>

查看芯片手册,设置P/M/S的bit,可以将频率拉升到我们想要的频率。
那么我们要设置升高频,翻看手册,P371页,找到PLL CONTROL REGISTERS。
• (APLL_LOCK, R/W, Address = 0xE010_0000)
• (MPLL_LOCK, R/W, Address = 0xE010_0008)
• (EPLL_LOCK, R/W, Address = 0xE010_0010)
• (VPLL_LOCK, R/W, Address = 0xE010_0020) 


由芯片手册上显示APLL的lock time是30us,如果是晶振输出的频率是24MHZ,lock time是30毫秒,那么PLL_LOCKTIME是720,也就是0x2D0。OK,下一篇开始结合u-boot的源码来分析如何配置系统时钟

Tiny210 U-BOOT(三)----配置时钟频率源码分析
上篇讲了配置时钟的原理,今天就结合源码具体分析一下。这里要小吐槽一下三星的S5PV210的芯片手册,之前的S5PC100的芯片手册,是按各个模块来列表的,Clock Control、Interrupt Controller、NFCON(Nandf Flash Controller),一个一个列出来,目录非常清楚,结果到了S5PV210的芯片手册上,出现了一个脑残的section分法,将各具模块全部按section划分成12大section,然后,你就自己去每个section里找去吧,如此划分真是让人搞不懂三星是什么意思,可能三星自己也觉是加入的section有些脑残,在后面的S5PV310的芯片手册中,又去掉了,恢复了原来的样子。OK,回到正题。
在u-boot的源码中,系统时钟的初始化是放在板文件的lowlevel_init.S文件中的system_clock_init函数中。我们的tiny210是拷贝的smdkc100,所以,大家可以先参看smdkc100的lowlevel_init.S文件中的system_clock_init函数。
对于芯片手册中每一个模块的学习,在了解前面的基本原理后,关键的寄存器的操作,可以先浏览一下这个模块的所有寄存器的简介,对各个寄存器的作用做到心中有数,这样,可以大体知道,需要配置哪些寄存器,不至于被下面一大堆的寄存器给弄懵了,系统时钟的寄存器简介在P367页--3.7 REGISTER DESCRIPTION 。
1.设置APLL/MPLL/EPLL/EPLL锁相环时间
翻看手册,P371页,找到PLL CONTROL REGISTERS。
• (APLL_LOCK, R/W, Address = 0xE010_0000)
• (MPLL_LOCK, R/W, Address = 0xE010_0008)
• (EPLL_LOCK, R/W, Address = 0xE010_0010)
• (VPLL_LOCK, R/W, Address = 0xE010_0020) 

<ignore_js_op>


这里出现了一个问题,APLL/MPLL/EPLL/VPLL的锁相环的时间是不一样的,而像S5PC100,频率为667MHz,他的A/M/E/HPLL的锁相环时间均是300us,而在Exynos 4xxx中,时间又变成了250,这个时间一定要查芯片手册。那么,所以,这里我们要用4个寄存器来存不同的LOCK_OFFSET的值。查询ATPCS文档(Procedure Call Standard for the ARM v2.08),在第P15页找到,r4-r8是用来存储变量的寄存器,OK,我们选取4个寄存器来保存值。
APLL的锁时间为:24*30=720    --0x2D0
MPLL的锁时间为:24*200=4800  --0x12C0
EPLL的锁时间为:24*375=9000  --0x2328
VPLL的锁时间为:24*100=2400  --0x960
将上面四个值存入寄存器r4--r8,这里为了表示代码的专业,是资深老菜鸟Codeing,所以使用寄存器的别名v1--v4,然后再将寄存值的分别存入APLL_LOCK/MPLL_LOCK/EPLL_LOCK/VPLL_LOCK对应的寄存器地址中

  1. ldr    r0, =0xE0100000                /*ELFIN_CLOCK_POWER_BASE*/
  2. ldr    v1, =0x2D0
  3. ldr    v2, =0x12C0
  4. ldr    v3, =0x2328
  5. ldr    v4, =0x960
  6. str    v1, [r0, #0x00]                /*APLL_LOCK_OFFSET*/
  7. str    v2, [r0, #0x08]                /*MPLL_LOCK_OFFSET*/
  8. str    v3, [r0, #0x10]                /*EPLL_LOCK_OFFSET*/
  9. str    v4, [r0, #0x20]                /*VPLL_LOCK_OFFSET*/
复制代码

2.开始倍频
2.1倍频APLL
我们只要把P=3 M=125,S=1即可让输出频率为1000MHz,即MSOUT_MSYS=1000MHz,那么配置哪个寄存器可以倍频呢?继续向下看手册,在P372页找到APLL_CON0(0xE010_0100)。

<ignore_js_op>


上面这个表显示出如何配置P/M/S位,那么如下代码,我们可以配置输出为1000MHz。

  1. ldr    r1, =(1<<31 |125<<16 |3<<8 |1<<0)  @APLL_VAL=(1<<31 | 445<<16 | 0x4<<8 | 1<<0)
  2. /*APLL_CON0_OFFSET 0x100*/
  3. str    r1, [r0, #APLL_CON_OFFSET]      @24  1000  APLL  3  125  1  2000.0  FOUTAPLL = 1000.0MHz
复制代码

2.2倍频MPLL
MPLL的P/M/S位配置为P=12 M=667,S=1,按上面的配置为MPLL_CON寄存器,Address=0xE010_0108


<ignore_js_op>


  1. ldr    r1, =(1<<31 |667<<16 |12<<8 |1<<0)  @MPLL_VAL=(1<<31 |667<<16 |12<<8 |1<<0)
  2. /*MPLL_CON0_OFFSET 0x108*/
  3. str    r1, [r0, #MPLL_CON_OFFSET]      @24  667  MPLL  12  667  1  667.0  FOUTAPLL = 667.0MHz
复制代码

2.3倍频EPLL
EPLL的P/M/S位配置为P=3 M=48,S=2,按上面的配置配置EPLL_CON寄存器,Address=0xE010_0110
<ignore_js_op>

  1. ldr    r1, =(1<<31 |108<<16 |6<<8 |3<<0)  @VPLL_VAL=(1<<31 |108<<16 |6<<8 |3<<0)
  2. /*VPLL_CON0_OFFSET 0x120*/
  3. str    r1, [r0, #VPLL_CON_OFFSET]      @24  54  VPLL  6  108  3  54.0  FOUTAPLL = 54.0MHz
复制代码

OK,倍频完成,代码整理一下:

  1. ldr    r1, =(1<<31 |125<<16 |3<<8 |1<<0)  @APLL_VAL=(1<<31 | 445<<16 | 0x4<<8 | 1<<0)
  2. /*APLL_CON0_OFFSET 0x100*/
  3. str    r1, [r0, #APLL_CON_OFFSET]      @24  1000  APLL  3  125  1  2000.0  FOUTAPLL = 1000.0MHz 
  4. ldr    r1, =(1<<31 |667<<16 |12<<8 |1<<0)  @MPLL_VAL=(1<<31 |667<<16 |12<<8 |1<<0)
  5. /*MPLL_CON0_OFFSET 0x108*/
  6. str    r1, [r0, #MPLL_CON_OFFSET]      @24  667  MPLL  12  667  1  667.0  FOUTAPLL = 667.0MHz 
  7. ldr    r1, =(1<<31 |48<<16 |3<<8 |2<<0)  @EPLL_VAL=(1<<31 |48<<16 |3<<8 |2<<0)
  8. /*EPLL_CON0_OFFSET 0x110*/
  9. str    r1, [r0, #EPLL_CON_OFFSET]      @24  96  EPLL  3  48  2  96.0  FOUTAPLL = 96.0MHz  
  10. ldr    r1, =(1<<31 |108<<16 |6<<8 |3<<0)  @VPLL_VAL=(1<<31 |108<<16 |6<<8 |3<<0)
  11. /*VPLL_CON0_OFFSET 0x120*/
  12. str    r1, [r0, #VPLL_CON_OFFSET]      @24  54  VPLL  6  108  3  54.0  FOUTAPLL = 54.0MHz
复制代码

参考芯片手册P361页时钟生成电路图,系统的MSYS/DSYS/PSYS的CLK全部来自于APLL和MPLL,现在开始对APLL/MPLL进行分频,分给不同的设备,如Cotrex A8内核或其它设备。在分频之前,必须要先选择时钟源,否则怎么分频呢?

3.选择时钟源<ignore_js_op>


参考芯片手册P361页时钟生成电路图,以APLL以例,查找FOUTAPLL和FINPLL
<ignore_js_op>


由图所知,FINPLL是没有倍频的频率,我们要选的是倍频后的,所以应该选择FOUTAPLL,依此类推,后面几位选上FOUTMPLL/FOUTEPLL/FOUTVPLL。那第[28]位,ONENADN选择什么呢?往上查一下ONENAND用的是什么CLK,在P363页查找到用的是PSYS,所以这里应该选择0。在Tiny210中是没有接ONENADN的吧,我们用的是NAND FLASH,因此,这一位应该可以不配置。
<ignore_js_op>

  1. //CLK_SRC0_OFFSET            0x200
  2. ldr    r1, [r0, #CLK_SRC0_OFFSET]
  3. ldr    r2, =(0<<28|1<<12 |1<<8 |1<<4 |1<<0)
  4. orr    r1, r1, r2
  5. str    r1, [r0, #CLK_SRC0_OFFSET]
复制代码

OK,现在开始对APLL/MPLL进行分频了。

4.分频
继续向下查找芯片手册,找到P387页,系统时钟分频操作的寄存器是CLK_DIV0,Address = 0xE010_0300
<ignore_js_op>



回过头去,再去看时钟频率的产生图,最终MSYS有五个CLK,ARMCLK/HCLK_MSYS/PCLK_MSYS/HCLK_IMEM/SCLK_DMC0。P356查看常用CLK
Values for the high-performance operation:
•  freq(ARMCLK)   = 1000 MHz
•  freq(HCLK_MSYS)   = 200 MHz
•  freq(HCLK_IMEM)   = 100 MHz
•  freq(PCLK_MSYS)   = 100 MHz
•  freq(HCLK_DSYS)   = 166 MHz
•  freq(PCLK_DSYS)   = 83 MHz
•  freq(HCLK_PSYS)   = 133 MHz
•  freq(PCLK_PSYS)   = 66 MHz
•  freq(SCLK_ONENAND)    = 133 MHz, 166
在寄存器CLK_DIV0的表格上还有这样一段话:There are operating frequency limitations. The maximum operating frequency of SCLKAPLL, SCLKMPLL, SCLKA2M, HCLK_MSYS, and PCLK_MSYS are 1GHz, 667 MHz, 400 MHz, 200 MHz, and 100 MHz, respectively. These operating clock conditions must be met through CLK_DIVX configuration.
查找SCLKAPLL,找到SCLKAPLL的来源

<ignore_js_op>


OK,将上面列表中CLK也整理一下,列表出来:
•  freq(SCLKAPLL)   = 1000 MHz
•  freq(SCLKMPLL)   = 667 MHz
•  freq(SCLKA2M)   = 400 MHz 

接着分析图,在[19:16]要用到的MSOUT_DSYS,他的频率来自MPLL,所以MSOUT_DSYS =MPLL = 667

<ignore_js_op>


下面一位一位的开始分析:
[2:0]ARMCLK = 1000MHz,MOUT_MSYS = 2000MHz,ARMCLK = MOUT_MSYS/(0+1),APLL_RATIO为0,0<<0
[6:4]SCLKA2M = 400MHz,SCLKAPLL = 1000MHz,由于无法整除,而SCLKA2M最大为400,我们只要保证不超过就行,可以低一些,SCLKA2M = SCLKAPLL / (2 + 1)  = 333,A2M_RATIO为2,2<<4
[10:8]HCLK_MSYS = 200MHz,ARMCLK = 1000MHz,HCLK_MSYS = ARMCLK /(4 + 1),HCLK_MSYS_RATIO为4,4<<8
[14:12]PCLK_MSYS = 100MHz,HCLK_MSYS = 200MHz,PCLK_MSYS = HCLK_MSYS / (1 + 1) ,PCLK_MSYS_RATIO为1,1<<12  
[19:16]HCLK_DSYS = 166MHz,MOUT_DSYS = 667MHz,HCLK_DSYS = MOUT_DSYS / (3+ 1) ,HCLK_DSYS_RATIO为1,3<<16
[22:20]PCLK_DSYS = 83MHz,HCLK_DSYS = 166MHz,PCLK_DSYS = HCLK_DSYS / (1 + 1) ,PCLK_DSYS_RATIO为1,1<<20 
[27:24]HCLK_PSYS = 133MHz,MOUT_PSYS = 667MHz,HCLK_PSYS = MOUT_PSYS / (4 + 1) ,HCLK_PSYS_RATIO为1,4<<24 
[30:28]PCLK_PSYS = 66MHz,HCLK_PSYS = 133MHz,PCLK_PSYS = HCLK_PSYS / (1 + 1) ,PCLK_PSYS_RATIO为1,1<<28 

OK,至此,分频完成,整理代码如下: 

  1. /*将r0的值偏移300,此时地址为CLK_DIV0 = 0xE010_0300),保存到r1中*/
  2. ldr r1, [r0, #0x300]            /*CLK_DIV0_OFFSET*/
  3. /*将CLK_DIV0中有关分频的位全部清零0~30*/
  4. ldr    r2, =0x7fff                /*CLK_DIV0_MASK*/
  5. bic    r1, r1, r2
  6. /*分频给系统时钟*/
  7. ldr    r2, =(0<<0)|(2<<4)|(4<<8)|(1<<12)|(3<<16)|(1<<20)|(4<<24)|(1<<28)       
  8. orr    r1, r1, r2
  9. str    r1, [r0, #0x300]  //CLK_DIV0_OFFSET
复制代码

5.延时一段时间

  1.     /*delay*/
  2.     mov    r1, #0x10000
  3. 1:  subs   r1, r1, #1
  4.     bne    1b
复制代码

6.返回指针

  1. mov    pc, lr
复制代码

OK,到此,系统时钟频率的全部设置工作完成。最后将代码重新归整如下:

  1. system_clock_init:
  2.     ldr    r0, =ELFIN_CLOCK_POWER_BASE    @0xe0100000
  3.         ldr    v1, =0x2D0
  4.         ldr    v2, =0x12C0
  5.         ldr    v3, =0x2328
  6.         ldr    v4, =0x960
  7.         str    v1, [r0, #0x00]                /*APLL_LOCK_OFFSET*/
  8.         str    v2, [r0, #0x08]                /*MPLL_LOCK_OFFSET*/
  9.         str    v3, [r0, #0x10]                /*EPLL_LOCK_OFFSET*/
  10.         str    v4, [r0, #0x20]                /*VPLL_LOCK_OFFSET*/
  11. /**************************************************************************
  12.   APLL_CON0_OFFSET 0x100   @24  1000  APLL  3  125  1  2000.0  FOUTAPLL = 1000.0MHz
  13.   MPLL_CON0_OFFSET 0x108   @24  667  MPLL  12  667  1  667.0  FOUTAPLL = 667.0MHz
  14.   EPLL_CON0_OFFSET 0x110   @24   54  VPLL  6  108  3  54.0  FOUTAPLL = 54.0MHz 
  15. ***************************************************************************/
  16.     ldr    r1, =(1<<31 |125<<16 |3<<8 |1<<0)  @APLL_VAL=(1<<31 | 445<<16 | 0x4<<8 | 1<<0)
  17.     str    r1, [r0, #APLL_CON0_OFFSET]      
  18.     ldr    r1, =(1<<31 |667<<16 |12<<8 |1<<0)  @MPLL_VAL=(1<<31 |667<<16 |12<<8 |1<<0)
  19.     str    r1, [r0, #MPLL_CON_OFFSET]     
  20.     ldr    r1, =(1<<31 |48<<16 |3<<8 |2<<0)  @EPLL_VAL=(1<<31 |48<<16 |3<<8 |2<<0)
  21.     str    r1, [r0, #EPLL_CON_OFFSET]     
  22.     ldr    r1, =(1<<31 |108<<16 |6<<8 |3<<0)  @VPLL_VAL=(1<<31 |108<<16 |6<<8 |3<<0)
  23.     str    r1, [r0, #VPLL_CON_OFFSET]     
  24.     //CLK_SRC0_OFFSET            0x200
  25.     ldr    r1, [r0, #CLK_SRC0_OFFSET]
  26.     ldr    r2, =(0<<28|1<<12 |1<<8 |1<<4 |1<<0)
  27.     orr    r1, r1, r2
  28.     str    r1, [r0, #CLK_SRC0_OFFSET]
  29.     /*将r0的值偏移300,此时地址为CLK_DIV0 = 0xE010_0300,保存到r1
  30.      * 将CLK_DIV0中有关分频的位全部清零0~30
  31.      */
  32.     ldr r1, [r0, #0x300]            /*CLK_DIV0_OFFSET*/
  33.     ldr    r2, =0x7fff                /*CLK_DIV0_MASK*/
  34.     bic    r1, r1, r2
  35.     /*分频给系统时钟*/
  36.     ldr    r2, =(0<<0)|(2<<4)|(4<<8)|(1<<12)|(3<<16)|(1<<20)|(4<<24)|(1<<28)      
  37.     orr    r1, r1, r2
  38.     str    r1, [r0, #0x300]  //CLK_DIV0_OFFSET
  39.     mov    r1, #0x10000
  40. 1:    subs    r1, r1, #1
  41.     bne    1b
  42.     mov    pc, lr
复制代码

OK,至此,u-boot中最简单的系统时钟配置完成,上面代码中各个位的配置全部直接用数字,是为了方便新学的朋友看代码对比各个bit的值,实际中,应该尽量用宏进行替换,后面,该是那几个让人头疼的NAND FLASH、DDR这些东西了,头疼,确实让人头疼

补充一点,在P363页,有系统各个时钟的分配图,一般各个模块进来的时钟,你想要查询是多少Hz,都可以在这里查的到<ignore_js_op>


比如,像P825页WDT的原理图中,进来的PCLK是多少,就可以在上面的表中查出来,PCLK = 66MHz。
<ignore_js_op>


上面的图中,VIC表示中断控制器,CFCON表示COMPACT FLASH CONTROLLER,CF卡控制器;NFCON表示NAND FLASH CONTROLLER,NAND FLASH控制器;TSADC表示TP的时钟频率,其余的PWM,WDT,RTC大家都知道,我就不用说了,这个表,在裸板开发,熟悉各个模块的原理是很有用的,至于在实际的Linux设备驱动开发调试中,好像用处不大,不过,知道原理,对自己总归没坏处。

Tiny210 U-BOOT(四)----Nand Flash原理
1.查看芯片型号
我在上次讲系统时钟的时候,提过一下,在S5PV210下面的那一片就是Nand Flash,旁边的那4片是DDR,我的型号是K9F2G08UOB
<ignore_js_op>


查找三星芯片的命名手册,这个网上有,PDF名称叫----三星_Nand_Flash_芯片型号命名规则.pdf,打开,查找K9F2G08UOB所代表的具体参数如下:
芯片类型:Nand Flash
Small Classification:SLC Normal 
容量:2G/8bit
Technology:Normal
Organization:x8
Vcc:2.7V~3.6V
Mode:Normal
Generation:3rd Generation
整理一下信息:Nand Flash , 容量256M(2G/8bit) , 是用SLC存储(Single Level Cell单层存储) , 总线为8bit , 工作电压为2.7V~3.6V , 第3批次。

2.存储原理
打开K9F2G08UOB芯片手册的P6页
<ignore_js_op>


由芯片手册上可知,这款NAND FLASH,页大小2K,块大小128K,意思就是读写操作,每次最小为2K,擦除操作每次最小为128K。什么原理呢,继续向下看。
<ignore_js_op>


由上图可知:1page = 2K, 1 block = 128K, 1Block=64Page, 芯片有128K
现在开始计算:
1 Block = 64*2 = 128K
那么我们FLASH的容量就2K * 128K = 256M (图中每个页还有Page都有保留区,所以实际上我们用不到256M),这里就有一个问题,为啥要设计成这样?
因为Nand Flash是用于设计成嵌入式系统的内存,程序进行内存的寻址时,为了提搞速度,所以,将线性的内存在逻辑上进行分块,分页,形成逻辑上的立体结构,这样,寻址就会方便,先找到是哪一块,再找到是哪一页,最后找到是这一页的哪个位置,这要比起从头到尾遍历,就要快很多了。

到这里,存储原理基本弄清楚了,Nand Flash的擦除操作是以block块为单位的,与此相对应的是其他很多存储设备,是以bit位为最小读取/写入的单位,Flash是一次性地擦除整个块:在发送一个擦除命令后,一次性地将一个block,常见的块的大小是128KB/256KB,全部擦除为1,也就是里面的内容全部都是0xFF了。这里就有了一个新问题,为什么擦除是写1,而不是写0呢?这和我们正常的逻辑怎么是个反的
所谓1和0的介定,在存储器中,是通过向存储单元中施加电压来控制向里面是充电荷还是释放电荷。所以,本质上来说,数据0和1的表示,就是存储单元上的电压否超过一个特定的电压值,通常叫阈值,超过了这个阈值,就是1,没超过,就是0。所以说,全部擦除为1,也就意味着对整个block进行一次全部的充电。那这里,我们又有新问题了,那为什么不把擦除做成统一放电呢
关于这个,我也没找到资料,书上也没有查到相关的,如果有人知道,麻烦也一并指点下我。我个人的理解是放电是时时刻刻存在的,放电比充电容易。就像我们的这些存储芯片,你充了一次电以后,是不是就一直为1了?不可能,能量是会衰减的,所以,你充一次电以后,隔了一段时间,不管你愿意不愿意,电荷必然会慢慢释放掉,那么,如果你要保证你的数据的正解性,仍然为1,不会被认为是0,你还得再充一次电,这个时间大概是多少,目前公认的标准是,存储体中电容的数据有效保存期上限是64ms(毫秒,1/1000 秒),也就是说每一行刷新的循环周期是 64ms。在书没有查到,至于是不是,我也不清楚,希望高手能考证一下,所以,反正是要隔一段时间还得再充电一下。那么,现在我们再来理解一下,SRAM和DRAM的区别,百度百科里查一下SRAM的介绍----SRAM不需要刷新电路即能保存它内部存储的数据。而DRAM(Dynamic Random Access Memory)每隔一段时间,要刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能。但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积,且功耗较大
到这里,大家是不是有点明白了,为什么SRAM不需要刷新电路,你不需要刷新电路,那你怎么解决电荷释放的问题?这世上没有天上掉馅饼的事儿,别人不给你,那就自己带一个呗,所以SRAM是自己带有刷新电路的,所以,它体积大,所以它容量做不上去。所以,SRAM和DRAM的区别,就非常好理解了,一个带充电电路,一个不带,而DRAM的充电是由内存控制器隔一段时间去充电的。
所以,到这里,我理解的Nand Flash为什么擦除是擦为1,就是因为放电比充电容易,你控制每个存储单元去放电,肯定比控制每一个存储单元去充电要容易的多,硬件实现也要简单的多吧。这里再次声明一下,这属于自己的推断,没有经过论证,如果有高手,在哪本书上有介绍,希望能指点或论证一下。

3.原理图和引脚分析 
<ignore_js_op>


芯片封装图,也就是实际的Pin脚位置图,N.C表示未连接,48pin,TSOP1封装,软件工程师,可以主要和原理图对比一下,看实际各个pin脚的位置,不过,其实,不看也没关系,看原理图吧

<ignore_js_op>


看Nand Flash的芯片手册对各个pin脚的描述(K9F2G08U0B.pdf--P5)
通过观察CE2#和R/B2#处于NC状态,也就是未连接状态,所以,这两个芯脚去掉,还剩下8个I/O和其余7条线
I/O0 ~ I/O7:  
<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/IO.jpg
I/O pin用于输入command,addresss和data,以及在读操作时输出数据。当芯片没有被选中或无法正常输出数据时,处于高阻态(high-z即高阻态,相当于隔断状态,当处于高阻抗状态时,其输出相当于断开状态,没有任何逻辑控制功能)
CLE(COMMAND LATCH ENABLE): 指示当前数据为控制命令
<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/CLE.jpg
CLE输入控制路径发送到命令寄存器。
ALE(ADDRESS LATCH ENABLE): 指示当前数据为地址
<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/ALE.jpg
CE#(CHIP ENABLE):芯片使能,俗称片选信号,表示flash chip是否被系统选中为当前操作芯片,低电平表示选中

<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/CE.png
RE#(READ ENABLE): 读使能,RE是串行数据输入,低电平时读取data到I/O总线,RE下降沿时读取数据有效,同时internal column address counter(内部列地址计数器)加1
<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/RE.png
WE#(WRITE ENABLE): 写使能,低电平时向I/O中写入Command,address和data,在上升沿时操作完成。
<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/WE.png
WP#(WRITE PROTECT): 写保护,在电源置换过程中处于有效的低电平状态,保护flash里面的数据不受改写。当WP低电平有效时,高电平产生复位
file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/WP.png
<ignore_js_op>


R/B#(READY/BUSY OUTPUT): 读/忙状态输出,表示芯片是处于Read还是Busy状态,低电平表示Busy,返回高电平时表示处理完成。当芯片未被选中或无法输出数据时不会处于high-z状态,仍可用于检测芯片的闲忙状态
<ignore_js_op>


file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/69b37b2a147e1c0e57a1a77cdf168333.png
Vcc/Vss/N.C(POWER/GROUND/NO CONNECTION):Vcc是供电电压,Vss接地,N.C没定义
file:///C:/Users/Xuer/AppData/Local/Temp/Wiz/dce2f929-663b-41db-8bdf-3f4615c43d4c_128_files/VCC.jpg
<ignore_js_op>






FAQ: 为什么要有CLE和ALE?
比如命令锁存使能(Command Latch Enable,CLE)地址锁存使能(Address Latch EnableALE),那是因为,Nand Flash8I/O,而且是复用的,也就是,可以传数据,也可以传地址,也可以传命令,为了区分你当前传入的到底是啥,所以,先要用发一个CLE(或ALE)命令,告诉nand Flash的控制器一声,我下面要传的是命令(或地址),这样,里面才能根据传入的内容,进行对应的动作。否则,nand flash内部,怎么知道你传入的是数据,还是地址,还是命令啊,也就无法实现正确的操作了。那么,为什么不多几个I/O呢?

FAQ:Nand Flash为什么
只设计8个I/O引脚,有什么好外?
1) 减少外围引脚:相对于并口(Parellel)的Nor Flash的48或52个引脚来说,的确是大大减小了引脚数目,这样封装后的芯片体积,就小很多。现在芯片在向体积更小,功能更强,功耗更低发展,减小芯片体积,就是很大的优势。同时,减少芯片接口,也意味着使用此芯片的相关的外围电路会更简化,避免了繁琐的硬件连线。

2) 提高系统的可扩展性,因为没有像其他设备一样用物理大小对应的完全数目的addr引脚,在芯片内部换了芯片的大小等的改动,对于用全部的地址addr的引脚,那么就会引起这些引脚数目的增加,比如容量扩大一倍,地址空间/寻址空间扩大一倍,所以,地址线数目/addr引脚数目,就要多加一个,而对于统一用8I/O的引脚的Nand Flash,由于对外提供的都是统一的8个引脚,内部的芯片大小的变化或者其他的变化,对于外部使用者(比如编写nand flash驱动的人)来说,不需要关心,只是保证新的芯片,还是遵循同样的接口,同样的时序,同样的命令,就可以了。这样就提高了系统的扩展性。

4.Nand原理分析
Nand Flash是一个典型的串口通信的芯片,我们知道,总线的三大元素地址、命令、数据。先看一个NOR FLASH的原理图:
<ignore_js_op>


上图中,地址线,数据线,命令线,三者清清楚楚,再来看我们的NAND FLASH的原理图,只有8根Data线,大部分线都是NC,未连接状态。
<ignore_js_op>

1)Nand Flash原理图上只有8根线Data0-Data7,是典型的串口通信,因为只有8根线,所以地址线、数据线肯定是复用的
2)Nand Flash K9F2G08UOB容量为256M * 8bit,它的地址应该有28位,原理图上只有Data0-Data7,所以需要发出多次地址信号,下面是地址时序图,A0-A28,一共要发送五次,每次发8bit。
<ignore_js_op>


3)Nand Flash不能像内存一样直接读写,要先发命令,再发地址,再读写数据
<ignore_js_op>


很容易看出,我们要读取数据,要用到Read命令,该命令需要2个周期,第一个周期发0x00,第二个周期发0x30。在这个过程,你发了一个命令,对方能否立刻变接收到,并做出反应呢,肯定不行,所以,这得需要一个维持时间,这样才能保证你的数据的有效性。
<ignore_js_op>


4)CLE为高电平时,Data0-Data7发的是命令;
   ALE为高电平时,Data0-Data7发的是地址;
   CLE/ALE都为低电平时,Data0-Data7发的是数据,nWE,表示写,nRE,表示读。

下面以一个实际的读操作,来讲一下Nand Flash的时序:
<ignore_js_op>


在开始解释前,多罗嗦一下”使能”这个词,使能(Enable),是指使其(某个信号)有效,使其生效的意思......比如,上面图中的CLE线号,是高电平有效,如果此时将其设为高电平,我们就叫做,将CLE使能,也就是使其生效的意思。使能,这个中文翻译有点怪怪的,有点像WDT(Watch Dog Timer),中文翻译叫看门狗,这是英文直译,另一种解释叫做监视定时器,也许后一种解释会更好一点儿吧。

按照时序来划分,总共下面七个信号共同协调工作,我们先来竖着看,看同一时刻的操作:<ignore_js_op>


我们来一起看看,我在图6中的特意标注的①边上的黄色竖线。
黄色竖线所处的时刻,是在发送读操作的第一个周期的命令0x00之前的那一刻。
让我们看看,在那一刻,其所穿过好几行都对应什么值,以及进一步理解,为何要那个值。
(1)黄色竖线穿过的第一行,是CLE。还记得前面介绍命令所存使能(CLE)那个引脚吧?CLE,将CLE置1,就说明你将要通过I/O复用端口发送进入Nand Flash的,是命令,而不是地址或者其他类型的数据。只有这样将CLE置1,使其有效,才能去通知了内部硬件逻辑,你接下来将收到的是命令,内部硬件逻辑,才会将受到的命令,放到命令寄存器中,才能实现后面正确的操作,否则,不去将CLE置1使其有效,硬件会无所适从,不知道你传入的到底是数据还是命令了。
(2)而第二行,是nCE,那一刻的值是0。这个道理很简单,你既然要向Nand Flash发命令,那么先要选中它,所以,要保证nCE为低电平,使其有效,也就是片选有效。
(3)第三行是nWE,意思是写使能。因为接下来是往nand Flash里面写命令,所以,要使得nWE有效,所以设为低电平。
(4)第四行,是ALE是低电平,而ALE是高电平有效,此时意思就是使其无效。而对应地,前面介绍的,使CLE有效,因为将要数据的是命令,而不是地址。如果在其他某些场合,比如接下来的要输入地址的时候,就要使其有效,而使CLE无效了。
(5)第五行,nRE,此时是高电平,无效。可以看到,知道后面低6阶段,才变成低电平,才有效,因为那时候,要发生读取命令,去读取数据。
(6)第六行,就是我们重点要介绍的,复用的输入输出I/O端口了,此刻,还没有输入数据,接下来,在不同的阶段,会输入或输出不同的数据/地址。
(7)第七行,R/nB,高电平,表示R(Ready)/就绪,因为到了后面的第5阶段,硬件内部,在第四阶段,接受了外界的读取命令后,把该页的数据一点点送到页寄存器中,这段时间,属于系统在忙着干活,属于忙的阶段,所以,R/nB才变成低,表示Busy忙的状态的。
读操作,查P7页的Command Set,1st Cycle发00,2nd Cycle发30。

<ignore_js_op>


<ignore_js_op>

 
 
<ignore_js_op>
 

上面介绍了时刻(1)的各个信号的值,以及为何是这个值之后,相信,后面的各个时刻,对应的不同信号的各个值,下面我们来分析一下其余时刻都干了什么。

上面图中标出来的,1-6个阶段,具体是什么含义。
(1) 操作准备阶段:此处是读(Read)操作,所以,先发一个图5中读命令的第一个阶段的0x00,表示,让硬件先准备一下,接下来的操作是读。
(2) 发送两个周期的列地址。也就是页内地址,表示,我要从一个页的什么位置开始读取数据。
(3) 接下来再传入三个行地址。对应的也就是页号。
(4) 然后再发一个读操作的第二个周期的命令0x30。接下来,就是硬件内部自己的事情了。
(5) Nand Flash内部硬件逻辑,负责去按照你的要求,根据传入的地址,找到哪个块中的哪个页,然后把整个这一页的数据,都一点点搬运到页缓存中去。而在此期间,你所能做的事,也就只需要去读取状态寄存器,看看对应的位的值,也就是R/nB那一位,是1还是0,0的话,就表示,系统是busy,仍在”忙“(着读取数据),如果是1,就说系统活干完了,忙清了,已经把整个页的数据都搬运到页缓存里去了,你可以接下来读取你要的数据了。
对于这里。估计有人会问了,这一个页一共2048+64字节,如果我传入的页内地址,就像上面给的1028一类的值,只是想读取1028到2011这部分数据,而不是页开始的0地址整个页的数据,那么内部硬件却读取整个页的数据出来,岂不是很浪费吗?答案是,的确很浪费,效率看起来不高,但是实际就是这么做的,而且本身读取整个页的数据,相对时间并不长,而且读出来之后,内部数据指针会定位到你刚才所制定的1208的那个位置。
(6) 接下来,就是你“窃取“系统忙了半天之后的劳动成果的时候了,呵呵。通过先去Nand Flash的控制器中的数据寄存器中写入你要读取多少个字节(byte)/字(word),然后就可以去Nand Flash的控制器的FIFO中,一点点读取你要的数据了。
至此,整个Nand Flash的读操作就完成了。
对于其他操作,可以根据我上面的分析,一点点自己去看datasheet,根据里面的时序图去分析具体的操作过程,然后对照代码,会更加清楚具体是如何实现的。

5.地址传送
Nand Flash有一个"位反转"的特性,就是在读一页的时候,有一位或某几位,可能出现原来是1,读完了以后就是0,Linux系统中,一般叫做OOB(Out Of Band),这个区域,基于Nand Flash的硬件特性:数据在读写时候相对容易错误,所以为了保证数据的正确性,必须要有对应的检测和纠错机制,此机制被叫做EDC(Error Detection Code)/ECC(Error Code Correction,或者Error Checking and Correcting),所以设计了多余的区域,用于放置数据的校验值。这也就是每一页2K Byte旁边都有64 Byte的一个区域,这个64Byte就是OOB区域。

比如访问地址8000
8000/2048 = 3.9,所以地址在第3页
8000-3*2048 = 1856,所以在第3页的第1856个地址
所以,1,2Cycle发的就是Ox740(1856),第1st Cycle发的就是0x40,2nd Cycle发的就是0x7
而3,4,5Cycle发的是3,第3rd Cycle发的是0x3,第4th Cycle发的是0x0,第5th Cycle发的是0x0。OK,至此,Nand Flash的原理分析完毕,将常用的存储原理,还有引脚功能,时序功能,和地址传送简单的讲了一些,这些对于后面分析u-boot源码中的Nand Flash的初始化代码和搬内核的代码很有用,理解了Nandf Flash的基本原理,大家再冲到u-boot内核中去对Nand Flash的代码来庖丁解牛吧!

扯些和技术无关的东西,个人觉得,学习内核,不管是u-boot这种小型的,还是Linux和Android这种超巨型的内核,有点类似于赵子龙长板桥上七进七出,只不过,刚开始的时候,我们每次冲进去找到一个点,然后,被打的灰头土脸,然后,爬出来,看视频,看书,查资料,补充能量,然后,补充的差不多了,再冲进去,然后,可能再被打败,那再回来补充,然后,再冲去,这时可能这个点,就被你打倒了。然后,可能再被遇到一个点,刚刚神气不了一会儿,又被打的灰头土脸,我们又得出去再补充能量,然后,再冲进来,当经历的失败和挫折多了,有一天,虽然我们可能达不到冲入内核中像赵子龙长板桥上七进七出,如入无人之境的地步,但,这时的你,应该比一般的人要深入或走的远的多了,信念这个东西,你要说虚,确实很虚,根本摸不着,多少钱一斤?不过,如果你愿意,你可以把它装在自己的内心里在,每天都可以感受的到,非常的真实。不因挫折和失败而放弃,不害怕改变和挑战,在Linux底层的学习路上,与大家共勉之,一起进步。

Tiny210 U-BOOT(五)----Nand Flash源码分析
1.u-boot参考源码 
Nand Flash的初始化代码在board/samsung/tiny210/lowlevel_init.S 

2.初始化Nand Flash
在u-boot中,Nand的低级初始化在lowlevel_init.S中的nand_asm_init函数中。打开原理图,配置各个功能引脚----状态引脚R/nB, 读使能引用脚nRE, 片选信号nCE, 命令使能引脚CLE ,地址使能引脚ALE, 写使能引脚nWE。

<ignore_js_op>


<ignore_js_op>


搜索各个引脚的值,比如R/nB接在Xm0FRnB0上,搜索Xm0FRnB0,发现Xm0FRnB0和ONDXL_INT0/MP03_4共用一个引脚,上图中将各个信号线的复用引脚全部在左边用红色标注出来。
2.1配置片选信号引脚
查找到nCE接在复用引脚Xm0CSn2/NFCSn0/MP01_2上,打开S5PV210芯片手册,P185页查找到MP0_1 Control Registe,MP0_1CON[2]的配置如下:

<ignore_js_op>


所以,只需将这一位,配置成011,才能识别成Nand Flash。

  1.         /*ELFIN_GPIO_BASE 0xE0200000*/
  2.     ldr    r0, =ELFIN_GPIO_BASE
  3.      
  4.     /*Nand Flash nCE pin*/
  5.     ldr    r1, [r0, #MP01CON_OFFSET]  @0x2E0
  6.     bic    r1, r1, #(0xf<<8)
  7.     orr    r1, r1, #(0x3<<8)
  8.     str    r1, [r0, #MP01CON_OFFSET] @0x2E0
复制代码

将Port Group MP0_1 Control Register 的4-5位全部清为0

<ignore_js_op>

  1.         /*MP01PUD_OFFSET 0x2E8*/
  2.     ldr    r1, [r0, #MP01PUD_OFFSET]
  3.     bic    r1, r1, #(0x3<<4)
  4.     str    r1, [r0, #MP01PUD_OFFSET]
复制代码

我们的nCE片选信号是低电平有效,在电路图中,像我们的三星的原理图中,nXX表示低电平有效,而在TI的芯片手册中,XXn表示低电平有效,比如,上面的芯片手册中nCE表示低电平有效,而在TI的芯片手册中,则是CEn,表示的也是低电平片选信号有效,反正看到带n的引脚,即表示低电平有效。

2.2配置CLE/ALE/nWE/nRE/R/nB引脚
除开nCE引脚,其余的几个引脚都是复用的MP03,OK,一起配置,查找MP03,P189,Port Group MP0_3 Control Register,配置如下:
Port Group MP0_3 Control Register (MP0_3CON, R/W, Address = 0xE020_0320)

<ignore_js_op>


<ignore_js_op>


先将0-23位全部清零,然后全部置为0010,也就是全部置为2

  1.        /*MP03CON_OFFSET 0x320*/
  2.     ldr    r1, [r0, #MP03CON_OFFSET]
  3.     bic    r1, r1, #0xFFFFFF
  4.     ldr    r2, =0x22222222
  5.     orr    r1, r1, r2
  6.     str    r1, [r0, #MP03CON_OFFSET]
复制代码

将Port Group MP0_3 Control Register的0-23位全部清为0
Port Group MP0_3 Control Register (MP0_3PUD, R/W, Address = 0xE020_0328)
<ignore_js_op>

  1.      /*MP03PUD_OFFSET 0x328*/
  2.     ldr    r1, [r0, #MP03PUD_OFFSET]
  3.     ldr    r2, =0x3fff
  4.     bic    r1, r1, r2
  5.     str    r1, [r0, #MP03PUD_OFFSET]
复制代码

2.3 配置Nandf Flash控制器的寄存器NFCONF
Nand Flash Configuration Register (NFCONF, R/W, Address = 0xB0E0_0000) 
先看前四位[3:0]
<ignore_js_op>


[0]位保留,置为0。
[1]用来配置页的一个地址周期,这款K9F2G08UOB的page是2K,那么地址周期是多少,再去看K9F2G08UOB的芯片手册,看到第P6页

<ignore_js_op>


显示地址周期为5th,所以[1]配为1
先看[3],这里是配置是SLC还是MCL,根据芯片名称,这款K9F2G08UOB是SLC存储,所以[3]配为0
再来看[2],K9F2G08UOB是SLC,PageSize是2K,所以[2]配为0
整理,前四位配置为--0010。OK,继续配置。

<ignore_js_op>


这里多出了两个不认识的东西TWRPH1和TWRPH0。查看一下芯片手册上的NAND FLASH MEMORY TIMING, P693-P694

<ignore_js_op>


TACLS:当将CLE和ALE拉高以后,再过多少时间才能发出写使能信号(nWE)
TWRPH0:nWE使能持续时间,信号被拉低的时间
TWRPH1:当将CLE和ALE拉高以后,WE使能,这时,后面开始发数据,这就是数据起作用的时间(Data hold time)

TACLS是发给NAND FLASH的,所以去查看K9F2G08UOB的芯片手册,打开P17页,并没有显示出来CLE拉高后,经过多少时间nWE,将WE的信号拉低,根据下面的时序图,显示:
<ignore_js_op>


打开K9F2G08UOB的芯片手册,在P10页查找到各个pin脚在各个状态下的最小时间

<ignore_js_op>


OK,至此,三个最小时间查出来了,串口通讯中最麻烦的就是时序,OK,整理数据
TACLS = tCLS-tWP = 12 -12 =0
TWRPH0 = tWP = 12
TWRPH1 = tDH = 5
OK,现在再返回去看NFCONF寄存器里这几个bit的一个计算公式:

这里S5PV210的NandFlash的HCLK频率为133MHz(P363页),NFCON属于HCLKD0 = 133MHz,T = 1/133 = 7.5ns
TACLS:配置0,算出时间为7.5*(0+1)= 7.5ns >0ns
TWRPH0:配置1,算出时间为7.5*(1+1)= 15ns >12ns
TWRPH1:配置0,算出时间为7.5*(0+1)= 7.5ns >5ns
通过实际的测试,如果配置成最小值,Nand Flash会出现无法读写的情况,这里我们还是参考提供的Nand Flash的裸机的参考代码,将这三位配成1--4--1
TACLS:配置0,算出时间为7.5*(1+1)= 15ns >0ns
TWRPH0:配置1,算出时间为7.5*(4+1)= 15ns >37.5ns
TWRPH1:配置0,算出时间为7.5*(1+1)= 15ns >5ns

OK,符合我们的最小时序的要求,所以[7:4]1,[11:8]4,[12:15]1。

  1.        /*ELFIN_NAND_BASE    0xB0E00000*/
  2.     ldr    r0, =ELFIN_NAND_BASE
  3.     ldr    r1, [r0, #NFCONF_OFFSET]
  4.     ldr    r2, =0x777F
  5.     bic    r1, r1, r2
  6.     /* NFCONF_VAL    (1<<12)|(4<<8)|(1<<4)|(0<<3)|(0<<2)|(1<<1)|(0<<0) */
  7.     ldr    r2, =NFCONF_VAL
  8.     orr    r1, r1, r2
  9.     str    r1, [r0, #NFCONF_OFFSET]
复制代码

ECC我们先不开,其余的全部配置为0,OK,到此,全部位配置完成。

2.4配置CONTROL REGISTER 
(NFCONT, R/W, ADDRESS = 0XE720_0004)
<ignore_js_op>


第[0]位,让NAND Flash控制器Enable,这里设置为1。使能意思就是生效的意思,为什么会翻译成使能,这个估计是和WDT翻译成看门狗的道理一样,直译吧。
剩下的几位,我没有设置,Nand Flash也没有出现问题,不过,为了谨慎期间,还是用三星的参考代码,全部设置上。OK,代码整理一下

  1. ldr    r1, [r0, #NFCONT_OFFSET]
  2. ldr    r2, =0x707C7
  3. bic    r1, r1, r2
  4. /*NFCONT_VAL  (0x1<<23)|(0x1<<22)|(0<<18)|(0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(0<<7)|(0<<6)|(0x2<<1)|(1<<0)*/
  5. ldr    r2, =NFCONT_VAL
  6. orr    r1, r1, r2
  7. str    r1, [r0, #NFCONT_OFFSET]
复制代码

OK,至此Nand Flash的初始化完成,代码整理如下:

  1. /*
  2. * Nand Interface Init for SMDKC110
  3. */
  4. nand_asm_init:
  5. /* Setting GPIO for NAND */
  6. /* This setting is NAND initialze code at booting time in iROM. */
  7. ldr        r0, =ELFIN_GPIO_BASE
  8. ldr        r1, [r0, #MP01CON_OFFSET]
  9. bic        r1, r1, #(0xf<<8)
  10. orr        r1, r1, #(0x3<<8)
  11. str        r1, [r0, #MP01CON_OFFSET]
  12. /*MP01PUD_OFFSET 0x2E8*/
  13. ldr        r1, [r0, #MP01PUD_OFFSET]
  14. bic        r1, r1, #(0x3<<4)
  15. str        r1, [r0, #MP01PUD_OFFSET]
  16. /*MP03CON_OFFSET 0x320*/ 
  17. ldr        r1, [r0, #MP03CON_OFFSET]
  18. bic        r1, r1, #0xFFFFFF
  19. ldr        r2, =0x22222222
  20. orr        r1, r1, r2
  21. str        r1, [r0, #MP03CON_OFFSET]
  22. /*MP03PUD_OFFSET 0x328*/
  23. ldr        r1, [r0, #MP03PUD_OFFSET]
  24. ldr        r2, =0x3fff
  25. bic        r1, r1, r2
  26. str        r1, [r0, #MP03PUD_OFFSET]
  27. /*ELFIN_NAND_BASE        0xB0E00000*/
  28. ldr        r0, =ELFIN_NAND_BASE
  29. ldr        r1, [r0, #NFCONF_OFFSET]
  30. ldr        r2, =0x777F
  31. bic        r1, r1, r2
  32. /* NFCONF_VAL        (0<<12)|(1<<8)|(0<<4)|(0<<3)|(0<<2)|(1<<1)|(0<<0) */
  33. ldr        r2, =NFCONF_VAL
  34. orr        r1, r1, r2
  35. str        r1, [r0, #NFCONF_OFFSET]
  36. ldr        r1, [r0, #NFCONT_OFFSET]
  37. ldr        r2, =0x707C7
  38. bic        r1, r1, r2
  39. /*NFCONT_VAL (0x1<<23)|(0x1<<22)|(0<<18)|(0<<17)|(0<<16)|(0<<10)|(0<<9)|(0<<8)|(0<<7)|(0<<6)|(0x2<<1)|(1<<0)*/
  40. ldr        r2, =NFCONT_VAL
  41. orr        r1, r1, r2
  42. str        r1, [r0, #NFCONT_OFFSET]
  43. mov        pc, lr
复制代码

OK,下一步,开始去分析Nand Flash的代码搬移那一块的代码,看Nand Flash是如何工作的。

南山一梦 发表于 2013-7-29 12:52
4.分频
继续向下查找芯片手册,找到P387页,系统时钟分频操作的寄存器是CLK_DIV0,Address = 0xE010_0300


  写的不错,学习了,关于CMU(时钟管理单元部分),(A/M/E/V)PLL_LOCK的地址有些笔误。
              ldr    r0, =ELFIN_CLOCK_POWER_BASE    @0xe0100000
        ldr    v1, =0x2D0
        ldr    v2, =0x12C0
        ldr    v3, =0x2328
        ldr    v4, =0x960

        str    v1, [r0, #0x00]                /*APLL_LOCK_OFFSET*/
        str    v2, [r0, #0x04]                /*MPLL_LOCK_OFFSET*/中间有保留位,实际应该是:str    v2, [r0, #0x08]
        str    v3, [r0, #0x08]                /*EPLL_LOCK_OFFSET*/中间有保留位,实际应该是:str    v2, [r0, #0x10]
        str    v4, [r0, #0x0c]                /*VPLL_LOCK_OFFSET*/中间有保留位,实际应该是:str    v2, [r0, #0x20]

   还有,通过学习发现,ARMCLK、HCLK_MSYS、PCLK_MSYS、HCLK_IMEM分频参数,其实是有规律的:
    1、先得出MOUT_MSYS频率,ARMCLK=MOUT_MSYS / n(1-8取值范围),参考CLK_DIV0[2:0]计算公式,该值很容易算出
    2、计算出ARMCLK后,HCLK_MSYS=ARMCLK / n(1-8取值范围),参考CLK_DIV0[10:8]计算公式,该值很容易算出
    3、计算出HCLK_MSYS后,PCLK_MSYS=HCLK_MSYS / n(1-8取值范围),参考CLK_DIV0[14:12]计算公式,该值很容易算出
    4、计算出HCLK_MSYS后,HCLK_IMEM=HCLK_MSYS / 2,参考CLK_DIV0[6:4]计算公式,该值很容易算出

    有一点困或的地方,就是MOUT_MSYS频率是怎么设置出来的?
     我的理解是:MOUT_MSYS分频==》ARMCLK分频==》HCLK_MSYS分频==》PCLK_MSYS、HCLK_IMEM

      不知道是否理解有误,主席看看哈!

 本帖最后由 南山一梦 于 2013-8-12 01:12 编辑

北山 发表于 2013-8-11 10:41
写的不错,学习了,关于CMU(时钟管理单元部分),(A/M/E/V)PLL_LOCK的地址有些笔误。
              l ...


谢谢北山的指出,确实写错了,已更改,谢谢!
另外关于MOUT_MSYS频率是怎么设置出来的?看下面的图
<ignore_js_op>


MOUT_MSYS的频率是怎么出来的,MUX的这个梯形的符号表示的是多路复用器,MOUT_MSYS表示你选的值就是1,可以向下查找到MUX_DSYS多路复用器,向左找到,可以发现,这个频率来自于多路复用器MUX_MPLL的输出,而MUX_MPLL,如果选0,选的是FIN_PLL的频率输入,选1,选的是FOUT_MPLL,MPLL倍频后的频率输入。<ignore_js_op>


而另外ARMCLK、HCLK_MSYS、PCLK_MSYSHCLK_IMEM这几个频率,可以看图:
ARMCLK::MUX_MSYS---->DIV_APLL,MUX_MSYS的频率经过DIV_APLL预分频后就直接生成
HCLK_MSYS: :MUX_MSYS---->DIV_APLL---->DIV_HCLKM,经过两次分频
PCLK_MSYS:MUX_MSYS---->DIV_APLL---->DIV_HCLKM---->DIV_PCLKM,经过三次分频
HCLK_IMEM:MUX_MSYS---->DIV_APLL---->DIV_HCLKM---->DIV_IMEM,经过三次分频
ARMCLK、HCLK_MSYS、PCLK_MSYSHCLK_IMEM这四个频率都是最终的输出频率,不存在再分频的关系。

接着上面的讲,上次讲完了Nand Flash的低级初始化,然后Nand Flash的操作主要是在board_init_f_nand(),中,涉及到将代码从Nand Flash中copy到DDR中,这个放到后面实际移植的过程中再结合源码流程来分析,正常来说,DDR应该是放在Nand Flash前面开始讲,因为DDR相对于Nand Flash来说,更加复杂一些,所以,将DDR拖后来讲,一来准备更多的资料研究,二来也是希望能把这个讲明白,自己如果都没搞懂,讲给别人听只能是贻笑大方了,OK,接着开始讲DDR。

Tiny210 U-BOOT(六)----DDR内存配置
1.S5PV210内存芯片简介
最左边的四片就是内存芯片,是DDR2-800,这表示数据传输频率为800MHz,外部时钟频率200MHz,内部时钟频率为100MHz;因为内部一次传输的数据就可供外部接口传输4次,虽然以DDR方式传输,但数据传输频率的基准——外部时钟频率仍要是内部时钟的两倍才行。<ignore_js_op>


我的板子上显示芯片型号为K4T1G084QF,打开三星的DDR2芯片命名规则文档(这个文档上网去搜索):
1~3. 比较简单,表示是DDR2内存;
4~5. Density,表示容量为1Gbit = 1Gbit/8 = 128MByte
6~7. Bit Organization,表示数据有多少bit,这里是08,则表示数据为8bit,四片拼起来,数据线就是32位。 
<ignore_js_op>


8. # of Internal Banks,表示多少Bank,这里是4,表示为8个Banks
9. Interface, VDD, VDDQ,这里解释一下这两个电压的意思
VDD:北桥芯片供电电压或者存储芯片的输入缓冲和核心逻辑的供电电压。
VDDQ:存储芯片的输出缓冲供电电压。
Q : SSTL_18, 1.8V, 1.8V,根据芯片手册得出这两个电压为1.8V
10. Generation
F : F-die,这是什么意思???
芯片上型号K4T1G084QF,再往上看,会看到有四个BCF7的字母,接着看代表什么意思
12. Package
B : FBGA (Lead-Free & Halogen-Free, Flip Chip),表示这FBGA封装,FBGA是Fine-Pitch Ball Grid Array(意译为“细间距球栅阵列”)的缩写
13. Temp, Power
C : Commercial Temp.(0°C ~ 85°C), Normal Power,表示工作的温度范围和供电
14~15. Speed (Wafer/Chip Biz/BGD: 00)
F7 : DDR2-800 (400MHz@CL=6, tRCD=6, tRP=6),表示为DDR2-800的内存
分析完成,开发板上共有4片这样的内存芯片,总容量就是128M*4=512M

2.分析硬件原理图
2.1 从芯片角度
地址线:
A0-A13 14根
BA0,BA1,BA2
3根BA线表示这个芯片被分成了2^3=8个Bank,每个Bank则为128M/8=16M
<ignore_js_op>


这里出现一个问题,为什么128M里面还要划分出8个Bank?
前面在Nand Flash的时候,我曾经分析过,由于DDR是不自备充电电路的,所以,每隔一段时间,内存控制器就会刷新一次电路,也就是要充一次电,如果只有一个Bank,那么结果就是在某一时刻,要么都充电,要么都不充电。
[size=14.399999618530273px]<ignore_js_op>


像上面这样分成了8个Bank,当我对000充电的时候,我还可以在010或是剩下的别的Bank中读取数据,这样就减小了等待的时间,不用说当电路刷新时,不能读取数据了。
数据线:DQ0-DQ7*4 = 8bit * 4chips = 32bit

[size=14.399999618530273px]
控制线:nCS,nRAS,nCAS
nCS:片选信号,当这个芯片被片选后,这个芯片就使能了,如果这芯片地址线上有地址,那么基本上数据线上就要出数据了。
地址线是A0-A13,是14根线,2^14=16K (128M = 2^27),所以地址线上地址要发两次,一次发行地址,一次发列地址,这就是行列地址复用--nRAS,nCAS这两根线的作用。
14 + 14 = 28根 + BA0/BA1/BA2 = 2^31=2G,最大支持2G的内存
128M = 2^27,27 - 3 = 24,行地址14根,所以,发过来的列地址是24-14=10
所以说,这个操作顺序是,先片选,CS拉低,然后,当RAS拉低时,表示传过的是行地址,是A0-A13,14位;当CAS拉低时,表示传过来的是列地址,是A0-A9,那列地址多的几位怎么办呢?很简单,用来接大内存的吗,谁说一片内存芯片就只能128M了?

2.2 从处理器角度
地址线 Xm1ADDR0-Xm1ADDR13
Xm1BA0, Xm1BA1, Xm1CSn1/BA2
数据线 Xm1DATA0-Xm1DATA31
控制线 Xm1CSn0, Xm1RASn, Xm1CASn  
对于地址线前面的Xm1,可以参看核心板的原理图中内存的总图,整个内存分为三块,Memory Port0/Memory Port1/Memory Port2,Memory Port1/Memory Port2分别对应于芯片手册P29页中Memory Map中的DRAM0/DRAM1,可以分别接两组不同的DDR内存。
<ignore_js_op>


而Memory Port0接的是SRAM,他的地址线是0~15,没有行地址,列地址,没有复用,所以他的容量就是2^16=64K
[size=14.399999618530273px]<ignore_js_op>



2.3DDR芯片手册
前面在查看芯片的型号命名规则时最后两位,我们的板子上是F7
14~15. Speed (Wafer/Chip Biz/BGD: 00)
F7 : DDR2-800 (400MHz@CL=6, tRCD=6, tRP=6),表示为DDR2-800的内存

打开K4T1G084QF.pdf的DDR芯片手册,P4的Key Features,由此我们得知,我们要看的是DDR2-800 6-6-6
<ignore_js_op>


这里又多出和几个我们不认识的东西----CL,tRCD,tRP,看字面意思,大概来猜应该是一些时间,从芯片手册的命名规则上特意用一个字母代号来标识这三个东西,这肯定是非常重要的影响内存性能的某些时间参数。这里,我们有必要把DDR的工作原理给大家梳理一下,这样,才能理解这些参数的意义,OK,请听下回分解。

Tiny210 U-BOOT(七)----SDRAM工作时序与原理

什么不是DDR吗,怎么又变成SDRAM,DDR出身自SDRAM,严格的说应该叫DDR SDRAM,DDR SDRAM是Double Data Rate SDRAM的缩写,是双倍速率同步动态随机存储器的意思,所以,有很大一部分,两者是一样的,后面我会介绍两者的不同,接着看吧,先上一张SDRAM的结构图。
<ignore_js_op>


下面上一张我画的简易的SDRAM工作流程图
<ignore_js_op>

图中用红色标明的就是我们需要找的几个主要时间,现在开始看图说话。
1.芯片初始化
SDRAM 芯片内部有一个逻辑控制单元,并且有一个模式寄存器为其提供控制参数。因此,每次开机时 SDRAM 都要先对这个控制逻辑核心进行初始化。

2.行有效 
初始化完成后,要想对一个 L-Bank 中的阵列进行寻址,首先就要确定行(Row),使之处于活动状态(Active),然后再确定列。简单点理解就先传行地址过来。

3.列读写
行地址确定之后,就要对列地址进行寻址了。读写的信号和列地址是同时发过来的,读写的操作取决于WE#引脚,当他使能则为写,否则为读。
在发送列读写命令时必须要与行有效命令有一个间隔,这个间隔被定义为 tRCD,即RAS to CAS Delay(RAS 至 CAS 延迟),大家也可以理解为行选通周期,简单点理解就是说,在发完行地址后,再发列地址和读写信号时,需要延迟一下,这应该是根据芯片存储阵列电子元件响应时间(从一种状态到另一种状态变化的过程)所制定的延迟。
广义的 tRCD 以时钟周期(tCK,Clock Time)数为单位,比如 tRCD=2,就代表延迟周期为两个时钟周期,具体到确切的时间,则要根据时钟频率而定,对于PC100 SDRAM,tRCD=2,代表1000/100 * 2 = 20ns 的延迟,下图是tRCD=3的时序图。
<ignore_js_op>


4.数据输出(读)
在选定列地址后,就已经确定了具体的存储单元,剩下的事情就是数据通过数据 I/O 通道(DQ)输出到内存总线上了。
但是在CAS发出之后,仍要经过一定的时间才能有数据输出,从CAS与读取命令发出到第一笔数据输出的这段时间,被定义为 CL(CAS Latency,CAS 潜伏期)。由于CL只在读取时出现,所以 CL 又被称为读取潜伏期(RL,Read Latency),下图是CL=2的示意图。
<ignore_js_op>


5.数据输入(写)
数据写入的操作也是在 tRCD 之后进行,但此时没有了 CL(记住,CL 只出现在读取操作中),行寻址与列寻址的时序图和上文一样,只是在列寻址时,WE#为有效状态。 
为了保证数据的可靠写入,都会留出足够的写入/校正时间(tWR,Write Recovery Time),这个操作也被称作写回(Write Back)。tWR 至少占用一个时钟周期或再多一点(时钟频率越高,tWR 占用周期越多)

6.预充电
由于 SDRAM 的寻址具体独占性,所以在进行完读写操作后,如果要对同一个Bank的另一行进行寻址,就要将原来有效(工作)的行关闭,重新发送行/列地址。Bank 关闭现有工作行,准备打开新行的操作就是预充电(Precharge)。 
在发出预充电命令之后,要经过一段时间才能允许发送 RAS 行有效命令打开新的工作行,这个间隔被称为tRP(Precharge command Period,预充电有效周期)。和 tRCD、CL 一样,tRP 的单位也是时钟周期数,具体值视时钟频率而定。

7.刷新
之所以称为 DRAM,就是因为它要不断进行刷新(Refresh)才能保留住数据,因此它是 DRAM 最重要的操作。刷新操作与预充电中重写的操作一样,都是用 S-AMP 先读再写。 
但为什么有预充电操作还要进行刷新呢?因为预充电是对一个或所有L-Bank 中的工作行操作,并且是不定期的,而刷新则是有固定的周期,依次对所有行进行操作,以保留那些久久没经历重写的存储体中的数据。但与所有 L-Bank 预充电不同的是,这里的行是指所有 L-Bank 中地址相同的行,而预充电中各 L-Bank 中的工作行地址并不是一定是相同的。比如我有四片,刷新是我依次刷新四片内存中的某个地址,然后再刷下一个;而预充电的工作行地址可以不同。
那么要隔多长时间重复一次刷新呢?目前公认的标准是,存储体中电容的数据有效保存期上限是64ms(毫秒,1/1000 秒),也就是说每一行刷新的循环周期是 64ms。这样刷新速度就是:行数量/64ms。我们在看内存规格时,经常会看到 4096 Refresh Cycles/64ms 或 8192 RefreshCycles/64ms 的标识,这里的 4096 与 8192 就代表这个芯片中每个 L-Bank 的行数。刷新命令一次对一行有效,发送间隔也是随总行数而变化,4096 行时为 15.625μs(微秒,1/1000 毫秒),8192 行时就为 7.8125μs。 
刷新操作分为两种:自动刷新(Auto Refresh,简称 AR)与自刷新(Self Refresh,简称 SR)。
SR 则主要用于休眠模式低功耗状态下的数据保存,这方面最著名的应用就是 STR(Suspend to RAM,休眠挂起于内存)。在发出 AR 命令时,将 CKE 置于无效状态,就进入了 SR 模式,此时不再依靠系统时钟工作,而是根据内部的时钟进行刷新操作。在 SR 期间除了 CKE 之外的所有外部信号都是无效的(无需外部提供刷新指令),只有重新使 CKE 有效才能退出自刷新模式并进入正常操作状态。

以上就是SDRAM是主要工作步骤,对比一下最上面的简易工作流程图,时间是不是就很清楚了呢?
CL=6:CAS Latency,CAS 潜伏期,CAS与读取命令发出到第一笔数据输出的时间  ----读操作
tRCD=6:RAS to CAS Delay(RAS 至 CAS 延迟),行地址发完后,再发列地址的延迟时间  ----行列地址延迟
tRP=6:关闭现有工作行,准备打开新行,经过一段时间才能允许发送 RAS 行有效命令打开新的工作行的时间 ----预充电时间
OK,至此三个时间全部清清楚楚了。

Tiny210 U-BOOT(八)----DDR工作时序与原理 

DDR SDRAM 全称为 Double Data Rate SDRAM,中文名为“双倍数据流 SDRAM”。DDR SDRAM 在原有的 SDRAM的基础上改进而来。下图是DDR和SDRAM的数据传输对比图
<ignore_js_op>


图上可以清楚的看到,DDR SDRAM可在一个时钟周期内传送两次数据,上升沿传一次,下降沿传一次。

1.DDR的基本原理
先来看一张DDR读操作时序图

<ignore_js_op>

从中可以发现它多了两个信号:CLK#与DQS,CLK#与正常 CLK 时钟相位相反,形成差分时钟信号。而数据的传输在 CLK 与 CLK#的交叉点进行,可见在 CLK 的上升与下降沿(此时正好是 CLK#的上升沿)都有数据被触发,从而实现双倍数据传输,也就是DDR。下面来看DDR的内部结构图的SDRAM有什么不同。
<ignore_js_op>


这也是一颗 128Mbit 的内存芯片,标称规格为 32×4bit,右边红框区域就是DDR不同的地方:首先就是内部的L-Bank 规格。SDRAM 中L-Bank 存储单元的容量与芯片位宽相同,但在DDR SDRAM 中并不是这样,存储单元的容量是芯片位宽的一倍,所以在此不能再套用讲解 SDRAM时“芯片位宽=存储单元容量”的公式了。也因此,真正的行、列地址数量也与同规格 SDRAM 不一样了。 
以本芯片为例,在读取时,L-Bank 在内部时钟信号的触发下一次传送 8bit 的数据给读取锁存器,再分成两路 4bit 数据传给复用器,由后者将它们合并为一路 4bit 数据流,然后由发送器在 DQS 的控制下在外部时钟上升与下降沿分两次传输 4bit 的数据给北桥的内存控制器(在ARM和现在的CPU中,内存控制器是集成在CPU中的,现在的PC机中北桥已无内存控制器)。这样,如果时钟频率为 100MHz,那么在 I/O 端口处,由于是上下沿触发,那么就是传输频率就是 200MHz。
现在大家基本明白 DDR SDRAM 的工作原理了吧,这种内部存储单元容量(也可以称为芯片内部总线位宽)=2×芯片位宽(也可称为芯片 I/O 总线位宽)的设计,就是所谓的两位预取(2-bit Prefetch)。  

2.DDR与SDRAM的异同
DDR SDRAM 与 SDRAM 一样,在开机时也要进行 MRS(ModeRegister Set,模式寄存器的设置),不过由于操作功能的增多,DDR SDRAM 在 MRS 之前还多了一 EMRS 阶段(Extended Mode Register Set,扩展模式寄存器设置),这个扩展模式寄存器控制着 DLL 的有效/禁止、输出驱动强度、QFC 有效/无效等。

3.差分时钟
CK#的作用,并不能理解为第二个触发时钟,而是起到触发时钟校准的作用。
由于数据是在 CK 的上下沿触发,造成传输周期缩短了一半,因此必须要保证传输周期的稳定以确保数据的正确传输,这就要求 CK 的上下沿间距要有精确的控制。但因为温度、电阻性能的改变等原因,CK 上下沿间距可能发生变化,此时与其反相的 CK#就起到纠正的作用(CK 上升快下降慢,CK#则是上升慢下降快)。而由于上下沿触发的原因,也使 CL=1.5 和 2.5 成为可能,并容易实现。
<ignore_js_op>


4.数据选取脉冲(DQS)

DQS 是 DDR SDRAM 中的重要功能,它的功能主要用来在一个时钟周期内准确的区分出每个传输周期,并便于接收方准确接收数据。每一颗芯片都有一个 DQS 信号线,它是双向的,在写入时它用来传送由内存控制器发来的 DQS 信号,读取时,则由DDR芯片生成 DQS 向内存控制器发送。完全可以说,它就是数据的同步信号。 

在读取时,DQS 与数据信号同时生成(也是在 CK 与 CK#的交叉点)。而 DDR 内存中的 CL 也就是从 CAS 发出到 DQS 生成的间隔,数据真正出现在数据 I/O 总线上相对于 DQS 触发的时间间隔被称为tAC实际上,DQS 生成时,芯片内部的预取已经完毕了,tAC 是指上文结构图中灰色部分的数据输出时间,由于预取的原因,实际的数据传出可能会提前于 DQS 发生数据提前于 DQS 传出)。

DQS 在读取时与数据同步传输,那么接收时也是以 DQS 的上下沿为准吗?不,如果以 DQS 的上下沿区分数据周期的危险很大。由于芯片有预取的操作,所以输出时的同步很难控制,只能限制在一定的时间范围内,数据在各 I/O 端口的出现时间可能有快有慢,会与 DQS 有一定的间隔,这也就是为什么要有一个 tAC 规定的原因(DDR中的tAC是在DQS触发和数据真正出现在I/O总线上的间隔时间)。而在接收方,一切必须保证同步接收,不能有 tAC 之类的偏差。这样在写入时,芯片不再自己生成 DQS,而以发送方传来的 DQS 为基准,并相应延后一定的时间,在 DQS 的中部为数据周期的选取分割点(在读取时分割点就是上下沿),从这里分隔开两个传输周期。这样做的好处是,由于各数据信号都会有一个逻辑电平保持周期,即使发送时不同步,在 DQS 上下沿时都处于保持周期中,此时数据接收触发的准确性无疑是最高的。
<ignore_js_op>

在写入时,以 DQS 的高/低电平期中部为数据周期分割点,而不是上/下沿,但数据的接收触发仍为 DQS 的上/下沿。

5.写入延迟 
在上面的 DQS 写入时序图中,可以发现写入延迟已经不是0了,在发出写入命令后,DQS与写入数据要等一段时间才会送达。这个周期被称为 DQS 相对于写入命令的延迟时间tDQSS, WRITE Command to the first corresponding rising edge of DQS)。 
    为什么要有这样的延迟设计呢?原因也在于同步,毕竟一个时钟周期两次传送,需要很高的控制精度,它必须要等接收方做好充分的准备才行。tDQSS 是 DDR 内存写入操作的一个重要参数,太短的话恐怕接受有误,太长则会造成总线空闲。tDQSS 最短不能小于 0.75 个时钟周期,最长不能超过 1.25 个时钟周期。
    正常情况下,tDQSS 是一个时钟周期,但写入时接受方的时钟只用来控制命令信号的同步,而数据的接受则完全依靠 DQS 进行同步,所以 DQS 与时钟不同步也无所谓。不过,tDQSS产生了一个不利影响— — 读后写操作延迟的增加,如果 CL=2.5,还要在 tDQSS 基础上加入半个时钟周期,因为命令都要在 CK 的上升沿发出。下图中,当 CL=2.5 时,读后写的延迟将为 tDQSS+0.5 个时钟周期(图中 BL=2)。 

<ignore_js_op>


另外,DDR 内存的数据真正写入由于要经过更多步骤的处理,所以写回时间(tWR)也明显延长,一般在3个时钟周期左右,而在 DDR-Ⅱ规范中更是将 tWR 列为模式寄存器的一项,可见它的重要性。 

6.突发长度
在 DDR SDRAM 中,突发长度只有 2、4、8 三种选择,没有了随机存取的操作(突发长度为 1)和全页式突发。这是为什么呢?因为 L-Bank一次就存取两倍于芯片位宽的数据,所以芯片至少也要进行两次传输才可以,否则内部多出来的数据怎么处理?但是,突发长度的定义也与 SDRAM 的不一样了,它不再指所连续寻址的存储单元数量,而是指连续的传输周期数,每次是一个芯片位宽的数据。 

7.延迟锁定回路(DLL) 
DDR SDRAM 对时钟的精确性有着很高的要求,而 DDR SDRAM 有两个时钟,一个是外部的总线时钟,一个是内部的工作时钟,在理论上 DDR SDRAM 这两个时钟应该是同步的,但由于种种原因,如温度、电压波动而产生延迟使两者很难同步,更何况时钟频率本身也有不稳定的情况(SDRAM 也有内部时钟,不过因为它的工作/传输频率较低,所以内外同步问题并不突出)。
DDR SDRAM 的 tAC 就是因为内部时钟与外部时钟有偏差而引起的,它很可能造成因数据不同步而产生错误的恶果。实际上,不同步就是一种正/负延迟,如果延迟不可避免,那么若是 设定一个延迟值,如一个时钟周期,那么内外时钟的上升与下降沿还是同步的。鉴于外部时钟周期也不会绝对统一,所以需要根据外部时钟动态修正内部时钟的延迟 来实现与外部时钟的同步,这就是 DLL 的任务。 
DLL 不同于主板上的 PLL,它不涉及频率与电压转换,而是生成一个延迟量给内部时钟。目前 DLL 有两种实现方法,一个是时钟频率测量法(CFM,Clock Frequency Measurement),一个是时钟比较法(CC,Clock Comparator)。 
CFM 是测量外部时钟的频率周期,然后以此周期为延迟值控制内部时钟,这样内外时钟正好就相差了一个时钟周期,从而实现同步。DLL 就这样反复测量反复控制延迟值,使内部时钟与外部时钟保持同步。
CC的方法则是比较内外部时钟的长短,如果内部时钟周期短了,就将所少的延迟加到下一个内部时钟周期里,然后再与外部时钟做比较,若是内部时钟周期长了,就将多出的延迟从下一个内部时钟中刨除,如此往复,最终使内外时钟同步。
CFM 式 DLL 工作示意图
<ignore_js_op>


CC 式 DLL 工作示意图
<ignore_js_op>


CFM 与 CC 各有优缺点,CFM 的校正速度快,仅用两个时钟周期,但容易受到噪音干扰,并且如果测量失误,则内部的延迟就永远错下去了。CC 的优点则是更稳定可靠,如果比较失败,延迟受影响的只是一个数据(而且不会太严重),不会涉及到后面的延迟修正,但它的修正时间要比 CFM 长。DLL 功能在 DDR SDRAM 中可以被禁止,但仅限于除错与评估操作,正常工作状态是自动有效的。

Tiny210(S5PV210) U-BOOT(九)----DDR2工作时序与原理
DDR的发展沿着更高数据传输频率,更大内存容量的方向发展,DDR2中做到更高数据传输频率,由DDR的2-bit pretetch向4-bit pretetch发展,而扩展容量,除了增加每个L-Bank的容量以外,另外就是增加L-Bank数,也就是说在内存中,原来DDR中Bank线只有2根,一块内存芯片最多2^2=4片L-Bank,而在DDR2中变成了2^3=8片L-Bank。(下面的一些图和文档出自尔必达的芯片手册)

 

1.4-bit Prefetch

直接上一个表,看看DDR2的三个频率的关系,下图是内部时钟均为133MHz的DDR2/DDR/SDRAM的比较,由图可以看到,相比于DDR,DDR2由于是4-bit Prefetch,外部时钟是内部总线时钟的2倍,而DDR和SDRAM中,这两个时钟频率相等
<ignore_js_op>


上一个对比图,看的会更清楚一点儿: 
<ignore_js_op>


在 SDRAM 与 DDR 时代,这两个时钟频率是相同的,但在 DDR-Ⅱ内存中,内部时钟变成了外部时钟的一半。以 DDR-Ⅱ 533 为例,数据传输频率为 533MHz( 对于每个数据引脚,则是 533Mbps/pin),外部时钟频率为 266MHz,内部时钟频率为 133MHz。因为内部一次传输的数据就可供外部接口传输 4 次,虽然以 DDR 方式传输,但数据传输频率的基准— — 外部时钟频率仍要是内部时钟的两倍才行。 

[size=14.399999618530273px]2. DDR-Ⅱ的新操作与新时序设计
[size=14.399999618530273px]2.1片外驱动调校(OCD,Off-Chip Driver)
DDR-Ⅱ内存在开机时也会有初始化过程,同时在 EMRS 中加入了新设置选项,由于大同小异,此就不多说了。在 EMRS 阶段,DDR-Ⅱ加入了可选的 OCD 功能。 
OCD 的主要用意在于调整 I/O 接口端的电压,来补偿上拉与下拉电阻值。目的是让 DQS 与 DQ 数据信号之间的偏差降低到最小。调校期间,分别测试 DQS 高电平/DQ 高电平,与 DQS 低电平/DQ 高电平时的同步情况,如果不满足要求,则通过设定突发长度的地址线来传送上拉/下拉电阻等级(加一档或减一档),直到测试合格才退出 OCD 操作。
<ignore_js_op>


[size=14.399999618530273px]2.2 片内终结(ODT,On-Die Termination) 
[size=14.399999618530273px]所谓的终结,就是让信号被电路的终端吸收掉,而不会在电路上形成反射,造成对后面信号[size=14.399999618530273px]的影响。 


在 DDR 时代,控制与数据信号的终结在主板上完成,每块 DDR 主板在 DIMM 槽的旁边都会有一个终结电压岛的设计,它主要由一排终结电阻构成。长期以来,这个电压岛一直是 DDR 主板设计上的一个难点。而 ODT 的出现,则将这个难点消灭了。[size=14.399999618530273px]ODT 将终结电阻从主板上移植到了内存芯片内部,主板上不在有终结电路。ODT 的功能与禁止由内存控制器控制,ODT 所终结的信号包括 DQS、RDQS(为 8bit 位宽芯片增设的专用 DQS 读取信号,主要用来简化一个模组中同时使用 4 与 8bit 位宽芯片时的控制设计)、DQ、DM 等。 
<ignore_js_op>


[size=14.399999618530273px]上图中,左边就是DDR时代,在主板上完成信号终结,右边就是从DDR2开始,在内存芯片内部终结信号。在内存芯片工作时系统会把终结电阻器屏蔽,而对于暂时不工作的内存芯片则打开终结电阻器以减少信号的反射。由此DDR2内存控制器可以通过ODT同时管理所有内存引脚的信号终结。并且阻抗值也可以有多种选择。如0Ω、50Ω、75Ω、150Ω等等。并且内存控制器可以根据系统内干扰信号的强度自动调整阻值的大小。

[size=14.399999618530273px]2.3前置 CAS、附加潜伏期与写入潜伏期 
[size=14.399999618530273px]前置 CAS(Posted CAS)是为了解决 DDR 内存中指令冲突而设计的功能。它允许 CAS 信号紧随 RAS 发送,相对于以往的 DDR 等于将 CAS 前置了。
这样,地址线可以立刻空出来,便于后面的行有效命令发出,避免造成命令冲突而被迫延后的情况发生,但读/写操作并没有因此而提前,仍有要保证有足够的延迟/潜伏期,为此,DDR-Ⅱ引入了附加潜伏期的概念(AL,Additive Latency),与 CL 一样,单位为时钟周期数。AL+CL 被定义为读取潜伏期(RL,Read Latency),相应的,DDR-Ⅱ还对写入潜伏期(WL,Write Latency)制定了标准,WL是指从写入命令发出到第一笔数据输入的潜伏期,不要将它和 tDQSS 弄混了,后者是指 DQS 而不是数据。按规定,WL=RL-1,即 AL+CL-1。
<ignore_js_op>


上图中,ACT表示的是激活信号,在没有前置 CAS 功能时,对其他 L-Bank 的寻址操作可能会因当前行的 CAS 命令占用地址线而延后,并使数据 I/O 总线出现空闲(上图中的BUBBLE处),当使用前置 CAS 后,消除了命令冲突并使数据 I/O 总线的利率提高。 
<ignore_js_op>


[size=14.399999618530273px]设置 Posted-CAS 后,必须附加潜伏期以保证应有延迟,此时读取潜伏期(RL)就等于 AL+CL,从中可以看出 AL 的值为 CL+tRCD-1。

至此,DDR2的来龙去脉全部分析完毕,现在可以开始打开芯片手册,开始分析源码了。

Tiny210(S5PV210) U-BOOT(十)----DDR2初始化顺序

现在网上的S5PV210的u-boot源码中关于内存的初始化过程,基本上我没有找到任何资料有过分析DDR2的内存初始化代码的。在看u-boot的这段代码时,也徘徊了很久,不知道如下手,很多文章或资料都将这一段分析过程有意无意的隐藏掉了,最多也只是提一下说参考裸板的代码,在找不到任何资料的情况下,我只能依靠芯片手册上,三星在内存控制器这一章,写的关于DDR2的初始化顺序的28个步骤来一条一条去读去看,在安静下来看了芯片手册以后,我发现三星给的裸板的DDR初始化代码和芯片手册上的初始化步骤完全一致,有的时候,最好的资料其实就在手边,只是我一直在想着找捷径,学习哪有那么多捷径?


现在开始关注一下芯片手册上关于DDR2的初始化流程,P598页:

1.查看芯片手册DDR2的初始化顺序

Initialization sequence for DDR2 memory type
1.  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. 
2. Set the PhyControl0.ctrl_start_point and PhyControl0.ctrl_incbit-fields to correct value according to clock frequency. Set the PhyControl0.ctrl_dll_onbit-field to ‘1’ to turn on the PHY DLL. 
3. DQS Cleaning: Set the PhyControl1.ctrl_shiftc and PhyControl1.ctrl_offsetcbit-fields to correct value according to clock frequency and memory tAC parameters. 
4. Set the PhyControl0.ctrl_start bit-field to ‘1’.  
5. Set the ConControl. At this moment, an auto refresh counter should be off.  
6. Set the MemControl. At this moment, all power down modes should be off. 
7. Set the MemConfig0 register. If there are two external memory chips, set the MemConfig1 register. 
8. Set the PrechConfigand PwrdnConfigregisters. 
9. Set the TimingAref, TimingRow, TimingDataand TimingPower registers according to memory AC parameters. 
10. If QoS scheme is required, set the QosControl0~15and QosConfig0~15registers. 
11. Wait for thePhyStatus0.ctrl_lockedbit-fields to change to ‘1’. Check whether PHY DLL is locked. 
12. PHY DLL compensates the changes of delay amountcaused 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 thePhyControl0.ctrl_forcebit-field to correct value according to thePhyStatus0.ctrl_lock_value[9:2]bit-field to fix delay amount. Clear the PhyControl0.ctrl_dll_on bit-field to turn off PHY DLL. 
13. Confirm whether stable clock is issued minimum 200us after power on 
14. Issue a NOPcommand using the DirectCmdregister 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 Refreshcommands 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. 
26. If there are two external memory chips, perform steps 14~25 for chip1 memory device. 
27. Set the ConControlto turn on an auto refresh counter. 
28. If power down modes is required, set the MemControl registers.

 译文如下
1. 提供稳压电源给内存控制器和内存芯片,内存控制器必须保持CLE在低电平,此时就会提供稳压电源。注:当CKE引脚为低电平时,XDDR2SEL应该处于高电平
2. 依照时钟频率正确配置PhyControl0.ctrl_start_point和PhyControl0.ctrl_incbit-fields的值。配置的PhyControl0.ctrl_dll_on值为'1'以打开PHY DLL。
3. DQS Cleaning:依照时钟频率和内存的tAC参数正确设置PhyControl1.ctrl_shiftc and PhyControl1.ctrl_offsetcbit-fields位的值。
4. 配置PhyControl0.ctrl_start位的值为'1'
5. 配置ConControl,与此同时,auto refresh自动刷新计数器应该关闭
6. 配置MemControl,与此同时,所有的power down(休眠模式)应该闭关
7. 配置MemConfig0寄存器。如果有两组内存芯片(比如有8片DDR,这8片DDR是分别挂在Memory Port1和Memory Port2上),再配置MemConfig1寄存器。
8. 配置PrechConfigPwrdnConfig寄存器
9. 依照内存的tAC参数配置TimingArefTimingRowTimingDataTimingPower寄存器
10. 如果需要QoS标准,配置QosControl0~15QosConfig0~15r寄存器
11. 等待PhyStatus0.ctrl_locked位变为'1'。检查是否PHY DLL是否已锁
12. PHY DLL补偿在内存操作时由PVT(Process, Voltage and Temperature,处理器、电压和温度)变化引起的延迟量。但是,PHY DLL不能因某些可靠的内存操作而切断,除非是工作在低频率下。如果关闭PHY DLL,依照PhyStatus0.ctrl_lock_value[9:2]位的值正确配置PhyControl0.ctrl_force位的值来弥补延迟量(fix delay amount)。清除PhyControl0.ctrl_dll_on位的值来关闭PHY DLL。
13. 上电后,确定最小值为200us的稳定时钟是否发出
14. 使用DirectCmd寄存器发出一个NOP命令,保证CKE引脚为高电平
15. 等最小400ns

16. 使用DirectCmd寄存器发出一个PALL命令
17. 使用DirectCmd寄存器发出一个EMRS2命令,program操作参数
18. 使用DirectCmd寄存器发出一个EMRS3命令,program操作参数
19. 使用DirectCmd寄存器发出一个EMRS命令来使能内存DLLs
20. 使用DirectCmd寄存器发出一个MRS命令,重启内存DLL
21. 使用DirectCmd寄存器发出一个PALL命令
22. 使用DirectCmd寄存器发出两个Auto Refresh(自动刷新)命令
23. 使用DirectCmd寄存器发出一个MRS命令,program操作参数,不要重启内存DLL
24. 等待最小200时钟周期
25. 使用DirectCmd寄存器发出一个EMRS命令给程序的运行参数。如果OCD校正(Off-Chip Driver,片外驱动调校)没有使用,改善一个EMRS命令去设置OCD校准的默认值。在此之后,发送一个EMRS指令去退出OCD校准模式,继续program操作参数
26. 如果有两组DDR芯片,重复14-25步配置chip1的内存,刚刚配置的是chip0,也就是第一组内存芯片
27. 配置ConControlto来打开自动刷新计数器
28. 如果需要power down(休眠)模式,配置MemControl寄存器.




知道了上面的这些初始化步骤,现在再去看三星给的裸板代码中关于SDRAM的BL1的代码,就比较清楚了。

Tiny210(S5PV210) U-BOOT(十一)----DDR2初始化源码分析


1.u-boot关于DDR的源码分析在mem_setup.S中,一点一点来,先看第一段

  1.    /* DMC0 Drive Strength (Setting 2X) */
  2.     ldr    r0, =ELFIN_GPIO_BASE
  3.     ldr    r1, =0x0000AAAA
  4.     str    r1, [r0, #MP1_0DRV_SR_OFFSET]
  5.     ldr    r1, =0x0000AAAA
  6.     str    r1, [r0, #MP1_1DRV_SR_OFFSET]
  7.     ldr    r1, =0x0000AAAA
  8.     str    r1, [r0, #MP1_2DRV_SR_OFFSET]
  9.     ldr    r1, =0x0000AAAA
  10.     str    r1, [r0, #MP1_3DRV_SR_OFFSET]
  11.     ldr    r1, =0x0000AAAA
  12.     str    r1, [r0, #MP1_4DRV_SR_OFFSET]
  13.     ldr    r1, =0x0000AAAA
  14.     str    r1, [r0, #MP1_5DRV_SR_OFFSET]
  15.     ldr    r1, =0x0000AAAA
  16.     str    r1, [r0, #MP1_6DRV_SR_OFFSET]
  17.     ldr    r1, =0x0000AAAA
  18.     str    r1, [r0, #MP1_7DRV_SR_OFFSET]
  19.     ldr    r1, =0x00002AAA
  20.     str    r1, [r0, #MP1_8DRV_SR_OFFSET]
复制代码

首先在初始化DDR之前,引入一个关于DRAM Drive Strength的概念----DRAM Drive Strength(也被称为:driving strength),表示“DRAM驱动强度”。这个参数用来控制内存数据总线的信号强度,数值越高代表信号强度越高,增加信号强度可以提高超频的稳定性。但是并非信号强度高就一定好。
所以,这里我们需要配置这个Drive Striegth,DDR2内存的所有线都需要配置,这里我们从地址线开始看,我们的内存是挂载在Memory Port1(以下简称MP1)上,所以,先在S5PV210的芯片手册搜索Xm1ADDR。
<ignore_js_op>


在P63页,找到MP1的各个pin脚的描述,继续向下搜索。
<ignore_js_op>


在P107页,Pin脚复用的描述表里找到他的GPIO口定义
<ignore_js_op>


Xm1ADDR[0]~[7]的GPIO的MP1_0[0]~7是复用的,OK,搜索MP1_0。
<ignore_js_op>


在P122页最终找到MP1_0DRV,在这个寄存器的描述中,显示这个寄存器就是用来配置我们的内存的Drive Strength的寄存器,搜索MP1_0DRV。
<ignore_js_op>


找到MP1_0DRV的寄存器的配置表,这里给出的初始值是AAAA,我总共是0-7,8根线,每根线我配的值都是10,也就是2x,符合我们上面介绍DRAM Driver Strength的描述,OK,到这里我们的MP1_0寄存器----Xm1ADDR[0]~[7]的Driver Strength配置完毕。剩下的就按照上面的方法,再把剩下的线全部配好,初始均按芯片手册的参考值配为2x,也就是10。现在再回过头来看上面的初始化代码,清楚多了,整个这么一长串代码,其实只做了一件事,就是给MP1上接的内存的每一根线都配置Driver Strength的值为2x,这里有一个地方,需要注意一下,最后一个MP1_8他的参考配置为0x2AAA。照样的,再配置DM1,把Memory Port2也配置完毕,这里我们没有用到MP2,不配置,应该也没有关系。继续看下一段代码:

  1. /* DMC0 initialization at single Type*/
  2.     ldr    r0, =APB_DMC_0_BASE
  3.     ldr    r1, =0x00101000                @PhyControl0 DLL parameter setting, manual 0x00101000
  4.     str    r1, [r0, #DMC_PHYCONTROL0]
  5.     ldr    r1, =0x00000086                @PhyControl1 DLL parameter setting, LPDDR/LPDDR2 Case
  6.     str    r1, [r0, #DMC_PHYCONTROL1]
  7.     ldr    r1, =0x00101002                @PhyControl0 DLL on
  8.     str    r1, [r0, #DMC_PHYCONTROL0]
  9.     ldr    r1, =0x00101003                @PhyControl0 DLL start
  10.     str    r1, [r0, #DMC_PHYCONTROL0]
复制代码

根据芯片手册给出的参考步骤,第2步如下:

2.依照时钟频率正确配置PhyControl0.ctrl_start_point和PhyControl0.ctrl_inc bit-fields的值。配置的PhyControl0.ctrl_dll_on值为'1'以打开PHY DLL。

那开始配置PhyControl0的相关位,P614页找到DRAM的寄存器配置表,找到有关PhyControl0的寄存器为PHYCONTROL0
<ignore_js_op>


OK,我们需要正确配置PhyControl0寄存器的ctrl_start_point和ctrl_inc这两个位的值,OK,查看寄存器的描述
<ignore_js_op>


[size=14.399999618530273px]ctrl_start_point和ctrl_inc这两位芯片手册上给的参考值为0x10,先配这两位,则为:
ctrl_start_point: 0x10 ---- 10000(二进制)

[size=14.399999618530273px]ctrl_inc: 0x10 ---- 10000(二进制)
[size=14.399999618530273px]其余的位全部配成0,最终为10000 00010000 00000000 ---- 0x00101000,代码如下:

  1. ldr    r0, =APB_DMC_0_BASE  
  2.   
  3. ldr    r1, =0x00101000                @PhyControl0 DLL parameter setting, manual 0x00101000  
  4. str    r1, [r0, #DMC_PHYCONTROL0]  
复制代码

[size=14.399999618530273px]第2步配置完成,接着往下看第3步--3.DQS Cleaning:依照时钟频率和内存的tAC参数正确设置PhyControl1.ctrl_shiftc and PhyControl1.ctrl_offsetc bit-fields位的值。

查看PhyControl1的ctrl_shiftc和ctrl_offsetc这两位的描述
<ignore_js_op>


我们的内存是DDR2-800,所以ctrl_shiftc配置为0x6 ---- 110,ctrl_offsetc的配置暂时参考三星的裸板参数配置为0,ctrl_ref配置为1000,整理代码如下:

  1. ldr    r1, =0x00000086                @PhyControl1 DLL parameter setting, LPDDR/LPDDR2 Case  
  2. str    r1, [r0, #DMC_PHYCONTROL1]  
复制代码

按照第2步的要求,打开PLL,将PhyControl0.ctrl_dll_on配置为1

  1. ldr    r1, =0x00101002                @PhyControl0 DLL on  
  2. str    r1, [r0, #DMC_PHYCONTROL0]  
复制代码

继续第4步--4.配置PhyControl0.ctrl_start位的值为'1'

  1. ldr    r1, =0x00101003                @PhyControl0 DLL start  
  2. str    r1, [r0, #DMC_PHYCONTROL0]  
复制代码

后面的步骤,全部是按照三星的芯片手册上的那28步来一步一步的在操作寄存器,没有一步有漏掉,所以,对比着芯片手册上的那28步一行一行的查看代码就OK了。有一点,在配置完16-25步后,到第26步时--配置第26步--26.如果有两组DDR芯片,重复14-25步配置chip1的内存,刚刚配置的是chip0,也就是第一组内存芯片。这里就把第16-25步重新再做一次,初始化chip1就OK了。代码太多,这里就不详细重复了,我这里参考的是三星的裸板的DDR源码。至此,DDR的源码也分析完成,下面,应该可以开始将这些移植进一个新的u-boot中去了。

  

 Tiny210(S5PV210) U-BOOT(十二)----编译出u-boot.bin
经过前面十一篇帖子的基础知识的梳理准备,现在我们可以正式开始移植了,所谓磨刀不误砍柴工,如果前面的一些知识没有准备好,后面你照着步骤来做这些事,毫无意义,试问在工作中,有人会写一个步骤与手册来教给你,让你照着做就OK了吗,那如果有这样的工作,而且薪水可观的话,请联系我,不甚感激!,言归正传,开始正式移植的第一天的任务。
第一天
任务:配置板文件,编译出u-boot.bin

1.cp -a board/samsung/smdkc100 board/samsung/tiny210

2.cp include/configs/smdkc100.h include/configs/tiny210.h

3.指定平台
约定如下:
Target(目标):tiny210
ARCH(架构):arm
CPU(芯片):armv7
Board name(板子名称):tiny210
Vendo(生产厂家):samsung
Soc(CPUO类型):s5pc1xx
Options(可选项)

#vim boards.cfg,在270行添加
tiny210    arm    armv7    tiny210    samsung    s5pc1xx
上面这句话主要是在选编译时的代码目录

4.修改顶层目录下的Makefile指定交叉编译工具链添加:

  1. ifeq(arm,$(ARCH))
  2. CROSS_COMPILE?=/opt/FriendlyARM/toolschain/4.5.1/bin/arm-linux-
  3. endif
复制代码

上面是我的编译工具链的路径,你换成自己的就OK了。

5.修改tiny210单板目录下的的相关文件--board/samsung/tiny210/
1)修改Makefile,编译tiny210.c文件,将smdkc100.c改名为tiny210.c
COBJS-y        := tiny210.o
2)修改board/samsung/tiny210/lowlevel_init.S
添加头文件引用

#include <s5pc110.h>

6.开始编译
#make tiny210_config
#make
上面的命令是调用u-boot根目录下的mkconfig脚本来完成,用来生成config.mk, config.h

错误1:
<ignore_js_op>


解决办法:include目录下添加s5pc110.h头文件(在lowlevel_init.S中包含的头文件没有添加,所以报错,加上)
错误2:
<ignore_js_op>


解决办法:从include/asm/arch-s5pc1xx下拷贝一个hardware.h到include/asm/arch目录
错误3:
<ignore_js_op>


解决办法:include目录下添加s5pc11x.h头文件(在s5pc110.h中包含有此头文件没有添加,所以报错,加上)

7.编译成功,出现u-boot.bin

原文地址:https://www.cnblogs.com/cainiaoaixuexi/p/3431586.html