Linux-Sensor驱动移植与调试(转载)

转自:https://mp.weixin.qq.com/s/ev4h7m4fhww36nBh_cJqBA?mpshare=1&scene=1&srcid=0418wyFa1e9PoUlkarIZ1FI0&sharer_shareinfo=2f1ea6c56a76b34c5596c483e2e018b5&sharer_shareinfo_first=2f1ea6c56a76b34c5596c483e2e018b5&color_scheme=light#rd

在嵌入式 Linux 平台进行图像传感器驱动移植与调试,通常包括以下几个关键步骤:

  1. 硬件上电与复位时序配置

    依据传感器 datasheet 时序要求,合理控制 vdd、reset、powerdown 及 clk 等信号的上下电顺序,确保传感器可靠启动。

  2. 传感器寄存器初始化

    配置 sensor 内部寄存器,设定所需的分辨率、像素格式、数据输出模式等基本图像参数

  3. 实现 V4L2 子设备操作集

    编写 struct v4l2_subdev_ops 中必要的回调函数,通常包括:

    • set_fmt

      :设置图像格式

    • get_fmt

      :获取当前图像格式

    • s_stream

      :启动/停止视频流

    • s_power

      :设备电源管理

  4. 扩展 V4L2 控制接口

    通过 **v4l2_ctrl_handler**为驱动添加控制项,支持动态调整 fps、曝光时间、增益、测试图等参数

  5. 驱动探测与设备注册

    probe() 函数中完成传感器检测、资源分配,并通过 Media Controller 框架注册子设备构建与上层视频设备节点的连接

  6. 寄存器配置方式

    一般I2C协议为主


来个实际的demo移植参考:rk3566移植豪威的OX05B1S到RK3562

先依葫芦画瓢

但编译过程报错:

修改后:

继续修复驱动,编译内核ok后即可,烧录到新的主板上面看看

接着扩展一下驱动代码的解释,重点是实现 V4L2 子设备代码操作:

复制代码
static const struct v4l2_subdev_pad_ops ox05b1s_pad_ops = {
	.enum_mbus_code = ox05b1s_enum_mbus_code,
	.enum_frame_size = ox05b1s_enum_frame_sizes,
	.enum_frame_interval = ox05b1s_enum_frame_interval,
	.get_fmt = ox05b1s_get_fmt,
	.set_fmt = ox05b1s_set_fmt,
	.get_mbus_config = ox05b1s_g_mbus_config,
};

内核的V4L2配置

复制代码
#
# Video4Linux options
#
CONFIG_VIDEO_V4L2=y
CONFIG_VIDEO_V4L2_I2C=y
CONFIG_VIDEO_V4L2_SUBDEV_API=y
# CONFIG_VIDEO_ADV_DEBUG is not set
# CONFIG_VIDEO_FIXED_MINOR_RANGES is not set
CONFIG_V4L2_MEM2MEM_DEV=y
CONFIG_V4L2_FWNODE=y
# end of Video4Linux options
  1. 编写 struct v4l2_subdev_ops 中必要的回调函数,通常包括:

    • set_fmt

      :设置图像格式

    • get_fmt

      :获取当前图像格式

    • s_stream

      :启动/停止视频流

    • s_power

      :设备电源管理

1. .set_fmt = ox05b1s_set_fmt, //设置图像格式

复制代码
static int ox05b1s_set_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_pad_config *cfg,
			  struct v4l2_subdev_format *fmt)
{
	struct ox05b1s *ox05b1s = to_ox05b1s(sd);
	const struct ox05b1s_mode *mode;
	s64 h_blank, vblank_def;

	mutex_lock(&ox05b1s->mutex);//加入互斥锁,禁止操作期间被其他函数操作

	mode = ox05b1s_find_best_fit(fmt);
	fmt->format.code = PIX_FORMAT;
	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, cfg, fmt->pad) = fmt->format;
#else
		mutex_unlock(&ox05b1s->mutex);
		return -ENOTTY;
#endif
	} else {
		ox05b1s->cur_mode = mode;
		h_blank = mode->hts_def - mode->width;
		__v4l2_ctrl_modify_range(ox05b1s->hblank, h_blank,
					 h_blank, 1, h_blank);
		vblank_def = mode->vts_def - mode->height;
		__v4l2_ctrl_modify_range(ox05b1s->vblank, vblank_def,
					 OX05B1S_VTS_MAX - mode->height,
					 1, vblank_def);
	}

	mutex_unlock(&ox05b1s->mutex); //释放互斥锁,把资源可以给到其他API操作

	return 0;
}

2. .get_fmt = ox05b1s_get_fmt, //获取当前图像格式

复制代码
static int ox05b1s_get_fmt(struct v4l2_subdev *sd,
			  struct v4l2_subdev_pad_config *cfg,
			  struct v4l2_subdev_format *fmt)
{
	struct ox05b1s *ox05b1s = to_ox05b1s(sd);
	const struct ox05b1s_mode *mode = ox05b1s->cur_mode;

	mutex_lock(&ox05b1s->mutex);
	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
		fmt->format = *v4l2_subdev_get_try_format(sd, cfg, fmt->pad);
#else
		mutex_unlock(&ox05b1s->mutex);
		return -ENOTTY;
#endif
	} else {
		fmt->format.width = mode->width;
		fmt->format.height = mode->height;
		fmt->format.code = PIX_FORMAT;
		fmt->format.field = V4L2_FIELD_NONE;
	}
	mutex_unlock(&ox05b1s->mutex);

	return 0;
}

篇幅有限就不展开CONFIG_VIDEO_V4L2_SUBDEV_API=y,则走的是

*v4l2_subdev_get_try_format(sd, cfg, fmt->pad);//指针函数

复制代码
static const struct v4l2_subdev_video_ops ox05b1s_video_ops = {
	.s_stream = ox05b1s_s_stream,
	.g_frame_interval = ox05b1s_g_frame_interval,
	//.g_mbus_config = ox05b1s_g_mbus_config,
};

3. .s_stream = ox05b1s_s_stream, //启动/停止视频流

复制代码
static int ox05b1s_s_stream(struct v4l2_subdev *sd, int on)
{
	struct ox05b1s *ox05b1s = to_ox05b1s(sd);
	struct i2c_client *client = ox05b1s->client;
	int ret = 0;

	mutex_lock(&ox05b1s->mutex);
	on = !!on;
	if (on == ox05b1s->streaming)
		goto unlock_and_return;

	if (on) {  //这个布尔on判断视频流的启停
		ret = pm_runtime_get_sync(&client->dev);
		if (ret < 0) {
			pm_runtime_put_noidle(&client->dev);
			goto unlock_and_return;
		}

		ret = __ox05b1s_start_stream(ox05b1s); //开始视频流
		if (ret) {
			v4l2_err(sd, "start stream failed while write regs\n");
			pm_runtime_put(&client->dev);
			goto unlock_and_return;
		}
	} else {
		__ox05b1s_stop_stream(ox05b1s); //停止视频流
		pm_runtime_put(&client->dev);
	}

	ox05b1s->streaming = on;

unlock_and_return:
	mutex_unlock(&ox05b1s->mutex);

	return ret;
}

static const struct v4l2_subdev_core_ops ox05b1s_core_ops = {
	.s_power = ox05b1s_s_power,
	.ioctl = ox05b1s_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl32 = ox05b1s_compat_ioctl32,
#endif
};

4. .s_power = ox05b1s_s_power //设备电源管理

复制代码
static int ox05b1s_s_power(struct v4l2_subdev *sd, int on)
{
	struct ox05b1s *ox05b1s = to_ox05b1s(sd);
	struct i2c_client *client = ox05b1s->client;
	int ret = 0;

	mutex_lock(&ox05b1s->mutex);

	/* If the power state is not modified - no work to do. */
	if (ox05b1s->power_on == !!on)
		goto unlock_and_return;

	if (on) {
		ret = pm_runtime_get_sync(&client->dev);
		if (ret < 0) {
			pm_runtime_put_noidle(&client->dev);
			goto unlock_and_return;
		}
		ret = ox05b1s_write_array(ox05b1s->client, OX05B1S_ARRAY_REG);//上电后,重新初始化
		if (ret) {
			v4l2_err(sd, "could not set init registers\n");
			pm_runtime_put_noidle(&client->dev);
			goto unlock_and_return;
		}
		ox05b1s->power_on = true;
	} else {
		pm_runtime_put(&client->dev);
		ox05b1s->power_on = false;
	}

unlock_and_return:
	mutex_unlock(&ox05b1s->mutex);

	return ret;
}

一、DTS配置

在嵌入式 Linux 设备树(Device Tree)中正确引用 Sensor 驱动,需完成以下关键配置:

  1. 时钟与管脚复用配置

    • 为 Sensor 分配正确的工作时钟,并在 pinmux 节点中配置对应 I/O 口的复用功能,确保信号路径畅通。
  2. 电源与 GPIO 配置

    • 根据原理图,在regulator 节点中定义 Sensor 所需的供电(如 AVDD、DVDD、DOVDD 等)。

    • 声明并配置 reset、powerdown 等控制引脚对应的 GPIO,为上电/复位时序提供硬件支持。

  3. Media Controller 链路连接

    • 在 Sensor 节点下添加 port 子节点,并通过 remote-endpoint 属性与主控的 CIF(Camera Interface)或 ISP(Image Signal Processor)节点建立连接,完成视频流管道的硬件绑定。

Linux DTS语法官网:

https://www.kernel.org/doc/html/latest/devicetree/usage-model.html

  • 二、时序

需特别关注上电时序的设计,不同传感器对此要求差异显著:

  • 大部分型号 :对时序要求相对宽松,只要保证主时钟(mclk)、各档电源(vdd)、复位(reset)与掉电(powerdown) 信号处于有效状态,即可正常进行 I2C 通信并输出图像,无需严格限定上电先后顺序与延时。

  • 部分型号 :存在严格的时序要求,必须严格遵循特定顺序与延时控制,否则无法正常启动或工作。

因此,在驱动实现前,务必查阅 Sensor 原厂提供的数据手册(DataSheet),其中通常会包含明确的上电时序图。开发者只需依据该时序,依次配置各信号即可确保硬件正确初始化。

状态流:

① 一般都是从模式,即从外部接收一个主时钟(MCLK)输入

② 主控SoC的时钟发生器(或外部晶振) → 提供MCLK信号给Sensor → Sensor 内部的PLL以此MCLK为参考,生成其内部处理所需的各种时钟

③ Sensor 在外部MCLK的同步下,将图像数据(通过MIPI CSI/DVP等接口)发送给主控SoC

上电:

① 时序要求 :从第一路电源开始上电,到所有电源完全稳定,整个过程应在 200 毫秒 内完成。

- DVDD(数字核心电源,1.2V) 首先为传感器内部的数字逻辑核心供电,通常标注为DVDD1DVDD2
- OVDD(接口IO电源,1.8V) 接着为传感器的I/O接口(如I2C、MIPI等)供电,通常标注为OVDD。此电压需与主控SoC的I/O电压匹配。
- AVDD(模拟/感光电源,2.9V) 最后为传感器的模拟电路(如像素阵列、ADC等)供电,通常标注为AVDD1AVDD2

② 在摄像头传感器上电后,其内部寄存器的初始状态是未定义的,必须通过系统复位将其恢复至已知的默认值。

具体操作是:在所有电源稳定后,将芯片的硬件复位引脚(XCLR保持低电平至少 500 纳秒,之后将其释放至高电平。此时寄存器将被初始化为默认值,为后续软件配置做好准备。

③ 在摄像头传感器的初始化过程中,系统复位 是通过将复位引脚XCLR从低电平切换为高电平来完成的。复位信号生效后,传感器内部逻辑才可被正常配置。

④ 在系统复位完成之后,需要通过寄存器配置来设置传感器的工作参数。

  • 至此,上电时序算是完成,还需要进行参数配置,这一过程通常通过I2C等串行总线进行,包括设置图像分辨率、输出格式、曝光时间、增益等关键参数(具体可看手册/咨询FAE)。只有完成正确的寄存器配置,传感器才能输出符合预期的图像数据。

下电:


  • 三、调试

1.I²C不通、识别不到设备

  • 排查I²C硬件接线是否OK

  • 上电时序是否正确(可检查驱动日志/直接量一下)

  • 测量电压、i2cdetect、检查 DTS 配置

2.出图异常

- 全黑 -> 镜头盖未摘、曝光时间过短、sensor 未正常工作 -> 检查镜头、曝光寄存器、sensor 电源、检查MCLK

- 全白 -> 曝光过度、增益过大 -> 降低曝光值、检查自动曝光是否生效

- 颜色异常 -> 数据格式不匹配、白平衡错误 -> 确认 YUV/RGB 格式、检查白平衡寄存器

- 条纹/噪点 -> 电源噪声、时钟抖动、MIPI 信号质量差 -> 测量电源纹波、检查 MIPI 等长和阻抗

- 图像撕裂 -> VSYNC/HSYNC 不同步、DMA 溢出 -> 检查时序寄存器、增加 DMA 缓冲区

  • 使用抓图工具,从各个阶段(raw、isp前后、编码前后等等)抓图,排查异常位置,找到异常点

3.帧率低

  • 检查时钟频率MCLK是否足够

  • 确认总线带宽(MIPI 速率)

  • 优化驱动中的数据传输(如使用 DMA 聚集/分散)

  • 调整曝光

  • 查看相关日志、状态先确认是哪个环节导致的帧率低

4.延迟大

  • 减少处理流水线中的缓冲区数量

  • 调整 ISP 处理参数

  • 检查 CPU 负载,避免调度延迟

  • 排查是否为业务层导致

5.其他

  • 一般SOC平台的SDK都可以对工作状态、RAW/YUV图等进行dump,方便分析

针对V4L2的知识扩展一下:


V4L2 (Video For Linux 2) 子设备驱动的典型实现

复制代码
static const struct v4l2_subdev_core_ops ox05b1s_core_ops = {
	.s_power = ox05b1s_s_power,
	.ioctl = ox05b1s_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl32 = ox05b1s_compat_ioctl32,
#endif
};

static const struct v4l2_subdev_video_ops ox05b1s_video_ops = {
	.s_stream = ox05b1s_s_stream,
	.g_frame_interval = ox05b1s_g_frame_interval,
	//.g_mbus_config = ox05b1s_g_mbus_config,
};

static const struct v4l2_subdev_pad_ops ox05b1s_pad_ops = {
	.enum_mbus_code = ox05b1s_enum_mbus_code,
	.enum_frame_size = ox05b1s_enum_frame_sizes,
	.enum_frame_interval = ox05b1s_enum_frame_interval,
	.get_fmt = ox05b1s_get_fmt,
	.set_fmt = ox05b1s_set_fmt,
	.get_mbus_config = ox05b1s_g_mbus_config,
};

static const struct v4l2_subdev_ops ox05b1s_subdev_ops = {
	.core	= &ox05b1s_core_ops,
	.video	= &ox05b1s_video_ops,
	.pad	= &ox05b1s_pad_ops,
};

1. 整体结构层次

复制代码
ox05b1s_subdev_ops
├── .core → ox05b1s_core_ops (核心操作)
├── .video → ox05b1s_video_ops (视频流操作)
└── .pad → ox05b1s_pad_ops (Pad/格式操作)

V4L2 子设备驱动将回调函数按功能分组,便于管理和复用。


2. ox05b1s_core_ops (核心操作)

复制代码
static const struct v4l2_subdev_core_ops ox05b1s_core_ops = {
    .s_power = ox05b1s_s_power, //控制子设备的电源状态(如开启/关闭传感器)
    .ioctl = ox05b1s_ioctl, //扩展私有控制命令(如读取传感器寄存器、调试信息)
#ifdef CONFIG_COMPAT
    .compat_ioctl32 = ox05b1s_compat_ioctl32, //兼容32bit的系统接口
#endif
};
成员 作用
.s_power 控制子设备的电源状态(如开启/关闭传感器)。上层调用 v4l2_subdev_call(sd, core, s_power, on) 触发。
.ioctl 自定义 IOCTL 命令处理,用于扩展私有控制命令(如读取传感器寄存器、调试信息)。
.compat_ioctl32 在 64 位内核上处理 32 位用户空间的 IOCTL,确保兼容性(需要 CONFIG_COMPAT 宏)。

3. ox05b1s_video_ops (视频操作)

复制代码
static const struct v4l2_subdev_video_ops ox05b1s_video_ops = {
    .s_stream = ox05b1s_s_stream, //启动/停止视频流,应用VIDIOC_STREAMON/STREAMOFF 触发
    .g_frame_interval = ox05b1s_g_frame_interval, //获取当前帧间隔,结合规格书
    //.g_mbus_config = ox05b1s_g_mbus_config,   // 被注释
};
成员 作用
.s_stream 启动/停止视频流 。传感器在此函数中配置寄存器开始输出 MIPI/并行数据。调用时机:应用通过 VIDIOC_STREAMON / VIDIOC_STREAMOFF 触发。
.g_frame_interval 获取当前帧间隔(即帧率)。返回结构如 { numerator = 1, denominator = 30 } 表示 30fps。
.g_mbus_config (注释) 获取媒体总线配置(数据 lane 数、时钟极性等)。被注释可能因为该传感器不需要动态查询,或改用 .get_mbus_config(见下面 pad ops)。

4. ox05b1s_pad_ops (Pad 操作)

复制代码
static const struct v4l2_subdev_pad_ops ox05b1s_pad_ops = {
    .enum_mbus_code       = ox05b1s_enum_mbus_code, //枚举像素格式
    .enum_frame_size      = ox05b1s_enum_frame_sizes, //枚举像素大小
    .enum_frame_interval  = ox05b1s_enum_frame_interval, //枚举在特定分辨率下支持的帧率
    .get_fmt              = ox05b1s_get_fmt,
    .set_fmt              = ox05b1s_set_fmt,
    .get_mbus_config      = ox05b1s_g_mbus_config,
};

这是 最核心 的格式协商部分,对应 V4L2 的 Media Controller + Pad 架构。

成员 作用
.enum_mbus_code 枚举传感器支持的 媒体总线像素格式 (如 MEDIA_BUS_FMT_SBGGR10_1X10 等)。
.enum_frame_size 枚举支持的 分辨率 (如 1920x1080, 1280x720)。用户程序通过 VIDIOC_ENUM_FRAMESIZES 调用。
.enum_frame_interval 枚举在特定分辨率下支持的 帧率(如 30fps, 15fps)。
.get_fmt 获取当前 Pad 上设置的 格式(包括分辨率、像素格式、颜色空间等)。
.set_fmt 设置新的格式,通常还会返回硬件实际调整后的格式(因为传感器可能有对齐或步长限制)。
.get_mbus_config 获取当前 硬件总线配置(如 MIPI CSI-2 lane 数、连续时钟还是非连续时钟等),用于上层或桥接驱动正确配置接收端。

5. ox05b1s_subdev_ops (顶层操作集合)

复制代码
static const struct v4l2_subdev_ops ox05b1s_subdev_ops = {
    .core   = &ox05b1s_core_ops,
    .video  = &ox05b1s_video_ops,
    .pad    = &ox05b1s_pad_ops,
};

这是注册到 V4L2 子设备框架时的 主结构体,包含上述三类操作。当驱动调用:

复制代码
v4l2_i2c_subdev_init(&sensor->sd, client, &ox05b1s_subdev_ops);//参考下面的probe函数

就将这些回调注册到系统中,后续上层可通过 v4l2_subdev_call 或直接调用相应函数。

复制代码
static int ox05b1s_probe(struct i2c_client *client,
			const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
	struct device_node *node = dev->of_node;
	struct ox05b1s *ox05b1s;
	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);

	ox05b1s = devm_kzalloc(dev, sizeof(*ox05b1s), GFP_KERNEL);
	if (!ox05b1s)
		return -ENOMEM;

	ret = of_property_read_u32(node, RKMODULE_CAMERA_MODULE_INDEX,
				   &ox05b1s->module_index);
	
.....

	if (ret) {
		dev_err(dev, "could not get module information!\n");
		return -EINVAL;
	}

	ox05b1s->client = client;
	ox05b1s->cur_mode = &supported_modes[0];
	ox05b1s->is_thunderboot = IS_ENABLED(CONFIG_VIDEO_ROCKCHIP_THUNDER_BOOT_ISP);

	ox05b1s->xvclk = devm_clk_get(dev, "xvclk");
	if (IS_ERR(ox05b1s->xvclk)) {
		dev_err(dev, "Failed to get xvclk\n");
		return -EINVAL;
	}

	ox05b1s->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
	if (IS_ERR(ox05b1s->reset_gpio))
		dev_warn(dev, "Failed to get reset-gpios\n");

....

	ret = ox05b1s_configure_regulators(ox05b1s);
	if (ret) {
		dev_err(dev, "Failed to get power regulators\n");
		return ret;
	}

	ox05b1s->pinctrl = devm_pinctrl_get(dev);
	if (!IS_ERR(ox05b1s->pinctrl)) {
		ox05b1s->pins_default =
			pinctrl_lookup_state(ox05b1s->pinctrl,
					     OF_CAMERA_PINCTRL_STATE_DEFAULT);
		if (IS_ERR(ox05b1s->pins_default))
			dev_err(dev, "could not get default pinstate\n");

		ox05b1s->pins_sleep =
			pinctrl_lookup_state(ox05b1s->pinctrl,
					     OF_CAMERA_PINCTRL_STATE_SLEEP);
		if (IS_ERR(ox05b1s->pins_sleep))
			dev_err(dev, "could not get sleep pinstate\n");
	} else {
		dev_err(dev, "no pinctrl\n");
	}

	mutex_init(&ox05b1s->mutex);

	sd = &ox05b1s->subdev;
	v4l2_i2c_subdev_init(sd, client, &ox05b1s_subdev_ops);//重点V4L2初始化函数

	ret = ox05b1s_initialize_controls(ox05b1s);//这个也都是重点函数
	if (ret)
		goto err_destroy_mutex;

	ret = __ox05b1s_power_on(ox05b1s); //上电
	if (ret)
		goto err_free_handler;

	ret = ox05b1s_check_sensor_id(ox05b1s, client); //检测是否读到ID
	if (ret)
		goto err_power_off;

#ifdef CONFIG_VIDEO_V4L2_SUBDEV_API
	sd->internal_ops = &ox05b1s_internal_ops;
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
		     V4L2_SUBDEV_FL_HAS_EVENTS;
#endif
#if defined(CONFIG_MEDIA_CONTROLLER)
	ox05b1s->pad.flags = MEDIA_PAD_FL_SOURCE;
	sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
	ret = media_entity_pads_init(&sd->entity, 1, &ox05b1s->pad);
	if (ret < 0)
		goto err_power_off;
#endif

	memset(facing, 0, sizeof(facing));
	if (strcmp(ox05b1s->module_facing, "back") == 0)
		facing[0] = 'b';
	else
		facing[0] = 'f';

	snprintf(sd->name, sizeof(sd->name), "m%02d_%s_%s %s",
		 ox05b1s->module_index, facing,
		 OX05B1S_NAME, dev_name(sd->dev));
	ret = v4l2_async_register_subdev_sensor_common(sd);
	if (ret) {
		dev_err(dev, "v4l2 async register subdev failed\n");
		goto err_clean_entity;
	}

	pm_runtime_set_active(dev);
	pm_runtime_enable(dev);
	pm_runtime_idle(dev);

	return 0;

err_clean_entity:
#if defined(CONFIG_MEDIA_CONTROLLER)
	media_entity_cleanup(&sd->entity);
#endif
err_power_off:
	__ox05b1s_power_off(ox05b1s);
err_free_handler:
	v4l2_ctrl_handler_free(&ox05b1s->ctrl_handler);
err_destroy_mutex:
	mutex_destroy(&ox05b1s->mutex);

	return ret;
}

6. 调用流程示例(从应用到传感器)

  1. 应用程序设置分辨率 → VIDIOC_S_FMT

    → 内核调用 .set_fmt → 传感器驱动配置寄存器。

  2. 应用程序查询帧率 → VIDIOC_G_PARM

    → 内核调用 .g_frame_interval → 返回当前帧率。

  3. 应用程序启动流 → VIDIOC_STREAMON

    → 内核调用 .s_stream(1) → 传感器开始输出图像数据。

  4. 调试或自定义操作 → VIDIOC_DBG_G_REGISTER

    → 内核调用 .ioctl → 读取传感器内部寄存器。


总结

结构体 主要职责
core_ops 电源、自定义控制、兼容性
video_ops 视频流开关、帧率控制
pad_ops 格式协商(分辨率、像素格式、总线配置)
subdev_ops 将上面三个组合成完整的子设备操作集

这种分层设计使得 V4L2 子设备驱动易于扩展,并且与媒体控制器(Media Controller)框架无缝配合。对于 OX05B1S 这种图像传感器,pad_ops 是实现正确图像格式传输的关键。

相关推荐
LCMICRO-133108477462 小时前
长芯微LCMDC8584完全P2P替代ADS8584,是一款16位、4通道同步采样的逐次逼近型(SAR)模数转换器(ADC)
stm32·单片机·嵌入式硬件·fpga开发·硬件工程·模数转换器adc
island13142 小时前
最详细VMware Workstation 17 上安装 Ubuntu 系统
linux·数据库·ubuntu
2401_895521342 小时前
Linux下安装Redis
linux·运维·redis
handler012 小时前
拒绝权限报错!三分钟掌握 Linux 权限管理
linux·c语言·c++·笔记·学习
Wmenghu3 小时前
Ubuntu手动安装jdk;Ubuntu手动安装Maven;Ubuntu手动安装RocketMQ;Ubuntu手动安装RocketMQ-Dashbo
java·linux·ubuntu
SPC的存折4 小时前
10、Docker容器故障排查
linux·运维·数据库·docker·容器
liuyunshengsir4 小时前
linux 下新增用户后无法使用TAB补全功能的最佳解决方法
linux·运维·服务器
书生执笔画浮沉4 小时前
rpmrebuild
linux·centos·rpm
异方辰电子5 小时前
8.原理图为什么看不到具体的电路(比如STM32的晶振等)
stm32·单片机·嵌入式硬件