// SPDX-License-Identifier: GPL-2.0
/*
 * Support for Intel Camera Imaging ISP subsystem.
 * Copyright (c) 2010 - 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 "system_global.h"
#include <linux/kernel.h>

#ifndef ISP2401

#include "ia_css_ifmtr.h"
#include <math_support.h>
#include "sh_css_internal.h"
#include "input_formatter.h"
#include "assert_support.h"
#include "sh_css_sp.h"
#include "isp/modes/interface/input_buf.isp.h"

/************************************************************
 * Static functions declarations
 ************************************************************/
static int ifmtr_start_column(
    const struct ia_css_stream_config *config,
    unsigned int bin_in,
    unsigned int *start_column);

static int ifmtr_input_start_line(
    const struct ia_css_stream_config *config,
    unsigned int bin_in,
    unsigned int *start_line);

static void ifmtr_set_if_blocking_mode(
    const input_formatter_cfg_t *const config_a,
    const input_formatter_cfg_t *const config_b);

/************************************************************
 * Public functions
 ************************************************************/

/* ISP expects GRBG bayer order, we skip one line and/or one row
 * to correct in case the input bayer order is different.
 */
unsigned int ia_css_ifmtr_lines_needed_for_bayer_order(
    const struct ia_css_stream_config *config)
{
	assert(config);
	if ((config->input_config.bayer_order == IA_CSS_BAYER_ORDER_BGGR)
	    || (config->input_config.bayer_order == IA_CSS_BAYER_ORDER_GBRG))
		return 1;

	return 0;
}

unsigned int ia_css_ifmtr_columns_needed_for_bayer_order(
    const struct ia_css_stream_config *config)
{
	assert(config);
	if ((config->input_config.bayer_order == IA_CSS_BAYER_ORDER_RGGB)
	    || (config->input_config.bayer_order == IA_CSS_BAYER_ORDER_GBRG))
		return 1;

	return 0;
}

int ia_css_ifmtr_configure(struct ia_css_stream_config *config,
				       struct ia_css_binary *binary)
{
	unsigned int start_line, start_column = 0,
				 cropped_height,
				 cropped_width,
				 num_vectors,
				 buffer_height = 2,
				 buffer_width,
				 two_ppc,
				 vmem_increment = 0,
				 deinterleaving = 0,
				 deinterleaving_b = 0,
				 width_a = 0,
				 width_b = 0,
				 bits_per_pixel,
				 vectors_per_buffer,
				 vectors_per_line = 0,
				 buffers_per_line = 0,
				 buf_offset_a = 0,
				 buf_offset_b = 0,
				 line_width = 0,
				 width_b_factor = 1, start_column_b,
				 left_padding = 0;
	input_formatter_cfg_t if_a_config, if_b_config;
	enum atomisp_input_format input_format;
	int err = 0;
	u8 if_config_index;

	/* Determine which input formatter config set is targeted. */
	/* Index is equal to the CSI-2 port used. */
	enum mipi_port_id port;

	if (binary) {
		cropped_height = binary->in_frame_info.res.height;
		cropped_width = binary->in_frame_info.res.width;
		/* This should correspond to the input buffer definition for
		ISP binaries in input_buf.isp.h */
		if (binary->info->sp.enable.continuous &&
		    binary->info->sp.pipeline.mode != IA_CSS_BINARY_MODE_COPY)
			buffer_width = MAX_VECTORS_PER_INPUT_LINE_CONT * ISP_VEC_NELEMS;
		else
			buffer_width = binary->info->sp.input.max_width;
		input_format = binary->input_format;
	} else {
		/* sp raw copy pipe (IA_CSS_PIPE_MODE_COPY): binary is NULL */
		cropped_height = config->input_config.input_res.height;
		cropped_width = config->input_config.input_res.width;
		buffer_width = MAX_VECTORS_PER_INPUT_LINE_CONT * ISP_VEC_NELEMS;
		input_format = config->input_config.format;
	}
	two_ppc = config->pixels_per_clock == 2;
	if (config->mode == IA_CSS_INPUT_MODE_SENSOR
	    || config->mode == IA_CSS_INPUT_MODE_BUFFERED_SENSOR) {
		port = config->source.port.port;
		if_config_index = (uint8_t)(port - MIPI_PORT0_ID);
	} else if (config->mode == IA_CSS_INPUT_MODE_MEMORY) {
		if_config_index = SH_CSS_IF_CONFIG_NOT_NEEDED;
	} else {
		if_config_index = 0;
	}

	assert(if_config_index <= SH_CSS_MAX_IF_CONFIGS
	       || if_config_index == SH_CSS_IF_CONFIG_NOT_NEEDED);

	/* TODO: check to see if input is RAW and if current mode interprets
	 * RAW data in any particular bayer order. copy binary with output
	 * format other than raw should not result in dropping lines and/or
	 * columns.
	 */
	err = ifmtr_input_start_line(config, cropped_height, &start_line);
	if (err)
		return err;
	err = ifmtr_start_column(config, cropped_width, &start_column);
	if (err)
		return err;

	if (config->left_padding == -1)
		if (!binary)
			/* sp raw copy pipe: set left_padding value */
			left_padding = 0;
		else
			left_padding = binary->left_padding;
	else
		left_padding = 2 * ISP_VEC_NELEMS - config->left_padding;

	if (left_padding) {
		num_vectors = CEIL_DIV(cropped_width + left_padding,
				       ISP_VEC_NELEMS);
	} else {
		num_vectors = CEIL_DIV(cropped_width, ISP_VEC_NELEMS);
		num_vectors *= buffer_height;
		/* todo: in case of left padding,
		   num_vectors is vectors per line,
		   otherwise vectors per line * buffer_height. */
	}

	start_column_b = start_column;

	bits_per_pixel = input_formatter_get_alignment(INPUT_FORMATTER0_ID)
			 * 8 / ISP_VEC_NELEMS;
	switch (input_format) {
	case ATOMISP_INPUT_FORMAT_YUV420_8_LEGACY:
		if (two_ppc) {
			vmem_increment = 1;
			deinterleaving = 1;
			deinterleaving_b = 1;
			/* half lines */
			width_a = cropped_width * deinterleaving / 2;
			width_b_factor = 2;
			/* full lines */
			width_b = width_a * width_b_factor;
			buffer_width *= deinterleaving * 2;
			/* Patch from bayer to yuv */
			num_vectors *= deinterleaving;
			buf_offset_b = buffer_width / 2 / ISP_VEC_NELEMS;
			vectors_per_line = num_vectors / buffer_height;
			/* Even lines are half size */
			line_width = vectors_per_line *
				     input_formatter_get_alignment(INPUT_FORMATTER0_ID) /
				     2;
			start_column /= 2;
		} else {
			vmem_increment = 1;
			deinterleaving = 3;
			width_a = cropped_width * deinterleaving / 2;
			buffer_width = buffer_width * deinterleaving / 2;
			/* Patch from bayer to yuv */
			num_vectors = num_vectors / 2 * deinterleaving;
			start_column = start_column * deinterleaving / 2;
		}
		break;
	case ATOMISP_INPUT_FORMAT_YUV420_8:
	case ATOMISP_INPUT_FORMAT_YUV420_10:
	case ATOMISP_INPUT_FORMAT_YUV420_16:
		if (two_ppc) {
			vmem_increment = 1;
			deinterleaving = 1;
			width_a = width_b = cropped_width * deinterleaving / 2;
			buffer_width *= deinterleaving * 2;
			num_vectors *= deinterleaving;
			buf_offset_b = buffer_width / 2 / ISP_VEC_NELEMS;
			vectors_per_line = num_vectors / buffer_height;
			/* Even lines are half size */
			line_width = vectors_per_line *
				     input_formatter_get_alignment(INPUT_FORMATTER0_ID) /
				     2;
			start_column *= deinterleaving;
			start_column /= 2;
			start_column_b = start_column;
		} else {
			vmem_increment = 1;
			deinterleaving = 1;
			width_a = cropped_width * deinterleaving;
			buffer_width *= deinterleaving * 2;
			num_vectors *= deinterleaving;
			start_column *= deinterleaving;
		}
		break;
	case ATOMISP_INPUT_FORMAT_YUV422_8:
	case ATOMISP_INPUT_FORMAT_YUV422_10:
	case ATOMISP_INPUT_FORMAT_YUV422_16:
		if (two_ppc) {
			vmem_increment = 1;
			deinterleaving = 1;
			width_a = width_b = cropped_width * deinterleaving;
			buffer_width *= deinterleaving * 2;
			num_vectors *= deinterleaving;
			start_column *= deinterleaving;
			buf_offset_b = buffer_width / 2 / ISP_VEC_NELEMS;
			start_column_b = start_column;
		} else {
			vmem_increment = 1;
			deinterleaving = 2;
			width_a = cropped_width * deinterleaving;
			buffer_width *= deinterleaving;
			num_vectors *= deinterleaving;
			start_column *= deinterleaving;
		}
		break;
	case ATOMISP_INPUT_FORMAT_RGB_444:
	case ATOMISP_INPUT_FORMAT_RGB_555:
	case ATOMISP_INPUT_FORMAT_RGB_565:
	case ATOMISP_INPUT_FORMAT_RGB_666:
	case ATOMISP_INPUT_FORMAT_RGB_888:
		num_vectors *= 2;
		if (two_ppc) {
			deinterleaving = 2;	/* BR in if_a, G in if_b */
			deinterleaving_b = 1;	/* BR in if_a, G in if_b */
			buffers_per_line = 4;
			start_column_b = start_column;
			start_column *= deinterleaving;
			start_column_b *= deinterleaving_b;
		} else {
			deinterleaving = 3;	/* BGR */
			buffers_per_line = 3;
			start_column *= deinterleaving;
		}
		vmem_increment = 1;
		width_a = cropped_width * deinterleaving;
		width_b = cropped_width * deinterleaving_b;
		buffer_width *= buffers_per_line;
		/* Patch from bayer to rgb */
		num_vectors = num_vectors / 2 * deinterleaving;
		buf_offset_b = buffer_width / 2 / ISP_VEC_NELEMS;
		break;
	case ATOMISP_INPUT_FORMAT_RAW_6:
	case ATOMISP_INPUT_FORMAT_RAW_7:
	case ATOMISP_INPUT_FORMAT_RAW_8:
	case ATOMISP_INPUT_FORMAT_RAW_10:
	case ATOMISP_INPUT_FORMAT_RAW_12:
		if (two_ppc) {
			int crop_col = (start_column % 2) == 1;

			vmem_increment = 2;
			deinterleaving = 1;
			width_a = width_b = cropped_width / 2;

			/* When two_ppc is enabled AND we need to crop one extra
			 * column, if_a crops by one extra and we swap the
			 * output offsets to interleave the bayer pattern in
			 * the correct order.
			 */
			buf_offset_a   = crop_col ? 1 : 0;
			buf_offset_b   = crop_col ? 0 : 1;
			start_column_b = start_column / 2;
			start_column   = start_column / 2 + crop_col;
		} else {
			vmem_increment = 1;
			deinterleaving = 2;
			if ((!binary) || (config->continuous && binary
					  && binary->info->sp.pipeline.mode == IA_CSS_BINARY_MODE_COPY)) {
				/* !binary -> sp raw copy pipe, no deinterleaving */
				deinterleaving = 1;
			}
			width_a = cropped_width;
			/* Must be multiple of deinterleaving */
			num_vectors = CEIL_MUL(num_vectors, deinterleaving);
		}
		buffer_height *= 2;
		if ((!binary) || config->continuous)
			/* !binary -> sp raw copy pipe */
			buffer_height *= 2;
		vectors_per_line = CEIL_DIV(cropped_width, ISP_VEC_NELEMS);
		vectors_per_line = CEIL_MUL(vectors_per_line, deinterleaving);
		break;
	case ATOMISP_INPUT_FORMAT_RAW_14:
	case ATOMISP_INPUT_FORMAT_RAW_16:
		if (two_ppc) {
			num_vectors *= 2;
			vmem_increment = 1;
			deinterleaving = 2;
			width_a = width_b = cropped_width;
			/* B buffer is one line further */
			buf_offset_b = buffer_width / ISP_VEC_NELEMS;
			bits_per_pixel *= 2;
		} else {
			vmem_increment = 1;
			deinterleaving = 2;
			width_a = cropped_width;
			start_column /= deinterleaving;
		}
		buffer_height *= 2;
		break;
	case ATOMISP_INPUT_FORMAT_BINARY_8:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT1:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT2:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT3:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT4:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT5:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT6:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT7:
	case ATOMISP_INPUT_FORMAT_GENERIC_SHORT8:
	case ATOMISP_INPUT_FORMAT_YUV420_8_SHIFT:
	case ATOMISP_INPUT_FORMAT_YUV420_10_SHIFT:
	case ATOMISP_INPUT_FORMAT_EMBEDDED:
	case ATOMISP_INPUT_FORMAT_USER_DEF1:
	case ATOMISP_INPUT_FORMAT_USER_DEF2:
	case ATOMISP_INPUT_FORMAT_USER_DEF3:
	case ATOMISP_INPUT_FORMAT_USER_DEF4:
	case ATOMISP_INPUT_FORMAT_USER_DEF5:
	case ATOMISP_INPUT_FORMAT_USER_DEF6:
	case ATOMISP_INPUT_FORMAT_USER_DEF7:
	case ATOMISP_INPUT_FORMAT_USER_DEF8:
		break;
	}
	if (width_a == 0)
		return -EINVAL;

	if (two_ppc)
		left_padding /= 2;

	/* Default values */
	if (left_padding)
		vectors_per_line = num_vectors;
	if (!vectors_per_line) {
		vectors_per_line = CEIL_MUL(num_vectors / buffer_height,
					    deinterleaving);
		line_width = 0;
	}
	if (!line_width)
		line_width = vectors_per_line *
			     input_formatter_get_alignment(INPUT_FORMATTER0_ID);
	if (!buffers_per_line)
		buffers_per_line = deinterleaving;
	line_width = CEIL_MUL(line_width,
			      input_formatter_get_alignment(INPUT_FORMATTER0_ID)
			      * vmem_increment);

	vectors_per_buffer = buffer_height * buffer_width / ISP_VEC_NELEMS;

	if (config->mode == IA_CSS_INPUT_MODE_TPG &&
	    ((binary && binary->info->sp.pipeline.mode == IA_CSS_BINARY_MODE_VIDEO) ||
	     (!binary))) {
		/* !binary -> sp raw copy pipe */
		/* workaround for TPG in video mode */
		start_line = 0;
		start_column = 0;
		cropped_height -= start_line;
		width_a -= start_column;
	}

	if_a_config.start_line = start_line;
	if_a_config.start_column = start_column;
	if_a_config.left_padding = left_padding / deinterleaving;
	if_a_config.cropped_height = cropped_height;
	if_a_config.cropped_width = width_a;
	if_a_config.deinterleaving = deinterleaving;
	if_a_config.buf_vecs = vectors_per_buffer;
	if_a_config.buf_start_index = buf_offset_a;
	if_a_config.buf_increment = vmem_increment;
	if_a_config.buf_eol_offset =
	    buffer_width * bits_per_pixel / 8 - line_width;
	if_a_config.is_yuv420_format =
	    (input_format == ATOMISP_INPUT_FORMAT_YUV420_8)
	    || (input_format == ATOMISP_INPUT_FORMAT_YUV420_10)
	    || (input_format == ATOMISP_INPUT_FORMAT_YUV420_16);
	if_a_config.block_no_reqs = (config->mode != IA_CSS_INPUT_MODE_SENSOR);

	if (two_ppc) {
		if (deinterleaving_b) {
			deinterleaving = deinterleaving_b;
			width_b = cropped_width * deinterleaving;
			buffer_width *= deinterleaving;
			/* Patch from bayer to rgb */
			num_vectors = num_vectors / 2 *
				      deinterleaving * width_b_factor;
			vectors_per_line = num_vectors / buffer_height;
			line_width = vectors_per_line *
				     input_formatter_get_alignment(INPUT_FORMATTER0_ID);
		}
		if_b_config.start_line = start_line;
		if_b_config.start_column = start_column_b;
		if_b_config.left_padding = left_padding / deinterleaving;
		if_b_config.cropped_height = cropped_height;
		if_b_config.cropped_width = width_b;
		if_b_config.deinterleaving = deinterleaving;
		if_b_config.buf_vecs = vectors_per_buffer;
		if_b_config.buf_start_index = buf_offset_b;
		if_b_config.buf_increment = vmem_increment;
		if_b_config.buf_eol_offset =
		    buffer_width * bits_per_pixel / 8 - line_width;
		if_b_config.is_yuv420_format =
		    input_format == ATOMISP_INPUT_FORMAT_YUV420_8
		    || input_format == ATOMISP_INPUT_FORMAT_YUV420_10
		    || input_format == ATOMISP_INPUT_FORMAT_YUV420_16;
		if_b_config.block_no_reqs =
		    (config->mode != IA_CSS_INPUT_MODE_SENSOR);

		if (if_config_index != SH_CSS_IF_CONFIG_NOT_NEEDED) {
			assert(if_config_index <= SH_CSS_MAX_IF_CONFIGS);

			ifmtr_set_if_blocking_mode(&if_a_config, &if_b_config);
			/* Set the ifconfigs to SP group */
			sh_css_sp_set_if_configs(&if_a_config, &if_b_config,
						 if_config_index);
		}
	} else {
		if (if_config_index != SH_CSS_IF_CONFIG_NOT_NEEDED) {
			assert(if_config_index <= SH_CSS_MAX_IF_CONFIGS);

			ifmtr_set_if_blocking_mode(&if_a_config, NULL);
			/* Set the ifconfigs to SP group */
			sh_css_sp_set_if_configs(&if_a_config, NULL,
						 if_config_index);
		}
	}

	return 0;
}

bool ifmtr_set_if_blocking_mode_reset = true;

/************************************************************
 * Static functions
 ************************************************************/
static void ifmtr_set_if_blocking_mode(
    const input_formatter_cfg_t *const config_a,
    const input_formatter_cfg_t *const config_b)
{
	int i;
	bool block[] = { false, false, false, false };

	assert(N_INPUT_FORMATTER_ID <= (ARRAY_SIZE(block)));

	block[INPUT_FORMATTER0_ID] = (bool)config_a->block_no_reqs;
	if (config_b)
		block[INPUT_FORMATTER1_ID] = (bool)config_b->block_no_reqs;

	/* TODO: next could cause issues when streams are started after
	 * eachother. */
	/*IF should not be reconfigured/reset from host */
	if (ifmtr_set_if_blocking_mode_reset) {
		ifmtr_set_if_blocking_mode_reset = false;
		for (i = 0; i < N_INPUT_FORMATTER_ID; i++) {
			input_formatter_ID_t id = (input_formatter_ID_t)i;

			input_formatter_rst(id);
			input_formatter_set_fifo_blocking_mode(id, block[id]);
		}
	}

	return;
}

static int ifmtr_start_column(
    const struct ia_css_stream_config *config,
    unsigned int bin_in,
    unsigned int *start_column)
{
	unsigned int in = config->input_config.input_res.width, start,
		     for_bayer = ia_css_ifmtr_columns_needed_for_bayer_order(config);

	if (bin_in + 2 * for_bayer > in)
		return -EINVAL;

	/* On the hardware, we want to use the middle of the input, so we
	 * divide the start column by 2. */
	start = (in - bin_in) / 2;
	/* in case the number of extra columns is 2 or odd, we round the start
	 * column down */
	start &= ~0x1;

	/* now we add the one column (if needed) to correct for the bayer
	 * order).
	 */
	start += for_bayer;
	*start_column = start;
	return 0;
}

static int ifmtr_input_start_line(
    const struct ia_css_stream_config *config,
    unsigned int bin_in,
    unsigned int *start_line)
{
	unsigned int in = config->input_config.input_res.height, start,
		     for_bayer = ia_css_ifmtr_lines_needed_for_bayer_order(config);

	if (bin_in + 2 * for_bayer > in)
		return -EINVAL;

	/* On the hardware, we want to use the middle of the input, so we
	 * divide the start line by 2. On the simulator, we cannot handle extra
	 * lines at the end of the frame.
	 */
	start = (in - bin_in) / 2;
	/* in case the number of extra lines is 2 or odd, we round the start
	 * line down.
	 */
	start &= ~0x1;

	/* now we add the one line (if needed) to correct for the bayer order */
	start += for_bayer;
	*start_line = start;
	return 0;
}

#endif
