xref: /linux/drivers/media/platform/xilinx/xilinx-vipp.c (revision 0ea5c948cb64bab5bc7a5516774eb8536f05aa0d)
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;
58*30c1886aSSakari Ailus 	struct list_head *lists[] = {
59*30c1886aSSakari Ailus 		&xdev->notifier.done_list,
60*30c1886aSSakari Ailus 		&xdev->notifier.waiting_list
61*30c1886aSSakari Ailus 	};
62*30c1886aSSakari Ailus 	unsigned int i;
63df330515SLaurent Pinchart 
64*30c1886aSSakari Ailus 	for (i = 0; i < ARRAY_SIZE(lists); i++) {
65*30c1886aSSakari Ailus 		list_for_each_entry(asd, lists[i], asc_entry) {
66d079f94cSSteve Longerbeam 			entity = to_xvip_entity(asd);
67d079f94cSSteve Longerbeam 			if (entity->asd.match.fwnode == fwnode)
68df330515SLaurent Pinchart 				return entity;
69df330515SLaurent Pinchart 		}
70*30c1886aSSakari Ailus 	}
71df330515SLaurent Pinchart 
72df330515SLaurent Pinchart 	return NULL;
73df330515SLaurent Pinchart }
74df330515SLaurent Pinchart 
75df330515SLaurent Pinchart static int xvip_graph_build_one(struct xvip_composite_device *xdev,
76df330515SLaurent Pinchart 				struct xvip_graph_entity *entity)
77df330515SLaurent Pinchart {
78df330515SLaurent Pinchart 	u32 link_flags = MEDIA_LNK_FL_ENABLED;
79df330515SLaurent Pinchart 	struct media_entity *local = entity->entity;
80df330515SLaurent Pinchart 	struct media_entity *remote;
81df330515SLaurent Pinchart 	struct media_pad *local_pad;
82df330515SLaurent Pinchart 	struct media_pad *remote_pad;
83df330515SLaurent Pinchart 	struct xvip_graph_entity *ent;
84859969b3SSakari Ailus 	struct v4l2_fwnode_link link;
85d079f94cSSteve Longerbeam 	struct fwnode_handle *ep = NULL;
86df330515SLaurent Pinchart 	int ret = 0;
87df330515SLaurent Pinchart 
88df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "creating links for entity %s\n", local->name);
89df330515SLaurent Pinchart 
90df330515SLaurent Pinchart 	while (1) {
91df330515SLaurent Pinchart 		/* Get the next endpoint and parse its link. */
92d079f94cSSteve Longerbeam 		ep = fwnode_graph_get_next_endpoint(entity->asd.match.fwnode,
93d079f94cSSteve Longerbeam 						    ep);
94ef94711aSAkinobu Mita 		if (ep == NULL)
95df330515SLaurent Pinchart 			break;
96df330515SLaurent Pinchart 
97d079f94cSSteve Longerbeam 		dev_dbg(xdev->dev, "processing endpoint %p\n", ep);
98df330515SLaurent Pinchart 
99d079f94cSSteve Longerbeam 		ret = v4l2_fwnode_parse_link(ep, &link);
100df330515SLaurent Pinchart 		if (ret < 0) {
101d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "failed to parse link for %p\n",
10268d9c47bSRob Herring 				ep);
103df330515SLaurent Pinchart 			continue;
104df330515SLaurent Pinchart 		}
105df330515SLaurent Pinchart 
106df330515SLaurent Pinchart 		/* Skip sink ports, they will be processed from the other end of
107df330515SLaurent Pinchart 		 * the link.
108df330515SLaurent Pinchart 		 */
109df330515SLaurent Pinchart 		if (link.local_port >= local->num_pads) {
110d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "invalid port number %u for %p\n",
111d079f94cSSteve Longerbeam 				link.local_port, link.local_node);
112859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
113df330515SLaurent Pinchart 			ret = -EINVAL;
114df330515SLaurent Pinchart 			break;
115df330515SLaurent Pinchart 		}
116df330515SLaurent Pinchart 
117df330515SLaurent Pinchart 		local_pad = &local->pads[link.local_port];
118df330515SLaurent Pinchart 
119df330515SLaurent Pinchart 		if (local_pad->flags & MEDIA_PAD_FL_SINK) {
120d079f94cSSteve Longerbeam 			dev_dbg(xdev->dev, "skipping sink port %p:%u\n",
121d079f94cSSteve Longerbeam 				link.local_node, link.local_port);
122859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
123df330515SLaurent Pinchart 			continue;
124df330515SLaurent Pinchart 		}
125df330515SLaurent Pinchart 
126df330515SLaurent Pinchart 		/* Skip DMA engines, they will be processed separately. */
127859969b3SSakari Ailus 		if (link.remote_node == of_fwnode_handle(xdev->dev->of_node)) {
128d079f94cSSteve Longerbeam 			dev_dbg(xdev->dev, "skipping DMA port %p:%u\n",
129d079f94cSSteve Longerbeam 				link.local_node, link.local_port);
130859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
131df330515SLaurent Pinchart 			continue;
132df330515SLaurent Pinchart 		}
133df330515SLaurent Pinchart 
134df330515SLaurent Pinchart 		/* Find the remote entity. */
135d079f94cSSteve Longerbeam 		ent = xvip_graph_find_entity(xdev, link.remote_node);
136df330515SLaurent Pinchart 		if (ent == NULL) {
137d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "no entity found for %p\n",
138d079f94cSSteve Longerbeam 				link.remote_node);
139859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
140df330515SLaurent Pinchart 			ret = -ENODEV;
141df330515SLaurent Pinchart 			break;
142df330515SLaurent Pinchart 		}
143df330515SLaurent Pinchart 
144df330515SLaurent Pinchart 		remote = ent->entity;
145df330515SLaurent Pinchart 
146df330515SLaurent Pinchart 		if (link.remote_port >= remote->num_pads) {
147d079f94cSSteve Longerbeam 			dev_err(xdev->dev, "invalid port number %u on %p\n",
148d079f94cSSteve Longerbeam 				link.remote_port, link.remote_node);
149859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
150df330515SLaurent Pinchart 			ret = -EINVAL;
151df330515SLaurent Pinchart 			break;
152df330515SLaurent Pinchart 		}
153df330515SLaurent Pinchart 
154df330515SLaurent Pinchart 		remote_pad = &remote->pads[link.remote_port];
155df330515SLaurent Pinchart 
156859969b3SSakari Ailus 		v4l2_fwnode_put_link(&link);
157df330515SLaurent Pinchart 
158df330515SLaurent Pinchart 		/* Create the media link. */
159df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n",
160df330515SLaurent Pinchart 			local->name, local_pad->index,
161df330515SLaurent Pinchart 			remote->name, remote_pad->index);
162df330515SLaurent Pinchart 
1638df00a15SMauro Carvalho Chehab 		ret = media_create_pad_link(local, local_pad->index,
164df330515SLaurent Pinchart 					       remote, remote_pad->index,
165df330515SLaurent Pinchart 					       link_flags);
166df330515SLaurent Pinchart 		if (ret < 0) {
167df330515SLaurent Pinchart 			dev_err(xdev->dev,
168df330515SLaurent Pinchart 				"failed to create %s:%u -> %s:%u link\n",
169df330515SLaurent Pinchart 				local->name, local_pad->index,
170df330515SLaurent Pinchart 				remote->name, remote_pad->index);
171df330515SLaurent Pinchart 			break;
172df330515SLaurent Pinchart 		}
173df330515SLaurent Pinchart 	}
174df330515SLaurent Pinchart 
175d079f94cSSteve Longerbeam 	fwnode_handle_put(ep);
176df330515SLaurent Pinchart 	return ret;
177df330515SLaurent Pinchart }
178df330515SLaurent Pinchart 
179df330515SLaurent Pinchart static struct xvip_dma *
180df330515SLaurent Pinchart xvip_graph_find_dma(struct xvip_composite_device *xdev, unsigned int port)
181df330515SLaurent Pinchart {
182df330515SLaurent Pinchart 	struct xvip_dma *dma;
183df330515SLaurent Pinchart 
184df330515SLaurent Pinchart 	list_for_each_entry(dma, &xdev->dmas, list) {
185df330515SLaurent Pinchart 		if (dma->port == port)
186df330515SLaurent Pinchart 			return dma;
187df330515SLaurent Pinchart 	}
188df330515SLaurent Pinchart 
189df330515SLaurent Pinchart 	return NULL;
190df330515SLaurent Pinchart }
191df330515SLaurent Pinchart 
192df330515SLaurent Pinchart static int xvip_graph_build_dma(struct xvip_composite_device *xdev)
193df330515SLaurent Pinchart {
194df330515SLaurent Pinchart 	u32 link_flags = MEDIA_LNK_FL_ENABLED;
195df330515SLaurent Pinchart 	struct device_node *node = xdev->dev->of_node;
196df330515SLaurent Pinchart 	struct media_entity *source;
197df330515SLaurent Pinchart 	struct media_entity *sink;
198df330515SLaurent Pinchart 	struct media_pad *source_pad;
199df330515SLaurent Pinchart 	struct media_pad *sink_pad;
200df330515SLaurent Pinchart 	struct xvip_graph_entity *ent;
201859969b3SSakari Ailus 	struct v4l2_fwnode_link link;
202df330515SLaurent Pinchart 	struct device_node *ep = NULL;
203df330515SLaurent Pinchart 	struct xvip_dma *dma;
204df330515SLaurent Pinchart 	int ret = 0;
205df330515SLaurent Pinchart 
206df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "creating links for DMA engines\n");
207df330515SLaurent Pinchart 
208df330515SLaurent Pinchart 	while (1) {
209df330515SLaurent Pinchart 		/* Get the next endpoint and parse its link. */
210ef94711aSAkinobu Mita 		ep = of_graph_get_next_endpoint(node, ep);
211ef94711aSAkinobu Mita 		if (ep == NULL)
212df330515SLaurent Pinchart 			break;
213df330515SLaurent Pinchart 
21468d9c47bSRob Herring 		dev_dbg(xdev->dev, "processing endpoint %pOF\n", ep);
215df330515SLaurent Pinchart 
216859969b3SSakari Ailus 		ret = v4l2_fwnode_parse_link(of_fwnode_handle(ep), &link);
217df330515SLaurent Pinchart 		if (ret < 0) {
21868d9c47bSRob Herring 			dev_err(xdev->dev, "failed to parse link for %pOF\n",
21968d9c47bSRob Herring 				ep);
220df330515SLaurent Pinchart 			continue;
221df330515SLaurent Pinchart 		}
222df330515SLaurent Pinchart 
223df330515SLaurent Pinchart 		/* Find the DMA engine. */
224df330515SLaurent Pinchart 		dma = xvip_graph_find_dma(xdev, link.local_port);
225df330515SLaurent Pinchart 		if (dma == NULL) {
226df330515SLaurent Pinchart 			dev_err(xdev->dev, "no DMA engine found for port %u\n",
227df330515SLaurent Pinchart 				link.local_port);
228859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
229df330515SLaurent Pinchart 			ret = -EINVAL;
230df330515SLaurent Pinchart 			break;
231df330515SLaurent Pinchart 		}
232df330515SLaurent Pinchart 
233df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating link for DMA engine %s\n",
234df330515SLaurent Pinchart 			dma->video.name);
235df330515SLaurent Pinchart 
236df330515SLaurent Pinchart 		/* Find the remote entity. */
237d079f94cSSteve Longerbeam 		ent = xvip_graph_find_entity(xdev, link.remote_node);
238df330515SLaurent Pinchart 		if (ent == NULL) {
23968d9c47bSRob Herring 			dev_err(xdev->dev, "no entity found for %pOF\n",
24068d9c47bSRob Herring 				to_of_node(link.remote_node));
241859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
242df330515SLaurent Pinchart 			ret = -ENODEV;
243df330515SLaurent Pinchart 			break;
244df330515SLaurent Pinchart 		}
245df330515SLaurent Pinchart 
246df330515SLaurent Pinchart 		if (link.remote_port >= ent->entity->num_pads) {
24768d9c47bSRob Herring 			dev_err(xdev->dev, "invalid port number %u on %pOF\n",
248859969b3SSakari Ailus 				link.remote_port,
24968d9c47bSRob Herring 				to_of_node(link.remote_node));
250859969b3SSakari Ailus 			v4l2_fwnode_put_link(&link);
251df330515SLaurent Pinchart 			ret = -EINVAL;
252df330515SLaurent Pinchart 			break;
253df330515SLaurent Pinchart 		}
254df330515SLaurent Pinchart 
255df330515SLaurent Pinchart 		if (dma->pad.flags & MEDIA_PAD_FL_SOURCE) {
256df330515SLaurent Pinchart 			source = &dma->video.entity;
257df330515SLaurent Pinchart 			source_pad = &dma->pad;
258df330515SLaurent Pinchart 			sink = ent->entity;
259df330515SLaurent Pinchart 			sink_pad = &sink->pads[link.remote_port];
260df330515SLaurent Pinchart 		} else {
261df330515SLaurent Pinchart 			source = ent->entity;
262df330515SLaurent Pinchart 			source_pad = &source->pads[link.remote_port];
263df330515SLaurent Pinchart 			sink = &dma->video.entity;
264df330515SLaurent Pinchart 			sink_pad = &dma->pad;
265df330515SLaurent Pinchart 		}
266df330515SLaurent Pinchart 
267859969b3SSakari Ailus 		v4l2_fwnode_put_link(&link);
268df330515SLaurent Pinchart 
269df330515SLaurent Pinchart 		/* Create the media link. */
270df330515SLaurent Pinchart 		dev_dbg(xdev->dev, "creating %s:%u -> %s:%u link\n",
271df330515SLaurent Pinchart 			source->name, source_pad->index,
272df330515SLaurent Pinchart 			sink->name, sink_pad->index);
273df330515SLaurent Pinchart 
2748df00a15SMauro Carvalho Chehab 		ret = media_create_pad_link(source, source_pad->index,
275df330515SLaurent Pinchart 					       sink, sink_pad->index,
276df330515SLaurent Pinchart 					       link_flags);
277df330515SLaurent Pinchart 		if (ret < 0) {
278df330515SLaurent Pinchart 			dev_err(xdev->dev,
279df330515SLaurent Pinchart 				"failed to create %s:%u -> %s:%u link\n",
280df330515SLaurent Pinchart 				source->name, source_pad->index,
281df330515SLaurent Pinchart 				sink->name, sink_pad->index);
282df330515SLaurent Pinchart 			break;
283df330515SLaurent Pinchart 		}
284df330515SLaurent Pinchart 	}
285df330515SLaurent Pinchart 
286df330515SLaurent Pinchart 	of_node_put(ep);
287df330515SLaurent Pinchart 	return ret;
288df330515SLaurent Pinchart }
289df330515SLaurent Pinchart 
290df330515SLaurent Pinchart static int xvip_graph_notify_complete(struct v4l2_async_notifier *notifier)
291df330515SLaurent Pinchart {
292df330515SLaurent Pinchart 	struct xvip_composite_device *xdev =
293df330515SLaurent Pinchart 		container_of(notifier, struct xvip_composite_device, notifier);
294df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
295adb2dcd5SSakari Ailus 	struct v4l2_async_connection *asd;
296df330515SLaurent Pinchart 	int ret;
297df330515SLaurent Pinchart 
298df330515SLaurent Pinchart 	dev_dbg(xdev->dev, "notify complete, all subdevs registered\n");
299df330515SLaurent Pinchart 
300df330515SLaurent Pinchart 	/* Create links for every entity. */
3019bf19fbfSSakari Ailus 	list_for_each_entry(asd, &xdev->notifier.done_list, asc_entry) {
302d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
303df330515SLaurent Pinchart 		ret = xvip_graph_build_one(xdev, entity);
304df330515SLaurent Pinchart 		if (ret < 0)
305df330515SLaurent Pinchart 			return ret;
306df330515SLaurent Pinchart 	}
307df330515SLaurent Pinchart 
308df330515SLaurent Pinchart 	/* Create links for DMA channels. */
309df330515SLaurent Pinchart 	ret = xvip_graph_build_dma(xdev);
310df330515SLaurent Pinchart 	if (ret < 0)
311df330515SLaurent Pinchart 		return ret;
312df330515SLaurent Pinchart 
313df330515SLaurent Pinchart 	ret = v4l2_device_register_subdev_nodes(&xdev->v4l2_dev);
314df330515SLaurent Pinchart 	if (ret < 0)
315df330515SLaurent Pinchart 		dev_err(xdev->dev, "failed to register subdev nodes\n");
316df330515SLaurent Pinchart 
3179832e155SJavier Martinez Canillas 	return media_device_register(&xdev->media_dev);
318df330515SLaurent Pinchart }
319df330515SLaurent Pinchart 
320df330515SLaurent Pinchart static int xvip_graph_notify_bound(struct v4l2_async_notifier *notifier,
321df330515SLaurent Pinchart 				   struct v4l2_subdev *subdev,
322adb2dcd5SSakari Ailus 				   struct v4l2_async_connection *asc)
323df330515SLaurent Pinchart {
324adb2dcd5SSakari Ailus 	struct xvip_graph_entity *entity = to_xvip_entity(asc);
325df330515SLaurent Pinchart 
326df330515SLaurent Pinchart 	entity->entity = &subdev->entity;
327df330515SLaurent Pinchart 	entity->subdev = subdev;
328df330515SLaurent Pinchart 
32964585805SSakari Ailus 	return 0;
330df330515SLaurent Pinchart }
331df330515SLaurent Pinchart 
332b6ee3f0dSLaurent Pinchart static const struct v4l2_async_notifier_operations xvip_graph_notify_ops = {
333b6ee3f0dSLaurent Pinchart 	.bound = xvip_graph_notify_bound,
334b6ee3f0dSLaurent Pinchart 	.complete = xvip_graph_notify_complete,
335b6ee3f0dSLaurent Pinchart };
336b6ee3f0dSLaurent Pinchart 
337df330515SLaurent Pinchart static int xvip_graph_parse_one(struct xvip_composite_device *xdev,
338d079f94cSSteve Longerbeam 				struct fwnode_handle *fwnode)
339df330515SLaurent Pinchart {
340d079f94cSSteve Longerbeam 	struct fwnode_handle *remote;
341d079f94cSSteve Longerbeam 	struct fwnode_handle *ep = NULL;
342df330515SLaurent Pinchart 	int ret = 0;
343df330515SLaurent Pinchart 
344d079f94cSSteve Longerbeam 	dev_dbg(xdev->dev, "parsing node %p\n", fwnode);
345df330515SLaurent Pinchart 
346df330515SLaurent Pinchart 	while (1) {
347b01edcbdSLaurent Pinchart 		struct xvip_graph_entity *xge;
348d079f94cSSteve Longerbeam 
349d079f94cSSteve Longerbeam 		ep = fwnode_graph_get_next_endpoint(fwnode, ep);
350c64ee347SFranck Jullien 		if (ep == NULL)
351df330515SLaurent Pinchart 			break;
352df330515SLaurent Pinchart 
353d079f94cSSteve Longerbeam 		dev_dbg(xdev->dev, "handling endpoint %p\n", ep);
354df330515SLaurent Pinchart 
355d079f94cSSteve Longerbeam 		remote = fwnode_graph_get_remote_port_parent(ep);
356df330515SLaurent Pinchart 		if (remote == NULL) {
357df330515SLaurent Pinchart 			ret = -EINVAL;
358d079f94cSSteve Longerbeam 			goto err_notifier_cleanup;
359df330515SLaurent Pinchart 		}
360df330515SLaurent Pinchart 
361d079f94cSSteve Longerbeam 		fwnode_handle_put(ep);
362d079f94cSSteve Longerbeam 
363df330515SLaurent Pinchart 		/* Skip entities that we have already processed. */
364d079f94cSSteve Longerbeam 		if (remote == of_fwnode_handle(xdev->dev->of_node) ||
365df330515SLaurent Pinchart 		    xvip_graph_find_entity(xdev, remote)) {
366d079f94cSSteve Longerbeam 			fwnode_handle_put(remote);
367df330515SLaurent Pinchart 			continue;
368df330515SLaurent Pinchart 		}
369df330515SLaurent Pinchart 
3703c8c1539SSakari Ailus 		xge = v4l2_async_nf_add_fwnode(&xdev->notifier, remote,
371b01edcbdSLaurent Pinchart 					       struct xvip_graph_entity);
372016413d9SSakari Ailus 		fwnode_handle_put(remote);
373b01edcbdSLaurent Pinchart 		if (IS_ERR(xge)) {
374b01edcbdSLaurent Pinchart 			ret = PTR_ERR(xge);
375d079f94cSSteve Longerbeam 			goto err_notifier_cleanup;
376d079f94cSSteve Longerbeam 		}
377df330515SLaurent Pinchart 	}
378df330515SLaurent Pinchart 
379d079f94cSSteve Longerbeam 	return 0;
380df330515SLaurent Pinchart 
381d079f94cSSteve Longerbeam err_notifier_cleanup:
3823c8c1539SSakari Ailus 	v4l2_async_nf_cleanup(&xdev->notifier);
383d079f94cSSteve Longerbeam 	fwnode_handle_put(ep);
384df330515SLaurent Pinchart 	return ret;
385df330515SLaurent Pinchart }
386df330515SLaurent Pinchart 
387df330515SLaurent Pinchart static int xvip_graph_parse(struct xvip_composite_device *xdev)
388df330515SLaurent Pinchart {
389df330515SLaurent Pinchart 	struct xvip_graph_entity *entity;
390adb2dcd5SSakari Ailus 	struct v4l2_async_connection *asd;
391df330515SLaurent Pinchart 	int ret;
392df330515SLaurent Pinchart 
393df330515SLaurent Pinchart 	/*
394df330515SLaurent Pinchart 	 * Walk the links to parse the full graph. Start by parsing the
395df330515SLaurent Pinchart 	 * composite node and then parse entities in turn. The list_for_each
396df330515SLaurent Pinchart 	 * loop will handle entities added at the end of the list while walking
397df330515SLaurent Pinchart 	 * the links.
398df330515SLaurent Pinchart 	 */
399d079f94cSSteve Longerbeam 	ret = xvip_graph_parse_one(xdev, of_fwnode_handle(xdev->dev->of_node));
400df330515SLaurent Pinchart 	if (ret < 0)
401df330515SLaurent Pinchart 		return 0;
402df330515SLaurent Pinchart 
4039bf19fbfSSakari Ailus 	list_for_each_entry(asd, &xdev->notifier.waiting_list, asc_entry) {
404d079f94cSSteve Longerbeam 		entity = to_xvip_entity(asd);
405d079f94cSSteve Longerbeam 		ret = xvip_graph_parse_one(xdev, entity->asd.match.fwnode);
406d079f94cSSteve Longerbeam 		if (ret < 0) {
4073c8c1539SSakari Ailus 			v4l2_async_nf_cleanup(&xdev->notifier);
408df330515SLaurent Pinchart 			break;
409df330515SLaurent Pinchart 		}
410d079f94cSSteve Longerbeam 	}
411df330515SLaurent Pinchart 
412df330515SLaurent Pinchart 	return ret;
413df330515SLaurent Pinchart }
414df330515SLaurent Pinchart 
415df330515SLaurent Pinchart static int xvip_graph_dma_init_one(struct xvip_composite_device *xdev,
416df330515SLaurent Pinchart 				   struct device_node *node)
417df330515SLaurent Pinchart {
418df330515SLaurent Pinchart 	struct xvip_dma *dma;
419df330515SLaurent Pinchart 	enum v4l2_buf_type type;
420df330515SLaurent Pinchart 	const char *direction;
421df330515SLaurent Pinchart 	unsigned int index;
422df330515SLaurent Pinchart 	int ret;
423df330515SLaurent Pinchart 
424df330515SLaurent Pinchart 	ret = of_property_read_string(node, "direction", &direction);
425df330515SLaurent Pinchart 	if (ret < 0)
426df330515SLaurent Pinchart 		return ret;
427df330515SLaurent Pinchart 
428df330515SLaurent Pinchart 	if (strcmp(direction, "input") == 0)
429df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
430df330515SLaurent Pinchart 	else if (strcmp(direction, "output") == 0)
431df330515SLaurent Pinchart 		type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
432df330515SLaurent Pinchart 	else
433df330515SLaurent Pinchart 		return -EINVAL;
434df330515SLaurent Pinchart 
435df330515SLaurent Pinchart 	of_property_read_u32(node, "reg", &index);
436df330515SLaurent Pinchart 
437df330515SLaurent Pinchart 	dma = devm_kzalloc(xdev->dev, sizeof(*dma), GFP_KERNEL);
438df330515SLaurent Pinchart 	if (dma == NULL)
439df330515SLaurent Pinchart 		return -ENOMEM;
440df330515SLaurent Pinchart 
441df330515SLaurent Pinchart 	ret = xvip_dma_init(xdev, dma, type, index);
442df330515SLaurent Pinchart 	if (ret < 0) {
44368d9c47bSRob Herring 		dev_err(xdev->dev, "%pOF initialization failed\n", node);
444df330515SLaurent Pinchart 		return ret;
445df330515SLaurent Pinchart 	}
446df330515SLaurent Pinchart 
447df330515SLaurent Pinchart 	list_add_tail(&dma->list, &xdev->dmas);
448df330515SLaurent Pinchart 
449df330515SLaurent Pinchart 	xdev->v4l2_caps |= type == V4L2_BUF_TYPE_VIDEO_CAPTURE
450df330515SLaurent Pinchart 			 ? V4L2_CAP_VIDEO_CAPTURE : V4L2_CAP_VIDEO_OUTPUT;
451df330515SLaurent Pinchart 
452df330515SLaurent Pinchart 	return 0;
453df330515SLaurent Pinchart }
454df330515SLaurent Pinchart 
455df330515SLaurent Pinchart static int xvip_graph_dma_init(struct xvip_composite_device *xdev)
456df330515SLaurent Pinchart {
457df330515SLaurent Pinchart 	struct device_node *ports;
458df330515SLaurent Pinchart 	struct device_node *port;
4591c78f19cSMiaoqian Lin 	int ret = 0;
460df330515SLaurent Pinchart 
461df330515SLaurent Pinchart 	ports = of_get_child_by_name(xdev->dev->of_node, "ports");
462df330515SLaurent Pinchart 	if (ports == NULL) {
463df330515SLaurent Pinchart 		dev_err(xdev->dev, "ports node not present\n");
464df330515SLaurent Pinchart 		return -EINVAL;
465df330515SLaurent Pinchart 	}
466df330515SLaurent Pinchart 
467df330515SLaurent Pinchart 	for_each_child_of_node(ports, port) {
468df330515SLaurent Pinchart 		ret = xvip_graph_dma_init_one(xdev, port);
4691c78f19cSMiaoqian Lin 		if (ret) {
470cea590d4SJulia Lawall 			of_node_put(port);
4711c78f19cSMiaoqian Lin 			break;
472df330515SLaurent Pinchart 		}
473cea590d4SJulia Lawall 	}
474df330515SLaurent Pinchart 
4751c78f19cSMiaoqian Lin 	of_node_put(ports);
4761c78f19cSMiaoqian Lin 	return ret;
477df330515SLaurent Pinchart }
478df330515SLaurent Pinchart 
479df330515SLaurent Pinchart static void xvip_graph_cleanup(struct xvip_composite_device *xdev)
480df330515SLaurent Pinchart {
481df330515SLaurent Pinchart 	struct xvip_dma *dmap;
482df330515SLaurent Pinchart 	struct xvip_dma *dma;
483df330515SLaurent Pinchart 
4843c8c1539SSakari Ailus 	v4l2_async_nf_unregister(&xdev->notifier);
4853c8c1539SSakari Ailus 	v4l2_async_nf_cleanup(&xdev->notifier);
486df330515SLaurent Pinchart 
487df330515SLaurent Pinchart 	list_for_each_entry_safe(dma, dmap, &xdev->dmas, list) {
488df330515SLaurent Pinchart 		xvip_dma_cleanup(dma);
489df330515SLaurent Pinchart 		list_del(&dma->list);
490df330515SLaurent Pinchart 	}
491df330515SLaurent Pinchart }
492df330515SLaurent Pinchart 
493df330515SLaurent Pinchart static int xvip_graph_init(struct xvip_composite_device *xdev)
494df330515SLaurent Pinchart {
495df330515SLaurent Pinchart 	int ret;
496df330515SLaurent Pinchart 
497df330515SLaurent Pinchart 	/* Init the DMA channels. */
498df330515SLaurent Pinchart 	ret = xvip_graph_dma_init(xdev);
499df330515SLaurent Pinchart 	if (ret < 0) {
500df330515SLaurent Pinchart 		dev_err(xdev->dev, "DMA initialization failed\n");
501df330515SLaurent Pinchart 		goto done;
502df330515SLaurent Pinchart 	}
503df330515SLaurent Pinchart 
504b8ec754aSSakari Ailus 	v4l2_async_nf_init(&xdev->notifier, &xdev->v4l2_dev);
5057f81d6f0SSakari Ailus 
506df330515SLaurent Pinchart 	/* Parse the graph to extract a list of subdevice DT nodes. */
507df330515SLaurent Pinchart 	ret = xvip_graph_parse(xdev);
508df330515SLaurent Pinchart 	if (ret < 0) {
509df330515SLaurent Pinchart 		dev_err(xdev->dev, "graph parsing failed\n");
510df330515SLaurent Pinchart 		goto done;
511df330515SLaurent Pinchart 	}
512df330515SLaurent Pinchart 
5139bf19fbfSSakari Ailus 	if (list_empty(&xdev->notifier.waiting_list)) {
514df330515SLaurent Pinchart 		dev_err(xdev->dev, "no subdev found in graph\n");
51595667791SJia-Ju Bai 		ret = -ENOENT;
516df330515SLaurent Pinchart 		goto done;
517df330515SLaurent Pinchart 	}
518df330515SLaurent Pinchart 
519df330515SLaurent Pinchart 	/* Register the subdevices notifier. */
520b6ee3f0dSLaurent Pinchart 	xdev->notifier.ops = &xvip_graph_notify_ops;
521df330515SLaurent Pinchart 
522b8ec754aSSakari Ailus 	ret = v4l2_async_nf_register(&xdev->notifier);
523df330515SLaurent Pinchart 	if (ret < 0) {
524df330515SLaurent Pinchart 		dev_err(xdev->dev, "notifier registration failed\n");
525df330515SLaurent Pinchart 		goto done;
526df330515SLaurent Pinchart 	}
527df330515SLaurent Pinchart 
528df330515SLaurent Pinchart 	ret = 0;
529df330515SLaurent Pinchart 
530df330515SLaurent Pinchart done:
531df330515SLaurent Pinchart 	if (ret < 0)
532df330515SLaurent Pinchart 		xvip_graph_cleanup(xdev);
533df330515SLaurent Pinchart 
534df330515SLaurent Pinchart 	return ret;
535df330515SLaurent Pinchart }
536df330515SLaurent Pinchart 
537df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
538df330515SLaurent Pinchart  * Media Controller and V4L2
539df330515SLaurent Pinchart  */
540df330515SLaurent Pinchart 
541df330515SLaurent Pinchart static void xvip_composite_v4l2_cleanup(struct xvip_composite_device *xdev)
542df330515SLaurent Pinchart {
543df330515SLaurent Pinchart 	v4l2_device_unregister(&xdev->v4l2_dev);
544df330515SLaurent Pinchart 	media_device_unregister(&xdev->media_dev);
5459832e155SJavier Martinez Canillas 	media_device_cleanup(&xdev->media_dev);
546df330515SLaurent Pinchart }
547df330515SLaurent Pinchart 
548df330515SLaurent Pinchart static int xvip_composite_v4l2_init(struct xvip_composite_device *xdev)
549df330515SLaurent Pinchart {
550df330515SLaurent Pinchart 	int ret;
551df330515SLaurent Pinchart 
552df330515SLaurent Pinchart 	xdev->media_dev.dev = xdev->dev;
553c0decac1SMauro Carvalho Chehab 	strscpy(xdev->media_dev.model, "Xilinx Video Composite Device",
554df330515SLaurent Pinchart 		sizeof(xdev->media_dev.model));
555df330515SLaurent Pinchart 	xdev->media_dev.hw_revision = 0;
556df330515SLaurent Pinchart 
5579832e155SJavier Martinez Canillas 	media_device_init(&xdev->media_dev);
558df330515SLaurent Pinchart 
559df330515SLaurent Pinchart 	xdev->v4l2_dev.mdev = &xdev->media_dev;
560df330515SLaurent Pinchart 	ret = v4l2_device_register(xdev->dev, &xdev->v4l2_dev);
561df330515SLaurent Pinchart 	if (ret < 0) {
562df330515SLaurent Pinchart 		dev_err(xdev->dev, "V4L2 device registration failed (%d)\n",
563df330515SLaurent Pinchart 			ret);
5649832e155SJavier Martinez Canillas 		media_device_cleanup(&xdev->media_dev);
565df330515SLaurent Pinchart 		return ret;
566df330515SLaurent Pinchart 	}
567df330515SLaurent Pinchart 
568df330515SLaurent Pinchart 	return 0;
569df330515SLaurent Pinchart }
570df330515SLaurent Pinchart 
571df330515SLaurent Pinchart /* -----------------------------------------------------------------------------
572df330515SLaurent Pinchart  * Platform Device Driver
573df330515SLaurent Pinchart  */
574df330515SLaurent Pinchart 
575df330515SLaurent Pinchart static int xvip_composite_probe(struct platform_device *pdev)
576df330515SLaurent Pinchart {
577df330515SLaurent Pinchart 	struct xvip_composite_device *xdev;
578df330515SLaurent Pinchart 	int ret;
579df330515SLaurent Pinchart 
580df330515SLaurent Pinchart 	xdev = devm_kzalloc(&pdev->dev, sizeof(*xdev), GFP_KERNEL);
581df330515SLaurent Pinchart 	if (!xdev)
582df330515SLaurent Pinchart 		return -ENOMEM;
583df330515SLaurent Pinchart 
584df330515SLaurent Pinchart 	xdev->dev = &pdev->dev;
585df330515SLaurent Pinchart 	INIT_LIST_HEAD(&xdev->dmas);
586df330515SLaurent Pinchart 
587df330515SLaurent Pinchart 	ret = xvip_composite_v4l2_init(xdev);
588df330515SLaurent Pinchart 	if (ret < 0)
589df330515SLaurent Pinchart 		return ret;
590df330515SLaurent Pinchart 
591df330515SLaurent Pinchart 	ret = xvip_graph_init(xdev);
592df330515SLaurent Pinchart 	if (ret < 0)
593df330515SLaurent Pinchart 		goto error;
594df330515SLaurent Pinchart 
595df330515SLaurent Pinchart 	platform_set_drvdata(pdev, xdev);
596df330515SLaurent Pinchart 
597df330515SLaurent Pinchart 	dev_info(xdev->dev, "device registered\n");
598df330515SLaurent Pinchart 
599df330515SLaurent Pinchart 	return 0;
600df330515SLaurent Pinchart 
601df330515SLaurent Pinchart error:
602df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
603df330515SLaurent Pinchart 	return ret;
604df330515SLaurent Pinchart }
605df330515SLaurent Pinchart 
606b8368fbeSUwe Kleine-König static void xvip_composite_remove(struct platform_device *pdev)
607df330515SLaurent Pinchart {
608df330515SLaurent Pinchart 	struct xvip_composite_device *xdev = platform_get_drvdata(pdev);
609df330515SLaurent Pinchart 
610df330515SLaurent Pinchart 	xvip_graph_cleanup(xdev);
611df330515SLaurent Pinchart 	xvip_composite_v4l2_cleanup(xdev);
612df330515SLaurent Pinchart }
613df330515SLaurent Pinchart 
614df330515SLaurent Pinchart static const struct of_device_id xvip_composite_of_id_table[] = {
615df330515SLaurent Pinchart 	{ .compatible = "xlnx,video" },
616df330515SLaurent Pinchart 	{ }
617df330515SLaurent Pinchart };
618df330515SLaurent Pinchart MODULE_DEVICE_TABLE(of, xvip_composite_of_id_table);
619df330515SLaurent Pinchart 
620df330515SLaurent Pinchart static struct platform_driver xvip_composite_driver = {
621df330515SLaurent Pinchart 	.driver = {
622df330515SLaurent Pinchart 		.name = "xilinx-video",
623df330515SLaurent Pinchart 		.of_match_table = xvip_composite_of_id_table,
624df330515SLaurent Pinchart 	},
625df330515SLaurent Pinchart 	.probe = xvip_composite_probe,
626b8368fbeSUwe Kleine-König 	.remove_new = xvip_composite_remove,
627df330515SLaurent Pinchart };
628df330515SLaurent Pinchart 
629df330515SLaurent Pinchart module_platform_driver(xvip_composite_driver);
630df330515SLaurent Pinchart 
631df330515SLaurent Pinchart MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
632df330515SLaurent Pinchart MODULE_DESCRIPTION("Xilinx Video IP Composite Driver");
633df330515SLaurent Pinchart MODULE_LICENSE("GPL v2");
634