《【高通SDM660平台】(1) — Camera 驱动 Bringup Guide》
《【高通SDM660平台】(2) — Camera Kernel 驱动层代码逻辑分析》
《【高通SDM660平台】(3) — Camera V4L2 驱动层分析 》
《【高通SDM660平台】(4) — Camera Init 初始化流程 》
《【高通SDM660平台】(5) — Camera Open 流程》
《【高通SDM660平台】(6) — Camera getParameters 及 setParameters 流程》
《【高通SDM660平台】(7) — Camera onPreview 代码流程》
《【高通SDM660平台】(8) — Camera MetaData介绍》
《【高通SDM660平台 Android 10.0】(9) — Qcom Camera Daemon 代码分析》
《【高通SDM660平台 Android 10.0】(10) — Camera Sensor lib 与 Kernel Camera Probe 代码分析》
《【高通SDM660平台】Camera Capture 流程》
《【高通SDM660平台】Camera mm-qcamera-app 代码分析》
Linux系统中视频输入设备主要包括以下四个部分:
V4L2核心源码位于drivers/media/v4l2-core
,根据功能可以划分为四类:
v4l2-dev.c
实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数。v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c
等文件构建V4L2基础框架。videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c
等文件实现,完成videobuffer的分配、管理和注销。v4l2-ioctl.c
文件实现,构建V4L2ioctl的框架。在该文件中,主要是负责创建/sys/classs/video4linux
目录 ,当有设备注册进来时,创建对应的 /dev/videox 、/dev/vbix、/dev/radiox、/dev/subdevx
等节点。
主要工作如下:
(81,0)
到(81,255)
这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
/sys/classs/video4linux
目录@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-dev.c
static struct class video_class = {
.name = VIDEO_NAME, // video4linux
.dev_groups = video_device_groups,
};
static int __init videodev_init(void)
{
dev_t dev = MKDEV(VIDEO_MAJOR, 0); // VIDEO_MAJOR: 81
printk(KERN_INFO "Linux video capture interface: v2.00\n");
// 1. 将字符设备号(81,0) 到 (81,255) 这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME); //VIDEO_NUM_DEVICES: 256 VIDEO_NAME:"video4linux"
======> int register_chrdev_region(dev_t from, unsigned count, const char *name)
// 2. 注册 /sys/classs/video4linux 目录
ret = class_register(&video_class);
return 0;
}
当用设备需要注册为 v4l2 subdev 时,会调用video_register_device()
函数进行注册:
例如如下, compatible = "qcom,msm-cam";
注册为一个V4L2 subdev
,代码如下:
@\kernel\msm-4.4\drivers\media\platform\msm\camera_v2\msm.c
// 2. 分配 video_device 结构体内存
pvdev->vdev = video_device_alloc();
pvdev->vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L; // V4L
pvdev->vdev->entity.group_id = QCAMERA_VNODE_GROUP_ID; // #define QCAMERA_VNODE_GROUP_ID 2
msm_v4l2_dev->notify = msm_sd_notify; // 用于发现对应的 subdev
pvdev->vdev->v4l2_dev = msm_v4l2_dev;
// 5. 设置父设备为 pdev->dev (也就是 qcom,msm-cam 的设备信息)dev->driver->name=msm-config, dev_name=qcom,msm-camera
rc = v4l2_device_register(&(pdev->dev), pvdev->vdev->v4l2_dev);
====>
pvdev->vdev->v4l2_dev->dev = pdev->dev;
snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), "%s %s", dev->driver->name, dev_name(dev));
// 6. 注册 video_device设备
strlcpy(pvdev->vdev->name, "msm-config", sizeof(pvdev->vdev->name));
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops; // 配置 video_device 的字符设备操作函数
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops; // 配置 v4l2 IOCTRL
pvdev->vdev->minor = -1;
pvdev->vdev->vfl_type = VFL_TYPE_GRABBER;
rc = video_register_device(pvdev->vdev, VFL_TYPE_GRABBER, -1);
此时调用 video_register_device()
函数,也就是调用 __video_register_device()
以"qcom,msm-cam"
为例,其注册时,传递的 nr = -1,说明从第一个开始分配,也就是 /dev/video0
。
因此 /dev/video0
对应的设备为"qcom,msm-cam"
,其设备类型为 video
。
主要工作如下:
(0~63 为video)(128,191 为sub-dev)
(81,minor)
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-dev.c
/**
* __video_register_device - register video4linux devices
* @vdev: video device structure we want to register
* @type: type of device to register
* @nr: which device node number (0 == /dev/video0, 1 == /dev/video1, ... -1 == first free)
* @warn_if_nr_in_use: warn if the desired device node number was already in use and another number was chosen instead.
* @owner: module that owns the video device node
*
* The registration code assigns minor numbers and device node numbersbased on the requested type and registers the new device node with the kernel.
*
* This function assumes that struct video_device was zeroed when it was allocated and does not contain any stale date.
*
* An error is returned if no free minor or device node number could be found, or if the registration of the device node failed.
*
* Zero is returned on success.
*
* Valid types are
* %VFL_TYPE_GRABBER - A frame grabber
* %VFL_TYPE_VBI - Vertical blank data (undecoded)
* %VFL_TYPE_RADIO - A radio card
* %VFL_TYPE_SUBDEV - A subdevice
* %VFL_TYPE_SDR - Software Defined Radio
*/
int __video_register_device(struct video_device *vdev, int type, int nr, int warn_if_nr_in_use, struct module *owner)
{
int minor_cnt = VIDEO_NUM_DEVICES;
const char *name_base;
/* A minor value of -1 marks this video device as never having been registered */
vdev->minor = -1;
// 1. 初始化 fh->list
/* v4l2_fh support */
INIT_LIST_HEAD(&vdev->fh_list);
// 2. 检查设备类型
/* Part 1: check device type */
switch (type) {
case VFL_TYPE_GRABBER: name_base = "video"; break;
case VFL_TYPE_VBI: name_base = "vbi"; break;
case VFL_TYPE_RADIO: name_base = "radio"; break;
case VFL_TYPE_SUBDEV: name_base = "v4l-subdev";break;
case VFL_TYPE_SDR: name_base = "swradio"; break; /* Use device name 'swradio' because 'sdr' was already taken. */
}
vdev->vfl_type = type; // VFL_TYPE_GRABBER
vdev->cdev = NULL;
// 3. 寻找一个不在使用的 次设备号, 主设备号为 81,(0~63 为video)(128,191 为sub-dev)
/* Part 2: find a free minor, device node number and device index. */
/* Keep the ranges for the first four types for historical reasons.
* Newer devices (not yet in place) should use the range of 128-191 and just pick the first free minor there (new style). */
switch (type) {
case VFL_TYPE_GRABBER: minor_offset = 0; minor_cnt = 64; break;
case VFL_TYPE_RADIO: minor_offset = 64; minor_cnt = 64; break;
case VFL_TYPE_VBI: minor_offset = 224; minor_cnt = 32; break;
default: minor_offset = 128; minor_cnt = 64; break;
}
/* Pick a device node number */
nr = devnode_find(vdev, nr == -1 ? 0 : nr, minor_cnt);
if (nr == minor_cnt)
nr = devnode_find(vdev, 0, minor_cnt);
/* The device node number and minor numbers are independent, so
we just find the first free minor number. */
for (i = 0; i < VIDEO_NUM_DEVICES; i++)
if (video_device[i] == NULL)
break;
vdev->minor = i + minor_offset;
vdev->num = nr;
devnode_set(vdev);
// 4. 获取 index,将当前需要注册的 video_device 设备保存在 video_device[]全局数组中
vdev->index = get_index(vdev);
video_device[vdev->minor] = vdev;
// 5. 分配对应的字符设备 /dev/video0,字符设备号,就是前面的 (81,minor)
/* Part 3: Initialize the character device */
vdev->cdev = cdev_alloc();
vdev->cdev->ops = &v4l2_fops;
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
// 6. 分配对应的sys节点 /sys/class/video4linux/video0
/* Part 4: register the device with sysfs */
vdev->dev.class = &video_class;
vdev->dev.devt = MKDEV(VIDEO_MAJOR, vdev->minor);
vdev->dev.parent = vdev->dev_parent;
dev_set_name(&vdev->dev, "%s%d", name_base, vdev->num);
ret = device_register(&vdev->dev);
// 7. 注册release 时调用的函数
/* Register the release callback that will be called when the last reference to the device goes away. */
vdev->dev.release = v4l2_device_release;
/* Increase v4l2_device refcount */
v4l2_device_get(vdev->v4l2_dev);
// 8. 将该 v4l2 subdevice 当成一个 entity 注册到 media device
/* Part 5: Register the entity. */
if (vdev->v4l2_dev->mdev && vdev->vfl_type != VFL_TYPE_SUBDEV) {
vdev->entity.type = MEDIA_ENT_T_DEVNODE_V4L;
vdev->entity.name = vdev->name;
vdev->entity.info.dev.major = VIDEO_MAJOR;
vdev->entity.info.dev.minor = vdev->minor;
ret = media_device_register_entity(vdev->v4l2_dev->mdev,&vdev->entity);
}
/* Part 6: Activate this minor. The char device can now be used. */
set_bit(V4L2_FL_REGISTERED, &vdev->flags);
return 0;
}
EXPORT_SYMBOL(__video_register_device);
static const struct file_operations v4l2_fops = {
.owner = THIS_MODULE,
.read = v4l2_read,
.write = v4l2_write,
.open = v4l2_open,
.get_unmapped_area = v4l2_get_unmapped_area,
.mmap = v4l2_mmap,
.unlocked_ioctl = v4l2_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = v4l2_compat_ioctl32,
#endif
.release = v4l2_release,
.poll = v4l2_poll,
.llseek = no_llseek,
};
创建成功 /dev/video0 节点后,后续要打开对应的节点时,会调用 fops对应的操作函数,对应的代码在注册时赋值的。
pvdev->vdev->release = video_device_release;
pvdev->vdev->fops = &msm_fops; // 配置 video_device 的字符设备操作函数
pvdev->vdev->ioctl_ops = &g_msm_ioctl_ops; // 配置 v4l2 IOCTRL
当操作/dev/video0
节点时,下发ioctl 会转到 do_video_ioctl()
中,
如下:
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-compat-ioctl32.c
long v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) == 'V' && _IOC_NR(cmd) < BASE_VIDIOC_PRIVATE)
ret = do_video_ioctl(file, cmd, arg);
else if (vdev->fops->compat_ioctl32)
ret = vdev->fops->compat_ioctl32(file, cmd, arg);
return ret;
}
do_video_ioctl()
函数实现如下:
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-compat-ioctl32.c
static long do_video_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
union {
struct v4l2_format v2f;
struct v4l2_buffer v2b;
struct v4l2_framebuffer v2fb;
struct v4l2_input v2i;
struct v4l2_standard v2s;
struct v4l2_ext_controls v2ecs;
struct v4l2_event v2ev;
struct v4l2_create_buffers v2crt;
struct v4l2_edid v2edid;
unsigned long vx;
int vi;
} karg;
void __user *up = compat_ptr(arg);
int compatible_arg = 1;
long err = 0;
memset(&karg, 0, sizeof(karg));
/* First, convert the command. */
switch (cmd) {
case VIDIOC_G_FMT32: cmd = VIDIOC_G_FMT; break;
case VIDIOC_S_FMT32: cmd = VIDIOC_S_FMT; break;
case VIDIOC_QUERYBUF32: cmd = VIDIOC_QUERYBUF; break;
case VIDIOC_G_FBUF32: cmd = VIDIOC_G_FBUF; break;
case VIDIOC_S_FBUF32: cmd = VIDIOC_S_FBUF; break;
case VIDIOC_QBUF32: cmd = VIDIOC_QBUF; break;
case VIDIOC_DQBUF32: cmd = VIDIOC_DQBUF; break;
case VIDIOC_ENUMSTD32: cmd = VIDIOC_ENUMSTD; break;
case VIDIOC_ENUMINPUT32: cmd = VIDIOC_ENUMINPUT; break;
case VIDIOC_TRY_FMT32: cmd = VIDIOC_TRY_FMT; break;
case VIDIOC_G_EXT_CTRLS32: cmd = VIDIOC_G_EXT_CTRLS; break;
case VIDIOC_S_EXT_CTRLS32: cmd = VIDIOC_S_EXT_CTRLS; break;
case VIDIOC_TRY_EXT_CTRLS32: cmd = VIDIOC_TRY_EXT_CTRLS; break;
case VIDIOC_DQEVENT32: cmd = VIDIOC_DQEVENT; break;
case VIDIOC_OVERLAY32: cmd = VIDIOC_OVERLAY; break;
case VIDIOC_STREAMON32: cmd = VIDIOC_STREAMON; break;
case VIDIOC_STREAMOFF32: cmd = VIDIOC_STREAMOFF; break;
case VIDIOC_G_INPUT32: cmd = VIDIOC_G_INPUT; break;
case VIDIOC_S_INPUT32: cmd = VIDIOC_S_INPUT; break;
case VIDIOC_G_OUTPUT32: cmd = VIDIOC_G_OUTPUT; break;
case VIDIOC_S_OUTPUT32: cmd = VIDIOC_S_OUTPUT; break;
case VIDIOC_CREATE_BUFS32: cmd = VIDIOC_CREATE_BUFS; break;
case VIDIOC_PREPARE_BUF32: cmd = VIDIOC_PREPARE_BUF; break;
case VIDIOC_G_EDID32: cmd = VIDIOC_G_EDID; break;
case VIDIOC_S_EDID32: cmd = VIDIOC_S_EDID; break;
}
switch (cmd) {
case VIDIOC_OVERLAY:
case VIDIOC_STREAMON:
case VIDIOC_STREAMOFF:
case VIDIOC_S_INPUT:
case VIDIOC_S_OUTPUT:
err = get_user(karg.vi, (s32 __user *)up);
compatible_arg = 0;
break;
case VIDIOC_G_INPUT:
case VIDIOC_G_OUTPUT:
compatible_arg = 0;
break;
case VIDIOC_G_EDID:
case VIDIOC_S_EDID:
err = get_v4l2_edid32(&karg.v2edid, up);
compatible_arg = 0;
break;
case VIDIOC_G_FMT:
case VIDIOC_S_FMT:
case VIDIOC_TRY_FMT:
err = get_v4l2_format32(&karg.v2f, up);
compatible_arg = 0;
break;
case VIDIOC_CREATE_BUFS:
err = get_v4l2_create32(&karg.v2crt, up);
compatible_arg = 0;
break;
case VIDIOC_PREPARE_BUF:
case VIDIOC_QUERYBUF:
case VIDIOC_QBUF:
case VIDIOC_DQBUF:
err = get_v4l2_buffer32(&karg.v2b, up);
compatible_arg = 0;
break;
case VIDIOC_S_FBUF:
err = get_v4l2_framebuffer32(&karg.v2fb, up);
compatible_arg = 0;
break;
case VIDIOC_G_FBUF:
compatible_arg = 0;
break;
case VIDIOC_ENUMSTD:
err = get_v4l2_standard32(&karg.v2s, up);
compatible_arg = 0;
break;
case VIDIOC_ENUMINPUT:
err = get_v4l2_input32(&karg.v2i, up);
compatible_arg = 0;
break;
case VIDIOC_G_EXT_CTRLS:
case VIDIOC_S_EXT_CTRLS:
case VIDIOC_TRY_EXT_CTRLS:
err = get_v4l2_ext_controls32(&karg.v2ecs, up);
compatible_arg = 0;
break;
case VIDIOC_DQEVENT:
compatible_arg = 0;
break;
}
if (compatible_arg)
err = native_ioctl(file, cmd, (unsigned long)up);
else {
mm_segment_t old_fs = get_fs();
set_fs(KERNEL_DS);
err = native_ioctl(file, cmd, (unsigned long)&karg);
set_fs(old_fs);
}
/* Special case: even after an error we need to put the
results back for these ioctls since the error_idx will
contain information on which control failed. */
switch (cmd) {
case VIDIOC_G_EXT_CTRLS:
case VIDIOC_S_EXT_CTRLS:
case VIDIOC_TRY_EXT_CTRLS:
if (put_v4l2_ext_controls32(&karg.v2ecs, up))
err = -EFAULT;
break;
}
if (err)
return err;
switch (cmd) {
case VIDIOC_S_INPUT:
case VIDIOC_S_OUTPUT:
case VIDIOC_G_INPUT:
case VIDIOC_G_OUTPUT:
err = put_user(((s32)karg.vi), (s32 __user *)up);
break;
case VIDIOC_G_FBUF:
err = put_v4l2_framebuffer32(&karg.v2fb, up);
break;
case VIDIOC_DQEVENT:
err = put_v4l2_event32(&karg.v2ev, up);
break;
case VIDIOC_G_EDID:
case VIDIOC_S_EDID:
err = put_v4l2_edid32(&karg.v2edid, up);
break;
case VIDIOC_G_FMT:
case VIDIOC_S_FMT:
case VIDIOC_TRY_FMT:
err = put_v4l2_format32(&karg.v2f, up);
break;
case VIDIOC_CREATE_BUFS:
err = put_v4l2_create32(&karg.v2crt, up);
break;
case VIDIOC_QUERYBUF:
case VIDIOC_QBUF:
case VIDIOC_DQBUF:
err = put_v4l2_buffer32(&karg.v2b, up);
break;
case VIDIOC_ENUMSTD:
err = put_v4l2_standard32(&karg.v2s, up);
break;
case VIDIOC_ENUMINPUT:
err = put_v4l2_input32(&karg.v2i, up);
break;
}
return err;
}
当有sub-dev 需要注册到v4l2 时,调用 v4l2_device_register_subdev()
函数。
最终调用 __video_register_device()
,传递参数 VFL_TYPE_SUBDEV
,说明是注册 sub_dev 设备。
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-device.c
int v4l2_device_register_subdev_nodes(struct v4l2_device *v4l2_dev)
{
struct video_device *vdev;
struct v4l2_subdev *sd;
/* Register a device node for every subdev marked with the V4L2_SUBDEV_FL_HAS_DEVNODE flag. */
list_for_each_entry(sd, &v4l2_dev->subdevs, list) {
vdev = kzalloc(sizeof(*vdev), GFP_KERNEL);
video_set_drvdata(vdev, sd);
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);
sd->entity.info.dev.major = VIDEO_MAJOR;
sd->entity.info.dev.minor = vdev->minor;
sd->devnode = vdev;
}
return 0;
}
EXPORT_SYMBOL_GPL(v4l2_device_register_subdev_nodes);
@ kernel/msm-4.4/drivers/media/v4l2-core/v4l2-subdev.c
const struct v4l2_file_operations v4l2_subdev_fops = {
.owner = THIS_MODULE,
.open = subdev_open,
.unlocked_ioctl = subdev_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = subdev_compat_ioctl32,
#endif
.release = subdev_close,
.poll = subdev_poll,
};
重点来看下 ioctl 函数
static long subdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, subdev_do_ioctl);
}
static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg)
{
switch (cmd) {
case VIDIOC_QUERYCTRL: return v4l2_queryctrl(vfh->ctrl_handler, arg);
case VIDIOC_QUERY_EXT_CTRL: return v4l2_query_ext_ctrl(vfh->ctrl_handler, arg);
case VIDIOC_QUERYMENU: return v4l2_querymenu(vfh->ctrl_handler, arg);
case VIDIOC_G_CTRL: return v4l2_g_ctrl(vfh->ctrl_handler, arg);
case VIDIOC_S_CTRL: return v4l2_s_ctrl(vfh, vfh->ctrl_handler, arg);
case VIDIOC_G_EXT_CTRLS: return v4l2_g_ext_ctrls(vfh->ctrl_handler, arg);
case VIDIOC_S_EXT_CTRLS: return v4l2_s_ext_ctrls(vfh, vfh->ctrl_handler, arg);
case VIDIOC_TRY_EXT_CTRLS: return v4l2_try_ext_ctrls(vfh->ctrl_handler, arg);
case VIDIOC_DQEVENT: return v4l2_event_dequeue(vfh, arg, file->f_flags & O_NONBLOCK);
case VIDIOC_SUBSCRIBE_EVENT: return v4l2_subdev_call(sd, core, subscribe_event, vfh, arg);
case VIDIOC_UNSUBSCRIBE_EVENT: return v4l2_subdev_call(sd, core, unsubscribe_event, vfh, arg);
case VIDIOC_DBG_G_REGISTER: return v4l2_subdev_call(sd, core, g_register, p);
case VIDIOC_DBG_S_REGISTER: return v4l2_subdev_call(sd, core, s_register, p);
case VIDIOC_LOG_STATUS: ret = v4l2_subdev_call(sd, core, log_status); return ret;
case VIDIOC_SUBDEV_G_FMT: return v4l2_subdev_call(sd, pad, get_fmt, subdev_fh->pad, format);
case VIDIOC_SUBDEV_S_FMT: return v4l2_subdev_call(sd, pad, set_fmt, subdev_fh->pad, format);
case VIDIOC_SUBDEV_G_CROP: rval = v4l2_subdev_call(sd, pad, get_selection, subdev_fh->pad, &sel); crop->rect = sel.r; return rval;
case VIDIOC_SUBDEV_S_CROP: rval = v4l2_subdev_call(sd, pad, set_selection, subdev_fh->pad, &sel);return rval;
case VIDIOC_SUBDEV_ENUM_MBUS_CODE: return v4l2_subdev_call(sd, pad, enum_mbus_code, subdev_fh->pad,code);
case VIDIOC_SUBDEV_ENUM_FRAME_SIZE: return v4l2_subdev_call(sd, pad, enum_frame_size, subdev_fh->pad,fse);
case VIDIOC_SUBDEV_G_FRAME_INTERVAL: return v4l2_subdev_call(sd, video, g_frame_interval, arg);
case VIDIOC_SUBDEV_S_FRAME_INTERVAL: return v4l2_subdev_call(sd, video, s_frame_interval, arg);
case VIDIOC_SUBDEV_ENUM_FRAME_INTERVAL: return v4l2_subdev_call(sd, pad, enum_frame_interval, subdev_fh->pad,fie);
case VIDIOC_SUBDEV_G_SELECTION: return v4l2_subdev_call(sd, pad, get_selection, subdev_fh->pad, sel);
case VIDIOC_SUBDEV_S_SELECTION: return v4l2_subdev_call(sd, pad, set_selection, subdev_fh->pad, sel);
case VIDIOC_G_EDID: return v4l2_subdev_call(sd, pad, get_edid, edid);
case VIDIOC_S_EDID: return v4l2_subdev_call(sd, pad, set_edid, edid);
case VIDIOC_SUBDEV_DV_TIMINGS_CAP: return v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
case VIDIOC_SUBDEV_ENUM_DV_TIMINGS: return v4l2_subdev_call(sd, pad, enum_dv_timings, dvt);
case VIDIOC_SUBDEV_QUERY_DV_TIMINGS: return v4l2_subdev_call(sd, video, query_dv_timings, arg);
case VIDIOC_SUBDEV_G_DV_TIMINGS: return v4l2_subdev_call(sd, video, g_dv_timings, arg);
case VIDIOC_SUBDEV_S_DV_TIMINGS: return v4l2_subdev_call(sd, video, s_dv_timings, arg);
}
可以发现,对 subdev 的操作,均是由 v4l2_subdev_call()
来转接,相关函数定义如下:
@ kernel/msm-4.4/include/media/v4l2-subdev.h
struct v4l2_subdev_core_ops {
int (*log_status)(struct v4l2_subdev *sd);
int (*s_io_pin_config)(struct v4l2_subdev *sd, size_t n, struct v4l2_subdev_io_pin_config *pincfg);
int (*init)(struct v4l2_subdev *sd, u32 val);
int (*load_fw)(struct v4l2_subdev *sd);
int (*reset)(struct v4l2_subdev *sd, u32 val);
int (*s_gpio)(struct v4l2_subdev *sd, u32 val);
int (*queryctrl)(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc);
int (*g_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
int (*s_ctrl)(struct v4l2_subdev *sd, struct v4l2_control *ctrl);
int (*g_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
int (*s_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
int (*try_ext_ctrls)(struct v4l2_subdev *sd, struct v4l2_ext_controls *ctrls);
int (*querymenu)(struct v4l2_subdev *sd, struct v4l2_querymenu *qm);
long (*ioctl)(struct v4l2_subdev *sd, unsigned int cmd, void *arg);
long (*compat_ioctl32)(struct v4l2_subdev *sd, unsigned int cmd,unsigned long arg);
int (*g_register)(struct v4l2_subdev *sd, struct v4l2_dbg_register *reg);
int (*s_register)(struct v4l2_subdev *sd, const struct v4l2_dbg_register *reg);
int (*s_power)(struct v4l2_subdev *sd, int on);
int (*interrupt_service_routine)(struct v4l2_subdev *sd,u32 status, bool *handled);
int (*subscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
int (*unsubscribe_event)(struct v4l2_subdev *sd, struct v4l2_fh *fh, struct v4l2_event_subscription *sub);
};
Linux V4L2之camera
Video4Linux框架简介
Video4Linux框架简介(5) - Streaming
video4linux 讲解
video4linux基础
Video4Linux