在rk 9.0之前还没有实现这个子系统,为了解决多媒体设备的复杂性和流动性,我的理解是把多媒体各个模块树状链接,可以很方便的操作各个链接点的格式分辨率等。但是这样需要代码来支持,所以就有media子系统,虽然增加了大段代码,但是这样操作更加灵活。
由于media子系统在网上很多,这里只做简单讲述,后面自己看看代码。
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的相关操作结构描述。