// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/genalloc.h>
#include <linux/amlogic/media/codec_mm/codec_mm.h>
#include <linux/amlogic/media/codec_mm/codec_mm_scatter.h>
#include <linux/amlogic/media/codec_mm/codec_mm_keeper.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include "codec_mm_priv.h"
#include "codec_mm_scatter_priv.h"

#define KEEP_NAME "keeper"
#define MAX_KEEP_FRAME 64
#define START_KEEP_ID 0x9
#define MAX_KEEP_ID    (INT_MAX - 1)

struct keep_mem_info {
	void *handle;
	int keep_id;
	int type;
	int user;
	u64 delay_on_jiffies64;
};

struct codec_mm_keeper_mgr {
	int num;
	/* spin lock */
	spinlock_t lock;
	struct delayed_work delay_work;
	int work_runs;
	struct keep_mem_info keep_list[MAX_KEEP_FRAME];
	int next_id;		/*id for keep & free. */
};

static struct codec_mm_keeper_mgr codec_keeper_mgr_private;
static struct codec_mm_keeper_mgr *get_codec_mm_keeper_mgr(void)
{
	return &codec_keeper_mgr_private;
}

int is_codec_mm_keeped(void *mem_handle)
{
	return codec_mm_has_owner(mem_handle, KEEP_NAME);
}
EXPORT_SYMBOL(is_codec_mm_keeped);
/*
 * not call in interrupt;
 */
int codec_mm_keeper_mask_keep_mem(void *mem_handle, int type)
{
	struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
	int keep_id;
	int i;
	unsigned long flags;
	int ret;
	int slot_i = -1;
	int have_samed = 0;

	if (codec_mm_get_keep_debug_mode() & 1)
		pr_err("%s %p\n", __func__, mem_handle);
	if (!mem_handle) {
		pr_err("NULL mem_handle for keeper!!\n");
		return -2;
	}
	if (type != MEM_TYPE_CODEC_MM_SCATTER &&
	    type != MEM_TYPE_CODEC_MM) {
		pr_err("not valid type for keeper!!,%d\n", type);
		return -3;
	}
	if (type == MEM_TYPE_CODEC_MM_SCATTER) {
		ret = codec_mm_scatter_inc_for_keeper(mem_handle);
		if (ret < 0) {
			pr_err("keeper scatter failed,%d,handle:%p\n",
			       ret, mem_handle);
			if (codec_mm_get_keep_debug_mode() & 1)
				codec_mm_dump_all_scatters();
			return -4;
		}
	} else if (type == MEM_TYPE_CODEC_MM) {
		ret = codec_mm_request_shared_mem(mem_handle, KEEP_NAME);
		if (ret < 0) {
			pr_err("keeper codec mm failed,%d,handle:%p\n",
			       ret, mem_handle);
			return -4;
		}
	}
	spin_lock_irqsave(&mgr->lock, flags);
	keep_id = mgr->next_id++;
	if (mgr->next_id >= MAX_KEEP_ID)
		mgr->next_id = START_KEEP_ID;
	for (i = 0; i < MAX_KEEP_FRAME; i++) {
		if (!mgr->keep_list[i].handle && slot_i < 0) {
			slot_i = i;
		} else if (mgr->keep_list[i].handle == mem_handle) {
			have_samed = 1;
			keep_id = mgr->keep_list[i].keep_id;
			break;
		}
	}
	if (slot_i >= 0 && !have_samed) {
		mgr->keep_list[slot_i].handle = mem_handle;
		mgr->keep_list[slot_i].keep_id = keep_id;
		mgr->keep_list[slot_i].type = type;
		mgr->keep_list[slot_i].user = 1;
		mgr->num++;
	} else {
		if (!have_samed)
			keep_id = -1;
	}
	if (codec_mm_get_keep_debug_mode() & 1) {
		/*keeped info */
		pr_err("%s %p id=%d\n", __func__,
		       mem_handle, keep_id);
	}
	if (keep_id < 0 || have_samed) {
		if (type == MEM_TYPE_CODEC_MM_SCATTER)
			ret = codec_mm_scatter_dec_keeper_user(mem_handle, 0);
		else if (type == MEM_TYPE_CODEC_MM)
			codec_mm_release_with_check(mem_handle, KEEP_NAME);
		if (keep_id < 0)
			pr_err("keep mem failed because keep buffer fulled!!!\n");
	}
	spin_unlock_irqrestore(&mgr->lock, flags);

	return keep_id;
}
EXPORT_SYMBOL(codec_mm_keeper_mask_keep_mem);

/*
 * can call in irq
 */
int codec_mm_keeper_unmask_keeper(int keep_id, int delayms)
{
	struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
	int i;
	unsigned long flags;

	if (codec_mm_get_keep_debug_mode() & 1)
		pr_err("%s %d\n", __func__, keep_id);
	if (keep_id < START_KEEP_ID || keep_id >= MAX_KEEP_ID) {
		pr_err("invalid keepid %d\n", keep_id);
		return -1;
	}
	spin_lock_irqsave(&mgr->lock, flags);
	for (i = 0; i < MAX_KEEP_FRAME; i++) {
		if (keep_id == mgr->keep_list[i].keep_id) {
			mgr->keep_list[i].user--;	/*mask can free. */
			mgr->keep_list[i].delay_on_jiffies64 =
				get_jiffies_64() + delayms * HZ / 1000;
			break;
		}
	}
	spin_unlock_irqrestore(&mgr->lock, flags);
	schedule_delayed_work(&mgr->delay_work, delayms);/*do free later, */
	return 0;
}
EXPORT_SYMBOL(codec_mm_keeper_unmask_keeper);

static int codec_mm_keeper_free_keep(int index)
{
	struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
	void *mem_handle = NULL;
	int type;
	unsigned long flags;

	spin_lock_irqsave(&mgr->lock, flags);
	mem_handle = mgr->keep_list[index].handle;
	type = mgr->keep_list[index].type;
	mgr->keep_list[index].handle = NULL;
	mgr->keep_list[index].delay_on_jiffies64 = 0;
	mgr->num--;
	spin_unlock_irqrestore(&mgr->lock, flags);
	if (!mem_handle)
		return -1;
	if (type == MEM_TYPE_CODEC_MM) {
		codec_mm_release_with_check(mem_handle, KEEP_NAME);
	} else if (type == MEM_TYPE_CODEC_MM_SCATTER) {
		struct codec_mm_scatter *sc = mem_handle;

		codec_mm_scatter_dec_keeper_user(sc, 0);
	}

	return 0;
}

/*
 * can't call it
 * in irq/timer.tasklet
 */
int codec_mm_keeper_free_all_keep(int force)
{
	struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
	int i;
	int time_after, want_free;

	for (i = 0; i < MAX_KEEP_FRAME; i++) {
		struct keep_mem_info *info = &mgr->keep_list[i];

		if (!info->handle || info->keep_id < 0)
			continue;
		want_free = 0;
		time_after = time_after64(get_jiffies_64(),
					  info->delay_on_jiffies64);
		if (force == 1)
			want_free = 1;
		else if (force == 2 && info->user <= 0)
			want_free = 1;
		else if (info->user <= 0 && time_after)
			want_free = 1;

		if (want_free)
			codec_mm_keeper_free_keep(i);
	}

	return 0;
}

int codec_mm_keeper_dump_info(void *buf, int size)
{
	struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();
	char *pbuf = buf;
	char sbuf[512];
	int tsize = 0;
	int s;
	int i;

	if (!pbuf)
		pbuf = sbuf;
#define BUFPRINT(args...) \
		do {\
			s = sprintf(pbuf, args);\
			tsize += s;\
			pbuf += s;\
		} while (0)\

	BUFPRINT("dump keep list:next_id=%d,work_run:%d,num=%d\n",
		 mgr->next_id,
		 mgr->work_runs,
		 mgr->num);
	for (i = 0; i < MAX_KEEP_FRAME; i++) {
		BUFPRINT("keeper:[%d]:\t\tid:%d\thandle:%p,type:%d,user:%d\n",
			 i,
			 mgr->keep_list[i].keep_id,
			 mgr->keep_list[i].handle,
			 mgr->keep_list[i].type,
			 mgr->keep_list[i].user);
	}
#undef BUFPRINT
	if (!buf)
		pr_info("%s", sbuf);

	return tsize;
}

static void codec_mm_keeper_monitor(struct work_struct *work)
{
	struct codec_mm_keeper_mgr *mgr = container_of(work,
					struct codec_mm_keeper_mgr,
					delay_work.work);
	mgr->work_runs++;
	codec_mm_keeper_free_all_keep(0);
	if (mgr->num > 0) /*have some not free, run later.*/
		schedule_delayed_work(&mgr->delay_work, 10);
}

int codec_mm_keeper_mgr_init(void)
{
	struct codec_mm_keeper_mgr *mgr = get_codec_mm_keeper_mgr();

	memset(mgr, 0, sizeof(struct codec_mm_keeper_mgr));
	mgr->next_id = START_KEEP_ID;
	spin_lock_init(&mgr->lock);
	INIT_DELAYED_WORK(&mgr->delay_work, codec_mm_keeper_monitor);
	return 0;
}
