复盘|嵌入式Linux驱动开发之I2C子系统
之前看了一位大佬使用费曼学习法复盘了自己对各驱动子系统的理解,这里我也尝试一下,使用文字来进行一下复盘!
I2C子系统各层关系
-
应用程序向下使用I2C 设备驱动层创建的设备节点进行读、写等操作访问硬件
-
设备驱动层本身包含控制I2C外设的驱动程序和设备节点。它向上为应用程序创建设备节点,向下调用I2C核心层提供的API与I2C外设通信。
-
I2C核心层向上为设备驱动层提供API接口;向下调用I2C 适配器驱动层提供的接口。
-
I2C 适配器驱动层本身包含I2C 控制器的驱动程序。它向上向I2C核心层注册i2c_adapter并提供I2C传输接口(i2c_algorithm);向下直接操作I2C控制器硬件,实现I2C时序。
-
硬件层中的I2C控制器需要I2C适配器驱动层驱动它工作。硬件层中的I2C外设通过SDA和SCL与处理器的I2C控制器连接

从左到右、从适配器驱动到设备驱动、从 Platform 总线到 I2C 总线的接力如何进行?
- I2C控制器放在platform总线上,adapter放在i2c_bus总线上

适配器驱动和设备驱动两条线不能分开看!!!
这是一个连续的过程:平台总线 的探测流程触发了 I2C 总线 的探测流程。
阶段一:Platform 总线 —— “适配器驱动” 的加载
这个阶段的目标是让 I2C 控制器(适配器) 本身开始工作。
控制器本身是挂载在 platform_bus_type(平台总线)上的一个设备。
- 起点 (DTS -> platform_device)
- 内核启动,调用
of_platform_populate来扫描设备树根节点 (/)。 - 然后,调用
of_platform_bus_create遍历根节点/下的所有直接子节点 of_platform_bus_create->of_platform_device_create_pdata。- 这会调用
of_device_alloc分配一个platform_device结构体(我们称之为pdev),并从设备树节点中读取信息(如 “compatible” = “fsl,imx6ul-i2c”)。 - 关键一步:
pdev->dev.bus = &platform_bus_type;,声明它属于platform_bus_type平台总线。
- 内核启动,调用
- 注册与探测 (platform_device -> platform_driver)
- 调用
of_device_add(pdev)->device_add(pdev)将这个platform_device注册到内核。 device_add内部调用bus_add_device将pdev添加到platform_bus_type的设备列表(bus->p->klist_devices)中。- 触发探测:
device_add最后调用bus_probe_device(pdev)。 - 内核开始为这个
pdev寻找驱动:device_attach->bus_for_each_drv(遍历platform_bus_type上的所有驱动)。 driver_match_device被调用,用于比较pdev和i2c_imx_driver。
- 调用
- 匹配与执行 (Match!)
- 匹配成功:
pdev的"compatible"属性 ("fsl,imx6ul-i2c") 与i2c_imx_driver的of_match_table匹配成功。 - 内核调用
driver_probe_device->really_probe(执行探测)。 - 阶段一终点:
i2c_imx_driver->probe函数被执行,也就是i2c_imx_probe(pdev)被调用。
- 匹配成功:
阶段二:I2C 总线 —— “设备驱动” 的加载
这个阶段在阶段一的 终点 (i2c_imx_probe 函数) 内部被触发。现在 I2C 控制器驱动已经运行,它的任务是初始化控制器硬件,并**“孵化”** 出挂载在它下面的所有 I2C 设备。
- 起点 (i2c_imx_probe 内部)
i2c_imx_probe函数(适配器驱动)正在执行。- 它初始化 I2C 硬件。
i2c_imx_probe调用i2c_add_numbered_adapter来注册i2c_adapter(代表 I2C 控制器本身)。- 关键接力棒:
i2c_imx_probe调用of_i2c_register_devices,这个函数会去扫描它在设备树中的 子节点(例如at24@50)。
- 孵化 (DTS 子节点 -> i2c_client)
- 对于
at24@50这个子节点,内核调用of_i2c_register_device。 - 这个函数的核心作用就是**“翻译”**:
- 它分配一个临时的
struct i2c_board_info info。 - 它解析
at24@50节点,把 “reg” (0x50) 存入info.addr,把 “compatible” (“atmel,24c16”) 存入info.type,并把节点指针存入info.of_node。
- 它分配一个临时的
i2c_board_info结构体就像一个“中转站”,它持有了从设备树解析出来的、创建i2c_client所需的所有信息。- 调用
i2c_new_device(adap, &info)。
- 对于
- 注册与探测 (i2c_client -> i2c_driver)
i2c_new_device分配一个i2c_client结构体(我们称之为client)。- 它从
info和adap中拷贝信息到client。 - 关键一步:
client->dev.bus = &i2c_bus_type;,声明它属于 I2C 总线。 - 调用
device_register(&client->dev)->device_add(&client->dev)。 - 触发探测(第二次):
device_add再次调用bus_probe_device(client)。 - 内核开始为这个
client寻找驱动:device_attach->bus_for_each_drv(这次遍历的是i2c_bus_type上的所有驱动)。 driver_match_device被调用,用于比较client和at24_driver。
- 匹配与执行 (Match!)
- 匹配成功:
client的 “compatible” 属性(来自info.of_node)与at24_driver的of_match_table匹配成功。 - 内核调用
driver_probe_device->really_probe。 - 阶段二终点:
at24_driver->probe函数被执行,也就是at24_probe(client)被调用。
- 匹配成功:
总结
- Platform 总线 负责匹配 DTS 节点 (
i2c@021a0000) 和 适配器驱动 (i2c_imx_driver)。 - 匹配成功后,执行
i2c_imx_probe。 i2c_imx_probe作为“接力”的中间人,它通过解析 DTS 子节点 (at24@50) 来创建i2c_client。i2c_client被注册到 I2C 总线。- I2C 总线 负责匹配
i2c_client(at24@50) 和 设备驱动 (at24_driver)。 - 匹配成功后,执行
at24_probe。
