I2C使用参考

Version 1.4


1. 概述

I2C Group SCL SDA DEV
HW I2C group0 PAD_I2C0_SCL PAD_I2C0_SDA /dev/i2c-0
HW I2C group1 PAD_I2C1_SCL PAD_I2C1_SDA /dev/i2c-1
HW I2C group2 PAD_I2C2_SCL PAD_I2C2_SDA /dev/i2c-2
HW I2C group3 PAD_I2C3_SCL PAD_I2C3_SDA /dev/i2c-3
HW I2C group4 PAD_I2C4_SCL PAD_I2C4_SDA /dev/i2c-4
HW I2C group5 PAD_I2C5_SCL PAD_I2C5_SDA /dev/i2c-5

提供6组HW I2C:

  • 第一组HW I2C 对应pad 是PAD_I2C0_SCL/PAD_I2C0_SDA,对应节点是/dev/i2c-0;

  • 第二组HW I2C 对应pad 是PAD_I2C1_SCL/PAD_I2C1_SDA,对应节点是/dev/i2c-1;

  • 第三组HW I2C 对应pad 是PAD_I2C2_SCL/PAD_I2C2_SDA,对应节点是/dev/i2c-2;

  • 第四组、第五组、第六组以此类推。


2. DTS定义

以下属性一般不需要修改:

  • compatible 属性:用于匹配驱动进行驱动注册,该属性需与代码中一致,正常情况下请不要修改该属性内容

  • reg 属性:从左到右分别为I2C bank base、Padmux bank base和Clkgen bank base,一般不需要更改

  • interrupts 属性:指定使用的硬件中断号及属性,一般不需要更改

  • clocks 属性:指定使用的时钟源,一般不需要更改

  • #address-cells:指定子节点的地址位宽,不需要更改

  • #size-cells:指定子节点的大小位宽,不需要更改

  • i2c-group属性:指定I2C IP序列号,不需要更改

以下属性可以根据需求修改:

  • status属性:选择是否使用I2C Driver,可根据需求开关

  • i2c-speed属性:选择I2C Speed,可以根据需求选择,无该属性时,默认设定为400kHz,各取值对应的速率如下:

属性 取值 对应速率
i2c-speed 0 25k Hz
1 50k Hz
2 100k Hz
3 200k Hz
4 300k Hz
5 400k Hz
6 600k Hz
7 800k Hz
8 1000k Hz
9 1500k Hz

[重要] 由于各IP各自管理各自使用引脚的复用设置会引起某些引脚的复用关系冲突,故现已将引脚复用关系移至mercury6-sscxxxx-s01b-padmux.dtsi中设定。

例:如使用 SSC016A-S01B-S 板时,对应请修改mercury6-ssc016a-s01b-padmux.dtsi

如上图,第一列为引脚索引号,可在/drivers/sstar/inlcude/{chipname}/gpio.h中查到;第二列为模式定义,在/drivers/sstar/gpio/{chipname}/mhal_pinmux.c中m_stPadMuxTbl数组罗列了所有引脚的复用关系,查询该引脚支持的复用功能可以查询该数组;第三列为改组设定的索引号,可在/drivers/sstar/include/mdrv_puse.h里找到。


3. 使用I2C

3.1. 用户态读写I2C

通过标准的/dev文件来读写I2C,示例如下。

#include <stdio.h>  
#include <linux/types.h>  
#include <stdlib.h>  
#include <fcntl.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/ioctl.h>  
#include <errno.h>  
#include <assert.h>  
#include <string.h>  
#include <linux/i2c.h>  
#include <linux/i2c-dev.h>

#define FILE_NAME "/dev/i2c-0"

static int i2c_write(int fd,unsigned char slave_addr, unsigned char reg_addr, unsigned char value) 
{
    unsigned char outbuf[2];
    struct i2c_rdwr_ioctl_data packets;
    struct i2c_msg messages[1];

    messages[0].addr  = slave_addr;
    messages[0].flags = 0;
    messages[0].len   = sizeof(outbuf);
    messages[0].buf   = outbuf;

    /* The first byte indicates which register we‘ll write */
    outbuf[0] = reg_addr;

    /* 
    * The second byte indicates the value to write.  Note that for many
    * devices, we can write multiple, sequential registers at once by
    * simply making outbuf bigger.
    */
    outbuf[1] = value;

    /* Transfer the i2c packets to the kernel and verify it worked */
    packets.msgs  = messages;
    packets.nmsgs = 1;
    if(ioctl(fd, I2C_RDWR, &packets) < 0) 
    {
        perror("Unable to send data");
        return 1;
    }

    return 0;
}

static int i2c_read(int fd, unsigned char slave_addr, unsigned char reg_addr, unsigned char *value) 
{
    unsigned char inbuf, outbuf;
    struct i2c_rdwr_ioctl_data packets;
    struct i2c_msg messages[2];

    /*
    * In order to read a register, we first do a "dummy write" by writing
    * 0 bytes to the register we want to read from.  This is similar to
    * the packet in set_i2c_register, except it‘s 1 byte rather than 2.
    */
    outbuf = reg_addr;
    messages[0].addr  = slave_addr;
    messages[0].flags = 0;
    messages[0].len   = sizeof(outbuf);
    messages[0].buf   = &outbuf;

    /* The data will get returned in this structure */
    messages[1].addr  = slave_addr;
    messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
    messages[1].len   = sizeof(inbuf);
    messages[1].buf   = &inbuf;

    /* Send the request to the kernel and get the result back */
    packets.msgs      = messages;
    packets.nmsgs     = 2;
    if(ioctl(fd, I2C_RDWR, &packets) < 0) 
    {
        perror("Unable to send data");
        return 1;
    }
    *value = inbuf;

    return 0;
}

int main(int argc, char **argv)  
{  
    int fd;  
    unsigned int slave_addr=0, reg_addr=0, value = 0;

    if (argc < 4){  
        printf("Usage:\n%s r[w] start_addr reg_addr [value]\n",argv[0]);  
        return 0;  
    }

    fd = open(FILE_NAME, O_RDWR);      
    if (!fd)
    {  
        printf("can not open file %s\n", FILE_NAME);  
        return 0;  
    }

    sscanf(argv[2], "%x", &slave_addr);  
    sscanf(argv[3], "%x", &reg_addr);

    if(!strcmp(argv[1],"r")) 
    {
        i2c_read(fd, slave_addr, reg_addr, (unsigned char*)&value);
    }
    else if(argc>4&&!strcmp(argv[1],"w")) 
    {
        sscanf(argv[4], "%x", &value);  
        i2c_write(fd, slave_addr, reg_addr, value);
    }

    close(fd);
    return 0;
}

3.2. 内核态读写I2C

通过内核标准接口操作I2C。示例如下:

struct i2c_adapter* adpa = NULL;
struct i2c_msg msg;
u8 data[4] = {0};

adpa = i2c_get_adapter(nr); // 获取i2c-0 adapter

data[0] = reg & 0xff;          // 需要发送的数据
data[1] = value & 0xff;        // 需要发送的数据

msg.addr = slaveAddr>>1;    // 填充i2c_msg结构体
msg.flags = 0;               // 指定一些特殊操作0master->slave
msg.buf = data;
msg.len = 2;

i2c_transfer(adpa, &msg, 1) ; // 调用API开始传输

msg.flags = I2C_M_RD;  // 指定传输方向slave->master

i2c_transfer(adpa, &msg, 1); //调用API开始传输