ILD

内存控制器
作者:HerbertYuan 邮箱:yuanjp89@163.com
发布时间:2017-9-23 站点:Inside Linux Development

本篇学习s3c2440的内存控制器,并编写运行在内存中的代码。


1 手册学习

支持下列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:

OM1OM0Booting Rom Data width
00Nand flash mode
0116 bit
1032 bit
11test mode


相关寄存器

RegisterAddressR/WReset ValueDesc
BWSCON0x48000000R/W0x0

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/W0x0700bank0-5控制寄存器,配置一些时序参数等。
BANKCON6/7

0x4800001C

0x48000020

R/W0x18008

bank6/7控制寄存器

[16:15] 11=SDRAM


REFRESH
0x48000024R/W0xac0000SDRAM刷新控制寄存器
BANKSIZE0x48000028R/W0x0

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/Wxxxsdram mode register set register


2 代码

本文所有初始化代码使用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)。

Copyright © linuxdev.cc 2017-2024. Some Rights Reserved.