title: i.MX6ULL 嵌入式 Linux:i2c-imx.c Slave 模式移植实战(Linux 4.9.88)
author: xiafan
tags: i.MX6ULL, 嵌入式Linux, I2C驱动, 内核移植, 驱动调试
categories: 嵌入式开发
date: 2026-05-12
在嵌入式 Linux 开发中,I2C 总线是常见的通信接口。近期我在 i.MX6ULL 开发板上做一个有趣的实验:用 GPIO bitbang I2C Master 与 I2C1 硬件控制器 Slave 进行闭环通信。
这个实验的目的是验证和理解 Linux I2C 子系统框架,但在 Linux 4.9.88 内核中,i2c-imx.c 驱动并不支持 slave 模式。本文详细记录了如何从 Linux 5.x 上游移植完整的 slave 支持到 4.9.88 内核的全过程。
Linux 4.9.88 的 drivers/i2c/busses/i2c-imx.c 不支持 slave 模式:
master_xfer 回调reg_slave/unreg_slave 回调struct imx_i2c_struct 中缺少 slave 相关字段而 Linux 5.x 的 i2c-imx.c 已经完整支持 slave 模式,包括:
reg_slave/unreg_slave 回调成功实现 GPIO bitbang Master → I2C1 Slave 通信:
# 写入 0xAB 到寄存器地址 0x00
i2cset -y 4 0x50 0x00 0xAB
# 读回验证
i2cget -y 4 0x50 0x00
0xab ✅cd ~/imx6ull-sdk/Linux-4.9.88
make menuconfig ARCH=arm需要打开的选项:
# 1. I2C GPIO bitbang master driver (作为 Master 端)
Device Drivers →
I2C support →
I2C Hardware Bus support →
[*] GPIO-based bitbanging I2C (CONFIG_I2C_GPIO)
# 2. I2C Slave support (内核级支持)
Device Drivers →
I2C support →
[*] I2C slave support (CONFIG_I2C_SLAVE)
# 3. I2C Slave EEPROM simulator (slave 端测试驱动)
Device Drivers →
I2C support →
I2C slave support →
[*] I2C slave-mode EEPROM simulator (CONFIG_I2C_SLAVE_EEPROM)

# 编译内核和设备树
make ARCH=arm CROSS_COMPILE=~/imx6ull-sdk/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin/arm-linux-gnueabihf- zImage dtbs -j4
# 推送到开发板(通过 adb)
adb push arch/arm/boot/zImage /
adb push arch/arm/boot/dts/imx6ull-14x14.dtb /位置:arch/arm/boot/dts/imx6ull-14x14.dts 根节点下
gpio_i2c: i2c-gpio-0 {
compatible = "i2c-gpio";
gpios = <&gpio4 20 GPIO_ACTIVE_HIGH>, /* SDA = GPIO4_IO20 */
<&gpio4 21 GPIO_ACTIVE_HIGH>; /* SCL = GPIO4_IO21 */
i2c-gpio,delay-us = <100>;
/* 注意:不加 i2c-gpio,sda-open-drain 和 i2c-gpio,scl-open-drain
加了会导致驱动使用 gpio_set_value() 推挽输出,slave 无法 ACK
不加则会使用方向切换模式(INPUT=高阻,OUTPUT LOW=拉低),真正的软件开漏 */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_i2c>;
#address-cells = <1>;
#size-cells = <0>;
};pinctrl_gpio_i2c: gpioi2cgrp {
fsl,pins = <
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x1b0b0 /* SDA, 47K pull-up, push-pull */
MX6UL_PAD_CSI_DATA00__GPIO4_IO21 0x1b0b0 /* SCL, 47K pull-up, push-pull */
>;
};重要:Pad 配置从 0x10b0(keeper,会锁死低电平)改为 0x1b0b0(pull-up),这是调试中的一个关键修复。
&i2c1 {
clock-frequency = <100000>;
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_i2c1>;
status = "okay";
/* Slave 设备节点 */
i2c_slave: i2c-slave@50 {
compatible = "slave-24c02"; /* 使用内核自带的 slave-eeprom 驱动 */
reg = <0x50>;
};
};I2C1 的 pinctrl 配置(硬件 I2C 控制器引脚必须保持开漏):
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0 /* 开漏+47K上拉 */
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0 /* 开漏+47K上拉 */
>;
};确保只有一个 gpio_i2c: i2c-gpio-0 节点,不要重复定义。重复节点会导致编译后的 dtb 行为不确定。
Linux 4.9.88 的 i2c-imx.c 只有 master 模式,而我们需要完整的 slave 支持。Linux 5.x 的驱动已经实现了完整的功能,因此我们需要将相关代码移植到 4.9.88。
组件 | 说明 |
|---|---|
| 新增 |
| 统一 IIF/IAL 清除方式(适配 W0C/W1C) |
| slave 回调封装,追踪 last_slave_event |
| STOP/重入时清理状态 |
| 核心状态机(IAAS→读/写、数据收发、NAK 结束) |
| hrtimer 回调,30μs 轮询 I2SR |
| slave 寄存器初始化 + hrtimer 启动 |
| i2c_algorithm 回调 |
| 重写:spinlock 保护、区分 master/slave 模式 |
| master 传输结束后重入 slave 模式 |
| 初始化/清理 hrtimer 和 spinlock |
#ifdef CONFIG_I2C_SLAVE
/* spinlock 保护的 ISR → slave_handle 处理 IAAS/ICF 状态机 */
/* hrtimer 30μs 轮询兜底,防止中断不触发时丢失事务 */
/* slave_handle 状态机:
*
* I2SR_IAAS=1 (被寻址):
* I2SR_SRW=1 → Master 读 → 配置 MTX → 写 I2DR
* I2SR_SRW=0 → Master 写 → 清除 MTX → 读 I2DR(虚拟)
*
* I2SR_IAAS=0 (数据阶段):
* !MTX+ICF → 收到数据 (WRITE_RECEIVED)
* MTX+RXAK → 收到 ACK (继续发送下一个)
* MTX+!RXAK → 收到 NAK (传输结束)
*/
static irqreturn_t i2c_imx_slave_handle(struct imx_i2c_struct *i2c_imx,
unsigned int status, unsigned int ctl)
{
u8 value = 0;
if (status & I2SR_IAL) { ... }
if (!(status & I2SR_IBB)) { /* STOP */ return IRQ_HANDLED; }
if (!(status & I2SR_ICF)) goto out; /* 传输中,忽略 */
if (status & I2SR_IAAS) { /* 被寻址 */ ... }
else if (!(ctl & I2CR_MTX)) { /* 接收数据 */ ... }
else if (!(status & I2SR_RXAK)) { /* 发送,收到 ACK */ ... }
else { /* 发送,收到 NAK,结束 */ ... }
out:
/* 重新调度 hrtimer 30μs 后轮询 */
hrtimer_try_to_cancel(&i2c_imx->slave_hrtimer);
hrtimer_forward_now(&i2c_imx->slave_hrtimer, ns_to_ktime(30000));
hrtimer_restart(&i2c_imx->slave_hrtimer);
return IRQ_HANDLED;
}
#endifslave_handle 在总线空闲时直接返回 IRQ_HANDLED(不走 out 标签重启 timer),因此 slave_timeout 回调中必须自行重启:
if (i2c_imx->slave) {
hrtimer_forward_now(t, ns_to_ktime(30000));
return HRTIMER_RESTART;
}
return HRTIMER_NORESTART;先写 IEN,usleep_range(50,150),再写 IIEN。不能一步完成,否则 IIEN 位不会被硬件正确采样。
ISR 和 hrtimer 回调都操作相同的寄存器,必须用 spinlock_irqsave 保护。
Bug | 现象 | 修复 | |
|---|---|---|---|
1 | IADR 地址未左移 | slave 监听错误地址 |
|
2 | 缺少 pm_runtime_get_sync | 10ms 后 I2C1 时钟被门控 | reg_slave 开头加 get_sync |
3 | I2CR 配置带 TXAK=1 | slave 自动 NAK 所有字节 | 去掉 |
4 | i2c-gpio,sda-open-drain flag 导致推挽输出 | EEPROM/slave 无法 ACK | 删除该 flag,方向切换模式 |
5 | hrtimer 只跑一次停止 | 第一次 i2cdetect 有 0x50,之后消失 |
|
6 | I2CR 两步使能缺少延时 | 中断从不触发 | 两步骤之间加 |
# 查看 GPIO 电平状态
cat /sys/kernel/debug/gpio | grep gpio-11[5-8]
# 查看 pinmux 配置
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pinmux-pins | grep -E "CSI_HSYNC|CSI_DATA00"
# 查看 pad 电气配置
cat /sys/kernel/debug/pinctrl/20e0000.iomuxc/pinconf-pins | grep -E "pin 120|pin 121"
# 查看 I2C1 寄存器(I2CR, I2SR, IADR)
cat /sys/kernel/debug/regmap/21a0000.i2c/registers 2>/dev/null
# 查看设备树 slave 节点
ls -la /sys/bus/i2c/devices/0-0050/
cat /sys/bus/i2c/devices/0-0050/name
# 查看中断计数
cat /proc/interrupts | grep 21a0
# 查看 I2C1 时钟
cat /sys/kernel/debug/clk/clk_summary | grep i2c1
# 查看 GPC 中断屏蔽状态
# I2C1 IRQ 67, GPC IMR3 bit 3 (67-64=3)# 正常启动日志
i2c-slave-eeprom 0-0050: i2c_slave_register: client slave flag not set.
← 这个警告可以忽略,不影响功能
i2c i2c-0: reg_slave: I2CR=0xc0 I2SR=0x81 IADR=0xa0
← I2CR=0xC0 表示 IEN+IIEN,配置正确
i2c i2c-0: I2C slave at 0x50 (aligned)0x10b0(keeper)会锁死 SDA 低电平,必须改为 0x1b0b0(pull-up)addr << 1(8-bit with R/W),而不是 client->addr(7-bit)reg_slave 回调必须调用 pm_runtime_get_sync,否则 10ms 后时钟被门控本移植方法适用于:
6.3 完整代码获取
由于篇幅限制,完整的 i2c-imx.c 移植后代码无法直接贴在正文。
注:平台限制,不能直接贴链接
获取方式:
1.参考 Linux 主线内核 drivers/i2c/busses/i2c-imx.c(Linux 5.x 版本)
2.按照本文第四节的移植清单,手动移植到 4.9.88 内核
3.如需完整修改后的文件,欢迎在评论区留言
提示:本文已详细列出所有需要修改的函数和代码结构,按照 4.2 节的移植清单操作即可完成移植。
如果本文对你有帮助,欢迎点赞收藏!如有问题,欢迎在评论区讨论。
作者注:本文基于实际项目调试经验撰写,所有 Bug 均已在真实硬件上验证修复。希望这篇笔记能帮助遇到类似问题的开发者少走弯路。
标签
#WorkBuddy #iMX6ULL #嵌入式Linux #I2C驱动 #内核移植 #驱动调试
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。