1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2020 NVIDIA CORPORATION. All rights reserved. |
4 | */ |
5 | |
6 | #include <linux/host1x.h> |
7 | #include <linux/module.h> |
8 | #include <linux/platform_device.h> |
9 | |
10 | #include <media/v4l2-event.h> |
11 | |
12 | #include "video.h" |
13 | |
14 | static void tegra_v4l2_dev_release(struct v4l2_device *v4l2_dev) |
15 | { |
16 | struct tegra_video_device *vid; |
17 | |
18 | vid = container_of(v4l2_dev, struct tegra_video_device, v4l2_dev); |
19 | |
20 | /* cleanup channels here as all video device nodes are released */ |
21 | tegra_channels_cleanup(vi: vid->vi); |
22 | |
23 | v4l2_device_unregister(v4l2_dev); |
24 | media_device_unregister(mdev: &vid->media_dev); |
25 | media_device_cleanup(mdev: &vid->media_dev); |
26 | kfree(objp: vid); |
27 | } |
28 | |
29 | static void tegra_v4l2_dev_notify(struct v4l2_subdev *sd, |
30 | unsigned int notification, void *arg) |
31 | { |
32 | struct tegra_vi_channel *chan; |
33 | const struct v4l2_event *ev = arg; |
34 | |
35 | if (notification != V4L2_DEVICE_NOTIFY_EVENT) |
36 | return; |
37 | |
38 | chan = v4l2_get_subdev_hostdata(sd); |
39 | v4l2_event_queue(vdev: &chan->video, ev: arg); |
40 | if (ev->type == V4L2_EVENT_SOURCE_CHANGE && vb2_is_streaming(q: &chan->queue)) |
41 | vb2_queue_error(q: &chan->queue); |
42 | } |
43 | |
44 | static int host1x_video_probe(struct host1x_device *dev) |
45 | { |
46 | struct tegra_video_device *vid; |
47 | int ret; |
48 | |
49 | vid = kzalloc(size: sizeof(*vid), GFP_KERNEL); |
50 | if (!vid) |
51 | return -ENOMEM; |
52 | |
53 | dev_set_drvdata(dev: &dev->dev, data: vid); |
54 | |
55 | vid->media_dev.dev = &dev->dev; |
56 | strscpy(vid->media_dev.model, "NVIDIA Tegra Video Input Device" , |
57 | sizeof(vid->media_dev.model)); |
58 | |
59 | media_device_init(mdev: &vid->media_dev); |
60 | ret = media_device_register(&vid->media_dev); |
61 | if (ret < 0) { |
62 | dev_err(&dev->dev, |
63 | "failed to register media device: %d\n" , ret); |
64 | goto cleanup; |
65 | } |
66 | |
67 | vid->v4l2_dev.mdev = &vid->media_dev; |
68 | vid->v4l2_dev.release = tegra_v4l2_dev_release; |
69 | vid->v4l2_dev.notify = tegra_v4l2_dev_notify; |
70 | ret = v4l2_device_register(dev: &dev->dev, v4l2_dev: &vid->v4l2_dev); |
71 | if (ret < 0) { |
72 | dev_err(&dev->dev, |
73 | "V4L2 device registration failed: %d\n" , ret); |
74 | goto unregister_media; |
75 | } |
76 | |
77 | ret = host1x_device_init(device: dev); |
78 | if (ret < 0) |
79 | goto unregister_v4l2; |
80 | |
81 | if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) { |
82 | /* |
83 | * Both vi and csi channels are available now. |
84 | * Register v4l2 nodes and create media links for TPG. |
85 | */ |
86 | ret = tegra_v4l2_nodes_setup_tpg(vid); |
87 | if (ret < 0) { |
88 | dev_err(&dev->dev, |
89 | "failed to setup tpg graph: %d\n" , ret); |
90 | goto device_exit; |
91 | } |
92 | } |
93 | |
94 | return 0; |
95 | |
96 | device_exit: |
97 | host1x_device_exit(device: dev); |
98 | /* vi exit ops does not clean channels, so clean them here */ |
99 | tegra_channels_cleanup(vi: vid->vi); |
100 | unregister_v4l2: |
101 | v4l2_device_unregister(v4l2_dev: &vid->v4l2_dev); |
102 | unregister_media: |
103 | media_device_unregister(mdev: &vid->media_dev); |
104 | cleanup: |
105 | media_device_cleanup(mdev: &vid->media_dev); |
106 | kfree(objp: vid); |
107 | return ret; |
108 | } |
109 | |
110 | static int host1x_video_remove(struct host1x_device *dev) |
111 | { |
112 | struct tegra_video_device *vid = dev_get_drvdata(dev: &dev->dev); |
113 | |
114 | if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) |
115 | tegra_v4l2_nodes_cleanup_tpg(vid); |
116 | |
117 | host1x_device_exit(device: dev); |
118 | |
119 | /* This calls v4l2_dev release callback on last reference */ |
120 | v4l2_device_put(v4l2_dev: &vid->v4l2_dev); |
121 | |
122 | return 0; |
123 | } |
124 | |
125 | static const struct of_device_id host1x_video_subdevs[] = { |
126 | #if defined(CONFIG_ARCH_TEGRA_2x_SOC) |
127 | { .compatible = "nvidia,tegra20-vip" , }, |
128 | { .compatible = "nvidia,tegra20-vi" , }, |
129 | #endif |
130 | #if defined(CONFIG_ARCH_TEGRA_210_SOC) |
131 | { .compatible = "nvidia,tegra210-csi" , }, |
132 | { .compatible = "nvidia,tegra210-vi" , }, |
133 | #endif |
134 | { } |
135 | }; |
136 | |
137 | static struct host1x_driver host1x_video_driver = { |
138 | .driver = { |
139 | .name = "tegra-video" , |
140 | }, |
141 | .probe = host1x_video_probe, |
142 | .remove = host1x_video_remove, |
143 | .subdevs = host1x_video_subdevs, |
144 | }; |
145 | |
146 | static struct platform_driver * const drivers[] = { |
147 | &tegra_csi_driver, |
148 | &tegra_vip_driver, |
149 | &tegra_vi_driver, |
150 | }; |
151 | |
152 | static int __init host1x_video_init(void) |
153 | { |
154 | int err; |
155 | |
156 | err = host1x_driver_register(&host1x_video_driver); |
157 | if (err < 0) |
158 | return err; |
159 | |
160 | err = platform_register_drivers(drivers, ARRAY_SIZE(drivers)); |
161 | if (err < 0) |
162 | goto unregister_host1x; |
163 | |
164 | return 0; |
165 | |
166 | unregister_host1x: |
167 | host1x_driver_unregister(driver: &host1x_video_driver); |
168 | return err; |
169 | } |
170 | module_init(host1x_video_init); |
171 | |
172 | static void __exit host1x_video_exit(void) |
173 | { |
174 | platform_unregister_drivers(drivers, ARRAY_SIZE(drivers)); |
175 | host1x_driver_unregister(driver: &host1x_video_driver); |
176 | } |
177 | module_exit(host1x_video_exit); |
178 | |
179 | MODULE_AUTHOR("Sowjanya Komatineni <skomatineni@nvidia.com>" ); |
180 | MODULE_DESCRIPTION("NVIDIA Tegra Host1x Video driver" ); |
181 | MODULE_LICENSE("GPL v2" ); |
182 | |