嵌入式 Linux I2C 驱动开发核心知识总结:设备树与 platform 架构全解析

嵌入式Linux I2C驱动开发核心知识点总结(设备树+platform架构)

本文基于IMX6ULL平台Linux 4.1内核,梳理I2C外设驱动开发的完整知识体系,覆盖驱动框架、设备树、字符设备、调试排障全流程。

一、Linux驱动分层架构总览

Linux驱动采用分层解耦设计思想,从应用层到硬件形成完整调用链:

  1. 应用层:用户空间程序,通过标准文件IO(open/read/write)访问设备
  2. VFS与系统调用层:统一文件抽象接口,屏蔽不同设备类型的差异
  3. 字符设备核心层:管理设备号、cdev结构体,建立文件操作与驱动的映射
  4. 内核子系统层:I2C子系统、platform平台总线,提供总线级通用能力
  5. 芯片驱动层:具体外设(如LM75温度传感器)的硬件操作逻辑
  6. 设备树层:独立描述板级硬件资源,实现驱动代码与硬件信息分离
  7. 硬件层:外设芯片通过I2C总线挂载到SOC的I2C控制器

二、platform平台驱动模型

1. 核心设计思想

平台总线模型实现驱动与设备分离 :platform总线下分为platform_driver(驱动逻辑)和platform_device(硬件信息,现由设备树替代),通过匹配机制自动绑定。

2. 驱动核心结构体

复制代码
struct platform_driver {
    int (*probe)(struct platform_device *);     // 匹配成功后执行初始化
    int (*remove)(struct platform_device *);    // 设备移除时执行资源清理
    struct device_driver driver;
    const struct platform_device_id *id_table;  // 传统ID匹配表
    const struct of_device_id *of_match_table;  // 设备树匹配表
};

3. probe函数核心职责

  • 解析设备树节点,提取硬件资源(I2C地址、引脚、中断号等)
  • 注册字符设备、创建/dev下的设备节点
  • 芯片硬件初始化、申请中断资源
  • 初始化驱动私有数据结构体

4. remove函数核心职责

  • 释放中断、注销字符设备
  • 销毁设备节点、释放内核内存
  • 芯片硬件复位/下电,完成收尾清理

三、设备树(DTS)匹配机制

1. 匹配核心:compatible属性

驱动与设备树节点通过compatible字符串完全精确匹配,大小写、空格、标点必须完全一致,是驱动probe函数执行的前提。

2. I2C设备节点编写规范

复制代码
&i2c1 {
    clock-frequency = <100000>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_i2c1>;
    status = "okay";

    lm75@48 {
        compatible = "alientek,lm75";
        reg = <0x48>;     // I2C外设器件地址
    };
};
  • 节点必须挂载到硬件对应的I2C控制器节点下
  • reg属性为I2C芯片的器件地址
  • status = "okay" 表示启用该节点

3. 设备树生效验证

开发板端通过/proc/device-tree目录实时验证节点状态:

复制代码
# 查看I2C控制器下的子节点
ls /proc/device-tree/soc/i2c@021a0000/
# 查看节点compatible属性
cat /proc/device-tree/soc/i2c@021a0000/lm75@48/compatible

四、I2C子系统驱动架构

1. I2C核心三要素

  1. I2C适配器(adapter):SOC内部I2C控制器的驱动,一般由芯片厂商提供
  2. I2C设备(client):外设芯片的内核抽象,对应设备树中的I2C节点
  3. I2C驱动(driver):外设芯片的业务逻辑,与client匹配后执行probe

2. I2C驱动注册流程

  1. 定义i2c_driver结构体,配置of_match_table设备树匹配表
  2. 调用i2c_add_driver将驱动注册到内核I2C总线
  3. 总线遍历设备树I2C节点,通过compatible匹配驱动
  4. 匹配成功自动调用驱动的probe函数

3. 核心通信API

  • i2c_smbus_read_byte_data:读取芯片指定寄存器的数据
  • i2c_smbus_write_byte_data:向芯片指定寄存器写入数据
  • 所有通信通过I2C适配器统一调度,无需直接操作SOC寄存器

五、内核模块加载与卸载

1. 常用操作命令

复制代码
insmod xxx.ko      # 加载内核模块
rmmod xxx          # 卸载模块(无需加.ko后缀)
lsmod              # 查看系统已加载模块
modinfo xxx.ko     # 查看模块详细信息
dmesg              # 查看内核打印日志

2. 常见加载问题

  • File exists报错 :内核已存在同名模块,属于重复加载,先执行rmmod再加载即可
  • 模块加载成功但probe不执行:90%以上为设备树compatible不匹配、设备树未更新、节点路径错误
  • lsmod引用计数为0:驱动未匹配到任何硬件设备,未与实际硬件完成绑定

六、字符设备驱动层实现

1. 字符设备注册步骤

  1. 申请或指定设备号(主设备号+次设备号)
  2. 初始化cdev结构体,绑定file_operations操作集
  3. 调用cdev_add向内核注册字符设备
  4. 创建设备类(class)、创建设备节点(device),自动生成/dev下的设备文件

2. file_operations核心操作集

复制代码
struct file_operations {
    .owner = THIS_MODULE,
    .open    = lm75_open,     // 打开设备
    .read    = lm75_read,     // 读取温度数据
    .write   = lm75_write,    // 写入芯片配置
    .release = lm75_release   // 关闭设备
};

七、中断与并发控制

1. 中断处理流程

  • 从设备树节点解析获取中断号
  • 申请中断资源,注册中断处理函数
  • 中断上下文采用底半部机制(tasklet/工作队列)处理耗时逻辑
  • 驱动卸载时释放中断资源

2. 并发保护机制

  • 原子操作:适用于简单计数、标志位保护
  • 自旋锁:中断上下文、短时间临界区保护
  • 互斥锁:进程上下文、耗时操作的临界区保护
  • 禁止在中断上下文调用可能导致睡眠的函数

八、驱动调试与问题排查

1. 问题排查优先级

  1. 确认模块加载 :执行lsmod确认模块已在内核中
  2. 确认设备树生效 :通过/proc/device-tree验证节点存在
  3. 确认匹配字符串:逐字符对比驱动与设备树的compatible属性
  4. 查看内核日志 :dmesg查看probe打印与错误信息
  5. 硬件通路验证 :i2cdetect -y 总线号扫描I2C设备地址

2. 经典问题定位

  • 模块已加载但无设备节点:probe函数未执行,优先排查设备树匹配
  • i2cdetect扫不到设备地址:硬件接线错误、引脚复用未配置、I2C控制器未启用
  • insmod直接加载失败:内核版本不匹配、依赖模块未加载、驱动代码语法错误
相关推荐
HideF8 小时前
Linux sudo
linux
FlightYe8 小时前
视界原理之3D视频(四):裸眼、眼镜和头显
android·linux·c++·3d·ffmpeg·音视频
ZhengEnCi8 小时前
GPT-6 Astra 爆改 Qwen 权重真有那么神吗:权重空间编程 0.016% 参数的真相
算法·架构
蹊黎9 小时前
Linux 下用 C 语言实现进度条
linux·c语言·单片机
mg66810 小时前
安卓玩机工具----告别面具!SukiSU Ultra 内核级 Root 与终极隐藏实战指南
驱动开发·root系统
牛马鸡niumasi10 小时前
【Linux】C语言文件操作
linux
say_fall10 小时前
【Linux系统编程】Ext 文件系统:磁盘与存储、文件与目录、现代计算机究竟如何寻找文件
linux·运维·服务器·数据库·c++
码流子10 小时前
移动收费怎么做?手持终端+App+收费中心的架构实践
java·开发语言·架构
wacsii11 小时前
【Linux进程(一)】Linux 进程初探:操作系统如何管理进程?fork() 为什么返回两次?
linux·运维·服务器