linux-media.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/8] rcar-vin: Enable Gen3 support
@ 2016-05-25 19:10 Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation Niklas Söderlund
                   ` (7 more replies)
  0 siblings, 8 replies; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Hi,

This series enable Gen3 support for the rcar-vin driver. It is based on 
top of the media_tree:

git://linuxtv.org/media_tree.git master

And it depends on the first rcar-vin patch at which I hope soon will 
enter the media tree:

https://patchwork.linuxtv.org/patch/34129/

This is a rather large patch since unfortunately the subdevice and input 
selection on Gen3 are much more complex than on Gen2, see individual 
patches for a more detailed explanation.

- Patch 1-3 picks up work done by Ulrich so that effort is not wasted 
  before the driver is updated for Gen3.
- Patch 4-6 are the big patches where the driver learns how to work with 
  Gen3.
- Patch 7-8 add compatible strings for Gen3 and fallback strings that 
  are present in the old soc-camera driver but not in this new driver.

The series is tested on Koelsch for Gen2 and it works as expected. If 
one wants to test the HDMI input the patch 'r8a7791-koelsch.dts: add 
HDMI input' from Hans Verkuil are needed to add it to DT . The driver 
passes a v4l2-compliance on Gen2 without errors or warnings.  And there 
are no problems grabbing frames using the CVBS or HDMI input sources 
using qv4l2.

For Gen3 there are more drivers needed to get working video input 
running. To be able to grab frames drivers are needed for the R-Car 
CSI-2 interface and the ADV7482 devices which are not yet present in the 
kernel. Prototypes for thees two drivers exist and a wiki page at 
http://elinux.org/R-Car/Tests:rcar-vin talks about how to test it all 
together.

Whit thees prototype drivers for CSI-2 and ADV7482 the rcar-vin driver 
pass the v4l2-compliance tool without errors or warnings on CVBS inputs.  
On HDMI inputs it complains about missing DV features, this is because 
the prototype ADV7482 do not yet implement thees operations and are not 
a fault in the rcar-vin driver.

Disregarding the v4l2-compliance result there is no issue grabbing 
frames from both CVBS and HDMI input sources on Salvator-X. But more 
work is needed on the prototype drivers before they are ready to be 
submitted for upstream.

Niklas Söderlund (5):
  [media] rcar-vin: allow subdevices to be bound late
  [media] rcar-vin: add Gen3 HW registers
  [media] rcar-vin: add shared subdevice groups
  [media] rcar-vin: enable Gen3
  [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings

Ulrich Hecht (3):
  media: rcar-vin: pad-aware driver initialisation
  media: rcar_vin: Use correct pad number in try_fmt
  media: rcar-vin: add DV timings support

 .../devicetree/bindings/media/rcar_vin.txt         |  218 +++-
 drivers/media/platform/rcar-vin/Kconfig            |    2 +-
 drivers/media/platform/rcar-vin/Makefile           |    2 +-
 drivers/media/platform/rcar-vin/rcar-core.c        |  474 ++++++---
 drivers/media/platform/rcar-vin/rcar-dma.c         |  202 +++-
 drivers/media/platform/rcar-vin/rcar-group.c       | 1122 ++++++++++++++++++++
 drivers/media/platform/rcar-vin/rcar-group.h       |  139 +++
 drivers/media/platform/rcar-vin/rcar-v4l2.c        |  449 ++++----
 drivers/media/platform/rcar-vin/rcar-vin.h         |   83 +-
 9 files changed, 2253 insertions(+), 438 deletions(-)
 create mode 100644 drivers/media/platform/rcar-vin/rcar-group.c
 create mode 100644 drivers/media/platform/rcar-vin/rcar-group.h

-- 
2.8.2


^ permalink raw reply	[flat|nested] 18+ messages in thread

* [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-06-16 14:56   ` Laurent Pinchart
  2016-05-25 19:10 ` [PATCH 2/8] media: rcar_vin: Use correct pad number in try_fmt Niklas Söderlund
                   ` (6 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Ulrich Hecht, William Towle, Rob Taylor,
	Niklas Söderlund

From: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>

Add detection of source pad number for drivers aware of the media controller
API, so that rcar-vin can create device nodes to support modern drivers such
as adv7604.c (for HDMI on Lager) and the converted adv7180.c (for composite)
underneath.

Building rcar_vin gains a dependency on CONFIG_MEDIA_CONTROLLER, in
line with requirements for building the drivers associated with it.

Signed-off-by: William Towle <william.towle@codethink.co.uk>
Signed-off-by: Rob Taylor <rob.taylor@codethink.co.uk>
[uli: adapted to rcar-vin rewrite]
Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/rcar-v4l2.c | 16 ++++++++++++++++
 drivers/media/platform/rcar-vin/rcar-vin.h  |  2 ++
 2 files changed, 18 insertions(+)

diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 0bc4487..929816b 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -683,6 +683,9 @@ int rvin_v4l2_probe(struct rvin_dev *vin)
 	struct v4l2_mbus_framefmt *mf = &fmt.format;
 	struct video_device *vdev = &vin->vdev;
 	struct v4l2_subdev *sd = vin_to_source(vin);
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	int pad_idx;
+#endif
 	int ret;
 
 	v4l2_set_subdev_hostdata(sd, vin);
@@ -729,6 +732,19 @@ int rvin_v4l2_probe(struct rvin_dev *vin)
 	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
 		V4L2_CAP_READWRITE;
 
+	vin->src_pad_idx = 0;
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
+		if (sd->entity.pads[pad_idx].flags
+				== MEDIA_PAD_FL_SOURCE)
+			break;
+	if (pad_idx >= sd->entity.num_pads)
+		return -EINVAL;
+
+	vin->src_pad_idx = pad_idx;
+#endif
+	fmt.pad = vin->src_pad_idx;
+
 	/* Try to improve our guess of a reasonable window format */
 	ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
 	if (ret) {
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index 544a3b3..a6dd6db 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -87,6 +87,7 @@ struct rvin_graph_entity {
  *
  * @vdev:		V4L2 video device associated with VIN
  * @v4l2_dev:		V4L2 device
+ * @src_pad_idx:	source pad index for media controller drivers
  * @ctrl_handler:	V4L2 control handler
  * @notifier:		V4L2 asynchronous subdevs notifier
  * @entity:		entity in the DT for subdevice
@@ -117,6 +118,7 @@ struct rvin_dev {
 
 	struct video_device vdev;
 	struct v4l2_device v4l2_dev;
+	int src_pad_idx;
 	struct v4l2_ctrl_handler ctrl_handler;
 	struct v4l2_async_notifier notifier;
 	struct rvin_graph_entity entity;
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 2/8] media: rcar_vin: Use correct pad number in try_fmt
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-06-16 14:56   ` Laurent Pinchart
  2016-05-25 19:10 ` [PATCH 3/8] media: rcar-vin: add DV timings support Niklas Söderlund
                   ` (5 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Ulrich Hecht, William Towle, Niklas Söderlund

From: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>

Fix rcar_vin_try_fmt's use of an inappropriate pad number when calling
the subdev set_fmt function - for the ADV7612, IDs should be non-zero.

Signed-off-by: William Towle <william.towle@codethink.co.uk>
Reviewed-by: Rob Taylor <rob.taylor@codethink.co.uk>
Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
[uli: adapted to rcar-vin rewrite]
Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/rcar-v4l2.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 929816b..3788f8a 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -98,7 +98,7 @@ static int __rvin_try_format_source(struct rvin_dev *vin,
 					struct rvin_source_fmt *source)
 {
 	struct v4l2_subdev *sd;
-	struct v4l2_subdev_pad_config pad_cfg;
+	struct v4l2_subdev_pad_config *pad_cfg;
 	struct v4l2_subdev_format format = {
 		.which = which,
 	};
@@ -108,10 +108,16 @@ static int __rvin_try_format_source(struct rvin_dev *vin,
 
 	v4l2_fill_mbus_format(&format.format, pix, vin->source.code);
 
+	pad_cfg = v4l2_subdev_alloc_pad_config(sd);
+	if (pad_cfg == NULL)
+		return -ENOMEM;
+
+	format.pad = vin->src_pad_idx;
+
 	ret = v4l2_device_call_until_err(sd->v4l2_dev, 0, pad, set_fmt,
-					 &pad_cfg, &format);
+					 pad_cfg, &format);
 	if (ret < 0)
-		return ret;
+		goto cleanup;
 
 	v4l2_fill_pix_format(pix, &format.format);
 
@@ -121,6 +127,8 @@ static int __rvin_try_format_source(struct rvin_dev *vin,
 	vin_dbg(vin, "Source resolution: %ux%u\n", source->width,
 		source->height);
 
+cleanup:
+	v4l2_subdev_free_pad_config(pad_cfg);
 	return 0;
 }
 
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 3/8] media: rcar-vin: add DV timings support
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 2/8] media: rcar_vin: Use correct pad number in try_fmt Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-06-16 15:05   ` Laurent Pinchart
  2016-05-25 19:10 ` [PATCH 4/8] [media] rcar-vin: allow subdevices to be bound late Niklas Söderlund
                   ` (4 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Ulrich Hecht, Niklas Söderlund

From: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>

Adds ioctls DV_TIMINGS_CAP, ENUM_DV_TIMINGS, G_DV_TIMINGS, S_DV_TIMINGS,
and QUERY_DV_TIMINGS.

Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/rcar-v4l2.c | 82 +++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 3788f8a..10a5c10 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -400,6 +400,10 @@ static int rvin_enum_input(struct file *file, void *priv,
 
 	i->type = V4L2_INPUT_TYPE_CAMERA;
 	i->std = vin->vdev.tvnorms;
+
+	if (v4l2_subdev_has_op(sd, pad, dv_timings_cap))
+		i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
+
 	strlcpy(i->name, "Camera", sizeof(i->name));
 
 	return 0;
@@ -478,6 +482,78 @@ static int rvin_subscribe_event(struct v4l2_fh *fh,
 	return v4l2_ctrl_subscribe_event(fh, sub);
 }
 
+static int rvin_enum_dv_timings(struct file *file, void *priv_fh,
+				    struct v4l2_enum_dv_timings *timings)
+{
+	struct rvin_dev *vin = video_drvdata(file);
+	struct v4l2_subdev *sd = vin_to_source(vin);
+	int pad, ret;
+
+	pad = timings->pad;
+	timings->pad = vin->src_pad_idx;
+
+	ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings);
+
+	timings->pad = pad;
+
+	return ret;
+}
+
+static int rvin_s_dv_timings(struct file *file, void *priv_fh,
+				    struct v4l2_dv_timings *timings)
+{
+	struct rvin_dev *vin = video_drvdata(file);
+	struct v4l2_subdev *sd = vin_to_source(vin);
+	int err;
+
+	err = v4l2_subdev_call(sd,
+			video, s_dv_timings, timings);
+	if (!err) {
+		vin->source.width = timings->bt.width;
+		vin->source.height = timings->bt.height;
+		vin->format.width = timings->bt.width;
+		vin->format.height = timings->bt.height;
+	}
+	return err;
+}
+
+static int rvin_g_dv_timings(struct file *file, void *priv_fh,
+				    struct v4l2_dv_timings *timings)
+{
+	struct rvin_dev *vin = video_drvdata(file);
+	struct v4l2_subdev *sd = vin_to_source(vin);
+
+	return v4l2_subdev_call(sd,
+			video, g_dv_timings, timings);
+}
+
+static int rvin_query_dv_timings(struct file *file, void *priv_fh,
+				    struct v4l2_dv_timings *timings)
+{
+	struct rvin_dev *vin = video_drvdata(file);
+	struct v4l2_subdev *sd = vin_to_source(vin);
+
+	return v4l2_subdev_call(sd,
+			video, query_dv_timings, timings);
+}
+
+static int rvin_dv_timings_cap(struct file *file, void *priv_fh,
+				    struct v4l2_dv_timings_cap *cap)
+{
+	struct rvin_dev *vin = video_drvdata(file);
+	struct v4l2_subdev *sd = vin_to_source(vin);
+	int pad, ret;
+
+	pad = cap->pad;
+	cap->pad = vin->src_pad_idx;
+
+	ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
+
+	cap->pad = pad;
+
+	return ret;
+}
+
 static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
 	.vidioc_querycap		= rvin_querycap,
 	.vidioc_try_fmt_vid_cap		= rvin_try_fmt_vid_cap,
@@ -494,6 +570,12 @@ static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
 	.vidioc_g_input			= rvin_g_input,
 	.vidioc_s_input			= rvin_s_input,
 
+	.vidioc_dv_timings_cap		= rvin_dv_timings_cap,
+	.vidioc_enum_dv_timings		= rvin_enum_dv_timings,
+	.vidioc_g_dv_timings		= rvin_g_dv_timings,
+	.vidioc_s_dv_timings		= rvin_s_dv_timings,
+	.vidioc_query_dv_timings	= rvin_query_dv_timings,
+
 	.vidioc_querystd		= rvin_querystd,
 	.vidioc_g_std			= rvin_g_std,
 	.vidioc_s_std			= rvin_s_std,
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 4/8] [media] rcar-vin: allow subdevices to be bound late
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
                   ` (2 preceding siblings ...)
  2016-05-25 19:10 ` [PATCH 3/8] media: rcar-vin: add DV timings support Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 5/8] [media] rcar-vin: add Gen3 HW registers Niklas Söderlund
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

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. There is a need to be able
to change which subdevices are involved in capturing during runtime (but
not while streaming). Furtherer more the subdevices can be shared by
more then one rcar-vin instance. To be able to facilitate this for Gen3
this patch adds support to select an input (set of subdevices) after the
struct video_device have been registered.

It makes the selection when the first open occurs on the video device,
concurrent opens of the device are stuck with the input which was set by
the first open. A exception to this is if there is only one user of the
video device it is possible to change the input selection using s_input.
If s_input is attempted while there are more then one user of the video
device it will be disallowed with a -EBUSY. If a user tries to open the
video device before it has found a valid input (bound its subdevices) it
will be denied to open the video device with a -EBUSY.

At this point this change is purely academic since the driver in its
current form only supports one subdevice so not much point in trying to
change it. It do however solve the issue of bind/unbind of subdevices
and still remaining operational.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/rcar-core.c  | 425 ++++++++++++++++-----------
 drivers/media/platform/rcar-vin/rcar-dma.c   |  36 +--
 drivers/media/platform/rcar-vin/rcar-group.h | 102 +++++++
 drivers/media/platform/rcar-vin/rcar-v4l2.c  | 416 ++++++++++++--------------
 drivers/media/platform/rcar-vin/rcar-vin.h   |  49 ++-
 5 files changed, 587 insertions(+), 441 deletions(-)
 create mode 100644 drivers/media/platform/rcar-vin/rcar-group.h

diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index 341c081..b8dff90 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -25,48 +25,127 @@
 
 #include "rcar-vin.h"
 
+static const struct of_device_id rvin_of_id_table[] = {
+	{ .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
+	{ .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
+	{ .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
+	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
+	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
+	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, rvin_of_id_table);
+
 /* -----------------------------------------------------------------------------
- * Async notifier
+ * Subdevice group helpers
  */
 
-#define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier)
-
-static int rvin_mbus_supported(struct rvin_dev *vin)
+int rvin_subdev_get(struct rvin_dev *vin)
 {
-	struct v4l2_subdev *sd;
-	struct v4l2_subdev_mbus_code_enum code = {
-		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
-	};
-
-	sd = vin_to_source(vin);
-
-	code.index = 0;
-	while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
-		code.index++;
-		switch (code.code) {
-		case MEDIA_BUS_FMT_YUYV8_1X16:
-		case MEDIA_BUS_FMT_YUYV8_2X8:
-		case MEDIA_BUS_FMT_YUYV10_2X10:
-		case MEDIA_BUS_FMT_RGB888_1X24:
-			vin->source.code = code.code;
-			vin_dbg(vin, "Found supported media bus format: %d\n",
-				vin->source.code);
-			return true;
-		default:
-			break;
-		}
+	int i, num = 0;
+
+	for (i = 0; i < RVIN_INPUT_MAX; i++) {
+		vin->inputs[i].type = RVIN_INPUT_NONE;
+		vin->inputs[i].hint = false;
+	}
+
+	/* Add local digital input */
+	if (num < RVIN_INPUT_MAX && vin->digital.subdev) {
+		vin->inputs[num].type = RVIN_INPUT_DIGITAL;
+		strncpy(vin->inputs[num].name, "Digital", RVIN_INPUT_NAME_SIZE);
+		vin->inputs[num].sink_idx =
+			sd_to_pad_idx(vin->digital.subdev, MEDIA_PAD_FL_SINK);
+		vin->inputs[num].source_idx =
+			sd_to_pad_idx(vin->digital.subdev, MEDIA_PAD_FL_SOURCE);
+		/* If last input was digital we want it again */
+		if (vin->current_input == RVIN_INPUT_DIGITAL)
+			vin->inputs[num].hint = true;
+	}
+
+	/* Make sure we have at least one input */
+	if (vin->inputs[0].type == RVIN_INPUT_NONE) {
+		vin_err(vin, "No inputs for channel with current selection\n");
+		return -EBUSY;
 	}
+	vin->current_input = 0;
+
+	/* Search for hint and prefer digital over CSI2 run over all elements */
+	for (i = 0; i < RVIN_INPUT_MAX; i++)
+		if (vin->inputs[i].hint)
+			vin->current_input = i;
+
+	return 0;
+}
+
+int rvin_subdev_put(struct rvin_dev *vin)
+{
+	/* Store what type of input we used */
+	vin->current_input = vin->inputs[vin->current_input].type;
+
+	return 0;
+}
+
+int rvin_subdev_set_input(struct rvin_dev *vin, struct rvin_input_item *item)
+{
+	if (vin->digital.subdev)
+		return 0;
+
+	return -EBUSY;
+}
+
+int rvin_subdev_get_code(struct rvin_dev *vin, u32 *code)
+{
+	*code = vin->digital.code;
+	return 0;
+}
+
+int rvin_subdev_get_mbus_cfg(struct rvin_dev *vin,
+			     struct v4l2_mbus_config *mbus_cfg)
+{
+	*mbus_cfg = vin->digital.mbus_cfg;
+	return 0;
+}
+
+struct v4l2_subdev_pad_config*
+rvin_subdev_alloc_pad_config(struct rvin_dev *vin)
+{
+	return v4l2_subdev_alloc_pad_config(vin->digital.subdev);
+}
+
+int rvin_subdev_ctrl_add_handler(struct rvin_dev *vin)
+{
+	int ret;
+
+	v4l2_ctrl_handler_free(&vin->ctrl_handler);
+
+	ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16);
+	if (ret < 0)
+		return ret;
 
-	return false;
+	return v4l2_ctrl_add_handler(&vin->ctrl_handler,
+				     vin->digital.subdev->ctrl_handler, NULL);
 }
 
-static int rvin_graph_notify_complete(struct v4l2_async_notifier *notifier)
+/* -----------------------------------------------------------------------------
+ * Async notifier for local Digital
+ */
+
+#define notifier_to_vin(n) container_of(n, struct rvin_dev, notifier)
+
+static int rvin_digital_notify_complete(struct v4l2_async_notifier *notifier)
 {
-	struct v4l2_subdev *sd;
 	struct rvin_dev *vin = notifier_to_vin(notifier);
 	int ret;
 
-	sd = vin_to_source(vin);
+	/* Verify subdevices mbus format */
+	if (!rvin_mbus_supported(&vin->digital)) {
+		vin_err(vin, "Unsupported media bus format for %s\n",
+			vin->digital.subdev->name);
+		return -EINVAL;
+	}
+
+	vin_dbg(vin, "Found media bus format for %s: %d\n",
+		vin->digital.subdev->name, vin->digital.code);
 
 	ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev);
 	if (ret < 0) {
@@ -74,236 +153,226 @@ static int rvin_graph_notify_complete(struct v4l2_async_notifier *notifier)
 		return ret;
 	}
 
-	if (!rvin_mbus_supported(vin)) {
-		vin_err(vin, "No supported mediabus format found\n");
-		return -EINVAL;
+	return 0;
+}
+
+static void rvin_digital_notify_unbind(struct v4l2_async_notifier *notifier,
+				       struct v4l2_subdev *subdev,
+				       struct v4l2_async_subdev *asd)
+{
+	struct rvin_dev *vin = notifier_to_vin(notifier);
+
+	if (vin->digital.subdev == subdev) {
+		vin_dbg(vin, "unbind digital subdev %s\n", subdev->name);
+		vin->digital.subdev = NULL;
+		return;
 	}
 
-	return rvin_v4l2_probe(vin);
+	vin_err(vin, "no entity for subdev %s to unbind\n", subdev->name);
 }
 
-static void rvin_graph_notify_unbind(struct v4l2_async_notifier *notifier,
-				     struct v4l2_subdev *sd,
+static int rvin_digital_notify_bound(struct v4l2_async_notifier *notifier,
+				     struct v4l2_subdev *subdev,
 				     struct v4l2_async_subdev *asd)
 {
 	struct rvin_dev *vin = notifier_to_vin(notifier);
 
-	rvin_v4l2_remove(vin);
+	v4l2_set_subdev_hostdata(subdev, vin);
+
+	if (vin->digital.asd.match.of.node == subdev->dev->of_node) {
+		vin_dbg(vin, "bound digital subdev %s\n", subdev->name);
+		vin->digital.subdev = subdev;
+		return 0;
+	}
+
+	vin_err(vin, "no entity for subdev %s to bind\n", subdev->name);
+	return -EINVAL;
 }
 
-static int rvin_graph_notify_bound(struct v4l2_async_notifier *notifier,
-				   struct v4l2_subdev *subdev,
-				   struct v4l2_async_subdev *asd)
+static int rvin_digitial_parse_v4l2(struct rvin_dev *vin,
+				    struct device_node *ep,
+				    struct v4l2_mbus_config *mbus_cfg)
 {
-	struct rvin_dev *vin = notifier_to_vin(notifier);
+	struct v4l2_of_endpoint v4l2_ep;
+	int ret;
 
-	vin_dbg(vin, "subdev %s bound\n", subdev->name);
+	ret = v4l2_of_parse_endpoint(ep, &v4l2_ep);
+	if (ret) {
+		vin_err(vin, "Could not parse v4l2 endpoint\n");
+		return -EINVAL;
+	}
+
+	mbus_cfg->type = v4l2_ep.bus_type;
 
-	vin->entity.entity = &subdev->entity;
-	vin->entity.subdev = subdev;
+	switch (mbus_cfg->type) {
+	case V4L2_MBUS_PARALLEL:
+		vin_dbg(vin, "Found PARALLEL media bus\n");
+		mbus_cfg->flags = v4l2_ep.bus.parallel.flags;
+		break;
+	case V4L2_MBUS_BT656:
+		vin_dbg(vin, "Found BT656 media bus\n");
+		mbus_cfg->flags = 0;
+		break;
+	default:
+		vin_err(vin, "Unknown media bus type\n");
+		return -EINVAL;
+	}
 
 	return 0;
 }
 
-static int rvin_graph_parse(struct rvin_dev *vin,
-			    struct device_node *node)
+static int rvin_digital_get(struct rvin_dev *vin)
 {
-	struct device_node *remote;
-	struct device_node *ep = NULL;
-	struct device_node *next;
-	int ret = 0;
+	struct device_node *ep, *np;
+	int ret;
 
-	while (1) {
-		next = of_graph_get_next_endpoint(node, ep);
-		if (!next)
-			break;
+	vin->digital.asd.match.of.node = NULL;
+	vin->digital.subdev = NULL;
 
+	/*
+	 * Port 0 id 0 is local digital input, try to get it.
+	 * Not all instances can or will have this, that is OK
+	 */
+	ep = of_graph_get_endpoint_by_regs(vin->dev->of_node, RVIN_PORT_LOCAL,
+					   0);
+	if (!ep)
+		return 0;
+
+	np = of_graph_get_remote_port_parent(ep);
+	if (!np) {
+		vin_err(vin, "No remote parent for digital input\n");
 		of_node_put(ep);
-		ep = next;
-
-		remote = of_graph_get_remote_port_parent(ep);
-		if (!remote) {
-			ret = -EINVAL;
-			break;
-		}
-
-		/* Skip entities that we have already processed. */
-		if (remote == vin->dev->of_node) {
-			of_node_put(remote);
-			continue;
-		}
-
-		/* Remote node to connect */
-		if (!vin->entity.node) {
-			vin->entity.node = remote;
-			vin->entity.asd.match_type = V4L2_ASYNC_MATCH_OF;
-			vin->entity.asd.match.of.node = remote;
-			ret++;
-		}
+		return -EINVAL;
 	}
+	of_node_put(np);
 
+	ret = rvin_digitial_parse_v4l2(vin, ep, &vin->digital.mbus_cfg);
 	of_node_put(ep);
+	if (ret)
+		return ret;
 
-	return ret;
+	vin->digital.asd.match.of.node = np;
+	vin->digital.asd.match_type = V4L2_ASYNC_MATCH_OF;
+
+	return 0;
 }
 
-static int rvin_graph_init(struct rvin_dev *vin)
+static int rvin_digital_graph_init(struct rvin_dev *vin)
 {
 	struct v4l2_async_subdev **subdevs = NULL;
 	int ret;
 
-	/* Parse the graph to extract a list of subdevice DT nodes. */
-	ret = rvin_graph_parse(vin, vin->dev->of_node);
-	if (ret < 0) {
-		vin_err(vin, "Graph parsing failed\n");
-		goto done;
-	}
-
-	if (!ret) {
-		vin_err(vin, "No subdev found in graph\n");
-		goto done;
-	}
+	ret = rvin_digital_get(vin);
+	if (ret)
+		return ret;
 
-	if (ret != 1) {
-		vin_err(vin, "More then one subdev found in graph\n");
-		goto done;
+	if (!vin->digital.asd.match.of.node) {
+		vin_dbg(vin, "No digital subdevice found\n");
+		return -EINVAL;
 	}
 
 	/* Register the subdevices notifier. */
 	subdevs = devm_kzalloc(vin->dev, sizeof(*subdevs), GFP_KERNEL);
-	if (subdevs == NULL) {
-		ret = -ENOMEM;
-		goto done;
-	}
+	if (subdevs == NULL)
+		return -ENOMEM;
 
-	subdevs[0] = &vin->entity.asd;
+	subdevs[0] =  &vin->digital.asd;
+
+	vin_dbg(vin, "Found digital subdevice %s\n",
+		of_node_full_name(subdevs[0]->match.of.node));
 
-	vin->notifier.subdevs = subdevs;
 	vin->notifier.num_subdevs = 1;
-	vin->notifier.bound = rvin_graph_notify_bound;
-	vin->notifier.unbind = rvin_graph_notify_unbind;
-	vin->notifier.complete = rvin_graph_notify_complete;
+	vin->notifier.subdevs = subdevs;
+	vin->notifier.bound = rvin_digital_notify_bound;
+	vin->notifier.unbind = rvin_digital_notify_unbind;
+	vin->notifier.complete = rvin_digital_notify_complete;
 
 	ret = v4l2_async_notifier_register(&vin->v4l2_dev, &vin->notifier);
 	if (ret < 0) {
 		vin_err(vin, "Notifier registration failed\n");
-		goto done;
-	}
-
-	ret = 0;
-
-done:
-	if (ret < 0) {
-		v4l2_async_notifier_unregister(&vin->notifier);
-		of_node_put(vin->entity.node);
+		return ret;
 	}
 
-	return ret;
+	return 0;
 }
 
 /* -----------------------------------------------------------------------------
  * Platform Device Driver
  */
 
-static const struct of_device_id rvin_of_id_table[] = {
-	{ .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
-	{ .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
-	{ .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
-	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
-	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
-	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
-	{ },
-};
-MODULE_DEVICE_TABLE(of, rvin_of_id_table);
-
-static int rvin_parse_dt(struct rvin_dev *vin)
+static int rvin_probe_channel(struct platform_device *pdev,
+			      struct rvin_dev *vin)
 {
-	const struct of_device_id *match;
-	struct v4l2_of_endpoint ep;
-	struct device_node *np;
-	int ret;
-
-	match = of_match_device(of_match_ptr(rvin_of_id_table), vin->dev);
-	if (!match)
-		return -ENODEV;
-
-	vin->chip = (enum chip_id)match->data;
+	struct resource *mem;
+	int irq, ret;
 
-	np = of_graph_get_next_endpoint(vin->dev->of_node, NULL);
-	if (!np) {
-		vin_err(vin, "Could not find endpoint\n");
+	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (mem == NULL)
 		return -EINVAL;
-	}
 
-	ret = v4l2_of_parse_endpoint(np, &ep);
-	if (ret) {
-		vin_err(vin, "Could not parse endpoint\n");
-		return ret;
-	}
-
-	of_node_put(np);
+	vin->base = devm_ioremap_resource(vin->dev, mem);
+	if (IS_ERR(vin->base))
+		return PTR_ERR(vin->base);
 
-	vin->mbus_cfg.type = ep.bus_type;
+	irq = platform_get_irq(pdev, 0);
+	if (irq <= 0)
+		return irq;
 
-	switch (vin->mbus_cfg.type) {
-	case V4L2_MBUS_PARALLEL:
-		vin->mbus_cfg.flags = ep.bus.parallel.flags;
-		break;
-	case V4L2_MBUS_BT656:
-		vin->mbus_cfg.flags = 0;
-		break;
-	default:
-		vin_err(vin, "Unknown media bus type\n");
-		return -EINVAL;
-	}
+	ret = rvin_dma_probe(vin, irq);
+	if (ret)
+		return ret;
 
 	return 0;
 }
 
 static int rcar_vin_probe(struct platform_device *pdev)
 {
+	const struct of_device_id *match;
 	struct rvin_dev *vin;
-	struct resource *mem;
-	int irq, ret;
+	int ret;
 
 	vin = devm_kzalloc(&pdev->dev, sizeof(*vin), GFP_KERNEL);
 	if (!vin)
 		return -ENOMEM;
 
+	match = of_match_device(of_match_ptr(rvin_of_id_table), &pdev->dev);
+	if (!match)
+		return -ENODEV;
+
 	vin->dev = &pdev->dev;
+	vin->chip = (enum chip_id)match->data;
 
-	ret = rvin_parse_dt(vin);
+	/* Prefer digital input */
+	vin->current_input = RVIN_INPUT_DIGITAL;
+
+	/* Initialize the top-level structure */
+	ret = v4l2_device_register(vin->dev, &vin->v4l2_dev);
 	if (ret)
 		return ret;
 
-	mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-	if (mem == NULL)
-		return -EINVAL;
-
-	vin->base = devm_ioremap_resource(vin->dev, mem);
-	if (IS_ERR(vin->base))
-		return PTR_ERR(vin->base);
+	ret = rvin_probe_channel(pdev, vin);
+	if (ret)
+		goto err_register;
 
-	irq = platform_get_irq(pdev, 0);
-	if (irq <= 0)
-		return ret;
+	ret = rvin_digital_graph_init(vin);
+	if (ret < 0)
+		goto err_dma;
 
-	ret = rvin_dma_probe(vin, irq);
+	ret = rvin_v4l2_probe(vin);
 	if (ret)
-		return ret;
+		goto err_dma;
 
-	ret = rvin_graph_init(vin);
-	if (ret < 0)
-		goto error;
+	platform_set_drvdata(pdev, vin);
 
 	pm_suspend_ignore_children(&pdev->dev, true);
 	pm_runtime_enable(&pdev->dev);
 
-	platform_set_drvdata(pdev, vin);
-
 	return 0;
-error:
+
+err_dma:
 	rvin_dma_remove(vin);
+err_register:
+	v4l2_device_unregister(&vin->v4l2_dev);
 
 	return ret;
 }
@@ -314,10 +383,14 @@ static int rcar_vin_remove(struct platform_device *pdev)
 
 	pm_runtime_disable(&pdev->dev);
 
+	rvin_v4l2_remove(vin);
+
 	v4l2_async_notifier_unregister(&vin->notifier);
 
 	rvin_dma_remove(vin);
 
+	v4l2_device_unregister(&vin->v4l2_dev);
+
 	return 0;
 }
 
diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c
index dad3b03..b3d3c5e 100644
--- a/drivers/media/platform/rcar-vin/rcar-dma.c
+++ b/drivers/media/platform/rcar-vin/rcar-dma.c
@@ -130,9 +130,16 @@ static u32 rvin_read(struct rvin_dev *vin, u32 offset)
 
 static int rvin_setup(struct rvin_dev *vin)
 {
-	u32 vnmc, dmr, dmr2, interrupts;
+	u32 code, vnmc, dmr, dmr2, interrupts;
+	struct v4l2_mbus_config mbus_cfg;
 	bool progressive = false, output_is_yuv = false, input_is_yuv = false;
 
+	if (rvin_subdev_get_mbus_cfg(vin, &mbus_cfg))
+		return -EINVAL;
+
+	if (rvin_subdev_get_code(vin, &code))
+		return -EINVAL;
+
 	switch (vin->format.field) {
 	case V4L2_FIELD_TOP:
 		vnmc = VNMC_IM_ODD;
@@ -163,7 +170,7 @@ static int rvin_setup(struct rvin_dev *vin)
 	/*
 	 * Input interface
 	 */
-	switch (vin->source.code) {
+	switch (code) {
 	case MEDIA_BUS_FMT_YUYV8_1X16:
 		/* BT.601/BT.1358 16bit YCbCr422 */
 		vnmc |= VNMC_INF_YUV16;
@@ -171,7 +178,7 @@ static int rvin_setup(struct rvin_dev *vin)
 		break;
 	case MEDIA_BUS_FMT_YUYV8_2X8:
 		/* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */
-		vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ?
+		vnmc |= mbus_cfg.type == V4L2_MBUS_BT656 ?
 			VNMC_INF_YUV8_BT656 : VNMC_INF_YUV8_BT601;
 		input_is_yuv = true;
 		break;
@@ -180,7 +187,7 @@ static int rvin_setup(struct rvin_dev *vin)
 		break;
 	case MEDIA_BUS_FMT_YUYV10_2X10:
 		/* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */
-		vnmc |= vin->mbus_cfg.type == V4L2_MBUS_BT656 ?
+		vnmc |= mbus_cfg.type == V4L2_MBUS_BT656 ?
 			VNMC_INF_YUV10_BT656 : VNMC_INF_YUV10_BT601;
 		input_is_yuv = true;
 		break;
@@ -192,11 +199,11 @@ static int rvin_setup(struct rvin_dev *vin)
 	dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
 
 	/* Hsync Signal Polarity Select */
-	if (!(vin->mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
+	if (!(mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
 		dmr2 |= VNDMR2_HPS;
 
 	/* Vsync Signal Polarity Select */
-	if (!(vin->mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
+	if (!(mbus_cfg.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW))
 		dmr2 |= VNDMR2_VPS;
 
 	/*
@@ -1030,12 +1037,10 @@ static void rvin_buffer_queue(struct vb2_buffer *vb)
 static int rvin_start_streaming(struct vb2_queue *vq, unsigned int count)
 {
 	struct rvin_dev *vin = vb2_get_drv_priv(vq);
-	struct v4l2_subdev *sd;
 	unsigned long flags;
 	int ret;
 
-	sd = vin_to_source(vin);
-	v4l2_subdev_call(sd, video, s_stream, 1);
+	rvin_subdev_call(vin, video, s_stream, 1);
 
 	spin_lock_irqsave(&vin->qlock, flags);
 
@@ -1060,7 +1065,7 @@ out:
 	/* Return all buffers if something went wrong */
 	if (ret) {
 		return_all_buffers(vin, VB2_BUF_STATE_QUEUED);
-		v4l2_subdev_call(sd, video, s_stream, 0);
+		rvin_subdev_call(vin, video, s_stream, 0);
 	}
 
 	spin_unlock_irqrestore(&vin->qlock, flags);
@@ -1071,7 +1076,6 @@ out:
 static void rvin_stop_streaming(struct vb2_queue *vq)
 {
 	struct rvin_dev *vin = vb2_get_drv_priv(vq);
-	struct v4l2_subdev *sd;
 	unsigned long flags;
 	int retries = 0;
 
@@ -1110,8 +1114,7 @@ static void rvin_stop_streaming(struct vb2_queue *vq)
 
 	spin_unlock_irqrestore(&vin->qlock, flags);
 
-	sd = vin_to_source(vin);
-	v4l2_subdev_call(sd, video, s_stream, 0);
+	rvin_subdev_call(vin, video, s_stream, 0);
 
 	/* disable interrupts */
 	rvin_disable_interrupts(vin);
@@ -1133,8 +1136,6 @@ void rvin_dma_remove(struct rvin_dev *vin)
 		vb2_dma_contig_cleanup_ctx(vin->alloc_ctx);
 
 	mutex_destroy(&vin->lock);
-
-	v4l2_device_unregister(&vin->v4l2_dev);
 }
 
 int rvin_dma_probe(struct rvin_dev *vin, int irq)
@@ -1142,11 +1143,6 @@ int rvin_dma_probe(struct rvin_dev *vin, int irq)
 	struct vb2_queue *q = &vin->queue;
 	int i, ret;
 
-	/* Initialize the top-level structure */
-	ret = v4l2_device_register(vin->dev, &vin->v4l2_dev);
-	if (ret)
-		return ret;
-
 	mutex_init(&vin->lock);
 	INIT_LIST_HEAD(&vin->buf_list);
 
diff --git a/drivers/media/platform/rcar-vin/rcar-group.h b/drivers/media/platform/rcar-vin/rcar-group.h
new file mode 100644
index 0000000..59eae46
--- /dev/null
+++ b/drivers/media/platform/rcar-vin/rcar-group.h
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+#ifndef __RCAR_GROUP__
+#define __RCAR_GROUP__
+
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+
+#define RVIN_PORT_LOCAL 0
+
+enum rvin_input_type {
+	RVIN_INPUT_NONE,
+	RVIN_INPUT_DIGITAL,
+};
+
+/* Max number of inputs supported */
+#define RVIN_INPUT_MAX 1
+#define RVIN_INPUT_NAME_SIZE 32
+
+/**
+ * struct rvin_input_item - One possible input for the channel
+ * @name:	User-friendly name of the input
+ * @type:	Type of the input or RVIN_INPUT_NONE if not available
+ * @chsel:	The chsel value needed to select this input
+ * @sink_idx:	Sink pad number from the subdevice associated with the input
+ * @source_idx:	Source pad number from the subdevice associated with the input
+ */
+struct rvin_input_item {
+	char name[RVIN_INPUT_NAME_SIZE];
+	enum rvin_input_type type;
+	int chsel;
+	bool hint;
+	int sink_idx;
+	int source_idx;
+};
+
+/**
+ * struct rvin_graph_entity - Video endpoint from async framework
+ * @asd:	sub-device descriptor for async framework
+ * @subdev:	subdevice matched using async framework
+ * @code:	Media bus format from source
+ * @mbus_cfg:	Media bus format from DT
+ */
+struct rvin_graph_entity {
+	struct v4l2_async_subdev asd;
+	struct v4l2_subdev *subdev;
+
+	u32 code;
+	struct v4l2_mbus_config mbus_cfg;
+};
+
+static inline int rvin_mbus_supported(struct rvin_graph_entity *entity)
+{
+	struct v4l2_subdev *sd = entity->subdev;
+	struct v4l2_subdev_mbus_code_enum code = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+
+	code.index = 0;
+	while (!v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code)) {
+		code.index++;
+		switch (code.code) {
+		case MEDIA_BUS_FMT_YUYV8_1X16:
+		case MEDIA_BUS_FMT_YUYV8_2X8:
+		case MEDIA_BUS_FMT_YUYV10_2X10:
+		case MEDIA_BUS_FMT_RGB888_1X24:
+			entity->code = code.code;
+			return true;
+		default:
+			break;
+		}
+	}
+
+	return false;
+}
+
+static inline int sd_to_pad_idx(struct v4l2_subdev *sd, int flag)
+{
+	int pad_idx;
+#if defined(CONFIG_MEDIA_CONTROLLER)
+	for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
+		if (sd->entity.pads[pad_idx].flags == flag)
+			break;
+
+	if (pad_idx >= sd->entity.num_pads)
+		pad_idx = 0;
+#else
+	pad_idx = 0;
+#endif
+	return pad_idx;
+}
+
+#endif
diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 10a5c10..2307f5b 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -93,30 +93,31 @@ static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix)
  */
 
 static int __rvin_try_format_source(struct rvin_dev *vin,
-					u32 which,
-					struct v4l2_pix_format *pix,
-					struct rvin_source_fmt *source)
+				    u32 which,
+				    struct v4l2_pix_format *pix,
+				    struct rvin_source_fmt *source)
 {
-	struct v4l2_subdev *sd;
 	struct v4l2_subdev_pad_config *pad_cfg;
 	struct v4l2_subdev_format format = {
 		.which = which,
 	};
+	u32 code;
 	int ret;
 
-	sd = vin_to_source(vin);
+	ret = rvin_subdev_get_code(vin, &code);
+	if (ret)
+		return -EINVAL;
 
-	v4l2_fill_mbus_format(&format.format, pix, vin->source.code);
+	v4l2_fill_mbus_format(&format.format, pix, code);
 
-	pad_cfg = v4l2_subdev_alloc_pad_config(sd);
+	pad_cfg = rvin_subdev_alloc_pad_config(vin);
 	if (pad_cfg == NULL)
 		return -ENOMEM;
 
-	format.pad = vin->src_pad_idx;
+	format.pad = vin->inputs[vin->current_input].source_idx;
 
-	ret = v4l2_device_call_until_err(sd->v4l2_dev, 0, pad, set_fmt,
-					 pad_cfg, &format);
-	if (ret < 0)
+	ret = rvin_subdev_call(vin, pad, set_fmt, pad_cfg, &format);
+	if (ret < 0 && ret != -ENOIOCTLCMD)
 		goto cleanup;
 
 	v4l2_fill_pix_format(pix, &format.format);
@@ -129,13 +130,13 @@ static int __rvin_try_format_source(struct rvin_dev *vin,
 
 cleanup:
 	v4l2_subdev_free_pad_config(pad_cfg);
-	return 0;
+	return ret;
 }
 
 static int __rvin_try_format(struct rvin_dev *vin,
-				 u32 which,
-				 struct v4l2_pix_format *pix,
-				 struct rvin_source_fmt *source)
+			     u32 which,
+			     struct v4l2_pix_format *pix,
+			     struct rvin_source_fmt *source)
 {
 	const struct rvin_video_format *info;
 	u32 rwidth, rheight, walign;
@@ -219,13 +220,11 @@ static int rvin_try_fmt_vid_cap(struct file *file, void *priv,
 	struct rvin_source_fmt source;
 
 	return __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix,
-				     &source);
+				 &source);
 }
 
-static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
-			      struct v4l2_format *f)
+static int __rvin_s_fmt_vid_cap(struct rvin_dev *vin, struct v4l2_format *f)
 {
-	struct rvin_dev *vin = video_drvdata(file);
 	struct rvin_source_fmt source;
 	int ret;
 
@@ -233,7 +232,7 @@ static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
 		return -EBUSY;
 
 	ret = __rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix,
-				    &source);
+				&source);
 	if (ret)
 		return ret;
 
@@ -245,6 +244,14 @@ static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
 	return 0;
 }
 
+static int rvin_s_fmt_vid_cap(struct file *file, void *priv,
+			      struct v4l2_format *f)
+{
+	struct rvin_dev *vin = video_drvdata(file);
+
+	return __rvin_s_fmt_vid_cap(vin, f);
+}
+
 static int rvin_g_fmt_vid_cap(struct file *file, void *priv,
 			      struct v4l2_format *f)
 {
@@ -334,8 +341,8 @@ static int rvin_s_selection(struct file *file, void *fh,
 		vin->crop = s->r = r;
 
 		vin_dbg(vin, "Cropped %dx%d@%d:%d of %dx%d\n",
-			 r.width, r.height, r.left, r.top,
-			 vin->source.width, vin->source.height);
+			r.width, r.height, r.left, r.top,
+			vin->source.width, vin->source.height);
 		break;
 	case V4L2_SEL_TGT_COMPOSE:
 		/* Make sure compose rect fits inside output format */
@@ -359,8 +366,8 @@ static int rvin_s_selection(struct file *file, void *fh,
 		vin->compose = s->r = r;
 
 		vin_dbg(vin, "Compose %dx%d@%d:%d in %dx%d\n",
-			 r.width, r.height, r.left, r.top,
-			 vin->format.width, vin->format.height);
+			r.width, r.height, r.left, r.top,
+			vin->format.width, vin->format.height);
 		break;
 	default:
 		return -EINVAL;
@@ -376,75 +383,167 @@ static int rvin_cropcap(struct file *file, void *priv,
 			struct v4l2_cropcap *crop)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 
 	if (crop->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
 		return -EINVAL;
 
-	return v4l2_subdev_call(sd, video, cropcap, crop);
+	return rvin_subdev_call(vin, video, cropcap, crop);
+}
+
+static int rvin_attach_subdevices(struct rvin_dev *vin)
+{
+	struct v4l2_subdev_format fmt = {
+		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
+	};
+	struct v4l2_mbus_framefmt *mf = &fmt.format;
+	struct v4l2_format f;
+	int ret;
+
+	ret = rvin_subdev_set_input(vin, &vin->inputs[vin->current_input]);
+	if (ret)
+		return ret;
+
+	ret = rvin_subdev_call(vin, core, s_power, 1);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		return ret;
+
+	vin->vdev.tvnorms = 0;
+	ret = rvin_subdev_call(vin, video, g_tvnorms, &vin->vdev.tvnorms);
+	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+		goto error;
+
+	/* Add controlls */
+	ret = rvin_subdev_ctrl_add_handler(vin);
+	if (ret < 0)
+		goto error;
+
+	v4l2_ctrl_handler_setup(&vin->ctrl_handler);
+
+	fmt.pad = vin->inputs[vin->current_input].source_idx;
+
+	/* Try to improve our guess of a reasonable window format */
+	ret = rvin_subdev_call(vin, pad, get_fmt, NULL, &fmt);
+	if (ret)
+		goto error;
+
+	/* Set default format */
+	vin->format.width	= mf->width;
+	vin->format.height	= mf->height;
+	vin->format.colorspace	= mf->colorspace;
+	vin->format.field	= mf->field;
+	vin->format.pixelformat	= RVIN_DEFAULT_FORMAT;
+
+	/* Set initial crop and compose */
+	vin->crop.top = vin->crop.left = 0;
+	vin->crop.width = mf->width;
+	vin->crop.height = mf->height;
+
+	vin->compose.top = vin->compose.left = 0;
+	vin->compose.width = mf->width;
+	vin->compose.height = mf->height;
+
+	f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	f.fmt.pix = vin->format;
+	ret = __rvin_s_fmt_vid_cap(vin, &f);
+	if (ret)
+		goto error;
+
+	return 0;
+error:
+	rvin_subdev_call(vin, core, s_power, 0);
+	return ret;
+}
+
+static void rvin_detach_subdevices(struct rvin_dev *vin)
+{
+	rvin_subdev_call(vin, core, s_power, 0);
 }
 
 static int rvin_enum_input(struct file *file, void *priv,
 			   struct v4l2_input *i)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
+	struct rvin_input_item *item;
 	int ret;
 
-	if (i->index != 0)
+	if (i->index > RVIN_INPUT_MAX ||
+	    vin->inputs[i->index].type == RVIN_INPUT_NONE)
 		return -EINVAL;
 
-	ret = v4l2_subdev_call(sd, video, g_input_status, &i->status);
+	item = &vin->inputs[i->index];
+
+	ret = rvin_subdev_call_input(vin, i->index, video,
+				     g_input_status, &i->status);
+
 	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
 		return ret;
 
 	i->type = V4L2_INPUT_TYPE_CAMERA;
-	i->std = vin->vdev.tvnorms;
+	strlcpy(i->name, item->name, sizeof(i->name));
 
-	if (v4l2_subdev_has_op(sd, pad, dv_timings_cap))
+	if (rvin_subdev_has_op(vin, pad, dv_timings_cap)) {
 		i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
-
-	strlcpy(i->name, "Camera", sizeof(i->name));
+		i->std = 0;
+	} else {
+		i->capabilities = V4L2_IN_CAP_STD;
+		ret = rvin_subdev_call_input(vin,
+					     vin->current_input,
+					     video, g_tvnorms, &i->std);
+		if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
+			return ret;
+	}
 
 	return 0;
 }
 
 static int rvin_g_input(struct file *file, void *priv, unsigned int *i)
 {
-	*i = 0;
+	struct rvin_dev *vin = video_drvdata(file);
+
+	*i = vin->current_input;
 	return 0;
 }
 
 static int rvin_s_input(struct file *file, void *priv, unsigned int i)
 {
-	if (i > 0)
+	struct rvin_dev *vin = video_drvdata(file);
+	int ret;
+
+	if (i > RVIN_INPUT_MAX || vin->inputs[i].type == RVIN_INPUT_NONE)
 		return -EINVAL;
-	return 0;
+
+	rvin_detach_subdevices(vin);
+
+	ret = rvin_subdev_set_input(vin, &vin->inputs[i]);
+	if (!ret)
+		vin->current_input = i;
+
+	/* Power on new subdevice */
+	return rvin_attach_subdevices(vin);
 }
 
 static int rvin_querystd(struct file *file, void *priv, v4l2_std_id *a)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 
-	return v4l2_subdev_call(sd, video, querystd, a);
+	return rvin_subdev_call(vin, video, querystd, a);
 }
 
 static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 	struct v4l2_subdev_format fmt = {
 		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
 	};
 	struct v4l2_mbus_framefmt *mf = &fmt.format;
-	int ret = v4l2_subdev_call(sd, video, s_std, a);
+	int ret;
 
+	ret = rvin_subdev_call(vin, video, s_std, a);
 	if (ret < 0)
 		return ret;
 
 	/* Changing the standard will change the width/height */
-	ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
+	ret = rvin_subdev_call(vin, pad, get_fmt, NULL, &fmt);
 	if (ret) {
 		vin_err(vin, "Failed to get initial format\n");
 		return ret;
@@ -467,9 +566,8 @@ static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a)
 static int rvin_g_std(struct file *file, void *priv, v4l2_std_id *a)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 
-	return v4l2_subdev_call(sd, video, g_std, a);
+	return rvin_subdev_call(vin, video, g_std, a);
 }
 
 static int rvin_subscribe_event(struct v4l2_fh *fh,
@@ -483,31 +581,29 @@ static int rvin_subscribe_event(struct v4l2_fh *fh,
 }
 
 static int rvin_enum_dv_timings(struct file *file, void *priv_fh,
-				    struct v4l2_enum_dv_timings *timings)
+				struct v4l2_enum_dv_timings *timings)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
-	int pad, ret;
+	int input, ret;
+
+	input = timings->pad;
 
-	pad = timings->pad;
-	timings->pad = vin->src_pad_idx;
+	timings->pad = vin->inputs[input].sink_idx;
 
-	ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings);
+	ret = rvin_subdev_call_input(vin, input, pad, enum_dv_timings, timings);
 
-	timings->pad = pad;
+	timings->pad = input;
 
 	return ret;
 }
 
 static int rvin_s_dv_timings(struct file *file, void *priv_fh,
-				    struct v4l2_dv_timings *timings)
+			     struct v4l2_dv_timings *timings)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 	int err;
 
-	err = v4l2_subdev_call(sd,
-			video, s_dv_timings, timings);
+	err = rvin_subdev_call(vin, video, s_dv_timings, timings);
 	if (!err) {
 		vin->source.width = timings->bt.width;
 		vin->source.height = timings->bt.height;
@@ -518,38 +614,35 @@ static int rvin_s_dv_timings(struct file *file, void *priv_fh,
 }
 
 static int rvin_g_dv_timings(struct file *file, void *priv_fh,
-				    struct v4l2_dv_timings *timings)
+			     struct v4l2_dv_timings *timings)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 
-	return v4l2_subdev_call(sd,
-			video, g_dv_timings, timings);
+	return rvin_subdev_call(vin, video, g_dv_timings, timings);
 }
 
 static int rvin_query_dv_timings(struct file *file, void *priv_fh,
-				    struct v4l2_dv_timings *timings)
+				 struct v4l2_dv_timings *timings)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
 
-	return v4l2_subdev_call(sd,
-			video, query_dv_timings, timings);
+	return rvin_subdev_call(vin, video, query_dv_timings, timings);
 }
 
 static int rvin_dv_timings_cap(struct file *file, void *priv_fh,
-				    struct v4l2_dv_timings_cap *cap)
+			       struct v4l2_dv_timings_cap *cap)
 {
 	struct rvin_dev *vin = video_drvdata(file);
-	struct v4l2_subdev *sd = vin_to_source(vin);
-	int pad, ret;
+	int input, ret;
 
-	pad = cap->pad;
-	cap->pad = vin->src_pad_idx;
+	input = cap->pad;
 
-	ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
+	cap->pad = vin->inputs[input].sink_idx;
 
-	cap->pad = pad;
+	ret = rvin_subdev_call_input(vin, input, pad,
+				     dv_timings_cap, cap);
+
+	cap->pad = input;
 
 	return ret;
 }
@@ -599,80 +692,6 @@ static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
  * File Operations
  */
 
-static int rvin_power_on(struct rvin_dev *vin)
-{
-	int ret;
-	struct v4l2_subdev *sd = vin_to_source(vin);
-
-	pm_runtime_get_sync(vin->v4l2_dev.dev);
-
-	ret = v4l2_subdev_call(sd, core, s_power, 1);
-	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
-		return ret;
-	return 0;
-}
-
-static int rvin_power_off(struct rvin_dev *vin)
-{
-	int ret;
-	struct v4l2_subdev *sd = vin_to_source(vin);
-
-	ret = v4l2_subdev_call(sd, core, s_power, 0);
-
-	pm_runtime_put(vin->v4l2_dev.dev);
-
-	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
-		return ret;
-
-	return 0;
-}
-
-static int rvin_initialize_device(struct file *file)
-{
-	struct rvin_dev *vin = video_drvdata(file);
-	int ret;
-
-	struct v4l2_format f = {
-		.type = V4L2_BUF_TYPE_VIDEO_CAPTURE,
-		.fmt.pix = {
-			.width		= vin->format.width,
-			.height		= vin->format.height,
-			.field		= vin->format.field,
-			.colorspace	= vin->format.colorspace,
-			.pixelformat	= vin->format.pixelformat,
-		},
-	};
-
-	ret = rvin_power_on(vin);
-	if (ret < 0)
-		return ret;
-
-	pm_runtime_enable(&vin->vdev.dev);
-	ret = pm_runtime_resume(&vin->vdev.dev);
-	if (ret < 0 && ret != -ENOSYS)
-		goto eresume;
-
-	/*
-	 * Try to configure with default parameters. Notice: this is the
-	 * very first open, so, we cannot race against other calls,
-	 * apart from someone else calling open() simultaneously, but
-	 * .host_lock is protecting us against it.
-	 */
-	ret = rvin_s_fmt_vid_cap(file, NULL, &f);
-	if (ret < 0)
-		goto esfmt;
-
-	v4l2_ctrl_handler_setup(&vin->ctrl_handler);
-
-	return 0;
-esfmt:
-	pm_runtime_disable(&vin->vdev.dev);
-eresume:
-	rvin_power_off(vin);
-
-	return ret;
-}
-
 static int rvin_open(struct file *file)
 {
 	struct rvin_dev *vin = video_drvdata(file);
@@ -684,17 +703,30 @@ static int rvin_open(struct file *file)
 
 	ret = v4l2_fh_open(file);
 	if (ret)
-		goto unlock;
-
-	if (!v4l2_fh_is_singular_file(file))
-		goto unlock;
+		goto err_out;
 
-	if (rvin_initialize_device(file)) {
-		v4l2_fh_release(file);
-		ret = -ENODEV;
+	ret = rvin_subdev_get(vin);
+	if (ret)
+		goto err_open;
+
+	if (v4l2_fh_is_singular_file(file)) {
+		pm_runtime_get_sync(vin->dev);
+		ret = rvin_attach_subdevices(vin);
+		if (ret) {
+			vin_err(vin, "Error attaching subdevices\n");
+			goto err_get;
+		}
 	}
 
-unlock:
+	mutex_unlock(&vin->lock);
+
+	return 0;
+err_get:
+	pm_runtime_put(vin->dev);
+	rvin_subdev_put(vin);
+err_open:
+	v4l2_fh_release(file);
+err_out:
 	mutex_unlock(&vin->lock);
 	return ret;
 }
@@ -718,11 +750,12 @@ static int rvin_release(struct file *file)
 	 * Then de-initialize hw module.
 	 */
 	if (fh_singular) {
-		pm_runtime_suspend(&vin->vdev.dev);
-		pm_runtime_disable(&vin->vdev.dev);
-		rvin_power_off(vin);
+		rvin_detach_subdevices(vin);
+		pm_runtime_put(vin->dev);
 	}
 
+	rvin_subdev_put(vin);
+
 	mutex_unlock(&vin->lock);
 
 	return ret;
@@ -767,49 +800,11 @@ static void rvin_notify(struct v4l2_subdev *sd,
 
 int rvin_v4l2_probe(struct rvin_dev *vin)
 {
-	struct v4l2_subdev_format fmt = {
-		.which = V4L2_SUBDEV_FORMAT_ACTIVE,
-	};
-	struct v4l2_mbus_framefmt *mf = &fmt.format;
 	struct video_device *vdev = &vin->vdev;
-	struct v4l2_subdev *sd = vin_to_source(vin);
-#if defined(CONFIG_MEDIA_CONTROLLER)
-	int pad_idx;
-#endif
 	int ret;
 
-	v4l2_set_subdev_hostdata(sd, vin);
-
 	vin->v4l2_dev.notify = rvin_notify;
 
-	ret = v4l2_subdev_call(sd, video, g_tvnorms, &vin->vdev.tvnorms);
-	if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV)
-		return ret;
-
-	if (vin->vdev.tvnorms == 0) {
-		/* Disable the STD API if there are no tvnorms defined */
-		v4l2_disable_ioctl(&vin->vdev, VIDIOC_G_STD);
-		v4l2_disable_ioctl(&vin->vdev, VIDIOC_S_STD);
-		v4l2_disable_ioctl(&vin->vdev, VIDIOC_QUERYSTD);
-		v4l2_disable_ioctl(&vin->vdev, VIDIOC_ENUMSTD);
-	}
-
-	/* Add the controls */
-	/*
-	 * Currently the subdev with the largest number of controls (13) is
-	 * ov6550. So let's pick 16 as a hint for the control handler. Note
-	 * that this is a hint only: too large and you waste some memory, too
-	 * small and there is a (very) small performance hit when looking up
-	 * controls in the internal hash.
-	 */
-	ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16);
-	if (ret < 0)
-		return ret;
-
-	ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, sd->ctrl_handler, NULL);
-	if (ret < 0)
-		return ret;
-
 	/* video node */
 	vdev->fops = &rvin_fops;
 	vdev->v4l2_dev = &vin->v4l2_dev;
@@ -822,43 +817,6 @@ int rvin_v4l2_probe(struct rvin_dev *vin)
 	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
 		V4L2_CAP_READWRITE;
 
-	vin->src_pad_idx = 0;
-#if defined(CONFIG_MEDIA_CONTROLLER)
-	for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
-		if (sd->entity.pads[pad_idx].flags
-				== MEDIA_PAD_FL_SOURCE)
-			break;
-	if (pad_idx >= sd->entity.num_pads)
-		return -EINVAL;
-
-	vin->src_pad_idx = pad_idx;
-#endif
-	fmt.pad = vin->src_pad_idx;
-
-	/* Try to improve our guess of a reasonable window format */
-	ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
-	if (ret) {
-		vin_err(vin, "Failed to get initial format\n");
-		return ret;
-	}
-
-	/* Set default format */
-	vin->format.width	= mf->width;
-	vin->format.height	= mf->height;
-	vin->format.colorspace	= mf->colorspace;
-	vin->format.field	= mf->field;
-	vin->format.pixelformat	= RVIN_DEFAULT_FORMAT;
-
-
-	/* Set initial crop and compose */
-	vin->crop.top = vin->crop.left = 0;
-	vin->crop.width = mf->width;
-	vin->crop.height = mf->height;
-
-	vin->compose.top = vin->compose.left = 0;
-	vin->compose.width = mf->width;
-	vin->compose.height = mf->height;
-
 	ret = video_register_device(&vin->vdev, VFL_TYPE_GRABBER, -1);
 	if (ret) {
 		vin_err(vin, "Failed to register video device\n");
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index a6dd6db..81780f1 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -23,6 +23,8 @@
 #include <media/v4l2-device.h>
 #include <media/videobuf2-v4l2.h>
 
+#include "rcar-group.h"
+
 /* Number of HW buffers */
 #define HW_BUFFER_NUM 3
 
@@ -50,12 +52,10 @@ enum rvin_dma_state {
 
 /**
  * struct rvin_source_fmt - Source information
- * @code:	Media bus format from source
  * @width:	Width from source
  * @height:	Height from source
  */
 struct rvin_source_fmt {
-	u32 code;
 	u32 width;
 	u32 height;
 };
@@ -70,27 +70,18 @@ struct rvin_video_format {
 	u8 bpp;
 };
 
-struct rvin_graph_entity {
-	struct device_node *node;
-	struct media_entity *entity;
-
-	struct v4l2_async_subdev asd;
-	struct v4l2_subdev *subdev;
-};
-
 /**
  * struct rvin_dev - Renesas VIN device structure
  * @dev:		(OF) device
  * @base:		device I/O register space remapped to virtual memory
  * @chip:		type of VIN chip
- * @mbus_cfg		media bus configuration
  *
  * @vdev:		V4L2 video device associated with VIN
  * @v4l2_dev:		V4L2 device
  * @src_pad_idx:	source pad index for media controller drivers
  * @ctrl_handler:	V4L2 control handler
  * @notifier:		V4L2 asynchronous subdevs notifier
- * @entity:		entity in the DT for subdevice
+ * @digital:		entity in the DT for local digital subdevice
  *
  * @lock:		protects @queue
  * @queue:		vb2 buffers queue
@@ -109,19 +100,21 @@ struct rvin_graph_entity {
  *
  * @crop:		active cropping
  * @compose:		active composing
+ *
+ * @current_input:	currently used input in @inputs
+ * @inputs:		list of valid inputs sources
  */
 struct rvin_dev {
 	struct device *dev;
 	void __iomem *base;
 	enum chip_id chip;
-	struct v4l2_mbus_config mbus_cfg;
 
 	struct video_device vdev;
 	struct v4l2_device v4l2_dev;
 	int src_pad_idx;
 	struct v4l2_ctrl_handler ctrl_handler;
 	struct v4l2_async_notifier notifier;
-	struct rvin_graph_entity entity;
+	struct rvin_graph_entity digital;
 
 	struct mutex lock;
 	struct vb2_queue queue;
@@ -139,9 +132,10 @@ struct rvin_dev {
 
 	struct v4l2_rect crop;
 	struct v4l2_rect compose;
-};
 
-#define vin_to_source(vin)		vin->entity.subdev
+	int current_input;
+	struct rvin_input_item inputs[RVIN_INPUT_MAX];
+};
 
 /* Debug */
 #define vin_dbg(d, fmt, arg...)		dev_dbg(d->dev, fmt, ##arg)
@@ -162,4 +156,27 @@ void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
 		    u32 width, u32 height);
 void rvin_crop_scale_comp(struct rvin_dev *vin);
 
+/* Subdevice group helpers */
+#define rvin_subdev_call(v, o, f, args...)				\
+	(v->digital.subdev ?						\
+	 v4l2_subdev_call(v->digital.subdev, o, f, ##args) : -ENODEV)
+#define rvin_subdev_call_input(v, i, o, f, args...)			\
+	(v->digital.subdev ?						\
+	 v4l2_subdev_call(v->digital.subdev, o, f, ##args) : -ENODEV)
+
+#define rvin_subdev_has_op(v, o, f)					\
+	(v->digital.subdev ?						\
+	v4l2_subdev_has_op(v->digital.subdev, o, f) : -ENODEV)
+
+int rvin_subdev_get(struct rvin_dev *vin);
+int rvin_subdev_put(struct rvin_dev *vin);
+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);
+int rvin_subdev_get_mbus_cfg(struct rvin_dev *vin,
+			     struct v4l2_mbus_config *mbus_cfg);
+
+int rvin_subdev_ctrl_add_handler(struct rvin_dev *vin);
+struct v4l2_subdev_pad_config *rvin_subdev_alloc_pad_config(struct rvin_dev
+							    *vin);
 #endif
-- 
2.8.2



^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 5/8] [media] rcar-vin: add Gen3 HW registers
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
                   ` (3 preceding siblings ...)
  2016-05-25 19:10 ` [PATCH 4/8] [media] rcar-vin: allow subdevices to be bound late Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-06-16 16:52   ` Laurent Pinchart
  2016-05-25 19:10 ` [PATCH 6/8] [media] rcar-vin: add shared subdevice groups Niklas Söderlund
                   ` (2 subsequent siblings)
  7 siblings, 1 reply; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Add the register needed to work with Gen3 hardware. This patch just adds
the logic for how to work with the Gen3 hardware. More work is required
to enable the subdevice structure needed to support capturing.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/rcar-dma.c  | 99 +++++++++++++++++++----------
 drivers/media/platform/rcar-vin/rcar-v4l2.c | 15 ++++-
 drivers/media/platform/rcar-vin/rcar-vin.h  |  1 +
 3 files changed, 80 insertions(+), 35 deletions(-)

diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c b/drivers/media/platform/rcar-vin/rcar-dma.c
index b3d3c5e..5196395 100644
--- a/drivers/media/platform/rcar-vin/rcar-dma.c
+++ b/drivers/media/platform/rcar-vin/rcar-dma.c
@@ -33,21 +33,23 @@
 #define VNELPRC_REG	0x10	/* Video n End Line Pre-Clip Register */
 #define VNSPPRC_REG	0x14	/* Video n Start Pixel Pre-Clip Register */
 #define VNEPPRC_REG	0x18	/* Video n End Pixel Pre-Clip Register */
-#define VNSLPOC_REG	0x1C	/* Video n Start Line Post-Clip Register */
-#define VNELPOC_REG	0x20	/* Video n End Line Post-Clip Register */
-#define VNSPPOC_REG	0x24	/* Video n Start Pixel Post-Clip Register */
-#define VNEPPOC_REG	0x28	/* Video n End Pixel Post-Clip Register */
 #define VNIS_REG	0x2C	/* Video n Image Stride Register */
 #define VNMB_REG(m)	(0x30 + ((m) << 2)) /* Video n Memory Base m Register */
 #define VNIE_REG	0x40	/* Video n Interrupt Enable Register */
 #define VNINTS_REG	0x44	/* Video n Interrupt Status Register */
 #define VNSI_REG	0x48	/* Video n Scanline Interrupt Register */
 #define VNMTC_REG	0x4C	/* Video n Memory Transfer Control Register */
-#define VNYS_REG	0x50	/* Video n Y Scale Register */
-#define VNXS_REG	0x54	/* Video n X Scale Register */
 #define VNDMR_REG	0x58	/* Video n Data Mode Register */
 #define VNDMR2_REG	0x5C	/* Video n Data Mode Register 2 */
 #define VNUVAOF_REG	0x60	/* Video n UV Address Offset Register */
+
+/* Register offsets specific for Gen2 */
+#define VNSLPOC_REG	0x1C	/* Video n Start Line Post-Clip Register */
+#define VNELPOC_REG	0x20	/* Video n End Line Post-Clip Register */
+#define VNSPPOC_REG	0x24	/* Video n Start Pixel Post-Clip Register */
+#define VNEPPOC_REG	0x28	/* Video n End Pixel Post-Clip Register */
+#define VNYS_REG	0x50	/* Video n Y Scale Register */
+#define VNXS_REG	0x54	/* Video n X Scale Register */
 #define VNC1A_REG	0x80	/* Video n Coefficient Set C1A Register */
 #define VNC1B_REG	0x84	/* Video n Coefficient Set C1B Register */
 #define VNC1C_REG	0x88	/* Video n Coefficient Set C1C Register */
@@ -73,9 +75,13 @@
 #define VNC8B_REG	0xF4	/* Video n Coefficient Set C8B Register */
 #define VNC8C_REG	0xF8	/* Video n Coefficient Set C8C Register */
 
+/* Register offsets specific for Gen3 */
+#define VNCSI_IFMD_REG		0x20 /* Video n CSI2 Interface Mode Register */
 
 /* Register bit fields for R-Car VIN */
 /* Video n Main Control Register bits */
+#define VNMC_DPINE		(1 << 27) /* Gen3 specific */
+#define VNMC_SCLE		(1 << 26) /* Gen3 specific */
 #define VNMC_FOC		(1 << 21)
 #define VNMC_YCAL		(1 << 19)
 #define VNMC_INF_YUV8_BT656	(0 << 16)
@@ -118,6 +124,12 @@
 #define VNDMR2_FTEV		(1 << 17)
 #define VNDMR2_VLV(n)		((n & 0xf) << 12)
 
+/* Video n CSI2 Interface Mode Register (Gen3) */
+#define VNCSI_IFMD_DES2		(1 << 27)
+#define VNCSI_IFMD_DES1		(1 << 26)
+#define VNCSI_IFMD_DES0		(1 << 25)
+#define VNCSI_IFMD_CSI_CHSEL(n) ((n & 0xf) << 0)
+
 static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset)
 {
 	iowrite32(value, vin->base + offset);
@@ -196,7 +208,10 @@ static int rvin_setup(struct rvin_dev *vin)
 	}
 
 	/* Enable VSYNC Field Toogle mode after one VSYNC input */
-	dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
+	if (vin->chip == RCAR_GEN3)
+		dmr2 = VNDMR2_FTEV;
+	else
+		dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
 
 	/* Hsync Signal Polarity Select */
 	if (!(mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
@@ -232,7 +247,8 @@ static int rvin_setup(struct rvin_dev *vin)
 		dmr = 0;
 		break;
 	case V4L2_PIX_FMT_XBGR32:
-		if (vin->chip == RCAR_GEN2 || vin->chip == RCAR_H1) {
+		if (vin->chip == RCAR_GEN2 || vin->chip == RCAR_H1 ||
+		    vin->chip == RCAR_GEN3) {
 			dmr = VNDMR_EXRGB;
 			break;
 		}
@@ -250,6 +266,14 @@ static int rvin_setup(struct rvin_dev *vin)
 	if (input_is_yuv == output_is_yuv)
 		vnmc |= VNMC_BPS;
 
+	if (vin->chip == RCAR_GEN3) {
+		/* Select between CSI-2 and Digital input */
+		if (mbus_cfg.type == V4L2_MBUS_CSI2)
+			vnmc &= ~VNMC_DPINE;
+		else
+			vnmc |= VNMC_DPINE;
+	}
+
 	/* Progressive or interlaced mode */
 	interrupts = progressive ? VNIE_FIE : VNIE_EFE;
 
@@ -740,28 +764,10 @@ static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs)
 	rvin_write(vin, p_set->coeff_set[23], VNC8C_REG);
 }
 
-void rvin_crop_scale_comp(struct rvin_dev *vin)
+static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin)
 {
 	u32 xs, ys;
 
-	/* Set Start/End Pixel/Line Pre-Clip */
-	rvin_write(vin, vin->crop.left, VNSPPRC_REG);
-	rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
-	switch (vin->format.field) {
-	case V4L2_FIELD_INTERLACED:
-	case V4L2_FIELD_INTERLACED_TB:
-	case V4L2_FIELD_INTERLACED_BT:
-		rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
-		rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
-			   VNELPRC_REG);
-		break;
-	default:
-		rvin_write(vin, vin->crop.top, VNSLPRC_REG);
-		rvin_write(vin, vin->crop.top + vin->crop.height - 1,
-			   VNELPRC_REG);
-		break;
-	}
-
 	/* Set scaling coefficient */
 	ys = 0;
 	if (vin->crop.height != vin->compose.height)
@@ -799,11 +805,6 @@ void rvin_crop_scale_comp(struct rvin_dev *vin)
 		break;
 	}
 
-	if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
-		rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
-	else
-		rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
-
 	vin_dbg(vin,
 		"Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n",
 		vin->crop.width, vin->crop.height, vin->crop.left,
@@ -811,9 +812,43 @@ void rvin_crop_scale_comp(struct rvin_dev *vin)
 		0, 0);
 }
 
+void rvin_crop_scale_comp(struct rvin_dev *vin)
+{
+	/* Set Start/End Pixel/Line Pre-Clip */
+	rvin_write(vin, vin->crop.left, VNSPPRC_REG);
+	rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
+	switch (vin->format.field) {
+	case V4L2_FIELD_INTERLACED:
+	case V4L2_FIELD_INTERLACED_TB:
+	case V4L2_FIELD_INTERLACED_BT:
+		rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
+		rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
+			   VNELPRC_REG);
+		break;
+	default:
+		rvin_write(vin, vin->crop.top, VNSLPRC_REG);
+		rvin_write(vin, vin->crop.top + vin->crop.height - 1,
+			   VNELPRC_REG);
+		break;
+	}
+
+	/* Driver do not support UDS */
+	if (vin->chip != RCAR_GEN3)
+		rvin_crop_scale_comp_gen2(vin);
+
+	if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
+		rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
+	else
+		rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
+}
+
 void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
 		    u32 width, u32 height)
 {
+	/* Driver do not support UDS */
+	if (vin->chip == RCAR_GEN3)
+		return;
+
 	/* All VIN channels on Gen2 have scalers */
 	pix->width = width;
 	pix->height = height;
diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c b/drivers/media/platform/rcar-vin/rcar-v4l2.c
index 2307f5b..f4eaef0 100644
--- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
+++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
@@ -25,6 +25,8 @@
 #define RVIN_DEFAULT_FORMAT	V4L2_PIX_FMT_YUYV
 #define RVIN_MAX_WIDTH		2048
 #define RVIN_MAX_HEIGHT		2048
+#define RVIN_MAX_WIDTH_GEN3	4096
+#define RVIN_MAX_HEIGHT_GEN3	4096
 
 /* -----------------------------------------------------------------------------
  * Format Conversions
@@ -139,7 +141,7 @@ static int __rvin_try_format(struct rvin_dev *vin,
 			     struct rvin_source_fmt *source)
 {
 	const struct rvin_video_format *info;
-	u32 rwidth, rheight, walign;
+	u32 rwidth, rheight, walign, max_width, max_height;
 
 	/* Requested */
 	rwidth = pix->width;
@@ -173,8 +175,15 @@ static int __rvin_try_format(struct rvin_dev *vin,
 	walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1;
 
 	/* Limit to VIN capabilities */
-	v4l_bound_align_image(&pix->width, 2, RVIN_MAX_WIDTH, walign,
-			      &pix->height, 4, RVIN_MAX_HEIGHT, 2, 0);
+	if (vin->chip == RCAR_GEN3) {
+		max_width = RVIN_MAX_WIDTH_GEN3;
+		max_height = RVIN_MAX_HEIGHT_GEN3;
+	} else {
+		max_width = RVIN_MAX_WIDTH;
+		max_height = RVIN_MAX_HEIGHT;
+	}
+	v4l_bound_align_image(&pix->width, 2, max_width, walign,
+			      &pix->height, 4, max_height, 2, 0);
 
 	switch (pix->field) {
 	case V4L2_FIELD_NONE:
diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h b/drivers/media/platform/rcar-vin/rcar-vin.h
index 81780f1..b97fa43 100644
--- a/drivers/media/platform/rcar-vin/rcar-vin.h
+++ b/drivers/media/platform/rcar-vin/rcar-vin.h
@@ -32,6 +32,7 @@
 #define HW_BUFFER_MASK 0x7f
 
 enum chip_id {
+	RCAR_GEN3,
 	RCAR_GEN2,
 	RCAR_H1,
 	RCAR_M1,
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 6/8] [media] rcar-vin: add shared subdevice groups
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
                   ` (4 preceding siblings ...)
  2016-05-25 19:10 ` [PATCH 5/8] [media] rcar-vin: add Gen3 HW registers Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 7/8] [media] rcar-vin: enable Gen3 Niklas Söderlund
  2016-05-25 19:10 ` [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings Niklas Söderlund
  7 siblings, 0 replies; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

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 --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);
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 7/8] [media] rcar-vin: enable Gen3
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
                   ` (5 preceding siblings ...)
  2016-05-25 19:10 ` [PATCH 6/8] [media] rcar-vin: add shared subdevice groups Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-06-16 16:55   ` Laurent Pinchart
  2016-05-25 19:10 ` [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings Niklas Söderlund
  7 siblings, 1 reply; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/Kconfig     | 2 +-
 drivers/media/platform/rcar-vin/rcar-core.c | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/rcar-vin/Kconfig b/drivers/media/platform/rcar-vin/Kconfig
index b2ff2d4..ca3ea91 100644
--- a/drivers/media/platform/rcar-vin/Kconfig
+++ b/drivers/media/platform/rcar-vin/Kconfig
@@ -5,7 +5,7 @@ config VIDEO_RCAR_VIN
 	select VIDEOBUF2_DMA_CONTIG
 	---help---
 	  Support for Renesas R-Car Video Input (VIN) driver.
-	  Supports R-Car Gen2 SoCs.
+	  Supports R-Car Gen2 and Gen3 SoCs.
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called rcar-vin.
diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index d901ad0..520690c 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -26,6 +26,7 @@
 #include "rcar-vin.h"
 
 static const struct of_device_id rvin_of_id_table[] = {
+	{ .compatible = "renesas,vin-r8a7795", .data = (void *)RCAR_GEN3 },
 	{ .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
 	{ .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
 	{ .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings
  2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
                   ` (6 preceding siblings ...)
  2016-05-25 19:10 ` [PATCH 7/8] [media] rcar-vin: enable Gen3 Niklas Söderlund
@ 2016-05-25 19:10 ` Niklas Söderlund
  2016-05-25 19:36   ` Sergei Shtylyov
  2016-06-16 16:55   ` Laurent Pinchart
  7 siblings, 2 replies; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-25 19:10 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

These are present in the soc-camera version of this driver and it's time
to add them to this driver as well.

Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
---
 drivers/media/platform/rcar-vin/rcar-core.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
index 520690c..87041db 100644
--- a/drivers/media/platform/rcar-vin/rcar-core.c
+++ b/drivers/media/platform/rcar-vin/rcar-core.c
@@ -33,6 +33,8 @@ static const struct of_device_id rvin_of_id_table[] = {
 	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
 	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
 	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
+	{ .compatible = "renesas,rcar-gen3-vin", .data = (void *)RCAR_GEN3 },
+	{ .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
 	{ },
 };
 MODULE_DEVICE_TABLE(of, rvin_of_id_table);
-- 
2.8.2


^ permalink raw reply related	[flat|nested] 18+ messages in thread

* Re: [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings
  2016-05-25 19:10 ` [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings Niklas Söderlund
@ 2016-05-25 19:36   ` Sergei Shtylyov
  2016-05-27 11:36     ` Niklas Söderlund
  2016-06-16 16:55   ` Laurent Pinchart
  1 sibling, 1 reply; 18+ messages in thread
From: Sergei Shtylyov @ 2016-05-25 19:36 UTC (permalink / raw)
  To: Niklas Söderlund, linux-media, ulrich.hecht, hverkuil
  Cc: linux-renesas-soc, Niklas Söderlund

On 05/25/2016 10:10 PM, Niklas Söderlund wrote:

> From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
>
> These are present in the soc-camera version of this driver and it's time
> to add them to this driver as well.
>
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  drivers/media/platform/rcar-vin/rcar-core.c | 2 ++
>  1 file changed, 2 insertions(+)
>
> diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
> index 520690c..87041db 100644
> --- a/drivers/media/platform/rcar-vin/rcar-core.c
> +++ b/drivers/media/platform/rcar-vin/rcar-core.c
> @@ -33,6 +33,8 @@ static const struct of_device_id rvin_of_id_table[] = {
>  	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
>  	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
>  	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
> +	{ .compatible = "renesas,rcar-gen3-vin", .data = (void *)RCAR_GEN3 },
> +	{ .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },

    What's the point of adding the H3 specific compatibility string in the 
previous patch then? The fallback stings were added not have to updated the 
driver for every new SoC exactly.

MBR, Sergei


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings
  2016-05-25 19:36   ` Sergei Shtylyov
@ 2016-05-27 11:36     ` Niklas Söderlund
  2016-05-27 18:18       ` Sergei Shtylyov
  0 siblings, 1 reply; 18+ messages in thread
From: Niklas Söderlund @ 2016-05-27 11:36 UTC (permalink / raw)
  To: Sergei Shtylyov; +Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc

Hi Sergei,

On 2016-05-25 22:36:02 +0300, Sergei Shtylyov wrote:
> On 05/25/2016 10:10 PM, Niklas Söderlund wrote:
> 
> > From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > 
> > These are present in the soc-camera version of this driver and it's time
> > to add them to this driver as well.
> > 
> > Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> > ---
> >  drivers/media/platform/rcar-vin/rcar-core.c | 2 ++
> >  1 file changed, 2 insertions(+)
> > 
> > diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
> > index 520690c..87041db 100644
> > --- a/drivers/media/platform/rcar-vin/rcar-core.c
> > +++ b/drivers/media/platform/rcar-vin/rcar-core.c
> > @@ -33,6 +33,8 @@ static const struct of_device_id rvin_of_id_table[] = {
> >  	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
> >  	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
> >  	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
> > +	{ .compatible = "renesas,rcar-gen3-vin", .data = (void *)RCAR_GEN3 },
> > +	{ .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
> 
>    What's the point of adding the H3 specific compatibility string in the
> previous patch then? The fallback stings were added not have to updated the
> driver for every new SoC exactly.

Since this driver aims to replace the previous R-Car VIN driver which 
uses soc-camera I think it also should contain all the compatibility 
strings that the soc-camera driver do.

Other then that I have no strong opining and are happy to drop the 
previous patch if the intended use of the fallback strings are to use 
them over a SoC specific one.

-- 
Regards,
Niklas Söderlund

^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings
  2016-05-27 11:36     ` Niklas Söderlund
@ 2016-05-27 18:18       ` Sergei Shtylyov
  0 siblings, 0 replies; 18+ messages in thread
From: Sergei Shtylyov @ 2016-05-27 18:18 UTC (permalink / raw)
  To: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc

Hello.

On 05/27/2016 02:36 PM, Niklas Söderlund wrote:

>>> From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
>>>
>>> These are present in the soc-camera version of this driver and it's time
>>> to add them to this driver as well.
>>>
>>> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
>>> ---
>>>  drivers/media/platform/rcar-vin/rcar-core.c | 2 ++
>>>  1 file changed, 2 insertions(+)
>>>
>>> diff --git a/drivers/media/platform/rcar-vin/rcar-core.c b/drivers/media/platform/rcar-vin/rcar-core.c
>>> index 520690c..87041db 100644
>>> --- a/drivers/media/platform/rcar-vin/rcar-core.c
>>> +++ b/drivers/media/platform/rcar-vin/rcar-core.c
>>> @@ -33,6 +33,8 @@ static const struct of_device_id rvin_of_id_table[] = {
>>>  	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
>>>  	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
>>>  	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
>>> +	{ .compatible = "renesas,rcar-gen3-vin", .data = (void *)RCAR_GEN3 },
>>> +	{ .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
>>
>>    What's the point of adding the H3 specific compatibility string in the
>> previous patch then? The fallback stings were added not have to updated the
>> driver for every new SoC exactly.
>
> Since this driver aims to replace the previous R-Car VIN driver which
> uses soc-camera I think it also should contain all the compatibility
> strings that the soc-camera driver do.

    Indeed. And I'm not seeing the gen2/3 strings there yet (I thought Simon 
had already pushed them there). Nevermind then.

MBR, Sergei


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation
  2016-05-25 19:10 ` [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation Niklas Söderlund
@ 2016-06-16 14:56   ` Laurent Pinchart
  0 siblings, 0 replies; 18+ messages in thread
From: Laurent Pinchart @ 2016-06-16 14:56 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc,
	Ulrich Hecht, William Towle, Rob Taylor, Niklas Söderlund

Hello Niklas,

Thank you for the patch.

On Wednesday 25 May 2016 21:10:02 Niklas Söderlund wrote:
> From: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
> 
> Add detection of source pad number for drivers aware of the media controller
> API, so that rcar-vin can create device nodes to support modern drivers
> such as adv7604.c (for HDMI on Lager) and the converted adv7180.c (for
> composite) underneath.
> 
> Building rcar_vin gains a dependency on CONFIG_MEDIA_CONTROLLER, in
> line with requirements for building the drivers associated with it.
> 
> Signed-off-by: William Towle <william.towle@codethink.co.uk>
> Signed-off-by: Rob Taylor <rob.taylor@codethink.co.uk>
> [uli: adapted to rcar-vin rewrite]
> Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  drivers/media/platform/rcar-vin/rcar-v4l2.c | 16 ++++++++++++++++
>  drivers/media/platform/rcar-vin/rcar-vin.h  |  2 ++
>  2 files changed, 18 insertions(+)
> 
> diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 0bc4487..929816b 100644
> --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
> @@ -683,6 +683,9 @@ int rvin_v4l2_probe(struct rvin_dev *vin)
>  	struct v4l2_mbus_framefmt *mf = &fmt.format;
>  	struct video_device *vdev = &vin->vdev;
>  	struct v4l2_subdev *sd = vin_to_source(vin);
> +#if defined(CONFIG_MEDIA_CONTROLLER)

I think you can get rid of conditional compilation here and below. Patch 2/8 
calls v4l2_subdev_alloc_pad_config() unconditionally, which depends on 
CONFIG_MEDIA_CONTROLLER.

> +	int pad_idx;
> +#endif
>  	int ret;
> 
>  	v4l2_set_subdev_hostdata(sd, vin);
> @@ -729,6 +732,19 @@ int rvin_v4l2_probe(struct rvin_dev *vin)
>  	vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING |
>  		V4L2_CAP_READWRITE;
> 
> +	vin->src_pad_idx = 0;
> +#if defined(CONFIG_MEDIA_CONTROLLER)
> +	for (pad_idx = 0; pad_idx < sd->entity.num_pads; pad_idx++)
> +		if (sd->entity.pads[pad_idx].flags
> +				== MEDIA_PAD_FL_SOURCE)

No need for a line break.

> +			break;
> +	if (pad_idx >= sd->entity.num_pads)
> +		return -EINVAL;
> +
> +	vin->src_pad_idx = pad_idx;
> +#endif
> +	fmt.pad = vin->src_pad_idx;
> +
>  	/* Try to improve our guess of a reasonable window format */
>  	ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt);
>  	if (ret) {
> diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h
> b/drivers/media/platform/rcar-vin/rcar-vin.h index 544a3b3..a6dd6db 100644
> --- a/drivers/media/platform/rcar-vin/rcar-vin.h
> +++ b/drivers/media/platform/rcar-vin/rcar-vin.h
> @@ -87,6 +87,7 @@ struct rvin_graph_entity {
>   *
>   * @vdev:		V4L2 video device associated with VIN
>   * @v4l2_dev:		V4L2 device
> + * @src_pad_idx:	source pad index for media controller drivers
>   * @ctrl_handler:	V4L2 control handler
>   * @notifier:		V4L2 asynchronous subdevs notifier
>   * @entity:		entity in the DT for subdevice
> @@ -117,6 +118,7 @@ struct rvin_dev {
> 
>  	struct video_device vdev;
>  	struct v4l2_device v4l2_dev;
> +	int src_pad_idx;
>  	struct v4l2_ctrl_handler ctrl_handler;
>  	struct v4l2_async_notifier notifier;
>  	struct rvin_graph_entity entity;

-- 
Regards,

Laurent Pinchart


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 2/8] media: rcar_vin: Use correct pad number in try_fmt
  2016-05-25 19:10 ` [PATCH 2/8] media: rcar_vin: Use correct pad number in try_fmt Niklas Söderlund
@ 2016-06-16 14:56   ` Laurent Pinchart
  0 siblings, 0 replies; 18+ messages in thread
From: Laurent Pinchart @ 2016-06-16 14:56 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc,
	Ulrich Hecht, William Towle, Niklas Söderlund

Hello Niklas,

Thank you for the patch.

On Wednesday 25 May 2016 21:10:03 Niklas Söderlund wrote:
> From: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
> 
> Fix rcar_vin_try_fmt's use of an inappropriate pad number when calling
> the subdev set_fmt function - for the ADV7612, IDs should be non-zero.
> 
> Signed-off-by: William Towle <william.towle@codethink.co.uk>
> Reviewed-by: Rob Taylor <rob.taylor@codethink.co.uk>
> Acked-by: Hans Verkuil <hans.verkuil@cisco.com>
> [uli: adapted to rcar-vin rewrite]
> Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  drivers/media/platform/rcar-vin/rcar-v4l2.c | 14 +++++++++++---
>  1 file changed, 11 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 929816b..3788f8a 100644
> --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
> @@ -98,7 +98,7 @@ static int __rvin_try_format_source(struct rvin_dev *vin,
>  					struct rvin_source_fmt *source)
>  {
>  	struct v4l2_subdev *sd;
> -	struct v4l2_subdev_pad_config pad_cfg;
> +	struct v4l2_subdev_pad_config *pad_cfg;
>  	struct v4l2_subdev_format format = {
>  		.which = which,
>  	};
> @@ -108,10 +108,16 @@ static int __rvin_try_format_source(struct rvin_dev
> *vin,
> 
>  	v4l2_fill_mbus_format(&format.format, pix, vin->source.code);
> 
> +	pad_cfg = v4l2_subdev_alloc_pad_config(sd);
> +	if (pad_cfg == NULL)
> +		return -ENOMEM;
> +
> +	format.pad = vin->src_pad_idx;
> +
>  	ret = v4l2_device_call_until_err(sd->v4l2_dev, 0, pad, set_fmt,
> -					 &pad_cfg, &format);
> +					 pad_cfg, &format);

pad_cfg is subdev-specific, you can't use v4l2_device_call_until_err(). You 
should use v4l2_subdev_call() instead. This will obviously not be enough if we 
have more than one subdev in the pipeline, but the code is broken in that case 
anyway.

>  	if (ret < 0)
> -		return ret;
> +		goto cleanup;
> 
>  	v4l2_fill_pix_format(pix, &format.format);
> 
> @@ -121,6 +127,8 @@ static int __rvin_try_format_source(struct rvin_dev
> *vin, vin_dbg(vin, "Source resolution: %ux%u\n", source->width,
>  		source->height);
> 
> +cleanup:

Nitpicking, I'd name the label "done".

> +	v4l2_subdev_free_pad_config(pad_cfg);
>  	return 0;
>  }

-- 
Regards,

Laurent Pinchart


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 3/8] media: rcar-vin: add DV timings support
  2016-05-25 19:10 ` [PATCH 3/8] media: rcar-vin: add DV timings support Niklas Söderlund
@ 2016-06-16 15:05   ` Laurent Pinchart
  0 siblings, 0 replies; 18+ messages in thread
From: Laurent Pinchart @ 2016-06-16 15:05 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc,
	Ulrich Hecht, Niklas Söderlund

Hello Niklas,

Thank you for the patch.

On Wednesday 25 May 2016 21:10:04 Niklas Söderlund wrote:
> From: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
> 
> Adds ioctls DV_TIMINGS_CAP, ENUM_DV_TIMINGS, G_DV_TIMINGS, S_DV_TIMINGS,
> and QUERY_DV_TIMINGS.
> 
> Signed-off-by: Ulrich Hecht <ulrich.hecht+renesas@gmail.com>
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  drivers/media/platform/rcar-vin/rcar-v4l2.c | 82 ++++++++++++++++++++++++++
>  1 file changed, 82 insertions(+)
> 
> diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 3788f8a..10a5c10 100644
> --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
> @@ -400,6 +400,10 @@ static int rvin_enum_input(struct file *file, void
> *priv,
> 
>  	i->type = V4L2_INPUT_TYPE_CAMERA;
>  	i->std = vin->vdev.tvnorms;
> +
> +	if (v4l2_subdev_has_op(sd, pad, dv_timings_cap))
> +		i->capabilities = V4L2_IN_CAP_DV_TIMINGS;
> +
>  	strlcpy(i->name, "Camera", sizeof(i->name));
> 
>  	return 0;
> @@ -478,6 +482,78 @@ static int rvin_subscribe_event(struct v4l2_fh *fh,
>  	return v4l2_ctrl_subscribe_event(fh, sub);
>  }
> 
> +static int rvin_enum_dv_timings(struct file *file, void *priv_fh,
> +				    struct v4l2_enum_dv_timings *timings)
> +{
> +	struct rvin_dev *vin = video_drvdata(file);
> +	struct v4l2_subdev *sd = vin_to_source(vin);
> +	int pad, ret;

pad can't be negative, you can make it an unsigned int.

	unsigned int pad = timings->pad;
	int ret;

	timings->pad = vin->src_pad_idx;

> +
> +	pad = timings->pad;
> +	timings->pad = vin->src_pad_idx;
> +
> +	ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings);
> +
> +	timings->pad = pad;
> +
> +	return ret;
> +}
> +
> +static int rvin_s_dv_timings(struct file *file, void *priv_fh,
> +				    struct v4l2_dv_timings *timings)
> +{
> +	struct rvin_dev *vin = video_drvdata(file);
> +	struct v4l2_subdev *sd = vin_to_source(vin);
> +	int err;

The driver uses ret instead of err, let's keep it that way.

> +
> +	err = v4l2_subdev_call(sd,
> +			video, s_dv_timings, timings);

No need for a line break.

> +	if (!err) {

I'd write this

	if (ret)
		return ret;

(with a return 0; at the end of the function) to lower the indentation level 
of the code below.

> +		vin->source.width = timings->bt.width;
> +		vin->source.height = timings->bt.height;
> +		vin->format.width = timings->bt.width;
> +		vin->format.height = timings->bt.height;
> +	}
> +	return err;
> +}
> +
> +static int rvin_g_dv_timings(struct file *file, void *priv_fh,
> +				    struct v4l2_dv_timings *timings)
> +{
> +	struct rvin_dev *vin = video_drvdata(file);
> +	struct v4l2_subdev *sd = vin_to_source(vin);
> +
> +	return v4l2_subdev_call(sd,
> +			video, g_dv_timings, timings);

No need for a line break.

> +}
> +
> +static int rvin_query_dv_timings(struct file *file, void *priv_fh,
> +				    struct v4l2_dv_timings *timings)
> +{
> +	struct rvin_dev *vin = video_drvdata(file);
> +	struct v4l2_subdev *sd = vin_to_source(vin);
> +
> +	return v4l2_subdev_call(sd,
> +			video, query_dv_timings, timings);

No need for a line break.

> +}
> +
> +static int rvin_dv_timings_cap(struct file *file, void *priv_fh,
> +				    struct v4l2_dv_timings_cap *cap)
> +{
> +	struct rvin_dev *vin = video_drvdata(file);
> +	struct v4l2_subdev *sd = vin_to_source(vin);
> +	int pad, ret;

Same comment as above about pad not being negative.

> +
> +	pad = cap->pad;
> +	cap->pad = vin->src_pad_idx;
> +
> +	ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap);
> +
> +	cap->pad = pad;
> +
> +	return ret;
> +}
> +
>  static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
>  	.vidioc_querycap		= rvin_querycap,
>  	.vidioc_try_fmt_vid_cap		= rvin_try_fmt_vid_cap,
> @@ -494,6 +570,12 @@ static const struct v4l2_ioctl_ops rvin_ioctl_ops = {
>  	.vidioc_g_input			= rvin_g_input,
>  	.vidioc_s_input			= rvin_s_input,
> 
> +	.vidioc_dv_timings_cap		= rvin_dv_timings_cap,
> +	.vidioc_enum_dv_timings		= rvin_enum_dv_timings,
> +	.vidioc_g_dv_timings		= rvin_g_dv_timings,
> +	.vidioc_s_dv_timings		= rvin_s_dv_timings,
> +	.vidioc_query_dv_timings	= rvin_query_dv_timings,
> +
>  	.vidioc_querystd		= rvin_querystd,
>  	.vidioc_g_std			= rvin_g_std,
>  	.vidioc_s_std			= rvin_s_std,

-- 
Regards,

Laurent Pinchart


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 5/8] [media] rcar-vin: add Gen3 HW registers
  2016-05-25 19:10 ` [PATCH 5/8] [media] rcar-vin: add Gen3 HW registers Niklas Söderlund
@ 2016-06-16 16:52   ` Laurent Pinchart
  0 siblings, 0 replies; 18+ messages in thread
From: Laurent Pinchart @ 2016-06-16 16:52 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc,
	Niklas Söderlund

Hi Niklas,

Thank you for the patch.

On Wednesday 25 May 2016 21:10:06 Niklas Söderlund wrote:
> From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> 
> Add the register needed to work with Gen3 hardware. This patch just adds
> the logic for how to work with the Gen3 hardware. More work is required
> to enable the subdevice structure needed to support capturing.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  drivers/media/platform/rcar-vin/rcar-dma.c  | 99 ++++++++++++++++----------
>  drivers/media/platform/rcar-vin/rcar-v4l2.c | 15 ++++-
>  drivers/media/platform/rcar-vin/rcar-vin.h  |  1 +
>  3 files changed, 80 insertions(+), 35 deletions(-)
> 
> diff --git a/drivers/media/platform/rcar-vin/rcar-dma.c
> b/drivers/media/platform/rcar-vin/rcar-dma.c index b3d3c5e..5196395 100644
> --- a/drivers/media/platform/rcar-vin/rcar-dma.c
> +++ b/drivers/media/platform/rcar-vin/rcar-dma.c
> @@ -33,21 +33,23 @@
>  #define VNELPRC_REG	0x10	/* Video n End Line Pre-Clip Register */
>  #define VNSPPRC_REG	0x14	/* Video n Start Pixel Pre-Clip Register */
>  #define VNEPPRC_REG	0x18	/* Video n End Pixel Pre-Clip Register */

By the way, that's not directly related to this patch, but hex constants in 
the kernel tend to use lowercase letters.

Also, feel free to give a bit more air to the register definitions even if it 
makes them cross the 80 characters per line limit, if it helps improving 
readability. If it was up to me I'd add one tab before the register address 
for instance to align them with the register fields values a bit further 
below.

Still from a readability point of view, I would extend the register address to 
4 (or just 3 if you prefer) hex digits as we have registers above the first 
0x100 bytes.

> -#define VNSLPOC_REG	0x1C	/* Video n Start Line Post-Clip Register */
> -#define VNELPOC_REG	0x20	/* Video n End Line Post-Clip Register */
> -#define VNSPPOC_REG	0x24	/* Video n Start Pixel Post-Clip Register */
> -#define VNEPPOC_REG	0x28	/* Video n End Pixel Post-Clip Register */
>  #define VNIS_REG	0x2C	/* Video n Image Stride Register */
>  #define VNMB_REG(m)	(0x30 + ((m) << 2)) /* Video n Memory Base m Register
> */
> #define VNIE_REG	0x40	/* Video n Interrupt Enable Register */
>  #define VNINTS_REG	0x44	/* Video n Interrupt Status Register */
>  #define VNSI_REG	0x48	/* Video n Scanline Interrupt Register */
>  #define VNMTC_REG	0x4C	/* Video n Memory Transfer Control Register */
> -#define VNYS_REG	0x50	/* Video n Y Scale Register */
> -#define VNXS_REG	0x54	/* Video n X Scale Register */
>  #define VNDMR_REG	0x58	/* Video n Data Mode Register */
>  #define VNDMR2_REG	0x5C	/* Video n Data Mode Register 2 */
>  #define VNUVAOF_REG	0x60	/* Video n UV Address Offset Register */
> +
> +/* Register offsets specific for Gen2 */
> +#define VNSLPOC_REG	0x1C	/* Video n Start Line Post-Clip Register */
> +#define VNELPOC_REG	0x20	/* Video n End Line Post-Clip Register */
> +#define VNSPPOC_REG	0x24	/* Video n Start Pixel Post-Clip Register */
> +#define VNEPPOC_REG	0x28	/* Video n End Pixel Post-Clip Register */
> +#define VNYS_REG	0x50	/* Video n Y Scale Register */
> +#define VNXS_REG	0x54	/* Video n X Scale Register */
>  #define VNC1A_REG	0x80	/* Video n Coefficient Set C1A Register */
>  #define VNC1B_REG	0x84	/* Video n Coefficient Set C1B Register */
>  #define VNC1C_REG	0x88	/* Video n Coefficient Set C1C Register */
> @@ -73,9 +75,13 @@
>  #define VNC8B_REG	0xF4	/* Video n Coefficient Set C8B Register */
>  #define VNC8C_REG	0xF8	/* Video n Coefficient Set C8C Register */
> 
> +/* Register offsets specific for Gen3 */
> +#define VNCSI_IFMD_REG		0x20 /* Video n CSI2 Interface Mode Register */
> 
>  /* Register bit fields for R-Car VIN */
>  /* Video n Main Control Register bits */
> +#define VNMC_DPINE		(1 << 27) /* Gen3 specific */
> +#define VNMC_SCLE		(1 << 26) /* Gen3 specific */
>  #define VNMC_FOC		(1 << 21)
>  #define VNMC_YCAL		(1 << 19)
>  #define VNMC_INF_YUV8_BT656	(0 << 16)
> @@ -118,6 +124,12 @@
>  #define VNDMR2_FTEV		(1 << 17)
>  #define VNDMR2_VLV(n)		((n & 0xf) << 12)
> 
> +/* Video n CSI2 Interface Mode Register (Gen3) */
> +#define VNCSI_IFMD_DES2		(1 << 27)
> +#define VNCSI_IFMD_DES1		(1 << 26)
> +#define VNCSI_IFMD_DES0		(1 << 25)
> +#define VNCSI_IFMD_CSI_CHSEL(n) ((n & 0xf) << 0)
> +
>  static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset)
>  {
>  	iowrite32(value, vin->base + offset);
> @@ -196,7 +208,10 @@ static int rvin_setup(struct rvin_dev *vin)
>  	}
> 
>  	/* Enable VSYNC Field Toogle mode after one VSYNC input */
> -	dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
> +	if (vin->chip == RCAR_GEN3)

I'm not too fond of scattering this kind of check across the code. My 
preferred approach is to add a device info structure that stores information 
about specific device models, and use it as appropriate. You can check the 
vsp1 or rcar-du driver for examples.

This being said, given that you have a limited number of such checks, there's 
no need to change this now, but please keep that in mind for the future when 
more code will be conditioned to the device model.

On a side note, I would rename the field from chip to model, and the values 
from RCAR_* to RCAR_VIN_*, as what matters is the VIN model, not the SoC.

> +		dmr2 = VNDMR2_FTEV;
> +	else
> +		dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1);
> 
>  	/* Hsync Signal Polarity Select */
>  	if (!(mbus_cfg.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW))
> @@ -232,7 +247,8 @@ static int rvin_setup(struct rvin_dev *vin)
>  		dmr = 0;
>  		break;
>  	case V4L2_PIX_FMT_XBGR32:
> -		if (vin->chip == RCAR_GEN2 || vin->chip == RCAR_H1) {
> +		if (vin->chip == RCAR_GEN2 || vin->chip == RCAR_H1 ||
> +		    vin->chip == RCAR_GEN3) {

If V4L2_PIX_FMT_XBGR32 isn't allowed on M1, VIDIOC_S_FMT and VIDIOC_TRY_FMT 
should disallow it. This isn't the right place for error checking.

I know the problem isn't introduced by this patch, so you can fix it in a 
separate patch. Ideally I would fix the problem first, which would get rid of 
this check, and then apply this patch with this hunk removed.

>  			dmr = VNDMR_EXRGB;
>  			break;
>  		}
> @@ -250,6 +266,14 @@ static int rvin_setup(struct rvin_dev *vin)
>  	if (input_is_yuv == output_is_yuv)
>  		vnmc |= VNMC_BPS;
> 
> +	if (vin->chip == RCAR_GEN3) {
> +		/* Select between CSI-2 and Digital input */
> +		if (mbus_cfg.type == V4L2_MBUS_CSI2)
> +			vnmc &= ~VNMC_DPINE;
> +		else
> +			vnmc |= VNMC_DPINE;
> +	}
> +
>  	/* Progressive or interlaced mode */
>  	interrupts = progressive ? VNIE_FIE : VNIE_EFE;
> 
> @@ -740,28 +764,10 @@ static void rvin_set_coeff(struct rvin_dev *vin,
> unsigned short xs) rvin_write(vin, p_set->coeff_set[23], VNC8C_REG);
>  }
> 
> -void rvin_crop_scale_comp(struct rvin_dev *vin)
> +static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin)
>  {
>  	u32 xs, ys;
> 
> -	/* Set Start/End Pixel/Line Pre-Clip */
> -	rvin_write(vin, vin->crop.left, VNSPPRC_REG);
> -	rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);
> -	switch (vin->format.field) {
> -	case V4L2_FIELD_INTERLACED:
> -	case V4L2_FIELD_INTERLACED_TB:
> -	case V4L2_FIELD_INTERLACED_BT:
> -		rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
> -		rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
> -			   VNELPRC_REG);
> -		break;
> -	default:
> -		rvin_write(vin, vin->crop.top, VNSLPRC_REG);
> -		rvin_write(vin, vin->crop.top + vin->crop.height - 1,
> -			   VNELPRC_REG);
> -		break;
> -	}
> -
>  	/* Set scaling coefficient */
>  	ys = 0;
>  	if (vin->crop.height != vin->compose.height)
> @@ -799,11 +805,6 @@ void rvin_crop_scale_comp(struct rvin_dev *vin)
>  		break;
>  	}
> 
> -	if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
> -		rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
> -	else
> -		rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
> -
>  	vin_dbg(vin,
>  		"Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n",
>  		vin->crop.width, vin->crop.height, vin->crop.left,
> @@ -811,9 +812,43 @@ void rvin_crop_scale_comp(struct rvin_dev *vin)
>  		0, 0);
>  }
> 
> +void rvin_crop_scale_comp(struct rvin_dev *vin)
> +{
> +	/* Set Start/End Pixel/Line Pre-Clip */
> +	rvin_write(vin, vin->crop.left, VNSPPRC_REG);
> +	rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG);

Please add a blank line here.

> +	switch (vin->format.field) {
> +	case V4L2_FIELD_INTERLACED:
> +	case V4L2_FIELD_INTERLACED_TB:
> +	case V4L2_FIELD_INTERLACED_BT:
> +		rvin_write(vin, vin->crop.top / 2, VNSLPRC_REG);
> +		rvin_write(vin, (vin->crop.top + vin->crop.height) / 2 - 1,
> +			   VNELPRC_REG);
> +		break;
> +	default:
> +		rvin_write(vin, vin->crop.top, VNSLPRC_REG);
> +		rvin_write(vin, vin->crop.top + vin->crop.height - 1,
> +			   VNELPRC_REG);
> +		break;
> +	}
> +
> +	/* Driver do not support UDS */
> +	if (vin->chip != RCAR_GEN3)
> +		rvin_crop_scale_comp_gen2(vin);

If you reorder the chip IDs as proposed below, I think

	if (vin->chip <= RCAR_GEN2)
		rvin_crop_scale_comp_gen2(vin);

would be clearer.

> +
> +	if (vin->format.pixelformat == V4L2_PIX_FMT_NV16)
> +		rvin_write(vin, ALIGN(vin->format.width, 0x20), VNIS_REG);
> +	else
> +		rvin_write(vin, ALIGN(vin->format.width, 0x10), VNIS_REG);
> +}
> +
>  void rvin_scale_try(struct rvin_dev *vin, struct v4l2_pix_format *pix,
>  		    u32 width, u32 height)
>  {
> +	/* Driver do not support UDS */

s/do not/does not/

And I'd write it as

/* TODO: Add support for the UDS scaler. */

> +	if (vin->chip == RCAR_GEN3)
> +		return;
> +
>  	/* All VIN channels on Gen2 have scalers */
>  	pix->width = width;
>  	pix->height = height;
> diff --git a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> b/drivers/media/platform/rcar-vin/rcar-v4l2.c index 2307f5b..f4eaef0 100644
> --- a/drivers/media/platform/rcar-vin/rcar-v4l2.c
> +++ b/drivers/media/platform/rcar-vin/rcar-v4l2.c
> @@ -25,6 +25,8 @@
>  #define RVIN_DEFAULT_FORMAT	V4L2_PIX_FMT_YUYV
>  #define RVIN_MAX_WIDTH		2048
>  #define RVIN_MAX_HEIGHT		2048
> +#define RVIN_MAX_WIDTH_GEN3	4096
> +#define RVIN_MAX_HEIGHT_GEN3	4096
> 
>  /* ------------------------------------------------------------------------
>   * Format Conversions
> @@ -139,7 +141,7 @@ static int __rvin_try_format(struct rvin_dev *vin,
>  			     struct rvin_source_fmt *source)
>  {
>  	const struct rvin_video_format *info;
> -	u32 rwidth, rheight, walign;
> +	u32 rwidth, rheight, walign, max_width, max_height;
> 
>  	/* Requested */
>  	rwidth = pix->width;
> @@ -173,8 +175,15 @@ static int __rvin_try_format(struct rvin_dev *vin,
>  	walign = vin->format.pixelformat == V4L2_PIX_FMT_NV16 ? 5 : 1;
> 
>  	/* Limit to VIN capabilities */
> -	v4l_bound_align_image(&pix->width, 2, RVIN_MAX_WIDTH, walign,
> -			      &pix->height, 4, RVIN_MAX_HEIGHT, 2, 0);
> +	if (vin->chip == RCAR_GEN3) {
> +		max_width = RVIN_MAX_WIDTH_GEN3;
> +		max_height = RVIN_MAX_HEIGHT_GEN3;
> +	} else {
> +		max_width = RVIN_MAX_WIDTH;
> +		max_height = RVIN_MAX_HEIGHT;
> +	}

This is one example where I think a device info structure would help. You 
could store there the maximum width and height, and remove the condition here.

Again, no need to change this now as long as we don't have too many such 
checks.

> +	v4l_bound_align_image(&pix->width, 2, max_width, walign,
> +			      &pix->height, 4, max_height, 2, 0);
> 
>  	switch (pix->field) {
>  	case V4L2_FIELD_NONE:
> diff --git a/drivers/media/platform/rcar-vin/rcar-vin.h
> b/drivers/media/platform/rcar-vin/rcar-vin.h index 81780f1..b97fa43 100644
> --- a/drivers/media/platform/rcar-vin/rcar-vin.h
> +++ b/drivers/media/platform/rcar-vin/rcar-vin.h
> @@ -32,6 +32,7 @@
>  #define HW_BUFFER_MASK 0x7f
> 
>  enum chip_id {
> +	RCAR_GEN3,
>  	RCAR_GEN2,
>  	RCAR_H1,
>  	RCAR_M1,

Nitpicking, it looks weird to me to list them in reverse chronological order, 
I'd order them as

	RCAR_H1
	RCAR_M1
	RCAR_GEN2
	RCAR_GEN3

Another node 

-- 
Regards,

Laurent Pinchart


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 7/8] [media] rcar-vin: enable Gen3
  2016-05-25 19:10 ` [PATCH 7/8] [media] rcar-vin: enable Gen3 Niklas Söderlund
@ 2016-06-16 16:55   ` Laurent Pinchart
  0 siblings, 0 replies; 18+ messages in thread
From: Laurent Pinchart @ 2016-06-16 16:55 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc,
	Niklas Söderlund

Hi Niklas,

Thank you for the patch.

On Wednesday 25 May 2016 21:10:08 Niklas Söderlund wrote:
> From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> ---
>  drivers/media/platform/rcar-vin/Kconfig     | 2 +-
>  drivers/media/platform/rcar-vin/rcar-core.c | 1 +
>  2 files changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/media/platform/rcar-vin/Kconfig
> b/drivers/media/platform/rcar-vin/Kconfig index b2ff2d4..ca3ea91 100644
> --- a/drivers/media/platform/rcar-vin/Kconfig
> +++ b/drivers/media/platform/rcar-vin/Kconfig
> @@ -5,7 +5,7 @@ config VIDEO_RCAR_VIN
>  	select VIDEOBUF2_DMA_CONTIG
>  	---help---
>  	  Support for Renesas R-Car Video Input (VIN) driver.
> -	  Supports R-Car Gen2 SoCs.
> +	  Supports R-Car Gen2 and Gen3 SoCs.
> 
>  	  To compile this driver as a module, choose M here: the
>  	  module will be called rcar-vin.
> diff --git a/drivers/media/platform/rcar-vin/rcar-core.c
> b/drivers/media/platform/rcar-vin/rcar-core.c index d901ad0..520690c 100644
> --- a/drivers/media/platform/rcar-vin/rcar-core.c
> +++ b/drivers/media/platform/rcar-vin/rcar-core.c
> @@ -26,6 +26,7 @@
>  #include "rcar-vin.h"
> 
>  static const struct of_device_id rvin_of_id_table[] = {
> +	{ .compatible = "renesas,vin-r8a7795", .data = (void *)RCAR_GEN3 },

This isn't needed with patch 8/8. I'd drop this hunk, and merge the previous 
one into 8/8.

>  	{ .compatible = "renesas,vin-r8a7794", .data = (void *)RCAR_GEN2 },
>  	{ .compatible = "renesas,vin-r8a7793", .data = (void *)RCAR_GEN2 },
>  	{ .compatible = "renesas,vin-r8a7791", .data = (void *)RCAR_GEN2 },

-- 
Regards,

Laurent Pinchart


^ permalink raw reply	[flat|nested] 18+ messages in thread

* Re: [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings
  2016-05-25 19:10 ` [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings Niklas Söderlund
  2016-05-25 19:36   ` Sergei Shtylyov
@ 2016-06-16 16:55   ` Laurent Pinchart
  1 sibling, 0 replies; 18+ messages in thread
From: Laurent Pinchart @ 2016-06-16 16:55 UTC (permalink / raw)
  To: Niklas Söderlund
  Cc: linux-media, ulrich.hecht, hverkuil, linux-renesas-soc,
	Niklas Söderlund

Hi Niklas,

Thank you for the patch.

On Wednesday 25 May 2016 21:10:09 Niklas Söderlund wrote:
> From: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>
> 
> These are present in the soc-camera version of this driver and it's time
> to add them to this driver as well.
> 
> Signed-off-by: Niklas Söderlund <niklas.soderlund+renesas@ragnatech.se>

Acked-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>

> ---
>  drivers/media/platform/rcar-vin/rcar-core.c | 2 ++
>  1 file changed, 2 insertions(+)
> 
> diff --git a/drivers/media/platform/rcar-vin/rcar-core.c
> b/drivers/media/platform/rcar-vin/rcar-core.c index 520690c..87041db 100644
> --- a/drivers/media/platform/rcar-vin/rcar-core.c
> +++ b/drivers/media/platform/rcar-vin/rcar-core.c
> @@ -33,6 +33,8 @@ static const struct of_device_id rvin_of_id_table[] = {
>  	{ .compatible = "renesas,vin-r8a7790", .data = (void *)RCAR_GEN2 },
>  	{ .compatible = "renesas,vin-r8a7779", .data = (void *)RCAR_H1 },
>  	{ .compatible = "renesas,vin-r8a7778", .data = (void *)RCAR_M1 },
> +	{ .compatible = "renesas,rcar-gen3-vin", .data = (void *)RCAR_GEN3 },
> +	{ .compatible = "renesas,rcar-gen2-vin", .data = (void *)RCAR_GEN2 },
>  	{ },
>  };
>  MODULE_DEVICE_TABLE(of, rvin_of_id_table);

-- 
Regards,

Laurent Pinchart


^ permalink raw reply	[flat|nested] 18+ messages in thread

end of thread, other threads:[~2016-06-16 16:55 UTC | newest]

Thread overview: 18+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-05-25 19:10 [PATCH 0/8] rcar-vin: Enable Gen3 support Niklas Söderlund
2016-05-25 19:10 ` [PATCH 1/8] media: rcar-vin: pad-aware driver initialisation Niklas Söderlund
2016-06-16 14:56   ` Laurent Pinchart
2016-05-25 19:10 ` [PATCH 2/8] media: rcar_vin: Use correct pad number in try_fmt Niklas Söderlund
2016-06-16 14:56   ` Laurent Pinchart
2016-05-25 19:10 ` [PATCH 3/8] media: rcar-vin: add DV timings support Niklas Söderlund
2016-06-16 15:05   ` Laurent Pinchart
2016-05-25 19:10 ` [PATCH 4/8] [media] rcar-vin: allow subdevices to be bound late Niklas Söderlund
2016-05-25 19:10 ` [PATCH 5/8] [media] rcar-vin: add Gen3 HW registers Niklas Söderlund
2016-06-16 16:52   ` Laurent Pinchart
2016-05-25 19:10 ` [PATCH 6/8] [media] rcar-vin: add shared subdevice groups Niklas Söderlund
2016-05-25 19:10 ` [PATCH 7/8] [media] rcar-vin: enable Gen3 Niklas Söderlund
2016-06-16 16:55   ` Laurent Pinchart
2016-05-25 19:10 ` [PATCH 8/8] [media] rcar-vin: add Gen2 and Gen3 fallback compatibility strings Niklas Söderlund
2016-05-25 19:36   ` Sergei Shtylyov
2016-05-27 11:36     ` Niklas Söderlund
2016-05-27 18:18       ` Sergei Shtylyov
2016-06-16 16:55   ` Laurent Pinchart

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).