| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * Copyright (C) 2020 NVIDIA CORPORATION. All rights reserved. |
| */ |
| |
| #include <linux/host1x.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| |
| #include <media/v4l2-event.h> |
| |
| #include "video.h" |
| |
| static void tegra_v4l2_dev_release(struct v4l2_device *v4l2_dev) |
| { |
| struct tegra_video_device *vid; |
| |
| vid = container_of(v4l2_dev, struct tegra_video_device, v4l2_dev); |
| |
| /* cleanup channels here as all video device nodes are released */ |
| tegra_channels_cleanup(vid->vi); |
| |
| v4l2_device_unregister(v4l2_dev); |
| media_device_unregister(&vid->media_dev); |
| media_device_cleanup(&vid->media_dev); |
| kfree(vid); |
| } |
| |
| static void tegra_v4l2_dev_notify(struct v4l2_subdev *sd, |
| unsigned int notification, void *arg) |
| { |
| struct tegra_vi_channel *chan; |
| const struct v4l2_event *ev = arg; |
| |
| if (notification != V4L2_DEVICE_NOTIFY_EVENT) |
| return; |
| |
| chan = v4l2_get_subdev_hostdata(sd); |
| v4l2_event_queue(&chan->video, arg); |
| if (ev->type == V4L2_EVENT_SOURCE_CHANGE && vb2_is_streaming(&chan->queue)) |
| vb2_queue_error(&chan->queue); |
| } |
| |
| static int host1x_video_probe(struct host1x_device *dev) |
| { |
| struct tegra_video_device *vid; |
| int ret; |
| |
| vid = kzalloc(sizeof(*vid), GFP_KERNEL); |
| if (!vid) |
| return -ENOMEM; |
| |
| dev_set_drvdata(&dev->dev, vid); |
| |
| vid->media_dev.dev = &dev->dev; |
| strscpy(vid->media_dev.model, "NVIDIA Tegra Video Input Device", |
| sizeof(vid->media_dev.model)); |
| |
| media_device_init(&vid->media_dev); |
| ret = media_device_register(&vid->media_dev); |
| if (ret < 0) { |
| dev_err(&dev->dev, |
| "failed to register media device: %d\n", ret); |
| goto cleanup; |
| } |
| |
| vid->v4l2_dev.mdev = &vid->media_dev; |
| vid->v4l2_dev.release = tegra_v4l2_dev_release; |
| vid->v4l2_dev.notify = tegra_v4l2_dev_notify; |
| ret = v4l2_device_register(&dev->dev, &vid->v4l2_dev); |
| if (ret < 0) { |
| dev_err(&dev->dev, |
| "V4L2 device registration failed: %d\n", ret); |
| goto unregister_media; |
| } |
| |
| ret = host1x_device_init(dev); |
| if (ret < 0) |
| goto unregister_v4l2; |
| |
| if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) { |
| /* |
| * Both vi and csi channels are available now. |
| * Register v4l2 nodes and create media links for TPG. |
| */ |
| ret = tegra_v4l2_nodes_setup_tpg(vid); |
| if (ret < 0) { |
| dev_err(&dev->dev, |
| "failed to setup tpg graph: %d\n", ret); |
| goto device_exit; |
| } |
| } |
| |
| return 0; |
| |
| device_exit: |
| host1x_device_exit(dev); |
| /* vi exit ops does not clean channels, so clean them here */ |
| tegra_channels_cleanup(vid->vi); |
| unregister_v4l2: |
| v4l2_device_unregister(&vid->v4l2_dev); |
| unregister_media: |
| media_device_unregister(&vid->media_dev); |
| cleanup: |
| media_device_cleanup(&vid->media_dev); |
| kfree(vid); |
| return ret; |
| } |
| |
| static int host1x_video_remove(struct host1x_device *dev) |
| { |
| struct tegra_video_device *vid = dev_get_drvdata(&dev->dev); |
| |
| if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
| tegra_v4l2_nodes_cleanup_tpg(vid); |
| |
| host1x_device_exit(dev); |
| |
| /* This calls v4l2_dev release callback on last reference */ |
| v4l2_device_put(&vid->v4l2_dev); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id host1x_video_subdevs[] = { |
| #if defined(CONFIG_ARCH_TEGRA_210_SOC) |
| { .compatible = "nvidia,tegra210-csi", }, |
| { .compatible = "nvidia,tegra210-vi", }, |
| #endif |
| { } |
| }; |
| |
| static struct host1x_driver host1x_video_driver = { |
| .driver = { |
| .name = "tegra-video", |
| }, |
| .probe = host1x_video_probe, |
| .remove = host1x_video_remove, |
| .subdevs = host1x_video_subdevs, |
| }; |
| |
| static struct platform_driver * const drivers[] = { |
| &tegra_csi_driver, |
| &tegra_vi_driver, |
| }; |
| |
| static int __init host1x_video_init(void) |
| { |
| int err; |
| |
| err = host1x_driver_register(&host1x_video_driver); |
| if (err < 0) |
| return err; |
| |
| err = platform_register_drivers(drivers, ARRAY_SIZE(drivers)); |
| if (err < 0) |
| goto unregister_host1x; |
| |
| return 0; |
| |
| unregister_host1x: |
| host1x_driver_unregister(&host1x_video_driver); |
| return err; |
| } |
| module_init(host1x_video_init); |
| |
| static void __exit host1x_video_exit(void) |
| { |
| platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); |
| host1x_driver_unregister(&host1x_video_driver); |
| } |
| module_exit(host1x_video_exit); |
| |
| MODULE_AUTHOR("Sowjanya Komatineni <skomatineni@nvidia.com>"); |
| MODULE_DESCRIPTION("NVIDIA Tegra Host1x Video driver"); |
| MODULE_LICENSE("GPL v2"); |