// SPDX-License-Identifier: GPL-2.0
/*
 * Support for Intel Camera Imaging ISP subsystem.
 * Copyright (c) 2015, Intel Corporation.
 *
 * 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.
 */

#include "ia_css_mipi.h"
#include "sh_css_mipi.h"
#include <type_support.h>
#include "system_global.h"
#include "ia_css_err.h"
#include "ia_css_pipe.h"
#include "ia_css_stream_format.h"
#include "sh_css_stream_format.h"
#include "ia_css_stream_public.h"
#include "ia_css_frame_public.h"
#include "ia_css_input_port.h"
#include "ia_css_debug.h"
#include "sh_css_struct.h"
#include "sh_css_defs.h"
#include "sh_css_sp.h" /* sh_css_update_host2sp_mipi_frame sh_css_update_host2sp_num_mipi_frames ... */
#include "sw_event_global.h" /* IA_CSS_PSYS_SW_EVENT_MIPI_BUFFERS_READY */

static u32
ref_count_mipi_allocation[N_CSI_PORTS]; /* Initialized in mipi_init */

int
ia_css_mipi_frame_specify(const unsigned int size_mem_words,
			  const bool contiguous) {
	int err = 0;

	my_css.size_mem_words = size_mem_words;
	(void)contiguous;

	return err;
}

/*
 * Check if a source port or TPG/PRBS ID is valid
 */
static bool ia_css_mipi_is_source_port_valid(struct ia_css_pipe *pipe,
	unsigned int *pport)
{
	bool ret = true;
	unsigned int port = 0;
	unsigned int max_ports = 0;

	switch (pipe->stream->config.mode) {
	case IA_CSS_INPUT_MODE_BUFFERED_SENSOR:
		port = (unsigned int)pipe->stream->config.source.port.port;
		max_ports = N_CSI_PORTS;
		break;
	case IA_CSS_INPUT_MODE_TPG:
		port = (unsigned int)pipe->stream->config.source.tpg.id;
		max_ports = N_CSS_TPG_IDS;
		break;
	case IA_CSS_INPUT_MODE_PRBS:
		port = (unsigned int)pipe->stream->config.source.prbs.id;
		max_ports = N_CSS_PRBS_IDS;
		break;
	default:
		assert(false);
		ret = false;
		break;
	}

	if (ret) {
		assert(port < max_ports);

		if (port >= max_ports)
			ret = false;
	}

	*pport = port;

	return ret;
}

/* Assumptions:
 *	- A line is multiple of 4 bytes = 1 word.
 *	- Each frame has SOF and EOF (each 1 word).
 *	- Each line has format header and optionally SOL and EOL (each 1 word).
 *	- Odd and even lines of YUV420 format are different in bites per pixel size.
 *	- Custom size of embedded data.
 *  -- Interleaved frames are not taken into account.
 *  -- Lines are multiples of 8B, and not necessary of (custom 3B, or 7B
 *  etc.).
 * Result is given in DDR mem words, 32B or 256 bits
 */
int
ia_css_mipi_frame_calculate_size(const unsigned int width,
				 const unsigned int height,
				 const enum atomisp_input_format format,
				 const bool hasSOLandEOL,
				 const unsigned int embedded_data_size_words,
				 unsigned int *size_mem_words) {
	int err = 0;

	unsigned int bits_per_pixel = 0;
	unsigned int even_line_bytes = 0;
	unsigned int odd_line_bytes = 0;
	unsigned int words_per_odd_line = 0;
	unsigned int words_for_first_line = 0;
	unsigned int words_per_even_line = 0;
	unsigned int mem_words_per_even_line = 0;
	unsigned int mem_words_per_odd_line = 0;
	unsigned int mem_words_for_first_line = 0;
	unsigned int mem_words_for_EOF = 0;
	unsigned int mem_words = 0;
	unsigned int width_padded = width;

#if defined(ISP2401)
	/* The changes will be reverted as soon as RAW
	 * Buffers are deployed by the 2401 Input System
	 * in the non-continuous use scenario.
	 */
	width_padded += (2 * ISP_VEC_NELEMS);
#endif

	IA_CSS_ENTER("padded_width=%d, height=%d, format=%d, hasSOLandEOL=%d, embedded_data_size_words=%d\n",
		     width_padded, height, format, hasSOLandEOL, embedded_data_size_words);

	switch (format)
	{
	case ATOMISP_INPUT_FORMAT_RAW_6:		/* 4p, 3B, 24bits */
		bits_per_pixel = 6;
		break;
	case ATOMISP_INPUT_FORMAT_RAW_7:		/* 8p, 7B, 56bits */
		bits_per_pixel = 7;
		break;
	case ATOMISP_INPUT_FORMAT_RAW_8:		/* 1p, 1B, 8bits */
	case ATOMISP_INPUT_FORMAT_BINARY_8:		/*  8bits, TODO: check. */
	case ATOMISP_INPUT_FORMAT_YUV420_8:		/* odd 2p, 2B, 16bits, even 2p, 4B, 32bits */
		bits_per_pixel = 8;
		break;
	case ATOMISP_INPUT_FORMAT_YUV420_10:		/* odd 4p, 5B, 40bits, even 4p, 10B, 80bits */
	case ATOMISP_INPUT_FORMAT_RAW_10:		/* 4p, 5B, 40bits */
#if !defined(HAS_NO_PACKED_RAW_PIXELS)
		/* The changes will be reverted as soon as RAW
		 * Buffers are deployed by the 2401 Input System
		 * in the non-continuous use scenario.
		 */
		bits_per_pixel = 10;
#else
		bits_per_pixel = 16;
#endif
		break;
	case ATOMISP_INPUT_FORMAT_YUV420_8_LEGACY:	/* 2p, 3B, 24bits */
	case ATOMISP_INPUT_FORMAT_RAW_12:		/* 2p, 3B, 24bits */
		bits_per_pixel = 12;
		break;
	case ATOMISP_INPUT_FORMAT_RAW_14:		/* 4p, 7B, 56bits */
		bits_per_pixel = 14;
		break;
	case ATOMISP_INPUT_FORMAT_RGB_444:		/* 1p, 2B, 16bits */
	case ATOMISP_INPUT_FORMAT_RGB_555:		/* 1p, 2B, 16bits */
	case ATOMISP_INPUT_FORMAT_RGB_565:		/* 1p, 2B, 16bits */
	case ATOMISP_INPUT_FORMAT_YUV422_8:		/* 2p, 4B, 32bits */
		bits_per_pixel = 16;
		break;
	case ATOMISP_INPUT_FORMAT_RGB_666:		/* 4p, 9B, 72bits */
		bits_per_pixel = 18;
		break;
	case ATOMISP_INPUT_FORMAT_YUV422_10:		/* 2p, 5B, 40bits */
		bits_per_pixel = 20;
		break;
	case ATOMISP_INPUT_FORMAT_RGB_888:		/* 1p, 3B, 24bits */
		bits_per_pixel = 24;
		break;

	case ATOMISP_INPUT_FORMAT_YUV420_16:		/* Not supported */
	case ATOMISP_INPUT_FORMAT_YUV422_16:		/* Not supported */
	case ATOMISP_INPUT_FORMAT_RAW_16:		/* TODO: not specified in MIPI SPEC, check */
	default:
		return -EINVAL;
	}

	odd_line_bytes = (width_padded * bits_per_pixel + 7) >> 3; /* ceil ( bits per line / 8) */

	/* Even lines for YUV420 formats are double in bits_per_pixel. */
	if (format == ATOMISP_INPUT_FORMAT_YUV420_8
	    || format == ATOMISP_INPUT_FORMAT_YUV420_10
	    || format == ATOMISP_INPUT_FORMAT_YUV420_16)
	{
		even_line_bytes = (width_padded * 2 * bits_per_pixel + 7) >>
			3; /* ceil ( bits per line / 8) */
	} else
	{
		even_line_bytes = odd_line_bytes;
	}

	/*  a frame represented in memory:  ()- optional; data - payload words.
	*  addr		0	1	2	3	4	5	6	7:
	*  first	SOF	(SOL)	PACK_H	data	data	data	data	data
	*		data	data	data	data	data	data	data	data
	*		...
	*		data	data	0	0	0	0	0	0
	*  second	(EOL)	(SOL)	PACK_H	data	data	data	data	data
	*		data	data	data	data	data	data	data	data
	*		...
	*		data	data	0	0	0	0	0	0
	*  ...
	*  last		(EOL)	EOF	0	0	0	0	0	0
	*
	*  Embedded lines are regular lines stored before the first and after
	*  payload lines.
	*/

	words_per_odd_line = (odd_line_bytes + 3) >> 2;
	/* ceil(odd_line_bytes/4); word = 4 bytes */
	words_per_even_line  = (even_line_bytes  + 3) >> 2;
	words_for_first_line = words_per_odd_line + 2 + (hasSOLandEOL ? 1 : 0);
	/* + SOF +packet header + optionally (SOL), but (EOL) is not in the first line */
	words_per_odd_line	+= (1 + (hasSOLandEOL ? 2 : 0));
	/* each non-first line has format header, and optionally (SOL) and (EOL). */
	words_per_even_line += (1 + (hasSOLandEOL ? 2 : 0));

	mem_words_per_odd_line	 = (words_per_odd_line + 7) >> 3;
	/* ceil(words_per_odd_line/8); mem_word = 32 bytes, 8 words */
	mem_words_for_first_line = (words_for_first_line + 7) >> 3;
	mem_words_per_even_line  = (words_per_even_line + 7) >> 3;
	mem_words_for_EOF        = 1; /* last line consisit of the optional (EOL) and EOF */

	mem_words = ((embedded_data_size_words + 7) >> 3) +
	mem_words_for_first_line +
	(((height + 1) >> 1) - 1) * mem_words_per_odd_line +
	/* ceil (height/2) - 1 (first line is calculated separatelly) */
	(height      >> 1) * mem_words_per_even_line + /* floor(height/2) */
	mem_words_for_EOF;

	*size_mem_words = mem_words; /* ceil(words/8); mem word is 32B = 8words. */
	/* Check if the above is still needed. */

	IA_CSS_LEAVE_ERR(err);
	return err;
}

#if !defined(ISP2401)
int
ia_css_mipi_frame_enable_check_on_size(const enum mipi_port_id port,
				       const unsigned int	size_mem_words) {
	u32 idx;

	int err = -EBUSY;

	OP___assert(port < N_CSI_PORTS);
	OP___assert(size_mem_words != 0);

	for (idx = 0; idx < IA_CSS_MIPI_SIZE_CHECK_MAX_NOF_ENTRIES_PER_PORT &&
	     my_css.mipi_sizes_for_check[port][idx] != 0;
	     idx++)   /* do nothing */
	{
	}
	if (idx < IA_CSS_MIPI_SIZE_CHECK_MAX_NOF_ENTRIES_PER_PORT)
	{
		my_css.mipi_sizes_for_check[port][idx] = size_mem_words;
		err = 0;
	}

	return err;
}
#endif

void
mipi_init(void)
{
	unsigned int i;

	for (i = 0; i < N_CSI_PORTS; i++)
		ref_count_mipi_allocation[i] = 0;
}

int
calculate_mipi_buff_size(
    struct ia_css_stream_config *stream_cfg,
    unsigned int *size_mem_words) {
#if !defined(ISP2401)
	int err = -EINVAL;
	(void)stream_cfg;
	(void)size_mem_words;
#else
	unsigned int width;
	unsigned int height;
	enum atomisp_input_format format;
	bool pack_raw_pixels;

	unsigned int width_padded;
	unsigned int bits_per_pixel = 0;

	unsigned int even_line_bytes = 0;
	unsigned int odd_line_bytes = 0;

	unsigned int words_per_odd_line = 0;
	unsigned int words_per_even_line = 0;

	unsigned int mem_words_per_even_line = 0;
	unsigned int mem_words_per_odd_line = 0;

	unsigned int mem_words_per_buff_line = 0;
	unsigned int mem_words_per_buff = 0;
	int err = 0;

	/**
	 * zhengjie.lu@intel.com
	 *
	 * NOTE
	 * - In the struct "ia_css_stream_config", there
	 *   are two members: "input_config" and "isys_config".
	 *   Both of them provide the same information, e.g.
	 *   input_res and format.
	 *
	 *   Question here is that: which one shall be used?
	 */
	width = stream_cfg->input_config.input_res.width;
	height = stream_cfg->input_config.input_res.height;
	format = stream_cfg->input_config.format;
	pack_raw_pixels = stream_cfg->pack_raw_pixels;
	/* end of NOTE */

	/**
	 * zhengjie.lu@intel.com
	 *
	 * NOTE
	 * - The following code is derived from the
	 *   existing code "ia_css_mipi_frame_calculate_size()".
	 *
	 *   Question here is: why adding "2 * ISP_VEC_NELEMS"
	 *   to "width_padded", but not making "width_padded"
	 *   aligned with "2 * ISP_VEC_NELEMS"?
	 */
	/* The changes will be reverted as soon as RAW
	 * Buffers are deployed by the 2401 Input System
	 * in the non-continuous use scenario.
	 */
	width_padded = width + (2 * ISP_VEC_NELEMS);
	/* end of NOTE */

	IA_CSS_ENTER("padded_width=%d, height=%d, format=%d\n",
		     width_padded, height, format);

	bits_per_pixel = sh_css_stream_format_2_bits_per_subpixel(format);
	bits_per_pixel =
	(format == ATOMISP_INPUT_FORMAT_RAW_10 && pack_raw_pixels) ? bits_per_pixel : 16;
	if (bits_per_pixel == 0)
		return -EINVAL;

	odd_line_bytes = (width_padded * bits_per_pixel + 7) >> 3; /* ceil ( bits per line / 8) */

	/* Even lines for YUV420 formats are double in bits_per_pixel. */
	if (format == ATOMISP_INPUT_FORMAT_YUV420_8
	    || format == ATOMISP_INPUT_FORMAT_YUV420_10)
	{
		even_line_bytes = (width_padded * 2 * bits_per_pixel + 7) >>
			3; /* ceil ( bits per line / 8) */
	} else
	{
		even_line_bytes = odd_line_bytes;
	}

	words_per_odd_line	 = (odd_line_bytes   + 3) >> 2;
	/* ceil(odd_line_bytes/4); word = 4 bytes */
	words_per_even_line  = (even_line_bytes  + 3) >> 2;

	mem_words_per_odd_line	 = (words_per_odd_line + 7) >> 3;
	/* ceil(words_per_odd_line/8); mem_word = 32 bytes, 8 words */
	mem_words_per_even_line  = (words_per_even_line + 7) >> 3;

	mem_words_per_buff_line =
	(mem_words_per_odd_line > mem_words_per_even_line) ? mem_words_per_odd_line : mem_words_per_even_line;
	mem_words_per_buff = mem_words_per_buff_line * height;

	*size_mem_words = mem_words_per_buff;

	IA_CSS_LEAVE_ERR(err);
#endif
	return err;
}

static bool buffers_needed(struct ia_css_pipe *pipe)
{
	if (!IS_ISP2401) {
		if (pipe->stream->config.mode == IA_CSS_INPUT_MODE_BUFFERED_SENSOR)
			return false;
		else
			return true;
	}

	if (pipe->stream->config.mode == IA_CSS_INPUT_MODE_BUFFERED_SENSOR ||
	    pipe->stream->config.mode == IA_CSS_INPUT_MODE_TPG ||
	    pipe->stream->config.mode == IA_CSS_INPUT_MODE_PRBS)
		return false;

	return true;
}

int
allocate_mipi_frames(struct ia_css_pipe *pipe,
		     struct ia_css_stream_info *info) {
	int err = -EINVAL;
	unsigned int port;

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
			    "allocate_mipi_frames(%p) enter:\n", pipe);

	assert(pipe);
	assert(pipe->stream);
	if ((!pipe) || (!pipe->stream))
	{
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
				    "allocate_mipi_frames(%p) exit: pipe or stream is null.\n",
				    pipe);
		return -EINVAL;
	}

#ifdef ISP2401
	if (pipe->stream->config.online)
	{
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
				    "allocate_mipi_frames(%p) exit: no buffers needed for 2401 pipe mode.\n",
				    pipe);
		return 0;
	}

#endif

	if (!buffers_needed(pipe)) {
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
				    "allocate_mipi_frames(%p) exit: no buffers needed for pipe mode.\n",
				    pipe);
		return 0; /* AM TODO: Check  */
	}

	if (!IS_ISP2401)
		port = (unsigned int)pipe->stream->config.source.port.port;
	else
		err = ia_css_mipi_is_source_port_valid(pipe, &port);

	assert(port < N_CSI_PORTS);

	if (port >= N_CSI_PORTS || err) {
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
				    "allocate_mipi_frames(%p) exit: error: port is not correct (port=%d).\n",
				    pipe, port);
		return -EINVAL;
	}

#ifdef ISP2401
	err = calculate_mipi_buff_size(
	    &pipe->stream->config,
	    &my_css.mipi_frame_size[port]);
#endif

#if !defined(ISP2401)
	if (ref_count_mipi_allocation[port] != 0)
	{
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
				    "allocate_mipi_frames(%p) exit: already allocated for this port (port=%d).\n",
				    pipe, port);
		return 0;
	}
#else
	/* 2401 system allows multiple streams to use same physical port. This is not
	 * true for 2400 system. Currently 2401 uses MIPI buffers as a temporary solution.
	 * TODO AM: Once that is changed (removed) this code should be removed as well.
	 * In that case only 2400 related code should remain.
	 */
	if (ref_count_mipi_allocation[port] != 0)
	{
		ref_count_mipi_allocation[port]++;
		ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
				    "allocate_mipi_frames(%p) leave: nothing to do, already allocated for this port (port=%d).\n",
				    pipe, port);
		return 0;
	}
#endif

	ref_count_mipi_allocation[port]++;

	/* AM TODO: mipi frames number should come from stream struct. */
	my_css.num_mipi_frames[port] = NUM_MIPI_FRAMES_PER_STREAM;

	/* Incremental allocation (per stream), not for all streams at once. */
	{ /* limit the scope of i,j */
		unsigned int i, j;

		for (i = 0; i < my_css.num_mipi_frames[port]; i++)
		{
			/* free previous frame */
			if (my_css.mipi_frames[port][i]) {
				ia_css_frame_free(my_css.mipi_frames[port][i]);
				my_css.mipi_frames[port][i] = NULL;
			}
			/* check if new frame is needed */
			if (i < my_css.num_mipi_frames[port]) {
				/* allocate new frame */
				err = ia_css_frame_allocate_with_buffer_size(
					  &my_css.mipi_frames[port][i],
					  my_css.mipi_frame_size[port] * HIVE_ISP_DDR_WORD_BYTES,
					  false);
				if (err) {
					for (j = 0; j < i; j++) {
						if (my_css.mipi_frames[port][j]) {
							ia_css_frame_free(my_css.mipi_frames[port][j]);
							my_css.mipi_frames[port][j] = NULL;
						}
					}
					ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
							    "allocate_mipi_frames(%p, %d) exit: error: allocation failed.\n",
							    pipe, port);
					return err;
				}
			}
			if (info->metadata_info.size > 0) {
				/* free previous metadata buffer */
				if (my_css.mipi_metadata[port][i]) {
					ia_css_metadata_free(my_css.mipi_metadata[port][i]);
					my_css.mipi_metadata[port][i] = NULL;
				}
				/* check if need to allocate a new metadata buffer */
				if (i < my_css.num_mipi_frames[port]) {
					/* allocate new metadata buffer */
					my_css.mipi_metadata[port][i] = ia_css_metadata_allocate(&info->metadata_info);
					if (!my_css.mipi_metadata[port][i]) {
						ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
								    "allocate_mipi_metadata(%p, %d) failed.\n",
								    pipe, port);
						return err;
					}
				}
			}
		}
	}
	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
			    "allocate_mipi_frames(%p) exit:\n", pipe);

	return err;
}

int
free_mipi_frames(struct ia_css_pipe *pipe) {
	int err = -EINVAL;
	unsigned int port;

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
			    "free_mipi_frames(%p) enter:\n", pipe);

	/* assert(pipe != NULL); TEMP: TODO: Should be assert only. */
	if (pipe)
	{
		assert(pipe->stream);
		if ((!pipe) || (!pipe->stream)) {
			ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
					    "free_mipi_frames(%p) exit: error: pipe or stream is null.\n",
					    pipe);
			return -EINVAL;
		}

		if (!buffers_needed(pipe)) {
			ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
					    "free_mipi_frames(%p) exit: error: wrong mode.\n",
					    pipe);
			return err;
		}

		if (!IS_ISP2401)
			port = (unsigned int)pipe->stream->config.source.port.port;
		else
			err = ia_css_mipi_is_source_port_valid(pipe, &port);

		assert(port < N_CSI_PORTS);

		if (port >= N_CSI_PORTS || err) {
			ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
					    "free_mipi_frames(%p, %d) exit: error: pipe port is not correct.\n",
					    pipe, port);
			return err;
		}

		if (ref_count_mipi_allocation[port] > 0) {
#if !defined(ISP2401)
			assert(ref_count_mipi_allocation[port] == 1);
			if (ref_count_mipi_allocation[port] != 1) {
				ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
						    "free_mipi_frames(%p) exit: error: wrong ref_count (ref_count=%d).\n",
						    pipe, ref_count_mipi_allocation[port]);
				return err;
			}
#endif

			ref_count_mipi_allocation[port]--;

			if (ref_count_mipi_allocation[port] == 0) {
				/* no streams are using this buffer, so free it */
				unsigned int i;

				for (i = 0; i < my_css.num_mipi_frames[port]; i++) {
					if (my_css.mipi_frames[port][i]) {
						ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
								    "free_mipi_frames(port=%d, num=%d).\n", port, i);
						ia_css_frame_free(my_css.mipi_frames[port][i]);
						my_css.mipi_frames[port][i] = NULL;
					}
					if (my_css.mipi_metadata[port][i]) {
						ia_css_metadata_free(my_css.mipi_metadata[port][i]);
						my_css.mipi_metadata[port][i] = NULL;
					}
				}

				ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
						    "free_mipi_frames(%p) exit (deallocated).\n", pipe);
			}
#if defined(ISP2401)
			else {
				/* 2401 system allows multiple streams to use same physical port. This is not
				 * true for 2400 system. Currently 2401 uses MIPI buffers as a temporary solution.
				 * TODO AM: Once that is changed (removed) this code should be removed as well.
				 * In that case only 2400 related code should remain.
				 */
				ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
						    "free_mipi_frames(%p) leave: nothing to do, other streams still use this port (port=%d).\n",
						    pipe, port);
			}
#endif
		}
	} else   /* pipe ==NULL */
	{
		/* AM TEMP: free-ing all mipi buffers just like a legacy code. */
		for (port = CSI_PORT0_ID; port < N_CSI_PORTS; port++) {
			unsigned int i;

			for (i = 0; i < my_css.num_mipi_frames[port]; i++) {
				if (my_css.mipi_frames[port][i]) {
					ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE_PRIVATE,
							    "free_mipi_frames(port=%d, num=%d).\n", port, i);
					ia_css_frame_free(my_css.mipi_frames[port][i]);
					my_css.mipi_frames[port][i] = NULL;
				}
				if (my_css.mipi_metadata[port][i]) {
					ia_css_metadata_free(my_css.mipi_metadata[port][i]);
					my_css.mipi_metadata[port][i] = NULL;
				}
			}
			ref_count_mipi_allocation[port] = 0;
		}
	}
	return 0;
}

int
send_mipi_frames(struct ia_css_pipe *pipe) {
	int err = -EINVAL;
	unsigned int i;
#ifndef ISP2401
	unsigned int port;
#else
	unsigned int port = 0;
#endif

	IA_CSS_ENTER_PRIVATE("pipe=%p", pipe);

	assert(pipe);
	assert(pipe->stream);
	if (!pipe || !pipe->stream)
	{
		IA_CSS_ERROR("pipe or stream is null");
		return -EINVAL;
	}

	/* multi stream video needs mipi buffers */
	/* nothing to be done in other cases. */
	if (!buffers_needed(pipe)) {
		IA_CSS_LOG("nothing to be done for this mode");
		return 0;
		/* TODO: AM: maybe this should be returning an error. */
	}

	if (!IS_ISP2401)
		port = (unsigned int)pipe->stream->config.source.port.port;
	else
		err = ia_css_mipi_is_source_port_valid(pipe, &port);

	assert(port < N_CSI_PORTS);

	if (port >= N_CSI_PORTS || err) {
		IA_CSS_ERROR("send_mipi_frames(%p) exit: invalid port specified (port=%d).\n",
			     pipe, port);
		return err;
	}

	/* Hand-over the SP-internal mipi buffers */
	for (i = 0; i < my_css.num_mipi_frames[port]; i++)
	{
		/* Need to include the ofset for port. */
		sh_css_update_host2sp_mipi_frame(port * NUM_MIPI_FRAMES_PER_STREAM + i,
						 my_css.mipi_frames[port][i]);
		sh_css_update_host2sp_mipi_metadata(port * NUM_MIPI_FRAMES_PER_STREAM + i,
						    my_css.mipi_metadata[port][i]);
	}
	sh_css_update_host2sp_num_mipi_frames(my_css.num_mipi_frames[port]);

	/**********************************
	 * Send an event to inform the SP
	 * that all MIPI frames are passed.
	 **********************************/
	if (!sh_css_sp_is_running())
	{
		/* SP is not running. The queues are not valid */
		IA_CSS_ERROR("sp is not running");
		return err;
	}

	ia_css_bufq_enqueue_psys_event(
	    IA_CSS_PSYS_SW_EVENT_MIPI_BUFFERS_READY,
	    (uint8_t)port,
	    (uint8_t)my_css.num_mipi_frames[port],
	    0 /* not used */);
	IA_CSS_LEAVE_ERR_PRIVATE(0);
	return 0;
}
