I2C的应用实例一: 利用i2c读取SPD的信息
下面以龙芯BIOS为例,介绍如何利用I2C读取内存条上的SPD信息。
1. 硬件连接
龙芯内部的i2c控制器包括时钟发生器、字节命令控制器、状态寄存器、发送寄存器、接收寄存器,结构如下图:
主要的寄存器介绍如下:
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读写的流程来处理这些差异。