版权声明:本文为博主原创文章,转载请注明出处:https://blog.csdn.net/huang_165/article/details/86217004
之前整理的“rockchip sensor core框架”和rkisp下的v4l2框架有点像,只不过v4l2框架有点大(而且不支持摄像头热插拔)。其实接触越多Linux子系统越发觉得这些子系统处理思想大同小异。
这种"核心思想"就是将"同类设备(soc/外设)的同种属性、内核资源管理"整理出一个"核心层",达到求同存异、松耦合、分层管理的目的。
v4l2简单框图:
从V4L2简单框图可以看出,V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。
一般来说,摄像头驱动需要实现与向核心层提交下面十几个ioctl接口
VIDIOC_REQBUFS:分配内存
VIDIOC_QUERYCAP:查询驱动功能
VIDIOC_ENUM_FMT:获取当前驱动支持的视频格式
VIDIOC_S_FMT:设置当前驱动的频捕获格式
VIDIOC_G_FMT:读取当前驱动的频捕获格式
VIDIOC_TRY_FMT:验证当前驱动的显示格式
VIDIOC_CROPCAP:查询驱动的修剪能力
VIDIOC_S_CROP:设置视频信号的边框
VIDIOC_G_CROP:读取视频信号的边框
VIDIOC_QBUF:把数据放回缓存队列
VIDIOC_DQBUF:把数据从缓存中读取出来
VIDIOC_STREAMON:开始图像捕获
VIDIOC_STREAMOFF:结束图像捕获
摄像头驱动-ov2659源码分析:
先写个断言“核心层和摄像头设备驱动是通过check_camera_id机制来确认具体摄像头driver的”,在《框架分析》证明。
我们分析下:下面的1,2,3步看看应用层、核心层、摄像头如何配合起来的。
1.应用层分配内存--VIDIOC_REQBUFS
2.应用层使能摄像头开始图像捕获--VIDIOC_STREAMON
3.应用层把捕获到的图像从缓存中读取出来--VIDIOC_DQBUF
框架分析:
由于应用层操作的是v4l2设备(/dev/videox),这里选video0(该通道用于抓图像)。
现在,从设备树开始,分析video0是如何构建起来的。
rk3399-linux.dtsi: compatible = "rockchip,rk3399-cif-isp";在驱动目录下查找rockchip,rk3399-cif-isp
在media/platform/rk-isp10/cif_isp10_v4l2.c-->cif_isp10_v4l2_of_match找到。所以,我这个rk3399 sdk版本下摄像头走rk-isp v4l2框架。
cif_isp10_v4l2_drv_probe
-->match = of_match_node(cif_isp10_v4l2_of_match, node); 找到设备树上的cif_isp1: cif_isp@ff920000节点,该节点内容见附录。
-->cif_isp10_create 构建ISP
-->cif_isp10_pltfrm_soc_init 初始化ISP
-->cif_isp10_img_srcs_init 初始化图像源设备(摄像头)
-->cif_isp10_pltfrm_get_img_src_device 查找ISP10下cif接口的图像源设备
-->phandle = of_get_property(node, "rockchip,camera-modules-attached", &size);
//根据该节点内容可知,这里就通过rockchip,camera-modules-attached找到camera4了
-->client = of_find_i2c_device_by_node(camera_list_node);
-->img_src_array[num_cameras] = cif_isp10_img_src_to_img_src(&client->dev, &(cif_isp10_dev->soc_cfg));
-->cif_isp10_img_src_ops[i].ops.to_img_src
-->cif_isp10_img_src_v4l2_i2c_subdev_to_img_src //至此核心层就能和摄像头设备驱动绑定了
-->i2c_get_clientdata
--> v4l2_subdev_call(subdev, core, ioctl, PLTFRM_CIFCAM_ATTACH, (void *)soc_cfg);
-->ov_camera_module_ioctl-->ov_camera_module_init
-->ov_camera_module_attach-->"custom->check_camera_id(cam_mod)"
//将从phandle遍历出来的i2c设备中找到符合条件的client,并将client和当前ISP绑定。
-->cif_isp10_v4l2_register_video_device
-->vdev->ioctl_ops = ioctl_ops; 使用最后一个参数作为和上层交互的ioctl
-->video_register_device 在这里将/dev/videox注册上
-->g_cif_isp10_v4l2_dev[g_cif_isp10_v4l2_dev_cnt] =cif_isp10_v4l2_dev; //将设置、注册好的ISP加入核心层中。
cif_isp10_v4l2_register_video_device最后一个参数:cif_isp10_v4l2_sp_ioctlops,是提供给上层用的,我们等下要分析的1,2,3点将用到。cif_isp10_v4l2_sp_ioctlops原型见附录。
看到cif_isp10_v4l2_sp_ioctlops原型一系列ioctl函数,我们对上层的调用就很清晰了:
第1步的VIDIOC_REQBUFS就调用.vidioc_reqbufs
第2步的VIDIOC_STREAMON就调用.vidioc_streamon
第3步的VIDIOC_DQBUF就调用.vidioc_dqbuf
1,3步和摄像头的设备驱动没什么关系,有兴趣的读者自行分析。
我分析第2步,看.vidioc_streamon如何调用到设备驱动的,也就是ov2659_custom_config.start_streaming。
要解决这个问题,其实是要知道核心层是如何寻找到摄像头设备驱动的。
由“框架分析”我们知道,rkisp组织的v4l2框架通过设备树rockchip,camera-modules-attached属性绑定具体摄像头硬件,通过check_camera_id机制来确认具体摄像头driver。
关于第2步顺序跟踪下cif_isp10_v4l2_sp_ioctlops.vidioc_streamon:
cif_isp10_v4l2_streamon
-->cif_isp10_v4l2_streamon
-->cif_isp10_start
-->cif_isp10_img_src_ioctl
-->img_src->ops->ioctl(img_src->img_src, cmd, arg);
而img_src:cif_isp10_img_src_ops[]是一个全局常量数组,其ops.ioctl字段为cif_isp10_img_src_v4l2_subdev_ioctl
-->cif_isp10_img_src_v4l2_subdev_ioctl
-->v4l2_subdev_call(subdev,core,ioctl,cmd,arg);//v4l2_subdev_call原型见附录
可以看出,cif_isp10_v4l2_streamon最终会调用subdev下的ioctl.ioctl这里的subdev就是摄像头驱动ov2659.sd
总结:因为摄像头(subdev)和核心层(rkisp)是通过设备树cif_isp1节点下的rockchip,camera-modules-attached属性、check_camera_id机制绑定。所以,摄像头是不支持“严格意义热插拔”的。其实,分析的难点也是知道核心层(rkisp)、摄像头(subdev)是如何绑定。
最后,分享一个调试经验。在对源码调用关系把握不好时,可以故意做一个编译错误、运行错误、运行打印等信息来帮助调试分析。看别人分析源码有时候会有点接不上,这时候最好是自己也分析一遍,分析多了就有一些想法,对一些关键组件看名字也能猜到一些调用关系。
附录:
v4l2_subdev_call原型:
#define v4l2_subdev_call(sd, o, f, args...) \
(!(sd) ? -ENODEV : (((sd)->ops->o && (sd)->ops->o->f) ? \
(sd)->ops->o->f((sd) , ##args) : -ENOIOCTLCMD))
cif_isp10_v4l2_sp_ioctlops原型:
const struct v4l2_ioctl_ops cif_isp10_v4l2_sp_ioctlops = {
.vidioc_reqbufs = cif_isp10_v4l2_reqbufs,
.vidioc_querybuf = cif_isp10_v4l2_querybuf,
.vidioc_create_bufs = vb2_ioctl_create_bufs,
.vidioc_qbuf = cif_isp10_v4l2_qbuf,
.vidioc_dqbuf = cif_isp10_v4l2_dqbuf,
.vidioc_streamon = cif_isp10_v4l2_streamon,
.vidioc_streamoff = cif_isp10_v4l2_streamoff,
...
.vidioc_enum_input = v4l2_enum_input,
.vidioc_g_ctrl = v4l2_g_ctrl,
.vidioc_s_ctrl = cif_isp10_v4l2_s_ctrl,
.vidioc_s_fmt_vid_cap = cif_isp10_v4l2_s_fmt,
.vidioc_g_fmt_vid_cap = cif_isp10_v4l2_g_fmt,
...
.vidioc_s_ext_ctrls = v4l2_s_ext_ctrls,
.vidioc_enum_fmt_vid_cap = v4l2_enum_fmt_cap,
.vidioc_enum_framesizes = cif_isp10_v4l2_enum_framesizes,
...
};
设备树:
cif_isp1: cif_isp@ff920000 {
compatible = "rockchip,rk3399-cif-isp";
rockchip,grf = <&grf>;
reg = <0x0 0xff920000 0x0 0x4000>, <0x0 0xff968000 0x0 0x8000>;
reg-names = "register", "dsihost-register";
clocks =
<&cru ACLK_ISP1_NOC>, <&cru ACLK_ISP1_WRAPPER>,
<&cru HCLK_ISP1_NOC>, <&cru HCLK_ISP1_WRAPPER>,
<&cru SCLK_ISP1>, <&cru PCLK_ISP1_WRAPPER>,
<&cru SCLK_DPHY_TX1RX1_CFG>,
<&cru PCLK_MIPI_DSI1>, <&cru SCLK_MIPIDPHY_CFG>,
<&cru SCLK_CIF_OUT>, <&cru SCLK_CIF_OUT>,
<&cru SCLK_MIPIDPHY_REF>;
clock-names =
"aclk_isp1_noc", "aclk_isp1_wrapper",
"hclk_isp1_noc", "hclk_isp1_wrapper",
"clk_isp1", "pclkin_isp1",
"pclk_dphytxrx",
"pclk_mipi_dsi","mipi_dphy_cfg",
"clk_cif_out", "clk_cif_pll",
"pclk_dphy_ref";
interrupts = ;
interrupt-names = "cif_isp10_irq";
power-domains = <&power RK3399_PD_ISP1>;
rockchip,isp,iommu-enable = <1>;
iommus = <&isp1_mmu>;
status = "okay";
};
&cif_isp1 { //cif_isp1指定了和camera4绑定(在上一篇《rk3399调试ov2659(camera模块@dvp接口)--移植过程》有介绍)
rockchip,camera-modules-attached = <&camera4>;
status = "okay";
};