首页/目录/全部文章

全部文章

八个专题的源码、算法与协议笔记都在这里。

笔记列表

RK3588 kernel-6.1/drivers/i2c/i2c-slave-eeprom.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-slave-eeprom.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-slave-eeprom.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-slave-eeprom.c(约 218 行)

编译关系:

1
obj-$(CONFIG_I2C_SLAVE_EEPROM) += i2c-slave-eeprom.o

依赖链:

1
2
3
CONFIG_I2C_SLAVE_EEPROM  →  依赖 CONFIG_I2C_SLAVE
CONFIG_I2C_SLAVE → 链接 i2c-core-slave.o
主机驱动 → 须实现 algo->reg_slave / unreg_slave

文档保存路径: linuxDoc/drivers/i2c/

定位: I2C 从机模式后端驱动,在软件中模拟 I2C EEPROM(24Cxx 系列) 行为:外部 I2C 主机通过总线读写“EEPROM”,Linux 侧通过 sysfs 二进制文件 读写同一块内存,实现 双端口存储(总线 + 用户空间)。

与 RK3588 的关系:

项目 RK3588 典型情况
CONFIG_I2C_SLAVE / I2C_SLAVE_EEPROM rockchip_linux_defconfig 通常未启用
i2c-rk3x.c reg_slave,即使编译也无法 i2c_slave_register()
产品用途 SoC 作 I2C Master;本驱动用于 开发/测试/特殊从机场景,非 RK3588 量产主路径

2. 功能总览

功能 实现位置 说明
EEPROM 协议模拟 i2c_slave_eeprom_slave_cb() 响应 I2C 从机事件,维护地址指针与数据区
用户空间访问 sysfs slave-eeprom bin_attribute 读写模拟存储
启动预载 firmware-name 属性 从固件文件填充 buffer
多种容量/型号 i2c_slave_eeprom_id[] 24c02 / 24c32 / 24c64 / 24c512 及 ro 变体
从机注册 i2c_slave_register() 依赖 i2c-core-slave.c + 主机 reg_slave

3. 架构与数据流

1
2
3
4
5
6
7
8
9
10
11
12
外部 I2C Master (MCU/另一 SoC)          Linux (本驱动)
│ │
│ START + 从机地址 + 写/读 │
▼ │
┌──────────────┐ i2c_slave_event() ┌────▼─────────────┐
│ 物理 I2C 总线 │ ──────────────────────►│ slave_cb │
└──────────────┘ (主机驱动调用) │ 更新 buffer[] │
▲ └────┬─────────────┘
│ │ 同一 buffer
│ ▼
│ sysfs slave-eeprom
│ (应用/脚本读写)

内核文档:Documentation/i2c/slave-eeprom-backend.rstslave-interface.rst

要点: 本文件 不操作硬件寄存器;仅实现 EEPROM 协议状态机RAM 缓冲区;硬件从机能力由 I2C 控制器驱动 + i2c-core-slave.c 提供。


4. 核心数据结构

4.1 struct eeprom_data

1
2
3
4
5
6
7
8
9
10
struct eeprom_data {
struct bin_attribute bin; /* sysfs: slave-eeprom */
spinlock_t buffer_lock; /* 保护 buffer 与 I2C 并发访问 */
u16 buffer_idx; /* 当前 EEPROM 地址指针 */
u16 address_mask; /* size - 1,地址回绕 */
u8 num_address_bytes; /* 1 或 2(24c02 vs 24c32+) */
u8 idx_write_cnt; /* 写事务中已收地址字节数 */
bool read_only; /* 只读型号 */
u8 buffer[]; /* 柔性数组,模拟 EEPROM 内容 */
};

4.2 驱动私有数据编码(id->driver_data

1
2
3
4
#define I2C_SLAVE_BYTELEN      GENMASK(15, 0)   /* 容量 - 1 编码在低位 */
#define I2C_SLAVE_FLAG_ADDR16 BIT(16) /* 16 位地址 */
#define I2C_SLAVE_FLAG_RO BIT(17) /* 只读 */
#define I2C_SLAVE_DEVICE_MAGIC(_len, _flags) ((_flags) | ((_len) - 1))

probe 中:

  • size = FIELD_GET(I2C_SLAVE_BYTELEN, id->driver_data) + 1
  • num_address_bytes = 2ADDR16,否则 1
  • address_mask = size - 1

5. 支持的模拟型号

i2c_device_id.name 容量(字节) 地址宽度 只读
slave-24c02 256 (2048/8) 8 位
slave-24c02ro 256 8 位
slave-24c32 4096 16 位
slave-24c32ro 4096 16 位
slave-24c64 8192 16 位
slave-24c64ro 8192 16 位
slave-24c512 65536 16 位
slave-24c512ro 65536 16 位

限制(文件头注释): 多数从机 IP 仅支持 一个从地址,故 不支持 需要多个 I2C 地址的大容量 EEPROM 型号拆分。


6. i2c_slave_eeprom_slave_cb() — 协议状态机

由主机控制器驱动在总线活动时调用 i2c_slave_event(),最终进入本回调。

6.1 写事务(外部 Master 写 EEPROM)

事件 行为
I2C_SLAVE_WRITE_REQUESTED idx_write_cnt = 0,准备接收地址
I2C_SLAVE_WRITE_RECEIVED idx_write_cnt < num_address_bytes:将字节拼入 buffer_idx(先低后高移位);否则在 非只读 时写入 buffer[buffer_idx++ & mask]
I2C_SLAVE_STOP idx_write_cnt = 0

地址字节顺序: 第一个地址字节在 idx_write_cnt==0 时清零 buffer_idx,随后 buffer_idx = *val | (buffer_idx << 8),与常见 24Cxx 大端地址 行为一致。

FIXME(源码注释): 16 位地址只收到 8 位时,当前仍将 8 位当作有效地址(与 ST M24C64 行为有关,其它芯片待验证)。

6.2 读事务(外部 Master 读 EEPROM)

事件 行为
I2C_SLAVE_READ_REQUESTED buffer[buffer_idx & mask] 取字节到 *val递增 buffer_idx(因该字节可能被 NACK)
I2C_SLAVE_READ_PROCESSED 上一字节已发出,buffer_idx++,再取下一字节到 *val
I2C_SLAVE_WRITE_REQUESTED / STOP 重置 idx_write_cnt

遵循 Documentation/i2c/slave-interface.rst 对 READ_REQUESTED / READ_PROCESSED 的语义。

6.3 并发

buffer 访问使用 spin_lock(&buffer_lock)(I2C 回调可能在中断上下文);sysfs 读写使用 spin_lock_irqsave


7. Sysfs 接口:slave-eeprom

路径:

1
/sys/bus/i2c/devices/<client>/slave-eeprom
属性 说明
文件名 slave-eeprom
权限 S_IRUSR | S_IWUSR(0600,属主可读写)
大小 bin.size = EEPROM 容量
read i2c_slave_eeprom_bin_readmemcpybuffer[off..]
write i2c_slave_eeprom_bin_write — 写入 buffer(只读型号仍可通过 sysfs 写,I2C 侧写被忽略)

注意: 文档说明 Linux 不支持 对 bin sysfs 的 poll;外部 Master 修改内容后,用户空间 不会自动得到通知


8. Probe / Remove 流程

8.1 i2c_slave_eeprom_probe()

1
2
3
4
5
6
7
8
1. devm_kzalloc(sizeof(eeprom_data) + size)
2. 根据 id_table 设置 num_address_bytes / address_mask / read_only
3. i2c_slave_init_eeprom_data()
- 若有 DT/ACPI 属性 firmware-name → request_firmware_into_buf()
- 否则 memset(buffer, 0xff, size) // 空 EEPROM 典型为全 1
4. sysfs_create_bin_file(slave-eeprom)
5. i2c_slave_register(client, i2c_slave_eeprom_slave_cb)
- 失败则删除 sysfs

8.2 i2c_slave_eeprom_remove()

1
2
i2c_slave_unregister(client)
sysfs_remove_bin_file()

8.3 模块注册

1
module_i2c_driver(i2c_slave_eeprom_driver);

标准 i2c_driver,通过 id_table 字符串 匹配(无 of_match_table),通常由 sysfs new_device 实例化,而非 DT compatible


9. 实例化方式(用户空间)

9.1 通过 adapter sysfs new_device

地址需加 0x1000 偏移(与 I2C_OWN_SLAVE_ADDRESS / 从机实例化约定一致,见 slave-interface.rst):

1
2
3
4
5
# 24c02,7 位地址 0x64,挂在 i2c-1
echo slave-24c02 0x1064 > /sys/bus/i2c/devices/i2c-1/new_device

# 24c512 只读,地址 0x42
echo slave-24c512ro 0x1042 > /sys/bus/i2c/devices/i2c-1/new_device

9.2 启动预载内容(DT/ACPI)

设备属性 firmware-name 指向固件文件,probe 时 request_firmware_into_buf() 载入 buffer

示例(概念性 DT,实际需配合支持从机的主机与 new_device 或专用绑定):

1
2
3
4
eeprom_slave: slave-eeprom@64 {
/* 通常仍通过 new_device 按 id 名实例化 */
firmware-name = "eeprom_init.bin";
};

10. 与 i2c-core-slave.c 的关系

步骤 模块
i2c_slave_register() i2c-core-slave.cadap->algo->reg_slave(client)
总线事件 主机驱动 ISR → i2c_slave_event()i2c_slave_eeprom_slave_cb
i2c_slave_unregister() remove 时对称注销

reg_slave 不存在(rk3x),i2c_slave_register() 返回 -EOPNOTSUPP,本驱动 probe 失败。

详见:rk3588-kernel-6.1-i2c-core-slave.c功能详细分析_zh.md


11. RK3588 平台说明

11.1 默认内核

  • CONFIG_I2C_SLAVECONFIG_I2C_SLAVE_EEPROMrockchip_linux_defconfig通常未配置 → 驱动 默认不编译
  • i2c-rk3x 仅 Master 模式,无法实现 本驱动所需的从机硬件路径。

11.2 若要在 RK3588 上实验

需同时满足:

  1. 确认 I2C IP 是否支持 Slave(查阅 TRM);
  2. i2c-rk3x.c(或另一控制器)实现 reg_slave / unreg_slavei2c_slave_event 调用链;
  3. CONFIG_I2C_SLAVE=yCONFIG_I2C_SLAVE_EEPROM=m
  4. 通过 new_device 在目标总线上创建 slave-24c02 等;
  5. 外部设备或另一主控作为 I2C Master 访问。

量产 RK3588 产品 一般 不使用 本驱动;常见为 SoC Master 访问真实 EEPROM/PMIC/传感器。

11.3 与真实 EEPROM 驱动对比

维度 i2c-slave-eeprom(本驱动) 普通 at24/eeprom
SoC 角色 I2C Slave(被访问) I2C Master(访问外设)
存储 内核 RAM 模拟 物理 EEPROM 芯片
典型用途 测试、协处理器仿真 板级配置/数据存储

12. 调试与验证

1
2
3
4
5
6
7
8
9
10
# 确认模块
lsmod | grep i2c_slave
zcat /proc/config.gz | grep I2C_SLAVE # 若可用

# 创建模拟 EEPROM 后
ls /sys/bus/i2c/devices/*/
hexdump -C /sys/bus/i2c/devices/1-0064/slave-eeprom | head

# 删除实例
echo 0x1064 > /sys/bus/i2c/devices/i2c-1/delete_device

配合外部 Master 或另一开发板用 i2cget/i2cset 访问同一从地址,对比 sysfs 内容是否一致。


13. 源码结构速查(约 218 行)

1
2
3
4
5
6
7
L29-38   struct eeprom_data
L45-91 i2c_slave_eeprom_slave_cb()
L93-121 sysfs bin read/write
L123-141 i2c_slave_init_eeprom_data()
L143-182 probe
L184-190 remove
L192-213 id_table + i2c_driver + module_i2c_driver

14. 结论

i2c-slave-eeprom.cI2C 从机子系统的一个后端示例/实用模块

  1. 模拟 24C02~24C512 EEPROM 的地址与读写时序;
  2. 通过 i2c_slave_register 挂接到支持从机模式的 I2C 控制器;
  3. 通过 slave-eeprom sysfs 向用户空间暴露同一块存储;
  4. 支持 只读型号firmware-name 预载
  5. 依赖 CONFIG_I2C_SLAVE 与主机 reg_slave,与 RK3588 默认 rk3x Master-only 栈正交

用于理解 Linux 作 I2C 从设备 时的软件模型;RK3588 常规 bring-up 可跳过,仅在从机/双主测试场景需要关注。


15. 推荐阅读顺序

  1. i2c-slave-eeprom.cslave_cbprobe
  2. Documentation/i2c/slave-interface.rst — 从机事件语义
  3. Documentation/i2c/slave-eeprom-backend.rst — 用户操作说明
  4. i2c-core-slave.ci2c_slave_register
  5. i2c-slave-testunit.c — 另一从机后端(测试用)

16. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-slave-eeprom.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-slave-testunit.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-slave-testunit.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-slave-testunit.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-slave-testunit.c(约 193 行)

编译关系:

1
obj-$(CONFIG_I2C_SLAVE_TESTUNIT) += i2c-slave-testunit.o

依赖:

1
2
CONFIG_I2C_SLAVE_TESTUNIT  →  依赖 CONFIG_I2C_SLAVE
CONFIG_I2C_SLAVE → i2c-core-slave.o + 主机 reg_slave/unreg_slave

文档保存路径: linuxDoc/drivers/i2c/

定位: I2C 从机模式测试单元(testunit)后端,供 I2C 主机控制器驱动开发与回归测试 使用。外部测试主机通过 写 4 字节寄存器 触发用例;从机在 workqueue 中可再作为 Master 发起 i2c_transfer(多主、Host Notify 等场景)。非生产用途,ABI 不保证稳定。

与 RK3588 的关系:

项目 RK3588 典型情况
CONFIG_I2C_SLAVE / TESTUNIT defconfig 通常未启用
i2c-rk3x 无从机 + 无 master/slave 切换 → 本驱动 无法在 rk3x 上正常工作
用途 主要在 其它支持 I2C Slave 的控制器 上验证主机驱动;RK3588 文档供架构对照

2. 功能总览

模块 符号 作用
寄存器协议 enum testunit_regs CMD / DATAL / DATAH / DELAY
测试命令 enum testunit_cmds READ_BYTES、HOST_NOTIFY、SMBUS_BLOCK_PROC_CALL
从机回调 i2c_slave_testunit_slave_cb() 解析主机写入、处理读版本号 / Block Proc Call
延迟执行 i2c_slave_testunit_work() STOP 后按 DELAY 在 workqueue 执行测试
注册 i2c_slave_register() 挂接从机事件
实例化 slave-testunit 经 sysfs new_device

3. 设计目的与使用场景

内核文档 Documentation/i2c/slave-testunit-backend.rst 说明:

  • 难以获得的远程从设备能力 提供可编程替身(多主机仲裁、SMBus Host Notify、Block Process Call 等);
  • 部分测试要求从机控制器能在 Slave ↔ Master 间切换(本驱动在 work 里调用 i2c_transfer);
  • 禁止用于量产镜像;带版本号 TU_CUR_VERSION = 0x01,读操作返回该版本。

4. 架构与数据流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
测试用 I2C Master (PC/另一 SoC/自测脚本)
│ 写 4 字节: CMD, DATAL, DATAH, DELAY

┌──────────────────┐
│ 被测 SoC 控制器 │ Slave 模式接收
│ + testunit 驱动 │ i2c_slave_testunit_slave_cb()
└────────┬─────────┘
│ STOP 且 reg_idx==4

queue_delayed_work(DELAY * 10ms)


i2c_slave_testunit_work()
│ 可能 i2c_transfer() 作为 Master

总线上其它设备 / Host Notify 地址 0x08

i2c-slave-eeprom.c 对比:

维度 slave-eeprom slave-testunit
目的 模拟 EEPROM 存储 触发主机驱动测试用例
用户接口 sysfs slave-eeprom 仅 I2C 寄存器协议
Master 行为 work 中可 i2c_transfer
读默认 EEPROM 数据 版本号 0x01

5. 寄存器与命令协议

5.1 四个写寄存器(须完整写入 4 字节触发,Block Proc Call 除外)

偏移 名称 含义
TU_REG_CMD (0) CMD 测试命令编号
TU_REG_DATAL (1) DATAL 参数低字节
TU_REG_DATAH (2) DATAH 参数高字节
TU_REG_DELAY (3) DELAY 延迟 = DELAY × 10ms 后执行

实例化示例:

1
echo slave-testunit 0x1030 > /sys/bus/i2c/devices/i2c-0/new_device

0x1030 = 0x1000 + 7 位地址 0x30,与从机 new_device 约定一致。)

i2c-tools 通用写法:

1
i2cset -y <bus> <addr> <CMD> <DATAL> <DATAH> <DELAY> i

5.2 命令列表

CMD 名称 需要 Master 模式 DATAL / DATAH 行为
0x01 TU_CMD_READ_BYTES DATAL=从地址(7bit),DATAH=读长度 work 中对 adapter 发起 读传输,测多主机
0x02 TU_CMD_HOST_NOTIFY 状态字低/高字节(Linux 侧可忽略) 向地址 0x08 写 3 字节 SMBus Host Notify
0x03 TU_CMD_SMBUS_BLOCK_PROC_CALL 否(从机读侧模拟) DATAL=1,DATAH=返回字节数 从机在后续 读事务 中模拟 Block Process Call 响应

TU_CMD >= TU_NUM_CMDS 时从机回调返回 -EINVAL(主机应 NACK)。

保留: CMD 0x00 为 NOOP(未实现)。


6. i2c_slave_testunit_slave_cb() 详解

6.1 I2C_SLAVE_WRITE_REQUESTED

  • TU_FLAG_IN_PROCESS 已置位 → -EBUSY(上一命令未结束);
  • 清零 regs[]reg_idx = 0

6.2 I2C_SLAVE_WRITE_RECEIVED

  • 忙时返回 -EBUSY
  • 按序写入 regs[reg_idx]reg_idx++
  • 写满 4 寄存器后校验 regs[TU_REG_CMD] < TU_NUM_CMDS,否则 -EINVAL
  • 超出 4 字节 → -EMSGSIZE

6.3 I2C_SLAVE_STOP

  • reg_idx == TU_NUM_REGS(4)
    • TU_FLAG_IN_PROCESS
    • queue_delayed_work(system_long_wq, delay = 10ms * regs[TU_REG_DELAY])
  • reg_idx = 0(避免后续读事务重复排队;不清 regs,work 仍需要)。

6.4 读路径

场景 *val
普通读 TU_CUR_VERSION (0x01)
Block Proc Call 模拟 (reg_idx==3 且 CMD=0x03 且 DATAL=1) 递减序列:先返回 DATAH,每次 READ_PROCESSEDDATAH--

Block Proc Call 时主机随后发起读;从机按 SMBus 语义在首字节放 长度,后续字节为 length-1 … 0(由 DATAH 递减实现,见内核文档 i2ctransfer 示例)。


7. i2c_slave_testunit_work() — 测试执行

在延迟结束后运行,根据 TU_REG_CMD 构造 i2c_msgi2c_transfer(tu->client->adapter, &msg, 1)

7.1 TU_CMD_READ_BYTES

1
2
3
msg.addr = tu->regs[TU_REG_DATAL];   /* 7 位从地址 */
msg.flags = I2C_M_RD;
msg.len = tu->regs[TU_REG_DATAH];

用于验证:被测主机驱动testunit 发起的读 同时访问总线时的仲裁/忙处理。

示例(文档):

1
i2cset -y 0 0x30 0x01 0x50 0x80 0x05 i   # 50ms 后从 0x50 读 128 字节

7.2 TU_CMD_HOST_NOTIFY

1
2
3
4
5
msg.addr = 0x08;
msg.len = 3;
msgbuf[0] = tu->client->addr;
msgbuf[1] = DATAL;
msgbuf[2] = DATAH;

SMBus Host Notify 地址 发送通知,测试主机侧 Host Notify 处理路径(与 i2c-core-base.c / i2c-smbus.c 中 Host Notify 相关)。

7.3 完成与错误

  • i2c_transfer 返回 0 条消息时转为 -EIO
  • 失败 dev_err 打印 CMD 与 errno;
  • clear_bit(TU_FLAG_IN_PROCESS),允许下一条命令。

8. Probe / Remove

8.1 i2c_slave_testunit_probe()

1
2
3
4
devm_kzalloc(testunit_data)
tu->client = client
INIT_DELAYED_WORK(&tu->worker, i2c_slave_testunit_work)
i2c_slave_register(client, i2c_slave_testunit_slave_cb)

使用 probe_new(无 id 参数,匹配靠 id_table 在绑定前完成)。

8.2 i2c_slave_testunit_remove()

1
2
cancel_delayed_work_sync(&tu->worker)
i2c_slave_unregister(client)

8.3 模块

  • 单一型号: { "slave-testunit", 0 }
  • of_match_table — 典型通过 new_device 创建,非 DT compatible

9. 与 I2C 子系统的关系

组件 关系
i2c-core-slave.c i2c_slave_register / i2c_slave_event
i2c-core-base.c work 中 i2c_transfer、Host Notify 消费端
i2c-core-smbus.c Block Proc Call 常经 SMBus 模拟;需 I2C_M_RECV_LEN
主机驱动 必须 reg_slave;READ_BYTES/HOST_NOTIFY 还需 同一 adapter 可作 Master

10. RK3588 平台说明

10.1 默认不可用原因

  1. CONFIG_I2C_SLAVE_TESTUNIT 未开 → 驱动不编译;
  2. i2c-rk3x 仅实现 Master(master_xfer), reg_slave
  3. 即使强行加载,i2c_slave_register() 返回 -EOPNOTSUPP
  4. 测试用例依赖的 Slave 期间再 Master 发送 在 rk3x 上 无硬件/驱动支持

10.2 若用于验证 RK3588 主机驱动

需在 支持 I2C Slave(及 preferably 主从切换) 的平台上运行 testunit,或使用 USB-I2C 适配器 + 另一套 slave 控制器 作为对端;不能 把 testunit 挂在 rk3x 控制器上作为完整方案。

10.3 与 slave-eeprom 选型

需求 选用
模拟 EEPROM、用户空间读写镜像 i2c-slave-eeprom.c
主机驱动回归、多主/Notify/Block Proc Call i2c-slave-testunit.c(本文)

11. 测试示例摘要(来自内核文档)

多主读字节:

1
i2cset -y 0 0x30 0x01 0x50 0x80 0x05 i

Host Notify:

1
i2cset -y 0 0x30 0x02 0x42 0x64 0x01 i

Block Process Call(i2ctransfer ≥ 4.2):

1
2
i2ctransfer -y 0 w3@0x30 0x03 0x01 0x10 r?
# 期望: 0x10 0x0f ... 0x00

12. 并发与状态标志

标志 含义
TU_FLAG_IN_PROCESS 命令已排队或 work 执行中;新写/新写请求返回 -EBUSY

DELAY 期间 主机写入会被拒绝,需等待 work 完成。


13. 源码结构速查(约 193 行)

1
2
3
4
5
6
L17-44   版本、命令/寄存器/标志、testunit_data
L46-86 i2c_slave_testunit_work()
L88-149 i2c_slave_testunit_slave_cb()
L151-164 probe (probe_new)
L166-172 remove
L174-188 id_table + i2c_driver + module_i2c_driver

14. 结论

i2c-slave-testunit.cI2C 从机框架的测试专用后端

  1. 通过 4 字节寄存器协议 远程触发 READ_BYTES / HOST_NOTIFY / SMBUS_BLOCK_PROC_CALL
  2. workqueue 中可切换为 Master 发送,用于 多主机与 SMBus 特性 验证;
  3. 从机读路径提供 版本号Block Proc Call 响应模拟;
  4. 依赖 CONFIG_I2C_SLAVE 与主机 reg_slave不保证稳定 ABI
  5. RK3588 rk3x 默认栈不支持,属开发与它平台主机驱动测试工具。

rk3588-kernel-6.1-i2c-slave-eeprom.c功能详细分析_zh.md 同属从机后端,但面向 总线主机驱动 QA 而非存储仿真。


15. 推荐阅读顺序

  1. Documentation/i2c/slave-testunit-backend.rst — 命令与示例
  2. i2c-slave-testunit.cslave_cbwork
  3. Documentation/i2c/slave-interface.rst — 从机事件语义
  4. i2c-core-slave.c — 注册与事件投递
  5. i2c-core-smbus.c — Block 读与 I2C_M_RECV_LEN

16. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-slave-testunit.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-smbus.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-smbus.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-smbus.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-smbus.c(约 461 行)

编译关系: 独立内核模块,由 CONFIG_I2C_SMBUS 控制:

1
obj-$(CONFIG_I2C_SMBUS) += i2c-smbus.o

i2c-core-smbus.c 的区别(易混淆):

文件 编入 职责
i2c-core-smbus.c 始终进 i2c-core.ko SMBus 协议 APIi2c_smbus_xfer、PEC、I2C 模拟)
i2c-smbus.c(本文) 可选 i2c-smbus.ko SMBus 扩展协议:Alert、Host Notify 从机接收、DMI SPD 自动枚举

文档保存路径: linuxDoc/drivers/i2c/

定位: 实现 SMBus 规范中 Alert Response Address (ARA)Host Notify 的 Linux 侧基础设施,并(在 x86 + DMI 场景)自动创建 SPD/EEPROM client。


2. 功能总览

功能块 条件编译 主要符号 作用
SMBus Alert 始终(模块主体) smbalert_driversmbus_alert() SMBALERT# 中断 → 读 ARA → 通知设备驱动 alert()
Alert 入口(总线驱动) 同上 i2c_handle_smbus_alert() 由主机驱动 ISR 调度 work
Host Notify 从机 CONFIG_I2C_SLAVE i2c_new_slave_host_notify_device() 从地址 0x08 收 Host Notify → 触发虚拟 IRQ
DMI SPD 自动实例化 CONFIG_DMI i2c_register_spd() 根据 BIOS DMI 在 0x50+n 探测内存 SPD

3. 架构:SMBus Alert

3.1 协议背景

  • 从设备将 SMBALERT# 线拉低表示有事件;
  • 主机通过 Alert Response Address (ARA) 0x0c 执行 Receive Byte,得到 待响应设备的 7 位地址(及 1 位标志);
  • 多个设备按地址 从低到高 依次响应,直至 SMBALERT# 释放。

3.2 Linux 组件关系

1
2
3
4
5
6
7
8
9
10
11
12
13
adapter 注册 (i2c-core-base.c)
→ i2c_setup_smbus_alert(adap) [i2c-core-smbus.c, 需 CONFIG_I2C_SMBUS]
→ i2c_new_smbus_alert_device() 创建 client "smbus_alert" @0x0c
→ smbalert_probe() [i2c-smbus.c]
→ 注册 IRQ "smbus_alert" 或仅保存 ARA client

SMBALERT# 触发:
方式 A: IRQ → smbus_alert() → work → 循环 i2c_smbus_read_byte(ara)
方式 B: 总线驱动 ISR → i2c_handle_smbus_alert(ara) → schedule_work → 同上

对每个 alert 地址:
device_for_each_child(adapter, smbus_do_alert)
→ i2c_driver->alert(client, type, data)

3.3 核心数据结构

1
2
3
4
5
6
7
8
9
10
struct i2c_smbus_alert {
struct work_struct alert;
struct i2c_client *ara; /* ARA @ 0x0c */
};

struct alert_data {
unsigned short addr; /* 报警设备 7 位地址 << 1 解码后 */
enum i2c_alert_protocol type; /* I2C_PROTOCOL_SMBUS_ALERT */
unsigned int data; /* status 低 1 位 */
};

3.4 smbus_alert() 中断/工作处理

1
2
3
4
status = i2c_smbus_read_byte(ara);   /* 从 0x0c 读一字节 */
data.addr = status >> 1;
data.data = status & 1;
device_for_each_child(&ara->adapter->dev, &data, smbus_do_alert);
  • 循环直到 read_byte 失败(无更多待处理设备);
  • 重复地址 且未处理:再 smbus_do_alert_force 广播所有带 alert 的驱动后 break,防止死循环。

3.5 smbus_do_alert() / smbus_do_alert_force()

函数 行为
smbus_do_alert 匹配 client->addr == data.addr,调用 driver->alert(),返回 -EBUSY 停止迭代
smbus_do_alert_force 对总线上 所有 已绑定且带 alert 的驱动调用(不比较地址)

驱动需在 struct i2c_driver 中实现:

1
void (*alert)(struct i2c_client *client, enum i2c_alert_protocol type, unsigned int data);

示例:sbs-battery.clm90.cstts751.c 等。

3.6 smbalert_probe() — ARA 设备

  • client 名称: smbus_alert,地址 0x0c(由 i2c_new_smbus_alert_device 创建);
  • IRQ 来源:
    • platform_datai2c_smbus_alert_setup.irq;或
    • 父设备 fwnode:interrupt-names = "smbus_alert"
  • devm_request_threaded_irqthread_fn = smbus_alertIRQF_SHARED | IRQF_ONESHOT
  • 无 IRQ 时仅创建 ARA client,由 总线驱动轮询 并调用 i2c_handle_smbus_alert()

3.7 i2c_handle_smbus_alert()

1
2
3
4
int i2c_handle_smbus_alert(struct i2c_client *ara)
{
return schedule_work(&alert->alert); /* 最终进入 smbus_alert(0, alert) */
}

导出符号,供 不能在 IRQ 里睡眠 的主机驱动使用(如 PCI SMBus 控制器)。

3.8 与 i2c-core-smbus.c 的衔接

1
2
3
4
5
6
7
8
9
10
11
12
// i2c-core-smbus.c
struct i2c_client *i2c_new_smbus_alert_device(struct i2c_adapter *adapter, ...)
{
I2C_BOARD_INFO("smbus_alert", 0x0c);
return i2c_new_client_device(adapter, &ara_board_info);
}

int i2c_setup_smbus_alert(struct i2c_adapter *adapter)
{
// 父设备 interrupt-names 含 "smbus_alert" 时创建 ARA
return i2c_new_smbus_alert_device(adapter, NULL);
}

i2c_register_adapter() 中调用 i2c_setup_smbus_alert()(见 i2c-core-base.c 文档)。


4. 架构:SMBus Host Notify(CONFIG_I2C_SLAVE

4.1 协议背景

  • 从设备向 固定地址 0x08 写入 3 字节(含通知方地址等),通知主机有事件;
  • Linux 主机侧常映射为 per-client 虚拟 IRQi2c-core-base.chost_notify_domain)。

4.2 本文件角色

当控制器 支持 Slave 模式 时,可创建 从机 client 监听 0x08

1
struct i2c_client *i2c_new_slave_host_notify_device(struct i2c_adapter *adapter)
步骤 说明
创建 client I2C_BOARD_INFO("smbus_host_notify", 0x08)I2C_CLIENT_SLAVE
从机回调 i2c_slave_host_notify_cb
STOP 且收到 3 字节 i2c_handle_smbus_host_notify(adapter, status->addr)

i2c_handle_smbus_host_notify()i2c-core-base.c 中实现:根据地址在 host_notify_domain 查找 IRQ 并 generic_handle_irq_safe()

4.3 从机回调逻辑

1
2
3
I2C_SLAVE_WRITE_RECEIVED: 记录首字节为 addr,index++
I2C_SLAVE_STOP: 若 index == 3 → i2c_handle_smbus_host_notify()
I2C_SLAVE_READ_*: 返回 0xff(占位)

注释说明:当前 未解析 Host Notify 的完整数据参数,仅用首字节作地址。

4.4 释放

1
2
i2c_free_slave_host_notify_device(client);
// i2c_slave_unregister + kfree(platform_data) + i2c_unregister_device

4.5 RK3588

  • CONFIG_I2C_SLAVE + i2c-rk3x 实现 reg_slave(当前 );
  • Host Notify 从机路径在 RK3588 上 不可用
  • 若仅作 Master 的 client 使用 I2C_CLIENT_HOST_NOTIFY,则由 i2c-core-base.chost_notify_domain + 总线驱动 i2c_handle_smbus_host_notify(Master 侧收到通知)处理,不依赖 本文件从机段。

5. DMI SPD 自动实例化(CONFIG_DMI

5.1 目的

PC/服务器 上根据 DMI 内存表 自动在 I2C 总线上创建 SPDee1004 client(DDR2/3/4),便于 ee1004/spd5118 等驱动读取内存条信息。

5.2 i2c_register_spd(struct i2c_adapter *adap)

限制(源码):

  • 1~4 个内存槽;
  • 所有已插槽 内存类型一致
  • 支持类型:DDR2/DDR3/LPDDR2/3 → "spd";DDR4/LPDDR4 → "ee1004"
  • 0x50 + slot_indexi2c_new_scanned_device 探测。

5.3 RK3588

  • 无 DMI(嵌入式 ARM)→ 该函数通常为 空操作 或不被调用;
  • 与 RK3588 产品 无关

6. 模块与驱动注册

1
2
3
4
5
6
7
static struct i2c_driver smbalert_driver = {
.driver.name = "smbus_alert",
.probe = smbalert_probe,
.remove = smbalert_remove,
.id_table = { { "smbus_alert", 0 }, {} },
};
module_i2c_driver(smbalert_driver);
  • ARA clienti2c_new_smbus_alert_device() 创建后,由 smbalert 驱动 probe 绑定;
  • 模块描述:"SMBus protocol extensions support"

7. 配置与 RK3588 构建

7.1 Kconfig

1
2
3
config I2C_SMBUS
tristate "SMBus-specific protocols"
# 当 I2C_HELPER_AUTO=y 时该选项在 menuconfig 中隐藏
  • 部分 x86 SMBus 主机驱动 select I2C_SMBUS(如 i2c-i801);
  • I2C_RK3X 不 select I2C_SMBUS

7.2 Rockchip defconfig 典型情况

配置 常见值
CONFIG_I2C_RK3X y
CONFIG_I2C_SMBUS 未显式配置 且 rk3x 不 select → i2c-smbus.ko 可能未构建
i2c_setup_smbus_alert() CONFIG_I2C_SMBUS 时为 inline 空函数,直接 return 0

因此 RK3588 产品 通常没有 SMBALERT# 基础设施,除非手动开启 CONFIG_I2C_SMBUS=y/m

7.3 与 PMIC/传感器的关系

板载 RK806、传感器 等使用 i2c_smbus_read_byte_data() 等 API,这些在 i2c-core-smbus.c 中,不依赖 i2c-smbus.ko


8. 端到端时序:Alert 示例

1
2
3
4
5
6
1. 温度芯片拉低 SMBALERT#
2. 控制器 IRQ → smbus_alert() 或 i2c_handle_smbus_alert(ara)
3. work: i2c_smbus_read_byte(ara@0x0c) → 0x4d (地址 0x26, flag=1)
4. smbus_do_alert → lm90_driver.alert(client@0x26, SMBUS_ALERT, 1)
5. 驱动读取状态寄存器、可能清 alert
6. 重复 read_byte(ara) 直至 < 0

9. 导出符号一览

符号 条件 说明
i2c_handle_smbus_alert 模块 调度 Alert 处理 work
i2c_new_slave_host_notify_device CONFIG_I2C_SLAVE 创建 Host Notify 从机
i2c_free_slave_host_notify_device CONFIG_I2C_SLAVE 释放
i2c_register_spd CONFIG_DMI DMI SPD 探测

i2c_new_smbus_alert_devicei2c-core-smbus.c 中导出。)


10. 调试建议

1
2
3
4
5
6
7
8
9
10
11
12
# 是否编译进内核
zcat /proc/config.gz 2>/dev/null | grep I2C_SMBUS
ls /sys/bus/i2c/drivers/smbus_alert/

# ARA 设备是否存在
ls /sys/bus/i2c/devices/*-000c/ # 地址 0x0c

# 某驱动是否实现 alert
grep -r '\.alert\s*=' drivers/hwmon/ drivers/power/

# 日志
dmesg | grep -i 'SMBALERT\|smbus_alert'

11. 源码结构速查(约 461 行)

1
2
3
4
5
6
7
8
L20-92    smbus_do_alert / smbus_do_alert_force
L98-152 smbus_alert (IRQ/thread)
L154-162 smbalert_work
L165-211 smbalert_probe / remove
L240-246 i2c_handle_smbus_alert
L248 module_i2c_driver
L250-355 Host Notify slave [CONFIG_I2C_SLAVE]
L365-455 i2c_register_spd [CONFIG_DMI]

12. 结论

i2c-smbus.c 提供 SMBus AlertHost Notify(从机侧) 的 Linux 实现,以及 x86 DMI SPD 便利功能:

  1. Alert: ARA 0x0c + IRQ/work + i2c_driver.alert() 回调链;
  2. Host Notify: 可选从机监听 0x08,转交 i2c_handle_smbus_host_notify()(core);
  3. SPD: 仅 DMI 平台有意义。

RK3588: 日常 I2C 访问依赖 i2c-core-smbus.ci2c-smbus.ko 通常未启用,板级也少见 SMBALERT#。分析嵌入式 I2C 问题时,应区分 通用 SMBus 读写(core)与 Alert/Notify 扩展(本模块)。


13. 推荐阅读顺序

  1. i2c-smbus.csmbus_alertsmbalert_probe
  2. rk3588-kernel-6.1-i2c-core-smbus.c功能详细分析_zh.mdi2c_smbus_xferi2c_new_smbus_alert_device
  3. rk3588-kernel-6.1-i2c-core-base.c功能详细分析_zh.mdhost_notify_domaini2c_setup_smbus_alert 调用点
  4. SMBus 规范 Alert / Host Notify 章节

14. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-smbus.c 分析。

RK3588 kernel-6.1/drivers/i2c/i2c-stub.c 功能详细分析

RK3588 kernel-6.1/drivers/i2c/i2c-stub.c 功能详细分析

1. 文档范围与说明

路径说明: 用户表述为「i2c-stub.c 目录」,实际为 单个源文件

  • rk3588/kernel-6.1/drivers/i2c/i2c-stub.c(约 414 行)

编译关系:

1
obj-$(CONFIG_I2C_STUB) += i2c-stub.o
1
2
3
config I2C_STUB
tristate "I2C/SMBus Test Stub"
depends on m # 仅能编译为模块,不能 built-in

文档保存路径: linuxDoc/drivers/i2c/

定位: 纯软件 I2C/SMBus 芯片模拟器(Test Stub)。注册一条 虚拟 i2c_adapter,在模块参数指定的 最多 10 个 7 位地址 上响应 SMBus 命令,数据保存在 内核内存数组 中,不连接任何硬件。用于 客户端驱动开发、回归测试、无实物传感器时的行为验证

与 RK3588 的关系:

项目 说明
产品内核 rockchip_linux_defconfig 通常不包含 CONFIG_I2C_STUB
硬件 ;与 i2c-rk3x 等真实总线 独立
用途 开发机/CI 上 modprobe i2c-stub + 加载被测驱动;非量产路径

2. 功能总览

功能 实现 说明
虚拟总线 全局 stub_adapter 单条 adapter,i2c_add_adapter() 注册
SMBus 仿真 stub_xfer() 实现 algo->smbus_xfer,无 master_xfer
多芯片地址 chip_addr[] 模块参数 最多 10 个地址 (0x03–0x77)
寄存器模型 stub_chip.words[256] 每地址 256 个 16 位“寄存器”
指针自增 chip->pointer Byte/Byte Data 类操作模拟 EEPROM 连续读
存储体切换 bank_* 模块参数 可选 bank 寄存器与扩展地址空间
SMBus Block smbus_block_data 链表 functionality 开启 I2C_FUNC_SMBUS_BLOCK_DATA
能力掩码 functionality 模块参数 可裁剪支持的 SMBus 命令类型

3. 架构示意

1
2
3
4
5
6
7
8
9
10
被测 i2c_driver (如 hwmon、PMIC 测试)
│ i2c_smbus_read_byte_data() 等

i2c-core-smbus.c → i2c_smbus_xfer()

stub_adapter (虚拟)
│ algo.smbus_xfer = stub_xfer

内存: stub_chips[i].words[] / bank_words[] / smbus_blocks
(无 SDA/SCL,无 rk3x)

特点:

  • 仅实现 smbus_xfer master_xfer → 用户态 I2C_RDWRi2c-dev不可用
  • class = I2C_CLASS_HWMON | I2C_CLASS_SPD,便于遗留 class 探测匹配;
  • 每次操作 dev_dbg 打日志(文档称可能刷屏)。

4. 模块参数

参数 类型 默认值 含义
chip_addr ushort[] 必须至少 1 个 模拟芯片的 7 位地址,最多 10 个
functionality ulong STUB_FUNC_DEFAULT 覆盖 stub_func() 返回值位掩码
bank_reg u8[] 0 选 bank 的寄存器号
bank_mask u8[] 0 bank 字段掩码(连续位假设)
bank_start u8[] bank 区内寄存器起始
bank_end u8[] bank 区内寄存器结束

4.1 默认 functionality

1
2
3
4
5
6
#define STUB_FUNC_DEFAULT \
(I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | \
I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | \
I2C_FUNC_SMBUS_I2C_BLOCK)

#define STUB_FUNC_ALL (STUB_FUNC_DEFAULT | I2C_FUNC_SMBUS_BLOCK_DATA)
  • I2C_FUNC_SMBUS_BLOCK_DATA 默认关闭;需 modprobe 时设置 functionality 显式开启;
  • stub_func() 返回 STUB_FUNC_ALL & functionality

4.2 加载示例

1
2
3
modprobe i2c-stub chip_addr=0x48,0x49
i2cset -y <stub_bus_num> 0x48 0x00 0x1a
modprobe <被测驱动>

可用 i2c-tools 脚本 i2c-stub-from-dump 从芯片 dump 批量加载寄存器初值。


5. 核心数据结构

5.1 struct stub_chip(每地址一个)

  • pointer:SMBus 当前地址,支持自增读;
  • words[256]:主寄存器文件,字节操作用 低 8 位(符合 SMBus 规范);
  • smbus_blocks:按 command 索引的 SMBus block 数据链表;
  • 可选 bank_words:多 bank 扩展寄存器。

5.2 SMBus block 语义

  • 写可部分更新;读返回历史上最大写长度 的数据;
  • 无先写直接读-EOPNOTSUPP

6. stub_xfer() — SMBus 命令处理

根据 addr 匹配 chip_addr[];未找到返回 -ENODEV

size 行为摘要
QUICK 直接 ACK
BYTE 写设 pointer;读从 pointer++ 取 LSB
BYTE_DATA 读/写 words[command];可切换 bank;pointer=command+1
WORD_DATA 读/写 16 位
I2C_BLOCK 连续访问 words[](忽略 bank)
BLOCK_DATA 链表存 block;须先写后读
其它 -EOPNOTSUPP

6.1 Bank 机制

bank_mask[i] 非 0 时分配 bank_words:写 bank_reg 切换 bank_sel[bank_start,bank_end] 范围内访问 bank 区。


7. 初始化与退出

init: 校验 chip_addr[0]、地址范围、分配 stub_chips、可选 bank、i2c_add_adapter(&stub_adapter)

exit: i2c_del_adapter + 释放内存。

1
2
3
4
5
static struct i2c_adapter stub_adapter = {
.class = I2C_CLASS_HWMON | I2C_CLASS_SPD,
.algo = &smbus_algorithm,
.name = "SMBus stub driver",
};

8. 典型使用流程

  1. modprobe i2c-stub chip_addr=0x48
  2. i2cset 预加载寄存器
  3. modprobe 被测客户端驱动
  4. 观察 dmesg

注意: 驱动若轮询等待寄存器变化可能死锁,需 i2cset 解锁;高频日志可能丢失。


9. 与真实 I2C 对比(RK3588)

维度 i2c-stub i2c-rk3x + 真实设备
硬件 SoC I2C
API 仅 smbus_xfer master_xfer + SMBus 模拟
配置 CONFIG_I2C_STUB=m CONFIG_I2C_RK3X=y

10. RK3588 结论

  • 产品镜像 默认不包含;可选模块用于 驱动开发/CI
  • slave-eeprom/testunit(从机模拟)不同:stub 是 主机侧虚拟总线 + 内存寄存器
  • 不能替代硬件联调。

11. 相关文档


文档版本:基于 rk3588/kernel-6.1 源码树 drivers/i2c/i2c-stub.c 分析。

RK3588 kernel-6.1 drivers/i2c 总线架构分析

RK3588 kernel-6.1 drivers/i2c 总线架构分析

1. 范围说明

本文聚焦目录:

  • rk3588/kernel-6.1/drivers/i2c

分析目标:

  • I2C 子系统在 Linux 设备模型中的位置
  • i2c-core 的总线、适配器、驱动、传输调用链
  • RK3588 所在 Rockchip I2C 主控驱动(i2c-rk3x.c)实现特点
  • 面向开发与排障的实践方法

2. 目录分层与模块组织

drivers/i2c/Makefile 可以看出四层结构:

  1. 核心层i2c-core-base.ci2c-core-smbus.ci2c-core-of.ci2c-core-acpi.c
    提供总线注册、设备/驱动匹配、传输入口、OF/ACPI 枚举与 SMBus 扩展能力。
  2. 用户态接口层i2c-dev.c
    对外暴露 /dev/i2c-*,用于用户态工具(如 i2c-tools)访问总线。
  3. 拓扑扩展层i2c-mux.c + muxes/
    把多路复用后的每条下游链路抽象成独立 i2c_adapter
  4. 控制器驱动层busses/
    各 SoC I2C Host Controller 驱动,Rockchip 关键文件是 busses/i2c-rk3x.c

这说明 I2C 架构本质上是:core 定义规则 + controller 驱动提供 algorithm + client 驱动按地址挂载到 adapter


3. I2C 在 Linux 设备模型中的角色

i2c-core-base.c 中,I2C 总线对象是 i2c_bus_type,并注册了:

  • .match:匹配 i2c_clienti2c_driver
  • .probe:调用 client 驱动探测逻辑
  • .remove:解绑时回收

因此,I2C 也是标准 bus_type,在系统里遵循统一的 device model 生命周期。

可以把三类对象理解为:

  • i2c_adapter:控制器实例(一条物理/逻辑总线)
  • i2c_client:该总线上的从设备抽象(地址 + 属性)
  • i2c_driver:具体芯片驱动(如触摸、PMIC、传感器)

4. 注册与绑定主链路

4.1 控制器侧(Host)注册

控制器驱动完成硬件初始化后,调用:

  • i2c_add_adapter()(动态 bus id)
  • i2c_add_numbered_adapter()(固定 bus id)

两者最终进入 i2c_register_adapter(),由 core 把 adapter 纳入 I2C 总线并触发设备侧匹配。

4.2 客户端驱动(Client)注册

I2C client 驱动通过 i2c_register_driver() 注册。
完成后 core 会尝试把该驱动与现有 i2c_client 逐个匹配。

4.3 匹配结果

匹配成功后进入 i2c_device_probe(),再调用具体 client 驱动的 probe
卸载走 i2c_device_remove()


5. 传输调用链(最关键)

I2C 数据传输链路分两层:

  1. i2c_transfer() / __i2c_transfer():core 通用入口,做重试、锁、trace、能力检查等。
  2. adapter->algo->master_xfer():具体控制器驱动实现,真正触发硬件收发。

也就是:协议框架在 core,时序细节在 bus controller 驱动

对 RK3588 来说,master_xfer() 对应 i2c-rk3x.c 中的 rk3x_i2c_xfer()


6. Rockchip I2C 控制器驱动架构(i2c-rk3x.c

i2c-rk3x.c 是平台驱动(platform_driver),通过 of_match_table 适配多代 Rockchip SoC。
该驱动对 RK3588 这类平台的价值是:把 SoC 寄存器/中断/时钟差异封装成统一 I2C adapter 能力

6.1 驱动入口与资源初始化

rk3x_i2c_probe() 主要做:

  • 解析 bus id(OF alias / ACPI UID)
  • 解析 timing(i2c_parse_fw_timings
  • 映射寄存器、申请 IRQ、获取时钟与复位
  • 设置 i2c_adapter 字段(algoretriesnrparent 等)
  • i2c_add_numbered_adapter() 向 core 注册

这条路径体现了 Linux 控制器驱动通用模板:platform probe -> resource init -> adapter register

6.2 i2c_algorithm 回调实现

Rockchip 驱动提供:

  • .master_xfer = rk3x_i2c_xfer
  • .master_xfer_atomic = rk3x_i2c_xfer_polling
  • .functionality = rk3x_i2c_func

其中 rk3x_i2c_xfer_common() 是核心传输函数,负责:

  • 消息分段处理(支持组合写读)
  • 中断模式与 polling 模式切换
  • 超时、NACK、STOP、恢复与错误上报

6.3 状态机与中断驱动执行

驱动内部状态机:

  • STATE_IDLE
  • STATE_WRITE
  • STATE_READ
  • STATE_STOP

IRQ 入口 rk3x_i2c_irq() 根据状态分发到:

  • rk3x_i2c_handle_write()
  • rk3x_i2c_handle_read()
  • rk3x_i2c_handle_stop()

这是典型主控 I2C 驱动范式:寄存器配置 + 中断推进状态机 + waitqueue 同步上层调用线程

6.4 FIFO 与 DMA 双通路

驱动根据消息长度/方向选择:

  • FIFO 路径(短消息)
  • DMA 路径(较长消息,含 bounce buffer 和 DMA map/unmap)

相关逻辑在:

  • rk3x_i2c_start_fifo()
  • rk3x_i2c_start_dma()
  • rk3x_i2c_complete_dma()

这套实现兼顾低延迟与吞吐量。

6.5 时序计算与动态时钟适配

Rockchip 驱动提供 v0/v1 两套 timing 计算函数:

  • rk3x_i2c_v0_calc_timings()
  • rk3x_i2c_v1_calc_timings()

并在时钟变化通知中重算分频:

  • rk3x_i2c_clk_notifier_cb()
  • rk3x_i2c_adapt_div()

这保证 DVFS 或时钟调整后,I2C 仍满足标准/快速/快速+模式时序约束。

6.6 SoC 差异封装点

通过 struct rk3x_i2c_soc_data 抽象差异:

  • grf_offset
  • calc_timings
  • dma_control

并在 rk3x_i2c_match[] 里按 compatible 选择具体实现。
这种做法能让同一驱动覆盖 rk3066/rk3399/rv1126 等多代硬件。


7. I2C 设备树与总线编号关系

该驱动通过 of_alias_get_id(np, "i2c") 获取 bus id,并赋给 adap.nr,最终对应用户态 i2c-X 编号。
所以系统里 i2c-0/i2c-1/... 稳定性很大程度取决于 DTS alias 配置。

此外,i2c_parse_fw_timings() 会读取固件里的时序属性(如 bus 频率、上升下降沿参数),直接影响硬件分频计算结果。


8. 用户态接口与调试入口

8.1 用户态接口

若使能 CONFIG_I2C_CHARDEVi2c-dev.c 会创建 /dev/i2c-*
这给 bring-up 和问题定位提供了快速验证通道。

8.2 常用排障思路

  1. 确认枚举
    • 适配器是否注册成功(i2c_add_numbered_adapter 路径)
    • /dev/i2c-* 是否出现(启用 I2C_CHARDEV
  2. 确认时序
    • i2c,clk-rate 与硬件支持模式是否匹配
    • 查看是否出现 timeout / NACK / SCL hold 报错
  3. 确认中断与状态机
    • IRQ 是否触发
    • 是否卡在 STATE_STOP 或等待队列超时
  4. 确认总线拓扑
    • 是否经过 mux,目标设备是否在正确下游 adapter

9. 面向驱动开发的结论

在 RK3588 kernel-6.1 中,I2C 架构可以总结为:

  • Core 负责“总线规则”:注册、匹配、生命周期与统一 API。
  • Controller 驱动负责“硬件执行”:时序、状态机、中断/DMA、容错恢复。
  • Client 驱动负责“芯片语义”:寄存器协议、功能逻辑、PM/中断等。

如果你要新增一个 I2C 设备驱动,优先关注的是 client 驱动;
如果你遇到“整条总线异常”,则应回到 i2c-rk3x.c 的传输状态机、时钟时序与中断链路。


10. 推荐阅读顺序(只看关键文件)

  1. drivers/i2c/i2c-core-base.c(总线/注册/传输主流程)
  2. drivers/i2c/i2c-core-of.c(OF 枚举)
  3. drivers/i2c/i2c-dev.c(用户态接口)
  4. drivers/i2c/busses/i2c-rk3x.c(Rockchip 控制器实现)
  5. drivers/i2c/muxes/* + drivers/i2c/i2c-mux.c(复杂拓扑场景)

一句话:RK3588 的 I2C 是“Linux 通用 I2C core + Rockchip rk3x 控制器状态机实现”的组合架构。

RK3506 Linux SMBus 协议与 Alert 机制(中文)

RK3506 Linux SMBus 协议与 Alert 机制(中文)

本文档说明 SMBus Alert、ARA 及 Linux 内核实现,并结合 RK3506 MPU 板级设备树(rk3506b-mpu.dtsi)举例。
内核源码路径以当前工程 kernel/ 为准。

术语

缩写 英文全称 中文
ARA Alert Response Address 告警响应地址
SMBALERT# SMBus Alert (active low) SMBus 告警信号(低有效)

1. SMBus 与 I2C 的关系

项目 说明
物理层 与 I2C 相同(SCL/SDA,开漏)
协议 在 I2C 之上定义超时、ACK 规则、部分命令格式
Linux CONFIG_I2C_SMBUS 启用后提供 i2c_smbus_*() API;Alert 由 i2c-smbus 模块处理

RK3506 的 I2C 控制器由 drivers/i2c/busses/i2c-rk3x.c 驱动,仅作 主机(Master),不实现 I2C 从机模式。


2. SMBus Alert 概述

2.1 信号:SMBALERT#

  • SMBALERT#:可选的开漏、线与信号(类似 SCL/SDA)。
  • 从设备有事件需主机处理时,将 SMBALERT# 拉低
  • 多条从设备可 同时 拉低同一根线;主机只看到一个低电平 → 触发 一次 GPIO 中断。

2.2 专用地址:ARA(0x0C)

ARA 全称:Alert Response Address(告警响应地址)

项目
缩写 ARA
英文全称 Alert Response Address
中文 告警响应地址
7 位地址 0x0C
用途 主机在 SMBALERT# 中断后访问,查询 当前正在告警 的从设备身份

「从设备才会响应 ARA」 的含义:

  • 只有 自己把 SMBALERT# 拉低 的从机,才会对地址 0x0C 应答并在读阶段送出 自己的 7 位地址 + 1 位标志
  • 未拉 SMBALERT# 的从机(例如总线上有 0x59 节点但未告警)不参与 此次 ARA 访问,等价于不存在。

2.3 与正常访问从设备地址的区别

操作 主机寻址 谁应答
读 PSU 寄存器 0x58 reg = <0x58> 的芯片
处理 SMBus Alert 0x0C (ARA) 正在拉 SMBALERT# 的芯片

3. 多从设备同时告警时的仲裁与读取顺序

3.1 规范行为(SMBus 1.1)

  1. 主机对 0x0C 执行 Receive Byte(读 1 字节)。
  2. 多个拉低 SMBALERT# 的从机同时参与;在 从机发送地址 阶段使用 标准 I2C 仲裁
    • SCL 为高时,SDA 上 发 0 胜、发 1 败
    • 7 位地址从高位比较 → 地址数值更小(优先级更高)的设备先胜出
  3. 胜出从机在应答 ARA 后应 释放 SMBALERT# 下拉
  4. 若传输结束后 SMBALERT# 仍为低,主机 再次读 ARA,处理下一个仍挂起的告警。

3.2 示例:0x58、0x59、0x5a,仅 0x58 与 0x5a 拉线

地址 SMBALERT# 第 1 次 ARA 第 2 次 ARA
0x58 拉低 读出 0x58
0x59 未拉低 不参与 不参与
0x5a 拉低 与 0x58 仲裁失败 读出 0x5a

实际读取顺序:0x58 → 0x5a(0x59 不会出现)。

若三者 拉低 SMBALERT#,顺序为:0x58 → 0x59 → 0x5a(严格按地址从小到大,每轮 ARA 读出一个)。

3.3 读回字节的格式

1
2
3
4
status = i2c_smbus_read_byte(ara);   /* ara->addr == 0x0c */

data.addr = status >> 1; /* 7 位从机地址 */
data.data = status & 1; /* 告警类型标志位 */

4. Linux 内核实现

4.1 相关文件

文件 作用
drivers/i2c/i2c-core-smbus.c i2c_new_smbus_alert_device():创建 ARA 客户端(0x0c);i2c_setup_smbus_alert()
drivers/i2c/i2c-smbus.c smbus_alert 驱动:GPIO 中断、for(;;) 循环读 ARA、调用 driver->alert()
drivers/i2c/i2c-core-base.c 适配器注册时调用 i2c_setup_smbus_alert(adap)
drivers/i2c/busses/i2c-rk3x.c RK3506 I2C 主机驱动( 硬件 SMBALERT, I2C Slave)

4.2 适配器注册时自动挂载 Alert

1
2
/* i2c-core-base.c */
res = i2c_setup_smbus_alert(adap);
1
2
3
4
5
6
7
8
/* i2c-core-smbus.c */
int i2c_setup_smbus_alert(struct i2c_adapter *adapter)
{
irq = device_property_match_string(parent, "interrupt-names", "smbus_alert");
if (irq == -EINVAL || irq == -ENODATA)
return 0;
return PTR_ERR_OR_ZERO(i2c_new_smbus_alert_device(adapter, NULL));
}

4.3 ARA 虚拟设备

1
2
3
struct i2c_board_info ara_board_info = {
I2C_BOARD_INFO("smbus_alert", 0x0c),
};

4.4 中断处理与循环读 ARA

流程:

1
2
3
4
5
6
7
8
SMBALERT# GPIO 中断
→ smbus_alert() [threaded IRQ]
→ schedule work → smbalert_work()
→ for (;;) {
i2c_smbus_read_byte(ara); /* 读 0x0C */
smbus_do_alert(); /* 匹配 client->addr,调 driver->alert() */
若同一地址连续两次且未处理 → smbus_do_alert_force() 后 break;
}

内核注释(仲裁与顺序):

1
2
3
4
5
6
/*
* Devices with pending alerts reply in address order, low
* to high, because of slave transmit arbitration. After
* responding, an SMBus device stops asserting SMBALERT#.
*/
status = i2c_smbus_read_byte(ara);

4.5 从设备驱动要求

  • 实现 struct i2c_driver.alert 回调。
  • alert()清除芯片内部告警状态,并确保释放 SMBALERT#(硬件规范要求)。
  • 若已处理完毕,应使 alert() 的语义能让上层不再死循环;未实现 alert() 时会打印 no driver alert()

5. 设备树配置(RK3506)

5.1 每条 I2C 总线仅一路 smbus_alert

Linux I2C 核心 每条 adapter 只支持一个 名为 smbus_alert 的中断,不能 在同一条 &i2cN 上配置多根独立 SMBALERT# GPIO。

多路 SMBALERT# 的推荐做法:

场景 做法
BMC、PSU 各有一条 SMBALERT# 分到 不同 I2C 总线i2c0 / i2c2 等),各配 1 个 smbus_alert
硬件多根线接同一总线 硬件 线与 成一根 SMBALERT#,软件仍只配 1 个 GPIO
插拔/存在检测 子节点 present-gpios,由各驱动处理,不是 SMBus Alert

5.2 板级示例(rk3506b-mpu.dtsi

i2c2(PSU 共用 SMBALERT#,GPIO1_B6):

1
2
3
4
5
6
7
8
9
10
&i2c2 {
/delete-property/ interrupts;
interrupts-extended = <&gic GIC_SPI 42 IRQ_TYPE_LEVEL_HIGH>,
<&gpio1 RK_PB6 IRQ_TYPE_LEVEL_LOW>;
interrupt-names = "irq", "smbus_alert";

aspower_d2000_0: power-supply@58 { reg = <0x58>; /* ... */ };
aspower_d2000_1: power-supply@59 { reg = <0x59>; /* ... */ };
aspower_d2000_2: power-supply@5a { reg = <0x5a>; /* ... */ };
};
  • irq:I2C 控制器完成传输等中断。
  • smbus_alert:SMBALERT# 外部 GPIO 中断。

i2c0(BMC SMBALERT#,示例中当前为注释状态):

1
2
/* interrupts-extended = <&gic ...>, <&gpio1 RK_PD1 IRQ_TYPE_LEVEL_LOW>; */
/* interrupt-names = "irq", "smbus_alert"; */

5.3 内核配置

  • CONFIG_I2C=y
  • CONFIG_I2C_SMBUS=y
  • CONFIG_I2C_RK3X=y(或对应 Rockchip I2C 选项)

6. RK3506 限制说明

能力 RK3506 / i2c-rk3x 说明
I2C Master 支持 常规 i2c0/1/2
SMBus Alert(GPIO + 软件 ARA) 支持 依赖 DTS smbus_alert + CONFIG_I2C_SMBUS
I2C Slave / SMBus Host Notify 不支持 i2c-rk3xI2C_FUNC_SLAVE,无法让 SoC 作总线从机

7. SMBus Host Notify(扩展,本板不适用)

  • 从设备向地址 0x08 写通知,主机需 I2C 从模式 接收。
  • 实现见 i2c-smbus.ci2c_new_slave_host_notify_device()依赖适配器 Slave 能力
  • RK3506 当前 i2c-rk3x 不支持,不能通过 DTS 单独启用。

8. 时序示意(0x58 + 0x5a 同时告警)

1
2
3
4
5
6
7
8
9
10
11
12
13
Dev@0x58          Dev@0x5a          SMBALERT#        Host
| | | |
|---- pull low ---|-----------------| |
| |---- pull low ---| |
| | |-- GPIO -->|
| | | | Read ARA 0x0C
|<--- win arb ---->| | | → 0x58
|-- release # ----| | | alert(0x58)
| | | (仍低) |
| | | | Read ARA 0x0C
| |<--- only 0x5a -| | → 0x5a
| |-- release # ----| | alert(0x5a)
| | | (高) | 结束

9. 调试建议

  1. 确认 dmesg 中有 supports SMBALERT#(适配器注册成功后 i2c-smbus 打印)。
  2. 触发告警后抓 I2C 波形:应对 0x0C 出现读事务,数据字节含从机地址。
  3. 若同一地址反复出现:检查从设备 alert() 是否清除告警 及是否释放 SMBALERT#。
  4. 使用 present-gpios 的插拔事件 不会 走 ARA,勿与 SMBALERT# 混淆。

10. 参考

资料 路径 / 说明
SMBus Alert 内核注释 kernel/drivers/i2c/i2c-smbus.c
ARA 设备创建 kernel/drivers/i2c/i2c-core-smbus.c
I2C 绑定 interrupt-names kernel/Documentation/devicetree/bindings/i2c/i2c.txt
SMBus 协议说明 kernel/Documentation/i2c/smbus-protocol.rst
RK3506 板级 DTS kernel/arch/arm/boot/dts/rk3506b-mpu.dtsi
SMBus 规范 SMBus 1.1+,Appendix / SMBALERT# 章节

文档版本:基于 RK3506 MPU 工程内核与 rk3506b-mpu.dtsi 整理。

Input Core:设备、Handler 与生命周期

Input Core:设备、Handler 与生命周期

1. input_dev

包含:

  • name/phys/uniq/id;
  • evbit/keybit/relbit/absbit/...能力;
  • key/led/snd/sw当前状态;
  • absinfo
  • open/close/flush/event;
  • users、going_away、inhibited;
  • event_lock、mutex;
  • handles list和事件帧缓存。

2. 分配

推荐 devm_input_allocate_device(parent)。填充 parent、能力和 callback 后调用 input_register_device()。注册成功后,非 devm对象由 input_unregister_device()释放,不能再额外 input_free_device()

3. 注册

input_register_device()

  1. 校验 repeat;
  2. 清理不可能的 capability bits;
  3. 分配 vals
  4. 设置 device name/minor/type;
  5. device_add()
  6. 加入全局 device list;
  7. 对所有 handler执行 input_attach_handler()
  8. 唤醒 /proc/bus/input/devices poll。

4. 匹配

input_handler.id_table 根据:

  • bus/vendor/product/version;
  • event type;
  • key/rel/abs/msc/led/snd/ff/sw;
  • property bits;
  • handler自定义 match。

匹配后 handler->connect() 分配私有连接对象和 input_handle

5. Handle

一个 device可连接多个 handler。handle同时挂在:

  • input_dev.h_list
  • input_handler.h_list

input_register_handle() 建立连接;handler通常随后按用户 open情况调用 input_open_device()

6. Open/close

input_open_device() 增加 device users,仅第一个 user调用 driver open();最后一个 input_close_device() 调 driver close()。driver可据此启停 IRQ、poll、URB或硬件。

7. Grab

input_grab_device(handle)dev->grab 指向唯一 handle。之后输入只发给该 handle;release后恢复 fan-out。evdev通过 EVIOCGRAB 使用它。

8. 卸载

1
2
3
4
5
6
input_unregister_device()
-> mark going_away
-> disconnect/release pressed keys
-> handler->disconnect() for every handle
-> remove global list/device model
-> put_device

going_away 阻止新的 open。disconnect前 driver应停止产生新 IRQ/work。

9. Handler 生命周期

input_register_handler() 遍历现有 devices;input_unregister_handler() 对其所有 handles调用 disconnect并移除。

10. proc/sysfs

Core暴露:

  • /proc/bus/input/devices
  • /proc/bus/input/handlers
  • /sys/class/input/inputN/{name,phys,uniq,properties}
  • capabilities和 ID;
  • modalias/uevent。

输入事件模型、状态、同步与时间戳

输入事件模型、状态、同步与时间戳

1. 三元组

每个事件是:

1
type + code + value

UAPI struct input_event 还含 timestamp。

2. 类型

类型 语义
EV_KEY 键/按钮,0释放、1按下、2重复
EV_REL 相对位移
EV_ABS 绝对坐标/轴
EV_SYN 帧边界/同步
EV_MSC 扫描码等杂项
EV_SW 开关状态
EV_LED/SND 发往设备的输出
EV_REP repeat参数
EV_FF 力反馈

3. Core状态

input_event()event_lock 下进入 input_handle_event()

  • 验证 type/code;
  • 对 key/switch/LED等更新 bitmap;
  • 丢弃无变化 key/abs;
  • REL 0通常不发送;
  • ABS应用 fuzz;
  • MT处理 slot;
  • 决定 PASS_TO_HANDLERS、PASS_TO_DEVICE、FLUSH等 disposition。

4. 帧

driver连续 report,最后:

1
input_sync(dev); /* EV_SYN/SYN_REPORT */

Core把 input_value[] 一批传给 handler。应用应按 SYN_REPORT处理原子逻辑帧,而不是把单个坐标事件当完整触摸。

5. SYN_DROPPED

evdev client buffer溢出时发送 SYN_DROPPED。应用必须忽略到下一个 SYN_REPORT之间的增量,并通过 EVIOCGKEY/ABS/SW 等重新同步当前状态。

6. Repeat

EV_REP 能力时 Core timer可软件重复:

  • REP_DELAY
  • REP_PERIOD
  • repeat value 2。

硬件自己重复时不要与软件 repeat双重上报。

7. Keycode

scancode是硬件码,keycode是 Linux KEY_*。driver可报告 MSC_SCAN,再 EV_KEY。keymap可由 matrix/sparse helper管理,并通过 ioctl查询/修改。

8. Timestamp

driver可 input_set_timestamp() 指定采样时刻;否则 handler在分发时取时钟。evdev client可选 realtime、monotonic或 boottime clock。用户程序不要混合不同 clock计算延迟。

9. 输出事件

LED/SND/FF等由用户向 device写入:

1
2
3
evdev write/ioctl -> input_inject_event()
-> input_event()
-> dev->event()

dev->event() 在 spinlock/禁中断语境,不能睡眠;需要 I2C写操作时 driver应排队 work。

10. 安全

事件 capability是 ABI合同。虚报能力会导致 libinput分类错误;漏报会令 handler不匹配。注册前应完整设置,注册后修改需按 Core锁规则。

Evdev 字符设备、缓冲与 ioctl

Evdev 字符设备、缓冲与 ioctl

1. 定位

evdev是现代 Linux/Android主要输入 ABI:

1
2
/dev/input/eventN
major 13, minor 64+

evdev_handler 按能力匹配几乎所有 input device。

2. Connect

1
2
3
4
5
evdev_connect()
-> allocate evdev + input_handle
-> input_register_handle()
-> allocate input minor
-> cdev/device_add

每个 open file创建独立 evdev_client和 ring buffer。

3. Open

首次 evdev client使 handle input_open_device(),进而可能启动真实硬件。close移除 client、释放 grab并在最后用户时关闭 device。

4. 输入路径

1
2
3
4
5
handler->events()
-> evdev_pass_values()
-> each client buffer
-> wake_up_interruptible/poll async
-> userspace read()

每个 reader有独立消费速度,慢 reader不会阻塞 IRQ和其它 reader。

5. 溢出

client buffer满时 evdev标记 packet丢失并插入 SYN_DROPPED恢复协议。应用必须实现状态重同步,不能只记录日志后继续解释增量。

6. Read/poll

  • blocking/nonblocking read;
  • poll/epoll;
  • async SIGIO;
  • 每次读取完整 struct input_event
  • device断开后返回相应 EOF/errno并唤醒等待者。

7. 常用 ioctl

  • EVIOCGVERSION
  • EVIOCGID/NAME/PHYS/UNIQ
  • EVIOCGBIT
  • EVIOCGABS/EVIOCSABS
  • EVIOCGKEY/LED/SW
  • EVIOCGKEYCODE/EVIOCSKEYCODE
  • EVIOCGREP/EVIOCSREP
  • EVIOCSCLOCKID
  • EVIOCGRAB
  • EVIOCREVOKE
  • FF upload/erase。

8. Grab/revoke

EVIOCGRAB 让该 evdev handle独占整个 input device,其他 handler也不再接收事件。EVIOCREVOKE 撤销当前 client后续访问,适合会话切换。

9. Compat

32位用户态在64位内核上由 input-compat.c 和 evdev compat ioctl转换时间与结构。不能直接假设 native struct布局。

10. 权限

读取键盘 event可窃取密码,写/uinput可伪造输入。通常由 udev设置 input group和 seat ACL;服务只授权所需节点,不应开放整个 /dev/input

多点触控、坐标与触摸屏框架

多点触控、坐标与触摸屏框架

1. 触摸分类

  • touchscreen:直接映射显示区域,INPUT_PROP_DIRECT
  • touchpad:间接指针,INPUT_PROP_POINTER
  • stylus/tablet:笔、压力、倾角、工具类型。

分类错误会影响 libinput手势和坐标映射。

2. Protocol B

现代多点触控使用 slots:

1
2
3
4
5
6
ABS_MT_SLOT
ABS_MT_TRACKING_ID
ABS_MT_POSITION_X/Y
ABS_MT_PRESSURE
...
SYN_REPORT

driver:

1
2
3
4
5
6
input_mt_init_slots()
input_mt_slot()
input_mt_report_slot_state()
input_report_abs()
input_mt_sync_frame()
input_sync()

tracking ID标识接触生命周期,不等同于 slot编号。

3. 旧 Protocol A

SYN_MT_REPORT 分隔 contacts,没有 slot状态。仅兼容旧硬件;新驱动应优先 Protocol B。

4. 坐标

input_set_abs_params() 设置 min/max/fuzz/flat。touchscreen_parse_properties() 读取:

  • touchscreen-size-x/y;
  • min-x/y;
  • inverted-x/y;
  • swapped-x-y。

随后 touchscreen_report_pos() 可统一变换。

5. I2C触摸路径

1
2
3
4
5
6
7
8
IRQ
-> read report/frame
-> validate length/checksum
-> parse contacts
-> update slots
-> report BTN_TOUCH/tool keys
-> input_mt_sync_frame()
-> input_sync()

IRQ thread可睡眠并访问 I2C。中断顶半部不应进行长事务。

6. RK3588厂商驱动

公共板 DTS可见:

  • goodix,gt1x
  • hyn,3240
  • focaltech,fts

主配置启用多个厂商 driver。不同板可能在相同 I2C pins上并列定义多个候选节点;必须核对 status、实际 DT和硬件,避免地址/IRQ冲突。

7. 电源管理

suspend:

  • disable IRQ;
  • 进入 sleep或 gesture wake;
  • pinctrl sleep;
  • regulator/reset处理;
  • 清理 slots。

resume后应重新初始化并避免残留 touch。gesture wake需要 IRQ wake和输入策略协调。

8. 校准

电容触摸通常在控制器/firmware校准;Input Core只提供坐标范围和变换。显示旋转还可能由 compositor处理,不应在多个层重复 swap/invert。

9. 故障

  • ghost touch:供电/接地/EMI/firmware;
  • 坐标偏移:size/min配置;
  • 触点黏住:漏报 release或丢帧;
  • IRQ storm:极性错误;
  • resume无触摸:reset/power时序;
  • 多指错乱:tracking ID/slot复用错误。