分类:
2010-04-07 10:49:22
实际上在较新的代码中(如笔者现在用的linux-2.6.30)里面其实己经有一个通用的I2C驱动了。所以在一些简单的场合,我们其实可以不用再去写驱动,只要会用就可以了,但是会用也不是一件很简单的事情,因为关于这方面的内容很少,有些时候,我们不得不去分析一下代码,才能明白如何去用。
I2C的代码是比较少的,因为协议本身也不是很复杂。我们可以从他的代码目录开始讲起。
Linux中,关于I2C的驱动代码C文件基本上都放在drivers/i2c目录下。
此文件夹包含了linux系统里的i2c实现的主要代码。
内容包括三个子文件夹,和三个.c文件。
子文件夹:
主要包含的是一个些总线传递数据时的时序算法。
包含的是不同平台上的i2c总线低层驱动方法。
包含的是一个些己知芯的驱动方法。
.c文件
,这个文件是i2c驱动代码核心,用于沟通VFS与低层实现。
i2c-dev.c这是一个通用的驱动,基本上大多数i2c驱动都可以通过调用这个操作。它在/dev下生成一个主设备号为89的i2c设备。它主要实现了与VFS中规定的操作。
包含一此板级信息。
总共的代码不是很多。正上面引用的那段所说,我们要关心的代码实在是不多,
从编译过的代码目录里,我们找到一些踪迹,我的代码树是针对mini2440进行编过的,通过查看.o文件,笔者发现,其实真正参加编译的只有如下几个文件:
根目录下的:i2c-core.c i2c-dev.c i2c-boardinfo.c
drivers/i2c/algos目录下的:i2c-algo-bit.c
drivers/i2c/busses目录下的:i2c-s3c2410.c
总共五个。文件虽少,所涉及的结构体嵌套却是十分复杂。笔者曾经试首整理,结果却发现头绪乱得很,终也没能整理出一个明白的线路来。
以笔者的经验来说,看一看<Linux设备驱动开发详解>这本书,是非常有益于理解的,但是笔者也发现,其中所整理的图表也不是完善,但在笔者看来,能整理到那个程度,却己经是相当不容易的了。
下面说一下上面所提到的五个文件,上面的三个在引用部他己有说明。下面的两个i2c-algo-bit.c主要是涉能一些算法相关的内容。而i2c-s3c2410.c则是平台相关的i2c适配器驱动,也即总线驱动。
这里首先要明白一些概念:
一、i2c-adapter,即i2c-适配器,这个东西对应s3c2440这块芯片面言,就是指片上的i2c-controller,就是i2c控制器。i2c adapter的作用的是产生总线时序,以读写i2c从设备。
二、i2c-client,即i2c从设备,从机,这个东西代表的是接到s3c2440芯片上的设备,如一个at24c08的eeprom芯片,或是ov9650摄像头芯中的摄像头控制总线(注:这个摄像头芯片中,官方给的芯片手册上提到的是sccb总线,实际上是一种弱化的i2c总线,我们可以利用i2c驱动来读写它,稍后我们也会演示)。
首先,我们先从i2c-dev.c这个文件开始分析,一一步步去看从上到下是怎样调用的。
这是一个模块化的驱动代码,所以我们先从初始化代码开始看:
static int __init i2c_dev_init(void) { int res; printk(KERN_INFO "i2c /dev entries driver\n"); res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); if (res) goto out; i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); if (IS_ERR(i2c_dev_class)) { res = PTR_ERR(i2c_dev_class); goto out_unreg_chrdev; } res = i2c_add_driver(&i2cdev_driver); if (res) goto out_unreg_class; return 0; out_unreg_class: class_destroy(i2c_dev_class); out_unreg_chrdev: unregister_chrdev(I2C_MAJOR, "i2c"); out: printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__); return res; }
为了简便起见,我们把这个代码不太重要的东西先拿掉,只看一个重要的,就变成:
static int __init i2c_dev_init(void) { int res; res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops); i2c_dev_class = class_create(THIS_MODULE, "i2c-dev"); res = i2c_add_driver(&i2cdev_driver); }
也就是说这个模块的初始化代码做了三件事:
一、注册一个字符设备驱动。mark1
二、向sysfs注册了一个类。
三、向上添加了一个驱动结构。这第三个动作不是内核标准动作,而是来自于i2c-core.c中。这个我们之后再谈。mark2
首先,我们来说这个字符设备。
字符设备,我想如果写过led驱动的人都可以理解了。重要的就两个部分,主次设备号和file_operations结构。
主设备号I2C_MAJOR定义于include/linux/i2c-dev.h
原定义为:
#define 89 /* Device major number */
而次设备号则不在这里定义。
file_operations结构原文件中初始化如下:
static const struct file_operations i2cdev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .read = i2cdev_read, .write = i2cdev_write, .unlocked_ioctl = i2cdev_ioctl, .open = i2cdev_open, .release = i2cdev_release, };
下面我们则要简要说明一下各个函数的作用,并介绍一些结构体。
通常使用一个设备的第一步,就是打开该设备的设备结点,我们来看看打开这个设备时会发生什么:
这里我们还先把这个文件简化,只抽出重要的部分,大家可以在自己的代码中查看原文,也可以打开on.usr.cc,搜索这个函数名。
static int i2cdev_open(struct inode *inode, struct file *file) { unsigned int minor = iminor(inode); struct i2c_client *client; struct i2c_adapter *adap; struct i2c_dev *i2c_dev; i2c_dev = i2c_dev_get_by_minor(minor) adap = i2c_get_adapter(i2c_dev->adap->nr); client = kzalloc(sizeof(*client), GFP_KERNEL); i2c_put_adapter(adap); client->driver = &i2cdev_driver; client->adapter = adap; file->private_data = client; }
这里第一个动作是i2c_dev_get_by_minor.这个函数的作用是通过子设备号,得到一个i2c_dev结构。
i2c_dev结构是这个文件的头部定义的一个结构体:
struct i2c_dev { struct list_head list; struct i2c_adapter *adap; struct device *dev; };
它有三个成员,list_head list,表明这个结构最终是要组成链表的。i2c_adapter *adap指向一个i2c_adapter结构,而i2c_adapter结构代表硬件上的i2c适配器,也就是说通过这个结构,我们还要能访问适配器。最后一个是device *dev指向一个device结构,但从目前的知识看,还不知到这个device是做什么用的。因此我们在这里标上mark3以备后查。
而i2c_dev_get_by_minor函数简化版本如下:
static struct i2c_dev *i2c_dev_get_by_minor(unsigned index) { struct i2c_dev *i2c_dev; list_for_each_entry(i2c_dev, &i2c_dev_list, list) if (i2c_dev->adap->nr == index) return i2c_dev; }
也就是说,这里查询了一个链表(看上面有一段同样颜色的字),这个链表的数据项是i2c_dev结构组成的。我们通过i2c_dev访问了adap,也就是适配器,并找到了这个适配器结构中保存的一个成员nr, 其实就是次设备号。如果某一个i2c_dev 项所找到的次设备号与传入的次设备号相同,就是找到了,返回这个i2c_dev结构。
到现在为止我们讲得都是file_operations结构中的open函数的第一个动作,也就通过次设备号,找到对应的i2c_dev结构,而通过这个结构可以访问适配器和一个不知用处的device结构。次设备号是open时从设备节点得到的。适配器结构却还不知于何时何地初始化。
下面我们来讲open函数中的第二个动作,,这个函数动作另人不解,我们己经有了i2c_dev,他本来就指向一个adapter啊,为什么还调用一个函数去找呢?记得看过别人的文章,说是有什么讲究来着,不过不记得了。mark4.
总之这里找到了一个结构,也就是说可以用了。
open函数的第三个动作,生成一个i2c_client结构,第三个动作就是为它开辟一段内存区。
open函数第四个动作,i2c_put_adapter这个函数中只是调用了module_put(),也就是说减少adapter的引用计数,但是为什么呢?mark5
open函数第五、六个动作:
client->driver = &i2cdev_driver;
client->adapter = adap;
这里面i2cdev_driver 是在文件头部定义的一个全局的i2c_driver结构体。而adap则是我们通子次设备节点找到的那个adapter.
open的最后一个动作与我们写字符设备驱动时一样:
file->private_data = client;
以上讲的是open中的动作,在里面声明了一个i2c_dev结构体指针,但是用过后就不要了。但我们却知道了,系统维护着一个i2c_dev的链表。而通过i2c_dev可以找到adapter和device(未明作用)。另外,这个驱动的中心结构体是i2c_client,系统还有一个全局变量i2c_dirver i2cdev_driver.
书中暗表:这个i2cdev_driver可能是与i2c_client配套的。
至此我们接解的结构体有:
i2c_dev
i2c_client
i2c_driver
i2c_adapter
其中i2c_client就是在这里第一次进入历史舞台的。
自此以后,我们可以在任意地点从fp获得client,并从而访问adapter和i2c_driver 了,但我们还不知其出处。
下面我们将先不去讲file_operations中的其他函数,因为这样讲下去是没有意义的。我们先留着。所以
file_operations中除open处的所有函数。
好了,我们记下来了,然后我们先去看点别的。
i2c_dev中除了一个基本字符设备之外,还有点别的东西。
还记得我们在模块初始化函数里说的吗?那里面有三步动作,我们上面讲的是注册字符设备的那个动作的准备。
下面我们要说说另外两个动作了。
一、i2c_dev_class = class_create(THIS_MODULE, “i2c-dev”);
注意哦,这里的i2c_dev_class也是一个全局变量呢!
static struct class *i2c_dev_class;
这一句在我的文件中的516行,其实所有另外两个动作的实现代码也都是从这里开始的。
仔细读了代码之后,发现这个i2c_dev_class的第一次使用,就是在模板初始化代码中的第二个动作。也就是调用函数,来填充这个指针。
之后,i2c_dev_class还出现在两个函数里,紧挨着上面引用中的那句话后面是两个函数,和一个结构体的填充。之后便是模块初始化函数了。我把中间这段简化后帖出来:
static int i2cdev_attach_adapter(struct i2c_adapter *adap) { struct i2c_dev *i2c_dev; i2c_dev = get_free_i2c_dev(adap); i2c_dev->dev = device_create(i2c_dev_class, &adap->dev, MKDEV(I2C_MAJOR, adap->nr), NULL, "i2c-%d", adap->nr); res = device_create_file(i2c_dev->dev, &dev_attr_name); } static int i2cdev_detach_adapter(struct i2c_adapter *adap) { struct i2c_dev *i2c_dev; i2c_dev = i2c_dev_get_by_minor(adap->nr); device_remove_file(i2c_dev->dev, &dev_attr_name); return_i2c_dev(i2c_dev); } static struct i2c_driver i2cdev_driver = { .driver = { .name = "dev_driver", }, .attach_adapter = i2cdev_attach_adapter, .detach_adapter = i2cdev_detach_adapter, };
看到了吧,从上到下思路很清晰,两个函数是为了下面的结构体而写的,而结构是为了模块初始化函数中的第三步动作而写的。
我们先看第一个函数:i2cdev_attach_adapter
第一个动作get_free_i2c_dev,记得上面我们讲open时说的第一步,是通次设备号找到一个相应的i2c_dev结构,也就是遍历一个i2c_dev的表,从中去找,但是我们还不知那个表是何时生成的,现在我们依然不知道,但己经可以见些端倪,就在get_free_i2c_dev这个函数中,其作用就是初始化一个i2c_dev结构,用传入的adapter去填充其adap成员,并且将这个i2c_dev加入到i2c_dev_list表中(详情请点击上面的链接)。
现在我们知道了,i2c_dev_list成员的填加过程。但上面加入的i2c_dev只有一个adap成员被初始化了,还有一个device成员没有初始化,而这就是i2cdev_attach_adapter函数中第二个动作要做的:i2c_dev->dev = device_create(),这里调用一个函数,并用其返回值去初始化i2c_dev->dev, 也就是device 成员。第三步:device_create_file在sysfs下创建节点。这个函数是在这个driver被注册,或是adapter被注册时调用。
总之,在这里i2cdev_driver被初始化,然后的事情就是在模块初始化时调用i2c_add_driver,这个i2c_add_driver实际上是转而调了i2c-core.c中的i2c_add_driver来完成些功能,
static inline int i2c_add_driver(struct i2c_driver *driver) { return i2c_register_driver(THIS_MODULE, driver); }
而这个函数在这里先不想仔细研究,只想说里面最后有一段代码,就是轮询所有driver 并调用其attach_adapter函数,这里也就是上面为什么说注册时会调用的原因了。
好,文章己经太长,本文先结束,待下文继续分解。