linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge
@ 2020-10-27  3:19 Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 01/10] media: tegra-video: Use zero crop settings if subdev has no get_selection Sowjanya Komatineni
                   ` (9 more replies)
  0 siblings, 10 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

This series includes below changes to allow capturing from HDMI-to-CSI bridges.
- Add DV timing, EDID and log status V4L2 IOCTLs
- Subscribe V4L2_EVENT_SOURCE_CHANGE
- Implement V4L2 device notify callback to report queue error on source change
  during active streaming.
- Add support for NV16 V4L2 Pixel format.
- Add x8 capture by multiple ports gang up for 4K captures from HDMI-to-CSI
  bridges.

Note: These patches are tested with TC358840 HDMI-to-CSI bridge.

This series also include below fixes
- Allow format change for subdevs that don't have crop support.
- Correct V4L2 Pixel format for RGB888_1X24
- Enable VI pixel transform for YUV and RGB formats.

Delta between patch versions:
[v2]:	v1 + additional patch for x8 capture support


Sowjanya Komatineni (10):
  media: tegra-video: Use zero crop settings if subdev has no
    get_selection
  media: tegra-video: Enable VI pixel transform for YUV and RGB formats
  media: tegra-video: Fix V4L2 pixel format for RGB888_1X24
  media: tegra-video: Add support for V4L2_PIX_FMT_NV16
  media: tegra-video: Add DV timing support
  media: tegra-video: Add support for EDID ioctl ops
  media: tegra-video: Add support for VIDIOC_LOG_STATUS ioctl
  media: tegra-video: Add support for V4L2_EVENT_SOURCE_CHANGE
  media: tegra-video: Implement V4L2 device notify callback
  media: tegra-video: Add support for x8 captures with gang ports

 drivers/staging/media/tegra-video/csi.c      |  47 +++-
 drivers/staging/media/tegra-video/csi.h      |  14 +-
 drivers/staging/media/tegra-video/tegra210.c | 312 +++++++++++++++++++--------
 drivers/staging/media/tegra-video/vi.c       | 288 +++++++++++++++++++++----
 drivers/staging/media/tegra-video/vi.h       |  17 +-
 drivers/staging/media/tegra-video/video.c    |  18 ++
 6 files changed, 547 insertions(+), 149 deletions(-)

-- 
2.7.4


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

* [PATCH v2 01/10] media: tegra-video: Use zero crop settings if subdev has no get_selection
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 02/10] media: tegra-video: Enable VI pixel transform for YUV and RGB formats Sowjanya Komatineni
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

Currently try format implementation doesn't check if subdevice has
get_selection ops implemented and returns -EINVAL on error from
direct v4l2_subdev_call of get_selection.

Selection API's are not mandatory for all V4L2 subdevices.

This patch fixes it by adding v4l2_subdev_has_ops check prior to
calling get_selection ops and continues with try or set format with
zero crop settings for subdevices that don't have get_selection and
returns -EINVAL only for subdevices that has get_selection ops.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/vi.c | 17 ++++++++++++-----
 1 file changed, 12 insertions(+), 5 deletions(-)

diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 560d8b3..7edd35c 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -533,11 +533,18 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan,
 	fse.code = fmtinfo->code;
 	ret = v4l2_subdev_call(subdev, pad, enum_frame_size, pad_cfg, &fse);
 	if (ret) {
-		ret = v4l2_subdev_call(subdev, pad, get_selection, NULL, &sdsel);
-		if (ret)
-			return -EINVAL;
-		pad_cfg->try_crop.width = sdsel.r.width;
-		pad_cfg->try_crop.height = sdsel.r.height;
+		if (!v4l2_subdev_has_op(subdev, pad, get_selection)) {
+			pad_cfg->try_crop.width = 0;
+			pad_cfg->try_crop.height = 0;
+		} else {
+			ret = v4l2_subdev_call(subdev, pad, get_selection,
+					       NULL, &sdsel);
+			if (ret)
+				return -EINVAL;
+
+			pad_cfg->try_crop.width = sdsel.r.width;
+			pad_cfg->try_crop.height = sdsel.r.height;
+		}
 	} else {
 		pad_cfg->try_crop.width = fse.max_width;
 		pad_cfg->try_crop.height = fse.max_height;
-- 
2.7.4


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

* [PATCH v2 02/10] media: tegra-video: Enable VI pixel transform for YUV and RGB formats
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 01/10] media: tegra-video: Use zero crop settings if subdev has no get_selection Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 03/10] media: tegra-video: Fix V4L2 pixel format for RGB888_1X24 Sowjanya Komatineni
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

VI Pixel transforms converts source pixel data to selected
destination pixel formats in memory and aligns properly.

YUV and RGB formats need this pixel transform to be enabled.

RAW formats use T_R16_I destination pixel format in memory and
does not need pixel transform as they support direct write to
memory.

So, this patch enables pixel transform for YUV and RGB and keeps
it bypass for RAW formats.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/tegra210.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
index ac066c0..6b23aa7 100644
--- a/drivers/staging/media/tegra-video/tegra210.c
+++ b/drivers/staging/media/tegra-video/tegra210.c
@@ -178,10 +178,23 @@ static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
 	u32 format = chan->fmtinfo->img_fmt;
 	u32 data_type = chan->fmtinfo->img_dt;
 	u32 word_count = (width * chan->fmtinfo->bit_width) / 8;
+	u32 bypass_pixel_transform = BIT(BYPASS_PXL_TRANSFORM_OFFSET);
+
+	/*
+	 * VI Pixel transformation unit converts source pixels data format
+	 * into selected destination pixel format and aligns properly while
+	 * interfacing with memory packer.
+	 * This pixel transformation should be enabled for YUV and RGB
+	 * formats and should be bypassed for RAW formats as RAW formats
+	 * only support direct to memory.
+	 */
+	if (chan->pg_mode || data_type == TEGRA_IMAGE_DT_YUV422_8 ||
+	    data_type == TEGRA_IMAGE_DT_RGB888)
+		bypass_pixel_transform = 0;
 
 	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
 	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DEF,
-		     ((chan->pg_mode ? 0 : 1) << BYPASS_PXL_TRANSFORM_OFFSET) |
+		     bypass_pixel_transform |
 		     (format << IMAGE_DEF_FORMAT_OFFSET) |
 		     IMAGE_DEF_DEST_MEM);
 	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DT, data_type);
-- 
2.7.4


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

* [PATCH v2 03/10] media: tegra-video: Fix V4L2 pixel format for RGB888_1X24
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 01/10] media: tegra-video: Use zero crop settings if subdev has no get_selection Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 02/10] media: tegra-video: Enable VI pixel transform for YUV and RGB formats Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-11-09 12:51   ` Hans Verkuil
  2020-10-27  3:19 ` [PATCH v2 04/10] media: tegra-video: Add support for V4L2_PIX_FMT_NV16 Sowjanya Komatineni
                   ` (6 subsequent siblings)
  9 siblings, 1 reply; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

V4L2 pixel format is incorrect for RGB888_1X24.

This patch fixes it.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/tegra210.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
index 6b23aa7..c883925 100644
--- a/drivers/staging/media/tegra-video/tegra210.c
+++ b/drivers/staging/media/tegra-video/tegra210.c
@@ -619,7 +619,7 @@ static const struct tegra_video_format tegra210_video_formats[] = {
 	TEGRA210_VIDEO_FMT(RAW12, 12, SGBRG12_1X12, 2, T_R16_I, SGBRG12),
 	TEGRA210_VIDEO_FMT(RAW12, 12, SBGGR12_1X12, 2, T_R16_I, SBGGR12),
 	/* RGB888 */
-	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X24, 4, T_A8R8G8B8, RGB24),
+	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X24, 4, T_A8R8G8B8, XRGB32),
 	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X32_PADHI, 4, T_A8B8G8R8,
 			   XBGR32),
 	/* YUV422 */
-- 
2.7.4


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

* [PATCH v2 04/10] media: tegra-video: Add support for V4L2_PIX_FMT_NV16
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (2 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 03/10] media: tegra-video: Fix V4L2 pixel format for RGB888_1X24 Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 05/10] media: tegra-video: Add DV timing support Sowjanya Komatineni
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

NV16 are two-plane versions of YUV422 format.

VI/CSI surface0 registers corresponds to first Y plane and
surface1 registers corresponds to seconds UV plane.

This patch updates image size for NV16 format to include both planes
and programs VI/CSI surface1 registers for UV plane capture.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/tegra210.c | 13 +++++++++++++
 drivers/staging/media/tegra-video/vi.c       |  2 ++
 2 files changed, 15 insertions(+)

diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
index c883925..929d277 100644
--- a/drivers/staging/media/tegra-video/tegra210.c
+++ b/drivers/staging/media/tegra-video/tegra210.c
@@ -287,6 +287,7 @@ static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
 {
 	u32 thresh, value, frame_start, mw_ack_done;
 	int bytes_per_line = chan->format.bytesperline;
+	u32 sizeimage = chan->format.sizeimage;
 	int err;
 
 	/* program buffer address by using surface 0 */
@@ -296,6 +297,18 @@ static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
 	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_STRIDE, bytes_per_line);
 
 	/*
+	 * Program surface 1 for UV plane with offset sizeimage from Y plane.
+	 */
+	if (chan->fmtinfo->fourcc == V4L2_PIX_FMT_NV16) {
+		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
+			     ((u64)buf->addr + sizeimage / 2) >> 32);
+		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
+			     buf->addr + sizeimage / 2);
+		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_STRIDE,
+			     bytes_per_line);
+	}
+
+	/*
 	 * Tegra VI block interacts with host1x syncpt for synchronizing
 	 * programmed condition of capture state and hardware operation.
 	 * Frame start and Memory write acknowledge syncpts has their own
diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 7edd35c..525c087 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -484,6 +484,8 @@ static void tegra_channel_fmt_align(struct tegra_vi_channel *chan,
 
 	pix->bytesperline = clamp(bpl, min_bpl, max_bpl);
 	pix->sizeimage = pix->bytesperline * pix->height;
+	if (pix->pixelformat == V4L2_PIX_FMT_NV16)
+		pix->sizeimage *= 2;
 }
 
 static int __tegra_channel_try_format(struct tegra_vi_channel *chan,
-- 
2.7.4


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

* [PATCH v2 05/10] media: tegra-video: Add DV timing support
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (3 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 04/10] media: tegra-video: Add support for V4L2_PIX_FMT_NV16 Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 06/10] media: tegra-video: Add support for EDID ioctl ops Sowjanya Komatineni
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

This patch adds below v4l2 DV timing ioctls to support HDMI-to-CSI
bridges.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/vi.c | 97 ++++++++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 525c087..d0d84da 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -18,6 +18,7 @@
 #include <linux/pm_runtime.h>
 #include <linux/slab.h>
 
+#include <media/v4l2-dv-timings.h>
 #include <media/v4l2-event.h>
 #include <media/v4l2-fh.h>
 #include <media/v4l2-fwnode.h>
@@ -720,6 +721,97 @@ static int tegra_channel_s_selection(struct file *file, void *fh,
 	return ret;
 }
 
+static int tegra_channel_g_dv_timings(struct file *file, void *fh,
+				      struct v4l2_dv_timings *timings)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, video, g_dv_timings))
+		return -ENOTTY;
+
+	return v4l2_device_call_until_err(chan->video.v4l2_dev, 0,
+					  video, g_dv_timings, timings);
+}
+
+static int tegra_channel_s_dv_timings(struct file *file, void *fh,
+				      struct v4l2_dv_timings *timings)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+	struct v4l2_bt_timings *bt = &timings->bt;
+	struct v4l2_dv_timings curr_timings;
+	int ret;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, video, s_dv_timings))
+		return -ENOTTY;
+
+	ret = tegra_channel_g_dv_timings(file, fh, &curr_timings);
+	if (ret)
+		return ret;
+
+	if (v4l2_match_dv_timings(timings, &curr_timings, 0, false))
+		return 0;
+
+	if (vb2_is_busy(&chan->queue))
+		return -EBUSY;
+
+	ret = v4l2_device_call_until_err(chan->video.v4l2_dev, 0,
+					 video, s_dv_timings, timings);
+	if (ret)
+		return ret;
+
+	chan->format.width = bt->width;
+	chan->format.height = bt->height;
+	chan->format.bytesperline = bt->width * chan->fmtinfo->bpp;
+	chan->format.sizeimage = chan->format.bytesperline * bt->height;
+	tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
+
+	return 0;
+}
+
+static int tegra_channel_query_dv_timings(struct file *file, void *fh,
+					  struct v4l2_dv_timings *timings)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, video, query_dv_timings))
+		return -ENOTTY;
+
+	return v4l2_device_call_until_err(chan->video.v4l2_dev, 0,
+					  video, query_dv_timings, timings);
+}
+
+static int tegra_channel_enum_dv_timings(struct file *file, void *fh,
+					 struct v4l2_enum_dv_timings *timings)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, pad, enum_dv_timings))
+		return -ENOTTY;
+
+	return v4l2_subdev_call(subdev, pad, enum_dv_timings, timings);
+}
+
+static int tegra_channel_dv_timings_cap(struct file *file, void *fh,
+					struct v4l2_dv_timings_cap *cap)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, pad, dv_timings_cap))
+		return -ENOTTY;
+
+	return v4l2_subdev_call(subdev, pad, dv_timings_cap, cap);
+}
+
 static int tegra_channel_enum_input(struct file *file, void *fh,
 				    struct v4l2_input *inp)
 {
@@ -779,6 +871,11 @@ static const struct v4l2_ioctl_ops tegra_channel_ioctl_ops = {
 	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
 	.vidioc_g_selection		= tegra_channel_g_selection,
 	.vidioc_s_selection		= tegra_channel_s_selection,
+	.vidioc_g_dv_timings		= tegra_channel_g_dv_timings,
+	.vidioc_s_dv_timings		= tegra_channel_s_dv_timings,
+	.vidioc_query_dv_timings	= tegra_channel_query_dv_timings,
+	.vidioc_enum_dv_timings		= tegra_channel_enum_dv_timings,
+	.vidioc_dv_timings_cap		= tegra_channel_dv_timings_cap,
 };
 
 /*
-- 
2.7.4


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

* [PATCH v2 06/10] media: tegra-video: Add support for EDID ioctl ops
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (4 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 05/10] media: tegra-video: Add DV timing support Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 07/10] media: tegra-video: Add support for VIDIOC_LOG_STATUS ioctl Sowjanya Komatineni
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

This patch adds support for EDID get and set v4l2 ioctl ops to use
with HDMI to CSI bridges.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/vi.c | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index d0d84da..92f36b1 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -721,6 +721,32 @@ static int tegra_channel_s_selection(struct file *file, void *fh,
 	return ret;
 }
 
+static int tegra_channel_g_edid(struct file *file, void *fh,
+				struct v4l2_edid *edid)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, pad, get_edid))
+		return -ENOTTY;
+
+	return v4l2_subdev_call(subdev, pad, get_edid, edid);
+}
+
+static int tegra_channel_s_edid(struct file *file, void *fh,
+				struct v4l2_edid *edid)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+	struct v4l2_subdev *subdev;
+
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	if (!v4l2_subdev_has_op(subdev, pad, set_edid))
+		return -ENOTTY;
+
+	return v4l2_subdev_call(subdev, pad, set_edid, edid);
+}
+
 static int tegra_channel_g_dv_timings(struct file *file, void *fh,
 				      struct v4l2_dv_timings *timings)
 {
@@ -871,6 +897,8 @@ static const struct v4l2_ioctl_ops tegra_channel_ioctl_ops = {
 	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
 	.vidioc_g_selection		= tegra_channel_g_selection,
 	.vidioc_s_selection		= tegra_channel_s_selection,
+	.vidioc_g_edid			= tegra_channel_g_edid,
+	.vidioc_s_edid			= tegra_channel_s_edid,
 	.vidioc_g_dv_timings		= tegra_channel_g_dv_timings,
 	.vidioc_s_dv_timings		= tegra_channel_s_dv_timings,
 	.vidioc_query_dv_timings	= tegra_channel_query_dv_timings,
-- 
2.7.4


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

* [PATCH v2 07/10] media: tegra-video: Add support for VIDIOC_LOG_STATUS ioctl
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (5 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 06/10] media: tegra-video: Add support for EDID ioctl ops Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 08/10] media: tegra-video: Add support for V4L2_EVENT_SOURCE_CHANGE Sowjanya Komatineni
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

This patch adds support for log_status ioctl.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/vi.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 92f36b1..936e5e5 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -838,6 +838,15 @@ static int tegra_channel_dv_timings_cap(struct file *file, void *fh,
 	return v4l2_subdev_call(subdev, pad, dv_timings_cap, cap);
 }
 
+static int tegra_channel_log_status(struct file *file, void *fh)
+{
+	struct tegra_vi_channel *chan = video_drvdata(file);
+
+	v4l2_device_call_all(chan->video.v4l2_dev, 0, core, log_status);
+
+	return 0;
+}
+
 static int tegra_channel_enum_input(struct file *file, void *fh,
 				    struct v4l2_input *inp)
 {
@@ -904,6 +913,7 @@ static const struct v4l2_ioctl_ops tegra_channel_ioctl_ops = {
 	.vidioc_query_dv_timings	= tegra_channel_query_dv_timings,
 	.vidioc_enum_dv_timings		= tegra_channel_enum_dv_timings,
 	.vidioc_dv_timings_cap		= tegra_channel_dv_timings_cap,
+	.vidioc_log_status		= tegra_channel_log_status,
 };
 
 /*
-- 
2.7.4


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

* [PATCH v2 08/10] media: tegra-video: Add support for V4L2_EVENT_SOURCE_CHANGE
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (6 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 07/10] media: tegra-video: Add support for VIDIOC_LOG_STATUS ioctl Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 09/10] media: tegra-video: Implement V4L2 device notify callback Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports Sowjanya Komatineni
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

Current implementation uses v4l2_ctrl_subscribe_event() and this
does not handle V4L2_EVENT_SOURCE_CHANGE.

So, update driver to handle V4L2_EVENT_SOURCE_CHANGE.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/vi.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 936e5e5..28c06a9 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -642,6 +642,18 @@ static int tegra_channel_set_subdev_active_fmt(struct tegra_vi_channel *chan)
 	return 0;
 }
 
+static int
+tegra_channel_subscribe_event(struct v4l2_fh *fh,
+			      const struct v4l2_event_subscription *sub)
+{
+	switch (sub->type) {
+	case V4L2_EVENT_SOURCE_CHANGE:
+		return v4l2_event_subscribe(fh, sub, 4, NULL);
+	}
+
+	return v4l2_ctrl_subscribe_event(fh, sub);
+}
+
 static int tegra_channel_g_selection(struct file *file, void *priv,
 				     struct v4l2_selection *sel)
 {
@@ -902,7 +914,7 @@ static const struct v4l2_ioctl_ops tegra_channel_ioctl_ops = {
 	.vidioc_expbuf			= vb2_ioctl_expbuf,
 	.vidioc_streamon		= vb2_ioctl_streamon,
 	.vidioc_streamoff		= vb2_ioctl_streamoff,
-	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
+	.vidioc_subscribe_event		= tegra_channel_subscribe_event,
 	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
 	.vidioc_g_selection		= tegra_channel_g_selection,
 	.vidioc_s_selection		= tegra_channel_s_selection,
-- 
2.7.4


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

* [PATCH v2 09/10] media: tegra-video: Implement V4L2 device notify callback
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (7 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 08/10] media: tegra-video: Add support for V4L2_EVENT_SOURCE_CHANGE Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-10-27  3:19 ` [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports Sowjanya Komatineni
  9 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

Implement V4L2 device notify callback to handle
V4L2_EVENT_SOURCE_CHANGE event from subdevices.

HDMI-to-CSI bridge drivers trigger V4L2_EVENT_SOURCE_CHANGE when
source DV timing changes are detected or when HDMI hotplug happens.

Runtime source parameter changes during active streaming is not
allowed and Tegra video driver calls vb2_queue_error to signal a
fatal error if a notification of this event happens during an active
streaming.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/vi.c    |  3 +++
 drivers/staging/media/tegra-video/video.c | 18 ++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 28c06a9..e866f7d 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -1634,6 +1634,9 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
 
 	v4l2_set_subdev_hostdata(subdev, chan);
 
+	subdev = tegra_channel_get_remote_source_subdev(chan);
+	v4l2_set_subdev_hostdata(subdev, chan);
+
 	return 0;
 
 unregister_video:
diff --git a/drivers/staging/media/tegra-video/video.c b/drivers/staging/media/tegra-video/video.c
index e50bd70..d966b31 100644
--- a/drivers/staging/media/tegra-video/video.c
+++ b/drivers/staging/media/tegra-video/video.c
@@ -7,6 +7,8 @@
 #include <linux/module.h>
 #include <linux/platform_device.h>
 
+#include <media/v4l2-event.h>
+
 #include "video.h"
 
 static void tegra_v4l2_dev_release(struct v4l2_device *v4l2_dev)
@@ -24,6 +26,21 @@ static void tegra_v4l2_dev_release(struct v4l2_device *v4l2_dev)
 	kfree(vid);
 }
 
+static void tegra_v4l2_dev_notify(struct v4l2_subdev *sd,
+				  unsigned int notification, void *arg)
+{
+	struct tegra_vi_channel *chan;
+	const struct v4l2_event *ev = arg;
+
+	if (notification != V4L2_DEVICE_NOTIFY_EVENT)
+		return;
+
+	chan = v4l2_get_subdev_hostdata(sd);
+	v4l2_event_queue(&chan->video, arg);
+	if (ev->type == V4L2_EVENT_SOURCE_CHANGE && vb2_is_streaming(&chan->queue))
+		vb2_queue_error(&chan->queue);
+}
+
 static int host1x_video_probe(struct host1x_device *dev)
 {
 	struct tegra_video_device *vid;
@@ -49,6 +66,7 @@ static int host1x_video_probe(struct host1x_device *dev)
 
 	vid->v4l2_dev.mdev = &vid->media_dev;
 	vid->v4l2_dev.release = tegra_v4l2_dev_release;
+	vid->v4l2_dev.notify = tegra_v4l2_dev_notify;
 	ret = v4l2_device_register(&dev->dev, &vid->v4l2_dev);
 	if (ret < 0) {
 		dev_err(&dev->dev,
-- 
2.7.4


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

* [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports
  2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
                   ` (8 preceding siblings ...)
  2020-10-27  3:19 ` [PATCH v2 09/10] media: tegra-video: Implement V4L2 device notify callback Sowjanya Komatineni
@ 2020-10-27  3:19 ` Sowjanya Komatineni
  2020-11-09 13:04   ` Hans Verkuil
  9 siblings, 1 reply; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-10-27  3:19 UTC (permalink / raw)
  To: skomatineni, thierry.reding, jonathanh, hverkuil
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

Tegra VI/CSI hardware don't have native 8 lane capture support.

Each CSI port has max 4 lanes only. So for x8 captures, consecutive
ports are ganged up for left half and right half captures on to each
x4 ports with buffer offsets based on source image split width to align
side-by-side.

All ports in gang are configured together during the corresponding
video device node streaming for x8 captures.

x8 capture with gang ports are supported with HDMI-to-CSI bridges
where they split 4K image into left half onto one x4 port and
right half onto second x4 port.

Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
---
 drivers/staging/media/tegra-video/csi.c      |  47 ++++-
 drivers/staging/media/tegra-video/csi.h      |  14 +-
 drivers/staging/media/tegra-video/tegra210.c | 304 ++++++++++++++++++---------
 drivers/staging/media/tegra-video/vi.c       | 117 ++++++++---
 drivers/staging/media/tegra-video/vi.h       |  17 +-
 5 files changed, 347 insertions(+), 152 deletions(-)

diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c
index a19c85c..a6c54f8 100644
--- a/drivers/staging/media/tegra-video/csi.c
+++ b/drivers/staging/media/tegra-video/csi.c
@@ -253,13 +253,14 @@ static unsigned int csi_get_pixel_rate(struct tegra_csi_channel *csi_chan)
 }
 
 void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
+				u8 csi_port_num,
 				u8 *clk_settle_time,
 				u8 *ths_settle_time)
 {
 	struct tegra_csi *csi = csi_chan->csi;
 	unsigned int cil_clk_mhz;
 	unsigned int pix_clk_mhz;
-	int clk_idx = (csi_chan->csi_port_num >> 1) + 1;
+	int clk_idx = (csi_port_num >> 1) + 1;
 
 	cil_clk_mhz = clk_get_rate(csi->clks[clk_idx].clk) / MHZ;
 	pix_clk_mhz = csi_get_pixel_rate(csi_chan) / MHZ;
@@ -410,7 +411,7 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi,
 				   unsigned int num_pads)
 {
 	struct tegra_csi_channel *chan;
-	int ret = 0;
+	int ret = 0, i;
 
 	chan = kzalloc(sizeof(*chan), GFP_KERNEL);
 	if (!chan)
@@ -418,8 +419,21 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi,
 
 	list_add_tail(&chan->list, &csi->csi_chans);
 	chan->csi = csi;
-	chan->csi_port_num = port_num;
-	chan->numlanes = lanes;
+	/*
+	 * Each CSI brick has maximum of 4 lanes.
+	 * For lanes more than 4, use multiple of immediate CSI bricks as gang.
+	 */
+	if (lanes <= CSI_LANES_PER_BRICK) {
+		chan->numlanes = lanes;
+		chan->numgangports = 1;
+	} else {
+		chan->numlanes = CSI_LANES_PER_BRICK;
+		chan->numgangports = lanes / CSI_LANES_PER_BRICK;
+	}
+
+	for (i = 0; i < chan->numgangports; i++)
+		chan->csi_port_nums[i] = port_num + i * CSI_PORTS_PER_BRICK;
+
 	chan->of_node = node;
 	chan->numpads = num_pads;
 	if (num_pads & 0x2) {
@@ -461,9 +475,6 @@ static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
 static int tegra_csi_channels_alloc(struct tegra_csi *csi)
 {
 	struct device_node *node = csi->dev->of_node;
-	struct v4l2_fwnode_endpoint v4l2_ep = {
-		.bus_type = V4L2_MBUS_CSI2_DPHY
-	};
 	struct fwnode_handle *fwh;
 	struct device_node *channel;
 	struct device_node *ep;
@@ -471,6 +482,8 @@ static int tegra_csi_channels_alloc(struct tegra_csi *csi)
 	int ret;
 
 	for_each_child_of_node(node, channel) {
+		struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 };
+
 		if (!of_node_name_eq(channel, "channel"))
 			continue;
 
@@ -499,8 +512,20 @@ static int tegra_csi_channels_alloc(struct tegra_csi *csi)
 			goto err_node_put;
 		}
 
-		lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
-		if (!lanes || ((lanes & (lanes - 1)) != 0)) {
+		if (v4l2_ep.bus_type == V4L2_MBUS_PARALLEL)
+			lanes = v4l2_ep.bus.parallel.bus_width;
+		else
+			lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
+
+		/*
+		 * Each CSI brick has maximum 4 data lanes.
+		 * For lanes more than 4, validate lanes to be multiple of 4
+		 * so multiple of consecutive CSI bricks can be ganged up for
+		 * streaming.
+		 */
+		if (!lanes || ((lanes & (lanes - 1)) != 0) ||
+		    (lanes > CSI_LANES_PER_BRICK && (((lanes & 3) != 0) ||
+		    ((portno & 1) != 0)))) {
 			dev_err(csi->dev, "invalid data-lanes %d for %pOF\n",
 				lanes, channel);
 			ret = -EINVAL;
@@ -544,7 +569,7 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
 	subdev->dev = csi->dev;
 	if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
 		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg",
-			 chan->csi_port_num);
+			 chan->csi_port_nums[0]);
 	else
 		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
 			 kbasename(chan->of_node->full_name));
@@ -596,7 +621,7 @@ static int tegra_csi_channels_init(struct tegra_csi *csi)
 		if (ret) {
 			dev_err(csi->dev,
 				"failed to initialize channel-%d: %d\n",
-				chan->csi_port_num, ret);
+				chan->csi_port_nums[0], ret);
 			return ret;
 		}
 	}
diff --git a/drivers/staging/media/tegra-video/csi.h b/drivers/staging/media/tegra-video/csi.h
index c65ff73..386f7c6 100644
--- a/drivers/staging/media/tegra-video/csi.h
+++ b/drivers/staging/media/tegra-video/csi.h
@@ -17,6 +17,10 @@
  * CILB.
  */
 #define CSI_PORTS_PER_BRICK	2
+#define CSI_LANES_PER_BRICK	4
+
+/* Maximum 2 CSI x4 ports can be ganged up for streaming */
+#define GANG_PORTS_MAX	2
 
 /* each CSI channel can have one sink and one source pads */
 #define TEGRA_CSI_PADS_NUM	2
@@ -43,8 +47,10 @@ struct tegra_csi;
  * @numpads: number of pads.
  * @csi: Tegra CSI device structure
  * @of_node: csi device tree node
- * @numlanes: number of lanes used per port/channel
- * @csi_port_num: CSI channel port number
+ * @numgangports: number of immediate ports ganged up to meet the
+ *             channel bus-width
+ * @numlanes: number of lanes used per port
+ * @csi_port_nums: CSI channel port numbers
  * @pg_mode: test pattern generator mode for channel
  * @format: active format of the channel
  * @framerate: active framerate for TPG
@@ -60,8 +66,9 @@ struct tegra_csi_channel {
 	unsigned int numpads;
 	struct tegra_csi *csi;
 	struct device_node *of_node;
+	u8 numgangports;
 	unsigned int numlanes;
-	u8 csi_port_num;
+	u8 csi_port_nums[GANG_PORTS_MAX];
 	u8 pg_mode;
 	struct v4l2_mbus_framefmt format;
 	unsigned int framerate;
@@ -150,6 +157,7 @@ extern const struct tegra_csi_soc tegra210_csi_soc;
 
 void tegra_csi_error_recover(struct v4l2_subdev *subdev);
 void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
+				u8 csi_port_num,
 				u8 *clk_settle_time,
 				u8 *ths_settle_time);
 #endif
diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
index 929d277..aff56fc 100644
--- a/drivers/staging/media/tegra-video/tegra210.c
+++ b/drivers/staging/media/tegra-video/tegra210.c
@@ -149,21 +149,22 @@ static u32 tegra_vi_read(struct tegra_vi_channel *chan, unsigned int addr)
 }
 
 /* Tegra210 VI_CSI registers accessors */
-static void vi_csi_write(struct tegra_vi_channel *chan, unsigned int addr,
-			 u32 val)
+static void vi_csi_write(struct tegra_vi_channel *chan, u8 portno,
+			 unsigned int addr, u32 val)
 {
 	void __iomem *vi_csi_base;
 
-	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(chan->portno);
+	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(portno);
 
 	writel_relaxed(val, vi_csi_base + addr);
 }
 
-static u32 vi_csi_read(struct tegra_vi_channel *chan, unsigned int addr)
+static u32 vi_csi_read(struct tegra_vi_channel *chan, u8 portno,
+		       unsigned int addr)
 {
 	void __iomem *vi_csi_base;
 
-	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(chan->portno);
+	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(portno);
 
 	return readl_relaxed(vi_csi_base + addr);
 }
@@ -171,7 +172,8 @@ static u32 vi_csi_read(struct tegra_vi_channel *chan, unsigned int addr)
 /*
  * Tegra210 VI channel capture operations
  */
-static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
+static int tegra_channel_capture_setup(struct tegra_vi_channel *chan,
+				       u8 portno)
 {
 	u32 height = chan->format.height;
 	u32 width = chan->format.width;
@@ -192,19 +194,30 @@ static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
 	    data_type == TEGRA_IMAGE_DT_RGB888)
 		bypass_pixel_transform = 0;
 
-	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
-	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DEF,
+	/*
+	 * For x8 source streaming, the source image is split onto two x4 ports
+	 * with left half to first x4 port and right half to second x4 port.
+	 * So, use split width and corresponding word count for each x4 port.
+	 */
+	if (chan->numgangports > 1) {
+		width = width >> 1;
+		word_count = (width * chan->fmtinfo->bit_width) / 8;
+	}
+
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_DEF,
 		     bypass_pixel_transform |
 		     (format << IMAGE_DEF_FORMAT_OFFSET) |
 		     IMAGE_DEF_DEST_MEM);
-	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DT, data_type);
-	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_SIZE_WC, word_count);
-	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_SIZE,
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_DT, data_type);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_SIZE_WC, word_count);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_SIZE,
 		     (height << IMAGE_SIZE_HEIGHT_OFFSET) | width);
 	return 0;
 }
 
-static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan)
+static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan,
+					u8 portno)
 {
 	/* disable clock gating to enable continuous clock */
 	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, 0);
@@ -212,15 +225,16 @@ static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan)
 	 * Soft reset memory client interface, pixel format logic, sensor
 	 * control logic, and a shadow copy logic to bring VI to clean state.
 	 */
-	vi_csi_write(chan, TEGRA_VI_CSI_SW_RESET, 0xf);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SW_RESET, 0xf);
 	usleep_range(100, 200);
-	vi_csi_write(chan, TEGRA_VI_CSI_SW_RESET, 0x0);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SW_RESET, 0x0);
 
 	/* enable back VI clock gating */
 	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, VI_CG_2ND_LEVEL_EN);
 }
 
-static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
+static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan,
+						u8 portno)
 {
 	struct v4l2_subdev *subdev;
 	u32 val;
@@ -232,9 +246,9 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
 	 * events which can cause CSI and VI hardware hang.
 	 * This helps to have a clean capture for next frame.
 	 */
-	val = vi_csi_read(chan, TEGRA_VI_CSI_ERROR_STATUS);
+	val = vi_csi_read(chan, portno, TEGRA_VI_CSI_ERROR_STATUS);
 	dev_dbg(&chan->video.dev, "TEGRA_VI_CSI_ERROR_STATUS 0x%08x\n", val);
-	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, val);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_ERROR_STATUS, val);
 
 	val = tegra_vi_read(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR);
 	dev_dbg(&chan->video.dev,
@@ -242,8 +256,8 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
 	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR, val);
 
 	/* recover VI by issuing software reset and re-setup for capture */
-	tegra_channel_vi_soft_reset(chan);
-	tegra_channel_capture_setup(chan);
+	tegra_channel_vi_soft_reset(chan, portno);
+	tegra_channel_capture_setup(chan, portno);
 
 	/* recover CSI block */
 	subdev = tegra_channel_get_remote_csi_subdev(chan);
@@ -282,80 +296,114 @@ static void release_buffer(struct tegra_vi_channel *chan,
 	vb2_buffer_done(&vb->vb2_buf, state);
 }
 
-static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
-				       struct tegra_channel_buffer *buf)
+static void tegra_channel_vi_buffer_setup(struct tegra_vi_channel *chan,
+					  u8 portno, u32 buf_offset,
+					  struct tegra_channel_buffer *buf)
 {
-	u32 thresh, value, frame_start, mw_ack_done;
-	int bytes_per_line = chan->format.bytesperline;
+	int bytesperline = chan->format.bytesperline;
 	u32 sizeimage = chan->format.sizeimage;
-	int err;
 
 	/* program buffer address by using surface 0 */
-	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_OFFSET_MSB,
-		     (u64)buf->addr >> 32);
-	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_OFFSET_LSB, buf->addr);
-	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_STRIDE, bytes_per_line);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_OFFSET_MSB,
+		     ((u64)buf->addr + buf_offset) >> 32);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_OFFSET_LSB,
+		     buf->addr + buf_offset);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_STRIDE, bytesperline);
 
+	if (chan->fmtinfo->fourcc != V4L2_PIX_FMT_NV16)
+		return;
 	/*
 	 * Program surface 1 for UV plane with offset sizeimage from Y plane.
 	 */
-	if (chan->fmtinfo->fourcc == V4L2_PIX_FMT_NV16) {
-		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
-			     ((u64)buf->addr + sizeimage / 2) >> 32);
-		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
-			     buf->addr + sizeimage / 2);
-		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_STRIDE,
-			     bytes_per_line);
-	}
-
-	/*
-	 * Tegra VI block interacts with host1x syncpt for synchronizing
-	 * programmed condition of capture state and hardware operation.
-	 * Frame start and Memory write acknowledge syncpts has their own
-	 * FIFO of depth 2.
-	 *
-	 * Syncpoint trigger conditions set through VI_INCR_SYNCPT register
-	 * are added to HW syncpt FIFO and when the HW triggers, syncpt
-	 * condition is removed from the FIFO and counter at syncpoint index
-	 * will be incremented by the hardware and software can wait for
-	 * counter to reach threshold to synchronize capturing frame with the
-	 * hardware capture events.
-	 */
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
+		     (((u64)buf->addr + sizeimage / 2) + buf_offset) >> 32);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
+		     buf->addr + sizeimage / 2 + buf_offset);
+	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_STRIDE, bytesperline);
+}
 
-	/* increase channel syncpoint threshold for FRAME_START */
-	thresh = host1x_syncpt_incr_max(chan->frame_start_sp, 1);
+static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
+				       struct tegra_channel_buffer *buf)
+{
+	u32 thresh, value, frame_start, mw_ack_done;
+	u32 fs_thresh[GANG_PORTS_MAX];
+	u8 *portnos = chan->portnos;
+	int gang_bpl = (chan->format.width >> 1) * chan->fmtinfo->bpp;
+	u32 buf_offset;
+	bool capture_timedout = false;
+	int err, i;
+
+	for (i = 0; i < chan->numgangports; i++) {
+		/*
+		 * Align buffers side-by-side for all consecutive x4 ports
+		 * in gang ports using bytes per line based on source split
+		 * width.
+		 */
+		buf_offset = i * roundup(gang_bpl, SURFACE_ALIGN_BYTES);
+		tegra_channel_vi_buffer_setup(chan, portnos[i], buf_offset,
+					      buf);
 
-	/* Program FRAME_START trigger condition syncpt request */
-	frame_start = VI_CSI_PP_FRAME_START(chan->portno);
-	value = VI_CFG_VI_INCR_SYNCPT_COND(frame_start) |
-		host1x_syncpt_id(chan->frame_start_sp);
-	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
+		/*
+		 * Tegra VI block interacts with host1x syncpt to synchronize
+		 * programmed condition and hardware operation for capture.
+		 * Frame start and Memory write acknowledge syncpts has their
+		 * own FIFO of depth 2.
+		 *
+		 * Syncpoint trigger conditions set through VI_INCR_SYNCPT
+		 * register are added to HW syncpt FIFO and when HW triggers,
+		 * syncpt condition is removed from the FIFO and counter at
+		 * syncpoint index will be incremented by the hardware and
+		 * software can wait for counter to reach threshold to
+		 * synchronize capturing frame with hardware capture events.
+		 */
 
-	/* increase channel syncpoint threshold for MW_ACK_DONE */
-	buf->mw_ack_sp_thresh = host1x_syncpt_incr_max(chan->mw_ack_sp, 1);
+		/* increase channel syncpoint threshold for FRAME_START */
+		thresh = host1x_syncpt_incr_max(chan->frame_start_sp[i], 1);
+		fs_thresh[i] = thresh;
+
+		/* Program FRAME_START trigger condition syncpt request */
+		frame_start = VI_CSI_PP_FRAME_START(portnos[i]);
+		value = VI_CFG_VI_INCR_SYNCPT_COND(frame_start) |
+			host1x_syncpt_id(chan->frame_start_sp[i]);
+		tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
+
+		/* increase channel syncpoint threshold for MW_ACK_DONE */
+		thresh = host1x_syncpt_incr_max(chan->mw_ack_sp[i], 1);
+		buf->mw_ack_sp_thresh[i] = thresh;
+
+		/* Program MW_ACK_DONE trigger condition syncpt request */
+		mw_ack_done = VI_CSI_MW_ACK_DONE(portnos[i]);
+		value = VI_CFG_VI_INCR_SYNCPT_COND(mw_ack_done) |
+			host1x_syncpt_id(chan->mw_ack_sp[i]);
+		tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
+	}
 
-	/* Program MW_ACK_DONE trigger condition syncpt request */
-	mw_ack_done = VI_CSI_MW_ACK_DONE(chan->portno);
-	value = VI_CFG_VI_INCR_SYNCPT_COND(mw_ack_done) |
-		host1x_syncpt_id(chan->mw_ack_sp);
-	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
+	/* enable single shot capture after all ganged ports are ready */
+	for (i = 0; i < chan->numgangports; i++)
+		vi_csi_write(chan, portnos[i], TEGRA_VI_CSI_SINGLE_SHOT,
+			     SINGLE_SHOT_CAPTURE);
 
-	/* enable single shot capture */
-	vi_csi_write(chan, TEGRA_VI_CSI_SINGLE_SHOT, SINGLE_SHOT_CAPTURE);
+	for (i = 0; i < chan->numgangports; i++) {
+		/*
+		 * Wait for syncpt counter to reach frame start event threshold
+		 */
+		err = host1x_syncpt_wait(chan->frame_start_sp[i], fs_thresh[i],
+					 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
+		if (err) {
+			capture_timedout = true;
+			/* increment syncpoint counter for timedout events */
+			host1x_syncpt_incr(chan->frame_start_sp[i]);
+			spin_lock(&chan->sp_incr_lock[i]);
+			host1x_syncpt_incr(chan->mw_ack_sp[i]);
+			spin_unlock(&chan->sp_incr_lock[i]);
+			/* clear errors and recover */
+			tegra_channel_capture_error_recover(chan, portnos[i]);
+		}
+	}
 
-	/* wait for syncpt counter to reach frame start event threshold */
-	err = host1x_syncpt_wait(chan->frame_start_sp, thresh,
-				 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
-	if (err) {
+	if (capture_timedout) {
 		dev_err_ratelimited(&chan->video.dev,
 				    "frame start syncpt timeout: %d\n", err);
-		/* increment syncpoint counter for timedout events */
-		host1x_syncpt_incr(chan->frame_start_sp);
-		spin_lock(&chan->sp_incr_lock);
-		host1x_syncpt_incr(chan->mw_ack_sp);
-		spin_unlock(&chan->sp_incr_lock);
-		/* clear errors and recover */
-		tegra_channel_capture_error_recover(chan);
 		release_buffer(chan, buf, VB2_BUF_STATE_ERROR);
 		return err;
 	}
@@ -376,21 +424,29 @@ static void tegra_channel_capture_done(struct tegra_vi_channel *chan,
 {
 	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
 	u32 value;
-	int ret;
+	bool capture_timedout = false;
+	int ret, i;
 
-	/* wait for syncpt counter to reach MW_ACK_DONE event threshold */
-	ret = host1x_syncpt_wait(chan->mw_ack_sp, buf->mw_ack_sp_thresh,
-				 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
-	if (ret) {
-		dev_err_ratelimited(&chan->video.dev,
-				    "MW_ACK_DONE syncpt timeout: %d\n", ret);
-		state = VB2_BUF_STATE_ERROR;
-		/* increment syncpoint counter for timedout event */
-		spin_lock(&chan->sp_incr_lock);
-		host1x_syncpt_incr(chan->mw_ack_sp);
-		spin_unlock(&chan->sp_incr_lock);
+	for (i = 0; i < chan->numgangports; i++) {
+		/*
+		 * Wait for syncpt counter to reach MW_ACK_DONE event threshold
+		 */
+		ret = host1x_syncpt_wait(chan->mw_ack_sp[i],
+					 buf->mw_ack_sp_thresh[i],
+					 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
+		if (ret) {
+			capture_timedout = true;
+			state = VB2_BUF_STATE_ERROR;
+			/* increment syncpoint counter for timedout event */
+			spin_lock(&chan->sp_incr_lock[i]);
+			host1x_syncpt_incr(chan->mw_ack_sp[i]);
+			spin_unlock(&chan->sp_incr_lock[i]);
+		}
 	}
 
+	if (capture_timedout)
+		dev_err_ratelimited(&chan->video.dev,
+				    "MW_ACK_DONE syncpt timeout: %d\n", ret);
 	release_buffer(chan, buf, state);
 }
 
@@ -463,14 +519,12 @@ static int tegra210_vi_start_streaming(struct vb2_queue *vq, u32 count)
 	struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
 	struct media_pipeline *pipe = &chan->video.pipe;
 	u32 val;
-	int ret;
+	u8 *portnos = chan->portnos;
+	int ret, i;
 
 	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, VI_CG_2ND_LEVEL_EN);
 
-	/* clear errors */
-	val = vi_csi_read(chan, TEGRA_VI_CSI_ERROR_STATUS);
-	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, val);
-
+	/* clear syncpt errors */
 	val = tegra_vi_read(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR);
 	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR, val);
 
@@ -489,7 +543,14 @@ static int tegra210_vi_start_streaming(struct vb2_queue *vq, u32 count)
 	if (ret < 0)
 		goto error_pipeline_start;
 
-	tegra_channel_capture_setup(chan);
+	/* clear csi errors and do capture setup for all ports in gang mode */
+	for (i = 0; i < chan->numgangports; i++) {
+		val = vi_csi_read(chan, portnos[i], TEGRA_VI_CSI_ERROR_STATUS);
+		vi_csi_write(chan, portnos[i], TEGRA_VI_CSI_ERROR_STATUS, val);
+
+		tegra_channel_capture_setup(chan, portnos[i]);
+	}
+
 	ret = tegra_channel_set_stream(chan, true);
 	if (ret < 0)
 		goto error_set_stream;
@@ -743,10 +804,10 @@ static void tpg_write(struct tegra_csi *csi, u8 portno, unsigned int addr,
 /*
  * Tegra210 CSI operations
  */
-static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
+static void tegra210_csi_port_recover(struct tegra_csi_channel *csi_chan,
+				      u8 portno)
 {
 	struct tegra_csi *csi = csi_chan->csi;
-	unsigned int portno = csi_chan->csi_port_num;
 	u32 val;
 
 	/*
@@ -795,16 +856,26 @@ static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
 	}
 }
 
-static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
+static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
+{
+	u8 *portnos = csi_chan->csi_port_nums;
+	int i;
+
+	for (i = 0; i < csi_chan->numgangports; i++)
+		tegra210_csi_port_recover(csi_chan, portnos[i]);
+}
+
+static int
+tegra210_csi_port_start_streaming(struct tegra_csi_channel *csi_chan,
+				  u8 portno)
 {
 	struct tegra_csi *csi = csi_chan->csi;
-	unsigned int portno = csi_chan->csi_port_num;
 	u8 clk_settle_time = 0;
 	u8 ths_settle_time = 10;
 	u32 val;
 
 	if (!csi_chan->pg_mode)
-		tegra_csi_calc_settle_time(csi_chan, &clk_settle_time,
+		tegra_csi_calc_settle_time(csi_chan, portno, &clk_settle_time,
 					   &ths_settle_time);
 
 	csi_write(csi, portno, TEGRA_CSI_CLKEN_OVERRIDE, 0);
@@ -903,10 +974,10 @@ static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
 	return 0;
 }
 
-static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
+static void
+tegra210_csi_port_stop_streaming(struct tegra_csi_channel *csi_chan, u8 portno)
 {
 	struct tegra_csi *csi = csi_chan->csi;
-	unsigned int portno = csi_chan->csi_port_num;
 	u32 val;
 
 	val = pp_read(csi, portno, TEGRA_CSI_PIXEL_PARSER_STATUS);
@@ -944,6 +1015,35 @@ static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
 	}
 }
 
+static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
+{
+	u8 *portnos = csi_chan->csi_port_nums;
+	int ret, i;
+
+	for (i = 0; i < csi_chan->numgangports; i++) {
+		ret = tegra210_csi_port_start_streaming(csi_chan, portnos[i]);
+		if (ret)
+			goto stream_start_fail;
+	}
+
+	return 0;
+
+stream_start_fail:
+	for (i = i - 1; i >= 0; i--)
+		tegra210_csi_port_stop_streaming(csi_chan, portnos[i]);
+
+	return ret;
+}
+
+static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
+{
+	u8 *portnos = csi_chan->csi_port_nums;
+	int i;
+
+	for (i = 0; i < csi_chan->numgangports; i++)
+		tegra210_csi_port_stop_streaming(csi_chan, portnos[i]);
+}
+
 /*
  * Tegra210 CSI TPG frame rate table with horizontal and vertical
  * blanking intervals for corresponding format and resolution.
diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index e866f7d..4812f2c 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -30,7 +30,6 @@
 #include "vi.h"
 #include "video.h"
 
-#define SURFACE_ALIGN_BYTES		64
 #define MAX_CID_CONTROLS		1
 
 static const struct tegra_video_format tegra_default_format = {
@@ -1090,12 +1089,20 @@ static int vi_fmts_bitmap_init(struct tegra_vi_channel *chan)
 	return 0;
 }
 
+static void tegra_channel_host1x_syncpts_free(struct tegra_vi_channel *chan)
+{
+	int i;
+
+	for (i = 0; i < chan->numgangports; i++) {
+		host1x_syncpt_free(chan->mw_ack_sp[i]);
+		host1x_syncpt_free(chan->frame_start_sp[i]);
+	}
+}
+
 static void tegra_channel_cleanup(struct tegra_vi_channel *chan)
 {
 	v4l2_ctrl_handler_free(&chan->ctrl_handler);
 	media_entity_cleanup(&chan->video.entity);
-	host1x_syncpt_free(chan->mw_ack_sp);
-	host1x_syncpt_free(chan->frame_start_sp);
 	mutex_destroy(&chan->video_lock);
 }
 
@@ -1107,6 +1114,7 @@ void tegra_channels_cleanup(struct tegra_vi *vi)
 		return;
 
 	list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
+		tegra_channel_host1x_syncpts_free(chan);
 		tegra_channel_cleanup(chan);
 		list_del(&chan->list);
 		kfree(chan);
@@ -1117,7 +1125,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
 {
 	struct tegra_vi *vi = chan->vi;
 	struct tegra_video_device *vid = dev_get_drvdata(vi->client.host);
-	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
 	int ret;
 
 	mutex_init(&chan->video_lock);
@@ -1125,7 +1132,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
 	INIT_LIST_HEAD(&chan->done);
 	spin_lock_init(&chan->start_lock);
 	spin_lock_init(&chan->done_lock);
-	spin_lock_init(&chan->sp_incr_lock);
 	init_waitqueue_head(&chan->start_wait);
 	init_waitqueue_head(&chan->done_wait);
 
@@ -1140,26 +1146,13 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
 	chan->format.sizeimage = chan->format.bytesperline * TEGRA_DEF_HEIGHT;
 	tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
 
-	chan->frame_start_sp = host1x_syncpt_request(&vi->client, flags);
-	if (!chan->frame_start_sp) {
-		dev_err(vi->dev, "failed to request frame start syncpoint\n");
-		return -ENOMEM;
-	}
-
-	chan->mw_ack_sp = host1x_syncpt_request(&vi->client, flags);
-	if (!chan->mw_ack_sp) {
-		dev_err(vi->dev, "failed to request memory ack syncpoint\n");
-		ret = -ENOMEM;
-		goto free_fs_syncpt;
-	}
-
 	/* initialize the media entity */
 	chan->pad.flags = MEDIA_PAD_FL_SINK;
 	ret = media_entity_pads_init(&chan->video.entity, 1, &chan->pad);
 	if (ret < 0) {
 		dev_err(vi->dev,
 			"failed to initialize media entity: %d\n", ret);
-		goto free_mw_ack_syncpt;
+		return ret;
 	}
 
 	ret = v4l2_ctrl_handler_init(&chan->ctrl_handler, MAX_CID_CONTROLS);
@@ -1175,7 +1168,7 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
 	chan->video.release = video_device_release_empty;
 	chan->video.queue = &chan->queue;
 	snprintf(chan->video.name, sizeof(chan->video.name), "%s-%s-%u",
-		 dev_name(vi->dev), "output", chan->portno);
+		 dev_name(vi->dev), "output", chan->portnos[0]);
 	chan->video.vfl_type = VFL_TYPE_VIDEO;
 	chan->video.vfl_dir = VFL_DIR_RX;
 	chan->video.ioctl_ops = &tegra_channel_ioctl_ops;
@@ -1211,10 +1204,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
 	v4l2_ctrl_handler_free(&chan->ctrl_handler);
 cleanup_media:
 	media_entity_cleanup(&chan->video.entity);
-free_mw_ack_syncpt:
-	host1x_syncpt_free(chan->mw_ack_sp);
-free_fs_syncpt:
-	host1x_syncpt_free(chan->frame_start_sp);
 	return ret;
 }
 
@@ -1234,7 +1223,8 @@ static int tegra_vi_channel_alloc(struct tegra_vi *vi, unsigned int port_num,
 		return -ENOMEM;
 
 	chan->vi = vi;
-	chan->portno = port_num;
+	chan->portnos[0] = port_num;
+	chan->numgangports = 1;
 	chan->of_node = node;
 	list_add_tail(&chan->list, &vi->vi_chans);
 
@@ -1312,7 +1302,7 @@ static int tegra_vi_channels_init(struct tegra_vi *vi)
 		if (ret < 0) {
 			dev_err(vi->dev,
 				"failed to initialize channel-%d: %d\n",
-				chan->portno, ret);
+				chan->portnos[0], ret);
 			goto cleanup;
 		}
 	}
@@ -1326,6 +1316,46 @@ static int tegra_vi_channels_init(struct tegra_vi *vi)
 	return ret;
 }
 
+static int tegra_vi_channel_syncpt_init(struct tegra_vi *vi)
+{
+	struct tegra_vi_channel *chan;
+	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
+	struct host1x_syncpt *fs_sp;
+	struct host1x_syncpt *mw_sp;
+	int ret, i;
+
+	list_for_each_entry(chan, &vi->vi_chans, list) {
+		for (i = 0; i < chan->numgangports; i++) {
+			fs_sp = host1x_syncpt_request(&vi->client, flags);
+			if (!fs_sp) {
+				dev_err(vi->dev, "failed to request frame start syncpoint\n");
+				ret = -ENOMEM;
+				goto free_syncpts;
+			}
+
+			mw_sp = host1x_syncpt_request(&vi->client, flags);
+			if (!mw_sp) {
+				dev_err(vi->dev, "failed to request memory ack syncpoint\n");
+				ret = -ENOMEM;
+				host1x_syncpt_free(fs_sp);
+				goto free_syncpts;
+			}
+
+			chan->frame_start_sp[i] = fs_sp;
+			chan->mw_ack_sp[i] = mw_sp;
+			spin_lock_init(&chan->sp_incr_lock[i]);
+		}
+	}
+
+	return 0;
+
+free_syncpts:
+	list_for_each_entry_continue_reverse(chan, &vi->vi_chans, list)
+		tegra_channel_host1x_syncpts_free(chan);
+
+	return ret;
+}
+
 void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid)
 {
 	struct tegra_vi *vi = vid->vi;
@@ -1576,8 +1606,9 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
 	struct v4l2_async_subdev *asd;
 	struct v4l2_subdev *subdev;
 	struct tegra_vi_channel *chan;
+	struct tegra_csi_channel *csi_chan;
 	struct tegra_vi *vi;
-	int ret;
+	int ret, i;
 
 	chan = container_of(notifier, struct tegra_vi_channel, notifier);
 	vi = chan->vi;
@@ -1634,6 +1665,15 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
 
 	v4l2_set_subdev_hostdata(subdev, chan);
 
+	/*
+	 * Retrieve number of gang ports from csi channel and add consecutive
+	 * ports to vi channel ports.
+	 */
+	csi_chan = v4l2_get_subdevdata(subdev);
+	chan->numgangports = csi_chan->numgangports;
+	for (i = 1; i < chan->numgangports; i++)
+		chan->portnos[i] = chan->portnos[0] + i * CSI_PORTS_PER_BRICK;
+
 	subdev = tegra_channel_get_remote_source_subdev(chan);
 	v4l2_set_subdev_hostdata(subdev, chan);
 
@@ -1759,7 +1799,8 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
 	 * next channels.
 	 */
 	list_for_each_entry(chan, &vi->vi_chans, list) {
-		remote = fwnode_graph_get_remote_node(fwnode, chan->portno, 0);
+		remote = fwnode_graph_get_remote_node(fwnode, chan->portnos[0],
+						      0);
 		if (!remote)
 			continue;
 
@@ -1774,7 +1815,7 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
 		if (ret < 0) {
 			dev_err(vi->dev,
 				"failed to register channel %d notifier: %d\n",
-				chan->portno, ret);
+				chan->portnos[0], ret);
 			v4l2_async_notifier_cleanup(&chan->notifier);
 		}
 	}
@@ -1825,11 +1866,27 @@ static int tegra_vi_init(struct host1x_client *client)
 	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
 		ret = tegra_vi_graph_init(vi);
 		if (ret < 0)
-			goto free_chans;
+			goto cleanup_chans;
 	}
 
+	/*
+	 * x8 capture uses multiple ports as gang with simultaneous capture
+	 * of left half onto one x4 port and right half onto second x4 port.
+	 * Total ports grouped together as gang is retrieved from CSI subdev
+	 * during graph build.
+	 * So, create host1x syncpts for all ports in a gang after graph init.
+	 */
+	ret = tegra_vi_channel_syncpt_init(vi);
+	if (ret < 0)
+		goto cleanup_graph;
+
 	return 0;
 
+cleanup_graph:
+	tegra_vi_graph_cleanup(vi);
+cleanup_chans:
+	list_for_each_entry(chan, &vi->vi_chans, list)
+		tegra_channel_cleanup(chan);
 free_chans:
 	list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
 		list_del(&chan->list);
diff --git a/drivers/staging/media/tegra-video/vi.h b/drivers/staging/media/tegra-video/vi.h
index 7d6b7a6..947e641 100644
--- a/drivers/staging/media/tegra-video/vi.h
+++ b/drivers/staging/media/tegra-video/vi.h
@@ -21,6 +21,8 @@
 #include <media/v4l2-subdev.h>
 #include <media/videobuf2-v4l2.h>
 
+#include "csi.h"
+
 #define TEGRA_MIN_WIDTH		32U
 #define TEGRA_MAX_WIDTH		32768U
 #define TEGRA_MIN_HEIGHT	32U
@@ -31,6 +33,7 @@
 #define TEGRA_IMAGE_FORMAT_DEF	32
 
 #define MAX_FORMAT_NUM		64
+#define SURFACE_ALIGN_BYTES	64
 
 enum tegra_vi_pg_mode {
 	TEGRA_VI_PG_DISABLED = 0,
@@ -151,7 +154,8 @@ struct tegra_vi_graph_entity {
  * @done: list of capture done queued buffers
  * @done_lock: protects the capture done queue list
  *
- * @portno: VI channel port number
+ * @portnos: VI channel port numbers
+ * @numgangports: number of ports combined together as a gang for capture
  * @of_node: device node of VI channel
  *
  * @ctrl_handler: V4L2 control handler of this video channel
@@ -168,10 +172,10 @@ struct tegra_vi_channel {
 	struct media_pad pad;
 
 	struct tegra_vi *vi;
-	struct host1x_syncpt *frame_start_sp;
-	struct host1x_syncpt *mw_ack_sp;
+	struct host1x_syncpt *frame_start_sp[GANG_PORTS_MAX];
+	struct host1x_syncpt *mw_ack_sp[GANG_PORTS_MAX];
 	/* protects the cpu syncpoint increment */
-	spinlock_t sp_incr_lock;
+	spinlock_t sp_incr_lock[GANG_PORTS_MAX];
 
 	struct task_struct *kthread_start_capture;
 	wait_queue_head_t start_wait;
@@ -190,7 +194,8 @@ struct tegra_vi_channel {
 	/* protects the capture done queue list */
 	spinlock_t done_lock;
 
-	unsigned char portno;
+	unsigned char portnos[GANG_PORTS_MAX];
+	u8 numgangports;
 	struct device_node *of_node;
 
 	struct v4l2_ctrl_handler ctrl_handler;
@@ -216,7 +221,7 @@ struct tegra_channel_buffer {
 	struct list_head queue;
 	struct tegra_vi_channel *chan;
 	dma_addr_t addr;
-	u32 mw_ack_sp_thresh;
+	u32 mw_ack_sp_thresh[GANG_PORTS_MAX];
 };
 
 /*
-- 
2.7.4


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

* Re: [PATCH v2 03/10] media: tegra-video: Fix V4L2 pixel format for RGB888_1X24
  2020-10-27  3:19 ` [PATCH v2 03/10] media: tegra-video: Fix V4L2 pixel format for RGB888_1X24 Sowjanya Komatineni
@ 2020-11-09 12:51   ` Hans Verkuil
  0 siblings, 0 replies; 14+ messages in thread
From: Hans Verkuil @ 2020-11-09 12:51 UTC (permalink / raw)
  To: Sowjanya Komatineni, thierry.reding, jonathanh
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

On 27/10/2020 04:19, Sowjanya Komatineni wrote:
> V4L2 pixel format is incorrect for RGB888_1X24.
> 
> This patch fixes it.
> 
> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
> ---
>  drivers/staging/media/tegra-video/tegra210.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
> 
> diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
> index 6b23aa7..c883925 100644
> --- a/drivers/staging/media/tegra-video/tegra210.c
> +++ b/drivers/staging/media/tegra-video/tegra210.c
> @@ -619,7 +619,7 @@ static const struct tegra_video_format tegra210_video_formats[] = {
>  	TEGRA210_VIDEO_FMT(RAW12, 12, SGBRG12_1X12, 2, T_R16_I, SGBRG12),
>  	TEGRA210_VIDEO_FMT(RAW12, 12, SBGGR12_1X12, 2, T_R16_I, SBGGR12),
>  	/* RGB888 */
> -	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X24, 4, T_A8R8G8B8, RGB24),
> +	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X24, 4, T_A8R8G8B8, XRGB32),

This is the wrong way around, it should be XBGR32. V4L2 defines the pixelformat
as the order of the color components in memory. Typically for a little endian
system that means that the four bytes are reversed when DMAed.

The same is true for the YUYV variants.

This patch fixes the order for RGB and YUYV:

--------------------------------------------------------
diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
index aff56fcdc400..063d0a33bf71 100644
--- a/drivers/staging/media/tegra-video/tegra210.c
+++ b/drivers/staging/media/tegra-video/tegra210.c
@@ -693,19 +693,19 @@ static const struct tegra_video_format tegra210_video_formats[] = {
 	TEGRA210_VIDEO_FMT(RAW12, 12, SGBRG12_1X12, 2, T_R16_I, SGBRG12),
 	TEGRA210_VIDEO_FMT(RAW12, 12, SBGGR12_1X12, 2, T_R16_I, SBGGR12),
 	/* RGB888 */
-	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X24, 4, T_A8R8G8B8, XRGB32),
+	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X24, 4, T_A8R8G8B8, XBGR32),
 	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X32_PADHI, 4, T_A8B8G8R8,
-			   XBGR32),
+			   RGBX32),
 	/* YUV422 */
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, UYVY8_1X16, 2, T_U8_Y8__V8_Y8, UYVY),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, VYUY8_1X16, 2, T_V8_Y8__U8_Y8, VYUY),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, YUYV8_1X16, 2, T_Y8_U8__Y8_V8, YUYV),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, YVYU8_1X16, 2, T_Y8_V8__Y8_U8, YVYU),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, UYVY8_1X16, 2, T_U8_Y8__V8_Y8, YVYU),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, VYUY8_1X16, 2, T_V8_Y8__U8_Y8, YUYV),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, YUYV8_1X16, 2, T_Y8_U8__Y8_V8, VYUY),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, YVYU8_1X16, 2, T_Y8_V8__Y8_U8, UYVY),
 	TEGRA210_VIDEO_FMT(YUV422_8, 16, UYVY8_1X16, 1, T_Y8__V8U8_N422, NV16),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, UYVY8_2X8, 2, T_U8_Y8__V8_Y8, UYVY),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, VYUY8_2X8, 2, T_V8_Y8__U8_Y8, VYUY),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, YUYV8_2X8, 2, T_Y8_U8__Y8_V8, YUYV),
-	TEGRA210_VIDEO_FMT(YUV422_8, 16, YVYU8_2X8, 2, T_Y8_V8__Y8_U8, YVYU),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, UYVY8_2X8, 2, T_U8_Y8__V8_Y8, YVYU),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, VYUY8_2X8, 2, T_V8_Y8__U8_Y8, YUYV),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, YUYV8_2X8, 2, T_Y8_U8__Y8_V8, VYUY),
+	TEGRA210_VIDEO_FMT(YUV422_8, 16, YVYU8_2X8, 2, T_Y8_V8__Y8_U8, UYVY),
 };

 /* Tegra210 VI operations */
--------------------------------------------------------

Regards,

	Hans

>  	TEGRA210_VIDEO_FMT(RGB888, 24, RGB888_1X32_PADHI, 4, T_A8B8G8R8,
>  			   XBGR32),
>  	/* YUV422 */
> 


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

* Re: [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports
  2020-10-27  3:19 ` [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports Sowjanya Komatineni
@ 2020-11-09 13:04   ` Hans Verkuil
  2020-11-09 16:22     ` Sowjanya Komatineni
  0 siblings, 1 reply; 14+ messages in thread
From: Hans Verkuil @ 2020-11-09 13:04 UTC (permalink / raw)
  To: Sowjanya Komatineni, thierry.reding, jonathanh
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

Hi Sowjanya,

On 27/10/2020 04:19, Sowjanya Komatineni wrote:
> Tegra VI/CSI hardware don't have native 8 lane capture support.
> 
> Each CSI port has max 4 lanes only. So for x8 captures, consecutive
> ports are ganged up for left half and right half captures on to each
> x4 ports with buffer offsets based on source image split width to align
> side-by-side.
> 
> All ports in gang are configured together during the corresponding
> video device node streaming for x8 captures.
> 
> x8 capture with gang ports are supported with HDMI-to-CSI bridges
> where they split 4K image into left half onto one x4 port and
> right half onto second x4 port.
> 
> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
> ---
>  drivers/staging/media/tegra-video/csi.c      |  47 ++++-
>  drivers/staging/media/tegra-video/csi.h      |  14 +-
>  drivers/staging/media/tegra-video/tegra210.c | 304 ++++++++++++++++++---------
>  drivers/staging/media/tegra-video/vi.c       | 117 ++++++++---
>  drivers/staging/media/tegra-video/vi.h       |  17 +-
>  5 files changed, 347 insertions(+), 152 deletions(-)
> 
> diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c
> index a19c85c..a6c54f8 100644
> --- a/drivers/staging/media/tegra-video/csi.c
> +++ b/drivers/staging/media/tegra-video/csi.c
> @@ -253,13 +253,14 @@ static unsigned int csi_get_pixel_rate(struct tegra_csi_channel *csi_chan)
>  }
>  
>  void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
> +				u8 csi_port_num,
>  				u8 *clk_settle_time,
>  				u8 *ths_settle_time)
>  {
>  	struct tegra_csi *csi = csi_chan->csi;
>  	unsigned int cil_clk_mhz;
>  	unsigned int pix_clk_mhz;
> -	int clk_idx = (csi_chan->csi_port_num >> 1) + 1;
> +	int clk_idx = (csi_port_num >> 1) + 1;
>  
>  	cil_clk_mhz = clk_get_rate(csi->clks[clk_idx].clk) / MHZ;
>  	pix_clk_mhz = csi_get_pixel_rate(csi_chan) / MHZ;
> @@ -410,7 +411,7 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi,
>  				   unsigned int num_pads)
>  {
>  	struct tegra_csi_channel *chan;
> -	int ret = 0;
> +	int ret = 0, i;
>  
>  	chan = kzalloc(sizeof(*chan), GFP_KERNEL);
>  	if (!chan)
> @@ -418,8 +419,21 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi,
>  
>  	list_add_tail(&chan->list, &csi->csi_chans);
>  	chan->csi = csi;
> -	chan->csi_port_num = port_num;
> -	chan->numlanes = lanes;
> +	/*
> +	 * Each CSI brick has maximum of 4 lanes.
> +	 * For lanes more than 4, use multiple of immediate CSI bricks as gang.
> +	 */
> +	if (lanes <= CSI_LANES_PER_BRICK) {
> +		chan->numlanes = lanes;
> +		chan->numgangports = 1;
> +	} else {
> +		chan->numlanes = CSI_LANES_PER_BRICK;
> +		chan->numgangports = lanes / CSI_LANES_PER_BRICK;
> +	}
> +
> +	for (i = 0; i < chan->numgangports; i++)
> +		chan->csi_port_nums[i] = port_num + i * CSI_PORTS_PER_BRICK;
> +
>  	chan->of_node = node;
>  	chan->numpads = num_pads;
>  	if (num_pads & 0x2) {
> @@ -461,9 +475,6 @@ static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
>  static int tegra_csi_channels_alloc(struct tegra_csi *csi)
>  {
>  	struct device_node *node = csi->dev->of_node;
> -	struct v4l2_fwnode_endpoint v4l2_ep = {
> -		.bus_type = V4L2_MBUS_CSI2_DPHY
> -	};
>  	struct fwnode_handle *fwh;
>  	struct device_node *channel;
>  	struct device_node *ep;
> @@ -471,6 +482,8 @@ static int tegra_csi_channels_alloc(struct tegra_csi *csi)
>  	int ret;
>  
>  	for_each_child_of_node(node, channel) {
> +		struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 };
> +
>  		if (!of_node_name_eq(channel, "channel"))
>  			continue;
>  
> @@ -499,8 +512,20 @@ static int tegra_csi_channels_alloc(struct tegra_csi *csi)
>  			goto err_node_put;
>  		}
>  
> -		lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
> -		if (!lanes || ((lanes & (lanes - 1)) != 0)) {
> +		if (v4l2_ep.bus_type == V4L2_MBUS_PARALLEL)
> +			lanes = v4l2_ep.bus.parallel.bus_width;
> +		else
> +			lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
> +
> +		/*
> +		 * Each CSI brick has maximum 4 data lanes.
> +		 * For lanes more than 4, validate lanes to be multiple of 4
> +		 * so multiple of consecutive CSI bricks can be ganged up for
> +		 * streaming.
> +		 */
> +		if (!lanes || ((lanes & (lanes - 1)) != 0) ||
> +		    (lanes > CSI_LANES_PER_BRICK && (((lanes & 3) != 0) ||
> +		    ((portno & 1) != 0)))) {
>  			dev_err(csi->dev, "invalid data-lanes %d for %pOF\n",
>  				lanes, channel);
>  			ret = -EINVAL;
> @@ -544,7 +569,7 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
>  	subdev->dev = csi->dev;
>  	if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
>  		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg",
> -			 chan->csi_port_num);
> +			 chan->csi_port_nums[0]);
>  	else
>  		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
>  			 kbasename(chan->of_node->full_name));
> @@ -596,7 +621,7 @@ static int tegra_csi_channels_init(struct tegra_csi *csi)
>  		if (ret) {
>  			dev_err(csi->dev,
>  				"failed to initialize channel-%d: %d\n",
> -				chan->csi_port_num, ret);
> +				chan->csi_port_nums[0], ret);
>  			return ret;
>  		}
>  	}
> diff --git a/drivers/staging/media/tegra-video/csi.h b/drivers/staging/media/tegra-video/csi.h
> index c65ff73..386f7c6 100644
> --- a/drivers/staging/media/tegra-video/csi.h
> +++ b/drivers/staging/media/tegra-video/csi.h
> @@ -17,6 +17,10 @@
>   * CILB.
>   */
>  #define CSI_PORTS_PER_BRICK	2
> +#define CSI_LANES_PER_BRICK	4
> +
> +/* Maximum 2 CSI x4 ports can be ganged up for streaming */
> +#define GANG_PORTS_MAX	2
>  
>  /* each CSI channel can have one sink and one source pads */
>  #define TEGRA_CSI_PADS_NUM	2
> @@ -43,8 +47,10 @@ struct tegra_csi;
>   * @numpads: number of pads.
>   * @csi: Tegra CSI device structure
>   * @of_node: csi device tree node
> - * @numlanes: number of lanes used per port/channel
> - * @csi_port_num: CSI channel port number
> + * @numgangports: number of immediate ports ganged up to meet the
> + *             channel bus-width
> + * @numlanes: number of lanes used per port
> + * @csi_port_nums: CSI channel port numbers
>   * @pg_mode: test pattern generator mode for channel
>   * @format: active format of the channel
>   * @framerate: active framerate for TPG
> @@ -60,8 +66,9 @@ struct tegra_csi_channel {
>  	unsigned int numpads;
>  	struct tegra_csi *csi;
>  	struct device_node *of_node;
> +	u8 numgangports;
>  	unsigned int numlanes;
> -	u8 csi_port_num;
> +	u8 csi_port_nums[GANG_PORTS_MAX];
>  	u8 pg_mode;
>  	struct v4l2_mbus_framefmt format;
>  	unsigned int framerate;
> @@ -150,6 +157,7 @@ extern const struct tegra_csi_soc tegra210_csi_soc;
>  
>  void tegra_csi_error_recover(struct v4l2_subdev *subdev);
>  void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
> +				u8 csi_port_num,
>  				u8 *clk_settle_time,
>  				u8 *ths_settle_time);
>  #endif
> diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
> index 929d277..aff56fc 100644
> --- a/drivers/staging/media/tegra-video/tegra210.c
> +++ b/drivers/staging/media/tegra-video/tegra210.c
> @@ -149,21 +149,22 @@ static u32 tegra_vi_read(struct tegra_vi_channel *chan, unsigned int addr)
>  }
>  
>  /* Tegra210 VI_CSI registers accessors */
> -static void vi_csi_write(struct tegra_vi_channel *chan, unsigned int addr,
> -			 u32 val)
> +static void vi_csi_write(struct tegra_vi_channel *chan, u8 portno,
> +			 unsigned int addr, u32 val)
>  {
>  	void __iomem *vi_csi_base;
>  
> -	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(chan->portno);
> +	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(portno);
>  
>  	writel_relaxed(val, vi_csi_base + addr);
>  }
>  
> -static u32 vi_csi_read(struct tegra_vi_channel *chan, unsigned int addr)
> +static u32 vi_csi_read(struct tegra_vi_channel *chan, u8 portno,
> +		       unsigned int addr)
>  {
>  	void __iomem *vi_csi_base;
>  
> -	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(chan->portno);
> +	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(portno);
>  
>  	return readl_relaxed(vi_csi_base + addr);
>  }
> @@ -171,7 +172,8 @@ static u32 vi_csi_read(struct tegra_vi_channel *chan, unsigned int addr)
>  /*
>   * Tegra210 VI channel capture operations
>   */
> -static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
> +static int tegra_channel_capture_setup(struct tegra_vi_channel *chan,
> +				       u8 portno)
>  {
>  	u32 height = chan->format.height;
>  	u32 width = chan->format.width;
> @@ -192,19 +194,30 @@ static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
>  	    data_type == TEGRA_IMAGE_DT_RGB888)
>  		bypass_pixel_transform = 0;
>  
> -	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DEF,
> +	/*
> +	 * For x8 source streaming, the source image is split onto two x4 ports
> +	 * with left half to first x4 port and right half to second x4 port.
> +	 * So, use split width and corresponding word count for each x4 port.
> +	 */

This is a mismatch with tc358840: that HDMI receiver only enables ganging mode
if the width > 1920. Here it is always enabled. As a result streaming will fail
if the source transmits 1920x1080p60 since the second CSI port will never receive
data, leading to endless syncpt timeouts.

IMHO it is better to disable ganging for formats with a width <= 1920. There are
some weird formats that might give issues if the width is devices by 2 (1366x768)
and in general using just one port for such formats is just simpler.

Regarding the endless syncpt timeouts: in my experience this is an indication that
something is seriously wrong. If more than X successive timeouts are seen then the
driver should call vb2_queue_error. I recommend using a V4L2 control to set the
threshold X (e.g. V4L2_CID_TEGRA_SYNCPT_TIMEOUT_RETRY).

> +	if (chan->numgangports > 1) {
> +		width = width >> 1;
> +		word_count = (width * chan->fmtinfo->bit_width) / 8;
> +	}
> +
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_DEF,
>  		     bypass_pixel_transform |
>  		     (format << IMAGE_DEF_FORMAT_OFFSET) |
>  		     IMAGE_DEF_DEST_MEM);
> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DT, data_type);
> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_SIZE_WC, word_count);
> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_SIZE,
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_DT, data_type);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_SIZE_WC, word_count);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_SIZE,
>  		     (height << IMAGE_SIZE_HEIGHT_OFFSET) | width);
>  	return 0;
>  }
>  
> -static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan)
> +static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan,
> +					u8 portno)
>  {
>  	/* disable clock gating to enable continuous clock */
>  	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, 0);
> @@ -212,15 +225,16 @@ static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan)
>  	 * Soft reset memory client interface, pixel format logic, sensor
>  	 * control logic, and a shadow copy logic to bring VI to clean state.
>  	 */
> -	vi_csi_write(chan, TEGRA_VI_CSI_SW_RESET, 0xf);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SW_RESET, 0xf);
>  	usleep_range(100, 200);
> -	vi_csi_write(chan, TEGRA_VI_CSI_SW_RESET, 0x0);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SW_RESET, 0x0);
>  
>  	/* enable back VI clock gating */
>  	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, VI_CG_2ND_LEVEL_EN);
>  }
>  
> -static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
> +static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan,
> +						u8 portno)
>  {
>  	struct v4l2_subdev *subdev;
>  	u32 val;
> @@ -232,9 +246,9 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
>  	 * events which can cause CSI and VI hardware hang.
>  	 * This helps to have a clean capture for next frame.
>  	 */
> -	val = vi_csi_read(chan, TEGRA_VI_CSI_ERROR_STATUS);
> +	val = vi_csi_read(chan, portno, TEGRA_VI_CSI_ERROR_STATUS);
>  	dev_dbg(&chan->video.dev, "TEGRA_VI_CSI_ERROR_STATUS 0x%08x\n", val);
> -	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, val);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_ERROR_STATUS, val);
>  
>  	val = tegra_vi_read(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR);
>  	dev_dbg(&chan->video.dev,
> @@ -242,8 +256,8 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
>  	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR, val);
>  
>  	/* recover VI by issuing software reset and re-setup for capture */
> -	tegra_channel_vi_soft_reset(chan);
> -	tegra_channel_capture_setup(chan);
> +	tegra_channel_vi_soft_reset(chan, portno);
> +	tegra_channel_capture_setup(chan, portno);
>  
>  	/* recover CSI block */
>  	subdev = tegra_channel_get_remote_csi_subdev(chan);
> @@ -282,80 +296,114 @@ static void release_buffer(struct tegra_vi_channel *chan,
>  	vb2_buffer_done(&vb->vb2_buf, state);
>  }
>  
> -static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
> -				       struct tegra_channel_buffer *buf)
> +static void tegra_channel_vi_buffer_setup(struct tegra_vi_channel *chan,
> +					  u8 portno, u32 buf_offset,
> +					  struct tegra_channel_buffer *buf)
>  {
> -	u32 thresh, value, frame_start, mw_ack_done;
> -	int bytes_per_line = chan->format.bytesperline;
> +	int bytesperline = chan->format.bytesperline;
>  	u32 sizeimage = chan->format.sizeimage;
> -	int err;
>  
>  	/* program buffer address by using surface 0 */
> -	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_OFFSET_MSB,
> -		     (u64)buf->addr >> 32);
> -	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_OFFSET_LSB, buf->addr);
> -	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_STRIDE, bytes_per_line);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_OFFSET_MSB,
> +		     ((u64)buf->addr + buf_offset) >> 32);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_OFFSET_LSB,
> +		     buf->addr + buf_offset);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_STRIDE, bytesperline);
>  
> +	if (chan->fmtinfo->fourcc != V4L2_PIX_FMT_NV16)
> +		return;
>  	/*
>  	 * Program surface 1 for UV plane with offset sizeimage from Y plane.
>  	 */
> -	if (chan->fmtinfo->fourcc == V4L2_PIX_FMT_NV16) {
> -		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
> -			     ((u64)buf->addr + sizeimage / 2) >> 32);
> -		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
> -			     buf->addr + sizeimage / 2);
> -		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_STRIDE,
> -			     bytes_per_line);
> -	}
> -
> -	/*
> -	 * Tegra VI block interacts with host1x syncpt for synchronizing
> -	 * programmed condition of capture state and hardware operation.
> -	 * Frame start and Memory write acknowledge syncpts has their own
> -	 * FIFO of depth 2.
> -	 *
> -	 * Syncpoint trigger conditions set through VI_INCR_SYNCPT register
> -	 * are added to HW syncpt FIFO and when the HW triggers, syncpt
> -	 * condition is removed from the FIFO and counter at syncpoint index
> -	 * will be incremented by the hardware and software can wait for
> -	 * counter to reach threshold to synchronize capturing frame with the
> -	 * hardware capture events.
> -	 */
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
> +		     (((u64)buf->addr + sizeimage / 2) + buf_offset) >> 32);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
> +		     buf->addr + sizeimage / 2 + buf_offset);
> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_STRIDE, bytesperline);
> +}
>  
> -	/* increase channel syncpoint threshold for FRAME_START */
> -	thresh = host1x_syncpt_incr_max(chan->frame_start_sp, 1);
> +static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
> +				       struct tegra_channel_buffer *buf)
> +{
> +	u32 thresh, value, frame_start, mw_ack_done;
> +	u32 fs_thresh[GANG_PORTS_MAX];
> +	u8 *portnos = chan->portnos;
> +	int gang_bpl = (chan->format.width >> 1) * chan->fmtinfo->bpp;
> +	u32 buf_offset;
> +	bool capture_timedout = false;
> +	int err, i;
> +
> +	for (i = 0; i < chan->numgangports; i++) {
> +		/*
> +		 * Align buffers side-by-side for all consecutive x4 ports
> +		 * in gang ports using bytes per line based on source split
> +		 * width.
> +		 */
> +		buf_offset = i * roundup(gang_bpl, SURFACE_ALIGN_BYTES);
> +		tegra_channel_vi_buffer_setup(chan, portnos[i], buf_offset,
> +					      buf);
>  
> -	/* Program FRAME_START trigger condition syncpt request */
> -	frame_start = VI_CSI_PP_FRAME_START(chan->portno);
> -	value = VI_CFG_VI_INCR_SYNCPT_COND(frame_start) |
> -		host1x_syncpt_id(chan->frame_start_sp);
> -	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
> +		/*
> +		 * Tegra VI block interacts with host1x syncpt to synchronize
> +		 * programmed condition and hardware operation for capture.
> +		 * Frame start and Memory write acknowledge syncpts has their
> +		 * own FIFO of depth 2.
> +		 *
> +		 * Syncpoint trigger conditions set through VI_INCR_SYNCPT
> +		 * register are added to HW syncpt FIFO and when HW triggers,
> +		 * syncpt condition is removed from the FIFO and counter at
> +		 * syncpoint index will be incremented by the hardware and
> +		 * software can wait for counter to reach threshold to
> +		 * synchronize capturing frame with hardware capture events.
> +		 */
>  
> -	/* increase channel syncpoint threshold for MW_ACK_DONE */
> -	buf->mw_ack_sp_thresh = host1x_syncpt_incr_max(chan->mw_ack_sp, 1);
> +		/* increase channel syncpoint threshold for FRAME_START */
> +		thresh = host1x_syncpt_incr_max(chan->frame_start_sp[i], 1);
> +		fs_thresh[i] = thresh;
> +
> +		/* Program FRAME_START trigger condition syncpt request */
> +		frame_start = VI_CSI_PP_FRAME_START(portnos[i]);
> +		value = VI_CFG_VI_INCR_SYNCPT_COND(frame_start) |
> +			host1x_syncpt_id(chan->frame_start_sp[i]);

This fails if this driver is compiled into the kernel. In that case
tegra_vi_channel_syncpt_init() is called too soon when chan->numgangports
is still 1, so chan->frame_start_sp[1] will be NULL.

Of course, I tested this driver by compiling it into the kernel, so that
took me some debugging to figure out why it crashed :-)

> +		tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
> +
> +		/* increase channel syncpoint threshold for MW_ACK_DONE */
> +		thresh = host1x_syncpt_incr_max(chan->mw_ack_sp[i], 1);
> +		buf->mw_ack_sp_thresh[i] = thresh;
> +
> +		/* Program MW_ACK_DONE trigger condition syncpt request */
> +		mw_ack_done = VI_CSI_MW_ACK_DONE(portnos[i]);
> +		value = VI_CFG_VI_INCR_SYNCPT_COND(mw_ack_done) |
> +			host1x_syncpt_id(chan->mw_ack_sp[i]);
> +		tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
> +	}
>  
> -	/* Program MW_ACK_DONE trigger condition syncpt request */
> -	mw_ack_done = VI_CSI_MW_ACK_DONE(chan->portno);
> -	value = VI_CFG_VI_INCR_SYNCPT_COND(mw_ack_done) |
> -		host1x_syncpt_id(chan->mw_ack_sp);
> -	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
> +	/* enable single shot capture after all ganged ports are ready */
> +	for (i = 0; i < chan->numgangports; i++)
> +		vi_csi_write(chan, portnos[i], TEGRA_VI_CSI_SINGLE_SHOT,
> +			     SINGLE_SHOT_CAPTURE);
>  
> -	/* enable single shot capture */
> -	vi_csi_write(chan, TEGRA_VI_CSI_SINGLE_SHOT, SINGLE_SHOT_CAPTURE);
> +	for (i = 0; i < chan->numgangports; i++) {
> +		/*
> +		 * Wait for syncpt counter to reach frame start event threshold
> +		 */
> +		err = host1x_syncpt_wait(chan->frame_start_sp[i], fs_thresh[i],
> +					 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
> +		if (err) {
> +			capture_timedout = true;
> +			/* increment syncpoint counter for timedout events */
> +			host1x_syncpt_incr(chan->frame_start_sp[i]);
> +			spin_lock(&chan->sp_incr_lock[i]);
> +			host1x_syncpt_incr(chan->mw_ack_sp[i]);
> +			spin_unlock(&chan->sp_incr_lock[i]);
> +			/* clear errors and recover */
> +			tegra_channel_capture_error_recover(chan, portnos[i]);
> +		}
> +	}
>  
> -	/* wait for syncpt counter to reach frame start event threshold */
> -	err = host1x_syncpt_wait(chan->frame_start_sp, thresh,
> -				 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
> -	if (err) {
> +	if (capture_timedout) {
>  		dev_err_ratelimited(&chan->video.dev,
>  				    "frame start syncpt timeout: %d\n", err);
> -		/* increment syncpoint counter for timedout events */
> -		host1x_syncpt_incr(chan->frame_start_sp);
> -		spin_lock(&chan->sp_incr_lock);
> -		host1x_syncpt_incr(chan->mw_ack_sp);
> -		spin_unlock(&chan->sp_incr_lock);
> -		/* clear errors and recover */
> -		tegra_channel_capture_error_recover(chan);
>  		release_buffer(chan, buf, VB2_BUF_STATE_ERROR);
>  		return err;
>  	}
> @@ -376,21 +424,29 @@ static void tegra_channel_capture_done(struct tegra_vi_channel *chan,
>  {
>  	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>  	u32 value;
> -	int ret;
> +	bool capture_timedout = false;
> +	int ret, i;
>  
> -	/* wait for syncpt counter to reach MW_ACK_DONE event threshold */
> -	ret = host1x_syncpt_wait(chan->mw_ack_sp, buf->mw_ack_sp_thresh,
> -				 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
> -	if (ret) {
> -		dev_err_ratelimited(&chan->video.dev,
> -				    "MW_ACK_DONE syncpt timeout: %d\n", ret);
> -		state = VB2_BUF_STATE_ERROR;
> -		/* increment syncpoint counter for timedout event */
> -		spin_lock(&chan->sp_incr_lock);
> -		host1x_syncpt_incr(chan->mw_ack_sp);
> -		spin_unlock(&chan->sp_incr_lock);
> +	for (i = 0; i < chan->numgangports; i++) {
> +		/*
> +		 * Wait for syncpt counter to reach MW_ACK_DONE event threshold
> +		 */
> +		ret = host1x_syncpt_wait(chan->mw_ack_sp[i],
> +					 buf->mw_ack_sp_thresh[i],
> +					 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
> +		if (ret) {
> +			capture_timedout = true;
> +			state = VB2_BUF_STATE_ERROR;
> +			/* increment syncpoint counter for timedout event */
> +			spin_lock(&chan->sp_incr_lock[i]);
> +			host1x_syncpt_incr(chan->mw_ack_sp[i]);
> +			spin_unlock(&chan->sp_incr_lock[i]);
> +		}
>  	}
>  
> +	if (capture_timedout)
> +		dev_err_ratelimited(&chan->video.dev,
> +				    "MW_ACK_DONE syncpt timeout: %d\n", ret);
>  	release_buffer(chan, buf, state);
>  }
>  
> @@ -463,14 +519,12 @@ static int tegra210_vi_start_streaming(struct vb2_queue *vq, u32 count)
>  	struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
>  	struct media_pipeline *pipe = &chan->video.pipe;
>  	u32 val;
> -	int ret;
> +	u8 *portnos = chan->portnos;
> +	int ret, i;
>  
>  	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, VI_CG_2ND_LEVEL_EN);
>  
> -	/* clear errors */
> -	val = vi_csi_read(chan, TEGRA_VI_CSI_ERROR_STATUS);
> -	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, val);
> -
> +	/* clear syncpt errors */
>  	val = tegra_vi_read(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR);
>  	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR, val);
>  
> @@ -489,7 +543,14 @@ static int tegra210_vi_start_streaming(struct vb2_queue *vq, u32 count)
>  	if (ret < 0)
>  		goto error_pipeline_start;
>  
> -	tegra_channel_capture_setup(chan);
> +	/* clear csi errors and do capture setup for all ports in gang mode */
> +	for (i = 0; i < chan->numgangports; i++) {
> +		val = vi_csi_read(chan, portnos[i], TEGRA_VI_CSI_ERROR_STATUS);
> +		vi_csi_write(chan, portnos[i], TEGRA_VI_CSI_ERROR_STATUS, val);
> +
> +		tegra_channel_capture_setup(chan, portnos[i]);
> +	}
> +
>  	ret = tegra_channel_set_stream(chan, true);
>  	if (ret < 0)
>  		goto error_set_stream;
> @@ -743,10 +804,10 @@ static void tpg_write(struct tegra_csi *csi, u8 portno, unsigned int addr,
>  /*
>   * Tegra210 CSI operations
>   */
> -static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
> +static void tegra210_csi_port_recover(struct tegra_csi_channel *csi_chan,
> +				      u8 portno)
>  {
>  	struct tegra_csi *csi = csi_chan->csi;
> -	unsigned int portno = csi_chan->csi_port_num;
>  	u32 val;
>  
>  	/*
> @@ -795,16 +856,26 @@ static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
>  	}
>  }
>  
> -static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
> +static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
> +{
> +	u8 *portnos = csi_chan->csi_port_nums;
> +	int i;
> +
> +	for (i = 0; i < csi_chan->numgangports; i++)
> +		tegra210_csi_port_recover(csi_chan, portnos[i]);
> +}
> +
> +static int
> +tegra210_csi_port_start_streaming(struct tegra_csi_channel *csi_chan,
> +				  u8 portno)
>  {
>  	struct tegra_csi *csi = csi_chan->csi;
> -	unsigned int portno = csi_chan->csi_port_num;
>  	u8 clk_settle_time = 0;
>  	u8 ths_settle_time = 10;
>  	u32 val;
>  
>  	if (!csi_chan->pg_mode)
> -		tegra_csi_calc_settle_time(csi_chan, &clk_settle_time,
> +		tegra_csi_calc_settle_time(csi_chan, portno, &clk_settle_time,
>  					   &ths_settle_time);
>  
>  	csi_write(csi, portno, TEGRA_CSI_CLKEN_OVERRIDE, 0);
> @@ -903,10 +974,10 @@ static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
>  	return 0;
>  }
>  
> -static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
> +static void
> +tegra210_csi_port_stop_streaming(struct tegra_csi_channel *csi_chan, u8 portno)
>  {
>  	struct tegra_csi *csi = csi_chan->csi;
> -	unsigned int portno = csi_chan->csi_port_num;
>  	u32 val;
>  
>  	val = pp_read(csi, portno, TEGRA_CSI_PIXEL_PARSER_STATUS);
> @@ -944,6 +1015,35 @@ static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
>  	}
>  }
>  
> +static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
> +{
> +	u8 *portnos = csi_chan->csi_port_nums;
> +	int ret, i;
> +
> +	for (i = 0; i < csi_chan->numgangports; i++) {
> +		ret = tegra210_csi_port_start_streaming(csi_chan, portnos[i]);
> +		if (ret)
> +			goto stream_start_fail;
> +	}
> +
> +	return 0;
> +
> +stream_start_fail:
> +	for (i = i - 1; i >= 0; i--)
> +		tegra210_csi_port_stop_streaming(csi_chan, portnos[i]);
> +
> +	return ret;
> +}
> +
> +static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
> +{
> +	u8 *portnos = csi_chan->csi_port_nums;
> +	int i;
> +
> +	for (i = 0; i < csi_chan->numgangports; i++)
> +		tegra210_csi_port_stop_streaming(csi_chan, portnos[i]);
> +}
> +
>  /*
>   * Tegra210 CSI TPG frame rate table with horizontal and vertical
>   * blanking intervals for corresponding format and resolution.
> diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
> index e866f7d..4812f2c 100644
> --- a/drivers/staging/media/tegra-video/vi.c
> +++ b/drivers/staging/media/tegra-video/vi.c
> @@ -30,7 +30,6 @@
>  #include "vi.h"
>  #include "video.h"
>  
> -#define SURFACE_ALIGN_BYTES		64
>  #define MAX_CID_CONTROLS		1
>  
>  static const struct tegra_video_format tegra_default_format = {
> @@ -1090,12 +1089,20 @@ static int vi_fmts_bitmap_init(struct tegra_vi_channel *chan)
>  	return 0;
>  }
>  
> +static void tegra_channel_host1x_syncpts_free(struct tegra_vi_channel *chan)
> +{
> +	int i;
> +
> +	for (i = 0; i < chan->numgangports; i++) {
> +		host1x_syncpt_free(chan->mw_ack_sp[i]);
> +		host1x_syncpt_free(chan->frame_start_sp[i]);
> +	}
> +}
> +
>  static void tegra_channel_cleanup(struct tegra_vi_channel *chan)
>  {
>  	v4l2_ctrl_handler_free(&chan->ctrl_handler);
>  	media_entity_cleanup(&chan->video.entity);
> -	host1x_syncpt_free(chan->mw_ack_sp);
> -	host1x_syncpt_free(chan->frame_start_sp);
>  	mutex_destroy(&chan->video_lock);
>  }
>  
> @@ -1107,6 +1114,7 @@ void tegra_channels_cleanup(struct tegra_vi *vi)
>  		return;
>  
>  	list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
> +		tegra_channel_host1x_syncpts_free(chan);
>  		tegra_channel_cleanup(chan);
>  		list_del(&chan->list);
>  		kfree(chan);
> @@ -1117,7 +1125,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>  {
>  	struct tegra_vi *vi = chan->vi;
>  	struct tegra_video_device *vid = dev_get_drvdata(vi->client.host);
> -	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
>  	int ret;
>  
>  	mutex_init(&chan->video_lock);
> @@ -1125,7 +1132,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>  	INIT_LIST_HEAD(&chan->done);
>  	spin_lock_init(&chan->start_lock);
>  	spin_lock_init(&chan->done_lock);
> -	spin_lock_init(&chan->sp_incr_lock);
>  	init_waitqueue_head(&chan->start_wait);
>  	init_waitqueue_head(&chan->done_wait);
>  
> @@ -1140,26 +1146,13 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>  	chan->format.sizeimage = chan->format.bytesperline * TEGRA_DEF_HEIGHT;
>  	tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
>  
> -	chan->frame_start_sp = host1x_syncpt_request(&vi->client, flags);
> -	if (!chan->frame_start_sp) {
> -		dev_err(vi->dev, "failed to request frame start syncpoint\n");
> -		return -ENOMEM;
> -	}
> -
> -	chan->mw_ack_sp = host1x_syncpt_request(&vi->client, flags);
> -	if (!chan->mw_ack_sp) {
> -		dev_err(vi->dev, "failed to request memory ack syncpoint\n");
> -		ret = -ENOMEM;
> -		goto free_fs_syncpt;
> -	}
> -
>  	/* initialize the media entity */
>  	chan->pad.flags = MEDIA_PAD_FL_SINK;
>  	ret = media_entity_pads_init(&chan->video.entity, 1, &chan->pad);
>  	if (ret < 0) {
>  		dev_err(vi->dev,
>  			"failed to initialize media entity: %d\n", ret);
> -		goto free_mw_ack_syncpt;
> +		return ret;
>  	}
>  
>  	ret = v4l2_ctrl_handler_init(&chan->ctrl_handler, MAX_CID_CONTROLS);
> @@ -1175,7 +1168,7 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>  	chan->video.release = video_device_release_empty;
>  	chan->video.queue = &chan->queue;
>  	snprintf(chan->video.name, sizeof(chan->video.name), "%s-%s-%u",
> -		 dev_name(vi->dev), "output", chan->portno);
> +		 dev_name(vi->dev), "output", chan->portnos[0]);
>  	chan->video.vfl_type = VFL_TYPE_VIDEO;
>  	chan->video.vfl_dir = VFL_DIR_RX;
>  	chan->video.ioctl_ops = &tegra_channel_ioctl_ops;
> @@ -1211,10 +1204,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>  	v4l2_ctrl_handler_free(&chan->ctrl_handler);
>  cleanup_media:
>  	media_entity_cleanup(&chan->video.entity);
> -free_mw_ack_syncpt:
> -	host1x_syncpt_free(chan->mw_ack_sp);
> -free_fs_syncpt:
> -	host1x_syncpt_free(chan->frame_start_sp);
>  	return ret;
>  }
>  
> @@ -1234,7 +1223,8 @@ static int tegra_vi_channel_alloc(struct tegra_vi *vi, unsigned int port_num,
>  		return -ENOMEM;
>  
>  	chan->vi = vi;
> -	chan->portno = port_num;
> +	chan->portnos[0] = port_num;
> +	chan->numgangports = 1;
>  	chan->of_node = node;
>  	list_add_tail(&chan->list, &vi->vi_chans);
>  
> @@ -1312,7 +1302,7 @@ static int tegra_vi_channels_init(struct tegra_vi *vi)
>  		if (ret < 0) {
>  			dev_err(vi->dev,
>  				"failed to initialize channel-%d: %d\n",
> -				chan->portno, ret);
> +				chan->portnos[0], ret);
>  			goto cleanup;
>  		}
>  	}
> @@ -1326,6 +1316,46 @@ static int tegra_vi_channels_init(struct tegra_vi *vi)
>  	return ret;
>  }
>  
> +static int tegra_vi_channel_syncpt_init(struct tegra_vi *vi)
> +{
> +	struct tegra_vi_channel *chan;
> +	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
> +	struct host1x_syncpt *fs_sp;
> +	struct host1x_syncpt *mw_sp;
> +	int ret, i;
> +
> +	list_for_each_entry(chan, &vi->vi_chans, list) {
> +		for (i = 0; i < chan->numgangports; i++) {
> +			fs_sp = host1x_syncpt_request(&vi->client, flags);
> +			if (!fs_sp) {
> +				dev_err(vi->dev, "failed to request frame start syncpoint\n");
> +				ret = -ENOMEM;
> +				goto free_syncpts;
> +			}
> +
> +			mw_sp = host1x_syncpt_request(&vi->client, flags);
> +			if (!mw_sp) {
> +				dev_err(vi->dev, "failed to request memory ack syncpoint\n");
> +				ret = -ENOMEM;
> +				host1x_syncpt_free(fs_sp);
> +				goto free_syncpts;
> +			}
> +
> +			chan->frame_start_sp[i] = fs_sp;
> +			chan->mw_ack_sp[i] = mw_sp;
> +			spin_lock_init(&chan->sp_incr_lock[i]);
> +		}
> +	}
> +
> +	return 0;
> +
> +free_syncpts:
> +	list_for_each_entry_continue_reverse(chan, &vi->vi_chans, list)
> +		tegra_channel_host1x_syncpts_free(chan);
> +
> +	return ret;
> +}
> +
>  void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid)
>  {
>  	struct tegra_vi *vi = vid->vi;
> @@ -1576,8 +1606,9 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
>  	struct v4l2_async_subdev *asd;
>  	struct v4l2_subdev *subdev;
>  	struct tegra_vi_channel *chan;
> +	struct tegra_csi_channel *csi_chan;
>  	struct tegra_vi *vi;
> -	int ret;
> +	int ret, i;
>  
>  	chan = container_of(notifier, struct tegra_vi_channel, notifier);
>  	vi = chan->vi;
> @@ -1634,6 +1665,15 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
>  
>  	v4l2_set_subdev_hostdata(subdev, chan);
>  
> +	/*
> +	 * Retrieve number of gang ports from csi channel and add consecutive
> +	 * ports to vi channel ports.
> +	 */
> +	csi_chan = v4l2_get_subdevdata(subdev);
> +	chan->numgangports = csi_chan->numgangports;
> +	for (i = 1; i < chan->numgangports; i++)
> +		chan->portnos[i] = chan->portnos[0] + i * CSI_PORTS_PER_BRICK;
> +
>  	subdev = tegra_channel_get_remote_source_subdev(chan);
>  	v4l2_set_subdev_hostdata(subdev, chan);
>  
> @@ -1759,7 +1799,8 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
>  	 * next channels.
>  	 */
>  	list_for_each_entry(chan, &vi->vi_chans, list) {
> -		remote = fwnode_graph_get_remote_node(fwnode, chan->portno, 0);
> +		remote = fwnode_graph_get_remote_node(fwnode, chan->portnos[0],
> +						      0);
>  		if (!remote)
>  			continue;
>  
> @@ -1774,7 +1815,7 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
>  		if (ret < 0) {
>  			dev_err(vi->dev,
>  				"failed to register channel %d notifier: %d\n",
> -				chan->portno, ret);
> +				chan->portnos[0], ret);
>  			v4l2_async_notifier_cleanup(&chan->notifier);
>  		}
>  	}
> @@ -1825,11 +1866,27 @@ static int tegra_vi_init(struct host1x_client *client)
>  	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
>  		ret = tegra_vi_graph_init(vi);
>  		if (ret < 0)
> -			goto free_chans;
> +			goto cleanup_chans;
>  	}
>  
> +	/*
> +	 * x8 capture uses multiple ports as gang with simultaneous capture
> +	 * of left half onto one x4 port and right half onto second x4 port.
> +	 * Total ports grouped together as gang is retrieved from CSI subdev
> +	 * during graph build.
> +	 * So, create host1x syncpts for all ports in a gang after graph init.
> +	 */
> +	ret = tegra_vi_channel_syncpt_init(vi);
> +	if (ret < 0)
> +		goto cleanup_graph;
> +
>  	return 0;
>  
> +cleanup_graph:
> +	tegra_vi_graph_cleanup(vi);
> +cleanup_chans:
> +	list_for_each_entry(chan, &vi->vi_chans, list)
> +		tegra_channel_cleanup(chan);
>  free_chans:
>  	list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
>  		list_del(&chan->list);
> diff --git a/drivers/staging/media/tegra-video/vi.h b/drivers/staging/media/tegra-video/vi.h
> index 7d6b7a6..947e641 100644
> --- a/drivers/staging/media/tegra-video/vi.h
> +++ b/drivers/staging/media/tegra-video/vi.h
> @@ -21,6 +21,8 @@
>  #include <media/v4l2-subdev.h>
>  #include <media/videobuf2-v4l2.h>
>  
> +#include "csi.h"
> +
>  #define TEGRA_MIN_WIDTH		32U
>  #define TEGRA_MAX_WIDTH		32768U
>  #define TEGRA_MIN_HEIGHT	32U
> @@ -31,6 +33,7 @@
>  #define TEGRA_IMAGE_FORMAT_DEF	32
>  
>  #define MAX_FORMAT_NUM		64
> +#define SURFACE_ALIGN_BYTES	64
>  
>  enum tegra_vi_pg_mode {
>  	TEGRA_VI_PG_DISABLED = 0,
> @@ -151,7 +154,8 @@ struct tegra_vi_graph_entity {
>   * @done: list of capture done queued buffers
>   * @done_lock: protects the capture done queue list
>   *
> - * @portno: VI channel port number
> + * @portnos: VI channel port numbers
> + * @numgangports: number of ports combined together as a gang for capture
>   * @of_node: device node of VI channel
>   *
>   * @ctrl_handler: V4L2 control handler of this video channel
> @@ -168,10 +172,10 @@ struct tegra_vi_channel {
>  	struct media_pad pad;
>  
>  	struct tegra_vi *vi;
> -	struct host1x_syncpt *frame_start_sp;
> -	struct host1x_syncpt *mw_ack_sp;
> +	struct host1x_syncpt *frame_start_sp[GANG_PORTS_MAX];
> +	struct host1x_syncpt *mw_ack_sp[GANG_PORTS_MAX];
>  	/* protects the cpu syncpoint increment */
> -	spinlock_t sp_incr_lock;
> +	spinlock_t sp_incr_lock[GANG_PORTS_MAX];
>  
>  	struct task_struct *kthread_start_capture;
>  	wait_queue_head_t start_wait;
> @@ -190,7 +194,8 @@ struct tegra_vi_channel {
>  	/* protects the capture done queue list */
>  	spinlock_t done_lock;
>  
> -	unsigned char portno;
> +	unsigned char portnos[GANG_PORTS_MAX];
> +	u8 numgangports;
>  	struct device_node *of_node;
>  
>  	struct v4l2_ctrl_handler ctrl_handler;
> @@ -216,7 +221,7 @@ struct tegra_channel_buffer {
>  	struct list_head queue;
>  	struct tegra_vi_channel *chan;
>  	dma_addr_t addr;
> -	u32 mw_ack_sp_thresh;
> +	u32 mw_ack_sp_thresh[GANG_PORTS_MAX];
>  };
>  
>  /*
> 

Regards,

	Hans

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

* Re: [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports
  2020-11-09 13:04   ` Hans Verkuil
@ 2020-11-09 16:22     ` Sowjanya Komatineni
  0 siblings, 0 replies; 14+ messages in thread
From: Sowjanya Komatineni @ 2020-11-09 16:22 UTC (permalink / raw)
  To: Hans Verkuil, thierry.reding, jonathanh
  Cc: mchehab, linux-media, linux-tegra, linux-kernel

Thanks Hans. Will address all your below feedback in v3.

Regards,

Sowjanya

On 11/9/20 5:04 AM, Hans Verkuil wrote:
> Hi Sowjanya,
>
> On 27/10/2020 04:19, Sowjanya Komatineni wrote:
>> Tegra VI/CSI hardware don't have native 8 lane capture support.
>>
>> Each CSI port has max 4 lanes only. So for x8 captures, consecutive
>> ports are ganged up for left half and right half captures on to each
>> x4 ports with buffer offsets based on source image split width to align
>> side-by-side.
>>
>> All ports in gang are configured together during the corresponding
>> video device node streaming for x8 captures.
>>
>> x8 capture with gang ports are supported with HDMI-to-CSI bridges
>> where they split 4K image into left half onto one x4 port and
>> right half onto second x4 port.
>>
>> Signed-off-by: Sowjanya Komatineni <skomatineni@nvidia.com>
>> ---
>>   drivers/staging/media/tegra-video/csi.c      |  47 ++++-
>>   drivers/staging/media/tegra-video/csi.h      |  14 +-
>>   drivers/staging/media/tegra-video/tegra210.c | 304 ++++++++++++++++++---------
>>   drivers/staging/media/tegra-video/vi.c       | 117 ++++++++---
>>   drivers/staging/media/tegra-video/vi.h       |  17 +-
>>   5 files changed, 347 insertions(+), 152 deletions(-)
>>
>> diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c
>> index a19c85c..a6c54f8 100644
>> --- a/drivers/staging/media/tegra-video/csi.c
>> +++ b/drivers/staging/media/tegra-video/csi.c
>> @@ -253,13 +253,14 @@ static unsigned int csi_get_pixel_rate(struct tegra_csi_channel *csi_chan)
>>   }
>>   
>>   void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
>> +				u8 csi_port_num,
>>   				u8 *clk_settle_time,
>>   				u8 *ths_settle_time)
>>   {
>>   	struct tegra_csi *csi = csi_chan->csi;
>>   	unsigned int cil_clk_mhz;
>>   	unsigned int pix_clk_mhz;
>> -	int clk_idx = (csi_chan->csi_port_num >> 1) + 1;
>> +	int clk_idx = (csi_port_num >> 1) + 1;
>>   
>>   	cil_clk_mhz = clk_get_rate(csi->clks[clk_idx].clk) / MHZ;
>>   	pix_clk_mhz = csi_get_pixel_rate(csi_chan) / MHZ;
>> @@ -410,7 +411,7 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi,
>>   				   unsigned int num_pads)
>>   {
>>   	struct tegra_csi_channel *chan;
>> -	int ret = 0;
>> +	int ret = 0, i;
>>   
>>   	chan = kzalloc(sizeof(*chan), GFP_KERNEL);
>>   	if (!chan)
>> @@ -418,8 +419,21 @@ static int tegra_csi_channel_alloc(struct tegra_csi *csi,
>>   
>>   	list_add_tail(&chan->list, &csi->csi_chans);
>>   	chan->csi = csi;
>> -	chan->csi_port_num = port_num;
>> -	chan->numlanes = lanes;
>> +	/*
>> +	 * Each CSI brick has maximum of 4 lanes.
>> +	 * For lanes more than 4, use multiple of immediate CSI bricks as gang.
>> +	 */
>> +	if (lanes <= CSI_LANES_PER_BRICK) {
>> +		chan->numlanes = lanes;
>> +		chan->numgangports = 1;
>> +	} else {
>> +		chan->numlanes = CSI_LANES_PER_BRICK;
>> +		chan->numgangports = lanes / CSI_LANES_PER_BRICK;
>> +	}
>> +
>> +	for (i = 0; i < chan->numgangports; i++)
>> +		chan->csi_port_nums[i] = port_num + i * CSI_PORTS_PER_BRICK;
>> +
>>   	chan->of_node = node;
>>   	chan->numpads = num_pads;
>>   	if (num_pads & 0x2) {
>> @@ -461,9 +475,6 @@ static int tegra_csi_tpg_channels_alloc(struct tegra_csi *csi)
>>   static int tegra_csi_channels_alloc(struct tegra_csi *csi)
>>   {
>>   	struct device_node *node = csi->dev->of_node;
>> -	struct v4l2_fwnode_endpoint v4l2_ep = {
>> -		.bus_type = V4L2_MBUS_CSI2_DPHY
>> -	};
>>   	struct fwnode_handle *fwh;
>>   	struct device_node *channel;
>>   	struct device_node *ep;
>> @@ -471,6 +482,8 @@ static int tegra_csi_channels_alloc(struct tegra_csi *csi)
>>   	int ret;
>>   
>>   	for_each_child_of_node(node, channel) {
>> +		struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 };
>> +
>>   		if (!of_node_name_eq(channel, "channel"))
>>   			continue;
>>   
>> @@ -499,8 +512,20 @@ static int tegra_csi_channels_alloc(struct tegra_csi *csi)
>>   			goto err_node_put;
>>   		}
>>   
>> -		lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
>> -		if (!lanes || ((lanes & (lanes - 1)) != 0)) {
>> +		if (v4l2_ep.bus_type == V4L2_MBUS_PARALLEL)
>> +			lanes = v4l2_ep.bus.parallel.bus_width;
>> +		else
>> +			lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
>> +
>> +		/*
>> +		 * Each CSI brick has maximum 4 data lanes.
>> +		 * For lanes more than 4, validate lanes to be multiple of 4
>> +		 * so multiple of consecutive CSI bricks can be ganged up for
>> +		 * streaming.
>> +		 */
>> +		if (!lanes || ((lanes & (lanes - 1)) != 0) ||
>> +		    (lanes > CSI_LANES_PER_BRICK && (((lanes & 3) != 0) ||
>> +		    ((portno & 1) != 0)))) {
>>   			dev_err(csi->dev, "invalid data-lanes %d for %pOF\n",
>>   				lanes, channel);
>>   			ret = -EINVAL;
>> @@ -544,7 +569,7 @@ static int tegra_csi_channel_init(struct tegra_csi_channel *chan)
>>   	subdev->dev = csi->dev;
>>   	if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
>>   		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s-%d", "tpg",
>> -			 chan->csi_port_num);
>> +			 chan->csi_port_nums[0]);
>>   	else
>>   		snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
>>   			 kbasename(chan->of_node->full_name));
>> @@ -596,7 +621,7 @@ static int tegra_csi_channels_init(struct tegra_csi *csi)
>>   		if (ret) {
>>   			dev_err(csi->dev,
>>   				"failed to initialize channel-%d: %d\n",
>> -				chan->csi_port_num, ret);
>> +				chan->csi_port_nums[0], ret);
>>   			return ret;
>>   		}
>>   	}
>> diff --git a/drivers/staging/media/tegra-video/csi.h b/drivers/staging/media/tegra-video/csi.h
>> index c65ff73..386f7c6 100644
>> --- a/drivers/staging/media/tegra-video/csi.h
>> +++ b/drivers/staging/media/tegra-video/csi.h
>> @@ -17,6 +17,10 @@
>>    * CILB.
>>    */
>>   #define CSI_PORTS_PER_BRICK	2
>> +#define CSI_LANES_PER_BRICK	4
>> +
>> +/* Maximum 2 CSI x4 ports can be ganged up for streaming */
>> +#define GANG_PORTS_MAX	2
>>   
>>   /* each CSI channel can have one sink and one source pads */
>>   #define TEGRA_CSI_PADS_NUM	2
>> @@ -43,8 +47,10 @@ struct tegra_csi;
>>    * @numpads: number of pads.
>>    * @csi: Tegra CSI device structure
>>    * @of_node: csi device tree node
>> - * @numlanes: number of lanes used per port/channel
>> - * @csi_port_num: CSI channel port number
>> + * @numgangports: number of immediate ports ganged up to meet the
>> + *             channel bus-width
>> + * @numlanes: number of lanes used per port
>> + * @csi_port_nums: CSI channel port numbers
>>    * @pg_mode: test pattern generator mode for channel
>>    * @format: active format of the channel
>>    * @framerate: active framerate for TPG
>> @@ -60,8 +66,9 @@ struct tegra_csi_channel {
>>   	unsigned int numpads;
>>   	struct tegra_csi *csi;
>>   	struct device_node *of_node;
>> +	u8 numgangports;
>>   	unsigned int numlanes;
>> -	u8 csi_port_num;
>> +	u8 csi_port_nums[GANG_PORTS_MAX];
>>   	u8 pg_mode;
>>   	struct v4l2_mbus_framefmt format;
>>   	unsigned int framerate;
>> @@ -150,6 +157,7 @@ extern const struct tegra_csi_soc tegra210_csi_soc;
>>   
>>   void tegra_csi_error_recover(struct v4l2_subdev *subdev);
>>   void tegra_csi_calc_settle_time(struct tegra_csi_channel *csi_chan,
>> +				u8 csi_port_num,
>>   				u8 *clk_settle_time,
>>   				u8 *ths_settle_time);
>>   #endif
>> diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
>> index 929d277..aff56fc 100644
>> --- a/drivers/staging/media/tegra-video/tegra210.c
>> +++ b/drivers/staging/media/tegra-video/tegra210.c
>> @@ -149,21 +149,22 @@ static u32 tegra_vi_read(struct tegra_vi_channel *chan, unsigned int addr)
>>   }
>>   
>>   /* Tegra210 VI_CSI registers accessors */
>> -static void vi_csi_write(struct tegra_vi_channel *chan, unsigned int addr,
>> -			 u32 val)
>> +static void vi_csi_write(struct tegra_vi_channel *chan, u8 portno,
>> +			 unsigned int addr, u32 val)
>>   {
>>   	void __iomem *vi_csi_base;
>>   
>> -	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(chan->portno);
>> +	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(portno);
>>   
>>   	writel_relaxed(val, vi_csi_base + addr);
>>   }
>>   
>> -static u32 vi_csi_read(struct tegra_vi_channel *chan, unsigned int addr)
>> +static u32 vi_csi_read(struct tegra_vi_channel *chan, u8 portno,
>> +		       unsigned int addr)
>>   {
>>   	void __iomem *vi_csi_base;
>>   
>> -	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(chan->portno);
>> +	vi_csi_base = chan->vi->iomem + TEGRA210_VI_CSI_BASE(portno);
>>   
>>   	return readl_relaxed(vi_csi_base + addr);
>>   }
>> @@ -171,7 +172,8 @@ static u32 vi_csi_read(struct tegra_vi_channel *chan, unsigned int addr)
>>   /*
>>    * Tegra210 VI channel capture operations
>>    */
>> -static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
>> +static int tegra_channel_capture_setup(struct tegra_vi_channel *chan,
>> +				       u8 portno)
>>   {
>>   	u32 height = chan->format.height;
>>   	u32 width = chan->format.width;
>> @@ -192,19 +194,30 @@ static int tegra_channel_capture_setup(struct tegra_vi_channel *chan)
>>   	    data_type == TEGRA_IMAGE_DT_RGB888)
>>   		bypass_pixel_transform = 0;
>>   
>> -	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DEF,
>> +	/*
>> +	 * For x8 source streaming, the source image is split onto two x4 ports
>> +	 * with left half to first x4 port and right half to second x4 port.
>> +	 * So, use split width and corresponding word count for each x4 port.
>> +	 */
> This is a mismatch with tc358840: that HDMI receiver only enables ganging mode
> if the width > 1920. Here it is always enabled. As a result streaming will fail
> if the source transmits 1920x1080p60 since the second CSI port will never receive
> data, leading to endless syncpt timeouts.
>
> IMHO it is better to disable ganging for formats with a width <= 1920. There are
> some weird formats that might give issues if the width is devices by 2 (1366x768)
> and in general using just one port for such formats is just simpler.
>
> Regarding the endless syncpt timeouts: in my experience this is an indication that
> something is seriously wrong. If more than X successive timeouts are seen then the
> driver should call vb2_queue_error. I recommend using a V4L2 control to set the
> threshold X (e.g. V4L2_CID_TEGRA_SYNCPT_TIMEOUT_RETRY).
>
>> +	if (chan->numgangports > 1) {
>> +		width = width >> 1;
>> +		word_count = (width * chan->fmtinfo->bit_width) / 8;
>> +	}
>> +
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_ERROR_STATUS, 0xffffffff);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_DEF,
>>   		     bypass_pixel_transform |
>>   		     (format << IMAGE_DEF_FORMAT_OFFSET) |
>>   		     IMAGE_DEF_DEST_MEM);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_DT, data_type);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_SIZE_WC, word_count);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_IMAGE_SIZE,
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_DT, data_type);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_SIZE_WC, word_count);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_IMAGE_SIZE,
>>   		     (height << IMAGE_SIZE_HEIGHT_OFFSET) | width);
>>   	return 0;
>>   }
>>   
>> -static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan)
>> +static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan,
>> +					u8 portno)
>>   {
>>   	/* disable clock gating to enable continuous clock */
>>   	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, 0);
>> @@ -212,15 +225,16 @@ static void tegra_channel_vi_soft_reset(struct tegra_vi_channel *chan)
>>   	 * Soft reset memory client interface, pixel format logic, sensor
>>   	 * control logic, and a shadow copy logic to bring VI to clean state.
>>   	 */
>> -	vi_csi_write(chan, TEGRA_VI_CSI_SW_RESET, 0xf);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SW_RESET, 0xf);
>>   	usleep_range(100, 200);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_SW_RESET, 0x0);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SW_RESET, 0x0);
>>   
>>   	/* enable back VI clock gating */
>>   	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, VI_CG_2ND_LEVEL_EN);
>>   }
>>   
>> -static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
>> +static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan,
>> +						u8 portno)
>>   {
>>   	struct v4l2_subdev *subdev;
>>   	u32 val;
>> @@ -232,9 +246,9 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
>>   	 * events which can cause CSI and VI hardware hang.
>>   	 * This helps to have a clean capture for next frame.
>>   	 */
>> -	val = vi_csi_read(chan, TEGRA_VI_CSI_ERROR_STATUS);
>> +	val = vi_csi_read(chan, portno, TEGRA_VI_CSI_ERROR_STATUS);
>>   	dev_dbg(&chan->video.dev, "TEGRA_VI_CSI_ERROR_STATUS 0x%08x\n", val);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, val);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_ERROR_STATUS, val);
>>   
>>   	val = tegra_vi_read(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR);
>>   	dev_dbg(&chan->video.dev,
>> @@ -242,8 +256,8 @@ static void tegra_channel_capture_error_recover(struct tegra_vi_channel *chan)
>>   	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR, val);
>>   
>>   	/* recover VI by issuing software reset and re-setup for capture */
>> -	tegra_channel_vi_soft_reset(chan);
>> -	tegra_channel_capture_setup(chan);
>> +	tegra_channel_vi_soft_reset(chan, portno);
>> +	tegra_channel_capture_setup(chan, portno);
>>   
>>   	/* recover CSI block */
>>   	subdev = tegra_channel_get_remote_csi_subdev(chan);
>> @@ -282,80 +296,114 @@ static void release_buffer(struct tegra_vi_channel *chan,
>>   	vb2_buffer_done(&vb->vb2_buf, state);
>>   }
>>   
>> -static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
>> -				       struct tegra_channel_buffer *buf)
>> +static void tegra_channel_vi_buffer_setup(struct tegra_vi_channel *chan,
>> +					  u8 portno, u32 buf_offset,
>> +					  struct tegra_channel_buffer *buf)
>>   {
>> -	u32 thresh, value, frame_start, mw_ack_done;
>> -	int bytes_per_line = chan->format.bytesperline;
>> +	int bytesperline = chan->format.bytesperline;
>>   	u32 sizeimage = chan->format.sizeimage;
>> -	int err;
>>   
>>   	/* program buffer address by using surface 0 */
>> -	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_OFFSET_MSB,
>> -		     (u64)buf->addr >> 32);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_OFFSET_LSB, buf->addr);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_SURFACE0_STRIDE, bytes_per_line);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_OFFSET_MSB,
>> +		     ((u64)buf->addr + buf_offset) >> 32);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_OFFSET_LSB,
>> +		     buf->addr + buf_offset);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE0_STRIDE, bytesperline);
>>   
>> +	if (chan->fmtinfo->fourcc != V4L2_PIX_FMT_NV16)
>> +		return;
>>   	/*
>>   	 * Program surface 1 for UV plane with offset sizeimage from Y plane.
>>   	 */
>> -	if (chan->fmtinfo->fourcc == V4L2_PIX_FMT_NV16) {
>> -		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
>> -			     ((u64)buf->addr + sizeimage / 2) >> 32);
>> -		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
>> -			     buf->addr + sizeimage / 2);
>> -		vi_csi_write(chan, TEGRA_VI_CSI_SURFACE1_STRIDE,
>> -			     bytes_per_line);
>> -	}
>> -
>> -	/*
>> -	 * Tegra VI block interacts with host1x syncpt for synchronizing
>> -	 * programmed condition of capture state and hardware operation.
>> -	 * Frame start and Memory write acknowledge syncpts has their own
>> -	 * FIFO of depth 2.
>> -	 *
>> -	 * Syncpoint trigger conditions set through VI_INCR_SYNCPT register
>> -	 * are added to HW syncpt FIFO and when the HW triggers, syncpt
>> -	 * condition is removed from the FIFO and counter at syncpoint index
>> -	 * will be incremented by the hardware and software can wait for
>> -	 * counter to reach threshold to synchronize capturing frame with the
>> -	 * hardware capture events.
>> -	 */
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_OFFSET_MSB,
>> +		     (((u64)buf->addr + sizeimage / 2) + buf_offset) >> 32);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_OFFSET_LSB,
>> +		     buf->addr + sizeimage / 2 + buf_offset);
>> +	vi_csi_write(chan, portno, TEGRA_VI_CSI_SURFACE1_STRIDE, bytesperline);
>> +}
>>   
>> -	/* increase channel syncpoint threshold for FRAME_START */
>> -	thresh = host1x_syncpt_incr_max(chan->frame_start_sp, 1);
>> +static int tegra_channel_capture_frame(struct tegra_vi_channel *chan,
>> +				       struct tegra_channel_buffer *buf)
>> +{
>> +	u32 thresh, value, frame_start, mw_ack_done;
>> +	u32 fs_thresh[GANG_PORTS_MAX];
>> +	u8 *portnos = chan->portnos;
>> +	int gang_bpl = (chan->format.width >> 1) * chan->fmtinfo->bpp;
>> +	u32 buf_offset;
>> +	bool capture_timedout = false;
>> +	int err, i;
>> +
>> +	for (i = 0; i < chan->numgangports; i++) {
>> +		/*
>> +		 * Align buffers side-by-side for all consecutive x4 ports
>> +		 * in gang ports using bytes per line based on source split
>> +		 * width.
>> +		 */
>> +		buf_offset = i * roundup(gang_bpl, SURFACE_ALIGN_BYTES);
>> +		tegra_channel_vi_buffer_setup(chan, portnos[i], buf_offset,
>> +					      buf);
>>   
>> -	/* Program FRAME_START trigger condition syncpt request */
>> -	frame_start = VI_CSI_PP_FRAME_START(chan->portno);
>> -	value = VI_CFG_VI_INCR_SYNCPT_COND(frame_start) |
>> -		host1x_syncpt_id(chan->frame_start_sp);
>> -	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
>> +		/*
>> +		 * Tegra VI block interacts with host1x syncpt to synchronize
>> +		 * programmed condition and hardware operation for capture.
>> +		 * Frame start and Memory write acknowledge syncpts has their
>> +		 * own FIFO of depth 2.
>> +		 *
>> +		 * Syncpoint trigger conditions set through VI_INCR_SYNCPT
>> +		 * register are added to HW syncpt FIFO and when HW triggers,
>> +		 * syncpt condition is removed from the FIFO and counter at
>> +		 * syncpoint index will be incremented by the hardware and
>> +		 * software can wait for counter to reach threshold to
>> +		 * synchronize capturing frame with hardware capture events.
>> +		 */
>>   
>> -	/* increase channel syncpoint threshold for MW_ACK_DONE */
>> -	buf->mw_ack_sp_thresh = host1x_syncpt_incr_max(chan->mw_ack_sp, 1);
>> +		/* increase channel syncpoint threshold for FRAME_START */
>> +		thresh = host1x_syncpt_incr_max(chan->frame_start_sp[i], 1);
>> +		fs_thresh[i] = thresh;
>> +
>> +		/* Program FRAME_START trigger condition syncpt request */
>> +		frame_start = VI_CSI_PP_FRAME_START(portnos[i]);
>> +		value = VI_CFG_VI_INCR_SYNCPT_COND(frame_start) |
>> +			host1x_syncpt_id(chan->frame_start_sp[i]);
> This fails if this driver is compiled into the kernel. In that case
> tegra_vi_channel_syncpt_init() is called too soon when chan->numgangports
> is still 1, so chan->frame_start_sp[1] will be NULL.
>
> Of course, I tested this driver by compiling it into the kernel, so that
> took me some debugging to figure out why it crashed :-)
>
>> +		tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
>> +
>> +		/* increase channel syncpoint threshold for MW_ACK_DONE */
>> +		thresh = host1x_syncpt_incr_max(chan->mw_ack_sp[i], 1);
>> +		buf->mw_ack_sp_thresh[i] = thresh;
>> +
>> +		/* Program MW_ACK_DONE trigger condition syncpt request */
>> +		mw_ack_done = VI_CSI_MW_ACK_DONE(portnos[i]);
>> +		value = VI_CFG_VI_INCR_SYNCPT_COND(mw_ack_done) |
>> +			host1x_syncpt_id(chan->mw_ack_sp[i]);
>> +		tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
>> +	}
>>   
>> -	/* Program MW_ACK_DONE trigger condition syncpt request */
>> -	mw_ack_done = VI_CSI_MW_ACK_DONE(chan->portno);
>> -	value = VI_CFG_VI_INCR_SYNCPT_COND(mw_ack_done) |
>> -		host1x_syncpt_id(chan->mw_ack_sp);
>> -	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT, value);
>> +	/* enable single shot capture after all ganged ports are ready */
>> +	for (i = 0; i < chan->numgangports; i++)
>> +		vi_csi_write(chan, portnos[i], TEGRA_VI_CSI_SINGLE_SHOT,
>> +			     SINGLE_SHOT_CAPTURE);
>>   
>> -	/* enable single shot capture */
>> -	vi_csi_write(chan, TEGRA_VI_CSI_SINGLE_SHOT, SINGLE_SHOT_CAPTURE);
>> +	for (i = 0; i < chan->numgangports; i++) {
>> +		/*
>> +		 * Wait for syncpt counter to reach frame start event threshold
>> +		 */
>> +		err = host1x_syncpt_wait(chan->frame_start_sp[i], fs_thresh[i],
>> +					 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
>> +		if (err) {
>> +			capture_timedout = true;
>> +			/* increment syncpoint counter for timedout events */
>> +			host1x_syncpt_incr(chan->frame_start_sp[i]);
>> +			spin_lock(&chan->sp_incr_lock[i]);
>> +			host1x_syncpt_incr(chan->mw_ack_sp[i]);
>> +			spin_unlock(&chan->sp_incr_lock[i]);
>> +			/* clear errors and recover */
>> +			tegra_channel_capture_error_recover(chan, portnos[i]);
>> +		}
>> +	}
>>   
>> -	/* wait for syncpt counter to reach frame start event threshold */
>> -	err = host1x_syncpt_wait(chan->frame_start_sp, thresh,
>> -				 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
>> -	if (err) {
>> +	if (capture_timedout) {
>>   		dev_err_ratelimited(&chan->video.dev,
>>   				    "frame start syncpt timeout: %d\n", err);
>> -		/* increment syncpoint counter for timedout events */
>> -		host1x_syncpt_incr(chan->frame_start_sp);
>> -		spin_lock(&chan->sp_incr_lock);
>> -		host1x_syncpt_incr(chan->mw_ack_sp);
>> -		spin_unlock(&chan->sp_incr_lock);
>> -		/* clear errors and recover */
>> -		tegra_channel_capture_error_recover(chan);
>>   		release_buffer(chan, buf, VB2_BUF_STATE_ERROR);
>>   		return err;
>>   	}
>> @@ -376,21 +424,29 @@ static void tegra_channel_capture_done(struct tegra_vi_channel *chan,
>>   {
>>   	enum vb2_buffer_state state = VB2_BUF_STATE_DONE;
>>   	u32 value;
>> -	int ret;
>> +	bool capture_timedout = false;
>> +	int ret, i;
>>   
>> -	/* wait for syncpt counter to reach MW_ACK_DONE event threshold */
>> -	ret = host1x_syncpt_wait(chan->mw_ack_sp, buf->mw_ack_sp_thresh,
>> -				 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
>> -	if (ret) {
>> -		dev_err_ratelimited(&chan->video.dev,
>> -				    "MW_ACK_DONE syncpt timeout: %d\n", ret);
>> -		state = VB2_BUF_STATE_ERROR;
>> -		/* increment syncpoint counter for timedout event */
>> -		spin_lock(&chan->sp_incr_lock);
>> -		host1x_syncpt_incr(chan->mw_ack_sp);
>> -		spin_unlock(&chan->sp_incr_lock);
>> +	for (i = 0; i < chan->numgangports; i++) {
>> +		/*
>> +		 * Wait for syncpt counter to reach MW_ACK_DONE event threshold
>> +		 */
>> +		ret = host1x_syncpt_wait(chan->mw_ack_sp[i],
>> +					 buf->mw_ack_sp_thresh[i],
>> +					 TEGRA_VI_SYNCPT_WAIT_TIMEOUT, &value);
>> +		if (ret) {
>> +			capture_timedout = true;
>> +			state = VB2_BUF_STATE_ERROR;
>> +			/* increment syncpoint counter for timedout event */
>> +			spin_lock(&chan->sp_incr_lock[i]);
>> +			host1x_syncpt_incr(chan->mw_ack_sp[i]);
>> +			spin_unlock(&chan->sp_incr_lock[i]);
>> +		}
>>   	}
>>   
>> +	if (capture_timedout)
>> +		dev_err_ratelimited(&chan->video.dev,
>> +				    "MW_ACK_DONE syncpt timeout: %d\n", ret);
>>   	release_buffer(chan, buf, state);
>>   }
>>   
>> @@ -463,14 +519,12 @@ static int tegra210_vi_start_streaming(struct vb2_queue *vq, u32 count)
>>   	struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
>>   	struct media_pipeline *pipe = &chan->video.pipe;
>>   	u32 val;
>> -	int ret;
>> +	u8 *portnos = chan->portnos;
>> +	int ret, i;
>>   
>>   	tegra_vi_write(chan, TEGRA_VI_CFG_CG_CTRL, VI_CG_2ND_LEVEL_EN);
>>   
>> -	/* clear errors */
>> -	val = vi_csi_read(chan, TEGRA_VI_CSI_ERROR_STATUS);
>> -	vi_csi_write(chan, TEGRA_VI_CSI_ERROR_STATUS, val);
>> -
>> +	/* clear syncpt errors */
>>   	val = tegra_vi_read(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR);
>>   	tegra_vi_write(chan, TEGRA_VI_CFG_VI_INCR_SYNCPT_ERROR, val);
>>   
>> @@ -489,7 +543,14 @@ static int tegra210_vi_start_streaming(struct vb2_queue *vq, u32 count)
>>   	if (ret < 0)
>>   		goto error_pipeline_start;
>>   
>> -	tegra_channel_capture_setup(chan);
>> +	/* clear csi errors and do capture setup for all ports in gang mode */
>> +	for (i = 0; i < chan->numgangports; i++) {
>> +		val = vi_csi_read(chan, portnos[i], TEGRA_VI_CSI_ERROR_STATUS);
>> +		vi_csi_write(chan, portnos[i], TEGRA_VI_CSI_ERROR_STATUS, val);
>> +
>> +		tegra_channel_capture_setup(chan, portnos[i]);
>> +	}
>> +
>>   	ret = tegra_channel_set_stream(chan, true);
>>   	if (ret < 0)
>>   		goto error_set_stream;
>> @@ -743,10 +804,10 @@ static void tpg_write(struct tegra_csi *csi, u8 portno, unsigned int addr,
>>   /*
>>    * Tegra210 CSI operations
>>    */
>> -static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
>> +static void tegra210_csi_port_recover(struct tegra_csi_channel *csi_chan,
>> +				      u8 portno)
>>   {
>>   	struct tegra_csi *csi = csi_chan->csi;
>> -	unsigned int portno = csi_chan->csi_port_num;
>>   	u32 val;
>>   
>>   	/*
>> @@ -795,16 +856,26 @@ static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
>>   	}
>>   }
>>   
>> -static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
>> +static void tegra210_csi_error_recover(struct tegra_csi_channel *csi_chan)
>> +{
>> +	u8 *portnos = csi_chan->csi_port_nums;
>> +	int i;
>> +
>> +	for (i = 0; i < csi_chan->numgangports; i++)
>> +		tegra210_csi_port_recover(csi_chan, portnos[i]);
>> +}
>> +
>> +static int
>> +tegra210_csi_port_start_streaming(struct tegra_csi_channel *csi_chan,
>> +				  u8 portno)
>>   {
>>   	struct tegra_csi *csi = csi_chan->csi;
>> -	unsigned int portno = csi_chan->csi_port_num;
>>   	u8 clk_settle_time = 0;
>>   	u8 ths_settle_time = 10;
>>   	u32 val;
>>   
>>   	if (!csi_chan->pg_mode)
>> -		tegra_csi_calc_settle_time(csi_chan, &clk_settle_time,
>> +		tegra_csi_calc_settle_time(csi_chan, portno, &clk_settle_time,
>>   					   &ths_settle_time);
>>   
>>   	csi_write(csi, portno, TEGRA_CSI_CLKEN_OVERRIDE, 0);
>> @@ -903,10 +974,10 @@ static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
>>   	return 0;
>>   }
>>   
>> -static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
>> +static void
>> +tegra210_csi_port_stop_streaming(struct tegra_csi_channel *csi_chan, u8 portno)
>>   {
>>   	struct tegra_csi *csi = csi_chan->csi;
>> -	unsigned int portno = csi_chan->csi_port_num;
>>   	u32 val;
>>   
>>   	val = pp_read(csi, portno, TEGRA_CSI_PIXEL_PARSER_STATUS);
>> @@ -944,6 +1015,35 @@ static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
>>   	}
>>   }
>>   
>> +static int tegra210_csi_start_streaming(struct tegra_csi_channel *csi_chan)
>> +{
>> +	u8 *portnos = csi_chan->csi_port_nums;
>> +	int ret, i;
>> +
>> +	for (i = 0; i < csi_chan->numgangports; i++) {
>> +		ret = tegra210_csi_port_start_streaming(csi_chan, portnos[i]);
>> +		if (ret)
>> +			goto stream_start_fail;
>> +	}
>> +
>> +	return 0;
>> +
>> +stream_start_fail:
>> +	for (i = i - 1; i >= 0; i--)
>> +		tegra210_csi_port_stop_streaming(csi_chan, portnos[i]);
>> +
>> +	return ret;
>> +}
>> +
>> +static void tegra210_csi_stop_streaming(struct tegra_csi_channel *csi_chan)
>> +{
>> +	u8 *portnos = csi_chan->csi_port_nums;
>> +	int i;
>> +
>> +	for (i = 0; i < csi_chan->numgangports; i++)
>> +		tegra210_csi_port_stop_streaming(csi_chan, portnos[i]);
>> +}
>> +
>>   /*
>>    * Tegra210 CSI TPG frame rate table with horizontal and vertical
>>    * blanking intervals for corresponding format and resolution.
>> diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
>> index e866f7d..4812f2c 100644
>> --- a/drivers/staging/media/tegra-video/vi.c
>> +++ b/drivers/staging/media/tegra-video/vi.c
>> @@ -30,7 +30,6 @@
>>   #include "vi.h"
>>   #include "video.h"
>>   
>> -#define SURFACE_ALIGN_BYTES		64
>>   #define MAX_CID_CONTROLS		1
>>   
>>   static const struct tegra_video_format tegra_default_format = {
>> @@ -1090,12 +1089,20 @@ static int vi_fmts_bitmap_init(struct tegra_vi_channel *chan)
>>   	return 0;
>>   }
>>   
>> +static void tegra_channel_host1x_syncpts_free(struct tegra_vi_channel *chan)
>> +{
>> +	int i;
>> +
>> +	for (i = 0; i < chan->numgangports; i++) {
>> +		host1x_syncpt_free(chan->mw_ack_sp[i]);
>> +		host1x_syncpt_free(chan->frame_start_sp[i]);
>> +	}
>> +}
>> +
>>   static void tegra_channel_cleanup(struct tegra_vi_channel *chan)
>>   {
>>   	v4l2_ctrl_handler_free(&chan->ctrl_handler);
>>   	media_entity_cleanup(&chan->video.entity);
>> -	host1x_syncpt_free(chan->mw_ack_sp);
>> -	host1x_syncpt_free(chan->frame_start_sp);
>>   	mutex_destroy(&chan->video_lock);
>>   }
>>   
>> @@ -1107,6 +1114,7 @@ void tegra_channels_cleanup(struct tegra_vi *vi)
>>   		return;
>>   
>>   	list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
>> +		tegra_channel_host1x_syncpts_free(chan);
>>   		tegra_channel_cleanup(chan);
>>   		list_del(&chan->list);
>>   		kfree(chan);
>> @@ -1117,7 +1125,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>>   {
>>   	struct tegra_vi *vi = chan->vi;
>>   	struct tegra_video_device *vid = dev_get_drvdata(vi->client.host);
>> -	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
>>   	int ret;
>>   
>>   	mutex_init(&chan->video_lock);
>> @@ -1125,7 +1132,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>>   	INIT_LIST_HEAD(&chan->done);
>>   	spin_lock_init(&chan->start_lock);
>>   	spin_lock_init(&chan->done_lock);
>> -	spin_lock_init(&chan->sp_incr_lock);
>>   	init_waitqueue_head(&chan->start_wait);
>>   	init_waitqueue_head(&chan->done_wait);
>>   
>> @@ -1140,26 +1146,13 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>>   	chan->format.sizeimage = chan->format.bytesperline * TEGRA_DEF_HEIGHT;
>>   	tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
>>   
>> -	chan->frame_start_sp = host1x_syncpt_request(&vi->client, flags);
>> -	if (!chan->frame_start_sp) {
>> -		dev_err(vi->dev, "failed to request frame start syncpoint\n");
>> -		return -ENOMEM;
>> -	}
>> -
>> -	chan->mw_ack_sp = host1x_syncpt_request(&vi->client, flags);
>> -	if (!chan->mw_ack_sp) {
>> -		dev_err(vi->dev, "failed to request memory ack syncpoint\n");
>> -		ret = -ENOMEM;
>> -		goto free_fs_syncpt;
>> -	}
>> -
>>   	/* initialize the media entity */
>>   	chan->pad.flags = MEDIA_PAD_FL_SINK;
>>   	ret = media_entity_pads_init(&chan->video.entity, 1, &chan->pad);
>>   	if (ret < 0) {
>>   		dev_err(vi->dev,
>>   			"failed to initialize media entity: %d\n", ret);
>> -		goto free_mw_ack_syncpt;
>> +		return ret;
>>   	}
>>   
>>   	ret = v4l2_ctrl_handler_init(&chan->ctrl_handler, MAX_CID_CONTROLS);
>> @@ -1175,7 +1168,7 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>>   	chan->video.release = video_device_release_empty;
>>   	chan->video.queue = &chan->queue;
>>   	snprintf(chan->video.name, sizeof(chan->video.name), "%s-%s-%u",
>> -		 dev_name(vi->dev), "output", chan->portno);
>> +		 dev_name(vi->dev), "output", chan->portnos[0]);
>>   	chan->video.vfl_type = VFL_TYPE_VIDEO;
>>   	chan->video.vfl_dir = VFL_DIR_RX;
>>   	chan->video.ioctl_ops = &tegra_channel_ioctl_ops;
>> @@ -1211,10 +1204,6 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
>>   	v4l2_ctrl_handler_free(&chan->ctrl_handler);
>>   cleanup_media:
>>   	media_entity_cleanup(&chan->video.entity);
>> -free_mw_ack_syncpt:
>> -	host1x_syncpt_free(chan->mw_ack_sp);
>> -free_fs_syncpt:
>> -	host1x_syncpt_free(chan->frame_start_sp);
>>   	return ret;
>>   }
>>   
>> @@ -1234,7 +1223,8 @@ static int tegra_vi_channel_alloc(struct tegra_vi *vi, unsigned int port_num,
>>   		return -ENOMEM;
>>   
>>   	chan->vi = vi;
>> -	chan->portno = port_num;
>> +	chan->portnos[0] = port_num;
>> +	chan->numgangports = 1;
>>   	chan->of_node = node;
>>   	list_add_tail(&chan->list, &vi->vi_chans);
>>   
>> @@ -1312,7 +1302,7 @@ static int tegra_vi_channels_init(struct tegra_vi *vi)
>>   		if (ret < 0) {
>>   			dev_err(vi->dev,
>>   				"failed to initialize channel-%d: %d\n",
>> -				chan->portno, ret);
>> +				chan->portnos[0], ret);
>>   			goto cleanup;
>>   		}
>>   	}
>> @@ -1326,6 +1316,46 @@ static int tegra_vi_channels_init(struct tegra_vi *vi)
>>   	return ret;
>>   }
>>   
>> +static int tegra_vi_channel_syncpt_init(struct tegra_vi *vi)
>> +{
>> +	struct tegra_vi_channel *chan;
>> +	unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
>> +	struct host1x_syncpt *fs_sp;
>> +	struct host1x_syncpt *mw_sp;
>> +	int ret, i;
>> +
>> +	list_for_each_entry(chan, &vi->vi_chans, list) {
>> +		for (i = 0; i < chan->numgangports; i++) {
>> +			fs_sp = host1x_syncpt_request(&vi->client, flags);
>> +			if (!fs_sp) {
>> +				dev_err(vi->dev, "failed to request frame start syncpoint\n");
>> +				ret = -ENOMEM;
>> +				goto free_syncpts;
>> +			}
>> +
>> +			mw_sp = host1x_syncpt_request(&vi->client, flags);
>> +			if (!mw_sp) {
>> +				dev_err(vi->dev, "failed to request memory ack syncpoint\n");
>> +				ret = -ENOMEM;
>> +				host1x_syncpt_free(fs_sp);
>> +				goto free_syncpts;
>> +			}
>> +
>> +			chan->frame_start_sp[i] = fs_sp;
>> +			chan->mw_ack_sp[i] = mw_sp;
>> +			spin_lock_init(&chan->sp_incr_lock[i]);
>> +		}
>> +	}
>> +
>> +	return 0;
>> +
>> +free_syncpts:
>> +	list_for_each_entry_continue_reverse(chan, &vi->vi_chans, list)
>> +		tegra_channel_host1x_syncpts_free(chan);
>> +
>> +	return ret;
>> +}
>> +
>>   void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid)
>>   {
>>   	struct tegra_vi *vi = vid->vi;
>> @@ -1576,8 +1606,9 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
>>   	struct v4l2_async_subdev *asd;
>>   	struct v4l2_subdev *subdev;
>>   	struct tegra_vi_channel *chan;
>> +	struct tegra_csi_channel *csi_chan;
>>   	struct tegra_vi *vi;
>> -	int ret;
>> +	int ret, i;
>>   
>>   	chan = container_of(notifier, struct tegra_vi_channel, notifier);
>>   	vi = chan->vi;
>> @@ -1634,6 +1665,15 @@ static int tegra_vi_graph_notify_complete(struct v4l2_async_notifier *notifier)
>>   
>>   	v4l2_set_subdev_hostdata(subdev, chan);
>>   
>> +	/*
>> +	 * Retrieve number of gang ports from csi channel and add consecutive
>> +	 * ports to vi channel ports.
>> +	 */
>> +	csi_chan = v4l2_get_subdevdata(subdev);
>> +	chan->numgangports = csi_chan->numgangports;
>> +	for (i = 1; i < chan->numgangports; i++)
>> +		chan->portnos[i] = chan->portnos[0] + i * CSI_PORTS_PER_BRICK;
>> +
>>   	subdev = tegra_channel_get_remote_source_subdev(chan);
>>   	v4l2_set_subdev_hostdata(subdev, chan);
>>   
>> @@ -1759,7 +1799,8 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
>>   	 * next channels.
>>   	 */
>>   	list_for_each_entry(chan, &vi->vi_chans, list) {
>> -		remote = fwnode_graph_get_remote_node(fwnode, chan->portno, 0);
>> +		remote = fwnode_graph_get_remote_node(fwnode, chan->portnos[0],
>> +						      0);
>>   		if (!remote)
>>   			continue;
>>   
>> @@ -1774,7 +1815,7 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
>>   		if (ret < 0) {
>>   			dev_err(vi->dev,
>>   				"failed to register channel %d notifier: %d\n",
>> -				chan->portno, ret);
>> +				chan->portnos[0], ret);
>>   			v4l2_async_notifier_cleanup(&chan->notifier);
>>   		}
>>   	}
>> @@ -1825,11 +1866,27 @@ static int tegra_vi_init(struct host1x_client *client)
>>   	if (!IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)) {
>>   		ret = tegra_vi_graph_init(vi);
>>   		if (ret < 0)
>> -			goto free_chans;
>> +			goto cleanup_chans;
>>   	}
>>   
>> +	/*
>> +	 * x8 capture uses multiple ports as gang with simultaneous capture
>> +	 * of left half onto one x4 port and right half onto second x4 port.
>> +	 * Total ports grouped together as gang is retrieved from CSI subdev
>> +	 * during graph build.
>> +	 * So, create host1x syncpts for all ports in a gang after graph init.
>> +	 */
>> +	ret = tegra_vi_channel_syncpt_init(vi);
>> +	if (ret < 0)
>> +		goto cleanup_graph;
>> +
>>   	return 0;
>>   
>> +cleanup_graph:
>> +	tegra_vi_graph_cleanup(vi);
>> +cleanup_chans:
>> +	list_for_each_entry(chan, &vi->vi_chans, list)
>> +		tegra_channel_cleanup(chan);
>>   free_chans:
>>   	list_for_each_entry_safe(chan, tmp, &vi->vi_chans, list) {
>>   		list_del(&chan->list);
>> diff --git a/drivers/staging/media/tegra-video/vi.h b/drivers/staging/media/tegra-video/vi.h
>> index 7d6b7a6..947e641 100644
>> --- a/drivers/staging/media/tegra-video/vi.h
>> +++ b/drivers/staging/media/tegra-video/vi.h
>> @@ -21,6 +21,8 @@
>>   #include <media/v4l2-subdev.h>
>>   #include <media/videobuf2-v4l2.h>
>>   
>> +#include "csi.h"
>> +
>>   #define TEGRA_MIN_WIDTH		32U
>>   #define TEGRA_MAX_WIDTH		32768U
>>   #define TEGRA_MIN_HEIGHT	32U
>> @@ -31,6 +33,7 @@
>>   #define TEGRA_IMAGE_FORMAT_DEF	32
>>   
>>   #define MAX_FORMAT_NUM		64
>> +#define SURFACE_ALIGN_BYTES	64
>>   
>>   enum tegra_vi_pg_mode {
>>   	TEGRA_VI_PG_DISABLED = 0,
>> @@ -151,7 +154,8 @@ struct tegra_vi_graph_entity {
>>    * @done: list of capture done queued buffers
>>    * @done_lock: protects the capture done queue list
>>    *
>> - * @portno: VI channel port number
>> + * @portnos: VI channel port numbers
>> + * @numgangports: number of ports combined together as a gang for capture
>>    * @of_node: device node of VI channel
>>    *
>>    * @ctrl_handler: V4L2 control handler of this video channel
>> @@ -168,10 +172,10 @@ struct tegra_vi_channel {
>>   	struct media_pad pad;
>>   
>>   	struct tegra_vi *vi;
>> -	struct host1x_syncpt *frame_start_sp;
>> -	struct host1x_syncpt *mw_ack_sp;
>> +	struct host1x_syncpt *frame_start_sp[GANG_PORTS_MAX];
>> +	struct host1x_syncpt *mw_ack_sp[GANG_PORTS_MAX];
>>   	/* protects the cpu syncpoint increment */
>> -	spinlock_t sp_incr_lock;
>> +	spinlock_t sp_incr_lock[GANG_PORTS_MAX];
>>   
>>   	struct task_struct *kthread_start_capture;
>>   	wait_queue_head_t start_wait;
>> @@ -190,7 +194,8 @@ struct tegra_vi_channel {
>>   	/* protects the capture done queue list */
>>   	spinlock_t done_lock;
>>   
>> -	unsigned char portno;
>> +	unsigned char portnos[GANG_PORTS_MAX];
>> +	u8 numgangports;
>>   	struct device_node *of_node;
>>   
>>   	struct v4l2_ctrl_handler ctrl_handler;
>> @@ -216,7 +221,7 @@ struct tegra_channel_buffer {
>>   	struct list_head queue;
>>   	struct tegra_vi_channel *chan;
>>   	dma_addr_t addr;
>> -	u32 mw_ack_sp_thresh;
>> +	u32 mw_ack_sp_thresh[GANG_PORTS_MAX];
>>   };
>>   
>>   /*
>>
> Regards,
>
> 	Hans

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

end of thread, other threads:[~2020-11-09 16:22 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-10-27  3:19 [PATCH v2 00/10] media: tegra-video: Add support for capturing from HDMI-to-CSI bridge Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 01/10] media: tegra-video: Use zero crop settings if subdev has no get_selection Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 02/10] media: tegra-video: Enable VI pixel transform for YUV and RGB formats Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 03/10] media: tegra-video: Fix V4L2 pixel format for RGB888_1X24 Sowjanya Komatineni
2020-11-09 12:51   ` Hans Verkuil
2020-10-27  3:19 ` [PATCH v2 04/10] media: tegra-video: Add support for V4L2_PIX_FMT_NV16 Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 05/10] media: tegra-video: Add DV timing support Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 06/10] media: tegra-video: Add support for EDID ioctl ops Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 07/10] media: tegra-video: Add support for VIDIOC_LOG_STATUS ioctl Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 08/10] media: tegra-video: Add support for V4L2_EVENT_SOURCE_CHANGE Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 09/10] media: tegra-video: Implement V4L2 device notify callback Sowjanya Komatineni
2020-10-27  3:19 ` [PATCH v2 10/10] media: tegra-video: Add support for x8 captures with gang ports Sowjanya Komatineni
2020-11-09 13:04   ` Hans Verkuil
2020-11-09 16:22     ` Sowjanya Komatineni

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).