// 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_circbuf.h"

#include <assert_support.h>

/**********************************************************************
 *
 * Forward declarations.
 *
 **********************************************************************/
/*
 * @brief Read the oldest element from the circular buffer.
 * Read the oldest element WITHOUT checking whehter the
 * circular buffer is empty or not. The oldest element is
 * also removed out from the circular buffer.
 *
 * @param cb The pointer to the circular buffer.
 *
 * @return the oldest element.
 */
static inline ia_css_circbuf_elem_t
ia_css_circbuf_read(ia_css_circbuf_t *cb);

/*
 * @brief Shift a chunk of elements in the circular buffer.
 * A chunk of elements (i.e. the ones from the "start" position
 * to the "chunk_src" position) are shifted in the circular buffer,
 * along the direction of new elements coming.
 *
 * @param cb	     The pointer to the circular buffer.
 * @param chunk_src  The position at which the first element in the chunk is.
 * @param chunk_dest The position to which the first element in the chunk would be shift.
 */
static inline void ia_css_circbuf_shift_chunk(ia_css_circbuf_t *cb,
	u32 chunk_src,
	uint32_t chunk_dest);

/*
 * @brief Get the "val" field in the element.
 *
 * @param elem The pointer to the element.
 *
 * @return the "val" field.
 */
static inline uint32_t
ia_css_circbuf_elem_get_val(ia_css_circbuf_elem_t *elem);

/**********************************************************************
 *
 * Non-inline functions.
 *
 **********************************************************************/
/*
 * @brief Create the circular buffer.
 * Refer to "ia_css_circbuf.h" for details.
 */
void
ia_css_circbuf_create(ia_css_circbuf_t *cb,
		      ia_css_circbuf_elem_t *elems,
		      ia_css_circbuf_desc_t *desc)
{
	u32 i;

	OP___assert(desc);

	cb->desc = desc;
	/* Initialize to defaults */
	cb->desc->start = 0;
	cb->desc->end = 0;
	cb->desc->step = 0;

	for (i = 0; i < cb->desc->size; i++)
		ia_css_circbuf_elem_init(&elems[i]);

	cb->elems = elems;
}

/*
 * @brief Destroy the circular buffer.
 * Refer to "ia_css_circbuf.h" for details.
 */
void ia_css_circbuf_destroy(ia_css_circbuf_t *cb)
{
	cb->desc = NULL;

	cb->elems = NULL;
}

/*
 * @brief Pop a value out of the circular buffer.
 * Refer to "ia_css_circbuf.h" for details.
 */
uint32_t ia_css_circbuf_pop(ia_css_circbuf_t *cb)
{
	u32 ret;
	ia_css_circbuf_elem_t elem;

	assert(!ia_css_circbuf_is_empty(cb));

	/* read an element from the buffer */
	elem = ia_css_circbuf_read(cb);
	ret = ia_css_circbuf_elem_get_val(&elem);
	return ret;
}

/*
 * @brief Extract a value out of the circular buffer.
 * Refer to "ia_css_circbuf.h" for details.
 */
uint32_t ia_css_circbuf_extract(ia_css_circbuf_t *cb, int offset)
{
	int max_offset;
	u32 val;
	u32 pos;
	u32 src_pos;
	u32 dest_pos;

	/* get the maximum offest */
	max_offset = ia_css_circbuf_get_offset(cb, cb->desc->start, cb->desc->end);
	max_offset--;

	/*
	 * Step 1: When the target element is at the "start" position.
	 */
	if (offset == 0) {
		val = ia_css_circbuf_pop(cb);
		return val;
	}

	/*
	 * Step 2: When the target element is out of the range.
	 */
	if (offset > max_offset) {
		val = 0;
		return val;
	}

	/*
	 * Step 3: When the target element is between the "start" and
	 * "end" position.
	 */
	/* get the position of the target element */
	pos = ia_css_circbuf_get_pos_at_offset(cb, cb->desc->start, offset);

	/* get the value from the target element */
	val = ia_css_circbuf_elem_get_val(&cb->elems[pos]);

	/* shift the elements */
	src_pos = ia_css_circbuf_get_pos_at_offset(cb, pos, -1);
	dest_pos = pos;
	ia_css_circbuf_shift_chunk(cb, src_pos, dest_pos);

	return val;
}

/*
 * @brief Peek an element from the circular buffer.
 * Refer to "ia_css_circbuf.h" for details.
 */
uint32_t ia_css_circbuf_peek(ia_css_circbuf_t *cb, int offset)
{
	int pos;

	pos = ia_css_circbuf_get_pos_at_offset(cb, cb->desc->end, offset);

	/* get the value at the position */
	return cb->elems[pos].val;
}

/*
 * @brief Get the value of an element from the circular buffer.
 * Refer to "ia_css_circbuf.h" for details.
 */
uint32_t ia_css_circbuf_peek_from_start(ia_css_circbuf_t *cb, int offset)
{
	int pos;

	pos = ia_css_circbuf_get_pos_at_offset(cb, cb->desc->start, offset);

	/* get the value at the position */
	return cb->elems[pos].val;
}

/* @brief increase size of a circular buffer.
 * Use 'CAUTION' before using this function. This was added to
 * support / fix issue with increasing size for tagger only
 * Please refer to "ia_css_circbuf.h" for details.
 */
bool ia_css_circbuf_increase_size(
    ia_css_circbuf_t *cb,
    unsigned int sz_delta,
    ia_css_circbuf_elem_t *elems)
{
	u8 curr_size;
	u8 curr_end;
	unsigned int i = 0;

	if (!cb || sz_delta == 0)
		return false;

	curr_size = cb->desc->size;
	curr_end = cb->desc->end;
	/* We assume cb was pre defined as global to allow
	 * increase in size */
	/* FM: are we sure this cannot cause size to become too big? */
	if (((uint8_t)(cb->desc->size + (uint8_t)sz_delta) > cb->desc->size) &&
	    ((uint8_t)sz_delta == sz_delta))
		cb->desc->size += (uint8_t)sz_delta;
	else
		return false; /* overflow in size */

	/* If elems are passed update them else we assume its been taken
	 * care before calling this function */
	if (elems) {
		/* cb element array size will not be increased dynamically,
		 * but pointers to new elements can be added at the end
		 * of existing pre defined cb element array of
		 * size >= new size if not already added */
		for (i = curr_size; i <  cb->desc->size; i++)
			cb->elems[i] = elems[i - curr_size];
	}
	/* Fix Start / End */
	if (curr_end < cb->desc->start) {
		if (curr_end == 0) {
			/* Easily fix End */
			cb->desc->end = curr_size;
		} else {
			/* Move elements and fix Start*/
			ia_css_circbuf_shift_chunk(cb,
						   curr_size - 1,
						   curr_size + sz_delta - 1);
		}
	}

	return true;
}

/****************************************************************
 *
 * Inline functions.
 *
 ****************************************************************/
/*
 * @brief Get the "val" field in the element.
 * Refer to "Forward declarations" for details.
 */
static inline uint32_t
ia_css_circbuf_elem_get_val(ia_css_circbuf_elem_t *elem)
{
	return elem->val;
}

/*
 * @brief Read the oldest element from the circular buffer.
 * Refer to "Forward declarations" for details.
 */
static inline ia_css_circbuf_elem_t
ia_css_circbuf_read(ia_css_circbuf_t *cb)
{
	ia_css_circbuf_elem_t elem;

	/* get the element from the target position */
	elem = cb->elems[cb->desc->start];

	/* clear the target position */
	ia_css_circbuf_elem_init(&cb->elems[cb->desc->start]);

	/* adjust the "start" position */
	cb->desc->start = ia_css_circbuf_get_pos_at_offset(cb, cb->desc->start, 1);
	return elem;
}

/*
 * @brief Shift a chunk of elements in the circular buffer.
 * Refer to "Forward declarations" for details.
 */
static inline void
ia_css_circbuf_shift_chunk(ia_css_circbuf_t *cb,
			   u32 chunk_src, uint32_t chunk_dest)
{
	int chunk_offset;
	int chunk_sz;
	int i;

	/* get the chunk offset and size */
	chunk_offset = ia_css_circbuf_get_offset(cb,
		       chunk_src, chunk_dest);
	chunk_sz = ia_css_circbuf_get_offset(cb, cb->desc->start, chunk_src) + 1;

	/* shift each element to its terminal position */
	for (i = 0; i < chunk_sz; i++) {
		/* copy the element from the source to the destination */
		ia_css_circbuf_elem_cpy(&cb->elems[chunk_src],
					&cb->elems[chunk_dest]);

		/* clear the source position */
		ia_css_circbuf_elem_init(&cb->elems[chunk_src]);

		/* adjust the source/terminal positions */
		chunk_src = ia_css_circbuf_get_pos_at_offset(cb, chunk_src, -1);
		chunk_dest = ia_css_circbuf_get_pos_at_offset(cb, chunk_dest, -1);
	}

	/* adjust the index "start" */
	cb->desc->start = ia_css_circbuf_get_pos_at_offset(cb, cb->desc->start,
			  chunk_offset);
}