RK3568 MIPI CSI摄像头GC8034 适配
本文以RK3568外接GC8034为例,首先介绍MIPI CSI摄像头的适配方法,然后介绍cmos sensor驱动的一些细节与cmos sensor驱动的工作流程。
硬件准备
首先介绍一下硬件。主板为风火轮科技的YY3568开发板,主控RK3568。此开发板的相关介绍可以参考
https://wiki.youyeetoo.cn/zh/YY3568
它支持一个MIPI CSI接口。此接口为4LANE,可以拆分为2个2LANE的。摄像头模组为TSC8034-HYX5,此模组主控为GC8034。此外,模组上面有一颗DW9714,这是一颗VCM,用于控制镜头伸缩。模组连接到一个转接板,然后转接板通过排线连接到YY3568开发板MIPI CSI接口。硬件连接如下
目前市面上大多数的cmos image sensor一般会包含控制接口(i2c\spi等)用于寄存器读写,数据接口(csi/bt656等)用于传原始图像(raw image)数据。
软件准备
本文所有的源码分析基于rk的4.19版本kernel。相关源码可以从风火轮科技获取,下载地址。
Android 源码链接:https://pan.baidu.com/s/1rK-3hwkVd0FYBdSrQrbyVA?pwd=n8io
Debian源码链接:https://pan.baidu.com/s/1NynxVL6VnqPBSOVkRu8J7Q?pwd=t6cg
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(Image Signal 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 mclkpinctl设置。如果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函数。
staticint gc8034_probe(struct i2c_client *client,
conststruct i2c_device_id *id)
{
struct device *dev = &client->dev;
structdevice_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);
...
// 读取gc8034otp寄存器
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)
gotoerr_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框架里面几个比较重要的结构体。如下
staticconststruct v4l2_subdev_core_ops gc8034_core_ops = {
.s_power = gc8034_s_power,
.ioctl = gc8034_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = gc8034_compat_ioctl32,
#endif
};
staticconststruct 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,
};
staticconststruct 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,
};
staticconststruct 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_fmtset_fmt两个成员则是用于获取和设置当前sensor的格式分辨率和帧率。
对于rk平台来说,要实现一个sensor驱动,实现上面的api就足够了。如果是其它平台,则要看是否需要私有的ioctl。通用的v4l2驱动是不需要私有ioctl的。
总结
本文以RK3568外接GC8034为例,首先介绍MIPI CSI摄像头的适配方法,然后介绍cmos sensor驱动的一些细节与cmos sensor驱动的工作流程。参考GC8034的驱动,可以在通用平台上实现sensor的适配。
|
|