【高通SDM660平台】Camera V4L2 驱动层分析



一、Camera V4L2 驱动层分析

Linux系统中视频输入设备主要包括以下四个部分:

  1. 字符设备驱动:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
  2. V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
  3. 平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v4l2_dev;
  4. 具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev。

V4L2核心源码位于drivers/media/v4l2-core,根据功能可以划分为四类:

  1. 字符设备模块:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数。
  2. V4L2基础框架:由v4l2-device.c、v4l2-subdev.c、v4l2-fh.c、v4l2-ctrls.c等文件构建V4L2基础框架。
  3. videobuf管理
    videobuf2-core.c、videobuf2-dma-contig.c、videobuf2-dma-sg.c、videobuf2-memops.c、videobuf2-vmalloc.c、v4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。
  4. Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。

二、V4L2基础框架

2.1 /media/v4l2-core/v4l2-dev.c

在该文件中,主要是负责创建/sys/classs/video4linux目录 ,当有设备注册进来时,创建对应的 /dev/videox 、/dev/vbix、/dev/radiox、/dev/subdevx等节点。

主要工作如下:

  1. 将字符设备号(81,0)(81,255)这期间256个字次设备号,均申请为 v4l2 使用,name=video4linux
  2. 注册 /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;
}

2.2 注册V4L2设备 __video_register_device()

当用设备需要注册为 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()

2.3 __video_register_device()

"qcom,msm-cam"为例,其注册时,传递的 nr = -1,说明从第一个开始分配,也就是 /dev/video0
因此 /dev/video0对应的设备为"qcom,msm-cam",其设备类型为 video

主要工作如下:

  1. 初始化 fh->list
  2. 检查设备类型
  3. 寻找一个不在使用的 次设备号, 主设备号为 81,(0~63 为video)(128,191 为sub-dev)
  4. 获取 index,将当前需要注册的 video_device 设备保存在 video_device[]全局数组中
  5. 分配对应的字符设备,字符设备号,就是前面的 (81,minor)
  6. 分配对应的sys节点 /sys/class/video4linux/video0
  7. 注册release 时调用的函数
  8. 将该 v4l2 subdevice 当成一个 entity 注册到 media device
  9. 注册release 时调用的函数
@ 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);

2.3.1 字符设备操作函数 v4l2_fops

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

2.3.1.1 video ioctl函数 do_video_ioctl()

当操作/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;
}

2.4 注册子设备 /media/v4l2-core/v4l2-subdev.c

当有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);

2.4.1 子设备操作函数 v4l2_fops

@  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

发布了355 篇原创文章 · 获赞 81 · 访问量 12万+

猜你喜欢

转载自blog.csdn.net/Ciellee/article/details/105483079