/* 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.
 */

#ifndef _IA_CSS_CIRCBUF_H
#define _IA_CSS_CIRCBUF_H

#include <sp.h>
#include <type_support.h>
#include <math_support.h>
#include <assert_support.h>
#include <platform_support.h>
#include "ia_css_circbuf_comm.h"
#include "ia_css_circbuf_desc.h"

/****************************************************************
 *
 * Data structures.
 *
 ****************************************************************/
/**
 * @brief Data structure for the circular buffer.
 */
typedef struct ia_css_circbuf_s ia_css_circbuf_t;
struct ia_css_circbuf_s {
	ia_css_circbuf_desc_t *desc;    /* Pointer to the descriptor of the circbuf */
	ia_css_circbuf_elem_t *elems;	/* an array of elements    */
};

/**
 * @brief Create the circular buffer.
 *
 * @param cb	The pointer to the circular buffer.
 * @param elems	An array of elements.
 * @param desc	The descriptor set to the size using ia_css_circbuf_desc_init().
 */
void ia_css_circbuf_create(
    ia_css_circbuf_t *cb,
    ia_css_circbuf_elem_t *elems,
    ia_css_circbuf_desc_t *desc);

/**
 * @brief Destroy the circular buffer.
 *
 * @param cb The pointer to the circular buffer.
 */
void ia_css_circbuf_destroy(
    ia_css_circbuf_t *cb);

/**
 * @brief Pop a value out of the circular buffer.
 * Get a value at the head of the circular buffer.
 * The user should call "ia_css_circbuf_is_empty()"
 * to avoid accessing to an empty buffer.
 *
 * @param cb	The pointer to the circular buffer.
 *
 * @return the pop-out value.
 */
uint32_t ia_css_circbuf_pop(
    ia_css_circbuf_t *cb);

/**
 * @brief Extract a value out of the circular buffer.
 * Get a value at an arbitrary poistion in the circular
 * buffer. The user should call "ia_css_circbuf_is_empty()"
 * to avoid accessing to an empty buffer.
 *
 * @param cb	 The pointer to the circular buffer.
 * @param offset The offset from "start" to the target position.
 *
 * @return the extracted value.
 */
uint32_t ia_css_circbuf_extract(
    ia_css_circbuf_t *cb,
    int offset);

/****************************************************************
 *
 * Inline functions.
 *
 ****************************************************************/
/**
 * @brief Set the "val" field in the element.
 *
 * @param elem The pointer to the element.
 * @param val  The value to be set.
 */
static inline void ia_css_circbuf_elem_set_val(
    ia_css_circbuf_elem_t *elem,
    uint32_t val)
{
	OP___assert(elem);

	elem->val = val;
}

/**
 * @brief Initialize the element.
 *
 * @param elem The pointer to the element.
 */
static inline void ia_css_circbuf_elem_init(
    ia_css_circbuf_elem_t *elem)
{
	OP___assert(elem);
	ia_css_circbuf_elem_set_val(elem, 0);
}

/**
 * @brief Copy an element.
 *
 * @param src  The element as the copy source.
 * @param dest The element as the copy destination.
 */
static inline void ia_css_circbuf_elem_cpy(
    ia_css_circbuf_elem_t *src,
    ia_css_circbuf_elem_t *dest)
{
	OP___assert(src);
	OP___assert(dest);

	ia_css_circbuf_elem_set_val(dest, src->val);
}

/**
 * @brief Get position in the circular buffer.
 *
 * @param cb		The pointer to the circular buffer.
 * @param base		The base position.
 * @param offset	The offset.
 *
 * @return the position at offset.
 */
static inline uint8_t ia_css_circbuf_get_pos_at_offset(
    ia_css_circbuf_t *cb,
    u32 base,
    int offset)
{
	u8 dest;

	OP___assert(cb);
	OP___assert(cb->desc);
	OP___assert(cb->desc->size > 0);

	/* step 1: adjudst the offset  */
	while (offset < 0) {
		offset += cb->desc->size;
	}

	/* step 2: shift and round by the upper limit */
	dest = OP_std_modadd(base, offset, cb->desc->size);

	return dest;
}

/**
 * @brief Get the offset between two positions in the circular buffer.
 * Get the offset from the source position to the terminal position,
 * along the direction in which the new elements come in.
 *
 * @param cb		The pointer to the circular buffer.
 * @param src_pos	The source position.
 * @param dest_pos	The terminal position.
 *
 * @return the offset.
 */
static inline int ia_css_circbuf_get_offset(
    ia_css_circbuf_t *cb,
    u32 src_pos,
    uint32_t dest_pos)
{
	int offset;

	OP___assert(cb);
	OP___assert(cb->desc);

	offset = (int)(dest_pos - src_pos);
	offset += (offset < 0) ? cb->desc->size : 0;

	return offset;
}

/**
 * @brief Get the maximum number of elements.
 *
 * @param cb The pointer to the circular buffer.
 *
 * @return the maximum number of elements.
 *
 * TODO: Test this API.
 */
static inline uint32_t ia_css_circbuf_get_size(
    ia_css_circbuf_t *cb)
{
	OP___assert(cb);
	OP___assert(cb->desc);

	return cb->desc->size;
}

/**
 * @brief Get the number of available elements.
 *
 * @param cb The pointer to the circular buffer.
 *
 * @return the number of available elements.
 */
static inline uint32_t ia_css_circbuf_get_num_elems(
    ia_css_circbuf_t *cb)
{
	int num;

	OP___assert(cb);
	OP___assert(cb->desc);

	num = ia_css_circbuf_get_offset(cb, cb->desc->start, cb->desc->end);

	return (uint32_t)num;
}

/**
 * @brief Test if the circular buffer is empty.
 *
 * @param cb	The pointer to the circular buffer.
 *
 * @return
 *	- true when it is empty.
 *	- false when it is not empty.
 */
static inline bool ia_css_circbuf_is_empty(
    ia_css_circbuf_t *cb)
{
	OP___assert(cb);
	OP___assert(cb->desc);

	return ia_css_circbuf_desc_is_empty(cb->desc);
}

/**
 * @brief Test if the circular buffer is full.
 *
 * @param cb	The pointer to the circular buffer.
 *
 * @return
 *	- true when it is full.
 *	- false when it is not full.
 */
static inline bool ia_css_circbuf_is_full(ia_css_circbuf_t *cb)
{
	OP___assert(cb);
	OP___assert(cb->desc);

	return ia_css_circbuf_desc_is_full(cb->desc);
}

/**
 * @brief Write a new element into the circular buffer.
 * Write a new element WITHOUT checking whether the
 * circular buffer is full or not. So it also overwrites
 * the oldest element when the buffer is full.
 *
 * @param cb	The pointer to the circular buffer.
 * @param elem	The new element.
 */
static inline void ia_css_circbuf_write(
    ia_css_circbuf_t *cb,
    ia_css_circbuf_elem_t elem)
{
	OP___assert(cb);
	OP___assert(cb->desc);

	/* Cannot continue as the queue is full*/
	assert(!ia_css_circbuf_is_full(cb));

	ia_css_circbuf_elem_cpy(&elem, &cb->elems[cb->desc->end]);

	cb->desc->end = ia_css_circbuf_get_pos_at_offset(cb, cb->desc->end, 1);
}

/**
 * @brief Push a value in the circular buffer.
 * Put a new value at the tail of the circular buffer.
 * The user should call "ia_css_circbuf_is_full()"
 * to avoid accessing to a full buffer.
 *
 * @param cb	The pointer to the circular buffer.
 * @param val	The value to be pushed in.
 */
static inline void ia_css_circbuf_push(
    ia_css_circbuf_t *cb,
    uint32_t val)
{
	ia_css_circbuf_elem_t elem;

	OP___assert(cb);

	/* set up an element */
	ia_css_circbuf_elem_init(&elem);
	ia_css_circbuf_elem_set_val(&elem, val);

	/* write the element into the buffer */
	ia_css_circbuf_write(cb, elem);
}

/**
 * @brief Get the number of free elements.
 *
 * @param cb The pointer to the circular buffer.
 *
 * @return: The number of free elements.
 */
static inline uint32_t ia_css_circbuf_get_free_elems(
    ia_css_circbuf_t *cb)
{
	OP___assert(cb);
	OP___assert(cb->desc);

	return ia_css_circbuf_desc_get_free_elems(cb->desc);
}

/**
 * @brief Peek an element in Circular Buffer.
 *
 * @param cb	 The pointer to the circular buffer.
 * @param offset Offset to the element.
 *
 * @return the elements value.
 */
uint32_t ia_css_circbuf_peek(
    ia_css_circbuf_t *cb,
    int offset);

/**
 * @brief Get an element in Circular Buffer.
 *
 * @param cb	 The pointer to the circular buffer.
 * @param offset Offset to the element.
 *
 * @return the elements value.
 */
uint32_t ia_css_circbuf_peek_from_start(
    ia_css_circbuf_t *cb,
    int offset);

/**
 * @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
 *
 * @param cb The pointer to the circular buffer.
 * @param sz_delta delta increase for new size
 * @param elems (optional) pointers to new additional elements
 *		cb element array size will not be increased dynamically,
 *		but new elements should be added at the end to existing
 *		cb element array which if of max_size >= new size
 *
 * @return	true on successfully increasing the size
 *			false on failure
 */
bool ia_css_circbuf_increase_size(
    ia_css_circbuf_t *cb,
    unsigned int sz_delta,
    ia_css_circbuf_elem_t *elems);

#endif /*_IA_CSS_CIRCBUF_H */