SSU_I2C使用参考


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 11/18/2021
    2.0
  • DTS new value
  • 07/26/2022

    1. 概述

    1.1. IP组

    设备节点和硬件组别的关系如下表所示。

    I2C Group bank addr DEV
    HW I2C group0 1114H /dev/i2c-0
    HW I2C group1 1115H /dev/i2c-1
    HW I2C group2 1116H /dev/i2c-2
    HW I2C group3 1117H /dev/i2c-3
    HW I2C group4 1118H /dev/i2c-4
    HW I2C group5 1119H /dev/i2c-5

    1.2. master device

    所有的I2C都只能作为主设备(master),无法作为从设备(slave)。

    1.3. kernel driver

    kernel/drivers/sstar/i2c/drv_iic.c

    kernel/drivers/sstar/i2c/drv_iic.h

    kernel/drivers/sstar/i2c/pioneer5/hal_iic.c

    kernel/drivers/sstar/i2c/pioneer5/hal_iic.h

    kernel/drivers/sstar/i2c/pioneer5/hal_iic_reg.h

    kernel/drivers/sstar/i2c/cam_drv_i2c.c (cam driver,仅开放给kernel space使用)

    kernel/drivers/sstar/include/cam_drv_i2c.h (cam driver,仅开放给kernel space使用)

    1.4. 功能说明

    a) 支持DMA/非DMA两种模式,可在dtsi设备节点当中修改dma属性值选择。

    b) 读写支持,支持S+W+RS+R和S+W+P+RS+R两种通讯时序,设定方法参考用户态读写I2C章节;对每次支持的字节数,非dma mode不做限制,理论上允许length变量的最大长度为u32即0xFFFFFFFF。dma mode因dma buffer长度限制每次最大允许为4096字节。建议较长字节数的时候打开dma mode,减少cpu loading。

    c) 时序可调,周期内起始结束信号可调,通过dtsi设备节点部分属性调整。

    d) 电平输出方式两种可选:推挽输出、开漏输出,通过dtsi设备节点当中修改push-pull属性选择。

    e) 通讯速率:理论上最高速率可达1.5M,速率设定在dtsi设备节点当中设置,可根据实际需要修改,不分档位,修改方法见DTS定义章节。

    f) 支持1toN通讯,只写。

    g) PAD可选,参考Padmux章节。

    h) 寻址方式:传输过程在struct i2c_msg当中把slave addr给下来,目前driver支持8bit addr,register addr放在data当中,所以原则上register addr支持8bit或16bit或更高,不做限制。


    2. DTS定义

    1. i2c0@0 {
    2.     compatible = "sstar,i2c";
    3.     reg = <0x1F222800 0x200>;
    4.     #address-cells = <1>;
    5.     #size-cells = <0>;
    6.     interrupts = <GIC_SPI INT_IRQ_MIIC IRQ_TYPE_LEVEL_HIGH>;
    7.     clocks = <&CLK_miic0>;
    8.     i2c-group = <0>;
    9.     i2c-en-dma = <1>;
    10.    i2c-speed = <400000>;
    11.    //speed <= 200KHz,src clk = 12MHz
    12.    //speed <= 700KHz,src clk = 54MHz
    13.    //else src clk = 72MHz
    14.    i2c-t-hd = <0>;//if u want set tHD,set here not 0,tHD = (i2c-t-hd / i2c-srcclk)S
    15.    i2c-t-su = <0>;//if u want set tSU,set here not 0,tSU = (i2c-t-su / i2c-srcclk)S
    16.    i2c-t-start = <0>;//set start cnt,or it will auto set in driver
    17.    i2c-t-stop = <0>;//set stop cnt,or it will auto set in driver
    18.    i2c-push-pull = <1>;//set 1 enable push-pull output,set 0 open drain
    19.    i2c-oen-cnt   = <1>;
    20.    status = "ok";
    21. }
    

    I2C master驱动中支持配置的属性如下表:

    属性 描述 设定值 备注
    compatible 用于匹配驱动进行驱动注册,需与代码中一致 “sstar,i2c” 禁止修改
    reg 用于指定IIC寄存器bank的地址 不需要修改
    interrupts 用于指定使用的硬件中断号及属性 不需要更改
    clocks 用于指定使用的时钟源 不需要更改
    #address-cells 用于指定子节点的地址位宽 不需要更改
    #size-cells 用于指定子节点的大小位宽 不需要更改
    i2c-group 用于指定IIC外设编号序列号 不需要修改
    i2c-speed 用于选择IIC Speed,直接填写实际值 如:250000即250K 可根据需要修改
    i2c-en-dma 用于选择是否使用DMA模式 1-开启;0-关闭 可根据需要修改
    i2c-t-hd 用于设定结束信号保持时间 cnt value 可根据需要修改
    i2c-t-su 用于设定起始信号建立时间 cnt value 可根据需要修改
    i2c-t-start 用于设定起始信号保持时间 cnt value 可根据需要修改
    i2c-t-stop 用于设定结束信号建立时间 cnt value 可根据需要修改
    i2c-push-pull 用于打开推挽输出方式 1-推挽输出;0-开漏输出 可根据需要修改
    i2c-oen-cnt 用于优化推挽输出的上升沿波形 可根据需要修改
    status 用于选择是否使能IIC master驱动 可根据需要修改

    几个详细说明:

    i2c-t-hd/ i2c-t-su/ i2c-t-start/ i2c-t-stop:如下图2-1所示几个红框,就是几个属性调整的位置。如果节点中属性值为0,driver会按照既定计算得出count值,如果有设定则按照设定值,设定的理论最大值为0xFFFF,即65535,但当设置过大时,如果是RIU mode的轮询方式会导致轮询超时,导致失败返回。属性值与时间对应关系如下:

    首先通过i2c-speed属性值确定时钟源:

    • speed <= 200KHz, src clk = 12MHz

    • speed <= 700KHz, src clk = 54MHz

    • else src clk = 72MHz

    计算公式:

    设定时间 = (属性值)/(时钟源)

    举例,设定时间需要4us,时钟源12MHz:

    属性值 = 设定时间 * 时钟源 = 4 * 10-6 * 12 * 106 = 48

    图2-1: i2c-t-hd/ i2c-t-su/ i2c-t-start/ i2c-t-stop属性调整位置

    i2c-push-pull:更改引脚的输出方式,设为1时是推挽输出,0为开漏输出。

    i2c-oen-cnt:当开启推挽输出时,可以设定这个属性来调整clk的上升沿波形,具体如下图,图2-2为该属性值为1的波形,图2-3为属性值为0的波形,如果i2c-push-pull没有打开的话,i2c-oen-cnt的设定都无效。

    图2-2: i2c-oen-cnt = 1

    图2-3: i2c-oen-cnt = 0


    3. Padmux

    3.1. 使用与修改

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

    例:如使用 SSC028A-S01a 板时,对应请修改pionner5-ssc028a-s01a-padmux.dtsi

    1. <PAD_I2C0_SCL      PINMUX_FOR_I2C0_MODE_5        MDRV_PUSE_I2C0_SCL>,
    2. <PAD_I2C0_SDA      PINMUX_FOR_I2C0_MODE_5        MDRV_PUSE_I2C0_SDA>,
    

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

    3.2. 各I2C硬件组padmux罗列

    I2C BUS padmod register addr PAD PIN_NAME
    I2C-0 1 bank 103cH offset 6fH bit[2:0] PAD_I2C0_SDA I2C0_SDA0
    PAD_I2C0_SCL I2C0_SCL0
    2 PAD_KEY5 I2C0_SDA0
    PAD_KEY6 I2C0_SCL0
    3 PAD_FUART_RTS I2C0_SDA0
    PAD_FUART_CTS I2C0_SCL0
    4 PAD_I2C0_SDA I2C0_SDA0
    PAD_I2C0_SCL I2C0_SCL0
    PAD_I2C1_SDA I2C0_SDA1
    PAD_I2C1_SCL I2C0_SCL1
    PAD_I2C2_SDA I2C0_SDA2
    PAD_I2C2_SCL I2C0_SCL2
    PAD_I2C3_SDA I2C0_SDA3
    PAD_I2C3_SCL I2C0_SCL3
    I2C-1 1 bank 103cH offset 53H bit[2:0] PAD_I2C1_SDA I2C1_SDA0
    PAD_I2C1_SCL I2C1_SCL0
    2 PAD_SD0_D3 I2C1_SDA0
    PAD_SD0_D2 I2C1_SCL0
    3 PAD_SAR_ADC_2 I2C1_SDA0
    PAD_SAR_ADC_3 I2C1_SCL0
    4 PAD_I2C0_SDA I2C1_SDA0
    PAD_I2C0_SCL I2C1_SCL0
    PAD_I2C1_SDA I2C1_SDA1
    PAD_I2C1_SCL I2C1_SCL1
    5 PAD_I2C0_SDA I2C1_SDA0
    PAD_I2C0_SCL I2C1_SCL0
    PAD_I2C1_SDA I2C1_SDA1
    PAD_I2C1_SCL I2C1_SCL1
    PAD_I2C2_SDA I2C1_SDA2
    PAD_I2C2_SCL I2C1_SCL2
    PAD_I2C3_SDA I2C1_SDA3
    PAD_I2C3_SCL I2C1_SCL3
    I2C-2 1 bank 103cH offset 6fH bit[9:8] PAD_I2C2_SDA I2C2_SDA
    PAD_I2C2_SCL I2C2_SCL
    2 PAD_RGMII0_RXD0 I2C2_SDA
    PAD_RGMII0_RXD1 I2C2_SCL
    3 PAD_I2C3_SDA I2C2_SDA
    PAD_I2C3_SCL I2C2_SCL
    I2C-3 1 bank 103cH offset 73H bit[1:0] PAD_I2C3_SDA I2C3_SDA
    PAD_I2C3_SCL I2C3_SCL
    2 PAD_PWM_OUT8 I2C3_SDA
    PAD_PWM_OUT9 I2C3_SCL
    3 PAD_OUTP_TX1_CH[4] I2C3_SDA
    PAD_OUTN_TX1_CH[4] I2C3_SCL
    I2C-4 1 bank 103cH offset 73H bit[6:4] PAD_KEY5 I2C4_SDA
    PAD_KEY6 I2C4_SCL
    2 PAD_PWM_OUT10 I2C4_SDA
    PAD_PWM_OUT11 I2C4_SCL
    3 PAD_OUTN_TX1_CH[1] I2C4_SDA
    PAD_OUTP_TX1_CH[2] I2C4_SCL
    4 PAD_RGMII0_MDIO I2C4_SDA
    PAD_RGMII0_MDC I2C4_SCL
    I2C-5 1 bank 103cH offset 103c offset 73H bit[10:8] PAD_I2C5_SDA I2C5_SDA
    PAD_I2C5_SCL I2C5_SCL
    2 PAD_SAR_ADC_4 I2C5_SDA
    PAD_SAR_ADC_5 I2C5_SCL
    3 PAD_RGMII0_TXD2 I2C5_SDA
    PAD_RGMII0_TXD3 I2C5_SCL
    4 PAD_PM_I2CM_SCL I2C5_SDA
    PAD_PM_I2CM_SDA I2C5_SCL

    3.3. 1toN

    功能说明:

    硬件预设某一路I2C的padmod同时接入到多组PAD上面,在每一路PAD上都可以接一个I2C slave device, 注意:这些slave device的地址都必须要相同,同时该功能只能做写操作,不能做读操作。

    操作方式:

    参考3.2节当中,如I2C-0 padmod4,共接到四路PAD上面,对应四组I2C PIN_NAME为I2C0_SCL0,I2C0_SCL3,I2C0_SDA0,I2C0_SDA3。I2C0_SCL0和I2C0_SDA0为一组,可接入一个I2C从设备,因此一共可以接入四组。

    在设定上没有什么特殊的地方,同样是在xxx-padmux.dtsi里面设定padmode,依照统一格式设定把对应的所有pad都写出来。请注意,一定要如下代码段把所有的PAD都写出来,否则padmux driver那边匹配不到所有的PAD的话,padmod设定就会失败。

    3. <PAD_I2C0_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA>,
    4. <PAD_I2C0_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL>,
    5. <PAD_I2C1_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA1>,
    6. <PAD_I2C1_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL1>,
    7. <PAD_I2C2_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA2>,
    8. <PAD_I2C2_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL2>,
    9. <PAD_I2C3_SDA            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SDA3>,
    10. <PAD_I2C3_SCL            PINMUX_FOR_I2C0_MODE_4          MDRV_PUSE_I2C0_SCL3>,
    

    之后通过正常的写操作调用完成写操作。


    4. 使用I2C

    4.1. 用户态读写I2C

    通过标准的/dev文件来读写I2C,以下是一个使用的小例子。

    在下方代码段的第62行中,flags变量的bit[1]用来指代i2c通讯中restart之前是否发送stop信号,即当次i2c_msg传输是否发送STOP信号。

    我们允许一次ioctl下发信息中包含多个struct i2c_message信息,每个message发送完成之后可以选择是否发送stop信号,但最后一次必然会发送stop信号。同时,如果是读操作,之后也必须发送stop信号。

    例如,当我们要读数据的时候,需要先写发送要读取的寄存器地址,此时我们可以通过flags的bit[1]来控制这次写发送是否要跟上stop信号:

    11. messages[0].flags = 0      // S+WR+RS+RD+P
    12. messages[0].flags |= 0x02 // S+WR+P+RS+RD+P
    
    1. #include <stdio.h>
    2. #include <linux/types.h>
    3. #include <stdlib.h>
    4. #include <fcntl.h>
    5. #include <unistd.h>
    6. #include <sys/types.h>
    7. #include <sys/ioctl.h>
    8. #include <errno.h>
    9. #include <assert.h>
    10. #include <string.h>
    11. #include <linux/i2c.h>
    12. #include <linux/i2c-dev.h>
    13.
    14. #define FILE_NAME "/dev/i2c-0"
    15.
    16. static int i2c_write(int fd,unsigned char slave_addr, unsigned char reg_addr, unsigned char value)
    17. {
    18.     unsigned char outbuf[2];
    19.     struct i2c_rdwr_ioctl_data packets;
    20.     struct i2c_msg messages[1];
    21.
    22.     messages[0].addr  = slave_addr;
    23.     messages[0].flags = 0;
    24.     messages[0].len   = sizeof(outbuf);
    25.     messages[0].buf   = outbuf;
    26.
    27.     /* The first byte indicates which register we‘ll write */
    28.     outbuf[0] = reg_addr;
    29.
    30.     /*
    31.      * The second byte indicates the value to write.  Note that for many
    32.      * devices, we can write multiple, sequential registers at once by
    33.      * simply making outbuf bigger.
    34.      */
    35.     outbuf[1] = value;
    36.
    37.     /* Transfer the i2c packets to the kernel and verify it worked */
    38.     packets.msgs  = messages;
    39.     packets.nmsgs = 1;
    40.     if(ioctl(fd, I2C_RDWR, &packets) < 0)
    41.     {
    42.         perror("Unable to send data");
    43.         return 1;
    44.     }
    45.
    46.     return 0;
    47. }
    48.
    49. static int i2c_read(int fd, unsigned char slave_addr, unsigned char reg_addr, unsigned char *value)
    50. {
    51.     unsigned char inbuf, outbuf;
    52.     struct i2c_rdwr_ioctl_data packets;
    53.     struct i2c_msg messages[2];
    54.
    55.     /*
    56.      * In order to read a register, we first do a "dummy write" by writing
    57.      * 0 bytes to the register we want to read from.  This is similar to
    58.      * the packet in set_i2c_register, except it‘s 1 byte rather than 2.
    59.      */
    60.     outbuf = reg_addr;
    61.     messages[0].addr  = slave_addr;
    62.     messages[0].flags = 0;// if S+WR+P+RS+RD+P is intended, then set:
    63.                              //nmessages[0].flags |= 0x02;
    64.     messages[0].len   = sizeof(outbuf);
    65.     messages[0].buf   = &outbuf;
    66.
    67.     /* The data will get returned in this structure */
    68.     messages[1].addr  = slave_addr;
    69.     messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
    70.     messages[1].len   = sizeof(inbuf);
    71.     messages[1].buf   = &inbuf;
    72.
    73.     /* Send the request to the kernel and get the result back */
    74.     packets.msgs      = messages;
    75.     packets.nmsgs     = 2;
    76.     if(ioctl(fd, I2C_RDWR, &packets) < 0)
    77.     {
    78.         perror("Unable to send data");
    79.         return 1;
    80.     }
    81.     *value = inbuf;
    82.
    83.     return 0;
    84. }
    85.
    86. int main(int argc, char **argv)
    87. {
    88.     int fd;
    89.     unsigned int slave_addr=0, reg_addr=0, value = 0;
    90.
    91.     if (argc < 4){
    92.         printf("Usage:\n%s r[w] start_addr reg_addr [value]\n",argv[0]);
    93.         return 0;
    94.     }
    95.
    96.     fd = open(FILE_NAME, O_RDWR);
    97.     if (!fd)
    98.     {
    99.         printf("can not open file %s\n", FILE_NAME);
    100.         return 0;
    101.     }
    102.
    103.     sscanf(argv[2], "%x", &slave_addr);
    104.     sscanf(argv[3], "%x", ®_addr);
    105.
    106.     if(!strcmp(argv[1],"r"))
    107.     {
    108.         i2c_read(fd, slave_addr, reg_addr, (unsigned char*)&value);
    109.     }
    110.     else if(argc>4&&!strcmp(argv[1],"w"))
    111.     {
    112.         sscanf(argv[4], "%x", &value);
    113.         i2c_write(fd, slave_addr, reg_addr, value);
    114.     }
    115.
    116.     close(fd);
    117.     return 0;
    118. }
    

    4.2. 内核态读写I2C

    通过内核标准接口操作I2C,内核态的如下方式调用同样可以像用户态那样一次给下多个struct i2c_msg信息,同时也可以选择每个msg是否接stop信号,与用户态的说明是一样的,不做赘述。

    下面是个小例子:

    1. struct i2c_adapter* adpa = NULL;
    2. struct i2c_msg msg;
    3. u8 data[4] = {0};
    4.
    5. adpa = i2c_get_adapter(nr); // Get i2c-0 adapter
    6.
    7. data[0] = reg & 0xff;
    8. data[1] = value & 0xff;
    9.
    10. msg.addr = slaveAddr>>1;
    11. msg.flags = 0;
    12. msg.buf = data;
    13. msg.len = 2;
    14.
    15. i2c_transfer(adpa, &msg, 1)