xref: /linux/drivers/media/platform/xilinx/xilinx-vipp.c (revision 016413d967061fc2eb6798a487b3022bef7698a6)
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 {
37d079f94cSSteve Longerbeam 	struct v4l2_async_subdev 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 *
43d079f94cSSteve Longerbeam to_xvip_entity(struct v4l2_async_subdev *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;
57d079f94cSSteve Longerbeam 	struct v4l2_async_subdev *asd;
58df330515SLaurent Pinchart 
59d079f94cSSteve Longerbeam 	list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) {
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;
288d079f94cSSteve Longerbeam 	struct v4l2_async_subdev *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. */
294d079f94cSSteve Longerbeam 	list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) {
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,
315d079f94cSSteve Longerbeam 				   struct v4l2_async_subdev *unused)
316df330515SLaurent Pinchart {
317df330515SLaurent Pinchart 	struct xvip_composite_device *xdev =
318df330515SLaurent Pinchart 		container_of(notifier, struct xvip_composite_device, notifier);
319df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
320d079f94cSSteve Longerbeam 	struct v4l2_async_subdev *asd;
321df330515SLaurent Pinchart 
322df330515SLaurent Pinchart 	/* Locate the entity corresponding to the bound subdev and store the
323df330515SLaurent Pinchart 	 * subdev pointer.
324df330515SLaurent Pinchart 	 */
325d079f94cSSteve Longerbeam 	list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) {
326d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
327d079f94cSSteve Longerbeam 
328d079f94cSSteve Longerbeam 		if (entity->asd.match.fwnode != subdev->fwnode)
329df330515SLaurent Pinchart 			continue;
330df330515SLaurent Pinchart 
331df330515SLaurent Pinchart 		if (entity->subdev) {
332d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "duplicate subdev for node %p\n",
333d079f94cSSteve Longerbeam 				entity->asd.match.fwnode);
334df330515SLaurent Pinchart 			return -EINVAL;
335df330515SLaurent Pinchart 		}
336df330515SLaurent Pinchart 
337df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "subdev %s bound\n", subdev->name);
338df330515SLaurent Pinchart 		entity->entity = &subdev->entity;
339df330515SLaurent Pinchart 		entity->subdev = subdev;
340df330515SLaurent Pinchart 		return 0;
341df330515SLaurent Pinchart 	}
342df330515SLaurent Pinchart 
343df330515SLaurent Pinchart 	dev_err(xdev->dev, "no entity for subdev %s\n", subdev->name);
344df330515SLaurent Pinchart 	return -EINVAL;
345df330515SLaurent Pinchart }
346df330515SLaurent Pinchart 
347b6ee3f0dSLaurent Pinchart static const struct v4l2_async_notifier_operations xvip_graph_notify_ops = {
348b6ee3f0dSLaurent Pinchart 	.bound = xvip_graph_notify_bound,
349b6ee3f0dSLaurent Pinchart 	.complete = xvip_graph_notify_complete,
350b6ee3f0dSLaurent Pinchart };
351b6ee3f0dSLaurent Pinchart 
352df330515SLaurent Pinchart static int xvip_graph_parse_one(struct xvip_composite_device *xdev,
353d079f94cSSteve Longerbeam 				struct fwnode_handle *fwnode)
354df330515SLaurent Pinchart {
355d079f94cSSteve Longerbeam 	struct fwnode_handle *remote;
356d079f94cSSteve Longerbeam 	struct fwnode_handle *ep = NULL;
357df330515SLaurent Pinchart 	int ret = 0;
358df330515SLaurent Pinchart 
359d079f94cSSteve Longerbeam 	dev_dbg(xdev->dev, "parsing node %p\n", fwnode);
360df330515SLaurent Pinchart 
361df330515SLaurent Pinchart 	while (1) {
362d079f94cSSteve Longerbeam 		struct v4l2_async_subdev *asd;
363d079f94cSSteve Longerbeam 
364d079f94cSSteve Longerbeam 		ep = fwnode_graph_get_next_endpoint(fwnode, ep);
365c64ee347SFranck Jullien 		if (ep == NULL)
366df330515SLaurent Pinchart 			break;
367df330515SLaurent Pinchart 
368d079f94cSSteve Longerbeam 		dev_dbg(xdev->dev, "handling endpoint %p\n", ep);
369df330515SLaurent Pinchart 
370d079f94cSSteve Longerbeam 		remote = fwnode_graph_get_remote_port_parent(ep);
371df330515SLaurent Pinchart 		if (remote == NULL) {
372df330515SLaurent Pinchart 			ret = -EINVAL;
373d079f94cSSteve Longerbeam 			goto err_notifier_cleanup;
374df330515SLaurent Pinchart 		}
375df330515SLaurent Pinchart 
376d079f94cSSteve Longerbeam 		fwnode_handle_put(ep);
377d079f94cSSteve Longerbeam 
378df330515SLaurent Pinchart 		/* Skip entities that we have already processed. */
379d079f94cSSteve Longerbeam 		if (remote == of_fwnode_handle(xdev->dev->of_node) ||
380df330515SLaurent Pinchart 		    xvip_graph_find_entity(xdev, remote)) {
381d079f94cSSteve Longerbeam 			fwnode_handle_put(remote);
382df330515SLaurent Pinchart 			continue;
383df330515SLaurent Pinchart 		}
384df330515SLaurent Pinchart 
385d079f94cSSteve Longerbeam 		asd = v4l2_async_notifier_add_fwnode_subdev(
386d079f94cSSteve Longerbeam 			&xdev->notifier, remote,
387d079f94cSSteve Longerbeam 			sizeof(struct xvip_graph_entity));
388*016413d9SSakari Ailus 		fwnode_handle_put(remote);
389d079f94cSSteve Longerbeam 		if (IS_ERR(asd)) {
390d079f94cSSteve Longerbeam 			ret = PTR_ERR(asd);
391d079f94cSSteve Longerbeam 			goto err_notifier_cleanup;
392d079f94cSSteve Longerbeam 		}
393df330515SLaurent Pinchart 	}
394df330515SLaurent Pinchart 
395d079f94cSSteve Longerbeam 	return 0;
396df330515SLaurent Pinchart 
397d079f94cSSteve Longerbeam err_notifier_cleanup:
398d079f94cSSteve Longerbeam 	v4l2_async_notifier_cleanup(&xdev->notifier);
399d079f94cSSteve Longerbeam 	fwnode_handle_put(ep);
400df330515SLaurent Pinchart 	return ret;
401df330515SLaurent Pinchart }
402df330515SLaurent Pinchart 
403df330515SLaurent Pinchart static int xvip_graph_parse(struct xvip_composite_device *xdev)
404df330515SLaurent Pinchart {
405df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
406d079f94cSSteve Longerbeam 	struct v4l2_async_subdev *asd;
407df330515SLaurent Pinchart 	int ret;
408df330515SLaurent Pinchart 
409df330515SLaurent Pinchart 	/*
410df330515SLaurent Pinchart 	 * Walk the links to parse the full graph. Start by parsing the
411df330515SLaurent Pinchart 	 * composite node and then parse entities in turn. The list_for_each
412df330515SLaurent Pinchart 	 * loop will handle entities added at the end of the list while walking
413df330515SLaurent Pinchart 	 * the links.
414df330515SLaurent Pinchart 	 */
415d079f94cSSteve Longerbeam 	ret = xvip_graph_parse_one(xdev, of_fwnode_handle(xdev->dev->of_node));
416df330515SLaurent Pinchart 	if (ret < 0)
417df330515SLaurent Pinchart 		return 0;
418df330515SLaurent Pinchart 
419d079f94cSSteve Longerbeam 	list_for_each_entry(asd, &xdev->notifier.asd_list, asd_list) {
420d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
421d079f94cSSteve Longerbeam 		ret = xvip_graph_parse_one(xdev, entity->asd.match.fwnode);
422d079f94cSSteve Longerbeam 		if (ret < 0) {
423d079f94cSSteve Longerbeam 			v4l2_async_notifier_cleanup(&xdev->notifier);
424df330515SLaurent Pinchart 			break;
425df330515SLaurent Pinchart 		}
426d079f94cSSteve Longerbeam 	}
427df330515SLaurent Pinchart 
428df330515SLaurent Pinchart 	return ret;
429df330515SLaurent Pinchart }
430df330515SLaurent Pinchart 
431df330515SLaurent Pinchart static int xvip_graph_dma_init_one(struct xvip_composite_device *xdev,
432df330515SLaurent Pinchart 				   struct device_node *node)
433df330515SLaurent Pinchart {
434df330515SLaurent Pinchart 	struct xvip_dma *dma;
435df330515SLaurent Pinchart 	enum v4l2_buf_type type;
436df330515SLaurent Pinchart 	const char *direction;
437df330515SLaurent Pinchart 	unsigned int index;
438df330515SLaurent Pinchart 	int ret;
439df330515SLaurent Pinchart 
440df330515SLaurent Pinchart 	ret = of_property_read_string(node, "direction", &direction);
441df330515SLaurent Pinchart 	if (ret < 0)
442df330515SLaurent Pinchart 		return ret;
443df330515SLaurent Pinchart 
444df330515SLaurent Pinchart 	if (strcmp(direction, "input") == 0)
445df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
446df330515SLaurent Pinchart 	else if (strcmp(direction, "output") == 0)
447df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
448df330515SLaurent Pinchart 	else
449df330515SLaurent Pinchart 		return -EINVAL;
450df330515SLaurent Pinchart 
451df330515SLaurent Pinchart 	of_property_read_u32(node, "reg", &index);
452df330515SLaurent Pinchart 
453df330515SLaurent Pinchart 	dma = devm_kzalloc(xdev->dev, sizeof(*dma), GFP_KERNEL);
454df330515SLaurent Pinchart 	if (dma == NULL)
455df330515SLaurent Pinchart 		return -ENOMEM;
456df330515SLaurent Pinchart 
457df330515SLaurent Pinchart 	ret = xvip_dma_init(xdev, dma, type, index);
458df330515SLaurent Pinchart 	if (ret < 0) {
45968d9c47bSRob Herring 		dev_err(xdev->dev, "%pOF initialization failed\n", node);
460df330515SLaurent Pinchart 		return ret;
461df330515SLaurent Pinchart 	}
462df330515SLaurent Pinchart 
463df330515SLaurent Pinchart 	list_add_tail(&dma->list, &xdev->dmas);
464df330515SLaurent Pinchart 
465df330515SLaurent Pinchart 	xdev->v4l2_caps |= type == V4L2_BUF_TYPE_VIDEO_CAPTURE
466df330515SLaurent Pinchart 			 ? V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_VIDEO_OUTPUT;
467df330515SLaurent Pinchart 
468df330515SLaurent Pinchart 	return 0;
469df330515SLaurent Pinchart }
470df330515SLaurent Pinchart 
471df330515SLaurent Pinchart static int xvip_graph_dma_init(struct xvip_composite_device *xdev)
472df330515SLaurent Pinchart {
473df330515SLaurent Pinchart 	struct device_node *ports;
474df330515SLaurent Pinchart 	struct device_node *port;
475df330515SLaurent Pinchart 	int ret;
476df330515SLaurent Pinchart 
477df330515SLaurent Pinchart 	ports = of_get_child_by_name(xdev->dev->of_node, "ports");
478df330515SLaurent Pinchart 	if (ports == NULL) {
479df330515SLaurent Pinchart 		dev_err(xdev->dev, "ports node not present\n");
480df330515SLaurent Pinchart 		return -EINVAL;
481df330515SLaurent Pinchart 	}
482df330515SLaurent Pinchart 
483df330515SLaurent Pinchart 	for_each_child_of_node(ports, port) {
484df330515SLaurent Pinchart 		ret = xvip_graph_dma_init_one(xdev, port);
485cea590d4SJulia Lawall 		if (ret < 0) {
486cea590d4SJulia Lawall 			of_node_put(port);
487df330515SLaurent Pinchart 			return ret;
488df330515SLaurent Pinchart 		}
489cea590d4SJulia Lawall 	}
490df330515SLaurent Pinchart 
491df330515SLaurent Pinchart 	return 0;
492df330515SLaurent Pinchart }
493df330515SLaurent Pinchart 
494df330515SLaurent Pinchart static void xvip_graph_cleanup(struct xvip_composite_device *xdev)
495df330515SLaurent Pinchart {
496df330515SLaurent Pinchart 	struct xvip_dma *dmap;
497df330515SLaurent Pinchart 	struct xvip_dma *dma;
498df330515SLaurent Pinchart 
499df330515SLaurent Pinchart 	v4l2_async_notifier_unregister(&xdev->notifier);
500d079f94cSSteve Longerbeam 	v4l2_async_notifier_cleanup(&xdev->notifier);
501df330515SLaurent Pinchart 
502df330515SLaurent Pinchart 	list_for_each_entry_safe(dma, dmap, &xdev->dmas, list) {
503df330515SLaurent Pinchart 		xvip_dma_cleanup(dma);
504df330515SLaurent Pinchart 		list_del(&dma->list);
505df330515SLaurent Pinchart 	}
506df330515SLaurent Pinchart }
507df330515SLaurent Pinchart 
508df330515SLaurent Pinchart static int xvip_graph_init(struct xvip_composite_device *xdev)
509df330515SLaurent Pinchart {
510df330515SLaurent Pinchart 	int ret;
511df330515SLaurent Pinchart 
512df330515SLaurent Pinchart 	/* Init the DMA channels. */
513df330515SLaurent Pinchart 	ret = xvip_graph_dma_init(xdev);
514df330515SLaurent Pinchart 	if (ret < 0) {
515df330515SLaurent Pinchart 		dev_err(xdev->dev, "DMA initialization failed\n");
516df330515SLaurent Pinchart 		goto done;
517df330515SLaurent Pinchart 	}
518df330515SLaurent Pinchart 
519df330515SLaurent Pinchart 	/* Parse the graph to extract a list of subdevice DT nodes. */
520df330515SLaurent Pinchart 	ret = xvip_graph_parse(xdev);
521df330515SLaurent Pinchart 	if (ret < 0) {
522df330515SLaurent Pinchart 		dev_err(xdev->dev, "graph parsing failed\n");
523df330515SLaurent Pinchart 		goto done;
524df330515SLaurent Pinchart 	}
525df330515SLaurent Pinchart 
526d079f94cSSteve Longerbeam 	if (list_empty(&xdev->notifier.asd_list)) {
527df330515SLaurent Pinchart 		dev_err(xdev->dev, "no subdev found in graph\n");
528df330515SLaurent Pinchart 		goto done;
529df330515SLaurent Pinchart 	}
530df330515SLaurent Pinchart 
531df330515SLaurent Pinchart 	/* Register the subdevices notifier. */
532b6ee3f0dSLaurent Pinchart 	xdev->notifier.ops = &xvip_graph_notify_ops;
533df330515SLaurent Pinchart 
534df330515SLaurent Pinchart 	ret = v4l2_async_notifier_register(&xdev->v4l2_dev, &xdev->notifier);
535df330515SLaurent Pinchart 	if (ret < 0) {
536df330515SLaurent Pinchart 		dev_err(xdev->dev, "notifier registration failed\n");
537df330515SLaurent Pinchart 		goto done;
538df330515SLaurent Pinchart 	}
539df330515SLaurent Pinchart 
540df330515SLaurent Pinchart 	ret = 0;
541df330515SLaurent Pinchart 
542df330515SLaurent Pinchart done:
543df330515SLaurent Pinchart 	if (ret < 0)
544df330515SLaurent Pinchart 		xvip_graph_cleanup(xdev);
545df330515SLaurent Pinchart 
546df330515SLaurent Pinchart 	return ret;
547df330515SLaurent Pinchart }
548df330515SLaurent Pinchart 
549df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
550df330515SLaurent Pinchart  * Media Controller and V4L2
551df330515SLaurent Pinchart  */
552df330515SLaurent Pinchart 
553df330515SLaurent Pinchart static void xvip_composite_v4l2_cleanup(struct xvip_composite_device *xdev)
554df330515SLaurent Pinchart {
555df330515SLaurent Pinchart 	v4l2_device_unregister(&xdev->v4l2_dev);
556df330515SLaurent Pinchart 	media_device_unregister(&xdev->media_dev);
5579832e155SJavier Martinez Canillas 	media_device_cleanup(&xdev->media_dev);
558df330515SLaurent Pinchart }
559df330515SLaurent Pinchart 
560df330515SLaurent Pinchart static int xvip_composite_v4l2_init(struct xvip_composite_device *xdev)
561df330515SLaurent Pinchart {
562df330515SLaurent Pinchart 	int ret;
563df330515SLaurent Pinchart 
564df330515SLaurent Pinchart 	xdev->media_dev.dev = xdev->dev;
565c0decac1SMauro Carvalho Chehab 	strscpy(xdev->media_dev.model, "Xilinx Video Composite Device",
566df330515SLaurent Pinchart 		sizeof(xdev->media_dev.model));
567df330515SLaurent Pinchart 	xdev->media_dev.hw_revision = 0;
568df330515SLaurent Pinchart 
5699832e155SJavier Martinez Canillas 	media_device_init(&xdev->media_dev);
570df330515SLaurent Pinchart 
571df330515SLaurent Pinchart 	xdev->v4l2_dev.mdev = &xdev->media_dev;
572df330515SLaurent Pinchart 	ret = v4l2_device_register(xdev->dev, &xdev->v4l2_dev);
573df330515SLaurent Pinchart 	if (ret < 0) {
574df330515SLaurent Pinchart 		dev_err(xdev->dev, "V4L2 device registration failed (%d)\n",
575df330515SLaurent Pinchart 			ret);
5769832e155SJavier Martinez Canillas 		media_device_cleanup(&xdev->media_dev);
577df330515SLaurent Pinchart 		return ret;
578df330515SLaurent Pinchart 	}
579df330515SLaurent Pinchart 
580df330515SLaurent Pinchart 	return 0;
581df330515SLaurent Pinchart }
582df330515SLaurent Pinchart 
583df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
584df330515SLaurent Pinchart  * Platform Device Driver
585df330515SLaurent Pinchart  */
586df330515SLaurent Pinchart 
587df330515SLaurent Pinchart static int xvip_composite_probe(struct platform_device *pdev)
588df330515SLaurent Pinchart {
589df330515SLaurent Pinchart 	struct xvip_composite_device *xdev;
590df330515SLaurent Pinchart 	int ret;
591df330515SLaurent Pinchart 
592df330515SLaurent Pinchart 	xdev = devm_kzalloc(&pdev->dev, sizeof(*xdev), GFP_KERNEL);
593df330515SLaurent Pinchart 	if (!xdev)
594df330515SLaurent Pinchart 		return -ENOMEM;
595df330515SLaurent Pinchart 
596df330515SLaurent Pinchart 	xdev->dev = &pdev->dev;
597df330515SLaurent Pinchart 	INIT_LIST_HEAD(&xdev->dmas);
598d079f94cSSteve Longerbeam 	v4l2_async_notifier_init(&xdev->notifier);
599df330515SLaurent Pinchart 
600df330515SLaurent Pinchart 	ret = xvip_composite_v4l2_init(xdev);
601df330515SLaurent Pinchart 	if (ret < 0)
602df330515SLaurent Pinchart 		return ret;
603df330515SLaurent Pinchart 
604df330515SLaurent Pinchart 	ret = xvip_graph_init(xdev);
605df330515SLaurent Pinchart 	if (ret < 0)
606df330515SLaurent Pinchart 		goto error;
607df330515SLaurent Pinchart 
608df330515SLaurent Pinchart 	platform_set_drvdata(pdev, xdev);
609df330515SLaurent Pinchart 
610df330515SLaurent Pinchart 	dev_info(xdev->dev, "device registered\n");
611df330515SLaurent Pinchart 
612df330515SLaurent Pinchart 	return 0;
613df330515SLaurent Pinchart 
614df330515SLaurent Pinchart error:
615df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
616df330515SLaurent Pinchart 	return ret;
617df330515SLaurent Pinchart }
618df330515SLaurent Pinchart 
619df330515SLaurent Pinchart static int xvip_composite_remove(struct platform_device *pdev)
620df330515SLaurent Pinchart {
621df330515SLaurent Pinchart 	struct xvip_composite_device *xdev = platform_get_drvdata(pdev);
622df330515SLaurent Pinchart 
623df330515SLaurent Pinchart 	xvip_graph_cleanup(xdev);
624df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
625df330515SLaurent Pinchart 
626df330515SLaurent Pinchart 	return 0;
627df330515SLaurent Pinchart }
628df330515SLaurent Pinchart 
629df330515SLaurent Pinchart static const struct of_device_id xvip_composite_of_id_table[] = {
630df330515SLaurent Pinchart 	{ .compatible = "xlnx,video" },
631df330515SLaurent Pinchart 	{ }
632df330515SLaurent Pinchart };
633df330515SLaurent Pinchart MODULE_DEVICE_TABLE(of, xvip_composite_of_id_table);
634df330515SLaurent Pinchart 
635df330515SLaurent Pinchart static struct platform_driver xvip_composite_driver = {
636df330515SLaurent Pinchart 	.driver = {
637df330515SLaurent Pinchart 		.name = "xilinx-video",
638df330515SLaurent Pinchart 		.of_match_table = xvip_composite_of_id_table,
639df330515SLaurent Pinchart 	},
640df330515SLaurent Pinchart 	.probe = xvip_composite_probe,
641df330515SLaurent Pinchart 	.remove = xvip_composite_remove,
642df330515SLaurent Pinchart };
643df330515SLaurent Pinchart 
644df330515SLaurent Pinchart module_platform_driver(xvip_composite_driver);
645df330515SLaurent Pinchart 
646df330515SLaurent Pinchart MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
647df330515SLaurent Pinchart MODULE_DESCRIPTION("Xilinx Video IP Composite Driver");
648df330515SLaurent Pinchart MODULE_LICENSE("GPL v2");
649