// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 * Author Owen.Tseng <Owen.Tseng@mediatek.com>
 */

#include <linux/err.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/watchdog.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include "wdt-mt5896-coda.h"
#include "wdt-mt5896-riu.h"
#include <linux/jiffies.h>
#include <linux/timer.h>
#include <linux/kmsg_dump.h>
#include <linux/of_address.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>

#define WDT_PRETIMEOUT		25
#define WDT_MAX_TIMEOUT		30
#define WDT_MIN_TIMEOUT		1

#define WDT_CLOCK		12000000
#define WDT_CLOCK_MS	12000
#define REG_0024_PM_POR_STATUS(base)	(base + 0x024)

#define DRV_NAME		"mtktv-wdt"
#define DRV_VERSION		"1.0"
#define WDT_HEART_BEATS_HZ	(HZ/2)
#define STR_LENSIZE		32
#define WORD_SHIFT		16
#define PMWDT_BASE		2
#define REG_0008_SYS_WDT	0x8
#define REG_0048_PM51_WDT	0x48
#define REG_0088_CM4_WDT	0x88

#define WDT_2ND_RST_BIT_PQU_RV55 6

static bool nowayout = WATCHDOG_NOWAYOUT;
static unsigned int timeout = WDT_MAX_TIMEOUT;
bool bwdtenable = true;
bool bwdttaskinit;
static bool wdt_pretimeout_isr_occurred = 1;
#define WDT_DELAY_UNIT_500MS    500

#define MTK_WDT_LOG_MIN			(0)
#define MTK_WDT_LOG_MUTE		MTK_WDT_LOG_MIN	/* set to no log mode */
#define MTK_WDT_LOG_ALERT		(1)
#define MTK_WDT_LOG_CRIT		(2)
#define MTK_WDT_LOG_ERR			(3)
#define MTK_WDT_LOG_WARN		(4)
#define MTK_WDT_LOG_NOTICE		(5)
#define MTK_WDT_LOG_INFO		(6)
#define MTK_WDT_LOG_MAX			MTK_WDT_LOG_INFO

/* set loglevel control to be useless, use dmesg can see log info */
#define MTK_WDT_LOG_NOT_CTRL	(255)

static int dtv_wdt_init_log_level = MTK_WDT_LOG_MUTE; //MTK_WDT_LOG_NOT_CTRL;
static int wdt_log_level;

#define wdt_log(level, fmt, args...) \
do { \
	if (wdt_log_level == MTK_WDT_LOG_NOT_CTRL) { \
		if (level == MTK_WDT_LOG_INFO) \
			pr_info(fmt, ##args); \
		else if (level == MTK_WDT_LOG_NOTICE) \
			pr_notice(fmt, ##args); \
		else if (level == MTK_WDT_LOG_WARN) \
			pr_warn(fmt, ##args); \
		else if (level == MTK_WDT_LOG_ERR) \
			pr_err(fmt, ##args); \
		else if (level == MTK_WDT_LOG_CRIT) \
			pr_crit(fmt, ##args); \
		else if (level == MTK_WDT_LOG_ALERT) \
			pr_alert(fmt, ##args); \
	} else if (wdt_log_level >= level) \
		pr_emerg(fmt, ##args); \
} while (0)

struct mtktv_wdt_dev {
	struct watchdog_device wdt_dev;
	void __iomem *wdt_base;
	void __iomem *pm_port_base;
	void __iomem *pm_wdt_base;
	int irq_num;
	struct timer_list timer;	/* The timer that pings the watchdog */
	u32 wdt_ping_self;
	u32 wdt_timeout_ms;
	struct kobject *wdt_kobj;
	u32 wdt_pre_timeout;
	struct task_struct *mtktv_wdt_task;
	u32 wdt_auto_restore;
};

static int mtktv_wdt_kthread_task(void *data);

static int mtktv_wdt_restart(struct watchdog_device *wdt_dev,
			     unsigned long action, void *data)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);

	while (1) {
		mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0190_L4_MAIN), 1);
		mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0194_L4_MAIN), 0);
		mdelay(5);
	}

	return 0;
}

static int mtktv_wdt_ping(struct watchdog_device *wdt_dev)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);

	return 0;
}

static int mtktv_wdt_set_timeout(struct watchdog_device *wdt_dev,
				  unsigned int timeout)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);
	unsigned int wdt_cnt = timeout*WDT_CLOCK;

	wdt_dev->timeout = timeout;

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0190_L4_MAIN), wdt_cnt);
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0194_L4_MAIN), (wdt_cnt >> WORD_SHIFT));

	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x1, REG_WDT_ENABLE_1ST);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x1, REG_WDT_ENABLE_2ND);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x0, REG_WDT_2ND_MAX);

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: timeout %d.\n", __func__, timeout);

	return 0;
}

static int mtktv_wdt_set_timeout_ms(struct watchdog_device *wdt_dev,
				  unsigned int timeout)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);
	unsigned int wdt_cnt = timeout*WDT_CLOCK_MS;

	mtktv_wdt->wdt_timeout_ms = timeout;
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0190_L4_MAIN), wdt_cnt);
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0194_L4_MAIN), (wdt_cnt >> WORD_SHIFT));

	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x1, REG_WDT_ENABLE_1ST);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x1, REG_WDT_ENABLE_2ND);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x0, REG_WDT_2ND_MAX);

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: timeout %d.\n", __func__, timeout);

	return 0;
}

static int mtktv_wdt_set_timeout_cnt(struct watchdog_device *wdt_dev,
				  unsigned int timeout)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);
	unsigned int wdt_cnt = timeout;

	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: timeout %d.\n", __func__, timeout);

	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x1, REG_WDT_ENABLE_1ST);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x1, REG_WDT_ENABLE_2ND);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x0, REG_WDT_2ND_MAX);

	// set wdt timeout to wdt_cnt
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0194_L4_MAIN), (wdt_cnt >> WORD_SHIFT));
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0190_L4_MAIN), wdt_cnt);

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);

	return 0;
}

static int mtktv_wdt_set_pretimeout(struct watchdog_device *wdt_dev,
				    unsigned int timeout)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);
	unsigned int wdt_cnt = timeout*WDT_CLOCK;

	wdt_dev->pretimeout = timeout;

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_018C_L4_MAIN), (wdt_cnt >> WORD_SHIFT));
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: timeout %d.\n", __func__, timeout);

	return 0;
}

static int mtktv_wdt_stop(struct watchdog_device *wdt_dev)
{
	u16 val;
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);

	val = mtk_readw_relaxed(REG_0024_PM_POR_STATUS(mtktv_wdt->pm_port_base));
	val |= (BIT(0));
	val |= (BIT(WDT_2ND_RST_BIT_PQU_RV55));
	mtk_writew_relaxed(REG_0024_PM_POR_STATUS(mtktv_wdt->pm_port_base), val);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x0, REG_WDT_ENABLE_1ST);
	mtk_write_fld((mtktv_wdt->wdt_base + REG_0198_L4_MAIN), 0x0, REG_WDT_ENABLE_2ND);
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0190_L4_MAIN), 0);
	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0194_L4_MAIN), 0);
	//mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_018C_L4_MAIN), 0);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: done.\n", __func__);

	return 0;
}

static int mtktv_wdt_start(struct watchdog_device *wdt_dev)
{
	u16 val;
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_dev);

	val = mtk_readw_relaxed(REG_0024_PM_POR_STATUS(mtktv_wdt->pm_port_base));
	if (bwdtenable)
		val &= ~(BIT(0));
	mtk_writew_relaxed(REG_0024_PM_POR_STATUS(mtktv_wdt->pm_port_base), val);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: done.\n", __func__);

	return 0;
}

static irqreturn_t mtktv_wdt_isr(int irq, void *wdt_arg)
{
	struct mtktv_wdt_dev *mtktv_wdt = watchdog_get_drvdata(wdt_arg);
	struct device *dev = mtktv_wdt->wdt_dev.parent;

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);
	if (wdt_pretimeout_isr_occurred) {
		disable_irq_nosync(irq);
		wdt_pretimeout_isr_occurred = 0;
	} else
		wdt_pretimeout_isr_occurred = 1;

	if (mtktv_wdt->wdt_ping_self == 0)
		sysfs_notify(&dev->kobj, "wdt_extend", "wdtwake");

	return IRQ_HANDLED;
}

static const struct watchdog_info mtktv_wdt_info = {
	.identity	= DRV_NAME,
	.options	= WDIOF_SETTIMEOUT |
			  WDIOF_KEEPALIVEPING |
			  WDIOF_MAGICCLOSE,
};

static const struct watchdog_info mtktv_wdt_pretimeout_info = {
	.identity	= DRV_NAME,
	.options	= WDIOF_SETTIMEOUT |
			  WDIOF_KEEPALIVEPING |
			  WDIOF_MAGICCLOSE |
			  WDIOF_PRETIMEOUT,
};

static const struct watchdog_ops mtktv_wdt_ops = {
	.owner		= THIS_MODULE,
	.start		= mtktv_wdt_start,
	.stop		= mtktv_wdt_stop,
	.ping		= mtktv_wdt_ping,
	.set_timeout	= mtktv_wdt_set_timeout,
	.set_pretimeout	= mtktv_wdt_set_pretimeout,
	.restart	= mtktv_wdt_restart,
};

static void wdtdev_inter_ping(struct timer_list *t)
{
	struct mtktv_wdt_dev *mtktv_wdt = from_timer(mtktv_wdt, t, timer);

	mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);
	mod_timer(&mtktv_wdt->timer, jiffies + WDT_HEART_BEATS_HZ);
}

/* wdt sysfs for wakeup reason poll */
static ssize_t wdtwake_show(struct device *dev,
				  struct device_attribute *attr,
				  char *buf)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);

	return scnprintf(buf, STR_LENSIZE, "%d\n", mtktv_wdt->wdt_dev.timeout);
}

static DEVICE_ATTR_RO(wdtwake);

/* wdt timeout path */
static ssize_t wdt_ms_timeout_show(struct device *dev,
				  struct device_attribute *attr,
				  char *buf)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);
	u32 val = 0;
	u32 time_tmp = 0;

	if (!mtktv_wdt)
		return -ENOMEM;
	val = mtk_readw_relaxed(mtktv_wdt->wdt_base + REG_0194_L4_MAIN);
	time_tmp = (val << WORD_SHIFT) |
	mtk_readw_relaxed(mtktv_wdt->wdt_base + REG_0190_L4_MAIN);
	mtktv_wdt->wdt_timeout_ms = time_tmp / WDT_CLOCK_MS;
	mtktv_wdt->wdt_dev.timeout = time_tmp / WDT_CLOCK;
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: wdt_timeout_ms %d.\n",
		__func__, mtktv_wdt->wdt_timeout_ms);

	return scnprintf(buf, STR_LENSIZE, "%d\n", mtktv_wdt->wdt_timeout_ms);
}

static ssize_t wdt_ms_timeout_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);

	mtktv_wdt_start(&mtktv_wdt->wdt_dev);
	mtktv_wdt_set_timeout_ms(&mtktv_wdt->wdt_dev, val);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: wdt_timeout_ms %d.\n",
		__func__, val);

	return ret ? : size;
}

static DEVICE_ATTR_RW(wdt_ms_timeout);

/* wdt off path */
static ssize_t wdtpinself_show(struct device *dev,
				  struct device_attribute *attr,
				  char *buf)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: wdt_ping_self %d.\n",
		__func__, mtktv_wdt->wdt_ping_self);

	return scnprintf(buf, STR_LENSIZE, "%d\n", mtktv_wdt->wdt_ping_self);
}

static ssize_t wdtpinself_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);

	mtktv_wdt->wdt_ping_self = val?1:0;

	if (val) {
		//timer_setup(&mtktv_wdt->timer, wdtdev_inter_ping, 0);
		//mod_timer(&mtktv_wdt->timer, jiffies + WDT_HEART_BEATS_HZ);
		mtktv_wdt_start(&mtktv_wdt->wdt_dev);
		mtktv_wdt_set_timeout(&mtktv_wdt->wdt_dev, timeout);
		if (bwdttaskinit == false) {
			bwdttaskinit = true;
			mtktv_wdt->mtktv_wdt_task = kthread_run(mtktv_wdt_kthread_task,
				mtktv_wdt, "mtk_wdt_task");
			wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: Creat mtk_wdt_task.\n", __func__);
		}
	} else
		mtktv_wdt_stop(&mtktv_wdt->wdt_dev);
	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: wdt_ping_self %d.\n",
		__func__, mtktv_wdt->wdt_ping_self);

	return ret ? : size;
}

static DEVICE_ATTR_RW(wdtpinself);

static ssize_t wdt_reset_chk_show(struct device *dev,
			   struct device_attribute *attr,
			   char *buf)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);
	unsigned int val = 0;

	val = mtk_readw_relaxed(mtktv_wdt->pm_wdt_base + REG_0008_SYS_WDT)&0x1;
	val |= (mtk_readw_relaxed(mtktv_wdt->pm_wdt_base + REG_0048_PM51_WDT)&0x1) << 1;
	val |= (mtk_readw_relaxed(mtktv_wdt->pm_wdt_base + REG_0088_CM4_WDT)&0x1) << PMWDT_BASE;
	if (val&0x1)
		return scnprintf(buf, STR_LENSIZE, "%s\n", "sys-wdt-reset");
	else if ((val >> 1)&0x1)
		return scnprintf(buf, STR_LENSIZE, "%s\n", "pm51-wdt-reset");
	else if ((val >> PMWDT_BASE)&0x1)
		return scnprintf(buf, STR_LENSIZE, "%s\n", "cm4-wdt-reset");
	else
		return scnprintf(buf, STR_LENSIZE, "%s\n", "no-pm-wdt-reset");
}
static DEVICE_ATTR_RO(wdt_reset_chk);

static ssize_t log_level_show(struct device *dev, struct device_attribute *attr,
			      char *buf)
{
	struct mtk_scanpwm *mdp = dev_get_drvdata(dev);
	char *str = buf;
	char *end = buf + PAGE_SIZE;

	str +=
	    scnprintf(str, end - str,
		      "not_ctrl:\t\t%d\nmute:\t\t%d\nalert:\t\t%d\ncritical:\t\t%d\nerror:\t\t%d\n"
		      "warning:\t\t%d\nnotice:\t\t%d\ninfo:\t\t%d\nCurrent:\t%d\n",
		      MTK_WDT_LOG_NOT_CTRL, MTK_WDT_LOG_MUTE,
		      MTK_WDT_LOG_ALERT, MTK_WDT_LOG_CRIT,
		      MTK_WDT_LOG_ERR, MTK_WDT_LOG_WARN,
		      MTK_WDT_LOG_NOTICE, MTK_WDT_LOG_INFO,
		      wdt_log_level);

	return (str - buf);
}

static ssize_t log_level_store(struct device *dev,
			       struct device_attribute *attr,
			       const char *buf, size_t count)
{
	struct mtk_scanpwm *mdp = dev_get_drvdata(dev);
	ssize_t retval;
	int level;

	retval = kstrtoint(buf, 10, &level);
	if (retval == 0) {
		if (((level >= MTK_WDT_LOG_MIN) && (level <= MTK_WDT_LOG_MAX)) ||
			(level == MTK_WDT_LOG_NOT_CTRL))
			wdt_log_level = level;
		else
			retval = -EINVAL;
	}

	return (retval < 0) ? retval : count;
}

static DEVICE_ATTR_RW(log_level);

static ssize_t wdt_cnt_timeout_store(struct device *dev,
			      struct device_attribute *attr,
			      const char *buf, size_t size)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);
	unsigned int val = 0;
	int ret;

	ret = kstrtouint(buf, 0, &val);
	mtktv_wdt_start(&mtktv_wdt->wdt_dev);
	mtktv_wdt_set_timeout_cnt(&mtktv_wdt->wdt_dev, val);

	return ret ? : size;
}

static DEVICE_ATTR_WO(wdt_cnt_timeout);

static struct attribute *wdt_extend_attrs[] = {
	&dev_attr_wdtwake.attr,
	&dev_attr_wdtpinself.attr,
	&dev_attr_wdt_ms_timeout.attr,
	&dev_attr_wdt_reset_chk.attr,
	&dev_attr_log_level.attr,
	&dev_attr_wdt_cnt_timeout.attr,
	NULL,
};
static const struct attribute_group wdt_extend_attr_group = {
	.name = "wdt_extend",
	.attrs = wdt_extend_attrs
};

static int mtktv_wdt_kthread_task(void *data)
{
	struct mtktv_wdt_dev *mtktv_wdt = (struct mtktv_wdt_dev *)data;

	while (!kthread_should_stop()) {
		if (mtktv_wdt->wdt_ping_self == 1)
			mtk_writew_relaxed((mtktv_wdt->wdt_base + REG_0180_L4_MAIN), 1);
		msleep(WDT_DELAY_UNIT_500MS);
	}
	return 0;
}

static int mtktv_wdt_probe(struct platform_device *pdev)
{
	struct mtktv_wdt_dev *mtktv_wdt;
	int err;
	int reset_sts;

	mtktv_wdt = devm_kzalloc(&pdev->dev, sizeof(*mtktv_wdt), GFP_KERNEL);
	if (!mtktv_wdt)
		return -ENOMEM;

	platform_set_drvdata(pdev, mtktv_wdt);

	mtktv_wdt->wdt_base = of_iomap(pdev->dev.of_node, 0);
	mtktv_wdt->pm_port_base = of_iomap(pdev->dev.of_node, 1);
	mtktv_wdt->pm_wdt_base = of_iomap(pdev->dev.of_node, PMWDT_BASE);

	if (IS_ERR(mtktv_wdt->wdt_base))
		return PTR_ERR(mtktv_wdt->wdt_base);

	if (IS_ERR(mtktv_wdt->pm_port_base)) {
		dev_err(&pdev->dev, "err pm_port_base=%#lx\n",
			(unsigned long)mtktv_wdt->pm_port_base);
		return PTR_ERR(mtktv_wdt->pm_port_base);
	}

	if (IS_ERR(mtktv_wdt->pm_wdt_base)) {
		dev_err(&pdev->dev, "err pm_wdt_base=%#lx\n",
			(unsigned long)mtktv_wdt->pm_wdt_base);
		return PTR_ERR(mtktv_wdt->pm_wdt_base);
	}

	err = of_property_read_u32(pdev->dev.of_node,
				"wdt-ping-self", &mtktv_wdt->wdt_ping_self);
	if (err) {
		dev_info(&pdev->dev, "could not get pingself resource\n");
		mtktv_wdt->wdt_ping_self = 1;
	}

	err = of_property_read_u32(pdev->dev.of_node,
				"wdt-auto-restore", &mtktv_wdt->wdt_auto_restore);
	if (err) {
		dev_info(&pdev->dev, "could not get wdt_auto_restore, set 0 for default.\n");
		mtktv_wdt->wdt_auto_restore = 0;
	}

	err = of_property_read_u32(pdev->dev.of_node,
				"pre-timeout-isr", &mtktv_wdt->wdt_pre_timeout);
	if (err) {
		dev_info(&pdev->dev, "could not get pingself resource\n");
		mtktv_wdt->wdt_pre_timeout = 0;
	}

	mtktv_wdt->wdt_dev.info = &mtktv_wdt_pretimeout_info;
	mtktv_wdt->irq_num = platform_get_irq(pdev, 0);
	if (mtktv_wdt->wdt_pre_timeout == 1) {
		if (!devm_request_threaded_irq(&pdev->dev, mtktv_wdt->irq_num,
			mtktv_wdt_isr,
			NULL,
			IRQF_ONESHOT, dev_name(&pdev->dev), &mtktv_wdt->wdt_dev))
			mtktv_wdt->wdt_dev.info = &mtktv_wdt_pretimeout_info;
	}
	mtktv_wdt->wdt_dev.ops = &mtktv_wdt_ops;
	mtktv_wdt->wdt_dev.timeout = WDT_MAX_TIMEOUT;
	mtktv_wdt->wdt_dev.max_timeout = WDT_MAX_TIMEOUT;
	mtktv_wdt->wdt_dev.min_timeout = WDT_MIN_TIMEOUT;
	mtktv_wdt->wdt_dev.parent = &pdev->dev;

	watchdog_init_timeout(&mtktv_wdt->wdt_dev, timeout, &pdev->dev);
	watchdog_set_nowayout(&mtktv_wdt->wdt_dev, nowayout);
	watchdog_set_restart_priority(&mtktv_wdt->wdt_dev, 128);

	reset_sts = mtk_readw_relaxed((mtktv_wdt->wdt_base + REG_0198_L4_MAIN));
	if ((reset_sts) & 1)
		mtktv_wdt->wdt_dev.bootstatus = WDIOF_CARDRESET;

	watchdog_set_drvdata(&mtktv_wdt->wdt_dev, mtktv_wdt);
	if (mtktv_wdt->wdt_ping_self == 1) {
		//timer_setup(&mtktv_wdt->timer, wdtdev_inter_ping, 0);
		//mod_timer(&mtktv_wdt->timer, jiffies + WDT_HEART_BEATS_HZ);
		mtktv_wdt_start(&mtktv_wdt->wdt_dev);
		mtktv_wdt_set_timeout(&mtktv_wdt->wdt_dev, timeout);
		bwdttaskinit = true;
		mtktv_wdt->mtktv_wdt_task = kthread_run(mtktv_wdt_kthread_task,
			mtktv_wdt, "mtk_wdt_task");
		dev_info(&pdev->dev, "[WDT][%s]: Creat mtk_wdt_task.\n", __func__);
	} else
		bwdttaskinit = false;

	if (mtktv_wdt->wdt_pre_timeout == 1)
		mtktv_wdt_set_pretimeout(&mtktv_wdt->wdt_dev, WDT_PRETIMEOUT);

	err = watchdog_register_device(&mtktv_wdt->wdt_dev);
	if (unlikely(err))
		return err;

	err = sysfs_create_group(&pdev->dev.kobj, &wdt_extend_attr_group);
	if (err < 0)
		dev_err(&pdev->dev, "wdt sysfs_create_group failed: %d\n", err);

	dev_info(&pdev->dev, "Watchdog enabled (timeout=%d sec, nowayout=%d)\n",
			mtktv_wdt->wdt_dev.timeout, nowayout);

	wdt_log_level = dtv_wdt_init_log_level;

	return 0;
}

static void mtktv_wdt_shutdown(struct platform_device *pdev)
{
	struct mtktv_wdt_dev *mtktv_wdt = platform_get_drvdata(pdev);

	if (watchdog_active(&mtktv_wdt->wdt_dev)) {
		mtktv_wdt_stop(&mtktv_wdt->wdt_dev);
		wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: done.\n", __func__);
	}
}

static int mtktv_wdt_remove(struct platform_device *pdev)
{
	struct mtktv_wdt_dev *mtktv_wdt = platform_get_drvdata(pdev);

	watchdog_unregister_device(&mtktv_wdt->wdt_dev);

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int mtktv_wdt_suspend(struct device *dev)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);

	if ((mtktv_wdt->wdt_ping_self == 1) ||
		((mtktv_wdt->wdt_auto_restore == 1) && watchdog_active(&mtktv_wdt->wdt_dev)))
		mtktv_wdt_stop(&mtktv_wdt->wdt_dev);

	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: done.\n", __func__);

	return 0;
}

static int mtktv_wdt_resume(struct device *dev)
{
	struct mtktv_wdt_dev *mtktv_wdt = dev_get_drvdata(dev);

	if ((mtktv_wdt->wdt_ping_self == 1) ||
		((mtktv_wdt->wdt_auto_restore == 1) && watchdog_active(&mtktv_wdt->wdt_dev))) {
		mtktv_wdt_start(&mtktv_wdt->wdt_dev);
		mtktv_wdt_set_timeout(&mtktv_wdt->wdt_dev, mtktv_wdt->wdt_dev.timeout);
	}
	if (mtktv_wdt->wdt_pre_timeout == 1)
		mtktv_wdt_set_pretimeout(&mtktv_wdt->wdt_dev, WDT_PRETIMEOUT);

	wdt_log(MTK_WDT_LOG_INFO, "[WDT][%s]: done.\n", __func__);

	return 0;
}
#endif

static const struct of_device_id mtktv_wdt_dt_ids[] = {
	{ .compatible = "mediatek,mt5896-wdt" },
	{},
};
MODULE_DEVICE_TABLE(of, mtktv_wdt_dt_ids);

static const struct dev_pm_ops mtktv_wdt_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(mtktv_wdt_suspend,
				mtktv_wdt_resume)
};

static struct platform_driver mtktv_wdt_driver = {
	.probe		= mtktv_wdt_probe,
	.remove		= mtktv_wdt_remove,
	.shutdown	= mtktv_wdt_shutdown,
	.driver		= {
		.name		= DRV_NAME,
		.pm		= &mtktv_wdt_pm_ops,
		.of_match_table	= mtktv_wdt_dt_ids,
	},
};

module_platform_driver(mtktv_wdt_driver);

module_param_named(wdt_enable, bwdtenable, bool, 0444);
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
			__MODULE_STRING(WATCHDOG_NOWAYOUT) ")");

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Owen Tseng");
MODULE_DESCRIPTION("Mediatek WatchDog Timer Driver");
MODULE_VERSION(DRV_VERSION);
