Subject: [RFC/PATCH] powerpc: Add support for 8xx style watchdog

From: Jochen Friedrich <[email-masked]>
Date: Sun, 11 Nov 2007 21:10:13 +0100

Hi,

this is an attempt to port the 8xx watchdog driver to ARC=powerpc. As the watchdog seems to
be similar on pq1 / pq2 and pq2pro platforms (except from the divider value), one driver
should be enough to support the whole family. Am i correct with this assumption?

Thanks,
Jochen
---
 arch/powerpc/platforms/8xx/mpc885ads_setup.c |    5 +
 arch/powerpc/sysdev/Makefile                 |    3 +
 arch/powerpc/sysdev/pq_wdt.c                 |  195 ++++++++++++++++++++++
 arch/powerpc/sysdev/pq_wdt.h                 |   27 +++
 drivers/watchdog/Kconfig                     |   13 ++-
 drivers/watchdog/Makefile                    |    1 +
 drivers/watchdog/pq_wdt.c                    |  222 ++++++++++++++++++++++++++
 7 files changed, 465 insertions(+), 1 deletions(-)
 create mode 100644 arch/powerpc/sysdev/pq_wdt.c
 create mode 100644 arch/powerpc/sysdev/pq_wdt.h
 create mode 100644 drivers/watchdog/pq_wdt.c

diff --git a/arch/powerpc/platforms/8xx/mpc885ads_setup.c b/arch/powerpc/platforms/8xx/mpc885ads_setup.c
index 2cf1b6a..a686747 100644
--- a/arch/powerpc/platforms/8xx/mpc885ads_setup.c
+++ b/arch/powerpc/platforms/8xx/mpc885ads_setup.c
@@ -41,6 +41,7 @@
 #include <asm/udbg.h>
 
 #include <sysdev/commproc.h>
+#include <sysdev/pq_wdt.h>
 
 static u32 __iomem *bcsr, *bcsr5;
 
@@ -246,6 +247,10 @@ static void __init mpc885ads_setup_arch(void)
 	m8xx_pcmcia_ops.hw_ctrl = pcmcia_hw_setup;
 	m8xx_pcmcia_ops.voltage_set = pcmcia_set_voltage;
 #endif
+
+#if defined(CONFIG_PQ_WDT) || defined(CONFIG_PQ_WDT_MODULE)
+	pq_wdt_init();
+#endif
 }
 
 static int __init mpc885ads_probe(void)
diff --git a/arch/powerpc/sysdev/Makefile b/arch/powerpc/sysdev/Makefile
index 99a77d7..84f190e 100644
--- a/arch/powerpc/sysdev/Makefile
+++ b/arch/powerpc/sysdev/Makefile
@@ -35,5 +35,8 @@ obj-$(CONFIG_CPM)		+= cpm_common.o
 obj-$(CONFIG_CPM2)		+= cpm2_common.o cpm2_pic.o
 obj-$(CONFIG_PPC_DCR)		+= dcr.o
 obj-$(CONFIG_8xx)		+= mpc8xx_pic.o commproc.o
+ifneq ($(CONFIG_PQ_WDT),)
+obj-y				+= pq_wdt.o
+endif
 obj-$(CONFIG_UCODE_PATCH)	+= micropatch.o
 endif
diff --git a/arch/powerpc/sysdev/pq_wdt.c b/arch/powerpc/sysdev/pq_wdt.c
new file mode 100644
index 0000000..10a196f
--- /dev/null
+++ b/arch/powerpc/sysdev/pq_wdt.c
@@ -0,0 +1,195 @@
+/*
+ * pq_wdt.c - Freescale PowerQUICC watchdog driver
+ *
+ * Author: Florian Schirmer <[email-masked]>
+ *
+ * 2002 (c) Florian Schirmer <[email-masked]> This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ * 2007 (c) Jochen Friedrich <[email-masked]> ported to ARCH=powerpc and
+ * extended to be useful on any Power QUICC 1/2/2pro which have the same
+ * style of watchdog.
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/sched.h>
+#include <linux/irq.h>
+#include <linux/of.h>
+#include <asm/irq.h>
+#include <asm/io.h>
+#include <asm/prom.h>
+
+#include "pq_wdt.h"
+
+struct pq_wdt {
+	__be32 res0;
+	__be32 swcrr; /* System watchdog control register */
+	__be32 swcnr; /* System watchdog count register */
+	u8 res1[2];
+	__be16 swsrr; /* System watchdog service register */
+};
+
+static int wdt_timeout;
+static int wdt_freq;
+static struct pq_wdt __iomem *wdt_reg;
+static int wdt_scale;
+static int wdt_timerun;
+static DEFINE_SPINLOCK(wdt_spinlock);
+
+void pq_wdt_reset(void)
+{
+	if (!wdt_reg)
+		return;
+
+	spin_lock(&wdt_spinlock);
+	out_be16(&wdt_reg->swsrr, 0x556c);	/* write magic1 */
+	out_be16(&wdt_reg->swsrr, 0xaa39);	/* write magic2 */
+	spin_unlock(&wdt_spinlock);
+}
+EXPORT_SYMBOL(pq_wdt_reset);
+
+static void wdt_timer_func(unsigned long data);
+
+static struct timer_list wdt_timer =
+	TIMER_INITIALIZER(wdt_timer_func, 0, 0);
+
+void pq_wdt_stop_timer(void)
+{
+	spin_lock(&wdt_spinlock);
+	if (wdt_timerun)
+		del_timer(&wdt_timer);
+	wdt_timerun = 0;
+	spin_unlock(&wdt_spinlock);
+}
+EXPORT_SYMBOL(pq_wdt_stop_timer);
+
+void pq_wdt_install_timer(void)
+{
+	pq_wdt_reset();
+	spin_lock(&wdt_spinlock);
+	if (!wdt_timerun) {
+		wdt_timer.expires = jiffies + (HZ/2);
+		add_timer(&wdt_timer);
+	}
+	wdt_timerun = 1;
+	spin_unlock(&wdt_spinlock);
+}
+EXPORT_SYMBOL(pq_wdt_install_timer);
+
+static void wdt_timer_func(unsigned long data)
+{
+	pq_wdt_install_timer();
+}
+
+int pq_wdt_get_timeout(void)
+{
+	return wdt_timeout / wdt_freq;
+}
+EXPORT_SYMBOL(pq_wdt_get_timeout);
+
+static int wdt_readparam(void)
+{
+	u32 swcrr;
+
+	wdt_timeout = 0;
+
+	swcrr = in_be32(&wdt_reg->swcrr);
+
+	if (!(swcrr & SWCRR_SWEN)) {
+		printk(KERN_NOTICE "pq_wdt: wdt disabled (SWCRR: 0x%08X)\n",
+		       swcrr);
+		return -EINVAL;
+	}
+
+	pq_wdt_reset();
+
+	printk(KERN_NOTICE
+	       "pq_wdt: active wdt found (SWTC: 0x%04X, SWP: 0x%01X)\n",
+	       (swcrr >> 16), swcrr & 0x07);
+
+	wdt_timeout = (swcrr >> 16) & 0xFFFF;
+
+	if (!wdt_timeout)
+		wdt_timeout = 0xFFFF;
+
+	if (swcrr & SWCRR_SWPR)
+		wdt_timeout *= wdt_scale;
+
+	return 0;
+}
+
+int pq_wdt_setup(int value)
+{
+	if (!wdt_reg)
+		return -ENODEV;
+		
+	out_be32(&wdt_reg->swcrr, value);
+	return wdt_readparam();
+}
+EXPORT_SYMBOL(pq_wdt_setup);
+
+int __init pq_wdt_init_timer(void)
+{
+	if (wdt_reg) {
+		pq_wdt_install_timer();
+		return 0;
+	} else
+		return -ENODEV;
+}
+arch_initcall(pq_wdt_init_timer);
+
+int pq_wdt_init(void)
+{
+	struct device_node *np, *soc;
+	int ret;
+	const u32 *data;
+
+	if (wdt_reg)
+		return 0;
+
+	wdt_scale = 2048;
+	np = of_find_compatible_node(NULL, NULL, "fsl,pq1-wdt");
+	if (np == NULL)
+		np = of_find_compatible_node(NULL, NULL, "fsl,pq2-wdt");
+	if (np == NULL) {
+		np = of_find_compatible_node(NULL, NULL, "fsl,pq2pro-wdt");
+		wdt_scale = 65536;
+	}
+	if (np == NULL) {
+		printk(KERN_ERR "Could not find fsl,pq1/2/2pro-wdt node\n");
+		return -ENODEV;
+	}
+
+	soc = of_find_node_by_type(NULL, "soc");
+	if (!soc) {
+		printk(KERN_ERR "Could not find soc node\n");
+		ret = -ENODEV;
+		goto out;
+	}
+
+	data = of_get_property(soc, "bus-frequency", NULL);
+	if (!data) {
+		of_node_put(soc);
+		printk(KERN_ERR "Could not find bus-frequency in soc node\n");
+		ret = -ENODEV;
+		goto out;
+	}
+	of_node_put(soc);
+	wdt_freq = *data;
+
+	wdt_reg = of_iomap(np, 0);
+	if (wdt_reg == NULL) {
+		printk(KERN_ERR "Could not iomap wdt\n");
+		ret = -EINVAL;
+		goto out;
+	}
+
+	ret = wdt_readparam();
+out:
+	of_node_put(np);
+	return ret;
+}
+EXPORT_SYMBOL(pq_wdt_init);
diff --git a/arch/powerpc/sysdev/pq_wdt.h b/arch/powerpc/sysdev/pq_wdt.h
new file mode 100644
index 0000000..6f9e085
--- /dev/null
+++ b/arch/powerpc/sysdev/pq_wdt.h
@@ -0,0 +1,27 @@
+/*
+ * Author: Florian Schirmer <[email-masked]>
+ *
+ * 2002 (c) Florian Schirmer <[email-masked]> This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ * 2007 (c) Jochen Friedrich <[email-masked]> ported to ARCH=powerpc and
+ * extended to be useful on any Power QUICC 1/2/2pro which have the same
+ * style of watchdog.
+ */
+#ifndef _POWERPC_SYSDEV_PQ_WDT_H
+#define _POWERPC_SYSDEV_PQ_WDT_H
+
+#define SWCRR_SWEN 0x00000004 /* Watchdog Enable bit. */
+#define SWCRR_SWRI 0x00000002 /* Software Watchdog Reset/Interrupt Select bit.*/
+#define SWCRR_SWPR 0x00000001 /* Software Watchdog Counter Prescale bit. */
+
+extern int pq_wdt_get_timeout(void);
+extern void pq_wdt_reset(void);
+extern void pq_wdt_install_timer(void);
+extern void pq_wdt_stop_timer(void);
+extern int pq_wdt_setup(int);
+extern int pq_wdt_init(void);
+
+#endif				/* _POWERPC_SYSDEV_PQ_WDT_H */
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 2792bc1..79ee351 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -626,7 +626,18 @@ config MPC5200_WDT
 
 config 8xx_WDT
 	tristate "MPC8xx Watchdog Timer"
-	depends on 8xx
+	depends on 8xx && ! OF
+
+config PQ_WDT
+	tristate "Power QUICC Watchdog Timer"
+	depends on (8xx || PPC_82xx || PPC_83xx) && OF
+	default y
+	help
+	  Watchdog driver for Power QUICC 1/2/2pro style watchdog drivers.
+	  You should really select this unless your boot loader turns
+	  off the watchdog. As the watchdog is turned on by default and
+	  can be turned on/off only once after reboot, your board won't
+	  run otherwise. Say 'M' if unsure.
 
 config 83xx_WDT
 	tristate "MPC83xx Watchdog Timer"
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 7d9e573..bdcf3f3 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -96,6 +96,7 @@ obj-$(CONFIG_AR7_WDT) += ar7_wdt.o
 
 # POWERPC Architecture
 obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o
+obj-$(CONFIG_PQ_WDT) += pq_wdt.o
 obj-$(CONFIG_MPC5200_WDT) += mpc5200_wdt.o
 obj-$(CONFIG_83xx_WDT) += mpc83xx_wdt.o
 obj-$(CONFIG_MV64X60_WDT) += mv64x60_wdt.o
diff --git a/drivers/watchdog/pq_wdt.c b/drivers/watchdog/pq_wdt.c
new file mode 100644
index 0000000..e03daa3
--- /dev/null
+++ b/drivers/watchdog/pq_wdt.c
@@ -0,0 +1,222 @@
+/*
+ * pq_wdt.c - Power QUICC watchdog userspace interface
+ *
+ * Author: Florian Schirmer <[email-masked]>
+ *
+ * 2002 (c) Florian Schirmer <[email-masked]> This file is licensed under
+ * the terms of the GNU General Public License version 2. This program
+ * is licensed "as is" without any warranty of any kind, whether express
+ * or implied.
+ *
+ * 2007 (c) Jochen Friedrich <[email-masked]> renamed to pq_wdt.c and
+ * extended to be useful on any Power QUICC 1/2/2pro which have the same
+ * style of watchdog.
+ */
+
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/watchdog.h>
+#include <linux/of_platform.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <sysdev/pq_wdt.h>
+
+static unsigned long wdt_opened;
+static int wdt_status;
+
+static u16 timeout = 0xffff;
+module_param(timeout, ushort, 0);
+MODULE_PARM_DESC(timeout,
+	"Watchdog timeout in ticks. (0<timeout<65536, default=65535");
+
+static int reset = 1;
+module_param(reset, bool, 0);
+MODULE_PARM_DESC(reset,
+	"Watchdog Interrupt/Reset Mode. 0 = interrupt, 1 = reset (default)");
+
+static int prescale = 1;
+
+static void pq_wdt_handler_disable(void)
+{
+	pq_wdt_stop_timer();
+
+	pr_debug("pq_wdt: keep-alive handler deactivated\n");
+}
+
+static void pq_wdt_handler_enable(void)
+{
+	pq_wdt_install_timer();
+
+	pr_debug("pq_wdt: keep-alive handler activated\n");
+}
+
+static int pq_wdt_open(struct inode *inode, struct file *file)
+{
+	u32 tmp = SWCRR_SWEN;
+
+	if (test_and_set_bit(0, &wdt_opened))
+		return -EBUSY;
+
+	pq_wdt_reset();
+
+	if (prescale)
+		tmp |= SWCRR_SWPR;
+	if (reset)
+		tmp |= SWCRR_SWRI;
+
+	tmp |= timeout << 16;
+
+	if (pq_wdt_setup(tmp))
+		return -EBUSY;
+
+#if defined(CONFIG_WATCHDOG_NOWAYOUT)
+	/* Once we start the watchdog we can't stop it */
+	__module_get(THIS_MODULE);
+#endif
+
+	pq_wdt_handler_disable();
+
+	return nonseekable_open(inode, file);
+}
+
+static int pq_wdt_release(struct inode *inode, struct file *file)
+{
+	pq_wdt_reset();
+
+#if !defined(CONFIG_WATCHDOG_NOWAYOUT)
+	pq_wdt_handler_enable();
+#endif
+
+	clear_bit(0, &wdt_opened);
+
+	return 0;
+}
+
+static ssize_t pq_wdt_write(struct file *file, const char *data, size_t len,
+				loff_t *ppos)
+{
+	if (len)
+		pq_wdt_reset();
+
+	return len;
+}
+
+static int pq_wdt_ioctl(struct inode *inode, struct file *file,
+			    unsigned int cmd, unsigned long arg)
+{
+	int timeout;
+	static struct watchdog_info info = {
+		.options = WDIOF_KEEPALIVEPING,
+		.firmware_version = 0,
+		.identity = "PQ watchdog",
+	};
+
+	switch (cmd) {
+	case WDIOC_GETSUPPORT:
+		if (copy_to_user((void *)arg, &info, sizeof(info)))
+			return -EFAULT;
+		break;
+
+	case WDIOC_GETSTATUS:
+	case WDIOC_GETBOOTSTATUS:
+		if (put_user(wdt_status, (int *)arg))
+			return -EFAULT;
+		wdt_status &= ~WDIOF_KEEPALIVEPING;
+		break;
+
+	case WDIOC_GETTEMP:
+		return -EOPNOTSUPP;
+
+	case WDIOC_SETOPTIONS:
+		return -EOPNOTSUPP;
+
+	case WDIOC_KEEPALIVE:
+		pq_wdt_reset();
+		wdt_status |= WDIOF_KEEPALIVEPING;
+		break;
+
+	case WDIOC_SETTIMEOUT:
+		return -EOPNOTSUPP;
+
+	case WDIOC_GETTIMEOUT:
+		timeout = pq_wdt_get_timeout();
+		if (put_user(timeout, (int *)arg))
+			return -EFAULT;
+		break;
+
+	default:
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static const struct file_operations pq_wdt_fops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.write = pq_wdt_write,
+	.ioctl = pq_wdt_ioctl,
+	.open = pq_wdt_open,
+	.release = pq_wdt_release,
+};
+
+static struct miscdevice pq_wdt_miscdev = {
+	.minor = WATCHDOG_MINOR,
+	.name = "watchdog",
+	.fops = &pq_wdt_fops,
+};
+
+static int __devinit pq_wdt_probe(struct of_device *op, const struct of_device_id *match)
+{
+	int ret;
+
+	ret = pq_wdt_init();
+	if (ret)
+		return ret;
+
+	pq_wdt_handler_enable();
+
+	return misc_register(&pq_wdt_miscdev);
+}
+
+static int __devexit pq_wdt_remove(struct of_device *op)
+{
+	misc_deregister(&pq_wdt_miscdev);
+	return 0;
+}
+
+static struct of_device_id pq_wdt_match[] = {
+	{ .compatible = "fsl,pq1-wdt", },
+	{ .compatible = "fsl,pq2-wdt", },
+	{ .compatible = "fsl,pq2pro-wdt", },
+	{},
+};
+
+static struct of_platform_driver pq_wdt_driver = {
+	.owner		= THIS_MODULE,
+	.name		= "pq-wdt",
+	.match_table	= pq_wdt_match,
+	.probe		= pq_wdt_probe,
+	.remove		= pq_wdt_remove,
+};
+
+static int __init pq_wdt_moduleinit(void)
+{
+	return of_register_platform_driver(&pq_wdt_driver);
+}
+
+static void __exit pq_wdt_moduleexit(void)
+{
+	of_unregister_platform_driver(&pq_wdt_driver);
+}
+
+module_init(pq_wdt_moduleinit);
+module_exit(pq_wdt_moduleexit);
+
+MODULE_AUTHOR("Florian Schirmer <[email-masked]>");
+MODULE_DESCRIPTION("PQ watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
-- 
1.5.3.5