camera_kernel之---media子系统(3)

在rk 9.0之前还没有实现这个子系统,为了解决多媒体设备的复杂性和流动性,我的理解是把多媒体各个模块树状链接,可以很方便的操作各个链接点的格式分辨率等。但是这样需要代码来支持,所以就有media子系统,虽然增加了大段代码,但是这样操作更加灵活。
由于media子系统在网上很多,这里只做简单讲述,后面自己看看代码。
camera_kernel之---media子系统(3)_第1张图片
Media子系统是一种pipeline形式
Rk从sensor到ddr设定了4个设备如下
sensor —> DPHY —> isp —> stream
其中每个设备都认为是一个entity,是一个节点。每个都包含一个或者多个pads,可以认为是各个分叉,link就是将各个分叉pad链接起来,也就是谁链接谁。谁是源端,谁是目的端。
根据上图:
sensor有ov5695和ov2659,它们只有一个pad,因为只能输出
DPHY 就是mipi dphy 接收sensor数据,输出输出到isp,所有有2个pads
Isp 因为isp除了要接收dphy进来的数据,还要接收input参数,输出统计信息,最后将数据输出到 mainpath和selfpath(这个不清楚是否rkisp独有),4个pads
Mp和sp 接收isp处理好的数据 一个pad。

下面看下media设备的注册,以及相关操作函数
Media设备结构体

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

	char model[32];	//名字以及相关信息
	char serial[40];
	char bus_info[32];
	u32 hw_revision;
	u32 driver_version;

	u32 entity_id;
	struct list_head entities;	//entity 列表

	/* Protects the entities list */
	spinlock_t lock;
	/* Serializes graph operations. */
	struct mutex graph_mutex;

	int (*link_notify)(struct media_link *link, u32 flags,
			   unsigned int notification);//link改变后的callback函数
};

在rkisp1_plat_probe函数是初始化rkisp1_device结构体的,里面包含media_device结构,

static int rkisp1_plat_probe(struct platform_device *pdev)
{
……
strlcpy(isp_dev->media_dev.model, "rkisp1",
		sizeof(isp_dev->media_dev.model));
isp_dev->media_dev.dev = &pdev->dev;
isp_dev->media_dev.link_notify = rkisp1_pipeline_link_notify;
media_device_register(&isp_dev->media_dev);
rkisp1_register_platform_subdevs (isp_dev);

media_device_register这个是公共函数,

int __must_check __media_device_register(struct media_device *mdev,
					 struct module *owner)
{
	int ret;

	if (WARN_ON(mdev->dev == NULL || mdev->model[0] == 0))
		return -EINVAL;

	mdev->entity_id = 1;
	INIT_LIST_HEAD(&mdev->entities);
	spin_lock_init(&mdev->lock);
	mutex_init(&mdev->graph_mutex);

	/* Register the device node. */
	mdev->devnode.fops = &media_device_fops;//ioctl函数就在这里
	mdev->devnode.parent = mdev->dev;
	mdev->devnode.release = media_device_release;
	ret = media_devnode_register(&mdev->devnode, owner);
	if (ret < 0)
		return ret;

	ret = device_create_file(&mdev->devnode.dev, &dev_attr_model);
	if (ret < 0) {
		media_devnode_unregister(&mdev->devnode);
		return ret;
	}

	return 0;
}

创建节点和文件。

static int rkisp1_register_platform_subdevs(struct rkisp1_device *dev)
{
	ret = rkisp1_register_isp_subdev(dev, &dev->v4l2_dev);//ispsubdev节点
	if (ret < 0)
		goto err_cleanup_ctx;

	ret = rkisp1_register_stream_vdevs(dev);//video节点
	if (ret < 0)
		goto err_unreg_isp_subdev;
……
ret = rkisp1_register_stats_vdev(&dev->stats_vdev, &dev->v4l2_dev, dev); 
ret = rkisp1_register_params_vdev(&dev->params_vdev, &dev->v4l2_dev,dev);
ret = isp_subdev_notifier(dev);
……

isp_subdev_notifier。
调用v4l2_async_notifier_parse_fwnode_endpoints
(里面包含endpoints的解析)和v4l2_async_notifier_register。
异步注册,就是父设备中调用v4l2_async_notifier_register(),并实现v4l2_async_notifier_operations结构体中的bound(),complete(),unbound()三个函数,当子设备调用v4l2_device_register_subdev()进行注册的时候,会根据match_type去进行匹配,如果匹配成功,则父设备就会收到通知,并且父设备中实现的bound函数就会被调用(子设备有几次匹配成功,该bound就会被调用几次),当所有的子设备都注册并匹配成功后,父设备中的complete()函数就会被调用,为所有的子设备生成设备节点的函数v4l2_device_register_subdev_nodes()一般都是在父设备的complete()函数中来调用的,所以complete函数打印rkisp1: Async subdev notifier completed说明注册成功

看注册isp subdev和初始化它的entity

int rkisp1_register_isp_subdev(struct rkisp1_device *isp_dev,
			       struct v4l2_device *v4l2_dev)
{
	struct rkisp1_isp_subdev *isp_sdev = &isp_dev->isp_sdev;
	struct v4l2_subdev *sd = &isp_sdev->sd;
	int ret;

	v4l2_subdev_init(sd, &rkisp1_isp_sd_ops);
	sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
	sd->entity.ops = &rkisp1_isp_sd_media_ops;//entity的操作函数,

	snprintf(sd->name, sizeof(sd->name), "rkisp1-isp-subdev");

	isp_sdev->pads[RKISP1_ISP_PAD_SINK].flags =
		MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
	isp_sdev->pads[RKISP1_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK;
	isp_sdev->pads[RKISP1_ISP_PAD_SOURCE_PATH].flags = MEDIA_PAD_FL_SOURCE;
	isp_sdev->pads[RKISP1_ISP_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE;
	ret = media_entity_init(&sd->entity, RKISP1_ISP_PAD_MAX,
				isp_sdev->pads, 0);
ret = v4l2_device_register_subdev(v4l2_dev, sd);//注册subdev
//注册input参数和统计设备
}

这部分是初始化rkisp1-isp-subdev这个entity和里面的pad,里面links分配结构空间。

再看isp vdev部分

int rkisp1_register_stream_vdevs(struct rkisp1_device *dev)
{
	struct rkisp1_stream *stream;
	int i, j, ret;

	for (i = 0; i < RKISP1_MAX_STREAM; i++) {
		stream = &dev->stream[i];
		stream->ispdev = dev;
		ret = rkisp1_register_stream_vdev(stream);
……
}
rkisp1_register_stream_vdev里面注册了mp,sp和raw 3个video节点,对应有操作函数和ioctl。初始化它们的pad,这几个只有一个pad。

上面注册了isp mp和sp的entity ,然后看看其他dphy和sensor怎么注册成为subdev
以ov5695为例:
在驱动probe函数调用
media_entity_init(&sd->entity, 1, &ov5695->pad, 0);//初始化entity,里面一个pad
v4l2_async_register_subdev_sensor_common //异步注册
phy-rockchip-mipi-rx.c,mipi 驱动也是也同样注册方式,注册2个pad。

当前问题,这些entity怎么链接起来的,看dts定义了一些remote-endpoint,
上面看v4l2_async_notifier_parse_fwnode_endpoints会解析这些端点。
异步注册绑定。

v4l2_async_notifier_operations rockchip_mipidphy_async_ops = {
	.bound = rockchip_mipidphy_notifier_bound,
	.unbind = rockchip_mipidphy_notifier_unbind,
};
static const struct v4l2_async_notifier_operations subdev_notifier_ops = {
	.bound = subdev_notifier_bound,//子设备匹配成功
	.complete = subdev_notifier_complete,//所有子设备匹配成功
};

子设备绑定成功后会调用bound函数,也就是camera绑定mipiphy,mipiphy绑定isp。Bound函数创建了link。
当所有的子设备绑定完之后会调用subdev_notifier_complete函数

static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
{
	ret = rkisp1_create_links(dev);// media_entity_create_link,enable相关link
ret = v4l2_device_register_subdev_nodes(&dev->v4l2_dev);//生成设备节点
	ret = rkisp1_update_sensor_info(dev);
ret = _set_pipeline_default_fmt(dev);
v4l2_info(&dev->v4l2_dev, "Async subdev notifier completed\n");
}

接下来看下media有哪些ioctl

#define MEDIA_IOC_DEVICE_INFO		_IOWR('|', 0x00, struct media_device_info)
#define MEDIA_IOC_ENUM_ENTITIES		_IOWR('|', 0x01, struct media_entity_desc)
#define MEDIA_IOC_ENUM_LINKS		_IOWR('|', 0x02, struct media_links_enum)
#define MEDIA_IOC_SETUP_LINK		_IOWR('|', 0x03, struct media_link_desc)
static const struct media_file_operations media_device_fops = {
	.owner = THIS_MODULE,
	.open = media_device_open,
	.ioctl = media_device_ioctl,	//所以只需关注这个函数
#ifdef CONFIG_COMPAT
	.compat_ioctl = media_device_compat_ioctl,
#endif /* CONFIG_COMPAT */
	.release = media_device_close,
};
static long media_device_ioctl(struct file *filp, unsigned int cmd,
			       unsigned long arg)
{
	struct media_devnode *devnode = media_devnode_data(filp);
	struct media_device *dev = to_media_device(devnode);
	long ret;

	switch (cmd) {
	case MEDIA_IOC_DEVICE_INFO:
		ret = media_device_get_info(dev,
				(struct media_device_info __user *)arg);
		break;//直接从media_device结构里获取信息返回

	case MEDIA_IOC_ENUM_ENTITIES:
		ret = media_device_enum_entities(dev,
				(struct media_entity_desc __user *)arg);
		break;//只是找到对应entity

	case MEDIA_IOC_ENUM_LINKS:
		mutex_lock(&dev->graph_mutex);
		ret = media_device_enum_links(dev,
				(struct media_links_enum __user *)arg);
		mutex_unlock(&dev->graph_mutex);
		break;//这个是枚举一个entity的pads和links,
//__media_device_enum_links函数里会判断links->pads,links->links决定枚举单个还是两个一起。注意links里面不会返回backlink到应用。(在创建link的时候,我在源端和sink端都会创建links。这样就会多一个backlink,创建时候当前entity的源端entity不一致,就说明是backlink,也就返回应用的link都是本地到外部的link)
	case MEDIA_IOC_SETUP_LINK:
		mutex_lock(&dev->graph_mutex);
		ret = media_device_setup_link(dev,
				(struct media_link_desc __user *)arg);
		mutex_unlock(&dev->graph_mutex);
		break;//先是从参数找到source和sink的entity,再从source中找到link和当前要设置的链接匹配然后调用

int __media_entity_setup_link(struct media_link *link, u32 flags)
{
……
	mdev = source->parent;
	if (mdev->link_notify) {
		ret = mdev->link_notify(link, flags,
					MEDIA_DEV_NOTIFY_PRE_LINK_CH);
		if (ret < 0)
			return ret;
	}
	ret = __media_entity_setup_link_notify(link, flags);
	if (mdev->link_notify)
		mdev->link_notify(link, flags, MEDIA_DEV_NOTIFY_POST_LINK_CH);
static int __media_entity_setup_link_notify(struct media_link *link, u32 flags)
{
……
	ret = media_entity_call(link->source->entity, link_setup,
				link->source, link->sink, flags);
	ret = media_entity_call(link->sink->entity, link_setup,
				link->sink, link->source, flags);
分辨执行source和sink的ops的link_setup函数
sd->entity.ops = &rkisp1_isp_sd_media_ops;//只有isp有entity的操作函数
实际只判断如果是dma设备置了变量,没做什么

Setup前后调用

static int rkisp1_pipeline_link_notify(struct media_link *link, u32 flags,
				    unsigned int notification)
{
	struct media_entity *source = link->source->entity;
	struct media_entity *sink = link->sink->entity;
	int source_use = rkisp1_pipeline_pm_use_count(source);
	int sink_use = rkisp1_pipeline_pm_use_count(sink);
	int ret;

	if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH &&
	    !(flags & MEDIA_LNK_FL_ENABLED)) {
		/* Powering off entities is assumed to never fail. */
		rkisp1_pipeline_pm_power(source, -sink_use);
		rkisp1_pipeline_pm_power(sink, -source_use);
		return 0;
	}

	if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH &&
		(flags & MEDIA_LNK_FL_ENABLED)) {
		ret = rkisp1_pipeline_pm_power(source, sink_use);
		if (ret < 0)
			return ret;

		ret = rkisp1_pipeline_pm_power(sink, source_use);
		if (ret < 0)
			rkisp1_pipeline_pm_power(source, -sink_use);

		return ret;
	}

	return 0;
}

看着名字似乎是只是电源管理,其实也是,使能链接就是把对应的电源供上。
遍历调用s_power函数

.s_power = rkisp1_isp_sd_s_power, isp的power函数
.s_power = mipidphy_s_power, phy的power函数
.s_power = ov13850_s_power camera的power函数,比如ov13580
小结: media子系统相对来说比较简单,只是对media设备和entity pad link的相关操作结构描述。

你可能感兴趣的:(camera,linux,v4l2相关接口)