// 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 "assert_support.h"		/* assert */
#include "ia_css_buffer.h"
#include "sp.h"
#include "ia_css_bufq.h"		/* Bufq API's */
#include "ia_css_queue.h"		/* ia_css_queue_t */
#include "sw_event_global.h"		/* Event IDs.*/
#include "ia_css_eventq.h"		/* ia_css_eventq_recv()*/
#include "ia_css_debug.h"		/* ia_css_debug_dtrace*/
#include "sh_css_internal.h"		/* sh_css_queue_type */
#include "sp_local.h"			/* sp_address_of */
#include "sh_css_firmware.h"		/* sh_css_sp_fw*/

#define BUFQ_DUMP_FILE_NAME_PREFIX_SIZE 256

static char prefix[BUFQ_DUMP_FILE_NAME_PREFIX_SIZE] = {0};

/*********************************************************/
/* Global Queue objects used by CSS                      */
/*********************************************************/

struct sh_css_queues {
	/* Host2SP buffer queue */
	ia_css_queue_t host2sp_buffer_queue_handles
	[SH_CSS_MAX_SP_THREADS][SH_CSS_MAX_NUM_QUEUES];
	/* SP2Host buffer queue */
	ia_css_queue_t sp2host_buffer_queue_handles
	[SH_CSS_MAX_NUM_QUEUES];

	/* Host2SP event queue */
	ia_css_queue_t host2sp_psys_event_queue_handle;

	/* SP2Host event queue */
	ia_css_queue_t sp2host_psys_event_queue_handle;

	/* Host2SP ISYS event queue */
	ia_css_queue_t host2sp_isys_event_queue_handle;

	/* SP2Host ISYS event queue */
	ia_css_queue_t sp2host_isys_event_queue_handle;
	/* Tagger command queue */
	ia_css_queue_t host2sp_tag_cmd_queue_handle;
};

/*******************************************************
*** Static variables
********************************************************/
static struct sh_css_queues css_queues;

static int
buffer_type_to_queue_id_map[SH_CSS_MAX_SP_THREADS][IA_CSS_NUM_DYNAMIC_BUFFER_TYPE];
static bool queue_availability[SH_CSS_MAX_SP_THREADS][SH_CSS_MAX_NUM_QUEUES];

/*******************************************************
*** Static functions
********************************************************/
static void map_buffer_type_to_queue_id(
    unsigned int thread_id,
    enum ia_css_buffer_type buf_type
);
static void unmap_buffer_type_to_queue_id(
    unsigned int thread_id,
    enum ia_css_buffer_type buf_type
);

static ia_css_queue_t *bufq_get_qhandle(
    enum sh_css_queue_type type,
    enum sh_css_queue_id id,
    int thread
);

/*******************************************************
*** Public functions
********************************************************/
void ia_css_queue_map_init(void)
{
	unsigned int i, j;

	for (i = 0; i < SH_CSS_MAX_SP_THREADS; i++) {
		for (j = 0; j < SH_CSS_MAX_NUM_QUEUES; j++)
			queue_availability[i][j] = true;
	}

	for (i = 0; i < SH_CSS_MAX_SP_THREADS; i++) {
		for (j = 0; j < IA_CSS_NUM_DYNAMIC_BUFFER_TYPE; j++)
			buffer_type_to_queue_id_map[i][j] = SH_CSS_INVALID_QUEUE_ID;
	}
}

void ia_css_queue_map(
    unsigned int thread_id,
    enum ia_css_buffer_type buf_type,
    bool map)
{
	assert(buf_type < IA_CSS_NUM_DYNAMIC_BUFFER_TYPE);
	assert(thread_id < SH_CSS_MAX_SP_THREADS);

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE,
			    "ia_css_queue_map() enter: buf_type=%d, thread_id=%d\n", buf_type, thread_id);

	if (map)
		map_buffer_type_to_queue_id(thread_id, buf_type);
	else
		unmap_buffer_type_to_queue_id(thread_id, buf_type);
}

/*
 * @brief Query the internal queue ID.
 */
bool ia_css_query_internal_queue_id(
    enum ia_css_buffer_type buf_type,
    unsigned int thread_id,
    enum sh_css_queue_id *val)
{
	IA_CSS_ENTER("buf_type=%d, thread_id=%d, val = %p", buf_type, thread_id, val);

	if ((!val) || (thread_id >= SH_CSS_MAX_SP_THREADS) ||
	    (buf_type >= IA_CSS_NUM_DYNAMIC_BUFFER_TYPE)) {
		IA_CSS_LEAVE("return_val = false");
		return false;
	}

	*val = buffer_type_to_queue_id_map[thread_id][buf_type];
	if ((*val == SH_CSS_INVALID_QUEUE_ID) || (*val >= SH_CSS_MAX_NUM_QUEUES)) {
		IA_CSS_LOG("INVALID queue ID MAP = %d\n", *val);
		IA_CSS_LEAVE("return_val = false");
		return false;
	}
	IA_CSS_LEAVE("return_val = true");
	return true;
}

/*******************************************************
*** Static functions
********************************************************/
static void map_buffer_type_to_queue_id(
    unsigned int thread_id,
    enum ia_css_buffer_type buf_type)
{
	unsigned int i;

	assert(thread_id < SH_CSS_MAX_SP_THREADS);
	assert(buf_type < IA_CSS_NUM_DYNAMIC_BUFFER_TYPE);
	assert(buffer_type_to_queue_id_map[thread_id][buf_type] ==
	       SH_CSS_INVALID_QUEUE_ID);

	/* queue 0 is reserved for parameters because it doesn't depend on events */
	if (buf_type == IA_CSS_BUFFER_TYPE_PARAMETER_SET) {
		assert(queue_availability[thread_id][IA_CSS_PARAMETER_SET_QUEUE_ID]);
		queue_availability[thread_id][IA_CSS_PARAMETER_SET_QUEUE_ID] = false;
		buffer_type_to_queue_id_map[thread_id][buf_type] =
		    IA_CSS_PARAMETER_SET_QUEUE_ID;
		return;
	}

	/* queue 1 is reserved for per frame parameters because it doesn't depend on events */
	if (buf_type == IA_CSS_BUFFER_TYPE_PER_FRAME_PARAMETER_SET) {
		assert(queue_availability[thread_id][IA_CSS_PER_FRAME_PARAMETER_SET_QUEUE_ID]);
		queue_availability[thread_id][IA_CSS_PER_FRAME_PARAMETER_SET_QUEUE_ID] = false;
		buffer_type_to_queue_id_map[thread_id][buf_type] =
		    IA_CSS_PER_FRAME_PARAMETER_SET_QUEUE_ID;
		return;
	}

	for (i = SH_CSS_QUEUE_C_ID; i < SH_CSS_MAX_NUM_QUEUES; i++) {
		if (queue_availability[thread_id][i]) {
			queue_availability[thread_id][i] = false;
			buffer_type_to_queue_id_map[thread_id][buf_type] = i;
			break;
		}
	}

	assert(i != SH_CSS_MAX_NUM_QUEUES);
	return;
}

static void unmap_buffer_type_to_queue_id(
    unsigned int thread_id,
    enum ia_css_buffer_type buf_type)
{
	int queue_id;

	assert(thread_id < SH_CSS_MAX_SP_THREADS);
	assert(buf_type < IA_CSS_NUM_DYNAMIC_BUFFER_TYPE);
	assert(buffer_type_to_queue_id_map[thread_id][buf_type] !=
	       SH_CSS_INVALID_QUEUE_ID);

	queue_id = buffer_type_to_queue_id_map[thread_id][buf_type];
	buffer_type_to_queue_id_map[thread_id][buf_type] = SH_CSS_INVALID_QUEUE_ID;
	queue_availability[thread_id][queue_id] = true;
}

static ia_css_queue_t *bufq_get_qhandle(
    enum sh_css_queue_type type,
    enum sh_css_queue_id id,
    int thread)
{
	ia_css_queue_t *q = NULL;

	switch (type) {
	case sh_css_host2sp_buffer_queue:
		if ((thread >= SH_CSS_MAX_SP_THREADS) || (thread < 0) ||
		    (id == SH_CSS_INVALID_QUEUE_ID))
			break;
		q = &css_queues.host2sp_buffer_queue_handles[thread][id];
		break;
	case sh_css_sp2host_buffer_queue:
		if (id == SH_CSS_INVALID_QUEUE_ID)
			break;
		q = &css_queues.sp2host_buffer_queue_handles[id];
		break;
	case sh_css_host2sp_psys_event_queue:
		q = &css_queues.host2sp_psys_event_queue_handle;
		break;
	case sh_css_sp2host_psys_event_queue:
		q = &css_queues.sp2host_psys_event_queue_handle;
		break;
	case sh_css_host2sp_isys_event_queue:
		q = &css_queues.host2sp_isys_event_queue_handle;
		break;
	case sh_css_sp2host_isys_event_queue:
		q = &css_queues.sp2host_isys_event_queue_handle;
		break;
	case sh_css_host2sp_tag_cmd_queue:
		q = &css_queues.host2sp_tag_cmd_queue_handle;
		break;
	default:
		break;
	}

	return q;
}

/* Local function to initialize a buffer queue. This reduces
 * the chances of copy-paste errors or typos.
 */
static inline void
init_bufq(unsigned int desc_offset,
	  unsigned int elems_offset,
	  ia_css_queue_t *handle)
{
	const struct ia_css_fw_info *fw;
	unsigned int q_base_addr;
	ia_css_queue_remote_t remoteq;

	fw = &sh_css_sp_fw;
	q_base_addr = fw->info.sp.host_sp_queue;

	/* Setup queue location as SP and proc id as SP0_ID */
	remoteq.location = IA_CSS_QUEUE_LOC_SP;
	remoteq.proc_id = SP0_ID;
	remoteq.cb_desc_addr = q_base_addr + desc_offset;
	remoteq.cb_elems_addr = q_base_addr + elems_offset;
	/* Initialize the queue instance and obtain handle */
	ia_css_queue_remote_init(handle, &remoteq);
}

void ia_css_bufq_init(void)
{
	int i, j;

	IA_CSS_ENTER_PRIVATE("");

	/* Setup all the local queue descriptors for Host2SP Buffer Queues */
	for (i = 0; i < SH_CSS_MAX_SP_THREADS; i++)
		for (j = 0; j < SH_CSS_MAX_NUM_QUEUES; j++) {
			init_bufq((uint32_t)offsetof(struct host_sp_queues,
						     host2sp_buffer_queues_desc[i][j]),
				  (uint32_t)offsetof(struct host_sp_queues, host2sp_buffer_queues_elems[i][j]),
				  &css_queues.host2sp_buffer_queue_handles[i][j]);
		}

	/* Setup all the local queue descriptors for SP2Host Buffer Queues */
	for (i = 0; i < SH_CSS_MAX_NUM_QUEUES; i++) {
		init_bufq(offsetof(struct host_sp_queues, sp2host_buffer_queues_desc[i]),
			  offsetof(struct host_sp_queues, sp2host_buffer_queues_elems[i]),
			  &css_queues.sp2host_buffer_queue_handles[i]);
	}

	/* Host2SP event queue*/
	init_bufq((uint32_t)offsetof(struct host_sp_queues,
				     host2sp_psys_event_queue_desc),
		  (uint32_t)offsetof(struct host_sp_queues, host2sp_psys_event_queue_elems),
		  &css_queues.host2sp_psys_event_queue_handle);

	/* SP2Host event queue */
	init_bufq((uint32_t)offsetof(struct host_sp_queues,
				     sp2host_psys_event_queue_desc),
		  (uint32_t)offsetof(struct host_sp_queues, sp2host_psys_event_queue_elems),
		  &css_queues.sp2host_psys_event_queue_handle);

	/* Host2SP ISYS event queue */
	init_bufq((uint32_t)offsetof(struct host_sp_queues,
				     host2sp_isys_event_queue_desc),
		  (uint32_t)offsetof(struct host_sp_queues, host2sp_isys_event_queue_elems),
		  &css_queues.host2sp_isys_event_queue_handle);

	/* SP2Host ISYS event queue*/
	init_bufq((uint32_t)offsetof(struct host_sp_queues,
				     sp2host_isys_event_queue_desc),
		  (uint32_t)offsetof(struct host_sp_queues, sp2host_isys_event_queue_elems),
		  &css_queues.sp2host_isys_event_queue_handle);

	/* Host2SP tagger command queue */
	init_bufq((uint32_t)offsetof(struct host_sp_queues, host2sp_tag_cmd_queue_desc),
		  (uint32_t)offsetof(struct host_sp_queues, host2sp_tag_cmd_queue_elems),
		  &css_queues.host2sp_tag_cmd_queue_handle);

	IA_CSS_LEAVE_PRIVATE("");
}

int ia_css_bufq_enqueue_buffer(
    int thread_index,
    int queue_id,
    uint32_t item)
{
	ia_css_queue_t *q;
	int error;

	IA_CSS_ENTER_PRIVATE("queue_id=%d", queue_id);
	if ((thread_index >= SH_CSS_MAX_SP_THREADS) || (thread_index < 0) ||
	    (queue_id == SH_CSS_INVALID_QUEUE_ID))
		return -EINVAL;

	/* Get the queue for communication */
	q = bufq_get_qhandle(sh_css_host2sp_buffer_queue,
			     queue_id,
			     thread_index);
	if (q) {
		error = ia_css_queue_enqueue(q, item);
	} else {
		IA_CSS_ERROR("queue is not initialized");
		error = -EBUSY;
	}

	IA_CSS_LEAVE_ERR_PRIVATE(error);
	return error;
}

int ia_css_bufq_dequeue_buffer(
    int queue_id,
    uint32_t *item)
{
	int error;
	ia_css_queue_t *q;

	IA_CSS_ENTER_PRIVATE("queue_id=%d", queue_id);
	if ((!item) ||
	    (queue_id <= SH_CSS_INVALID_QUEUE_ID) ||
	    (queue_id >= SH_CSS_MAX_NUM_QUEUES)
	   )
		return -EINVAL;

	q = bufq_get_qhandle(sh_css_sp2host_buffer_queue,
			     queue_id,
			     -1);
	if (q) {
		error = ia_css_queue_dequeue(q, item);
	} else {
		IA_CSS_ERROR("queue is not initialized");
		error = -EBUSY;
	}

	IA_CSS_LEAVE_ERR_PRIVATE(error);
	return error;
}

int ia_css_bufq_enqueue_psys_event(
    u8 evt_id,
    u8 evt_payload_0,
    u8 evt_payload_1,
    uint8_t evt_payload_2)
{
	int error = 0;
	ia_css_queue_t *q;

	IA_CSS_ENTER_PRIVATE("evt_id=%d", evt_id);
	q = bufq_get_qhandle(sh_css_host2sp_psys_event_queue, -1, -1);
	if (!q) {
		IA_CSS_ERROR("queue is not initialized");
		return -EBUSY;
	}

	error = ia_css_eventq_send(q,
				   evt_id, evt_payload_0, evt_payload_1, evt_payload_2);

	IA_CSS_LEAVE_ERR_PRIVATE(error);
	return error;
}

int ia_css_bufq_dequeue_psys_event(
    u8 item[BUFQ_EVENT_SIZE])
{
	int error = 0;
	ia_css_queue_t *q;

	/* No ENTER/LEAVE in this function since this is polled
	 * by some test apps. Enablign logging here floods the log
	 * files which may cause timeouts. */
	if (!item)
		return -EINVAL;

	q = bufq_get_qhandle(sh_css_sp2host_psys_event_queue, -1, -1);
	if (!q) {
		IA_CSS_ERROR("queue is not initialized");
		return -EBUSY;
	}
	error = ia_css_eventq_recv(q, item);

	return error;
}

int ia_css_bufq_dequeue_isys_event(
    u8 item[BUFQ_EVENT_SIZE])
{
	int error = 0;
	ia_css_queue_t *q;

	/* No ENTER/LEAVE in this function since this is polled
	 * by some test apps. Enablign logging here floods the log
	 * files which may cause timeouts. */
	if (!item)
		return -EINVAL;

	q = bufq_get_qhandle(sh_css_sp2host_isys_event_queue, -1, -1);
	if (!q) {
		IA_CSS_ERROR("queue is not initialized");
		return -EBUSY;
	}
	error = ia_css_eventq_recv(q, item);
	return error;
}

int ia_css_bufq_enqueue_isys_event(uint8_t evt_id)
{
	int error = 0;
	ia_css_queue_t *q;

	IA_CSS_ENTER_PRIVATE("event_id=%d", evt_id);
	q = bufq_get_qhandle(sh_css_host2sp_isys_event_queue, -1, -1);
	if (!q) {
		IA_CSS_ERROR("queue is not initialized");
		return -EBUSY;
	}

	error = ia_css_eventq_send(q, evt_id, 0, 0, 0);

	IA_CSS_LEAVE_ERR_PRIVATE(error);
	return error;
}

int ia_css_bufq_enqueue_tag_cmd(
    uint32_t item)
{
	int error;
	ia_css_queue_t *q;

	IA_CSS_ENTER_PRIVATE("item=%d", item);
	q = bufq_get_qhandle(sh_css_host2sp_tag_cmd_queue, -1, -1);
	if (!q) {
		IA_CSS_ERROR("queue is not initialized");
		return -EBUSY;
	}
	error = ia_css_queue_enqueue(q, item);

	IA_CSS_LEAVE_ERR_PRIVATE(error);
	return error;
}

int ia_css_bufq_deinit(void)
{
	return 0;
}

static void bufq_dump_queue_info(const char *prefix, ia_css_queue_t *qhandle)
{
	u32 free = 0, used = 0;

	assert(prefix && qhandle);
	ia_css_queue_get_used_space(qhandle, &used);
	ia_css_queue_get_free_space(qhandle, &free);
	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "%s: used=%u free=%u\n",
			    prefix, used, free);
}

void ia_css_bufq_dump_queue_info(void)
{
	int i, j;

	ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "Queue Information:\n");

	for (i = 0; i < SH_CSS_MAX_SP_THREADS; i++) {
		for (j = 0; j < SH_CSS_MAX_NUM_QUEUES; j++) {
			snprintf(prefix, BUFQ_DUMP_FILE_NAME_PREFIX_SIZE,
				 "host2sp_buffer_queue[%u][%u]", i, j);
			bufq_dump_queue_info(prefix,
					     &css_queues.host2sp_buffer_queue_handles[i][j]);
		}
	}

	for (i = 0; i < SH_CSS_MAX_NUM_QUEUES; i++) {
		snprintf(prefix, BUFQ_DUMP_FILE_NAME_PREFIX_SIZE,
			 "sp2host_buffer_queue[%u]", i);
		bufq_dump_queue_info(prefix,
				     &css_queues.sp2host_buffer_queue_handles[i]);
	}
	bufq_dump_queue_info("host2sp_psys_event",
			     &css_queues.host2sp_psys_event_queue_handle);
	bufq_dump_queue_info("sp2host_psys_event",
			     &css_queues.sp2host_psys_event_queue_handle);

	bufq_dump_queue_info("host2sp_isys_event",
			     &css_queues.host2sp_isys_event_queue_handle);
	bufq_dump_queue_info("sp2host_isys_event",
			     &css_queues.sp2host_isys_event_queue_handle);
	bufq_dump_queue_info("host2sp_tag_cmd",
			     &css_queues.host2sp_tag_cmd_queue_handle);
}