免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 8895 | 回复: 4
打印 上一主题 下一主题

IIC总线数据传输流程分析 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2009-03-13 19:14 |只看该作者 |倒序浏览
这些天研究IIC 总线,得到论坛很多大虾的帮助,谢谢你们的无私,才让我们这样的新手能得到提高. 本着班门弄斧的精神,把自己的学习经验共享出来,以供更多的新手能够少走一些弯路. 里面可能也会有错误,希望指正.谢谢. 别笑我哈,我是新手.呵呵

因为I2C.DEV 中的IOCTL 函数就能完成IIC总线的读写数据,因此
首先分析I2C.DEV 中的 ioctl 函数,
=========两个重要的结构体==============
struct i2c_msg {
 __u16 addr; /* slave address   */
 __u16 flags;
#define I2C_M_TEN 0x10 /* we have a ten bit chip address */
#define I2C_M_RD 0x01
#define I2C_M_NOSTART 0x4000
#define I2C_M_REV_DIR_ADDR 0x2000
#define I2C_M_IGNORE_NAK 0x1000
#define I2C_M_NO_RD_ACK  0x0800
#define I2C_M_RECV_LEN  0x0400 /* length will be first received byte */
 __u16 len;  /* msg length    */
 __u8 *buf;  /* pointer to msg data   */
};
 
struct i2c_rdwr_ioctl_data {
 struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
 __u32 nmsgs;   /* number of i2c_msgs */
};

====================================
case I2C_RDWR:
  if (copy_from_user(&rdwr_arg,
       (struct i2c_rdwr_ioctl_data __user *)arg,
       sizeof(rdwr_arg)))   // arg 传递过来的是地址,强制转换为结构体类型指针

   return -EFAULT;
  /* Put an arbitrary limit on the number of messages that can
   * be sent at once */

  if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
   return -EINVAL;
  rdwr_pa = (struct i2c_msg *)
   kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg),
   GFP_KERNEL);
  if (rdwr_pa == NULL) return -ENOMEM;
  if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
       rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {   
   kfree(rdwr_pa);
   return -EFAULT;
  }    // 第一个copy,已经拷贝arg->msgs指针到rdwr_arg的msgs指针,这里的COPY会把msgs指向的内容拷贝到rdwr_pa中,同时rdwr_pa->buf 指针指向用户空间msgs->buf 的指针

  data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
  if (data_ptrs == NULL) {
   kfree(rdwr_pa);
   return -ENOMEM;
  }
  res = 0;
&nbsp;&nbsp;for( i=0; i<rdwr_arg.nmsgs; i++ ) {
&nbsp;&nbsp;&nbsp;/* Limit the size of the message to a sane amount */
&nbsp;&nbsp;&nbsp;if (rdwr_pa[i].len > 8192) {
&nbsp;&nbsp;&nbsp;&nbsp;res = -EINVAL;
&nbsp;&nbsp;&nbsp;&nbsp;break;
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;  
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// data_prts 指向 RDWR_pa.buf,即指向用户空间穿过来的buf中数据缓冲区

&nbsp;&nbsp;&nbsp;rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);//重新分配buf

&nbsp;&nbsp;&nbsp;if(rdwr_pa[i].buf == NULL) {
&nbsp;&nbsp;&nbsp;&nbsp;res = -ENOMEM;
&nbsp;&nbsp;&nbsp;&nbsp;break;
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;if(copy_from_user(rdwr_pa[i].buf,
&nbsp;&nbsp;&nbsp;&nbsp;data_ptrs[i],               // 把用户空间数据拷贝到 rdwr_pa.buf 中

&nbsp;&nbsp;&nbsp;&nbsp;rdwr_pa[i].len)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;++i; /* Needs to be kfreed too */
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = -EFAULT;
&nbsp;&nbsp;&nbsp;&nbsp;break;
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
&nbsp;&nbsp;if (res < 0) {
&nbsp;&nbsp;&nbsp;int j;
&nbsp;&nbsp;&nbsp;for (j = 0; j < i; ++j)
&nbsp;&nbsp;&nbsp;&nbsp;kfree(rdwr_pa[j].buf);
&nbsp;&nbsp;&nbsp;kfree(data_ptrs);
&nbsp;&nbsp;&nbsp;kfree(rdwr_pa);
&nbsp;&nbsp;&nbsp;return res;
&nbsp;&nbsp;}
&nbsp;&nbsp;res = i2c_transfer(client->adapter, // 最终会调用s3c24xx_i2c_xfer函数

&nbsp;&nbsp;&nbsp;rdwr_pa,
&nbsp;&nbsp;&nbsp;rdwr_arg.nmsgs);    // 拷贝完毕,开始发送

&nbsp;&nbsp;while(i-- > 0) {
&nbsp;&nbsp;&nbsp;if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {
&nbsp;&nbsp;&nbsp;&nbsp;if(copy_to_user(
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;data_ptrs[i],
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rdwr_pa[i].buf,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;rdwr_pa[i].len)) {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;res = -EFAULT;   // 如果为读,把数据拷贝到用户空间

&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;kfree(rdwr_pa[i].buf);
&nbsp;&nbsp;}
&nbsp;&nbsp;kfree(data_ptrs);
&nbsp;&nbsp;kfree(rdwr_pa);
&nbsp;&nbsp;return res;
&nbsp;

=============下面分析i2c_s3c2410.c的传输过程=========
启动s3c24xx_i2c_message_start函数开始传输,
在这个函数里面会首先发送从器件地址
addr = (msg->addr & 0x7f) << 1;    // 我的24C02地址为0xa0,此处msg->addr=0x50
if (msg->flags & I2C_M_RD) {
  stat |= S3C2410_IICSTAT_MASTER_RX;
  addr |= 1;
} else
  stat |= S3C2410_IICSTAT_MASTER_TX;
writeb(addr, i2c->regs + S3C2410_IICDS);

发送完后,会进入中断s3c24xx_i2c_irq中,处理中断标志后,进入发送下一个字节i2s_s3c_irq_nextbyte函数,此函数非常重要,几乎所有的功能在此完成

进入s3c24xx_i2c_doxfer函数的时候,会设置status=STATE_START,
因此,i2s_s3c_irq_nextbyte 函数首先分析 case state_start
if (i2c->msg->flags & I2C_M_RD)
&nbsp;&nbsp;&nbsp;i2c->state = STATE_READ;
&nbsp;&nbsp;else
&nbsp;&nbsp;&nbsp;i2c->state = STATE_WRITE;   // 判断为读还是写

&nbsp;&nbsp;
&nbsp;&nbsp;if (is_lastmsg(i2c) && i2c->msg->len == 0) { //没有消息,或者长度为0,停止总线

&nbsp;&nbsp;&nbsp;s3c24xx_i2c_stop(i2c, 0);
&nbsp;&nbsp;&nbsp;goto out_ack;
&nbsp;&nbsp;}
&nbsp;&nbsp;if (i2c->state == STATE_READ)    // 若是读,直接跳到读命令分支

&nbsp;&nbsp;&nbsp;goto prepare_read;
&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 否则,进入写分支

&nbsp;case STATE_WRITE:
&nbsp;&nbsp;retry_write:
&nbsp;&nbsp;if (!is_msgend(i2c)) {         // 判断是否单个消息中的发送内容msg->buf已经传输完毕

&nbsp;&nbsp;&nbsp;byte = i2c->msg->buf[i2c->msg_ptr++];  // 发送下一个内容

&nbsp;&nbsp;&nbsp;writeb(byte, i2c->regs + S3C2410_IICDS);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ndelay(i2c->tx_setup);
&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;else if (!is_lastmsg(i2c))  // 判断多个消息msgs是否已经结束 了


&nbsp;{
&nbsp;&nbsp;&nbsp;/* we need to go to the next i2c message */
&nbsp;&nbsp;&nbsp;dev_dbg(i2c->dev, "WRITE: Next Message\n");
&nbsp;&nbsp;&nbsp;i2c->msg_ptr = 0;
&nbsp;&nbsp;&nbsp;i2c->msg_idx ++;  
&nbsp;&nbsp;&nbsp;i2c->msg++;                // 指向下一个消息

&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;/* check to see if we need to do another message */
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (i2c->msg->flags & I2C_M_NOSTART)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{                           // 不需要重新启动,注释说不支持此模式

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (i2c->msg->flags & I2C_M_RD)
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s3c24xx_i2c_stop(i2c, -EINVAL);
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;goto retry_write;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{                                    
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;s3c24xx_i2c_message_start(i2c, i2c->msg); // 发送下条消息,重新启动,发送从机地址

&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;i2c->state = STATE_START;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}
&nbsp;&nbsp;}
&nbsp;&nbsp;else
&nbsp;&nbsp;{                             // 全部发送完成,结束

&nbsp;&nbsp;&nbsp;/* send stop */
&nbsp;&nbsp;&nbsp;s3c24xx_i2c_stop(i2c, 0);
&nbsp;&nbsp;}
&nbsp;&nbsp;break;

case STATE_READ: 读部分,比较简单就不分析了

就这样不停的在中断函数s3c24xx_i2c_irq里面跳啊跳,又跳到i2s_s3c_irq_nextbyte
最后到了结束的时候,s3c24xx_i2c_stop调用s3c24xx_i2c_master_complete函数,
接着使 i2c->msg_num = 0 唤醒睡眠

s3c24xx_i2c_doxfer函数中
timeout = wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); 返回,
整个传输完成.

[ 本帖最后由 dreamice 于 2009-3-13 21:50 编辑 ]

评分

参与人数 2可用积分 +30 收起 理由
T-bagwell + 15 精品文章
yidou + 15 Good

查看全部评分

论坛徽章:
5
2 [报告]
发表于 2009-03-13 21:09 |只看该作者
我以为是协议分析呢, 你这个是Linux下I2C驱动和应用程序分析。多谢共享.

[ 本帖最后由 yidou 于 2009-3-13 21:11 编辑 ]

论坛徽章:
3
金牛座
日期:2014-06-14 22:04:062015年辞旧岁徽章
日期:2015-03-03 16:54:152015年迎新春徽章
日期:2015-03-04 09:49:45
3 [报告]
发表于 2009-03-13 21:50 |只看该作者
写得不错,呵呵

论坛徽章:
0
4 [报告]
发表于 2010-01-06 09:46 |只看该作者
正在弄iic,头晕目眩的,呜呜,谢谢楼主~~

论坛徽章:
0
5 [报告]
发表于 2010-03-11 18:59 |只看该作者
&nbsp;&nbsp为什么这么多这个符号呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP