本篇学习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.S 0 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: all all: $(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, =ram ram: ldr sp, =0x34000000 bl main loop: 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)。