手把手教你写Linux I2C设备驱动

Linux I2C驱动是嵌入式Linux驱动开发人员常常须要编写的一种驱动,由于凡是系统中使用到的I2C设备,几乎都须要编写相应的I2C驱动去配置和控制它,例如 RTC实时时钟芯片、音视频采集芯片、音视频输出芯片、EEROM芯片、AD/DA转换芯片等等。linux

    

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设备注册的时候会自动调用,须要本身实现这两个函数,后面将详细讲述。.net

 

2. i2c_client 结构体对象指针

    

上面定义的i2c_driver对象,抽象为一个i2c的驱动模型,提供对i2C设备的探测和注销方法,而i2c_client结构体则是表明着一个具体的i2c设备,该结构体有一个data指针,能够指向任何私有的设备数据,在复杂点的驱动中可能会用到。示例以下:  orm

 

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设备,这个方法我会在后面的文章中讲述。

 

总结和说明

   

免费学习更多精品课程,登陆乐搏学院官网http://www.learnbo.com/

或关注咱们的官方微博微信,还有更多惊喜哦~

 

 

本文出自 “Jhuster的专栏” 博客,请务必保留此出处http://ticktick.blog.51cto.com/823160/760020

相关文章
相关标签/搜索