前段时间写了一篇文章,《手把手教你写Linux I2C设备驱动》,是基于Linux2.6.18内核,使用的是老的I2C Client驱动模型,这篇文章基于Linux2.6.32内核,采用新的I2C Client驱动模型,给出I2C驱动的编写示例,主要给出驱动层代码的示例,其他内容参考《Linux下读写芯片的I2C寄存器》《用户空间访问I2C设备驱动》这两篇文章。

    闲话不说,先给出完整的示例代码,加好注释,后面再进一步解释。

  1. //////////////////////////////////////////////////////////////////////////    
  2. //  COPYRIGHT NOTICE    
  3. //  Copyright (c) 2012, 华中科技大学 卢俊(版权声明)    
  4. //  All rights reserved.    
  5. //     
  6. /// @file    mydev.c      
  7. /// @brief   i2c driver示例代码  
  8. ///    
  9. /// Linux2.6.32 new i2c driver model example  
  10. ///    
  11. /// @version 1.0       
  12. /// @author  lujun     
  13. /// @E-mail  lujun.hust@gmail.com    
  14. /// @date    2012/08/24    
  15. //    
  16. //    
  17. //  修订说明:    
  18. //////////////////////////////////////////////////////////////////////////    
  19.   
  20. #include <linux/kernel.h>  
  21. #include <linux/module.h>  
  22. #include <linux/fs.h>  
  23. #include <linux/slab.h>  
  24. #include <linux/init.h>  
  25. #include <linux/list.h>  
  26. #include <linux/i2c.h>  
  27. #include <linux/i2c-dev.h>  
  28. #include <linux/smp_lock.h>  
  29. #include <linux/jiffies.h>  
  30. #include <asm/uaccess.h>  
  31. #include <linux/delay.h>  
  32.   
  33. #define I2C_DEV_NAME "MyDevice" //这个名字要跟board_info中的名字一致,才会与I2C_Client匹配  
  34.   
  35. static struct i2c_device_id my_id[] = {  
  36.     {I2C_DEV_NAME,0}, //作为i2c client 与 driver 匹配的关键词  
  37.     {}  
  38. };  
  39.   
  40. MODULE_DEVICE_TABLE(i2c, my_id);  
  41.   
  42. //I2c client对象指针,给本模块i2c read/write提供参数,在probe成功后获取 
  43. static struct i2c_client *my_client;  
  44.   
  45. //新的I2C Client模型采用probe方式,老的接口采用的是attach_adapter  
  46. static int my_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id)  
  47. {  
  48.     //系统探测到了本模块的i2c设备,保存得到的i2c对象指针,供read/write参数  
  49.     my_client = client;  
  50.   
  51.     return 0;  
  52. }  
  53.   
  54. static int my_i2c_remove(struct i2c_client *client)
  55. {
  56. //释放I2C Client对象
  57. if( my_client != NULL ) {
  58. i2c_unregister_device(my_client);
  59. }
  60. return 0;  
  61. }
  62.   
  63. static struct i2c_driver my_driver = {  
  64.     .driver   = {  
  65.         .name  = "my i2c driver",  
  66.         .owner = THIS_MODULE,  
  67.     },  
  68.     .probe    = my_i2c_probe,  
  69.     .remove   = __devexit_p(my_i2c_remove),  
  70.     .id_table = my_id,  
  71. };  
  72.   
  73. static int __init my_i2c_init(void)  
  74. {  
  75.     //i2c_add_driver函数内部会根据my_driver提供的id_table在注册到系统中的boardinfo列表中找到匹配的client,然后调用my_driver的probe函数,将构造出来的i2c client传递过来  
  76.     return i2c_add_driver(&my_driver);  
  77. }  
  78.   
  79. static void __exit my_i2c_exit(void)  
  80. {  
  81.     //删除驱动,解除绑定  
  82.     i2c_del_driver(&my_driver);  
  83. }  
  84.   
  85. int my_i2c_write( uint8_t reg,uint8_t data )  
  86. {  
  87.     unsigned char buffer[2];  
  88.     buffer[0] = reg;  
  89.     buffer[1] = data;  
  90.   
  91.     if( 2!= i2c_master_send(my_client,buffer,2) ) {  
  92.         printk( KERN_ERR "my i2c send fail! \n" );  
  93.         return -1;  
  94.     }  
  95.     return 0;  
  96. }  
  97.   
  98. int my_i2c_read( uint8_t reg,uint8_t *data )  
  99. {  
  100.     // write reg addr  
  101.     if( 1!= i2c_master_send(my_client,&reg,1) ) {  
  102.         printk( KERN_ERR "my i2c recv fail! \n" );  
  103.         return -1;  
  104.     }  
  105.     // wait  
  106.     msleep(10);  
  107.     // read  
  108.     if( 1!= i2c_master_recv(my_client,data,1) ) {  
  109.         printk( KERN_ERR "my i2c recv fail! \n" );  
  110.         return -1;  
  111.     }  
  112.   
  113.     return 0;  
  114. }  
  115.   
  116. MODULE_DESCRIPTION("my i2c driver");  
  117. MODULE_AUTHOR("Lujun @HUST");  
  118. MODULE_LICENSE("GPL");  
  119.   
  120. module_init(my_i2c_init);  
  121. module_exit(my_i2c_exit);  

    上面的代码还不能完全成功运行,因为还没有添加自己的I2C设备信息到系统中,模块Probe函数不会被调用执行。注释中已经提到,i2c_add_driver的时候会扫描本模块的 id_table 中的名称是否与注册到系统中的boardinfo列表中有名称匹配的client,如果有,则会构造 i2c_client 对象,并调用本模块的 probe 函数。

    那么,如何注册自己的i2c设备信息到系统的 boardinfo 列表中呢?Linux内核文档:Documentation/i2c/instantiating-devices 中讲了多种方式,我在此只说2种方式。

    第一种方式,在内核的初始化中定义你的I2C设备的信息。比如在/arch/arm/mach-xxxx/board_xxxx.c 中添加一个新的 Boardinfo信息:

  1. static struct i2c_board_info __initdata i2c_info[] =  { 
  2.         { 
  3.                 I2C_BOARD_INFO("24c256", 0x50), 
  4.                 .platform_data  = &eeprom_info, 
  5.         }, 
  6.         { 
  7.                 I2C_BOARD_INFO("MyDevice", (0xbc>>1)), 
  8.                 .platform_data  = NULL, 
  9.         }, 
  10. }; 

     注意,添加的新的 I2C_BOARD_INFO的名称一定要与本模块的driver的名称字符串一致,地址是I2C设备地址右移1位以后的地址。

     第二种方式,使用i2c_new_device()。相关函数如下:

  1. //这个函数将会使用info提供的信息建立一个i2c_client并与第一个参数指向的i2c_adapter绑定。返回的参数是一个i2c_client指针。 
  2. struct i2c_client * i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info); 
  3.  
  4. //获取i2c_adapter的函数如下,它的参数是i2c总线编号。 
  5. struct i2c_adapter* i2c_get_adapter(int id); 
  6.  
  7. //如何知道i2c总线编号呢 
  8. ls /sys/class/i2c-adapter/  
  9. //即可看到对应的I2C adapter编号,i2c-0代表编号为0,i2c-1代表编号为1 

     那么,使用第二种方式的示例代码如下,在 my_i2c_init 函数开头,添加如下代码,动态注册 I2C 设备信息到内核 Boardinfo 列表中。     

  1. //自定义boardinfo信息  
  2. static struct i2c_board_info my_dev_info[] __initdata = {  
  3.     {  
  4.         I2C_BOARD_INFO(I2C_DEV_NAME,I2C_DEV_ADDR),  //设备名称和地址(很关键)  
  5.         .platform_data= NULL,    // 传递私有数据,赋值给client->dev->platform_data  
  6.     },  
  7. };  
  8.   
  9. //ls /sys/class/i2c-adapter/得到的编号  
  10. static int sys_adap_bus_num = 0;   
  11.   
  12. static int __init my_i2c_init(void)  
  13. {  
  14.     struct i2c_adapter* adap = i2c_get_adapter(sys_adap_bus_num);  
  15.    
  16.     if(adap==NULL) {  
  17.         printk("[LUJUN-DEBUG] i2c_get_adapter fail!\n");  
  18.         return -1;  
  19.     }  
  20.   
  21.     //动态构造i2c client对象 
  22.     my_client = i2c_new_device(adap, &my_dev_info[0]);  
  23.     if( my_client==NULL ){  
  24.         printk("[LUJUN-DEBUG] i2c_new_device fail!\n");  
  25.         return -1;  
  26.     }  
  27.       
  28.     //释放资源  
  29.     i2c_put_adapter(adap);  
  30.   
  31.     //函数内部会找到匹配的driver和client,然后调用probe  
  32.     return i2c_add_driver(&my_driver);  
  33. }  

    到此为止,我想基于Linux2.6.32的新的I2C Driver模型的编写示例已经基本上说清楚了。如果发现文中有错误的地方或者不清楚的地方,欢迎留言或者来信交流lujun.hust@gmail.com