zoukankan      html  css  js  c++  java
  • 《Linux4.0设备驱动开发详解》笔记--第十五章:Linux I2C核心、总线与设备驱动

    15.1 Linux I2C体系结构

    • I2C核心
      • I2C核心提供了I2C总线驱动和设备驱动的注册、注销的方法,I2C通信(Algorithm)方法上层的与具体适配器无关代码以及探测设备、检测设备地址的上层代码等
    • I2C总线驱动
      • 是对I2C体系结构中适配器端的实现,适配器可由CPU控制,甚至可以直接集成在CPU内部
      • 总线驱动包含I2C适配器数据结构i2c_adapter、I2C适配器的Algorithm数据结构i2c_algorithm和控制I2C适配器产生通信信号的函数
    • I2C设备驱动
      • 它是对I2C硬件体系结构中设备端的实现,设备一般挂接在受CPU控制的I2C适配器上,通过I2C适配器与CPU通信
      • I2C驱动主要包含数据结构i2c_driver和i2c_client,需要根据的设备实现其中的成员函数
    • 所有的I2C设备都在sysfs文件系统中显示,存在/sys/bus/i2c目录下,以适配器地址和芯片地址的形式列出
    • /drivers/i2c/下的文件介绍
      • i2c-core.c:实现了I2C核心的功能以及/proc/bus/i2c*接口
      • i2c-dev.c:实现I2C适配器设备文件的功能,每一个适配器被分配一个设备
        • 设配器的主设备号位89,次设备号位:0-255
        • i2c_dev.c并不是根据具体的设备而设计的,只是提供了同用的read()、write()和ioctl()等接口,应用层可以通过这些接口访问挂接在适配器上的I2C设备的存储空间或寄存器,并控制I2C设备的工作方式
      • busses文件夹:包含一些I2C主机控制器驱动,如i2c_omap.c、i2c_s3c2440c.等
      • algos文件夹:实现了一些I2C总线适配器的通信方法
    • i2c_adapter、i2c_algorithm、i2c_driver和i2c_client数据结构的作用及其之间的关系
      • i2c_adapter与i2c_algorithm
        • i2c_adapter对应于物理上的一个适配器,而i2c_algorithm对应于一套通信方法
        • 一个i2c_adapter需要i2c_algorithm提供的通信函数来控制适配器产生特定的访问周期
        • i2c_algorithm中的关键函数master_xfer()用于产生I2C访问周期需要的信号,以i2c_msg(即I2C消息)为单位
      • i2c_driver与i2c_client
        • i2c_driver对应于一套驱动方法,struct i2c_device_id形式的id_table是该驱动所支持的I2C设备的ID表
        • i2c_client对应于真实的物理设备,每个I2C设备都需一个i2c_client来描述
        • 一个i2c_driver支持多个同类型的i2c_client
        • i2c_client的信息通常在BSP的板文件中通过i2c_board_info填充,包括设备的ID号、地址、中断号等信息
        • 在I2C总线驱动i2c_bus_type的match()函数i2c_device_match()中,会调用i2c_match_id()函数匹配在板文件中定义的ID和i2c_driver所支持的ID表
      • i2c_adapter与i2c_client
        • 其关系与I2C设备体系中适配器与设备的关系一致,即i2c_client依附于i2c_driver
        • 一个i2c_driver可以被多个i2c_client依附,i2c_driver中包含有依附它的i2c_client的链表

    15.2 I2C核心

    • 增加、删除i2c_adapter
    int i2c_add_adapter(struct i2c_adapter *adap);
    int i2c_del_adapter(struct i2c_adapter *adap);
    • 增加、删除i2c_driver
    int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
    int i2c_del_driver(struct i2c_driver *driver);
    inline int i2c_add_driver(struct i2c_driver *driver);
    
    • i2c_client依附/脱离
    int i2c_attach_client(struct i2c_client *client);
    int i2c_detach_client(struct i2c_client *client);

    当一个具体的client被侦测到并被关联的时候,设备和sysfs文件将被注册。相反地,在client被取消关联的时候,sysfs文件和设备也被注销
    代码清单15.6 I2C核心client attach/detach函数

    1  int i2c_attach_client(struct i2c_client *client)
    2  {
    3    ...
    4   device_register(&client->dev);
    5   device_create_file(&client->dev, &dev_attr_client_name);
    6   
    7   return 0;
    8  }
    9  
    10 int i2c_detach_client(struct i2c_client *client)
    11 {
    12   ...
    13  device_remove_file(&client->dev, &dev_attr_client_name);
    14  device_unregister(&client->dev);
    15   ...
    16 }
    • I2C传输、发送和接收
    int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num);
    int i2c_master_send(struct i2c_client *client,const char *buf ,int count);
    int i2c_master_recv(struct i2c_client *client, char *buf ,int count);
    

    i2c_transfer ()函数用于进行I2C适配器和I2C设备之间的一组消息交互,i2c_master_send()函数和i2c_master_recv()函数内部会 调用i2c_transfer()函数分别完成一条写消息和一条读消
    例如:

    代码清单15.7 I2C核心i2c_master_send函数
    1  int i2c_master_send(struct i2c_client *client,const char *buf ,int count)
    2  {
    3   int ret;
    4   struct i2c_adapter *adap=client->adapter;
    5   struct i2c_msg msg;
    6    /*构造一个写消息*/
    7   msg.addr = client->addr;
    8   msg.flags = client->flags & I2C_M_TEN;
    9   msg.len = count;
    10  msg.buf = (char *)buf;
    11  /*传输消息*/
    12  ret = i2c_transfer(adap, &msg, 1);
    13 
    14  return (ret == 1) ? count : ret;
    15 }
    代码清单15.8 I2C核心i 2c_master_recv函数
    1  int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
    2  {
    3   struct i2c_adapter *adap=client->adapter;
    4   struct i2c_msg msg;
    5   int ret;
    6   /*构造一个读消息*/
    7   msg.addr = client->addr;
    8   msg.flags = client->flags & I2C_M_TEN;
    9   msg.flags |= I2C_M_RD;
    10  msg.len = count;
    11  msg.buf = buf;
    12  /*传输消息*/
    13  ret = i2c_transfer(adap, &msg, 1);
    14 
    15  /* 成功(1条消息被处理), 返回读的字节数 */
    16  return (ret == 1) ? count : ret;
    17 }

    i2c_transfer()函数本身不具备驱动适配器物理硬件完成消息交互的能力,它只是寻找到i2c_adapter对应的i2c_algorithm,并使用i2c_algorithm的master_xfer()函数真正驱动硬件流程,如下例程:

    I2C核心i 2c_transfer函数
    1  int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
    2  {
    3   int ret;
    4  
    5   if (adap->algo->master_xfer) {
    6    down(&adap->bus_lock);
    7    ret = adap->algo->master_xfer(adap,msgs,num); /* 消息传输 */
    8    up(&adap->bus_lock);
    9    return ret;
    10  } else {
    11   dev_dbg(&adap->dev, "I2C level transfers not supported
    ");
    12   return -ENOSYS;
    13  }
    14 }

    15.3 Linux I2C总线驱动

    15.3.1 I2C适配器驱动加载与卸载

    • I2C总线驱动模块的加载函数要完成两个工作:
      • 初始化I2C适配器所使用的硬件资源,申请I/O地址、中断号等。
      • 通过i2c_add_adapter()添加i2c_adapter的数据结构,当然这个i2c_adapter数据结构的成员已经被xxx适配器的相应函数指针所初始化。
    • I2C总线驱动模块的卸载函数要完成的工作与加载函数的相反:
      • 释放I2C适配器所使用的硬件资源,释放I/O地址、中断号等。
      • 通过i2c_del_adapter()删除i2c_adapter的数据结构。
    • 代码清单给出了I2C适配器驱动模块加载和卸载函数的模板。
    I2C总线驱动模块加载和卸载函数模板
    1  static int __init i2c_adapter_xxx_init(void)
    2  {
    3    xxx_adpater_hw_init();
    4    i2c_add_adapter(&xxx_adapter);
    5  }
    6  
    7  static void __exit i2c_adapter_xxx_exit(void)
    8  {
    9    xxx_adpater_hw_free();
    10   i2c_del_adapter(&xxx_adapter);
    11 }
     - 上述代码中xxx_adpater_hw_init()和xxx_adpater_hw_free()函数的实现都与具体的CPU和I2C设备硬件直接相关。
    

    15.3.2 I2C总线通信方法

    • 我们需要为特定的I2C适配器实现其通信方法,主要实现i2c_algorithm的master_xfer()函数和functionality()函数。
      • functionality()函数用于返回algorithm所支持的通信协议,如I2C_FUNC_I2C、I2C_FUNC_10BIT_ADDR、SMBUS_READ_BYTE、I2C_FUNC_SMBUS_WRITE_BYTE等。
      • master_xfer()函数在I2C适配器上完成传递给它的i2c_msg数组中的每个I2C消息
    • 代码给出了xxx设备的master_xfer()函数模板。
    I2C总线驱动master_xfer函数模板
    1  static int i2c_adapter_xxx_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs,
    2    int num)
    3  {
    4    ...
    5    for (i = 0; i < num; i++)
    6    {
    7      i2c_adapter_xxx_start(); /*产生开始位*/
    8      /*是读消息*/
    9      if (msgs[i]->flags &I2C_M_RD)
    10     {
    11       i2c_adapter_xxx_setaddr((msg->addr << 1) | 1); /*发送从设备读地址*/
    12       i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
    13       i2c_adapter_xxx_readbytes(msgs[i]->buf, msgs[i]->len); /*读取msgs[i]
    14         ->len长的数据到msgs[i]->buf*/
    15     }
    16     else
    17      /*是写消息*/
    18     {
    19       i2c_adapter_xxx_setaddr(msg->addr << 1); /*发送从设备写地址*/
    20       i2c_adapter_xxx_wait_ack(); /*获得从设备的ack*/
    21       i2c_adapter_xxx_writebytes(msgs[i]->buf, msgs[i]->len); /*读取msgs[i]
    22         ->len长的数据到msgs[i]->buf*/
    23     }
    24   }
    25   i2c_adapter_xxx_stop(); /*产生停止位*/
    26 }
     - 上述代码实际上给出了一个master_xfer()函数处理I2C消息数组的流程,对于数组中的每个消息,判断消息类型,若为读消息,则赋从设备地址为    (msg->addr << 1) | 1,否则为msg->addr << 1。对每个消息产生1个开始位,紧接着传送从设备地址,然后开始数据的发送或接收,对最后的消息还需产生1个停止位。图15.3描述了整个master_xfer()完成的时序。
    
    • 多数I2C总线驱动会定义一个xxx_i2c结构体,作为i2c_adapter的algo_data(类似“私有数据”)
      • xxx_i2c结构体包含I2C消息数组指针、数组索引及I2C适配器algorithm访问控制用的自旋锁、等待队列等
      • master_xfer()函数完成消息数组中消息的处理也可通过对xxx_i2c结构体相关成员的访问来控制。
    1  struct xxx_i2c 
    2  {
    3   spinlock_t  lock;
    4   wait_queue_head_t wait;  
    5   struct i2c_msg  *msg;
    6   unsigned int  msg_num;
    7   unsigned int  msg_idx;
    8   unsigned int  msg_ptr;
    9   ...
    10  struct i2c_adapter adap;
    11 };

    15.4 linux I2C设备驱动

    15.4.1 Linux I2C设备驱动模块加载与卸载

    • I2C设备驱动模块加载函数通用的方法是在I2C设备驱动模块加载函数中完成两件事:
      • 通过register_chrdev()函数将I2C设备注册为一个字符设备。
      • 通过I2C核心的i2c_add_driver()函数添加i2c_driver。
    • 在模块卸载函数中需要做相反的两件事:
      • 通过I2C核心的i2c_del_driver()函数删除i2c_driver。
      • 通过unregister_chrdev()函数注销字符设备。
    1  static int __init yyy_init(void)
    2  {
    3    int res;
    4    /*注册字符设备*/
    5    res = register_chrdev(YYY_MAJOR, "yyy", &yyy_fops); //老内核接口
    6    if (res)
    7      goto out;
    8    /*添加i2c_driver*/
    9    res = i2c_add_driver(&yyy_driver);
    10   if (res)
    11     goto out_unreg_class;
    12   return 0;
    13 
    14   out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c");
    15   out: printk(KERN_ERR "%s: Driver Initialisation failed
    ", __FILE__);
    16   return res;
    17 }
    18 
    19 static void __exit yyy_exit(void)
    20 {
    21   i2c_del_driver(&i2cdev_driver);
    22   unregister_chrdev(YYY_MAJOR, "yyy");
    23 }

    15.4.2 Linux I2C设备驱动的数据传输

    • I2C设备上的读写数据的时序且数据通常通过i2c_msg消息数组进行组织,最后通过i2c_transfer函数完成
    1  static int yyy_cmd1(struct i2c_client *client, struct rtc_time *dt)
    2  {
    3   struct i2c_msg msg[2];
    4   /*第一条消息是写消息*/
    5   msg[0].addr = client->addr;
    6   msg[0].flags = 0;
    7   msg[0].len = 1;
    8   msg[0].buf = &offs;
    9   /*第二条消息是读消息*/
    10  msg[1].addr = client->addr;
    11  msg[1].flags = I2C_M_RD;
    12  msg[1].len = sizeof(buf);
    13  msg[1].buf = &buf[0];
    14  
    15  i2c_transfer(client->adapter, msg, 2);
    16   ...
    17 }

    15.4.3 Linux i2c-dev.c文件分析

    • i2c-dev.c文件可以被看作一个I2C设 备驱动,它实现的一个i2c_client是虚拟的、临时的,随着设备文件的打开而产生,并随设备文件的关闭而撤销,并没有被添加到i2c_adapter的clients链表中。

    • i2c-dev.c针对每个I2C适配器生成一个主设备为89的设备文件,实现了i2c_driver的成员函数以及文件操作接口

    • i2c-dev.c的主体是“i2c_driver成员函数 + 字符设备驱动”。

    • i2c-dev.c中提供i2cdev_read()、i2cdev_write()函数来对应用户空间要使用的read()和 write()文件操作接口,这两个函数分别调用I2C核心的i2c_master_recv()和i2c_master_send()函数来构造1条 I2C消息并引发适配器algorithm通信函数的调用,完成消息的传输

    • i2c-dev.c中i2cdev_read()和i2cdev_write()函数不具备太强的通用性,没有太大的实用价值,只能适用于非RepStart模式的情况。

    • 对于2条以上消息组成的读写,在用户空间需要组织i2c_msg消息数组并调用I2C_RDWR IOCTL命令。

    • 代码给出了i2cdev_ioctl()函数的框架,其中详细列出了I2C_RDWR命令的处理过程。

     i2c-dev.c中的i2cdev_ioctl函数
    1  static int i2cdev_ioctl(struct inode *inode, struct file *file,
    2    unsigned int cmd, unsigned long arg)
    3  {
    4   struct i2c_client *client = (struct i2c_client *)file->private_data;
    5    ...  
    6   switch ( cmd ) {
    7   case I2C_SLAVE:
    8   case I2C_SLAVE_FORCE:
    9    ...   /*设置从设备地址*/
    10  case I2C_TENBIT:
    11   ...
    12  case I2C_PEC:
    13   ...
    14  case I2C_FUNCS:
    15   ...  
    16  case I2C_RDWR:
    17   if (copy_from_user(&rdwr_arg, 
    18        (struct i2c_rdwr_ioctl_data __user *)arg, 
    19        sizeof(rdwr_arg)))
    20    return -EFAULT;
    21   /* 一次传入的消息太多 */
    22   if (rdwr_arg.nmsgs > I2C_RDRW_IOCTL_MAX_MSGS)
    23    return -EINVAL;
    24   /*获得用户空间传入的消息数组
    25   rdwr_pa = (struct i2c_msg *)
    26    kmalloc(rdwr_arg.nmsgs * sizeof(struct i2c_msg), 
    27    GFP_KERNEL);    
    28   if (rdwr_pa == NULL) return -ENOMEM;
    29   if (copy_from_user(rdwr_pa, rdwr_arg.msgs,
    30        rdwr_arg.nmsgs * sizeof(struct i2c_msg))) {
    31    kfree(rdwr_pa);
    32    return -EFAULT;
    33   }
    34   data_ptrs = kmalloc(rdwr_arg.nmsgs * sizeof(u8 __user *), GFP_KERNEL);
    35   if (data_ptrs == NULL) {
    36    kfree(rdwr_pa);
    37    return -ENOMEM;
    38   }
    39   res = 0;
    40   for( i=0; i41    /* 限制消息的长度 */
    42    if (rdwr_pa[i].len > 8192) {
    43     res = -EINVAL;
    44     break;
    45    }
    46    data_ptrs[i] = (u8 __user *)rdwr_pa[i].buf;
    47    rdwr_pa[i].buf = kmalloc(rdwr_pa[i].len, GFP_KERNEL);
    48    if(rdwr_pa[i].buf == NULL) {
    49     res = -ENOMEM;
    50     break;
    51    }
    52    if(copy_from_user(rdwr_pa[i].buf,
    53     data_ptrs[i],
    54     rdwr_pa[i].len)) {
    55      ++i; /* Needs to be kfreed too */
    56      res = -EFAULT;
    57     break;
    58    }
    59   }
    60   if (res < 0) {
    61    int j;
    62    for (j = 0; j < i; ++j)
    63     kfree(rdwr_pa[j].buf);
    64    kfree(data_ptrs);
    65    kfree(rdwr_pa);
    66    return res;
    67   }
    68    /*把这些消息交给通信方法去处理*/
    69   res = i2c_transfer(client->adapter,
    70    rdwr_pa,
    71    rdwr_arg.nmsgs);
    72   while(i-- > 0) {   /*如果是读消息,把值拷贝到用户空间*/
    73    if( res>=0 && (rdwr_pa[i].flags & I2C_M_RD)) {
    74     if(copy_to_user(
    75      data_ptrs[i],
    76      rdwr_pa[i].buf,
    77      rdwr_pa[i].len)) {
    78      res = -EFAULT;
    79     }
    80    }
    81    kfree(rdwr_pa[i].buf);
    82   }
    83   kfree(data_ptrs);
    84   kfree(rdwr_pa);
    85   return res;
    86  case I2C_SMBUS:
    87   ... 
    88  default:
    89   return i2c_control(client,cmd,arg);
    90  }
    91  return 0;
    92 }
     - 常用的IOCTL包括I2C_SLAVE(设置从设备地址)、I2C_RETRIES(没有收到设备ACK情况下的重试次数,缺省为1)、I2C_TIMEOU(超时)以及I2C_RDWR。
    
    • 下面两个代码分别演示了直接通过read()、write()接口和O_RDWR IOCTL读写I2C设备的例子。
    
    代码清单15.23 直接通过read()/write()读写I2C设备
    1  #include 
    2  #include 
    3  #include 
    4  #include 
    5  #include 
    6  #include 
    7  #include 
    8  
    9  #define I2C_RETRIES     0x0701
    10 #define I2C_TIMEOUT     0x0702
    11 #define I2C_SLAVE       0x0703
    12 
    13 int main(int argc, char **argv)
    14 {
    15   unsigned int fd;
    16   unsigned short mem_addr;
    17   unsigned short size;
    18   unsigned short idx;
    19   #define BUFF_SIZE    32
    20   char buf[BUFF_SIZE];
    21   char cswap;
    22   union
    23   {
    24     unsigned short addr;
    25     char bytes[2];
    26   } tmp;
    27 
    28   if (argc < 3)
    29   {
    30     printf("Use:
    %s /dev/i2c-x mem_addr size
    ", argv[0]);
    31     return 0;
    32   }
    33   sscanf(argv[2], "%d", &mem_addr);
    34   sscanf(argv[3], "%d", &size);
    35 
    36   if (size > BUFF_SIZE)
    37     size = BUFF_SIZE;
    38 
    39   fd = open(argv[1], O_RDWR);
    40 
    41   if (!fd)
    42   {
    43     printf("Error on opening the device file
    ");
    44     return 0;
    45   }
    46 
    47   ioctl(fd, I2C_SLAVE, 0x50); /* 设置eeprom地址 */
    48   ioctl(fd, I2C_TIMEOUT, 1); /* 设置超时 */
    49   ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */
    50 
    51   for (idx = 0; idx < size; ++idx, ++mem_addr)
    52   {
    53     tmp.addr = mem_addr;
    54     cswap = tmp.bytes[0];
    55     tmp.bytes[0] = tmp.bytes[1];
    56     tmp.bytes[1] = cswap;
    57     write(fd, &tmp.addr, 2);
    58     read(fd, &buf[idx], 1);
    59   }
    60   buf[size] = 0;
    61   close(fd);
    62   printf("Read %d char: %s
    ", size, buf);
    63   return 0;
    64 }
    代码清单15.24 通过O_RDWR IOCTL读写I2C设备
    1  #include 
    2  #include 
    3  #include 
    4  #include 
    5  #include 
    6  #include 
    7  #include 
    8  #include 
    9  #include 
    10 #include 
    11 
    12 #define MAX_I2C_MSG         2
    13 
    14 #define I2C_RETRIES     0x0701
    15 #define I2C_TIMEOUT     0x0702
    16 #define I2C_RDWR        0x0707
    17 
    18 struct i2c_msg
    19 {
    20   __u16 addr; /* 从地址 */
    21   __u16 flags;
    22   #define I2C_M_RD        0x01
    23   __u8 *buf; /* 消息数据指针 */
    24 };
    25 struct i2c_rdwr_ioctl_data
    26 {
    27   struct i2c_msg *msgs; /* i2c_msg[]指针 */
    28   int nmsgs; /* i2c_msg数量 */
    29 };
    30 
    31 int main(int argc, char **argv)
    32 {
    33   struct i2c_rdwr_ioctl_data work_queue;
    34   unsigned int idx;
    35   unsigned int fd;
    36   unsigned short start_address;
    37   int ret;
    38 
    39   if (argc < 4)
    40   {
    41     printf("Usage:
    %s /dev/i2c-x start_addr
    ", argv[0]);
    42     return 0;
    43   }
    44 
    45   fd = open(argv[1], O_RDWR);
    46 
    47   if (!fd)
    48   {
    49     printf("Error on opening the device file
    ");
    50     return 0;
    51   }
    52   sscanf(argv[2], "%x", &start_address);
    53   work_queue.nmsgs = MAX_I2C_MSG; /* 消息数量 */
    54 
    55   work_queue.msgs = (struct i2c_msg*)malloc(work_queue.nmsgs *sizeof(struct
    56     i2c_msg));
    57   if (!work_queue.msgs)
    58   {
    59     printf("Memory alloc error
    ");
    60     close(fd);
    61     return 0;
    62   }
    63 
    64   for (idx = 0; idx < work_queue.nmsgs; ++idx)
    65   {
    66     (work_queue.msgs[idx]).len = 0;
    67     (work_queue.msgs[idx]).addr = start_address + idx;
    68     (work_queue.msgs[idx]).buf = NULL;
    69   }
    70 
    71   ioctl(fd, I2C_TIMEOUT, 2); /* 设置超时 */
    72   ioctl(fd, I2C_RETRIES, 1); /* 设置重试次数 */
    73 
    74   ret = ioctl(fd, I2C_RDWR, (unsigned long) &work_queue);
    75 
    76   if (ret < 0)
    77   {
    78     printf("Error during I2C_RDWR ioctl with error code: %d
    ", ret);
    79   }
    80 
    81   close(fd);
    82   return ;
    83 }
  • 相关阅读:
    uboot和内核分区的修改
    2440移植内核到uboot上,打印乱码
    启动新内核出现:No filesystem could mount root, tried: ext3 ext2 cramfs vfa
    启动新内核出现:Kernel panic
    移植最新版本3.4.2内核
    2017团体程序设计天梯赛大区赛 L3-3 球队“食物链”
    leetcode543 Diameter of Binary Tree
    CF599B Spongebob and Joke
    poj1930 Dead Fraction
    poj3040 Allowance
  • 原文地址:https://www.cnblogs.com/zcjboke/p/5513126.html
Copyright © 2011-2022 走看看