#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
+#include <linux/of_graph.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/pm_runtime.h>
return container_of(vb, struct tegra_channel_buffer, buf);
}
+static inline struct tegra_vi_graph_entity *
+to_tegra_vi_graph_entity(struct v4l2_async_subdev *asd)
+{
+ return container_of(asd, struct tegra_vi_graph_entity, asd);
+}
+
static int tegra_get_format_idx_by_code(struct tegra_vi *vi,
unsigned int code,
unsigned int offset)
}
struct v4l2_subdev *
-tegra_channel_get_remote_subdev(struct tegra_vi_channel *chan)
+tegra_channel_get_remote_csi_subdev(struct tegra_vi_channel *chan)
+{
+ struct media_pad *pad;
+
+ pad = media_entity_remote_pad(&chan->pad);
+ if (!pad)
+ return NULL;
+
+ return media_entity_to_v4l2_subdev(pad->entity);
+}
+
+struct v4l2_subdev *
+tegra_channel_get_remote_source_subdev(struct tegra_vi_channel *chan)
{
struct media_pad *pad;
struct v4l2_subdev *subdev;
struct media_entity *entity;
- pad = media_entity_remote_pad(&chan->pad);
- entity = pad->entity;
- subdev = media_entity_to_v4l2_subdev(entity);
+ subdev = tegra_channel_get_remote_csi_subdev(chan);
+ if (!subdev)
+ return NULL;
+
+ pad = &subdev->entity.pads[0];
+ while (!(pad->flags & MEDIA_PAD_FL_SOURCE)) {
+ pad = media_entity_remote_pad(pad);
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
+ break;
+ entity = pad->entity;
+ pad = &entity->pads[0];
+ subdev = media_entity_to_v4l2_subdev(entity);
+ }
return subdev;
}
-int tegra_channel_set_stream(struct tegra_vi_channel *chan, bool on)
+static int tegra_channel_enable_stream(struct tegra_vi_channel *chan)
+{
+ struct v4l2_subdev *csi_subdev, *src_subdev;
+ int ret;
+
+ /*
+ * Tegra CSI receiver can detect the first LP to HS transition.
+ * So, start the CSI stream-on prior to sensor stream-on and
+ * vice-versa for stream-off.
+ */
+ csi_subdev = tegra_channel_get_remote_csi_subdev(chan);
+ ret = v4l2_subdev_call(csi_subdev, video, s_stream, true);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+
+ if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return 0;
+
+ src_subdev = tegra_channel_get_remote_source_subdev(chan);
+ ret = v4l2_subdev_call(src_subdev, video, s_stream, true);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ v4l2_subdev_call(csi_subdev, video, s_stream, false);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tegra_channel_disable_stream(struct tegra_vi_channel *chan)
{
struct v4l2_subdev *subdev;
int ret;
- /* stream CSI */
- subdev = tegra_channel_get_remote_subdev(chan);
- ret = v4l2_subdev_call(subdev, video, s_stream, on);
- if (on && ret < 0 && ret != -ENOIOCTLCMD)
+ /*
+ * Stream-off subdevices in reverse order to stream-on.
+ * Remote source subdev in TPG mode is same as CSI subdev.
+ */
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ ret = v4l2_subdev_call(subdev, video, s_stream, false);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+
+ if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ return 0;
+
+ subdev = tegra_channel_get_remote_csi_subdev(chan);
+ ret = v4l2_subdev_call(subdev, video, s_stream, false);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
return ret;
return 0;
}
+int tegra_channel_set_stream(struct tegra_vi_channel *chan, bool on)
+{
+ int ret;
+
+ if (on)
+ ret = tegra_channel_enable_stream(chan);
+ else
+ ret = tegra_channel_disable_stream(chan);
+
+ return ret;
+}
+
void tegra_channel_release_buffers(struct tegra_vi_channel *chan,
enum vb2_buffer_state state)
{
struct tegra_vi_channel *chan = video_drvdata(file);
struct v4l2_subdev *subdev;
- subdev = tegra_channel_get_remote_subdev(chan);
+ subdev = tegra_channel_get_remote_source_subdev(chan);
return v4l2_g_parm_cap(&chan->video, subdev, a);
}
struct tegra_vi_channel *chan = video_drvdata(file);
struct v4l2_subdev *subdev;
- subdev = tegra_channel_get_remote_subdev(chan);
+ subdev = tegra_channel_get_remote_source_subdev(chan);
return v4l2_s_parm_cap(&chan->video, subdev, a);
}
fse.code = fmtinfo->code;
- subdev = tegra_channel_get_remote_subdev(chan);
+ subdev = tegra_channel_get_remote_source_subdev(chan);
ret = v4l2_subdev_call(subdev, pad, enum_frame_size, NULL, &fse);
if (ret)
return ret;
fie.code = fmtinfo->code;
- subdev = tegra_channel_get_remote_subdev(chan);
+ subdev = tegra_channel_get_remote_source_subdev(chan);
ret = v4l2_subdev_call(subdev, pad, enum_frame_interval, NULL, &fie);
if (ret)
return ret;
unsigned int index = 0, i;
unsigned long *fmts_bitmap = chan->tpg_fmts_bitmap;
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ fmts_bitmap = chan->fmts_bitmap;
+
if (f->index >= bitmap_weight(fmts_bitmap, MAX_FORMAT_NUM))
return -EINVAL;
struct v4l2_subdev *subdev;
struct v4l2_subdev_format fmt;
struct v4l2_subdev_pad_config *pad_cfg;
+ int ret;
+
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ if (!subdev)
+ return -ENODEV;
- subdev = tegra_channel_get_remote_subdev(chan);
pad_cfg = v4l2_subdev_alloc_pad_config(subdev);
if (!pad_cfg)
return -ENOMEM;
fmt.which = V4L2_SUBDEV_FORMAT_TRY;
fmt.pad = 0;
v4l2_fill_mbus_format(&fmt.format, pix, fmtinfo->code);
- v4l2_subdev_call(subdev, pad, set_fmt, pad_cfg, &fmt);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, pad_cfg, &fmt);
+ if (ret < 0)
+ return ret;
+
v4l2_fill_pix_format(pix, &fmt.format);
tegra_channel_fmt_align(chan, pix, fmtinfo->bpp);
fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
fmt.pad = 0;
v4l2_fill_mbus_format(&fmt.format, pix, fmtinfo->code);
- subdev = tegra_channel_get_remote_subdev(chan);
- v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt);
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt);
+ if (ret < 0)
+ return ret;
+
v4l2_fill_pix_format(pix, &fmt.format);
tegra_channel_fmt_align(chan, pix, fmtinfo->bpp);
return 0;
}
+static int tegra_channel_set_subdev_active_fmt(struct tegra_vi_channel *chan)
+{
+ int ret, index;
+ struct v4l2_subdev *subdev;
+ struct v4l2_subdev_format fmt = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ /*
+ * Initialize channel format to the sub-device active format if there
+ * is corresponding match in the Tegra supported video formats.
+ */
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt);
+ if (ret)
+ return ret;
+
+ index = tegra_get_format_idx_by_code(chan->vi, fmt.format.code, 0);
+ if (index < 0)
+ return -EINVAL;
+
+ chan->fmtinfo = &chan->vi->soc->video_formats[index];
+ v4l2_fill_pix_format(&chan->format, &fmt.format);
+ chan->format.pixelformat = chan->fmtinfo->fourcc;
+ chan->format.bytesperline = chan->format.width * chan->fmtinfo->bpp;
+ chan->format.sizeimage = chan->format.bytesperline *
+ chan->format.height;
+ tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
+
+ return 0;
+}
+
static int tegra_channel_enum_input(struct file *file, void *fh,
struct v4l2_input *inp)
{
- /* currently driver supports internal TPG only */
+ struct tegra_vi_channel *chan = video_drvdata(file);
+ struct v4l2_subdev *subdev;
+
if (inp->index)
return -EINVAL;
inp->type = V4L2_INPUT_TYPE_CAMERA;
- strscpy(inp->name, "Tegra TPG", sizeof(inp->name));
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ strscpy(inp->name, subdev->name, sizeof(inp->name));
return 0;
}
v4l2_ctrl_handler_free(&chan->ctrl_handler);
return chan->ctrl_handler.error;
}
+#else
+ struct v4l2_subdev *subdev;
+
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ if (!subdev)
+ return -ENODEV;
+
+ ret = v4l2_ctrl_add_handler(&chan->ctrl_handler, subdev->ctrl_handler,
+ NULL, true);
+ if (ret < 0) {
+ dev_err(chan->vi->dev,
+ "failed to add subdev %s ctrl handler: %d\n",
+ subdev->name, ret);
+ v4l2_ctrl_handler_free(&chan->ctrl_handler);
+ return ret;
+ }
#endif
/* setup the controls */
bitmap_set(chan->tpg_fmts_bitmap, index, 1);
}
+static int vi_fmts_bitmap_init(struct tegra_vi_channel *chan)
+{
+ int index, ret, match_code = 0;
+ struct v4l2_subdev *subdev;
+ struct v4l2_subdev_mbus_code_enum code = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+
+ bitmap_zero(chan->fmts_bitmap, MAX_FORMAT_NUM);
+
+ /*
+ * Set the bitmap bits based on all the matched formats between the
+ * available media bus formats of sub-device and the pre-defined Tegra
+ * supported video formats.
+ */
+ subdev = tegra_channel_get_remote_source_subdev(chan);
+ while (1) {
+ ret = v4l2_subdev_call(subdev, pad, enum_mbus_code,
+ NULL, &code);
+ if (ret < 0)
+ break;
+
+ index = tegra_get_format_idx_by_code(chan->vi, code.code, 0);
+ while (index >= 0) {
+ bitmap_set(chan->fmts_bitmap, index, 1);
+ if (!match_code)
+ match_code = code.code;
+ /* look for other formats with same mbus code */
+ index = tegra_get_format_idx_by_code(chan->vi,
+ code.code,
+ index + 1);
+ }
+
+ code.index++;
+ }
+
+ /*
+ * Set the bitmap bit corresponding to default tegra video format if
+ * there are no matched formats.
+ */
+ if (!match_code) {
+ match_code = tegra_default_format.code;
+ index = tegra_get_format_idx_by_code(chan->vi, match_code, 0);
+ if (WARN_ON(index < 0))
+ return -EINVAL;
+
+ bitmap_set(chan->fmts_bitmap, index, 1);
+ }
+
+ /* initialize channel format to the sub-device active format */
+ tegra_channel_set_subdev_active_fmt(chan);
+
+ return 0;
+}
+
static void tegra_channel_cleanup(struct tegra_vi_channel *chan)
{
v4l2_ctrl_handler_free(&chan->ctrl_handler);
goto free_v4l2_ctrl_hdl;
}
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ v4l2_async_notifier_init(&chan->notifier);
+
return 0;
free_v4l2_ctrl_hdl:
return ret;
}
-static int tegra_vi_tpg_channels_alloc(struct tegra_vi *vi)
+static int tegra_vi_channel_alloc(struct tegra_vi *vi, unsigned int port_num,
+ struct device_node *node)
{
struct tegra_vi_channel *chan;
+
+ /*
+ * Do not use devm_kzalloc as memory is freed immediately
+ * when device instance is unbound but application might still
+ * be holding the device node open. Channel memory allocated
+ * with kzalloc is freed during video device release callback.
+ */
+ chan = kzalloc(sizeof(*chan), GFP_KERNEL);
+ if (!chan)
+ return -ENOMEM;
+
+ chan->vi = vi;
+ chan->portno = port_num;
+ chan->of_node = node;
+ list_add_tail(&chan->list, &vi->vi_chans);
+
+ return 0;
+}
+
+static int tegra_vi_tpg_channels_alloc(struct tegra_vi *vi)
+{
unsigned int port_num;
unsigned int nchannels = vi->soc->vi_max_channels;
+ int ret;
for (port_num = 0; port_num < nchannels; port_num++) {
- /*
- * Do not use devm_kzalloc as memory is freed immediately
- * when device instance is unbound but application might still
- * be holding the device node open. Channel memory allocated
- * with kzalloc is freed during video device release callback.
- */
- chan = kzalloc(sizeof(*chan), GFP_KERNEL);
- if (!chan)
- return -ENOMEM;
-
- chan->vi = vi;
- chan->portno = port_num;
- list_add_tail(&chan->list, &vi->vi_chans);
+ ret = tegra_vi_channel_alloc(vi, port_num, vi->dev->of_node);
+ if (ret < 0)
+ return ret;
}
return 0;
}
+static int tegra_vi_channels_alloc(struct tegra_vi *vi)
+{
+ struct device_node *node = vi->dev->of_node;
+ struct device_node *ep = NULL;
+ struct device_node *ports;
+ struct device_node *port;
+ unsigned int port_num;
+ int ret = 0;
+
+ ports = of_get_child_by_name(node, "ports");
+ if (!ports)
+ return -ENODEV;
+
+ for_each_child_of_node(ports, port) {
+ if (!of_node_name_eq(port, "port"))
+ continue;
+
+ ret = of_property_read_u32(port, "reg", &port_num);
+ if (ret < 0)
+ continue;
+
+ if (port_num > vi->soc->vi_max_channels) {
+ dev_err(vi->dev, "invalid port num %d for %pOF\n",
+ port_num, port);
+ ret = -EINVAL;
+ of_node_put(port);
+ goto cleanup;
+ }
+
+ ep = of_get_child_by_name(port, "endpoint");
+ if (!ep)
+ continue;
+
+ of_node_put(ep);
+ ret = tegra_vi_channel_alloc(vi, port_num, port);
+ if (ret < 0) {
+ of_node_put(port);
+ goto cleanup;
+ }
+ }
+
+cleanup:
+ of_node_put(ports);
+ return ret;
+}
+
static int tegra_vi_channels_init(struct tegra_vi *vi)
{
struct tegra_vi_channel *chan;
return 0;
}
+/*
+ * Graph Management
+ */
+static struct tegra_vi_graph_entity *
+tegra_vi_graph_find_entity(struct tegra_vi_channel *chan,
+ const struct fwnode_handle *fwnode)
+{
+ struct tegra_vi_graph_entity *entity;
+ struct v4l2_async_subdev *asd;
+
+ list_for_each_entry(asd, &chan->notifier.asd_list, asd_list) {
+ entity = to_tegra_vi_graph_entity(asd);
+ if (entity->asd.match.fwnode == fwnode)
+ return entity;
+ }
+
+ return NULL;
+}
+
+static int tegra_vi_graph_build(struct tegra_vi_channel *chan,
+ struct tegra_vi_graph_entity *entity)
+{
+ struct tegra_vi *vi = chan->vi;
+ struct tegra_vi_graph_entity *ent;
+ struct fwnode_handle *ep = NULL;
+ struct v4l2_fwnode_link link;
+ struct media_entity *local = entity->entity;
+ struct media_entity *remote;
+ struct media_pad *local_pad;
+ struct media_pad *remote_pad;
+ u32 link_flags = MEDIA_LNK_FL_ENABLED;
+ int ret = 0;
+
+ dev_dbg(vi->dev, "creating links for entity %s\n", local->name);
+
+ while (1) {
+ ep = fwnode_graph_get_next_endpoint(entity->asd.match.fwnode,
+ ep);
+ if (!ep)
+ break;
+
+ ret = v4l2_fwnode_parse_link(ep, &link);
+ if (ret < 0) {
+ dev_err(vi->dev, "failed to parse link for %pOF: %d\n",
+ to_of_node(ep), ret);
+ continue;
+ }
+
+ if (link.local_port >= local->num_pads) {
+ dev_err(vi->dev, "invalid port number %u on %pOF\n",
+ link.local_port, to_of_node(link.local_node));
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ local_pad = &local->pads[link.local_port];
+ /* Remote node is vi node. So use channel video entity and pad
+ * as remote/sink.
+ */
+ if (link.remote_node == of_fwnode_handle(vi->dev->of_node)) {
+ remote = &chan->video.entity;
+ remote_pad = &chan->pad;
+ goto create_link;
+ }
+
+ /*
+ * Skip sink ports, they will be processed from the other end
+ * of the link.
+ */
+ if (local_pad->flags & MEDIA_PAD_FL_SINK) {
+ dev_dbg(vi->dev, "skipping sink port %pOF:%u\n",
+ to_of_node(link.local_node), link.local_port);
+ v4l2_fwnode_put_link(&link);
+ continue;
+ }
+
+ /* find the remote entity from notifier list */
+ ent = tegra_vi_graph_find_entity(chan, link.remote_node);
+ if (!ent) {
+ dev_err(vi->dev, "no entity found for %pOF\n",
+ to_of_node(link.remote_node));
+ v4l2_fwnode_put_link(&link);
+ ret = -ENODEV;
+ break;
+ }
+
+ remote = ent->entity;
+ if (link.remote_port >= remote->num_pads) {
+ dev_err(vi->dev, "invalid port number %u on %pOF\n",
+ link.remote_port,
+ to_of_node(link.remote_node));
+ v4l2_fwnode_put_link(&link);
+ ret = -EINVAL;
+ break;
+ }
+
+ remote_pad = &remote->pads[link.remote_port];
+
+create_link:
+ dev_dbg(vi->dev, "creating %s:%u -> %s:%u link\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index);
+
+ ret = media_create_pad_link(local, local_pad->index,
+ remote, remote_pad->index,
+ link_flags);
+ v4l2_fwnode_put_link(&link);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to create %s:%u -> %s:%u link: %d\n",
+ local->name, local_pad->index,
+ remote->name, remote_pad->index, ret);
+ break;
+ }
+ }
+
+ fwnode_handle_put(ep);
+ return ret;
+}
+
+static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+ struct tegra_vi_graph_entity *entity;
+ struct v4l2_async_subdev *asd;
+ struct v4l2_subdev *subdev;
+ struct tegra_vi_channel *chan;
+ struct tegra_vi *vi;
+ int ret;
+
+ chan = container_of(notifier, struct tegra_vi_channel, notifier);
+ vi = chan->vi;
+
+ dev_dbg(vi->dev, "notify complete, all subdevs registered\n");
+
+ /*
+ * Video device node should be created at the end of all the device
+ * related initialization/setup.
+ * Current video_register_device() does both initialize and register
+ * video device in same API.
+ *
+ * TODO: Update v4l2-dev driver to split initialize and register into
+ * separate APIs and then update Tegra video driver to do video device
+ * initialize followed by all video device related setup and then
+ * register the video device.
+ */
+ ret = video_register_device(&chan->video, VFL_TYPE_VIDEO, -1);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to register video device: %d\n", ret);
+ goto unregister_video;
+ }
+
+ /* create links between the entities */
+ list_for_each_entry(asd, &chan->notifier.asd_list, asd_list) {
+ entity = to_tegra_vi_graph_entity(asd);
+ ret = tegra_vi_graph_build(chan, entity);
+ if (ret < 0)
+ goto unregister_video;
+ }
+
+ ret = tegra_channel_setup_ctrl_handler(chan);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to setup channel controls: %d\n", ret);
+ goto unregister_video;
+ }
+
+ ret = vi_fmts_bitmap_init(chan);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to initialize formats bitmap: %d\n", ret);
+ goto unregister_video;
+ }
+
+ subdev = tegra_channel_get_remote_csi_subdev(chan);
+ if (!subdev) {
+ ret = -ENODEV;
+ dev_err(vi->dev,
+ "failed to get remote csi subdev: %d\n", ret);
+ goto unregister_video;
+ }
+
+ v4l2_set_subdev_hostdata(subdev, chan);
+
+ return 0;
+
+unregister_video:
+ vb2_video_unregister_device(&chan->video);
+ return ret;
+}
+
+static int tegra_vi_graph_notify_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct tegra_vi_graph_entity *entity;
+ struct tegra_vi *vi;
+ struct tegra_vi_channel *chan;
+
+ chan = container_of(notifier, struct tegra_vi_channel, notifier);
+ vi = chan->vi;
+
+ /*
+ * Locate the entity corresponding to the bound subdev and store the
+ * subdev pointer.
+ */
+ entity = tegra_vi_graph_find_entity(chan, subdev->fwnode);
+ if (!entity) {
+ dev_err(vi->dev, "no entity for subdev %s\n", subdev->name);
+ return -EINVAL;
+ }
+
+ if (entity->subdev) {
+ dev_err(vi->dev, "duplicate subdev for node %pOF\n",
+ to_of_node(entity->asd.match.fwnode));
+ return -EINVAL;
+ }
+
+ dev_dbg(vi->dev, "subdev %s bound\n", subdev->name);
+ entity->entity = &subdev->entity;
+ entity->subdev = subdev;
+
+ return 0;
+}
+
+static const struct v4l2_async_notifier_operations tegra_vi_async_ops = {
+ .bound = tegra_vi_graph_notify_bound,
+ .complete = tegra_vi_graph_notify_complete,
+};
+
+static int tegra_vi_graph_parse_one(struct tegra_vi_channel *chan,
+ struct fwnode_handle *fwnode)
+{
+ struct tegra_vi *vi = chan->vi;
+ struct fwnode_handle *ep = NULL;
+ struct fwnode_handle *remote = NULL;
+ struct v4l2_async_subdev *asd;
+ struct device_node *node = NULL;
+ int ret;
+
+ dev_dbg(vi->dev, "parsing node %pOF\n", to_of_node(fwnode));
+
+ /* parse all the remote entities and put them into the list */
+ for_each_endpoint_of_node(to_of_node(fwnode), node) {
+ ep = of_fwnode_handle(node);
+ remote = fwnode_graph_get_remote_port_parent(ep);
+ if (!remote) {
+ dev_err(vi->dev,
+ "remote device at %pOF not found\n", node);
+ ret = -EINVAL;
+ goto cleanup;
+ }
+
+ /* skip entities that are already processed */
+ if (remote == dev_fwnode(vi->dev) ||
+ tegra_vi_graph_find_entity(chan, remote)) {
+ fwnode_handle_put(remote);
+ continue;
+ }
+
+ asd = v4l2_async_notifier_add_fwnode_subdev(&chan->notifier,
+ remote, sizeof(struct tegra_vi_graph_entity));
+ if (IS_ERR(asd)) {
+ ret = PTR_ERR(asd);
+ dev_err(vi->dev,
+ "failed to add subdev to notifier: %d\n", ret);
+ fwnode_handle_put(remote);
+ goto cleanup;
+ }
+
+ ret = tegra_vi_graph_parse_one(chan, remote);
+ if (ret < 0) {
+ fwnode_handle_put(remote);
+ goto cleanup;
+ }
+
+ fwnode_handle_put(remote);
+ }
+
+ return 0;
+
+cleanup:
+ dev_err(vi->dev, "failed parsing the graph: %d\n", ret);
+ v4l2_async_notifier_cleanup(&chan->notifier);
+ of_node_put(node);
+ return ret;
+}
+
+static int tegra_vi_graph_init(struct tegra_vi *vi)
+{
+ struct tegra_video_device *vid = dev_get_drvdata(vi->client.host);
+ struct tegra_vi_channel *chan;
+ struct fwnode_handle *fwnode = dev_fwnode(vi->dev);
+ int ret;
+ struct fwnode_handle *remote = NULL;
+
+ /*
+ * Walk the links to parse the full graph. Each channel will have
+ * one endpoint of the composite node. Start by parsing the
+ * composite node and parse the remote entities in turn.
+ * Each channel will register v4l2 async notifier to make the graph
+ * independent between the channels so we can the current channel
+ * in case of something wrong during graph parsing and continue with
+ * next channels.
+ */
+ list_for_each_entry(chan, &vi->vi_chans, list) {
+ remote = fwnode_graph_get_remote_node(fwnode, chan->portno, 0);
+ if (!remote)
+ continue;
+
+ ret = tegra_vi_graph_parse_one(chan, remote);
+ fwnode_handle_put(remote);
+ if (ret < 0 || list_empty(&chan->notifier.asd_list))
+ continue;
+
+ chan->notifier.ops = &tegra_vi_async_ops;
+ ret = v4l2_async_notifier_register(&vid->v4l2_dev,
+ &chan->notifier);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to register channel %d notifier: %d\n",
+ chan->portno, ret);
+ v4l2_async_notifier_cleanup(&chan->notifier);
+ }
+ }
+
+ return 0;
+}
+
+static void tegra_vi_graph_cleanup(struct tegra_vi *vi)
+{
+ struct tegra_vi_channel *chan;
+
+ list_for_each_entry(chan, &vi->vi_chans, list) {
+ vb2_video_unregister_device(&chan->video);
+ v4l2_async_notifier_unregister(&chan->notifier);
+ v4l2_async_notifier_cleanup(&chan->notifier);
+ }
+}
+
static int tegra_vi_init(struct host1x_client *client)
{
struct tegra_video_device *vid = dev_get_drvdata(client->host);
INIT_LIST_HEAD(&vi->vi_chans);
- if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
+ if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
ret = tegra_vi_tpg_channels_alloc(vi);
- if (ret < 0) {
- dev_err(vi->dev,
- "failed to allocate tpg channels: %d\n", ret);
- goto free_chans;
- }
+ else
+ ret = tegra_vi_channels_alloc(vi);
+ if (ret < 0) {
+ dev_err(vi->dev,
+ "failed to allocate vi channels: %d\n", ret);
+ goto free_chans;
}
ret = tegra_vi_channels_init(vi);
vid->vi = vi;
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
+ ret = tegra_vi_graph_init(vi);
+ if (ret < 0)
+ goto free_chans;
+ }
+
return 0;
free_chans:
static int tegra_vi_exit(struct host1x_client *client)
{
+ struct tegra_vi *vi = host1x_client_to_vi(client);
+
/*
* Do not cleanup the channels here as application might still be
* holding video device nodes. Channels cleanup will happen during
* device nodes are released.
*/
+ if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
+ tegra_vi_graph_cleanup(vi);
+
return 0;
}