// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */

#include <linux/of_device.h>
#include <linux/io.h>
#include <linux/spinlock.h>
#include <linux/mutex.h>
#include <linux/interrupt.h>
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/debugfs.h>
#include <linux/version.h>

#include <common/mdla_device.h>
#include <common/mdla_cmd_proc.h>
#include <common/mdla_ioctl.h>
#include <common/mdla_scheduler.h>
#include <utilities/mdla_debug.h>
#include <utilities/mdla_util.h>
#include <utilities/mdla_trace.h>
#include <platform/mdla_plat_api.h>

#include "comm_driver.h"
#include "mdla_cmd_data_v2_0.h"
#include "mdla_hw_reg_v2_0.h"
#include "mdla_plat_model.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 15, 0)
#include <linux/panic_notifier.h>
#else
#include <linux/kernel.h>
#endif
#include <soc/mediatek/mtktv-miup.h>

extern int mtk_miup_get_hit_status(struct mtk_miup_hitlog *hit_log);

#define MIU_INFO		"miu_info"

struct hit_tbl {
	u32 mask;
	u32 val;
};

struct mdla_hit_info {
	u32 version;
	u32 count;
	struct hit_tbl tbl[];
};

static struct mdla_hit_info *hit_info;

static bool miup_event_on_mdla(void)
{
	struct mtk_miup_hitlog hit_log;
	struct hit_tbl *tbl;
	int i;

	if (mtk_miup_get_hit_status(&hit_log)) {
		pr_crit("[MDLA][MIUP][CID:0x%llX %s address: 0x%llX - 0x%llX block:%d\n",
				hit_log.cid, (hit_log.attr == 0) ? "read" : "write",
				hit_log.hit_addr, (hit_log.hit_addr + 0x1F), hit_log.hit_blk);

		for (i = 0; i < hit_info->count; i++) {
			tbl = &hit_info->tbl[i];

			if ((hit_log.cid & tbl->mask) == (tbl->val & tbl->mask)) {
				pr_crit("%s(): CID:%llx match on mask:%x val:%x\n",
					__func__, hit_log.cid, tbl->mask, tbl->val);
				return true;
			}
		}
	}

	return false;
}

static int mdla_panic_event_handler(struct notifier_block *this, unsigned long event, void *ptr)
{
	struct command_entry *ce;
	struct mdla_dev *mdla_device;
	u32 core_id;

	pr_crit("\033[1;41m%s-%u\033[m\n", __func__, __LINE__);

	if (miup_event_on_mdla()) {
		// unmask and enable mdla log
		mdla_dbg_write_u32(FS_KLOG_LVL, MDLA_KLOG_LVL_CRIT);

		for_each_mdla_core(core_id) {
			mdla_device = mdla_get_device(core_id);
			ce = mdla_device->sched->pro_ce;
			if (ce) {
				mdla_err("Print process ce\n");
				mdla_dbg_ce_info(core_id, ce);
				mdla_dbg_plat_cb()->dump_reg(core_id, NULL);

				mdla_err("%s(): core:%d mva:%x count:%x kva:%lx\n",
						__func__, core_id, ce->mva,
						ce->count, (uintptr_t)ce->kva);
				//TODO: check secure path load and str sequence
				if (!ce->secure) {
					mdla_dbg_plat_cb()->create_dump_cmdbuf(mdla_device, ce);
					mdla_dbg_plat_cb()->memory_show(NULL);
					mdla_plat_model_dump_info(NULL);
				}
			}
		}
	}

	return NOTIFY_DONE;
}

static int of_mdla_miu_info_get(struct device *dev, struct mdla_hit_info **info)
{
	int num;
	u32 *arr;

	num = of_property_count_elems_of_size(dev->of_node, MIU_INFO, sizeof(u32));
	if (num <= 0) {
		dev_err(dev, "%s: %s property not defined\n", __func__, MIU_INFO);
		return -EFAULT;
	}

	arr = vzalloc(num * sizeof(u32));
	if (arr == NULL)
		return -ENOMEM;

	if (of_property_read_u32_array(dev->of_node, MIU_INFO, arr, num)) {
		vfree(arr);
		return -EFAULT;
	}

	*info = (struct mdla_hit_info *)arr;

	return 0;
}

static struct notifier_block panic_notify = {
	.notifier_call = mdla_panic_event_handler,
	.priority = 0,
};

int mdla_panic_notify_init(struct device *dev)
{
	int i;

	if (of_mdla_miu_info_get(dev, &hit_info))
		return -EFAULT;

	for (i = 0; i < hit_info->count; i++)
		dev_info(dev, "%s(): [%x] = [%x/%x]", __func__,
			i, hit_info->tbl[i].mask, hit_info->tbl[i].val);

	atomic_notifier_chain_register(&panic_notifier_list, &panic_notify);

	return 0;
}

void mdla_panic_notify_uninit(struct device *dev)
{
	if (hit_info) {
		vfree(hit_info);
		hit_info = NULL;
	}

	atomic_notifier_chain_unregister(&panic_notifier_list, &panic_notify);
}

