Chinaunix首页 | 论坛 | 博客
  • 博客访问: 505265
  • 博文数量: 157
  • 博客积分: 3010
  • 博客等级: 中校
  • 技术积分: 1608
  • 用 户 组: 普通用户
  • 注册时间: 2008-08-16 09:30
文章存档

2010年(155)

2008年(2)

我的朋友

分类:

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函数,这里也就是上面为什么说注册时会调用的原因了。

好,文章己经太长,本文先结束,待下文继续分解。

阅读(2801) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

zanget2010-07-22 16:47:40

i2c_dirver 没见到... “书中暗表”也不知从哪里能看出。。。