zoukankan      html  css  js  c++  java
  • 9.1 IIC驱动源码分析

    学习目标:分析linux内核源码下的i2c总线驱动 drivers/i2c/busses/i2c-s3c2410.c 和 driver/i2c/chips/eeprom.c 设备驱动;


    一、i2c驱动框架

    在drivers/i2c/目录下查看文件结构可看到:

    其中,

    1)Busses: I2C总线驱动相关的文件。例如i2c-s3c2410.c.

    2)Chips: I2C设备驱动相关文件。例如:eeprom.c、m41t00.c.

    3)Algos:i2c通信相关,使能中断、启动传输、i2c寄存器操作等。

    4)i2c-core.c:实现了I2C核心的功能,例如:I2C总线的初始化、注册和适配器添加和注销等相关工作以及/proc/bus/i2c*接口。
    5)i2c-dev.c:提供了通用的read、 write和ioctl等接口,实现了I2C适配器设备文件的功能。

    二、源码分析

    简单的结构图:

     第一部分: i2c总线驱动程序 drivers/i2c/busses/i2c-s3c2410.c

     1 static struct platform_driver s3c2440_i2c_driver = {
     2     .probe        = s3c24xx_i2c_probe,
     3     .remove        = s3c24xx_i2c_remove,
     4     .resume        = s3c24xx_i2c_resume,
     5     .driver        = {
     6         .owner    = THIS_MODULE,
     7         .name    = "s3c2440-i2c",
     8     },
     9 };
    10 
    11 static int __init i2c_adap_s3c_init(void)
    12 {
    13     int ret;
    15     ret = platform_driver_register(&s3c2410_i2c_driver);//注册平台platform_driver
    16     if (ret == 0) {
    17         ret = platform_driver_register(&s3c2440_i2c_driver);
    18         if (ret)
    19             platform_driver_unregister(&s3c2410_i2c_driver);
    20     }
    22     return ret;
    23 }

    进入probe函数可以看到:

     (1)*设置i2c_adapter适配器结构体,i2c_adapter适配器指向s3c24xx_i2c;

        i2c->adap.algo_data = i2c;   
        i2c->adap.dev.parent = &pdev->dev;

    其中,s3c24xx_i2c结构体为:

    这里.master_xfer  = s3c24xx_i2c_xfe函数:与i2c通信相关,使能中断、启动传输、i2c寄存器操作等功能。==》发送i2c信号函数。

    (2)i2c_add_adapter(&i2c->adap);注册adapter适配器。

    进入其中的i2c_register_adapter函数:

     static int i2c_register_adapter(struct i2c_adapter *adap)
    { .......
    list_add_tail(&adap->list, &adapters);//添加adap到链表中
    /* let legacy drivers scan this bus for matching devices */ list_for_each(item,&drivers) { driver = list_entry(item, struct i2c_driver, list); if (driver->attach_adapter) /* We ignore the return code; if it fails, too bad */ driver->attach_adapter(adap); }
    }

    1)首先将adapter放入i2c_bus_type的adapter链表中

    2)然后将所有的i2c设备调出来,执行i2c_driver设备的attach_adapter函数进行匹配;

    第二部分:i2c设备驱动,以driver/i2c/chips/eeprom.c 设备驱动为例

    利用i2c_add_driver分配eeprom_driver结构体:

    进入i2c_add_driver函数,可看到调用的int i2c_register_driver函数:

     1  int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
     2 {  .....................
     3    list_add_tail(&driver->list,&drivers);
     4    pr_debug("i2c-core: driver [%s] registered
    ", driver->driver.name);
     5    /* legacy drivers scan i2c busses directly */
     6    if (driver->attach_adapter) {
     7    struct i2c_adapter *adapter;
     8    list_for_each_entry(adapter, &adapters, list) {
     9     driver->attach_adapter(adapter);
    10  }

    1)首先添加driver到driver链表中

    2)然后取出adapters链表中所有的i2c_adapter, 然后调用了i2c_driver->attach_adapter()函数,进行匹配。

    接下来会进入eeprom_driver结构体的eeprom_attach_adapter函数:

    其中,第1个参数就是i2c_adapter适配器, 参数addr_data存放了I2C设备地址的信息, 调用i2c_probe_address 发出S信号,发出设备地址(来自addr_data),最终会调用到adap->algo->master_xfer函数发信号,如果如果收到ACK信号,就调用eeprom_detect()回调函数来注册i2c_client结构体,该结构体对应真实的物理从设备。

    这里的addr_data为 i2c_client_address_data结构体:

    1 struct i2c_client_address_data {
    2        unsigned short *normal_i2c;     //存放正常的设备高7位地址数据
    3        unsigned short *probe;          //存放不受*ignore影响的高7位设备地址数据
    4        unsigned short *ignore;         //存放*ignore的高7位设备地址数据
    5        unsigned short **forces;        //forces表示适配器匹配不了该设备,也要将其放入适配器中
    6 
    7 };

    例如:DS1374.c设备驱动程序中:

    接下来的i2c_probe函数的调用顺序为:

    i2c_probe(adapter, &addr_data, eeprom_detect);
           -->i2c_probe_address // 发出S信号,发出设备地址(来自addr_data)
              -->i2c_smbus_xfer
                --> i2c_smbus_xfer_emulated
                   --> i2c_transfer
                       -->adap->algo->master_xfer(adap,msgs,num);// s3c24xx_i2c_xfer

    其中,在i2c_smbus_xfer_emulated函数中,

     1 static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,
     2                                    unsigned short flags,
     3                                    char read_write, u8 command, int size,
     4                                    union i2c_smbus_data * data)
     5 {
    10    unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];  
    11    unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
    12    int num = read_write == I2C_SMBUS_READ?2:1;
    13    struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },    ///定义2个i2c_msg结构体,
    14                              { addr, flags | I2C_M_RD, 0, msgbuf1 }
    15                            };
    16    int i;
    17    u8 partial_pec = 0;
    18    msgbuf0[0] = command;
    19    switch(size) {
    20    case I2C_SMBUS_QUICK:
    21    msg[0].len = 0;
    22    /* Special case: The read/write field is used as data */
    23     msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;
    24     num = 1;
    25     break;
    26     case I2C_SMBUS_BYTE:
    27     if (read_write == I2C_SMBUS_READ) {
    28      /* Special case: only a read! */
    29     msg[0].flags = I2C_M_RD | flags;
    30     num = 1;
    31     }
    32     break;
    33    ....
    34    if (i2c_transfer(adapter, msg, num) < 0)   //将 i2c_msg结构体的内容发送给I2C设备
    35    return -1;
    36    /* Check PEC if last message is a read */
    37    if (i && (msg[num-1].flags & I2C_M_RD)) {
    38     if (i2c_smbus_check_pec(partial_pec, &msg[num-1]) < 0)
    39     return -1;
    40    }
    41   .....
    42 }

    其中,其中i2c_msg结构体为:

    1 struct i2c_msg {
    2        __u16 addr;          //I2C从机的设备地址
    3        __u16 flags;           //当flags=0表示写, flags= I2C_M_RD表示读
    4        __u16 len;              //传输的数据长度,等于buf数组里的字节数
    5        __u8 *buf;              //存放数据的数组
    6 };

    进入i2c_transfer函数的master_xfer(adap,msgs,num);即总线驱动适配器指向结构体的s3c24xx_i2c_xfer函数;其中,参数*adap表示通过哪个适配器传输出去,msgs表示I2C消息,num表示msgs的数目。

    总而言之,根据以上分析,i2c_driver ->attach_adapter(adapter)函数里主要执行以下几步:

    1) 调用 i2c_probe(adap, addr_data 设备地址结构体, 回调函数);

    2) 将要发的设备地址结构体打包成i2c_msg,

    3) 然后执行i2c_transfer()来调用i2c_adapter->algo->master_xfer()将i2c_msg发出去

    4) 若收到ACK回应,便进入回调函数,注册i2c_client从设备,使该设备与适配器联系在一起。

    第三部分:通过回调函数eeprom_detect注册和设置i2c_client从设备(即结构体),使用i2c_transfer()来实现与设备进行后续的传输数据了。 

    eeprom_detect函数的代码如下:

     1 static int eeprom_detect(struct i2c_adapter *adapter, int address, int kind)
     2 {
     3    struct i2c_client *new_client;  //分配i2c_client结构体
     4    struct eeprom_data *data;
     5    int err = 0;
         ....
    6   new_client = &data->client; //设置i2c_client结构体 7   memset(data->data, 0xff, EEPROM_SIZE); 8    i2c_set_clientdata(new_client, data); 9    new_client->addr = address; 10    new_client->adapter = adapter; 11    new_client->driver = &eeprom_driver; 12    new_client->flags = 0; 13    ..... 14     /* Tell the I2C layer a new client has arrived */ 15   if ((err = i2c_attach_client(new_client))) //注册i2c_client结构体 16   goto exit_kfree; 17    .....
       exit_detach:
         i2c_detach_client(new_client);
       exit_kfree:
         kfree(data);
    18 }

    在函数中主要做的以下工作:1)分配i2c_client结构体;2)设置i2c_client结构体;3)注册i2c_client结构体。之后就可以用i2c_transfer()来传输数据了。

  • 相关阅读:
    nginx开启ssl模式
    安装nginx
    node的express框架
    使用Thumbnails工具对图片进行缩放,压缩
    正则
    VUE设置全局方法和属性
    js——XHR知识归纳
    页面资源加载过程
    Mongodb的增删改查
    Mongodb基本概念
  • 原文地址:https://www.cnblogs.com/lxl-lennie/p/10220598.html
Copyright © 2011-2022 走看看