// SPDX-License-Identifier: GPL-2.0-only
// Copyright (C) 2023 NVIDIA CORPORATION. All rights reserved.

#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_irq.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/syscore_ops.h>
#include <linux/uaccess.h>
#include <linux/watchdog.h>

#if 0
#include <soc/tegra/pmc.h>
#define PMC_HALT_IN_FIQ() tegra_pmc_is_halt_in_fiq()
#else
#define PMC_HALT_IN_FIQ() (0)
#endif

/* The total expiry count of Tegra WDTs supported by HW */
#define EXPIRY_COUNT		5

/*
 * minimum and maximum Timer PTV in seconds
 * MAX_TMR_PTV defines maximum value in seconds which can be fed into
 * TOP_TKE_TMR_PTV which is of size 29 bits
 * MAX_TMR_PTV = (1 << 29) / USEC_PER_SEC;
 */
#define MIN_TMR_PTV		1
#define MAX_TMR_PTV		536

/*
 * To detect lockup condition, the heartbeat should be EXPIRY_COUNT*lockup.
 * It may be taken over later by timeout value requested by application.
 * Must be greater than MIN_TMR_PTV and lower than MAX_TMR_PTV*active_count.
 */
#define HEARTBEAT		120

/* Bit numbers for status flags */
#define WDT_ENABLED		0
#define WDT_ENABLED_ON_INIT	1
#define WDT_ENABLED_USERSPACE	2

#define TIMER_INDEX_T186	0
#define TIMER_INDEX_T234	1

struct tegra_wdt_t18x_soc {
	bool unmask_hw_irq;
	int timer_index;
};

struct tegra_wdt_t18x {
	struct device		*dev;
	struct watchdog_device	wdt;
	unsigned long		users;
	void __iomem		*wdt_source;
	void __iomem		*wdt_timer;
	void __iomem		*wdt_tke;
	struct dentry		*root;
	const struct tegra_wdt_t18x_soc	*soc;
	u32			config;
	int			irq;
	int			hwirq;
	unsigned long		status;
	bool			enable_on_init;
	int			active_count;
	int			shutdown_timeout;
	int			wdt_index;
	int			tmr_index;
	bool			extended_suspend;
	bool			config_locked;
};

/*
 * Global variable to store wdt pointer required by nvdumper and pmic to
 * change wdt state
 */
static struct tegra_wdt_t18x *t18x_wdt;

static bool nowayout = WATCHDOG_NOWAYOUT;
module_param(nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close");

static bool default_disable;
module_param(default_disable, bool, 0644);
MODULE_PARM_DESC(default_disable, "Default state of watchdog");

static struct syscore_ops tegra_wdt_t18x_syscore_ops;

static struct tegra_wdt_t18x *to_tegra_wdt_t18x(struct watchdog_device *wdt)
{
	return container_of(wdt, struct tegra_wdt_t18x, wdt);
}

#define TOP_TKE_TKEIE_BASE		0x100
#define TOP_TKE_TKEIE(i)		(0x100 + 4 * (i))
#define TOP_TKE_TKEIE_WDT_MASK(i)	(1 << (16 + 4 * (i)))
#define TOP_TKE_TMR_PTV			0
#define TOP_TKE_TMR_EN			BIT(31)
#define TOP_TKE_TMR_PERIODIC		BIT(30)
#define TOP_TKE_TMR_PCR			0x4
#define TOP_TKE_TMR_PCR_INTR		BIT(30)
#define WDT_CFG				(0)
#define WDT_CFG_PERIOD			BIT(4)
#define WDT_CFG_INT_EN			BIT(12)
#define WDT_CFG_FINT_EN			BIT(13)
#define WDT_CFG_REMOTE_INT_EN		BIT(14)
#define WDT_CFG_DBG_RST_EN		BIT(15)
#define WDT_CFG_SYS_PORST_EN		BIT(16)
#define WDT_CFG_DISALLOW_FREEZE		BIT(25)
#define WDT_CFG_ERR_THRESHOLD		(7 << 20)
#define WDT_STATUS			(0x4)
#define WDT_INTR_STAT			BIT(1)
#define WDT_CMD				(0x8)
#define WDT_CMD_START_COUNTER		BIT(0)
#define WDT_CMD_DISABLE_COUNTER		BIT(1)
#define WDT_UNLOCK			(0xC)
#define WDT_UNLOCK_PATTERN		(0xC45A << 0)
#define WDT_SKIP			(0x10)
#define WDT_SKIP_VAL(i, val)		(((val) & 0x7) << (4 * (i)))
#define TIMER_INDEX(add)		((((add) >> 16) & 0xF) - 0x2)
#define WATCHDOG_INDEX(add)		((((add) >> 16) & 0xF) - 0xc)
#define T234_TIMER_INDEX(add)		((((add) >> 16) & 0xF) - 0x9)
#define T234_WATCHDOG_INDEX(add)	((((add) >> 16) & 0xF) - 0x9)

static int __tegra_wdt_t18x_ping(struct tegra_wdt_t18x *twdt_t18x)
{
	u32 val;

	/* Disable timer, load the timeout value and restart. */
	writel(WDT_UNLOCK_PATTERN, twdt_t18x->wdt_source + WDT_UNLOCK);
	writel(WDT_CMD_DISABLE_COUNTER, twdt_t18x->wdt_source + WDT_CMD);

	writel(TOP_TKE_TMR_PCR_INTR, twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR);

	val = (twdt_t18x->wdt.timeout * USEC_PER_SEC) / twdt_t18x->active_count;
	val |= (TOP_TKE_TMR_EN | TOP_TKE_TMR_PERIODIC);
	writel(val, twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV);

	writel(WDT_CMD_START_COUNTER, twdt_t18x->wdt_source + WDT_CMD);

	dev_dbg(twdt_t18x->dev, "Watchdog%d: wdt cleared\n", twdt_t18x->wdt.id);

	return 0;
}

static irqreturn_t tegra_wdt_t18x_isr(int irq, void *data)
{
	struct tegra_wdt_t18x *twdt_t18x = data;

	__tegra_wdt_t18x_ping(twdt_t18x);

	return IRQ_HANDLED;
}

static void tegra_wdt_t18x_ref(struct watchdog_device *wdt)
{
	struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);

	if (twdt_t18x->irq <= 0)
		return;

	/* Remove the interrupt handler if userspace is taking over WDT. */
	if (!test_and_set_bit(WDT_ENABLED_USERSPACE, &twdt_t18x->status) &&
	    test_bit(WDT_ENABLED_ON_INIT, &twdt_t18x->status))
		disable_irq(twdt_t18x->irq);
}

static inline int tegra_wdt_t18x_skip(struct tegra_wdt_t18x *twdt_t18x)
{
	u32 val = 0;
	int skip_count = 0;
	bool remote_skip = !(twdt_t18x->config & WDT_CFG_REMOTE_INT_EN);
	bool dbg_skip = !(twdt_t18x->config & WDT_CFG_DBG_RST_EN);

	if (remote_skip) {
		if (dbg_skip) {
			val |= WDT_SKIP_VAL(2, 2);
			skip_count += 2;
		} else {
			val |= WDT_SKIP_VAL(2, 1);
			skip_count += 1;
		}
	} else {
		if (dbg_skip) {
			val |= WDT_SKIP_VAL(3, 1);
			skip_count += 1;
		}
	}

	if (val)
		writel(val, twdt_t18x->wdt_source + WDT_SKIP);

	return skip_count;
}

static int __tegra_wdt_t18x_enable(struct tegra_wdt_t18x *twdt_t18x)
{
	u32 val;

	/* Unmask IRQ. This has to be called after every WDT power gate */
	if (twdt_t18x->soc->unmask_hw_irq)
		writel(TOP_TKE_TKEIE_WDT_MASK(twdt_t18x->wdt_index),
		       twdt_t18x->wdt_tke + TOP_TKE_TKEIE(twdt_t18x->hwirq));

	/* Update skip configuration and active expiry count */
	twdt_t18x->active_count = EXPIRY_COUNT - tegra_wdt_t18x_skip(twdt_t18x);
	if (twdt_t18x->active_count < 1)
		twdt_t18x->active_count = 1;

	writel(TOP_TKE_TMR_PCR_INTR, twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR);
	/*
	 * The timeout needs to be divided by active expiry count here so as
	 * to keep the ultimate watchdog reset timeout the same as the program
	 * timeout requested by application. The program timeout should make
	 * sure WDT FIQ will never be asserted in a valid use case.
	 */
	val = (twdt_t18x->wdt.timeout * USEC_PER_SEC) / twdt_t18x->active_count;
	val |= (TOP_TKE_TMR_EN | TOP_TKE_TMR_PERIODIC);
	writel(val, twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV);

	if (!twdt_t18x->config_locked)
		writel(twdt_t18x->config, twdt_t18x->wdt_source + WDT_CFG);
	writel(WDT_CMD_START_COUNTER, twdt_t18x->wdt_source + WDT_CMD);
	set_bit(WDT_ENABLED, &twdt_t18x->status);

	return 0;
}

static int __tegra_wdt_t18x_disable(struct tegra_wdt_t18x *twdt_t18x)
{
	writel(WDT_UNLOCK_PATTERN, twdt_t18x->wdt_source + WDT_UNLOCK);
	writel(WDT_CMD_DISABLE_COUNTER, twdt_t18x->wdt_source + WDT_CMD);

	writel(0, twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV);

	clear_bit(WDT_ENABLED, &twdt_t18x->status);

	return 0;
}

static int tegra_wdt_t18x_enable(struct watchdog_device *wdt)
{
	struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);

	return __tegra_wdt_t18x_enable(twdt_t18x);
}

static int tegra_wdt_t18x_disable(struct watchdog_device *wdt)
{
	struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);

	return __tegra_wdt_t18x_disable(twdt_t18x);
}

static int tegra_wdt_t18x_ping(struct watchdog_device *wdt)
{
	struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);

	tegra_wdt_t18x_ref(wdt);

	return __tegra_wdt_t18x_ping(twdt_t18x);
}

static int tegra_wdt_t18x_set_timeout(struct watchdog_device *wdt,
				      unsigned int timeout)
{
	struct tegra_wdt_t18x *twdt_t18x = to_tegra_wdt_t18x(wdt);

	tegra_wdt_t18x_disable(wdt);
	wdt->timeout = timeout;
	tegra_wdt_t18x_enable(wdt);

	dev_info(twdt_t18x->dev, "Watchdog(%d): wdt timeout set to %u sec\n",
		 wdt->id, timeout);
	return 0;
}

static const struct watchdog_info tegra_wdt_t18x_info = {
	.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
	.identity = "Tegra WDT",
	.firmware_version = 1,
};

static const struct watchdog_ops tegra_wdt_t18x_ops = {
	.owner = THIS_MODULE,
	.start = tegra_wdt_t18x_enable,
	.stop  = tegra_wdt_t18x_disable,
	.ping  = tegra_wdt_t18x_ping,
	.set_timeout = tegra_wdt_t18x_set_timeout,
};

#ifdef CONFIG_DEBUG_FS
#define FILE_PERMISSIONS		644
#define COUNTER_ENABLED			0x1

static int dump_registers_show(struct seq_file *s, void *unused)
{
	struct tegra_wdt_t18x *twdt_t18x = s->private;

	seq_printf(s, "Timer config register \t\t0x%08x\n",
		   readl(twdt_t18x->wdt_timer + TOP_TKE_TMR_PTV));
	seq_printf(s, "Timer status register \t\t0x%08x\n",
		   readl(twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR));
	seq_printf(s, "Watchdog config register \t0x%08x\n",
		   readl(twdt_t18x->wdt_source + WDT_CFG));
	seq_printf(s, "Watchdog status register \t0x%08x\n",
		   readl(twdt_t18x->wdt_source + WDT_STATUS));
	seq_printf(s, "Watchdog command register \t0x%08x\n",
		   readl(twdt_t18x->wdt_source + WDT_CMD));
	seq_printf(s, "Watchdog skip register \t\t0x%08x\n",
		   readl(twdt_t18x->wdt_source + WDT_SKIP));

	return 0;
}

static int disable_dbg_reset_show(struct seq_file *s, void *unused)
{
	struct tegra_wdt_t18x *twdt_t18x = s->private;

	seq_printf(s, "%d\n",
		   (twdt_t18x->config & WDT_CFG_DBG_RST_EN) ? 0 : 1);

	return 0;
}

static int disable_por_reset_show(struct seq_file *s, void *unused)
{
	struct tegra_wdt_t18x *twdt_t18x = s->private;

	seq_printf(s, "%d\n",
		   (twdt_t18x->config & WDT_CFG_SYS_PORST_EN) ? 0 : 1);

	return 0;
}

static int disable_wdt_show(void *data, u64 *val)
{
	struct tegra_wdt_t18x *twdt_t18x = data;
	*val = (twdt_t18x->status & COUNTER_ENABLED) ? 0 : 1;
	return 0;
}

static int disable_wdt_store(void *data, u64 val)
{
	struct tegra_wdt_t18x *twdt_t18x = data;

	if (val) {
		if (COUNTER_ENABLED & twdt_t18x->status)
			__tegra_wdt_t18x_disable(twdt_t18x);
	} else {
		if (!(COUNTER_ENABLED & twdt_t18x->status))
			__tegra_wdt_t18x_enable(twdt_t18x);
	}

	return 0;
}

#define SIMPLE_FOPS(_name, _show)					\
static int dbg_open_##_name(struct inode *inode, struct file *file)	\
{									\
	return single_open(file, _show, inode->i_private);		\
}									\
static const struct file_operations _name##_fops = {			\
	.open = dbg_open_##_name,					\
	.read = seq_read,						\
	.llseek = seq_lseek,						\
	.release = single_release,					\
}

SIMPLE_FOPS(dump_regs, dump_registers_show);
SIMPLE_FOPS(disable_dbg_reset, disable_dbg_reset_show);
SIMPLE_FOPS(disable_por_reset, disable_por_reset_show);
DEFINE_SIMPLE_ATTRIBUTE(disable_wdt_fops, disable_wdt_show,
	disable_wdt_store, "%lld\n");

static void tegra_wdt_t18x_debugfs_init(struct tegra_wdt_t18x *twdt_t18x)
{
	struct dentry *root;
	struct dentry *retval;
	struct platform_device *pdev = to_platform_device(twdt_t18x->dev);

	root = debugfs_create_dir(pdev->name, NULL);
	if (IS_ERR_OR_NULL(root))
		goto clean;

	retval = debugfs_create_file("dump_regs", FILE_PERMISSIONS,
				     root, twdt_t18x, &dump_regs_fops);
	if (IS_ERR_OR_NULL(retval))
		goto clean;

	retval = debugfs_create_file("disable_dbg_reset", FILE_PERMISSIONS,
				     root, twdt_t18x,
				     &disable_dbg_reset_fops);
	if (IS_ERR_OR_NULL(retval))
		goto clean;

	retval = debugfs_create_file("disable_por_reset", FILE_PERMISSIONS,
				     root, twdt_t18x,
				     &disable_por_reset_fops);
	if (IS_ERR_OR_NULL(retval))
		goto clean;

	retval = debugfs_create_file("disable_wdt", FILE_PERMISSIONS,
					root, twdt_t18x,
					&disable_wdt_fops);
	if (IS_ERR_OR_NULL(retval))
		goto clean;

	twdt_t18x->root = root;

	return;
clean:
	dev_warn(twdt_t18x->dev, "Failed to create debugfs!\n");
	debugfs_remove_recursive(root);
}

#else /* !CONFIG_DEBUG_FS */
static void tegra_wdt_t18x_debugfs_init(struct tegra_wdt_t18x *twdt_t18x)
{
}
#endif /* CONFIG_DEBUG_FS */

static int tegra_wdt_t18x_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct resource *res_wdt, *res_tmr, *res_tke;
	struct tegra_wdt_t18x *twdt_t18x;
	struct device_node *np = pdev->dev.of_node;
	struct of_phandle_args oirq;
	int timer_id, wdt_id;
	int skip_count = 0;
	u32 pval = 0;
	u32 error_threshold;
	int ret;

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

	ret = of_property_read_u32(np, "nvidia,shutdown-timeout", &pval);
	if (ret)
		dev_info(&pdev->dev, "shutdown timeout disabled\n");
	else
		twdt_t18x->shutdown_timeout = pval;

	twdt_t18x->soc = of_device_get_match_data(&pdev->dev);
	if (!twdt_t18x->soc) {
		dev_err(&pdev->dev, "Unable to find soc data\n");
		return -EINVAL;
	}

	twdt_t18x->dev = &pdev->dev;
	twdt_t18x->wdt.info = &tegra_wdt_t18x_info;
	twdt_t18x->wdt.ops = &tegra_wdt_t18x_ops;

	ret = of_property_read_u32(np, "nvidia,expiry-count", &pval);
	if (!ret)
		dev_info(twdt_t18x->dev, "Expiry count is deprecated\n");

	watchdog_set_nowayout(&twdt_t18x->wdt, nowayout);

	twdt_t18x->irq = platform_get_irq(pdev, 0);
	if (twdt_t18x->irq < 0) {
		dev_err(&pdev->dev, "Interrupt is not available\n");
		return -EINVAL;
	}

	/*
	 * Find the IRQ number from the perspective of the interrupt
	 * controller. This is different than Linux's IRQ number
	 */
	ret = of_irq_parse_one(np, 0, &oirq);
	if (ret < 0) {
		dev_err(&pdev->dev, "Failed to parse IRQ: %d\n", ret);
		return -EINVAL;
	}

	/* The second entry is the IRQ */
	twdt_t18x->hwirq = oirq.args[1];

	twdt_t18x->enable_on_init = of_property_read_bool(np,
					"nvidia,enable-on-init");

	twdt_t18x->extended_suspend = of_property_read_bool(np,
				"nvidia,extend-watchdog-suspend");

	t18x_wdt = twdt_t18x;
	platform_set_drvdata(pdev, twdt_t18x);

	res_wdt = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	res_tmr = platform_get_resource(pdev, IORESOURCE_MEM, 1);
	res_tke = platform_get_resource(pdev, IORESOURCE_MEM, 2);
	if (!res_wdt || !res_tmr || !res_tke) {
		dev_err(&pdev->dev, "Insufficient resources\n");
		return -ENOENT;
	}

	/* WDT ID from base address */
	switch (twdt_t18x->soc->timer_index) {
	case TIMER_INDEX_T234:
		wdt_id = T234_WATCHDOG_INDEX(res_wdt->start);
		break;
	default:
		wdt_id = WATCHDOG_INDEX(res_wdt->start);
		break;
	}

	ret = of_property_read_u32(np, "nvidia,watchdog-index", &pval);
	if (!ret) {
		twdt_t18x->wdt_index = pval;

		/* If WDT index provided then address must be for base */
		if (wdt_id && (wdt_id != twdt_t18x->wdt_index)) {
			dev_err(dev, "WDT base address must be for WDT0\n");
			return -EINVAL;
		}

		/* Adjust address for WDT */
		ret = adjust_resource(res_wdt, res_wdt->start + pval * 0x1000,
				      res_wdt->end - res_wdt->start);
		if (ret < 0) {
			dev_err(dev, "Cannot adjust address of WDT: %d\n", ret);
			return ret;
		}
	} else {
		/* Watchdog index in list of wdts under top_tke */
		twdt_t18x->wdt_index = wdt_id;
	}

	twdt_t18x->wdt_source = devm_ioremap_resource(&pdev->dev, res_wdt);
	if (IS_ERR(twdt_t18x->wdt_source)) {
		dev_err(&pdev->dev, "Cannot request memregion/iomap res_wdt\n");
		return PTR_ERR(twdt_t18x->wdt_source);
	}

	twdt_t18x->config = readl(twdt_t18x->wdt_source + WDT_CFG);
	twdt_t18x->config_locked = !!(twdt_t18x->config & WDT_CFG_INT_EN);

	/*
	 * Get Timer index,
	 *	First from BL, if locked else
	 *	from DT property else
	 *	From base address.
	 */
	if (twdt_t18x->config_locked) {
		twdt_t18x->tmr_index = twdt_t18x->config & 0xF;
	} else {
		ret = of_property_read_u32(np, "nvidia,timer-index", &pval);
		if (!ret)
			twdt_t18x->tmr_index = pval;
		else
			twdt_t18x->tmr_index = TIMER_INDEX(res_tmr->start);
	}

	/*
	 * If timer ID from address different then timer index
	 * then adjust address.
	 */
	switch (twdt_t18x->soc->timer_index) {
	case TIMER_INDEX_T234:
		timer_id = T234_TIMER_INDEX(res_tmr->start);
		break;
	default:
		timer_id = TIMER_INDEX(res_tmr->start);
		break;
	}

	if (timer_id != twdt_t18x->tmr_index) {
		if (timer_id) {
			dev_err(dev, "Timer base address must be Timer0\n");
			return -EINVAL;
		}

		ret = adjust_resource(res_tmr, res_tmr->start +
				      twdt_t18x->tmr_index * 0x10000,
				      res_tmr->end - res_tmr->start);
		if (ret < 0) {
			dev_err(dev, "Cannot adjust address of TMR:%d\n", ret);
			return ret;
		}
	}

	twdt_t18x->wdt_timer = devm_ioremap_resource(&pdev->dev, res_tmr);
	if (IS_ERR(twdt_t18x->wdt_timer)) {
		dev_err(&pdev->dev, "Cannot request memregion/iomap res_tmr\n");
		return PTR_ERR(twdt_t18x->wdt_timer);
	}

	twdt_t18x->wdt_tke = devm_ioremap_resource(&pdev->dev, res_tke);
	if (IS_ERR(twdt_t18x->wdt_tke)) {
		dev_err(&pdev->dev, "Cannot request memregion/iomap res_tke\n");
		return PTR_ERR(twdt_t18x->wdt_tke);
	}

	if (!twdt_t18x->config_locked) {
		/* Configure timer source and period */
		twdt_t18x->config = twdt_t18x->tmr_index;
		twdt_t18x->config |= WDT_CFG_PERIOD;

		/* Enable local interrupt for WDT petting */
		twdt_t18x->config |= WDT_CFG_INT_EN;

		/*
		 * 'ErrorThreshold' field @ TKE_TOP_WDT1_WDTCR_0 decides the
		 * indication to HSM. The WDT logic asserts an error signal to
		 * HSM when ExpirationLevel >= ErrorThreshold. Update the error
		 * threshold value, if provided in the device tree.
		 *
		 * Else retain the POR value to avoid nuisance trigger to HSM.
		 */
		ret = of_property_read_u32(np, "nvidia,wdt-error-threshold",
				&error_threshold);
		if (!ret) {
			if (error_threshold > 7) {
				dev_warn(&pdev->dev, "Invalid error threshold value\n");
				twdt_t18x->config |= WDT_CFG_ERR_THRESHOLD;
			} else {
				twdt_t18x->config |= (error_threshold << 20);
			}
		} else {
			twdt_t18x->config |= WDT_CFG_ERR_THRESHOLD;
		}

		/* Enable local FIQ and remote interrupt for debug dump */
		twdt_t18x->config |= WDT_CFG_FINT_EN;

		if (of_property_read_bool(np, "nvidia,disallow-wdt-freeze"))
			twdt_t18x->config |= WDT_CFG_DISALLOW_FREEZE;

		if (!of_property_read_bool(np,
					   "nvidia,disable-remote-interrupt"))
			twdt_t18x->config |= WDT_CFG_REMOTE_INT_EN;
		else
			skip_count++;

		/*
		 * Debug and POR reset events should be enabled by default.
		 * Disable only if explicitly indicated in device tree or
		 * HALT_IN_FIQ is set, so as to allow external debugger to poke.
		 */
		if (!PMC_HALT_IN_FIQ()) {
			if (!of_property_read_bool(
				np, "nvidia,disable-debug-reset"))
				twdt_t18x->config |= WDT_CFG_DBG_RST_EN;
			else
				skip_count++;

			if (!of_property_read_bool(
				np, "nvidia,disable-por-reset"))
				twdt_t18x->config |= WDT_CFG_SYS_PORST_EN;
		}
	}

	twdt_t18x->wdt.min_timeout = MIN_TMR_PTV;
	twdt_t18x->wdt.max_timeout = MAX_TMR_PTV * (EXPIRY_COUNT - skip_count);
	ret = watchdog_init_timeout(&twdt_t18x->wdt, 0, &pdev->dev);
	if (ret < 0)
		twdt_t18x->wdt.timeout = HEARTBEAT;

	tegra_wdt_t18x_disable(&twdt_t18x->wdt);
	writel(TOP_TKE_TMR_PCR_INTR, twdt_t18x->wdt_timer + TOP_TKE_TMR_PCR);

	if (twdt_t18x->enable_on_init) {
		/* Register interrupt handler only when WDT is configured to enable on boot */
		ret = devm_request_threaded_irq(&pdev->dev, twdt_t18x->irq,
						NULL, tegra_wdt_t18x_isr,
						IRQF_ONESHOT | IRQF_TRIGGER_HIGH,
						dev_name(&pdev->dev), twdt_t18x);
		if (ret < 0) {
			dev_err(&pdev->dev, "Failed to register irq %d err %d\n",
				twdt_t18x->irq, ret);
			return ret;
		}

		tegra_wdt_t18x_enable(&twdt_t18x->wdt);
		set_bit(WDOG_ACTIVE, &twdt_t18x->wdt.status);
		set_bit(WDT_ENABLED_ON_INIT, &twdt_t18x->status);
		dev_info(twdt_t18x->dev, "Tegra WDT init timeout = %u sec\n",
			 twdt_t18x->wdt.timeout);
	}

	tegra_wdt_t18x_debugfs_init(twdt_t18x);

	if (twdt_t18x->extended_suspend)
		register_syscore_ops(&tegra_wdt_t18x_syscore_ops);

	ret = devm_watchdog_register_device(&pdev->dev, &twdt_t18x->wdt);
	if (ret) {
		dev_err(&pdev->dev, "Failed to register watchdog device\n");
		goto cleanup;
	}

	dev_info(&pdev->dev, "Registered successfully\n");

	return 0;

cleanup:
	__tegra_wdt_t18x_disable(twdt_t18x);

	if (twdt_t18x->extended_suspend)
		unregister_syscore_ops(&tegra_wdt_t18x_syscore_ops);

	debugfs_remove_recursive(twdt_t18x->root);

	return ret;
}

static void tegra_wdt_t18x_shutdown(struct platform_device *pdev)
{
	struct tegra_wdt_t18x *twdt_t18x = platform_get_drvdata(pdev);

	if (twdt_t18x->shutdown_timeout) {
		dev_dbg(twdt_t18x->dev,
			"Watchdog%d: setting shutdown timeout as %d seconds",
			twdt_t18x->wdt.id, twdt_t18x->shutdown_timeout);
		twdt_t18x->wdt.timeout = twdt_t18x->shutdown_timeout;
		__tegra_wdt_t18x_ping(twdt_t18x);
		return;
	}

	__tegra_wdt_t18x_disable(twdt_t18x);
}

static int tegra_wdt_t18x_remove(struct platform_device *pdev)
{
	struct tegra_wdt_t18x *twdt_t18x = platform_get_drvdata(pdev);

	t18x_wdt = NULL;
	__tegra_wdt_t18x_disable(twdt_t18x);

	if (twdt_t18x->extended_suspend)
		unregister_syscore_ops(&tegra_wdt_t18x_syscore_ops);

	debugfs_remove_recursive(twdt_t18x->root);

	return 0;
}

#ifdef CONFIG_PM_SLEEP
static int tegra_wdt_t18x_suspend(struct device *dev)
{
	struct tegra_wdt_t18x *twdt_t18x = dev_get_drvdata(dev);

	if (twdt_t18x->extended_suspend)
		__tegra_wdt_t18x_ping(twdt_t18x);
	else
		__tegra_wdt_t18x_disable(twdt_t18x);

	return 0;
}

static int tegra_wdt_t18x_resume(struct device *dev)
{
	struct tegra_wdt_t18x *twdt_t18x = dev_get_drvdata(dev);

	if (watchdog_active(&twdt_t18x->wdt)) {
		if (twdt_t18x->extended_suspend)
			__tegra_wdt_t18x_ping(twdt_t18x);
		else
			__tegra_wdt_t18x_enable(twdt_t18x);
	} else {
		if (twdt_t18x->extended_suspend)
			__tegra_wdt_t18x_disable(twdt_t18x);
	}

	return 0;
}

static int tegra_wdt_t18x_syscore_suspend(void)
{
	if (t18x_wdt && t18x_wdt->extended_suspend)
		__tegra_wdt_t18x_disable(t18x_wdt);

	return 0;
}

static void tegra_wdt_t18x_syscore_resume(void)
{
	if (t18x_wdt && t18x_wdt->extended_suspend)
		__tegra_wdt_t18x_enable(t18x_wdt);
}
#else
static int tegra_wdt_t18x_syscore_suspend(void)
{
	return 0;
}

static void tegra_wdt_t18x_syscore_resume(void) { }
#endif

static struct syscore_ops tegra_wdt_t18x_syscore_ops = {
	.suspend =	tegra_wdt_t18x_syscore_suspend,
	.resume =	tegra_wdt_t18x_syscore_resume,
};

static const struct dev_pm_ops tegra_wdt_t18x_pm_ops = {
	SET_SYSTEM_SLEEP_PM_OPS(tegra_wdt_t18x_suspend, tegra_wdt_t18x_resume)
};

static const struct tegra_wdt_t18x_soc t234_wdt_silicon = {
	.unmask_hw_irq	= true,
	.timer_index	= TIMER_INDEX_T234,
};

static const struct tegra_wdt_t18x_soc t18x_wdt_silicon = {
	.unmask_hw_irq	= true,
	.timer_index	= TIMER_INDEX_T186,
};

static const struct tegra_wdt_t18x_soc t18x_wdt_sim = {
	.unmask_hw_irq	= false,
	.timer_index	= TIMER_INDEX_T186,
};

static const struct of_device_id tegra_wdt_t18x_match[] = {
	{ .compatible = "nvidia,tegra-wdt-t234", .data = &t234_wdt_silicon},
	{ .compatible = "nvidia,tegra-wdt-t19x", .data = &t18x_wdt_silicon},
	{ .compatible = "nvidia,tegra-wdt-t18x", .data = &t18x_wdt_silicon},
	{ .compatible = "nvidia,tegra-wdt-t18x-linsim", .data = &t18x_wdt_sim},
	{}
};
MODULE_DEVICE_TABLE(of, tegra_wdt_t18x_match);

static struct platform_driver tegra_wdt_t18x_driver = {
	.probe		= tegra_wdt_t18x_probe,
	.remove		= tegra_wdt_t18x_remove,
	.shutdown	= tegra_wdt_t18x_shutdown,
	.driver		= {
		.owner	= THIS_MODULE,
		.name	= "tegra_wdt_t18x",
		.pm	= &tegra_wdt_t18x_pm_ops,
		.of_match_table = of_match_ptr(tegra_wdt_t18x_match),
	},
};

module_platform_driver(tegra_wdt_t18x_driver);

MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>");
MODULE_DESCRIPTION("Tegra Watchdog Driver");
MODULE_LICENSE("GPL v2");
