mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-23 09:42:19 +03:00
The earlier porting of the driver for Linux 6.6 is not
matching with the previous kernel implementation.
Few V4L2 APIs have been deprecated from Linux 6.6. and
added the new APIs.
Use the correct API available from Linux6.6 for the
replacement as the previous supported kernel APIs.
For reference, the changes in core kernels are:
The device argument is added in the API v4l2_async_nf_init() with
commit b8ec754ae4c5 ("media: v4l: async: Set v4l2_device and subdev
in async notifier init") in Linux 6.6.
The API is removed with commit bda8953e8c3e ("media: v4l: async: Drop
v4l2_async_nf_parse_fwnode_endpoints()") in Linux 6.6
Bug 4346767
Change-Id: I94877a8fe6a6764fc3c913fea7ecdbfdc7c793d6
Signed-off-by: Laxman Dewangan <ldewangan@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3042993
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
741 lines
20 KiB
C
741 lines
20 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* NVIDIA Media controller graph management
|
|
*
|
|
* Copyright (c) 2015-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
*/
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include <linux/clk.h>
|
|
#include <linux/list.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_graph.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
#include <linux/reset.h>
|
|
#include <linux/slab.h>
|
|
#include <soc/tegra/fuse.h>
|
|
#include <media/media-device.h>
|
|
#include <media/v4l2-async.h>
|
|
#include <media/v4l2-common.h>
|
|
#include <media/v4l2-device.h>
|
|
#include <media/v4l2-fwnode.h>
|
|
#include <media/tegra_v4l2_camera.h>
|
|
#include <media/mc_common.h>
|
|
#include <media/csi.h>
|
|
#include "nvcsi/nvcsi.h"
|
|
|
|
/* -----------------------------------------------------------------------------
|
|
* Graph Management
|
|
*/
|
|
|
|
static struct tegra_vi_graph_entity *
|
|
tegra_vi_graph_find_entity(struct tegra_channel *chan,
|
|
const struct device_node *node)
|
|
{
|
|
struct tegra_vi_graph_entity *entity;
|
|
|
|
list_for_each_entry(entity, &chan->entities, list) {
|
|
if (entity->node == node)
|
|
return entity;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int tegra_vi_graph_build_one(struct tegra_channel *chan,
|
|
struct tegra_vi_graph_entity *entity)
|
|
{
|
|
u32 link_flags = MEDIA_LNK_FL_ENABLED;
|
|
struct media_entity *local;
|
|
struct media_entity *remote;
|
|
struct media_pad *local_pad;
|
|
struct media_pad *remote_pad;
|
|
struct tegra_vi_graph_entity *ent;
|
|
struct v4l2_fwnode_link link;
|
|
struct device_node *ep = NULL;
|
|
struct device_node *next;
|
|
int ret = 0;
|
|
|
|
if (!entity->subdev) {
|
|
dev_err(chan->vi->dev, "%s:No subdev under entity, skip linking\n",
|
|
__func__);
|
|
return 0;
|
|
}
|
|
|
|
local = entity->entity;
|
|
dev_dbg(chan->vi->dev, "creating links for entity %s\n", local->name);
|
|
|
|
do {
|
|
/* Get the next endpoint and parse its link. */
|
|
next = of_graph_get_next_endpoint(entity->node, ep);
|
|
if (next == NULL)
|
|
break;
|
|
|
|
ep = next;
|
|
|
|
dev_dbg(chan->vi->dev, "processing endpoint %pOF\n",
|
|
ep);
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
|
|
#else
|
|
dev_err(chan->vi->dev, "CONFIG_V4L2_FWNODE not enabled!\n");
|
|
ret = -ENOTSUPP;
|
|
#endif
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev,
|
|
"failed to parse link for %pOF\n", ep);
|
|
continue;
|
|
}
|
|
|
|
if (link.local_port >= local->num_pads) {
|
|
dev_err(chan->vi->dev,
|
|
"invalid port number %u for %pOF\n",
|
|
link.local_port, to_of_node(link.local_node));
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
local_pad = &local->pads[link.local_port];
|
|
|
|
/* Skip sink ports, they will be processed from the other end of
|
|
* the link.
|
|
*/
|
|
if (local_pad->flags & MEDIA_PAD_FL_SINK) {
|
|
dev_dbg(chan->vi->dev, "skipping sink port %pOF:%u\n",
|
|
to_of_node(link.local_node), link.local_port);
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
/* Skip channel entity , they will be processed separately. */
|
|
if (link.remote_node == of_fwnode_handle(chan->vi->dev->of_node)) {
|
|
dev_dbg(chan->vi->dev, "skipping channel port %pOF:%u\n",
|
|
to_of_node(link.local_node), link.local_port);
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
continue;
|
|
}
|
|
|
|
/* Find the remote entity. */
|
|
ent = tegra_vi_graph_find_entity(chan, to_of_node(link.remote_node));
|
|
if (ent == NULL) {
|
|
dev_err(chan->vi->dev, "no entity found for %pOF\n",
|
|
to_of_node(link.remote_node));
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
remote = ent->entity;
|
|
|
|
if (link.remote_port >= remote->num_pads) {
|
|
dev_err(chan->vi->dev, "invalid port number %u on %pOF\n",
|
|
link.remote_port, to_of_node(link.remote_node));
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
remote_pad = &remote->pads[link.remote_port];
|
|
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
|
|
/* Create the media link. */
|
|
dev_dbg(chan->vi->dev, "creating %s:%u -> %s:%u link\n",
|
|
local->name, local_pad->index,
|
|
remote->name, remote_pad->index);
|
|
|
|
ret = tegra_media_create_link(local, local_pad->index, remote,
|
|
remote_pad->index, link_flags);
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev,
|
|
"failed to create %s:%u -> %s:%u link\n",
|
|
local->name, local_pad->index,
|
|
remote->name, remote_pad->index);
|
|
break;
|
|
}
|
|
} while (next);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int tegra_vi_graph_build_links(struct tegra_channel *chan)
|
|
{
|
|
u32 link_flags = MEDIA_LNK_FL_ENABLED;
|
|
struct media_entity *source;
|
|
struct media_entity *sink;
|
|
struct media_pad *source_pad;
|
|
struct media_pad *sink_pad;
|
|
struct tegra_vi_graph_entity *ent;
|
|
struct v4l2_fwnode_link link;
|
|
struct device_node *ep = NULL;
|
|
int ret = 0;
|
|
|
|
dev_dbg(chan->vi->dev, "creating links for channels\n");
|
|
|
|
/* Device not registered */
|
|
if (!chan->init_done)
|
|
return -EINVAL;
|
|
|
|
ep = chan->endpoint_node;
|
|
|
|
dev_dbg(chan->vi->dev, "processing endpoint %pOF\n", ep);
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
|
|
#else
|
|
dev_err(chan->vi->dev, "CONFIG_V4L2_FWNODE not enabled!\n");
|
|
ret = -ENOTSUPP;
|
|
#endif
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev, "failed to parse link for %pOF\n",
|
|
ep);
|
|
return ret;
|
|
}
|
|
|
|
if (link.local_port >= chan->vi->num_channels) {
|
|
dev_err(chan->vi->dev, "wrong channel number for port %u\n",
|
|
link.local_port);
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_dbg(chan->vi->dev, "creating link for channel %s\n",
|
|
chan->video->name);
|
|
|
|
/* Find the remote entity. */
|
|
ent = tegra_vi_graph_find_entity(chan, to_of_node(link.remote_node));
|
|
if (ent == NULL) {
|
|
dev_err(chan->vi->dev, "no entity found for %pOF\n",
|
|
to_of_node(link.remote_node));
|
|
v4l2_fwnode_put_link(&link);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (ent->entity == NULL) {
|
|
dev_err(chan->vi->dev, "entity not bounded %pOF\n",
|
|
to_of_node(link.remote_node));
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
return -EINVAL;
|
|
}
|
|
|
|
source = ent->entity;
|
|
source_pad = &source->pads[link.remote_port];
|
|
sink = &chan->video->entity;
|
|
sink_pad = &chan->pad;
|
|
|
|
#if defined(CONFIG_V4L2_FWNODE)
|
|
v4l2_fwnode_put_link(&link);
|
|
#endif
|
|
|
|
/* Create the media link. */
|
|
dev_dbg(chan->vi->dev, "creating %s:%u -> %s:%u link\n",
|
|
source->name, source_pad->index,
|
|
sink->name, sink_pad->index);
|
|
|
|
ret = tegra_media_create_link(source, source_pad->index,
|
|
sink, sink_pad->index,
|
|
link_flags);
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev,
|
|
"failed to create %s:%u -> %s:%u link\n",
|
|
source->name, source_pad->index,
|
|
sink->name, sink_pad->index);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = tegra_channel_init_subdevices(chan);
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev, "Failed to initialize sub-devices\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tegra_vi_graph_remove_links(struct tegra_channel *chan)
|
|
{
|
|
struct tegra_vi_graph_entity *entity;
|
|
|
|
/* remove entity links and subdev for nvcsi */
|
|
entity = list_first_entry(&chan->entities,
|
|
struct tegra_vi_graph_entity, list);
|
|
if (entity->entity != NULL) {
|
|
media_entity_remove_links(entity->entity);
|
|
video_unregister_device(entity->subdev->devnode);
|
|
}
|
|
|
|
/* remove video node for vi */
|
|
tegra_channel_remove_subdevices(chan);
|
|
}
|
|
|
|
static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
|
|
{
|
|
struct tegra_channel *chan =
|
|
container_of(notifier, struct tegra_channel, notifier);
|
|
struct tegra_vi_graph_entity *entity;
|
|
int ret;
|
|
|
|
dev_dbg(chan->vi->dev, "notify complete, all subdevs registered\n");
|
|
|
|
/* Allocate video_device */
|
|
ret = tegra_channel_init_video(chan);
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev, "failed to allocate video device %s\n",
|
|
chan->video->name);
|
|
return ret;
|
|
}
|
|
|
|
ret = video_register_device(chan->video, VFL_TYPE_VIDEO, -1);
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev, "failed to register %s\n",
|
|
chan->video->name);
|
|
goto register_device_error;
|
|
}
|
|
|
|
/* Create links for every entity. */
|
|
list_for_each_entry(entity, &chan->entities, list) {
|
|
if (entity->entity != NULL) {
|
|
ret = tegra_vi_graph_build_one(chan, entity);
|
|
if (ret < 0)
|
|
goto graph_error;
|
|
}
|
|
}
|
|
|
|
/* Create links for channels */
|
|
ret = tegra_vi_graph_build_links(chan);
|
|
if (ret < 0)
|
|
goto graph_error;
|
|
|
|
ret = v4l2_device_register_subdev_nodes(&chan->vi->v4l2_dev);
|
|
if (ret < 0) {
|
|
dev_err(chan->vi->dev, "failed to register subdev nodes\n");
|
|
goto graph_error;
|
|
}
|
|
|
|
chan->link_status++;
|
|
|
|
return 0;
|
|
|
|
graph_error:
|
|
video_unregister_device(chan->video);
|
|
register_device_error:
|
|
video_device_release(chan->video);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(NV_V4L2_ASYNC_CONNECTION_STRUCT_PRESENT) /* Linux 6.5 */
|
|
static int tegra_vi_graph_notify_bound(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *subdev,
|
|
struct v4l2_async_connection *asd)
|
|
#else
|
|
static int tegra_vi_graph_notify_bound(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *subdev,
|
|
struct v4l2_async_subdev *asd)
|
|
#endif
|
|
{
|
|
struct tegra_channel *chan =
|
|
container_of(notifier, struct tegra_channel, notifier);
|
|
struct tegra_vi_graph_entity *entity;
|
|
|
|
/* Locate the entity corresponding to the bound subdev and store the
|
|
* subdev pointer.
|
|
*/
|
|
list_for_each_entry(entity, &chan->entities, list) {
|
|
if (entity->node != to_of_node(subdev->dev->fwnode) &&
|
|
entity->node != to_of_node(subdev->fwnode))
|
|
continue;
|
|
|
|
if (entity->subdev) {
|
|
dev_err(chan->vi->dev, "duplicate subdev for node %pOF\n",
|
|
entity->node);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dev_info(chan->vi->dev, "subdev %s bound\n", subdev->name);
|
|
entity->entity = &subdev->entity;
|
|
entity->subdev = subdev;
|
|
chan->subdevs_bound++;
|
|
return 0;
|
|
}
|
|
|
|
dev_err(chan->vi->dev, "no entity for subdev %s\n", subdev->name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
#if defined(NV_V4L2_ASYNC_CONNECTION_STRUCT_PRESENT) /* Linux 6.5 */
|
|
static void tegra_vi_graph_notify_unbind(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *subdev,
|
|
struct v4l2_async_connection *asd)
|
|
#else
|
|
static void tegra_vi_graph_notify_unbind(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_subdev *subdev,
|
|
struct v4l2_async_subdev *asd)
|
|
#endif
|
|
{
|
|
struct tegra_channel *chan =
|
|
container_of(notifier, struct tegra_channel, notifier);
|
|
struct tegra_vi_graph_entity *entity;
|
|
|
|
/* cleanup for complete */
|
|
if (chan->link_status) {
|
|
tegra_vi_graph_remove_links(chan);
|
|
tegra_channel_cleanup_video(chan);
|
|
chan->link_status--;
|
|
}
|
|
|
|
/* cleanup for bound */
|
|
list_for_each_entry(entity, &chan->entities, list) {
|
|
if (entity->subdev == subdev) {
|
|
/* remove subdev node */
|
|
chan->subdevs_bound--;
|
|
entity->subdev = NULL;
|
|
entity->entity = NULL;
|
|
dev_info(chan->vi->dev, "subdev %s unbind\n",
|
|
subdev->name);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void tegra_vi_graph_cleanup(struct tegra_mc_vi *vi)
|
|
{
|
|
struct tegra_vi_graph_entity *entityp;
|
|
struct tegra_vi_graph_entity *entity;
|
|
struct tegra_channel *chan;
|
|
|
|
list_for_each_entry(chan, &vi->vi_chans, list) {
|
|
#if defined(CONFIG_V4L2_ASYNC)
|
|
#if defined(NV_V4L2_ASYNC_NOTIFIER_INIT_PRESENT)
|
|
v4l2_async_notifier_unregister(&chan->notifier);
|
|
#else
|
|
v4l2_async_nf_unregister(&chan->notifier);
|
|
#endif
|
|
#endif
|
|
list_for_each_entry_safe(entity, entityp,
|
|
&chan->entities, list) {
|
|
of_node_put(entity->node);
|
|
list_del(&entity->list);
|
|
}
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(tegra_vi_graph_cleanup);
|
|
|
|
static int tegra_vi_graph_parse_one(struct tegra_channel *chan,
|
|
struct device_node *node)
|
|
{
|
|
struct device_node *ep = NULL;
|
|
struct device_node *next;
|
|
struct device_node *remote = NULL;
|
|
struct tegra_vi_graph_entity *entity;
|
|
int ret = 0;
|
|
|
|
dev_dbg(chan->vi->dev, "parsing node %s\n", node->full_name);
|
|
/* Parse all the remote entities and put them into the list */
|
|
do {
|
|
next = of_graph_get_next_endpoint(node, ep);
|
|
if (next == NULL || !of_device_is_available(next))
|
|
break;
|
|
ep = next;
|
|
|
|
dev_dbg(chan->vi->dev, "handling endpoint %s\n", ep->full_name);
|
|
|
|
remote = of_graph_get_remote_port_parent(ep);
|
|
if (!remote) {
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
|
|
/* skip the vi of_node and duplicated entities */
|
|
if (remote == chan->vi->dev->of_node ||
|
|
tegra_vi_graph_find_entity(chan, remote) ||
|
|
!of_device_is_available(remote))
|
|
continue;
|
|
|
|
entity = devm_kzalloc(chan->vi->dev, sizeof(*entity),
|
|
GFP_KERNEL);
|
|
if (entity == NULL) {
|
|
ret = -ENOMEM;
|
|
break;
|
|
}
|
|
|
|
entity->node = remote;
|
|
#if !defined(NV_V4L2_ASYNC_CONNECTION_STRUCT_PRESENT) /* Linux 6.5 */
|
|
#if defined(NV_V4L2_ASYNC_MATCH_TYPE_ENUM_PRESENT) /* Linux 6.5 */
|
|
entity->asd.match.type = V4L2_ASYNC_MATCH_TYPE_FWNODE;
|
|
#else
|
|
entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
|
|
#endif
|
|
entity->asd.match.fwnode = of_fwnode_handle(remote);
|
|
#endif
|
|
|
|
list_add_tail(&entity->list, &chan->entities);
|
|
chan->num_subdevs++;
|
|
|
|
/* Find remote entities, which are linked to this entity */
|
|
ret = tegra_vi_graph_parse_one(chan, entity->node);
|
|
if (ret < 0)
|
|
break;
|
|
} while (next);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int tegra_vi_tpg_graph_init(struct tegra_mc_vi *mc_vi)
|
|
{
|
|
int err = 0;
|
|
u32 link_flags = MEDIA_LNK_FL_ENABLED;
|
|
struct tegra_csi_device *csi = mc_vi->csi;
|
|
struct tegra_channel *vi_it;
|
|
struct tegra_csi_channel *csi_it;
|
|
|
|
if (!csi) {
|
|
dev_err(mc_vi->dev, "CSI is NULL\n");
|
|
return -EINVAL;
|
|
}
|
|
mc_vi->num_subdevs = mc_vi->num_channels;
|
|
vi_it = mc_vi->tpg_start;
|
|
csi_it = csi->tpg_start;
|
|
|
|
list_for_each_entry_from(vi_it, &mc_vi->vi_chans, list) {
|
|
/* Device not registered */
|
|
if (!vi_it->init_done)
|
|
continue;
|
|
|
|
list_for_each_entry_from(csi_it, &csi->csi_chans, list) {
|
|
struct media_entity *source = &csi_it->subdev.entity;
|
|
struct media_entity *sink = &vi_it->video->entity;
|
|
struct media_pad *source_pad = csi_it->pads;
|
|
struct media_pad *sink_pad = &vi_it->pad;
|
|
|
|
vi_it->bypass = 0;
|
|
err = v4l2_device_register_subdev(&mc_vi->v4l2_dev,
|
|
&csi_it->subdev);
|
|
if (err) {
|
|
dev_err(mc_vi->dev,
|
|
"%s:Fail to register subdev\n",
|
|
__func__);
|
|
goto register_fail;
|
|
}
|
|
dev_dbg(mc_vi->dev, "creating %s:%u -> %s:%u link\n",
|
|
source->name, source_pad->index,
|
|
sink->name, sink_pad->index);
|
|
|
|
err = tegra_media_create_link(source, source_pad->index,
|
|
sink, sink_pad->index, link_flags);
|
|
if (err < 0) {
|
|
dev_err(mc_vi->dev,
|
|
"failed to create %s:%u -> %s:%u link\n",
|
|
source->name, source_pad->index,
|
|
sink->name, sink_pad->index);
|
|
goto register_fail;
|
|
}
|
|
err = tegra_channel_init_subdevices(vi_it);
|
|
if (err) {
|
|
dev_err(mc_vi->dev,
|
|
"%s:Init subdevice error\n", __func__);
|
|
goto register_fail;
|
|
}
|
|
csi_it = list_next_entry(csi_it, list);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
register_fail:
|
|
csi_it = csi->tpg_start;
|
|
list_for_each_entry_from(csi_it, &csi->csi_chans, list)
|
|
v4l2_device_unregister_subdev(&csi_it->subdev);
|
|
return err;
|
|
}
|
|
EXPORT_SYMBOL(tegra_vi_tpg_graph_init);
|
|
|
|
int tegra_vi_graph_init(struct tegra_mc_vi *vi)
|
|
{
|
|
struct tegra_vi_graph_entity *entity;
|
|
unsigned int num_subdevs = 0;
|
|
int ret = 0, i;
|
|
struct device_node *ep = NULL;
|
|
struct device_node *next;
|
|
struct device_node *remote = NULL;
|
|
struct tegra_channel *chan;
|
|
static const struct v4l2_async_notifier_operations vi_chan_notify_ops = {
|
|
.bound = tegra_vi_graph_notify_bound,
|
|
.complete = tegra_vi_graph_notify_complete,
|
|
.unbind = tegra_vi_graph_notify_unbind,
|
|
};
|
|
|
|
/*
|
|
* Walk the links to parse the full graph. Each struct tegra_channel
|
|
* in vi->vi_chans points to each endpoint of the composite node.
|
|
* Thus parse the remote entity for each endpoint in turn.
|
|
* Each channel will register a v4l2 async notifier, this makes graph
|
|
* init independent between vi_chans. There we can skip the current
|
|
* channel in case of something wrong during graph parsing and try
|
|
* the next channel. Return error only if memory allocation is failed.
|
|
*/
|
|
chan = list_first_entry(&vi->vi_chans, struct tegra_channel, list);
|
|
do {
|
|
/* Get the next endpoint and parse its entities. */
|
|
next = of_graph_get_next_endpoint(vi->dev->of_node, ep);
|
|
if (next == NULL)
|
|
break;
|
|
|
|
ep = next;
|
|
|
|
if (!of_device_is_available(ep)) {
|
|
dev_info(vi->dev, "ep of_device is not enabled %s.\n",
|
|
ep->full_name);
|
|
if (list_is_last(&chan->list, &vi->vi_chans))
|
|
break;
|
|
/* Try the next channel */
|
|
chan = list_next_entry(chan, list);
|
|
continue;
|
|
}
|
|
|
|
chan->endpoint_node = ep;
|
|
entity = devm_kzalloc(vi->dev, sizeof(*entity), GFP_KERNEL);
|
|
if (entity == NULL) {
|
|
ret = -ENOMEM;
|
|
goto done;
|
|
}
|
|
|
|
dev_dbg(vi->dev, "handling endpoint %s\n", ep->full_name);
|
|
remote = of_graph_get_remote_port_parent(ep);
|
|
if (!remote) {
|
|
dev_info(vi->dev, "cannot find remote port parent\n");
|
|
if (list_is_last(&chan->list, &vi->vi_chans))
|
|
break;
|
|
/* Try the next channel */
|
|
chan = list_next_entry(chan, list);
|
|
continue;
|
|
}
|
|
|
|
if (!of_device_is_available(remote)) {
|
|
dev_info(vi->dev, "remote of_device is not enabled %s.\n",
|
|
ep->full_name);
|
|
if (list_is_last(&chan->list, &vi->vi_chans))
|
|
break;
|
|
/* Try the next channel */
|
|
chan = list_next_entry(chan, list);
|
|
continue;
|
|
}
|
|
|
|
/* Add the remote entity of this endpoint */
|
|
entity->node = remote;
|
|
#if !defined(NV_V4L2_ASYNC_CONNECTION_STRUCT_PRESENT) /* Linux 6.5 */
|
|
#if defined(NV_V4L2_ASYNC_MATCH_TYPE_ENUM_PRESENT) /* Linux 6.5 */
|
|
entity->asd.match.type = V4L2_ASYNC_MATCH_TYPE_FWNODE;
|
|
#else
|
|
entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
|
|
#endif
|
|
entity->asd.match.fwnode = of_fwnode_handle(remote);
|
|
#endif
|
|
list_add_tail(&entity->list, &chan->entities);
|
|
chan->num_subdevs++;
|
|
chan->notifier.ops = chan->notifier.ops ? chan->notifier.ops : &vi_chan_notify_ops;
|
|
|
|
/* Parse and add entities on this enpoint/channel */
|
|
ret = tegra_vi_graph_parse_one(chan, entity->node);
|
|
if (ret < 0) {
|
|
dev_info(vi->dev, "graph parse error: %s.\n",
|
|
entity->node->full_name);
|
|
if (list_is_last(&chan->list, &vi->vi_chans))
|
|
break;
|
|
/* Try the next channel */
|
|
chan = list_next_entry(chan, list);
|
|
continue;
|
|
}
|
|
|
|
num_subdevs = chan->num_subdevs;
|
|
|
|
i = 0;
|
|
#if defined(CONFIG_V4L2_ASYNC)
|
|
#if defined(NV_V4L2_ASYNC_NOTIFIER_INIT_PRESENT)
|
|
v4l2_async_notifier_init(&chan->notifier);
|
|
list_for_each_entry(entity, &chan->entities, list)
|
|
__v4l2_async_notifier_add_subdev(&chan->notifier, &entity->asd);
|
|
#else
|
|
#if defined(NV_V4L2_ASYNC_NF_INIT_HAS_V4L2_DEV_ARG) /* Linux 6.6 */
|
|
v4l2_async_nf_init(&chan->notifier, &vi->v4l2_dev);
|
|
#else
|
|
v4l2_async_nf_init(&chan->notifier);
|
|
#endif
|
|
|
|
#if defined(NV_V4L2_ASYNC_NF_ADD_SUBDEV_PRESENT) /* Linux 6.6 */
|
|
list_for_each_entry(entity, &chan->entities, list)
|
|
__v4l2_async_nf_add_subdev(&chan->notifier, &entity->asd);
|
|
#else
|
|
list_for_each_entry(entity, &chan->entities, list) {
|
|
struct v4l2_async_connection *asc;
|
|
asc = v4l2_async_nf_add_fwnode(&chan->notifier, of_fwnode_handle(entity->node),
|
|
struct v4l2_async_connection);
|
|
if (IS_ERR(asc))
|
|
asc = NULL;
|
|
entity->asc = asc;
|
|
}
|
|
#endif /* NV_V4L2_ASYNC_NF_ADD_SUBDEV_PRESENT */
|
|
#endif /* NV_V4L2_ASYNC_NOTIFIER_INIT_PRESENT */
|
|
|
|
chan->link_status = 0;
|
|
chan->subdevs_bound = 0;
|
|
|
|
/* Register the async notifier for this channel */
|
|
#if defined(NV_V4L2_ASYNC_NOTIFIER_INIT_PRESENT)
|
|
ret = v4l2_async_notifier_register(&vi->v4l2_dev,
|
|
&chan->notifier);
|
|
#else
|
|
#if defined (NV_V4L2_ASYNC_NF_INIT_HAS_V4L2_DEV_ARG) /* Linux 6.6 */
|
|
ret = v4l2_async_nf_register(&chan->notifier);
|
|
if (ret < 0)
|
|
v4l2_async_nf_cleanup(&chan->notifier);
|
|
#else
|
|
ret = v4l2_async_nf_register(&vi->v4l2_dev,
|
|
&chan->notifier);
|
|
#endif
|
|
#endif
|
|
#else
|
|
dev_err(vi->dev, "CONFIG_V4L2_ASYNC is not enabled!\n");
|
|
ret = -ENOTSUPP;
|
|
#endif /* CONFIG_V4L2_ASYNC */
|
|
if (ret < 0) {
|
|
dev_err(vi->dev, "notifier registration failed %d\n", ret);
|
|
goto done;
|
|
}
|
|
|
|
if (list_is_last(&chan->list, &vi->vi_chans))
|
|
break;
|
|
/* One endpoint for each vi channel, go with the next channel */
|
|
chan = list_next_entry(chan, list);
|
|
} while (next);
|
|
|
|
done:
|
|
if (ret == -ENOMEM) {
|
|
dev_err(vi->dev, "graph init failed\n");
|
|
tegra_vi_graph_cleanup(vi);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_vi_graph_init);
|