#include "rcar-vin.h"
 
+/*
+ * The companion CSI-2 receiver driver (rcar-csi2) is known
+ * and we know it has one source pad (pad 0) and four sink
+ * pads (pad 1-4). So to translate a pad on the remote
+ * CSI-2 receiver to/from the VIN internal channel number simply
+ * subtract/add one from the pad/channel number.
+ */
+#define rvin_group_csi_pad_to_channel(pad) ((pad) - 1)
+#define rvin_group_csi_channel_to_pad(channel) ((channel) + 1)
+
+/*
+ * Not all VINs are created equal, master VINs control the
+ * routing for other VIN's. We can figure out which VIN is
+ * master by looking at a VINs id.
+ */
+#define rvin_group_id_to_master(vin) ((vin) < 4 ? 0 : 4)
+
 /* -----------------------------------------------------------------------------
  * Gen3 CSI2 Group Allocator
  */
        return 0;
 }
 
+/* -----------------------------------------------------------------------------
+ * Group async notifier
+ */
+
+static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier)
+{
+       struct rvin_dev *vin = notifier_to_vin(notifier);
+       const struct rvin_group_route *route;
+       unsigned int i;
+       int ret;
+
+       ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
+       if (ret) {
+               vin_err(vin, "Failed to register subdev nodes\n");
+               return ret;
+       }
+
+       /* Register all video nodes for the group. */
+       for (i = 0; i < RCAR_VIN_NUM; i++) {
+               if (vin->group->vin[i]) {
+                       ret = rvin_v4l2_register(vin->group->vin[i]);
+                       if (ret)
+                               return ret;
+               }
+       }
+
+       /* Create all media device links between VINs and CSI-2's. */
+       mutex_lock(&vin->group->lock);
+       for (route = vin->info->routes; route->mask; route++) {
+               struct media_pad *source_pad, *sink_pad;
+               struct media_entity *source, *sink;
+               unsigned int source_idx;
+
+               /* Check that VIN is part of the group. */
+               if (!vin->group->vin[route->vin])
+                       continue;
+
+               /* Check that VIN' master is part of the group. */
+               if (!vin->group->vin[rvin_group_id_to_master(route->vin)])
+                       continue;
+
+               /* Check that CSI-2 is part of the group. */
+               if (!vin->group->csi[route->csi].subdev)
+                       continue;
+
+               source = &vin->group->csi[route->csi].subdev->entity;
+               source_idx = rvin_group_csi_channel_to_pad(route->channel);
+               source_pad = &source->pads[source_idx];
+
+               sink = &vin->group->vin[route->vin]->vdev.entity;
+               sink_pad = &sink->pads[0];
+
+               /* Skip if link already exists. */
+               if (media_entity_find_link(source_pad, sink_pad))
+                       continue;
+
+               ret = media_create_pad_link(source, source_idx, sink, 0, 0);
+               if (ret) {
+                       vin_err(vin, "Error adding link from %s to %s\n",
+                               source->name, sink->name);
+                       break;
+               }
+       }
+       mutex_unlock(&vin->group->lock);
+
+       return ret;
+}
+
+static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier,
+                                    struct v4l2_subdev *subdev,
+                                    struct v4l2_async_subdev *asd)
+{
+       struct rvin_dev *vin = notifier_to_vin(notifier);
+       unsigned int i;
+
+       for (i = 0; i < RCAR_VIN_NUM; i++)
+               if (vin->group->vin[i])
+                       rvin_v4l2_unregister(vin->group->vin[i]);
+
+       mutex_lock(&vin->group->lock);
+
+       for (i = 0; i < RVIN_CSI_MAX; i++) {
+               if (vin->group->csi[i].fwnode != asd->match.fwnode)
+                       continue;
+               vin->group->csi[i].subdev = NULL;
+               vin_dbg(vin, "Unbind CSI-2 %s from slot %u\n", subdev->name, i);
+               break;
+       }
+
+       mutex_unlock(&vin->group->lock);
+}
+
+static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier,
+                                  struct v4l2_subdev *subdev,
+                                  struct v4l2_async_subdev *asd)
+{
+       struct rvin_dev *vin = notifier_to_vin(notifier);
+       unsigned int i;
+
+       mutex_lock(&vin->group->lock);
+
+       for (i = 0; i < RVIN_CSI_MAX; i++) {
+               if (vin->group->csi[i].fwnode != asd->match.fwnode)
+                       continue;
+               vin->group->csi[i].subdev = subdev;
+               vin_dbg(vin, "Bound CSI-2 %s to slot %u\n", subdev->name, i);
+               break;
+       }
+
+       mutex_unlock(&vin->group->lock);
+
+       return 0;
+}
+
+static const struct v4l2_async_notifier_operations rvin_group_notify_ops = {
+       .bound = rvin_group_notify_bound,
+       .unbind = rvin_group_notify_unbind,
+       .complete = rvin_group_notify_complete,
+};
+
+static int rvin_mc_parse_of_endpoint(struct device *dev,
+                                    struct v4l2_fwnode_endpoint *vep,
+                                    struct v4l2_async_subdev *asd)
+{
+       struct rvin_dev *vin = dev_get_drvdata(dev);
+
+       if (vep->base.port != 1 || vep->base.id >= RVIN_CSI_MAX)
+               return -EINVAL;
+
+       if (!of_device_is_available(to_of_node(asd->match.fwnode))) {
+
+               vin_dbg(vin, "OF device %pOF disabled, ignoring\n",
+                       to_of_node(asd->match.fwnode));
+               return -ENOTCONN;
+
+       }
+
+       if (vin->group->csi[vep->base.id].fwnode) {
+               vin_dbg(vin, "OF device %pOF already handled\n",
+                       to_of_node(asd->match.fwnode));
+               return -ENOTCONN;
+       }
+
+       vin->group->csi[vep->base.id].fwnode = asd->match.fwnode;
+
+       vin_dbg(vin, "Add group OF device %pOF to slot %u\n",
+               to_of_node(asd->match.fwnode), vep->base.id);
+
+       return 0;
+}
+
+static int rvin_mc_parse_of_graph(struct rvin_dev *vin)
+{
+       unsigned int count = 0;
+       unsigned int i;
+       int ret;
+
+       mutex_lock(&vin->group->lock);
+
+       /* If there already is a notifier something has gone wrong, bail out. */
+       if (WARN_ON(vin->group->notifier)) {
+               mutex_unlock(&vin->group->lock);
+               return -EINVAL;
+       }
+
+       /* If not all VIN's are registered don't register the notifier. */
+       for (i = 0; i < RCAR_VIN_NUM; i++)
+               if (vin->group->vin[i])
+                       count++;
+
+       if (vin->group->count != count) {
+               mutex_unlock(&vin->group->lock);
+               return 0;
+       }
+
+       /*
+        * Have all VIN's look for subdevices. Some subdevices will overlap
+        * but the parser function can handle it, so each subdevice will
+        * only be registered once with the notifier.
+        */
+
+       vin->group->notifier = &vin->notifier;
+
+       for (i = 0; i < RCAR_VIN_NUM; i++) {
+               if (!vin->group->vin[i])
+                       continue;
+
+               ret = v4l2_async_notifier_parse_fwnode_endpoints_by_port(
+                               vin->group->vin[i]->dev, vin->group->notifier,
+                               sizeof(struct v4l2_async_subdev), 1,
+                               rvin_mc_parse_of_endpoint);
+               if (ret) {
+                       mutex_unlock(&vin->group->lock);
+                       return ret;
+               }
+       }
+
+       mutex_unlock(&vin->group->lock);
+
+       vin->group->notifier->ops = &rvin_group_notify_ops;
+
+       ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier);
+       if (ret < 0) {
+               vin_err(vin, "Notifier registration failed\n");
+               return ret;
+       }
+
+       return 0;
+}
+
 static int rvin_mc_init(struct rvin_dev *vin)
 {
        int ret;
        if (ret)
                return ret;
 
-       return rvin_group_get(vin);
+       ret = rvin_group_get(vin);
+       if (ret)
+               return ret;
+
+       ret = rvin_mc_parse_of_graph(vin);
+       if (ret)
+               rvin_group_put(vin);
+
+       return ret;
 }
 
 /* -----------------------------------------------------------------------------
        v4l2_async_notifier_unregister(&vin->notifier);
        v4l2_async_notifier_cleanup(&vin->notifier);
 
-       if (vin->info->use_mc)
+       if (vin->info->use_mc) {
+               mutex_lock(&vin->group->lock);
+               if (vin->group->notifier == &vin->notifier)
+                       vin->group->notifier = NULL;
+               mutex_unlock(&vin->group->lock);
                rvin_group_put(vin);
-       else
+       } else {
                v4l2_ctrl_handler_free(&vin->ctrl_handler);
+       }
 
        rvin_dma_unregister(vin);