// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (C) 2020 MediaTek Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 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 the
 * GNU General Public License for more details.
 */

#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/of.h>

#include "mdw_rsc.h"
#include "mdw_queue.h"
#include "mdw_cmn.h"
#include "mdw_sched.h"
#include "comm_driver.h"

#define STRING_SIZE_RANGE 16

static struct device *mdw_dev;
static uint32_t g_sched_plcy_show;
int utilization_init_timer_flag;

static ssize_t dsp_task_num_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct mdw_queue *mq = NULL;
	int ret = 0;

	mq = mdw_rsc_get_queue(APUSYS_DEVICE_VPU);
	if (!mq)
		return -EINVAL;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%u\n", (uint32_t)mq->normal_task_num);
	if (ret < 0)
		mdw_drv_warn("show dsp task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(dsp_task_num);

static ssize_t dla_task_num_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct mdw_queue *mq = NULL;
	int ret = 0;

	mq = mdw_rsc_get_queue(APUSYS_DEVICE_MDLA);
	if (!mq)
		return -EINVAL;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%u\n", (uint32_t)mq->normal_task_num);
	if (ret < 0)
		mdw_drv_warn("show dla task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(dla_task_num);

static ssize_t dma_task_num_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	struct mdw_queue *mq = NULL;
	int ret = 0;

	mq = mdw_rsc_get_queue(APUSYS_DEVICE_EDMA);
	if (!mq)
		return -EINVAL;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%u\n", (uint32_t)mq->normal_task_num);
	if (ret < 0)
		mdw_drv_warn("show dma task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(dma_task_num);

#ifdef COMMAND_UTILIZATION
#define STRING_SIZE_UTILIZATION 128
#endif

static ssize_t utilization_task_num_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;
#ifdef COMMAND_UTILIZATION
	struct exec_time_result result;

	result.min = 0xffffffff;
	result.max = 0;
	result.avg = 0;
	result.tot = 0;
	result.cnt = 0;

	if (utilization_init_timer_flag == UTILIZATION_FLAG_ON) {
		exec_time_log_proc(&result);
		mdw_drv_debug("min(%x)\n", result.min);
		mdw_drv_debug("max(%x)\n", result.max);
		mdw_drv_debug("avg(%x)\n", result.avg);
		mdw_drv_debug("tot(%x)\n", result.tot);
		mdw_drv_debug("cnt(%x)\n", result.cnt);
		ret = snprintf(buf, STRING_SIZE_UTILIZATION,
			"min(%d ns), max(%d ns), avg(%d ns), tot(%d ns), cnt(%d)\n",
			result.min
			, result.max, result.avg, result.tot, result.cnt);
	}
#endif
	if (ret < 0)
		mdw_drv_warn("show utilization task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(utilization_task_num);

static ssize_t dla_switch_utilization_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;

#ifdef COMMAND_UTILIZATION
	if (utilization_init_timer_flag != UTILIZATION_FLAG_ON) {
		utilization_init_timer_flag = UTILIZATION_FLAG_ON;
		mdw_drv_debug("utilization on\n");
		exec_time_log_init();
	} else {
		mdw_drv_debug("already utilization on\n");
	}
#endif
	return ret;
}
static DEVICE_ATTR_RO(dla_switch_utilization);

static struct attribute *mdw_task_attrs[] = {
	&dev_attr_dsp_task_num.attr,
	&dev_attr_dla_task_num.attr,
	&dev_attr_dma_task_num.attr,
	&dev_attr_utilization_task_num.attr,
	&dev_attr_dla_switch_utilization.attr,
	NULL,
};

static struct attribute_group mdw_devinfo_attr_group = {
	.name	= "queue",
	.attrs	= mdw_task_attrs,
};

#define COMPATIBLE_NAME	"mtk,apusys"
#define PLAT_NAME	"plat_name"
#define GSM_SIZE	"gsm_size"
#define CONV_SIZE	"conv_size"
#define MDW_SW_VER	"mdw_sw_ver"
#define DLA_HW_VER	"mdla_hw_ver"
#define DMA_HW_VER	"edma_hw_ver"

static u32 gsm_size;
static u32 conv_size;
static const char *plat_name;
static const char *mdw_sw_ver;
static const char *dla_hw_ver;
static const char *dma_hw_ver;

int mdw_dev_info_init(void)
{
	struct device_node *np;

	np = of_find_compatible_node(NULL, NULL, COMPATIBLE_NAME);
	if (!np) {
		pr_crit("of_node: %s not found\n", COMPATIBLE_NAME);
		return -EFAULT;
	}

	if (of_property_read_string(np, PLAT_NAME, &plat_name)) {
		pr_crit("property %s not found\n", PLAT_NAME);
		return -EFAULT;
	}

	if (of_property_read_u32(np, GSM_SIZE, &gsm_size)) {
		pr_crit("property %s not found\n", GSM_SIZE);
		return -EFAULT;
	}

	if (of_property_read_u32(np, CONV_SIZE, &conv_size)) {
		pr_crit("property %s not found\n", CONV_SIZE);
		return -EFAULT;
	}

	if (of_property_read_string(np, MDW_SW_VER, &mdw_sw_ver)) {
		pr_crit("property %s not found\n", MDW_SW_VER);
		return -EFAULT;
	}

	if (of_property_read_string(np, DLA_HW_VER, &dla_hw_ver)) {
		pr_crit("property %s not found\n", DLA_HW_VER);
		return -EFAULT;
	}

	if (of_property_read_string(np, DMA_HW_VER, &dma_hw_ver)) {
		pr_crit("property %s not found\n", DMA_HW_VER);
		return -EFAULT;
	}

	return 0;
}

static ssize_t gsm_size_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%u Kb\n", (uint32_t)gsm_size);
	if (ret < 0)
		mdw_drv_warn("show dla task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(gsm_size);

static ssize_t conv_size_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%u Kb\n", conv_size);
	if (ret < 0)
		mdw_drv_warn("show dla task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(conv_size);

static ssize_t mdw_sw_ver_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%s\n", mdw_sw_ver);
	if (ret < 0)
		mdw_drv_warn("show dla task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(mdw_sw_ver);

static ssize_t dla_hw_ver_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%s\n", dla_hw_ver);
	if (ret < 0)
		mdw_drv_warn("show dla task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(dla_hw_ver);

static ssize_t dma_hw_ver_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	int ret = 0;

	ret = snprintf(buf, STRING_SIZE_RANGE, "%s\n", dma_hw_ver);
	if (ret < 0)
		mdw_drv_warn("show dla task num fail(%d)\n", ret);

	return ret;
}
static DEVICE_ATTR_RO(dma_hw_ver);

static struct attribute *mdw_plat_attrs[] = {
	&dev_attr_gsm_size.attr,
	&dev_attr_conv_size.attr,
	&dev_attr_dla_hw_ver.attr,
	&dev_attr_mdw_sw_ver.attr,
	&dev_attr_dma_hw_ver.attr,
	NULL,
};

static struct attribute_group mdw_platinfo_attr_group = {
	.name	= "plat_info",
	.attrs	= mdw_plat_attrs,
};

static ssize_t policy_show(struct device *dev,
	struct device_attribute *attr, char *buf)
{
	char *p = buf;

	mdw_drv_debug("g_sched_plcy_show(%u)\n", g_sched_plcy_show);
	if (!g_sched_plcy_show)
		p += sprintf(p, "%u\n", mdw_rsc_get_preempt_plcy());
	else {
		p += sprintf(p, "preemption(%u)\n", mdw_rsc_get_preempt_plcy());
		p += sprintf(p, "  0: rr,simple\n");
		p += sprintf(p, "  1: rr,prefer lower priority\n");
	}

	WARN_ON(p - buf >= PAGE_SIZE);

	return p - buf;
}

static ssize_t policy_store(struct device *dev,
	struct device_attribute *attr, const char *buf,
	size_t count)
{
	char *tmp, *val, *cursor, *plcy;
	uint32_t in = 0;
	int ret = 0;

	tmp = kzalloc(count + 1, GFP_KERNEL);
	if (!tmp)
		return -ENOMEM;

	memcpy(tmp, buf, count);
	tmp[count] = '\0';
	cursor = tmp;

	/* parse a command */
	plcy = strsep(&cursor, " ");
	if (!plcy)
		return -EPERM;

	val = strsep(&cursor, " ");
	if (!val)
		return -EPERM;

	ret = kstrtoint(val, 0, &in);
	if (ret) {
		mdw_drv_err("get preempt arg fail(%d)\n", ret);
		goto out;
	}

	if (!strncmp(plcy, "preemption", strlen(plcy))) {
		ret = mdw_rsc_set_preempt_plcy(in);
		if (ret) {
			mdw_drv_err("set preempt plcy(%u) fail(%d)\n",
				in, ret);
			goto out;
		}
	} else if (!strncmp(plcy, "show_info", strlen(plcy))) {
		g_sched_plcy_show = in;
	}

	mdw_drv_debug("plcy(%s), in(%u)\n", plcy, in);

	ret = count;
out:
	kfree(tmp);
	return ret;
}
static DEVICE_ATTR_RW(policy);

static struct attribute *mdw_sched_attrs[] = {
	&dev_attr_policy.attr,
	NULL,
};

static struct attribute_group mdw_sched_attr_group = {
	.name	= "sched",
	.attrs	= mdw_sched_attrs,
};

int mdw_sysfs_init(struct device *kdev)
{
	int ret = 0;

	g_sched_plcy_show = 0;

	mdw_dev_info_init();

	/* create /sys/devices/platform/apusys/device/xxx */
	mdw_dev = kdev;
	ret = sysfs_create_group(&mdw_dev->kobj, &mdw_devinfo_attr_group);
	if (ret)
		mdw_drv_err("create mdw devinfo attr fail, ret %d\n", ret);
	ret = sysfs_create_group(&mdw_dev->kobj, &mdw_sched_attr_group);
	if (ret)
		mdw_drv_err("create mdw sched attr fail, ret %d\n", ret);

	ret = sysfs_create_group(&mdw_dev->kobj, &mdw_platinfo_attr_group);
	if (ret)
		mdw_drv_err("create mdw platinfo attr fail, ret %d\n", ret);

	return ret;
}

void mdw_sysfs_exit(void)
{
	if (mdw_dev) {
		sysfs_remove_group(&mdw_dev->kobj, &mdw_devinfo_attr_group);
		sysfs_remove_group(&mdw_dev->kobj, &mdw_sched_attr_group);
		sysfs_remove_group(&mdw_dev->kobj, &mdw_platinfo_attr_group);
	}

	mdw_dev = NULL;
}
