Linux设备驱动之Camera驱动
Camera,相机,平常手机使用较多,但是手机的相机怎么进行拍照的,硬件和软件,都是如何配合拍摄到图像的,下面大家一起来了解一下。
基础知识
在介绍具体Camera框架前,我们先了解一下视频设备硬件。日常嵌入式开发,经常会听到说camera,它就是嵌入式开发中的视频设备,有些时候又会听到一些什么sensor、模组等,这些介绍如下:
名词 | 解释 |
---|---|
cmos sensor | 传感器,它将外界的光信息转换为模拟信号,然后再ADC采样转换为数字信号输出 |
camera模组 | 模组厂将sensor、lens、PCB等组合起来,完成一个摄像头硬件模块 |
DVP接口 | sensor数据输出方式的一种,为并口,一般有PCLK、VSYNC、HSYNC、DATA[11:0]等引脚 |
MIPI接口 | mipi接口是一个差分串行信号,有clk lane和data lane,可传输很高的数据速率 |
I2C接口 | sensor除了有数据输出接口以外,还会有I2C接口,一般会通过该接口进行寄存器配置 |
分辨率 | sensor输出图像的大小 |
帧率 | sensor每秒可输出多少张转换好的图像数据 |
YUV | 颜色编码的一种,Y表示明亮度,也就是灰阶值,U和V表示的则是色度,作用是描述影像色彩及饱和度,用于指定像素的颜色,常用NV21格式 |
RAW | 原始图像文件包含图像传感器所处理数据 |
ISP | 图像信号处理器,完成RAW到YUV的转换,并进行了若干效果处理 |
Camera成像原理
首先,我们平常通过眼睛是如何看到世界万物的?光,光线进入眼睛,通过晶状体,落在视网膜上,视网膜上的感光细胞浆光信号转化为神经信号,最终传递到大脑皮层,让我们感受到世界万物。上面的话很粗糙,但是整体的传递通路大差不大。那么,在日常电子产品的Camera,又是如何成像的呢?
上图是简单的Camera成像原理,是否与人眼很类似呢。光传递信息,经过凸透镜,映射到sensor的感光区域上,凸透镜不就对应眼睛的晶状体,sensor的感光区域不就是视网膜了么。至于sensor是如何捕捉到光信息的并转换为机器可以使用的数据,我们拿一个sensor的datasheet继续跟进。
sensor datasheet中的block diagram所示,Pixel Array就是感光区域,它负责捕捉光信息并转换为电信号,电信号是模拟信号,经过模拟信号处理(信号降噪等),然后再经过ADC采样转换为数字信号,转换为数字信号之后,经过sensor内部的图像信号处理,最后通过传输模块将图像数据传递出来。
上图中的column是列,CDS是指相关双取样电路(Correlated Double Sampling),sensor每个像素的输出波形只在一部分时间内是图像信号,其余时间是复位电平和干扰。为了取出图像信号并消除干扰, 要采用取样保持电路。每个像素信号被取样后, 由一电容把信号保持下来, 直到取样下一个像素信号。
Row Decoder则是行译码器,控制电信号读取,一行行的读取像素数据。
Analog Processing,包含将模拟信号放大,滤波等功能,在设置图像增益时,则是应用到该单元。
ADC,模数转换器,将模拟信号采样转换为数字信号。
Image Signal Processing则是sensor内部的一个图像处理单元,这个单元,一般会将每个像素点的黑电平设置到一个固定值,方便后续芯片进行ISP处理,同时,还会负责输出图像的裁剪、缩放等功能。如果sensor能输出YUV格式的数据,将包含RAW转YUV功能。
V4L2
目前绝大部分的V4L2设备都是由多个组件组成,在/dev下导出多个设备节点。一般我们会基于V4L2框架,通过video_device结构体创建V4L2设备节点,并使用video2_buf出视频缓冲数据。像日常介绍的csi pipeline,里面会包含cmos sensor、mipi、csi、isp等子设备,他们共同完成图像信号的采集、转换及处理。
驱动结构
所有的V4L2驱动都会有如下结构:
- 每个设备示例的结构体–包含其设备状态;
- 初始化和控制子设备的方法(非必需);
- 创建V4L2设备节点(/dev/videoX等);
- 特定文件句柄结构体–包含每个文件句柄的数据(可用于配置sensor的一些特性等);
- 视频缓冲处理;
框架结构
该框架非常类似驱动结构:它有一个 v4l2_device 结构用于保存设备实例的数据;一个 v4l2_subdev 结构体代表子设备实例;video_device结构体保存 V4L2 设备节点的数据。
v4l2_device 结构体
每个设备实例都通过 v4l2_device (v4l2-device.h)结构体来表示。简单设备可以仅分配这个结构体,但在大多数情况下,都会将这个结构体嵌入到一个更大的结构体中。驱动中,将会通过v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
注册设备示例。
v4l2_subdev结构体
很多camera驱动需要与子设备通信,控制这些子设备完成各种任务,V4L2为了给驱动提供调用子设备的统一接口,创建了v4l2_subdev结构体。每个 v4l2_subdev 都包含子设备驱动需要实现的函数指针(如果对此设备不适用,可为NULL)。由于子设备可完成许多不同的工作,而在一个庞大的函数指针结构体中通常仅有少数有用的函数实现其功能肯定不合适。所以,函数指针根据其实现的功能被分类,每一类都有自己的函数指针结构体。
这些结构体定义如下:
struct v4l2_subdev_core_ops {
int (*log_status)(struct v4l2_subdev *sd);
int (*init)(struct v4l2_subdev *sd, u32 val);
...
};
struct v4l2_subdev_tuner_ops {
...
};
struct v4l2_subdev_audio_ops {
...
};
struct v4l2_subdev_video_ops {
...
};
struct v4l2_subdev_pad_ops {
...
};
struct v4l2_subdev_ops {
const struct v4l2_subdev_core_ops *core;
const struct v4l2_subdev_tuner_ops *tuner;
const struct v4l2_subdev_audio_ops *audio;
const struct v4l2_subdev_video_ops *video;
const struct v4l2_subdev_pad_ops *video;
};
然后驱动可通过v4l2_subdev_init
进行初始化,通过v4l2_device_register_subdev
向 v4l2_device 注册 v4l2_subdev。在设备注册之后,可以通过以下方式直接调用其操作函数:
err = sd->ops->core->g_std(sd, &norm);
但使用如下宏会比较容易且合适:
err = v4l2_subdev_call(sd, core, g_std, &norm);
I2C 子设备驱动
由于sensor大部分都是通过I2C接口传输控制数据,所以V4L2框架也提供了I2C子设备驱动,通过v4l2_i2c_subdev_init(&state->sd, client, subdev_ops)
将v4l2_subdev和i2c_client建立联系。
video_device结构体
在/dev目录下的实际设备节点(比如/dev/videoX)根据video_device结构体创建,在这个结构体中,需要设置以下结构成员:
-
v4l2_dev: 设置为 v4l2_device 父设备。
-
name: 设置为唯一的描述性设备名。
-
fops: 设置为已有的 v4l2_file_operations 结构体。
-
ioctl_ops: v4l2_ioctl_ops 结构体.
-
lock: 如果你要在驱动中实现所有的锁操作,则设为 NULL 。
video_device注册
接下来你需要注册视频设备:这会为你创建一个字符设备。
err = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
if (err) {
video_device_release(vdev); /* or kfree(my_vdev); */
return err;
}
如果 v4l2_device 父设备的 mdev 域为非 NULL 值,视频设备实体将自动注册为媒体设备。
注册哪种设备是根据类型(type)参数。存在以下类型:
- VFL_TYPE_VIDEO: 用于视频输入/输出设备的 videoX
- VFL_TYPE_VBI: 用于垂直消隐数据的 vbiX (例如,隐藏式字幕,图文电视)
- VFL_TYPE_RADIO: 用于广播调谐器的 radioX
最后一个参数让你确定一个所控制设备的设备节点号数量(例如 videoX 中的 X)。通常你可以传入-1,让 v4l2 框架自己选择第一个空闲的编号。
视频缓冲辅助函数
v4l2 核心 API 提供了一个处理视频缓冲的标准方法(称为“videobuf2”)。这些方法使驱动可以通过统一的方式实现 read()、mmap() 和 overlay()。目前在设备上支持视频缓冲的方法有分散/聚集 DMA(videobuf-dma-sg)、
线性 DMA(videobuf-dma-contig)以及大多用于 USB 设备的用 vmalloc分配的缓冲(videobuf-vmalloc)。
所以V4L2整体框架如下图:
全志sunxi-vin
suxi-vin是全志平台的camera驱动,主要包含以下目录结构:
- vin.c 是驱动的主要功能实现,包括注册/注销、参数读取、与 v4l2 上层接口、与各 device 的
下层接口、中断处理、buffer 申请切换等; - modules/sensor 文件夹里面是各个 sensor 的器件层实现,一般包括上下电、初始化,各分
辨率切换,yuv sensor 包括绝大部分的 v4l2 定义的 ioctrl 命令的实现;而 raw sensor 的话
大部分 ioctrl 命令在 vfe 层调用 isp 的库实现,少数如曝光/增益调节会透过 vin 层到实际器件
层; - modules/actuator 文件夹内是各种 vcm 的驱动;
- modules/flash 文件夹内是闪光灯控制接口实现;
- vin-csi 和 vin-mipi 为对 csi 接口和 mipi 接口的控制文件;
- vin-isp 文件夹为 isp 的库操作文件,vin-stat则是3A统计值驱动;
- vin-video 文件夹内主要是 video 设备操作文件;
- vin-vipp为ISP处理之后的缩放硬件驱动;
前面有介绍到,当前的camera驱动,都是由各个v4l2_subdev子设备组成的,而当前全志sunxi-vin的csi pipeline如下:
cmos sensor ---> mipi ---> csi ---> isp ---> vipp ---> user space
sensor驱动
sensor驱动通过cci_dev_init_helper()
向系统注册i2c驱动,驱动与设备匹配之后,再在probe()中通过v4l2_i2c_subdev_init()初始化v4l2_subdev和i2c_client,并初始化media_entity。
初此之外,sensor驱动还会配置I2C总线的位宽、sensor上下电、支持的分辨率、帧率、sensor的输出格式类型、输出接口方式、初始化寄存器配置等信息。
sensor v4l2_subdev支持的操作如下:
static const struct v4l2_subdev_core_ops sensor_core_ops = {
.g_chip_ident = sensor_g_chip_ident,
.g_ctrl = sensor_g_ctrl,
.s_ctrl = sensor_s_ctrl,
.queryctrl = sensor_queryctrl,
.reset = sensor_reset,
.init = sensor_init,
.s_power = sensor_power,
.ioctl = sensor_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = sensor_compat_ioctl32,
#endif
};
static const struct v4l2_subdev_video_ops sensor_video_ops = {
.s_parm = sensor_s_parm,
.g_parm = sensor_g_parm,
.s_stream = sensor_s_stream,
.g_mbus_config = sensor_g_mbus_config,
};
static const struct v4l2_subdev_pad_ops sensor_pad_ops = {
.enum_mbus_code = sensor_enum_mbus_code,
.enum_frame_size = sensor_enum_frame_size,
.get_fmt = sensor_get_fmt,
.set_fmt = sensor_set_fmt,
};
static const struct v4l2_subdev_ops sensor_ops = {
.core = &sensor_core_ops,
.video = &sensor_video_ops,
.pad = &sensor_pad_ops,
};
mipi、csi、isp、vipp驱动
mipi、csi、isp、vipp驱动也是初始化v4l2_subdev和media_entity,同时提供下面的操作:
/************************** mipi ***********************************/
static const struct v4l2_subdev_core_ops sunxi_mipi_core_ops = {
.s_power = sunxi_mipi_subdev_s_power,
.init = sunxi_mipi_subdev_init,
};
static const struct v4l2_subdev_video_ops sunxi_mipi_subdev_video_ops = {
.s_stream = sunxi_mipi_subdev_s_stream,
.s_mbus_config = sunxi_mipi_s_mbus_config,
};
static const struct v4l2_subdev_pad_ops sunxi_mipi_subdev_pad_ops = {
.enum_mbus_code = sunxi_mipi_enum_mbus_code,
.get_fmt = sunxi_mipi_subdev_get_fmt,
.set_fmt = sunxi_mipi_subdev_set_fmt,
};
static struct v4l2_subdev_ops sunxi_mipi_subdev_ops = {
.core = &sunxi_mipi_core_ops,
.video = &sunxi_mipi_subdev_video_ops,
.pad = &sunxi_mipi_subdev_pad_ops,
};
/************************** csi ***********************************/
static const struct v4l2_subdev_core_ops sunxi_csi_core_ops = {
.s_power = sunxi_csi_subdev_s_power,
.init = sunxi_csi_subdev_init,
.ioctl = sunxi_csi_subdev_ioctl,
};
static const struct v4l2_subdev_video_ops sunxi_csi_subdev_video_ops = {
.s_stream = sunxi_csi_subdev_s_stream,
.s_crop = sunxi_csi_subdev_set_crop,
.s_mbus_config = sunxi_csi_s_mbus_config,
.s_parm = sunxi_csi_subdev_s_parm,
};
static const struct v4l2_subdev_pad_ops sunxi_csi_subdev_pad_ops = {
.enum_mbus_code = sunxi_csi_enum_mbus_code,
.get_fmt = sunxi_csi_subdev_get_fmt,
.set_fmt = sunxi_csi_subdev_set_fmt,
};
static struct v4l2_subdev_ops sunxi_csi_subdev_ops = {
.core = &sunxi_csi_core_ops,
.video = &sunxi_csi_subdev_video_ops,
.pad = &sunxi_csi_subdev_pad_ops,
};
/************************** isp ***********************************/
static const struct v4l2_subdev_core_ops sunxi_isp_subdev_core_ops = {
.g_ctrl = v4l2_subdev_g_ctrl,
.s_ctrl = v4l2_subdev_s_ctrl,
.s_power = sunxi_isp_subdev_s_power,
.init = sunxi_isp_subdev_init,
.ioctl = sunxi_isp_subdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = isp_compat_ioctl32,
#endif
.subscribe_event = sunxi_isp_subscribe_event,
.unsubscribe_event = sunxi_isp_unsubscribe_event,
};
static const struct v4l2_subdev_video_ops sunxi_isp_subdev_video_ops = {
.s_parm = sunxi_isp_s_parm,
.g_parm = sunxi_isp_g_parm,
.s_crop = sunxi_isp_subdev_set_crop,
.s_stream = sunxi_isp_subdev_s_stream,
};
static const struct v4l2_subdev_pad_ops sunxi_isp_subdev_pad_ops = {
.enum_mbus_code = sunxi_isp_enum_mbus_code,
.enum_frame_size = sunxi_isp_enum_frame_size,
.get_fmt = sunxi_isp_subdev_get_fmt,
.set_fmt = sunxi_isp_subdev_set_fmt,
};
static struct v4l2_subdev_ops sunxi_isp_subdev_ops = {
.core = &sunxi_isp_subdev_core_ops,
.video = &sunxi_isp_subdev_video_ops,
.pad = &sunxi_isp_subdev_pad_ops,
};
/************************** vipp ***********************************/
static const struct v4l2_subdev_core_ops sunxi_scaler_subdev_core_ops = {
.s_power = sunxi_scaler_subdev_s_power,
.init = sunxi_scaler_subdev_init,
};
static const struct v4l2_subdev_video_ops sunxi_scaler_subdev_video_ops = {
.s_stream = sunxi_scaler_subdev_s_stream,
};
/* subdev pad operations */
static const struct v4l2_subdev_pad_ops sunxi_scaler_subdev_pad_ops = {
.enum_mbus_code = sunxi_scaler_enum_mbus_code,
.enum_frame_size = sunxi_scaler_enum_frame_size,
.get_fmt = sunxi_scaler_subdev_get_fmt,
.set_fmt = sunxi_scaler_subdev_set_fmt,
.get_selection = sunxi_scaler_subdev_get_selection,
.set_selection = sunxi_scaler_subdev_set_selection,
};
static struct v4l2_subdev_ops sunxi_scaler_subdev_ops = {
.core = &sunxi_scaler_subdev_core_ops,
.video = &sunxi_scaler_subdev_video_ops,
.pad = &sunxi_scaler_subdev_pad_ops,
};
vin-core驱动
vin-core解析dts得到csi pipeline信息,得知pipeline中sensor、mipi、csi、isp等子设备的索引,并申请vipp中断,最后还是初始化vin_cap的v4l2_subdev和media_entity,它与其他子设备不一样的地方是v4l2_subdev配置了internal_ops。
static const struct v4l2_subdev_internal_ops vin_capture_sd_internal_ops = {
.registered = vin_capture_subdev_registered,
.unregistered = vin_capture_subdev_unregistered,
};
sd->internal_ops = &vin_capture_sd_internal_ops;
驱动加载过程
视频设备节点,在开机的时候驱动与设备配置一致,则将会进行相应的注册,sensor驱动的加载比较简单,就是v4l2_subdev和media_entity设备的初始化。而vin.c,则将会依次向系统注册csi、isp、mipi、flash、vipp、vin等platform驱动,而这些驱动的probe函数中,则是进行v4l2_subdev和media_entity设备的初始化。启动vin的probe进行较多操作,主要如下:
-
进行dts信息解析,dts中将配置sensor的硬件信息以及csi pipeline的连续顺序:
parse_modules_from_device_tree(vind);
-
申请gpio硬件资源:
vin_gpio_request(vind);
-
通过v4l2_device_register()注册v4l2_device:
v4l2_dev = &vind->v4l2_dev; v4l2_dev->mdev = &vind->media_dev; strlcpy(v4l2_dev->name, "sunxi-vin", sizeof(v4l2_dev->name)); ret = v4l2_device_register(dev, &vind->v4l2_dev);
-
通过media_device_register()注册media_device:
ret = media_device_register(&vind->media_dev);
-
获取时钟信息:
ret = vin_md_get_clocks(vind);
-
注册各个media entity设备:
ret = vin_md_register_entities(vind, dev->of_node); /************************ sensor ***************************/ for (j = 0; j < sensors->detect_num; j++) { if (sensors->use_sensor_list == 1) __vin_handle_sensor_info(&sensors->inst[j]); __vin_verify_sensor_info(&sensors->inst[j]); /* 通过v4l2_device_register_subdev或v4l2_i2c_new_subdev向系统注册v4l2_subdev */ if (__vin_register_module(vind, module, j)) { sensors->valid_idx = j; break; } } /************************ video device ***************************/ /* 通过v4l2_device_register_subdev向系统注册v4l2_subdev */ vin_md_register_core_entity(vind, vind->vinc[i]); /******************** csi、mipi、isp、vipp *************************/ /* 通过v4l2_device_register_subdev向系统注册v4l2_subdev */ ret = v4l2_device_register_subdev(&vind->v4l2_dev, vind->csi[i].sd); ret = v4l2_device_register_subdev(&vind->v4l2_dev, vind->mipi[i].sd); ret = v4l2_device_register_subdev(&vind->v4l2_dev, vind->isp[i].sd); ret = v4l2_device_register_subdev(&vind->v4l2_dev, vind->scaler[i].sd);
-
建立csi pipeline默认连接:
ret = vin_setup_default_links(vind); /* 此时csi与isp、isp与vipp建立默认连接,同时根据dts中的配置,初始化好csi pipeline中的subdev */
-
注册v4l2_subdev节点(/dev/v4l-subdevX):
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev) { strlcpy(vdev->name, sd->name, sizeof(vdev->name)); vdev->v4l2_dev = v4l2_dev; vdev->fops = &v4l2_subdev_fops; vdev->release = v4l2_device_release_subdev_node; vdev->ctrl_handler = sd->ctrl_handler; err = __video_register_device(vdev, VFL_TYPE_SUBDEV, -1, 1, sd->owner); }
到这里,基本各个设备都已经完成加载注册,硬件与dts都正确的情况,应该存在/dev/videoX节点。但是可能存在疑惑,这个节点到底是什么时候,哪里进行注册的呢,下面介绍。
video节点的注册
在初始化vin-core v4l2_subdev的时候,将v4l2_subdev的internal_ops成员赋值为vin_capture_sd_internal_ops,而在开机加载的第六步处,将会通过v4l2_device_register_subdev()向系统注册v4l2_subdev,该函数中,将会调用到internal_ops->registered()。sunxi-vin中,该函数指针指向vin_capture_subdev_registered()函数,最后的创建配置如下:
int vin_init_video(struct v4l2_device *v4l2_dev, struct vin_vid_cap *cap)
{
snprintf(cap->vdev.name, sizeof(cap->vdev.name),
"vin_video%d", cap->vinc->id);
cap->vdev.fops = &vin_fops; /* 当操作/dev/videoX时,将调用到这里 */
cap->vdev.ioctl_ops = &vin_ioctl_ops; /* ioctl将会来到这里 */
cap->vdev.release = video_device_release_empty;
cap->vdev.ctrl_handler = &cap->ctrl_handler;
cap->vdev.v4l2_dev = v4l2_dev;
cap->vdev.queue = &cap->vb_vidq;
cap->vdev.lock = &cap->lock;
cap->vdev.flags = V4L2_FL_USES_V4L2_FH;
/* VFL_TYPE_GRABBER类型,就是/dev/videoX节点 */
ret = video_register_device(&cap->vdev, VFL_TYPE_GRABBER, cap->vinc->id);
/* 创建videobuf2管理队列 */
/* initialize queue */
q = &cap->vb_vidq;
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
q->drv_priv = cap;
q->buf_struct_size = sizeof(struct vin_buffer);
q->ops = &vin_video_qops; /* 队列的操作集合 */
q->mem_ops = &vb2_dma_contig_memops; /* videobuf2的内存操作集合 */
q->timestamp_type = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
q->lock = &cap->lock;
ret = vb2_queue_init(q);
}
此时,视频设备节点注册完成。
应用操作
全志平台的camera,应用主要进行下面的几个操作:
- open /dev/videoX:此时将会调用到sunxi-vin的vin_fops.open,也就是vin_open()函数,这里主要也就是设置一些设备的标志;
- ioctl VIDIOC_QUERYCAP:确认底层驱动支持的采集类型,V4L2_CAP_VIDEO_CAPTURE与V4L2_CAP_VIDEO_CAPTURE_MPLANE使用的方式不一样;
- ioctl VIDIOC_S_INPUT:此时调用到底层vin_ioctl_ops.vidioc_s_input,sunxi-vin将会根据传递进来的参数,选择相应的sensor,完成csi pipeline的各子设备的连接以及初始化;
- ioctl VIDIOC_S_PARM:此时调用到底层vin_ioctl_ops.vidioc_s_parm,驱动将调用各子设备的s_parm函数,实际就是完成帧率的设置;
- ioctl VIDIOC_S_FMT:驱动的vin_ioctl_ops.vidioc_s_fmt_vid_cap_mplane,匹配pipeline的各个子设备的输入输出格式与分辨率;
- ioctl VIDIOC_REQBUFS、VIDIOC_QUERYBUF、MMAP:完成底层驱动videobuf2的申请以及相应内存的映射;
- ioctl VIDIOC_STREAMON:开启流传输,底层vin_ioctl_ops.vidioc_streamon函数的调用,使能各个子设备进行视频流传输;
- ioctl VIDIOC_QBUF、VIDIOC_DQBUF:视频缓冲buf的获取;
sensor驱动移植
上面介绍到,sensor实际上也是一个I2C设备,按照全志平台的实际框架,拷贝一份其他的sensor驱动,然后进行相关的配置修改。主要修改点如下:
- SENSOR_NAME、I2C_ADDR、MCLK;
- sensor的初始化代码,寄存器配置数组;
- 曝光、增益设置接口函数;
- 上下电时序;
- sensor_detect函数;
- sensor支持的ioctl操作;
- CSI相关的接口:DVP/MIPI,几lane;
- 分辨率、帧率等配置;
设备注册异常怎么办?
- I2C通信是否正常,如果I2C通信异常,确认sensor的供电、PWDN/RESET、MCLK、I2C addr等是否正确,配合I2C波形测试确认;
- I2C正常通信,但是获取不到数据:通过示波器测量sensor是否正常输出数据,如果没有输出,确认配置正确的情况下,找模组厂支持,更新寄存器配置表;如果由数据输出,确认mipi的相应参数是否配置正确,看看内核是否有异常打印;
_QBUF、VIDIOC_DQBUF:视频缓冲buf的获取;
sensor驱动移植
上面介绍到,sensor实际上也是一个I2C设备,按照全志平台的实际框架,拷贝一份其他的sensor驱动,然后进行相关的配置修改。主要修改点如下:
- SENSOR_NAME、I2C_ADDR、MCLK;
- sensor的初始化代码,寄存器配置数组;
- 曝光、增益设置接口函数;
- 上下电时序;
- sensor_detect函数;
- sensor支持的ioctl操作;
- CSI相关的接口:DVP/MIPI,几lane;
- 分辨率、帧率等配置;
设备注册异常怎么办?
- I2C通信是否正常,如果I2C通信异常,确认sensor的供电、PWDN/RESET、MCLK、I2C addr等是否正确,配合I2C波形测试确认;
- I2C正常通信,但是获取不到数据:通过示波器测量sensor是否正常输出数据,如果没有输出,确认配置正确的情况下,找模组厂支持,更新寄存器配置表;如果由数据输出,确认mipi的相应参数是否配置正确,看看内核是否有异常打印;