1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Parallel video capture module (VIP) for the Tegra VI. |
4 | * |
5 | * This file implements the VIP-specific infrastructure. |
6 | * |
7 | * Copyright (C) 2023 SKIDATA GmbH |
8 | * Author: Luca Ceresoli <luca.ceresoli@bootlin.com> |
9 | */ |
10 | |
11 | #include <linux/device.h> |
12 | #include <linux/host1x.h> |
13 | #include <linux/module.h> |
14 | #include <linux/of.h> |
15 | #include <linux/of_graph.h> |
16 | #include <linux/platform_device.h> |
17 | #include <linux/pm_runtime.h> |
18 | |
19 | #include <media/v4l2-fwnode.h> |
20 | |
21 | #include "vip.h" |
22 | #include "video.h" |
23 | |
24 | static inline struct tegra_vip *host1x_client_to_vip(struct host1x_client *client) |
25 | { |
26 | return container_of(client, struct tegra_vip, client); |
27 | } |
28 | |
29 | static inline struct tegra_vip_channel *subdev_to_vip_channel(struct v4l2_subdev *subdev) |
30 | { |
31 | return container_of(subdev, struct tegra_vip_channel, subdev); |
32 | } |
33 | |
34 | static inline struct tegra_vip *vip_channel_to_vip(struct tegra_vip_channel *chan) |
35 | { |
36 | return container_of(chan, struct tegra_vip, chan); |
37 | } |
38 | |
39 | /* Find the previous subdev in the pipeline (i.e. the one connected to our sink pad) */ |
40 | static struct v4l2_subdev *tegra_vip_channel_get_prev_subdev(struct tegra_vip_channel *chan) |
41 | { |
42 | struct media_pad *remote_pad; |
43 | |
44 | remote_pad = media_pad_remote_pad_first(pad: &chan->pads[TEGRA_VIP_PAD_SINK]); |
45 | if (!remote_pad) |
46 | return NULL; |
47 | |
48 | return media_entity_to_v4l2_subdev(remote_pad->entity); |
49 | } |
50 | |
51 | static int tegra_vip_enable_stream(struct v4l2_subdev *subdev) |
52 | { |
53 | struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev); |
54 | struct tegra_vip *vip = vip_channel_to_vip(chan: vip_chan); |
55 | struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(chan: vip_chan); |
56 | int err; |
57 | |
58 | err = pm_runtime_resume_and_get(dev: vip->dev); |
59 | if (err) |
60 | return dev_err_probe(dev: vip->dev, err, fmt: "failed to get runtime PM\n" ); |
61 | |
62 | err = vip->soc->ops->vip_start_streaming(vip_chan); |
63 | if (err < 0) |
64 | goto err_start_streaming; |
65 | |
66 | err = v4l2_subdev_call(prev_subdev, video, s_stream, true); |
67 | if (err < 0 && err != -ENOIOCTLCMD) |
68 | goto err_prev_subdev_start_stream; |
69 | |
70 | return 0; |
71 | |
72 | err_prev_subdev_start_stream: |
73 | err_start_streaming: |
74 | pm_runtime_put(dev: vip->dev); |
75 | return err; |
76 | } |
77 | |
78 | static int tegra_vip_disable_stream(struct v4l2_subdev *subdev) |
79 | { |
80 | struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev); |
81 | struct tegra_vip *vip = vip_channel_to_vip(chan: vip_chan); |
82 | struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(chan: vip_chan); |
83 | |
84 | v4l2_subdev_call(prev_subdev, video, s_stream, false); |
85 | |
86 | pm_runtime_put(dev: vip->dev); |
87 | |
88 | return 0; |
89 | } |
90 | |
91 | static int tegra_vip_s_stream(struct v4l2_subdev *subdev, int enable) |
92 | { |
93 | int err; |
94 | |
95 | if (enable) |
96 | err = tegra_vip_enable_stream(subdev); |
97 | else |
98 | err = tegra_vip_disable_stream(subdev); |
99 | |
100 | return err; |
101 | } |
102 | |
103 | static const struct v4l2_subdev_video_ops tegra_vip_video_ops = { |
104 | .s_stream = tegra_vip_s_stream, |
105 | }; |
106 | |
107 | static const struct v4l2_subdev_ops tegra_vip_ops = { |
108 | .video = &tegra_vip_video_ops, |
109 | }; |
110 | |
111 | static int tegra_vip_channel_of_parse(struct tegra_vip *vip) |
112 | { |
113 | struct device *dev = vip->dev; |
114 | struct device_node *np = dev->of_node; |
115 | struct v4l2_fwnode_endpoint v4l2_ep = { |
116 | .bus_type = V4L2_MBUS_PARALLEL |
117 | }; |
118 | struct fwnode_handle *fwh; |
119 | struct device_node *ep; |
120 | unsigned int num_pads; |
121 | int err; |
122 | |
123 | dev_dbg(dev, "Parsing %pOF" , np); |
124 | |
125 | ep = of_graph_get_endpoint_by_regs(parent: np, port_reg: 0, reg: 0); |
126 | if (!ep) { |
127 | err = -EINVAL; |
128 | dev_err_probe(dev, err, fmt: "%pOF: error getting endpoint node\n" , np); |
129 | goto err_node_put; |
130 | } |
131 | |
132 | fwh = of_fwnode_handle(ep); |
133 | err = v4l2_fwnode_endpoint_parse(fwnode: fwh, vep: &v4l2_ep); |
134 | of_node_put(node: ep); |
135 | if (err) { |
136 | dev_err_probe(dev, err, fmt: "%pOF: failed to parse v4l2 endpoint\n" , np); |
137 | goto err_node_put; |
138 | } |
139 | |
140 | num_pads = of_graph_get_endpoint_count(np); |
141 | if (num_pads != TEGRA_VIP_PADS_NUM) { |
142 | err = -EINVAL; |
143 | dev_err_probe(dev, err, fmt: "%pOF: need 2 pads, got %d\n" , np, num_pads); |
144 | goto err_node_put; |
145 | } |
146 | |
147 | vip->chan.of_node = of_node_get(node: np); |
148 | vip->chan.pads[TEGRA_VIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK; |
149 | vip->chan.pads[TEGRA_VIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; |
150 | |
151 | return 0; |
152 | |
153 | err_node_put: |
154 | of_node_put(node: np); |
155 | return err; |
156 | } |
157 | |
158 | static int tegra_vip_channel_init(struct tegra_vip *vip) |
159 | { |
160 | struct v4l2_subdev *subdev; |
161 | int err; |
162 | |
163 | subdev = &vip->chan.subdev; |
164 | v4l2_subdev_init(sd: subdev, ops: &tegra_vip_ops); |
165 | subdev->dev = vip->dev; |
166 | snprintf(buf: subdev->name, size: sizeof(subdev->name), fmt: "%s" , |
167 | kbasename(path: vip->chan.of_node->full_name)); |
168 | |
169 | v4l2_set_subdevdata(sd: subdev, p: &vip->chan); |
170 | subdev->fwnode = of_fwnode_handle(vip->chan.of_node); |
171 | subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; |
172 | |
173 | err = media_entity_pads_init(entity: &subdev->entity, num_pads: TEGRA_VIP_PADS_NUM, pads: vip->chan.pads); |
174 | if (err) |
175 | return dev_err_probe(dev: vip->dev, err, fmt: "failed to initialize media entity\n" ); |
176 | |
177 | err = v4l2_async_register_subdev(sd: subdev); |
178 | if (err) { |
179 | dev_err_probe(dev: vip->dev, err, fmt: "failed to register subdev\n" ); |
180 | goto err_register_subdev; |
181 | } |
182 | |
183 | return 0; |
184 | |
185 | err_register_subdev: |
186 | media_entity_cleanup(entity: &subdev->entity); |
187 | return err; |
188 | } |
189 | |
190 | static int tegra_vip_init(struct host1x_client *client) |
191 | { |
192 | struct tegra_vip *vip = host1x_client_to_vip(client); |
193 | int err; |
194 | |
195 | err = tegra_vip_channel_of_parse(vip); |
196 | if (err) |
197 | return err; |
198 | |
199 | err = tegra_vip_channel_init(vip); |
200 | if (err) |
201 | goto err_init; |
202 | |
203 | return 0; |
204 | |
205 | err_init: |
206 | of_node_put(node: vip->chan.of_node); |
207 | return err; |
208 | } |
209 | |
210 | static int tegra_vip_exit(struct host1x_client *client) |
211 | { |
212 | struct tegra_vip *vip = host1x_client_to_vip(client); |
213 | struct v4l2_subdev *subdev = &vip->chan.subdev; |
214 | |
215 | v4l2_async_unregister_subdev(sd: subdev); |
216 | media_entity_cleanup(entity: &subdev->entity); |
217 | of_node_put(node: vip->chan.of_node); |
218 | |
219 | return 0; |
220 | } |
221 | |
222 | static const struct host1x_client_ops vip_client_ops = { |
223 | .init = tegra_vip_init, |
224 | .exit = tegra_vip_exit, |
225 | }; |
226 | |
227 | static int tegra_vip_probe(struct platform_device *pdev) |
228 | { |
229 | struct tegra_vip *vip; |
230 | int err; |
231 | |
232 | dev_dbg(&pdev->dev, "Probing VIP \"%s\" from %pOF\n" , pdev->name, pdev->dev.of_node); |
233 | |
234 | vip = devm_kzalloc(dev: &pdev->dev, size: sizeof(*vip), GFP_KERNEL); |
235 | if (!vip) |
236 | return -ENOMEM; |
237 | |
238 | vip->soc = of_device_get_match_data(dev: &pdev->dev); |
239 | |
240 | vip->dev = &pdev->dev; |
241 | platform_set_drvdata(pdev, data: vip); |
242 | |
243 | /* initialize host1x interface */ |
244 | INIT_LIST_HEAD(list: &vip->client.list); |
245 | vip->client.ops = &vip_client_ops; |
246 | vip->client.dev = &pdev->dev; |
247 | |
248 | err = host1x_client_register(&vip->client); |
249 | if (err) |
250 | return dev_err_probe(dev: &pdev->dev, err, fmt: "failed to register host1x client\n" ); |
251 | |
252 | pm_runtime_enable(dev: &pdev->dev); |
253 | |
254 | return 0; |
255 | } |
256 | |
257 | static void tegra_vip_remove(struct platform_device *pdev) |
258 | { |
259 | struct tegra_vip *vip = platform_get_drvdata(pdev); |
260 | |
261 | host1x_client_unregister(client: &vip->client); |
262 | |
263 | pm_runtime_disable(dev: &pdev->dev); |
264 | } |
265 | |
266 | #if defined(CONFIG_ARCH_TEGRA_2x_SOC) |
267 | extern const struct tegra_vip_soc tegra20_vip_soc; |
268 | #endif |
269 | |
270 | static const struct of_device_id tegra_vip_of_id_table[] = { |
271 | #if defined(CONFIG_ARCH_TEGRA_2x_SOC) |
272 | { .compatible = "nvidia,tegra20-vip" , .data = &tegra20_vip_soc }, |
273 | #endif |
274 | { } |
275 | }; |
276 | MODULE_DEVICE_TABLE(of, tegra_vip_of_id_table); |
277 | |
278 | struct platform_driver tegra_vip_driver = { |
279 | .driver = { |
280 | .name = "tegra-vip" , |
281 | .of_match_table = tegra_vip_of_id_table, |
282 | }, |
283 | .probe = tegra_vip_probe, |
284 | .remove_new = tegra_vip_remove, |
285 | }; |
286 | |