linux中iic驅動編寫—有設備樹&沒有設備樹


1.iic設備(client)注冊

1.1 老內核版本下沒有設備樹的情況

  在老內核版本下,可以使用如下三種方法注冊client:

  (1)i2c_register_board_info函數。以i2c_devs0為例,i2c_devs0是一個數組,里面是i2c0上所有的設備,i2c_register_board_info(0, i2c_devs0, ARRAY_SIZE(i2c_devs0));

  static struct i2c_board_info i2c_devs0[] __initdata = {

    {I2C_BOARD_INFO("wm8580", 0x1b),

    /*假如要添加設備,就在這里加*/},

  };

  查看i2c_devs0的定義,我們發現該數組內部都是i2c_board_info結構體,如果要添加設備到i2c0,只需在該數組中使用I2C_BOARD_INFO這個宏即可,第一個參數是名字,第二個參數是設備在i2c上的地址,此宏的本質就是填充一個struct i2c_board_info,這一步作用  是把wm8580以i2c設備的身份被注冊,並且綁定i2c0這個適配器。

     (2)static struct i2c_client *xx_client;

     static struct i2c_board_info xx_info = { //所支持的i2c設備的列表
       I2C_BOARD_INFO("at24c08", 0x50),      //一項代表一個支持的設備,它的名字叫做“at24c08”,器件地址是0x50
    };

    struct i2c_adapter *i2c_adap;    //分配一個適配器的指針

      i2c_adap = i2c_get_adapter(0);    //調用core層的函數,獲得一個i2c總線。這里我們已經知道新增的器件掛接在編號為0的i2c總線上
     xx_client = i2c_new_device(i2c_adap, &xx_info);  // 把i2c適配器和新增的I2C器件關聯起來,這個用了i2c總線0,地址是0x50。這就組成了一個客戶端xx_client。

     i2c_put_adapter(i2c_adap);//釋放adapter

  (3)該方法與第二種方法類似,只是在iic總線未知的情況下使用,即將i2c_new_device改為i2c_new_probe_device

1.2新內核版本下使用設備樹

  &i2c1 {

      pinctrl-names = "default";

      pinctrl-0 = <&pinctrl_hummingboard_i2c1>;

      status = "okay";

      rtc: pcf8523@68 {

      compatible = "nxp,pcf8523";

      reg = <0x68>;

       };

     };

2.IIC驅動端程序

首先要明白一點,對於驅動工程師,如果手中是移植過的內核,則i2c總線核心和i2c適配器驅動是不需要動的,我們主要關注點在:提供i2c設備(client)、編寫i2c設備驅動

  • i2c子系統的本質是:工程師任意選用input子系統、misc框架、普通字符驅動等方式實現i2c驅動,i2c子系統的意義僅僅是為硬件操作提供接口(庫)

這里寫圖片描述

  • 此外,對於新內核和老內核,設備樹會導致驅動會有一些細微的不同,主要體現在驅動和設備match的部分

老內核下的i2c驅動

  1. #include <linux/i2c.h>
  2. #include <linux/module.h>
  3. #include <linux/string.h>
  4. #include <linux/fs.h>
  5. #include <linux/device.h>
  6. #include <linux/cdev.h>
  7. #include <linux/delay.h>
  8. #include <asm/uaccess.h>
  9.  
  10. /*mpu6050內部寄存器地址 */
  11. #define MPU6050_RA_PWR_MGMT_1 0x6B
  12. #define MPU6050_RA_ACCEL_XOUT_H 0x3B
  13.  
  14. #define MPU6050_CNT 1
  15. #define MPU6050_NAME "mpu6050"
  16.  
  17. struct i2c_client *mpu6050_client;
  18.  
  19. /*
  20. * 寫mpu6050內部的寄存器。先發寄存器地址再發寄存器的值
  21. */
  22. static int mpu6050_write_reg(unsigned char addr, unsigned char dat)
  23. {
  24. int ret = -1;
  25. struct i2c_msg msgs[2];
  26.  
  27. msgs[ 0].addr = mpu6050_client -> addr;//MPU6050_ADDR
  28. msgs[ 0].buf = &addr;
  29. msgs[ 0].len = 1; //長度1 byte
  30. msgs[ 0].flags = 0; //表示寫
  31.  
  32. msgs[ 1].addr = mpu6050_client -> addr;//MPU6050_ADDR
  33. msgs[ 1].buf = &dat;
  34. msgs[ 1].len = 1; //長度1 byte
  35. msgs[ 1].flags = 0; //表示寫
  36.  
  37. /*連續發送兩幀信息*/
  38. ret = i2c_transfer(mpu6050_client ->adapter, msgs, 2);
  39. if (ret != 2) {
  40. printk(KERN_INFO "i2c_transfer(mpu6050 write) error \n");
  41. return -EIO;
  42. }
  43. return 0;
  44. }
  45.  
  46. /*
  47. *讀mpu6050內部的寄存器。先發寄存器地址再讀寄存器的值
  48. */
  49. static int mpu6050_read_reg(unsigned char addr, unsigned char buf)
  50. {
  51. int ret = -1;
  52. struct i2c_msg msgs[2];
  53.  
  54. msgs[ 0].addr = mpu6050_client -> addr;//MPU6050_ADDR
  55. msgs[ 0].buf = &addr;
  56. msgs[ 0].len = 1; //長度1 byte
  57. msgs[ 0].flags = 0; //表示寫
  58.  
  59. msgs[ 1].addr = mpu6050_client -> addr;//MPU6050_ADDR
  60. msgs[ 1].buf = &buf;
  61. msgs[ 1].len = 1; //長度1 byte
  62. msgs[ 1].flags = I2C_M_RD; //表示讀
  63.  
  64. /*連續發送兩幀信息*/
  65. ret = i2c_transfer(mpu6050_client ->adapter, msgs, 2);
  66. if (ret != 2) {
  67. printk(KERN_INFO "i2c_transfer(mpu6050 read) error \n");
  68. return -EIO;
  69. }
  70.  
  71. return 0;
  72. }
  73.  
  74. static int mpu6050_open(struct inode *inode, struct file *file)
  75. {
  76. printk(KERN_INFO "open mpu6050\n");
  77. msleep( 50);
  78. mpu6050_write_reg(MPU6050_RA_PWR_MGMT_1, 0X80);//復位
  79. /*這里僅僅做個例子,一般在這里要做初始化*/
  80. return 0;
  81. }
  82.  
  83.  
  84. ssize_t mpu6050_read(struct file *file, char __user *ubuf,
  85. size_t size, loff_t *opp)
  86. {
  87. unsigned char buf [6] = {0};
  88.  
  89. mpu6050_read_reg(MPU6050_RA_GYRO_XOUT_H, buf[ 0]);
  90.  
  91. /*這里僅僅是舉個例子,怎么從外設中讀數據*/
  92.  
  93. ret = copy_to_user(ubuf, buf , size);
  94. if (ret) {
  95. printk(KERN_INFO "copy_to_user fail\n");
  96. return -EINVAL;
  97. }
  98.  
  99. return 0;
  100. }
  101.  
  102. static int mpu6050_release(struct inode *inode, struct file *file)
  103. {
  104. return 0;
  105. }
  106.  
  107. static const struct file_operations mpu6050_fops = {
  108. .owner = THIS_MODULE,
  109. .open = mpu6050_open,
  110. .read = mpu6050_read,
  111. .release = mpu6050_release,
  112. };
  113.  
  114.  
  115. static struct cdev *mpu6050_pcdev;
  116. static struct class *mpu6050_pclass;
  117.  
  118. dev_t mpu6050dev_num = 0;
  119. unsigned int mpu6050dev_major = 0;
  120.  
  121. int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id)
  122. {
  123. int ret = -1;
  124.  
  125. mpu6050_client = client;
  126. /*內核自動分配一個設備號*/
  127. ret = alloc_chrdev_region(&mpu6050dev_num, 0, MPU6050_CNT, MPU6050_NAME);
  128. mpu6050dev_major = MAJOR(mpu6050dev_num);
  129. if (ret < 0) {
  130. printk(KERN_INFO "alloc_chrdev_region fail\n");
  131. goto out_err_0;
  132. }
  133. printk(KERN_INFO "MAJOR %d\n", mpu6050dev_major);
  134.  
  135. /*實例化一個字符設備體*/
  136. mpu6050_pcdev = cdev_alloc();
  137. /*填充cdev設備體 。最主要是將file_operations填充進去*/
  138. cdev_init(mpu6050_pcdev, &mpu6050_fops);
  139.  
  140. /* 將設備體與設備號綁定並向內核注冊一個字符設備*/
  141. ret = cdev_add(mpu6050_pcdev, mpu6050dev_num, MPU6050_CNT);
  142. if (ret) {
  143. printk(KERN_INFO "cdev_add fail\n");
  144. goto out_err_1;
  145. }
  146.  
  147. /*創建類、設備*/
  148. mpu6050_pclass = class_create(THIS_MODULE, "mpu6050");
  149. if (IS_ERR(mpu6050_pclass)) { //排錯
  150. printk(KERN_ERR "can't register class\n");
  151. goto out_err_2;
  152. }
  153. device_create(mpu6050_pclass, NULL, mpu6050dev_num, NULL, "mpu6050");
  154.  
  155. return 0;
  156.  
  157. /* “倒影式”錯誤處理流程*/
  158. out_err_3:
  159. class_destroy(mpu6050_pclass);
  160.  
  161. out_err_2:
  162. cdev_del(mpu6050_pcdev);
  163.  
  164. out_err_1:
  165. unregister_chrdev_region(mpu6050dev_num, MPU6050_CNT);
  166.  
  167. out_err_0:
  168. return -EINVAL;
  169. }
  170.  
  171. int mpu6050_remove(struct i2c_client *client)
  172. {
  173. /*倒影式注銷流程*/
  174. device_destroy(mpu6050_pclass, mpu6050dev_num);
  175. class_destroy(mpu6050_pclass);
  176. cdev_del(mpu6050_pcdev);
  177. unregister_chrdev_region(mpu6050dev_num, MPU6050_CNT);
  178.  
  179. return 0;
  180. }
  181.  
  182. /*
  183. * i2c設備驅動結構體內的id_table。用作匹配功能
  184. */
  185. static struct i2c_device_id mpu6050_id[] = {
  186. { "mpu6050", 0},
  187. { }
  188. };
  189. /*
  190. * 這里開始定義i2c設備驅動結構體
  191. */
  192. static struct i2c_driver mpu6050_driver = {
  193. .driver = {
  194. .name = "mpu6050",//i2c總線和platform不同這個name僅僅是名字。並不用作匹配功能
  195. .owner = THIS_MODULE,
  196. },
  197. .probe = mpu6050_probe,
  198. .remove = mpu6050_remove,
  199. .id_table = mpu6050_id, //i2c總線和platform不同。只用id_table來匹配driver和client
  200. };
  201.  
  202. /*
  203. * 模塊加載函數負責注冊i2c設備驅動
  204. */
  205. static int __init mpu6050_init(void)
  206. {
  207. return i2c_add_driver(&mpu6050_driver);
  208. }
  209.  
  210. static void __exit mpu6050_exit(void)
  211. {
  212. i2c_del_driver(&mpu6050_driver);
  213. }
  214.  
  215. module_init(mpu6050_init);
  216. module_exit(mpu6050_exit);
  217.  
  218. MODULE_LICENSE( "GPL");


整個程序很簡單,關鍵點主要是driver結構體里要有一個id_table,如果mach-xxx中定義的設備名字和id_table相同,那么會probe函數就被觸發
  • 觸發probe后,i2c_client *client將作為參數傳入probe,這個i2c_client里面就包含了設備的私有數據(比如設備的i2c地址、綁定的i2c適配器等),類似plat_data,我們在probe中將i2c_client *client綁定給全局變量mpu6050_client,這樣就能在read、write等函數中用mpu6050_client -> addr來得到設備的i2c地址了,用mpu6050_client ->adapter來得到綁定的i2c適配器
  • 只要read、write知道了設備的i2c地址、綁定的i2c適配器,那么就能利用kernel提供的接口來進行i2c傳輸。上面代碼中用的接口是i2c_transfer,這種方法有點老舊;kernel官方強烈推薦smbus族接口來進行i2c收發,smbus是i2c_transfer的子集,很多soc可能不支持i2c_transfer這個接口,這時就只能使用smbus族接口。這兩個接口在內部邏輯上有很大不同,比如我們要寫mpu6050內部的RA_PWR_MGMT_1寄存器,根據上面的代碼,我們調用了兩次i2c_transfer,第一次發送RA_PWR_MGMT_1寄存器的地址,第二次發送要寫的值。而對於smbus族接口來說,只需調用一次就行了,可以認為smbus族接口進行了更好的封裝,不僅寫操作如此,讀操作也如此,具體接口如下
  1. /*第一個參數是client,第二個參數是i2c設備內的寄存器地址,第三個參數是要寫入的值*/
  2. i2c_smbus_write_byte_data(mpu6050_client, MPU6050_RA_PWR_MGMT_1, data);
  3.  
  4. /*第一個參數是client,第二個參數是i2c設備內的寄存器地址,返回值是讀出來的值*/
  5. read_val = i2c_smbus_read_byte_data(mpu6050_client, MPU6050_RA_ACCEL_XOUT_H);
  • 如果要以16bit為單位讀寫i2c,那么可以用下面的接口,使用方法都是類似的
  1. i2c_smbus_read_word_data();
  2. i2c_smbus_write_word_data();

新內核下的i2c驅動

設備樹對i2c設備的注冊有比較大的影響,詳見前面的章節,這里不再贅述;而對於驅動程序,設備樹帶來的變化極小,主要是驅動和設備之間的匹配方式變了

  • 老舊的id_table方式不再使用,取而代之的是類似的一種結構:of_match_table
  • 這里以pcf8523驅動為例,只要驅動中的of_match_table 中的compatible 值和設備節點中的compatible 相匹配,那么probe函數就會被觸發。不僅i2c是這樣,platform、spi等都是這個原理
  1. /*定義的of_match_table*/
  2. static const struct of_device_id pcf8523_of_match[] = {
  3. { .compatible = "nxp,pcf8523" },
  4. { }
  5. } ;
  6.  
  7. /*driver 結構體中的of_match_table*/
  8. static struct i2c_driver pcf8523_driver = {
  9. .driver = {
  10. .name = DRIVER_NAME,
  11. .owner = THIS_MODULE,
  12. .of_match_table = of_match_ptr(pcf8523_of_match),
  13. },
  14. .probe = pcf8523_probe,
  15. .id_table = pcf8523_id,
  16. } ;
    i2c和spi驅動還支持一種“別名匹配”的機制,就以pcf8523為例,假設某程序員在設備樹中的pcf8523設備節點中寫了compatible = “pcf8523”;,顯然相對於驅動id_table中的”nxp,pcf8523”,他遺漏了nxp字段,但是驅動卻仍然可以匹配上,因為別名匹配對compatible中字符串里第二個字段敏感。
     
    以上內容參考如下博文:https://blog.csdn.net/sdkdlwk/article/details/76105112
    https://www.cnblogs.com/alan666/p/8311853.html
    https://www.linuxidc.com/Linux/2014-05/101649.htm


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM