zoukankan      html  css  js  c++  java
  • Linux驱动:I2C驱动编写要点

    继续上一篇博文没讲完的内容“针对 RepStart 型i2c设备的驱动模型”,其中涉及的内容有:i2c_client 的注册、i2c_driver 的注册、驱动程序的编写。

    一、i2c 设备的注册分析:在新版本内核的i2c驱动模型中,支持多种方式来注册 i2c 设备,在Documentation/i2c/instantiating-devices文件中有讲到,在内核中对应的抽象数据结构就是 struct i2c_client。

    (1)Declare the I2C devices by bus number 以i2c总线号来声明设备:主要适用于嵌入式系统设备,系统外的设备比较固定。

    通过 struct i2c_board_info 结构来声明,使用 i2c_register_board_info() 函数来注册。

     1 //在include/linux/i2c.h中
     2 struct i2c_board_info {
     3     char              type[I2C_NAME_SIZE]; //设备名称
     4     unsigned short    flags;
     5     unsigned short    addr;  //设备地址
     6     void              *platform_data;
     7     struct dev_archdata     *archdata;
     8     struct device_node      *of_node;
     9     struct acpi_dev_node acpi_node;
    10     int       irq;
    11 };
    12 
    13 //在drivers/i2c/i2c-boardinfo.c中
    14 LIST_HEAD(__i2c_board_list);
    15 EXPORT_SYMBOL_GPL(__i2c_board_list);
    16 
    17 int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
    18 {
    19     int status;
    20 
    21     /* dynamic bus numbers will be assigned after the last static one */
    22     if (busnum >= __i2c_first_dynamic_bus_num)
    23         __i2c_first_dynamic_bus_num = busnum + 1;
    24         //__i2c_first_dynamic_bus_num是个全局变量,初始值为0,因此,他的值始终比busnum大1!这一点可以解决上一篇博文中提出的一个问题。
    25 
    26     for (status = 0; len; len--, info++) {
    27         struct i2c_devinfo    *devinfo;
    28 
    29         devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
    30         if (!devinfo) {
    31             pr_debug("i2c-core: can't register boardinfo!
    ");
    32             status = -ENOMEM;
    33             break;
    34         }
    35 
    36         devinfo->busnum = busnum;
    37         devinfo->board_info = *info;
    38         list_add_tail(&devinfo->list, &__i2c_board_list);//将这个 i2c_board_info 添加到该总线的 __i2c_board_list i2c设备链表中
    39     }
    40     return status;
    41 }

    问题思考:board_list 链表结构是怎样的?是一条 i2c总线一条链表还是类似于哈希链表?
    寻找答案:答案是一条i2c设备链表,里边链着的是所有i2c总线上的设备,分析i2c-core.c中的函数:

    1 i2c_scan_static_board_info(struct i2c_adapter *adapter)
    2     list_for_each_entry(devinfo, &__i2c_board_list, list) {
    3             if (devinfo->busnum == adapter->nr && !i2c_new_device(adapter,&devinfo->board_info))
    4                     dev_err(&adapter->dev,"Can't create device at 0x%02x
    ",devinfo->board_info.addr);
    5     }
    6 //遍历__i2c_board_list这整个链表,比较其中每一个结点的 busnum 与 adapter->nr是否相等

    看看这种方法的具体应用:以 at24cxx 为例来分析。

     1 //在arch/arm/mach-s5pv210/mach-smdkv210.c中
     2 static struct i2c_board_info smdkv210_i2c_devs0[] __initdata = {
     3     { I2C_BOARD_INFO("tq210-at24cxx", 0x50), }, /* 目标i2c外设 */
     4     { I2C_BOARD_INFO("wm8580", 0x1b), },
     5 };
     6 smdkv210_machine_init()
     7 {
     8     ...
     9     i2c_register_board_info(0, smdkv210_i2c_devs0,ARRAY_SIZE(smdkv210_i2c_devs0));//注册i2c-0总线上的i2c设备到设备链表
    10     i2c_register_board_info(1, smdkv210_i2c_devs1,ARRAY_SIZE(smdkv210_i2c_devs1));//注册i2c-1总线上的i2c设备到设备链表
    11     i2c_register_board_info(2, smdkv210_i2c_devs2,ARRAY_SIZE(smdkv210_i2c_devs2));//注册i2c-2总线上的i2c设备到设备链表
    12     ...
    13 }

    我们在前一篇博文中分析platform_driver.probe函数(s3c24xx_i2c_probe)的时候得知函数的调用关系是:

    i2c_add_numbered_adapter -> i2c_register_adapter -> i2c_scan_static_board_info -> i2c_new_device,即在i2c_adapter注册好之后内核会去尝试从__i2c_board_list链表中搜索匹配的i2c设备。这种方法有个缺陷就是:必须在调用 i2c_register_adapter 来注册 i2c_adapter 之前就把i2c设备链表注册好,因此,不适合使用 insmod 来动态注册i2c设备。

    (2)Instantiate the devices explicitly 立即检测 i2c设备:

    这种方法就是“认为i2c设备一定肯定存在”后直接使用 i2c_new_device函数来注册i2c_client。看看怎样做,再来详细跟进这个函数:

     1 struct i2c_client *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
     2 {
     3     struct i2c_client    *client;
     4     int            status;
     5 
     6     client = kzalloc(sizeof *client, GFP_KERNEL);
     7     
     8     // 设置这个client
     9     client->adapter = adap;
    10     client->dev.platform_data = info->platform_data;
    11     ......
    12     client->flags = info->flags;
    13     client->addr = info->addr;
    14     client->irq = info->irq;
    15     strlcpy(client->name, info->type, sizeof(client->name));
    16     /* Check for address validity */
    17     status = i2c_check_client_addr_validity(client); // 地址合法性
    18     ......
    19     /* Check for address business */
    20     status = i2c_check_addr_busy(adap, client->addr);// 地址是否被复用
    21     .....
    22     client->dev.bus = &i2c_bus_type;
    23     client->dev.type = &i2c_client_type;
    24     .....
    25     status = device_register(&client->dev); //注册i2c_client设备
    26     ......
    27     return client; //返回i2c_client结构,方便在字符设备编程中的使用!!!
    28     ......
    29 }

    但是使用这个函数前从其参数可以发现需要准备一些材料:i2c_adapter 和 i2c_board_list ,i2c_adapter 需要用核心层的 i2c_get_adapter() 函数来获得, i2c_board_list 的话自己现场构造一个。

    其实到这里就可以看穿其真实面目:把之前放在注册 i2c_adapter之后的任务:遍历 i2c_board_list 链表来注册 i2c_client 版移到这里来实现,这样就可以使用 “insmod” 来动态装载i2c设备了。

    (3)Probe an I2C bus for certain devices 通过probe来探测确定在i2c总线上的设备:

    使用核心层的 i2c_new_probed_device函数来实现:

     1 i2c_new_probed_device(struct i2c_adapter *adap,struct i2c_board_info *info,unsigned short const *addr_list,int (*probe)(struct i2c_adapter *, unsigned short addr))
     2 {
     3     if (!probe)
     4         probe = i2c_default_probe;
     5     ......
     6     /* Test address responsiveness */
     7     for (i = 0; addr_list[i] != I2C_CLIENT_END; i++) {
     8         ......
     9         if (probe(adap, addr_list[i])) //真实的发地址去探测
    10             break; //成功就跳出for循环往后进行注册i2c设备
    11     }
    12     if (addr_list[i] == I2C_CLIENT_END) { //看看是不是探测地址用完了,用完了函数就返回,不往下注册设备。
    13         dev_dbg(&adap->dev, "Probing failed, no device found
    ");
    14         return NULL;
    15     }
    16     info->addr = addr_list[i];
    17     return i2c_new_device(adap, info);//注册设备:创建 i2c_client 并注册
    18 }

    其实这种方法是2.6或之前的内核做法,传闻说这种探测机制会有副作用不建议使用,具体什么副作用就不知道了。

    (4)Instantiate from user-space 从用户空间来创建i2c设备:

    这种方法最最......好吧,竟然找不到一个词汇来形容对它的这种感觉。

    直接使用指令来完成:

    echo eeprom 0x50 > /sys/bus/i2c/devices/i2c-0/new_device

    大体原理可以意会到一些,具体的原理可以看看内核文档的介绍。

    二、i2c 设备驱动的分析和实例编写

    不管上边的哪一种方法注册 i2c_client ,对我的i2c设备驱动的设计都没有太大的出入。下边给出是在第一种方式下注册的 i2c_client 时的驱动编写,分析的内容已经容纳在程序的注释中。

      1 #include <linux/module.h>  
      2 #include <linux/input.h>  
      3 #include <linux/gpio.h>  
      4 #include <linux/delay.h>  
      5 #include <linux/input.h>  
      6 #include <plat/gpio-cfg.h>  
      7 #include <linux/interrupt.h> 
      8 #include <linux/kernel.h>
      9 #include <linux/init.h>
     10 #include <linux/module.h>
     11 #include <linux/slab.h>
     12 #include <linux/jiffies.h>
     13 #include <linux/mutex.h>
     14 #include <linux/fs.h>
     15 #include <asm/uaccess.h>
     16 #include <linux/device.h>
     17 #include <linux/notifier.h>
     18 #include <linux/fs.h>
     19 #include <linux/list.h>
     20 #include <linux/i2c.h>
     21 #include <linux/i2c-dev.h>
     22 
     23 static int major;
     24 static struct class    *cls;
     25 static struct device *i2c_dev;
     26 struct i2c_client       *at24cxx_client ;
     27 
     28 static unsigned write_timeout = 25;
     29 
     30 
     31 int at24cxx_open(struct inode *inode, struct file *file)
     32 {
     33     printk("at24cxx_open
    ");
     34     return 0;
     35 }
     36 
     37 static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
     38 {
     39     unsigned char    address;
     40     unsigned char    data;
     41     int ret;
     42     struct i2c_msg msg[2];
     43 
     44     unsigned long timeout, read_time;
     45     
     46     /* address = buf[0] 
     47      * data    = buf[1]
     48      */
     49     if (size != 1)
     50         return -EINVAL;
     51     
     52     ret = copy_from_user(&address, buf, 1);
     53     printk("read addr:%d
    ",address);
     54     /* 数据传输三要素: 源,目的,长度 */
     55     /* 读AT24CXX时,要先把要读的存储空间的地址发给它 */
     56     msg[0].addr  = at24cxx_client->addr;  /* 目的 */
     57     msg[0].buf   = &address;                         /* 读的源头 */
     58     msg[0].len   = 1;                                     /* 地址=1 byte */                
     59     //msg[0].flags = at24cxx_client->flags & I2C_M_TEN; /* 表示写 */
     60 
     61     /* 然后启动读操作 */
     62     msg[1].addr  = at24cxx_client->addr;  /**/
     63     msg[1].buf   = &data;                              /* 目的 */
     64     msg[1].len   = 1;                                      /* 数据=1 byte */
     65     msg[1].flags = at24cxx_client->flags & I2C_M_TEN;
     66     msg[1].flags |= I2C_M_RD;                     /* 表示读 */
     67 
     68     timeout = jiffies + msecs_to_jiffies(write_timeout);
     69     do{
     70         read_time = jiffies;
     71         ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
     72         msleep(1);
     73     } while (time_before(read_time, timeout));
     74     printk("ret=%d
    ",ret);
     75     if (ret  >= 0)
     76     {
     77         ret = copy_to_user(buf, &data, 1);
     78         return ret;
     79     }
     80     else 
     81         return -1;
     82 
     83 }
     84 
     85 static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
     86 {
     87     unsigned char    val[2];
     88     struct i2c_msg msg[1];
     89     int ret;
     90     /* address = buf[0] 
     91      * data    = buf[1]
     92      */
     93     if (size != 2)
     94         return -EINVAL;
     95     printk("at24cxx_write
    ");
     96     ret = copy_from_user(val, buf, 2);
     97 
     98     /* 数据传输三要素: 源,目的,长度 */
     99     msg[0].addr  = at24cxx_client->addr;  /* 目的 */
    100     msg[0].buf    = val;                   /**/
    101     msg[0].len    = 2;                      /* 地址+数据=2 byte */
    102     msg[0].flags = at24cxx_client->flags & I2C_M_TEN;                     /* 表示写:i2c_transfer函数就知道将buf[0]当做写地址,后边的是写地址 */
    103 
    104     ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
    105     if (ret == 1)
    106         return 2;
    107     else
    108         return -EIO;
    109 }
    110 
    111 long at24cxx__ioctl(struct file *file, unsigned int cmd, unsigned long arg)
    112 {
    113     
    114     return 0;
    115 }
    116 
    117 static struct file_operations at24cxx_fops = {
    118     .owner = THIS_MODULE,
    119     .open   = at24cxx_open,
    120     .read    = at24cxx_read,
    121     .write   = at24cxx_write,
    122     .unlocked_ioctl = at24cxx__ioctl,
    123 };
    124 
    125 static int at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *dev_id)
    126 {
    127     printk("at24cxx_probe
    ");
    128 
    129     /* 注册字符设备驱动:通用模型中这个任务是放在xxx_init()函数来实现 */
    130     major = register_chrdev(0, "at24cxx", &at24cxx_fops);
    131 
    132     cls = class_create(THIS_MODULE, "at24cxx");
    133     i2c_dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx");  /* /dev/at24cxx */
    
    134 
    135     /* 拽住要操作的i2c_client,要它成为读写的操作对象 */
    136     at24cxx_client = client;
    137     
    138     return 0;
    139 }
    140 
    141 int at24cxx_remove(struct i2c_client *client)
    142 {
    143     device_unregister(i2c_dev);
    144     class_destroy(cls);
    145     unregister_chrdev(major, "at24cxx");
    146     
    147     return 0;
    148 }
    149 
    150 static const struct i2c_device_id at24cxx_id[] = {  
    151     { "tq210-at24cxx", 0 },   /* i2c设备的设备名:这个驱动根据这个名在i2c总线中来找匹配的client */
    152     { }
    153 }; 
    154 
    155 static struct i2c_driver at24cxx_driver = {
    156     .driver = {
    157         .name= "at24cxx",
    158         .owner = THIS_MODULE,
    159     },
    160     .probe    = at24cxx_probe,    /* 当有i2c_client和i2c_driver匹配时调用 */
    161     .remove = at24cxx_remove, /* 注销时调用 */
    162     .id_table = at24cxx_id,             /* 匹配规则 */
    163 };
    164 
    165 static int at24cxx_init(void)
    166 {  
    167     printk("at24cxx_init
    ");  
    168     i2c_add_driver(&at24cxx_driver);  
    169     return 0;  
    170 }
    171   
    172 static void at24cxx_exit(void)
    173 {
    174     printk("at24cxx_exit
    ");  
    175     i2c_del_driver(&at24cxx_driver);  
    176 }  
    177   
    178 module_init(at24cxx_init);  
    179 module_exit(at24cxx_exit);  
    180 MODULE_LICENSE("GPL"); 

    应用层测试程序:

     1 #include <stdio.h>
     2 #include <linux/types.h>
     3 #include <fcntl.h>
     4 #include <unistd.h>
     5 #include <stdlib.h>
     6 #include <sys/types.h>
     7 #include <sys/ioctl.h>
     8 #include <linux/i2c.h>
     9 #include <linux/i2c-dev.h>
    10 
    11 int main()
    12 {
    13     int  fd;
    14     unsigned char wr_buf[2];
    15     unsigned char rd_buf;
    16     int ret;
    17 
    18     fd = open("/dev/at24cxx",O_RDWR);
    19     if(!fd)
    20     {
    21         printf("open error.
    ");
    22         return -1;
    23     }
    24     wr_buf[0] = 0x10;//wr_addr
    25     wr_buf[1] = 0x66;//wr_val
    26     ret = write(fd,wr_buf,2); //2bytes
    27     printf("write %dbytes.
    ",ret);
    28 
    29     rd_buf = 0x10;//rd_addr
    30     ret = read(fd,&rd_buf,1); // rd_val -> rd_buf[0]
    31     printf("read addr:0x00 -> data:%d
    ",rd_buf);
    32     close(fd);
    33     return 0;
    34 }
  • 相关阅读:
    程序跳转到itunes商店
    程序跳转到itunes商店
    app被Rejected 的各种原因翻译。这个绝对有用
    app被Rejected 的各种原因翻译。这个绝对有用
    iphone开发中使用nib(xib)文件的内存管理
    iphone开发中使用nib(xib)文件的内存管理
    Resource Management in View Controllers
    Linux_RHEL7_YUM
    Python基本语法_函数_返回值
    Python基本语法_函数_返回值
  • 原文地址:https://www.cnblogs.com/lubiao/p/4854576.html
Copyright © 2011-2022 走看看