Skip to main content

I2C

介绍I2C的功能和使用方法。

模块介绍

I2C 总线是一种两线式串行总线,用于连接微控制器及其外围设备,多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短。每个设备都有自己的唯一地址,且I2C为半双工,任意时刻只能有一个主机并进行单行通信。

功能介绍

i2c

Linux 中 I2C 体系结构上图所示,共分成了三个层次:

  1. 用户空间,包括所有使用I2C 设备的应用程序;
  2. 内核,也就是驱动部分;
  3. 硬件,指实际物理设备,包括了 I2C 控制器和 I2C 外设。

其中,Linux 内核中的 I2C 驱动程序从逻辑上主要实现:

  • I2C framework 提供一种 "访问 I2C slave devices" 的方法。由于这些 slave devices 由 I2C controller 控制,因而主要由 I2C controller 驱动实现这一目标。
  • 在 I2C framework 内部,有 I2C core、I2C busses、I2C algos 和 I2C muxes 四个模 块。
  • I2C core 使用 I2C adapter 和 I2C algorithm 两个子模块抽象 I2C controller 的功能。
  • I2C busses 是各个 I2C controller drivers 的集合,位于 drivers/i2c/busses/目录下, i2c-k1.c。
  • I2C algos 包含了一些通用的 I2C algorithm,所谓的 algorithm,是指 I2C 协议的通信方 法,用于实现 I2C 的 read/write 指令。

源码结构介绍

控制器驱动代码在drivers/i2c/目录下:

drivers/i2c/
|-- i2c-core-of.c #I2C子系统核心文件,提供相关的接口函数
|-- i2c-boardinfo.c
|-- i2c-core-base.c
|-- i2c-core-slave.c
|-- i2c-core-smbus.c
|-- i2c-dev.c #I2C子系统的设备相关文件,用于注册相关的设备文件
|-- busses/i2c-k1.c #K3平台的I2C控制器驱动代码

关键特性

特性

特性特性说明
支持8组I2C支持8组I2C接口(i2c0-6、i2c8)
支持100K / 400K两种速度模式支持标准模式(100KHz)和快速模式(400KHz),通过dts中 clock-frequency 属性配置
支持总线仲裁多主机模式下支持总线仲裁
支持SDA毛刺抑制控制器内建SDA毛刺修复逻辑,默认为开启,可在 DTS 中添加 spacemit,sda-glitch-nofix 属性关闭
支持I2C Slave模式控制器支持作为从设备(Slave)响应外部主机的读写请求

此外,K3 的 R-domain(RCPU 侧)还提供 2 组独立的 I2C 控制器 r_i2c0 和 r_i2c1(定义在 k3-rdomain.dtsi 中),使用相同的 spacemit,k1-i2c 驱动,时钟由 syscon_rcpu_i2cctrl 提供,默认 disabled。pinctrl 可选 ri2c0_0_cfg ~ ri2c0_3_cfg、ri2c1_0_cfg ~ ri2c1_3_cfg。

配置介绍

主要包括驱动使能配置和dts配置

CONFIG配置

  • CONFIG_I2C 和 CONFIG_I2C_K1,K3 SDK 的 defconfig 中此选项已开启
Device Drivers
I2C support
I2C support (I2C [=y])
I2C Hardware Bus support
SpacemiT K1 I2C adapter (I2C_K1 [=y])
  • CONFIG_I2C_CHARDEV为I2C字符设备
Device Drivers
I2C support
I2C support (I2C [=y])
I2C device interface (I2C_CHARDEV [=y])
  • CONFIG_I2C_SLAVE 用于开启 I2C 从设备功能
Device Drivers
I2C support
I2C support (I2C [=y])
I2C slave support (I2C_SLAVE [=y])

dts配置

K3 的 I2C 控制器复用 spacemit,k1-i2c IP 及其 DT binding(见 Documentation/devicetree/bindings/i2c/spacemit,k1-i2c.yaml),主要属性如下:

配置说明
clock-frequencyI2C总线时钟频率,支持100000(标准模式)和400000(快速模式),默认400000
spacemit,sda-glitch-nofix旁路SDA毛刺修复逻辑,连接某些设备时,可消除重复起始信号的延迟

总线默认速率为 400KHz。如需切换为标准模式(100KHz),在板级 DTS 中修改 clock-frequency 即可:

&i2c2 {
clock-frequency = <100000>;
};

pinctrl

查看开发板原理图,找到 i2c 控制器使用的 pin 组。

K3 平台每个 I2C 控制器提供多组 pinctrl 配置,定义在 k3-pinctrl.dtsi 中。以 i2c2 为例,假设使用 i2c2_1_cfg 组(PAD 106/107),板级 DTS 配置如下。

&i2c2 {
pinctrl-names = "default";
pinctrl-0 = <&i2c2_1_cfg>;
};

各 I2C 控制器可用的 pinctrl 配置组如下:

控制器可用配置组
i2c0i2c0_0_cfg ~ i2c0_4_cfg(5组)
i2c1i2c1_0_cfg ~ i2c1_3_cfg(4组)
i2c2i2c2_0_cfg ~ i2c2_3_cfg(4组)
i2c3i2c3_0_cfg ~ i2c3_3_cfg(4组)
i2c4i2c4_0_cfg ~ i2c4_3_cfg(4组)
i2c5i2c5_0_cfg ~ i2c5_3_cfg(4组)
i2c6i2c6_0_cfg ~ i2c6_3_cfg(4组)
i2c8i2c8_cfg(mux 0,PAD128/129 连接 AP 侧 i2c8);i2c8_cfg_rcpu(mux 1,同一组引脚切换到 RCPU 侧功能,常用于 rpmi_regulator/PMIC 场景,此时 AP 侧 i2c8 不能使用这组引脚)

i2c 设备配置

以 EEPROM 设备为例说明 i2c 设备的 DTS 如何配置。

设备类型

确认设备类型以及使用的驱动。

选择 EEPROM 设备的 compatible 配置为 atmel,24c02

设备地址

确认设备的 i2c 通信地址,再落到 DTS 的 reg 属性。

查询原理图得到 EEPROM 设备地址为 0x50,配置如下。

eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
}
通信频率

确认设备通信频率。

EEPROM 通信频率支持 400KHz,选择挂载在 i2c2 下,使用默认 clock-frequency = <400000>

设备控制信号

在板级原理图中查询设备所使用的控制信号。

EEPROM 设备一般不需要额外的控制信号(如 reset/irq),仅需 I2C 总线连接即可。如设备需要写保护控制,可配置对应 GPIO。

设备 dts

EEPROM 设备地址为 0x50,通信频率为 400KHz,配置为只读并使用 ONIE TLV 布局存储板级信息。

设备 dts 配置如下:

eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
read-only;
status = "okay";

nvmem-layout {
compatible = "onie,tlv-layout";
};
};

dts示例

综合上述信息,i2c2 连接 EEPROM 设备,板级 DTS 配置如下。

&i2c2 {
pinctrl-names = "default";
pinctrl-0 = <&i2c2_1_cfg>;
status = "okay";

eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
read-only;
status = "okay";

nvmem-layout {
compatible = "onie,tlv-layout";
};
};
};

I2C Slave 设备配置

K3 的 I2C 控制器支持作为从设备(Slave)运行。当作为从设备时,可以通过内核标准的 I2C Slave 框架响应外部主机的读写请求。下面以模拟 EEPROM 设备(开启 CONFIG_I2C_SLAVE_EEPROM)为例,介绍如何实例化一个 I2C Slave 设备。

目前主要有 DTS 声明sysfs 动态声明 两种方式:

DTS 声明方式

在板级 DTS 的 I2C 控制器节点下,可以直接添加 Slave 设备节点。与普通主设备下挂载外设不同,声明自身的 Slave 节点时,reg 属性需要在设定的从设备真实地址的基础上加上 I2C_OWN_SLAVE_ADDRESS 标志位(即 1 << 300x40000000)。

假设我们要把 i2c2 控制器配置为 Slave 模式,设定其被访问的设备地址为 0x53,DTS 配置示例如下:

#include <dt-bindings/i2c/i2c.h>

&i2c2 {
pinctrl-names = "default";
pinctrl-0 = <&i2c2_1_cfg>;
status = "okay";

/* 模拟一个 24c02 的 Slave 设备,设定自身通信地址为 0x53 */
slave_eeprom: eeprom@53 {
compatible = "slave-24c02";
reg = <(I2C_OWN_SLAVE_ADDRESS | 0x53)>;
status = "okay";
};
};

注意:配置为Slave模式并加载驱动后,此时该开发板的I2C2接口会被外部的主机当做一个物理的24c02 EEPROM芯片进行访问。

sysfs动态声明方式

如果不想在 DTS 中固定写死,也可以在系统启动后,通过用户态的 sysfs 接口动态实例化 Slave 设备。使用 sysfs 接口时,传入的设备地址需加上 Slave 节点特有的地址偏移标志位 0x1000。

以在 i2c-2 总线上动态实例化地址为 0x53 的 24c02 模拟 EEPROM 为例:

# 0x1000 (I2C_CLIENT_SLAVE 偏移) + 0x53 = 0x1053
echo slave-24c02 0x1053 > /sys/bus/i2c/devices/i2c-2/new_device

执行后,系统会创建一个 Slave 模拟节点(如 /sys/bus/i2c/devices/2-1053/slave-eeprom)。外部主机可以直接通过该 I2C 总线向地址 0x53 写入数据,而本机的用户态程序也能通过读取该 sysfs 节点来验证和处理收到的数据:

# 在本机查看作为 Slave 收到的外部主机写入的数据
hexdump -C /sys/bus/i2c/devices/2-1053/slave-eeprom

若不再需要从设备功能,要注销该设备可执行:

echo 0x1053 > /sys/bus/i2c/devices/i2c-2/delete_device

接口介绍

API介绍

内核态:I2C的读写通信都是使用linux标准接口,请参考kernel目录下 Documentation/i2c/writing-clients.rst 文档说明,关于发送和接收部分有详细的介绍。

用户态:从用户态可以通过 /dev/i2c-%d 节点访问总线上所有的设备。demo示例是一个简单的访问i2c设备读写,具体可以参考Documentation/i2c/dev-interface.rst文档。

K3 的 I2C 总线号由设备树 aliases 决定(见 k3.dtsi),对应的用户态设备节点为 /dev/i2c-0 ~ /dev/i2c-6/dev/i2c-8(无 /dev/i2c-7)。

demo示例

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>

#define I2C_DEV_FILE "/dev/i2c-1" // I2C设备文件路径,通常为/dev/i2c-1
#define DEVICE_ADDR 0x68 // I2C设备的地址,示例为0x68<根据实际设备修改>

// 写数据到I2C设备
int write_to_i2c(int file, uint8_t reg, uint8_t value) {
uint8_t buffer[2];
buffer[0] = reg; // 寄存器地址
buffer[1] = value; // 写入的值

if (write(file, buffer, 2) != 2) {
perror("I2C write failed");
return -1;
}
return 0;
}

// 从I2C设备读取数据
int read_from_i2c(int file, uint8_t reg, uint8_t *data, size_t len) {
if (write(file, &reg, 1) != 1) {
perror("I2C write register failed");
return -1;
}

if (read(file, data, len) != len) {
perror("I2C read failed");
return -1;
}
return 0;
}

int main() {
int file;
uint8_t data[2]; // 用于存储读取到的数据

// 打开I2C设备文件
if ((file = open(I2C_DEV_FILE, O_RDWR)) < 0) {
perror("Failed to open I2C bus");
exit(1);
}

// 设置I2C从设备的地址
if (ioctl(file, I2C_SLAVE, DEVICE_ADDR) < 0) {
perror("Failed to acquire bus access and/or talk to slave");
close(file);
exit(1);
}

if (write_to_i2c(file, 0x6B, 0x00) < 0) {
close(file);
exit(1);
}
printf("Write 0x00 to register 0x6B\n");

if (read_from_i2c(file, 0x75, data, 1) < 0) {
close(file);
exit(1);
}
printf("Read data 0x%02x from register 0x75\n", data[0]);

// 关闭I2C设备文件
close(file);

return 0;
}

Debug介绍

sysfs

启用 CONFIG_I2C_CHARDEV 后,适配器和挂载的设备都会出现在 /sys/bus/i2c/devices/ 目录,可用以下命令快速查看:

ls /sys/bus/i2c/devices/i2c-*/name
cat /sys/bus/i2c/devices/2-0050/name # 查看 EEPROM client 节点

debugfs

K3 默认开启 debugfs,挂载后可在 /sys/kernel/debug/i2c/ 查看各适配器目录,再进入对应目录查看挂载的 client(例如 2-0050):

ls /sys/kernel/debug/i2c # 列出所有 i2c 控制器
ls /sys/kernel/debug/i2c/i2c-2 # 查看 i2c-2 下的 client,显示类似 2-0050

测试介绍

I2C tool是一个开源工具,可自行下载进行交叉编译,代码下载地址 编译后会生成 i2cdetect, i2cdump, i2cset, i2cget 等工具,可以直接用命令行调试使用:

  • i2cdetect:用来列举i2c bus和上面所有的设备
  • i2cdump:显示i2c设备上所有register的值
  • i2cset:写入i2c设备某个register的值
  • i2cget:读取i2c设备某个register的值

FAQ

1. i2c传输超时问题

异常log如下:

[ 126.800897] i2c-k1 d401d800.i2c: msg completion timeout

排查步骤:

  1. 确认i2c硬件电阻,如sda线上有slave需要旁路电容,建议把sda上拉电阻换成1k;
  2. 确认i2c pinctrl驱动能力,提高对应i2c口的pinctrl驱动能力(drive-strength);
  3. 如因SDA毛刺修复逻辑导致重复起始信号延迟,可尝试在dts中加入 spacemit,sda-glitch-nofix 属性关闭该逻辑。下图展示了示波器采样到的异常波形:默认修复逻辑在 ACK 后主动将 SDA 拉低保持,释放时 SDA 上升沿延迟,在 ACK 与 Repeated Start 之间产生一个尖峰毛刺(红色箭头处),导致后续 Repeated Start 条件无法被从设备正确识别,控制器报 msg completion timeout

alt text