Linux V4L2 源码分析

前言

Video For Linux 2真的是一个很复杂的框架,抽象倒不是它复杂的原因,是因为耦合了其他框架的内容,导致要掌握V4L2必须得需要一个非常广的内核层知识面,以及Linux抽象思想。这篇文章将会以ov2640.c为例子进行源码分析,我会尽量从上往下,也就是用户层调用到内核层进行分析。
注:在Linux内核层学习,你得学会适应庞大代码给你带来的不安感。

层次

在这里插入图片描述

在这里插入图片描述
首先,像OV2640这样类似的摄像头传感器,是由多部分组成的,需要I2C作为控制,MIPI-CSI作为数据传输。
那么在V4L2的框架里,V4L2_device就是OV2640设备,而V4L2_device负责管理V4L2_subdev,V4L2_subdev分别对于I2C和MIPI-CSI硬件设备。

必要的数据结构

video_device是负责和用户层对接的数据结构,/dev/videoX和/dev/subdevX都是使用video_device及相关函数向用户空间开放用户接口(字符设备)。

struct video_device
{
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
	struct media_intf_devnode *intf_devnode;
	struct media_pipeline pipe;
#endif
	const struct v4l2_file_operations *fops;

	u32 device_caps;

	/* sysfs */
	struct device dev;
	struct cdev *cdev;

	struct v4l2_device *v4l2_dev;
	struct device *dev_parent;

	struct v4l2_ctrl_handler *ctrl_handler;

	struct vb2_queue *queue;

	struct v4l2_prio_state *prio;

	/* device info */
	char name[32];
	enum vfl_devnode_type vfl_type;
	enum vfl_devnode_direction vfl_dir;
	int minor;
	u16 num;
	unsigned long flags;
	int index;

	/* V4L2 file handles */
	spinlock_t		fh_lock;
	struct list_head	fh_list;

	int dev_debug;

	v4l2_std_id tvnorms;

	/* callbacks */
	void (*release)(struct video_device *vdev);
	const struct v4l2_ioctl_ops *ioctl_ops;
	DECLARE_BITMAP(valid_ioctls, BASE_VIDIOC_PRIVATE);

	struct mutex *lock;
};

v4l2_device 是用于管理全体子设备subdevs的数据结构。

struct v4l2_device {
	struct device *dev;
	struct media_device *mdev;
	struct list_head subdevs;
	spinlock_t lock;
	char name[V4L2_DEVICE_NAME_SIZE];
	void (*notify)(struct v4l2_subdev *sd,
			unsigned int notification, void *arg);
	struct v4l2_ctrl_handler *ctrl_handler;
	struct v4l2_prio_state prio;
	struct kref ref;
	void (*release)(struct v4l2_device *v4l2_dev);
};

v4l2_subdev是代表子设备的数据结构,I2C和MIPI,ISP都应该有一个subdev

struct v4l2_subdev {
#if defined(CONFIG_MEDIA_CONTROLLER)
	struct media_entity entity;
#endif
	struct list_head list;
	struct module *owner;
	bool owner_v4l2_dev;
	u32 flags;
	struct v4l2_device *v4l2_dev;
	const struct v4l2_subdev_ops *ops;
	const struct v4l2_subdev_internal_ops *internal_ops;
	struct v4l2_ctrl_handler *ctrl_handler;
	char name[V4L2_SUBDEV_NAME_SIZE];
	u32 grp_id;
	void *dev_priv;
	void *host_priv;
	struct video_device *devnode;
	struct device *dev;
	struct fwnode_handle *fwnode;
	struct list_head async_list;
	struct v4l2_async_subdev *asd;
	struct v4l2_async_notifier *notifier;
	struct v4l2_async_notifier *subdev_notifier;
	struct v4l2_subdev_platform_data *pdata;
};

代表数据流,不是实体设备。

struct media_device {
	/* dev->driver_data points to this struct. */
	struct device *dev;
	struct media_devnode *devnode;

	char model[32];
	char driver_name[32];
	char serial[40];
	char bus_info[32];
	u32 hw_revision;

	u64 topology_version;

	u32 id;
	struct ida entity_internal_idx;
	int entity_internal_idx_max;

	struct list_head entities; //存储pad的链表
	struct list_head interfaces;
	struct list_head pads;
	struct list_head links;

	/* notify callback list invoked when a new entity is registered */
	struct list_head entity_notify;

	/* Serializes graph operations. */
	struct mutex graph_mutex;
	struct media_graph pm_count_walk;

	void *source_priv;
	int (*enable_source)(struct media_entity *entity,
			     struct media_pipeline *pipe);
	void (*disable_source)(struct media_entity *entity);

	const struct media_device_ops *ops;

	struct mutex req_queue_mutex;
	atomic_t request_id;
};

源码分析ov2640.c

直接看Probe函数:

static int ov2640_probe(struct i2c_client *client,
			const struct i2c_device_id *did)
{
	struct ov2640_priv	*priv;  私有数据
	struct i2c_adapter	*adapter = client->adapter; 获取I2C控制器
	int			ret;

	if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
		dev_err(&adapter->dev,
			"OV2640: I2C-Adapter doesn't support SMBUS\n");
		return -EIO;
	}

	priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	if (client->dev.of_node) { 如果设备树有解析
		priv->clk = devm_clk_get(&client->dev, "xvclk"); 系统时钟输入
		if (IS_ERR(priv->clk))
			return PTR_ERR(priv->clk);
		ret = clk_prepare_enable(priv->clk); 启动时钟
		if (ret)
			return ret;
	}

	ret = ov2640_probe_dt(client, priv); 从设备树中查找指定GPIO,找不到报错
	if (ret)
		goto err_clk;

	priv->win = ov2640_select_win(SVGA_WIDTH, SVGA_HEIGHT);
	priv->cfmt_code = MEDIA_BUS_FMT_UYVY8_2X8;

前面大概做了些用于OV2640的数据填充工作。

v4l2_i2c_subdev_init(&priv->subdev, client, &ov2640_subdev_ops); //初始化subdev,绑定subdev I2C,操作函数
priv->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |    子节点需要设备节点
			      V4L2_SUBDEV_FL_HAS_EVENTS;   子节点有事件发送

v4l2_i2c_subdev_init初始化subdev里面的数据,将ops赋值给subdev。

v4l2_ctrl_handler_init(&priv->hdl, 3); 分配空间
	priv->hdl.lock = &priv->lock;
	v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops,
			V4L2_CID_VFLIP, 0, 1, 1, 0); 添加非菜单控件,用户在使用ioctl时候会调用的函数 V4L2_CID_VFLIP为属性
	v4l2_ctrl_new_std(&priv->hdl, &ov2640_ctrl_ops,
			V4L2_CID_HFLIP, 0, 1, 1, 0);
	v4l2_ctrl_new_std_menu_items(&priv->hdl, &ov2640_ctrl_ops,
			V4L2_CID_TEST_PATTERN,
			ARRAY_SIZE(ov2640_test_pattern_menu) - 1, 0, 0,
			ov2640_test_pattern_menu);
	priv->subdev.ctrl_handler = &priv->hdl;  给subdev里添加控件(用户空间使用)
	if (priv->hdl.error) {
		ret = priv->hdl.error;
		goto err_hdl;
	}

这里是一些Ctrl的设置

	ret = ov2640_video_probe(client);
	if (ret < 0)
		goto err_videoprobe;

	ret = v4l2_async_register_subdev(&priv->subdev);//注册subdev
	if (ret < 0)
		goto err_videoprobe;

	dev_info(&adapter->dev, "OV2640 Probed\n");

	return 0;

设置好subdev的相关信息后,通知sun4i_csi模块异步注册video_device.

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值