引言
上文我们基本理清了ov13855.c代码的基本框架,以及V4L2和IIC两个驱动框架是如何共存的。
但是,终归是框架性的内容,理解起来比较方便,重点内容比如probe函数的启动流程还是需要详细讲解一下的。
文件路径: drivers/media/i2c/ov13855.c
OV13855 probe函数详解(按三大任务组织)
文件路径 : drivers/media/i2c/ov13855.c
函数入口与基础准备
c
static int ov13855_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct device *dev = &client->dev;
struct device_node *node = dev->of_node;
struct ov13855 *ov13855;
struct v4l2_subdev *sd;
char facing[2];
int ret;
dev_info(dev, "driver version: %02x.%02x.%02x",
DRIVER_VERSION >> 16,
(DRIVER_VERSION & 0xff00) >> 8,
DRIVER_VERSION & 0x00ff);
ov13855 = devm_kzalloc(dev, sizeof(*ov13855), GFP_KERNEL);
if (!ov13855)
return -ENOMEM;
基础准备阶段 :分配驱动私有数据结构,这个结构体将贯穿整个probe流程,存储所有硬件资源句柄和状态信息。
probe函数执行后,内存中的数据结构
bash
ov13855 (私有数据)
├── client (I2C客户端)
├── subdev (V4L2子设备)
│ ├── ops → ov13855_subdev_ops
│ ├── ctrl_handler → 控制器集合
│ └── entity (media实体)
│ └── pad[0] (输出pad)
├── xvclk (时钟句柄)
├── power_gpio (电源GPIO)
├── reset_gpio (复位GPIO)
└── cur_mode → supported_modes[0]
任务一:硬件资源准备
1. 硬件上电时序背景
sensor上电的物理要求:
1. 供电顺序: AVDD(模拟电源) → DOVDD(IO电源) → DVDD(数字电源)
2. 时钟稳定: XVCLK必须在24MHz稳定运行
3. 复位时序: RESET保持低电平足够时间后拉高
4. 稳定延时: 需要8192个时钟周期让内部电路稳定
5. I2C就绪: 之后才能进行I2C通信
上面这个看看就行
2. 读取DTS中的模块配置
c
ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
&ov13855->module_index);
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_FACING,
&ov13855->module_facing);
ret |= of_property_read_string(node, RKMODULE_CAMERA_MODULE_NAME,
&ov13855->module_name);
ret |= of_property_read_string(node, RKMODULE_CAMERA_LENS_NAME,
&ov13855->len_name);
if (ret) {
dev_err(dev, "could not get module information!\n");
return -EINVAL;
}
ov13855->client = client;
ov13855->cur_mode = &supported_modes[0];
作用:从设备树读取硬件模块的身份信息,保存I2C客户端指针用于后续寄存器访问,设置默认输出模式。
3. 获取时钟资源
c
ov13855->xvclk = devm_clk_get(dev, "xvclk");
if (IS_ERR(ov13855->xvclk)) {
dev_err(dev, "Failed to get xvclk\n");
return -EINVAL;
}
硬件连接:XVCLK由SoC的时钟控制器提供,通常是24MHz方波。sensor的所有内部时序都依赖这个时钟,包括MIPI输出、寄存器访问等。
4. 获取GPIO控制引脚
c
ov13855->power_gpio = devm_gpiod_get(dev, "power", GPIOD_OUT_LOW);
if (IS_ERR(ov13855->power_gpio))
dev_warn(dev, "Failed to get power-gpios, maybe no use\n");
ov13855->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(ov13855->reset_gpio))
dev_warn(dev, "Failed to get reset-gpios\n");
ov13855->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
if (IS_ERR(ov13855->pwdn_gpio))
dev_warn(dev, "Failed to get pwdn-gpios\n");
三个GPIO的硬件作用:
power_gpio
:控制LDO使能,决定是否给sensor供电reset_gpio
:硬件复位线,低电平时sensor内部寄存器复位pwdn_gpio
:掉电模式控制,低电平进入超低功耗模式
初始状态GPIOD_OUT_LOW
:获取GPIO的同时设为低电平,此时sensor处于关闭或复位状态。
5. 配置电源调节器
c
ret = ov13855_configure_regulators(ov13855);
if (ret) {
dev_err(dev, "Failed to get power regulators\n");
return ret;
}
电源调节器背景:sensor需要三路独立电源------AVDD(2.8V模拟)、DOVDD(1.8V IO)、DVDD(1.2V数字核心)。这个函数批量获取这些regulator的控制句柄。
ov13855_configure_regulators
实现:(第二篇讲解设备树的时候也有简单讲过)
c
static int ov13855_configure_regulators(struct ov13855 *ov13855)
{
unsigned int i;
for (i = 0; i < OV13855_NUM_SUPPLIES; i++)
ov13855->supplies[i].supply = ov13855_supply_names[i];
return devm_regulator_bulk_get(&ov13855->client->dev,
OV13855_NUM_SUPPLIES,
ov13855->supplies);
}
遍历电源名称数组,批量获取regulator,存入ov13855->supplies[]
数组。
6. 获取引脚复用配置
c
ov13855->pinctrl = devm_pinctrl_get(dev);
if (!IS_ERR(ov13855->pinctrl)) {
ov13855->pins_default =
pinctrl_lookup_state(ov13855->pinctrl,
OF_CAMERA_PINCTRL_STATE_DEFAULT);
if (IS_ERR(ov13855->pins_default))
dev_err(dev, "could not get default pinstate\n");
ov13855->pins_sleep =
pinctrl_lookup_state(ov13855->pinctrl,
OF_CAMERA_PINCTRL_STATE_SLEEP);
if (IS_ERR(ov13855->pins_sleep))
dev_err(dev, "could not get sleep pinstate\n");
}
引脚复用管理:SoC的引脚是多功能的,同一个引脚可能是GPIO、I2C、SPI等。pinctrl子系统确保相关引脚配置为摄像头功能。获取"default"(工作态)和"sleep"(休眠态)两种配置。
7. 初始化同步机制
c
mutex_init(&ov13855->mutex);
并发保护:创建互斥锁保护sensor状态变量。多个进程可能同时调用驱动接口(如开流、设置参数),mutex确保同一时刻只有一个操作在访问硬件寄存器。
8. 硬件上电
c
ret = __ov13855_power_on(ov13855);
if (ret)
goto err_free_handler;
__ov13855_power_on
的上电时序实现:
c
static int __ov13855_power_on(struct ov13855 *ov13855)
{
int ret;
u32 delay_us;
struct device *dev = &ov13855->client->dev;
// 1. 使能主电源GPIO
if (!IS_ERR(ov13855->power_gpio))
gpiod_set_value_cansleep(ov13855->power_gpio, 1);
usleep_range(1000, 2000);
// 2. 切换pinctrl到工作状态
if (!IS_ERR_OR_NULL(ov13855->pins_default)) {
ret = pinctrl_select_state(ov13855->pinctrl,
ov13855->pins_default);
if (ret < 0)
dev_err(dev, "could not set pins\n");
}
// 3. 设置并启动外部时钟
ret = clk_set_rate(ov13855->xvclk, OV13855_XVCLK_FREQ);
if (ret < 0)
dev_warn(dev, "Failed to set xvclk rate (24MHz)\n");
ret = clk_prepare_enable(ov13855->xvclk);
if (ret < 0) {
dev_err(dev, "Failed to enable xvclk\n");
return ret;
}
// 4. 释放复位信号(拉低)
if (!IS_ERR(ov13855->reset_gpio))
gpiod_set_value_cansleep(ov13855->reset_gpio, 0);
// 5. 使能regulator电源
ret = regulator_bulk_enable(OV13855_NUM_SUPPLIES, ov13855->supplies);
if (ret < 0) {
dev_err(dev, "Failed to enable regulators\n");
goto disable_clk;
}
// 6. 拉高复位信号,sensor开始工作
if (!IS_ERR(ov13855->reset_gpio))
gpiod_set_value_cansleep(ov13855->reset_gpio, 1);
usleep_range(5000, 6000);
// 7. 退出掉电模式
if (!IS_ERR(ov13855->pwdn_gpio))
gpiod_set_value_cansleep(ov13855->pwdn_gpio, 1);
// 8. 等待sensor内部稳定(8192时钟周期)
delay_us = ov13855_cal_delay(8192);
usleep_range(delay_us * 2, delay_us * 3);
return 0;
disable_clk:
clk_disable_unprepare(ov13855->xvclk);
return ret;
}
上电时序的物理过程:
- 主电源使能 → LDO输出电压
- 时钟启动 → 24MHz方波输出
- 电源稳定 → AVDD/DOVDD/DVDD按序上电
- 释放复位 → sensor内部开始初始化
- 延时等待 → 内部电路稳定,I2C控制器就绪
9. 验证硬件ID
c
ret = ov13855_check_sensor_id(ov13855, client);
if (ret)
goto err_power_off;
ov13855_check_sensor_id
实现:
c
static int ov13855_check_sensor_id(struct ov13855 *ov13855,
struct i2c_client *client)
{
struct device *dev = &ov13855->client->dev;
u32 id = 0;
int ret;
// 读取3字节芯片ID寄存器
ret = ov13855_read_reg(client, OV13855_REG_CHIP_ID,
OV13855_REG_VALUE_24BIT, &id);
if (id != CHIP_ID) {
dev_err(dev, "Unexpected sensor id(%06x), ret(%d)\n", id, ret);
return -ENODEV;
}
// 读取芯片版本号
ret = ov13855_read_reg(client, OV13855_CHIP_REVISION_REG,
OV13855_REG_VALUE_08BIT, &id);
if (ret) {
dev_err(dev, "Read chip revision register error\n");
return ret;
}
dev_info(dev, "Detected OV%06x sensor, REVISION 0x%x\n", CHIP_ID, id);
return 0;
}
硬件验证逻辑:
- 通过I2C从寄存器
OV13855_REG_CHIP_ID
读取3字节 - 期望值是
CHIP_ID = 0x00d855
- 如果匹配,说明硬件连接正确、上电时序正确、I2C通信正常
- 否则返回
-ENODEV
错误,probe失败
至此,硬件资源准备任务完成:所有资源已获取,硬件已上电并验证通过。
任务二:V4L2框架集成
1. V4L2子设备的作用
V4L2子设备概念 :把硬件模块抽象成一个具有标准化操作接口的软件对象。每个子设备通过v4l2_subdev_ops
结构体向框架提供功能函数。
OV13855作为子设备:对外提供开流、关流、设置分辨率、读取帧率等标准操作,隐藏底层寄存器细节。
2. 初始化V4L2子设备
c
sd = &ov13855->subdev;
v4l2_i2c_subdev_init(sd, client, &ov13855_subdev_ops);
v4l2_i2c_subdev_init
做的事情:
- 设置
sd->owner = THIS_MODULE
- 设置
sd->dev = &client->dev
(指向I2C设备) - 设置
sd->ops = &ov13855_subdev_ops
(操作函数集) - 用I2C设备名初始化
sd->name
- 调用
v4l2_set_subdevdata(sd, client)
(保存私有数据)
建立的关系:
v4l2_subdev (sd)
├── ops → ov13855_subdev_ops(提供标准操作)
├── dev → i2c_client->dev(关联硬件设备)
└── i2c_client(通过subdevdata获取)
3. 创建V4L2控制器
c
ret = ov13855_initialize_controls(ov13855);
if (ret)
goto err_destroy_mutex;
控制器的作用:让用户空间能够调整sensor参数(曝光、增益、测试图案等)。
ov13855_initialize_controls
的实现逻辑:
c
static int ov13855_initialize_controls(struct ov13855 *ov13855)
{
const struct ov13855_mode *mode;
struct v4l2_ctrl_handler *handler;
s64 exposure_max, vblank_def;
u32 h_blank;
int ret;
handler = &ov13855->ctrl_handler;
mode = ov13855->cur_mode;
// 初始化控制器处理器
ret = v4l2_ctrl_handler_init(handler, 8);
if (ret)
return ret;
handler->lock = &ov13855->mutex;
// 创建链路频率控制器(只读)
ov13855->link_freq = v4l2_ctrl_new_int_menu(handler, NULL,
V4L2_CID_LINK_FREQ,
1, 0, link_freq_items);
// 创建像素速率控制器(只读)
ov13855->pixel_rate = v4l2_ctrl_new_std(handler, NULL,
V4L2_CID_PIXEL_RATE,
0, OV13855_PIXEL_RATE,
1, dst_pixel_rate);
// 创建水平消隐控制器(只读)
ov13855->hblank = v4l2_ctrl_new_std(handler, NULL, V4L2_CID_HBLANK,
h_blank, h_blank, 1, h_blank);
if (ov13855->hblank)
ov13855->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
// 创建垂直消隐控制器(可写,影响帧率)
ov13855->vblank = v4l2_ctrl_new_std(handler, &ov13855_ctrl_ops,
V4L2_CID_VBLANK, vblank_def,
OV13855_VTS_MAX - mode->height,
1, vblank_def);
// 创建曝光时间控制器(可写)
ov13855->exposure = v4l2_ctrl_new_std(handler, &ov13855_ctrl_ops,
V4L2_CID_EXPOSURE, OV13855_EXPOSURE_MIN,
exposure_max, OV13855_EXPOSURE_STEP,
mode->exp_def);
// 创建模拟增益控制器(可写)
ov13855->anal_gain = v4l2_ctrl_new_std(handler, &ov13855_ctrl_ops,
V4L2_CID_ANALOGUE_GAIN, OV13855_GAIN_MIN,
OV13855_GAIN_MAX, OV13855_GAIN_STEP,
OV13855_GAIN_DEFAULT);
// 创建测试图案控制器(可写)
ov13855->test_pattern = v4l2_ctrl_new_std_menu_items(handler,
&ov13855_ctrl_ops, V4L2_CID_TEST_PATTERN,
ARRAY_SIZE(ov13855_test_pattern_menu) - 1,
0, 0, ov13855_test_pattern_menu);
if (handler->error) {
ret = handler->error;
dev_err(&ov13855->client->dev,
"Failed to init controls(%d)\n", ret);
goto err_free_handler;
}
// 关联控制器处理器到子设备
ov13855->subdev.ctrl_handler = handler;
return 0;
err_free_handler:
v4l2_ctrl_handler_free(handler);
return ret;
}
控制器的分类:
- 只读控制器 :
link_freq
、pixel_rate
、hblank
,用于查询sensor当前参数 - 可写控制器 :
vblank
、exposure
、anal_gain
、test_pattern
,用户可以调整
用户空间的使用:
c
// 查询控制器
ioctl(fd, VIDIOC_QUERYCTRL, &qctrl);
// 设置曝光时间
struct v4l2_control ctrl;
ctrl.id = V4L2_CID_EXPOSURE;
ctrl.value = 1000;
ioctl(fd, VIDIOC_S_CTRL, &ctrl);
内核会调用ov13855_ctrl_ops.s_ctrl
函数,最终写入硬件寄存器。
4. 设置子设备ops函数集
文件中的ops定义:
c
static const struct v4l2_subdev_ops ov13855_subdev_ops = {
.core = &ov13855_core_ops,
.video = &ov13855_video_ops,
.pad = &ov13855_pad_ops,
};
这个ops在v4l2_i2c_subdev_init
时已经设置到sd->ops
。
三个ops子集的作用:
core_ops
:电源管理(s_power
)、ioctl处理video_ops
:流控制(s_stream
)、帧率操作(g_frame_interval
)pad_ops
:格式协商(set_fmt
)、枚举支持的格式
关键函数s_stream
:
c
static int ov13855_s_stream(struct v4l2_subdev *sd, int on)
{
// 通过container_of宏从v4l2_subdev指针反向获取ov13855私有数据结构
// 这样就能访问sensor的所有状态和硬件资源句柄
struct ov13855 *ov13855 = to_ov13855(sd);
// 获取I2C客户端指针,后续所有寄存器读写都通过它进行
struct i2c_client *client = ov13855->client;
int ret = 0;
// 获取互斥锁,保护streaming状态和硬件寄存器访问
// 防止多个进程同时调用造成状态混乱或硬件冲突
mutex_lock(&ov13855->mutex);
// 双重逻辑非运算:将任意非零值规范化为1,0保持为0
// 例如:on=5 → !5=0 → !0=1 → on=1
// 作用:统一参数格式,上层可能传入任意非零值表示开启
on = !!on;
// 状态去重检查:如果目标状态与当前状态相同则直接返回
// 避免重复操作:已开流时再次开流、或已关流时再次关流
// 防止重复写寄存器造成硬件异常,提高效率
if (on == ov13855->streaming)
goto unlock_and_return;
// ===== 开流分支 =====
if (on) {
// 运行时电源管理:同步上电
// 1. 增加设备使用计数
// 2. 如果设备处于suspend状态,唤醒设备
// 3. 调用ov13855_runtime_resume → __ov13855_power_on
// - 使能GPIO电源
// - 启动24MHz外部时钟
// - 按时序使能regulator电源
// - 释放复位信号
// 4. 等待设备完全上电后返回
ret = pm_runtime_get_sync(&client->dev);
if (ret < 0) {
// 上电失败(可能是电源异常、时钟失败等)
// 使用put_noidle:降低计数但不触发suspend
// 原因:设备根本没成功上电,不能执行下电操作
pm_runtime_put_noidle(&client->dev);
goto unlock_and_return;
}
// 启动数据流输出
// 1. 写入当前模式的寄存器配置(分辨率、帧率、像素格式等)
// 2. 应用控制器的值(曝光时间、增益等参数)
// 3. 向control mode寄存器写0x01,sensor开始输出MIPI信号
ret = __ov13855_start_stream(ov13855);
if (ret) {
// 启动流失败(I2C通信错误、寄存器配置错误等)
v4l2_err(sd, "start stream failed while write regs\n");
// 使用put:降低使用计数,允许PM框架正常关闭电源
// 因为设备已经上电成功,需要执行完整的下电流程
pm_runtime_put(&client->dev);
goto unlock_and_return;
}
}
// ===== 关流分支 =====
else {
// 停止数据流输出
// 向control mode寄存器写0x00,sensor进入standby模式
// 硬件效果:停止输出MIPI信号,像素阵列停止工作,进入低功耗
__ov13855_stop_stream(ov13855);
// 运行时电源管理:允许下电
// 1. 降低设备使用计数
// 2. 如果计数降为0,PM框架调用ov13855_runtime_suspend
// 3. 执行__ov13855_power_off:
// - 关闭regulator电源
// - 停止外部时钟
// - 拉低电源GPIO
// 4. sensor进入最低功耗状态
pm_runtime_put(&client->dev);
}
// 更新streaming状态标志
// 用于:1) 下次调用时的状态去重判断
// 2) 其他函数读取当前状态(如suspend流程)
ov13855->streaming = on;
unlock_and_return:
// 释放互斥锁,允许其他操作访问
mutex_unlock(&ov13855->mutex);
// 返回操作结果:0=成功,负数=失败
return ret;
}
开流流程(on=1):
1. 获取锁 (mutex_lock)
2. 规范化参数 (on = !!on)
3. 检查状态去重
└─ 已经开流? → 直接返回
4. 上电 (pm_runtime_get_sync)
├─ 调用 ov13855_runtime_resume
└─ 执行 __ov13855_power_on
├─ 使能GPIO电源
├─ 启动24MHz时钟
├─ 使能regulator
└─ 释放复位信号
5. 启动流 (__ov13855_start_stream)
├─ 写入模式寄存器(分辨率、帧率)
├─ 应用控制器值(曝光、增益)
└─ 写streaming寄存器0x01
6. 更新状态 (streaming = 1)
7. 释放锁返回
↓
[sensor开始输出MIPI数据]
关流流程(on=0):
1. 获取锁
2. 规范化参数
3. 检查状态去重
└─ 已经关流? → 直接返回
4. 停止流 (__ov13855_stop_stream)
└─ 写streaming寄存器0x00
5. 下电 (pm_runtime_put)
├─ 降低使用计数
└─ 计数=0时调用 ov13855_runtime_suspend
└─ 执行 __ov13855_power_off
├─ 关闭regulator
├─ 停止时钟
└─ 拉低电源GPIO
6. 更新状态 (streaming = 0)
7. 释放锁返回
↓
[sensor完全关闭,最低功耗]
5. 配置子设备标志位
c
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
sd->internal_ops = &ov13855_internal_ops;
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
#endif
V4L2_SUBDEV_FL_HAS_DEVNODE
标志的含义 :
告诉V4L2框架"这个子设备需要创建独立的字符设备节点"。
为什么需要子设备节点:
bash
子设备节点 = sensor的直通接口
没有子设备节点:应用 → /dev/video0 → ISP驱动 → sensor(间接控制)
有子设备节点:应用 → /dev/v4l-subdev2 → sensor(直接控制)
应用程序可以直接打开/dev/v4l-subdev2
,独立控制sensor而不经过ISP。这对调试和高级应用很有用。
6. 初始化media entity
c
#if defined(CONFIG_MEDIA_CONTROLLER)
ov13855->pad.flags = MEDIA_PAD_FL_SOURCE;
sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
ret = media_entity_pads_init(&sd->entity, 1, &ov13855->pad);
if (ret < 0)
goto err_power_off;
#endif
这个还是可以重点关注一下的,后面的文章也会提到
media框架的作用:描述硬件模块间的连接拓扑。
pad的概念:连接点,类似硬件接口。sensor有一个输出pad,连接到CSI2的输入pad。
media_entity_pads_init
做的事:
- 分配pad数组内存
- 设置pad属性(方向、标志)
- 关联pad到entity
拓扑关系:
[OV13855 entity]
└─ pad[0] (SOURCE) --连接线--> [CSI2 entity] pad[0] (SINK)
7. 构造子设备名称
c
memset(facing, 0, sizeof(facing));
if (strcmp(ov13855->module_facing, "back") == 0)
facing[0] = 'b';
else
facing[0] = 'f';
snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
ov13855->module_index, facing,
OV13855_NAME, dev_name(sd->dev));
命名规则 :m<模块号>_<朝向>_<sensor型号> <I2C设备名>
示例 :m00_b_ov13855 4-0010
m00
:模块0b
:后置摄像头ov13855
:sensor型号4-0010
:I2C总线4,设备地址0x10
用途:用户空间通过这个名字识别具体的摄像头。
至此,V4L2框架集成完成:子设备已初始化,控制器已创建,ops已设置,media entity已配置。
任务三:异步框架注册
1. 异步注册的必要性
问题背景:Linux设备驱动的probe顺序是不确定的。可能出现:
- 场景1:sensor先probe,ISP后probe
- 场景2:ISP先probe,sensor后probe
传统同步方式的问题 :
如果ISP驱动在probe时直接查找sensor子设备,当sensor还未probe时就会失败。
异步框架的解决方案:
- sensor probe时注册到异步框架,声明"我是一个可用的子设备"
- ISP probe时建立notifier,声明"我需要这些子设备"
- 框架自动匹配,当所有需要的子设备都就绪时触发complete回调
- 在complete回调中建立连接、创建设备节点
2. 注册到异步框架
c
ret = v4l2_async_register_subdev_sensor_common(sd);
if (ret) {
dev_err(dev, "v4l2 async register subdev failed\n");
goto err_clean_entity;
}
v4l2_async_register_subdev_sensor_common
做的事:
- 设置子设备的fwnode(从设备树获取)
- 调用
v4l2_async_register_subdev(sd)
注册到全局异步子设备列表 - 框架开始尝试匹配:查找是否有notifier在等待这个fwnode的子设备
注册后的状态:
全局async子设备列表
├── ov13855_subdev (fwnode = /i2c@.../ov13855@10)
└── ... 其他子设备
3. ISP驱动的notifier建立
文件 : drivers/staging/media/rkisp1/rkisp1-dev.c
ISP驱动probe时的操作:
c
static int rkisp1_subdev_notifier(struct rkisp1_device *rkisp1)
{
struct v4l2_async_notifier *ntf = &rkisp1->notifier;
unsigned int next_id = 0;
int ret;
v4l2_async_notifier_init(ntf);
// 遍历设备树中的endpoint
while (1) {
struct v4l2_fwnode_endpoint vep = {
.bus_type = V4L2_MBUS_CSI2_DPHY
};
struct rkisp1_sensor_async *rk_asd = NULL;
struct v4l2_async_subdev *asd;
struct fwnode_handle *ep;
// 获取endpoint节点
ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(rkisp1->dev),
0, next_id, FWNODE_GRAPH_ENDPOINT_NEXT);
if (!ep)
break;
// 解析endpoint参数
ret = v4l2_fwnode_endpoint_parse(ep, &vep);
if (ret)
goto err_parse;
// 添加到notifier,指定需要endpoint的remote端子设备
asd = v4l2_async_notifier_add_fwnode_remote_subdev(ntf, ep,
sizeof(*rk_asd));
if (IS_ERR(asd)) {
ret = PTR_ERR(asd);
goto err_parse;
}
rk_asd = container_of(asd, struct rkisp1_sensor_async, asd);
rk_asd->mbus_type = vep.bus_type;
rk_asd->mbus_flags = vep.bus.mipi_csi2.flags;
rk_asd->lanes = vep.bus.mipi_csi2.num_data_lanes;
next_id = vep.base.id + 1;
fwnode_handle_put(ep);
}
// 设置回调函数
ntf->ops = &rkisp1_subdev_notifier_ops;
// 注册notifier,框架开始匹配
ret = v4l2_async_notifier_register(&rkisp1->v4l2_dev, ntf);
...
}
Notifier的作用 :
声明"我需要这些fwnode对应的子设备",框架会自动匹配。
4. 异步匹配与回调
匹配过程:
- ISP注册notifier,列出需要的fwnode列表
- 框架遍历已注册的子设备,比对fwnode
- 找到匹配的子设备,调用
bound
回调 - 所有需要的子设备都匹配后,调用
complete
回调
bound回调(每匹配一个子设备调用一次):
c
static int rkisp1_subdev_notifier_bound(struct v4l2_async_notifier *notifier,
struct v4l2_subdev *sd,
struct v4l2_async_subdev *asd)
{
struct rkisp1_device *rkisp1 =
container_of(notifier, struct rkisp1_device, notifier);
struct rkisp1_sensor_async *s_asd =
container_of(asd, struct rkisp1_sensor_async, asd);
s_asd->pixel_rate_ctrl = v4l2_ctrl_find(sd->ctrl_handler,
V4L2_CID_PIXEL_RATE);
s_asd->sd = sd; // 保存sensor子设备指针
s_asd->dphy = devm_phy_get(rkisp1->dev, "dphy");
...
phy_init(s_asd->dphy);
return 0;
}
作用:保存子设备指针,初始化MIPI D-PHY。
5. complete回调:创建设备节点
complete回调(所有子设备都匹配后调用):
c
static int rkisp1_subdev_notifier_complete(struct v4l2_async_notifier *notifier)
{
struct rkisp1_device *rkisp1 =
container_of(notifier, struct rkisp1_device, notifier);
int ret;
// 建立media连接(pad之间的link)
ret = rkisp1_create_links(rkisp1);
if (ret)
return ret;
// 创建子设备节点!关键代码!
ret = v4l2_device_register_subdev_nodes(&rkisp1->v4l2_dev);
if (ret)
return ret;
dev_dbg(rkisp1->dev, "Async subdev notifier completed\n");
return 0;
}
6. 设备节点创建的实际执行
v4l2_device_register_subdev_nodes
的实现逻辑:
c
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
{
struct v4l2_subdev *sd;
int err;
// 遍历所有已注册的子设备
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
// 检查是否有HAS_DEVNODE标志
if (!(sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE))
continue;
// 创建字符设备节点
err = __video_register_device(&sd->devnode, VFL_TYPE_SUBDEV,
-1, 1, sd->owner);
if (err < 0)
return err;
}
return 0;
}
创建过程:
- 遍历
v4l2_dev->subdevs
链表 - 检查
sd->flags & V4L2_SUBDEV_FL_HAS_DEVNODE
- 如果标志存在,调用
__video_register_device
- 注册为
VFL_TYPE_SUBDEV
类型的video设备 - 内核分配次设备号,创建
/dev/v4l-subdev<N>
节点
最终结果:
bash
$ ls -l /dev/v4l-subdev*
crw-rw---- 1 root video 81, 2 ... /dev/v4l-subdev0 # ISP子设备
crw-rw---- 1 root video 81, 3 ... /dev/v4l-subdev1 # CSI2子设备
crw-rw---- 1 root video 81, 4 ... /dev/v4l-subdev2 # OV13855子设备
设备节点的文件操作 :
用户空间打开/dev/v4l-subdev2
后,可以:
- 用
VIDIOC_QUERYCTRL
查询支持的控制器 - 用
VIDIOC_G_CTRL/S_CTRL
读写控制器 - 用
VIDIOC_SUBDEV_G_FMT/S_FMT
查询/设置格式
7. 使能运行时电源管理
c
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
pm_runtime_idle(dev);
return 0;
运行时PM的作用:
pm_runtime_set_active
:标记设备当前为活动状态(因为probe中已上电)pm_runtime_enable
:使能运行时电源管理pm_runtime_idle
:设为空闲态,PM框架可以决定是否关闭电源
节能机制 :
当没有应用打开设备时,PM框架会调用ov13855_runtime_suspend
,执行__ov13855_power_off
关闭电源。需要时再通过ov13855_runtime_resume
上电。
至此,异步框架注册完成:子设备已加入异步框架,等待ISP完成匹配,最终在complete回调中创建设备节点。
完整时序图
时间线:
↓
[OV13855 probe开始]
├─ 任务一:硬件资源准备
│ ├─ 分配私有数据结构
│ ├─ 读取DTS模块配置
│ ├─ 获取时钟、GPIO、regulator、pinctrl
│ ├─ 初始化mutex
│ ├─ 上电(按时序控制GPIO和regulator)
│ └─ 验证芯片ID ✓
│
├─ 任务二:V4L2框架集成
│ ├─ 初始化v4l2_subdev
│ ├─ 创建控制器(曝光、增益等)
│ ├─ 设置ops函数集
│ ├─ 设置V4L2_SUBDEV_FL_HAS_DEVNODE标志 ← 预约设备节点
│ └─ 初始化media entity和pad
│
└─ 任务三:异步框架注册
├─ 调用v4l2_async_register_subdev_sensor_common
├─ 注册到全局async子设备列表
└─ 使能runtime PM
[OV13855 probe结束]
↓
[等待ISP驱动probe...]
↓
[ISP probe]
├─ 建立async notifier
├─ 注册notifier
└─ 框架开始匹配
↓
[框架匹配到OV13855]
├─ 调用bound回调
│ └─ 保存sensor指针,初始化D-PHY
↓
[所有子设备匹配完成]
├─ 调用complete回调
│ ├─ rkisp1_create_links() - 建立media连接
│ └─ v4l2_device_register_subdev_nodes() ← 实际创建设备节点
│ └─ 遍历子设备,检查V4L2_SUBDEV_FL_HAS_DEVNODE标志
│ └─ 调用video_register_device()
│ └─ 创建/dev/v4l-subdev2 ✓
└─ 系统就绪
核心问题解答
Q: 哪一行代码创建了设备节点?
A: 两阶段过程
阶段1 - OV13855 probe(预约):
c
// drivers/media/i2c/ov13855.c 第108行
sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
这一行设置标志,告诉框架"需要设备节点"。
阶段2 - ISP complete回调(实际创建):
c
// drivers/staging/media/rkisp1/rkisp1-dev.c complete回调中
ret = v4l2_device_register_subdev_nodes(&rkisp1->v4l2_dev);
这一行遍历所有子设备,为带HAS_DEVNODE
标志的子设备创建/dev/v4l-subdevX
。
节点创建的物理位置 :
/dev/v4l-subdev2
→ 字符设备 → 主设备号81,次设备号由内核分配
总结
probe函数按三大任务清晰组织:
任务一:硬件资源准备
- 获取资源句柄 → 按上电时序控制硬件 → 验证ID
任务二:V4L2框架集成
- 初始化subdev → 创建控制器 → 设置ops → 配置标志位和media entity
任务三:异步框架注册
- 注册到async框架 → 等待ISP匹配 → complete回调创建节点
设备节点创建的关键 :
probe中设置V4L2_SUBDEV_FL_HAS_DEVNODE
标志,ISP complete回调中调用v4l2_device_register_subdev_nodes()
实际创建。这是一个"预约-兑现"的两阶段机制。