// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */

#include <linux/types.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/kfifo.h>
#include <linux/wait.h>
#include <linux/mutex.h>

#include "atsc30_log.h"
#include "alp_hal_if.h"

#include "mdrv_atsc30_if.h"
#include "mdrv_atsc30_msg_q.h"

//-------------------------------------------------------------------------------------------------
//  Debug Constants and Macros
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Constants
//-------------------------------------------------------------------------------------------------
#define MS_PER_SEC (1000)

//-------------------------------------------------------------------------------------------------
//  Macros
//-------------------------------------------------------------------------------------------------
#define MDRV_ATSC30_MSG_Q_LOCK(lock) \
	mutex_lock(&(lock))
#define MDRV_ATSC30_MSG_Q_UNLOCK(lock) \
	mutex_unlock(&(lock))

//-------------------------------------------------------------------------------------------------
//  Structs and Enums
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Functions Prototype
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Variables
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Internal Functions
//-------------------------------------------------------------------------------------------------

//-------------------------------------------------------------------------------------------------
//  Public Functions
//-------------------------------------------------------------------------------------------------
int mdrv_atsc30_msg_q_init(struct mdrv_atsc30_msg_q *msg_q, u32 size)
{
	int ret = 0;

	if (ATSC30_DRVIF_VERIFY(msg_q == NULL))
		return MDRV_ATSC30_RETURN_CODE_INVALID_PARAMETER;
	if (ATSC30_DRVIF_VERIFY(msg_q->init))
		return MDRV_ATSC30_RETURN_CODE_OK;

	msg_q->buf = vmalloc(sizeof(u8) * size);
	ret = kfifo_init(
			&msg_q->msg_q,
			msg_q->buf,
			size);
	if (IS_ERR(ADDR_CAST(const void *) ret)) {
		ATSC30_DRVIF_LOG_ENHANCED_ERROR(
			"kfifo_init(..., %d, ...) is failed\n",
			size);

		return MDRV_ATSC30_RETURN_CODE_RESOURCE;
	}

	mutex_init(&msg_q->lock);

	init_waitqueue_head(&msg_q->wait_q_head_get);
	msg_q->wait_q_condition_get = false;
	init_waitqueue_head(&msg_q->wait_q_head_put);
	msg_q->wait_q_condition_put = false;

	msg_q->init = true;

	return MDRV_ATSC30_RETURN_CODE_OK;
}

int mdrv_atsc30_msg_q_exit(struct mdrv_atsc30_msg_q *msg_q)
{
	if (ATSC30_DRVIF_VERIFY(msg_q == NULL))
		return MDRV_ATSC30_RETURN_CODE_INVALID_PARAMETER;
	if (ATSC30_DRVIF_VERIFY(!msg_q->init))
		return MDRV_ATSC30_RETURN_CODE_OK;

	memset(&msg_q->msg_q, 0, sizeof(msg_q->msg_q));
	vfree(msg_q->buf);

	mutex_destroy(&msg_q->lock);

	msg_q->init = false;

	return MDRV_ATSC30_RETURN_CODE_OK;
}

int mdrv_atsc30_msg_q_get(
		struct mdrv_atsc30_msg_q *msg_q,
		u8 *buffer,
		size_t *len,
		u32 timeout_ms,
		u32 flag)
{
	int ret = MDRV_ATSC30_RETURN_CODE_OK;
	int ret_tmp = 0;
	u32 len_out = 0;

	if (ATSC30_DRVIF_VERIFY(msg_q == NULL))
		return MDRV_ATSC30_RETURN_CODE_INVALID_PARAMETER;
	if (ATSC30_DRVIF_VERIFY(!msg_q->init))
		return MDRV_ATSC30_RETURN_CODE_FAILURE;

	MDRV_ATSC30_MSG_Q_LOCK(msg_q->lock);

	while (kfifo_len(&msg_q->msg_q) < (*len)) {
		MDRV_ATSC30_MSG_Q_UNLOCK(msg_q->lock);

		if (!(flag & MDRV_ATSC30_MSG_Q_FLAG_WAIT)) {
			(*len) = 0;

			return MDRV_ATSC30_RETURN_CODE_FAILURE;
		}

		if (timeout_ms == MDRV_ATSC30_MSG_Q_TIMEOUT_FOREVER) {
			ret_tmp = wait_event_interruptible(
					msg_q->wait_q_head_get,
					msg_q->wait_q_condition_get);
			if (ret_tmp != 0)
				ATSC30_DRVIF_LOG_ENHANCED_DEBUG(
					"wait_event_interruptible ret %d\n",
					ret_tmp);
		} else {
			ret_tmp = wait_event_interruptible_timeout(
					msg_q->wait_q_head_get,
					msg_q->wait_q_condition_get,
					((long)timeout_ms) * HZ / MS_PER_SEC);
			if (ret_tmp <= 0) {
				ATSC30_DRVIF_LOG_ENHANCED_DEBUG(
					"wait_event_interruptible_timeout ret %d\n",
					ret_tmp);

				return MDRV_ATSC30_RETURN_CODE_FAILURE;
			}
		}

		MDRV_ATSC30_MSG_Q_LOCK(msg_q->lock);
	}

	msg_q->wait_q_condition_get = false;

	len_out = kfifo_out(&msg_q->msg_q, buffer, *len);
	if (len_out < (*len)) {
		ATSC30_DRVIF_LOG_ENHANCED_ERROR(
			"len_out[0x%x] < len[0x%zx]\n",
			len_out,
			*len);

		ret = MDRV_ATSC30_RETURN_CODE_FAILURE;
	} else {
		msg_q->wait_q_condition_put = true;
		wake_up_interruptible(&msg_q->wait_q_head_put);
	}

	(*len) = len_out;

	MDRV_ATSC30_MSG_Q_UNLOCK(msg_q->lock);

	return ret;
}

int mdrv_atsc30_msg_q_put(
		struct mdrv_atsc30_msg_q *msg_q,
		u8 *buffer,
		size_t *len,
		u32 timeout_ms,
		u32 flag)
{
	int ret = MDRV_ATSC30_RETURN_CODE_OK;
	int ret_tmp = 0;
	u32 len_in = 0;

	if (ATSC30_DRVIF_VERIFY(msg_q == NULL))
		return MDRV_ATSC30_RETURN_CODE_INVALID_PARAMETER;
	if (ATSC30_DRVIF_VERIFY(!msg_q->init))
		return MDRV_ATSC30_RETURN_CODE_FAILURE;

	MDRV_ATSC30_MSG_Q_LOCK(msg_q->lock);

	while (kfifo_avail(&msg_q->msg_q) < *len) {
		MDRV_ATSC30_MSG_Q_UNLOCK(msg_q->lock);

		if (!(flag & MDRV_ATSC30_MSG_Q_FLAG_WAIT)) {
			*len = 0;

			return MDRV_ATSC30_RETURN_CODE_FAILURE;
		}
		if (timeout_ms == MDRV_ATSC30_MSG_Q_TIMEOUT_FOREVER) {
			ret_tmp = wait_event_interruptible(
					msg_q->wait_q_head_put,
					msg_q->wait_q_condition_put);
			if (ret_tmp != 0)
				ATSC30_DRVIF_LOG_ENHANCED_DEBUG(
					"wait_event_interruptible ret %d\n",
					ret_tmp);
		} else {
			ret_tmp = wait_event_interruptible_timeout(
					msg_q->wait_q_head_put,
					msg_q->wait_q_condition_put,
					((long)timeout_ms) * HZ / MS_PER_SEC);
			if (ret_tmp <= 0) {
				ATSC30_DRVIF_LOG_ENHANCED_DEBUG(
					"wait_event_interruptible_timeout ret %d\n",
					ret_tmp);

				return MDRV_ATSC30_RETURN_CODE_FAILURE;
			}
		}

		MDRV_ATSC30_MSG_Q_LOCK(msg_q->lock);
	}

	msg_q->wait_q_condition_put = false;

	len_in = kfifo_in(&msg_q->msg_q, buffer, *len);
	if (len_in < *len) {
		ATSC30_DRVIF_LOG_ENHANCED_ERROR(
			"len_in[0x%x] < len[0x%zx]\n",
			len_in,
			*len);

		ret = MDRV_ATSC30_RETURN_CODE_FAILURE;
	} else {
		msg_q->wait_q_condition_get = true;
		wake_up_interruptible(&msg_q->wait_q_head_get);
	}

	*len = len_in;

	MDRV_ATSC30_MSG_Q_UNLOCK(msg_q->lock);

	return ret;
}

int mdrv_atsc30_msg_q_get_available_size(
		struct mdrv_atsc30_msg_q *msg_q,
		size_t *size)
{
	int ret = MDRV_ATSC30_RETURN_CODE_OK;

	if (ATSC30_DRVIF_VERIFY(msg_q == NULL))
		return MDRV_ATSC30_RETURN_CODE_INVALID_PARAMETER;
	if (ATSC30_DRVIF_VERIFY(!msg_q->init))
		return MDRV_ATSC30_RETURN_CODE_FAILURE;

	*size = kfifo_avail(&msg_q->msg_q);

	return ret;
}

int mdrv_atsc30_msg_q_get_used_size(
		struct mdrv_atsc30_msg_q *msg_q,
		size_t *size)
{
	int ret = MDRV_ATSC30_RETURN_CODE_OK;

	if (ATSC30_DRVIF_VERIFY(msg_q == NULL))
		return MDRV_ATSC30_RETURN_CODE_INVALID_PARAMETER;
	if (ATSC30_DRVIF_VERIFY(!msg_q->init))
		return MDRV_ATSC30_RETURN_CODE_FAILURE;

	*size = kfifo_len(&msg_q->msg_q);

	return ret;
}