// SPDX-License-Identifier: GPL-2.0
/******************************************************************************
 *
 * Copyright(C) 2023 MediaTek Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See http://www.gnu.org/licenses/gpl-2.0.html for more details.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *****************************************************************************/


#include <mali_kbase_mtk_dtv_gdec.h>

extern const u8 max_mfdec_slot_num;
#ifndef MTK_DTV_ZEBU
extern unsigned long mtk_get_nullmem_buffer_info(int share_fd);
#endif

u16 mfdec_used_id = 0;
DEFINE_SPINLOCK(mfdec_lock);

int mfdec_query(struct kbase_context *kctx, struct kbase_ioctl_mfdec_query *query)
{
#if defined(MTK_DTV_MT5896) || defined(MTK_DTV_MT5897)
	/* Workaround: skip slot[13] bit check when query available slots */
	u16 full_mfdec_used_ids = (((1 << max_mfdec_slot_num) - 1) & ~(1 << 0xD));
#else
	u16 full_mfdec_used_ids = ((1 << max_mfdec_slot_num) - 1);
#endif

	query->slot_in_use = mfdec_used_id;

	if (mfdec_used_id == full_mfdec_used_ids) {
		dev_warn(kctx->kbdev->dev, "[Mali][MFDec] cannot find any valid slot!\n");
		return -ENOMEM;
	} else {
		return 0;
	}
}

int mfdec_get_slot(struct kbase_context *kctx, u8 *slot_id, struct base_mfdec_info *info)
{
	int ret = 0;
	unsigned long flags;

	spin_lock_irqsave(&mfdec_lock, flags);
	if (info->id == INVALID_SLOT_ID)
	{
		ret = get_mfdec_slot(kctx->kbdev, slot_id);

		if( ret < 0)
		{
			dev_warn(kctx->kbdev->dev, "[Mali][MFDec] cannot find any valid slot!\n");
			spin_unlock_irqrestore(&mfdec_lock, flags);
			return -ENOMEM;
		}
	}
	else
	{
		*slot_id = info->id;
	}

	mfdec_used_id |= (1 << *slot_id);
	kctx->occupied_slot |= (1 << *slot_id);
	spin_unlock_irqrestore(&mfdec_lock, flags);

	return 0;
}

static int mfdec_put_slot(struct kbase_context *kctx, u8 slot_id)
{
	unsigned long flags;

	spin_lock_irqsave(&mfdec_lock, flags);

	release_mfdec_slot(kctx->kbdev, slot_id);
	mfdec_used_id &= ~(1 << slot_id);
	kctx->occupied_slot &= ~(1 << slot_id);

	spin_unlock_irqrestore(&mfdec_lock, flags);

	return 0;
}

int mfdec_katom_put_slots(struct kbase_context *kctx, struct kbase_jd_atom *katom)
{
	unsigned long flags;
	u8 i;

	spin_lock_irqsave(&mfdec_lock, flags);
	for (i = 0; i < katom->nr_extres_mfdec; i++)	{
		u8 slot_id = katom->mfdec_infos[i].mfdec_slot;

		if (slot_id != INVALID_SLOT_ID) {
			release_mfdec_slot(kctx->kbdev, slot_id);
			mfdec_used_id &= ~(1 << slot_id);
			kctx->occupied_slot &= ~(1 << slot_id);
			katom->mfdec_infos[i].mfdec_slot = INVALID_SLOT_ID;
		}
	}
	spin_unlock_irqrestore(&mfdec_lock, flags);
	return 0;
}

static int mfdec_katom_put_slots_locked(struct kbase_context *kctx, struct kbase_jd_atom *katom)
{
	u8 i;

	for (i = 0; i < katom->nr_extres_mfdec; i++) {
		u8 slot_id = katom->mfdec_infos[i].mfdec_slot;

		if (slot_id != INVALID_SLOT_ID) {
			release_mfdec_slot(kctx->kbdev, slot_id);
			mfdec_used_id &= ~(1 << slot_id);
			kctx->occupied_slot &= ~(1 << slot_id);
			katom->mfdec_infos[i].mfdec_slot = INVALID_SLOT_ID;
		}
	}

	return 0;
}

int mfdec_katom_get_slots(struct kbase_context *kctx, struct kbase_jd_atom *katom)
{
	int ret = 0;
	unsigned long flags;
	u8 i;

	spin_lock_irqsave(&mfdec_lock, flags);

	for (i = 0; i < katom->nr_extres_mfdec; i++) {
		struct extres_mfdec *slot_info = &katom->mfdec_infos[i];

		if (slot_info->mfdec_info == NULL) {
			dev_warn(kctx->kbdev->dev, "[Mali][MFDec] %s, mfdec_info = NULL, job might be removed!\n",
				__func__);
			mfdec_katom_put_slots_locked(kctx, katom);
			spin_unlock_irqrestore(&mfdec_lock, flags);
			return -ENOMEM;
		}

		ret = get_mfdec_slot(kctx->kbdev, &slot_info->mfdec_slot);
		if (ret < 0) {
			dev_warn(kctx->kbdev->dev, "[Mali][MFDec] cannot find sufficient slots for job!\n");
			mfdec_katom_put_slots_locked(kctx, katom);
			spin_unlock_irqrestore(&mfdec_lock, flags);
			return -ENOMEM;
		}
		mfdec_used_id |= (1 << slot_info->mfdec_slot);
		kctx->occupied_slot |= (1 << slot_info->mfdec_slot);
	}

	spin_unlock_irqrestore(&mfdec_lock, flags);

	return 0;
}

int mfdec_bind_video_buffer(struct kbase_context *kctx, struct kbase_ioctl_mfdec_info *info)
{
	int ret = 0;

#ifndef MTK_DTV_ZEBU
	unsigned long buffer_pfn = 0;

	/* get iova from fake buffer fd when binding a new slot */
	buffer_pfn = mtk_get_nullmem_buffer_info(info->in.base);
	if (buffer_pfn == (unsigned long)(-EINVAL)) {
		dev_err(kctx->kbdev->dev, "[Mali][MFDec] fail to get fake buffer addr.\n");
		return -EINVAL;
	}
	/* write iova back to mfdec_info */
	info->in.base = buffer_pfn << PAGE_SHIFT;
#endif

	spin_lock(&mfdec_lock);
	/* When the input index is invalid, treat this binding as a new one */
	if (info->in.id == INVALID_SLOT_ID)
	{
		ret = get_mfdec_slot(kctx->kbdev, &(info->in.id));

		if( ret < 0)
		{
			dev_warn(kctx->kbdev->dev, "[Mali][MFDec] cannot find any valid slot!\n");
			spin_unlock(&mfdec_lock);
			return -ENOMEM;
		}

		/* Once the binding is done, we don't need to update bridge setting untill unbind */
		info->out.frame_id = info->in.id;
	}

	if (info->in.is_tiled_yuv)
		ret = set_mfdec_detile(kctx->kbdev, info->in.id, info->in.video_ybase, info->in.video_uvbase, info->in.video_pitch, info->in.encode_type, info->in.flags);
	else
		ret = set_mfdec_decode(kctx->kbdev, info->in.id, info->in.video_ybase, info->in.video_uvbase, info->in.BLDbase, info->in.BLDuvbase, info->in.BLDpitch, info->in.encode_type, info->in.flags);

	if (ret != 0)
	{
		spin_unlock(&mfdec_lock);
		return -EFAULT;
	}

	ret = set_mfdec_bridge(kctx->kbdev, info->in.id, info->in.base, info->in.height, info->in.pitch);

	if (ret != 0) {
		spin_unlock(&mfdec_lock);
		return -EFAULT;
	}

	mfdec_used_id |= (1 << info->in.id);
	kctx->occupied_slot |= (1 << info->in.id);
	spin_unlock(&mfdec_lock);

	return ret;
}

static int mfdec_bind_video_buffer_v2(struct kbase_device *kbdev, u8 slot_id,
	struct base_mfdec_info *info)
{
	unsigned long flags;
	int ret = 0;

	spin_lock_irqsave(&mfdec_lock, flags);

	if (info->is_tiled_yuv)
		ret = set_mfdec_detile(kbdev, slot_id, info->video_ybase, info->video_uvbase, info->video_pitch, info->encode_type, info->flags);
	else
		ret = set_mfdec_decode(kbdev, slot_id, info->video_ybase, info->video_uvbase, info->BLDbase, info->BLDuvbase, info->BLDpitch, info->encode_type, info->flags);

	if (ret != 0)
	{
		spin_unlock_irqrestore(&mfdec_lock, flags);
		return -EFAULT;
	}

	ret = set_mfdec_bridge(kbdev, slot_id, info->base, info->height, info->pitch);

	if (ret != 0) {
		spin_unlock_irqrestore(&mfdec_lock, flags);
		return -EFAULT;
	}

	spin_unlock_irqrestore(&mfdec_lock, flags);

	return ret;
}

int mfdec_unbind_video_buffer(struct kbase_context *kctx, struct kbase_ioctl_mfdec_info *info)
{
	spin_lock(&mfdec_lock);

	/* reset HW state */
	reset_mfdec_slot_reg(kctx->kbdev, info->in.id);

	/* set SW state to make slot available again.*/
	release_mfdec_slot(kctx->kbdev, info->in.id);

	mfdec_used_id &= ~(1 << info->in.id);
	kctx->occupied_slot &= ~(1 << info->in.id);
	spin_unlock(&mfdec_lock);

	return 0;
}

static void mfdec_unbind_video_buffer_v2(struct kbase_device *kbdev, u8 slot_id)
{
	unsigned long flags;

	spin_lock_irqsave(&mfdec_lock, flags);
	/* reset HW state only. */
	reset_mfdec_slot_reg(kbdev, slot_id);
	spin_unlock_irqrestore(&mfdec_lock, flags);
}

int mfdec_katom_bind_slots(struct kbase_device *kbdev, struct kbase_jd_atom *katom)
{
	u8 nr_slots;
	u8 i;
	u8 slot;
	int err;

	nr_slots = katom->nr_extres_mfdec;

	if (nr_slots > max_mfdec_slot_num)
		return -ENOMEM;

	/* slots sanity */
	for (slot = 0; slot < max_mfdec_slot_num; slot++)
		reset_mfdec_slot_reg(kbdev, slot);

	for (i = 0; i < nr_slots; i++) {
		err = mfdec_bind_video_buffer_v2(kbdev, katom->mfdec_infos[i].mfdec_slot,
			katom->mfdec_infos[i].mfdec_info);

		if (err < 0) {
			/* reset all slots */
			mfdec_katom_unbind_slots(kbdev, katom);
			return err;
		}
	}

	return 0;
}

void mfdec_katom_unbind_slots(struct kbase_device *kbdev, struct kbase_jd_atom *katom)
{
	u8 i;

	for (i = 0; i < katom->nr_extres_mfdec; i++)
		mfdec_unbind_video_buffer_v2(kbdev, katom->mfdec_infos[i].mfdec_slot);
}

void kbase_context_gdec_term(struct kbase_context *kctx)
{
	u8 slot;

	if (kctx->occupied_slot == 0x0) return;

	dev_dbg(kctx->kbdev->dev, "[Mali][MFDec] clear residual GDEC slot 0x%x.\n", kctx->occupied_slot);
	for (slot = 0; slot < max_mfdec_slot_num; slot++)
	{
		/* find residual slot which is not unbound by application and clear sw states */
		if ((kctx->occupied_slot >> slot) & 0x1)
		{
			mfdec_put_slot(kctx, slot);
		}
	}
}
