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

    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驱动的整体架构、核心原理等可以在网上搜索其他相关文章学习。

    本文主要参考了Linux内核源码目录下的 ./Documentation/i2c/writing-clients 文档。以手头的一款视频采集芯片TVP5158为驱动目标,编写Linux I2C设备驱动。

1.   i2c_driver结构体对象     

    每一个I2C设备驱动,必须首先创造一个i2c_driver结构体对象,该结构体包含了I2C设备探测和注销的一些基本方法和信息,示例如下:

  1. static struct i2c_driver tvp5158_i2c_driver = {  
  2.         .driver = {  
  3.             .name = "tvp5158_i2c_driver",  
  4.         },   
  5.         .attach_adapter = &tvp5158_attach_adapter,  
  6.         .detach_client  = &tvp5158_detach_client,  
  7.         .command        = NULL,  
  8. };  

    其中,name字段标识本驱动的名称(不要超过31个字符),attach_adapter和detach_client字段为函数指针,这两个函数在I2C设备注册的时候会自动调用,需要自己实现这两个函数,后面将详细讲述。

2.   i2c_client 结构体对象

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

  1. struct tvp5158_obj{        
  2.     struct i2c_client client;        
  3.     int users; // how many users using the driver    
  4. };  
  5.  
  6. struct tvp5158_obj* g_tvp5158_obj;  

    其中,users为示例,用户可以自己在tvp5158_obj这个结构体里面添加感兴趣的字段,但是i2c_client字段不可少。具体用法后面再详细讲。

3.   设备注册及探测功能

    这一步很关键,按照标准的要求来写,则Linux系统会自动调用相关的代码去探测你的I2C设备,并且添加到系统的I2C设备列表中以供后面访问。

    我们知道,每一个I2C设备芯片,都通过硬件连接设定好了该设备的I2C设备地址。因此,I2C设备的探测一般是靠设备地址来完成的。那么,首先要在驱动代码中声明你要探测的I2C设备地址列表,以及一个宏。示例如下:

  1. static unsigned short normal_i2c[] = {  
  2.         0xbc >> 1,  
  3.         0xbe >> 1,  
  4.         I2C_CLIENT_END  
  5. };  
  6. I2C_CLIENT_INSMOD;  

    normal_i2c 数组包含了你需要探测的I2C设备地址列表,并且必须以I2C_CLIENT_END作为结尾,注意,上述代码中的0xbc和0xbe是我在硬件上为我的tvp5158分配的地址,硬件上我支持通过跳线将该地址设置为 0xbc 或者 0xbe,所以把这两个地址均写入到探测列表中,让系统进行探测。如果你的I2C设备的地址是固定的,那么,这里可以只写你自己的I2C设备地址,注意必须向右移位1。

    宏 I2C_CLIENT_INSMOD 的作用网上有许多文章进行了详细的讲解,这里我就不详细描述了,记得加上就行,我们重点关注实现。

    下一步就应该编写第1步中的两个回调函数,一个用于注册设备,一个用于注销设备。探测函数示例如下:

  1. static int tvp5158_attach_adapter(struct i2c_adapter *adapter)  
  2. {  
  3.     return i2c_probe(adapter, &addr_data, &tvp5158_detect_client);  

    这个回调函数系统会自动调用,我们只需要按照上述代码形式写好就行,这里调用了系统的I2C设备探测函数,i2c_probe(),第三个参数为具体的设备探测回调函数,系统会在探测设备的时候调用这个函数,需要自己实现。示例如下:

  1. static int tvp5158_detect_client(struct i2c_adapter *adapter,int address,int kind)  
  2. {  
  3.     struct tvp5158_obj *pObj;  
  4.     int err = 0;  
  5.  
  6.     printk(KERN_INFO "I2C: tvp5158_detect_client at address %x ...\n", address);  
  7.  
  8.     if( g_tvp5158_obj != NULL  ) {  
  9.         //already allocated,inc user count, and return the allocated handle  
  10.         g_tvp5158_obj->users++;  
  11.         return 0;  
  12.     }  
  13.  
  14.     /* alloc obj */ 
  15.     pObj = kmalloc(sizeof(struct tvp5158_obj), GFP_KERNEL);  
  16.     if (pObj==0){  
  17.         return -ENOMEM;  
  18.     }  
  19.     memset(pObj, 0, sizeof(struct tvp5158_obj));  
  20.     pObj->client.addr    = address;  
  21.     pObj->client.adapter = adapter;  
  22.     pObj->client.driver  = &tvp5158_i2c_driver;  
  23.     pObj->client.flags   = I2C_CLIENT_ALLOW_USE;  
  24.     pObj->users++;  
  25.  
  26.     /* attach i2c client to sys i2c clients list */ 
  27.     if((err = i2c_attach_client(&pObj->client))){  
  28.         printk( KERN_ERR "I2C: ERROR: i2c_attach_client fail! address=%x\n",address);  
  29.         return err;  
  30.     }  
  31.  
  32.     // store the pObj  
  33.     g_tvp5158_obj = pObj;  
  34.  
  35.     printk( KERN_ERR "I2C: i2c_attach_client ok! address=%x\n",address);  
  36.  
  37.     return 0;  

    到此为止,探测并且注册设备的代码已经完成,以后对该  I2C 设备的访问均可以通过 g_tvp5158_obj 这个全局的指针进行了。

相关推荐