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