xref: /linux/drivers/media/platform/xilinx/xilinx-vipp.c (revision 9bf19fbf0c8bc4392210c1ea104a8db732624f3d)
183268fa6SDhaval Shah // SPDX-License-Identifier: GPL-2.0
2df330515SLaurent Pinchart /*
3df330515SLaurent Pinchart  * Xilinx Video IP Composite Device
4df330515SLaurent Pinchart  *
5df330515SLaurent Pinchart  * Copyright (C) 2013-2015 Ideas on Board
6df330515SLaurent Pinchart  * Copyright (C) 2013-2015 Xilinx, Inc.
7df330515SLaurent Pinchart  *
8df330515SLaurent Pinchart  * Contacts: Hyun Kwon <hyun.kwon@xilinx.com>
9df330515SLaurent Pinchart  *           Laurent Pinchart <laurent.pinchart@ideasonboard.com>
10df330515SLaurent Pinchart  */
11df330515SLaurent Pinchart 
12df330515SLaurent Pinchart #include <linux/list.h>
13df330515SLaurent Pinchart #include <linux/module.h>
14df330515SLaurent Pinchart #include <linux/of.h>
15df330515SLaurent Pinchart #include <linux/of_graph.h>
16df330515SLaurent Pinchart #include <linux/platform_device.h>
17df330515SLaurent Pinchart #include <linux/slab.h>
18df330515SLaurent Pinchart 
19df330515SLaurent Pinchart #include <media/v4l2-async.h>
20df330515SLaurent Pinchart #include <media/v4l2-common.h>
21df330515SLaurent Pinchart #include <media/v4l2-device.h>
22859969b3SSakari Ailus #include <media/v4l2-fwnode.h>
23df330515SLaurent Pinchart 
24df330515SLaurent Pinchart #include "xilinx-dma.h"
25df330515SLaurent Pinchart #include "xilinx-vipp.h"
26df330515SLaurent Pinchart 
27df330515SLaurent Pinchart #define XVIPP_DMA_S2MM				0
28df330515SLaurent Pinchart #define XVIPP_DMA_MM2S				1
29df330515SLaurent Pinchart 
30df330515SLaurent Pinchart /**
31df330515SLaurent Pinchart  * struct xvip_graph_entity - Entity in the video graph
32df330515SLaurent Pinchart  * @asd: subdev asynchronous registration information
33d079f94cSSteve Longerbeam  * @entity: media entity, from the corresponding V4L2 subdev
34df330515SLaurent Pinchart  * @subdev: V4L2 subdev
35df330515SLaurent Pinchart  */
36df330515SLaurent Pinchart struct xvip_graph_entity {
37adb2dcd5SSakari Ailus 	struct v4l2_async_connection asd; /* must be first */
38df330515SLaurent Pinchart 	struct media_entity *entity;
39df330515SLaurent Pinchart 	struct v4l2_subdev *subdev;
40df330515SLaurent Pinchart };
41df330515SLaurent Pinchart 
42d079f94cSSteve Longerbeam static inline struct xvip_graph_entity *
43adb2dcd5SSakari Ailus to_xvip_entity(struct v4l2_async_connection *asd)
44d079f94cSSteve Longerbeam {
45d079f94cSSteve Longerbeam 	return container_of(asd, struct xvip_graph_entity, asd);
46d079f94cSSteve Longerbeam }
47d079f94cSSteve Longerbeam 
48df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
49df330515SLaurent Pinchart  * Graph Management
50df330515SLaurent Pinchart  */
51df330515SLaurent Pinchart 
52df330515SLaurent Pinchart static struct xvip_graph_entity *
53df330515SLaurent Pinchart xvip_graph_find_entity(struct xvip_composite_device *xdev,
54d079f94cSSteve Longerbeam 		       const struct fwnode_handle *fwnode)
55df330515SLaurent Pinchart {
56df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
57adb2dcd5SSakari Ailus 	struct v4l2_async_connection *asd;
58df330515SLaurent Pinchart 
59*9bf19fbfSSakari Ailus 	list_for_each_entry(asd, &xdev->notifier.done_list, asc_entry) {
60d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
61d079f94cSSteve Longerbeam 		if (entity->asd.match.fwnode == fwnode)
62df330515SLaurent Pinchart 			return entity;
63df330515SLaurent Pinchart 	}
64df330515SLaurent Pinchart 
65df330515SLaurent Pinchart 	return NULL;
66df330515SLaurent Pinchart }
67df330515SLaurent Pinchart 
68df330515SLaurent Pinchart static int xvip_graph_build_one(struct xvip_composite_device *xdev,
69df330515SLaurent Pinchart 				struct xvip_graph_entity *entity)
70df330515SLaurent Pinchart {
71df330515SLaurent Pinchart 	u32 link_flags = MEDIA_LNK_FL_ENABLED;
72df330515SLaurent Pinchart 	struct media_entity *local = entity->entity;
73df330515SLaurent Pinchart 	struct media_entity *remote;
74df330515SLaurent Pinchart 	struct media_pad *local_pad;
75df330515SLaurent Pinchart 	struct media_pad *remote_pad;
76df330515SLaurent Pinchart 	struct xvip_graph_entity *ent;
77859969b3SSakari Ailus 	struct v4l2_fwnode_link link;
78d079f94cSSteve Longerbeam 	struct fwnode_handle *ep = NULL;
79df330515SLaurent Pinchart 	int ret = 0;
80df330515SLaurent Pinchart 
81df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "creating links for entity %s\n", local->name);
82df330515SLaurent Pinchart 
83df330515SLaurent Pinchart 	while (1) {
84df330515SLaurent Pinchart 		/* Get the next endpoint and parse its link. */
85d079f94cSSteve Longerbeam 		ep = fwnode_graph_get_next_endpoint(entity->asd.match.fwnode,
86d079f94cSSteve Longerbeam 						    ep);
87ef94711aSAkinobu Mita 		if (ep == NULL)
88df330515SLaurent Pinchart 			break;
89df330515SLaurent Pinchart 
90d079f94cSSteve Longerbeam 		dev_dbg(xdev->dev, "processing endpoint %p\n", ep);
91df330515SLaurent Pinchart 
92d079f94cSSteve Longerbeam 		ret = v4l2_fwnode_parse_link(ep, &link);
93df330515SLaurent Pinchart 		if (ret < 0) {
94d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "failed to parse link for %p\n",
9568d9c47bSRob Herring 				ep);
96df330515SLaurent Pinchart 			continue;
97df330515SLaurent Pinchart 		}
98df330515SLaurent Pinchart 
99df330515SLaurent Pinchart 		/* Skip sink ports, they will be processed from the other end of
100df330515SLaurent Pinchart 		 * the link.
101df330515SLaurent Pinchart 		 */
102df330515SLaurent Pinchart 		if (link.local_port >= local->num_pads) {
103d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "invalid port number %u for %p\n",
104d079f94cSSteve Longerbeam 				link.local_port, link.local_node);
105859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
106df330515SLaurent Pinchart 			ret = -EINVAL;
107df330515SLaurent Pinchart 			break;
108df330515SLaurent Pinchart 		}
109df330515SLaurent Pinchart 
110df330515SLaurent Pinchart 		local_pad = &local->pads[link.local_port];
111df330515SLaurent Pinchart 
112df330515SLaurent Pinchart 		if (local_pad->flags & MEDIA_PAD_FL_SINK) {
113d079f94cSSteve Longerbeam 			dev_dbg(xdev->dev, "skipping sink port %p:%u\n",
114d079f94cSSteve Longerbeam 				link.local_node, link.local_port);
115859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
116df330515SLaurent Pinchart 			continue;
117df330515SLaurent Pinchart 		}
118df330515SLaurent Pinchart 
119df330515SLaurent Pinchart 		/* Skip DMA engines, they will be processed separately. */
120859969b3SSakari Ailus 		if (link.remote_node == of_fwnode_handle(xdev->dev->of_node)) {
121d079f94cSSteve Longerbeam 			dev_dbg(xdev->dev, "skipping DMA port %p:%u\n",
122d079f94cSSteve Longerbeam 				link.local_node, link.local_port);
123859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
124df330515SLaurent Pinchart 			continue;
125df330515SLaurent Pinchart 		}
126df330515SLaurent Pinchart 
127df330515SLaurent Pinchart 		/* Find the remote entity. */
128d079f94cSSteve Longerbeam 		ent = xvip_graph_find_entity(xdev, link.remote_node);
129df330515SLaurent Pinchart 		if (ent == NULL) {
130d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "no entity found for %p\n",
131d079f94cSSteve Longerbeam 				link.remote_node);
132859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
133df330515SLaurent Pinchart 			ret = -ENODEV;
134df330515SLaurent Pinchart 			break;
135df330515SLaurent Pinchart 		}
136df330515SLaurent Pinchart 
137df330515SLaurent Pinchart 		remote = ent->entity;
138df330515SLaurent Pinchart 
139df330515SLaurent Pinchart 		if (link.remote_port >= remote->num_pads) {
140d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "invalid port number %u on %p\n",
141d079f94cSSteve Longerbeam 				link.remote_port, link.remote_node);
142859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
143df330515SLaurent Pinchart 			ret = -EINVAL;
144df330515SLaurent Pinchart 			break;
145df330515SLaurent Pinchart 		}
146df330515SLaurent Pinchart 
147df330515SLaurent Pinchart 		remote_pad = &remote->pads[link.remote_port];
148df330515SLaurent Pinchart 
149859969b3SSakari Ailus 		v4l2_fwnode_put_link(&link);
150df330515SLaurent Pinchart 
151df330515SLaurent Pinchart 		/* Create the media link. */
152df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n",
153df330515SLaurent Pinchart 			local->name, local_pad->index,
154df330515SLaurent Pinchart 			remote->name, remote_pad->index);
155df330515SLaurent Pinchart 
1568df00a15SMauro Carvalho Chehab 		ret = media_create_pad_link(local, local_pad->index,
157df330515SLaurent Pinchart 					       remote, remote_pad->index,
158df330515SLaurent Pinchart 					       link_flags);
159df330515SLaurent Pinchart 		if (ret < 0) {
160df330515SLaurent Pinchart 			dev_err(xdev->dev,
161df330515SLaurent Pinchart 				"failed to create %s:%u -> %s:%u link\n",
162df330515SLaurent Pinchart 				local->name, local_pad->index,
163df330515SLaurent Pinchart 				remote->name, remote_pad->index);
164df330515SLaurent Pinchart 			break;
165df330515SLaurent Pinchart 		}
166df330515SLaurent Pinchart 	}
167df330515SLaurent Pinchart 
168d079f94cSSteve Longerbeam 	fwnode_handle_put(ep);
169df330515SLaurent Pinchart 	return ret;
170df330515SLaurent Pinchart }
171df330515SLaurent Pinchart 
172df330515SLaurent Pinchart static struct xvip_dma *
173df330515SLaurent Pinchart xvip_graph_find_dma(struct xvip_composite_device *xdev, unsigned int port)
174df330515SLaurent Pinchart {
175df330515SLaurent Pinchart 	struct xvip_dma *dma;
176df330515SLaurent Pinchart 
177df330515SLaurent Pinchart 	list_for_each_entry(dma, &xdev->dmas, list) {
178df330515SLaurent Pinchart 		if (dma->port == port)
179df330515SLaurent Pinchart 			return dma;
180df330515SLaurent Pinchart 	}
181df330515SLaurent Pinchart 
182df330515SLaurent Pinchart 	return NULL;
183df330515SLaurent Pinchart }
184df330515SLaurent Pinchart 
185df330515SLaurent Pinchart static int xvip_graph_build_dma(struct xvip_composite_device *xdev)
186df330515SLaurent Pinchart {
187df330515SLaurent Pinchart 	u32 link_flags = MEDIA_LNK_FL_ENABLED;
188df330515SLaurent Pinchart 	struct device_node *node = xdev->dev->of_node;
189df330515SLaurent Pinchart 	struct media_entity *source;
190df330515SLaurent Pinchart 	struct media_entity *sink;
191df330515SLaurent Pinchart 	struct media_pad *source_pad;
192df330515SLaurent Pinchart 	struct media_pad *sink_pad;
193df330515SLaurent Pinchart 	struct xvip_graph_entity *ent;
194859969b3SSakari Ailus 	struct v4l2_fwnode_link link;
195df330515SLaurent Pinchart 	struct device_node *ep = NULL;
196df330515SLaurent Pinchart 	struct xvip_dma *dma;
197df330515SLaurent Pinchart 	int ret = 0;
198df330515SLaurent Pinchart 
199df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "creating links for DMA engines\n");
200df330515SLaurent Pinchart 
201df330515SLaurent Pinchart 	while (1) {
202df330515SLaurent Pinchart 		/* Get the next endpoint and parse its link. */
203ef94711aSAkinobu Mita 		ep = of_graph_get_next_endpoint(node, ep);
204ef94711aSAkinobu Mita 		if (ep == NULL)
205df330515SLaurent Pinchart 			break;
206df330515SLaurent Pinchart 
20768d9c47bSRob Herring 		dev_dbg(xdev->dev, "processing endpoint %pOF\n", ep);
208df330515SLaurent Pinchart 
209859969b3SSakari Ailus 		ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
210df330515SLaurent Pinchart 		if (ret < 0) {
21168d9c47bSRob Herring 			dev_err(xdev->dev, "failed to parse link for %pOF\n",
21268d9c47bSRob Herring 				ep);
213df330515SLaurent Pinchart 			continue;
214df330515SLaurent Pinchart 		}
215df330515SLaurent Pinchart 
216df330515SLaurent Pinchart 		/* Find the DMA engine. */
217df330515SLaurent Pinchart 		dma = xvip_graph_find_dma(xdev, link.local_port);
218df330515SLaurent Pinchart 		if (dma == NULL) {
219df330515SLaurent Pinchart 			dev_err(xdev->dev, "no DMA engine found for port %u\n",
220df330515SLaurent Pinchart 				link.local_port);
221859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
222df330515SLaurent Pinchart 			ret = -EINVAL;
223df330515SLaurent Pinchart 			break;
224df330515SLaurent Pinchart 		}
225df330515SLaurent Pinchart 
226df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating link for DMA engine %s\n",
227df330515SLaurent Pinchart 			dma->video.name);
228df330515SLaurent Pinchart 
229df330515SLaurent Pinchart 		/* Find the remote entity. */
230d079f94cSSteve Longerbeam 		ent = xvip_graph_find_entity(xdev, link.remote_node);
231df330515SLaurent Pinchart 		if (ent == NULL) {
23268d9c47bSRob Herring 			dev_err(xdev->dev, "no entity found for %pOF\n",
23368d9c47bSRob Herring 				to_of_node(link.remote_node));
234859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
235df330515SLaurent Pinchart 			ret = -ENODEV;
236df330515SLaurent Pinchart 			break;
237df330515SLaurent Pinchart 		}
238df330515SLaurent Pinchart 
239df330515SLaurent Pinchart 		if (link.remote_port >= ent->entity->num_pads) {
24068d9c47bSRob Herring 			dev_err(xdev->dev, "invalid port number %u on %pOF\n",
241859969b3SSakari Ailus 				link.remote_port,
24268d9c47bSRob Herring 				to_of_node(link.remote_node));
243859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
244df330515SLaurent Pinchart 			ret = -EINVAL;
245df330515SLaurent Pinchart 			break;
246df330515SLaurent Pinchart 		}
247df330515SLaurent Pinchart 
248df330515SLaurent Pinchart 		if (dma->pad.flags & MEDIA_PAD_FL_SOURCE) {
249df330515SLaurent Pinchart 			source = &dma->video.entity;
250df330515SLaurent Pinchart 			source_pad = &dma->pad;
251df330515SLaurent Pinchart 			sink = ent->entity;
252df330515SLaurent Pinchart 			sink_pad = &sink->pads[link.remote_port];
253df330515SLaurent Pinchart 		} else {
254df330515SLaurent Pinchart 			source = ent->entity;
255df330515SLaurent Pinchart 			source_pad = &source->pads[link.remote_port];
256df330515SLaurent Pinchart 			sink = &dma->video.entity;
257df330515SLaurent Pinchart 			sink_pad = &dma->pad;
258df330515SLaurent Pinchart 		}
259df330515SLaurent Pinchart 
260859969b3SSakari Ailus 		v4l2_fwnode_put_link(&link);
261df330515SLaurent Pinchart 
262df330515SLaurent Pinchart 		/* Create the media link. */
263df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n",
264df330515SLaurent Pinchart 			source->name, source_pad->index,
265df330515SLaurent Pinchart 			sink->name, sink_pad->index);
266df330515SLaurent Pinchart 
2678df00a15SMauro Carvalho Chehab 		ret = media_create_pad_link(source, source_pad->index,
268df330515SLaurent Pinchart 					       sink, sink_pad->index,
269df330515SLaurent Pinchart 					       link_flags);
270df330515SLaurent Pinchart 		if (ret < 0) {
271df330515SLaurent Pinchart 			dev_err(xdev->dev,
272df330515SLaurent Pinchart 				"failed to create %s:%u -> %s:%u link\n",
273df330515SLaurent Pinchart 				source->name, source_pad->index,
274df330515SLaurent Pinchart 				sink->name, sink_pad->index);
275df330515SLaurent Pinchart 			break;
276df330515SLaurent Pinchart 		}
277df330515SLaurent Pinchart 	}
278df330515SLaurent Pinchart 
279df330515SLaurent Pinchart 	of_node_put(ep);
280df330515SLaurent Pinchart 	return ret;
281df330515SLaurent Pinchart }
282df330515SLaurent Pinchart 
283df330515SLaurent Pinchart static int xvip_graph_notify_complete(struct v4l2_async_notifier *notifier)
284df330515SLaurent Pinchart {
285df330515SLaurent Pinchart 	struct xvip_composite_device *xdev =
286df330515SLaurent Pinchart 		container_of(notifier, struct xvip_composite_device, notifier);
287df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
288adb2dcd5SSakari Ailus 	struct v4l2_async_connection *asd;
289df330515SLaurent Pinchart 	int ret;
290df330515SLaurent Pinchart 
291df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "notify complete, all subdevs registered\n");
292df330515SLaurent Pinchart 
293df330515SLaurent Pinchart 	/* Create links for every entity. */
294*9bf19fbfSSakari Ailus 	list_for_each_entry(asd, &xdev->notifier.done_list, asc_entry) {
295d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
296df330515SLaurent Pinchart 		ret = xvip_graph_build_one(xdev, entity);
297df330515SLaurent Pinchart 		if (ret < 0)
298df330515SLaurent Pinchart 			return ret;
299df330515SLaurent Pinchart 	}
300df330515SLaurent Pinchart 
301df330515SLaurent Pinchart 	/* Create links for DMA channels. */
302df330515SLaurent Pinchart 	ret = xvip_graph_build_dma(xdev);
303df330515SLaurent Pinchart 	if (ret < 0)
304df330515SLaurent Pinchart 		return ret;
305df330515SLaurent Pinchart 
306df330515SLaurent Pinchart 	ret = v4l2_device_register_subdev_nodes(&xdev->v4l2_dev);
307df330515SLaurent Pinchart 	if (ret < 0)
308df330515SLaurent Pinchart 		dev_err(xdev->dev, "failed to register subdev nodes\n");
309df330515SLaurent Pinchart 
3109832e155SJavier Martinez Canillas 	return media_device_register(&xdev->media_dev);
311df330515SLaurent Pinchart }
312df330515SLaurent Pinchart 
313df330515SLaurent Pinchart static int xvip_graph_notify_bound(struct v4l2_async_notifier *notifier,
314df330515SLaurent Pinchart 				   struct v4l2_subdev *subdev,
315adb2dcd5SSakari Ailus 				   struct v4l2_async_connection *asc)
316df330515SLaurent Pinchart {
317adb2dcd5SSakari Ailus 	struct xvip_graph_entity *entity = to_xvip_entity(asc);
318df330515SLaurent Pinchart 
319df330515SLaurent Pinchart 	entity->entity = &subdev->entity;
320df330515SLaurent Pinchart 	entity->subdev = subdev;
321df330515SLaurent Pinchart 
32264585805SSakari Ailus 	return 0;
323df330515SLaurent Pinchart }
324df330515SLaurent Pinchart 
325b6ee3f0dSLaurent Pinchart static const struct v4l2_async_notifier_operations xvip_graph_notify_ops = {
326b6ee3f0dSLaurent Pinchart 	.bound = xvip_graph_notify_bound,
327b6ee3f0dSLaurent Pinchart 	.complete = xvip_graph_notify_complete,
328b6ee3f0dSLaurent Pinchart };
329b6ee3f0dSLaurent Pinchart 
330df330515SLaurent Pinchart static int xvip_graph_parse_one(struct xvip_composite_device *xdev,
331d079f94cSSteve Longerbeam 				struct fwnode_handle *fwnode)
332df330515SLaurent Pinchart {
333d079f94cSSteve Longerbeam 	struct fwnode_handle *remote;
334d079f94cSSteve Longerbeam 	struct fwnode_handle *ep = NULL;
335df330515SLaurent Pinchart 	int ret = 0;
336df330515SLaurent Pinchart 
337d079f94cSSteve Longerbeam 	dev_dbg(xdev->dev, "parsing node %p\n", fwnode);
338df330515SLaurent Pinchart 
339df330515SLaurent Pinchart 	while (1) {
340b01edcbdSLaurent Pinchart 		struct xvip_graph_entity *xge;
341d079f94cSSteve Longerbeam 
342d079f94cSSteve Longerbeam 		ep = fwnode_graph_get_next_endpoint(fwnode, ep);
343c64ee347SFranck Jullien 		if (ep == NULL)
344df330515SLaurent Pinchart 			break;
345df330515SLaurent Pinchart 
346d079f94cSSteve Longerbeam 		dev_dbg(xdev->dev, "handling endpoint %p\n", ep);
347df330515SLaurent Pinchart 
348d079f94cSSteve Longerbeam 		remote = fwnode_graph_get_remote_port_parent(ep);
349df330515SLaurent Pinchart 		if (remote == NULL) {
350df330515SLaurent Pinchart 			ret = -EINVAL;
351d079f94cSSteve Longerbeam 			goto err_notifier_cleanup;
352df330515SLaurent Pinchart 		}
353df330515SLaurent Pinchart 
354d079f94cSSteve Longerbeam 		fwnode_handle_put(ep);
355d079f94cSSteve Longerbeam 
356df330515SLaurent Pinchart 		/* Skip entities that we have already processed. */
357d079f94cSSteve Longerbeam 		if (remote == of_fwnode_handle(xdev->dev->of_node) ||
358df330515SLaurent Pinchart 		    xvip_graph_find_entity(xdev, remote)) {
359d079f94cSSteve Longerbeam 			fwnode_handle_put(remote);
360df330515SLaurent Pinchart 			continue;
361df330515SLaurent Pinchart 		}
362df330515SLaurent Pinchart 
3633c8c1539SSakari Ailus 		xge = v4l2_async_nf_add_fwnode(&xdev->notifier, remote,
364b01edcbdSLaurent Pinchart 					       struct xvip_graph_entity);
365016413d9SSakari Ailus 		fwnode_handle_put(remote);
366b01edcbdSLaurent Pinchart 		if (IS_ERR(xge)) {
367b01edcbdSLaurent Pinchart 			ret = PTR_ERR(xge);
368d079f94cSSteve Longerbeam 			goto err_notifier_cleanup;
369d079f94cSSteve Longerbeam 		}
370df330515SLaurent Pinchart 	}
371df330515SLaurent Pinchart 
372d079f94cSSteve Longerbeam 	return 0;
373df330515SLaurent Pinchart 
374d079f94cSSteve Longerbeam err_notifier_cleanup:
3753c8c1539SSakari Ailus 	v4l2_async_nf_cleanup(&xdev->notifier);
376d079f94cSSteve Longerbeam 	fwnode_handle_put(ep);
377df330515SLaurent Pinchart 	return ret;
378df330515SLaurent Pinchart }
379df330515SLaurent Pinchart 
380df330515SLaurent Pinchart static int xvip_graph_parse(struct xvip_composite_device *xdev)
381df330515SLaurent Pinchart {
382df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
383adb2dcd5SSakari Ailus 	struct v4l2_async_connection *asd;
384df330515SLaurent Pinchart 	int ret;
385df330515SLaurent Pinchart 
386df330515SLaurent Pinchart 	/*
387df330515SLaurent Pinchart 	 * Walk the links to parse the full graph. Start by parsing the
388df330515SLaurent Pinchart 	 * composite node and then parse entities in turn. The list_for_each
389df330515SLaurent Pinchart 	 * loop will handle entities added at the end of the list while walking
390df330515SLaurent Pinchart 	 * the links.
391df330515SLaurent Pinchart 	 */
392d079f94cSSteve Longerbeam 	ret = xvip_graph_parse_one(xdev, of_fwnode_handle(xdev->dev->of_node));
393df330515SLaurent Pinchart 	if (ret < 0)
394df330515SLaurent Pinchart 		return 0;
395df330515SLaurent Pinchart 
396*9bf19fbfSSakari Ailus 	list_for_each_entry(asd, &xdev->notifier.waiting_list, asc_entry) {
397d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
398d079f94cSSteve Longerbeam 		ret = xvip_graph_parse_one(xdev, entity->asd.match.fwnode);
399d079f94cSSteve Longerbeam 		if (ret < 0) {
4003c8c1539SSakari Ailus 			v4l2_async_nf_cleanup(&xdev->notifier);
401df330515SLaurent Pinchart 			break;
402df330515SLaurent Pinchart 		}
403d079f94cSSteve Longerbeam 	}
404df330515SLaurent Pinchart 
405df330515SLaurent Pinchart 	return ret;
406df330515SLaurent Pinchart }
407df330515SLaurent Pinchart 
408df330515SLaurent Pinchart static int xvip_graph_dma_init_one(struct xvip_composite_device *xdev,
409df330515SLaurent Pinchart 				   struct device_node *node)
410df330515SLaurent Pinchart {
411df330515SLaurent Pinchart 	struct xvip_dma *dma;
412df330515SLaurent Pinchart 	enum v4l2_buf_type type;
413df330515SLaurent Pinchart 	const char *direction;
414df330515SLaurent Pinchart 	unsigned int index;
415df330515SLaurent Pinchart 	int ret;
416df330515SLaurent Pinchart 
417df330515SLaurent Pinchart 	ret = of_property_read_string(node, "direction", &direction);
418df330515SLaurent Pinchart 	if (ret < 0)
419df330515SLaurent Pinchart 		return ret;
420df330515SLaurent Pinchart 
421df330515SLaurent Pinchart 	if (strcmp(direction, "input") == 0)
422df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
423df330515SLaurent Pinchart 	else if (strcmp(direction, "output") == 0)
424df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
425df330515SLaurent Pinchart 	else
426df330515SLaurent Pinchart 		return -EINVAL;
427df330515SLaurent Pinchart 
428df330515SLaurent Pinchart 	of_property_read_u32(node, "reg", &index);
429df330515SLaurent Pinchart 
430df330515SLaurent Pinchart 	dma = devm_kzalloc(xdev->dev, sizeof(*dma), GFP_KERNEL);
431df330515SLaurent Pinchart 	if (dma == NULL)
432df330515SLaurent Pinchart 		return -ENOMEM;
433df330515SLaurent Pinchart 
434df330515SLaurent Pinchart 	ret = xvip_dma_init(xdev, dma, type, index);
435df330515SLaurent Pinchart 	if (ret < 0) {
43668d9c47bSRob Herring 		dev_err(xdev->dev, "%pOF initialization failed\n", node);
437df330515SLaurent Pinchart 		return ret;
438df330515SLaurent Pinchart 	}
439df330515SLaurent Pinchart 
440df330515SLaurent Pinchart 	list_add_tail(&dma->list, &xdev->dmas);
441df330515SLaurent Pinchart 
442df330515SLaurent Pinchart 	xdev->v4l2_caps |= type == V4L2_BUF_TYPE_VIDEO_CAPTURE
443df330515SLaurent Pinchart 			 ? V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_VIDEO_OUTPUT;
444df330515SLaurent Pinchart 
445df330515SLaurent Pinchart 	return 0;
446df330515SLaurent Pinchart }
447df330515SLaurent Pinchart 
448df330515SLaurent Pinchart static int xvip_graph_dma_init(struct xvip_composite_device *xdev)
449df330515SLaurent Pinchart {
450df330515SLaurent Pinchart 	struct device_node *ports;
451df330515SLaurent Pinchart 	struct device_node *port;
4521c78f19cSMiaoqian Lin 	int ret = 0;
453df330515SLaurent Pinchart 
454df330515SLaurent Pinchart 	ports = of_get_child_by_name(xdev->dev->of_node, "ports");
455df330515SLaurent Pinchart 	if (ports == NULL) {
456df330515SLaurent Pinchart 		dev_err(xdev->dev, "ports node not present\n");
457df330515SLaurent Pinchart 		return -EINVAL;
458df330515SLaurent Pinchart 	}
459df330515SLaurent Pinchart 
460df330515SLaurent Pinchart 	for_each_child_of_node(ports, port) {
461df330515SLaurent Pinchart 		ret = xvip_graph_dma_init_one(xdev, port);
4621c78f19cSMiaoqian Lin 		if (ret) {
463cea590d4SJulia Lawall 			of_node_put(port);
4641c78f19cSMiaoqian Lin 			break;
465df330515SLaurent Pinchart 		}
466cea590d4SJulia Lawall 	}
467df330515SLaurent Pinchart 
4681c78f19cSMiaoqian Lin 	of_node_put(ports);
4691c78f19cSMiaoqian Lin 	return ret;
470df330515SLaurent Pinchart }
471df330515SLaurent Pinchart 
472df330515SLaurent Pinchart static void xvip_graph_cleanup(struct xvip_composite_device *xdev)
473df330515SLaurent Pinchart {
474df330515SLaurent Pinchart 	struct xvip_dma *dmap;
475df330515SLaurent Pinchart 	struct xvip_dma *dma;
476df330515SLaurent Pinchart 
4773c8c1539SSakari Ailus 	v4l2_async_nf_unregister(&xdev->notifier);
4783c8c1539SSakari Ailus 	v4l2_async_nf_cleanup(&xdev->notifier);
479df330515SLaurent Pinchart 
480df330515SLaurent Pinchart 	list_for_each_entry_safe(dma, dmap, &xdev->dmas, list) {
481df330515SLaurent Pinchart 		xvip_dma_cleanup(dma);
482df330515SLaurent Pinchart 		list_del(&dma->list);
483df330515SLaurent Pinchart 	}
484df330515SLaurent Pinchart }
485df330515SLaurent Pinchart 
486df330515SLaurent Pinchart static int xvip_graph_init(struct xvip_composite_device *xdev)
487df330515SLaurent Pinchart {
488df330515SLaurent Pinchart 	int ret;
489df330515SLaurent Pinchart 
490df330515SLaurent Pinchart 	/* Init the DMA channels. */
491df330515SLaurent Pinchart 	ret = xvip_graph_dma_init(xdev);
492df330515SLaurent Pinchart 	if (ret < 0) {
493df330515SLaurent Pinchart 		dev_err(xdev->dev, "DMA initialization failed\n");
494df330515SLaurent Pinchart 		goto done;
495df330515SLaurent Pinchart 	}
496df330515SLaurent Pinchart 
497df330515SLaurent Pinchart 	/* Parse the graph to extract a list of subdevice DT nodes. */
498df330515SLaurent Pinchart 	ret = xvip_graph_parse(xdev);
499df330515SLaurent Pinchart 	if (ret < 0) {
500df330515SLaurent Pinchart 		dev_err(xdev->dev, "graph parsing failed\n");
501df330515SLaurent Pinchart 		goto done;
502df330515SLaurent Pinchart 	}
503df330515SLaurent Pinchart 
504*9bf19fbfSSakari Ailus 	if (list_empty(&xdev->notifier.waiting_list)) {
505df330515SLaurent Pinchart 		dev_err(xdev->dev, "no subdev found in graph\n");
50695667791SJia-Ju Bai 		ret = -ENOENT;
507df330515SLaurent Pinchart 		goto done;
508df330515SLaurent Pinchart 	}
509df330515SLaurent Pinchart 
510df330515SLaurent Pinchart 	/* Register the subdevices notifier. */
511b6ee3f0dSLaurent Pinchart 	xdev->notifier.ops = &xvip_graph_notify_ops;
512df330515SLaurent Pinchart 
5133c8c1539SSakari Ailus 	ret = v4l2_async_nf_register(&xdev->v4l2_dev, &xdev->notifier);
514df330515SLaurent Pinchart 	if (ret < 0) {
515df330515SLaurent Pinchart 		dev_err(xdev->dev, "notifier registration failed\n");
516df330515SLaurent Pinchart 		goto done;
517df330515SLaurent Pinchart 	}
518df330515SLaurent Pinchart 
519df330515SLaurent Pinchart 	ret = 0;
520df330515SLaurent Pinchart 
521df330515SLaurent Pinchart done:
522df330515SLaurent Pinchart 	if (ret < 0)
523df330515SLaurent Pinchart 		xvip_graph_cleanup(xdev);
524df330515SLaurent Pinchart 
525df330515SLaurent Pinchart 	return ret;
526df330515SLaurent Pinchart }
527df330515SLaurent Pinchart 
528df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
529df330515SLaurent Pinchart  * Media Controller and V4L2
530df330515SLaurent Pinchart  */
531df330515SLaurent Pinchart 
532df330515SLaurent Pinchart static void xvip_composite_v4l2_cleanup(struct xvip_composite_device *xdev)
533df330515SLaurent Pinchart {
534df330515SLaurent Pinchart 	v4l2_device_unregister(&xdev->v4l2_dev);
535df330515SLaurent Pinchart 	media_device_unregister(&xdev->media_dev);
5369832e155SJavier Martinez Canillas 	media_device_cleanup(&xdev->media_dev);
537df330515SLaurent Pinchart }
538df330515SLaurent Pinchart 
539df330515SLaurent Pinchart static int xvip_composite_v4l2_init(struct xvip_composite_device *xdev)
540df330515SLaurent Pinchart {
541df330515SLaurent Pinchart 	int ret;
542df330515SLaurent Pinchart 
543df330515SLaurent Pinchart 	xdev->media_dev.dev = xdev->dev;
544c0decac1SMauro Carvalho Chehab 	strscpy(xdev->media_dev.model, "Xilinx Video Composite Device",
545df330515SLaurent Pinchart 		sizeof(xdev->media_dev.model));
546df330515SLaurent Pinchart 	xdev->media_dev.hw_revision = 0;
547df330515SLaurent Pinchart 
5489832e155SJavier Martinez Canillas 	media_device_init(&xdev->media_dev);
549df330515SLaurent Pinchart 
550df330515SLaurent Pinchart 	xdev->v4l2_dev.mdev = &xdev->media_dev;
551df330515SLaurent Pinchart 	ret = v4l2_device_register(xdev->dev, &xdev->v4l2_dev);
552df330515SLaurent Pinchart 	if (ret < 0) {
553df330515SLaurent Pinchart 		dev_err(xdev->dev, "V4L2 device registration failed (%d)\n",
554df330515SLaurent Pinchart 			ret);
5559832e155SJavier Martinez Canillas 		media_device_cleanup(&xdev->media_dev);
556df330515SLaurent Pinchart 		return ret;
557df330515SLaurent Pinchart 	}
558df330515SLaurent Pinchart 
559df330515SLaurent Pinchart 	return 0;
560df330515SLaurent Pinchart }
561df330515SLaurent Pinchart 
562df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
563df330515SLaurent Pinchart  * Platform Device Driver
564df330515SLaurent Pinchart  */
565df330515SLaurent Pinchart 
566df330515SLaurent Pinchart static int xvip_composite_probe(struct platform_device *pdev)
567df330515SLaurent Pinchart {
568df330515SLaurent Pinchart 	struct xvip_composite_device *xdev;
569df330515SLaurent Pinchart 	int ret;
570df330515SLaurent Pinchart 
571df330515SLaurent Pinchart 	xdev = devm_kzalloc(&pdev->dev, sizeof(*xdev), GFP_KERNEL);
572df330515SLaurent Pinchart 	if (!xdev)
573df330515SLaurent Pinchart 		return -ENOMEM;
574df330515SLaurent Pinchart 
575df330515SLaurent Pinchart 	xdev->dev = &pdev->dev;
576df330515SLaurent Pinchart 	INIT_LIST_HEAD(&xdev->dmas);
5773c8c1539SSakari Ailus 	v4l2_async_nf_init(&xdev->notifier);
578df330515SLaurent Pinchart 
579df330515SLaurent Pinchart 	ret = xvip_composite_v4l2_init(xdev);
580df330515SLaurent Pinchart 	if (ret < 0)
581df330515SLaurent Pinchart 		return ret;
582df330515SLaurent Pinchart 
583df330515SLaurent Pinchart 	ret = xvip_graph_init(xdev);
584df330515SLaurent Pinchart 	if (ret < 0)
585df330515SLaurent Pinchart 		goto error;
586df330515SLaurent Pinchart 
587df330515SLaurent Pinchart 	platform_set_drvdata(pdev, xdev);
588df330515SLaurent Pinchart 
589df330515SLaurent Pinchart 	dev_info(xdev->dev, "device registered\n");
590df330515SLaurent Pinchart 
591df330515SLaurent Pinchart 	return 0;
592df330515SLaurent Pinchart 
593df330515SLaurent Pinchart error:
594df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
595df330515SLaurent Pinchart 	return ret;
596df330515SLaurent Pinchart }
597df330515SLaurent Pinchart 
598b8368fbeSUwe Kleine-König static void xvip_composite_remove(struct platform_device *pdev)
599df330515SLaurent Pinchart {
600df330515SLaurent Pinchart 	struct xvip_composite_device *xdev = platform_get_drvdata(pdev);
601df330515SLaurent Pinchart 
602df330515SLaurent Pinchart 	xvip_graph_cleanup(xdev);
603df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
604df330515SLaurent Pinchart }
605df330515SLaurent Pinchart 
606df330515SLaurent Pinchart static const struct of_device_id xvip_composite_of_id_table[] = {
607df330515SLaurent Pinchart 	{ .compatible = "xlnx,video" },
608df330515SLaurent Pinchart 	{ }
609df330515SLaurent Pinchart };
610df330515SLaurent Pinchart MODULE_DEVICE_TABLE(of, xvip_composite_of_id_table);
611df330515SLaurent Pinchart 
612df330515SLaurent Pinchart static struct platform_driver xvip_composite_driver = {
613df330515SLaurent Pinchart 	.driver = {
614df330515SLaurent Pinchart 		.name = "xilinx-video",
615df330515SLaurent Pinchart 		.of_match_table = xvip_composite_of_id_table,
616df330515SLaurent Pinchart 	},
617df330515SLaurent Pinchart 	.probe = xvip_composite_probe,
618b8368fbeSUwe Kleine-König 	.remove_new = xvip_composite_remove,
619df330515SLaurent Pinchart };
620df330515SLaurent Pinchart 
621df330515SLaurent Pinchart module_platform_driver(xvip_composite_driver);
622df330515SLaurent Pinchart 
623df330515SLaurent Pinchart MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
624df330515SLaurent Pinchart MODULE_DESCRIPTION("Xilinx Video IP Composite Driver");
625df330515SLaurent Pinchart MODULE_LICENSE("GPL v2");
626