// 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.h>
#include <linux/delay.h>
#include "mali_kbase_platform_mtk_dtv.h"
#include "mali_kbase_platform_mtk_dtv_mt5897_reg.h"

#ifdef CONFIG_DEBUG_FS
extern int mali_gpu_clock;
extern int mali_debug_property;

static void update_frequency(u32 frequency)
{
    mali_gpu_clock = frequency;
    /* set GPU clock */
    SET_REG(GPUBRDGPLL, pd_gpubrdgpll, 0x0);
    SET_REG(GPUBRDGPLL, gc_gpubrdgpll_loopdiv_second, (frequency/GPU_CLOCK_STEP));
    udelay(1);
}

static ssize_t gpu_clock_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
    char buf[64];
    size_t r;

    r = snprintf(buf, 64, "%u\n", mali_gpu_clock);

    if ((r < 0) || (r >= sizeof(buf)))
    {
        return 0;
    }

    return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_clock_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
    int ret;
    char buffer[32];
    unsigned long val;

    if (cnt >= sizeof(buffer))
    {
        return -ENOMEM;
    }

    if (copy_from_user(&buffer[0], ubuf, cnt))
    {
        return -EFAULT;
    }

    buffer[cnt] = '\0';
    ret = kstrtoul(&buffer[0], 10, &val);

    if (0 != ret)
    {
        return -EINVAL;
    }

    if (mali_debug_property == MALI_DEBUG_PROPERTY_PASSWORD)
    {
        update_frequency(val);
    }

    return cnt;
}

static const struct file_operations kbasep_gpu_clock_debugfs_fops = {
    .read = gpu_clock_read,
    .write = gpu_clock_write,
};

#if defined(CONFIG_MALI_DEBUG) && (defined(MTK_DTV_ZEBU) || defined(MTK_DTV_HAPS)) && !defined(MTK_DTV_DEVICE_TREE_PROBE)

#define RIU_DW ((volatile unsigned int*)(MTK_DTV_RIU_BASE))
#define RIU_B ((volatile unsigned char*)(MTK_DTV_RIU_BASE))

static unsigned int g_mst_reg_adress = 0x1c000000;
static unsigned int g_mst_reg_bank = 0x2ee9;
static unsigned int g_mst_reg_base = 0x49;
static unsigned int g_mst_lsb = 0;
static unsigned int g_mst_msb = 15;

static unsigned int mst_get_reg(void)
{
	unsigned int mask_bank_name ;
	unsigned int shift_bank_name ;

	mask_bank_name = ((1 << (g_mst_msb - g_mst_lsb + 1)) - 1) << g_mst_lsb;
	shift_bank_name = g_mst_lsb;

	return ((RIU[(g_mst_reg_bank << 8) + (g_mst_reg_base <<1) ] & mask_bank_name) >> shift_bank_name );
}

static unsigned int mst_get_reg_dw(void)
{
	return RIU_DW[(g_mst_reg_bank << 7) + g_mst_reg_base ];
}

static void mst_set_reg(unsigned int value)
{
	unsigned int mask_bank_name ;
	unsigned int shift_bank_name ;

	mask_bank_name = ((1 << (g_mst_msb - g_mst_lsb + 1)) - 1) << g_mst_lsb;
	shift_bank_name = g_mst_lsb;

	RIU[(g_mst_reg_bank << 8)  + (g_mst_reg_base <<1)] &= ~mask_bank_name; \
	RIU[(g_mst_reg_bank << 8)  + (g_mst_reg_base <<1)] |= ((value) << shift_bank_name) & mask_bank_name;
}

static void mst_set_reg_dw(unsigned int value)
{
	RIU_DW[(g_mst_reg_bank << 7)  + g_mst_reg_base] = value;
}

static ssize_t gpu_reg_bank_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
	char buf[64];
	size_t r;
	r = snprintf(buf, 64, "0x%x\n", g_mst_reg_bank);

	return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_reg_bank_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	int ret;
	char buffer[32];
	unsigned long val;

	if (cnt >= sizeof(buffer))
	{
		return -ENOMEM;
	}

	if (copy_from_user(&buffer[0], ubuf, cnt))
	{
		return -EFAULT;
	}

	buffer[cnt] = '\0';
	ret = kstrtoul(&buffer[0], 0, &val);

	g_mst_reg_bank = val;

	if (0 != ret)
	{
		return -EINVAL;
	}

	return cnt;
}

static ssize_t gpu_reg_base_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
	char buf[64];
	size_t r;
	r = snprintf(buf, 64, "0x%x\n", g_mst_reg_base);

	return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_reg_base_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	int ret;
	char buffer[32];
	unsigned long val;

	if (cnt >= sizeof(buffer))
	{
		return -ENOMEM;
	}

	if (copy_from_user(&buffer[0], ubuf, cnt))
	{
		return -EFAULT;
	}

	buffer[cnt] = '\0';
	ret = kstrtoul(&buffer[0], 0, &val);

	g_mst_reg_base = val;

	if (0 != ret)
	{
		return -EINVAL;
	}

	return cnt;
}

static ssize_t gpu_reg_lsb_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
	char buf[64];
	size_t r;
	r = snprintf(buf, 64, "%u\n", g_mst_lsb);

	return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_reg_lsb_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	int ret;
	char buffer[32];
	unsigned long val;

	if (cnt >= sizeof(buffer))
	{
		return -ENOMEM;
	}

	if (copy_from_user(&buffer[0], ubuf, cnt))
	{
		return -EFAULT;
	}

	buffer[cnt] = '\0';
	ret = kstrtoul(&buffer[0], 0, &val);

	g_mst_lsb = val;

	if (0 != ret)
	{
		return -EINVAL;
	}

	return cnt;
}

static ssize_t gpu_reg_msb_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
	char buf[64];
	size_t r;
	r = snprintf(buf, 64, "%u\n", g_mst_msb);

	return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_reg_msb_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	int ret;
	char buffer[32];
	unsigned long val;

	if (cnt >= sizeof(buffer))
	{
		return -ENOMEM;
	}

	if (copy_from_user(&buffer[0], ubuf, cnt))
	{
		return -EFAULT;
	}

	buffer[cnt] = '\0';
	ret = kstrtoul(&buffer[0], 0, &val);

	g_mst_msb = val;

	if (0 != ret)
	{
		return -EINVAL;
	}

	return cnt;
}

static ssize_t gpu_reg_operate_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
	char buf[64];
	size_t r;
	unsigned int reg;
	reg = mst_get_reg();

	r = snprintf(buf, 64, "0x%x\n", reg);

	g_mst_reg_adress = MTK_DTV_RIU_BASE + g_mst_reg_bank*0x200 + g_mst_reg_base *4;

       printk("[read] 0x%x, Bank: 0x%x, Reg: 0x%x [%d:%d] = 0x%x\n", g_mst_reg_adress, g_mst_reg_bank,  g_mst_reg_base, g_mst_msb, g_mst_lsb, reg);

	return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_reg_operate_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	int ret;
	char buffer[32];
	unsigned long val;

	if (cnt >= sizeof(buffer))
	{
		return -ENOMEM;
	}

	if (copy_from_user(&buffer[0], ubuf, cnt))
	{
		return -EFAULT;
	}

	buffer[cnt] = '\0';
	ret = kstrtoul(&buffer[0], 0, &val);

	mst_set_reg((unsigned int)val);

       g_mst_reg_adress = MTK_DTV_RIU_BASE + g_mst_reg_bank*0x200 + g_mst_reg_base *4;
	printk("[write] 0x%x, Bank: 0x%x, Reg: 0x%x [%d:%d] = 0x%x\n",g_mst_reg_adress,  g_mst_reg_bank,  g_mst_reg_base, g_mst_msb, g_mst_lsb,  (unsigned int)val);

	if (0 != ret)
	{
		return -EINVAL;
	}

	return cnt;
}

static ssize_t gpu_reg_dw_operate_read(struct file *filp, char __user *ubuf, size_t cnt, loff_t *ppos)
{
	char buf[64];
	size_t r;
	unsigned int reg;
	reg = mst_get_reg_dw();


	r = snprintf(buf, 64, "0x%x\n", reg);

	g_mst_reg_adress = MTK_DTV_RIU_BASE + g_mst_reg_bank*0x200 + g_mst_reg_base *4;

       printk("[read] 0x%x, Bank: 0x%x, Reg: 0x%x [31:0] = 0x%x\n", g_mst_reg_adress, g_mst_reg_bank,  g_mst_reg_base, reg);

	return simple_read_from_buffer(ubuf, cnt, ppos, buf, r);
}

static ssize_t gpu_reg_dw_operate_write(struct file *filp, const char __user *ubuf, size_t cnt, loff_t *ppos)
{
	int ret;
	char buffer[32];
	unsigned long val;

	if (cnt >= sizeof(buffer))
	{
		return -ENOMEM;
	}

	if (copy_from_user(&buffer[0], ubuf, cnt))
	{
		return -EFAULT;
	}

	buffer[cnt] = '\0';
	ret = kstrtoul(&buffer[0], 0, &val);

	mst_set_reg_dw((unsigned int)val);

       g_mst_reg_adress = MTK_DTV_RIU_BASE + g_mst_reg_bank*0x200 + g_mst_reg_base *4;
	printk("[write] 0x%x, Bank: 0x%x, Reg: 0x%x [31:0] = 0x%x\n", g_mst_reg_adress,  g_mst_reg_bank,  g_mst_reg_base,  (unsigned int)val);

	if (0 != ret)
	{
		return -EINVAL;
	}

	return cnt;
}

static const struct file_operations kbasep_gpu_reg_bank_debugfs_fops = {
	.read = gpu_reg_bank_read,
	.write = gpu_reg_bank_write,
};
static const struct file_operations kbasep_gpu_reg_base_debugfs_fops = {
	.read = gpu_reg_base_read,
	.write = gpu_reg_base_write,
};

static const struct file_operations kbasep_gpu_reg_lsb_debugfs_fops = {
	.read = gpu_reg_lsb_read,
	.write = gpu_reg_lsb_write,
};
static const struct file_operations kbasep_gpu_reg_msb_debugfs_fops = {
	.read = gpu_reg_msb_read,
	.write = gpu_reg_msb_write,
};

static const struct file_operations kbasep_gpu_reg_operate_debugfs_fops = {
	.read = gpu_reg_operate_read,
	.write = gpu_reg_operate_write,
};

static const struct file_operations kbasep_gpu_reg_dw_operate_debugfs_fops = {
	.read = gpu_reg_dw_operate_read,
	.write = gpu_reg_dw_operate_write,
};
#endif

/*
 *  Initialize debugfs entry for gpu clock
 */
void kbasep_gpu_clock_debugfs_init(struct kbase_device *kbdev)
{
	debugfs_create_file("gpu_clock", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_clock_debugfs_fops);
#if defined(CONFIG_MALI_DEBUG) && (defined(MTK_DTV_ZEBU) || defined(MTK_DTV_HAPS)) && !defined(MTK_DTV_DEVICE_TREE_PROBE)
	debugfs_create_file("reg_bank", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_reg_bank_debugfs_fops);
	debugfs_create_file("reg_base", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_reg_base_debugfs_fops);
	debugfs_create_file("reg_lsb", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_reg_lsb_debugfs_fops);
	debugfs_create_file("reg_msb", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_reg_msb_debugfs_fops);
	debugfs_create_file("reg", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_reg_operate_debugfs_fops);
	debugfs_create_file("reg_dw", S_IRUGO,
			kbdev->mali_debugfs_directory, NULL,
			&kbasep_gpu_reg_dw_operate_debugfs_fops);
#endif

    return;
}

#else
/*
 * Stub functions for when debugfs is disabled
 */
void kbasep_gpu_clock_debugfs_init(struct kbase_device *kbdev)
{
    return;
}
#endif
