使用GPIO口模拟I2C总线并挂载设备

时间:2021-06-06 17:52:43
  1. 前言:  
  2. 在许多情况下,我们并没有足够的I2C总线,本文主在介绍如何利用Linux内核中的i2c-gpio模块,利用2条GPIO线模拟i2c总线,并挂载设备。  
  3. 思路:  
  4. 先通过对i2c-gpio所定义的结构体初始化(包括初始化i2c的2条线,频率,timeout等)并将i2c-gpio模块编译进内核,实现用GPIO_X,GPIO_Y 2条GPIO线注册新的i2c总线。此时这个模块对i2c设备是透明的,及挂在这2条GPIO线的i2c设备可以直接使用Linux内核通用的i2c设备注册,传输和注销等方法。  
  5. 步骤:  
  6. 首先确认在注册i2c-gpio模块前,所要用到的2条GPIO口是没有被系统其它地方所调用的。  
  7. 在每个系统平台启动时,都会打开一系列的设备,他们通常实现在arch/目录下相应的平台子目录中的例如setup.c,devices.c文件中,在这里我们进行i2c总线的注册以及设备的挂载。i2c-gpio定义的结构在include/linux/i2c-gpio.h中:  
  8. /** 
  9.  * struct i2c_gpio_platform_data - Platform-dependent data for i2c-gpio 
  10.  * @sda_pin: GPIO pin ID to use for SDA 
  11.  * @scl_pin: GPIO pin ID to use for SCL 
  12.  * @udelay: signal toggle delay. SCL frequency is (500 / udelay) kHz 
  13.  * @timeout: clock stretching timeout in jiffies. If the slave keeps 
  14.  *  SCL low for longer than this, the transfer will time out. 
  15.  * @sda_is_open_drain: SDA is configured as open drain, i.e. the pin 
  16.  *  isn't actively driven high when setting the output value high. 
  17.  *  gpio_get_value() must return the actual pin state even if the 
  18.  *  pin is configured as an output. 
  19.  * @scl_is_open_drain: SCL is set up as open drain. Same requirements 
  20.  *  as for sda_is_open_drain apply. 
  21.  * @scl_is_output_only: SCL output drivers cannot be turned off. 
  22.  */  
  23. struct i2c_gpio_platform_data {  
  24. unsigned int    sda_pin;  
  25. unsigned int    scl_pin;  
  26. int  udelay;  
  27. int  timeout;  
  28. unsigned int    sda_is_open_drain:1;  
  29. unsigned int    scl_is_open_drain:1;  
  30. unsigned int    scl_is_output_only:1;  
  31. };  
  32. 其中sda_pin和scl_pin分别是i2c总线的数据线和时钟线,在i2c-gpio中会通过gpio_request函数对这2个口进行申请,udelay和timeout如果不设初值,i2c-gpio中会自动将其设为默认值。  
  33. if (pdata->udelay)  
  34. bit_data->udelay = pdata->udelay;  
  35. else if (pdata->scl_is_output_only)  
  36. bit_data->udelay = 50;    /* 10 kHz */  
  37. else  
  38. bit_data->udelay = 5;     /* 100 kHz */  
  39. if (pdata->timeout)  
  40. bit_data->timeout = pdata->timeout;  
  41. else  
  42. bit_data->timeout = HZ / 10;  /* 100 ms */  
  43. 初始化这个结构体后再将其装入platform_device结构体,方便注册:  
  44. static struct platform_device i2c_device = {  
  45. .name    = "device-name",  
  46. .id  = your-id,  
  47. .dev = {  
  48. .platform_data  = &i2c_data,       // i2c_gpio_platform_data  
  49. },  
  50. };  
  51. 注册i2c-gpio设备  
  52. 将i2c设备挂入我们注册的总线:  
  53. platform_device_register(&i2c_device);  
  54. static struct i2c_board_info i2c_device[] = {  
  55. {  
  56. I2C_BOARD_INFO("name", i2c_device_addr),  
  57. }  
  58. };  
  59. i2c_register_board_info(your-id, i2c_device, ARRAY_SIZE(i2c_device));  
  60. 此时我们就可以在i2c设备的驱动程序中通过遍历所在i2c总线,得到其所在的地址i2c_device_addr。  
  61. 在i2c驱动中,需要注册一个i2c_driver的结构体,例如:  
  62. static const struct i2c_device_id lis35de_id[] = {  
  63. "lis35de", 0 },  
  64. { }  
  65. };  
  66. static struct i2c_driver st_lis35de_driver = {  
  67. .probe   = st_lis35de_probe,  
  68. .remove  = st_lis35de_remove,  
  69. .suspend    = st_lis35de_suspend,  
  70. .resume  = st_lis35de_resume,  
  71. .id_table   = lis35de_id,  
  72. .driver  = {  
  73. .name   = "lis35de",  
  74. },  
  75. };  
  76. static int __init st_lis35de_init(void)  
  77. {  
  78. printk(KERN_INFO "st_lis35de_init/n");  
  79. return i2c_add_driver(&st_lis35de_driver);  
  80. }  
  81. 在init时用i2c_add_driver(&st_lis35de_driver),此时将会对所在i2c总线进行遍历并得到该设备的适配器等信息,主要目的即是使驱动得到自己的i2c_client,在这个i2c_client中,已经有了该i2c设备的地址等信息,我们在驱动中定义一个新的i2c_client全局变量,把得到的这个i2c_client传给这个全局变量,从而可以继续后面的i2c操作。  
  82. 此时我们就可以使用通用的i2c读写操作了。  
  83. 总结:  
  84. 直接用GPIO口模拟I2C时序和利用内核模块i2c-gpio虚拟i2c总线的区别:  
  85. 1. 用GPIO口模拟I2C时序不需要在系统启动时注册I2C总线,只需要在I2C设备驱动中单独实现。用i2c-gpio模块虚拟i2c总线需要在系统启动时注册新的I2C总线,并将i2c设备挂载到新的i2c总线,涉及的范围较广。  
  86. 2. 用GPIO口模拟I2C时序,代码操作较繁琐,且不方便挂载多个i2c设备。用i2c-gpio模块可以完全模拟i2c总线,可以挂载多个设备。  
  87. 3. 在i2c读写操作时,用GPIO口模拟I2C时序需要每次根据读/写操作发送器件地址<<1+1/0,然后再发送寄存器地址。用i2c-gpio模块相当于直接在i2c总线上操作,在系统启动挂载i2c设备时已经告诉了i2c总线它的地址,在该设备自己的驱动中,只需要通过i2c_add_driver操作即可以得到其地址等诸多信息,读写操作只需要发送寄存器地址即可。  
  88. 附:i2c一般的读写操作  
  89. #include <linux/i2c.h>  
  90. /* 
  91. 读操作: 
  92. */  
  93. static int i2c_RxData(char *rxData, int length)  
  94. {  
  95. struct i2c_msg msgs[] = {  
  96.          /* 把1个字节的i2c设备寄存器地址告诉总线 */  
  97. {  
  98.  .addr = client->addr,  
  99.  .flags = 0,                     //写操作  
  100.  .len = 1,  
  101.  .buf = rxData,  
  102.  },  
  103.           /* 从总线读取length个字节的数据,存入rxData */  
  104. {  
  105.  .addr =client ->addr,  
  106.  .flags = I2C_M_RD,             //I2C_M_RD在i2c.h中被定义为1,读操作  
  107.  .len = length,  
  108.  .buf = rxData,  
  109.  },  
  110. };  
  111. if (i2c_transfer(client->adapter, msgs, 2) < 0) {   /* 传输并判断是否传输错误 */  
  112. printk(KERN_ERR "I2C_RxData: transfer error/n");  
  113. return -EIO;  
  114. else  
  115. return 0;  
  116. }  
  117. /* 
  118. 写操作 
  119. */  
  120. static int i2c_TxData(char *txData, int length)  
  121. {  
  122. struct i2c_msg msg[] = {  
  123.      /* 第1个字节是器件寄存器地址,后面的字节是写入的数据 */  
  124. {  
  125.  .addr = client->addr,  
  126.  .flags = 0,  
  127.  .len = length,  
  128.  .buf = txData,  
  129.  },  
  130. };  
  131. if (i2c_transfer(client->adapter, msg, 1) < 0) {  
  132. printk(KERN_ERR "I2C_TxData: transfer error/n");  
  133. return -EIO;  
  134. else  
  135. return 0;  
  136. }  
  137. 以上内容载自网络。以下内容是公司一位同事提供的代码修改位置:  
  138. /********************************************/  
  139. 在具体开发的过程当中,开发板上的I2C总线有限,如果I2C设备太多的话,就需要用GPIO模拟I2C来解决了。  
  140. 对于用GPIO模拟I2C有两种方法:  
  141. 1. 一种是直接对gpio口进行拉高拉低操作,来模拟I2C协议对设备进行读写。这个具体的实现方式也不是太难,但它有自己的缺点,如果相同的gpio口上多挂载几个I2C设备,则多个设备对gpio口的使用必然会造成冲突,进而造成通讯不正常。  
  142. 2. 另一种方法是利用linux内核当中自带的机制来实现这种模拟。对于最近版本的linux操作系统来说,本身已经自带了一套机制来实现这种模拟。  
  143. 最重要的是,第二种方法可以方便地挂载多个设备,而不用担心他们之间的通信问题。  
  144. 对于高通的7x27平台,首先在文件devices-msm7x27.c中添加设备如下:  
  145. static struct i2c_gpio_platform_data resources_gpio_i2c[] = {  
  146.     {  
  147.         .sda_pin=xx,  
  148.         .scl_pin=xx,  
  149.         .udelay=xx,  
  150.         .sda_is_open_drain=1,  
  151.         .scl_is_open_drain=1,  
  152. }  
  153. };  
  154. struct platform_device gpio_device_i2c = {  
  155.     .name        = "i2c-gpio",  
  156.     .id        = 2,  
  157.     .dev = {  
  158.            .platform_data = &resources_gpio_i2c,  
  159.     },  
  160. };  
  161. 其中第一个结构体中sda_pin和scl_pin正是开发板上对应的gpio口(data线和clock线),udelay是与具体芯片时钟相关的参数,需要参考具体的datasheet。下面的两个open_drain是表明两个管脚是否是开漏电路,如果是则填1,否则填0。下面一个机构体中需要注意name应该填写i2c-gpio,另外id要注意设定为2,因为系统当中已经有两个I2C设备了。可以在当前文件中找到,另外也可以在设备启动之后用adb shell进入文件系统进行查看。  
  162. 另外需要在该文件中添加上需要的头文件:#include <linux/i2c-gpio.h>。在头文件devices.h中添加上设备结构体的声明,extern struct platform_device gpio_device_i2c;否则编译过程会报错  
  163. 然后将gpio_device_i2c放在board-msm7x27.c的数组devices中,形式请参考该数组中其他的设备。  
  164. 最后还需要在该文件中对相关设备进行注册,比如添加如下代码:  
  165. static struct i2c_board_info i2c_gpio_devices[] = {  
  166.     {  
  167.         I2C_BOARD_INFO("tritonFN", 0x39),  
  168.         .irq           =  MSM_GPIO_TO_INT(107),  
  169.     },  
  170. };  
  171. 然后再用i2c_register_board_info对其进行注册:  
  172. i2c_register_board_info(2, i2c_gpio_devices, ARRAY_SIZE(i2c_gpio_devices));  
  173. 这样就完成了模拟步骤,可以直接用系统的I2C相关的注册等方法对设备进行注册和读写操作。