// SPDX-License-Identifier: GPL-2.0-only /* * Parallel video capture module (VIP) for the Tegra VI. * * This file implements the VIP-specific infrastructure. * * Copyright (C) 2023 SKIDATA GmbH * Author: Luca Ceresoli */ #include #include #include #include #include #include #include #include #include "vip.h" #include "video.h" static inline struct tegra_vip *host1x_client_to_vip(struct host1x_client *client) { return container_of(client, struct tegra_vip, client); } static inline struct tegra_vip_channel *subdev_to_vip_channel(struct v4l2_subdev *subdev) { return container_of(subdev, struct tegra_vip_channel, subdev); } static inline struct tegra_vip *vip_channel_to_vip(struct tegra_vip_channel *chan) { return container_of(chan, struct tegra_vip, chan); } /* Find the previous subdev in the pipeline (i.e. the one connected to our sink pad) */ static struct v4l2_subdev *tegra_vip_channel_get_prev_subdev(struct tegra_vip_channel *chan) { struct media_pad *remote_pad; remote_pad = media_pad_remote_pad_first(&chan->pads[TEGRA_VIP_PAD_SINK]); if (!remote_pad) return NULL; return media_entity_to_v4l2_subdev(remote_pad->entity); } static int tegra_vip_enable_stream(struct v4l2_subdev *subdev) { struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev); struct tegra_vip *vip = vip_channel_to_vip(vip_chan); struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(vip_chan); int err; err = pm_runtime_resume_and_get(vip->dev); if (err) return dev_err_probe(vip->dev, err, "failed to get runtime PM\n"); err = vip->soc->ops->vip_start_streaming(vip_chan); if (err < 0) goto err_start_streaming; err = v4l2_subdev_call(prev_subdev, video, s_stream, true); if (err < 0 && err != -ENOIOCTLCMD) goto err_prev_subdev_start_stream; return 0; err_prev_subdev_start_stream: err_start_streaming: pm_runtime_put(vip->dev); return err; } static int tegra_vip_disable_stream(struct v4l2_subdev *subdev) { struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev); struct tegra_vip *vip = vip_channel_to_vip(vip_chan); struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(vip_chan); v4l2_subdev_call(prev_subdev, video, s_stream, false); pm_runtime_put(vip->dev); return 0; } static int tegra_vip_s_stream(struct v4l2_subdev *subdev, int enable) { int err; if (enable) err = tegra_vip_enable_stream(subdev); else err = tegra_vip_disable_stream(subdev); return err; } static const struct v4l2_subdev_video_ops tegra_vip_video_ops = { .s_stream = tegra_vip_s_stream, }; static const struct v4l2_subdev_ops tegra_vip_ops = { .video = &tegra_vip_video_ops, }; static int tegra_vip_channel_of_parse(struct tegra_vip *vip) { struct device *dev = vip->dev; struct device_node *np = dev->of_node; struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_PARALLEL }; struct fwnode_handle *fwh; struct device_node *ep; unsigned int num_pads; int err; dev_dbg(dev, "Parsing %pOF", np); ep = of_graph_get_endpoint_by_regs(np, 0, 0); if (!ep) { err = -EINVAL; dev_err_probe(dev, err, "%pOF: error getting endpoint node\n", np); goto err_node_put; } fwh = of_fwnode_handle(ep); err = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep); of_node_put(ep); if (err) { dev_err_probe(dev, err, "%pOF: failed to parse v4l2 endpoint\n", np); goto err_node_put; } num_pads = of_graph_get_endpoint_count(np); if (num_pads != TEGRA_VIP_PADS_NUM) { err = -EINVAL; dev_err_probe(dev, err, "%pOF: need 2 pads, got %d\n", np, num_pads); goto err_node_put; } vip->chan.of_node = of_node_get(np); vip->chan.pads[TEGRA_VIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; vip->chan.pads[TEGRA_VIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; return 0; err_node_put: of_node_put(np); return err; } static int tegra_vip_channel_init(struct tegra_vip *vip) { struct v4l2_subdev *subdev; int err; subdev = &vip->chan.subdev; v4l2_subdev_init(subdev, &tegra_vip_ops); subdev->dev = vip->dev; snprintf(subdev->name, sizeof(subdev->name), "%s", kbasename(vip->chan.of_node->full_name)); v4l2_set_subdevdata(subdev, &vip->chan); subdev->fwnode = of_fwnode_handle(vip->chan.of_node); subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; err = media_entity_pads_init(&subdev->entity, TEGRA_VIP_PADS_NUM, vip->chan.pads); if (err) return dev_err_probe(vip->dev, err, "failed to initialize media entity\n"); err = v4l2_async_register_subdev(subdev); if (err) { dev_err_probe(vip->dev, err, "failed to register subdev\n"); goto err_register_subdev; } return 0; err_register_subdev: media_entity_cleanup(&subdev->entity); return err; } static int tegra_vip_init(struct host1x_client *client) { struct tegra_vip *vip = host1x_client_to_vip(client); int err; err = tegra_vip_channel_of_parse(vip); if (err) return err; err = tegra_vip_channel_init(vip); if (err) goto err_init; return 0; err_init: of_node_put(vip->chan.of_node); return err; } static int tegra_vip_exit(struct host1x_client *client) { struct tegra_vip *vip = host1x_client_to_vip(client); struct v4l2_subdev *subdev = &vip->chan.subdev; v4l2_async_unregister_subdev(subdev); media_entity_cleanup(&subdev->entity); of_node_put(vip->chan.of_node); return 0; } static const struct host1x_client_ops vip_client_ops = { .init = tegra_vip_init, .exit = tegra_vip_exit, }; static int tegra_vip_probe(struct platform_device *pdev) { struct tegra_vip *vip; int err; dev_dbg(&pdev->dev, "Probing VIP \"%s\" from %pOF\n", pdev->name, pdev->dev.of_node); vip = devm_kzalloc(&pdev->dev, sizeof(*vip), GFP_KERNEL); if (!vip) return -ENOMEM; vip->soc = of_device_get_match_data(&pdev->dev); vip->dev = &pdev->dev; platform_set_drvdata(pdev, vip); /* initialize host1x interface */ INIT_LIST_HEAD(&vip->client.list); vip->client.ops = &vip_client_ops; vip->client.dev = &pdev->dev; err = host1x_client_register(&vip->client); if (err) return dev_err_probe(&pdev->dev, err, "failed to register host1x client\n"); pm_runtime_enable(&pdev->dev); return 0; } static void tegra_vip_remove(struct platform_device *pdev) { struct tegra_vip *vip = platform_get_drvdata(pdev); host1x_client_unregister(&vip->client); pm_runtime_disable(&pdev->dev); } #if defined(CONFIG_ARCH_TEGRA_2x_SOC) extern const struct tegra_vip_soc tegra20_vip_soc; #endif static const struct of_device_id tegra_vip_of_id_table[] = { #if defined(CONFIG_ARCH_TEGRA_2x_SOC) { .compatible = "nvidia,tegra20-vip", .data = &tegra20_vip_soc }, #endif { } }; MODULE_DEVICE_TABLE(of, tegra_vip_of_id_table); struct platform_driver tegra_vip_driver = { .driver = { .name = "tegra-vip", .of_match_table = tegra_vip_of_id_table, }, .probe = tegra_vip_probe, .remove_new = tegra_vip_remove, };