ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

【I2C 技术系列 11】I2C Linux 驱动框架

【I2C 技术系列 11】I2C Linux 驱动框架

📚 I2C 技术系列 · 持续更新中

本系列从 OD 开漏物理层一路打到 Linux i2c子系统,把"两根线"背后那套电气/协议/仲裁/恢复/驱动全拧成一根线。

👉 想系统追更,戳这里查看完整目录:
【I2C 技术系列 00】总目录


本篇定位:裸机你已经会写 I2C(08-10),本篇讲 Linux 下 I2C 子系统怎么组织。i2c_adapter(控制器)/i2c_algorithm(算法)/i2c_client(从机)/i2c_driver(驱动)四对象,加上设备树、i2c_transfer、i2c-dev 用户态接口。读完你能写 Linux I2C 控制器驱动和设备驱动,这是你 BSP 转型的主场。

Linux I2C 框架把裸机那套"全封装"了
你裸机直接写寄存器。Linux 把控制器抽象成 i2c_adapter,把从机抽象成 i2c_client,驱动用 i2c_transfer 提交消息,框架调控制器的 master_xfer。你写的是"适配层",框架管调度/锁/恢复。对照 【Linux内核专栏 11】驱动框架:platform / char / misc / 设备树 一起看。


目录

  • 一、Linux I2C 子系统总览
  • 二、四大核心对象
    • 关系图
  • 三、i2c_adapter 与 i2c_algorithm(控制器驱动)
    • 3.1 i2c_adapter 结构(简化)
    • 3.2 i2c_algorithm
    • 3.3 控制器驱动骨架(DesignWare 风格)
    • 3.4 注册 adapter
  • 四、i2c_client 与 i2c_driver(设备驱动)
    • 4.1 i2c_client
    • 4.2 i2c_driver
    • 4.3 设备驱动骨架(传感器 BMP280 风格)
    • 4.4 匹配机制
  • 五、i2c_transfer 与消息传输
    • 5.1 i2c_msg
    • 5.2 i2c_transfer
    • 5.3 读寄存器(Sr 经典用法)
    • 5.4 SMBus 简化 API
    • 5.5 regmap(更高层封装)
  • 六、设备树描述
    • 6.1 I2C 控制器节点
    • 6.2 从机节点
    • 6.3 设备树到 client
  • 七、i2c-dev 用户态接口
    • 7.1 /dev/i2c-N
    • 7.2 用户态读写代码
    • 7.3 i2c-tools
  • 八、sysfs 与 i2c-tools
    • 8.1 sysfs 路径
    • 8.2 看总线设备
  • 九、关键问题 Q&A
    • Q1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOP+START?
    • Q2:设备树 reg 是 7bit 还是 8bit?
    • Q3:控制器驱动和设备驱动谁先加载?
    • Q4:devm_ 资源管理在 I2C 驱动怎么用?
    • Q5:用户态 i2c-dev 和内核设备驱动冲突吗?
    • Q6:I2C 总线锁怎么加?
  • 十、实战案例
    • 案例 1:写一个 BMP280 设备驱动(简化)
    • 案例 2:用户态调试 EEPROM
    • 案例 3:控制器驱动调试,probe 失败
    • 案例 4:i2c_transfer 读寄存器返回错
    • 案例 5:多 client 共享 adapter,锁冲突?
  • 十一、本篇小结与速查
    • 核心要点
    • 速查表
    • 延伸阅读
    • 参考资料

一、Linux I2C 子系统总览

用户态: i2c-tools(i2cdetect/dump/get/set) / 自写程序(/dev/i2c-N) │ │ syscall ▼ VFS: /dev/i2c-N(i2c-dev 字符设备) │ ▼ I2C 核心:i2c-core(adapter/client/driver 匹配 + i2c_transfer) │ ├── 控制器驱动(i2c_adapter + i2c_algorithm) │ │ │ ▼ │ 硬件寄存器(08 篇) │ └── 设备驱动(i2c_driver + i2c_client) │ ▼ 传感器/EEPROM/PMIC
  • i2c-core:框架核心,匹配 adapter/client/driver,提供 i2c_transfer API
  • 控制器驱动(adapter driver):实现 master_xfer,操作硬件寄存器
  • 设备驱动(client driver):针对具体器件(传感器/EEPROM),用 i2c_transfer 读写
  • i2c-dev:把 controller 暴露成 /dev/i2c-N,用户态直接读写

二、四大核心对象

对象作用类比裸机
i2c_adapter一个 I2C 控制器(总线)你的 I2C 控制器实例
i2c_algorithmadapter 的传输算法(master_xfer)你的 i2c_write/read 实现
i2c_client一个 I2C 从机设备(挂在某 adapter)你的从机地址 + 上下文
i2c_driver从机设备驱动(probe/remove)你的器件驱动逻辑

关系图

i2c_adapter(控制器0) ├── i2c_algorithm(master_xfer) └── i2c_client(从机 0x50,EEPROM) └── i2c_driver(at24 驱动) ← 匹配 compatible i2c_client(从机 0x76,BMP280) └── i2c_driver(bmp280 驱动)
  • 一个 adapter 管一条 I2C 总线,挂多个 client
  • 每个 client 对应一个从机设备
  • i2c_driver 通过 compatible/name 匹配 client

三、i2c_adapter 与 i2c_algorithm(控制器驱动)

3.1 i2c_adapter 结构(简化)

structi2c_adapter{structmodule*owner;conststructi2c_algorithm*algo;// 传输算法void*algo_data;// 驱动私有数据structdevicedev;// 设备模型intnr;// 总线号(i2c-0)charname[48];...};

3.2 i2c_algorithm

structi2c_algorithm{int(*master_xfer)(structi2c_adapter*adap,structi2c_msg*msgs,intnum);int(*master_xfer_atomic)(...);// 原子上下文(不可睡眠)int(*smbus_xfer)(...);// SMBus 传输(可选)u32(*functionality)(...);// 支持的功能(标志位)...};
  • master_xfer:核心,提交一组 i2c_msg 传输
  • functionality:报告支持的功能(I2C_FUNC_I2C / SMBUS / 10BIT 等)

3.3 控制器驱动骨架(DesignWare 风格)

structdw_i2c_priv{void__iomem*base;structi2c_adapteradapter;structi2c_msg*msgs;intmsg_idx;...};staticintdw_i2c_xfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum){structdw_i2c_priv*priv=i2c_get_adapdata(adap);intret;// 1. 使能控制器writel(1,priv->base+IC_ENABLE);// 2. 逐 msg 传输for(inti=0;i<num;i++){ret=dw_i2c_xfer_one(priv,&msgs[i]);if(ret<0)returnret;}returnnum;// 返回成功传输的 msg 数}staticu32dw_i2c_func(structi2c_adapter*adap){returnI2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_10BIT;}staticconststructi2c_algorithmdw_i2c_algo={.master_xfer=dw_i2c_xfer,.functionality=dw_i2c_func,};// probe(platform 驱动)staticintdw_i2c_probe(structplatform_device*pdev){structdw_i2c_priv*priv;structresource*res;priv=devm_kzalloc(&pdev->dev,sizeof(*priv),GFP_KERNEL);res=platform_get_resource(pdev,IORESOURCE_MEM,0);priv->base=devm_ioremap_resource(&pdev->dev,res);// 初始化硬件(分频/中断/...)dw_i2c_init(priv);// 申请中断devm_request_irq(&pdev->dev,platform_get_irq(pdev,0),dw_i2c_isr,0,"dw_i2c",priv);// 注册 adapterpriv->adapter.owner=THIS_MODULE;priv->adapter.algo=&dw_i2c_algo;priv->adapter.dev.parent=&pdev->dev;snprintf(priv->adapter.name,sizeof(priv->adapter.name),"i2c-%d",pdev->id);i2c_set_adapdata(&priv->adapter,priv);returni2c_add_adapter(&priv->adapter);// 注册后,内核自动扫描设备树,创建 i2c_client}

3.4 注册 adapter

// 动态总线号i2c_add_adapter(&adap);// 自动分配 nr// 固定总线号i2c_add_numbered_adapter(&adap);// adap.nr 必须预设
  • 注册后,框架扫描设备树/ACPI,为子节点创建 i2c_client
  • 注册后,/dev/i2c-N出现(i2c-dev 自动绑定)

四、i2c_client 与 i2c_driver(设备驱动)

4.1 i2c_client

structi2c_client{unsignedshortaddr;// 从机地址(7bit)charname[I2C_NAME_SIZE];structi2c_adapter*adapter;// 所属控制器structdevicedev;// 设备模型intirq;// 中断号...};
  • 一个 client = 一个从机设备实例
  • 由框架根据设备树创建

4.2 i2c_driver

structi2c_driver{int(*probe)(structi2c_client*client,conststructi2c_device_id*id);int(*remove)(structi2c_client*client);structdevice_driverdriver;conststructi2c_device_id*id_table;// 传统匹配conststructof_device_id*of_match_table;// 设备树匹配...};

4.3 设备驱动骨架(传感器 BMP280 风格)

structbmp280_data{structi2c_client*client;structregmap*regmap;...};staticintbmp280_probe(structi2c_client*client,conststructi2c_device_id*id){structbmp280_data*data;intret;data=devm_kzalloc(&client->dev,sizeof(*data),GFP_KERNEL);data->client=client;i2c_set_clientdata(client,data);// 读芯片 ID 验证ret=i2c_smbus_read_byte_data(client,BMP280_REG_ID);if(ret!=BMP280_CHIP_ID)return-ENODEV;// 初始化(regmap/iio/输入设备...)bmp280_init(data);// 注册到子系统(iio/input/hwmon...)returnbmp280_register_hwmon(data);}staticintbmp280_remove(structi2c_client*client){// devm_ 自动清理,多数无需操作return0;}staticconststructof_device_idbmp280_of_match[]={{.compatible="bosch,bmp280",},{},};MODULE_DEVICE_TABLE(of,bmp280_of_match);staticstructi2c_driverbmp280_driver={.probe=bmp280_probe,.remove=bmp280_remove,.driver={.name="bmp280",.of_match_table=bmp280_of_match,},};module_i2c_driver(bmp280_driver);// 自动 init/exit

4.4 匹配机制

设备树: compatible = "bosch,bmp280" ↓ 框架: 扫描 i2c_driver.of_match_table ↓ 匹配: bmp280_driver.match("bosch,bmp280") → 命中 ↓ probe: 调 bmp280_probe(client),client 包含地址/中断

五、i2c_transfer 与消息传输

5.1 i2c_msg

structi2c_msg{__u16 addr;// 从机地址(7bit)__u16 flags;// 标志(I2C_M_RD 读 / I2C_M_TEN 10bit / I2C_M_NOSTART ...)__u16 len;// 数据长度__u8*buf;// 数据缓冲};

5.2 i2c_transfer

inti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum);
  • 提交一组 msg,原子执行(中间不发 STOP,自动产生 Sr)
  • 返回成功 msg 数,负值错误

5.3 读寄存器(Sr 经典用法)

// 读 BMP280 寄存器 0xF6,2 字节intbmp280_read_reg(structi2c_client*client,u8 reg,u8*buf,intlen){structi2c_msgmsgs[2]={[0]={.addr=client->addr,.flags=0,// 写.len=1,.buf=&reg,// 寄存器地址},[1]={.addr=client->addr,.flags=I2C_M_RD,// 读.len=len,.buf=buf,},};returni2c_transfer(client->adapter,msgs,2);// 框架自动:START + 写reg + Sr + 读 + NACK + STOP}
  • 两个 msg 一组,框架产生 Sr(不是 STOP+START)
  • 这就是 04 篇 Sr 的 Linux 实现

5.4 SMBus 简化 API

// 读一字节寄存器intval=i2c_smbus_read_byte_data(client,reg);// 写一字节寄存器i2c_smbus_write_byte_data(client,reg,val);// 读一字节(当前地址)intval=i2c_smbus_read_byte(client);// 读 block(长度+数据)i2c_smbus_read_block_data(client,reg,buf);// 写 blocki2c_smbus_write_block_data(client,reg,len,buf);
  • SMBus API 封装常用操作,不用手写 i2c_msg
  • 多数器件用 SMBus API 足够

5.5 regmap(更高层封装)

structregmap_configconfig={.reg_bits=8,.val_bits=8,};structregmap*regmap=devm_regmap_init_i2c(client,&config);regmap_read(regmap,reg,&val);regmap_write(regmap,reg,val);regmap_bulk_read(regmap,reg,buf,len);
  • regmap 进一步封装,支持缓存/同步/调试
  • 现代驱动首选

六、设备树描述

6.1 I2C 控制器节点

&i2c0 { status = "okay"; clock-frequency = <400000>; // 400k pinctrl-0 = <&i2c0_pins>; /* 子节点 = I2C 从机 */ };

6.2 从机节点

&i2c0 { status = "okay"; clock-frequency = <400000>; bmp280@76 { compatible = "bosch,bmp280"; reg = <0x76>; // I2C 地址(7bit) interrupt-parent = <&gpio0>; interrupts = <5 IRQ_TYPE_EDGE_FALLING>; vddio-supply = <&vcc3v3>; }; eeprom@50 { compatible = "atmel,24c04"; reg = <0x50>; pagesize = <16>; }; pmic@5a { compatible = "rockchip,rk817"; reg = <0x5a>; interrupt-parent = <&gpio0>; interrupts = <3 IRQ_TYPE_LEVEL_LOW>; #clock-cells = <1>; }; };
  • reg= I2C 地址(7bit,不要左移)
  • compatible匹配 i2c_driver
  • 其他属性(中断/电源/clock)给驱动用

6.3 设备树到 client

1. 内核解析 DTS,i2c0 节点下每个子节点 2. 为每个子节点创建 i2c_client(addr = reg, irq = interrupts) 3. 用 compatible 匹配 i2c_driver 4. 匹配成功调 probe

七、i2c-dev 用户态接口

7.1 /dev/i2c-N

ls/dev/i2c-*# /dev/i2c-0 /dev/i2c-1
  • 每个 adapter 一个 /dev/i2c-N
  • 用户态 open 后用 ioctl 读写

7.2 用户态读写代码

#include<linux/i2c-dev.h>#include<sys/ioctl.h>#include<fcntl.h>intfd=open("/dev/i2c-0",O_RDWR);ioctl(fd,I2C_SLAVE,0x50);// 设从机地址// 读寄存器 0x10__u8 reg=0x10;write(fd,&reg,1);// 写寄存器地址__u8 val;read(fd,&val,1);// 读数据// 或用 SMBus ioctl__u8 val=i2c_smbus_read_byte_data(fd,0x10);// 用户态封装close(fd);

7.3 i2c-tools

i2cdetect-y0# 扫描总线i2cdump-y00x50# dump 0x50 寄存器i2cget-y00x50 0x10# 读 0x50 的 0x10i2cset-y00x50 0x10 0xAB# 写
  • i2c-tools 基于 i2c-dev,调试神器
  • 详见 [[13-I2C调试与排查]]

八、sysfs 与 i2c-tools

8.1 sysfs 路径

/sys/bus/i2c/ devices/ 0-0076/ <- i2c-0 上地址 0x76 的 client name uevent of_node/ <- 设备树节点 ... i2c-0/ <- adapter name device/ ... drivers/ bmp280/ 0-0076

8.2 看总线设备

ls/sys/bus/i2c/devices/# 0-0076 0-0050 i2c-0cat/sys/bus/i2c/devices/0-0076/name# bmp280

九、关键问题 Q&A

Q1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOP+START?

A:Sr(Repeated START)。i2c_transfer 一次调用内的多个 msg 之间不发 STOP,自动产生 Sr,保证原子性。这是读寄存器(写地址+Sr+读)的正确方式。若需要 STOP,分两次 i2c_transfer 调用。

Q2:设备树 reg 是 7bit 还是 8bit?

A:7bit。reg = <0x50>是 7bit 地址 0x50。框架自动左移加 R/W。别写 0xA0(那是 8bit),会地址错位。

Q3:控制器驱动和设备驱动谁先加载?

A:控制器驱动先(platform 驱动,probe 时注册 adapter)。框架注册 adapter 后扫描设备树子节点,创建 client 并匹配设备驱动。控制器驱动在make menuconfig里是I2C_DESIGNWARE之类,设备驱动是BMP280之类。

Q4:devm_ 资源管理在 I2C 驱动怎么用?

A:devm_kzalloc/devm_ioremap_resource/devm_request_irq/devm_regmap_init_i2c都用 devm_。probe 失败或 remove 时自动释放,不用手动 free。现代 I2C 驱动 remove 多数为空(devm_ 全自动)。

Q5:用户态 i2c-dev 和内核设备驱动冲突吗?

A:可以共存,但同一 client 最好别同时用。i2c-dev 直接操作 adapter,不经设备驱动。若设备驱动已 probe 某从机,用 i2c-dev 再操作可能干扰。调试用 i2c-dev,生产用设备驱动,别混。

Q6:I2C 总线锁怎么加?

A:i2c-core 内部有 adapter 锁(__i2c_lock),同一 adapter 的传输串行化。你写设备驱动不用自己加锁(框架已加)。多 client 共享 adapter,框架保证一次只一个传输。


十、实战案例

案例 1:写一个 BMP280 设备驱动(简化)

// 完整最小骨架staticintbmp280_probe(structi2c_client*client,conststructi2c_device_id*id){structdevice*hwmon;intchip_id=i2c_smbus_read_byte_data(client,0xD0);if(chip_id!=0x58)return-ENODEV;hwmon=devm_hwmon_device_register_with_groups(&client->dev,"bmp280",NULL,bmp280_groups);returnPTR_ERR_OR_ZERO(hwmon);}staticconststructof_device_idbmp280_of_match[]={{.compatible="bosch,bmp280",},{},};module_i2c_driver(&(structi2c_driver){.probe=bmp280_probe,.driver={.name="bmp280",.of_match_table=bmp280_of_match},});

设备树加bmp280@76 { compatible = "bosch,bmp280"; reg = <0x76>; };,启动后 sysfs 出现 hwmon。

案例 2:用户态调试 EEPROM

i2cdetect-y0# 看到 0x50i2cget-y00x50 0x00# 读 0x00i2cset-y00x50 0x00 0xAA# 写 0xAA 到 0x00i2cget-y00x50 0x00# 读回验证

案例 3:控制器驱动调试,probe 失败

现象:自定义 RISC-V SoC I2C 控制器驱动 probe 失败。

排查:

  1. dmesg 看错误
  2. 确认设备树 compatible 匹配驱动 of_match_table
  3. 确认 clock/reset/pinctrl 已配
  4. 确认寄存器地址映射对(devm_ioremap_resource)
  5. 确认中断号对

案例 4:i2c_transfer 读寄存器返回错

现象:i2c_transfer返回 < 2,读失败。

排查:

  1. 查 dmesg 是否有 NACK/ARB_LOST
  2. 示波器看波形,确认 START/地址/ACK
  3. 确认地址对(7bit)
  4. 确认 client->adapter 对

案例 5:多 client 共享 adapter,锁冲突?

现象:担心多传感器共总线,驱动并发访问冲突。

答:i2c-core adapter 锁自动串行化,不用驱动管。但设备驱动内部状态(如缓存)要自己加锁(mutex)。


十一、本篇小结与速查

核心要点

  • Linux I2C 四对象:i2c_adapter(控制器)/i2c_algorithm(传输)/i2c_client(从机)/i2c_driver(驱动)
  • 控制器驱动:实现 master_xfer,注册 i2c_adapter,操作硬件寄存器
  • 设备驱动:实现 probe/remove,用 i2c_transfer/i2c_smbus/regmap 读写器件
  • i2c_transfer 一组 msg 间自动产生 Sr(不是 STOP+START),保证原子性
  • 设备树:i2c 节点 + 子节点(reg=7bit 地址,compatible 匹配)
  • i2c-dev:/dev/i2c-N + ioctl,用户态调试
  • sysfs:/sys/bus/i2c/devices/
  • 框架自动加 adapter 锁,驱动不用管总线锁
  • devm_ 资源管理,remove 多数为空
  • i2c-tools(i2cdetect/dump/get/set)调试神器

速查表

想干啥API/做法
写控制器驱动i2c_add_adapter + master_xfer
写设备驱动module_i2c_driver + probe
读寄存器i2c_smbus_read_byte_data(client, reg)
写寄存器i2c_smbus_write_byte_data(client, reg, val)
多 msg 原子传输i2c_transfer(adap, msgs, num)
高层封装devm_regmap_init_i2c
设备树地址reg = <0x50>(7bit)
用户态读写open /dev/i2c-N + ioctl I2C_SLAVE
扫描总线i2cdetect -y N
看 client/sys/bus/i2c/devices/

延伸阅读

  • 【I2C 技术系列 08】I2C 控制器寄存器与 IP 架构 —— master_xfer 操作的硬件
  • 【I2C 技术系列 09】I2C 传输模式:轮询 / 中断 / DMA —— master_xfer 内部用中断/DMA
  • 【I2C 技术系列 10】I2C 错误处理与总线恢复 —— Linux i2c_recover_bus 框架
  • [[12-I2C设备驱动实战]] —— 完整器件驱动实例
  • [[13-I2C调试与排查]] —— i2c-tools 详解
  • 【Linux内核专栏 11】驱动框架:platform / char / misc / 设备树 —— platform 驱动骨架
  • 【linux内核专栏 10】设备模型 —— bus/driver/device

参考资料

  • Linux Documentation/i2c/(writing-clients / dev-interface / i2c-protocol)
  • Linux drivers/i2c/(i2c-core-base.c / i2c-dev.c / busses/)
  • LDD3 第 10 章(I2C)
  • Linux 内核源码 drivers/iio/pressure/bmp280-i2c.c

【I2C 技术系列 00】总目录 · 上一篇:【I2C 技术系列 10】I2C 错误处理与总线恢复 ·下一篇:[[12-I2C设备驱动实战]]


✨ 感谢你的阅读 ✨

技术之路漫漫,分享是为了更好地交流。
如果本文的内容对你有启发,希望能得到你的支持:

👍 点赞|⭐ 收藏|👀 关注

如果你在调试过程中遇到了其他问题,
欢迎在评论区 💬留言,我们一起探讨。

关注我,一起交流底层开发的那些事儿!🚀


RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进