电子说
本文以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 = <0x0c>; rockchip,camera-module-index = <0>; rockchip,vcm-start-current = <10>; rockchip,vcm-rated-current = <85>; rockchip,vcm-step-mode = <5>; rockchip,camera-module-facing = "back"; }; gc8034: gc8034@37 { compatible = "galaxycore,gc8034"; reg = <0x37>; clocks = <&cru CLK_CIF_OUT>; clock-names = "xvclk"; power-domains = <&power RK3568_PD_VI>; //sensor mclk pinctl设置。如果sensor的工作时钟由主控提供,则此处必须配置 pinctrl-names = "default"; pinctrl-0 = <&cif_clk>; // reset管脚分配及有效电平 reset-gpios = <&gpio3 RK_PB5 GPIO_ACTIVE_LOW>; // powerdown管脚分配及有效电平 pwdn-gpios = <&gpio4 RK_PB5 GPIO_ACTIVE_LOW>; rockchip,grf = <&grf>; // 模组编号,该编号不要重复 rockchip,camera-module-index = <0>; // 模组朝向,有"back"和"front" rockchip,camera-module-facing = "back"; rockchip,camera-module-name = "RK-CMK-8M-2-v1"; rockchip,camera-module-lens-name = "CK8401"; lens-focus = <&dw9714>; port { gc8034_out: endpoint { // csi2 dphy端的port名 remote-endpoint = <&mipi_in_ucam0>; // csi2 dphy lane数,1lane为 <1>, 4lane为 <1 2 3 4> // rk3568支持1*4lane和2*2lane两种模式 data-lanes = <1 2 3 4>; }; }; }; };
然后需要配置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 = <1>; #size-cells = <0>; port@0 { reg = <0>; #address-cells = <1>; #size-cells = <0>; mipi_in_ucam0: endpoint@2 { reg = <2>; remote-endpoint = <&gc8034_out>; data-lanes = <1 2 3 4>; }; }; port@1 { reg = <1>; #address-cells = <1>; #size-cells = <0>; csidphy_out: endpoint@0 { reg = <0>; remote-endpoint = <&isp0_in>; }; }; }; };
然后ISP只需要配置输入端,为CSI
&rkisp_vir0 { status = "okay"; port { #address-cells = <1>; #size-cells = <0>; isp0_in: endpoint@0 { reg = <0>; remote-endpoint = <&csidphy_out>; }; }; };
配置完这个链路之后,编译内核,然后将内核烧录到板上,即可使用摄像头。
内核日志如下
如果看到这个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的适配。
审核编辑:刘清
全部0条评论
快来发表一下你的评论吧 !