I2C 驱动开发
本文档介绍 RK3588 I2C 控制器的内核配置、设备树绑定(在设备树里描述 I2C 控制器和挂在总线上的从设备)以及用户态的调试方法。
适用范围: RK3588 Linux 6.1 Debian 12 / Ubuntu 22.04 系统,C-3588LQ V1.0 + SHIMETAPI-M5-R2A V1.0 底板。
1. 概念基础
1.1 I2C 是什么
I2C(Inter-Integrated Circuit)是一种两线串行通信协议,一根 SDA 传数据、一根 SCL 传时钟,由 Philips 制定。RK3588 内置 9 路 I2C 控制器,支持的速率有:
| 模式 | 速率 |
|---|---|
| Standard | 100 kHz |
| Fast | 400 kHz |
| Fast-Plus | 1 MHz |
| High-Speed | 3.4 MHz |
常见用途:传感器(温湿度、加速度计)、EEPROM、PMIC、触摸屏 IC、Codec 芯片、HDMI/DP 控制器等。
1.2 总线架构
- 主从结构,支持多主(仲裁机制)
- 7-bit 和 10-bit 寻址
- 内置 FIFO + DMA,减轻 CPU 负担
2. 准备工作
2.1 硬件清单
| 组件 | 型号/规格 | 数量 | 备注 |
|---|---|---|---|
| 主控板 | C-3588LQ 核心板 + SHIMETAPI-M5-R2A 底板 | 1 | |
| 调试串口线 | USB 转 TTL(3.3V) | 1 | 波特率 1500000 |
| I2C 从设备 | AT24C02 EEPROM / TMP102 温度传感器 | 1 | 地址 0x50 / 0x48 |
| 上拉电阻 | 4.7 kΩ | 2 | SDA、SCL 各一个 |
| 杜邦线 | 母对母 | 若干 |
2.2 引脚连接示例
以 I2C1 为例:
| 主控板引脚 | 外设引脚 | 功能说明 |
|---|---|---|
| I2C1_SDA(Sheet 17/21) | EEPROM SDA | I2C 数据线 |
| I2C1_SCL(Sheet 17/21) | EEPROM SCL | I2C 时钟线 |
| 3.3V | EEPROM VCC | 电源 |
| GND | EEPROM GND | 共地 |
注意:SDA、SCL 必须各自外接 4.7 kΩ 上拉电阻到 3.3V(很多从设备内部已含上拉,可省略;具体见从设备数据手册)。
2.3 设备树 I2C 控制器基地址
RK3588 9 路 I2C 控制器的设备树基地址:
| 控制器 | 基地址 | 典型用途 |
|---|---|---|
| I2C0 | 0xfe2b0000 | PMU、电源管理 |
| I2C1 | 0xfe5a0000 | Codec、HDMI |
| I2C2 | 0xfe5b0000 | 外设 |
| I2C3 | 0xfe5c0000 | HDMI |
| I2C4 | 0xfe5d0000 | 外设 |
| I2C5 | 0xfe5e0000 | 外设 |
| I2C6 | 0xfe5f0000 | 触摸(如有) |
| I2C7 | 0xfe5a8000 | 外设 |
| I2C8 | 0xfe5a4000 | 摄像头 I2C(如有) |
3. 软件准备
3.1 内核配置
CONFIG_I2C=y
CONFIG_I2C_CHARDEV=y /* 用户态 /dev/i2c-x 接口 */
CONFIG_I2C_RK3X=y /* RK3xxx 系列控制器驱动,含 RK3588 */
CONFIG_OF=y
CONFIG_OF_I2C=y
CONFIG_I2C_RK3X是 Rockchip I2C 控制器的通用驱动,覆盖 RK3588。RK3588 实际可能为专用CONFIG_I2C_RK3X,驱动源码drivers/i2c/busses/i2c-rk3x.c。
3.2 设备树
SoC 级定义(rk3588.dtsi):
i2c1: i2c@fe5a0000 {
compatible = "rockchip,rk3588-i2c", "rockchip,rk3399-i2c";
reg = <0x0 0xfe5a0000 0x0 0x1000>;
clocks = <&cru CLK_I2C1>, <&cru PCLK_I2C1>;
clock-names = "i2c", "pclk";
interrupts = <GIC_SPI 45 IRQ_TYPE_LEVEL_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&i2c1m0_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled"; /* 默认关闭,由板级 dts enable */
};板级 enable 并挂载从设备:
&i2c1 {
status = "okay";
clock-frequency = <400000>; /* 400 kHz (Fast Mode) */
/* AT24C02 EEPROM */
eeprom: eeprom@50 {
compatible = "atmel,at24";
reg = <0x50>;
pagesize = <8>;
};
/* TMP102 温度传感器 */
temp_sensor: temperature-sensor@48 {
compatible = "ti,tmp102";
reg = <0x48>;
interrupt-parent = <&gpio3>;
interrupts = <RK_PB1 IRQ_TYPE_LEVEL_LOW>;
};
};4. 设备端 I2C 操作
4.1 查看 I2C 总线
先看看系统里启用了哪几路 I2C:
ls /sys/class/i2c-adapter/实机输出如下:
i2c-0
i2c-1
i2c-3
i2c-5
i2c-6
i2c-7
i2c-9i2c-2 / i2c-4 / i2c-8 缺失是正常的:这些总线对应的 pin 复用为其他功能(SPI / UART / PWM 等),设备树中未启用。
4.2 安装 i2c 工具(默认未装)
i2cdetect、i2cget、i2cset 这些命令来自 i2c-tools 软件包,系统默认没有装,先安装:
apt install -y i2c-tools4.3 扫描 I2C 总线
扫描能确认从设备是否在线、地址是否正确:
# 总线 0 扫描
i2cdetect -y 0输出示例:
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- ← AT24 EEPROM at 0x50
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --4.4 读写从设备
# 读 AT24C02 EEPROM 地址 0x00(1 字节)
i2cget -y 0 0x50 0x00
# 写 AT24C02 EEPROM 地址 0x00 数据 0xAA
i2cset -y 0 0x50 0x00 0xAA
# dump EEPROM 前 16 字节
i2cdump -y 0 0x50
# 扫描指定总线全部设备
i2cdetect -y 1 # bus 1
i2cdetect -y 3 # bus 34.5 通过 sysfs 查看从设备
sysfs(内核把设备信息映射到 /sys 下的文件接口)里也能看到已注册的从设备:
# 列出已注册到驱动的从设备
ls /sys/bus/i2c/devices/输出示例:
0-0050 i2c-0 i2c-1 ...0-0050 表示总线 0、地址 0x50(就是 AT24 EEPROM)。
5. 设备树中添加从设备(PC 端流程)
[电脑端] 编辑板级 .dts:
&i2c1 {
status = "okay";
clock-frequency = <400000>;
eeprom: eeprom@50 {
compatible = "atmel,at24";
reg = <0x50>;
pagesize = <8>;
};
};[电脑端] 重新编译并烧录:
./build.sh kernel
./mkfirmware.sh
# 用 RKDevTool 烧录 boot.img开发板端无法直接修改设备树,必须通过烧录。
6. 代码解读
6.1 内核驱动注册
// drivers/i2c/busses/i2c-rk3x.c(简化)
static struct platform_driver rk3x_i2c_driver = {
.probe = rk3x_i2c_probe,
.remove = rk3x_i2c_remove,
.driver = {
.name = "rk3x-i2c",
.of_match_table = rk3x_i2c_match,
},
};
static const struct of_device_id rk3x_i2c_match[] = {
{ .compatible = "rockchip,rk3588-i2c" },
{ /* sentinel */ }
};6.2 用户态调用链
用户空间: i2cget -y 0 0x50 0x00
→ open(/dev/i2c-0)
→ ioctl(I2C_SLAVE, 0x50) /* 设置从地址 */
→ ioctl(I2C_RDWR, ...) /* 读写 */
→ i2cdev_ioctl_rdwr()
→ i2c_transfer() /* I2C core */
→ rk3x_i2c_xfer() /* Rockchip 控制器驱动 */
→ 操作寄存器,发起传输7. 故障排除
7.1 扫描无设备
现象:i2cdetect 整片空白。
按顺序检查:
# 1. 确认适配器存在
ls /dev/i2c-*
# 2. 查看节点状态
cat /proc/device-tree/soc/i2c@fe5a0000/status
# 应输出 "okay",否则设备树未启用
# 3. 内核日志
dmesg | grep -i i2c常见原因:
- SDA/SCL 上拉电阻缺失 / 阻值不对
- 从设备地址设错(可在板子原理图核对外设的 I2C 地址)
- 设备树
status = "disabled"而非"okay"
7.2 传输超时
现象:i2cget / i2cset 提示 timeout。
先看内核日志里有没有报错:
dmesg | grep -i i2c | tail -20再依次排查:
- 时钟频率过高:先降到 100 kHz 测试
- 引脚复用冲突:检查
pinmux-pins - 总线被其他驱动占用
7.3 地址冲突
现象:多设备响应同一地址,造成 ACK 异常。
处理:确认每颗 IC 的 A0/A1/A2 地址引脚设置,i2cdetect 逐个扫描。
