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 */
    li  a1, 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
    bal  hexserial
    nop

    PRINTSTR("\r\n")    

        /* restore a1 */
    move  a1,t1
    addiu a1,a1,1
    li   v0, 0x20
    bleu  a1, 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    
    or  sdCfg, 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
    #li  msize,0x10000000
    #li    sdCfg,0x3d9043df    #~133MHz
    li  msize,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 */
    li  a1, 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 */
        li  v0, 0xbfd00000 + SMBUS_HOST_ADDRESS
    li  a0, 0xa1
    sb  a0, 0(v0);

    /* store register offset */
        li  v0, 0xbfd00000 + SMBUS_HOST_COMMAND
    sb  a1, 0(v0);

    /* read byte data protocol */
    li  v0, 0x08
    li  v1, 0xbfd00000 + SMBUS_HOST_CONTROL
    sb  v0, 0(v1);

    /* make sure SMB host ready to start, important!--zfx */
    li  v1, 0xbfd00000 + SMBUS_HOST_STATUS
    lbu v0, 0(v1)
    andi v0,v0, 0x1f
    beqz  v0,1f
    nop
    sb  v0, 0(v1)
    lbu v0, 0(v1)   #flush the write

1:
    /* start */
    li  v1, 0xbfd00000 + SMBUS_HOST_CONTROL
    lbu v0, 0(v1)
    ori v0, v0, 0x40
    sb  v0, 0(v1);

    /* wait */
    li  v1, 0xbfd00000 + SMBUS_HOST_STATUS
    li  a1, 0x1000
1:

#if 1
    /* delay */
    li a0, 0x1000    
2:            
    bnez    a0,2b
    addiu    a0, -1
#endif
    addiu    a1, -1
    beqz    a1, 1f
    nop

    lbu  v0, 0(v1)
    andi v0, SMBUS_HOST_STATUS_BUSY
    bnez  v0, 1b  #IDEL ?
    nop

1:

    li  v1, 0xbfd00000 + SMBUS_HOST_STATUS
    lbu v0, 0(v1)
    andi v0,v0, 0x1f
    beqz  v0,1f
    nop
    sb  v0, 0(v1)   #reset
    lbu v0, 0(v1)   #flush the write
1:

    li  v1, 0xbfd00000 + SMBUS_HOST_DATA0
    lbu  v0, 0(v1)

    jr    ra
    nop
END(i2cread)    

=====================
解释:
    /* set device address */
        li  v0, 0xbfd00000 + SMBUS_HOST_ADDRESS
    li  a0, 0xa1
    sb  a0, 0(v0);
上面代码是输出从设备的地址。
    /* store register offset */
        li  v0, 0xbfd00000 + SMBUS_HOST_COMMAND
    sb  a1, 0(v0);
上面代码是输出从设备的寄存器偏移量。
    /* read byte data protocol */
    li  v0, 0x08
    li  v1, 0xbfd00000 + SMBUS_HOST_CONTROL
    sb  v0, 0(v1);

    /* make sure SMB host ready to start, important!--zfx */
    li  v1, 0xbfd00000 + SMBUS_HOST_STATUS
    lbu v0, 0(v1)
    andi v0,v0, 0x1f
    beqz  v0,1f
    nop
    sb  v0, 0(v1)
    lbu v0, 0(v1)   #flush the write
上面代码是查看数据总线是否准备好数据。
1:
    /* start */
    li  v1, 0xbfd00000 + SMBUS_HOST_CONTROL
    lbu v0, 0(v1)
    ori v0, v0, 0x40
    sb  v0, 0(v1);

    /* wait */
    li  v1, 0xbfd00000 + SMBUS_HOST_STATUS
    li  a1, 0x1000
1:
#if 1
    /* delay */
    li a0, 0x1000    
2:            
    bnez    a0,2b
    addiu    a0, -1
#endif
    addiu    a1, -1
    beqz    a1, 1f
    nop

    lbu  v0, 0(v1)
    andi v0, SMBUS_HOST_STATUS_BUSY
    bnez  v0, 1b  #IDEL ?
    nop
上面代码是查看总线是否在忙状态。
1:
    li  v1, 0xbfd00000 + SMBUS_HOST_STATUS
    lbu v0, 0(v1)
    andi v0,v0, 0x1f
    beqz  v0,1f
    nop
    sb  v0, 0(v1)   #reset
    lbu v0, 0(v1)   #flush the write
1:

    li  v1, 0xbfd00000 + SMBUS_HOST_DATA0
    lbu  v0, 0(v1)

    jr    ra
    nop
上面代码是已经把命令成功发送出去,然后成功地读取回来数据,保存在v0寄存里。

通过上面的子函数,就可以通过I2C总线去读取内存条上的EEPROM参数,以便后面进行内存初始化。
在这里第一次读取是第一个字节。

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License