【PMON 研究】【02】PMON 中读取 DDR 内存 SPD 信息的代码
本帖最后由 lophyxp 于 2017-1-21 18:13 编辑心映真的空间
苦心励志 技术强国
让我们面对现实 让我们忠于理想
欢迎来到唐刚的首页
PMON 中读取 DDR 内存 SPD 信息的代码
龙芯相关 » PMON 研究 » PMON 中读取 DDR 内存 SPD 信息的代码
本文讲述用I2C协议从内存条上的SPD(eeprom)中读取内存参数。
SPD 信息简述
SPD(eeprom)中,定义了很多参数,这里给出一个简单的说明。
第0字节 表示厂商使用的字节数。
第1字节 表示EERPOM存储容量。
第2字节 表示内存类型。
第3字节 表示行地址位数。
第4字节 表示列地址位数。
第5字节 表示排数。
第6字节 表示数据宽度(低字节)。
第7字节 表示数据宽度(高字节)。
第8字节 表示信号电平。
第9字节 表示SDRAM最高时钟频率。
第10字节 表示SDRAM访问时间。
第11字节 表示配置类型。
第12字节 表示刷行率/类型。
第13字节 表示最小SDRAM颗粒数据宽度。
第16字节 表示支持突发传输长度。
第17字节 表示逻辑BANK数。
第18字节 表示CAS延时。
第23字节 表示SDRM时钟。是2的最大指数倍。
第24字节 表示SDRAM访问时间。
第34字节 表示输入数据建立时间。
第35字节 表示输入数据保持时间。
第62字节 表示SPD版本号。
其它的字节,就要参考SPD文档了。后面一大段程序就是实现了读取这些参数,然后根据这些参数来设置龙芯内存的SDRAM寄存器。
PMON 中对内存SPD读取及处理的方法
读取spd
● (offset 3)读取行地址数目
● (offset 4)读取列地址数目
● 根据行列地址数目判断ddrtype
● (offset 31)读取每个片选的容量
– 用该值初始化内存大小tmpsize
● (offset 17)读取芯片bank,一般为4
● (offset 5)读取片选数(相当于module数)
– 按照片选数,计算内存大小tmpsize
– 按照读出的片选数设置sdcfg寄存器
● (offset 6)读取位宽
● 其中sdcfg的初始值是按照ddr333设置的
(注释表明,没有详细验证)
初始化内存控制器
● 经过spd的读取过程我们获得sdcfg配置和
内存大小memsize
● 设置CPU内部sdcfg寄存器(0x1ff00008)
● 按照memsize设置内存窗口
● 1ff0 0000偏移开始的四个寄存器
– 0x10/0x20(mem window base)
– 0x18/0x28(mem window size)
– e.g. 128MB内存
● 0x10=0,0x18=128MB,0x20=512MB,0x28=0
– e.g. 512MB内存
● 0x10=0,0x18=256MB,0x20=512MB,0x28=256MB
– 有很多的nop,调试试验的结果
代码讲解
/*
* Now determine DRAM configuration and size by
* reading the I2C EEROM (SPD) on the DIMMS (DDR)
*/
PRINTSTR("DIMM read\r\n")
/* only one memory slot, slave address is 10100001b */
lia1, 0x0
1:
li a0, 0xa1 /* a0: slave address, a1: reg index to read */
bal i2cread
nop
/* save a1 */
move t1, a1
/* print */
move a0, v0
balhexserial
nop
PRINTSTR("\r\n")
/* restore a1 */
movea1,t1
addiu a1,a1,1
li v0, 0x20
bleua1, v0, 1b /* repeat for 32 times */
nop
li msize, 0 /* msize is register s2 */
/* set some parameters for DDR333
rank number and DDR type field will be filled later
to check: fix TCAS?
*/
li sdCfg, 0x341043df /* sdCfg is register s6 */
/* read DIMM memory type (must be DDRAM) */
#if 0
li a0,0xa1
li a1,2
bal i2cread
nop
bne v0,7,.nodimm
nop
PRINTSTR("read memory type\r\n")
#endif
/* read DIMM number of rows */
li a0, 0xa1
li a1, 3
bal i2cread
nop
move a0, v0 // v0 is the return value register
subu v0, 12
move s1, v0 // save for later use
bgtu v0, 2, .nodimm // if v0 > 2 then jump to .nodimm
nop
PRINTSTR("read number of rows\r\n")
2: /* read DIMM number of cols */
li a0, 0xa1
li a1, 4
bal i2cread
nop
subu v0, 8 // v0 saved the return value
bgtu v0, 4, .nodimm
nop
// read and check ddr type, the combination of t1 and v0 represents a ddr type
move t1, s1
bne t1, 0, 10f
nop
bne v0, 2, 20f
nop
li v0, 0
b .ddrtype
nop
20: bne v0, 1, 21f
nop
li v0, 1
b .ddrtype
nop
21: bne v0, 0, 22f
nop
li v0, 2
b .ddrtype
nop
22: bne v0, 3, 33f
nop
li v0, 3
b .ddrtype
nop
10: bne t1, 1, 11f
nop
bne v0, 3, 20f
nop
li v0, 4
b .ddrtype
nop
20: bne v0, 2, 21f
nop
li v0, 5
b .ddrtype
nop
21: bne v0, 1, 22f
nop
li v0, 6
b .ddrtype
nop
22: bne v0, 4, 33f
nop
li v0, 7
b .ddrtype
nop
11: bne t1, 2, 33f
nop
bne v0, 4, 20f
nop
li v0, 8
b .ddrtype
nop
20: bne v0, 3, 21f
nop
li v0, 9
b .ddrtype
nop
21: bne v0, 2, 33f
nop
li v0, 10
b .ddrtype
nop
33: PRINTSTR("DDR type not supported!\r\n");
34: b 34b
nop
.ddrtype:
#bit 25:22 is DDR type field
sll v0, 22
and v0, 0x03c00000
or sdCfg, v0
/* read DIMM memory size per side */
li a0, 0xa1
li a1, 31
bal i2cread
nop
beqz v0,.nodimm
nop
sll tmpsize,v0,22 # multiply by 4M
PRINTSTR("read memory size per side\r\n")
2: /* read DIMM number of blocks-per-ddrram */
li a1,17
bal i2cread
nop
beq v0,2,2f
nop
bne v0,4,.nodimm
nop
PRINTSTR("read blocks per ddrram\r\n")
2: /* read DIMM number of sides (banks) */
li a1,5
bal i2cread
nop
beq v0,1,2f
nop
bne v0,2,.nodimm
nop
sll tmpsize,1 # msize *= 2
orsdCfg, 0x1<<27
PRINTSTR("read number of sides\r\n")
2: /* read DIMM width */
li a1,6
bal i2cread
nop
bleu v0,36,2f
nop
bgtu v0,72,.nodimm
nop
PRINTSTR("read width\r\n")
2: addu msize,tmpsize
b 2f
nop
.nodimm:
move dbg,a0 // dbg is s5
PRINTSTR ("\r\nNo DIMM in slot ")
move a0,dbg
bal hexserial
nop
PRINTSTR("\r\n")
move a0,dbg
#limsize,0x10000000
#li sdCfg,0x3d9043df #~133MHz
limsize,0x20000000
li sdCfg,0x3d5043df #~133MHz
2:
PRINTSTR("DIMM SIZE=")
move a0,msize
bal hexserial
nop
PRINTSTR("\r\n")
li t0, 0xbff00008
sd sdCfg, 0(t0)
nop
nop
/* (uint32_t *)0xbfe00040 = 0x80000000
* means only address below 1G will be sent to CPU
*/
lui t0, 0xbfe0
li t1, 0x80000000
sw t1, 0x40(t0)
nop
#### gx 2006-03-17: mode ####
#li t1,0x20
li t1,0x28
li t0, 0xbff00000
sd t1,0(t0)
nop
li t1,0x0
li t0, 0xbff00000
sd t1,0x30(t0)
nop
##fixed base address reg##
sd zero, 0x10(t0)
nop
lui t1,0x2000
sd t1,0x20(t0)
nop
li t1, 0x10000000
blt msize, t1, 1f
nop
####bigger than 256MB####
sd t1, 0x18(t0)
nop
move a0, msize
subu a0, t1
nop
nop
nop
sd a0, 0x28(t0)
nop
b 2f
1:
nop
nop
sd msize, 0x18(t0)
nop
nop
nop
sd zero, 0x28(t0)
nop
nop
nop
2:
PRINTSTR("sdcfg=");
move a0,sdCfg
bal hexserial
nop
PRINTSTR("\r\n");
PRINTSTR("msize=");
move a0,msize
bal hexserial
nop
PRINTSTR("\r\n")
skipdimm:
li t1,0 # accumulate pcimembasecfg settings
/* set bar0 mask and translation to point to SDRAM */
sub t0,msize,1
not t0
srl t0,BONITO_PCIMEMBASECFG_ASHIFT-BONITO_PCIMEMBASECFG_MEMBASE0_MASK_SHIFT
and t0,BONITO_PCIMEMBASECFG_MEMBASE0_MASK
or t1,t0
li t0,0x00000000
srl t0,BONITO_PCIMEMBASECFG_ASHIFT-BONITO_PCIMEMBASECFG_MEMBASE0_TRANS_SHIFT
and t0,BONITO_PCIMEMBASECFG_MEMBASE0_TRANS
or t1,t0
or t1,BONITO_PCIMEMBASECFG_MEMBASE0_CACHED
/* set bar1 to minimum size to conserve PCI space */
li t0, ~0
srl t0,BONITO_PCIMEMBASECFG_ASHIFT-BONITO_PCIMEMBASECFG_MEMBASE1_MASK_SHIFT
and t0,BONITO_PCIMEMBASECFG_MEMBASE1_MASK
or t1,t0
li t0,0x00000000
srl t0,BONITO_PCIMEMBASECFG_ASHIFT-BONITO_PCIMEMBASECFG_MEMBASE1_TRANS_SHIFT
and t0,BONITO_PCIMEMBASECFG_MEMBASE1_TRANS
or t1,t0
or t1,BONITO_PCIMEMBASECFG_MEMBASE1_CACHED
sw t1,BONITO_PCIMEMBASECFG(bonito)
/* enable configuration cycles now */
lw t0,BONITO_BONPONCFG(bonito)
and t0,~BONITO_BONPONCFG_CONFIG_DIS
sw t0,BONITO_BONPONCFG(bonito)
PRINTSTR("Init SDRAM Done!\r\n");
=====================
解释:
/* only one memory slot, slave address is 10100001b */
lia1, 0x0
1:
li a0, 0xa1 /* a0: slave address, a1: reg index to read */
bal i2cread
nop
上面这段代码,把 0 设置给 a1,然后把 0xa1 设置给 a0,然后就调用 I2C 的子函数来读取数据。a0 和 a1 寄存器是 i2cread 这个函数的两个参数。
i2cread 函数的实现
下面来看 i2cread 函数的内容
/* a0: slave address
a1: reg off
*/
LEAF(i2cread)
/* set device address */
liv0, 0xbfd00000 + SMBUS_HOST_ADDRESS
lia0, 0xa1
sba0, 0(v0);
/* store register offset */
liv0, 0xbfd00000 + SMBUS_HOST_COMMAND
sba1, 0(v0);
/* read byte data protocol */
liv0, 0x08
liv1, 0xbfd00000 + SMBUS_HOST_CONTROL
sbv0, 0(v1);
/* make sure SMB host ready to start, important!--zfx */
liv1, 0xbfd00000 + SMBUS_HOST_STATUS
lbu v0, 0(v1)
andi v0,v0, 0x1f
beqzv0,1f
nop
sbv0, 0(v1)
lbu v0, 0(v1) #flush the write
1:
/* start */
liv1, 0xbfd00000 + SMBUS_HOST_CONTROL
lbu v0, 0(v1)
ori v0, v0, 0x40
sbv0, 0(v1);
/* wait */
liv1, 0xbfd00000 + SMBUS_HOST_STATUS
lia1, 0x1000
1:
#if 1
/* delay */
li a0, 0x1000
2:
bnez a0,2b
addiu a0, -1
#endif
addiu a1, -1
beqz a1, 1f
nop
lbuv0, 0(v1)
andi v0, SMBUS_HOST_STATUS_BUSY
bnezv0, 1b#IDEL ?
nop
1:
liv1, 0xbfd00000 + SMBUS_HOST_STATUS
lbu v0, 0(v1)
andi v0,v0, 0x1f
beqzv0,1f
nop
sbv0, 0(v1) #reset
lbu v0, 0(v1) #flush the write
1:
liv1, 0xbfd00000 + SMBUS_HOST_DATA0
lbuv0, 0(v1)
jr ra
nop
END(i2cread)
=====================
解释:
/* set device address */
liv0, 0xbfd00000 + SMBUS_HOST_ADDRESS
lia0, 0xa1
sba0, 0(v0);
上面代码是输出从设备的地址。
/* store register offset */
liv0, 0xbfd00000 + SMBUS_HOST_COMMAND
sba1, 0(v0);
上面代码是输出从设备的寄存器偏移量。
/* read byte data protocol */
liv0, 0x08
liv1, 0xbfd00000 + SMBUS_HOST_CONTROL
sbv0, 0(v1);
/* make sure SMB host ready to start, important!--zfx */
liv1, 0xbfd00000 + SMBUS_HOST_STATUS
lbu v0, 0(v1)
andi v0,v0, 0x1f
beqzv0,1f
nop
sbv0, 0(v1)
lbu v0, 0(v1) #flush the write
上面代码是查看数据总线是否准备好数据。
1:
/* start */
liv1, 0xbfd00000 + SMBUS_HOST_CONTROL
lbu v0, 0(v1)
ori v0, v0, 0x40
sbv0, 0(v1);
/* wait */
liv1, 0xbfd00000 + SMBUS_HOST_STATUS
lia1, 0x1000
1:
#if 1
/* delay */
li a0, 0x1000
2:
bnez a0,2b
addiu a0, -1
#endif
addiu a1, -1
beqz a1, 1f
nop
lbuv0, 0(v1)
andi v0, SMBUS_HOST_STATUS_BUSY
bnezv0, 1b#IDEL ?
nop
上面代码是查看总线是否在忙状态。
1:
liv1, 0xbfd00000 + SMBUS_HOST_STATUS
lbu v0, 0(v1)
andi v0,v0, 0x1f
beqzv0,1f
nop
sbv0, 0(v1) #reset
lbu v0, 0(v1) #flush the write
1:
liv1, 0xbfd00000 + SMBUS_HOST_DATA0
lbuv0, 0(v1)
jr ra
nop
上面代码是已经把命令成功发送出去,然后成功地读取回来数据,保存在v0寄存里。
通过上面的子函数,就可以通过I2C总线去读取内存条上的EEPROM参数,以便后面进行内存初始化。
在这里第一次读取是第一个字节。
页:
[1]