// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (c) 2019 MediaTek Inc.
 */

#include <linux/init.h>
#include <linux/export.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/io.h>
#include "mtk_dcm_common.h"
#include <mtk_dcm.h>

DEFINE_MUTEX(dcm_lock);
static short dcm_debug;
static short dcm_initiated;
struct DCM *common_dcm_array;
struct DCM_OPS *common_dcm_ops;
unsigned int common_init_dcm_type;
unsigned int common_all_dcm_type;
unsigned int common_init_dcm_type_by_k;


unsigned int __attribute__((weak)) dcm_get_chip_sw_ver(void)
{
	return 0;
}

/*****************************************
 * DCM driver will provide regular APIs :
 * 1. dcm_restore(type) to recovery CURRENT_STATE before any power-off reset.
 * 2. dcm_set_default(type) to reset as cold-power-on init state.
 * 3. dcm_disable(type) to disable all dcm.
 * 4. dcm_set_state(type) to set dcm state.
 * 5. dcm_dump_state(type) to show CURRENT_STATE.
 * 6. /sys/dcm/dcm_state interface:
 *			'restore', 'disable', 'dump', 'set'. 4 commands.
 *
 * spsecified APIs for workaround:
 * 1. (definitely no workaround now)
 *****************************************/
void dcm_set_default(unsigned int type)
{
	int i;
	struct DCM *dcm;

	dcm_pr_info("[%s]type:0x%08x, init_dcm_type=0x%x, INIT_DCM_TYPE_BY_K=0x%x\n",
		 __func__, type, common_init_dcm_type,
		 common_init_dcm_type_by_k);

	mutex_lock(&dcm_lock);

	for (i = 0, dcm = &common_dcm_array[0]; i < NR_DCM_TYPE; i++, dcm++) {
		if (type & dcm->typeid) {
			dcm->saved_state = dcm->default_state;
			dcm->current_state = dcm->default_state;
			dcm->disable_refcnt = 0;
			if (common_init_dcm_type_by_k & dcm->typeid) {
				if (dcm->preset_func)
					dcm->preset_func();
				dcm->func(dcm->current_state);
			}

			dcm_pr_info("[%16s 0x%08x] current state:%d (%d)\n",
				 dcm->name, dcm->typeid, dcm->current_state,
				 dcm->disable_refcnt);
		}
	}

	/*dcm_smc_msg_send(common_init_dcm_type);*/

	mutex_unlock(&dcm_lock);
}

void dcm_set_state(unsigned int type, int state)
{
	int i;
	struct DCM *dcm;
	unsigned int init_dcm_type_pre = common_init_dcm_type;

	dcm_pr_info("[%s]type:0x%08x, set:%d, init_dcm_type_pre=0x%x\n",
		 __func__, type, state, init_dcm_type_pre);

	mutex_lock(&dcm_lock);

	for (i = 0, dcm = &common_dcm_array[0];
		type && (i < NR_DCM_TYPE); i++, dcm++) {
		if (type & dcm->typeid) {
			type &= ~(dcm->typeid);

			dcm->saved_state = state;
			if (dcm->disable_refcnt == 0) {
				if (state)
					common_init_dcm_type |= dcm->typeid;
				else
					common_init_dcm_type &= ~(dcm->typeid);

				dcm->current_state = state;
				dcm->func(dcm->current_state);
			}

			dcm_pr_info("[%16s 0x%08x] current state:%d (%d)\n",
				 dcm->name, dcm->typeid, dcm->current_state,
				 dcm->disable_refcnt);

		}
	}

	if (init_dcm_type_pre != common_init_dcm_type) {
		dcm_pr_info("[%s]type:0x%08x, set:%d, init_dcm_type=0x%x->0x%x\n",
			__func__, type, state,
			init_dcm_type_pre,
			common_init_dcm_type);
		/*dcm_smc_msg_send(common_init_dcm_type);*/
	}

	mutex_unlock(&dcm_lock);
}

void dcm_disable(unsigned int type)
{
	int i;
	struct DCM *dcm;
	unsigned int init_dcm_type_pre = common_init_dcm_type;

	dcm_pr_info("[%s]type:0x%08x\n", __func__, type);

	mutex_lock(&dcm_lock);

	for (i = 0, dcm = &common_dcm_array[0];
		type && (i < NR_DCM_TYPE); i++, dcm++) {
		if (type & dcm->typeid) {
			type &= ~(dcm->typeid);

			dcm->current_state = DCM_OFF;
			if (dcm->disable_refcnt++ == 0)
				common_init_dcm_type &= ~(dcm->typeid);
			dcm->func(dcm->current_state);

			dcm_pr_info("[%16s 0x%08x] current state:%d (%d)\n",
				 dcm->name, dcm->typeid, dcm->current_state,
				 dcm->disable_refcnt);

		}
	}

	if (init_dcm_type_pre != common_init_dcm_type) {
		dcm_pr_info("[%s]type:0x%08x, init_dcm_type=0x%x->0x%x\n",
			 __func__, type, init_dcm_type_pre,
			 common_init_dcm_type);
		/*dcm_smc_msg_send(common_init_dcm_type);*/
	}

	mutex_unlock(&dcm_lock);

}
EXPORT_SYMBOL(dcm_disable);

void dcm_restore(unsigned int type)
{
	int i;
	struct DCM *dcm;
	unsigned int init_dcm_type_pre = common_init_dcm_type;

	dcm_pr_info("[%s]type:0x%08x\n", __func__, type);

	mutex_lock(&dcm_lock);

	for (i = 0, dcm = &common_dcm_array[0];
		type && (i < NR_DCM_TYPE); i++, dcm++) {
		if (type & dcm->typeid) {
			type &= ~(dcm->typeid);

			if (dcm->disable_refcnt > 0)
				dcm->disable_refcnt--;
			if (dcm->disable_refcnt == 0) {
				if (dcm->saved_state)
					common_init_dcm_type |= dcm->typeid;
				else
					common_init_dcm_type &= ~(dcm->typeid);

				dcm->current_state = dcm->saved_state;
				dcm->func(dcm->current_state);
			}

			dcm_pr_info("[%16s 0x%08x] current state:%d (%d)\n",
				 dcm->name, dcm->typeid, dcm->current_state,
				 dcm->disable_refcnt);

		}
	}

	if (init_dcm_type_pre != common_init_dcm_type) {
		dcm_pr_info("[%s]type:0x%08x, init_dcm_type=0x%x->0x%x\n",
			 __func__, type, init_dcm_type_pre,
			 common_init_dcm_type);
		/*dcm_smc_msg_send(common_init_dcm_type);*/
	}

	mutex_unlock(&dcm_lock);
}

void dcm_dump_state(int type)
{
	int i;
	struct DCM *dcm;

	dcm_pr_info("\n******** Kernel dcm dump state *********\n");
	for (i = 0, dcm = &common_dcm_array[0]; i < NR_DCM_TYPE; i++, dcm++) {
		if (type & dcm->typeid) {
			dcm_pr_info("[%-16s 0x%08x] current state:%d (%d)\n",
				 dcm->name, dcm->typeid, dcm->current_state,
				 dcm->disable_refcnt);
		}
	}
}
EXPORT_SYMBOL(dcm_dump_state);

#ifdef CONFIG_PM
static ssize_t dcm_state_show(struct kobject *kobj, struct kobj_attribute *attr,
				  char *buf)
{
	int len = 0;
	int i;
	struct DCM *dcm;

	/* dcm_dump_state(common_all_dcm_type); */
	len += snprintf(buf+len, PAGE_SIZE-len,
			"\n******** dcm dump state *********\n");
	for (i = 0, dcm = &common_dcm_array[0]; i < NR_DCM_TYPE; i++, dcm++)
		len += snprintf(buf+len, PAGE_SIZE-len,
				"[%-16s 0x%08x] current state:%d (%d)\n",
				dcm->name, dcm->typeid, dcm->current_state,
				dcm->disable_refcnt);

	len += snprintf(buf+len, PAGE_SIZE-len,
			"\n********** dcm_state help *********\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"set:       echo set [mask] [mode] > /sys/dcm/dcm_state\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"disable:   echo disable [mask] > /sys/dcm/dcm_state\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"restore:   echo restore [mask] > /sys/dcm/dcm_state\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"dump:      echo dump [mask] > /sys/dcm/dcm_state\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"debug:     echo debug [0/1] > /sys/dcm/dcm_state\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"***** [mask] is hexl bit mask of dcm;\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"***** [mode] is type of DCM to set and retained\n");
	len += snprintf(buf+len, PAGE_SIZE-len,
			"init_dcm_type=0x%x, all_dcm_type=0x%x, dcm_debug=%d, ",
			common_init_dcm_type, common_all_dcm_type, dcm_debug);
	len += snprintf(buf+len, PAGE_SIZE-len, "dcm_get_chip_sw_ver=0x%x\n",
			dcm_get_chip_sw_ver());

	return len;
}

static ssize_t dcm_state_store(struct kobject *kobj,
				   struct kobj_attribute *attr, const char *buf,
				   size_t n)
{
	char cmd[16];
	unsigned int mask;
	/*unsigned int val0, val1;*/
	int ret, mode;

	if (sscanf(buf, "%15s %x", cmd, &mask) == 2) {
		mask &= common_all_dcm_type;

		if (!strcmp(cmd, "restore")) {
			/* dcm_dump_regs(); */
			dcm_restore(mask);
			/* dcm_dump_regs(); */
		} else if (!strcmp(cmd, "disable")) {
			/* dcm_dump_regs(); */
			dcm_disable(mask);
			/* dcm_dump_regs(); */
		} else if (!strcmp(cmd, "dump")) {
			dcm_dump_state(mask);
			common_dcm_ops->dump_regs();
		} else if (!strcmp(cmd, "debug")) {
			if (mask == 0) {
				dcm_debug = 0;
				common_dcm_ops->set_debug_mode(dcm_debug);
			} else if (mask == 1) {
				dcm_debug = 1;
				common_dcm_ops->set_debug_mode(dcm_debug);
			}
		} else if (!strcmp(cmd, "set")) {
			if (sscanf(buf, "%15s %x %d", cmd, &mask, &mode) == 3) {
				mask &= common_all_dcm_type;
				if (mode == 0 || mode == 1) {
					dcm_set_state(mask, mode);
				} else {
					dcm_pr_info("SORRY, do not support your value(must be 1 or 0): %s\n",
				    cmd);
				}

			}
		} else {
			dcm_pr_info("SORRY, do not support your command: %s\n",
				    cmd);
		}
		ret = n;
	} else {
		dcm_pr_info("SORRY, do not support your command.\n");
		ret = -EINVAL;
	}

	return ret;
}

static struct kobj_attribute dcm_state_attr = {
	.attr = {
		 .name = "dcm_state",
		 .mode = 0644,
		 },
	.show = dcm_state_show,
	.store = dcm_state_store,
};
static struct kobject *kobj;
#endif /* #ifdef CONFIG_PM */

int mt_dcm_common_init(void)
{

	unsigned int default_type;
	int default_state;
	int err = 0;
	/*dcm_pr_info("[%s]: dcm common init\n", __func__);*/
	if (common_dcm_ops == NULL) {
		dcm_pr_notice("[%s] dcm common ops null\n",
					__func__);
		return -1;
	}
	common_dcm_ops->dump_regs();
	common_dcm_ops->get_default(&default_type, &default_state);
	common_dcm_ops->get_init_type(&common_init_dcm_type);
	common_dcm_ops->get_all_type(&common_all_dcm_type);
	common_dcm_ops->get_init_by_k_type(&common_init_dcm_type_by_k);

	if (default_state == DCM_DEFAULT)
		dcm_set_default(default_type);
	else
		dcm_set_state(default_type, default_state);

#ifdef CONFIG_PM
	{
		kobj = kobject_create_and_add("dcm", NULL);
		if (!kobj)
			return -ENOMEM;

		err = sysfs_create_file(kobj, &dcm_state_attr.attr);
		if (err)
			dcm_pr_notice("[%s]: fail to create sysfs\n", __func__);
	}

#ifdef DCM_DEBUG_MON
	{
		err = sysfs_create_file(power_kobj, &dcm_debug_mon_attr.attr);
		if (err)
			dcm_pr_notice("[%s]: fail to create sysfs\n", __func__);
	}
#endif /* #ifdef DCM_DEBUG_MON */
#endif /* #ifdef CONFIG_PM */


	dcm_initiated = 1;

	return err;
}
EXPORT_SYMBOL(mt_dcm_common_init);

void mt_dcm_array_register(struct DCM *array, struct DCM_OPS *ops)
{
	common_dcm_array = array;
	common_dcm_ops = ops;
}
EXPORT_SYMBOL(mt_dcm_array_register);

/**** public APIs *****/
bool is_dcm_initialized(void)
{
	bool ret = true;

	if (!dcm_initiated)
		ret = false;
	return ret;
}
EXPORT_SYMBOL(is_dcm_initialized);

void mt_dcm_disable(void)
{
	if (!dcm_initiated)
		return;

	dcm_disable(common_all_dcm_type);
}

void mt_dcm_restore(void)
{
	if (!dcm_initiated)
		return;

	dcm_restore(common_all_dcm_type);
}

static int __init mtk_dcm_init(void)
{
	dcm_debug = 0;
	dcm_initiated = 0;
	return 0;
}
//arch_initcall(mt6779_dcm_init);

static void mtk_dcm_exit(void)
{
}
module_init(mtk_dcm_init);
module_exit(mtk_dcm_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("MediaTek DCM driver");
