
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_algorithm3.3 控制器驱动骨架(DesignWare 风格)3.4 注册 adapter四、i2c_client 与 i2c_driver(设备驱动)4.1 i2c_client4.2 i2c_driver4.3 设备驱动骨架(传感器 BMP280 风格)4.4 匹配机制五、i2c_transfer 与消息传输5.1 i2c_msg5.2 i2c_transfer5.3 读寄存器(Sr 经典用法)5.4 SMBus 简化 API5.5 regmap(更高层封装)六、设备树描述6.1 I2C 控制器节点6.2 从机节点6.3 设备树到 client七、i2c-dev 用户态接口7.1 /dev/i2c-N7.2 用户态读写代码7.3 i2c-tools八、sysfs 与 i2c-tools8.1 sysfs 路径8.2 看总线设备九、关键问题 QAQ1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOPSTART?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/PMICi2c-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_algorithmstructi2c_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*privi2c_get_adapdata(adap);intret;// 1. 使能控制器writel(1,priv-baseIC_ENABLE);// 2. 逐 msg 传输for(inti0;inum;i){retdw_i2c_xfer_one(priv,msgs[i]);if(ret0)returnret;}returnnum;// 返回成功传输的 msg 数}staticu32dw_i2c_func(structi2c_adapter*adap){returnI2C_FUNC_I2C|I2C_FUNC_SMBUS_EMUL|I2C_FUNC_10BIT;}staticconststructi2c_algorithmdw_i2c_algo{.master_xferdw_i2c_xfer,.functionalitydw_i2c_func,};// probe(platform 驱动)staticintdw_i2c_probe(structplatform_device*pdev){structdw_i2c_priv*priv;structresource*res;privdevm_kzalloc(pdev-dev,sizeof(*priv),GFP_KERNEL);resplatform_get_resource(pdev,IORESOURCE_MEM,0);priv-basedevm_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.ownerTHIS_MODULE;priv-adapter.algodw_i2c_algo;priv-adapter.dev.parentpdev-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_clientstructi2c_client{unsignedshortaddr;// 从机地址(7bit)charname[I2C_NAME_SIZE];structi2c_adapter*adapter;// 所属控制器structdevicedev;// 设备模型intirq;// 中断号...};一个 client 一个从机设备实例由框架根据设备树创建4.2 i2c_driverstructi2c_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;datadevm_kzalloc(client-dev,sizeof(*data),GFP_KERNEL);data-clientclient;i2c_set_clientdata(client,data);// 读芯片 ID 验证reti2c_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[]{{.compatiblebosch,bmp280,},{},};MODULE_DEVICE_TABLE(of,bmp280_of_match);staticstructi2c_driverbmp280_driver{.probebmp280_probe,.removebmp280_remove,.driver{.namebmp280,.of_match_tablebmp280_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_msgstructi2c_msg{__u16 addr;// 从机地址(7bit)__u16 flags;// 标志(I2C_M_RD 读 / I2C_M_TEN 10bit / I2C_M_NOSTART ...)__u16 len;// 数据长度__u8*buf;// 数据缓冲};5.2 i2c_transferinti2c_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]{.addrclient-addr,.flags0,// 写.len1,.bufreg,// 寄存器地址},[1]{.addrclient-addr,.flagsI2C_M_RD,// 读.lenlen,.bufbuf,},};returni2c_transfer(client-adapter,msgs,2);// 框架自动:START 写reg Sr 读 NACK STOP}两个 msg 一组,框架产生 Sr(不是 STOPSTART)这就是 04 篇 Sr 的 Linux 实现5.4 SMBus 简化 API// 读一字节寄存器intvali2c_smbus_read_byte_data(client,reg);// 写一字节寄存器i2c_smbus_write_byte_data(client,reg,val);// 读一字节(当前地址)intvali2c_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_bits8,.val_bits8,};structregmap*regmapdevm_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; bmp28076 { compatible bosch,bmp280; reg 0x76; // I2C 地址(7bit) interrupt-parent gpio0; interrupts 5 IRQ_TYPE_EDGE_FALLING; vddio-supply vcc3v3; }; eeprom50 { compatible atmel,24c04; reg 0x50; pagesize 16; }; pmic5a { 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 设备树到 client1. 内核解析 DTS,i2c0 节点下每个子节点 2. 为每个子节点创建 i2c_client(addr reg, irq interrupts) 3. 用 compatible 匹配 i2c_driver 4. 匹配成功调 probe七、i2c-dev 用户态接口7.1 /dev/i2c-Nls/dev/i2c-*# /dev/i2c-0 /dev/i2c-1每个 adapter 一个 /dev/i2c-N用户态 open 后用 ioctl 读写7.2 用户态读写代码#includelinux/i2c-dev.h#includesys/ioctl.h#includefcntl.hintfdopen(/dev/i2c-0,O_RDWR);ioctl(fd,I2C_SLAVE,0x50);// 设从机地址// 读寄存器 0x10__u8 reg0x10;write(fd,reg,1);// 写寄存器地址__u8 val;read(fd,val,1);// 读数据// 或用 SMBus ioctl__u8 vali2c_smbus_read_byte_data(fd,0x10);// 用户态封装close(fd);7.3 i2c-toolsi2cdetect-y0# 扫描总线i2cdump-y00x50# dump 0x50 寄存器i2cget-y00x50 0x10# 读 0x50 的 0x10i2cset-y00x50 0x10 0xAB# 写i2c-tools 基于 i2c-dev,调试神器详见 [[13-I2C调试与排查]]八、sysfs 与 i2c-tools8.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九、关键问题 QAQ1:i2c_transfer 的多个 msg 之间是 Sr 还是 STOPSTART?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_idi2c_smbus_read_byte_data(client,0xD0);if(chip_id!0x58)return-ENODEV;hwmondevm_hwmon_device_register_with_groups(client-dev,bmp280,NULL,bmp280_groups);returnPTR_ERR_OR_ZERO(hwmon);}staticconststructof_device_idbmp280_of_match[]{{.compatiblebosch,bmp280,},{},};module_i2c_driver((structi2c_driver){.probebmp280_probe,.driver{.namebmp280,.of_match_tablebmp280_of_match},});设备树加bmp28076 { compatible bosch,bmp280; reg 0x76; };,启动后 sysfs 出现 hwmon。案例 2:用户态调试 EEPROMi2cdetect-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(不是 STOPSTART),保证原子性设备树:i2c 节点 子节点(reg7bit 地址,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设备驱动实战]]✨ 感谢你的阅读 ✨技术之路漫漫分享是为了更好地交流。如果本文的内容对你有启发希望能得到你的支持 点赞|⭐ 收藏| 关注如果你在调试过程中遇到了其他问题欢迎在评论区 留言我们一起探讨。关注我一起交流底层开发的那些事儿