新手自学嵌入式 | 学习日记: Linux驱动进阶 —— Platform总线与I2C子系统实战

一、Platform 总线思想(设备与驱动分离)

在Linux内核中,为了更好对驱动资源进行管理,引入了 Platform 思想。它的核心就是:将 device(设备)和 driver(驱动)分离。

1. 什么是 Platform 思想?

  • 将 device 和 driver 分别注册到 platform总线(bus) 上。

  • 总线会通过一定的匹配方式(如 name、设备树 compatible 属性、ID table)进行匹配。

  • 匹配成功后 :执行 driver 中的 probe 函数。

  • 注销时 :执行 driver 中的 remove 函数。

2. Platform 匹配方式

常用的有三种:

  • name 字符串匹配。

  • 设备树 compatible 属性匹配(现代驱动最常用)。

  • ID table 匹配。

3. Platform 驱动实现流程(套路)

  1. 描述硬件 :通过设备树描述 device 信息,在设备树中增加一个设备节点。通过设置 compatible 属性,方便后续与驱动进行匹配。

  2. 定义驱动结构体 :驱动中通过 platform_driver 结构体,填充 probe、remove 函数。

  3. 构建匹配表 :驱动中构建 compatible 列表,后续与设备树中的 compatible 属性匹配。

  4. 注册驱动 :驱动中的 init 函数中调用 platform_driver_register 函数,将 driver 注册到 platform 总线。

  5. 注销驱动 :驱动中的 exit 函数中调用 platform_driver_unregister 函数,将 driver 从 platform 总线注销。

  6. 执行 Probe :当设备树节点中的 compatible 属性与驱动中的 compatible 属性匹配成功后,会自动执行 probe 函数。

  7. 在 Probe 中干活 :在 probe 函数中完成杂项设备/字符型设备驱动的注册,并实现 file_operations 结构体中的 open、read、write、release 函数。


二、I2C 子系统驱动框架

I2C是嵌入式开发中极其常用的总线,Linux内核为其设计了一套非常完善的子系统架构。我们将整个I2C系统分为四层:

  1. I2C 物理硬件层:具体的I2C硬件设备(如I2C传感器)。

  2. I2C 适配器驱动层 (Adapter):I2C控制器驱动,一般由芯片驱动厂商实现。

  3. I2C 核心层 (core):提供I2C设备和驱动的注册、匹配、通信的方法。

  4. I2C 设备驱动层 (Device/Client):这是我们自己需要实现的,针对具体I2C设备的驱动。

  5. (最上层) 用户层:调用驱动层的接口,操作I2C设备。

💡 通俗理解:

传感器挂在I2C总线上。芯片厂商写好了"适配器驱动层"来管物理控制器;内核提供了"核心层"来牵线搭桥;我们只需要写"设备驱动层",通过核心层去找适配器,把数据读写出来给应用层(用户层)用就行了。


三、实战解析:LM75 温度传感器驱动

结合图片里的代码,我们来拆解一个真实的I2C驱动 lm75。

1. 驱动注册与注销

和Platform驱动类似,I2C驱动也有标准的入口和出口:

c

复制代码
static int __init lm75_driver_init(void)
{
    int ret = i2c_add_driver(&lm75_driver); // 将i2c驱动注册到i2c总线
    if (ret < 0)
        goto err_i2c_add;
    printk("lm75_driver_init ...\n");
    return 0;
err_i2c_add:
    return ret;
}

static void __exit lm75_driver_exit(void)
{
    i2c_del_driver(&lm75_driver); // 将i2c驱动从总线注销
    printk("lm75_driver_exit ...\n");
}

2. 核心结构体描述 (i2c_driver)

通过 i2c_driver 结构体来描述驱动信息,最重要的就是 probe 和 remove 函数,以及匹配表。

c

复制代码
static struct i2c_driver lm75_driver = {
    .probe = probe,
    .remove = remove,
    .driver = {
        .name = DEV_NAME,
        .of_match_table = of_lm75_table, // 设备树匹配表
    },
    .id_table = lm75_table, // 传统ID匹配表
};

3. 设备树匹配 (compatible)

驱动通过 compatible 属性去匹配设备树中的节点,匹配成功就会调用 probe。

c

复制代码
static const struct of_device_id of_lm75_table[] = {
    { .compatible = "ti,lm75" }, // 通过compatible属性匹配
    {}
};

4. 读数据 (read 函数实现)

在字符设备的 read 函数中,我们使用 i2c_msg 结构体来封装发送的数据,并使用 i2c_transfer 来完成真正的通信。

c

复制代码
static ssize_t read(struct file * file, char __user * buf, size_t size, loff_t * loff)
{
    int ret = 0;
    unsigned char temp[2] = {0};
    
    // 1. 构建i2c通信结构体
    struct i2c_msg msg = {
        .addr = lm75_client->addr, // 从机地址
        .flags = I2C_M_RD,         // 数据流向位:I2C_M_RD 读
        .len = 2,                  // 数据长度
        .buf = temp                // 缓冲区
    };
    
    // 2. 完成i2c数据的传输
    lm75_client->adapter->algo->master_xfer(lm75_client->adapter, &msg, 1);
    // 或者使用内核提供的封装函数:i2c_transfer(lm75_client->adapter, &msg, 1);
    
    // 3. 将数据拷贝给用户层
    ret = copy_to_user(buf, temp, sizeof(temp));
    
    return sizeof(temp);
}

注:在实际开发中,i2c_transfer 是更标准、更推荐使用的接口,它能更好地处理锁和错误。


💡 学习小结:

从今天的代码可以看出,驱动开发其实很像"填空题"。

  1. 对于Platform总线,核心是分离,设备树负责描述硬件,驱动负责处理逻辑,probe 是关键。

  2. 对于I2C驱动,内核框架已经帮我们做好了适配器,我们只需要面向 i2c_client 构建i2c_msg,调用 i2c_transfer 即可。

相关推荐
Android系统攻城狮1 小时前
Linux Gstreamer深度解析之gst_audio_aggregator_set_sink_caps调用流程与实战(九十一)
linux·运维·服务器·gstreamer音视频·音视频进阶
外收内放1 小时前
FastAPI猜字谜实战(3):把每一局存成JSON
开发语言·python·学习·json·fastapi
‎ദ്ദിᵔ.˛.ᵔ₎1 小时前
Linux线程:this指针在pthread中的解决方案
linux
tedcloud1231 小时前
Docker Compose 部署 Memos:从安装到备份恢复
linux·开发语言·jvm·后端·docker·容器·rust
qq_377572771 小时前
typora install linux ubuntu
linux·ubuntu
John_Hui1 小时前
CentOS7安装Nginx
linux·服务器·nginx
qetfw1 小时前
Bash 基础:脚本执行、引号与命令替换验证
linux·开发语言·chrome·bash
怪力左手1 小时前
cuda-toolkit离线安装
linux·运维·人工智能
Gauss松鼠会1 小时前
GaussDB 内存相关参数及故障诊断
运维·数据库·sql·oracle·性能优化·架构·gaussdb