首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >i.MX6ULL 嵌入式 Linux:i2c-imx.c Slave 模式移植实战(Linux 4.9.88)

i.MX6ULL 嵌入式 Linux:i2c-imx.c Slave 模式移植实战(Linux 4.9.88)

原创
作者头像
用户12479575
修改2026-05-12 18:17:12
修改2026-05-12 18:17:12
1860
举报

title: i.MX6ULL 嵌入式 Linux:i2c-imx.c Slave 模式移植实战(Linux 4.9.88)

author: xiafan

tags: i.MX6ULL, 嵌入式Linux, I2C驱动, 内核移植, 驱动调试

categories: 嵌入式开发

date: 2026-05-12


i.MX6ULL 嵌入式 Linux:i2c-imx.c Slave 模式移植实战(Linux 4.9.88)

前言

在嵌入式 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 内核的全过程。


一、工程背景

1.1 硬件平台

  • 核心板: i.MX6ULL 14x14 EVK(内核 4.9.88)
  • 扩展板:配套扩展板
  • 连接方式:杜邦线将 GPIO4_IO20(SDA)/GPIO4_IO21(SCL) 连接到 I2C1_SDA/I2C1_SCL 排针

1.2 发现问题

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 回调
  • 使用 hrtimer 30μs 轮询弥补硬件中断不可靠的问题
  • 完整的状态机处理(IAAS/ICF/SRW/RXAK)

1.3 最终成果

成功实现 GPIO bitbang Master → I2C1 Slave 通信:

代码语言:bash
复制
# 写入 0xAB 到寄存器地址 0x00
i2cset -y 4 0x50 0x00 0xAB

# 读回验证
i2cget -y 4 0x50 0x00
0xab    ✅

二、环境准备

2.1 内核配置(make menuconfig)

代码语言:bash
复制
cd ~/imx6ull-sdk/Linux-4.9.88
make menuconfig ARCH=arm

需要打开的选项:

代码语言:bash
复制
# 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)
设置gpio模拟i2c为模块编译
设置gpio模拟i2c为模块编译
设置slave模式支持,模块编译eeprom
设置slave模式支持,模块编译eeprom

2.2 内核编译与部署

代码语言:bash
复制
# 编译内核和设备树
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 /

三、设备树修改

3.1 gpio_i2c (bitbang master) 节点

位置arch/arm/boot/dts/imx6ull-14x14.dts 根节点下

代码语言: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>;
};

3.2 pinctrl 引脚配置

代码语言:dts
复制
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),这是调试中的一个关键修复。

3.3 I2C1 控制器节点(作为 Slave)

代码语言:dts
复制
&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 控制器引脚必须保持开漏):

代码语言:dts
复制
pinctrl_i2c1: i2c1grp {
    fsl,pins = <
        MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0  /* 开漏+47K上拉 */
        MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0  /* 开漏+47K上拉 */
    >;
};

3.4 重要提醒:DTS 中不要出现重复节点

确保只有一个 gpio_i2c: i2c-gpio-0 节点,不要重复定义。重复节点会导致编译后的 dtb 行为不确定。


四、i2c-imx.c 移植过程

4.1 为什么需要移植?

Linux 4.9.88 的 i2c-imx.c 只有 master 模式,而我们需要完整的 slave 支持。Linux 5.x 的驱动已经实现了完整的功能,因此我们需要将相关代码移植到 4.9.88。

4.2 移植内容清单

组件

说明

struct imx_i2c_struct

新增 slavelast_slave_eventslave_lockslave_hrtimer

i2c_imx_clear_irq()

统一 IIF/IAL 清除方式(适配 W0C/W1C)

i2c_imx_slave_event()

slave 回调封装,追踪 last_slave_event

i2c_imx_slave_finish_op()

STOP/重入时清理状态

i2c_imx_slave_handle()

核心状态机(IAAS→读/写、数据收发、NAK 结束)

i2c_imx_slave_timeout()

hrtimer 回调,30μs 轮询 I2SR

i2c_imx_slave_init()

slave 寄存器初始化 + hrtimer 启动

reg_slave / unreg_slave

i2c_algorithm 回调

i2c_imx_isr()

重写:spinlock 保护、区分 master/slave 模式

i2c_imx_xfer()

master 传输结束后重入 slave 模式

probe / remove

初始化/清理 hrtimer 和 spinlock

4.3 核心代码结构

代码语言:c
复制
#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;
}
#endif

4.4 移植注意事项

(1)hrtimer 必须永远 HRTIMER_RESTART

slave_handle 在总线空闲时直接返回 IRQ_HANDLED(不走 out 标签重启 timer),因此 slave_timeout 回调中必须自行重启:

代码语言:c
复制
if (i2c_imx->slave) {
     hrtimer_forward_now(t, ns_to_ktime(30000));
     return HRTIMER_RESTART;
}
return HRTIMER_NORESTART;
(2)I2CR 两步使能

先写 IEN,usleep_range(50,150),再写 IIEN。不能一步完成,否则 IIEN 位不会被硬件正确采样。

(3)spinlock 保护

ISR 和 hrtimer 回调都操作相同的寄存器,必须用 spinlock_irqsave 保护。


五、调试过程

5.1 已排查/修复的 Bug 清单

Bug

现象

修复

1

IADR 地址未左移

slave 监听错误地址

client->addr << 1

2

缺少 pm_runtime_get_sync

10ms 后 I2C1 时钟被门控

reg_slave 开头加 get_sync

3

I2CR 配置带 TXAK=1

slave 自动 NAK 所有字节

去掉 | I2CR_TXAK

4

i2c-gpio,sda-open-drain flag 导致推挽输出

EEPROM/slave 无法 ACK

删除该 flag,方向切换模式

5

hrtimer 只跑一次停止

第一次 i2cdetect 有 0x50,之后消失

slave_timeout 始终 HRTIMER_RESTART

6

I2CR 两步使能缺少延时

中断从不触发

两步骤之间加 usleep_range(50,150)

5.2 调试工具与命令

代码语言:bash
复制
# 查看 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)

5.3 关键日志分析

代码语言:txt
复制
# 正常启动日志
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)

六、总结与心得

6.1 关键经验教训

  1. Pad 配置非常重要0x10b0(keeper)会锁死 SDA 低电平,必须改为 0x1b0b0(pull-up)
  2. I2C 地址必须左移 1 位:IADR 寄存器需要 addr << 1(8-bit with R/W),而不是 client->addr(7-bit)
  3. pm_runtime 必须手动管理reg_slave 回调必须调用 pm_runtime_get_sync,否则 10ms 后时钟被门控
  4. hrtimer 是必须的:硬件中断不可靠,30μs 轮询兜底才能保证不丢失事务
  5. I2CR 使能必须分两步:IEN 和 IIEN 之间必须加延时

6.2 适用范围

本移植方法适用于:

  • NXP i.MX6ULL / i.MX6UL / i.MX7D 等系列
  • Linux 内核版本 4.9.x 到 5.x 之间的移植
  • 需要 I2C slave 模式的嵌入式 Linux 项目

6.3 完整代码获取

由于篇幅限制,完整的 i2c-imx.c 移植后代码无法直接贴在正文。

注:平台限制,不能直接贴链接

获取方式:

1.参考 Linux 主线内核 drivers/i2c/busses/i2c-imx.c(Linux 5.x 版本)

2.按照本文第四节的移植清单,手动移植到 4.9.88 内核

3.如需完整修改后的文件,欢迎在评论区留言

提示:本文已详细列出所有需要修改的函数和代码结构,按照 4.2 节的移植清单操作即可完成移植。


参考资料

  • 上游参考代码:drivers/i2c/busses/i2c-imx.c(Linux 5.x 版本) 注:平台限制,不能直接贴链接
  • i.MX6ULL Reference Manual: I2C 章节 (Chapter 34)
  • Linux 内核文档: Documentation/i2c/slave-interface.rst

如果本文对你有帮助,欢迎点赞收藏!如有问题,欢迎在评论区讨论。


作者注:本文基于实际项目调试经验撰写,所有 Bug 均已在真实硬件上验证修复。希望这篇笔记能帮助遇到类似问题的开发者少走弯路。

标签

#WorkBuddy #iMX6ULL #嵌入式Linux #I2C驱动 #内核移植 #驱动调试

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • i.MX6ULL 嵌入式 Linux:i2c-imx.c Slave 模式移植实战(Linux 4.9.88)
    • 前言
    • 一、工程背景
      • 1.1 硬件平台
      • 1.2 发现问题
      • 1.3 最终成果
    • 二、环境准备
      • 2.1 内核配置(make menuconfig)
      • 2.2 内核编译与部署
    • 三、设备树修改
      • 3.1 gpio_i2c (bitbang master) 节点
      • 3.2 pinctrl 引脚配置
      • 3.3 I2C1 控制器节点(作为 Slave)
      • 3.4 重要提醒:DTS 中不要出现重复节点
    • 四、i2c-imx.c 移植过程
      • 4.1 为什么需要移植?
      • 4.2 移植内容清单
      • 4.3 核心代码结构
      • 4.4 移植注意事项
    • 五、调试过程
      • 5.1 已排查/修复的 Bug 清单
      • 5.2 调试工具与命令
      • 5.3 关键日志分析
    • 六、总结与心得
      • 6.1 关键经验教训
      • 6.2 适用范围
    • 参考资料
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档