linux 下IIC设备驱动(old style)编写
2014-03-23 18:18
459 查看
手把手教你写Linux I2C设备驱动
2012-01-08 15:00:58
标签:Linux设备
驱动
详解
i2c
原创作品,允许转载,转载时请务必以超链接形式标明文章
原始出处 、作者信息和本声明。否则将追究法律责任。http://ticktick.blog.51cto.com/823160/760020
Linux I2C驱动是嵌入式Linux驱动开发人员经常需要编写的一种驱动,因为凡是系统中使用到的I2C设备,几乎都需要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。
Linux I2C驱动涉及的知识点还是挺多的,主要分为Linux I2C的总线驱动(I2C BUS Driver)和设备驱动(I2C Clients Driver),本文主要关注如何快速地完成一个具体的I2C设备驱动(I2C Clients Driver)。关于Linux I2C驱动的整体架构、核心原理等可以在网上搜索其他相关文章学习。
注意:本系列文章的I2C设备驱动是基于Linux 2.6.18内核。
本文主要参考了Linux内核源码目录下的 ./Documentation/i2c/writing-clients 文档。以手头的一款视频采集芯片TVP5158为驱动目标,编写Linux I2C设备驱动。
1. i2c_driver结构体对象
每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象,该结构体包含了I2C设备探测和注销的一些基本方法和信息,示例如下:
static struct i2c_driver tvp5158_i2c_driver = {
.driver = {
.name = "tvp5158_i2c_driver",
},
.attach_adapter = &tvp5158_attach_adapter,
.detach_client = &tvp5158_detach_client,
.command = NULL,
};
其中,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detach_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数,后面将详细讲述。
2. i2c_client 结构体对象
上面定义的i2c_driver对象,抽象为一个i2c的驱动模型,提供对i2C设备的探测和注销方法,而i2c_client结构体则是代表着一个具体的i2c设备,该结构体有一个data指针,可以指向任何私有的设备数据,在复杂点的驱动中可能会用到。示例如下:
struct tvp5158_obj{
struct i2c_client client;
int users; // how many users using the driver
};
struct tvp5158_obj* g_tvp5158_obj;
其中,users为示例,用户可以自己在tvp5158_obj这个结构体里面添加感兴趣的字段,但是i2c_client字段不可少。具体用法后面再详细讲。
3. 设备注册及探测功能
这一步很关键,按照标准的要求来写,则Linux系统会自动调用相关的代码去探测你的I2C设备,并且添加到系统的I2C设备列表中以供后面访问。
我们知道,每一个I2C设备芯片,都通过硬件连接设定好了该设备的I2C设备地址。因此,I2C设备的探测一般是靠设备地址来完成的。那么,首先要在驱动代码中声明你要探测的I2C设备地址列表,以及一个宏。示例如下:
static unsigned short normal_i2c[] = {
0xbc >> 1,
0xbe >> 1,
I2C_CLIENT_END
};
I2C_CLIENT_INSMOD;
normal_i2c 数组包含了你需要探测的I2C设备地址列表,并且必须以I2C_CLIENT_END作为结尾,注意,上述代码中的0xbc和0xbe是我在硬件上为我的tvp5158分配的地址,硬件上我支持通过跳线将该地址设置为 0xbc 或者 0xbe,所以把这两个地址均写入到探测列表中,让系统进行探测。如果你的I2C设备的地址是固定的,那么,这里可以只写你自己的I2C设备地址,注意必须向右移位1。
宏 I2C_CLIENT_INSMOD 的作用网上有许多文章进行了详细的讲解,这里我就不详细描述了,记得加上就行,我们重点关注实现。
下一步就应该编写第1步中的两个回调函数,一个用于注册设备,一个用于注销设备。探测函数示例如下:
static int tvp5158_attach_adapter(struct i2c_adapter *adapter)
{
return i2c_probe(adapter, &addr_data, &tvp5158_detect_client);
}
这个回调函数系统会自动调用,我们只需要按照上述代码形式写好就行,这里调用了系统的I2C设备探测函数,i2c_probe(),第三个参数为具体的设备探测回调函数,系统会在探测设备的时候调用这个函数,需要自己实现。示例如下:
static int tvp5158_detect_client(struct i2c_adapter *adapter,int address,int kind)
{
struct tvp5158_obj *pObj;
int err = 0;
printk(KERN_INFO "I2C: tvp5158_detect_client at address %x ...\n", address);
if( g_tvp5158_obj != NULL ) {
//already allocated,inc user count, and return the allocated handle
g_tvp5158_obj->users++;
return 0;
}
/* alloc obj */
pObj = kmalloc(sizeof(struct tvp5158_obj), GFP_KERNEL);
if (pObj==0){
return -ENOMEM;
}
memset(pObj, 0, sizeof(struct tvp5158_obj));
pObj->client.addr = address;
pObj->client.adapter = adapter;
pObj->client.driver = &tvp5158_i2c_driver;
pObj->client.flags = I2C_CLIENT_ALLOW_USE;
pObj->users++;
/* attach i2c client to sys i2c clients list */
if((err = i2c_attach_client(&pObj->client))){
printk( KERN_ERR "I2C: ERROR: i2c_attach_client fail! address=%x\n",address);
return err;
}
// store the pObj
g_tvp5158_obj = pObj;
printk( KERN_ERR "I2C: i2c_attach_client ok! address=%x\n",address);
return 0;
}
到此为止,探测并且注册设备的代码已经完成,以后对该 I2C 设备的访问均可以通过 g_tvp5158_obj 这个全局的指针进行了。
4. 注销I2C设备
同理,设备注销的回调函数也会自动被系统调用,只需要按照模板写好设备注销代码,示例如下:
static int tvp5158_detach_client(struct i2c_client *client)
{
int err;
if( ! client->adapter ){
return -ENODEV;
}
if( (err = i2c_detach_client(client)) ) {
printk( KERN_ERR "Client deregistration failed (address=%x), client not detached.\n", client->addr);
return err;
}
client->adapter = NULL;
if( g_tvp5158_obj ){
kfree(g_tvp5158_obj);
}
return 0;
}
到此为止,设备的注册和注销代码已经全部完成,下面要做的就是提供读写I2C设备的方法。
5. I2C设备的读写
对I2C设备的读写,Linux系统提供了多种接口,可以在内核的 i2c.h 中找到,这里简单介绍其中的两种接口。
【接口一】:
extern int i2c_master_send(struct i2c_client *,const char* ,int);
extern int i2c_master_recv(struct i2c_client *,char* ,int);
第一个参数是 i2c_client 对象指针,第二个参数是要传输的数据buffer指针,第三个参数为buffer的大小。
【接口二】:
extern int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msg, int num);
这个接口支持一次向I2C设备发送多个消息,每一个消息可以是读也可以是写,读或者写以及读写的目标地址(寄存器地址)均包含在msg消息参数里面。
这些接口仅仅是最底层的读写方法,关于具体怎么与I2C设备交互,比如具体怎么读芯片的某个特定寄存器的值,这需要看具体的芯片手册,每个I2C芯片都会有具体的I2C寄存器读写时序图。因此,为了在驱动中提供更好的访问接口,还需要根据具体的时序要求对这些读写函数进行进一步封装,这些内容将在后面的文章中讲述。
6. 模块初始化及其他
下一步就是整个模块的初始化代码和逆初始化代码,以及模块声明了。
static int __init tvp5158_i2c_init(void)
{
g_tvp5158_obj = NULL;
return i2c_add_driver(&tvp5158_i2c_driver);
}
static void __exit tvp5158_i2c_exit(void)
{
i2c_del_driver(&tvp5158_i2c_driver);
}
module_init(tvp5158_i2c_init);
module_exit(tvp5158_i2c_exit);
MODULE_DESCRIPTION("TVP5158 i2c driver");
MODULE_AUTHOR("Lujun @hust");
MODULE_LICENSE("GPL");
在初始化的代码里面,添加本模块的 i2c driver 对象,在逆初始化代码里面,删除本模块的 i2c driver 对象。
7. 总结
到此为止,算是从应用的角度把编写一个I2C的设备驱动代码讲完了,很多原理性的东西我都没有具体分析(其实我也了解的不深),以后会慢慢更深入地学习和了解,文中有什么讲述不正确的地方,欢迎留言或者来信lujun.hust@gmail.com交流。
读到最后,大家可能还有一个疑问,这个驱动写完了怎么在用户空间(应用层)去使用它呢?由于本文不想把代码弄得太多太复杂,怕提高理解的难度,所以就没有讲,其实要想在用户空间使用该I2C设备驱动,则还需要借助字符设备驱动来完成,即为这个I2C设备驱动封装一层字符设备驱动,这样,用户空间就可以通过对字符设备驱动的访问来访问I2C设备,这个方法我会在后面的文章中讲述。
要想在Linux下读写芯片的I2C寄存器,一般需要在Linux编写一份该芯片的I2C驱动,关于Linux下如何编写I2C驱动,前一篇文章《手把手教你写Linux I2C设备驱动》已经做了初步的介绍,并且留下了两个疑问尚未解决,第一个是如何对Linux提供的I2C操作函数进行进一步封装,实现对芯片寄存器的读写;另一个是如何在用户空间调用该I2C驱动代码。本文将讨论前一个问题。
首先,我们要了解Linux系统提供的I2C操作函数怎么使用,上篇文章已经提到过,对I2C设备的读写,Linux系统提供了多种接口,这些接口可以在内核的 i2c.h 中找到,这里我主要介绍下面这组读写接口:
extern int i2c_master_send(struct i2c_client *,const char* ,int);
extern int i2c_master_recv(struct i2c_client *,char* ,int);
第一个参数是 i2c_client 对象指针,第二个参数是要传输的数据buffer指针,第三个参数为buffer的大小。
接口函数已经有了,下面我们要解决的问题就是以何种形式/规则去使用这些接口才能正确地读写芯片的相关寄存器。
首先,我们需要查询芯片手册,找到芯片手册中,关于寄存器的I2C读写时序,其实,大多数芯片的I2C寄存器的读写时序都是一样的,下面我还是以手头的TVP5158芯片为例。
首先分析写操作,该芯片的手册中给出的I2C寄存器写时序图如下:
从上图可以看出,真正需要执行写操作的有两处,Step4 和 Step6 ,Step4首先写入寄存器的偏移地址,而Step6则是写入到该寄存器的值。由此已经很清楚了,对于写I2C寄存器,我们需要做的就是给 i2c_master_send 函数传入两个字节的数据即可,第一个字节为寄存器的地址,第二个字节为要写入寄存器的数据。示例如下:
static int tvp5158_i2c_write( struct i2c_client* client,uint8_t reg,uint8_t data)
{
unsigned char buffer[2];
buffer[0] = reg;
buffer[1] = data;
if( 2!= i2c_master_send(client,buffer,2) ) {
printk( KERN_ERR " tvp5158_i2c_write fail! \n" );
return -1;
}
return 0;
}
其实挺简单的,没有什么复杂的代码。下面再看看读时序。
由上图可以,读时序需要做的操作是,先向I2C总线上写入需要读的寄存器地址,然后读I2C总线上的值。代码写起来也不难,示例如下:
static int tvp5158_i2c_read( struct i2c_client* client,uint8_t reg,uint8_t *data)
{
// write reg addr
if( 1!= i2c_master_send(client,®,1) ) {
printk( KERN_ERR " tvp5158_i2c_read fail! \n" );
return -1;
}
// wait
msleep(10);
// read
if( 1!= i2c_master_recv(client,data,1) ) {
printk( KERN_ERR " tvp5158_i2c_read fail! \n" );
return -1;
}
return 0;
}
到此为止,Linux下具体如何封装读写芯片寄存器的方法已经介绍完毕,其实并不复杂,希望对初学者有所帮助,文中有什么讲述不正确的地方,欢迎留言或者来信lujun.hust@gmail.com交流。
相关文章推荐
- Linux下SPI和IIC驱动免在设备树上添加设备信息的编写方法
- 编写Linux网络设备驱动(上)
- linux驱动编写(字符设备编写框架)
- Linux设备驱动前的工作准备 ---- 内核的配置及Makefile编写
- Linux字符设备驱动编写流程
- Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动
- 编写Linux网络设备驱动(上)
- 编写Linux设备驱动程序教程
- linux驱动编写之十三(设备模型之sysfs,bus,device,driver源代码分析)
- Linux驱动编写(块设备驱动代码)
- Linux I2C设备驱动编写(二)
- 【转】Linux I2C设备驱动编写(一)
- linux驱动中的platform总线架构(含具体IIC设备驱动)
- Linux I2C设备驱动编写(一)
- 编写Linux网卡设备驱动(下)
- 入门文章:教你学会编写Linux设备驱动
- 编写Linux设备驱动的技术基础
- 【转】Linux I2C设备驱动编写(二)
- Linux I2C设备驱动编写
- Linux I2C设备驱动编写(一)