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!\n");
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\n",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\n");
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\n");
 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\n",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\n",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\n");
 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\n");
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\n");
168     i2c_add_driver(&at24cxx_driver);
169     return 0;
170 }
171
172 static void at24cxx_exit(void)
173 {
174     printk("at24cxx_exit\n");
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.\n");
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.\n",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\n",rd_buf);
32     close(fd);
33     return 0;
34 }
时间: 2024-08-05 09:27:27

Linux I2C驱动编写要点的相关文章

SPI驱动编写要点

题外话:面对成功和失败,一个人有没有“冠军之心”,直接影响他的表现. 几周前剖析了Linux SPI 驱动框架,算是明白个所以然,对于这么一个庞大的框架,并不是每一行代码都要自己去敲,因为前人已经把这个框架搭建好了,作为驱动开发者的我们只需要搞清楚哪一部分是需要自己修改或重新编写就OK了. 结合Linux内核面向对象的设计思想,SPI总的设计思路大概是这样的: 第①处:内核中抽象了SPI控制器,让spi_master成为他的象征,他的实例化对象就是与硬生生的SPI控制器对应的,在Linux内核中

Smart210学习记录-----Linux i2c驱动

一:Linux i2c子系统简介: 1.Linux 的 I2C 体系结构分为 3 个组成部分: (1) I2C 核心. I2C 核心提供了 I2C 总线驱动和设备驱动的注册.注销方法,I2C 通信方法(即“algorithm”)上层的.与具体适配器无关的代码以及探测设备.检测设备地址的上层代码等. (2) I2C 总线驱动. I2C 总线驱动是对 I2C 硬件体系结构中适配器端的实现,适配器可由 CPU 控制,甚至可以直接集成在 CPU 内部. I2C 总线驱动主要包含了 I2C 适配器数据结构

linux设备驱动编写_tasklet机制(转)

在编写设备驱动时, tasklet 机制是一种比较常见的机制,通常用于减少中断处理的时间,将本应该是在中断服务程序中完成的任务转化成软中断完成. 为了最大程度的避免中断处理时间过长而导致中断丢失,有时候我们需要把一些在中断处理中不是非常紧急的任务放在后面执行,而让中断处理程序尽快返回.在老版本的 linux 中通常将中断处理分为 top half handler . bottom half handler .利用 top half handler 处理中断必须处理的任务,而 bottom hal

Linux I2C驱动--用户态驱动简单示例

1. Linux内核支持I2C通用设备驱动(用户态驱动:由应用层实现对硬件的控制可以称之为用户态驱动),实现文件位于drivers/i2c/i2c-dev.c,设备文件为/dev/i2c-0 2. I2C通用设备驱动以字符设备注册进内核的 static const struct file_operations i2cdev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = i2cdev_read, .write = i2cde

Linux内核调用I2C驱动_以MPU6050为例

Linux内核调用I2C驱动_以MPU6050为例 0. 导语 最近一段时间都在恶补数据结构和C++,加上导师的事情比较多,Linux内核驱动的学习进程总是被阻碍.不过,十一假期终于没有人打扰,有这个奢侈的大块时间,可以一个人安安静静的在教研室看看Linux内核驱动的东西.按照Linux嵌入式学习的进程,SPI驱动搞完了之后就进入到I2C驱动的学习当中,十一还算是比较顺利,I2C的Linux驱动完成了. 为了测试I2C是否好用,选择一个常用的I2C传感器,手头有个MPU6050,刚好作为I2C的

v4l2驱动编写篇【转】

转自:http://blog.csdn.net/michaelcao1980/article/details/53008418 大部分所需的信息都在这里.作为一个驱动作者,当挖掘头文件的时候,你可能也得看看include/media/v4l2-dev.h,它定义了许多你将来要打交道的结构体. 一个视频驱动很可能要有处理PCI总线,或USB总线的部分.这里我们不会花什么时间还接触这些东西.通常会有一个内部一I2C接口,我们在这一系列的后续文章中会接触到它.然后还有一个V4L2的子系统接口.这个子系

I.MX6 Linux I2C device&amp; driver hacking

/******************************************************************************************* * I.MX6 Linux I2C device& driver hacking * 声明: * 1. 本文主要是对Linux I2C驱动进行代码跟踪,主要是为了能够对I2C驱动框架有个全面的了解: * 2. 本文源代码来自myzr_android4_2_2_1_1_0.tar.bz2: * 3. 如果你有兴趣,

Linux I2C设备驱动编写(二)

在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c_client.三者的关系也在上一节进行了描述.应该已经算是对Linux I2C子系统有了初步的了解.下面再对他们之间的关系进行代码层的深入分析,我认为对他们的关系了解的越好,越有助于I2C设备的驱动开发及调试. 带着问题去分析可能会更有帮助吧,通过对(一)的了解后,可能会产生以下的几点疑问: i2c_adapter驱动如何添加? i2c_client与i2c_board_info究竟是什么

Linux I2C设备驱动编写(一)

在Linux驱动中I2C系统中主要包含以下几个成员: I2C adapter 即I2C适配器 I2C driver 某个I2C设备的设备驱动,可以以driver理解. I2C client 某个I2C设备的设备声明,可以以device理解. I2C adapter 是CPU集成或外接的I2C适配器,用来控制各种I2C从设备,其驱动需要完成对适配器的完整描述,最主要的工作是需要完成i2c_algorithm结构体.这个结构体包含了此I2C控制器的数据传输具体实现,以及对外上报此设备所支持的功能类型