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