📚 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_algorithm | adapter 的传输算法(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/exit4.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=®,// 寄存器地址},[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,®,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-00768.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 失败。
排查:
- dmesg 看错误
- 确认设备树 compatible 匹配驱动 of_match_table
- 确认 clock/reset/pinctrl 已配
- 确认寄存器地址映射对(devm_ioremap_resource)
- 确认中断号对
案例 4:i2c_transfer 读寄存器返回错
现象:i2c_transfer返回 < 2,读失败。
排查:
- 查 dmesg 是否有 NACK/ARB_LOST
- 示波器看波形,确认 START/地址/ACK
- 确认地址对(7bit)
- 确认 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设备驱动实战]]
✨ 感谢你的阅读 ✨
技术之路漫漫,分享是为了更好地交流。
如果本文的内容对你有启发,希望能得到你的支持:
👍 点赞|⭐ 收藏|👀 关注
如果你在调试过程中遇到了其他问题,
欢迎在评论区 💬留言,我们一起探讨。
关注我,一起交流底层开发的那些事儿!🚀