本篇学习s3c2440的内存控制器,并编写运行在内存中的代码。
支持下列features:
- 软件可配置大小端。
- 1GB地址空间,8个banks,每个bank 128Mbytes
- 可编程访问大小(8/16/32 位),bank0只支持16/32位
- 6个用于ROM, SRAM等内存的banks,2个用于ROM/SRAM/SDRAM的banks。
- 7个固定起始地址的banks
- 1个灵活的起始地址bank,bank大小也可编程。
- 支持external wait
- 支持SDRAM的self-refresh和power down mode
复位后的地址映射:
bank6/7通常作为SDRAM内存地址,所以bank7的起始地址不是固定的,它的起始地址是bank6的结束地址的下一个地址,这样做是为了保证内存地址的连续。而且bank6和bank7必须有相同的内存大小。如bank6的大小为2M,则bank6的地址范围为:0x30000000-0x301fffff,bank7的地址范围为0x30200000-0x303fffff。
bank0(nGCS0)的位宽为16位或32位,因为bank0作为booting ROM bank,依赖于operation mode:
| OM1 | OM0 | Booting Rom Data width | 
| 0 | 0 | Nand flash mode | 
| 0 | 1 | 16 bit | 
| 1 | 0 | 32 bit | 
| 1 | 1 | test mode | 
相关寄存器
| Register | Address | R/W | Reset Value | Desc | 
| BWSCON | 0x48000000 | R/W | 0x0 | Bus width & wait status control register 每个bank占4位,低2位,数据宽度,第3位是否使用等待状态,第4位,UB/LB。 bank0比较特殊,[2:1]表示位宽,只读,其它两位为0。  | 
| BANKCONn | 0x48000004 0x48000008 0x4800000C 0x48000010 0x48000014 0x48000018  | R/W | 0x0700 | bank0-5控制寄存器,配置一些时序参数等。 | 
| BANKCON6/7 | 0x4800001C 0x48000020  | R/W | 0x18008 | bank6/7控制寄存器 [16:15] 11=SDRAM  | 
| REFRESH | 0x48000024 | R/W | 0xac0000 | SDRAM刷新控制寄存器 | 
| BANKSIZE | 0x48000028 | R/W | 0x0 | bank size寄存器 [2:0] bank6/7 memory map 010=128M 001=64M 000=32M 111=16M 110=8M 101=4M 100=2M  | 
| MRSRB6/7 | 0x4800002C 0x48000030  | R/W | xxx | sdram mode register set register | 
本文所有初始化代码使用C语言实现,这需要一点技巧,源码树如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14  | herbert@herbert-pc:/work/code/s3c2440/sdram$ tree.├── bare.lds├── init.c├── init.o├── Makefile├── sdram_bin├── sdram.c├── sdram_elf├── sdram.o├── start.o└── start.S0 directories, 10 files | 
bare.lds
1 2 3 4 5 6  | SECTIONS{    . = 0x30000000;    .text : { *(.text) }    .data : { *(.data) }} | 
链接地址变为SDRAM的起始地址。
Makefile
1 2 3 4 5 6 7 8  | .PHONY: allall:    $(AS) -o start.o start.S    $(CC) -c -o init.o init.c    $(CC) -c -o sdram.o sdram.c     $(LD) -T bare.lds -o sdram_elf start.o init.o sdram.o    $(OBJCOPY) -O binary -S sdram_elf sdram_bin    cp sdram_bin /work/targets | 
start.o的顺序必须是第一个,因为_start入口必须在代码段的最前面。
start.S源码如下:
1 2 3 4 5 6 7 8 9 10 11 12  |     .text    .global _start_start:    ldr sp, =1024*4    bl init    ldr pc, =ramram:    ldr sp, =0x34000000    bl mainloop:    b loop | 
这里的技巧是使用两个栈,底层初始化代码的栈在SRAM,main函数执行的栈在SDRAM。在进入main函数运行之前,修改pc的值为SDRAM中的指令地址,这需要让链接器来填写该地址,所以使用ram标签,链接脚本按照0x30000000地址链接,所以ram标签的值就是SDRAM中正确的指令地址。
需要指出的是,start.o和init.o也是按照0x30000000地址来链接的,但是他们实际运行在0地址,由于这里的代码都是PC相对位置代码,所以链接地址不匹配是没有关系的,这两处代码不关心链接地址。
init.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38  | void disable_watchdog(){    *(volatile unsigned long *)0x53000000 = 0; }void init_sdram(){    *(volatile unsigned long *)0x48000000 = 0x22011110;  // BWSCON    *(volatile unsigned long *)0x48000004 = 0x00000700;  // BANKCON0    *(volatile unsigned long *)0x48000008 = 0x00000700;  // BANKCON1    *(volatile unsigned long *)0x4800000c = 0x00000700;  // BANKCON2    *(volatile unsigned long *)0x48000010 = 0x00000700;  // BANKCON3    *(volatile unsigned long *)0x48000014 = 0x00000700;  // BANKCON4    *(volatile unsigned long *)0x48000018 = 0x00000700;  // BANKCON5    *(volatile unsigned long *)0x4800001c = 0x00018005;  // BANKCON6    *(volatile unsigned long *)0x48000020 = 0x00018005;  // BANKCON7    *(volatile unsigned long *)0x48000024 = 0x008C07A3;  // REFRESH    *(volatile unsigned long *)0x48000028 = 0x000000B1;  // BANKSIZE    *(volatile unsigned long *)0x4800002c = 0x00000030;  // MRSRB6    *(volatile unsigned long *)0x48000030 = 0x00000030;  // MRSRB7}void cpy_sram(){    int i;    for (i = 0; i < 4096; i += 4)    {        *(volatile unsigned long *)(0x30000000+i) = *(volatile unsigned long *)i;    }}void init(){    disable_watchdog();    init_sdram();    cpy_sram();} | 
代码非常简单,关看门狗,初始化内存,拷贝数据到内存,这里函数内部的变量是可以使用的,因为我们有定义栈,但是由于链接地址和代码的实际地址不一致,全局变量是否可以使用?(后续需要在学习linkers & loaders)。
sdram.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34  | /* * my s3c2440 develop board has 4 led * led1 ==> GPF4 * led2 ==> GPF5 * led4 ==> GPF6 * led8 ==> GPF7 */#define GPFCON      (*(volatile unsigned long *)0x56000050)#define GPFDAT      (*(volatile unsigned long *)0x56000054)#define GPF4_out    (1<<(4*2))#define GPF5_out    (1<<(5*2))#define GPF6_out    (1<<(6*2))void main(){    int i = 0, j = 4;    // configure gpio as output mode    GPFCON = GPF4_out | GPF5_out | GPF6_out;    while (1)    {        i++;        if (i == 30000) {            GPFDAT = ~(1 << j);            j++;            if (j == 7)                j = 4;            i = 0;        }    }} | 
和上一篇的跑马灯代码完全一样。
编译后,烧录到板子上,可正常跑马亮灯,但速度比直接在SRAM中慢(obviously)。