Chinaunix首页 | 论坛 | 博客
  • 博客访问: 769889
  • 博文数量: 478
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 4831
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-28 11:12
文章分类

全部博文(478)

文章存档

2019年(1)

2018年(27)

2017年(21)

2016年(171)

2015年(258)

我的朋友

分类: Android平台

2016-08-03 16:14:28

http://blog.csdn.net/g_salamander/article/details/8064627
 

i2c 编程接口

 9165人阅读 评论(1) 收藏 举报
 分类:
 
内核接口(3) 

1、通信接口

i2c发送或者接收一次数据都以数据包 struct i2c_msg 封装


[cpp] view plain copy
  1. struct i2c_msg {  
  2.     __u16 addr;     // 从机地址  
  3.     __u16 flags;    // 标志  
  4. #define I2C_M_TEN   0x0010  // 十位地址标志  
  5. #define I2C_M_RD    0x0001  // 接收数据标志  
  6.     __u16 len;      // 数据长度  
  7.     __u8 *buf;      // 数据指针  
  8. };  

其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为 I2C_M_RD;len为此次通信的数据字节数;buf 为发送或接收数据的指针。在设备驱动中我们通常调用 i2c-core 定义的接口 i2c_master_send 和 i2c_master_recv 来发送或接收一次数据。

[cpp] view plain copy
  1. int i2c_master_send(struct i2c_client *client,const char *buf ,int count)  
  2. {  
  3.     int ret;  
  4.     struct i2c_adapter *adap=client->adapter;  // 获取adapter信息  
  5.     struct i2c_msg msg;                        // 定义一个临时的数据包  
  6.   
  7.     msg.addr = client->addr;                   // 将从机地址写入数据包  
  8.     msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包  
  9.     msg.len = count;                           // 将此次发送的数据字节数写入数据包  
  10.     msg.buf = (char *)buf;                     // 将发送数据指针写入数据包  
  11.   
  12.     ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口发送数据  
  13.   
  14.     /* If everything went ok (i.e. 1 msg transmitted), return #bytes 
  15.        transmitted, else error code. */  
  16.     return (ret == 1) ? count : ret;           // 如果发送成功就返回字节数  
  17. }  
  18. EXPORT_SYMBOL(i2c_master_send);  

i2c_master_send 接口的三个参数:client 为此次与主机通信的从机,buf 为发送的数据指针,count 为发送数据的字节数。

[cpp] view plain copy
  1. int i2c_master_recv(struct i2c_client *client, char *buf ,int count)  
  2. {  
  3.     struct i2c_adapter *adap=client->adapter;  // 获取adapter信息  
  4.     struct i2c_msg msg;                        // 定义一个临时的数据包  
  5.     int ret;  
  6.   
  7.     msg.addr = client->addr;                   // 将从机地址写入数据包  
  8.     msg.flags = client->flags & I2C_M_TEN;     // 将从机标志并入数据包  
  9.     msg.flags |= I2C_M_RD;                     // 将此次通信的标志并入数据包  
  10.     msg.len = count;                           // 将此次接收的数据字节数写入数据包  
  11.     msg.buf = buf;  
  12.   
  13.     ret = i2c_transfer(adap, &msg, 1);         // 调用平台接口接收数据  
  14.   
  15.     /* If everything went ok (i.e. 1 msg transmitted), return #bytes 
  16.        transmitted, else error code. */  
  17.     return (ret == 1) ? count : ret;           // 如果接收成功就返回字节数  
  18. }  
  19. EXPORT_SYMBOL(i2c_master_recv);  

i2c_master_recv 接口的三个参数:client 为此次与主机通信的从机,buf 为接收的数据指针,count 为接收数据的字节数。我们看一下 i2c_transfer 接口的参数说明:


[cpp] view plain copy
  1. int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);  
其中 adap 为此次主机与从机通信的适配器;msgs 为通信的数据包,这里可以是单个或多个数据包;num 用于指定数据包的个数,如果大于1则表明将进行不止一次的通信。通信一次就需要寻址一次,如果需要多次通信就需要多次寻址,前面2个接口都是进行一次通信,所以 num 为1;有的情况下我们要读一个寄存器的值,就需要先向从机发送一个寄存器地址然后再接收数据,这样如果想自己封装一个接口就需要将 num 设置为2。接口的返回值如果失败则为负数,如果成功则返回传输的数据包个数。比如读一个寄存器的接口可以按照如下方式封装:



[cpp] view plain copy
  1. static int read_reg(struct i2c_client *client, unsigned char reg, unsigned char *data)  
  2. {  
  3.     int ret;  
  4.   
  5.     struct i2c_msg msgs[] = {  
  6.         {  
  7.             .addr   = client->addr,  
  8.             .flags  = 0,  
  9.             .len    = 1,  
  10.             .buf    = ®,  // 寄存器地址  
  11.         },  
  12.         {  
  13.             .addr   = client->addr,  
  14.             .flags  = I2C_M_RD,  
  15.             .len    = 1,  
  16.             .buf    = data,  // 寄存器的值  
  17.         },  
  18.     };  
  19.   
  20.     ret = i2c_transfer(client->adapter, msgs, 2);  // 这里 num = 2,通信成功 ret = 2  
  21.     if (ret < 0)  
  22.         tp_err("%s error: %d\n", __func__, ret);  
  23.   
  24.     return ret;  
  25. }  

还可调用前面所述的接口封装:


[cpp] view plain copy
  1. static unsigned char read_reg(struct i2c_client *client, unsigned char reg)  
  2. {  
  3.     unsigned char buf;  
  4.   
  5.     i2c_master_send(client, ®, 1);  // 发送寄存器地址  
  6.     i2c_master_recv(client, &buf, 1);  // 接收寄存器的值  
  7.   
  8.     return  buf;  
  9. }  
2、reset 接口

最近因为平台的i2c总线经常发生死锁,用逻辑分析仪检测发现通常为SDA和SCL都被拉低,于是在i2c-core中加入了reset机制,总体思路如下:

(1)在i2c.driver和i2c.adapter的结构中加入reset接口,即每一个i2c设备都可以注册reset函数,每条i2c总线都有相应的reset接口

(2)当发生死锁时,首先根据i2c-timeout的信息获取当前通信的设备地址和总线编号,然后依次执行当前总线下所有i2c设备的reset函数,再尝试发送是否成功;如果总线仍然处于死锁状态则执行i2c.adapter的reset函数;如果总线还是处于死锁状态就重启机器;总共3层reset机制

(3)i2c.driver的reset函数一般操作设备的reset pin或者电源(需要根据硬件设计进行相应操作)

(4)i2c.adapter的reset函数首选进行SCL的模拟解锁方案,然后再是操作整个总线上设备的电源(需要根据硬件设计进行相应操作)

(5)重启是最后的一层机制,此时无法恢复设备的正常使用就只能重启了

因为i2c.adapter层的需要,在i2c-core中加入了遍历当前总线所有设备并执行设备reset函数的接口i2c_reset_device:


[cpp] view plain copy
  1. /** 
  2.  * i2c_reset_device - reset I2C device when bus dead 
  3.  * @adapter: the adapter being reset 
  4.  * @addr: the device address 
  5.  */  
  6. static int __i2c_reset_device(struct device *dev, void *addrp)  
  7. {  
  8.     struct i2c_client *client = to_i2c_client(dev);  
  9.     int addr = *(int *)addrp;  
  10.   
  11.     if (client && client->driver && client->driver->reset)  
  12.         return client->driver->reset();  
  13.   
  14.     return 0;  
  15. }  
  16.   
  17. int i2c_reset_device(struct i2c_adapter *adapter, int addr)  
  18. {  
  19.     return device_for_each_child(&adapter->dev, &addr, __i2c_reset_device);  
  20. }  
  21. EXPORT_SYMBOL(i2c_reset_device);  

需要注意的是i2c.driver的reset函数返回值需要为0,不然device_for_each_child不会继续后面的遍历。用GPIO模拟SCL解锁的参考代码如下:


[cpp] view plain copy
  1. static int i2c_reset_adapter(void)  
  2. {  
  3.     int counter = 0;  
  4.   
  5.     gpio_request(I2C_BUS_DATA, "gpioxx");  
  6.     gpio_request(I2C_BUS_CLK, "gpioxx");  
  7.     /* try to recover I2C bus */  
  8.     gpio_direction_input(I2C_BUS_DATA);  
  9.   
  10.     if (!__gpio_get_value(I2C_BUS_DATA)) {  
  11.         while((!__gpio_get_value(I2C_BUS_DATA)) && ++counter < 10)  
  12.         {  
  13.             udelay(5);  
  14.             gpio_direction_output(I2C_BUS_CLK, 1);  
  15.             udelay(5);  
  16.             gpio_direction_output(I2C_BUS_CLK, 0);  
  17.         }  
  18.         i2c_err("try to recover i2c bus, retry times are %d\n",counter);  
  19.         if (counter < 10) {  
  20.             udelay(5);  
  21.             gpio_direction_output(I2C_BUS_DATA, 0);  
  22.             udelay(5);  
  23.             gpio_direction_output(I2C_BUS_CLK, 1);  
  24.             udelay(5);  
  25.             gpio_direction_output(I2C_BUS_DATA, 1);  
  26.             msleep(10);  
  27.         } else {  
  28.             i2c_err("try to recover i2c bus failed!\n");  
  29.         }  
  30.     }  
  31.   
  32.     gpio_free(I2C_BUS_DATA);  
  33.     gpio_free(I2C_BUS_CLK);  
  34.   
  35.     return 0;  
  36. }  

3、i2c_detect 接口

最近在看一个 wifi 驱动的时候发现了另外一种注册 i2c 设备的方式,其实也是属于动态注册的一种,不过内核支持得更好,可以用内核提供的宏来实现。该方法以 struct i2c_client_address_data 结构为基础,我们先来看下这个结构的组成:

[cpp] view plain copy
  1. struct i2c_client_address_data {  
  2.     const unsigned short *normal_i2c;      /* 常规探测地址 */  
  3.     const unsigned short *probe;           /* 常规的通道和地址 */  
  4.     const unsigned short *ignore;          /* 过滤的通道和地址 */  
  5.     const unsigned short * const *forces;  /* 强制的通道和地址 */  
  6. };  
这个结构定义了 4 个指向不同数组的指针,数组元素都以 I2C_CLIENT_END 结尾,其中 normal_i2c 指向的数组保存的是常规探测地址,而且该数组中的地址如果和 ignore 数组中的地址相同则会被过滤掉,常见定义如下:
[cpp] view plain copy
  1. static const unsigned short normal_i2c[] = {0x2c, 0x2d, 0x2e, I2C_CLIENT_END};  /* 该数组定义了3个可能的设备地址 */  
其他 3 个数组的元素依次是通道和地址成对的组合,而且元素个数最好定义为偶数个即最后两个 I2C_CLIENT_END。在探测过程中,i2c_detect 函数会遍历系统中所有的 i2c 通道,在每个通道中依次探测地址,顺序是 forces -> probe -> normal_i2c,比如在探测 forces 里面的通道和地址时,会先比较当前通道和数组里面定义的通道是否一致,不一致则不会继续探测该对地址,如果定义的是 ANY_I2C_BUS 则任意通道都会探测该对地址,接着探测地址会调用 driver->detect 接口,在该接口中一般会去读取设备的某个寄存器值以判断设备是否存在,如果设备存在则返回 0 值,如果不存在则返回 -ENODEV,无论返回 0 值或者 -ENODEV 都会继续后面的探测,如果返回其他错误值则会结束整个探测过程。一个常见的例子如下:
[cpp] view plain copy
  1. static const struct i2c_device_id wifi_core_i2c_id[] = {{WIFI_CORE_I2C_DEVNAME, 0}, {}};  
  2. MODULE_DEVICE_TABLE(i2c, wifi_core_i2c_id);  
  3.   
  4. static unsigned short wifi_core_force[] = {ANY_I2C_BUS, WIFI_CORE_ADDR, I2C_CLIENT_END, I2C_CLIENT_END};  
  5. static const unsigned short * const wifi_core_forces[] = {wifi_core_force, NULL};  
  6.   
  7. static struct i2c_client_address_data wifi_core_addr_data = {  
  8.     .forces     = wifi_core_forces,  
  9. };  
  10.   
  11. static int wifi_core_detect(struct i2c_client *client, int kind, struct i2c_board_info *info)  
  12. {  
  13.     strcpy(info->type, WIFI_CORE_I2C_DEVNAME);  /* 如果探测成功则在detect接口中至少要设置设备名称 */  
  14.     return 0;  
  15. }  
  16.   
  17. static struct i2c_driver wifi_core_driver =   
  18. {  
  19.     .probe = wifi_core_probe,  
  20.     .remove = wifi_core_remove,  
  21.     .detect = wifi_core_detect,  
  22.     .shutdown = wifi_shutdown,  
  23.     .driver.name = WIFI_CORE_I2C_DEVNAME,  
  24.     .id_table = wifi_core_i2c_id,  
  25.     .address_data = &wifi_core_addr_data,  
  26. };  
  27.   
  28. i2c_add_driver(&wifi_core_driver)  


阅读(305) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~