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

#include <linux/version.h>
#include <asm/barrier.h>
#include <linux/clk.h>
#include <linux/dma-map-ops.h>
#include <linux/dma-mapping.h>
#include <linux/dma-direct.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/mfd/syscon.h>
#include <linux/module.h>
#include <linux/of_address.h>
#include <linux/of_platform.h>
#include <linux/of_reserved_mem.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/remoteproc.h>
#include <linux/mutex.h>
#include <linux/string.h>
#include <linux/devcoredump.h>
#include <linux/workqueue.h>
#include <linux/pinctrl/consumer.h>
#include <linux/list.h>
#include <linux/ktime.h>
#include <linux/debugfs.h>
#include <soc/mediatek/mtk-pm.h>

#include "mtk-reserved-memory.h"
#include "remoteproc_internal.h"
#include "remoteproc_elf_helpers.h"

#include "mtk_pqu_fdt.h"
#include "mtk_pqu_internal.h"

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 16, 0)
MODULE_IMPORT_NS(DMA_BUF);
#endif

#define REG_0024_PM_POR_STATUS(base)   (base + 0x024)
#define WDT_PM_INDEX             1
#define WDT_2ND_RST_BIT_PQU_RV55 6
#define WDT_MAX_LCNT            (0x8 << 1)
#define WDT_MAX_HCNT            (0xa << 1)
#define WDT_INT_CNT             (0x6 << 1)

#define REG_RST_RESETED         0x14
#define REG_IMPLEMENTATION_0    0x38
#define REG_IMPLEMENTATION_1    0x3c
#define REG_I0_ADR_OFFSET_0     0x48
#define REG_I0_ADR_OFFSET_1     0x4c
#define REG_D0_ADR_OFFSET_0     0x60
#define REG_D0_ADR_OFFSET_1     0x64
#define REG_L2_CONF_BASE_0      0x78
#define REG_L2_CONF_BASE_1      0x7c
#define REG_RST_TIMER           0x1c4
#define REG_RST_TIMER_SEL       0x1c8

#define REG_RST_TIMER_SEL_VAL    (0x3 << 8)
#define REG_RST_TIMER3_BASE_MASK (0xf << 8)
#define REG_RST_TIMER3_BASE_VAL  (0x2 << 8)
#define REG_RST_TIMER3_EXP_MASK  (0xf << 12)
#define REG_RST_TIMER3_EXP_VAL   (0x9 << 12)
#define POLL_SAFE_RESET_MAX      100

#define REG_HIGH_BIT_SHIFT      16
#define REG_PC_ADR_OFFSET_0     0x80
#define REG_PC_ADR_OFFSET_1     0x84
#define REG_LR_ADR_OFFSET_0     0x88
#define REG_LR_ADR_OFFSET_1     0x8c
#define REG_SP_ADR_OFFSET_0     0x90
#define REG_SP_ADR_OFFSET_1     0x94

#define REG_NUM                 32
#define TASKNAME_SIZE           32
#define REG_PC_IDX              1
#define REG_SP_IDX              2
#define REG_FP_IDX              8
#define FP_MASK                 0x7
#define DUMP_DATA_SIZE          8192
#define MEMORY_BIN_SECTION      "[memory_bin]\n"
#define MEMORY_BIN_CONTENT      "0x0 = ['mem.bin', 'little']\n"
#define REGISTER_SECTION        "[register]\n"
#define REGISTER_BASE_IDX       2
#define CORE_MAGIC              0xFACBFACB
#define STACK_DEFAULT_SIZE      512
#define COREDUMP_TABLE_RESERVED 2
#define TCB_TASK_NAME_LEN       16
#define TCB_LIST_ITEM_BYTES     20
#define FP_SEARCH_OFFSET        4
#define PQU_WORKQUEUE           "PQU_WQ"
#define SYMBOL_BUF_LEN          128

#define SHMCTRL_CMA_ID 2

#define PQU_FIRMWARE_LEN	0x1200000	// 18MB
#define PQU_LOG_BUF_LEN		0x700000	// 7MB
#define PQU_LOG_BUF_HEADER_SIZE		128

// Atrace and Log use the same shared memory, they can not be enabled together.
#define ATRACE_BUF_LEN		PQU_LOG_BUF_LEN
#define ATRACE_BUF_HEADER_SIZE	PQU_LOG_BUF_HEADER_SIZE
#define ATRACE_RECORD_SIZE	256
#define ATRACE_RECORD_END   0x0A

enum fw_cdmp_type {
	CDMP_CORE = 0,
	CDMP_TASKDUMP = 1,
	CDMP_LAST = 2,
};

struct mtk_pqu_table {
	void *core_table_ptr;
	size_t core_table_sz;
	void *sym_table_ptr;
	size_t sym_table_sz;
	void *str_table_ptr;
	size_t str_table_sz;
	void *symbol_currentTCB;
	uint32_t symbol_taskExit;
};

struct mtk_pqu_symbol {
	uint32_t address;
	uint32_t address_end;
	char *name;
	struct list_head list;
};

struct mtk_pqu_symbol_list {
	uint32_t address_min;
	uint32_t address_max;
	struct list_head head;
};

struct mtk_pqu {
	bool support_suspend;
	bool core_created;
	bool symbol_parsed;
	bool wdt_reboot_stop;
	int irq;
	int recovery_irq;
	struct device *dev;
	struct rproc *rproc;
	struct clk *clk;
	void *fw_base, *intg_base, *hwdbg_base, *frcpll, *fw_valid_addr;
	u32 ipi_offset, ipi_bit, emi_base;
	u64 phy_base;
	u64 fw_len;
	void *core_base;
	void __iomem *wdt_pm_base;
	void __iomem *pqu_wdt_base;
	struct regmap *ipi_regmap, *ckgen_regmap;
	struct list_head clk_init_seq, pll_init_seq;
	char dump_data[DUMP_DATA_SIZE];
	char symbol_buf[SYMBOL_BUF_LEN];
	u64 dump_offset;
	void *log_header_base;
	void *log_base;
	u64 log_len;
	void *atrace_header_base;
	void *atrace_base;
	u64 atrace_len;
	u32 pll_ver;
	struct bin_attribute mem_bin_attr;
	struct bin_attribute core_bin_attr;
	struct bin_attribute log_bin_attr;
	struct bin_attribute atrace_bin_attr;
	struct kobject *kobject_dump;
	struct workqueue_struct *pqu_workqueue;
	struct work_struct coredump_wq;
	struct work_struct parse_symbol_wq;
	struct mtk_pqu_table tables;
	struct mtk_pqu_symbol_list symbol_list;
	struct dentry *dbg_dir;
	struct debugfs_regset32 hwdbg_regset;
	ktime_t firmware_start;
	u64 firmware_load_time_us;
	struct pinctrl *pinctrl;
	struct pinctrl_state *uart_pin_state;
	u32 uart_digmux_sel;
	u32 uart_digmux_source;
};

struct pqu_clk {
	struct list_head list;
	u32 offset, mask, val;
};

struct pqu_coredump {
	uint32_t type;
	uint32_t mstatus;
	uint32_t mepc;
	uint32_t mtval;
	uint32_t fcsr;
	uint32_t fflags;
	uint32_t frm;
	uint32_t misa;
	uint32_t mie;
	uint32_t mip;
	uint32_t mtvec;
	uint32_t mcycle;
	uint32_t minstret;
	uint32_t reg[REG_NUM];
	uint8_t  task[TASKNAME_SIZE];
	uint32_t magic_number;
	uint32_t version;
};

struct pqu_tasks_dump {
	uint32_t type;
	uint32_t num;
	uint32_t stack_top[];
};

struct coredump_table {
	uint32_t ver;
	uint32_t num;
	uint32_t reserved[COREDUMP_TABLE_RESERVED];
	uint32_t offset[];
} __packed;

struct fw_cdmp_hdr {
	uint32_t type;
	uint8_t data[];
} __packed;

struct fw_cdmp_task_control_block {
	uint32_t top_of_stack;
	uint8_t reserved1[TCB_LIST_ITEM_BYTES]; // ListItem_t xStateListItem
	uint8_t reserved2[TCB_LIST_ITEM_BYTES]; // ListItem_t xEventListItem
	uint32_t priority;
	uint32_t stack;
	char task_name[TCB_TASK_NAME_LEN];
	uint32_t stack_end;
} __packed;

struct pqu_stackframe {
	uint32_t fp;
	uint32_t ra;
};

typedef int (*pqu_handle_coredump_t)(struct mtk_pqu *pqu,
				 void *, int offset, int avail);

#define OFFSET_INDEX	0
#define MASK_INDEX		1
#define VAL_INDEX		2

// @TODO: move the following codes to device tree
#define PLL_BYTE_MASK			0xFF
#define PLL_BIT_SHIFT			8
#define PLL_CONFIG_0			0x0000
#define PLL_CONFIG_1			0x0001
#define PLL_CONFIG_2			0x1110
#define PLL_CONFIG_3			0x1100
#define PLL_CONFIG_0010			0x0010
#define PLL_CONFIG_004f			0x004f
#define PLL_CONFIG_0200			0x0200
#define PLL_DELAY				100
#define PLL_DELAY_500			500
#define REG_0000_RV55FRCPLL		0x0
#define REG_0004_RV55FRCPLL		0x4
#define REG_0008_RV55FRCPLL		0x8
#define REG_000C_RV55FRCPLL		0xc
#define REG_0010_RV55FRCPLL		0x10
#define REG_0014_RV55FRCPLL		0x14
#define REG_0018_RV55FRCPLL		0x18
#define REG_001C_RV55FRCPLL		0x1c
#define REG_0020_RV55FRCPLL		0x20
#define REG_0028_RV55FRCPLL		0x28
#define REG_0038_RV55FRCPLL		0x38
#define REG_0044_RV55FRCPLL		0x44

#define UART_SEL			0x1c601004
#define SZ_UART_SEL			0x4
#define UART_DIGMUX_DEFAULT_SELECT	0
#define UART_DIGMUX_DEFAULT_SOURCE	15

#define UART_DIGMUX_SELECT_NUM		6
#define UART_DIGMUX_SOURCE_NUM		16
#define UART_DIGMUX_SEPARATE_NUM	4
#define UART_DIGMUX_SOURCE_BITS		4

#define UART_PINSTATE_NAME		"console-uart"

#if !defined(LOWORD)
#define LOWORD(d)		((unsigned short)(d))
#endif
#if !defined(HIWORD)
#define HIWORD(d)		((unsigned short)((((unsigned long)(d)) >> 16) & 0xffff))
#endif

#define ADDR_MSB(d)		((unsigned short)((((unsigned long long)(d)) >> 32) & 0x0003))

#define MRV55E035		1
#define L2_BASE_ADDR	0xa8000000

#define FREQ_996MHZ		996
#define FREQ_1200MHZ	1200
#define DIV_2			0x00
#define DIV_BYPASS		0x04
#define CLK_12MHZ		12
#define CLK_24MHZ		24

#define CAUSE_MISALIGNED_FETCH    0x0
#define CAUSE_FAULT_FETCH         0x1
#define CAUSE_ILLEGAL_INSTRUCTION 0x2
#define CAUSE_BREAKPOINT          0x3
#define CAUSE_MISALIGNED_LOAD     0x4
#define CAUSE_FAULT_LOAD          0x5
#define CAUSE_MISALIGNED_STORE    0x6
#define CAUSE_FAULT_STORE         0x7
#define CAUSE_USER_ECALL          0x8
#define CAUSE_SUPERVISOR_ECALL    0x9
#define CAUSE_HYPERVISOR_ECALL    0xa
#define CAUSE_MACHINE_ECALL       0xb
#define CAUSE_FETCH_PAGE_FAULT    0xc
#define CAUSE_LOAD_PAGE_FAULT     0xd
#define CAUSE_RESERVED            0xe
#define CAUSE_STORE_PAGE_FAULT    0xf

static bool auto_boot;
module_param(auto_boot, bool, 0444);
MODULE_PARM_DESC(auto_boot, "Enable auto boot");

static int def_freq = FREQ_996MHZ;
module_param(def_freq, int, 0444);
MODULE_PARM_DESC(def_freq, "Set default frequency");

/* Lock protecting enter/exit critical region. */
static struct mutex pqu_state_mutex;
static bool is_mutex_inited;
static int common_clk_init_count = -1;
static int output_div;
static int loop_div;

static void check_def_freq(void)
{
	if (def_freq < FREQ_1200MHZ) {
		output_div = DIV_2;
		loop_div = def_freq / CLK_12MHZ;
		def_freq = loop_div * CLK_12MHZ;
	} else {
		output_div = DIV_BYPASS;
		loop_div = def_freq / CLK_24MHZ;
		def_freq = loop_div * CLK_24MHZ;
	}
}

static int setup_init_seq(struct device *dev, struct mtk_pqu *pqu,
		struct device_node *np)
{
	int count, i, rc;
	struct of_phandle_args clkspec;

	count = of_count_phandle_with_args(np, "pqu-clk", "#pqu-clk-cells");
	if (count < 0) {
		dev_warn(dev, "pqu-clk not found\n");
		return count;
	}

	dev_dbg(dev, "%d clock to configure\n", count);

	for (i = 0; i < count; i++) {
		struct pqu_clk *clk;

		rc = of_parse_phandle_with_args(np, "pqu-clk",
				"#pqu-clk-cells", i, &clkspec);
		if (rc < 0) {
			dev_err(dev, "pqu-clk parse fail with err %d\n", rc);
			return rc;
		}

		dev_dbg(dev, "clk\t%d:\t%x\t%x\t%x\n", i, clkspec.args[OFFSET_INDEX],
			clkspec.args[MASK_INDEX], clkspec.args[VAL_INDEX]);

		clk = devm_kzalloc(dev, sizeof(*clk), GFP_KERNEL);
		if (!clk)
			return -ENOMEM;

		clk->offset = clkspec.args[OFFSET_INDEX];
		clk->mask = clkspec.args[MASK_INDEX];
		clk->val = clkspec.args[VAL_INDEX];

		list_add_tail(&clk->list, &pqu->clk_init_seq);
	}

	return 0;
}

static void pqu_clk_init(struct device *dev, struct mtk_pqu *pqu)
{
	struct pqu_clk *clk;

	if (list_empty(&pqu->clk_init_seq)) {
		dev_warn(dev, "clk_init_seq is not set\n");
		return;
	}

	list_for_each_entry(clk, &pqu->clk_init_seq, list) {
		dev_dbg(dev, "clk.offset = %x, clk.mask = %x, clk.val = %x\n",
			 clk->offset, clk->mask, clk->val);
		regmap_update_bits_base(pqu->ckgen_regmap, clk->offset, clk->mask,
					clk->val, NULL, false, true);
	}
}

void pqu_pll_init_ver0(struct mtk_pqu *pqu)
{
	if (!pqu)
		return;

	//base_addr: 0x0200c00, offset(byte): 0x00010
	//base_addr: 0x0200c00, offset(byte): 0x00011
	writew(PLL_CONFIG_0, pqu->frcpll + REG_0010_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00018
	//base_addr: 0x0200c00, offset(byte): 0x00019
	writew(((PLL_BYTE_MASK & output_div) << PLL_BIT_SHIFT) | PLL_CONFIG_0,
			pqu->frcpll + REG_0018_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x0001c
	//base_addr: 0x0200c00, offset(byte): 0x0001d
	writew(PLL_CONFIG_0, pqu->frcpll + REG_001C_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00020
	//base_addr: 0x0200c00, offset(byte): 0x00021
	writew(PLL_CONFIG_0 | (PLL_BYTE_MASK & loop_div), pqu->frcpll + REG_0020_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00008
	//base_addr: 0x0200c00, offset(byte): 0x00009
	writew(PLL_CONFIG_0, pqu->frcpll + REG_0008_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00004
	//base_addr: 0x0200c00, offset(byte): 0x00005
	writew(PLL_CONFIG_1, pqu->frcpll + REG_0004_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00000
	//base_addr: 0x0200c00, offset(byte): 0x00001
	writew(PLL_CONFIG_2, pqu->frcpll + REG_0000_RV55FRCPLL);

	udelay(PLL_DELAY_500);

	//base_addr: 0x0200c00, offset(byte): 0x00000
	//base_addr: 0x0200c00, offset(byte): 0x00001
	writew(PLL_CONFIG_3, pqu->frcpll + REG_0000_RV55FRCPLL);
}

void pqu_pll_init_ver1(struct mtk_pqu *pqu)
{
	if (!pqu)
		return;

	// read 0x100622 == 0x00 => rv55frcpll init done.
	if (readb(pqu->frcpll + REG_0044_RV55FRCPLL) == 0x00)
		return;

	//base_addr: 0x0200c00, offset(byte): 0x00008 = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x00009 = 0x00
	writew(PLL_CONFIG_0, pqu->frcpll + REG_0008_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x0000c = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x0000d = 0x00
	writew(PLL_CONFIG_0, pqu->frcpll + REG_000C_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00010 = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x00011 = 0x00
	writew(PLL_CONFIG_0, pqu->frcpll + REG_0010_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00014 = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x00015 = 0x02
	writew(PLL_CONFIG_0200, pqu->frcpll + REG_0014_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00018 = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x00019 = 0x00
	writew(PLL_CONFIG_0, pqu->frcpll + REG_0018_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x0001c = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x0001d = 0x00
	writew(PLL_CONFIG_0, pqu->frcpll + REG_001C_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00020 = 0x4f  948MHz
	//base_addr: 0x0200c00, offset(byte): 0x00021 = 0x00
	writew(PLL_CONFIG_004f, pqu->frcpll + REG_0020_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00038 = 0x10
	//base_addr: 0x0200c00, offset(byte): 0x00039 = 0x00
	writew(PLL_CONFIG_0010, pqu->frcpll + REG_0038_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00004 = 0x01
	//base_addr: 0x0200c00, offset(byte): 0x00005 = 0x00
	writew(PLL_CONFIG_1, pqu->frcpll + REG_0004_RV55FRCPLL);

	//base_addr: 0x0200c00, offset(byte): 0x00000 = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x00001 = 0x11
	writew(PLL_CONFIG_3, pqu->frcpll + REG_0000_RV55FRCPLL);

	udelay(PLL_DELAY_500);

	//base_addr: 0x0200c00, offset(byte): 0x00044 = 0x00
	//base_addr: 0x0200c00, offset(byte): 0x00045 = 0x11
	writew(PLL_CONFIG_3, pqu->frcpll + REG_0044_RV55FRCPLL);
}

void pqu_pll_init(struct mtk_pqu *pqu)
{
	if (!pqu) {
		pr_err("pqu is null!\n");
		return;
	}

	if (pqu->pll_ver == 1) {
		pqu_pll_init_ver1(pqu);
	} else {
		pqu_pll_init_ver0(pqu);
	}
}

void pqu_uart_init(struct mtk_pqu *pqu)
{
	unsigned int tmp;
	unsigned int offset_shift;
	void __iomem *reg;

	if (!pqu)
		return;

	// console UART DIGMUX setting
	// pqu->uart_digmux_source & pqu->uart_digmux_sel has already trimmed to valid value

	// calculate digmux source shift and register address
	if (pqu->uart_digmux_sel < UART_DIGMUX_SEPARATE_NUM) {
		offset_shift = pqu->uart_digmux_sel * UART_DIGMUX_SOURCE_BITS;
		reg = ioremap(UART_SEL, SZ_UART_SEL);
	} else {
		offset_shift = pqu->uart_digmux_sel - UART_DIGMUX_SEPARATE_NUM;
		offset_shift *= UART_DIGMUX_SOURCE_BITS;
		reg = ioremap(UART_SEL + SZ_UART_SEL, SZ_UART_SEL);
	}

	// setup register for console UART DIGMUX
	tmp = readw(reg);
	tmp &= ~((UART_DIGMUX_SOURCE_NUM - 1) << offset_shift);
	tmp |= (pqu->uart_digmux_source << offset_shift);
	writew(tmp, reg);
	iounmap(reg);

	// console UART pad mux setting
	if (pqu->pinctrl && pqu->uart_pin_state)
		pinctrl_select_state(pqu->pinctrl, pqu->uart_pin_state);
}

#ifdef CONFIG_DMABUF_HEAPS
static int mtk_pqu_dma_heaps_init(struct device *dev)
{
	int ret;

	/* shmctrl cma SHMCTRL_CMA_ID */
	ret = of_reserved_mem_device_init_by_idx(dev, dev->of_node, SHMCTRL_CMA_ID);
	if (ret && ret != -ENODEV)
		return ret;

	/* create dma heaps */
	ret = mtk_pqu_dma_heaps_create(dev);
	if (ret)
		dev_warn(dev, "dma_heap create failed (%d)\n", ret);

	return 0;
}
#endif

static int pqu_start(struct rproc *rproc)
{
	struct mtk_pqu *pqu = rproc->priv;
	u64 emi_offset;
	unsigned long val;

	emi_offset = pqu->phy_base - pqu->emi_base;

	dev_info(pqu->dev, "fw mem base = %llx, emi_offset = %llx\n", pqu->phy_base, emi_offset);

	writew(LOWORD(emi_offset) + ADDR_MSB(emi_offset), pqu->intg_base + REG_I0_ADR_OFFSET_0);
	writew(HIWORD(emi_offset), pqu->intg_base + REG_I0_ADR_OFFSET_1);
	writew(LOWORD(emi_offset) + ADDR_MSB(emi_offset), pqu->intg_base + REG_D0_ADR_OFFSET_0);
	writew(HIWORD(emi_offset), pqu->intg_base + REG_D0_ADR_OFFSET_1);
	val = readw(pqu->intg_base + REG_IMPLEMENTATION_0);
	if (val & MRV55E035) {
		writew(LOWORD(L2_BASE_ADDR), pqu->intg_base + REG_L2_CONF_BASE_0);
		writew(HIWORD(L2_BASE_ADDR), pqu->intg_base + REG_L2_CONF_BASE_1);
	}

	writew(1, pqu->intg_base);

	pqu->firmware_start = ktime_get();

	return 0;
}

struct _dma_mem {
	void		*virt_base;
	dma_addr_t	device_base;
	unsigned long	pfn_base;
	int		size;
	unsigned long	*bitmap;
	spinlock_t	spinlock;
	bool		use_dev_dma_pfn_offset;
};

static void _dma_release_coherent_memory(struct _dma_mem *mem)
{
	if (!mem)
		return;

	memunmap(mem->virt_base);
	kfree(mem->bitmap);
	kfree(mem);
}


static int pqu_stop(struct rproc *rproc)
{
	struct mtk_pqu *pqu = rproc->priv;
	struct rproc_subdev *subdev;
	struct rproc_vdev *rvdev;
	unsigned int val = 0;
	unsigned int try_cnt = POLL_SAFE_RESET_MAX;

	if (!pqu)
		return -EINVAL;

	if (pqu->wdt_pm_base && pqu->pqu_wdt_base) {
		dev_info(pqu->dev, "WDT disable before stop pqu.\n");
		val = readw(REG_0024_PM_POR_STATUS(pqu->wdt_pm_base));
		val |= (BIT(WDT_2ND_RST_BIT_PQU_RV55));
		writew(val, REG_0024_PM_POR_STATUS(pqu->wdt_pm_base));
		writew(0, pqu->pqu_wdt_base + WDT_MAX_HCNT);
		writew(0, pqu->pqu_wdt_base + WDT_MAX_LCNT);
		writeb(0, pqu->pqu_wdt_base + WDT_INT_CNT);
	}

	/* Enable Reset Timer for bus breakdown deadlock */
	val = readw(pqu->intg_base + REG_RST_TIMER_SEL);
	val |= REG_RST_TIMER_SEL_VAL;    // bit[9:8] = 0x3
	writew(val, pqu->intg_base + REG_RST_TIMER_SEL);
	val = readw(pqu->intg_base + REG_RST_TIMER);
	val &= ~REG_RST_TIMER3_BASE_MASK;
	val |= REG_RST_TIMER3_BASE_VAL;  // bit[11:8] = 0x2
	val &= ~REG_RST_TIMER3_EXP_MASK;
	val |= REG_RST_TIMER3_EXP_VAL;   // bit[15:12] = 0x9
	writew(val, pqu->intg_base + REG_RST_TIMER);

	/* pll disable */
	writew(0, pqu->intg_base);

	/* Polling safe reset */
	dev_info(pqu->dev, "Polling safe reset start.");
	while (readw(pqu->intg_base + REG_RST_RESETED) == 0) {
		if (try_cnt == 0) {
			dev_err(pqu->dev, "Polling safe reset timeout.\n");
			break;
		}
		try_cnt--;
	}
	dev_info(pqu->dev, "Safe reset finish.");

	list_for_each_entry_reverse(subdev, &rproc->subdevs, node) {
		rvdev = container_of(subdev, struct rproc_vdev, subdev);
#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 0, 0)
		if (rvdev->dev.dma_mem) {
			_dma_release_coherent_memory((struct _dma_mem *)(rvdev->dev.dma_mem));
			rvdev->dev.dma_mem = NULL;
		}
#else
		if (rvdev->pdev->dev.dma_mem) {
			_dma_release_coherent_memory((struct _dma_mem *)(rvdev->pdev->dev.dma_mem));
			rvdev->pdev->dev.dma_mem = NULL;
		}
#endif
	}

	return 0;
}

static void *pqu_da_to_va(struct rproc *rproc, u64 da, size_t len, bool *is_iomem)
{
	struct mtk_pqu *pqu = rproc->priv;
	void *va_last = 0;

	if (!pqu || !pqu->dev)
		return NULL;

	va_last = pqu->fw_base + da + len;
	if (va_last >= pqu->fw_valid_addr) {
		dev_err(pqu->dev, "da(0x%llx) is out of range!\n", da);
		return NULL;
	}

	return pqu->fw_base + da;
}

/* kick a virtqueue */
static void pqu_kick(struct rproc *rproc, int vqid)
{
	struct mtk_pqu *pqu = rproc->priv;

	regmap_update_bits_base(pqu->ipi_regmap, pqu->ipi_offset, BIT(pqu->ipi_bit),
				BIT(pqu->ipi_bit), NULL, false, true);
	regmap_update_bits_base(pqu->ipi_regmap, pqu->ipi_offset, BIT(pqu->ipi_bit),
				0, NULL, false, true);
}

static int _overlay_dtb(struct mtk_pqu *pqu, u32 memsz, void *ptr, bool is_iomem)
{
	int ret = 0;
	const char *dtb = NULL;
	const struct firmware *firmware_p;
	int cov_sz;   // for coverity check

	of_property_read_string_index(pqu->dev->of_node, "dtb-name", 0, &dtb);

	dev_info(pqu->dev, "dtb-name = %s\n", dtb);

	if (dtb && strlen(dtb)) {
		ret = request_firmware(&firmware_p, dtb, pqu->dev);
		if (ret != 0) {
			dev_err(pqu->dev, "dtb request_firmware failed: %d\n", ret);
		} else {
			dev_info(pqu->dev, "dtb filesz = %zu\n", firmware_p->size);

			if (firmware_p->size <= memsz) {
				dev_info(pqu->dev, "replace dtb\n");

				if (is_iomem)
					memcpy_toio((void __iomem *)ptr, firmware_p->data, firmware_p->size);
				else
					memcpy(ptr, firmware_p->data, firmware_p->size);
			}

			cov_sz = (int)memsz - firmware_p->size;
			cov_sz = ((cov_sz < 0) || (cov_sz > (INT_MAX - 1))) ? 0 : cov_sz;
			//if ((memsz - firmware_p->size) > 0) {
			//	if (is_iomem)
			//		memset_io((void __iomem *)(ptr + firmware_p->size), 0, memsz - firmware_p->size);
			//	else
			//		memset(ptr + firmware_p->size, 0, memsz - firmware_p->size);
			//}
			if ((memsz - firmware_p->size) > 0) {
				if (is_iomem)
					memset_io((void __iomem *)(ptr + firmware_p->size), 0, cov_sz);
				else
					memset(ptr + firmware_p->size, 0, cov_sz);
			}
			release_firmware(firmware_p);
		}
	}

	return ret;
}

static int _overlay_dtbo(struct mtk_pqu *pqu, const u8 *elf_data, u64 offset,
		u32 memsz, void *ptr, bool is_iomem)
{
	int ret = 0;
	const char *dtbo;
	const char *dtbo_name;
	const struct firmware *firmware_p;
	int cov_sz;   // for coverity check
	int i, j, prop_count, count;
	void *expand_buf = NULL;

	cov_sz = (int)memsz;
	cov_sz = ((cov_sz < 0) || (cov_sz > (INT_MAX - 1))) ? 0 : cov_sz;
	if (cov_sz == 0)
		return -EINVAL;
	expand_buf = vmalloc(/*memsz*/cov_sz);
	if (!expand_buf) {
		dev_err(pqu->dev, "unable to allocate expand_buf\n");
		return -ENOMEM;
	}

	ret = fdt_open_into((const void *)
			(elf_data + offset), expand_buf,
			memsz);
	dev_info(pqu->dev, "expanded result = %d\n", ret);
	if (ret != 0) {
		dev_err(pqu->dev, "fdt_open_into failed: %d\n", ret);
		goto _overlay_dtbo_end;
	}

	prop_count = of_property_count_strings(pqu->dev->of_node, "dtbo-prop");
	dev_info(pqu->dev, "dtbo-prop count = %d\n", prop_count);

	for (i = 0; i < prop_count; i++) {
		of_property_read_string_index(pqu->dev->of_node, "dtbo-prop", i, &dtbo_name);
		dev_info(pqu->dev, "dtbo-prop[%d] = %s\n", i, dtbo_name);

		count = of_property_count_strings(pqu->dev->of_node, dtbo_name);
		for (j = 0; j < count; j++) {
			of_property_read_string_index(pqu->dev->of_node, dtbo_name, j, &dtbo);
			dev_info(pqu->dev, "dtbo-name[%d] = %s\n", j, dtbo);

			if (dtbo && strlen(dtbo)) {
				ret = request_firmware(&firmware_p, dtbo, pqu->dev);
				if (ret != 0) {
					dev_err(pqu->dev, "dtbo request_firmware fail: %d\n", ret);
					continue;
				}

				dev_info(pqu->dev, "dtbo filesz = %zu\n", firmware_p->size);
				dev_info(pqu->dev, "start to do dtbo overlay...\n");

				ret = fdt_overlay_apply(expand_buf,
						(void *)firmware_p->data);
				dev_info(pqu->dev, "overlay result = %d\n", ret);
				if (ret != 0)
					dev_err(pqu->dev, "fdt_overlay_apply fail: %d\n", ret);

				release_firmware(firmware_p);
			}
		}
	}

	if (is_iomem)
		memcpy_toio((void __iomem *)ptr, expand_buf, memsz);
	else
		memcpy(ptr, expand_buf, memsz);

_overlay_dtbo_end:
	vfree(expand_buf);

	return ret;
}

static int _pqu_add_sym_node(struct mtk_pqu *pqu, uint32_t addr, uint32_t size,
	char *name)
{
	struct mtk_pqu_symbol *new_node = NULL;

	if (!pqu || !addr || !size || !name)
		return -EINVAL;

	new_node = kmalloc(sizeof(struct mtk_pqu_symbol), GFP_KERNEL);
	if (!new_node)
		return -ENOMEM;
	new_node->address = addr;
	new_node->address_end = addr + size;
	new_node->name = name;

	list_add_tail(&new_node->list, &pqu->symbol_list.head);

	if (new_node->address < pqu->symbol_list.address_min)
		pqu->symbol_list.address_min = new_node->address;
	if (new_node->address_end > pqu->symbol_list.address_max)
		pqu->symbol_list.address_max = new_node->address_end;

	return 0;
}

static void pqu_find_symbol(struct mtk_pqu *pqu, uint32_t addr, char **name)
{
	struct mtk_pqu_symbol *entry = NULL;
	struct list_head *pos = NULL;

	if (!pqu || !addr || !name)
		return;

	if (!pqu->tables.core_table_ptr || !pqu->symbol_parsed)
		return;

	if (addr < pqu->symbol_list.address_min || addr > pqu->symbol_list.address_max)
		return;

	list_for_each(pos, &pqu->symbol_list.head) {
		entry = list_entry(pos, struct mtk_pqu_symbol, list);
		if (entry && addr >= entry->address && addr <= entry->address_end) {
			*name = entry->name;
			return;
		}
	}
}

static void pqu_destory_symbol_list(struct mtk_pqu *pqu)
{
	struct list_head *pos = NULL, *q = NULL;
	struct mtk_pqu_symbol *entry = NULL;

	if (!pqu)
		return;

	list_for_each_safe(pos, q, &pqu->symbol_list.head) {
		entry = list_entry(pos, struct mtk_pqu_symbol, list);
		list_del(pos);
		kfree(entry);
	}
}

static int pqu_elf_sym_tbl_parse(struct mtk_pqu *pqu)
{
	Elf32_Sym *symbol = NULL;
	char *symbol_str_tbl = NULL;
	uint32_t sym_tbl_size = 0, str_tbl_size = 0;
	uint64_t offset = 0;
	uint32_t index = 0, str_offset = 0, sym_addr = 0, sym_size = 0, sym_info = 0;
	char *sym_name = NULL;
	ktime_t start, end;
	s64 elapsed;

	if (!pqu)
		return -EINVAL;

	if (pqu->symbol_parsed)
		return 0;

	symbol = pqu->tables.sym_table_ptr;
	sym_tbl_size = pqu->tables.sym_table_sz;
	symbol_str_tbl = pqu->tables.str_table_ptr;
	str_tbl_size = pqu->tables.str_table_sz;

	dev_info(pqu->dev, "symbol table 0x%p size 0x%x\n", symbol, sym_tbl_size);
	dev_info(pqu->dev, "string table 0x%p size 0x%x\n", symbol_str_tbl, str_tbl_size);

	if (symbol == NULL || symbol_str_tbl == NULL) {
		dev_warn(pqu->dev, "symbol or string table is NULL\n");
		return -ENXIO;
	}

	start = ktime_get();

	while (offset < sym_tbl_size) {
		str_offset = symbol[index].st_name;
		sym_name =
			(str_offset < str_tbl_size)?(char *)(symbol_str_tbl + str_offset):"UNDEF";
		sym_addr = symbol[index].st_value;
		sym_size = symbol[index].st_size;
		sym_info = symbol[index].st_info;
		if (ELF32_ST_TYPE(sym_info) == STT_FUNC && sym_addr && sym_size) {
			if (_pqu_add_sym_node(pqu, sym_addr, sym_size, sym_name))
				dev_err(pqu->dev, "Out of memory symbol \'%s\'\n", sym_name);
		}
		if (pqu->tables.symbol_currentTCB == NULL &&
			ELF32_ST_TYPE(sym_info) == STT_OBJECT &&
			strcmp(sym_name, "pxCurrentTCB") == 0) {
			pqu->tables.symbol_currentTCB = pqu->fw_base + sym_addr;
		}
		if (pqu->tables.symbol_taskExit == 0 &&
			ELF32_ST_TYPE(sym_info) == STT_FUNC &&
			strcmp(sym_name, "prvTaskExitError") == 0) {
			pqu->tables.symbol_taskExit = sym_addr;
		}
		index++;
		offset += sizeof(Elf32_Sym);
	}

	end = ktime_get();

	elapsed = ktime_to_us(ktime_sub(end, start));
	dev_info(pqu->dev, "Symbol parsing run %lld us\n", elapsed);

	pqu->symbol_parsed = true;

	return 0;
}

static int pqu_find_symbol_byname(struct mtk_pqu *pqu, char *target_symbol,
	uint32_t *addr, uint32_t *size)
{
	Elf32_Sym *symbol = NULL;
	char *symbol_str_tbl = NULL;
	uint32_t sym_tbl_size = 0, str_tbl_size = 0;
	uint64_t offset = 0;
	uint32_t index = 0, str_offset = 0, sym_addr = 0, sym_size = 0, sym_info = 0;
	char *sym_name = NULL;

	if (!pqu || !target_symbol || !addr || !size)
		return -EINVAL;

	symbol = pqu->tables.sym_table_ptr;
	sym_tbl_size = pqu->tables.sym_table_sz;
	symbol_str_tbl = pqu->tables.str_table_ptr;
	str_tbl_size = pqu->tables.str_table_sz;

	if (symbol == NULL || symbol_str_tbl == NULL) {
		dev_warn(pqu->dev, "symbol or string table is NULL\n");
		return -ENXIO;
	}

	while (offset < sym_tbl_size) {
		str_offset = symbol[index].st_name;
		sym_name =
			(str_offset < str_tbl_size)?(char *)(symbol_str_tbl + str_offset):"UNDEF";
		sym_addr = symbol[index].st_value;
		sym_size = symbol[index].st_size;
		sym_info = symbol[index].st_info;
		if (strcmp(sym_name, target_symbol) == 0) {
			*addr = sym_addr;
			*size = sym_size;
			return 0;
		}
		index++;
		offset += sizeof(Elf32_Sym);
	}

	return -EFAULT;
}

static const void *
_find_table(struct device *dev, const struct firmware *fw, const char *table_name)
{
	const void *shdr, *name_table_shdr;
	int i;
	const char *name_table;
	const u8 *elf_data = (void *)fw->data;
	u8 class = fw_elf_get_class(fw);
	const void *ehdr = elf_data;
	u16 shnum = elf_hdr_get_e_shnum(class, ehdr);
	u32 elf_shdr_get_size = elf_size_of_shdr(class);
	u16 shstrndx = elf_hdr_get_e_shstrndx(class, ehdr);

	/* look for the resource table and handle it */
	/* First, get the section header according to the elf class */
	shdr = elf_data + elf_hdr_get_e_shoff(class, ehdr);
	/* Compute name table section header entry in shdr array */
	name_table_shdr = shdr + (shstrndx * elf_shdr_get_size);
	/* Finally, compute the name table section address in elf */
	name_table = elf_data + elf_shdr_get_sh_offset(class, name_table_shdr);

	for (i = 0; i < shnum; i++, shdr += elf_shdr_get_size) {
		u32 name = elf_shdr_get_sh_name(class, shdr);

		if (strcmp(name_table + name, table_name))
			continue;

		return shdr;
	}

	return NULL;
}

static void pqu_elf_load_customize_tables(struct mtk_pqu *pqu, const struct firmware *fw)
{
	const void *shdr = NULL;
	u64 table_va, table_size, offset;
	struct resource_table *table = NULL;
	u8 class = 0;
	const u8 *elf_data = NULL;
	size_t fw_size = 0;
	const struct firmware *fw_dup = NULL, *fw_dup_2 = NULL;

	if (!pqu || !fw) {
		pr_err("Null pointer\n");
		return;
	}
	fw_dup = fw;
	fw_dup_2 = fw;
	elf_data = (void *)fw->data;
	fw_size = fw->size;

	shdr = _find_table(pqu->dev, fw, ".coredump_table");
	do {
		if (shdr == NULL) {
			dev_err(pqu->dev, "%s: find coredump_table fail\n", __func__);
			break;
		}
		class = fw_elf_get_class(fw);
		table_va = elf_shdr_get_sh_addr(class, shdr);
		table_size = elf_shdr_get_sh_size(class, shdr);
		offset = elf_shdr_get_sh_offset(class, shdr);

		table = (struct resource_table *)(elf_data + offset);

		/* make sure we have the entire table */
		if (offset + table_size > fw_size || offset + table_size < table_size) {
			dev_err(pqu->dev, "table truncated\n");
			break;
		}

		/* make sure table has at least the header */
		if (sizeof(struct resource_table) > table_size) {
			dev_err(pqu->dev, "header-less table\n");
			break;
		}

		/* make sure the offsets array isn't truncated */
		if (struct_size(table, offset, table->num) > table_size) {
			dev_err(pqu->dev, "coredump table incomplete\n");
			break;
		}

		dev_info(pqu->dev, "coredump table addr : 0x%llx\n", table_va);

		/* because coredump table would be loaded into PQU memory, thus use it */
		pqu->tables.core_table_ptr = (void *)(pqu->fw_base + table_va);
		pqu->tables.core_table_sz = table_size;
	} while (0);

	shdr = _find_table(pqu->dev, fw_dup, ".symtab");
	if (shdr) {
		class = fw_elf_get_class(fw);
		table_size = elf_shdr_get_sh_size(class, shdr);
		offset = elf_shdr_get_sh_offset(class, shdr);
		table = (struct resource_table *)(elf_data + offset);

		/* symbol table only exists in firmware, duplicate it */
		if (table_size > 0 && table_size < fw_size && !pqu->tables.sym_table_ptr) {
			pqu->tables.sym_table_ptr = kmemdup(table, table_size, GFP_KERNEL);
			if (!pqu->tables.sym_table_ptr)
				goto clean_up_resources;
			pqu->tables.sym_table_sz = table_size;
		}
	}

	shdr = _find_table(pqu->dev, fw_dup_2, ".strtab");
	if (shdr) {
		class = fw_elf_get_class(fw);
		table_size = elf_shdr_get_sh_size(class, shdr);
		offset = elf_shdr_get_sh_offset(class, shdr);
		table = (struct resource_table *)(elf_data + offset);

		/* string table only exists in firmware, duplicate it */
		if (table_size > 0 && table_size < fw_size && !pqu->tables.str_table_ptr) {
			pqu->tables.str_table_ptr = kmemdup(table, table_size, GFP_KERNEL);
			if (!pqu->tables.str_table_ptr)
				goto clean_up_resources;
			pqu->tables.str_table_sz = table_size;
		}
	}

	queue_work(pqu->pqu_workqueue, &pqu->parse_symbol_wq);

	return;

clean_up_resources:
	kfree(pqu->tables.sym_table_ptr);
	pqu->tables.sym_table_ptr = NULL;
	pqu->tables.sym_table_sz = 0;
	kfree(pqu->tables.str_table_ptr);
	pqu->tables.str_table_ptr = NULL;
	pqu->tables.str_table_sz = 0;
}

static int pqu_elf_load_segments(struct rproc *rproc, const struct firmware *fw)
{
	const void *ehdr, *phdr;
	int i, ret = 0;
	u16 phnum;
	const u8 *elf_data = fw->data;
	u8 class = 0;
	u32 elf_phdr_get_size = 0;
	struct mtk_pqu *pqu = (struct mtk_pqu *)rproc->priv;
	int cov_num;   // for coverity check
	int cov_sz;   // for coverity check
	void *cov_ptr; // for coverity check

	pqu_elf_load_customize_tables(pqu, fw);

	class = fw_elf_get_class(fw);
	elf_phdr_get_size = elf_size_of_phdr(class);

	ehdr = elf_data;
	phnum = elf_hdr_get_e_phnum(class, ehdr);
	phdr = elf_data + elf_hdr_get_e_phoff(class, ehdr);

	/* go through the available ELF segments */
	cov_num = (int)phnum;
	cov_num = ((cov_num < 0) || (cov_num > (INT_MAX - 1))) ? 0 : cov_num;
	for (i = 0; i < /*phnum*/cov_num; i++, phdr += elf_phdr_get_size) {
		u64 da = elf_phdr_get_p_paddr(class, phdr);
		u64 memsz = elf_phdr_get_p_memsz(class, phdr);
		u64 filesz = elf_phdr_get_p_filesz(class, phdr);
		u64 offset = elf_phdr_get_p_offset(class, phdr);
		u32 type = elf_phdr_get_p_type(class, phdr);
		bool is_iomem = false;
		void *ptr;

		if (type != PT_LOAD)
			continue;

		dev_dbg(pqu->dev, "phdr: type %d da 0x%llx memsz 0x%llx filesz 0x%llx\n",
			type, da, memsz, filesz);

		if (filesz > memsz) {
			dev_err(pqu->dev, "bad phdr filesz 0x%llx memsz 0x%llx\n",
				filesz, memsz);
			ret = -EINVAL;
			break;
		}

		if (offset + filesz > fw->size) {
			dev_err(pqu->dev, "truncated fw: need 0x%llx avail 0x%zx\n",
				offset + filesz, fw->size);
			ret = -EINVAL;
			break;
		}

		if (!rproc_u64_fit_in_size_t(memsz)) {
			dev_err(pqu->dev, "size (%llx) does not fit in size_t type\n",
				memsz);
			ret = -EOVERFLOW;
			break;
		}

		/* grab the kernel address for this device address */
		ptr = rproc_da_to_va(rproc, da, memsz, &is_iomem);
		if (!ptr) {
			dev_err(pqu->dev, "bad phdr da 0x%llx mem 0x%llx\n", da,
				memsz);
			ret = -EINVAL;
			break;
		}

		/* put the segment where the remote processor expects it */
		if (filesz) {
			if (is_iomem)
				memcpy_toio((void __iomem *)ptr, elf_data + offset, filesz);
			else
				memcpy(ptr, elf_data + offset, filesz);
		}

		/*
		 * Zero out remaining memory for this segment.
		 *
		 * This isn't strictly required since dma_alloc_coherent already
		 * did this for us. albeit harmless, we may consider removing
		 * this.
		 */
		cov_sz = (int)memsz - filesz;
		cov_sz = ((cov_sz < 0) || (cov_sz > (INT_MAX - 1))) ? 0 : cov_sz;
		if (memsz > filesz) {
			if (is_iomem)
				memset_io((void __iomem *)(ptr + filesz), 0, /*memsz - filesz*/cov_sz);
			else
				memset(ptr + filesz, 0, /*memsz - filesz*/cov_sz);
		}

		//if (fdt_magic((elf_data + offset)) == FDT_MAGIC) {
		cov_ptr = (void *)(elf_data + offset);
		if (cov_ptr && (fdt_magic(cov_ptr) == FDT_MAGIC)) {

			dev_info(pqu->dev,
					"DTB section is found, phdr(%d): filesz 0x%x offset 0x%x\n",
					i, filesz, offset);

			ret = _overlay_dtb(pqu, memsz, ptr, is_iomem);
			if (ret == 0) {
				cov_sz = (int)memsz;
				cov_sz = ((cov_sz < 0) || (cov_sz > (INT_MAX - 1))) ? 0 : cov_sz;
				ret = _overlay_dtbo(pqu, elf_data, offset, /*memsz*/cov_sz, ptr, is_iomem);
			}
			if (ret != 0)
				break;
		}
	}

	return ret;
}

static const struct rproc_ops pqu_ops = {
	.start		= pqu_start,
	.stop		= pqu_stop,
	.kick		= pqu_kick,
	.da_to_va	= pqu_da_to_va,
};

/**
 * handle_event() - inbound virtqueue message workqueue function
 *
 * This function is registered as a kernel thread and is scheduled by the
 * kernel handler.
 */
static irqreturn_t handle_event(int irq, void *p)
{
	struct rproc *rproc = (struct rproc *)p;
	struct mtk_pqu *pqu = NULL;

	/* Process incoming buffers on all our vrings */
	if (rproc) {
		rproc_vq_interrupt(rproc, 0);
		rproc_vq_interrupt(rproc, 1);

		if (rproc && rproc->priv) {
			pqu = (struct mtk_pqu *)rproc->priv;
			if (pqu->firmware_start > 0) {
				ktime_t current_time = ktime_get();

				pqu->firmware_load_time_us =
					(u64)ktime_to_us(ktime_sub(current_time,
					pqu->firmware_start));
				pqu->firmware_start = 0;
			}
		}
	}

	return IRQ_HANDLED;
}

static ssize_t core_read(struct file *filp, struct kobject *kobj,
			  struct bin_attribute *bin_attr, char *buf,
			  loff_t pos, size_t count)
{
	struct mtk_pqu *pqu = bin_attr->private;

	if (!buf || !pqu)
		return -EINVAL;
	if (pos < 0)
		return -EINVAL;

	return memory_read_from_buffer(buf, count, &pos,
		pqu->dump_data, pqu->dump_offset);
}

static ssize_t mem_read(struct file *filp, struct kobject *kobj,
			  struct bin_attribute *bin_attr, char *buf,
			  loff_t pos, size_t count)
{
	struct mtk_pqu *pqu = bin_attr->private;

	if (!buf || !pqu)
		return -EINVAL;
	if (pos < 0)
		return -EINVAL;
	if (pos >= pqu->fw_len)
		return 0;
	if (count > pqu->fw_len - pos)
		count = pqu->fw_len - pos;
	memcpy_fromio(buf, pqu->fw_base + pos, count);
	pos = pos + count;

	return count;
}

static ssize_t log_read(struct file *filp, struct kobject *kobj,
			  struct bin_attribute *bin_attr, char *buf,
			  loff_t pos, size_t count)
{
	static int overflow, start_offset;
	int new_pos = 0;
	struct mtk_pqu *pqu = bin_attr->private;

	if (!buf || !pqu)
		return -EINVAL;
	if (pos < 0)
		return -EINVAL;
	if (pos >= pqu->log_len)
		return 0;
	if (count > pqu->log_len - pos)
		count = pqu->log_len - pos;

	// Parse the 16 bytes LOG_MEMORY_HEADER (OVERFLOW + START_OFFSET + ...)
	if (pos == 0) {
		memcpy_fromio(&overflow, pqu->log_header_base, sizeof(int));
		memcpy_fromio(&start_offset, pqu->log_header_base+sizeof(int), sizeof(int));
		pr_crit("pos=%d count=%d overflow=%d, start_offset=%d\n",
			pos, count, overflow, start_offset);
	}

	if (overflow) {
		int part1_count = 0, part2_count = 0;

		new_pos = start_offset + pos;
		if (new_pos >= pqu->log_len)
			new_pos = new_pos - pqu->log_len;

		if (count > pqu->log_len - new_pos) {
			part1_count = pqu->log_len - new_pos;
			part2_count = count - part1_count;
			memcpy_fromio(buf, pqu->log_base + new_pos, part1_count);
			memcpy_fromio(buf+part1_count, pqu->log_base, part2_count);
		} else {
			memcpy_fromio(buf, pqu->log_base + new_pos, count);
		}
	} else {
		if (pos >= start_offset)
			return 0;
		if (count > start_offset - pos)
			count = start_offset - pos;

		memcpy_fromio(buf, pqu->log_base + pos, count);
	}

	return count;
}

static ssize_t atrace_read(struct file *filp, struct kobject *kobj,
			  struct bin_attribute *bin_attr, char *buf,
			  loff_t pos, size_t count)
{
	static int overflow, start_offset, event_count;
	int new_pos = 0, i;
	struct mtk_pqu *pqu = bin_attr->private;

	if (!buf || !pqu)
		return -EINVAL;
	if (pos < 0)
		return -EINVAL;
	if (pos >= pqu->atrace_len)
		return 0;
	if (count > pqu->atrace_len - pos)
		count = pqu->atrace_len - pos;

	// Parse the 128bytes ATRACE_MEMORY_HEADER
	// (OVERFLOW + START_OFFSET + EVENT_COUNT + reserved)
	if (pos == 0) {
		memcpy_fromio(&overflow, pqu->atrace_header_base, sizeof(int));
		memcpy_fromio(&start_offset,
			pqu->atrace_header_base+sizeof(int),
			sizeof(int));
		memcpy_fromio(&event_count,
			pqu->atrace_header_base+sizeof(int)+sizeof(int),
			sizeof(int));

		pr_crit("pos=%d count=%d overflow=%d, start_offset=%d event_count=%d\n",
			pos, count, overflow, start_offset, event_count);

		// The 1st atrace record will be overwirted and may corrupt
		// So we should omit the 1st record to avoid parse fail
		for (i = 0; i < ATRACE_RECORD_SIZE; i++) {
			if (*((uint8_t *)(pqu->atrace_base + start_offset + i)) ==
				ATRACE_RECORD_END) {
				i++;
				break;
			}
		}

		start_offset = start_offset + i;
		pr_crit("adjust start_offset to %d\n", start_offset);
	}

	if (overflow) {
		int part1_count = 0, part2_count = 0;

		new_pos = start_offset + pos;
		if (new_pos >= pqu->atrace_len)
			new_pos = new_pos - pqu->atrace_len;

		if (count > pqu->atrace_len - new_pos) {
			part1_count = pqu->atrace_len - new_pos;
			part2_count = count - part1_count;
			memcpy_fromio(buf, pqu->atrace_base + new_pos, part1_count);
			memcpy_fromio(buf+part1_count, pqu->atrace_base, part2_count);
		} else {
			memcpy_fromio(buf, pqu->atrace_base + new_pos, count);
		}
	} else {
		if (pos >= start_offset)
			return 0;
		if (count > start_offset - pos)
			count = start_offset - pos;
		memcpy_fromio(buf, pqu->atrace_base + pos, count);
	}

	return count;
}

static const char *mtk_pqu_exception_cause(uint32_t cause)
{
	switch (cause) {
	case CAUSE_MISALIGNED_FETCH:
		return "CAUSE_MISALIGNED_FETCH";
	case CAUSE_FAULT_FETCH:
		return "CAUSE_FAULT_FETCH";
	case CAUSE_ILLEGAL_INSTRUCTION:
		return "CAUSE_ILLEGAL_INSTRUCTION";
	case CAUSE_BREAKPOINT:
		return "CAUSE_BREAKPOINT";
	case CAUSE_MISALIGNED_LOAD:
		return "CAUSE_MISALIGNED_LOAD";
	case CAUSE_FAULT_LOAD:
		return "CAUSE_FAULT_LOAD";
	case CAUSE_MISALIGNED_STORE:
		return "CAUSE_MISALIGNED_STORE";
	case CAUSE_FAULT_STORE:
		return "CAUSE_FAULT_STORE";
	case CAUSE_USER_ECALL:
		return "CAUSE_USER_ECALL";
	case CAUSE_SUPERVISOR_ECALL:
		return "CAUSE_SUPERVISOR_ECALL";
	case CAUSE_HYPERVISOR_ECALL:
		return "CAUSE_HYPERVISOR_ECALL";
	case CAUSE_MACHINE_ECALL:
		return "CAUSE_MACHINE_ECALL";
	case CAUSE_FETCH_PAGE_FAULT:
		return "CAUSE_FETCH_PAGE_FAULT";
	case CAUSE_LOAD_PAGE_FAULT:
		return "CAUSE_LOAD_PAGE_FAULT";
	case CAUSE_RESERVED:
		return "CAUSE_RESERVED";
	case CAUSE_STORE_PAGE_FAULT:
		return "CAUSE_STORE_PAGE_FAULT";
	default:
		return "CAUSE_UNKNOWN";
	}
}

static bool mtk_pqu_crash_stack(struct mtk_pqu *pqu, uint32_t pc, uint32_t sp, uint32_t fp,
	uint32_t stack_end)
{
	struct pqu_stackframe *frame = NULL;
	uint32_t low = 0, high = 0;
	char *symbol = NULL;

	if (pqu == NULL)
		return false;

	dev_crit(pqu->dev, "PQU crash stack :\n");

	for (;;) {
		symbol = NULL;
		pqu_find_symbol(pqu, pc, &symbol);
		dev_crit(pqu->dev, "0x%08x %s\n", pc, (symbol != NULL)?symbol:"??");

		low = sp + sizeof(struct pqu_stackframe);
		high = stack_end;
		if (fp < low || fp > high || fp & FP_MASK)
			break;

		frame = (struct pqu_stackframe *)(fp + pqu->fw_base) - 1;
		if ((void *)frame > pqu->fw_valid_addr)
			break;
		sp = fp;
		fp = frame->fp;
		pc = frame->ra;
	}

	return (pc == pqu->tables.symbol_taskExit);
}

static void mtk_pqu_analyze_stack(struct mtk_pqu *pqu, uint32_t stack, uint32_t stack_end)
{
#define STACK_PROTECTOR 0xa5a5a5a5
	char *symbol = NULL;
	uint32_t *scanner = NULL, *stack_top = NULL;
	uint32_t sp = 0, pc = 0;

	if (pqu == NULL)
		return;

	dev_crit(pqu->dev, "PQU analyze stack :\n");

	scanner = (uint32_t *)(pqu->fw_base + stack_end - sizeof(uint32_t));
	stack_top = (uint32_t *)(pqu->fw_base + stack);

	while (scanner > stack_top) {
		if (*scanner == STACK_PROTECTOR)
			break;
		/* find start point (prvTaskExitError) */
		if (sp == 0 && *scanner == pqu->tables.symbol_taskExit) {
			pc = pqu->tables.symbol_taskExit;
			symbol = NULL;
			pqu_find_symbol(pqu, pc, &symbol);
			dev_crit(pqu->dev, "0x%08x %s\n", pc, (symbol != NULL)?symbol:"??");
			sp = stack_end; // start point sp
		}
		if (sp > 0 && *scanner == sp) {
			pc = *(scanner + 1);
			symbol = NULL;
			pqu_find_symbol(pqu, pc, &symbol);
			dev_crit(pqu->dev, "0x%08x %s\n", pc, (symbol != NULL)?symbol:"??");
			sp = (stack_end + sizeof(uint32_t));
		}
		scanner--;
		stack_end -= sizeof(uint32_t);
	}
}

static int mtk_pqu_register_dump(const struct device *dev, struct pqu_coredump *coredump,
	void *data, size_t data_size, size_t *poffset)
{
	int ret = 0;

	if (!dev || !coredump || !data || !poffset) {
		ret = -EINVAL;
		goto error;
	}

	ret = snprintf(data + *poffset, data_size - *poffset,
		"mstatus = 0x%08x\n", coredump->mstatus);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "mepc = 0x%08x\n", coredump->mepc);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;
	dev_crit(dev, "mepc = 0x%08x\n", coredump->mepc);

	ret = snprintf(data + *poffset, data_size - *poffset, "mtval = 0x%08x\n", coredump->mtval);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;
	dev_crit(dev, "mstatus = 0x%08x(%s) mtval = 0x%08x\n",
		coredump->mstatus, mtk_pqu_exception_cause(coredump->mstatus), coredump->mtval);

	ret = snprintf(data + *poffset, data_size - *poffset, "fcsr = 0x%08x\n", coredump->fcsr);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset,
		"fflags = 0x%08x\n", coredump->fflags);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "frm = 0x%08x\n", coredump->frm);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "misa = 0x%08x\n", coredump->misa);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "mie = 0x%08x\n", coredump->mie);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "mip = 0x%08x\n", coredump->mip);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "mtvec = 0x%08x\n", coredump->mtvec);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset,
		"mcycle = 0x%08x\n", coredump->mcycle);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = snprintf(data + *poffset, data_size - *poffset, "minstret = 0x%08x\n",
		coredump->minstret);
	if (ret <= 0) {
		ret = -EINVAL;
		goto error;
	}
	*poffset += ret;

	ret = 0;
error:
	return ret;
}

static void mtk_pqu_program_counter_check(struct mtk_pqu *pqu)
{
	char *pc_sym = NULL;
	const uint32_t RESAMPLE_MAX = 5;
	uint32_t pc, cnt = 0;

	if (pqu == NULL)
		return;

	while (cnt < RESAMPLE_MAX) {
		cnt++;
		pc = readw(pqu->hwdbg_base + REG_PC_ADR_OFFSET_0) +
			(readw(pqu->hwdbg_base + REG_PC_ADR_OFFSET_1) << REG_HIGH_BIT_SHIFT);
		pc_sym = NULL;
		pqu_find_symbol(pqu, pc, &pc_sym);
		dev_crit(pqu->dev, "After hwdbg pc[%d] = 0x%x %s\n", cnt, pc,
			(pc_sym != NULL)?pc_sym:"??");
	}
}

static int mtk_pqu_custom_coredump(struct mtk_pqu *pqu, void *core_base)
{
	int ret;
	struct pqu_coredump *coredump = NULL;
	void *data = NULL;
	size_t data_size = 0;
	size_t offset = 0;
	uint8_t i = 0;

	if (!pqu) {
		pr_err("pqu is not present\n");
		ret = -EINVAL;
		goto dump_end;
	}

	dev_crit(pqu->dev, "PQU crash coredump.\n");

	if (core_base == 0) {
		dev_crit(pqu->dev, "Coredump base is not present\n");
		ret = -EINVAL;
		goto dump_end;
	}

	coredump = (struct pqu_coredump *)core_base;

	if (coredump->magic_number != CORE_MAGIC) {
		uint32_t pc, sp, fp, stack_end;
		uint32_t *currentTCB_ptr = pqu->tables.symbol_currentTCB;
		struct fw_cdmp_task_control_block *currentTCB = NULL;
		char *pc_sym = NULL, *fp_sym = NULL;

		dev_crit(pqu->dev, "Coredump magic number is not match\n");

		pc = readw(pqu->hwdbg_base + REG_PC_ADR_OFFSET_0) +
			(readw(pqu->hwdbg_base + REG_PC_ADR_OFFSET_1) << REG_HIGH_BIT_SHIFT);
		sp = readw(pqu->hwdbg_base + REG_SP_ADR_OFFSET_0) +
			(readw(pqu->hwdbg_base + REG_SP_ADR_OFFSET_1) << REG_HIGH_BIT_SHIFT);
		fp = readw(pqu->hwdbg_base + REG_LR_ADR_OFFSET_0) +
			(readw(pqu->hwdbg_base + REG_LR_ADR_OFFSET_1) << REG_HIGH_BIT_SHIFT);
		currentTCB = (currentTCB_ptr)?pqu->fw_base + *currentTCB_ptr:NULL;
		stack_end = (currentTCB)?currentTCB->stack_end:0;
		dev_crit(pqu->dev, "Dump from hwdbg pc = 0x%x sp = 0x%x lr = 0x%x currentTCB = %s\n",
			pc, sp, fp, (currentTCB)?currentTCB->task_name:"??");
		pqu_find_symbol(pqu, pc, &pc_sym);
		pqu_find_symbol(pqu, fp, &fp_sym);
		dev_crit(pqu->dev, "pc : %s fp : %s\n",
			(pc_sym != NULL)?pc_sym:"??", (fp_sym != NULL)?fp_sym:"??");

		mtk_pqu_crash_stack(pqu, pc, sp, fp, stack_end);
		mtk_pqu_program_counter_check(pqu);
		ret = 0;
		goto dump_end;
	}

	dev_crit(pqu->dev, "Coredump version = %u\n", coredump->version);

	data = pqu->dump_data;
	data_size = DUMP_DATA_SIZE;
	offset = 0;

	ret = snprintf(data + offset, data_size - offset, MEMORY_BIN_SECTION);
	if (ret <= 0) {
		dev_err(pqu->dev, "Dump file size is insufficient.\n");
		ret = -EINVAL;
		goto dump_end;
	}
	offset += ret;

	ret = snprintf(data + offset, data_size - offset, MEMORY_BIN_CONTENT);
	if (ret <= 0) {
		dev_err(pqu->dev, "Dump file size is insufficient.\n");
		ret = -EINVAL;
		goto dump_end;
	}
	offset += ret;

	ret = snprintf(data + offset, data_size - offset, REGISTER_SECTION);
	if (ret <= 0) {
		dev_err(pqu->dev, "Dump file size is insufficient.\n");
		ret = -EINVAL;
		goto dump_end;
	}
	offset += ret;

	for (i = 0; i < REG_NUM; i++) {
		dev_crit(pqu->dev, "register[%d] = 0x%08x\n", i, coredump->reg[i]);
		ret = snprintf(data + offset, data_size - offset,
			"x%d=0x%08x\n", i, coredump->reg[i]);
		if (ret <= 0) {
			dev_err(pqu->dev, "Dump file size is insufficient.\n");
			ret = -EINVAL;
			goto dump_end;
		}
		offset += ret;
	}

	ret = mtk_pqu_register_dump(pqu->dev, coredump, data, data_size, &offset);
	if (ret < 0) {
		dev_err(pqu->dev, "Dump file size is insufficient.\n");
		ret = -EINVAL;
		goto dump_end;
	}
	pqu->dump_offset = offset;

	dev_crit(pqu->dev, "PQU crash task : %s\n", coredump->task);
	mtk_pqu_crash_stack(pqu,
		coredump->reg[REG_PC_IDX],
		coredump->reg[REG_SP_IDX],
		coredump->reg[REG_FP_IDX],
		coredump->reg[REG_SP_IDX] + STACK_DEFAULT_SIZE);

	sysfs_bin_attr_init(&pqu->core_bin_attr);
	pqu->core_bin_attr.attr.name = "core";
	pqu->core_bin_attr.attr.mode = 0444;
	pqu->core_bin_attr.size = 0;
	pqu->core_bin_attr.read = core_read;
	pqu->core_bin_attr.private = pqu;
	ret = sysfs_create_bin_file(pqu->kobject_dump, &pqu->core_bin_attr);
	if (ret) {
		dev_err(pqu->dev, "sysfs_create_bin_file failed! ret = %d\n", ret);
		goto dump_end;
	}
	mtk_pqu_program_counter_check(pqu);

	dev_crit(pqu->dev, "PQU coredump success.\n");

	pqu->core_created = true;

	ret = 0;
dump_end:
	return ret;
}

static int mtk_pqu_handle_core(struct mtk_pqu *pqu,
				 void *ptr, int offset, int avail)
{
	struct pqu_coredump *coredump = ptr;
	struct device *dev = NULL;

	if (!pqu | !ptr) {
		pr_err("NULL pointer\n");
		return -EINVAL;
	}
	dev = pqu->dev;
	coredump = (struct pqu_coredump *)(ptr + offset);

	if (sizeof(*coredump) > avail) {
		dev_err(dev, "coredump is truncated\n");
		return -EINVAL;
	}

	mtk_pqu_custom_coredump(pqu, coredump);

	return 0;
}

static int mtk_pqu_handle_taskdump(struct mtk_pqu *pqu,
				 void *ptr, int offset, int avail)
{
	struct device *dev = NULL;
	struct pqu_tasks_dump *tasks_dump = NULL;
	struct fw_cdmp_task_control_block *tcb = NULL;
	uint32_t index = 0, pc = 0, sp = 0, fp = 0, stack_end = 0, fp_finder = 0;
	uint32_t *pc_ptr = 0, *sp_ptr = 0, *fp_finder_ptr = 0;
	void *fw_base = 0;
	bool find_taskExit = false;

	if (!pqu | !ptr) {
		pr_err("NULL pointer\n");
		return -EINVAL;
	}
	dev = pqu->dev;
	fw_base = pqu->fw_base;
	tasks_dump = (struct pqu_tasks_dump *)(ptr + offset);

	for (index = 0; index < tasks_dump->num; index++) {
		tcb = (struct fw_cdmp_task_control_block *)(fw_base + tasks_dump->stack_top[index]);
		dev_crit(pqu->dev, "==========================================\n");
		dev_crit(pqu->dev, "[%2u] name = %-16s priority = %2u\n", index, tcb->task_name,
			tcb->priority);
		dev_crit(pqu->dev, "stack = 0x%x end = 0x%x size = 0x%x\n",
			 tcb->stack, tcb->stack_end, tcb->stack_end - tcb->stack);
		/* Host access sp pointer */
		sp_ptr = (uint32_t *)(fw_base + tasks_dump->stack_top[index]);
		if ((void *)sp_ptr > pqu->fw_valid_addr)
			continue;
		/* Get sp address from PQU memory */
		sp = *sp_ptr;
		/* Host access pc pointer */
		pc_ptr = (uint32_t *)(fw_base + sp);
		if ((void *)pc_ptr > pqu->fw_valid_addr)
			continue;
		/* Get pc address from PQU memory */
		pc = *pc_ptr;
		stack_end = tcb->stack_end;
		fp_finder = sp;
		/* fp is restored in stack, find it */
		while (fp_finder < stack_end) {
			/* Host access fp pointer */
			fp_finder_ptr = (uint32_t *)(fw_base + fp_finder);
			if ((void *)fp_finder_ptr > pqu->fw_valid_addr)
				break;
			/* Get fp address from PQU memory */
			fp = *fp_finder_ptr;
			if (fp > sp && fp < stack_end)
				break;
			fp_finder += FP_SEARCH_OFFSET;
		}
		dev_info(pqu->dev, "pc = 0x%x sp = 0x%x fp = 0x%x\n", pc, sp, fp);
		find_taskExit = mtk_pqu_crash_stack(pqu, pc, sp, fp, stack_end);
		if (find_taskExit == false)
			mtk_pqu_analyze_stack(pqu, tcb->stack, tcb->stack_end);
	}

	return 0;
}

static pqu_handle_coredump_t rproc_loading_handlers[CDMP_LAST] = {
	[CDMP_CORE] = mtk_pqu_handle_core,
	[CDMP_TASKDUMP] = mtk_pqu_handle_taskdump,
};

static int mtk_pqu_handle_resources(struct mtk_pqu *pqu,
				  pqu_handle_coredump_t handlers[CDMP_LAST])
{
	struct device *dev = NULL;
	pqu_handle_coredump_t handler;
	struct coredump_table *core_table = NULL;
	int ret = 0, i;

	if (!pqu)
		return -EINVAL;

	if (!pqu->tables.core_table_ptr)
		return -ENXIO;

	dev = pqu->dev;
	core_table = pqu->tables.core_table_ptr;

	for (i = 0; i < core_table->num; i++) {
		int offset = core_table->offset[i];
		struct fw_rsc_hdr *hdr = (void *)core_table + offset;
		int avail = pqu->tables.core_table_sz  - offset - sizeof(*hdr);

		/* make sure table isn't truncated */
		if (avail < 0) {
			dev_err(dev, "coredump table is truncated\n");
			return -EINVAL;
		}

		dev_info(dev, "coredump: type %d\n", hdr->type);

		if (hdr->type >= CDMP_LAST) {
			dev_warn(dev, "unsupported type %d\n", hdr->type);
			continue;
		}

		handler = handlers[hdr->type];
		if (!handler)
			continue;

		ret = handler(pqu, core_table, offset, avail);
		if (ret)
			break;
	}

	return ret;
}

static void wq_pqu_coredump(struct work_struct *work)
{
	struct mtk_pqu *pqu =
		container_of(work, struct mtk_pqu, coredump_wq);

	if (mtk_pqu_handle_resources(pqu, rproc_loading_handlers) == -ENXIO)
		mtk_pqu_custom_coredump(pqu, pqu->core_base);
}

static void wq_pqu_parse_symbol(struct work_struct *work)
{
	struct mtk_pqu *pqu =
		container_of(work, struct mtk_pqu, parse_symbol_wq);

	pqu_elf_sym_tbl_parse(pqu);

	/* Chip different, we have to initialize boot reason */
	if (pm_get_boot_reason() == PM_BR_WATCHDOG_PQU) {
		dev_info(pqu->dev, "Boot reason still WATCHDOG_PQU, need to initialize.");
		pm_set_boot_reason(PM_BR_AC);
	}
}

static irqreturn_t handle_recovery_event(int irq, void *p)
{
	struct rproc *rproc = (struct rproc *)p;
	struct mtk_pqu *pqu = NULL;
	unsigned long val = 0;

	pqu = (struct mtk_pqu *)rproc->priv;
	if (pqu == NULL)
		return IRQ_HANDLED;

	if (pm_get_boot_reason() == PM_BR_WATCHDOG_PQU) {
		dev_info(pqu->dev, "Duplicate crash irq, disable PQU WDT irq.");
		disable_irq(pqu->recovery_irq);
		return IRQ_HANDLED;
	}

	val = readw(pqu->intg_base);
	if (val == 0) {
		dev_info(pqu->dev, "PQU has been stopped. Skip WDT event.\n");
		return IRQ_HANDLED;
	}
	dev_info(pqu->dev, "PQU is still running. Handle WDT event.\n");

	if (pqu->wdt_reboot_stop && pqu->wdt_pm_base && pqu->pqu_wdt_base) {
		pr_crit("PQU crash interrupt, disable PQU WDT.");
		val = readw(REG_0024_PM_POR_STATUS(pqu->wdt_pm_base));
		val |= (BIT(WDT_2ND_RST_BIT_PQU_RV55));
		writew(val, REG_0024_PM_POR_STATUS(pqu->wdt_pm_base));
		writew(0, pqu->pqu_wdt_base + WDT_MAX_HCNT);
		writew(0, pqu->pqu_wdt_base + WDT_MAX_LCNT);
		writeb(0, pqu->pqu_wdt_base + WDT_INT_CNT);
	}

	if (pqu->pqu_workqueue)
		queue_work(pqu->pqu_workqueue, &pqu->coredump_wq);

	rproc_report_crash(rproc, RPROC_WATCHDOG);

	dev_info(pqu->dev, "Original boot_reason = %d\n", pm_get_boot_reason());
	pm_set_boot_reason(PM_BR_WATCHDOG_PQU);

	return IRQ_HANDLED;
}

static int mtk_pqu_check_mem_region(struct device *dev, struct device_node *np)
{
	int ret;
	struct of_mmap_info_data dma, fwbuf;

	/* dma mem */
	ret = of_mtk_get_reserved_memory_info_by_idx(np, 0, &dma);
	if (ret)
		return ret;

	/* firmware buffer */
	ret = of_mtk_get_reserved_memory_info_by_idx(np, 1, &fwbuf);
	if (ret)
		return ret;

	if (dma.start_bus_address < fwbuf.start_bus_address) {
		dev_err(dev, "invalid memory region 0:%llx 1:%llx\n", dma.start_bus_address,
				fwbuf.start_bus_address);
		return -EINVAL;
	}

	return 0;
}

static int mtk_pqu_wdt_parse_dt(struct platform_device *pdev, struct mtk_pqu *pqu)
{
	struct device *dev = NULL;
	struct resource *res = NULL;
	struct device_node *wdt_np = NULL;

	if (!pdev || !pqu)
		return -EINVAL;

	dev = &pdev->dev;

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt");
	pqu->pqu_wdt_base = devm_ioremap_resource(dev, res);
	if (IS_ERR(pqu->pqu_wdt_base)) {
		dev_err(dev, "no wdt base\n");
		return PTR_ERR(pqu->pqu_wdt_base);
	}

	wdt_np = of_find_compatible_node(NULL, NULL, "mediatek,mt5896-wdt");
	if (wdt_np) {
		pqu->wdt_pm_base = of_iomap(wdt_np, WDT_PM_INDEX);
		if (IS_ERR(pqu->wdt_pm_base)) {
			dev_warn(dev, "Unable to iomap WDT base! %#llx\n", (u64)pqu->wdt_pm_base);
			pqu->wdt_pm_base = 0;
		} else {
			dev_info(dev, "WDT base = %#llx\n", (u64)pqu->wdt_pm_base);
		}
	} else {
		dev_warn(dev, "Unable to find WDT node!\n");
	}

	return 0;
}

static int mtk_pqu_parse_dt(struct platform_device *pdev)
{
	const char *key;
	int ret;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node, *syscon_np, *mmi_node;
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_pqu *pqu = rproc->priv;
	struct resource *res;
	struct of_mmap_info_data mmap_info;

	pqu->irq = platform_get_irq(pdev, 0);
	if (pqu->irq < 0) {
		dev_err(dev, "%s no irq found\n", dev->of_node->name);
		return -ENXIO;
	}

	pqu->recovery_irq = platform_get_irq(pdev, 1);
	if (pqu->recovery_irq < 0)
		dev_err(dev, "%s no recovery irq found\n", dev->of_node->name);


	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intg");
	pqu->intg_base = devm_ioremap_resource(dev, res);
	if (IS_ERR(pqu->intg_base)) {
		dev_err(dev, "no intg base\n");
		return PTR_ERR(pqu->intg_base);
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "frcpll");
	if (!res) {
		dev_err(dev, "no frcpll resource\n");
		return -ENODEV;
	}
	// FIXME: frcpll is shared by mulitple mrv
	// pqu->frcpll = devm_ioremap_resource(dev, res);
	pqu->frcpll = ioremap(res->start, resource_size(res));
	if (IS_ERR(pqu->frcpll)) {
		dev_err(dev, "no frcpll\n");
		return PTR_ERR(pqu->frcpll);
	}

	mtk_pqu_wdt_parse_dt(pdev, pqu);

	syscon_np = of_parse_phandle(np, "mtk,ipi", 0);
	if (!syscon_np) {
		dev_err(dev, "no mtk,ipi node\n");
		return -ENODEV;
	}

	pqu->ipi_regmap = syscon_node_to_regmap(syscon_np);
	if (IS_ERR(pqu->ipi_regmap))
		return PTR_ERR(pqu->ipi_regmap);

	/* TODO: Remove this after memcu clk init seq ready */
	pqu->support_suspend = of_property_read_bool(np, "mediatek,pqu-support-suspend");
	if (pqu->support_suspend) {
		syscon_np = of_parse_phandle(np, "pqu-clk-bank", 0);
		if (!syscon_np) {
			dev_err(dev, "no mtk,ckgen node\n");
			return -ENODEV;
		}

		pqu->ckgen_regmap = syscon_node_to_regmap(syscon_np);
		if (IS_ERR(pqu->ckgen_regmap))
			return PTR_ERR(pqu->ckgen_regmap);

		ret = setup_init_seq(dev, pqu, np);
		if (ret)
			return ret;
	}

	key = "mtk,ipi";
	ret = of_property_read_u32_index(np, key, 1, &pqu->ipi_offset);
	if (ret < 0) {
		dev_err(dev, "no offset in %s\n", key);
		return -EINVAL;
	}

	ret = of_property_read_u32_index(np, key, 2, &pqu->ipi_bit);
	if (ret < 0) {
		dev_err(dev, "no bit in %s\n", key);
		return -EINVAL;
	}

	/* check memory region, share dma region should be after fw mem */
	ret = mtk_pqu_check_mem_region(dev, np);
	if (ret)
		return ret;

	ret = of_reserved_mem_device_init(dev);
	if (ret)
		return ret;

	ret = of_mtk_get_reserved_memory_info_by_idx(np, 1, &mmap_info);
	if (ret)
		return ret;

	dev_info(dev, "fwmem start = %llx, size = %llx\n", mmap_info.start_bus_address,
			mmap_info.buffer_size);
	pqu->fw_base = devm_ioremap_wc(dev, mmap_info.start_bus_address, mmap_info.buffer_size);
	pqu->fw_len = mmap_info.buffer_size;
	pqu->fw_valid_addr = pqu->fw_base + mmap_info.buffer_size;

	if (IS_ERR(pqu->fw_base)) {
		of_reserved_mem_device_release(dev);
		return PTR_ERR(pqu->fw_base);
	}

	pqu->phy_base = mmap_info.start_bus_address;

	pqu->core_base = pqu->fw_base + pqu->fw_len - sizeof(struct pqu_coredump);
	dev_info(dev, "coredump address = 0x%p\n", pqu->core_base);

	dev_err(dev,
		"phy_base=0x%llx, fw_len=0x%llx log_base pa=0x%llx\n",
		pqu->phy_base,
		pqu->fw_len,
		(uint64_t)(pqu->phy_base + pqu->fw_len - (PQU_FIRMWARE_LEN + PQU_LOG_BUF_LEN)));

	pqu->log_header_base = pqu->fw_base + pqu->fw_len - (PQU_FIRMWARE_LEN + PQU_LOG_BUF_LEN);
	pqu->log_base = pqu->log_header_base + PQU_LOG_BUF_HEADER_SIZE;
	pqu->log_len = PQU_LOG_BUF_LEN - PQU_LOG_BUF_HEADER_SIZE;

	pqu->atrace_header_base = pqu->fw_base + pqu->fw_len - (PQU_FIRMWARE_LEN + ATRACE_BUF_LEN);
	pqu->atrace_base = pqu->atrace_header_base + ATRACE_BUF_HEADER_SIZE;
	pqu->atrace_len = ATRACE_BUF_LEN - ATRACE_BUF_HEADER_SIZE;

	mmi_node = of_find_node_by_name(NULL, "memory_info");
	if (!mmi_node)
		return -ENODEV;
	ret = of_property_read_u32(mmi_node, "cpu_emi0_base", &pqu->emi_base);
	dev_info(dev, "emi_base = 0x%x\n", pqu->emi_base);
	if (ret)
		return ret;

	return 0;
}

static int mtk_pqu_parse_extra_dt(struct platform_device *pdev)
{
	struct device *dev = NULL;
	struct device_node *np = NULL;
	struct rproc *rproc = NULL;
	struct mtk_pqu *pqu = NULL;
	struct resource *res = NULL;
	int ret;

	if (!pdev)
		return -ENODEV;

	dev = &pdev->dev;
	if (!dev)
		return -ENODEV;

	np = dev->of_node;
	if (!np)
		return -ENODEV;

	rproc = platform_get_drvdata(pdev);
	if (!rproc)
		return -ENODEV;

	pqu = rproc->priv;
	if (!pqu)
		return -ENODEV;

	if (!of_property_read_u32(np, "pll-ver", &pqu->pll_ver)) {
		dev_info(dev, "Value of property 'pll-ver': %u\n", pqu->pll_ver);
	} else {
		dev_err(dev, "Failed to read pll-ver property\n");
		return -EINVAL;
	}

	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hwdbg");
	pqu->hwdbg_base = devm_ioremap_resource(dev, res);
	if (IS_ERR(pqu->hwdbg_base)) {
		dev_warn(dev, "no hwdbg base\n");
		pqu->hwdbg_base = NULL;
	}

	pqu->pinctrl = devm_pinctrl_get(dev);
	if (IS_ERR(pqu->pinctrl)) {
		dev_err(dev, "Failed to get pinctrl %ld\n", PTR_ERR(pqu->pinctrl));
		pqu->pinctrl = NULL;
		return 0;
	}
	pqu->uart_pin_state = pinctrl_lookup_state(pqu->pinctrl, UART_PINSTATE_NAME);
	if (IS_ERR(pqu->uart_pin_state)) {
		dev_err(dev, "Failed to lookup state '%s' for console uart %ld\n",
			UART_PINSTATE_NAME, PTR_ERR(pqu->uart_pin_state));
		pqu->uart_pin_state = NULL;
	}

	ret = of_property_read_u32_index(np, "uart-digmux-select", 1, &pqu->uart_digmux_source);
	if (ret < 0) {
		dev_warn(dev, "no source in 'uart-digmux-select'\n");
		pqu->uart_digmux_source = UART_DIGMUX_DEFAULT_SOURCE;
	}
	ret = of_property_read_u32_index(np, "uart-digmux-select", 0, &pqu->uart_digmux_sel);
	if (ret < 0) {
		dev_warn(dev, "no selection in 'uart-digmux-select'\n");
		pqu->uart_digmux_sel = UART_DIGMUX_DEFAULT_SELECT;
	}
	if ((pqu->uart_digmux_source >= UART_DIGMUX_SOURCE_NUM)
	 || (pqu->uart_digmux_sel >= UART_DIGMUX_SELECT_NUM)) {
		dev_err(dev, "invalid console uart digmux setting %d,%d\n", pqu->uart_digmux_sel,
			pqu->uart_digmux_source);
		pqu->uart_digmux_source = UART_DIGMUX_DEFAULT_SOURCE;
		pqu->uart_digmux_sel = UART_DIGMUX_DEFAULT_SELECT;
	}
	dev_info(dev, "console uart digmux source=%d sel=%d\n", pqu->uart_digmux_source,
		 pqu->uart_digmux_sel);

	return 0;
}

static int __maybe_unused mtk_pqu_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_pqu *pqu = rproc->priv;

	mutex_lock(&pqu_state_mutex);
	if (common_clk_init_count > 0)
		common_clk_init_count--;
	mutex_unlock(&pqu_state_mutex);

	if (!pqu->support_suspend) {
		dev_warn(dev, "device doesn't support suspend\n");
		return 0;
	}

	/* do not handle pm ops if !auto_boot*/
	if (!rproc->auto_boot)
		return 0;

	dev_info(dev, "shutdown pqu\n");
	rproc_shutdown(rproc);
	return 0;
}

static int __maybe_unused mtk_pqu_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_pqu *pqu = rproc->priv;
	struct device_node *np = dev->of_node;

	if (!pqu->support_suspend) {
		dev_warn(dev, "device doesn't support suspend\n");
		return 0;
	}

	/* do not handle pm ops if !auto_boot*/
	if (!rproc->auto_boot)
		return 0;

	mutex_lock(&pqu_state_mutex);
	if (common_clk_init_count == 0) {
		dev_info(dev, "reinit pqu clk\n");
		pqu_pll_init(pqu);
		pqu_clk_init(dev, pqu);
		udelay(PLL_DELAY);
	}
	if (strncmp(np->name, "pqu", strlen("pqu")) == 0) {
		pqu_uart_init(pqu);
		udelay(PLL_DELAY);
	}
	common_clk_init_count++;
	mutex_unlock(&pqu_state_mutex);

	dev_info(dev, "boot pqu\n");
	return rproc_boot(rproc);
}

static struct debugfs_reg32 hwreg_regs[] = {
	{ .name = "pc_l", .offset = REG_PC_ADR_OFFSET_0},
	{ .name = "pc_h", .offset = REG_PC_ADR_OFFSET_1},
	{ .name = "sp_l", .offset = REG_SP_ADR_OFFSET_0},
	{ .name = "sp_h", .offset = REG_SP_ADR_OFFSET_1},
	{ .name = "lr_l", .offset = REG_LR_ADR_OFFSET_0},
	{ .name = "lr_h", .offset = REG_LR_ADR_OFFSET_1},
};

static ssize_t mtk_pqu_debugfs_read_symbol(struct file *file, char __user *userbuf,
	size_t len, loff_t *ppos)
{
	char buf[SYMBOL_BUF_LEN] = {0};
	struct mtk_pqu *pqu = NULL;
	uint32_t symbol_addr;
	uint32_t symbol_size;
	size_t size;
	int ret = 0;

	if (!file || !userbuf || !ppos)
		return 0;

	pqu = file->private_data;
	if (!pqu || pqu->symbol_buf[0] == '\0')
		return 0;

	ret = pqu_find_symbol_byname(pqu, pqu->symbol_buf, &symbol_addr, &symbol_size);
	if (ret) {
		size = scnprintf(buf, sizeof(buf), "%s is not found\n", pqu->symbol_buf);
		goto out;
	}

	size = scnprintf(buf, sizeof(buf), "%s 0x%x 0x%x\n", pqu->symbol_buf, symbol_addr,
					 symbol_size);
out:
	return simple_read_from_buffer(userbuf, len, ppos, buf, size);
}

static ssize_t mtk_pqu_debugfs_write_symbol(struct file *file, const char __user *userbuf,
	size_t len, loff_t *ppos)
{
	struct mtk_pqu *pqu = NULL;
	size_t size;
	char *str_ptr = NULL;
	int ret = 0;

	if (!file || !userbuf || !ppos)
		return 0;

	pqu = file->private_data;
	if (!pqu)
		return 0;
	str_ptr = pqu->symbol_buf;

	size = min(sizeof(pqu->symbol_buf) - 1, len);
	ret = copy_from_user(str_ptr, userbuf, size);
	if (ret)
		return -EFAULT;

	str_ptr[strcspn(str_ptr, "\n")] = '\0';

	return size;
}

static const struct file_operations fops_symbol = {
	.read =		mtk_pqu_debugfs_read_symbol,
	.write =		mtk_pqu_debugfs_write_symbol,
	.open =		simple_open,
	.llseek =	default_llseek,
};

static void mtk_pqu_create_debugfs(struct mtk_pqu *pqu)
{
	struct device *dev = NULL;

	if (!pqu)
		return;

	dev = pqu->dev;
	if (!dev)
		return;

	pqu->dbg_dir = debugfs_create_dir(dev->of_node->name, NULL);
	if (!pqu->dbg_dir)
		return;

	debugfs_create_bool("crash", 0400, pqu->dbg_dir, &pqu->core_created);

	debugfs_create_bool("wdt_reboot_stop", 0600, pqu->dbg_dir, &pqu->wdt_reboot_stop);

	if (pqu->hwdbg_base) {
		pqu->hwdbg_regset.base = pqu->hwdbg_base;
		pqu->hwdbg_regset.regs = hwreg_regs;
		pqu->hwdbg_regset.nregs = ARRAY_SIZE(hwreg_regs);
		debugfs_create_regset32("hwdbg", 0400, pqu->dbg_dir, &pqu->hwdbg_regset);
	}

	debugfs_create_file("symbol", 0600, pqu->dbg_dir, pqu, &fops_symbol);

	debugfs_create_u64("load_time_us", 0400, pqu->dbg_dir, &pqu->firmware_load_time_us);
}

static void mtk_pqu_remove_debugfs(struct mtk_pqu *pqu)
{
	if (!pqu)
		return;
	debugfs_remove(pqu->dbg_dir);
}

static const struct dev_pm_ops mtk_pqu_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(mtk_pqu_suspend, mtk_pqu_resume)
};

/*
 * Copy from kernel/dma/direct.c
 * Before change to use of_dma_configure(),
 * we need to have a mechanism to always
 * keep the dma-ranges and mmap in sync.
 */
#define MIN_DMA_REGION_NR 2

static int mtk_dma_direct_set_offset(struct device *dev, phys_addr_t cpu_start,
			 dma_addr_t dma_start, u64 size)
{
	struct bus_dma_region *map;
	u64 offset = (u64)cpu_start - (u64)dma_start;

	if (dev->dma_range_map) {
		dev_err(dev, "attempt to add DMA range to existing map\n");
		return -EINVAL;
	}

	if (!offset)
		return 0;

	map = kcalloc(MIN_DMA_REGION_NR, sizeof(*map), GFP_KERNEL);
	if (!map)
		return -ENOMEM;
	map[0].cpu_start = cpu_start;
	map[0].dma_start = dma_start;
	map[0].offset = offset;
	map[0].size = size;
	dev->dma_range_map = map;
	return 0;
}

static int mtk_pqu_rproc_probe(struct platform_device *pdev)
{
	int ret;
	struct device *dev = &pdev->dev;
	struct device_node *np = dev->of_node;
	struct mtk_pqu *pqu = NULL;
	struct rproc *rproc = NULL;
	const char *firmware;

	if (!is_mutex_inited) {
		is_mutex_inited = true;
		mutex_init(&pqu_state_mutex);
		common_clk_init_count = 0;
	}

	ret = of_property_read_string(pdev->dev.of_node, "firmware-name",
				      &firmware);
	if (ret) {
		dev_err(dev, "prop: firmware-name not found\n");
		return ret;
	}

	rproc = rproc_alloc(dev, np->name, &pqu_ops,
				firmware, sizeof(*pqu));
	if (!rproc) {
		dev_err(dev, "unable to allocate remoteproc\n");
		ret = -ENOMEM;
		return ret;
	}

	rproc->ops->load = pqu_elf_load_segments;

	/* error recovery is not supported at present */
	rproc->recovery_disabled = true;

	/*
	 * At first we lookup the device tree setting.
	 * If there's no auto-boot prop, then check the module param.
	 */
	rproc->auto_boot = of_property_read_bool(np, "mediatek,auto-boot");
	if (!rproc->auto_boot)
		rproc->auto_boot = auto_boot;

	rproc->has_iommu = false;

	pqu = (struct mtk_pqu *)rproc->priv;
	pqu->rproc = rproc;
	pqu->dev = dev;
	INIT_LIST_HEAD(&pqu->clk_init_seq);
	INIT_LIST_HEAD(&pqu->pll_init_seq);
	INIT_LIST_HEAD(&pqu->symbol_list.head);
	INIT_WORK(&pqu->coredump_wq, wq_pqu_coredump);
	INIT_WORK(&pqu->parse_symbol_wq, wq_pqu_parse_symbol);

	platform_set_drvdata(pdev, rproc);
	ret = mtk_pqu_parse_dt(pdev);
	if (ret) {
		dev_err(dev, "parse dt fail\n");
		goto free_rproc;
	}

	ret = mtk_pqu_parse_extra_dt(pdev);
	if (ret) {
		dev_err(dev, "parse extra dt fail : %d\n", ret);
		goto free_rproc;
	}

	pqu->pqu_workqueue = create_workqueue(PQU_WORKQUEUE);
	if (!pqu->pqu_workqueue) {
		dev_err(dev, "cannot create workqueue\n");
		ret = -ENOMEM;
		goto free_rproc;
	}

#ifdef CONFIG_DMABUF_HEAPS
	ret = mtk_pqu_dma_heaps_init(dev);
	if (ret) {
		dev_err(dev, "dma heap init fail\n");
		goto destroy_workqueue;
	}
#endif

	ret = mtk_dma_direct_set_offset(dev, pqu->phy_base, 0, SZ_4G - pqu->phy_base);
	if (ret) {
		dev_err(dev, "Failed to set dma range\n");
		goto destroy_workqueue;
	}

	device_enable_async_suspend(dev);

	ret = devm_request_threaded_irq(dev, pqu->irq, NULL,
				handle_event, IRQF_ONESHOT,
				"mtk-pqu", rproc);
	if (ret) {
		dev_err(dev, "request irq @%d failed\n", pqu->irq);
		goto destroy_workqueue;
	}

	if (pqu->recovery_irq > 0) {
		ret = devm_request_threaded_irq(dev, pqu->recovery_irq, NULL,
				handle_recovery_event, IRQF_ONESHOT | IRQF_SHARED,
				"mtk-pqu", rproc);

		if (ret) {
			dev_err(dev, "request irq @%d failed\n", pqu->recovery_irq);
			goto destroy_workqueue;
		}
	}

	dev_info(dev, "request irq @%d for kick pqu\n",  pqu->irq);

	ret = rproc_add(rproc);
	if (ret) {
		dev_err(dev, "rproc_add failed! ret = %d\n", ret);
		goto destroy_workqueue;
	}

	pqu->kobject_dump = kobject_create_and_add("dump", &dev->kobj);
	if (!pqu->kobject_dump) {
		dev_err(dev, "kobject_create_and_add failed!\n");
		ret = -ENOMEM;
		goto del_rproc;
	}

	sysfs_bin_attr_init(&pqu->mem_bin_attr);
	pqu->mem_bin_attr.attr.name = "mem";
	pqu->mem_bin_attr.attr.mode = 0444;
	pqu->mem_bin_attr.size = 0;
	pqu->mem_bin_attr.read = mem_read;
	pqu->mem_bin_attr.private = pqu;
	ret = sysfs_create_bin_file(pqu->kobject_dump, &pqu->mem_bin_attr);
	if (ret) {
		dev_err(dev, "sysfs_create_bin_file failed! ret = %d\n", ret);
		goto destroy_kobject;
	}

	if (pqu->log_base != 0) {
		sysfs_bin_attr_init(&pqu->log_bin_attr);
		pqu->log_bin_attr.attr.name = "log";
		pqu->log_bin_attr.attr.mode = 0444;
		pqu->log_bin_attr.size = 0;
		pqu->log_bin_attr.read = log_read;
		pqu->log_bin_attr.private = pqu;
		ret = sysfs_create_bin_file(pqu->kobject_dump, &pqu->log_bin_attr);
		if (ret) {
			dev_err(dev, "sysfs_create_bin_file failed! ret = %d\n", ret);
			goto remove_sysfs;
		}
	}

	if (pqu->atrace_base != 0) {
		sysfs_bin_attr_init(&pqu->atrace_bin_attr);
		pqu->atrace_bin_attr.attr.name = "atrace";
		pqu->atrace_bin_attr.attr.mode = 0444;
		pqu->atrace_bin_attr.size = 0;
		pqu->atrace_bin_attr.read = atrace_read;
		pqu->atrace_bin_attr.private = pqu;
		ret = sysfs_create_bin_file(pqu->kobject_dump, &pqu->atrace_bin_attr);
		if (ret) {
			dev_err(dev, "sysfs_create_bin_file failed! ret = %d\n", ret);
			goto remove_sysfs;
		}
	}

	mtk_pqu_create_debugfs(pqu);

	mutex_lock(&pqu_state_mutex);
	if (common_clk_init_count == 0) {
		check_def_freq();
		dev_info(dev, "init pqu clk @ %d MHz\n", def_freq);
		pqu_pll_init(pqu);
		pqu_clk_init(dev, pqu);
		udelay(PLL_DELAY);
	}
	if (strncmp(np->name, "pqu", strlen("pqu")) == 0) {
		pqu_uart_init(pqu);
		udelay(PLL_DELAY);
	}
	common_clk_init_count++;
	mutex_unlock(&pqu_state_mutex);

	return ret;

remove_sysfs:
	sysfs_remove_bin_file(pqu->kobject_dump, &pqu->mem_bin_attr);
	sysfs_remove_bin_file(pqu->kobject_dump, &pqu->log_bin_attr);
	sysfs_remove_bin_file(pqu->kobject_dump, &pqu->atrace_bin_attr);
destroy_kobject:
	kobject_del(pqu->kobject_dump);
	kobject_put(pqu->kobject_dump);
del_rproc:
	rproc_del(rproc);
destroy_workqueue:
	destroy_workqueue(pqu->pqu_workqueue);
free_rproc:
	rproc_free(rproc);
	return ret;
}

static int mtk_pqu_rproc_remove(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct mtk_pqu *pqu = (struct mtk_pqu *)rproc->priv;

	if (pqu) {
		pqu_destory_symbol_list(pqu);

		kfree(pqu->tables.sym_table_ptr);
		pqu->tables.sym_table_ptr = NULL;
		pqu->tables.sym_table_sz = 0;

		kfree(pqu->tables.str_table_ptr);
		pqu->tables.str_table_ptr = NULL;
		pqu->tables.str_table_sz = 0;

		mtk_pqu_remove_debugfs(pqu);

		if (pqu->kobject_dump) {
			sysfs_remove_bin_file(pqu->kobject_dump, &pqu->mem_bin_attr);
			if (pqu->core_created)
				sysfs_remove_bin_file(pqu->kobject_dump, &pqu->core_bin_attr);
			sysfs_remove_bin_file(pqu->kobject_dump, &pqu->log_bin_attr);
			sysfs_remove_bin_file(pqu->kobject_dump, &pqu->atrace_bin_attr);
			kobject_del(pqu->kobject_dump);
			kobject_put(pqu->kobject_dump);
			pqu->kobject_dump = NULL;
		}
		flush_work(&pqu->coredump_wq);
		flush_work(&pqu->parse_symbol_wq);
		if (pqu->pqu_workqueue)
			destroy_workqueue(pqu->pqu_workqueue);
	}
	of_reserved_mem_device_release(dev);
	rproc_del(rproc);
	rproc_free(rproc);
	return 0;
}

static const struct of_device_id mtk_pqu_of_match[] = {
	{ .compatible = "mediatek,pqu"},
	{},
};

MODULE_DEVICE_TABLE(of, mtk_pqu_of_match);

static struct platform_driver mtk_pqu_rproc = {
	.probe = mtk_pqu_rproc_probe,
	.remove = mtk_pqu_rproc_remove,
	.driver = {
		.name = "mtk-pqu",
		.pm = &mtk_pqu_pm_ops,
		.of_match_table = of_match_ptr(mtk_pqu_of_match),
	},
};

module_platform_driver(mtk_pqu_rproc);

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Mark-PK Tsai <mark-pk.tsai@mediatek.com>");
MODULE_DESCRIPTION("MediaTek PQU Remoteproce driver");
