当前位置: 首页 > news >正文

完善IMX415驱动回调函数

1.v4l2子设备操作集

1.1 v4l2_subdev_pad_ops

(1)imx415_enum_mbus_code:

枚举(List/Enumerate)该传感器支持的媒体总线格式编码(Media Bus Pixel Codes)。当应用层或上游驱动(如 ISP、MIPI CSI-2 接收器)询问“这个摄像头支持哪些图像输出格式”时,就会调用这个函数。

假设supported_modes为:

索引 (index)分辨率 (width x height)总线格式 (bus_fmt)最大帧率 (max_fps)寄存器配置表
03840 x 2160 (4K)RAW 10-bit30 fps&imx415_4k_raw10_regs
13840 x 2160 (4K)RAW 12-bit20 fps&imx415_4k_raw12_regs
21920 x 1080 (1080P)RAW 10-bit60 fps&imx415_1080p_raw10_regs
static int imx415_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) { struct imx415 *imx415 = to_imx415(sd); if (code->index >= imx415->cfg_num) return -EINVAL; code->code = imx415->supported_modes[code->index].bus_fmt; return 0; }

参数:

struct v4l2_subdev *sd:指向当前 V4L2 子设备的结构体指针,代表当前的 IMX415 传感器实体。 struct v4l2_subdev_state *sd_state:子设备的状态(在较新的内核中用于管理 pad、stream 的配置和格式),在这个具体的实现中暂时没有被直接使用。 struct v4l2_subdev_mbus_code_enum *code:这是一个输入/输出型的结构体。 输入:code->index(框架或用户空间传入的索引号,从 0 开始递增) 输出:code->code(驱动需要填入该索引对应的媒体总线编码,例MEDIA_BUS_FMT_SRGGB10_1X10 )。
struct imx415 *imx415 = to_imx415(sd);
static inline struct imx415 *to_imx415(struct v4l2_subdev *_sd) { return container_of(_sd, struct imx415, sd); }

作用:通过 V4L2 的通用子设备指针sd,反向推导出 IMX415 驱动特有的私有数据结构体指针imx415

inline关键字在 C/C++ 中用于定义内联函数,其主要目的是通过将函数代码直接插入到调用位置来减少函数调用的开销,从而提高运行效率。内联函数适合短小、频繁调用的函数,相比宏更安全,但可能会增大代码体积。

code->code = imx415->supported_modes[code->index].bus_fmt;

作用:如果索引合法,驱动就会从它内部维护的模式数组supported_modes中,根据当前的index找到对应的模式,并将其中的媒体总线格式bus_fmt(例如 RAW10, RAW12 等 Bayer 格式编码)赋值给code->code

问:为什么不用一个 switch case 直接返回,而是要去查 supported_modes 表?

答:如果后续产品升级,要增加对 HDR 模式或不同像素格式(比如从 RAW10 升级到 RAW12)的支持,我们只需要在supported_modes数组里增加一项,并修改cfg_num的大小。这个enum_mbus_code函数本身一行代码都不用改! 这种将“业务逻辑”与“数据”分离的方式,是内核驱动低耦合设计的典范。

(2)imx415_enum_frame_sizes

根据指定的媒体总线编码(图像格式),枚举该图像传感器所支持的图像分辨率(帧大小/Frame Sizes)。当上层应用或 ISP 想要知道“当传感器输出某种格式时,支持哪些分辨率(如 4K, 1080P)”时,就会调用此函数。

static int imx415_enum_frame_sizes(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_size_enum *fse) { struct imx415 *imx415 = to_imx415(sd); if (fse->index >= imx415->cfg_num) return -EINVAL; if (fse->code != imx415->supported_modes[fse->index].bus_fmt) return -EINVAL; fse->min_width = imx415->supported_modes[fse->index].width; fse->max_width = imx415->supported_modes[fse->index].width; fse->max_height = imx415->supported_modes[fse->index].height; fse->min_height = imx415->supported_modes[fse->index].height; return 0; }
struct v4l2_subdev_frame_size_enum *fse:这是核心的输入/输出结构体。 输入:fse->index(分辨率索引)和 fse->code(特定的媒体总线编码,即图像格式)。 输出:fse->min_width, fse->max_width, fse->min_height, fse->max_height(用于描述支持的分辨率范围或固定分辨率)。

enum_mbus_code是格式枚举,enum_frame_sizes是分辨率枚举,比如系统说:“我已经决定要用 RAW12 格式了 (fse->code = RAW12),请问在这个格式下,你支持哪些分辨率?”

驱动中的函数只会求出一个格式下的分辨率,但是上层是index++的while循环的,所以是枚举。

(3)imx415_get_fmt

获取(读取)传感器当前的图像格式、分辨率等配置信息。当上层应用(如抓图软件)或 ISP 驱动想要知道“摄像头现在正处于什么工作模式(如 4K 还是 1080P?RAW10 还是 RAW12?)”时,就会调用这个函数。

static int imx415_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { struct imx415 *imx415 = to_imx415(sd); const struct imx415_mode *mode = imx415->cur_mode; mutex_lock(&imx415->mutex); if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API fmt->format = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); #else mutex_unlock(&imx415->mutex); return -ENOTTY; #endif } else { fmt->format.width = mode->width; fmt->format.height = mode->height; fmt->format.code = mode->bus_fmt; fmt->format.field = V4L2_FIELD_NONE; if (fmt->pad < PAD_MAX && mode->hdr_mode != NO_HDR) fmt->reserved[0] = mode->vc[fmt->pad]; else fmt->reserved[0] = mode->vc[PAD0]; } mutex_unlock(&imx415->mutex); return 0; }

fmt和mode的关系与区别:

mode是驱动内部维护的硬件参数集,它直接映射了图像传感器(如 IMX415)的物理硬件极限。 摄像头芯片需要改写内部成百上千个寄存器才能改变一次工作状态。因此,驱动开发者会把这些状态打包成一个个固定的mode

fmt是 V4L2框架标准定义的通用结构体(struct v4l2_subdev_format),它是摄像头对外的“共同语言”。

特性fmt (格式)mode (模式)
本质V4L2 框架层面的标准数据结构驱动内部自定义的硬件配置集合
可见性对外公开(应用程序和系统直接可见)对内隐藏(一般只在驱动源码内部使用)
关注点图像长宽是多少?数据打包格式是什么?硬件寄存器怎么配?时钟跑多快?支持多少帧率?
灵活性理论上可以传入任意分辨率进行协商测试只有固定的几个(如 4K@30, 1080P@60)
举例3840x2160,MEDIA_BUS_FMT_SRGGB10_1X10imx415_mode_4k_30fps(含上百行寄存器参数)

(4)imx415_set_fmt

设置传感器的图像格式与分辨率。当用户层或 ISP 驱动说“我要把摄像头改成 1080P 分辨率”时,就会调用此函数。驱动在这里不仅要完成fmtmode的匹配,还要动态计算并更新一大堆与时序(Blanking)和时钟(Pixel Rate)相关的控制参数。

static int imx415_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { struct imx415 *imx415 = to_imx415(sd); const struct imx415_mode *mode; s64 h_blank, vblank_def, vblank_min; u64 pixel_rate = 0; u8 lanes = imx415->bus_cfg.bus.mipi_csi2.num_data_lanes; mutex_lock(&imx415->mutex); mode = imx415_find_best_fit(imx415, fmt); fmt->format.code = mode->bus_fmt; fmt->format.width = mode->width; fmt->format.height = mode->height; fmt->format.field = V4L2_FIELD_NONE; if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { #ifdef CONFIG_VIDEO_V4L2_SUBDEV_API *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; #else mutex_unlock(&imx415->mutex); return -ENOTTY; #endif } else { imx415_change_mode(imx415, mode); h_blank = mode->hts_def - mode->width; __v4l2_ctrl_modify_range(imx415->hblank, h_blank, h_blank, 1, h_blank); vblank_def = mode->vts_def - mode->height; /* VMAX >= (PIX_VWIDTH / 2) + 46 = height + 46 */ vblank_min = (mode->height + 46) - mode->height; __v4l2_ctrl_modify_range(imx415->vblank, vblank_min, IMX415_VTS_MAX - mode->height, 1, vblank_def); __v4l2_ctrl_s_ctrl(imx415->vblank, vblank_def); __v4l2_ctrl_s_ctrl(imx415->link_freq, mode->mipi_freq_idx); pixel_rate = (u32)link_freq_items[mode->mipi_freq_idx] / mode->bpp * 2 * lanes; __v4l2_ctrl_s_ctrl_int64(imx415->pixel_rate, pixel_rate); } dev_info(&imx415->client->dev, "%s: mode->mipi_freq_idx(%d)", __func__, mode->mipi_freq_idx); mutex_unlock(&imx415->mutex); return 0; }

上面的get_fmt是根据mode获取fmt,现在的set_fmt就是根据用户传入的fmt来设置mode。

这段代码做的事情可以总结为:

imx415_set_fmt表面上是在“设置一个长宽格式”,其实在 ACTIVE 分支下,它在幕后帮硬件拉动了一整条多米诺骨牌

  1. 改格式:把用户要的分辨率对齐成硬件支持的mode

  2. 算时序:重新计算这一档模式下的水平消隐、垂直消隐限制,防止硬件时序冲突。

  3. 调时钟:根据当前的物理带宽、通道数和图像位深,算出精准的MIPI 链路频率像素速率并通知系统。

(5)imx415_get_selection

向系统报告传感器的“裁剪/窗口(Crop/Selection)”边界信息。当上层应用或 ISP 想要知道“这个摄像头当前输出的图像是从多大的原始像素区域中裁剪出来的,它的起点坐标和宽高是多少”时,就会调用这个函数。

为什么需要这个函数?

你可能买到的 IMX415 摄像头号称是4K(3840x2160)或者是1080P(1920x1080)的。但实际上,IMX415 芯片内部的物理像素矩阵为了留出边缘光屏蔽区和对齐缓冲,它的全尺寸其实是3864x2192(或者 1080P 模式下硬件输出1944x1096)。

如果直接把 3864x2192 的图送给后端,由于长宽不是标准的 16:9 比例,普通的显示器或标准的 4K/1080P ISP 编码器就无法处理。

因此,驱动必须告诉系统:如何从硬件输出的略大尺寸中,居中裁剪出标准的 4K 或 1080P 图像

static int imx415_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_selection *sel) { struct imx415 *imx415 = to_imx415(sd); if (sel->target == V4L2_SEL_TGT_CROP_BOUNDS) { if (imx415->cur_mode->width == 3864) { sel->r.left = CROP_START(imx415->cur_mode->width, DST_WIDTH_3840); sel->r.width = DST_WIDTH_3840; sel->r.top = CROP_START(imx415->cur_mode->height, DST_HEIGHT_2160); sel->r.height = DST_HEIGHT_2160; } else if (imx415->cur_mode->width == 1944) { sel->r.left = CROP_START(imx415->cur_mode->width, DST_WIDTH_1920); sel->r.width = DST_WIDTH_1920; sel->r.top = CROP_START(imx415->cur_mode->height, DST_HEIGHT_1080); sel->r.height = DST_HEIGHT_1080; } else { sel->r.left = CROP_START(imx415->cur_mode->width, imx415->cur_mode->width); sel->r.width = imx415->cur_mode->width; sel->r.top = CROP_START(imx415->cur_mode->height, imx415->cur_mode->height); sel->r.height = imx415->cur_mode->height; } return 0; } return -EINVAL; }
  • 背景:当传感器硬件实际吐出3864宽度的图像时。

  • 裁剪行为:驱动要求最终对外输出的有效宽度sel->r.width3840,有效高度sel->r.height2160(标准的 4K UHD)。

  • 起点坐标计算CROP_START通常是一个宏,用来计算居中裁剪的起始 X 坐标(left)和 Y 坐标(top)。

    其数学公式通常为:

​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​

  • 这意味着,告诉 ISP 驱动:“请从第 $(12, 16)$ 个像素开始,切出一个 $3840 \times 2160$ 的标准 4K 矩形”。

1.2 v4l2_subdev_video_ops

static const struct v4l2_subdev_video_ops imx415_video_ops = { .s_stream = imx415_s_stream, .g_frame_interval = imx415_g_frame_interval, };

此操作集负责视频流相关操作,最主要的就是开关视频流。

imx415_s_stream

static int imx415_s_stream(struct v4l2_subdev *sd, int on) { struct imx415 *imx415 = to_imx415(sd); struct i2c_client *client = imx415->client; int ret = 0; dev_info(&imx415->client->dev, "s_stream: %d. %dx%d, hdr: %d, bpp: %d\n", on, imx415->cur_mode->width, imx415->cur_mode->height, imx415->cur_mode->hdr_mode, imx415->cur_mode->bpp); mutex_lock(&imx415->mutex); on = !!on; if (on == imx415->streaming) goto unlock_and_return; if (on) { if (imx415->is_thunderboot && rkisp_tb_get_state() == RKISP_TB_NG) { imx415->is_thunderboot = false; __imx415_power_on(imx415); } ret = pm_runtime_get_sync(&client->dev); if (ret < 0) { pm_runtime_put_noidle(&client->dev); goto unlock_and_return; } ret = __imx415_start_stream(imx415); if (ret) { v4l2_err(sd, "start stream failed while write regs\n"); pm_runtime_put(&client->dev); goto unlock_and_return; } } else { __imx415_stop_stream(imx415); pm_runtime_put(&client->dev); } imx415->streaming = on; unlock_and_return: mutex_unlock(&imx415->mutex); return ret; }

对于正常的开流启动:先做pm_runtime_get_sync(&client->dev)进行上电设备唤醒,再做__imx415_start_stream(imx415)进行启动流。

__imx415_start_stream(imx415)代码如下:

作用:完成 IMX415 开流前的寄存器初始化、控制参数同步、HDR 初始曝光配置,最后退出待机状态,让传感器开始输出图像。

static int __imx415_start_stream(struct imx415 *imx415) { int ret; if (!imx415->is_thunderboot) { ret = imx415_write_array(imx415->client, imx415->cur_mode->global_reg_list); if (ret) return ret; ret = imx415_write_array(imx415->client, imx415->cur_mode->reg_list); if (ret) return ret; } imx415_get_pclk_and_tline(imx415); /* In case these controls are set before streaming */ ret = __v4l2_ctrl_handler_setup(&imx415->ctrl_handler); if (ret) return ret; if (imx415->has_init_exp && imx415->cur_mode->hdr_mode != NO_HDR) { imx415->rhs1_old = IMX415_RHS1_DEFAULT; imx415->rhs2_old = IMX415_RHS2_DEFAULT; ret = imx415_ioctl(&imx415->subdev, PREISP_CMD_SET_HDRAE_EXP, &imx415->init_hdrae_exp); if (ret) { dev_err(&imx415->client->dev, "init exp fail in hdr mode\n"); return ret; } } return imx415_write_reg(imx415->client, IMX415_REG_CTRL_MODE, IMX415_REG_VALUE_08BIT, 0); }

函数执行顺序:

判断是否为 Thunderboot │ ├─ 普通启动 │ ├─ 写全局初始化寄存器 │ └─ 写当前分辨率/帧率模式寄存器 │ └─ Thunderboot └─ 跳过重复初始化 │ 计算像素时钟和单行曝光时间 │ 同步 V4L2 控件到传感器寄存器 │ 如果是 HDR 模式,写入初始 HDR 曝光参数 │ 写 CTRL_MODE = 0,退出待机,开始输出图像

(1)写全局初始化寄存器和模式寄存器

(2)计算像素时钟频率和单行曝光时间

计算出当前模式下的——pclk(像素时钟频率)tline(传输一行图像所需的时间)

static void imx415_get_pclk_and_tline(struct imx415 *imx415) { const struct imx415_mode *mode = imx415->cur_mode; imx415->pclk = (u32)div_u64((u64)mode->hts_def * mode->vts_def * mode->max_fps.denominator, mode->max_fps.numerator); imx415->tline = (u32)div_u64((u64)mode->hts_def * 1000000000, imx415->pclk); }

在看公式前,必须先了解以下传感器时序名词:

hts_def (Horizontal Total Size):水平总尺寸。指 Sensor 内部读出一行数据物理上所需的总像素时钟数(包含有效像素和水平消隐区)。

vts_def (Vertical Total Size):垂直总尺寸。指 Sensor 读出一帧数据物理上所需的总行数(包含有效行和垂直消隐区)。

max_fps (Max Frames Per Second):该模式下的最大帧率。它由两个整数组成:numerator(分子)和 denominator(分母)。例如 30 fps 通常表示为 30/1,即分子 30,分母 1。

公式1(计算pclk)

  • 传感器吐出一帧完整的图像,物理上需要扫描的总像素点数就是:

  • 如果一秒钟要吐出 $FPS$ 帧图像,那么一秒钟内 Sensor 物理上总共要扫描处理的像素点数就是:

  • pclk的本质就是像素时钟频率(单位:Hz),即“一秒钟内处理多少个像素点”。驱动通过这个乘法反向推算出当前分辨率下的物理像素时钟。

公式2(计算tline):

  • 既然pclk代表一秒钟(即 $10^{9}$ 纳秒)内可以跑多少个像素时钟。

  • 那么跑完一行所需的像素时钟数(hts_def)需要消耗多少纳秒呢?

  • 答案显而易见:用一行的总时钟数hts_def除以一秒钟的总时钟数pclk,就能得到传输一行图像所需的绝对时间(单位:纳秒 ns)

为什么要大费周折算这两个值?

答:

这两个值算出来后,并不会直接写进 IMX415 的寄存器,而是提供给 Linux 内核及后端的 AE(自动曝光)算法使用的

  • 精准控制曝光时间

    在用户层或者 AE 算法看来,曝光时间是以“微秒”或“毫秒”为单位的。但 IMX415 芯片非常“笨”,它内部的曝光控制寄存器只认“行数(Lines)”。

    当系统算法说:“我想曝光 10ms (10,000,000ns)”。

    驱动就会去计算:

    曝光寄存器应填写的行数 = 1000000ns / tline​​​​​​​

    有了tline这个严丝合缝的“时间面额”,驱动才能把上层的时间单位精准转换为底层的硬件行数寄存器值。

(3)同步V4L2控件

目的:

用户可能在开流之前就调用了:设置曝光,设置增益,设置垂直消隐等

但那时传感器可能:

  • 尚未上电;
  • 尚未完成寄存器初始化;
  • 处于 runtime suspend;
  • 还没有进入可写寄存器的状态。

因此这些控制值虽然已经保存在 V4L2 控件框架中,却不一定已经真正写入传感器。

__v4l2_ctrl_handler_setup()

会遍历imx415->ctrl_handler中注册的所有控制项,并调用控制的回调函数:

static const struct v4l2_ctrl_ops imx415_ctrl_ops = { .s_ctrl = imx415_set_ctrl, };

调用关系大概为:

__v4l2_ctrl_handler_setup() │ ├─ exposure 控件 │ └─ imx415_set_ctrl() │ └─ 写曝光寄存器 │ ├─ analogue gain 控件 │ └─ imx415_set_ctrl() │ └─ 写增益寄存器 │ ├─ vblank 控件 │ └─ imx415_set_ctrl() │ └─ 写 VMAX │ └─ test pattern 控件 └─ imx415_set_ctrl() └─ 写测试图寄存器

(4)判断是否需要写入 HDR 初始曝光

首先判断是否需要写入HDR初始曝光:

if (imx415->has_init_exp && imx415->cur_mode->hdr_mode != NO_HDR) {

如果需要则初始化HDR曝光边界:

imx415->rhs1_old = IMX415_RHS1_DEFAULT; imx415->rhs2_old = IMX415_RHS2_DEFAULT;

(5)通过私有 ioctl 写入 HDR AE 参数

(6)写控制模式寄存器正式开流

return imx415_write_reg(imx415->client, IMX415_REG_CTRL_MODE, IMX415_REG_VALUE_08BIT, 0);

1.3 v4l2_subdev_core_ops

static const struct v4l2_subdev_core_ops imx415_core_ops = { .s_power = imx415_s_power, .ioctl = imx415_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl32 = imx415_compat_ioctl32, #endif };

(1)imx415_s_power

应用层接口上电/下电,最终还是调用__imx415_power_on()电路真正接通电源;

http://www.cnnetsun.cn/news/3286491.html

相关文章:

  • Llama-3.3-70B-Instruct-MXFP4-Preview核心技术解密:AutoSmoothQuant算法如何平衡性能与精度
  • VSCode远程编码叠甲kimicc智能编码
  • 当你的Windows开始“思考“:如何用Win11Debloat重新掌控你的电脑
  • 英雄联盟对局先知:选人阶段精准识别队友实力,轻松判断谁是上等马
  • 技术视角下的Syncthing Android:构建去中心化文件同步生态
  • 基于Demucs深度学习模型的人声分离技术实践指南
  • 三模块解析:通达信缠论插件的算法实现与实战应用
  • AIGS是新范式 并非等同于AIGC
  • AI学术写作工具横向测评:沁言学术、DeepSeek、知网研学、Zotero 哪家更香?
  • 基于MKV58F1M0VLQ24与PAM8904的可编程报警系统设计
  • 从零开始掌握ppInk:5个技巧让你的Windows屏幕标注更高效
  • OpCore Simplify:三分钟完成黑苹果OpenCore EFI配置的智能工具
  • 如何用Sports实现足球比赛智能分析:完整实战指南
  • 神级 Cos 出圈!韩国 Coser 完美还原《剑星》伊芙,冷峻科幻气质拉满
  • Docker一键部署PSWD密码生成器:打造你的本地安全密码工厂
  • WaveTools:终极鸣潮游戏性能优化工具箱完整指南
  • 3个核心优化技巧:如何用Ryujinx让Switch游戏在PC上流畅运行
  • SSM框架+MySQL 8.0 网上书店系统:3层架构设计与5大核心模块实现
  • python 基础类json和csv
  • 开源项目推荐:bert-loves-chemistry
  • Windows 11系统优化终极指南:使用Win11Debloat实现专业级性能调优
  • 学习C++之命名空间
  • macOS Xbox控制器驱动深度解析:从系统内核到游戏体验的完整实战指南
  • MateCloud成本优化:微服务架构下的云资源使用成本控制策略
  • 163MusicLyrics:如何高效获取网易云和QQ音乐歌词的终极方案
  • 苹果起诉 OpenAI:前员工为其窃取商业机密,诉讼聚焦硬件业务机密窃取
  • 自动驾驶数据集大全:Awesome Self-Driving Car推荐的10个关键数据集
  • OpenCore Legacy Patcher完整指南:3步让老旧Mac运行最新macOS系统
  • Prompt 模板版本化管理:从字符串散落到可追溯的工程体系
  • Ultra-Fast-Lane-Detection-v2快速入门:10分钟搭建车道线检测系统