分类: LINUX
2012-01-31 14:39:12
第一章开头已经说明过i2c驱动的两种模式,这一张主要说明第二种模式的驱动如何编写。和上一章一样,本章内容只涉及设备驱动,不涉及适配器驱动的编写。
板级配置信息I2C的Standard driver model驱动中不需要创建i2c_client,可以选择支持内核配置的i2c设备。
那么设备的地址的信息是怎么得到的呢?这些信息保存在硬件相关的内核源文件中。对于smartarm3250来说,这个源文件是arc/arm/mach-lpc3250/board-smartarm3250.c。在这个文件中i2c相关的代码如程序清单 2.1所示。
2.1 i2c板级信息
/* arc/arm/mach-lpc3250/board-smartarm3250.c */
#if defined(CONFIG_RTC_DRV_PCF8563)
static struct i2c_board_info __initdata smartarm3250_i2c_board_info [] = {
{
I2C_BOARD_INFO("rtc-pcf8563", 0x51),
},
};
#endif
·····················
void __init smartarm3250_board_init(void)
{
·····················
#if defined(CONFIG_RTC_DRV_PCF8563)
/* I2C based RTC device on I2C2 */
i2c_register_board_info(1, smartarm3250_i2c_board_info,
ARRAY_SIZE(smartarm3250_i2c_board_info));
#endif
····················
}
从这段程序我们可以看出smartarm3250的板子默认支持的i2c设备只有一个,那就是RTC芯片,但是支持RTC芯片需要配置内核的时候选上CONFIG_RTC_DRV_PCF8563相关的选项。
程序清单 2.1中定义的i2c_board_info数组填充了设备的名字和地址。i2c_board_info的定义如程序清单 2.2所示。
2.2 i2c_board_info
/* include/linux/i2c.h */
struct i2c_board_info {
char type[I2C_NAME_SIZE];
unsigned short flags;
unsigned short addr;
void *platform_data;
int irq;
};
其中的type成员其实是设备的名字,flags用来初始化i2c_client .flags,addr是设备的地址,platform_data用来初始化i2c_client.dev.platform_data,irq是设备用到的中断。初始化i2c_board_info变量至少应该初始化名字和地址两个成员。
Standard driver model类型的驱动的特征是i2c_client不再需要自己创建,但是仍然可细分为两种类型。第一种就是使用内核中配置的i2c设备信息,第二种就是使用自己定义的设备地址。这两种驱动并不冲突,但是为了简单起见,我们将这两个功能分开讲。
I2C设备在Linux内核目录的文档documentation/i2c/upgrading-clients中有一个Standard driver model的驱动例程,支持内核配置信息提供的设备,如程序清单 2.3所示。
2.3 Standard driver model驱动例程
struct example_state {
struct i2c_client *client;
....
};
static int example_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct example_state *state;
struct device *dev = &client->dev;
state = kzalloc(sizeof(struct example_state), GFP_KERNEL);
if (state == NULL) {
dev_err(dev, "failed to create our state/n");
return -ENOMEM;
}
state->client = client;
i2c_set_clientdata(client, state);
/* rest of the initialisation goes here. */
dev_info(dev, "example client created/n");
return 0;
}
static int __devexit example_remove(struct i2c_client *client)
{
struct example_state *state = i2c_get_clientdata(client);
kfree(state);
return 0;
}
static struct i2c_device_id example_idtable[] = {
{ "example", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, example_idtable);
static struct i2c_driver example_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "example",
},
.id_table = example_idtable,
.probe = example_probe,
.remove = __devexit_p(example_remove),
};
和程序清单 1.4比较本程序主要有三点不同:
(1) 驱动中不再需要创建i2c_client结构体,它是由i2c内核创建的。
(2) 驱动中不需要定义设备的地址,取而代之的是i2c_device_id,用来保存支持的设备类型。这里面保存的设备名将会和程序清单 2.1中的i2c_board_info的名字进行比较,在i2c_device_id中存在的名字才能依附于本驱动。
(3) i2c_driver函数指针成员只需要初始化probe和remove就就够了,不需要程序清单 1.4中定义的函数。其它的函数都是可选的。
特别需要注意的是,如果同时初始化两种模式需要用到的i2c_driver的成员,那么会报错,因为i2c内核无法判断是哪种模式的驱动。i2c_driver中的probe、remove、detect任何一个被初始化意味着这是一个Standard driver model模式的驱动,attach_adapter和detach_adapter绝对不可以初始化。
上一小节的驱动用来支持内核中已经配置的设备,主要定义了probe和remove两个函数接口。如果想要i2c核心探测自己模块中定义的地址,需要初始化i2c_driver中的detect和address_data成员。将上一小节的例程修一下,如程序清单 2.4所示。
2.4 使用自定义地址的standard driver
#define CHIP_NAME "example"
static struct i2c_driver example_driver;
static unsigned short normal_addr[] = { OUR_ADDR, I2C_CLIENT_END };
I2C_CLIENT_INSMOD;
int example_detect(struct i2c_client *client, int kind, struct i2c_board_info *info)
{
if(client->adapter== example_driver->adapter && info->addr== OUR_ADDR) {
strlcpy(info->type, CHIP_NAME, I2C_NAME_SIZE);
return 0;
}
else
return –ENODEV;
}
static struct i2c_driver example_driver = {
.driver = {
.owner = THIS_MODULE,
.name = CHIP_NAME,
},
.detect = example_detect,
. address_data = &addr_data,
};
程序清单 2.4可以实现和程序清单 1.4同样的功能,但是代码更少。因此没有特殊情况我们应该使用这种风格的驱动。
需要注意的地方有三点:
(1) example_detect的参数client并不是最终要和example_driver建立联系的client,所以不能保存这个指针以备之后使用,应该在i2c_driver注册完毕之后遍历它的client链表得到每一个和它建立连接的设备client。但是最终注册的client的driver成员不指向任何驱动。
(2) client参数只有adapter和addr成员可用,info只有addr成员可用。example_detect函数应该判断携带info的client是否能被支持,能的话则至少填充info->type,info的其它成员也可以填充,但不应该修改addr。
(3) example_detect返回任何0和-ENODEV之外的值,都将终止对当前适配器的探测。
核心如何实现在程序清单 2.1中我们发现,smartarm3250_i2c_board_info被注册为板级配置信息,每一个i2c_adapter注册的时候都会探测板级信息中注册的设备地址,探测成功的则建立client注册进i2c核心。当i2c_driver的driver成员注册的时候,I2C核心会将已经注册的client名字和该驱动的id_table进行名字的比较,比较成功的则建立i2c_driver和i2c_client之间的附属关系。这就是为什么程序清单 2.3中定义example_idtable的原因。为了支持多个设备,可以给example_idtable多加几个成员。
程序清单 2.4的功能又是如何实现的呢?首先程序清单 2.4中的example_driver的注册流程和程序清单 1.4中是类似的。区别在于第二类Standard driver model驱动注册的时候i2c核心会在__attach_adapter中调用i2c_detect函数,这个函数可以探测i2c_driver. address_data中的地址,为探测成功的设备创建client并注册。
现在总结一下i2c_adapter、i2c_driver和i2c_client的关系。
首先i2c_adapter代表具体的适配器,拥有控制I2c总线的方法。具体的通信任务就是由它完成的。
i2c_client代表具体的设备,它包含了设备使用的地址、名字等资源,也包含了设备依附的驱动和适配器的指针,还包含了发送消息时的配置信息在flags成员中。一个client注册进某个i2c_adapter实例,其它的设备就不能在同一个适配器上占用同一个地址了。i2c_adapter也保存了依附它的client链表,这样它就能知道哪些地址被占用了。
i2c_driver是一个辅助性的结构体。一般由驱动开发人员自己创建,它的主要任务是提供需要探测的地址和支持的设备名单,还要判定是否愿意支持一个特定client。i2c_driver更像一个用户,提供需求和产品检验。
FROM:http://blog.csdn.net/yuanlulu/article/details/6161698