Linux2.6.37 I2C驱动框架分析(五)

来源:互联网 发布:淘宝卖家怎么进入 编辑:程序博客网 时间:2024/06/08 02:05
   虽然时间不早了,但是还是检查再写点内容,争取早日写完这部分的内容,。看了不少的Linux驱动,其实任何Linux驱动都可以分为两部分:一部分是所谓的Linux驱动框架,前面分析的那些就是Linux驱动框架里的东西,另一部分就是硬件相关的东西,这部分内容就需要对硬件有一定的了解,其实这部分的内容跟裸机驱动没什么区别。比如i2c硬件相关的知识,s3c2440a硬件相关的东西,这些都是从datasheet就可以知道的。下面就来分析硬件相关的i2c适配器驱动(也称为i2c总线驱动),在/driver/i2c/busses/i2-s3c2410.c就是s3c2440的i2c总线驱动,下面就来仔细分析这个驱动吧!!

  /driver/i2c/busses/i2c-s3c2410.c:

  入口:

  i2c_adap_s3c_init(void)

    platform_driver_register(&s3c24xx_i2c_driver);  注册平台驱动s3c24xx_i2c_driver
  static struct platform_driver s3c24xx_i2c_driver = {
     .probe  = s3c24xx_i2c_probe,
     .remove  = s3c24xx_i2c_remove,
     .id_table = s3c24xx_driver_ids,
     .driver  = {
       .owner = THIS_MODULE,
       .name = "s3c-i2c",
       .pm = S3C24XX_DEV_PM_OPS,
     },
  };

  当找到匹配的平台设备时,平台驱动的probe方法就会被调用,直接进入s3c24xx_i2c_probe函数的分析。

s3c24xx_i2c_probe(struct platform_device *pdev)

  pdata = pdev->dev.platform_data; 获取平台设备的平台数据

  i2c = kzalloc(sizeof(struct s3c24xx_i2c), GFP_KERNEL); 分配一个s3c24xx_i2c空间,s3c24xx_i2c是对i2c_adapter结构进一步的封装

  strlcpy(i2c->adap.name, "s3c2410-i2c", sizeof(i2c->adap.name)); 设置适配器的名字

  i2c->adap.owner   = THIS_MODULE; 
  i2c->adap.algo    = &s3c24xx_i2c_algorithm;   设置适配器的总线通信方法,核心
  i2c->adap.retries = 2;        适配器重复的次数,一般用于没反映的情况
  i2c->adap.class   = I2C_CLASS_HWMON | I2C_CLASS_SPD;
  i2c->tx_setup     = 50;      建立时间

  i2c->clk = clk_get(&pdev->dev, "i2c");  获取I2C模块的时钟

  clk_enable(i2c->clk);  使能该模块的时钟

 

  res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 获取平台设备I/O内存资源,就是一些寄存器

  i2c->ioarea = request_mem_region(res->start, resource_size(res),pdev->name); 对该I/O内存进行检查是否可用

  i2c->regs = ioremap(res->start, resource_size(res)); 将I/O内存映射到内核空间,就可以操作这些寄存器了

  ret = s3c24xx_i2c_init(i2c);  使能i2c硬件模块

  i2c->irq = ret = platform_get_irq(pdev, 0); 获取平台设备中的中断资源,中断号

  request_irq(i2c->irq, s3c24xx_i2c_irq, IRQF_DISABLED,dev_name(&pdev->dev), i2c); 注册中断处理函数

  ret = s3c24xx_i2c_register_cpufreq(i2c); 设置i2c模块的工作时钟

  i2c->adap.nr = pdata->bus_num;  看来总线号是在平台设备中写死的哦

  ret = i2c_add_numbered_adapter(&i2c->adap); 注册该适配器

  这里就没有细致的去分析每个函数的实现了,如果你有Datesheet的话应该一步一步就能看懂,没什么大不了的,probe工作:

 1.从平台设备中获取平台资源,对于寄存器资源将其进行映射,对于中断资源,注册相应的中断的处理函数

 2.获取时钟,使能时钟,设置工作时钟

 3.向系统注册这个i2c_adapter

通过前面的学习,我们知道一个i2c总线驱动最重要的就是总线驱动提供的通信方法,对于S3C2440的总线通信方法在s3c24xx_i2c_algorithm中实现,还有需要关注的就是中断处理函数了。

static const struct i2c_algorithm s3c24xx_i2c_algorithm = {
   .master_xfer  = s3c24xx_i2c_xfer,  总线的通信方法
   .functionality  = s3c24xx_i2c_func, 总线支持的功能
};

重点分析s3c24xx_i2c_xfer和s3c24xx_i2c_irq。

s3c24xx_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num) 都是以i2c消息的形式进行传输的

  s3c24xx_i2c_doxfer(i2c, msgs, num);  最终还是调用了该函数啊

    ret = s3c24xx_i2c_set_master(i2c);  设置I2C模块为主机模式,貌似Linux I2C适配器驱动不支持从机模式哦

    i2c->msg     = msgs;  指向i2c_mgs
    i2c->msg_num = num;   消息的个数
    i2c->msg_ptr = 0;    
    i2c->msg_idx = 0;
    i2c->state   = STATE_START;  适配器的状态为发送S信号的状态

    s3c24xx_i2c_enable_irq(i2c);  使能I2C模块的中断

    timeout = s3c24xx_i2c_message_start(i2c, msgs);  开始发送这些消息了,这个函数比较重要,等下分析

    wait_event_timeout(i2c->wait, i2c->msg_num == 0, HZ * 5); 接下来就在这睡眠了,醒来的条件要么是超时了,要么是消息发送完了在中断程序中被唤醒,如果是超时了,醒来后会打印一些信息,不是重点。

接下来看看重点:

  s3c24xx_i2c_message_start(i2c, msgs); 

   unsigned int addr = (msg->addr & 0x7f) << 1;  计算出设备的7位地址
   unsigned long stat;
   unsigned long iiccon;

   stat = 0;
   stat |=  S3C2410_IICSTAT_TXRXEN;  设置发送接收使能

   if (msg->flags & I2C_M_RD) {      根据flags设置主机接收模式还是发送模式
    stat |= S3C2410_IICSTAT_MASTER_RX;
    addr |= 1;                       第8为1表示读操作
   } else
     stat |= S3C2410_IICSTAT_MASTER_TX;  发送时,第8位为0

 

    if (msg->flags & I2C_M_REV_DIR_ADDR)
    addr ^= 1;

    s3c24xx_i2c_enable_ack(i2c);     使能ACK,当主机接收到数据后会发出ACK信号

    iiccon = readl(i2c->regs + S3C2410_IICCON);  读取IICCON,就设置这些信息写回寄存器中生效
    writel(stat, i2c->regs + S3C2410_IICSTAT);

 

    writeb(addr, i2c->regs + S3C2410_IICDS);  将设备的地址写入

    stat |= S3C2410_IICSTAT_START;             
    writel(stat, i2c->regs + S3C2410_IICSTAT); 发出S信号,发出S信号后就会自动的把设备的地址发送出去,从机能响应时,会在SCL的第9个时钟周期发出ACK信号,这个信号会产生中断。而该函数的使命就完成了,剩下的一切一切都是中断处理函数中完了。

 

中断处理函数:s3c24xx_i2c_irq(int irqno, void *dev_id)

s3c24xx_i2c_irq(int irqno, void *dev_id)

  status = readl(i2c->regs + S3C2410_IICSTAT);  读取状态寄存器,来查看是哪类时间产生了

  if (status & S3C2410_IICSTAT_ARBITR) {  如果是仲裁总线失败了,啥也干不了只能打印一些出错信息了
  dev_err(i2c->dev, "deal with arbitration loss\n");
 }

 if (i2c->state == STATE_IDLE) { 处于空闲状态,接触中断挂起就OK,不能会持续不断的中断哦
  dev_dbg(i2c->dev, "IRQ: error i2c->state == IDLE\n");

  tmp = readl(i2c->regs + S3C2410_IICCON);
  tmp &= ~S3C2410_IICCON_IRQPEND;
  writel(tmp, i2c->regs +  S3C2410_IICCON);
  goto out;
 }

 i2s_s3c_irq_nextbyte(i2c, status);  其他事件产生的中断,都交给你去处理了,这个函数有点复杂哦

i2s_s3c_irq_nextbyte(struct s3c24xx_i2c *i2c, unsigned long iicstat) 该函数是根据处理器的所处的状态来进行处理的

  switch (i2c->state) {

   case STATE_IDLE:  都处于空闲状态了,肯定啥事不干了
     dev_err(i2c->dev, "%s: called in STATE_IDLE\n", __func__);
     goto out;
     break;

 

   case STATE_STOP: 处于发送STOP信号的状态了,直接禁止中断就OK
     dev_err(i2c->dev, "%s: called in STATE_STOP\n", __func__);
     s3c24xx_i2c_disable_irq(i2c);
     goto out_ack;

 

   case STATE_START: 处于发送S信号的阶段,一开始传输不就是线发出了S信号吗?
     if (i2c->msg->flags & I2C_M_RD)  会根据消息的标志位来设置适配器接下来处于哪种状态
      i2c->state = STATE_READ;    接下来处理器就处于读数据的状态了
    else
      i2c->state = STATE_WRITE;  接下来处理器就处于写数据的状态了

 

    if (is_lastmsg(i2c) && i2c->msg->len == 0) { 如果是最后一个消息且消息发送完了
     s3c24xx_i2c_stop(i2c, 0);  直接停止了i2c
     goto out_ack;   貌似这种情况应该是验证一个设备是否存在的时候有用
    }

   if (i2c->state == STATE_READ)  如果是进行读操作,就跳转到标号prepare_read去了
   goto prepare_read;  是写的话直接往下执行就OK了,没加break哦

 

  case STATE_WRITE:

    if (!(i2c->msg->flags & I2C_M_IGNORE_NAK)) {  如果不忽略ACK信号的话,此时应该接收到ACK信号
      if (iicstat & S3C2410_IICSTAT_LASTBIT) {    没接到ACK信号,完了出错了
       dev_dbg(i2c->dev, "WRITE: No Ack\n");

       s3c24xx_i2c_stop(i2c, -ECONNREFUSED);  停止传输吧
       goto out_ack;
     }
   }

retry_write:

  if (!is_msgend(i2c)) {   当前消息的最后一个字节(该消息传输完了)
    byte = i2c->msg->buf[i2c->msg_ptr++];
    writeb(byte, i2c->regs + S3C2410_IICDS); 继续写就是了

    ndelay(i2c->tx_setup);

  } else if (!is_lastmsg(i2c)) {  不是当前消息的最后一个字节,也不是最后一个消息

   dev_dbg(i2c->dev, "WRITE: Next Message\n");

   i2c->msg_ptr = 0;  消息内部数据指针清0,
   i2c->msg_idx++;    消息索引加1
   i2c->msg++;        指向下一个消息


   if (i2c->msg->flags & I2C_M_NOSTART) { 标志设置诶不需要重复的START信号

      if (i2c->msg->flags & I2C_M_RD) { 如果是读操作

       s3c24xx_i2c_stop(i2c, -EINVAL); 停止
     }

    goto retry_write;   进行重复写
   } else {  如果需要如果发送完一个消息需要重新发送START信号
    s3c24xx_i2c_message_start(i2c, i2c->msg); 重新开始传输
    i2c->state = STATE_START;  适配器为START状态
   }

  } else {  消息传完了,停止传输

   s3c24xx_i2c_stop(i2c, 0);
  }
  break;

 

  case STATE_READ:  读操作

    byte = readb(i2c->regs + S3C2410_IICDS); 从寄存器中读取数据
    i2c->msg->buf[i2c->msg_ptr++] = byte;  将消息数据放到buf中

prepare_read:
    if (is_msglast(i2c)) { 如果是最后一个消息

      if (is_lastmsg(i2c))   最后一个消息的最后一个字节
        s3c24xx_i2c_disable_ack(i2c);  停止传输

    } else if (is_msgend(i2c)) { 最后一个消息的最后一个字节

       if (is_lastmsg(i2c)) { 停止传输
        dev_dbg(i2c->dev, "READ: Send Stop\n");

        s3c24xx_i2c_stop(i2c, 0);
    } else {  接收下一个消息
      dev_dbg(i2c->dev, "READ: Next Transfer\n");

      i2c->msg_ptr = 0;
      i2c->msg_idx++;
      i2c->msg++;
    }
  }

  break;
 }

 

适配器驱动的代码就分析到这了,相信此时你会适配器驱动已经不再感到神秘,接下来一章分析及编写I2C设备驱动及最终的总结

0 0
原创粉丝点击