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

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/slab.h>
#include <linux/export.h>
#include <linux/kthread.h>
#include <linux/semaphore.h>

#include <linux/amlogic/media/clk/gp_pll.h>

static DEFINE_MUTEX(gp_mutex);

static struct list_head gp_pll_user_list = LIST_HEAD_INIT(gp_pll_user_list);
static DEFINE_SEMAPHORE(sem);

static void gp_pll_user_consolidate(void)
{
	struct gp_pll_user_handle_s *pos, *grant, *request;

	mutex_lock(&gp_mutex);

	grant = NULL;
	request = NULL;

	list_for_each_entry(pos, &gp_pll_user_list, list) {
		if (!request &&
		    (pos->flag & GP_PLL_USER_FLAG_REQUEST) &&
		    ((pos->flag & GP_PLL_USER_FLAG_GRANTED) == 0)) {
			request = pos;
		}

		if (!grant &&
		    (pos->flag & GP_PLL_USER_FLAG_GRANTED)) {
			grant = pos;
		}
	}

	if (request) {
		if (!grant) {
			request->flag |= GP_PLL_USER_FLAG_GRANTED;

			if (request->callback)
				request->callback(request,
					GP_PLL_USER_EVENT_GRANT);
		} else if (grant->priority > request->priority) {
			if (grant->callback)
				grant->callback(grant,
					GP_PLL_USER_EVENT_YIELD);
		}
	}

	mutex_unlock(&gp_mutex);
}

struct gp_pll_user_handle_s *gp_pll_user_register
	(const char *name,
	u32 priority,
	int (*callback)(struct gp_pll_user_handle_s *, int))
{
	struct gp_pll_user_handle_s *user, *pos;

	user = (struct gp_pll_user_handle_s *)
		kmalloc(sizeof(struct gp_pll_user_handle_s), GFP_KERNEL);

	if (!user)
		return NULL;

	user->name = name;
	user->priority = priority;
	user->callback = callback;
	user->flag = 0;

	mutex_lock(&gp_mutex);

	list_for_each_entry(pos, &gp_pll_user_list, list) {
		if (pos->priority > priority)
			break;
	}

	list_add_tail(&user->list, &pos->list);

	mutex_unlock(&gp_mutex);

	return user;
}
EXPORT_SYMBOL(gp_pll_user_register);

void gp_pll_user_unregister(struct gp_pll_user_handle_s *user)
{
	mutex_lock(&gp_mutex);

	list_del(&user->list);

	mutex_unlock(&gp_mutex);

	if (user->flag & GP_PLL_USER_FLAG_GRANTED)
		up(&sem);
	kfree(user);
}
EXPORT_SYMBOL(gp_pll_user_unregister);

void gp_pll_request(struct gp_pll_user_handle_s *user)
{
	if (user->flag & GP_PLL_USER_FLAG_GRANTED)
		return;

	user->flag = GP_PLL_USER_FLAG_REQUEST;

	up(&sem);
}
EXPORT_SYMBOL(gp_pll_request);

void gp_pll_release(struct gp_pll_user_handle_s *user)
{
	if (user->flag & GP_PLL_USER_FLAG_GRANTED) {
		user->flag = 0;

		up(&sem);
	}

	user->flag = 0;
}
EXPORT_SYMBOL(gp_pll_release);

static int gp_pll_thread(void *data)
{
	while (down_interruptible(&sem) == 0)
		gp_pll_user_consolidate();

	return 0;
}

int __init gp_pll_init(void)
{
	kthread_run(gp_pll_thread, NULL, "gp_pll");

	return 0;
}

