xref: /linux/drivers/media/platform/xilinx/xilinx-vipp.c (revision ef94711a04027066c4ae77d3477a5a4e0ba0784f)
1df330515SLaurent Pinchart /*
2df330515SLaurent Pinchart  * Xilinx Video IP Composite Device
3df330515SLaurent Pinchart  *
4df330515SLaurent Pinchart  * Copyright (C) 2013-2015 Ideas on Board
5df330515SLaurent Pinchart  * Copyright (C) 2013-2015 Xilinx, Inc.
6df330515SLaurent Pinchart  *
7df330515SLaurent Pinchart  * Contacts: Hyun Kwon <hyun.kwon@xilinx.com>
8df330515SLaurent Pinchart  *           Laurent Pinchart <laurent.pinchart@ideasonboard.com>
9df330515SLaurent Pinchart  *
10df330515SLaurent Pinchart  * This program is free software; you can redistribute it and/or modify
11df330515SLaurent Pinchart  * it under the terms of the GNU General Public License version 2 as
12df330515SLaurent Pinchart  * published by the Free Software Foundation.
13df330515SLaurent Pinchart  */
14df330515SLaurent Pinchart 
15df330515SLaurent Pinchart #include <linux/list.h>
16df330515SLaurent Pinchart #include <linux/module.h>
17df330515SLaurent Pinchart #include <linux/of.h>
18df330515SLaurent Pinchart #include <linux/of_graph.h>
19df330515SLaurent Pinchart #include <linux/platform_device.h>
20df330515SLaurent Pinchart #include <linux/slab.h>
21df330515SLaurent Pinchart 
22df330515SLaurent Pinchart #include <media/v4l2-async.h>
23df330515SLaurent Pinchart #include <media/v4l2-common.h>
24df330515SLaurent Pinchart #include <media/v4l2-device.h>
25859969b3SSakari Ailus #include <media/v4l2-fwnode.h>
26df330515SLaurent Pinchart 
27df330515SLaurent Pinchart #include "xilinx-dma.h"
28df330515SLaurent Pinchart #include "xilinx-vipp.h"
29df330515SLaurent Pinchart 
30df330515SLaurent Pinchart #define XVIPP_DMA_S2MM				0
31df330515SLaurent Pinchart #define XVIPP_DMA_MM2S				1
32df330515SLaurent Pinchart 
33df330515SLaurent Pinchart /**
34df330515SLaurent Pinchart  * struct xvip_graph_entity - Entity in the video graph
35df330515SLaurent Pinchart  * @list: list entry in a graph entities list
36df330515SLaurent Pinchart  * @node: the entity's DT node
37df330515SLaurent Pinchart  * @entity: media entity, from the corresponding V4L2 subdev
38df330515SLaurent Pinchart  * @asd: subdev asynchronous registration information
39df330515SLaurent Pinchart  * @subdev: V4L2 subdev
40df330515SLaurent Pinchart  */
41df330515SLaurent Pinchart struct xvip_graph_entity {
42df330515SLaurent Pinchart 	struct list_head list;
43df330515SLaurent Pinchart 	struct device_node *node;
44df330515SLaurent Pinchart 	struct media_entity *entity;
45df330515SLaurent Pinchart 
46df330515SLaurent Pinchart 	struct v4l2_async_subdev asd;
47df330515SLaurent Pinchart 	struct v4l2_subdev *subdev;
48df330515SLaurent Pinchart };
49df330515SLaurent Pinchart 
50df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
51df330515SLaurent Pinchart  * Graph Management
52df330515SLaurent Pinchart  */
53df330515SLaurent Pinchart 
54df330515SLaurent Pinchart static struct xvip_graph_entity *
55df330515SLaurent Pinchart xvip_graph_find_entity(struct xvip_composite_device *xdev,
56df330515SLaurent Pinchart 		       const struct device_node *node)
57df330515SLaurent Pinchart {
58df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
59df330515SLaurent Pinchart 
60df330515SLaurent Pinchart 	list_for_each_entry(entity, &xdev->entities, list) {
61df330515SLaurent Pinchart 		if (entity->node == node)
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;
78df330515SLaurent Pinchart 	struct device_node *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. */
85*ef94711aSAkinobu Mita 		ep = of_graph_get_next_endpoint(entity->node, ep);
86*ef94711aSAkinobu Mita 		if (ep == NULL)
87df330515SLaurent Pinchart 			break;
88df330515SLaurent Pinchart 
8968d9c47bSRob Herring 		dev_dbg(xdev->dev, "processing endpoint %pOF\n", ep);
90df330515SLaurent Pinchart 
91859969b3SSakari Ailus 		ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
92df330515SLaurent Pinchart 		if (ret < 0) {
9368d9c47bSRob Herring 			dev_err(xdev->dev, "failed to parse link for %pOF\n",
9468d9c47bSRob Herring 				ep);
95df330515SLaurent Pinchart 			continue;
96df330515SLaurent Pinchart 		}
97df330515SLaurent Pinchart 
98df330515SLaurent Pinchart 		/* Skip sink ports, they will be processed from the other end of
99df330515SLaurent Pinchart 		 * the link.
100df330515SLaurent Pinchart 		 */
101df330515SLaurent Pinchart 		if (link.local_port >= local->num_pads) {
10268d9c47bSRob Herring 			dev_err(xdev->dev, "invalid port number %u for %pOF\n",
103859969b3SSakari Ailus 				link.local_port,
10468d9c47bSRob Herring 				to_of_node(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) {
11368d9c47bSRob Herring 			dev_dbg(xdev->dev, "skipping sink port %pOF:%u\n",
11468d9c47bSRob Herring 				to_of_node(link.local_node),
115859969b3SSakari Ailus 				link.local_port);
116859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
117df330515SLaurent Pinchart 			continue;
118df330515SLaurent Pinchart 		}
119df330515SLaurent Pinchart 
120df330515SLaurent Pinchart 		/* Skip DMA engines, they will be processed separately. */
121859969b3SSakari Ailus 		if (link.remote_node == of_fwnode_handle(xdev->dev->of_node)) {
12268d9c47bSRob Herring 			dev_dbg(xdev->dev, "skipping DMA port %pOF:%u\n",
12368d9c47bSRob Herring 				to_of_node(link.local_node),
124859969b3SSakari Ailus 				link.local_port);
125859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
126df330515SLaurent Pinchart 			continue;
127df330515SLaurent Pinchart 		}
128df330515SLaurent Pinchart 
129df330515SLaurent Pinchart 		/* Find the remote entity. */
130859969b3SSakari Ailus 		ent = xvip_graph_find_entity(xdev,
131859969b3SSakari Ailus 					     to_of_node(link.remote_node));
132df330515SLaurent Pinchart 		if (ent == NULL) {
13368d9c47bSRob Herring 			dev_err(xdev->dev, "no entity found for %pOF\n",
13468d9c47bSRob Herring 				to_of_node(link.remote_node));
135859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
136df330515SLaurent Pinchart 			ret = -ENODEV;
137df330515SLaurent Pinchart 			break;
138df330515SLaurent Pinchart 		}
139df330515SLaurent Pinchart 
140df330515SLaurent Pinchart 		remote = ent->entity;
141df330515SLaurent Pinchart 
142df330515SLaurent Pinchart 		if (link.remote_port >= remote->num_pads) {
14368d9c47bSRob Herring 			dev_err(xdev->dev, "invalid port number %u on %pOF\n",
14468d9c47bSRob Herring 				link.remote_port, to_of_node(link.remote_node));
145859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
146df330515SLaurent Pinchart 			ret = -EINVAL;
147df330515SLaurent Pinchart 			break;
148df330515SLaurent Pinchart 		}
149df330515SLaurent Pinchart 
150df330515SLaurent Pinchart 		remote_pad = &remote->pads[link.remote_port];
151df330515SLaurent Pinchart 
152859969b3SSakari Ailus 		v4l2_fwnode_put_link(&link);
153df330515SLaurent Pinchart 
154df330515SLaurent Pinchart 		/* Create the media link. */
155df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n",
156df330515SLaurent Pinchart 			local->name, local_pad->index,
157df330515SLaurent Pinchart 			remote->name, remote_pad->index);
158df330515SLaurent Pinchart 
1598df00a15SMauro Carvalho Chehab 		ret = media_create_pad_link(local, local_pad->index,
160df330515SLaurent Pinchart 					       remote, remote_pad->index,
161df330515SLaurent Pinchart 					       link_flags);
162df330515SLaurent Pinchart 		if (ret < 0) {
163df330515SLaurent Pinchart 			dev_err(xdev->dev,
164df330515SLaurent Pinchart 				"failed to create %s:%u -> %s:%u link\n",
165df330515SLaurent Pinchart 				local->name, local_pad->index,
166df330515SLaurent Pinchart 				remote->name, remote_pad->index);
167df330515SLaurent Pinchart 			break;
168df330515SLaurent Pinchart 		}
169df330515SLaurent Pinchart 	}
170df330515SLaurent Pinchart 
171df330515SLaurent Pinchart 	of_node_put(ep);
172df330515SLaurent Pinchart 	return ret;
173df330515SLaurent Pinchart }
174df330515SLaurent Pinchart 
175df330515SLaurent Pinchart static struct xvip_dma *
176df330515SLaurent Pinchart xvip_graph_find_dma(struct xvip_composite_device *xdev, unsigned int port)
177df330515SLaurent Pinchart {
178df330515SLaurent Pinchart 	struct xvip_dma *dma;
179df330515SLaurent Pinchart 
180df330515SLaurent Pinchart 	list_for_each_entry(dma, &xdev->dmas, list) {
181df330515SLaurent Pinchart 		if (dma->port == port)
182df330515SLaurent Pinchart 			return dma;
183df330515SLaurent Pinchart 	}
184df330515SLaurent Pinchart 
185df330515SLaurent Pinchart 	return NULL;
186df330515SLaurent Pinchart }
187df330515SLaurent Pinchart 
188df330515SLaurent Pinchart static int xvip_graph_build_dma(struct xvip_composite_device *xdev)
189df330515SLaurent Pinchart {
190df330515SLaurent Pinchart 	u32 link_flags = MEDIA_LNK_FL_ENABLED;
191df330515SLaurent Pinchart 	struct device_node *node = xdev->dev->of_node;
192df330515SLaurent Pinchart 	struct media_entity *source;
193df330515SLaurent Pinchart 	struct media_entity *sink;
194df330515SLaurent Pinchart 	struct media_pad *source_pad;
195df330515SLaurent Pinchart 	struct media_pad *sink_pad;
196df330515SLaurent Pinchart 	struct xvip_graph_entity *ent;
197859969b3SSakari Ailus 	struct v4l2_fwnode_link link;
198df330515SLaurent Pinchart 	struct device_node *ep = NULL;
199df330515SLaurent Pinchart 	struct xvip_dma *dma;
200df330515SLaurent Pinchart 	int ret = 0;
201df330515SLaurent Pinchart 
202df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "creating links for DMA engines\n");
203df330515SLaurent Pinchart 
204df330515SLaurent Pinchart 	while (1) {
205df330515SLaurent Pinchart 		/* Get the next endpoint and parse its link. */
206*ef94711aSAkinobu Mita 		ep = of_graph_get_next_endpoint(node, ep);
207*ef94711aSAkinobu Mita 		if (ep == NULL)
208df330515SLaurent Pinchart 			break;
209df330515SLaurent Pinchart 
21068d9c47bSRob Herring 		dev_dbg(xdev->dev, "processing endpoint %pOF\n", ep);
211df330515SLaurent Pinchart 
212859969b3SSakari Ailus 		ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
213df330515SLaurent Pinchart 		if (ret < 0) {
21468d9c47bSRob Herring 			dev_err(xdev->dev, "failed to parse link for %pOF\n",
21568d9c47bSRob Herring 				ep);
216df330515SLaurent Pinchart 			continue;
217df330515SLaurent Pinchart 		}
218df330515SLaurent Pinchart 
219df330515SLaurent Pinchart 		/* Find the DMA engine. */
220df330515SLaurent Pinchart 		dma = xvip_graph_find_dma(xdev, link.local_port);
221df330515SLaurent Pinchart 		if (dma == NULL) {
222df330515SLaurent Pinchart 			dev_err(xdev->dev, "no DMA engine found for port %u\n",
223df330515SLaurent Pinchart 				link.local_port);
224859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
225df330515SLaurent Pinchart 			ret = -EINVAL;
226df330515SLaurent Pinchart 			break;
227df330515SLaurent Pinchart 		}
228df330515SLaurent Pinchart 
229df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating link for DMA engine %s\n",
230df330515SLaurent Pinchart 			dma->video.name);
231df330515SLaurent Pinchart 
232df330515SLaurent Pinchart 		/* Find the remote entity. */
233859969b3SSakari Ailus 		ent = xvip_graph_find_entity(xdev,
234859969b3SSakari Ailus 					     to_of_node(link.remote_node));
235df330515SLaurent Pinchart 		if (ent == NULL) {
23668d9c47bSRob Herring 			dev_err(xdev->dev, "no entity found for %pOF\n",
23768d9c47bSRob Herring 				to_of_node(link.remote_node));
238859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
239df330515SLaurent Pinchart 			ret = -ENODEV;
240df330515SLaurent Pinchart 			break;
241df330515SLaurent Pinchart 		}
242df330515SLaurent Pinchart 
243df330515SLaurent Pinchart 		if (link.remote_port >= ent->entity->num_pads) {
24468d9c47bSRob Herring 			dev_err(xdev->dev, "invalid port number %u on %pOF\n",
245859969b3SSakari Ailus 				link.remote_port,
24668d9c47bSRob Herring 				to_of_node(link.remote_node));
247859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
248df330515SLaurent Pinchart 			ret = -EINVAL;
249df330515SLaurent Pinchart 			break;
250df330515SLaurent Pinchart 		}
251df330515SLaurent Pinchart 
252df330515SLaurent Pinchart 		if (dma->pad.flags & MEDIA_PAD_FL_SOURCE) {
253df330515SLaurent Pinchart 			source = &dma->video.entity;
254df330515SLaurent Pinchart 			source_pad = &dma->pad;
255df330515SLaurent Pinchart 			sink = ent->entity;
256df330515SLaurent Pinchart 			sink_pad = &sink->pads[link.remote_port];
257df330515SLaurent Pinchart 		} else {
258df330515SLaurent Pinchart 			source = ent->entity;
259df330515SLaurent Pinchart 			source_pad = &source->pads[link.remote_port];
260df330515SLaurent Pinchart 			sink = &dma->video.entity;
261df330515SLaurent Pinchart 			sink_pad = &dma->pad;
262df330515SLaurent Pinchart 		}
263df330515SLaurent Pinchart 
264859969b3SSakari Ailus 		v4l2_fwnode_put_link(&link);
265df330515SLaurent Pinchart 
266df330515SLaurent Pinchart 		/* Create the media link. */
267df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n",
268df330515SLaurent Pinchart 			source->name, source_pad->index,
269df330515SLaurent Pinchart 			sink->name, sink_pad->index);
270df330515SLaurent Pinchart 
2718df00a15SMauro Carvalho Chehab 		ret = media_create_pad_link(source, source_pad->index,
272df330515SLaurent Pinchart 					       sink, sink_pad->index,
273df330515SLaurent Pinchart 					       link_flags);
274df330515SLaurent Pinchart 		if (ret < 0) {
275df330515SLaurent Pinchart 			dev_err(xdev->dev,
276df330515SLaurent Pinchart 				"failed to create %s:%u -> %s:%u link\n",
277df330515SLaurent Pinchart 				source->name, source_pad->index,
278df330515SLaurent Pinchart 				sink->name, sink_pad->index);
279df330515SLaurent Pinchart 			break;
280df330515SLaurent Pinchart 		}
281df330515SLaurent Pinchart 	}
282df330515SLaurent Pinchart 
283df330515SLaurent Pinchart 	of_node_put(ep);
284df330515SLaurent Pinchart 	return ret;
285df330515SLaurent Pinchart }
286df330515SLaurent Pinchart 
287df330515SLaurent Pinchart static int xvip_graph_notify_complete(struct v4l2_async_notifier *notifier)
288df330515SLaurent Pinchart {
289df330515SLaurent Pinchart 	struct xvip_composite_device *xdev =
290df330515SLaurent Pinchart 		container_of(notifier, struct xvip_composite_device, notifier);
291df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
292df330515SLaurent Pinchart 	int ret;
293df330515SLaurent Pinchart 
294df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "notify complete, all subdevs registered\n");
295df330515SLaurent Pinchart 
296df330515SLaurent Pinchart 	/* Create links for every entity. */
297df330515SLaurent Pinchart 	list_for_each_entry(entity, &xdev->entities, list) {
298df330515SLaurent Pinchart 		ret = xvip_graph_build_one(xdev, entity);
299df330515SLaurent Pinchart 		if (ret < 0)
300df330515SLaurent Pinchart 			return ret;
301df330515SLaurent Pinchart 	}
302df330515SLaurent Pinchart 
303df330515SLaurent Pinchart 	/* Create links for DMA channels. */
304df330515SLaurent Pinchart 	ret = xvip_graph_build_dma(xdev);
305df330515SLaurent Pinchart 	if (ret < 0)
306df330515SLaurent Pinchart 		return ret;
307df330515SLaurent Pinchart 
308df330515SLaurent Pinchart 	ret = v4l2_device_register_subdev_nodes(&xdev->v4l2_dev);
309df330515SLaurent Pinchart 	if (ret < 0)
310df330515SLaurent Pinchart 		dev_err(xdev->dev, "failed to register subdev nodes\n");
311df330515SLaurent Pinchart 
3129832e155SJavier Martinez Canillas 	return media_device_register(&xdev->media_dev);
313df330515SLaurent Pinchart }
314df330515SLaurent Pinchart 
315df330515SLaurent Pinchart static int xvip_graph_notify_bound(struct v4l2_async_notifier *notifier,
316df330515SLaurent Pinchart 				   struct v4l2_subdev *subdev,
317df330515SLaurent Pinchart 				   struct v4l2_async_subdev *asd)
318df330515SLaurent Pinchart {
319df330515SLaurent Pinchart 	struct xvip_composite_device *xdev =
320df330515SLaurent Pinchart 		container_of(notifier, struct xvip_composite_device, notifier);
321df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
322df330515SLaurent Pinchart 
323df330515SLaurent Pinchart 	/* Locate the entity corresponding to the bound subdev and store the
324df330515SLaurent Pinchart 	 * subdev pointer.
325df330515SLaurent Pinchart 	 */
326df330515SLaurent Pinchart 	list_for_each_entry(entity, &xdev->entities, list) {
327df330515SLaurent Pinchart 		if (entity->node != subdev->dev->of_node)
328df330515SLaurent Pinchart 			continue;
329df330515SLaurent Pinchart 
330df330515SLaurent Pinchart 		if (entity->subdev) {
33168d9c47bSRob Herring 			dev_err(xdev->dev, "duplicate subdev for node %pOF\n",
33268d9c47bSRob Herring 				entity->node);
333df330515SLaurent Pinchart 			return -EINVAL;
334df330515SLaurent Pinchart 		}
335df330515SLaurent Pinchart 
336df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "subdev %s bound\n", subdev->name);
337df330515SLaurent Pinchart 		entity->entity = &subdev->entity;
338df330515SLaurent Pinchart 		entity->subdev = subdev;
339df330515SLaurent Pinchart 		return 0;
340df330515SLaurent Pinchart 	}
341df330515SLaurent Pinchart 
342df330515SLaurent Pinchart 	dev_err(xdev->dev, "no entity for subdev %s\n", subdev->name);
343df330515SLaurent Pinchart 	return -EINVAL;
344df330515SLaurent Pinchart }
345df330515SLaurent Pinchart 
346b6ee3f0dSLaurent Pinchart static const struct v4l2_async_notifier_operations xvip_graph_notify_ops = {
347b6ee3f0dSLaurent Pinchart 	.bound = xvip_graph_notify_bound,
348b6ee3f0dSLaurent Pinchart 	.complete = xvip_graph_notify_complete,
349b6ee3f0dSLaurent Pinchart };
350b6ee3f0dSLaurent Pinchart 
351df330515SLaurent Pinchart static int xvip_graph_parse_one(struct xvip_composite_device *xdev,
352df330515SLaurent Pinchart 				struct device_node *node)
353df330515SLaurent Pinchart {
354df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
355df330515SLaurent Pinchart 	struct device_node *remote;
356df330515SLaurent Pinchart 	struct device_node *ep = NULL;
357df330515SLaurent Pinchart 	int ret = 0;
358df330515SLaurent Pinchart 
35968d9c47bSRob Herring 	dev_dbg(xdev->dev, "parsing node %pOF\n", node);
360df330515SLaurent Pinchart 
361df330515SLaurent Pinchart 	while (1) {
362c64ee347SFranck Jullien 		ep = of_graph_get_next_endpoint(node, ep);
363c64ee347SFranck Jullien 		if (ep == NULL)
364df330515SLaurent Pinchart 			break;
365df330515SLaurent Pinchart 
36668d9c47bSRob Herring 		dev_dbg(xdev->dev, "handling endpoint %pOF\n", ep);
367df330515SLaurent Pinchart 
368df330515SLaurent Pinchart 		remote = of_graph_get_remote_port_parent(ep);
369df330515SLaurent Pinchart 		if (remote == NULL) {
370df330515SLaurent Pinchart 			ret = -EINVAL;
371df330515SLaurent Pinchart 			break;
372df330515SLaurent Pinchart 		}
373df330515SLaurent Pinchart 
374df330515SLaurent Pinchart 		/* Skip entities that we have already processed. */
375df330515SLaurent Pinchart 		if (remote == xdev->dev->of_node ||
376df330515SLaurent Pinchart 		    xvip_graph_find_entity(xdev, remote)) {
377df330515SLaurent Pinchart 			of_node_put(remote);
378df330515SLaurent Pinchart 			continue;
379df330515SLaurent Pinchart 		}
380df330515SLaurent Pinchart 
381df330515SLaurent Pinchart 		entity = devm_kzalloc(xdev->dev, sizeof(*entity), GFP_KERNEL);
382df330515SLaurent Pinchart 		if (entity == NULL) {
383df330515SLaurent Pinchart 			of_node_put(remote);
384df330515SLaurent Pinchart 			ret = -ENOMEM;
385df330515SLaurent Pinchart 			break;
386df330515SLaurent Pinchart 		}
387df330515SLaurent Pinchart 
388df330515SLaurent Pinchart 		entity->node = remote;
389859969b3SSakari Ailus 		entity->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
390859969b3SSakari Ailus 		entity->asd.match.fwnode.fwnode = of_fwnode_handle(remote);
391df330515SLaurent Pinchart 		list_add_tail(&entity->list, &xdev->entities);
392df330515SLaurent Pinchart 		xdev->num_subdevs++;
393df330515SLaurent Pinchart 	}
394df330515SLaurent Pinchart 
395df330515SLaurent Pinchart 	of_node_put(ep);
396df330515SLaurent Pinchart 	return ret;
397df330515SLaurent Pinchart }
398df330515SLaurent Pinchart 
399df330515SLaurent Pinchart static int xvip_graph_parse(struct xvip_composite_device *xdev)
400df330515SLaurent Pinchart {
401df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
402df330515SLaurent Pinchart 	int ret;
403df330515SLaurent Pinchart 
404df330515SLaurent Pinchart 	/*
405df330515SLaurent Pinchart 	 * Walk the links to parse the full graph. Start by parsing the
406df330515SLaurent Pinchart 	 * composite node and then parse entities in turn. The list_for_each
407df330515SLaurent Pinchart 	 * loop will handle entities added at the end of the list while walking
408df330515SLaurent Pinchart 	 * the links.
409df330515SLaurent Pinchart 	 */
410df330515SLaurent Pinchart 	ret = xvip_graph_parse_one(xdev, xdev->dev->of_node);
411df330515SLaurent Pinchart 	if (ret < 0)
412df330515SLaurent Pinchart 		return 0;
413df330515SLaurent Pinchart 
414df330515SLaurent Pinchart 	list_for_each_entry(entity, &xdev->entities, list) {
415df330515SLaurent Pinchart 		ret = xvip_graph_parse_one(xdev, entity->node);
416df330515SLaurent Pinchart 		if (ret < 0)
417df330515SLaurent Pinchart 			break;
418df330515SLaurent Pinchart 	}
419df330515SLaurent Pinchart 
420df330515SLaurent Pinchart 	return ret;
421df330515SLaurent Pinchart }
422df330515SLaurent Pinchart 
423df330515SLaurent Pinchart static int xvip_graph_dma_init_one(struct xvip_composite_device *xdev,
424df330515SLaurent Pinchart 				   struct device_node *node)
425df330515SLaurent Pinchart {
426df330515SLaurent Pinchart 	struct xvip_dma *dma;
427df330515SLaurent Pinchart 	enum v4l2_buf_type type;
428df330515SLaurent Pinchart 	const char *direction;
429df330515SLaurent Pinchart 	unsigned int index;
430df330515SLaurent Pinchart 	int ret;
431df330515SLaurent Pinchart 
432df330515SLaurent Pinchart 	ret = of_property_read_string(node, "direction", &direction);
433df330515SLaurent Pinchart 	if (ret < 0)
434df330515SLaurent Pinchart 		return ret;
435df330515SLaurent Pinchart 
436df330515SLaurent Pinchart 	if (strcmp(direction, "input") == 0)
437df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
438df330515SLaurent Pinchart 	else if (strcmp(direction, "output") == 0)
439df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
440df330515SLaurent Pinchart 	else
441df330515SLaurent Pinchart 		return -EINVAL;
442df330515SLaurent Pinchart 
443df330515SLaurent Pinchart 	of_property_read_u32(node, "reg", &index);
444df330515SLaurent Pinchart 
445df330515SLaurent Pinchart 	dma = devm_kzalloc(xdev->dev, sizeof(*dma), GFP_KERNEL);
446df330515SLaurent Pinchart 	if (dma == NULL)
447df330515SLaurent Pinchart 		return -ENOMEM;
448df330515SLaurent Pinchart 
449df330515SLaurent Pinchart 	ret = xvip_dma_init(xdev, dma, type, index);
450df330515SLaurent Pinchart 	if (ret < 0) {
45168d9c47bSRob Herring 		dev_err(xdev->dev, "%pOF initialization failed\n", node);
452df330515SLaurent Pinchart 		return ret;
453df330515SLaurent Pinchart 	}
454df330515SLaurent Pinchart 
455df330515SLaurent Pinchart 	list_add_tail(&dma->list, &xdev->dmas);
456df330515SLaurent Pinchart 
457df330515SLaurent Pinchart 	xdev->v4l2_caps |= type == V4L2_BUF_TYPE_VIDEO_CAPTURE
458df330515SLaurent Pinchart 			 ? V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_VIDEO_OUTPUT;
459df330515SLaurent Pinchart 
460df330515SLaurent Pinchart 	return 0;
461df330515SLaurent Pinchart }
462df330515SLaurent Pinchart 
463df330515SLaurent Pinchart static int xvip_graph_dma_init(struct xvip_composite_device *xdev)
464df330515SLaurent Pinchart {
465df330515SLaurent Pinchart 	struct device_node *ports;
466df330515SLaurent Pinchart 	struct device_node *port;
467df330515SLaurent Pinchart 	int ret;
468df330515SLaurent Pinchart 
469df330515SLaurent Pinchart 	ports = of_get_child_by_name(xdev->dev->of_node, "ports");
470df330515SLaurent Pinchart 	if (ports == NULL) {
471df330515SLaurent Pinchart 		dev_err(xdev->dev, "ports node not present\n");
472df330515SLaurent Pinchart 		return -EINVAL;
473df330515SLaurent Pinchart 	}
474df330515SLaurent Pinchart 
475df330515SLaurent Pinchart 	for_each_child_of_node(ports, port) {
476df330515SLaurent Pinchart 		ret = xvip_graph_dma_init_one(xdev, port);
477cea590d4SJulia Lawall 		if (ret < 0) {
478cea590d4SJulia Lawall 			of_node_put(port);
479df330515SLaurent Pinchart 			return ret;
480df330515SLaurent Pinchart 		}
481cea590d4SJulia Lawall 	}
482df330515SLaurent Pinchart 
483df330515SLaurent Pinchart 	return 0;
484df330515SLaurent Pinchart }
485df330515SLaurent Pinchart 
486df330515SLaurent Pinchart static void xvip_graph_cleanup(struct xvip_composite_device *xdev)
487df330515SLaurent Pinchart {
488df330515SLaurent Pinchart 	struct xvip_graph_entity *entityp;
489df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
490df330515SLaurent Pinchart 	struct xvip_dma *dmap;
491df330515SLaurent Pinchart 	struct xvip_dma *dma;
492df330515SLaurent Pinchart 
493df330515SLaurent Pinchart 	v4l2_async_notifier_unregister(&xdev->notifier);
494df330515SLaurent Pinchart 
495df330515SLaurent Pinchart 	list_for_each_entry_safe(entity, entityp, &xdev->entities, list) {
496df330515SLaurent Pinchart 		of_node_put(entity->node);
497df330515SLaurent Pinchart 		list_del(&entity->list);
498df330515SLaurent Pinchart 	}
499df330515SLaurent Pinchart 
500df330515SLaurent Pinchart 	list_for_each_entry_safe(dma, dmap, &xdev->dmas, list) {
501df330515SLaurent Pinchart 		xvip_dma_cleanup(dma);
502df330515SLaurent Pinchart 		list_del(&dma->list);
503df330515SLaurent Pinchart 	}
504df330515SLaurent Pinchart }
505df330515SLaurent Pinchart 
506df330515SLaurent Pinchart static int xvip_graph_init(struct xvip_composite_device *xdev)
507df330515SLaurent Pinchart {
508df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
509df330515SLaurent Pinchart 	struct v4l2_async_subdev **subdevs = NULL;
510df330515SLaurent Pinchart 	unsigned int num_subdevs;
511df330515SLaurent Pinchart 	unsigned int i;
512df330515SLaurent Pinchart 	int ret;
513df330515SLaurent Pinchart 
514df330515SLaurent Pinchart 	/* Init the DMA channels. */
515df330515SLaurent Pinchart 	ret = xvip_graph_dma_init(xdev);
516df330515SLaurent Pinchart 	if (ret < 0) {
517df330515SLaurent Pinchart 		dev_err(xdev->dev, "DMA initialization failed\n");
518df330515SLaurent Pinchart 		goto done;
519df330515SLaurent Pinchart 	}
520df330515SLaurent Pinchart 
521df330515SLaurent Pinchart 	/* Parse the graph to extract a list of subdevice DT nodes. */
522df330515SLaurent Pinchart 	ret = xvip_graph_parse(xdev);
523df330515SLaurent Pinchart 	if (ret < 0) {
524df330515SLaurent Pinchart 		dev_err(xdev->dev, "graph parsing failed\n");
525df330515SLaurent Pinchart 		goto done;
526df330515SLaurent Pinchart 	}
527df330515SLaurent Pinchart 
528df330515SLaurent Pinchart 	if (!xdev->num_subdevs) {
529df330515SLaurent Pinchart 		dev_err(xdev->dev, "no subdev found in graph\n");
530df330515SLaurent Pinchart 		goto done;
531df330515SLaurent Pinchart 	}
532df330515SLaurent Pinchart 
533df330515SLaurent Pinchart 	/* Register the subdevices notifier. */
534df330515SLaurent Pinchart 	num_subdevs = xdev->num_subdevs;
535df330515SLaurent Pinchart 	subdevs = devm_kzalloc(xdev->dev, sizeof(*subdevs) * num_subdevs,
536df330515SLaurent Pinchart 			       GFP_KERNEL);
537df330515SLaurent Pinchart 	if (subdevs == NULL) {
538df330515SLaurent Pinchart 		ret = -ENOMEM;
539df330515SLaurent Pinchart 		goto done;
540df330515SLaurent Pinchart 	}
541df330515SLaurent Pinchart 
542df330515SLaurent Pinchart 	i = 0;
543df330515SLaurent Pinchart 	list_for_each_entry(entity, &xdev->entities, list)
544df330515SLaurent Pinchart 		subdevs[i++] = &entity->asd;
545df330515SLaurent Pinchart 
546df330515SLaurent Pinchart 	xdev->notifier.subdevs = subdevs;
547df330515SLaurent Pinchart 	xdev->notifier.num_subdevs = num_subdevs;
548b6ee3f0dSLaurent Pinchart 	xdev->notifier.ops = &xvip_graph_notify_ops;
549df330515SLaurent Pinchart 
550df330515SLaurent Pinchart 	ret = v4l2_async_notifier_register(&xdev->v4l2_dev, &xdev->notifier);
551df330515SLaurent Pinchart 	if (ret < 0) {
552df330515SLaurent Pinchart 		dev_err(xdev->dev, "notifier registration failed\n");
553df330515SLaurent Pinchart 		goto done;
554df330515SLaurent Pinchart 	}
555df330515SLaurent Pinchart 
556df330515SLaurent Pinchart 	ret = 0;
557df330515SLaurent Pinchart 
558df330515SLaurent Pinchart done:
559df330515SLaurent Pinchart 	if (ret < 0)
560df330515SLaurent Pinchart 		xvip_graph_cleanup(xdev);
561df330515SLaurent Pinchart 
562df330515SLaurent Pinchart 	return ret;
563df330515SLaurent Pinchart }
564df330515SLaurent Pinchart 
565df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
566df330515SLaurent Pinchart  * Media Controller and V4L2
567df330515SLaurent Pinchart  */
568df330515SLaurent Pinchart 
569df330515SLaurent Pinchart static void xvip_composite_v4l2_cleanup(struct xvip_composite_device *xdev)
570df330515SLaurent Pinchart {
571df330515SLaurent Pinchart 	v4l2_device_unregister(&xdev->v4l2_dev);
572df330515SLaurent Pinchart 	media_device_unregister(&xdev->media_dev);
5739832e155SJavier Martinez Canillas 	media_device_cleanup(&xdev->media_dev);
574df330515SLaurent Pinchart }
575df330515SLaurent Pinchart 
576df330515SLaurent Pinchart static int xvip_composite_v4l2_init(struct xvip_composite_device *xdev)
577df330515SLaurent Pinchart {
578df330515SLaurent Pinchart 	int ret;
579df330515SLaurent Pinchart 
580df330515SLaurent Pinchart 	xdev->media_dev.dev = xdev->dev;
581df330515SLaurent Pinchart 	strlcpy(xdev->media_dev.model, "Xilinx Video Composite Device",
582df330515SLaurent Pinchart 		sizeof(xdev->media_dev.model));
583df330515SLaurent Pinchart 	xdev->media_dev.hw_revision = 0;
584df330515SLaurent Pinchart 
5859832e155SJavier Martinez Canillas 	media_device_init(&xdev->media_dev);
586df330515SLaurent Pinchart 
587df330515SLaurent Pinchart 	xdev->v4l2_dev.mdev = &xdev->media_dev;
588df330515SLaurent Pinchart 	ret = v4l2_device_register(xdev->dev, &xdev->v4l2_dev);
589df330515SLaurent Pinchart 	if (ret < 0) {
590df330515SLaurent Pinchart 		dev_err(xdev->dev, "V4L2 device registration failed (%d)\n",
591df330515SLaurent Pinchart 			ret);
5929832e155SJavier Martinez Canillas 		media_device_cleanup(&xdev->media_dev);
593df330515SLaurent Pinchart 		return ret;
594df330515SLaurent Pinchart 	}
595df330515SLaurent Pinchart 
596df330515SLaurent Pinchart 	return 0;
597df330515SLaurent Pinchart }
598df330515SLaurent Pinchart 
599df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
600df330515SLaurent Pinchart  * Platform Device Driver
601df330515SLaurent Pinchart  */
602df330515SLaurent Pinchart 
603df330515SLaurent Pinchart static int xvip_composite_probe(struct platform_device *pdev)
604df330515SLaurent Pinchart {
605df330515SLaurent Pinchart 	struct xvip_composite_device *xdev;
606df330515SLaurent Pinchart 	int ret;
607df330515SLaurent Pinchart 
608df330515SLaurent Pinchart 	xdev = devm_kzalloc(&pdev->dev, sizeof(*xdev), GFP_KERNEL);
609df330515SLaurent Pinchart 	if (!xdev)
610df330515SLaurent Pinchart 		return -ENOMEM;
611df330515SLaurent Pinchart 
612df330515SLaurent Pinchart 	xdev->dev = &pdev->dev;
613df330515SLaurent Pinchart 	INIT_LIST_HEAD(&xdev->entities);
614df330515SLaurent Pinchart 	INIT_LIST_HEAD(&xdev->dmas);
615df330515SLaurent Pinchart 
616df330515SLaurent Pinchart 	ret = xvip_composite_v4l2_init(xdev);
617df330515SLaurent Pinchart 	if (ret < 0)
618df330515SLaurent Pinchart 		return ret;
619df330515SLaurent Pinchart 
620df330515SLaurent Pinchart 	ret = xvip_graph_init(xdev);
621df330515SLaurent Pinchart 	if (ret < 0)
622df330515SLaurent Pinchart 		goto error;
623df330515SLaurent Pinchart 
624df330515SLaurent Pinchart 	platform_set_drvdata(pdev, xdev);
625df330515SLaurent Pinchart 
626df330515SLaurent Pinchart 	dev_info(xdev->dev, "device registered\n");
627df330515SLaurent Pinchart 
628df330515SLaurent Pinchart 	return 0;
629df330515SLaurent Pinchart 
630df330515SLaurent Pinchart error:
631df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
632df330515SLaurent Pinchart 	return ret;
633df330515SLaurent Pinchart }
634df330515SLaurent Pinchart 
635df330515SLaurent Pinchart static int xvip_composite_remove(struct platform_device *pdev)
636df330515SLaurent Pinchart {
637df330515SLaurent Pinchart 	struct xvip_composite_device *xdev = platform_get_drvdata(pdev);
638df330515SLaurent Pinchart 
639df330515SLaurent Pinchart 	xvip_graph_cleanup(xdev);
640df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
641df330515SLaurent Pinchart 
642df330515SLaurent Pinchart 	return 0;
643df330515SLaurent Pinchart }
644df330515SLaurent Pinchart 
645df330515SLaurent Pinchart static const struct of_device_id xvip_composite_of_id_table[] = {
646df330515SLaurent Pinchart 	{ .compatible = "xlnx,video" },
647df330515SLaurent Pinchart 	{ }
648df330515SLaurent Pinchart };
649df330515SLaurent Pinchart MODULE_DEVICE_TABLE(of, xvip_composite_of_id_table);
650df330515SLaurent Pinchart 
651df330515SLaurent Pinchart static struct platform_driver xvip_composite_driver = {
652df330515SLaurent Pinchart 	.driver = {
653df330515SLaurent Pinchart 		.name = "xilinx-video",
654df330515SLaurent Pinchart 		.of_match_table = xvip_composite_of_id_table,
655df330515SLaurent Pinchart 	},
656df330515SLaurent Pinchart 	.probe = xvip_composite_probe,
657df330515SLaurent Pinchart 	.remove = xvip_composite_remove,
658df330515SLaurent Pinchart };
659df330515SLaurent Pinchart 
660df330515SLaurent Pinchart module_platform_driver(xvip_composite_driver);
661df330515SLaurent Pinchart 
662df330515SLaurent Pinchart MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
663df330515SLaurent Pinchart MODULE_DESCRIPTION("Xilinx Video IP Composite Driver");
664df330515SLaurent Pinchart MODULE_LICENSE("GPL v2");
665