// SPDX-License-Identifier: GPL-2.0-only
/* SPDX-FileCopyrightText: Copyright (c) 2017-2025 NVIDIA CORPORATION & AFFILIATES.
 * All rights reserved.
 *
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 */

/**
 * @file drivers/media/platform/tegra/camera/fusa-capture/capture-vi.c
 *
 * @brief VI channel operations for the T234 Camera RTCPU platform.
 */

#include <nvidia/conftest.h>

#include <linux/completion.h>
#include <linux/nospec.h>
#include <linux/nvhost.h>
#include <linux/of_platform.h>
#include <linux/printk.h>
#include <linux/vmalloc.h>
#include <linux/tegra-capture-ivc.h>
#include <linux/tegra-camera-rtcpu.h>

#include <asm/arch_timer.h>
#include <uapi/linux/nvhost_events.h>
#include <camera/nvcamera_log.h>
#include "soc/tegra/camrtc-capture.h"
#include "soc/tegra/camrtc-capture-messages.h"
#include <media/fusa-capture/capture-vi-channel.h>
#include <media/fusa-capture/capture-common.h>

#include <media/fusa-capture/capture-vi.h>
#include <media/vi.h>
#include <media/mc_common.h>
#include <media/tegra_camera_platform.h>
#include "camera/vi/vi5_fops.h"

/**
 * @brief Invalid VI channel ID; the channel is not initialized.
 */
#define CAPTURE_CHANNEL_INVALID_ID	U16_C(0xFFFF)

/**
 * @brief Invalid VI channel mask; no channels are allocated.
 */
#define CAPTURE_CHANNEL_INVALID_MASK	U64_C(0x0)

/**
 * @brief Invalid NVCSI stream ID; the stream is not initialized.
 */
#define NVCSI_STREAM_INVALID_ID		U32_C(0xFFFF)

/**
 * @brief INVALID NVCSI TPG virtual channel ID; the TPG stream is not enabled.
 */
#define NVCSI_STREAM_INVALID_TPG_VC_ID	U32_C(0xFFFF)

/**
 * @brief The default number of VI channels to be used if not specified in
 * the device tree.
 */
#define DEFAULT_VI_CHANNELS	U32_C(64)

/**
 * @brief Maximum number of VI channels supported by KMD in total
 */
#define NUM_VI_CHANNELS	U32_C(72)

/**
 * @brief Maximum number of VI devices supported.
 */
#define MAX_VI_UNITS	U32_C(0x2)

/**
 * @brief Invalid VI unit ID, to initialize vi-mapping table before parsing DT.
 */
#define INVALID_VI_UNIT_ID  U32_C(0xFFFF)

/**
 * @brief Maximum number of NVCSI streams supported.
 */
#define MAX_NVCSI_STREAM_IDS  U32_C(0x6)

/**
 * @brief Maximum number of virtual channel supported per stream.
 */
#define MAX_VIRTUAL_CHANNEL_PER_STREAM  U32_C(16)

/**
 * @brief A 2-D array for storing all possible tegra_vi_channel struct pointers.
 */
static struct tegra_vi_channel *channels[MAX_NVCSI_STREAM_IDS][MAX_VIRTUAL_CHANNEL_PER_STREAM];
/**
 * @brief Names of VI-unit and CSI-stream mapping elements in device-tree node
 */
static const char * const vi_mapping_elements[] = {
	"csi-stream-id",
	"vi-unit-id"
};

/**
 * @brief The Capture-VI standalone driver context.
 */
struct tegra_capture_vi_data {
	struct vi vi_common; /**< VI device context */
	uint32_t num_vi_devices; /**< Number of available VI devices */
	struct platform_device *vi_pdevices[MAX_VI_UNITS];
		/**< VI nvhost client platform device for each VI instance */
	uint32_t max_vi_channels;
		/**< Maximum number of VI capture channel devices */
	uint32_t num_csi_vi_maps;
		/**< Number of NVCSI to VI mapping elements in the table */
	uint32_t vi_instance_table[MAX_NVCSI_STREAM_IDS];
		/**< NVCSI stream-id & VI instance mapping, read from the DT */
};

/**
 * @brief Initialize a VI syncpoint and get its GoS backing.
 *
 * @param[in]	chan	VI channel context
 * @param[in]	name	Syncpoint name
 * @param[in]	enable	Whether to initialize or just clear @a sp
 * @param[out]	sp	Syncpoint handle
 *
 * @returns	0 (success), neg. errno (failure)
 */
static int vi_capture_setup_syncpt(
	struct tegra_vi_channel *chan,
	const char *name,
	bool enable,
	struct syncpoint_info *sp)
{
	struct platform_device *pdev = chan->ndev;
	uint32_t gos_index, gos_offset;
	int err;

	memset(sp, 0, sizeof(*sp));
	sp->gos_index = GOS_INDEX_INVALID;

	if (!enable)
		return 0;

	err = chan->ops->alloc_syncpt(pdev, name, &sp->id);
	if (err)
		return err;

	err = nvhost_syncpt_read_ext_check(pdev, sp->id, &sp->threshold);
	if (err)
		goto cleanup;

	err = chan->ops->get_syncpt_gos_backing(pdev, sp->id, &sp->shim_addr,
				&gos_index, &gos_offset);
	if (err)
		goto cleanup;

	sp->gos_index = gos_index;
	sp->gos_offset = gos_offset;

	return 0;

cleanup:
	chan->ops->release_syncpt(pdev, sp->id);
	memset(sp, 0, sizeof(*sp));

	return err;
}

/**
 * @brief Fast forward a VI syncpoint to its threshold value.
 *
 * @param[in]	chan	VI channel context
 */
static void vi_capture_fastforward_syncpt(
	struct tegra_vi_channel *chan)
{
	struct vi_capture *capture = chan->capture_data;

	if (capture->progress_sp.id)
		chan->ops->fast_forward_syncpt(chan->ndev,
						capture->progress_sp.id,
						capture->progress_sp.threshold);
}

/**
 * @brief Release a VI syncpoint and clear its handle.
 *
 * @param[in]	chan	VI channel context
 * @param[out]	sp	Syncpoint handle
 */
static void vi_capture_release_syncpt(
	struct tegra_vi_channel *chan,
	struct syncpoint_info *sp)
{
	if (sp->id)
		chan->ops->release_syncpt(chan->ndev, sp->id);

	memset(sp, 0, sizeof(*sp));
}

/**
 * @brief Release the VI channel progress, embedded data and line timer
 * syncpoints.
 *
 * @param[in]	chan	VI channel context
 */
static void vi_capture_release_syncpts(
	struct tegra_vi_channel *chan)
{
	struct vi_capture *capture = chan->capture_data;

	vi_capture_release_syncpt(chan, &capture->progress_sp);
	vi_capture_release_syncpt(chan, &capture->embdata_sp);
	vi_capture_release_syncpt(chan, &capture->linetimer_sp);
}

/**
 * @brief Set up the VI channel progress, embedded data and line timer
 * syncpoints.
 *
 * @param[in]	chan	VI channel context
 * @param[in]	flags	Bitmask for channel flags, see
 *			@ref CAPTURE_CHANNEL_FLAGS
 *
 * @returns	0 (success), neg. errno (failure)
 */
static int vi_capture_setup_syncpts(
	struct tegra_vi_channel *chan,
	uint32_t flags)
{
	struct vi_capture *capture = chan->capture_data;
	int err = 0;

	chan->ops->get_gos_table(chan->ndev,
				&capture->num_gos_tables,
				&capture->gos_tables);

	err = vi_capture_setup_syncpt(chan, "progress", true,
			&capture->progress_sp);
	if (err < 0)
		goto fail;

	err = vi_capture_setup_syncpt(chan, "embdata",
				(flags & CAPTURE_CHANNEL_FLAG_EMBDATA) != 0,
				&capture->embdata_sp);
	if (err < 0)
		goto fail;

	err = vi_capture_setup_syncpt(chan, "linetimer",
				(flags & CAPTURE_CHANNEL_FLAG_LINETIMER) != 0,
				&capture->linetimer_sp);
	if (err < 0)
		goto fail;

	return 0;

fail:
	vi_capture_release_syncpts(chan);
	return err;
}

/**
 * @brief Read the value of a VI channel syncpoint.
 *
 * @param[in]	chan	VI channel context
 * @param[in]	sp	Syncpoint handle
 * @param[out]	val	Syncpoint value
 *
 * @returns	0 (success), neg. errno (failure)
 */
static int vi_capture_read_syncpt(
	struct tegra_vi_channel *chan,
	struct syncpoint_info *sp,
	uint32_t *val)
{
	int err;

	if (sp->id) {
		err = nvhost_syncpt_read_ext_check(chan->ndev,
						sp->id, val);
		if (err < 0) {
			dev_err(chan->dev,
				"%s: get syncpt %i val failed\n", __func__,
				sp->id);
			return -EINVAL;
		}
	}

	return 0;
}

/**
 * @brief VI channel callback function for @em capture IVC messages.
 *
 * @param[in]	ivc_resp	IVC @ref CAPTURE_MSG from RCE
 * @param[in]	pcontext	VI channel capture context
 */
static void vi_capture_ivc_status_callback(
	const void *ivc_resp,
	const void *pcontext)
{
	struct CAPTURE_MSG *status_msg = (struct CAPTURE_MSG *)ivc_resp;
	struct vi_capture *capture = (struct vi_capture *)pcontext;
	struct tegra_vi_channel *chan = capture->vi_channel;
	uint32_t buffer_index;

	if (unlikely(capture == NULL)) {
		dev_err(chan->dev, "%s: invalid context", __func__);
		return;
	}

	if (unlikely(status_msg == NULL)) {
		dev_err(chan->dev, "%s: invalid response", __func__);
		return;
	}

	switch (status_msg->header.msg_id) {
	case CAPTURE_STATUS_IND:
		buffer_index = status_msg->capture_status_ind.buffer_index;
		if (capture->is_mem_pinned)
			vi_capture_request_unpin(chan, buffer_index);
		dma_sync_single_range_for_cpu(capture->rtcpu_dev,
			capture->requests.iova,
			buffer_index * capture->request_size,
			capture->request_size, DMA_FROM_DEVICE);

		if (capture->is_progress_status_notifier_set) {
			capture_common_set_progress_status(
					&capture->progress_status_notifier,
					buffer_index,
					capture->progress_status_buffer_depth,
					PROGRESS_STATUS_DONE);
		} else {
			/*
			 * Only fire completions if not using
			 * the new progress status buffer mechanism
			 */
			complete(&capture->capture_resp);
		}
		dev_dbg(chan->dev, "%s: status chan_id %u msg_id %u\n",
				__func__, status_msg->header.channel_id,
				status_msg->header.msg_id);
		break;
	default:
		dev_err(chan->dev,
			"%s: unknown capture resp", __func__);
		break;
	}
}

/**
 * @brief Send a @em capture-control IVC message to RCE on a VI channel, and
 * block w/ timeout, waiting for the RCE response.
 *
 * @param[in]	chan	VI channel context
 * @param[in]	msg	IVC message payload
 * @param[in]	size	Size of @a msg [byte]
 * @param[in]	resp_id	IVC message identifier, see @CAPTURE_MSG_IDS
 *
 * @returns	0 (success), neg. errno (failure)
 */
static int vi_capture_ivc_send_control(
	struct tegra_vi_channel *chan,
	const struct CAPTURE_CONTROL_MSG *msg,
	size_t size,
	uint32_t resp_id)
{
	struct vi_capture *capture = chan->capture_data;
	struct CAPTURE_MSG_HEADER resp_header = msg->header;
	uint32_t timeout = HZ;
	int err = 0;

	dev_dbg(chan->dev, "%s: sending chan_id %u msg_id %u\n",
			__func__, resp_header.channel_id, resp_header.msg_id);
	resp_header.msg_id = resp_id;
	/* Send capture control IVC message */
	mutex_lock(&capture->control_msg_lock);
	err = tegra_capture_ivc_control_submit(msg, size);
	if (err < 0) {
		dev_err(chan->dev, "IVC control submit failed\n");
		goto fail;
	}

	timeout = wait_for_completion_timeout(
			&capture->control_resp, timeout);
	if (timeout <= 0) {
		dev_err(chan->dev,
			"capture control message timed out\n");
		err = -ETIMEDOUT;
		goto fail;
	}

	if (memcmp(&resp_header, &capture->control_resp_msg.header,
			sizeof(resp_header)) != 0) {
		dev_err(chan->dev,
			"unexpected response from camera processor\n");
		err = -EINVAL;
		goto fail;
	}

	mutex_unlock(&capture->control_msg_lock);
	dev_dbg(chan->dev, "%s: response chan_id %u msg_id %u\n",
			__func__, capture->control_resp_msg.header.channel_id,
			capture->control_resp_msg.header.msg_id);
	return 0;

fail:
	mutex_unlock(&capture->control_msg_lock);
	return err;
}

/**
 * @brief VI channel callback function for @em capture-control IVC messages,
 * this unblocks the channel's @em capture-control completion.
 *
 * @param[in]	ivc_resp	IVC @ref CAPTURE_CONTROL_MSG from RCE
 * @param[in]	pcontext	VI channel capture context
 */
static void vi_capture_ivc_control_callback(
	const void *ivc_resp,
	const void *pcontext)
{
	const struct CAPTURE_CONTROL_MSG *control_msg = ivc_resp;
	struct vi_capture *capture = (struct vi_capture *)pcontext;
	struct tegra_vi_channel *chan = capture->vi_channel;

	if (unlikely(capture == NULL)) {
		dev_err(chan->dev, "%s: invalid context", __func__);
		return;
	}

	if (unlikely(control_msg == NULL)) {
		dev_err(chan->dev, "%s: invalid response", __func__);
		return;
	}

	switch (control_msg->header.msg_id) {
	case CAPTURE_CHANNEL_SETUP_RESP:
	case CAPTURE_CHANNEL_RESET_RESP:
	case CAPTURE_CHANNEL_RELEASE_RESP:
	case CAPTURE_COMPAND_CONFIG_RESP:
	case CAPTURE_PDAF_CONFIG_RESP:
	case CAPTURE_SYNCGEN_ENABLE_RESP:
	case CAPTURE_SYNCGEN_DISABLE_RESP:
	case CAPTURE_PHY_STREAM_OPEN_RESP:
	case CAPTURE_PHY_STREAM_CLOSE_RESP:
	case CAPTURE_PHY_STREAM_DUMPREGS_RESP:
	case CAPTURE_CSI_STREAM_SET_CONFIG_RESP:
	case CAPTURE_CSI_STREAM_SET_PARAM_RESP:
	case CAPTURE_CSI_STREAM_TPG_SET_CONFIG_RESP:
	case CAPTURE_CSI_STREAM_TPG_START_RESP:
	case CAPTURE_CSI_STREAM_TPG_START_RATE_RESP:
	case CAPTURE_CSI_STREAM_TPG_APPLY_GAIN_RESP:
	case CAPTURE_CSI_STREAM_TPG_STOP_RESP:
	case CAPTURE_CHANNEL_EI_RESP:
	case CAPTURE_HSM_CHANSEL_ERROR_MASK_RESP:
		memcpy(&capture->control_resp_msg, control_msg,
				sizeof(*control_msg));
		complete(&capture->control_resp);
		break;
	default:
		dev_err(chan->dev,
			"%s: unknown capture control resp 0x%x", __func__,
			control_msg->header.msg_id);
		break;
	}
}

int vi_capture_init(
	struct tegra_vi_channel *chan,
	bool is_mem_pinned)
{
	struct vi_capture *capture;
	struct device_node *dn;
	struct platform_device *rtc_pdev;
	struct device *dev;

	dev = &chan->vi_capture_pdev->dev;

	dev_dbg(dev, "%s++\n", __func__);
	dn = of_find_node_by_path("tegra-camera-rtcpu");
	if (of_device_is_available(dn) == 0) {
		dev_err(dev, "failed to find rtcpu device node\n");
		return -ENODEV;
	}
	rtc_pdev = of_find_device_by_node(dn);
	if (rtc_pdev == NULL) {
		dev_err(dev, "failed to find rtcpu platform\n");
		return -ENODEV;
	}

	capture = kzalloc(sizeof(*capture), GFP_KERNEL);
	if (unlikely(capture == NULL)) {
		dev_err(dev, "failed to allocate capture channel\n");
		return -ENOMEM;
	}

	capture->rtcpu_dev = &rtc_pdev->dev;

	init_completion(&capture->control_resp);
	init_completion(&capture->capture_resp);

	mutex_init(&capture->reset_lock);
	mutex_init(&capture->control_msg_lock);
	mutex_init(&capture->unpins_list_lock);

	capture->vi_channel = chan;
	chan->capture_data = capture;
	chan->rtcpu_dev = capture->rtcpu_dev;

	capture->is_mem_pinned = is_mem_pinned;
	capture->channel_id = CAPTURE_CHANNEL_INVALID_ID;

	capture->stream_id = NVCSI_STREAM_INVALID_ID;
	capture->csi_port = NVCSI_PORT_UNSPECIFIED;
	capture->virtual_channel_id = NVCSI_STREAM_INVALID_TPG_VC_ID;

	return 0;
}
EXPORT_SYMBOL_GPL(vi_capture_init);

void vi_capture_shutdown(
	struct tegra_vi_channel *chan)
{
	struct vi_capture *capture;

	if (unlikely(chan == NULL)) {
		pr_err("%s: vi channel pointer is NULL\n", __func__);
		return;
	}

	dev_dbg(chan->dev, "%s--\n", __func__);

	capture = chan->capture_data;
	if (unlikely(capture == NULL)) {
		dev_err(chan->dev, "%s: invalid context", __func__);
		return;
	}

	if (capture->channel_id != CAPTURE_CHANNEL_INVALID_ID)
		vi_capture_reset(chan,
			CAPTURE_CHANNEL_RESET_FLAG_IMMEDIATE);

	if (capture->stream_id != NVCSI_STREAM_INVALID_ID)
		csi_stream_release(chan);

	if (capture->channel_id != CAPTURE_CHANNEL_INVALID_ID)	{
		int i;

		vi_capture_release(chan,
			CAPTURE_CHANNEL_RESET_FLAG_IMMEDIATE);

		if (capture->is_mem_pinned) {
			for (i = 0; i < capture->queue_depth; i++)
				vi_capture_request_unpin(chan, i);
		}
		capture_common_unpin_memory(&capture->requests);
		if (capture->buf_ctx != NULL) {
			destroy_buffer_table(capture->buf_ctx);
			capture->buf_ctx = NULL;
		}

		vfree(capture->unpins_list);
		capture->unpins_list = NULL;
	}
	kfree(capture);
	chan->capture_data = NULL;
}
EXPORT_SYMBOL_GPL(vi_capture_shutdown);

void vi_get_nvhost_device(
	struct tegra_vi_channel *chan,
	struct vi_capture_setup *setup)
{
	uint32_t vi_inst = 0;

	struct tegra_capture_vi_data *info =
		platform_get_drvdata(chan->vi_capture_pdev);

	if (setup->csi_stream_id >= MAX_NVCSI_STREAM_IDS) {
		dev_err(chan->dev,
			"%s: csi stream id is out of bound\n", __func__);
		return;
	}

	vi_inst = info->vi_instance_table[setup->csi_stream_id];

	if (vi_inst >= MAX_VI_UNITS) {
		dev_err(&chan->vi_capture_pdev->dev, "Invalid VI device Id\n");
		chan->dev = NULL;
		chan->ndev = NULL;
		return;
	}
	vi_inst = array_index_nospec(vi_inst, MAX_VI_UNITS);

	chan->dev = &info->vi_pdevices[vi_inst]->dev;
	chan->ndev = info->vi_pdevices[vi_inst];
}
EXPORT_SYMBOL_GPL(vi_get_nvhost_device);

struct device *vi_csi_stream_to_nvhost_device(
	struct platform_device *pdev,
	uint32_t csi_stream_id)
{
	struct tegra_capture_vi_data *info = platform_get_drvdata(pdev);
	uint32_t vi_inst_id = 0;

	if (csi_stream_id >= MAX_NVCSI_STREAM_IDS) {
		dev_err(&pdev->dev, "Invalid NVCSI stream Id\n");
		return NULL;
	}

	vi_inst_id = info->vi_instance_table[csi_stream_id];
	return &info->vi_pdevices[vi_inst_id]->dev;
}
EXPORT_SYMBOL(vi_csi_stream_to_nvhost_device);

int vi_capture_setup(
	struct tegra_vi_channel *chan,
	struct vi_capture_setup *setup)
{
	struct vi_capture *capture = chan->capture_data;
	struct tegra_capture_vi_data *info;
	uint32_t transaction;
	struct CAPTURE_CONTROL_MSG control_desc;
	struct CAPTURE_CONTROL_MSG *resp_msg = &capture->control_resp_msg;
	struct capture_channel_config *config =
		&control_desc.channel_setup_req.channel_config;
	int err = 0;
#ifdef HAVE_VI_GOS_TABLES
	int i;
#endif

	uint32_t vi_inst = 0;
	struct device *dev;

	dev = &chan->vi_capture_pdev->dev;

	if (setup->csi_stream_id >= MAX_NVCSI_STREAM_IDS ||
		setup->virtual_channel_id >= MAX_VIRTUAL_CHANNEL_PER_STREAM) {
		dev_err(dev, "Invalid stream id or virtual channel id\n");
		return -EINVAL;
	}

	if (chan->vi_capture_pdev == NULL) {
		dev_err(dev,
			"%s: channel capture device is NULL", __func__);
		return -EINVAL;
	}

	info = platform_get_drvdata(chan->vi_capture_pdev);
	vi_inst = info->vi_instance_table[setup->csi_stream_id];

	/* V4L2 directly calls this function. So need to make sure the
	 * correct VI5 instance is associated with the VI capture channel.
	 */
	if (chan->dev == NULL) {
		vi_get_nvhost_device(chan, setup);
		if (chan->dev == NULL) {
			dev_err(&chan->vi_capture_pdev->dev,
				"%s: channel device is NULL", __func__);
			return -EINVAL;
		}
	}

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_SETUP);

	if (setup->mem == 0 && setup->iova == 0) {
		dev_err(chan->dev,
			"%s: request buffer is NULL\n", __func__);
		return -EINVAL;
	}

	if (capture == NULL) {
		dev_err(chan->dev,
			 "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	if (capture->channel_id != CAPTURE_CHANNEL_INVALID_ID) {
		dev_err(chan->dev,
			"%s: already setup, release first\n", __func__);
		return -EEXIST;
	}

	dev_dbg(chan->dev, "chan flags %u\n", setup->channel_flags);
	dev_dbg(chan->dev, "chan mask %llx\n", setup->vi_channel_mask);
	dev_dbg(chan->dev, "queue depth %u\n", setup->queue_depth);
	dev_dbg(chan->dev, "request size %u\n", setup->request_size);
	dev_dbg(chan->dev, "csi_stream_id %u\n", setup->csi_stream_id);
	dev_dbg(chan->dev, "vi unit id %u\n", vi_inst);
	dev_dbg(chan->dev, "vi2 chan mask %llx\n", setup->vi2_channel_mask);

	if (WARN_ON(vi_inst == VI_UNIT_VI &&
		setup->vi_channel_mask == CAPTURE_CHANNEL_INVALID_MASK) ||
		WARN_ON(vi_inst == VI_UNIT_VI2 &&
		setup->vi2_channel_mask == CAPTURE_CHANNEL_INVALID_MASK) ||
		WARN_ON(setup->channel_flags == 0) ||
		WARN_ON(setup->queue_depth == 0) ||
		WARN_ON(setup->request_size == 0) ||
		WARN_ON(setup->csi_stream_id == NVCSI_STREAM_INVALID_ID)) {

		dev_err(chan->dev, "%s: invalid setup parameters\n", __func__);
		return -EINVAL;
	}

	capture->queue_depth = setup->queue_depth;
	capture->request_size = setup->request_size;
	capture->request_buf_size = setup->request_size * setup->queue_depth;

	capture->stream_id = setup->csi_stream_id;
	capture->csi_port = setup->csi_port;
	capture->virtual_channel_id = setup->virtual_channel_id;

	err = vi_capture_setup_syncpts(chan, setup->channel_flags);
	if (err < 0) {
		dev_err(chan->dev, "failed to setup syncpts\n");
		goto syncpt_fail;
	}

	err = tegra_capture_ivc_register_control_cb(
			&vi_capture_ivc_control_callback,
			&transaction, capture);
	if (err < 0) {
		dev_err(chan->dev, "failed to register control callback\n");
		goto control_cb_fail;
	}

	memset(&control_desc, 0, sizeof(control_desc));
	control_desc.header.msg_id = CAPTURE_CHANNEL_SETUP_REQ;
	control_desc.header.transaction = transaction;

	/* Allocate memoryinfo ringbuffer */
	capture->requests_memoryinfo = dma_alloc_coherent(capture->rtcpu_dev,
		setup->queue_depth * sizeof(*capture->requests_memoryinfo),
		&capture->requests_memoryinfo_iova, GFP_KERNEL);

	if (!capture->requests_memoryinfo) {
		dev_err(chan->dev,
			"%s: memoryinfo ringbuffer alloc failed\n", __func__);
		goto memoryinfo_alloc_fail;
	}

	WARN_ON(capture->unpins_list != NULL);

	capture->unpins_list =
		vzalloc(setup->queue_depth * sizeof(*capture->unpins_list));

	if (!capture->unpins_list) {
		dev_err(chan->dev,
			"%s: channel_unpins alloc failed\n", __func__);
		goto unpin_alloc_fail;
	}

	config->requests_memoryinfo = capture->requests_memoryinfo_iova;
	config->request_memoryinfo_size =
			sizeof(struct capture_descriptor_memoryinfo);

	config->channel_flags = setup->channel_flags;
	config->vi_channel_mask = setup->vi_channel_mask;
	config->vi2_channel_mask = setup->vi2_channel_mask;
	config->slvsec_stream_main = setup->slvsec_stream_main;
	config->slvsec_stream_sub = setup->slvsec_stream_sub;

	config->vi_unit_id = vi_inst;

	config->csi_stream.stream_id = setup->csi_stream_id;
	config->csi_stream.csi_port = setup->csi_port;
	config->csi_stream.virtual_channel = setup->virtual_channel_id;

	config->queue_depth = setup->queue_depth;
	config->request_size = setup->request_size;
	config->requests = setup->iova;

	config->error_mask_correctable = setup->error_mask_correctable;
	config->error_mask_uncorrectable = setup->error_mask_uncorrectable;
	config->stop_on_error_notify_bits = setup->stop_on_error_notify_bits;

#ifdef HAVE_VI_GOS_TABLES
	dev_dbg(chan->dev, "%u GoS tables configured.\n",
		capture->num_gos_tables);
	for (i = 0; i < capture->num_gos_tables; i++) {
		config->vi_gos_tables[i] = (iova_t)capture->gos_tables[i];
		dev_dbg(chan->dev, "gos[%d] = 0x%08llx\n",
			i, (u64)capture->gos_tables[i]);
	}
	config->num_vi_gos_tables = capture->num_gos_tables;
#endif

	config->progress_sp = capture->progress_sp;
	config->embdata_sp = capture->embdata_sp;
	config->linetimer_sp = capture->linetimer_sp;

	err = vi_capture_ivc_send_control(chan, &control_desc,
			sizeof(control_desc), CAPTURE_CHANNEL_SETUP_RESP);
	if (err < 0)
		goto submit_fail;

	if (resp_msg->channel_setup_resp.result != CAPTURE_OK) {
		dev_err(chan->dev, "%s: control failed, errno %d", __func__,
			resp_msg->channel_setup_resp.result);
		err = -EINVAL;
		goto resp_fail;
	}

	capture->channel_id = resp_msg->channel_setup_resp.channel_id;

	if (vi_inst == VI_UNIT_VI)
		capture->vi_channel_mask =
				resp_msg->channel_setup_resp.vi_channel_mask;
	else if (vi_inst == VI_UNIT_VI2)
		capture->vi2_channel_mask =
				resp_msg->channel_setup_resp.vi_channel_mask;
	else {
		dev_err(chan->dev, "failed response for vi:%u\n", vi_inst);
		err = -EINVAL;
		goto resp_fail;
	}


	err = tegra_capture_ivc_notify_chan_id(capture->channel_id,
			transaction);
	if (err < 0) {
		dev_err(chan->dev, "failed to update control callback\n");
		goto cb_fail;
	}

	err = tegra_capture_ivc_register_capture_cb(
			&vi_capture_ivc_status_callback,
			capture->channel_id, capture);
	if (err < 0) {
		dev_err(chan->dev, "failed to register capture callback\n");
		goto cb_fail;
	}

	channels[setup->csi_stream_id][setup->virtual_channel_id] = chan;

	return 0;

cb_fail:
resp_fail:
submit_fail:
	vfree(capture->unpins_list);
	capture->unpins_list = NULL;
unpin_alloc_fail:
	/* Release memoryinfo ringbuffer */
	dma_free_coherent(capture->rtcpu_dev,
		capture->queue_depth *
		sizeof(struct capture_descriptor_memoryinfo),
		capture->requests_memoryinfo,
		capture->requests_memoryinfo_iova);
	capture->requests_memoryinfo = NULL;
memoryinfo_alloc_fail:
	tegra_capture_ivc_unregister_control_cb(transaction);
control_cb_fail:
	vi_capture_release_syncpts(chan);
syncpt_fail:
	return err;
}
EXPORT_SYMBOL_GPL(vi_capture_setup);

struct tegra_vi_channel *get_tegra_vi_channel(
	unsigned int stream_id,
	unsigned int virtual_channel_id)
{
	if (stream_id >= MAX_NVCSI_STREAM_IDS || virtual_channel_id >= MAX_VIRTUAL_CHANNEL_PER_STREAM)
		return NULL;

	return channels[stream_id][virtual_channel_id];
}

int vi_capture_reset(
	struct tegra_vi_channel *chan,
	uint32_t reset_flags)
{
	struct vi_capture *capture = chan->capture_data;
	struct CAPTURE_CONTROL_MSG control_desc;
#ifdef CAPTURE_RESET_BARRIER_IND
	struct CAPTURE_MSG capture_desc;
#endif
	struct CAPTURE_CONTROL_MSG *resp_msg = &capture->control_resp_msg;
	int err = 0;

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_RESET);

	if (capture == NULL) {
		dev_err(chan->dev,
			 "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	if (capture->channel_id == CAPTURE_CHANNEL_INVALID_ID) {
		dev_err(chan->dev,
			"%s: setup channel first\n", __func__);
		return -ENODEV;
	}

	mutex_lock(&capture->reset_lock);

#ifdef CAPTURE_RESET_BARRIER_IND
	memset(&capture_desc, 0, sizeof(capture_desc));
	capture_desc.header.msg_id = CAPTURE_RESET_BARRIER_IND;
	capture_desc.header.channel_id = capture->channel_id;
	err = tegra_capture_ivc_capture_submit(&capture_desc,
			sizeof(capture_desc));
	if (err < 0) {
		dev_err(chan->dev, "%s:IVC capture submit failed\n", __func__);
		goto submit_fail;
	}
#endif

	memset(&control_desc, 0, sizeof(control_desc));
	control_desc.header.msg_id = CAPTURE_CHANNEL_RESET_REQ;
	control_desc.header.channel_id = capture->channel_id;
	control_desc.channel_reset_req.reset_flags = reset_flags;

	err = vi_capture_ivc_send_control(chan, &control_desc,
			sizeof(control_desc), CAPTURE_CHANNEL_RESET_RESP);
	if (err < 0)
		goto submit_fail;

#ifdef CAPTURE_RESET_BARRIER_IND
	if (resp_msg->channel_reset_resp.result == CAPTURE_ERROR_TIMEOUT) {
		dev_dbg(chan->dev, "%s:reset timeout\n", __func__);
		err = -EAGAIN;
		goto submit_fail;
	}
#endif

	if (resp_msg->channel_reset_resp.result != CAPTURE_OK) {
		dev_err(chan->dev, "%s: control failed, errno %d", __func__,
			resp_msg->channel_reset_resp.result);
		err = -EINVAL;
	}

	vi_capture_fastforward_syncpt(chan);

submit_fail:
	mutex_unlock(&capture->reset_lock);
	return err;
}
EXPORT_SYMBOL_GPL(vi_capture_reset);

int vi_capture_release(
	struct tegra_vi_channel *chan,
	uint32_t reset_flags)
{
	struct vi_capture *capture;
	struct CAPTURE_CONTROL_MSG control_desc;
	struct CAPTURE_CONTROL_MSG *resp_msg;
	int err = 0;
	int ret = 0;
	int i = 0;

	if (unlikely(chan == NULL)) {
		pr_err("%s: vi channel pointer is NULL\n", __func__);
		return -ENODEV;
	}

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_RELEASE);

	capture = chan->capture_data;
	if (unlikely(capture == NULL)) {
		dev_err(chan->dev, "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	resp_msg = &capture->control_resp_msg;

	if (capture->channel_id == CAPTURE_CHANNEL_INVALID_ID) {
		dev_err(chan->dev,
			"%s: setup channel first\n", __func__);
		return -ENODEV;

	}

	memset(&control_desc, 0, sizeof(control_desc));
	control_desc.header.msg_id = CAPTURE_CHANNEL_RELEASE_REQ;
	control_desc.header.channel_id = capture->channel_id;
	control_desc.channel_release_req.reset_flags = reset_flags;

	err = vi_capture_ivc_send_control(chan, &control_desc,
			sizeof(control_desc), CAPTURE_CHANNEL_RELEASE_RESP);
	if (err < 0) {
		dev_err(chan->dev,
				"%s: release channel IVC failed\n", __func__);
		WARN_ON("RTCPU is in a bad state. Reboot to recover");

		tegra_camrtc_reboot(chan->rtcpu_dev);

		err = -EIO;
	} else if (resp_msg->channel_release_resp.result != CAPTURE_OK) {
		dev_err(chan->dev, "%s: control failed, errno %d", __func__,
			resp_msg->channel_release_resp.result);
		err = -EIO;
	}

	if (capture->requests_memoryinfo) {
		/* Release memoryinfo ringbuffer */
		dma_free_coherent(capture->rtcpu_dev,
			capture->queue_depth * sizeof(struct capture_descriptor_memoryinfo),
			capture->requests_memoryinfo, capture->requests_memoryinfo_iova);
		capture->requests_memoryinfo = NULL;
	}

	ret = tegra_capture_ivc_unregister_capture_cb(capture->channel_id);
	if (ret < 0 && err == 0) {
		dev_err(chan->dev,
			"failed to unregister capture callback\n");
		err = ret;
	}

	ret = tegra_capture_ivc_unregister_control_cb(capture->channel_id);
	if (ret < 0 && err == 0) {
		dev_err(chan->dev,
			"failed to unregister control callback\n");
		err = ret;
	}

	for (i = 0; i < capture->queue_depth; i++)
		complete(&capture->capture_resp);

	vi_capture_release_syncpts(chan);

	if (capture->stream_id < MAX_NVCSI_STREAM_IDS &&
	    capture->virtual_channel_id < MAX_VIRTUAL_CHANNEL_PER_STREAM) {
		channels[capture->stream_id][capture->virtual_channel_id] = NULL;
	}

	capture->channel_id = CAPTURE_CHANNEL_INVALID_ID;
	capture->stream_id = NVCSI_STREAM_INVALID_ID;
	capture->csi_port = NVCSI_PORT_UNSPECIFIED;
	capture->virtual_channel_id = NVCSI_STREAM_INVALID_TPG_VC_ID;

	if (capture->is_progress_status_notifier_set) {
		capture_common_release_progress_status_notifier(
			&capture->progress_status_notifier);
	}

	return err;
}
EXPORT_SYMBOL_GPL(vi_capture_release);

static int vi_capture_control_send_message(
	struct tegra_vi_channel *chan,
	const struct CAPTURE_CONTROL_MSG *msg_cpy,
	size_t size)
{
	int err = 0;
	struct vi_capture *capture = chan->capture_data;
	struct CAPTURE_MSG_HEADER *header;
	uint32_t resp_id;

	header = (struct CAPTURE_MSG_HEADER *)msg_cpy;
	header->channel_id = capture->channel_id;

	switch (header->msg_id) {
	case CAPTURE_COMPAND_CONFIG_REQ:
		resp_id = CAPTURE_COMPAND_CONFIG_RESP;
		break;
	case CAPTURE_PDAF_CONFIG_REQ:
		resp_id = CAPTURE_PDAF_CONFIG_RESP;
		break;
	case CAPTURE_SYNCGEN_ENABLE_REQ:
		resp_id = CAPTURE_SYNCGEN_ENABLE_RESP;
		break;
	case CAPTURE_SYNCGEN_DISABLE_REQ:
		resp_id = CAPTURE_SYNCGEN_DISABLE_RESP;
		break;
	case CAPTURE_PHY_STREAM_OPEN_REQ:
		if (chan->is_stream_opened) {
			dev_dbg(chan->dev,
				"%s: NVCSI stream is already opened for this VI channel",
				__func__);
			return 0;
		}
		resp_id = CAPTURE_PHY_STREAM_OPEN_RESP;
		capture->stream_id = msg_cpy->phy_stream_open_req.stream_id;
		capture->csi_port = msg_cpy->phy_stream_open_req.csi_port;
		break;
	case CAPTURE_PHY_STREAM_CLOSE_REQ:
		if (!chan->is_stream_opened) {
			dev_dbg(chan->dev,
				"%s: NVCSI stream is already closed for this VI channel",
				__func__);
			return 0;
		}
		resp_id = CAPTURE_PHY_STREAM_CLOSE_RESP;
		break;
	case CAPTURE_PHY_STREAM_DUMPREGS_REQ:
		resp_id = CAPTURE_PHY_STREAM_DUMPREGS_RESP;
		break;
	case CAPTURE_CSI_STREAM_SET_CONFIG_REQ:
		resp_id = CAPTURE_CSI_STREAM_SET_CONFIG_RESP;
		break;
	case CAPTURE_CSI_STREAM_SET_PARAM_REQ:
		resp_id = CAPTURE_CSI_STREAM_SET_PARAM_RESP;
		break;
	case CAPTURE_CSI_STREAM_TPG_SET_CONFIG_REQ:
		resp_id = CAPTURE_CSI_STREAM_TPG_SET_CONFIG_RESP;
		break;
	case CAPTURE_CSI_STREAM_TPG_START_REQ:
		resp_id = CAPTURE_CSI_STREAM_TPG_START_RESP;
		capture->virtual_channel_id =
			msg_cpy->csi_stream_tpg_start_req.virtual_channel_id;
		break;
	case CAPTURE_CSI_STREAM_TPG_START_RATE_REQ:
		resp_id = CAPTURE_CSI_STREAM_TPG_START_RATE_RESP;
		capture->virtual_channel_id = msg_cpy->
			csi_stream_tpg_start_rate_req.virtual_channel_id;
		break;
	case CAPTURE_CSI_STREAM_TPG_APPLY_GAIN_REQ:
		resp_id = CAPTURE_CSI_STREAM_TPG_APPLY_GAIN_RESP;
		break;
	case CAPTURE_CSI_STREAM_TPG_STOP_REQ:
		resp_id = CAPTURE_CSI_STREAM_TPG_STOP_RESP;
		break;
	case CAPTURE_CHANNEL_EI_REQ:
		resp_id = CAPTURE_CHANNEL_EI_RESP;
		break;
	case CAPTURE_HSM_CHANSEL_ERROR_MASK_REQ:
		resp_id = CAPTURE_HSM_CHANSEL_ERROR_MASK_RESP;
		break;
	default:
		dev_err(chan->dev, "%s: unknown capture control req 0x%x",
			__func__, header->msg_id);
		return -EINVAL;
	}

	err = vi_capture_ivc_send_control(chan, msg_cpy, size, resp_id);
	if (err < 0) {
		dev_err(chan->dev, "%s: failed to send IVC control message", __func__);
		return err;
	}

	if (header->msg_id == CAPTURE_PHY_STREAM_OPEN_REQ)
		chan->is_stream_opened = true;
	else if (header->msg_id == CAPTURE_PHY_STREAM_CLOSE_REQ)
		chan->is_stream_opened = false;

	return err;
}

/**
 * @brief Disable the VI channel's NVCSI TPG stream in RCE.
 *
 * @param[in]	chan	VI channel context
 *
 * @returns	0 (success), neg. errno (failure)
 */
static int csi_stream_tpg_disable(
	struct tegra_vi_channel *chan)
{
	struct vi_capture *capture = chan->capture_data;
	struct CAPTURE_CONTROL_MSG control_desc;
	struct CAPTURE_CONTROL_MSG *resp_msg = &capture->control_resp_msg;
	int err = 0;

	memset(&control_desc, 0, sizeof(control_desc));
	control_desc.header.msg_id = CAPTURE_CSI_STREAM_TPG_STOP_REQ;
	control_desc.header.channel_id = capture->channel_id;
	control_desc.csi_stream_tpg_stop_req.stream_id = capture->stream_id;
	control_desc.csi_stream_tpg_stop_req.virtual_channel_id =
		capture->virtual_channel_id;

	err = vi_capture_ivc_send_control(chan, &control_desc,
		sizeof(control_desc), CAPTURE_CSI_STREAM_TPG_STOP_RESP);
	if ((err < 0) ||
			(resp_msg->csi_stream_tpg_stop_resp.result
				!= CAPTURE_OK))
		return err;

	return 0;
}

/**
 * @brief Disable the VI channel's NVCSI stream in RCE.
 *
 * @param[in]	chan	VI channel context
 *
 * @returns	0 (success), neg. errno (failure)
 */
static int csi_stream_close(
	struct tegra_vi_channel *chan)
{
	struct vi_capture *capture = chan->capture_data;
	struct CAPTURE_CONTROL_MSG control_desc;
	struct CAPTURE_CONTROL_MSG *resp_msg = &capture->control_resp_msg;
	int err = 0;

	memset(&control_desc, 0, sizeof(control_desc));
	control_desc.header.msg_id = CAPTURE_PHY_STREAM_CLOSE_REQ;
	control_desc.header.channel_id = capture->channel_id;
	control_desc.phy_stream_close_req.phy_type = NVPHY_TYPE_CSI;
	control_desc.phy_stream_close_req.stream_id = capture->stream_id;
	control_desc.phy_stream_close_req.csi_port = capture->csi_port;

	err = vi_capture_control_send_message(chan, &control_desc,
		sizeof(control_desc));
	if ((err < 0) ||
			(resp_msg->phy_stream_close_resp.result != CAPTURE_OK))
		return err;

	return 0;
}

int csi_stream_release(
	struct tegra_vi_channel *chan)
{
	struct vi_capture *capture = chan->capture_data;
	int err = 0;

	if (capture->stream_id == NVCSI_STREAM_INVALID_ID)
		return 0;

	if (capture->virtual_channel_id != NVCSI_STREAM_INVALID_TPG_VC_ID) {
		err = csi_stream_tpg_disable(chan);
		if (err < 0) {
			dev_err(chan->dev,
				"%s: failed to disable nvcsi tpg on stream %u virtual channel %u\n",
				__func__, capture->stream_id,
				capture->virtual_channel_id);
			return err;
		}
	}

	if (chan->is_stream_opened) {
		err = csi_stream_close(chan);
		if (err < 0)
			dev_err(chan->dev,
				"%s: failed to close nvcsi stream %u\n",
				__func__, capture->stream_id);
	}

	return err;
}

int vi_capture_control_message_from_user(
	struct tegra_vi_channel *chan,
	struct vi_capture_control_msg *msg)
{
	struct vi_capture *capture;
	const void __user *msg_ptr;
	void __user *response;
	void *msg_cpy;
	struct CAPTURE_CONTROL_MSG *resp_msg;
	int err = 0;

	if (chan == NULL) {
		dev_err(NULL, "%s: NULL VI channel received\n", __func__);
		return -ENODEV;
	}

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_SET_CONFIG);

	capture = chan->capture_data;

	if (capture == NULL) {
		dev_err(chan->dev,
			 "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	resp_msg = &capture->control_resp_msg;

	if (msg->ptr == 0ull || msg->response == 0ull || msg->size == 0)
		return -EINVAL;

	msg_ptr = (const void __user *)(uintptr_t)msg->ptr;
	response = (void __user *)(uintptr_t)msg->response;

	msg_cpy = kzalloc(msg->size, GFP_KERNEL);
	if (unlikely(msg_cpy == NULL))
		return -ENOMEM;

	err = copy_from_user(msg_cpy, msg_ptr, msg->size) ? -EFAULT : 0;
	if (err < 0)
		goto fail;


	err = vi_capture_control_send_message(chan, msg_cpy, msg->size);
	if (err < 0)
		goto fail;

	err = copy_to_user(response, resp_msg,
		sizeof(*resp_msg)) ? -EFAULT : 0;
	if (err < 0)
		goto fail;

fail:
	kfree(msg_cpy);
	return err;
}
EXPORT_SYMBOL_GPL(vi_capture_control_message_from_user);

int vi_capture_control_message(
	struct tegra_vi_channel *chan,
	struct vi_capture_control_msg *msg)
{
	struct vi_capture *capture;
	void *msg_cpy;
	struct CAPTURE_CONTROL_MSG *resp_msg;
	int err = 0;

	if (chan == NULL) {
		dev_err(NULL,"%s: NULL VI channel received\n", __func__);
		return -ENODEV;
	}

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_SET_CONFIG);

	capture = chan->capture_data;

	if (capture == NULL) {
		dev_err(chan->dev,
			 "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	resp_msg = &capture->control_resp_msg;
	if (msg->ptr == 0ull || msg->response == 0ull || msg->size == 0)
		return -EINVAL;

	msg_cpy = kzalloc(msg->size, GFP_KERNEL);
	if (unlikely(msg_cpy == NULL))
		return -ENOMEM;

	memcpy(msg_cpy, (const void *)(uintptr_t)msg->ptr,
						msg->size);

	err = vi_capture_control_send_message(chan, msg_cpy, msg->size);
	if (err < 0)
		goto fail;

	memcpy((void *)(uintptr_t)msg->response, resp_msg,
						sizeof(*resp_msg));

fail:
	kfree(msg_cpy);
	return err;
}

int vi_capture_get_info(
	struct tegra_vi_channel *chan,
	struct vi_capture_info *info)
{
	struct vi_capture *capture = chan->capture_data;
	int err;

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_GET_INFO);

	if (capture == NULL) {
		dev_err(chan->dev,
			 "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	if (capture->channel_id == CAPTURE_CHANNEL_INVALID_ID) {
		dev_err(chan->dev,
			"%s: setup channel first\n", __func__);
		return -ENODEV;
	}

	if (info == NULL)
		return -EINVAL;

	info->syncpts.progress_syncpt = capture->progress_sp.id;
	info->syncpts.emb_data_syncpt = capture->embdata_sp.id;
	info->syncpts.line_timer_syncpt = capture->linetimer_sp.id;

	err = vi_capture_read_syncpt(chan, &capture->progress_sp,
			&info->syncpts.progress_syncpt_val);
	if (err < 0)
		return err;
	err = vi_capture_read_syncpt(chan, &capture->embdata_sp,
			&info->syncpts.emb_data_syncpt_val);
	if (err < 0)
		return err;
	err = vi_capture_read_syncpt(chan, &capture->linetimer_sp,
			&info->syncpts.line_timer_syncpt_val);
	if (err < 0)
		return err;

	info->hw_channel_id = capture->channel_id;
	info->vi_channel_mask = capture->vi_channel_mask;
	info->vi2_channel_mask = capture->vi2_channel_mask;

	return 0;
}
EXPORT_SYMBOL_GPL(vi_capture_get_info);

static uint32_t vi_capture_get_num_progress(
	struct tegra_vi_channel *chan,
	struct vi_capture_req *req)
{
	struct vi_capture *capture = chan->capture_data;
	struct capture_descriptor* desc = (struct capture_descriptor*)
		(capture->requests.va +
				req->buffer_index * capture->request_size);
	struct vi_channel_config* config = &desc->ch_cfg;

	const uint16_t minProgress = 2U;
	uint16_t numProgress = minProgress;

	/* Minimum of two progress fences for PXL_SOF and PXL_EOF */
	if (config->flush_enable == 0x1UL)
	{
		if (config->flush_periodic == 0x1UL)
		{
			if (config->flush_first != 0U)
			{
				numProgress++;
			}
			numProgress += (config->frame.frame_y - config->flush_first) /
				config->flush;
		}
		else
		{
			numProgress++;
		}
		/**
		 * if (HEIGHT % TRIPLINE == 0) then TRIPLINE is reached at the same
		 * time as PXL_EOF. EOF occurs on the cropped last line, hence EOF and
		 * EOL are simultaneous. In this case, final PXL_EOF tag have NLINES bit
		 * set in its payload, while there is no NLINE_DONE tag. Therefore,
		 * number of progress fences should be decreased by one.
		 */
		if (((config->frame.frame_y - config->flush_first) % config->flush) == 0U)
		{
			numProgress--;
		}
	}
	return (uint32_t)numProgress;
}

int vi_capture_request(
	struct tegra_vi_channel *chan,
	struct vi_capture_req *req)
{
	struct vi_capture *capture = chan->capture_data;
	struct CAPTURE_MSG capture_desc;
	int err = 0;

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_REQUEST);

	if (capture == NULL) {
		dev_err(chan->dev,
			"%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	if (capture->channel_id == CAPTURE_CHANNEL_INVALID_ID) {
		dev_err(chan->dev,
			"%s: setup channel first\n", __func__);
		return -ENODEV;
	}

	if (req == NULL) {
		dev_err(chan->dev,
			"%s: Invalid req\n", __func__);
		return -EINVAL;
	}

	mutex_lock(&capture->reset_lock);

	memset(&capture_desc, 0, sizeof(capture_desc));
	capture_desc.header.msg_id = CAPTURE_REQUEST_REQ;
	capture_desc.header.channel_id = capture->channel_id;
	capture_desc.capture_request_req.buffer_index = req->buffer_index;

	nv_camera_log_vi_submit(
			chan->ndev,
			capture->progress_sp.id,
			capture->progress_sp.threshold,
			capture_desc.header.channel_id,
			__arch_counter_get_cntvct());

	dev_dbg(chan->dev, "%s: sending chan_id %u msg_id %u buf:%u\n",
			__func__, capture_desc.header.channel_id,
			capture_desc.header.msg_id, req->buffer_index);
	err = tegra_capture_ivc_capture_submit(&capture_desc,
			sizeof(capture_desc));
	if (err < 0) {
		mutex_unlock(&capture->reset_lock);
		dev_err(chan->dev, "IVC capture submit failed\n");
		return err;
	}

	// Progress syncpoints + 1 for status syncpoint
	capture->progress_sp.threshold += vi_capture_get_num_progress(chan, req) + 1;

	mutex_unlock(&capture->reset_lock);

	return 0;
}
EXPORT_SYMBOL_GPL(vi_capture_request);

int vi_capture_status(
	struct tegra_vi_channel *chan,
	int32_t timeout_ms)
{
	struct vi_capture *capture = chan->capture_data;
	int ret = 0;

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_STATUS);

	if (capture == NULL) {
		dev_err(chan->dev,
			 "%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	if (capture->channel_id == CAPTURE_CHANNEL_INVALID_ID) {
		dev_err(chan->dev,
			"%s: setup channel first\n", __func__);
		return -ENODEV;
	}

	dev_dbg(chan->dev, "%s: waiting for status, timeout:%d ms\n",
		__func__, timeout_ms);

	/* negative timeout means wait forever */
	if (timeout_ms < 0) {
		ret = wait_for_completion_interruptible(&capture->capture_resp);
		if (ret == -ERESTARTSYS) {
			dev_dbg(chan->dev,
				"capture status interrupted\n");
			return -ETIMEDOUT;
		}
	} else {
		ret = wait_for_completion_timeout(
				&capture->capture_resp,
				msecs_to_jiffies(timeout_ms));
		if (ret == 0) {
			dev_dbg(chan->dev,
				"capture status timed out\n");
			return -ETIMEDOUT;
		}
	}

	if (ret < 0) {
		dev_err(chan->dev,
			"wait for capture status failed\n");
		return ret;
	}

	return 0;
}
EXPORT_SYMBOL_GPL(vi_capture_status);

int vi_capture_set_progress_status_notifier(
	struct tegra_vi_channel *chan,
	struct vi_capture_progress_status_req *req)
{
	int err = 0;
	struct vi_capture *capture = chan->capture_data;

	nv_camera_log(chan->ndev,
		__arch_counter_get_cntvct(),
		NVHOST_CAMERA_VI_CAPTURE_SET_PROGRESS_STATUS);

	if (req->mem == 0 ||
		req->buffer_depth == 0) {
		dev_err(chan->dev,
				"%s: request buffer is invalid\n", __func__);
		return -EINVAL;
	}

	if (capture == NULL) {
		dev_err(chan->dev,
				"%s: vi capture uninitialized\n", __func__);
		return -ENODEV;
	}

	if (req->buffer_depth < capture->queue_depth) {
		dev_err(chan->dev,
			"Progress status buffer is smaller than queue depth");
		return -EINVAL;
	}

	/* Setup the progress status buffer */
	err = capture_common_setup_progress_status_notifier(
		&capture->progress_status_notifier,
		req->mem,
		sizeof(uint32_t) * req->buffer_depth,
		req->mem_offset);

	if (err < 0) {
		dev_err(chan->dev, "%s: memory setup failed\n", __func__);
		return -EFAULT;
	}

	dev_dbg(chan->dev, "mem offset %u\n", req->mem_offset);
	dev_dbg(chan->dev, "buffer depth %u\n", req->buffer_depth);

	capture->progress_status_buffer_depth = req->buffer_depth;
	capture->is_progress_status_notifier_set = true;
	return err;
}
EXPORT_SYMBOL_GPL(vi_capture_set_progress_status_notifier);

static int csi_vi_get_mapping_table(struct platform_device *pdev)
{
	uint32_t index = 0;
	int err = 0;
	struct device *dev = &pdev->dev;
	struct tegra_capture_vi_data *info = platform_get_drvdata(pdev);

	int nmap_elems;
	uint32_t map_table_size;
	uint32_t *map_table = info->vi_instance_table;

	const struct device_node *np = dev->of_node;

	(void)of_property_read_u32(np,
			"nvidia,vi-mapping-size", &map_table_size);
	if (map_table_size > MAX_NVCSI_STREAM_IDS) {
		dev_err(dev, "invalid mapping table size %u\n", map_table_size);
		return -EINVAL;
	}
	info->num_csi_vi_maps = map_table_size;

	nmap_elems = of_property_count_strings(np, "nvidia,vi-mapping-names");
	if (nmap_elems != ARRAY_SIZE(vi_mapping_elements))
		return -EINVAL;

	/* check for order of csi-stream-id and vi-unit-id in DT entry */
	for (index = 0; index < ARRAY_SIZE(vi_mapping_elements); index++) {
		int map_elem = of_property_match_string(np,
			"nvidia,vi-mapping-names", vi_mapping_elements[index]);
		if (map_elem != index) {
			dev_err(dev, "invalid mapping order\n");
			return -EINVAL;
		}
	}

	for (index = 0; index < map_table_size; index++)
		map_table[index] = INVALID_VI_UNIT_ID;

	for (index = 0; index < map_table_size; index++) {
		uint32_t stream_index = NVCSI_STREAM_INVALID_ID;
		uint32_t vi_unit_id = INVALID_VI_UNIT_ID;

		err = of_property_read_u32_index(np,
			"nvidia,vi-mapping",
			2 * index,
			&stream_index);
		if (err) {
			dev_err(dev,
				"%s: ERR %d: missing property nvidia,vi-mapping or csi_stream_id at index %d",
				__func__, err, index);
			return err;
		}

		/* Check for valid/duplicate csi-stream-id */
		if (stream_index >= MAX_NVCSI_STREAM_IDS ||
			map_table[stream_index] != INVALID_VI_UNIT_ID) {
			dev_err(dev, "%s: mapping invalid csi_stream_id: %u\n",
					__func__, stream_index);
			return -EINVAL;
		}

		err = of_property_read_u32_index(np,
			"nvidia,vi-mapping",
			2 * index + 1,
			&vi_unit_id);
		if (err) {
			dev_err(dev,
				"%s: ERR %d: missing property nvidia,vi-mapping or vi_unit_id at index %d",
				__func__, err, index);
			return err;
		}

		/* check for valid vi-unit-id */
		if (vi_unit_id >= MAX_VI_UNITS) {
			dev_err(dev, "%s: mapping invalid vi_unit_id: %u\n",
					__func__, vi_unit_id);
			return -EINVAL;
		}

		map_table[stream_index] = vi_unit_id;
	}

	dev_dbg(dev, "%s: csi-stream to vi-instance mapping table size: %u\n",
		__func__, info->num_csi_vi_maps);

	for (index = 0; index < ARRAY_SIZE(info->vi_instance_table); index++)
		dev_dbg(dev, "%s: vi_instance_table[%d] = %d\n",
			__func__, index, info->vi_instance_table[index]);

	return 0;
}

static int capture_vi_probe(struct platform_device *pdev)
{
	uint32_t ii;
	int err = 0;
	struct tegra_capture_vi_data *info;
	struct device *dev = &pdev->dev;

	dev_dbg(dev, "%s: tegra-camrtc-capture-vi probe\n", __func__);

	info = devm_kzalloc(dev,
			sizeof(*info), GFP_KERNEL);
	if (info == NULL)
		return -ENOMEM;

	info->num_vi_devices = 0;

	err = of_property_read_u32(dev->of_node, "nvidia,vi-max-channels",
			&info->max_vi_channels);
	if (err || info->max_vi_channels > NUM_VI_CHANNELS) {
		err = -EINVAL;
		goto cleanup;
	}

	if (info->max_vi_channels == 0)
		info->max_vi_channels = DEFAULT_VI_CHANNELS;

	for (ii = 0; ; ii++) {
		struct device_node *np;
		struct platform_device *pvidev;

		np = of_parse_phandle(dev->of_node, "nvidia,vi-devices", ii);
		if (np == NULL)
			break;

		if (info->num_vi_devices >= ARRAY_SIZE(info->vi_pdevices)) {
			of_node_put(np);
			err = -EINVAL;
			goto cleanup;
		}

		pvidev = of_find_device_by_node(np);
		of_node_put(np);

		if (pvidev == NULL) {
			dev_WARN(dev, "vi node %d has no device\n", ii);
			err = -ENODEV;
			goto cleanup;
		}

		info->vi_pdevices[ii] = pvidev;
		info->num_vi_devices++;
	}

	if (info->num_vi_devices < 1)
		return -EINVAL;

	platform_set_drvdata(pdev, info);

	if (info->num_vi_devices == 1) {
		dev_dbg(dev, "default 0 vi-unit-id for all csi-stream-ids\n");
	} else {
		/* read mapping table from DT for multiple VIs */
		err = csi_vi_get_mapping_table(pdev);
		if (err) {
			dev_err(dev,
				"%s: reading csi-to-vi mapping failed\n",
				__func__);
			goto cleanup;
		}
	}

	err = vi_channel_drv_register(pdev, info->max_vi_channels);
	if (err) {
		vi_channel_drv_exit();
		goto cleanup;
	}

	info->vi_common.mc_vi.vi = &info->vi_common;
	info->vi_common.mc_vi.fops = &vi5_fops;
	err = tegra_capture_vi_media_controller_init(
			&info->vi_common.mc_vi, pdev);
	if (err) {
		dev_warn(&pdev->dev, "media controller init failed\n");
		err = 0;
	}

	memset(channels, 0 , sizeof(channels));

	return 0;

cleanup:
	for (ii = 0; ii < info->num_vi_devices; ii++)
		put_device(&info->vi_pdevices[ii]->dev);

	dev_err(dev, "%s: tegra-camrtc-capture-vi probe failed\n", __func__);
	return err;
}

static int capture_vi_remove(struct platform_device *pdev)
{
	struct tegra_capture_vi_data *info;
	uint32_t ii;
	struct device *dev = &pdev->dev;

	dev_dbg(dev, "%s:tegra-camrtc-capture-vi remove\n", __func__);

	info = platform_get_drvdata(pdev);

	for (ii = 0; ii < info->num_vi_devices; ii++)
		put_device(&info->vi_pdevices[ii]->dev);

	vi_channel_drv_unregister(&pdev->dev);
	tegra_vi_media_controller_cleanup(&info->vi_common.mc_vi);
	vi_channel_drv_exit();

	return 0;
}

static const struct of_device_id capture_vi_of_match[] = {
	{ .compatible = "nvidia,tegra-camrtc-capture-vi" },
	{ },
};
MODULE_DEVICE_TABLE(of, capture_vi_of_match);

#if defined(NV_PLATFORM_DRIVER_STRUCT_REMOVE_RETURNS_VOID) /* Linux v6.11 */
static void capture_vi_remove_wrapper(struct platform_device *pdev)
{
	capture_vi_remove(pdev);
}
#else
static int capture_vi_remove_wrapper(struct platform_device *pdev)
{
	return capture_vi_remove(pdev);
}
#endif

static struct platform_driver capture_vi_driver = {
	.probe = capture_vi_probe,
	.remove = capture_vi_remove_wrapper,
	.driver = {
		.owner = THIS_MODULE,
		.name = "tegra-camrtc-capture-vi",
		.of_match_table = capture_vi_of_match
	}
};

static int __init capture_vi_init(void)
{
	int err;
	err = vi_channel_drv_init();
	if (err)
		return err;

	err = platform_driver_register(&capture_vi_driver);
	if (err) {
		vi_channel_drv_exit();
		return err;
	}

	return 0;
}
static void __exit capture_vi_exit(void)
{
	vi_channel_drv_exit();
	platform_driver_unregister(&capture_vi_driver);
}

module_init(capture_vi_init);
module_exit(capture_vi_exit);

#if defined(NV_MODULE_IMPORT_NS_CALLS_STRINGIFY)
MODULE_IMPORT_NS(DMA_BUF);
#else
MODULE_IMPORT_NS("DMA_BUF");
#endif
MODULE_DESCRIPTION("tegra fusa-capture driver");
MODULE_LICENSE("GPL");
