现在的位置: 首页 > 综合 > 正文

omap 3530 boot i2c 调试笔记

2013年04月06日 ⁄ 综合 ⁄ 共 7508字 ⁄ 字号 评论关闭

1,板子时omap的,外设TPS65930的控制接口是I2C的。借此机会调试一下I2C的功能。如果有什么地方没有分析周到的,还请大家指正。

I2C的驱动接触过,但是都是在u-boot或是在“裸奔”的环境中调试。这样的环境往往更注重硬件的特性,以及硬件的功能。软件架构相对较为简单。但是“简单”并不是说容易。想要单靠一些简单的设备,比如示波器,稳压电源,赤手空拳的写出最精简的驱动,未必是一件容易的事情。从脉宽调整,到双跟踪示波器的使用,比较datasheet的数据,波形分析等等,都是烦人的事情。因此拿到代码,还是先庆幸一下,带着学习的态度去看代码,调式开发板子。

前面说了Linux的驱动(对硬件寄存器操作)很少。更多的是系统为设备驱动提供的调用接口,以及在系统下,被linux所管理的逻辑。这样的行为用代码描述,为系统所识别往往更加重要,层次多,代码杂。

现在有一个很不好的习惯,做笔记,交流,动不动的就是“xxx架构”。其实“架构”这个字眼还真的是最好的解释了项目的管理以及应用的流程最好的产物。确实好的项目,在开发的同时也是层次划分清晰,功能比较明确的。这样的项目称之为“架构”。在这里非常想值得一提的事情:代码的风格与编写的习惯,以及对操作系统的了解等等。这些因素都会为今后的看代码,写代码造成很大的影响。不承认已有的linux的“xxx架构”是最合理的,但是就目前来看,还没有哪个组织可以颠覆它的风格。或是维护的习惯。久而久之也就默认了。相信Linux的风格和结构是科学的,经的起事件的考验。

其实看过很多企业级别的大型软件项目,刚接触的时候,总是带着疑问,怀疑别人的代码。但还是久而久之,慢慢的真正的了解了设计的用意以及以意图,也会慢慢的适应。在这里由于“xxx架构”引出了一段废话,还请谅解。但是真的还是要静下心来,仔细地阅读,以及体会。

2,硬件介绍

    在开始之前还是先要看看I2C的硬件描述。

                                                                                                                             图 1

图 1 中只列出了I2C1-I2C3。其实关于I2C的硬件接口,或是被用作I2C的硬件接口不只三个。很多GPIO的pin也可以用作I2C。但是这里没有列出。

Function架构中描述了SCCB这样的一个输出,有别于I2C的spec定义。这里我也是第一次看到,不知道我的板子上有没有用到这个输出的信号。除了这个意外就没有什么特别的地方。

具体的读写时序以及要求可以看看I2C的spec。完全兼容的硬件方案。

 

I2C数据传输方式(无中断模式):

数据传输

    1,地址传输

在I2C的数据线上产生了发送标志以后,写入I2Ci.I2C_SA[9:0] SA 寄存器的slave地址将会被自动通过总线发送出去。之后硬件将会把R_Wn (I2C_CON )bit:0 设置为“0”。同时外设slave的应答位也会马上传送给I2Ci模块。

 

2,数据传输

每一次当数据(data)将要被传输时, I2Ci.I2C_STAT[4] XRDY将会被I2Ci模块置为1。当传输时,data寄存器一定要有数据,并且这个寄存器I2Ci.I2C_STAT[4] XRDY必须设置为“1”。

3,多少data的发送取决于I2Ci.I2C_CNT[15:0] DCOUNT 的值。

 

备注:

(1),只要I2Ci.I2C_CON[0] STT bit 被设置为“1”,开始位(start)就会在I2Ci总线上产生。同时这个开始位会在传输完毕后自动清零。

(2),在每一次传送完毕以后,I2Ci.I2C_CON[1] STP bit 这个位自动被清除为“0”。

 

    看了spec的描述大致和之前接触过的I2C设备相似。关键还是结合I2C的数据传送的格式(FORMATE)。

                                                                                                                              图 2

I2C数据传送的格式如上图所示,记得第一次调试SPI外设的时候看了SPI总线的协议。有了错觉,认为只要是data的内容就一定是外设的数据。后来仔细读了datasheet后发现,原来外设可以利用data的数据内容作为外部设备的寄存地址。

    回到硬件分析。S和 P标志很重要,标志着data传输的开始和结束。结合上面的寄存器分析就可以知道,如何产生S位,通过设置I2Ci.I2C_SA写入寻址的外设地址再同过I2C_CON寄存器。如何产生P位,很可惜,spec中没有找到。但是看了代码,以及之前相似的硬件应用来分析。看了I2C的spec,原来I2C的报文只有这两种模式。Repeat Start以及without Repeat Start。

有两个寄存器来控制。一个是count一个是data。(这里的分析还没有达到实践的成果,没有双踪示波器,只是停留于理论。大家觉得分析的不对,还请指正)。Count寄存器的意义在于告诉硬件到底有多少个data要发送。应为I2C的frame是8bits,data寄存器的buffer容量也就是8bits。当然可以让buffer支持更大的容量。比如10bits等。Count寄存器中的值也就是表示了有多少个单位容量的data要发送,这里其实是一个硬件计数器的使用。当这个硬件计数器的值为0的时候。就会自动地在总线上产生P位,表示结束传输。在后面的调试种会结合代码阐释这一点。

 

    关于中断,也是非常可惜。应为u-boot中没有中断。一切的数据有效性均有软件配合寄存器的状态和延时轮循来满足。但是I2C的模块确实提供了中断通道。中断时和FIFO的阀数值相关。在kernel中分析。这篇文章主要还是看看在没有中断的情况下,分析一下硬件的特性和寄存器的操作控制。

 

   

3,软件实现

宏定义:

#define inb(a)        __raw_readb(i2c_base + (a))

#define outb(v,a)     __raw_writeb((v), (i2c_base + (a)))

#define inw(a)        __raw_readw(i2c_base +(a))

#define outw(v,a)     __raw_writew((v), (i2c_base + (a)))

其中i2c_base的定义可以查看一下。具体的数值就是datasheet上出现的外设io的地址。在u-boot模式下,一切都是实地址模式,不用转化。宏定义的作用方便代码编写,增加可读性。

 

I2C初始化函数:

void i2c_init (int speed, int slaveadd)

{

      u16 scl;

 

      outw(0x2, I2C_SYSC); /* for ES2 after soft reset */

      udelay(1000);

      outw(0x0, I2C_SYSC); /* will probably self clear but */

      /*

      I2C_SYSC寄存器主要涉及的idle和clock的管理,原应不详,设置为idle模式后又设置为disable模式。

*/

      if (inw (I2C_CON) & I2C_CON_EN) {

           outw (0, I2C_CON);

           udelay (50000);

      }

      /*

      I2C_CON寄存器涉及的是I2C的工作模式。这里设置了0

*/

      /* 12Mhz I2C module clock */

      outw (0, I2C_PSC);

      speed = speed/1000;             /* 100 or 400 */

      scl = ((12000/(speed*2)) - 7);    /* use 7 when PSC = 0 */

      outw (scl, I2C_SCLL);

      outw (scl, I2C_SCLH);

      /* own address */

      outw (slaveadd, I2C_OA);

      outw (I2C_CON_EN, I2C_CON);

 

      /* have to enable intrrupts or OMAP i2c module doesn't work */

      outw (I2C_IE_XRDY_IE | I2C_IE_RRDY_IE | I2C_IE_ARDY_IE |

            I2C_IE_NACK_IE | I2C_IE_AL_IE, I2C_IE);

      udelay (1000);

      flush_fifo();

      outw (0xFFFF, I2C_STAT);

      outw (0, I2C_CNT);

}

    在u-boot的代码中的数据发送,接受都没有中断的概念。这样的机制也体现在代码的逻辑上。

发送函数:

 

static int i2c_write_byte (u8 devaddr, u8 regoffset, u8 value)

{

  int i2c_error = 0;

  u16 status, stat;

 

  /* wait until bus not busy

可以查看着个函数的实现,关键代码如:

while ((stat = inw (I2C_STAT) & I2C_STAT_BB) && timeout--) {

           outw (stat, I2C_STAT);

           udelay (50000);

     }

把stat寄存器的值读出来,和bb位(BUS BUSY)相比较。如果寄存器的bb位发生了变化,那么说明总线现在的状况处于空闲。这个位代表了总线的工作逻辑,有硬件自动完成。

然后短暂延时,再次判断。

*/

  wait_for_bb ();

 

  /* two bytes

向count寄存器写入数值,代表了要发送的数据有几个byte。操作过程也是和bb一样,向代表数值得一个寄存器中写入具体数值。

如果这个寄存器中写入的实际发送数值大于实际要发送的数据(每8bit一个单位)

:Time out

如果这个寄存器中写入的实际发送数值小于实际要发送的数据(每8bit一个单位)

:I/O Error

*/

  outw (2, I2C_CNT);

  /* set slave address

向SA寄存器写入数据,这里面的数据代表了外设的地址。而不是数据本身。同时这样也在总线上产生了S位。

*/

  outw (devaddr, I2C_SA);

  /* stop bit needed here

写控制寄存器,要求硬件的控制行为。注意这个时候其实i2c的dat clk总线上已经有了数据的串行移动。移动的内容就是SA寄存器中的内容。不是data寄存器的内容。

*/

  outw (I2C_CON_EN | I2C_CON_MST | I2C_CON_STT | I2C_CON_TRX |

        I2C_CON_STP, I2C_CON);

 

  /* wait until state change

do {

          udelay (1000);

          status = inw (I2C_STAT);

    } while (  !(status &

             (I2C_STAT_ROVR | I2C_STAT_XUDF | I2C_STAT_XRDY |

              I2C_STAT_RRDY | I2C_STAT_ARDY | I2C_STAT_NACK |

              I2C_STAT_AL)) && timeout--);

再次读取stat寄存器的数值,但是这里的判断比之前bb位来说,条件放宽了。因为只要是代表的数据发送成功的位一旦被硬件设置以后就可以认为发送成功了,那么紧接着这样的数据判断,第二个数据就可以发送了。这里没有种断。要是在kernel的代码。判断是会放在中断的过程中吗?不是很确定。如果在中断中判断慢速外设的状态是否会影响中断的效率?

Uboot中没有interrupt的概念。只有poll轮询的概念,在这里就是监视stat寄存器的变化,每一种变化都代表了一种硬件的行为的发生。

*/

  status = wait_for_pin ();

 

  if (status & I2C_STAT_XRDY) {

#if defined(CONFIG_OMAP243X) || defined(CONFIG_OMAP34XX)

                /* send out 1 byte

Data寄存器开始亮相,但是这个寄存器的内容又是什么,那就要看上图 2 中的数据包格式了。其实为什么计数寄存器中的数值时 “2”。也是有原因的,应为2个数值,第一个代表了外设偏移,第二个代表了具体寄存器中的数值。

*/

                outb(regoffset, I2C_DATA);

                    /*

                      问题来了,看了寄存器的描述:

Transmit Data Ready IRQ status. Set to 1 in transmit mode when new data is requested RW 0

for transmission. When this bit is set to 1, an interrupt is signaled to MPU subsystem.

原来是中断发送的。和之前没有中断的理解有出入,那么是什么原因呢?不从得知,但是如果真的是触发中断的话,岂不是去掉了这样的发送函数就不会发送了。

在看看data寄存器的描述:

Poll the I2Ci.I2C_STAT[4] XRDY bit,

or use the XRDY interrupt (the I2Ci.I2C_IE[4] XRDY_IE bit must be

set to 1)

or the DMA TX channel (the I2Ci.I2C_BUF[7] XDMA_EN bit must be set to 1) to write data to the I2Ci.I2C_DATA register.

有三种发送的方式:

1,poll XRDY bit。

2,poll XRDY bit,同时使用XRDY_IE中断控制寄存器的发送位。

3,最后一种方式,也就是DMA。设置相关的寄存器值。

在boot中没有中断,那么只要满足1方式就可以了。一但读出XRDY位,那么数据就表示数据已经发送。在没有中断的模式下,SRDY_IE和XDMA_EN没有被使用的情况下,不会有中断。但是数据依旧可以发送出去。

不需要设置任何寄存器,只要data寄存器中有数据。其实,在data寄存器中有数据后,data line中就会出现串行信号。

4,是否判断数据已近发送完毕,除了判断一些状态寄存器之外还有一个很重要的判断,就是计数(CNT)寄存器。在上上面的文章中已经贴出了由于不同的设置导致的两种错误结果。

*/

                outw(I2C_STAT_XRDY, I2C_STAT);

                /*

相应的状态位清零。这个位XDRY,软件的作用就是:设 1,清除的动作。

硬件会把它设置为 1,代表data可以写入data寄存器了。

这里只是软件的设置,是由软件开发人员故意而为之,确保了数据写入的有效性,硬件的有效性有硬件保证。软件在介入时候,必要的清除一下。

*/

                /*

                  再次等待,准备把数据发送。

*/

                status = wait_for_pin();

                if ((status & I2C_STAT_XRDY)) {

                        /* send out next 1 byte */

                        outb(value, I2C_DATA);

                        outw(I2C_STAT_XRDY, I2C_STAT);

                } else {

                        i2c_error = 1;

                }

                /*

                      注意两次发送的数据:

                第一次是地址 regoffset

                      第二次是数据 value

*/

#else

                /* send out two bytes */

                outw ((value << 8) + regoffset, I2C_DATA);

#endif

 

       /* must have enough delay to allow BB bit to go low */

       udelay (50000);

       if (inw (I2C_STAT) & I2C_STAT_NACK) {

             i2c_error = 1;

       }

  } else {

       i2c_error = 1;

  }

  /*

   如果发送不成功,那么再做一下后续的工作。

*/

  if (!i2c_error) {

       int eout = 200;

       outw (I2C_CON_EN, I2C_CON);

       while ((stat = inw (I2C_STAT)) || (inw (I2C_CON) & I2C_CON_MST)) {

             udelay (1000);

             /* have to read to clear intrrupt */

             outw (0xFFFF, I2C_STAT);

             if(--eout == 0) /* better leave with error than hang */

                  break;

       }

  }

  flush_fifo(); //清除数据寄存器中的数据

  outw (0xFFFF, I2C_STAT);//恢复初始状态

  outw (0, I2C_CNT);

  return i2c_error;

}

    接受数据的函数是read类型,大致的做法与发送类似,还是沿袭了发送的API加上自己的poll机制和延时。不再复述。

 

抱歉!评论已关闭.