summaryrefslogtreecommitdiff
path: root/drivers/staging/media/tegra-video
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/media/tegra-video')
-rw-r--r--drivers/staging/media/tegra-video/Kconfig1
-rw-r--r--drivers/staging/media/tegra-video/Makefile2
-rw-r--r--drivers/staging/media/tegra-video/csi.c48
-rw-r--r--drivers/staging/media/tegra-video/csi.h4
-rw-r--r--drivers/staging/media/tegra-video/tegra20.c661
-rw-r--r--drivers/staging/media/tegra-video/tegra210.c90
-rw-r--r--drivers/staging/media/tegra-video/vi.c222
-rw-r--r--drivers/staging/media/tegra-video/vi.h71
-rw-r--r--drivers/staging/media/tegra-video/video.c5
-rw-r--r--drivers/staging/media/tegra-video/video.h2
-rw-r--r--drivers/staging/media/tegra-video/vip.c287
-rw-r--r--drivers/staging/media/tegra-video/vip.h68
12 files changed, 1274 insertions, 187 deletions
diff --git a/drivers/staging/media/tegra-video/Kconfig b/drivers/staging/media/tegra-video/Kconfig
index df1b2cff2417..c53441822fdf 100644
--- a/drivers/staging/media/tegra-video/Kconfig
+++ b/drivers/staging/media/tegra-video/Kconfig
@@ -15,5 +15,6 @@ config VIDEO_TEGRA
config VIDEO_TEGRA_TPG
bool "NVIDIA Tegra VI driver TPG mode"
depends on VIDEO_TEGRA
+ depends on ARCH_TEGRA_210_SOC
help
Say yes here to enable Tegra internal TPG mode
diff --git a/drivers/staging/media/tegra-video/Makefile b/drivers/staging/media/tegra-video/Makefile
index dfa2ef8f99ef..6c7552e05109 100644
--- a/drivers/staging/media/tegra-video/Makefile
+++ b/drivers/staging/media/tegra-video/Makefile
@@ -2,7 +2,9 @@
tegra-video-objs := \
video.o \
vi.o \
+ vip.o \
csi.o
+tegra-video-$(CONFIG_ARCH_TEGRA_2x_SOC) += tegra20.o
tegra-video-$(CONFIG_ARCH_TEGRA_210_SOC) += tegra210.o
obj-$(CONFIG_VIDEO_TEGRA) += tegra-video.o
diff --git a/drivers/staging/media/tegra-video/csi.c b/drivers/staging/media/tegra-video/csi.c
index 36ca639622c9..052172017b3b 100644
--- a/drivers/staging/media/tegra-video/csi.c
+++ b/drivers/staging/media/tegra-video/csi.c
@@ -328,12 +328,42 @@ static int tegra_csi_enable_stream(struct v4l2_subdev *subdev)
}
csi_chan->pg_mode = chan->pg_mode;
+
+ /*
+ * Tegra CSI receiver can detect the first LP to HS transition.
+ * So, start the CSI stream-on prior to sensor stream-on and
+ * vice-versa for stream-off.
+ */
ret = csi->ops->csi_start_streaming(csi_chan);
if (ret < 0)
goto finish_calibration;
+ if (csi_chan->mipi) {
+ struct v4l2_subdev *src_subdev;
+ /*
+ * TRM has incorrectly documented to wait for done status from
+ * calibration logic after CSI interface power on.
+ * As per the design, calibration results are latched and applied
+ * to the pads only when the link is in LP11 state which will happen
+ * during the sensor stream-on.
+ * CSI subdev stream-on triggers start of MIPI pads calibration.
+ * Wait for calibration to finish here after sensor subdev stream-on.
+ */
+ src_subdev = tegra_channel_get_remote_source_subdev(chan);
+ ret = v4l2_subdev_call(src_subdev, video, s_stream, true);
+
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ goto disable_csi_stream;
+
+ err = tegra_mipi_finish_calibration(csi_chan->mipi);
+ if (err < 0)
+ dev_warn(csi->dev, "MIPI calibration failed: %d\n", err);
+ }
+
return 0;
+disable_csi_stream:
+ csi->ops->csi_stop_streaming(csi_chan);
finish_calibration:
if (csi_chan->mipi)
tegra_mipi_finish_calibration(csi_chan->mipi);
@@ -352,10 +382,24 @@ rpm_put:
static int tegra_csi_disable_stream(struct v4l2_subdev *subdev)
{
+ struct tegra_vi_channel *chan = v4l2_get_subdev_hostdata(subdev);
struct tegra_csi_channel *csi_chan = to_csi_chan(subdev);
struct tegra_csi *csi = csi_chan->csi;
int err;
+ /*
+ * Stream-off subdevices in reverse order to stream-on.
+ * Remote source subdev in TPG mode is same as CSI subdev.
+ */
+ if (csi_chan->mipi) {
+ struct v4l2_subdev *src_subdev;
+
+ src_subdev = tegra_channel_get_remote_source_subdev(chan);
+ err = v4l2_subdev_call(src_subdev, video, s_stream, false);
+ if (err < 0 && err != -ENOIOCTLCMD)
+ dev_err_probe(csi->dev, err, "source subdev stream off failed\n");
+ }
+
csi->ops->csi_stop_streaming(csi_chan);
if (csi_chan->mipi) {
@@ -786,6 +830,10 @@ static int tegra_csi_remove(struct platform_device *pdev)
return 0;
}
+#if defined(CONFIG_ARCH_TEGRA_210_SOC)
+extern const struct tegra_csi_soc tegra210_csi_soc;
+#endif
+
static const struct of_device_id tegra_csi_of_id_table[] = {
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
{ .compatible = "nvidia,tegra210-csi", .data = &tegra210_csi_soc },
diff --git a/drivers/staging/media/tegra-video/csi.h b/drivers/staging/media/tegra-video/csi.h
index 6960ea2e3d36..3e6e5ee1bb1e 100644
--- a/drivers/staging/media/tegra-video/csi.h
+++ b/drivers/staging/media/tegra-video/csi.h
@@ -151,10 +151,6 @@ struct tegra_csi {
struct list_head csi_chans;
};
-#if defined(CONFIG_ARCH_TEGRA_210_SOC)
-extern const struct tegra_csi_soc tegra210_csi_soc;
-#endif
-
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,
diff --git a/drivers/staging/media/tegra-video/tegra20.c b/drivers/staging/media/tegra-video/tegra20.c
new file mode 100644
index 000000000000..c25286772603
--- /dev/null
+++ b/drivers/staging/media/tegra-video/tegra20.c
@@ -0,0 +1,661 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tegra20-specific VI implementation
+ *
+ * Copyright (C) 2023 SKIDATA GmbH
+ * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
+ */
+
+/*
+ * This source file contains Tegra20 supported video formats,
+ * VI and VIP SoC specific data, operations and registers accessors.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/host1x.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/v4l2-mediabus.h>
+
+#include "vip.h"
+#include "vi.h"
+
+#define TEGRA_VI_SYNCPT_WAIT_TIMEOUT msecs_to_jiffies(200)
+
+/* This are just good-sense numbers. The actual min/max is not documented. */
+#define TEGRA20_MIN_WIDTH 32U
+#define TEGRA20_MIN_HEIGHT 32U
+#define TEGRA20_MAX_WIDTH 2048U
+#define TEGRA20_MAX_HEIGHT 2048U
+
+/* --------------------------------------------------------------------------
+ * Registers
+ */
+
+#define TEGRA_VI_CONT_SYNCPT_OUT_1 0x0060
+#define VI_CONT_SYNCPT_OUT_1_CONTINUOUS_SYNCPT BIT(8)
+#define VI_CONT_SYNCPT_OUT_1_SYNCPT_IDX_SFT 0
+
+#define TEGRA_VI_VI_INPUT_CONTROL 0x0088
+#define VI_INPUT_FIELD_DETECT BIT(27)
+#define VI_INPUT_BT656 BIT(25)
+#define VI_INPUT_YUV_INPUT_FORMAT_SFT 8 /* bits [9:8] */
+#define VI_INPUT_YUV_INPUT_FORMAT_UYVY (0 << VI_INPUT_YUV_INPUT_FORMAT_SFT)
+#define VI_INPUT_YUV_INPUT_FORMAT_VYUY (1 << VI_INPUT_YUV_INPUT_FORMAT_SFT)
+#define VI_INPUT_YUV_INPUT_FORMAT_YUYV (2 << VI_INPUT_YUV_INPUT_FORMAT_SFT)
+#define VI_INPUT_YUV_INPUT_FORMAT_YVYU (3 << VI_INPUT_YUV_INPUT_FORMAT_SFT)
+#define VI_INPUT_INPUT_FORMAT_SFT 2 /* bits [5:2] */
+#define VI_INPUT_INPUT_FORMAT_YUV422 (0 << VI_INPUT_INPUT_FORMAT_SFT)
+#define VI_INPUT_VIP_INPUT_ENABLE BIT(1)
+
+#define TEGRA_VI_VI_CORE_CONTROL 0x008c
+#define VI_VI_CORE_CONTROL_PLANAR_CONV_IN_SEL_EXT BIT(31)
+#define VI_VI_CORE_CONTROL_CSC_INPUT_SEL_EXT BIT(30)
+#define VI_VI_CORE_CONTROL_INPUT_TO_ALT_MUX_SFT 27
+#define VI_VI_CORE_CONTROL_INPUT_TO_CORE_EXT_SFT 24
+#define VI_VI_CORE_CONTROL_OUTPUT_TO_ISP_EXT_SFT 21
+#define VI_VI_CORE_CONTROL_ISP_HOST_STALL_OFF BIT(20)
+#define VI_VI_CORE_CONTROL_V_DOWNSCALING BIT(19)
+#define VI_VI_CORE_CONTROL_V_AVERAGING BIT(18)
+#define VI_VI_CORE_CONTROL_H_DOWNSCALING BIT(17)
+#define VI_VI_CORE_CONTROL_H_AVERAGING BIT(16)
+#define VI_VI_CORE_CONTROL_CSC_INPUT_SEL BIT(11)
+#define VI_VI_CORE_CONTROL_PLANAR_CONV_INPUT_SEL BIT(10)
+#define VI_VI_CORE_CONTROL_INPUT_TO_CORE_SFT 8
+#define VI_VI_CORE_CONTROL_ISP_DOWNSAMPLE_SFT 5
+#define VI_VI_CORE_CONTROL_OUTPUT_TO_EPP_SFT 2
+#define VI_VI_CORE_CONTROL_OUTPUT_TO_ISP_SFT 0
+
+#define TEGRA_VI_VI_FIRST_OUTPUT_CONTROL 0x0090
+#define VI_OUTPUT_FORMAT_EXT BIT(22)
+#define VI_OUTPUT_V_DIRECTION BIT(20)
+#define VI_OUTPUT_H_DIRECTION BIT(19)
+#define VI_OUTPUT_YUV_OUTPUT_FORMAT_SFT 17
+#define VI_OUTPUT_YUV_OUTPUT_FORMAT_UYVY (0 << VI_OUTPUT_YUV_OUTPUT_FORMAT_SFT)
+#define VI_OUTPUT_YUV_OUTPUT_FORMAT_VYUY (1 << VI_OUTPUT_YUV_OUTPUT_FORMAT_SFT)
+#define VI_OUTPUT_YUV_OUTPUT_FORMAT_YUYV (2 << VI_OUTPUT_YUV_OUTPUT_FORMAT_SFT)
+#define VI_OUTPUT_YUV_OUTPUT_FORMAT_YVYU (3 << VI_OUTPUT_YUV_OUTPUT_FORMAT_SFT)
+#define VI_OUTPUT_OUTPUT_BYTE_SWAP BIT(16)
+#define VI_OUTPUT_LAST_PIXEL_DUPLICATION BIT(8)
+#define VI_OUTPUT_OUTPUT_FORMAT_SFT 0
+#define VI_OUTPUT_OUTPUT_FORMAT_YUV422POST (3 << VI_OUTPUT_OUTPUT_FORMAT_SFT)
+#define VI_OUTPUT_OUTPUT_FORMAT_YUV420PLANAR (6 << VI_OUTPUT_OUTPUT_FORMAT_SFT)
+
+#define TEGRA_VI_VIP_H_ACTIVE 0x00a4
+#define VI_VIP_H_ACTIVE_PERIOD_SFT 16 /* active pixels/line, must be even */
+#define VI_VIP_H_ACTIVE_START_SFT 0
+
+#define TEGRA_VI_VIP_V_ACTIVE 0x00a8
+#define VI_VIP_V_ACTIVE_PERIOD_SFT 16 /* active lines */
+#define VI_VIP_V_ACTIVE_START_SFT 0
+
+#define TEGRA_VI_VB0_START_ADDRESS_FIRST 0x00c4
+#define TEGRA_VI_VB0_BASE_ADDRESS_FIRST 0x00c8
+#define TEGRA_VI_VB0_START_ADDRESS_U 0x00cc
+#define TEGRA_VI_VB0_BASE_ADDRESS_U 0x00d0
+#define TEGRA_VI_VB0_START_ADDRESS_V 0x00d4
+#define TEGRA_VI_VB0_BASE_ADDRESS_V 0x00d8
+
+#define TEGRA_VI_FIRST_OUTPUT_FRAME_SIZE 0x00e0
+#define VI_FIRST_OUTPUT_FRAME_HEIGHT_SFT 16
+#define VI_FIRST_OUTPUT_FRAME_WIDTH_SFT 0
+
+#define TEGRA_VI_VB0_COUNT_FIRST 0x00e4
+
+#define TEGRA_VI_VB0_SIZE_FIRST 0x00e8
+#define VI_VB0_SIZE_FIRST_V_SFT 16
+#define VI_VB0_SIZE_FIRST_H_SFT 0
+
+#define TEGRA_VI_VB0_BUFFER_STRIDE_FIRST 0x00ec
+#define VI_VB0_BUFFER_STRIDE_FIRST_CHROMA_SFT 30
+#define VI_VB0_BUFFER_STRIDE_FIRST_LUMA_SFT 0
+
+#define TEGRA_VI_H_LPF_CONTROL 0x0108
+#define VI_H_LPF_CONTROL_CHROMA_SFT 16
+#define VI_H_LPF_CONTROL_LUMA_SFT 0
+
+#define TEGRA_VI_H_DOWNSCALE_CONTROL 0x010c
+#define TEGRA_VI_V_DOWNSCALE_CONTROL 0x0110
+
+#define TEGRA_VI_VIP_INPUT_STATUS 0x0144
+
+#define TEGRA_VI_VI_DATA_INPUT_CONTROL 0x0168
+#define VI_DATA_INPUT_SFT 0 /* [11:0] = mask pin inputs to VI core */
+
+#define TEGRA_VI_PIN_INPUT_ENABLE 0x016c
+#define VI_PIN_INPUT_VSYNC BIT(14)
+#define VI_PIN_INPUT_HSYNC BIT(13)
+#define VI_PIN_INPUT_VD_SFT 0 /* [11:0] = data bin N input enable */
+
+#define TEGRA_VI_PIN_INVERSION 0x0174
+#define VI_PIN_INVERSION_VSYNC_ACTIVE_HIGH BIT(1)
+#define VI_PIN_INVERSION_HSYNC_ACTIVE_HIGH BIT(0)
+
+#define TEGRA_VI_CAMERA_CONTROL 0x01a0
+#define VI_CAMERA_CONTROL_STOP_CAPTURE BIT(2)
+#define VI_CAMERA_CONTROL_TEST_MODE BIT(1)
+#define VI_CAMERA_CONTROL_VIP_ENABLE BIT(0)
+
+#define TEGRA_VI_VI_ENABLE 0x01a4
+#define VI_VI_ENABLE_SW_FLOW_CONTROL_OUT1 BIT(1)
+#define VI_VI_ENABLE_FIRST_OUTPUT_TO_MEM_DISABLE BIT(0)
+
+#define TEGRA_VI_VI_RAISE 0x01ac
+#define VI_VI_RAISE_ON_EDGE BIT(0)
+
+/* --------------------------------------------------------------------------
+ * VI
+ */
+
+static void tegra20_vi_write(struct tegra_vi_channel *chan, unsigned int addr, u32 val)
+{
+ writel(val, chan->vi->iomem + addr);
+}
+
+/*
+ * Get the main input format (YUV/RGB...) and the YUV variant as values to
+ * be written into registers for the current VI input mbus code.
+ */
+static void tegra20_vi_get_input_formats(struct tegra_vi_channel *chan,
+ unsigned int *main_input_format,
+ unsigned int *yuv_input_format)
+{
+ unsigned int input_mbus_code = chan->fmtinfo->code;
+
+ (*main_input_format) = VI_INPUT_INPUT_FORMAT_YUV422;
+
+ switch (input_mbus_code) {
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ (*yuv_input_format) = VI_INPUT_YUV_INPUT_FORMAT_UYVY;
+ break;
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ (*yuv_input_format) = VI_INPUT_YUV_INPUT_FORMAT_VYUY;
+ break;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ (*yuv_input_format) = VI_INPUT_YUV_INPUT_FORMAT_YUYV;
+ break;
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ (*yuv_input_format) = VI_INPUT_YUV_INPUT_FORMAT_YVYU;
+ break;
+ }
+}
+
+/*
+ * Get the main output format (YUV/RGB...) and the YUV variant as values to
+ * be written into registers for the current VI output pixel format.
+ */
+static void tegra20_vi_get_output_formats(struct tegra_vi_channel *chan,
+ unsigned int *main_output_format,
+ unsigned int *yuv_output_format)
+{
+ u32 output_fourcc = chan->format.pixelformat;
+
+ /* Default to YUV422 non-planar (U8Y8V8Y8) after downscaling */
+ (*main_output_format) = VI_OUTPUT_OUTPUT_FORMAT_YUV422POST;
+ (*yuv_output_format) = VI_OUTPUT_YUV_OUTPUT_FORMAT_UYVY;
+
+ switch (output_fourcc) {
+ case V4L2_PIX_FMT_UYVY:
+ (*yuv_output_format) = VI_OUTPUT_YUV_OUTPUT_FORMAT_UYVY;
+ break;
+ case V4L2_PIX_FMT_VYUY:
+ (*yuv_output_format) = VI_OUTPUT_YUV_OUTPUT_FORMAT_VYUY;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ (*yuv_output_format) = VI_OUTPUT_YUV_OUTPUT_FORMAT_YUYV;
+ break;
+ case V4L2_PIX_FMT_YVYU:
+ (*yuv_output_format) = VI_OUTPUT_YUV_OUTPUT_FORMAT_YVYU;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ (*main_output_format) = VI_OUTPUT_OUTPUT_FORMAT_YUV420PLANAR;
+ break;
+ }
+}
+
+/*
+ * Make the VI accessible (needed on Tegra20).
+ *
+ * This function writes an unknown bit into an unknown register. The code
+ * comes from a downstream 3.1 kernel that has a working VIP driver for
+ * Tegra20, and removing it makes the VI completely unaccessible. It should
+ * be rewritten and possibly moved elsewhere, but the appropriate location
+ * and implementation is unknown due to a total lack of documentation.
+ */
+static int tegra20_vi_enable(struct tegra_vi *vi, bool on)
+{
+ /* from arch/arm/mach-tegra/iomap.h */
+ const phys_addr_t TEGRA_APB_MISC_BASE = 0x70000000;
+ const unsigned long reg_offset = 0x42c;
+ void __iomem *apb_misc;
+ u32 val;
+
+ apb_misc = ioremap(TEGRA_APB_MISC_BASE, PAGE_SIZE);
+ if (!apb_misc)
+ apb_misc = ERR_PTR(-ENOENT);
+ if (IS_ERR(apb_misc))
+ return dev_err_probe(vi->dev, PTR_ERR(apb_misc), "cannot access APB_MISC");
+
+ val = readl(apb_misc + reg_offset);
+ val &= ~BIT(0);
+ val |= on ? BIT(0) : 0;
+ writel(val, apb_misc + reg_offset);
+ iounmap(apb_misc);
+
+ return 0;
+}
+
+static int tegra20_channel_host1x_syncpt_init(struct tegra_vi_channel *chan)
+{
+ struct tegra_vi *vi = chan->vi;
+ struct host1x_syncpt *out_sp;
+
+ out_sp = host1x_syncpt_request(&vi->client, HOST1X_SYNCPT_CLIENT_MANAGED);
+ if (!out_sp)
+ return dev_err_probe(vi->dev, -ENOMEM, "failed to request syncpoint\n");
+
+ chan->mw_ack_sp[0] = out_sp;
+
+ return 0;
+}
+
+static void tegra20_channel_host1x_syncpt_free(struct tegra_vi_channel *chan)
+{
+ host1x_syncpt_put(chan->mw_ack_sp[0]);
+}
+
+static void tegra20_fmt_align(struct v4l2_pix_format *pix, unsigned int bpp)
+{
+ pix->width = clamp(pix->width, TEGRA20_MIN_WIDTH, TEGRA20_MAX_WIDTH);
+ pix->height = clamp(pix->height, TEGRA20_MIN_HEIGHT, TEGRA20_MAX_HEIGHT);
+
+ switch (pix->pixelformat) {
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ pix->bytesperline = roundup(pix->width, 2) * 2;
+ pix->sizeimage = roundup(pix->width, 2) * 2 * pix->height;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ pix->bytesperline = roundup(pix->width, 8);
+ pix->sizeimage = roundup(pix->width, 8) * pix->height * 3 / 2;
+ break;
+ }
+}
+
+/*
+ * Compute buffer offsets once per stream so that
+ * tegra20_channel_vi_buffer_setup() only has to do very simple maths for
+ * each buffer.
+ */
+static void tegra20_channel_queue_setup(struct tegra_vi_channel *chan)
+{
+ unsigned int stride = chan->format.bytesperline;
+ unsigned int height = chan->format.height;
+
+ chan->start_offset = 0;
+
+ switch (chan->format.pixelformat) {
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ if (chan->vflip)
+ chan->start_offset += stride * (height - 1);
+ if (chan->hflip)
+ chan->start_offset += stride - 1;
+ break;
+
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ chan->addr_offset_u = stride * height;
+ chan->addr_offset_v = chan->addr_offset_u + stride * height / 4;
+
+ /* For YVU420, we swap the locations of the U and V planes. */
+ if (chan->format.pixelformat == V4L2_PIX_FMT_YVU420) {
+ unsigned long temp;
+
+ temp = chan->addr_offset_u;
+ chan->addr_offset_u = chan->addr_offset_v;
+ chan->addr_offset_v = temp;
+ }
+
+ chan->start_offset_u = chan->addr_offset_u;
+ chan->start_offset_v = chan->addr_offset_v;
+
+ if (chan->vflip) {
+ chan->start_offset += stride * (height - 1);
+ chan->start_offset_u += (stride / 2) * ((height / 2) - 1);
+ chan->start_offset_v += (stride / 2) * ((height / 2) - 1);
+ }
+ if (chan->hflip) {
+ chan->start_offset += stride - 1;
+ chan->start_offset_u += (stride / 2) - 1;
+ chan->start_offset_v += (stride / 2) - 1;
+ }
+ break;
+ }
+}
+
+static void release_buffer(struct tegra_vi_channel *chan,
+ struct tegra_channel_buffer *buf,
+ enum vb2_buffer_state state)
+{
+ struct vb2_v4l2_buffer *vb = &buf->buf;
+
+ vb->sequence = chan->sequence++;
+ vb->field = V4L2_FIELD_NONE;
+ vb->vb2_buf.timestamp = ktime_get_ns();
+ vb2_buffer_done(&vb->vb2_buf, state);
+}
+
+static void tegra20_channel_vi_buffer_setup(struct tegra_vi_channel *chan,
+ struct tegra_channel_buffer *buf)
+{
+ dma_addr_t base = buf->addr;
+
+ switch (chan->fmtinfo->fourcc) {
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ tegra20_vi_write(chan, TEGRA_VI_VB0_BASE_ADDRESS_U, base + chan->addr_offset_u);
+ tegra20_vi_write(chan, TEGRA_VI_VB0_START_ADDRESS_U, base + chan->start_offset_u);
+ tegra20_vi_write(chan, TEGRA_VI_VB0_BASE_ADDRESS_V, base + chan->addr_offset_v);
+ tegra20_vi_write(chan, TEGRA_VI_VB0_START_ADDRESS_V, base + chan->start_offset_v);
+ fallthrough;
+
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_VYUY:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YVYU:
+ tegra20_vi_write(chan, TEGRA_VI_VB0_BASE_ADDRESS_FIRST, base);
+ tegra20_vi_write(chan, TEGRA_VI_VB0_START_ADDRESS_FIRST, base + chan->start_offset);
+ break;
+ }
+}
+
+static int tegra20_channel_capture_frame(struct tegra_vi_channel *chan,
+ struct tegra_channel_buffer *buf)
+{
+ int err;
+
+ chan->next_out_sp_idx++;
+
+ tegra20_channel_vi_buffer_setup(chan, buf);
+
+ tegra20_vi_write(chan, TEGRA_VI_CAMERA_CONTROL, VI_CAMERA_CONTROL_VIP_ENABLE);
+
+ /* Wait for syncpt counter to reach frame start event threshold */
+ err = host1x_syncpt_wait(chan->mw_ack_sp[0], chan->next_out_sp_idx,
+ TEGRA_VI_SYNCPT_WAIT_TIMEOUT, NULL);
+ if (err) {
+ host1x_syncpt_incr(chan->mw_ack_sp[0]);
+ dev_err_ratelimited(&chan->video.dev, "frame start syncpt timeout: %d\n", err);
+ release_buffer(chan, buf, VB2_BUF_STATE_ERROR);
+ return err;
+ }
+
+ tegra20_vi_write(chan, TEGRA_VI_CAMERA_CONTROL,
+ VI_CAMERA_CONTROL_STOP_CAPTURE | VI_CAMERA_CONTROL_VIP_ENABLE);
+
+ release_buffer(chan, buf, VB2_BUF_STATE_DONE);
+
+ return 0;
+}
+
+static int tegra20_chan_capture_kthread_start(void *data)
+{
+ struct tegra_vi_channel *chan = data;
+ struct tegra_channel_buffer *buf;
+ unsigned int retries = 0;
+ int err = 0;
+
+ while (1) {
+ /*
+ * Source is not streaming if error is non-zero.
+ * So, do not dequeue buffers on error and let the thread sleep
+ * till kthread stop signal is received.
+ */
+ wait_event_interruptible(chan->start_wait,
+ kthread_should_stop() ||
+ (!list_empty(&chan->capture) && !err));
+
+ if (kthread_should_stop())
+ break;
+
+ /* dequeue the buffer and start capture */
+ spin_lock(&chan->start_lock);
+ if (list_empty(&chan->capture)) {
+ spin_unlock(&chan->start_lock);
+ continue;
+ }
+
+ buf = list_first_entry(&chan->capture, struct tegra_channel_buffer, queue);
+ list_del_init(&buf->queue);
+ spin_unlock(&chan->start_lock);
+
+ err = tegra20_channel_capture_frame(chan, buf);
+ if (!err) {
+ retries = 0;
+ continue;
+ }
+
+ if (retries++ > chan->syncpt_timeout_retry)
+ vb2_queue_error(&chan->queue);
+ else
+ err = 0;
+ }
+
+ return 0;
+}
+
+static void tegra20_camera_capture_setup(struct tegra_vi_channel *chan)
+{
+ u32 output_fourcc = chan->format.pixelformat;
+ int width = chan->format.width;
+ int height = chan->format.height;
+ int stride_l = chan->format.bytesperline;
+ int stride_c = (output_fourcc == V4L2_PIX_FMT_YUV420 ||
+ output_fourcc == V4L2_PIX_FMT_YVU420) ? 1 : 0;
+ int main_output_format;
+ int yuv_output_format;
+
+ tegra20_vi_get_output_formats(chan, &main_output_format, &yuv_output_format);
+
+ /*
+ * Set up low pass filter. Use 0x240 for chromaticity and 0x240
+ * for luminance, which is the default and means not to touch
+ * anything.
+ */
+ tegra20_vi_write(chan, TEGRA_VI_H_LPF_CONTROL,
+ 0x0240 << VI_H_LPF_CONTROL_LUMA_SFT |
+ 0x0240 << VI_H_LPF_CONTROL_CHROMA_SFT);
+
+ /* Set up raise-on-edge, so we get an interrupt on end of frame. */
+ tegra20_vi_write(chan, TEGRA_VI_VI_RAISE, VI_VI_RAISE_ON_EDGE);
+
+ tegra20_vi_write(chan, TEGRA_VI_VI_FIRST_OUTPUT_CONTROL,
+ (chan->vflip ? VI_OUTPUT_V_DIRECTION : 0) |
+ (chan->hflip ? VI_OUTPUT_H_DIRECTION : 0) |
+ yuv_output_format << VI_OUTPUT_YUV_OUTPUT_FORMAT_SFT |
+ main_output_format << VI_OUTPUT_OUTPUT_FORMAT_SFT);
+
+ /* Set up frame size */
+ tegra20_vi_write(chan, TEGRA_VI_FIRST_OUTPUT_FRAME_SIZE,
+ height << VI_FIRST_OUTPUT_FRAME_HEIGHT_SFT |
+ width << VI_FIRST_OUTPUT_FRAME_WIDTH_SFT);
+
+ /* First output memory enabled */
+ tegra20_vi_write(chan, TEGRA_VI_VI_ENABLE, 0);
+
+ /* Set the number of frames in the buffer */
+ tegra20_vi_write(chan, TEGRA_VI_VB0_COUNT_FIRST, 1);
+
+ /* Set up buffer frame size */
+ tegra20_vi_write(chan, TEGRA_VI_VB0_SIZE_FIRST,
+ height << VI_VB0_SIZE_FIRST_V_SFT |
+ width << VI_VB0_SIZE_FIRST_H_SFT);
+
+ tegra20_vi_write(chan, TEGRA_VI_VB0_BUFFER_STRIDE_FIRST,
+ stride_l << VI_VB0_BUFFER_STRIDE_FIRST_LUMA_SFT |
+ stride_c << VI_VB0_BUFFER_STRIDE_FIRST_CHROMA_SFT);
+
+ tegra20_vi_write(chan, TEGRA_VI_VI_ENABLE, 0);
+}
+
+static int tegra20_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;
+ int err;
+
+ chan->next_out_sp_idx = host1x_syncpt_read(chan->mw_ack_sp[0]);
+
+ err = video_device_pipeline_start(&chan->video, pipe);
+ if (err)
+ goto error_pipeline_start;
+
+ tegra20_camera_capture_setup(chan);
+
+ err = tegra_channel_set_stream(chan, true);
+ if (err)
+ goto error_set_stream;
+
+ chan->sequence = 0;
+
+ chan->kthread_start_capture = kthread_run(tegra20_chan_capture_kthread_start,
+ chan, "%s:0", chan->video.name);
+ if (IS_ERR(chan->kthread_start_capture)) {
+ err = PTR_ERR(chan->kthread_start_capture);
+ chan->kthread_start_capture = NULL;
+ dev_err_probe(&chan->video.dev, err, "failed to run capture kthread\n");
+ goto error_kthread_start;
+ }
+
+ return 0;
+
+error_kthread_start:
+ tegra_channel_set_stream(chan, false);
+error_set_stream:
+ video_device_pipeline_stop(&chan->video);
+error_pipeline_start:
+ tegra_channel_release_buffers(chan, VB2_BUF_STATE_QUEUED);
+
+ return err;
+}
+
+static void tegra20_vi_stop_streaming(struct vb2_queue *vq)
+{
+ struct tegra_vi_channel *chan = vb2_get_drv_priv(vq);
+
+ if (chan->kthread_start_capture) {
+ kthread_stop(chan->kthread_start_capture);
+ chan->kthread_start_capture = NULL;
+ }
+
+ tegra_channel_release_buffers(chan, VB2_BUF_STATE_ERROR);
+ tegra_channel_set_stream(chan, false);
+ video_device_pipeline_stop(&chan->video);
+}
+
+static const struct tegra_vi_ops tegra20_vi_ops = {
+ .vi_enable = tegra20_vi_enable,
+ .channel_host1x_syncpt_init = tegra20_channel_host1x_syncpt_init,
+ .channel_host1x_syncpt_free = tegra20_channel_host1x_syncpt_free,
+ .vi_fmt_align = tegra20_fmt_align,
+ .channel_queue_setup = tegra20_channel_queue_setup,
+ .vi_start_streaming = tegra20_vi_start_streaming,
+ .vi_stop_streaming = tegra20_vi_stop_streaming,
+};
+
+#define TEGRA20_VIDEO_FMT(MBUS_CODE, BPP, FOURCC) \
+{ \
+ .code = MEDIA_BUS_FMT_##MBUS_CODE, \
+ .bpp = BPP, \
+ .fourcc = V4L2_PIX_FMT_##FOURCC, \
+}
+
+static const struct tegra_video_format tegra20_video_formats[] = {
+ TEGRA20_VIDEO_FMT(UYVY8_2X8, 2, UYVY),
+ TEGRA20_VIDEO_FMT(VYUY8_2X8, 2, VYUY),
+ TEGRA20_VIDEO_FMT(YUYV8_2X8, 2, YUYV),
+ TEGRA20_VIDEO_FMT(YVYU8_2X8, 2, YVYU),
+ TEGRA20_VIDEO_FMT(UYVY8_2X8, 1, YUV420),
+ TEGRA20_VIDEO_FMT(UYVY8_2X8, 1, YVU420),
+};
+
+const struct tegra_vi_soc tegra20_vi_soc = {
+ .video_formats = tegra20_video_formats,
+ .nformats = ARRAY_SIZE(tegra20_video_formats),
+ .default_video_format = &tegra20_video_formats[0],
+ .ops = &tegra20_vi_ops,
+ .vi_max_channels = 1, /* parallel input (VIP) */
+ .vi_max_clk_hz = 150000000,
+ .has_h_v_flip = true,
+};
+
+/* --------------------------------------------------------------------------
+ * VIP
+ */
+
+/*
+ * VIP-specific configuration for stream start.
+ *
+ * Whatever is common among VIP and CSI is done by the VI component (see
+ * tegra20_vi_start_streaming()). Here we do what is VIP-specific.
+ */
+static int tegra20_vip_start_streaming(struct tegra_vip_channel *vip_chan)
+{
+ struct tegra_vi_channel *vi_chan = v4l2_get_subdev_hostdata(&vip_chan->subdev);
+ int width = vi_chan->format.width;
+ int height = vi_chan->format.height;
+
+ unsigned int main_input_format;
+ unsigned int yuv_input_format;
+
+ tegra20_vi_get_input_formats(vi_chan, &main_input_format, &yuv_input_format);
+
+ tegra20_vi_write(vi_chan, TEGRA_VI_VI_CORE_CONTROL, 0);
+
+ tegra20_vi_write(vi_chan, TEGRA_VI_VI_INPUT_CONTROL,
+ VI_INPUT_VIP_INPUT_ENABLE | main_input_format | yuv_input_format);
+
+ tegra20_vi_write(vi_chan, TEGRA_VI_V_DOWNSCALE_CONTROL, 0);
+ tegra20_vi_write(vi_chan, TEGRA_VI_H_DOWNSCALE_CONTROL, 0);
+
+ tegra20_vi_write(vi_chan, TEGRA_VI_VIP_V_ACTIVE, height << VI_VIP_V_ACTIVE_PERIOD_SFT);
+ tegra20_vi_write(vi_chan, TEGRA_VI_VIP_H_ACTIVE,
+ roundup(width, 2) << VI_VIP_H_ACTIVE_PERIOD_SFT);
+
+ /*
+ * For VIP, D9..D2 is mapped to the video decoder's P7..P0.
+ * Disable/mask out the other Dn wires. When not in BT656
+ * mode we also need the V/H sync.
+ */
+ tegra20_vi_write(vi_chan, TEGRA_VI_PIN_INPUT_ENABLE,
+ GENMASK(9, 2) << VI_PIN_INPUT_VD_SFT |
+ VI_PIN_INPUT_HSYNC | VI_PIN_INPUT_VSYNC);
+ tegra20_vi_write(vi_chan, TEGRA_VI_VI_DATA_INPUT_CONTROL,
+ GENMASK(9, 2) << VI_DATA_INPUT_SFT);
+ tegra20_vi_write(vi_chan, TEGRA_VI_PIN_INVERSION, 0);
+
+ tegra20_vi_write(vi_chan, TEGRA_VI_CONT_SYNCPT_OUT_1,
+ VI_CONT_SYNCPT_OUT_1_CONTINUOUS_SYNCPT |
+ host1x_syncpt_id(vi_chan->mw_ack_sp[0])
+ << VI_CONT_SYNCPT_OUT_1_SYNCPT_IDX_SFT);
+
+ tegra20_vi_write(vi_chan, TEGRA_VI_CAMERA_CONTROL, VI_CAMERA_CONTROL_STOP_CAPTURE);
+
+ return 0;
+}
+
+static const struct tegra_vip_ops tegra20_vip_ops = {
+ .vip_start_streaming = tegra20_vip_start_streaming,
+};
+
+const struct tegra_vip_soc tegra20_vip_soc = {
+ .ops = &tegra20_vip_ops,
+};
diff --git a/drivers/staging/media/tegra-video/tegra210.c b/drivers/staging/media/tegra-video/tegra210.c
index d58370a84737..da99f19a39e7 100644
--- a/drivers/staging/media/tegra-video/tegra210.c
+++ b/drivers/staging/media/tegra-video/tegra210.c
@@ -17,6 +17,13 @@
#include "csi.h"
#include "vi.h"
+#define TEGRA210_MIN_WIDTH 32U
+#define TEGRA210_MAX_WIDTH 32768U
+#define TEGRA210_MIN_HEIGHT 32U
+#define TEGRA210_MAX_HEIGHT 32768U
+
+#define SURFACE_ALIGN_BYTES 64
+
#define TEGRA_VI_SYNCPT_WAIT_TIMEOUT msecs_to_jiffies(200)
/* Tegra210 VI registers */
@@ -172,6 +179,84 @@ static u32 vi_csi_read(struct tegra_vi_channel *chan, u8 portno,
/*
* Tegra210 VI channel capture operations
*/
+
+static int tegra210_channel_host1x_syncpt_init(struct tegra_vi_channel *chan)
+{
+ struct tegra_vi *vi = chan->vi;
+ unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
+ struct host1x_syncpt *fs_sp;
+ struct host1x_syncpt *mw_sp;
+ int ret, i;
+
+ 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");
+ host1x_syncpt_put(fs_sp);
+ ret = -ENOMEM;
+ 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:
+ for (i = 0; i < chan->numgangports; i++) {
+ host1x_syncpt_put(chan->mw_ack_sp[i]);
+ host1x_syncpt_put(chan->frame_start_sp[i]);
+ }
+ return ret;
+}
+
+static void tegra210_channel_host1x_syncpt_free(struct tegra_vi_channel *chan)
+{
+ int i;
+
+ for (i = 0; i < chan->numgangports; i++) {
+ host1x_syncpt_put(chan->mw_ack_sp[i]);
+ host1x_syncpt_put(chan->frame_start_sp[i]);
+ }
+}
+
+static void tegra210_fmt_align(struct v4l2_pix_format *pix, unsigned int bpp)
+{
+ unsigned int min_bpl;
+ unsigned int max_bpl;
+ unsigned int bpl;
+
+ /*
+ * The transfer alignment requirements are expressed in bytes.
+ * Clamp the requested width and height to the limits.
+ */
+ pix->width = clamp(pix->width, TEGRA210_MIN_WIDTH, TEGRA210_MAX_WIDTH);
+ pix->height = clamp(pix->height, TEGRA210_MIN_HEIGHT, TEGRA210_MAX_HEIGHT);
+
+ /* Clamp the requested bytes per line value. If the maximum bytes per
+ * line value is zero, the module doesn't support user configurable
+ * line sizes. Override the requested value with the minimum in that
+ * case.
+ */
+ min_bpl = pix->width * bpp;
+ max_bpl = rounddown(TEGRA210_MAX_WIDTH, SURFACE_ALIGN_BYTES);
+ bpl = roundup(pix->bytesperline, SURFACE_ALIGN_BYTES);
+
+ 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_capture_setup(struct tegra_vi_channel *chan,
u8 portno)
{
@@ -718,6 +803,9 @@ static const struct tegra_video_format tegra210_video_formats[] = {
/* Tegra210 VI operations */
static const struct tegra_vi_ops tegra210_vi_ops = {
+ .channel_host1x_syncpt_init = tegra210_channel_host1x_syncpt_init,
+ .channel_host1x_syncpt_free = tegra210_channel_host1x_syncpt_free,
+ .vi_fmt_align = tegra210_fmt_align,
.vi_start_streaming = tegra210_vi_start_streaming,
.vi_stop_streaming = tegra210_vi_stop_streaming,
};
@@ -730,8 +818,10 @@ const struct tegra_vi_soc tegra210_vi_soc = {
.hw_revision = 3,
.vi_max_channels = 6,
#if IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG)
+ .default_video_format = &tegra210_video_formats[0],
.vi_max_clk_hz = 499200000,
#else
+ .default_video_format = &tegra210_video_formats[4],
.vi_max_clk_hz = 998400000,
#endif
};
diff --git a/drivers/staging/media/tegra-video/vi.c b/drivers/staging/media/tegra-video/vi.c
index 2f1aff7e8717..79284c3b6cae 100644
--- a/drivers/staging/media/tegra-video/vi.c
+++ b/drivers/staging/media/tegra-video/vi.c
@@ -30,15 +30,19 @@
#include "vi.h"
#include "video.h"
-#define MAX_CID_CONTROLS 1
-
-static const struct tegra_video_format tegra_default_format = {
- .img_dt = TEGRA_IMAGE_DT_RAW10,
- .bit_width = 10,
- .code = MEDIA_BUS_FMT_SRGGB10_1X10,
- .bpp = 2,
- .img_fmt = TEGRA_IMAGE_FORMAT_DEF,
- .fourcc = V4L2_PIX_FMT_SRGGB10,
+#define MAX_CID_CONTROLS 3
+
+/**
+ * struct tegra_vi_graph_entity - Entity in the video graph
+ *
+ * @asd: subdev asynchronous registration information
+ * @entity: media entity from the corresponding V4L2 subdev
+ * @subdev: V4L2 subdev
+ */
+struct tegra_vi_graph_entity {
+ struct v4l2_async_subdev asd;
+ struct media_entity *entity;
+ struct v4l2_subdev *subdev;
};
static inline struct tegra_vi *
@@ -98,6 +102,7 @@ tegra_get_format_by_fourcc(struct tegra_vi *vi, u32 fourcc)
/*
* videobuf2 queue operations
*/
+
static int tegra_channel_queue_setup(struct vb2_queue *vq,
unsigned int *nbuffers,
unsigned int *nplanes,
@@ -113,6 +118,9 @@ static int tegra_channel_queue_setup(struct vb2_queue *vq,
sizes[0] = chan->format.sizeimage;
alloc_devs[0] = chan->vi->dev;
+ if (chan->vi->ops->channel_queue_setup)
+ chan->vi->ops->channel_queue_setup(chan);
+
return 0;
}
@@ -164,6 +172,9 @@ tegra_channel_get_remote_csi_subdev(struct tegra_vi_channel *chan)
return media_entity_to_v4l2_subdev(pad->entity);
}
+/*
+ * Walk up the chain until the initial source (e.g. image sensor)
+ */
struct v4l2_subdev *
tegra_channel_get_remote_source_subdev(struct tegra_vi_channel *chan)
{
@@ -190,49 +201,15 @@ tegra_channel_get_remote_source_subdev(struct tegra_vi_channel *chan)
static int tegra_channel_enable_stream(struct tegra_vi_channel *chan)
{
- struct v4l2_subdev *csi_subdev, *src_subdev;
- struct tegra_csi_channel *csi_chan;
- int ret, err;
+ struct v4l2_subdev *subdev;
+ int ret;
- /*
- * Tegra CSI receiver can detect the first LP to HS transition.
- * So, start the CSI stream-on prior to sensor stream-on and
- * vice-versa for stream-off.
- */
- csi_subdev = tegra_channel_get_remote_csi_subdev(chan);
- ret = v4l2_subdev_call(csi_subdev, video, s_stream, true);
+ subdev = tegra_channel_get_remote_csi_subdev(chan);
+ ret = v4l2_subdev_call(subdev, video, s_stream, true);
if (ret < 0 && ret != -ENOIOCTLCMD)
return ret;
- if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
- return 0;
-
- csi_chan = v4l2_get_subdevdata(csi_subdev);
- /*
- * TRM has incorrectly documented to wait for done status from
- * calibration logic after CSI interface power on.
- * As per the design, calibration results are latched and applied
- * to the pads only when the link is in LP11 state which will happen
- * during the sensor stream-on.
- * CSI subdev stream-on triggers start of MIPI pads calibration.
- * Wait for calibration to finish here after sensor subdev stream-on.
- */
- src_subdev = tegra_channel_get_remote_source_subdev(chan);
- ret = v4l2_subdev_call(src_subdev, video, s_stream, true);
- err = tegra_mipi_finish_calibration(csi_chan->mipi);
-
- if (ret < 0 && ret != -ENOIOCTLCMD)
- goto err_disable_csi_stream;
-
- if (err < 0)
- dev_warn(csi_chan->csi->dev,
- "MIPI calibration failed: %d\n", err);
-
return 0;
-
-err_disable_csi_stream:
- v4l2_subdev_call(csi_subdev, video, s_stream, false);
- return ret;
}
static int tegra_channel_disable_stream(struct tegra_vi_channel *chan)
@@ -240,18 +217,6 @@ static int tegra_channel_disable_stream(struct tegra_vi_channel *chan)
struct v4l2_subdev *subdev;
int ret;
- /*
- * Stream-off subdevices in reverse order to stream-on.
- * Remote source subdev in TPG mode is same as CSI subdev.
- */
- subdev = tegra_channel_get_remote_source_subdev(chan);
- ret = v4l2_subdev_call(subdev, video, s_stream, false);
- if (ret < 0 && ret != -ENOIOCTLCMD)
- return ret;
-
- if (IS_ENABLED(CONFIG_VIDEO_TEGRA_TPG))
- return 0;
-
subdev = tegra_channel_get_remote_csi_subdev(chan);
ret = v4l2_subdev_call(subdev, video, s_stream, false);
if (ret < 0 && ret != -ENOIOCTLCMD)
@@ -457,36 +422,6 @@ static int tegra_channel_get_format(struct file *file, void *fh,
return 0;
}
-static void tegra_channel_fmt_align(struct tegra_vi_channel *chan,
- struct v4l2_pix_format *pix,
- unsigned int bpp)
-{
- unsigned int min_bpl;
- unsigned int max_bpl;
- unsigned int bpl;
-
- /*
- * The transfer alignment requirements are expressed in bytes.
- * Clamp the requested width and height to the limits.
- */
- pix->width = clamp(pix->width, TEGRA_MIN_WIDTH, TEGRA_MAX_WIDTH);
- pix->height = clamp(pix->height, TEGRA_MIN_HEIGHT, TEGRA_MAX_HEIGHT);
-
- /* Clamp the requested bytes per line value. If the maximum bytes per
- * line value is zero, the module doesn't support user configurable
- * line sizes. Override the requested value with the minimum in that
- * case.
- */
- min_bpl = pix->width * bpp;
- max_bpl = rounddown(TEGRA_MAX_WIDTH, SURFACE_ALIGN_BYTES);
- bpl = roundup(pix->bytesperline, SURFACE_ALIGN_BYTES);
-
- 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,
struct v4l2_pix_format *pix)
{
@@ -563,7 +498,7 @@ static int __tegra_channel_try_format(struct tegra_vi_channel *chan,
return ret;
v4l2_fill_pix_format(pix, &fmt.format);
- tegra_channel_fmt_align(chan, pix, fmtinfo->bpp);
+ chan->vi->ops->vi_fmt_align(pix, fmtinfo->bpp);
__v4l2_subdev_state_free(sd_state);
@@ -616,7 +551,7 @@ static int tegra_channel_set_format(struct file *file, void *fh,
return ret;
v4l2_fill_pix_format(pix, &fmt.format);
- tegra_channel_fmt_align(chan, pix, fmtinfo->bpp);
+ chan->vi->ops->vi_fmt_align(pix, fmtinfo->bpp);
chan->format = *pix;
chan->fmtinfo = fmtinfo;
@@ -652,7 +587,7 @@ static int tegra_channel_set_subdev_active_fmt(struct tegra_vi_channel *chan)
chan->format.bytesperline = chan->format.width * chan->fmtinfo->bpp;
chan->format.sizeimage = chan->format.bytesperline *
chan->format.height;
- tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
+ chan->vi->ops->vi_fmt_align(&chan->format, chan->fmtinfo->bpp);
tegra_channel_update_gangports(chan);
return 0;
@@ -821,7 +756,7 @@ static int tegra_channel_s_dv_timings(struct file *file, void *fh,
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);
+ chan->vi->ops->vi_fmt_align(&chan->format, chan->fmtinfo->bpp);
tegra_channel_update_gangports(chan);
return 0;
@@ -977,6 +912,12 @@ static int vi_s_ctrl(struct v4l2_ctrl *ctrl)
case V4L2_CID_TEGRA_SYNCPT_TIMEOUT_RETRY:
chan->syncpt_timeout_retry = ctrl->val;
break;
+ case V4L2_CID_HFLIP:
+ chan->hflip = ctrl->val;
+ break;
+ case V4L2_CID_VFLIP:
+ chan->vflip = ctrl->val;
+ break;
default:
return -EINVAL;
}
@@ -1048,6 +989,12 @@ static int tegra_channel_setup_ctrl_handler(struct tegra_vi_channel *chan)
v4l2_ctrl_handler_free(&chan->ctrl_handler);
return ret;
}
+
+ if (chan->vi->soc->has_h_v_flip) {
+ v4l2_ctrl_new_std(&chan->ctrl_handler, &vi_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0);
+ v4l2_ctrl_new_std(&chan->ctrl_handler, &vi_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0);
+ }
+
#endif
/* setup the controls */
@@ -1119,7 +1066,7 @@ static int vi_fmts_bitmap_init(struct tegra_vi_channel *chan)
* there are no matched formats.
*/
if (!match_code) {
- match_code = tegra_default_format.code;
+ match_code = chan->vi->soc->default_video_format->code;
index = tegra_get_format_idx_by_code(chan->vi, match_code, 0);
if (WARN_ON(index < 0))
return -EINVAL;
@@ -1133,21 +1080,11 @@ 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_put(chan->mw_ack_sp[i]);
- host1x_syncpt_put(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);
- tegra_channel_host1x_syncpts_free(chan);
+ chan->vi->ops->channel_host1x_syncpt_free(chan);
mutex_destroy(&chan->video_lock);
}
@@ -1165,42 +1102,6 @@ void tegra_channels_cleanup(struct tegra_vi *vi)
}
}
-static int tegra_channel_host1x_syncpt_init(struct tegra_vi_channel *chan)
-{
- struct tegra_vi *vi = chan->vi;
- unsigned long flags = HOST1X_SYNCPT_CLIENT_MANAGED;
- struct host1x_syncpt *fs_sp;
- struct host1x_syncpt *mw_sp;
- int ret, i;
-
- 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");
- host1x_syncpt_put(fs_sp);
- ret = -ENOMEM;
- 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:
- tegra_channel_host1x_syncpts_free(chan);
- return ret;
-}
-
static int tegra_channel_init(struct tegra_vi_channel *chan)
{
struct tegra_vi *vi = chan->vi;
@@ -1216,7 +1117,7 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
init_waitqueue_head(&chan->done_wait);
/* initialize the video format */
- chan->fmtinfo = &tegra_default_format;
+ chan->fmtinfo = chan->vi->soc->default_video_format;
chan->format.pixelformat = chan->fmtinfo->fourcc;
chan->format.colorspace = V4L2_COLORSPACE_SRGB;
chan->format.field = V4L2_FIELD_NONE;
@@ -1224,9 +1125,9 @@ static int tegra_channel_init(struct tegra_vi_channel *chan)
chan->format.height = TEGRA_DEF_HEIGHT;
chan->format.bytesperline = TEGRA_DEF_WIDTH * chan->fmtinfo->bpp;
chan->format.sizeimage = chan->format.bytesperline * TEGRA_DEF_HEIGHT;
- tegra_channel_fmt_align(chan, &chan->format, chan->fmtinfo->bpp);
+ vi->ops->vi_fmt_align(&chan->format, chan->fmtinfo->bpp);
- ret = tegra_channel_host1x_syncpt_init(chan);
+ ret = vi->ops->channel_host1x_syncpt_init(chan);
if (ret)
return ret;
@@ -1289,7 +1190,7 @@ free_v4l2_ctrl_hdl:
cleanup_media:
media_entity_cleanup(&chan->video.entity);
free_syncpts:
- tegra_channel_host1x_syncpts_free(chan);
+ vi->ops->channel_host1x_syncpt_free(chan);
return ret;
}
@@ -1351,7 +1252,7 @@ static int tegra_vi_channels_alloc(struct tegra_vi *vi)
struct device_node *node = vi->dev->of_node;
struct device_node *ep = NULL;
struct device_node *ports;
- struct device_node *port;
+ struct device_node *port = NULL;
unsigned int port_num;
struct device_node *parent;
struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 };
@@ -1360,7 +1261,7 @@ static int tegra_vi_channels_alloc(struct tegra_vi *vi)
ports = of_get_child_by_name(node, "ports");
if (!ports)
- return -ENODEV;
+ return dev_err_probe(vi->dev, -ENODEV, "%pOF: missing 'ports' node\n", node);
for_each_child_of_node(ports, port) {
if (!of_node_name_eq(port, "port"))
@@ -1374,7 +1275,6 @@ static int tegra_vi_channels_alloc(struct tegra_vi *vi)
dev_err(vi->dev, "invalid port num %d for %pOF\n",
port_num, port);
ret = -EINVAL;
- of_node_put(port);
goto cleanup;
}
@@ -1397,13 +1297,12 @@ static int tegra_vi_channels_alloc(struct tegra_vi *vi)
lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes;
ret = tegra_vi_channel_alloc(vi, port_num, port, lanes);
- if (ret < 0) {
- of_node_put(port);
+ if (ret < 0)
goto cleanup;
- }
}
cleanup:
+ of_node_put(port);
of_node_put(ports);
return ret;
}
@@ -1858,10 +1757,10 @@ static int tegra_vi_graph_init(struct tegra_vi *vi)
* Walk the links to parse the full graph. Each channel will have
* one endpoint of the composite node. Start by parsing the
* composite node and parse the remote entities in turn.
- * Each channel will register v4l2 async notifier to make the graph
- * independent between the channels so we can the current channel
+ * Each channel will register a v4l2 async notifier to make the graph
+ * independent between the channels so we can skip the current channel
* in case of something wrong during graph parsing and continue with
- * next channels.
+ * the next channels.
*/
list_for_each_entry(chan, &vi->vi_chans, list) {
struct fwnode_handle *ep, *remote;
@@ -1920,11 +1819,8 @@ static int tegra_vi_init(struct host1x_client *client)
ret = tegra_vi_tpg_channels_alloc(vi);
else
ret = tegra_vi_channels_alloc(vi);
- if (ret < 0) {
- dev_err(vi->dev,
- "failed to allocate vi channels: %d\n", ret);
+ if (ret < 0)
goto free_chans;
- }
ret = tegra_vi_channels_init(vi);
if (ret < 0)
@@ -2026,6 +1922,9 @@ static int tegra_vi_probe(struct platform_device *pdev)
vi->client.ops = &vi_client_ops;
vi->client.dev = &pdev->dev;
+ if (vi->ops->vi_enable)
+ vi->ops->vi_enable(vi, true);
+
ret = host1x_client_register(&vi->client);
if (ret < 0) {
dev_err(&pdev->dev,
@@ -2036,6 +1935,8 @@ static int tegra_vi_probe(struct platform_device *pdev)
return 0;
rpm_disable:
+ if (vi->ops->vi_enable)
+ vi->ops->vi_enable(vi, false);
pm_runtime_disable(&pdev->dev);
return ret;
}
@@ -2046,12 +1947,17 @@ static int tegra_vi_remove(struct platform_device *pdev)
host1x_client_unregister(&vi->client);
+ if (vi->ops->vi_enable)
+ vi->ops->vi_enable(vi, false);
pm_runtime_disable(&pdev->dev);
return 0;
}
static const struct of_device_id tegra_vi_of_id_table[] = {
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ { .compatible = "nvidia,tegra20-vi", .data = &tegra20_vi_soc },
+#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
{ .compatible = "nvidia,tegra210-vi", .data = &tegra210_vi_soc },
#endif
diff --git a/drivers/staging/media/tegra-video/vi.h b/drivers/staging/media/tegra-video/vi.h
index a68e2c02c7b0..1e6a5caa7082 100644
--- a/drivers/staging/media/tegra-video/vi.h
+++ b/drivers/staging/media/tegra-video/vi.h
@@ -25,17 +25,11 @@
#define V4L2_CID_TEGRA_SYNCPT_TIMEOUT_RETRY (V4L2_CTRL_CLASS_CAMERA | 0x1001)
-#define TEGRA_MIN_WIDTH 32U
-#define TEGRA_MAX_WIDTH 32768U
-#define TEGRA_MIN_HEIGHT 32U
-#define TEGRA_MAX_HEIGHT 32768U
-
#define TEGRA_DEF_WIDTH 1920
#define TEGRA_DEF_HEIGHT 1080
#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,
@@ -43,8 +37,17 @@ enum tegra_vi_pg_mode {
TEGRA_VI_PG_PATCH,
};
+struct tegra_vi;
+struct tegra_vi_channel;
+
/**
* struct tegra_vi_ops - Tegra VI operations
+ * @vi_enable: soc-specific operations needed to enable/disable the VI peripheral
+ * @channel_host1x_syncpt_init: initialize synchronization points
+ * @channel_host1x_syncpt_free: free all synchronization points
+ * @vi_fmt_align: modify `pix` to fit the hardware alignment
+ * requirements and fill image geometry
+ * @channel_queue_setup: additional operations at the end of vb2_ops::queue_setup
* @vi_start_streaming: starts media pipeline, subdevice streaming, sets up
* VI for capture and runs capture start and capture finish
* kthreads for capturing frames to buffer and returns them back.
@@ -52,6 +55,11 @@ enum tegra_vi_pg_mode {
* back any queued buffers.
*/
struct tegra_vi_ops {
+ int (*vi_enable)(struct tegra_vi *vi, bool on);
+ int (*channel_host1x_syncpt_init)(struct tegra_vi_channel *chan);
+ void (*channel_host1x_syncpt_free)(struct tegra_vi_channel *chan);
+ void (*vi_fmt_align)(struct v4l2_pix_format *pix, unsigned int bpp);
+ void (*channel_queue_setup)(struct tegra_vi_channel *chan);
int (*vi_start_streaming)(struct vb2_queue *vq, u32 count);
void (*vi_stop_streaming)(struct vb2_queue *vq);
};
@@ -61,18 +69,22 @@ struct tegra_vi_ops {
*
* @video_formats: supported video formats
* @nformats: total video formats
+ * @default_video_format: default video format (pointer to a @video_formats item)
* @ops: vi operations
* @hw_revision: VI hw_revision
* @vi_max_channels: supported max streaming channels
* @vi_max_clk_hz: VI clock max frequency
+ * @has_h_v_flip: the chip can do H and V flip, and the driver implements it
*/
struct tegra_vi_soc {
const struct tegra_video_format *video_formats;
const unsigned int nformats;
+ const struct tegra_video_format *default_video_format;
const struct tegra_vi_ops *ops;
u32 hw_revision;
unsigned int vi_max_channels;
unsigned int vi_max_clk_hz;
+ bool has_h_v_flip:1;
};
/**
@@ -99,19 +111,6 @@ struct tegra_vi {
};
/**
- * struct tegra_vi_graph_entity - Entity in the video graph
- *
- * @asd: subdev asynchronous registration information
- * @entity: media entity from the corresponding V4L2 subdev
- * @subdev: V4L2 subdev
- */
-struct tegra_vi_graph_entity {
- struct v4l2_async_subdev asd;
- struct media_entity *entity;
- struct v4l2_subdev *subdev;
-};
-
-/**
* struct tegra_vi_channel - Tegra video channel
*
* @list: list head for this entry
@@ -122,11 +121,13 @@ struct tegra_vi_graph_entity {
* @vi: Tegra video input device structure
* @frame_start_sp: host1x syncpoint pointer to synchronize programmed capture
* start condition with hardware frame start events through host1x
- * syncpoint counters.
+ * syncpoint counters. (Tegra210)
* @mw_ack_sp: host1x syncpoint pointer to synchronize programmed memory write
* ack trigger condition with hardware memory write done at end of
- * frame through host1x syncpoint counters.
+ * frame through host1x syncpoint counters (On Tegra20 used for the
+ * OUT_1 syncpt)
* @sp_incr_lock: protects cpu syncpoint increment.
+ * @next_out_sp_idx: next expected value for mw_ack_sp[0], i.e. OUT_1 (Tegra20)
*
* @kthread_start_capture: kthread to start capture of single frame when
* vb buffer is available. This thread programs VI CSI hardware
@@ -151,6 +152,12 @@ struct tegra_vi_graph_entity {
* @queue: vb2 buffers queue
* @sequence: V4L2 buffers sequence number
*
+ * @addr_offset_u: U plane base address, relative to buffer base address (only for planar)
+ * @addr_offset_v: V plane base address, relative to buffer base address (only for planar)
+ * @start_offset: 1st Y byte to write, relative to buffer base address (for H/V flip)
+ * @start_offset_u: 1st U byte to write, relative to buffer base address (for H/V flip)
+ * @start_offset_v: 1st V byte to write, relative to buffer base address (for H/V flip)
+ *
* @capture: list of queued buffers for capture
* @start_lock: protects the capture queued list
* @done: list of capture done queued buffers
@@ -167,6 +174,9 @@ struct tegra_vi_graph_entity {
* @tpg_fmts_bitmap: a bitmap for supported TPG formats
* @pg_mode: test pattern generator mode (disabled/direct/patch)
* @notifier: V4L2 asynchronous subdevs notifier
+ *
+ * @hflip: Horizontal flip is enabled
+ * @vflip: Vertical flip is enabled
*/
struct tegra_vi_channel {
struct list_head list;
@@ -180,6 +190,7 @@ struct tegra_vi_channel {
struct host1x_syncpt *mw_ack_sp[GANG_PORTS_MAX];
/* protects the cpu syncpoint increment */
spinlock_t sp_incr_lock[GANG_PORTS_MAX];
+ u32 next_out_sp_idx;
struct task_struct *kthread_start_capture;
wait_queue_head_t start_wait;
@@ -191,6 +202,12 @@ struct tegra_vi_channel {
struct vb2_queue queue;
u32 sequence;
+ unsigned int addr_offset_u;
+ unsigned int addr_offset_v;
+ unsigned int start_offset;
+ unsigned int start_offset_u;
+ unsigned int start_offset_v;
+
struct list_head capture;
/* protects the capture queued list */
spinlock_t start_lock;
@@ -210,6 +227,9 @@ struct tegra_vi_channel {
enum tegra_vi_pg_mode pg_mode;
struct v4l2_async_notifier notifier;
+
+ bool hflip:1;
+ bool vflip:1;
};
/**
@@ -260,11 +280,11 @@ enum tegra_image_dt {
/**
* struct tegra_video_format - Tegra video format description
*
- * @img_dt: image data type
- * @bit_width: format width in bits per component
+ * @img_dt: MIPI CSI-2 data type (for CSI-2 only)
+ * @bit_width: format width in bits per component (for CSI/Tegra210 only)
* @code: media bus format code
* @bpp: bytes per pixel (when stored in memory)
- * @img_fmt: image format
+ * @img_fmt: image format (for CSI/Tegra210 only)
* @fourcc: V4L2 pixel format FCC identifier
*/
struct tegra_video_format {
@@ -276,6 +296,9 @@ struct tegra_video_format {
u32 fourcc;
};
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+extern const struct tegra_vi_soc tegra20_vi_soc;
+#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
extern const struct tegra_vi_soc tegra210_vi_soc;
#endif
diff --git a/drivers/staging/media/tegra-video/video.c b/drivers/staging/media/tegra-video/video.c
index d966b319553f..074ad0dc56ca 100644
--- a/drivers/staging/media/tegra-video/video.c
+++ b/drivers/staging/media/tegra-video/video.c
@@ -123,6 +123,10 @@ static int host1x_video_remove(struct host1x_device *dev)
}
static const struct of_device_id host1x_video_subdevs[] = {
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ { .compatible = "nvidia,tegra20-vip", },
+ { .compatible = "nvidia,tegra20-vi", },
+#endif
#if defined(CONFIG_ARCH_TEGRA_210_SOC)
{ .compatible = "nvidia,tegra210-csi", },
{ .compatible = "nvidia,tegra210-vi", },
@@ -141,6 +145,7 @@ static struct host1x_driver host1x_video_driver = {
static struct platform_driver * const drivers[] = {
&tegra_csi_driver,
+ &tegra_vip_driver,
&tegra_vi_driver,
};
diff --git a/drivers/staging/media/tegra-video/video.h b/drivers/staging/media/tegra-video/video.h
index fadaf2189dc9..7275affa6558 100644
--- a/drivers/staging/media/tegra-video/video.h
+++ b/drivers/staging/media/tegra-video/video.h
@@ -12,7 +12,6 @@
#include <media/v4l2-device.h>
#include "vi.h"
-#include "csi.h"
struct tegra_video_device {
struct v4l2_device v4l2_dev;
@@ -25,5 +24,6 @@ int tegra_v4l2_nodes_setup_tpg(struct tegra_video_device *vid);
void tegra_v4l2_nodes_cleanup_tpg(struct tegra_video_device *vid);
extern struct platform_driver tegra_vi_driver;
+extern struct platform_driver tegra_vip_driver;
extern struct platform_driver tegra_csi_driver;
#endif
diff --git a/drivers/staging/media/tegra-video/vip.c b/drivers/staging/media/tegra-video/vip.c
new file mode 100644
index 000000000000..a1ab886acc18
--- /dev/null
+++ b/drivers/staging/media/tegra-video/vip.c
@@ -0,0 +1,287 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Parallel video capture module (VIP) for the Tegra VI.
+ *
+ * This file implements the VIP-specific infrastructure.
+ *
+ * Copyright (C) 2023 SKIDATA GmbH
+ * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
+ */
+
+#include <linux/device.h>
+#include <linux/host1x.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+
+#include <media/v4l2-fwnode.h>
+
+#include "vip.h"
+
+static inline struct tegra_vip *host1x_client_to_vip(struct host1x_client *client)
+{
+ return container_of(client, struct tegra_vip, client);
+}
+
+static inline struct tegra_vip_channel *subdev_to_vip_channel(struct v4l2_subdev *subdev)
+{
+ return container_of(subdev, struct tegra_vip_channel, subdev);
+}
+
+static inline struct tegra_vip *vip_channel_to_vip(struct tegra_vip_channel *chan)
+{
+ return container_of(chan, struct tegra_vip, chan);
+}
+
+/* Find the previous subdev in the pipeline (i.e. the one connected to our sink pad) */
+static struct v4l2_subdev *tegra_vip_channel_get_prev_subdev(struct tegra_vip_channel *chan)
+{
+ struct media_pad *remote_pad;
+
+ remote_pad = media_pad_remote_pad_first(&chan->pads[TEGRA_VIP_PAD_SINK]);
+ if (!remote_pad)
+ return NULL;
+
+ return media_entity_to_v4l2_subdev(remote_pad->entity);
+}
+
+static int tegra_vip_enable_stream(struct v4l2_subdev *subdev)
+{
+ struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev);
+ struct tegra_vip *vip = vip_channel_to_vip(vip_chan);
+ struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(vip_chan);
+ int err;
+
+ err = pm_runtime_resume_and_get(vip->dev);
+ if (err)
+ return dev_err_probe(vip->dev, err, "failed to get runtime PM\n");
+
+ err = vip->soc->ops->vip_start_streaming(vip_chan);
+ if (err < 0)
+ goto err_start_streaming;
+
+ err = v4l2_subdev_call(prev_subdev, video, s_stream, true);
+ if (err < 0 && err != -ENOIOCTLCMD)
+ goto err_prev_subdev_start_stream;
+
+ return 0;
+
+err_prev_subdev_start_stream:
+err_start_streaming:
+ pm_runtime_put(vip->dev);
+ return err;
+}
+
+static int tegra_vip_disable_stream(struct v4l2_subdev *subdev)
+{
+ struct tegra_vip_channel *vip_chan = subdev_to_vip_channel(subdev);
+ struct tegra_vip *vip = vip_channel_to_vip(vip_chan);
+ struct v4l2_subdev *prev_subdev = tegra_vip_channel_get_prev_subdev(vip_chan);
+
+ v4l2_subdev_call(prev_subdev, video, s_stream, false);
+
+ pm_runtime_put(vip->dev);
+
+ return 0;
+}
+
+static int tegra_vip_s_stream(struct v4l2_subdev *subdev, int enable)
+{
+ int err;
+
+ if (enable)
+ err = tegra_vip_enable_stream(subdev);
+ else
+ err = tegra_vip_disable_stream(subdev);
+
+ return err;
+}
+
+static const struct v4l2_subdev_video_ops tegra_vip_video_ops = {
+ .s_stream = tegra_vip_s_stream,
+};
+
+static const struct v4l2_subdev_ops tegra_vip_ops = {
+ .video = &tegra_vip_video_ops,
+};
+
+static int tegra_vip_channel_of_parse(struct tegra_vip *vip)
+{
+ struct device *dev = vip->dev;
+ struct device_node *np = dev->of_node;
+ struct v4l2_fwnode_endpoint v4l2_ep = {
+ .bus_type = V4L2_MBUS_PARALLEL
+ };
+ struct fwnode_handle *fwh;
+ struct device_node *ep;
+ unsigned int num_pads;
+ int err;
+
+ dev_dbg(dev, "Parsing %pOF", np);
+
+ ep = of_graph_get_endpoint_by_regs(np, 0, 0);
+ if (!ep) {
+ err = -EINVAL;
+ dev_err_probe(dev, err, "%pOF: error getting endpoint node\n", np);
+ goto err_node_put;
+ }
+
+ fwh = of_fwnode_handle(ep);
+ err = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep);
+ of_node_put(ep);
+ if (err) {
+ dev_err_probe(dev, err, "%pOF: failed to parse v4l2 endpoint\n", np);
+ goto err_node_put;
+ }
+
+ num_pads = of_graph_get_endpoint_count(np);
+ if (num_pads != TEGRA_VIP_PADS_NUM) {
+ err = -EINVAL;
+ dev_err_probe(dev, err, "%pOF: need 2 pads, got %d\n", np, num_pads);
+ goto err_node_put;
+ }
+
+ vip->chan.of_node = of_node_get(np);
+ vip->chan.pads[TEGRA_VIP_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ vip->chan.pads[TEGRA_VIP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ return 0;
+
+err_node_put:
+ of_node_put(np);
+ return err;
+}
+
+static int tegra_vip_channel_init(struct tegra_vip *vip)
+{
+ struct v4l2_subdev *subdev;
+ int err;
+
+ subdev = &vip->chan.subdev;
+ v4l2_subdev_init(subdev, &tegra_vip_ops);
+ subdev->dev = vip->dev;
+ snprintf(subdev->name, V4L2_SUBDEV_NAME_SIZE, "%s",
+ kbasename(vip->chan.of_node->full_name));
+
+ v4l2_set_subdevdata(subdev, &vip->chan);
+ subdev->fwnode = of_fwnode_handle(vip->chan.of_node);
+ subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
+
+ err = media_entity_pads_init(&subdev->entity, TEGRA_VIP_PADS_NUM, vip->chan.pads);
+ if (err)
+ return dev_err_probe(vip->dev, err, "failed to initialize media entity\n");
+
+ err = v4l2_async_register_subdev(subdev);
+ if (err) {
+ dev_err_probe(vip->dev, err, "failed to register subdev\n");
+ goto err_register_subdev;
+ }
+
+ return 0;
+
+err_register_subdev:
+ media_entity_cleanup(&subdev->entity);
+ return err;
+}
+
+static int tegra_vip_init(struct host1x_client *client)
+{
+ struct tegra_vip *vip = host1x_client_to_vip(client);
+ int err;
+
+ err = tegra_vip_channel_of_parse(vip);
+ if (err)
+ return err;
+
+ err = tegra_vip_channel_init(vip);
+ if (err)
+ goto err_init;
+
+ return 0;
+
+err_init:
+ of_node_put(vip->chan.of_node);
+ return err;
+}
+
+static int tegra_vip_exit(struct host1x_client *client)
+{
+ struct tegra_vip *vip = host1x_client_to_vip(client);
+ struct v4l2_subdev *subdev = &vip->chan.subdev;
+
+ v4l2_async_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+ of_node_put(vip->chan.of_node);
+
+ return 0;
+}
+
+static const struct host1x_client_ops vip_client_ops = {
+ .init = tegra_vip_init,
+ .exit = tegra_vip_exit,
+};
+
+static int tegra_vip_probe(struct platform_device *pdev)
+{
+ struct tegra_vip *vip;
+ int err;
+
+ dev_dbg(&pdev->dev, "Probing VIP \"%s\" from %pOF\n", pdev->name, pdev->dev.of_node);
+
+ vip = devm_kzalloc(&pdev->dev, sizeof(*vip), GFP_KERNEL);
+ if (!vip)
+ return -ENOMEM;
+
+ vip->soc = of_device_get_match_data(&pdev->dev);
+
+ vip->dev = &pdev->dev;
+ platform_set_drvdata(pdev, vip);
+
+ /* initialize host1x interface */
+ INIT_LIST_HEAD(&vip->client.list);
+ vip->client.ops = &vip_client_ops;
+ vip->client.dev = &pdev->dev;
+
+ err = host1x_client_register(&vip->client);
+ if (err)
+ return dev_err_probe(&pdev->dev, err, "failed to register host1x client\n");
+
+ pm_runtime_enable(&pdev->dev);
+
+ return 0;
+}
+
+static int tegra_vip_remove(struct platform_device *pdev)
+{
+ struct tegra_vip *vip = platform_get_drvdata(pdev);
+
+ host1x_client_unregister(&vip->client);
+
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+extern const struct tegra_vip_soc tegra20_vip_soc;
+#endif
+
+static const struct of_device_id tegra_vip_of_id_table[] = {
+#if defined(CONFIG_ARCH_TEGRA_2x_SOC)
+ { .compatible = "nvidia,tegra20-vip", .data = &tegra20_vip_soc },
+#endif
+ { }
+};
+MODULE_DEVICE_TABLE(of, tegra_vip_of_id_table);
+
+struct platform_driver tegra_vip_driver = {
+ .driver = {
+ .name = "tegra-vip",
+ .of_match_table = tegra_vip_of_id_table,
+ },
+ .probe = tegra_vip_probe,
+ .remove = tegra_vip_remove,
+};
diff --git a/drivers/staging/media/tegra-video/vip.h b/drivers/staging/media/tegra-video/vip.h
new file mode 100644
index 000000000000..32ceaaccbba2
--- /dev/null
+++ b/drivers/staging/media/tegra-video/vip.h
@@ -0,0 +1,68 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2023 SKIDATA GmbH
+ * Author: Luca Ceresoli <luca.ceresoli@bootlin.com>
+ */
+
+#ifndef __TEGRA_VIP_H__
+#define __TEGRA_VIP_H__
+
+#include <media/media-entity.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-subdev.h>
+
+enum {
+ TEGRA_VIP_PAD_SINK,
+ TEGRA_VIP_PAD_SOURCE,
+ TEGRA_VIP_PADS_NUM,
+};
+
+struct tegra_vip;
+
+/**
+ * struct tegra_vip_channel - Tegra VIP (parallel video capture) channel
+ *
+ * @subdev: V4L2 subdevice associated with this channel
+ * @pads: media pads for the subdevice entity
+ * @of_node: vip device tree node
+ */
+struct tegra_vip_channel {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[TEGRA_VIP_PADS_NUM];
+ struct device_node *of_node;
+};
+
+/**
+ * struct tegra_vip_ops - Tegra VIP operations
+ *
+ * @vip_start_streaming: programs vip hardware to enable streaming.
+ */
+struct tegra_vip_ops {
+ int (*vip_start_streaming)(struct tegra_vip_channel *vip_chan);
+};
+
+/**
+ * struct tegra_vip_soc - NVIDIA Tegra VIP SoC structure
+ *
+ * @ops: vip hardware operations
+ */
+struct tegra_vip_soc {
+ const struct tegra_vip_ops *ops;
+};
+
+/**
+ * struct tegra_vip - NVIDIA Tegra VIP device structure
+ *
+ * @dev: device struct
+ * @client: host1x_client struct
+ * @soc: pointer to SoC data structure
+ * @chan: the VIP channel
+ */
+struct tegra_vip {
+ struct device *dev;
+ struct host1x_client client;
+ const struct tegra_vip_soc *soc;
+ struct tegra_vip_channel chan;
+};
+
+#endif