I2C的应用实例一: 利用i2c读取SPD的信息

下面以龙芯BIOS为例,介绍如何利用I2C读取内存条上的SPD信息。
1. 硬件连接
龙芯内部的i2c控制器包括时钟发生器、字节命令控制器、状态寄存器、发送寄存器、接收寄存器,结构如下图:
 wKiom1YqED3zY94zAADMVttYhXI379.jpg

主要的寄存器介绍如下:
 wKioL1YqEIOAkQPCAAEo34do7rE662.jpg

wKiom1YqEGbSowk8AAEFu-W3Xxg957.jpg

2. 利用i2c读取内存SPD信息
以读取芯片类型(DDR2,DDR3,DDR4)为例子,根据SPD规范,SPD偏移为2的寄存器表示DIMM Type,如果它的值是0x08,就表示DDR2,如果值为0xb则表示DDR3。
通过上面的寄存器说明,结合i2c读写的时序河代码,我们可以写出如下进行i2c读写的流程:
1.初始化控制寄存器;
2.查询状态寄存器,直至TIP=0(表示i2c控制器空闲);
3.发送地址或者偏移到TX寄存器;
4.设置命令寄存器的START bit和读bit,启动传输;
5.查询状态寄存器直到TIP 和busy bit 都为0,读取接收寄存器。
实现的过程如下:

    dli     a1, 2
    GET_I2C_NODE_ID_a2
  

*i2c_send_b*/              
    /* load device address to write reg offset */
    andi    v1, a0, 0xfe        
    li  v0, H2LS_I2C0_TXR_REG   
    sb  v1, 0x0(v0)     

    /* send start frame */
    li  v1, CR_START | CR_WRITE
    li  v0, H2LS_I2C0_CR_REG        
    sb  v1, 0x0(v0)     

    /* waite send finished */
//  i2c_wait_tip            
    li  v0, H2LS_I2C0_SR_REG    
1:                      
    lb  v1, 0x0(v0)     
    andi    v1, v1, SR_TIP      
    bnez    v1, 1b          
    nop
    
    /* send data to be written */
    move    v1, a1          
    li  v0, H2LS_I2C0_TXR_REG   
    sb  v1, 0x0(v0)     

    /* send data frame */
    li  v1, CR_WRITE        
    li  v0, H2LS_I2C0_CR_REG        
    sb  v1, 0x0(v0)     

    /* waite send finished */
//  i2c_wait_tip            
    li  v0, H2LS_I2C0_SR_REG    
1:                      
    lb  v1, 0x0(v0)     
    andi    v1, v1, SR_TIP      
    bnez    v1, 1b           
  nop

/* i2c_read_b */                   
    /* load device address */
    ori v1, a0, 0x1
    li  v0, H2LS_I2C0_TXR_REG   
    sb  v1, 0x0(v0)     
    
    /* send start frame */
    li  v1, CR_START | CR_WRITE
    li  v0, H2LS_I2C0_CR_REG        
    sb  v1, 0x0(v0)     

    /* waite send finished */
//  i2c_wait_tip            
    li  v0, H2LS_I2C0_SR_REG    
1:                      
    lb  v1, 0x0(v0)     
    andi    v1, v1, SR_TIP      
    bnez    v1, 1b          
    nop
    
    /* receive data to fifo */
    li  v1, CR_READ | CR_ACK    
    li  v0, H2LS_I2C0_CR_REG        
    sb  v1, 0x0(v0)     

//  i2c_wait_tip            
    li  v0, H2LS_I2C0_SR_REG    
1:                      
    lb  v1, 0x0(v0)     
    andi    v1, v1, SR_TIP      
    bnez    v1, 1b          
    nop

    /* read data from fifo */
    li  v0, H2LS_I2C0_RXR_REG                                                                                                                                          
                                 
   lb  a1, 0x0(v0)

/* i2c_stop */              
    /* free i2c bus */
    li  v0, H2LS_I2C0_CR_REG        
    li  v1, CR_STOP     
    sb  v1, 0x0(v0)     
1:                  
    li  v0, H2LS_I2C0_SR_REG        
    lb  v1, 0x0(v0)     
    andi    v1, v1, SR_BUSY     
    bnez    v1, 1b          
    nop             
    
    move    v0, a1

    jr  ra
    nop
END(i2cread)


( 1.写寄存器偏移到SPD;2.读指定偏移的SPD。其中上面的每个步骤都可以再分解成两步:1.写地址到TX 寄存器,start,直到TIP变成0;2.用要读或者写的寄存器偏移填充TX,start,直到TIP变成0 。当然最后是通过清除状态寄存器的busy bit来设置当前控制器从busy状态转移到free状态)。


实现了基本的i2cread读写函数之后,就可以利用它来读取SPD上任意偏移寄存器的信息了,下面摘取了一部分代码:
 bal     i2cread
    nop  
    //only bit[7:0] used
    andi    v0, v0, 0xff
    /* v0 should be 0xb or 0x8,else error DIMM type */
    dli     a1, 0x08
    beq     v0, a1, DDR2
    nop  
    dli     a1, 0x0B
    beq     v0, a1, DDR3
    nop  
================
上面的代码主要是调用i2cread函数去访问SPD.

I2C的应用实例二: 利用Integrated SMBus 读取HasWell SPD
和龙芯利用自己的i2c控制器读取SPD信息类似,Intel的HasWell处理器芯片系列也提供了SMBus来访问SPD。 HasWell芯片提供了下面的几个寄存器来专门读取SPD和TSOD的信息:
smb_stat: 重要的位段有
smb_rdo:表示在一次SMBus Read命令执行完成后,这个寄存器的接收数据位段保存读到的数据是有效的
smb_wod:写操作完成
smb_busy:表示当前有i2c或者SMBus的命令正在总线上执行
tsod_sa:保存有上次执行的读取TSOD指令的从地址
smb_rdata:当smb_rdo为1时,这些位段表示接收到的有效数据
smb_cmd: 重要的位段有
smb_cmd_trigger:设置这个bit为1之后,才来出发smbus发送命令
smb_word_access:控制是按照byte还是word来访问device
smb_wrt_cmd:smbus读写控制选择控制的位段
smb_sa:slave address,这个位段决定要访问的SPD或者TSOD
smb_ba:要访问的设备的bus地址
smb_wdata:缓存要通过SPDW指令写入的数据
smb_cntl:重要的位段有
smb_dti (bit31~bit28):指定访问的是SPD还是TSOD,如果访问的是SPD,需要把这个位段设置成2'b1010
smb_ckovrd: 以防任何等待的事务出现,或者使SMbus芯片从hang状态中解放出来
smb_soft_rst: 软件重启smb 控制器,用来终止所有之前还有待进行的传输事务,软件需要,和smb_ckovrd结合通过设置smb_ckovrd=0 且smb_soft_rst=1,维持35ms,来使得smb 控制器恢复空闲状态。
smb_rst_on_forcest: 控制是否让force self reset 信号重启smb 控制器。
了解了上述的寄存器的功能和 用法之后,我们就不难整理出基于HasWell处理器的读取SPD的步骤:
初始化SMbus控制器:利用smb_cntl寄存器中的smb_soft_rst和smb_ckovrd字段来重启smbus控制器;
设置要读取的SPD的i2c 总线地址和从地址、设置按照byte访问、访问模式是写入,往smb_wdata里写入要访问SPD上的寄存器的偏移;
把smb_cmd_trigger设置成1,发送smbus command;
查询状态寄存器的smb_wod 和smb_busy bit,直至它们表示写操作已经完成;
设置要读取的SPD的i2c 总线地址和从地址、设置按照byte访问、访问模式是读出;
把smb_cmd_trigger设置成1,发送smbus command;
查询状态寄存器的smb_rdo 和smb_busy bit,直至它们表示读操作已经完成;
读取状态寄存器中的smb_rdata字段,返回给上层程序。

通过总结HasWell和龙芯访问SPD信息的步骤,我们可以发现控制的流程是相同的,区别就在于不同的Smbus/i2c控制器里相关寄存器的实现不一样,这就要求工程师一定要根据具体的芯片手册,结合I2C/Smubus读写的流程来处理这些差异。