IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux ARM平台开发系列讲解(摄像头V4L2子系统) 2.12.7 摄像头V4L2驱动开发流程总结 -> 正文阅读

[系统运维]Linux ARM平台开发系列讲解(摄像头V4L2子系统) 2.12.7 摄像头V4L2驱动开发流程总结

1. 概述

前几章对摄像头V4L2驱动开发各个环节做了展开分析,这一章节就做一个汇总,用来回顾前几章得知识点,概括一下摄像头开发得流程,本次使用RK3399

2. Sensor Sub-device 开发移植

Sensor 驱动位于 drivers/media/i2c 目录下,注意到本章节所描述的是具有 media controller 属性的 sensor 驱动, 故 drivers/media/i2c/soc_camera 目录下的驱动并不适用。

Media Controller 结构下, Sensor 一般作为 sub-device 并通过 padcifisp 或者 mipi phy 链接在一起。本章主要介绍 Sensor 驱动的代码[1], dts 配置,及如何验证 sensor 驱动 的正确性。

本章将 Sensor 驱动的开发移植概括为 5 个部分

  • 按照 datasheet 编写上电时序, 主要包括 vddresetpowerdownclk 等。
  • 配置 sensor 的寄存器以输出所需的分辨率、格式。
  • 编写 struct v4l2_subdev_ops 所需要的回调函数,一般包括 set_fmtget_fmtov5695_s_stream
  • 增加 v4l2 controller 用来设置如 fpsexposuregaintest pattern
  • 编写.probe()函数,并添加 media controlv4l2 sub device 初始化代码 作为良好的习惯,完成驱动编码后,也需要增加相应的 Documentation,可以参考 Documentation/devicetree/bindings/media/i2c/。这样板级 dts 可以根据该文档快速配置。

在板级 dts 中,引用 Sensor 驱动,一般需要

  • 配置正确的 clkio mux
  • 根据原理图设置上电时序所需要的 regulatorgpio
  • 增加 port 子节点,与 cif 或者 isp 建立连接

本章以 ov13850 为例,简单分析 Sensor 驱动。

3. 上电时序

不同 Sensor 对上电时序要求不同,例如 OV Camera。可能很大部分的 OV Camera 对时序 要求不严格,只要 mclkvddresetpowerdown 状态是对的,就能正确进行 I2C 通讯并输出 图片。但还是有小部分 Sensor 对上电要求非常严格,例如 OV2685 必须严格按时序上电。 在 Sensor 厂家提供的 DataSheet 中,一般会有上电时序图,只需要按顺序配置即可。以 OV13850 为例,其中__ov13850_power_on()即是用来给 Sensor 上电。如下(有删减)。

static int ov13850_probe(struct i2c_client *client,
			 const struct i2c_device_id *id)
{
	struct device *dev = &client->dev;
	struct device_node *node = dev->of_node;
	struct ov13850 *ov13850;
	struct v4l2_subdev *sd;
	char facing[2];
	int ret;
	if (ov13850_master == ov13850) {
		/* 获取时钟 */
		ov13850->xvclk = devm_clk_get(dev, "xvclk");
		if (IS_ERR(ov13850->xvclk)) {
			dev_err(dev, "Failed to get xvclk\n");
			return -EINVAL;
		}
		/* 获取复位gpio信息 */
		ov13850->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
		if (IS_ERR(ov13850->reset_gpio))
			dev_warn(dev, "Failed to get reset-gpios\n");

		/* 配置电源 */
		ret = ov13850_configure_regulators(dev);
		if (ret) {
			dev_err(dev, "Failed to get power regulators\n");
			return ret;
		}
	/* 配置上电时序 */
	ov13850->pwdn_gpio = devm_gpiod_get(dev, "pwdn", GPIOD_OUT_LOW);
	if (IS_ERR(ov13850->pwdn_gpio))
		dev_warn(dev, "Failed to get pwdn-gpios\n");
	else
		gpiod_set_value_cansleep(ov13850->pwdn_gpio, 0);

	/* 上电 */
	ret = __ov13850_power_on(ov13850);

	return ret;
}

static int __ov13850_power_on(struct ov13850 *ov13850)
{
	int ret;
	u32 delay_us;
	struct device *dev = &ov13850->client->dev;
	struct i2c_client *client = ov13850->client;
	unsigned short addr;

	mutex_lock(&ov13850_power_mutex);
	/* 上电 */
	ret = __ov13850_master_power_on(dev);
	if (ret) {
		dev_err(dev, "could not power on, error %d\n", ret);
		goto err_power;
	}
	/* 等待启动 */
	usleep_range(500, 1000);
	if (!IS_ERR(ov13850->pwdn_gpio))
		gpiod_set_value_cansleep(ov13850->pwdn_gpio, 1);

	/* 8192 cycles prior to first SCCB transaction */
	delay_us = ov13850_cal_delay(8192);
	usleep_range(delay_us, delay_us * 2);

        /* Change i2c address by programming SCCB_ID */
	addr = client->addr;
	if (addr != OV13850_VENDOR_I2C_ADDR) {
		client->addr = OV13850_VENDOR_I2C_ADDR;
		ret = ov13850_write_reg(client, OV13850_REG_SCCB_ID,
					OV13850_REG_VALUE_08BIT,
					addr * 2);
		if (ret) {
			dev_err(dev, "write SCCB_ID failed\n");
			goto err_i2c_addr;
		}
		client->addr = addr;
	}

	mutex_unlock(&ov13850_power_mutex);
	return 0;

err_i2c_addr:
	if (!IS_ERR(ov13850->pwdn_gpio))
		gpiod_set_value_cansleep(ov13850->pwdn_gpio, 0);
	__ov13850_master_power_off(dev);
err_power:
	mutex_unlock(&ov13850_power_mutex);
	return ret;
}

OV13850 的上电时序简要说明如下:

  • 首先提供 xvclk(即 mclk)
  • 紧接着 reset pin 使能
  • 各路的 vdd 上电。这里使用了 ov13850_configure_regulators,里面调用了devm_regulator_bulk_get,因为 vdd, vodd, avdd 三者无严格顺序。如 果 vdd 之间有严格的要求,需要分开处理,可参考 OV2685 驱动代码
  • Vdd 上电后, 取消 Sensor Resetpowerdown 状态。Reset, powerdown 可能只需要一 个,Sensor 封装不同, 可能有差异
  • 最后按时序要求,需要 8192clk cycle 之后,上电才算完成。

注意,虽然不按 datasheet 要求上电许多 Sensor 也能正常工作,但按原厂建议的时序操作, 无疑是最可靠的。

同样,datasheet 中还会有下电时序(Power Down Sequence),也同样按要求即可。

3.1 判断上电时序是否正确

.probe()阶段会去尝试读取 chip id,如 ov13850ov13850_check_sensor_id(),如果能够正确读取到 chip id,一般就认为上电时序正确,Sensor 能够正常进行 i2c 通信。

ret = ov13850_check_sensor_id(ov13850, client);

4. Sensor 初始化寄存器列表

OV13850OV13850 中,各定义了 struct ov13850_modestruct ov13850_mode, 用来表示 sensor 不同的初始化 modeMode 可以包括如分辨率,mbus code,寄存器初始化列 表等。

寄存器初始化列表,请按厂家提供的直接填入即可。需要注意的是, 列表最后用了 REG_NULL 表示结束。

static const struct regval ov13850_2112x1568_regs[] = {
	{0x3612, 0x27},
	{0x370a, 0x26},
	{0x372a, 0x00},
	{0x372f, 0x90},
	{0x3801, 0x08},
	{0x3805, 0x97},
	{0x3807, 0x4b},
	{0x3808, 0x08},
	{0x3809, 0x40},
	{0x380a, 0x06},
	{0x380b, 0x20},
	{0x380c, 0x12},
	{0x380d, 0xc0},
	{0x380e, 0x06},
	{0x380f, 0x80},
	{0x3813, 0x02},
	{0x3814, 0x31},
	{0x3815, 0x31},
	{0x3820, 0x02},
	{0x3821, 0x05},
	{0x3836, 0x08},
	{0x3837, 0x02},
	{0x4601, 0x04},
	{0x4603, 0x00},
	{0x4020, 0x00},
	{0x4021, 0xE4},
	{0x4022, 0x07},
	{0x4023, 0x5F},
	{0x4024, 0x08},
	{0x4025, 0x44},
	{0x4026, 0x08},
	{0x4027, 0x47},
	{0x4603, 0x01},
	{0x5401, 0x61},
	{0x5405, 0x40},
	{REG_NULL, 0x00},
};

static const struct ov13850_mode supported_modes[] = {
	{
		.width = 2112,
		.height = 1568,
		.max_fps = {
			.numerator = 10000,
			.denominator = 300000,
		},
		.exp_def = 0x0600,
		.hts_def = 0x12c0,
		.vts_def = 0x0680,
		.reg_list = ov13850_2112x1568_regs,
	},{
		.width = 4224,
		.height = 3136,
		.max_fps = {
			.numerator = 20000,
			.denominator = 150000,
		},
		.exp_def = 0x0600,
		.hts_def = 0x12c0,
		.vts_def = 0x0d00,
		.reg_list = ov13850_4224x3136_regs,
	},
};


ov13850->cur_mode = &supported_modes[0];

5. V4l2_subdev_ops 回调函数

V4l2_subdev_ops 回调函数是 Sensor 驱动中逻辑控制的核心。回调函数包括非常多的功能, 具体可以查看 kernel 代码 include/media/v4l2-subdev.h。建议 Sensor 驱动至少包括如下回调 函数。

  • .open,这样上层才可以打开/dev/v4l-subdev节点。在上层需要单独对 sensor 设置 v4l control 时,.open()是必须实现的
  • .s_stream,即 set stream,包括 stream onstream off,一般在这里配置寄存器,使其 输出图像
  • .enum_mbus_code,枚举驱动支持的 mbus_code
  • .enum_frame_size,枚举驱动支持的分辨率
  • .get_fmt,返回当前 Sensor 选中的 format/size。如果.get_fmt 缺失,media-ctl 工具无法 查看 sensor entity 当前配置的 fmt
  • .set_fmt,设置 Sensorformat/size 以上回调中, .s_stream stream_on 会比较复杂些。在 ov13850 驱动代码中,它包括 pm_runtime 使能(即唤醒并上电),配置 control 信息(v4l2 control 可能会在 sensor 下电时 配置)即 v4l2_ctrl_handler_setup(),并最终写入寄存器 stream on

以上回调中, .s_stream stream_on 会比较复杂些。在 ov13850 驱动代码中,它包括 pm_runtime 使能(即唤醒并上电),配置 control 信息(v4l2 control 可能会在 sensor 下电时 配置)即 v4l2_ctrl_handler_setup(),并最终写入寄存器 stream on

6. V4l2 controller

对于需要配置 fpsexposure, gain, blanking 的场景,v4l2 controller 部分是必要的。 OV13850 驱动代码中

  • ov13850_initialize_controls(),用来声明支持哪些 control,并设置最大最小值等信息
  • Struct v4l2_ctrl_ops,包含了 ov13850_set_ctrl()回调函数,用以响应上层的设置。

7. Probe 函数及注册 media entity, v4l2 subdev

Probe 函数中, 首先对 dts 进行解析,获取 regulator, gpio, clk 等信息用以对 sensor 上下 电。其次注册 media entity, v4l2 subdev,及 v4l2 controller 信息。注意到 v4l2 subdev 的注 册是异步。如下几个关键的函数调用。

  • v4l2_i2c_subdev_init(), 注册为一个 v4l2 subdev,参数中提供回调函数
  • ov13850_initialize_controls(),初始化 v4l2 controls
  • media_entity_init(),注册成为一个 media entityOV13850仅有一个输出,即 Source Pad
  • v4l2_async_register_subdev(),声明 sensor 需要异步注册。因为 RKISP1CIF 都 采用异步注册 sub device,所以这个调用是必须的。

8. dts 示例 subdev

Sensor 的 dts 配置大同小异,根据硬件的设计,主要是 pinctl(iomux),clk,gpio,以及 remote port。以下示例是在 RK3399-AIO 上的 OV13850 dts 节点。

ov13850: ov13850@36 {
                compatible = "ovti,ov13850";
                status = "okay";
                reg = <0x36>;
                clocks = <&cru SCLK_CIF_OUT>;
                clock-names = "xvclk";

                /* conflict with csi-ctl-gpios */
                reset-gpios = <&gpio0 8 GPIO_ACTIVE_HIGH>;
                pwdn-gpios = <&gpio2 0 GPIO_ACTIVE_HIGH>;

                rockchip,camera-module-index = <0>;
                rockchip,camera-module-facing = "back";
                rockchip,camera-module-name = "CMK-CT0116";
                rockchip,camera-module-lens-name = "Largan-50013A1";

                avdd-supply = <&vcc_mipi>; /* VCC28_MIPI */
                dovdd-supply = <&vcc_mipi>; /* VCC18_MIPI */
                dvdd-supply = <&dvdd_1v2>; /* DVDD_1V2 */

                port {
                        ucam_out0: endpoint {
                                remote-endpoint = <&mipi_in_ucam0>;
                                data-lanes = <1 2>;
                        };
                };
        };

  • Pinctrl,声明了必要的 pinctrl, 该例子中包括了 reset pin 初始化和 clk iomux
  • Clock,指定名称为 xvclk(驱动会讯取名为 xvclkclock),即 24M 时钟
  • Vdd supplyOV13850 需要的三路供电
  • Port 子节点,定义了一个 endpoint,声明需要与 mipi_in_wcam 建立连接。同样地 mipi dphy 会引用 wcam_out
  • Data-lanes 指定了 OV13850 使用两个 lane

9. 使用gstreamer方式预览摄像头 subdev

gst-launch-1.0 v4l2src device=/dev/video0 ! video/x-raw,format=NV12,width=640,height=480, framerate=30/1 ! videoconvert ! kmssink &
  • ov13850出图效果预览:
    在这里插入图片描述

返回总目录

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-07-17 17:01:55  更:2022-07-17 17:04:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 11:51:43-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码