diff mbox

[6/8,media] rcar-vin: add shared subdevice groups

Message ID 1464203409-1279-7-git-send-email-niklas.soderlund@ragnatech.se (mailing list archive)
State New, archived
Headers show

Commit Message

Niklas Söderlund May 25, 2016, 7:10 p.m. UTC
From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

This is done to prepare for Gen3 support where there are more than one
subdevice and the usage of them are complex and can be shared between
multiple rcar-vin instances. There are a few trouble areas with Gen3
that needs to be solved in order to be able to capture video.

1. There can be up to 4 CSI-2 sources, CSI20, CSI21, CSI40 and CSI41.
   Each CSI-2 source can be used simultaneously by more then one VIN
   instance, as shown below. This requires that more then one rcar-vin
   instance be able to to use the same set of subdevices at the same
   time.

2. There can be up to 8 VIN instances, VIN0-VIN7. Each instance can
   capture video simultaneous as any other instance, but they are not
   fully independent of each other. There is one register which controls
   what input source is used that is only present in VIN0 and VIN4. The
   register in VIN0 controls input source for VIN0-VIN3 and the register
   in VIN4 input source for VIN4-7.

   To further complicate input selection it is not possible to
   independently select an input for a specific VIN instance, the whole
   group of VIN0-3 or VIN4-7 are needs to be set according to these
   predetermined selections.

   - VIN0-3 controlled by chsel bits in VnCSI_IFMD register in VIN0
   chsel    VIN0        VIN1        VIN2        VIN3
   0        CSI40/VC0   CSI20/VC0   CSI21/VC0   CSI40/VC1
   1        CSI20/VC0   CSI21/VC0   CSI40/VC0   CSI20/VC1
   2        CSI21/VC0   CSI40/VC0   CSI20/VC0   CSI21/VC1
   3        CSI40/VC0   CSI40/VC1   CSI40/VC2   CSI40/VC3
   4        CSI20/VC0   CSI20/VC1   CSI20/VC2   CSI20/VC3
   5        CSI21/VC0   CSI21/VC1   CSI21/VC2   CSI21/VC3

   - VIN4-7 controlled by chsel bits in VnCSI_IFMD register in VIN4
   chsel    VIN4        VIN5        VIN6        VIN7
   0        CSI41/VC0   CSI20/VC0   CSI21/VC0   CSI41/VC1
   1        CSI20/VC0   CSI21/VC0   CSI41/VC0   CSI20/VC1
   2        CSI21/VC0   CSI41/VC0   CSI20/VC0   CSI21/VC1
   3        CSI41/VC0   CSI41/VC1   CSI41/VC2   CSI41/VC3
   4        CSI20/VC0   CSI20/VC1   CSI20/VC2   CSI20/VC3
   5        CSI21/VC0   CSI21/VC1   CSI21/VC2   CSI21/VC3

3. Some VIN instances (VIN4 and VIN5) can in addition the shared CSI-2
   sources described above have access to a private digital input
   channel.

This patch tries to solve this problem by adding a group concept to the
rcar-vin driver. One VIN instance is in DT described to be the group
master. It can be any VIN node but preferably it should be VIN0 or VIN4
since at lest one of those nodes are required to control the chsel bits.
To allow CSI-2 input for VIN0-3 the VIN0 node must be present and the
same is true for VIN4-7 and VIN4. One can even have two separate groups
one for VIN0-3 and one for VIN4-7 provided the two groups don't want to
share a CSI-2 input source.

Each rcar-vin instance will register itself as a v4l2 subdevice in
addition to a video device. This subdevice serves a few purposes:

    1. Allow for the group master to find all rcar-vin members of its
       group and bind them.

    2. Allow for the group master to control the chsel bits using the
       only operation implemented on the subdevice, s_gpio. This
       operation is only valid for VIN0 and VIN4 instances of rcar-vin.

    3. Allow for the slave rcar-vin members to access the group API
       exposed by the master by use of the subdevice v4l2_dev pointer.

The master rcar-vin instance will bind to all subdevices needed by the
group. That is all the rcar-vin slave nodes, CSI-2 nodes and the video
source subdevices which is connected to the other end of the CSI-2
nodes. It will expose an API to the slave nodes by setting the subdevice
v4l2_dev pointer.

The group API exposed by the master allows each slave rcar-vin instance
to operate on the correct set of subdevices for the current chsel value
simply by using the operation rvin_subdev_call() instead of
v4l2_subdev_call(). There is one special case for the operations
involved in input enumeration (g_input_status, g_tvnorms, dv_timings_cap
and *enum_dv_timings), where an extension is made
v4l2_subdev_call_input() which allows for the slave to specify which of
its inputs it wish to operate on.

Inside the group API there is refcounting keeping track of s_power and
s_stream calls so they are not called multiple times for the same set of
subdevices. This is needed since more then one rcar-vin slave can view
the same input. The second instance will simply join an ongoing stream.

Each rcar-vin slave can request for the input to be changed (chsel
value) for its subgroup (VIN0-3 or VIN4-7). But there are a few
restrictions on if the input changed is allowed. These restrictions
exist to prevent one rcar-vin instance from pulling the rug from
another.

    1. It is only allowed to change input if the request is coming from
       a sole user of the subgroup. That is to say if there is exactly
       one open video device in the subgroup (the one requesting the
       input change). Multiple opens of the same video device are
       treated as there are more then one user of the subgroup.

    2. A rcar-vin slave can only switch to an input source that is valid
       for itself. That is to say if the group only have access to
       CSI20 and CSI40 it is invalid for a slave instance to request a
       chsel value that would put CSI21 as its input.

A final restriction on the driver as a whole are that it's not allowed
to open a video device if the current chsel value puts its input on a
CSI-2 node that is not available. An attempt to do so will result in a
-EBUSY error.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 .../devicetree/bindings/media/rcar_vin.txt         |  218 +++-
 drivers/media/platform/rcar-vin/Makefile           |    2 +-
 drivers/media/platform/rcar-vin/rcar-core.c        |   52 +
 drivers/media/platform/rcar-vin/rcar-dma.c         |   67 ++
 drivers/media/platform/rcar-vin/rcar-group.c       | 1122 ++++++++++++++++++++
 drivers/media/platform/rcar-vin/rcar-group.h       |   39 +-
 drivers/media/platform/rcar-vin/rcar-vin.h         |   41 +-
 7 files changed, 1527 insertions(+), 14 deletions(-)
 create mode 100644 drivers/media/platform/rcar-vin/rcar-group.c
diff mbox

Patch

diff --git a/Documentation/devicetree/bindings/media/rcar_vin.txt b/Documentation/devicetree/bindings/media/rcar_vin.txt
index 6a4e61c..9aa81dd 100644
--- a/Documentation/devicetree/bindings/media/rcar_vin.txt
+++ b/Documentation/devicetree/bindings/media/rcar_vin.txt
@@ -2,8 +2,13 @@  Renesas RCar Video Input driver (rcar_vin)
 ------------------------------------------
 
 The rcar_vin device provides video input capabilities for the Renesas R-Car
-family of devices. The current blocks are always slaves and suppot one input
-channel which can be either RGB, YUYV or BT656.
+family of devices.
+
+On Gen2 the current blocks are always slaves and support one input
+channel which can be either RGB, YUYV or BT656
+
+On Gen3 the current blocks are always slaves and support multiple inputs
+channels which can be ether RGB, YUVU, BT656 or CSI-2.
 
  - compatible: Must be one or more of the following
    - "renesas,vin-r8a7795" for the R8A7795 device
@@ -28,7 +33,30 @@  channel which can be either RGB, YUYV or BT656.
 Additionally, an alias named vinX will need to be created to specify
 which video input device this is.
 
-The per-board settings:
+On Gen3 additional ports can be specified to describe the CSI-2 group
+hierarchy. Port 0 are used to describe inputs (in per-board settings)
+and VIN group membership. Port 1 are used to describe CSI-2 endpoints.
+Port 2 are used to describe VIN endpoints which are part of the group.
+    - ports:
+        - port@0:
+            - reg 1: sub-node describing a endpoint connected to the VIN
+              group master.
+        - port@1: remote CSI-2 endpoints part of VIN group
+            - reg 0: sub-node describing a endpoint to CSI20
+            - reg 1: sub-node describing a endpoint to CSI21
+            - reg 3: sub-node describing a endpoint to CSI40
+            - reg 4: sub-node describing a endpoint to CSI41
+        - port@2: remote VIN endpoints part of VIN group
+            - reg 0: sub-node describing a endpoint to VIN0
+            - reg 1: sub-node describing a endpoint to VIN1
+            - reg 2: sub-node describing a endpoint to VIN2
+            - reg 3: sub-node describing a endpoint to VIN3
+            - reg 4: sub-node describing a endpoint to VIN4
+            - reg 5: sub-node describing a endpoint to VIN5
+            - reg 6: sub-node describing a endpoint to VIN6
+            - reg 7: sub-node describing a endpoint to VIN7
+
+The per-board settings Gen2:
  - port sub-node describing a single endpoint connected to the vin
    as described in video-interfaces.txt[1]. Only the first one will
    be considered as each vin interface has one input port.
@@ -36,9 +64,17 @@  The per-board settings:
    These settings are used to work out video input format and widths
    into the system.
 
+The per-board settings Gen3:
+-ports:
+    - port@0:
+        - reg 0: sub-node describing a endpoint connected to the VIN
+          private digital input as described in video-interfaces.txt[1].
+
+   These settings are used to work out video input format and widths
+   into the system.
 
-Device node example
--------------------
+Device node example Gen2
+------------------------
 
 	aliases {
 	       vin0 = &vin0;
@@ -52,8 +88,8 @@  Device node example
                 status = "disabled";
         };
 
-Board setup example (vin1 composite video input)
-------------------------------------------------
+Board setup example Gen2 (vin1 composite video input)
+-----------------------------------------------------
 
 &i2c2   {
         status = "ok";
@@ -92,6 +128,174 @@  Board setup example (vin1 composite video input)
         };
 };
 
+Device node example Gen3
+------------------------
+
+aliases {
+       vin0 = &vin0;
+       vin4 = &vin4;
+};
+
+csi21: csi2@fea90000 {
+        ...
+
+        ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@1 {
+                        reg = <1>;
+
+                        csi21_out: endpoint@1 {
+                                remote-endpoint =  <&vin0csi21>;
+                        };
+                };
+        };
+};
+
+vin0: video@e6ef0000 {
+        compatible = "renesas,vin-r8a7795";
+        reg = <0 0xe6ef0000 0 0x1000>;
+        interrupts = <0 188 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&cpg CPG_MOD 811>;
+        power-domains = <&cpg>;
+        status = "disabled";
+
+        ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                        reg = <0>;
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        vin0csi: endpoint@1 {
+                                reg = <1>;
+                                remote-endpoint= <&vin0out0>;
+                        };
+
+                };
+                port@1 {
+                        reg = <1>;
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        vin0csi21: endpoint@1 {
+                                reg = <1>;
+                                remote-endpoint= <&csi21_out>;
+                        };
+                };
+                port@2 {
+                        reg = <2>;
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        vin0out0: endpoint@0 {
+                                reg = <0>;
+                                remote-endpoint = <&vin0csi>;
+                        };
+                        vin0out4: endpoint@4 {
+                                reg = <4>;
+                                remote-endpoint = <&vin4csi>;
+                        };
+                };
+        };
+};
+
+vin4: video@e6ef4000 {
+        compatible = "renesas,vin-r8a7795";
+        reg = <0 0xe6ef4000 0 0x1000>;
+        interrupts = <0 174 IRQ_TYPE_LEVEL_HIGH>;
+        clocks = <&cpg CPG_MOD 807>;
+        power-domains = <&cpg>;
+        status = "disabled";
+
+        ports {
+                #address-cells = <1>;
+                #size-cells = <0>;
+
+                port@0 {
+                        reg = <0>;
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        vin4csi: endpoint@1 {
+                                reg = <1>;
+                                remote-endpoint = <&vin0out4>;
+                        };
+                };
+        };
+};
+
+Board setup example Gen3
+- VIN0 and VIN4 part of CSI-2 group
+- VIN4 with local digital input
+-----------------------------------------------------
+
+&i2c2   {
+        ...
+
+        adv7482: composite-in@70 {
+                ...
+                port {
+                        adv7482_out: endpoint@1 {
+                                clock-lanes = <0>;
+                                data-lanes = <1>;
+                                remote-endpoint = <&csi21_in>;
+                        };
+                };
+        };
 
+        adv7612: composite-in@20 {
+                ...
+                port {
+                        adv7612_out: endpoint {
+                                bus-width = <8>;
+                                remote-endpoint = <&vin4ep0>;
+                        };
+                };
+        };
+};
+
+&csi21 {
+        status = "okay";
+
+        ...
+
+        ports {
+                port@0 {
+                        reg = <0>;
+
+                        csi21_in: endpoint@0 {
+                                clock-lanes = <0>;
+                                data-lanes = <1>;
+                                remote-endpoint = <&adv7482_out>;
+                        };
+                };
+        };
+};
+
+&vin0 {
+        status = "okay";
+};
+
+&vin4 {
+        status = "okay";
+
+        ports {
+                port@0 {
+                        reg = <0>;
+                        #address-cells = <1>;
+                        #size-cells = <0>;
+
+                        vin4ep0: endpoint@0 {
+                                reg = <0>;
+                                remote-endpoint= <&adv7612_out>;
+                        };
+
+                };
+        };
+};
 
 [1] video-interfaces.txt common video media interface
diff --git a/drivers/media/platform/rcar-vin/Makefile b/drivers/media/platform/rcar-vin/Makefile
index 48c5632..7af1b36 100644
--- a/drivers/media/platform/rcar-vin/Makefile
+++ b/drivers/media/platform/rcar-vin/Makefile
@@ -1,3 +1,3 @@ 
-rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o
+rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o rcar-group.o
 
 obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin.o
diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index b8dff90..d901ad0 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -40,6 +40,9 @@  MODULE_DEVICE_TABLE(of, rvin_of_id_table);
  * Subdevice group helpers
  */
 
+#define rvin_group_call_func(v, f, args...)				\
+	(v->slave.v4l2_dev ? vin_to_group(v)->f(&v->slave, ##args) : -ENODEV)
+
 int rvin_subdev_get(struct rvin_dev *vin)
 {
 	int i, num = 0;
@@ -49,6 +52,10 @@  int rvin_subdev_get(struct rvin_dev *vin)
 		vin->inputs[i].hint = false;
 	}
 
+	/* Get inputs from CSI2 group */
+	if (vin->slave.v4l2_dev)
+		num = rvin_group_call_func(vin, get, vin->inputs);
+
 	/* Add local digital input */
 	if (num < RVIN_INPUT_MAX && vin->digital.subdev) {
 		vin->inputs[num].type = RVIN_INPUT_DIGITAL;
@@ -82,11 +89,17 @@  int rvin_subdev_put(struct rvin_dev *vin)
 	/* Store what type of input we used */
 	vin->current_input = vin->inputs[vin->current_input].type;
 
+	if (vin->slave.v4l2_dev)
+		rvin_group_call_func(vin, put);
+
 	return 0;
 }
 
 int rvin_subdev_set_input(struct rvin_dev *vin, struct rvin_input_item *item)
 {
+	if (rvin_input_is_csi(vin))
+		return rvin_group_call_func(vin, set_input, item);
+
 	if (vin->digital.subdev)
 		return 0;
 
@@ -95,6 +108,9 @@  int rvin_subdev_set_input(struct rvin_dev *vin, struct rvin_input_item *item)
 
 int rvin_subdev_get_code(struct rvin_dev *vin, u32 *code)
 {
+	if (rvin_input_is_csi(vin))
+		return rvin_group_call_func(vin, get_code, code);
+
 	*code = vin->digital.code;
 	return 0;
 }
@@ -102,6 +118,9 @@  int rvin_subdev_get_code(struct rvin_dev *vin, u32 *code)
 int rvin_subdev_get_mbus_cfg(struct rvin_dev *vin,
 			     struct v4l2_mbus_config *mbus_cfg)
 {
+	if (rvin_input_is_csi(vin))
+		return rvin_group_call_func(vin, get_mbus_cfg, mbus_cfg);
+
 	*mbus_cfg = vin->digital.mbus_cfg;
 	return 0;
 }
@@ -109,6 +128,14 @@  int rvin_subdev_get_mbus_cfg(struct rvin_dev *vin,
 struct v4l2_subdev_pad_config*
 rvin_subdev_alloc_pad_config(struct rvin_dev *vin)
 {
+	struct v4l2_subdev_pad_config *cfg;
+
+	if (rvin_input_is_csi(vin)) {
+		if (rvin_group_call_func(vin, alloc_pad_config, &cfg))
+			return NULL;
+		return cfg;
+	}
+
 	return v4l2_subdev_alloc_pad_config(vin->digital.subdev);
 }
 
@@ -122,6 +149,10 @@  int rvin_subdev_ctrl_add_handler(struct rvin_dev *vin)
 	if (ret < 0)
 		return ret;
 
+	if (rvin_input_is_csi(vin))
+		return rvin_group_call_func(vin, ctrl_add_handler,
+					    &vin->ctrl_handler);
+
 	return v4l2_ctrl_add_handler(&vin->ctrl_handler,
 				     vin->digital.subdev->ctrl_handler, NULL);
 }
@@ -268,6 +299,11 @@  static int rvin_digital_graph_init(struct rvin_dev *vin)
 
 	if (!vin->digital.asd.match.of.node) {
 		vin_dbg(vin, "No digital subdevice found\n");
+
+		/* OK for Gen3 where we can be part of a subdevice group */
+		if (vin->chip == RCAR_GEN3)
+			return 0;
+
 		return -EINVAL;
 	}
 
@@ -354,6 +390,11 @@  static int rcar_vin_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_register;
 
+	if (vin->chip == RCAR_GEN3)
+		vin->api = rvin_group_probe(&pdev->dev, &vin->v4l2_dev);
+	else
+		vin->api = NULL;
+
 	ret = rvin_digital_graph_init(vin);
 	if (ret < 0)
 		goto err_dma;
@@ -364,11 +405,17 @@  static int rcar_vin_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, vin);
 
+	ret = rvin_subdev_probe(vin);
+	if (ret)
+		goto err_subdev;
+
 	pm_suspend_ignore_children(&pdev->dev, true);
 	pm_runtime_enable(&pdev->dev);
 
 	return 0;
 
+err_subdev:
+	rvin_v4l2_remove(vin);
 err_dma:
 	rvin_dma_remove(vin);
 err_register:
@@ -383,10 +430,15 @@  static int rcar_vin_remove(struct platform_device *pdev)
 
 	pm_runtime_disable(&pdev->dev);
 
+	rvin_subdev_remove(vin);
+
 	rvin_v4l2_remove(vin);
 
 	v4l2_async_notifier_unregister(&vin->notifier);
 
+	if (vin->api)
+		rvin_group_remove(vin->api);
+
 	rvin_dma_remove(vin);
 
 	v4l2_device_unregister(&vin->v4l2_dev);
diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c
index 5196395..dc41b3b 100644
--- a/drivers/media/platform/rcar-vin/rcar-dma.c
+++ b/drivers/media/platform/rcar-vin/rcar-dma.c
@@ -16,6 +16,7 @@ 
 
 #include <linux/delay.h>
 #include <linux/interrupt.h>
+#include <linux/pm_runtime.h>
 
 #include <media/videobuf2-dma-contig.h>
 
@@ -1225,3 +1226,69 @@  error:
 
 	return ret;
 }
+
+/* -----------------------------------------------------------------------------
+ * Salve Subdevice
+ */
+
+static int rvin_subdev_s_gpio(struct v4l2_subdev *sd, u32 val)
+{
+	struct rvin_dev *vin = container_of(sd, struct rvin_dev, slave);
+	u32 ifmd;
+
+	if (vin->chip != RCAR_GEN3)
+		return 0;
+
+	pm_runtime_get_sync(vin->v4l2_dev.dev);
+
+	/*
+	 * Undocumented feature: Writing to VNCSI_IFMD_REG will go
+	 * through and on read back look correct but won't have
+	 * any effect if VNMC_REG is not first set to 0.
+	 */
+	rvin_write(vin, 0, VNMC_REG);
+
+	ifmd = VNCSI_IFMD_DES2 | VNCSI_IFMD_DES1 | VNCSI_IFMD_DES0 |
+		VNCSI_IFMD_CSI_CHSEL(val);
+
+	rvin_write(vin, ifmd, VNCSI_IFMD_REG);
+
+	vin_dbg(vin, "Set IFMD 0x%x\n", ifmd);
+
+	pm_runtime_put(vin->v4l2_dev.dev);
+
+	return 0;
+}
+
+static struct v4l2_subdev_core_ops rvin_subdev_core_ops = {
+	.s_gpio		= rvin_subdev_s_gpio,
+};
+
+static struct v4l2_subdev_ops rvin_subdev_ops = {
+	.core	= &rvin_subdev_core_ops,
+};
+
+int rvin_subdev_probe(struct rvin_dev *vin)
+{
+	vin->slave.v4l2_dev = NULL;
+
+	if (vin->chip != RCAR_GEN3)
+		return 0;
+
+	vin->slave.owner = THIS_MODULE;
+	vin->slave.dev = vin->dev;
+	v4l2_subdev_init(&vin->slave, &rvin_subdev_ops);
+	v4l2_set_subdevdata(&vin->slave, vin->dev);
+	snprintf(vin->slave.name, V4L2_SUBDEV_NAME_SIZE, "rcar-vin-slave.%s",
+		 dev_name(vin->dev));
+
+	return v4l2_async_register_subdev(&vin->slave);
+}
+
+void rvin_subdev_remove(struct rvin_dev *vin)
+{
+	if (vin->chip != RCAR_GEN3)
+		return;
+
+	v4l2_async_unregister_subdev(&vin->slave);
+}
diff --git a/drivers/media/platform/rcar-vin/rcar-group.c b/drivers/media/platform/rcar-vin/rcar-group.c
new file mode 100644
index 0000000..5b4eb55
--- /dev/null
+++ b/drivers/media/platform/rcar-vin/rcar-group.c
@@ -0,0 +1,1122 @@ 
+/*
+ * Driver for Renesas R-Car VIN
+ *
+ * Copyright (C) 2016 Renesas Electronics Corp.
+ *
+ * This program is free software; you can redistribute  it and/or modify it
+ * under  the terms of  the GNU General  Public License as published by the
+ * Free Software Foundation;  either version 2 of the  License, or (at your
+ * option) any later version.
+ */
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-of.h>
+
+#include "rcar-group.h"
+
+/* Max chsel supported by HW */
+#define RVIN_CHSEL_MAX 6
+
+enum rvin_csi_id {
+	RVIN_CSI20,
+	RVIN_CSI21,
+	RVIN_CSI40,
+	RVIN_CSI41,
+	RVIN_CSI_MAX,
+	RVIN_CSI_ERROR,
+};
+
+enum rvin_chan_id {
+	RVIN_CHAN0,
+	RVIN_CHAN1,
+	RVIN_CHAN2,
+	RVIN_CHAN3,
+	RVIN_CHAN4,
+	RVIN_CHAN5,
+	RVIN_CHAN6,
+	RVIN_CHAN7,
+	RVIN_CHAN_MAX,
+	RVIN_CHAN_ERROR,
+};
+
+struct rvin_group_map_item {
+	enum rvin_csi_id csi;
+	const char *name;
+};
+
+static const struct rvin_group_map_item
+rvin_group_map[RVIN_CHAN_MAX][RVIN_CHSEL_MAX] = {
+	{
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC0 chsel1: 0" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel1: 1" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel1: 2" },
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC0 chsel1: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel1: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel1: 5" },
+	}, {
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel1: 0" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel1: 1" },
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC0 chsel1: 2" },
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC1 chsel1: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC1 chsel1: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC1 chsel1: 5" },
+	}, {
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel1: 0" },
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC0 chsel1: 1" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel1: 2" },
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC2 chsel1: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC2 chsel1: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC2 chsel1: 5" },
+	}, {
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC1 chsel1: 0" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC1 chsel1: 1" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC1 chsel1: 2" },
+		{ .csi = RVIN_CSI40, .name = "CSI40/VC3 chsel1: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC3 chsel1: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC3 chsel1: 5" },
+	}, {
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC0 chsel2: 0" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel2: 1" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel2: 2" },
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC0 chsel2: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel2: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel2: 5" },
+	}, {
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel2: 0" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel2: 1" },
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC0 chsel2: 2" },
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC1 chsel2: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC1 chsel2: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC1 chsel2: 5" },
+	}, {
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC0 chsel2: 0" },
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC0 chsel2: 1" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC0 chsel2: 2" },
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC2 chsel2: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC2 chsel2: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC2 chsel2: 5" },
+	}, {
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC1 chsel2: 0" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC1 chsel2: 1" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC1 chsel2: 2" },
+		{ .csi = RVIN_CSI41, .name = "CSI41/VC3 chsel2: 3" },
+		{ .csi = RVIN_CSI20, .name = "CSI20/VC3 chsel2: 4" },
+		{ .csi = RVIN_CSI21, .name = "CSI21/VC3 chsel2: 5" },
+	},
+};
+
+struct rvin_group {
+	struct device *dev;
+	struct v4l2_device *v4l2_dev;
+	struct mutex lock;
+
+	struct rvin_group_api api;
+
+	struct v4l2_async_notifier notifier;
+
+	struct rvin_graph_entity bridge[RVIN_CSI_MAX];
+	struct rvin_graph_entity source[RVIN_CSI_MAX];
+	int stream[RVIN_CSI_MAX];
+	int power[RVIN_CSI_MAX];
+
+	struct rvin_graph_entity chan[RVIN_CHAN_MAX];
+	int users[RVIN_CHAN_MAX];
+
+	int chsel1;
+	int chsel2;
+};
+
+#define grp_dbg(d, fmt, arg...)		dev_dbg(d->dev, fmt, ##arg)
+#define grp_info(d, fmt, arg...)	dev_info(d->dev, fmt, ##arg)
+#define grp_err(d, fmt, arg...)		dev_err(d->dev, fmt, ##arg)
+
+/* -----------------------------------------------------------------------------
+ * Group API - Helpers
+ */
+
+static enum rvin_chan_id sd_to_chan(struct rvin_group *grp,
+				    struct v4l2_subdev *sd)
+{
+	enum rvin_chan_id chan = RVIN_CHAN_ERROR;
+	int i;
+
+	for (i = 0; i < RVIN_CHAN_MAX; i++) {
+		if (grp->chan[i].subdev == sd) {
+			chan =  i;
+			break;
+		}
+	}
+
+	/* Something is wrong, subdevice can't be resolved to channel id */
+	BUG_ON(chan == RVIN_CHAN_ERROR);
+
+	return chan;
+}
+
+static enum rvin_chan_id chan_to_master(struct rvin_group *grp,
+					enum rvin_chan_id chan)
+{
+	enum rvin_chan_id master;
+
+	switch (chan) {
+	case RVIN_CHAN0:
+	case RVIN_CHAN1:
+	case RVIN_CHAN2:
+	case RVIN_CHAN3:
+		master = RVIN_CHAN0;
+		break;
+	case RVIN_CHAN4:
+	case RVIN_CHAN5:
+	case RVIN_CHAN6:
+	case RVIN_CHAN7:
+		master = RVIN_CHAN4;
+		break;
+	default:
+		master = RVIN_CHAN_ERROR;
+		break;
+	}
+
+	/* Something is wrong, subdevice can't be resolved to channel id */
+	BUG_ON(master == RVIN_CHAN_ERROR);
+
+	return master;
+}
+
+static enum rvin_csi_id rvin_group_get_csi(struct rvin_group *grp,
+					   struct v4l2_subdev *sd, int chsel)
+{
+	enum rvin_chan_id chan;
+	enum rvin_csi_id csi;
+
+	if (chsel < 0 || chsel > RVIN_CHSEL_MAX)
+		return RVIN_CSI_ERROR;
+
+	chan = sd_to_chan(grp, sd);
+
+	csi = rvin_group_map[sd_to_chan(grp, sd)][chsel].csi;
+
+	/* Not all CSI source might be available */
+	if (!grp->bridge[csi].subdev || !grp->source[csi].subdev)
+		return RVIN_CSI_ERROR;
+
+	return csi;
+}
+
+static int rvin_group_chsel_get(struct rvin_group *grp, struct v4l2_subdev *sd)
+{
+	enum rvin_chan_id master;
+
+	master = chan_to_master(grp, sd_to_chan(grp, sd));
+
+	if (master == RVIN_CHAN0)
+		return grp->chsel1;
+
+	return grp->chsel2;
+}
+
+static void rvin_group_chsel_set(struct rvin_group *grp, struct v4l2_subdev *sd,
+				 int chsel)
+{
+	enum rvin_chan_id master;
+
+	master = chan_to_master(grp, sd_to_chan(grp, sd));
+
+	if (master == RVIN_CHAN0)
+		grp->chsel1 = chsel;
+	else
+		grp->chsel2 = chsel;
+}
+
+static enum rvin_csi_id sd_to_csi(struct rvin_group *grp,
+				  struct v4l2_subdev *sd)
+{
+	return rvin_group_get_csi(grp, sd, rvin_group_chsel_get(grp, sd));
+}
+
+/* -----------------------------------------------------------------------------
+ * Group API - logic
+ */
+
+static int rvin_group_get(struct v4l2_subdev *sd,
+			  struct rvin_input_item *inputs)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_chan_id chan, master;
+	enum rvin_csi_id csi;
+	int i, num = 0;
+
+	mutex_lock(&grp->lock);
+
+	chan = sd_to_chan(grp, sd);
+
+	/* If subgroup master is not present channel is useless */
+	master = chan_to_master(grp, chan);
+	if (!grp->chan[master].subdev) {
+		grp_err(grp, "chan%d: No group master found\n", chan);
+		goto out;
+	}
+
+	/* Make sure channel is usable with current chsel */
+	if (sd_to_csi(grp, sd) == RVIN_CSI_ERROR) {
+		grp_info(grp, "chan%d: Unavailable with current chsel\n", chan);
+		goto out;
+	}
+
+	/* Create list of valid inputs */
+	for (i = 0; i < RVIN_CHSEL_MAX; i++) {
+		csi = rvin_group_get_csi(grp, sd, i);
+		if (rvin_group_get_csi(grp, sd, i) != RVIN_CSI_ERROR) {
+			inputs[num].type = RVIN_INPUT_CSI2;
+			inputs[num].chsel = i;
+			inputs[num].hint = rvin_group_chsel_get(grp, sd) == i;
+			inputs[num].sink_idx =
+				sd_to_pad_idx(grp->source[csi].subdev,
+					      MEDIA_PAD_FL_SINK);
+			inputs[num].source_idx =
+				sd_to_pad_idx(grp->source[csi].subdev,
+					      MEDIA_PAD_FL_SOURCE);
+			strlcpy(inputs[num].name, rvin_group_map[chan][i].name,
+				RVIN_INPUT_NAME_SIZE);
+			num++;
+		}
+	}
+
+	grp->users[chan]++;
+out:
+	mutex_unlock(&grp->lock);
+
+	return num;
+}
+
+static int rvin_group_put(struct v4l2_subdev *sd)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+
+	mutex_lock(&grp->lock);
+	grp->users[sd_to_chan(grp, sd)]--;
+	mutex_unlock(&grp->lock);
+
+	return 0;
+}
+
+static int rvin_group_set_input(struct v4l2_subdev *sd,
+				struct rvin_input_item *item)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_chan_id chan, master;
+	int i, chsel, ret = 0;
+
+	chan = sd_to_chan(grp, sd);
+	chsel = item->chsel;
+
+	mutex_lock(&grp->lock);
+
+	/* No need to set chsel if it's already set */
+	if (chsel == rvin_group_chsel_get(grp, sd))
+		goto out;
+
+	/* Do not allow a chsel that is not usable for channel */
+	if (rvin_group_get_csi(grp, sd, chsel) == RVIN_CSI_ERROR) {
+		grp_err(grp, "chan%d: Invalid chsel\n", chan);
+		goto out;
+	}
+
+	/* If subgroup master is not present we can't write the chsel */
+	master = chan_to_master(grp, chan);
+	if (!grp->chan[master].subdev) {
+		grp_err(grp, "chan%d: No group master found\n", chan);
+		goto out;
+	}
+
+	/*
+	 * Check that all needed resurces are free. We don't want to
+	 * change input selection if somone else uses our subgroup or
+	 * if there are another user of our channel.
+	 */
+	for (i = 0; i < RVIN_CHAN_MAX; i++) {
+
+		/* Only look in our sub group */
+		if (master != chan_to_master(grp, i))
+			continue;
+
+		/* Need to be only user of channel and subgroup to set hsel */
+		if ((i == chan && grp->users[i] != 1) ||
+		    (i != chan && grp->users[i] != 0)) {
+			grp_info(grp, "chan%d: %s in use, can't set chsel\n",
+				 chan, i == chan ? "Channel" : "Group");
+			ret = -EBUSY;
+			goto out;
+		}
+	}
+
+	ret = v4l2_subdev_call(grp->chan[master].subdev, core, s_gpio, chsel);
+	rvin_group_chsel_set(grp, sd, chsel);
+out:
+	mutex_unlock(&grp->lock);
+	return ret;
+}
+
+static int rvin_group_get_code(struct v4l2_subdev *sd, u32 *code)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	*code = grp->source[csi].code;
+
+	return 0;
+}
+
+static int rvin_group_get_mbus_cfg(struct v4l2_subdev *sd,
+				   struct v4l2_mbus_config *mbus_cfg)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	*mbus_cfg = grp->source[csi].mbus_cfg;
+
+	return 0;
+}
+
+static int rvin_group_ctrl_add_handler(struct v4l2_subdev *sd,
+				       struct v4l2_ctrl_handler *hdl)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_ctrl_add_handler(hdl, grp->source[csi].subdev->ctrl_handler,
+				     NULL);
+}
+
+static int rvin_group_alloc_pad_config(struct v4l2_subdev *sd,
+				       struct v4l2_subdev_pad_config **cfg)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	*cfg =  v4l2_subdev_alloc_pad_config(grp->source[csi].subdev);
+
+	return 0;
+}
+
+static int rvin_group_g_tvnorms_input(struct v4l2_subdev *sd,
+				      struct rvin_input_item *item,
+				      v4l2_std_id *std)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+	if (csi == RVIN_CSI_ERROR)
+		return -EINVAL;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video,
+				g_tvnorms, std);
+}
+
+static int rvin_group_g_input_status(struct v4l2_subdev *sd,
+				     struct rvin_input_item *item, u32 *status)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+	if (csi == RVIN_CSI_ERROR)
+		return -EINVAL;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video,
+				g_input_status, status);
+}
+
+static int rvin_group_dv_timings_cap(struct v4l2_subdev *sd,
+				     struct rvin_input_item *item,
+				     struct v4l2_dv_timings_cap *cap)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+	if (csi == RVIN_CSI_ERROR)
+		return -EINVAL;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, pad,
+				dv_timings_cap, cap);
+}
+
+static int rvin_group_enum_dv_timings(struct v4l2_subdev *sd,
+				      struct rvin_input_item *item,
+				      struct v4l2_enum_dv_timings *timings)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = rvin_group_get_csi(grp, sd, item->chsel);
+
+	if (csi == RVIN_CSI_ERROR)
+		return -EINVAL;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, pad,
+				enum_dv_timings, timings);
+}
+
+static const struct rvin_group_input_ops rvin_input_ops = {
+	.g_tvnorms = &rvin_group_g_tvnorms_input,
+	.g_input_status = &rvin_group_g_input_status,
+	.dv_timings_cap = &rvin_group_dv_timings_cap,
+	.enum_dv_timings = &rvin_group_enum_dv_timings,
+};
+
+/* -----------------------------------------------------------------------------
+ * Basic group subdev operations
+ */
+
+static int rvin_group_s_power(struct v4l2_subdev *sd, int on)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+	int ret = 0;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	mutex_lock(&grp->lock);
+	/* If we already are powerd just increment the usage */
+	if ((on && grp->power[csi] != 0) || (!on && grp->power[csi] != 1))
+		goto unlock;
+
+	/* Important to start bridge fist, it needs a quiet bus to start */
+	ret = v4l2_subdev_call(grp->bridge[csi].subdev, core, s_power, on);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		goto unlock_err;
+	ret = v4l2_subdev_call(grp->source[csi].subdev, core, s_power, on);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		goto unlock_err;
+
+	grp_dbg(grp, "csi%d: power: %d bridge: %s source %s\n", csi,
+		on, grp->bridge[csi].subdev->name,
+		grp->source[csi].subdev->name);
+unlock:
+	grp->power[csi] += on ? 1 : -1;
+unlock_err:
+	mutex_unlock(&grp->lock);
+	return ret;
+}
+
+static const struct v4l2_subdev_core_ops rvin_group_core_ops = {
+	.s_power	= &rvin_group_s_power,
+};
+
+static int rvin_group_g_std(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video, g_std, std);
+}
+
+static int rvin_group_s_std(struct v4l2_subdev *sd, v4l2_std_id std)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video, s_std, std);
+}
+
+static int rvin_group_querystd(struct v4l2_subdev *sd, v4l2_std_id *std)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video, querystd, std);
+}
+
+static int rvin_group_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *tvnorms)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video, g_tvnorms,
+				tvnorms);
+}
+
+static int rvin_group_s_stream(struct v4l2_subdev *sd, int enable)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+	int ret = 0;
+
+	mutex_lock(&grp->lock);
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR) {
+		ret = -ENODEV;
+		goto unlock_err;
+	}
+
+	/* If we already are streaming just increment the usage */
+	if ((enable && grp->stream[csi] != 0) ||
+	    (!enable && grp->stream[csi] != 1))
+		goto unlock;
+
+	/* Important to start bridge fist, it needs a quiet bus to start */
+	ret = v4l2_subdev_call(grp->bridge[csi].subdev, video, s_stream,
+			       enable);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		goto unlock_err;
+	ret = v4l2_subdev_call(grp->source[csi].subdev, video, s_stream,
+			       enable);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		goto unlock_err;
+
+	grp_dbg(grp, "csi%d: stream: %d bridge: %s source %s\n", csi,
+		enable, grp->bridge[csi].subdev->name,
+		grp->source[csi].subdev->name);
+unlock:
+	grp->stream[csi] += enable ? 1 : -1;
+unlock_err:
+	mutex_unlock(&grp->lock);
+	return ret;
+}
+
+static int rvin_group_cropcap(struct v4l2_subdev *sd, struct v4l2_cropcap *crop)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video, cropcap, crop);
+}
+
+static int rvin_group_g_dv_timings(struct v4l2_subdev *sd,
+				   struct v4l2_dv_timings *timings)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video,
+				g_dv_timings, timings);
+}
+
+static int rvin_group_s_dv_timings(struct v4l2_subdev *sd,
+				   struct v4l2_dv_timings *timings)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video,
+				s_dv_timings, timings);
+}
+
+static int rvin_group_query_dv_timings(struct v4l2_subdev *sd,
+				       struct v4l2_dv_timings *timings)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, video,
+				query_dv_timings, timings);
+}
+
+static const struct v4l2_subdev_video_ops rvin_group_video_ops = {
+	.g_std		= &rvin_group_g_std,
+	.s_std		= &rvin_group_s_std,
+	.querystd	= &rvin_group_querystd,
+	.g_tvnorms	= &rvin_group_g_tvnorms,
+	.s_stream	= &rvin_group_s_stream,
+	.cropcap	= &rvin_group_cropcap,
+	.g_dv_timings		= &rvin_group_g_dv_timings,
+	.s_dv_timings		= &rvin_group_s_dv_timings,
+	.query_dv_timings	= &rvin_group_query_dv_timings,
+};
+
+static int rvin_group_get_fmt(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_pad_config *pad_cfg,
+			      struct v4l2_subdev_format *fmt)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	return v4l2_subdev_call(grp->source[csi].subdev, pad, get_fmt,
+				pad_cfg, fmt);
+}
+
+static int rvin_group_set_fmt(struct v4l2_subdev *sd,
+			      struct v4l2_subdev_pad_config *pad_cfg,
+			      struct v4l2_subdev_format *fmt)
+{
+	struct rvin_group *grp = v4l2_get_subdev_hostdata(sd);
+	enum rvin_csi_id csi;
+	int ret;
+
+	csi = sd_to_csi(grp, sd);
+	if (csi == RVIN_CSI_ERROR)
+		return -ENODEV;
+
+	/* First the source and then inform the bridge about the format. */
+	ret = v4l2_subdev_call(grp->source[csi].subdev, pad, set_fmt,
+			       pad_cfg, fmt);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		return ret;
+	return v4l2_subdev_call(grp->bridge[csi].subdev, pad, set_fmt,
+				NULL, fmt);
+}
+
+static const struct v4l2_subdev_pad_ops rvin_group_pad_ops = {
+	.get_fmt		= &rvin_group_get_fmt,
+	.set_fmt		= &rvin_group_set_fmt,
+};
+
+static const struct v4l2_subdev_ops rvin_group_ops = {
+	.core		= &rvin_group_core_ops,
+	.video		= &rvin_group_video_ops,
+	.pad		= &rvin_group_pad_ops,
+};
+
+/* -----------------------------------------------------------------------------
+ * Async notifier
+ */
+
+#define notifier_to_grp(n) container_of(n, struct rvin_group, notifier)
+
+static int rvin_graph_notify_complete(struct v4l2_async_notifier *notifier)
+{
+	struct rvin_group *grp = notifier_to_grp(notifier);
+	int i, ret;
+
+	for (i = 0; i < RVIN_CSI_MAX; i++) {
+		if (!grp->source[i].subdev)
+			continue;
+
+		if (!rvin_mbus_supported(&grp->source[i])) {
+			grp_err(grp, "Unsupported media bus format for %s\n",
+				grp->source[i].subdev->name);
+			return -EINVAL;
+		}
+
+		grp_dbg(grp, "Found media bus format for %s: %d\n",
+			grp->source[i].subdev->name, grp->source[i].code);
+	}
+
+	ret = v4l2_device_register_subdev_nodes(grp->v4l2_dev);
+	if (ret < 0) {
+		grp_err(grp, "Failed to register subdev nodes\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static void rvin_graph_notify_unbind(struct v4l2_async_notifier *notifier,
+				     struct v4l2_subdev *subdev,
+				     struct v4l2_async_subdev *asd)
+{
+	struct rvin_group *grp = notifier_to_grp(notifier);
+	int i;
+
+	for (i = 0; i < RVIN_CSI_MAX; i++) {
+		if (grp->bridge[i].subdev == subdev) {
+			grp_dbg(grp, "unbind bridge subdev %s\n", subdev->name);
+			grp->bridge[i].subdev = NULL;
+			return;
+		}
+		if (grp->source[i].subdev == subdev) {
+			grp_dbg(grp, "unbind source subdev %s\n", subdev->name);
+			grp->source[i].subdev = NULL;
+			return;
+		}
+	}
+
+	for (i = 0; i < RVIN_CHAN_MAX; i++) {
+		if (grp->chan[i].subdev == subdev) {
+			grp_dbg(grp, "unbind chan subdev %s\n", subdev->name);
+			grp->chan[i].subdev = NULL;
+			return;
+		}
+	}
+
+	grp_err(grp, "no entity for subdev %s to unbind\n", subdev->name);
+}
+
+static int rvin_graph_notify_bound(struct v4l2_async_notifier *notifier,
+				   struct v4l2_subdev *subdev,
+				   struct v4l2_async_subdev *asd)
+{
+	struct rvin_group *grp = notifier_to_grp(notifier);
+	int i;
+
+	v4l2_set_subdev_hostdata(subdev, grp);
+
+	for (i = 0; i < RVIN_CSI_MAX; i++) {
+		if (grp->bridge[i].asd.match.of.node == subdev->dev->of_node) {
+			grp_dbg(grp, "bound bridge subdev %s\n", subdev->name);
+			grp->bridge[i].subdev = subdev;
+			return 0;
+		}
+		if (grp->source[i].asd.match.of.node == subdev->dev->of_node) {
+			grp_dbg(grp, "bound source subdev %s\n", subdev->name);
+			grp->source[i].subdev = subdev;
+			return 0;
+		}
+	}
+
+	for (i = 0; i < RVIN_CHAN_MAX; i++) {
+		if (grp->chan[i].asd.match.of.node == subdev->dev->of_node) {
+			grp_dbg(grp, "bound chan subdev %s\n", subdev->name);
+			grp->chan[i].subdev = subdev;
+
+			/* Write initial chsel if binding subgroup master */
+			if (i == RVIN_CHAN0)
+				v4l2_subdev_call(subdev, core, s_gpio,
+						 grp->chsel1);
+			if (i == RVIN_CHAN4)
+				v4l2_subdev_call(subdev, core, s_gpio,
+						 grp->chsel2);
+
+			return 0;
+		}
+	}
+
+	grp_err(grp, "no entity for subdev %s to bind\n", subdev->name);
+	return -EINVAL;
+}
+
+static int rvin_parse_v4l2_endpoint(struct rvin_group *grp,
+				    struct device_node *ep,
+				    struct v4l2_mbus_config *mbus_cfg)
+{
+	struct v4l2_of_endpoint v4l2_ep;
+	int ret;
+
+	ret = v4l2_of_parse_endpoint(ep, &v4l2_ep);
+	if (ret) {
+		grp_err(grp, "Could not parse v4l2 endpoint\n");
+		return -EINVAL;
+	}
+
+	if (v4l2_ep.bus_type != V4L2_MBUS_CSI2) {
+		grp_err(grp, "Unsupported media bus type for %s\n",
+			of_node_full_name(ep));
+		return -EINVAL;
+	}
+
+	mbus_cfg->type = v4l2_ep.bus_type;
+	mbus_cfg->flags = v4l2_ep.bus.mipi_csi2.flags;
+
+	return 0;
+}
+
+static int rvin_get_csi_source(struct rvin_group *grp, int id)
+{
+	struct device_node *ep, *np, *bridge = NULL, *source = NULL;
+	struct v4l2_mbus_config mbus_cfg;
+	int ret;
+
+	grp->bridge[id].asd.match.of.node = NULL;
+	grp->bridge[id].subdev = NULL;
+	grp->source[id].asd.match.of.node = NULL;
+	grp->source[id].subdev = NULL;
+
+	/* Not all indexes will be defined, this is OK */
+	ep = of_graph_get_endpoint_by_regs(grp->dev->of_node, RVIN_PORT_CSI,
+					   id);
+	if (!ep)
+		return 0;
+
+	/* Get bridge */
+	bridge = of_graph_get_remote_port_parent(ep);
+	of_node_put(ep);
+	if (!bridge) {
+		grp_err(grp, "No bridge found for endpoint '%s'\n",
+			of_node_full_name(ep));
+		return -EINVAL;
+	}
+
+	/* Not all bridges are available, this is OK */
+	if (!of_device_is_available(bridge)) {
+		of_node_put(bridge);
+		return 0;
+	}
+
+	/* Get source */
+	for_each_endpoint_of_node(bridge, ep) {
+		np = of_graph_get_remote_port_parent(ep);
+		if (!np) {
+			grp_err(grp, "No remote found for endpoint '%s'\n",
+				of_node_full_name(ep));
+			of_node_put(bridge);
+			of_node_put(ep);
+			return -EINVAL;
+		}
+
+		if (grp->dev->of_node == np) {
+			/* Ignore loop-back */
+		} else if (!of_device_is_available(np)) {
+			/* Not all sources are available, this is OK */
+		} else if (source) {
+			grp_err(grp, "Multiple source for %s, will use first\n",
+				of_node_full_name(bridge));
+		} else {
+			/* Get endpoint v4l2 information */
+			ret = rvin_parse_v4l2_endpoint(grp, ep, &mbus_cfg);
+			if (ret) {
+				of_node_put(bridge);
+				of_node_put(ep);
+				of_node_put(np);
+				return ret;
+			}
+			source = np;
+		}
+
+		of_node_put(np);
+	}
+	of_node_put(bridge);
+
+	grp->source[id].mbus_cfg = mbus_cfg;
+
+	grp->bridge[id].asd.match.of.node = bridge;
+	grp->bridge[id].asd.match_type = V4L2_ASYNC_MATCH_OF;
+	grp->source[id].asd.match.of.node = source;
+	grp->source[id].asd.match_type = V4L2_ASYNC_MATCH_OF;
+
+	grp_dbg(grp, "csi%d: bridge: %s source: %s", id,
+		of_node_full_name(grp->bridge[id].asd.match.of.node),
+		of_node_full_name(grp->source[id].asd.match.of.node));
+
+	return ret;
+}
+
+static int rvin_get_remote_channels(struct rvin_group *grp, int id)
+{
+	struct device_node *ep, *remote;
+	int ret = 0;
+
+	grp->chan[id].asd.match.of.node = NULL;
+	grp->chan[id].subdev = NULL;
+
+	/* Not all indexes will be defined, this is OK */
+	ep = of_graph_get_endpoint_by_regs(grp->dev->of_node, RVIN_PORT_REMOTE,
+					   id);
+	if (!ep)
+		return 0;
+
+	/* Find remote subdevice */
+	remote = of_graph_get_remote_port_parent(ep);
+	if (!remote) {
+		grp_err(grp, "No remote parent for endpoint '%s'\n",
+			of_node_full_name(ep));
+		ret = -EINVAL;
+		goto out_ep;
+	}
+
+	/* Not all remotes will be available, this is OK */
+	if (!of_device_is_available(remote)) {
+		ret = 0;
+		goto out_remote;
+	}
+
+	grp->chan[id].asd.match.of.node = remote;
+	grp->chan[id].asd.match_type = V4L2_ASYNC_MATCH_OF;
+
+	grp_dbg(grp, "chan%d: node: '%s'\n", id,
+		of_node_full_name(grp->chan[id].asd.match.of.node));
+
+out_remote:
+	of_node_put(remote);
+out_ep:
+	of_node_put(ep);
+
+	return ret;
+}
+
+static void __node_add(struct v4l2_async_subdev **subdev, int num,
+		       struct rvin_graph_entity *entity)
+{
+	int i;
+
+	if (!entity->asd.match.of.node)
+		return;
+
+	for (i = 0; i < num; i++) {
+		if (!subdev[i])
+			subdev[i] = &entity->asd;
+		if (subdev[i] == &entity->asd)
+			return;
+	}
+}
+
+static int rvin_graph_init(struct rvin_group *grp)
+{
+	struct v4l2_async_subdev **subdevs = NULL;
+	int i, ret, num = 0;
+
+	/* Try to get CSI2 sources */
+	for (i = 0; i < RVIN_CSI_MAX; i++) {
+		ret = rvin_get_csi_source(grp, i);
+		if (ret)
+			return ret;
+		if (grp->bridge[i].asd.match.of.node &&
+		    grp->source[i].asd.match.of.node)
+			num += 2;
+	}
+
+	/* Try to get slave channels */
+	for (i = 0; i < RVIN_CHAN_MAX; i++) {
+		ret = rvin_get_remote_channels(grp, i);
+		if (ret)
+			return ret;
+		if (grp->chan[i].asd.match.of.node)
+			num++;
+	}
+
+	if (!num)
+		return -ENODEV;
+
+	grp_dbg(grp, "found %d group subdevice(s)\n", num);
+
+	/* Register the subdevices notifier. */
+	subdevs = devm_kzalloc(grp->dev, sizeof(*subdevs) * num, GFP_KERNEL);
+	if (subdevs == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < RVIN_CSI_MAX; i++) {
+		__node_add(subdevs, num, &grp->bridge[i]);
+		__node_add(subdevs, num, &grp->source[i]);
+	}
+	for (i = 0; i < RVIN_CHAN_MAX; i++)
+		__node_add(subdevs, num, &grp->chan[i]);
+
+	grp->notifier.num_subdevs = num;
+	grp->notifier.subdevs = subdevs;
+	grp->notifier.bound = rvin_graph_notify_bound;
+	grp->notifier.unbind = rvin_graph_notify_unbind;
+	grp->notifier.complete = rvin_graph_notify_complete;
+
+	ret = v4l2_async_notifier_register(grp->v4l2_dev, &grp->notifier);
+	if (ret < 0) {
+		grp_err(grp, "Notifier registration failed\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Base
+ */
+
+struct rvin_group_api *rvin_group_probe(struct device *dev,
+					struct v4l2_device *v4l2_dev)
+{
+	struct rvin_group *grp;
+	int i, ret;
+
+	grp = devm_kzalloc(dev, sizeof(*grp), GFP_KERNEL);
+	if (!grp)
+		return NULL;
+
+	grp->dev = dev;
+	grp->v4l2_dev = v4l2_dev;
+	grp->chsel1 = 0;
+	grp->chsel2 = 0;
+
+	for (i = 0; i < RVIN_CSI_MAX; i++) {
+		grp->power[i] = 0;
+		grp->stream[i] = 0;
+	}
+
+	for (i = 0; i < RVIN_CHAN_MAX; i++)
+		grp->users[i] = 0;
+
+	ret = rvin_graph_init(grp);
+	if (ret) {
+		devm_kfree(dev, grp);
+		return NULL;
+	}
+
+	mutex_init(&grp->lock);
+
+	grp->api.ops = &rvin_group_ops;
+	grp->api.input_ops = &rvin_input_ops;
+
+	grp->api.get = rvin_group_get;
+	grp->api.put = rvin_group_put;
+	grp->api.set_input = rvin_group_set_input;
+	grp->api.get_code = rvin_group_get_code;
+	grp->api.get_mbus_cfg = rvin_group_get_mbus_cfg;
+	grp->api.ctrl_add_handler = rvin_group_ctrl_add_handler;
+	grp->api.alloc_pad_config = rvin_group_alloc_pad_config;
+
+	return &grp->api;
+}
+
+int rvin_group_remove(struct rvin_group_api *api)
+{
+	struct rvin_group *grp = container_of(api, struct rvin_group, api);
+
+	v4l2_async_notifier_unregister(&grp->notifier);
+
+	mutex_destroy(&grp->lock);
+
+	return 0;
+}
diff --git a/drivers/media/platform/rcar-vin/rcar-group.h b/drivers/media/platform/rcar-vin/rcar-group.h
index 59eae46..8295ae4 100644
--- a/drivers/media/platform/rcar-vin/rcar-group.h
+++ b/drivers/media/platform/rcar-vin/rcar-group.h
@@ -16,14 +16,17 @@ 
 #include <media/v4l2-device.h>
 
 #define RVIN_PORT_LOCAL 0
+#define RVIN_PORT_CSI 1
+#define RVIN_PORT_REMOTE 2
 
 enum rvin_input_type {
 	RVIN_INPUT_NONE,
 	RVIN_INPUT_DIGITAL,
+	RVIN_INPUT_CSI2,
 };
 
 /* Max number of inputs supported */
-#define RVIN_INPUT_MAX 1
+#define RVIN_INPUT_MAX 7
 #define RVIN_INPUT_NAME_SIZE 32
 
 /**
@@ -99,4 +102,38 @@  static inline int sd_to_pad_idx(struct v4l2_subdev *sd, int flag)
 	return pad_idx;
 }
 
+struct rvin_group_input_ops {
+	int (*g_input_status)(struct v4l2_subdev *sd,
+			      struct rvin_input_item *item, u32 *status);
+	int (*g_tvnorms)(struct v4l2_subdev *sd,
+			 struct rvin_input_item *item, v4l2_std_id *std);
+	int (*dv_timings_cap)(struct v4l2_subdev *sd,
+			      struct rvin_input_item *item,
+			      struct v4l2_dv_timings_cap *cap);
+	int (*enum_dv_timings)(struct v4l2_subdev *sd,
+			       struct rvin_input_item *item,
+			       struct v4l2_enum_dv_timings *timings);
+};
+
+struct rvin_group_api {
+	int (*get)(struct v4l2_subdev *sd, struct rvin_input_item *inputs);
+	int (*put)(struct v4l2_subdev *sd);
+	int (*set_input)(struct v4l2_subdev *sd, struct rvin_input_item *item);
+	int (*get_code)(struct v4l2_subdev *sd, u32 *code);
+	int (*get_mbus_cfg)(struct v4l2_subdev *sd,
+			    struct v4l2_mbus_config *mbus_cfg);
+
+	int (*ctrl_add_handler)(struct v4l2_subdev *sd,
+				struct v4l2_ctrl_handler *hdl);
+	int (*alloc_pad_config)(struct v4l2_subdev *sd,
+				struct v4l2_subdev_pad_config **cfg);
+
+	const struct v4l2_subdev_ops *ops;
+	const struct rvin_group_input_ops *input_ops;
+};
+
+struct rvin_group_api *rvin_group_probe(struct device *dev,
+					struct v4l2_device *v4l2_dev);
+int rvin_group_remove(struct rvin_group_api *grp);
+
 #endif
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index b97fa43..c956153 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -102,6 +102,9 @@  struct rvin_video_format {
  * @crop:		active cropping
  * @compose:		active composing
  *
+ * @slave:		subdevice used to register with the group master
+ * @api:		group api controller (only used on master channel)
+ *
  * @current_input:	currently used input in @inputs
  * @inputs:		list of valid inputs sources
  */
@@ -134,6 +137,9 @@  struct rvin_dev {
 	struct v4l2_rect crop;
 	struct v4l2_rect compose;
 
+	struct v4l2_subdev slave;
+	struct rvin_group_api *api;
+
 	int current_input;
 	struct rvin_input_item inputs[RVIN_INPUT_MAX];
 };
@@ -147,6 +153,9 @@  struct rvin_dev {
 int rvin_dma_probe(struct rvin_dev *vin, int irq);
 void rvin_dma_remove(struct rvin_dev *vin);
 
+int rvin_subdev_probe(struct rvin_dev *vin);
+void rvin_subdev_remove(struct rvin_dev *vin);
+
 int rvin_v4l2_probe(struct rvin_dev *vin);
 void rvin_v4l2_remove(struct rvin_dev *vin);
 
@@ -158,16 +167,38 @@  void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
 void rvin_crop_scale_comp(struct rvin_dev *vin);
 
 /* Subdevice group helpers */
-#define rvin_subdev_call(v, o, f, args...)				\
+#define rvin_input_is_csi(v) (v->inputs[v->current_input].type == \
+			      RVIN_INPUT_CSI2)
+#define vin_to_group(v) container_of(v->slave.v4l2_dev, struct rvin_dev, \
+				     v4l2_dev)->api
+#define rvin_subdev_call_local(v, o, f, args...)			\
 	(v->digital.subdev ?						\
 	 v4l2_subdev_call(v->digital.subdev, o, f, ##args) : -ENODEV)
+#define rvin_subdev_call_group(v, o, f, args...)			\
+	(!(v)->slave.v4l2_dev ? -ENODEV :				\
+	 (vin_to_group(v)->ops->o && vin_to_group(v)->ops->o->f) ?	\
+	 vin_to_group(v)->ops->o->f(&v->slave, ##args) : -ENOIOCTLCMD)
+#define rvin_subdev_call_group_input(v, i, f, args...)			\
+	(!(v)->slave.v4l2_dev ? -ENODEV :				\
+	 (vin_to_group(v)->input_ops->f ?				\
+	  vin_to_group(v)->input_ops->f(&v->slave, i, ##args) : -ENOIOCTLCMD))
+#define rvin_subdev_call(v, o, f, args...)				\
+	(rvin_input_is_csi(v) ? rvin_subdev_call_group(v, o, f, ##args) :\
+	 rvin_subdev_call_local(v, o, f, ##args))
 #define rvin_subdev_call_input(v, i, o, f, args...)			\
-	(v->digital.subdev ?						\
-	 v4l2_subdev_call(v->digital.subdev, o, f, ##args) : -ENODEV)
+	(v->inputs[i].type == RVIN_INPUT_CSI2 ?				\
+	 rvin_subdev_call_group_input(v, &v->inputs[i], f, ##args) :	\
+	 rvin_subdev_call_local(v, o, f, ##args))
 
-#define rvin_subdev_has_op(v, o, f)					\
+#define rvin_subdev_has_op_local(v, o, f)				\
 	(v->digital.subdev ?						\
-	v4l2_subdev_has_op(v->digital.subdev, o, f) : -ENODEV)
+	 v4l2_subdev_has_op(v->digital.subdev, o, f) : -ENODEV)
+#define rvin_subdev_has_op_group(v, o, f)				\
+	(!(v)->slave.v4l2_dev ? -ENODEV :				\
+	 (vin_to_group(v)->ops->o && vin_to_group(v)->ops->o->f))
+#define rvin_subdev_has_op(v, o, f)					\
+	(rvin_input_is_csi(v) ? rvin_subdev_has_op_group(v, o, f) :	\
+	 rvin_subdev_has_op_local(v, o, f))
 
 int rvin_subdev_get(struct rvin_dev *vin);
 int rvin_subdev_put(struct rvin_dev *vin);