本文以rk3568外接gc8034为例,首先介绍mipi csi摄像头的适配方法,然后介绍cmos sensor驱动的一些细节与cmos sensor驱动的工作流程。
硬件准备
首先介绍一下硬件。主板为风火轮科技的yy3568开发板,主控rk3568。
它支持一个mipi csi接口。此接口为4lane,可以拆分为2个2lane的。摄像头模组为tsc8034-hyx5,此模组主控为gc8034。此外,模组上面有一颗dw9714,这是一颗vcm,用于控制镜头伸缩。模组连接到一个转接板,然后转接板通过排线连接到yy3568开发板mipi csi接口。
目前市面上大多数的cmos image sensor一般会包含控制接口(i2cspi等)用于寄存器读写,数据接口(csi/bt656等)用于传原始图像(raw image)数据。
软件准备
本文所有的源码分析基于rk的4.19版本kernel。
android和debian用的内核源码是一样的。
v4l2框架简介
v4l2(video for linux2)为linux中关于video设备的内核驱动。目前rk平台全部使用v4l2框架来操作摄像头设备。v4l2框架的组成大致如下图所示
v4l2里面有v4l2-subdev和v4l2_device,以及videobuf2-core三类设备。
v4l2-subdev指的是硬件上面的接口,包括sensor,以及sensor的接口如mipicsi或者bt656等。对于用户层来说,其控制节点为/dev/v4l-subdevx。
v4l2_device指的是那种能够向用户层传递数据的设备,在rk平台上,这个设备可以是isp(imagesignal processing unit),也可以是cif(camera interface)。isp具备图像处理功能,缩放以及压缩功能。如果需要对图像进行预处理,则需要用到isp。其控制节点为 /dev/videox
videobuf2-core用于分配和处理视频帧缓冲区,比如对mmap等操作提供支持。
kernel部分修改
首先要配置链路。由于从gc8034获取的图像需要进行前处理才能被用户层使用,因此需要使用isp,将链路设置为gc8034-> mipi 接口->isp。
首先配置gc8034。板上摄像头接口的定义如下
此处可以看出,其复位脚使用的是gpio3_b5,电源使能脚用的是gpio4_b5,然后使用i2c4与gc8034和dw9714通信,另外摄像头的时钟要由主控提供,设备树配置如下。
&i2c4 { status = okay; dw9714: dw9714@c { compatible = dongwoon,dw9714; status = okay; reg = ; rockchip,camera-module-index = ; rockchip,vcm-start-current = ; rockchip,vcm-rated-current = ; rockchip,vcm-step-mode = ; rockchip,camera-module-facing = back; }; gc8034: gc8034@37 { compatible = galaxycore,gc8034; reg = ; clocks = ; clock-names = xvclk; power-domains = ; //sensor mclk pinctl设置。如果sensor的工作时钟由主控提供,则此处必须配置 pinctrl-names = default; pinctrl-0 = ; // reset管脚分配及有效电平 reset-gpios = ; // powerdown管脚分配及有效电平 pwdn-gpios = ; rockchip,grf = ; // 模组编号,该编号不要重复 rockchip,camera-module-index = ; // 模组朝向,有back和front rockchip,camera-module-facing = back; rockchip,camera-module-name = rk-cmk-8m-2-v1; rockchip,camera-module-lens-name = ck8401; lens-focus = ; port { gc8034_out: endpoint { // csi2 dphy端的port名 remote-endpoint = ; // csi2 dphy lane数,1lane为 , 4lane为 // rk3568支持1*4lane和2*2lane两种模式 data-lanes = ; }; }; };};
然后需要配置csi链路。rk3568的csi有两种工作模式,1*4lane和2*2lane。如果是前者,则需要使能csi2_dphy0,同时禁用csi2_dphy1/csi2_dphy2。后者相反。csi的输入端为gc8034,4lane连接,输出端为isp,因此配置如下。
&csi2_dphy0 { //csi2_dphy0不与csi2_dphy1/csi2_dphy2同时使用,互斥 status = okay; ports { #address-cells = ; #size-cells = ; port@0 { reg = ; #address-cells = ; #size-cells = ; mipi_in_ucam0: endpoint@2 { reg = ; remote-endpoint = ; data-lanes = ; }; }; port@1 { reg = ; #address-cells = ; #size-cells = ; csidphy_out: endpoint@0 { reg = ; remote-endpoint = ; }; }; };};
然后isp只需要配置输入端,为csi
&rkisp_vir0 { status = okay; port { #address-cells = ; #size-cells = ; isp0_in: endpoint@0 { reg = ; remote-endpoint = ; }; };};
配置完这个链路之后,编译内核,然后将内核烧录到板上,即可使用摄像头。
内核日志如下
如果看到这个log,说明gc8034和csi2-dphy的链路已通。
gc8034驱动介绍
下面介绍一下gc8034的驱动。在kernel里面,由于有v4l2这个框架的存在,因此多数cmos image sensor的驱动的框架流程都差不多,只是在寄存器操作上存在差别。
首先是驱动注册方面,gc8034支持通过i2c进行寄存器配置,而它的csi接口只能用于raw image数据的发送,并不能进行控制。因此,它是一个i2c device。驱动的开始便是注册了一个i2c device。如下
在设备树上面的设备描述和驱动匹配时,便会执行probe函数。下面看下probe函数。
static int gc8034_probe(struct i2c_client *client, const struct i2c_device_id *id){ struct device *dev = &client->dev; struct device_node *node = dev->of_node; struct gc8034 *gc8034; struct v4l2_subdev *sd; char facing[2]; int ret;... gc8034 = devm_kzalloc(dev, sizeof(*gc8034), gfp_kernel); if (!gc8034) return -enomem; // 这些数据是为了 sensor 的 ioctl 获取模组状态 // rk平台的一些上层应用(比如android的camera应用) 需要获取这些信息,以便实现前后摄像头切换,3a参数匹配等 ret = of_property_read_u32(node, rkmodule_camera_module_index, &gc8034->module_index); ret |= of_property_read_string(node, rkmodule_camera_module_facing, &gc8034->module_facing); ret |= of_property_read_string(node, rkmodule_camera_module_name, &gc8034->module_name); ret |= of_property_read_string(node, rkmodule_camera_lens_name, &gc8034->len_name);... gc8034->client = client; gc8034->xvclk = devm_clk_get(dev, xvclk);... // 电源 复位等 gpio 获取... ret = gc8034_configure_regulators(gc8034);... // 这个函数是获取与之匹配的接口,也就是csi的设备树配置,主要是lane数 ret = gc8034_parse_of(gc8034);... // pinctrl资源获取... mutex_init(&gc8034->mutex); // 最重要的函数 v4l2_i2c_subdev_init 此函数注册了一个v4l2 subdev,也就是将此摄像头注册到v4l2框架里面了 // 其中 gc8034_subdev_ops 里面的就是gc8034响应上层控制的回调函数 sd = &gc8034->subdev; v4l2_i2c_subdev_init(sd, client, &gc8034_subdev_ops); ret = gc8034_initialize_controls(gc8034);... // 上电 ret = __gc8034_power_on(gc8034);... // 识别一下i2c上面是不是有gc8034存在,不存在的话会将上面的操作全部注销掉 ret = gc8034_check_sensor_id(gc8034, client);... // 读取gc8034 otp寄存器 gc8034_otp_enable(gc8034); gc8034_otp_read(gc8034); gc8034_otp_disable(gc8034);#ifdef config_video_v4l2_subdev_api sd->internal_ops = &gc8034_internal_ops; sd->flags |= v4l2_subdev_fl_has_devnode | v4l2_subdev_fl_has_events;#endif#if defined(config_media_controller) gc8034->pad.flags = media_pad_fl_source; sd->entity.function = media_ent_f_cam_sensor; ret = media_entity_pads_init(&sd->entity, 1, &gc8034->pad); if (ret < 0) goto err_power_off;#endif... // 此v4l2设备采用异步注册 ret = v4l2_async_register_subdev_sensor_common(sd);... return 0;...}
整个probe函数很长,这里删掉了那些错误处理和打印的部分,并为剩下的操作提供了注释。它的操作流程就是先从设备树上获取信息,然后申请gpio等资源,注册v4l2设备,然后尝试读取一下gc8034的id,如果gc8034存在,则读取其otp寄存器。在kernel启动的过程中,这些信息就是操作otp寄存器打印出来的
一般来说,模组出厂的时候要进行校准,因为各个厂家基于同一个sensor芯片设计的模组存在硬件上面的差异,校准的内容包括af(自动对焦校准)、awb(白平衡校准)、lsc(镜头阴影校准),以及模组的出厂年月日厂商名等。前者一般需要被上层获取,以便上层配置校准参数。
然后就是v4l2框架里面几个比较重要的结构体。如下
static const struct v4l2_subdev_core_ops gc8034_core_ops = { .s_power = gc8034_s_power, .ioctl = gc8034_ioctl,#ifdef config_compat .compat_ioctl32 = gc8034_compat_ioctl32,#endif};static const struct v4l2_subdev_video_ops gc8034_video_ops = { .s_stream = gc8034_s_stream, .g_frame_interval = gc8034_g_frame_interval, .g_mbus_config = gc8034_g_mbus_config,};static const struct v4l2_subdev_pad_ops gc8034_pad_ops = { .enum_mbus_code = gc8034_enum_mbus_code, .enum_frame_size = gc8034_enum_frame_sizes, .enum_frame_interval = gc8034_enum_frame_interval, .get_fmt = gc8034_get_fmt, .set_fmt = gc8034_set_fmt,};static const struct v4l2_subdev_ops gc8034_subdev_ops = { .core = &gc8034_core_ops, .video = &gc8034_video_ops, .pad = &gc8034_pad_ops,};
其中,
v4l2_subdev_core_ops主要是通用初始化的实现,gc8034中实现了上下电操作,也就是gc8034_s_power函数,还有一个ioctl操作,也就是gc8034_ioctl和gc8034_compat_ioctl32。
这个s_power函数主要借助linux的电源管理框架(pm)来实现gc8034电源使能脚的拉高拉低,此驱动是实现了dev_pm_ops里面的suspend和resume函数的。
另外还有一个write_array函数,这个函数主要用于初始化大多数的寄存器。大多数sensor的原厂会提供几种分辨率下寄存器的配置表。将这个函数放在这里,意思就是每一次使能gc8034的电源之后都重新初始化所有寄存器。
而ioctl函数一般是实现平台私有的一些ioctl命令(通用的v4l2 ioctl命令通过其它的ops实现)。比如这里rkmodule_get_module_info就是获取设备树配置的朝向和名称等信息,rkmodule_awb_cfg就是获取上文所述的otp寄存器里面的值。
v4l2_subdev_video_ops主要是对视频流进行控制,其中s_stream是必须实现的,用于控制视频流的开启和关闭。g_frame_interval是用于获取当前帧率,g_mbus_config则是用于获取lane数的,对于gc8034只能是2lane和4lane。
v4l2_subdev_pad_ops主要是对视频格式进行控制。enum_mbus_codeenum_frame_size
enum_frame_interval三个成员是获取当前sensor支持的格式,分辨率以及帧率。get_fmt set_fmt两个成员则是用于获取和设置当前sensor的格式分辨率和帧率。
对于rk平台来说,要实现一个sensor驱动,实现上面的api就足够了。如果是其它平台,则要看是否需要私有的ioctl。通用的v4l2驱动是不需要私有ioctl的。
总结
本文以rk3568外接gc8034为例,首先介绍mipi csi摄像头的适配方法,然后介绍cmos sensor驱动的一些细节与cmos sensor驱动的工作流程。参考gc8034的驱动,可以在通用平台上实现sensor的适配。
严肃编码人员的10条低代码规则
城市NOA战役打响,禾赛科技激光雷达助力中国车企反超
英特尔揭露Ivy Bridge技术细节,将包含至少四个版本
在生物识别技术的应用上 安卓智能手机阵营花招百出
航宇微:对LEON5核、RISC-V技术完成初步验证
RK3568 MIPI CSI摄像头GC8034适配工作流程
亨鑫科技搭载完整覆盖通信网络无线侧,为中国的5G建设添砖加瓦
手语识别技术的应用和前景
荣耀8和荣耀8青春版有什么区别?荣耀8和荣耀8青春版评测对比
像计算机这类完全按逻辑运行的机器是如何生成随机数的?
SpaceX计划到2021年利用卫星网络将组建一个超级组合网络
SK Hynix将在2020年削减资本支出 加速过渡1z nm DRAM和128L 4D NAND
供CMD55/CMD55/CTS60 GSM手机测试仪
手机记忆棒
海康威视发布工业面阵相机,助力高效生产、柔性制造
东芝推出汽车用单通道高边N沟道功率MOSFET栅极驱动器
数据挖掘分析方法
苹果即将推出App隐私报告功能
关于ARM7 S3C4510B上μClinux移植问题
“保姆级”教程,教你快速上手用低代码调用 GPT!