i2c 驱动编程接口 i2c_master_send 和 i2c_master_recv i2c_transfer

来源:互联网 发布:腾讯微信数据报告2017 编辑:程序博客网 时间:2024/06/07 17:59
1、通信接口
i2c发送或者接收一次数据都以数据包 struct i2c_msg 封装
[cpp] 

struct i2c_msg { 
    __u16addr;    // 从机地址 
    __u16flags;    //标志 
#define I2C_M_TEN  0x0010  // 十位地址标志 
#defineI2C_M_RD   0x0001  // 接收数据标志 
    __u16len;     // 数据长度 
    __u8*buf;     // 数据指针 
}; 
其中addr为从机地址;flags则是这次通信的标志,发送数据为0,接收数据则为I2C_M_RD;len为此次通信的数据字节数;buf 为发送或接收数据的指针。在设备驱动中我们通常调用 i2c-core 定义的接口i2c_master_send 和 i2c_master_recv 来发送或接收一次数据。
[cpp]
int i2c_master_send(struct i2c_client *client,const char *buf ,intcount) 

    intret; 
    structi2c_adapter *adap=client->adapter;  //获取adapter信息 
    structi2c_msgmsg;                       // 定义一个临时的数据包 
 
    msg.addr =client->addr;                  // 将从机地址写入数据包 
    msg.flags =client->flags &I2C_M_TEN;    // 将从机标志并入数据包 
    msg.len =count;                          // 将此次发送的数据字节数写入数据包 
    msg.buf =(char*)buf;                    // 将发送数据指针写入数据包 
 
    ret =i2c_transfer(adap, &msg,1);        // 调用平台接口发送数据 
 
    
    return (ret== 1) ? count :ret;          // 如果发送成功就返回字节数 

EXPORT_SYMBOL(i2c_master_send); 
i2c_master_send 接口的三个参数:client 为此次与主机通信的从机,buf 为发送的数据指针,count为发送数据的字节数。
[cpp] 
int i2c_master_recv(struct i2c_client *client, char *buf ,intcount) 

    structi2c_adapter *adap=client->adapter;  //获取adapter信息 
    structi2c_msgmsg;                       // 定义一个临时的数据包 
    intret; 
 
    msg.addr =client->addr;                  // 将从机地址写入数据包 
    msg.flags =client->flags &I2C_M_TEN;    // 将从机标志并入数据包 
    msg.flags |=I2C_M_RD;                    // 将此次通信的标志并入数据包 
    msg.len =count;                          // 将此次接收的数据字节数写入数据包 
    msg.buf =buf; 
 
    ret =i2c_transfer(adap, &msg,1);        // 调用平台接口接收数据 
 
    
    return (ret== 1) ? count :ret;          // 如果接收成功就返回字节数 

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

    intret; 
 
    structi2c_msg msgs[] = { 
       
           .addr   =client->addr, 
           .flags  = 0, 
           .len    =1, 
           .buf    =®,  // 寄存器地址 
       }, 
       
           .addr   =client->addr, 
           .flags  = I2C_M_RD, 
           .len    =1, 
           .buf    =data,  // 寄存器的值 
       }, 
   }; 
 
    ret =i2c_transfer(client->adapter, msgs, 2);  // 这里num = 2,通信成功 ret = 2 
    if (ret <0) 
       tp_err("%s error: %d\n", __func__, ret); 
 
    returnret; 

还可调用前面所述的接口封装:
[cpp] 
static unsigned char read_reg(struct i2c_client *client, unsignedchar reg) 

    unsignedchar buf; 
 
   i2c_master_send(client, ®, 1);  //发送寄存器地址 
   i2c_master_recv(client, &buf, 1);  //接收寄存器的值 
 
   return  buf; 

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]
 
static int __i2c_reset_device(struct device *dev, void*addrp) 

    structi2c_client *client = to_i2c_client(dev); 
    int addr =*(int *)addrp; 
 
    if (client&& client->driver &&client->driver->reset) 
       return client->driver->reset(); 
 
    return0; 

 
int i2c_reset_device(struct i2c_adapter *adapter, intaddr) 

    returndevice_for_each_child(&adapter->dev, &addr,__i2c_reset_device); 

EXPORT_SYMBOL(i2c_reset_device); 
需要注意的是i2c.driver的reset函数返回值需要为0,不然device_for_each_child不会继续后面的遍历。用GPIO模拟SCL解锁的参考代码如下:
[cpp] 
static int i2c_reset_adapter(void) 

    int counter= 0; 
 
   gpio_request(I2C_BUS_DATA, "gpioxx"); 
   gpio_request(I2C_BUS_CLK, "gpioxx"); 
    
   gpio_direction_input(I2C_BUS_DATA); 
 
    if(!__gpio_get_value(I2C_BUS_DATA)) { 
       while((!__gpio_get_value(I2C_BUS_DATA)) && ++counter <10) 
       
           udelay(5); 
           gpio_direction_output(I2C_BUS_CLK, 1); 
           udelay(5); 
           gpio_direction_output(I2C_BUS_CLK, 0); 
       
       i2c_err("try to recover i2c bus, retry times are%d\n",counter); 
       if (counter < 10) { 
           udelay(5); 
           gpio_direction_output(I2C_BUS_DATA, 0); 
           udelay(5); 
           gpio_direction_output(I2C_BUS_CLK, 1); 
           udelay(5); 
           gpio_direction_output(I2C_BUS_DATA, 1); 
           msleep(10); 
       } else { 
           i2c_err("try to recover i2c busfailed!\n"); 
       
   
 
   gpio_free(I2C_BUS_DATA); 
   gpio_free(I2C_BUS_CLK); 
 
    return0; 
}


 Write功能的实际实现原理如图3所示:

        (1)设置GPIO的相关引脚为IIC输出;

        (2)设置IIC(打开ACK,打开IIC中断,设置CLK等);

        (3)设备地址赋给IICDS,并设置IICSTAT,启动IIC发送设备地址出去;从而找到相应的设备即IIC总线上的设备。

        (4)第一个Byte的设备地址发送后,从EEPROM得到ACK信号,此信号触发中断;

        (5)在中断处理函数中把第二个Byte(设备内地址)发送出去;发送之后,接收到ACK又触发中断;

        (6)中断处理函数把第三个Byte(真正的数据)发送到设备中。

        (7)发送之后同样接收到ACK并触发中断,中断处理函数判断,发现数据传送完毕。

        (8)IICStop信号,关IIC中断,置位各寄存器。

0 0