4. Subdev初始化实战
本章是课程的第一个实战环节。我们将从零构建一个标准的 V4L2 Subdev 驱动模板,重点实现 probe 函数与设备树(Device Tree)匹配机制。通过本节的动手实践,你将掌握 Subdev 驱动的注册骨架、内核模块生命周期管理以及如何通过设备树节点绑定驱动。
4.1 Subdev 驱动模板结构
一个完整的 Subdev 驱动通常包含以下核心组件:
| 组件 | 说明 | 关键 API / 结构体 |
|---|---|---|
| 模块入口/出口 | 内核模块的加载与卸载 | module_init / module_exit |
| I2C 驱动结构体 | 注册到 I2C 子系统,绑定设备树 | struct i2c_driver |
| Subdev 结构体 | V4L2 子设备实例,包含 ops | struct v4l2_subdev |
| probe 函数 | 设备发现时的初始化入口 | probe 回调 |
| 设备树匹配表 | 定义 compatible 字符串 | struct of_device_id |
工程提示: Subdev 驱动通常挂载在 I2C 或 SPI 总线上。本实战以 I2C 子设备为例,这是嵌入式摄像头传感器最常用的连接方式。
4.2 编写 Subdev 驱动模板代码
以下是一个最小化的 Subdev 驱动模板,包含模块声明、I2C 驱动注册、probe 函数骨架和设备树匹配表。
// my_subdev.c — 最小化 Subdev 驱动模板
#include <linux/module.h>
#include <linux/i2c.h>
#include <media/v4l2-subdev.h>
#include <media/v4l2-device.h>
/* 设备结构体:保存每个实例的私有数据 */
struct my_subdev_dev {
struct v4l2_subdev sd;
struct i2c_client *client;
int power_on;
};
/* Subdev 核心操作集(占位) */
static const struct v4l2_subdev_core_ops my_core_ops = {
.log_status = NULL, /* 后续章节实现 */
};
/* Subdev 视频操作集(占位) */
static const struct v4l2_subdev_video_ops my_video_ops = {
.s_stream = NULL, /* 后续章节实现 */
};
/* Subdev 操作集总表 */
static const struct v4l2_subdev_ops my_subdev_ops = {
.core = &my_core_ops,
.video = &my_video_ops,
};
/* probe 函数:设备匹配时调用 */
static int my_subdev_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct my_subdev_dev *dev;
int ret;
/* 1. 分配私有数据结构体 */
dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->client = client;
dev->power_on = 0;
/* 2. 初始化 v4l2_subdev 结构体 */
v4l2_i2c_subdev_init(&dev->sd, client, &my_subdev_ops);
/* 3. 设置 Subdev 内部名称(用于调试) */
snprintf(dev->sd.name, sizeof(dev->sd.name),
"my_subdev %d-%04x", client->adapter->nr,
client->addr);
/* 4. 注册 Subdev 到父设备(v4l2_device) */
ret = v4l2_device_register_subdev(&client->dev, &dev->sd);
if (ret < 0) {
dev_err(&client->dev, "subdev registration failed\n");
return ret;
}
dev_info(&client->dev, "my_subdev probed successfully\n");
return 0;
}
/* remove 函数:设备移除时调用 */
static int my_subdev_remove(struct i2c_client *client)
{
struct v4l2_subdev *sd = i2c_get_clientdata(client);
v4l2_device_unregister_subdev(sd);
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id my_subdev_of_match[] = {
{ .compatible = "myvendor,my-sensor-v1" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_subdev_of_match);
/* I2C 设备 ID 表(传统方式) */
static const struct i2c_device_id my_subdev_id[] = {
{ "my-sensor-v1", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, my_subdev_id);
/* I2C 驱动结构体 */
static struct i2c_driver my_subdev_driver = {
.driver = {
.name = "my_subdev",
.of_match_table = my_subdev_of_match,
},
.probe = my_subdev_probe,
.remove = my_subdev_remove,
.id_table = my_subdev_id,
};
/* 模块入口/出口 */
module_i2c_driver(my_subdev_driver);
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Minimal V4L2 Subdev Driver Template");
MODULE_LICENSE("GPL v2");
关键点解析:
v4l2_i2c_subdev_init将 I2C client 与 v4l2_subdev 绑定,并设置sd->owner。v4l2_device_register_subdev将 Subdev 注册到父设备,建立层级关系。of_match_table是设备树匹配的核心,必须与 DTS 中的 compatible 一致。
4.3 probe 函数实现详解
probe 函数是驱动初始化的核心入口。当内核发现 I2C 总线上的设备与驱动匹配时,调用此函数。其典型流程如下:
- 分配私有数据:使用
devm_kzalloc自动管理内存生命周期。 - 初始化 Subdev:调用
v4l2_i2c_subdev_init建立 I2C 与 V4L2 的桥梁。 - 设置标识信息:填充
sd.name便于调试和日志输出。 - 注册到 V4L2 设备:
v4l2_device_register_subdev将 Subdev 加入父设备链表。 - 硬件初始化(可选):读取芯片 ID、配置寄存器、申请中断等。
常见错误: 忘记调用
v4l2_device_register_subdev 会导致 Subdev 无法被媒体框架发现;probe 失败时务必释放已分配的资源,使用 devm_ 系列 API 可简化错误处理。
4.4 设备树匹配机制
设备树(Device Tree)是嵌入式 Linux 描述硬件拓扑的标准方式。Subdev 驱动通过 compatible 字符串与设备树节点匹配。
以下是一个典型的设备树节点示例:
// my-board.dts
&i2c2 {
status = "okay";
clock-frequency = <400000>;
my_sensor: my-sensor@36 {
compatible = "myvendor,my-sensor-v1";
reg = <0x36>;
clocks = <&clk IMX8MM_CLK_24M>;
reset-gpios = <&gpio1 13 GPIO_ACTIVE_LOW>;
};
};
匹配过程的内核流程如下 SVG 图所示:
4.5 编译与加载验证
完成代码编写后,按以下步骤编译并加载驱动:
- 添加 Kconfig 选项(可选):在驱动目录的 Kconfig 中添加
config VIDEO_MY_SUBDEV。 - 修改 Makefile:添加
obj-$(CONFIG_VIDEO_MY_SUBDEV) += my_subdev.o。 - 编译内核/模块:
make modules或make M=drivers/media/i2c。 - 加载模块:
sudo insmod my_subdev.ko。 - 验证注册:查看
dmesg | tail确认 probe 成功,检查/sys/bus/i2c/devices/下是否出现新设备。
调试技巧: 如果 probe 未被调用,首先检查设备树节点是否被正确编译进内核(查看
/proc/device-tree),其次确认 compatible 字符串是否完全一致(包括大小写和通配符)。
4.6 本章小结
通过本实战,你完成了以下关键任务:
- 编写了一个完整的 Subdev 驱动模板,包含模块入口/出口、I2C 驱动结构体、Subdev 初始化。
- 实现了
probe函数,掌握了设备内存分配、Subdev 初始化与注册的标准流程。 - 配置了设备树匹配表,理解了
compatible字符串在驱动绑定中的核心作用。 - 通过 SVG 流程图清晰梳理了从 DTS 到 probe 调用的完整链路。