[PATCH linux dev-4.13 v1 2/2] watchdog: npcm: add NPCM7xx watchdog driver

Tomer Maimon tmaimon77 at gmail.com
Tue Dec 19 00:22:24 AEDT 2017


Add Nuvoton BMC NPCM7xx watchdog driver.

Nuvoton NPCM7xx have three watchdog timer modules, each watchdog timer
is a free-running timer with programmable timeout intervals.

Signed-off-by: Tomer Maimon <tmaimon77 at gmail.com>
---
 drivers/watchdog/Kconfig                   |  11 +
 drivers/watchdog/Makefile                  |   1 +
 drivers/watchdog/npcm7xx_wdt.c             | 369 +++++++++++++++++++++++++++++
 drivers/watchdog/npcm7xx_wdt_fiq_handler.S |  68 ++++++
 4 files changed, 449 insertions(+)
 create mode 100644 drivers/watchdog/npcm7xx_wdt.c
 create mode 100644 drivers/watchdog/npcm7xx_wdt_fiq_handler.S

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index c722cbfdc7e6..5995e099ccf3 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -504,6 +504,17 @@ config COH901327_WATCHDOG
 	  This watchdog is used to reset the system and thus cannot be
 	  compiled as a module.
 
+config NPCM7XX_WATCHDOG
+	bool "NPCM750 watchdog"
+	depends on ARCH_NPCM7XX || COMPILE_TEST
+	default y if ARCH_NPCM7XX
+	select WATCHDOG_CORE
+	help
+	  Say Y here to include Watchdog timer support for the
+	  watchdog embedded into the NPCM7xx.
+	  This watchdog is used to reset the system and thus cannot be
+	  compiled as a module.
+
 config TWL4030_WATCHDOG
 	tristate "TWL4030 Watchdog"
 	depends on TWL4030_CORE
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 56adf9fa67d0..4ac0c790093a 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -60,6 +60,7 @@ obj-$(CONFIG_ORION_WATCHDOG) += orion_wdt.o
 obj-$(CONFIG_SUNXI_WATCHDOG) += sunxi_wdt.o
 obj-$(CONFIG_RN5T618_WATCHDOG) += rn5t618_wdt.o
 obj-$(CONFIG_COH901327_WATCHDOG) += coh901327_wdt.o
+obj-$(CONFIG_NPCM7XX_WATCHDOG) += npcm7xx_wdt.o npcm7xx_wdt_fiq_handler.o
 obj-$(CONFIG_STMP3XXX_RTC_WATCHDOG) += stmp3xxx_rtc_wdt.o
 obj-$(CONFIG_NUC900_WATCHDOG) += nuc900_wdt.o
 obj-$(CONFIG_TS4800_WATCHDOG) += ts4800_wdt.o
diff --git a/drivers/watchdog/npcm7xx_wdt.c b/drivers/watchdog/npcm7xx_wdt.c
new file mode 100644
index 000000000000..2c5aba04adbf
--- /dev/null
+++ b/drivers/watchdog/npcm7xx_wdt.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2014-2017 Nuvoton Technology corporation.
+ *
+ * Released under the GPLv2 only.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/watchdog.h>
+#include <asm/fiq.h>
+#include <linux/of_irq.h>
+
+/* Wdog0-2 are connected with OR gate directly to nFIQ. */
+#define WATCHDOG_FIQ
+
+/* #define DEBUG */
+#ifdef DEBUG
+	#undef WDOG_DEBUG
+	#define WDOG_DEBUG(f, x...)	pr_info("NPCM7XX-WDOG: %s():"
+					f, __func__, ## x)
+#else
+	#define WDOG_DEBUG(f, x...)
+#endif
+
+#define REG_WTCR	0x1C	/* WTCR Register Offset */
+
+#define WTCLK		(0x03 << 10)
+#define WTE		(0x01 << 7)	/* WTCR enable*/
+#define WTIE		(0x01 << 6)	/* WTCR enable interrupt*/
+#define WTIS		(0x03 << 4)	/* WTCR interval selection */
+#define WTIF		(0x01 << 3)	/* WTCR interrupt flag*/
+#define WTRF		(0x01 << 2)	/* WTCR reset flag */
+#define WTRE		(0x01 << 1)	/* WTCR reset enable */
+#define WTR		(0x01 << 0)	/* WTCR reset counter */
+
+/*
+ *Watchdog timeouts
+ *
+ *170     msec:    WTCLK=01 WTIS=00     VAL= 0x400
+ *670     msec:    WTCLK=01 WTIS=01     VAL= 0x410
+ *1360    msec:    WTCLK=10 WTIS=00     VAL= 0x800
+ *2700    msec:    WTCLK=01 WTIS=10     VAL= 0x420
+ *5360    msec:    WTCLK=10 WTIS=01     VAL= 0x810
+ *10700   msec:    WTCLK=01 WTIS=11     VAL= 0x430
+ *21600   msec:    WTCLK=10 WTIS=10     VAL= 0x820
+ *43000   msec:    WTCLK=11 WTIS=00     VAL= 0xC00
+ *85600   msec:    WTCLK=10 WTIS=11     VAL= 0x830
+ *172000  msec:    WTCLK=11 WTIS=01     VAL= 0xC10
+ *687000  msec:    WTCLK=11 WTIS=10     VAL= 0xC20
+ *2750000 msec:    WTCLK=11 WTIS=11     VAL= 0xC30
+ */
+
+/* Default is 86 seconds */
+#define NPCM7XX_WDOG_TIMEOUT	86
+
+static bool nowayout = WATCHDOG_NOWAYOUT;
+module_param(nowayout, bool, 0000);
+MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default="
+		 __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
+
+struct npcm7xx_wdt {
+	struct resource		*res;
+	struct platform_device	*pdev;
+	void __iomem		*wdt_base;
+	int irq;
+};
+
+static struct npcm7xx_wdt *npcm7xx_wdt;
+
+static int npcm7xx_wdt_ping(struct watchdog_device *wdd)
+{
+	unsigned int val;
+
+	WDOG_DEBUG("Ping\n");
+	val = __raw_readl(npcm7xx_wdt->wdt_base + REG_WTCR);
+	val |= WTR;
+	WDOG_DEBUG("Ping REG_WTCR = 0x%x\n", val);
+	__raw_writel(val, npcm7xx_wdt->wdt_base + REG_WTCR);
+	return 0;
+}
+
+static int npcm7xx_wdt_start(struct watchdog_device *wdd)
+{
+	unsigned int val = 0;
+
+	WDOG_DEBUG("Start timeout = %d\n", wdd->timeout);
+	val |= (WTRE | WTE | WTR | WTIE);
+
+	if (wdd->timeout < 2)
+		val |= 0x800;
+	else if (wdd->timeout < 3)
+		val |= 0x420;
+	else if (wdd->timeout < 6)
+		val |= 0x810;
+	else if (wdd->timeout < 11)
+		val |= 0x430;
+	else if (wdd->timeout < 22)
+		val |= 0x820;
+	else if (wdd->timeout < 44)
+		val |= 0xC00;
+	else if (wdd->timeout < 87)
+		val |= 0x830;
+	else if (wdd->timeout < 173)
+		val |= 0xC10;
+	else if (wdd->timeout < 688)
+		val |= 0xC20;
+	else if (wdd->timeout < 2751)
+		val |= 0xC30;
+	else
+		val |= 0x830;
+
+	WDOG_DEBUG("Start REG_WTCR = 0x%x\n", val);
+	__raw_writel(val, npcm7xx_wdt->wdt_base + REG_WTCR);
+	return 0;
+}
+
+static int npcm7xx_wdt_stop(struct watchdog_device *wdd)
+{
+	WDOG_DEBUG("Stop\n");
+	__raw_writel(0, npcm7xx_wdt->wdt_base + REG_WTCR);
+	return 0;
+}
+
+
+static int npcm7xx_wdt_set_timeout(struct watchdog_device *wdd,
+				  unsigned int timeout)
+{
+	unsigned int val;
+
+	WDOG_DEBUG("Timeout = %d\n", timeout);
+
+	wdd->timeout = timeout;     /* New timeout */
+
+	val = __raw_readl(npcm7xx_wdt->wdt_base + REG_WTCR);
+	val &= ~(WTCLK | WTIS);
+
+	if (wdd->timeout < 2)
+		val |= 0x800;
+	else if (wdd->timeout < 3)
+		val |= 0x420;
+	else if (wdd->timeout < 6)
+		val |= 0x810;
+	else if (wdd->timeout < 11)
+		val |= 0x430;
+	else if (wdd->timeout < 22)
+		val |= 0x820;
+	else if (wdd->timeout < 44)
+		val |= 0xC00;
+	else if (wdd->timeout < 87)
+		val |= 0x830;
+	else if (wdd->timeout < 173)
+		val |= 0xC10;
+	else if (wdd->timeout < 688)
+		val |= 0xC20;
+	else if (wdd->timeout < 2751)
+		val |= 0xC30;
+	else
+		val |= 0x830;
+
+	WDOG_DEBUG("Set Timeout REG_WTCR = 0x%x\n", val);
+	__raw_writel(val, npcm7xx_wdt->wdt_base + REG_WTCR);
+
+	return 0;
+}
+
+static int npcm7xx_wdt_restart(struct watchdog_device *wdd,
+			      unsigned long action, void *data)
+{
+
+	__raw_writel(0x83, npcm7xx_wdt->wdt_base + REG_WTCR);
+	udelay(1000);
+
+	return 0;
+}
+
+#ifndef WATCHDOG_FIQ
+/*
+ * This interrupt occurs 10 ms before the watchdog WILL bark.
+ */
+static irqreturn_t npcm7xx_wdt_interrupt(int irq, void *data)
+{
+	unsigned int val;
+
+	val = __raw_readl(npcm7xx_wdt->wdt_base + REG_WTCR);
+	WDOG_DEBUG("%s() val = 0x%x\n", __func__, val);
+
+	if (val & WTIF) {
+		__raw_writel((val & ~WTIE) | WTIF,
+			     npcm7xx_wdt->wdt_base + REG_WTCR);
+		pr_info("NPCM7XX - Watchdog is barking!!!\n");
+
+		/*
+		 * Just disable and clear interrupt and await the imminent end.
+		 * If you at some point need a host of callbacks to be called
+		 * when the system is about to watchdog-reset, add them here!
+		 *
+		 * NOTE: on future versions of this IP-block, it will be
+		 * possible to prevent a watchdog reset by feeding the
+		 * watchdog at this point.
+		 */
+	}
+	return IRQ_HANDLED;
+}
+#endif
+
+static const struct watchdog_info npcm7xx_wdt_info = {
+	.identity	= "npcm7xx watchdog",
+	.options	= WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING
+				| WDIOF_MAGICCLOSE,
+};
+
+static struct watchdog_ops npcm7xx_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = npcm7xx_wdt_start,
+	.stop = npcm7xx_wdt_stop,
+	.ping = npcm7xx_wdt_ping,
+	.restart = npcm7xx_wdt_restart,
+	.set_timeout = npcm7xx_wdt_set_timeout,
+};
+
+static struct watchdog_device npcm7xx_wdd = {
+	.status = WATCHDOG_NOWAYOUT_INIT_STATUS,
+	.info = &npcm7xx_wdt_info,
+	.ops = &npcm7xx_wdt_ops,
+	.min_timeout = 1,
+	.max_timeout = 2751,
+};
+
+#ifdef WATCHDOG_FIQ
+extern unsigned char npcm7xx_wdt_fiq_start, npcm7xx_wdt_fiq_end;
+#endif
+
+static int npcm7xx_wdt_probe(struct platform_device *pdev)
+{
+	int ret = 0;
+#ifdef WATCHDOG_FIQ
+	struct pt_regs regs;
+#endif
+
+	WDOG_DEBUG("Probing...\n");
+
+	npcm7xx_wdt = devm_kzalloc(&pdev->dev, sizeof(struct npcm7xx_wdt),
+				   GFP_KERNEL);
+	if (!npcm7xx_wdt)
+		return -ENOMEM;
+
+	npcm7xx_wdt->pdev = pdev;
+
+	npcm7xx_wdt->res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (npcm7xx_wdt->res == NULL) {
+		dev_err(&pdev->dev, "no memory resource specified\n");
+		ret = -ENOENT;
+		goto err_alloc;
+	}
+
+	if (!devm_request_mem_region(&pdev->dev, npcm7xx_wdt->res->start,
+				resource_size(npcm7xx_wdt->res), pdev->name)) {
+		dev_err(&pdev->dev, "failed to get memory region\n");
+		return -ENOENT;
+	}
+
+	npcm7xx_wdt->wdt_base = ioremap(npcm7xx_wdt->res->start,
+					resource_size(npcm7xx_wdt->res));
+	if (npcm7xx_wdt->wdt_base == NULL) {
+		dev_err(&pdev->dev, "failed to ioremap() region\n");
+		ret = -EINVAL;
+		goto err_mem;
+	}
+
+	WDOG_DEBUG("PA_ADDR = 0x%x\n", npcm7xx_wdt->res->start);
+	WDOG_DEBUG("VA_ADDR = 0x%x\n", npcm7xx_wdt->wdt_base);
+
+	npcm7xx_wdd.timeout = NPCM7XX_WDOG_TIMEOUT;
+
+#ifdef WATCHDOG_FIQ
+
+	set_fiq_handler(&npcm7xx_wdt_fiq_start,
+			&npcm7xx_wdt_fiq_end - &npcm7xx_wdt_fiq_start);
+
+//    regs.ARM_r10 = (long)DUMP_SRC;
+//    regs.ARM_fp = (long)DUMP_DST;          // r11
+	regs.ARM_ip = (long)npcm7xx_wdt->wdt_base;     // r12
+	set_fiq_regs(&regs);
+#else
+
+	npcm7xx_wdt->irq = platform_get_irq(pdev, 0);
+	WDOG_DEBUG("npcm7xx_wdt->irq = %d\n", npcm7xx_wdt->irq);
+
+	if (request_irq(npcm7xx_wdt->irq, npcm7xx_wdt_interrupt,
+			0, pdev->name, pdev)) {
+		ret = -EIO;
+		goto err_map;
+	}
+#endif
+
+	watchdog_set_nowayout(&npcm7xx_wdd, nowayout);
+	ret = watchdog_register_device(&npcm7xx_wdd);
+	if (ret) {
+		dev_err(&pdev->dev, "Error register watchdog device\n");
+		goto err_irq;
+	}
+
+	WDOG_DEBUG("Probed\n");
+	return 0;
+err_irq:
+	free_irq(npcm7xx_wdt->irq, pdev);
+#ifndef WATCHDOG_FIQ
+err_map:
+#endif
+	iounmap(npcm7xx_wdt->wdt_base);
+err_mem:
+	release_mem_region(npcm7xx_wdt->res->start,
+			   resource_size(npcm7xx_wdt->res));
+err_alloc:
+	kfree(npcm7xx_wdt);
+	return ret;
+}
+
+static int npcm7xx_wdt_remove(struct platform_device *pdev)
+{
+	WDOG_DEBUG("%s()\n", __func__);
+
+	watchdog_unregister_device(&npcm7xx_wdd);
+	iounmap(npcm7xx_wdt->wdt_base);
+	release_mem_region(npcm7xx_wdt->res->start,
+			   resource_size(npcm7xx_wdt->res));
+	kfree(npcm7xx_wdt);
+
+	return 0;
+}
+
+static void npcm7xx_wdt_shutdown(struct platform_device *pdev)
+{
+	WDOG_DEBUG("%s()\n", __func__);
+	npcm7xx_wdt_stop(&npcm7xx_wdd);
+}
+
+#define npcm7xx_wdt_suspend	NULL
+#define npcm7xx_wdt_resume	NULL
+
+static const struct of_device_id wd_dt_id[] = {
+	{.compatible = "nuvoton,npcm750-wdt"},
+	{},
+};
+MODULE_DEVICE_TABLE(of, wd_dt_id);
+
+static struct platform_driver npcm7xx_wdt_driver = {
+	.probe		= npcm7xx_wdt_probe,
+	.remove		= npcm7xx_wdt_remove,
+	.shutdown	= npcm7xx_wdt_shutdown,
+	.suspend	= npcm7xx_wdt_suspend,
+	.resume		= npcm7xx_wdt_resume,
+	.driver		= {
+		.name	= "npcm7xx-wdt",
+		.owner	= THIS_MODULE,
+		.of_match_table = of_match_ptr(wd_dt_id),
+	},
+};
+
+module_platform_driver(npcm7xx_wdt_driver);
+
+MODULE_DESCRIPTION("Watchdog driver for NPCM7XX");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/watchdog/npcm7xx_wdt_fiq_handler.S b/drivers/watchdog/npcm7xx_wdt_fiq_handler.S
new file mode 100644
index 000000000000..92ea5e7bd267
--- /dev/null
+++ b/drivers/watchdog/npcm7xx_wdt_fiq_handler.S
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2014-2017 Nuvoton Technology corporation.
+ *
+ * Released under the GPLv2 only.
+ * SPDX-License-Identifier: GPL-2.0
+ */
+
+#include <linux/linkage.h>
+#include <asm/assembler.h>
+
+#define WDOG_REG_WTCR	0x1C		/* Watchdog register offset */
+#define WDOG_BIT_WTIF	(0x01 << 3)	/* WTCR interrupt flag*/
+#define WDOG_BIT_WTIE	(0x01 << 6)	/* WTCR enable interrupt*/
+
+/*
+ * Register usage
+
+ * r12 - virt. base addr
+ * r13 - reg_val
+ */
+
+	.text
+	.global npcm7xx_wdt_fiq_end
+
+ENTRY(npcm7xx_wdt_fiq_start)
+	@ FIQ intrrupt handler
+	MRC p15,0,r0,c0,c0,5@ read Multiprocessor ID register
+	BIC r0, #0xFFFFFFF0
+	CMP r0,#0x0
+	BEQ THIS_IS_CPU0
+	b .     @ loop - all other cpus stuck here
+
+	THIS_IS_CPU0:
+
+        ldr r13, [r12, #WDOG_REG_WTCR]
+        tst r13, #WDOG_BIT_WTIF
+	beq exit				@ none - spurious FIQ? exit
+
+	ldr r13, [r12, #WDOG_REG_WTCR]
+	bic r13, r13, #WDOG_BIT_WTIE            @ Disable watchdog interrupt
+	str r13, [r12, #WDOG_REG_WTCR]
+
+@@@@@@@@@@@@@@@@@@@@@@@@@@
+@
+@ FIQ - Kernel Dump
+@ Add Assembler Code here ...
+
+
+@
+@
+@@@@@@@@@@@@@@@@@@@@@@@@@@
+
+	b .	@ CPU0 - loop until HW watchdog reset
+
+
+exit:
+	subs	pc, lr, #4			@ return from FIQ
+
+
+npcm7xx_wdt_fiq_end:
+
+/*
+ * Check the size of the FIQ,
+ * it cannot go beyond 0xffff0200, and is copied to 0xffff001c
+ */
+.if (npcm7xx_wdt_fiq_end - npcm7xx_wdt_fiq_start) > (0x200 - 0x1c)
+	.err
+.endif
-- 
2.14.1



More information about the openbmc mailing list